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/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..c849a9f78e5 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------------------------------------------- +# 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/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 + && apt-get -y install --no-install-recommends git procps lsb-release \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +# Switch back to dialog for any ad-hoc use of apt-get +ENV DEBIAN_FRONTEND=dialog diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..eded2d1bdec --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// See https://aka.ms/vscode-remote/devcontainer.json for format details. +{ + "name": ".NET Core 6.0, including pwsh (Ubuntu 18.04)", + "dockerFile": "Dockerfile", + + "workspaceMount": "source=${localWorkspaceFolder},target=/PowerShell,type=bind", + "workspaceFolder": "/PowerShell", + + // 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/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..57d2f6c6c3e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,213 @@ +# EditorConfig is awesome: https://EditorConfig.org +# .NET coding convention settings for EditorConfig +# 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 +# https://github.com/dotnet/roslyn/blob/master/.editorconfig + +# Top-most EditorConfig file +root = true + +[*] +charset = utf-8 +# indent_size intentionally not specified in this section +indent_style = space +insert_final_newline = true + +# Source code +[*.{cs,ps1,psd1,psm1}] +indent_size = 4 + +# Shell scripts +[*.sh] +end_of_line = lf +indent_size = 4 + +# Xml project files +[*.{csproj,resx,ps1xml}] +indent_size = 2 + +# Data serialization +[*.{json,yaml,yml}] +indent_size = 2 + +# Markdown +[*.md] +indent_size = 2 + +# Xml files +[*.{resx,ruleset,stylecop,xml,xsd,xsl}] +indent_size = 2 + +# Xml config files +[*.{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 + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# Static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected + +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# Internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style + +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +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 +dotnet_style_readonly_field = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_auto_properties = true:suggestion +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] + +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false +csharp_prefer_braces = true:silent + +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_range_operator = false:none +csharp_style_prefer_index_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +csharp_using_directive_placement = outside_namespace:suggestion + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Only use var when it's obvious what the variable type is +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_local_functions = true:silent + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true diff --git a/.gitattributes b/.gitattributes index bb4da29acd3..c9033dc798a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +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 9a778e4b4cd..14569b81924 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,82 +1,68 @@ # https://help.github.com/articles/about-codeowners/ # Areas are not limited to the filters defined in this file -# First Lets start with areas with no filters or paths +# First, let's start with areas with no filters or paths -# Area: Performance -# @lzybkr @adityapatwardhan +# Default +* @PowerShell/powershell-maintainers -# Area: Portability -# @BrucePay @JamesWTruher +# Area: Performance +# @adityapatwardhan # Area: Security -# @TravisEz13 @PaulHigin - -# Area: Documentation -.github/ @joeyaiello @TravisEz13 - -# Area: Test -# @JamesWTruher @TravisEz13 @adityapatwardhan +src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @seeminglyscience -# Area: Cmdlets Core -# @JamesWTruher @SteveL-MSFT @anmenaga @chunqingchen +# 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 +# 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 +# Area: Cmdlets Management +# src/Microsoft.PowerShell.Commands.Management/ @daxian-dbw @adityapatwardhan -# Area: CmdLets Utility -src/Microsoft.PowerShell.Commands.Utility/ @JamesWTruher @dantraMSFT @PaulHigin +# Area: Utility Cmdlets +# src/Microsoft.PowerShell.Commands.Utility/ # Area: Console -src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw @lzybkr @anmenaga - -# Area: Demo -demos/ @joeyaiello @SteveL-MSFT @HemantMahawar +# src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw # Area: DSC -src/System.Management.Automation/DscSupport @TravisEz13 @dantraMSFT +# src/System.Management.Automation/DscSupport @TravisEz13 @SteveL-MSFT -# Area: engine -src/System.Management.Automation/engine @daxian-dbw @lzybkr @BrucePay +# Area: Engine +# src/System.Management.Automation/engine @daxian-dbw # Area: Debugging # Must be below engine to override -src/System.Management.Automation/engine/debugger/ @BrucePay @dantraMSFT @PaulHigin +# src/System.Management.Automation/engine/debugger/ -# Area: help -src/System.Management.Automation/help @Francisco-Gamino @adityapatwardhan +# Area: Help +src/System.Management.Automation/help @adityapatwardhan @daxian-dbw # Area: Intellisense -# @daxian-dbw @lzybkr @charub +# @daxian-dbw # Area: Language -src/System.Management.Automation/engine/parser @daxian-dbw @vors @lzybkr @BrucePay +src/System.Management.Automation/engine/parser @daxian-dbw @seeminglyscience # Area: Providers -src/System.Management.Automation/namespaces @BrucePay @anmenaga - -# Area: PSReadLine -src/Microsoft.PowerShell.PSReadLine @lzybkr @charub +# src/System.Management.Automation/namespaces # Area: Remoting -src/System.Management.Automation/engine/remoting @dantraMSFT @mirichmo @PaulHigin - -# Area: Side-By-Side -# @mirichmo @charub +src/System.Management.Automation/engine/remoting @daxian-dbw @TravisEz13 # Areas: Build # Must be last -*.config @daxian-dbw @TravisEz13 @adityapatwardhan -*.props @daxian-dbw @TravisEz13 @adityapatwardhan -*.yml @daxian-dbw @TravisEz13 @adityapatwardhan -*.csproj @daxian-dbw @TravisEz13 @adityapatwardhan -build.* @daxian-dbw @TravisEz13 @adityapatwardhan -tools/ @daxian-dbw @TravisEz13 @adityapatwardhan -docker/ @daxian-dbw @TravisEz13 @adityapatwardhan +*.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 7d00be5c8f1..35eab8c9b5b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,79 +1,96 @@ # 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. +We welcome and appreciate contributions from the community! -## Intro to Git and GitHub +There are many ways to become involved with PowerShell including: -* 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 +- [Contributing to Documentation](#contributing-to-documentation) +- [Contributing to Issues](#contributing-to-issues) +- [Contributing to Code](#contributing-to-code) -## Quick Start Checklist +Please read the rest of this document to ensure a smooth contribution process. -* Review the [Contribution License Agreement][CLA] requirement. -* Get familiar with the [PowerShell repository](../docs/git). +## Contributing to Documentation -## Contributing to Issues +Contributing to the docs is an excellent way to get started with the process of making open source contributions with minimal technical skill required. -* 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), - 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. +Please see the [Contributor Guide in `MicrosoftDocs/PowerShell-Docs`](https://aka.ms/PSDocsContributor). -## Contributing to Documentation +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 -### Contributing to documentation related to PowerShell +If you made a change to an existing cmdlet and would like to update the documentation using PlatyPS, +here are the quick steps: -Please see the [Contributor Guide in `PowerShell/PowerShell-Docs`](https://github.com/PowerShell/PowerShell-Docs/blob/staging/CONTRIBUTING.md). +1. Install +`PlatyPS` +if you don't have it - +`Install-Module PlatyPS`. +1. Clone the +[`MicrosoftDocs/PowerShell-Docs`](https://github.com/MicrosoftDocs/PowerShell-Docs) +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 +`PowerShell-Docs/reference///.md` +(Ex. `PowerShell-Docs/reference/7/Microsoft.PowerShell.Utility/Select-String.md`) +1. Run +`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 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. - -#### Spellchecking documentation +* Otherwise, these issues should be treated like any other issue in this repository. -Documentation are spellchecked. We make use of 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). +### Spell checking documentation -To run the spellchecker, follow the steps as follows: +Documentation is spellchecked. We use the +[textlint](https://github.com/textlint/textlint/wiki/Collection-of-textlint-rule) command-line tool, +which can be run in interactive mode to correct typos. -* 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` -* if the `.spelling` file is updated, commit and push it +To run the spell checker, follow these steps: -## Contributing to Code +* 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. -### Code Editor +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). -You should use the multi-platform [Visual Studio Code (VS Code)][use-vscode-editor]. +### Checking links in documentation -### Building and testing +Documentation is link-checked. We make use of the +`markdown-link-check` command-line tool, +which can be run to see if any links are dead. -#### Building PowerShell +To run the link-checker, follow these steps: -Please see [Building PowerShell](../README.md#building-the-repository). +* 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 {} \;` -#### Testing PowerShell +## Contributing to Issues -Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running-tests-outside-of-ci] on how to test you 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 @@ -94,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 @@ -118,25 +188,25 @@ 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. An issue title is to briefly describe what is wrong, while a PR title is to briefly describe what is changed. 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, - including a summary about your changes in the PR description. - The description is used to create change logs, + include a summary about your changes in the PR description. + 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 PR description (e.g. ```Fix #11```). + 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: @@ -148,7 +218,34 @@ 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 `.cs` files use the copyright header with empty line after it: + + ```c# + // Copyright (c) Microsoft Corporation. + // Licensed under the MIT License. + + ``` + + * For `.ps1` and `.psm1` files use the copyright header with empty line after it: + + ```powershell + # Copyright (c) Microsoft Corporation. + # Licensed under the MIT License. + + ``` + +* If your change adds a new module manifest (.psd1 file), ensure that: + + ```powershell + Author = "PowerShell" + Company = "Microsoft Corporation" + Copyright = "Copyright (c) Microsoft Corporation." + ``` + + is at the top. ### Pull Request - Work in Progress @@ -162,12 +259,16 @@ Additional references: * Make sure you follow the [Common Engineering Practices](#common-engineering-practices) and [testing guidelines](../docs/testing-guidelines/testing-guidelines.md). * After submitting your pull request, - our [CI system (Travis CI and AppVeyor)][ci-system] + 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. 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. + 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 1. The PR *author* creates a pull request from a fork. @@ -184,22 +285,22 @@ 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. If you rewrite the history in the pull request, review could be much slower. - The PR is likely to be squashed on merge to master by the *assignee*. + The PR is likely to be squash-merged to master by the *assignee*. 1. *Reviewers* are anyone who wants to contribute. They are responsible for ensuring the code: addresses the issue being fixed, does not create new issues (functional, performance, reliability, or security), and implements proper design. *Reviewers* should use the `Review changes` drop down to indicate they are done with their review. - `Request changes` if you believe the PR merge should be blocked if your feedback is not addressed, - `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](../docs/dev-process/coding-guidelines.md), however, - after the PR has been approved, it is generally _not_ recommended to focus on formatting issues unless they go against the [Coding Guidelines](../docs/dev-process/coding-guidelines.md). + 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]. Non-critical late feedback (after PR has been approved) can be submitted as a new issue or new pull request from the *reviewer*. -1. *Assignee* 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. +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. Once the PR has been approved and the CI system is passing, the *assignee* will merge the PR after giving one business day for any critical feedback. For more information on the PowerShell Maintainers' process, see the [documentation](../docs/maintainers). @@ -216,27 +317,27 @@ 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](../docs/dev-process/breaking-change-contract.md). +please pay attention to these that can affect the [Public Contract][breaking-changes-contract]. For example, changing PowerShell parameters, APIs, or protocols break the public contract. Before making changes to the code, -first review the [breaking changes contract](../docs/dev-process/breaking-change-contract.md) +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)](https://github.com/PowerShell/PowerShell-RFC) process. +please follow the [PowerShell Request for Comments (RFC)][rfc-process] process. -## Common Engineering Practices +### Common Engineering Practices -Other than the guidelines for ([coding](../docs/dev-process/coding-guidelines.md), -the [RFC process](https://github.com/PowerShell/PowerShell-RFC) for design, -[documentation](#contributing-to-documentation) and [testing](../docs/testing-guidelines/testing-guidelines.md)) discussed above, +Other than the guidelines for [coding][coding-guidelines], +the [RFC process][rfc-process] for design, +[documentation](#contributing-to-documentation) and [testing](../docs/testing-guidelines/testing-guidelines.md) discussed above, we encourage contributors to follow these common engineering practices: * Format commit messages following these guidelines: @@ -270,7 +371,7 @@ Using semantic line feeds (breaks that separate ideas) is also appropriate, as is using Markdown syntax. ``` -* These are based on Tim Pope's [guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), +* These are based on Tim Pope's [guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), Git SCM [submitting patches](https://git.kernel.org/cgit/git/git.git/tree/Documentation/SubmittingPatches), Brandon Rhodes' [semantic linefeeds][], and John Gruber's [Markdown syntax](https://daringfireball.net/projects/markdown/syntax). @@ -278,31 +379,37 @@ is also appropriate, as is using Markdown syntax. If you find code that you think is a good fit to add to PowerShell, file an issue and start a discussion before proceeding. * Create and/or update tests when making code changes. -* Run tests and ensure they are passing before pull request. +* Run tests and ensure they are passing before opening a pull request. * All pull requests **must** pass CI systems before they can be approved. * Avoid making big pull requests. 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 could [sign a Microsoft Contribution Licensing Agreement (CLA)](https://cla.microsoft.com/) ahead of time. -If you've already contributed to PowerShell repositories in the past, congratulations! +you should sign the Microsoft [Contributor License Agreement (CLA)](https://cla.microsoft.com/) ahead of time. +If you've already contributed to PowerShell or Microsoft repositories in the past, congratulations! You've already completed this step. This a one-time requirement for the PowerShell project. Signing the CLA process is simple and can be done in less than a minute. You don't have to do this up-front. You can simply clone, fork, and submit your pull request as usual. -When your pull request is created, it is classified by a CLA bot. -If the change is trivial, it's classified as `cla-required`. -Once you sign a CLA, all your existing and future pull requests will be labeled as `cla-signed`. +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`. + +## 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. -[testing-guidelines]: ../docs/testing-guidelines/testing-guidelines.md [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]: ../docs/maintainers/issue-management.md#Security-Vulnerabilities -[governance]: ../docs/community/governance.md +[vuln-reporting]: ./SECURITY.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/ @@ -312,9 +419,13 @@ Once you sign a CLA, all your existing and future pull requests will be labeled [contribute-issues]: #contributing-to-issues [open-issue]: https://github.com/PowerShell/PowerShell/issues [up-for-grabs]: https://github.com/powershell/powershell/issues?q=is%3Aopen+is%3Aissue+label%3AUp-for-Grabs -[semantic linefeeds]: http://rhodesmill.org/brandon/2012/one-sentence-per-line/ +[semantic linefeeds]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ [PowerShell-Docs]: https://github.com/powershell/powershell-docs/ -[use-vscode-editor]: ../docs/learning-powershell/using-vscode.md#editing-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 -[ci-system]: ../docs/testing-guidelines/testing-guidelines.md#ci-system +[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.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index fc111db035e..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,46 +0,0 @@ - - -Steps to reproduce ------------------- - -```powershell - -``` - -Expected behavior ------------------ - -```none - -``` - -Actual behavior ---------------- - -```none - -``` - -Environment data ----------------- - - - -```powershell -> $PSVersionTable - -``` 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/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.yaml b/.github/ISSUE_TEMPLATE/Release_Process.yaml new file mode 100644 index 00000000000..7e8d6282db1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Release_Process.yaml @@ -0,0 +1,41 @@ +name: Release Process +description: Maintainers Only - Release Process +title: "Release Process for v7.x.x" +labels: [Issue-Meta, Needs-Triage] +body: +- type: markdown + attributes: + value: > + This template is for maintainers to create an issues to track the release process. + Please **only** use this template if you are a maintainer. +- type: textarea + attributes: + label: Checklist + value: | + - [ ] Verify that [`PowerShell-Native`](https://github.com/PowerShell/PowerShell-Native) has been updated / released as needed. + - [ ] Check for `PowerShellGet` and `PackageManagement` release plans. + - [ ] Start process to sync Azure DevOps artifacts feed such as modules and NuGet packages. + - [ ] Create a private branch named `release/v6.x.x` in Azure DevOps repository. + All release related changes should happen in this branch. + - [ ] Prepare packages + - [ ] Kick off coordinated build. + - [ ] Kick off Release pipeline. + - *These tasks are orchestrated by the release pipeline, but here as status to the community.* + - [ ] Prepare packages + - [ ] Sign the RPM package. + - [ ] Install and verify the packages. + - [ ] Trigger the docker staging builds (signing must be done). + - [ ] Create the release tag and push the tag to `PowerShell/PowerShell` repository. + - [ ] Run tests on all supported Linux distributions and publish results. + - [ ] Update documentation, and scripts. + - [ ] Update [CHANGELOG.md](../../CHANGELOG.md) with the finalized change log draft. + - [ ] Stage a PR to master to update other documents and + scripts to use the new package names, links, and `metadata.json`. + - [ ] For preview releases, + merge the release branch to GitHub `master` with a merge commit. + - [ ] For non-preview releases, + make sure all changes are either already in master or have a PR open. + - [ ] Delete the release branch. + - [ ] Trigger the Docker image release. + - [ ] Retain builds. + - [ ] Update https://github.com/dotnet/dotnet-docker/tree/master/3.0/sdk with new version and SHA hashes for global tool. NOTE: this link is broken! diff --git a/.github/ISSUE_TEMPLATE/WG_member_request.yaml b/.github/ISSUE_TEMPLATE/WG_member_request.yaml new file mode 100644 index 00000000000..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 new file mode 100644 index 00000000000..973921cb24a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Windows 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/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/Images/GitHub-PR.png b/.github/Images/GitHub-PR.png index 80bb77e4830..1ae852aecd5 100644 Binary files a/.github/Images/GitHub-PR.png and b/.github/Images/GitHub-PR.png differ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b76cd0cb22f..27089847987 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,18 +1,31 @@ -## PR Summary + - +# PR Summary -## PR Checklist + + +## PR Context -Note: Please mark anything not applicable to this PR `NA`. + + +## 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) -- [ ] User facing [Documentation needed](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) - - [ ] Issue filed - Issue link: -- [ ] [Change is not breaking](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#making-breaking-changes) -- [ ] [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) - - [ ] [Add `[feature]` if the change is significant or affects feature tests](https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/testing-guidelines.md#requesting-additional-tests-for-a-pr) -- [ ] 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:` to the beginning of the title and remove the prefix when the PR is ready. +- [ ] [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. 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/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: +- **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) diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000000..797f7003851 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,39 @@ + + +## Security + +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 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 new file mode 100644 index 00000000000..6acedb28d27 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,13 @@ +# PowerShell Support + +If you have any problems, please consult the [known issues][], developer [FAQ][], and [GitHub issues][]. +If you do not see your problem captured, please file a [new issue][] and follow the provided template. +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://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://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/issue_label_bot.yaml b/.github/issue_label_bot.yaml new file mode 100644 index 00000000000..4374b1996b5 --- /dev/null +++ b/.github/issue_label_bot.yaml @@ -0,0 +1,4 @@ +label-alias: + bug: 'Issue-Bug' + feature_request: 'Issue-Enhancement' + question: 'Issue-Question' 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/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 5900f796f96..48556cf1b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin/ obj/ +.ionide/ project.lock.json *-tests.xml /debug/ @@ -10,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 @@ -24,6 +26,9 @@ dotnet-uninstall-debian-packages.sh # Visual Studio IDE directory .vs/ +# VSCode directories that are not at the repository root +/**/.vscode/ + # Project Rider IDE files .idea.powershell/ @@ -31,10 +36,12 @@ dotnet-uninstall-debian-packages.sh *.exe *.msi *.appx +*.msix # Ignore binaries and symbols *.pdb *.dll +*.wixpdb # Ignore packages *.deb @@ -45,9 +52,6 @@ dotnet-uninstall-debian-packages.sh *.nupkg *.AppImage -# ignore the telemetry semaphore file -DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY - # default location for produced nuget packages /nuget-artifacts @@ -59,9 +63,64 @@ gen # macOS .DS_Store +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +.AppleDouble +.LSOverride # TestsResults TestsResults*.xml +ParallelXUnitResults.xml +xUnitResults.xml + +# Attack Surface Analyzer results +asa-results/ # Resharper settings PowerShell.sln.DotSettings.user +*.msp +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/.gitmodules b/.gitmodules deleted file mode 100644 index c677d18caff..00000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "src/libpsl-native/test/googletest"] - path = src/libpsl-native/test/googletest - url = https://github.com/google/googletest.git - ignore = dirty 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/.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 66e5cc76791..cc711f0aa5a 100644 --- a/.spelling +++ b/.spelling @@ -3,1095 +3,1755 @@ # global dictionary is at the start, file overrides afterwards # one word per line, to define a file override use ' - filename' # where filename is relative to this configuration file - -#region Global Dictionary +-title +0-powershell-crossplatform +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 -ActiveDirectory +adamdriscoll +add-localgroupmember +add-ons +AddType.cs +adelton +adhoc aditya adityapatwardhan +ADOPTERS.md +aetos382 +aiello +Aishat452 +al-cheb +alepauly +alexandair +alexjordan6 +alpha.10 +alpha.11 +alpha.12 +alpha.13 +alpha.14 alpha.15 +alpha.16 +alpha.17 +alpha.18 alpha.7 alpha.8 alpha.9 -AlternateStream +alternatestream +alvarodelvalle amd64 -analyzing -AppImage -AppVeyor +ananya26-vishnoi +andschwa +anmenaga +api +apis +APIScan +appimage +applocker +appveyor +appx +ArchitectureSensitiveAttribute +args argumentlist arm32 arm64 -artifact -artifacts -ASP.NET -AssemblyLoadContext +asp.net +ast.cs +assemblyloadcontext +AssemblyInfo +assessibility +AtariDreams authenticode authenticodesignature -behavior -behaviors +azdevops +AzFileCopy +AzureFileCopy +azurerm.netcore.preview +azurerm.profile.netcore.preview +azurerm.resources.netcore.preview +backgrounded +backgrounding +backport +beatcracker +bergmeister +beta.1 +beta.2 +beta.3 +beta.4 +beta.5 +beta.6 beta.7 -booleans -catalog -cataloged +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 -chucklu +ChuckieChen945 +ChrisLGardner +chrullrich cimsession cimsupport +ci.psm1 +cgmanifest +classlib +clear-itemproperty +cloudydino +cls cmake cmd cmdlet +cmdletproviderclasses cmdlets +codebase codecov.io -color -colors +codecoverage.zip +codefactor +CodeFormatter +codeowner +codepage +commanddiscovery +CommandInvocationIntrinsics +commandsearch +CommandSearcher comobject +Compiler.cs +composability +computerinfo +ComRuntimeHelpers.cs config -ConsoleHost -ConvertFrom-Json +connect-pssession +consolehost +consolehostrunspaceinit +consolehostuserinterface +consolelineoutput +contenttype +convertfrom-csv +convertfrom-json +convertfrom-sddlstring +convertfrom-securestring +convertfrom-stringdata +convertto-csv +convertto-html +convertto-json +convertto-securestring convertto-xml -CoreCLR -CoreFX +copy-itemproperty +CopyItem.Tests.ps1 +corbob +coreclr +coreconsolehost +corefx +CorePsAssemblyLoadContext.cs +coveralls.exe +coveralls.io. +coveralls.net +CreateFile +CreateFileW credssp +cron crontab crossgen -cs -CsPhysicallyInstalledMemory +crossgen'ing +crossplatform +csharp +csmacnz +csphysicallyinstalledmemory +ctrl +CurrentCulture +CustomShellCommands.cs +DamirAinullin +DarylGraves +darquewarrior +darwinjs +DateTime +DateTime.UnixEpoch +daxian-dbw +dayofweek +dchristian3188 +ddwr debughandler -DevOps -DockerFile -DockerFiles -DotNetCore -DottedScopes -eBook +dee-see +defaultRefAssemblies +dependabot +deps +deserialization +deserialize +deserialized +deserializing +dest +dest.txt +dev +devblackops +devcontainer +deviceguard +devlead +devops +dgoldman-msft +Dictionary.TryAdd +diddledan +disable-localuser +disable-psbreakpoint +disable-pstrace +disable-pswsmancombinedtrace +disable-runspacedebug +disable-wsmantrace +disconnect-pssession +displaydataquery +Distribution_Request.md +distro +distros +dkaszews +dll +DllImport +dlls +dlwyatt +dockerbasedbuild +dockerfile +dockerfiles +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 +edyoung +enable-localuser +enable-psbreakpoint +enable-pstrace +enable-pswsmancombinedtrace +enable-runspacedebug +enable-wsmantrace +encodings +endian +enter-pshostprocess +enter-pssession enum -env +enums +Environment.NewLine ergo3114 +errorrecord +etl +eugenesmlv +EventLogLogProvider +excludeversion exe -favor -favorite +executables +executionpolicy +exit-pshostprocess +exit-pssession +export-binarymilog +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 +fullclr +FunctionInfo functionprovider -Get-Acl -Get-AuthenticodeSignature -Get-ChildItem -Get-ComputerInfo -Get-PSSessionConfiguration -Get-WinEvent +FunctionTable +fxdependent +gabrielsroka +GAC_Arm64 +gamified +gc.regions.xml +Generic.SortedList +get-apachemodule +get-apachevhost +get-childitem +get-cimassociatedinstance +get-cimclass +get-ciminstance +get-computerinfo +get-cronjob +get-eventsubscriber +Get-ExperimentalFeature +get-filehash +get-formatdata +get-installedmodule +get-installedscript +get-itemproperty +get-itempropertyvalue +get-localgroup +get-localgroupmember +get-localuser +get-logproperties +get-packageprovider +get-packagesource +get-psbreakpoint +get-pscallstack +get-pshostprocessinfo +get-psprovider +get-psreadlinekeyhandler +get-psreadlineoption +get-psrepository +get-pssession +get-pssessioncapability +get-runspacedebug +get-systemdjournal +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 -HttpBin's +httpbin's +https +hubuk +hvitved +i3arnon +i.e. +ico +idera +IDictionary ifdef'ed +iisresetme ilya -init -Invoke-RestMethod -Invoke-WebRequest +import-binarymilog +import-clixml +import-csv +import-localizeddata +import-packageprovider +import-powershelldatafile +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 +iscoreclr +isError isnot +itemtype +itpro +jackdcasey +jameswtruher +Jawz84 +jazzdelightsme +jeffbi +jellyfrog +jen +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 +kiazhi +kirkmunro +kittholland korygill -labeled -linux-x64 -lockfile -macOS +kpis +krishnayalavarthi +kvprasoon +kwiknick +kwkam +kylesferrazza +labelling +LabhanshAgrawal +lastwritetime +launch.json +ldspits +lee303 +Leonhardt +Libera.Chat +libicu +LibraryImport +libpsl +libpsl-native +libunwind8 +license.rtf +linux +locationglobber +lockdown +loopback +lossless +louistio +LucaFilipozzi +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 -Microsoft.PowerShell.Archive +meir017 +memberresolution +Menagarishvili +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 microsoft.powershell.consolehost +microsoft.powershell.core microsoft.powershell.coreclr.assemblyloadcontext microsoft.powershell.coreclr.eventing +microsoft.powershell.diagnostics +microsoft.powershell.localaccounts +microsoft.powershell.management +microsoft.powershell.markdownrender microsoft.powershell.psreadline microsoft.powershell.security +microsoft.powershell.utility +Microsoft.Security.Extensions +Microsoft.WSMan microsoft.wsman.management microsoft.wsman.runtime -MSBuild -MS-PSRP -Multipart +mikeTWC1984 +mirichmo +mjanko5 +mkdir +mkht +mklement0 +ModuleCmdletBase.cs +MohiTheFish +Molkree +move-itemproperty +ms-psrp +msbuild +msftrncs +mshexpression.cs +mshsnapinloadunload +msi +multiline +multipart +mv +mvps +mwrock myget namedpipe -New-PSSessionOption -New-PSTransportOption +nameof +NameObscurerTelemetryInitializer +namespace +nano +nanoserver +NativeCommandProcessor.cs +NativeCultureResolver +nativeexecution +net5.0 +net10.0 +netcoreapp5.0 +netip.ps1. +netstandard.dll +new-apachevhost +new-ciminstance +new-cimsessionoption +new-cronjob +New-DockerTestBuild +new-guid +new-itemproperty +new-localgroup +new-localuser +new-modulemanifest +new-psrolecapabilityfile +new-pssession +new-pssessionconfigurationfile +new-pssessionoption +new-pstransportoption +new-scriptfileinfo +new-temporaryfile +new-timespan +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 nuget -NuGet -NUnit -nunit +nuget.config +nuget.exe +nugetfeed +Nullable +numberbytes +numberOfPowershellRefAssemblies nupkg -nuspec -OpenCover -OpenSSH -OpenSUSE -PackageManagement -param -parameterized +oauth +object.ReferenceEquals +offthewoll +oising +omi +omnisharp +OneDrive +oneget.org +OneScripter +opencover +opencover.zip +openssh +openssl +opensuse +oss +OutputType +p1 +packagemanagement +PackageVersion +parameshbabu +parameterbinderbase +parameterbindercontroller +parameterbinding +ParenExpression +ParseError.ToString +Path.Join +pathresolution +PathResolvedToMultiple +patochun patwardhan +paulhigin +pawamoy +payette +perf +perfview +perfview.exe +peter-evans +petseral +PingPathCommand.cs +pinvoke +pinvokes +plaintext +pluggable +pluralsight +poshcode +pougetat +powerbi +powercode powershell -PowerShell -PowerShell.Core.Instrumentation +powershell-unix +powershell.6 +powershell.com +PowerShell.Common.props +powershell.core.instrumentation powershell.exe -PowerShellGet +powershell.org +powershellcore +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 +prepend +preprocessor +preview.1 +preview.2 +preview.2.22153.17 +preview.3 +preview.4 +preview.5 +preview.5.20279.10 +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 -ProgramFiles -ProxyCommand +prototyyppi +providername +proxycommand ps1 -PSCredential -PSObject +ps1xml +pscore +pscredential +psd1 +psdrive +psdriveinfo +pseudoparameterbinder +psgallery +PSGalleryModules +psm1 +psobject psobjects +psoptions.json psproxyjobs -PSReadline +psreadline +psresourceget psrp.windows -PSSessionConfiguration +psscriptanalyzer +pssessionconfiguration +pssnapinloadunload +pssnapins +psversion +psversiontable +PSWindowsPowerShellCompatibility +PublishReadyToRun +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-EngineEvent -Register-PSSessionConfiguration +register-argumentcompleter +register-cimindicationevent +register-engineevent +register-objectevent +register-packagesource +register-psrepository registryprovider +relationlink +releaseTools.psm1 +RemoteSessionNamedPipe +remotesigned remoting -ResGen +remove-ciminstance +remove-cronjob +remove-itemproperty +remove-localgroup +remove-localgroupmember +remove-localuser +remove-psbreakpoint +remove-psreadlinekeyhandler +remove-pssession +remove-typedata +remove-wsmaninstance +rename-itemproperty +rename-localgroup +rename-localuser +renehernandez +reparse +replicaJunction +repo +reportgenerator +resgen +responseheaders +REST +rest.ps1 +restart-apachehttpserver resx -RFCs +Rethrow +reynoldsbd +richardszalay +Rin +rjmholt +rkeithhill +rkitover +robo210 +ronn +rpalo +rpolley runas -Runspace +runspace +runspaceinit +runspaces +runtime runtimes +Ryan-Hutchison-USAF +SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation +Saancreed +SafeRegistryHandle +sample-dotnet1 +sample-dotnet2 sarithsutha +sarthakmalik savehelp sazonov -SecureString +sba923 +schvartzman +schwartzmeyer +scriptblock +securestring +seemethere +select-xml +SemanticChecks +semver +serverless +sessionid +sessionstate sessionstatecontainer sessionstateitem -Set-Acl -Set-AuthenticodeSignature -Set-ExecutionPolicy -ShouldBeErrorId +set-ciminstance +set-itemproperty +set-localgroup +set-localuser +set-logproperties +set-packagesource +set-psbreakpoint +set-psdebug +set-psreadlinekeyhandler +set-psreadlineoption +set-psrepository +set-strictmode +set-wsmaninstance +set-wsmanquickconfig +sethvs +setversionvariables +sha256 +ShaydeNofziger +shellexecute +shouldbeerrorid showcommandinfo -Singleline +Shriram0908 +silijon +simonwahlin +singleline +sles15 +smes +snapcraft snapin -ssh -StackOverflow +snover +sometext +SortedList +source.txt +spongemike2 +src +ss64.com +st0le +stackoverflow +stanzilla +start-codecoveragerun +start-pspester +stdin +stevel-msft +StevenLiekens +stevend811 +stknohg +strawgate +streamdescribecifeaturescenariodescribecontextitcontextcontextbeforeallafterallbeforeeachaftereachshould +StrictMode +string.split +stringbuilder +stuntguy3000 +StyleCop +subfolder submodule submodules sudo +superproject +svg +swarfegagit +SwitchParameter +sxs +sydneyhsmith +symlink +symlinks +syscall +syslog +System.IO.Packaging +System.InvalidOperationException +system.manage system.management.automation -TeamCity +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 +test1.txt +test2.txt +testcase +testdrive +TestPathCommand.cs +tests.zip +tgz +theflyingcorpse thenewstellw +thezim +thlac +ThomasNieto +threadjob +ThreadStatic throttlelimit -toolset +throw-testcasesitmockdescribe +ThrowExceptionForHR +timcurwick +timestamp +timothywlewis +TKey +tobias +tokenizer.cs +tokenizing +tomconte +tommymaynard toolchain -TraceSource -Unregister-Event -Unregister-PSSessionConfiguration +toolset +tracesource +travisez13 +travisty +trossr32 +truher +TSAUpload +turbedi +TValue +tylerleonhardt +typecataloggen +typeconversion +typegen +typematch +t_ +ubuntu +ubuntu22.04 +uint +un-versioned +unicode +UnixSocket +unregister-event +unregister-packagesource +unregister-psrepository +unregister-pssessionconfiguration +unregistering +untracked +unvalidated +UpdateDotnetRuntime.ps1 +update-formatdata +update-modulemanifest +update-scriptfileinfo +update-typedata +uri +urizen-source +urls +UseMU +userdata +uservoice +utf-8 +utf8 +utf8nobom utils utils.cs -vscode -walkthrough -WebCmdlets -wget -whitespace -Win32 -win7 -WinRM -WiX -writingpestertests.md -WSMan -wsmansessionoption.cs -xUnit -#endregion - -#region ./tools/install-powershell.readme.md Overrides - - ./tools/install-powershell.readme.md -includeide -sed -#endregion - -#region CHANGELOG.md Overrides - - CHANGELOG.md -_ -_Jobs --Command --Exclude --File --Include --Title -0xfeeddeadbeef -acceptance -alpha.10 -alpha.11 -alpha.12 -alpha.13 -alpha.14 -alpha.16 -alpha.17 -alpha.18 -args -Bhaal22 -behavioral -bergmeister -beta.1 -beta.2 -beta.3 -beta.4 -beta.5 -beta.6 -beta.8 -beta.9 -binding -bool -CDXML -charset -CI -cleanup -CodeMethod -CodeOwner -codepage -CommandNotFoundException -ContentType -ConvertTo-Html -CoreConsoleHost -crossgen'ing -DarwinJS -dchristian3188 -DdWr -deserialization -deserialize -dlwyatt -dotnet -enums -EXE's -ExecutionPolicy -FileCatalog -FilterHashtable -foreach -GetParentProcess -GitCommitId -globbing -HelpersCommon.psm1 -Himura2la -honors -hostname -IISResetMe -IncludeUserName -InformationRecord -IoT -iSazonov -IsCore -IsCoreCLR -jeffbi -joandrsn -JsonConfigFileAccessor -KeyFileParameter -KeyHandler -KirkMunro -kittholland -kvprasoon -kwiknick -kylesferrazza -LDSpits -Lee303 -libpsl-native -libunwind8 -LoadFrom -markekraus -meta -MiaRomero -Microsoft.Management.Infrastructure.Native -Microsoft.PowerShell.LocalAccounts -mklement0 -mwrock -nanoserver-insider -non-22 -non-CIM -non-R2 -OAuth -offthewoll -oising -oneget.org -PetSerAl -powercode -PowershellNinja -PowerShellProperties -preview1-24530-04 -ProductVersion -PRs -PSDrive -PseudoParameterBinder -PSReadLine -PSScriptAnalyzer -PSVersion -PVS-Studio -Pwrshplughin.dll -raghav710 -Raspbian -rc -rc.2 -rc2-24027 -rc3-24011 -README.md -RelationLink -richardszalay -rkeithhill -SemVer -shebang -ShellExecute -showwindow -startup -startuptype -stdin -StringBuilder -SxS -system.manage -Tadas -TestCase -TheFlyingCorpse -thezim -TimCurwick -timestamp -TimeZone -TPA -Travis -travisty -TTY's -UserAgent -UserData -UserVoice -Utf8 -UTF8NoBOM v0.1.0 v0.2.0 v0.3.0 v0.4.0 v0.5.0 v0.6.0 +v141 +v2 +v3 +v4 +v5 +v5.0 +v6 +v6.0. v6.0.0 v6.0.1 -ValidateNotNullOrEmpty -WebListener -WebRequest -win7-x86 -Windos -WindowsVersion -WSManCredSSP -XPath -Youtube -stuntguy3000 -SwarfegaGit -#endregion - -#region CODE_OF_CONDUCT.md Overrides - - CODE_OF_CONDUCT.md -microsoft.com -opencode -#endregion - -#region demos/Apache/readme.md Overrides - - demos/Apache/readme.md -Get-ApacheModule -Get-ApacheVHost -New-ApacheVHost -Restart-ApacheHTTPserver -#endregion - -#region demos/Azure/README.md Overrides - - demos/Azure/README.md -AzureRM.NetCore.Preview -AzureRM.Profile.NetCore.Preview -AzureRM.Resources.NetCore.Preview -ExcludeVersion -ProviderName -#endregion - -#region demos/crontab/README.md Overrides - - demos/crontab/README.md -DayOfWeek -Get-CronJob -New-CronJob -Remove-CronJob -u -#endregion - -#region demos/DSC/readme.md Overrides - - demos/DSC/readme.md -#endregion - -#region demos/python/README.md Overrides - - demos/python/README.md -_script.ps1 -_script.ps1. -#endregion - -#region demos/rest/README.md Overrides - - demos/rest/README.md -rest.ps1 -#endregion - -#region demos/SSHRemoting/README.md Overrides - - demos/SSHRemoting/README.md -_config -2kCbnhT2dUE6WCGgVJ8Hyfu1z2wE4lifaJXLO7QJy0Y -com.openssh.sshd -ComputerName -ComputerType -ConfigurationName -Enter-PSSession -HostName -KeyFilePath -KeyPath -launchctl -New-PSSession -NoLogo -NoProfile -openssh-client -openssh-server -PasswordAuthentication -PSCredential -PSSessions -PubkeyAuthentication -RSAAuthentication -ssh.exe -sshd -sshd.exe -sshs -TestUser -UbuntuVM1 -UbuntuVM1s -usr -#endregion - -#region demos/SystemD/readme.md Overrides - - demos/SystemD/readme.md -Get-SystemDJournal -journalctl -SystemD -#endregion - -#region demos/WindowsPowerShellModules/README.md Overrides - - demos/WindowsPowerShellModules/README.md -PowerShellGallery -PSSnapins -WindowsPSModulePath -#endregion - -#region docker/README.md Overrides - - docker/README.md -andschwa's -CurrentUser -hub.docker.com -microsoft -NanoServer-Insider -nanoserver-insider-powershell -#endregion - -#region docs/BREAKINGCHANGES.md Overrides -- docs/BREAKINGCHANGES.md -7-bit -CoreFX -honored -non-Windows -psd1 -runbooks -runspace -v1 -v2 -WMI-based -#endregion - -#region docs/building/internals.md Overrides - - docs/building/internals.md -_arm -_arm64 -Catalog -flavor -libpsl -MSBuild -nuget.exe -plugin -powershell-unix -src -v141 -#endregion - -#region docs/building/macos.md Overrides - - docs/building/macos.md -preview3 -#endregion - -#region docs/cmdlet-example/command-line-simple-example.md Overrides -- docs/cmdlet-example/command-line-simple-example.md -aka -classlib -dotnet -netstandard.dll +v6.0.2 +v6.0.4 +v6.0.5 +v6.1.0 +v6.1.1 +v6.1.2 +v6.2.0 +v6.2.1 +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 +vors +vpondala +vscode +vstsbuild.ps1 +walkthrough +webcmdlets +weblistener +webrequest +webrequestpscmdlet.common.cs +webresponseobject.common +weltner +wesholton84 +wget +whitespace +wildcard +wildcarded +wildcards +win32 +win32-openssh +win7 +win8 +windos +windows.json +windowspsmodulepath +windowsversion +winrm +wix +wmentha +WNetGetConnection +WNetAddConnection2 +worrenb +wpr +wprui.exe +writingpestertests.md wsl -#endregion - -#region docs/cmdlet-example/visual-studio-simple-example.md Overrides -- docs/cmdlet-example/visual-studio-simple-example.md -dropdown -v3 -#endregion - -#region docs/community/governance.md Overrides - - docs/community/governance.md -Aiello -AngelCalvo -BrucePay -Calvo -daxian-dbw -Dongbo -DON'Ts -Hemant -HemantMahawar -JamesWTruher -joeyaiello -jpsnover -khansen00 -lzybkr -Mahawar -Payette -PRs -Snover -SteveL-MSFT -Truher -#endregion - -#region docs/debugging/README.md Overrides +wsman +wsmancredssp +wsmansessionoption.cs +www.github.com +x64 +x86 +xpath +xtqqczze +xunit +xunit.runner.visualstudio +Xunit.SkippableFact +yaml +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 +azkarmoulana +chucklu +Claustn +CVE-2018-8256 +CVE-2018-8415 +daviddreher2 +honour +iGotenz +jeis2497052 +Jocapear +lassehastrup +markwragg +nbkalex +NeoBeum +nycjan +paalbra +robdy +SeeminglyScience +StingyJack +ThreeFive-O +tobvil +uninstallation +vongrippen +yurko7 +zhenggu +davinci26 +vedhasp +SeeminglyScience +eugenesmlv +steviecoaster +machgo +derek-xia +weltkante +kilasuit +tnieto88 +Orca88 +OrderBy +centreboard +romero126 +Greg-Smulko +danstur +vdamewood +MJECloud +DamirAinullin +NoMoreFood +silijon +NextTurn +mikeTWC1984 +Marusyk +M1kep +doctordns +alvarodelvalle +devlead +RandomNoun7 +edyoung +stevend811 +LabhanshAgrawal +ShaydeNofziger +alepauly +bpayette +joeltankam +OneScripter +Francisco-Gamino +adamdriscoll +analytics +deserialized +string.Join +string.Split +StringSplitOptions.TrimEntries +Dictionary.TryAdd +Environment.NewLine +ParseError.ToString +EditorConfig +GetExceptionForHR +ThrowExceptionForHR +Get-ExperimentalFeature +PSWindowsPowerShellCompatibility +currentculture +NJsonSchema +Microsoft.CodeAnalysis.CSharp +NJsonSchema +StrictMode +devcontainer +AzFileCopy +metadata.json +ADOPTERS.md +powershell.exe +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 -CmdletProviderClasses -CommandDiscovery -CommandSearch -ConsoleHostRunspaceInit -ConsoleHostUserInterface -ConsoleLineOutput corehost -DisplayDataQuery -FileSystemProvider -FormatFileLoading -FormatViewBinding -LocationGlobber -MemberResolution -MshSnapinLoadUnload -OmniSharp -ParameterBinderBase -ParameterBinderController -ParameterBinding -PathResolution -PSDriveInfo -PSSnapInLoadUnload -RunspaceInit -SessionState -TypeConversion -TypeMatch -XTerm -#endregion - -#region docs/dev-process/breaking-change-contract.md Overrides - - docs/dev-process/breaking-change-contract.md -cd -cdxml -int -p1 -#endregion - -#region docs/dev-process/coding-guidelines.md Overrides - - docs/dev-process/coding-guidelines.md -interop -PaulHigin -SMEs -TravisEz13 -uppercase -#endregion - -#region docs/FAQ.md Overrides - - docs/FAQ.md -PoshCode -SS64.com -TypeGen -v6.0.0 -#endregion - -#region docs/git/submodules.md Overrides - - docs/git/submodules.md -GoogleTest -superproject -#endregion - -#region docs/host-powershell/README.md Overrides - - docs/host-powershell/README.md -0-powershell -CorePsAssemblyLoadContext.cs -Kerberos-based -NTLM-based -post-6 -preview1-002106-00 -sample-dotnet1 -sample-dotnet2 -#endregion - -#region docs/installation/linux.md Overrides - - docs/installation/linux.md -compat-openssl10 -dockerfile -libc6 -libcurl -libcurl3 -libgcc1 -libgssapi-krb5-2 -libicu -libicu52 -libicu55 -libicu57 -liblttng-ust0 -libssl1.0.0 -libssl1.0.2 -libstdc -libunwind -libunwind8 -libuuid1 -openssl-libs -OpenSUSE -zlib1g -zypper -#endregion - -#region docs/installation/windows.md Overrides - - docs/installation/windows.md -Install-PowerShellRemoting -pwrshplugin.dll -System32 -Win8 -windir -#endregion - -#region docs/KNOWNISSUES.md Overrides - - docs/KNOWNISSUES.md -cp -globbing -pipelining -psl-omi-provider -Register-WmiEvent -System.Management.Automation.SemanticVersion -System.Timers.Timer -#endregion - -#region docs/learning-powershell/create-powershell-scripts.md Overrides - - docs/learning-powershell/create-powershell-scripts.md -NetIP.ps1. -RemoteSigned -#endregion - -#region docs/learning-powershell/debugging-from-commandline.md Overrides - - docs/learning-powershell/debugging-from-commandline.md -_Debuggers -celsius -Set-PSBreakpoint -test.ps1 -#endregion - -#region docs/learning-powershell/powershell-beginners-guide.md Overrides - - docs/learning-powershell/powershell-beginners-guide.md -dir -jen -LastWriteTime -#endregion - -#region docs/learning-powershell/README.md Overrides - docs/learning-powershell/README.md -Lynda.com -Pluralsight -PowerShell.com -PowerShellMagazine.com -ScriptCenter -TechNet -#endregion - -#region docs/learning-powershell/using-vscode.md Overrides - - docs/learning-powershell/using-vscode.md -helloworld.ps1 -launch.json -OSs -#endregion - -#region docs/learning-powershell/working-with-powershell-objects.md Overrides - - docs/learning-powershell/working-with-powershell-objects.md -ForEach-Object -#endregion - -#region docs/maintainers/issue-management.md Overrides - - docs/maintainers/issue-management.md -Microsoft.PowerShell.Core -Microsoft.PowerShell.Management -Microsoft.PowerShell.Utility -omi -#endregion - -#region docs/maintainers/pull-request-process.md Overrides - - docs/maintainers/pull-request-process.md -ci-system -#endregion - -#region docs/maintainers/README.md Overrides - - docs/maintainers/README.md -andschwa -daxian-dbw -Dongbo -mirichmo -Schwartzmeyer -Sergei -TravisEz13 -Vorobev -vors -#endregion - -#region docs/maintainers/releasing.md Overrides - - docs/maintainers/releasing.md -2012r2 -CHANGELOG.md -Dockerfiles -downlevel -Effing -PowerShellCore -Ronn -Toolset -v6 -#endregion - -#region docs/testing-guidelines/PowerShellCoreTestStatus.md Overrides - - docs/testing-guidelines/PowerShellCoreTestStatus.md -Add-LocalGroupMember -add-on -adhoc -Clear-ItemProperty -Connect-PSSession -Connect-WSMan -ConvertFrom-Csv -ConvertFrom-SddlString -ConvertFrom-SecureString -ConvertFrom-StringData -ConvertTo-Csv -ConvertTo-Json -ConvertTo-SecureString -ConvertTo-Xml -Copy-ItemProperty -Debug-Runspace -Disable-LocalUser -Disable-PSBreakpoint -Disable-PSSessionConfiguration -Disable-PSTrace -Disable-PSWSManCombinedTrace -Disable-RunspaceDebug -Disable-WSManCredSSP -Disable-WSManTrace -Disconnect-PSSession -Disconnect-WSMan -Enable-LocalUser -Enable-PSBreakpoint -Enable-PSSessionConfiguration -Enable-PSTrace -Enable-PSWSManCombinedTrace -Enable-RunspaceDebug -Enable-WSManCredSSP -Enable-WSManTrace -Enter-PSHostProcess -Exit-PSHostProcess -Exit-PSSession -Export-BinaryMiLog -Export-Clixml -Export-Csv -Export-FormatData -Export-ModuleMember -Find-DscResource -Find-PackageProvider -Find-RoleCapability -Get-CimAssociatedInstance -Get-CimClass -Get-CimInstance -Get-CimSession -Get-EventSubscriber -Get-ExecutionPolicy -Get-FileHash -Get-FormatData -Get-InstalledModule -Get-InstalledScript -Get-ItemProperty -Get-ItemPropertyValue -Get-LocalGroup -Get-LocalGroupMember -Get-LocalUser -Get-LogProperties -Get-PackageProvider -Get-PackageSource -Get-PSBreakpoint -Get-PSCallStack -Get-PSDrive -Get-PSHostProcessInfo -Get-PSProvider -Get-PSReadlineKeyHandler -Get-PSReadlineOption -Get-PSRepository -Get-PSSession -Get-PSSessionCapability -Get-Runspace -Get-RunspaceDebug -Get-TimeZone -Get-TypeData -Get-UICulture -Get-WSManCredSSP -Get-WSManInstance -Import-BinaryMiLog -Import-Clixml -Import-Csv -Import-LocalizedData -Import-PackageProvider -Import-PowerShellDataFile -Install-PackageProvider -Invoke-CimMethod -Invoke-WSManAction -Move-ItemProperty -New-CimInstance -New-CimSession -New-CimSessionOption -New-FileCatalog -New-Guid -New-ItemProperty -New-LocalGroup -New-LocalUser -New-ModuleManifest -New-PSDrive -New-PSRoleCapabilityFile -New-PSSessionConfigurationFile -New-ScriptFileInfo -New-TemporaryFile -New-WinEvent -New-WSManInstance -New-WSManSessionOption -Receive-PSSession -Register-ArgumentCompleter -Register-CimIndicationEvent -Register-ObjectEvent -Register-PackageSource -Register-PSRepository -Remove-CimInstance -Remove-CimSession -Remove-ItemProperty -Remove-LocalGroup -Remove-LocalGroupMember -Remove-LocalUser -Remove-PSBreakpoint -Remove-PSDrive -Remove-PSReadlineKeyHandler -Remove-PSSession -Remove-TypeData -Remove-WSManInstance -Rename-ItemProperty -Rename-LocalGroup -Rename-LocalUser -Select-xml -Set-CimInstance -Set-ItemProperty -Set-LocalGroup -Set-LocalUser -Set-LogProperties -Set-PackageSource -Set-PSDebug -Set-PSReadlineKeyHandler -Set-PSReadlineOption -Set-PSRepository -Set-PSSessionConfiguration -Set-StrictMode -Set-TimeZone -Set-WSManInstance -Set-WSManQuickConfig -Test-FileCatalog -Test-ModuleManifest -Test-PSSessionConfigurationFile -Test-ScriptFileInfo -Test-WSMan -Unregister-PackageSource -Unregister-PSRepository -Update-FormatData -Update-ModuleManifest -Update-ScriptFileInfo -Update-TypeData -#endregion - -#region docs/testing-guidelines/testing-guidelines.md Overrides - - docs/testing-guidelines/testing-guidelines.md -100ms -Api -build.psm1 -DotNet -Interop -MessageAnalyzer -Microsoft.PowerShell.Core -Microsoft.PowerShell.Diagnostics -Microsoft.PowerShell.Management -Microsoft.PowerShell.Security -Microsoft.PowerShell.Utility -NativeExecution -TabCompletion -#endregion - -#region docs/testing-guidelines/TestRoadmap.md Overrides +PSKoans + - docs/testing-guidelines/CodeCoverageAnalysis.md +de5f69c - docs/testing-guidelines/TestRoadmap.md -corefx -DotCover -Downlevel -KPIs -loopback -MVPs -OpenCover -org -PowerBI -Syslog -#endregion - -#region docs/testing-guidelines/WritingPesterTests.md Overrides +_no_ - docs/testing-guidelines/WritingPesterTests.md -ErrorRecord -FQErrorId -FullyQualifiedErrorId -HelpersCommon.psm1 nGet-ContentOut-String nGet-MultiLineString PSDefaultParameterValues-skip ShouldShouldIt -StreamDescribeCIFeatureScenarioDescribeContextItContextContextBeforeAllAfterAllBeforeEachAfterEachshould -TestDrive -throw-testcasesItMockDescribe -#endregion - -#region README.md Overrides - - README.md -Gitter -microsoft.com -msi -omnisharp-vscode -opencode -pkg -tgz -UserVoice -#endregion - -#region src/libpsl-native/README.md Overrides - - src/libpsl-native/README.md -Ansi -C#'s -codepage -libpsl-native -#endregion - -#region src/Microsoft.PowerShell.PSReadLine/en-US/PSReadline.md Overrides - - src/Microsoft.PowerShell.PSReadLine/en-US/PSReadline.md -_history.txt -_PSReadline -50ms -AddToHistoryHandler -AppData -BackgroundColor -BellStyle -BriefDescription -coloring -CompletionQueryItems -ConsoleColor -ConsoleKeyInfo -ContinuationPrompt -ContinuationPromptBackgroundColor -ContinuationPromptForegroundColor -DingDuration -DingTone -EditMode -EmphasisBackgroundColor -EmphasisForegroundColor -ErrorBackgroundColor -ErrorForegroundColor -ExtraPromptLineCount -ForegroundColor -ForwardWord -Func -HistoryNoDuplicates -HistorySavePath -HistorySaveStyle -HistorySearchBackward -HistorySearchCaseSensitive -HistorySearchCursorMovesToEnd -host.Name -Int32 -KillWord -MaximumHistoryCount -MaximumKillRingCount -Microsoft.PowerShell.KeyHandler -ResetTokenColors -ReverseSearchHistory -SaveAtExit -SaveIncrementally -SaveNothing -ScriptBlock -ShowToolTips -TokenClassification -TokenKind -ToString -ValidateAndAcceptLine -ValidationHandler -WordDelimiters -#endregion - -#region src/Microsoft.PowerShell.SDK/README.md Overrides - - src/Microsoft.PowerShell.SDK/README.md -metapackage -project.json -#endregion - -#region src/Modules/README.md Overrides - - src/Modules/README.md -ps1xml -psd1 -psm1 -#endregion - -#region src/powershell/README.md Overrides - - src/powershell/README.md -powershell-unix -#endregion - -#region src/TypeCatalogGen/README.md Overrides - - src/TypeCatalogGen/README.md -TypeCatalogGen -#endregion - -#region test/README.md Overrides - - test/README.md -csharp -fullclr -shebang -#endregion - -#region test/tools/CodeCoverageAutomation/README.md Overrides - - test/tools/CodeCoverageAutomation/README.md -CodeCoverage.zip -Coveralls.exe -Coveralls.io. -Coveralls.net -csmacnz -OpenCover.zip -powershell.version -Start-CodeCoverageRun -tests.zip -v5.0 -v6.0. -#endregion - -#region test/tools/WebListener/README.md Overrides - - test/tools/WebListener/README.md -Auth -NTLM -ResponseHeaders -#endregion + - tools/performance/README.md +Invoke-PerfviewPS +JIT.Regions.xml +PowerShell.Regions.xml +PowerShell.stacktags +PowerShell.wpaProfile +PowerShell.wprp +wpa +wpaProfile + - demos/WindowsPowerShellModules/README.md +2.x. + - 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/.travis.yml b/.travis.yml deleted file mode 100644 index 472f23a807d..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -language: cpp - -git: - depth: 1000 - -matrix: - include: - - os: linux - dist: trusty - sudo: required - - os: osx - osx_image: xcode8.1 - fast_finish: true - -addons: - artifacts: - paths: - - $(ls powershell*{deb,pkg,AppImage,gz} | tr "\n" ":") - - pester-tests.xml - -install: - # Default 2.0.0 Ruby is buggy - # Default bundler version is buggy - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - rvm install ruby-2.3.3; - rvm --default use 2.3.3; - fi - # Ensure that libcurl+openssl is used on macOS for greater feature support. - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - export DYLD_LIBRARY_PATH=/usr/local/opt/curl/lib:/usr/local/opt/openssl/lib:${DYLD_LIBRARY_PATH}; - fi - - pushd tools - - ./install-powershell.sh - - popd - # spellcheck - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - nvm install 6.4.0 && - npm install -g markdown-spellcheck@0.11.0; - fi - - ulimit -n 4096 - - pwsh -File tools/travis.ps1 -Stage Bootstrap - -script: - - pwsh -File tools/travis.ps1 - # spellcheck - # Ignore 'Pester' folder because it's third party - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - mdspell '**/*.md' '!**/Pester/**/*.md' --ignore-numbers --ignore-acronyms --report; - fi - -after_failure: - - pwsh -File tools/travis.ps1 -Stage Failure - -after_success: - - pwsh -File tools/travis.ps1 -Stage Success - -# travis-ci will quit using the cache if an enviroment variable changes -env: - - CACHE_VERSION=netcoreapp.2.0.5-sdk.2.1.4 - -# timeout uploading cache after 6 minutes (360 seconds) -cache: - timeout: 360 - directories: - - $HOME/.nuget - - $HOME/.dotnet - - $HOME/Library/Caches/Homebrew diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b0c23e31c2d..fa227cd9944 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,11 +1,14 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 + // 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-vscode.csharp", + "ms-dotnettools.csharp", "ms-vscode.PowerShell", "twxs.cmake", - "DavidAnson.vscode-markdownlint" + "DavidAnson.vscode-markdownlint", + "vitaliymaz.vscode-svg-previewer" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 792e547d8a5..ad298823d10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,16 +22,19 @@ // Sets the codeformatting options to follow the given indent style in a way that is compatible with PowerShell syntax. For more information about the brace styles please refer to https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/81. "powershell.codeFormatting.preset": "OTBS", - + // Adds a space between a keyword and its associated scriptblock expression. "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, - + // Adds a space between a keyword (if, elseif, while, switch, etc) and its associated conditional expression. "powershell.codeFormatting.whitespaceBeforeOpenParen": true, - + // Adds spaces before and after an operator ('=', '+', '-', etc.). "powershell.codeFormatting.whitespaceAroundOperator": true, - + // Adds a space after a separator (',' and ';'). - "powershell.codeFormatting.whitespaceAfterSeparator": true + "powershell.codeFormatting.whitespaceAfterSeparator": true, + + // Omnisharp : Enable EditorConfig support + "omnisharp.enableEditorConfigSupport": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4283a9f10eb..b92aaddeb0d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,19 +41,19 @@ { "label": "Bootstrap", "type": "shell", - "command": "Import-Module ${workspaceFolder}/build.psm1; Start-PSBootstrap", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBootstrap", "problemMatcher": [] }, { "label": "Clean Build", "type": "shell", - "command": "Import-Module ${workspaceFolder}/build.psm1; Start-PSBuild -Clean -Output (Join-Path ${workspaceFolder} debug)", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild -Clean -Output (Join-Path '${workspaceFolder}' debug)", "problemMatcher": "$msCompile" }, { "label": "Build", "type": "shell", - "command": "Import-Module ${workspaceFolder}/build.psm1; Start-PSBuild -Output (Join-Path ${workspaceFolder} debug)", + "command": "Import-Module '${workspaceFolder}/build.psm1'; Start-PSBuild -Output (Join-Path '${workspaceFolder}' debug)", "group": { "kind": "build", "isDefault": true diff --git a/.vsts-ci/install-ps.yml b/.vsts-ci/install-ps.yml new file mode 100644 index 00000000000..7190e228578 --- /dev/null +++ b/.vsts-ci/install-ps.yml @@ -0,0 +1,161 @@ +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: + - /tools/install-powershell.* + - /tools/installpsh-*.sh + - /.vsts-ci/install-ps.yml +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - /tools/install-powershell.sh + - /tools/installpsh-*.sh + - /tools/install-powershell.ps1 + - /.vsts-ci/install-ps.yml + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +resources: +- repo: self + clean: true +phases: +- template: templates/install-ps-phase.yml + parameters: + scriptName: sudo ./tools/install-powershell.sh + jobName: InstallPowerShellUbuntu + pool: ubuntu-latest + verification: | + 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)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: sudo ./tools/install-powershell.sh + jobName: InstallPowerShellAmazonLinux + 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]"7.3.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: sudo ./tools/installpsh-amazonlinux.sh + jobName: InstallPSHAmazonLinux + 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]"7.3.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + continueOnError: false + +# TODO: add sudo to script and use image with sudo +- template: templates/install-ps-phase.yml + parameters: + scriptName: ./tools/install-powershell.sh + jobName: InstallPowerShellCentOS + pool: ubuntu-latest + container: mcr.microsoft.com/powershell/test-deps:centos-7 + +- template: templates/install-ps-phase.yml + parameters: + scriptName: ./tools/install-powershell.sh + jobName: InstallPowerShellDebian9 + pool: ubuntu-latest + container: mcr.microsoft.com/powershell/test-deps:debian-9 + + +# VSTS could not find pwsh in: +# mcr.microsoft.com/powershell:opensuse-42.3 +# could not repo locally + +# sudo is not needed on macOS +- template: templates/install-ps-phase.yml + parameters: + scriptName: ./tools/install-powershell.sh + jobName: InstallPowerShellMacOS + pool: macOS-latest + verification: | + 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)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: pwsh -c ./tools/install-powershell.ps1 -AddToPath + jobName: InstallPowerShellPS1Ubuntu + pool: ubuntu-latest + +- template: templates/install-ps-phase.yml + parameters: + scriptName: pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellPS1UbuntuDaily + pool: ubuntu-latest + verification: | + Write-Verbose $PSVersionTable.PSVersion -verbose + 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: pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellMacOSDaily + pool: macOS-latest + verification: | + Write-Verbose $PSVersionTable.PSVersion -verbose + if ([Version]"$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor).$($PSVersionTable.PSVersion.Patch)" -lt [version]"7.0.0") + { + throw "powershell was not upgraded: $($PSVersionTable.PSVersion)" + } + +- template: templates/install-ps-phase.yml + parameters: + scriptName: | + pwsh -c ./tools/install-powershell.ps1 -AddToPath -Daily + jobName: InstallPowerShellWindowsDaily + pool: windows-latest + verification: | + $newVersion = &$env:LOCALAPPDATA\Microsoft\powershell-daily\pwsh -v + $newVersion -match '^PowerShell ((\d*\.\d*\.\d*)(-\w*(\.\d*)?)?){1}' + $versionOnly = $Matches[2] + Write-verbose "$newVersion; versionOnly: $versionOnly" -verbose + if ([Version]$versionOnly -lt [version]"7.0.0") + { + throw "powershell was not upgraded: $newVersion" + } diff --git a/.vsts-ci/linux-daily.yml b/.vsts-ci/linux-daily.yml new file mode 100644 index 00000000000..10effadd1e3 --- /dev/null +++ b/.vsts-ci/linux-daily.yml @@ -0,0 +1,149 @@ +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 + paths: + include: + - .vsts-ci/linux-daily.yml + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + +resources: +- repo: self + clean: true + +stages: +- stage: BuildLinux + displayName: Build for Linux + jobs: + - template: templates/ci-build.yml + parameters: + pool: ubuntu-20.04 + jobName: linux_build + displayName: linux Build + +- stage: TestLinux + displayName: Test for Linux + jobs: + - job: linux_test + timeoutInMinutes: 90 + pool: + vmImage: ubuntu-20.04 + displayName: Linux Test + + steps: + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - task: DownloadBuildArtifacts@0 + displayName: 'Download Build Artifacts' + inputs: + downloadType: specific + itemPattern: | + build/**/* + xunit/**/* + downloadPath: '$(System.ArtifactsDirectory)' + + - pwsh: | + Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse + displayName: 'Capture Artifacts Directory' + continueOnError: true + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + displayName: Bootstrap + condition: succeededOrFailed() + + - pwsh: | + Import-Module .\build.psm1 + Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' + $output = (Get-PSOptions).Output + $rootPath = Split-Path (Split-Path $output) + Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force + + ## Fix permissions + Get-ChildItem $rootPath -Recurse | ForEach-Object { + if ($_ -is [System.IO.DirectoryInfo]) { + chmod +rwx $_.FullName + } else { + chmod +rw $_.FullName + } + } + chmod a+x $output + + Write-Host "=== Capture Unzipped Directory ===" + Get-ChildItem $rootPath -Recurse + displayName: 'Unzip Build and Fix Permissions' + 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() + +- 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-20.04 + steps: + - pwsh: | + Import-Module .\tools\ci.psm1 + New-CodeCoverageAndTestPackage + displayName: CodeCoverage and Test Package 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 new file mode 100644 index 00000000000..5d9dc663e1c --- /dev/null +++ b/.vsts-ci/linux.yml @@ -0,0 +1,79 @@ +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 + 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: + - .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 + 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-latest + jobName: linux_build + displayName: linux Build + +- stage: PackageLinux + displayName: Package Linux + dependsOn: ["BuildLinuxStage"] + jobs: + - template: linux/templates/packaging.yml + parameters: + 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 new file mode 100644 index 00000000000..678ded65259 --- /dev/null +++ b/.vsts-ci/mac.yml @@ -0,0 +1,114 @@ +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: + - tools/releaseBuild/**/* + - .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/packaging/* + - tools/releaseBuild/* + - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling + - .pipelines/* + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + # Turn off Homebrew analytics + HOMEBREW_NO_ANALYTICS: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +resources: +- repo: self + clean: true + +stages: +- stage: BuildMac + displayName: Build for macOS + jobs: + - template: templates/ci-build.yml + parameters: + pool: macOS-latest + jobName: mac_build + displayName: macOS Build + +- stage: TestMac + displayName: Test for macOS + jobs: + - template: templates/nix-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: Others + + - template: templates/nix-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: Others + + - template: templates/verify-xunit.yml + parameters: + pool: macOS-latest + +- stage: PackageMac + dependsOn: ['BuildMac'] + displayName: Package macOS (bootstrap only) + jobs: + - 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/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 new file mode 100644 index 00000000000..72c5710016b --- /dev/null +++ b/.vsts-ci/sshremoting-tests.yml @@ -0,0 +1,97 @@ +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: + - '/src/System.Management.Automation/engine/*' + - '/test/SSHRemoting/*' +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - '/src/System.Management.Automation/engine/*' + - '/test/SSHRemoting/*' + +variables: + - 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: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - 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 + + - pwsh: | + sudo apt-get update + sudo apt-get install -y git + displayName: Install Github + condition: succeeded() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + displayName: Bootstrap + condition: succeededOrFailed() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild + displayName: Build + condition: succeeded() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Restore-PSOptions + $options = (Get-PSOptions) + Import-Module .\test\tools\Modules\HelpersRemoting + Install-SSHRemoting -PowerShellFilePath $options.Output + displayName: Install SSH Remoting + condition: succeeded() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Restore-PSOptions + $options = (Get-PSOptions) + Import-Module .\build.psm1 + Start-PSPester -Path test/SSHRemoting -powershell $options.Output -OutputFile "$PWD/sshTestResults.xml" + displayName: Test + condition: succeeded() diff --git a/.vsts-ci/templates/ci-build.yml b/.vsts-ci/templates/ci-build.yml new file mode 100644 index 00000000000..5ec458c3c5a --- /dev/null +++ b/.vsts-ci/templates/ci-build.yml @@ -0,0 +1,87 @@ +parameters: + - 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: + ${{ if eq( parameters.PoolType, 'AzDoHosted') }}: + vmImage: ${{ parameters.pool }} + ${{ else }}: + name: ${{ parameters.pool }} + demands: + - ImageOverride -equals ${{ parameters.imageName }} + + displayName: ${{ parameters.displayName }} + + steps: + - powershell: | + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 + $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue -CommandType Application + + if ($null -eq $pwsh) { + $powerShellPath = Join-Path -Path $env:AGENT_TEMPDIRECTORY -ChildPath 'powershell' + Invoke-WebRequest -Uri https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.ps1 -outfile ./install-powershell.ps1 + ./install-powershell.ps1 -Destination $powerShellPath + $vstsCommandString = "vso[task.setvariable variable=PATH]$powerShellPath;$env:PATH" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + } + + displayName: Install PowerShell + + - checkout: self + fetchDepth: 1000 + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - 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') + + - ${{ if ne(variables['UseAzDevOpsFeed'], '') }}: + - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" + displayName: Bootstrap + condition: succeeded() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild + displayName: Build + condition: succeeded() + + - pwsh: | + Import-Module .\tools\ci.psm1 + Restore-PSOptions + Invoke-CIxUnit -SkipFailing + displayName: xUnit Tests + condition: succeeded() + continueOnError: true diff --git a/.vsts-ci/templates/credscan.yml b/.vsts-ci/templates/credscan.yml new file mode 100644 index 00000000000..60094ff3d77 --- /dev/null +++ b/.vsts-ci/templates/credscan.yml @@ -0,0 +1,29 @@ +parameters: + pool: 'windows-latest' + jobName: 'credscan' + displayName: Secret Scan + +jobs: +- job: ${{ parameters.jobName }} + pool: + vmImage: ${{ parameters.pool }} + + displayName: ${{ parameters.displayName }} + + steps: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2 + displayName: 'Scan for Secrets' + inputs: + suppressionsFile: tools/credScan/suppress.json + toolMajorVersion: V2 + debugMode: false + + - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2 + displayName: 'Publish Secret Scan Logs to Build Artifacts' + continueOnError: true + + - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1 + displayName: 'Check for Failures' + inputs: + CredScan: true + ToolLogsNotFoundAction: Error diff --git a/.vsts-ci/templates/install-ps-phase.yml b/.vsts-ci/templates/install-ps-phase.yml new file mode 100644 index 00000000000..4e650273264 --- /dev/null +++ b/.vsts-ci/templates/install-ps-phase.yml @@ -0,0 +1,42 @@ +parameters: + pool: 'ubuntu-latest' + jobName: 'none' + scriptName: '' + container: '' + verification: '' + continueOnError: false + +jobs: + +- job: ${{ parameters.jobName }} + variables: + scriptName: ${{ parameters.scriptName }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + pool: + vmImage: ${{ parameters.pool }} + + displayName: ${{ parameters.jobName }} + + steps: + - 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"))" + displayName: Set Build Name for Non-PR + condition: ne(variables['Build.Reason'], 'PullRequest') + + - bash: | + $(scriptName) + displayName: Run Script - $(scriptName) + condition: succeededOrFailed() + continueOnError: ${{ parameters.continueOnError }} + + - ${{ if ne(parameters.verification, '') }}: + - pwsh: ${{ parameters.verification }} + displayName: Verification + continueOnError: ${{ parameters.continueOnError }} diff --git a/.vsts-ci/templates/nix-test.yml b/.vsts-ci/templates/nix-test.yml new file mode 100644 index 00000000000..214ae14b2c6 --- /dev/null +++ b/.vsts-ci/templates/nix-test.yml @@ -0,0 +1,25 @@ +parameters: + pool: 'macOS-latest' + purpose: '' + tagSet: 'CI' + name: 'mac' + +jobs: +- job: ${{ parameters.name }}_test_${{ parameters.purpose }}_${{ parameters.tagSet }} + + 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: ./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 new file mode 100644 index 00000000000..b43cb9339f9 --- /dev/null +++ b/.vsts-ci/templates/verify-xunit.yml @@ -0,0 +1,33 @@ +parameters: + parentJobs: [] + pool: 'windows-latest' + jobName: 'xunit_verify' + +jobs: +- job: verify_xunit + displayName: Verify xUnit Results + pool: + vmImage: ${{ parameters.pool }} + dependsOn: + ${{ parameters.parentJobs }} + steps: + - task: DownloadBuildArtifacts@0 + displayName: 'Download build artifacts' + inputs: + downloadType: specific + itemPattern: | + xunit/**/* + downloadPath: '$(System.ArtifactsDirectory)' + + - pwsh: | + dir "$(System.ArtifactsDirectory)\*" -Recurse + displayName: 'Capture artifacts directory' + continueOnError: true + + - pwsh: | + Import-Module .\tools\ci.psm1 + $xUnitTestResultsFile = "$(System.ArtifactsDirectory)\xunit\xUnitTestResults.xml" + + Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile + 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.yml b/.vsts-ci/windows.yml new file mode 100644 index 00000000000..4171d09643d --- /dev/null +++ b/.vsts-ci/windows.yml @@ -0,0 +1,83 @@ +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: + - .vsts-ci/templates/* + - .vsts-ci/windows.yml + - '*.props' + - build.psm1 + - src/* + - test/* + - tools/buildCommon/* + - tools/ci.psm1 + - tools/WindowsCI.psm1 + exclude: + - test/common/markdown/* + - test/perf/* + +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 for Windows + jobs: + - template: templates/windows-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: CI + + - template: templates/windows-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: CI + + - template: templates/windows-test.yml + parameters: + purpose: UnelevatedPesterTests + tagSet: Others + + - template: templates/windows-test.yml + parameters: + purpose: ElevatedPesterTests + tagSet: Others + + - template: templates/verify-xunit.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 new file mode 100644 index 00000000000..186b99dd093 --- /dev/null +++ b/ADOPTERS.md @@ -0,0 +1,39 @@ +# Adopters + + + +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 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://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. + 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://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. +* [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.md b/CHANGELOG.md index 94a26d337ed..73ffd17cdb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,1163 +1,3 @@ # Changelog -## v6.0.1 - 2018-01-25 - -### Engine updates and fixes - -- Update PowerShell to use `2.0.5` dotnet core runtime and packages. (#5903, #5961) (Thanks @iSazonov!) - -### Build and Packaging Improvements - -- Re-release of `v6.0.0` as `v6.0.1` due to issues upgrading from pre-release versions - -### Test - -- Update regular expression to validate `GitCommitId` in `$PSVersionTable` to not require a pre-release tag (#5893) - -## v6.0.0 - 2018-01-10 - -### Breaking changes - -- Remove `sc` alias which conflicts with `sc.exe` (#5827) -- Separate group policy settings and enable policy controlled logging in PowerShell Core (#5791) - -### Engine updates and fixes - -- Handle `DLLImport` failure of `libpsrpclient` in PowerShell Remoting on Unix platforms (#5622) - -### Test - -- Replace `lee.io` Tests with `WebListener` (#5709) (Thanks @markekraus!) -- Update the docker based release package tests due to the removal of `Pester` module and other issues (#5692) -- Replace Remaining `HttpBin.org` Tests with `WebListener` (#5665) (Thanks @markekraus!) - -### Build and Packaging Improvements - -- Update x86 and x64 `MSI` packages to not overwrite each other (#5812) (Thanks @bergmeister!) -- Update `Restore-PSPester` to include the fix for nested describe errors (#5771) -- Automate the generation of release change log draft (#5712) - -### Documentation and Help Content - -- Updated help Uri to point to latest help content for `Microsoft.PowerShell.Core` module (#5820) -- Update the installation doc for `Raspberry-Pi` about supported devices (#5773) -- Fix a typo and a Markdown linting error in the Pull Request Template (#5807) (Thanks @markekraus!) -- Update submodule documentation for pester removal (#5786) (Thanks @bergmeister!) -- Change `Github` to `GitHub` in `CONTRIBUTING.md` (#5697) (Thanks @stuntguy3000!) -- Fix incorrect release date on the changelog (#5698) (Thanks @SwarfegaGit!) -- Add instructions to deploy `win-arm` build on Windows IoT (#5682) - -## v6.0.0-rc.2 - 2017-12-14 - -### Breaking changes - -- Skip null-element check for collections with a value-type element type (#5432) -- Make `AllSigned` execution policy require modules under `$PSHome` to be signed (#5511) - -### Engine updates and fixes - -- Update PowerShell to use `2.0.4` dotnet core runtime. (#5677) -- Remove references to the old executable `powershell` or `powershell.exe` (#5408) - -### General cmdlet updates and fixes - -- Remove unnecessary check for `Paths.count > 0`, in the `*-FileCatalog` CmdLets (#5596) -- Use explicit `libpsl-native` binary name for `dllimport`. (#5580) - -### Build and Packaging Improvements - -- Fix `Get-EnvironmentInformation` to properly check for CoreCLR (#5592) (Thanks @markekraus!) -- Make Travis CI use `libcurl+openssl+gssapi` (#5629) (Thanks @markekraus!) -- Disambiguate icon for daily builds on Windows (#5467) (Thanks @bergmeister!) -- Fix `Import-CliXml` tests which still use `powershell` instead of `pwsh` and make sure it fails if it regresses (#5521) (Thanks @markekraus!) -- Update port number used for WebCmdlets tests which broke due to a change in AppVeyor (#5520) (Thanks @markekraus!) -- Clean up use of `Runspaceconfiguration` from comments and xUnit test code (#5569) (Thanks @Bhaal22!) -- Replace `HttpListener` Response Tests with WebListener (#5540, #5605) (Thanks @markekraus!) -- Fix the path to `powershell_xxx.inc` in Start-Build (#5538) (Thanks @iSazonov!) -- Remove Pester as a module include with the PowerShell Packages. - You should be able to add it by running `Install-Module Pester`. (#5623, #5631) -- Refactor `New-UnixPackaging` into functions to make the large function more readable. (#5625) -- Make the experience better when `Start-PSPester` doesn't find Pester (#5673) -- Update packaging and release build scripts to produce zip packages for `win-arm` and `win-arm64` (#5664) -- Enable `Install-Debian` to work with VSTS Hosted Linux Preview (#5659) -- Add `linux-arm` tarball package to release build (#5652, #5660) -- Enable building for `win-arm` and `win-arm64` (#5524) -- Make macOS package require 10.12 or newer (#5649, #5654) -- Update signing subjects to something meaningful (#5650) -- Make `New-UnixPackage` more readable (#5625) -- Update `PowerShellGet` tests to validate the new install location of `AllUsers` scope. (#5633) -- Increase reliability of flaky test that fails intermittently in CI (#5641) -- Exclude markdown files from `Pester` folder from the Markdown meta test (#5636) -- Run tests for Windows installer only on Windows (#5619) -- Suppress the expected errors from `Select-Xml` tests (#5591) -- Add retry logic to prerequisite URL and output URL on failure so you can more easily troubleshoot (#5601, #5570) -- Make sure submodule are initialized when running Mac release build (#5496) -- Remove duplicate files in Windows packages in a folder called `signed` (#5527) -- Add PowerShell VSCode style settings (#5529) (Thanks @bergmeister) -- Add Travis CI matrix for improved job tagging (#5547) -- Remove community docker files from official docker image validation (#5508) - -### Documentation and Help Content - -- XML documentation fix for `CompletionResult` (#5550) (Thanks @bergmeister!) -- Change synopsis of `install-powershell.ps1` to reflect that it works cross-platform (#5465) (Thanks @bergmeister!) -- Add more helpful message for `AmbiguousParameterSet` exception (#5537) (Thanks @kvprasoon!) -- Update the contribution guideline to note that updating the changelog is required. (#5586) -- Updated doc to build arm/arm64 versions of `psrp.windows` and `PowerShell.Core.Instrumentation.dll` libraries (#5668) -- Update Contribution guidelines with work in progress guidance (#5655) -- Update code coverage tests to get GitCommitId using the ProductVersion from Assembly (#5651) -- Remove requirement to updating changelog update in PR (#5644, #5586) -- Minor refactoring of the release build scripts (#5632) -- Update PowerShell executable name in `using-vscode.md` (#5593) -- Fix xUnit test for PS (#4780) -- Update install link and instructions for R-Pi (#5495) - -### Compliance Work - -[Compliance](https://github.com/PowerShell/PowerShell/blob/master/docs/maintainers/issue-management.md#miscellaneous-labels) -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) -- Replace `strlen` with `strnlen` in native code (#5510) - -## v6.0.0-rc - 2017-11-16 - -### Breaking changes - -- Fix `-Verbose` to not override `$ErrorActionPreference`. (#5113) -- Fix `Get-Item -LiteralPath a*b` to return error if `a*b` doesn't actually exist. (#5197) -- Remove `AllScope` from most default aliases to reduce overhead on creating new scopes. (#5268) -- Change `$OutputEncoding` default to be `UTF8` without `BOM` rather than `ASCII`. (#5369) -- Add error on legacy credential over non-HTTPS for Web Cmdlets. (#5402) (Thanks @markekraus!) -- Fix single value JSON `null` in `Invoke-RestMethod`. (#5338) (Thanks @markekraus!) -- Add `PSTypeName` Support for `Import-Csv` and `ConvertFrom-Csv`. (#5389) (Thanks @markekraus!) - -### Engine updates and fixes - -- Add char range overload to the `..` operator, so `'a'..'z'` returns characters from 'a' to 'z'. (#5026) (Thanks @IISResetMe!) -- Remove `CommandFactory` because it serves no real purpose. (#5266) -- Change to not insert line breaks at console window width to output (except for tables). (#5193) -- Use `Ast` for context in parameter binding and fix to glob the native command argument only when it's not quoted. (#5188) -- Fix dynamic class assembly name. (#5292) -- Update PowerShell to use `2.0.4-servicing` dotnet core runtime. (#5295) -- Fix `ExecutionContext.LoadAssembly` to load with name when file cannot be found. (#5161) -- Speed up the check for suspicious content in script texts. (#5302) -- Use native `os_log` APIs on macOS for PowerShell Core logging. (#5310) -- Redirect `ETW` logging to `Syslog` on Linux. (#5144) -- Improve how we pass the array literal to native commands. (#5301) -- Make `SemanticVersion` compatible with `SemVer 2.0`. (#5037) (Thanks @iSazonov!) -- Revert refactoring changes that broke remoting to Windows PowerShell 5.1. (#5321) -- Port some fixes in `Job` for an issue that causes PowerShell to not respond. (#5258) -- Multiple improvements by `CodeRush` static analysis. (#5132) (Thanks @Himura2la!) -- Fix the Runspace cleanup issue that causes PowerShell to not respond on exit. (#5356) -- Update PowerShell to depend on new version of `psrp` and `libmi` nuget packages on Unix platforms. (#5469) - -### General cmdlet updates and fixes - -- Add `-AsHashtable` to `ConvertFrom-Json` to return a `Hashtable` instead. (#5043) (Thanks @bergmeister!) -- Fix `Import-module` to not report a loaded module was not found. (#5238) -- Fix performance issues in `Add-Type`. (#5243) (Thanks @iSazonov!) -- Fix `PSUserAgent` generation for Web Cmdlets on Windows 7. (#5256) (Thanks @markekraus!) -- Remove `DCOM` support from `*-Computer` cmdlets. (#5277) -- Add multiple link header support to Web Cmdlets. (#5265) (Thanks @markekraus!) -- Use wider columns for process id and user. (#5303) -- Add `Remove-Alias` Command. (#5143) (Thanks @PowershellNinja!) -- Update `installpsh-suse.sh` to work with the `tar.gz` package. (#5309) -- Add `Jobject` serialization support to `ConvertTo-Json`. (#5141) -- Display full help with 'help' function. (#5195) (Thanks @rkeithhill!) -- Fix `help` function to not pipe to `more` if objects are returned instead of help text. (#5395) -- Fix `Unblock-File` to not write an error if the file is already unblocked. (#5362) (Thanks @iSazonov!) -- Clean up FullCLR code from Web Cmdlets. (#5376) (Thanks @markekraus!) -- Exclude cmdlets that are not supported on Unix platforms. (#5083) -- Make `Import-Csv` support `CR`, `LF` and `CRLF` as line delimiters. (#5363) (Thanks @iSazonov!) -- Fix spelling in Web Cmdlet errors. (#5427) (Thanks @markekraus!) -- Add `SslProtocol` support to Web Cmdlets. (#5329) (Thanks @markekraus!) - -### Build and Packaging Improvements - -- Use `RCEdit` to embed icon and version information into `pwsh.exe`. (#5178) -- Update Docker file for Nano Server 1709 release. (#5252) -- Change VSCode build task to use `pwsh`. (#5255) -- Refactor building and packaging scripts for signing in release build workflow. (#5300) -- Always build with `-CrossGen` in CI to verify a fix in `CrossGen` tool. (#5315) -- Separate `Install-PowerShellRemoting.ps1` from `psrp.windows` nuget package. (#5330) -- Include symbols folder an embedded zip when packaging symbols. (#5333) -- Add Uniform Type Identifier conforming with Apple standards using a reverse DNS style prefix. (#5323) -- Update `Wix` toolset download link to newer version 3.11 (#5339) (Thanks @bergmeister!) -- Re-enable macOS launcher after fixing an issue that blocked macOS package generation. (#5291) (Thanks @thezim!) -- Set expected binaries and variable name for folder for symbols build. (#5357) -- Rename and update PowerShell `ETW` manifest to remove the Windows PowerShell dependency. (#5360) -- Add ability to produce `tar.gz` package for Raspbian. (#5387) -- Update `Find-Dotnet` to find dotnet with the compatible SDK. (#5341) (Thanks @rkeithhill!) -- Add signing manifest and script to update it with production values. (#5397) -- Add `install-powershell.ps1` to install PowerShell Core on windows. (#5383) -- Make `-Name` a dynamic parameter in `Start-PSPackage`. (#5415) -- Support `[package]` tag in PR CI and fix nightly build on macOS. (#5410) -- Enhance `install-powershell.ps1` to work on Linux and macOS. (#5411) -- Move the `RCEdit` step to the build phase rather than the packaging phase. (#5404) -- Allow packaging from a zip package to allow for signing. (#5418) -- Add automation to validate PowerShell Core packages using Docker containers. (#5401) -- Fix the `brew update` issue in bootstrap script. (#5400) -- Enable `install-powershell.ps1` to update the current running PowerShell Core. (#5429) -- Add standard set of VSCode workspace setting files. (#5457) (Thanks @rkeithhill!) -- Add support for installing PowerShell Core on Amazon Linux via `install-powershell.sh`. (#5461) (Thanks @DarwinJS!) -- Get `PowerShellGet` and `PackageManagement` from the PowerShell Gallery. (#5452) -- Fix `Start-PSBuild` on `WSL` if repository was already built on Windows. (#5346) (Thanks @bergmeister!) -- Fix build in VSCode and use an improved version of `tasks.json` from @rkeithhill. (#5453) -- Add scripts for signing packages in the release build workflow. (#5463) - -### Documentation and Help Content - -- Fix the codebase to use the consistent copyright string. (#5210) -- Add documentation about how to create `libpsl` and `psrp.windows` nuget packages. (#5278) -- Add help strings in PowerShell banner. (#5275) (Thanks @iSazonov!) -- Change all links in `README.md` to absolute as they are being used in other places outside of GitHub. (#5354) -- Update instructions to build on VSCode based on `pwsh`. (#5368) -- Update `FAQ.md` about how to use PowerShell Core nuget packages. (#5366) -- Correct the Fedora documentation (#5384) (Thanks @offthewoll!) -- Add instructions about how to create the `PowerShell.Core.Instrumentation` nuget package. (#5396) -- Updated PowerShell to use the latest help package. (#5454) - -### Compliance Work - -[Compliance](https://github.com/PowerShell/PowerShell/blob/master/docs/maintainers/issue-management.md#miscellaneous-labels) -work is required for Microsoft to continue to sign and release packages from the project as official Microsoft packages. - -- Replace the word `hang` with something more appropriate and add rules about other terms. (#5213, #5297, #5358) -- Use simplified names for compliance folders (#5388) -- Add compliance label description (#5355) -- Set `requestedExecutionLevel` to `asInvoker` for `pwsh.exe` on Windows. (#5285) -- Add `HighEntropyVA` to building pwsh. (#5455) - -## v6.0.0-beta.9 - 2017-10-24 - -### Breaking changes - -- Fix `ValueFromRemainingArguments` to have consistent behavior between script and C# cmdlets. (#2038) (Thanks @dlwyatt) -- Remove parameters `-importsystemmodules` and `-psconsoleFile` from `powershell.exe`. (#4995) -- Removed code to show a GUI prompt for credentials as PowerShell Core prompts in console. (#4995) -- Remove `-ComputerName` from `Get/Set/Remove-Service`. (#5094) -- Rename the executable name from `powershell` to `pwsh`. (#5101) -- Remove `RunspaceConfiguration` support. (#4942) -- Remove `-ComputerName` support since .NET Core `Process.GetProcesses(computer)` returns local processes. (#4960) -- Make `-NoTypeInformation` the default on `Export-Csv` and `ConvertTo-Csv`. (#5164) (Thanks @markekraus) -- Unify cmdlets with parameter `-Encoding` to be of type `System.Text.Encoding`. (#5080) - -### Engine updates and fixes - -- Fix PowerShell to update the `PATH` environment variable only if `PATH` exists. (#5021) -- Enable support of folders and files with colon in name on Unix. (#4959) -- Fix detection of whether `-LiteralPath` was used to suppress wildcard expansion for navigation cmdlets. (#5038) -- Enable using filesystem from a UNC location. (#4998) -- Escape trailing backslash when dealing with native command arguments. (#4965) -- Change location of `ModuleAnalysisCache` so it isn't shared with Windows PowerShell. (#5133) -- Put command discovery before scripts for Unix. (#5116) - -### General cmdlet updates and fixes - -- Correct comma position in `SecureStringCommands.resx`. (#5033) (Thanks @markekraus) -- User Agent of Web Cmdlets now reports the OS platform (#4937) (Thanks @LDSpits) -- Add the positional parameter attribute to `-InputObject` for `Set-Service`. (#5017) (Thanks @travisty-) -- Add `ValidateNotNullOrEmpty` attribute to `-UFormat` for `Get-Date`. (#5055) (Thanks @DdWr) -- Add `-NoNewLine` switch for `Out-String`. (#5056) (Thanks @raghav710) -- Improve progress messages written by Web Cmdlets. (#5078) (Thanks @markekraus) -- Add verb descriptions and alias prefixes for `Get-Verb`. (#4746) (Thanks @Tadas) -- Fix `Get-Content -Raw` to not miss the last line feed character. (#5076) -- Add authentication parameters to Web Cmdlets. (#5052) (Thanks @markekraus) - - Add `-Authentication` that provides three options: Basic, OAuth, and Bearer. - - Add `-Token` to get the bearer token for OAuth and Bearer options. - - Add `-AllowUnencryptedAuthentication` to bypass authentication that is provided for any transport scheme other than HTTPS. -- Fix `MatchInfoContext` clone implementation (#5121) (Thanks @dee-see) -- Exclude `PSHostProcess` cmdlets from Unix platforms. (#5105) -- Fix `Add-Member` to fetch resource string correctly. (#5114) -- Enable `Import-Module` to be case insensitive. (#5097) -- Add exports for `syslog` APIs in `libpsl-native`. (#5149) -- Fix `Get-ChildItem` to not ignore `-Depth` parameter when using with `-Include` or `-Exclude`. (#4985) (Thanks @Windos) -- Added properties `UserName`, `Description`, `DelayedAutoStart`, `BinaryPathName` and `StartupType` to the `ServiceController` objects returned by `Get-Service`. (#4907) (Thanks @joandrsn) - -### Build and Packaging Improvements - -- Treat `.rtf` files as binary so EOL don't get changed. (#5020) -- Improve the output of `tools/installpsh-osx.sh` and update Travis-CI to use Ruby 2.3.3. (#5065) -- Improve `Start-PSBootstrap` to locate dotnet SDK before installing it. (#5059) (Thanks @PetSerAl) -- Fix the prerequisite check of the MSI package. (#5070) -- Support creating `tar.gz` package for Linux and macOS. (#5085) -- Add release builds that produce symbols for compliance scans. (#5086) -- Update existing Docker files for the Linux package changes. (#5102) -- Add compiler switches and replace dangerous function with safer ones. (#5089) -- Add macOS launcher. (#5138) (Thanks @thezim) -- Replace `httpbin.org/response-headers` Tests with WebListener. (#5058) (Thanks @markekraus) -- Update `appimage.sh` to reflect the new name `pwsh`. (#5172) -- Update the man help file used in packaging. (#5173) -- Update to use `pwsh` in macOS launcher. (#5174) (Thanks @thezim) -- Add code to send web hook for Travis-CI daily build. (#5183) -- Add `global.json` to pick correct SDK version. (#5118) (Thanks @rkeithhill) -- Update packaging to only package PowerShell binaries when packaging symbols. (#5145) -- Update Docker files and related due to the name change. (#5156) - -### Code Cleanup - -- Clean up Json cmdlets. (#5001) (Thanks @iSazonov) -- Remove code guarded by `RELATIONSHIP_SUPPORTED` and `SUPPORTS_IMULTIVALUEPROPERTYCMDLETPROVIDER`, which has never been used. (#5066) -- Remove PSMI code that has never been used. (#5075) -- Remove unreachable code for `Stop-Job`. (#5091) (Thanks @travisty-) -- Removed font and codepage handling code that is only applicable to Windows PowerShell. (#4995) - -### Test - -- Fix a race condition between `WebListener` and Web Cmdlets tests. (#5035) (Thanks @markekraus) -- Add warning to `Start-PSPester` if Pester module is not found (#5069) (Thanks @DdWr) -- Add tests for DSC configuration compilation on Windows. (#5011) -- Test fixes and code coverage automation fixes. (#5046) - -### Documentation and Help Content - -- Update Pi demo instructions about installing libunwind8. (#4974) -- Add links on best practice guidelines in coding guideline. (#4983) (Thanks @iSazonov) -- Reformat command line help for `powershell -help` (#4989) (Thanks @iSazonov) -- Change logo in readme to current black icon. (#5030) -- Fix RPM package name in `README.md`. (#5044) -- Update `docs/building/linux.md` to reflect the current status of powershell build. (#5068) (Thanks @dee-see) -- Add black version of `.icns` file for macOS. (#5073) (Thanks @thezim) -- Update Arch Linux installation instructions. (#5048) (Thanks @kylesferrazza) -- Add submodule reminder to `testing-guidelines.md`. (#5061) (Thanks @DdWr) -- Update instructions in `docs/building/internals.md` for building from source. (#5072) (Thanks @kylesferrazza) -- Add UserVoice link to Issue Template. (#5100) (Thanks @markekraus) -- Add `Get-WebListenerUrl` Based Examples to WebListener `README.md`. (#4981) (Thanks @markekraus) -- Add document about how to create cmdlet with dotnet CLI. (#5117) (Thanks @rkeithhill) -- Update the help text for PowerShell executable with the new name `pwsh`. (#5182) -- Add new forward links for PowerShell 6.0.0 help content. (#4978) -- Fix VSCode `launch.json` to point to `pwsh`. (#5189) -- Add example of how to create .NET Core cmdlet with Visual Studio. (#5096) - -## v6.0.0-beta.8 - 2017-10-05 - -### Breaking changes - -* Changed `New-Service` to return error when given unsupported `-StartupType` and fixed `Set-Service` icon failing test. (#4802) -* Allow `*` to be used in registry path for `Remove-Item`. (#4866) -* Remove unsupported `-ShowWindow` switch from `Get-Help`. (#4903) -* Fix incorrect position of a parameter which resulted in the args passed as input instead of as args for `InvokeScript()`. (#4963) - -### Engine updates and fixes - -* Make calls to `void CodeMethod` work. (#4850) (Thanks @powercode) -* Get `PSVersion` and `GitCommitId` from the `ProductVersion` attribute of assembly (#4863) (Thanks @iSazonov) -* Fix `powershell -version` and built-in help for `powershell.exe` to align with other native tools. (#4958 & #4931) (Thanks @iSazonov) -* Load assemblies with `Assembly.LoadFrom` before `Assembly.Load` when the file path is given. (#4196) -* Add a generic file watcher function in `HelpersCommon.psm1`. (#4775) -* Update old links and fix broken links in `docs/host-powershell/README.md`. (#4877) -* Fix when importing remote modules using version filters (and added tests). (#4900) -* Enable transcription of native commands on non-Windows platforms. (#4871) -* Add a new line to `CommandNotFoundException` error string. (#4934 & #4991) -* Fix bug where PowerShell would exit with an error within an SSH remoting connection on Linux. (#4993) -* Fix issues with expression redirected to file. (#4847) - -### General cmdlet updates and fixes - -* Added `Remove-Service` to Management module. (#4858) (Thanks @joandrsn) -* Added functionality to set credentials on `Set-Service` command. (#4844) (Thanks @joandrsn) -* Fix `Select-String` to exclude directories (as opposed to individual files) discovered from `-Path`. (#4829) (Thanks @iSazonov) -* `Get-Date` now supports more argument completion scenarios by adding `ArgumentCompletionsAttribute`. (#4835) (Thanks @iSazonov) -* Exclude `-ComObject` parameter of `New-Object` on unsupported (currently non-Windows) platforms. (#4922) (Thanks @iSazonov) -* Updated default `ModuleVersion` in `New-ModuleManifest` to `0.0.1` to align with SemVer. (#4842) (Thanks @LDSpits) -* Add Multipart support to web cmdlets. (#4782) (Thanks @markekraus) -* Add `-ResponseHeadersVariable` to `Invoke-RestMethod` to enable the capture of response headers. (#4888) (Thanks @markekraus) -* Initialize web cmdlets headers dictionary only once. (#4853) (Thanks @markekraus) -* Change web cmdlets `UserAgent` from `WindowsPowerShell` to `PowerShell`. (#4914) (Thanks @markekraus) - -### Build and Packaging Improvements - -* Make the build output the WiX compilation log if it failed. (#4831) (Thanks @bergmeister) -* Use a simple file based check in the MSI for the VC++ 2015 redistributables. (#4745) (Thanks @bergmeister) -* New icon for PowerShell Core. (#4848) -* Build Powershell Core using the generic RID `linux-x64`. (#4841) -* Create generic Linux-x64 packages that are portable to all supported RPM Linux distros (and more similar for Debian based distros). (#4902 & #4994) -* Suppress the output of building test tools in `Compress-TestContent`. (#4957) -* Remove unnecessary error messages from output. (#4954) -* Update Travis CI script so that PRs can fail due to Pester tests. (#4830) -* Move release build definition into PowerShell. (#4884) -* Fix credential scan issues. (#4927 & #4935) -* Enable security flags in native compiler. (#4933) -* Add VS 2017 solution file for `powershell-win-core`. (#4748) - -### Code Cleanup - -* Remove remainder of `Utility.Activities` (Workflow code). (#4880) -* Remove `Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll`. (#4868) -* Enable auto EOL on Git repo side, fix some character encoding issues. (#4912) -* Updated EOL for all files to be LF in the repository. (#4943 & #4956) -* Removed leading whitespace. (#4991) - -### DSC Language - -* Update version of `PSDesiredStateConfiguration` in project files to fix complication of MOF files with the `Configuration` keyword. (#4979) - -### Test - -* Replace httpbin.org tests with `WebListener`. (Thanks @markekraus) - * headers (#4799) - * user-agent (#4798) - * redirect (#4852) - * encoding (#4869) - * delay (#4905) - * gzip & enable deflate (#4948) - * related changes and fixes (#4920) -* Port tests for constrained language mode. (#4816) -* Enable `Select-String` test from a network path. (#4921) (Thanks @iSazonov) -* Reformat `Measure-Object` test. (#4972) (Thanks @iSazonov) -* Mitigate intermittent failures in access denied tests. (#4788) -* Fix tests that incorrectly use `ShouldBeErrorId`. (#4793) -* Fix a test issue that causes tests to be skipped in Travis CI run (#4891) -* Skip web cmdlet certificate authentication tests on CentOS and Mac. (#4822) -* Validate product resource strings against resx files. (#4811 & #4861) -* Add source files for coverage run. (#4925) -* Add the UTC offset correctly in tests for CDXML cmdlets. (#4867) -* Be sure to change `PSDefaultParameterValue` in the global scope. (#4977 & #4892) -* Reduce output of Pester for CI. (#4855) -* Add tests for - * `Get-Content` (#4723) (Thanks @sarithsutha) - * Remoting and Jobs (#4928) - * `Get-Help` (#4895) - * `Get-Command -ShowCommandInfo` (#4906) - * `Get-Content -Tail` (#4790) - * `Get-Module` over remoting (#4787) - * `Start/Stop/Suspend/Resume/Restart-Service` cmdlets (#4774) - * WSMan Config provider tests (#4756) - * CDXML CIM `DateTime` test (#4796) - -### Documentation and Graphics - -* Sort `.spelling` (Thanks @markekraus) -* Improve the guideline for performance consideration. (#4824) -* Add setup steps for MacOS to use PSRP over SSH. (#4872) -* Instructions to demo PowerShell Core on Raspbian. (#4882) -* Added instructions to get permission to use PowerShell image assets. (#4938) -* Added demo for using Windows PowerShell modules. (#4886) - -## v6.0.0-beta.7 - 2017-09-13 - -### Breaking change - -* Fix `Get-Content -Delimiter` to not include the delimiter in the array elements returned (#3706) (Thanks @mklement0) -* Rename `$IsOSX` to `$IsMacOS` (#4757) - -### Engine updates and fixes - -* Use stricter rules when unwrapping a PSObject that wraps a COM object (#4614) -* Remove appended Windows PowerShell `PSModulePath` on Windows. (#4656) -* Ensure `GetNetworkCredential()` returns null if PSCredential has null or empty user name (#4697) -* Push locals of automatic variables to 'DottedScopes' when dotting script cmdlets (#4709) -* Fix `using module` when module has non-terminating errors handled with `SilentlyContinue` (#4711) (Thanks @iSazonov) -* Enable use of 'Singleline,Multiline' option in split operator (#4721) (Thanks @iSazonov) -* Fix error message in `ValidateSetAttribute.ValidateElement()` (#4722) (Thanks @iSazonov) - -### General cmdlet updates and fixes - -* Add Meta, Charset, and Transitional parameters to `ConvertTo-HTML` (#4184) (Thanks @ergo3114) -* Prevent `Test-ModuleManifest` from loading unnecessary modules (#4541) -* Remove AlternateStream code and `-Stream` from provider cmdlets on non-Windows (#4567) -* Add explicit ContentType detection to `Invoke-RestMethod` (#4692) -* Fix an error on `Enter-PSSession` exit (#4693) -* Add `-WhatIf` switch to `Start-Process` cmdlet (#4735) (Thanks @sarithsutha) -* Remove double spaces in .cs, .ps1, and .resx files (#4741 & #4743) (Thanks @korygill) -* Replace 'Windows PowerShell' with 'PowerShell' in resx files (#4758) (Thanks @iSazonov) - -### Build and Packaging Improvements - -* Refactor MSBuild project files to get PowerShell version from git tag (#4182) (Thanks @iSazonov) -* Create a single package for each Windows supported architecture (x86 and amd64) (#4540) -* Set the default windows RID to win7- (#4701) -* Enable cross-compiling for Raspberry-PI arm32 (#4742) -* Fix macOS brew reinstall command (#4627) (Thanks @TheNewStellW) -* Improvements to the Travis-CI script (#4689, #4731, #4807) -* Update OpenSUSE docker image to 42.2 (#4737) -* Confirm `Start-PSPackage` produces a package (#4795) - -### Code Cleanup - -* Remove Workflow code (#4777) -* Clean up CORECLR preprocessor directives in TraceSource (#4684) - -### Test - -* Add test WebListener module and tests for Web Cmdlet Certificate Authentication (#4622) (Thanks @markekraus) -* Move WebCmdlets HTTPS tests to WebListener (#4733) (Thanks @markekraus) -* Replace httpbin.org/get tests With WebListener (#4738) (Thanks @markekraus) -* Use `-PassThru` on Pester tests to reliably catch failures (#4644) -* Display the same number of tests regardless of platform (#4728) -* Improve comparison of code coverage values for a file (#4764) -* Silence PSSessionConfiguration test warning messages in the log (#4794) -* Add tests for - * `Get-Service` (#4773) - * `Set-Service` and `New-Service` (#4785) - * `Trace-Command` (#4288) - * `StaticParameter` (#4779) - * `Test-Wsman` (#4771) - * `New-Object -ComObject` (#4776) - * ProxyCommand APIs (#4791) -* Disable tests - * 'VC++ Redistributable'(#4673 & #4729) - * "Test 01. Standard Property test - all properties ()" due to missing CsPhysicallyInstalledMemory (#4763) - * `New-Service` failing test (#4806) - -### Documentation - -* Update WritingPesterTests.md to recommend ShouldBeErrorId (#4637) -* Clarify the Pull Request process, roles, and responsibilities (#4710) -* Add absolute URLs in the issue template and pull request template (#4718) (Thanks @chucklu) -* Add new approved Build and Deploy verbs (#4725) -* Update using-vscode.md to use the new exe path (#4736) -* Update coding guidelines to make it more concrete and useful in a review process (#4754) - -## v6.0.0-beta.6 - 2017-08-24 - -### Breaking change - -* Make invalid argument error messages for `-File` and `-Command` consistent and make exit codes consistent with Unix standards (#4573) - -### Engine updates and fixes - -* Make resource loading to work with PowerShell SxS installation (#4139) -* Add missing assemblies to TPA list to make Pwrshplughin.dll work (#4502) -* Make sure running `powershell` starts instance of the current version of PowerShell. (#4481) -* Make sure we only use Unicode output by default on Nano and IoT systems (#4074) -* Enable `powershell -WindowStyle` to work on Windows. (#4573) -* Enable enumeration of COM collections. (#4553) - -### General cmdlet updates and fixes - -* Fix Web CmdLets `-SkipHeaderValidation` to work with non-standard User-Agent headers. (#4479 & #4512) (Thanks @markekraus) -* Add Certificate authentication support for Web CmdLets. (#4646) (Thanks @markekraus) -* Add support for content headers to Web CmdLets. (#4494 & #4640) (Thanks @markekraus) -* Add support for converting enums to string (#4318) (Thanks @KirkMunro) -* Ignore casing when binding PSReadline KeyHandler functions (#4300) (Thanks @oising) -* Fix `Unblock-File` for the case of a read-only file. (#4395) (Thanks @iSazonov) -* Use supported API to set Central Access Policy ID (CAPID) in SACL. (#4496) -* Make `Start-Trace` support paths that require escaping in the underlying APIs (#3863) -* Removing `#if CORECLR` enabled, `Enable-PSRemoting` and `Disable-PSRemoting` (#2671) -* Enable WSManCredSSP cmdlets and add tests. (#4336) -* Use .NET Core's implementation for ShellExecute. (#4523) -* Fix SSH Remoting handling of KeyFileParameter when the path must be quoted. (#4529) -* Make Web CmdLets use HTML meta charset attribute value, if present (#4338) -* Move to .NET Core 2.0 final (#4603) - -### Build/test and code cleanup - -* Add Amazon Linux Docker image and enable related tests. (#4393) (Thanks @DarwinJS) -* Make MSI verify pre-requisites are installed. (#4602) (Thank @bergmeister) -* Fixed formatting issues in build files. (#4630) (Thanks @iSazonov) -* Make sure `install-powershell.sh` installs latest powershell on macOS, even if an old version is cached in brew. (#4509) (Thanks @richardszalay for reporting.) -* Fixes install scripts issue for macOS. (#4631) (Thanks @DarwinJS) -* Many stability improvements to our nightly code coverage automation. (#4313 & #4550) -* Remove hash validation from nanoserver-insider Docker file, due to frequent changes. (#4498) -* Update to make Travis-CI daily build badge more reliable. (#4522) -* Remove unused build files, build code, and product code. (#4532, #4580, #4590, #4589, #4588, #4587, #4586, #4583, #4582, #4581) -* Add additional acceptance tests for PowerShellGet. (#4531) -* Only publish a NuGet of the full PowerShell core package on daily builds and not merge. (#4517) -* Update nanoserver-insider Docker file due to breaking changes in the base image. (#4555) -* Cleanup engine tests (#4551) -* Fix intermittent failures in filesystem tests (#4566) -* Add tests for - * `New-WinEvent`. (#4384) - * tab completion. (#4560) - * various types. (#4503) - * CDXML CmdLets. (#4537) -* Only allow packaging of powershell, if it was built from a repo at the root of the file system named powershell. (#4569 & #4600) -* Update `Format-Hex` test cases to use -TestCase instead of foreach loops. (#3800) -* Added functionality to get code coverage for a single file locally. (#4556) - -### Documentation - -* Added Ilya (@iSazonov) as a Maintainer. (#4365) -* Grammar fix to the Pull Request Guide. (#4322) -* Add homebrew for macOS to install documentation. (#3838) -* Added a CodeOwner file. (#4565 & #4597) - -### Cleanup `#if CORECLR` code - -PowerShell 6.0 will be exclusively built on top of CoreCLR, -so we are removing a large amount of code that's built only for FullCLR. -To read more about this, check out [this blog post](https://blogs.msdn.microsoft.com/powershell/2017/07/14/powershell-6-0-roadmap-coreclr-backwards-compatibility-and-more/). - -## v6.0.0-beta.5 - 2017-08-02 - -### Breaking changes - -* Remove the `*-Counter` cmdlets in `Microsoft.PowerShell.Diagnostics` due to the use of unsupported APIs until a better solution is found. (#4303) -* Remove the `Microsoft.PowerShell.LocalAccounts` due to the use of unsupported APIs until a better solution is found. (#4302) - -### Engine updates and fixes - -* Fix the issue where PowerShell Core wasn't working on Windows 7 or Windows Server 2008 R2/2012 (non-R2). (#4463) -* `ValidateSetAttribute` enhancement: support set values to be dynamically generated from a custom `ValidateSetValueGenerator`. (#3784) (Thanks to @iSazonov!) -* Disable breaking into debugger on Ctrl+Break when running non-interactively. (#4283) (Thanks to @mwrock!) -* Give error instead of crashing if WSMan client library is not available. (#4387) -* Allow passing `$true`/`$false` as a parameter to scripts using `powershell.exe -File`. (#4178) -* Enable `DataRow`/`DataRowView` adapters in PowerShell Core to fix an issue with `DataTable` usage. (#4258) -* Fix an issue where PowerShell class static methods were being shared across `Runspace`s/`SessionState`s. (#4209) -* Fix array expression to not return null or throw error. (#4296) -* Fixes a CIM deserialization bug where corrupted CIM classes were instantiating non-CIM types. (#4234) -* Improve error message when `HelpMessage` property of `ParameterAttribute` is set to empty string. (#4334) -* Make `ShellExecuteEx` run in a STA thread. (#4362) - -### General cmdlet updates and fixes - -* Add `-SkipHeaderValidation` switch to `Invoke-WebRequest` and `Invoke-RestMethod` to support adding headers without validating the header value. (#4085) -* Add support for `Invoke-Item -Path `. (#4262) -* Fix `ConvertTo-Html` output when using a single column header. (#4276) -* Fix output of `Length` for `FileInfo` when using `Format-List`. (#4437) -* Fix an issue in implicit remoting where restricted sessions couldn't use `Get-FormatData –PowerShellVersion`. (#4222) -* Fix an issue where `Register-PSSessionConfiguration` fails if `SessionConfig` folder doesn't exist. (#4271) - -### Installer updates - -* Create script to install latest PowerShell from Microsoft package repositories (or Homebrew) on non-Windows platforms. (#3608) (Thanks to @DarwinJS!) -* Enable MSI upgrades rather than a side-by-side install. (#4259) -* Add a checkbox to open PowerShell after the Windows MSI installer has finished. (#4203) (Thanks to @bergmeister!) -* Add Amazon Linux compatibility to `install-powershell.sh`. (#4360) (Thanks to @DarwinJS!) -* Add ability to package PowerShell Core as a NuGet package. (#4363) - -### Build/test and code cleanup - -* Add build check for MFC for Visual C++ during Windows builds. - This fixes a long-standing (and very frustrating!) issue with missing build dependencies! (#4185) (Thanks to @KirkMunro!) -* Move building Windows PSRP binary out of `Start-PSBuild`. - Now `Start-PSBuild` doesn't build PSRP binary on windows. Instead, we consume the PSRP binary from a NuGet package. (#4335) -* Add tests for built-in type accelerators. (#4230) (Thanks to @dchristian3188!) -* Increase code coverage of `Get-ChildItem` on file system. (#4342) (Thanks to @jeffbi!) -* Increase test coverage for `Rename-Item` and `Move-Item`. (#4329) (Thanks to @jeffbi!) -* Add test coverage for Registry provider. (#4354) (Thanks to @jeffbi!) -* Fix warnings and errors thrown by PSScriptAnalyzer. (#4261) (Thanks to @bergmeister!) -* Fix regressions that cause implicit remoting tests to fail. (#4326) -* Disable legacy UTC and SQM Windows telemetry by enclosing the code in '#if LEGACYTELEMETRY'. (#4190) - -### Cleanup `#if CORECLR` code - -PowerShell 6.0 will be exclusively built on top of CoreCLR, -so we are removing a large amount of code that's built only for FullCLR. -To read more about this, check out [this blog post](https://blogs.msdn.microsoft.com/powershell/2017/07/14/powershell-6-0-roadmap-coreclr-backwards-compatibility-and-more/). - -## v6.0.0-beta.4 - 2017-07-12 - -## Windows PowerShell backwards compatibility - -In the `beta.4` release, we've introduced a change to add the Windows PowerShell `PSModulePath` to the default `PSModulePath` in PowerShell Core on Windows. (#4132) - -Along with the introduction of .NET Standard 2.0 in `6.0.0-beta.1` and a GAC probing fix in `6.0.0-beta.3`, -**this change will enable a large number of your existing Windows PowerShell modules/scripts to "just work" inside of PowerShell Core on Windows**. -(Note: We have also fixed the CDXML modules on Windows that were regressed in `6.0.0-beta.2` as part of #4144). - -So that we can further enable this backwards compatibility, -we ask that you tell us more about what modules or scripts do and don't work in Issue #4062. -This feedback will also help us determine if `PSModulePath` should include the Windows PowerShell values by default in the long run. - -For more information on this, we invite you to read [this blog post explaining PowerShell Core and .NET Standard in more detail](https://blogs.msdn.microsoft.com/powershell/?p=13355). - -### Engine updates and fixes - -- Add Windows PowerShell `PSModulePath` by default on Windows. (#4132) -- Move PowerShell to `2.0.0-preview3-25426-01` and using the .NET CLI version `2.0.0-preview2-006502`. (#4144) -- Performance improvement in PSReadline by minimizing writing ANSI escape sequences. (#4110) -- Implement Unicode escape parsing so that users can use Unicode characters as arguments, strings or variable names. (#3958) (Thanks to @rkeithhill!) -- Script names or full paths can have commas. (#4136) (Thanks to @TimCurwick!) -- Added `semver` as a type accelerator for `System.Management.Automation.SemanticVersion`. (#4142) (Thanks to @oising!) -- Close `eventLogSession` and `EventLogReader` to unlock an ETL log. (#4034) (Thanks to @iSazonov!) - -### General cmdlet updates and fixes - -- `Move-Item` cmdlet honors `-Include`, `-Exclude`, and `-Filter` parameters. (#3878) -- Add a parameter to `Get-ChildItem` called `-FollowSymlink` that traverses symlinks on demand, with checks for link loops. (#4020) -- Change `New-ModuleManifest` encoding to UTF8NoBOM on non-Windows platforms. (#3940) -- `Get-AuthenticodeSignature` cmdlets can now get file signature timestamp. (#4061) -- Add tab completion for `Export-Counter` `-FileFormat` parameter. (#3856) -- Fixed `Import-Module` on non-Windows platforms so that users can import modules with `NestedModules` and `RootModules`. (#4010) -- Close `FileStream` opened by `Get-FileHash`. (#4175) (Thanks to @rkeithhill!) - -### Remoting - -- Fixed PowerShell not responding when the SSH client abruptly terminates. (#4123) - -### Documentation - -- Added recommended settings for VS Code. (#4054) (Thanks to @iSazonov!) - -## v6.0.0-beta.3 - 2017-06-20 - -### Breaking changes - -- Remove the `BuildVersion` property from `$PSVersionTable`. - This property was strongly tied to the Windows build version. - Instead, we recommend that you use `GitCommitId` to retrieve the exact build version of PowerShell Core. - (#3877) (Thanks to @iSazonov!) -- Change positional parameter for `powershell.exe` from `-Command` to `-File`. - This fixes the usage of `#!` (aka as a shebang) in PowerShell scripts that are being executed from non-PowerShell shells on non-Windows platforms. - This also means that you can now do things like `powershell foo.ps1` or `powershell fooScript` without specifying `-File`. - However, this change now requires that you explicitly specify `-c` or `-Command` when trying to do things like `powershell.exe Get-Command`. - (#4019) -- Remove `ClrVersion` property from `$PSVersionTable`. - (This property is largely irrelevant for .NET Core, - and was only preserved in .NET Core for specific legacy purposes that are inapplicable to PowerShell.) - (#4027) - -### Engine updates and fixes - -- Add support to probe and load assemblies from GAC on Windows platform. - This means that you can now load Windows PowerShell modules with assembly dependencies which reside in the GAC. - If you're interested in running your traditional Windows PowerShell scripts and cmdlets using the power of .NET Standard 2.0, - try adding your Windows PowerShell module directories to your PowerShell Core `$PSModulePath`. - (E.g. `$env:PSModulePath += ';C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules'`) - Even if the module isn't owned by the PowerShell Team, please tell us what works and what doesn't by leaving a comment in [issue #4062][issue-4062]! (#3981) -- Enhance type inference in tab completion based on runtime variable values. (#2744) (Thanks to @powercode!) - This enables tab completion in situations like: - ```powershell - $p = Get-Process - $p | Foreach-Object Prio - ``` -- Add `GitCommitId` to PowerShell Core banner. - Now you don't have to run `$PSVersionTable` as soon as you start PowerShell to get the version! (#3916) (Thanks to @iSazonov!) -- Fix a bug in tab completion to make `native.exe --` call into native completer. (#3633) (Thanks to @powercode!) -- Fix PowerShell Core to allow use of long paths that are more than 260 characters. (#3960) -- Fix ConsoleHost to honour `NoEcho` on Unix platforms. (#3801) -- Fix transcription to not stop when a Runspace is closed during the transcription. (#3896) - -[issue-4062]: https://github.com/PowerShell/PowerShell/issues/4062 - -### General cmdlet updates and fixes - -- Enable `Send-MailMessage` in PowerShell Core. (#3869) -- Fix `Get-Help` to support case insensitive pattern matching on Unix platforms. (#3852) -- Fix tab completion on `Get-Help` for `about_*` topics. (#4014) -- Fix PSReadline to work in Windows Server Core container image. (#3937) -- Fix `Import-Module` to honour `ScriptsToProcess` when `-Version` is specified. (#3897) -- Strip authorization header on redirects with web cmdlets. (#3885) -- `Start-Sleep`: add the alias `ms` to the parameter `-Milliseconds`. (#4039) (Thanks to @Tadas!) - -### Developer experience - -- Make hosting PowerShell Core in your own .NET applications much easier by refactoring PowerShell Core to use the default CoreCLR loader. (#3903) -- Update `Add-Type` to support `CSharpVersion7`. (#3933) (Thanks to @iSazonov) - -## v6.0.0-beta.2 - 2017-06-01 - -### Support backgrounding of pipelines with ampersand (`&`) (#3360) - -- Putting `&` at the end of a pipeline will cause the pipeline to be run as a PowerShell job. -- When a pipeline is backgrounded, a job object is returned. -- Once the pipeline is running as a job, all of the standard `*-Job` cmdlets can be used to manage the job. -- Variables (ignoring process-specific variables) used in the pipeline are automatically copied to the job so `Copy-Item $foo $bar &` just works. -- The job is also run in the current directory instead of the user's home directory. -- For more information about PowerShell jobs, see [about_Jobs](https://msdn.microsoft.com/en-us/powershell/reference/6/about/about_jobs). - -### Engine updates and fixes - -- Crossgen more of the .NET Core assemblies to improve PowerShell Core startup time. (#3787) -- Enable comparison between a `SemanticVersion` instance and a `Version` instance that is constructed only with `Major` and `Minor` version values. - This will fix some cases where PowerShell Core was failing to import older Windows PowerShell modules. (#3793) (Thanks to @mklement0!) - -### General cmdlet updates and fixes - -- Support Link header pagination in web cmdlets (#3828) - - For `Invoke-WebRequest`, when the response includes a Link header we create a RelationLink property as a Dictionary representing the URLs and `rel` attributes and ensure the URLs are absolute to make it easier for the developer to use. - - For `Invoke-RestMethod`, when the response includes a Link header we expose a `-FollowRelLink` switch to automatically follow `next` `rel` links until they no longer exist or once we hit the optional `-MaximumFollowRelLink` parameter value. -- Update `Get-ChildItem` to be more in line with the way that the *nix `ls -R` and the Windows `DIR /S` native commands handle symbolic links to directories during a recursive search. - Now, `Get-ChildItem` returns the symbolic links it encountered during the search, but it won't search the directories those links target. (#3780) -- Fix `Get-ChildItem` to continue enumeration after throwing an error in the middle of a set of items. - This fixes some issues where inaccessible directories or files would halt execution of `Get-ChildItem`. (#3806) -- Fix `ConvertFrom-Json` to deserialize an array of strings from the pipeline that together construct a complete JSON string. - This fixes some cases where newlines would break JSON parsing. (#3823) -- Enable `Get-TimeZone` for macOS/Linux. (#3735) -- Change to not expose unsupported aliases and cmdlets on macOS/Linux. (#3595) (Thanks to @iSazonov!) -- Fix `Invoke-Item` to accept a file path that includes spaces on macOS/Linux. (#3850) -- Fix an issue where PSReadline was not rendering multi-line prompts correctly on macOS/Linux. (#3867) -- Fix an issue where PSReadline was not working on Nano Server. (#3815) - -## v6.0.0-beta.1 - 2017-05-08 - -### 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://blogs.msdn.microsoft.com/dotnet/2016/09/26/introducing-net-standard/), -and [on GitHub](https://github.com/dotnet/standard/blob/master/docs/faq.md). -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! - -### Telemetry - -- For the first beta of PowerShell Core 6.0, telemetry has been to the console host to report two values (#3620): - - the OS platform (`$PSVersionTable.OSDescription`) - - the exact version of PowerShell (`$PSVersionTable.GitCommitId`) - -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/1-Draft/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://blogs.msdn.microsoft.com/powershell/2017/01/31/powershell-open-source-community-dashboard/). - -If you have any questions or comments about our telemetry, please file an issue. - -### Engine updates and fixes - -- Add support for native command globbing on Unix platforms. (#3643) - - This means you can now use wildcards with native binaries/commands (e.g. `ls *.txt`). -- Fix PowerShell Core to find help content from `$PSHome` instead of the Windows PowerShell base directory. (#3528) - - This should fix issues where about_* topics couldn't be found on Unix platforms. -- Add the `OS` entry to `$PSVersionTable`. (#3654) -- Arrange the display of `$PSVersionTable` entries in the following way: (#3562) (Thanks to @iSazonov!) - - `PSVersion` - - `PSEdition` - - alphabetical order for rest entries based on the keys -- Make PowerShell Core more resilient when being used with an account that doesn't have some key environment variables. (#3437) -- Update PowerShell Core to accept the `-i` switch to indicate an interactive shell. (#3558) - - This will help when using PowerShell as a default shell on Unix platforms. -- Relax the PowerShell `SemanticVersion` constructors to not require 'minor' and 'patch' portions of a semantic version name. (#3696) -- Improve performance to security checks when group policies are in effect for ExecutionPolicy. (#2588) (Thanks to @powercode) -- Fix code in PowerShell to use `IntPtr(-1)` for `INVALID_HANDLE_VALUE` instead of `IntPtr.Zero`. (#3544) (Thanks to @0xfeeddeadbeef) - -### General cmdlet updates and fixes - -- Change the default encoding and OEM encoding used in PowerShell Core to be compatible with Windows PowerShell. (#3467) (Thanks to @iSazonov!) -- Fix a bug in `Import-Module` to avoid incorrect cyclic dependency detection. (#3594) -- Fix `New-ModuleManifest` to correctly check if a URI string is well formed. (#3631) - -### Filesystem-specific updates and fixes - -- Use operating system calls to determine whether two paths refer to the same file in file system operations. (#3441) - - This will fix issues where case-sensitive file paths were being treated as case-insensitive on Unix platforms. -- Fix `New-Item` to allow creating symbolic links to file/directory targets and even a non-existent target. (#3509) -- Change the behavior of `Remove-Item` on a symbolic link to only removing the link itself. (#3637) -- Use better error message when `New-Item` fails to create a symbolic link because the specified link path points to an existing item. (#3703) -- Change `Get-ChildItem` to list the content of a link to a directory on Unix platforms. (#3697) -- Fix `Rename-Item` to allow Unix globbing patterns in paths. (#3661) - -### Interactive fixes - -- Add Hashtable tab completion for `-Property` of `Select-Object`. (#3625) (Thanks to @powercode) -- Fix tab completion with `@{` to avoid crash in PSReadline. (#3626) (Thanks to @powercode) -- Use ` - ` as `ToolTip` and `ListItemText` when tab completing process ID. (#3664) (Thanks to @powercode) - -### Remoting fixes - -- Update PowerShell SSH remoting to handle multi-line error messages from OpenSSH client. (#3612) -- Add `-Port` parameter to `New-PSSession` to create PowerShell SSH remote sessions on non-standard (non-22) ports. (#3499) (Thanks to @Lee303) - -### API Updates - -- Add the public property `ValidRootDrives` to `ValidateDriveAttribute` to make it easy to discover the attribute state via `ParameterMetadata` or `PSVariable` objects. (#3510) (Thanks to @indented-automation!) -- Improve error messages for `ValidateCountAttribute`. (#3656) (Thanks to @iSazonov) -- Update `ValidatePatternAttribute`, `ValidateSetAttribute` and `ValidateScriptAttribute` to allow users to more easily specify customized error messages. (#2728) (Thanks to @powercode) - -## v6.0.0-alpha.18 - 2017-04-05 - -### Progress Bar - -We made a number of fixes to the progress bar rendering and the `ProgressRecord` object that improved cmdlet performance and fixed some rendering bugs on non-Windows platforms. - -- Fix a bug that caused the progress bar to drift on Unix platforms. (#3289) -- Improve the performance of writing progress records. (#2822) (Thanks to @iSazonov!) -- Fix the progress bar rendering on Unix platforms. (#3362) (#3453) -- Reuse `ProgressRecord` in Web Cmdlets to reduce the GC overhead. (#3411) (Thanks to @iSazonov!) - -### Cmdlet updates - -- Use `ShellExecute` with `Start-Process`, `Invoke-Item`, and `Get-Help -Online` so that those cmdlets use standard shell associations to open a file/URI. - This means you `Get-Help -Online` will always use your default browser, and `Start-Process`/`Invoke-Item` can open any file or path with a handler. - (Note: there are still some problems with STA threads.) (#3281, partially fixes #2969) -- Add `-Extension` and `-LeafBase` switches to `Split-Path` so that you can split paths between the filename extension and the rest of the filename. (#2721) (Thanks to @powercode!) -- Implement `Format-Hex` in C# along with some behavioral changes to multiple parameters and the pipeline. (#3320) (Thanks to @MiaRomero!) -- Add `-NoProxy` to web cmdlets so that they ignore the system-wide proxy setting. (#3447) (Thanks to @TheFlyingCorpse!) -- Fix `Out-Default -Transcript` to properly revert out of the `TranscribeOnly` state, so that further output can be displayed on Console. (#3436) (Thanks to @PetSerAl!) -- Fix `Get-Help` to not return multiple instances of the same help file. (#3410) - -### Interactive fixes - -- Enable argument auto-completion for `-ExcludeProperty` and `-ExpandProperty` of `Select-Object`. (#3443) (Thanks to @iSazonov!) -- Fix a tab completion bug that prevented `Import-Module -n` from working. (#1345) - -### Cross-platform fixes - -- Ignore the `-ExecutionPolicy` switch when running PowerShell on non-Windows platforms because script signing is not currently supported. (#3481) -- Standardize the casing of the `PSModulePath` environment variable. (#3255) - -### JEA fixes - -- Fix the JEA transcription to include the endpoint configuration name in the transcript header. (#2890) -- Fix `Get-Help` in a JEA session. (#2988) - -## v6.0.0-alpha.17 - 2017-03-08 - -- Update PSRP client libraries for Linux and Mac. - - We now support customer configurations for Office 365 interaction, as well as NTLM authentication for WSMan based remoting from Linux (more information [here](https://github.com/PowerShell/psl-omi-provider/releases/tag/v1.0.0.18)). (#3271) -- We now support remote step-in debugging for `Invoke-Command -ComputerName`. (#3015) -- Use prettier formatter with `ConvertTo-Json` output. (#2787) (Thanks to @kittholland!) -- Port `*-CmsMessage` and `Get-PfxCertificate` cmdlets to Powershell Core. (#3224) -- `powershell -version` now returns version information for PowerShell Core. (#3115) -- Add the `-TimeOut` parameter to `Test-Connection`. (#2492) -- Add `ShouldProcess` support to `New-FileCatalog` and `Test-FileCatalog` (fixes `-WhatIf` and `-Confirm`). (#3074) (Thanks to @iSazonov!) -- Fix `Test-ModuleManifest` to normalize paths correctly before validating. - - This fixes some problems when using `Publish-Module` on non-Windows platforms. (#3097) -- Remove the `AliasProperty "Count"` defined for `System.Array`. - - This removes the extraneous `Count` property on some `ConvertFrom-Json` output. (#3231) (Thanks to @PetSerAl!) -- Port `Import-PowerShellDatafile` from PowerShell script to C#. (#2750) (Thanks to @powercode!) -- Add `-CustomMethod` parameter to web cmdlets to allow for non-standard method verbs. (#3142) (Thanks to @Lee303!) -- Fix web cmdlets to include the HTTP response in the exception when the response status code is not success. (#3201) -- Expose a process' parent process by adding the `CodeProperty "Parent"` to `System.Diagnostics.Process`. (#2850) (Thanks to @powercode!) -- Fix crash when converting a recursive array to a bool. (#3208) (Thanks to @PetSerAl!) -- Fix casting single element array to a generic collection. (#3170) -- Allow profile directory creation failures for Service Account scenarios. (#3244) -- Allow Windows' reserved device names (e.g. CON, PRN, AUX, etc.) to be used on non-Windows platforms. (#3252) -- Remove duplicate type definitions when reusing an `InitialSessionState` object to create another Runspace. (#3141) -- Fix `PSModuleInfo.CaptureLocals` to not do `ValidateAttribute` check when capturing existing variables from the caller's scope. (#3149) -- Fix a race bug in WSMan command plug-in instance close operation. (#3203) -- Fix a problem where newly mounted volumes aren't available to modules that have already been loaded. (#3034) -- Remove year from PowerShell copyright banner at start-up. (#3204) (Thanks to @kwiknick!) -- Fixed spelling for the property name `BiosSerialNumber` for `Get-ComputerInfo`. (#3167) (Thanks to @iSazonov!) - -## v6.0.0-alpha.16 - 2017-02-15 - -- Add `WindowsUBR` property to `Get-ComputerInfo` result -- Cache padding strings to speed up formatting a little -- Add alias `Path` to the `-FilePath` parameter of `Out-File` -- Fix the `-InFile` parameter of `Invoke-WebRequest` -- Add the default help content to powershell core -- Speed up `Add-Type` by crossgen'ing its dependency assemblies -- Convert `Get-FileHash` from script to C# implementation -- Fix lock contention when compiling the code to run in interpreter -- Avoid going through WinRM remoting stack when using `Get-ComputerInfo` locally -- Fix native parameter auto-completion for tokens that begin with a single "Dash" -- Fix parser error reporting for incomplete input to allow defining class in interactive host -- Add the `RoleCapabilityFiles` keyword for JEA support on Windows - -## v6.0.0-alpha.15 - 2017-01-18 - -- Use parentheses around file length for offline files -- Fix issues with the Windows console mode (terminal emulation) and native executables -- Fix error recovery with `using module` -- Report `PlatformNotSupported` on IoT for Get/Import/Export-Counter -- Add `-Group` parameter to `Get-Verb` -- Use MB instead of KB for memory columns of `Get-Process` -- Add new escape character for ESC: `` `e`` -- Fix a small parsing issue with a here string -- Improve tab completion of types that use type accelerators -- `Invoke-RestMethod` improvements for non-XML non-JSON input -- PSRP remoting now works on CentOS without addition setup - -## v6.0.0-alpha.14 - 2016-12-14 - -- Moved to .NET Core 1.1 -- Add Windows performance counter cmdlets to PowerShell Core -- Fix try/catch to choose the more specific exception handler -- Fix issue reloading modules that define PowerShell classes -- `Add ValidateNotNullOrEmpty` to approximately 15 parameters -- `New-TemporaryFile` and `New-Guid` rewritten in C# -- Enable client side PSRP on non-Windows platforms -- `Split-Path` now works with UNC roots -- Implicitly convert value assigned to XML property to string -- Updates to `Invoke-Command` parameters when using SSH remoting transport -- Fix `Invoke-WebRequest` with non-text responses on non-Windows platforms -- `Write-Progress` performance improvement from `alpha13` reverted because it introduced crash with a race condition - -## v6.0.0-alpha.13 - 2016-11-22 - -- Fix `NullReferenceException` in binder after turning on constrained language mode -- Enable `Invoke-WebRequest` and `Invoke-RestMethod` to not validate the HTTPS certificate of the server if required. -- Enable binder debug logging in PowerShell Core -- Add parameters `-Top` and `-Bottom` to `Sort-Object` for Top/Bottom N sort -- Enable `Update-Help` and `Save-Help` on Unix platforms -- Update the formatter for `System.Diagnostics.Process` to not show the `Handles` column -- Improve `Write-Progress` performance by adding timer to update a progress pane every 100 ms -- Enable correct table width calculations with ANSI escape sequences on Unix -- Fix background jobs for Unix and Windows -- Add `Get-Uptime` to `Microsoft.PowerShell.Utility` -- Make `Out-Null` as fast as `> $null` -- Add DockerFile for 'Windows Server Core' and 'Nano Server' -- Fix WebRequest failure to handle missing ContentType in response header -- Make `Write-Host` fast by delay initializing some properties in InformationRecord -- Ensure PowerShell Core adds an initial `/` rooted drive on Unix platforms -- Enable streaming behavior for native command execution in pipeline, so that `ping | grep` doesn't block -- Make `Write-Information` accept objects from pipeline -- Fixes deprecated syscall issue on macOS 10.12 -- Fix code errors found by the static analysis using PVS-Studio -- Add support to W3C Extended Log File Format in `Import-Csv` -- Guard against `ReflectionTypeLoadException` in type name auto-completion -- Update build scripts to support win7-x86 runtime -- Move PackageManagement code/test to oneget.org - -## v6.0.0-alpha.12 - 2016-11-03 - -- Fix `Get-ChildItem -Recurse -ErrorAction Ignore` to ignore additional errors -- Don't block pipeline when running Windows EXE's -- Fix for PowerShell SSH remoting with recent Win32-OpenSSH change. -- `Select-Object` with `-ExcludeProperty` now implies `-Property *` if -Property is not specified. -- Adding ValidateNotNullOrEmpty to `-Name` parameter of `Get-Alias` -- Enable Implicit remoting commands in PowerShell Core -- Fix GetParentProcess() to replace an expensive WMI query with Win32 API calls -- Fix `Set-Content` failure to create a file in PSDrive under certain conditions. -- Adding ValidateNotNullOrEmpty to `-Name` parameter of `Get-Service` -- Adding support in `Get-WinEvent -FilterHashtable` -- Adding WindowsVersion to `Get-ComputerInfo` -- Remove the unnecessary use of lock in PseudoParameterBinder to avoid deadlock -- Refactor `Get-WinEvent` to use StringBuilder for XPath query construction -- Clean up and fix error handling of libpsl-native -- Exclude Registry and Certificate providers from UNIX PS -- Update PowerShell Core to consume .Net Core preview1-24530-04 - -## v6.0.0-alpha.11 - 2016-10-17 - -- Add '-Title' to 'Get-Credential' and unify the prompt experience -- Update dependency list for PowerShell Core on Linux and OS X -- Fix 'powershell -Command -' to not stop responding and to not ignore the last command -- Fix binary operator tab completion -- Enable 'ConvertTo-Html' in PowerShell Core -- Remove most Maximum* capacity variables -- Fix 'Get-ChildItem -Hidden' to work on system hidden files on Windows -- Fix 'JsonConfigFileAccessor' to handle corrupted 'PowerShellProperties.json' - and defer creating the user setting directory until a write request comes -- Fix variable assignment to not overwrite read-only variables -- Fix 'Get-WinEvent -FilterHashtable' to work with named fields in UserData of event logs -- Fix 'Get-Help -Online' in PowerShell Core on Windows -- Spelling/grammar fixes - -## v6.0.0-alpha.10 - 2016-09-15 - -- Fix passing escaped double quoted spaces to native executables -- Add DockerFiles to build each Linux distribution -- `~/.config/PowerShell` capitalization bug fixed -- Fix crash on Windows 7 -- Fix remote debugging on Windows client -- Fix multi-line input with redirected stdin -- Add PowerShell to `/etc/shells` on installation -- Fix `Install-Module` version comparison bug -- Spelling fixes - -## v6.0.0-alpha.9 - 2016-08-15 - -- Better man page -- Added third-party and proprietary licenses -- Added license to MSI - -## v6.0.0-alpha.8 - 2016-08-11 - -- PowerShell packages pre-compiled with crossgen -- `Get-Help` content added -- `Get-Help` null reference exception fixed -- Ubuntu 16.04 support added -- Unsupported cmdlets removed from Unix modules -- PSReadline long prompt bug fixed -- PSReadline custom key binding bug on Linux fixed -- Default terminal colors now respected -- Semantic Version support added -- `$env:` fixed for case-sensitive variables -- Added JSON config files to hold some settings -- `cd` with no arguments now behaves as `cd ~` -- `ConvertFrom-Json` fixed for multiple lines -- Windows branding removed -- .NET CoreCLR Runtime patched to version 1.0.4 -- `Write-Host` with unknown hostname bug fixed -- `powershell` man-page added to package -- `Get-PSDrive` ported to report free space -- Desired State Configuration MOF compilation ported to Linux -- Windows 2012 R2 / Windows 8.1 remoting enabled - -## v6.0.0-alpha.7 - 2016-07-26 - -- Invoke-WebRequest and Invoke-RestMethod ported to PowerShell Core -- Set PSReadline default edit mode to Emacs on Linux -- IsCore variable renamed to IsCoreCLR -- Microsoft.PowerShell.LocalAccounts and other Windows-only assemblies excluded on Linux -- PowerShellGet fully ported to Linux -- PackageManagement NuGet provider ported -- Write-Progress ported to Linux -- Get-Process -IncludeUserName ported -- Enumerating symlinks to folders fixed -- Bugs around administrator permissions fixed on Linux -- ConvertFrom-Json multi-line bug fixed -- Execution policies fixed on Windows -- TimeZone cmdlets added back; excluded from Linux -- FileCatalog cmdlets added back for Windows -- Get-ComputerInfo cmdlet added back for Windows - -## v0.6.0 - 2016-07-08 - -- Targets .NET Core 1.0 release -- PowerShellGet enabled -- [system.manage] completion issues fixed -- AssemblyLoadContext intercepts dependencies correctly -- Type catalog issues fixed -- Invoke-Item enabled for Linux and OS X -- Windows ConsoleHost reverted to native interfaces -- Portable ConsoleHost redirection issues fixed -- Bugs with pseudo (and no) TTY's fixed -- Source Depot synced to baseline changeset 717473 -- SecureString stub replaced with .NET Core package - -## v0.5.0 - 2016-06-16 - -- Paths given to cmdlets are now slash-agnostic (both / and \ work as directory separator) -- Lack of cmdlet support for paths with literal \ is a known issue -- .NET Core packages downgraded to build rc2-24027 (Nano's build) -- XDG Base Directory Specification is now respected and used by default -- Linux and OS X profile path is now `~/.config/powershell/profile.ps1` -- Linux and OS X history save path is now `~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt` -- Linux and OS X user module path is now `~/.local/share/powershell/Modules` -- The `~/.powershell` folder is deprecated and should be deleted -- Scripts can be called within PowerShell without the `.ps1` extension -- `Trace-Command` and associated source cmdlets are now available -- `Ctrl-C` now breaks running cmdlets correctly -- Source Depot changesets up to 715912 have been merged -- `Set-PSBreakPoint` debugging works on Linux, but not on Windows -- MSI and APPX packages for Windows are now available -- Microsoft.PowerShell.LocalAccounts is available on Windows -- Microsoft.PowerShell.Archive is available on Windows -- Linux xUnit tests are running again -- Many more Pester tests are running - -## v0.4.0 - 2016-05-17 - -- PSReadline is ported and included by default -- Original Windows ConsoleHost is ported and replaced CoreConsoleHost -- .NET Core packages set to the RC2 release at build 24103 -- OS X 10.11 added to Continuous Integration matrix -- Third-party C# cmdlets can be built with .NET CLI -- Improved symlink support on Linux -- Microsoft.Management.Infrastructure.Native replaced with package -- Many more Pester tests - -## v0.3.0 - 2016-04-11 - -- Supports Windows, Nano, OS X, Ubuntu 14.04, and CentOS 7.1 -- .NET Core packages are build rc3-24011 -- Native Linux commands are not shadowed by aliases -- `Get-Help -Online` works -- `more` function respects the Linux `$PAGER`; defaults to `less` -- `IsWindows`, `IsLinux`, `IsOSX`, `IsCore` built-in PowerShell variables added -- `Microsoft.PowerShell.Platform` removed for the above -- Cross-platform core host is now `CoreConsoleHost` -- Host now catches exceptions in `--command` scripts -- Host's shell ID changed to `Microsoft.PowerShellCore` -- Modules that use C# assemblies can be loaded -- `New-Item -ItemType SymbolicLink` supports arbitrary targets -- PSReadline implementation supports multi-line input -- `Ctrl-R` provides incremental reverse history search -- `$Host.UI.RawUI` now supported -- `Ctrl-K` and `Ctrl-Y` for kill and yank implemented -- `Ctrl-L` to clear screen now works -- Documentation was completely overhauled -- Many more Pester and xUnit tests added - -## v0.2.0 - 2016-03-08 - -- Supports Windows, OS X, Ubuntu 14.04, and CentOS 7.1 -- .NET Core packages are build 23907 -- `System.Console` PSReadline is fully functional -- Tests pass on OS X -- `Microsoft.PowerShell.Platform` module is available -- `New-Item` supports symbolic and hard links -- `Add-Type` now works -- PowerShell code merged with upstream `rs1_srv_ps` - -## v0.1.0 - 2016-02-23 - -- Supports Windows, OS X, and Ubuntu 14.04 +The change logs have been split by version and moved to [CHANGELOG](./CHANGELOG). diff --git a/CHANGELOG/6.0.md b/CHANGELOG/6.0.md new file mode 100644 index 00000000000..52db53afabf --- /dev/null +++ b/CHANGELOG/6.0.md @@ -0,0 +1,1180 @@ +# 6.0 Changelog + +## [6.0.0] - 2018-01-10 + +### Breaking changes + +- Remove `sc` alias which conflicts with `sc.exe` (#5827) +- Separate group policy settings and enable policy controlled logging in PowerShell Core (#5791) + +### Engine updates and fixes + +- Handle `DLLImport` failure of `libpsrpclient` in PowerShell Remoting on Unix platforms (#5622) + +### Test + +- Replace `lee.io` Tests with `WebListener` (#5709) (Thanks @markekraus!) +- Update the docker based release package tests due to the removal of `Pester` module and other issues (#5692) +- Replace Remaining `HttpBin.org` Tests with `WebListener` (#5665) (Thanks @markekraus!) + +### Build and Packaging Improvements + +- Update x86 and x64 `MSI` packages to not overwrite each other (#5812) (Thanks @bergmeister!) +- Update `Restore-PSPester` to include the fix for nested describe errors (#5771) +- Automate the generation of release change log draft (#5712) + +### Documentation and Help Content + +- Updated help Uri to point to latest help content for `Microsoft.PowerShell.Core` module (#5820) +- Update the installation doc for `Raspberry-Pi` about supported devices (#5773) +- Fix a typo and a Markdown linting error in the Pull Request Template (#5807) (Thanks @markekraus!) +- Update submodule documentation for pester removal (#5786) (Thanks @bergmeister!) +- Change `Github` to `GitHub` in `CONTRIBUTING.md` (#5697) (Thanks @stuntguy3000!) +- Fix incorrect release date on the changelog (#5698) (Thanks @SwarfegaGit!) +- Add instructions to deploy `win-arm` build on Windows IoT (#5682) + +## [6.0.0-rc.2] - 2017-12-14 + +### Breaking changes + +- Skip null-element check for collections with a value-type element type (#5432) +- Make `AllSigned` execution policy require modules under `$PSHome` to be signed (#5511) + +### Engine updates and fixes + +- Update PowerShell to use `2.0.4` dotnet core runtime. (#5677) +- Remove references to the old executable `powershell` or `powershell.exe` (#5408) + +### General cmdlet updates and fixes + +- Remove unnecessary check for `Paths.count > 0`, in the `*-FileCatalog` CmdLets (#5596) +- Use explicit `libpsl-native` binary name for `dllimport`. (#5580) + +### Build and Packaging Improvements + +- Fix `Get-EnvironmentInformation` to properly check for CoreCLR (#5592) (Thanks @markekraus!) +- Make Travis CI use `libcurl+openssl+gssapi` (#5629) (Thanks @markekraus!) +- Disambiguate icon for daily builds on Windows (#5467) (Thanks @bergmeister!) +- Fix `Import-CliXml` tests which still use `powershell` instead of `pwsh` and make sure it fails if it regresses (#5521) (Thanks @markekraus!) +- Update port number used for WebCmdlets tests which broke due to a change in AppVeyor (#5520) (Thanks @markekraus!) +- Clean up use of `Runspaceconfiguration` from comments and xUnit test code (#5569) (Thanks @Bhaal22!) +- Replace `HttpListener` Response Tests with WebListener (#5540, #5605) (Thanks @markekraus!) +- Fix the path to `powershell_xxx.inc` in Start-Build (#5538) (Thanks @iSazonov!) +- Remove Pester as a module include with the PowerShell Packages. + You should be able to add it by running `Install-Module Pester`. (#5623, #5631) +- Refactor `New-UnixPackaging` into functions to make the large function more readable. (#5625) +- Make the experience better when `Start-PSPester` doesn't find Pester (#5673) +- Update packaging and release build scripts to produce zip packages for `win-arm` and `win-arm64` (#5664) +- Enable `Install-Debian` to work with VSTS Hosted Linux Preview (#5659) +- Add `linux-arm` tarball package to release build (#5652, #5660) +- Enable building for `win-arm` and `win-arm64` (#5524) +- Make macOS package require 10.12 or newer (#5649, #5654) +- Update signing subjects to something meaningful (#5650) +- Make `New-UnixPackage` more readable (#5625) +- Update `PowerShellGet` tests to validate the new install location of `AllUsers` scope. (#5633) +- Increase reliability of flaky test that fails intermittently in CI (#5641) +- Exclude markdown files from `Pester` folder from the Markdown meta test (#5636) +- Run tests for Windows installer only on Windows (#5619) +- Suppress the expected errors from `Select-Xml` tests (#5591) +- Add retry logic to prerequisite URL and output URL on failure so you can more easily troubleshoot (#5601, #5570) +- Make sure submodule are initialized when running Mac release build (#5496) +- Remove duplicate files in Windows packages in a folder called `signed` (#5527) +- Add PowerShell VSCode style settings (#5529) (Thanks @bergmeister) +- Add Travis CI matrix for improved job tagging (#5547) +- Remove community docker files from official docker image validation (#5508) + +### Documentation and Help Content + +- XML documentation fix for `CompletionResult` (#5550) (Thanks @bergmeister!) +- Change synopsis of `install-powershell.ps1` to reflect that it works cross-platform (#5465) (Thanks @bergmeister!) +- Add more helpful message for `AmbiguousParameterSet` exception (#5537) (Thanks @kvprasoon!) +- Update the contribution guideline to note that updating the changelog is required. (#5586) +- Updated doc to build arm/arm64 versions of `psrp.windows` and `PowerShell.Core.Instrumentation.dll` libraries (#5668) +- Update Contribution guidelines with work in progress guidance (#5655) +- Update code coverage tests to get GitCommitId using the ProductVersion from Assembly (#5651) +- Remove requirement to updating changelog update in PR (#5644, #5586) +- Minor refactoring of the release build scripts (#5632) +- Update PowerShell executable name in `using-vscode.md` (#5593) +- Fix xUnit test for PS (#4780) +- Update install link and instructions for R-Pi (#5495) + +### Compliance Work + +[Compliance](https://github.com/PowerShell/PowerShell/blob/master/docs/maintainers/issue-management.md#miscellaneous-labels) +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 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 + +### Breaking changes + +- Fix `-Verbose` to not override `$ErrorActionPreference`. (#5113) +- Fix `Get-Item -LiteralPath a*b` to return error if `a*b` doesn't actually exist. (#5197) +- Remove `AllScope` from most default aliases to reduce overhead on creating new scopes. (#5268) +- Change `$OutputEncoding` default to be `UTF8` without `BOM` rather than `ASCII`. (#5369) +- Add error on legacy credential over non-HTTPS for Web Cmdlets. (#5402) (Thanks @markekraus!) +- Fix single value JSON `null` in `Invoke-RestMethod`. (#5338) (Thanks @markekraus!) +- Add `PSTypeName` Support for `Import-Csv` and `ConvertFrom-Csv`. (#5389) (Thanks @markekraus!) + +### Engine updates and fixes + +- Add char range overload to the `..` operator, so `'a'..'z'` returns characters from 'a' to 'z'. (#5026) (Thanks @IISResetMe!) +- Remove `CommandFactory` because it serves no real purpose. (#5266) +- Change to not insert line breaks at console window width to output (except for tables). (#5193) +- Use `Ast` for context in parameter binding and fix to glob the native command argument only when it's not quoted. (#5188) +- Fix dynamic class assembly name. (#5292) +- Update PowerShell to use `2.0.4-servicing` dotnet core runtime. (#5295) +- Fix `ExecutionContext.LoadAssembly` to load with name when file cannot be found. (#5161) +- Speed up the check for suspicious content in script texts. (#5302) +- Use native `os_log` APIs on macOS for PowerShell Core logging. (#5310) +- Redirect `ETW` logging to `Syslog` on Linux. (#5144) +- Improve how we pass the array literal to native commands. (#5301) +- Make `SemanticVersion` compatible with `SemVer 2.0`. (#5037) (Thanks @iSazonov!) +- Revert refactoring changes that broke remoting to Windows PowerShell 5.1. (#5321) +- Port some fixes in `Job` for an issue that causes PowerShell to not respond. (#5258) +- Multiple improvements by `CodeRush` static analysis. (#5132) (Thanks @Himura2la!) +- Fix the Runspace cleanup issue that causes PowerShell to not respond on exit. (#5356) +- Update PowerShell to depend on new version of `psrp` and `libmi` nuget packages on Unix platforms. (#5469) + +### General cmdlet updates and fixes + +- Add `-AsHashtable` to `ConvertFrom-Json` to return a `Hashtable` instead. (#5043) (Thanks @bergmeister!) +- Fix `Import-module` to not report a loaded module was not found. (#5238) +- Fix performance issues in `Add-Type`. (#5243) (Thanks @iSazonov!) +- Fix `PSUserAgent` generation for Web Cmdlets on Windows 7. (#5256) (Thanks @markekraus!) +- Remove `DCOM` support from `*-Computer` cmdlets. (#5277) +- Add multiple link header support to Web Cmdlets. (#5265) (Thanks @markekraus!) +- Use wider columns for process id and user. (#5303) +- Add `Remove-Alias` Command. (#5143) (Thanks @PowershellNinja!) +- Update `installpsh-suse.sh` to work with the `tar.gz` package. (#5309) +- Add `Jobject` serialization support to `ConvertTo-Json`. (#5141) +- Display full help with 'help' function. (#5195) (Thanks @rkeithhill!) +- Fix `help` function to not pipe to `more` if objects are returned instead of help text. (#5395) +- Fix `Unblock-File` to not write an error if the file is already unblocked. (#5362) (Thanks @iSazonov!) +- Clean up FullCLR code from Web Cmdlets. (#5376) (Thanks @markekraus!) +- Exclude cmdlets that are not supported on Unix platforms. (#5083) +- Make `Import-Csv` support `CR`, `LF` and `CRLF` as line delimiters. (#5363) (Thanks @iSazonov!) +- Fix spelling in Web Cmdlet errors. (#5427) (Thanks @markekraus!) +- Add `SslProtocol` support to Web Cmdlets. (#5329) (Thanks @markekraus!) + +### Build and Packaging Improvements + +- Use `RCEdit` to embed icon and version information into `pwsh.exe`. (#5178) +- Update Docker file for Nano Server 1709 release. (#5252) +- Change VSCode build task to use `pwsh`. (#5255) +- Refactor building and packaging scripts for signing in release build workflow. (#5300) +- Always build with `-CrossGen` in CI to verify a fix in `CrossGen` tool. (#5315) +- Separate `Install-PowerShellRemoting.ps1` from `psrp.windows` nuget package. (#5330) +- Include symbols folder an embedded zip when packaging symbols. (#5333) +- Add Uniform Type Identifier conforming with Apple standards using a reverse DNS style prefix. (#5323) +- Update `Wix` toolset download link to newer version 3.11 (#5339) (Thanks @bergmeister!) +- Re-enable macOS launcher after fixing an issue that blocked macOS package generation. (#5291) (Thanks @thezim!) +- Set expected binaries and variable name for folder for symbols build. (#5357) +- Rename and update PowerShell `ETW` manifest to remove the Windows PowerShell dependency. (#5360) +- Add ability to produce `tar.gz` package for Raspbian. (#5387) +- Update `Find-Dotnet` to find dotnet with the compatible SDK. (#5341) (Thanks @rkeithhill!) +- Add signing manifest and script to update it with production values. (#5397) +- Add `install-powershell.ps1` to install PowerShell Core on windows. (#5383) +- Make `-Name` a dynamic parameter in `Start-PSPackage`. (#5415) +- Support `[package]` tag in PR CI and fix nightly build on macOS. (#5410) +- Enhance `install-powershell.ps1` to work on Linux and macOS. (#5411) +- Move the `RCEdit` step to the build phase rather than the packaging phase. (#5404) +- Allow packaging from a zip package to allow for signing. (#5418) +- Add automation to validate PowerShell Core packages using Docker containers. (#5401) +- Fix the `brew update` issue in bootstrap script. (#5400) +- Enable `install-powershell.ps1` to update the current running PowerShell Core. (#5429) +- Add standard set of VSCode workspace setting files. (#5457) (Thanks @rkeithhill!) +- Add support for installing PowerShell Core on Amazon Linux via `install-powershell.sh`. (#5461) (Thanks @DarwinJS!) +- Get `PowerShellGet` and `PackageManagement` from the PowerShell Gallery. (#5452) +- Fix `Start-PSBuild` on `WSL` if repository was already built on Windows. (#5346) (Thanks @bergmeister!) +- Fix build in VSCode and use an improved version of `tasks.json` from @rkeithhill. (#5453) +- Add scripts for signing packages in the release build workflow. (#5463) + +### Documentation and Help Content + +- Fix the codebase to use the consistent copyright string. (#5210) +- Add documentation about how to create `libpsl` and `psrp.windows` nuget packages. (#5278) +- Add help strings in PowerShell banner. (#5275) (Thanks @iSazonov!) +- Change all links in `README.md` to absolute as they are being used in other places outside of GitHub. (#5354) +- Update instructions to build on VSCode based on `pwsh`. (#5368) +- Update `FAQ.md` about how to use PowerShell Core nuget packages. (#5366) +- Correct the Fedora documentation (#5384) (Thanks @offthewoll!) +- Add instructions about how to create the `PowerShell.Core.Instrumentation` nuget package. (#5396) +- Updated PowerShell to use the latest help package. (#5454) + +### Compliance Work + +[Compliance](https://github.com/PowerShell/PowerShell/blob/master/docs/maintainers/issue-management.md#miscellaneous-labels) +work is required for Microsoft to continue to sign and release packages from the project as official Microsoft packages. + +- Replace the word `hang` with something more appropriate and add rules about other terms. (#5213, #5297, #5358) +- Use simplified names for compliance folders (#5388) +- Add compliance label description (#5355) +- Set `requestedExecutionLevel` to `asInvoker` for `pwsh.exe` on Windows. (#5285) +- Add `HighEntropyVA` to building pwsh. (#5455) + +## [6.0.0-beta.9] - 2017-10-24 + +### Breaking changes + +- Fix `ValueFromRemainingArguments` to have consistent behavior between script and C# cmdlets. (#2038) (Thanks @dlwyatt) +- Remove parameters `-importsystemmodules` and `-psconsoleFile` from `powershell.exe`. (#4995) +- Removed code to show a GUI prompt for credentials as PowerShell Core prompts in console. (#4995) +- Remove `-ComputerName` from `Get/Set/Remove-Service`. (#5094) +- Rename the executable name from `powershell` to `pwsh`. (#5101) +- Remove `RunspaceConfiguration` support. (#4942) +- Remove `-ComputerName` support since .NET Core `Process.GetProcesses(computer)` returns local processes. (#4960) +- Make `-NoTypeInformation` the default on `Export-Csv` and `ConvertTo-Csv`. (#5164) (Thanks @markekraus) +- Unify cmdlets with parameter `-Encoding` to be of type `System.Text.Encoding`. (#5080) + +### Engine updates and fixes + +- Fix PowerShell to update the `PATH` environment variable only if `PATH` exists. (#5021) +- Enable support of folders and files with colon in name on Unix. (#4959) +- Fix detection of whether `-LiteralPath` was used to suppress wildcard expansion for navigation cmdlets. (#5038) +- Enable using filesystem from a UNC location. (#4998) +- Escape trailing backslash when dealing with native command arguments. (#4965) +- Change location of `ModuleAnalysisCache` so it isn't shared with Windows PowerShell. (#5133) +- Put command discovery before scripts for Unix. (#5116) + +### General cmdlet updates and fixes + +- Correct comma position in `SecureStringCommands.resx`. (#5033) (Thanks @markekraus) +- User Agent of Web Cmdlets now reports the OS platform (#4937) (Thanks @LDSpits) +- Add the positional parameter attribute to `-InputObject` for `Set-Service`. (#5017) (Thanks @travisty-) +- Add `ValidateNotNullOrEmpty` attribute to `-UFormat` for `Get-Date`. (#5055) (Thanks @DdWr) +- Add `-NoNewLine` switch for `Out-String`. (#5056) (Thanks @raghav710) +- Improve progress messages written by Web Cmdlets. (#5078) (Thanks @markekraus) +- Add verb descriptions and alias prefixes for `Get-Verb`. (#4746) (Thanks @Tadas) +- Fix `Get-Content -Raw` to not miss the last line feed character. (#5076) +- Add authentication parameters to Web Cmdlets. (#5052) (Thanks @markekraus) + - Add `-Authentication` that provides three options: Basic, OAuth, and Bearer. + - Add `-Token` to get the bearer token for OAuth and Bearer options. + - Add `-AllowUnencryptedAuthentication` to bypass authentication that is provided for any transport scheme other than HTTPS. +- Fix `MatchInfoContext` clone implementation (#5121) (Thanks @dee-see) +- Exclude `PSHostProcess` cmdlets from Unix platforms. (#5105) +- Fix `Add-Member` to fetch resource string correctly. (#5114) +- Enable `Import-Module` to be case insensitive. (#5097) +- Add exports for `syslog` APIs in `libpsl-native`. (#5149) +- Fix `Get-ChildItem` to not ignore `-Depth` parameter when using with `-Include` or `-Exclude`. (#4985) (Thanks @Windos) +- Added properties `UserName`, `Description`, `DelayedAutoStart`, `BinaryPathName` and `StartupType` to the `ServiceController` objects returned by `Get-Service`. (#4907) (Thanks @joandrsn) + +### Build and Packaging Improvements + +- Treat `.rtf` files as binary so EOL don't get changed. (#5020) +- Improve the output of `tools/installpsh-osx.sh` and update Travis-CI to use Ruby 2.3.3. (#5065) +- Improve `Start-PSBootstrap` to locate dotnet SDK before installing it. (#5059) (Thanks @PetSerAl) +- Fix the prerequisite check of the MSI package. (#5070) +- Support creating `tar.gz` package for Linux and macOS. (#5085) +- Add release builds that produce symbols for compliance scans. (#5086) +- Update existing Docker files for the Linux package changes. (#5102) +- Add compiler switches and replace dangerous function with safer ones. (#5089) +- Add macOS launcher. (#5138) (Thanks @thezim) +- Replace `httpbin.org/response-headers` Tests with WebListener. (#5058) (Thanks @markekraus) +- Update `appimage.sh` to reflect the new name `pwsh`. (#5172) +- Update the man help file used in packaging. (#5173) +- Update to use `pwsh` in macOS launcher. (#5174) (Thanks @thezim) +- Add code to send web hook for Travis-CI daily build. (#5183) +- Add `global.json` to pick correct SDK version. (#5118) (Thanks @rkeithhill) +- Update packaging to only package PowerShell binaries when packaging symbols. (#5145) +- Update Docker files and related due to the name change. (#5156) + +### Code Cleanup + +- Clean up Json cmdlets. (#5001) (Thanks @iSazonov) +- Remove code guarded by `RELATIONSHIP_SUPPORTED` and `SUPPORTS_IMULTIVALUEPROPERTYCMDLETPROVIDER`, which has never been used. (#5066) +- Remove PSMI code that has never been used. (#5075) +- Remove unreachable code for `Stop-Job`. (#5091) (Thanks @travisty-) +- Removed font and codepage handling code that is only applicable to Windows PowerShell. (#4995) + +### Test + +- Fix a race condition between `WebListener` and Web Cmdlets tests. (#5035) (Thanks @markekraus) +- Add warning to `Start-PSPester` if Pester module is not found (#5069) (Thanks @DdWr) +- Add tests for DSC configuration compilation on Windows. (#5011) +- Test fixes and code coverage automation fixes. (#5046) + +### Documentation and Help Content + +- Update Pi demo instructions about installing libunwind8. (#4974) +- Add links on best practice guidelines in coding guideline. (#4983) (Thanks @iSazonov) +- Reformat command line help for `powershell -help` (#4989) (Thanks @iSazonov) +- Change logo in readme to current black icon. (#5030) +- Fix RPM package name in `README.md`. (#5044) +- Update `docs/building/linux.md` to reflect the current status of powershell build. (#5068) (Thanks @dee-see) +- Add black version of `.icns` file for macOS. (#5073) (Thanks @thezim) +- Update Arch Linux installation instructions. (#5048) (Thanks @kylesferrazza) +- Add submodule reminder to `testing-guidelines.md`. (#5061) (Thanks @DdWr) +- Update instructions in `docs/building/internals.md` for building from source. (#5072) (Thanks @kylesferrazza) +- Add UserVoice link to Issue Template. (#5100) (Thanks @markekraus) +- Add `Get-WebListenerUrl` Based Examples to WebListener `README.md`. (#4981) (Thanks @markekraus) +- Add document about how to create cmdlet with dotnet CLI. (#5117) (Thanks @rkeithhill) +- Update the help text for PowerShell executable with the new name `pwsh`. (#5182) +- Add new forward links for PowerShell 6.0.0 help content. (#4978) +- Fix VSCode `launch.json` to point to `pwsh`. (#5189) +- Add example of how to create .NET Core cmdlet with Visual Studio. (#5096) + +## [6.0.0-beta.8] - 2017-10-05 + +### Breaking changes + +* Changed `New-Service` to return error when given unsupported `-StartupType` and fixed `Set-Service` icon failing test. (#4802) +* Allow `*` to be used in registry path for `Remove-Item`. (#4866) +* Remove unsupported `-ShowWindow` switch from `Get-Help`. (#4903) +* Fix incorrect position of a parameter which resulted in the args passed as input instead of as args for `InvokeScript()`. (#4963) + +### Engine updates and fixes + +* Make calls to `void CodeMethod` work. (#4850) (Thanks @powercode) +* Get `PSVersion` and `GitCommitId` from the `ProductVersion` attribute of assembly (#4863) (Thanks @iSazonov) +* Fix `powershell -version` and built-in help for `powershell.exe` to align with other native tools. (#4958 & #4931) (Thanks @iSazonov) +* Load assemblies with `Assembly.LoadFrom` before `Assembly.Load` when the file path is given. (#4196) +* Add a generic file watcher function in `HelpersCommon.psm1`. (#4775) +* Update old links and fix broken links in `docs/host-powershell/README.md`. (#4877) +* Fix when importing remote modules using version filters (and added tests). (#4900) +* Enable transcription of native commands on non-Windows platforms. (#4871) +* Add a new line to `CommandNotFoundException` error string. (#4934 & #4991) +* Fix bug where PowerShell would exit with an error within an SSH remoting connection on Linux. (#4993) +* Fix issues with expression redirected to file. (#4847) + +### General cmdlet updates and fixes + +* Added `Remove-Service` to Management module. (#4858) (Thanks @joandrsn) +* Added functionality to set credentials on `Set-Service` command. (#4844) (Thanks @joandrsn) +* Fix `Select-String` to exclude directories (as opposed to individual files) discovered from `-Path`. (#4829) (Thanks @iSazonov) +* `Get-Date` now supports more argument completion scenarios by adding `ArgumentCompletionsAttribute`. (#4835) (Thanks @iSazonov) +* Exclude `-ComObject` parameter of `New-Object` on unsupported (currently non-Windows) platforms. (#4922) (Thanks @iSazonov) +* Updated default `ModuleVersion` in `New-ModuleManifest` to `0.0.1` to align with SemVer. (#4842) (Thanks @LDSpits) +* Add Multipart support to web cmdlets. (#4782) (Thanks @markekraus) +* Add `-ResponseHeadersVariable` to `Invoke-RestMethod` to enable the capture of response headers. (#4888) (Thanks @markekraus) +* Initialize web cmdlets headers dictionary only once. (#4853) (Thanks @markekraus) +* Change web cmdlets `UserAgent` from `WindowsPowerShell` to `PowerShell`. (#4914) (Thanks @markekraus) + +### Build and Packaging Improvements + +* Make the build output the WiX compilation log if it failed. (#4831) (Thanks @bergmeister) +* Use a simple file based check in the MSI for the VC++ 2015 redistributables. (#4745) (Thanks @bergmeister) +* New icon for PowerShell Core. (#4848) +* Build Powershell Core using the generic RID `linux-x64`. (#4841) +* Create generic Linux-x64 packages that are portable to all supported RPM Linux distros (and more similar for Debian based distros). (#4902 & #4994) +* Suppress the output of building test tools in `Compress-TestContent`. (#4957) +* Remove unnecessary error messages from output. (#4954) +* Update Travis CI script so that PRs can fail due to Pester tests. (#4830) +* Move release build definition into PowerShell. (#4884) +* Fix credential scan issues. (#4927 & #4935) +* Enable security flags in native compiler. (#4933) +* Add VS 2017 solution file for `powershell-win-core`. (#4748) + +### Code Cleanup + +* Remove remainder of `Utility.Activities` (Workflow code). (#4880) +* Remove `Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll`. (#4868) +* Enable auto EOL on Git repo side, fix some character encoding issues. (#4912) +* Updated EOL for all files to be LF in the repository. (#4943 & #4956) +* Removed leading whitespace. (#4991) + +### DSC Language + +* Update version of `PSDesiredStateConfiguration` in project files to fix complication of MOF files with the `Configuration` keyword. (#4979) + +### Test + +* Replace httpbin.org tests with `WebListener`. (Thanks @markekraus) + * headers (#4799) + * user-agent (#4798) + * redirect (#4852) + * encoding (#4869) + * delay (#4905) + * gzip & enable deflate (#4948) + * related changes and fixes (#4920) +* Port tests for constrained language mode. (#4816) +* Enable `Select-String` test from a network path. (#4921) (Thanks @iSazonov) +* Reformat `Measure-Object` test. (#4972) (Thanks @iSazonov) +* Mitigate intermittent failures in access denied tests. (#4788) +* Fix tests that incorrectly use `ShouldBeErrorId`. (#4793) +* Fix a test issue that causes tests to be skipped in Travis CI run (#4891) +* Skip web cmdlet certificate authentication tests on CentOS and Mac. (#4822) +* Validate product resource strings against resx files. (#4811 & #4861) +* Add source files for coverage run. (#4925) +* Add the UTC offset correctly in tests for CDXML cmdlets. (#4867) +* Be sure to change `PSDefaultParameterValue` in the global scope. (#4977 & #4892) +* Reduce output of Pester for CI. (#4855) +* Add tests for + * `Get-Content` (#4723) (Thanks @sarithsutha) + * Remoting and Jobs (#4928) + * `Get-Help` (#4895) + * `Get-Command -ShowCommandInfo` (#4906) + * `Get-Content -Tail` (#4790) + * `Get-Module` over remoting (#4787) + * `Start/Stop/Suspend/Resume/Restart-Service` cmdlets (#4774) + * WSMan Config provider tests (#4756) + * CDXML CIM `DateTime` test (#4796) + +### Documentation and Graphics + +* Sort `.spelling` (Thanks @markekraus) +* Improve the guideline for performance consideration. (#4824) +* Add setup steps for MacOS to use PSRP over SSH. (#4872) +* Instructions to demo PowerShell Core on Raspbian. (#4882) +* Added instructions to get permission to use PowerShell image assets. (#4938) +* Added demo for using Windows PowerShell modules. (#4886) + +## [6.0.0-beta.7] - 2017-09-13 + +### Breaking change + +* Fix `Get-Content -Delimiter` to not include the delimiter in the array elements returned (#3706) (Thanks @mklement0) +* Rename `$IsOSX` to `$IsMacOS` (#4757) + +### Engine updates and fixes + +* Use stricter rules when unwrapping a PSObject that wraps a COM object (#4614) +* Remove appended Windows PowerShell `PSModulePath` on Windows. (#4656) +* Ensure `GetNetworkCredential()` returns null if PSCredential has null or empty user name (#4697) +* Push locals of automatic variables to 'DottedScopes' when dotting script cmdlets (#4709) +* Fix `using module` when module has non-terminating errors handled with `SilentlyContinue` (#4711) (Thanks @iSazonov) +* Enable use of 'Singleline,Multiline' option in split operator (#4721) (Thanks @iSazonov) +* Fix error message in `ValidateSetAttribute.ValidateElement()` (#4722) (Thanks @iSazonov) + +### General cmdlet updates and fixes + +* Add Meta, Charset, and Transitional parameters to `ConvertTo-HTML` (#4184) (Thanks @ergo3114) +* Prevent `Test-ModuleManifest` from loading unnecessary modules (#4541) +* Remove AlternateStream code and `-Stream` from provider cmdlets on non-Windows (#4567) +* Add explicit ContentType detection to `Invoke-RestMethod` (#4692) +* Fix an error on `Enter-PSSession` exit (#4693) +* Add `-WhatIf` switch to `Start-Process` cmdlet (#4735) (Thanks @sarithsutha) +* Remove double spaces in .cs, .ps1, and .resx files (#4741 & #4743) (Thanks @korygill) +* Replace 'Windows PowerShell' with 'PowerShell' in resx files (#4758) (Thanks @iSazonov) + +### Build and Packaging Improvements + +* Refactor MSBuild project files to get PowerShell version from git tag (#4182) (Thanks @iSazonov) +* Create a single package for each Windows supported architecture (x86 and amd64) (#4540) +* Set the default windows RID to win7- (#4701) +* Enable cross-compiling for Raspberry-PI arm32 (#4742) +* Fix macOS brew reinstall command (#4627) (Thanks @TheNewStellW) +* Improvements to the Travis-CI script (#4689, #4731, #4807) +* Update OpenSUSE docker image to 42.2 (#4737) +* Confirm `Start-PSPackage` produces a package (#4795) + +### Code Cleanup + +* Remove Workflow code (#4777) +* Clean up CORECLR preprocessor directives in TraceSource (#4684) + +### Test + +* Add test WebListener module and tests for Web Cmdlet Certificate Authentication (#4622) (Thanks @markekraus) +* Move WebCmdlets HTTPS tests to WebListener (#4733) (Thanks @markekraus) +* Replace httpbin.org/get tests With WebListener (#4738) (Thanks @markekraus) +* Use `-PassThru` on Pester tests to reliably catch failures (#4644) +* Display the same number of tests regardless of platform (#4728) +* Improve comparison of code coverage values for a file (#4764) +* Silence PSSessionConfiguration test warning messages in the log (#4794) +* Add tests for + * `Get-Service` (#4773) + * `Set-Service` and `New-Service` (#4785) + * `Trace-Command` (#4288) + * `StaticParameter` (#4779) + * `Test-Wsman` (#4771) + * `New-Object -ComObject` (#4776) + * ProxyCommand APIs (#4791) +* Disable tests + * 'VC++ Redistributable'(#4673 & #4729) + * "Test 01. Standard Property test - all properties ()" due to missing CsPhysicallyInstalledMemory (#4763) + * `New-Service` failing test (#4806) + +### Documentation + +* Update WritingPesterTests.md to recommend ShouldBeErrorId (#4637) +* Clarify the Pull Request process, roles, and responsibilities (#4710) +* Add absolute URLs in the issue template and pull request template (#4718) (Thanks @chucklu) +* Add new approved Build and Deploy verbs (#4725) +* Update using-vscode.md to use the new exe path (#4736) +* Update coding guidelines to make it more concrete and useful in a review process (#4754) + +## [6.0.0-beta.6] - 2017-08-24 + +### Breaking change + +* Make invalid argument error messages for `-File` and `-Command` consistent and make exit codes consistent with Unix standards (#4573) + +### Engine updates and fixes + +* Make resource loading to work with PowerShell SxS installation (#4139) +* Add missing assemblies to TPA list to make Pwrshplughin.dll work (#4502) +* Make sure running `powershell` starts instance of the current version of PowerShell. (#4481) +* Make sure we only use Unicode output by default on Nano and IoT systems (#4074) +* Enable `powershell -WindowStyle` to work on Windows. (#4573) +* Enable enumeration of COM collections. (#4553) + +### General cmdlet updates and fixes + +* Fix Web CmdLets `-SkipHeaderValidation` to work with non-standard User-Agent headers. (#4479 & #4512) (Thanks @markekraus) +* Add Certificate authentication support for Web CmdLets. (#4646) (Thanks @markekraus) +* Add support for content headers to Web CmdLets. (#4494 & #4640) (Thanks @markekraus) +* Add support for converting enums to string (#4318) (Thanks @KirkMunro) +* Ignore casing when binding PSReadline KeyHandler functions (#4300) (Thanks @oising) +* Fix `Unblock-File` for the case of a read-only file. (#4395) (Thanks @iSazonov) +* Use supported API to set Central Access Policy ID (CAPID) in SACL. (#4496) +* Make `Start-Trace` support paths that require escaping in the underlying APIs (#3863) +* Removing `#if CORECLR` enabled, `Enable-PSRemoting` and `Disable-PSRemoting` (#2671) +* Enable WSManCredSSP cmdlets and add tests. (#4336) +* Use .NET Core's implementation for ShellExecute. (#4523) +* Fix SSH Remoting handling of KeyFileParameter when the path must be quoted. (#4529) +* Make Web CmdLets use HTML meta charset attribute value, if present (#4338) +* Move to .NET Core 2.0 final (#4603) + +### Build/test and code cleanup + +* Add Amazon Linux Docker image and enable related tests. (#4393) (Thanks @DarwinJS) +* Make MSI verify pre-requisites are installed. (#4602) (Thank @bergmeister) +* Fixed formatting issues in build files. (#4630) (Thanks @iSazonov) +* Make sure `install-powershell.sh` installs latest powershell on macOS, even if an old version is cached in brew. (#4509) (Thanks @richardszalay for reporting.) +* Fixes install scripts issue for macOS. (#4631) (Thanks @DarwinJS) +* Many stability improvements to our nightly code coverage automation. (#4313 & #4550) +* Remove hash validation from nanoserver-insider Docker file, due to frequent changes. (#4498) +* Update to make Travis-CI daily build badge more reliable. (#4522) +* Remove unused build files, build code, and product code. (#4532, #4580, #4590, #4589, #4588, #4587, #4586, #4583, #4582, #4581) +* Add additional acceptance tests for PowerShellGet. (#4531) +* Only publish a NuGet of the full PowerShell core package on daily builds and not merge. (#4517) +* Update nanoserver-insider Docker file due to breaking changes in the base image. (#4555) +* Cleanup engine tests (#4551) +* Fix intermittent failures in filesystem tests (#4566) +* Add tests for + * `New-WinEvent`. (#4384) + * tab completion. (#4560) + * various types. (#4503) + * CDXML CmdLets. (#4537) +* Only allow packaging of powershell, if it was built from a repo at the root of the file system named powershell. (#4569 & #4600) +* Update `Format-Hex` test cases to use -TestCase instead of foreach loops. (#3800) +* Added functionality to get code coverage for a single file locally. (#4556) + +### Documentation + +* Added Ilya (@iSazonov) as a Maintainer. (#4365) +* Grammar fix to the Pull Request Guide. (#4322) +* Add homebrew for macOS to install documentation. (#3838) +* Added a CodeOwner file. (#4565 & #4597) + +### Cleanup `#if CORECLR` code + +PowerShell 6.0 will be exclusively built on top of CoreCLR, +so we are removing a large amount of code that's built only for FullCLR. +To read more about this, check out [this blog post](https://devblogs.microsoft.com/powershell/powershell-6-0-roadmap-coreclr-backwards-compatibility-and-more/). + +## [6.0.0-beta.5] - 2017-08-02 + +### Breaking changes + +* Remove the `*-Counter` cmdlets in `Microsoft.PowerShell.Diagnostics` due to the use of unsupported APIs until a better solution is found. (#4303) +* Remove the `Microsoft.PowerShell.LocalAccounts` due to the use of unsupported APIs until a better solution is found. (#4302) + +### Engine updates and fixes + +* Fix the issue where PowerShell Core wasn't working on Windows 7 or Windows Server 2008 R2/2012 (non-R2). (#4463) +* `ValidateSetAttribute` enhancement: support set values to be dynamically generated from a custom `ValidateSetValueGenerator`. (#3784) (Thanks to @iSazonov!) +* Disable breaking into debugger on Ctrl+Break when running non-interactively. (#4283) (Thanks to @mwrock!) +* Give error instead of crashing if WSMan client library is not available. (#4387) +* Allow passing `$true`/`$false` as a parameter to scripts using `powershell.exe -File`. (#4178) +* Enable `DataRow`/`DataRowView` adapters in PowerShell Core to fix an issue with `DataTable` usage. (#4258) +* Fix an issue where PowerShell class static methods were being shared across `Runspace`s/`SessionState`s. (#4209) +* Fix array expression to not return null or throw error. (#4296) +* Fixes a CIM deserialization bug where corrupted CIM classes were instantiating non-CIM types. (#4234) +* Improve error message when `HelpMessage` property of `ParameterAttribute` is set to empty string. (#4334) +* Make `ShellExecuteEx` run in a STA thread. (#4362) + +### General cmdlet updates and fixes + +* Add `-SkipHeaderValidation` switch to `Invoke-WebRequest` and `Invoke-RestMethod` to support adding headers without validating the header value. (#4085) +* Add support for `Invoke-Item -Path `. (#4262) +* Fix `ConvertTo-Html` output when using a single column header. (#4276) +* Fix output of `Length` for `FileInfo` when using `Format-List`. (#4437) +* Fix an issue in implicit remoting where restricted sessions couldn't use `Get-FormatData �PowerShellVersion`. (#4222) +* Fix an issue where `Register-PSSessionConfiguration` fails if `SessionConfig` folder doesn't exist. (#4271) + +### Installer updates + +* Create script to install latest PowerShell from Microsoft package repositories (or Homebrew) on non-Windows platforms. (#3608) (Thanks to @DarwinJS!) +* Enable MSI upgrades rather than a side-by-side install. (#4259) +* Add a checkbox to open PowerShell after the Windows MSI installer has finished. (#4203) (Thanks to @bergmeister!) +* Add Amazon Linux compatibility to `install-powershell.sh`. (#4360) (Thanks to @DarwinJS!) +* Add ability to package PowerShell Core as a NuGet package. (#4363) + +### Build/test and code cleanup + +* Add build check for MFC for Visual C++ during Windows builds. + This fixes a long-standing (and very frustrating!) issue with missing build dependencies! (#4185) (Thanks to @KirkMunro!) +* Move building Windows PSRP binary out of `Start-PSBuild`. + Now `Start-PSBuild` doesn't build PSRP binary on windows. Instead, we consume the PSRP binary from a NuGet package. (#4335) +* Add tests for built-in type accelerators. (#4230) (Thanks to @dchristian3188!) +* Increase code coverage of `Get-ChildItem` on file system. (#4342) (Thanks to @jeffbi!) +* Increase test coverage for `Rename-Item` and `Move-Item`. (#4329) (Thanks to @jeffbi!) +* Add test coverage for Registry provider. (#4354) (Thanks to @jeffbi!) +* Fix warnings and errors thrown by PSScriptAnalyzer. (#4261) (Thanks to @bergmeister!) +* Fix regressions that cause implicit remoting tests to fail. (#4326) +* Disable legacy UTC and SQM Windows telemetry by enclosing the code in '#if LEGACYTELEMETRY'. (#4190) + +### Cleanup `#if CORECLR` code + +PowerShell 6.0 will be exclusively built on top of CoreCLR, +so we are removing a large amount of code that's built only for FullCLR. +To read more about this, check out [this blog post](https://devblogs.microsoft.com/powershell/powershell-6-0-roadmap-coreclr-backwards-compatibility-and-more/). + +## [6.0.0-beta.4] - 2017-07-12 + +## Windows PowerShell backwards compatibility + +In the `beta.4` release, we've introduced a change to add the Windows PowerShell `PSModulePath` to the default `PSModulePath` in PowerShell Core on Windows. (#4132) + +Along with the introduction of .NET Standard 2.0 in `6.0.0-beta.1` and a GAC probing fix in `6.0.0-beta.3`, +**this change will enable a large number of your existing Windows PowerShell modules/scripts to "just work" inside of PowerShell Core on Windows**. +(Note: We have also fixed the CDXML modules on Windows that were regressed in `6.0.0-beta.2` as part of #4144). + +So that we can further enable this backwards compatibility, +we ask that you tell us more about what modules or scripts do and don't work in Issue #4062. +This feedback will also help us determine if `PSModulePath` should include the Windows PowerShell values by default in the long run. + +For more information on this, we invite you to read [this blog post explaining PowerShell Core and .NET Standard in more detail](https://blogs.msdn.microsoft.com/powershell/?p=13355). + +### Engine updates and fixes + +- Add Windows PowerShell `PSModulePath` by default on Windows. (#4132) +- Move PowerShell to `2.0.0-preview3-25426-01` and using the .NET CLI version `2.0.0-preview2-006502`. (#4144) +- Performance improvement in PSReadline by minimizing writing ANSI escape sequences. (#4110) +- Implement Unicode escape parsing so that users can use Unicode characters as arguments, strings or variable names. (#3958) (Thanks to @rkeithhill!) +- Script names or full paths can have commas. (#4136) (Thanks to @TimCurwick!) +- Added `semver` as a type accelerator for `System.Management.Automation.SemanticVersion`. (#4142) (Thanks to @oising!) +- Close `eventLogSession` and `EventLogReader` to unlock an ETL log. (#4034) (Thanks to @iSazonov!) + +### General cmdlet updates and fixes + +- `Move-Item` cmdlet honors `-Include`, `-Exclude`, and `-Filter` parameters. (#3878) +- Add a parameter to `Get-ChildItem` called `-FollowSymlink` that traverses symlinks on demand, with checks for link loops. (#4020) +- Change `New-ModuleManifest` encoding to UTF8NoBOM on non-Windows platforms. (#3940) +- `Get-AuthenticodeSignature` cmdlets can now get file signature timestamp. (#4061) +- Add tab completion for `Export-Counter` `-FileFormat` parameter. (#3856) +- Fixed `Import-Module` on non-Windows platforms so that users can import modules with `NestedModules` and `RootModules`. (#4010) +- Close `FileStream` opened by `Get-FileHash`. (#4175) (Thanks to @rkeithhill!) + +### Remoting + +- Fixed PowerShell not responding when the SSH client abruptly terminates. (#4123) + +### Documentation + +- Added recommended settings for VS Code. (#4054) (Thanks to @iSazonov!) + +## [6.0.0-beta.3] - 2017-06-20 + +### Breaking changes + +- Remove the `BuildVersion` property from `$PSVersionTable`. + This property was strongly tied to the Windows build version. + Instead, we recommend that you use `GitCommitId` to retrieve the exact build version of PowerShell Core. + (#3877) (Thanks to @iSazonov!) +- Change positional parameter for `powershell.exe` from `-Command` to `-File`. + This fixes the usage of `#!` (aka as a shebang) in PowerShell scripts that are being executed from non-PowerShell shells on non-Windows platforms. + This also means that you can now do things like `powershell foo.ps1` or `powershell fooScript` without specifying `-File`. + However, this change now requires that you explicitly specify `-c` or `-Command` when trying to do things like `powershell.exe Get-Command`. + (#4019) +- Remove `ClrVersion` property from `$PSVersionTable`. + (This property is largely irrelevant for .NET Core, + and was only preserved in .NET Core for specific legacy purposes that are inapplicable to PowerShell.) + (#4027) + +### Engine updates and fixes + +- Add support to probe and load assemblies from GAC on Windows platform. + This means that you can now load Windows PowerShell modules with assembly dependencies which reside in the GAC. + If you're interested in running your traditional Windows PowerShell scripts and cmdlets using the power of .NET Standard 2.0, + try adding your Windows PowerShell module directories to your PowerShell Core `$PSModulePath`. + (E.g. `$env:PSModulePath += ';C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules'`) + Even if the module isn't owned by the PowerShell Team, please tell us what works and what doesn't by leaving a comment in [issue #4062][issue-4062]! (#3981) +- Enhance type inference in tab completion based on runtime variable values. (#2744) (Thanks to @powercode!) + This enables tab completion in situations like: + + ```powershell + $p = Get-Process + $p | Foreach-Object Prio + ``` + +- Add `GitCommitId` to PowerShell Core banner. + Now you don't have to run `$PSVersionTable` as soon as you start PowerShell to get the version! (#3916) (Thanks to @iSazonov!) +- Fix a bug in tab completion to make `native.exe --` call into native completer. (#3633) (Thanks to @powercode!) +- Fix PowerShell Core to allow use of long paths that are more than 260 characters. (#3960) +- Fix ConsoleHost to honour `NoEcho` on Unix platforms. (#3801) +- Fix transcription to not stop when a Runspace is closed during the transcription. (#3896) + +[issue-4062]: https://github.com/PowerShell/PowerShell/issues/4062 + +### General cmdlet updates and fixes + +- Enable `Send-MailMessage` in PowerShell Core. (#3869) +- Fix `Get-Help` to support case insensitive pattern matching on Unix platforms. (#3852) +- Fix tab completion on `Get-Help` for `about_*` topics. (#4014) +- Fix PSReadline to work in Windows Server Core container image. (#3937) +- Fix `Import-Module` to honour `ScriptsToProcess` when `-Version` is specified. (#3897) +- Strip authorization header on redirects with web cmdlets. (#3885) +- `Start-Sleep`: add the alias `ms` to the parameter `-Milliseconds`. (#4039) (Thanks to @Tadas!) + +### Developer experience + +- Make hosting PowerShell Core in your own .NET applications much easier by refactoring PowerShell Core to use the default CoreCLR loader. (#3903) +- Update `Add-Type` to support `CSharpVersion7`. (#3933) (Thanks to @iSazonov) + +## [6.0.0-beta.2] - 2017-06-01 + +### Support backgrounding of pipelines with ampersand (`&`) (#3360) + +- Putting `&` at the end of a pipeline will cause the pipeline to be run as a PowerShell job. +- When a pipeline is backgrounded, a job object is returned. +- Once the pipeline is running as a job, all of the standard `*-Job` cmdlets can be used to manage the job. +- Variables (ignoring process-specific variables) used in the pipeline are automatically copied to the job so `Copy-Item $foo $bar &` just works. +- The job is also run in the current directory instead of the user's home directory. +- For more information about PowerShell jobs, see [about_Jobs](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_jobs). + +### Engine updates and fixes + +- Crossgen more of the .NET Core assemblies to improve PowerShell Core startup time. (#3787) +- Enable comparison between a `SemanticVersion` instance and a `Version` instance that is constructed only with `Major` and `Minor` version values. + This will fix some cases where PowerShell Core was failing to import older Windows PowerShell modules. (#3793) (Thanks to @mklement0!) + +### General cmdlet updates and fixes + +- Support Link header pagination in web cmdlets (#3828) + - For `Invoke-WebRequest`, when the response includes a Link header we create a RelationLink property as a Dictionary representing the URLs and `rel` attributes and ensure the URLs are absolute to make it easier for the developer to use. + - For `Invoke-RestMethod`, when the response includes a Link header we expose a `-FollowRelLink` switch to automatically follow `next` `rel` links until they no longer exist or once we hit the optional `-MaximumFollowRelLink` parameter value. +- Update `Get-ChildItem` to be more in line with the way that the *nix `ls -R` and the Windows `DIR /S` native commands handle symbolic links to directories during a recursive search. + Now, `Get-ChildItem` returns the symbolic links it encountered during the search, but it won't search the directories those links target. (#3780) +- Fix `Get-ChildItem` to continue enumeration after throwing an error in the middle of a set of items. + This fixes some issues where inaccessible directories or files would halt execution of `Get-ChildItem`. (#3806) +- Fix `ConvertFrom-Json` to deserialize an array of strings from the pipeline that together construct a complete JSON string. + This fixes some cases where newlines would break JSON parsing. (#3823) +- Enable `Get-TimeZone` for macOS/Linux. (#3735) +- Change to not expose unsupported aliases and cmdlets on macOS/Linux. (#3595) (Thanks to @iSazonov!) +- Fix `Invoke-Item` to accept a file path that includes spaces on macOS/Linux. (#3850) +- Fix an issue where PSReadline was not rendering multi-line prompts correctly on macOS/Linux. (#3867) +- Fix an issue where PSReadline was not working on Nano Server. (#3815) + +## [6.0.0-beta.1] - 2017-05-08 + +### 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) +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! + +### Telemetry + +- For the first beta of PowerShell Core 6.0, telemetry has been to the console host to report two values (#3620): + - the OS platform (`$PSVersionTable.OSDescription`) + - the exact version of PowerShell (`$PSVersionTable.GitCommitId`) + +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/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. + +### Engine updates and fixes + +- Add support for native command globbing on Unix platforms. (#3643) + - This means you can now use wildcards with native binaries/commands (e.g. `ls *.txt`). +- Fix PowerShell Core to find help content from `$PSHome` instead of the Windows PowerShell base directory. (#3528) + - This should fix issues where about_* topics couldn't be found on Unix platforms. +- Add the `OS` entry to `$PSVersionTable`. (#3654) +- Arrange the display of `$PSVersionTable` entries in the following way: (#3562) (Thanks to @iSazonov!) + - `PSVersion` + - `PSEdition` + - alphabetical order for rest entries based on the keys +- Make PowerShell Core more resilient when being used with an account that doesn't have some key environment variables. (#3437) +- Update PowerShell Core to accept the `-i` switch to indicate an interactive shell. (#3558) + - This will help when using PowerShell as a default shell on Unix platforms. +- Relax the PowerShell `SemanticVersion` constructors to not require 'minor' and 'patch' portions of a semantic version name. (#3696) +- Improve performance to security checks when group policies are in effect for ExecutionPolicy. (#2588) (Thanks to @powercode) +- Fix code in PowerShell to use `IntPtr(-1)` for `INVALID_HANDLE_VALUE` instead of `IntPtr.Zero`. (#3544) (Thanks to @0xfeeddeadbeef) + +### General cmdlet updates and fixes + +- Change the default encoding and OEM encoding used in PowerShell Core to be compatible with Windows PowerShell. (#3467) (Thanks to @iSazonov!) +- Fix a bug in `Import-Module` to avoid incorrect cyclic dependency detection. (#3594) +- Fix `New-ModuleManifest` to correctly check if a URI string is well formed. (#3631) + +### Filesystem-specific updates and fixes + +- Use operating system calls to determine whether two paths refer to the same file in file system operations. (#3441) + - This will fix issues where case-sensitive file paths were being treated as case-insensitive on Unix platforms. +- Fix `New-Item` to allow creating symbolic links to file/directory targets and even a non-existent target. (#3509) +- Change the behavior of `Remove-Item` on a symbolic link to only removing the link itself. (#3637) +- Use better error message when `New-Item` fails to create a symbolic link because the specified link path points to an existing item. (#3703) +- Change `Get-ChildItem` to list the content of a link to a directory on Unix platforms. (#3697) +- Fix `Rename-Item` to allow Unix globbing patterns in paths. (#3661) + +### Interactive fixes + +- Add Hashtable tab completion for `-Property` of `Select-Object`. (#3625) (Thanks to @powercode) +- Fix tab completion with `@{` to avoid crash in PSReadline. (#3626) (Thanks to @powercode) +- Use ` - ` as `ToolTip` and `ListItemText` when tab completing process ID. (#3664) (Thanks to @powercode) + +### Remoting fixes + +- Update PowerShell SSH remoting to handle multi-line error messages from OpenSSH client. (#3612) +- Add `-Port` parameter to `New-PSSession` to create PowerShell SSH remote sessions on non-standard (non-22) ports. (#3499) (Thanks to @Lee303) + +### API Updates + +- Add the public property `ValidRootDrives` to `ValidateDriveAttribute` to make it easy to discover the attribute state via `ParameterMetadata` or `PSVariable` objects. (#3510) (Thanks to @indented-automation!) +- Improve error messages for `ValidateCountAttribute`. (#3656) (Thanks to @iSazonov) +- Update `ValidatePatternAttribute`, `ValidateSetAttribute` and `ValidateScriptAttribute` to allow users to more easily specify customized error messages. (#2728) (Thanks to @powercode) + +## [6.0.0-alpha.18] - 2017-04-05 + +### Progress Bar + +We made a number of fixes to the progress bar rendering and the `ProgressRecord` object that improved cmdlet performance and fixed some rendering bugs on non-Windows platforms. + +- Fix a bug that caused the progress bar to drift on Unix platforms. (#3289) +- Improve the performance of writing progress records. (#2822) (Thanks to @iSazonov!) +- Fix the progress bar rendering on Unix platforms. (#3362) (#3453) +- Reuse `ProgressRecord` in Web Cmdlets to reduce the GC overhead. (#3411) (Thanks to @iSazonov!) + +### Cmdlet updates + +- Use `ShellExecute` with `Start-Process`, `Invoke-Item`, and `Get-Help -Online` so that those cmdlets use standard shell associations to open a file/URI. + This means you `Get-Help -Online` will always use your default browser, and `Start-Process`/`Invoke-Item` can open any file or path with a handler. + (Note: there are still some problems with STA threads.) (#3281, partially fixes #2969) +- Add `-Extension` and `-LeafBase` switches to `Split-Path` so that you can split paths between the filename extension and the rest of the filename. (#2721) (Thanks to @powercode!) +- Implement `Format-Hex` in C# along with some behavioral changes to multiple parameters and the pipeline. (#3320) (Thanks to @MiaRomero!) +- Add `-NoProxy` to web cmdlets so that they ignore the system-wide proxy setting. (#3447) (Thanks to @TheFlyingCorpse!) +- Fix `Out-Default -Transcript` to properly revert out of the `TranscribeOnly` state, so that further output can be displayed on Console. (#3436) (Thanks to @PetSerAl!) +- Fix `Get-Help` to not return multiple instances of the same help file. (#3410) + +### Interactive fixes + +- Enable argument auto-completion for `-ExcludeProperty` and `-ExpandProperty` of `Select-Object`. (#3443) (Thanks to @iSazonov!) +- Fix a tab completion bug that prevented `Import-Module -n` from working. (#1345) + +### Cross-platform fixes + +- Ignore the `-ExecutionPolicy` switch when running PowerShell on non-Windows platforms because script signing is not currently supported. (#3481) +- Standardize the casing of the `PSModulePath` environment variable. (#3255) + +### JEA fixes + +- Fix the JEA transcription to include the endpoint configuration name in the transcript header. (#2890) +- Fix `Get-Help` in a JEA session. (#2988) + +## [6.0.0-alpha.17] - 2017-03-08 + +- Update PSRP client libraries for Linux and Mac. + - We now support customer configurations for Office 365 interaction, as well as NTLM authentication for WSMan based remoting from Linux (more information [here](https://github.com/PowerShell/psl-omi-provider/releases/tag/v1.0.0.18)). (#3271) +- We now support remote step-in debugging for `Invoke-Command -ComputerName`. (#3015) +- Use prettier formatter with `ConvertTo-Json` output. (#2787) (Thanks to @kittholland!) +- Port `*-CmsMessage` and `Get-PfxCertificate` cmdlets to Powershell Core. (#3224) +- `powershell -version` now returns version information for PowerShell Core. (#3115) +- Add the `-TimeOut` parameter to `Test-Connection`. (#2492) +- Add `ShouldProcess` support to `New-FileCatalog` and `Test-FileCatalog` (fixes `-WhatIf` and `-Confirm`). (#3074) (Thanks to @iSazonov!) +- Fix `Test-ModuleManifest` to normalize paths correctly before validating. + - This fixes some problems when using `Publish-Module` on non-Windows platforms. (#3097) +- Remove the `AliasProperty "Count"` defined for `System.Array`. + - This removes the extraneous `Count` property on some `ConvertFrom-Json` output. (#3231) (Thanks to @PetSerAl!) +- Port `Import-PowerShellDatafile` from PowerShell script to C#. (#2750) (Thanks to @powercode!) +- Add `-CustomMethod` parameter to web cmdlets to allow for non-standard method verbs. (#3142) (Thanks to @Lee303!) +- Fix web cmdlets to include the HTTP response in the exception when the response status code is not success. (#3201) +- Expose a process' parent process by adding the `CodeProperty "Parent"` to `System.Diagnostics.Process`. (#2850) (Thanks to @powercode!) +- Fix crash when converting a recursive array to a bool. (#3208) (Thanks to @PetSerAl!) +- Fix casting single element array to a generic collection. (#3170) +- Allow profile directory creation failures for Service Account scenarios. (#3244) +- Allow Windows' reserved device names (e.g. CON, PRN, AUX, etc.) to be used on non-Windows platforms. (#3252) +- Remove duplicate type definitions when reusing an `InitialSessionState` object to create another Runspace. (#3141) +- Fix `PSModuleInfo.CaptureLocals` to not do `ValidateAttribute` check when capturing existing variables from the caller's scope. (#3149) +- Fix a race bug in WSMan command plug-in instance close operation. (#3203) +- Fix a problem where newly mounted volumes aren't available to modules that have already been loaded. (#3034) +- Remove year from PowerShell copyright banner at start-up. (#3204) (Thanks to @kwiknick!) +- Fixed spelling for the property name `BiosSerialNumber` for `Get-ComputerInfo`. (#3167) (Thanks to @iSazonov!) + +## [6.0.0-alpha.16] - 2017-02-15 + +- Add `WindowsUBR` property to `Get-ComputerInfo` result +- Cache padding strings to speed up formatting a little +- Add alias `Path` to the `-FilePath` parameter of `Out-File` +- Fix the `-InFile` parameter of `Invoke-WebRequest` +- Add the default help content to powershell core +- Speed up `Add-Type` by crossgen'ing its dependency assemblies +- Convert `Get-FileHash` from script to C# implementation +- Fix lock contention when compiling the code to run in interpreter +- Avoid going through WinRM remoting stack when using `Get-ComputerInfo` locally +- Fix native parameter auto-completion for tokens that begin with a single "Dash" +- Fix parser error reporting for incomplete input to allow defining class in interactive host +- Add the `RoleCapabilityFiles` keyword for JEA support on Windows + +## [6.0.0-alpha.15] - 2017-01-18 + +- Use parentheses around file length for offline files +- Fix issues with the Windows console mode (terminal emulation) and native executables +- Fix error recovery with `using module` +- Report `PlatformNotSupported` on IoT for Get/Import/Export-Counter +- Add `-Group` parameter to `Get-Verb` +- Use MB instead of KB for memory columns of `Get-Process` +- Add new escape character for ESC: `` `e`` +- Fix a small parsing issue with a here string +- Improve tab completion of types that use type accelerators +- `Invoke-RestMethod` improvements for non-XML non-JSON input +- PSRP remoting now works on CentOS without addition setup + +## [6.0.0-alpha.14] - 2016-12-14 + +- Moved to .NET Core 1.1 +- Add Windows performance counter cmdlets to PowerShell Core +- Fix try/catch to choose the more specific exception handler +- Fix issue reloading modules that define PowerShell classes +- `Add ValidateNotNullOrEmpty` to approximately 15 parameters +- `New-TemporaryFile` and `New-Guid` rewritten in C# +- Enable client side PSRP on non-Windows platforms +- `Split-Path` now works with UNC roots +- Implicitly convert value assigned to XML property to string +- Updates to `Invoke-Command` parameters when using SSH remoting transport +- Fix `Invoke-WebRequest` with non-text responses on non-Windows platforms +- `Write-Progress` performance improvement from `alpha13` reverted because it introduced crash with a race condition + +## [6.0.0-alpha.13] - 2016-11-22 + +- Fix `NullReferenceException` in binder after turning on constrained language mode +- Enable `Invoke-WebRequest` and `Invoke-RestMethod` to not validate the HTTPS certificate of the server if required. +- Enable binder debug logging in PowerShell Core +- Add parameters `-Top` and `-Bottom` to `Sort-Object` for Top/Bottom N sort +- Enable `Update-Help` and `Save-Help` on Unix platforms +- Update the formatter for `System.Diagnostics.Process` to not show the `Handles` column +- Improve `Write-Progress` performance by adding timer to update a progress pane every 100 ms +- Enable correct table width calculations with ANSI escape sequences on Unix +- Fix background jobs for Unix and Windows +- Add `Get-Uptime` to `Microsoft.PowerShell.Utility` +- Make `Out-Null` as fast as `> $null` +- Add DockerFile for 'Windows Server Core' and 'Nano Server' +- Fix WebRequest failure to handle missing ContentType in response header +- Make `Write-Host` fast by delay initializing some properties in InformationRecord +- Ensure PowerShell Core adds an initial `/` rooted drive on Unix platforms +- Enable streaming behavior for native command execution in pipeline, so that `ping | grep` doesn't block +- Make `Write-Information` accept objects from pipeline +- Fixes deprecated syscall issue on macOS 10.12 +- Fix code errors found by the static analysis using PVS-Studio +- Add support to W3C Extended Log File Format in `Import-Csv` +- Guard against `ReflectionTypeLoadException` in type name auto-completion +- Update build scripts to support win7-x86 runtime +- Move PackageManagement code/test to oneget.org + +## [6.0.0-alpha.12] - 2016-11-03 + +- Fix `Get-ChildItem -Recurse -ErrorAction Ignore` to ignore additional errors +- Don't block pipeline when running Windows EXE's +- Fix for PowerShell SSH remoting with recent Win32-OpenSSH change. +- `Select-Object` with `-ExcludeProperty` now implies `-Property *` if -Property is not specified. +- Adding ValidateNotNullOrEmpty to `-Name` parameter of `Get-Alias` +- Enable Implicit remoting commands in PowerShell Core +- Fix GetParentProcess() to replace an expensive WMI query with Win32 API calls +- Fix `Set-Content` failure to create a file in PSDrive under certain conditions. +- Adding ValidateNotNullOrEmpty to `-Name` parameter of `Get-Service` +- Adding support in `Get-WinEvent -FilterHashtable` +- Adding WindowsVersion to `Get-ComputerInfo` +- Remove the unnecessary use of lock in PseudoParameterBinder to avoid deadlock +- Refactor `Get-WinEvent` to use StringBuilder for XPath query construction +- Clean up and fix error handling of libpsl-native +- Exclude Registry and Certificate providers from UNIX PS +- Update PowerShell Core to consume .Net Core preview1-24530-04 + +## [6.0.0-alpha.11] - 2016-10-17 + +- Add '-Title' to 'Get-Credential' and unify the prompt experience +- Update dependency list for PowerShell Core on Linux and OS X +- Fix 'powershell -Command -' to not stop responding and to not ignore the last command +- Fix binary operator tab completion +- Enable 'ConvertTo-Html' in PowerShell Core +- Remove most Maximum* capacity variables +- Fix 'Get-ChildItem -Hidden' to work on system hidden files on Windows +- Fix 'JsonConfigFileAccessor' to handle corrupted 'PowerShellProperties.json' + and defer creating the user setting directory until a write request comes +- Fix variable assignment to not overwrite read-only variables +- Fix 'Get-WinEvent -FilterHashtable' to work with named fields in UserData of event logs +- Fix 'Get-Help -Online' in PowerShell Core on Windows +- Spelling/grammar fixes + +## [6.0.0-alpha.10] - 2016-09-15 + +- Fix passing escaped double quoted spaces to native executables +- Add DockerFiles to build each Linux distribution +- `~/.config/PowerShell` capitalization bug fixed +- Fix crash on Windows 7 +- Fix remote debugging on Windows client +- Fix multi-line input with redirected stdin +- Add PowerShell to `/etc/shells` on installation +- Fix `Install-Module` version comparison bug +- Spelling fixes + +## [6.0.0-alpha.9] - 2016-08-15 + +- Better man page +- Added third-party and proprietary licenses +- Added license to MSI + +## [6.0.0-alpha.8] - 2016-08-11 + +- PowerShell packages pre-compiled with crossgen +- `Get-Help` content added +- `Get-Help` null reference exception fixed +- Ubuntu 16.04 support added +- Unsupported cmdlets removed from Unix modules +- PSReadline long prompt bug fixed +- PSReadline custom key binding bug on Linux fixed +- Default terminal colors now respected +- Semantic Version support added +- `$env:` fixed for case-sensitive variables +- Added JSON config files to hold some settings +- `cd` with no arguments now behaves as `cd ~` +- `ConvertFrom-Json` fixed for multiple lines +- Windows branding removed +- .NET CoreCLR Runtime patched to version 1.0.4 +- `Write-Host` with unknown hostname bug fixed +- `powershell` man-page added to package +- `Get-PSDrive` ported to report free space +- Desired State Configuration MOF compilation ported to Linux +- Windows 2012 R2 / Windows 8.1 remoting enabled + +## [6.0.0-alpha.7] - 2016-07-26 + +- Invoke-WebRequest and Invoke-RestMethod ported to PowerShell Core +- Set PSReadline default edit mode to Emacs on Linux +- IsCore variable renamed to IsCoreCLR +- Microsoft.PowerShell.LocalAccounts and other Windows-only assemblies excluded on Linux +- PowerShellGet fully ported to Linux +- PackageManagement NuGet provider ported +- Write-Progress ported to Linux +- Get-Process -IncludeUserName ported +- Enumerating symlinks to folders fixed +- Bugs around administrator permissions fixed on Linux +- ConvertFrom-Json multi-line bug fixed +- Execution policies fixed on Windows +- TimeZone cmdlets added back; excluded from Linux +- FileCatalog cmdlets added back for Windows +- Get-ComputerInfo cmdlet added back for Windows + +## [0.6.0] - 2016-07-08 + +- Targets .NET Core 1.0 release +- PowerShellGet enabled +- [system.manage] completion issues fixed +- AssemblyLoadContext intercepts dependencies correctly +- Type catalog issues fixed +- Invoke-Item enabled for Linux and OS X +- Windows ConsoleHost reverted to native interfaces +- Portable ConsoleHost redirection issues fixed +- Bugs with pseudo (and no) TTY's fixed +- Source Depot synced to baseline changeset 717473 +- SecureString stub replaced with .NET Core package + +## [0.5.0] - 2016-06-16 + +- Paths given to cmdlets are now slash-agnostic (both / and \ work as directory separator) +- Lack of cmdlet support for paths with literal \ is a known issue +- .NET Core packages downgraded to build rc2-24027 (Nano's build) +- XDG Base Directory Specification is now respected and used by default +- Linux and OS X profile path is now `~/.config/powershell/profile.ps1` +- Linux and OS X history save path is now `~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt` +- Linux and OS X user module path is now `~/.local/share/powershell/Modules` +- The `~/.powershell` folder is deprecated and should be deleted +- Scripts can be called within PowerShell without the `.ps1` extension +- `Trace-Command` and associated source cmdlets are now available +- `Ctrl-C` now breaks running cmdlets correctly +- Source Depot changesets up to 715912 have been merged +- `Set-PSBreakPoint` debugging works on Linux, but not on Windows +- MSI and APPX packages for Windows are now available +- Microsoft.PowerShell.LocalAccounts is available on Windows +- Microsoft.PowerShell.Archive is available on Windows +- Linux xUnit tests are running again +- Many more Pester tests are running + +## [0.4.0] - 2016-05-17 + +- PSReadline is ported and included by default +- Original Windows ConsoleHost is ported and replaced CoreConsoleHost +- .NET Core packages set to the RC2 release at build 24103 +- OS X 10.11 added to Continuous Integration matrix +- Third-party C# cmdlets can be built with .NET CLI +- Improved symlink support on Linux +- Microsoft.Management.Infrastructure.Native replaced with package +- Many more Pester tests + +## [0.3.0] - 2016-04-11 + +- Supports Windows, Nano, OS X, Ubuntu 14.04, and CentOS 7.1 +- .NET Core packages are build rc3-24011 +- Native Linux commands are not shadowed by aliases +- `Get-Help -Online` works +- `more` function respects the Linux `$PAGER`; defaults to `less` +- `IsWindows`, `IsLinux`, `IsOSX`, `IsCore` built-in PowerShell variables added +- `Microsoft.PowerShell.Platform` removed for the above +- Cross-platform core host is now `CoreConsoleHost` +- Host now catches exceptions in `--command` scripts +- Host's shell ID changed to `Microsoft.PowerShellCore` +- Modules that use C# assemblies can be loaded +- `New-Item -ItemType SymbolicLink` supports arbitrary targets +- PSReadline implementation supports multi-line input +- `Ctrl-R` provides incremental reverse history search +- `$Host.UI.RawUI` now supported +- `Ctrl-K` and `Ctrl-Y` for kill and yank implemented +- `Ctrl-L` to clear screen now works +- Documentation was completely overhauled +- Many more Pester and xUnit tests added + +## [0.2.0] - 2016-03-08 + +- Supports Windows, OS X, Ubuntu 14.04, and CentOS 7.1 +- .NET Core packages are build 23907 +- `System.Console` PSReadline is fully functional +- Tests pass on OS X +- `Microsoft.PowerShell.Platform` module is available +- `New-Item` supports symbolic and hard links +- `Add-Type` now works +- PowerShell code merged with upstream `rs1_srv_ps` + +## 0.1.0 - 2016-02-23 + +- Supports Windows, OS X, and Ubuntu 14.04 + +[6.0.0]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-rc.2...v6.0.0 +[6.0.0-rc.2]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-rc...v6.0.0-rc.2 +[6.0.0-rc]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.9...v6.0.0-rc +[6.0.0-beta.9]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.8...v6.0.0-beta.9 +[6.0.0-beta.8]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.7...v6.0.0-beta.8 +[6.0.0-beta.7]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.6...v6.0.0-beta.7 +[6.0.0-beta.6]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.5...v6.0.0-beta.6 +[6.0.0-beta.5]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.4...v6.0.0-beta.5 +[6.0.0-beta.4]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.3...v6.0.0-beta.4 +[6.0.0-beta.3]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.2...v6.0.0-beta.3 +[6.0.0-beta.2]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-beta.1...v6.0.0-beta.2 +[6.0.0-beta.1]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.18...v6.0.0-beta.1 +[6.0.0-alpha.18]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.17...v6.0.0-alpha.18 +[6.0.0-alpha.17]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.16...v6.0.0-alpha.17 +[6.0.0-alpha.16]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.15...v6.0.0-alpha.16 +[6.0.0-alpha.15]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.14...v6.0.0-alpha.15 +[6.0.0-alpha.14]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.13...v6.0.0-alpha.14 +[6.0.0-alpha.13]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.12...v6.0.0-alpha.13 +[6.0.0-alpha.12]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.11...v6.0.0-alpha.12 +[6.0.0-alpha.11]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.10...v6.0.0-alpha.11 +[6.0.0-alpha.10]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.9...v6.0.0-alpha.10 +[6.0.0-alpha.9]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.8...v6.0.0-alpha.9 +[6.0.0-alpha.8]: https://github.com/PowerShell/PowerShell/compare/v6.0.0-alpha.7...v6.0.0-alpha.8 +[6.0.0-alpha.7]: https://github.com/PowerShell/PowerShell/compare/v0.6.0...v6.0.0-alpha.7 +[0.6.0]: https://github.com/PowerShell/PowerShell/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/PowerShell/PowerShell/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/PowerShell/PowerShell/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/PowerShell/PowerShell/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/PowerShell/PowerShell/compare/v0.1.0...v0.2.0 diff --git a/CHANGELOG/6.1.md b/CHANGELOG/6.1.md new file mode 100644 index 00000000000..59cf2842d78 --- /dev/null +++ b/CHANGELOG/6.1.md @@ -0,0 +1,797 @@ +# 6.1 Changelog + +## [6.1.6] - 2019-09-12 + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9945) + +## [6.1.5] - 2019-07-16 + +### Breaking changes + +- Disable `Enter-PSHostProcess` cmdlet when system in lock down mode (Internal 8968) + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9087) +- Add automated RPM signing to release build (#10013) +- Update copyright symbol for NuGet packages (#9936) +- Bump `System.Net.Http.WinHttpHandler` from `4.5.3` to `4.5.4` (#9790) +- Integrate building NuGet package in the coordinated build (#8947) (#9708) +- Bump `Newtonsoft.Json` (#9662) + +## [6.1.4] - 2019-05-21 + +### Build and Packaging Improvements + +- Disable debugger in System Lock down mode (Internal 8430) +- Port changes for release automation to `6.1` (Internal 8402) +- Fix `MSI` `WIX` generation (#9013) (Internal 8385) +- Update `Microsoft.PowerShell.Archive` version (Internal 8380) +- Update package version in hosting test (Internal 8374) +- Bump to `dotnet` `2.1.11` release +- Remove update build table logic from release build (Internal 8364) +- Add `AccessToken` variable to jobs that perform signing (#9351) +- Support release branches based on the forward slash separator (#8903) + +## [6.1.3] - 2019-02-14 + +### Engine Updates and Fixes + +- Add security mitigation for 6.1.3 release (Internal 6561) + +### Tools + +- Change the feed URL to feed name due to changes in Azure DevOps (#8664) + +### Tests + +- Updating test gallery URL in PackageManagement tests (#7879) + +### Build and Packaging Improvements + +- Get PowerShellGet tests working (#7831) +- Start tracking release build information in an azure storage table (#8850) +- Remove `PDBs` from `fxdependent` package (#8006) +- Make every `csproj` files have its own folder (#8750) +- Update packaging script to build reference assembly targeting `netcoreapp2.1` and use actual `.csproj` files (#8729) +- Move Final artifacts from coordinated build to `finalResults` folder (#8806) +- Refactor Unified Release Build (#8804) +- Add compliance to Coordinated build (#8798) +- Switch to 1.11 of FPM to fix FPM install issue (#8797) +- Update the coordinated build with framework dependent package for dotnet SDK (#8773) +- Add Windows build to coordinated release build YAML (#8695) +- Build package build using Ubuntu 18.04 image (#8666) +- Adding `yml` for Windows Release builds (#8374) +- Update `SignType` in `signing.xml` (#8223) +- Update DotNet SDK and Runtime version (Internal 7004) +- Add `binskim` to coordinated build and increase timeout (#8834) + +## [6.1.2] - 2019-01-15 + +### Tests + +- Fix test failures (Internal 6310) + +### Build and Packaging Improvements + +- Moved the cleanup logic to `Restore-PSModuleToBuild` (Internal 6442) +- Update dependency versions (Internal 6421) +- Create unified release build for macOS and Linux packages (#8399) +- Build Alpine `tar.gz` package in release builds (Internal 6027) + +### Documentation and Help Content + +- Update version for README, Alpine docker file and hosting tests (Internal 6438) + +## [6.1.1] - 2018-11-13 + +### Engine Updates and Fixes + +- Fix issue with logging the null character in `ScriptBlock` logging (Internal 5607) +- Consolidation of all Windows PowerShell work ported to 6.1 (Internal 5233) + +### General Cmdlet Updates and Fixes + +- Use `ZipFile` and `ExtractToDirectory` APIs to extract zip file (Internal 5608) + +## [6.0.5] - 2018-11-13 + +### Engine updates and fixes + +- Fix issue with logging the null character in `ScriptBlock` logging (Internal 5605) + +### General cmdlet updates and fixes + +- Use `ZipFile` and `ExtractToDirectory` APIs to extract zip file (Internal 4802) + +### Build and Packaging Improvements + +- Update `SignType` in `signing.xml` (Internal 5721) +- Port changes to pull PowerShell Gallery modules from Modules `csproj` (Internal 5713) +- Port macOS Release build changes changes from GitHub (#8189, #8188, #8185) +- Fix script path for `PowerShellPackageVsts.ps1` (#8189) +- Workaround for accessing `AzDevOps` Artifacts (#8188) +- Bump various packages to latest patch version (Internal 5675) +- Update PowerShell SDK NuGet various metadata description (Internal 4527, 4510, 4505) + +## [6.0.4] - 2018-08-10 + +### Build and Packaging Improvements + +- Update the Archive module version (Internal 5671) +- Update to .NET Core `2.1.5` with SDK `2.1.403` (#7936) (Thanks @iSazonov!) +- Disable package major upgrade tests for release branch (Internal 5209) +- Bump versions for dependencies (Internal 5612) +- Port changes to allow `AzDevOps` NuGet feeds for macOS build (Internal 5716) +- Port macOS changes from GitHub (#8189, #8188, #8185) +- Add function to create a new `nuget.config` file (#8170) +- Updated `wxs` file to match published packages (Internal 5660) + +### Tests + +- Change API to match cmdlet which is more reliable in `AzDevOps` Pipelines Windows (#8003) +- Fix conflict with `Get-AdlStoreChildItem` from `az` module in tab completion tests (#8167) + +## [6.1.0] - 2018-09-13 + +### Engine Updates and Fixes + +- Enable indexing operations on `System.Tuple` and `System.ValueTuple` (#7633) (Thanks @SeeminglyScience!) +- Use non-virtual call to invoke 'family or assembly' methods on base class from PowerShell class (#7624) (Thanks @yurko7!) +- Handle operations with `ByRef-like` types gracefully in PowerShell (#7533) +- Make the `-settingfile` flag on `pwsh` work for `ScriptBlock` logging on windows (#7631) +- Ensure the `SSHClientSessionTransportManager` stream writer and reader fields are cleared after disposing (#7746) +- Add `LocationChangedAction` handler to support the Windows Compatibility module (#7552) + +### General Cmdlet Updates and Fixes + +- Fix `Set-Service -Status Stopped` to stop services with dependencies (#5525) (Thanks @zhenggu!) +- Add the `Duration` property to `HistoryInfo` (#5208) (Thanks @powercode!) +- Fix null reference in `ConvertFrom-Markdown` when the markdown content is empty (#7463) +- Fix file blocking issue with WebCmdlets (#7676) (Thanks @Claustn!) +- Fix performance issue in `WSMan` provider by using `Refresh()` to update the status rather than instantiating `ServiceController` (#7680) + +### Code Cleanup + +- Remove `Suspend-Job` and `Resume-Job` cmdlets from compilation on Unix platforms (#7650) +- Remove extra spaces in error messages in `Modules.resx` (#7662) (Thanks @sethvs!) +- Cleanup the platform runtime checks from `FileSystemProvider` (#7655) (Thanks @iSazonov!) +- Improve code style of `Send-MailMessage` cmdlet (#7723) (Thanks @ThreeFive-O!) + +### Tools + +- Add tools for PowerShell performance analysis (#7595) (Thanks @lzybkr!) +- Update code coverage module to download zip files based on job ID (#7653) + +### Tests + +- Update test which assumes all previews have the name preview in the version (#7625) +- Update Pester syntax in `Set-Location` test (#7615) (Thanks @iSazonov!) +- Add `ScriptBlock` logging test for Linux and macOS (#7599) (#7586) +- Add tests to report when package references are out of date (#7661) +- Fix `ModuleSpecification.Tests.ps1` (#7663) (Thanks @sethvs!) +- Updates Docker package tests (#7667) + +### Build and Packaging Improvements + +- Update to the latest package references, dotnet core SDK and framework (#7646) (Thanks @iSazonov!) +- Make the artifact upload only occur for non-PR builds (#7657) +- Change to not upload artifacts during pull request due to missing VSTS feature (#7588) +- Remove workaround on VSTS that is no longer needed (#7666) +- Update docker files to use MCR (#7656) +- Add symbolic links for `libssl` and `libcrypto` to Debian 9 build to make remoting work (#7609) +- Simplify the `StartupInfo` type used in Jumplist creation for faster `P/Invoke` (#7580) (Thanks @powercode!) +- Add VSTS CI for Windows (#7536) +- Update the version of `PowerShellGet` module to `1.6.7` (#7564) +- update the version of `PSReadLine` module to `2.0.0-beta3` (#7711) +- Make sure MSI build works for non-preview builds (#7752) +- Build and package framework dependent package (#7729) +- Change locale of `mdspell` to `en-US` (#7671) +- Add daily build on non-windows platforms (#7683) +- Fix Windows MSI to remove the `Uninstall` shortcut during an uninstall when more than one version is installed (#7701) (Thanks @bergmeister!) +- Fix docker image names for release build (#7726) + +### Documentation and Help Content + +- Update the version of .NET Core in docs (#7467) (Thanks @bergmeister!) +- Fix links in `README.md` (#7619) (Thanks @iSazonov!) +- Add VSTS CI build badges for master branch to `README.md` (#7691) (Thanks @bergmeister!) +- Add a paragraph in `CONTRIBUTING.md` about updating `files.wxs` (#7695) (Thanks @iSazonov!) + +# [6.1.0-rc.1]- 2018-08-22 + +### Engine Updates and Fixes + +- Fix to not duplicate the `System32` module path when starting `pwsh` from `pwsh` (#7414) +- Fix sequence point update for `switch/if/for/while/do-while/do-until` statements (#7305) +- Set the cursor to the place where a user hits tab key (#7299) +- Adding `LanguagePrimitives.TryCompare` to provide faster comparisons (#7438) (Thanks @powercode!) +- Improving performance of `LanguagePrimitives.TryConvertTo` (#7418) (Thanks @powercode!) +- Set `PowerShellVersion` to `3.0` for built-in modules to make Windows PowerShell work when starting from PowerShell Core (#7365) +- Avoid extra unnecessary allocations in `PSMemberInfoInternalCollection` (#7435) (Thanks @iSazonov!) +- Enforce the `CompatiblePSEditions` check for modules from the legacy `System32` module path (#7183) +- Make sure that `SettingFile` argument is parsed before we load the settings (#7449) +- Default to `DefaultConsoleWidth` when DotNet says `WindowWidth` is 0 (#7465) + +### General Cmdlet Updates and Fixes + +- Fix parameter name in the `Get-Variable` cmdlet error message (#7384) (Thanks @sethvs!) +- Fix `Move-Item -Path` with wildcard character (#7397) (Thanks @kwkam!) +- Ignore `Newtonsoft.Json` metadata properties in `ConvertFrom-Json` (#7308) (Thanks @louistio!) +- Fix several issues in Markdown cmdlets (#7329) +- Add support for parsing Link Header with variable whitespace (#7322) +- Change parameter order in `Get-Help` and help in order to get first `-Full` and + then `-Functionality` when using Get-Help `-Fu` followed by pressing tab and help `-Fu` followed by pressing tab (#7370) (Thanks @sethvs!) +- Add support for passing files and Markdown directly to `Show-Markdown` (#7354) +- Add `-SkipIndex` parameter to `Select-Object` (#7483) (Thanks @powercode!) +- Improve performance of `Import-CSV` up to 10 times (#7413) (Thanks @powercode!) +- Update `Enable-PSRemoting` so configuration name is unique for Preview releases (#7202) +- Improve performance on JSON to PSObject conversion (#7482) (Thanks @powercode!) +- Fix error message for `Add-Type` when `-AssemblyName` with wildcard is not found (#7444) +- Make native globbing on Unix return an absolute path when it is given an absolute path (#7106) +- Improve the performance of `Group-Object` (#7410) (Thanks @powercode!) +- Remove one unneeded verbose output from `ConvertTo-Json` (#7487) (Thanks @devblackops!) +- Enable `Get-ChildItem` to produce `Mode` property even if cannot determine if hard link (#7355) + +### Code Cleanup + +- Remove empty XML comment lines (#7401) (Thanks @iSazonov!) +- Cleanup Docker files (#7328) +- Correct the comment for `WSManReceiveDataResult.Unmarshal` (#7364) +- Format Utility `csproj` with updated `codeformatter` (#7263) (Thanks @iSazonov!) +- Bulk update format for files in Management folder with `codeformatter` (#7346) (Thanks @iSazonov!) +- Cleanup: replace `Utils.FileExists()/DirectoryExists()/ItemExists()` with DotNet methods (#7129) (Thanks @iSazonov!) +- Update `Utils.IsComObject` to use `Marshal.IsComObject` since CAS is no longer supported in DotNet Core (#7344) +- Fix some style issues in engine code (#7246) (Thanks @iSazonov!) + +### Test + +- Use `-BeExactly` and `-HaveCount` instead of `-Be` in `BugFix.Tests.ps1` (#7386) (Thanks @sethvs!) +- Use `-BeExactly` and `-HaveCount` instead of `-Be` in `TabCompletion.Tests.ps1` (#7380) (Thanks @sethvs!) +- Update CI scripts to support running tests for experimental features (#7419) +- Use `-HaveCount` instead of `-Be` in `Where-Object.Tests.ps1` (#7379) (Thanks @sethvs!) +- Fix ThreadJob tests so that they will run more reliably (#7360) +- Make logging tests for macOS pending (#7433) + +### Build and Packaging Improvements + +- Update Build script owners (#7321) +- Make `MUSL` NuGet package optional (#7316) +- Enable `pwsh-preview` to work on Windows (#7345) +- Fix SDK dependencies +- Add back the `powershell-core` NuGet source for hosting tests +- Fix typo in environment checker (#7547 & #7549) +- Only remove the revision if it is `0` from module version when restoring modules (#7538) +- Update `WCF` and `NJsonSchema` NuGet packages to latest released patch version (#7411) (Thanks @bergmeister!) +- Add Linux and macOS VSTS CI (#7490, #7527, #7535, #7515 & #7516) +- Updated ThreadJob to version `1.1.2` (#7522) +- Add xUnit project to `PowerShell.sln` and make it runnable from within VisualStudio (#7254) (Thanks @bergmeister!) +- Update NuGet packaging code for the new markdown assembly (#7431) +- Update version of modules shipped with PowerShell (#7531) +- Retry restore on failure (#7544 & #7550) +- Update `PowerShellGet` version +- Update NuGet package metadata (#7517) +- Update reference to use packages from `NuGet.org` (#7525) +- `Start-DevPowerShell`: add `-Configuration` and handle `-ArgumentList` more properly (#7300) (Thanks @jazzdelightsme!) +- Add preview icon to macOS launcher (#7448) (Thanks @thezim!) +- Add `Microsoft.PowerShell.MarkdownRender` to `signing.xml` (#7472) +- Fix building on RedHat Enterprise Linux (#7489) +- Build: Also search PATH for `rcedit` (#7503) (Thanks @kwkam!) +- Save modules to un-versioned folder to enable servicing (#7518 & #7523) +- Fix macOS launcher app to allow release and preview versions (#7306) (Thanks @thezim!) + +### Documentation and Help Content + +- Fix docs comments in utility folder (#7192) (Thanks @iSazonov!) +- Fix a typo in `issue-management.md` (#7393) (Thanks @alexandair!) +- Fix casing of `GitHub` in `best-practice.md` (#7392) (Thanks @alexandair!) +- Fix typos in `docs/maintainers/README.md` (#7390) (Thanks @alexandair!) +- Add maintainer's best practice document and update maintainer list (#7311) +- Update Docker link to `PowerShell-Docker` (#7351) (Thanks @JoshuaCooper!) +- Add `Snapcraft` to spelling dictionary (#7318) +- Update `README.md` and `metadata.json` for release `v6.0.4` (#7497) +- Add `Former Repository Maintainers` section in `maintainers/README.md` (#7475) +- Update the `HelpUri` for `Get-ExperimentalFeature` (#7466) + +# [6.1.0-preview.4]- 2018-07-19 + +### Breaking Changes + +- Remove the `VisualBasic` support from Add-Type (#7284) +- Update PowerShell Direct to try `pwsh` then fallback to `powershell` (#7241) +- Make pwsh able to start in a directory with wildcards in the name (#7240) +- Update `Enable-PSRemoting` so configuration name is unique for Preview releases (#7202) +- Enforce the `CompatiblePSEditions` check for modules from the legacy `System32` module path (#7183) + +### Engine Updates and Fixes + +- Add support to experimental features (#7242) +- Fix error when using `Get-ChildItem c:` (#7033) (Thanks @sethvs!) +- Add location history for `Set-Location` to enable `cd -` scenario (issue #2188) (#5051) (Thanks @bergmeister!) +- Fix padding for right aligned column in table formatting (#7136) +- Fix a performance regression to the `-replace` operator after adding `ScriptBlock` support (#7135) +- Fix tab expansion for `Get-Process` on macOS (#7176) +- When using PSRP, if we receive text instead of XML, output it as error to help troubleshoot (#7168) +- Fix trimming of whitespace when table is wrapped (#7184) +- Modified the `Group-Object -AsHashTable` to use the base object of `PSObject` as the key for the `Hashtable` (#7123) +- Add back ADSI and WMI type accelerators (#7085) +- Add `CompatiblePSEditions` to PowerShell Core built-in modules (#7083) +- Make `Start-Process -ArgumentList` to accept `@()` or `$null` (#6597) +- Avoid calling native APIs to check for existence of FileSystem items (#6929) (Thanks @iSazonov!) +- Add copy environment variables from `ProcessStartInfo` to key/pair array used in creating SSH process (#7070) +- Add markdown rendering feature assemblies to the trusted assembly list (#7280) +- Don't fail if `SaferPolicy` API is not available on Windows 10 IoT or NanoServer (#7075) +- Fix conditions for transcription of `Write-Information` command. (#6917) (Thanks @hubuk!) +- Fix a parsing error when `break` and `continue` are used in a switch statement in a finally block (#7273) +- Fix prompt string to be platform agnostic and keep its trailing spaces (#7255) +- Make progress panel display correctly on UNIX when the user is typing. (#6972) +- Revert change to have `SetLocation()` treat wildcarded path as literal if it exists (#7101) +- Make `Select-Object`/`ForEach-Object`/`Where-Object` see dynamic properties (#6898) (Thanks @jazzdelightsme!) +- Fix class searcher to ignore hidden properties (#7188) +- Update remote prompt when using SSH to show username if different (#7191) +- Remove `SemanticVersion` from `knowntypes` list in serialization code to enable interop between Windows PowerShell and PowerShell Core (#7016) +- Add more information to job process failure error (#7251) +- Use .Net Core `File.Delete()` method to remove symbolic links and alternate streams (#7017) (Thanks @iSazonov!) +- Enable `UseShellExecute` on all platforms (#7198) +- Methods with return type `[object]` should return `null` for an empty result (#7138) + +### General Cmdlet Updates and Fixes + +- Add Markdown rendering cmdlets (#6926) +- `Send-MailMessage`: Update all parameters to support `ValueFromPipelineByPropertyName`. (#6911) (Thanks @sethvs!) +- Allow Basic Auth over HTTPS (#6890) +- Add `ThreadJob` module package and tests (#7169) +- Fix Windows Event Log channel isolation semantics (#6956) (Thanks @Robo210!) +- Make `Measure-Object` handle `scriptblock` properties. (#6934) +- Added functionality to retry in `Invoke-RestMethod` and `Invoke-WebRequest`. (#5760) +- Add type inference for `Select-Object` command (#7171) (Thanks @powercode!) +- Add `-AllStats` Switch parameter for `Measure-Object` cmdlet (#7220) (Thanks @kvprasoon!) + +### Code Cleanup + +- Remove unneeded code that forces ARM platforms to run PowerShell in CL mode (#7046) +- Bulk update code base to put `null` on the right-hand-side of a comparison expression (#6949) (Thanks @iSazonov!) +- Remove `MapSecurityZoneWithUrlmon` method and related code (#7103) +- Cleanup: remove the unneeded type `RemotingCommandUtils` (#7029) +- Remove unneeded "Windows-Full" modules (#7030) +- CodeFactor code style cleanup: replace literal empty strings with `string.Empty` (#6950) (Thanks @iSazonov!) +- Remove dummy comments in Utility module files (#7224) (Thanks @iSazonov!) +- Use empty array for Functions/Cmdlets/`AliasesToExport` to follow the best practice (#7108) +- Refactor module code related to `Get-Module -ListAvailable` (#7145) +- Refactor module specification logic (#7126) + +### Test + +- Add tests for module specifications (#7140) +- Update test string for better clarity in `Send-MailMessage.Tests.ps1` (#7195) (Thanks @sethvs!) +- Add test to verify filesystem provider isn't used when accessing root path in `PSDrive` (#7173) +- Fix to address `ThreadJob` tests reliability and speed (#7270) +- Add additional checks for test that passes inconsistently (#7051) + +### Build and Packaging Improvements + +- `install-powershell.sh` filter pre-releases (when available), `params` documentation (#6849) (Thanks @DarwinJS!) +- Fedora 28 was released, Fedora 26 and 25 went end of life. (#7079) (Thanks @adelton!) +- Disambiguate icon on Windows for preview builds/installers to use `Powershell_av_colors` and + make daily build use `Powershell_avatar` instead (#7086) (Thanks @bergmeister!) +- Update to build for Alpine (#7139) +- Update build and packaging modules for Alpine (#7149) +- Add ability to install previews side-by-side with production releases (#7194) (Thanks @DarwinJS!) +- Enable NuGet Package Registration for compliance (#7053) +- Fix the preview macOS package link (#7061) +- Remove PSReadLine from then `PowerShell.sln` file (#7137) +- Fix the file `PowerShell.sln` that was corrupted by accident (#7288) +- Fix the encoding of `PowerShell.sln` to be `utf-8` (#7289) +- Make sure all references to the Package ID for previews packages is powershell-preview (#7066) +- Update `internals.md` with the latest build changes (#7058) +- When installing using MSI, set the working directory of the shortcut to the user home directory (#7072) +- Move to dotnet core 2.1.1 (#7161) (Thanks @iSazonov!) +- Update to latest package references, runtime framework, and SDK (#7272) +- AppVeyor build matrix: more efficient build job split to reduce total time by another 5 minutes (#7021) (Thanks @bergmeister!) +- Build: Fix the source location of `PowerShell.Core.Instrumentation.dll` (#7226) +- Add Andrew to the default reviewers of the build related files (#7019) +- Build: Fix a check to avoid null argument in case `vcvarsall.bat` is absent (#7218) (Thanks @PetSerAl!) +- Update `releaseTag` in `tools/metadata.json` (#7214) +- Update `Start-PSPester` to make it more user friendly (#7210) (Thanks @bergmeister!) +- Make `Start-PSBuild -Clean` not prompt due to locked files when Visual Studio is open by excluding `sqlite3` folder and use `-x` instead of `-X` option on `git clean` (#7235) (Thanks @bergmeister!) + +### Documentation and Help Content + +- Fix typos in `DOCSMIGRATION.md` (#7094) (Thanks @alexandair!) +- Add instructions to update Homebrew formula for the preview version PowerShell (#7067) (Thanks @vors!) +- Merge Third Party Notices and License updates (#7203) +- Update third party notices (#7042) +- Fix Markdown and spelling errors in `CHANGELOG.md` (#7064) +- Fix `New-TemporaryFile` online help URI (#6608) +- Fix links to PowerShell install docs (#7001) (Thanks @jokajak!) +- Update links that contain `en-us` culture (#7013) (Thanks @bergmeister!) +- Update docs for `ArgumentCompleterAttribute` class (#7227) (Thanks @Meir017!) +- Fix the name of a `Register-EngineEvent` test (#7222) (Thanks @alexjordan6!) +- Update README files for native code for migration (#7248) +- Comment about dynamic members for the `DotNetAdapter`, `GetMember` and `GetMembers` (#7087) +- Update the PowerShell executable location in building guide docs (#7205) (Thanks @louistio!) + +# [6.1.0-preview.3]- 2018-06-07 + +### Breaking Changes + +- Clean up uses of `CommandTypes.Workflow` and `WorkflowInfo` (#6708) +- Disallow Basic Auth over HTTP in PowerShell Remoting on Unix (#6787) +- Change packaging to differentiate only between major versions and previews (#6968) +- Enhance and refactor `Add-Type` cmdlet (#6141) (Thanks @iSazonov!) + - A few error strings were removed and thus the corresponding fully qualified error ids are no longer in use. + +### Engine Updates and Fixes + +- 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) +- 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!) +- Fix parser to continue parsing key-value pairs after an `If-Statement` value in a `HashExpression` (#7002) +- Add error handling for `#requires` in an interactive session (#6469) + +### General Cmdlet Updates and Fixes + +- Improve parameter validation in `ExportCsvHelper` (#6816) (Thanks @sethvs!) +- Quote `Multipart` form-data field names (#6782) (Thanks @markekraus!) +- Fix Web Cmdlets for .NET Core 2.1 (#6806) (Thanks @markekraus!) +- Fix `Set-Location DriveName:` to restore current working directory in the drive (#6774) (Thanks @mcbobke!) +- Add the alias `-lp` for `-LiteralPath` parameters #6732 (#6770) (Thanks @kvprasoon!) +- Remove `more` function and move the `$env:PAGER` capability into the `help` function (#6059) (Thanks @iSazonov!) +- Add line break to the error message for `Set-ExecutionPolicy` (#6803) (Thanks @wesholton84!) + +### Code Cleanup + +- Clean up `#if SILVERLIGHT` (#6907) (Thanks @iSazonov!) +- Clean up the unused method `NonWindowsGetDomainName()` (#6948) (Thanks @iSazonov!) +- Clean up FileSystem provider (#6909) (Thanks @iSazonov!) + +### Test + +- Add tests for PowerShell hosting API to verify MyGet packages (#6737) +- Remove Web Cmdlets tests using proxy environment variables (#6808) (Thanks @iSazonov!) +- Enable Web Cmdlets tests for greater platform support (#6836) (Thanks @markekraus!) +- Convert `ShouldBeErrorId` to `Should -Throw -ErrorId` in PowerShell tests (#6682) +- Fix CIM cmdlets tests (#6755) (Thanks @sethvs!) +- Add tests for PowerShell classes inheriting from abstract .NET classes (#6752) +- Fix `Select-Object.Tests.ps1` which previously failed intermittently on Unix platforms. (#6747) +- Update docker package tests to fix error on OpenSUSE 42 (#6783) +- Fix test and infrastructure that block code coverage runs (#6790) +- Update Tests `Isfile` to correct response for `"/"` (#6754) (Thanks @Patochun!) +- Improve code coverage in `Export-Csv.Tests.ps1` (#6795) (Thanks @sethvs!) +- Change `-Quiet` parameter of `Invoke-Pester` to `-Show None` in `OpenCover.psm1` (#6798) (Thanks @sethvs!) +- Replace `Dbg.Assert` with `if () throw` in `CSVCommands.cs` (#6910) (Thanks @sethvs!) +- Fix xUnit test `GetTempFileName` (#6943) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +- Add Windows Compatibility Pack 2.0.0 to PowerShell Core and adopt the official .NET Core 2.1 (#6958) +- Add Jumplist 'Run as Administrator' to Taskbar on Windows (#6913, #6985) (Thanks @bergmeister!) +- Use AppVeyor matrix for faster Pull Request builds (#6945) (Thanks @bergmeister!) +- Fix `build.psm1` to not add tool path to $PATH twice (#6834) +- Add script to create a container manifest (#6735) +- Fix docker manifest creation script to work with more complex tags and with repeated use (#6852) +- Add functions to merge Pester and xUnit logs (#6854) +- Enable generating full symbols for the Windows debug build (#6853) +- Add functions into `build.psm1` to save and restore `PSOptions` between different sessions. (#6884) +- Update signing XML based on new signing guidelines (#6893) +- Update the release docker files to allow specifying the version of to-be-installed PowerShell and the version of image to use (#6835) +- Updates docker files for Fedora 27 and Kali Linux (#6819) +- Change packaging to support Ubuntu 17.10 and 18.04 (#6769) +- Update `Get-ChangeLog` to make it more accurate (#6764) +- Fix comparison to see if sudo test is needed in `install-*.sh` (#6771) (Thanks @bjh7242!) +- Packaging: Add registry keys to support library folder background for explorer context menu (#6784) (Thanks @bergmeister!) +- Skip `dotnet-cli` initialization and stop caching the `dotnet` folder for Travis CI (#7007) +- Skip compiling the non-supported cmdlets on Unix in `System.Management.Automation.dll` to fix the crash in Unix debug build (#6939) +- Use `PSReadLine` 2.0.0-beta2 from PSGallery (#6998) +- Update `PSRP` Linux NuGet package version to 1.4.2-* (#6711) +- Add path cleanup utility `Reset-PWSHSystemPath.ps1` (#6892) (Thanks @DarwinJS!) +- Add logic to create signing XML for NuGet packages (#6921) +- Add and config the `Settings.StyleCop` file (#6930, #6986) (Thanks @iSazonov!) +- Fix the double curly bracket typo in a docker file (#6960) (Thanks @adelton!) +- Remove dependencies on `libcurl` and `libunwind` in packaging to match the .NET Core behavior (#6964) (Thanks @qmfrederik!) +- Make the docker build fail when the curl operation fails. (#6961) (Thanks @adelton!) + +### Documentation and Help Content + +- Update installation doc about Raspbian (#6859) +- Add code coverage report generation instructions (#6515) +- Migrate docs from PowerShell repository to Docs repository (#6899) +- Fix broken links due to migrating GitHub docs on Installation, Known Issues and Breaking Changes to `docs.microsoft.com` (#6981) (Thanks @bergmeister!) +- Update documentation on how to write tests verifying errors conditions (#6687) +- Fix preview download links in `README.md` (#6762) + +# [6.1.0-preview.2]- 2018-04-27 + +### Breaking Changes + +- Remove support for file to opt-out of telemetry, only support environment variable (#6601) +- Simplify the installation paths the MSI uses (#6442) + +### Engine Updates and Fixes + +- Fix running `pwsh` produced from `dotnet build` (#6549) +- Remove the `FullCLR-only` symbol-info related code from `EventManager.cs` (#6563) +- Improve `PSMethod-to-Delegate` conversion (#6570) +- Fix `PsUtils.GetManModule()` to avoid infinite loop when there was no main module (#6358) +- Fix error in windows environment provider when the environment variable has duplicates that differ only by case (#6489) (Thanks @mklement0!) +- Make sure that the width of the header is at least the size of the label (or property name) (#6487) +- Enable `[Environment]::OSVersion` to return current OS rather than compatible version (#6457) +- Change the `SaveError` method in Parser to use `nameof` for error ids (#6498) +- Fix error when `Format-Wide -AutoSize | Out-String` is called (#6491) (Thanks @stknohg!) +- Make `LanguagePrimitive.GetEnumerable` treat `DataTable` as Enumerable (#6511) +- Fix formatting of tables where headers span multiple rows (#6504) +- Improve performance of parsing `RegexOption` for `-split` by using `if` branches (#6605) (Thanks @iSazonov!) +- Enable specifying `sshd` subsystem to use via `-Subsystem` (#6603) +- Add some optimizations in formatting subsystem (#6678) (Thanks @iSazonov!) +- Throw better parsing error when statements should be put in named block (#6434) +- Use `Unregister-Event` to remove an event subscriber when removing `PSEdit` function (#6449) +- Make the `PSISERemoteSessionOpenFile` a support event (#6582) +- Add `-WorkingDirectory` parameter to `pwsh` (#6612) +- Support importing module paths that end in trailing directory separator (#6602) +- Formatting: Use cache for dash padding strings for tables (#6625) (Thanks @iSazonov!) +- Port Windows PowerShell AppLocker and DeviceGuard `UMCI` application white listing support (#6133) +- Reduce allocations in `TableWriter` (#6648) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Add `-Resume` Feature to WebCmdlets (#6447) (Thanks @markekraus!) +- Support `user@host:port` syntax for `SSH` transport (#6558) +- Add ported `Test-Connection` cmdlet (#5328) (Thanks @iSazonov!) +- Added line break to Access-Denied error message (#6607) +- Some fixes in `Get-Date -UFormat` (#6542) (Thanks @iSazonov!) +- Added check for existence of Location HTTP header before using it (#6560) (Thanks @ffeldhaus!) +- Enable `Update-Help` to save help content in user scope by default (#6352) +- Update `Enable-PSRemoting` to create PowerShell.6 endpoint and version specific endpoint (#6519, #6630) +- Update error message that `Disconnect-PSSession` is only supported with `WSMan` (#6689) +- Make `Export-FormatData` print pretty XML output (#6691) (Thanks @iSazonov!) +- Add `-AsArray` parameter to `ConvertoTo-Json` command (#6438) +- Add `Test-Json` cmdlet (`NJsonSchema`) (#5229) (Thanks @iSazonov!) +- Correct a typo in comment for `Invoke-WebRequest` (#6700) (Thanks @gabrielsroka!) +- Re-order `UFormat` options in `Get-Date` (#6627) (Thanks @iSazonov!) +- Add the parameter `-Not` to `Where-Object` (#6464) (Thanks @SimonWahlin!) + +### Code Cleanup + +- Engine: Fix several code cleanup issues (#6552, #6609) +- Clean up workflow logic in the module loading component (#6523) +- Engine: Clean up unneeded `GetTypeInfo()` calls (#6613, #6636, #6633, #6635, #6634) + +### Test + +- Fix line ending in `DefaultCommands.Tests.ps1` from `CRLF` to `LF` (#6553) +- Use new Pester parameter syntax in tests (#6490, #6574, #6535, #6536, #6488, #6366, #6351, #6349, #6256, #6250) (Thanks @KevinMarquette, @sethvs, @bergmeister!) +- Fix `Copy.Item.Tests.ps1` (#6596) (Thanks @sethvs!) +- Fix typos or formatting in some test files (#6595, #6593, #6594, #6592, #6591) (Thanks @sethvs!) +- Add missing `Start-WebListener` to WebCmdlets tests (#6604) (Thanks @markekraus!) +- Update Dockerfile test to use Ubuntu 17.10 as the base image (#6503) +- Add PowerShell logging tests for macOS and Linux (#6025) +- Add tests for `Format-Table -Wrap` (#6670) (Thanks @iSazonov!) +- Reformat `Format-Table` tests (#6657) (Thanks @iSazonov!) +- Add new reliable tests for `Get-Date -UFormat` (#6614) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +- Use C# latest language in `.csproj` files (#6559) (Thanks @iSazonov!) +- Update `installpsh-.sh` installers to handle "preview" in version number (#6573) (Thanks @DarwinJS!) +- Enable `PowerShell.sln` to work in VisualStudio (#6546) +- Remove duplicate `Restore-PSPackage` (#6544) +- Use `-WorkingDirectory` parameter to handle context menu when path contains single quotes (#6660) (Thanks @bergmeister!) +- Make `-CI` not depend on `-PSModuleRestore` in `Start-PSBuild` (#6450) +- Restore for official Linux arm builds (#6455) +- Fix error about setting readonly variable in `install-powershell.sh` (#6617) +- Make release macOS build work better (#6619, #6610) +- MSI: add function to generate a `MSP` package (#6445) + +### Documentation and Help Content + +- Doc: Update Ubuntu source creation commands to use `curl -o` (#6510) (Thanks @M-D-M!) +- Update stale bot message (#6462) (Thanks @iSazonov!) +- Remove extraneous SSH and install docs from the 'demos' folder (#6628) + +# [6.1.0-preview.1]- 2018-03-23 + +### Breaking Changes + +- Throw terminating error in `New-TemporaryFile` and make it not rely on the presence of the `TEMP` environment variable (#6182) (Thanks @bergmeister!) +- Remove the unnecessary `AddTypeCommandBase` class from `Add-Type` (#5407) (Thanks @iSazonov!) +- Remove unsupported members from the enum `Language` in `Add-Type` (#5829) (Thanks @iSazonov!) +- Fix range operator to work better with character ranges (#5732) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Fix `ValidateSet` with generator in a module (#5702) +- Update `SAL` annotation and fix warnings (#5617) +- Add `ForEach` and `Where` methods to `PSCustomobject` (#5756) (Thanks @iSazonov!) +- Add `Count` and `Length` properties to `PSCustomobject` (#5745) (Thanks @iSazonov!) +- Make minor fixes in compiler to properly handle void type expression (#5764) +- Logging: Fix the escaped characters when generating `.resx` file from PowerShell `ETW` manifest. (#5892) +- Remove `PSv2` only code from `Types_Ps1Xml.cs` and `HostUtilities.cs` (#5907) (Thanks @iSazonov!) +- Enable passing arrays to `pwsh -EncodedArguments` on debug builds. (#5836) +- Logging: Handle path that contains spaces in `RegisterManifest.ps1` (#5859) (Thanks @tandasat!) +- Add `-settingsfile` to `pwsh` to support loading a custom powershell config file. (#5920) +- Return better error for `pwsh -WindowStyle` on unsupported platforms. (#5975) (Thanks @thezim!) +- Enable conversions from `PSMethod` to `Delegate` (#5287) (Thanks @powercode!) +- Minor code clean-up changes in tab completion code (#5737) (Thanks @kwkam!) +- Add lambda support to `-replace` operator (#6029) (Thanks @IISResetMe!) +- Fix retrieval of environment variables on Windows in cases where variable names differ only by case. (#6320) +- Fix the `NullRefException` when using `-PipelineVariable` with `DynamicParam` block (#6433) +- Add `NullReference` checks to two code paths related to `PseudoParameterBinder` (#5738) (Thanks @kwkam!) +- Fix `PropertyOnlyAdapter` to allow calling base methods (#6394) +- Improve table view for `Certs` and `Signatures` by adding `EnhancedKeyUsageList` and `StatusMessage` (#6123) +- Fix the filtering of analytic events on Unix platforms. (#6086) +- Update copyright and license headers (#6134) +- Set pipeline thread stack size to 10MB (#6224) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Fix the `NullRefException` in `Enter-PSHostProcess` (#5995) +- Merge and Sort `BasicHtmlWebResponseObject` and `ContentHelper` in Web Cmdlets (#5720) (Thanks @markekraus!) +- Encoding for `New-ModuleManifest` on all platforms should be `UTF-8 NoBOM` (#5923) +- Make `Set-Location` use path with wildcard characters as literal if it exists (#5839) +- Combine Web Cmdlets partial class files (#5612) (Thanks @markekraus!) +- Change `Microsoft.PowerShell.Commands.SetDateCommand.SystemTime` to `struct`. (#6006) (Thanks @stknohg!) +- Add Simplified `multipart/form-data` support to Web Cmdlets through `-Form` parameter (#5972) (Thanks @markekraus!) +- Make a relative redirect URI absolute when `Authorization` header present (#6325) (Thanks @markekraus!) +- Make relation-link handling in Web Cmdlets case-insensitive (#6338) +- Make `Get-ChildItem -LiteralPath` accept `Include` or `Exclude` filter (#5462) +- Stop `ConvertTo-Json` when `Ctrl+c` is hit (#6392) +- Make `Resolve-Path -Relative` return useful path when `$PWD` and `-Path` is on different drive (#5740) (Thanks @kwkam!) +- Correct the `%c`, `%l`, `%k`, `%s` and `%j` formats in `Get-Date -UFormat` (#4805) (Thanks @iSazonov!) +- Add standard deviation implementation on `Measure-Object` (#6238) (Thanks @CloudyDino!) +- Make `Get-ChildItem /* -file` include `` as search directory (#5431) +- Enable setting `PSSession` Name when using `SSHTransport` and add `Transport` property (#5954) +- Add `Path` alias to `-FilePath` parameters and others for several commands (#5817) (Thanks @KevinMarquette!) +- Add the parameter `-Password` to `Get-PfxCertificate` (#6113) (Thanks @maybe-hello-world!) +- Don't add trailing spaces to last column when using `Format-Table` (#5568) +- Fix table alignment and padding. (#6230) +- Add `-SkipHeaderValidation` Support to `ContentType` on Web Cmdlets (#6018) (Thanks @markekraus!) +- Add common aliases for all `write-*` commands default message parameter (#5816) (Thanks @KevinMarquette!) +- Make `UTF-8` the default encoding for `application/json` (#6109) (Thanks @markekraus!) +- Enable `$env:PAGER` to work correctly if arguments are used (#6144) + +### Test + +- Convert Web Cmdlets test to `one-true-brace-style` formatting (#5716) (Thanks @markekraus!) +- Add a test for `IValidateSetValuesGenerator` in a module (#5830) (Thanks @iSazonov!) +- Fix function to test for docker OS due to change to use `linuxkit` for macOS (#5843) +- Replace `HttpListener` tests with `WebListener` (#5806, #5840, #5872) (Thanks @markekraus!) +- Stop `HttpListener` from running in Web Cmdlets tests (#5921) (Thanks @markekraus!) +- Fix `PSVersion` in `PSSessionConfiguration` tests (#5554) (Thanks @iSazonov!) +- Update test framework to support Pester v4 (#6064) +- Update tests to use Pester v4 Syntax. (#6294, #6257, #6306, #6304, #6298) +- Add negative tests for `Copy-Item` over remote sessions (#6231) +- Markdown test: Use strict in JavaScript (#6328) +- Add tests for `Get-Process` about the `-Module` and `-FileVersion` parameters (#6272) +- Add test for the `OsLocalDateTime` property of `Get-ComputerInfo`. (#6253) +- Change `Get-FileHash` tests to use raw bytes (#6430) +- Remove `runas.exe` from tests as we have tags to control this behavior (#6432) +- Refactor the `Get-Content` tests to use `-TestCases`. (#6082) +- Use `RequireAdminOnWindows` tag in `Set-Date` tests (#6034) (Thanks @stknohg!) +- Remove `-TimeOutSec` from non timeout related tests (#6055) (Thanks @markekraus!) +- Add verbosity and more accurate timeout implementation for `Start-WebListener` (#6013) (Thanks @markekraus!) +- Skip tests that use `ExecutionPolicy` cmdlets on Unix (#6021) +- Change Web Cmdlet tests to use `127.0.0.1` instead of `Localhost` (#6069) (Thanks @markekraus!) +- Fix `Start-PSPester` to include or exclude `RequireSudoOnUnix` tag smartly on Unix (#6241) +- Fix the terse output on Windows for test runs without admin privilege (#6252) +- Add `RequireSudoOnUnix` tag for `Get-Help` tests. (#6223) +- Add tests for `*-Item` Cmdlets in function provider (#6172) +- Support running tests in root privilege on Linux. (#6145) + +### Build and Packaging Improvements + +- Add option to add explorer shell context menu in Windows installer (#5774) (Thanks @bergmeister!) +- Make the explorer shell context menu registry entries platform specific to allow side by side of `x86` and `x64`. (#5824) (Thanks @bergmeister!) +- Fix start menu folder clash of shortcut when `x86` and `x64` are both installed by appending ` (x86)` for `x86` installation. (#5826) (Thanks @bergmeister!) +- Reduce image file sizes using lossless compression with `imgbot` (#5808) (Thanks @bergmeister!) +- Windows installer: Allow `Launch PowerShell` checkbox to be toggled using the space bar. (#5792) (Thanks @bergmeister!) +- Fix release packaging build (#6459) +- Fail `AppVeyor` Build if `MSI` does not build (#5755) (Thanks @bergmeister!) +- Cleanup temporarily created `WiX` files after compilation to be able to have a clean re-build (#5757) (Thanks @bergmeister!) +- Fix `install-powershell.ps1` for running during window setup (#5727) +- Start using `Travis-CI` cache (#6003) +- Fix build, packaging and installation scripts for `SLES` (#5918) (Thanks @tomconte!) +- Update recommended `WiX` toolset link to be generic to `WiX 3.x` but mention that latest version of 3.11 has to be taken (#5926) (Thanks @bergmeister!) +- Add service point manager call in `Install-PowerShell.ps1` to force `TLS1.2`. (#6310) (Thanks @DarqueWarrior!) +- Add `-Restore` when build `win-arm` and `win-arm64` (#6353) +- Make sure package verification failure fails the `AppVeyor` build (#6337) +- Specify the runtime when running `dotnet restore` in `Start-PSBuild` (#6345) +- Rename `log` and `logerror` to `Write-Log [$message] [-error]` (#6333) +- Make Linux packages use correct version scheme for preview releases (#6318) +- Add support for Debian in `installpsh-debian.sh` (#6314) (Thanks @Pawamoy!) +- MSI: Make preview builds to install Side by side with release builds (#6301) +- Add `TLS1.2` workaround for code coverage script (#6299) +- Cleanup after Powershell install for `CentOS` and `Fedora` Docker images (#6264) (Thanks @strawgate!) +- MSI: Update the environment variable PATH with proper value (#6441) +- MSI: Remove the version from the product name (#6415) +- Support non-GitHub commits in the change log generation script (#6389) +- Fix secret and JavaScript compliance issues (#6408) +- Remove `AppVeyor` specific cmdlet from `Start-NativeExecution` (#6263) +- Restore modules from the `NuGet` package cache by using `dotnet restore` (#6111) +- CI Build: Use `TRAVIS_PULL_REQUEST_SHA` to accurately get the commit message (#6024) +- Use `TLS1.2` on Windows during `Start-PSBootstrap` (#6235) (Thanks @CallmeJoeBob!) +- Use `TLS1.2` in `Start-PSBootStrap` without breaking `HTTPS` (#6236) (Thanks @markekraus!) +- Add options to enable `PSRemoting` and register Windows Event Logging Manifest to MSI installer (#5999) (Thanks @bergmeister!) + +### Documentation and Help Content + +- Separate macOS from Linux install instructions. (#5823) (Thanks @thezim!) +- Show usage (short) help if command line parameter is wrong (#5780) (Thanks @iSazonov!) +- Add the breaking changes doc for 6.0.0 release. (#5620) (Thanks @maertendMSFT!) +- Remove DockerFile for Fedora 25 and add DockerFile for Fedora 27 (#5984) (Thanks @seemethere!) +- Add a missing step to prepare the build environment on Mac. (#5901) (Thanks @zackJKnight!) +- Update `BREAKINGCHANGES.md` to include WebCmdlets breaking changes (#5852) (Thanks @markekraus!) +- Fix typos in `BREAKINGCHANGES.md` (#5913) (Thanks @brianbunke!) +- Update `macos.md` to use `brew cask upgrade` for upgrading powershell (#5875) (Thanks @timothywlewis!) +- Add verification step to macOS install docs (#5860) (Thanks @rpalo!) +- Fix links in macOS install docs (#5861) (Thanks @kanjibates!) +- Update docs with test guidelines with the `RequireSudoOnUnix` tag. (#6274) +- Add `Alpine` Linux support (#6367) (Thanks @kasper3!) +- Update to Governance doc to reflect current working model (#6323) +- Add guidance on adding copyright and license header to new source files (#6140) +- Fix the command to build type catalog in `internals.md` (#6084) (Thanks @ppadmavilasom!) +- Fix `Pull Request Process` dead link (#6066) (Thanks @IISResetMe!) +- Update processes to allow for coordinated vulnerability disclosure (#6042) +- Rework Windows Start menu folder name (#5891) (Thanks @Stanzilla!) +- Update `Raspbian` installation instructions to create `symlink` for `pwsh` (#6122) +- Fix various places that still refer to old versions of `pwsh` (#6179) (Thanks @bergmeister!) +- Correct a Linux installation typo (#6219) (Thanks @mababio!) +- Change synopsis of `install-powershell.ps1` to reflect that it works cross-platform (#5465) (Thanks @bergmeister!) + +## [6.0.2] - 2018-03-15 + +### Engine updates and fixes + +- Update PowerShell to use `2.0.6` dotnet core runtime and packages (#6403) + - This change addresses this vulnerability: [Microsoft Security Advisory `CVE-2018-0875`: Hash Collision can cause Denial of Service](https://github.com/PowerShell/Announcements/issues/4) + +### Build and Packaging Improvements + +- Add Ubuntu build without `AppImage` (#6380) +- Add scripts to set and or update the release tag in `VSTS` (#6107) +- Fix `DSC` Configuration compilation (#6225) +- Fix errors in `Start-PSBootStrap` during release builds (#6159) +- Fix spelling failures in `CI` (#6191) +- Use PowerShell `windowsservercore` Docker image for release builds (#6226) +- Use `ADD` instead of `Invoke-WebRequest` in `nanoserver` Docker file (#6255) +- When doing daily/test build in a non-release branch use the branch name as the preview name (#6355) +- Add Environment Variable override of telemetry (#6063) (Thanks @diddledan!) +- Build: Remove two unneeded lines from `Invoke-AppveyorFinish` (#6344) +- MSI: Refactor `New-MsiPackage` into `packaging.psm1` + and various fixes to enable patching + (#5871, #6221, #6254, #6303, #6356, #6208, #6334, #6379, #6094, #6192) +- MSI: Use `HKLM` instead of `HKCU` registry keys since the current installation scope is per-machine. (#5915) (Thanks @bergmeister!) + +## [6.0.1] - 2018-01-25 + +### Engine updates and fixes + +- Update PowerShell to use `2.0.5` dotnet core runtime and packages. (#5903, #5961) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +- Re-release of `v6.0.0` as `v6.0.1` due to issues upgrading from pre-release versions + +### Test + +- Update regular expression to validate `GitCommitId` in `$PSVersionTable` to not require a pre-release tag (#5893) + +[6.1.6]: https://github.com/PowerShell/PowerShell/compare/v6.1.5...v6.1.6 +[6.1.5]: https://github.com/PowerShell/PowerShell/compare/v6.1.4...v6.1.5 +[6.1.4]: https://github.com/PowerShell/PowerShell/compare/v6.1.3...v6.1.4 +[6.1.3]: https://github.com/PowerShell/PowerShell/compare/v6.1.2...v6.1.3 +[6.1.2]: https://github.com/PowerShell/PowerShell/compare/v6.1.1...v6.1.2 +[6.1.1]: https://github.com/PowerShell/PowerShell/compare/v6.1.0...v6.1.1 +[6.1.0]: https://github.com/PowerShell/PowerShell/compare/v6.1.0-rc.1...v6.1.0 +[6.1.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v6.1.0-preview.4...v6.1.0-rc.1 +[6.1.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v6.1.0-preview.3...v6.1.0-preview.4 +[6.1.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v6.1.0-preview.2...v6.1.0-preview.3 +[6.1.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v6.1.0-preview.1...v6.1.0-preview.2 +[6.1.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v6.0.2...v6.1.0-preview.1 +[6.0.2]: https://github.com/PowerShell/PowerShell/compare/v6.0.1...v6.0.2 +[6.0.1]: https://github.com/PowerShell/PowerShell/compare/v6.0.0...v6.0.1 diff --git a/CHANGELOG/6.2.md b/CHANGELOG/6.2.md new file mode 100644 index 00000000000..bf54f978eba --- /dev/null +++ b/CHANGELOG/6.2.md @@ -0,0 +1,875 @@ +# 6.2 Changelog + +## [6.2.7] - 2020-07-16 + +### Build and Packaging Improvements + +
+ +
    +
  • Fix Azure file copy issues in release build by fixing the path to upload directory content (#13182)
  • +
  • Update .NET Core to version 2.1.808 (Internal 12003)
  • +
  • Fix Azure file copy break in AzDevOps by updating task version to latest (#13173)
  • +
+ +
+ +## [6.2.6] - 2020-06-11 + +### Engine Updates and Fixes + +- Restrict loading of `amsi.dll` to `system32` folder (#12730) + +### Tools + +- Update the PowerShell team list to correct changelog generation (#12927) + +### Tests + +- Pin major Pester version to 4 to prevent breaking changes caused by upcoming release of `v5` (#12262) (Thanks @bergmeister!) + +### Build and Packaging Improvements + +
+ + + +

Update to `.NET Core 2.1.807`

+ +
+ +
    +
  • update to dotnet 2.1.807 (Internal 11697)
  • +
  • update hosting tests
  • +
  • Check if Azure Blob exists before overwriting (#12921)
  • +
  • Upgrade APIScan version (#12876)
  • +
  • Fix break in package build by pinning ffi version to 1.12 (#12889)
  • +
  • Update the build to sign any unsigned files as 3rd party Dlls (#12581)
  • +
+ +
+ +## [6.2.5] - 2020-05-14 + +### Build and Packaging Improvements + +
+ +
    +
  • Port back the code for new changelog format.
  • +
  • Work around FPM issue with a specific version on macOS
  • +
  • Update the combined package build to release the daily builds (#10449)
  • +
  • Refactor packaging pipeline (#11852)
  • +
  • Bump .NET SDK version to the version 2.1.18
  • +
  • Move to standard internal pool for building (#12119)
  • +
+ +
+ +## [6.2.4] - 2020-01-27 + +### General Cmdlet Updates and Fixes + +- Enable `Start-Process` to work on Windows 7 (#10417) (Thanks @iSazonov!) +- Fix global tool issues around exit code, command line parameters, and paths with spaces (#10461) +- Make `Add-Type` usable in applications that host PowerShell (#10587) + +### Build and Packaging Improvements + +- Update to use `TSAv2` (#9914) +- Update the dotnet SDK install script URL in `build.psm1` (#10927) +- Update dependencies needed by Azure PowerShell and patch for `Newtonsoft.Json` (Internal 10798) +- Fix path for getting reference assemblies (Internal 10792) + +## [6.2.3] - 2019-09-12 + +### Engine Updates and Fixes + +- Fix debugger performance regression in system lock down mode (#10269) + +### Tests + +- Remove `markdownlint` tests due to security issues (#10163) + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9946) +- Fix macOS build break (#10207) + +## [6.2.2] - 2019-07-16 + +### Breaking Changes + +- Disable `Enter-PSHostProcess` cmdlet when system in lock down mode (Internal 8969) + +### Engine Updates and Fixes + +- Create `JumpList` in STA thread as some COM APIs are strictly STA only to avoid sporadic CLR crashes (#10057, #9928) (Thanks @bergmeister!) + +### Build and Packaging Improvements + +- Update DotNet SDK and runtime framework version (Internal 9082, 9088, 9092) +- Make `Hashtable` case insensitivity test use current culture rather than shell to set culture (Internal 8529) +- Add automated RPM signing to release build (#10013) +- Update copyright symbol for NuGet packages (#9936) +- Bump `Microsoft.ApplicationInsights` from `2.9.1` to `2.10.0` (#9757) +- Switch from BMP to PNG for graphical MSI installer assets (#9606) +- Bump `System.Net.Http.WinHttpHandler` from `4.5.3` to `4.5.4` (#9789) +- Enable building of MSIX package (#9289, #9715) + +## [6.2.1] - 2019-05-21 + +### Engine Updates and Fixes + +- Re-enable tab completion for functions (#9383) +- Disable debugger in System Lock down mode (Internal 8428) + +### Code Cleanup + +- Update repo for Ubuntu 14.04 EOL (#9324) + +### Tests + +- Fix skipping of tests in `RemoteSession.Basic.Tests.ps1` (#9304) +- Update tests to account for when `$PSHOME` is read only (#9279) +- Mark tests in macOS CI which use `applescript` as pending/inconclusive (#9352) +- Make sure non-Windows CI fails when a test fails (#9303) + +### Build and Packaging Improvements + +- Partially revert "Fix the failed test and update `Publish-TestResults` to make `AzDO` fail the task when any tests failed (#9457)" +- Bump `Markdig.Signed` from `0.16.0` to `0.17.0` (#9595) +- Bump `Microsoft.PowerShell.Archive` from `1.2.2.0` to `1.2.3.0` in `/src/Modules` (#9594) +- Enable building on Kali Linux (#9471) +- Fix the failed test and update `Publish-TestResults` to make `AzDO` fail the task when any tests failed (#9457) +- Add Preview assets for `msix` (#9375) +- Create code coverage and test packages for non-windows (#9373) +- Fix publishing daily `nupkg` to MyGet (#9269) +- Bump `PackageManagement` from `1.3.1` to `1.3.2` in `/src/Modules` (#9568) +- Bump `NJsonSchema` from `9.13.27` to `9.13.37` (#9524) +- Bump `gulp` from `4.0.0` to `4.0.2` in `/test/common/markdown` (#9443) +- Bump `Newtonsoft.Json` from `12.0.1` to `12.0.2` (#9433) +- Bump `System.Net.Http.WinHttpHandler` from `4.5.2` to `4.5.3` (#9367) +- Add `AccessToken` variable to jobs that perform signing (#9351) +- Create test package for macOS on release builds (#9344) +- Add component detection to all jobs (#8964) +- Move artifacts to artifact staging directory before uploading (#9273) + +## [6.2.0] - 2019-03-28 + +### Breaking Changes + +- Fix `-NoEnumerate` behavior in `Write-Output` to be consistent with Windows PowerShell (#9069) (Thanks @vexx32!) + +### Engine Updates and Fixes + +- Add PowerShell remoting enable/disable cmdlet warning messages (#9203) +- Fix for `FormatTable` remote deserialization regression (#9116) +- Update the task-based `async` APIs added to PowerShell to return a Task object directly (#9079) +- Add 5 `InvokeAsync` overloads and `StopAsync` to the `PowerShell` type (#8056) (Thanks @KirkMunro!) + +### General Cmdlet Updates and Fixes + +- Enable `SecureString` cmdlets for non-Windows by storing the plain text (#9199) +- Add Obsolete message to `Send-MailMessage` (#9178) +- Fix `Restart-Computer` to work on `localhost` when WinRM is not present (#9160) +- Make `Start-Job` throw terminating error when PowerShell is being hosted (#9128) +- Update version for `PowerShell.Native` and hosting tests (#8983) + +### Tools + +- Adding `CmdletsToExport` and `AliasesToExport` to test module manifests. (#9108) (Thanks @powercode!) +- Comment cleanup in `releaseTools.psm1` (#9064) (Thanks @RDIL!) + +### Tests + +- Fix `Enter-PSHostProcess` tests flakiness (#9007) +- Add tests for command globbing (#9180) +- Add source for `Install-package` to install `netDumbster` (#9081) (Thanks @Geweldig!) +- Fix tab completion test to handle multiple matches (#8891) +- Refactor macOS and Linux CI so that tests run in parallel (#9056, #9209) +- Added `RequireSudoOnUnix` tags to `PowerShellGet` tests and remove `-pending` parameter (#8954) (Thanks @RDIL!) +- Pending `NamedPipeConnectionInfo` test (#9003) (Thanks @iSazonov!) +- Add test for `-WhatIf` for `New-FileCatalog` (#8966) (Thanks @mjanko5!) + +### Build and Packaging Improvements + +- Performance improvements for release build (#9179) +- Add `tsaVersion` property as `TsaV1` for compliance build phase (#9176) +- Publish global tool packages to `pwshtool` blob and bug fixes (#9163) +- Translate Skipped test results into something Azure DevOps does **not** understand (#9124) +- Disable Homebrew analytics in macOS VSTS builds (#9130) (Thanks @RDIL!) +- Remove AppVeyor references from packaging tools (#9117) (Thanks @RDIL!) +- Fixed Dockerfile syntax highlighting (#8991) (Thanks @RDIL!) +- Fix dependencies of NuGet build to wait on DEB uploads to finish (#9118) +- Fix artifact download issue in release build (#9095) +- Publish test package on release builds (#9063) +- Bump `Microsoft.PowerShell.Native` from `6.2.0-rc.1` to `6.2.0` (#9200) +- Bump `NJsonSchema` from `9.13.19` to `9.13.27` (#9044, #9136, #9166, #9172, #9184 #9196) +- Bump `PowerShellGet` from `2.0.4` to `2.1.2` in /src/Modules (#9110, #9145) +- Bump `SelfSignedCertificate` in `/test/tools/Modules` (#9055) + +### Documentation and Help Content + +- Update docs for `6.2.0-rc.1` release (#9022) + +## [6.2.0-rc.1] - 2019-03-05 + +### Breaking Changes + +- Make `Join-String -InputObject 1,2,3` result equal to `1,2,3 | Join-String` result (#8611) (Thanks @sethvs!) + +### Engine Updates and Fixes + +- Improve check for developer mode by checking minimum required build number (#8749) +- Simplify the declaration of new experimental features (#8726) +- Remove AMSI uninitialized assert and replace with call to uninitialized (#8713) +- Port Security bypass fixes from 6.1.3 (#8915) +- Enable discovering modules that have names same as a culture (e.g. `Az`) (#8777) +- Flatten interface hierarchy when generating properties that implement interface properties (#8382) (Thanks @IISResetMe!) +- Don't use Win32 native APIs on non-Windows for cryptography of secure string over remoting (#8746) +- Allow `.exe` files to be used as IL binary modules (#7281) +- Remove unused cached types (#9015) + +### Experimental Features + +- Add the experimental feature for creating `Temp:\` drive when `FileSystemProvider` initializes (#8696) +- Move `CommandNotFoundException` suggestion to an experimental feature (#8805) + +### General Cmdlet Updates and Fixes + +- Correctly Report impact level when `SupportsShouldProcess` is not set to 'true' (#8209) (Thanks @vexx32!) +- Fix Request Charset Issues in Web Cmdlets (#8742) (Thanks @markekraus!) +- Refactor `ConvertTo-Json` to expose `JsonObject.ConvertToJson` as a public API (#8682) +- Add `-CustomPipeName` to `pwsh` and `Enter-PSHostProcess` (#8889) +- Add configurable maximum depth in `ConvertFrom-Json` with `-Depth` (#8199) (Thanks @louistio!) +- Enable creating relative symbolic links on Windows with `New-Item` (#8783) +- Parse numeric strings as numbers again during conversions (#8681) (Thanks @vexx32!) +- Expose file attributes of `OneDrive` placeholders (#8745) (Thanks @sba923!) +- Enable `Write-Information` to accept `$null` (#8774) +- Adding parameter `ReplyTo` to `Send-MailMessage` (#8727) (Thanks @replicaJunction!) +- Fix `Get-Help` `PSTypeName` issue with `-Parameter` when only one parameter is declared (#8754) (Thanks @pougetat!) + +### Code Cleanup + +- Use HTTPS in URLs where available (#8622) (Thanks @xtqqczze!) +- Update code to use single method to check if path is UNC (#8680) +- Fix typo: `aganist` ➜ `against` (#8943) (Thanks @lupino3!) +- Use the `OperationCancellationException` to replace the `StoppingException` in `ConvertToJson` (#8920) +- Fix style issues in CSV cmdlets (#8894) (Thanks @iSazonov!) +- Fix `LGTM` issues (#8843) (Thanks @iSazonov!) +- Fix length check in `PSSnapinQualifiedName.GetInstance()` (#8837) (Thanks @hvitved!) +- Reduce string allocations when formatting file system objects. (#8831) (Thanks @powercode!) +- Fix many instances of CodeFactor style issue `A single-line comment must not be followed by a blank line` (#8825) (Thanks @RDIL!) +- Refactor `appveyor.psm1` to `ci.psm1` (#8733, #8854, #8709, #8756, #8867) (Thanks @RDIL!) +- Refactor `travis.ps1` into `ci.psm1` (#8822, #8888) (Thanks @RDIL!) +- Fix Markdown lint issues (#8929) +- Fix code-of-conduct linting (#8896) (Thanks @RDIL!) + +### Tools + +- Fix broken reference (#8753) (Thanks @RDIL!) +- Remove `GitKracken` files from `.gitignore` (#8743) (Thanks @RDIL!) +- Update path of `test\xUnit\xUnit.tests.csproj` in `PowerShell.sln` (#8730) (Thanks @markekraus!) +- Ignore files added by `SelfSignedCertificate` (#8728) (Thanks @markekraus!) +- Build Global tool for PowerShell and SDK container (#8984) +- Add Experimental Features to change log creation (#8827) +- Remove unneeded `Invoke-Expression` on unvalidated input (#8826) +- Update CLA pull request labeling info (#8820) (Thanks @RDIL!) +- Update some info in `md-link-checks` (#8757) (Thanks @RDIL!) + +### Tests + +- Fix `Enter-PSHostProcess` test to wait until runspace is ready before attempting to enter (#8725) +- Package validation tests updates (#8714) +- Make xUnit tests run sequentially to avoid race conditions caused by manipulating `powershell.config.json` in tests (#8945) +- Use verbatim string literals for paths (#8937) (Thanks @iSazonov!) +- Parallelize the Windows CI to enable us to run all tests all the time (#8868) +- Fixes for Scheduled release build (#8887) +- Remove references to uninitialized variable (#8849) +- Remove directory causing static analysis failure (#8812) +- Update Pester version to 4.4.4 (#8739) +- Change xUnit Runspace tests to run sequentially (#8796) +- Fix cleanup config files for the csharp xUnit tests (#8761) (Thanks @iSazonov!) +- Moved `fxdependent-dotnetsdk-latest/Dockerfile` (#8738) + +### Build and Packaging Improvements + +- Make every `csproj` files have its own folder (#8750) +- Update packaging script to build reference assembly targeting `netcoreapp2.1` and use actual `.csproj` files (#8729) +- Generate and deploy reference assembly for `Microsoft.PowerShell.Commands.Utility.dll` (#8716) +- Make test file result names unique (#8979) +- Add variable to control the version of the signing task we use (#8982) +- Publish test and code coverage artifacts for daily builds (#8955) +- Integrate building NuGet package in the coordinated build (#8947) +- Support release branches based on the forward slash separator (#8903) +- Port DotNet fixes from 6.1.3 (#8914) +- Start tracking release build information in an azure storage table (#8850) +- Make license a link in the MSI (#8846) +- Use `-ErrorAction Ignore` instead of `SilentlyContinue` with `Get-Command` in build.psm1 (#8832) +- Add `binskim` to coordinated build and increase timeout (#8834) +- Fix daily CI builds to publish tar package as artifacts (#8775) +- Add instrumentation for `Start-PSPackage` (#8811) +- Fix passing credential to the `SyncGalleryToAzArtifacts.psm1` script (#8808) +- Move Final artifacts from coordinated build to `finalResults` folder (#8806) +- Refactor coordinated release build (#8804) +- Add compliance to Coordinated build (#8798) +- Switch to 1.11 of FPM to fix FPM install issue (#8797) +- Update the coordinated build with framework dependent package for dotnet SDK (#8773) +- Fix MSI upgrade failure for preview builds (#9013) +- Build(deps): Bump `Microsoft.ApplicationInsights` from `2.8.1` to `2.9.1` (#8807,#8848) +- Build(deps): Bump `Microsoft.PowerShell.Native` (#8712) +- Build(deps): Bump `NJsonSchema` from `9.13.15` to `9.13.19` (#8732, #8747, #8881, #8952) +- Build(deps): Bump `PackageManagement` from `1.2.4` to `1.3.1` (#8800) +- Build(deps): Bump `XunitXml.TestLogger` from `2.0.0` to `2.1.26` (#8731) +- Build(deps): Bump `Markdig.Signed` from `0.15.7` to `0.16.0` (#8981) + +### Documentation and Help Content + +- Updating README.md for supported openSUSE version and updating link to OS versions supported by CoreFx (#8701) (Thanks @stknohg!) +- Add complete XML docs for `ConvertToJsonContext` constructors (#8737) +- Update README.md for ARM to include both 32-bit and 64-bit PS package links (#8677) (Thanks @slide!) +- Update issue templates with new supported values (#8718) (Thanks @RDIL!) +- Update maintainer docs about the CLA PR labels (#8734) (Thanks @RDIL!) +- Add Andrew to the maintainer list (#8722) +- Update release process template (#8711) +- Change label in doc issue template (#8895) (Thanks @iSazonov!) +- Update the `dir -recurse` example (#8939) (Thanks @vmsilvamolina!) +- Update CHANGELOG for release `6.1.3` (#8918) +- Update stable version to `6.1.3` (#8902) +- Fix broken link (#8905) +- Update Coding Guidelines (#8844) (Thanks @iSazonov!) +- Update governance documentation (#8776) (Thanks @RDIL!) +- Fix broken python method (#8821) (Thanks @RDIL!) +- Changing docs issue template to new docs repo location (#8818) +- Fix spelling in `releaseTool/README.md` (#8810) +- Update GitHub templates (#8792) (Thanks @iSazonov!) +- Fix broken link in `FAQs.md` (#8803) +- Updated `basics.md` to add a link for showing example for installing git on all package managers (#8735) (Thanks @RDIL!) +- Update `README.md` for `preview.4` (#8772) + +## [6.2.0-preview.4] - 2019-01-28 + +### Breaking Changes + +- Add `-Stable` to `Sort-Object` and related tests (#7862) (Thanks @KirkMunro!) +- Improve `Start-Sleep` cmdlet to accept fractional seconds (#8537) (Thanks @Prototyyppi!) +- Change hashtable to use `OrdinalIgnoreCase` to be case-insensitive in all Cultures (#8566) +- Fix `LiteralPath` in `Import-Csv` to bind to `Get-ChildItem` output (#8277) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Allow user-specified underlying type for enums (#8329) (Thanks @IISResetMe!) +- Handle case where AppLocker test script fails to delete (#8627) +- Update `CommandNotFound` fuzzy suggestion to only return unique results (#8640) +- Add support to show suggestions on `CommandNotFound` exception (#8458) +- Make `S.M.A.PowerShell.GetSteppablePipeline` method public (#8055) (Thanks @KirkMunro!) +- Add `S.M.A.PowerShell.Create` method overload with Runspace argument (#8057) (Thanks @KirkMunro!) +- Fix mistake on deserialization (#8502) +- Fix formatting of header of table when center aligned (#8497) +- Add `-RepeatHeader` to `Format-Table` to enable repeating header for each screen full (#8481) +- Fix `Debug-Runspace` for Unix platforms and properly enable Windows identity impersonation code (#8451) +- Reset output attributes if column had `ESC` char when using `Format-Table`; Replace `...` with unicode ellipsis (#8326) + +### Experimental Features + +- Add the experimental feature `PSUseAbbreviationExpansion` to support tab completion on abbreviated command names (#8109) + +### General Cmdlet Updates and Fixes + +- Fix code page parsing issue in `Invoke-RestMethod` (#8694) (Thanks @markekraus!) +- Fix `Expect 100-continue` issue with Web Cmdlets (#8679) (Thanks @markekraus!) +- Allow 'name' as an alias key for 'label' in `ConvertTo-Html`, allow the 'width' entry to be an integer (#8426) (Thanks @mklement0!) +- Resolve `:PAGER` if its path contains spaces (#8571) (Thanks @pougetat!) +- Add support enum and char types in `Format-Hex` cmdlet (#8191) (Thanks @iSazonov!) +- Change `Get-Help` cmdlet `-Parameter` parameter so it accepts string arrays (#8454) (Thanks @sethvs!) +- Fix `FixupFileName` to not load resolved assembly during module discovery (#8634) +- Change `Clear-Host` back to using `$RAWUI` and `clear` to work over remoting (#8609) +- Fix `LiteralPath` in `Import-Csv` to bind to `Get-ChildItem` output (#8277) (Thanks @iSazonov!) +- Make scriptblock based calculated properties work again in `ConvertTo-Html` (#8427) (Thanks @mklement0!) +- Fix `Join-String` cmdlet `FormatString` parameter logic (#8449) (Thanks @sethvs!) +- Allow Windows users in developer mode to create symlinks without elevation (#8534) +- `Help` function should only pass content to pager if content was found (#8528) +- Change `Clear-Host` to simply called `[console]::clear` and remove `clear` alias from Unix (#8603) +- `help` function shouldn't use pager for `AliasHelpInfo` (#8552) +- Fix XML nesting bug in `CustomSerializer.WriteMemberInfoCollection()` (#8476) (Thanks @IISResetMe!) +- Add `-UseMinimalHeader` to `Start-Transcript` to minimize transcript header (#8402) (Thanks @lukexjeremy!) + +### Code Cleanup + +- Remove the no longer used `RunspaceConfigurationEntry` types (#8424) +- Remove unneeded catch/throw from `mkdir` and `oss` functions (#8425) +- Remove comments after closing brackets (#8344) (Thanks @Meir017!) +- Cleanup `Format-Hex` (#8683) (Thanks @vexx32!) +- Delete `appveyor.yml` (#8639) (Thanks @RDIL!) +- Revise use of `Start-Sleep` cmdlet (#8633) (Thanks @xtqqczze!) +- Style: Change first char to upper in summary comments (#8597) (Thanks @iSazonov!) +- Style: Use the type aliases `char` and `bool` instead of `Char` and `Boolean` (#8572) (Thanks @iSazonov!) +- Style: Use the type alias `string` instead of `String` in places that are appropriate (#8573) (Thanks @iSazonov!) +- Correctly capitalize the `ForEach` operator in `*.ps1` (#8583) (Thanks @xtqqczze!) +- Remove unnecessary trim of passed-in command line in interactive debugging (#8594) +- Style: Add a space after "//" in comments and remove unneeded comments after "}" (#8576) (Thanks @iSazonov!) +- Style: Add the ending period to the XML document texts (#8577) (Thanks @iSazonov!) +- Avoid use of `mkdir` alias in `*.ps1` and `*.psm1` (#8582) (Thanks @xtqqczze!) +- Regularize redirection operator spacing in `*.ps1` and `*.psm1` (#8581) (Thanks @xtqqczze!) +- Style: Change 'String.' to 'string.' (#8568) (Thanks @iSazonov!) +- Style: Replace `String.IsNullOrEmpty` with `string.IsNullOrEmpty` (#8557) (Thanks @iSazonov!) +- Fix typo in AMSI test (#8561) (Thanks @iSazonov!) +- Style: Convert to upper first char in `` and `` doc tags (#8556) (Thanks @iSazonov!) +- Style: Add period before `` and `` doc tags (#8553) (Thanks @iSazonov!) +- Remove use of cmdlet aliases from `.\test\powershell` (#8546) (Thanks @xtqqczze!) +- Style: Remove extra spaces after `` and before `` docs tags (#8547) (Thanks @iSazonov!) +- Style: Remove preceding spaces from C# `preprocessor-type` keywords (#8540) (Thanks @xtqqczze!) +- Style: remove ` ` (#8538) (Thanks @iSazonov!) +- Style: Add period before returns doc tag (#8535) (Thanks @iSazonov!) +- Style: Change `Object[]` to `object[]` (#8526) (Thanks @iSazonov!) +- Style: Change `Object` to `object` (#8522) (Thanks @iSazonov!) +- Style: Change `UInt64?` to `ulong?` (#8527) (Thanks @iSazonov!) +- Style: Change `Byte{}` to `byte[]` (#8525) (Thanks @iSazonov!) +- Code cleanup: Add space after closing brace where needed (#8530) +- Style: Change `System.Boolean` to `bool` (#8521) (Thanks @iSazonov!) +- Change `String` to `string` for simple references (#8519) +- Change `Int32` to `int` for simple references in variable declaration (#8518) +- Style: Member access symbols should be followed with member name (#8517) +- Style: Remove extra space before colon in named parameters (#8504) +- Style: Use the shorthand of the `nullable` type (#8501) +- Remove empty lines; correct space on closing square brackets, negative signs, and generic brackets (#8508) +- Remove space after new keyword in implicitly typed array allocation (#8505) +- The static keyword should be right after access modifier (#8506) +- Remove comments after closing bracket (#8503) +- Remove space character after `'!'` (#8507) +- Style: Remove extra space before colon in named parameters (#8504) + +### Tools + +- Recommend Azure DevOps extension inside VS-Code for better `YAML` editing. (#8403) (Thanks @bergmeister!) +- `-AddToPath` re-implementation in `install-powershell.ps1` (#8081) (Thanks @glachancecmaisonneuve!) +- Change the feed `URL` to feed name due to changes in `AzDevOps` (#8664) +- Batch merge builds together while a merge build is running (#8668) +- Fix grammar in stale bot message (#8660) (Thanks @RDIL!) +- Add macOS files to `.gitignore` (#8456) (Thanks @RDIL!) +- Name the spelling yaml something more appropriate (#8601) (Thanks @RDIL!) +- Add script to create `icns` files. (#7456) (Thanks @thezim!) +- Pass `nugetkey` as parameter (#8461) +- Add `gitkracken` files to `gitignore` (#8434) (Thanks @RDIL!) +- Create release process issue template (#8417) +- Support for `linuxmint` in `installpsh-debian.sh` (#8440) (Thanks @DarwinJS!) +- Enable `install-powershell.ps1` to use `MSI` (#8418) + +### Tests + +- Remove broken `HelpUri` from `CimTest` (#8688) (Thanks @xtqqczze!) +- Remove appveyor environment checks (#8669) (Thanks @RDIL!) +- Adding tests for `PSDiagnostics Module` (#8431) (Thanks @kvprasoon!) +- Increase diagnose-ability of Link Checker failures (#8667) +- Fix broken urls (#8653) +- Update fuzzy test to fix daily build (#8629) +- Create link check task (#8471) (Thanks @RDIL!) +- Add Tests for `ConfirmImpact` Ratings (#8214) (Thanks @vexx32!) +- Fix style issues in xUnit tests (#8465) (Thanks @iSazonov!) +- Move `xUnit` tests in new folder (#8356) (Thanks @iSazonov!) +- Fix environment variable test and add missing null check in `CommandHelpProvider` (#8408) +- Remove `dotnet` dependency to start WebListener (#8390) + +### Build and Packaging Improvements + +- Update Third Party Notices (#8415) +- Adding yaml for Windows Release builds (#8374) +- Bump `NJsonSchema` from `9.13.1` to `9.13.2` (#8422) +- Do not ship fullclr binaries of `PackageManagement` (#8700) (Thanks @bergmeister!) +- Fix the build for `fxdependent` build for `dotnet sdk` (#8670) +- Add Windows build to universal release build YAML (#8695) +- Remove `Debian 8` references as it is EOL (#8678) +- Build(deps): Bump `NJsonSchema` from `9.13.14` to `9.13.15` (#8671) +- Build package build using ubuntu 18.04 image (#8666) +- Fix a typo in `packaging.psm1` (#8647) (Thanks @sethvs!) +- Add function to create a framework dependent package `dotnet-sdk` containers (#8644) +- Build(deps): Bump `NJsonSchema` from `9.13.13` to `9.13.14` (#8648) +- Build(deps): Bump `PowerShellGet` from `2.0.3` to `2.0.4` (#8649) +- Fix installing `fpm` and `ronn` in macOS CI by avoid installing docs for them (#8656) +- Build(deps): Bump `Markdig.Signed` from `0.15.6` to `0.15.7` (#8637) +- Build(deps): Bump `System.Security.Cryptography.Pkcs` from `4.5.1` to `4.5.2` (#8614) +- Build(deps): Bump `System.Net.Http.WinHttpHandler` from `4.5.1` to `4.5.2` (#8615) +- Build(deps): Bump `NJsonSchema` from `9.13.11` to `9.13.13` (#8616) +- Build(deps): Bump `System.Text.Encoding.CodePages` from `4.5.0` to `4.5.1` (#8613) +- Enable install of Preview MSI release side-by-side with Stable release (#8513) +- Get macOS to publish daily build to nugetfeed (#8464) +- Build(deps): Bump `Markdig.Signed` from `0.15.5` to `0.15.6` (#8558) +- Build(deps): Bump `NJsonSchema` from `9.13.10` to `9.13.11` (#8569) +- Remove duplicate `Open Here` context menu item upgrading to newer Preview release (#8496) +- Bump `NJsonSchema` from `9.13.9` to `9.13.10` (#8511) +- Bump `NJsonSchema` from `9.13.7` to `9.13.9` (#8498) +- Bump `NJsonSchema` from `9.13.4` to `9.13.7` (#8493) +- Bump `NJsonSchema` from `9.13.3` to `9.13.4` (#8462) +- Fix daily NuGet publishing (#8460) +- Bump `NJsonSchema` from `9.13.2` to `9.13.3` (#8457) +- Bump `Markdig.Signed` from `0.15.4` to `0.15.5` (#8444) + +### Documentation and Help Content + +- Remove unused `AppVeyor` links from `README.md` (#8685) (Thanks @RDIL!) +- Update `README.md` (#8684) +- Update Package Management license to MIT (#8676) (Thanks @RDIL!) +- Create Support File (#8618) (Thanks @RDIL!) +- Update git clone URL (#8673) (Thanks @RDIL!) +- docs(contributing): add link check information (#8659) (Thanks @RDIL!) +- Update License and Third Party Notice (#8646) +- Update README, `metadata.json` and changelog for release `6.1.2` (#8658) +- Fix typo in `README.md` (#8642) (Thanks @MarkTiedemann!) +- Fix some typos in the README (#8623) (Thanks @RDIL!) +- Remove `en-us` from `docs.microsoft.com` URL (#8628) (Thanks @xtqqczze!) +- Update examples for hosting PSCore and remove old outdated examples (#8472) (Thanks @bergmeister!) +- Update the pull request template (#8624) (Thanks @RDIL!) +- Contributing guidelines: Remove references to Travis CI and AppVeyor (#8617) (Thanks @RDIL!) +- Update code coverage analysis document (#8543) (Thanks @xtqqczze!) +- Remove `en-us` from our doc links (#8602) +- Document `First-time-issue` and `Hackathon`/`Hacktoberfest` labels (#8575) +- Updated linux build link (#8579) (Thanks @jwmoss!) +- Update contributing guidelines doc to run spellchecking in English (#8473) (Thanks @RDIL!) +- Updating links to point to new VS Code docs (#8468) + +## [6.2.0-preview.3] - 2018-12-10 + +### Breaking Changes + +- `Get-ExperimentalFeature` no longer has `-ListAvailable` switch (#8318) +- `Debug` parameter now sets `DebugPreference` to `Continue` instead of `Inquire` (#8195) (Thanks @KirkMunro!) + +### Engine Updates and Fixes + +- Improve PowerShell startup time by 24% (#8341) (#8396) +- Remove extra newlines from formatting which resulted in unnecessary double newlines (#8247) +- Add `Enable-ExperimentalFeature` and `Disable-ExperimentalFeature` cmdlets (#8318) +- Fix `Export-ModuleMember` bug for a `ScriptBlock` having no context (#8363) +- Fix race condition to access `powershell.config.json` (#8249) (Thanks @iSazonov!) +- Add `SkipCA` and `SkipCN` check requirement to WinRM/OMI HTTPS connection (#8279) +- Add fix for `Start-Job` initialization script which should not be executed as trusted in system lockdown (#8284) + +### General Cmdlet Updates and Fixes + +- Add `Enable-ExperimentalFeature` and `Disable-ExperimentalFeature` cmdlets (#8318) +- Add cmdlet `Join-String` for creating text from pipeline input (#7660) (Thanks @powercode!) +- Expose all cmdlets from `PSDiagnostics` if `logman.exe` is available (#8366) +- Fix `Get-Help` for advanced functions with MAML help content (#8353) +- Conditionally mark getter/setter implementations as virtual in generated classes (#8303) (Thanks @IISResetMe!) +- Fix for `PSDrive` creation with a UNC path with a trailing backslash or forward slash when combined with `-Persist` (#8305) (Thanks @kvprasoon!) +- Remove `Persist` parameter from `New-PSDrive` on non-Windows platform (#8291) (Thanks @lukexjeremy!) +- `Test-Path`: Return `$false` when given an empty or `$null` `-Path`/`-LiteralPath` value (#8080) (Thanks @vexx32!) +- Token calculation fix for `Get-Help` executed on `ScriptBlock` for comment help. (#8238) (Thanks @hubuk!) +- Support `Get-PSHostProcessInfo` and `Enter-PSHostProcess` on Unix platforms (#8232) + +### Code Cleanup + +- Update `resgen`, `typegen` to use .Net Core 2.1 (#8369) (Thanks @bergmeister!) +- Change `Newtonsoft` deserializing bug comment to link to the new issue (#8377) (Thanks @louistio!) +- Cleanup `#if !CORECLR` code (#8337) (Thanks @iSazonov!) +- Cleanup `UpdatableHelpSystem` and enable XSD validation on MAML help content (#8335) (Thanks @iSazonov!) +- Remove old `customPSSnapInType` parameter from `PSSnapInInfo()` (#8333) (Thanks @iSazonov!) +- Cleanup `#if CORECLR` from some files (#8332) (Thanks @iSazonov!) +- Cleanup `AssemblyInfo` (#8190) (Thanks @iSazonov!) +- Fix `GetLocationCommand` output type parameter set and style issues (#8324) (Thanks @Meir017!) + +### Tools + +- Remove `dependabot` attribution and generate changelog sections using `CL-*` labels (#8386) + +### Tests + +- Update folder path for storing optimization profile and add test to validate loaded assemblies and libraries on startup (#8406) +- Fix an intermittent failure in macOS logging tests (#8385) +- Created a `csproj` to pin test modules and updated `build.psm1` accordingly (#8350) +- Update help content for `TabCompletion` tests only if it does not exist (#8355) +- Skip `Enter-PSHostProcess` tests on `AppVeyor` due to `PSReadline` issue (#8317) + +### Build and Packaging Improvements + +- Remove `AmazonLinux` Dockerfile (#8271) (Thanks @kiazhi!) +- Make `install-powershell.sh` auto-detect if it should use `wget` or `curl` (#8225) (Thanks @DarwinJS!) +- Bump `NJsonSchema` from `9.12.2` to `9.13.1` (#8319) (#8328) (#8412) (#8371) (#8384) +- Bump `Microsoft.PowerShell.Native` from `6.2.0-preview.2` to `6.2.0-preview.3` (#8411) +- Update the name of the artifact to be unique per artifact (#8405) +- Create unified release build for macOS and Linux packages (#8399) +- Add Linux `ARM64` build support (#8016) (Thanks @slide!) +- Update the timeout of CI builds (#8398) +- Bump `PackageManagement` from `1.2.2` to `1.2.4` in `/src/Modules` (#8320) (#8383) +- Bump `Newtonsoft.Json` from `11.0.2` to `12.0.1` (#8348) +- Enable pipeline to sync `PSGallery` modules to `AzArtifacts` feed (#8316) +- Build Alpine `tar.gz` package in release builds (#8340) +- Publish test package to `AppVeyor` daily build (#8273) +- Bump `Microsoft.CodeAnalysis.CSharp` from `2.9.0` to `2.10.0` (#8294) +- Bump `PowerShellGet` from `2.0.1` to `2.0.3` in `/src/Modules` (#8321) +- Enable `Open Here` context menu on Windows to work with root of a drive (#8287) +- Bump `System.Data.SqlClient` from `4.5.1` to `4.6.0` (#8266) + +### Documentation and Help Content + +- Merge `changelogs` from `6.1.1` and `6.0.5` into master (#8283) +- Remove all reference to `AppVeyor` and `Travis CI` from docs (#8376) +- Change default issue template to use different categories (#8203) + +## [6.2.0-preview.2] - 2018-11-15 + +### Breaking Changes + +- Honor `-OutputFormat` if specified in non-interactive, redirected, encoded command used with `pwsh` (#8115) +- Load assembly from module base path before trying to load from the `GAC` (#8073) +- Remove tilde from Linux preview packages (#8244) +- Move processing of `-WorkingDirectory` before processing of profiles (#8079) + +### Known Issues + +- PowerShell WSMan remoting does not work on Debian 9 due to missing symbolic links. + For more information and a workaround see issue [#7598](https://github.com/PowerShell/PowerShell/issues/7598) + +### Engine Updates and Fixes + +- Enable case-insensitive tab completion for files and folders on case-sensitive filesystem (#8128) +- Experimental feature: Implicit remoting batching performance improvements (#8038) +- Add a path for checking `ZoneInformation` without throwing an exception (#8025) (Thanks @powercode!) +- Fix [CVE-2018-8256](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8256), + issues with expanding `ZIP` files with relative paths (#8252) +- Fix [CVE-2018-8415](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-8415), + issue logging when the `scriptblock` has a null character (#8253) +- Make `PSVersionInfo.PSVersion` and `PSVersionInfo.PSEdition` public (#8054) (Thanks @KirkMunro!) +- Enable distinct `ModuleAnalysisCache` files for each installation of `pwsh` (#8174) +- Consolidation of all Windows PowerShell work ported to PowerShell Core (#8257) +- Fix incorrect name check when auto-loading required modules (#8218) +- Adding verbose output for experimental implicit remoting batching feature (#8166) +- Add Type Inference for `$_ / $PSItem in catch{ }` blocks (#8020) (Thanks @vexx32!) +- Fix static method invocation type inference (#8018) (Thanks @SeeminglyScience!) + +### General Cmdlet Updates and Fixes + +- Reduce allocations in `Get-Content` cmdlet (#8103) (Thanks @iSazonov!) +- Enable `Set-Location -LiteralPath` to work with folders named `-` and `+` (#8089) +- Enable `Add-Content` to share read access with other tools while writing content (#8091) +- Add new `Offset` and `Count` parameters to `Format-Hex` and refactor the cmdlet (#7877) (Thanks @iSazonov!) +- Add `-Name`, `-NoUserOverrides` and `-ListAvailable` parameters to `Get-Culture` cmdlet (#7702) (Thanks @iSazonov!) +- Allow dynamic parameter to be returned even if path does not match any provider (#7957) +- Style fixes in `Format-Hex` (#8083) (Thanks @iSazonov!) +- Fix logic to rely on PowerShell major and minor version instead of build number to determine whether to output `formatdata` (#8063) +- Fix `Rename-Item -Path` with wildcard `char` (#7398) (Thanks @kwkam!) +- When using `Start-Transcript` and file exists, empty file rather than deleting (#8131) (Thanks @paalbra!) +- Error message enhancement for `Clear-Content` cmdlet when targeting a directory (#8134) (Thanks @kvprasoon!) +- Make `Select-String` faster by not doing extra work (#7673) (Thanks @powercode!) +- Remove `ShouldProcess` from `Format-Hex` (#8178) + +### Code Cleanup + +- Remove clone of command-line arguments array (#7910) (Thanks @iSazonov!) +- Use `DefaultPathSeparator` `char` instead of `DefaultPathSeparatorString` (#8082) (Thanks @iSazonov!) +- Replace `StringComparision.CurrentCulture` with `StringComparision.Ordinal` (#8068) (Thanks @iSazonov!) +- Fix typo in `-icontains` description from `incase sensitive` to `case insensitive` (#7840) (Thanks @StingyJack!) +- Refactor module version/`GUID` comparison logic (#7125) + +### Tools + +- Update `installpsh-amazonlinux.sh` for container specific issues (#7907) (Thanks @DarwinJS!) +- Update the `codeowners` file (#8017) + +### Tests + +- Filter the `TestPackage` artifact upload by name to avoid other `ZIP` files being uploaded (#8116) +- Adding `fxdependent` PowerShell package tests (#7830) +- Fix Windows Feature tests running in Azure DevOps (#8220) +- Create `$PROFILE` if it does not exist for `-WorkingDirectory` processing test (#8152) +- Add test coverage for additional `Get-Module` parameters (#8137) (Thanks @KevinMarquette!) +- Fix conflict with `Get-AdlStoreChildItem` from `az` module in tab completion tests (#8167) +- Fix static secret in code (#8186) + +### Build and Packaging Improvements + +- Bump `xunit.runner.visualstudio` from `2.4.0` to `2.4.1` (#8139) +- Bump `xunit` from `2.4.0` to `2.4.1` (#8140) +- Bump `Microsoft.ApplicationInsights` from `2.8.0` to `2.8.1` (#8104) +- Bump `NJsonSchema` from `9.11.1` to `9.12.1` (#8183, #8248) +- Fix `Start-PSBuild -Output` (#7504) (Thanks @kwkam!) +- Adding `YML` for Linux builds (#8168) +- Publish test package at `AGENT_WORKFOLDER` if `TEMP` is not available (#8108) +- Fix `psmodulerestore` path when built in Visual Studio Code (#8075) +- Use approved verb instead of `Generate-CrossGenAssembly` (#8151) (Thanks @kvprasoon!) +- Add path filters to CI `YAML` (#8222) +- Update `SignType` in `signing.xml` (#8223) +- Update metadata for `6.0.5` and `6.1.1` releases (#8259) +- Port changes to allow Azure DevOps NuGet feeds for Mac build (Internal 5818) +- Update version for dependencies (Internal 5822) +- Add code to use private NuGet feeds when running in internal CI system (#8187) +- Add title to `Open Here` window for `MSI` installer (#8164) +- Remove build and documentation references to `git` submodules (#8177) (Thanks @andschwa!) +- Add function to create a new `nuget.config` file (#8170) +- Update macOS release build to create the `nuget.config` (#8185) +- Workaround for accessing Azure Artifacts (#8188) +- Fix script path for `PowerShellPackageVsts.ps1` (#8189) +- `Microsoft.PowerShell.Native` now has `MUSL` binaries for Alpine. + +### Documentation and Help Content + +- Fix grammar in `README.md` (#8059) (Thanks @daviddreher2!) +- Update `powershell-beginners-guide.md` to add alias for `Clear-Host` (#7912) (Thanks @aavdberg!) +- Add Microsoft Docs link to FAQ (#8133) (Thanks @vongrippen!) +- Added updated photo of Visual Studio Code due to new version of Code (#8084) (Thanks @lassehastrup!) +- Update `license.rtf` to only have major version (#8127) +- Updated Pester Syntax in Writing Tests Guide (#8039) (Thanks @markwragg!) +- Remove duplicate parts from license file (#8143) (Thanks @azkarmoulana!) +- Fix spellings in `CHANGELOG.md` (#8062) +- Update license RTF to 6.2 (#8065) +- Combine notes about `ITuple` changes in Change Log (#8077) (Thanks @Jocapear!) +- Correct typos in `powershell-beginners-guide.md` (#8088) (Thanks @nycjan!) +- Added `Learn Windows PowerShell in a Month of Lunches` as recommended reading (#8067) (Thanks @tobvil!) +- Update `README.md` for `v6.1.1` (#8255) +- Fix some typos (#8206) (Thanks @jeis2497052!) +- Promote `HTTPS` (#8160) (Thanks @RDIL!) +- Simple grammatical correction in `README.md` file (#7978) (Thanks @iGotenz!) +- Update URLs to use `HTTPS` instead of `HTTP` in the documentation (#8165) (Thanks @RDIL!) +- Remove #7633 from `v6.2.0-preview.1` `CHANGELOG.md` updates. (#8101) (Thanks @stknohg!) + +## [6.2.0-preview.1] - 2018-10-18 + +### Breaking Changes + +- Do not add `PATHEXT` environment variable on Unix (#7697) (Thanks @iSazonov!) + +### Known Issues + +- Remoting on Windows IOT ARM platforms has an issue loading modules. See [#8053](https://github.com/PowerShell/PowerShell/issues/8053) + +### Engine Updates and Fixes + +- Add C# style type accelerators and suffixes for `ushort`, `uint`, `ulong`, and `short` literals (#7813) (Thanks @vexx32!) +- Create inferred types for `Select-Object`, `Group-Object`, `PSObject` and `Hashtable` (#7231) (Thanks @powercode!) +- Fix .NET adapter to be able to get members from `System.IntPtr` (#7808) +- Fix .NET adapter to not throw when fails to create a `PSMethod` due to `ByRef-like` type (#7788) +- Support calling method with `ByRef-like` type parameters (#7721) +- Fix perf issue in provider by using `Refresh()` to update the status rather than instantiating `ServiceController` which has a significant perf degradation from .NET Framework (#7680) +- Update PowerShell to handle the case where the Windows PowerShell module path is already in the environment's `PSModulePath` (#7727) +- Ensure the `SSHClientSessionTransportManager` stream writer and reader fields are cleared after dispose. (#7746) +- Add unified attribute for completion for `Encoding` parameter. (#7732) (Thanks @ThreeFive-O!) +- Add support for Byte Literals (#7901) (Thanks @vexx32!) +- Fix Property and `ScriptBlock` expressions in `EntrySelectedBy` tags within custom controls (#7913) (Thanks @SeeminglyScience!) +- Fix `BeginInvoke`/`EndInvoke` to return results when `Stop` or `BeginStop`/`EndStop` was called previously (#7917) +- Allow root node of `format.ps1xml` to have attributes that are ignored (#7987) +- Use non-virtual call to invoke 'family or assembly' methods on base class from PowerShell class (#7622) (#7624) (Thanks @yurko7!) +- Make the parameter to `ImportPSModule` use `params` so that it is easier to call (#7933) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Add `EscapeHandling` parameter in `ConvertTo-Json` cmdlet (#7775) (Thanks @iSazonov!) +- Make `Add-Type` open source files with `FileAccess.Read` and `FileShare.Read` explicitly (#7915) (Thanks @IISResetMe!) +- No longer skips a column without name if double quote delimiter is used in `Import-Csv` (#7899) (Thanks @Topping!) +- Add support for `cd +` (#7206) (Thanks @bergmeister!) +- Allow numeric Ids and name of registered code pages in `-Encoding` parameters (#7636) (Thanks @iSazonov!) +- Remove extra space in `LastWriteTime` format (#7810) (Thanks @iSazonov!) +- Fix `Enter-PSSession -ContainerId` for the latest Windows (#7883) +- `Get/Add-Content` throws improved error when targeting a container (#7823) (Thanks @kvprasoon!) +- Ensure `NestedModules` property gets populated by `Test-ModuleManifest` (#7859) +- Add `%F` case to `Get-Date -UFormat` (#7630) (Thanks @britishben!) +- Fix file blocking issue with web cmdlets (#7676) (Thanks @Claustn!) +- Improve error message on non-Windows when importing `clixml` with `securestring` (#7997) +- Add prompt to the use of less in the function 'help' to instruct user how to quit (#7998) +- Fix `Set-Service -Status Stopped` to stop services with dependencies (#5525) (Thanks @zhenggu!) + +### Code Cleanup + +- Use `nameof()` in bound `parameters.contains key()` (#7908) (Thanks @iSazonov!) +- Cleanup all native code from repository (#7892) +- Add `XSDs` for Format and `Types.ps1xml` files (#7832) (Thanks @felixfbecker!) +- Remove unused commented out code (#7935) (Thanks @vpondala!) +- Add `.editorconfig` (#7357) (Thanks @iSazonov!) +- Remove unused stopwatch (#7878) +- Clean up `MshObject.cs` and `MshMemberInfo.cs` (#7446) +- Add `TimeToLive` and `Hops` aliases to `MaxHops` parameter of `Test-Connection` cmdlet. (#7850) (Thanks @sethvs!) +- Fix a typo in `Credential.cs` (#7696) (Thanks @sethvs!) +- Remove workaround on VSTS that is no longer needed (#7666) +- Improve code style of `Send-MailMessage` cmdlet (#7723) (Thanks @ThreeFive-O!) +- Cleanup `FileSystemProvider` from runtime checks (#7655) (Thanks @iSazonov!) +- Remove extra spaces in error messages in `Modules.resx` (#7662) (Thanks @sethvs!) +- Remove empty XML comment lines (missed in #7401) (#7641) (Thanks @kvprasoon!) +- Remove `Suspend-Job` and `Resume-Job` cmdlets from compilation (#7650) + +### Tools + +- Fix syntax error in `installpwsh-amazonlinux.sh` (#7905) (Thanks @DarwinJS!) +- Add tools for PowerShell perf analysis (#7595) (Thanks @lzybkr!) +- Started using [Dependabot](https://dependabot.com) to create PRs to update package and module versions + +### Tests + +- Add test for `$error[0]` tab completion (#7924) (Thanks @iSazonov!) +- Replace test certificates with self-signed certificate generating command (#7875) +- Standardize Pester syntax in `ReplaceOperator.Tests.ps1` (#7963) (Thanks @sethvs!) +- Updating `ModulePath.Tests` for `fxdependent` package (#7772) +- Add tests for `Import-Module -Force` (#7491) +- Updates to Docker package tests (#7667) +- Updating test gallery URL in `PackageManagement` tests (#7879) +- Add version checking tests for `Import-Module` (#7499) +- Update Markdown tests (#7838) +- Change locale of `mdspell` to `en-US` (#7671) +- Test changes needed for running in a container (#7869) +- Add daily build non-windows platforms (#7683) +- Remove workaround on VSTS that is no longer needed (#7666) +- Fix module specification `hashtable` in `ModuleSpecification.Tests.ps1` (#7663) (Thanks @sethvs!) +- Use `dotnet test` since the `dotnet xunit` test runner has been deprecated (#7980) (Thanks @bergmeister!) +- Fix pipeline test where `SmtpServer` key was set wrong in pipeline object (#7745) (Thanks @ThreeFive-O!) +- Change API to get host name to match cmdlet which is more reliable in Azure DevOps Pipelines `Hosted Windows VS2017` (#8003) +- Disable `travis-ci` (#7766) +- Make artifact upload only occur for non-PR builds (#7657) +- Change logic for downloading zip files based on job id (#7653) +- Add missing dependency for hosting xUnit tests + +### Build and Packaging Improvements + +- Change default of `Start-PSBuild` to include `-PSModuleRestore` (#7881) +- Specify verb, `pwsh`, for shell context menu to avoid overriding the default verb (#7932) (Thanks @bergmeister!) +- Converting aliases to cmdlets in `build.psm1` (#7964) (Thanks @kvprasoon!) +- Add dependencies for SUSE (#7938) (Thanks @Jellyfrog!) +- Wait for package manager not to be locked (#7817) +- Make `Start-PSPackage` give better message about how to fix `files.wxs` (#7841) +- Bump to .NET Core `2.1.5` with SDK `2.1.403` and latest packages (#7646, #7834, #7922, #7936) (Thanks @iSazonov!) +- Bump `Markdig.Signed` NuGet package from `0.15.3` to `0.15.4` (#7960) (Thanks @bergmeister!) +- Bump `Microsoft.ApplicationInsights` from `2.7.2` to `2.8.0` (#8002) +- Bump `Microsoft.PowerShell.Native` from `6.1.0-rc.1` to `6.1.0` (#7861) +- Bump `NJsonSchema` from `9.10.71` to `9.11.1` (#7705, #7764, #7990) +- Bump `PackageManagement` from `1.1.7.2` to `1.2.2` in /src/Modules (#8014, #8029) +- Bump `Pester` to use latest version (#8015) +- Bump `PowerShellGet` to `2.0.0` (#7831) +- Bump `PSReadLine` to `2.0.0-beta3` (#7711) +- Bump `Xunit.SkippableFact` from `1.3.6` to `1.3.12` (#7972) +- Make Windows MSI uninstallation shortcut remove work when more than one version is installed (#7701) (Thanks @bergmeister!) +- Update Docker files to use MCR (#7726) +- Update `metadata.json` in preparation for `6.1` release (#7741) +- Build and package framework dependent package (#7729) +- Make sure MSI build works when not preview (#7752) +- Remove `PDBs` from `fxdependent` package (#8006) +- Improve debugging of NuGet package generation and add type to filtering + +### Documentation and Help Content + +- 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!) +- Optimize image assets used in documentation (#7874) (Thanks @RDIL!) +- Update build badges (#7792) +- Remove packaging, building and installation scripts for Ubuntu 17.10 (#7773) +- Remove badges for master build as it reflects last PR which is not actionable from the `README` file (#7816) +- Improve Markdown formatting of beginners guide (#7684) (Thanks @fbehrens!) +- Fix the `Basic cookbooks` link (#7934) (Thanks @vmsilvamolina!) +- Update version for PowerShell release `6.1.0` (#7751) +- Add VSTS CI build badges for master branch to `README.md` (#7691) (Thanks @bergmeister!) +- Add a paragraph on `files.wxs` updating (#7695) (Thanks @iSazonov!) +- Update `CONTRIBUTION.md` about adding an empty line after the copyright header (#7706) (Thanks @iSazonov!) +- Update docs about .NET Core version `2.0` to be about version `2.x` (#7467) (Thanks @bergmeister!) + +[6.2.7]: https://github.com/PowerShell/PowerShell/compare/v6.2.6...v6.2.7 +[6.2.6]: https://github.com/PowerShell/PowerShell/compare/v6.2.5...v6.2.6 +[6.2.5]: https://github.com/PowerShell/PowerShell/compare/v6.2.4...v6.2.5 +[6.2.4]: https://github.com/PowerShell/PowerShell/compare/v6.2.3...v6.2.4 +[6.2.3]: https://github.com/PowerShell/PowerShell/compare/v6.2.2...v6.2.3 +[6.2.2]: https://github.com/PowerShell/PowerShell/compare/v6.2.1...v6.2.2 +[6.2.1]: https://github.com/PowerShell/PowerShell/compare/v6.2.0...v6.2.1 +[6.2.0]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-rc.1...v6.2.0 +[6.2.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-rc.1...v6.2.0 +[6.2.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-preview.3...v6.2.0-preview.4 +[6.2.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-preview.2...v6.2.0-preview.3 +[6.2.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-preview.1...v6.2.0-preview.2 +[6.2.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v6.1.0...v6.2.0-preview.1 diff --git a/CHANGELOG/7.0.md b/CHANGELOG/7.0.md new file mode 100644 index 00000000000..e054b34cfc9 --- /dev/null +++ b/CHANGELOG/7.0.md @@ -0,0 +1,1421 @@ +# 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 + +- Remove dependency on DNS for `Test-Connection` tests on macOS (#12943) + +### Build and Packaging Improvements + +
+ +
    +
  • Fix Azure file copy issues in release build by fixing the path to upload directory content (#13182)
  • +
  • Update .NET Core to 3.1.6 (Internal 12005)
  • +
  • Fix Azure file copy break in AzDevOps by updating task version to latest (#13173)
  • +
+ +
+ +[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 + +- Ensure null-coalescing LHS is evaluated only once (#12667) +- Restrict loading of `amsi.dll` to `system32` folder (#12730) + +### General Cmdlet Updates and Fixes + +- Change `Get-FileHash` to close file handles before writing output (#12474) (Thanks @iSazonov!) + +### Tools + +- Update the PowerShell team list to correct changelog generation (#12927) + +### Tests + +- Pin major Pester version to 4 to prevent breaking changes caused by upcoming release of `v5` (#12262) (Thanks @bergmeister!) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET Core 3.1.5

+ +
+ +
    +
  • Bump to .NET 3.1.5 and update dependencies (Internal 11699)
  • +
  • Check if Azure Blob exists before overwriting (#12921)
  • +
  • Upgrade APIScan version (#12876)
  • +
  • Fix break in package build by pinning ffi version to 1.12 (#12889)
  • +
  • Update the build to sign any unsigned files as 3rd party Dlls (#12581)
  • +
+ +
+ +## [7.0.1] - 2020-05-14 + +### Engine Updates and Fixes + +- Discover assemblies loaded by `Assembly.Load(byte[])` and `Assembly.LoadFile` (#12203) +- Allow case insensitive paths for determining `PSModulePath` (#12192) + +### General Cmdlet Updates and Fixes + +- Add `null` check for Windows PowerShell install path (#12296) +- Fix Null Reference error in CSV commands (#12281) (Thanks @iSazonov!) +- Fix `WinCompat` module loading to treat Core edition modules higher priority (#12269) +- Fix `` detection regex in web cmdlets (#12099) (Thanks @vexx32!) +- Miscellaneous minor updates to `WinCompat` (#11980) +- Fix `ConciseView` where error message is wider than window width and doesn't have whitespace (#11880, #11746) +- Make `Test-Connection` always use the default synchronization context for sending ping requests (#11517) + +### Tests + +- Fix CIM tab complete test failure (#12636) + +### Build and Packaging Improvements + +
+ + +Move to .NET Core 3.1.202 SDK and update packages. + + +
    +
  • Use dotnet core 3.1.202 (Internal 11551)
  • +
  • Bump PowerShellGet from 2.2.3 to 2.2.4 (#12342)
  • +
  • Move to standard internal pool for building (#12119)
  • +
  • Bump NJsonSchema from 10.1.5 to 10.1.7 (#12050)
  • +
+ +
+ +### Documentation and Help Content + +- Remove the version number of PowerShell from `LICENSE` (#12019) + +## [7.0.0] - 2020-03-04 + +### General Cmdlet Updates and Fixes + +- Enable `Ctrl+C` to work for global tool (#11959) +- Fix `ConciseView` to not show the line information within the error messages (#11952) + +### Build and Packaging Improvements + +- Publish PowerShell into the Windows engineering system package format (#11960) +- Bump .NET core framework to `3.1.2` (#11963) +- Ensure the man page `gzip` has the correct name for LTS release (#11956) +- Bump `Microsoft.ApplicationInsights` from `2.13.0` to `2.13.1` (#11925) + +## [7.0.0-rc.3] - 2020-02-21 + +### Breaking Changes + +- Fix `Invoke-Command` missing error on session termination (#11586) + +### Engine Updates and Fixes + +- Update the map between console color to `VT` sequences (#11891) +- Fix SSH remoting error on Windows platform (#11907) +- Restore the `PowerShellStreamType` `enum` with an `ObsoleteAttribute` (#11836) +- Handle cases where `CustomEvent` was not initially sent (#11807) +- Fix how COM objects are enumerated (#11795) +- Fix `NativeDllHandler` to not throw when file is not found (#11787) +- Restore `SetBreakpoints` API (#11622) +- Do not needlessly pass `-l login_name` or `-p port` to `ssh` (#11518) (Thanks @LucaFilipozzi!) +- Fix for `JEA` user role in virtual account (#11668) +- Do not resolve types from assemblies that are loaded in separate `AssemblyLoadContext` (#11088) + +### General Cmdlet Updates and Fixes + +- Sync current directory in `WinCompat` remote session (#11809) +- Add `WinCompat` deny list support using a setting in `powershell.config.json` (#11726) +- Fix unnecessary trimming of line resulting in incorrect index with `ConciseView` (#11670) + +### Code Cleanup + +- Change name of `ClrVersion` parameter back to revert change in capitalization (#11623) + +### Tools + +- Update changelog generation script (#11736) (Thanks @xtqqczze!) +- Update to `CredScan v2` (#11765) + +### Tests + +- Make sure to test whether we skip a test using consistent logic (#11892) +- Skip directory creation at root test on macOS (#11878) +- Update `Get-PlatformInfo` helper and tests for Debian 10, 11 and CentOS 8 (#11842) +- Ensure correct `pwsh` is used for test runs (#11486) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +- Add `LTSRelease` value from `metadata.json` to `release.json` (#11897) +- Bump `Microsoft.ApplicationInsights` from `2.12.1` to `2.13.0` (#11894) +- Make LTS package always not a preview (#11895) +- Bump `System.Data.SqlClient` from `4.8.0` to `4.8.1` (#11879) +- Change `LTSRelease` value in `metadata.json` to true for `RC.3` release (Internal 10960) +- Update `LTS` logic to depend on `metadata.json` (#11877) +- Set default value of `LTSRelease` to false (#11874) +- Refactor packaging pipeline (#11852) +- Make sure `LTS` packages have symbolic links for `pwsh` and `pwsh-lts` (#11843) +- Bump `Microsoft.PowerShell.Native` from `7.0.0-rc.2` to `7.0.0` (#11839) +- Update the NuGet package generation to include `cimcmdlet.dll` and most of the built-in modules (#11832) +- Bump `Microsoft.PowerShell.Archive` from `1.2.4.0` to `1.2.5` (#11833) +- Bump `PSReadLine` from `2.0.0-rc2` to `2.0.0` (#11831) +- Add trace source and serialization primitives to the allowed assembly list (Internal 10911) +- Update the `NextReleaseTag` to be v7.0.0-preview.7 (#11372) +- Change packaging to produce `LTS` packages (#11772) +- Build tar packages only when building on Ubuntu (#11766) +- Bump `NJsonSchema` from `10.1.4` to `10.1.5` (#11730) +- Fix symbolic link creation in `packaging.psm1` (#11723) +- Bump `Microsoft.ApplicationInsights` from `2.12.0` to `2.12.1` (#11708) +- Bump `NJsonSchema` from `10.1.3` to `10.1.4` (#11620) +- Move to latest Azure DevOps agent images (#11704) +- Bump `Markdig.Signed` from `0.18.0` to `0.18.1` (#11641) + +### Documentation and Help Content + +- Add links to diffs on Github in changelog (#11652) (Thanks @xtqqczze!) +- Fix markdown-link test failure (#11653) (Thanks @xtqqczze!) + +## [7.0.0-rc.2] - 2020-01-16 + +### Breaking Changes +- Use `ISOWeek` for week numbers in `Get-Date` accounting for leap years #11536 (Thanks @paalbra!) + +### Engine Updates and Fixes +- Revert the PRs that made `DBNull.Value` and `NullString.Value` treated as `$null` (#11584) +- Support expanding `~` in `$env:PATH` when doing command discovery (#11552) +- 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 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 +- Fix `NullReferenceException` in `ConciseView` (#11435) (Thanks @iSazonov!) +- Remove the default value of `$true` for the parameter `-RequireLicenseAcceptance` in `New-ModuleManifest` (#11512) (Thanks @ThomasNieto!) +- Make Web Cmdlets skip processing the content headers with a null or empty value for backward compatibility (#11421) (Thanks @spongemike2!) +- Don't format exceptions that are not `ErrorRecord` objects (#11415) +- Mark `InitialSessionState.ImportPSSnapIn` as Obsolete (#11399) +- Use `PositionMessage` for the line context information for `ConciseView` (#11398) +- Add trailing line number to `filename` for `ConciseView` (#11391) +- Update `HelpInfoUri` for all modules in PowerShell 7.0 (#11389) +- Remove unnecessary newline in `ConciseView` (#11383) +- Move `Set-StrictMode` to the outer script block for `ErrorView` (#11381) +- Remove the declaration of `Get-Error` experimental feature from module manifest (#11369) +- Update error message if `Update-Help` fails for the current `UICulture` (#11356) +- `Test-Connection`: Fallback to hop IP Address on `-Traceroute` without `-ResolveDestination` (#11335) (Thanks @vexx32!) +- Add null host name check in `WSMan` (#11288) (Thanks @iSazonov!) +- Add `Type` member to exceptions containing type of exception for `Get-Error` (#11076) +- Write an error if argument is a directory in `Get-FileHash` cmdlet (#11114) (Thanks @iSazonov!) +- Update `Get-Error` to not modify the original `$Error` object (#11125) + +### Code Cleanup +- Use .NET code to check for processor architecture instead of P/Invoke (#11046) (Thanks @iSazonov!) + +### Tests +- Test fixes for various platforms (#11579, #11541) +- Various test fixes for debugger and remoting (#11528) +- `DSC` test fixes for `Alpine` and `Raspbian` (#11508) +- Normalize line endings before comparing string in tests (#11499) +- Fix `ssh` remoting test to work on all platforms (#11500) +- Build test artifacts for `Alpine` (#11483) +- Make null member access tests as string to avoid parsing errors (#11385) +- Fix test failing when `UnixStat` feature is disabled (#11370) +- Update hosting tests to use the SDK version from the build property (#11368) +- Add retry to `Enter-PSHostProcess` test (#11360) + +### Build and Packaging Improvements +- Bump `Microsoft.PowerShell.Native` from `7.0.0-rc.1` to `7.0.0.rc.2` (#11583) +- Update .NET SDK version to 3.1.101 (#11582) +- Bump `PSReadLine` from `2.0.0-rc1` to `2.0.0-rc2` (#11581) +- Bump `NJsonSchema` from `10.0.28` to `10.1.3` (#11382, #11573) +- Generate the correct reference assembly for `Microsoft.PowerShell.ConsoleHost` NuGet package (#11545) +- Update building of `MSIX` for `RC` to use 100 range revision (#11526) +- Fix symbolic links on Debian 10 packages (#11474) +- Bump `Microsoft.PowerShell.Archive` from `1.2.3.0` to `1.2.4.0` (#11502) +- Add script to rebuild `WIX` component references (#11485) +- Bump `PackageManagement` from `1.4.5` to `1.4.6` (#11427) +- Bump `PowerShellGet` from `2.2.2` to `2.2.3` (#11426) +- Bump `ThreadJob` from `2.0.2` to `2.0.3` (#11416) +- Fix symbolic links to `libs` on Debian 10 (#11390) +- Improve Ubuntu detection for Ubuntu derivatives like `GalliumOS` etc (#11155) + +### Documentation and Help Content +- Fix broken link in debugging `README.md` (#11503) + +## [7.0.0-rc.1] - 2019-12-16 + +### Breaking Changes +- Make update notification support `LTS` and default channels (#11132) + +### Engine Updates and Fixes +- Improvements in breakpoint APIs for remote scenarios (#11312) +- Fix PowerShell class definition leaking into another Runspace (#11273) +- Fix a regression in formatting caused by the `FirstOrDefault` primitive added in `7.0.0-Preview1` (#11258) +- Additional Microsoft Modules to track in `PS7` Telemetry (#10751) +- Make approved features non-experimental (#11303) +- Update `ConciseView` to use `TargetObject` if applicable (#11075) +- Fix `NullReferenceException` in `CompletionCompleters` public methods (#11274) +- Fix apartment thread state check on non-Windows platforms (#11301) +- Update setting `PSModulePath` to concatenate the process and machine environment variables (#11276) +- Bump `.NET Core` to `3.1.0` (#11260) +- Fix detection of `$PSHOME` in front of `$env:PATH` (#11141) + +### General Cmdlet Updates and Fixes +- Fix for issue on Raspbian for setting date of file changes in `UnixStat` Experimental Feature (#11313) +- Add `-AsPlainText` to `ConvertFrom-SecureString` (#11142) +- Added `WindowsPS` version check for `WinCompat` (#11148) +- Fix error-reporting in some `WinCompat` scenarios (#11259) +- Add native binary resolver (#11032) (Thanks @iSazonov!) +- Update calculation of char width to respect `CJK` chars correctly (#11262) +- Add `Unblock-File` for macOS (#11137) +- Fix regression in `Get-PSCallStack` (#11210) (Thanks @iSazonov!) +- Avoid automatically loading the `ScheduledJob` module when using Job cmdlets (#11194) +- Add `OutputType` to `Get-Error` cmdlet and preserve original `TypeNames` (#10856) +- Fix null reference in `SupportsVirtualTerminal` property (#11105) + +### Code Cleanup +- Change comment and element text to meet Microsoft standards (#11304) + +### Tests +- Make unreliable `DSC` test pending (#11131) + +### Build and Packaging Improvements +- Fix Nuget package signing for Coordinated Package build (#11316) +- Update dependencies from PowerShell Gallery and NuGet (#11323) +- Bump `Microsoft.ApplicationInsights` from `2.11.0` to `2.12.0` (#11305) +- Bump `Microsoft.CodeAnalysis.CSharp` from `3.3.1` to `3.4.0` (#11265) +- Updates packages for Debian 10 and 11 (#11236) +- Only enable experimental features prior to `RC` (#11162) +- Update macOS minimum version (#11163) +- Bump `NJsonSchema` from `10.0.27` to `10.0.28` (#11170) + +### Documentation and Help Content +- Refactor change logs into one log per release (#11165) +- Fix `FWLinks` for PowerShell 7 online help documents (#11071) + +## [7.0.0-preview.6] - 2019-11-21 + +### Breaking Changes + +- Update `Test-Connection` to work more like the one in Windows PowerShell (#10697) (Thanks @vexx32!) +- Preserve `$?` for `ParenExpression`, `SubExpression` and `ArrayExpression` (#11040) +- Set working directory to current directory in `Start-Job` (#10920) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Allow `pwsh` to inherit `$env:PSModulePath` and enable `powershell.exe` to start correctly (#11057) + +### Experimental Features + +- Provide Unix stat information in filesystem output (#11042) +- Support null-conditional operators `?.` and `?[]` in PowerShell language (#10960) +- Support using non-compatible Windows PowerShell modules in PowerShell Core (#10973) + +### Performance + +- Avoid using closure in `Parser.SaveError` (#11006) +- Improve the caching when creating new `Regex` instances (#10657) (Thanks @iSazonov!) +- Improve processing of the PowerShell built-in type data from `types.ps1xml`, `typesV3.ps1xml` and `GetEvent.types.ps1xml` (#10898) +- Update `PSConfiguration.ReadValueFromFile` to make it faster and more memory efficient (#10839) + +### General Cmdlet Updates and Fixes + +- Add limit check in `Get-WinEvent` (#10648) (Thanks @iSazonov!) +- Fix command runtime so `StopUpstreamCommandsException` doesn't get populated in `-ErrorVariable` (#10840) +- Set the output encoding to `[Console]::OutputEncoding` for native commands (#10824) +- Support multi-line code blocks in examples (#10776) (Thanks @Greg-Smulko!) +- Add Culture parameter to `Select-String` cmdlet (#10943) (Thanks @iSazonov!) +- Fix `Start-Job` working directory path with trailing backslash (#11041) +- `ConvertFrom-Json`: Unwrap collections by default (#10861) (Thanks @danstur!) +- Use case-sensitive Hashtable for `Group-Object` cmdlet with `-CaseSensitive` and `-AsHashtable` switches (#11030) (Thanks @vexx32!) +- Handle exception if enumerating files fails when rebuilding path to have correct casing (#11014) +- Fix `ConciseView` to show `Activity` instead of `myCommand` (#11007) +- Allow web cmdlets to ignore HTTP error statuses (#10466) (Thanks @vdamewood!) +- Fix piping of more than one `CommandInfo` to `Get-Command` (#10929) +- Add back `Get-Counter` cmdlet for Windows (#10933) +- Make `ConvertTo-Json` treat `[AutomationNull]::Value` and `[NullString]::Value` as `$null` (#10957) +- Remove brackets from `ipv6` address for SSH remoting (#10968) +- Fix crash if command sent to pwsh is just whitespace (#10977) +- Added cross-platform `Get-Clipboard` and `Set-Clipboard` (#10340) +- Fix setting original path of filesystem object to not have extra trailing slash (#10959) +- Support `$null` for `ConvertTo-Json` (#10947) +- Add back `Out-Printer` command on Windows (#10906) +- Fix `Start-Job -WorkingDirectory` with whitespace (#10951) +- Return default value when getting `null` for a setting in `PSConfiguration.cs` (#10963) (Thanks @iSazonov!) +- Handle IO exception as non-terminating (#10950) +- Add `GraphicalHost` assembly to enable `Out-GridView`, `Show-Command`, and `Get-Help -ShowWindow` (#10899) +- Take `ComputerName` via pipeline in `Get-HotFix` (#10852) (Thanks @kvprasoon!) +- Fix tab completion for parameters so that it shows common parameters as available (#10850) +- Fix `GetCorrectCasedPath()` to first check if any system file entries is returned before calling `First()` (#10930) +- Set working directory to current directory in `Start-Job` (#10920) (Thanks @iSazonov!) +- Change `TabExpansion2` to not require `-CursorColumn` and treat as `$InputScript.Length` (#10849) +- Handle case where Host may not return Rows or Columns of screen (#10938) +- Fix use of accent colors for hosts that don't support them (#10937) +- Add back `Update-List` command (#10922) +- Update `FWLink` Id for `Clear-RecycleBin` (#10925) +- During tab completion, skip file if can't read file attributes (#10910) +- Add back `Clear-RecycleBin` for Windows (#10909) +- Add `$env:__SuppressAnsiEscapeSequences` to control whether to have VT escape sequence in output (#10814) + +### Code Cleanup + +- Cleanup style issues in `Compiler.cs` (#10368) (Thanks @iSazonov!) +- Remove the unused type converter for `CommaDelimitedStringCollection` (#11000) (Thanks @iSazonov!) +- Cleanup style in `InitialSessionState.cs` (#10865) (Thanks @iSazonov!) +- Code clean up for `PSSession` class (#11001) +- Remove the not-working 'run `Update-Help` from `Get-Help` when `Get-Help` runs for the first time' feature (#10974) +- Fix style issues (#10998) (Thanks @iSazonov!) +- Cleanup: use the built-in type alias (#10882) (Thanks @iSazonov!) +- Remove the unused setting key `ConsolePrompting` and avoid unnecessary string creation when querying `ExecutionPolicy` setting (#10985) +- Disable update notification check for daily builds (#10903) (Thanks @bergmeister!) +- Reinstate debugging API lost in #10338 (#10808) + +### Tools + +- Add default setting for the `SDKToUse` property so that it builds in VS (#11085) +- `Install-Powershell.ps1`: Add parameter to use MSI installation (#10921) (Thanks @MJECloud!) +- Add basic examples for `install-powershell.ps1` (#10914) (Thanks @kilasuit!) + +### Tests + +- Fix `stringdata` test to correctly validate keys of hashtables (#10810) +- Unload test modules (#11061) (Thanks @iSazonov!) +- Increase time between retries of testing URL (#11015) +- Update tests to accurately describe test actions. (#10928) (Thanks @romero126!) + +### Build and Packaging Improvements + +- Updating links in `README.md` and `metadata.json` for Preview.5 (#10854) +- Select the files for compliance tests which are owned by PowerShell (#10837) +- Allow `win7x86` `msix` package to build. (Internal 10515) +- Allow semantic versions to be passed to `NormalizeVersion` function (#11087) +- Bump .NET core framework to `3.1-preview.3` (#11079) +- Bump `PSReadLine` from `2.0.0-beta5` to `2.0.0-beta6` in /src/Modules (#11078) +- Bump `Newtonsoft.Json` from `12.0.2` to `12.0.3` (#11037) (#11038) +- Add Debian 10, 11 and CentOS 8 packages (#11028) +- Upload `Build-Info` Json file with the `ReleaseDate` field (#10986) +- Bump .NET core framework to `3.1-preview.2` (#10993) +- Enable build of x86 MSIX package (#10934) +- Update the dotnet SDK install script URL in `build.psm1` (#10927) +- Bump `Markdig.Signed` from `0.17.1` to `0.18.0` (#10887) +- Bump `ThreadJob` from `2.0.1` to `2.0.2` (#10886) +- Update `AppX` Manifest and Packaging module to conform to MS Store requirements (#10878) + +### Documentation and Help Content + +- Update `CONTRIBUTING.md` (#11096) (Thanks @mklement0!) +- Fix installation doc links in `README.md` (#11083) +- Adds examples to `install-powershell.ps1` script (#11024) (Thanks @kilasuit!) +- Fix to `Select-String` emphasis and `Import-DscResource` in CHANGELOG.md (#10890) +- Remove the stale link from `powershell-beginners-guide.md` (#10926) + +## [7.0.0-preview.5] - 2019-10-23 + +### Breaking Changes + +- Make `$PSCulture` consistently reflect in-session culture changes (#10138) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Move to `.NET Core 3.1 preview 1` (#10798) +- Refactor reparse tag checks in file system provider (#10431) (Thanks @iSazonov!) +- Replace `CR` and new line with a `0x23CE` character in script logging (#10616) +- Fix a resource leak by unregistering the event handler from `AppDomain.CurrentDomain.ProcessExit` (#10626) + +### Experimental Features + +- Implement `Get-Error` cmdlet as Experimental Feature (#10727,#10800) +- Add `ConciseView` for `$ErrorView` and update it to remove unnecessary text and not color entire line in red (#10641,#10724) +- Support the pipeline chain operators `&&` and `||` in PowerShell language (#9849,#10825,#10836) +- Implement null coalescing (`??`) and null coalescing assignment (`??=`) operators (#10636) +- Support notification on `pwsh` startup when a new release is available and update notification message (#10689,#10777) + +### General Cmdlet Updates and Fixes + +- Add emphasis to `Select-String` output (with `-NoEmphasis` parameter to opt-out) (#8963) (Thanks @derek-xia!) +- Add back `Get-HotFix` cmdlet (#10740) +- Make `Add-Type` usable in applications that host `PowerShell` (#10587) +- Use more effective evaluation order in `LanguagePrimitives.IsNullLike()` (#10781) (Thanks @vexx32!) +- Improve handling of mixed-collection piped input and piped streams of input in `Format-Hex` (#8674) (Thanks @vexx32!) +- Use type conversion in `SSHConnection` hashtables when value doesn't match expected type (#10720) (Thanks @SeeminglyScience!) +- Fix `Get-Content -ReadCount 0` behavior when `-TotalCount` is set (#10749) (Thanks @eugenesmlv!) +- Reword access denied error message in `Get-WinEvent` (#10639) (Thanks @iSazonov!) +- Enable tab completion for variable assignment that is enum or type constrained (#10646) +- Remove unused `SourceLength` remoting property causing formatting issues (#10765) +- Add `-Delimiter` parameter to `ConvertFrom-StringData` (#10665) (Thanks @steviecoaster!) +- Add positional parameter for `ScriptBlock` when using `Invoke-Command` with `SSH` (#10721) (Thanks @machgo!) +- Show line context information if multiple lines but no script name for `ConciseView` (#10746) +- Add support for `\\wsl$\` paths to file system provider (#10674) +- Add the missing token text for `TokenKind.QuestionMark` in parser (#10706) +- Set current working directory of each `ForEach-Object -Parallel` running script to the same location as the calling script. (#10672) +- Replace `api-ms-win-core-file-l1-2-2.dll` with `Kernell32.dll` for `FindFirstStreamW` and `FindNextStreamW` APIs (#10680) (Thanks @iSazonov!) +- Tweak help formatting script to be more `StrictMode` tolerant (#10563) +- Add `-SecurityDescriptorSDDL` parameter to `New-Service` (#10483) (Thanks @kvprasoon!) +- Remove informational output, consolidate ping usage in `Test-Connection` (#10478) (Thanks @vexx32!) +- Read special reparse points without accessing them (#10662) (Thanks @iSazonov!) +- Direct `Clear-Host` output to terminal (#10681) (Thanks @iSazonov!) +- Add back newline for grouping with `Format-Table` and `-Property` (#10653) +- Remove [ValidateNotNullOrEmpty] from `-InputObject` on `Get-Random` to allow empty string (#10644) +- Make suggestion system string distance algorithm case-insensitive (#10549) (Thanks @iSazonov!) +- Fix null reference exception in `ForEach-Object -Parallel` input processing (#10577) + +### Code Cleanup + +- Remove `WorkflowJobSourceAdapter` reference that is no longer used (#10326) (Thanks @KirkMunro!) +- Cleanup `COM` interfaces in jump list code by fixing `PreserveSig` attributes (#9899) (Thanks @weltkante!) +- Add a comment describing why `-ia` is not the alias for `-InformationAction` common parameter (#10703) (Thanks @KirkMunro!) +- Rename `InvokeCommandCmdlet.cs` to `InvokeExpressionCommand.cs` (#10659) (Thanks @kilasuit!) +- Add minor code cleanups related to update notifications (#10698) +- Remove deprecated workflow logic from the remoting setup scripts (#10320) (Thanks @KirkMunro!) +- Update help format to use proper case (#10678) (Thanks @tnieto88!) +- Clean up `CodeFactor` style issues coming in commits for the last month (#10591) (Thanks @iSazonov!) +- Fix typo in description of `PSTernaryOperator` experimental feature (#10586) (Thanks @bergmeister!) + +### Performance + +- Add minor performance improvements for runspace initialization (#10569) (Thanks @iSazonov!) + +### Tools + +- Make `Install-PowerShellRemoting.ps1` handle empty string in `PowerShellHome` parameter (#10526) (Thanks @Orca88!) +- Switch from `/etc/lsb-release` to `/etc/os-release` in `install-powershell.sh` (#10773) (Thanks @Himura2la!) +- Check `pwsh.exe` and `pwsh` in daily version on Windows (#10738) (Thanks @centreboard!) +- Remove unneeded tap in `installpsh-osx.sh` (#10752) + +### Tests + +- Temporary skip the flaky test `TestAppDomainProcessExitEvenHandlerNotLeaking` (#10827) +- Make the event handler leaking test stable (#10790) +- Sync capitalization in `CI` `YAML` (#10767) (Thanks @RDIL!) +- Add test for the event handler leaking fix (#10768) +- Add `Get-ChildItem` test (#10507) (Thanks @iSazonov!) +- Replace ambiguous language for tests from `switch` to `parameter` for accuracy (#10666) (Thanks @romero126!) + +### Build and Packaging Improvements + +- Update package reference for `PowerShell SDK` to `preview.5` (Internal 10295) +- Update `ThirdPartyNotices.txt` (#10834) +- Bump `Microsoft.PowerShell.Native` to `7.0.0-preview.3` (#10826) +- Bump `Microsoft.ApplicationInsights` from `2.10.0` to `2.11.0` (#10608) +- Bump `NJsonSchema` from `10.0.24` to `10.0.27` (#10756) +- Add `MacPorts` support to the build system (#10736) (Thanks @Lucius-Q-User!) +- Bump `PackageManagement` from `1.4.4` to `1.4.5` (#10728) +- Bump `NJsonSchema` from `10.0.23` to `10.0.24` (#10635) +- Add environment variable to differentiate client/server telemetry in `MSI` (#10612) +- Bump `PSDesiredStateConfiguration` from `2.0.3` to `2.0.4` (#10603) +- Bump `Microsoft.CodeAnalysis.CSharp` from `3.2.1` to `3.3.1` (#10607) +- Update to `.Net Core 3.0 RTM` (#10604) (Thanks @bergmeister!) +- Update `MSIX` packaging so the version to `Windows Store` requirements (#10588) + +### Documentation and Help Content + +- Merge stable and servicing change logs (#10527) +- Update used `.NET` version in build docs (#10775) (Thanks @Greg-Smulko!) +- Replace links from `MSDN` to `docs.microsoft.com` in `powershell-beginners-guide.md` (#10778) (Thanks @iSazonov!) +- Fix broken `DSC` overview link (#10702) +- Update `Support_Question.md` to link to `Stack Overflow` as another community resource (#10638) (Thanks @mklement0!) +- Add processor architecture to distribution request template (#10661) +- Add new PowerShell MoL book to learning PowerShell docs (#10602) + +## [7.0.0-preview.4] - 2019-09-19 + +### Engine Updates and Fixes + +- Add support to `ActionPreference.Break` to break into debugger when `Debug`, `Error`, `Information`, `Progress`, `Verbose` or `Warning` messages are generated (#8205) (Thanks @KirkMunro!) +- Enable starting control panel add-ins within PowerShell Core without specifying `.CPL` extension. (#9828) + +### Performance + +- Make `ForEach-Object` faster for its commonly used scenarios (#10454) and fix `ForEach-Object -Parallel` performance problem with many runspaces (#10455) + +### Experimental Features + +- Update `PSDesiredStateConfiguration` module version to `2.0.3` and bring new tests; enable compilation to MOF on non-Windows and use of Invoke-DSCResource without LCM (#10516) +- Add APIs for breakpoint management in runspaces and enable attach to process without `BreakAll` for PowerShell Editor Services (#10338) (Thanks @KirkMunro!) +- Support [ternary operator](https://github.com/PowerShell/PowerShell-RFC/pull/218) in PowerShell language (#10367) + +### General Cmdlet Updates and Fixes + +- Add PowerShell Core group policy definitions (#10468) +- Update console host to support `XTPUSHSGR`/`XTPOPSGR` VT control sequences that are used in [composability scenarios](https://github.com/microsoft/terminal/issues/1796). (#10208) +- Add `WorkingDirectory` parameter to `Start-Job` (#10324) (Thanks @davinci26!) +- Remove the event handler that was causing breakpoint changes to be erroneously replicated to the host runspace debugger (#10503) (Thanks @KirkMunro!) +- Replace `api-ms-win-core-job-12-1-0.dll` with `Kernell32.dll` in `Microsoft.PowerShell.Commands.NativeMethods` P/Invoke API(#10417) (Thanks @iSazonov!) +- Fix wrong output for `New-Service` in variable assignment and `-OutVariable` (#10444) (Thanks @kvprasoon!) +- Fix global tool issues around exit code, command line parameters and path with spaces (#10461) +- Fix recursion into OneDrive - change `FindFirstFileEx()` to use `SafeFindHandle` type (#10405) +- Skip auto-loading `PSReadLine` on Windows if the [NVDA screen reader](https://www.nvaccess.org/about-nvda/) is active (#10385) +- Increase built-with-PowerShell module versions to `7.0.0.0` (#10356) +- Add throwing an error in `Add-Type` if a type with the same name already exists (#9609) (Thanks @iSazonov!) + +### Code Cleanup + +- Convert `ActionPreference.Suspend` enumeration value into a non-supported, reserved state, and remove restriction on using `ActionPreference.Ignore` in preference variables (#10317) (Thanks @KirkMunro!) +- Replace `ArrayList` with `List` to get more readable and reliable code without changing functionality (#10333) (Thanks @iSazonov!) +- Make code style fixes to `TestConnectionCommand` (#10439) (Thanks @vexx32!) +- Cleanup `AutomationEngine` and remove extra `SetSessionStateDrive` method call (#10416) (Thanks @iSazonov!) +- Rename default `ParameterSetName` back to `Delimiter` for `ConvertTo-Csv` and `ConvertFrom-Csv` (#10425) + +### Tools + +- Update `install-powershell.ps1` to check for already installed daily build (#10489) + +### Tests + +- Add experimental check to `ForEach-Object -Parallel` tests (#10354) (Thanks @KirkMunro!) +- Update tests for Alpine validation (#10428) + +### Build and Packaging Improvements + +- Bump `PowerShellGet` version from `2.2` to `2.2.1` (#10382) +- Bump `PackageManagement` version from `1.4.3` to `1.4.4` (#10383) +- Update `README.md` and `metadata.json` for `7.0.0-preview.4` (Internal 10011) +- Upgrade `.Net Core 3.0` version from `Preview 9` to `RC1` (#10552) (Thanks @bergmeister!) +- Fix `ExperimentalFeature` list generation (Internal 9996) +- Bump `PSReadLine` version from `2.0.0-beta4` to `2.0.0-beta5` (#10536) +- Fix release build script to set release tag +- Update version of `Microsoft.PowerShell.Native` to `7.0.0-preview.2` (#10519) +- Upgrade to `Netcoreapp3.0 preview9` (#10484) (Thanks @bergmeister!) +- Make sure the daily coordinated build, knows it is a daily build (#10464) +- Update the combined package build to release the daily builds (#10449) +- Remove appveyor reference (#10445) (Thanks @RDIL!) +- Bump `NJsonSchema` version from `10.0.22` to `10.0.23` (#10421) +- Remove the deletion of `linux-x64` build folder because some dependencies for Alpine need it (#10407) + +### Documentation and Help Content + +- Update `README.md` and metadata for `v6.1.6` and `v6.2.3` releases (#10523) +- Fix a typo in `README.md` (#10465) (Thanks @vedhasp!) +- Add a reference to `PSKoans` module to Learning Resources documentation (#10369) (Thanks @vexx32!) +- Update `README.md` and `metadata.json` for `7.0.0-preview.3` (#10393) + +## [7.0.0-preview.3] - 2019-08-20 + +### Breaking Changes + +- Remove `kill` alias for `Stop-Process` cmdlet on Unix (#10098) (Thanks @iSazonov!) +- Support for starting PowerShell as a login shell (`pwsh -Login` / `pwsh -l`) support (#10050) + +### Engine Updates and Fixes + +- Additional Telemetry - implementation of [`RFC0036`](https://github.com/PowerShell/PowerShell-RFC/pull/158) (#10336) +- Implement `ForEach-Object -Parallel` as an experimental feature (#10229) +- Skip `JumpList` on `NanoServer` and `IoT` (#10164) +- Make `Get-DscResource` work with class based resources (#10350) +- Fix `#requires -version` for `pwsh` 7 to include `6.1` and `6.2` in `PSCompatibleVersions` (#9943) (Thanks @bgelens!) +- Add dispose of `_runspaceDebugCompleteEvent` event object. (#10323) +- Fix performance regression from disabling debugger in system lockdown mode (#10269) +- Special case the `posix` locale in `WildcardPattern` (#10186) +- Use `Platform.IsWindowsDesktop` instead of checking both NanoServer and IoT (#10205) + +### General Cmdlet Updates and Fixes + +- Enable Experimental Features by default on Preview builds (#10228) +- Enable `-sta` and `-mta` switches for `pwsh` (`-sta` is required for `GUIs`) (#10061) +- Make breakpoints display better over PowerShell remoting (#10339) (Thanks @KirkMunro!) +- Add support for `AppX` reparse points (#10331) +- Make module name matching for `get-module -FullyQualifiedName` case insensitive (#10329) +- Expose `PreRelease` label in `PSModuleInfo` formatter (#10316) +- Add `-Raw` switch to `Select-String` which allows returning only the string that was matched (#9901) (Thanks @Jawz84!) + +- ### Performance + +- Reduce allocations in `MakePath()` method (#10027) (Thanks @iSazonov!) +- Remove extra check that the system dll exists (#10244) (Thanks @iSazonov!) +- Avoid boxing when passing value type arguments to `PSTraceSource.WriteLine` (#10052) (Thanks @iSazonov!) +- Reduce allocations in `Escape()` and `Unescape()` (#10041) (Thanks @iSazonov!) + +### Code Cleanup + +- Add the license header to `nanoserver.tests.ps1` (#10171) +- Mark `-parallel` and `-throttlelimit` reserved for `foreach` and `switch` statements (#10328) (Thanks @KirkMunro!) +- Deprecate workflow debugging code (#10321) (Thanks @KirkMunro!) +- Fix style issues in `InternalCommands.cs` (#10352) (Thanks @iSazonov!) +- Deprecate internal `HelpCategory.Workflow` enumeration (#10319) (Thanks @KirkMunro!) +- Update `Microsoft.PowerShell.CoreCLR.Eventing` to resolve conflict with `System.Diagnostics.EventLog` (#10305) +- Don't collect process start time as it's not being used on `consolehost` startup (#10294) +- .NET Core 3.0 now aborts the thread for us. Remove the `ThreadAbortException` code (#10230) (Thanks @iSazonov!) +- Use `nameof()` in `LocationGlobber` and `PathInfo` (#10200) (Thanks @iSazonov!) + +### Tools + +- Fix Hungarian prefix `my` (#9976) (Thanks @RDIL!) +- Fix spelling error in issue template (#10256) +- Quote arguments in `.vscode/tasks.json` in case of spaces (#10204) (Thanks @msftrncs!) + +### Tests + +- Remove `markdownlint` tests due to security issues (#10163) +- Add tests for `WildcardPattern.Escape()` and `Unescape()` (#10090) (Thanks @iSazonov!) +- Cleanup Docker release testing (#10310) (Thanks @RDIL!) + +### Build and Packaging Improvements + +- Update `Microsoft.Management.Infrastructure` version to `2.0.0-preview.2` (#10366) +- Move to `.NET Core 3.0 preview.8` (#10351) (#10227) (Thanks @bergmeister!) +- Bump `NJsonSchema` from `10.0.21` to `10.0.22` (#10364) +- Add `Microsoft.PowerShell.CoreCLR.Eventing.dll` to exception list for build fix (#10337) +- Bump `Microsoft.CodeAnalysis.CSharp` from `3.1.0` to `3.2.1` (#10273) (#10330) +- Revert the temporary AzDevOps artifact workaround (#10260) +- Fix macOS build break (#10207) + +### Documentation and Help Content + +- Update docs for `7.0.0-preview.2` release (#10160) (#10176) +- `PSSA` also includes formatting (#10172) +- Refactor security policy documentation so that they appear in the Security policy tab of GitHub (#9905) (Thanks @bergmeister!) +- Add tooling section to PR template (#10144) +- Update `README.md` and `metadata.json` for next releases (#10087) +- Update DotNet Support links (#10145) +- Update our language on our policy applying to security issues (#10304) +- Update dead links from `powershell.com` (#10297) +- Create `Distribution_Request` issue template (#10253) +- Fix: Removed dependency file with `Dependabot` (#10212) (Thanks @RDIL!) + +## [7.0.0-preview.2] - 2019-07-17 + +### Breaking Changes + +- Cleanup workflow - remove `PSProxyJob` (#10083) (Thanks @iSazonov!) +- Disable `Enter-PSHostProcess` cmdlet when system in lock down mode (Internal 9168) + +### Engine Updates and Fixes + +- Consider `DBNull.Value` and `NullString.Value` the same as `$null` when comparing with `$null` and casting to bool (#9794) (Thanks @vexx32!) +- Allow methods to be named after keywords (#9812) (Thanks @vexx32!) +- Create `JumpList` in `STA` thread as some `COM` `APIs` are strictly `STA` only to avoid sporadic `CLR` crashes (#9928) (#10057) (Thanks @bergmeister!) +- Skip `JumpList` on `NanoServer` and `IoT` (#10164) +- Display `COM` method signature with argument names (#9858) (Thanks @nbkalex!) +- Use the original precision (prior-dotnet-core-3) for double/float-to-string conversion (#9893) +- `Import-DscResource` can now clobber built-in DSC resource names (#9879) +- Add ability to pass `InitialSessionState` to the `ConsoleShell.Start` (#9802) (Thanks @asrosent!) +- Have console host not enter command prompt mode when using `Read-Host -Prompt` (#9743) +- Fix use of `Start-Process http://bing.com` (#9793) +- Support negative numbers in `-split` operator (#8960) (Thanks @ece-jacob-scott!) + +### General Cmdlet Updates and Fixes + +- Support DSC compilation on Linux. (#9834) +- Add alias for Service `StartType` (#9940) (Thanks @NeoBeum!) +- Add `-SecurityDescriptorSddl` parameter to `Set-Service` (#8626) (Thanks @kvprasoon!) +- Fix auto-download of files when enumerating files from a `OneDrive` folder (#9895) +- Set request headers when request body is empty in Web Cmdlets (#10034) (Thanks @markekraus!) +- Fix wrong comparison in `CertificateProvider` (#9987) (Thanks @iSazonov!) +- Sync docs changes into the embedded help for `pwsh` (#9952) +- Display Duration when displaying `HistoryInfo` (#9751) (Thanks @rkeithhill!) +- Update console startup and help `url` for PowerShell docs (#9775) +- Make `UseAbbreviationExpansion` and `TempDrive` official features (#9872) +- Fix `Get-ChildItem -Path` with wildcard `char` (#9257) (Thanks @kwkam!) + +### Performance + +- Add another fast path to `WildcardPattern.IsMatch` for patterns that only have an asterisk in the end (#10054) (Thanks @iSazonov!) +- Move some of the creations of `WildcardPattern` in outer loop to avoid unnecessary allocation (#10053) (Thanks @iSazonov!) +- Make `Foreach-Object` 2 times faster by reducing unnecessary allocations and boxing (#10047) +- Use a static cache for `PSVersionInfo.PSVersion` to avoid casting `SemanticVersion` to `Version` every time accessing that property (#10028) +- Reduce allocations in `NavigationCmdletProvider.NormalizePath()` (#10038) (Thanks @iSazonov!) +- Add fast path for wildcard patterns that contains no wildcard characters (#10020) +- Avoid `Assembly.GetName()` in `ClrFacade.GetAssemblies(string)` to reduce allocations of `CultureInfo` objects (#10024) (Thanks @iSazonov!) +- Avoid the `int[]` and `int[,]` allocation when tokenizing line comments and matching wildcard pattern (#10009) + +### Tools + +- Update change log generation tool to deal with private commits (#10096) +- Update `Start-PSBuild -Clean` logic of `git clean` to ignore locked files from `VS2019` (#10071) (Thanks @bergmeister!) +- Indent fix in `markdown-link.tests.ps1` (#10049) (Thanks @RDIL!) +- `Start-PSBuild -Clean` does not remove all untracked files (#10022) (Thanks @vexx32!) +- Add module to support Pester tests for automating debugger commands (`stepInto`, `stepOut`, etc.), along with basic tests (#9825) (Thanks @KirkMunro!) +- Remove `markdownlint` tests due to security issues (#10163) + +### Code Cleanup + +- Cleanup `CompiledScriptBlock.cs` (#9735) (Thanks @vexx32!) +- Cleanup workflow code (#9638) (Thanks @iSazonov!) +- Use `AddOrUpdate()` instead of `Remove` then `Add` to register runspace (#10007) (Thanks @iSazonov!) +- Suppress `PossibleIncorrectUsageOfAssignmentOperator` rule violation by adding extra parenthesis (#9460) (Thanks @xtqqczze!) +- Use `AddRange` in `GetModules()` (#9975) (Thanks @iSazonov!) +- Code cleanup: use `IndexOf(char)` overload (#9722) (Thanks @iSazonov!) +- Move `consts` and methods to single `CharExtensions` class (#9992) (Thanks @iSazonov!) +- Cleanup: Use `EndsWith(char)` and `StartsWith(char)` (#9994) (Thanks @iSazonov!) +- Remove `LCIDToLocaleName` `P/Invoke` from `GetComputerInfoCommand` (#9716) (Thanks @iSazonov!) +- Cleanup Parser tests (#9792) (Thanks @vexx32!) +- Remove `EtwActivity` empty constructor and make minor style fixes (#9958) (Thanks @RDIL!) +- Fix style issues from last commits (#9937) (Thanks @iSazonov!) +- Remove dead code about `IsTransparentProxy` (#9966) +- Fix minor typos in code comments (#9917) (Thanks @RDIL!) +- Style fixes for `CimAsyncOperations` (#9945) (Thanks @RDIL!) +- Fix minor `CodeFactor` style issues in `ModuleCmdletBase` (#9915) (Thanks @RDIL!) +- Clean up the use of `SetProfileRoot` and `StartProfile` in ConsoleHost (#9931) +- Fix minor style issues come from last commits (#9640) (Thanks @iSazonov!) +- Improve whitespace for Parser tests (#9806) (Thanks @vexx32!) +- Use new `string.ConCat()` in `Process.cs` (#9720) (Thanks @iSazonov!) +- Code Cleanup: Tidy up `scriptblock.cs` (#9732) (Thanks @vexx32!) + +### Tests + +- Mark `Set-Service` tests with password as `Pending` (#10146) +- Fix test password generation rule to meet Windows complexity requirements (#10143) +- Add test for `New-Item -Force` (#9971) (Thanks @robdy!) +- Fix gulp versions (#9916) (Thanks @RDIL!) +- Indentation fixes in `ci.psm1` (#9947) (Thanks @RDIL!) +- Remove some `Travis-CI` references (#9919) (Thanks @RDIL!) +- Improve release testing Docker images (#9942) (Thanks @RDIL!) +- Use `yarn` to install global tools (#9904) (Thanks @RDIL!) +- Attempt to work around the zip download issue in Azure DevOps Windows CI (#9911) +- Update PowerShell SDK version for hosting tests (Internal 9185) + +### Build and Packaging Improvements + +- Update the target framework for reference assemblies to `netcoreapp3.0` (#9747) +- Pin version of `netDumbster` to `2.0.0.4` (#9748) +- Fix daily `CodeCoverageAndTest` build by explicitly calling `Start-PSBootStrap` (#9724) +- Split the `fxdependent` package on Windows into two packages (#10134) +- Bump `System.Data.SqlClient` (#10109) +- Bump `System.Security.AccessControl` (#10100) +- Add performance tag to change log command (Internal) +- Upgrade .Net Core 3 SDK from `preview5` to `preview6` and related out of band `Nuget` packages from `2.1` to `3.0-preview6` (#9888) (Thanks @bergmeister!) +- Add to `/etc/shells` on macOS (#10066) +- Bump `Markdig.Signed` from `0.17.0` to `0.17.1` (#10062) +- Update copyright symbol for `NuGet` packages (#9936) +- Download latest version `(6.2.0)` of `PSDesiredStateConfiguration` `nuget` package. (#9932) +- Add automated `RPM` signing to release build (#10013) +- Bump `ThreadJob` from `1.1.2` to `2.0.1` in `/src/Modules` (#10003) +- Bump `PowerShellGet` from `2.1.4` to `2.2` in /src/Modules (#9933) (#10085) +- Bump `PackageManagement` from `1.4` to `1.4.3` in `/src/Modules` (#9820) (#9918) (#10084) +- Update to use `TSAv2` (#9914) +- Bump `NJsonSchema` from `9.14.1` to `10.0.21` (#9805) (#9843) (#9854) (#9862) (#9875) (#9885) (#9954) (#10017) +- Bump `System.Net.Http.WinHttpHandler` from `4.5.3` to `4.5.4` (#9786) +- Bump `Microsoft.ApplicationInsights` from `2.9.1` to `2.10.0` (#9757) +- Increase timeout of NuGet job to workaround build timeout (#9772) + +### Documentation and Help Content + +- Change log `6.1.4` (#9759) +- Change log for release `6.2.1` (#9760) +- Add quick steps for adding docs to cmdlets (#9978) +- Update readme `gitter` badge (#9920) (Thanks @RDIL!) +- Update `README` and `metadata.json` for `7.0.0-preview.1` release (#9767) + +## [7.0.0-preview.1] - 2019-05-30 + +### Breaking Changes + +- Disable the debugger when in system lock-down mode (#9645) +- Fix `Get-Module -FullyQualifiedName` option to work with paths (#9101) (Thanks @pougetat!) +- Fix `-NoEnumerate` behavior in `Write-Output` (#9069) (Thanks @vexx32!) +- Make command searcher treat wildcard as literal if target exists for execution (#9202) + +### Engine Updates and Fixes + +- Port PowerShell to .NET Core 3.0 (#9597) +- Make sure we always return an object in command searcher (#9623) +- Support line continuance with pipe at the start of a line (#8938) (Thanks @KirkMunro!) +- Add support for `ValidateRangeKind` to `ParameterMetadata.GetProxyAttributeData` (#9059) (Thanks @indented-automation!) +- Allow passing just a dash as an argument to a file via pwsh (#9479) +- Fix tab completion for functions (#9383) +- Reduce string allocation in console output code (#6882) (Thanks @iSazonov!) +- Fixing test run crash by not passing script block to the callback (#9298) +- Add Binary Parsing Support & Refactor `TryGetNumberValue` & `ScanNumberHelper` (#7993) (Thanks @vexx32!) +- Add PowerShell remoting enable/disable cmdlet warning messages (#9203) +- Add `xsd` for `cdxml` (#9177) +- Improve formatting performance by having better primitives on `PSObject` (#8785) (Thanks @powercode!) +- Improve type inference of array literals and foreach statement variables (#8100) (Thanks @SeeminglyScience!) +- Fix for `FormatTable` remote deserialization regression (#9116) +- Get `MethodInfo` from .NET public type with explicit parameter types (#9029) (Thanks @iSazonov!) +- Add retry logic to the operation that updates `powershell.config.json` (#8779) (Thanks @iSazonov!) +- Update the task-based `async` APIs added to PowerShell to return a Task object directly (#9079) +- Add 5 `InvokeAsync` overloads and `StopAsync` to the `PowerShell` type (#8056) (Thanks @KirkMunro!) +- Remove unused cached types (#9015) + +### General Cmdlet Updates and Fixes + +- Fix use of unicode ellipsis in `XML` for truncating error messages (#9589) +- Improve error message in FileSystemProvider when removing a folder containing hidden or read only files (#9551) (Thanks @iSazonov!) +- Enable recursion into `OneDrive` by not treating placeholders as symlinks (#9509) +- Change `MatchType` for `EnumerationOptions` to be `Win32` making this consistent with Windows PowerShell (#9529) +- Add Support for null Usernames in Web Cmdlet Basic Auth (#9536) (Thanks @markekraus!) +- Fix null reference when `Microsoft.PowerShell.Utility` is loaded as a `snapin` in hosting scenarios (#9404) +- Update width of `DateTime` to accommodate change in Japan `DateTime` format with new era starting 5/1/19 (#9503) +- Fix `Get-Runspace` runspace object format Type column (#9438) +- Return correct casing of filesystem path during normalization (#9250) +- Move warning message to `EndProcessing` so it only shows up once (#9385) +- Fix the platform check in `CimDSCParser.cs` (#9338) +- New `New-PSBreakpoint` cmdlet & new `-Breakpoint` parameter for `Debug-Runspace` (#8923) +- Fix help paging issues on macOS/Linux and with custom pager that takes arguments (#9033) (Thanks @rkeithhill!) +- Add `QuoteFields` parameter to `ConvertTo-Csv` and `Export-Csv` (#9132) (Thanks @iSazonov!) +- Fix progress for Get-ComputerInfo (#9236) (Thanks @powercode!) +- Add `ItemSeparator` and `AltItemSeparator` properties in `ProviderInfo` (#8587) (Thanks @renehernandez!) +- Add timestamp to `pshost` trace listener (#9230) +- Implement `Get-Random -Count` without specifying an `InputObject` list (#9111) (Thanks @pougetat!) +- Enable `SecureString` cmdlets for non-Windows (#9199) +- Add Obsolete message to `Send-MailMessage` (#9178) +- Fix `Restart-Computer` to work on `localhost` when WinRM is not present (#9160) +- Make `Start-Job` throw terminating exception when `-RunAs32` is specified in 64-bit pwsh (#9143) +- Make `Start-Job` throw terminating error when PowerShell is being hosted (#9128) +- Made `-Subject` parameter of `Send-MailMessage` command no longer mandatory. (#8961) (Thanks @ece-jacob-scott!) +- Make `New-ModuleManifest` consistent with `Update-ModuleManifest` (#9104) (Thanks @pougetat!) +- Add support for empty `NoteProperty` in `Group-Object` (#9109) (Thanks @iSazonov!) +- Remove `Hardlink` from `Mode` property in default file system format (#8789) (Thanks @powercode!) +- Fixing issue with help progress with `Get-Help` not calling `Completed` (#8788) (Thanks @powercode!) +- Allow `Test-ModuleManifest` to work when `RootModule` has no file extension (#8687) (Thanks @pougetat!) +- Add `UseQuotes` parameter to `Export-Csv` and `ConvertTo-Csv` cmdlets (#8951) (Thanks @iSazonov!) +- Update version for `PowerShell.Native` and hosting tests (#8983) +- Refactor shuffle in `Get-Random` to save a full iteration of the objects. (#8969) (Thanks @st0le!) +- Suggest `-Id pid` for `Get-Process pid` (#8959) (Thanks @MohiTheFish!) + +### Code Cleanup + +- `Attributes.cs` - Style / Formatting Fixes (#9625) (Thanks @vexx32!) +- Remove Workflow from `PSSessionType` (#9618) (Thanks @iSazonov!) +- Update use of "PowerShell Core" to just "PowerShell" (#9513) +- Use `IPGlobalProperties` on all platforms for getting host name (#9530) (Thanks @iSazonov!) +- Remove `IsSymLink()` P/Invoke on Unix (#9534) (Thanks @iSazonov!) +- Cleanup unused P/Invokes on Unix (#9531) (Thanks @iSazonov!) +- Update use of `Windows PowerShell` to just `PowerShell` (#9508) +- Cleanup: sort `usings` (#9490) (Thanks @iSazonov!) +- Cleanup `Export-Command` from `AssemblyInfo` (#9455) (Thanks @iSazonov!) +- Run CodeFormatter for `System.Management.Automation` (#9402) (Thanks @iSazonov!) +- Run CodeFormatter with `BraceNewLine`,`UsingLocation`,`FormatDocument`,`NewLineAbove` rules (#9393) (Thanks @iSazonov!) +- Run CodeFormatter for `WSMan.Management` (#9400) (Thanks @iSazonov!) +- Run CodeFormatter for `WSMan.Runtime` (#9401) (Thanks @iSazonov!) +- Run CodeFormatter for `Security` module (#9399) (Thanks @iSazonov!) +- Run CodeFormatter for `MarkdownRender` (#9398) (Thanks @iSazonov!) +- Run CodeFormatter for `Eventing` (#9394) (Thanks @iSazonov!) +- Use `Environment.NewLine` for new lines in `ConsoleHost` code (#9392) (Thanks @iSazonov!) +- Run CodeFormatter for Diagnostics module (#9378) (Thanks @iSazonov!) +- Run CodeFormatter for `Microsoft.PowerShell.Commands.Management` (#9377) (Thanks @iSazonov!) +- Run CodeFormatter for Utility module (#9376) (Thanks @iSazonov!) +- Style: Match file name casings of C# source files for Utility commands (#9329) (Thanks @ThreeFive-O!) +- Update repo for Ubuntu 14.04 EOL (#9324) +- Cleanup: sort `usings` (#9283) (Thanks @iSazonov!) +- Fix StyleCop Hungarian Notation (#9281) (Thanks @iSazonov!) +- Style: Update StyleCop rules (#8500) +- Enhance the P/Invoke code for `LookupAccountSid` in `Process.cs` (#9197) (Thanks @iSazonov!) +- Fix coding style for `NewModuleManifestCommand` (#9134) (Thanks @pougetat!) +- Remove unused method `CredUIPromptForCredential` from `HostUtilities.cs` (#9220) (Thanks @iSazonov!) +- Remove non-existent paths from `.csproj` files (#9214) (Thanks @ThreeFive-O!) +- Typo in new parameter set (#9205) +- Minor `FileSystemProvider` cleanup (#9182) (Thanks @RDIL!) +- Cleanup style issues in `CoreAdapter` and `MshObject` (#9190) (Thanks @iSazonov!) +- Minor cleanups in `Process.cs` (#9195) (Thanks @iSazonov!) +- Refactor `ReadConsole` P/Invoke in `ConsoleHost` (#9165) (Thanks @iSazonov!) +- Clean up `Get-Random` cmdlet (#9133) (Thanks @pougetat!) +- Fix to not pass `StringBuilder` by reference (`out` or `ref`) in P/Invoke (#9066) (Thanks @iSazonov!) +- Update AppVeyor comments in `Implicit.Remoting.Tests.ps1` (#9020) (Thanks @RDIL!) +- Remove AppImage from tools (#9100) (Thanks @Geweldig!) +- Using supported syntax for restoring warnings - Visual Studio 2019 complains about enable. (#9107) (Thanks @powercode!) +- Use `Type.EmptyTypes` and `Array.Empty()` to replace our custom code of the same functionality (#9042) (Thanks @iSazonov!) +- Rename private methods in `MshCommandRuntime.cs` (#9074) (Thanks @vexx32!) +- Cleanup & update `ErrorRecord` class code style (#9021) (Thanks @vexx32!) +- Remove unused cached types from `CachedReflectionInfo` (#9019) (Thanks @iSazonov!) +- Fix CodeFactor brace style issues in `FileSystemProvider` (#8992) (Thanks @RDIL!) +- Use `List.AddRange` to optimize `-Split` (#9001) (Thanks @iSazonov!) +- Remove Arch Linux Dockerfile (#8990) (Thanks @RDIL!) +- Cleanup `dllimport` (#8847) (Thanks @iSazonov!) + +### Tools + +- Convert custom attribute `ValidatePathNotInSettings` to function (#9406) +- Create `DependaBot` `config.yml` (#9368) +- Add more users to failures detection and fix alias for static analysis (#9292) +- Make `install-powershell.ps1` work on Windows Server 2012 R2 (#9271) +- Enable `PoshChan` for getting and automatic retrieval of test failures for a PR (#9232) +- Fix capitalization cases for `PoshChan` (#9188) (Thanks @RDIL!) +- Update to new format for `PoshChan` settings and allow all users access to reminders (#9198) +- Fix settings to use dashes instead of underscore (#9167) +- Fix `AzDevOps` context names and add all PowerShell team members (#9164) +- Add settings for `PoshChan` (#9162) +- Adding `CmdletsToExport` and `AliasesToExport` to test module manifests. (#9108) (Thanks @powercode!) +- Delete Docker manifest creation script (#9076) (Thanks @RDIL!) +- Make install scripts more consistent over different operating systems (#9071) (Thanks @Geweldig!) +- Comment cleanup in `releaseTools.psm1` (#9064) (Thanks @RDIL!) +- Fix duplicate recommendation of Azure DevOps extension for Visual Studio Code (#9032) (Thanks @ThreeFive-O!) +- Code coverage artifacts (#8993) + +### Tests + +- Update version tests to use `NextReleaseVersion` from `metadata.json` (#9646) +- Convert Windows CI to stages (#9607) +- Multiple test fixes and improved logging for fragile tests (#9569) +- Add unit and feature tests for `Send-MailMessage` (#9213) (Thanks @ThreeFive-O!) +- Update to Pester `4.8.0` (#9510) +- Ensure `Wait-UntilTrue` returns `$true` in Pester tests (#9458) (Thanks @xtqqczze!) +- Adding tests for `Remove-Module` (#9276) (Thanks @pougetat!) +- Allow CI to run on branches with this name pattern: `feature*` (#9415) +- Mark tests in macOS CI which use `AppleScript` as pending/inconclusive (#9352) +- Reduce time for stack overflow test (#9302) +- Added more tests for `Import-Alias` by file regarding parsing difficult aliases strings (#9247) (Thanks @SytzeAndr!) +- Move from `npm` to `Yarn` for markdown tests (#9312) (Thanks @RDIL!) +- Only search for functions in Constrained Language help tests (#9301) +- Fix skipping of tests in `RemoteSession.Basic.Tests.ps1` (#9304) +- Make sure non-Windows CI fails when a test fails (#9303) +- Update tests to account for when `$PSHOME` is read only (#9279) +- Add tests for command globbing (#9180) +- Fix tab completion test to handle multiple matches (#8891) +- Refactor macOS CI so that tests run in parallel (#9056) +- Fix `Enter-PSHostProcess` tests flakiness (#9007) +- Add source for `Install-Package` to install `netDumbster` (#9081) +- Style fixes for `Select-Xml` tests (#9037) (Thanks @ThreeFive-O!) +- Enable cross-platform `Send-MailMessage` tests for CI (#8859) (Thanks @ThreeFive-O!) +- Added `RequireSudoOnUnix` tags to `PowerShellGet` tests and remove pending parameter (#8954) (Thanks @RDIL!) +- Style fixes for `ConvertTo-Xml` tests (#9036) (Thanks @ThreeFive-O!) +- Align name schemes for test files (#9034) (Thanks @ThreeFive-O!) +- Pending `NamedPipeConnectionInfo` test (#9003) (Thanks @iSazonov!) +- Add test for `-WhatIf` for `New-FileCatalog` (#8966) (Thanks @mjanko5!) + +### Build and Packaging Improvements + +- Fix the PowerShell version number in MSI packages (Internal 8547) +- Add cleanup before building test package (Internal 8529) +- Update version for SDK tests and `Microsoft.PowerShell.Native` package (Internal 8512) +- Update the target framework for reference assemblies to `netcoreapp3.0` (Internal 8510) +- Fix syncing modules from PowerShell gallery by normalizing version numbers (Internal 8504) +- Add `tsaVersion` property as `TsaV1` for compliance build phase (#9176) +- Add ability to cross compile (#9374) +- Add `AcessToken` variable to jobs that perform signing (#9351) +- Add CI for `install-powershell.sh` and Amazon Linux (#9314) +- Add component detection to all jobs (#8964) +- Add Preview assets for `MSIX` (#9375) +- Add secret scanning to CI (#9249) +- Build test packages for `windows`, `linux-x64`, `linux-arm`, `linux-arm64` and `macOS` (#9476) +- Bump `gulp` from `4.0.0` to `4.0.2` (#9441, #9544) +- Bump `Markdig.Signed` from `0.15.7` to `0.17.0` (#8981, #9579) +- Bump `Microsoft.CodeAnalysis.CSharp` from `2.10.0` to `3.1.0` (#9277, 9653) +- Bump `Microsoft.PowerShell.Native` from `6.2.0-rc.1` to `6.2.0` (#9200) +- Bump `Microsoft.Windows.Compatibility` from `2.0.1` to `2.1.1` (#9605) +- Bump `Newtonsoft.Json` from `12.0.1` to `12.0.2` (#9431, #9434) +- Bump `NJsonSchema` from `9.13.19` to `9.14.1` (#9044, #9136, #9166, #9172, #9184, #9196, #9265, #9349, #9388, #9421, #9429, #9478, #9523, #9616) +- Bump `PackageManagement` from `1.3.1` to `1.4` (#9567, #9650) +- Bump `PowerShellGet` from `2.0.4` to `2.1.4` in /src/Modules (#9110, #9145, #9600, #9691) +- Bump `PSReadLine` from `2.0.0-beta3` to `2.0.0-beta4` (#9554) +- Bump `SelfSignedCertificate` (#9055) +- Bump `System.Data.SqlClient` from `4.6.0` to `4.6.1` (#9601) +- Bump `System.Net.Http.WinHttpHandler` from `4.5.2` to `4.5.3` (#9333) +- Bump `Microsoft.PowerShell.Archive` from `1.2.2.0` to `1.2.3.0` (#9593) +- Check to be sure that the test result file has actual results before uploading (#9253) +- Clean up static analysis config (#9113) (Thanks @RDIL!) +- Create `codecoverage` and test packages for non-Windows (#9373) +- Create test package for macOS on release builds (#9344) +- Disable Homebrew analytics in macOS Azure DevOps builds (#9130) (Thanks @RDIL!) +- Enable building of `MSIX` package (#9289) +- Enable building on Kali Linux (#9471) +- Fix artifact Download issue in release build (#9095) +- Fix build order in `windows-daily` build (#9275) +- Fix dependencies of NuGet build to wait on `DEB` uploads to finish (#9118) +- Fix `MSI` Upgrade failure for preview builds (#9013) +- Fix publishing daily `nupkg` to MyGet (#9269) +- Fix the failed test and update `Publish-TestResults` to make Azure DevOps fail the task when any tests failed (#9457) +- Fix variable name in `windows-daily.yml` (#9274) +- Fixed Dockerfile syntax highlighting (#8991) (Thanks @RDIL!) +- Make `CodeCoverage` configuration build portable symbol files (#9346) +- Make Linux CI parallel (#9209) +- Move artifacts to artifact staging directory before uploading (#9273) +- Performance improvements for release build (#9179) +- Preserve user shortcuts pinned to TaskBar during MSI upgrade (#9305) (Thanks @bergmeister!) +- Publish global tool packages to `pwshtool` blob and bug fixes (#9163) +- Publish test package on release builds (#9063) +- Publish windows daily build to MyGet (#9288) +- Remove appveyor references from packaging tools (#9117) (Thanks @RDIL!) +- Remove code from `CI.psm1` to optionally run Feature tests (#9212) (Thanks @RDIL!) +- Remove duplicate `PoliCheck` task and pin to specific version (#9297) +- Run `Start-PSBootStrap` in Code Coverage build to install .NET SDK (#9690) +- Switch from `BMP` to `PNG` for graphical `MSI` installer assets (#9606) +- Translate Skipped the test results into something Azure DevOps does NOT understand (#9124) +- Update Markdown test dependencies (#9075) (Thanks @RDIL!) +- Update UML to represent SDK and Global tool builds (#8997) +- Use IL assemblies for NuGet packages to reduce size (#9171) + +### Documentation and Help Content + +- Add checkbox to PR checklist for experimental feature use (#9619) (Thanks @KirkMunro!) +- Updating committee membership (#9577) (Thanks @HemantMahawar!) +- Update `CODEOWNERS` file to reduce noise (#9547) +- add download link to `raspbian64` to readme (#9520) +- Update `Support_Question.md` (#9218) (Thanks @vexx32!) +- Fix version of `PowerShellGet` in changelog (#9335) +- Update release process template to clarify that most tasks are coordinated by the release pipeline (#9238) +- Fix several problems in `WritingPesterTests` guideline (#9078) (Thanks @ThreeFive-O!) +- Update `ChangeLog` for `6.2.0` (#9245) +- Update docs for `v6.2.0` (#9229) +- Update `feature-request` issue template to move instructions into comments. (#9187) (Thanks @mklement0!) +- Update link to Contributing guide to new `PowerShell-Doc` repo (#9090) (Thanks @iSazonov!) +- Correct punctuation in `README.md` (#9045) (Thanks @yashrajbharti!) +- Update Docker `README.md` (#9010) (Thanks @RDIL!) +- Update release process issue template (#9051) (Thanks @RDIL!) +- Documentation Cleanup (#8851) (Thanks @RDIL!) +- 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 +[7.0.0]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-rc.3...v7.0.0 +[7.0.0-rc.3]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-rc.2...v7.0.0-rc.3 +[7.0.0-rc.2]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-rc.1...v7.0.0-rc.2 +[7.0.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.6...v7.0.0-rc.1 +[7.0.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.5...v7.0.0-preview.6 +[7.0.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.4...v7.0.0-preview.5 +[7.0.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.3...v7.0.0-preview.4 +[7.0.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.2...v7.0.0-preview.3 +[7.0.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.1...v7.0.0-preview.2 +[7.0.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v6.2.0-rc.1...v7.0.0-preview.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 new file mode 100644 index 00000000000..c20cd311ff5 --- /dev/null +++ b/CHANGELOG/README.md @@ -0,0 +1,11 @@ +# Changelogs + +- [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 new file mode 100644 index 00000000000..8f23d37c9bb --- /dev/null +++ b/CHANGELOG/preview.md @@ -0,0 +1,252 @@ +# Preview Changelog + +## [7.7.0-preview.1] + +### Breaking Changes + +- 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 + +- Update `MaxVisitCount` and `MaxHashtableKeyCount` if `VisitorSafeValueContext` indicates `SkipLimitCheck` is true +(#27308) +- Enable usage in AppContainers (#27305) +- Delay update notification for one week to ensure all packages become available (#27095) +- Fix up default value for parameters with the `in` modifier (#26785) (Thanks @jborean93!) +- Fix `WSManInstance` COM interface with `ResourceURI` (#26692) (Thanks @jborean93!) +- Refactor the module path construction code to make it more robust and easier to maintain (#26565) +- Fix checks for local user config file paths (#26269) + +### General Cmdlet Updates and Fixes + +- Add 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 + +
+ + + +

We thank the following contributors!

+

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

+ +
+ +
    +
  • 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 + +- Add GitOps policy to auto-label backport candidates when CL-BuildPackaging is added (#26881) +- Add Pester CI Analysis Skill (#26806) +- Delete unused winget release script (#26683) +- Improve error message from `Start-NativeExecution` (#26500) (Thanks @logiclrd!) +- Add default CODEOWNERS entry for maintainers (#26660) +- Add Attack Surface Analyzer Script (#26379) +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26350) +- Add reusable get-changed-files action and refactor existing actions (#26355) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26322) +- Create github copilot setup workflow (#26285) +- Update dependabot.yml to monitor release/* branches (#26251) + +### Tests + +- Fix 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 + +
+ + + +

We thank the following contributors!

+

@powercode, @kasperk81, @xtqqczze

+ +
+ +
    +
  • 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 + +- 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 dddfa22df2a..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]: http://opensource.microsoft.com/codeofconduct/ -[conduct-FAQ]: http://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 new file mode 100644 index 00000000000..f8288b53b67 --- /dev/null +++ b/DotnetRuntimeMetadata.json @@ -0,0 +1,15 @@ +{ + "sdk": { + "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/LICENSE.txt b/LICENSE.txt index f2618346d7b..b2f52a2bad4 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,13 +1,9 @@ -PowerShell 6.0 - -Copyright (c) Microsoft Corporation. All rights reserved. - -All rights reserved. +Copyright (c) Microsoft Corporation. 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 diff --git a/PowerShell-Win.sln b/PowerShell-Win.sln deleted file mode 100644 index 35a5c99e93d..00000000000 --- a/PowerShell-Win.sln +++ /dev/null @@ -1,151 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "powershell-win-core", "src\powershell-win-core\powershell-win-core.csproj", "{8359D422-E0C4-4A0D-94EB-3C9DD16B7932}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Management.Automation", "src\System.Management.Automation\System.Management.Automation.csproj", "{AF660EE7-0183-4B79-A93F-221B6AC1C24B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Utility", "src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{EAB203E1-2A68-4166-BE54-5C44DE825229}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.CoreCLR.Eventing", "src\Microsoft.PowerShell.CoreCLR.Eventing\Microsoft.PowerShell.CoreCLR.Eventing.csproj", "{981D3972-343D-4E17-935B-037E1C622771}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleHost", "src\Microsoft.PowerShell.ConsoleHost\Microsoft.PowerShell.ConsoleHost.csproj", "{8FFE645D-F0C9-4220-9A88-83062ED211D2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Management", "src\Microsoft.PowerShell.Commands.Management\Microsoft.PowerShell.Commands.Management.csproj", "{FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.SDK", "src\Microsoft.PowerShell.SDK\Microsoft.PowerShell.SDK.csproj", "{4BC19063-1F66-467B-87DE-80449C72BCD6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Management.Infrastructure.CimCmdlets", "src\Microsoft.Management.Infrastructure.CimCmdlets\Microsoft.Management.Infrastructure.CimCmdlets.csproj", "{131A8527-92D7-468F-822D-5354229A865C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Commands.Diagnostics", "src\Microsoft.PowerShell.Commands.Diagnostics\Microsoft.PowerShell.Commands.Diagnostics.csproj", "{439A24FC-8E0A-48B6-8227-44C297311F49}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.PSReadLine", "src\Microsoft.PowerShell.PSReadLine\Microsoft.PowerShell.PSReadLine.csproj", "{07BFD271-8992-4F34-9091-6CFC3E224A24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WSMan.Management", "src\Microsoft.WSMan.Management\Microsoft.WSMan.Management.csproj", "{8F63D134-E413-4181-936D-D82F3F5F1D85}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.Security", "src\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.csproj", "{C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WSMan.Runtime", "src\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj", "{D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - CodeCoverage|Any CPU = CodeCoverage|Any CPU - Debug|Any CPU = Debug|Any CPU - Linux|Any CPU = Linux|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Linux|Any CPU.Build.0 = Linux|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Release|Any CPU.Build.0 = Release|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Linux|Any CPU.Build.0 = Linux|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Release|Any CPU.Build.0 = Release|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Linux|Any CPU.Build.0 = Linux|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAB203E1-2A68-4166-BE54-5C44DE825229}.Release|Any CPU.Build.0 = Release|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Debug|Any CPU.Build.0 = Debug|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Linux|Any CPU.Build.0 = Linux|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Release|Any CPU.ActiveCfg = Release|Any CPU - {981D3972-343D-4E17-935B-037E1C622771}.Release|Any CPU.Build.0 = Release|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Linux|Any CPU.Build.0 = Linux|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Release|Any CPU.Build.0 = Release|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Linux|Any CPU.Build.0 = Linux|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Release|Any CPU.Build.0 = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Linux|Any CPU.ActiveCfg = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Linux|Any CPU.Build.0 = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BC19063-1F66-467B-87DE-80449C72BCD6}.Release|Any CPU.Build.0 = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Linux|Any CPU.ActiveCfg = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Linux|Any CPU.Build.0 = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {131A8527-92D7-468F-822D-5354229A865C}.Release|Any CPU.Build.0 = Release|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Debug|Any CPU.Build.0 = Debug|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Linux|Any CPU.Build.0 = Linux|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Release|Any CPU.ActiveCfg = Release|Any CPU - {439A24FC-8E0A-48B6-8227-44C297311F49}.Release|Any CPU.Build.0 = Release|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Linux|Any CPU.Build.0 = Linux|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07BFD271-8992-4F34-9091-6CFC3E224A24}.Release|Any CPU.Build.0 = Release|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Linux|Any CPU.Build.0 = Linux|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F63D134-E413-4181-936D-D82F3F5F1D85}.Release|Any CPU.Build.0 = Release|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Linux|Any CPU.Build.0 = Linux|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Release|Any CPU.Build.0 = Release|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Linux|Any CPU.ActiveCfg = Linux|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Linux|Any CPU.Build.0 = Linux|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9128A855-8499-43C0-9C7C-08ECC47768B0} - EndGlobalSection -EndGlobal diff --git a/PowerShell.Common.props b/PowerShell.Common.props index 32d8bb5008f..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,28 +89,37 @@ --> - $(PSCoreBuildVersion) + $(PSCoreFileVersion) $(PSCoreFormattedVersion) $(PSCoreFormattedVersion) $(PSCoreBuildVersion) + ..\..\assets\Powershell_av_colors.ico + ..\..\assets\Powershell_avatar.ico + ..\..\assets\Powershell_black.ico + + + + + + $(DefineConstants);UNIX + + + + + portable + + + + + + EnvironmentVariable;Global + false + false + + + + + Global + + + + + true + true + + + + AppLocal + + + + + true + true + + + + + true + portable + + + + + true + + full + + + + strict + + + + true + diff --git a/PowerShell.sln b/PowerShell.sln new file mode 100644 index 00000000000..4938316281d --- /dev/null +++ b/PowerShell.sln @@ -0,0 +1,180 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# 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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "powershell-win-core", "src\powershell-win-core\powershell-win-core.csproj", "{8359D422-E0C4-4A0D-94EB-3C9DD16B7932}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Management.Automation", "src\System.Management.Automation\System.Management.Automation.csproj", "{AF660EE7-0183-4B79-A93F-221B6AC1C24B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.Commands.Utility", "src\Microsoft.PowerShell.Commands.Utility\Microsoft.PowerShell.Commands.Utility.csproj", "{EAB203E1-2A68-4166-BE54-5C44DE825229}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.CoreCLR.Eventing", "src\Microsoft.PowerShell.CoreCLR.Eventing\Microsoft.PowerShell.CoreCLR.Eventing.csproj", "{981D3972-343D-4E17-935B-037E1C622771}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.ConsoleHost", "src\Microsoft.PowerShell.ConsoleHost\Microsoft.PowerShell.ConsoleHost.csproj", "{8FFE645D-F0C9-4220-9A88-83062ED211D2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.Commands.Management", "src\Microsoft.PowerShell.Commands.Management\Microsoft.PowerShell.Commands.Management.csproj", "{FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.SDK", "src\Microsoft.PowerShell.SDK\Microsoft.PowerShell.SDK.csproj", "{4BC19063-1F66-467B-87DE-80449C72BCD6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Infrastructure.CimCmdlets", "src\Microsoft.Management.Infrastructure.CimCmdlets\Microsoft.Management.Infrastructure.CimCmdlets.csproj", "{131A8527-92D7-468F-822D-5354229A865C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.Commands.Diagnostics", "src\Microsoft.PowerShell.Commands.Diagnostics\Microsoft.PowerShell.Commands.Diagnostics.csproj", "{439A24FC-8E0A-48B6-8227-44C297311F49}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WSMan.Management", "src\Microsoft.WSMan.Management\Microsoft.WSMan.Management.csproj", "{8F63D134-E413-4181-936D-D82F3F5F1D85}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.Security", "src\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.csproj", "{C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WSMan.Runtime", "src\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj", "{D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}" +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}") = "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 +# +# 4BC19063-1F66-467B-87DE-80449C72BCD6 - Microsoft.PowerShell.SDK +# 8359D422-E0C4-4A0D-94EB-3C9DD16B7932 - PowerShell-Win +# Linux is invalid and mapped to Release +# +# 73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690 - powershell-unix +# Only Linux is valid, all configurations mapped to Linux +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeCoverage|Any CPU = CodeCoverage|Any CPU + Debug|Any CPU = Debug|Any CPU + Linux|Any CPU = Linux|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Linux|Any CPU.Build.0 = Release|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8359D422-E0C4-4A0D-94EB-3C9DD16B7932}.Release|Any CPU.Build.0 = Release|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Linux|Any CPU.Build.0 = Linux|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF660EE7-0183-4B79-A93F-221B6AC1C24B}.Release|Any CPU.Build.0 = Release|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Linux|Any CPU.Build.0 = Linux|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAB203E1-2A68-4166-BE54-5C44DE825229}.Release|Any CPU.Build.0 = Release|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Debug|Any CPU.Build.0 = Debug|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Linux|Any CPU.Build.0 = Linux|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Release|Any CPU.ActiveCfg = Release|Any CPU + {981D3972-343D-4E17-935B-037E1C622771}.Release|Any CPU.Build.0 = Release|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Linux|Any CPU.Build.0 = Linux|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FFE645D-F0C9-4220-9A88-83062ED211D2}.Release|Any CPU.Build.0 = Release|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Linux|Any CPU.Build.0 = Linux|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCE53A5E-5FAC-48BE-BAD8-2110040B5C2E}.Release|Any CPU.Build.0 = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Linux|Any CPU.Build.0 = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BC19063-1F66-467B-87DE-80449C72BCD6}.Release|Any CPU.Build.0 = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Linux|Any CPU.ActiveCfg = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Linux|Any CPU.Build.0 = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {131A8527-92D7-468F-822D-5354229A865C}.Release|Any CPU.Build.0 = Release|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Linux|Any CPU.Build.0 = Linux|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {439A24FC-8E0A-48B6-8227-44C297311F49}.Release|Any CPU.Build.0 = Release|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Linux|Any CPU.Build.0 = Linux|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F63D134-E413-4181-936D-D82F3F5F1D85}.Release|Any CPU.Build.0 = Release|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Linux|Any CPU.Build.0 = Linux|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4F81816-C87A-4ABF-8A37-24AC16A0A6CF}.Release|Any CPU.Build.0 = Release|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Linux|Any CPU.Build.0 = Linux|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9CCCB67-4EBE-4854-AB52-C0129DC5BAE4}.Release|Any CPU.Build.0 = Release|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Linux|Any CPU.Build.0 = Linux|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Release|Any CPU.Build.0 = Release|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Linux|Any CPU.Build.0 = Linux|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}.Release|Any CPU.Build.0 = Release|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Linux|Any CPU.ActiveCfg = Linux|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Linux|Any CPU.Build.0 = Linux|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08704934-9764-48CE-86DB-BCF0A1CF7899}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9128A855-8499-43C0-9C7C-08ECC47768B0} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 6688d2d7141..a7b31c475f8 100644 --- a/README.md +++ b/README.md @@ -1,201 +1,104 @@ # ![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 repo started as a fork of the Windows PowerShell code base, changes made in this repo do not make their way back to Windows PowerShell 5.1 automatically. -This also means that issues tracked here are only for PowerShell Core 6.0. -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. -[UserVoice]: https://windowsserver.uservoice.com/forums/301869-powershell +[issues]: https://github.com/PowerShell/PowerShell/issues +[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 | Downloads | How to Install | -| -------------------------------------------| ------------------------| ----------------------------- | -| [Windows (x64)][corefx-win] | [.msi][rl-windows-64] | [Instructions][in-windows] | -| [Windows (x86)][corefx-win] | [.msi][rl-windows-86] | [Instructions][in-windows] | -| [Ubuntu 17.04][corefx-linux] | [.deb][rl-ubuntu17] | [Instructions][in-ubuntu17] | -| [Ubuntu 16.04][corefx-linux] | [.deb][rl-ubuntu16] | [Instructions][in-ubuntu16] | -| [Ubuntu 14.04][corefx-linux] | [.deb][rl-ubuntu14] | [Instructions][in-ubuntu14] | -| [Debian 8.7+][corefx-linux] | [.deb][rl-debian8] | [Instructions][in-deb8] | -| [Debian 9][corefx-linux] | [.deb][rl-debian9] | [Instructions][in-deb9] | -| [CentOS 7][corefx-linux] | [.rpm][rl-centos] | [Instructions][in-centos] | -| [Red Hat Enterprise Linux 7][corefx-linux] | [.rpm][rl-centos] | [Instructions][in-rhel7] | -| [OpenSUSE 42.2][corefx-linux] | [.rpm][rl-centos] | [Instructions][in-opensuse422]| -| [Fedora 25][corefx-linux] | [.rpm][rl-centos] | [Instructions][in-fedora25] | -| [Fedora 26][corefx-linux] | [.rpm][rl-centos] | [Instructions][in-fedora26] | -| [macOS 10.12+][corefx-macos] | [.pkg][rl-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 | How to Install | -| -------------------------| ------------------------| ----------------------------- | -| Arch Linux | | [Instructions][in-archlinux] | -| Kali Linux | [.deb][rl-ubuntu16] | [Instructions][in-kali] | -| Many Linux distributions | [.AppImage][rl-ai] | [Instructions][in-appimage] | - -You can also download the PowerShell binary archives for Windows, macOS and Linux. - -| Platform | Downloads | How to Install | -| ------------------------------------| ------------------------------------------------ | ------------------------------ | -| Windows | [32-bit][rl-winx86-zip]/[64-bit][rl-winx64-zip] | [Instructions][in-windows-zip] | -| macOS | [64-bit][rl-macos-tar] | [Instructions][in-tar] | -| Linux | [64-bit][rl-linux-tar] | [Instructions][in-tar] | -| Windows (arm) **Experimental** | [32-bit][rl-winarm]/[64-bit][rl-winarm64] | [Instructions][in-windows-zip] | -| Raspbian (Stretch) **Experimental** | [.tgz][rl-raspbian] | [Instructions][in-raspbian] | - -[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-x64.msi -[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-x86.msi -[rl-ubuntu17]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell_6.0.1-1.ubuntu.17.04_amd64.deb -[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell_6.0.1-1.ubuntu.16.04_amd64.deb -[rl-ubuntu14]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell_6.0.1-1.ubuntu.14.04_amd64.deb -[rl-debian8]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell_6.0.1-1.debian.8_amd64.deb -[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell_6.0.1-1.debian.9_amd64.deb -[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -[rl-ai]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-x86_64.AppImage -[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-osx.10.12-x64.pkg -[rl-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-arm32.zip -[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-arm64.zip -[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-x86.zip -[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/PowerShell-6.0.1-win-x64.zip -[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-osx-x64.tar.gz -[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-linux-x64.tar.gz -[rl-raspbian]: https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-linux-arm32.tar.gz - -[installation]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation -[in-windows]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/windows.md#msi -[in-ubuntu14]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#ubuntu-1404 -[in-ubuntu16]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#ubuntu-1604 -[in-ubuntu17]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#ubuntu-1704 -[in-deb8]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#debian-8 -[in-deb9]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#debian-9 -[in-centos]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#centos-7 -[in-rhel7]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#red-hat-enterprise-linux-rhel-7 -[in-opensuse422]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#opensuse-422 -[in-fedora25]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#fedora-25 -[in-fedora26]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#fedora-26 -[in-archlinux]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#arch-linux -[in-appimage]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#linux-appimage -[in-macos]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/macos.md -[in-docker]: https://github.com/PowerShell/PowerShell/tree/master/docker -[in-kali]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#kali -[in-windows-zip]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/windows.md#zip -[in-tar]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#binary-archives -[in-raspbian]: https://github.com/PowerShell/PowerShell/tree/master/docs/installation/linux.md#raspbian -[corefx-win]:https://github.com/dotnet/core/blob/master/release-notes/2.0/2.0-supported-os.md#windows -[corefx-linux]:https://github.com/dotnet/core/blob/master/release-notes/2.0/2.0-supported-os.md#linux -[corefx-macos]:https://github.com/dotnet/core/blob/master/release-notes/2.0/2.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). -## Community Dashboard - -[Dashboard](https://aka.ms/psgithubbi) 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://blogs.msdn.microsoft.com/powershell/2017/01/31/powershell-open-source-community-dashboard/). +## Upgrading PowerShell -## Chat Room +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. -Want to chat with other members of the PowerShell community? +## Community Dashboard -We have a Gitter Room which you can join below. +[Dashboard](https://aka.ms/PSPublicDashboard) with visualizations for community contributions and project status using PowerShell, Azure, and PowerBI. -[![Join the chat at https://gitter.im/PowerShell/PowerShell](https://badges.gitter.im/PowerShell/PowerShell.svg)](https://gitter.im/PowerShell/PowerShell?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +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/). -There is also the community driven PowerShell Slack Team which you can sign up for at [Slack Sign up]. +## Discussions -[Slack Sign up]: http://slack.poshcode.org +[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. -## Add-ons and libraries +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. -[Awesome PowerShell](https://github.com/janikvonrotz/awesome-powershell) is a great curated list of add-ons and resources. +Create or join a [discussion](https://github.com/PowerShell/PowerShell/discussions). -## Building the Repository +## Chat -| Linux | Windows | macOS | -|--------------------------|----------------------------|------------------------| -| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | +Want to chat with other members of the PowerShell community? -If you have any problems building, please consult the developer [FAQ][]. +There are dozens of topic-specific channels on our community-driven PowerShell Virtual User Group, which you can join on: -### Build status of master branches +* [Discord](https://discord.gg/PowerShell) +* [IRC](https://web.libera.chat/#powershell) on Libera.Chat +* [Slack](https://aka.ms/psslack) -| AppVeyor (Windows) | Travis CI (Linux / macOS) | -|--------------------------|--------------------------| -| [![av-image][]][av-site] | [![tv-image][]][tv-site] | +## Developing and Contributing -### Build status of nightly builds +Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. -| AppVeyor (Windows) | Travis CI (Linux) | Travis CI (macOS) | Code Coverage Status | -|--------------------------|-------------------|-------------------|----------------------| -| [![av-nightly-image][]][av-nightly-site] | [![linux-nightly-image][]][tv-site] | [![macOS-nightly-image][]][tv-site] | [![cc-image][]][cc-site] | +If you are developing .NET Core C# applications targeting PowerShell Core, [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. -[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 +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. -[FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md +[Contribution Guide]: .github/CONTRIBUTING.md +[check out our FAQ]: docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package -[tv-image]: https://travis-ci.org/PowerShell/PowerShell.svg?branch=master -[tv-site]: https://travis-ci.org/PowerShell/PowerShell/branches -[av-image]: https://ci.appveyor.com/api/projects/status/nsng9iobwa895f98/branch/master?svg=true -[av-site]: https://ci.appveyor.com/project/PowerShell/powershell -[linux-nightly-image]: https://jimtru1979.blob.core.windows.net/badges/DailyBuildStatus.Linux.svg -[macOS-nightly-image]: https://jimtru1979.blob.core.windows.net/badges/DailyBuildStatus.OSX.svg -[av-nightly-image]: https://ci.appveyor.com/api/projects/status/46yd4jogtm2jodcq?svg=true -[av-nightly-site]: https://ci.appveyor.com/project/PowerShell/powershell-f975h -[cc-site]: https://codecov.io/gh/PowerShell/PowerShell -[cc-image]: https://codecov.io/gh/PowerShell/PowerShell/branch/master/graph/badge.svg +## Building PowerShell -## Downloading the Source Code +| Linux | Windows | macOS | +|--------------------------|----------------------------|------------------------| +| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | -The PowerShell repository has a number of other repositories embedded as submodules. +If you have any problems building PowerShell, please start by consulting the developer [FAQ]. -To make things easy, you can just clone recursively: +[bd-linux]: docs/building/linux.md +[bd-windows]: docs/building/windows-core.md +[bd-macOS]: docs/building/macos.md +[FAQ]: docs/FAQ.md -```sh -git clone --recursive https://github.com/PowerShell/PowerShell.git -``` +## Downloading the Source Code -If you already cloned but forgot to use `--recursive`, you can update submodules manually: +You can clone the repository: ```sh -git submodule update --init +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. +For more information, see [working with the PowerShell repository](https://github.com/PowerShell/PowerShell/tree/master/docs/git). -## Developing and Contributing - -Please see the [Contribution Guide][] for how to develop and contribute. - -If you have any problems, please consult the [known issues][], developer [FAQ][], and [GitHub issues][]. -If you do not see your problem captured, please file a [new issue][] and follow the provided template. -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. +## Support -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. +For support, see the [Support Section][]. -[check out our FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package -[Contribution Guide]: https://github.com/PowerShell/PowerShell/tree/master/.github/CONTRIBUTING.md -[known issues]: https://github.com/PowerShell/PowerShell/tree/master/docs/KNOWNISSUES.md -[GitHub issues]: https://github.com/PowerShell/PowerShell/issues -[new issue]:https://github.com/PowerShell/PowerShell/issues/new +[Support Section]: https://github.com/PowerShell/PowerShell/tree/master/.github/SUPPORT.md ## Legal and Licensing @@ -203,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. -- [Window 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/en-us/services/application-insights/). -To opt-out of sending telemetry, delete the file `DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY` before starting PowerShell from the installed location. -The telemetry we collect fall under the [Microsoft Privacy Statement](https://privacy.microsoft.com/en-us/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]: http://opensource.microsoft.com/codeofconduct/ -[conduct-FAQ]: http://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 new file mode 100644 index 00000000000..e10c02bdd12 --- /dev/null +++ b/Settings.StyleCop @@ -0,0 +1,220 @@ + + + + + + + False + + + + + False + + + + + + False + + + + + + False + + + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + True + + + + + True + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + + False + + + + + + False + + + + + + False + + + + + False + + + + + + as + at + by + do + go + if + in + is + it + no + of + on + or + to + n + r + l + i + io + fs + lp + dw + h + rs + ps + op + my + sb + vt + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + False + + + + + + + + + + + False + + + + + + False + + + + + + + diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 971899c4e05..4d033a0f682 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -1,15 +1,4110 @@ +NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. +Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, +or you may send a check or money order for US $5.00, including the product name, +the open source component name, platform, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +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. + +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 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Humanizer.Core 2.14.1 - MIT + + +Copyright .NET Foundation and Contributors +Copyright (c) .NET Foundation and Contributors + +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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Json.More.Net 2.1.1 - MIT + + +Copyright (c) .NET Foundation and Contributors + +MIT License + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +JsonPointer.Net 5.3.1 - MIT + + +Copyright (c) .NET Foundation and Contributors + +MIT License + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +JsonSchema.Net 7.4.0 - MIT + + +Copyright (c) .NET Foundation and Contributors + +MIT License + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION 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.23.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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Bcl.AsyncInterfaces 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.Common 5.0.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors + +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.CodeAnalysis.CSharp 5.0.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation +ACopyright (c) Microsoft Corporation +CCopyright (c) Microsoft Corporation +DCopyright (c) Microsoft Corporation +OCopyright (c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors + +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.Extensions.ObjectPool 10.0.3 - MIT + + +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) + +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.PowerShell.MarkdownRender 7.2.1 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Markdown Rendering project PowerShell Markdown Renderer + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Security.Extensions 1.4.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.Registry.AccessControl 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.SystemEvents 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.Compatibility 10.0.3 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Newtonsoft.Json 13.0.4 - MIT + + +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 +Copyright James Newton-King 2008 Json.NET + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-arm.runtime.native.System.IO.Ports 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.android-x86.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-arm.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-musl-arm.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-musl-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +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) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.osx-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.osx-x64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.CodeDom 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.ComponentModel.Composition 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.ComponentModel.Composition.Registration 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Configuration.ConfigurationManager 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Data.Odbc 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Data.OleDb 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.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 + + +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.Diagnostics.PerformanceCounter 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.DirectoryServices 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.DirectoryServices.AccountManagement 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.DirectoryServices.Protocols 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Drawing.Common 10.0.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) Sven Groot (Ookii.org) 2009 +Copyright (c) .NET Foundation and Contributors + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.IO.Packaging 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Management 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Net.Http.WinHttpHandler 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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: -THIRD-PARTY SOFTWARE NOTICES AND INFORMATION -Do Not Translate or Localize +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Reflection.Context 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.Runtime.Caching 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Security.Cryptography.Pkcs 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Security.Cryptography.ProtectedData 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Security.Cryptography.Xml 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.Security.Permissions 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.ServiceModel.Http 10.0.652802 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.NetFramingBase 10.0.652802 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.NetTcp 10.0.652802 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Primitives 10.0.652802 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- -The software is based on or incorporates material from the projects listed below (collectively, “Third Party Code”). Microsoft is not the original author of the Third Party Code. The original copyright notice and license, under which Microsoft received such Third Party Code, are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. +--------------------------------------------------------- +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 + + +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.Speech 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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.Web.Services.Description 8.1.2 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Windows.Extensions 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 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. + +--------------------------------------------------------- + + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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.Management.Infrastructure.Runtime.Unix +Microsoft.Management.Infrastructure +------------------------------------------------- + +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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 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. + +-------------------------------------------------------- +• 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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. + +------------------------------------------------- +PowerShellGet +------------------------------------------------- + +Copyright (c) Microsoft Corporation + +All rights reserved. + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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/lzybkr/PSReadLine +https://github.com/PowerShell/PSReadLine Copyright (c) 2013, Jason Shirk @@ -18,13 +4113,13 @@ 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: +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. + 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. + 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 @@ -37,42 +4132,29 @@ 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 ----------------------------------------------- - -http://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. - - ------------------------------------------------- -File: PackageManagement +ThreadJob ------------------------------------------------- -Copyright (c) Microsoft Corporation. +Copyright (c) 2018 Paul Higinbotham -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: -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at 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 -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index c997322a085..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,28 +0,0 @@ -# version is set in tools\appveyor.psm1 - Invoke-AppVeyorInstall - -image: Visual Studio 2017 - -# cache version - netcoreapp.2.0.5-sdk.2.1.4 -cache: - - '%LocalAppData%\Microsoft\dotnet -> appveyor.yml' - - '%HOMEDRIVE%%HOMEPATH%\.nuget\packages -> appveyor.yml' - -nuget: - project_feed: true - -install: - - git submodule update --init - - ps: Import-Module .\tools\Appveyor.psm1 - - ps: Invoke-AppveyorInstall - -build_script: - - ps: Invoke-AppveyorBuild - -test_script: - - ps: Invoke-AppveyorTest - -after_test: - - ps: Invoke-AppVeyorAfterTest - -on_finish: - - ps: Invoke-AppveyorFinish diff --git a/assets/AppImageThirdPartyNotices.txt b/assets/AppImageThirdPartyNotices.txt deleted file mode 100644 index 0eaf1e0cd58..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 - # http://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: http://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 new file mode 100644 index 00000000000..dfcd95935d9 --- /dev/null +++ b/assets/AppxManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + $DISPLAYNAME$ + Microsoft Corporation + assets\StoreLogo.png + disabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/Chibi_Avatar.png b/assets/Chibi_Avatar.png new file mode 100644 index 00000000000..bbcf9569bb2 Binary files /dev/null and b/assets/Chibi_Avatar.png differ diff --git a/assets/Chibi_Avatar.svg b/assets/Chibi_Avatar.svg new file mode 100644 index 00000000000..ac678777be2 --- /dev/null +++ b/assets/Chibi_Avatar.svg @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 new file mode 100644 index 00000000000..ee3c725b1bd --- /dev/null +++ b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 @@ -0,0 +1,88 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.Synopsis + Group Policy tools use administrative template files (.admx, .adml) to populate policy settings in the user interface. + This allows administrators to manage registry-based policy settings. + This script installes PowerShell Core Administrative Templates for Windows. +.Notes + The PowerShellCoreExecutionPolicy.admx and PowerShellCoreExecutionPolicy.adml files are + expected to be at the location specified by the Path parameter with default value of the location of this script. +#> +[CmdletBinding()] +param +( + [ValidateNotNullOrEmpty()] + [string] $Path = $PSScriptRoot +) +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +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") +} +$IsWindowsOs = $PSHOME.EndsWith('\WindowsPowerShell\v1.0', [System.StringComparison]::OrdinalIgnoreCase) -or $IsWindows + +if (-not $IsWindowsOs) +{ + throw 'This script must be run on Windows.' +} + +if (-not (Test-Elevated)) +{ + throw 'This script must be run from an elevated process.' +} + +if ([System.Management.Automation.Platform]::IsNanoServer) +{ + throw 'Group policy definitions are not supported on Nano Server.' +} + +$admxName = 'PowerShellCoreExecutionPolicy.admx' +$admlName = 'PowerShellCoreExecutionPolicy.adml' +$admx = Get-Item -Path (Join-Path -Path $Path -ChildPath $admxName) +$adml = Get-Item -Path (Join-Path -Path $Path -ChildPath $admlName) +$admxTargetPath = Join-Path -Path $env:WINDIR -ChildPath "PolicyDefinitions" +$admlTargetPath = Join-Path -Path $admxTargetPath -ChildPath "en-US" + +$files = @($admx, $adml) +foreach ($file in $files) +{ + if (-not (Test-Path -Path $file)) + { + throw "Could not find $($file.Name) at $Path" + } +} + +Write-Verbose "Copying $admx to $admxTargetPath" +Copy-Item -Path $admx -Destination $admxTargetPath -Force +$admxTargetFullPath = Join-Path -Path $admxTargetPath -ChildPath $admxName +if (Test-Path -Path $admxTargetFullPath) +{ + Write-Verbose "$admxName was installed successfully" +} +else +{ + Write-Error "Could not install $admxName" +} + +Write-Verbose "Copying $adml to $admlTargetPath" +Copy-Item -Path $adml -Destination $admlTargetPath -Force +$admlTargetFullPath = Join-Path -Path $admlTargetPath -ChildPath $admlName +if (Test-Path -Path $admlTargetFullPath) +{ + Write-Verbose "$admlName was installed successfully" +} +else +{ + Write-Error "Could not install $admlName" +} diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml new file mode 100644 index 00000000000..3068ae57a24 --- /dev/null +++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml @@ -0,0 +1,125 @@ + + + PowerShell Core + This file contains the configuration options for PowerShell Core + + + Allow all scripts + Allow only signed scripts + Turn on Script Execution + This policy setting lets you configure the script execution policy, controlling which scripts are allowed to run. + +If you enable this policy setting, the scripts selected in the drop-down list are allowed to run. + +The "Allow only signed scripts" policy setting allows scripts to execute only if they are signed by a trusted publisher. + +The "Allow local scripts and remote signed scripts" policy setting allows any local scrips to run; scripts that originate from the internet must be signed by a trusted publisher. + +The "Allow all scripts" policy setting allows all scripts to run. + +If you disable this policy setting, no scripts are allowed to run. + +Note: This policy setting exists under both "Computer Configuration" and "User Configuration" in the Local Group Policy Editor. The "Computer Configuration" has precedence over "User Configuration." + +If you disable or do not configure this policy setting, it reverts to a per-machine preference setting; the default if that is not configured is "Allow local scripts and remote signed scripts." + PowerShell Core + Allow local scripts and remote signed scripts + At least Microsoft Windows 7 or Windows Server 2008 family + + Turn on Module Logging + + This policy setting allows you to turn on logging for PowerShell Core modules. + + If you enable this policy setting, pipeline execution events for members of the specified modules are recorded in the PowerShell Core log in Event Viewer. Enabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to True. + + If you disable this policy setting, logging of execution events is disabled for all PowerShell Core modules. Disabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to False. + + If this policy setting is not configured, the LogPipelineExecutionDetails property of a module determines whether the execution events of a module are logged. By default, the LogPipelineExecutionDetails property of all modules is set to False. + + To add modules to the policy setting list, click Show, and then type the module names in the list. The modules in the list must be installed on the computer. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Turn on PowerShell Transcription + + This policy setting lets you capture the input and output of PowerShell Core commands into text-based transcripts. + + If you enable this policy setting, PowerShell Core will enable transcription logging for PowerShell Core and any other + applications that leverage the PowerShell Core engine. By default, PowerShell Core will record transcript output to each users' My Documents + directory, with a file name that includes 'PowerShell_transcript', along with the computer name and time started. Enabling this policy is equivalent + to calling the Start-Transcript cmdlet on each PowerShell Core session. + + If you disable this policy setting, transcription logging of PowerShell-based applications is disabled by default, although transcripting can still be enabled + through the Start-Transcript cmdlet. + + If you use the OutputDirectory setting to enable transcription logging to a shared location, be sure to limit access to that directory to prevent users + from viewing the transcripts of other users or computers. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Turn on PowerShell Script Block Logging + + This policy setting enables logging of all PowerShell script input to the Microsoft-Windows-PowerShell/Operational event log. If you enable this policy setting, + PowerShell Core will log the processing of commands, script blocks, functions, and scripts - whether invoked interactively, or through automation. + + If you disable this policy setting, logging of PowerShell script input is disabled. + + If you enable the Script Block Invocation Logging, PowerShell additionally logs events when invocation of a command, script block, function, or script + starts or stops. Enabling Invocation Logging generates a high volume of event logs. + + Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + + Set the default source path for Update-Help + This policy setting allows you to set the default value of the SourcePath parameter on the Update-Help cmdlet. + +If you enable this policy setting, the Update-Help cmdlet will use the specified value as the default value for the SourcePath parameter. This default value can be overridden by specifying a different value with the SourcePath parameter on the Update-Help cmdlet. + +If this policy setting is disabled or not configured, this policy setting does not set a default value for the SourcePath parameter of the Update-Help cmdlet. + +Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting. + + Console session configuration + Specifies a configuration endpoint in which PowerShell is run. This can be any endpoint registered on the local machine including the default PowerShell remoting endpoints or a custom endpoint having specific user role capabilities. + + + + + + Use Windows PowerShell Policy setting. + Execution Policy + + + Use Windows PowerShell Policy setting. + To turn on logging for one or more modules, click Show, and then type the module names in the list. Wildcards are supported. + Module Names + To turn on logging for the PowerShell Core core modules, type the following module names in the list: + Microsoft.PowerShell.* + Microsoft.WSMan.Management + + + Use Windows PowerShell Policy setting. + + Include invocation headers: + + + Use Windows PowerShell Policy setting. + Log script block invocation start / stop events: + + + Use Windows PowerShell Policy setting. + + + + + + + + + + + + + diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx new file mode 100644 index 00000000000..0622a8d2695 --- /dev/null +++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AllSigned + + + + + RemoteSigned + + + + + Unrestricted + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/Powershell-preview.icns b/assets/Powershell-preview.icns new file mode 100644 index 00000000000..a0ed0c4bc2c Binary files /dev/null and b/assets/Powershell-preview.icns differ diff --git a/assets/Product.wxs b/assets/Product.wxs deleted file mode 100644 index b10e6993b9f..00000000000 --- a/assets/Product.wxs +++ /dev/null @@ -1,313 +0,0 @@ - - - = 601" ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - shortcutPath = Session.Property("ShortcutPath") - Set fso = CreateObject("Scripting.FileSystemObject") - if (fso.FileExists(shortcutPath)) Then - Set wshShell = CreateObject("WSCript.Shell") - Set shortcut = wshShell.CreateShortcut(shortcutPath) - - shortcut.workingdirectory = "%HOMEDRIVE%%HOMEPATH%" - shortcut.save - End If - - - - - - - - - - - - - - - - - - - - 1 - - - LAUNCHAPPONEXIT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NOT Installed OR NOT REMOVE OR UPGRADINGPRODUCTCODE - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - "1"]]> - - 1 - - NOT Installed - Installed AND PATCH - - 1 - LicenseAccepted = "1" - - 1 - 1 - - 1 - 1 - NOT WIXUI_DONTVALIDATEPATH - "1"]]> - WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" - 1 - 1 - 1 - - NOT Installed - Installed AND NOT PATCH - Installed AND PATCH - - 1 - - 1 - 1 - 1 - - - - - - - diff --git a/assets/Square150x150Logo-Preview.png b/assets/Square150x150Logo-Preview.png new file mode 100644 index 00000000000..e206cf8064f Binary files /dev/null and b/assets/Square150x150Logo-Preview.png differ diff --git a/assets/Square150x150Logo.png b/assets/Square150x150Logo.png new file mode 100644 index 00000000000..bba1843f120 Binary files /dev/null and b/assets/Square150x150Logo.png differ diff --git a/assets/Square44x44Logo-Preview.png b/assets/Square44x44Logo-Preview.png new file mode 100644 index 00000000000..df150a063b9 Binary files /dev/null and b/assets/Square44x44Logo-Preview.png differ diff --git a/assets/Square44x44Logo.png b/assets/Square44x44Logo.png new file mode 100644 index 00000000000..16b1f6dd160 Binary files /dev/null and b/assets/Square44x44Logo.png differ diff --git a/assets/Square44x44Logo.targetsize-48-Preview.png b/assets/Square44x44Logo.targetsize-48-Preview.png new file mode 100644 index 00000000000..df150a063b9 Binary files /dev/null and b/assets/Square44x44Logo.targetsize-48-Preview.png differ diff --git a/assets/Square44x44Logo.targetsize-48.png b/assets/Square44x44Logo.targetsize-48.png new file mode 100644 index 00000000000..16b1f6dd160 Binary files /dev/null and b/assets/Square44x44Logo.targetsize-48.png differ diff --git a/assets/Square44x44Logo.targetsize-48_altform-unplated-Preview.png b/assets/Square44x44Logo.targetsize-48_altform-unplated-Preview.png new file mode 100644 index 00000000000..df150a063b9 Binary files /dev/null and b/assets/Square44x44Logo.targetsize-48_altform-unplated-Preview.png differ diff --git a/assets/Square44x44Logo.targetsize-48_altform-unplated.png b/assets/Square44x44Logo.targetsize-48_altform-unplated.png new file mode 100644 index 00000000000..16b1f6dd160 Binary files /dev/null and b/assets/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/assets/StoreLogo-Preview.png b/assets/StoreLogo-Preview.png new file mode 100644 index 00000000000..8c1fa58568f Binary files /dev/null and b/assets/StoreLogo-Preview.png differ diff --git a/assets/StoreLogo.png b/assets/StoreLogo.png new file mode 100644 index 00000000000..afd20a3d9d4 Binary files /dev/null and b/assets/StoreLogo.png differ diff --git a/assets/WixUIBannerBmp.bmp b/assets/WixUIBannerBmp.bmp deleted file mode 100644 index 92248d4a524..00000000000 Binary files a/assets/WixUIBannerBmp.bmp and /dev/null differ diff --git a/assets/WixUIDialogBmp.bmp b/assets/WixUIDialogBmp.bmp deleted file mode 100644 index 81e8546d572..00000000000 Binary files a/assets/WixUIDialogBmp.bmp and /dev/null differ diff --git a/assets/WixUIInfoIco.bmp b/assets/WixUIInfoIco.bmp deleted file mode 100644 index af892ffe887..00000000000 Binary files a/assets/WixUIInfoIco.bmp and /dev/null differ diff --git a/assets/additionalAttributions.txt b/assets/additionalAttributions.txt new file mode 100644 index 00000000000..6676ca99cf5 --- /dev/null +++ b/assets/additionalAttributions.txt @@ -0,0 +1,192 @@ + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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.Management.Infrastructure.Runtime.Unix +Microsoft.Management.Infrastructure +------------------------------------------------- + +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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 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. + +-------------------------------------------------------- +• 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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. + +------------------------------------------------- +PowerShellGet +------------------------------------------------- + +Copyright (c) Microsoft Corporation + +All rights reserved. + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or 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/license.rtf b/assets/license.rtf deleted file mode 100644 index 0e507920b40..00000000000 --- a/assets/license.rtf +++ /dev/null @@ -1,1248 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;} -{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;} -{\f11\fbidi \froman\fcharset128\fprq1{\*\panose 02020609040205080304}MS Mincho{\*\falt \'82\'6c\'82\'72 \'96\'be\'92\'a9};}{\f34\fbidi \froman\fcharset1\fprq2{\*\panose 02040503050406030204}Cambria Math;} -{\f39\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\f40\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Tahoma;}{\f41\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0603020202020204}Trebuchet MS;} -{\f42\fbidi \fswiss\fcharset0\fprq2{\*\panose 00000000000000000000}Segoe UI;}{\f43\fbidi \froman\fcharset128\fprq1{\*\panose 02020609040205080304}@MS Mincho;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} -{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} -{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f44\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f45\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f47\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f48\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f49\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f50\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\f51\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f52\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f64\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f65\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;} -{\f67\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f68\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f69\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f70\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);} -{\f71\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f72\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f434\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f435\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} -{\f437\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f438\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f439\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f440\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} -{\f441\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f442\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\f444\fbidi \fswiss\fcharset238\fprq2 Tahoma CE;}{\f445\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr;} -{\f447\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek;}{\f448\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur;}{\f449\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f450\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic);} -{\f451\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic;}{\f452\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese);}{\f453\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai);}{\f454\fbidi \fswiss\fcharset238\fprq2 Trebuchet MS CE;} -{\f455\fbidi \fswiss\fcharset204\fprq2 Trebuchet MS Cyr;}{\f457\fbidi \fswiss\fcharset161\fprq2 Trebuchet MS Greek;}{\f458\fbidi \fswiss\fcharset162\fprq2 Trebuchet MS Tur;}{\f461\fbidi \fswiss\fcharset186\fprq2 Trebuchet MS Baltic;} -{\f464\fbidi \fswiss\fcharset238\fprq2 Segoe UI CE;}{\f465\fbidi \fswiss\fcharset204\fprq2 Segoe UI Cyr;}{\f467\fbidi \fswiss\fcharset161\fprq2 Segoe UI Greek;}{\f468\fbidi \fswiss\fcharset162\fprq2 Segoe UI Tur;} -{\f469\fbidi \fswiss\fcharset177\fprq2 Segoe UI (Hebrew);}{\f470\fbidi \fswiss\fcharset178\fprq2 Segoe UI (Arabic);}{\f471\fbidi \fswiss\fcharset186\fprq2 Segoe UI Baltic;}{\f472\fbidi \fswiss\fcharset163\fprq2 Segoe UI (Vietnamese);} -{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;} -{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);} -{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);} -{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red51\green51\blue51;}{\*\defchp \f31506\fs22 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 -\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 -\ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\s1\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext1 \slink22 \sqformat \styrsid7813854 heading 1;}{ -\s2\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext2 \slink23 \sqformat \styrsid7813854 heading 2;}{\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 -\af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext3 \slink24 \sqformat \styrsid7813854 heading 3;}{\s4\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar -\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext4 \slink25 \sqformat \styrsid7813854 heading 4;}{\s5\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar\tx1792\jclisttab\tx2155\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl4\outlinelevel4\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 -\af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext5 \slink26 \sqformat \styrsid7813854 heading 5;}{\s6\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar -\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl5\outlinelevel5\adjustright\rin0\lin2149\itap0 \rtlch\fcs1 \af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext6 \slink27 \sqformat \styrsid7813854 heading 6;}{\s7\ql \fi-357\li2506\ri0\sb120\sa120\widctlpar\jclisttab\tx2509\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl6\outlinelevel6\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 -\af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext7 \slink28 \sqformat \styrsid7813854 heading 7;}{\s8\ql \fi-357\li2863\ri0\sb120\sa120\widctlpar -\jclisttab\tx2866\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl7\outlinelevel7\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 \af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext8 \slink29 \sqformat \styrsid7813854 heading 8;}{\s9\ql \fi-358\li3221\ri0\sb120\sa120\widctlpar\jclisttab\tx3223\wrapdefault\aspalpha\aspnum\faauto\ls12\ilvl8\outlinelevel8\adjustright\rin0\lin3221\itap0 \rtlch\fcs1 -\af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext9 \slink30 \sqformat \styrsid7813854 heading 9;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 -Default Paragraph Font;}{\*\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv -\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused -Normal Table;}{\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext15 \ssemihidden \sunhideused \styrsid3804850 Normal (Web);}{\s16\ql \li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af39\afs22\alang1025 \ltrch\fcs0 -\f39\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext16 \sqformat \spriority34 \styrsid6173475 List Paragraph;}{\s17\ql \li0\ri0\widctlpar -\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af2\afs20\alang1025 \ltrch\fcs0 -\f2\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 \slink18 \ssemihidden \sunhideused \styrsid6573559 HTML Preformatted;}{\*\cs18 \additive \rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20 -\sbasedon10 \slink17 \slocked \ssemihidden \styrsid6573559 HTML Preformatted Char;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf1 \sbasedon10 \sunhideused \styrsid7092439 Hyperlink;}{\*\cs20 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 -\sbasedon10 \spriority0 \styrsid7092439 spelle;}{\*\cs21 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \sbasedon10 \spriority0 \styrsid7092439 grame;}{\*\cs22 \additive \rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \fs19\loch\f40\hich\af40\dbch\af11 -\sbasedon10 \slink1 \slocked \styrsid7813854 Heading 1 Char;}{\*\cs23 \additive \rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink2 \slocked \styrsid7813854 Heading 2 Char;}{\*\cs24 \additive \rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink3 \slocked \styrsid7813854 Heading 3 Char;}{\*\cs25 \additive \rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 -\sbasedon10 \slink4 \slocked \styrsid7813854 Heading 4 Char;}{\*\cs26 \additive \rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink5 \slocked \styrsid7813854 Heading 5 Char;}{\*\cs27 \additive \rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink6 \slocked \styrsid7813854 Heading 6 Char;}{\*\cs28 \additive \rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 -\sbasedon10 \slink7 \slocked \styrsid7813854 Heading 7 Char;}{\*\cs29 \additive \rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink8 \slocked \styrsid7813854 Heading 8 Char;}{\*\cs30 \additive \rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \b\fs19\loch\f40\hich\af40\dbch\af11 \sbasedon10 \slink9 \slocked \styrsid7813854 Heading 9 Char;}{\s31\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af40\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext31 \styrsid7813854 Body 1;}{ -\s32\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af40\afs28\alang1025 \ltrch\fcs0 \fs28\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext0 \styrsid7813854 Heading EULA;}{\s33\ql \li0\ri0\sb120\sa120\widctlpar\brdrb\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af40\afs28\alang1025 \ltrch\fcs0 -\fs28\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \styrsid7813854 Heading Software Title;}{\s34\ql \li0\ri0\sb120\sa120\widctlpar\brdrt\brdrs\brdrw10\brsp20 -\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext34 \styrsid7813854 -Preamble Border Above;}}{\*\listtable{\list\listtemplateid412752146\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 -\fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 } -{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel -\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23 -\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid256596791}{\list\listtemplateid-234466468 -\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23 -\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 -\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid268852893}{\list\listtemplateid2071779370\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers -;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid472724034}{\list\listtemplateid837583652\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers -;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li6480\lin6480 }{\listname ;}\listid678236712}{\list\listtemplateid468190476\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;} -\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 -\fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 } -{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname -;}\listid696808916}{\list\listtemplateid256027766\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 -\fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 } -{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel -\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23 -\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid833446968}{\list\listtemplateid812297174 -{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;} -\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2157\lin2157 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\hres0\chhres0 \fi-360\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 } -{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listname ;}\listid888110291}{\list\listtemplateid812297174{\listlevel\levelnfc4 -\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;} -\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2157\lin2157 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\hres0\chhres0 \fi-360\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 } -{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listname ;}\listid1282881056}{\list\listtemplateid827650700\listhybrid{\listlevel -\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 -{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid1470391773}{\list\listtemplateid-1331279738\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 } -{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid1543639483}{\list\listtemplateid812297174{\listlevel\levelnfc4\levelnfcn4 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 -{\leveltext\'02\'02);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'03);}{\levelnumbers -\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\hres0\chhres0 \fi-360\li2157\lin2157 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2517\lin2517 } -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listname ;}\listid1670060985}{\list\listtemplateid-1676387632{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af40\afs20 \ltrch\fcs0 \b\i0\f40\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b\i0\f3\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af40\afs20 \ltrch\fcs0 \b\i0\f40\fs20\fbias0\hres0\chhres0 \s3\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af41\afs20 \ltrch\fcs0 \b0\i0\strike0\f41\fs20\ulnone\fbias0\hres0\chhres0 \s4\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1 -\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af41\afs20 \ltrch\fcs0 \b0\i0\strike0\f41\fs20\ulnone\fbias0\hres0\chhres0 \s5\fi-357\li1792\jclisttab\tx2155\lin1792 } -{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af41\afs20 \ltrch\fcs0 \b0\i0\f41\fs20\fbias0\hres0\chhres0 \s6\fi-357\li2149 -\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af41\afs20 \ltrch\fcs0 \b0\i0\f41\fs20\fbias0\hres0\chhres0 -\s7\fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af41\afs20 \ltrch\fcs0 -\b0\i0\f41\fs20\fbias0\hres0\chhres0 \s8\fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af41\afs20 -\ltrch\fcs0 \b0\i0\f41\fs20\fbias0\hres0\chhres0 \s9\fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid1743720866}{\list\listtemplateid-646571904\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 -\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers -;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid2003921709}{\list\listtemplateid1067461628\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 -\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers -;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li6480\lin6480 }{\listname ;}\listid2022782249}{\list\listtemplateid1736062262\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 -\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;} -\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} -\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} -\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 -\fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 -\fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 -\fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 } -{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname -;}\listid2055036124}{\list\listtemplateid812297174{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\hres0\chhres0 -\fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1077\lin1077 }{\listlevel -\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 -\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1797\lin1797 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 -\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2157\lin2157 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext -\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2517\lin2517 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;} -\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 -\hres0\chhres0 \fi-360\li3237\lin3237 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 } -{\listname ;}\listid2106000387}}{\*\listoverridetable{\listoverride\listid256596791\listoverridecount0\ls1}{\listoverride\listid1543639483\listoverridecount0\ls2}{\listoverride\listid268852893\listoverridecount0\ls3}{\listoverride\listid678236712 -\listoverridecount0\ls4}{\listoverride\listid2022782249\listoverridecount0\ls5}{\listoverride\listid472724034\listoverridecount0\ls6}{\listoverride\listid1470391773\listoverridecount0\ls7}{\listoverride\listid2003921709\listoverridecount0\ls8} -{\listoverride\listid2055036124\listoverridecount0\ls9}{\listoverride\listid696808916\listoverridecount0\ls10}{\listoverride\listid833446968\listoverridecount0\ls11}{\listoverride\listid1743720866\listoverridecount0\ls12}{\listoverride\listid1670060985 -\listoverridecount0\ls13}{\listoverride\listid1282881056\listoverridecount0\ls14}{\listoverride\listid888110291\listoverridecount0\ls15}{\listoverride\listid2106000387\listoverridecount0\ls16}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0 -\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp10\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li300\ri300\sb300\sa300}{\pgp\ipgp0\itap0\li0\ri0 -\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid93983\rsid217518\rsid1984289\rsid3804850\rsid5845909\rsid6173475\rsid6573559\rsid7092439\rsid7813854\rsid8005660\rsid8394862 -\rsid10169937\rsid10363382\rsid10624128\rsid13960513\rsid14557619\rsid16084641}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Duane Okamoto (CELA)} -{\operator Raghu Shantha}{\creatim\yr2016\mo8\dy12\hr14\min30}{\revtim\yr2016\mo8\dy12\hr14\min30}{\version2}{\edmins1}{\nofpages15}{\nofwords5899}{\nofchars33626}{\*\company Microsoft IT}{\nofcharsws39447}{\vern19}}{\*\xmlnstbl {\xmlns1 http://schemas.mi -crosoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen -\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 -\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct -\asianbrkrule\rsidroot3804850\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 -{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang -{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} -\pard\plain \ltrpar\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3804850 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \b\f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid93983\charrsid93983 PowerShell 6.0}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \b\f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 Copyright (c) Microsoft Corporation}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 All rights reserved.\~}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 MIT License}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software - without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following cond -itions:}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.}{\rtlch\fcs1 -\af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850\charrsid93983 THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRA -NTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT -OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid3804850 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs20\ul\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid10363382 IMPORTANT NOTICE}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid10363382 -: THE SOFTWARE ALSO CONTAINS THIRD PARTY AND OTHER }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid10624128 PROPRIETARY }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 -\f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid10363382 SOFTWARE THAT ARE GOVERNED BY SEPARATE LICENSE TERMS. BY ACCEPTING THE LICENSE TERMS ABOVE, -YOU ALSO ACCEPT THE LICENSE TERMS GOVERNING THE THIRD PARTY AND OTHER SOFTWARE, WHICH ARE SET FORTH BELOW: -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 The following components }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid16084641 listed }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 -\f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 are governed by }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid16084641 the license terms that follow }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 -\f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 the component}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid16084641 (s) name}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 -\f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 : -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6173475 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 --------------------------------------}{ -\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid10363382 ----------------}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14557619 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 Libmi.so -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6173475 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 --------------------------------------}{ -\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid10363382 ----------------}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -\par }\pard \ltrpar\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3804850 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -Copyright (c) Microsoft Corporation -\par }\pard \ltrpar\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6173475 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 -All rights reserved.\~}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 MIT License}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated docu -mentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Soft -ware is furnished to do so, subject to the following conditions:}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.}{\rtlch\fcs1 -\af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475\charrsid93983 THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, E -XPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -\par }\pard \ltrpar\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14557619 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619 -The following components are governed by the MIT license, a copy of which appears below the list of components: -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14557619 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619 ------------------------------------------------------- -\par Newtonsoft.Json -\par ------------------------------------------------------ -\par }\pard \ltrpar\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14557619 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619 -Copyright (c) 2007 James Newton-King -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 All rights reserved.\~}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 MIT License}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies o -f the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.}{\rtlch\fcs1 -\af2\afs20 \ltrch\fcs0 \f2\fs20\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619\charrsid93983 THE SOFTWAR -E IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 -\f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid14557619 -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6573559 ---------------------------------------------------------- -\par Libuv v.1.9.0 -\par --------------------------------------------------------- -\par }\pard\plain \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 \rtlch\fcs1 -\af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid10169937 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 https://raw.githubusercontent.com/aspnet/libuv-package/dev/content/License.txt -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs20\insrsid6573559 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 This software is licensed to you by Microsoft Corporation under the original terms of the copyright holder provided below: -\par -\par ========================================= -\par -\par libuv is part of the Node project: http://nodejs.org/ -\par libuv may be distributed alone under Node's license: -\par -\par ==== -\par -\par Copyright Joyent, Inc. and other Node contributors. All rights reserved. -\par -\par Permission is hereby granted, free of charge, to any person obtaining a copy -\par of this software and associated documentation files (the "Software"), to -\par deal in the Software without restriction, including without limitation the -\par rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -\par sell copies of the Software, and to permit persons to whom the Software is -\par furnished to do so, subject to the following conditions: -\par -\par The above copyright notice and this permission notice shall be included in -\par all copies or substantial portions of the Software. -\par -\par THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -\par IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -\par FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -\par AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -\par LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -\par FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -\par IN THE SOFTWARE. -\par -\par ==== -\par -\par This license applies to all parts of libuv that are not externally -\par maintained libraries. -\par -\par The externally maintained libraries used by libuv are: -\par -\par - tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license. -\par -\par - inet_pton and inet_ntop implementations, contained in src/inet.c, are -\par }\pard \ltrpar\ql \li450\ri0\widctlpar\tx450\tx1530\tx2700\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin450\itap0\pararsid6573559 {\rtlch\fcs1 -\af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 copyright the Internet Systems Consortium, Inc., and licensed under the ISC license. -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 {\rtlch\fcs1 -\af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 -\par - stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three -\par clause BSD license. -\par -\par - pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile -\par Communications AB. Three clause BSD license. -\par -\par }\pard \ltrpar\ql \fi-450\li450\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin450\itap0\pararsid6573559 { -\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 - android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 {\rtlch\fcs1 -\af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 n\'b0 289016). Three clause BSD license. -\par -\par ========================================= -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs18 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6573559 -\par }{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid10169937 -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6573559 ---------------------------------------------------}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid10169937 --}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6573559 -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard\plain \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls11\adjustright\rin0\lin720\itap0\pararsid10169937 \rtlch\fcs1 \af39\afs22\alang1025 \ltrch\fcs0 \f39\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs18 -\ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 dotnet-test-xunit 2.2.0-preview2-build1029 -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}{\rtlch\fcs1 \af2 \ltrch\fcs0 \f2\fs18\insrsid16084641\charrsid10169937 xunit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.abstractions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.assert -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.core -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.extensibility.core -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.extensibility.execution -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.runner.reporters -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid16084641\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}xunit.runner.utility -\par }\pard\plain \ltrpar\ql \li0\ri0\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439 ---------------------------------------------------}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid10169937 -}{\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439 -\par }\pard \ltrpar\ql \li0\ri0\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid10169937 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid16084641 https://www.nuget.org/packages -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid10169937 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 Copyright 2015 Outercurve Foundation -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10169937 {\rtlch\fcs1 -\af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6573559 {\rtlch\fcs1 -\af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid6573559\charrsid6573559 Licensed under the Apache License, Version 2.0 (the "License"); -\par you may not use this file except in compliance with the License. -\par You may obtain a copy of the License at -\par -\par http://www.apache.org/licenses/LICENSE-2.0 -\par -\par Unless required by applicable law or agreed to in writing, software -\par distributed under the License is distributed on an "AS IS" BASIS, -\par WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -\par See the License for the specific language governing permissions and -\par limitations under the License.}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid6573559 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid7092439 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid6573559 -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs20 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 --------------------------------------------------}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6573559 -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard\plain \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls10\adjustright\rin0\lin720\itap0\pararsid10169937 \rtlch\fcs1 \af39\afs22\alang1025 \ltrch\fcs0 \f39\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs18 -\ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 Microsoft.CodeAnalysis.Analyzers -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.CodeAnalysis.Common -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.CodeAnalysis.CSharp -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.CodeAnalysis.VisualBasic -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.CSharp -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.DiaSymReader -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.DiaSymReader.Native -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.DotNet.Cli.Utils -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.DotNet.InternalAbstractions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.DotNet.ProjectModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Extensions.DependencyModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Extensions.Testing.Abstractions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.App -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Platforms -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Portable.Compatibility -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Runtime -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Runtime.Native -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Targets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.NETCore.Windows.ApiSets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.VisualBasic -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Win32.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls9\adjustright\rin0\lin720\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 Microsoft.Win32.Registry -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Win32.Registry.AccessControl -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NETStandard.Library -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Collections -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Diagnostics.Tools -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Diagnostics.Tracing -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Globalization -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Globalization.Calendars -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.IO -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Reflection -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Reflection.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Reflection.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Resources.ResourceManager -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Runtime -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Runtime.Handles -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Runtime.InteropServices -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Text.Encoding -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Text.Encoding.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Threading.Tasks -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.any.System.Threading.Timer -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.debian.8-x64.runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System.Data.SqlClient.sni -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.rhel.7-x64.runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls8\adjustright\rin0\lin720\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 -runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.runtime.native.System -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.Microsoft.Win32.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Console -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Diagnostics.Debug -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.IO.FileSystem -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Net.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Net.Sockets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Private.Uri -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.unix.System.Runtime.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.Microsoft.Win32.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.Console -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.Diagnostics.Debug -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.IO.FileSystem -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.Net.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.Net.Sockets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win.System.Runtime.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.DotNetHost -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.DotNetHostPolicy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.DotNetHostResolver -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.Jit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.Runtime.CoreCLR -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.Microsoft.NETCore.Windows.ApiSets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.runtime.native.System.Data.SqlClient.sni -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x64.runtime.native.System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7-x86.runtime.native.System.Data.SqlClient.sni -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win7.System.Private.Uri -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}runtime.win81-x64.Microsoft.NETCore.Windows.ApiSets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.AppContext -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Buffers -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Collections -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Collections.Concurrent -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Collections.Immutable -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Collections.NonGeneric -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Collections.Specialized -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ComponentModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ComponentModel.Annotations -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ComponentModel.EventBasedAsync -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ComponentModel.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ComponentModel.TypeConverter -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Console -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Data.Common -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Data.SqlClient -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.Contracts -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.Debug -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.DiagnosticSource -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.FileVersionInfo -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.Process -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.StackTrace -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.TextWriterTraceListener -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.Tools -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.TraceSource -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Diagnostics.Tracing -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Dynamic.Runtime -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Globalization -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Globalization.Calendars -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Globalization.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.Compression -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.Compression.ZipFile -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.FileSystem -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.FileSystem.AccessControl -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls7\adjustright\rin0\lin720\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 System.IO.FileSystem.DriveInfo -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.FileSystem.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.FileSystem.Watcher -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.MemoryMappedFiles -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.Packaging -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.Pipes -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.IO.UnmanagedMemoryStream -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Linq -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Linq.Expressions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Linq.Parallel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Linq.Queryable -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Http.WinHttpHandler -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.NameResolution -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.NetworkInformation -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Ping -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Requests -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.Sockets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.WebHeaderCollection -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.WebSockets -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Net.WebSockets.Client -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Numerics.Vectors -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ObjectModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Private.DataContractSerialization -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Private.ServiceModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Private.Uri -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.DispatchProxy -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Emit -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Emit.ILGeneration -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Emit.Lightweight -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Metadata -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Reflection.TypeExtensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Resources.Reader -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls6\adjustright\rin0\lin720\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 System.Resources.ResourceManager -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.CompilerServices.VisualC -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Handles -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.InteropServices -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.InteropServices.PInvoke -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.InteropServices.RuntimeInformation -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Loader -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Numerics -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Serialization.Json -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Serialization.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Runtime.Serialization.Xml -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.AccessControl -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Claims -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Algorithms -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Cng -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Csp -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Encoding -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.OpenSsl -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Pkcs -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Cryptography.X509Certificates -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Principal -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.Principal.Windows -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Security.SecureString -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ServiceModel.Duplex -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ServiceModel.Http -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ServiceModel.NetTcp -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ServiceModel.Primitives -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin720\itap0\pararsid10169937 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 System.ServiceModel.Security -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.ServiceProcess.ServiceController -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Text.Encoding -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Text.Encoding.CodePages -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Text.Encoding.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Text.Encodings.Web -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Text.RegularExpressions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.AccessControl -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Overlapped -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Tasks -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Tasks.Dataflow -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Tasks.Extensions -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Tasks.Parallel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Thread -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.ThreadPool -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Threading.Timer -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.ReaderWriter -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XDocument -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XmlDocument -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XmlSerializer -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XPath -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XPath.XDocument -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}System.Xml.XPath.XmlDocument -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs20 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 ------------------------------------------------------- -\par -\par }\pard\plain \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \ab\af40\afs24 \ltrch\fcs0 \b\f40\fs24\cf1\insrsid7092439\charrsid7092439 MICROSOFT SOFTWARE LICENSE TERMS }{\rtlch\fcs1 \ab\af40\afs28 \ltrch\fcs0 \b\f40\fs28\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs24 \ltrch\fcs0 \b\f40\fs24\cf1\insrsid7092439\charrsid7092439 MICROSOFT .NET LIBRARY }{\rtlch\fcs1 \ab\af40\afs28 \ltrch\fcs0 \b\f40\fs28\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They apply to the software named above, which includes the media on which you received it, if any. The terms -also apply to any Microsoft -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 updates,}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 supplements,}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 Internet-based services, and -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 support services -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -for this software, unless other terms accompany those items. If so, those terms apply. -\par BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT USE THE SOFTWARE. -\par IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW. -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 1.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 INSTALLATION AND USE RIGHTS. -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 a.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 Installation and Use.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 \~You may install and use any number of copies of the software to design, develop and test your programs. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 b.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 -\~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 Third Party Programs.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 \~ -The software may include third party programs that Microsoft, not the third party, licenses to you under this agreement. Notices, if any, for the third party program are included for your information only. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 -\b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 2.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 DATA.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 -\af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to improve our products and services.\~ -You can learn more about data collection and use in the help documentation and the privacy statement at\~ }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid11493340 - HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=528096&clcid=0x409" \\t "_blank" }}{\fldrslt {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\ul\cf2\insrsid7092439\charrsid7092439 http://go.microsoft.com/fwlink/?LinkId=528096}{\rtlch\fcs1 \af0\afs24 -\ltrch\fcs0 \f0\fs24\ul\cf1\insrsid7092439\charrsid7092439 }}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 -. Your use of the software operates as your consent to these practices. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 3.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 -ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 a.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 DISTRIBUTABLE CODE.\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 The software is comprised of Distributable Code. \'93Distributable Code\'94 is code that you are permitted to distribute in programs you develop if you comply with the terms below. }{\rtlch\fcs1 \ab\af40\afs19 -\ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 i}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 .}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 -\b\f40\fs20\cf1\insrsid7092439\charrsid7092439 Right to Use and Distribute.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1434\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1434\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 You may copy and distribute the object code form of the software. }{\rtlch\fcs1 \af40\afs19 -\ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 ii.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 Distribution Requirements.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 \~}{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 For any Distributable Code you distribute, you must }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1434\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1434\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 add significant primary functionality to it in your programs; }{\rtlch\fcs1 \af40\afs19 -\ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 require distributors and external end users to agree to terms that protect it at least as much as this agreement; }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 display your valid copyright notice on your programs; and }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\rquote fees, related to the distribution or use of your programs. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 iii.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 Distribution Restrictions.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 \~}{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 You may not}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 -\ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li1434\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1434\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 alter any copyright, trademark or patent notice in the Distributable Code; }{\rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 use Microsoft\rquote s trademarks in your programs\rquote names or in a way that suggests your programs come from or are endorsed by Microsoft; }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 include Distributable Code in malicious, deceptive or unlawful programs; or }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 - modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that }{\rtlch\fcs1 \af40\afs19 -\ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-358\li1792\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1792\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\cf1\insrsid7092439\charrsid7092439 the code be disclosed or distributed in source code form; or }{\rtlch\fcs1 \af40\afs19 -\ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f3\fs20\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 -\f40\fs20\cf1\insrsid7092439\charrsid7092439 others have the right to modify it. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 4.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 SCOPE OF LICENSE.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitt -ed in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to use it in certain ways. You may not }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 - -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 work around any technical limitations in the software; -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation; -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 publish the software for others to copy; -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 rent, lease or lend the software; -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 transfer the software or this agreement to any third party; or -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 use the software for commercial software hosting services. -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 5.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 BACKUP COPY.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 You may make one backup copy of the software. You may use it only to reinstall the software. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 -\b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 6.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -DOCUMENTATION.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -Any person that has valid access to your computer or internal network may copy and use the documentation for your internal, reference purposes. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 7.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -EXPORT RESTRICTIONS.\~ }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These laws include restrictions on destin -ations, end users and end use. For additional information, see\~ }{\rtlch\fcs1 \af40\afs20 \ltrch\fcs0 \f40\fs20\kerning36\insrsid7092439\charrsid7092439 www.microsoft.com/exporting}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 .}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 8.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -SUPPORT SERVICES.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 Because this software is \'93as is,\'94 - we may not provide support services for it. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 9.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -ENTIRE AGREEMENT.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -This agreement, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 -\b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 10.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -APPLICABLE LAW.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 a.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{ -\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 United States.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -If you acquired the software in the United States, Washington state law governs the interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws principles. The laws of the state where you live govern all other c -laims, including claims under state consumer protection laws, unfair competition laws, and in tort. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\insrsid7092439\charrsid7092439 b.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 -\~\~\~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 -Outside the United States. If you acquired the software in any other country, the laws of that country apply. -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 11.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 LEGAL EFFECT.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agreement does not change your rights under the laws of y -our country if the laws of your country do not permit it to do so. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 12.}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 -\f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \'93AS-IS.\'94 YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. YOU MAY HAVE ADDITIONAL CONS -UMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMEN -T. -\par }\pard \ltrpar\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\insrsid7092439\charrsid7092439 FOR AUSTRALIA \endash - YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\f40\fs20\cf1\kerning36\insrsid7092439\charrsid7092439 13.}{ -\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\kerning36\insrsid7092439\charrsid7092439 \~\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 -\b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS -ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. -\par }\pard \ltrpar\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 This limitation applies to}{\rtlch\fcs1 -\af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 -\ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 - anything related to the software, services, content (including code) on third party Internet sites, or third party programs; and -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 \af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by applicable law. -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential or other d -amages. -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French. }{\rtlch\fcs1 -\af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 Remarque : Ce logiciel \'e9tant distribu\'e9 au Qu\'e9bec, Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7ais. } -{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 EXON\'c9 -RATION DE GARANTIE.\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 Le logiciel vis\'e9 par une licence est offert \'ab - tel quel \'bb. Toute utilisation de ce logiciel est \'e0 votre seule risque et p\'e9ril. Microsoft n\rquote accorde aucune autre garantie expresse. Vous pouvez b\'e9n\'e9ficier de droits - additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9 marchande, d\rquote ad\'e9quation \'e0 - un usage particulier et d\rquote absence de contrefa\'e7on sont exclues. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par LIMITATION DES DOMMAGES-INT\'c9R\'caTS ET EXCLUSION DE RESPONSABILIT\'c9 POUR LES DOMMAGES.\~ }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 Vous}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 -\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0 - hauteur de 5,00 $ US. Vous ne pouvez pr\'e9tendre \'e0 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9ciaux, indirects ou accessoires et pertes de b\'e9n\'e9fices. }{\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 -\b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 Cette}{\rtlch\fcs1 -\af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 limitationconcerne:}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 -\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li720\ri0\sb120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 -\af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 tout ce qui est reli\'e9 - au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li720\ri0\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f3\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 \'b7}{\rtlch\fcs1 -\af0\afs14 \ltrch\fcs0 \f0\fs14\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 \~\~\~\~\~\~\~\~\~}{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 les r\'e9 -clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9 stricte, de n\'e9gligence ou d\rquote une autre faute dans la limite autoris\'e9e par la loi en vigueur. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 Elle s\rquote -applique \'e9galement, m\'eame si Microsoft connaissait ou devrait conna\'eetre l\rquote \'e9ventualit\'e9 d\rquote un tel dommage. Si votre pays n\rquote autorise pas l\rquote exclusion ou la limitation de responsabilit\'e9 - pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\rquote exclusion ci-dessus ne s\rquote appliquera pas \'e0 votre \'e9gard. }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 -\f40\fs19\cf1\insrsid7092439\charrsid7092439 -\par }\pard \ltrpar\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0\pararsid7092439 {\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 EFFET JURIDIQUE. -\~}{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid7092439\charrsid7092439 }{\rtlch\fcs1 \af40\afs19 \ltrch\fcs0 \f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 Le pr\'e9sent contrat d\'e9 -crit certains droits juridiques. Vous pourriez avoir d\rquote autres droits pr\'e9vus par les lois de votre pays. Le pr\'e9sent contrat ne modifie pas les droits que vous conf\'e8rent les lois de votre pays si celles-ci ne le permettent pas. }{ -\rtlch\fcs1 \ab\af40\afs19 \ltrch\fcs0 \b\f40\fs19\cf1\kerning36\insrsid7092439\charrsid7092439 -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs20 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid16084641 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 -------------------------------------------------------- -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard\plain \ltrpar -\s16\ql \fi-360\li720\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin720\itap0\pararsid10169937 \rtlch\fcs1 \af39\afs22\alang1025 \ltrch\fcs0 \f39\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2 -\ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid10169937 NuGet.Common -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Configuration -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.DependencyResolver.Core -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Frameworks -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.LibraryModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Packaging -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Packaging.Core -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Packaging.Core.Types -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.ProjectModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Protocol.Core.Types -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Protocol.Core.v3 -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Repositories -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.RuntimeModel -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid7092439\charrsid10169937 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}NuGet.Versioning -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs20 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 ---------------------------------------------------------- -\par -\par }\pard\plain \ltrpar\ql \li0\ri0\widctlpar\tx916\tx1832\tx2748\tx3664\tx4580\tx5496\tx6412\tx7328\tx8244\tx9160\tx10076\tx10992\tx11908\tx12824\tx13740\tx14656\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 -\af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs18 \ltrch\fcs0 \f2\fs18\insrsid7092439\charrsid7092439 Copyright (c) .NET Foundation. All rights reserved. -\par -\par Licensed under the Apache License, Version 2.0 (the "License"); you may not use -\par these files except in compliance with the License. You may obtain a copy of the -\par License at -\par -\par http://www.apache.org/licenses/LICENSE-2.0 -\par -\par Unless required by applicable law or agreed to in writing, software distributed -\par under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -\par CONDITIONS OF ANY KIND, either express or implied. See the License for the -\par specific language governing permissions and limitations under the License. -\par }\pard\plain \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7092439 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2\afs18 -\ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439\charrsid7092439 -\par }{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid7092439 -\par }\pard \ltrpar\s15\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6173475 {\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -----------------------------------------}{ -\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid10363382 ----------------}{\rtlch\fcs1 \af2\afs20 \ltrch\fcs0 \f2\fs18\cf17\lang9\langfe1033\langnp9\insrsid6173475 -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid6173475\charrsid6173475 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}}\pard\plain \ltrpar -\s16\ql \fi-360\li540\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls2\ilvl2\adjustright\rin0\lin540\itap0\pararsid10363382 \rtlch\fcs1 \af39\afs22\alang1025 \ltrch\fcs0 \f39\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af2 -\ltrch\fcs0 \f2\fs18\insrsid6173475\charrsid6173475 Microsoft.Management.Infrastructure.dll -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid6173475\charrsid6173475 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Management.Infrastructure.Native.dll -\par {\listtext\pard\plain\ltrpar \s16 \rtlch\fcs1 \af2\afs22 \ltrch\fcs0 \f10\fs18\insrsid6173475\charrsid6173475 \loch\af10\dbch\af31506\hich\f10 \'a7\tab}Microsoft.Management.Infrastructure.Unmanaged.dll -\par }\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid10363382 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -{\rtlch\fcs1 \af2 \ltrch\fcs0 \f2\fs18\insrsid10363382 ----------------------------------------------------------}{\rtlch\fcs1 \af2 \ltrch\fcs0 \f2\fs18\insrsid10363382\charrsid10363382 -\par }\pard\plain \ltrpar\s32\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs28\alang1025 \ltrch\fcs0 -\fs28\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 MICROSOFT SOFTWARE LICENSE TERMS -\par }\pard\plain \ltrpar\s33\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs28\alang1025 \ltrch\fcs0 -\fs28\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\fs24\insrsid7813854\charrsid6843334 \hich\af42\dbch\af11\loch\f42 MANAGEMENT.INFRASTRUCTURE.DLL }{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\fs24\insrsid7813854 -\par }{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\fs24\insrsid7813854\charrsid6843334 \hich\af42\dbch\af11\loch\f42 MANAGEMENT.INFRASTRUCTURE.NATIVE.DLL\hich\af42\dbch\af11\loch\f42 MANAGEMENT.INFRASTRUCTURE.UNMANAGED.DLL -\par }\pard \ltrpar\s33\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid11493340 -{\pict{\*\picprop\shplid1025{\sp{\sn shapeType}{\sv 1}}{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}}{\sp{\sn fillColor}{\sv 10526880}}{\sp{\sn fFilled}{\sv 1}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn alignHR}{\sv 1}}{\sp{\sn dxHeightHR}{\sv 30}} -{\sp{\sn fStandardHR}{\sv 1}}{\sp{\sn fHorizRule}{\sv 1}}{\sp{\sn fLayoutInCell}{\sv 1}}}\picscalex1\picscaley1\piccropl0\piccropr0\piccropt0\piccropb0\picw7620\pich7620\picwgoal4320\pichgoal4320\wmetafile8}}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\fs24\insrsid7813854\charrsid6112664 -\par }\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}} -{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar -\s34\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\lang9\langfe1033\langnp9\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 -These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent su\hich\af42\dbch\af11\loch\f42 -ch services or updates are accompanied by new or }{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 additional terms, in which case those different terms {\*\bkmkstart OLE_LINK84}{\*\bkmkstart OLE_LINK85} -apply prospectively {\*\bkmkend OLE_LINK84}{\*\bkmkend OLE_LINK85}and do not alter your or Microsoft\hich\f42 \rquote \loch\f42 s rights relating to pre-updated software or services}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\lang9\langfe1033\langnp9\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 ). IF YOU COMPLY WITH THESE LICENSE TERMS, YO\hich\af42\dbch\af11\loch\f42 U HAVE THE RIGHTS BELOW. }{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\lang9\langfe1033\langnp9\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854 \hich\af40\dbch\af11\loch\f40 1.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls12\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854 \hich\af42\dbch\af11\loch\f42 INSTALLATION AND USE RIGHTS}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\*\bkmkstart OLE_LINK124}{\*\bkmkstart OLE_LINK125}{\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 a)\tab}}\pard\plain \ltrpar -\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls13\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 General.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 You may install and use any number of copies of the software on your devices. -\par {\*\bkmkend OLE_LINK124}{\*\bkmkend OLE_LINK125}{\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 b)\tab}}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 Included Microsoft Applications.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - The software may include other Microsoft applications. These license terms apply to those included applications, if any, {\*\bkmkstart OLE_LINK80}{\*\bkmkstart OLE_LINK81}unless other license terms are provided with the other Microsoft applications -{\*\bkmkend OLE_LINK80}{\*\bkmkend OLE_LINK81}.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 c)\tab}\hich\af42\dbch\af11\loch\f42 Third Party Software.}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 The software may include third pa\hich\af42\dbch\af11\loch\f42 -rty applications that are licensed to you under this agreement or under their own terms. License terms, notices, and acknowledgements, if any, for the third party applications may be accessible online at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af40 -\ltrch\fcs0 \insrsid11493340 \hich\af40\dbch\af11\loch\f40 HYPERLINK "http://aka.ms/thirdpartynotices" }}{\fldrslt {\rtlch\fcs1 \af42 \ltrch\fcs0 \cs19\f42\ul\cf1\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 http -\hich\af42\dbch\af11\loch\f42 ://aka.ms/thirdpartynotices}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - or in an accompanying notices file. Even if such applications are governed by other agreements, the disclaimer, limitations on, and exclusions of damages below also apply to the extent allowed by applicable law.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854 \hich\af40\dbch\af11\loch\f40 2.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls12\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854 \hich\af42\dbch\af11\loch\f42 TIME-SENSITIVE S\hich\af42\dbch\af11\loch\f42 OFTWARE}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 a)\tab}}\pard\plain \ltrpar -\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls14\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 Period.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 The software is time-sensitive and may stop running on a date that is defined in the software.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 b)\tab}\hich\af42\dbch\af11\loch\f42 Notice.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 You may receive periodic reminder notices of this date through the software.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 c)\tab}\hich\af42\dbch\af11\loch\f42 Access to data.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 You may not be able to access data used in the software when it stops running.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 3.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls12\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 PRE-RELEASE SOFTWARE.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - The software is a pre-release version. It may not operate correctly. It may be different from the commercially released version.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 4.\tab}\hich\af42\dbch\af11\loch\f42 FEEDBACK.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 If you give fee\hich\af42\dbch\af11\loch\f42 -dback about the software to Microsoft, you give to Microsoft, without charge, the right to use, share and commercialize your feedback in any way and for any purpose. You will not give feedback that is subject to a license that requires Microsoft to licens -\hich\af42\dbch\af11\loch\f42 e\hich\af42\dbch\af11\loch\f42 its software or documentation to third parties because Microsoft includes your feedback in them. These rights survive this agreement.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 5.\tab}\hich\af42\dbch\af11\loch\f42 DATA COLLECTION.}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 The software may collect information about you and your use of the software and send that to Microsoft. \hich\af42\dbch\af11\loch\f42 -Microsoft may use this information to provide services and improve Microsoft\hich\f42 \rquote \loch\f42 -s products and services. Your opt-out rights, if any, are described in the product documentation. Some features in the software may enable collection of data from users of your a\hich\af42\dbch\af11\loch\f42 p\hich\af42\dbch\af11\loch\f42 -plications that access or use the software. If you use these features to enable data collection in your applications, you must comply with applicable law, including getting any required user consent, and maintain a prominent privacy policy that accurately -\hich\af42\dbch\af11\loch\f42 \hich\af42\dbch\af11\loch\f42 informs users about how you use, collect, and share their data. You can learn more about Microsoft\hich\f42 \rquote \loch\f42 -s data collection and use in the product documentation and the Microsoft Privacy Statement}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\delrsid16459130\charrsid6112664 \hich\af42\dbch\af11\loch\f42 }{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af40 \ltrch\fcs0 \insrsid11493340 \hich\af40\dbch\af11\loch\f40 HYPERLINK "https://go.microsoft.com/fwlink/?LinkId=521839" }}{\fldrslt { -\rtlch\fcs1 \af42 \ltrch\fcs0 \cs19\f42\ul\cf1\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 https://go.microsoft.com/fwlink/?LinkId=521839}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 . You agree to comply with all applicable provisions of the Microsoft Privacy Statement.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 6.\tab}\hich\af42\dbch\af11\loch\f42 FONTS.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 While the software is running, you may use its fonts to display and print content. You may only (i) embed fonts in\hich\af42\dbch\af11\loch\f42 - content as permitted by the embedding restrictions in the fonts; and (ii) temporarily download them to a printer or other output device to help print content.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 7.\tab}\hich\af42\dbch\af11\loch\f42 SCOPE OF LICENSE.}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 The software is licensed, not sold. Microsoft reserves all other rights. Unles\hich\af42\dbch\af11\loch\f42 -s applicable law gives you more rights despite this limitation, you will not (and have no right to):}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 a)\tab}}\pard\plain \ltrpar -\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls15\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 -work around any technical limitations in the software that only allow you to use it in certain ways; -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 b)\tab}\hich\af42\dbch\af11\loch\f42 -reverse engineer, decompile or disassemble the softwar\hich\af42\dbch\af11\loch\f42 e; -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 c)\tab}\hich\af42\dbch\af11\loch\f42 -remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software; -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 d)\tab}\hich\af42\dbch\af11\loch\f42 -use the software in any way that is against the law or to create or propagate malware; or -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 e)\tab}\hich\af42\dbch\af11\loch\f42 -share, publish, distribute, or lend the software, provide the softwar\hich\af42\dbch\af11\loch\f42 e as a stand-alone hosted solution for others to use, or transfer the software or this agreement to any third party. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 8.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls12\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 EXPORT RESTRICTIONS.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - You must comply with all domestic and international export laws and regulations that apply to the software, which incl\hich\af42\dbch\af11\loch\f42 -ude restrictions on destinations, end users, and end use. For further information on export restrictions, visit }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af40 \ltrch\fcs0 \insrsid11493340 \hich\af40\dbch\af11\loch\f40 HYPERLINK "http://aka.ms/exporting" -}}{\fldrslt {\rtlch\fcs1 \af42 \ltrch\fcs0 \cs19\f42\ul\cf1\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 http://aka.ms/exporting}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 .}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 9.\tab}\hich\af42\dbch\af11\loch\f42 SUPPORT SERVICES.}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 Microsoft is not obligated under this agreement to provide an\hich\af42\dbch\af11\loch\f42 \hich\f42 y support services for the software. Any support provided is \'93\loch\f42 -\hich\f42 as is\'94\loch\f42 \hich\f42 , \'93\loch\f42 \hich\f42 with all faults\'94\loch\f42 , and without warranty of any kind.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 10.\tab}\hich\af42\dbch\af11\loch\f42 UPDATES.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 The software may periodically check for updates, and download and install them for you. You may obtain updates only from Mic\hich\af42\dbch\af11\loch\f42 -rosoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or -\hich\af42\dbch\af11\loch\f42 \hich\af42\dbch\af11\loch\f42 peripheral devices.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 11.\tab}\hich\af42\dbch\af11\loch\f42 ENTIRE AGREEMENT.}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software. -\par {\*\bkmkstart OLE_LINK170}{\*\bkmkstart OLE_LINK171}{\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 12.\tab}}{\rtlch\fcs1 \af42 -\ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and a -\hich\af42\dbch\af11\loch\f42 l\hich\af42\dbch\af11\loch\f42 -l other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles. If you acquired the software in any other country, its laws apply. If U.S. federal jurisdiction exists, you and Microsoft conse -\hich\af42\dbch\af11\loch\f42 n\hich\af42\dbch\af11\loch\f42 -t to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court. If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes -\hich\af42\dbch\af11\loch\f42 h\hich\af42\dbch\af11\loch\f42 eard in court.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\*\bkmkend OLE_LINK170}{\*\bkmkend OLE_LINK171}{\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 13.\tab} -\hich\af42\dbch\af11\loch\f42 CONSUMER RIGHTS; REGIONAL VARIATIONS.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state, province, or country. Separate and apart from your relationship with Mic\hich\af42\dbch\af11\loch\f42 -rosoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state, province, or country do not permit it to do so. For example, if you acquired the -\hich\af42\dbch\af11\loch\f42 s\hich\af42\dbch\af11\loch\f42 oftware in one of the below regions, or mandatory country law applies, then the following provisions apply to you:}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 a)\tab}}\pard\plain \ltrpar -\s2\ql \fi-360\li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls16\outlinelevel1\adjustright\rin0\lin717\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 Australia.}{\rtlch\fcs1 \af42 \ltrch\fcs0 -\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights. -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 b)\tab}}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\hich\af42\dbch\af11\loch\f42 Canada.}{\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 - If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking -\hich\af42\dbch\af11\loch\f42 for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software. -\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af42\afs19 \ltrch\fcs0 \b\fs19\loch\af42\hich\af42\dbch\af11\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 c)\tab}}{\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 -\hich\af42\dbch\af11\loch\f42 Germany and Austria. -\par }\pard\plain \ltrpar\ql \fi-360\li1080\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1080\itap0\pararsid7813854 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 -\f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 i.\tab Warranty. The properly licensed software will perform subs -tantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software. -\par ii.\tab Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law. -\par }\pard\plain \ltrpar\s1\ql \li717\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin717\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 -Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in br\hich\af42\dbch\af11\loch\f42 -each of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-ca -\hich\af42\dbch\af11\loch\f42 l\hich\af42\dbch\af11\loch\f42 led "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 14.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar -\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls12\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7813854 {\rtlch\fcs1 \af42 \ltrch\fcs0 \b\f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 \hich\f42 -DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED \'93\loch\f42 \hich\f42 AS IS.\'94\loch\f42 YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, \hich\af42\dbch\af11\loch\f42 -GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. -\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af40\afs20 \ltrch\fcs0 \b\fs20\loch\af40\hich\af40\dbch\af11\insrsid7813854\charrsid6112664 \hich\af40\dbch\af11\loch\f40 15.\tab}\hich\af42\dbch\af11\loch\f42 -LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY \hich\af42\dbch\af11\loch\f42 -BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIREC -\hich\af42\dbch\af11\loch\f42 T\hich\af42\dbch\af11\loch\f42 OR INCIDENTAL DAMAGES. -\par }\pard\plain \ltrpar\s31\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0\pararsid7813854 \rtlch\fcs1 \af40\afs19\alang1025 \ltrch\fcs0 -\b\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 -This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) {\*\bkmkstart OLE_LINK76}{\*\bkmkstart OLE_LINK77} -claims for breach of contract, warranty, guarantee, or condition\hich\af42\dbch\af11\loch\f42 ; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law.{\*\bkmkend OLE_LINK76} -{\*\bkmkend OLE_LINK77} -\par \hich\af42\dbch\af11\loch\f42 It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion m\hich\af42\dbch\af11\loch\f42 -ay not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages. -\par }\pard\plain \ltrpar\s2\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0\pararsid7813854 \rtlch\fcs1 \ab\af40\afs19\alang1025 \ltrch\fcs0 -\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 -\par }\pard\plain \ltrpar\s31\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 \rtlch\fcs1 \af40\afs19\alang1025 \ltrch\fcs0 -\b\fs19\lang1033\langfe1033\loch\af40\hich\af40\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 -Please note: As this software is distributed in Canada, some of the clauses in this agreement are provid\hich\af42\dbch\af11\loch\f42 ed below in French. -\par \hich\af42\dbch\af11\loch\f42 \hich\f42 Remarque: Ce logiciel \'e9\loch\f42 \hich\f42 tant distribu\'e9\loch\f42 \hich\f42 au Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en fran\'e7\loch\f42 ais. -\par \hich\af42\dbch\af11\loch\f42 \hich\f42 EXON\'c9\loch\f42 \hich\f42 RATION DE GARANTIE. Le logiciel vis\'e9\loch\f42 \hich\f42 par une licence est offert \'ab\loch\f42 \hich\f42 tel quel \'bb\loch\f42 . Toute utilisation de ce logi -\hich\af42\dbch\af11\loch\f42 \hich\f42 ciel est \'e0\loch\f42 \hich\f42 votre seule risque et p\'e9\loch\f42 ril. Microsoft n\hich\f42 \rquote \loch\f42 \hich\f42 accorde aucune autre garantie expresse. Vous pouvez b\'e9\loch\f42 \hich\f42 n\'e9 -\loch\f42 ficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permis\hich\af42\dbch\af11\loch\f42 e\hich\af42\dbch\af11\loch\f42 \hich\f42 -s par le droit locale, les garanties implicites de qualit\'e9\loch\f42 marchande, d\hich\f42 \rquote \loch\f42 \hich\f42 ad\'e9\loch\f42 \hich\f42 quation \'e0\loch\f42 un usage particulier et d\hich\f42 \rquote \loch\f42 \hich\f42 absence de contrefa -\'e7\loch\f42 on sont exclues. -\par \hich\af42\dbch\af11\loch\f42 \hich\f42 LIMITATION DES DOMMAGES-INT\'c9\loch\f42 \hich\f42 R\'ca\loch\f42 \hich\f42 TS ET EXCLUSION DE RESPONSABILIT\'c9\loch\f42 POUR LES DOMMAGES. Vous pouvez obtenir de Mi\hich\af42\dbch\af11\loch\f42 \hich\f42 -crosoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement \'e0\loch\f42 \hich\f42 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9\loch\f42 \hich\f42 tendre \'e0\loch\f42 \hich\f42 - aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9\loch\f42 ciaux, indirects ou accessoires et pertes de\hich\af42\dbch\af11\loch\f42 \hich\af42\dbch\af11\loch\f42 \hich\f42 b\'e9\loch\f42 \hich\f42 n\'e9\loch\f42 fices. -\par \hich\af42\dbch\af11\loch\f42 Cette limitation concerne: -\par }\pard \ltrpar\s31\ql \fi-360\li360\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin360\itap0\pararsid7813854 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \bullet \tab \hich\af42\dbch\af11\loch\f42 -\hich\f42 tout ce qui est reli\'e9\loch\f42 au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers; et -\par \bullet \tab \hich\af42\dbch\af11\loch\f42 \hich\f42 les r\'e9\loch\f42 clamations au titre de violation de contrat ou de garan\hich\af42\dbch\af11\loch\f42 \hich\f42 tie, ou au titre de responsabilit\'e9\loch\f42 \hich\f42 stricte, de n\'e9\loch\f42 -gligence ou d\hich\f42 \rquote \loch\f42 \hich\f42 une autre faute dans la limite autoris\'e9\loch\f42 e par la loi en vigueur. -\par }\pard \ltrpar\s31\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid7813854 {\rtlch\fcs1 \af42 \ltrch\fcs0 \f42\insrsid7813854\charrsid6112664 \hich\af42\dbch\af11\loch\f42 Elle s\hich\f42 \rquote -\loch\f42 \hich\f42 applique \'e9\loch\f42 \hich\f42 galement, m\'ea\loch\f42 \hich\f42 me si Microsoft connaissait ou devrait conna\'ee\loch\f42 tre l\hich\f42 \rquote \'e9\loch\f42 \hich\f42 ventualit\'e9\loch\f42 d\hich\f42 \rquote \loch\f42 -un tel dommage. Si votre pays n\hich\f42 \rquote \loch\f42 aut\hich\af42\dbch\af11\loch\f42 orise pas l\hich\f42 \rquote \loch\f42 \hich\f42 exclusion ou la limitation de responsabilit\'e9\loch\f42 - pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\hich\f42 \rquote \loch\f42 exclusion ci-dessus ne s\hich\f42 \rquote \loch\f42 \hich\f42 appliquera pas \'e0\loch\f42 \hich\f42 votre \'e9 -\loch\f42 gard. -\par \hich\af42\dbch\af11\loch\f42 \hich\f42 EFFET JURIDIQUE. Le pr\'e9\loch\f42 sent contrat\hich\af42\dbch\af11\loch\f42 \hich\f42 d\'e9\loch\f42 crit certains droits juridiques. Vous pourriez avoir d\hich\f42 \rquote \loch\f42 \hich\f42 autres droits pr -\'e9\loch\f42 \hich\f42 vus par les lois de votre pays. Le pr\'e9\loch\f42 \hich\f42 sent contrat ne modifie pas les droits que vous conf\'e8\loch\f42 rent les lois de votre pays si celles-ci ne le permettent pas. -\par }\pard\plain \ltrpar\ql \li0\ri0\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid6173475 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { -\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid8394862 -\par }{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid6573559 -\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a -9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad -5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 -b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 -0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 -a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f -c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 -0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 -a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b -4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b -4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210007b740aaca0600008f1a0000160000007468656d652f7468656d652f -7468656d65312e786d6cec595b8bdb46147e2ff43f08bd3bbe49be2cf1065bb69336bb49889d943cceda636bb2238dd18c776342a0244f7d2914d2d28706fad6 -87521a68a0a12ffd310b1bdaf447f4cc489667ec71f6420aa1640d8b34face996fce39face48ba7aed51449d239c70c2e2965bbe52721d1c8fd898c4d3967b6f -d82f345c870b148f1165316eb90bccdd6bbb9f7e7215ed881047d801fb98efa0961b0a31db2916f9088611bfc26638866b13964448c069322d8e13740c7e235a -ac944ab5628448ec3a318ac0ededc9848cb033942edddda5f31e85d358703930a2c940bac68685c28e0fcb12c1173ca089738468cb8579c6ec78881f09d7a188 -0bb8d0724beacf2dee5e2da29dcc888a2db69a5d5ffd657699c1f8b0a2e64ca607f9a49ee77bb576ee5f01a8d8c4f5eabd5aaf96fb5300341ac14a532eba4fbf -d3ec74fd0cab81d2438bef6ebd5b2d1b78cd7f758373db973f03af40a97f6f03dfef07104503af4029dedfc07b5ebd1278065e81527c6d035f2fb5bb5eddc02b -5048497cb8812ef9b56ab05c6d0e99307ac30a6ffa5ebf5ec99caf50500d7975c929262c16db6a2d420f59d2078004522448ec88c50c4fd008aa3840941c24c4 -d923d3100a6f8662c661b85429f54b55f82f7f9e3a5211413b1869d6921730e11b43928fc34709998996fb39787535c8e9ebd7274f5f9d3cfdfde4d9b393a7bf -66732b5786dd0d144f75bbb73f7df3cf8b2f9dbf7ffbf1edf36fd3a9d7f15cc7bff9e5ab377ffcf92ef7b0e255284ebf7bf9e6d5cbd3efbffeebe7e716efed04 -1de8f0218930776ee163e72e8b608116fef820b998c5304444b768c7538e622467b1f8ef89d040df5a208a2cb80e36e3783f01a9b101afcf1f1a8407613217c4 -e2f1661819c07dc6688725d628dc947369611ecee3a97df264aee3ee2274649b3b40b191e5de7c061a4b6c2e83101b34ef50140b34c531168ebcc60e31b6acee -0121465cf7c928619c4d84f380381d44ac21199203a39a56463748047959d80842be8dd8ecdf773a8cda56ddc5472612ee0d442de487981a61bc8ee602453697 -4314513de07b48843692834532d2713d2e20d3534c99d31b63ce6d36b71358af96f49b2033f6b4efd345642213410e6d3ef710633ab2cb0e831045331b7640e2 -50c77ec60fa144917387091b7c9f9977883c873ca0786bbaef136ca4fb6c35b8070aab535a1588bc324f2cb9bc8e9951bf83059d20aca4061a80a1eb1189cf14 -f93579f7ff3b7907113dfde1856545ef47d2ed8e8d7c5c50ccdb09b1de4d37d6247c1b6e5db803968cc987afdb5d348fef60b855369bd747d9fe28dbeeff5eb6 -b7ddcfef5fac57fa0cd22db7ade9765d6ddea3ad7bf709a174201614ef71b57de7d095c67d189476eab915e7cf72b3100ee59d0c1318b86982948d9330f10511 -e1204433d8e3975de964ca33d753eecc1887adbf1ab6fa96783a8ff6d9387d642d97e5e3692a1e1c89d578c9cfc7e17143a4e85a7df51896bb576ca7ea717949 -40da5e8484369949a26a21515f0eca20a98773089a85845ad97b61d1b4b06848f7cb546db0006a795660dbe4c066abe5fa1e9880113c55218ac7324f69aa97d9 -55c97c9f99de164ca302600fb1ac8055a69b92ebd6e5c9d5a5a5768e4c1b24b4723349a8c8a81ec64334c65975cad1f3d0b868ae9bab941af46428d47c505a2b -1af5c6bb585c36d760b7ae0d34d69582c6ce71cbad557d2899119ab5dc093cfac3613483dae172bb8be814de9f8d4492def097519659c24517f1300db8129d54 -0d222270e25012b55cb9fc3c0d34561aa2b8952b20081f2cb926c8ca87460e926e26194f267824f4b46b2332d2e929287caa15d6abcafcf26069c9e690ee4138 -3e760ee83cb98ba0c4fc7a5906704c38bc012aa7d11c1378a5990bd9aafed61a5326bbfa3b455543e938a2b310651d4517f314aea43ca7a3cef2186867d99a21 -a05a48b2467830950d560faad14df3ae9172d8da75cf369291d34473d5330d55915dd3ae62c60ccb36b016cbcb35798dd532c4a0697a874fa57b5d729b4bad5b -db27e45d02029ec7cfd275cfd110346aabc90c6a92f1a60c4bcdce46cddeb15ce019d4ced32434d5af2dddaec52def11d6e960f0529d1fecd6ab168626cb7da5 -8ab4faf6a17f9e60070f413cbaf022784e0557a9848f0f09820dd140ed4952d9805be491c86e0d3872e60969b98f4b7edb0b2a7e502835fc5ec1ab7aa542c36f -570b6ddfaf967b7eb9d4ed549e4063116154f6d3ef2e7d780d4517d9d71735bef105265abe69bb32625191a92f2c45455c7d812957b67f81710888cee35aa5df -ac363bb542b3daee17bc6ea7516806b54ea15b0beadd7e37f01bcdfe13d7395260af5d0dbc5aaf51a89583a0e0d54a927ea359a87b954adbabb71b3daffd24db -c6c0ca53f9c86201e155bc76ff050000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f7468656d652f5f72 -656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c08 -2e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd0 -8a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa -4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000000000000000000000000000005b436f -6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000000000300100005f72 -656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000190200007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210007b740aaca0600008f1a00001600000000000000000000000000d60200 -007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000000000000000000000 -00d40900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000cf0a00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax373\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 4; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 7; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdlocked0 Placeholder Text; -\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; -\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; -\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; -\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; -\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; -\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; -\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; -\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; -\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; -\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; -\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; -\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; -\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; -\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; -\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; -\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; -\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; -\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; -\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; -\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; -\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; -\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; -\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; -\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; -\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; -\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;}}{\*\datastore 0105000002000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 -d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c051 -85b5e0f4d101feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 -000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file 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 fb074aa7881..6e1acaaef59 100644 --- a/docs/debugging/README.md +++ b/docs/debugging/README.md @@ -1,11 +1,9 @@ -Visual Studio Code -================== +# Visual Studio Code -[Experimental .NET Core Debugging in VS Code][core-debug] enables -cross-platform debugging with the [Visual Studio Code][vscode] editor. +The [Visual Studio Code][vscode] editor supports cross-platform debugging. This is made possible by the [OmniSharp][] extension for VS Code. -Please review their [detailed instructions][vscclrdebugger]. In +Please review their [detailed instructions][core-debug]. In addition to being able to build PowerShell, you need: - C# Extension for VS Code installed @@ -19,7 +17,7 @@ You can do this in Bash with `export PATH=$PATH:$HOME/.dotnet` or in PowerShell Once the extension is installed, you have to open a C# file to force VS Code to install the actual .NET Core debugger (the editor will tell you to do this if -you attempt to debug and haven't already open a C# file). +you attempt to debug and haven't already opened a C# file). The committed `.vscode` folder in the root of this repository contains the `launch.json` and `tasks.json` files which provide Core PowerShell @@ -39,17 +37,15 @@ launch an external console with PowerShell running interactively. If neither of these installed, the editor will tell you to do so. Alternatively, the ".NET Core Attach" configuration will start listening for a -process named `powershell`, and will attach to it. If you need more fine grained +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://blogs.msdn.microsoft.com/visualstudioalm/2016/03/10/experimental-net-core-debugging-in-vs-code/ +[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 -[vscclrdebugger]: http://aka.ms/vscclrdebugger -PowerShell -========== +## PowerShell The `Trace-Command` cmdlet can be used to enable tracing of certain PowerShell subsystems. Use `Get-TraceSource` for a list of tracers: @@ -91,8 +87,7 @@ The `-PSHost` specifies the sink, in this case the console host, so we can see the tracing messages. The `-Name` chooses the list of tracers to enable. -LLDB with SOS plug-in -===================== +## LLDB with SOS plug-in The `./tools/debug.sh` script can be used to launch PowerShell inside of LLDB with the SOS plug-in provided by .NET Core. This provides an additional way to @@ -104,14 +99,12 @@ The script is self-documented and contains a link to the [clr-debug]: https://github.com/dotnet/coreclr/blob/master/Documentation/building/debugging-instructions.md#debugging-coreclr-on-linux -corehost -======== +## `corehost` The native executable produced by .NET CLI will produce trace output if launched with `COREHOST_TRACE=1 ./powershell`. -CoreCLR PAL -=========== +## CoreCLR PAL The native code in the CLR has debug channels to selectively output information to the console. These are controlled by the @@ -123,8 +116,7 @@ you will need to narrow your scope. [header]: https://github.com/dotnet/coreclr/blob/release/1.0.0/src/pal/src/include/pal/dbgmsg.h -Debugging .NET Core -=================== +## Debugging .NET Core The .NET Core libraries downloaded from NuGet and shipped with PowerShell are release versions. This means that `PAL_DBG_CHANNELS` will not work with them, @@ -134,16 +126,14 @@ but should prove useful. They are currently written for Linux and are meant only as a shortcut means to debug. -Build and deploy CoreCLR ------------------------- +## Build and deploy CoreCLR * Clone CoreCLR: `git clone -b release/1.0.0 https://github.com/dotnet/coreclr.git` * Follow [building instructions](https://github.com/dotnet/coreclr/blob/release/1.0.0/Documentation/building/linux-instructions.md) * Wait for `./build.sh` to finish * Overwrite PowerShell libraries: `cp bin/Product/Linux.x64.Debug/*{so,dll} /path/to/powershell/` -Build and deploy CoreFX ------------------------ +## Build and deploy CoreFX * Clone CoreFX: `git clone -b release/1.0.0 https://github.com/dotnet/corefx.git` * Follow [building instructions](https://github.com/dotnet/corefx/blob/release/1.0.0/Documentation/building/unix-instructions.md) diff --git a/docs/dev-process/breaking-change-contract.md b/docs/dev-process/breaking-change-contract.md index 44950297c4c..12121b31dfe 100644 --- a/docs/dev-process/breaking-change-contract.md +++ b/docs/dev-process/breaking-change-contract.md @@ -4,34 +4,38 @@ We have a serious commitment to backwards compatibility with all earlier version Below is a summary of our approach to handing breaking changes including what kinds of things constitute breaking changes, how we categorize them, and how we decide what we're willing to take. -Note that these rules only apply to existing stable features that have shipped in a supported release. New features marked as “in preview” that are still under development may be modified from one preview release to the next. These are not considered breaking changes. +Note that these rules only apply to existing stable features that have shipped in a supported release. New features marked as “in preview” that are still under development may be modified from one preview release to the next. +These are **not** considered breaking changes. To help triage breaking changes, we classify them in to four buckets: 1. Public Contract -2. Reasonable Grey Area -3. Unlikely Grey Area -4. Clearly Non-Public +1. Reasonable Grey Area +1. Unlikely Grey Area +1. Clearly Non-Public ## Bucket 1: Public Contract Any change that is a clear violation of the public contract. -### Unacceptable changes: +### Unacceptable changes + A code change that results in a change to the existing behavior observed for a given input with an API, protocol or the PowerShell language. -+ Renaming or removing a public type, type member, or type parameter; renaming or removing a cmdlet or cmdlet parameter (note: it is possible to rename a cmdlet parameter if a parameter alias is added. This is an acceptable solution for PowerShell scripts but may break .NET code that depends on the name of the original member on the cmdlet object type.) ++ Renaming or removing a public type, type member, or type parameter; renaming or removing a cmdlet or cmdlet parameter (note: it is possible to rename a cmdlet parameter if a parameter alias is added. + +This is an acceptable solution for PowerShell scripts but may break .NET code that depends on the name of the original member on the cmdlet object type.) + + Decreasing the range of accepted values within a given parameter. + Changing the value of a public constant or enum member; changing the type of a cmdlet parameter to a more restrictive type. + Example: A cmdlet with a parameter -p1 that was previously type as [object] cannot be changed to be or a more restrictive type such as [int]. + Making an incompatible change to any protocol without increasing the protocol version number. -### Acceptable changes: +### Acceptable changes + Any existing behavior that results in an error message generally may be changed to provide new functionality. + A new instance field is added to a type (this impacts .NET serialization but not PowerShell serialization and so is considered acceptable.) + Adding new types, new type members and new cmdlets -+ Making changes to the protocols with a protocol version increment. Older versions of the protocol would still need to be maintained to allow communication with earlier systems. This would require that protocol negotiation take place between the two systems. In addition to any protocol code changes, the Microsoft Open Specification program requires that the formal protocol specification for a protocol be updated in a timely manner. An example of a MS protocol specification document (MS-PSRP) can be found at: https://msdn.microsoft.com/en-us/library/dd357801.aspx ++ Making changes to the protocols with a protocol version increment. Older versions of the protocol would still need to be maintained to allow communication with earlier systems. This would require that protocol negotiation take place between the two systems. In addition to any protocol code changes, the Microsoft Open Specification program requires that the formal protocol specification for a protocol be updated in a timely manner. An example of a MS protocol specification document (MS-PSRP) can be found at: https://msdn.microsoft.com/library/mt242417.aspx ## Bucket 2: Reasonable Grey Area @@ -44,7 +48,8 @@ Examples: + Change in parsing of input and throwing new errors (even if parsing behavior is not specified in the docs) + Example: a script may be using a JSON parser that is forgiving to minor syntactic errors in the JSON text. Changing that parser to be more rigorous in its processing would result in errors being thrown when no error was generated in the past thus breaking scripts. -Judiciously making changes in these type of features require judgement: how predictable, obvious, consistent was the behavior? In general, a significant external preview of the change would need to be done also possibly requiring an RFC to be created to allow for community input on the proposal. +Judiciously making changes in these type of features require judgement: how predictable, obvious, consistent was the behavior? +In general, a significant external preview of the change would need to be done also possibly requiring an RFC to be created to allow for community input on the proposal. ## Bucket 3: Unlikely Grey Area @@ -66,11 +71,14 @@ Changes to surface area or behavior that is clearly internal or non-breaking in Examples: -+ Changes to internal APIs that break private reflection ++ Changes to internal APIs that break private reflection. ++ Changes to APIs in the `System.Management.Automation.Internal` namespace (even if they are public, they are still considered internal and subject to change). ++ Renaming a parameter set (see related discussion [here](https://github.com/PowerShell/PowerShell/issues/10058)). -It is impossible to evolve a code base without making such changes, so we don't require up-front approval for these, but we will sometimes have to go back and revisit such change if there's too much pain inflicted on the ecosystem through a popular app or library. +It is impossible to evolve a code base without making such changes, so we don't require up-front approval for these, but we will sometimes have to go back and +revisit such change if there's too much pain inflicted on the ecosystem through a popular app or library. -# What This Means for Contributors +## What This Means for Contributors + All bucket 1, 2, and 3 breaking changes require contacting team at @powershell/powershell. + If you're not sure into which bucket a given change falls, contact us as well. @@ -78,4 +86,3 @@ It is impossible to evolve a code base without making such changes, so we don't + If a change is deemed too breaking, we can help identify alternatives such as introducing a new API or cmdlet and obsoleting the old one. Request for clarifications or suggested alterations to this document should be done by opening issues against this document. - diff --git a/docs/dev-process/coding-guidelines.md b/docs/dev-process/coding-guidelines.md index 2c0d3947b5f..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://msdn.microsoft.com/en-us/library/b2s063f7.aspx). +### 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. @@ -108,8 +114,8 @@ Some general guidelines: * Be aware of APIs such as `String.Split(params char[])` that do not provide overloads to avoid array allocation. When calling such APIs, reuse a static array when possible (e.g. `Utils.Separators.Colon`). -* Avoid creating empty arrays. - Instead, reuse the static ones via `Utils.EmptyArray`. +* 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 unnecessary memory allocation in a loop. Move the memory allocation outside the loop if possible. @@ -152,7 +158,7 @@ such as `password`, `crypto`, `encryption`, `decryption`, `certificate`, `authen When facing a PR with such changes, the reviewers should request a designated security Subject Matter Expert (SME) to review the PR. -Currently, @PaulHigin and @TravisEz13 are our security SMEs. +Currently, [@PaulHigin](https://github.com/PaulHigin) and [@TravisEz13](https://github.com/TravisEz13) are our security SMEs. See [CODEOWNERS](../../.github/CODEOWNERS) for more information about the area experts. ## Best Practices @@ -162,7 +168,7 @@ See [CODEOWNERS](../../.github/CODEOWNERS) for more information about the area e * Avoid a method that is too long and complex. In such case, separate it to multiple methods or even a nested class as you see fit. -* Use `using` statement instead of `try/finally` if the only code in the `finally` block is to call the `Dispose` method. +* Use the `using` statement instead of `try/finally` if the only code in the `finally` block is to call the `Dispose` method. * Use of object initializers (e.g. `new Example { Name = "Name", ID = 1 }`) is encouraged for better readability, but not required. @@ -183,16 +189,16 @@ See [CODEOWNERS](../../.github/CODEOWNERS) for more information about the area e * Consider using the `Interlocked` class instead of the `lock` statement to atomically change simple states. The `Interlocked` class provides better performance for updates that must be atomic. * Here are some useful links for your reference: - * [Framework Design Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/index) - Naming, Design and Usage guidelines including: - * [Arrays](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/arrays) - * [Collections](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/guidelines-for-collections) - * [Exceptions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions) - * [Best Practices for Developing World-Ready Applications](https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/best-practices-for-developing-world-ready-apps) - Unicode, Culture, Encoding and Localization. - * [Best Practices for Exceptions](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions) - * [Best Practices for Using Strings in .NET](https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings) - * [Best Practices for Regular Expressions in .NET](https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices) - * [Serialization Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/serialization/serialization-guidelines) - * [Managed Threading Best Practices](https://docs.microsoft.com/en-us/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/dev-process/resx-files.md b/docs/dev-process/resx-files.md index 61bcfa4277d..ec9eace54fe 100644 --- a/docs/dev-process/resx-files.md +++ b/docs/dev-process/resx-files.md @@ -9,33 +9,32 @@ We are using our own `Start-ResGen` to generate them. Usually it's called as part of the regular build with -``` -PS C:\> Start-PSBuild -ResGen +```powershell +Start-PSBuild -ResGen ``` If you see compilation errors related to resources, try to call `Start-ResGen` explicitly. -``` -PS C:\> Start-ResGen +```powershell +Start-ResGen ``` ## Editing `.resx` files -**Don't edit** `.resx` files from Visual Studio. +**Don't edit** `.resx` files from Visual Studio. It will try to create `.cs` files for you and you will get whole bunch of hard-to-understand errors. -To edit a resource file, use any **plain text editor**. +To edit a resource file, use any **plain text editor**. A resource file is a simple XML file, and it's easy to edit. - ## Convert `.txt` resource files into `.resx` files `dotnet cli` doesn't support embedding old-fashioned `.txt` resource. You can do a one-time conversion of `.txt` resources into `.resx` files with a helper function: -``` +```powershell # example, converting all .txt resources under src\Microsoft.WSMan.Management\resources -PS C:\> Convert-TxtResourceToXml -Path src\Microsoft.WSMan.Management\resources +Convert-TxtResourceToXml -Path src\Microsoft.WSMan.Management\resources ``` `.resx` files would be placed next to `.txt` files. diff --git a/docs/git/README.md b/docs/git/README.md index b896490aae3..46b5eee4c62 100644 --- a/docs/git/README.md +++ b/docs/git/README.md @@ -1,50 +1,34 @@ -Working with PowerShell repository -================================== +# Working with PowerShell repository -#### Get the code for the first time +## Get the code for the first time ```sh -git clone --recursive https://github.com/PowerShell/PowerShell +git clone https://github.com/PowerShell/PowerShell.git --branch=master ``` -The PowerShell repository has **submodules**. -They are required to build and test PowerShell. -That's why you need `--recursive`, when you `git clone`. - -If you already cloned the repo without `--recursive`, update submodules manually - -```sh -git submodule init -git submodule update -``` - -See [FAQ](../FAQ.md#why-is-my-submodule-empty) for details. - - -Branches ---------- +## Branches * Don't commit your changes directly to master. It will make the collaborative workflow messy. * 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 +### Understand branches * **master** is the branch with the latest and greatest changes. 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 and all submodules +# 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 @@ -53,65 +37,63 @@ git fetch --all -p git rebase origin/master ``` -#### More complex scenarios +### More complex scenarios 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://web.archive.org/web/20230522041845/https://wincent.com/wiki/git_rebase%3A_you're_doing_it_wrong -[Linus]:http://thread.gmane.org/gmane.comp.video.dri.devel/34739/focus=34744 - - -Tags ------- +## Tags If you are looking for the source code for a particular release, you will find it via **tags**. -* `git tag` will show you list of all tags. +* `git tag` will show you list of all 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 git checkout -b vors/hotfix ``` - -Recommended Git configurations -============================== +## Recommended Git configurations We highly recommend these configurations to help deal with whitespace, rebasing, and general use of Git. > Auto-corrects your command when it's sure (`stats` to `status`) + ```sh git config --global help.autoCorrect -1 ``` > Refuses to merge when pulling, and only pushes to branch with same name. + ```sh git config --global pull.ff only git config --global push.default current ``` > Shows shorter commit hashes and always shows reference names in the log. + ```sh git config --global log.abbrevCommit true git config --global log.decorate short ``` > Ignores whitespace changes and uses more information when merging. + ```sh git config --global apply.ignoreWhitespace change git config --global rerere.enabled true diff --git a/docs/git/basics.md b/docs/git/basics.md index f1fcb6939c7..aa7629cf746 100644 --- a/docs/git/basics.md +++ b/docs/git/basics.md @@ -1,14 +1,12 @@ -Getting started with Git -======================== +# Getting started with Git We are using Git version 2.9.0, but any recent version should be good. It's recommended to learn the `git` command-line tool for full cross-platform experience and a deeper understanding of Git itself. -Install ---------- +## Install -#### Windows +### Windows Install [Git for Windows][]. @@ -22,16 +20,12 @@ During the installation process, choose these recommended settings: [Git for Windows]: https://git-scm.com/download/win -#### Linux +### Linux -Install by using the package manager: +Install by using the package manager on your system. +A list of all the package managers and commands can be found [here][linux-git-dl]. -```sh -sudo apt-get install git -``` - -Interactive tutorials ----------------------- +### Interactive tutorials There are (too) many Git tutorials on the internet. Here we post references to our favorites. @@ -46,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 @@ -58,11 +47,10 @@ learn Git in couple hours. After finishing 50+ real-world scenarios you will have a pretty good idea about what and when you can do with Git. +## Authentication -Authentication --------------- +### Windows -#### Windows On Windows, the best way to use Git securely is [Git Credential Manager for Windows][manager]. It's included in the official Git installer for Windows. @@ -78,13 +66,12 @@ git config --global credential.helper store Alternatively, you can use [SSH key][]. In this case, you may want to use git-ssh even for HTTPS Git URLs. -It will help you to use submodules transparently. -``` +```none git config --global url.git@github.com:.insteadOf https://github.com/ ``` - [SSH key]: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/#generating-a-new-ssh-key [token]: https://help.github.com/articles/creating-an-access-token-for-command-line-use/ [manager]: https://github.com/Microsoft/Git-Credential-Manager-for-Windows +[linux-git-dl]: https://git-scm.com/download/linux diff --git a/docs/git/submodules.md b/docs/git/submodules.md deleted file mode 100644 index 52acc6ad5f5..00000000000 --- a/docs/git/submodules.md +++ /dev/null @@ -1,24 +0,0 @@ -# Submodules - -While most developers will not have to deal with submodules on a regular basis, those who do should read this information. -The submodules currently in this project are: - -- `src/libpsl-native/test/googletest`: The GoogleTest framework for - Linux native code - -[submodules]: https://www.git-scm.com/book/en/v2/Git-Tools-Submodules - -## Rebase and Fast-Forward Merge Pull Requests in Submodules - -Note: *This is not necessary in the superproject, only submodules!* - -**DO NOT** commit updates unless absolutely necessary. -When submodules must be updated, a separate Pull Request must be submitted, reviewed, and merged before updating the superproject. - -Because GitHub's "Merge Pull Request" button merges with `--no-ff`, an extra merge commit will always be created. -This is especially annoying when trying to commit updates to submodules. -Therefore our policy is to merge using the Git CLI after approval, with a rebase onto master to enable a fast-forward merge. - -When committing submodule updates, ensure no other changes are in the same commit. -Submodule bumps may be included in feature branches for ease of work, -but the update must be independently approved before merging into master. diff --git a/docs/host-powershell/README.md b/docs/host-powershell/README.md index 0b09af9dd06..174f986dfa4 100644 --- a/docs/host-powershell/README.md +++ b/docs/host-powershell/README.md @@ -1,153 +1,26 @@ # Host PowerShell Core in .NET Core Applications -## PowerShell Core v6.0.0-beta.3 and Later - -PowerShell Core is refactored in v6.0.0-beta.3 to remove the dependency on a customized `AssemblyLoadContext`. -With this change, hosting PowerShell Core in .NET Core will be the same as hosting Windows PowerShell in .NET. - -Please see the [.NET Core Sample Application](#net-core-sample-application) section for an example that uses PowerShell Core `beta.3` NuGet packages. - -## PowerShell Core v6.0.0-beta.2 and Prior - -### Overview - -Due to the lack of necessary APIs for manipulating assemblies in .NET Core 1.1 and prior, -PowerShell Core needs to control assembly loading via our customized `AssemblyLoadContext` ([CorePsAssemblyLoadContext.cs][]) in order to do tasks like type resolution. -So applications that want to host PowerShell Core (using PowerShell APIs) need to be bootstrapped from `PowerShellAssemblyLoadContextInitializer`. - -`PowerShellAssemblyLoadContextInitializer` exposes 2 APIs for this purpose: -`SetPowerShellAssemblyLoadContext` and `InitializeAndCallEntryMethod`. -They are for different scenarios: - -- `SetPowerShellAssemblyLoadContext` - It's designed to be used by a native host - whose Trusted Platform Assemblies (TPA) do not include PowerShell assemblies, - such as the in-box `powershell.exe` and other native CoreCLR host in Nano Server. - When using this API, instead of setting up a new load context, - `PowerShellAssemblyLoadContextInitializer` will register a handler to the [Resolving][] event of the default load context. - Then PowerShell Core will depend on the default load context to handle TPA and the `Resolving` event to handle other assemblies. - -- `InitializeAndCallEntryMethod` - It's designed to be used with `dotnet.exe` - where the TPA list includes PowerShell assemblies. - When using this API, `PowerShellAssemblyLoadContextInitializer` will set up a new load context to handle all assemblies. - PowerShell Core itself also uses this API for [bootstrapping][]. - -This documentation only covers the `InitializeAndCallEntryMethod` API, -as it's what you need when building a .NET Core application with .NET CLI. - -### Comparison - Hosting Windows PowerShell vs. Hosting PowerShell Core - -The following code demonstrates how to host Windows PowerShell in an application. -As shown below, you can insert your business logic code directly in the `Main` method. - -```CSharp -// MyApp.exe -using System; -using System.Management.Automation; - -public class Program -{ - static void Main(string[] args) - { - // My business logic code - using (PowerShell ps = PowerShell.Create()) - { - var results = ps.AddScript("Get-Command Write-Output").Invoke(); - Console.WriteLine(results[0].ToString()); - } - } -} -``` +## PowerShell Core v6.0.1 and Later -However, when it comes to hosting PowerShell Core, there will be a layer of redirection for the PowerShell load context to take effect. -In a .NET Core application, the entry point assembly that contains the `Main` method is loaded in the default load context, -and thus all assemblies referenced by the entry point assembly, implicitly or explicitly, will also be loaded into the default load context. - -In order to have the PowerShell load context to control assembly loading for the execution of an application, -the business logic code needs to be extracted out of the entry point assembly and put into a different assembly, say `Logic.dll`. -The entry point `Main` method shall do one thing only -- let the PowerShell load context load `Logic.dll` and start the execution of the business logic. -Once the execution starts this way, all further assembly loading requests will be handled by the PowerShell load context. - -So the above example needs to be altered as follows in a .NET Core application: - -```CSharp -// MyApp.exe -using System.Management.Automation; -using System.Reflection; - -namespace Application.Test -{ - public class Program - { - /// - /// Managed entry point shim, which starts the actual program - /// - public static int Main(string[] args) - { - // Application needs to use PowerShell AssemblyLoadContext if it needs to create PowerShell runspace - // PowerShell engine depends on PS ALC to provide the necessary assembly loading/searching support that is missing from .NET Core - string appBase = System.IO.Path.GetDirectoryName(typeof(Program).GetTypeInfo().Assembly.Location); - System.Console.WriteLine("\nappBase: {0}", appBase); - - // Initialize the PS ALC and let it load 'Logic.dll' and start the execution - return (int)PowerShellAssemblyLoadContextInitializer. - InitializeAndCallEntryMethod( - appBase, - new AssemblyName("Logic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), - "Application.Test.Logic", - "Start", - new object[] { args }); - } - } -} - -// Logic.dll -using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Application.Test -{ - public sealed class Logic - { - /// - /// Start the actual logic - /// - public static int Start(string[] args) - { - // My business logic code - using (PowerShell ps = PowerShell.Create()) - { - var results = ps.AddScript("Get-Command Write-Output").Invoke(); - Console.WriteLine(results[0].ToString()); - } - return 0; - } - } -} -``` +The runtime assemblies for Windows, Linux and OSX are now published in NuGet package version 6.*. -[CorePsAssemblyLoadContext.cs]: https://github.com/PowerShell/PowerShell/blob/v6.0.0-beta.2/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs -[Resolving]: https://github.com/dotnet/corefx/blob/ec2a6190efa743ab600317f44d757433e44e859b/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs#L35 -[bootstrapping]: https://github.com/PowerShell/PowerShell/blob/v6.0.0-beta.2/src/powershell/Program.cs#L27 +Please see the [.NET Core Sample Application](#net-core-sample-application) section for an example that uses PowerShell Core NuGet packages. ## .NET Core Sample Application -- [sample-dotnet1.1](./sample-dotnet1.1) - .NET Core `1.1` + PowerShell Core `alpha.17` NuGet packages. - [.NET Core SDK 1.0.1](https://github.com/dotnet/cli/releases/tag/v1.0.1) is required. -- [sample-dotnet2.0-powershell.beta.1](./sample-dotnet2.0-powershell.beta.1) - .NET Core `2.0.0` + PowerShell Core `beta.1` NuGet packages. - .NET Core SDK `2.0.0-preview1-005952` or higher is required. -- [sample-dotnet2.0-powershell.beta.3](./sample-dotnet2.0-powershell.beta.3) - .NET Core `2.0.0` + PowerShell Core `beta.3` NuGet packages. - .NET Core SDK `2.0.0-preview1-005952` or higher is required. +Note: The .NET Core `2.1` runtime and .NET Core SDK `2.1` or higher is required for the examples below: -You can find the sample application project `"MyApp"` in each of the above 3 sample folders. -To build the sample project, run the following commands (make sure the required .NET Core SDK is in use): +- [sample](./sample) + +You can find the sample application project `MyApp` in each of the above 2 sample folders. You can quickly test-run it using `dotnet run`. +To build the sample project properly for distribution, run the following command (make sure the required .NET Core SDK is in use): ```powershell -dotnet restore .\MyApp\MyApp.csproj -dotnet publish .\MyApp -c release -r win10-x64 +dotnet publish .\MyApp --configuration release ``` -Then you can run `MyApp.exe` from the publish folder and see the results: +This builds it for the runtimes specified by the `RuntimeIdentifiers` property in the `.csproj` file. +Then you can run the `MyApp` binary from the publish folder and see the results: ```none PS:> .\MyApp.exe @@ -162,8 +35,13 @@ System.Management.Automation.ActionPreference System.Management.Automation.AliasAttribute ``` -## Remaining Issue +## Special Hosting Scenario For Native Host + +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. -PowerShell Core builds separately for Windows and Unix, so the assemblies are different between Windows and Unix platforms. -Unfortunately, all PowerShell NuGet packages that have been published so far only contain PowerShell assemblies built specifically for Windows. -The issue [#3417](https://github.com/PowerShell/PowerShell/issues/3417) was opened to track publishing PowerShell NuGet packages for Unix platforms. +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-dotnet1.1/Logic/Logic.csproj b/docs/host-powershell/sample-dotnet1.1/Logic/Logic.csproj deleted file mode 100644 index dc8816bca50..00000000000 --- a/docs/host-powershell/sample-dotnet1.1/Logic/Logic.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard1.6 - Logic - win10-x64 - $(PackageTargetFallback);dnxcore50;portable-net45+win8 - - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet1.1/Logic/UseRunspace.cs b/docs/host-powershell/sample-dotnet1.1/Logic/UseRunspace.cs deleted file mode 100644 index f9aa64abfdd..00000000000 --- a/docs/host-powershell/sample-dotnet1.1/Logic/UseRunspace.cs +++ /dev/null @@ -1,35 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Application.Test -{ - public sealed class Logic - { - /// - /// Start the actual logic - /// - public static int Start(string[] args) - { - using (PowerShell ps = PowerShell.Create()) - { - Console.WriteLine("\nEvaluating 'Get-Command Write-Output' in PS Core Runspace\n"); - var results = ps.AddScript("Get-Command Write-Output").Invoke(); - Console.WriteLine(results[0].ToString()); - - ps.Commands.Clear(); - - Console.WriteLine("\nEvaluating '([S.M.A.ActionPreference], [S.M.A.AliasAttribute]).FullName' in PS Core Runspace\n"); - results = ps.AddScript("([System.Management.Automation.ActionPreference], [System.Management.Automation.AliasAttribute]).FullName").Invoke(); - foreach (dynamic result in results) - { - Console.WriteLine(result.ToString()); - } - } - return 0; - } - } -} diff --git a/docs/host-powershell/sample-dotnet1.1/MyApp/MyApp.csproj b/docs/host-powershell/sample-dotnet1.1/MyApp/MyApp.csproj deleted file mode 100644 index 012db33cc60..00000000000 --- a/docs/host-powershell/sample-dotnet1.1/MyApp/MyApp.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netcoreapp1.1 - MyApp - Exe - win10-x64 - $(PackageTargetFallback);dnxcore50;portable-net45+win8 - 1.1.1 - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet1.1/MyApp/Program.cs b/docs/host-powershell/sample-dotnet1.1/MyApp/Program.cs deleted file mode 100644 index 9391a589827..00000000000 --- a/docs/host-powershell/sample-dotnet1.1/MyApp/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Management.Automation; -using System.Reflection; - -namespace Application.Test -{ - public class Program - { - /// - /// Managed entry point shim, which starts the actual program - /// - public static int Main(string[] args) - { - // Application needs to use PowerShell AssemblyLoadContext if it needs to create powershell runspace - // PowerShell engine depends on PS ALC to provide the necessary assembly loading/searching support that is missing from .NET Core - string appBase = System.IO.Path.GetDirectoryName(typeof(Program).GetTypeInfo().Assembly.Location); - System.Console.WriteLine("\nappBase: {0}", appBase); - - // Initialize the PS ALC and let it load 'Logic.dll' and start the execution - return (int)PowerShellAssemblyLoadContextInitializer. - InitializeAndCallEntryMethod( - appBase, - new AssemblyName("Logic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), - "Application.Test.Logic", - "Start", - new object[] { args }); - } - } -} diff --git a/docs/host-powershell/sample-dotnet1.1/NuGet.config b/docs/host-powershell/sample-dotnet1.1/NuGet.config deleted file mode 100644 index 58f8d2c9b6d..00000000000 --- a/docs/host-powershell/sample-dotnet1.1/NuGet.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/Logic.csproj b/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/Logic.csproj deleted file mode 100644 index 0d32ce0bbfc..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/Logic.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp2.0 - Logic - win10-x64 - - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/UseRunspace.cs b/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/UseRunspace.cs deleted file mode 100644 index f9aa64abfdd..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/Logic/UseRunspace.cs +++ /dev/null @@ -1,35 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Application.Test -{ - public sealed class Logic - { - /// - /// Start the actual logic - /// - public static int Start(string[] args) - { - using (PowerShell ps = PowerShell.Create()) - { - Console.WriteLine("\nEvaluating 'Get-Command Write-Output' in PS Core Runspace\n"); - var results = ps.AddScript("Get-Command Write-Output").Invoke(); - Console.WriteLine(results[0].ToString()); - - ps.Commands.Clear(); - - Console.WriteLine("\nEvaluating '([S.M.A.ActionPreference], [S.M.A.AliasAttribute]).FullName' in PS Core Runspace\n"); - results = ps.AddScript("([System.Management.Automation.ActionPreference], [System.Management.Automation.AliasAttribute]).FullName").Invoke(); - foreach (dynamic result in results) - { - Console.WriteLine(result.ToString()); - } - } - return 0; - } - } -} diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/MyApp.csproj b/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/MyApp.csproj deleted file mode 100644 index 6bdba82519b..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/MyApp.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp2.0 - MyApp - Exe - win10-x64 - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/Program.cs b/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/Program.cs deleted file mode 100644 index 9391a589827..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/MyApp/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Management.Automation; -using System.Reflection; - -namespace Application.Test -{ - public class Program - { - /// - /// Managed entry point shim, which starts the actual program - /// - public static int Main(string[] args) - { - // Application needs to use PowerShell AssemblyLoadContext if it needs to create powershell runspace - // PowerShell engine depends on PS ALC to provide the necessary assembly loading/searching support that is missing from .NET Core - string appBase = System.IO.Path.GetDirectoryName(typeof(Program).GetTypeInfo().Assembly.Location); - System.Console.WriteLine("\nappBase: {0}", appBase); - - // Initialize the PS ALC and let it load 'Logic.dll' and start the execution - return (int)PowerShellAssemblyLoadContextInitializer. - InitializeAndCallEntryMethod( - appBase, - new AssemblyName("Logic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), - "Application.Test.Logic", - "Start", - new object[] { args }); - } - } -} diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/NuGet.config b/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/NuGet.config deleted file mode 100644 index 58f8d2c9b6d..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.1/NuGet.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/MyApp.csproj b/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/MyApp.csproj deleted file mode 100644 index 6901e37dda8..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/MyApp.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netcoreapp2.0 - MyApp - Exe - win10-x64 - - - - - - - - - diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/Program.cs b/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/Program.cs deleted file mode 100644 index 92f03751969..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/MyApp/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Management.Automation; - -namespace Application.Test -{ - public class Program - { - /// - /// Managed entry point shim, which starts the actual program - /// - public static int Main(string[] args) - { - using (PowerShell ps = PowerShell.Create()) - { - Console.WriteLine("\nEvaluating 'Get-Command Write-Output' in PS Core Runspace\n"); - var results = ps.AddScript("Get-Command Write-Output").Invoke(); - Console.WriteLine(results[0].ToString()); - - ps.Commands.Clear(); - - Console.WriteLine("\nEvaluating '([S.M.A.ActionPreference], [S.M.A.AliasAttribute]).FullName' in PS Core Runspace\n"); - results = ps.AddScript("([System.Management.Automation.ActionPreference], [System.Management.Automation.AliasAttribute]).FullName").Invoke(); - foreach (dynamic result in results) - { - Console.WriteLine(result.ToString()); - } - } - return 0; - } - } -} diff --git a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/NuGet.config b/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/NuGet.config deleted file mode 100644 index 58f8d2c9b6d..00000000000 --- a/docs/host-powershell/sample-dotnet2.0-powershell.beta.3/NuGet.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/host-powershell/sample/MyApp/MyApp.csproj b/docs/host-powershell/sample/MyApp/MyApp.csproj new file mode 100644 index 00000000000..ab507b5acfc --- /dev/null +++ b/docs/host-powershell/sample/MyApp/MyApp.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + MyApp + Exe + win10-x64;linux-x64;osx-x64 + + + + + + + + + diff --git a/docs/host-powershell/sample/MyApp/Program.cs b/docs/host-powershell/sample/MyApp/Program.cs new file mode 100644 index 00000000000..fc54fa2d709 --- /dev/null +++ b/docs/host-powershell/sample/MyApp/Program.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; + +namespace Application.Test +{ + public class Program + { + /// + /// Managed entry point shim, which starts the actual program. + /// + public static int Main(string[] args) + { + using (PowerShell ps = PowerShell.Create()) + { + Console.WriteLine("\nEvaluating 'Get-Command Write-Output' in PS Core Runspace\n"); + var results = ps.AddScript("Get-Command Write-Output").Invoke(); + Console.WriteLine(results[0].ToString()); + + ps.Commands.Clear(); + + Console.WriteLine("\nEvaluating '([S.M.A.ActionPreference], [S.M.A.AliasAttribute]).FullName' in PS Core Runspace\n"); + results = ps.AddScript("([System.Management.Automation.ActionPreference], [System.Management.Automation.AliasAttribute]).FullName").Invoke(); + foreach (dynamic result in results) + { + Console.WriteLine(result.ToString()); + } + } + + return 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/installation/linux.md b/docs/installation/linux.md deleted file mode 100644 index 45e996a83fe..00000000000 --- a/docs/installation/linux.md +++ /dev/null @@ -1,716 +0,0 @@ -# Package Installation Instructions - -Supports [Ubuntu 14.04][u14], [Ubuntu 16.04][u16], [Ubuntu 17.04][u17], [Debian 8][deb8], [Debian 9][deb9], -[CentOS 7][cos], [Red Hat Enterprise Linux (RHEL) 7][rhel7], [OpenSUSE 42.2][opensuse], [Fedora 25][fed25], -[Fedora 26][fed26], and [Arch Linux][arch]. - -For Linux distributions that are not officially supported, -you can try using the [PowerShell AppImage][lai]. -You can also try deploying PowerShell binaries directly using the Linux [`tar.gz` archive][tar], -but you would need to set up the necessary dependencies based on the OS in separate steps. - -All packages are available on our GitHub [releases][] page. -Once the package is installed, run `pwsh` from a terminal. - -[u14]: #ubuntu-1404 -[u16]: #ubuntu-1604 -[u17]: #ubuntu-1704 -[deb8]: #debian-8 -[deb9]: #debian-9 -[cos]: #centos-7 -[rhel7]: #red-hat-enterprise-linux-rhel-7 -[opensuse]: #opensuse-422 -[fed25]: #fedora-25 -[fed26]: #fedora-26 -[arch]: #arch-linux -[lai]: #linux-appimage -[mac]: #macos-1012 -[tar]: #binary-archives - -## Ubuntu 14.04 - -### Installation via Package Repository - Ubuntu 14.04 - -PowerShell Core, for Linux, is published to package repositories for easy installation (and updates). -This is the preferred method. - -```sh -# Import the public repository GPG keys -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - -# Register the Microsoft Ubuntu repository -curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - -# Update the list of products -sudo apt-get update - -# Install PowerShell -sudo apt-get install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -from then on, you just need to use `sudo apt-get upgrade powershell` to update it. - -### Installation via Direct Download - Ubuntu 14.04 - -Download the Debian package -`powershell_6.0.1-1.ubuntu.14.04_amd64.deb` -from the [releases][] page onto the Ubuntu machine. - -Then execute the following in the terminal: - -```sh -sudo dpkg -i powershell_6.0.1-1.ubuntu.14.04_amd64.deb -sudo apt-get install -f -``` - -> Please note that `dpkg -i` will fail with unmet dependencies; -> the next command, `apt-get install -f` resolves these -> and then finishes configuring the PowerShell package. - -### Uninstallation - Ubuntu 14.04 - -```sh -sudo apt-get remove powershell -``` - -## Ubuntu 16.04 - -### Installation via Package Repository - Ubuntu 16.04 - -PowerShell Core, for Linux, is published to package repositories for easy installation (and updates). -This is the preferred method. - -```sh -# Import the public repository GPG keys -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - -# Register the Microsoft Ubuntu repository -curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - -# Update the list of products -sudo apt-get update - -# Install PowerShell -sudo apt-get install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -from then on, you just need to use `sudo apt-get upgrade powershell` to update it. - -### Installation via Direct Download - Ubuntu 16.04 - -Download the Debian package -`powershell_6.0.1-1.ubuntu.16.04_amd64.deb` -from the [releases][] page onto the Ubuntu machine. - -Then execute the following in the terminal: - -```sh -sudo dpkg -i powershell_6.0.1-1.ubuntu.16.04_amd64.deb -sudo apt-get install -f -``` - -> Please note that `dpkg -i` will fail with unmet dependencies; -> the next command, `apt-get install -f` resolves these -> and then finishes configuring the PowerShell package. - -### Uninstallation - Ubuntu 16.04 - -```sh -sudo apt-get remove powershell -``` - -## Ubuntu 17.04 - -### Installation via Package Repository - Ubuntu 17.04 - -PowerShell Core, for Linux, is published to package repositories for easy installation (and updates). -This is the preferred method. - -```sh -# Import the public repository GPG keys -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - -# Register the Microsoft Ubuntu repository -curl https://packages.microsoft.com/config/ubuntu/17.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - -# Update the list of products -sudo apt-get update - -# Install PowerShell -sudo apt-get install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -from then on, you just need to use `sudo apt-get upgrade powershell` to update it. - -### Installation via Direct Download - Ubuntu 17.04 - -Download the Debian package -`powershell_6.0.1-1.ubuntu.17.04_amd64.deb` -from the [releases][] page onto the Ubuntu machine. - -Then execute the following in the terminal: - -```sh -sudo dpkg -i powershell_6.0.1-1.ubuntu.17.04_amd64.deb -sudo apt-get install -f -``` - -> Please note that `dpkg -i` will fail with unmet dependencies; -> the next command, `apt-get install -f` resolves these -> and then finishes configuring the PowerShell package. - -### Uninstallation - Ubuntu 17.04 - -```sh -sudo apt-get remove powershell -``` - -## Debian 8 - -### Installation via Package Repository - Debian 8 - -PowerShell Core, for Linux, is published to package repositories for easy installation (and updates). -This is the preferred method. - -```sh -# Install system components -sudo apt-get update -sudo apt-get install curl apt-transport-https - -# Import the public repository GPG keys -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - -# Register the Microsoft Product feed -sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-jessie-prod jessie main" > /etc/apt/sources.list.d/microsoft.list' - -# Update the list of products -sudo apt-get update - -# Install PowerShell -sudo apt-get install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -from then on, you just need to use `sudo apt-get upgrade powershell` to update it. - -### Installation via Direct Download - Debian 8 - -Download the Debian package -`powershell_6.0.1-1.debian.8_amd64.deb` -from the [releases][] page onto the Debian machine. - -Then execute the following in the terminal: - -```sh -sudo dpkg -i powershell_6.0.1-1.debian.8_amd64.deb -sudo apt-get install -f -``` - -> Please note that `dpkg -i` will fail with unmet dependencies; -> the next command, `apt-get install -f` resolves these -> and then finishes configuring the PowerShell package. - -### Uninstallation - Debian 8 - -```sh -sudo apt-get remove powershell -``` - -## Debian 9 - -### Installation via Package Repository - Debian 9 - -PowerShell Core, for Linux, is published to package repositories for easy installation (and updates). -This is the preferred method. - -```sh -# Install system components -sudo apt-get update -sudo apt-get install curl gnupg apt-transport-https - -# Import the public repository GPG keys -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - -# Register the Microsoft Product feed -sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/microsoft.list' - -# Update the list of products -sudo apt-get update - -# Install PowerShell -sudo apt-get install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -from then on, you just need to use `sudo apt-get upgrade powershell` to update it. - -### Installation via Direct Download - Debian 9 - -Download the Debian package -`powershell_6.0.1-1.debian.9_amd64.deb` -from the [releases][] page onto the Debian machine. - -Then execute the following in the terminal: - -```sh -sudo dpkg -i powershell_6.0.1-1.debian.9_amd64.deb -sudo apt-get install -f -``` - -> Please note that `dpkg -i` will fail with unmet dependencies; -> the next command, `apt-get install -f` resolves these -> and then finishes configuring the PowerShell package. - -### Uninstallation - Debian 9 - -```sh -sudo apt-get remove powershell -``` - -## CentOS 7 - -> This package also works on Oracle Linux 7. - -### Installation via Package Repository (preferred) - CentOS 7 - -PowerShell Core for Linux is published to official Microsoft repositories for easy installation (and updates). - -```sh -# Register the Microsoft RedHat repository -curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo - -# Install PowerShell -sudo yum install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -you just need to use `sudo yum update powershell` to update PowerShell. - -### Installation via Direct Download - CentOS 7 - -Using [CentOS 7][], download the RPM package -`powershell-6.0.1-1.rhel.7.x86_64.rpm` -from the [releases][] page onto the CentOS machine. - -Then execute the following in the terminal: - -```sh -sudo yum install powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -You can also install the RPM without the intermediate step of downloading it: - -```sh -sudo yum install https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -### Uninstallation - CentOS 7 - -```sh -sudo yum remove powershell -``` - -[CentOS 7]: https://www.centos.org/download/ - -## Red Hat Enterprise Linux (RHEL) 7 - -### Installation via Package Repository (preferred) - Red Hat Enterprise Linux (RHEL) 7 - -PowerShell Core for Linux is published to official Microsoft repositories for easy installation (and updates). - -```sh -# Register the Microsoft RedHat repository -curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo - -# Install PowerShell -sudo yum install -y powershell - -# Start PowerShell -pwsh -``` - -After registering the Microsoft repository once as superuser, -you just need to use `sudo yum update powershell` to update PowerShell. - -### Installation via Direct Download - Red Hat Enterprise Linux (RHEL) 7 - -Download the RPM package -`powershell-6.0.1-1.rhel.7.x86_64.rpm` -from the [releases][] page onto the Red Hat Enterprise Linux machine. - -Then execute the following in the terminal: - -```sh -sudo yum install powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -You can also install the RPM without the intermediate step of downloading it: - -```sh -sudo yum install https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -### Uninstallation - Red Hat Enterprise Linux (RHEL) 7 - -```sh -sudo yum remove powershell -``` - -## OpenSUSE 42.2 - -> **Note:** When installing PowerShell Core, OpenSUSE may report that nothing provides `libcurl`. -`libcurl` should already be installed on supported versions of OpenSUSE. -Run `zypper search libcurl` to confirm. -The error will present 2 'solutions'. Choose 'Solution 2' to continue installing PowerShell Core. - -### Installation via Package Repository (preferred) - OpenSUSE 42.2 - -PowerShell Core for Linux is published to official Microsoft repositories for easy installation (and updates). - -```sh -# Register the Microsoft signature key -sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc - -# Add the Microsoft Product feed -curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/zypp/repos.d/microsoft.repo - -# Update the list of products -sudo zypper update - -# Install PowerShell -sudo zypper install powershell - -# Start PowerShell -pwsh -``` - -### Installation via Direct Download - OpenSUSE 42.2 - -Download the RPM package `powershell-6.0.1-1.rhel.7.x86_64.rpm` -from the [releases][] page onto the OpenSUSE machine. - -```sh -sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc -sudo zypper install powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -You can also install the RPM without the intermediate step of downloading it: - -```sh -sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc -sudo zypper install https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -### Uninstallation - OpenSUSE 42.2 - -```sh -sudo zypper remove powershell -``` - -## Fedora 25 - -### Installation via Package Repository (preferred) - Fedora 25 - -PowerShell Core for Linux is published to official Microsoft repositories for easy installation (and updates). - -```sh -# Register the Microsoft signature key -sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc - -# Register the Microsoft RedHat repository -curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo - -# Update the list of products -sudo dnf update - -# Install PowerShell -sudo dnf install -y powershell - -# Start PowerShell -pwsh -``` - -### Installation via Direct Download - Fedora 25 - -Download the RPM package -`powershell-6.0.1-1.rhel.7.x86_64.rpm` -from the [releases][] page onto the Fedora machine. - -Then execute the following in the terminal: - -```sh -sudo dnf install powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -You can also install the RPM without the intermediate step of downloading it: - -```sh -sudo dnf install https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -### Uninstallation - Fedora 25 - -```sh -sudo dnf remove powershell -``` - -## Fedora 26 - -### Installation via Package Repository (preferred) - Fedora 26 - -PowerShell Core for Linux is published to official Microsoft repositories for easy installation (and updates). - -```sh -# Register the Microsoft signature key -sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc - -# Register the Microsoft RedHat repository -curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo - -# Update the list of products -sudo dnf update - -# Install a system component -sudo dnf install compat-openssl10 - -# Install PowerShell -sudo dnf install -y powershell - -# Start PowerShell -pwsh -``` - -### Installation via Direct Download - Fedora 26 - -Download the RPM package -`powershell-6.0.1-1.rhel.7.x86_64.rpm` -from the [releases][] page onto the Fedora machine. - -Then execute the following in the terminal: - -```sh -sudo dnf update -sudo dnf install compat-openssl10 -sudo dnf install powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -You can also install the RPM without the intermediate step of downloading it: - -```sh -sudo dnf update -sudo dnf install compat-openssl10 -sudo dnf install https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-1.rhel.7.x86_64.rpm -``` - -### Uninstallation - Fedora 26 - -```sh -sudo dnf remove powershell -``` - -## Arch Linux - -PowerShell is available from the [Arch Linux][] User Repository (AUR). - -* It can be compiled with the [latest tagged release][arch-release] -* It can be compiled from the [latest commit to master][arch-git] -* It can be installed using the [latest release binary][arch-bin] - -Packages in the AUR are community maintained - there is no official support. - -For more information on installing packages from the AUR, see the [Arch Linux wiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages) or the community [DockerFile](https://github.com/PowerShell/PowerShell/blob/master/docker/community/archlinux/Dockerfile). - -[Arch Linux]: https://www.archlinux.org/download/ -[arch-release]: https://aur.archlinux.org/packages/powershell/ -[arch-git]: https://aur.archlinux.org/packages/powershell-git/ -[arch-bin]: https://aur.archlinux.org/packages/powershell-bin/ - -## Linux AppImage - -Using a recent Linux distribution, -download the AppImage `powershell-6.0.1-x86_64.AppImage` -from the [releases][] page onto the Linux machine. - -Then execute the following in the terminal: - -```bash -chmod a+x powershell-6.0.1-x86_64.AppImage -./powershell-6.0.1-x86_64.AppImage -``` - -The [AppImage][] lets you run PowerShell without installing it. -It is a portable application that bundles PowerShell and its dependencies -(including .NET Core's system dependencies) into one cohesive package. -This package works independently of the user's Linux distribution, -and is a single binary. - -[appimage]: http://appimage.org/ - -## Kali - -### Installation - -```sh -# Install prerequisites -apt-get install libunwind8 libicu55 -wget http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb -dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb - -# Install PowerShell -dpkg -i powershell_6.0.1-1.ubuntu.16.04_amd64.deb - -# Start PowerShell -pwsh -``` - -### Run PowerShell in latest Kali (Kali GNU/Linux Rolling) without installing it - -```sh -# Grab the latest App Image -wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-x86_64.AppImage - -# Make executable -chmod a+x powershell-6.0.1-x86_64.AppImage - -# Start PowerShell -./powershell-6.0.1-x86_64.AppImage -``` - -### Uninstallation - Kali - -```sh -dpkg -r powershell_6.0.1-1.ubuntu.16.04_amd64.deb -``` - -## Raspbian - -Currently, PowerShell is only supported on Raspbian Stretch. - -Also CoreCLR (and thus PowerShell Core) will only work on Pi 2 and Pi 3 devices as other devices like [Pi Zero](https://github.com/dotnet/coreclr/issues/10605) have an unsupported processor. - -Download [Raspbian Stretch](https://www.raspberrypi.org/downloads/raspbian/) and follow the [installation instructions](https://www.raspberrypi.org/documentation/installation/installing-images/README.md) to get it onto your Pi. - -### Installation - -```sh -# Install prerequisites -sudo apt-get install libunwind8 - -# Grab the latest tar.gz -wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-linux-arm32.tar.gz - -# Make folder to put powershell -mkdir ~/powershell - -# Unpack the tar.gz file -tar -xvf ./powershell-6.0.1-linux-arm32.tar.gz -C ~/powershell - -# Start PowerShell -~/powershell/pwsh -``` - -### Uninstallation - Raspbian - -```sh -rm -rf ~/powershell -``` - -## Binary Archives - -PowerShell binary `tar.gz` archives are provided for macOS and Linux platforms to enable advanced deployment scenarios. - -### Dependencies - -For Linux, PowerShell builds portable binaries for all Linux distributions. -But .NET Core runtime requires different dependencies on different distributions, -and hence PowerShell does the same. - -The following chart shows the .NET Core 2.0 dependencies on different Linux distributions that are officially supported. - -| OS | Dependencies | -| ------------------ | ------------ | -| Ubuntu 14.04 | libc6, libgcc1, libgssapi-krb5-2, liblttng-ust0, libstdc++6,
libcurl3, libunwind8, libuuid1, zlib1g, libssl1.0.0, libicu52 | -| Ubuntu 16.04 | libc6, libgcc1, libgssapi-krb5-2, liblttng-ust0, libstdc++6,
libcurl3, libunwind8, libuuid1, zlib1g, libssl1.0.0, libicu55 | -| Ubuntu 17.04 | libc6, libgcc1, libgssapi-krb5-2, liblttng-ust0, libstdc++6,
libcurl3, libunwind8, libuuid1, zlib1g, libssl1.0.0, libicu57 | -| Debian 8 (Jessie) | libc6, libgcc1, libgssapi-krb5-2, liblttng-ust0, libstdc++6,
libcurl3, libunwind8, libuuid1, zlib1g, libssl1.0.0, libicu52 | -| Debian 9 (Stretch) | libc6, libgcc1, libgssapi-krb5-2, liblttng-ust0, libstdc++6,
libcurl3, libunwind8, libuuid1, zlib1g, libssl1.0.2, libicu57 | -| CentOS 7
Oracle Linux 7
RHEL 7
OpenSUSE 42.2
Fedora 25 | libunwind, libcurl, openssl-libs, libicu | -| Fedora 26 | libunwind, libcurl, openssl-libs, libicu, compat-openssl10 | - -In order to deploy PowerShell binaries on Linux distributions that are not officially supported, -you would need to install the necessary dependencies for the target OS in separate steps. -For example, our [Amazon Linux dockerfile][amazon-dockerfile] installs dependencies first, -and then extracts the Linux `tar.gz` archive. - -[amazon-dockerfile]: https://github.com/PowerShell/PowerShell/blob/master/docker/community/amazonlinux/Dockerfile - -### Installation - Binary Archives - -#### Linux - -```sh -# Download the powershell '.tar.gz' archive -curl -L -o /tmp/powershell.tar.gz https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-linux-x64.tar.gz - -# Create the target folder where powershell will be placed -sudo mkdir -p /opt/microsoft/powershell/6.0.1 - -# Expand powershell to the target folder -sudo tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/6.0.1 - -# Set execute permissions -sudo chmod +x /usr/local/microsoft/powershell/6.0.1/pwsh - -# Create the symbolic link that points to pwsh -sudo ln -s /opt/microsoft/powershell/6.0.1/pwsh /usr/bin/pwsh -``` - -### Uninstallation - Binary Archives - -```sh -sudo rm -rf /usr/bin/pwsh /opt/microsoft/powershell -``` - -## Paths - -* `$PSHOME` is `/opt/microsoft/powershell/6.0.1/` -* User profiles will be read from `~/.config/powershell/profile.ps1` -* Default profiles will be read from `$PSHOME/profile.ps1` -* User modules will be read from `~/.local/share/powershell/Modules` -* Shared modules will be read from `/usr/local/share/powershell/Modules` -* Default modules will be read from `$PSHOME/Modules` -* PSReadline history will be recorded to `~/.local/share/powershell/PSReadLine/ConsoleHost_history.txt` - -The profiles respect PowerShell's per-host configuration, -so the default host-specific profiles exists at `Microsoft.PowerShell_profile.ps1` in the same locations. - -On Linux and macOS, the [XDG Base Directory Specification][xdg-bds] is respected. - -Note that because macOS is a derivation of BSD, -instead of `/opt`, the prefix used is `/usr/local`. -Thus, `$PSHOME` is `/usr/local/microsoft/powershell/6.0.1/`, -and the symlink is placed at `/usr/local/bin/pwsh`. - -[releases]: https://github.com/PowerShell/PowerShell/releases/latest -[xdg-bds]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html diff --git a/docs/installation/macos.md b/docs/installation/macos.md deleted file mode 100644 index 8f321384b17..00000000000 --- a/docs/installation/macos.md +++ /dev/null @@ -1,102 +0,0 @@ -# Package Installation Instructions - -## macOS 10.12+ - -### Installation via Homebrew (preferred) - -[Homebrew][brew] is the missing package manager for macOS. -If the `brew` command is not found, -you need to install Homebrew following [their instructions][brew]. - -Once you've installed Homebrew, installing PowerShell is easy. -First, install [Homebrew-Cask][cask], so you can install more packages: - -```sh -brew tap caskroom/cask -``` - -Now, you can install PowerShell: - -```sh -brew cask install powershell -``` - -Finally, verify that your install is working properly: - -```sh -pwsh -``` - -When new versions of PowerShell are released, -simply update Homebrew's formulae and upgrade PowerShell: - -```sh -brew update -brew cask upgrade powershell -``` - -[brew]: http://brew.sh/ -[cask]: https://caskroom.github.io/ - -### Installation via Direct Download - -Using macOS 10.12+, download the PKG package -`powershell-6.0.1-osx.10.12-x64.pkg` -from the [releases][] page onto the macOS machine. - -Either double-click the file and follow the prompts, -or install it from the terminal: - -```sh -sudo installer -pkg powershell-6.0.1-osx.10.12-x64.pkg -target / -``` - -[releases]: https://github.com/PowerShell/PowerShell/releases - -### Installation via Binary Archive - -```sh -# Download the powershell '.tar.gz' archive -curl -L -o /tmp/powershell.tar.gz https://github.com/PowerShell/PowerShell/releases/download/v6.0.1/powershell-6.0.1-osx-x64.tar.gz - -# Create the target folder where powershell will be placed -sudo mkdir -p /usr/local/microsoft/powershell/6.0.1 - -# Expand powershell to the target folder -sudo tar zxf /tmp/powershell.tar.gz -C /usr/local/microsoft/powershell/6.0.1 - -# Set execute permissions -sudo chmod +x /usr/local/microsoft/powershell/6.0.1/pwsh - -# Create the symbolic link that points to pwsh -sudo ln -s /usr/local/microsoft/powershell/6.0.1/pwsh /usr/local/bin/pwsh -``` - -### Uninstallation - -If you installed PowerShell with Homebrew, uninstallation is easy: - -```sh -brew cask uninstall powershell -``` - -If you installed PowerShell via direct download, -PowerShell must be removed manually: - -```sh -sudo rm -rf /usr/local/microsoft /Applications/PowerShell.app -sudo rm -f /usr/local/bin/pwsh /usr/local/share/man/man1/pwsh.1.gz -sudo pkgutil --forget com.microsoft.powershell -``` - -If you installed PowerShell via binary archive, PowerShell must be removed manually. - -```sh -sudo rm -rf /usr/local/microsoft -sudo rm -f /usr/local/bin/pwsh -``` - -To uninstall the additional PowerShell paths (such as the user profile path) -please see the [paths](linux.md#path) section below in this document -and remove the desired the paths with `sudo rm`. -(Note: this is not necessary if you installed with Homebrew.) diff --git a/docs/installation/windows.md b/docs/installation/windows.md deleted file mode 100644 index 558269b804f..00000000000 --- a/docs/installation/windows.md +++ /dev/null @@ -1,194 +0,0 @@ -# Package Installation Instructions - -## MSI - -To install PowerShell on Windows Full SKU (works on Windows 7 SP1 and later), download either the MSI from [AppVeyor][] for a nightly build, -or a released package from our GitHub [releases][] page. - -Once downloaded, double-click the installer and follow the prompts. - -There is a shortcut placed in the Start Menu upon installation. - -* By default the package is installed to `$env:ProgramFiles\PowerShell\` -* You can launch PowerShell via the Start Menu or `$env:ProgramFiles\PowerShell\pwsh.exe` - -### Prerequisites - -To enable PowerShell remoting over WinRM, the following prerequisites need to be met: - -* Install the [Universal C Runtime](https://www.microsoft.com/download/details.aspx?id=50410) on Windows versions prior to Windows 10. - It is available via direct download or Windows Update. - Fully patched (including optional packages), supported systems will already have this installed. -* Install the Windows Management Framework (WMF) [4.0](https://www.microsoft.com/download/details.aspx?id=40855) - or newer ([5.0](https://www.microsoft.com/download/details.aspx?id=50395), - [5.1](https://www.microsoft.com/download/details.aspx?id=54616)) on Windows 7. - -## ZIP - -PowerShell binary ZIP archives are provided to enable advanced deployment scenarios. -Be noted that when using the ZIP archive, you won't get the prerequisites check as in the MSI package. -So in order for remoting over WinRM to work properly on Windows versions prior to Windows 10, -you need to make sure the [prerequisites](#prerequisites) are met. - -## Deploying on Windows IoT - -Windows IoT already comes with Windows PowerShell which we will use to deploy PowerShell Core 6. - -* Create `PSSession` to target device - -```powershell -$s = New-PSSession -ComputerName -Credential Administrator -``` - -* Copy the zip package to the device - -```powershell -# change the destination to however you had partitioned it with sufficient space for the zip and the unzipped contents -# the path should be local to the device -Copy-Item .\PowerShell-6.0.1-win-arm32.zip -Destination u:\users\administrator\Downloads -ToSession $s -``` - -* Connect to the device and expand the archive - -```powershell -Enter-PSSession $s -cd u:\users\administrator\downloads -Expand-Archive .\PowerShell-6.0.1-win-arm32.zip -``` - -* Setup remoting to PowerShell Core 6 - -```powershell -cd .\PowerShell-6.0.1-win-arm32 -# Be sure to use the -PowerShellHome parameter otherwise it'll try to create a new endpoint with Windows PowerShell 5.1 -.\Install-PowerShellRemoting.ps1 -PowerShellHome . -# You'll get an error message and will be disconnected from the device because it has to restart WinRM -``` - -* Connect to PowerShell Core 6 endpoint on device - -```powershell -# Be sure to use the -Configuration parameter. If you omit it, you will connect to Windows PowerShell 5.1 -Enter-PSSession -ComputerName -Credential Administrator -Configuration powershell.6.0.1 -``` - -## Deploying on Nano Server - -These instructions assume that Windows PowerShell is running on the Nano Server image and that it has been generated by the [Nano Server Image Builder](https://technet.microsoft.com/windows-server-docs/get-started/deploy-nano-server). -Nano Server is a "headless" OS and deployment of PowerShell Core binaries can happen in two different ways: - -1. Offline - Mount the Nano Server VHD and unzip the contents of the zip file to your chosen location within the mounted image. -1. Online - Transfer the zip file over a PowerShell Session and unzip it in your chosen location. - -In both cases, you will need the Windows 10 x64 Zip release package and will need to run the commands within an "Administrator" PowerShell instance. - -### Offline Deployment of PowerShell Core - -1. Use your favorite zip utility to unzip the package to a directory within the mounted Nano Server image. -1. Unmount the image and boot it. -1. Connect to the inbox instance of Windows PowerShell. -1. Follow the instructions to create a remoting endpoint using the [another instance technique](#executed-by-another-instance-of-powershell-on-behalf-of-the-instance-that-it-will-register). - -### Online Deployment of PowerShell Core - -The following steps will guide you through the deployment of PowerShell Core to a running instance of Nano Server and the configuration of its remote endpoint. - -* Connect to the inbox instance of Windows PowerShell - -```powershell -$session = New-PSSession -ComputerName -Credential -``` - -* Copy the file to the Nano Server instance - -```powershell -Copy-Item \powershell--win-x64.zip c:\ -ToSession $session -``` - -* Enter the session - -```powershell -Enter-PSSession $session -``` - -* Extract the Zip file - -```powershell -# Insert the appropriate version. -Expand-Archive -Path C:\powershell--win-x64.zip -DestinationPath "C:\PowerShellCore_" -``` - -* Follow the instructions to create a remoting endpoint using the [another instance technique](#executed-by-another-instance-of-powershell-on-behalf-of-the-instance-that-it-will-register). - -## Instructions to Create a Remoting Endpoint - -Beginning with 6.0.0-alpha.9, the PowerShell package for Windows includes a WinRM plug-in (pwrshplugin.dll) and an installation script (Install-PowerShellRemoting.ps1). -These files enable PowerShell to accept incoming PowerShell remote connections when its endpoint is specified. - -### Motivation - -An installation of PowerShell can establish PowerShell sessions to remote computers using `New-PSSession` and `Enter-PSSession`. -To enable it to accept incoming PowerShell remote connections, the user must create a WinRM remoting endpoint. -This is an explicit opt-in scenario where the user runs Install-PowerShellRemoting.ps1 to create the WinRM endpoint. -The installation script is a short-term solution until we add additional functionality to `Enable-PSRemoting` to perform the same action. -For more details, please see issue [#1193](https://github.com/PowerShell/PowerShell/issues/1193). - -### Script Actions - -The script - -1. Creates a directory for the plug-in within %windir%\System32\PowerShell -1. Copies pwrshplugin.dll to that location -1. Generates a configuration file -1. Registers that plug-in with WinRM - -### Registration - -The script must be executed within an Administrator-level PowerShell session and runs in two modes. - -#### Executed by the instance of PowerShell that it will register - -``` powershell -Install-PowerShellRemoting.ps1 -``` - -#### Executed by another instance of PowerShell on behalf of the instance that it will register - -``` powershell -\Install-PowerShellRemoting.ps1 -PowerShellHome "" -PowerShellVersion "" -``` - -For Example: - -``` powershell -C:\Program Files\PowerShell\6.0.0.9\Install-PowerShellRemoting.ps1 -PowerShellHome "C:\Program Files\PowerShell\6.0.0.9\" -PowerShellVersion "6.0.0-alpha.9" -``` - -**NOTE:** The remoting registration script will restart WinRM, so all existing PSRP sessions will terminate immediately after the script is run. If run during a remote session, this will terminate the connection. - -## How to Connect to the New Endpoint - -Create a PowerShell session to the new PowerShell endpoint by specifying `-ConfigurationName "some endpoint name"`. To connect to the PowerShell instance from the example above, use either: - -``` powershell -New-PSSession ... -ConfigurationName "powershell.6.0.0-alpha.9" -Enter-PSSession ... -ConfigurationName "powershell.6.0.0-alpha.9" -``` - -Note that `New-PSSession` and `Enter-PSSession` invocations that do not specify `-ConfigurationName` will target the default PowerShell endpoint, `microsoft.powershell`. - -## Artifact Installation Instructions - -We publish an archive with CoreCLR bits on every CI build with [AppVeyor][]. - -[releases]: https://github.com/PowerShell/PowerShell/releases -[signing]: ../../tools/Sign-Package.ps1 -[AppVeyor]: https://ci.appveyor.com/project/PowerShell/powershell - -## CoreCLR Artifacts - -* Download zip package from **artifacts** tab of the particular build. -* Unblock zip file: right-click in File Explorer -> Properties -> - check 'Unblock' box -> apply -* Extract zip file to `bin` directory -* `./bin/pwsh.exe` diff --git a/docs/learning-powershell/README.md b/docs/learning-powershell/README.md deleted file mode 100644 index 6b59e21ce85..00000000000 --- a/docs/learning-powershell/README.md +++ /dev/null @@ -1,136 +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](../../README.md#building-powershell) - -[inst-linux]: ../installation/linux.md -[inst-win]: ../installation/windows.md -[inst-macos]: ../installation/macos.md - -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)][use-vscode-editor] - -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)][use-vscode-debugger] -- [PowerShell Command-line Debugging][cli-debugging] - -[use-vscode-editor]:./using-vscode.md#editing-with-vs-code -[use-vscode-debugger]:./using-vscode.md#debugging-with-vs-code -[cli-debugging]:./debugging-from-commandline.md -[get-powershell]:../../README.md#get-powershell -[build-powershell]:../../README.md#building-the-repository - - -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 |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 - - -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] -- [PowerShell eBook][ebook-from-powershell.com] from PowerShell.com -- [PowerShell-related Videos][channel9-learn-powershell] on Channel 9 -- [Learn PowerShell Video Library][powershell.com-learn-powershell] from PowerShell.com -- [PowerShell Quick Reference Guides][quick-reference] by PowerShellMagazine.com -- [PowerShell 5 How-To Videos][script-guy-how-to] by Ed Wilson -- [PowerShell TechNet Resources](https://technet.microsoft.com/en-us/scriptcenter/dd742419.aspx) from ScriptCenter - - -Commercial Resources ----- -- [Windows PowerShell in Action][in-action] by Bruce Payette -- [Introduction to PowerShell][powershell-intro] from Pluralsight -- [PowerShell Training and Tutorials][lynda-training] from Lynda.com - - -[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 - -[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/ -[basic-cookbooks]: https://msdn.microsoft.com/en-us/powershell/scripting/getting-started/basic-cookbooks -[ebook-from-powershell.com]: http://powershell.com/cs/blogs/ebookv2/default.aspx -[channel9-learn-powershell]: https://channel9.msdn.com/Search?term=powershell#ch9Search -[powershell.com-learn-powershell]: http://powershell.com/cs/media/14/default.aspx -[quick-reference]: http://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/ diff --git a/docs/learning-powershell/create-powershell-scripts.md b/docs/learning-powershell/create-powershell-scripts.md deleted file mode 100644 index f5657587d2f..00000000000 --- a/docs/learning-powershell/create-powershell-scripts.md +++ /dev/null @@ -1,61 +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 -PS> .\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 -PS C:\> 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]:http://windowsitpro.com/powershell/running-powershell-scripts-easy-1-2-3 \ No newline at end of file diff --git a/docs/learning-powershell/debugging-from-commandline.md b/docs/learning-powershell/debugging-from-commandline.md deleted file mode 100644 index 2880774a0c0..00000000000 --- a/docs/learning-powershell/debugging-from-commandline.md +++ /dev/null @@ -1,174 +0,0 @@ -Debugging in PowerShell Command-line -===== - -As we know, we can debug PowerShell code via GUI tools like [Visual Studio Code](./using-vscode.md#debugging-with-vs-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" - -``` - - - **1. 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> - -``` - - -**2. 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 - -PS /home/jen/debug> ./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://technet.microsoft.com/en-us/library/hh847790.aspx) -- [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 3f0c1cfaafb..00000000000 --- a/docs/learning-powershell/powershell-beginners-guide.md +++ /dev/null @@ -1,309 +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 are 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 - -``` - -**2. 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. - -**3. Get-Alias**: Gets the aliases for the current session. -```PowerShell -PS /> Get-Alias - -CommandType Name ------------ ---- -… - -Alias cd -> Set-Location -Alias cls -> 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" is an alias of Clear-Host. -Now try it: - -PS /> Get-Process -PS /> cls -``` -**4. cd - Set-Location**: Sets the current working location to a specified location. -```PowerShell -PS /> Set-Location /home -PS /home> -``` -**5. 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 -``` - -**6. 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: -``` -# 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. - -**7. 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! -``` -**8. 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 -``` - -**9. $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 - -``` - -**10. Exit**: To exit the PowerShell session, type "exit". -```PowerShell -PS /home/jen> 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://powershell.org/ebooks/) -- [eBooks from PowerShell.com][ebooks-powershell.com] -- [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 PowerShell Scripts][examples] -- [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-Second-Payette/dp/1935182137 -[cookbook]: http://shop.oreilly.com/product/9780596801519.do -[ebook-list]: https://blogs.technet.microsoft.com/pstips/2014/05/26/free-powershell-ebooks/ -[ebooks-powershell.com]: http://powershell.com/cs/blogs/ebookv2/default.aspx -[tutorial]: http://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://msdn.microsoft.com/en-us/library/dd878324%28v=vs.85%29.aspx -[create-ps-module]:http://www.tomsitpro.com/articles/powershell-modules,2-846.html -[remoting]:https://channel9.msdn.com/Series/GetStartedPowerShell3/06 -[in-depth]: https://channel9.msdn.com/events/MMS/2012/SV-B406 -[remote-mgmt]:http://windowsitpro.com/powershell/powershell-basics-remote-management -[remote-commands]:https://msdn.microsoft.com/en-us/powershell/scripting/core-powershell/running-remote-commands -[examples]:http://examples.oreilly.com/9780596528492/ -[examples-ps-module]:https://msdn.microsoft.com/en-us/library/dd878340%28v=vs.85%29.aspx -[writing-ps-module]:http://www.powershellmagazine.com/2014/03/18/writing-a-powershell-module-in-c-part-1-the-basics/ -[sample-code]:https://msdn.microsoft.com/en-us/library/ff602031%28v=vs.85%29.aspx -[create-run-script]:./create-powershell-scripts.md diff --git a/docs/learning-powershell/using-vscode.md b/docs/learning-powershell/using-vscode.md deleted file mode 100644 index 3623039153d..00000000000 --- a/docs/learning-powershell/using-vscode.md +++ /dev/null @@ -1,181 +0,0 @@ -Using Visual Studio Code for PowerShell Development -==== - -If you are working on Linux and macOS, you cannot use the PowerShell ISE because it is not supported on these platforms. -In this case, you can choose your favorite editor to write PowerShell scripts. -Here we choose Visual Studio Code as a PowerShell editor. - -You can use Visual Studio Code on Windows with PowerShell version 5 by using Windows 10 or by installing [Windows Management Framework 5.0 RTM](https://www.microsoft.com/en-us/download/details.aspx?id=50395) for down-level Windows OSs (e.g. Windows 8.1, etc.). - -Before starting it, please make sure PowerShell exists on your system. -By following the [Installing PowerShell](./README.md#installing-powershell) instructions you can install PowerShell and launch a PowerShell session. - -Editing with Visual Studio Code ----- -[**1. Installing Visual Studio Code**](https://code.visualstudio.com/Docs/setup/setup-overview) - -* **Linux**: follow the installation instructions on the [Running VS Code on Linux](https://code.visualstudio.com/docs/setup/linux) page - -* **macOS**: follow the installation instructions on the [Running VS Code on macOS](https://code.visualstudio.com/docs/setup/mac) page - - **NOTE:** On OS X you must install OpenSSL for the PowerShell extension to work correctly. - The easiest way to accomplish this is to install [Homebrew](http://brew.sh/) and then run `brew install openssl`. - The PowerShell extension will now be able to load successfully. - -* **Windows**: follow the installation instructions on the [Running VS Code on Windows](https://code.visualstudio.com/docs/setup/windows) page - - -**2. Installing PowerShell Extension** - -- Launch the Visual Studio Code app by: - * **Windows**: typing **code** in your PowerShell session - * **Linux**: typing **code** in your terminal - * **macOS**: typing **code** in your terminal - -- Launch **Quick Open** by pressing **Ctrl+P** (**Cmd+P** on Mac). -- In Quick Open, type **ext install powershell** and hit **Enter**. -- The **Extensions** view will open on the Side Bar. Select the PowerShell extension from Microsoft. - You will see something like below: - - ![VSCode](vscode.png) - -- Click the **Install** button on the PowerShell extension from Microsoft. -- After the install, you will see the **Install** button turns to **Reload**. - Click on **Reload**. -- After Visual Studio Code has reload, you are ready for editing. - -For example, to create a new file, click **File->New**. -To save it, click **File->Save** and then provide a file name, let's say "HelloWorld.ps1". -To close the file, click on "x" next to the file name. -To exit Visual Studio Code, **File->Exit**. - -#### Using a specific installed version of PowerShell - -If you wish to use a specific installation of PowerShell with Visual Studio Code, you will need to add a new variable to your user settings file. - -1. Click **File -> Preferences -> Settings** -2. Two editor panes will appear. - In the right-most pane (`settings.json`), insert the setting below appropriate for your OS somewhere between the two curly brackets (`{` and `}`) and replace ** with the installed PowerShell version: - - ```json - // On Windows: - "powershell.powerShellExePath": "c:/Program Files/PowerShell//pwsh.exe" - - // On Linux: - "powershell.powerShellExePath": "/opt/microsoft/powershell//pwsh" - - // On macOS: - "powershell.powerShellExePath": "/usr/local/microsoft/powershell//pwsh" - ``` - -3. Replace the setting with the path to the desired PowerShell executable -4. Save the settings file and restart Visual Studio Code - -#### Configuration settings for Visual Studio Code - -By using the steps in the previous paragraph you can add configuration settings in `settings.json`. - -We recommend the following configuration settings for Visual Studio Code: - -```json -{ - "csharp.suppressDotnetRestoreNotification": true, - "editor.renderWhitespace": "all", - "editor.renderControlCharacters": true, - "omnisharp.projectLoadTimeout": 120, - "files.trimTrailingWhitespace": true -} -``` - -Debugging with Visual Studio Code ----- -### No-workspace debugging -As of Visual Studio Code version 1.9 you can debug PowerShell scripts without having to open the folder containing the PowerShell script. -Simply open the PowerShell script file with **File->Open File...**, set a breakpoint on a line (press F9) and then press F5 to start debugging. -You will see the Debug actions pane appear which allows you to break into the debugger, step, resume and stop debugging. - -### Workspace debugging -Workspace debugging refers to debugging in the context of a folder that you have opened in Visual Studio Code using **Open Folder...** from the **File** menu. -The folder you open is typically your PowerShell project folder and/or the root of your Git repository. - -Even in this mode, you can start debugging the currently selected PowerShell script by simply pressing F5. -However, workspace debugging allows you to define multiple debug configurations other than just debugging the currently open file. -For instance, you can add a configurations to: - -* Launch Pester tests in the debugger -* Launch a specific file with arguments in the debugger -* Launch an interactive session in the debugger -* Attach the debugger to a PowerShell host process - -Follow these steps to create your debug configuration file: -1. Open the **Debug** view by pressing **Ctrl+Shift+D** (**Cmd+Shift+D** on Mac). -2. Press the **Configure** gear icon in the toolbar. -3. Visual Studio Code will prompt you to **Select Environment**. -Choose **PowerShell**. - - When you do this, Visual Studio Code creates a directory and a file ".vscode\launch.json" in the root of your workspace folder. - This is where your debug configuration is stored. If your files are in a Git repository, you will typically want to commit the launch.json file. - The contents of the launch.json file are: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch (current file)", - "script": "${file}", - "args": [], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "attach", - "name": "PowerShell Attach to Host Process", - "processId": "${command.PickPSHostProcess}", - "runspaceId": 1 - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Interactive Session", - "cwd": "${workspaceRoot}" - } - ] -} - -``` -This represents the common debug scenarios. -However, when you open this file in the editor, you will see an **Add Configuration...** button. -You can press this button to add more PowerShell debug configurations. One handy configuration to add is **PowerShell: Launch Script**. -With this configuration, you can specify a specific file with optional arguments that should be launched whenever you press F5 no matter which file is currently active in the editor. - -Once the debug configuration is established, you can select which configuration you want to use during a debug session by selecting one from the debug configuration drop-down in the **Debug** view's toolbar. - -There are a few blogs that may be helpful to get you started using PowerShell extension for Visual Studio Code - -- Visual Studio Code: [PowerShell Extension][ps-extension] -- [Write and debug PowerShell scripts in Visual Studio Code][debug] -- [Debugging Visual Studio Code Guidance][vscode-guide] -- [Debugging PowerShell in Visual Studio Code][ps-vscode] -- [Get started with PowerShell development in Visual Studio Code][getting-started] -- [Visual Studio Code editing features for PowerShell development – Part 1][editing-part1] -- [Visual Studio Code editing features for PowerShell development – Part 2][editing-part2] -- [Debugging PowerShell script in Visual Studio Code – Part 1][debugging-part1] -- [Debugging PowerShell script in Visual Studio Code – Part 2][debugging-part2] - -[ps-extension]:https://blogs.msdn.microsoft.com/cdndevs/2015/12/11/visual-studio-code-powershell-extension/ -[debug]:https://blogs.msdn.microsoft.com/powershell/2015/11/16/announcing-powershell-language-support-for-visual-studio-code-and-more/ -[vscode-guide]:https://johnpapa.net/debugging-with-visual-studio-code/ -[ps-vscode]:https://github.com/PowerShell/vscode-powershell/tree/master/examples -[getting-started]:https://blogs.technet.microsoft.com/heyscriptingguy/2016/12/05/get-started-with-powershell-development-in-visual-studio-code/ -[editing-part1]:https://blogs.technet.microsoft.com/heyscriptingguy/2017/01/11/visual-studio-code-editing-features-for-powershell-development-part-1/ -[editing-part2]:https://blogs.technet.microsoft.com/heyscriptingguy/2017/01/12/visual-studio-code-editing-features-for-powershell-development-part-2/ -[debugging-part1]:https://blogs.technet.microsoft.com/heyscriptingguy/2017/02/06/debugging-powershell-script-in-visual-studio-code-part-1/ -[debugging-part2]:https://blogs.technet.microsoft.com/heyscriptingguy/2017/02/13/debugging-powershell-script-in-visual-studio-code-part-2/ - -PowerShell Extension for Visual Studio Code ----- - -The PowerShell extension's source code can be found on [GitHub](https://github.com/PowerShell/vscode-powershell). diff --git a/docs/learning-powershell/vscode.png b/docs/learning-powershell/vscode.png deleted file mode 100644 index 4c9354bd0c9..00000000000 Binary files a/docs/learning-powershell/vscode.png and /dev/null differ 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 adbdc3eeef6..00000000000 --- a/docs/learning-powershell/working-with-powershell-objects.md +++ /dev/null @@ -1,124 +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 -``` \ No newline at end of file diff --git a/docs/maintainers/Images/merge-commit-confirm.png b/docs/maintainers/Images/merge-commit-confirm.png new file mode 100644 index 00000000000..a26e1aa0b4f Binary files /dev/null and b/docs/maintainers/Images/merge-commit-confirm.png differ diff --git a/docs/maintainers/Images/merge-commit.png b/docs/maintainers/Images/merge-commit.png new file mode 100644 index 00000000000..2ae3b6a8ba5 Binary files /dev/null and b/docs/maintainers/Images/merge-commit.png differ diff --git a/docs/maintainers/Images/squash-confirm.png b/docs/maintainers/Images/squash-confirm.png new file mode 100644 index 00000000000..3a5df89be14 Binary files /dev/null and b/docs/maintainers/Images/squash-confirm.png differ diff --git a/docs/maintainers/Images/squash-merge.png b/docs/maintainers/Images/squash-merge.png new file mode 100644 index 00000000000..98147cdd6a7 Binary files /dev/null and b/docs/maintainers/Images/squash-merge.png differ diff --git a/docs/maintainers/README.md b/docs/maintainers/README.md index 82dee5ec454..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 -1. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/) +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,19 +15,27 @@ 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 -* Sergei Vorobev ([vors](https://github.com/vors)) -* Jason Shirk ([lzybkr](https://github.com/lzybkr)) -* Dongbo Wang ([daxian-dbw](https://github.com/daxian-dbw)) -* Travis Plunk ([TravisEz13](https://github.com/TravisEz13)) -* Mike Richmond ([mirichmo](https://github.com/mirichmo)) -* Andy Schwartzmeyer ([andschwa](https://github.com/andschwa)) -* Aditya Patwardhan ([adityapatwardhan](https://github.com/adityapatwardhan)) -* Ilya Sazonov ([iSazonov](https://github.com/iSazonov)) + + +- Aditya Patwardhan ([adityapatwardhan](https://github.com/adityapatwardhan)) +- Andrew Menagarishvili ([anmenaga](https://github.com/anmenaga)) +- Dongbo Wang ([daxian-dbw](https://github.com/daxian-dbw)) +- Ilya Sazonov ([iSazonov](https://github.com/iSazonov)) +- Robert Holt ([rjmholt](https://github.com/rjmholt)) +- Travis Plunk ([TravisEz13](https://github.com/TravisEz13)) + +## Former Repository Maintainers + + + +- 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)) ## Repository Maintainer Responsibilities @@ -35,7 +43,8 @@ Repository Maintainers enable rapid contributions while maintaining a high level If you are a Repository Maintainer, you: -1. **MUST** ensure that each contributor has signed a valid Contributor License Agreement (CLA) +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 1. **MUST** validate that code reviews have been conducted before merging a pull request when no code is written @@ -54,10 +63,10 @@ If you are a Repository Maintainer, you: However, they should be reminded to create an issue in the future to frontload any potential problems with the work and to minimize duplication of efforts. 1. **SHOULD** encourage contributors to create meaningful titles for all PRs. Edit the title if necessary to provide clarity on the problem -1. **SHOULD** encourage contributes to write meaningful, descriptive git commits +1. **SHOULD** encourage contributors to write meaningful, descriptive git commits 1. **SHOULD NOT** merge pull requests with a failed CI build (unless, for instance, the pull request is being submitted to fix broken CI) -1. **SHOULD NOT** merge pull requests without the label `cla-signed` or `cla-not-required` from the Microsoft CLA bot +1. **SHOULD NOT** merge pull requests without the status check passing from the Microsoft CLA bot (unless the CLA bot is broken, and CLA signing can be confirmed through other means) 1. **SHOULD NOT** merge pull requests too quickly after they're submitted. Even if the pull request meets all the requirements, people should have time to give their input @@ -73,6 +82,10 @@ Please see [Issue Management][issue-management] Please see [Contributing][CONTRIBUTING] +## Maintainer Best Practices + +Please see [Best Practices][best-practice] + ## Becoming a Repository Maintainer Repository Maintainers currently consist mostly of Microsoft employees. @@ -82,10 +95,12 @@ Eligibility is heavily dependent on the level of contribution and expertise: ind At any point in time, the existing Repository Maintainers can unanimously nominate a strong community member to become a Repository Maintainer. 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. -Once 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. +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. [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/best-practice.md b/docs/maintainers/best-practice.md new file mode 100644 index 00000000000..2496e1ae42b --- /dev/null +++ b/docs/maintainers/best-practice.md @@ -0,0 +1,50 @@ +# Maintainer Best Practices + +## PR Types + +- Feature-work PR: A PR that implements an RFC, which usually involves relatively large set of changes. +- Regular PR: A bug fix or an enhancement change that are not backed by an RFC. + +## Review PRs + +- Ask the author to reword the PR title based on guidelines in [Contributing](../../.github/CONTRIBUTING.md). +- Ask the author to apply `[feature]` tag to trigger full test builds if it's necessary. +- Label the PR with `Breaking-Change`, `Documentation Needed` and `Area-XXX` as appropriate. +- When labelling a PR with `Review-Committee`, leave a detailed comment to summarize the issue you want the committee to look into. + It's recommended to include examples to explain/demonstrate behaviors. + +## Merge PRs + +- Use `Squash and merge` by default to keep clean commit history in Master branch. + + ![Squash Merge Example](./Images/squash-merge.png)    ![Squash Confirm Example](./Images/squash-confirm.png) + +- Use `Create a merge commit` for feature-work PRs **only if** the commit history of the PR is reasonably clean. + After using this option, GitHub will make it your default option for merging a PR. + Do remember to change the default back to `Squash and merge` as it will be useful next time. + + ![Merge Commit Example](./Images/merge-commit.png)    ![Merge Confirm Example](./Images/merge-commit-confirm.png) + +- Avoid `Rebase and merge` unless you have a strong argument for using it. + +- Before clicking `Confirm squash and merge` or `Confirm merge`, + make sure you run through the following steps: + + 1. The commit title should be a short summary of the PR. + + - When merging with the `Squash and merge` option, + the PR title will be used as the commit title by default. + **Reword the title as needed** to make sure it makes sense (can be used without change in `CHANGELOG.md`). + + - When merging with the `Create a merge commit` option, + the default commit title would be `Merge pull request XXX from YYY`. + **Replace it with a short summary of the PR**, and add the PR number to the end, like `(#1234)`. + + 1. The optional extended description is required for feature-work PRs, or regular PRs with breaking changes. + For other PRs, it's not required but good to have based on the judgement of the maintainer. + + - If a PR introduces breaking changes from the previous stable release, + make sure you put the tag `[breaking change]` at the first line of the extended description, + and start the description text from the second line. + + 1. Use the present tense and imperative mood for both the commit title and description. diff --git a/docs/maintainers/issue-management.md b/docs/maintainers/issue-management.md index 51f5c580468..0cc8eb00e37 100644 --- a/docs/maintainers/issue-management.md +++ b/docs/maintainers/issue-management.md @@ -2,12 +2,13 @@ ## Security Vulnerabilities -If you believe that there is a security vulnerability in PowerShell Core, -it **must** be reported to [secure@microsoft.com](https://technet.microsoft.com/en-us/security/ff852094.aspx) to allow for [Coordinated Vulnerability Disclosure](https://technet.microsoft.com/en-us/security/dn467923). -**Only** file an issue, if secure@microsoft.com has confirmed filing an issue is appropriate. +If you believe that there is a security vulnerability in PowerShell, +first follow the [vulnerability issue reporting policy](../../.github/SECURITY.md) before submitting an issue. ## 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. @@ -21,7 +22,7 @@ We use the following labels for issue classifications: * `Issue-Enhancement`: the issue is more of a feature request than a bug. * `Issue-Meta`: an issue used to track multiple issues. * `Issue-Question`: ideally support can be provided via other mechanisms, - but sometimes folks to open an issue to get a question answered and we will use this label for such issues. + but sometimes folks do open an issue to get a question answered and we will use this label for such issues. [ln-rfc]: https://github.com/PowerShell/PowerShell-RFC @@ -38,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 like FileSystem, Certificates, Registry, etc... -* `Area-PSReadline`: PSReadline related issues -* `Area-Remoting`: PSRP issues with any transport layer -* `Area-Security`: security related areas like [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 @@ -80,24 +84,27 @@ The following labels are used on PRs: * `Review - Needed` : The PR is being reviewed. Please see [Pull Request - Code Review](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---code-review) * `Review - Waiting on Author` : The PR was reviewed by the team and requires changes or comments from the author before being accepted. -* `Review - Abandoned` : The PR was not updated for significant number of days (the exact number could vary over time). +* `Review - Abandoned` : The PR was not updated for a significant number of days (the exact number could vary over time). Maintainers should look into such PRs and re-evaluate them. * `Review - Committee` : The PR/Issue needs a review from [powershell-committee](../community/governance.md#powershell-committee) ### Miscellaneous labels -* `Committee-Reviewed` : The PR/Issue has been reviewed by the [powershell-committee](../community/governance.md#powershell-committee) -* `Up-for-Grabs`: We've acknowledged the issue but have no immediate plans to address it. - If you're looking for a way to contribute, these issues can be a good place to start. -* `Blocked`: an issue cannot be addressed due to external factors, +* `Blocked` : An issue cannot be addressed due to external factors, but should not be closed because those external factors are temporary. -* `BVT/DRT`: an issue affecting or exposed by tests that have not been open sourced. -* `Porting`: an issue that affects a feature not yet ported to other platforms. -* `Usability`: this label is used to help us filter issues that might be higher priority - because they more directly affect the usability of a particular feature or area. -* `Changelog Needed`: The PR requires an addition to the changelog, +* `BVT/DRT` : An issue affecting or exposed by tests that have not been open sourced. +* `Changelog Needed` : The PR requires an addition to the changelog, and should be removed when it has been added. -* `Documentation Needed` : The PR has changes that require a documentation change or new documentation added to [PowerShell-Docs](http://github.com/powershell/powershell-docs) +* `Committee-Reviewed` : The PR/Issue has been reviewed by the [powershell-committee](../community/governance.md#powershell-committee) * `Compliance` : Issues with the compliance label are required to be fixed either in the long term or short term for Microsoft to continue to sign and release packages from the project as official Microsoft packages. The time frame in which it needs to be fixed should be identified in the issue. +* `Documentation Needed` : The PR has changes that require a documentation change or new documentation added to [PowerShell-Docs](https://github.com/powershell/powershell-docs) +* `First-Time-Issue` : An issue that is identified as being easy and a good candidate for first time contributors +* `Hackathon` or `Hacktoberfest` : An issue that would be a good candidate for hackathons such as `Hacktoberfest` or `Hackillinois` +* `Porting` : An issue that affects a feature not yet ported to other platforms. +* `Up-for-Grabs` : We've acknowledged the issue but have no immediate plans to address it. + If you're looking for a way to contribute, these issues can be a good place to start. +* `Usability` : This label is used to help us filter issues that might be higher priority + because they more directly affect the usability of a particular feature or area. +* `Waiting - DotNetCore` : An issue waiting on a fix/change in DotNetCore. diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index c345acab296..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,22 +165,15 @@ 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 -In the `release` branch, run `Publish-NuGetFeed` to generate PowerShell NuGet packages: - -```powershell -# Assume the to-be-used release tag is 'v6.0.0-beta.1' -Publish-NuGetFeed -ReleaseTag 'v6.0.0-beta.1' -``` - -PowerShell NuGet packages and the corresponding symbol packages will be generated at `PowerShell/nuget-artifacts` by default. -Currently the NuGet packages published to [powershell-core feed][ps-core-feed] only contain assemblies built for Windows. -Maintainers are working on including the assemblies built for non-Windows platforms. +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]. [ps-core-feed]: https://powershell.myget.org/gallery/powershell-core @@ -197,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'. @@ -207,24 +198,25 @@ GitHub will see the tag and present it as an option when creating a new [release Start the release, use the annotated tag's summary as the title, and save the release as a draft while you upload the binary packages. -[semver]: http://semver.org/ +[semver]: https://semver.org/ [tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging [release]: https://help.github.com/articles/creating-releases/ ## Homebrew -After the release, you can update homebrew formula. - -On macOS: - -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 reinstall powershell`, make sure that powershell was updates successfully -1. Commit your changes, send a PR to [homebrew-cask](https://github.com/caskroom/homebrew-cask) +After the release, update homebrew formula. +You need macOS to do it. + +There are 2 homebrew formulas: main and preview. + +### Main + +Update it on stable releases. + +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. 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 3766827c575..00000000000 --- a/docs/testing-guidelines/CodeCoverageAnalysis.md +++ /dev/null @@ -1,139 +0,0 @@ -# Code coverage analysis for commit [2ae5d07](https://codecov.io/gh/PowerShell/PowerShell/tree/c7b959bd6e5356fbbd395f22ba0c6cba49f354f6/src) - -Code coverage runs are enabled on daily Windows builds for PowerShell Core 6.0. -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 06/18/2017 - -| Assembly | Hit % | -| -------- |:-----:| -| Microsoft.PowerShell.Commands.Diagnostics | 58.01% | -| Microsoft.PowerShell.Commands.Management | 32.02% | -| Microsoft.PowerShell.Commands.Utility | 67.55% | -| Microsoft.PowerShell.ConsoleHost | 41.15% | -| Microsoft.PowerShell.CoreCLR.AssemblyLoadContext | 97.65% | -| Microsoft.PowerShell.CoreCLR.Eventing | 29.91% | -| Microsoft.PowerShell.LocalAccounts | 86.35% | -| Microsoft.PowerShell.PSReadLine | 10.18% | -| Microsoft.PowerShell.Security | 44.44% | -| Microsoft.WSMan.Management | 4.91% | -| System.Management.Automation | 50.42% | -| Microsoft.WSMan.Runtime/WSManSessionOption.cs | 100% | -| powershell/Program.cs | 100% | - -## Hot Spots with missing coverage - -### Microsoft.PowerShell.Commands.Management - -- [ ] CDXML cmdlet coverage. It is 0% as CDXML is broken due to CoreCLR [issue](https://github.com/dotnet/corefx/issues/18877). -- [ ] Add tests for *-Computer cmdlets [#4146](https://github.com/PowerShell/PowerShell/issues/4146) -- [ ] Add tests for *-Service cmdlets [#4147](https://github.com/PowerShell/PowerShell/issues/4147) -- [ ] Add tests for *-Item cmdlets. Especially for literal paths and error cases. [#4148](https://github.com/PowerShell/PowerShell/issues/4148) -- [ ] Add tests for Get-Content -Tail. [#4150](https://github.com/PowerShell/PowerShell/issues/4150) -- [ ] 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 Trace-Command. Especially Trace-Command -Expression [#4151](https://github.com/PowerShell/PowerShell/issues/4151) -- [ ] Add tests for ConvertTo-XML serialization of PSObjects [#4152](https://github.com/PowerShell/PowerShell/issues/4152) -- [ ] Add tests for Debug-Runspace [#4153](https://github.com/PowerShell/PowerShell/issues/4153) -- [ ] Add tests for New-Object for ArgumentList, ComObject [#4154](https://github.com/PowerShell/PowerShell/issues/4154) - -### 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.PSReadLine - -- [ ] We need tests from PSReadline repo or ignore coverage data for this module. (This will be filtered out.) - -### 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. Issue #[3565](https://github.com/PowerShell/PowerShell/issues/3565) - -#### CIMSupport - -- [ ] Missing coverage possibly due to: CoreCLR [issue](https://github.com/dotnet/corefx/issues/18877). -[4159](https://github.com/PowerShell/PowerShell/issues/4159) - -#### Engine - -- [ ] Add tests for COM. [#4154](https://github.com/PowerShell/PowerShell/issues/4154) -- [ ] Add tests for Tab Completion of various types of input. [#4160](https://github.com/PowerShell/PowerShell/issues/4160) -- [ ] Add tests for Import-Module / Get-Module over PSRP and CIMSession. [#4161](https://github.com/PowerShell/PowerShell/issues/4161) -- [ ] Add tests for debugging PS Jobs.[#4153](https://github.com/PowerShell/PowerShell/issues/4153) -- [ ] Add test for -is, -isnot, -contains, -notcontains and -like operators.[#4162](https://github.com/PowerShell/PowerShell/issues/4162) -- [ ] Remove Snapin code from CommandDiscovery. Issue #[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 tests for Get-Command -ShowCommandInfo [#4163](https://github.com/PowerShell/PowerShell/issues/4163) -- [ ] Add tests for Proxy Commands [#4164](https://github.com/PowerShell/PowerShell/issues/4164) -- [ ] 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) - -#### HelpSystem - -- [ ] Add tests for Alias help [#4167](https://github.com/PowerShell/PowerShell/issues/4167) -- [ ] Add tests for Class help [#4167](https://github.com/PowerShell/PowerShell/issues/4167) -- [ ] Add tests for SaveHelp [#4167](https://github.com/PowerShell/PowerShell/issues/4167) -- [ ] Add tests for HelpProviderWithCache [#4167](https://github.com/PowerShell/PowerShell/issues/4167) -- [ ] HelpProviderWithFullCache, potential dead code. [#4167](https://github.com/PowerShell/PowerShell/issues/4167) - -#### 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/Images/AppVeyor-Badge-Green.png b/docs/testing-guidelines/Images/AppVeyor-Badge-Green.png deleted file mode 100644 index 777ca2c4b44..00000000000 Binary files a/docs/testing-guidelines/Images/AppVeyor-Badge-Green.png and /dev/null differ diff --git a/docs/testing-guidelines/Images/AzDevOps-Success.png b/docs/testing-guidelines/Images/AzDevOps-Success.png new file mode 100644 index 00000000000..be0439f7c83 Binary files /dev/null and b/docs/testing-guidelines/Images/AzDevOps-Success.png differ diff --git a/docs/testing-guidelines/Images/CoverageReportFilter.PNG b/docs/testing-guidelines/Images/CoverageReportFilter.PNG new file mode 100644 index 00000000000..69e2e28ace5 Binary files /dev/null and b/docs/testing-guidelines/Images/CoverageReportFilter.PNG differ diff --git a/docs/testing-guidelines/Images/CoverageReportTop.PNG b/docs/testing-guidelines/Images/CoverageReportTop.PNG new file mode 100644 index 00000000000..f1737e9da5d Binary files /dev/null and b/docs/testing-guidelines/Images/CoverageReportTop.PNG differ diff --git a/docs/testing-guidelines/Images/Travis-CI-Badge-Green.png b/docs/testing-guidelines/Images/Travis-CI-Badge-Green.png deleted file mode 100644 index 9207e71cc13..00000000000 Binary files a/docs/testing-guidelines/Images/Travis-CI-Badge-Green.png and /dev/null differ diff --git a/docs/testing-guidelines/PowerShellCoreTestStatus.md b/docs/testing-guidelines/PowerShellCoreTestStatus.md index 6cb2853080f..ff8d225b7aa 100644 --- a/docs/testing-guidelines/PowerShellCoreTestStatus.md +++ b/docs/testing-guidelines/PowerShellCoreTestStatus.md @@ -12,7 +12,7 @@ Here are some statistics about our current test coverage: - More than 1200 tests have been created to validate the PowerShell Core cmdlets ## PowerShell Cmdlets -The follow table represents the test coverage of the PowerShell Core Cmdlets in relation to the delivery platform as of 8/17/2016: +The follow table represents the test coverage of the PowerShell Core Cmdlets in relation to the delivery platform as of 2016-08-17: | Name | Linux | Windows | Test Coverage | |---|---|---|:---:| diff --git a/docs/testing-guidelines/TestRoadmap.md b/docs/testing-guidelines/TestRoadmap.md index 0b0b92c6657..aea71be5aec 100644 --- a/docs/testing-guidelines/TestRoadmap.md +++ b/docs/testing-guidelines/TestRoadmap.md @@ -44,7 +44,6 @@ Running code coverage more often on full PowerShell is something that we should We currently run only those tests which are tagged `CI` excluding the tag `SLOW` as part of our continuous integration systems. This means roughly 1/3rd of our github tests are not being run on any regular schedule. In order to provide us with higher confidence in our code, we should be running *ALL* of our tests on a regular basis. -We have recently added to `AppVeyor` running all of our tests on a daily basis, but are not yet running these tests on Linux/Mac via `Travis`, which should be done. However, running the tests is only the first step, we need an easy way to be notified of test failures, and to track progress of those runs over time. Tracking this over time affords us the ability to see how our test count increases, implying an improvement in coverage. It also provides us mechanism whereby we can see trends in instability. @@ -80,7 +79,7 @@ We need to be sure that we can easily enable remoting for the non-Windows platfo * Our current multi-machine tests do not test the connection code, they simply execute test code remotely and retrieve results and assume a good connection. The infrastructure used for these tests is STEX which is not an open environment. We will need to create automation to create and configure the test systems in the test matrix and then invoke tests on them. -It is not clear that our current CI systems can accommodate our needs here as neither AppVeyor or Travis can supply us with all of the OS images needed. +It is not clear that our current CI systems can accommodate our needs here as Azure DevOps can supply us with all of the OS images needed. We may need to create our own heterogeneous environment in Azure, or look to other teams (MS Build Lab/Jenkins) for assistance. We need to investigate whether there are solutions available, and if not, design/implement an environment to meet our needs. @@ -89,9 +88,6 @@ We need to investigate whether there are solutions available, and if not, design Currently, we report against the simplest of KPI: * is the CI build error free (which is part of the PR/Merge process - and reported on our landing page) -We are also collecting data for a daily build, but not yet report on the following KPI -* is the daily build error free (we are running this on AppVeyor, we still need to do this for Travis-CI) - There are a number of KPIs which we could report on: * Code KPIs * What is the coverage (% blocks covered) of our `CI` tests @@ -194,4 +190,4 @@ Below is my suggestion for prioritization to reduce risk and improve confidence 6. Replace in-lab tests with PowerShell Core tests 7. Investigate feasibility of running current in-lab tests on PowerShell Core -These are [tracked](https://github.com/PowerShell/PowerShell/issues?utf8=%E2%9C%93&q=is%3Aissue%20%23testability%20) as issues \ No newline at end of file +These are [tracked](https://github.com/PowerShell/PowerShell/issues?utf8=%E2%9C%93&q=is%3Aissue%20%23testability%20) as issues diff --git a/docs/testing-guidelines/WritingPesterTests.md b/docs/testing-guidelines/WritingPesterTests.md index 5866ea9b115..5225dd94a22 100755 --- a/docs/testing-guidelines/WritingPesterTests.md +++ b/docs/testing-guidelines/WritingPesterTests.md @@ -1,133 +1,143 @@ -### Writing Pester Tests -Note that this document does not replace the documents found in the [Pester](https://github.com/pester/pester "Pester") project. +# Writing Pester Tests + +Note that this document does not replace the documents found in the [Pester](https://github.com/pester/pester) project. This is just some quick tips and suggestions for creating Pester tests for this project. The Pester community is vibrant and active, if you have questions about Pester or creating tests, the [Pester Wiki](https://github.com/pester/pester/wiki) has a lot of great information. As of January 2018, PowerShell Core is using Pester version 4 which has some changes from earlier versions. See [Migrating from Pester 3 to Pester 4](https://github.com/pester/Pester/wiki/Migrating-from-Pester-3-to-Pester-4) for more information. +When creating tests, keep the following in mind: +- Tests should not be overly complicated and test too many things. + - Boil down your tests to their essence, test only what you need. +- Tests should be as simple as they can. +- Tests should generally not rely on any other test. -When creating tests, keep the following in mind: -* Tests should not be overly complicated and test too many things - * boil down your tests to their essence, test only what you need -* Tests should be as simple as they can -* Tests should generally not rely on any other test +## Examples -Examples: -Here's the simplest of tests +Here's the simplest of tests: ```powershell Describe "A variable can be assigned and retrieved" { - It "Create a variable and make sure its value is correct" { + It "Creates a variable and makes sure its value is correct" { $a = 1 - $a | Should Be 1 + $a | Should -Be 1 } } ``` -If you need to do type checking, that can be done as well +If you need to do type checking, that can be done as well: ```powershell Describe "One is really one" { It "Compare 1 to 1" { $a = 1 - $a | Should Be 1 + $a | Should -Be 1 } It "1 is really an int" { $i = 1 - $i.GetType() | Should Be "int" + $i | Should -BeOfType System.Int32 } } ``` -alternatively, you could do the following: +If you are checking for proper errors, use the `Should -Throw -ErrorId` Pester syntax. +It checks against `FullyQualifiedErrorId` property, which is recommended because it does not change based on culture as an error message might. ```powershell -Describe "One is really one" { - It "Compare 1 to 1" { - $a = 1 - $a | Should Be 1 - } - It "1 is really an int" { - $i = 1 - $i.GetType() | Should Be ([System.Int32]) - } +... +It "Get-Item on a nonexisting file should have error PathNotFound" { + { Get-Item "ThisFileCannotPossiblyExist" -ErrorAction Stop } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand" } ``` -If you are checking for proper errors, use the `ShouldBeErrorId` helper defined in HelpersCommon.psm1 module which is in your path if you import `build.psm1`. -Checking against `FullyQualifiedErrorId` is recommended because it does not change based on culture as an error message might. +Note that if `Get-Item` were to succeed, the test will fail. + +However, if you need to check the `InnerException` or other members of the ErrorRecord, you should use `-PassThru` parameter: ```powershell -... -It "Get-Item on a nonexisting file should have error PathNotFound" { - { Get-Item "ThisFileCannotPossiblyExist" -ErrorAction Stop } | ShouldBeErrorId "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand" +It "InnerException sample" { + $e = { Invoke-WebRequest https://expired.badssl.com/ } | Should -Throw -ErrorId "WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand" -PassThru + $e.Exception.InnerException.NativeErrorCode | Should -Be 12175 } ``` -Note that if get-item were to succeed, a different FullyQualifiedErrorId would be thrown and the test will fail. -This is the suggested path because Pester wants to check the error message, which will likely not work here because of localized builds, but the FullyQualifiedErrorId is constant regardless of the locale. +## Describe/Context/It -However, if you need to check the `InnerException` or other members of the ErrorRecord, the recommended pattern to use is: +For creation of PowerShell tests, the `Describe` block is the level of granularity suggested and one of three tags should be used: `CI`, `Feature`, or `Scenario`. -```powershell - It "InnerException sample" { +If the tag is not provided, the build process will fail. - $e = { Invoke-WebRequest https://expired.badssl.com/ } | ShouldBeErrorId "WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand" - $e.Exception.InnerException.NativeErrorCode | Should Be 12175 - ... - } -``` +### Describe + +Creates a logical group of tests. +All `Mocks` and `TestDrive` contents defined within a `Describe` block are scoped to that `Describe`; +they will no longer be present when the `Describe` block exits. +A `Describe` block may contain any number of `Context` and `It` blocks. + +### Context + +Provides logical grouping of `It` blocks within a single `Describe` block. Any `Mocks` defined inside a `Context` are removed at the end of the `Context` scope, as are any files or folders added to the `TestDrive` during the `Context` block's execution. + +Any `BeforeEach` or `AfterEach` blocks defined inside a `Context` also only apply to tests within that `Context`. + +### It + +The `It` block is intended to be used inside of a `Describe` or `Context` block. If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of the `It` block is the appropriate location for an assert. -### Describe/Context/It -For creation of PowerShell tests, the Describe block is the level of granularity suggested and one of three tags should be used: "CI", "Feature", or "Scenario". If the tag is not provided, tests in that describe block will be run any time tests are executed. +The convention is to assert a single expectation for each `It` block. The code inside of the `It` block should throw a terminating error if the expectation of the test is not met and thus cause the test to fail. -#### Describe -Creates a logical group of tests. All Mocks and TestDrive contents defined within a Describe block are scoped to that Describe; they will no longer be present when the Describe block exits. A `Describe block may contain any number of Context and It blocks. +The name of the `It` block should expressively state the expectation of the test. -#### Context -Provides logical grouping of It blocks within a single Describe block. Any Mocks defined inside a Context are removed at the end of the Context scope, as are any files or folders added to the TestDrive during the Context block's execution. Any BeforeEach or AfterEach blocks defined inside a Context also only apply to tests within that Context . +## Admin privileges in tests -#### It -The It block is intended to be used inside of a Describe or Context Block. If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of the It block is the appropriate location for an assert. The convention is to assert a single expectation for each It block. The code inside of the It block should throw a terminating error if the expectation of the test is not met and thus cause the test to fail. The name of the It block should expressively state the expectation of the test. +Tests that require admin privileges **on Windows** must be additionally marked with `RequireAdminOnWindows` Pester tag. -### Admin privileges in tests -Tests that require admin privileges **on windows** should be additionally marked with 'RequireAdminOnWindows' Pester tag. -In the AppVeyor CI, we run two different passes: +In the Azure DevOps Windows CI, we run two different passes: -- The pass with exclusion of tests with 'RequireAdminOnWindows' tag -- The pass where we run only 'RequireAdminOnWindows' tests +- The pass with exclusion of `RequireAdminOnWindows` tagged tests. +- The pass where only `RequireAdminOnWindows` tagged tests are being executed. In each case, tests are executed with appropriate privileges. -### Selected Features +Tests that need to be run with sudo **on Unix systems** must be additionally marked with `RequireSudoOnUnix` Pester tag. -#### Test Drive -A PSDrive is available for file activity during a tests and this drive is limited to the scope of a single Describe block. The contents of the drive are cleared when a context block is exited. -A test may need to work with file operations and validate certain types of file activities. It is usually desirable not to perform file activity tests that will produce side effects outside of an individual test. Pester creates a PSDrive inside the user's temporary drive that is accessible via a names PSDrive TestDrive:. Pester will remove this drive after the test completes. You may use this drive to isolate the file operations of your test to a temporary store. +`RequireSudoOnUnix` tag takes precedence over all other tags like `CI`, `Feature`, etc. (which are ignored when `RequireSudoOnUnix` is present). +Tests tagged with `RequireSudoOnUnix` will run as a separate pass for any Unix test. + +## Selected Features + +### Test Drive + +A `PSDrive` is available for file activity during a test and this drive is limited to the scope of a single `Describe` block. The contents of the drive are cleared when a `Context` block is exited. + +A test may need to work with file operations and validate certain types of file activities. It is usually desirable not to perform file activity tests that will produce side effects outside of an individual test. + +Pester creates a `PSDrive` inside the user's temporary drive that is accessible via `TestDrive:` or `$TestDrive`. **Pester will remove** this drive after the test completes. +You may use this drive to isolate the file operations of your test to a temporary store. The following example illustrates the feature: ```powershell function Add-Footer($path, $footer) { - Add-Content $path -Value $footer + Add-Content $path -Value $footer } Describe "Add-Footer" { - $testPath="TestDrive:\test.txt" - Set-Content $testPath -value "my test text." - Add-Footer $testPath "-Footer" - $result = Get-Content $testPath + $testPath="TestDrive:\test.txt" + Set-Content $testPath -value "my test text." + Add-Footer $testPath "-Footer" + $result = Get-Content $testPath - It "adds a footer" { - (-join $result) | Should Be("my test text.-Footer") - } + It "adds a footer" { + (-join $result) | Should -BeExactly "my test text.-Footer" + } } ``` -When this test completes, the contents of the TestDrive PSDrive will be removed. +When this test completes, the contents of the `TestDrive:` will be removed. -#### Parameter Generation +### Parameter Generation ```powershell $testCases = @( @@ -138,30 +148,34 @@ $testCases = @( ) Describe "A test" { - It " -xor should be " -testcase $testcases { + It " -xor should be " -TestCases $testCases { param ($a, $b, $ExpectedResult) - $a -xor $b | Should Be $ExpectedResult + $a -xor $b | Should -Be $ExpectedResult } } ``` -You can also construct loops and pass values as parameters, including the expected value, but Pester does this for you. +### Mocking + +Mocks the behavior of an existing command with an alternate implementation. This creates new behavior for any existing command within the scope of a `Describe` or `Context` block. +The function allows you to specify a script block that will become the command's new behavior. -#### Mocking -Mocks the behavior of an existing command with an alternate implementation. This creates new behavior for any existing command within the scope of a Describe or Context block. The function allows you to specify a script block that will become the command's new behavior. The following example illustrates simple use: ```powershell Context "Get-Random is not random" { - Mock Get-Random { return 3 } - It "Get-Random returns 3" { - Get-Random | Should Be 3 - } + Mock Get-Random { return 3 } + + It "Get-Random returns 3" { + Get-Random | Should -Be 3 } +} ``` -More information may be found on the [wiki](https://github.com/pester/Pester/wiki/Mock) +More information may be found [here](https://github.com/pester/Pester/wiki/Mock). + ### Free Code in a Describe block + Code execution in Pester can be very subtle and can cause issues when executing test code. The execution of code which lays outside of the usual code blocks may not happen as you expect. Consider the following: ```powershell @@ -181,7 +195,7 @@ Describe it { Write-Host -for DarkRed "Before It" It "should not be a surprise" { - 1 | should be 1 + 1 | should -Be 1 } Write-Host -for DarkRed "After It" } @@ -192,7 +206,7 @@ Describe it { } ``` -Now, when run, you can see the execution schedule +Now, when run, you can see the execution schedule: ``` PS# invoke-pester c:\temp\pester.demo.tests.ps1 @@ -218,11 +232,19 @@ Tests completed in 79ms Passed: 1 Failed: 0 Skipped: 0 Pending: 0 ``` -The DESCRIBE BeforeAll block is executed before any other code even though it was at the bottom of the Describe block, so if state is set elsewhere in the describe BLOCK, that state will not be visible (as the code will not yet been run). Notice, too, that the BEFOREALL block in Context is executed before any other code in that block. -Generally, you should have code reside in one of the code block elements of `[Before|After][All|Each]`, especially if those block rely on state set by free code elsewhere in the block. +The `Describe` - `BeforeAll` block is executed before any other code even though it was at the bottom of the `Describe` block. +So if some state is set elsewhere in the `Describe` block, that state will not yet be visible (as the code will not yet been run). + +Notice, too, that the `BeforeAll` block in `Context` is executed before any other code in that block. +Generally, you should have code reside in one of the code block elements of `BeforeAll`, `BeforeEach`, `AfterEach` and/or `AfterAll`, especially if those blocks rely on some state set by free code elsewhere in the block. + +### Skipping Tests in Bulk + +Sometimes it is beneficial to skip all the tests in a particular `Describe` block. +For example, tests which are not applicable to a platform could be skipped, and they would be reported as skipped. + +The following is an example of how this may be done: -#### Skipping tests in bulk -Sometimes it is beneficial to skip all the tests in a particular `Describe` block. For example, tests which are not applicable to a platform could be skipped, and they would be reported as skipped. The following is an example of how this may be done: ```powershell Describe "Should not run these tests on non-Windows platforms" { BeforeAll { @@ -236,23 +258,25 @@ Describe "Should not run these tests on non-Windows platforms" { } Context "Block 1" { It "This block 1 test 1" { - 1 | should be 1 + 1 | should -Be 1 } It "This is block 1 test 2" { - 1 | should be 1 + 1 | should -Be 1 } } Context "Block 2" { It "This block 2 test 1" { - 2 | should be 1 + 2 | should -Be 1 } It "This is block 2 test 2" { - 2 | should be 1 + 2 | should -Be 1 } } } ``` + Here is the output when run on a Linux distribution: + ``` Describing Should not run these tests on non-Windows platforms Context Block 1 @@ -262,7 +286,9 @@ Describing Should not run these tests on non-Windows platforms [!] This block 2 test 1 73ms [!] This is block 2 test 2 6ms ``` + and here is the output when run on a Windows distribution: + ``` Describing Should not run these tests on non-Windows platforms Context Block 1 @@ -272,41 +298,40 @@ Describing Should not run these tests on non-Windows platforms [-] This block 2 test 1 52ms Expected: {1} But was: {2} - 22: 2 | should be 1 + 22: 2 | should -Be 1 at , : line 22 [-] This is block 2 test 2 77ms Expected: {1} But was: {2} - 25: 2 | should be 1 + 25: 2 | should -Be 1 at , : line 25 ``` -this technique uses the `$PSDefaultParameterValues` feature of PowerShell to temporarily set the It block parameter `-skip` to true (or in the case of Windows, it is not set at all) - +This technique uses the `$PSDefaultParameterValues` feature of PowerShell to temporarily set the `It` block parameter `-skip` to true (or in the case of Windows, it is not set at all) -#### Multi-line strings +### Multi-line strings -You may want to have a test like +You may want to have a test like: ```powershell It 'tests multi-line string' { - Get-MultiLineString | Should Be @' + Get-MultiLineString | Should -Be @' first line second line '@ } ``` -There are problems with using here-strings with verifying the output results. +There are problems with using multi-line strings with verifying the output results. The reason for it are line-ends. They cause problems for two reasons: -* They are different on different platforms (`\r\n` on windows and `\n` on unix). -* Even on the same system, they depends on the way how the repo was cloned. +- They are different on different platforms (`\r\n` on Windows and `\n` on Unix). +- Even on the same system, they depend on the way how the repo was cloned (local git configuration). -Particularly, in the default AppVeyour CI windows image, you will get `\n` line ends in all your files. -That causes problems, because at runtime `Get-MultiLineString` would likely produce `\r\n` line ends on windows. +Particularly, in the default Azure DevOps CI Windows image, you will get `\n` line ends in all your files. +That causes problems, because at runtime `Get-MultiLineString` would likely produce `\r\n` line ends on Windows. Some workaround could be added, but they are sub-optimal and make reading test code harder. @@ -317,7 +342,7 @@ function normalizeEnds([string]$text) } It 'tests multi-line string' { - normalizeEnds (Get-MultiLineString) | Should Be (normalizeEnds @' + normalizeEnds (Get-MultiLineString) | Should -Be (normalizeEnds @' first line second line '@) @@ -327,43 +352,45 @@ second line When appropriate, you can avoid creating multi-line strings at the first place. These commands create an array of strings: -* `Get-Content` -* `Out-String -Stream` - -Pester Do and Don't -=================== - -## Do -1. Name your files .tests.ps1 -2. Keep tests simple - 1. Test only what you need - 2. Reduce dependencies -3. Be sure to tag your `Describe` blocks based on their purpose - 1. Tag `CI` indicates that it will be run as part of the continuous integration process. These should be unit test like, and generally take less than a second. - 2. Tag `Feature` indicates a higher level feature test (we will run these on a regular basis), for example, tests which go to remote resources, or test broader functionality - 3. Tag `Scenario` indicates tests of integration with other features (these will be run on a less regular basis and test even broader functionality than feature tests. -4. Make sure that `Describe`/`Context`/`It` descriptions are useful - 1. The error message should not be the place where you describe the test -5. Use `Context` to group tests - 1. Multiple `Context` blocks can help you group your test suite into logical sections -6. Use `BeforeAll`/`AfterAll`/`BeforeEach`/`AfterEach` instead of custom initiators -7. Prefer Try-Catch for expected errors and check $_.fullyQualifiedErrorId (don't use `should throw`) -8. Use `-testcases` when iterating over multiple `It` blocks -9. Use code coverage functionality where appropriate -10. Use `Mock` functionality when you don't have your entire environment -11. Avoid free code in a `Describe` block - 1. Use `[Before|After][Each|All]` see [Free Code in a Describe block](WritingPesterTests.md#free-code-in-a-describe-block) -12. Avoid creating or using test files outside of TESTDRIVE: - 1. TESTDRIVE: has automatic clean-up -13. Keep in mind that we are creating cross platform tests - 1. Avoid using the registry - 2. Avoid using COM -14. Avoid being too specific about the _count_ of a resource as these can change platform to platform - 1. ex: checking for the count of loaded format files, check rather for format data for a specific type - -## Don't -1. Don't have too many evaluations in a single It block - 1. The first `Should` failure will stop that block -2. Don't use `Should` outside of an `It` Block -3. Don't use the word "Error" or "Fail" to test a positive case - 1. ex: "Get-ChildItem TESTDRIVE: shouldn't fail", rather "Get-ChildItem should be able to retrieve file listing from TESTDRIVE" +- `Get-Content` +- `Out-String -Stream` + +## Pester Do and Don't + +### Do + +1. Name your file `.tests.ps1`. +2. Keep tests simple: + - Test only what you need. + - Reduce dependencies. +3. Be sure to tag your `Describe` blocks based on their purpose: + - Tag `CI` indicates that it will be run as part of the continuous integration process. These should be unit test like, and generally take less than a second. + - Tag `Feature` indicates a higher level feature test (we will run these on a regular basis), for example, tests which go to remote resources, or test broader functionality. + - Tag `Scenario` indicates tests of integration with other features (these will be run on a less regular basis and test even broader functionality than feature tests. +4. Make sure that `Describe`/`Context`/`It` descriptions are useful. + - The error message should not be the place where you describe the test. +5. Use `Context` to group tests. + - Multiple `Context` blocks can help you group your test suite into logical sections. +6. Use `BeforeAll`/`BeforeEach`/`AfterEach`/`AfterAll` instead of custom initiators. +7. Use `Should -Throw -ErrorId` to check for expected errors. +8. Use `-TestCases` when iterating over multiple `It` blocks. +9. Use code coverage functionality where appropriate. +10. Use `Mock` functionality when you don't have your entire environment. +11. Avoid free code in a `Describe` block. + - Use `BeforeAll`/`BeforeEach`/`AfterEach`/`AfterAll`. + - See [Free Code in a Describe block](WritingPesterTests.md#free-code-in-a-describe-block) +12. Avoid creating or using test files outside of `TESTDRIVE:`. + - `TESTDRIVE:` has automatic clean-up. +13. Keep in mind that we are creating cross platform tests. + - Avoid using the registry. + - Avoid using COM. +14. Avoid being too specific about the _count_ of a resource as these can change platform to platform. + - Example: Avoid checking for the count of loaded format files, but rather check for format data for a specific type. + +### Don't + +1. Don't have too many evaluations in a single `It` block. + - The first `Should` failure will stop that block. +2. Don't use `Should` outside of an `It` Block. +3. Don't use the word "Error" or "Fail" to test a positive case. + - Example: Rephrase the negative sentence `"Get-ChildItem TESTDRIVE: shouldn't fail"` to the following positive case `"Get-ChildItem should be able to retrieve file listing from TESTDRIVE"`. diff --git a/docs/testing-guidelines/getting-code-coverage.md b/docs/testing-guidelines/getting-code-coverage.md new file mode 100644 index 00000000000..0f2bc8eb983 --- /dev/null +++ b/docs/testing-guidelines/getting-code-coverage.md @@ -0,0 +1,172 @@ +# Getting Code Coverage Analysis for PowerShell + +**Note: Code coverage is currently only supported on Windows, since we use OpenCover.** + +The PowerShell code base is configured to build with code coverage support using [OpenCover]. + +You can see the testing coverage of the current [`master`] branch build at any time at [codecov.io]. + +To run test coverage analysis of PowerShell on your own branch/machine, +you will need to take the following steps +(and be aware that running the code coverage analysis can take as long as 8 hours). + +## Running tests with code coverage analysis + +**First**: Open PowerShell in an **elevated** session. +OpenCover needs elevated privileges to work. + +Now, in PowerShell: + +```powershell +# Go to your PowerShell build directory root +PS> Set-Location "C:\Path\to\powershell\build\dir" + +# Import the PowerShell build module +PS> Import-Module .\build.psm1 + +# Build PowerShell. You may need to add other flags here like +# -ResGen or -Restore +PS> Start-PSBuild -Configuration CodeCoverage -Clean -PsModuleRestore + +# Now ensure Pester is installed +PS> Restore-PSPester + +# We also need to build the test executor +PS> Publish-PSTestTools + +# Import the OpenCover module +PS> Import-Module $PWD\test\tools\OpenCover + +# Install OpenCover to a temporary directory +PS> Install-OpenCover -TargetDirectory $env:TEMP -Force + +# Finally, run the tests with code coverage analysis. +# If you want to run only the continuous integration tests, +# add -CIOnly, which will take less time +PS> Invoke-OpenCover -OutputLog coverage.xml -OpenCoverPath $env:TEMP\OpenCover +``` + +## Examining the code coverage data + +Once the code coverage test run is done, you'll want to examine the data: + +```powershell +# Collect the coverage data using Get-CodeCoverage from the OpenCover +# module that was imported above. This operation is generally expensive +# to compute, so worth storing in a variable +PS> $coverageData = Get-CodeCoverage .\coverage.xml + +# Take a look at a summary of the results +PS> $coverageData.CoverageSummary + +NumSequencePoints : 298237 +VisitedSequencePoints : 125949 +NumBranchPoints : 101477 +VisitedBranchPoints : 39389 +SequenceCoverage : 42.23 +BranchCoverage : 38.82 +MaxCyclomaticComplexity : 393 +MinCyclomaticComplexity : 1 +VisitedClasses : 1990 +NumClasses : 3187 +VisitedMethods : 15115 +NumMethods : 32517 + +# You can also view results by assembly +PS> $coverageData.Assembly | Format-Table AssemblyName,Branch,Sequence + +AssemblyName Branch Sequence +------------ ------ -------- +pwsh 100 100 +Microsoft.PowerShell.ConsoleHost 21.58 23.32 +System.Management.Automation 41.22 45.01 +Microsoft.PowerShell.CoreCLR.Eventing 1.88 2.03 +Microsoft.PowerShell.Security 17.32 20.09 +Microsoft.PowerShell.Commands.Utility 20.14 21.39 +Microsoft.PowerShell.Commands.Management 43.05 43.39 +Microsoft.WSMan.Management 52.58 56.98 +Microsoft.WSMan.Runtime 80.95 80.33 +Microsoft.PowerShell.Commands.Diagnostics 0 0 +``` + +If you have made changes to tests or code +and run a second code coverage run, +you can also compare code coverage results: + +```powershell +PS> $cov1 = Get-CodeCoverage ./coverage1.xml +PS> $cov2 = Get-CodeCoverage ./coverage2.xml +PS> Compare-CodeCoverage -Run1 $cov1 -Run2 $cov2 + +AssemblyName Sequence SequenceDelta Branch BranchDelta +------------ -------- ------------- ------ ----------- +Microsoft.PowerShell.Security 20.09 -30.12 17.32 -31.63 +Microsoft.PowerShell.Commands.Management 43.39 9.10 43.05 11.59 +System.Management.Automation 45.04 -10.63 41.23 -11.07 +Microsoft.PowerShell.Commands.Utility 21.39 -47.22 20.14 -46.47 +Microsoft.PowerShell.Commands.Diagnostics 0 -51.91 0 -48.62 +Microsoft.PowerShell.ConsoleHost 23.32 -22.28 21.58 -22.47 +pwsh 100 0.00 100 0.00 +Microsoft.WSMan.Management 57.73 48.23 53.02 43.22 +Microsoft.WSMan.Runtime 80.33 -19.67 80.95 -19.05 +Microsoft.PowerShell.CoreCLR.Eventing 2.03 -32.74 1.88 -26.01 +``` + +To get file-specific coverage data, +you can use `Compare-FileCoverage`: + +```powershell +PS> Compare-FileCoverage -ReferenceCoverage $cov2 -DifferenceCoverage $cov1 -FileName LanguagePrimitives.cs + +FileName ReferenceCoverage DifferenceCoverage CoverageDelta +-------- ----------------- ------------------ ------------- +LanguagePrimitives.cs 53.68 69.03 15.34 +``` + +You can see more ways to use `Compare-CodeCoverage` and `Compare-FileCoverage` +by running: + +```powershell +PS> Get-Help Compare-CodeCoverage -Full +# Or +PS> Get-Help Compare-FileCoverage -Full +``` + +## Visualizing code coverage + +For a more detailed, graphical representation of the code coverage results, +you can use the ReportGenerator package. +This generates an HTML report of the coverage from the XML file +and will provide much more detail about the coverage analysis. +The package is available on [NuGet], +and you can install and run it as follows: + +```powershell +# Install ReportGenerator +PS> Find-Package ReportGenerator ` +>> -ProviderName Nuget ` +>> -Source "https://nuget.org/api/v2" ` +>> | Install-Package -Scope CurrentUser + +# Get the ReportGenerator executable path +# Make sure use the appropriate version number in the path +$ReportGenExe = "$HOME\AppData\Local\PackageManagement\NuGet\Packages\ReportGenerator.\tools\ReportGenerator.exe" + +# Run ReportGenerator +& $ReportGenExe -report:coverage.xml -targetdir:C:\temp\Coverage + +# Finally, open the report in your browser +Invoke-Item C:\temp\Coverage\index.htm +``` + +This should open a screen in the browser like this: +![Coverage report browser page](Images/CoverageReportTop.PNG) + +The main report, which is below the summary and risk hot spots, has +a filter functionality as well (when "Enable Filtering" is clicked on): +![Coverage report with filter on](Images/CoverageReportFilter.PNG) + +[OpenCover]: https://github.com/OpenCover/opencover +[codecov.io]: https://codecov.io +[`master`]: https://github.com/PowerShell/PowerShell +[NuGet]: https://nuget.org/packages/ReportGenerator diff --git a/docs/testing-guidelines/testing-guidelines.md b/docs/testing-guidelines/testing-guidelines.md index 92072071e94..e00c8352ee7 100755 --- a/docs/testing-guidelines/testing-guidelines.md +++ b/docs/testing-guidelines/testing-guidelines.md @@ -13,35 +13,21 @@ When adding new tests, place them in the directories as [outlined below](#test-l ## CI System -We use [AppVeyor](http://www.appveyor.com/) as a continuous integration (CI) system for Windows -and [Travis CI](http://www.travis-ci.com) for non-Windows platforms. +We use [Azure DevOps](https://azure.microsoft.com/en-us/solutions/devops) as a continuous integration (CI) system for Windows +and non-Windows platforms. -### AppVeyor - -In the `README.md` at the top of the repo, you can see AppVeyor badge. +In the `README.md` at the top of the repository, you can see Azure CI badge. It indicates the last build status of `master` branch. Hopefully, it's green: -![AppVeyor-Badge-Green.png](Images/AppVeyor-Badge-Green.png) - -This badge is **clickable**; you can open corresponding build page with logs, artifacts, and tests results. -From there you can easily navigate to the build history. - -### Travis CI - -Travis CI works similarly to AppVeyor. -For Travis CI there will be multiple badges. -The badges indicate the last build status of `master` branch for different platforms. -Hopefully, it's green: - -![Travis-CI-Badge-Green.png](Images/Travis-CI-Badge-Green.png) +![AzDevOps-Success.png](Images/AzDevOps-Success.png) This badge is **clickable**; you can open corresponding build page with logs, artifacts, and tests results. From there you can easily navigate to the build history. ### Getting CI Results -CI System builds (AppVeyor and Travis CI) and runs tests on every pull request and provides quick feedback about it. +CI System builds and runs tests on every pull request and provides quick feedback about it. ![AppVeyor-Github](Images/AppVeyor-Github.png) @@ -69,9 +55,11 @@ The Pester framework allows `Describe` blocks to be tagged, and our CI system re One of the following tags must be used: * `CI` - this tag indicates that the tests in the `Describe` block will be executed as part of the CI/PR process -* `Feature` - tests with this tag will not be executed as part of the CI/PR process, but they will be executed on a daily basis as part of a `cron` driven build. - They indicate that the test will be validating more behavior, or will be using remote network resources (ex: package management tests) * `Scenario` - this tag indicates a larger scale test interacting with multiple areas of functionality and/or remote resources, these tests are also run daily. +* `Feature` - tests with this tag will not be executed as part of the CI/PR process, + but they will be executed on a daily basis as part of a `cron` driven build. + They indicate that the test will be validating more behavior, + or will be using remote network resources (ex: package management tests) Additionally, the tag: @@ -100,22 +88,19 @@ 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 When working on new features or fixes, it is natural to want to run those tests locally before making a PR. -Three helper functions are part of the build.psm1 module to help with that: +These helper functions are part of the build.psm1 module to help with that: -* `Restore-PSPester` will restore Pester, which is needed to run `Start-PSPester` * `Start-PSPester` will execute all Pester tests which are run by the CI system * `Start-PSxUnit` will execute the available xUnit tests run by the CI system Our CI system runs these as well; there should be no difference between running these on your dev system, versus in CI. -Make sure that you run `Restore-PSPester` before running `Start-PSPester`, or it will fail to run. - When running tests in this way, be sure that you have started PowerShell with `-noprofile` as some tests will fail if the environment is not the default or has any customization. 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 815be4bfb90..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.4" + "version": "11.0.100-preview.3.26207.106" } } diff --git a/license_thirdparty_proprietary.txt b/license_thirdparty_proprietary.txt deleted file mode 100644 index 911a40b8dcd..00000000000 --- a/license_thirdparty_proprietary.txt +++ /dev/null @@ -1,771 +0,0 @@ -PowerShell 6.0 -Copyright (c) Microsoft Corporation. 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. - -IMPORTANT NOTICE: THE SOFTWARE ALSO CONTAINS THIRD PARTY AND OTHER -PROPRIETARY SOFTWARE THAT ARE GOVERNED BY SEPARATE LICENSE TERMS. BY ACCEPTING -THE LICENSE TERMS ABOVE, YOU ALSO ACCEPT THE LICENSE TERMS GOVERNING THE -THIRD PARTY AND OTHER SOFTWARE, WHICH ARE SET FORTH BELOW: -The following components listed are governed by the license terms that follow the -component(s) name: ------------------------------------------------------- -Libmi.so ------------------------------------------------------- -Copyright (c) Microsoft Corporation. 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. -The following components are governed by the MIT license, a copy of which appears -below the list of components: ------------------------------------------------------- -Newtonsoft.Json ------------------------------------------------------- -Copyright (c) 2007 James Newton-King -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. ---------------------------------------------------------- -Libuv v.1.9.0 ---------------------------------------------------------- - -https://raw.githubusercontent.com/aspnet/libuv-package/dev/content/License.txt - -This software is licensed to you by Microsoft Corporation under the original terms of -the copyright holder provided below: - -========================================= - -libuv is part of the Node project: http://nodejs.org/ -libuv may be distributed alone under Node's license: - -==== - -Copyright Joyent, Inc. and other Node contributors. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. - -==== - -This license applies to all parts of libuv that are not externally -maintained libraries. - -The externally maintained libraries used by libuv are: - - - tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license. - - - inet_pton and inet_ntop implementations, contained in src/inet.c, are -copyright the Internet Systems Consortium, Inc., and licensed under the ISC -license. - - - stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three - clause BSD license. - - - pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile - Communications AB. Three clause BSD license. - - - android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design Inc, -Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement - n° 289016). Three clause BSD license. - -========================================= - - ----------------------------------------------------- -• dotnet-test-xunit 2.2.0-preview2-build1029 -• xunit -• xunit.abstractions -• xunit.assert -• xunit.core -• xunit.extensibility.core -• xunit.extensibility.execution -• xunit.runner.reporters -• xunit.runner.utility ----------------------------------------------------- - -https://www.nuget.org/packages - -Copyright 2015 Outercurve Foundation - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://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. - - --------------------------------------------------- -• Microsoft.CodeAnalysis.Analyzers -• Microsoft.CodeAnalysis.Common -• Microsoft.CodeAnalysis.CSharp -• Microsoft.CodeAnalysis.VisualBasic -• Microsoft.CSharp -• Microsoft.DiaSymReader -• Microsoft.DiaSymReader.Native -• Microsoft.DotNet.Cli.Utils -• Microsoft.DotNet.InternalAbstractions -• Microsoft.DotNet.ProjectModel -• Microsoft.Extensions.DependencyModel -• Microsoft.Extensions.Testing.Abstractions -• Microsoft.NETCore -• Microsoft.NETCore.App -• Microsoft.NETCore.DotNetHost -• Microsoft.NETCore.DotNetHostPolicy -• Microsoft.NETCore.DotNetHostResolver -• Microsoft.NETCore.Jit -• Microsoft.NETCore.Platforms -• Microsoft.NETCore.Portable.Compatibility -• Microsoft.NETCore.Runtime -• Microsoft.NETCore.Runtime.CoreCLR -• Microsoft.NETCore.Runtime.Native -• Microsoft.NETCore.Targets -• Microsoft.NETCore.Windows.ApiSets -• Microsoft.VisualBasic -• Microsoft.Win32.Primitives -• Microsoft.Win32.Registry -• Microsoft.Win32.Registry.AccessControl -• NETStandard.Library -• runtime.any.System.Collections -• runtime.any.System.Diagnostics.Tools -• runtime.any.System.Diagnostics.Tracing -• runtime.any.System.Globalization -• runtime.any.System.Globalization.Calendars -• runtime.any.System.IO -• runtime.any.System.Reflection -• runtime.any.System.Reflection.Extensions -• runtime.any.System.Reflection.Primitives -• runtime.any.System.Resources.ResourceManager -• runtime.any.System.Runtime -• runtime.any.System.Runtime.Handles -• runtime.any.System.Runtime.InteropServices -• runtime.any.System.Text.Encoding -• runtime.any.System.Text.Encoding.Extensions -• runtime.any.System.Threading.Tasks -• runtime.any.System.Threading.Timer -• runtime.debian.8-x64.Microsoft.NETCore.DotNetHost -• runtime.debian.8-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.debian.8-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.debian.8-x64.Microsoft.NETCore.Jit -• runtime.debian.8-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.debian.8-x64.runtime.native.System -• runtime.debian.8-x64.runtime.native.System.IO.Compression -• runtime.debian.8-x64.runtime.native.System.Net.Http -• runtime.debian.8-x64.runtime.native.System.Net.Security -• runtime.debian.8-x64.runtime.native.System.Security.Cryptography -• runtime.native.System -• runtime.native.System.Data.SqlClient.sni -• runtime.native.System.IO.Compression -• runtime.native.System.Net.Http -• runtime.native.System.Net.Security -• runtime.native.System.Security.Cryptography -• runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHost -• runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.osx.10.10-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.osx.10.10-x64.Microsoft.NETCore.Jit -• runtime.osx.10.10-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.osx.10.10-x64.runtime.native.System -• runtime.osx.10.10-x64.runtime.native.System.IO.Compression -• runtime.osx.10.10-x64.runtime.native.System.Net.Http -• runtime.osx.10.10-x64.runtime.native.System.Net.Security -• runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography -• runtime.rhel.7-x64.Microsoft.NETCore.DotNetHost -• runtime.rhel.7-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.rhel.7-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.rhel.7-x64.Microsoft.NETCore.Jit -• runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.rhel.7-x64.runtime.native.System -• runtime.rhel.7-x64.runtime.native.System.IO.Compression -• runtime.rhel.7-x64.runtime.native.System.Net.Http -• runtime.rhel.7-x64.runtime.native.System.Net.Security -• runtime.rhel.7-x64.runtime.native.System.Security.Cryptography -• runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHost -• runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.ubuntu.14.04-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.ubuntu.14.04-x64.Microsoft.NETCore.Jit -• runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.ubuntu.14.04-x64.runtime.native.System -• runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression -• runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http -• runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security -• runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography -• runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHost -• runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.ubuntu.16.04-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.ubuntu.16.04-x64.Microsoft.NETCore.Jit -• runtime.ubuntu.16.04-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.ubuntu.16.04-x64.runtime.native.System -• runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression -• runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http -• runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security -• runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography -• runtime.unix.Microsoft.Win32.Primitives -• runtime.unix.System.Console -• runtime.unix.System.Diagnostics.Debug -• runtime.unix.System.IO.FileSystem -• runtime.unix.System.Net.Primitives -• runtime.unix.System.Net.Sockets -• runtime.unix.System.Private.Uri -• runtime.unix.System.Runtime.Extensions -• runtime.win.Microsoft.Win32.Primitives -• runtime.win.System.Console -• runtime.win.System.Diagnostics.Debug -• runtime.win.System.IO.FileSystem -• runtime.win.System.Net.Primitives -• runtime.win.System.Net.Sockets -• runtime.win.System.Runtime.Extensions -• runtime.win7-x64.Microsoft.NETCore.DotNetHost -• runtime.win7-x64.Microsoft.NETCore.DotNetHostPolicy -• runtime.win7-x64.Microsoft.NETCore.DotNetHostResolver -• runtime.win7-x64.Microsoft.NETCore.Jit -• runtime.win7-x64.Microsoft.NETCore.Runtime.CoreCLR -• runtime.win7-x64.Microsoft.NETCore.Windows.ApiSets -• runtime.win7-x64.runtime.native.System.Data.SqlClient.sni -• runtime.win7-x64.runtime.native.System.IO.Compression -• runtime.win7-x86.runtime.native.System.Data.SqlClient.sni -• runtime.win7.System.Private.Uri -• runtime.win81-x64.Microsoft.NETCore.Windows.ApiSets -• System.AppContext -• System.Buffers -• System.Collections -• System.Collections.Concurrent -• System.Collections.Immutable -• System.Collections.NonGeneric -• System.Collections.Specialized -• System.ComponentModel -• System.ComponentModel.Annotations -• System.ComponentModel.EventBasedAsync -• System.ComponentModel.Primitives -• System.ComponentModel.TypeConverter -• System.Console -• System.Data.Common -• System.Data.SqlClient -• System.Diagnostics.Contracts -• System.Diagnostics.Debug -• System.Diagnostics.DiagnosticSource -• System.Diagnostics.FileVersionInfo -• System.Diagnostics.Process -• System.Diagnostics.StackTrace -• System.Diagnostics.TextWriterTraceListener -• System.Diagnostics.Tools -• System.Diagnostics.TraceSource -• System.Diagnostics.Tracing -• System.Dynamic.Runtime -• System.Globalization -• System.Globalization.Calendars -• System.Globalization.Extensions -• System.IO -• System.IO.Compression -• System.IO.Compression.ZipFile -• System.IO.FileSystem -• System.IO.FileSystem.AccessControl -• System.IO.FileSystem.DriveInfo -• System.IO.FileSystem.Primitives -• System.IO.FileSystem.Watcher -• System.IO.MemoryMappedFiles -• System.IO.Packaging -• System.IO.Pipes -• System.IO.UnmanagedMemoryStream -• System.Linq -• System.Linq.Expressions -• System.Linq.Parallel -• System.Linq.Queryable -• System.Net.Http -• System.Net.Http.WinHttpHandler -• System.Net.NameResolution -• System.Net.NetworkInformation -• System.Net.Ping -• System.Net.Primitives -• System.Net.Requests -• System.Net.Security -• System.Net.Sockets -• System.Net.WebHeaderCollection -• System.Net.WebSockets -• System.Net.WebSockets.Client -• System.Numerics.Vectors -• System.ObjectModel -• System.Private.DataContractSerialization -• System.Private.ServiceModel -• System.Private.Uri -• System.Reflection -• System.Reflection.DispatchProxy -• System.Reflection.Emit -• System.Reflection.Emit.ILGeneration -• System.Reflection.Emit.Lightweight -• System.Reflection.Extensions -• System.Reflection.Metadata -• System.Reflection.Primitives -• System.Reflection.TypeExtensions -• System.Resources.Reader -• System.Resources.ResourceManager -• System.Runtime -• System.Runtime.CompilerServices.VisualC -• System.Runtime.Extensions -• System.Runtime.Handles -• System.Runtime.InteropServices -• System.Runtime.InteropServices.PInvoke -• System.Runtime.InteropServices.RuntimeInformation -• System.Runtime.Loader -• System.Runtime.Numerics -• System.Runtime.Serialization.Json -• System.Runtime.Serialization.Primitives -• System.Runtime.Serialization.Xml -• System.Security.AccessControl -• System.Security.Claims -• System.Security.Cryptography.Algorithms -• System.Security.Cryptography.Cng -• System.Security.Cryptography.Csp -• System.Security.Cryptography.Encoding -• System.Security.Cryptography.OpenSsl -• System.Security.Cryptography.Pkcs -• System.Security.Cryptography.Primitives -• System.Security.Cryptography.X509Certificates -• System.Security.Principal -• System.Security.Principal.Windows -• System.Security.SecureString -• System.ServiceModel.Duplex -• System.ServiceModel.Http -• System.ServiceModel.NetTcp -• System.ServiceModel.Primitives -• System.ServiceModel.Security -• System.ServiceProcess.ServiceController -• System.Text.Encoding -• System.Text.Encoding.CodePages -• System.Text.Encoding.Extensions -• System.Text.Encodings.Web -• System.Text.RegularExpressions -• System.Threading -• System.Threading.AccessControl -• System.Threading.Overlapped -• System.Threading.Tasks -• System.Threading.Tasks.Dataflow -• System.Threading.Tasks.Extensions -• System.Threading.Tasks.Parallel -• System.Threading.Thread -• System.Threading.ThreadPool -• System.Threading.Timer -• System.Xml.ReaderWriter -• System.Xml.XDocument -• System.Xml.XmlDocument -• System.Xml.XmlSerializer -• System.Xml.XPath -• System.Xml.XPath.XDocument -• System.Xml.XPath.XmlDocument -------------------------------------------------------- - -MICROSOFT SOFTWARE LICENSE TERMS -MICROSOFT .NET LIBRARY -These license terms are an agreement between Microsoft Corporation (or based on where you -live, one of its affiliates) and you. Please read them. They apply to the software named above, -which includes the media on which you received it, if any. The terms also apply to any Microsoft -• updates, -• supplements, -• Internet-based services, and -• support services -for this software, unless other terms accompany those items. If so, those terms apply. -BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. IF YOU DO NOT ACCEPT THEM, DO NOT -USE THE SOFTWARE. -IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE PERPETUAL RIGHTS BELOW. -1. INSTALLATION AND USE RIGHTS. -a. Installation and Use. You may install and use any number of copies of the software to design, -develop and test your programs. -b. Third Party Programs. The software may include third party programs that Microsoft, not the -third party, licenses to you under this agreement. Notices, if any, for the third party program are -included for your information only. -2. DATA. The software may collect information about you and your use of the software, and send that -to Microsoft. Microsoft may use this information to improve our products and services. You can learn -more about data collection and use in the help documentation and the privacy statement -at https://go.microsoft.com/fwlink/?LinkId=528096 . Your use of the software operates as your -consent to these practices. -3. ADDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS. -a. DISTRIBUTABLE CODE. The software is comprised of Distributable Code. “Distributable -Code” is code that you are permitted to distribute in programs you develop if you comply with -the terms below. -i . Right to Use and Distribute. -• You may copy and distribute the object code form of the software. -• Third Party Distribution. You may permit distributors of your programs to copy and -distribute the Distributable Code as part of those programs. -ii. Distribution Requirements. For any Distributable Code you distribute, you must -• add significant primary functionality to it in your programs; -• require distributors and external end users to agree to terms that protect it at least as -much as this agreement; -• display your valid copyright notice on your programs; and -• indemnify, defend, and hold harmless Microsoft from any claims, including attorneys’ -fees, related to the distribution or use of your programs. -iii. Distribution Restrictions. You may not -• alter any copyright, trademark or patent notice in the Distributable Code; -• use Microsoft’s trademarks in your programs’ names or in a way that suggests your -programs come from or are endorsed by Microsoft; -• include Distributable Code in malicious, deceptive or unlawful programs; or -• modify or distribute the source code of any Distributable Code so that any part of it -becomes subject to an Excluded License. An Excluded License is one that requires, as a -condition of use, modification or distribution, that -• the code be disclosed or distributed in source code form; or -• others have the right to modify it. -4. SCOPE OF LICENSE. The software is licensed, not sold. This agreement only gives you some rights to -use the software. Microsoft reserves all other rights. Unless applicable law gives you more rights despite -this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you -must comply with any technical limitations in the software that only allow you to use it in certain ways. -You may not -• work around any technical limitations in the software; -• reverse engineer, decompile or disassemble the software, except and only to the extent that -applicable law expressly permits, despite this limitation; -• publish the software for others to copy; -• rent, lease or lend the software; -• transfer the software or this agreement to any third party; or -• use the software for commercial software hosting services. -5. BACKUP COPY. You may make one backup copy of the software. You may use it only to reinstall the -software. -6. DOCUMENTATION. Any person that has valid access to your computer or internal network may copy -and use the documentation for your internal, reference purposes. -7. EXPORT RESTRICTIONS. The software is subject to United States export laws and regulations. You -must comply with all domestic and international export laws and regulations that apply to the software. -These laws include restrictions on destinations, end users and end use. For additional information, -see www.microsoft.com/exporting. -8. SUPPORT SERVICES. Because this software is “as is,” we may not provide support services for it. -9. ENTIRE AGREEMENT. This agreement, and the terms for supplements, updates, Internet-based -services and support services that you use, are the entire agreement for the software and support -services. -10. APPLICABLE LAW. -a. United States. If you acquired the software in the United States, Washington state law governs the -interpretation of this agreement and applies to claims for breach of it, regardless of conflict of laws -principles. The laws of the state where you live govern all other claims, including claims under state -consumer protection laws, unfair competition laws, and in tort. -b. Outside the United States. If you acquired the software in any other country, the laws of -that country apply. -11. LEGAL EFFECT. This agreement describes certain legal rights. You may have other rights under the -laws of your country. You may also have rights with respect to the party from whom you acquired the -software. This agreement does not change your rights under the laws of your country if the laws of your -country do not permit it to do so. -12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS-IS.” YOU BEAR THE RISK OF -USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES OR CONDITIONS. -YOU MAY HAVE ADDITIONAL CONSUMER RIGHTS OR STATUTORY GUARANTEES UNDER YOUR -LOCAL LAWS WHICH THIS AGREEMENT CANNOT CHANGE. TO THE EXTENT PERMITTED -UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES THE IMPLIED WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. -FOR AUSTRALIA – YOU HAVE STATUTORY GUARANTEES UNDER THE AUSTRALIAN CONSUMER -LAW AND NOTHING IN THESE TERMS IS INTENDED TO AFFECT THOSE RIGHTS. -13. LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. YOU CAN RECOVER FROM -MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT -RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, -INDIRECT OR INCIDENTAL DAMAGES. -This limitation applies to -• anything related to the software, services, content (including code) on third party Internet sites, or -third party programs; and -• claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, -or other tort to the extent permitted by applicable law. -It also applies even if Microsoft knew or should have known about the possibility of the damages. The above -limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of -incidental, consequential or other damages. -Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are -provided below in French. -Remarque : Ce logiciel étant distribué au Québec, Canada, certaines des clauses dans ce contrat sont fournies -ci-dessous en français. -EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute utilisation de ce -logiciel est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous pouvez -bénéficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne -peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, -d’adéquation à un usage particulier et d’absence de contrefaçon sont exclues. -LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES -DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de -dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation -pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. -Cette limitationconcerne: -• tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur des -sites Internet tiers ou dans des programmes tiers ; et -• les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité -stricte, de négligence ou d’une autre faute dans la limite autorisée par la loi en vigueur. -Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel dommage. -Si votre pays n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages indirects, -accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci-dessus ne -s’appliquera pas à votre égard. -EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits -prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de -votre pays si celles-ci ne le permettent pas. - - --------------------------------------------------------- -• 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 - -http://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. - - ---------------------------------------------------------- -• Microsoft.Management.Infrastructure.dll -• Microsoft.Management.Infrastructure.Native.dll -• Microsoft.Management.Infrastructure.Unmanaged.dll ----------------------------------------------------------- -MICROSOFT SOFTWARE LICENSE TERMS -MANAGEMENT.INFRASTRUCTURE.DLL -MANAGEMENT.INFRASTRUCTURE.NATIVE.DLL -MANAGEMENT.INFRASTRUCTURE.UNMANAGED.DLL - -These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They -apply to the software named above and any Microsoft services or software updates (except to the extent such -services or updates are accompanied by new or additional terms, in which case those different terms apply -prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU -COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU -ACCEPT THESE TERMS. -1. INSTALLATION AND USE RIGHTS -a) General. You may install and use any number of copies of the software on your devices. -b) Included Microsoft Applications. The software may include other Microsoft applications. These -license terms apply to those included applications, if any, unless other license terms are provided with -the other Microsoft applications. -c) Third Party Software. The software may include third party applications that are licensed to you -under this agreement or under their own terms. License terms, notices, and acknowledgements, if any, -for the third party applications may be accessible online at http://aka.ms/thirdpartynotices or in an -accompanying notices file. Even if such applications are governed by other agreements, the disclaimer, -limitations on, and exclusions of damages below also apply to the extent allowed by applicable law. -2. TIME-SENSITIVE SOFTWARE -a) Period. The software is time-sensitive and may stop running on a date that is defined in the software. -b) Notice. You may receive periodic reminder notices of this date through the software. -c) Access to data. You may not be able to access data used in the software when it stops running. -3. PRE-RELEASE SOFTWARE. The software is a pre-release version. It may not operate correctly. It may be -different from the commercially released version. -4. FEEDBACK. If you give feedback about the software to Microsoft, you give to Microsoft, without charge, -the right to use, share and commercialize your feedback in any way and for any purpose. You will not give -feedback that is subject to a license that requires Microsoft to license its software or documentation to -third parties because Microsoft includes your feedback in them. These rights survive this agreement. -5. DATA COLLECTION. The software may collect information about you and your use of the software and -send that to Microsoft. Microsoft may use this information to provide services and improve Microsoft’s -products and services. Your opt-out rights, if any, are described in the product documentation. Some -features in the software may enable collection of data from users of your applications that access or use the -software. If you use these features to enable data collection in your applications, you must comply with -applicable law, including getting any required user consent, and maintain a prominent privacy policy that -accurately informs users about how you use, collect, and share their data. You can learn more about -Microsoft’s data collection and use in the product documentation and the Microsoft Privacy Statement at -https://go.microsoft.com/fwlink/?LinkId=521839. You agree to comply with all applicable provisions of the -Microsoft Privacy Statement. -6. FONTS. While the software is running, you may use its fonts to display and print content. You may only (i) -embed fonts in content as permitted by the embedding restrictions in the fonts; and (ii) temporarily -download them to a printer or other output device to help print content. -7. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable -law gives you more rights despite this limitation, you will not (and have no right to): -a) work around any technical limitations in the software that only allow you to use it in certain ways; -b) reverse engineer, decompile or disassemble the software; -c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software; -d) use the software in any way that is against the law or to create or propagate malware; or -e) share, publish, distribute, or lend the software, provide the software as a stand-alone hosted solution -for others to use, or transfer the software or this agreement to any third party. -8. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations -that apply to the software, which include restrictions on destinations, end users, and end use. For further -information on export restrictions, visit http://aka.ms/exporting. -9. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for -the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind. -10. UPDATES. The software may periodically check for updates, and download and install them for you. You -may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system -to provide you with updates. You agree to receive these automatic updates without any additional notice. -Updates may not include or support all existing software features, services, or peripheral devices. -11. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, -updates, or third-party applications, is the entire agreement for the software. -12. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States -or Canada, the laws of the state or province where you live (or, if a business, where your principal place of -business is located) govern the interpretation of this agreement, claims for its breach, and all other claims -(including consumer protection, unfair competition, and tort claims), regardless of conflict of laws -principles. If you acquired the software in any other country, its laws apply. If U.S. federal jurisdiction exists, -you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, -Washington for all disputes heard in court. If not, you and Microsoft consent to exclusive jurisdiction and -venue in the Superior Court of King County, Washington for all disputes heard in court. -13. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may -have other rights, including consumer rights, under the laws of your state, province, or country. Separate -and apart from your relationship with Microsoft, you may also have rights with respect to the party from -which you acquired the software. This agreement does not change those other rights if the laws of your -state, province, or country do not permit it to do so. For example, if you acquired the software in one of the -below regions, or mandatory country law applies, then the following provisions apply to you: -a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this -agreement is intended to affect those rights. -b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the -automatic update feature, disconnecting your device from the Internet (if and when you re-connect to -the Internet, however, the software will resume checking for and installing updates), or uninstalling the -software. The product documentation, if any, may also specify how to turn off updates for your specific -device or software. -c) Germany and Austria. -i. Warranty. The properly licensed software will perform substantially as described in -any Microsoft materials that accompany the software. However, Microsoft gives no -contractual guarantee in relation to the licensed software. -ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based -on the Product Liability Act, as well as, in case of death or personal or physical injury, -Microsoft is liable according to the statutory law. -Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in -breach of such material contractual obligations, the fulfillment of which facilitate the due performance -of this agreement, the breach of which would endanger the purpose of this agreement and the -compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases -of slight negligence, Microsoft will not be liable for slight negligence. -14. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING -IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT -PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, -INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. -15. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING -DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM -MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER -ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR -INCIDENTAL DAMAGES. -This limitation applies to (a) anything related to the software, services, content (including code) on -third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, -guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to -the extent permitted by applicable law. -It also applies even if Microsoft knew or should have known about the possibility of the damages. -The above limitation or exclusion may not apply to you because your state, province, or country -may not allow the exclusion or limitation of incidental, consequential, or other damages. - -Please note: As this software is distributed in Canada, some of the clauses in this agreement are -provided below in French. -Remarque: Ce logiciel étant distribué au Canada, certaines des clauses dans ce contrat sont fournies ci- -dessous en français. -EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute utilisation de -ce logiciel est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous -pouvez bénéficier de droits additionnels en vertu du droit local sur la protection des consommateurs, -que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de -qualité marchande, d’adéquation à un usage particulier et d’absence de contrefaçon sont exclues. -LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. -Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs -uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres -dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. -Cette limitation concerne: -• tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites -Internet tiers ou dans des programmes tiers; et -• les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, -de négligence ou d’une autre faute dans la limite autorisée par la loi en vigueur. -Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel -dommage. Si votre pays n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages -indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci- -dessus ne s’appliquera pas à votre égard. -EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits -prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois -de votre pays si celles-ci ne le permettent pas. - -The following components are governed by the MIT license, a copy of which appears -below the list of components: ------------------------------------------------------- -tools/appimage.sh ------------------------------------------------------- -Copyright (c) 2016 Simon Peter -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. ---------------------------------------------------------- -DotNet/CoreFX -https://github.com/dotnet/corefx ---------------------------------------------------------- -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/nuget.config b/nuget.config index ca674f6c218..388a65572dd 100644 --- a/nuget.config +++ b/nuget.config @@ -2,8 +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/AssemblyInfo.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/AssemblyInfo.cs index 7a2005fee6c..2e914a2299a 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/AssemblyInfo.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/AssemblyInfo.cs @@ -1,13 +1,6 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Diagnostics; -using System.Reflection; using System.Runtime.CompilerServices; -[assembly:InternalsVisibleTo("Microsoft.Windows.DSC.CoreConfProviders,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly:InternalsVisibleTo("Microsoft.Management.Infrastructure.CimCmdlets.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -//This is equal to Debuggable(true,true) which enables IsJITTracking and Disable Optimization. CoreCLR does not have constructor Debuggable(true,true) -[assembly: Debuggable(DebuggableAttribute.DebuggingModes.DisableOptimizations)] +[assembly: InternalsVisibleTo("Microsoft.Windows.DSC.CoreConfProviders,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs index bbf97b2fb50..e794fd929d1 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -10,7 +8,6 @@ using System.Collections.Generic; using System.Management.Automation; using System.Threading; -using System.Globalization; #endregion @@ -29,9 +26,9 @@ internal abstract class CimAsyncOperation : IDisposable #region Constructor /// - /// Constructor + /// Initializes a new instance of the class. /// - public CimAsyncOperation() + protected CimAsyncOperation() { this.moreActionEvent = new ManualResetEventSlim(false); this.actionQueue = new ConcurrentQueue(); @@ -52,10 +49,10 @@ public CimAsyncOperation() /// /// object raised the event /// - /// event argument + /// Event argument. protected void NewCmdletActionHandler(object cimSession, CmdletActionEventArgs actionArgs) { - DebugHelper.WriteLogEx("Disposed {0}, action type = {1}", 0, this._disposed, actionArgs.Action); + DebugHelper.WriteLogEx("Disposed {0}, action type = {1}", 0, this.Disposed, actionArgs.Action); if (this.Disposed) { @@ -64,6 +61,7 @@ protected void NewCmdletActionHandler(object cimSession, CmdletActionEventArgs a // unblock the thread waiting for response (actionArgs.Action as CimSyncAction).OnComplete(); } + return; } @@ -82,14 +80,14 @@ protected void NewCmdletActionHandler(object cimSession, CmdletActionEventArgs a /// /// /// - /// object raised the event + /// object raised the event. /// - /// event argument + /// Event argument. protected void OperationCreatedHandler(object cimSession, OperationEventArgs actionArgs) { DebugHelper.WriteLogEx(); - lock (this.myLock) + lock (this.a_lock) { this.operationCount++; } @@ -102,14 +100,14 @@ protected void OperationCreatedHandler(object cimSession, OperationEventArgs act /// /// /// - /// object raised the event + /// object raised the event. /// - /// event argument + /// Event argument. protected void OperationDeletedHandler(object cimSession, OperationEventArgs actionArgs) { DebugHelper.WriteLogEx(); - lock (this.myLock) + lock (this.a_lock) { this.operationCount--; if (this.operationCount == 0) @@ -127,7 +125,7 @@ protected void OperationDeletedHandler(object cimSession, OperationEventArgs act /// /// /// - /// wrapper of cmdlet, for details + /// Wrapper of cmdlet, for details. /// public void ProcessActions(CmdletOperationBase cmdletOperation) { @@ -147,12 +145,12 @@ public void ProcessActions(CmdletOperationBase cmdletOperation) /// /// - /// process remaining actions until all operations are completed or - /// current cmdlet is terminated by user + /// Process remaining actions until all operations are completed or + /// current cmdlet is terminated by user. /// /// /// - /// wrapper of cmdlet, for details + /// Wrapper of cmdlet, for details. /// public void ProcessRemainActions(CmdletOperationBase cmdletOperation) { @@ -166,6 +164,7 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) DebugHelper.WriteLogEx("Either disposed or all operations completed.", 2); break; } + try { this.moreActionEvent.Wait(); @@ -179,6 +178,7 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) break; } } + ProcessActions(cmdletOperation); } @@ -186,11 +186,11 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) /// /// - /// Get action object from action queue + /// Get action object from action queue. /// /// - /// next action to execute - /// true indicates there is an valid action, otherwise false + /// Next action to execute. + /// True indicates there is an valid action, otherwise false. protected bool GetActionAndRemove(out CimBaseAction action) { return this.actionQueue.TryDequeue(out action); @@ -201,16 +201,13 @@ protected bool GetActionAndRemove(out CimBaseAction action) /// Add temporary object to cache. /// /// - /// Computer name of the cimsession - /// cimsession wrapper object + /// Cimsession wrapper object. protected void AddCimSessionProxy(CimSessionProxy sessionproxy) { lock (cimSessionProxyCacheLock) { - if (this.cimSessionProxyCache == null) - { - this.cimSessionProxyCache = new List(); - } + this.cimSessionProxyCache ??= new List(); + if (!this.cimSessionProxyCache.Contains(sessionproxy)) { this.cimSessionProxyCache.Add(sessionproxy); @@ -223,7 +220,7 @@ protected void AddCimSessionProxy(CimSessionProxy sessionproxy) /// Are there active operations? /// /// - /// true for having active operations, otherwise false. + /// True for having active operations, otherwise false. protected bool IsActive() { DebugHelper.WriteLogEx("Disposed {0}, Operation Count {1}", 2, this.Disposed, this.operationCount); @@ -237,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; } @@ -259,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; } @@ -282,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; } @@ -294,10 +291,9 @@ protected CimSessionProxy CreateCimSessionProxy(string computerName) /// /// /// - protected CimSessionProxy CreateCimSessionProxy(string computerName, - CimInstance cimInstance) + protected CimSessionProxy CreateCimSessionProxy(string computerName, CimInstance cimInstance) { - CimSessionProxy proxy = new CimSessionProxy(computerName, cimInstance); + CimSessionProxy proxy = new(computerName, cimInstance); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } @@ -317,7 +313,7 @@ protected CimSessionProxy CreateCimSessionProxy(string computerName, CimInstance } /// - /// Subscribe event from proxy and add proxy to cache + /// Subscribe event from proxy and add proxy to cache. /// /// protected void SubscribeEventAndAddProxytoCache(CimSessionProxy proxy) @@ -342,22 +338,20 @@ protected virtual void SubscribeToCimSessionProxyEvent(CimSessionProxy proxy) } /// - /// Retrieve the base object out if wrapped in psobject + /// Retrieve the base object out if wrapped in psobject. /// /// /// 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; } @@ -368,6 +362,7 @@ protected object GetBaseObject(object value) { arraybaseObject[i] = GetBaseObject(arrayObject[i]); } + return arraybaseObject; } } @@ -379,41 +374,40 @@ protected object GetBaseObject(object value) /// if not thrown exception. /// /// - /// output the cimtype of the value, either Reference or ReferenceArray - /// + /// Output the cimtype of the value, either Reference or ReferenceArray. + /// 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; } + referenceType = CimType.Reference; return cimInstance; } 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; } + 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; } + object baseObject = GetBaseObject(tempCimReference.Value); cimInstanceArray[i] = baseObject as CimInstance; if (cimInstanceArray[i] == null) @@ -421,6 +415,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re return null; } } + referenceType = CimType.ReferenceArray; return cimInstanceArray; } @@ -438,10 +433,11 @@ protected bool Disposed { get { - return (Interlocked.Read(ref this._disposed) == 1); + return this._disposed == 1; } } - private long _disposed; + + private int _disposed; /// /// @@ -453,6 +449,7 @@ protected bool Disposed public void Dispose() { Dispose(true); + // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to // take this object off the finalization queue @@ -472,7 +469,7 @@ public void Dispose() /// other objects. Only unmanaged resources can be disposed. /// /// - /// Whether it is directly called + /// Whether it is directly called. protected virtual void Dispose(bool disposing) { if (Interlocked.CompareExchange(ref this._disposed, 1, 0) == 0) @@ -488,7 +485,7 @@ protected virtual void Dispose(bool disposing) /// /// - /// Clean up managed resources + /// Clean up managed resources. /// /// private void Cleanup() @@ -508,6 +505,7 @@ private void Cleanup() (action as CimSyncAction).OnComplete(); } } + if (this.cimSessionProxyCache != null) { List temporaryProxy; @@ -516,6 +514,7 @@ private void Cleanup() temporaryProxy = new List(this.cimSessionProxyCache); this.cimSessionProxyCache.Clear(); } + // clean up all proxy objects foreach (CimSessionProxy proxy in temporaryProxy) { @@ -525,10 +524,8 @@ private void Cleanup() } this.moreActionEvent.Dispose(); - if (this.ackedEvent != null) - { - this.ackedEvent.Dispose(); - } + this.ackedEvent?.Dispose(); + DebugHelper.WriteLog("Cleanup complete.", 2); } @@ -537,34 +534,34 @@ private void Cleanup() #region private members /// - /// lock object + /// Lock object. /// - private readonly object myLock = new object(); + private readonly object a_lock = new(); /// - /// number of active operations + /// Number of active operations. /// - private UInt32 operationCount = 0; + private uint operationCount; /// - /// Event to notify ps thread that more action is available + /// 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 + /// Lock object. /// - private readonly object cimSessionProxyCacheLock = new object(); + private readonly object cimSessionProxyCacheLock = new(); /// - /// cache all objects related to + /// Cache all objects related to /// the current operation. /// private List cimSessionProxyCache; @@ -574,7 +571,7 @@ private void Cleanup() #region protected members /// /// Event to notify ps thread that either a ACK message sent back - /// or a error happened. Currently only used by class + /// or a error happened. Currently only used by /// . /// protected ManualResetEventSlim ackedEvent; @@ -584,6 +581,5 @@ private void Cleanup() internal const string ComputerNameArgument = @"ComputerName"; internal const string CimSessionArgument = @"CimSession"; #endregion - - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs index 773f81ad6d2..4702e47e2f2 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs @@ -1,13 +1,11 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives +using System; using System.Threading; using Microsoft.Management.Infrastructure.Options; -using System; #endregion @@ -19,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() { } @@ -46,19 +44,8 @@ public virtual void Execute(CmdletOperationBase cmdlet) /// , object. /// /// - protected XOperationContextBase Context - { - get - { - return this.context; - } - set - { - this.context = value; - } - } - private XOperationContextBase context; - }//End Class + protected XOperationContextBase Context { get; set; } + } /// /// @@ -69,7 +56,7 @@ protected XOperationContextBase Context internal class CimSyncAction : CimBaseAction, IDisposable { /// - /// Constructor + /// Initializes a new instance of the class. /// public CimSyncAction() { @@ -82,7 +69,7 @@ public CimSyncAction() /// Block current thread until action completed /// /// - /// Response from user + /// Response from user. public virtual CimResponseType GetResponse() { this.Block(); @@ -91,7 +78,7 @@ public virtual CimResponseType GetResponse() /// /// - /// Set response result + /// Set the response result. /// /// internal CimResponseType ResponseType @@ -102,7 +89,7 @@ internal CimResponseType ResponseType /// /// /// Call this method when the action is completed or - /// the operation is terminated + /// the operation is terminated. /// /// internal virtual void OnComplete() @@ -112,7 +99,7 @@ internal virtual void OnComplete() /// /// - /// block current thread. + /// Block current thread. /// /// protected virtual void Block() @@ -124,12 +111,12 @@ protected virtual void Block() #region members /// - /// action completed event + /// Action completed event. /// - private ManualResetEventSlim completeEvent; + private readonly ManualResetEventSlim completeEvent; /// - /// response result + /// Response result. /// protected CimResponseType responseType; @@ -137,7 +124,7 @@ protected virtual void Block() #region IDisposable interface /// - /// IDisposable interface + /// IDisposable interface. /// private bool _disposed; @@ -170,7 +157,7 @@ public void Dispose() /// other objects. Only unmanaged resources can be disposed. /// /// - /// Whether it is directly called + /// Whether it is directly called. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. @@ -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 @@ -197,5 +181,5 @@ protected virtual void Dispose(bool disposing) } } #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCmdletModuleInitialize.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCmdletModuleInitialize.cs deleted file mode 100644 index b9f9962710d..00000000000 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCmdletModuleInitialize.cs +++ /dev/null @@ -1,116 +0,0 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ - -#region Using directives - -using System; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -#endregion - -namespace Microsoft.Management.Infrastructure.CimCmdlets -{ - /// - /// - /// Initialize the cimcmdlets. - /// - /// - /// - /// Provide a hook to the engine for startup initialization - /// w.r.t compiled assembly loading. - /// - public sealed class CimCmdletsAssemblyInitializer : IModuleAssemblyInitializer - { - /// - /// - /// constructor - /// - /// - public CimCmdletsAssemblyInitializer() - { - } - - /// - /// PowerShell engine will call this method when the cimcmdlets module - /// is loaded. - /// - public void OnImport() - { - DebugHelper.WriteLogEx(); - using (System.Management.Automation.PowerShell invoker = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - foreach (CimCmdletAliasEntry alias in Aliases) - { - invoker.AddScript(string.Format(CultureInfo.CurrentUICulture, "Set-Alias -Name {0} -Value {1} -Option {2} -ErrorAction SilentlyContinue", alias.Name, alias.Value, alias.Options)); - DebugHelper.WriteLog(@"Add commands {0} of {1} with option {2} to current runspace.", 1, alias.Name, alias.Value, alias.Options); - } - System.Collections.ObjectModel.Collection psObjects = invoker.Invoke(); - DebugHelper.WriteLog(@"Invoke results {0}.", 1, psObjects.Count); - } - } - - #region readonly string - - /// - /// - /// CimCmdlet alias entry - /// - /// - internal sealed class CimCmdletAliasEntry - { - /// - /// - /// Constructor - /// - /// - /// - /// - internal CimCmdletAliasEntry(string name, string value) - { - this._name = name; - this._value = value; - } - - /// - /// The string defining the name of this alias - /// - internal string Name { get { return this._name; } } - private string _name; - - /// - /// The string defining real cmdlet name - /// - internal string Value { get { return this._value; } } - private string _value = String.Empty; - - /// - /// The string defining real cmdlet name - /// - internal ScopedItemOptions Options { get { return this._options; } } - private ScopedItemOptions _options = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly; - } - - /// - /// Returns a new array of alias entries everytime it's called. - /// - internal static CimCmdletAliasEntry[] Aliases = new CimCmdletAliasEntry[] { - new CimCmdletAliasEntry("gcim", "Get-CimInstance"), - new CimCmdletAliasEntry("scim", "Set-CimInstance"), - new CimCmdletAliasEntry("ncim", "New-CimInstance "), - new CimCmdletAliasEntry("rcim", "Remove-cimInstance"), - new CimCmdletAliasEntry("icim", "Invoke-CimMethod"), - new CimCmdletAliasEntry("gcai", "Get-CimAssociatedInstance"), - new CimCmdletAliasEntry("rcie", "Register-CimIndicationEvent"), - new CimCmdletAliasEntry("ncms", "New-CimSession"), - new CimCmdletAliasEntry("rcms", "Remove-cimSession"), - new CimCmdletAliasEntry("gcms", "Get-CimSession"), - new CimCmdletAliasEntry("ncso", "New-CimSessionOption"), - new CimCmdletAliasEntry("gcls", "Get-CimClass"), - }; - #endregion - }//End Class -}//End namespace diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs index 2f5dd0ac2ab..89f7478b513 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -27,39 +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 + /// 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 + /// 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,135 +54,77 @@ 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(); } /// - /// reset the internal status + /// Reset the internal status. /// internal void reset() { - this.setMandatoryParameterCount = this.setMandatoryParameterCountAtBeginProcess; - this.isValueSet = this.isValueSetAtBeginProcess; + this.SetMandatoryParameterCount = this.SetMandatoryParameterCountAtBeginProcess; + this.IsValueSet = this.IsValueSetAtBeginProcess; } /// - /// property DefaultParameterSet + /// Property DefaultParameterSet /// - internal bool IsDefaultParameterSet - { - get - { - return this.isDefaultParameterSet; - } - } - private readonly bool isDefaultParameterSet = false; + internal bool IsDefaultParameterSet { get; } /// - /// property MandatoryParameterCount + /// Property MandatoryParameterCount /// - internal UInt32 MandatoryParameterCount - { - get - { - return this.mandatoryParameterCount; - } - } - private readonly UInt32 mandatoryParameterCount = 0; + internal uint MandatoryParameterCount { get; } = 0; /// - /// property IsValueSet + /// Property IsValueSet /// - internal bool IsValueSet - { - get - { - return this.isValueSet; - } - set - { - this.isValueSet = value; - } - } - private bool isValueSet = false; + internal bool IsValueSet { get; set; } /// - /// property IsValueSetAtBeginProcess + /// Property IsValueSetAtBeginProcess /// - internal bool IsValueSetAtBeginProcess - { - get - { - return this.isValueSetAtBeginProcess; - } - set - { - this.isValueSetAtBeginProcess = value; - } - } - private bool isValueSetAtBeginProcess = false; + internal bool IsValueSetAtBeginProcess { get; set; } /// - /// property SetMandatoryParameterCount + /// 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 + /// Property SetMandatoryParameterCountAtBeginProcess /// - internal UInt32 SetMandatoryParameterCountAtBeginProcess - { - get - { - return this.setMandatoryParameterCountAtBeginProcess; - } - set - { - this.setMandatoryParameterCountAtBeginProcess = value; - } - } - private UInt32 setMandatoryParameterCountAtBeginProcess = 0; + internal uint SetMandatoryParameterCountAtBeginProcess { get; set; } = 0; } /// @@ -207,7 +133,7 @@ internal UInt32 SetMandatoryParameterCountAtBeginProcess internal class ParameterBinder { /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -246,12 +172,12 @@ internal ParameterBinder( /// throw exception /// /// - private List parametersetNamesList = new List(); + private List parametersetNamesList = new(); /// - /// Parameter names list + /// Parameter names list. /// - private List parameterNamesList = new List(); + private readonly List parameterNamesList = new(); /// /// @@ -260,12 +186,12 @@ internal ParameterBinder( /// throw exception /// /// - private List parametersetNamesListAtBeginProcess = new List(); + private List parametersetNamesListAtBeginProcess = new(); /// - /// Parameter names list before begin process + /// Parameter names list before begin process. /// - private List parameterNamesListAtBeginProcess = new List(); + private readonly List parameterNamesListAtBeginProcess = new(); /// /// @@ -300,7 +226,7 @@ internal void reset() /// /// /// - /// throw if conflict parameter was set + /// Throw if conflict parameter was set. internal void SetParameter(string parameterName, bool isBeginProcess) { DebugHelper.WriteLogEx("ParameterName = {0}, isBeginProcess = {1}", 0, parameterName, isBeginProcess); @@ -321,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); @@ -336,8 +262,10 @@ internal void SetParameter(string parameterName, bool isBeginProcess) { psEntry.SetMandatoryParameterCountAtBeginProcess++; } + DebugHelper.WriteLogEx("parameterset name = '{0}'; SetMandatoryParameterCount = '{1}'", 1, parameterDefinitionEntry.ParameterSetName, psEntry.SetMandatoryParameterCount); } + if (!psEntry.IsValueSet) { psEntry.IsValueSet = true; @@ -346,8 +274,10 @@ internal void SetParameter(string parameterName, bool isBeginProcess) psEntry.IsValueSetAtBeginProcess = true; } } + nameset.Add(parameterDefinitionEntry.ParameterSetName); } + this.parametersetNamesList = nameset; if (isBeginProcess) { @@ -356,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)) @@ -370,6 +300,7 @@ internal void SetParameter(string parameterName, bool isBeginProcess) { psEntry.SetMandatoryParameterCountAtBeginProcess++; } + DebugHelper.WriteLogEx("parameterset name = '{0}'; SetMandatoryParameterCount = '{1}'", 1, entry.ParameterSetName, @@ -377,9 +308,10 @@ internal void SetParameter(string parameterName, bool isBeginProcess) } } } + if (nameset.Count == 0) { - throw new PSArgumentException(Strings.UnableToResolveParameterSetName); + throw new PSArgumentException(CimCmdletStrings.UnableToResolveParameterSetName); } else { @@ -393,7 +325,7 @@ internal void SetParameter(string parameterName, bool isBeginProcess) } /// - /// Get the parameter set name based on current binding results + /// Get the parameter set name based on current binding results. /// /// internal string GetParameterSet() @@ -402,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) @@ -422,19 +354,23 @@ internal string GetParameterSet() { defaultParameterSetName = parameterSetName; } + if (entry.IsValueSet) { noMandatoryParameterSet.Add(parameterSetName); } + continue; } + if ((entry.SetMandatoryParameterCount == entry.MandatoryParameterCount) && this.parametersetNamesList.Contains(parameterSetName)) { if (boundParameterSetName != null) { - throw new PSArgumentException(Strings.UnableToResolveParameterSetName); + throw new PSArgumentException(CimCmdletStrings.UnableToResolveParameterSetName); } + boundParameterSetName = parameterSetName; } } @@ -445,7 +381,7 @@ internal string GetParameterSet() // throw if there are > 1 parameter set if (noMandatoryParameterSet.Count > 1) { - throw new PSArgumentException(Strings.UnableToResolveParameterSetName); + throw new PSArgumentException(CimCmdletStrings.UnableToResolveParameterSetName); } else if (noMandatoryParameterSet.Count == 1) { @@ -454,21 +390,19 @@ 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) { - throw new PSArgumentException(Strings.UnableToResolveParameterSetName); + throw new PSArgumentException(CimCmdletStrings.UnableToResolveParameterSetName); } + return boundParameterSetName; } /// - /// Deep clone the parameter entries to member variable + /// Deep clone the parameter entries to member variable. /// private void CloneParameterEntries( Dictionary> parameters, @@ -486,11 +420,10 @@ private void CloneParameterEntries( #endregion /// - /// Base command for all cim cmdlets + /// Base command for all cim cmdlets. /// public class CimBaseCommand : Cmdlet, IDisposable { - #region resolve parameter set name /// /// @@ -511,18 +444,19 @@ internal void CheckParameterSet() { try { - this.parameterSetName = this.parameterBinder.GetParameterSet(); + this.ParameterSetName = this.parameterBinder.GetParameterSet(); } finally { this.parameterBinder.reset(); } } - DebugHelper.WriteLog("current parameterset is: " + this.parameterSetName, 4); + + DebugHelper.WriteLog("current parameterset is: " + this.ParameterSetName, 4); } /// - /// Redirect to parameterBinder to set one parameter + /// Redirect to parameterBinder to set one parameter. /// /// internal void SetParameter(object value, string parameterName) @@ -535,17 +469,15 @@ 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() { @@ -554,7 +486,7 @@ internal CimBaseCommand() } /// - /// constructor + /// Initializes a new instance of the class. /// internal CimBaseCommand(Dictionary> parameters, Dictionary sets) @@ -573,13 +505,13 @@ internal CimBaseCommand(Dictionary> pa protected override void StopProcessing() { Dispose(); - }//End StopProcessing() + } #endregion #region IDisposable interface /// - /// IDisposable interface + /// IDisposable interface. /// private bool disposed; @@ -612,7 +544,7 @@ public void Dispose() /// other objects. Only unmanaged resources can be disposed. /// /// - /// Whether it is directly called + /// Whether it is directly called. protected void Dispose(bool disposing) { // Check to see if Dispose has already been called. @@ -636,24 +568,21 @@ protected void Dispose(bool disposing) } /// - /// Clean up resources + /// Clean up resources. /// protected virtual void DisposeInternal() { // Dispose managed resources. - if (this.operation != null) - { - this.operation.Dispose(); - } + this.operation?.Dispose(); } #endregion #region private members /// - /// Parameter binder used to resolve parameter set name + /// Parameter binder used to resolve parameter set name. /// - private ParameterBinder parameterBinder; + private readonly ParameterBinder parameterBinder; /// /// @@ -663,29 +592,24 @@ protected virtual void DisposeInternal() private CimAsyncOperation operation; /// - /// lock object + /// 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 /// during process record - /// Whether at begin process time, false means in processrecord + /// Whether at begin process time, false means in processrecord. /// private bool atBeginProcess = true; + internal bool AtBeginProcess { get { return this.atBeginProcess; } + set { this.atBeginProcess = value; @@ -703,6 +627,11 @@ internal bool AtBeginProcess /// internal CimAsyncOperation AsyncOperation { + get + { + return this.operation; + } + set { lock (this.myLock) @@ -711,10 +640,6 @@ internal CimAsyncOperation AsyncOperation this.operation = value; } } - get - { - return this.operation; - } } /// @@ -722,16 +647,10 @@ 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 + /// Gets/Sets cmdlet operation wrapper object. /// internal virtual CmdletOperationBase CmdletOperation { @@ -744,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); } @@ -755,78 +675,77 @@ internal void ThrowTerminatingError(Exception exception, string operation) #region internal const strings /// - /// alias CN - computer name + /// Alias CN - computer name. /// internal const string AliasCN = "CN"; /// - /// alias ServerName - computer name + /// Alias ServerName - computer name. /// internal const string AliasServerName = "ServerName"; /// - /// alias OT - operation timeout + /// Alias OT - operation timeout. /// internal const string AliasOT = "OT"; /// - /// session set name + /// Session set name. /// internal const string SessionSetName = "SessionSet"; /// - /// computer set name + /// Computer set name. /// internal const string ComputerSetName = "ComputerSet"; /// - /// class name computer set name + /// Class name computer set name. /// internal const string ClassNameComputerSet = "ClassNameComputerSet"; /// - /// resource Uri computer set name + /// Resource Uri computer set name. /// internal const string ResourceUriComputerSet = "ResourceUriComputerSet"; /// - /// computer set name + /// computer set name. /// internal const string CimInstanceComputerSet = "CimInstanceComputerSet"; /// - /// query computer set name + /// Query computer set name. /// internal const string QueryComputerSet = "QueryComputerSet"; /// - /// class name session set name + /// Class name session set name. /// internal const string ClassNameSessionSet = "ClassNameSessionSet"; - /// - /// resource Uri session set name + /// Resource Uri session set name. /// internal const string ResourceUriSessionSet = "ResourceUriSessionSet"; /// - /// session set name + /// session set name. /// internal const string CimInstanceSessionSet = "CimInstanceSessionSet"; /// - /// query session set name + /// Query session set name. /// internal const string QuerySessionSet = "QuerySessionSet"; /// - /// computer set name + /// computer set name. /// internal const string CimClassComputerSet = "CimClassComputerSet"; /// - /// session set name + /// session set name. /// internal const string CimClassSessionSet = "CimClassSessionSet"; @@ -848,17 +767,17 @@ internal void ThrowTerminatingError(Exception exception, string operation) #endregion /// - /// credential parameter set + /// Credential parameter set. /// internal const string CredentialParameterSet = "CredentialParameterSet"; /// - /// certificate parameter set + /// Certificate parameter set. /// internal const string CertificateParameterSet = "CertificateParameterSet"; /// - /// CimInstance parameter alias + /// CimInstance parameter alias. /// internal const string AliasCimInstance = "CimInstance"; @@ -879,19 +798,19 @@ internal void ThrowInvalidAuthenticationTypeError( string parameterName, PasswordAuthenticationMechanism authentication) { - string message = String.Format(CultureInfo.CurrentUICulture, Strings.InvalidAuthenticationTypeWithNullCredential, + string message = string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.InvalidAuthenticationTypeWithNullCredential, authentication, ImpersonatedAuthenticationMechanism.None, ImpersonatedAuthenticationMechanism.Negotiate, ImpersonatedAuthenticationMechanism.Kerberos, ImpersonatedAuthenticationMechanism.NtlmDomain); - PSArgumentOutOfRangeException exception = new PSArgumentOutOfRangeException( + PSArgumentOutOfRangeException exception = new( parameterName, authentication, message); ThrowTerminatingError(exception, operationName); } /// - /// Throw conflict parameter error + /// Throw conflict parameter error. /// /// /// @@ -901,10 +820,10 @@ internal void ThrowConflictParameterWasSet( string parameterName, string conflictParameterName) { - string message = String.Format(CultureInfo.CurrentUICulture, - Strings.ConflictParameterWasSet, + string message = string.Format(CultureInfo.CurrentUICulture, + CimCmdletStrings.ConflictParameterWasSet, parameterName, conflictParameterName); - PSArgumentException exception = new PSArgumentException(message, parameterName); + PSArgumentException exception = new(message, parameterName); ThrowTerminatingError(exception, operationName); } @@ -920,24 +839,26 @@ 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); } - string message = String.Format(CultureInfo.CurrentUICulture, Strings.CouldNotFindPropertyFromGivenClass, + + string message = string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.CouldNotFindPropertyFromGivenClass, className, propList); - PSArgumentOutOfRangeException exception = new PSArgumentOutOfRangeException( + PSArgumentOutOfRangeException exception = new( parameterName, actualValue, message); ThrowTerminatingError(exception, operationName); } /// - /// Create credentials based on given authentication type and PSCredential + /// Create credentials based on given authentication type and PSCredential. /// /// /// @@ -955,7 +876,6 @@ internal CimCredential CreateCimCredentials(PSCredential psCredentials, NetworkCredential networkCredential = psCredentials.GetNetworkCredential(); DebugHelper.WriteLog("Domain:{0}; UserName:{1}; Password:{2}.", 1, networkCredential.Domain, networkCredential.UserName, psCredentials.Password); credentials = new CimCredential(passwordAuthentication, networkCredential.Domain, networkCredential.UserName, psCredentials.Password); - } else { @@ -978,11 +898,13 @@ internal CimCredential CreateCimCredentials(PSCredential psCredentials, ThrowInvalidAuthenticationTypeError(operationName, parameterName, passwordAuthentication); return null; } + credentials = new CimCredential(impersonatedAuthentication); } + DebugHelper.WriteLogEx("return credential {0}", 1, credentials); return credentials; } #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs index 47a0df29796..0ab4988c57d 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs @@ -1,12 +1,8 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System.Collections; -using System; using System.Collections.Generic; #endregion @@ -21,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal sealed class CimGetAssociatedInstance : CimAsyncOperation { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimGetAssociatedInstance() : base() @@ -35,7 +29,7 @@ public CimGetAssociatedInstance() /// Base on parametersetName to retrieve associated ciminstances /// /// - /// object + /// object. public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) { IEnumerable computerNames = ConstValue.GetComputerNames(cmdlet.ComputerName); @@ -46,15 +40,17 @@ public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) // try to use namespace of ciminstance, then fall back to default namespace nameSpace = ConstValue.GetNamespace(cmdlet.CimInstance.CimSystemProperties.Namespace); } - List proxys = new List(); + + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.ComputerSetName: foreach (string computerName in computerNames) { CimSessionProxy proxy = CreateSessionProxy(computerName, cmdlet.CimInstance, cmdlet); - proxys.Add(proxy); + proxys.Add(proxy); } + break; case CimBaseCommand.SessionSetName: foreach (CimSession session in cmdlet.CimSession) @@ -62,10 +58,12 @@ public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) CimSessionProxy proxy = CreateSessionProxy(session, cmdlet); proxys.Add(proxy); } + break; default: return; } + foreach (CimSessionProxy proxy in proxys) { proxy.EnumerateAssociatedInstancesAsync( @@ -87,7 +85,7 @@ public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, GetCimAssociatedInstanceCommand cmdlet) { @@ -119,7 +117,7 @@ private CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -135,5 +133,5 @@ private CimSessionProxy CreateSessionProxy( #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs index e660b8ad4a9..c775325094b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs @@ -1,15 +1,10 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System.Collections; -using System; using System.Collections.Generic; using System.Management.Automation; -using System.Globalization; #endregion @@ -22,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimGetCimClassContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -35,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; } /// @@ -49,12 +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,11 +51,7 @@ public String ClassName /// Then Filter the by given methodname /// /// - internal String MethodName - { - get { return methodName; } - } - private String methodName; + internal string MethodName { get; } /// /// @@ -76,11 +60,7 @@ internal String MethodName /// Filter the by given property name. /// /// - internal String PropertyName - { - get { return propertyName; } - } - private String propertyName; + internal string PropertyName { get; } /// /// @@ -89,11 +69,7 @@ internal String PropertyName /// Filter the by given methodname /// /// - internal String QualifierName - { - get { return qualifierName; } - } - private String qualifierName; + internal string QualifierName { get; } } /// @@ -104,9 +80,7 @@ internal String QualifierName internal sealed class CimGetCimClass : CimAsyncOperation { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimGetCimClass() : base() @@ -118,13 +92,13 @@ public CimGetCimClass() /// Base on parametersetName to retrieve /// /// - /// object + /// 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, @@ -142,6 +116,7 @@ public void GetCimClass(GetCimClassCommand cmdlet) proxys.Add(proxy); } } + break; case CimBaseCommand.SessionSetName: { @@ -152,6 +127,7 @@ public void GetCimClass(GetCimClassCommand cmdlet) proxys.Add(proxy); } } + break; default: return; @@ -184,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; } /// @@ -210,7 +187,7 @@ private CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -227,5 +204,5 @@ private CimSessionProxy CreateSessionProxy( #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs index ad23b933c3e..371b06d9356 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs @@ -1,12 +1,8 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Management.Automation; @@ -17,7 +13,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets { /// - /// a class used to add pstypename to partial ciminstance + /// A class used to add pstypename to partial ciminstance /// for , if -KeyOnly /// or -SelectProperties is been specified, then add a pstypename: /// "Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance" @@ -25,12 +21,12 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class FormatPartialCimInstance : IObjectPreProcess { /// - /// Partial ciminstance pstypename + /// Partial ciminstance pstypename. /// internal const string PartialPSTypeName = @"Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance"; /// - /// Add pstypename to the resultobject if necessary + /// Add pstypename to the resultobject if necessary. /// /// /// @@ -42,6 +38,7 @@ public object Process(object resultObject) obj.TypeNames.Insert(0, PartialPSTypeName); return obj; } + return resultObject; } } @@ -54,6 +51,7 @@ public object Process(object resultObject) internal class CimGetInstance : CimAsyncOperation { /// + /// Initializes a new instance of the class. /// /// Constructor /// @@ -67,7 +65,7 @@ public CimGetInstance() : base() /// Base on parametersetName to retrieve ciminstances /// /// - /// object + /// object. public void GetCimInstance(GetCimInstanceCommand cmdlet) { GetCimInstanceInternal(cmdlet); @@ -84,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) { @@ -96,10 +94,12 @@ 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); } + break; case CimBaseCommand.ClassNameComputerSet: case CimBaseCommand.QueryComputerSet: @@ -109,10 +109,12 @@ 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); } + break; case CimBaseCommand.ClassNameSessionSet: case CimBaseCommand.CimInstanceSessionSet: @@ -123,14 +125,17 @@ 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); } + break; default: break; } + switch (cmdlet.ParameterSetName) { case CimBaseCommand.ClassNameComputerSet: @@ -154,6 +159,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) proxy.EnumerateInstancesAsync(nameSpace, GetClassName(cmdlet)); } } + break; case CimBaseCommand.CimInstanceComputerSet: case CimBaseCommand.CimInstanceSessionSet: @@ -165,6 +171,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) proxy.GetInstanceAsync(nameSpace, instance); } } + break; case CimBaseCommand.QueryComputerSet: case CimBaseCommand.QuerySessionSet: @@ -175,6 +182,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) ConstValue.GetQueryDialectWithDefault(GetQueryDialect(cmdlet)), GetQuery(cmdlet)); } + break; case CimBaseCommand.ResourceUriSessionSet: case CimBaseCommand.ResourceUriComputerSet: @@ -182,6 +190,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) { proxy.EnumerateInstancesAsync(GetNamespace(cmdlet), GetClassName(cmdlet)); } + break; default: break; @@ -190,7 +199,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) #region bridge methods to read properties from cmdlet - protected static String[] GetComputerName(CimBaseCommand cmdlet) + protected static string[] GetComputerName(CimBaseCommand cmdlet) { if (cmdlet is GetCimInstanceCommand) { @@ -204,10 +213,11 @@ protected static String[] GetComputerName(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).ComputerName; } + return null; } - protected static String GetNamespace(CimBaseCommand cmdlet) + protected static string GetNamespace(CimBaseCommand cmdlet) { if (cmdlet is GetCimInstanceCommand) { @@ -221,6 +231,7 @@ protected static String GetNamespace(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).Namespace; } + return null; } @@ -238,19 +249,21 @@ protected static CimSession[] GetCimSession(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).CimSession; } + return null; } - protected static String GetClassName(CimBaseCommand cmdlet) + protected static string GetClassName(CimBaseCommand cmdlet) { if (cmdlet is GetCimInstanceCommand) { return (cmdlet as GetCimInstanceCommand).ClassName; } + return null; } - protected static String GetQuery(CimBaseCommand cmdlet) + protected static string GetQuery(CimBaseCommand cmdlet) { if (cmdlet is GetCimInstanceCommand) { @@ -264,33 +277,33 @@ protected static String GetQuery(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).Query; } + return null; } 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) { return true; } } + return false; } - protected static String CreateQuery(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 { @@ -298,19 +311,22 @@ protected static String CreateQuery(CimBaseCommand cmdlet) { if (propertyList.Length > 0) { - propertyList.Append(","); + propertyList.Append(','); } + propertyList.Append(property); } } + return (cmd.Filter == null) ? - String.Format(CultureInfo.CurrentUICulture, queryWithoutWhere, propertyList, cmd.ClassName) : - String.Format(CultureInfo.CurrentUICulture, queryWithWhere, propertyList, cmd.ClassName, cmd.Filter); + string.Format(CultureInfo.CurrentUICulture, queryWithoutWhere, propertyList, cmd.ClassName) : + string.Format(CultureInfo.CurrentUICulture, queryWithWhere, propertyList, cmd.ClassName, cmd.Filter); } + return null; } - protected static String GetQueryDialect(CimBaseCommand cmdlet) + protected static string GetQueryDialect(CimBaseCommand cmdlet) { if (cmdlet is GetCimInstanceCommand) { @@ -324,6 +340,7 @@ protected static String GetQueryDialect(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).QueryDialect; } + return null; } @@ -341,6 +358,7 @@ protected static CimInstance GetCimInstanceParameter(CimBaseCommand cmdlet) { return (cmdlet as SetCimInstanceCommand).CimInstance; } + return null; } #endregion @@ -354,7 +372,7 @@ protected static CimInstance GetCimInstanceParameter(CimBaseCommand cmdlet) /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, CimBaseCommand cmdlet) { @@ -364,7 +382,7 @@ private void SetSessionProxyProperties( proxy.KeyOnly = getCimInstance.KeyOnly; proxy.Shallow = getCimInstance.Shallow; proxy.OperationTimeout = getCimInstance.OperationTimeoutSec; - if(getCimInstance.ResourceUri != null ) + if (getCimInstance.ResourceUri != null) { proxy.ResourceUri = getCimInstance.ResourceUri; } @@ -373,11 +391,12 @@ private void SetSessionProxyProperties( { RemoveCimInstanceCommand removeCimInstance = cmdlet as RemoveCimInstanceCommand; proxy.OperationTimeout = removeCimInstance.OperationTimeoutSec; - if(removeCimInstance.ResourceUri != null ) + if (removeCimInstance.ResourceUri != null) { proxy.ResourceUri = removeCimInstance.ResourceUri; } - CimRemoveCimInstanceContext context = new CimRemoveCimInstanceContext( + + CimRemoveCimInstanceContext context = new( ConstValue.GetNamespace(removeCimInstance.Namespace), proxy); proxy.ContextObject = context; @@ -386,11 +405,12 @@ private void SetSessionProxyProperties( { SetCimInstanceCommand setCimInstance = cmdlet as SetCimInstanceCommand; proxy.OperationTimeout = setCimInstance.OperationTimeoutSec; - if(setCimInstance.ResourceUri != null ) + if (setCimInstance.ResourceUri != null) { proxy.ResourceUri = setCimInstance.ResourceUri; } - CimSetCimInstanceContext context = new CimSetCimInstanceContext( + + CimSetCimInstanceContext context = new( ConstValue.GetNamespace(setCimInstance.Namespace), setCimInstance.Property, proxy, @@ -438,7 +458,7 @@ protected CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -472,7 +492,7 @@ protected CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -489,11 +509,11 @@ protected CimSessionProxy CreateSessionProxy( /// /// Set object to proxy to pre-process - /// the result object if necessary + /// the result object if necessary. /// /// /// - private void SetPreProcess(CimSessionProxy proxy, GetCimInstanceCommand cmdlet) + private static void SetPreProcess(CimSessionProxy proxy, GetCimInstanceCommand cmdlet) { if (cmdlet.KeyOnly || (cmdlet.SelectProperties != null)) { @@ -504,14 +524,14 @@ private void SetPreProcess(CimSessionProxy proxy, GetCimInstanceCommand cmdlet) #region const strings /// - /// wql query format with where clause + /// Wql query format with where clause. /// private const string queryWithWhere = @"SELECT {0} FROM {1} WHERE {2}"; /// - /// wql query format without where clause + /// Wql query format without where clause. /// private const string queryWithoutWhere = @"SELECT {0} FROM {1}"; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs index 34a6319f08a..899e67495cc 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -26,18 +24,19 @@ public abstract class CimIndicationEventArgs : EventArgs /// Returns an Object value for an operation context /// /// - public Object Context + public object Context { get { return context; } } - internal Object context; + + internal object context; } /// - /// Cimindication exception event args, which containing occurred exception + /// Cimindication exception event args, which containing occurred exception. /// public class CimIndicationEventExceptionEventArgs : CimIndicationEventArgs { @@ -46,25 +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; } } @@ -75,42 +65,40 @@ public CimIndicationEventExceptionEventArgs(Exception theException) public class CimIndicationEventInstanceEventArgs : CimIndicationEventArgs { /// - /// Get ciminstance of the indication object + /// Get ciminstance of the indication object. /// public CimInstance NewEvent { get { - return (result == null) ? null : result.Instance; + return result?.Instance; } } /// - /// Get MachineId of the indication object + /// Get MachineId of the indication object. /// public string MachineId { get { - return (result == null) ? null : result.MachineId; + return result?.MachineId; } } /// - /// Get BookMark of the indication object + /// Get BookMark of the indication object. /// 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; } /// @@ -137,7 +125,7 @@ public CimIndicationEventInstanceEventArgs(CimSubscriptionResult result) public class CimIndicationWatcher { /// - /// status of object. + /// Status of object. /// internal enum Status { @@ -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,13 +239,14 @@ private void NewSubscriptionResultHandler(object src, CimSubscriptionEventArgs a /// If set EnableRaisingEvents to false, which will be ignored /// /// - [BrowsableAttribute(false)] + [Browsable(false)] public bool EnableRaisingEvents { get { return enableRaisingEvents; } + set { DebugHelper.WriteLogEx(); @@ -275,6 +257,7 @@ public bool EnableRaisingEvents } } } + private bool enableRaisingEvents; /// @@ -286,7 +269,7 @@ public void Start() { DebugHelper.WriteLogEx(); - lock(myLock) + lock (myLock) { if (status == Status.Default) { @@ -308,6 +291,7 @@ public void Start() this.queryExpression, this.operationTimeout); } + status = Status.Started; } } @@ -331,6 +315,7 @@ public void Stop() DebugHelper.WriteLog("Dispose CimRegisterCimIndication object", 4); this.cimRegisterCimIndication.Dispose(); } + status = Status.Stopped; } } @@ -339,7 +324,7 @@ public void Stop() #region internal method /// /// Set the cmdlet object to throw ThrowTerminatingError - /// in case there is a subscription failure + /// in case there is a subscription failure. /// /// internal void SetCmdlet(Cmdlet cmdlet) @@ -360,22 +345,22 @@ internal void SetCmdlet(Cmdlet cmdlet) private CimRegisterCimIndication cimRegisterCimIndication; /// - /// the status of object + /// The status of object. /// private Status status; /// - /// lock started field + /// Lock started field. /// private object myLock; /// - /// CimSession parameter name + /// CimSession parameter name. /// private const string cimSessionParameterName = "cimSession"; /// - /// QueryExpression parameter name + /// QueryExpression parameter name. /// private const string queryExpressionParameterName = "queryExpression"; @@ -390,8 +375,8 @@ internal void SetCmdlet(Cmdlet cmdlet) private string nameSpace; private string queryDialect; private string queryExpression; - private UInt32 operationTimeout; + private uint operationTimeout; #endregion #endregion } -}//End namespace +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs index 79f91a1d868..50f4d12dd74 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -10,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Management.Automation; #endregion @@ -30,9 +27,7 @@ internal sealed class CimInvokeCimMethod : CimAsyncOperation internal class CimInvokeCimMethodContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -43,40 +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() @@ -88,12 +67,12 @@ public CimInvokeCimMethod() /// Base on parametersetName to retrieve ciminstances /// /// - /// object + /// object. 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) @@ -103,6 +82,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { proxys.Add(CreateSessionProxy(computerName, cmdlet.CimInstance, cmdlet)); } + break; case CimBaseCommand.ClassNameComputerSet: case CimBaseCommand.CimClassComputerSet: @@ -112,6 +92,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { proxys.Add(CreateSessionProxy(computerName, cmdlet)); } + break; case CimBaseCommand.ClassNameSessionSet: case CimBaseCommand.CimClassSessionSet: @@ -123,10 +104,12 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) CimSessionProxy proxy = CreateSessionProxy(session, cmdlet); proxys.Add(proxy); } + break; default: break; } + CimMethodParametersCollection paramsCollection = CreateParametersCollection(cmdlet.Arguments, cmdlet.CimClass, cmdlet.CimInstance, cmdlet.MethodName); @@ -139,7 +122,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) case CimBaseCommand.ResourceUriComputerSet: { string target = string.Format(CultureInfo.CurrentUICulture, targetClass, cmdlet.ClassName); - if(cmdlet.ResourceUri != null ) + if (cmdlet.ResourceUri != null) { nameSpace = cmdlet.Namespace; } @@ -147,12 +130,14 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { nameSpace = ConstValue.GetNamespace(cmdlet.Namespace); } + foreach (CimSessionProxy proxy in proxys) { if (!cmdlet.ShouldProcess(target, action)) { return; } + proxy.InvokeMethodAsync( nameSpace, cmdlet.ClassName, @@ -160,6 +145,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) paramsCollection); } } + break; case CimBaseCommand.CimClassComputerSet: case CimBaseCommand.CimClassSessionSet: @@ -172,6 +158,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { return; } + proxy.InvokeMethodAsync( nameSpace, cmdlet.CimClass.CimSystemProperties.ClassName, @@ -179,6 +166,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) paramsCollection); } } + break; case CimBaseCommand.QueryComputerSet: case CimBaseCommand.QuerySessionSet: @@ -186,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, @@ -195,12 +183,13 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) // firstly query instance and then invoke method upon returned instances proxy.QueryInstancesAsync(nameSpace, ConstValue.GetQueryDialectWithDefault(cmdlet.QueryDialect), cmdlet.Query); } + break; case CimBaseCommand.CimInstanceComputerSet: case CimBaseCommand.CimInstanceSessionSet: { string target = cmdlet.CimInstance.ToString(); - if(cmdlet.ResourceUri != null ) + if (cmdlet.ResourceUri != null) { nameSpace = cmdlet.Namespace; } @@ -208,12 +197,14 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { nameSpace = ConstValue.GetNamespace(cmdlet.CimInstance.CimSystemProperties.Namespace); } + foreach (CimSessionProxy proxy in proxys) { if (!cmdlet.ShouldProcess(target, action)) { return; } + proxy.InvokeMethodAsync( nameSpace, cmdlet.CimInstance, @@ -221,6 +212,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) paramsCollection); } } + break; default: break; @@ -262,12 +254,12 @@ public void InvokeCimMethodOnCimInstance(CimInstance cimInstance, XOperationCont /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, InvokeCimMethodCommand cmdlet) { proxy.OperationTimeout = cmdlet.OperationTimeoutSec; - if(cmdlet.ResourceUri != null ) + if (cmdlet.ResourceUri != null) { proxy.ResourceUri = cmdlet.ResourceUri; } @@ -310,7 +302,7 @@ private CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -335,8 +327,8 @@ private CimSessionProxy CreateSessionProxy( /// /// /// - /// See CimProperty.Create - /// CimProperty.Create + /// See CimProperty.Create. + /// CimProperty.Create. private CimMethodParametersCollection CreateParametersCollection( IDictionary parameters, CimClass cimClass, @@ -361,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, @@ -378,8 +370,8 @@ private CimMethodParametersCollection CreateParametersCollection( declaration = cimClass.CimClassMethods[methodName]; if (declaration == null) { - throw new ArgumentException(String.Format( - CultureInfo.CurrentUICulture, Strings.InvalidMethod, methodName, className)); + throw new ArgumentException(string.Format( + CultureInfo.CurrentUICulture, CimCmdletStrings.InvalidMethod, methodName, className)); } } else if (cimInstance != null) @@ -393,9 +385,10 @@ private CimMethodParametersCollection CreateParametersCollection( CimMethodParameterDeclaration paramDeclaration = declaration.Parameters[parameterName]; if (paramDeclaration == null) { - throw new ArgumentException(String.Format( - CultureInfo.CurrentUICulture, Strings.InvalidMethodParameter, parameterName, methodName, className)); + throw new ArgumentException(string.Format( + CultureInfo.CurrentUICulture, CimCmdletStrings.InvalidMethodParameter, parameterName, methodName, className)); } + parameter = CimMethodParameter.Create( parameterName, parameterValue, @@ -436,23 +429,25 @@ private CimMethodParametersCollection CreateParametersCollection( } } } + if (parameter != null) collection.Add(parameter); } + return collection; } #endregion #region const strings /// - /// operation target + /// Operation target. /// private const string targetClass = @"{0}"; /// - /// action + /// Action. /// private const string actionTemplate = @"Invoke-CimMethod: {0}"; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs index 84b0883feb1..beb3e551d33 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -22,9 +20,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimNewCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -46,6 +42,7 @@ internal CimNewCimInstanceContext( internal sealed class CimNewCimInstance : CimAsyncOperation { /// + /// Initializes a new instance of the class. /// /// Constructor /// @@ -61,7 +58,7 @@ public CimNewCimInstance() /// either remotely or locally /// /// - /// object + /// object. public void NewCimInstance(NewCimInstanceCommand cmdlet) { DebugHelper.WriteLogEx(); @@ -81,20 +78,20 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) cmdlet.Key, cmdlet.Property, cmdlet); - } + break; case CimBaseCommand.ResourceUriSessionSet: case CimBaseCommand.ResourceUriComputerSet: { - nameSpace = cmdlet.Namespace; //passing null is ok for resourceUri set + nameSpace = cmdlet.Namespace; // passing null is ok for resourceUri set cimInstance = CreateCimInstance("DummyClass", nameSpace, cmdlet.Key, cmdlet.Property, cmdlet); - } + break; case CimBaseCommand.CimClassComputerSet: case CimBaseCommand.CimClassSessionSet: @@ -103,8 +100,8 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) cimInstance = CreateCimInstance(cmdlet.CimClass, cmdlet.Property, cmdlet); - } + break; default: return; @@ -135,7 +132,7 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) } // create ciminstance on server - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { @@ -150,6 +147,7 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) proxys.Add(CreateSessionProxy(computerName, cmdlet)); } } + break; case CimBaseCommand.CimClassSessionSet: case CimBaseCommand.ClassNameSessionSet: @@ -158,6 +156,7 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) { proxys.Add(CreateSessionProxy(session, cmdlet)); } + break; } @@ -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,12 +201,12 @@ internal void GetCimInstance(CimInstance cimInstance, XOperationContextBase cont /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, NewCimInstanceCommand cmdlet) { proxy.OperationTimeout = cmdlet.OperationTimeoutSec; - if(cmdlet.ResourceUri != null) + if (cmdlet.ResourceUri != null) { proxy.ResourceUri = cmdlet.ResourceUri; } @@ -233,7 +231,7 @@ private CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// @@ -258,8 +256,8 @@ private CimSessionProxy CreateSessionProxy( /// /// /// - /// See CimProperty.Create - /// CimProperty.Create + /// See CimProperty.Create. + /// CimProperty.Create. private CimInstance CreateCimInstance( string className, string cimNamespace, @@ -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) @@ -291,12 +289,12 @@ private CimInstance CreateCimInstance( { flag = CimFlags.Key; } + object propertyValue = GetBaseObject(enumerator.Value); 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); @@ -309,8 +307,8 @@ private CimInstance CreateCimInstance( flag); cimInstance.CimInstanceProperties.Add(newProperty); } - } + return cimInstance; } @@ -323,19 +321,20 @@ private CimInstance CreateCimInstance( /// /// /// - /// See CimProperty.Create - /// CimProperty.Create + /// See CimProperty.Create. + /// CimProperty.Create. private CimInstance CreateCimInstance( CimClass cimClass, 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) @@ -344,9 +343,11 @@ private CimInstance CreateCimInstance( cmdlet.ThrowInvalidProperty(notfoundProperties, cmdlet.CimClass.CimSystemProperties.ClassName, @"Property", action, properties); return null; } + object propertyValue = GetBaseObject(properties[property]); cimInstance.CimInstanceProperties[property].Value = propertyValue; } + return cimInstance; } @@ -354,9 +355,9 @@ private CimInstance CreateCimInstance( #region const strings /// - /// action + /// Action. /// private const string action = @"New-CimInstance"; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs index 785e0f20002..48b887f00b4 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using Microsoft.Management.Infrastructure.Options; @@ -24,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; } @@ -56,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; @@ -84,11 +82,12 @@ public override void Execute(CmdletOperationBase cmdlet) // unblocking the waiting thread this.OnComplete(); } + break; case CimPromptType.Normal: try { - result = cmdlet.ShouldProcess(message); + result = cmdlet.ShouldProcess(Message); if (result) { this.responseType = CimResponseType.Yes; @@ -108,32 +107,27 @@ public override void Execute(CmdletOperationBase cmdlet) // unblocking the waiting thread this.OnComplete(); } + break; default: break; } + this.OnComplete(); } #region members /// - /// prompt message + /// Prompt message. /// - public string Message - { - get - { - return message; - } - } - private string message; + public string Message { get; } /// - /// prompt type -Normal or Critical + /// Prompt type -Normal or Critical. /// - private CimPromptType prompt; + private readonly CimPromptType prompt; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs index 91005d8ecf0..692a5f123e8 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -14,7 +12,6 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets { - /// /// /// Subscription result event args @@ -27,14 +24,15 @@ internal abstract class CimSubscriptionEventArgs : EventArgs /// Returns an Object value for an operation context /// /// - public Object Context + public object Context { get { return context; } } - protected Object context; + + protected object context; } /// @@ -49,24 +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; } } @@ -82,24 +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() @@ -131,7 +113,7 @@ public CimRegisterCimIndication() /// /// Start an indication subscription target to the given computer. /// - /// null stands for localhost + /// Null stands for localhost. /// /// /// @@ -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; @@ -153,24 +135,23 @@ public void RegisterCimIndication( /// /// Start an indication subscription through a given . /// - /// Cannot be null + /// Cannot be null. /// /// /// /// - /// throw if cimSession is null + /// Throw if cimSession is null. public void RegisterCimIndication( CimSession cimSession, 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, Strings.NullArgument, @"cimSession")); - } + + ArgumentNullException.ThrowIfNull(cimSession, string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.NullArgument, nameof(cimSession))); + this.TargetComputerName = cimSession.ComputerName; CimSessionProxy proxy = CreateSessionProxy(cimSession, operationTimeout); proxy.SubscribeAsync(nameSpace, queryDialect, queryExpression); @@ -204,7 +185,7 @@ protected override void SubscribeToCimSessionProxyEvent(CimSessionProxy proxy) /// /// object raised the event /// - /// event argument + /// Event argument. private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actionArgs) { DebugHelper.WriteLogEx("action is {0}. Disposed {1}", 0, actionArgs.Action, this.Disposed); @@ -215,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 @@ -226,21 +206,21 @@ private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actio this.ackedEvent.Set(); return; } + EventHandler temp = this.OnNewSubscriptionResult; if (temp != null) { 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) @@ -267,13 +247,13 @@ private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actio } /// - /// block the ps thread until ACK message or Error happened. + /// Block the ps thread until ACK message or Error happened. /// 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) @@ -282,16 +262,17 @@ 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; } } + DebugHelper.WriteLogEx("ACK happened", 0); } #endregion @@ -300,22 +281,22 @@ private void WaitForAckMessage() /// /// The cmdlet object who issue this subscription, /// to throw ThrowTerminatingError - /// in case there is a subscription failure + /// in case there is a subscription failure. /// /// internal Cmdlet Cmdlet { - set; get; + set; } /// - /// target computername + /// Target computername. /// - internal String TargetComputerName + internal string TargetComputerName { - set; get; + set; } #endregion @@ -331,7 +312,7 @@ internal String TargetComputerName /// private CimSessionProxy CreateSessionProxy( string computerName, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(computerName); proxy.OperationTimeout = timeout; @@ -339,14 +320,14 @@ private CimSessionProxy CreateSessionProxy( } /// - /// Create and set properties + /// Create and set properties. /// /// /// /// private CimSessionProxy CreateSessionProxy( CimSession session, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(session); proxy.OperationTimeout = timeout; @@ -357,18 +338,11 @@ private CimSessionProxy CreateSessionProxy( #region private members /// - /// Exception occurred while start the subscription + /// Exception occurred while start the subscription. /// - internal Exception Exception - { - get - { - return exception; - } - } - private Exception exception; + internal Exception Exception { get; private set; } #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs index e5fed39d402..a6182166170 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs @@ -1,12 +1,8 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System.Collections; -using System; using System.Collections.Generic; using System.Diagnostics; @@ -21,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimRemoveCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -43,9 +37,7 @@ internal CimRemoveCimInstanceContext(string theNamespace, internal sealed class CimRemoveCimInstance : CimGetInstance { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimRemoveCimInstance() : base() @@ -57,14 +49,14 @@ public CimRemoveCimInstance() /// Base on parametersetName to retrieve ciminstances /// /// - /// object + /// object. public void RemoveCimInstance(RemoveCimInstanceCommand cmdlet) { DebugHelper.WriteLogEx(); IEnumerable computerNames = ConstValue.GetComputerNames( GetComputerName(cmdlet)); - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: @@ -72,22 +64,25 @@ public void RemoveCimInstance(RemoveCimInstanceCommand cmdlet) { proxys.Add(CreateSessionProxy(computerName, cmdlet.CimInstance, cmdlet)); } + break; case CimBaseCommand.CimInstanceSessionSet: foreach (CimSession session in GetCimSession(cmdlet)) { proxys.Add(CreateSessionProxy(session, cmdlet)); } + break; default: break; } + switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: case CimBaseCommand.CimInstanceSessionSet: string nameSpace = null; - if(cmdlet.ResourceUri != null ) + if (cmdlet.ResourceUri != null) { nameSpace = GetCimInstanceParameter(cmdlet).CimSystemProperties.Namespace; } @@ -95,6 +90,7 @@ public void RemoveCimInstance(RemoveCimInstanceCommand cmdlet) { nameSpace = ConstValue.GetNamespace(GetCimInstanceParameter(cmdlet).CimSystemProperties.Namespace); } + string target = cmdlet.CimInstance.ToString(); foreach (CimSessionProxy proxy in proxys) { @@ -102,8 +98,10 @@ public void RemoveCimInstance(RemoveCimInstanceCommand cmdlet) { return; } + proxy.DeleteInstanceAsync(nameSpace, cmdlet.CimInstance); } + break; case CimBaseCommand.QueryComputerSet: case CimBaseCommand.QuerySessionSet: @@ -139,9 +137,9 @@ internal void RemoveCimInstance(CimInstance cimInstance, XOperationContextBase c #region const strings /// - /// action + /// Action. /// private const string action = @"Remove-CimInstance"; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs index f8758d98a69..389c45c8314 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs @@ -1,13 +1,11 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; -using System.Management.Automation; using System.Globalization; +using System.Management.Automation; #endregion @@ -29,30 +27,23 @@ public enum AsyncResultType #region CimResultContext /// - /// Cim Result Context + /// Cim Result Context. /// internal class CimResultContext { /// - /// constructor + /// Initializes a new instance of the class. /// /// internal CimResultContext(object ErrorSource) { - this.errorSource = ErrorSource; + this.ErrorSource = ErrorSource; } /// - /// ErrorSource property + /// ErrorSource property. /// - internal object ErrorSource - { - get - { - return this.errorSource; - } - } - private object errorSource; + internal object ErrorSource { get; } } #endregion @@ -65,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) @@ -81,13 +72,13 @@ public AsyncResultEventArgsBase( } /// - /// Constructor + /// Initializes a new instance of the class. /// /// /// /// /// - public AsyncResultEventArgsBase( + protected AsyncResultEventArgsBase( CimSession session, IObservable observable, AsyncResultType resultType, @@ -118,16 +109,14 @@ public AsyncResultEventArgsBase( internal class AsyncResultCompleteEventArgs : AsyncResultEventArgsBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// - /// object + /// object. /// public AsyncResultCompleteEventArgs( CimSession session, - IObservable observable) : - base(session, observable, AsyncResultType.Completion) + IObservable observable) + : base(session, observable, AsyncResultType.Completion) { } } @@ -140,7 +129,7 @@ public AsyncResultCompleteEventArgs( internal class AsyncResultObjectEventArgs : AsyncResultEventArgsBase { /// - /// Constructor + /// Initializes a new instance of the class. /// /// /// @@ -148,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; } @@ -165,7 +154,7 @@ public AsyncResultObjectEventArgs( internal class AsyncResultErrorEventArgs : AsyncResultEventArgsBase { /// - /// Constructor + /// Initializes a new instance of the class. /// /// /// @@ -173,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. /// /// /// @@ -190,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; } @@ -207,7 +196,7 @@ public AsyncResultErrorEventArgs( /// EnumerateInstancesAsync operation of object. /// /// - /// (See http://channel9.msdn.com/posts/J.Van.Gogh/Reactive-Extensions-API-in-depth-Contract/) + /// (See https://channel9.msdn.com/posts/J.Van.Gogh/Reactive-Extensions-API-in-depth-Contract/) /// for the IObserver/IObservable contact /// - the only possible sequence is OnNext* (OnCompleted|OnError)? /// - callbacks are serialized @@ -218,41 +207,31 @@ public AsyncResultErrorEventArgs( 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 + /// 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 + /// 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 + /// object that issued the operation. + /// Operation that can be observed. public CimResultObserver(CimSession session, IObservable observable, CimResultContext cimResultContext) { - this.session = session; + this.CurrentSession = session; this.observable = observable; this.context = cimResultContext; } @@ -270,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) @@ -286,13 +265,13 @@ public virtual void OnCompleted() /// Operation completed with an error /// /// - /// error object + /// Error object. 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) @@ -303,7 +282,7 @@ public virtual void OnError(Exception error) } /// - /// Deliver the result value + /// Deliver the result value. /// /// protected void OnNextCore(object value) @@ -311,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) @@ -327,7 +306,7 @@ protected void OnNextCore(object value) /// Operation got a new result object /// /// - /// result object + /// Result object. public virtual void OnNext(T value) { DebugHelper.WriteLogEx("value = {0}.", 1, value); @@ -336,42 +315,36 @@ public virtual void OnNext(T value) { return; } + this.OnNextCore(value); } #region members /// - /// Session object of the operation + /// Session object of the operation. /// - protected CimSession CurrentSession - { - get - { - return session; - } - } - private CimSession session; + protected CimSession CurrentSession { get; } /// - /// async operation that can be observed + /// 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 } /// - /// CimSubscriptionResultObserver class definition + /// CimSubscriptionResultObserver class definition. /// internal class CimSubscriptionResultObserver : CimResultObserver { /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -381,7 +354,7 @@ public CimSubscriptionResultObserver(CimSession session, IObservable obs } /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -394,7 +367,7 @@ public CimSubscriptionResultObserver( } /// - /// Override the OnNext method + /// Override the OnNext method. /// /// public override void OnNext(CimSubscriptionResult value) @@ -405,12 +378,12 @@ public override void OnNext(CimSubscriptionResult value) } /// - /// CimMethodResultObserver class definition + /// CimMethodResultObserver class definition. /// internal class CimMethodResultObserver : CimResultObserver { /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -420,7 +393,7 @@ public CimMethodResultObserver(CimSession session, IObservable observabl } /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -434,7 +407,7 @@ public CimMethodResultObserver( } /// - /// Override the OnNext method + /// Override the OnNext method. /// /// public override void OnNext(CimMethodResultBase value) @@ -446,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(); @@ -458,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(); @@ -468,28 +439,29 @@ public override void OnNext(CimMethodResultBase value) resultObject.Properties.Add(new PSNoteProperty(@"ItemValue", methodStreamedResult.ItemValue)); } } + if (resultObject != null) { resultObject.Properties.Add(new PSNoteProperty(@"PSComputerName", this.CurrentSession.ComputerName)); resultObject.TypeNames.Insert(0, resultObjectPSType); - resultObject.TypeNames.Insert(0, String.Format(CultureInfo.InvariantCulture, PSTypeCimMethodResultTemplate, resultObjectPSType, ClassName, MethodName)); + resultObject.TypeNames.Insert(0, string.Format(CultureInfo.InvariantCulture, PSTypeCimMethodResultTemplate, resultObjectPSType, ClassName, MethodName)); base.OnNextCore(resultObject); } } /// - /// methodname + /// Methodname. /// - internal String MethodName + internal string MethodName { get; set; } /// - /// classname + /// Classname. /// - internal String ClassName + internal string ClassName { get; set; @@ -497,12 +469,12 @@ internal String ClassName } /// - /// IgnoreResultObserver class definition + /// IgnoreResultObserver class definition. /// internal class IgnoreResultObserver : CimResultObserver { /// - /// constructor + /// Initializes a new instance of the class. /// /// /// @@ -512,7 +484,7 @@ public IgnoreResultObserver(CimSession session, IObservable observable) } /// - /// Override the OnNext method + /// Override the OnNext method. /// /// public override void OnNext(CimInstance value) diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs index d1cf303cb50..b8ebc9adaef 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs @@ -1,16 +1,14 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives 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 +22,32 @@ internal class CimSessionWrapper #region members /// - /// id of the cimsession + /// Id of the cimsession. /// - public uint SessionId - { - get - { - return this.sessionId; - } - } - private uint sessionId; + public uint SessionId { get; } /// - /// instanceId of the cimsession + /// InstanceId of the cimsession. /// - public Guid InstanceId - { - get - { - return this.instanceId; - } - } - private Guid instanceId; + public Guid InstanceId { get; } /// - /// name of the cimsession + /// Name of the cimsession. /// - public string Name - { - get - { - return this.name; - } - } - private string name; + public string Name { get; } /// - /// computer name of the cimsession + /// Computer name of the cimsession. /// - public string ComputerName - { - get - { - return this.computerName; - } - } - private string computerName; + public string ComputerName { get; } /// - /// wrapped cimsession object + /// Wrapped cimsession object. /// - public CimSession CimSession - { - get - { - return this.cimSession; - } - } - private CimSession cimSession; + public CimSession CimSession { get; } /// - /// computer name of the cimsession + /// Computer name of the cimsession. /// public string Protocol { @@ -101,14 +64,16 @@ public string Protocol } } } + internal ProtocolType GetProtocolType() { return protocol; } - private ProtocolType protocol; + + private readonly ProtocolType protocol; /// - /// PSObject that wrapped the cimSession + /// PSObject that wrapped the cimSession. /// private PSObject psObject; @@ -122,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; } @@ -135,21 +100,22 @@ 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; } + return psObject; } } @@ -174,93 +140,91 @@ internal class CimSessionState : IDisposable /// where is the next available session number. /// For example, CimSession1, CimSession2, etc... /// - internal static string CimSessionClassName = "CimSession"; + internal static readonly string CimSessionClassName = "CimSession"; /// - /// CimSession object name + /// CimSession object name. /// - internal static string CimSessionObject = "{CimSession Object}"; + internal static readonly string CimSessionObject = "{CimSession Object}"; /// /// /// CimSession object path, which is identifying a cimsession object /// /// - internal static string SessionObjectPath = @"CimSession id = {0}, name = {2}, ComputerName = {3}, instance id = {1}"; + internal static readonly string SessionObjectPath = @"CimSession id = {0}, name = {2}, ComputerName = {3}, instance id = {1}"; /// - /// Id property name of cimsession wrapper object + /// Id property name of cimsession wrapper object. /// - internal static string idPropName = "Id"; + internal static readonly string idPropName = "Id"; /// - /// Instanceid property name of cimsession wrapper object + /// Instanceid property name of cimsession wrapper object. /// - internal static string instanceidPropName = "InstanceId"; + internal static readonly string instanceidPropName = "InstanceId"; /// - /// Name property name of cimsession wrapper object + /// Name property name of cimsession wrapper object. /// - internal static string namePropName = "Name"; + internal static readonly string namePropName = "Name"; /// - /// Computer name property name of cimsession object + /// Computer name property name of cimsession object. /// - internal static string computernamePropName = "ComputerName"; + internal static readonly string computernamePropName = "ComputerName"; /// - /// Protocol name property name of cimsession object + /// Protocol name property name of cimsession object. /// - internal static string protocolPropName = "Protocol"; + internal static readonly string protocolPropName = "Protocol"; /// /// - /// session counter bound to current runspace. + /// Session counter bound to current runspace. /// /// - private UInt32 sessionNameCounter; + private uint sessionNameCounter; /// /// /// 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 /// - /// - /// constructor - /// + /// Initializes a new instance of the class. /// internal CimSessionState() { @@ -290,8 +254,8 @@ internal int GetSessionsCount() /// Generates an unique session id. /// /// - /// Unique session id under current runspace - internal UInt32 GenerateSessionId() + /// Unique session id under current runspace. + internal uint GenerateSessionId() { return this.sessionNameCounter++; } @@ -299,7 +263,7 @@ internal UInt32 GenerateSessionId() /// /// - /// Indicates whether this object was disposed or not + /// Indicates whether this object was disposed or not. /// /// private bool _disposed; @@ -333,7 +297,7 @@ public void Dispose() /// other objects. Only unmanaged resources can be disposed. /// /// - /// Whether it is directly called + /// Whether it is directly called. protected virtual void Dispose(bool disposing) { if (!this._disposed) @@ -360,6 +324,7 @@ public void Cleanup() { session.Dispose(); } + curCimSessionWrapper.Clear(); curCimSessionsByName.Clear(); curCimSessionsByComputerName.Clear(); @@ -374,23 +339,25 @@ public void Cleanup() /// /// - /// Add new CimSession object to cache + /// Add new CimSession object to cache. /// /// /// /// /// /// + /// + /// /// internal PSObject AddObjectToCache( CimSession session, - UInt32 sessionId, + uint sessionId, Guid instanceId, - String name, - String computerName, + string name, + string computerName, ProtocolType protocol) { - CimSessionWrapper wrapper = new CimSessionWrapper( + CimSessionWrapper wrapper = new( sessionId, instanceId, name, computerName, session, protocol); HashSet objects; @@ -399,6 +366,7 @@ internal PSObject AddObjectToCache( objects = new HashSet(); this.curCimSessionsByComputerName.Add(computerName, objects); } + objects.Add(wrapper); if (!this.curCimSessionsByName.TryGetValue(name, out objects)) @@ -406,6 +374,7 @@ internal PSObject AddObjectToCache( objects = new HashSet(); this.curCimSessionsByName.Add(name, objects); } + objects.Add(wrapper); this.curCimSessionsByInstanceId.Add(instanceId, wrapper); @@ -422,37 +391,42 @@ internal PSObject AddObjectToCache( /// internal string GetRemoveSessionObjectTarget(PSObject psObject) { - String message = String.Empty; + string message = string.Empty; if (psObject.BaseObject is CimSession) { - UInt32 id = 0x0; + uint id = 0x0; Guid instanceId = Guid.Empty; - String name = String.Empty; - String computerName = string.Empty; - if (psObject.Properties[idPropName].Value is UInt32) + string name = string.Empty; + string computerName = string.Empty; + if (psObject.Properties[idPropName].Value is uint) { id = Convert.ToUInt32(psObject.Properties[idPropName].Value, null); } + if (psObject.Properties[instanceidPropName].Value is Guid) { instanceId = (Guid)psObject.Properties[instanceidPropName].Value; } - if (psObject.Properties[namePropName].Value is String) + + if (psObject.Properties[namePropName].Value is string) { - name = (String)psObject.Properties[namePropName].Value; + name = (string)psObject.Properties[namePropName].Value; } - if (psObject.Properties[computernamePropName].Value is String) + + if (psObject.Properties[computernamePropName].Value is string) { - computerName = (String)psObject.Properties[computernamePropName].Value; + computerName = (string)psObject.Properties[computernamePropName].Value; } - message = String.Format(CultureInfo.CurrentUICulture, SessionObjectPath, id, instanceId, name, computerName); + + message = string.Format(CultureInfo.CurrentUICulture, SessionObjectPath, id, instanceId, name, computerName); } + return message; } /// /// - /// Remove given object from cache + /// Remove given object from cache. /// /// /// @@ -468,7 +442,7 @@ internal void RemoveOneSessionObjectFromCache(PSObject psObject) /// /// - /// Remove given object from cache + /// Remove given object from cache. /// /// /// @@ -480,9 +454,10 @@ internal void RemoveOneSessionObjectFromCache(CimSession session) { return; } + CimSessionWrapper wrapper = this.curCimSessionWrapper[session]; - String name = wrapper.Name; - String computerName = wrapper.ComputerName; + string name = wrapper.Name; + string computerName = wrapper.ComputerName; DebugHelper.WriteLog("name {0}, computername {1}, id {2}, instanceId {3}", 1, name, computerName, wrapper.SessionId, wrapper.InstanceId); @@ -491,10 +466,12 @@ internal void RemoveOneSessionObjectFromCache(CimSession session) { objects.Remove(wrapper); } + if (this.curCimSessionsByName.TryGetValue(name, out objects)) { objects.Remove(wrapper); } + RemoveSessionInternal(session, wrapper); } @@ -527,39 +504,39 @@ private void RemoveSessionInternal(CimSession session, CimSessionWrapper wrapper /// /// /// - private void AddErrorRecord( + private static void AddErrorRecord( ref List errRecords, string propertyName, object propertyValue) { errRecords.Add( new ErrorRecord( - new CimException(String.Format(CultureInfo.CurrentUICulture, Strings.CouldNotFindCimsessionObject, propertyName, propertyValue)), + new CimException(string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.CouldNotFindCimsessionObject, propertyName, propertyValue)), string.Empty, ErrorCategory.ObjectNotFound, null)); } /// - /// Query session list by given id array + /// Query session list by given id array. /// /// - /// List of session wrapper objects - internal IEnumerable QuerySession(IEnumerable ids, + /// List of session wrapper objects. + 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 (UInt32 id in ids) + 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()); } } @@ -568,20 +545,22 @@ internal IEnumerable QuerySession(IEnumerable ids, AddErrorRecord(ref errRecords, idPropName, id); } } + return sessions; } /// - /// Query session list by given instance id array + /// Query session list by given instance id array. /// /// - /// List of session wrapper objects - internal IEnumerable QuerySession(IEnumerable instanceIds, + /// List of session wrapper objects. + 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) { @@ -599,31 +578,32 @@ internal IEnumerable QuerySession(IEnumerable instanceIds, AddErrorRecord(ref errRecords, instanceidPropName, instanceid); } } + return sessions; } /// - /// Query session list by given name array + /// Query session list by given name array. /// /// - /// List of session wrapper objects + /// List of session wrapper objects. 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); - foreach (KeyValuePair> kvp in this.curCimSessionsByName) + 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)) @@ -634,26 +614,28 @@ internal IEnumerable QuerySession(IEnumerable nameArray, } } } + if (!foundSession && !WildcardPattern.ContainsWildcardCharacters(name)) { AddErrorRecord(ref errRecords, namePropName, name); } } + return sessions; } /// - /// Query session list by given computer name array + /// Query session list by given computer name array. /// /// - /// List of session wrapper objects + /// List of session wrapper objects. 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) { @@ -661,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)) @@ -671,25 +653,27 @@ internal IEnumerable QuerySessionByComputerName( } } } + if (!foundSession) { AddErrorRecord(ref errRecords, computernamePropName, computername); } } + return sessions; } /// - /// Query session list by given session objects array + /// Query session list by given session objects array. /// /// - /// List of session wrapper objects + /// List of session wrapper objects. 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) { @@ -707,14 +691,15 @@ internal IEnumerable QuerySession(IEnumerable cimsessions, AddErrorRecord(ref errRecords, CimSessionClassName, CimSessionObject); } } + return sessions; } /// - /// Query session wrapper object + /// Query session wrapper object. /// /// - /// session wrapper + /// Session wrapper. internal CimSessionWrapper QuerySession(CimSession cimsession) { CimSessionWrapper wrapper; @@ -723,10 +708,10 @@ internal CimSessionWrapper QuerySession(CimSession cimsession) } /// - /// Query session object with given CimSessionInstanceID + /// Query session object with given CimSessionInstanceID. /// /// - /// CimSession object + /// CimSession object. internal CimSession QuerySession(Guid cimSessionInstanceId) { if (this.curCimSessionsByInstanceId.ContainsKey(cimSessionInstanceId)) @@ -734,6 +719,7 @@ internal CimSession QuerySession(Guid cimSessionInstanceId) CimSessionWrapper wrapper = this.curCimSessionsByInstanceId[cimSessionInstanceId]; return wrapper.CimSession; } + return null; } #endregion @@ -756,18 +742,19 @@ internal class CimSessionBase #region constructor /// - /// Constructor + /// Initializes a new instance of the class. /// public CimSessionBase() { this.sessionState = cimSessions.GetOrAdd( CurrentRunspaceId, - delegate(Guid instanceId) + (Guid instanceId) => { if (Runspace.DefaultRunspace != null) { Runspace.DefaultRunspace.StateChanged += DefaultRunspace_StateChanged; } + return new CimSessionState(); }); } @@ -783,15 +770,15 @@ public CimSessionBase() /// can running parallelly under more than one runspace(s). /// /// - static internal ConcurrentDictionary cimSessions - = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary cimSessions + = new(); /// /// - /// Default runspace id + /// Default runspace Id. /// /// - static internal Guid defaultRunspaceId = Guid.Empty; + internal static readonly Guid defaultRunspaceId = Guid.Empty; /// /// @@ -802,7 +789,7 @@ static internal ConcurrentDictionary cimSessions internal CimSessionState sessionState; /// - /// Get current runspace id + /// Get current runspace id. /// private static Guid CurrentRunspaceId { @@ -829,11 +816,11 @@ public static CimSessionState GetCimSessionState() /// /// - /// clean up the dictionaries if the runspace is closed or broken. + /// Clean up the dictionaries if the runspace is closed or broken. /// /// - /// Runspace - /// Event args + /// Runspace. + /// Event args. private static void DefaultRunspace_StateChanged(object sender, RunspaceStateEventArgs e) { Runspace runspace = (Runspace)sender; @@ -844,9 +831,10 @@ private static void DefaultRunspace_StateChanged(object sender, RunspaceStateEve CimSessionState state; if (cimSessions.TryRemove(runspace.InstanceId, out state)) { - DebugHelper.WriteLog(String.Format(CultureInfo.CurrentUICulture, DebugHelper.runspaceStateChanged, runspace.InstanceId, e.RunspaceStateInfo.State)); + DebugHelper.WriteLog(string.Format(CultureInfo.CurrentUICulture, DebugHelper.runspaceStateChanged, runspace.InstanceId, e.RunspaceStateInfo.State)); state.Dispose(); } + runspace.StateChanged -= DefaultRunspace_StateChanged; break; default: @@ -872,14 +860,12 @@ private static void DefaultRunspace_StateChanged(object sender, RunspaceStateEve internal class CimNewSession : CimSessionBase, IDisposable { /// - /// CimTestCimSessionContext + /// CimTestCimSessionContext. /// internal class CimTestCimSessionContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -888,37 +874,28 @@ internal CimTestCimSessionContext( CimSessionWrapper wrapper) { this.proxy = theProxy; - this.cimSessionWrapper = wrapper; + this.CimSessionWrapper = wrapper; this.nameSpace = null; } /// - /// namespace + /// Namespace /// - internal CimSessionWrapper CimSessionWrapper - { - get - { - return this.cimSessionWrapper; - } - } - private CimSessionWrapper cimSessionWrapper; + internal CimSessionWrapper CimSessionWrapper { get; } } /// - /// - /// constructor - /// + /// Initializes a new instance of the class. /// internal CimNewSession() : base() { this.cimTestSession = new CimTestSession(); - this._disposed = false; + this.Disposed = false; } /// /// Create a new base on given cmdlet - /// and its parameter + /// and its parameter. /// /// /// @@ -939,19 +916,20 @@ internal void NewCimSession(NewCimSessionCommand cmdlet, sessionOptions = CimSessionProxy.CreateCimSessionOption(computerName, cmdlet.OperationTimeoutSec, credential); } + 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) + if (cmdlet.SkipTestConnection.IsPresent) { AddSessionToCache(proxy.CimSession, context, new CmdletOperationBase(cmdlet)); } else { - //CimSession will be returned as part of TestConnection + // CimSession will be returned as part of TestConnection this.cimTestSession.TestCimSession(computerName, proxy); } } @@ -959,7 +937,7 @@ internal void NewCimSession(NewCimSessionCommand cmdlet, /// /// - /// Add session to global cache + /// Add session to global cache, /// /// /// @@ -970,9 +948,9 @@ internal void AddSessionToCache(CimSession cimSession, XOperationContextBase con DebugHelper.WriteLogEx(); CimTestCimSessionContext testCimSessionContext = context as CimTestCimSessionContext; - UInt32 sessionId = this.sessionState.GenerateSessionId(); + 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(); @@ -988,11 +966,11 @@ internal void AddSessionToCache(CimSession cimSession, XOperationContextBase con /// /// - /// process all actions in the action queue + /// Process all actions in the action queue. /// /// /// - /// wrapper of cmdlet, for details + /// Wrapper of cmdlet, for details. /// public void ProcessActions(CmdletOperationBase cmdletOperation) { @@ -1001,12 +979,12 @@ public void ProcessActions(CmdletOperationBase cmdletOperation) /// /// - /// process remaining actions until all operations are completed or - /// current cmdlet is terminated by user + /// Process remaining actions until all operations are completed or + /// current cmdlet is terminated by user. /// /// /// - /// wrapper of cmdlet, for details + /// Wrapper of cmdlet, for details. /// public void ProcessRemainActions(CmdletOperationBase cmdletOperation) { @@ -1019,24 +997,17 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) /// object. /// /// - private CimTestSession cimTestSession; - #endregion //private members + private readonly CimTestSession cimTestSession; + #endregion // private members #region IDisposable /// /// - /// Indicates whether this object was disposed or not + /// Indicates whether this object was disposed or not. /// /// - protected bool Disposed - { - get - { - return _disposed; - } - } - private bool _disposed; + protected bool Disposed { get; private set; } /// /// @@ -1067,22 +1038,22 @@ public void Dispose() /// other objects. Only unmanaged resources can be disposed. /// /// - /// Whether it is directly called + /// 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 } } #endregion - }//End Class + } #endregion @@ -1090,13 +1061,13 @@ protected virtual void Dispose(bool disposing) /// /// - /// Get CimSession based on given id/instanceid/computername/name + /// Get CimSession based on given id/instanceid/computername/name. /// /// internal class CimGetSession : CimSessionBase { /// - /// constructor + /// Initializes a new instance of the class. /// public CimGetSession() : base() { @@ -1104,7 +1075,7 @@ public CimGetSession() : base() /// /// Get objects based on the given cmdlet - /// and its parameter + /// and its parameter. /// /// public void GetCimSession(GetCimSessionCommand cmdlet) @@ -1124,6 +1095,7 @@ public void GetCimSession(GetCimSessionCommand cmdlet) { sessionToGet = this.sessionState.QuerySessionByComputerName(cmdlet.ComputerName, out errorRecords); } + break; case CimBaseCommand.SessionIdSet: sessionToGet = this.sessionState.QuerySession(cmdlet.Id, out errorRecords); @@ -1137,13 +1109,15 @@ public void GetCimSession(GetCimSessionCommand cmdlet) default: break; } + if (sessionToGet != null) { - foreach(PSObject psobject in sessionToGet) + foreach (PSObject psobject in sessionToGet) { cmdlet.WriteObject(psobject); } } + if (errorRecords != null) { foreach (ErrorRecord errRecord in errorRecords) @@ -1156,7 +1130,7 @@ public void GetCimSession(GetCimSessionCommand cmdlet) #region helper methods #endregion - }//End Class + } #endregion @@ -1164,18 +1138,18 @@ public void GetCimSession(GetCimSessionCommand cmdlet) /// /// - /// Get CimSession based on given id/instanceid/computername/name + /// Get CimSession based on given id/instanceid/computername/name. /// /// internal class CimRemoveSession : CimSessionBase { /// - /// Remove session action string + /// Remove session action string. /// - internal static string RemoveCimSessionActionName = "Remove CimSession"; + internal static readonly string RemoveCimSessionActionName = "Remove CimSession"; /// - /// constructor + /// Initializes a new instance of the class. /// public CimRemoveSession() : base() { @@ -1183,7 +1157,7 @@ public CimRemoveSession() : base() /// /// Remove the objects based on given cmdlet - /// and its parameter + /// and its parameter. /// /// public void RemoveCimSession(RemoveCimSessionCommand cmdlet) @@ -1212,6 +1186,7 @@ public void RemoveCimSession(RemoveCimSessionCommand cmdlet) default: break; } + if (sessionToRemove != null) { foreach (PSObject psobject in sessionToRemove) @@ -1222,6 +1197,7 @@ public void RemoveCimSession(RemoveCimSessionCommand cmdlet) } } } + if (errorRecords != null) { foreach (ErrorRecord errRecord in errorRecords) @@ -1230,7 +1206,7 @@ public void RemoveCimSession(RemoveCimSessionCommand cmdlet) } } } - }//End Class + } #endregion @@ -1243,7 +1219,7 @@ public void RemoveCimSession(RemoveCimSessionCommand cmdlet) internal class CimTestSession : CimAsyncOperation { /// - /// Constructor + /// Initializes a new instance of the class. /// internal CimTestSession() : base() @@ -1268,4 +1244,4 @@ internal void TestCimSession( #endregion -}//End namespace +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs index 2dd000d96d2..5cdc168ae24 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -24,7 +22,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets #region Context base class /// - /// context base class for cross operations + /// Context base class for cross operations /// for example, some cmdlets need to query instance first and then /// remove instance, those scenarios need context object transferred /// from one operation to another. @@ -41,11 +39,12 @@ internal string Namespace return this.nameSpace; } } + protected string nameSpace; /// /// - /// Session proxy + /// Session proxy. /// /// internal CimSessionProxy Proxy @@ -55,17 +54,18 @@ internal CimSessionProxy Proxy return this.proxy; } } + protected CimSessionProxy proxy; } /// - /// class provides all information regarding the - /// current invocation to .net api + /// Class provides all information regarding the + /// current invocation to the .NET API. /// 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,36 +91,28 @@ 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 #region Preprocessing of result object interface /// - /// Defines a method to preprocessing an result object before sending to - /// output pipeline. + /// Defines a method to preprocessing an result object before sending to + /// output pipeline. /// [ComVisible(false)] internal interface IObjectPreProcess @@ -129,7 +121,7 @@ internal interface IObjectPreProcess /// Performs pre processing of given result object. /// /// - /// Pre-processed object + /// Pre-processed object. object Process(object resultObject); } #endregion @@ -143,13 +135,14 @@ internal interface IObjectPreProcess internal sealed class CmdletActionEventArgs : EventArgs { /// - /// Constructor + /// Initializes a new instance of the class. /// - /// CimBaseAction object bound to the event + /// CimBaseAction object bound to the event. public CmdletActionEventArgs(CimBaseAction action) { this.Action = action; } + public readonly CimBaseAction Action; } @@ -159,10 +152,10 @@ 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 + /// Object used to cancel the operation. + /// Async observable operation. public OperationEventArgs(IDisposable operationCancellation, IObservable operation, bool theSuccess) @@ -171,6 +164,7 @@ public OperationEventArgs(IDisposable operationCancellation, this.operation = operation; this.success = theSuccess; } + public readonly IDisposable operationCancellation; public readonly IObservable operation; public readonly bool success; @@ -196,9 +190,9 @@ internal class CimSessionProxy : IDisposable private static long gOperationCounter = 0; /// - /// temporary CimSession cache lock + /// Temporary CimSession cache lock. /// - private static readonly object temporarySessionCacheLock = new object(); + private static readonly object temporarySessionCacheLock = new(); /// /// temporary CimSession cache @@ -216,7 +210,7 @@ internal class CimSessionProxy : IDisposable /// then call Dispose on it. /// /// - private static Dictionary temporarySessionCache = new Dictionary(); + private static readonly Dictionary temporarySessionCache = new(); /// /// @@ -225,10 +219,10 @@ internal class CimSessionProxy : IDisposable /// otherwise insert it into the cache. /// /// - /// CimSession to be added + /// CimSession to be added. internal static void AddCimSessionToTemporaryCache(CimSession session) { - if (null != session) + if (session != null) { lock (temporarySessionCacheLock) { @@ -250,11 +244,11 @@ internal static void AddCimSessionToTemporaryCache(CimSession session) /// Wrapper function to remove CimSession from cache /// /// - /// Whether need to dispose the object + /// Whether need to dispose the object. private static void RemoveCimSessionFromTemporaryCache(CimSession session, bool dispose) { - if (null != session) + if (session != null) { bool removed = false; lock (temporarySessionCacheLock) @@ -293,7 +287,7 @@ private static void RemoveCimSessionFromTemporaryCache(CimSession session, /// If refcount became 0, call dispose on the object. /// /// - /// CimSession to be added + /// CimSession to be added. internal static void RemoveCimSessionFromTemporaryCache(CimSession session) { RemoveCimSessionFromTemporaryCache(session, true); @@ -303,87 +297,79 @@ 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 + /// Define an Event based on the NewActionHandler. /// - public event NewCmdletActionHandler OnNewCmdletAction; + public event EventHandler OnNewCmdletAction; /// - /// Define delegate that handles operation creation and complete - /// issued by the current CimSession object. + /// Event triggered when a new operation is started. /// - /// cimSession object, which raised the event - /// Event args - public delegate void OperationEventHandler( - object cimSession, - OperationEventArgs actionArgs); - - /// - /// 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) @@ -414,41 +400,51 @@ public CimSessionProxy(string computerName, CimInstance cimInstance) return; } } - String cimsessionComputerName = cimInstance.GetCimSessionComputerName(); + + 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); } /// - /// Create by given computer name, - /// session options + /// Initializes a new instance of the class. /// + /// + /// Create by given computer name, + /// session options. + /// /// /// - /// Used when create async operation + /// 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 + /// 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) { @@ -456,17 +452,20 @@ 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 + /// Used when create async operation. public CimSessionProxy(CimSession session, CimOperationOptions operOptions) { CreateSetSession(null, session, null, operOptions, false); } /// - /// Initialize CimSessionProxy object + /// Initialize CimSessionProxy object. /// /// /// @@ -486,20 +485,21 @@ private void CreateSetSession( this.CancelOperation = null; this.operation = null; } + 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(); } } } @@ -510,27 +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 @@ -538,36 +540,58 @@ 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); } } /// - /// Set resource URI of the operation + /// Set resource URI of the operation. /// public Uri ResourceUri { + get + { + return this.OperationOptions.ResourceUri; + } + set { DebugHelper.WriteLogEx("ResourceUri {0},", 0, value); - this.options.ResourceUri= value; - } - get - { - return this.options.ResourceUri; + this.OperationOptions.ResourceUri = value; } } @@ -579,12 +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; } } @@ -597,15 +622,15 @@ public bool EnablePromptUser set { DebugHelper.WriteLogEx("EnablePromptUser {0}", 0, value); - if(value) + if (value) { - this.options.PromptUser = this.PromptUser; + this.OperationOptions.PromptUser = this.PromptUser; } } } /// - /// Enable the pssemantics + /// Enable the pssemantics. /// private void EnablePSSemantics() { @@ -613,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; } /// @@ -629,7 +654,7 @@ private void EnablePSSemantics() /// public SwitchParameter KeyOnly { - set { this.options.KeysOnly = value.IsPresent; } + set { this.OperationOptions.KeysOnly = value.IsPresent; } } /// @@ -641,17 +666,17 @@ 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; } } } /// - /// Initialize the operation option + /// Initialize the operation option. /// private void InitOption(CimOperationOptions operOptions) { @@ -659,12 +684,13 @@ 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; this.EnablePSSemantics(); } @@ -683,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; } @@ -707,7 +733,7 @@ private void AddOperation(IObservable operation) } /// - /// Remove object from cache + /// Remove object from cache. /// /// private void RemoveOperation(IObservable operation) @@ -724,7 +750,8 @@ 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(); @@ -742,21 +769,22 @@ 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 { DebugHelper.WriteLog("Ignore action since OnNewCmdletAction is null.", 5); } + this.PostNewActionEvent(actionArgs); } @@ -773,13 +801,10 @@ 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); } @@ -795,14 +820,11 @@ 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); this.operationName = null; @@ -819,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) @@ -843,23 +865,25 @@ 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]}"); } } + string operationStartMessage = string.Format(CultureInfo.CurrentUICulture, - Strings.CimOperationStart, + CimCmdletStrings.CimOperationStart, operation, (parameters.Length == 0) ? "null" : parameters.ToString()); - WriteMessage((UInt32)CimWriteMessageChannel.Verbose, operationStartMessage); + WriteMessage((uint)CimWriteMessageChannel.Verbose, operationStartMessage); } /// @@ -872,9 +896,9 @@ internal void WriteOperationCompleteMessage(string operation) { DebugHelper.WriteLogEx(); string operationCompleteMessage = string.Format(CultureInfo.CurrentUICulture, - Strings.CimOperationCompleted, + CimCmdletStrings.CimOperationCompleted, operation); - WriteMessage((UInt32)CimWriteMessageChannel.Verbose, operationCompleteMessage); + WriteMessage((uint)CimWriteMessageChannel.Verbose, operationCompleteMessage); } /// @@ -890,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, @@ -925,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(); } @@ -937,7 +961,7 @@ public CimResponseType WriteError(CimInstance instance) } /// - /// PromptUser callback + /// PromptUser callback. /// /// /// @@ -947,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(); } @@ -963,11 +987,11 @@ public CimResponseType PromptUser(string message, CimPromptType prompt) /// /// - /// Handle async event triggered by + /// Handle async event triggered by /// /// - /// object triggered the event - /// async result event argument + /// Object triggered the event. + /// Async result event argument. internal void ResultEventHandler( object observer, AsyncResultEventArgsBase resultArgs) @@ -982,18 +1006,21 @@ internal void ResultEventHandler( AsyncResultCompleteEventArgs args = resultArgs as AsyncResultCompleteEventArgs; this.FireOperationDeletedEvent(args.observable, true); } + break; case AsyncResultType.Exception: { 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); } + this.FireOperationDeletedEvent(args.observable, false); } + break; case AsyncResultType.Result: { @@ -1004,6 +1031,7 @@ internal void ResultEventHandler( { AddShowComputerNameMarker(resultObject); } + if (this.ObjectPreProcess != null) { resultObject = this.ObjectPreProcess.Process(resultObject); @@ -1011,9 +1039,10 @@ 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); } + break; default: break; @@ -1034,22 +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)) @@ -1057,9 +1088,10 @@ private object PostProcessCimInstance(object resultObject) string serializedForm = PSSerializer.Serialize(resultObject as CimInstance, depth: 1); object deserializedObject = PSSerializer.Deserialize(serializedForm); object returnObject = (deserializedObject is PSObject) ? (deserializedObject as PSObject).BaseObject : deserializedObject; - DebugHelper.WriteLogEx("Deserialized Object is {0}, type {1}", 1, returnObject, returnObject.GetType()); + DebugHelper.WriteLogEx("Deserialized object is {0}, type {1}", 1, returnObject, returnObject.GetType()); return returnObject; } + return resultObject; } #endif @@ -1077,20 +1109,20 @@ 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.operationName = Strings.CimOperationNameCreateInstance; + 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)); } /// - /// delete a cim instance asynchronously + /// Delete a cim instance asynchronously. /// /// /// @@ -1099,38 +1131,38 @@ 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.operationName = Strings.CimOperationNameDeleteInstance; + 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)); } /// - /// Get cim instance asynchronously + /// Get cim instance asynchronously. /// /// /// 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.operationName = Strings.CimOperationNameGetInstance; + 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)); } /// - /// Modify cim instance asynchronously + /// Modify cim instance asynchronously. /// /// /// @@ -1139,19 +1171,19 @@ 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.operationName = Strings.CimOperationNameModifyInstance; + 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)); } /// /// Enumerate cim instance associated with the - /// given instance asynchronously + /// given instance asynchronously. /// /// /// @@ -1170,8 +1202,8 @@ 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.operationName = Strings.CimOperationNameEnumerateAssociatedInstances; + this.TargetCimInstance = sourceInstance; + this.operationName = CimCmdletStrings.CimOperationNameEnumerateAssociatedInstances; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"sourceInstance", sourceInstance); @@ -1180,27 +1212,27 @@ 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)); } /// - /// Enumerate cim instance asynchronously + /// Enumerate cim instance asynchronously. /// /// /// 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.operationName = Strings.CimOperationNameEnumerateInstances; + 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)); } @@ -1236,21 +1268,21 @@ 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.operationName = Strings.CimOperationNameQueryInstances; + 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); } /// - /// Enumerate cim class asynchronously + /// Enumerate cim class asynchronously. /// /// /// @@ -1258,36 +1290,36 @@ public void EnumerateClassesAsync(string namespaceName) { DebugHelper.WriteLogEx("namespace {0}", 0, namespaceName); this.CheckAvailability(); - this.targetCimInstance = null; - this.operationName = Strings.CimOperationNameEnumerateClasses; + 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); } /// - /// Enumerate cim class asynchronously + /// Enumerate cim class asynchronously. /// /// /// public void EnumerateClassesAsync(string namespaceName, string className) { this.CheckAvailability(); - this.targetCimInstance = null; - this.operationName = Strings.CimOperationNameEnumerateClasses; + 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)); } /// - /// Get cim class asynchronously + /// Get cim class asynchronously. /// /// /// @@ -1295,19 +1327,19 @@ public void GetClassAsync(string namespaceName, string className) { DebugHelper.WriteLogEx("namespace = {0}, className = {1}", 0, namespaceName, className); this.CheckAvailability(); - this.targetCimInstance = null; - this.operationName = Strings.CimOperationNameGetClass; + 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)); } /// - /// Invoke method of a given cim instance asynchronously + /// Invoke method of a given cim instance asynchronously. /// /// /// @@ -1320,21 +1352,21 @@ 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.operationName = Strings.CimOperationNameInvokeMethod; + 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)); } /// - /// Invoke static method of a given class asynchronously + /// Invoke static method of a given class asynchronously. /// /// /// @@ -1346,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.operationName = Strings.CimOperationNameInvokeMethod; + 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)); } @@ -1375,16 +1407,16 @@ public void SubscribeAsync( { DebugHelper.WriteLogEx("QueryDialect = '{0}'; queryExpression = '{1}'", 0, queryDialect, queryExpression); this.CheckAvailability(); - this.targetCimInstance = null; - this.operationName = Strings.CimOperationNameSubscribeIndication; + this.TargetCimInstance = null; + this.operationName = CimCmdletStrings.CimOperationNameSubscribeIndication; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"queryDialect", queryDialect); 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); } @@ -1397,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); } @@ -1407,7 +1439,7 @@ public void TestConnectionAsync() #region pre action APIs /// - /// Called before new action event + /// Called before new action event. /// /// protected virtual bool PreNewActionEvent(CmdletActionEventArgs args) @@ -1415,7 +1447,7 @@ protected virtual bool PreNewActionEvent(CmdletActionEventArgs args) return true; } /// - /// Called before operation delete event + /// Called before operation delete event. /// /// protected virtual void PreOperationDeleteEvent(OperationEventArgs args) @@ -1426,21 +1458,21 @@ protected virtual void PreOperationDeleteEvent(OperationEventArgs args) #region post action APIs /// - /// Called after new action event + /// Called after new action event. /// /// protected virtual void PostNewActionEvent(CmdletActionEventArgs args) { } /// - /// Called after operation create event + /// Called after operation create event. /// /// protected virtual void PostOperationCreateEvent(OperationEventArgs args) { } /// - /// Called after operation delete event + /// Called after operation delete event. /// /// protected virtual void PostOperationDeleteEvent(OperationEventArgs args) @@ -1461,41 +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 + /// current operation, it could be null. /// - internal CimInstance TargetCimInstance - { - get - { - return this.targetCimInstance; - } - } - private CimInstance targetCimInstance = null; + internal CimInstance TargetCimInstance { get; private set; } - /// - /// Flag controls whether session object should be closed or not. - /// - private bool isTemporaryCimSession; - internal bool IsTemporaryCimSession - { - get - { - return isTemporaryCimSession; - } - } + internal bool IsTemporaryCimSession { get; private set; } /// /// The CimOperationOptions object, which specifies the options @@ -1505,14 +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. @@ -1523,38 +1524,38 @@ private bool Completed } /// - /// lock object used to lock - /// operation & cancelOperation members + /// Lock object used to lock + /// operation & cancelOperation members. /// - private readonly object stateLock = new object(); + private readonly object stateLock = new(); /// - /// the operation issued by cimSession + /// The operation issued by cimSession. /// private IObservable operation; /// - /// the current operation name + /// The current operation name. /// private string operationName; /// - /// the current operation parameters + /// The current operation parameters. /// - private Hashtable operationParameters = new Hashtable(); + private readonly Hashtable operationParameters = new(); /// - /// handler used to cancel operation + /// Handler used to cancel operation. /// private IDisposable _cancelOperation; /// - /// cancelOperation disposed flag + /// CancelOperation disposed flag. /// private int _cancelOperationDisposed = 0; /// - /// Dispose the cancel operation + /// Dispose the cancel operation. /// private void DisposeCancelOperation() { @@ -1571,86 +1572,58 @@ private void DisposeCancelOperation() } /// - /// Set the cancel operation + /// Set the cancel operation. /// 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 + /// 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 + /// 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 + /// Invocation context object. /// private InvocationContext invocationContextObject; /// - /// a preprocess object to pre-processing the result object, + /// 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 #region IDisposable /// - /// IDisposable interface + /// IDisposable interface. /// private int _disposed; @@ -1686,11 +1659,12 @@ 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(); } } @@ -1711,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 @@ -1752,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; @@ -1776,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); @@ -1797,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); @@ -1818,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); @@ -1838,15 +1812,15 @@ protected void ConsumeCimSubscriptionResultAsync( /// protected void ConsumeCimInvokeMethodResultAsync( IObservable asyncResult, - String className, - String methodName, + string className, + string methodName, CimResultContext cimResultContext) { - CimMethodResultObserver observer = new CimMethodResultObserver(this.session, asyncResult, cimResultContext) - { - ClassName = className, - MethodName = methodName - }; + CimMethodResultObserver observer = new(this.CimSession, asyncResult, cimResultContext) + { + ClassName = className, + MethodName = methodName + }; observer.OnNewResult += this.ResultEventHandler; this.operationID = Interlocked.Increment(ref gOperationCounter); @@ -1869,10 +1843,11 @@ private void CheckAvailability() { if (!this.Completed) { - throw new InvalidOperationException(Strings.OperationInProgress); + throw new InvalidOperationException(CimCmdletStrings.OperationInProgress); } } - DebugHelper.WriteLog("KeyOnly {0},", 1, this.options.KeysOnly); + + DebugHelper.WriteLog("KeyOnly {0},", 1, this.OperationOptions.KeysOnly); } /// @@ -1882,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()); } } @@ -1903,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 @@ -1924,7 +1899,7 @@ private CimSession CreateCimSessionByComputerName(string computerName) /// /// internal static CimSessionOptions CreateCimSessionOption(string computerName, - UInt32 timeout, CimCredential credential) + uint timeout, CimCredential credential) { DebugHelper.WriteLogEx(); @@ -1939,21 +1914,24 @@ internal static CimSessionOptions CreateCimSessionOption(string computerName, DebugHelper.WriteLog("<<<<<<<<<< Use protocol WSMAN {0}", 1, computerName); option = new WSManSessionOptions(); } + if (timeout != 0) { option.Timeout = TimeSpan.FromSeconds((double)timeout); } + if (credential != null) { option.AddDestinationCredentials(credential); } + DebugHelper.WriteLogEx("returned option :{0}.", 1, option); return option; } #endregion - }//End Class + } #region class CimSessionProxyTestConnection /// @@ -1966,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) @@ -1982,7 +1963,7 @@ public CimSessionProxyTestConnection(string computerName, CimSessionOptions sess #region pre action APIs /// - /// Called after operation delete event + /// Called after operation delete event. /// /// protected override void PreOperationDeleteEvent(OperationEventArgs args) @@ -1992,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); } } @@ -2015,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) @@ -2025,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) @@ -2040,22 +2027,21 @@ public CimSessionProxyGetCimClass(CimSession session) #region pre action APIs /// - /// Called before new action event + /// Called before new action event. /// /// 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; } @@ -2074,12 +2060,13 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) return false; } } + if (context.PropertyName != null) { - pattern = new WildcardPattern(context.PropertyName, WildcardOptions.IgnoreCase); bool match = false; if (cimClass.CimClassProperties != null) { + pattern = new WildcardPattern(context.PropertyName, WildcardOptions.IgnoreCase); foreach (CimPropertyDeclaration decl in cimClass.CimClassProperties) { DebugHelper.WriteLog("--- property name : {0}", 1, decl.Name); @@ -2097,12 +2084,13 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) return match; } } + if (context.MethodName != null) { - pattern = new WildcardPattern(context.MethodName, WildcardOptions.IgnoreCase); bool match = false; if (cimClass.CimClassMethods != null) { + pattern = new WildcardPattern(context.MethodName, WildcardOptions.IgnoreCase); foreach (CimMethodDeclaration decl in cimClass.CimClassMethods) { DebugHelper.WriteLog("--- method name : {0}", 1, decl.Name); @@ -2120,12 +2108,13 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) return match; } } + if (context.QualifierName != null) { - pattern = new WildcardPattern(context.QualifierName, WildcardOptions.IgnoreCase); bool match = false; if (cimClass.CimClassQualifiers != null) { + pattern = new WildcardPattern(context.QualifierName, WildcardOptions.IgnoreCase); foreach (CimQualifier qualifier in cimClass.CimClassQualifiers) { DebugHelper.WriteLog("--- qualifier name : {0}", 1, qualifier.Name); @@ -2143,6 +2132,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) return match; } } + DebugHelper.WriteLog("CimClass '{0}' is qualified.", 1, cimClass.CimSystemProperties.ClassName); return true; } @@ -2163,49 +2153,54 @@ 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 #region pre action APIs /// - /// Called before new action event + /// Called before new action event. /// /// 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; } @@ -2218,14 +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 } @@ -2243,11 +2231,14 @@ 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 + /// PassThru, true means output the modified instance; otherwise does not output. public CimSessionProxySetCimInstance(CimSessionProxy originalProxy, bool passThru) : base(originalProxy) { @@ -2255,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. - /// + /// /// /// /// @@ -2270,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) @@ -2285,7 +2282,7 @@ public CimSessionProxySetCimInstance(CimSession session, bool passThru) #region pre action APIs /// - /// Called before new action event + /// Called before new action event. /// /// protected override bool PreNewActionEvent(CmdletActionEventArgs args) @@ -2305,12 +2302,12 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) #region private members /// - /// Ture indicates need to output the modified result + /// Ture indicates need to output the modified result. /// - private bool passThru = false; + private readonly bool passThru = false; #endregion } #endregion -}//End namespace +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs index eadc0bc8e68..91cf332bde9 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs @@ -1,16 +1,12 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System.Collections; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Management.Automation; - #endregion @@ -23,9 +19,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimSetCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -37,47 +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() @@ -102,12 +73,12 @@ public CimSetCimInstance() /// Base on parametersetName to set ciminstances /// /// - /// object + /// object. public void SetCimInstance(SetCimInstanceCommand cmdlet) { IEnumerable computerNames = ConstValue.GetComputerNames( GetComputerName(cmdlet)); - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: @@ -116,6 +87,7 @@ public void SetCimInstance(SetCimInstanceCommand cmdlet) // create CimSessionProxySetCimInstance object internally proxys.Add(CreateSessionProxy(computerName, cmdlet.CimInstance, cmdlet, cmdlet.PassThru)); } + break; case CimBaseCommand.CimInstanceSessionSet: foreach (CimSession session in GetCimSession(cmdlet)) @@ -123,10 +95,12 @@ public void SetCimInstance(SetCimInstanceCommand cmdlet) // create CimSessionProxySetCimInstance object internally proxys.Add(CreateSessionProxy(session, cmdlet, cmdlet.PassThru)); } + break; default: break; } + switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: @@ -151,8 +125,10 @@ public void SetCimInstance(SetCimInstanceCommand cmdlet) return; } } + proxy.ModifyInstanceAsync(nameSpace, instance); } + break; case CimBaseCommand.QueryComputerSet: case CimBaseCommand.QuerySessionSet: @@ -184,6 +160,7 @@ public void SetCimInstance(CimInstance cimInstance, CimSetCimInstanceContext con cmdlet.ThrowTerminatingError(exception, action); return; } + CimSessionProxy proxy = CreateCimSessionProxy(context.Proxy, context.PassThru); proxy.ModifyInstanceAsync(cimInstance.CimSystemProperties.Namespace, cimInstance); } @@ -208,6 +185,7 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re // simply ignore if empty properties was provided return true; } + IDictionaryEnumerator enumerator = properties.GetEnumerator(); while (enumerator.MoveNext()) { @@ -224,8 +202,8 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re if ((property.Flags & CimFlags.ReadOnly) == CimFlags.ReadOnly) { // can not modify ReadOnly property - exception = new CimException(String.Format(CultureInfo.CurrentUICulture, - Strings.CouldNotModifyReadonlyProperty, key, cimInstance)); + exception = new CimException(string.Format(CultureInfo.CurrentUICulture, + CimCmdletStrings.CouldNotModifyReadonlyProperty, key, cimInstance)); return false; } // allow modify the key property value as long as it is not readonly, @@ -236,7 +214,7 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re else // For dynamic instance, it is valid to add a new property { CimProperty newProperty; - if( value == null ) + if (value == null) { newProperty = CimProperty.Create( key, @@ -264,6 +242,7 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re CimFlags.Property); } } + try { cimInstance.CimInstanceProperties.Add(newProperty); @@ -272,8 +251,8 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re { if (e.NativeErrorCode == NativeErrorCode.Failed) { - string errorMessage = String.Format(CultureInfo.CurrentUICulture, - Strings.UnableToAddPropertyToInstance, + string errorMessage = string.Format(CultureInfo.CurrentUICulture, + CimCmdletStrings.UnableToAddPropertyToInstance, newProperty.Name, cimInstance); exception = new CimException(errorMessage, e); @@ -282,8 +261,10 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re { exception = e; } + return false; } + DebugHelper.WriteLog("Add non-key property name '{0}' with value '{1}'.", 3, key, value); } } @@ -294,6 +275,7 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re return false; } } + return true; } @@ -301,9 +283,9 @@ private bool SetProperty(IDictionary properties, ref CimInstance cimInstance, re #region const strings /// - /// action + /// Action. /// private const string action = @"Set-CimInstance"; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs index 9b2396c7d29..f1ebe911603 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -31,8 +29,8 @@ internal sealed class ErrorToErrorRecord /// /// /// - /// the context starting the operation, which generated the error - /// the CimResultContext used to provide ErrorSource, etc. info. + /// The context starting the operation, which generated the error. + /// The CimResultContext used to provide ErrorSource, etc. info. /// internal static ErrorRecord ErrorRecordFromAnyException( InvocationContext context, @@ -41,17 +39,15 @@ 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, + exception: inner, errorId: "CimCmdlet_" + containsErrorRecord.ErrorRecord.FullyQualifiedErrorId, errorCategory: containsErrorRecord.ErrorRecord.CategoryInfo.Category, cimResultContext: cimResultContext); @@ -59,7 +55,7 @@ internal static ErrorRecord ErrorRecordFromAnyException( else { return InitializeErrorRecord(context, - exception :inner, + exception: inner, errorId: "CimCmdlet_" + inner.GetType().Name, errorCategory: ErrorCategory.NotSpecified, cimResultContext: cimResultContext); @@ -72,7 +68,7 @@ internal static ErrorRecord ErrorRecordFromAnyException( /// /// /// - /// the CimResultContext used to provide ErrorSource, etc. info. + /// The CimResultContext used to provide ErrorSource, etc. info. /// internal static ErrorRecord CreateFromCimException( InvocationContext context, @@ -91,7 +87,7 @@ internal static ErrorRecord CreateFromCimException( /// /// /// - /// the CimResultContext used to provide ErrorSource, etc. info. + /// The CimResultContext used to provide ErrorSource, etc. info. /// internal static ErrorRecord InitializeErrorRecord( InvocationContext context, @@ -113,7 +109,7 @@ internal static ErrorRecord InitializeErrorRecord( /// /// /// - /// the CimResultContext used to provide ErrorSource, etc. info. + /// The CimResultContext used to provide ErrorSource, etc. info. /// internal static ErrorRecord InitializeErrorRecord( InvocationContext context, @@ -131,6 +127,7 @@ internal static ErrorRecord InitializeErrorRecord( { errorRecord.CategoryInfo.TargetName = cimException.ErrorSource; } + return errorRecord; } @@ -141,7 +138,7 @@ internal static ErrorRecord InitializeErrorRecord( /// /// /// - /// the CimResultContext used to provide ErrorSource, etc. info. + /// The CimResultContext used to provide ErrorSource, etc. info. /// internal static ErrorRecord InitializeErrorRecordCore( InvocationContext context, @@ -155,6 +152,7 @@ internal static ErrorRecord InitializeErrorRecordCore( { theTargetObject = cimResultContext.ErrorSource; } + if (theTargetObject == null) { if (context != null) @@ -165,7 +163,8 @@ internal static ErrorRecord InitializeErrorRecordCore( } } } - ErrorRecord coreErrorRecord = new ErrorRecord( + + ErrorRecord coreErrorRecord = new( exception: exception, errorId: errorId, errorCategory: errorCategory, @@ -176,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); @@ -185,8 +184,6 @@ internal static ErrorRecord InitializeErrorRecordCore( originInfo); DebugHelper.WriteLogEx("Created RemotingErrorRecord.", 0); - // errorRecord.SetInvocationInfo(jobContext.CmdletInvocationInfo); - // errorRecord.PreserveInvocationInfoOnce = true; return errorRecord; } @@ -318,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; } /// @@ -349,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; @@ -376,60 +375,20 @@ 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 - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs index f0259d744b2..f4b244b97f9 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -22,38 +20,26 @@ internal sealed class CimWriteMessage : CimBaseAction #region members /// - /// channel id + /// 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; } /// @@ -66,20 +52,20 @@ 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; } } - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs index 23b34f60a0d..b99407ccc81 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -20,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 @@ -42,22 +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; - if (String.IsNullOrEmpty(theStatusDescription)) + this.Activity = theActivity; + this.ActivityID = theActivityID; + this.CurrentOperation = theCurrentOperation; + if (string.IsNullOrEmpty(theStatusDescription)) { - this.statusDescription = Strings.DefaultStatusDescription; + this.StatusDescription = CimCmdletStrings.DefaultStatusDescription; } else { - this.statusDescription = theStatusDescription; + this.StatusDescription = theStatusDescription; } - this.percentageCompleted = thePercentageCompleted; - this.secondsRemaining = theSecondsRemaining; + + this.PercentageCompleted = thePercentageCompleted; + this.SecondsRemaining = theSecondsRemaining; } /// @@ -71,85 +70,55 @@ 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 - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs index 1a87b451af3..a4dcdfaa5c4 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -17,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; } @@ -34,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 + /// Result object. /// - internal object Result - { - get - { - return result; - } - } - private object result; + internal object Result { get; } #endregion - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs index f5d84647cdd..bd1a2751622 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs @@ -1,12 +1,9 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives -using System.Management.Automation; using System; -using System.Globalization; +using System.Management.Automation; #endregion @@ -42,58 +39,73 @@ public virtual bool ShouldContinue(string query, string caption) { return cmdlet.ShouldContinue(query, caption); } + public virtual bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) { return cmdlet.ShouldContinue(query, caption, ref yesToAll, ref noToAll); } + public virtual bool ShouldProcess(string target) { return cmdlet.ShouldProcess(target); } + public virtual bool ShouldProcess(string target, string action) { return cmdlet.ShouldProcess(target, action); } + public virtual bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) { return cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption); } + public virtual bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason) { return cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption, out shouldProcessReason); } + + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public virtual void ThrowTerminatingError(ErrorRecord errorRecord) { cmdlet.ThrowTerminatingError(errorRecord); } + public virtual void WriteCommandDetail(string text) { cmdlet.WriteCommandDetail(text); } + public virtual void WriteDebug(string text) { cmdlet.WriteDebug(text); } + public virtual void WriteError(ErrorRecord errorRecord) { cmdlet.WriteError(errorRecord); } + public virtual void WriteObject(object sendToPipeline, XOperationContextBase context) { cmdlet.WriteObject(sendToPipeline); } + public virtual void WriteObject(object sendToPipeline, bool enumerateCollection, XOperationContextBase context) { cmdlet.WriteObject(sendToPipeline, enumerateCollection); } + public virtual void WriteProgress(ProgressRecord progressRecord) { cmdlet.WriteProgress(progressRecord); } + public virtual void WriteVerbose(string text) { cmdlet.WriteVerbose(text); } + public virtual void WriteWarning(string text) { cmdlet.WriteWarning(text); @@ -104,22 +116,23 @@ 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) { ValidationHelper.ValidateNoNullArgument(cmdlet, "cmdlet"); this.cmdlet = cmdlet; } - }//End Class + } #region Class CmdletOperationRemoveCimInstance @@ -132,7 +145,7 @@ public CmdletOperationBase(Cmdlet cmdlet) internal class CmdletOperationRemoveCimInstance : CmdletOperationBase { /// - /// Constructor method + /// Initializes a new instance of the class. /// /// public CmdletOperationRemoveCimInstance(Cmdlet cmdlet, @@ -176,12 +189,12 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimRemoveCimInstance removeCimInstance; + private readonly CimRemoveCimInstance removeCimInstance; private const string cimRemoveCimInstanceParameterName = @"cimRemoveCimInstance"; #endregion - }//End Class + } #endregion @@ -196,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, @@ -219,11 +232,10 @@ 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.Compare(setContext.ParameterSetName, CimBaseCommand.QueryComputerSet, StringComparison.OrdinalIgnoreCase) == 0) || - (String.Compare(setContext.ParameterSetName, CimBaseCommand.QuerySessionSet, StringComparison.OrdinalIgnoreCase) == 0)) + if (string.Equals(setContext.ParameterSetName, CimBaseCommand.QueryComputerSet, StringComparison.OrdinalIgnoreCase) || + string.Equals(setContext.ParameterSetName, CimBaseCommand.QuerySessionSet, StringComparison.OrdinalIgnoreCase)) { this.setCimInstance.SetCimInstance(sendToPipeline as CimInstance, setContext, this); return; @@ -238,6 +250,7 @@ public override void WriteObject(object sendToPipeline, XOperationContextBase co DebugHelper.WriteLog("Assert. CimSetCimInstance::SetCimInstance has NULL CimSetCimInstanceContext", 4); } } + base.WriteObject(sendToPipeline, context); } @@ -255,12 +268,12 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimSetCimInstance setCimInstance; + private readonly CimSetCimInstance setCimInstance; private const string theCimSetCimInstanceParameterName = @"theCimSetCimInstance"; #endregion - }//End Class + } #endregion #region Class CmdletOperationInvokeCimMethod @@ -273,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, @@ -318,12 +331,12 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimInvokeCimMethod cimInvokeCimMethod; + private readonly CimInvokeCimMethod cimInvokeCimMethod; private const string theCimInvokeCimMethodParameterName = @"theCimInvokeCimMethod"; #endregion - }//End Class + } #endregion @@ -338,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, @@ -379,12 +392,12 @@ public override void WriteObject(object sendToPipeline, XOperationContextBase co #region private methods - private CimNewSession cimNewSession; + private readonly CimNewSession cimNewSession; private const string theCimNewSessionParameterName = @"theCimNewSession"; #endregion - }//End Class + } #endregion -}//End namespace +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs index 87f5466f121..6c4d0c94d2b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs @@ -1,18 +1,15 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using System.Collections.Generic; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -25,6 +22,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// Association parameter. /// /// + [Alias("gcai")] [Cmdlet(VerbsCommon.Get, GetCimAssociatedInstanceCommand.Noun, DefaultParameterSetName = CimBaseCommand.ComputerSetName, @@ -35,7 +33,7 @@ public class GetCimAssociatedInstanceCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public GetCimAssociatedInstanceCommand() : base(parameters, parameterSets) @@ -55,12 +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,37 +61,7 @@ public String Association /// the given instance. /// [Parameter] - public String ResultClassName - { - get { return resultClassName; } - set { resultClassName = value; } - } - private String resultClassName; - - /// - /// The following is the definition of the input parameter "AssociatorRole". - /// Specifies the name of the association role of the instances to be retrieved. - /// - //[Parameter(ValueFromPipelineByPropertyName = true)] - //public String AssociatorRole - //{ - // get { return associatorRole; } - // set { associatorRole = value; } - //} - //private String associatorRole; - - /// - /// The following is the definition of the input parameter "SourceRole". - /// Specifies the name of the association role of the source instance where the - /// association traversal should begin. - /// - //[Parameter(ValueFromPipelineByPropertyName = true)] - //public String SourceRole - //{ - // get { return sourcerole; } - // set { sourcerole = value; } - //} - //private String sourcerole; + public string ResultClassName { get; set; } /// /// @@ -113,22 +76,22 @@ public String ResultClassName [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } + set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } /// - /// Property for internal usage purpose + /// 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". @@ -136,12 +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". @@ -152,12 +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; } /// /// @@ -168,13 +121,18 @@ public UInt32 OperationTimeoutSec [Parameter] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -192,16 +150,21 @@ public Uri ResourceUri [Parameter( ParameterSetName = ComputerSetName)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } + set { computerName = value; base.SetParameter(value, nameComputerName); } } - private String[] computerName; + + private string[] computerName; /// /// The following is the definition of the input parameter "CimSession". @@ -214,13 +177,18 @@ public String[] ComputerName [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Microsoft.Management.Infrastructure.CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private Microsoft.Management.Infrastructure.CimSession[] cimSession; /// @@ -231,12 +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 @@ -249,7 +212,7 @@ protected override void BeginProcessing() { this.CmdletOperation = new CmdletOperationBase(this); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -257,14 +220,11 @@ protected override void BeginProcessing() protected override void ProcessRecord() { base.CheckParameterSet(); - CimGetAssociatedInstance operation = this.GetOperationAgent(); - if (operation == null) - { - operation = this.CreateOperationAgent(); - } + CimGetAssociatedInstance operation = this.GetOperationAgent() ?? this.CreateOperationAgent(); + operation.GetCimAssociatedInstance(this); operation.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -272,9 +232,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetAssociatedInstance operation = this.GetOperationAgent(); - if (operation != null) - operation.ProcessRemainActions(this.CmdletOperation); - }//End EndProcessing() + operation?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -286,7 +245,7 @@ protected override void EndProcessing() /// used to delegate all Get-CimAssociatedInstance operations. /// /// - CimGetAssociatedInstance GetOperationAgent() + private CimGetAssociatedInstance GetOperationAgent() { return this.AsyncOperation as CimGetAssociatedInstance; } @@ -298,7 +257,7 @@ CimGetAssociatedInstance GetOperationAgent() /// /// /// - CimGetAssociatedInstance CreateOperationAgent() + private CimGetAssociatedInstance CreateOperationAgent() { this.AsyncOperation = new CimGetAssociatedInstance(); return GetOperationAgent(); @@ -309,7 +268,7 @@ CimGetAssociatedInstance CreateOperationAgent() #region internal const strings /// - /// Noun of current cmdlet + /// Noun of current cmdlet. /// internal const string Noun = @"CimAssociatedInstance"; @@ -318,20 +277,16 @@ CimGetAssociatedInstance CreateOperationAgent() #region private members #region const string of parameter names - // internal const string nameAssociation = "Association"; internal const string nameCimInstance = "InputObject"; - // internal const string nameNamespace = "Namespace"; - // internal const string nameOperationTimeoutSec = "OperationTimeoutSec"; internal const string nameComputerName = "ComputerName"; internal const string nameCimSession = "CimSession"; internal const string nameResourceUri = "ResourceUri"; - // internal const string nameKeyOnly = "KeyOnly"; #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameComputerName, new HashSet { @@ -358,13 +313,13 @@ CimGetAssociatedInstance CreateOperationAgent() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.SessionSetName, new ParameterSetEntry(2, false) }, { CimBaseCommand.ComputerSetName, new ParameterSetEntry(1, true) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs index 58a8538e077..1bededc485f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -25,6 +23,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// Should the class remember what Session it came from? No. /// /// + [Alias("gcls")] [Cmdlet(VerbsCommon.Get, GetCimClassCommand.Noun, DefaultParameterSetName = ComputerSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227959")] [OutputType(typeof(CimClass))] public class GetCimClassCommand : CimBaseCommand @@ -32,7 +31,7 @@ public class GetCimClassCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public GetCimClassCommand() : base(parameters, parameterSets) @@ -44,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". @@ -55,12 +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; } /// /// @@ -76,12 +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". @@ -90,12 +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". @@ -108,13 +98,18 @@ public UInt32 OperationTimeoutSec [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession;} + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimSession; /// @@ -130,16 +125,21 @@ public CimSession[] CimSession ValueFromPipelineByPropertyName = true, ParameterSetName = ComputerSetName)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } + set { computerName = value; base.SetParameter(value, nameComputerName); } } - private String[] computerName; + + private string[] computerName; /// /// @@ -149,12 +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; } /// /// @@ -164,12 +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; } /// /// @@ -179,12 +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 @@ -197,7 +182,7 @@ protected override void BeginProcessing() { this.CmdletOperation = new CmdletOperationBase(this); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -205,14 +190,11 @@ protected override void BeginProcessing() protected override void ProcessRecord() { base.CheckParameterSet(); - CimGetCimClass cimGetCimClass = this.GetOperationAgent(); - if (cimGetCimClass == null) - { - cimGetCimClass = CreateOperationAgent(); - } + CimGetCimClass cimGetCimClass = this.GetOperationAgent() ?? CreateOperationAgent(); + cimGetCimClass.GetCimClass(this); cimGetCimClass.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -220,11 +202,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetCimClass cimGetCimClass = this.GetOperationAgent(); - if (cimGetCimClass != null) - { - cimGetCimClass.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimGetCimClass?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -236,9 +215,9 @@ protected override void EndProcessing() /// used to delegate all New-CimInstance operations. /// /// - CimGetCimClass GetOperationAgent() + private CimGetCimClass GetOperationAgent() { - return (this.AsyncOperation as CimGetCimClass); + return this.AsyncOperation as CimGetCimClass; } /// @@ -248,9 +227,9 @@ CimGetCimClass GetOperationAgent() /// /// /// - CimGetCimClass CreateOperationAgent() + private CimGetCimClass CreateOperationAgent() { - CimGetCimClass cimGetCimClass = new CimGetCimClass(); + CimGetCimClass cimGetCimClass = new(); this.AsyncOperation = cimGetCimClass; return cimGetCimClass; } @@ -260,7 +239,7 @@ CimGetCimClass CreateOperationAgent() #region internal const strings /// - /// Noun of current cmdlet + /// Noun of current cmdlet. /// internal const string Noun = @"CimClass"; @@ -274,9 +253,9 @@ CimGetCimClass CreateOperationAgent() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -292,13 +271,13 @@ CimGetCimClass CreateOperationAgent() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.SessionSetName, new ParameterSetEntry(1) }, { CimBaseCommand.ComputerSetName, new ParameterSetEntry(0, true) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs index a59208be6a8..65eeae8e450 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs @@ -1,19 +1,15 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; using System.Collections.Generic; -using System.Management.Automation; -using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -21,6 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// specified in the Property parameter, KeysOnly parameter or the Select clause /// of the Query parameter. /// + [Alias("gcim")] [Cmdlet(VerbsCommon.Get, "CimInstance", DefaultParameterSetName = CimBaseCommand.ClassNameComputerSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227961")] [OutputType(typeof(CimInstance))] public class GetCimInstanceCommand : CimBaseCommand @@ -28,7 +25,8 @@ public class GetCimInstanceCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. + /// Constructor. /// public GetCimInstanceCommand() : base(parameters, parameterSets) @@ -65,13 +63,18 @@ public GetCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimSession; /// @@ -88,16 +91,21 @@ public CimSession[] CimSession Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ClassNameComputerSet)] - public String ClassName + public string ClassName { - get { return className; } + get + { + return className; + } + set { this.className = value; base.SetParameter(value, nameClassName); } } - private String className; + + private string className; /// /// @@ -123,13 +131,18 @@ public String ClassName ParameterSetName = CimBaseCommand.QuerySessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -155,16 +168,21 @@ public Uri ResourceUri [Parameter( ParameterSetName = CimBaseCommand.CimInstanceComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } + set { computerName = value; base.SetParameter(value, nameComputerName); } } - private String[] computerName; + + private string[] computerName; /// /// @@ -179,13 +197,18 @@ public String[] ComputerName [Parameter(ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] public SwitchParameter KeyOnly { - get { return keyOnly; } + get + { + return keyOnly; + } + set { keyOnly = value; base.SetParameter(value, nameKeyOnly); } } + private SwitchParameter keyOnly; /// @@ -210,16 +233,21 @@ public SwitchParameter KeyOnly ParameterSetName = CimBaseCommand.QueryComputerSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QuerySessionSet)] - public String Namespace + public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } + set { nameSpace = value; base.SetParameter(value, nameNamespace); } } - private String nameSpace; + + private string nameSpace; /// /// @@ -232,12 +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". @@ -265,22 +288,22 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } + set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } /// - /// Property for internal usage purpose + /// 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". @@ -293,16 +316,21 @@ internal CimInstance CimInstance [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QuerySessionSet)] - public String Query + public string Query { - get { return query; } + get + { + return query; + } + set { query = value; base.SetParameter(value, nameQuery); } } - private String query; + + private string query; /// /// @@ -320,16 +348,21 @@ public String Query [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ClassNameComputerSet)] - public String QueryDialect + public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } + set { queryDialect = value; base.SetParameter(value, nameQueryDialect); } } - private String queryDialect; + + private string queryDialect; /// /// @@ -348,13 +381,18 @@ public String QueryDialect [Parameter(ParameterSetName = CimBaseCommand.QuerySessionSet)] public SwitchParameter Shallow { - get { return shallow; } + get + { + return shallow; + } + set { shallow = value; base.SetParameter(value, nameShallow); } } + private SwitchParameter shallow; /// @@ -371,16 +409,21 @@ public SwitchParameter Shallow ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] - public String Filter + public string Filter { - get { return filter; } + get + { + return filter; + } + set { filter = value; base.SetParameter(value, nameFilter); } } - private String filter; + + private string filter; /// /// @@ -398,23 +441,23 @@ public String Filter ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("SelectProperties")] - public String[] Property + public string[] Property { - get { return property; } + get + { + return SelectProperties; + } + set { - property = value; + SelectProperties = value; base.SetParameter(value, nameSelectProperties); } } /// - /// Property for internal usage + /// Property for internal usage. /// - internal String[] SelectProperties - { - get { return property; } - } - private String[] property; + internal string[] SelectProperties { get; private set; } #endregion @@ -427,7 +470,7 @@ protected override void BeginProcessing() { this.CmdletOperation = new CmdletOperationBase(this); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -436,14 +479,11 @@ protected override void ProcessRecord() { base.CheckParameterSet(); this.CheckArgument(); - CimGetInstance cimGetInstance = this.GetOperationAgent(); - if (cimGetInstance == null) - { - cimGetInstance = CreateOperationAgent(); - } + CimGetInstance cimGetInstance = this.GetOperationAgent() ?? CreateOperationAgent(); + cimGetInstance.GetCimInstance(this); cimGetInstance.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -451,11 +491,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimGetInstance cimGetInstance = this.GetOperationAgent(); - if (cimGetInstance != null) - { - cimGetInstance.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimGetInstance?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -468,9 +505,9 @@ protected override void EndProcessing() /// as enumerate instances, get instance, query instance. /// /// - CimGetInstance GetOperationAgent() + private CimGetInstance GetOperationAgent() { - return (this.AsyncOperation as CimGetInstance); + return this.AsyncOperation as CimGetInstance; } /// @@ -481,15 +518,15 @@ CimGetInstance GetOperationAgent() /// /// /// - CimGetInstance CreateOperationAgent() + private CimGetInstance CreateOperationAgent() { - CimGetInstance cimGetInstance = new CimGetInstance(); + CimGetInstance cimGetInstance = new(); this.AsyncOperation = cimGetInstance; return cimGetInstance; } /// - /// check argument value + /// Check argument value. /// private void CheckArgument() { @@ -499,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; @@ -526,9 +563,9 @@ private void CheckArgument() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -598,7 +635,6 @@ private void CheckArgument() new ParameterDefinitionEntry(CimBaseCommand.QueryComputerSet, false), new ParameterDefinitionEntry(CimBaseCommand.ClassNameSessionSet, false), new ParameterDefinitionEntry(CimBaseCommand.ClassNameComputerSet, false), - } }, { @@ -630,9 +666,9 @@ private void CheckArgument() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimInstanceComputerSet, new ParameterSetEntry(1) }, { CimBaseCommand.CimInstanceSessionSet, new ParameterSetEntry(2) }, @@ -644,5 +680,5 @@ private void CheckArgument() { CimBaseCommand.QuerySessionSet, new ParameterSetEntry(2) } }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs index 57e610ddcc6..3289b6c86c0 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -10,14 +8,13 @@ using System.Management.Automation; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// /// The command returns zero, one or more CimSession objects that represent /// connections with remote computers established from the current PS Session. /// - + [Alias("gcms")] [Cmdlet(VerbsCommon.Get, "CimSession", DefaultParameterSetName = ComputerNameSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227966")] [OutputType(typeof(CimSession))] public sealed class GetCimSessionCommand : CimBaseCommand @@ -25,7 +22,7 @@ public sealed class GetCimSessionCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public GetCimSessionCommand() : base(parameters, parameterSets) @@ -59,16 +56,21 @@ public GetCimSessionCommand() ValueFromPipelineByPropertyName = true, ParameterSetName = ComputerNameSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computername;} + get + { + return computername; + } + set { computername = value; base.SetParameter(value, nameComputerName); } } - private String[] computername; + + private string[] computername; /// /// The following is the definition of the input parameter "Id". @@ -79,20 +81,25 @@ 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 { id = value; base.SetParameter(value, nameId); } } - private UInt32[] id; + + private uint[] id; /// /// The following is the definition of the input parameter "InstanceID". - /// Specifies one or Session Instance IDs + /// Specifies one or Session Instance IDs. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, @@ -100,13 +107,18 @@ public UInt32[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Guid[] InstanceId { - get { return instanceid;} + get + { + return instanceid; + } + set { instanceid = value; base.SetParameter(value, nameInstanceId); } } + private Guid[] instanceid; /// @@ -118,16 +130,21 @@ public Guid[] InstanceId ValueFromPipelineByPropertyName = true, ParameterSetName = NameSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Name + public string[] Name { - get { return name;} + get + { + return name; + } + set { name = value; base.SetParameter(value, nameName); } } - private String[] name; + + private string[] name; #endregion @@ -139,7 +156,7 @@ protected override void BeginProcessing() { cimGetSession = new CimGetSession(); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -148,13 +165,13 @@ protected override void ProcessRecord() { base.CheckParameterSet(); cimGetSession.GetCimSession(this); - }//End ProcessRecord() + } #endregion #region private members /// - /// object used to search CimSession from cache + /// object used to search CimSession from cache. /// private CimGetSession cimGetSession; @@ -166,9 +183,9 @@ protected override void ProcessRecord() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameComputerName, new HashSet { @@ -193,9 +210,9 @@ protected override void ProcessRecord() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ComputerNameSet, new ParameterSetEntry(0, true) }, { CimBaseCommand.SessionIdSet, new ParameterSetEntry(1) }, @@ -203,5 +220,5 @@ protected override void ProcessRecord() { CimBaseCommand.NameSet, new ParameterSetEntry(1) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs index f67ee6cb5ba..e3bc6f293b6 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -11,14 +9,13 @@ using System.Management.Automation; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// /// This cmdlet enables the user to invoke a static method on a CIM class using /// the arguments passed as a list of name value pair dictionary. /// - + [Alias("icim")] [Cmdlet( "Invoke", "CimMethod", @@ -30,7 +27,7 @@ public class InvokeCimMethodCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public InvokeCimMethodCommand() : base(parameters, parameterSets) @@ -55,16 +52,21 @@ public InvokeCimMethodCommand() ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ClassNameSessionSet)] [Alias("Class")] - public String ClassName + public string ClassName { - get { return className; } + get + { + return className; + } + set { className = value; base.SetParameter(value, nameClassName); } } - private String className; + + private string className; /// /// @@ -84,13 +86,18 @@ public String ClassName ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -107,13 +114,18 @@ public Uri ResourceUri ParameterSetName = CimClassSessionSet)] public CimClass CimClass { - get { return cimClass; } + get + { + return cimClass; + } + set { cimClass = value; base.SetParameter(value, nameCimClass); } } + private CimClass cimClass; /// @@ -128,13 +140,18 @@ public CimClass CimClass ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Query { - get { return query; } + get + { + return query; + } + set { query = value; base.SetParameter(value, nameQuery); } } + private string query; /// @@ -148,21 +165,26 @@ public string Query ParameterSetName = CimBaseCommand.QueryComputerSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QuerySessionSet)] - public String QueryDialect + public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } + set { queryDialect = value; base.SetParameter(value, nameQueryDialect); } } - private String queryDialect; + + private string queryDialect; /// /// The following is the definition of the input parameter "InputObject". /// Takes a CimInstance object retrieved by a Get-CimInstance call. - /// Invoke the method against the given instance + /// Invoke the method against the given instance. /// [Parameter(Mandatory = true, Position = 0, @@ -175,22 +197,22 @@ public String QueryDialect [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } + set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } /// - /// Property for internal usage purpose + /// 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". @@ -218,9 +240,13 @@ internal CimInstance CimInstance ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } + set { DebugHelper.WriteLogEx(); @@ -228,7 +254,8 @@ public String[] ComputerName base.SetParameter(value, nameComputerName); } } - private String[] computerName; + + private string[] computerName; /// /// @@ -258,13 +285,18 @@ public String[] ComputerName [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimSession; /// @@ -274,12 +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". @@ -289,16 +316,21 @@ public IDictionary Arguments Position = 2, ValueFromPipelineByPropertyName = true)] [Alias("Name")] - public String MethodName + public string MethodName { - get { return methodName; } + get + { + return methodName; + } + set { methodName = value; base.SetParameter(value, nameMethodName); } } - private String methodName; + + private string methodName; /// /// The following is the definition of the input parameter "Namespace". @@ -318,16 +350,21 @@ public String MethodName [Parameter( ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] - public String Namespace + public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } + set { nameSpace = value; base.SetParameter(value, nameNamespace); } } - private String nameSpace; + + private string nameSpace; /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -336,12 +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 @@ -352,14 +384,11 @@ public UInt32 OperationTimeoutSec /// protected override void BeginProcessing() { - CimInvokeCimMethod cimInvokeMethod = this.GetOperationAgent(); - if (cimInvokeMethod == null) - { - cimInvokeMethod = CreateOperationAgent(); - } + CimInvokeCimMethod cimInvokeMethod = this.GetOperationAgent() ?? CreateOperationAgent(); + this.CmdletOperation = new CmdletOperationInvokeCimMethod(this, cimInvokeMethod); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -371,7 +400,7 @@ protected override void ProcessRecord() CimInvokeCimMethod cimInvokeMethod = this.GetOperationAgent(); cimInvokeMethod.InvokeCimMethod(this); cimInvokeMethod.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -379,11 +408,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimInvokeCimMethod cimInvokeMethod = this.GetOperationAgent(); - if (cimInvokeMethod != null) - { - cimInvokeMethod.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimInvokeMethod?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -395,7 +421,7 @@ protected override void EndProcessing() /// used to delegate all Invoke-CimMethod operations. /// /// - CimInvokeCimMethod GetOperationAgent() + private CimInvokeCimMethod GetOperationAgent() { return this.AsyncOperation as CimInvokeCimMethod; } @@ -407,15 +433,15 @@ CimInvokeCimMethod GetOperationAgent() /// /// /// - CimInvokeCimMethod CreateOperationAgent() + private CimInvokeCimMethod CreateOperationAgent() { - CimInvokeCimMethod cimInvokeMethod = new CimInvokeCimMethod(); + CimInvokeCimMethod cimInvokeMethod = new(); this.AsyncOperation = cimInvokeMethod; return cimInvokeMethod; } /// - /// check argument value + /// Check argument value. /// private void CheckArgument() { @@ -449,9 +475,9 @@ private void CheckArgument() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -536,9 +562,9 @@ private void CheckArgument() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ClassNameComputerSet, new ParameterSetEntry(2, true) }, { CimBaseCommand.ResourceUriSessionSet, new ParameterSetEntry(3) }, @@ -552,5 +578,5 @@ private void CheckArgument() { CimBaseCommand.CimClassSessionSet, new ParameterSetEntry(3) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/Microsoft.Management.Infrastructure.CimCmdlets.csproj b/src/Microsoft.Management.Infrastructure.CimCmdlets/Microsoft.Management.Infrastructure.CimCmdlets.csproj index d7db6f54025..582858a592b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/Microsoft.Management.Infrastructure.CimCmdlets.csproj +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/Microsoft.Management.Infrastructure.CimCmdlets.csproj @@ -1,7 +1,7 @@ - + - PowerShell Core's Microsoft.Management.Infrastructure.CimCmdlets project + PowerShell's Microsoft.Management.Infrastructure.CimCmdlets project $(NoWarn);CS1570;CS1572;CS1573;CS1574;CS1584;CS1587;CS1591 Microsoft.Management.Infrastructure.CimCmdlets @@ -10,9 +10,4 @@ - - $(DefineConstants);CORECLR - portable - - diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs index c843acdb045..5843f25a26b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -11,7 +9,6 @@ using System.Management.Automation; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -24,6 +21,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// on the server, otherwise just create client in-memory instance /// /// + [Alias("ncim")] [Cmdlet(VerbsCommon.New, "CimInstance", DefaultParameterSetName = CimBaseCommand.ClassNameComputerSet, SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227963")] [OutputType(typeof(CimInstance))] public class NewCimInstanceCommand : CimBaseCommand @@ -31,7 +29,7 @@ public class NewCimInstanceCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public NewCimInstanceCommand() : base(parameters, parameterSets) @@ -57,16 +55,21 @@ public NewCimInstanceCommand() Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ClassNameComputerSet)] - public String ClassName + public string ClassName { - get { return className; } + get + { + return className; + } + set { className = value; base.SetParameter(value, nameClassName); } } - private String className; + + private string className; /// /// @@ -82,13 +85,18 @@ public String ClassName ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -113,16 +121,21 @@ public Uri ResourceUri ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Key + public string[] Key { - get { return key; } + get + { + return key; + } + set { key = value; base.SetParameter(value, nameKey); } } - private String[] key; + + private string[] key; /// /// The following is the definition of the input parameter "CimClass". @@ -140,13 +153,18 @@ public String[] Key ParameterSetName = CimClassComputerSet)] public CimClass CimClass { - get { return cimClass; } + get + { + return cimClass; + } + set { cimClass = value; base.SetParameter(value, nameCimClass); } } + private CimClass cimClass; /// @@ -163,12 +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". @@ -187,16 +200,21 @@ public IDictionary Property [Parameter( ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] - public String Namespace + public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } + set { nameSpace = value; base.SetParameter(value, nameNamespace); } } - private String nameSpace; + + private string nameSpace; /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -205,12 +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; } /// /// @@ -233,13 +246,18 @@ public UInt32 OperationTimeoutSec [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimSession; /// @@ -261,16 +279,21 @@ public CimSession[] CimSession ValueFromPipelineByPropertyName = true, ParameterSetName = CimClassComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } + set { computerName = value; base.SetParameter(value, nameComputerName); } } - private String[] computerName; + + private string[] computerName; /// /// @@ -289,12 +312,18 @@ public String[] ComputerName ParameterSetName = CimBaseCommand.CimClassSessionSet)] public SwitchParameter ClientOnly { - get { return clientOnly; } - set { + get + { + return clientOnly; + } + + set + { clientOnly = value; base.SetParameter(value, nameClientOnly); - } + } } + private SwitchParameter clientOnly; #endregion @@ -308,7 +337,7 @@ protected override void BeginProcessing() { this.CmdletOperation = new CmdletOperationBase(this); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -320,29 +349,27 @@ protected override void ProcessRecord() if (this.ClientOnly) { string conflictParameterName = null; - if (null != this.ComputerName) + if (this.ComputerName != null) { conflictParameterName = @"ComputerName"; } - else if (null != this.CimSession) + else if (this.CimSession != null) { conflictParameterName = @"CimSession"; } - if (null != conflictParameterName) + + if (conflictParameterName != null) { ThrowConflictParameterWasSet(@"New-CimInstance", conflictParameterName, @"ClientOnly"); return; } } - CimNewCimInstance cimNewCimInstance = this.GetOperationAgent(); - if (cimNewCimInstance == null) - { - cimNewCimInstance = CreateOperationAgent(); - } + CimNewCimInstance cimNewCimInstance = this.GetOperationAgent() ?? CreateOperationAgent(); + cimNewCimInstance.NewCimInstance(this); cimNewCimInstance.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -350,11 +377,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimNewCimInstance cimNewCimInstance = this.GetOperationAgent(); - if (cimNewCimInstance != null) - { - cimNewCimInstance.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimNewCimInstance?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -366,9 +390,9 @@ protected override void EndProcessing() /// used to delegate all New-CimInstance operations. /// /// - CimNewCimInstance GetOperationAgent() + private CimNewCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimNewCimInstance); + return this.AsyncOperation as CimNewCimInstance; } /// @@ -378,15 +402,15 @@ CimNewCimInstance GetOperationAgent() /// /// /// - CimNewCimInstance CreateOperationAgent() + private CimNewCimInstance CreateOperationAgent() { - CimNewCimInstance cimNewCimInstance = new CimNewCimInstance(); + CimNewCimInstance cimNewCimInstance = new(); this.AsyncOperation = cimNewCimInstance; return cimNewCimInstance; } /// - /// check argument value + /// Check argument value. /// private void CheckArgument() { @@ -418,9 +442,9 @@ private void CheckArgument() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -481,9 +505,9 @@ private void CheckArgument() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ClassNameSessionSet, new ParameterSetEntry(2) }, { CimBaseCommand.ClassNameComputerSet, new ParameterSetEntry(1, true) }, @@ -493,5 +517,5 @@ private void CheckArgument() { CimBaseCommand.ResourceUriComputerSet, new ParameterSetEntry(1) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs index 8e601f60e92..8b8e36cf829 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs @@ -1,17 +1,14 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; -using System.Management.Automation; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; using Microsoft.Management.Infrastructure.Options; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -21,6 +18,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// The CimSession object returned by the Cmdlet is used by all other CIM /// cmdlets. /// + [Alias("ncms")] [Cmdlet(VerbsCommon.New, "CimSession", DefaultParameterSetName = CredentialParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227967")] [OutputType(typeof(CimSession))] public sealed class NewCimSessionCommand : CimBaseCommand @@ -35,13 +33,18 @@ public sealed class NewCimSessionCommand : CimBaseCommand ParameterSetName = CredentialParameterSet)] public PasswordAuthenticationMechanism Authentication { - get { return authentication;} + get + { + return authentication; + } + set { authentication = value; authenticationSet = true; } } + private PasswordAuthenticationMechanism authentication; private bool authenticationSet = false; @@ -51,13 +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". @@ -65,12 +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". @@ -83,12 +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; } /// /// @@ -102,12 +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; } /// /// @@ -117,20 +100,25 @@ 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 { operationTimeout = value; operationTimeoutSet = true; } } - private UInt32 operationTimeout; + + private uint operationTimeout; internal bool operationTimeoutSet = false; /// @@ -140,15 +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". @@ -156,16 +136,21 @@ 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 { port = value; portSet = true; } } - private UInt32 port; + + private uint port; private bool portSet = false; /// @@ -177,17 +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 @@ -201,7 +183,7 @@ protected override void BeginProcessing() cimNewSession = new CimNewSession(); this.CmdletOperation = new CmdletOperationTestCimSession(this, this.cimNewSession); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -213,7 +195,7 @@ protected override void ProcessRecord() BuildSessionOptions(out outputOptions, out outputCredential); cimNewSession.NewCimSession(this, outputOptions, outputCredential); cimNewSession.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -221,15 +203,15 @@ protected override void ProcessRecord() protected override void EndProcessing() { cimNewSession.ProcessRemainActions(this.CmdletOperation); - }//End EndProcessing() + } #endregion #region helper methods /// - /// Build a CimSessionOptions, used to create CimSession + /// Build a CimSessionOptions, used to create CimSession. /// - /// Null means no prefer CimSessionOptions + /// Null means no prefer CimSessionOptions. internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCredential outputCredential) { DebugHelper.WriteLogEx(); @@ -240,19 +222,19 @@ 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); } } + outputOptions = null; 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; @@ -261,11 +243,13 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr conflict = true; parameterName = @"CertificateThumbprint"; } + if (portSet) { conflict = true; parameterName = @"Port"; } + if (conflict) { ThrowConflictParameterWasSet(@"New-CimSession", parameterName, @"DComSessionOptions"); @@ -273,6 +257,7 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr } } } + if (portSet || (this.CertificateThumbprint != null)) { WSManSessionOptions wsmanOptions = (options == null) ? new WSManSessionOptions() : options as WSManSessionOptions; @@ -281,13 +266,16 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr wsmanOptions.DestinationPort = this.Port; portSet = false; } + if (this.CertificateThumbprint != null) { - CimCredential credentials = new CimCredential(CertificateAuthenticationMechanism.Default, this.CertificateThumbprint); + CimCredential credentials = new(CertificateAuthenticationMechanism.Default, this.CertificateThumbprint); wsmanOptions.AddDestinationCredentials(credentials); } + options = wsmanOptions; } + if (this.operationTimeoutSet) { if (options != null) @@ -295,13 +283,15 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr options.Timeout = TimeSpan.FromSeconds((double)this.OperationTimeoutSec); } } - if (this.authenticationSet || (this.credential != null)) + + if (this.authenticationSet || (this.Credential != null)) { PasswordAuthenticationMechanism authentication = this.authenticationSet ? this.Authentication : PasswordAuthenticationMechanism.Default; if (this.authenticationSet) { this.authenticationSet = false; } + CimCredential credentials = CreateCimCredentials(this.Credential, authentication, @"New-CimSession", @"Authentication"); if (credentials == null) { @@ -316,6 +306,7 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr options.AddDestinationCredentials(credentials); } } + DebugHelper.WriteLogEx("Set outputOptions: {0}", 1, outputOptions); outputOptions = options; } @@ -335,18 +326,15 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr #region IDisposable /// - /// Clean up resources + /// Clean up resources. /// protected override void DisposeInternal() { base.DisposeInternal(); // Dispose managed resources. - if (this.cimNewSession != null) - { - this.cimNewSession.Dispose(); - } + this.cimNewSession?.Dispose(); } #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs index c03352af012..54956a9805a 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -12,7 +10,6 @@ using Microsoft.Management.Infrastructure.Options; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -21,11 +18,13 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets public enum ProtocolType { Default, + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] Dcom, + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] Wsman - }; + } /// /// The Cmdlet allows the IT Pro to create a CimSessionOptions object that she/he @@ -38,6 +37,7 @@ public enum ProtocolType /// DComSessionOptions or WSManSessionOptions, which derive from /// CimSessionOptions. /// + [Alias("ncso")] [Cmdlet(VerbsCommon.New, "CimSessionOption", DefaultParameterSetName = ProtocolNameParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227969")] [OutputType(typeof(CimSessionOptions))] public sealed class NewCimSessionOptionCommand : CimBaseCommand @@ -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,11 @@ public NewCimSessionOptionCommand() [Parameter(ParameterSetName = WSManParameterSet)] public SwitchParameter NoEncryption { - get { return noEncryption; } + get + { + return noEncryption; + } + set { noEncryption = value; @@ -72,18 +76,23 @@ public SwitchParameter NoEncryption base.SetParameter(value, nameNoEncryption); } } + private SwitchParameter noEncryption; private bool noEncryptionSet = false; /// /// The following is the definition of the input parameter "CertificateCACheck". - /// Switch indicating if Certificate Authority should be validated + /// Switch indicating if Certificate Authority should be validated. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] public SwitchParameter SkipCACheck { - get { return skipCACheck; } + get + { + return skipCACheck; + } + set { skipCACheck = value; @@ -91,18 +100,23 @@ public SwitchParameter SkipCACheck base.SetParameter(value, nameSkipCACheck); } } + private SwitchParameter skipCACheck; private bool skipCACheckSet = false; /// /// The following is the definition of the input parameter "CertificateCNCheck". - /// Switch indicating if Certificate Name should be validated + /// Switch indicating if Certificate Name should be validated. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] public SwitchParameter SkipCNCheck { - get { return skipCNCheck; } + get + { + return skipCNCheck; + } + set { skipCNCheck = value; @@ -110,6 +124,7 @@ public SwitchParameter SkipCNCheck base.SetParameter(value, nameSkipCNCheck); } } + private SwitchParameter skipCNCheck; private bool skipCNCheckSet = false; @@ -121,7 +136,11 @@ public SwitchParameter SkipCNCheck ParameterSetName = WSManParameterSet)] public SwitchParameter SkipRevocationCheck { - get { return skipRevocationCheck; } + get + { + return skipRevocationCheck; + } + set { skipRevocationCheck = value; @@ -129,18 +148,23 @@ public SwitchParameter SkipRevocationCheck base.SetParameter(value, nameSkipRevocationCheck); } } + private SwitchParameter skipRevocationCheck; private bool skipRevocationCheckSet = false; /// /// The following is the definition of the input parameter "EncodePortInServicePrincipalName". - /// Switch indicating if to encode Port In Service Principal Name + /// Switch indicating if to encode Port In Service Principal Name. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] public SwitchParameter EncodePortInServicePrincipalName { - get { return encodeportinserviceprincipalname; } + get + { + return encodeportinserviceprincipalname; + } + set { encodeportinserviceprincipalname = value; @@ -148,6 +172,7 @@ public SwitchParameter EncodePortInServicePrincipalName base.SetParameter(value, nameEncodePortInServicePrincipalName); } } + private SwitchParameter encodeportinserviceprincipalname; private bool encodeportinserviceprincipalnameSet = false; @@ -161,7 +186,11 @@ public SwitchParameter EncodePortInServicePrincipalName ParameterSetName = WSManParameterSet)] public PacketEncoding Encoding { - get { return encoding; } + get + { + return encoding; + } + set { encoding = value; @@ -169,6 +198,7 @@ public PacketEncoding Encoding base.SetParameter(value, nameEncoding); } } + private PacketEncoding encoding; private bool encodingSet = false; @@ -181,13 +211,18 @@ public PacketEncoding Encoding ParameterSetName = WSManParameterSet)] public Uri HttpPrefix { - get { return httpprefix; } + get + { + return httpprefix; + } + set { httpprefix = value; base.SetParameter(value, nameHttpPrefix); } } + private Uri httpprefix; /// @@ -196,9 +231,13 @@ public Uri HttpPrefix /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] - public UInt32 MaxEnvelopeSizeKB + public uint MaxEnvelopeSizeKB { - get { return maxenvelopesizekb; } + get + { + return maxenvelopesizekb; + } + set { maxenvelopesizekb = value; @@ -206,7 +245,8 @@ public UInt32 MaxEnvelopeSizeKB base.SetParameter(value, nameMaxEnvelopeSizeKB); } } - private UInt32 maxenvelopesizekb; + + private uint maxenvelopesizekb; private bool maxenvelopesizekbSet = false; /// @@ -217,7 +257,11 @@ public UInt32 MaxEnvelopeSizeKB ParameterSetName = WSManParameterSet)] public PasswordAuthenticationMechanism ProxyAuthentication { - get { return proxyAuthentication; } + get + { + return proxyAuthentication; + } + set { proxyAuthentication = value; @@ -225,6 +269,7 @@ public PasswordAuthenticationMechanism ProxyAuthentication base.SetParameter(value, nameProxyAuthentication); } } + private PasswordAuthenticationMechanism proxyAuthentication; private bool proxyauthenticationSet = false; @@ -233,32 +278,42 @@ public PasswordAuthenticationMechanism ProxyAuthentication /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] - public String ProxyCertificateThumbprint + public string ProxyCertificateThumbprint { - get { return proxycertificatethumbprint; } + get + { + return proxycertificatethumbprint; + } + set { proxycertificatethumbprint = value; base.SetParameter(value, nameProxyCertificateThumbprint); } } - private String proxycertificatethumbprint; + + private string proxycertificatethumbprint; /// /// The following is the definition of the input parameter "ProxyCredential". /// 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 { proxycredential = value; base.SetParameter(value, nameProxyCredential); } } + private PSCredential proxycredential; /// @@ -270,7 +325,11 @@ public PSCredential ProxyCredential ParameterSetName = WSManParameterSet)] public ProxyType ProxyType { - get { return proxytype; } + get + { + return proxytype; + } + set { proxytype = value; @@ -278,6 +337,7 @@ public ProxyType ProxyType base.SetParameter(value, nameProxyType); } } + private ProxyType proxytype; private bool proxytypeSet = false; @@ -289,7 +349,11 @@ public ProxyType ProxyType ParameterSetName = WSManParameterSet)] public SwitchParameter UseSsl { - get { return usessl; } + get + { + return usessl; + } + set { usessl = value; @@ -297,6 +361,7 @@ public SwitchParameter UseSsl base.SetParameter(value, nameUseSsl); } } + private SwitchParameter usessl; private bool usesslSet = false; @@ -308,7 +373,11 @@ public SwitchParameter UseSsl [Parameter(ParameterSetName = DcomParameterSet)] public ImpersonationType Impersonation { - get { return impersonation; } + get + { + return impersonation; + } + set { impersonation = value; @@ -316,6 +385,7 @@ public ImpersonationType Impersonation base.SetParameter(value, nameImpersonation); } } + private ImpersonationType impersonation; private bool impersonationSet = false; @@ -327,7 +397,11 @@ public ImpersonationType Impersonation [Parameter(ParameterSetName = DcomParameterSet)] public SwitchParameter PacketIntegrity { - get { return packetintegrity; } + get + { + return packetintegrity; + } + set { packetintegrity = value; @@ -335,6 +409,7 @@ public SwitchParameter PacketIntegrity base.SetParameter(value, namePacketIntegrity); } } + private SwitchParameter packetintegrity; private bool packetintegritySet = false; @@ -346,7 +421,11 @@ public SwitchParameter PacketIntegrity [Parameter(ParameterSetName = DcomParameterSet)] public SwitchParameter PacketPrivacy { - get { return packetprivacy; } + get + { + return packetprivacy; + } + set { packetprivacy = value; @@ -354,12 +433,13 @@ public SwitchParameter PacketPrivacy base.SetParameter(value, namePacketPrivacy); } } + private SwitchParameter packetprivacy; private bool packetprivacySet = false; /// /// The following is the definition of the input parameter "Protocol". - /// Switch indicating if to encode Port In Service Principal Name + /// Switch indicating if to encode Port In Service Principal Name. /// [Parameter( Mandatory = true, @@ -368,38 +448,33 @@ public SwitchParameter PacketPrivacy ParameterSetName = ProtocolNameParameterSet)] public ProtocolType Protocol { - get { return protocol; } + get + { + return protocol; + } + set { protocol = value; base.SetParameter(value, nameProtocol); } } + private ProtocolType protocol; /// /// The following is the definition of the input parameter "UICulture". - /// Specifies the UI Culture to use. i.e. en-us, ar-sa + /// 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 + /// 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 @@ -412,7 +487,7 @@ protected override void BeginProcessing() { this.CmdletOperation = new CmdletOperationBase(this); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -427,11 +502,13 @@ protected override void ProcessRecord() { options = CreateWSMANSessionOptions(); } + break; case DcomParameterSet: { options = CreateDComSessionOptions(); } + break; case ProtocolNameParameterSet: switch (Protocol) @@ -444,41 +521,45 @@ protected override void ProcessRecord() options = CreateWSMANSessionOptions(); break; } + break; default: return; } + if (options != null) { if (this.Culture != null) { options.Culture = this.Culture; } + if (this.UICulture != null) { options.UICulture = this.UICulture; } + this.WriteObject(options); } - }//End ProcessRecord() + } /// /// EndProcessing method. /// protected override void EndProcessing() { - }//End EndProcessing() + } #endregion #region helper functions /// - /// Create DComSessionOptions + /// Create DComSessionOptions. /// /// internal DComSessionOptions CreateDComSessionOptions() { - DComSessionOptions dcomoptions = new DComSessionOptions(); + DComSessionOptions dcomoptions = new(); if (this.impersonationSet) { dcomoptions.Impersonation = this.Impersonation; @@ -488,6 +569,7 @@ internal DComSessionOptions CreateDComSessionOptions() { dcomoptions.Impersonation = ImpersonationType.Impersonate; } + if (this.packetintegritySet) { dcomoptions.PacketIntegrity = this.packetintegrity; @@ -497,6 +579,7 @@ internal DComSessionOptions CreateDComSessionOptions() { dcomoptions.PacketIntegrity = true; } + if (this.packetprivacySet) { dcomoptions.PacketPrivacy = this.PacketPrivacy; @@ -506,16 +589,17 @@ internal DComSessionOptions CreateDComSessionOptions() { dcomoptions.PacketPrivacy = true; } + return dcomoptions; } /// - /// Create WSMANSessionOptions + /// Create WSMANSessionOptions. /// /// internal WSManSessionOptions CreateWSMANSessionOptions() { - WSManSessionOptions wsmanoptions = new WSManSessionOptions(); + WSManSessionOptions wsmanoptions = new(); if (this.noEncryptionSet) { wsmanoptions.NoEncryption = true; @@ -525,6 +609,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.NoEncryption = false; } + if (this.skipCACheckSet) { wsmanoptions.CertCACheck = false; @@ -534,6 +619,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.CertCACheck = true; } + if (this.skipCNCheckSet) { wsmanoptions.CertCNCheck = false; @@ -543,6 +629,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.CertCNCheck = true; } + if (this.skipRevocationCheckSet) { wsmanoptions.CertRevocationCheck = false; @@ -552,6 +639,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.CertRevocationCheck = true; } + if (this.encodeportinserviceprincipalnameSet) { wsmanoptions.EncodePortInServicePrincipalName = this.EncodePortInServicePrincipalName; @@ -561,6 +649,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.EncodePortInServicePrincipalName = false; } + if (this.encodingSet) { wsmanoptions.PacketEncoding = this.Encoding; @@ -569,10 +658,12 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.PacketEncoding = PacketEncoding.Utf8; } + if (this.HttpPrefix != null) { wsmanoptions.HttpUrlPrefix = this.HttpPrefix; } + if (this.maxenvelopesizekbSet) { wsmanoptions.MaxEnvelopeSize = this.MaxEnvelopeSizeKB; @@ -581,11 +672,13 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.MaxEnvelopeSize = 0; } - if (!String.IsNullOrWhiteSpace(this.ProxyCertificateThumbprint)) + + if (!string.IsNullOrWhiteSpace(this.ProxyCertificateThumbprint)) { - CimCredential credentials = new CimCredential(CertificateAuthenticationMechanism.Default, this.ProxyCertificateThumbprint); + CimCredential credentials = new(CertificateAuthenticationMechanism.Default, this.ProxyCertificateThumbprint); wsmanoptions.AddProxyCredentials(credentials); } + if (this.proxyauthenticationSet) { this.proxyauthenticationSet = false; @@ -601,10 +694,11 @@ internal WSManSessionOptions CreateWSMANSessionOptions() catch (Exception ex) { DebugHelper.WriteLogEx(ex.ToString(), 1); - throw ex; + throw; } } } + if (this.proxytypeSet) { wsmanoptions.ProxyType = this.ProxyType; @@ -614,6 +708,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.ProxyType = Options.ProxyType.WinHttp; } + if (this.usesslSet) { wsmanoptions.UseSsl = this.UseSsl; @@ -623,6 +718,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() { wsmanoptions.UseSsl = false; } + wsmanoptions.DestinationPort = 0; return wsmanoptions; } @@ -651,9 +747,9 @@ internal WSManSessionOptions CreateWSMANSessionOptions() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameNoEncryption, new HashSet { @@ -752,14 +848,14 @@ internal WSManSessionOptions CreateWSMANSessionOptions() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ProtocolNameParameterSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.DcomParameterSet, new ParameterSetEntry(0) }, { CimBaseCommand.WSManParameterSet, new ParameterSetEntry(0) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs index 39ce6cfcd62..b314691e41f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -11,7 +9,6 @@ using Microsoft.PowerShell.Commands; #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// @@ -22,6 +19,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// cancel the subscription /// Should we have the second parameter set with a -Query? /// + [Alias("rcie")] [Cmdlet(VerbsLifecycle.Register, "CimIndicationEvent", DefaultParameterSetName = CimBaseCommand.ClassNameComputerSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227960")] public class RegisterCimIndicationCommand : ObjectEventRegistrationBase { @@ -37,12 +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". @@ -55,16 +48,21 @@ public String Namespace [Parameter(Mandatory = true, Position = 0, ParameterSetName = CimBaseCommand.ClassNameComputerSet)] - public String ClassName + public string ClassName { - get { return className; } + get + { + return className; + } + set { className = value; this.SetParameter(value, nameClassName); } } - private String className; + + private string className; /// /// The following is the definition of the input parameter "Query". @@ -78,16 +76,21 @@ public String ClassName Mandatory = true, Position = 0, ParameterSetName = CimBaseCommand.QueryExpressionComputerSet)] - public String Query + public string Query { - get { return query; } + get + { + return query; + } + set { query = value; this.SetParameter(value, nameQuery); } } - private String query; + + private string query; /// /// @@ -98,16 +101,21 @@ public String Query /// [Parameter(ParameterSetName = CimBaseCommand.QueryExpressionComputerSet)] [Parameter(ParameterSetName = CimBaseCommand.QueryExpressionSessionSet)] - public String QueryDialect + public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } + set { queryDialect = value; this.SetParameter(value, nameQueryDialect); } } - private String queryDialect; + + private string queryDialect; /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -116,12 +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". @@ -135,13 +138,18 @@ public UInt32 OperationTimeoutSec ParameterSetName = CimBaseCommand.ClassNameSessionSet)] public CimSession CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; this.SetParameter(value, nameCimSession); } } + private CimSession cimSession; /// @@ -152,23 +160,28 @@ public CimSession CimSession [Alias(CimBaseCommand.AliasCN, CimBaseCommand.AliasServerName)] [Parameter(ParameterSetName = CimBaseCommand.QueryExpressionComputerSet)] [Parameter(ParameterSetName = CimBaseCommand.ClassNameComputerSet)] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; this.SetParameter(value, nameComputerName); } } - private String computername; + + private string computername; #endregion /// - /// Returns the object that generates events to be monitored + /// Returns the object that generates events to be monitored. /// - protected override Object GetSourceObject() + protected override object GetSourceObject() { CimIndicationWatcher watcher = null; string parameterSetName = null; @@ -180,6 +193,7 @@ protected override Object GetSourceObject() { this.parameterBinder.reset(); } + string tempQueryExpression = string.Empty; switch (parameterSetName) { @@ -191,9 +205,10 @@ 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; } + switch (parameterSetName) { case CimBaseCommand.QueryExpressionSessionSet: @@ -201,25 +216,26 @@ protected override Object GetSourceObject() { watcher = new CimIndicationWatcher(this.CimSession, this.Namespace, this.QueryDialect, tempQueryExpression, this.OperationTimeoutSec); } + break; case CimBaseCommand.QueryExpressionComputerSet: case CimBaseCommand.ClassNameComputerSet: { watcher = new CimIndicationWatcher(this.ComputerName, this.Namespace, this.QueryDialect, tempQueryExpression, this.OperationTimeoutSec); } + break; } - if (watcher != null) - { - watcher.SetCmdlet(this); - } + + watcher?.SetCmdlet(this); + return watcher; } /// - /// Returns the event name to be monitored on the input object + /// Returns the event name to be monitored on the input object. /// - protected override String GetSourceObjectEventName() + protected override string GetSourceObjectEventName() { return "CimIndicationArrived"; } @@ -239,9 +255,9 @@ protected override void EndProcessing() if (newSubscriber != null) { DebugHelper.WriteLog("RegisterCimIndicationCommand::EndProcessing subscribe to Unsubscribed event", 4); - newSubscriber.Unsubscribed += new PSEventUnsubscribedEventHandler(newSubscriber_Unsubscribed); + newSubscriber.Unsubscribed += newSubscriber_Unsubscribed; } - }//End EndProcessing() + } /// /// @@ -256,15 +272,12 @@ private static void newSubscriber_Unsubscribed( DebugHelper.WriteLogEx(); CimIndicationWatcher watcher = sender as CimIndicationWatcher; - if (watcher != null) - { - watcher.Stop(); - } + watcher?.Stop(); } #region private members /// - /// check argument value + /// Check argument value. /// private void CheckArgument() { @@ -272,13 +285,13 @@ private void CheckArgument() } /// - /// Parameter binder used to resolve parameter set name + /// Parameter binder used to resolve parameter set name. /// - private ParameterBinder parameterBinder = new ParameterBinder( + private readonly ParameterBinder parameterBinder = new( parameters, parameterSets); /// - /// Set the parameter + /// Set the parameter. /// /// private void SetParameter(object value, string parameterName) @@ -287,6 +300,7 @@ private void SetParameter(object value, string parameterName) { return; } + this.parameterBinder.SetParameter(parameterName, true); } @@ -299,9 +313,9 @@ private void SetParameter(object value, string parameterName) #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -336,9 +350,9 @@ private void SetParameter(object value, string parameterName) }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.QueryExpressionSessionSet, new ParameterSetEntry(2) }, { CimBaseCommand.QueryExpressionComputerSet, new ParameterSetEntry(1) }, @@ -347,5 +361,5 @@ private void SetParameter(object value, string parameterName) }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs index 6544f9d4427..5ac8d129367 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives @@ -12,16 +10,16 @@ #endregion - namespace Microsoft.Management.Infrastructure.CimCmdlets { /// /// Enables the user to remove a CimInstance. /// + [Alias("rcim")] [Cmdlet( VerbsCommon.Remove, "CimInstance", - SupportsShouldProcess=true, + SupportsShouldProcess = true, DefaultParameterSetName = CimBaseCommand.CimInstanceComputerSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=227964")] public class RemoveCimInstanceCommand : CimBaseCommand @@ -29,7 +27,7 @@ public class RemoveCimInstanceCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public RemoveCimInstanceCommand() : base(parameters, parameterSets) @@ -55,15 +53,19 @@ public RemoveCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } - private CimSession[] cimSession; + private CimSession[] cimSession; /// /// @@ -77,13 +79,18 @@ public CimSession[] CimSession ParameterSetName = CimBaseCommand.CimInstanceSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -96,16 +103,21 @@ public Uri ResourceUri [Parameter( ParameterSetName = CimBaseCommand.CimInstanceComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; base.SetParameter(value, nameComputerName); } } - private String[] computername; + + private string[] computername; /// /// The following is the definition of the input parameter "Namespace". @@ -119,16 +131,21 @@ public String[] ComputerName Position = 1, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QueryComputerSet)] - public String Namespace + public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } + set { nameSpace = value; base.SetParameter(value, nameNamespace); } } - private String nameSpace; + + private string nameSpace; /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -137,12 +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". @@ -161,22 +173,22 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } + set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } /// - /// Property for internal usage purpose + /// 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". @@ -191,16 +203,21 @@ internal CimInstance CimInstance Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QuerySessionSet)] - public String Query + public string Query { - get { return query;} + get + { + return query; + } + set { query = value; base.SetParameter(value, nameQuery); } } - private String query; + + private string query; /// /// The following is the definition of the input parameter "QueryDialect". @@ -211,16 +228,21 @@ public String Query ParameterSetName = CimBaseCommand.QuerySessionSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QueryComputerSet)] - public String QueryDialect + public string QueryDialect { - get { return querydialect;} + get + { + return querydialect; + } + set { querydialect = value; base.SetParameter(value, nameQueryDialect); } } - private String querydialect; + + private string querydialect; #endregion @@ -231,14 +253,11 @@ public String QueryDialect /// protected override void BeginProcessing() { - CimRemoveCimInstance cimRemoveInstance = this.GetOperationAgent(); - if (cimRemoveInstance == null) - { - cimRemoveInstance = CreateOperationAgent(); - } + CimRemoveCimInstance cimRemoveInstance = this.GetOperationAgent() ?? CreateOperationAgent(); + this.CmdletOperation = new CmdletOperationRemoveCimInstance(this, cimRemoveInstance); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -249,7 +268,7 @@ protected override void ProcessRecord() CimRemoveCimInstance cimRemoveInstance = this.GetOperationAgent(); cimRemoveInstance.RemoveCimInstance(this); cimRemoveInstance.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -257,11 +276,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimRemoveCimInstance cimRemoveInstance = this.GetOperationAgent(); - if (cimRemoveInstance != null) - { - cimRemoveInstance.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimRemoveInstance?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -273,9 +289,9 @@ protected override void EndProcessing() /// used to delegate all Remove-CimInstance operations. /// /// - CimRemoveCimInstance GetOperationAgent() + private CimRemoveCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimRemoveCimInstance); + return this.AsyncOperation as CimRemoveCimInstance; } /// @@ -285,9 +301,9 @@ CimRemoveCimInstance GetOperationAgent() /// /// /// - CimRemoveCimInstance CreateOperationAgent() + private CimRemoveCimInstance CreateOperationAgent() { - CimRemoveCimInstance cimRemoveInstance = new CimRemoveCimInstance(); + CimRemoveCimInstance cimRemoveInstance = new(); this.AsyncOperation = cimRemoveInstance; return cimRemoveInstance; } @@ -307,9 +323,9 @@ CimRemoveCimInstance CreateOperationAgent() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -356,9 +372,9 @@ CimRemoveCimInstance CreateOperationAgent() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimInstanceComputerSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.CimInstanceSessionSet, new ParameterSetEntry(2) }, @@ -366,5 +382,5 @@ CimRemoveCimInstance CreateOperationAgent() { CimBaseCommand.QuerySessionSet, new ParameterSetEntry(2) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs index 97194b95247..2f1a5ad026e 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs @@ -1,6 +1,6 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; @@ -19,7 +19,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// /// This Cmdlet allows the to remove, or terminate, one or more CimSession(s). /// - + [Alias("rcms")] [Cmdlet(VerbsCommon.Remove, "CimSession", SupportsShouldProcess = true, DefaultParameterSetName = CimSessionSet, @@ -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,13 +54,18 @@ public RemoveCimSessionCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimsession;} + get + { + return cimsession; + } + set { cimsession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimsession; /// @@ -77,16 +82,21 @@ public CimSession[] CimSession ValueFromPipelineByPropertyName = true, ParameterSetName = ComputerNameSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; base.SetParameter(value, nameComputerName); } } - private String[] computername; + + private string[] computername; /// /// The following is the definition of the input parameter "Id". @@ -98,16 +108,21 @@ 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 { id = value; base.SetParameter(value, nameId); } } - private UInt32[] id; + + private uint[] id; /// /// The following is the definition of the input parameter "InstanceId". @@ -121,13 +136,18 @@ public UInt32[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Guid[] InstanceId { - get { return instanceid;} + get + { + return instanceid; + } + set { instanceid = value; base.SetParameter(value, nameInstanceId); } } + private Guid[] instanceid; /// @@ -140,16 +160,21 @@ public Guid[] InstanceId ValueFromPipelineByPropertyName = true, ParameterSetName = NameSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Name + public string[] Name { - get { return name;} + get + { + return name; + } + set { name = value; base.SetParameter(value, nameName); } } - private String[] name; + + private string[] name; #endregion @@ -160,7 +185,7 @@ protected override void BeginProcessing() { this.cimRemoveSession = new CimRemoveSession(); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -169,12 +194,12 @@ protected override void ProcessRecord() { base.CheckParameterSet(); this.cimRemoveSession.RemoveCimSession(this); - }//End ProcessRecord() + } #region private members /// /// object used to remove the session from - /// session cache + /// session cache. /// private CimRemoveSession cimRemoveSession; @@ -187,9 +212,9 @@ protected override void ProcessRecord() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -219,9 +244,9 @@ protected override void ProcessRecord() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimSessionSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.ComputerNameSet, new ParameterSetEntry(1) }, @@ -230,5 +255,5 @@ protected override void ProcessRecord() { CimBaseCommand.NameSet, new ParameterSetEntry(1) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs index 3ff061400c5..d190e5fafba 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs @@ -1,7 +1,5 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; @@ -19,6 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets /// CimInstance must have values of all [KEY] properties. /// /// + [Alias("scim")] [Cmdlet( VerbsCommon.Set, "CimInstance", @@ -30,7 +29,7 @@ public class SetCimInstanceCommand : CimBaseCommand #region constructor /// - /// constructor + /// Initializes a new instance of the class. /// public SetCimInstanceCommand() : base(parameters, parameterSets) @@ -55,13 +54,18 @@ public SetCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } + set { cimSession = value; base.SetParameter(value, nameCimSession); } } + private CimSession[] cimSession; /// @@ -74,16 +78,21 @@ public CimSession[] CimSession [Parameter( ParameterSetName = CimBaseCommand.CimInstanceComputerSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName + public string[] ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; base.SetParameter(value, nameComputerName); } } - private String[] computername; + + private string[] computername; /// /// @@ -97,13 +106,18 @@ public String[] ComputerName ParameterSetName = CimBaseCommand.CimInstanceSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } + set { this.resourceUri = value; base.SetParameter(value, nameResourceUri); } } + private Uri resourceUri; /// @@ -114,16 +128,21 @@ public Uri ResourceUri ParameterSetName = CimBaseCommand.QuerySessionSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QueryComputerSet)] - public String Namespace + public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } + set { nameSpace = value; base.SetParameter(value, nameNamespace); } } - private String nameSpace; + + private string nameSpace; /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -132,12 +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". @@ -156,22 +170,22 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } + set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } /// - /// Property for internal usage purpose + /// 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". @@ -186,16 +200,21 @@ internal CimInstance CimInstance Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QuerySessionSet)] - public String Query + public string Query { - get { return query; } + get + { + return query; + } + set { query = value; base.SetParameter(value, nameQuery); } } - private String query; + + private string query; /// /// The following is the definition of the input parameter "QueryDialect". @@ -206,16 +225,21 @@ public String Query ParameterSetName = CimBaseCommand.QuerySessionSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CimBaseCommand.QueryComputerSet)] - public String QueryDialect + public string QueryDialect { - get { return querydialect; } + get + { + return querydialect; + } + set { querydialect = value; base.SetParameter(value, nameQueryDialect); } } - private String querydialect; + + private string querydialect; /// /// @@ -241,18 +265,25 @@ public String QueryDialect [Alias("Arguments")] public IDictionary Property { - get { return property; } + get + { + return property; + } + set { property = value; base.SetParameter(value, nameProperty); } } + 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. @@ -260,18 +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 @@ -282,14 +302,11 @@ public SwitchParameter PassThru /// protected override void BeginProcessing() { - CimSetCimInstance cimSetCimInstance = this.GetOperationAgent(); - if (cimSetCimInstance == null) - { - cimSetCimInstance = CreateOperationAgent(); - } + CimSetCimInstance cimSetCimInstance = this.GetOperationAgent() ?? CreateOperationAgent(); + this.CmdletOperation = new CmdletOperationSetCimInstance(this, cimSetCimInstance); this.AtBeginProcess = false; - }//End BeginProcessing() + } /// /// ProcessRecord method. @@ -300,7 +317,7 @@ protected override void ProcessRecord() CimSetCimInstance cimSetCimInstance = this.GetOperationAgent(); cimSetCimInstance.SetCimInstance(this); cimSetCimInstance.ProcessActions(this.CmdletOperation); - }//End ProcessRecord() + } /// /// EndProcessing method. @@ -308,11 +325,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { CimSetCimInstance cimSetCimInstance = this.GetOperationAgent(); - if (cimSetCimInstance != null) - { - cimSetCimInstance.ProcessRemainActions(this.CmdletOperation); - } - }//End EndProcessing() + cimSetCimInstance?.ProcessRemainActions(this.CmdletOperation); + } #endregion @@ -324,9 +338,9 @@ protected override void EndProcessing() /// used to delegate all Set-CimInstance operations. /// /// - CimSetCimInstance GetOperationAgent() + private CimSetCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimSetCimInstance); + return this.AsyncOperation as CimSetCimInstance; } /// @@ -336,9 +350,9 @@ CimSetCimInstance GetOperationAgent() /// /// /// - CimSetCimInstance CreateOperationAgent() + private CimSetCimInstance CreateOperationAgent() { - CimSetCimInstance cimSetCimInstance = new CimSetCimInstance(); + CimSetCimInstance cimSetCimInstance = new(); this.AsyncOperation = cimSetCimInstance; return cimSetCimInstance; } @@ -359,9 +373,9 @@ CimSetCimInstance CreateOperationAgent() #endregion /// - /// static parameter definition entries + /// Static parameter definition entries. /// - static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -416,9 +430,9 @@ CimSetCimInstance CreateOperationAgent() }; /// - /// static parameter set entries + /// Static parameter set entries. /// - static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.QuerySessionSet, new ParameterSetEntry(3) }, { CimBaseCommand.QueryComputerSet, new ParameterSetEntry(2) }, @@ -426,5 +440,5 @@ CimSetCimInstance CreateOperationAgent() { CimBaseCommand.CimInstanceComputerSet, new ParameterSetEntry(1, true) }, }; #endregion - }//End Class -}//End namespace + } +} diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs index 66828f9b611..adcab254231 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs @@ -1,19 +1,15 @@ -/*============================================================================ - * Copyright (c) Microsoft Corporation. All rights reserved. - *============================================================================ - */ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // #define LOGENABLE // uncomment this line to enable the log, - // create c:\temp\cim.log before invoking cimcmdlets +// create c:\temp\cim.log before invoking cimcmdlets using System; using System.Collections.Generic; 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 { @@ -29,47 +25,47 @@ internal static class ConstValue /// Default computername /// /// - internal static string[] DefaultSessionName = {@"*"}; + internal static readonly string[] DefaultSessionName = { @"*" }; /// /// /// Empty computername, which will create DCOM session /// /// - internal static string NullComputerName = null; + internal static readonly string NullComputerName = null; /// /// /// Empty computername array, which will create DCOM session /// /// - internal static string[] NullComputerNames = { NullComputerName }; + internal static readonly string[] NullComputerNames = { NullComputerName }; /// /// /// localhost computername, which will create WSMAN session /// /// - internal static string LocalhostComputerName = @"localhost"; + internal static readonly string LocalhostComputerName = @"localhost"; /// /// /// Default namespace /// /// - internal static string DefaultNameSpace = @"root\cimv2"; + internal static readonly string DefaultNameSpace = @"root\cimv2"; /// /// /// Default namespace /// /// - internal static string DefaultQueryDialect = @"WQL"; + internal static readonly string DefaultQueryDialect = @"WQL"; /// - /// Name of the note property that controls if "PSComputerName" column is shown + /// Name of the note property that controls if "PSComputerName" column is shown. /// - internal static string ShowComputerNameNoteProperty = "PSShowComputerName"; + internal static readonly string ShowComputerNameNoteProperty = "PSShowComputerName"; /// /// @@ -80,7 +76,7 @@ internal static class ConstValue /// internal static bool IsDefaultComputerName(string computerName) { - return String.IsNullOrEmpty(computerName); + return string.IsNullOrEmpty(computerName); } /// @@ -92,11 +88,11 @@ internal static bool IsDefaultComputerName(string computerName) /// internal static IEnumerable GetComputerNames(IEnumerable computerNames) { - return (computerNames == null) ? NullComputerNames : computerNames; + return computerNames ?? NullComputerNames; } /// - /// Get computer name, if it is null then return default one + /// Get computer name, if it is null then return default one. /// /// /// @@ -114,7 +110,7 @@ internal static string GetComputerName(string computerName) /// internal static string GetNamespace(string nameSpace) { - return (nameSpace == null) ? DefaultNameSpace : nameSpace; + return nameSpace ?? DefaultNameSpace; } /// @@ -126,7 +122,7 @@ internal static string GetNamespace(string nameSpace) /// internal static string GetQueryDialectWithDefault(string queryDialect) { - return (queryDialect == null) ? DefaultQueryDialect : queryDialect; + return queryDialect ?? DefaultQueryDialect; } } @@ -142,42 +138,29 @@ 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 + /// 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 + /// Flag used to control generating message into powershell. /// - internal static string logFile = @"c:\temp\Cim.log"; + internal static readonly string logFile = @"c:\temp\Cim.log"; /// - /// Indent space string + /// Indent space string. /// - internal static string space = @" "; + internal static readonly string space = @" "; /// - /// Indent space strings array + /// Indent space strings array. /// - internal static string[] spaces = { + internal static readonly string[] spaces = { string.Empty, space, space + space, @@ -187,51 +170,42 @@ internal static bool GenerateVerboseMessage }; /// - /// Lock the log file + /// Lock the log file. /// - internal static object logLock = new object(); + internal static readonly object logLock = new(); #endregion #region internal strings - internal static string runspaceStateChanged = "Runspace {0} state changed to {1}"; - internal static string classDumpInfo = @"Class type is {0}"; - internal static string propertyDumpInfo = @"Property name {0} of type {1}, its value is {2}"; - internal static string defaultPropertyType = @"It is a default property, default value is {0}"; - internal static string propertyValueSet = @"This property value is set by user {0}"; - internal static string addParameterSetName = @"Add parameter set {0} name to cache"; - internal static string removeParameterSetName = @"Remove parameter set {0} name from cache"; - internal static string currentParameterSetNameCount = @"Cache have {0} parameter set names"; - internal static string currentParameterSetNameInCache = @"Cache have parameter set {0} valid {1}"; - internal static string currentnonMandatoryParameterSetInCache = @"Cache have optional parameter set {0} valid {1}"; - internal static string optionalParameterSetNameCount = @"Cache have {0} optional parameter set names"; - internal static string finalParameterSetName = @"------Final parameter set name of the cmdlet is {0}"; - internal static string addToOptionalParameterSet = @"Add to optional ParameterSetNames {0}"; - internal static string startToResolveParameterSet = @"------Resolve ParameterSet Name"; - internal static string reservedString = @"------"; + internal static readonly string runspaceStateChanged = "Runspace {0} state changed to {1}"; + internal static readonly string classDumpInfo = @"Class type is {0}"; + internal static readonly string propertyDumpInfo = @"Property name {0} of type {1}, its value is {2}"; + internal static readonly string defaultPropertyType = @"It is a default property, default value is {0}"; + internal static readonly string propertyValueSet = @"This property value is set by user {0}"; + internal static readonly string addParameterSetName = @"Add parameter set {0} name to cache"; + internal static readonly string removeParameterSetName = @"Remove parameter set {0} name from cache"; + internal static readonly string currentParameterSetNameCount = @"Cache have {0} parameter set names"; + internal static readonly string currentParameterSetNameInCache = @"Cache have parameter set {0} valid {1}"; + internal static readonly string currentnonMandatoryParameterSetInCache = @"Cache have optional parameter set {0} valid {1}"; + internal static readonly string optionalParameterSetNameCount = @"Cache have {0} optional parameter set names"; + internal static readonly string finalParameterSetName = @"------Final parameter set name of the cmdlet is {0}"; + internal static readonly string addToOptionalParameterSet = @"Add to optional ParameterSetNames {0}"; + internal static readonly string startToResolveParameterSet = @"------Resolve ParameterSet Name"; + internal static readonly string reservedString = @"------"; #endregion #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 /// - /// Write message to log file named @logFile + /// Write message to log file named @logFile. /// /// internal static void WriteLog(string message) @@ -240,7 +214,7 @@ internal static void WriteLog(string message) } /// - /// Write blank line to log file named @logFile + /// Write blank line to log file named @logFile. /// /// internal static void WriteEmptyLine() @@ -249,18 +223,18 @@ internal static void WriteEmptyLine() } /// - /// Write message to log file named @logFile with args + /// Write message to log file named @logFile with args. /// /// internal static void WriteLog(string message, int indent, params object[] args) { - String outMessage = String.Empty; + string outMessage = string.Empty; FormatLogMessage(ref outMessage, message, args); WriteLog(outMessage, indent); } /// - /// Write message to log file w/o arguments + /// Write message to log file w/o arguments. /// /// /// @@ -270,19 +244,19 @@ internal static void WriteLog(string message, int indent) } /// - /// Write message to log file named @logFile with args + /// Write message to log file named @logFile with args. /// /// internal static void WriteLogEx(string message, int indent, params object[] args) { - String outMessage = String.Empty; + string outMessage = string.Empty; WriteLogInternal(string.Empty, 0, -1); FormatLogMessage(ref outMessage, message, args); WriteLogInternal(outMessage, indent, 3); } /// - /// Write message to log file w/o arguments + /// Write message to log file w/o arguments. /// /// /// @@ -293,7 +267,7 @@ internal static void WriteLogEx(string message, int indent) } /// - /// Write message to log file w/o arguments + /// Write message to log file w/o arguments. /// /// /// @@ -304,15 +278,15 @@ internal static void WriteLogEx() } /// - /// Format the message + /// Format the message. /// /// /// /// [Conditional("LOGENABLE")] - private static void FormatLogMessage(ref String outMessage, string message, params object[] args) + private static void FormatLogMessage(ref string outMessage, string message, params object[] args) { - outMessage = String.Format(CultureInfo.CurrentCulture, message, args); + outMessage = string.Format(CultureInfo.CurrentCulture, message, args); } /// @@ -336,36 +310,38 @@ private static void WriteLogInternal(string message, int indent, int depth) } } - if (generateLog) + if (GenerateLog) { if (indent < 0) { indent = 0; } + if (indent > 5) { indent = 5; } + string sourceInformation = string.Empty; if (depth != -1) { 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, GetSourceCodeInformation(true, 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); } - } } } @@ -379,26 +355,23 @@ private static void WriteLogInternal(string message, int indent, int depth) internal static class ValidationHelper { /// - /// Validate the argument is not null + /// Validate the argument is not null. /// /// /// public static void ValidateNoNullArgument(object obj, string argumentName) { - if (obj == null) - { - throw new ArgumentNullException(argumentName); - } + ArgumentNullException.ThrowIfNull(obj, argumentName); } /// - /// Validate the argument is not null and not whitespace + /// Validate the argument is not null and not whitespace. /// /// /// public static void ValidateNoNullorWhiteSpaceArgument(string obj, string argumentName) { - if (String.IsNullOrWhiteSpace(obj)) + if (string.IsNullOrWhiteSpace(obj)) { throw new ArgumentException(argumentName); } @@ -406,12 +379,12 @@ public static void ValidateNoNullorWhiteSpaceArgument(string obj, string argumen /// /// Validate that given classname/propertyname is a valid name compliance with DMTF standard. - /// Only for verifying ClassName and PropertyName argument + /// Only for verifying ClassName and PropertyName argument. /// /// /// /// - /// Throw if the given value is not a valid name (class name or property name) + /// Throw if the given value is not a valid name (class name or property name). public static string ValidateArgumentIsValidName(string parameterName, string value) { DebugHelper.WriteLogEx(); @@ -420,15 +393,16 @@ 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); return trimed; } } + DebugHelper.WriteLogEx("An invalid name: {0}={1}", 0, parameterName, value); - throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, Strings.InvalidParameterValue, value, parameterName)); + throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.InvalidParameterValue, value, parameterName)); } /// @@ -438,21 +412,23 @@ public static string ValidateArgumentIsValidName(string parameterName, string va /// /// /// - /// Throw if the given value contains any invalid name (class name or property name) - public static String[] ValidateArgumentIsValidName(string parameterName, String[] value) + /// Throw if the given value contains any invalid name (class name or property name). + public static string[] ValidateArgumentIsValidName(string parameterName, string[] value) { if (value != null) { foreach (string propertyName in value) { // * is wild char supported in select properties - if ((propertyName != null) && (String.Compare(propertyName.Trim(), "*", StringComparison.OrdinalIgnoreCase) == 0)) + if ((propertyName != null) && string.Equals(propertyName.Trim(), "*", StringComparison.OrdinalIgnoreCase)) { continue; } + ValidationHelper.ValidateArgumentIsValidName(parameterName, propertyName); } } + return value; } } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/CimCmdletStrings.resx b/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/CimCmdletStrings.resx new file mode 100644 index 00000000000..d479d28b5bf --- /dev/null +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/CimCmdletStrings.resx @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Operation '{0}' complete. + {0} is a placeholder for operation name. (i.e, GetCimInstance) + + + Create CimInstance + + + Delete CimInstance + + + Enumerate Associated CimInstances + + + Enumerate CimClasses + + + Enumerate CimInstances + + + Get CimClass + + + Get CimInstance + + + Invoke CimMethod + + + Modify CimInstance + + + Query CimInstances + + + Subscribe CimIndication + + + Perform operation '{0}' with following parameters, '{1}'. + {0} is a placeholder for operation name; {1} is a placeholder for parameters value + + + Parameter '{0}' cannot be used with the parameter '{1}'. + {0} is a placeholder for parameter name; {1} is a placeholder for another parameter name; + + + Could not find CimSession with the given {0} = {1} + {0} is a placeholder for property name; {1} is a placeholder for property value. + + + Could not find the following properties from the given class {0}: {1}. + {0} is a placeholder for class name; {1} is a placeholder for list of property names. + + + Could not modify readonly property '{0}' of object '{1}'. + {0} is a placeholder for propertyname; {1} is a placeholder for object string. + + + Default status description. + N/A + + + Cannot perform operation because the wildcard path {0} did not resolve to a file. + {0} is a placeholder for a path + + + Authentication type '{0}' is invalid without credential. Only following authentication type are allowed without credential, '{1}', '{2}', '{3}', or '{4}'. + {0} is a placeholder for authentication type. {1}-{4} are placeholders for authentication types. + + + Can not find method '{0}' in class '{1}'. + {0} is a placeholder for method name. {1} is a placeholders for class name. + + + Can not find Parameter '{0}' in method '{1}' of class '{2}'. + {0} is a placeholder for parameter name; {1} is a placeholder for method name; {2} is a placeholder for class name. + + + Invalid operation. Current cmdlet already have operation created. + N/A + + + Argument '{0}' contains characters that are not allowed in parameter '{1}'. Supply an argument that is valid and then try the command again. + {0} stand for argument value, {1} stand for parameter name. + + + Cannot perform operation because the path resolved to more than one file. This command cannot operate on multiple files. + + + Argument '{0}' can not be null. + N/A + + + CimSession proxy object already have operation in progress. + N/A + + + Cannot open file because the current provider ({0}) cannot open a file. + {0} is a placeholder for PowerShell filesystem-like provider name (i.e. registry provider) + + + Unable to add property '{0}' to input object '{1}'. The class schema does not contain the property. + {0} stand for property name, {1} stand for cim instance path. + + + Unable to resolve the parameter set name. + N/A + + diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/Microsoft.Management.Infrastructure.CimCmdlets.Strings.resx b/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/Microsoft.Management.Infrastructure.CimCmdlets.Strings.resx deleted file mode 100644 index 5015ddad221..00000000000 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/resources/Microsoft.Management.Infrastructure.CimCmdlets.Strings.resx +++ /dev/null @@ -1,228 +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 - - - Operation '{0}' complete. - {0} is a placeholder for operation name. (i.e, GetCimInstance) - - - Create CimInstance - - - Delete CimInstance - - - Enumerate Associated CimInstances - - - Enumerate CimClasses - - - Enumerate CimInstances - - - Get CimClass - - - Get CimInstance - - - Invoke CimMethod - - - Modify CimInstance - - - Query CimInstances - - - Subscribe CimIndication - - - Perform operation '{0}' with following parameters, '{1}'. - {0} is a placeholder for operation name; {1} is a placeholder for parameters value - - - Parameter '{0}' cannot be used with the parameter '{1}'. - {0} is a placeholder for parameter name; {1} is a placeholder for another parameter name; - - - Could not find CimSession with the given {0} = {1} - {0} is a placeholder for property name; {1} is a placeholder for property value. - - - Could not find the following properties from the given class {0}: {1}. - {0} is a placeholder for class name; {1} is a placeholder for list of property names. - - - Could not modify readonly property '{0}' of object '{1}'. - {0} is a placeholder for propertyname; {1} is a placeholder for object string. - - - Default status description. - N/A - - - Cannot perform operation because the wildcard path {0} did not resolve to a file. - {0} is a placeholder for a path - - - Authentication type '{0}' is invalid without credential. Only following authentication type are allowed without credential, '{1}', '{2}', '{3}', or '{4}'. - {0} is a placeholder for authentication type. {1}-{4} are placeholders for authentication types. - - - Can not find method '{0}' in class '{1}'. - {0} is a placeholder for method name. {1} is a placeholders for class name. - - - Can not find Parameter '{0}' in method '{1}' of class '{2}'. - {0} is a placeholder for parameter name; {1} is a placeholder for method name; {2} is a placeholder for class name. - - - Invalid operation. Current cmdlet already have operation created. - N/A - - - Argument '{0}' contains characters that are not allowed in parameter '{1}'. Supply an argument that is valid and then try the command again. - {0} stand for argument value, {1} stand for parameter name. - - - Cannot perform operation because the path resolved to more than one file. This command cannot operate on multiple files. - - - Argument '{0}' can not be null. - N/A - - - CimSession proxy object already have operation in progress. - N/A - - - Cannot open file because the current provider ({0}) cannot open a file. - {0} is a placeholder for PowerShell filesystem-like provider name (i.e. registry provider) - - - Unable to add property '{0}' to input object '{1}'. The class schema does not contain the property. - {0} stand for property name, {1} stand for cim instance path. - - - Unable to resolve the parameter set name. - N/A - - diff --git a/src/Microsoft.Management.UI.Internal/CommonHelper.cs b/src/Microsoft.Management.UI.Internal/CommonHelper.cs new file mode 100644 index 00000000000..c9492c701d6 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/CommonHelper.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Windows; + +// Specifies the location in which theme dictionaries are stored for types in an assembly. +[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] + +namespace Microsoft.Management.UI +{ + /// + /// Utilities in common in this assembly. + /// + internal static class CommonHelper + { + /// + /// Restore the values from the settings to the actual window position, size and state. + /// + /// The window we are setting position and size of. + /// The value for top from the user settings. + /// The value for left from the user settings. + /// The value for width from the user settings. + /// The value for height from the user settings. + /// The with used if is not valid. + /// The height used if is not valid. + /// True if the window is maximized in the user setting. + internal static void SetStartingPositionAndSize(Window target, double userSettingTop, double userSettingLeft, double userSettingWidth, double userSettingHeight, double defaultWidth, double defaultHeight, bool userSettingMaximized) + { + bool leftInvalid = userSettingLeft < System.Windows.SystemParameters.VirtualScreenLeft || + userSettingWidth > System.Windows.SystemParameters.VirtualScreenLeft + + System.Windows.SystemParameters.VirtualScreenWidth; + + bool topInvalid = userSettingTop < System.Windows.SystemParameters.VirtualScreenTop || + userSettingTop > System.Windows.SystemParameters.VirtualScreenTop + + System.Windows.SystemParameters.VirtualScreenHeight; + + bool widthInvalid = userSettingWidth < 0 || + userSettingWidth > System.Windows.SystemParameters.VirtualScreenWidth; + + bool heightInvalid = userSettingHeight < 0 || + userSettingHeight > System.Windows.SystemParameters.VirtualScreenHeight; + + if (leftInvalid || topInvalid) + { + target.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; + } + else + { + target.Left = userSettingLeft; + target.Top = userSettingTop; + } + + // If any saved coordinate is invalid, we set the window to the default position + if (widthInvalid || heightInvalid) + { + target.Width = defaultWidth; + target.Height = defaultHeight; + } + else + { + target.Width = userSettingWidth; + target.Height = userSettingHeight; + } + + if (userSettingMaximized) + { + target.WindowState = WindowState.Maximized; + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs new file mode 100644 index 00000000000..fdb81a6d416 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpParagraphBuilder.cs @@ -0,0 +1,994 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Management.Automation; +using System.Text; +using System.Windows.Documents; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Builds a help paragraph for a cmdlet. + /// + internal class HelpParagraphBuilder : ParagraphBuilder + { + /// + /// Indentation size. + /// + internal const int IndentSize = 4; + + /// + /// new line separators. + /// + private static readonly string[] Separators = new[] { "\r\n", "\n" }; + + /// + /// Object with the cmdelt. + /// + private readonly PSObject psObj; + + /// + /// Initializes a new instance of the HelpParagraphBuilder class. + /// + /// Paragraph being built. + /// Object with help information. + internal HelpParagraphBuilder(Paragraph paragraph, PSObject psObj) + : base(paragraph) + { + this.psObj = psObj; + this.AddTextToParagraphBuilder(); + } + + /// + /// Enum for category of Help. + /// + private enum HelpCategory + { + Default, + DscResource, + Class + } + + /// + /// Gets the string value of a property or null if it could not be retrieved. + /// + /// Object with the property. + /// Property name. + /// The string value of a property or null if it could not be retrieved. + internal static string GetPropertyString(PSObject psObj, string propertyName) + { + Debug.Assert(psObj != null, "ensured by caller"); + object value = GetPropertyObject(psObj, propertyName); + + if (value == null) + { + return null; + } + + return value.ToString(); + } + + /// + /// Adds the help text to the paragraph. + /// + internal void AddTextToParagraphBuilder() + { + this.ResetAllText(); + + string strCategory = HelpParagraphBuilder.GetProperty(this.psObj, "Category").Value.ToString(); + + HelpCategory category = HelpCategory.Default; + + if (string.Equals(strCategory, "DscResource", StringComparison.OrdinalIgnoreCase)) + { + category = HelpCategory.DscResource; + } + else if (string.Equals(strCategory, "Class", StringComparison.OrdinalIgnoreCase)) + { + category = HelpCategory.Class; + } + + if (HelpParagraphBuilder.GetProperty(this.psObj, "Syntax") == null) + { + if (category == HelpCategory.Default) + { + // if there is no syntax, this is not the standard help + // it might be an about page + this.AddText(this.psObj.ToString(), false); + return; + } + } + + switch (category) + { + case HelpCategory.Class: + this.AddDescription(HelpWindowSettings.Default.HelpSynopsysDisplayed, HelpWindowResources.SynopsisTitle, "Introduction"); + this.AddMembers(HelpWindowSettings.Default.HelpParametersDisplayed, HelpWindowResources.PropertiesTitle); + this.AddMembers(HelpWindowSettings.Default.HelpParametersDisplayed, HelpWindowResources.MethodsTitle); + break; + case HelpCategory.DscResource: + this.AddStringSection(HelpWindowSettings.Default.HelpSynopsysDisplayed, "Synopsis", HelpWindowResources.SynopsisTitle); + this.AddDescription(HelpWindowSettings.Default.HelpDescriptionDisplayed, HelpWindowResources.DescriptionTitle, "Description"); + this.AddParameters(HelpWindowSettings.Default.HelpParametersDisplayed, HelpWindowResources.PropertiesTitle, "Properties", HelpCategory.DscResource); + break; + default: + this.AddStringSection(HelpWindowSettings.Default.HelpSynopsysDisplayed, "Synopsis", HelpWindowResources.SynopsisTitle); + this.AddDescription(HelpWindowSettings.Default.HelpDescriptionDisplayed, HelpWindowResources.DescriptionTitle, "Description"); + this.AddParameters(HelpWindowSettings.Default.HelpParametersDisplayed, HelpWindowResources.ParametersTitle, "Parameters", HelpCategory.Default); + this.AddSyntax(HelpWindowSettings.Default.HelpSyntaxDisplayed, HelpWindowResources.SyntaxTitle); + break; + } + + this.AddInputOrOutputEntries(HelpWindowSettings.Default.HelpInputsDisplayed, HelpWindowResources.InputsTitle, "inputTypes", "inputType"); + this.AddInputOrOutputEntries(HelpWindowSettings.Default.HelpOutputsDisplayed, HelpWindowResources.OutputsTitle, "returnValues", "returnValue"); + this.AddNotes(HelpWindowSettings.Default.HelpNotesDisplayed, HelpWindowResources.NotesTitle); + this.AddExamples(HelpWindowSettings.Default.HelpExamplesDisplayed, HelpWindowResources.ExamplesTitle); + this.AddNavigationLink(HelpWindowSettings.Default.HelpRelatedLinksDisplayed, HelpWindowResources.RelatedLinksTitle); + this.AddStringSection(HelpWindowSettings.Default.HelpRemarksDisplayed, "Remarks", HelpWindowResources.RemarksTitle); + } + + /// + /// Gets the object property or null if it could not be retrieved. + /// + /// Object with the property. + /// Property name. + /// The object property or null if it could not be retrieved. + private static PSPropertyInfo GetProperty(PSObject psObj, string propertyName) + { + Debug.Assert(psObj != null, "ensured by caller"); + return psObj.Properties[propertyName]; + } + + /// + /// Gets a PSObject and then a value from it or null if the value could not be retrieved. + /// + /// PSObject that contains another PSObject as a property. + /// Property name that contains the 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) + { + Debug.Assert(psObj != null, "ensured by caller"); + PSObject innerPsObj = GetPropertyObject(psObj, psObjectName) as PSObject; + + if (innerPsObj == null) + { + return null; + } + + object value = GetPropertyObject(innerPsObj, propertyName); + + if (value == null) + { + return null; + } + + return value.ToString(); + } + + /// + /// Gets the value of a property or null if the value could not be retrieved. + /// + /// Object with the property. + /// Property name. + /// The value of a property or null if the value could not be retrieved. + private static object GetPropertyObject(PSObject psObj, string propertyName) + { + Debug.Assert(psObj != null, "ensured by caller"); + PSPropertyInfo property = HelpParagraphBuilder.GetProperty(psObj, propertyName); + if (property == null) + { + return null; + } + + object value = null; + try + { + value = property.Value; + } + catch (ExtendedTypeSystemException) + { + // ignore this exception + } + + return value; + } + + /// + /// Gets the text from a property of type PSObject[] where the first object has a text property. + /// + /// Objhect to get text from. + /// Property with PSObject[] containing text. + /// The text from a property of type PSObject[] where the first object has a text property. + private static string GetTextFromArray(PSObject psObj, string propertyText) + { + PSObject[] introductionObjects = HelpParagraphBuilder.GetPropertyObject(psObj, propertyText) as PSObject[]; + if (introductionObjects != null && introductionObjects.Length > 0) + { + return GetPropertyString(introductionObjects[0], "text"); + } + + return null; + } + + /// + /// Returns the largest size of a group of strings. + /// + /// Strings to evaluate the largest size from. + /// The largest size of a group of strings. + private static int LargestSize(params string[] strs) + { + int returnValue = 0; + + foreach (string str in strs) + { + if (str != null && str.Length > returnValue) + { + returnValue = str.Length; + } + } + + return returnValue; + } + + /// + /// Splits the string adding indentation before each line. + /// + /// String to add indentation to. + /// The string indented. + private static string AddIndent(string str) + { + return HelpParagraphBuilder.AddIndent(str, 1); + } + + /// + /// Splits the string adding indentation before each line. + /// + /// String to add indentation to. + /// Number of indentations. + /// The string indented. + private static string AddIndent(string str, int numberOfIdents) + { + StringBuilder indent = new StringBuilder(); + indent.Append(' ', numberOfIdents * HelpParagraphBuilder.IndentSize); + return HelpParagraphBuilder.AddIndent(str, indent.ToString()); + } + + /// + /// Splits the string adding indentation before each line. + /// + /// String to add indentation to. + /// Indentation string. + /// The string indented. + private static string AddIndent(string str, string indentString) + { + if (str == null) + { + return string.Empty; + } + + string[] lines = str.Split(Separators, StringSplitOptions.None); + + StringBuilder returnValue = new StringBuilder(); + foreach (string line in lines) + { + // Indentation is not localized + returnValue.Append($"{indentString}{line}\r\n"); + } + + if (returnValue.Length > 2) + { + // remove the last \r\n + returnValue.Remove(returnValue.Length - 2, 2); + } + + return returnValue.ToString(); + } + + /// + /// Get the object array value of a property. + /// + /// Object containing the property. + /// Property with the array value. + /// The object array value of a property. + private static object[] GetPropertyObjectArray(PSObject obj, string propertyName) + { + object innerObject; + if ((innerObject = HelpParagraphBuilder.GetPropertyObject(obj, propertyName)) == null) + { + return null; + } + + if (innerObject is PSObject) + { + return new[] { innerObject }; + } + + object[] innerObjectArray = innerObject as object[]; + return innerObjectArray; + } + + /// + /// Adds a section that contains only a string. + /// + /// True if it should add the segment. + /// Name of the section to add. + /// Title of the section. + private void AddStringSection(bool setting, string sectionName, string sectionTitle) + { + string propertyValue; + if (!setting || (propertyValue = HelpParagraphBuilder.GetPropertyString(this.psObj, sectionName)) == null) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + this.AddText(HelpParagraphBuilder.AddIndent(propertyValue), false); + this.AddText("\r\n\r\n", false); + } + + /// + /// Adds the help syntax segment. + /// + /// True if it should add the segment. + /// Title of the section. + private void AddSyntax(bool setting, string sectionTitle) + { + PSObject syntaxObject; + if (!setting || (syntaxObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, "Syntax") as PSObject) == null) + { + return; + } + + object[] syntaxItemsObj = HelpParagraphBuilder.GetPropertyObjectArray(syntaxObject, "syntaxItem"); + if (syntaxItemsObj == null || syntaxItemsObj.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (object syntaxItemObj in syntaxItemsObj) + { + PSObject syntaxItem = syntaxItemObj as PSObject; + if (syntaxItem == null) + { + continue; + } + + string commandName = GetPropertyString(syntaxItem, "name"); + + object[] parameterObjs = HelpParagraphBuilder.GetPropertyObjectArray(syntaxItem, "parameter"); + if (commandName == null || parameterObjs == null || parameterObjs.Length == 0) + { + continue; + } + + string commandStart = string.Create(CultureInfo.CurrentCulture, $"{commandName} "); + this.AddText(HelpParagraphBuilder.AddIndent(commandStart), false); + + foreach (object parameterObj in parameterObjs) + { + PSObject parameter = parameterObj as PSObject; + if (parameter == null) + { + continue; + } + + string parameterValue = GetPropertyString(parameter, "parameterValue"); + string position = GetPropertyString(parameter, "position"); + string required = GetPropertyString(parameter, "required"); + string parameterName = GetPropertyString(parameter, "name"); + if (position == null || required == null || parameterName == null) + { + continue; + } + + string parameterType = parameterValue == null ? string.Empty : string.Create(CultureInfo.CurrentCulture, $"<{parameterValue}>"); + + string parameterOptionalOpenBrace, parameterOptionalCloseBrace; + + if (string.Equals(required, "true", StringComparison.OrdinalIgnoreCase)) + { + parameterOptionalOpenBrace = string.Empty; + parameterOptionalCloseBrace = string.Empty; + } + else + { + parameterOptionalOpenBrace = "["; + parameterOptionalCloseBrace = "]"; + } + + string parameterNameOptionalOpenBrace, parameterNameOptionalCloseBrace; + + if (string.Equals(position, "named", StringComparison.OrdinalIgnoreCase)) + { + parameterNameOptionalOpenBrace = parameterNameOptionalCloseBrace = string.Empty; + } + else + { + parameterNameOptionalOpenBrace = "["; + parameterNameOptionalCloseBrace = "]"; + } + + string paramterPrefix = string.Format( + CultureInfo.CurrentCulture, + "{0}{1}-", + parameterOptionalOpenBrace, + parameterNameOptionalOpenBrace); + + this.AddText(paramterPrefix, false); + this.AddText(parameterName, true); + + string paramterSuffix = string.Format( + CultureInfo.CurrentCulture, + "{0} {1}{2} ", + parameterNameOptionalCloseBrace, + parameterType, + parameterOptionalCloseBrace); + this.AddText(paramterSuffix, false); + } + + string commonParametersText = string.Format( + CultureInfo.CurrentCulture, + "[<{0}>]\r\n\r\n", + HelpWindowResources.CommonParameters); + + this.AddText(commonParametersText, false); + } + + this.AddText("\r\n", false); + } + + /// + /// Adds the help description segment. + /// + /// True if it should add the segment. + /// Title of the section. + /// PropertyName that has description. + private void AddDescription(bool setting, string sectionTitle, string propertyName) + { + PSObject[] descriptionObjects; + if (!setting || + (descriptionObjects = HelpParagraphBuilder.GetPropertyObject(this.psObj, propertyName) as PSObject[]) == null || + descriptionObjects.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (PSObject description in descriptionObjects) + { + string descriptionText = GetPropertyString(description, "text"); + this.AddText(HelpParagraphBuilder.AddIndent(descriptionText), false); + this.AddText("\r\n", false); + } + + this.AddText("\r\n\r\n", false); + } + + /// + /// Adds the help examples segment. + /// + /// True if it should add the segment. + /// Title of the section. + private void AddExamples(bool setting, string sectionTitle) + { + if (!setting) + { + return; + } + + PSObject exampleRootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, "Examples") as PSObject; + if (exampleRootObject == null) + { + return; + } + + object[] exampleObjects = HelpParagraphBuilder.GetPropertyObjectArray(exampleRootObject, "example"); + if (exampleObjects == null || exampleObjects.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (object exampleObj in exampleObjects) + { + PSObject example = exampleObj as PSObject; + if (example == null) + { + continue; + } + + string introductionText = null; + introductionText = GetTextFromArray(example, "introduction"); + + string codeText = GetPropertyString(example, "code"); + string title = GetPropertyString(example, "title"); + + if (codeText == null) + { + continue; + } + + if (title != null) + { + this.AddText(HelpParagraphBuilder.AddIndent(title), false); + this.AddText("\r\n", false); + } + + string codeLine = string.Format( + CultureInfo.CurrentCulture, + "{0}{1}\r\n\r\n", + introductionText, + codeText); + + this.AddText(HelpParagraphBuilder.AddIndent(codeLine), false); + + PSObject[] remarks = HelpParagraphBuilder.GetPropertyObject(example, "remarks") as PSObject[]; + if (remarks == null) + { + continue; + } + + foreach (PSObject remark in remarks) + { + string remarkText = GetPropertyString(remark, "text"); + if (remarkText == null) + { + continue; + } + + this.AddText(remarkText, false); + this.AddText("\r\n", false); + } + } + + this.AddText("\r\n\r\n", false); + } + + private void AddMembers(bool setting, string sectionTitle) + { + if (!setting || string.IsNullOrEmpty(sectionTitle)) + { + return; + } + + PSObject memberRootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, "Members") as PSObject; + if (memberRootObject == null) + { + return; + } + + object[] memberObjects = HelpParagraphBuilder.GetPropertyObjectArray(memberRootObject, "member"); + + if (memberObjects == null) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (object memberObj in memberObjects) + { + string description = null; + string memberText = null; + + PSObject member = memberObj as PSObject; + if (member == null) + { + continue; + } + + string name = GetPropertyString(member, "title"); + string type = GetPropertyString(member, "type"); + string propertyType = null; + + if (string.Equals("field", type, StringComparison.OrdinalIgnoreCase)) + { + PSObject fieldData = HelpParagraphBuilder.GetPropertyObject(member, "fieldData") as PSObject; + + if (fieldData != null) + { + PSObject propertyTypeObject = HelpParagraphBuilder.GetPropertyObject(fieldData, "type") as PSObject; + if (propertyTypeObject != null) + { + propertyType = GetPropertyString(propertyTypeObject, "name"); + description = GetPropertyString(propertyTypeObject, "description"); + } + + memberText = string.Create(CultureInfo.CurrentCulture, $" [{propertyType}] {name}\r\n"); + } + } + else if (string.Equals("method", type, StringComparison.OrdinalIgnoreCase)) + { + FormatMethodData(member, name, out memberText, out description); + } + + if (!string.IsNullOrEmpty(memberText)) + { + this.AddText(HelpParagraphBuilder.AddIndent(string.Empty), false); + this.AddText(memberText, true); + + if (description != null) + { + this.AddText(HelpParagraphBuilder.AddIndent(description, 2), false); + this.AddText("\r\n", false); + } + + this.AddText("\r\n", false); + } + } + } + + private static void FormatMethodData(PSObject member, string name, out string memberText, out string description) + { + memberText = null; + description = null; + + if (member == null || string.IsNullOrEmpty(name)) + { + return; + } + + string returnType = null; + StringBuilder parameterText = new StringBuilder(); + + // Get method return type + PSObject returnTypeObject = HelpParagraphBuilder.GetPropertyObject(member, "returnValue") as PSObject; + if (returnTypeObject != null) + { + PSObject returnTypeData = HelpParagraphBuilder.GetPropertyObject(returnTypeObject, "type") as PSObject; + if (returnTypeData != null) + { + returnType = GetPropertyString(returnTypeData, "name"); + } + } + + // Get method description. + PSObject[] methodDescriptions = HelpParagraphBuilder.GetPropertyObject(member, "introduction") as PSObject[]; + if (methodDescriptions != null) + { + foreach (var methodDescription in methodDescriptions) + { + description = GetPropertyString(methodDescription, "Text"); + + // If we get an text we do not need to iterate more. + if (!string.IsNullOrEmpty(description)) + { + break; + } + } + } + + // Get method parameters. + PSObject parametersObject = HelpParagraphBuilder.GetPropertyObject(member, "parameters") as PSObject; + if (parametersObject != null) + { + PSObject[] paramObject = HelpParagraphBuilder.GetPropertyObject(parametersObject, "parameter") as PSObject[]; + + if (paramObject != null) + { + foreach (var param in paramObject) + { + string parameterName = GetPropertyString(param, "name"); + string parameterType = null; + + PSObject parameterTypeData = HelpParagraphBuilder.GetPropertyObject(param, "type") as PSObject; + + if (parameterTypeData != null) + { + parameterType = GetPropertyString(parameterTypeData, "name"); + + // If there is no type for the parameter, we expect it is System.Object + if (string.IsNullOrEmpty(parameterType)) + { + parameterType = "object"; + } + } + + string paramString = string.Create(CultureInfo.CurrentCulture, $"[{parameterType}] ${parameterName},"); + + parameterText.Append(paramString); + } + + if (string.Equals(parameterText[parameterText.Length - 1].ToString(), ",", StringComparison.OrdinalIgnoreCase)) + { + parameterText = parameterText.Remove(parameterText.Length - 1, 1); + } + } + } + + memberText = string.Create(CultureInfo.CurrentCulture, $" [{returnType}] {name}({parameterText})\r\n"); + } + + /// + /// Adds the help parameters segment. + /// + /// True if it should add the segment. + /// Title of the section. + /// Name of the property which has properties. + /// Category of help. + private void AddParameters(bool setting, string sectionTitle, string paramPropertyName, HelpCategory helpCategory) + { + if (!setting) + { + return; + } + + PSObject parameterRootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, paramPropertyName) as PSObject; + if (parameterRootObject == null) + { + return; + } + + object[] parameterObjects = null; + + // Root object for Class has members not parameters. + if (helpCategory != HelpCategory.Class) + { + parameterObjects = HelpParagraphBuilder.GetPropertyObjectArray(parameterRootObject, "parameter"); + } + + if (parameterObjects == null || parameterObjects.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (object parameterObj in parameterObjects) + { + PSObject parameter = parameterObj as PSObject; + if (parameter == null) + { + continue; + } + + string parameterValue = GetPropertyString(parameter, "parameterValue"); + string name = GetPropertyString(parameter, "name"); + string description = GetTextFromArray(parameter, "description"); + string required = GetPropertyString(parameter, "required"); + string position = GetPropertyString(parameter, "position"); + string pipelineinput = GetPropertyString(parameter, "pipelineInput"); + string defaultValue = GetPropertyString(parameter, "defaultValue"); + string acceptWildcard = GetPropertyString(parameter, "globbing"); + + if (string.IsNullOrEmpty(name)) + { + continue; + } + + if (helpCategory == HelpCategory.DscResource) + { + this.AddText(HelpParagraphBuilder.AddIndent(string.Empty), false); + } + else + { + this.AddText(HelpParagraphBuilder.AddIndent("-"), false); + } + + this.AddText(name, true); + string parameterText = string.Format( + CultureInfo.CurrentCulture, + " <{0}>\r\n", + parameterValue); + + this.AddText(parameterText, false); + + if (description != null) + { + this.AddText(HelpParagraphBuilder.AddIndent(description, 2), false); + this.AddText("\r\n", false); + } + + this.AddText("\r\n", false); + + int largestSize = HelpParagraphBuilder.LargestSize( + HelpWindowResources.ParameterRequired, + HelpWindowResources.ParameterPosition, + HelpWindowResources.ParameterDefaultValue, + HelpWindowResources.ParameterPipelineInput, + HelpWindowResources.ParameterAcceptWildcard); + + // justification of parameter values is not localized + string formatString = string.Format( + CultureInfo.CurrentCulture, + "{{0,-{0}}}{{1}}", + largestSize + 2); + + string tableLine; + + tableLine = string.Format( + CultureInfo.CurrentCulture, + formatString, + HelpWindowResources.ParameterRequired, + required); + this.AddText(HelpParagraphBuilder.AddIndent(tableLine, 2), false); + this.AddText("\r\n", false); + + // these are not applicable for Dsc Resource help + if (helpCategory != HelpCategory.DscResource) + { + tableLine = string.Format( + CultureInfo.CurrentCulture, + formatString, + HelpWindowResources.ParameterPosition, + position); + this.AddText(HelpParagraphBuilder.AddIndent(tableLine, 2), false); + this.AddText("\r\n", false); + + tableLine = string.Format( + CultureInfo.CurrentCulture, + formatString, + HelpWindowResources.ParameterDefaultValue, + defaultValue); + this.AddText(HelpParagraphBuilder.AddIndent(tableLine, 2), false); + this.AddText("\r\n", false); + + tableLine = string.Format( + CultureInfo.CurrentCulture, + formatString, + HelpWindowResources.ParameterPipelineInput, + pipelineinput); + this.AddText(HelpParagraphBuilder.AddIndent(tableLine, 2), false); + this.AddText("\r\n", false); + + tableLine = string.Format( + CultureInfo.CurrentCulture, + formatString, + HelpWindowResources.ParameterAcceptWildcard, + acceptWildcard); + this.AddText(HelpParagraphBuilder.AddIndent(tableLine, 2), false); + } + + this.AddText("\r\n\r\n", false); + } + + this.AddText("\r\n\r\n", false); + } + + /// + /// Adds the help navigation links segment. + /// + /// True if it should add the segment. + /// Title of the section. + private void AddNavigationLink(bool setting, string sectionTitle) + { + if (!setting) + { + return; + } + + PSObject linkRootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, "RelatedLinks") as PSObject; + if (linkRootObject == null) + { + return; + } + + PSObject[] linkObjects; + + if ((linkObjects = HelpParagraphBuilder.GetPropertyObject(linkRootObject, "navigationLink") as PSObject[]) == null || + linkObjects.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (PSObject linkObject in linkObjects) + { + string text = GetPropertyString(linkObject, "linkText"); + string uri = GetPropertyString(linkObject, "uri"); + + string linkLine = string.IsNullOrEmpty(uri) ? text : string.Format( + CultureInfo.CurrentCulture, + HelpWindowResources.LinkTextFormat, + text, + uri); + + this.AddText(HelpParagraphBuilder.AddIndent(linkLine), false); + this.AddText("\r\n", false); + } + + this.AddText("\r\n\r\n", false); + } + + /// + /// Adds the help input or output segment. + /// + /// True if it should add the segment. + /// Title of the section. + /// Property with the outter object. + /// Property with the inner object. + private void AddInputOrOutputEntries(bool setting, string sectionTitle, string inputOrOutputProperty, string inputOrOutputInnerProperty) + { + if (!setting) + { + return; + } + + PSObject rootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, inputOrOutputProperty) as PSObject; + if (rootObject == null) + { + return; + } + + object[] inputOrOutputObjs; + inputOrOutputObjs = HelpParagraphBuilder.GetPropertyObjectArray(rootObject, inputOrOutputInnerProperty); + + if (inputOrOutputObjs == null || inputOrOutputObjs.Length == 0) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + + foreach (object inputOrOutputObj in inputOrOutputObjs) + { + PSObject inputOrOutput = inputOrOutputObj as PSObject; + if (inputOrOutput == null) + { + continue; + } + + string type = HelpParagraphBuilder.GetInnerPSObjectPropertyString(inputOrOutput, "type", "name"); + string description = GetTextFromArray(inputOrOutput, "description"); + + this.AddText(HelpParagraphBuilder.AddIndent(type), false); + this.AddText("\r\n", false); + if (description != null) + { + this.AddText(HelpParagraphBuilder.AddIndent(description), false); + this.AddText("\r\n", false); + } + } + + this.AddText("\r\n", false); + } + + /// + /// Adds the help notes segment. + /// + /// True if it should add the segment. + /// Title of the section. + private void AddNotes(bool setting, string sectionTitle) + { + if (!setting) + { + return; + } + + PSObject rootObject = HelpParagraphBuilder.GetPropertyObject(this.psObj, "alertSet") as PSObject; + if (rootObject == null) + { + return; + } + + string note = GetTextFromArray(rootObject, "alert"); + + if (note == null) + { + return; + } + + this.AddText(sectionTitle, true); + this.AddText("\r\n", false); + this.AddText(HelpParagraphBuilder.AddIndent(note), false); + this.AddText("\r\n\r\n", false); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs new file mode 100644 index 00000000000..7ab8681fef4 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpViewModel.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Management.Automation; +using System.Windows.Documents; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// ViewModel for the Help Dialog used to: + /// build the help document + /// search the help document + /// offer text for labels. + /// + internal class HelpViewModel : INotifyPropertyChanged + { + /// + /// The builder for the help FlowDocument Paragraph used in a RichEditText control. + /// + private readonly HelpParagraphBuilder helpBuilder; + + /// + /// Searcher for selecting current matches in paragraph text. + /// + private readonly ParagraphSearcher searcher; + + /// + /// Title of the help window. + /// + private readonly string helpTitle; + + /// + /// the zoom bound to the zoom slider value. + /// + private double zoom = 100; + + /// + /// Text to be found. This is bound to the find TextBox. + /// + private string findText; + + /// + /// text for the number of matches found. + /// + private string matchesLabel; + + /// + /// Initializes a new instance of the HelpViewModel class. + /// + /// Object containing help. + /// Paragraph in which help text is built/searched. + internal HelpViewModel(PSObject psObj, Paragraph documentParagraph) + { + Debug.Assert(psObj != null, "ensured by caller"); + Debug.Assert(documentParagraph != null, "ensured by caller"); + + this.helpBuilder = new HelpParagraphBuilder(documentParagraph, psObj); + this.helpBuilder.BuildParagraph(); + this.searcher = new ParagraphSearcher(); + this.helpBuilder.PropertyChanged += this.HelpBuilder_PropertyChanged; + this.helpTitle = string.Format( + CultureInfo.CurrentCulture, + HelpWindowResources.HelpTitleFormat, + HelpParagraphBuilder.GetPropertyString(psObj, "name")); + } + + #region INotifyPropertyChanged Members + /// + /// Used to notify of property changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + /// + /// Gets or sets the Zoom bound to the zoom slider value. + /// + public double Zoom + { + get + { + return this.zoom; + } + + set + { + this.zoom = value; + this.OnNotifyPropertyChanged("Zoom"); + this.OnNotifyPropertyChanged("ZoomLabel"); + this.OnNotifyPropertyChanged("ZoomLevel"); + } + } + + /// + /// Gets the value bound to the RichTextEdit zoom, which is calculated based on the zoom. + /// + public double ZoomLevel + { + get + { + return this.zoom / 100.0; + } + } + + /// + /// Gets the label to be displayed for the zoom. + /// + public string ZoomLabel + { + get + { + return string.Format(CultureInfo.CurrentCulture, HelpWindowResources.ZoomLabelTextFormat, this.zoom); + } + } + + /// + /// Gets or sets the text to be found. + /// + public string FindText + { + get + { + return this.findText; + } + + set + { + this.findText = value; + this.Search(); + this.SetMatchesLabel(); + } + } + + /// + /// Gets the title of the window. + /// + public string HelpTitle + { + get + { + return this.helpTitle; + } + } + + /// + /// Gets or sets the label for current matches. + /// + public string MatchesLabel + { + get + { + return this.matchesLabel; + } + + set + { + this.matchesLabel = value; + this.OnNotifyPropertyChanged("MatchesLabel"); + } + } + + /// + /// Gets a value indicating whether there are matches to go to. + /// + public bool CanGoToNextOrPrevious + { + get + { + return this.HelpBuilder.HighlightCount != 0; + } + } + + /// + /// Gets the searcher for selecting current matches in paragraph text. + /// + internal ParagraphSearcher Searcher + { + get { return this.searcher; } + } + + /// + /// Gets the paragraph builder used to write help content. + /// + internal HelpParagraphBuilder HelpBuilder + { + get { return this.helpBuilder; } + } + + /// + /// Highlights all matches to this.findText + /// Called when findText changes or whenever the search has to be refreshed + /// + internal void Search() + { + this.HelpBuilder.HighlightAllInstancesOf(this.findText, HelpWindowSettings.Default.HelpSearchMatchCase, HelpWindowSettings.Default.HelpSearchWholeWord); + this.searcher.ResetSearch(); + } + + /// + /// Increases Zoom if not above maximum. + /// + internal void ZoomIn() + { + if (this.Zoom + HelpWindow.ZoomInterval <= HelpWindow.MaximumZoom) + { + this.Zoom += HelpWindow.ZoomInterval; + } + } + + /// + /// Decreases Zoom if not below minimum. + /// + internal void ZoomOut() + { + if (this.Zoom - HelpWindow.ZoomInterval >= HelpWindow.MinimumZoom) + { + this.Zoom -= HelpWindow.ZoomInterval; + } + } + + /// + /// Called to update the matches label. + /// + /// Event sender. + /// Event arguments. + private void HelpBuilder_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "HighlightCount") + { + this.SetMatchesLabel(); + this.OnNotifyPropertyChanged("CanGoToNextOrPrevious"); + } + } + + /// + /// Sets the current matches label. + /// + private void SetMatchesLabel() + { + if (this.findText == null || this.findText.Trim().Length == 0) + { + this.MatchesLabel = string.Empty; + } + else + { + if (this.HelpBuilder.HighlightCount == 0) + { + this.MatchesLabel = HelpWindowResources.NoMatches; + } + else + { + if (this.HelpBuilder.HighlightCount == 1) + { + this.MatchesLabel = HelpWindowResources.OneMatch; + } + else + { + this.MatchesLabel = string.Format( + CultureInfo.CurrentCulture, + HelpWindowResources.SomeMatchesFormat, + this.HelpBuilder.HighlightCount); + } + } + } + } + + /// + /// Called internally to notify when a property changed. + /// + /// Property name. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml new file mode 100644 index 00000000000..2b2a8fe60b1 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml.cs new file mode 100644 index 00000000000..6793806503a --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindow.xaml.cs @@ -0,0 +1,318 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; +using System.Management.Automation; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; + +using Microsoft.Management.UI.Internal; + +namespace Microsoft.Management.UI +{ + /// + /// A window displaying help content and allowing search. + /// + public partial class HelpWindow : Window + { + /// + /// Minimum zoom in the slider. + /// + public static double MinimumZoom + { + get + { + return 20; + } + } + + /// + /// Maximum zoom in the slider. + /// + public static double MaximumZoom + { + get + { + return 300; + } + } + + /// + /// Zoom interval. + /// + public static double ZoomInterval + { + get + { + return 10; + } + } + + /// + /// The ViewModel for the dialog. + /// + private readonly HelpViewModel viewModel; + + /// + /// Initializes a new instance of the HelpWindow class. + /// + /// The object with help information. + public HelpWindow(PSObject helpObject) + { + InitializeComponent(); + this.viewModel = new HelpViewModel(helpObject, this.DocumentParagraph); + CommonHelper.SetStartingPositionAndSize( + this, + HelpWindowSettings.Default.HelpWindowTop, + HelpWindowSettings.Default.HelpWindowLeft, + HelpWindowSettings.Default.HelpWindowWidth, + HelpWindowSettings.Default.HelpWindowHeight, + double.Parse((string)HelpWindowSettings.Default.Properties["HelpWindowWidth"].DefaultValue, CultureInfo.InvariantCulture.NumberFormat), + double.Parse((string)HelpWindowSettings.Default.Properties["HelpWindowHeight"].DefaultValue, CultureInfo.InvariantCulture.NumberFormat), + HelpWindowSettings.Default.HelpWindowMaximized); + + this.ReadZoomUserSetting(); + + this.viewModel.PropertyChanged += this.ViewModel_PropertyChanged; + this.DataContext = this.viewModel; + + this.Loaded += this.HelpDialog_Loaded; + this.Closed += this.HelpDialog_Closed; + } + + /// + /// Handles the mouse wheel to zoom in/out. + /// + /// Event arguments. + protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) + { + if (Keyboard.Modifiers != ModifierKeys.Control) + { + return; + } + + if (e.Delta > 0) + { + this.viewModel.ZoomIn(); + e.Handled = true; + } + else + { + this.viewModel.ZoomOut(); + e.Handled = true; + } + } + + /// + /// Handles key down to fix the Page/Douwn going to end of help issue + /// And to implement some additional shortcuts like Ctrl+F and ZoomIn/ZoomOut. + /// + /// Event arguments. + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + if (Keyboard.Modifiers == ModifierKeys.None) + { + if (e.Key == Key.PageUp) + { + this.Scroll.PageUp(); + e.Handled = true; + return; + } + + if (e.Key == Key.PageDown) + { + this.Scroll.PageDown(); + e.Handled = true; + return; + } + } + + if (Keyboard.Modifiers == ModifierKeys.Control) + { + this.HandleZoomInAndZoomOut(e); + if (e.Handled) + { + return; + } + + if (e.Key == Key.F) + { + this.Find.Focus(); + e.Handled = true; + return; + } + } + + if (Keyboard.Modifiers == (ModifierKeys.Control | ModifierKeys.Shift)) + { + this.HandleZoomInAndZoomOut(e); + } + } + + /// + /// Reads the zoom part of the user settings. + /// + private void ReadZoomUserSetting() + { + if (HelpWindowSettings.Default.HelpZoom < HelpWindow.MinimumZoom || HelpWindowSettings.Default.HelpZoom > HelpWindow.MaximumZoom) + { + HelpWindowSettings.Default.HelpZoom = 100; + } + + this.viewModel.Zoom = HelpWindowSettings.Default.HelpZoom; + } + + /// + /// Handles Zoom in and Zoom out keys. + /// + /// Event arguments. + private void HandleZoomInAndZoomOut(KeyEventArgs e) + { + if (e.Key == Key.OemPlus || e.Key == Key.Add) + { + this.viewModel.ZoomIn(); + e.Handled = true; + } + + if (e.Key == Key.OemMinus || e.Key == Key.Subtract) + { + this.viewModel.ZoomOut(); + e.Handled = true; + } + } + + /// + /// Listens to changes in the zoom in order to update the user settings. + /// + /// Event sender. + /// Event arguments. + private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == "Zoom") + { + HelpWindowSettings.Default.HelpZoom = this.viewModel.Zoom; + } + } + + /// + /// Saves the user settings. + /// + /// Event sender. + /// Event arguments. + private void HelpDialog_Closed(object sender, System.EventArgs e) + { + HelpWindowSettings.Default.Save(); + } + + /// + /// Updates the user setting with window state. + /// + /// Event sender. + /// Event arguments. + private void HelpDialog_StateChanged(object sender, System.EventArgs e) + { + HelpWindowSettings.Default.HelpWindowMaximized = this.WindowState == WindowState.Maximized; + } + + /// + /// Sets the positions from user settings and start monitoring position changes. + /// + /// Event sender. + /// Event arguments. + private void HelpDialog_Loaded(object sender, RoutedEventArgs e) + { + this.StateChanged += this.HelpDialog_StateChanged; + this.LocationChanged += this.HelpDialog_LocationChanged; + this.SizeChanged += this.HelpDialog_SizeChanged; + } + + /// + /// Saves size changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void HelpDialog_SizeChanged(object sender, SizeChangedEventArgs e) + { + HelpWindowSettings.Default.HelpWindowWidth = this.Width; + HelpWindowSettings.Default.HelpWindowHeight = this.Height; + } + + /// + /// Saves position changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void HelpDialog_LocationChanged(object sender, System.EventArgs e) + { + HelpWindowSettings.Default.HelpWindowTop = this.Top; + HelpWindowSettings.Default.HelpWindowLeft = this.Left; + } + + /// + /// Called when the settings button is clicked. + /// + /// Event sender. + /// Event arguments. + private void Settings_Click(object sender, RoutedEventArgs e) + { + SettingsDialog settings = new SettingsDialog(); + settings.Owner = this; + + settings.ShowDialog(); + + if (settings.DialogResult == true) + { + this.viewModel.HelpBuilder.AddTextToParagraphBuilder(); + this.viewModel.Search(); + } + } + + /// + /// Called when the Previous button is clicked. + /// + /// Event sender. + /// Event arguments. + private void PreviousMatch_Click(object sender, RoutedEventArgs e) + { + this.MoveToNextMatch(false); + } + + /// + /// Called when the Next button is clicked. + /// + /// Event sender. + /// Event arguments. + private void NextMatch_Click(object sender, RoutedEventArgs e) + { + this.MoveToNextMatch(true); + } + + /// + /// Moves to the previous or next match. + /// + /// True for forward false for backwards. + private void MoveToNextMatch(bool forward) + { + TextPointer caretPosition = this.HelpText.CaretPosition; + Run nextRun = this.viewModel.Searcher.MoveAndHighlightNextNextMatch(forward, caretPosition); + this.MoveToRun(nextRun); + } + + /// + /// Moves to the caret and brings the view to the . + /// + /// Run to move to. + private void MoveToRun(Run run) + { + if (run == null) + { + return; + } + + run.BringIntoView(); + this.HelpText.CaretPosition = run.ElementEnd; + this.HelpText.Focus(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.Designer.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.Designer.cs new file mode 100644 index 00000000000..937429ebf09 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.Designer.cs @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.16598 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// namespace Microsoft.Management.UI.Internal { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class HelpWindowSettings : global::System.Configuration.ApplicationSettingsBase { + + private static HelpWindowSettings defaultInstance = ((HelpWindowSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new HelpWindowSettings()))); + + public static HelpWindowSettings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpRemarksDisplayed + { + get + { + return ((bool)(this["HelpRemarksDisplayed"])); + } + set + { + this["HelpRemarksDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpSyntaxDisplayed { + get { + return ((bool)(this["HelpSyntaxDisplayed"])); + } + set { + this["HelpSyntaxDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpExamplesDisplayed { + get { + return ((bool)(this["HelpExamplesDisplayed"])); + } + set { + this["HelpExamplesDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpSynopsysDisplayed { + get { + return ((bool)(this["HelpSynopsysDisplayed"])); + } + set { + this["HelpSynopsysDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpDescriptionDisplayed { + get { + return ((bool)(this["HelpDescriptionDisplayed"])); + } + set { + this["HelpDescriptionDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpParametersDisplayed { + get { + return ((bool)(this["HelpParametersDisplayed"])); + } + set { + this["HelpParametersDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpInputsDisplayed { + get { + return ((bool)(this["HelpInputsDisplayed"])); + } + set { + this["HelpInputsDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpOutputsDisplayed { + get { + return ((bool)(this["HelpOutputsDisplayed"])); + } + set { + this["HelpOutputsDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpNotesDisplayed { + get { + return ((bool)(this["HelpNotesDisplayed"])); + } + set { + this["HelpNotesDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool HelpRelatedLinksDisplayed { + get { + return ((bool)(this["HelpRelatedLinksDisplayed"])); + } + set { + this["HelpRelatedLinksDisplayed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool HelpSearchMatchCase { + get { + return ((bool)(this["HelpSearchMatchCase"])); + } + set { + this["HelpSearchMatchCase"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool HelpSearchWholeWord { + get { + return ((bool)(this["HelpSearchWholeWord"])); + } + set { + this["HelpSearchWholeWord"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("400")] + public double HelpWindowHeight { + get { + return ((double)(this["HelpWindowHeight"])); + } + set { + this["HelpWindowHeight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("600")] + public double HelpWindowWidth { + get { + return ((double)(this["HelpWindowWidth"])); + } + set { + this["HelpWindowWidth"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double HelpWindowTop { + get { + return ((double)(this["HelpWindowTop"])); + } + set { + this["HelpWindowTop"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double HelpWindowLeft { + get { + return ((double)(this["HelpWindowLeft"])); + } + set { + this["HelpWindowLeft"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool HelpWindowMaximized { + get { + return ((bool)(this["HelpWindowMaximized"])); + } + set { + this["HelpWindowMaximized"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("100")] + public double HelpZoom { + get { + return ((double)(this["HelpZoom"])); + } + set { + this["HelpZoom"] = value; + } + } + } +//} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.settings b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.settings new file mode 100644 index 00000000000..2631ff7c38a --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/HelpWindowSettings.settings @@ -0,0 +1,60 @@ + + + + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + False + + + False + + + -1 + + + -1 + + + False + + + 100 + + + 500 + + + 700 + + + True + + + diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs new file mode 100644 index 00000000000..77b1b5c966f --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphBuilder.cs @@ -0,0 +1,357 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Builds a paragraph based on Text + Bold + Highlight information. + /// 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 + { + /// + /// The text spans that should be bold. + /// + private readonly List boldSpans; + + /// + /// The text spans that should be highlighted. + /// + private readonly List highlightedSpans; + + /// + /// The text displayed. + /// + private readonly StringBuilder textBuilder; + + /// + /// Paragraph built in BuildParagraph. + /// + private readonly Paragraph paragraph; + + /// + /// Initializes a new instance of the ParagraphBuilder class. + /// + /// Paragraph we will be adding lines to in BuildParagraph. + internal ParagraphBuilder(Paragraph paragraph) + { + ArgumentNullException.ThrowIfNull(paragraph); + + this.paragraph = paragraph; + this.boldSpans = new List(); + this.highlightedSpans = new List(); + this.textBuilder = new StringBuilder(); + } + + #region INotifyPropertyChanged Members + /// + /// Used to notify of property changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + /// + /// Gets the number of highlights. + /// + internal int HighlightCount + { + get { return this.highlightedSpans.Count; } + } + + /// + /// Gets the paragraph built in BuildParagraph. + /// + internal Paragraph Paragraph + { + get { return this.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 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 highlighted are. + /// + internal void BuildParagraph() + { + this.paragraph.Inlines.Clear(); + + int currentBoldIndex = 0; + TextSpan? currentBoldSpan = this.boldSpans.Count == 0 ? (TextSpan?)null : this.boldSpans[0]; + int currentHighlightedIndex = 0; + TextSpan? currentHighlightedSpan = this.highlightedSpans.Count == 0 ? (TextSpan?)null : this.highlightedSpans[0]; + + bool currentBold = false; + bool currentHighlighted = false; + + StringBuilder sequence = new StringBuilder(); + int i = 0; + foreach (char c in this.textBuilder.ToString()) + { + bool newBold = false; + bool newHighlighted = false; + + ParagraphBuilder.MoveSpanToPosition(ref currentBoldIndex, ref currentBoldSpan, i, this.boldSpans); + newBold = currentBoldSpan == null ? false : currentBoldSpan.Value.Contains(i); + + ParagraphBuilder.MoveSpanToPosition(ref currentHighlightedIndex, ref currentHighlightedSpan, i, this.highlightedSpans); + newHighlighted = currentHighlightedSpan == null ? false : currentHighlightedSpan.Value.Contains(i); + + if (newBold != currentBold || newHighlighted != currentHighlighted) + { + ParagraphBuilder.AddInline(this.paragraph, currentBold, currentHighlighted, sequence); + } + + sequence.Append(c); + + currentHighlighted = newHighlighted; + currentBold = newBold; + i++; + } + + ParagraphBuilder.AddInline(this.paragraph, currentBold, currentHighlighted, sequence); + } + + /// + /// Highlights all occurrences of . + /// This is called after all calls to AddText have been made. + /// + /// Search string. + /// True if search should be case sensitive. + /// True if we should search whole word only. + internal void HighlightAllInstancesOf(string search, bool caseSensitive, bool wholeWord) + { + this.highlightedSpans.Clear(); + + if (search == null || search.Trim().Length == 0) + { + this.BuildParagraph(); + this.OnNotifyPropertyChanged("HighlightCount"); + return; + } + + string text = this.textBuilder.ToString(); + StringComparison comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + int start = 0; + int match; + while ((match = text.IndexOf(search, start, comparison)) != -1) + { + // false loop + do + { + if (wholeWord) + { + if (match > 0 && char.IsLetterOrDigit(text[match - 1])) + { + break; + } + + if ((match + search.Length <= text.Length - 1) && char.IsLetterOrDigit(text[match + search.Length])) + { + break; + } + } + + this.AddHighlight(match, search.Length); + } + while (false); + + start = match + search.Length; + } + + this.BuildParagraph(); + this.OnNotifyPropertyChanged("HighlightCount"); + } + + /// + /// Adds text to the paragraph later build with BuildParagraph. + /// + /// Text to be added. + /// True if the text should be bold. + internal void AddText(string str, bool bold) + { + ArgumentNullException.ThrowIfNull(str); + + if (str.Length == 0) + { + return; + } + + if (bold) + { + this.boldSpans.Add(new TextSpan(this.textBuilder.Length, str.Length)); + } + + this.textBuilder.Append(str); + } + + /// + /// Called before a derived class starts adding text + /// to reset the current content. + /// + internal void ResetAllText() + { + this.boldSpans.Clear(); + this.highlightedSpans.Clear(); + this.textBuilder.Clear(); + } + + /// + /// Adds an inline to based on the remaining parameters. + /// + /// Paragraph to add Inline to. + /// True if text should be added in bold. + /// True if the text should be added with highlight. + /// The text to add and clear. + private static void AddInline(Paragraph currentParagraph, bool currentBold, bool currentHighlighted, StringBuilder sequence) + { + if (sequence.Length == 0) + { + return; + } + + Run run = new Run(sequence.ToString()); + if (currentHighlighted) + { + run.Background = ParagraphSearcher.HighlightBrush; + } + + Inline inline = currentBold ? (Inline)new Bold(run) : run; + currentParagraph.Inlines.Add(inline); + sequence.Clear(); + } + + /// + /// This is an auxiliar method in BuildParagraph to move the current bold or highlighted spans + /// according to the + /// The current bold and highlighted span should be ending ahead of the current position. + /// Moves and to the + /// proper span in according to the + /// This is an auxiliar method in BuildParagraph. + /// + /// Current index within . + /// Current span within . + /// 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) + { + if (currentSpan == null || caracterPosition <= currentSpan.Value.End) + { + return; + } + + for (int newBoldIndex = currentSpanIndex + 1; newBoldIndex < allSpans.Count; newBoldIndex++) + { + TextSpan newBoldSpan = allSpans[newBoldIndex]; + if (caracterPosition <= newBoldSpan.End) + { + currentSpanIndex = newBoldIndex; + currentSpan = newBoldSpan; + return; + } + } + + // there is no span ending ahead of current position, so + // we set the current span to null to prevent unnecessary comparisons against the currentSpan + currentSpan = null; + } + + /// + /// Adds one individual text highlight + /// This is called after all calls to AddText have been made. + /// + /// Highlight start. + /// Highlight length. + private void AddHighlight(int start, int 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 property changed. + /// + /// Property name. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// A text span used to mark bold and highlighted segments. + /// + internal struct TextSpan + { + /// + /// Index of the first character in the span. + /// + private readonly int start; + + /// + /// Index of the last character in the span. + /// + private readonly int end; + + /// + /// Initializes a new instance of the TextSpan struct. + /// + /// Index of the first character in the span. + /// Index of the last character in the span. + internal TextSpan(int start, int length) + { + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfLessThan(length, 1); + + this.start = start; + this.end = start + length - 1; + } + + /// + /// Gets the index of the first character in the span. + /// + internal int Start + { + get { return this.start; } + } + + /// + /// Gets the index of the first character in the span. + /// + internal int End + { + get + { + return this.end; + } + } + + /// + /// Returns true if the is between start and end (inclusive). + /// + /// Position to verify if is in the span. + /// True if the is between start and end (inclusive). + internal bool Contains(int position) + { + return (position >= this.start) && (position <= this.end); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs new file mode 100644 index 00000000000..c8b9907751d --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/ParagraphSearcher.cs @@ -0,0 +1,241 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.Windows.Documents; +using System.Windows.Media; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Moves through search highlights built in a ParagraphBuilder + /// changing the color of the current highlight. + /// + internal class ParagraphSearcher + { + /// + /// Highlight for all matches except the current. + /// + internal static readonly Brush HighlightBrush = Brushes.Yellow; + + /// + /// Highlight for the current match. + /// + private static readonly Brush CurrentHighlightBrush = Brushes.Cyan; + + /// + /// Current match being highlighted in search. + /// + private Run currentHighlightedMatch; + + /// + /// Initializes a new instance of the ParagraphSearcher class. + /// + internal ParagraphSearcher() + { + } + + /// + /// Move to the next highlight starting at the . + /// + /// True for next false for previous. + /// Caret position. + /// The next highlight starting at the . + internal Run MoveAndHighlightNextNextMatch(bool forward, TextPointer caretPosition) + { + 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; + + if (this.currentHighlightedMatch != null) + { + // restore the curent highlighted background to plain highlighted + this.currentHighlightedMatch.Background = ParagraphSearcher.HighlightBrush; + } + + // 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 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 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 + // and the caret is at the beginning will wrap to the end + if ((!forward && IsFirstRun(caretRun)) || + ((caretPosition.GetOffsetToPosition(caretRun.ContentEnd) == 0) && ParagraphSearcher.Ishighlighted(caretRun))) + { + currentRun = ParagraphSearcher.GetNextRun(caretRun, forward); + } + else + { + currentRun = caretRun; + } + + currentRun = ParagraphSearcher.GetNextMatch(currentRun, forward); + + if (currentRun == null) + { + // if we could not find a next highlight wraparound + currentRun = ParagraphSearcher.GetFirstOrLastRun(caretRun, forward); + currentRun = ParagraphSearcher.GetNextMatch(currentRun, forward); + } + + this.currentHighlightedMatch = currentRun; + if (this.currentHighlightedMatch != null) + { + // restore the curent highlighted background to current highlighted + this.currentHighlightedMatch.Background = ParagraphSearcher.CurrentHighlightBrush; + } + + return currentRun; + } + + /// + /// Resets the search for fresh calls to MoveAndHighlightNextNextMatch. + /// + internal void ResetSearch() + { + this.currentHighlightedMatch = null; + } + + /// + /// Returns true if is highlighted. + /// + /// Run to check if is highlighted. + /// True if is highlighted. + private static bool Ishighlighted(Run run) + { + if (run == null) + { + return false; + } + + SolidColorBrush background = run.Background as SolidColorBrush; + if (background != null && background == ParagraphSearcher.HighlightBrush) + { + return true; + } + + return false; + } + + /// + /// Get the next or previous run according to . + /// + /// The current run. + /// True for next false for previous. + /// The next or previous run according to . + private static Run GetNextRun(Run currentRun, bool forward) + { + Bold parentBold = currentRun.Parent as Bold; + + Inline nextInline; + + if (forward) + { + nextInline = parentBold != null ? ((Inline)parentBold).NextInline : currentRun.NextInline; + } + else + { + nextInline = parentBold != null ? ((Inline)parentBold).PreviousInline : currentRun.PreviousInline; + } + + return GetRun(nextInline); + } + + /// + /// Gets the run of an inline. Inlines in a ParagrahBuilder are either a Run or a Bold + /// which contains a Run. + /// + /// Inline to get the run from. + /// The run of the inline. + private static Run GetRun(Inline inline) + { + Bold inlineBold = inline as Bold; + if (inlineBold != null) + { + return (Run)inlineBold.Inlines.FirstInline; + } + + return (Run)inline; + } + + /// + /// Gets the next highlighted run starting and including + /// according to the direction specified in . + /// + /// The current run. + /// True for next false for previous. + /// + /// the next highlighted run starting and including + /// according to the direction specified in . + /// + private static Run GetNextMatch(Run currentRun, bool forward) + { + while (currentRun != null) + { + if (ParagraphSearcher.Ishighlighted(currentRun)) + { + return currentRun; + } + + currentRun = ParagraphSearcher.GetNextRun(currentRun, forward); + } + + return currentRun; + } + + /// + /// Gets the run's paragraph. + /// + /// Run to get the paragraph from. + /// The run's paragraph. + private static Paragraph GetParagraph(Run run) + { + Bold parentBold = run.Parent as Bold; + Paragraph parentParagraph = (parentBold != null ? parentBold.Parent : run.Parent) as Paragraph; + Debug.Assert(parentParagraph != null, "the documents we are saerching are built with ParagraphBuilder, which builds the document like this"); + return parentParagraph; + } + + /// + /// Returns true if the run is the first run of the paragraph. + /// + /// Run to check. + /// True if the run is the first run of the paragraph. + private static bool IsFirstRun(Run run) + { + Paragraph paragraph = GetParagraph(run); + Run firstRun = ParagraphSearcher.GetRun(paragraph.Inlines.FirstInline); + return run == firstRun; + } + + /// + /// Gets the first or lasr run in the paragraph containing . + /// + /// Run containing the caret. + /// True for first false for last. + /// 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 always valid"); + + Paragraph paragraph = GetParagraph(caretRun); + + Inline firstOrLastInline; + if (forward) + { + firstOrLastInline = paragraph.Inlines.FirstInline; + } + else + { + firstOrLastInline = paragraph.Inlines.LastInline; + } + + return GetRun(firstOrLastInline); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml b/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml new file mode 100644 index 00000000000..d99c86c2a6a --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml.cs b/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml.cs new file mode 100644 index 00000000000..5d499d47f71 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/HelpWindow/SettingsDialog.xaml.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.UI +{ + using System.Windows; + using Microsoft.Management.UI.Internal; + + /// + /// Dialog with settings for the help dialog. + /// + public partial class SettingsDialog : Window + { + /// + /// Initializes a new instance of the SettingsDialog class. + /// + public SettingsDialog() + { + InitializeComponent(); + this.Description.IsChecked = HelpWindowSettings.Default.HelpDescriptionDisplayed; + this.Examples.IsChecked = HelpWindowSettings.Default.HelpExamplesDisplayed; + this.Inputs.IsChecked = HelpWindowSettings.Default.HelpInputsDisplayed; + this.Notes.IsChecked = HelpWindowSettings.Default.HelpNotesDisplayed; + this.Outputs.IsChecked = HelpWindowSettings.Default.HelpOutputsDisplayed; + this.Parameters.IsChecked = HelpWindowSettings.Default.HelpParametersDisplayed; + this.RelatedLinks.IsChecked = HelpWindowSettings.Default.HelpRelatedLinksDisplayed; + this.Remarks.IsChecked = HelpWindowSettings.Default.HelpRemarksDisplayed; + this.Synopsys.IsChecked = HelpWindowSettings.Default.HelpSynopsysDisplayed; + this.Syntax.IsChecked = HelpWindowSettings.Default.HelpSyntaxDisplayed; + this.CaseSensitive.IsChecked = HelpWindowSettings.Default.HelpSearchMatchCase; + this.WholeWord.IsChecked = HelpWindowSettings.Default.HelpSearchWholeWord; + } + + /// + /// Called when the OK button has been clicked. + /// + /// Event sender. + /// Event arguments. + private void OK_Click(object sender, RoutedEventArgs e) + { + HelpWindowSettings.Default.HelpDescriptionDisplayed = this.Description.IsChecked == true; + HelpWindowSettings.Default.HelpExamplesDisplayed = this.Examples.IsChecked == true; + HelpWindowSettings.Default.HelpInputsDisplayed = this.Inputs.IsChecked == true; + HelpWindowSettings.Default.HelpOutputsDisplayed = this.Outputs.IsChecked == true; + HelpWindowSettings.Default.HelpNotesDisplayed = this.Notes.IsChecked == true; + HelpWindowSettings.Default.HelpParametersDisplayed = this.Parameters.IsChecked == true; + HelpWindowSettings.Default.HelpRelatedLinksDisplayed = this.RelatedLinks.IsChecked == true; + HelpWindowSettings.Default.HelpRemarksDisplayed = this.Remarks.IsChecked == true; + HelpWindowSettings.Default.HelpSynopsysDisplayed = this.Synopsys.IsChecked == true; + HelpWindowSettings.Default.HelpSyntaxDisplayed = this.Syntax.IsChecked == true; + HelpWindowSettings.Default.HelpSearchMatchCase = this.CaseSensitive.IsChecked == true; + HelpWindowSettings.Default.HelpSearchWholeWord = this.WholeWord.IsChecked == true; + HelpWindowSettings.Default.Save(); + this.DialogResult = true; + this.Close(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationButton.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationButton.cs new file mode 100644 index 00000000000..40f3e9e15a9 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationButton.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Provides a control that is always visible in the automation tree. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + [Description("Provides a System.Windows.Controls.Button control that is always visible in the automation tree.")] + public class AutomationButton : Button + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public AutomationButton() + { + // This constructor intentionally left blank + } + + #endregion + + #region Overides + + /// + /// Returns the implementations for this control. + /// + /// The implementations for this control. + protected override AutomationPeer OnCreateAutomationPeer() + { + return new AutomationButtonAutomationPeer(this); + } + + #endregion + } + + /// + /// Provides an automation peer for AutomationButton. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + internal class AutomationButtonAutomationPeer : ButtonAutomationPeer + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + public AutomationButtonAutomationPeer(Button owner) + : base(owner) + { + // This constructor intentionally left blank + } + + #endregion + + #region Overrides + + /// + /// Gets a value that indicates whether the element is understood by the user as interactive or as contributing to the logical structure of the control in the GUI. Called by IsControlElement(). + /// + /// This method always returns false. + protected override bool IsControlElementCore() + { + return this.Owner.Visibility != Visibility.Hidden; + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationImage.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationImage.cs new file mode 100644 index 00000000000..1c0690a43c9 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationImage.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Provides a control that is always visible in the automation tree. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + [Description("Provides a System.Windows.Controls.Image control that is always visible in the automation tree.")] + public class AutomationImage : Image + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public AutomationImage() + { + // This constructor intentionally left blank + } + + #endregion + + #region Overides + + /// + /// Returns the implementations for this control. + /// + /// The implementations for this control. + protected override AutomationPeer OnCreateAutomationPeer() + { + return new AutomationImageAutomationPeer(this); + } + + #endregion + } + + /// + /// Provides an automation peer for AutomationImage. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + internal class AutomationImageAutomationPeer : ImageAutomationPeer + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + public AutomationImageAutomationPeer(Image owner) + : base(owner) + { + // This constructor intentionally left blank + } + + #endregion + + #region Overrides + + /// + /// Gets a value that indicates whether the element is understood by the user as interactive or as contributing to the logical structure of the control in the GUI. Called by IsControlElement(). + /// + /// This method always returns false. + protected override bool IsControlElementCore() + { + return false; + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlock.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlock.cs new file mode 100644 index 00000000000..50db1bb78c1 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlock.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Provides a control that is always visible in the automation tree. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + [Description("Provides a System.Windows.Controls.TextBlock control that is always visible in the automation tree.")] + public class AutomationTextBlock : TextBlock + { + #region Structors + + /// + /// Initializes a new instance of the class. + /// + public AutomationTextBlock() + { + // This constructor intentionally left blank + } + + #endregion + + #region Overides + + /// + /// Returns the implementations for this control. + /// + /// The implementations for this control. + protected override AutomationPeer OnCreateAutomationPeer() + { + return new AutomationTextBlockAutomationPeer(this); + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlockAutomationPeer.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlockAutomationPeer.cs new file mode 100644 index 00000000000..6de73299abb --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/AutomationTextBlockAutomationPeer.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Provides an automation peer for AutomationTextBlock. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + internal class AutomationTextBlockAutomationPeer : TextBlockAutomationPeer + { + #region Structors + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + public AutomationTextBlockAutomationPeer(TextBlock owner) + : base(owner) + { + // This constructor intentionally left blank + } + + #endregion + + #region Overrides + + /// + /// Gets a value that indicates whether the element is understood by the user as interactive or as contributing to the logical structure of the control in the GUI. Called by IsControlElement(). + /// + /// This method always returns true. + protected override bool IsControlElementCore() + { + return true; + } + + /// + /// Gets the class name. + /// + /// The class name. + protected override string GetClassNameCore() + { + return this.Owner.GetType().Name; + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/BooleanBoxes.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/BooleanBoxes.cs new file mode 100644 index 00000000000..92d50d5d007 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/BooleanBoxes.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.UI.Internal +{ + /// + /// A class which returns the same boxed bool values. + /// + internal static class BooleanBoxes + { + private static object trueBox = true; + private static object falseBox = false; + + internal static object TrueBox + { + get + { + return trueBox; + } + } + + internal static object FalseBox + { + get + { + return falseBox; + } + } + + internal static object Box(bool value) + { + if (value) + { + return TrueBox; + } + else + { + return FalseBox; + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/CommandHelper.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/CommandHelper.cs new file mode 100644 index 00000000000..c9c8606f899 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/CommandHelper.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Security; +using System.Text; +using System.Windows; +using System.Windows.Input; + +namespace Microsoft.Management.UI.Internal +{ + internal static class CommandHelper + { + internal static void ExecuteCommand(ICommand command, object parameter, IInputElement target) + { + RoutedCommand command2 = command as RoutedCommand; + if (command2 != null) + { + if (command2.CanExecute(parameter, target)) + { + command2.Execute(parameter, target); + } + } + else if (command.CanExecute(parameter)) + { + command.Execute(parameter); + } + } + + internal static bool CanExecuteCommand(ICommand command, object parameter, IInputElement target) + { + if (command == null) + { + return false; + } + + RoutedCommand command2 = command as RoutedCommand; + + if (command2 != null) + { + return command2.CanExecute(parameter, target); + } + else + { + return command.CanExecute(parameter); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/CustomTypeComparer.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/CustomTypeComparer.cs new file mode 100644 index 00000000000..6b0db6d5f95 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/CustomTypeComparer.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// The CustomTypeComparer is responsible for holding custom comparers + /// for different types, which are in turn used to perform comparison + /// operations instead of the default IComparable comparison. + /// with a custom comparer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public static class CustomTypeComparer + { + private static Dictionary comparers = new Dictionary(); + + /// + /// The static constructor. + /// + static CustomTypeComparer() + { + comparers.Add(typeof(DateTime), new DateTimeApproximationComparer()); + } + + /// + /// Compares two objects and returns a value indicating + /// whether one is less than, equal to, or greater than the other. + /// + /// + /// The first object to compare. + /// + /// + /// The second object to compare. + /// + /// + /// A type implementing IComparable. + /// + /// + /// If value1 is less than value2, then a value less than zero is returned. + /// If value1 equals value2, than zero is returned. + /// If value1 is greater than value2, then a value greater than zero is returned. + /// + public static int Compare(T value1, T value2) where T : IComparable + { + IComparer comparer; + if (TryGetCustomComparer(out comparer) == false) + { + return value1.CompareTo(value2); + } + + return comparer.Compare(value1, value2); + } + + private static bool TryGetCustomComparer(out IComparer comparer) where T : IComparable + { + comparer = null; + + object uncastComparer = null; + if (comparers.TryGetValue(typeof(T), out uncastComparer) == false) + { + return false; + } + + Debug.Assert(uncastComparer is IComparer, "must be IComparer"); + comparer = (IComparer)uncastComparer; + + return true; + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs new file mode 100644 index 00000000000..f75555b1da4 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DataRoutedEventArgs.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Windows; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Routed event args which provide the ability to attach an + /// arbitrary piece of data. + /// + /// There are no restrictions on type T. + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class DataRoutedEventArgs : RoutedEventArgs + { + private T data; + + /// + /// Constructs a new instance of the DataRoutedEventArgs class. + /// + /// The data payload to be stored. + /// The routed event. + public DataRoutedEventArgs(T data, RoutedEvent routedEvent) + { + this.data = data; + this.RoutedEvent = routedEvent; + } + + /// + /// Gets a value containing the data being stored. + /// + public T Data + { + get { return this.data; } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DateTimeApproximationComparer.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DateTimeApproximationComparer.cs new file mode 100644 index 00000000000..affbe8ca4ab --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DateTimeApproximationComparer.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// The DateTimeApproximationComparer is responsible for comparing two + /// DateTime objects at a level of precision determined by + /// the first object. The comparison either compares at the + /// date level or the date and time (down to Seconds precision). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class DateTimeApproximationComparer : IComparer + { + /// + /// Compares two objects and returns a value indicating + /// whether one is less than, equal to, or greater than the other. + /// + /// + /// The first object to compare. + /// + /// + /// The second object to compare. + /// + /// + /// If value1 is less than value2, then a value less than zero is returned. + /// If value1 equals value2, than zero is returned. + /// If value1 is greater than value2, then a value greater than zero is returned. + /// + public int Compare(DateTime value1, DateTime value2) + { + DateTime roundedX; + DateTime roundedY; + GetRoundedValues(value1, value2, out roundedX, out roundedY); + + return roundedX.CompareTo(roundedY); + } + + private static void GetRoundedValues(DateTime value1, DateTime value2, out DateTime roundedValue1, out DateTime roundedValue2) + { + roundedValue1 = value1; + roundedValue2 = value2; + + bool hasTimeComponent = HasTimeComponent(value1); + + int hour = hasTimeComponent ? value1.Hour : value2.Hour; + int minute = hasTimeComponent ? value1.Minute : value2.Minute; + int second = hasTimeComponent ? value1.Second : value2.Second; + + roundedValue1 = new DateTime(value1.Year, value1.Month, value1.Day, hour, minute, second); + roundedValue2 = new DateTime(value2.Year, value2.Month, value2.Day, value2.Hour, value2.Minute, value2.Second); + } + + private static bool HasTimeComponent(DateTime value) + { + bool hasNoTimeComponent = value.Hour == 0 + && value.Minute == 0 + && value.Second == 0 + && value.Millisecond == 0; + + return !hasNoTimeComponent; + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs new file mode 100644 index 00000000000..3a2768b17b0 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.Generated.cs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#region StyleCop Suppression - generated code +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Input; + +namespace Microsoft.Management.UI.Internal +{ + + /// + /// 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. + /// + [Localizability(LocalizationCategory.None)] + partial class DismissiblePopup + { + // + // DismissPopup routed command + // + /// + /// A command which child controls can use to tell the popup to close. + /// + public static readonly RoutedCommand DismissPopupCommand = new RoutedCommand("DismissPopup",typeof(DismissiblePopup)); + + static private void DismissPopupCommand_CommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + DismissiblePopup obj = (DismissiblePopup) sender; + obj.OnDismissPopupExecuted( e ); + } + + /// + /// Called when DismissPopup executes. + /// + /// + /// A command which child controls can use to tell the popup to close. + /// + protected virtual void OnDismissPopupExecuted(ExecutedRoutedEventArgs e) + { + OnDismissPopupExecutedImplementation(e); + } + + partial void OnDismissPopupExecutedImplementation(ExecutedRoutedEventArgs e); + + // + // CloseOnEscape dependency property + // + /// + /// Identifies the CloseOnEscape dependency property. + /// + public static readonly DependencyProperty CloseOnEscapeProperty = DependencyProperty.Register( "CloseOnEscape", typeof(bool), typeof(DismissiblePopup), new PropertyMetadata( BooleanBoxes.TrueBox, CloseOnEscapeProperty_PropertyChanged) ); + + /// + /// Gets or sets a value indicating whether the popup closes when ESC is pressed. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("Gets or sets a value indicating whether the popup closes when ESC is pressed.")] + [Localizability(LocalizationCategory.None)] + public bool CloseOnEscape + { + get + { + return (bool) GetValue(CloseOnEscapeProperty); + } + set + { + SetValue(CloseOnEscapeProperty,BooleanBoxes.Box(value)); + } + } + + static private void CloseOnEscapeProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + DismissiblePopup obj = (DismissiblePopup) o; + obj.OnCloseOnEscapeChanged( new PropertyChangedEventArgs((bool)e.OldValue, (bool)e.NewValue) ); + } + + /// + /// Occurs when CloseOnEscape property changes. + /// + public event EventHandler> CloseOnEscapeChanged; + + /// + /// Called when CloseOnEscape property changes. + /// + protected virtual void OnCloseOnEscapeChanged(PropertyChangedEventArgs e) + { + OnCloseOnEscapeChangedImplementation(e); + RaisePropertyChangedEvent(CloseOnEscapeChanged, e); + } + + partial void OnCloseOnEscapeChangedImplementation(PropertyChangedEventArgs e); + + // + // FocusChildOnOpen dependency property + // + /// + /// Identifies the FocusChildOnOpen dependency property. + /// + public static readonly DependencyProperty FocusChildOnOpenProperty = DependencyProperty.Register( "FocusChildOnOpen", typeof(bool), typeof(DismissiblePopup), new PropertyMetadata( BooleanBoxes.TrueBox, FocusChildOnOpenProperty_PropertyChanged) ); + + /// + /// Gets or sets a value indicating whether focus should be set on the child when the popup opens. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("Gets or sets a value indicating whether focus should be set on the child when the popup opens.")] + [Localizability(LocalizationCategory.None)] + public bool FocusChildOnOpen + { + get + { + return (bool) GetValue(FocusChildOnOpenProperty); + } + set + { + SetValue(FocusChildOnOpenProperty,BooleanBoxes.Box(value)); + } + } + + static private void FocusChildOnOpenProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + DismissiblePopup obj = (DismissiblePopup) o; + obj.OnFocusChildOnOpenChanged( new PropertyChangedEventArgs((bool)e.OldValue, (bool)e.NewValue) ); + } + + /// + /// Occurs when FocusChildOnOpen property changes. + /// + public event EventHandler> FocusChildOnOpenChanged; + + /// + /// Called when FocusChildOnOpen property changes. + /// + protected virtual void OnFocusChildOnOpenChanged(PropertyChangedEventArgs e) + { + OnFocusChildOnOpenChangedImplementation(e); + RaisePropertyChangedEvent(FocusChildOnOpenChanged, e); + } + + partial void OnFocusChildOnOpenChangedImplementation(PropertyChangedEventArgs e); + + // + // SetFocusOnClose dependency property + // + /// + /// Identifies the SetFocusOnClose dependency property. + /// + public static readonly DependencyProperty SetFocusOnCloseProperty = DependencyProperty.Register( "SetFocusOnClose", typeof(bool), typeof(DismissiblePopup), new PropertyMetadata( BooleanBoxes.FalseBox, SetFocusOnCloseProperty_PropertyChanged) ); + + /// + /// Indicates whether the focus returns to either a defined by the FocusOnCloseTarget dependency property UIElement or PlacementTarget or not. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("Indicates whether the focus returns to either a defined by the FocusOnCloseTarget dependency property UIElement or PlacementTarget or not.")] + [Localizability(LocalizationCategory.None)] + public bool SetFocusOnClose + { + get + { + return (bool) GetValue(SetFocusOnCloseProperty); + } + set + { + SetValue(SetFocusOnCloseProperty,BooleanBoxes.Box(value)); + } + } + + static private void SetFocusOnCloseProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + DismissiblePopup obj = (DismissiblePopup) o; + obj.OnSetFocusOnCloseChanged( new PropertyChangedEventArgs((bool)e.OldValue, (bool)e.NewValue) ); + } + + /// + /// Occurs when SetFocusOnClose property changes. + /// + public event EventHandler> SetFocusOnCloseChanged; + + /// + /// Called when SetFocusOnClose property changes. + /// + protected virtual void OnSetFocusOnCloseChanged(PropertyChangedEventArgs e) + { + OnSetFocusOnCloseChangedImplementation(e); + RaisePropertyChangedEvent(SetFocusOnCloseChanged, e); + } + + partial void OnSetFocusOnCloseChangedImplementation(PropertyChangedEventArgs e); + + // + // SetFocusOnCloseElement dependency property + // + /// + /// Identifies the SetFocusOnCloseElement dependency property. + /// + public static readonly DependencyProperty SetFocusOnCloseElementProperty = DependencyProperty.Register( "SetFocusOnCloseElement", typeof(UIElement), typeof(DismissiblePopup), new PropertyMetadata( null, SetFocusOnCloseElementProperty_PropertyChanged) ); + + /// + /// If the SetFocusOnClose property is set True and this property is set to a valid UIElement, focus returns to this UIElement after the DismissiblePopup is closed. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("If the SetFocusOnClose property is set True and this property is set to a valid UIElement, focus returns to this UIElement after the DismissiblePopup is closed.")] + [Localizability(LocalizationCategory.None)] + public UIElement SetFocusOnCloseElement + { + get + { + return (UIElement) GetValue(SetFocusOnCloseElementProperty); + } + set + { + SetValue(SetFocusOnCloseElementProperty,value); + } + } + + static private void SetFocusOnCloseElementProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + DismissiblePopup obj = (DismissiblePopup) o; + obj.OnSetFocusOnCloseElementChanged( new PropertyChangedEventArgs((UIElement)e.OldValue, (UIElement)e.NewValue) ); + } + + /// + /// Occurs when SetFocusOnCloseElement property changes. + /// + public event EventHandler> SetFocusOnCloseElementChanged; + + /// + /// Called when SetFocusOnCloseElement property changes. + /// + protected virtual void OnSetFocusOnCloseElementChanged(PropertyChangedEventArgs e) + { + OnSetFocusOnCloseElementChangedImplementation(e); + RaisePropertyChangedEvent(SetFocusOnCloseElementChanged, e); + } + + partial void OnSetFocusOnCloseElementChangedImplementation(PropertyChangedEventArgs e); + + /// + /// Called when a property changes. + /// + private void RaisePropertyChangedEvent(EventHandler> eh, PropertyChangedEventArgs e) + { + if (eh != null) + { + eh(this,e); + } + } + + // + // Static constructor + // + + /// + /// Called when the type is initialized. + /// + static DismissiblePopup() + { + CommandManager.RegisterClassCommandBinding( typeof(DismissiblePopup), new CommandBinding( DismissiblePopup.DismissPopupCommand, DismissPopupCommand_CommandExecuted )); + StaticConstructorImplementation(); + } + + static partial void StaticConstructorImplementation(); + + } +} +#endregion diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.cs new file mode 100644 index 00000000000..582be1c7ecd --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/DismissiblePopup.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Partial class implementation for DismissiblePopup control. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public partial class DismissiblePopup : Popup + { + /// + /// Constructs an instance of DismissablePopup. + /// + public DismissiblePopup() : base() + { + // nothing + } + + private delegate void FocusChildDelegate(); + + /// + /// Responds to the condition in which the value of the IsOpen property changes from false to true. + /// + /// The event arguments. + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + + if (this.FocusChildOnOpen) + { + this.Dispatcher.BeginInvoke( + System.Windows.Threading.DispatcherPriority.Loaded, + new FocusChildDelegate(this.FocusChild)); + } + + this.SetupAutomationIdBinding(); + } + + /// + /// Responds when the value of the IsOpen property changes from to true to false. + /// + /// The event arguments. + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + if (this.SetFocusOnClose) + { + // Find a control to set focus on. + if (this.SetFocusOnCloseElement != null) + { + // The focus target is set explicitly. + this.SetFocus(this.SetFocusOnCloseElement); + } + else if (this.PlacementTarget != null) + { + // Use PlacementTarget as a first chance option. + this.SetFocus(this.PlacementTarget); + } + else + { + // Use parent UIObject when neither FocusOnCloseTarget nor PlacementTarget is set. + UIElement parent = this.Parent as UIElement; + if (parent != null) + { + this.SetFocus(parent); + } + } + } + } + + private void SetFocus(UIElement element) + { + if (element.Focusable) + { + element.Focus(); + } + else + { + element.MoveFocus(new TraversalRequest(FocusNavigationDirection.First)); + } + } + + private void SetupAutomationIdBinding() + { + var popupRoot = this.FindPopupRoot(); + + var binding = new Binding(); + binding.Source = this; + binding.Path = new PropertyPath(AutomationProperties.AutomationIdProperty); + popupRoot.SetBinding(AutomationProperties.AutomationIdProperty, binding); + } + + private FrameworkElement FindPopupRoot() + { + DependencyObject element = this.Child; + + while (element.GetType().Name.Equals("PopupRoot", StringComparison.Ordinal) == false) + { + element = VisualTreeHelper.GetParent(element); + } + + Debug.Assert(element != null, "element not null"); + + return (FrameworkElement)element; + } + + /// + /// Provides class handling for the KeyDown routed event that occurs when the user presses a key while this control has focus. + /// + /// The event data. + protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) + { + //// + // Close the popup if ESC is pressed + //// + if (e.Key == System.Windows.Input.Key.Escape && this.CloseOnEscape) + { + this.IsOpen = false; + } + else + { + base.OnKeyDown(e); + } + } + + partial void OnDismissPopupExecutedImplementation(ExecutedRoutedEventArgs e) + { + this.IsOpen = false; + } + + private void FocusChild() + { + if (this.Child != null) + { + this.Child.MoveFocus(new TraversalRequest(FocusNavigationDirection.First)); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ExtendedFrameworkElementAutomationPeer.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ExtendedFrameworkElementAutomationPeer.cs new file mode 100644 index 00000000000..0f269df5b75 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ExtendedFrameworkElementAutomationPeer.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Provides a base automation peer for FrameworkElement controls. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class ExtendedFrameworkElementAutomationPeer : FrameworkElementAutomationPeer + { + #region Fields + + /// + /// Gets or sets the control type of the element that is associated with this automation peer. + /// + private AutomationControlType controlType = AutomationControlType.Custom; + + /// + /// Gets or sets a value that indicates whether the control should show in the logical tree. + /// + private bool isControlElement = true; + + #endregion + + #region Structors + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + public ExtendedFrameworkElementAutomationPeer(FrameworkElement owner) + : base(owner) + { + // This constructor intentionally left blank + } + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + /// The control type of the element that is associated with the automation peer. + public ExtendedFrameworkElementAutomationPeer(FrameworkElement owner, AutomationControlType controlType) + : this(owner) + { + this.controlType = controlType; + } + + /// + /// Initializes a new instance of the class. + /// + /// The owner of the automation peer. + /// The control type of the element that is associated with the automation peer. + /// Whether the element should show in the logical tree. + public ExtendedFrameworkElementAutomationPeer(FrameworkElement owner, AutomationControlType controlType, bool isControlElement) + : this(owner, controlType) + { + this.isControlElement = isControlElement; + } + + #endregion + + #region Overrides + + /// + /// Gets the class name. + /// + /// The class name. + protected override string GetClassNameCore() + { + return this.Owner.GetType().Name; + } + + /// + /// Gets the control type of the element that is associated with the automation peer. + /// + /// Returns the control type of the element that is associated with the automation peer. + protected override AutomationControlType GetAutomationControlTypeCore() + { + return this.controlType; + } + + /// + /// Gets a value that indicates whether the element is understood by the user as interactive or as contributing to the logical structure of the control in the GUI. Called by IsControlElement(). + /// + /// This method always returns true. + protected override bool IsControlElementCore() + { + return this.isControlElement; + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IAsyncProgress.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IAsyncProgress.cs new file mode 100644 index 00000000000..a87266dd696 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IAsyncProgress.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Windows; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// An interface designed to provide updates about an asynchronous operation. + /// If the UI is data bound to the properties in this interface then INotifyPropertyChanged should + /// be implemented by the type implementing IAsyncProgress so the UI can get notification of the properties + /// being changed. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public interface IAsyncProgress + { + /// + /// Gets a value indicating whether the async operation is currently running. + /// + bool OperationInProgress + { + get; + } + + /// + /// Gets a the error for the async operation. This field is only valid if + /// OperationInProgress is false. null indicates there was no error. + /// + Exception OperationError + { + get; + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IStateDescriptorFactory.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IStateDescriptorFactory.cs new file mode 100644 index 00000000000..e1539e76707 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IStateDescriptorFactory.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Defines an interface for a factory that creates + /// StateDescriptors. + /// + /// The type T used by the StateDescriptor. + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public interface IStateDescriptorFactory + { + /// + /// Creates a new StateDescriptor based upon custom + /// logic. + /// + /// A new StateDescriptor. + StateDescriptor Create(); + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs new file mode 100644 index 00000000000..27c45ef288b --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IntegralConverter.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; +using System.Windows; +using System.Windows.Data; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Takes a value and returns the largest value which is a integral amount of the second value. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class IntegralConverter : IMultiValueConverter + { + /// + /// Takes a value and returns the largest value which is a integral amount of the second value. + /// + /// + /// The first value is the source. The second is the factor. + /// + /// The parameter is not used. + /// The padding to subtract from the first value. + /// The parameter is not used. + /// + /// The integral value. + /// + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(values); + + if (values.Length != 2) + { + throw new ArgumentException("Two values expected", "values"); + } + + if (values[0] == DependencyProperty.UnsetValue || + values[1] == DependencyProperty.UnsetValue) + { + return DependencyProperty.UnsetValue; + } + + var source = (double)values[0]; + var factor = (double)values[1]; + + double padding = 0; + + if (parameter != null) + { + padding = double.Parse((string)parameter, CultureInfo.InvariantCulture); + } + + var newSource = source - padding; + + if (newSource < factor) + { + return source; + } + + var remainder = newSource % factor; + var result = newSource - remainder; + + return result; + } + + /// + /// This method is not used. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs new file mode 100644 index 00000000000..efefb08bae9 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/InverseBooleanConverter.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Windows.Data; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Takes a bool value and returns the inverse. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class InverseBooleanConverter : IValueConverter + { + /// + /// Converts a boolean value to be it's inverse. + /// + /// The source value. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The inverted boolean value. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(value); + + var boolValue = (bool)value; + + return !boolValue; + } + + /// + /// This method is not used. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs new file mode 100644 index 00000000000..dbd806a64d6 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsEqualConverter.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Windows; +using System.Windows.Data; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// Takes two objects and determines whether they are equal. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class IsEqualConverter : IMultiValueConverter + { + /// + /// Takes two items and determines whether they are equal. + /// + /// + /// Two objects of any type. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// + /// True if-and-only-if the two objects are equal per Object.Equals(). + /// Null is equal only to null. + /// + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + ArgumentNullException.ThrowIfNull(values); + + if (values.Length != 2) + { + throw new ArgumentException("Two values expected", "values"); + } + + object item1 = values[0]; + object item2 = values[1]; + + if (item1 == null) + { + return item2 == null; + } + + if (item2 == null) + { + return false; + } + + bool equal = item1.Equals(item2); + return equal; + } + + /// + /// This method is not used. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsNotNullConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsNotNullConverter.cs new file mode 100644 index 00000000000..265e0266c53 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/IsNotNullConverter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Windows.Data; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// The IsNotNullConverter is responsible for converting a value into + /// a boolean indicting whether the value is not null. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] + public class IsNotNullConverter : IValueConverter + { + #region IValueConverter Members + + /// + /// Determines if value is not null. + /// + /// The object to check. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// Returns true if value is not null, false otherwise. + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value != null; + } + + /// + /// This method is not used. + /// + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + /// The parameter is not used. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs new file mode 100644 index 00000000000..386b33996c1 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/KeyboardHelp.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Windows; +using System.Windows.Input; + +namespace Microsoft.Management.UI.Internal +{ + internal enum LogicalDirection + { + None, + Left, + Right + } + + internal static class KeyboardHelp + { + /// + /// Gets the logical direction for a key, taking into account RTL settings. + /// + /// The element to get FlowDirection from. + /// The key pressed. + /// The logical direction. + public static LogicalDirection GetLogicalDirection(DependencyObject element, Key key) + { + Debug.Assert(element != null, "element not null"); + + bool rightToLeft = IsElementRightToLeft(element); + + switch (key) + { + case Key.Right: + if (rightToLeft) + { + return LogicalDirection.Left; + } + else + { + return LogicalDirection.Right; + } + + case Key.Left: + if (rightToLeft) + { + return LogicalDirection.Right; + } + else + { + return LogicalDirection.Left; + } + + default: + return LogicalDirection.None; + } + } + + /// + /// Gets the focus direction for a key, taking into account RTL settings. + /// + /// The element to get FlowDirection from. + /// The key pressed. + /// The focus direction. + public static FocusNavigationDirection GetNavigationDirection(DependencyObject element, Key key) + { + Debug.Assert(element != null, "element not null"); + Debug.Assert(IsFlowDirectionKey(key)); + + bool rightToLeft = IsElementRightToLeft(element); + + switch (key) + { + case Key.Right: + if (rightToLeft) + { + return FocusNavigationDirection.Left; + } + else + { + return FocusNavigationDirection.Right; + } + + case Key.Left: + if (rightToLeft) + { + return FocusNavigationDirection.Right; + } + else + { + return FocusNavigationDirection.Left; + } + + case Key.Down: + return FocusNavigationDirection.Down; + case Key.Up: + return FocusNavigationDirection.Up; + default: + Debug.Fail("Non-direction key specified"); + return FocusNavigationDirection.First; + } + } + + /// + /// Determines if the control key is pressed. + /// + /// True if a control is pressed. + public static bool IsControlPressed() + { + if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) + { + return true; + } + else + { + return false; + } + } + + /// + /// Determines if the key is a navigation key. + /// + /// The key pressed. + /// True if the key is a navigation key. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private static bool IsFlowDirectionKey(Key key) + { + switch (key) + { + case Key.Right: + case Key.Left: + case Key.Down: + case Key.Up: + return true; + default: + return false; + } + } + + private static bool IsElementRightToLeft(DependencyObject element) + { + FlowDirection flowDirection = FrameworkElement.GetFlowDirection(element); + bool rightToLeft = flowDirection == FlowDirection.RightToLeft; + return rightToLeft; + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizer.Generated.cs b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizer.Generated.cs new file mode 100644 index 00000000000..799cd260e12 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/ListOrganizer.Generated.cs @@ -0,0 +1,487 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// +// This code was generated by a tool. DO NOT EDIT +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +#region StyleCop Suppression - generated code +using System; +using System.Collections; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; + +namespace Microsoft.Management.UI.Internal +{ + + /// + /// This control presents a dropdown listbox with associated organizing actions that can be performed on it. + /// + /// + /// + /// + /// If a custom template is provided for this control, then the template MUST provide the following template parts: + /// + /// PART_Picker - A required template part which must be of type PickerBase. This control provides basic functionality for Picker-like controls. + /// + /// + [TemplatePart(Name="PART_Picker", Type=typeof(PickerBase))] + [Localizability(LocalizationCategory.None)] + partial class ListOrganizer + { + // + // Fields + // + private PickerBase picker; + + // + // ItemDeleted RoutedEvent + // + /// + /// Identifies the ItemDeleted RoutedEvent. + /// + public static readonly RoutedEvent ItemDeletedEvent = EventManager.RegisterRoutedEvent("ItemDeleted",RoutingStrategy.Bubble,typeof(EventHandler>),typeof(ListOrganizer)); + + /// + /// Occurs when an item is deleted from the list. + /// + public event EventHandler> ItemDeleted + { + add + { + AddHandler(ItemDeletedEvent,value); + } + remove + { + RemoveHandler(ItemDeletedEvent,value); + } + } + + // + // ItemSelected RoutedEvent + // + /// + /// Identifies the ItemSelected RoutedEvent. + /// + public static readonly RoutedEvent ItemSelectedEvent = EventManager.RegisterRoutedEvent("ItemSelected",RoutingStrategy.Bubble,typeof(EventHandler>),typeof(ListOrganizer)); + + /// + /// Occurs when an item is selected in the list. + /// + public event EventHandler> ItemSelected + { + add + { + AddHandler(ItemSelectedEvent,value); + } + remove + { + RemoveHandler(ItemSelectedEvent,value); + } + } + + // + // DeleteItem routed command + // + /// + /// Informs the ListOrganizer that it should delete the item passed. + /// + public static readonly RoutedCommand DeleteItemCommand = new RoutedCommand("DeleteItem",typeof(ListOrganizer)); + + static private void DeleteItemCommand_CommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + ListOrganizer obj = (ListOrganizer) sender; + obj.OnDeleteItemExecuted( e ); + } + + /// + /// Called when DeleteItem executes. + /// + /// + /// Informs the ListOrganizer that it should delete the item passed. + /// + protected virtual void OnDeleteItemExecuted(ExecutedRoutedEventArgs e) + { + OnDeleteItemExecutedImplementation(e); + } + + partial void OnDeleteItemExecutedImplementation(ExecutedRoutedEventArgs e); + + // + // SelectItem routed command + // + /// + /// Informs the ListOrganizer that it should select the item passed. + /// + public static readonly RoutedCommand SelectItemCommand = new RoutedCommand("SelectItem",typeof(ListOrganizer)); + + static private void SelectItemCommand_CommandExecuted(object sender, ExecutedRoutedEventArgs e) + { + ListOrganizer obj = (ListOrganizer) sender; + obj.OnSelectItemExecuted( e ); + } + + /// + /// Called when SelectItem executes. + /// + /// + /// Informs the ListOrganizer that it should select the item passed. + /// + protected virtual void OnSelectItemExecuted(ExecutedRoutedEventArgs e) + { + OnSelectItemExecutedImplementation(e); + } + + partial void OnSelectItemExecutedImplementation(ExecutedRoutedEventArgs e); + + // + // DropDownButtonTemplate dependency property + // + /// + /// Identifies the DropDownButtonTemplate dependency property. + /// + public static readonly DependencyProperty DropDownButtonTemplateProperty = DependencyProperty.Register( "DropDownButtonTemplate", typeof(ControlTemplate), typeof(ListOrganizer), new PropertyMetadata( null, DropDownButtonTemplateProperty_PropertyChanged) ); + + /// + /// Gets or sets a value that controls the visual tree of the DropDown button. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("Gets or sets a value that controls the visual tree of the DropDown button.")] + [Localizability(LocalizationCategory.None)] + public ControlTemplate DropDownButtonTemplate + { + get + { + return (ControlTemplate) GetValue(DropDownButtonTemplateProperty); + } + set + { + SetValue(DropDownButtonTemplateProperty,value); + } + } + + static private void DropDownButtonTemplateProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ListOrganizer obj = (ListOrganizer) o; + obj.OnDropDownButtonTemplateChanged( new PropertyChangedEventArgs((ControlTemplate)e.OldValue, (ControlTemplate)e.NewValue) ); + } + + /// + /// Occurs when DropDownButtonTemplate property changes. + /// + public event EventHandler> DropDownButtonTemplateChanged; + + /// + /// Called when DropDownButtonTemplate property changes. + /// + protected virtual void OnDropDownButtonTemplateChanged(PropertyChangedEventArgs e) + { + OnDropDownButtonTemplateChangedImplementation(e); + RaisePropertyChangedEvent(DropDownButtonTemplateChanged, e); + } + + partial void OnDropDownButtonTemplateChangedImplementation(PropertyChangedEventArgs e); + + // + // DropDownStyle dependency property + // + /// + /// Identifies the DropDownStyle dependency property. + /// + public static readonly DependencyProperty DropDownStyleProperty = DependencyProperty.Register( "DropDownStyle", typeof(Style), typeof(ListOrganizer), new PropertyMetadata( null, DropDownStyleProperty_PropertyChanged) ); + + /// + /// Gets or sets the style of the drop-down. + /// + [Bindable(true)] + [Category("Common Properties")] + [Description("Gets or sets the style of the drop-down.")] + [Localizability(LocalizationCategory.None)] + public Style DropDownStyle + { + get + { + return (Style) GetValue(DropDownStyleProperty); + } + set + { + SetValue(DropDownStyleProperty,value); + } + } + + static private void DropDownStyleProperty_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ListOrganizer obj = (ListOrganizer) o; + obj.OnDropDownStyleChanged( new PropertyChangedEventArgs + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/AllModulesControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/AllModulesControl.xaml.cs new file mode 100644 index 00000000000..5d360017c16 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/AllModulesControl.xaml.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for AllModulesControl.xaml. + /// + public partial class AllModulesControl : UserControl + { + #region Construction and Destructor + + /// + /// Initializes a new instance of the AllModulesControl class. + /// + public AllModulesControl() + { + InitializeComponent(); + + this.Loaded += (obj, args) => + { + this.ModulesCombo.Focus(); + }; + } + + #endregion + /// + /// Gets current control of the ShowModuleControl. + /// + internal ShowModuleControl CurrentShowModuleControl + { + get { return this.ShowModuleControl; } + } + + private void RefreshButton_Click(object sender, System.Windows.RoutedEventArgs e) + { + AllModulesViewModel viewModel = this.DataContext as AllModulesViewModel; + if (viewModel == null) + { + return; + } + + viewModel.OnRefresh(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml new file mode 100644 index 00000000000..f7e77ed944d --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml.cs new file mode 100644 index 00000000000..0837bb567e4 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/CmdletControl.xaml.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Windows; +using System.Windows.Controls; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for CmdletControl.xaml. + /// + public partial class CmdletControl : UserControl + { + /// + /// Field used for the CurrentCommandViewModel parameter. + /// + private CommandViewModel currentCommandViewModel; + + #region Construction and Destructor + /// + /// Initializes a new instance of the CmdletControl class. + /// + public CmdletControl() + { + InitializeComponent(); + this.NotImportedControl.ImportModuleButton.Click += ImportModuleButton_Click; + this.ParameterSetTabControl.DataContextChanged += new DependencyPropertyChangedEventHandler(this.ParameterSetTabControl_DataContextChanged); + this.KeyDown += this.CmdletControl_KeyDown; + this.helpButton.innerButton.Click += this.HelpButton_Click; + } + #endregion + + #region Properties + /// + /// Gets the owner of the ViewModel. + /// + private CommandViewModel CurrentCommandViewModel + { + get { return this.currentCommandViewModel; } + } + #endregion + + #region Private Events + + /// + /// DataContextChanged event. + /// + /// Event sender. + /// Event args. + private void ParameterSetTabControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (this.DataContext == null) + { + return; + } + + CommandViewModel viewModel = (CommandViewModel)this.DataContext; + this.currentCommandViewModel = viewModel; + + if (viewModel.ParameterSets.Count == 0) + { + return; + } + + this.ParameterSetTabControl.SelectedItem = viewModel.ParameterSets[0]; + } + + /// + /// Key down event for user press F1 button. + /// + /// Event sender. + /// Event args. + private void CmdletControl_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.F1) + { + this.CurrentCommandViewModel.OpenHelpWindow(); + } + } + + /// + /// Help button event. + /// + /// Event sender. + /// Event args. + private void HelpButton_Click(object sender, RoutedEventArgs e) + { + this.CurrentCommandViewModel.OpenHelpWindow(); + } + + /// + /// Import Module Button event. + /// + /// Event sender. + /// Event args. + private void ImportModuleButton_Click(object sender, RoutedEventArgs e) + { + this.CurrentCommandViewModel.OnImportModule(); + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml new file mode 100644 index 00000000000..024b287e80a --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml.cs new file mode 100644 index 00000000000..05247f46ff0 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButton.xaml.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Button with images to represent enabled and disabled states. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Required by XAML")] + public partial class ImageButton : ImageButtonBase + { + /// + /// Initializes a new instance of the ImageButton class. + /// + public ImageButton() + { + InitializeComponent(); + this.Loaded += this.ImageButton_Loaded; + } + + /// + /// Copies the automation id and name from the parent control to the inner button. + /// + /// Event sender. + /// Event arguments. + private void ImageButton_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + object thisAutomationId = this.GetValue(AutomationProperties.AutomationIdProperty); + if (thisAutomationId != null) + { + this.innerButton.SetValue(AutomationProperties.AutomationIdProperty, thisAutomationId); + } + + object thisAutomationName = this.GetValue(AutomationProperties.NameProperty); + if (thisAutomationName != null) + { + this.innerButton.SetValue(AutomationProperties.NameProperty, thisAutomationName); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonBase.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonBase.cs new file mode 100644 index 00000000000..76ab5cf3c28 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonBase.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Implements the ImageButtonBase base class to the ImageButton and ImageToggleButton. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Required by XAML")] + public class ImageButtonBase : Grid + { + /// + /// Command associated with this button. + /// + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(RoutedUICommand), typeof(ImageButton)); + + /// + /// Image to be used for the enabled state. + /// + public static readonly DependencyProperty EnabledImageSourceProperty = + DependencyProperty.Register("EnabledImageSource", typeof(ImageSource), typeof(ImageButton)); + + /// + /// Image to be used for the disabled state. + /// + public static readonly DependencyProperty DisabledImageSourceProperty = + DependencyProperty.Register("DisabledImageSource", typeof(ImageSource), typeof(ImageButton)); + + /// + /// Gets or sets the image to be used for the enabled state. + /// + public ImageSource EnabledImageSource + { + get { return (ImageSource)GetValue(ImageButton.EnabledImageSourceProperty); } + set { SetValue(ImageButton.EnabledImageSourceProperty, value); } + } + + /// + /// Gets or sets the image to be used for the disabled state. + /// + public ImageSource DisabledImageSource + { + get { return (ImageSource)GetValue(ImageButton.DisabledImageSourceProperty); } + set { SetValue(ImageButton.DisabledImageSourceProperty, value); } + } + + /// + /// Gets or sets the command associated with this button. + /// + public RoutedUICommand Command + { + get { return (RoutedUICommand)GetValue(ImageButton.CommandProperty); } + set { SetValue(ImageButton.CommandProperty, value); } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonCommon.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonCommon.xaml new file mode 100644 index 00000000000..f89e474a2de --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonCommon.xaml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonToolTipConverter.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonToolTipConverter.cs new file mode 100644 index 00000000000..bec10afc6b7 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageButtonToolTipConverter.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Controls; +using System.Windows.Data; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Converts a an ImageButtonBase to its corresponding ToolTip. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed for XAML")] + public class ImageButtonToolTipConverter : IValueConverter + { + // This class is meant to be used like this in XAML: + // + // ... + // + // + // + // ... + // + #region IValueConverter Members + + /// + /// Converts a an ImageButtonBase to its corresponding ToolTip by checking if it has a tooltip property + /// or a command with tooltip text + /// + /// The ImageButtonBase we are trying to Convert. + /// is not used. + /// is not used. + /// is not used. + /// The resulting object obtained from retrieving the property value in (or property values if contains dots) out of . . + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + ImageButtonBase imageButtonBase = value as ImageButtonBase; + if (imageButtonBase == null) + { + return null; + } + + object toolTipObj = imageButtonBase.GetValue(Button.ToolTipProperty); + if (toolTipObj != null) + { + return toolTipObj.ToString(); + } + + if (imageButtonBase.Command != null && !string.IsNullOrEmpty(imageButtonBase.Command.Text)) + { + return imageButtonBase.Command.Text.Replace("_", string.Empty); + } + + return null; + } + + /// + /// This method is not supported. + /// + /// is not used. + /// is not used. + /// is not used. + /// is not used. + /// No value is returned. + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml new file mode 100644 index 00000000000..c472721ac85 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml.cs new file mode 100644 index 00000000000..adc1e1a6421 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ImageButton/ImageToggleButton.xaml.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Automation; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Toggle button with images to represent enabled and disabled states. + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Required by XAML")] + public partial class ImageToggleButton : ImageButtonBase + { + /// + /// Value indicating the button is checked. + /// + public static readonly DependencyProperty IsCheckedProperty = + DependencyProperty.Register("IsChecked", typeof(bool), typeof(ImageToggleButton)); + + /// + /// Initializes a new instance of the ImageToggleButton class. + /// + public ImageToggleButton() + { + InitializeComponent(); + this.Loaded += this.ImageButton_Loaded; + } + + /// + /// Gets or sets a value indicating whether the button is checked. + /// + public bool IsChecked + { + get { return (bool)GetValue(ImageToggleButton.IsCheckedProperty); } + set { SetValue(ImageToggleButton.IsCheckedProperty, value); } + } + + /// + /// Copies the automation id and name from the parent control to the inner button. + /// + /// Event sender. + /// Event arguments. + private void ImageButton_Loaded(object sender, System.Windows.RoutedEventArgs e) + { + object thisAutomationId = this.GetValue(AutomationProperties.AutomationIdProperty); + if (thisAutomationId != null) + { + this.toggleInnerButton.SetValue(AutomationProperties.AutomationIdProperty, thisAutomationId); + } + + object thisAutomationName = this.GetValue(AutomationProperties.NameProperty); + if (thisAutomationName != null) + { + this.toggleInnerButton.SetValue(AutomationProperties.NameProperty, thisAutomationName); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml new file mode 100644 index 00000000000..557741edf0d --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs new file mode 100644 index 00000000000..f57d5dfda51 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/MultipleSelectionControl.xaml.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; +using System.Management.Automation; +using System.Text; +using System.Windows; +using System.Windows.Controls; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for MultipleSelectionControl.xaml. + /// + public partial class MultipleSelectionControl : UserControl + { + /// + /// Initializes a new instance of the MultipleSelectionControl class. + /// + public MultipleSelectionControl() + { + InitializeComponent(); + } + + /// + /// Show more items in new dialog. + /// + /// Event sender. + /// Event arguments. + private void ButtonBrowse_Click(object sender, RoutedEventArgs e) + { + MultipleSelectionDialog multipleSelectionDialog = new MultipleSelectionDialog(); + multipleSelectionDialog.Title = this.multipleValueButton.ToolTip.ToString(); + multipleSelectionDialog.listboxParameter.ItemsSource = comboxParameter.ItemsSource; + multipleSelectionDialog.ShowDialog(); + + if (multipleSelectionDialog.DialogResult != true) + { + return; + } + + StringBuilder newComboText = new StringBuilder(); + + foreach (object selectedItem in multipleSelectionDialog.listboxParameter.SelectedItems) + { + newComboText.Append(CultureInfo.InvariantCulture, $"{selectedItem},"); + } + + if (newComboText.Length > 1) + { + newComboText.Remove(newComboText.Length - 1, 1); + } + + comboxParameter.Text = newComboText.ToString(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml new file mode 100644 index 00000000000..cb0a9198b80 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml.cs new file mode 100644 index 00000000000..65774004236 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/NotImportedCmdletControl.xaml.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Windows.Controls; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for NotImportedCmdletControl.xaml. + /// + public partial class NotImportedCmdletControl : UserControl + { + #region Construction and Destructor + + /// + /// Initializes a new instance of the NotImportedCmdletControl class. + /// + public NotImportedCmdletControl() + { + InitializeComponent(); + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml new file mode 100644 index 00000000000..c59f519d663 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs new file mode 100644 index 00000000000..6efef65eec6 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ParameterSetControl.xaml.cs @@ -0,0 +1,403 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Management.Automation; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Data; + +using Microsoft.Management.UI.Internal; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for ParameterSetControl.xaml. + /// + public partial class ParameterSetControl : UserControl + { + /// + /// First focusable element in the generated UI. + /// + private UIElement firstFocusableElement; + + /// + /// Field used for the CurrentParameterSetViewModel parameter. + /// + private ParameterSetViewModel currentParameterSetViewModel; + + #region Construction and Destructor + /// + /// Initializes a new instance of the ParameterSetControl class. + /// + public ParameterSetControl() + { + InitializeComponent(); + this.DataContextChanged += new DependencyPropertyChangedEventHandler(this.ParameterSetControl_DataContextChanged); + } + #endregion + + #region Public Methods + + /// + /// Focuses the first focusable element in this control. + /// + public void FocusFirstElement() + { + if (this.firstFocusableElement != null) + { + this.firstFocusableElement.Focus(); + } + } + + #endregion + + #region Private Property + /// + /// Gets current ParameterSetViewModel. + /// + private ParameterSetViewModel CurrentParameterSetViewModel + { + get { return this.currentParameterSetViewModel; } + } + + #endregion + + /// + /// Creates a CheckBox for switch parameters. + /// + /// DataContext object. + /// Row number. + /// a CheckBox for switch parameters. + private static CheckBox CreateCheckBox(ParameterViewModel parameterViewModel, int rowNumber) + { + CheckBox checkBox = new CheckBox(); + + checkBox.SetBinding(Label.ContentProperty, new Binding("NameCheckLabel")); + checkBox.DataContext = parameterViewModel; + checkBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; + checkBox.SetValue(Grid.ColumnProperty, 0); + checkBox.SetValue(Grid.ColumnSpanProperty, 2); + checkBox.SetValue(Grid.RowProperty, rowNumber); + checkBox.IsThreeState = false; + checkBox.Margin = new Thickness(8, rowNumber == 0 ? 7 : 5, 0, 5); + checkBox.SetBinding(CheckBox.ToolTipProperty, new Binding("ToolTip")); + checkBox.SetBinding(AutomationProperties.HelpTextProperty, new Binding("ToolTip")); + Binding valueBinding = new Binding("Value"); + checkBox.SetBinding(CheckBox.IsCheckedProperty, valueBinding); + + //// Add AutomationProperties.AutomationId for Ui Automation test. + checkBox.SetValue( + System.Windows.Automation.AutomationProperties.AutomationIdProperty, + string.Create(CultureInfo.CurrentCulture, $"chk{parameterViewModel.Name}")); + + checkBox.SetValue( + System.Windows.Automation.AutomationProperties.NameProperty, + parameterViewModel.Name); + + return checkBox; + } + + /// + /// Creates a ComboBox control for input type field. + /// + /// DataContext object. + /// Row number. + /// Control data source. + /// Return a ComboBox control. + private static ComboBox CreateComboBoxControl(ParameterViewModel parameterViewModel, int rowNumber, IEnumerable itemsSource) + { + ComboBox comboBox = new ComboBox(); + + comboBox.DataContext = parameterViewModel; + comboBox.SetValue(Grid.ColumnProperty, 1); + comboBox.SetValue(Grid.RowProperty, rowNumber); + comboBox.Margin = new Thickness(2); + comboBox.SetBinding(TextBox.ToolTipProperty, new Binding("ToolTip")); + comboBox.ItemsSource = itemsSource; + + Binding selectedItemBinding = new Binding("Value"); + comboBox.SetBinding(ComboBox.SelectedItemProperty, selectedItemBinding); + + string automationId = string.Create(CultureInfo.CurrentCulture, $"combox{parameterViewModel.Name}"); + + //// Add AutomationProperties.AutomationId for Ui Automation test. + comboBox.SetValue( + System.Windows.Automation.AutomationProperties.AutomationIdProperty, + automationId); + + comboBox.SetValue( + System.Windows.Automation.AutomationProperties.NameProperty, + parameterViewModel.Name); + + return comboBox; + } + + /// + /// Creates a MultiSelectCombo control for input type field. + /// + /// DataContext object. + /// Row number. + /// Control data source. + /// Return a MultiSelectCombo control. + private static MultipleSelectionControl CreateMultiSelectComboControl(ParameterViewModel parameterViewModel, int rowNumber, IEnumerable itemsSource) + { + MultipleSelectionControl multiControls = new MultipleSelectionControl(); + + multiControls.DataContext = parameterViewModel; + multiControls.SetValue(Grid.ColumnProperty, 1); + multiControls.SetValue(Grid.RowProperty, rowNumber); + multiControls.Margin = new Thickness(2); + multiControls.comboxParameter.ItemsSource = itemsSource; + multiControls.SetBinding(TextBox.ToolTipProperty, new Binding("ToolTip")); + + Binding valueBinding = new Binding("Value"); + valueBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; + multiControls.comboxParameter.SetBinding(ComboBox.TextProperty, valueBinding); + + // Add AutomationProperties.AutomationId for Ui Automation test. + multiControls.SetValue(System.Windows.Automation.AutomationProperties.AutomationIdProperty, string.Create(CultureInfo.CurrentCulture, $"combox{parameterViewModel.Name}")); + + multiControls.comboxParameter.SetValue( + System.Windows.Automation.AutomationProperties.NameProperty, + parameterViewModel.Name); + + string buttonToolTipAndName = string.Format( + CultureInfo.CurrentUICulture, + ShowCommandResources.SelectMultipleValuesForParameterFormat, + parameterViewModel.Name); + + multiControls.multipleValueButton.SetValue(Button.ToolTipProperty, buttonToolTipAndName); + multiControls.multipleValueButton.SetValue( + System.Windows.Automation.AutomationProperties.NameProperty, + buttonToolTipAndName); + + return multiControls; + } + + /// + /// Creates a TextBox control for input type field. + /// + /// DataContext object. + /// Row number. + /// Return a TextBox control. + private static TextBox CreateTextBoxControl(ParameterViewModel parameterViewModel, int rowNumber) + { + TextBox textBox = new TextBox(); + + textBox.DataContext = parameterViewModel; + textBox.SetValue(Grid.ColumnProperty, 1); + textBox.SetValue(Grid.RowProperty, rowNumber); + textBox.Margin = new Thickness(2); + textBox.SetBinding(TextBox.ToolTipProperty, new Binding("ToolTip")); + + Binding valueBinding = new Binding("Value"); + valueBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; + textBox.SetBinding(TextBox.TextProperty, valueBinding); + + //// Add AutomationProperties.AutomationId for UI Automation test. + textBox.SetValue( + System.Windows.Automation.AutomationProperties.AutomationIdProperty, + string.Create(CultureInfo.CurrentCulture, $"txt{parameterViewModel.Name}")); + + textBox.SetValue( + System.Windows.Automation.AutomationProperties.NameProperty, + parameterViewModel.Name); + + ShowCommandParameterType parameterType = parameterViewModel.Parameter.ParameterType; + + if (parameterType.IsArray) + { + parameterType = parameterType.ElementType; + } + + if (parameterType.IsScriptBlock || parameterType.ImplementsDictionary) + { + textBox.AcceptsReturn = true; + textBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; + textBox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + textBox.Loaded += ParameterSetControl.MultiLineTextBox_Loaded; + } + + return textBox; + } + + /// + /// Called for a newly created multiline text box to increase its height and. + /// + /// Event sender. + /// Event arguments. + private static void MultiLineTextBox_Loaded(object sender, RoutedEventArgs e) + { + TextBox senderTextBox = (TextBox)sender; + senderTextBox.Loaded -= ParameterSetControl.MultiLineTextBox_Loaded; + + // This will set the height to about 3 lines since the total height of the + // TextBox is a bit greater than a line's height + senderTextBox.Height = senderTextBox.ActualHeight * 2; + } + + #region Event Methods + + /// + /// When user switch ParameterSet.It will trigger this event. + /// This event method will renew generate all controls for current ParameterSet. + /// + /// Event sender. + /// Event args. + private void ParameterSetControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + this.firstFocusableElement = null; + this.MainGrid.Children.Clear(); + this.MainGrid.RowDefinitions.Clear(); + + ParameterSetViewModel viewModel = e.NewValue as ParameterSetViewModel; + if (viewModel == null) + { + return; + } + + this.currentParameterSetViewModel = viewModel; + + for (int rowNumber = 0; rowNumber < viewModel.Parameters.Count; rowNumber++) + { + ParameterViewModel parameter = viewModel.Parameters[rowNumber]; + this.MainGrid.RowDefinitions.Add(this.CreateNewRow()); + + if (parameter.Parameter.ParameterType.IsSwitch) + { + this.AddControlToMainGrid(ParameterSetControl.CreateCheckBox(parameter, rowNumber)); + } + else + { + this.CreateAndAddLabel(parameter, rowNumber); + Control control = null; + if (parameter.Parameter.HasParameterSet) + { + // For ValidateSet parameter + ArrayList itemsSource = new ArrayList(); + itemsSource.Add(string.Empty); + + for (int i = 0; i < parameter.Parameter.ValidParamSetValues.Count; i++) + { + itemsSource.Add(parameter.Parameter.ValidParamSetValues[i]); + } + + control = ParameterSetControl.CreateComboBoxControl(parameter, rowNumber, itemsSource); + } + else if (parameter.Parameter.ParameterType.IsEnum) + { + if (parameter.Parameter.ParameterType.HasFlagAttribute) + { + ArrayList itemsSource = new ArrayList(); + itemsSource.Add(string.Empty); + itemsSource.AddRange(parameter.Parameter.ParameterType.EnumValues); + control = ParameterSetControl.CreateComboBoxControl(parameter, rowNumber, itemsSource); + } + else + { + control = ParameterSetControl.CreateMultiSelectComboControl(parameter, rowNumber, parameter.Parameter.ParameterType.EnumValues); + } + } + else if (parameter.Parameter.ParameterType.IsBoolean) + { + control = ParameterSetControl.CreateComboBoxControl(parameter, rowNumber, new string[] { string.Empty, "$True", "$False" }); + } + else + { + // For input parameter + control = ParameterSetControl.CreateTextBoxControl(parameter, rowNumber); + } + + if (control != null) + { + this.AddControlToMainGrid(control); + } + } + } + } + + /// + /// When user trigger click on anyone CheckBox. Get value from sender. + /// + /// Event sender. + /// Event args. + private void CheckBox_Click(object sender, RoutedEventArgs e) + { + CheckBox senderCheck = (CheckBox)sender; + ((ParameterViewModel)senderCheck.DataContext).Value = senderCheck.IsChecked.ToString(); + } + + #endregion + + #region Private Method + + /// + /// Creates a RowDefinition for MainGrid. + /// + /// Return a RowDefinition object. + private RowDefinition CreateNewRow() + { + RowDefinition row = new RowDefinition(); + row.Height = GridLength.Auto; + return row; + } + + /// + /// Adds a control to MainGrid;. + /// + /// Will adding UIControl. + private void AddControlToMainGrid(UIElement uiControl) + { + if (this.firstFocusableElement == null && uiControl is not Label) + { + this.firstFocusableElement = uiControl; + } + + this.MainGrid.Children.Add(uiControl); + } + + /// + /// Creates a Label control and add it to MainGrid. + /// + /// DataContext object. + /// Row number. + private void CreateAndAddLabel(ParameterViewModel parameterViewModel, int rowNumber) + { + Label label = this.CreateLabel(parameterViewModel, rowNumber); + this.AddControlToMainGrid(label); + } + + /// + /// Creates a Label control for input type field. + /// + /// DataContext object. + /// Row number. + /// Return a Label control. + private Label CreateLabel(ParameterViewModel parameterViewModel, int rowNumber) + { + Label label = new Label(); + + label.SetBinding(Label.ContentProperty, new Binding("NameTextLabel")); + label.DataContext = parameterViewModel; + label.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; + label.SetValue(Grid.ColumnProperty, 0); + label.SetValue(Grid.RowProperty, rowNumber); + label.Margin = new Thickness(2); + label.SetBinding(Label.ToolTipProperty, new Binding("ToolTip")); + + //// Add AutomationProperties.AutomationId for Ui Automation test. + label.SetValue( + System.Windows.Automation.AutomationProperties.AutomationIdProperty, + string.Create(CultureInfo.CurrentCulture, $"lbl{parameterViewModel.Name}")); + + return label; + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml new file mode 100644 index 00000000000..e03c7859ed0 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs new file mode 100644 index 00000000000..4bdaa32fd9f --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Controls/ShowModuleControl.xaml.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Control taht shows cmdlets in a module and details for a selected cmdlet. + /// + public partial class ShowModuleControl : UserControl + { + /// + /// Field used for the Owner parameter. + /// + private Window owner; + + /// + /// Initializes a new instance of the ShowModuleControl class. + /// + public ShowModuleControl() + { + InitializeComponent(); + + // See comment in method summary to understand why this event is handled + this.CommandList.PreviewMouseMove += this.CommandList_PreviewMouseMove; + + // See comment in method summary to understand why this event is handled + this.CommandList.SelectionChanged += this.CommandList_SelectionChanged; + } + + /// + /// Gets or sets the owner of the container. + /// + public Window Owner + { + get { return this.owner; } + set { this.owner = value; } + } + + #region Events Handlers + /// + /// WPF has an interesting feature in list selection where if you hold the mouse button down, + /// 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 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 + /// since it causes this problem. + /// WPF sets up this behavior by using a mouse capture. We undo the behavior in the handler below + /// which removes the behavior. + /// + /// Event sender. + /// Event arguments. + private void CommandList_PreviewMouseMove(object sender, MouseEventArgs e) + { + if (this.CommandList.IsMouseCaptured) + { + this.CommandList.ReleaseMouseCapture(); + } + } + + /// + /// Ensures the selected item is scrolled into view and that the list is focused. + /// An item could be out of the view if the selection was changed in the object model + /// + /// Event sender. + /// Event arguments. + private void CommandList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (this.CommandList.SelectedItem == null) + { + return; + } + + this.CommandList.ScrollIntoView(this.CommandList.SelectedItem); + } + #endregion Events Handlers + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.Designer.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.Designer.cs new file mode 100644 index 00000000000..18194cec8fb --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.Designer.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.16808 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Management.UI.Internal.ShowCommand { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class ShowCommandSettings : global::System.Configuration.ApplicationSettingsBase { + + private static ShowCommandSettings defaultInstance = ((ShowCommandSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new ShowCommandSettings()))); + + public static ShowCommandSettings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowOneCommandTop { + get { + return ((double)(this["ShowOneCommandTop"])); + } + set { + this["ShowOneCommandTop"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowOneCommandLeft { + get { + return ((double)(this["ShowOneCommandLeft"])); + } + set { + this["ShowOneCommandLeft"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowOneCommandWidth { + get { + return ((double)(this["ShowOneCommandWidth"])); + } + set { + this["ShowOneCommandWidth"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowOneCommandHeight { + get { + return ((double)(this["ShowOneCommandHeight"])); + } + set { + this["ShowOneCommandHeight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowCommandsTop { + get { + return ((double)(this["ShowCommandsTop"])); + } + set { + this["ShowCommandsTop"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowCommandsLeft { + get { + return ((double)(this["ShowCommandsLeft"])); + } + set { + this["ShowCommandsLeft"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowCommandsWidth { + get { + return ((double)(this["ShowCommandsWidth"])); + } + set { + this["ShowCommandsWidth"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public double ShowCommandsHeight { + get { + return ((double)(this["ShowCommandsHeight"])); + } + set { + this["ShowCommandsHeight"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowCommandsWindowMaximized { + get { + return ((bool)(this["ShowCommandsWindowMaximized"])); + } + set { + this["ShowCommandsWindowMaximized"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ShowOneCommandWindowMaximized { + get { + return ((bool)(this["ShowOneCommandWindowMaximized"])); + } + set { + this["ShowOneCommandWindowMaximized"] = value; + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.settings b/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.settings new file mode 100644 index 00000000000..53a4da1a5d7 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ShowCommandSettings.settings @@ -0,0 +1,36 @@ + + + + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + False + + + False + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs new file mode 100644 index 00000000000..04fc95e4223 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/AllModulesViewModel.cs @@ -0,0 +1,658 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Windows; + +using Microsoft.Management.UI.Internal; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Contains all Commands, Parameters, ParameterSet and Common Parameter. + /// + public class AllModulesViewModel : INotifyPropertyChanged + { + #region Private Fields + /// + /// Flag indicating a wait message is being displayed. + /// + private bool waitMessageDisplayed; + + /// + /// True if this ViewModel is not supposed to show common parameters. + /// + private bool noCommonParameter; + + /// + /// the filterName of command. + /// + private string commandNameFilter; + + /// + /// Field used for the Modules property. + /// + private List modules; + + /// + /// true if a command can be run. + /// + private bool canRun; + + /// + /// true if a command can be copied. + /// + private bool canCopy; + + /// + /// the selected module being displayed in the GUI. + /// + private ModuleViewModel selectedModule; + + /// + /// the visibility of the refresh button. + /// + private Visibility refreshVisibility = Visibility.Collapsed; + + /// + /// Provides an extra viewModel object that allows callers to control certain aspects of the GUI. + /// + private object extraViewModel; + + /// + /// private property for ZoomLevel. + /// + private double zoomLevel = 1.0; + #endregion + + #region Construction and Destructor + /// + /// Initializes a new instance of the AllModulesViewModel class. + /// + /// The loaded modules. + /// Commands to show. + public AllModulesViewModel(Dictionary importedModules, IEnumerable commands) + { + ArgumentNullException.ThrowIfNull(commands); + + if (!commands.GetEnumerator().MoveNext()) + { + throw new ArgumentNullException("commands"); + } + + this.Initialization(importedModules, commands, true); + } + + /// + /// Initializes a new instance of the AllModulesViewModel class. + /// + /// The loaded modules. + /// All PowerShell commands. + /// True not to show common parameters. + public AllModulesViewModel(Dictionary importedModules, IEnumerable commands, bool noCommonParameter) + { + ArgumentNullException.ThrowIfNull(commands); + + this.Initialization(importedModules, commands, noCommonParameter); + } + + #endregion + + #region INotifyPropertyChanged Members + /// + /// PropertyChanged Event. + /// + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + /// + /// Indicates the selected command in the selected module needs to display the help for a command. + /// + public event EventHandler SelectedCommandInSelectedModuleNeedsHelp; + + /// + /// Indicates the selected command in the selected module needs to import a module for a command. + /// + public event EventHandler SelectedCommandInSelectedModuleNeedsImportModule; + + /// + /// Indicates the selected command in the selected module should be run. + /// + public event EventHandler RunSelectedCommandInSelectedModule; + + /// + /// Indicates we want to refresh the viewModel. + /// + public event EventHandler Refresh; + + #region Public Properties + + /// + /// Get or Sets Zoom level. + /// + public double ZoomLevel + { + get + { + return this.zoomLevel; + } + + set + { + if (value > 0) + { + this.zoomLevel = value / 100.0; + this.OnNotifyPropertyChanged("ZoomLevel"); + } + } + } + + /// + /// Gets the tooltip for the refresh button. + /// + public static string RefreshTooltip + { + get { return string.Format(CultureInfo.CurrentUICulture, ShowCommandResources.RefreshShowCommandTooltipFormat, "import-module"); } + } + + /// + /// Gets or sets the visibility of the refresh button. + /// + public Visibility RefreshVisibility + { + get + { + return this.refreshVisibility; + } + + set + { + if (this.refreshVisibility == value) + { + return; + } + + this.refreshVisibility = value; + this.OnNotifyPropertyChanged("RefreshVisibility"); + } + } + + /// + /// Gets a value indicating whether common parameters are displayed. + /// + public bool NoCommonParameter + { + get { return this.noCommonParameter; } + } + + /// + /// Gets or sets the filterName of command. + /// + public string CommandNameFilter + { + get + { + return this.commandNameFilter; + } + + set + { + if (this.CommandNameFilter == value) + { + return; + } + + this.commandNameFilter = value; + if (this.selectedModule != null) + { + this.selectedModule.RefreshFilteredCommands(this.CommandNameFilter); + this.selectedModule.SelectedCommand = null; + } + + this.OnNotifyPropertyChanged("CommandNameFilter"); + } + } + + /// + /// Gets or sets the selected module being displayed in the GUI. + /// + public ModuleViewModel SelectedModule + { + get + { + return this.selectedModule; + } + + set + { + if (this.selectedModule == value) + { + return; + } + + if (this.selectedModule != null) + { + this.selectedModule.SelectedCommandNeedsImportModule -= this.SelectedModule_SelectedCommandNeedsImportModule; + this.selectedModule.SelectedCommandNeedsHelp -= this.SelectedModule_SelectedCommandNeedsHelp; + this.selectedModule.RunSelectedCommand -= this.SelectedModule_RunSelectedCommand; + this.selectedModule.PropertyChanged -= this.SelectedModule_PropertyChanged; + } + + this.selectedModule = value; + this.SetCanRun(); + this.SetCanCopy(); + + if (this.selectedModule != null) + { + this.selectedModule.RefreshFilteredCommands(this.CommandNameFilter); + this.selectedModule.SelectedCommandNeedsImportModule += this.SelectedModule_SelectedCommandNeedsImportModule; + this.selectedModule.SelectedCommandNeedsHelp += this.SelectedModule_SelectedCommandNeedsHelp; + this.selectedModule.RunSelectedCommand += this.SelectedModule_RunSelectedCommand; + this.selectedModule.PropertyChanged += this.SelectedModule_PropertyChanged; + this.selectedModule.SelectedCommand = null; + } + + this.OnNotifyPropertyChanged("SelectedModule"); + } + } + + /// + /// Gets a value indicating whether we can run a command. + /// + public bool CanRun + { + get + { + return this.canRun; + } + } + + /// + /// Gets a value indicating whether we can copy a command. + /// + public bool CanCopy + { + get + { + return this.canCopy; + } + } + + /// + /// Gets the Modules parameter. + /// + public List Modules + { + get { return this.modules; } + } + + /// + /// Gets the visibility of the wait message. + /// + public Visibility WaitMessageVisibility + { + get + { + return this.waitMessageDisplayed ? Visibility.Visible : Visibility.Hidden; + } + } + + /// + /// Gets the visibility of the main grid. + /// + public Visibility MainGridVisibility + { + get + { + return this.waitMessageDisplayed ? Visibility.Hidden : Visibility.Visible; + } + } + + /// + /// Gets a value indicating whether the main grid is displayed. + /// + public bool MainGridDisplayed + { + get + { + return !this.waitMessageDisplayed; + } + } + + /// + /// Gets or sets a value indicating whether the wait message is displayed. + /// + public bool WaitMessageDisplayed + { + get + { + return this.waitMessageDisplayed; + } + + set + { + if (this.waitMessageDisplayed == value) + { + return; + } + + this.waitMessageDisplayed = value; + this.SetCanCopy(); + this.SetCanRun(); + this.OnNotifyPropertyChanged("WaitMessageDisplayed"); + this.OnNotifyPropertyChanged("WaitMessageVisibility"); + this.OnNotifyPropertyChanged("MainGridDisplayed"); + this.OnNotifyPropertyChanged("MainGridVisibility"); + } + } + + /// + /// Gets or sets an extra viewModel object that allows callers to control certain aspects of the GUI. + /// + public object ExtraViewModel + { + get + { + return this.extraViewModel; + } + + set + { + if (this.extraViewModel == value) + { + return; + } + + this.extraViewModel = value; + this.OnNotifyPropertyChanged("ExtraViewModel"); + } + } + #endregion + + /// + /// Returns the selected script. + /// + /// The selected script. + public string GetScript() + { + if (this.SelectedModule == null) + { + return null; + } + + if (this.SelectedModule.SelectedCommand == null) + { + return null; + } + + return this.SelectedModule.SelectedCommand.GetScript(); + } + + /// + /// Triggers Refresh. + /// + internal void OnRefresh() + { + EventHandler handler = this.Refresh; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + + #region Private Methods + /// + /// If current modules name is ALL, then return true. + /// + /// The modules name. + /// Return true is the module name is ALLModulesViewModel. + private static bool IsAll(string name) + { + return name.Equals(ShowCommandResources.All, StringComparison.Ordinal); + } + + /// + /// Monitors property changes in the selected module to call: + /// SetCanRun for IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues + /// SetCanCopy for SetCanCopy + /// + /// Event sender. + /// Event arguments. + private void SelectedModule_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues") + { + this.SetCanRun(); + } + else if (e.PropertyName == "IsThereASelectedCommand") + { + this.SetCanCopy(); + } + } + + /// + /// Called to set this.CanRun when: + /// The SelectedModule changes, since there will be no selected command in the new module, and CanRun should be false + /// WaitMessageDisplayedMessage changes since this being true will cause this.MainGridDisplayed to be false and CanRun should be false + /// IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues changes in the selected module + /// + private void SetCanRun() + { + bool newValue = this.selectedModule != null && this.MainGridDisplayed && + this.selectedModule.IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues; + + if (this.canRun == newValue) + { + return; + } + + this.canRun = newValue; + this.OnNotifyPropertyChanged("CanRun"); + } + + /// + /// Called to set this.CanCopy when: + /// The SelectedModule changes, since there will be no selected command in the new module, and CanCopy should be false + /// WaitMessageDisplayedMessage changes since this being true will cause this.MainGridDisplayed to be false and CanCopy should be false + /// IsThereASelectedCommand changes in the selected module + /// + private void SetCanCopy() + { + bool newValue = this.selectedModule != null && this.MainGridDisplayed && this.selectedModule.IsThereASelectedCommand; + + if (this.canCopy == newValue) + { + return; + } + + this.canCopy = newValue; + this.OnNotifyPropertyChanged("CanCopy"); + } + + /// + /// Initialize AllModulesViewModel. + /// + /// All loaded modules. + /// List of commands in all modules. + /// Whether showing common parameter. + private void Initialization(Dictionary importedModules, IEnumerable commands, bool noCommonParameterInModel) + { + if (commands == null) + { + return; + } + + Dictionary rawModuleViewModels = new Dictionary(); + + this.noCommonParameter = noCommonParameterInModel; + + // separates commands in their Modules + foreach (ShowCommandCommandInfo command in commands) + { + ModuleViewModel moduleViewModel; + if (!rawModuleViewModels.TryGetValue(command.ModuleName, out moduleViewModel)) + { + moduleViewModel = new ModuleViewModel(command.ModuleName, importedModules); + rawModuleViewModels.Add(command.ModuleName, moduleViewModel); + } + + CommandViewModel commandViewModel; + + try + { + commandViewModel = CommandViewModel.GetCommandViewModel(moduleViewModel, command, noCommonParameterInModel); + } + catch (RuntimeException) + { + continue; + } + + moduleViewModel.Commands.Add(commandViewModel); + moduleViewModel.SetAllModules(this); + } + + // populates this.modules + this.modules = new List(); + + // if there is just one module then use only it + if (rawModuleViewModels.Values.Count == 1) + { + this.modules.Add(rawModuleViewModels.Values.First()); + this.modules[0].SortCommands(false); + this.SelectedModule = this.modules[0]; + return; + } + + // 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); + + if (rawModuleViewModels.Values.Count > 0) + { + foreach (ModuleViewModel module in rawModuleViewModels.Values) + { + module.SortCommands(false); + this.modules.Add(module); + + allCommandsModule.Commands.AddRange(module.Commands); + } + } + + allCommandsModule.SortCommands(true); + + this.modules.Sort(this.Compare); + this.SelectedModule = this.modules.Count == 0 ? null : this.modules[0]; + } + + /// + /// Compare two ModuleViewModel target and source. + /// + /// The source ModuleViewModel. + /// The target ModuleViewModel. + /// Compare result. + private int Compare(ModuleViewModel source, ModuleViewModel target) + { + if (AllModulesViewModel.IsAll(source.Name) && !AllModulesViewModel.IsAll(target.Name)) + { + return -1; + } + + if (!AllModulesViewModel.IsAll(source.Name) && AllModulesViewModel.IsAll(target.Name)) + { + return 1; + } + + return string.Compare(source.Name, target.Name, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Called when the SelectedCommandNeedsHelp event is triggered in the Selected Module. + /// + /// Event sender. + /// Event arguments. + private void SelectedModule_SelectedCommandNeedsHelp(object sender, HelpNeededEventArgs e) + { + this.OnSelectedCommandInSelectedModuleNeedsHelp(e); + } + + /// + /// Called when the SelectedCommandNeedsImportModule event is triggered in the Selected Module. + /// + /// Event sender. + /// Event arguments. + private void SelectedModule_SelectedCommandNeedsImportModule(object sender, ImportModuleEventArgs e) + { + this.OnSelectedCommandInSelectedModuleNeedsImportModule(e); + } + + /// + /// Triggers SelectedCommandInSelectedModuleNeedsHelp. + /// + /// Event arguments. + private void OnSelectedCommandInSelectedModuleNeedsHelp(HelpNeededEventArgs e) + { + EventHandler handler = this.SelectedCommandInSelectedModuleNeedsHelp; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Triggers SelectedCommandInSelectedModuleNeedsImportModule. + /// + /// Event arguments. + private void OnSelectedCommandInSelectedModuleNeedsImportModule(ImportModuleEventArgs e) + { + EventHandler handler = this.SelectedCommandInSelectedModuleNeedsImportModule; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Called when the RunSelectedCommand is triggered in the selected module. + /// + /// Event sender. + /// Event arguments. + private void SelectedModule_RunSelectedCommand(object sender, CommandEventArgs e) + { + this.OnRunSelectedCommandInSelectedModule(e); + } + + /// + /// Triggers RunSelectedCommandInSelectedModule. + /// + /// Event arguments. + private void OnRunSelectedCommandInSelectedModule(CommandEventArgs e) + { + EventHandler handler = this.RunSelectedCommandInSelectedModule; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// If property changed will be notify. + /// + /// The changed property. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandEventArgs.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandEventArgs.cs new file mode 100644 index 00000000000..857d466c80f --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandEventArgs.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Arguments for the event triggered when something happens at the cmdlet level. + /// + public class CommandEventArgs : EventArgs + { + /// + /// the command targeted by the event. + /// + private CommandViewModel command; + + /// + /// Initializes a new instance of the CommandEventArgs class. + /// + /// The command targeted by the event. + public CommandEventArgs(CommandViewModel command) + { + this.command = command; + } + + /// + /// Gets the command targeted by the event. + /// + public CommandViewModel Command + { + get { return this.command; } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs new file mode 100644 index 00000000000..cfa2798963c --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/CommandViewModel.cs @@ -0,0 +1,645 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Windows; + +using Microsoft.Management.UI; +using Microsoft.Management.UI.Internal; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +using SMAI = System.Management.Automation.Internal; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Contains information about a cmdlet's Shard ParameterSet, + /// ParameterSets, Parameters, Common Parameters and error message. + /// + public class CommandViewModel : INotifyPropertyChanged + { + #region Private Fields + /// + /// The name of the AllParameterSets. + /// + private const string SharedParameterSetName = "__AllParameterSets"; + + /// + /// Grid length constant. + /// + private static readonly GridLength star = new GridLength(1, GridUnitType.Star); + + /// + /// The module containing this cmdlet in the gui. + /// + private ModuleViewModel parentModule; + + /// + /// The name of the default ParameterSet. + /// + private string defaultParameterSetName; + + /// + /// Field used for the AreCommonParametersExpanded parameter. + /// + private bool areCommonParametersExpanded; + + /// + /// Field used for the SelectedParameterSet parameter. + /// + private ParameterSetViewModel selectedParameterSet; + + /// + /// Field used for the ParameterSets parameter. + /// + private List parameterSets = new List(); + + /// + /// Field used for the ParameterSetTabControlVisibility parameter. + /// + private bool noCommonParameters; + + /// + /// Field used for the CommonParameters parameter. + /// + private ParameterSetViewModel comonParameters; + + /// + /// The ShowCommandCommandInfo this model is based on. + /// + private ShowCommandCommandInfo commandInfo; + + /// + /// value indicating whether the selected parameter set has all mandatory parameters valid. + /// + private bool selectedParameterSetAllMandatoryParametersHaveValues; + + /// + /// value indicating whether the command name should be qualified by the module in GetScript. + /// + private bool moduleQualifyCommandName; + + /// + /// The height for common parameters that will depend on CommonParameterVisibility. + /// + private GridLength commonParametersHeight; + #endregion + + /// + /// Prevents a default instance of the CommandViewModel class from being created. + /// + private CommandViewModel() + { + } + + #region INotifyPropertyChanged Members + + /// + /// PropertyChanged Event. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + /// + /// Indicates the command needs to display the help for a command. + /// + public event EventHandler HelpNeeded; + + /// + /// Indicates a module needs to be imported. + /// + public event EventHandler ImportModule; + + #region Public Properties + /// + /// Gets or sets a value indicating whether the command name should be qualified by the module in GetScript. + /// + public bool ModuleQualifyCommandName + { + get { return this.moduleQualifyCommandName; } + set { this.moduleQualifyCommandName = value; } + } + + /// + /// Gets or sets a value indicating whether the common parameters are expanded. + /// + public bool AreCommonParametersExpanded + { + get + { + return this.areCommonParametersExpanded; + } + + set + { + if (this.areCommonParametersExpanded == value) + { + return; + } + + this.areCommonParametersExpanded = value; + this.OnNotifyPropertyChanged("AreCommonParametersExpanded"); + this.SetCommonParametersHeight(); + } + } + + /// + /// Gets or sets the SelectedParameterSet parameter. + /// + public ParameterSetViewModel SelectedParameterSet + { + get + { + return this.selectedParameterSet; + } + + set + { + if (this.selectedParameterSet != value) + { + if (this.selectedParameterSet != null) + { + this.selectedParameterSet.PropertyChanged -= this.SelectedParameterSet_PropertyChanged; + } + + this.selectedParameterSet = value; + if (this.selectedParameterSet != null) + { + this.selectedParameterSet.PropertyChanged += this.SelectedParameterSet_PropertyChanged; + this.SelectedParameterSetAllMandatoryParametersHaveValues = this.SelectedParameterSet.AllMandatoryParametersHaveValues; + } + else + { + this.SelectedParameterSetAllMandatoryParametersHaveValues = true; + } + + this.OnNotifyPropertyChanged("SelectedParameterSet"); + } + } + } + + /// + /// Gets or sets a value indicating whether the selected parameter set has all mandatory parameters valid. + /// If there is no selected parameter set this value is true + /// + public bool SelectedParameterSetAllMandatoryParametersHaveValues + { + get + { + return this.selectedParameterSetAllMandatoryParametersHaveValues; + } + + set + { + if (this.selectedParameterSetAllMandatoryParametersHaveValues == value) + { + return; + } + + this.selectedParameterSetAllMandatoryParametersHaveValues = value; + this.OnNotifyPropertyChanged("SelectedParameterSetAllMandatoryParametersHaveValues"); + } + } + + /// + /// Gets the ParameterSets parameter. + /// + public List ParameterSets + { + get { return this.parameterSets; } + } + + /// + /// Gets the visibility for the tab control displaying several ParameterSetControl. This is displayed when there are more than 1 parameter sets. + /// + public Visibility ParameterSetTabControlVisibility + { + get { return (this.ParameterSets.Count > 1) && this.IsImported ? Visibility.Visible : Visibility.Collapsed; } + } + + /// + /// Gets the visibility for the single ParameterSetControl displayed when there is only 1 parameter set. + /// + public Visibility SingleParameterSetControlVisibility + { + get { return (this.ParameterSets.Count == 1) ? Visibility.Visible : Visibility.Collapsed; } + } + + /// + /// Gets the CommonParameters parameter. + /// + public ParameterSetViewModel CommonParameters + { + get { return this.comonParameters; } + } + + /// + /// Gets the CommonParameterVisibility parameter. + /// + public Visibility CommonParameterVisibility + { + get { return this.noCommonParameters || (this.CommonParameters.Parameters.Count == 0) ? Visibility.Collapsed : Visibility.Visible; } + } + + /// + /// Gets or sets the height for common parameters that will depend on CommonParameterVisibility. + /// + public GridLength CommonParametersHeight + { + get + { + return this.commonParametersHeight; + } + + set + { + if (this.commonParametersHeight == value) + { + return; + } + + this.commonParametersHeight = value; + this.OnNotifyPropertyChanged("CommonParametersHeight"); + } + } + + /// + /// Gets the visibility for the control displayed when the module is not imported. + /// + public Visibility NotImportedVisibility + { + get + { + return this.IsImported ? Visibility.Collapsed : Visibility.Visible; + } + } + + /// + /// Gets the visibility for the control displayed when there are no parameters. + /// + public Visibility NoParameterVisibility + { + get + { + bool hasNoParameters = this.ParameterSets.Count == 0 || (this.ParameterSets.Count == 1 && this.ParameterSets[0].Parameters.Count == 0); + return this.IsImported && hasNoParameters ? Visibility.Visible : Visibility.Collapsed; + } + } + + /// + /// Gets a value indicating whether the cmdlet comes from a module which is imported. + /// + public bool IsImported + { + get + { + return this.commandInfo.Module == null || this.ParentModule.IsModuleImported; + } + } + + /// + /// Gets the Name parameter. + /// + public string Name + { + get + { + if (this.commandInfo != null) + { + return this.commandInfo.Name; + } + + return string.Empty; + } + } + + /// + /// Gets the module path if it is not null or empty, or the name otherwise. + /// + public string ModuleName + { + get + { + if (this.commandInfo != null && this.commandInfo.ModuleName != null) + { + return this.commandInfo.ModuleName; + } + + return string.Empty; + } + } + + /// + /// Gets the module containing this cmdlet in the GUI. + /// + public ModuleViewModel ParentModule + { + get + { + return this.parentModule; + } + } + + /// + /// Gets Tooltip string for the cmdlet. + /// + public string ToolTip + { + get + { + return string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.CmdletTooltipFormat, + this.Name, + this.ParentModule.DisplayName, + this.IsImported ? ShowCommandResources.Imported : ShowCommandResources.NotImported); + } + } + + /// + /// Gets the message to be displayed when the cmdlet belongs to a module that is not imported. + /// + public string ImportModuleMessage + { + get + { + return string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.NotImportedFormat, + this.ModuleName, + this.Name, + ShowCommandResources.ImportModuleButtonText); + } + } + + /// + /// Gets the title for the cmdlet details. + /// + public string DetailsTitle + { + get + { + if (this.IsImported) + { + return string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.DetailsParameterTitleFormat, + this.Name); + } + else + { + return string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.NameLabelFormat, + this.Name); + } + } + } + #endregion + + /// + /// Gets a Grid length constant. + /// + internal static GridLength Star + { + get { return CommandViewModel.star; } + } + + /// + /// Gets the builded PowerShell script. + /// + /// Return script as string. + public string GetScript() + { + StringBuilder builder = new StringBuilder(); + + string commandName = this.commandInfo.CommandType == CommandTypes.ExternalScript ? this.commandInfo.Definition : this.Name; + + if (this.ModuleQualifyCommandName && !string.IsNullOrEmpty(this.ModuleName)) + { + commandName = this.ModuleName + "\\" + commandName; + } + + if (commandName.Contains(' ')) + { + builder.Append($"& \"{commandName}\""); + } + else + { + builder.Append(commandName); + } + + builder.Append(' '); + + if (this.SelectedParameterSet != null) + { + builder.Append(this.SelectedParameterSet.GetScript()); + builder.Append(' '); + } + + if (this.CommonParameters != null) + { + builder.Append(this.CommonParameters.GetScript()); + } + + string script = builder.ToString(); + + return script.Trim(); + } + + /// + /// Showing help information for current active cmdlet. + /// + public void OpenHelpWindow() + { + this.OnHelpNeeded(); + } + + /// + /// Determines whether current command name and a specified ParameterSetName have same name. + /// + /// The name of ShareParameterSet. + /// Return true is ShareParameterSet. Else return false. + internal static bool IsSharedParameterSetName(string name) + { + return name.Equals(CommandViewModel.SharedParameterSetName, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Creates a new CommandViewModel out the . + /// + /// Module to which the CommandViewModel will belong to. + /// Will showing command. + /// True to ommit displaying common parameter. + /// If commandInfo is null + /// + /// If could not create the CommandViewModel. For instance the ShowCommandCommandInfo corresponding to + /// the following function will throw a RuntimeException when the ShowCommandCommandInfo Parameters + /// are retrieved: + /// function CrashMe ([I.Am.A.Type.That.Does.Not.Exist]$name) {} + /// + /// The CommandViewModel corresponding to commandInfo. + internal static CommandViewModel GetCommandViewModel(ModuleViewModel module, ShowCommandCommandInfo commandInfo, bool noCommonParameters) + { + ArgumentNullException.ThrowIfNull(commandInfo); + + CommandViewModel returnValue = new CommandViewModel(); + returnValue.commandInfo = commandInfo; + returnValue.noCommonParameters = noCommonParameters; + returnValue.parentModule = module; + + Dictionary commonParametersTable = new Dictionary(); + + foreach (ShowCommandParameterSetInfo parameterSetInfo in commandInfo.ParameterSets) + { + if (parameterSetInfo.IsDefault) + { + returnValue.defaultParameterSetName = parameterSetInfo.Name; + } + + List parametersForParameterSet = new List(); + foreach (ShowCommandParameterInfo parameterInfo in parameterSetInfo.Parameters) + { + bool isCommon = Cmdlet.CommonParameters.Contains(parameterInfo.Name); + + if (isCommon) + { + if (!commonParametersTable.ContainsKey(parameterInfo.Name)) + { + commonParametersTable.Add(parameterInfo.Name, new ParameterViewModel(parameterInfo, parameterSetInfo.Name)); + } + + continue; + } + + parametersForParameterSet.Add(new ParameterViewModel(parameterInfo, parameterSetInfo.Name)); + } + + if (parametersForParameterSet.Count != 0) + { + returnValue.ParameterSets.Add(new ParameterSetViewModel(parameterSetInfo.Name, parametersForParameterSet)); + } + } + + List commonParametersList = commonParametersTable.Values.ToList(); + returnValue.comonParameters = new ParameterSetViewModel(string.Empty, commonParametersList); + + returnValue.parameterSets.Sort(returnValue.Compare); + + if (returnValue.parameterSets.Count > 0) + { + // Setting SelectedParameterSet will also set SelectedParameterSetAllMandatoryParametersHaveValues + returnValue.SelectedParameterSet = returnValue.ParameterSets[0]; + } + else + { + returnValue.SelectedParameterSetAllMandatoryParametersHaveValues = true; + } + + returnValue.SetCommonParametersHeight(); + + return returnValue; + } + + /// + /// Called to trigger the event fired when help is needed for the command. + /// + internal void OnHelpNeeded() + { + EventHandler handler = this.HelpNeeded; + if (handler != null) + { + handler(this, new HelpNeededEventArgs(this.Name)); + } + } + + /// + /// Called to trigger the event fired when a module needs to be imported. + /// + internal void OnImportModule() + { + EventHandler handler = this.ImportModule; + if (handler != null) + { + handler(this, new EventArgs()); + } + } + + #region Private Methods + /// + /// Called to set the height for common parameters initially or when the AreCommonParametersExpanded changes. + /// + private void SetCommonParametersHeight() + { + this.CommonParametersHeight = this.AreCommonParametersExpanded ? CommandViewModel.Star : GridLength.Auto; + } + + /// + /// Compares source and target by being the default parameter set and then by name. + /// + /// Source paremeterset. + /// Target parameterset. + /// 0 if they are the same, -1 if source is smaller, 1 if source is larger. + private int Compare(ParameterSetViewModel source, ParameterSetViewModel target) + { + if (this.defaultParameterSetName != null) + { + if (source.Name.Equals(this.defaultParameterSetName) && target.Name.Equals(this.defaultParameterSetName)) + { + return 0; + } + + if (source.Name.Equals(this.defaultParameterSetName, StringComparison.Ordinal)) + { + return -1; + } + + if (target.Name.Equals(this.defaultParameterSetName, StringComparison.Ordinal)) + { + return 1; + } + } + + return string.CompareOrdinal(source.Name, target.Name); + } + + /// + /// If property changed will be notify. + /// + /// The changed property. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// Called when the PropertyChanged event is triggered on the SelectedParameterSet. + /// + /// Event sender. + /// Event arguments. + private void SelectedParameterSet_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (!e.PropertyName.Equals("AllMandatoryParametersHaveValues")) + { + return; + } + + this.SelectedParameterSetAllMandatoryParametersHaveValues = this.SelectedParameterSet.AllMandatoryParametersHaveValues; + } + + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/HelpNeededEventArgs.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/HelpNeededEventArgs.cs new file mode 100644 index 00000000000..3d4c42a6cbe --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/HelpNeededEventArgs.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Arguments for the event triggered when it is necessary to display help for a command. + /// + public class HelpNeededEventArgs : EventArgs + { + /// + /// the name for the command needing help. + /// + private string commandName; + + /// + /// Initializes a new instance of the HelpNeededEventArgs class. + /// + /// The name for the command needing help. + public HelpNeededEventArgs(string commandName) + { + this.commandName = commandName; + } + + /// + /// Gets the name for the command needing help. + /// + public string CommandName + { + get { return this.commandName; } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ImportModuleEventArgs.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ImportModuleEventArgs.cs new file mode 100644 index 00000000000..3d7a7ccf3f0 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ImportModuleEventArgs.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Arguments for the event triggered when it is necessary to display help for a command. + /// + public class ImportModuleEventArgs : EventArgs + { + /// + /// the name for the command belonging to the module to be imported. + /// + private string commandName; + + /// + /// the module path or name for the module we want to import. + /// + private string parentModuleName; + + /// + /// the name of the module that is selected, which can be different from parentModuleName + /// if "All" is selected + /// + private string selectedModuleName; + + /// + /// Initializes a new instance of the ImportModuleEventArgs class. + /// + /// The name for the command needing help. + /// The name of the module containing the command. + /// + /// the name of the module that is selected, which can be different from parentModuleName + /// if "All" is selected + /// + public ImportModuleEventArgs(string commandName, string parentModuleName, string selectedModuleName) + { + this.commandName = commandName; + this.parentModuleName = parentModuleName; + this.selectedModuleName = selectedModuleName; + } + + /// + /// Gets the name for the command belonging to the module to be imported. + /// + public string CommandName + { + get { return this.commandName; } + } + + /// + /// Gets the module path or name for the module we want to import. + /// + public string ParentModuleName + { + get { return this.parentModuleName; } + } + + /// + /// Gets the name of the module that is selected, which can be different from parentModuleName + /// if "All" is selected + /// + public string SelectedModuleName + { + get { return this.selectedModuleName; } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs new file mode 100644 index 00000000000..950dbe93758 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ModuleViewModel.cs @@ -0,0 +1,528 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Management.Automation; +using System.Windows; + +using Microsoft.Management.UI.Internal; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// ModuleViewModel Contains information about a PowerShell module. + /// + public class ModuleViewModel : INotifyPropertyChanged + { + /// + /// True if the module is imported. + /// + private bool isModuleImported; + + /// + /// Field used for the Name parameter. + /// + private string name; + + /// + /// Filter commands property of this module. + /// + private ObservableCollection filteredCommands; + + /// + /// The selected command property of this module. + /// + private CommandViewModel selectedCommand; + + /// + /// Field used for the Commands parameter. + /// + private List commands; + + /// + /// value indicating whether there is a selected command which belongs to an imported module, + /// with no parameter sets or with a selected parameter set where all mandatory parameters have values + /// + private bool isThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues; + + /// + /// value indicating whether there is a selected command. + /// + private bool isThereASelectedCommand; + + /// + /// The AllModulesViewModel containing this, if any. + /// + private AllModulesViewModel allModules; + + #region Construction and Destructor + /// + /// Initializes a new instance of the ModuleViewModel class. + /// + /// Module name. + /// All loaded modules. + public ModuleViewModel(string name, Dictionary importedModules) + { + ArgumentNullException.ThrowIfNull(name); + + this.name = name; + this.commands = new List(); + this.filteredCommands = new ObservableCollection(); + + // This check looks to see if the given module name shows up in + // the set of modules that are known to be imported in the current + // session. In remote PowerShell sessions, the core cmdlet module + // Microsoft.PowerShell.Core doesn't appear as being imported despite + // always being loaded by default. To make sure we don't incorrectly + // mark this module as not imported, check for it by name. + this.isModuleImported = + importedModules == null ? true : name.Length == 0 || + importedModules.ContainsKey(name) || + string.Equals("Microsoft.PowerShell.Core", name, StringComparison.OrdinalIgnoreCase); + } + #endregion + + #region INotifyPropertyChanged Members + + /// + /// PropertyChanged Event. + /// + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + /// + /// Indicates the selected command in needs to display the help for a command. + /// + public event EventHandler SelectedCommandNeedsHelp; + + /// + /// Indicates the selected command needs to import a module. + /// + public event EventHandler SelectedCommandNeedsImportModule; + + /// + /// Indicates the selected command should be run. + /// + public event EventHandler RunSelectedCommand; + + #region Public Property + /// + /// Gets the name property of this ModuleView. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the GUI friendly module name. + /// + public string DisplayName + { + get + { + if (!string.IsNullOrEmpty(this.name)) + { + return this.name; + } + + return ShowCommandResources.NoModuleName; + } + } + + /// + /// Gets CommandControl is visibility or not. + /// + public Visibility CommandControlVisibility + { + get { return this.selectedCommand == null ? Visibility.Collapsed : Visibility.Visible; } + } + + /// + /// Gets CommandControl Height. + /// + public GridLength CommandRowHeight + { + get { return this.selectedCommand == null ? GridLength.Auto : CommandViewModel.Star; } + } + + /// + /// Gets the commands under in this module. + /// + public List Commands + { + get { return this.commands; } + } + + /// + /// Gets the filter commands of this module. + /// + public ObservableCollection FilteredCommands + { + get { return this.filteredCommands; } + } + + /// + /// Gets or sets the selected commands of this module. + /// + public CommandViewModel SelectedCommand + { + get + { + return this.selectedCommand; + } + + set + { + if (value == this.selectedCommand) + { + return; + } + + if (this.selectedCommand != null) + { + this.selectedCommand.PropertyChanged -= this.SelectedCommand_PropertyChanged; + this.selectedCommand.HelpNeeded -= this.SelectedCommand_HelpNeeded; + this.selectedCommand.ImportModule -= this.SelectedCommand_ImportModule; + } + + this.selectedCommand = value; + + this.SetIsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues(); + + if (this.selectedCommand != null) + { + this.selectedCommand.PropertyChanged += this.SelectedCommand_PropertyChanged; + this.selectedCommand.HelpNeeded += this.SelectedCommand_HelpNeeded; + this.selectedCommand.ImportModule += this.SelectedCommand_ImportModule; + this.IsThereASelectedCommand = true; + } + else + { + this.IsThereASelectedCommand = false; + } + + this.OnNotifyPropertyChanged("SelectedCommand"); + this.OnNotifyPropertyChanged("CommandControlVisibility"); + this.OnNotifyPropertyChanged("CommandRowHeight"); + } + } + + /// + /// Gets or sets a value indicating whether there is a selected command. + /// + public bool IsThereASelectedCommand + { + get + { + return this.isThereASelectedCommand; + } + + set + { + if (value == this.isThereASelectedCommand) + { + return; + } + + this.isThereASelectedCommand = value; + this.OnNotifyPropertyChanged("IsThereASelectedCommand"); + } + } + + /// + /// Gets or sets a value indicating whether there is a selected command which belongs + /// to an imported module, with no parameter sets or with a selected parameter set + /// where all mandatory parameters have values + /// + public bool IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues + { + get + { + return this.isThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues; + } + + set + { + if (value == this.isThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues) + { + return; + } + + this.isThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues = value; + + this.OnNotifyPropertyChanged("IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues"); + } + } + + /// + /// Gets the AllModulesViewModel containing this, if any. + /// + public AllModulesViewModel AllModules + { + get + { + return this.allModules; + } + } + #endregion + + /// + /// Gets a value indicating whether the module is imported. + /// + internal bool IsModuleImported + { + get + { + return this.isModuleImported; + } + } + + /// + /// Sets the AllModulesViewModel containing this. + /// + /// The AllModulesViewModel containing this. + internal void SetAllModules(AllModulesViewModel parentAllModules) + { + this.allModules = parentAllModules; + } + + /// + /// Sorts commands and optionally sets ModuleQualifyCommandName. + /// + /// True to mark repeated commands with a flag that will produce a module qualified name in GetScript. + internal void SortCommands(bool markRepeatedCmdlets) + { + this.commands.Sort(this.Compare); + + if (!markRepeatedCmdlets || this.commands.Count == 0) + { + return; + } + + CommandViewModel reference = this.commands[0]; + for (int i = 1; i < this.commands.Count; i++) + { + CommandViewModel command = this.commands[i]; + if (reference.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase)) + { + reference.ModuleQualifyCommandName = true; + command.ModuleQualifyCommandName = true; + } + else + { + reference = command; + } + } + } + + /// + /// According commandNameFilter to filter command,and added the filter commands into filteredCommands property. + /// + /// Current filter. + internal void RefreshFilteredCommands(string filter) + { + this.filteredCommands.Clear(); + if (string.IsNullOrEmpty(filter)) + { + foreach (CommandViewModel command in this.Commands) + { + this.filteredCommands.Add(command); + } + + return; + } + + WildcardPattern filterPattern = null; + if (WildcardPattern.ContainsWildcardCharacters(filter)) + { + filterPattern = new WildcardPattern(filter, WildcardOptions.IgnoreCase); + } + + foreach (CommandViewModel command in this.Commands) + { + if (ModuleViewModel.Matches(filterPattern, command.Name, filter)) + { + this.filteredCommands.Add(command); + continue; + } + + if (filterPattern != null) + { + continue; + } + + string[] textSplit = filter.Split(' '); + if (textSplit.Length != 2) + { + continue; + } + + if (ModuleViewModel.Matches(filterPattern, command.Name, textSplit[0] + "-" + textSplit[1])) + { + this.filteredCommands.Add(command); + } + } + } + + /// + /// Called in response to a GUI event that requires the command to be run. + /// + internal void OnRunSelectedCommand() + { + EventHandler handler = this.RunSelectedCommand; + if (handler != null) + { + handler(this, new CommandEventArgs(this.SelectedCommand)); + } + } + + /// + /// Triggers the SelectedCommandNeedsHelp event. + /// + /// Event arguments. + internal void OnSelectedCommandNeedsHelp(HelpNeededEventArgs e) + { + EventHandler handler = this.SelectedCommandNeedsHelp; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Triggers the SelectedCommandNeedsImportModule event. + /// + internal void OnSelectedCommandNeedsImportModule() + { + EventHandler handler = this.SelectedCommandNeedsImportModule; + if (handler != null) + { + handler(this, new ImportModuleEventArgs(this.SelectedCommand.Name, this.SelectedCommand.ModuleName, this.Name)); + } + } + #region Private Method + + /// + /// Uses pattern matching if pattern is not null or calls MatchesEvenIfInPlural otherwise. + /// + /// Pattern corresponding to filter. + /// Command name string. + /// Filter string. + /// True if coparisonText matches str or pattern. + private static bool Matches(WildcardPattern filterPattern, string commandName, string filter) + { + if (filterPattern != null) + { + return filterPattern.IsMatch(commandName); + } + + return ModuleViewModel.MatchesEvenIfInPlural(commandName, filter); + } + + /// + /// Returns true if filter matches commandName, even when filter is in the plural. + /// + /// Command name string. + /// Filter string. + /// Return match result. + private static bool MatchesEvenIfInPlural(string commandName, string filter) + { + if (commandName.Contains(filter, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (filter.Length > 5 && filter.EndsWith("es", StringComparison.OrdinalIgnoreCase)) + { + ReadOnlySpan filterSpan = filter.AsSpan(0, filter.Length - 2); + return commandName.AsSpan().Contains(filterSpan, StringComparison.OrdinalIgnoreCase); + } + + if (filter.Length > 4 && filter.EndsWith("s", StringComparison.OrdinalIgnoreCase)) + { + ReadOnlySpan filterSpan = filter.AsSpan(0, filter.Length - 1); + return commandName.AsSpan().Contains(filterSpan, StringComparison.OrdinalIgnoreCase); + } + + return false; + } + + /// + /// Handles the HelpNeeded event in the selected command and triggers the SelectedCommandNeedsHelp event. + /// + /// HelpNeeded event sender. + /// HelpNeeded event argument. + private void SelectedCommand_HelpNeeded(object sender, HelpNeededEventArgs e) + { + this.OnSelectedCommandNeedsHelp(e); + } + + /// + /// Handles the ImportModule event in the selected command and triggers the SelectedCommandNeedsImportModule event. + /// + /// HelpNeeded event sender. + /// HelpNeeded event argument. + private void SelectedCommand_ImportModule(object sender, EventArgs e) + { + this.OnSelectedCommandNeedsImportModule(); + } + + /// + /// Called when the SelectedCommand property changes to update IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues. + /// + /// Event sender. + /// Event arguments. + private void SelectedCommand_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (!e.PropertyName.Equals("SelectedParameterSetAllMandatoryParametersHaveValues")) + { + return; + } + + this.SetIsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues(); + } + + /// + /// Called to set IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues when + /// SelectedParameterSetAllMandatoryParametersHaveValues changes in the SelectedCommand or + /// when the SelectedCommand changes + /// + private void SetIsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues() + { + this.IsThereASelectedImportedCommandWhereAllMandatoryParametersHaveValues = + this.selectedCommand != null && + this.selectedCommand.IsImported && + this.selectedCommand.SelectedParameterSetAllMandatoryParametersHaveValues; + } + + /// + /// Compare source commandmodule is equal like target commandmodule. + /// + /// Source commandmodule. + /// Target commandmodule. + /// Return compare result. + private int Compare(CommandViewModel source, CommandViewModel target) + { + return string.Compare(source.Name, target.Name, StringComparison.OrdinalIgnoreCase); + } + #endregion + + /// + /// If property changed will be notify. + /// + /// The changed property. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs new file mode 100644 index 00000000000..b4f42dd78a2 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterSetViewModel.cs @@ -0,0 +1,388 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Text; + +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Contains information about a single ParameterSet inside a cmdlet. + /// + public class ParameterSetViewModel : INotifyPropertyChanged + { + /// + /// Field used for the Name parameter. + /// + private string name; + + /// + /// value indicating all mandatory parameters have values. + /// + private bool allMandatoryParametersHaveValues; + + /// + /// Field used for the Parameters parameter. + /// + private List parameters; + + #region Construction and Destructor + + /// + /// Initializes a new instance of the ParameterSetViewModel class. + /// + /// The name 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) + { + ArgumentNullException.ThrowIfNull(name); + + ArgumentNullException.ThrowIfNull(parameters); + + parameters.Sort(Compare); + + this.name = name; + this.parameters = parameters; + foreach (ParameterViewModel parameter in this.parameters) + { + if (!parameter.IsMandatory) + { + continue; + } + + parameter.PropertyChanged += this.MandatoryParameter_PropertyChanged; + } + + this.EvaluateAllMandatoryParametersHaveValues(); + } + #endregion + + #region INotifyPropertyChanged Members + + /// + /// PropertyChanged Event. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region Public Property + /// + /// Gets the ParameterSet Name. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the Parameters of this parameterset. + /// + public List Parameters + { + get { return this.parameters; } + } + + /// + /// Gets or sets a value indicating whether all mandatory parameters have values. + /// + public bool AllMandatoryParametersHaveValues + { + get + { + return this.allMandatoryParametersHaveValues; + } + + set + { + if (this.allMandatoryParametersHaveValues != value) + { + this.allMandatoryParametersHaveValues = value; + this.OnNotifyPropertyChanged("AllMandatoryParametersHaveValues"); + } + } + } + #endregion + + #region Public Method + /// + /// Creates script according parameters of this parameterset. + /// + /// Return script of this parameterset parameters. + public string GetScript() + { + if (this.Parameters == null || this.Parameters.Count == 0) + { + return string.Empty; + } + + StringBuilder builder = new StringBuilder(); + foreach (ParameterViewModel parameter in this.Parameters) + { + if (parameter.Value == null) + { + continue; + } + + if (parameter.Parameter.ParameterType.IsSwitch) + { + if (((bool?)parameter.Value) == true) + { + builder.Append($"-{parameter.Name} "); + } + + continue; + } + + string parameterValueString = parameter.Value.ToString(); + + if (parameterValueString.Length == 0) + { + continue; + } + + ShowCommandParameterType parameterType = parameter.Parameter.ParameterType; + + if (parameterType.IsEnum || parameterType.IsString || (parameterType.IsArray && parameterType.ElementType.IsString)) + { + parameterValueString = ParameterSetViewModel.GetDelimitedParameter(parameterValueString, "\"", "\""); + } + else if (parameterType.IsScriptBlock) + { + parameterValueString = ParameterSetViewModel.GetDelimitedParameter(parameterValueString, "{", "}"); + } + else + { + parameterValueString = ParameterSetViewModel.GetDelimitedParameter(parameterValueString, "(", ")"); + } + + builder.Append($"-{parameter.Name} {parameterValueString} "); + } + + return builder.ToString().Trim(); + } + + /// + /// Gets the individual parameter count of this parameterset. + /// + /// Return individual parameter count of this parameterset. + public int GetIndividualParameterCount() + { + if (this.Parameters == null || this.Parameters.Count == 0) + { + return 0; + } + + int i = 0; + + foreach (ParameterViewModel p in this.Parameters) + { + if (p.IsInSharedParameterSet) + { + return i; + } + + i++; + } + + return i; + } + + #endregion + + #region Internal Method + + /// + /// Compare source parametermodel is equal like target parametermodel. + /// + /// The source of parametermodel. + /// The target of parametermodel. + /// Return compare result. + internal static int Compare(ParameterViewModel source, ParameterViewModel target) + { + if (source.Parameter.IsMandatory && !target.Parameter.IsMandatory) + { + return -1; + } + + if (!source.Parameter.IsMandatory && target.Parameter.IsMandatory) + { + return 1; + } + + return string.Compare(source.Parameter.Name, target.Parameter.Name); + } + + #endregion + + /// + /// Gets the delimited parameter if it needs delimitation and is not delimited. + /// + /// Value needing delimitation. + /// Open delimitation. + /// Close delimitation. + /// 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(); + + if (parameterValueTrimmed.Length == 0) + { + return openDelimiter + parameterValue + closeDelimiter; + } + + char delimitationChar = ParameterSetViewModel.ParameterNeedsDelimitation(parameterValueTrimmed, openDelimiter.Length == 1 && openDelimiter[0] == '{'); + switch (delimitationChar) + { + case '1': + return openDelimiter + parameterValue + closeDelimiter; + case '\'': + return '\'' + parameterValue + '\''; + case '\"': + return '\"' + parameterValue + '\"'; + default: + return parameterValueTrimmed; + } + } + + /// + /// Returns '0' if the does not need delimitation, '1' if it does, and a quote character if it needs to be delimited with a quote. + /// + /// Parameter value to check. + /// True if the parameter value should be a scriptblock. + /// '0' if the parameter does not need delimitation, '1' if it needs, '\'' if it needs to be delimited with single quote and '\"' if it needs to be delimited with double quotes. + private static char ParameterNeedsDelimitation(string parameterValue, bool requireScriptblock) + { + Token[] tokens; + ParseError[] errors; + ScriptBlockAst values = Parser.ParseInput("commandName -parameterName " + parameterValue, out tokens, out errors); + + if (values == null || values.EndBlock == null || values.EndBlock.Statements.Count == 0) + { + return '1'; + } + + PipelineAst pipeline = values.EndBlock.Statements[0] as PipelineAst; + if (pipeline == null || pipeline.PipelineElements.Count == 0) + { + return '1'; + } + + CommandAst commandAst = pipeline.PipelineElements[0] as CommandAst; + + if (commandAst == null || commandAst.CommandElements.Count == 0) + { + return '1'; + } + + // 3 is for CommandName, Parameter and its value + if (commandAst.CommandElements.Count != 3) + { + return '1'; + } + + if (requireScriptblock) + { + ScriptBlockExpressionAst scriptAst = commandAst.CommandElements[2] as ScriptBlockExpressionAst; + return scriptAst == null ? '1' : '0'; + } + + StringConstantExpressionAst stringValue = commandAst.CommandElements[2] as StringConstantExpressionAst; + if (stringValue != null) + { + if (errors.Length == 0) + { + return '0'; + } + + char stringTerminationChar; + + if (stringValue.StringConstantType == StringConstantType.BareWord) + { + stringTerminationChar = parameterValue[0]; + } + else if (stringValue.StringConstantType == StringConstantType.DoubleQuoted || stringValue.StringConstantType == StringConstantType.DoubleQuotedHereString) + { + stringTerminationChar = '\"'; + } + else + { + stringTerminationChar = '\''; + } + + char oppositeTerminationChar = stringTerminationChar == '\"' ? '\'' : '\"'; + + // If the string is not terminated, it should be delimited by the opposite string termination character + return oppositeTerminationChar; + } + + if (errors.Length != 0) + { + return '1'; + } + + return '0'; + } + + /// + /// Called to evaluate the value of AllMandatoryParametersHaveValues. + /// + private void EvaluateAllMandatoryParametersHaveValues() + { + bool newCanRun = true; + foreach (ParameterViewModel parameter in this.parameters) + { + if (!parameter.IsMandatory) + { + continue; + } + + if (!parameter.HasValue) + { + newCanRun = false; + break; + } + } + + this.AllMandatoryParametersHaveValues = newCanRun; + } + + /// + /// If property changed will be notify. + /// + /// The changed property. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// Used to track changes to parameter values in order to verify the enabled state of buttons. + /// + /// Event arguments. + /// Event sender. + private void MandatoryParameter_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (!e.PropertyName.Equals("Value", StringComparison.Ordinal)) + { + return; + } + + this.EvaluateAllMandatoryParametersHaveValues(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs new file mode 100644 index 00000000000..93227df87a1 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/ViewModel/ParameterViewModel.cs @@ -0,0 +1,272 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Globalization; +using System.Management.Automation; +using System.Text; + +using Microsoft.Management.UI.Internal; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Contains information about a single parameter inside a parameter set. + /// If a parameter with the same name belongs to two (or more) parameter sets, + /// there will be two (or more) ParameterViewModel objects for the parameters, + /// each one inside its own ParameterSetViewModel. + /// + public class ParameterViewModel : INotifyPropertyChanged + { + /// + /// ParameterMetadata contains information that is the same throughout parameter sets + /// like Name and Type. + /// Note: It also happens to contain a list of all ParameterSetMetadata for the parametersets + /// in this cmdlet, but this information is not used in this class since if a parameter is + /// in multiple parametersets, there will be a ParameterViewModel for each time the parameter + /// appears in a parameterset. + /// + private ShowCommandParameterInfo parameter; + + /// + /// value entered in the GUI for the parameter. + /// + private object parameterValue; + + /// + /// Name of the parameter set this parameter is in. + /// + private string parameterSetName; + + #region Construction and Destructor + /// + /// Initializes a new instance of the ParameterViewModel class. + /// + /// The parameter information for this parameter. + /// The name of the parameter set this parameter is in. + public ParameterViewModel(ShowCommandParameterInfo parameter, string parameterSetName) + { + ArgumentNullException.ThrowIfNull(parameter); + + ArgumentNullException.ThrowIfNull(parameterSetName); + + this.parameter = parameter; + this.parameterSetName = parameterSetName; + + if (this.parameter.ParameterType.IsSwitch) + { + this.parameterValue = false; + } + else + { + this.parameterValue = string.Empty; + } + } + #endregion + + #region INotifyPropertyChanged Members + + /// + /// PropertyChanged Event. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region Properties + /// + /// Gets the ParameterMetadata that contains information that is the same throughout parameter sets + /// like Name and Type. + /// + public ShowCommandParameterInfo Parameter + { + get { return this.parameter; } + } + + /// + /// Gets or sets the value for this parameter from the GUI. + /// + public object Value + { + get + { + return this.parameterValue; + } + + set + { + if (this.parameterValue != value) + { + this.parameterValue = value; + this.OnNotifyPropertyChanged("Value"); + } + } + } + + /// + /// Gets the parameter name. + /// + public string Name + { + get { return this.Parameter.Name; } + } + + /// + /// Gets the name of the parameter set this parameter is in. + /// + public string ParameterSetName + { + get { return this.parameterSetName; } + } + + /// + /// Gets a value indicating whether this parameter is in the shared parameterset. + /// + public bool IsInSharedParameterSet + { + get { return CommandViewModel.IsSharedParameterSetName(this.parameterSetName); } + } + + /// + /// Gets Name with an extra suffix to indicate if the parameter is mandatory to serve. + /// + public string NameTextLabel + { + get + { + return this.Parameter.IsMandatory ? + string.Format( + CultureInfo.CurrentUICulture, + ShowCommandResources.MandatoryNameLabelFormat, + this.Name, + ShowCommandResources.MandatoryLabelSegment) : + string.Format( + CultureInfo.CurrentUICulture, + ShowCommandResources.NameLabelFormat, + this.Name); + } + } + + /// + /// Gets Label in the case this parameter is used in a combo box. + /// + public string NameCheckLabel + { + get + { + string returnValue = this.Parameter.Name; + if (this.Parameter.IsMandatory) + { + returnValue = string.Create(CultureInfo.CurrentUICulture, $"{returnValue}{ShowCommandResources.MandatoryLabelSegment}"); + } + + return returnValue; + } + } + + /// + /// Gets Tooltip string for the parameter. + /// + public string ToolTip + { + get + { + return ParameterViewModel.EvaluateTooltip( + this.Parameter.ParameterType.FullName, + this.Parameter.Position, + this.Parameter.IsMandatory, + this.IsInSharedParameterSet, + this.Parameter.ValueFromPipeline); + } + } + + /// + /// Gets a value indicating whether the parameter is mandatory. + /// + public bool IsMandatory + { + get { return this.Parameter.IsMandatory; } + } + + /// + /// Gets a value indicating whether the parameter has a value. + /// + public bool HasValue + { + get + { + if (this.Value == null) + { + return false; + } + + if (this.Parameter.ParameterType.IsSwitch) + { + return ((bool?)this.Value) == true; + } + + return this.Value.ToString().Length != 0; + } + } + #endregion + + /// + /// Evaluates the tooltip based on the parameters. + /// + /// Parameter type name. + /// Parameter position. + /// True if the parameter is mandatory. + /// True if the parameter is shared by parameter sets. + /// True if the parameter takes value from the pipeline. + /// the tooltip based on the parameters. + internal static string EvaluateTooltip(string typeName, int position, bool mandatory, bool shared, bool valueFromPipeline) + { + StringBuilder returnValue = new StringBuilder(string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.TypeFormat, + typeName)); + string newlineFormatString = Environment.NewLine + "{0}"; + + if (position >= 0) + { + string positionFormat = string.Format( + CultureInfo.CurrentCulture, + ShowCommandResources.PositionFormat, + position); + + returnValue.AppendFormat(CultureInfo.InvariantCulture, newlineFormatString, positionFormat); + } + + string optionalOrMandatory = mandatory ? ShowCommandResources.Mandatory : ShowCommandResources.Optional; + + returnValue.AppendFormat(CultureInfo.InvariantCulture, newlineFormatString, optionalOrMandatory); + + if (shared) + { + returnValue.AppendFormat(CultureInfo.InvariantCulture, newlineFormatString, ShowCommandResources.CommonToAllParameterSets); + } + + if (valueFromPipeline) + { + returnValue.AppendFormat(CultureInfo.InvariantCulture, newlineFormatString, ShowCommandResources.CanReceiveValueFromPipeline); + } + + return returnValue.ToString(); + } + + /// + /// If property changed will be notify. + /// + /// The changed property. + private void OnNotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml new file mode 100644 index 00000000000..185c5513ddb --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml.cs new file mode 100644 index 00000000000..8097952ffab --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/MultipleSelectionDialog.xaml.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Windows; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for MultipleSelectionDialog.xaml. + /// + public partial class MultipleSelectionDialog : Window + { + /// + /// Initializes a new instance of the MultipleSelectionDialog class. + /// + public MultipleSelectionDialog() + { + this.InitializeComponent(); + } + + /// + /// OK Click event function. + /// + /// Event sender. + /// Event arguments. + private void ButtonOK_Click(object sender, RoutedEventArgs e) + { + this.DialogResult = true; + this.Close(); + } + + /// + /// Cancel Click event function. + /// + /// Event sender. + /// Event arguments. + private void ButtonCancel_Click(object sender, RoutedEventArgs e) + { + this.DialogResult = false; + this.Close(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml new file mode 100644 index 00000000000..7a88bb3699b --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml.cs new file mode 100644 index 00000000000..b644264376b --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowAllModulesWindow.xaml.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Windows; +using System.Windows.Input; + +using Microsoft.Management.UI.Internal; +using Microsoft.Management.UI.Internal.ShowCommand; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for CmdletGUI.xaml. + /// + public partial class ShowAllModulesWindow : Window + { + /// + /// private constants for ZoomLevel. + /// + private double zoomLevel = 1.0; + + /// + /// Zoom Increments. + /// + private const double ZOOM_INCREMENT = 0.2; + + /// + /// Max ZoomLevel. + /// + private const double ZOOM_MAX = 3.0; + + /// + /// Min ZoomLevel. + /// + private const double ZOOM_MIN = 0.5; + + #region Construction and Destructor + + /// + /// Initializes a new instance of the ShowAllModulesWindow class. + /// + public ShowAllModulesWindow() + { + this.InitializeComponent(); + + if (this.AllModulesControl != null && this.AllModulesControl.ShowModuleControl != null) + { + this.AllModulesControl.ShowModuleControl.Owner = this; + } + + this.SizeChanged += this.ShowAllModulesWindow_SizeChanged; + this.LocationChanged += this.ShowAllModulesWindow_LocationChanged; + this.StateChanged += this.ShowAllModulesWindow_StateChanged; + this.Loaded += this.ShowAllModulesWindow_Loaded; + + RoutedCommand plusSettings = new RoutedCommand(); + KeyGestureConverter keyGestureConverter = new KeyGestureConverter(); + + try + { + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn1Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn2Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn3Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn4Shortcut)); + CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus)); + } + catch (NotSupportedException) + { + // localized has a problematic string - going to default + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Add")); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Plus")); + CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus)); + } + + RoutedCommand minusSettings = new RoutedCommand(); + try + { + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut1Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut2Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut3Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut4Shortcut)); + + CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus)); + } + catch (NotSupportedException) + { + // localized has a problematic string - going to default + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Subtract")); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Minus")); + CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus)); + } + } + + /// + /// Saves the user settings. + /// + /// Event arguments. + protected override void OnClosed(System.EventArgs e) + { + ShowCommandSettings.Default.Save(); + base.OnClosed(e); + } + + /// + /// Sets the focus on the CommandName control. + /// + /// Event sender. + /// Event arguments. + private void ShowAllModulesWindow_Loaded(object sender, RoutedEventArgs e) + { + this.AllModulesControl.CommandName.Focus(); + } + + /// + /// Saves size changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void ShowAllModulesWindow_SizeChanged(object sender, SizeChangedEventArgs e) + { + ShowCommandSettings.Default.ShowCommandsWidth = this.Width; + ShowCommandSettings.Default.ShowCommandsHeight = this.Height; + } + + /// + /// Saves position changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void ShowAllModulesWindow_LocationChanged(object sender, System.EventArgs e) + { + ShowCommandSettings.Default.ShowCommandsTop = this.Top; + ShowCommandSettings.Default.ShowCommandsLeft = this.Left; + } + + /// + /// Updates the user setting with window state. + /// + /// Event sender. + /// Event arguments. + private void ShowAllModulesWindow_StateChanged(object sender, System.EventArgs e) + { + ShowCommandSettings.Default.ShowCommandsWindowMaximized = this.WindowState == WindowState.Maximized; + } + + /// + /// Implements ZoomIn. + /// + /// . + /// . + private void ZoomEventHandlerPlus(object sender, ExecutedRoutedEventArgs e) + { + AllModulesViewModel viewModel = this.DataContext as AllModulesViewModel; + if (viewModel == null) + { + return; + } + + if (this.zoomLevel == 0) + { + this.zoomLevel = 1; + } + + if (this.zoomLevel < ZOOM_MAX) + { + // ViewModel applies ZoomLevel after dividing it by 100, So multiply it by 100 and then later reset to normal by dividing for next zoom + this.zoomLevel = (this.zoomLevel + ZOOM_INCREMENT) * 100; + viewModel.ZoomLevel = this.zoomLevel; + this.zoomLevel /= 100; + } + } + + /// + /// Implements ZoomOut. + /// + /// . + /// . + private void ZoomEventHandlerMinus(object sender, ExecutedRoutedEventArgs e) + { + AllModulesViewModel viewModel = this.DataContext as AllModulesViewModel; + if (viewModel == null) + { + return; + } + + if (this.zoomLevel >= ZOOM_MIN) + { + // ViewModel applies ZoomLevel after dividing it by 100, So multiply it by 100 and then later reset to normal by dividing it for next zoom + this.zoomLevel = (this.zoomLevel - ZOOM_INCREMENT) * 100; + viewModel.ZoomLevel = this.zoomLevel; + this.zoomLevel /= 100; + } + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml new file mode 100644 index 00000000000..6617070be4e --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml.cs b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml.cs new file mode 100644 index 00000000000..c03a42ed864 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ShowCommand/Windows/ShowCommandWindow.xaml.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Windows; + +using Microsoft.Management.UI.Internal.ShowCommand; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Interaction logic for CmdletGUI.xaml. + /// + public partial class ShowCommandWindow : Window + { + #region Construction and Destructor + + /// + /// Initializes a new instance of the ShowCommandWindow class. + /// + public ShowCommandWindow() + { + this.InitializeComponent(); + this.SizeChanged += this.ShowCommandWindow_SizeChanged; + this.LocationChanged += this.ShowCommandWindow_LocationChanged; + this.StateChanged += this.ShowCommandWindow_StateChanged; + } + + /// + /// Saves the user settings. + /// + /// Event arguments. + protected override void OnClosed(System.EventArgs e) + { + ShowCommandSettings.Default.Save(); + base.OnClosed(e); + } + + /// + /// Saves size changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void ShowCommandWindow_SizeChanged(object sender, SizeChangedEventArgs e) + { + ShowCommandSettings.Default.ShowOneCommandWidth = this.Width; + ShowCommandSettings.Default.ShowOneCommandHeight = this.Height; + } + + /// + /// Saves position changes in user settings. + /// + /// Event sender. + /// Event arguments. + private void ShowCommandWindow_LocationChanged(object sender, System.EventArgs e) + { + ShowCommandSettings.Default.ShowOneCommandTop = this.Top; + ShowCommandSettings.Default.ShowOneCommandLeft = this.Left; + } + + /// + /// Updates the user setting with window state. + /// + /// Event sender. + /// Event arguments. + private void ShowCommandWindow_StateChanged(object sender, System.EventArgs e) + { + ShowCommandSettings.Default.ShowOneCommandWindowMaximized = this.WindowState == WindowState.Maximized; + } + #endregion + } +} diff --git a/src/Microsoft.Management.UI.Internal/app.config b/src/Microsoft.Management.UI.Internal/app.config new file mode 100644 index 00000000000..a2f13686380 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/app.config @@ -0,0 +1,99 @@ + + + + +
+
+ + + + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + -1 + + + False + + + False + + + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + False + + + False + + + -1 + + + -1 + + + False + + + 100 + + + 500 + + + 700 + + + True + + + + diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs new file mode 100644 index 00000000000..e0b036a93d0 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/HelpWindowHelper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Threading; +using System.Windows; + +using Microsoft.Management.UI; +using Microsoft.PowerShell.Commands.ShowCommandInternal; + +namespace Microsoft.PowerShell.Commands.Internal +{ + /// + /// Implements the WPF window part of the ShowWindow option of get-help. + /// + internal static class HelpWindowHelper + { + /// + /// Shows the help window. + /// + /// Object with help information. + /// Cmdlet calling this method. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from methods called using reflection")] + private static void ShowHelpWindow(PSObject helpObj, PSCmdlet cmdlet) + { + Window ownerWindow = ShowCommandHelper.GetHostWindow(cmdlet); + if (ownerWindow != null) + { + ownerWindow.Dispatcher.Invoke( + new SendOrPostCallback( + (_) => + { + HelpWindow helpWindow = new HelpWindow(helpObj); + helpWindow.Owner = ownerWindow; + helpWindow.Show(); + + helpWindow.Closed += new EventHandler((sender, e) => ownerWindow.Focus()); + }), + string.Empty); + return; + } + + Thread guiThread = new Thread( + (ThreadStart)delegate + { + HelpWindow helpWindow = new HelpWindow(helpObj); + helpWindow.ShowDialog(); + }); + guiThread.SetApartmentState(ApartmentState.STA); + guiThread.Start(); + } + } +} diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs new file mode 100644 index 00000000000..81621624992 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/OutGridView.cs @@ -0,0 +1,607 @@ +// 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.Internal; +using System.Threading; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Microsoft.Management.UI.Internal +{ + /// + /// OutGridViewWindow definition for PowerShell command out-gridview. + /// + internal class OutGridViewWindow + { + #region private Fields + + /// + /// Zoom Increments. + /// + private const double ZOOM_INCREMENT = 0.2; + + /// + /// Max ZoomLevel. + /// + private const double ZOOM_MAX = 3.0; + + /// + /// Min ZoomLevel. + /// + private const double ZOOM_MIN = 0.5; + + /// + /// Window for gridView. + /// + private Window gridViewWindow; + + /// + /// Local ManagementList. + /// + private ManagementList managementList; + + /// + /// A collection of PSObjects to be data bound to the local Management List. + /// + private ObservableCollection listItems; + + /// + /// Event used for the thread gridViewWindows signaling main thread after Windows loaded. + /// + private AutoResetEvent gridViewWindowLoaded; + + /// Is used to store any Management list calls exceptions. + private Exception exception = null; + + /// + /// Is used to block thread of the pipeline. + /// + private AutoResetEvent closedEvent; + + /// + /// OK Button's content. + /// + private static readonly string OKButtonContent = XamlLocalizableResources.OutGridView_Button_OK; + + /// + /// Cancel Button's content. + /// + private static readonly string CancelButtonContent = XamlLocalizableResources.OutGridView_Button_Cancel; + + /// + /// Used to store selected items in the ok processing. + /// + private List selectedItems; + + /// + /// The GUI thread of Out-GridView. + /// + private Thread guiThread; + + /// + /// private constants for ZoomLevel. + /// + private double zoomLevel = 1.0; + + #endregion private Fields + + #region internal Constructors + + /// + /// Constructor for OutGridView. + /// + internal OutGridViewWindow() + { + // Initialize the data source collection. + this.listItems = new ObservableCollection(); + } + + #endregion internal Constructors + + #region private delegates + /// + /// ThreadDelegate definition. + /// + /// Start GridView Window delegate. + private delegate void ThreadDelegate(object arg); + + #endregion private delegates + + #region Private method that are intended to be called by the Out-GridView cmdlet. + + /// + /// Start a new thread as STA for gridView Window. + /// + /// Commands of the PowerShell. + /// Selection mode of the list. + /// ClosedEvent. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")] + private void StartWindow(string invocation, string outputModeOptions, AutoResetEvent closedEvent) + { + this.closedEvent = closedEvent; + this.gridViewWindowLoaded = new AutoResetEvent(false); + + ParameterizedThreadStart threadStart = new ParameterizedThreadStart( + new ThreadDelegate(delegate + { + try + { + this.gridViewWindow = new Window(); + this.managementList = CreateManagementList(outputModeOptions); + this.gridViewWindow.Loaded += this.GridViewWindowLoaded; + this.gridViewWindow.Content = CreateMainGrid(outputModeOptions); + this.gridViewWindow.Title = invocation; + this.gridViewWindow.Closed += this.GridViewWindowClosed; + + RoutedCommand plusSettings = new RoutedCommand(); + KeyGestureConverter keyGestureConverter = new KeyGestureConverter(); + + try + { + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn1Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn2Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn3Shortcut)); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomIn4Shortcut)); + this.gridViewWindow.CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus)); + } + catch (NotSupportedException) + { + // localized has a problematic string - going to default + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Add")); + plusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Plus")); + this.gridViewWindow.CommandBindings.Add(new CommandBinding(plusSettings, ZoomEventHandlerPlus)); + } + + RoutedCommand minusSettings = new RoutedCommand(); + try + { + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut1Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut2Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut3Shortcut)); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString(UICultureResources.ZoomOut4Shortcut)); + + this.gridViewWindow.CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus)); + } + catch (NotSupportedException) + { + // localized has a problematic string - going to default + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Subtract")); + minusSettings.InputGestures.Add((KeyGesture)keyGestureConverter.ConvertFromString("Ctrl+Minus")); + this.gridViewWindow.CommandBindings.Add(new CommandBinding(minusSettings, ZoomEventHandlerMinus)); + } + + this.gridViewWindow.ShowDialog(); + } + catch (Exception e) + { + // Store the exception in a local variable that will be checked later. + if (e.InnerException != null) + { + this.exception = e.InnerException; + } + else + { + this.exception = e; + } + } + })); + + guiThread = new Thread(threadStart); + guiThread.SetApartmentState(ApartmentState.STA); + + guiThread.Start(); + } + + /// + /// Implements ZoomIn. + /// + /// . + /// . + private void ZoomEventHandlerPlus(object sender, ExecutedRoutedEventArgs e) + { + if (this.zoomLevel == 0) + { + this.zoomLevel = 1; + } + + if (this.zoomLevel < ZOOM_MAX) + { + this.zoomLevel += ZOOM_INCREMENT; + Grid g = this.gridViewWindow.Content as Grid; + + if (g != null) + { + g.LayoutTransform = new ScaleTransform(this.zoomLevel, this.zoomLevel, 0, 0); + } + } + } + + /// + /// Implements ZoomOut. + /// + /// . + /// . + private void ZoomEventHandlerMinus(object sender, ExecutedRoutedEventArgs e) + { + if (this.zoomLevel >= ZOOM_MIN) + { + this.zoomLevel -= ZOOM_INCREMENT; + Grid g = this.gridViewWindow.Content as Grid; + if (g != null) + { + g.LayoutTransform = new ScaleTransform(this.zoomLevel, this.zoomLevel, 0, 0); + } + } + } + + /// + /// Creates a new ManagementList. + /// + /// Output mode of the out-gridview. + /// A new ManagementList. + private ManagementList CreateManagementList(string outputMode) + { + ManagementList newList = new ManagementList(); + + newList.ViewSaverUserActionState = UserActionState.Hidden; + newList.ViewManagerUserActionState = UserActionState.Hidden; + newList.List.VerticalAlignment = VerticalAlignment.Stretch; + newList.List.SetValue(Grid.RowProperty, 0); + newList.List.SelectionMode = (outputMode == "Single") ? SelectionMode.Single : SelectionMode.Extended; + + return newList; + } + + /// + /// Creates a new main grid for window. + /// + /// Output mode of the out-gridview. + /// A new mainGrid. + private Grid CreateMainGrid(string outputMode) + { + Grid mainGrid = new Grid(); + mainGrid.RowDefinitions.Add(new RowDefinition()); + mainGrid.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star); + mainGrid.Children.Add(managementList); + + if (outputMode == "None") + { + return mainGrid; + } + + // OK and Cancel button should only be displayed if OutputMode is not None. + mainGrid.RowDefinitions.Add(new RowDefinition()); + mainGrid.RowDefinitions[1].Height = GridLength.Auto; + mainGrid.Children.Add(CreateButtonGrid()); + + return mainGrid; + } + + /// + /// Creates a OK button. + /// + /// A new buttonGrid. + private Grid CreateButtonGrid() + { + Grid buttonGrid = new Grid(); + + //// This will allow OK and Cancel to have the same width + buttonGrid.SetValue(Grid.IsSharedSizeScopeProperty, true); + buttonGrid.ColumnDefinitions.Add(new ColumnDefinition()); + buttonGrid.ColumnDefinitions.Add(new ColumnDefinition()); + buttonGrid.ColumnDefinitions[0].Width = GridLength.Auto; + buttonGrid.ColumnDefinitions[0].SharedSizeGroup = "okCancel"; + buttonGrid.ColumnDefinitions[1].Width = GridLength.Auto; + buttonGrid.ColumnDefinitions[1].SharedSizeGroup = "okCancel"; + buttonGrid.HorizontalAlignment = HorizontalAlignment.Right; + buttonGrid.SetValue(Grid.RowProperty, 1); + + //// This will add OK and Cancel button to buttonGrid. + buttonGrid.Children.Add(CreateOKButton()); + buttonGrid.Children.Add(CreateCancelButton()); + + return buttonGrid; + } + + /// + /// Creates a OK button. + /// + /// A new OK button. + private Button CreateOKButton() + { + Button ok = new Button(); + ok.Content = OKButtonContent; + ok.Margin = new Thickness(5); + ok.Padding = new Thickness(2); + ok.SetValue(Grid.ColumnProperty, 0); + ok.IsDefault = true; + ok.SetValue(AutomationProperties.AutomationIdProperty, "OGVOK"); + ok.Click += OK_Click; + return ok; + } + + /// + /// Creates a Cancel button. + /// + /// A new Cancel button. + private Button CreateCancelButton() + { + Button cancel = new Button(); + cancel.Content = CancelButtonContent; + cancel.Margin = new Thickness(5); + cancel.Padding = new Thickness(2); + cancel.SetValue(Grid.ColumnProperty, 1); + cancel.IsCancel = true; + cancel.SetValue(AutomationProperties.AutomationIdProperty, "OGVCancel"); + cancel.Click += Cancel_Click; + return cancel; + } + + /// + /// Store the selected items for use in EndProcessing. + /// + /// Event sender. + /// Event arguments. + private void OK_Click(object sender, RoutedEventArgs e) + { + if (this.managementList.List.SelectedItems.Count != 0) + { + this.selectedItems = new List(); + foreach (PSObject obj in this.managementList.List.SelectedItems) + { + this.selectedItems.Add(obj); + } + } + + this.gridViewWindow.Close(); + } + + /// + /// Closes the window. + /// + /// Event sender. + /// Event arguments. + private void Cancel_Click(object sender, RoutedEventArgs e) + { + this.gridViewWindow.Close(); + } + + /// + /// Gets selected items from List. + /// + /// Selected items of the list. + private List SelectedItems() + { + return this.selectedItems; + } + + /// + /// Closes the window. + /// + public void CloseWindow() + { + this.gridViewWindow.Dispatcher.Invoke(new ThreadStart(delegate { this.gridViewWindow.Close(); })); + } + + /// + /// Add column definitions to the underlying management list. + /// + /// An array of property names to add. + /// An array of display names to add. + /// An array of types to add. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")] + private void AddColumns(string[] propertyNames, string[] displayNames, Type[] types) + { + // Wait for the gridViewWindow thread to signal that loading of Window is done + if (this.gridViewWindowLoaded != null) + { + this.gridViewWindowLoaded.WaitOne(); + this.gridViewWindowLoaded = null; + } + + this.managementList.Dispatcher.Invoke( + System.Windows.Threading.DispatcherPriority.Normal, + new Action( + () => + { + // Pick the length of the shortest incoming arrays. Normally all incoming arrays should be of the same length. + int length = propertyNames.Length; + if (length > displayNames.Length) + { + length = displayNames.Length; + } + + if (length > types.Length) + { + length = types.Length; + } + + try + { + // Clear all columns in case the view is changed. + this.managementList.List.Columns.Clear(); + + // Clear column filter rules. + this.managementList.AddFilterRulePicker.ColumnFilterRules.Clear(); + + // Add columns with provided names and Types. + for (int i = 0; i < propertyNames.Length; ++i) + { + DataTemplate dataTemplate; + bool haveTemplate = this.managementList.FilterRulePanel.TryGetContentTemplate(types[i], out dataTemplate); + InnerListColumn column = null; + + if (haveTemplate) + { + column = new InnerListColumn(new UIPropertyGroupDescription(propertyNames[i], displayNames[i], types[i])); + } + else + { + column = new InnerListColumn(new UIPropertyGroupDescription(propertyNames[i], displayNames[i], typeof(string))); + } + + this.managementList.AddColumn(column); + } + + this.managementList.List.SetColumnHeaderActions(); + + if (this.managementList.List.ItemsSource == null) + { + // Setting ItemsSource implicitly regenerates all columns. + this.managementList.List.ItemsSource = this.listItems; + } + + // Set focus on ListView + this.managementList.List.SelectedIndex = 0; + this.managementList.List.Focus(); + } + catch (Exception e) + { + // Store the exception in a local variable that will be checked later. + if (e.InnerException != null) + { + this.exception = e.InnerException; + } + else + { + this.exception = e; + } + } + })); + } + + /// + /// Add an item to ObservableCollection. + /// + /// PSObject of comlet data. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")] + private void AddItem(PSObject value) + { + if (this.GetWindowClosedStatus()) + { + return; + } + + this.managementList.Dispatcher.BeginInvoke( + System.Windows.Threading.DispatcherPriority.Normal, + new Action( + () => + { + 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) + { + // Store the exception in a local variable that will be checked later. + if (e.InnerException != null) + { + this.exception = e.InnerException; + } + else + { + this.exception = e; + } + } + })); + } + + /// + /// Returns the state of GridView Window. + /// + /// The status of GridView Window close or not. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")] + private bool GetWindowClosedStatus() + { + if (this.closedEvent == null) + { + return false; + } + + return this.closedEvent.WaitOne(0); + } + + /// + /// Returns any exception that has been thrown by previous method calls. + /// + /// The thrown and caught exception. It returns null if no exceptions were thrown by any previous method calls. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "The method is called using reflection.")] + private Exception GetLastException() + { + Exception local = this.exception; + + if (local != null) + { + // Clear the caught exception. + this.exception = null; + return local; + } + + return this.exception; + } + + #endregion Private method that are intended to be called by the Out-GridView cmdlet. + + #region Private methods + + /// + /// GridView Window is closing callback process. + /// + /// The sender object. + /// Event Args. + private void GridViewWindowClosed(object sender, EventArgs e) + { + if (this.closedEvent != null && !this.closedEvent.SafeWaitHandle.IsClosed) + { + try + { + this.closedEvent.Set(); + } + catch (ObjectDisposedException) + { + // we tried to avoid this exception with "&& !this.closedEvent.SafeWaitHandle.IsClosed" + // but since this runs in a different thread the if condition could be evaluated and after that + // the handle disposed + } + } + } + + /// + /// Set loaded as true when this method invoked. + /// + /// The sender object. + /// RoutedEvent Args. + private void GridViewWindowLoaded(object sender, RoutedEventArgs e) + { + // signal the main thread + this.gridViewWindowLoaded.Set(); + + // Make gridview window as active window + this.gridViewWindow.Activate(); + + // Set up AutomationId and Name + AutomationProperties.SetName(this.gridViewWindow, GraphicalHostResources.OutGridViewWindowObjectName); + AutomationProperties.SetAutomationId(this.gridViewWindow, "OutGridViewWindow"); + } + + #endregion Private methods + } +} diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs new file mode 100644 index 00000000000..10690b65dc7 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs @@ -0,0 +1,1267 @@ +// 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.Globalization; +using System.Management.Automation; +using System.Reflection; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Threading; + +using Microsoft.Management.UI; +using Microsoft.Management.UI.Internal; +using Microsoft.Management.UI.Internal.ShowCommand; +using Microsoft.PowerShell.Commands.ShowCommandExtension; + +namespace Microsoft.PowerShell.Commands.ShowCommandInternal +{ + /// + /// Implements the WPF window part of the show-command cmdlet. + /// + internal class ShowCommandHelper : IDisposable + { + #region fields + + internal const string CommandTypeSegment = " -CommandType Cmdlet, Function, Script, ExternalScript"; + + /// + /// Method that will return the dialog from ShowAllModulesWindow or ShowCommandWindow. + /// This is necessary because the PlainInvokeAndShowDialog thread starter cannot receive parameters + /// + private DispatcherOperationCallback methodThatReturnsDialog; + + /// + /// Event set when the window is closed. + /// + private AutoResetEvent windowClosed = new AutoResetEvent(false); + + /// + /// Event set when help is needed. + /// + private AutoResetEvent helpNeeded = new AutoResetEvent(false); + + /// + /// Event set when it is necessary to import a module. + /// + private AutoResetEvent importModuleNeeded = new AutoResetEvent(false); + + /// + /// Event set when the window is loaded. + /// + private AutoResetEvent windowLoaded = new AutoResetEvent(false); + + /// + /// String with the command that needs help set when helpNeeded is set. + /// + private string commandNeedingHelp; + + /// + /// String with the command name that needs to import a module. + /// + private string commandNeedingImportModule; + + /// + /// String with the module name that needs to be imported. + /// + private string parentModuleNeedingImportModule; + + /// + /// String with the selected module at the time a module needs to be imported. + /// + private string selectedModuleNeedingImportModule; + + /// + /// Keeps the window for the implementation of CloseWindow. + /// + private Window window; + + /// + /// host window, if any. + /// + private Window hostWindow; + + /// + /// ViewModel when showing all modules. + /// + private AllModulesViewModel allModulesViewModel; + + /// + /// ViewModel when showing a single command. + /// + private CommandViewModel commandViewModel; + + /// + /// true when the window is closed with cancel. + /// + private bool dialogCanceled = true; + #endregion fields + + #region GetSerializedCommand script + + private const string ScriptGetSerializedCommand = @" +Function PSGetSerializedShowCommandInfo +{ + Function GetParameterType + { + param ( + [Type] $parameterType) + + $returnParameterType = new-object PSObject + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""FullName"" -Value $parameterType.FullName + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""IsEnum"" -Value $parameterType.IsEnum + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""IsArray"" -Value $parameterType.IsArray + + if ($parameterType.IsEnum) + { + $enumValues = [System.Enum]::GetValues($parameterType) + } + else + { + $enumValues = [string[]] @() + } + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""EnumValues"" -Value $enumValues + + if ($parameterType.IsArray) + { + $hasFlagAttribute = ($parameterType.GetCustomAttributes([System.FlagsAttribute], $true).Length -gt 0) + + # Recurse into array elements. + $elementType = GetParameterType($parameterType.GetElementType()) + } + else + { + $hasFlagAttribute = $false + $elementType = $null + } + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""HasFlagAttribute"" -Value $hasFlagAttribute + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""ElementType"" -Value $elementType + + + if (!($parameterType.IsEnum) -and !($parameterType.IsArray)) + { + $implementsDictionary = [System.Collections.IDictionary].IsAssignableFrom($parameterType) + } + else + { + $implementsDictionary = $false + } + $returnParameterType | Add-Member -MemberType NoteProperty -Name ""ImplementsDictionary"" -Value $implementsDictionary + + return $returnParameterType + } + + Function GetParameterInfo + { + param ( + $parameters) + + [PSObject[]] $parameterInfos = @() + + foreach ($parameter in $parameters) + { + $parameterInfo = new-object PSObject + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""Name"" -Value $parameter.Name + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""IsMandatory"" -Value $parameter.IsMandatory + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""ValueFromPipeline"" -Value $parameter.ValueFromPipeline + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""Position"" -Value $parameter.Position + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""ParameterType"" -Value (GetParameterType($parameter.ParameterType)) + + $hasParameterSet = $false + [string[]] $validValues = @() + if ($PSVersionTable.PSVersion.Major -gt 2) + { + $validateSetAttributes = $parameter.Attributes | Where { + [ValidateSet].IsAssignableFrom($_.GetType()) + } + if (($validateSetAttributes -ne $null) -and ($validateSetAttributes.Count -gt 0)) + { + $hasParameterSet = $true + $validValues = $validateSetAttributes[0].ValidValues + } + } + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""HasParameterSet"" -Value $hasParameterSet + $parameterInfo | Add-Member -MemberType NoteProperty -Name ""ValidParamSetValues"" -Value $validValues + + $parameterInfos += $parameterInfo + } + + return (,$parameterInfos) + } + + Function GetParameterSets + { + param ( + [System.Management.Automation.CommandInfo] $cmdInfo + ) + + $parameterSets = $null + try + { + $parameterSets = $cmdInfo.ParameterSets + } + catch [System.InvalidOperationException] { } + catch [System.Management.Automation.PSNotSupportedException] { } + catch [System.Management.Automation.PSNotImplementedException] { } + + if (($parameterSets -eq $null) -or ($parameterSets.Count -eq 0)) + { + return (,@()) + } + + [PSObject[]] $returnParameterSets = @() + + foreach ($parameterSet in $parameterSets) + { + $parameterSetInfo = new-object PSObject + $parameterSetInfo | Add-Member -MemberType NoteProperty -Name ""Name"" -Value $parameterSet.Name + $parameterSetInfo | Add-Member -MemberType NoteProperty -Name ""IsDefault"" -Value $parameterSet.IsDefault + $parameterSetInfo | Add-Member -MemberType NoteProperty -Name ""Parameters"" -Value (GetParameterInfo($parameterSet.Parameters)) + + $returnParameterSets += $parameterSetInfo + } + + return (,$returnParameterSets) + } + + Function GetModuleInfo + { + param ( + [System.Management.Automation.CommandInfo] $cmdInfo + ) + + if ($cmdInfo.ModuleName -ne $null) + { + $moduleName = $cmdInfo.ModuleName + } + else + { + $moduleName = """" + } + + $moduleInfo = new-object PSObject + $moduleInfo | Add-Member -MemberType NoteProperty -Name ""Name"" -Value $moduleName + + return $moduleInfo + } + + Function ConvertToShowCommandInfo + { + param ( + [System.Management.Automation.CommandInfo] $cmdInfo + ) + + $showCommandInfo = new-object PSObject + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""Name"" -Value $cmdInfo.Name + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""ModuleName"" -Value $cmdInfo.ModuleName + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""Module"" -Value (GetModuleInfo($cmdInfo)) + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""CommandType"" -Value $cmdInfo.CommandType + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""Definition"" -Value $cmdInfo.Definition + $showCommandInfo | Add-Member -MemberType NoteProperty -Name ""ParameterSets"" -Value (GetParameterSets($cmdInfo)) + + return $showCommandInfo + } + + $commandList = @(""Cmdlet"", ""Function"", ""Script"", ""ExternalScript"") + if ($PSVersionTable.PSVersion.Major -gt 2) + { + $commandList += ""Workflow"" + } + + foreach ($command in @(Get-Command -CommandType $commandList)) + { + Write-Output (ConvertToShowCommandInfo($command)) + } +}"; + + #endregion + + #region constructor and destructor + /// + /// Prevents a default instance of the ShowCommandHelper class from being created. + /// + private ShowCommandHelper() + { + } + + /// + /// Finalizes an instance of the class. + /// + ~ShowCommandHelper() + { + this.Dispose(false); + } + #endregion constructor and destructor + + #region properties called using reflection + /// + /// Gets the Screen Width. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private static double ScreenWidth + { + get + { + return System.Windows.SystemParameters.PrimaryScreenWidth; + } + } + + /// + /// Gets the Screen Height. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private static double ScreenHeight + { + get + { + return System.Windows.SystemParameters.PrimaryScreenHeight; + } + } + + /// + /// Gets the event set when the show-command window is closed. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private AutoResetEvent WindowClosed + { + get + { + return this.windowClosed; + } + } + + /// + /// Gets the event set when help is needed for a command. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private AutoResetEvent HelpNeeded + { + get + { + return this.helpNeeded; + } + } + + /// + /// Gets the event set when it is necessary to import a module. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private AutoResetEvent ImportModuleNeeded + { + get + { + return this.importModuleNeeded; + } + } + + /// + /// Gets the event set when the window is loaded. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private AutoResetEvent WindowLoaded + { + get + { + return this.windowLoaded; + } + } + + /// + /// Gets the command needing help when HelpNeeded is set. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private string CommandNeedingHelp + { + get + { + return this.commandNeedingHelp; + } + } + + /// + /// Gets the module we want to import. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private string ParentModuleNeedingImportModule + { + get + { + return this.parentModuleNeedingImportModule; + } + } + + /// + /// Gets a value indicating whether there is a host window. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private bool HasHostWindow + { + get + { + return this.hostWindow != null; + } + } + #endregion properties called using reflection + + #region public Dispose + /// + /// Dispose method in IDisposeable. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + #endregion public Dispose + + #region internal static methods called using reflection from show-command + /// + /// Sets the text in the clipboard. + /// + /// Text to set the clipboard to. + internal static void SetClipboardText(string text) + { + try + { + Clipboard.SetText(text); + } + catch (System.Runtime.InteropServices.COMException) + { + // This is the recommended way to set clipboard text + System.Threading.Thread.Sleep(0); + try + { + Clipboard.SetText(text); + } + catch (System.Runtime.InteropServices.COMException) + { + } + } + } + + /// + /// Gets the command to be run to get commands and imported modules. + /// + /// 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 get commands and imported modules. + internal static string GetShowAllModulesCommand(bool isRemoteRunspace = false, bool isFirstChance = true) + { + string scriptBase; + + if (isRemoteRunspace) + { + if (isFirstChance) + { + // Return command to run. + scriptBase = "@(Get-Command " + ShowCommandHelper.CommandTypeSegment + @" -ShowCommandInfo)"; + } + else + { + // Return script to run. + scriptBase = GetSerializedCommandScript(); + } + } + else + { + scriptBase = "@(Get-Command " + ShowCommandHelper.CommandTypeSegment + ")"; + } + + scriptBase += ShowCommandHelper.GetGetModuleSuffix(); + return scriptBase; + } + + /// + /// Retrieves the script for Get-SerializedCommand from local machine. + /// + /// String representation of the script for Get-SerializedCommand. + private static string GetSerializedCommandScript() + { + return string.Format( + CultureInfo.InvariantCulture, + "@({0};{1};{2})", + ScriptGetSerializedCommand, + @"PSGetSerializedShowCommandInfo", + @"Remove-Item -Path 'function:\PSGetSerializedShowCommandInfo' -Force"); + } + + /// + /// Gets the command to be run in order to show help for a command. + /// + /// Command we want to get help from. + /// The command to be run in order to show help for a command. + internal static string GetHelpCommand(string command) + { + return "Get-Help " + ShowCommandHelper.SingleQuote(command); + } + + /// + /// Constructs a dictionary of imported modules based on the module names. + /// + /// The imported modules. + /// a dictionary of imported modules based on the module names. + internal static Dictionary GetImportedModulesDictionary(object[] moduleObjects) + { + Dictionary returnValue = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (PSObject rawModule in moduleObjects) + { + ShowCommandModuleInfo wrappedModule = null; + PSModuleInfo module = rawModule.BaseObject as PSModuleInfo; + if (module != null) + { + wrappedModule = new ShowCommandModuleInfo(module); + } + else + { + wrappedModule = new ShowCommandModuleInfo(rawModule); + } + + // It is probably an issue somewhere else that a module would show up twice in the list, but we want to avoid + // throwing an exception regarding that in returnValue.Add + if (!returnValue.ContainsKey(wrappedModule.Name)) + { + returnValue.Add(wrappedModule.Name, wrappedModule); + } + } + + return returnValue; + } + + /// + /// Constructs a list of commands out of . + /// + /// The results of a get-command command. + /// a list of commands out of . + internal static List GetCommandList(object[] commandObjects) + { + List returnValue = new List(); + foreach (PSObject rawCommand in commandObjects) + { + CommandInfo command = rawCommand.BaseObject as CommandInfo; + if (command != null) + { + returnValue.Add(new ShowCommandCommandInfo(command)); + } + else + { + PSObject obj = rawCommand as PSObject; + if (obj != null) + { + returnValue.Add(new ShowCommandCommandInfo(obj)); + } + } + } + + return returnValue; + } + + /// + /// Constructs an array of objects out of . + /// + /// The result of a get-command command. + /// An array of objects out of . + internal static object[] ObjectArrayFromObjectCollection(object commandObjects) + { + object[] objectArray = commandObjects as object[] ?? ((System.Collections.ArrayList)commandObjects).ToArray(); + + return objectArray; + } + + /// + /// Called after a module in is imported to refresh the view model. + /// Gets a new AllModulesViewModel populated with and . + /// The is used to cleanup event listening in the old view model and to copy NoCommonParameters. + /// The new ViewModel will have the command selected according to , + /// and . + /// + /// The viewModel before the module was imported. + /// The list of imported modules. + /// The list of commands. + /// The name of the module that was selected in . + /// The name of the module that was imported. + /// The name of the command that was selected in . + /// The new ViewModel based on and . + internal static AllModulesViewModel GetNewAllModulesViewModel(AllModulesViewModel oldViewModel, Dictionary importedModules, IEnumerable commands, string selectedModuleNeedingImportModule, string parentModuleNeedingImportModule, string commandNeedingImportModule) + { + string oldFilter = null; + + if (oldViewModel.SelectedModule != null) + { + // this will allow the old view model to stop listening for events before we + // replace it with a new view model + oldViewModel.SelectedModule.SelectedCommand = null; + oldViewModel.SelectedModule = null; + oldFilter = oldViewModel.CommandNameFilter; + } + + AllModulesViewModel returnValue = new AllModulesViewModel(importedModules, commands, oldViewModel.NoCommonParameter); + if (!string.IsNullOrEmpty(oldFilter)) + { + returnValue.CommandNameFilter = oldFilter; + } + + if (selectedModuleNeedingImportModule == null || parentModuleNeedingImportModule == null) + { + return returnValue; + } + + ModuleViewModel moduleToSelect = returnValue.Modules.Find( + new Predicate((module) => + { + return module.Name.Equals(selectedModuleNeedingImportModule, StringComparison.OrdinalIgnoreCase) ? true : false; + })); + + if (moduleToSelect == null) + { + return returnValue; + } + + returnValue.SelectedModule = moduleToSelect; + + CommandViewModel commandToSelect = moduleToSelect.Commands.Find( + new Predicate((command) => + { + return command.ModuleName.Equals(parentModuleNeedingImportModule, StringComparison.OrdinalIgnoreCase) && + command.Name.Equals(commandNeedingImportModule, StringComparison.OrdinalIgnoreCase) ? true : false; + })); + + if (commandToSelect == null) + { + return returnValue; + } + + moduleToSelect.SelectedCommand = commandToSelect; + return returnValue; + } + + /// + /// Gets an error message to be displayed when failed to import a module. + /// + /// 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. + internal static string GetImportModuleFailedMessage(string command, string module, string error) + { + return string.Format( + CultureInfo.CurrentUICulture, + ShowCommandResources.ImportModuleFailedFormat, + command, + module, + error); + } + + /// + /// Single quotes . + /// + /// String to quote. + /// single quoted. + internal static string SingleQuote(string str) + { + if (str == null) + { + str = string.Empty; + } + + return "\'" + System.Management.Automation.Language.CodeGeneration.EscapeSingleQuotedStringContent(str) + "\'"; + } + #endregion internal static methods called using reflection from show-command + + #region internal static methods used internally in this assembly + /// + /// Gets the host window, if it is present or null if it is not. + /// + /// Cmdlet calling this method. + /// The host window, if it is present or null if it is not. + internal static Window GetHostWindow(PSCmdlet cmdlet) + { + // 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; + } + + try + { + return windowProperty.Value as Window; + } + catch (ExtendedTypeSystemException) + { + return null; + } + } + #endregion internal static methods used internally in this assembly + + #region static private methods used only on this file + + /// + /// Gets a property value using reflection. + /// + /// Type containing the property. + /// Object containing the property (null for a static property). + /// Name of property to get. + /// Flags passed to reflection. + /// + /// Property value or null if it was not able to retrieve it. This method is not suitable to return a property value that might be null. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from a method called using reflection")] + private static object GetPropertyValue(Type type, object obj, string propertyName, BindingFlags bindingFlags) + { + PropertyInfo property = type.GetProperty(propertyName, bindingFlags); + if (property == null) + { + return null; + } + + try + { + return property.GetValue(obj, Array.Empty()); + } + catch (ArgumentException) + { + return null; + } + catch (TargetException) + { + return null; + } + catch (TargetParameterCountException) + { + return null; + } + catch (MethodAccessException) + { + return null; + } + catch (TargetInvocationException) + { + return null; + } + } + + /// + /// Sets a property value using reflection. + /// + /// Type containing the property. + /// Object containing the property (null for a static property). + /// Name of property to set. + /// Value to set the property with. + /// Flags passed to reflection. + /// True if it was able to set. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from a method called using reflection")] + private static bool SetPropertyValue(Type type, object obj, string propertyName, object value, BindingFlags bindingFlags) + { + PropertyInfo property = type.GetProperty(propertyName, bindingFlags); + if (property == null) + { + return false; + } + + try + { + property.SetValue(obj, value, Array.Empty()); + } + catch (ArgumentException) + { + return false; + } + catch (TargetException) + { + return false; + } + catch (TargetParameterCountException) + { + return false; + } + catch (MethodAccessException) + { + return false; + } + catch (TargetInvocationException) + { + return false; + } + + return true; + } + + /// + /// Gets the suffix that adds imported modules to a command. + /// + /// The suffix that adds imported modules to a command. + private static string GetGetModuleSuffix() + { + return ",@(get-module)"; + } + + #endregion static private methods used only on this file + + #region private methods called using reflection from show-command + /// + /// Gets the command to be run when calling show-command for a particular command. + /// + /// The particular command we are running show-command on. + /// True if we want to include aliases and retrieve modules. + /// The command to be run when calling show-command for a particular command. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private static string GetShowCommandCommand(string commandName, bool includeAliasAndModules) + { + string quotedCommandName = ShowCommandHelper.SingleQuote(commandName); + return "@(get-command " + quotedCommandName + " " + ShowCommandHelper.CommandTypeSegment + + (includeAliasAndModules ? ",Alias" : string.Empty) + ")" + + (includeAliasAndModules ? ShowCommandHelper.GetGetModuleSuffix() : string.Empty); + } + + /// + /// Gets a CommandViewModel of a CommandInfo. + /// + /// Command we want to get a CommandViewModel of. + /// True if we do not want common parameters. + /// The loaded modules. + /// True to qualify command with module name in GetScript. + /// A CommandViewModel of a CommandInfo. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private static object GetCommandViewModel(ShowCommandCommandInfo command, bool noCommonParameter, Dictionary importedModules, bool moduleQualify) + { + CommandViewModel returnValue = CommandViewModel.GetCommandViewModel(new ModuleViewModel(command.ModuleName, importedModules), command, noCommonParameter); + returnValue.ModuleQualifyCommandName = moduleQualify; + return returnValue; + } + + /// + /// Dispatches a message to the window for it to activate. + /// + /// Window to be activated. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from ActivateWindow() which is called using reflection")] + private static void ActivateWindow(Window window) + { + window.Dispatcher.Invoke( + new SendOrPostCallback( + (_) => window.Activate()), + string.Empty); + } + + /// + /// Shows the window listing cmdlets. + /// + /// Cmdlet calling this method. + /// All loaded modules. + /// Commands to be listed. + /// True if we should not show common parameters. + /// Window width. + /// Window height. + /// True if the GUI should mention ok instead of run. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void ShowAllModulesWindow(PSCmdlet cmdlet, Dictionary importedModules, IEnumerable commands, bool noCommonParameter, double windowWidth, double windowHeight, bool passThrough) + { + this.methodThatReturnsDialog = new DispatcherOperationCallback((object ignored) => + { + ShowAllModulesWindow allModulesWindow = new ShowAllModulesWindow(); + this.allModulesViewModel = new AllModulesViewModel(importedModules, commands, noCommonParameter); + + this.SetupButtonEvents(allModulesWindow.Run, allModulesWindow.Copy, allModulesWindow.Cancel, passThrough); + this.SetupWindow(allModulesWindow); + this.SetupViewModel(); + CommonHelper.SetStartingPositionAndSize( + allModulesWindow, + ShowCommandSettings.Default.ShowCommandsTop, + ShowCommandSettings.Default.ShowCommandsLeft, + windowWidth != 0.0 && windowWidth > allModulesWindow.MinWidth ? windowWidth : ShowCommandSettings.Default.ShowCommandsWidth, + windowHeight != 0.0 && windowHeight > allModulesWindow.MinHeight ? windowHeight : ShowCommandSettings.Default.ShowCommandsHeight, + allModulesWindow.Width, + allModulesWindow.Height, + ShowCommandSettings.Default.ShowCommandsWindowMaximized); + + return allModulesWindow; + }); + + this.CallShowDialog(cmdlet); + } + + /// + /// Calls ShowsDialog on methodThatReturnsDialog either in a separate thread or dispatched + /// to the hostWindow thread if there is a hostWindow + /// + /// Cmdlet used to retrieve the host window. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from a method called using reflection")] + private void CallShowDialog(PSCmdlet cmdlet) + { + this.hostWindow = ShowCommandHelper.GetHostWindow(cmdlet); + if (this.hostWindow == null) + { + Thread guiThread = new Thread(new ThreadStart(this.PlainInvokeAndShowDialog)); + guiThread.SetApartmentState(ApartmentState.STA); + guiThread.Start(); + return; + } + + this.hostWindow.Dispatcher.Invoke( + new SendOrPostCallback( + (_) => + { + Window childWindow = (Window)this.methodThatReturnsDialog.Invoke(null); + childWindow.Owner = this.hostWindow; + childWindow.Show(); + }), + string.Empty); + } + + /// + /// Called from CallMethodThatShowsDialog as the thtead start when there is no host window. + /// + private void PlainInvokeAndShowDialog() + { + ((Window)this.methodThatReturnsDialog.Invoke(null)).ShowDialog(); + } + + /// + /// Shows the window for the cmdlet. + /// + /// Cmdlet calling this method. + /// Command to show in the window. + /// Window width. + /// Window height. + /// True if the GUI should mention ok instead of run. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void ShowCommandWindow(PSCmdlet cmdlet, object commandViewModelObj, double windowWidth, double windowHeight, bool passThrough) + { + this.methodThatReturnsDialog = new DispatcherOperationCallback((object ignored) => + { + this.commandViewModel = (CommandViewModel)commandViewModelObj; + ShowCommandWindow showCommandWindow = new ShowCommandWindow(); + + this.commandViewModel.HelpNeeded += this.CommandNeedsHelp; + showCommandWindow.DataContext = this.commandViewModel; + + this.SetupButtonEvents(showCommandWindow.Run, showCommandWindow.Copy, showCommandWindow.Cancel, passThrough); + this.SetupWindow(showCommandWindow); + + CommonHelper.SetStartingPositionAndSize( + showCommandWindow, + ShowCommandSettings.Default.ShowOneCommandTop, + ShowCommandSettings.Default.ShowOneCommandLeft, + windowWidth != 0.0 && windowWidth > showCommandWindow.MinWidth ? windowWidth : ShowCommandSettings.Default.ShowOneCommandWidth, + windowHeight != 0.0 && windowHeight > showCommandWindow.MinHeight ? windowHeight : ShowCommandSettings.Default.ShowOneCommandHeight, + showCommandWindow.Width, + showCommandWindow.Height, + ShowCommandSettings.Default.ShowOneCommandWindowMaximized); + + return showCommandWindow; + }); + + this.CallShowDialog(cmdlet); + } + + /// + /// Called when the module importation is done. + /// + /// All modules currently imported. + /// Commands to be displayed. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void ImportModuleDone(Dictionary importedModules, IEnumerable commands) + { + this.allModulesViewModel.WaitMessageDisplayed = false; + if (this.window != null) + { + this.window.Dispatcher.Invoke( + new SendOrPostCallback( + delegate(object ignored) + { + this.allModulesViewModel = ShowCommandHelper.GetNewAllModulesViewModel( + this.allModulesViewModel, + importedModules, + commands, + this.selectedModuleNeedingImportModule, + this.parentModuleNeedingImportModule, + this.commandNeedingImportModule); + this.SetupViewModel(); + }), + string.Empty); + } + } + + /// + /// Called when the module importation has failed. + /// + /// Reason why the module importation failed. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void ImportModuleFailed(Exception reason) + { + this.allModulesViewModel.WaitMessageDisplayed = false; + if (this.window != null) + { + this.window.Dispatcher.Invoke( + new SendOrPostCallback( + (_) => + { + string message = ShowCommandHelper.GetImportModuleFailedMessage( + this.commandNeedingImportModule, + this.parentModuleNeedingImportModule, + reason.Message); + MessageBox.Show(this.window, message, ShowCommandResources.ShowCommandError, MessageBoxButton.OK, MessageBoxImage.Error); + }), + string.Empty); + } + } + + /// + /// Called when the results or get-help are ready in order to display the help window for a command. + /// + /// Results of a get-help call. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void DisplayHelp(Collection getHelpResults) + { + if (this.window != null && getHelpResults != null && getHelpResults.Count > 0) + { + this.window.Dispatcher.Invoke( + new SendOrPostCallback( + delegate(object ignored) + { + HelpWindow help = new HelpWindow(getHelpResults[0]); + help.Owner = this.window; + help.Show(); + }), + string.Empty); + } + } + + /// + /// Activates this.window. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private void ActivateWindow() + { + if (this.window != null) + { + ShowCommandHelper.ActivateWindow(this.window); + } + } + + /// + /// Returns the script to execute if dialog has not been canceled. + /// + /// The script to execute if dialog has not been canceled. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called using reflection")] + private string GetScript() + { + if (this.dialogCanceled) + { + return null; + } + + return this.InternalGetScript(); + } + #endregion private methods called using reflection from show-command + + #region instance private methods used only on this file + /// + /// Sets up window settings common between the two flavors of show-command. + /// + /// The window being displayed. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from ShowAllModulesWindow and ShowCommandWindow which are called with reflection")] + private void SetupWindow(Window commandWindow) + { + this.window = commandWindow; + this.window.Closed += this.Window_Closed; + this.window.Loaded += this.Window_Loaded; + } + + /// + /// Handles the SelectedCommandInSelectedModuleNeedsImportModule event. + /// + /// Event sender. + /// Event arguments. + private void CommandNeedsImportModule(object sender, ImportModuleEventArgs e) + { + this.commandNeedingImportModule = e.CommandName; + this.parentModuleNeedingImportModule = e.ParentModuleName; + this.selectedModuleNeedingImportModule = e.SelectedModuleName; + this.allModulesViewModel.WaitMessageDisplayed = true; + this.ImportModuleNeeded.Set(); + } + + /// + /// Handles the SelectedCommandInSelectedModuleNeedsHelp event. + /// + /// Event sender. + /// Event arguments. + private void CommandNeedsHelp(object sender, HelpNeededEventArgs e) + { + this.commandNeedingHelp = e.CommandName; + this.HelpNeeded.Set(); + } + + /// + /// Called when the window is closed to set this.dialogCanceled. + /// + /// Event sender. + /// Event arguments. + private void Window_Closed(object sender, EventArgs e) + { + if (this.hostWindow != null) + { + this.hostWindow.Focus(); + } + + this.window = null; + this.windowClosed.Set(); + } + + /// + /// Called when the window is loaded to set this.Window_Loaded. + /// + /// Event sender. + /// Event arguments. + private void Window_Loaded(object sender, RoutedEventArgs e) + { + this.window.Loaded -= this.Window_Loaded; + this.windowLoaded.Set(); + } + + /// + /// Sets up event listening on the buttons. + /// + /// Button to run command. + /// Button to copy command code. + /// Button to close window. + /// True to change the text of Run to OK. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called from methods called using reflection")] + private void SetupButtonEvents(Button run, Button copy, Button cancel, bool passThrough) + { + if (passThrough) + { + run.Content = ShowCommandResources.ActionButtons_Button_Ok; + } + + run.Click += this.Buttons_RunClick; + copy.Click += this.Buttons_CopyClick; + cancel.Click += this.Buttons_CancelClick; + } + + /// + /// Sets up event listening for a new viewModel. + /// + private void SetupViewModel() + { + this.allModulesViewModel.SelectedCommandInSelectedModuleNeedsHelp += this.CommandNeedsHelp; + this.allModulesViewModel.SelectedCommandInSelectedModuleNeedsImportModule += this.CommandNeedsImportModule; + this.window.DataContext = this.allModulesViewModel; + } + + /// + /// Copies the script into the clipboard. + /// + /// Event sender. + /// Event arguments. + private void Buttons_CopyClick(object sender, RoutedEventArgs e) + { + string script = this.InternalGetScript(); + if (script == null) + { + return; + } + + this.window.Dispatcher.Invoke(new ThreadStart(delegate { ShowCommandHelper.SetClipboardText(script); })); + } + + /// + /// Sets a successful dialog result and then closes the window. + /// + /// Event sender. + /// Event arguments. + private void Buttons_RunClick(object sender, RoutedEventArgs e) + { + this.dialogCanceled = false; + this.CloseWindow(); + } + + /// + /// Closes the window. + /// + /// Event sender. + /// Event arguments. + private void Buttons_CancelClick(object sender, RoutedEventArgs e) + { + this.CloseWindow(); + } + + /// + /// Closes the window. + /// + private void CloseWindow() + { + if (this.window == null) + { + return; + } + + this.window.Dispatcher.Invoke(new ThreadStart(delegate + { + // This can happen if ISE is closed while show-command is up + if (this.window != null) + { + this.window.Close(); + } + })); + } + + /// + /// Showing a MessageBox when user type a invalidate command name. + /// + /// Error message. + private void ShowErrorString(string errorString) + { + if (errorString != null && errorString.Trim().Length > 0) + { + MessageBox.Show( + string.Format( + CultureInfo.CurrentUICulture, + ShowCommandResources.EndProcessingErrorMessage, + errorString), + "Show-Command", + MessageBoxButton.OK, + MessageBoxImage.Error); + } + } + + /// + /// Returns the script to execute. + /// + /// The script to execute. + private string InternalGetScript() + { + if (this.allModulesViewModel != null) + { + return this.allModulesViewModel.GetScript(); + } + + if (this.commandViewModel == null) + { + return null; + } + + return this.commandViewModel.GetScript(); + } + + /// + /// Implements IDisposable logic. + /// + /// True if being called from Dispose. + private void Dispose(bool isDisposing) + { + if (isDisposing) + { + this.windowClosed.Dispose(); + this.helpNeeded.Dispose(); + this.windowLoaded.Dispose(); + this.importModuleNeeded.Dispose(); + } + } + #endregion instance private methods used only on this file + } +} diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Add16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/Add16.png new file mode 100644 index 00000000000..e5b964d9199 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Add16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/CloseTile16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/CloseTile16.png new file mode 100644 index 00000000000..bef254260eb Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/CloseTile16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Delete16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/Delete16.png new file mode 100644 index 00000000000..1faef5d45ca Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Delete16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Error16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/Error16.png new file mode 100644 index 00000000000..8fce7ba03df Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Error16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Help.ico b/src/Microsoft.Management.UI.Internal/resources/Graphics/Help.ico new file mode 100644 index 00000000000..f6b92d0b8c2 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Help.ico differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Rename16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/Rename16.png new file mode 100644 index 00000000000..fa59ea35618 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Rename16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/Save16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/Save16.png new file mode 100644 index 00000000000..66893c82d66 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/Save16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerAscending.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerAscending.png new file mode 100644 index 00000000000..a58856e553c Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerAscending.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerDescending.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerDescending.png new file mode 100644 index 00000000000..d95bc066e5b Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/SortAdornerDescending.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass10.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass10.png new file mode 100644 index 00000000000..71abc2ecf32 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass10.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass16.png b/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass16.png new file mode 100644 index 00000000000..37cf86490d4 Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/SpyGlass16.png differ diff --git a/src/Microsoft.Management.UI.Internal/resources/Graphics/down.ico b/src/Microsoft.Management.UI.Internal/resources/Graphics/down.ico new file mode 100644 index 00000000000..98e0339426a Binary files /dev/null and b/src/Microsoft.Management.UI.Internal/resources/Graphics/down.ico differ diff --git a/src/Microsoft.Management.UI.Internal/resources/public.GraphicalHostResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.GraphicalHostResources.resx new file mode 100644 index 00000000000..0a86d51a82e --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.GraphicalHostResources.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 + + + OutGridViewWindow Object + + diff --git a/src/Microsoft.Management.UI.Internal/resources/public.HelpWindowResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.HelpWindowResources.resx new file mode 100644 index 00000000000..3bcab7bfb8a --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.HelpWindowResources.resx @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Cancel + + + Match Case + + + CommonParameters + Name of a group of parameters common to all cmdlets + + + Description + + + Examples + + + _Find: + + + Help Sections + + + {0} Help + + + Inputs + + + {0} {1} + + + Methods + + + _Next + + + No matches found + + + Notes + + + OK + + + 1 match + + + Outputs + + + Accept wildcard characters? + + + Default value + + + Accept pipeline input? + + + Position? + + + Required? + + + Parameters + + + _Previous + + + Properties + + + RelatedLinks + + + Remarks + + + Search Options + + + Settings + + + {0} matches + + + Synopsis + + + Syntax + + + Help for {0} + {0} is the name of a cmdlet + + + Whole Word + + + {0}% + + + Zoom + + diff --git a/src/Microsoft.Management.UI.Internal/resources/public.InvariantResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.InvariantResources.resx new file mode 100644 index 00000000000..62924692a43 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.InvariantResources.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + {0} cannot be modified directly, use {1} instead. + + + Columns + + + {0:G} + The format string that is used by the InnerList in the case where a DateTime type is used. The {0} will be the column value. + + + {0} + The format string that is used by the InnerList in the default case. The {0} will be the column value. + + + {0:N} + The format string that is used by the InnerList in the case where a floating point number is used. The {0} will be the column value. + + + {0:N0} + The format string that is used by the InnerList in the case where a whole number type is used. The {0} will be the column value. + + + {0} does not support adding to the Items collection, use {1} instead. + + + If View is set to a {0}, it should have the type {1}. + + diff --git a/src/Microsoft.Management.UI.Internal/resources/public.ShowCommandResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.ShowCommandResources.resx new file mode 100644 index 00000000000..5853a0a3c01 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.ShowCommandResources.resx @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Cancel + + + Co_py + + + _Run + + + All + + + Modules: + + + ? + + + Help + + + Common Parameters + + + Errors + + + Parameters for "{0}": + + + Name: {0} +Module: {1} ({2}) + + + The following errors occurred running the command: +{0} + + + * + Used in MandatoryNameLabelFormat to designate a mandatory parameter with a * + + + {0}:{1} + This is a label for a control, hence the colon. {0} is a parameter name, {1} is MandatoryLabelSegment or an empty string + + + {0}: + This is a label for a control, hence the colon. {0} is a parameter name + + + Name: + + + OK + + + ... + + + Command Name + + + Modules + + + <No module name> + + + Select multiple values for "{0}" + + + Can receive value from pipeline + + + Common to all parameter sets + + + Mandatory + + + Optional + + + Position: {0} + + + Type: {0} + + + Imported + + + Not Imported + + + Show Details + + + Failed to import the module required by command "{0}". Module name: "{1}". Error message: "{2}". + + + To import the "{0}" module and its cmdlets, including "{1}", click {2}. + + + Show Command - Error + + + Please Wait... + + + Refresh + + + There are no parameters. + + + Click after using "{0}" to see the new commands + + diff --git a/src/Microsoft.Management.UI.Internal/resources/public.UICultureResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.UICultureResources.resx new file mode 100644 index 00000000000..7f5730b3f86 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.UICultureResources.resx @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Select Columns... + + + (none) + The group title for items within a column whose value is empty/null. + + + Value should be of Type {0}. + This text represents the error which will be shown when the entered type does not match the type we are expecting. {0} is the expected type. + + + The current selection is empty. + The error validation string to present to the user when they have selected a value out of bounds. + + + contains + A filter rule that indicates a field must contain the specified value. + + + does not contain + A filter rule that indicates a field must not contain the specified value. + + + does not equal + A filter rule that indicates a field must not equal the specified value. + + + equals + A filter rule that indicates a field must equal the specified value. + + + is greater than or equal to + A filter rule that indicates a field must be greater than or equal to the specified value. + + + is between + A filter rule that indicates a field must be between the specified values. + + + is empty + A filter rule that indicates a field must be empty. + + + is not empty + A filter rule that indicates a field must not be empty. + + + is less than or equal to + A filter rule that indicates a field must be less than or equal to the specified value. + + + ends with + A filter rule that indicates a field must end with the specified value. + + + starts with + A filter rule that indicates a field must start with the specified value. + + + Back + The text representing the tool tip and help text for the Back Button in the Back Forward History control when the button is disabled + + + Forward + The text representing the tool tip and help text for the Forward Button in the Back Forward History control when the button is disabled + + + The value must be a valid date in the following format: {0}. + {0} will be filled in with the culture appropriate ShortDatePattern + + + The value must be a valid number. + + + Search + The default background text of the search box. + + + LeftToRight + This value will be loaded at runtime to define the flow direction of WPF application. This value should be set to "RightToLeft" for mirrored language and "LeftToRight" for others. + + + + An ellipsis character. + + + Ctrl+Add + + + Ctrl+Shift+Add + + + Ctrl+Plus + + + Ctrl+Shift+Plus + + + Ctrl+Subtract + + + Ctrl+Shift+Subtract + + + Ctrl+Minus + + + Ctrl+Shift+Minus + + diff --git a/src/Microsoft.Management.UI.Internal/resources/public.XamlLocalizableResources.resx b/src/Microsoft.Management.UI.Internal/resources/public.XamlLocalizableResources.resx new file mode 100644 index 00000000000..caff99962fd --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/resources/public.XamlLocalizableResources.resx @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Available Columns + + + Add + + + Remove + + + Selected Columns + + + Back + Localizable AutomationName for control that is used by accessibility screen readers. + + + Forward + Localizable AutomationName for control that is used by accessibility screen readers. + + + Find in this column + Background text shown in the search box. + + + Expand + + + Name + + + New Query + + + Tasks + This is the title string for the Task Pane. + + + Tasks + AutomationProperties.Name of a SeparatedList. + + + Indeterminate Progress Icon + + + Add criteria + + + Overwrite the existing query or type a different name to save a new query. Each query consists of criteria, sorting, and column customizations. + + + Ok + + + Cancel + + + Click to save a search query + + + Available columns + + + >> + The contents of a button which indicates that items will move from the left column to right column + + + << + The contents of a button which indicates that items will move from the right column to left column + + + Move up + + + Move down + + + OK + + + Cancel + + + Select columns + + + Move selected column to list of visible columns + + + Move selected column to list of hidden columns + + + This column may not be removed. + + + The list must always display at least one column. + + + Selected columns + + + Find in this column + + + Expand + + + Click to clear all filter criteria. + + + Click to add search criteria. + + + Click to expand search criteria. + + + There are currently no saved queries. + + + Queries + + + Delete + + + Rename + + + {0} rule + The text representation of a rule in the filter panel, displayed to accessibility clients. {0} will be the name of the rule. + + + Add + + + Cancel + + + Add Filter Criteria + + + Value + The name for text input fields + + + <Empty> + + + Rules + The name of the panel which contains the filter rules + + + Delete + + + Query + + + Queries + + + Search + + + ({0} of {1}) + The text displayed in the management list title when the list has a filter applied. {0} will be the number of items shown in the list. {1} will be the total number of items in the list before filtering. + + + Searching... + The text displayed in the management list title when the list is processing a filter. + + + ({0}) + The text displayed in the management list title when the list does not have a filter applied. {0} will be the number of items shown in the list. + + + Filter + Localizable AutomationName for control that is used by accessibility screen readers. + + + Filter + + + Shortcut Rules + The name used to indicate custom filter rules which are specific to a particular application. + + + Columns Rules + The name used to indicate filter rules that are based upon the properties of the items in the list. + + + Sort Glyph + + + Sort Glyph + + + Collapse + + + Collapse + + + Sorted ascending + The text used for the accessible ItemStatus property when a column is sorted ascending. + + + Sorted descending + The text used for the accessible ItemStatus property when a column is sorted descending. + + + Collapse + + + Expand + + + Search + + + Cancel + + + Clear All + + + Clear All + + + Clear Search Text + + + Tasks + + + Search + The accessible name of the Search button in the filter panel. + + + Cancel + The accessible name of the Stop Search button in the filter panel. + + + Expand or Collapse Filter Panel + The accessible name of the button that expands/collapses the filter panel. + + + Filter + The background text of the list's search box when filtering is immediate. + + + Click to display saved search queries. + + + Filter applied. + + + and + The first header operator indicates that it is the first item in the list of filter rules. The AND value is used to indicate that it is and'ed with the above SearchBox. + + + and + The header operator indicates that it is the first item in a group of filter rules which are the same. The AND value is used to indicate that it is and'ed with the other groups in the panel. + + + or + The Item operator indicates that it is NOT the first item in a group of filter rules which are the same. The OR value is used to indicate that it is or'ed with the other items in the same group. + + + No matches found. + The text displayed in the ManagementList when the filter has been applied but matching items were found. + + + Collapse + + + Expand + + + Show Children + + + Show Children + + + {0}: {1} + The format string used for the ManagementList title when query has been applied. For example, "Users: My Fancy Query" + + + Cancel + + + OK + + diff --git a/src/Microsoft.Management.UI.Internal/themes/generic.xaml b/src/Microsoft.Management.UI.Internal/themes/generic.xaml new file mode 100644 index 00000000000..36d2354e1af --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/themes/generic.xaml @@ -0,0 +1,2790 @@ + + + + + M 0,0 L 4,3.5 L 0,7 Z + + M7.3391155,8.3084572 L7.3391708,16.109179 10.974259,12.431834 z + + + + + + + + + + + + + + + + + + M0.83647823,8.3277649 L5.9975096,11.519699 11.198949,8.3030383 + + M205.63696,8.9046901 L201.73368,12.456607 205.68294,16.093484 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs index 8ee3e95903d..dfe046ec8ca 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs @@ -1,70 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// 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; using System.Text; -using System.Resources; -using System.Reflection; - -#if CORECLR -using System.ComponentModel; -#else -using System.Threading; -#endif namespace Microsoft.PowerShell.Commands.Diagnostics.Common { internal static class CommonUtilities { - // - // StringArrayToString helper converts a string array into a comma-separated string. - // Note this has only limited use, individual strings cannot have commas. - // - public static string StringArrayToString(IEnumerable input) - { - string ret = ""; - foreach (string element in input) - { - ret += element + ", "; - } - - ret = ret.TrimEnd(); - ret = ret.TrimEnd(','); - - return ret; - } - -#if CORECLR private const string LibraryLoadDllName = "api-ms-win-core-libraryloader-l1-2-0.dll"; private const string LocalizationDllName = "api-ms-win-core-localization-l1-2-1.dll"; - private const string SysInfoDllName = "api-ms-win-core-sysinfo-l1-2-1.dll"; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct OSVERSIONINFOEX - { - public int OSVersionInfoSize; - public int MajorVersion; - public int MinorVersion; - public int BuildNumber; - public int PlatformId; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string CSDVersion; - public ushort ServicePackMajor; - public ushort ServicePackMinor; - public short SuiteMask; - public byte ProductType; - public byte Reserved; - } - - [DllImport(SysInfoDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool GetVersionEx(ref OSVERSIONINFOEX osVerEx); -#else - private const string LibraryLoadDllName = "kernel32.dll"; - private const string LocalizationDllName = "kernel32.dll"; -#endif - private const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002; @@ -87,20 +37,17 @@ uint dwFlags [DllImport(LibraryLoadDllName)] private static extern bool FreeLibrary(IntPtr hModule); - [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; - IntPtr moduleHandle = IntPtr.Zero; + msg = string.Empty; - moduleHandle = LoadLibraryEx(moduleName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); + IntPtr moduleHandle = LoadLibraryEx(moduleName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); if (moduleHandle == IntPtr.Zero) { return (uint)Marshal.GetLastWin32Error(); @@ -108,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) @@ -116,19 +62,19 @@ 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) { formatError = (uint)Marshal.GetLastWin32Error(); - //Console.WriteLine("Win32FormatMessage", String.Format(null, "Error formatting message: {0}", formatError)); } else { @@ -148,9 +94,7 @@ public static uint FormatMessageFromModule(uint lastError, string moduleName, ou public static ResourceManager GetResourceManager() { - // this naming pattern is dictated by the dotnet cli return new ResourceManager("Microsoft.PowerShell.Commands.Diagnostics.resources.GetEventResources", typeof(CommonUtilities).GetTypeInfo().Assembly); } } } - 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 88b3a6f134f..00000000000 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CoreCLR/Stubs.cs +++ /dev/null @@ -1,251 +0,0 @@ - -#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 858f5f0b6ff..2b11b6d3b03 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs @@ -1,13 +1,12 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; -using System.Collections.Specialized; using System.Collections.Generic; -using System.Diagnostics; +using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; namespace Microsoft.PowerShell.Commands.GetCounter { @@ -31,6 +30,7 @@ public DateTime OldestRecord return _oldestRecord; } } + private DateTime _oldestRecord = DateTime.MinValue; public DateTime NewestRecord @@ -40,6 +40,7 @@ public DateTime NewestRecord return _newestRecord; } } + private DateTime _newestRecord = DateTime.MaxValue; public UInt32 SampleCount @@ -49,7 +50,7 @@ public UInt32 SampleCount return _sampleCount; } } + private UInt32 _sampleCount = 0; } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs index 4ba65b3c8ea..9089216a485 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs @@ -1,18 +1,10 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Reflection; -using System.Collections.Generic; using System.Diagnostics; -using System.ComponentModel; -using System.Resources; -using Microsoft.Powershell.Commands.GetCounter.PdhNative; -using System.Globalization; using System.Diagnostics.CodeAnalysis; - +using System.Resources; namespace Microsoft.PowerShell.Commands.GetCounter { @@ -35,106 +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; } - set { _path = value; } - } - private string _path = ""; + public string Path { get; set; } = string.Empty; + public string InstanceName { get; set; } = string.Empty; - public string InstanceName - { - get { return _instanceName; } - set { _instanceName = value; } - } - private string _instanceName = ""; + public double CookedValue { get; set; } + public UInt64 RawValue { get; set; } - public double CookedValue - { - get { return _cookedValue; } - set { _cookedValue = value; } - } - private double _cookedValue = 0; + public UInt64 SecondValue { get; set; } - public UInt64 RawValue - { - get { return _rawValue; } - set { _rawValue = value; } - } - private UInt64 _rawValue = 0; + public uint MultipleCount { get; set; } - public UInt64 SecondValue - { - get { return _secondValue; } - set { _secondValue = value; } - } - private UInt64 _secondValue = 0; + public PerformanceCounterType CounterType { get; set; } - public uint MultipleCount - { - get { return _multiCount; } - set { _multiCount = value; } - } - private uint _multiCount = 0; + public DateTime Timestamp { get; set; } = DateTime.MinValue; - public PerformanceCounterType CounterType - { - get { return _counterType; } - set { _counterType = value; } - } - private PerformanceCounterType _counterType = 0; - - public DateTime Timestamp - { - get { return _timeStamp; } - set { _timeStamp = value; } - } - private DateTime _timeStamp = DateTime.MinValue; + public UInt64 Timestamp100NSec { get; set; } + public UInt32 Status { get; set; } - public UInt64 Timestamp100NSec - { - get { return _timeStamp100nSec; } - set { _timeStamp100nSec = value; } - } - private UInt64 _timeStamp100nSec = 0; + public UInt32 DefaultScale { get; set; } - 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 @@ -148,28 +77,18 @@ internal PerformanceCounterSampleSet(DateTime timeStamp, PerformanceCounterSample[] counterSamples, bool firstSet) : this() { - _timeStamp = timeStamp; - _counterSamples = counterSamples; + Timestamp = timeStamp; + CounterSamples = counterSamples; } - public DateTime Timestamp - { - get { return _timeStamp; } - set { _timeStamp = value; } - } - 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 fb7edd1ac92..dfc175b604c 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs @@ -1,13 +1,10 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Collections; -using System.Collections.Specialized; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; -using System.ComponentModel; namespace Microsoft.PowerShell.Commands.GetCounter { @@ -19,90 +16,56 @@ 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; - } - public string CounterSetName - { - get - { - return _counterSetName; - } + CounterSetType = categoryType; + Description = setHelp; + CounterInstanceMapping = counterInstanceMapping; } - private string _counterSetName = ""; - public string MachineName - { - get - { - return _machineName; - } - } - private string _machineName = "."; + public string CounterSetName { get; } = string.Empty; - public PerformanceCounterCategoryType CounterSetType - { - get - { - return _counterSetType; - } - } - private PerformanceCounterCategoryType _counterSetType; + public string MachineName { get; } = "."; + public PerformanceCounterCategoryType CounterSetType { get; } - public string Description - { - get - { - return _description; - } - } - private string _description = ""; + public string Description { get; } = string.Empty; - internal Dictionary CounterInstanceMapping - { - get - { - return _counterInstanceMapping; - } - } - 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); } @@ -114,17 +77,18 @@ 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); } } + return retColl; } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/ExportCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/ExportCounterCommand.cs index 9a7e138b521..9385e55909f 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/ExportCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/ExportCounterCommand.cs @@ -1,32 +1,30 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.IO; -using System.Xml; -using System.Net; -using System.Management.Automation; -using System.ComponentModel; -using System.Reflection; -using System.Globalization; -using System.Management.Automation.Runspaces; using System.Collections; -using System.Collections.Specialized; 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.Resources; +using System.Text; using System.Threading; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Powershell.Commands.GetCounter.PdhNative; -using Microsoft.PowerShell.Commands.GetCounter; -using Microsoft.PowerShell.Commands.Diagnostics.Common; +using System.Xml; +using Microsoft.PowerShell.Commands.Diagnostics.Common; +using Microsoft.PowerShell.Commands.GetCounter; +using Microsoft.Powershell.Commands.GetCounter.PdhNative; namespace Microsoft.PowerShell.Commands { @@ -49,12 +47,13 @@ public sealed class ExportCounterCommand : PSCmdlet public string Path { get { return _path; } + set { _path = value; } } + private string _path; private string _resolvedPath; - // // Format parameter. // Valid strings are "blg", "csv", "tsv" (case-insensitive). @@ -69,11 +68,11 @@ public string Path public string FileFormat { get { return _format; } + set { _format = value; } } - private string _format = "blg"; - + private string _format = "blg"; // // MaxSize parameter @@ -84,10 +83,11 @@ public string FileFormat public UInt32 MaxSize { get { return _maxSize; } + set { _maxSize = value; } } - private UInt32 _maxSize = 0; + private UInt32 _maxSize = 0; // // InputObject parameter @@ -105,10 +105,11 @@ public UInt32 MaxSize public PerformanceCounterSampleSet[] InputObject { get { return _counterSampleSets; } + set { _counterSampleSets = value; } } - private PerformanceCounterSampleSet[] _counterSampleSets = new PerformanceCounterSampleSet[0]; + private PerformanceCounterSampleSet[] _counterSampleSets = new PerformanceCounterSampleSet[0]; // // Force switch @@ -118,8 +119,10 @@ public PerformanceCounterSampleSet[] InputObject public SwitchParameter Force { get { return _force; } + set { _force = value; } } + private SwitchParameter _force; // @@ -130,11 +133,11 @@ public SwitchParameter Force public SwitchParameter Circular { get { return _circular; } + set { _circular = value; } } - private SwitchParameter _circular; - + private SwitchParameter _circular; private ResourceManager _resourceMgr = null; @@ -159,7 +162,7 @@ protected override void BeginProcessing() throw new PlatformNotSupportedException(); } - // PowerShell Core requires at least Windows 7, + // PowerShell 7 requires at least Windows 7, // so no version test is needed _pdhHelper = new PdhHelper(false); #else @@ -213,7 +216,6 @@ protected override void EndProcessing() _pdhHelper.Dispose(); } - /// /// Handle Control-C /// @@ -247,6 +249,7 @@ protected override void ProcessRecord() { res = _pdhHelper.AddRelogCountersPreservingPaths(_counterSampleSets[0]); } + if (res != 0) { ReportPdhError(res, true); @@ -279,7 +282,6 @@ protected override void ProcessRecord() _queryInitialized = true; } - foreach (PerformanceCounterSampleSet set in _counterSampleSets) { _pdhHelper.ResetRelogValues(); @@ -299,6 +301,7 @@ protected override void ProcessRecord() ReportPdhError(res, true); } } + res = _pdhHelper.WriteRelogSample(set.Timestamp); if (res != 0) { @@ -365,6 +368,7 @@ private void ReportPdhError(uint res, bool bTerminate) { msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterApiError"), res); } + Exception exc = new Exception(msg); if (bTerminate) { diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs index 29da31a48bd..0122ad1941f 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs @@ -1,39 +1,25 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.IO; -using System.Xml; -using System.Net; -using System.Management.Automation; -using System.ComponentModel; -using System.Reflection; -using System.Globalization; -using System.Management.Automation.Runspaces; -using System.Collections; -using System.Collections.Specialized; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Diagnostics; -using System.Security; -using System.Security.Principal; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation; using System.Resources; using System.Threading; -using System.Diagnostics.CodeAnalysis; using Microsoft.Powershell.Commands.GetCounter.PdhNative; -using Microsoft.PowerShell.Commands.GetCounter; using Microsoft.PowerShell.Commands.Diagnostics.Common; - +using Microsoft.PowerShell.Commands.GetCounter; namespace Microsoft.PowerShell.Commands { - /// + /// /// Class that implements the Get-Counter cmdlet. - /// - [Cmdlet(VerbsCommon.Get, "Counter", DefaultParameterSetName = "GetCounterSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=138335")] + /// + [Cmdlet(VerbsCommon.Get, "Counter", DefaultParameterSetName = "GetCounterSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109647")] public sealed class GetCounterCommand : PSCmdlet { // @@ -51,14 +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 @@ -76,23 +55,28 @@ 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 { _counter = value; _defaultCounters = false; } } + private string[] _counter = {@"\network interface(*)\bytes total/sec", @"\processor(_total)\% processor time", @"\memory\% committed bytes in use", @"\memory\cache faults/sec", @"\physicaldisk(_total)\% disk time", @"\physicaldisk(_total)\current disk queue length"}; - private bool _defaultCounters = true; - private List _accumulatedCounters = new List(); + private bool _defaultCounters = true; + private readonly List _accumulatedCounters = new(); // // SampleInterval parameter. @@ -104,18 +88,13 @@ 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 // private const Int64 KEEP_ON_SAMPLING = -1; + [Parameter( ParameterSetName = "GetCounterSet", ValueFromPipeline = false, @@ -124,17 +103,21 @@ public int SampleInterval [ValidateRange((Int64)1, Int64.MaxValue)] public Int64 MaxSamples { - get { return _maxSamples; } + get + { + return _maxSamples; + } + set { _maxSamples = value; _maxSamplesSpecified = true; } } + private Int64 _maxSamples = 1; private bool _maxSamplesSpecified = false; - // // Continuous switch // @@ -142,10 +125,11 @@ public Int64 MaxSamples public SwitchParameter Continuous { get { return _continuous; } + set { _continuous = value; } } - private bool _continuous = false; + private bool _continuous = false; // // ComputerName parameter @@ -162,18 +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 = new string[0]; + 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"; @@ -185,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), } } }; @@ -205,33 +184,25 @@ public string[] ComputerName // protected override void BeginProcessing() { - -#if CORECLR if (Platform.IsIoT) { // IoT does not have the '$env:windir\System32\pdh.dll' assembly which is required by this cmdlet. throw new PlatformNotSupportedException(); } - // PowerShell Core requires at least Windows 7, - // so no version test is needed - _pdhHelper = new PdhHelper(false); -#else - _pdhHelper = new PdhHelper(System.Environment.OSVersion.Version.Major < 6); -#endif + _pdhHelper = new PdhHelper(); _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; } - 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)); } } @@ -249,7 +220,6 @@ protected override void EndProcessing() _pdhHelper.Dispose(); } - // // Handle Control-C // @@ -278,7 +248,7 @@ protected override void ProcessRecord() break; default: - Debug.Assert(false, string.Format(CultureInfo.InvariantCulture, "Invalid parameter set name: {0}", ParameterSetName)); + Debug.Fail(string.Create(CultureInfo.InvariantCulture, $"Invalid parameter set name: {ParameterSetName}")); break; } } @@ -302,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); } @@ -319,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 + // 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; @@ -349,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) { @@ -358,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; @@ -391,13 +361,10 @@ private void ProcessListSetPerMachine(string machine) instanceArray[0] = "*"; } - Dictionary counterInstanceMapping = new Dictionary(); + Dictionary counterInstanceMapping = new(); foreach (string counter in counterSetCounters) { - if (!counterInstanceMapping.ContainsKey(counter)) - { - counterInstanceMapping.Add(counter, instanceArray); - } + counterInstanceMapping.TryAdd(counter, instanceArray); } PerformanceCounterCategoryType categoryType = PerformanceCounterCategoryType.Unknown; @@ -405,14 +372,14 @@ private void ProcessListSetPerMachine(string machine) { categoryType = PerformanceCounterCategoryType.MultiInstance; } - else //if (counterSetInstances.Count == 1) //??? + else // if (counterSetInstances.Count == 1) //??? { categoryType = PerformanceCounterCategoryType.SingleInstance; } 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; } @@ -420,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)); } @@ -442,25 +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; @@ -476,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); @@ -488,26 +454,29 @@ 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; } + allExpandedPaths.Add(expandedPath); } } + if (allExpandedPaths.Count == 0) { return; } 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); @@ -529,16 +498,16 @@ 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 + // Display valid data if (!bSkip) { WriteSampleSetObject(nextSet); sampleReads++; } - //Don't need to skip anymore + // Don't need to skip anymore bSkip = false; } else if (res == PdhResults.PDH_NO_DATA || res == PdhResults.PDH_INVALID_DATA) @@ -565,12 +534,7 @@ private void ProcessGetCounter() break; } -#if CORECLR - // CoreCLR has no overload of WaitOne with (interval, exitContext) - bool cancelled = _cancelEventArrived.WaitOne((int)_sampleInterval * 1000); -#else - bool cancelled = _cancelEventArrived.WaitOne((int)_sampleInterval * 1000, true); -#endif + bool cancelled = _cancelEventArrived.WaitOne((int)SampleInterval * 1000, true); if (cancelled) { break; @@ -586,7 +550,8 @@ 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,7 +562,6 @@ private void ReportPdhError(uint res, bool bTerminate) } } - // // CombineMachinesAndCounterPaths() helper. // For paths that do not contain machine names, creates a path for each machine in machineNames. @@ -605,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; @@ -615,21 +579,22 @@ private List CombineMachinesAndCounterPaths() foreach (string path in _accumulatedCounters) { - if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) //NOTE: can we do anything smarter here? + if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) // NOTE: can we do anything smarter here? { retColl.Add(path); } 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); } } } @@ -650,22 +615,18 @@ 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; } } + WriteObject(set); } private static CultureInfo GetCurrentCulture() { -#if CORECLR - return CultureInfo.CurrentCulture; -#else return Thread.CurrentThread.CurrentUICulture; -#endif } } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs index f23a6da9ad0..0ce68a31d73 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs @@ -1,35 +1,36 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Xml; -using System.Net; -using System.Management.Automation; -using System.Reflection; -using System.Globalization; using System.Collections; -using System.Collections.Specialized; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Eventing.Reader; -using System.Security.Principal; +using System.Globalization; +using System.Management.Automation; +using System.Net; using System.Resources; -using System.Diagnostics.CodeAnalysis; +using System.Security.Principal; using System.Text; +using System.Xml; [assembly: CLSCompliant(false)] namespace Microsoft.PowerShell.Commands { - /// + /// /// Class that implements the Get-WinEvent cmdlet. - /// - [Cmdlet(VerbsCommon.Get, "WinEvent", DefaultParameterSetName = "GetLogSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=138336")] + /// + [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 { /// - /// ListLog parameter + /// ListLog parameter. /// [Parameter( Position = 0, @@ -44,16 +45,10 @@ 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 + /// GetLog parameter. /// [Parameter( Position = 0, @@ -66,16 +61,10 @@ 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 + /// ListProvider parameter. /// [Parameter( Position = 0, @@ -86,22 +75,14 @@ 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 + /// ProviderName parameter. /// [Parameter( Position = 0, @@ -110,22 +91,14 @@ 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 + /// Path parameter. /// [Parameter( Position = 0, @@ -134,22 +107,15 @@ 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 + /// MaxEvents parameter. /// [Parameter( ParameterSetName = "FileSet", @@ -181,16 +147,11 @@ 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 + /// ComputerName parameter. /// [Parameter( ParameterSetName = "ListProviderSet", @@ -216,18 +177,12 @@ 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 + /// Credential parameter. /// [Parameter(ParameterSetName = "ListProviderSet")] [Parameter(ParameterSetName = "GetProviderSet")] @@ -237,16 +192,10 @@ 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 + /// FilterXPath parameter. /// [Parameter( ParameterSetName = "FileSet", @@ -264,15 +213,10 @@ 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 + /// FilterXml parameter. /// [Parameter( Position = 0, @@ -281,22 +225,10 @@ 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 + /// FilterHashtable parameter. /// [Parameter( Position = 0, @@ -305,50 +237,37 @@ 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 + /// Force switch. /// [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 + /// Oldest switch. /// [Parameter(ParameterSetName = "FileSet")] [Parameter(ParameterSetName = "GetProviderSet")] [Parameter(ParameterSetName = "GetLogSet")] - [Parameter(ParameterSetName = "HashQuerySet")] [Parameter(ParameterSetName = "XmlQuerySet")] public SwitchParameter Oldest { get { return _oldest; } + set { _oldest = value; } } - private bool _oldest = false; + private bool _oldest = false; // // Query builder constant strings @@ -361,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}')"; @@ -378,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; @@ -404,18 +323,16 @@ public SwitchParameter Oldest private const string hashkey_data_lc = "data"; private const string hashkey_supress_lc = "suppresshashfilter"; - /// - /// BeginProcessing() is invoked once per pipeline: we will load System.Core.dll here + /// BeginProcessing() is invoked once per pipeline: we will load System.Core.dll here. /// protected override void BeginProcessing() { _resourceMgr = Microsoft.PowerShell.Commands.Diagnostics.Common.CommonUtilities.GetResourceManager(); } - /// - /// EndProcessing() is invoked once per pipeline + /// EndProcessing() is invoked once per pipeline. /// protected override void EndProcessing() { @@ -438,7 +355,6 @@ protected override void EndProcessing() } } - /// /// ProcessRecord() override. /// This is the main entry point for the cmdlet. @@ -476,19 +392,18 @@ 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; } } - // // AccumulatePipelineCounters() accumulates log names in the pipeline scenario: // we do not want to construct a query until all the log names are supplied. // private void AccumulatePipelineLogNames() { - _accumulatedLogNames.AddRange(_logName); + _accumulatedLogNames.AddRange(LogName); } // @@ -497,7 +412,7 @@ private void AccumulatePipelineLogNames() // private void AccumulatePipelineProviderNames() { - _accumulatedProviderNames.AddRange(_logName); + _accumulatedProviderNames.AddRange(LogName); } // @@ -506,7 +421,7 @@ private void AccumulatePipelineProviderNames() // private void AccumulatePipelineFileNames() { - _accumulatedFileNames.AddRange(_logName); + _accumulatedFileNames.AddRange(LogName); } // @@ -531,8 +446,9 @@ private void ProcessGetLog() } else { - logQuery = new EventLogQuery(_logNamesMatchingWildcard[0], PathType.LogName, _filter); + logQuery = new EventLogQuery(_logNamesMatchingWildcard[0], PathType.LogName, FilterXPath); } + logQuery.Session = eventLogSession; logQuery.ReverseDirection = !_oldest; @@ -540,7 +456,6 @@ private void ProcessGetLog() } } - // // Process GetProviderSet parameter set // @@ -548,7 +463,7 @@ private void ProcessGetProvider() { using (EventLogSession eventLogSession = CreateSession()) { - FindProvidersByLogForWildcardPatterns(eventLogSession, _providerName); + FindProvidersByLogForWildcardPatterns(eventLogSession, ProviderName); if (_providersByLogMap.Count == 0) { @@ -558,7 +473,6 @@ private void ProcessGetProvider() return; } - EventLogQuery logQuery = null; if (_providersByLogMap.Count > 1) { @@ -574,17 +488,17 @@ 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")); } } + logQuery.Session = eventLogSession; - logQuery.ReverseDirection = !_oldest; ; + logQuery.ReverseDirection = !_oldest; ReadEvents(logQuery); } } - // // Process ListLog parameter set // @@ -592,22 +506,23 @@ private void ProcessListLog() { using (EventLogSession eventLogSession = CreateSession()) { - foreach (string logPattern in _listLog) + foreach (string logPattern in ListLog) { bool bMatchFound = false; + WildcardPattern wildLogPattern = new(logPattern, WildcardOptions.IgnoreCase); foreach (string logName in eventLogSession.GetLogNames()) { - WildcardPattern wildLogPattern = new WildcardPattern(logPattern, WildcardOptions.IgnoreCase); - if (((!WildcardPattern.ContainsWildcardCharacters(logPattern)) - && string.Equals(logPattern, logName, StringComparison.CurrentCultureIgnoreCase)) + && string.Equals(logPattern, logName, StringComparison.OrdinalIgnoreCase)) || (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. @@ -620,35 +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)); } } @@ -662,22 +594,21 @@ private void ProcessListProvider() { using (EventLogSession eventLogSession = CreateSession()) { - foreach (string provPattern in _listProvider) + foreach (string provPattern in ListProvider) { bool bMatchFound = false; + WildcardPattern wildProvPattern = new(provPattern, WildcardOptions.IgnoreCase); foreach (string provName in eventLogSession.GetProviderNames()) { - WildcardPattern wildProvPattern = new WildcardPattern(provPattern, WildcardOptions.IgnoreCase); - if (((!WildcardPattern.ContainsWildcardCharacters(provPattern)) - && string.Equals(provPattern, provName, StringComparison.CurrentCultureIgnoreCase)) + && string.Equals(provPattern, provName, StringComparison.OrdinalIgnoreCase)) || (wildProvPattern.IsMatch(provName))) { try { - ProviderMetadata provObj = new ProviderMetadata(provName, eventLogSession, CultureInfo.CurrentCulture); + ProviderMetadata provObj = new(provName, eventLogSession, CultureInfo.CurrentCulture); WriteObject(provObj); bMatchFound = true; } @@ -686,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; } @@ -696,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)); } } @@ -716,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) { @@ -738,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; @@ -746,7 +677,6 @@ private void ProcessFilterXml() } } - // // Process FileSet parameter set // @@ -758,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}")); } } @@ -781,8 +711,9 @@ private void ProcessFile() } else { - logQuery = new EventLogQuery(_resolvedPaths[0], PathType.FilePath, _filter); + logQuery = new EventLogQuery(_resolvedPaths[0], PathType.FilePath, FilterXPath); } + logQuery.Session = eventLogSession; logQuery.ReverseDirection = !_oldest; @@ -799,14 +730,13 @@ private void ProcessHashQuery() using (EventLogSession eventLogSession = CreateSession()) { - string query = BuildStructuredQuery(eventLogSession); if (query.Length == 0) { 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; @@ -823,49 +753,48 @@ 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 ); // // Force the destruction of cached password // - netCred.Password = ""; + netCred.Password = string.Empty; return eventLogSession; } - // // ReadEvents helper. // 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) @@ -879,16 +808,18 @@ private void ReadEvents(EventLogQuery logQuery) WriteError(new ErrorRecord(exc, exc.Message, ErrorCategory.NotSpecified, null)); continue; } + if (evtObj == null) { 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 @@ -899,8 +830,8 @@ private void ReadEvents(EventLogQuery logQuery) { WriteError(new ErrorRecord(exc, exc.Message, ErrorCategory.NotSpecified, null)); } - outputObj.Properties.Add(new PSNoteProperty("Message", evtMessage)); + outputObj.Properties.Add(new PSNoteProperty("Message", evtMessage)); // // Enumerate the object one level to get to event payload @@ -912,20 +843,18 @@ 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)); } } } - - // // BuildStructuredQuery() builds a structured query from cmdlet arguments. // private string BuildStructuredQuery(EventLogSession eventLogSession) { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); switch (ParameterSetName) { @@ -945,20 +874,32 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) string providerFilter = AddProviderPredicatesToFilter(_providersByLogMap[log]); result.AppendFormat(CultureInfo.InvariantCulture, queryTemplate, new object[] { queryId++, log, providerFilter }); } + result.Append(queryListClose); } + break; case "GetLogSet": { + const int WindowsEventLogAPILimit = 256; + if (_logNamesMatchingWildcard.Count > WindowsEventLogAPILimit) + { + string msg = _resourceMgr.GetString("LogCountLimitExceeded"); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, _logNamesMatchingWildcard.Count, WindowsEventLogAPILimit)); + ThrowTerminatingError(new ErrorRecord(exc, "LogCountLimitExceeded", ErrorCategory.LimitsExceeded, null)); + } + result.Append(queryListOpen); 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); } + break; case "FileSet": @@ -968,10 +909,12 @@ 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); } + break; case "HashQuerySet": @@ -979,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; } @@ -993,12 +936,12 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) // private string BuildXPathFromHashTable(Hashtable hash) { - StringBuilder xpathString = new StringBuilder(""); + StringBuilder xpathString = new(string.Empty); bool bDateTimeHandled = false; foreach (string key in hash.Keys) { - string added = ""; + string added = string.Empty; switch (key.ToLowerInvariant()) { @@ -1057,8 +1000,8 @@ private string BuildXPathFromHashTable(Hashtable hash) // // Fix Issue #2327 added = HandleNamedDataHashValue(key, hash[key]); - } + break; } @@ -1068,9 +1011,9 @@ private string BuildXPathFromHashTable(Hashtable hash) { xpathString.Append(" and "); } + xpathString.Append(added); } - } return xpathString.ToString(); @@ -1082,16 +1025,16 @@ private string BuildXPathFromHashTable(Hashtable hash) // private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession) { - StringBuilder result = new StringBuilder(""); + StringBuilder result = new(string.Empty); result.Append(queryListOpen); uint queryId = 0; - foreach (Hashtable hash in _selector) + foreach (Hashtable hash in FilterHashtable) { - string xpathString = ""; - string xpathStringSuppress = ""; + string xpathString = string.Empty; + string xpathStringSuppress = string.Empty; CheckHashTableForQueryPathPresence(hash); @@ -1099,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 @@ -1115,10 +1058,10 @@ 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]) + foreach (object elt in (Array)hash[hashkey_logname_lc]) { logPatterns.Add(elt.ToString()); } @@ -1138,11 +1081,12 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession string.Format(CultureInfo.InvariantCulture, suppressOpener, queryId++, logName)); } } + if (hash.ContainsKey(hashkey_path_lc)) { if (hash[hashkey_path_lc] is Array) { - foreach (Object elt in (Array)hash[hashkey_path_lc]) + foreach (object elt in (Array)hash[hashkey_path_lc]) { StringCollection resolvedPaths = ValidateAndResolveFilePath(elt.ToString()); foreach (string resolvedPath in resolvedPaths) @@ -1166,12 +1110,13 @@ 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]) + foreach (object elt in (Array)hash[hashkey_providername_lc]) { provPatterns.Add(elt.ToString()); } @@ -1201,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) { @@ -1231,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; } @@ -1256,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); } @@ -1274,7 +1218,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession string query = queriedLogsQueryMap[keyLogName]; result.Append(query); - if (query.EndsWith("*", StringComparison.OrdinalIgnoreCase)) + if (query.EndsWith('*')) { // // No provider predicate: just add the XPath string @@ -1293,6 +1237,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession { result.Append(" and ").Append(xpathString); } + result.Append(propClose); } @@ -1309,8 +1254,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession result.Append(queryCloser); } - } //end foreach hashtable - + } result.Append(queryListClose); @@ -1321,26 +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(); @@ -1350,26 +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(); @@ -1379,15 +1323,14 @@ private string HandleLevelHashValue(Object value) // HandleKeywordHashValue helper for hashtable structured query builder. // Constructs and returns Keyword XPath portion as a string. // - private string HandleKeywordHashValue(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) + foreach (object keyword in keywordArray) { if (KeywordStringToInt64(keyword.ToString(), ref keywordLong)) { @@ -1399,8 +1342,9 @@ private string HandleKeywordHashValue(Object value) { if (!KeywordStringToInt64(value.ToString(), ref keywordLong)) { - return ""; + return string.Empty; } + keywordsMask |= keywordLong; } @@ -1413,7 +1357,7 @@ private string HandleKeywordHashValue(Object value) // Handles both SIDs and domain account names. // Writes an error and returns an empty string if the SID or account names are not valid. // - private string HandleContextHashValue(Object value) + private string HandleContextHashValue(object value) { SecurityIdentifier sidCandidate = null; try @@ -1429,34 +1373,33 @@ 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 ""; + return string.Empty; } } return string.Format(CultureInfo.InvariantCulture, SystemSecurityTemplate, sidCandidate.ToString()); } - // // HandleStartTimeHashValue helper for hashtable structured query builder. // Constructs and returns TimeCreated XPath portion as a string. // NOTE that it also handles the hashtable "endtime" value (if supplied). // - private string HandleStartTimeHashValue(Object value, Hashtable hash) + 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 ""; + return string.Empty; } startTime = startTime.ToUniversalTime(); @@ -1464,10 +1407,10 @@ 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 ""; + return string.Empty; } endTime = endTime.ToUniversalTime(); @@ -1488,19 +1431,18 @@ private string HandleStartTimeHashValue(Object value, Hashtable hash) return ret.ToString(); } - // // HandleEndTimeHashValue helper for hashtable structured query builder. // Constructs and returns TimeCreated XPath portion as a string. // NOTE that it also handles the hashtable "starttime" value (if supplied). // - private string HandleEndTimeHashValue(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 ""; + return string.Empty; } endTime = endTime.ToUniversalTime(); @@ -1509,10 +1451,10 @@ 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 ""; + return string.Empty; } startTime = startTime.ToUniversalTime(); @@ -1538,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()); @@ -1553,7 +1494,8 @@ private string HandleDataHashValue(Object value) ret.Append(" or "); } } - ret.Append(")"); + + ret.Append(')'); } else { @@ -1563,19 +1505,17 @@ private string HandleDataHashValue(Object value) return ret.ToString(); } - // // HandleNamedDataHashValue helper for hashtable structured query builder. // 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, @@ -1586,7 +1526,8 @@ private string HandleNamedDataHashValue(String key, Object value) ret.Append(" or "); } } - ret.Append(")"); + + ret.Append(')'); } else { @@ -1598,7 +1539,6 @@ private string HandleNamedDataHashValue(String key, Object value) return ret.ToString(); } - // // Helper checking whether at least one of log, _path, provider is specified. // It will ThrowTerminatingError in case none of those keys are present. @@ -1612,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)); } } @@ -1628,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)); } } @@ -1648,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; } @@ -1657,29 +1597,30 @@ 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; } + if (!Oldest.IsPresent) { 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)); } } + return true; } - // // KeywordStringToInt64 helper converts a string to Int64. // 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 { @@ -1688,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; } @@ -1710,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; } @@ -1726,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 @@ -1735,27 +1676,27 @@ private StringCollection ValidateAndResolveFilePath(string path) } catch (PSNotSupportedException notSupported) { - WriteError(new ErrorRecord(notSupported, "", ErrorCategory.ObjectNotFound, path)); + WriteError(new ErrorRecord(notSupported, string.Empty, ErrorCategory.ObjectNotFound, path)); return retColl; } catch (System.Management.Automation.DriveNotFoundException driveNotFound) { - WriteError(new ErrorRecord(driveNotFound, "", ErrorCategory.ObjectNotFound, path)); + WriteError(new ErrorRecord(driveNotFound, string.Empty, ErrorCategory.ObjectNotFound, path)); return retColl; } catch (ProviderNotFoundException providerNotFound) { - WriteError(new ErrorRecord(providerNotFound, "", ErrorCategory.ObjectNotFound, path)); + WriteError(new ErrorRecord(providerNotFound, string.Empty, ErrorCategory.ObjectNotFound, path)); return retColl; } catch (ItemNotFoundException pathNotFound) { - WriteError(new ErrorRecord(pathNotFound, "", ErrorCategory.ObjectNotFound, path)); + WriteError(new ErrorRecord(pathNotFound, string.Empty, ErrorCategory.ObjectNotFound, path)); return retColl; } catch (Exception exc) { - WriteError(new ErrorRecord(exc, "", ErrorCategory.ObjectNotFound, path)); + WriteError(new ErrorRecord(exc, string.Empty, ErrorCategory.ObjectNotFound, path)); return retColl; } @@ -1767,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; } @@ -1784,9 +1725,10 @@ 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)); } + continue; } @@ -1805,28 +1747,27 @@ private StringCollection ValidateAndResolveFilePath(string path) // private void CheckHashTablesForNullValues() { - foreach (Hashtable hash in _selector) + foreach (Hashtable hash in FilterHashtable) { foreach (string key in hash.Keys) { - Object value = hash[key]; + object value = hash[key]; 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) + 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)); } } @@ -1847,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 + "]"; } @@ -1862,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 + "]"; @@ -1876,34 +1817,33 @@ private string AddProviderPredicatesToFilter(StringCollection providers) return ret; } - // // BuildProvidersPredicate() builds a predicate expression like: // "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 ""; + 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(); } - // // BuildAllProvidersPredicate() builds a predicate expression like: // "System/Provider[@Name='a' or @Name='b']" @@ -1915,12 +1855,12 @@ private string BuildAllProvidersPredicate() { if (_providersByLogMap.Count == 0) { - return ""; + 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) { @@ -1936,19 +1876,18 @@ 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(); } - // // AddLogsForProviderToInternalMap helper. // Retrieves log names to which _providerName writes. @@ -1960,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; @@ -1972,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) @@ -1985,13 +1924,12 @@ 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); } else - { // // Log is there: add provider, if needed @@ -2012,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; } @@ -2038,12 +1976,12 @@ private void FindLogNamesMatchingWildcards(EventLogSession eventLogSession, IEnu foreach (string logPattern in logPatterns) { bool bMatched = false; + WildcardPattern wildLogPattern = new(logPattern, WildcardOptions.IgnoreCase); + foreach (string actualLogName in eventLogSession.GetLogNames()) { - WildcardPattern wildLogPattern = new WildcardPattern(logPattern, WildcardOptions.IgnoreCase); - if (((!WildcardPattern.ContainsWildcardCharacters(logPattern)) - && (logPattern.Equals(actualLogName, StringComparison.CurrentCultureIgnoreCase))) + && (logPattern.Equals(actualLogName, StringComparison.OrdinalIgnoreCase))) || (wildLogPattern.IsMatch(actualLogName))) { @@ -2061,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; } @@ -2080,13 +2018,15 @@ private void FindLogNamesMatchingWildcards(EventLogSession eventLogSession, IEnu { _logNamesMatchingWildcard.Add(actualLogName.ToLowerInvariant()); } + bMatched = true; } } + 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)); } } @@ -2105,29 +2045,28 @@ private void FindProvidersByLogForWildcardPatterns(EventLogSession eventLogSessi foreach (string provPattern in providerPatterns) { bool bMatched = false; + WildcardPattern wildProvPattern = new(provPattern, WildcardOptions.IgnoreCase); + foreach (string provName in eventLogSession.GetProviderNames()) { - WildcardPattern wildProvPattern = new WildcardPattern(provPattern, WildcardOptions.IgnoreCase); - if (((!WildcardPattern.ContainsWildcardCharacters(provPattern)) - && (provPattern.Equals(provName, StringComparison.CurrentCultureIgnoreCase))) + && (provPattern.Equals(provName, StringComparison.OrdinalIgnoreCase))) || (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; } } + 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 6aa23f2eca7..a4693c454c0 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventSnapin.cs @@ -1,13 +1,11 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Text; -using System.Management.Automation; using System.ComponentModel; +using System.Management.Automation; +using System.Text; namespace Microsoft.PowerShell.Commands { @@ -83,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 { @@ -92,10 +90,11 @@ public override string[] Types return _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 d8635b38b5d..729334d0605 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs @@ -1,32 +1,30 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.IO; -using System.Xml; -using System.Net; -using System.Management.Automation; -using System.ComponentModel; -using System.Reflection; -using System.Globalization; -using System.Management.Automation.Runspaces; using System.Collections; -using System.Collections.Specialized; 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.Resources; +using System.Text; using System.Threading; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Powershell.Commands.GetCounter.PdhNative; -using Microsoft.PowerShell.Commands.GetCounter; -using Microsoft.PowerShell.Commands.Diagnostics.Common; +using System.Xml; +using Microsoft.PowerShell.Commands.Diagnostics.Common; +using Microsoft.PowerShell.Commands.GetCounter; +using Microsoft.Powershell.Commands.GetCounter.PdhNative; namespace Microsoft.PowerShell.Commands { @@ -54,15 +52,16 @@ public sealed class ImportCounterCommand : PSCmdlet public string[] Path { get { return _path; } + set { _path = value; } } + private string[] _path; private StringCollection _resolvedPaths = new StringCollection(); private List _accumulatedFileNames = new List(); - // // ListSet parameter // @@ -80,10 +79,11 @@ public string[] Path public string[] ListSet { get { return _listSet; } + set { _listSet = value; } } - private string[] _listSet = new string[0]; + private string[] _listSet = Array.Empty(); // // StartTime parameter @@ -96,10 +96,11 @@ public string[] ListSet public DateTime StartTime { get { return _startTime; } + set { _startTime = value; } } - private DateTime _startTime = DateTime.MinValue; + private DateTime _startTime = DateTime.MinValue; // // EndTime parameter @@ -112,10 +113,11 @@ public DateTime StartTime public DateTime EndTime { get { return _endTime; } + set { _endTime = value; } } - private DateTime _endTime = DateTime.MaxValue; + private DateTime _endTime = DateTime.MaxValue; // // Counter parameter @@ -133,9 +135,11 @@ public DateTime EndTime public string[] Counter { get { return _counter; } + set { _counter = value; } } - private string[] _counter = new string[0]; + + private string[] _counter = Array.Empty(); // // Summary switch @@ -144,8 +148,10 @@ public string[] Counter public SwitchParameter Summary { get { return _summary; } + set { _summary = value; } } + private SwitchParameter _summary; // @@ -161,10 +167,11 @@ public SwitchParameter Summary public Int64 MaxSamples { get { return _maxSamples; } + set { _maxSamples = value; } } - private Int64 _maxSamples = KEEP_ON_SAMPLING; + private Int64 _maxSamples = KEEP_ON_SAMPLING; private ResourceManager _resourceMgr = null; @@ -172,7 +179,6 @@ public Int64 MaxSamples private bool _stopping = false; - // // AccumulatePipelineFileNames() accumulates counter file paths in the pipeline scenario: // we do not want to construct a Pdh query until all the file names are supplied. @@ -182,7 +188,6 @@ private void AccumulatePipelineFileNames() _accumulatedFileNames.AddRange(_path); } - // // BeginProcessing() is invoked once per pipeline // @@ -196,7 +201,7 @@ protected override void BeginProcessing() throw new PlatformNotSupportedException(); } - // PowerShell Core requires at least Windows 7, + // PowerShell 7 requires at least Windows 7, // so no version test is needed _pdhHelper = new PdhHelper(false); #else @@ -217,6 +222,7 @@ protected override void EndProcessing() { return; } + ValidateFilePaths(); switch (ParameterSetName) @@ -234,14 +240,13 @@ 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; } _pdhHelper.Dispose(); } - // // Handle Control-C // @@ -251,7 +256,6 @@ protected override void StopProcessing() _pdhHelper.Dispose(); } - // // ProcessRecord() override. // This is the main entry point for the cmdlet. @@ -331,7 +335,6 @@ private void ProcessListSet() continue; } - StringCollection counterSetCounters = new StringCollection(); StringCollection counterSetInstances = new StringCollection(); @@ -360,7 +363,7 @@ private void ProcessListSet() { categoryType = PerformanceCounterCategoryType.MultiInstance; } - else //if (counterSetInstances.Count == 1) //??? + else // if (counterSetInstances.Count == 1) //??? { categoryType = PerformanceCounterCategoryType.SingleInstance; } @@ -371,6 +374,7 @@ private void ProcessListSet() WriteObject(setObj); bMatched = true; } + if (!bMatched) { string msg = _resourceMgr.GetString("NoMatchingCounterSetsInFile"); @@ -432,9 +436,11 @@ private void ProcessGetCounter() continue; } + validPaths.Add(expandedPath); } } + if (validPaths.Count == 0) { return; @@ -488,6 +494,7 @@ private void ProcessGetCounter() { break; } + if (res != 0 && res != PdhResults.PDH_INVALID_DATA) { ReportPdhError(res, false); @@ -508,7 +515,6 @@ private void ProcessGetCounter() } } - // // ValidateFilePaths() helper. // Validates the _resolvedPaths: present for all parametersets. @@ -525,9 +531,9 @@ private void ValidateFilePaths() WriteVerbose(fileName); string curExtension = System.IO.Path.GetExtension(fileName); - if (!curExtension.Equals(".blg", StringComparison.CurrentCultureIgnoreCase) - && !curExtension.Equals(".csv", StringComparison.CurrentCultureIgnoreCase) - && !curExtension.Equals(".tsv", StringComparison.CurrentCultureIgnoreCase)) + if (!curExtension.Equals(".blg", StringComparison.OrdinalIgnoreCase) + && !curExtension.Equals(".csv", StringComparison.OrdinalIgnoreCase) + && !curExtension.Equals(".tsv", StringComparison.OrdinalIgnoreCase)) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterNotALogFile"), fileName); Exception exc = new Exception(msg); @@ -535,7 +541,7 @@ private void ValidateFilePaths() return; } - if (!curExtension.Equals(firstExt, StringComparison.CurrentCultureIgnoreCase)) + if (!curExtension.Equals(firstExt, StringComparison.OrdinalIgnoreCase)) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterNoMixedLogTypes"), fileName); Exception exc = new Exception(msg); @@ -544,7 +550,7 @@ private void ValidateFilePaths() } } - if (firstExt.Equals(".blg", StringComparison.CurrentCultureIgnoreCase)) + if (firstExt.Equals(".blg", StringComparison.OrdinalIgnoreCase)) { if (_resolvedPaths.Count > 32) { @@ -563,7 +569,6 @@ private void ValidateFilePaths() } } - // // ResolveFilePath helper. // Returns a string collection of resolved file paths. @@ -583,27 +588,27 @@ private bool ResolveFilePaths() } catch (PSNotSupportedException notSupported) { - WriteError(new ErrorRecord(notSupported, "", ErrorCategory.ObjectNotFound, origPath)); + WriteError(new ErrorRecord(notSupported, string.Empty, ErrorCategory.ObjectNotFound, origPath)); continue; } catch (System.Management.Automation.DriveNotFoundException driveNotFound) { - WriteError(new ErrorRecord(driveNotFound, "", ErrorCategory.ObjectNotFound, origPath)); + WriteError(new ErrorRecord(driveNotFound, string.Empty, ErrorCategory.ObjectNotFound, origPath)); continue; } catch (ProviderNotFoundException providerNotFound) { - WriteError(new ErrorRecord(providerNotFound, "", ErrorCategory.ObjectNotFound, origPath)); + WriteError(new ErrorRecord(providerNotFound, string.Empty, ErrorCategory.ObjectNotFound, origPath)); continue; } catch (ItemNotFoundException pathNotFound) { - WriteError(new ErrorRecord(pathNotFound, "", ErrorCategory.ObjectNotFound, origPath)); + WriteError(new ErrorRecord(pathNotFound, string.Empty, ErrorCategory.ObjectNotFound, origPath)); continue; } catch (Exception exc) { - WriteError(new ErrorRecord(exc, "", ErrorCategory.ObjectNotFound, origPath)); + WriteError(new ErrorRecord(exc, string.Empty, ErrorCategory.ObjectNotFound, origPath)); continue; } @@ -635,6 +640,7 @@ private void ReportPdhError(uint res, bool bTerminate) { msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterApiError"), res); } + Exception exc = new Exception(msg); if (bTerminate) { @@ -673,5 +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 a0b008be9e5..9552f72c83a 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -1,13 +1,14 @@ - + - PowerShell Core's Microsoft.PowerShell.Commands.Diagnostics project - $(NoWarn);CS1591 + PowerShell's Microsoft.PowerShell.Commands.Diagnostics project + $(NoWarn);CS1591;CA1416 Microsoft.PowerShell.Commands.Diagnostics + @@ -15,28 +16,21 @@ + + + + + + + - - - - + + - - portable - - - - $(DefineConstants);UNIX - - - - full - - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs index ec064f0d8be..cdddba939f3 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs @@ -1,26 +1,24 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Globalization; -using System.Reflection; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Eventing; using System.Diagnostics.Eventing.Reader; +using System.Globalization; +using System.IO; +using System.Management.Automation; using System.Resources; -using System.Diagnostics.CodeAnalysis; -using System.Collections.Generic; using System.Xml; -using System.IO; 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=217469")] + /// + [Cmdlet(VerbsCommon.New, "WinEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096808")] public sealed class NewWinEventCommand : PSCmdlet { private ProviderMetadata _providerMetadata; @@ -28,28 +26,17 @@ 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 + /// ProviderName. /// [Parameter( 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) @@ -64,16 +51,17 @@ public int Id { return _id; } + set { _id = value; _idSpecified = true; } } + private int _id; private bool _idSpecified = false; - /// /// Version (event version) /// @@ -86,18 +74,19 @@ public byte Version { return _version; } + set { _version = value; _versionSpecified = true; } } + private byte _version; private bool _versionSpecified = false; - /// - /// Event Payload + /// Event Payload. /// [Parameter( Position = 2, @@ -107,21 +96,10 @@ 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 + /// BeginProcessing. /// protected override void BeginProcessing() { @@ -133,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 { @@ -153,6 +131,7 @@ private void LoadProvider() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("ProviderMetadataUnavailable"), providerName, exc.Message); throw new Exception(msg, exc); } + break; } } @@ -160,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); } } @@ -169,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) @@ -183,7 +162,7 @@ private void LoadEventDescriptor() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("IncorrectEventId"), _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -204,13 +183,14 @@ private void LoadEventDescriptor() break; } } + if (matchedEvent == null) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("IncorrectEventVersion"), _version, _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -220,7 +200,7 @@ private void LoadEventDescriptor() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("VersionNotSpecified"), _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -239,16 +219,14 @@ private bool VerifyTemplate(EventMetadata emd) { if (emd.Template != null) { - XmlReaderSettings readerSettings = new XmlReaderSettings + XmlReaderSettings readerSettings = new() { CheckCharacters = false, IgnoreComments = true, IgnoreProcessingInstructions = true, MaxCharactersInDocument = 0, // no limit ConformanceLevel = ConformanceLevel.Fragment, -#if !CORECLR - XmlResolver = null, -#endif + XmlResolver = null }; int definedParameterCount = 0; @@ -265,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); @@ -274,6 +252,7 @@ private bool VerifyTemplate(EventMetadata emd) return false; } } + return true; } @@ -304,46 +283,47 @@ private static EventDescriptor CreateEventDescriptor(ProviderMetadata providerMe } /// - /// ProcessRecord + /// ProcessRecord. /// 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); } } + base.ProcessRecord(); } /// - /// EndProcessing + /// EndProcessing. /// 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) @@ -353,4 +333,4 @@ internal EventWriteException(string msg) : base(msg) { } } -} \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs index ce1eea08e91..6f5d8f6e5ec 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs @@ -1,15 +1,13 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Runtime.InteropServices.ComTypes; + using Microsoft.PowerShell.Commands.GetCounter; using Microsoft.Win32; @@ -17,96 +15,94 @@ 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 { public const uint PERF_DETAIL_NOVICE = 100; // The uninformed can understand it @@ -194,16 +190,8 @@ internal struct CounterHandleNInstance public string InstanceName; } - internal class PdhHelper : IDisposable + internal sealed class PdhHelper : IDisposable { - private bool _isPreVista = false; - - public PdhHelper(bool isPreVista) - { - _isPreVista = isPreVista; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct PDH_COUNTER_PATH_ELEMENTS { @@ -232,12 +220,12 @@ private struct PDH_FMT_COUNTERVALUE_LARGE public Int64 largeValue; - //[FieldOffset (4), MarshalAs(UnmanagedType.LPStr)] - //public string AnsiStringValue; + // [FieldOffset (4), MarshalAs(UnmanagedType.LPStr)] + // public string AnsiStringValue; - //[FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)] - //public string WideStringValue; - }; + // [FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)] + // public string WideStringValue; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_FMT_COUNTERVALUE_DOUBLE @@ -245,7 +233,7 @@ private struct PDH_FMT_COUNTERVALUE_DOUBLE public uint CStatus; public double doubleValue; - }; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_FMT_COUNTERVALUE_UNICODE @@ -254,7 +242,7 @@ private struct PDH_FMT_COUNTERVALUE_UNICODE [MarshalAs(UnmanagedType.LPWStr)] public string WideStringValue; - }; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_RAW_COUNTER @@ -274,36 +262,71 @@ private struct PDH_TIME_INFO public UInt32 SampleCount; } - - /* // // This is the structure returned by PdhGetCounterInfo(). // 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)] - 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; - }*/ + [StructLayout(LayoutKind.Sequential)] + private unsafe struct PDH_COUNTER_INFO + { + 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)] private static extern uint PdhBindInputDataSource(out PdhSafeDataSourceHandle phDataSource, string szLogFileNameList); @@ -314,13 +337,7 @@ struct PDH_COUNTER_INFO { [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhAddCounter(PdhSafeQueryHandle queryHandle, string counterPath, IntPtr userData, out IntPtr counterHandle); - //Win7+ only - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] - private static extern uint PdhAddRelogCounter(PdhSafeQueryHandle queryHandle, string counterPath, - UInt32 counterType, UInt32 counterDefaultScale, - UInt64 timeBase, out IntPtr counterHandle); - - //not on XP + // not on XP [DllImport("pdh.dll")] private static extern uint PdhCollectQueryDataWithTime(PdhSafeQueryHandle queryHandle, ref Int64 pllTimeStamp); @@ -343,24 +360,6 @@ private static extern uint PdhOpenLog(string szLogFileName, out PdhSafeLogHandle phLog ); - //Win7+ only - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] - private static extern void PdhResetRelogCounterValues(PdhSafeLogHandle LogHandle); - - - //Win7+ only - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] - private static extern uint PdhSetCounterValue(IntPtr CounterHandle, - ref PDH_RAW_COUNTER Value, /*PPDH_RAW_COUNTER */ - string InstanceName - ); - - //Win7+ only - [DllImport("pdh.dll")] - private static extern uint PdhWriteRelogSample(PdhSafeLogHandle LogHandle, - Int64 Timestamp - ); - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhGetFormattedCounterValue(IntPtr counterHandle, uint dwFormat, out IntPtr lpdwType, out PDH_FMT_COUNTERVALUE_DOUBLE pValue); @@ -392,11 +391,10 @@ private static extern uint PdhMakeCounterPath(ref PDH_COUNTER_PATH_ELEMENTS pCou [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhParseCounterPath(string szFullPathBuffer, - IntPtr pCounterPathElements, //PDH_COUNTER_PATH_ELEMENTS + IntPtr pCounterPathElements, // PDH_COUNTER_PATH_ELEMENTS ref IntPtr pdwBufferSize, uint dwFlags); - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhExpandWildCardPathH(PdhSafeDataSourceHandle hDataSource, string szWildCardPath, @@ -404,19 +402,15 @@ private static extern uint PdhExpandWildCardPathH(PdhSafeDataSourceHandle hDataS ref IntPtr pcchPathListLength, uint dwFlags); - //not available on XP + // not available on XP [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhValidatePathEx(PdhSafeDataSourceHandle hDataSource, string szFullPathBuffer); [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhValidatePath(string szFullPathBuffer); - //not available on XP - [DllImport("pdh.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = true)] //private export - private static extern IntPtr PdhGetExplainText(string szMachineName, string szObjectName, string szCounterName); - [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); @@ -427,11 +421,9 @@ private static extern uint PdhExpandWildCardPathH(PdhSafeDataSourceHandle hDataS [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhSetQueryTimeRange(PdhSafeQueryHandle hQuery, ref PDH_TIME_INFO pInfo); - [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhLookupPerfNameByIndex(string szMachineName, UInt32 dwNameIndex, IntPtr szNameBuffer, ref int pcchNameBufferSize); - private PdhSafeDataSourceHandle _hDataSource = null; private PdhSafeQueryHandle _hQuery = null; @@ -466,13 +458,7 @@ public void Dispose() // // m_ConsumerPathToHandleAndInstanceMap map is used for reading counter date (live or from files). // - private Dictionary _consumerPathToHandleAndInstanceMap = new Dictionary(); - - - // - // m_ReloggerPathToHandleAndInstanceMap map is used for writing relog counters. - // - private Dictionary _reloggerPathToHandleAndInstanceMap = new Dictionary(); + private readonly Dictionary _consumerPathToHandleAndInstanceMap = new(); /// /// A helper reading in a Unicode string with embedded NULLs and splitting it into a StringCollection. @@ -480,12 +466,11 @@ 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; - string allSubstringsWithNulls = ""; + string allSubstringsWithNulls = string.Empty; while (offset <= ((strSize * sizeof(char)) - 4)) { Int32 next4 = Marshal.ReadInt32(strNative, offset); @@ -504,19 +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; @@ -528,12 +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 = (uint)Marshal.ReadInt32(bufCounterInfo, 4); - defaultScale = (uint)Marshal.ReadInt32(bufCounterInfo, 20); + PDH_COUNTER_INFO pdhCounterInfo = (PDH_COUNTER_INFO)Marshal.PtrToStructure(bufCounterInfo, typeof(PDH_COUNTER_INFO)); + counterType = pdhCounterInfo.Type; + defaultScale = (uint)pdhCounterInfo.DefaultScale; } } finally @@ -542,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; } @@ -558,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); + // 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. @@ -579,14 +560,14 @@ 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); + // Console.WriteLine("error in PdhBindInputDataSource: " + res); } + return res; } - public uint ConnectToDataSource(StringCollection blgFileNames) { if (blgFileNames.Count == 1) @@ -594,7 +575,7 @@ public uint ConnectToDataSource(StringCollection blgFileNames) return ConnectToDataSource(blgFileNames[0]); } - string doubleNullTerminated = ""; + string doubleNullTerminated = string.Empty; foreach (string fileName in blgFileNames) { doubleNullTerminated += fileName + '\0'; @@ -609,10 +590,11 @@ 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); + // Console.WriteLine("error in PdhOpenQueryH: " + res); } + return res; } @@ -635,24 +617,25 @@ public uint OpenLogForWriting(string logName, PdhLogFileType logFileType, bool b return res; } - 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) { startTime = new DateTime(startTime.Ticks, DateTimeKind.Utc); } + pTimeInfo.StartTime = (startTime == DateTime.MinValue) ? 0 : startTime.ToFileTimeUtc(); if (endTime != DateTime.MaxValue && endTime.Kind == DateTimeKind.Local) { endTime = new DateTime(endTime.Ticks, DateTimeKind.Utc); } + pTimeInfo.EndTime = (endTime == DateTime.MaxValue) ? Int64.MaxValue : endTime.ToFileTimeUtc(); pTimeInfo.SampleCount = 0; @@ -662,20 +645,20 @@ 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) { return res; } - Int32 cChars = MachineListTcharSizePtr.ToInt32(); //should be ok on 64 bit + Int32 cChars = MachineListTcharSizePtr.ToInt32(); // should be ok on 64 bit IntPtr strMachineList = Marshal.AllocHGlobal(cChars * sizeof(char)); 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); } @@ -690,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) { @@ -703,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); } @@ -718,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, @@ -728,23 +711,23 @@ 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) { - //Console.WriteLine("error in PdhEnumObjectItemsH 1st call: " + res); + // Console.WriteLine("error in PdhEnumObjectItemsH 1st call: " + res); return res; } Int32 cChars = pCounterBufferSize.ToInt32(); IntPtr strCountersList = (cChars > 0) ? Marshal.AllocHGlobal((cChars) * sizeof(char)) : IntPtr.Zero; - //re-set count to 0 if it is lte 2 + // re-set count to 0 if it is lte 2 if (cChars < 0) { pCounterBufferSize = new IntPtr(0); @@ -754,7 +737,7 @@ public uint EnumObjectItems(string machineName, string objectName, ref StringCol IntPtr strInstancesList = (cChars > 0) ? Marshal.AllocHGlobal((cChars) * sizeof(char)) : IntPtr.Zero; - //re-set count to 0 if it is lte 2 + // re-set count to 0 if it is lte 2 if (cChars < 0) { pInstanceBufferSize = new IntPtr(0); @@ -766,9 +749,9 @@ 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 " + // Console.WriteLine("error in PdhEnumObjectItemsH 2nd call: " + res + "\n Counter buffer size is " // + pCounterBufferSize.ToInt32() + "\n Instance buffer size is " + pInstanceBufferSize.ToInt32()); } else @@ -786,6 +769,7 @@ public uint EnumObjectItems(string machineName, string objectName, ref StringCol { Marshal.FreeHGlobal(strCountersList); } + if (strInstancesList != IntPtr.Zero) { Marshal.FreeHGlobal(strInstancesList); @@ -799,51 +783,51 @@ 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; } foreach (string counterSet in counterSets) { - //Console.WriteLine("Counter set " + counterSet); + // 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; } } } + return res; } private bool IsPathValid(ref PDH_COUNTER_PATH_ELEMENTS pathElts, out string outPath) { bool ret = false; - outPath = ""; - IntPtr pPathBufferSize = new IntPtr(0); + outPath = string.Empty; + IntPtr pPathBufferSize = new(0); uint res = PdhMakeCounterPath(ref pathElts, IntPtr.Zero, ref pPathBufferSize, 0); if (res != PdhResults.PDH_MORE_DATA) @@ -857,18 +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); - if (!_isPreVista) - { - ret = (PdhValidatePathEx(_hDataSource, outPath) == 0); - } - else - { - ret = (PdhValidatePath(outPath) == 0); - } + ret = (PdhValidatePathEx(_hDataSource, outPath) == PdhResults.PDH_CSTATUS_VALID_DATA); } } finally @@ -881,24 +858,13 @@ private bool IsPathValid(ref PDH_COUNTER_PATH_ELEMENTS pathElts, out string outP public bool IsPathValid(string path) { - if (!_isPreVista) - { - return (PdhValidatePathEx(_hDataSource, path) == 0); - } - else - { - // - // Note: this assumes the paths already contain machine names - // - return (PdhValidatePath(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 = ""; - IntPtr pPathBufferSize = new IntPtr(0); + outPath = string.Empty; + IntPtr pPathBufferSize = new(0); if (bWildcardInstances) { @@ -919,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); } @@ -932,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; } @@ -947,18 +913,17 @@ 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); + // Console.WriteLine("error in PdhParseCounterPath: " + res); return res; } @@ -970,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, @@ -1001,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; - localizedPath = ""; - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); + uint res = PdhResults.PDH_CSTATUS_VALID_DATA; + localizedPath = string.Empty; + PDH_COUNTER_PATH_ELEMENTS pathElts = new(); res = ParsePath(englishPath, ref pathElts); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -1022,11 +987,10 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa RegistryKey rootKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009"); string[] regCounters = (string[])rootKey.GetValue("Counter"); - // NOTE: 1-based enumeration because the name strings follow index strings in the array Int32 counterIndex = -1; Int32 objIndex = -1; - for (uint enumIndex = 1; enumIndex < regCounters.Length; enumIndex++) + for (int enumIndex = 1; enumIndex < regCounters.Length; enumIndex++) { string regString = regCounters[enumIndex]; if (regString.ToLowerInvariant() == lowerEngCtrName) @@ -1037,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) @@ -1048,9 +1012,10 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa } catch (Exception) { - return (uint)PdhResults.PDH_INVALID_PATH; + return PdhResults.PDH_INVALID_PATH; } } + if (counterIndex != -1 && objIndex != -1) { break; @@ -1059,37 +1024,35 @@ 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; } - pathElts.ObjectName = objNameLocalized; + pathElts.ObjectName = objNameLocalized; string ctrNameLocalized; res = LookupPerfNameByIndex(pathElts.MachineName, (uint)counterIndex, out ctrNameLocalized); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } + pathElts.CounterName = ctrNameLocalized; // Assemble the path back by using the translated object and counter names: res = MakePath(pathElts, out localizedPath, false); - return res; } - public uint LookupPerfNameByIndex(string machineName, uint index, out string locName) { // @@ -1100,8 +1063,8 @@ public uint LookupPerfNameByIndex(string machineName, uint index, out string loc int strSize = 256; IntPtr localizedPathPtr = Marshal.AllocHGlobal(strSize * sizeof(char)); - locName = ""; - uint res = 0; + locName = string.Empty; + uint res; try { res = PdhLookupPerfNameByIndex(machineName, index, localizedPathPtr, ref strSize); @@ -1111,7 +1074,8 @@ public uint LookupPerfNameByIndex(string machineName, uint index, out string loc localizedPathPtr = Marshal.AllocHGlobal(strSize * sizeof(char)); res = PdhLookupPerfNameByIndex(machineName, index, localizedPathPtr, ref strSize); } - if (res == 0) + + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { locName = Marshal.PtrToStringUni(localizedPathPtr); } @@ -1124,17 +1088,13 @@ public uint LookupPerfNameByIndex(string machineName, uint index, out string loc return res; } - - public uint GetValidPaths(string machineName, string objectName, ref StringCollection counters, 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; @@ -1165,9 +1125,9 @@ public uint GetValidPaths(string machineName, } } } - return res; - } + return PdhResults.PDH_CSTATUS_VALID_DATA; + } public uint AddCounters(ref StringCollection validPaths, bool bFlushOldCounters) { @@ -1179,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(); } @@ -1207,344 +1167,20 @@ public uint AddCounters(ref StringCollection validPaths, bool bFlushOldCounters) } } - return bAtLeastOneAdded ? 0 : res; - } - - - // - // AddRelogCounters combines instances and adds counters to m_hQuery. - // The counter handles and full paths - // - public uint AddRelogCounters(PerformanceCounterSampleSet sampleSet) - { - Debug.Assert(_hQuery != null && !_hQuery.IsInvalid); - - uint res = 0; - - Dictionary> prefixInstanceMap = new Dictionary>(); - - // - // Go through all the samples one, constructing prefixInstanceMap and adding new counters as needed - // - foreach (PerformanceCounterSample sample in sampleSet.CounterSamples) - { - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); - res = ParsePath(sample.Path, ref pathElts); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - continue; - } - - string lowerCaseMachine = pathElts.MachineName.ToLowerInvariant(); - string lowerCaseObject = pathElts.ObjectName.ToLowerInvariant(); - string lowerCaseCounter = pathElts.CounterName.ToLowerInvariant(); - - string lcPathMinusInstance = @"\\" + lowerCaseMachine + @"\" + lowerCaseObject + @"\" + lowerCaseCounter; - - List sampleList; - if (prefixInstanceMap.TryGetValue(lcPathMinusInstance, out sampleList)) - { - prefixInstanceMap[lcPathMinusInstance].Add(sample); - } - else - { - List newList = new List(); - newList.Add(sample); - prefixInstanceMap.Add(lcPathMinusInstance, newList); - } - - //Console.WriteLine ("Added path " + sample.Path + " to the 1ist map with prefix " + lcPathMinusInstance); - } - - // - // Add counters to the query, consolidating multi-instance with a wildcard path, - // and construct m_ReloggerPathToHandleAndInstanceMap where each full path would be pointing to its counter handle - // and an instance name (might be empty for no-instance counter types). - // You can have multiple full paths inside m_ReloggerPathToHandleAndInstanceMap pointing to the same handle. - // - - foreach (string prefix in prefixInstanceMap.Keys) - { - IntPtr counterHandle; - string unifiedPath = prefixInstanceMap[prefix][0].Path; - - if (prefixInstanceMap[prefix].Count > 1) - { - res = MakeAllInstancePath(prefixInstanceMap[prefix][0].Path, out unifiedPath); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - continue; - } - } - - res = PdhAddRelogCounter(_hQuery, - unifiedPath, - (UInt32)prefixInstanceMap[prefix][0].CounterType, - prefixInstanceMap[prefix][0].DefaultScale, - prefixInstanceMap[prefix][0].TimeBase, - out counterHandle); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - // Console.WriteLine ("PdhAddCounter returned " + res + " for counter path " + unifiedPath); - continue; - } - - //now, add all actual paths to m_ReloggerPathToHandleAndInstanceMap - foreach (PerformanceCounterSample sample in prefixInstanceMap[prefix]) - { - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); - res = ParsePath(sample.Path, ref pathElts); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - continue; - } - - CounterHandleNInstance chi = new CounterHandleNInstance(); - - chi.hCounter = counterHandle; - - if (pathElts.InstanceName != null) - { - chi.InstanceName = pathElts.InstanceName.ToLowerInvariant(); - } - - if (!_reloggerPathToHandleAndInstanceMap.ContainsKey(sample.Path.ToLowerInvariant())) - { - _reloggerPathToHandleAndInstanceMap.Add(sample.Path.ToLowerInvariant(), chi); - //Console.WriteLine ("added map path:" + sample.Path ); - } - } - } - - //TODO: verify that all counters are in the map - - return (_reloggerPathToHandleAndInstanceMap.Keys.Count > 0) ? 0 : res; - } - - - - // - // AddRelogCountersPreservingPaths preserves all paths and adds as relog counters to m_hQuery. - // The counter handles and full paths are added to m_ReloggerPathToHandleAndInstanceMap - // - public uint AddRelogCountersPreservingPaths(PerformanceCounterSampleSet sampleSet) - { - Debug.Assert(_hQuery != null && !_hQuery.IsInvalid); - - uint res = 0; - - // - // Go through all the samples one, constructing prefixInstanceMap and adding new counters as needed - // - foreach (PerformanceCounterSample sample in sampleSet.CounterSamples) - { - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); - res = ParsePath(sample.Path, ref pathElts); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - continue; - } - - IntPtr counterHandle; - res = PdhAddRelogCounter(_hQuery, - sample.Path, - (uint)sample.CounterType, - sample.DefaultScale, - sample.TimeBase, - out counterHandle); - if (res != 0) - { - // Skipping for now, but should be a non-terminating error - continue; - } - - CounterHandleNInstance chi = new CounterHandleNInstance(); - - chi.hCounter = counterHandle; - if (pathElts.InstanceName != null) - { - chi.InstanceName = pathElts.InstanceName.ToLowerInvariant(); - } - - if (!_reloggerPathToHandleAndInstanceMap.ContainsKey(sample.Path.ToLowerInvariant())) - { - _reloggerPathToHandleAndInstanceMap.Add(sample.Path.ToLowerInvariant(), chi); - } - } - - return (_reloggerPathToHandleAndInstanceMap.Keys.Count > 0) ? 0 : res; + return bAtLeastOneAdded ? PdhResults.PDH_CSTATUS_VALID_DATA : res; } public string GetCounterSetHelp(string szMachineName, string szObjectName) { - if (_isPreVista) - { - return string.Empty; - } - IntPtr retString = PdhGetExplainText(szMachineName, szObjectName, null); - return Marshal.PtrToStringUni(retString); + // API not available to retrieve + return string.Empty; } - public uint ReadNextSetPreVista(out PerformanceCounterSampleSet nextSet, bool bSkipReading) - { - uint res = 0; - nextSet = null; - - res = PdhCollectQueryData(_hQuery); - if (bSkipReading) - { - return res; - } - if (res != 0 && res != PdhResults.PDH_NO_DATA) - { - return res; - } - - PerformanceCounterSample[] samplesArr = new PerformanceCounterSample[_consumerPathToHandleAndInstanceMap.Count]; - uint sampleIndex = 0; - uint numInvalidDataSamples = 0; - uint lastErr = 0; - - DateTime sampleTimeStamp = DateTime.Now; - - foreach (string path in _consumerPathToHandleAndInstanceMap.Keys) - { - IntPtr counterTypePtr = new IntPtr(0); - UInt32 counterType = (UInt32)PerformanceCounterType.RawBase; - UInt32 defaultScale = 0; - UInt64 timeBase = 0; - - IntPtr hCounter = _consumerPathToHandleAndInstanceMap[path].hCounter; - Debug.Assert(hCounter != null); - - res = GetCounterInfoPlus(hCounter, out counterType, out defaultScale, out timeBase); - if (res != 0) - { - //Console.WriteLine ("GetCounterInfoPlus for " + path + " failed with " + res); - } - - PDH_RAW_COUNTER rawValue; - res = PdhGetRawCounterValue(hCounter, out counterTypePtr, out rawValue); - if (res == PdhResults.PDH_INVALID_DATA || res == PdhResults.PDH_NO_DATA) - { - //Console.WriteLine ("PdhGetRawCounterValue returned " + res); - samplesArr[sampleIndex++] = new PerformanceCounterSample(path, - _consumerPathToHandleAndInstanceMap[path].InstanceName, - 0, - (ulong)0, - (ulong)0, - 0, - PerformanceCounterType.RawBase, - defaultScale, - timeBase, - DateTime.Now, - (UInt64)DateTime.Now.ToFileTime(), - rawValue.CStatus); - - numInvalidDataSamples++; - lastErr = res; - continue; - } - else if (res != 0) - { - return res; - } - - long dtFT = (((long)rawValue.TimeStamp.dwHighDateTime) << 32) + - (uint)rawValue.TimeStamp.dwLowDateTime; - - // - // NOTE: PDH returns the filetime as local time, therefore - // we need to call FromFileTimUtc() to avoid .NET applying the timezone adjustment. - // However, that would result in the DateTime object having Kind.Utc. - // We have to copy it once more to correct that (Kind is a read-only property). - // - sampleTimeStamp = new DateTime(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 == PdhResults.PDH_INVALID_DATA || res == PdhResults.PDH_NO_DATA) - { - //Console.WriteLine ("PdhGetFormattedCounterValue returned " + res); - samplesArr[sampleIndex++] = new PerformanceCounterSample(path, - _consumerPathToHandleAndInstanceMap[path].InstanceName, - 0, - (ulong)rawValue.FirstValue, - (ulong)rawValue.SecondValue, - rawValue.MultiCount, - (PerformanceCounterType)counterType, - defaultScale, - timeBase, - sampleTimeStamp, - (UInt64)dtFT, - fmtValueDouble.CStatus); - - numInvalidDataSamples++; - lastErr = res; - continue; - } - else if (res != 0) - { - //Console.WriteLine ("PdhGetFormattedCounterValue returned " + res); - return res; - } - - samplesArr[sampleIndex++] = new PerformanceCounterSample(path, - _consumerPathToHandleAndInstanceMap[path].InstanceName, - fmtValueDouble.doubleValue, - (ulong)rawValue.FirstValue, - (ulong)rawValue.SecondValue, - rawValue.MultiCount, - (PerformanceCounterType)counterTypePtr.ToInt32(), - defaultScale, - timeBase, - sampleTimeStamp, - (UInt64)dtFT, - fmtValueDouble.CStatus); - } - - // - // Prior to Vista, PdhCollectQueryDataWithTime() was not available, - // so we could not collect a timestamp for the entire sample set. - // We will use the last sample's timestamp instead. - // - nextSet = new PerformanceCounterSampleSet(sampleTimeStamp, samplesArr, _firstReading); - _firstReading = false; - - if (numInvalidDataSamples == samplesArr.Length) - { - res = lastErr; - } - else - { - // - // Reset the error - any errors are saved per sample in PerformanceCounterSample.Status for kvetching later - // - res = 0; - } - - return res; - } - - public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReading) { Debug.Assert(_hQuery != null && !_hQuery.IsInvalid); - if (_isPreVista) - { - return ReadNextSetPreVista(out nextSet, bSkipReading); - } - - uint res = 0; + uint res = PdhResults.PDH_CSTATUS_VALID_DATA; nextSet = null; Int64 batchTimeStampFT = 0; @@ -1554,7 +1190,8 @@ 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; } @@ -1574,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); + // 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, @@ -1607,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; @@ -1617,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, @@ -1637,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; @@ -1658,7 +1295,6 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi fmtValueDouble.CStatus); } - nextSet = new PerformanceCounterSampleSet(batchStamp, samplesArr, _firstReading); _firstReading = false; @@ -1671,40 +1307,16 @@ 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; - } - - return res; - } - - - public uint GetFilesSummary(out CounterFileInfo summary) - { - IntPtr pNumEntries = new IntPtr(0); - PDH_TIME_INFO pInfo = new PDH_TIME_INFO(); - IntPtr bufSize = new IntPtr(System.Runtime.InteropServices.Marshal.SizeOf(pInfo)); - - uint res = PdhGetDataSourceTimeRangeH(_hDataSource, - ref pNumEntries, - ref pInfo, - ref bufSize); - if (res != 0) - { - summary = new CounterFileInfo(); - return res; + res = PdhResults.PDH_CSTATUS_VALID_DATA; } - summary = new CounterFileInfo(new DateTime(DateTime.FromFileTimeUtc(pInfo.StartTime).Ticks, DateTimeKind.Local), - new DateTime(DateTime.FromFileTimeUtc(pInfo.EndTime).Ticks, DateTimeKind.Local), - pInfo.SampleCount); - return res; } 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, @@ -1723,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); } @@ -1735,45 +1347,5 @@ public uint ExpandWildCardPath(string path, out StringCollection expandedPaths) return res; } - - public void ResetRelogValues() - { - Debug.Assert(_hOutputLog != null && !_hOutputLog.IsInvalid); - PdhResetRelogCounterValues(_hOutputLog); - } - - public uint WriteRelogSample(DateTime timeStamp) - { - Debug.Assert(_hOutputLog != null && !_hOutputLog.IsInvalid); - return PdhWriteRelogSample(_hOutputLog, (new DateTime(timeStamp.Ticks, DateTimeKind.Utc)).ToFileTimeUtc()); - } - - public uint SetCounterValue(PerformanceCounterSample sample, out bool bUnknownPath) - { - Debug.Assert(_hOutputLog != null && !_hOutputLog.IsInvalid); - - bUnknownPath = false; - - string lcPath = sample.Path.ToLowerInvariant(); - - if (!_reloggerPathToHandleAndInstanceMap.ContainsKey(lcPath)) - { - bUnknownPath = true; - return 0; - } - - PDH_RAW_COUNTER rawStruct = new PDH_RAW_COUNTER(); - rawStruct.FirstValue = (long)sample.RawValue; - rawStruct.SecondValue = (long)sample.SecondValue; - rawStruct.MultiCount = sample.MultipleCount; - rawStruct.TimeStamp.dwHighDateTime = (int)((new DateTime(sample.Timestamp.Ticks, DateTimeKind.Utc).ToFileTimeUtc() >> 32) & 0xFFFFFFFFL); - rawStruct.TimeStamp.dwLowDateTime = (int)(new DateTime(sample.Timestamp.Ticks, DateTimeKind.Utc).ToFileTimeUtc() & 0xFFFFFFFFL); - rawStruct.CStatus = sample.Status; - - - return PdhSetCounterValue(_reloggerPathToHandleAndInstanceMap[lcPath].hCounter, - ref rawStruct, /*PPDH_RAW_COUNTER */ - _reloggerPathToHandleAndInstanceMap[lcPath].InstanceName); - } } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs index b2adb011268..2fc58cc00e1 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs @@ -1,12 +1,9 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Runtime.InteropServices; -using System.Runtime.ConstrainedExecution; - namespace Microsoft.Powershell.Commands.GetCounter.PdhNative { internal sealed class PdhSafeDataSourceHandle : SafeHandle @@ -21,17 +18,12 @@ public override bool IsInvalid } } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return (PdhHelper.PdhCloseLog(handle, 0) == 0); } } - - - internal sealed class PdhSafeQueryHandle : SafeHandle { private PdhSafeQueryHandle() : base(IntPtr.Zero, true) { } @@ -44,8 +36,6 @@ public override bool IsInvalid } } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return (PdhHelper.PdhCloseQuery(handle) == 0); @@ -64,8 +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 20b9fe45eb7..5c66f2b9e94 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx @@ -253,7 +253,13 @@ The defined template is following: The following value is not in a valid DateTime format: {0}. - Could not retrieve information about the {0} log. Error: {1}. + 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. @@ -282,5 +288,28 @@ The defined template is following: You cannot import more than one comma-separated (.csv) or tab-separated (.tsv) performance counter file in each command. - + + 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.Diagnostics/resources/GetEventResources.txt b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.txt index 6e004d457d8..3e0744889e7 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.txt +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.txt @@ -1,7 +1,7 @@ -#Copyright (c) Microsoft Corporation. All rights reserved. +#Copyright (c) Microsoft Corporation. Vendor=Microsoft -Description=This Windows PowerShell snap-in contains Windows Eventing and Performance Counter cmdlets. +Description=This PowerShell snap-in contains Windows Eventing and Performance Counter cmdlets. NoMatchingEventsFound=No events were found that match the specified selection criteria. NoMatchingLogsFound=There is not an event log on the {0} computer that matches "{1}". @@ -56,4 +56,4 @@ CounterCircularNoMaxSize=The Circular parameter will be ignored unless the MaxSi ExportCtrWin7Required=This cmdlet can be run only on Microsoft Windows 7 and above. FileOpenFailed=Unable to open the {0} file for writing. FileCreateFailed=Unable to create the {0} file. Verify that the path is valid. -ExportDestPathAmbiguous=The following export destination path is ambiguous: {0}. \ No newline at end of file +ExportDestPathAmbiguous=The following export destination path is ambiguous: {0}. 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 a95f1deb2ee..8ce2a97d67b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -1,8 +1,8 @@ - + - PowerShell Core's Microsoft.PowerShell.Commands.Management project - $(NoWarn);CS1570 + PowerShell's Microsoft.PowerShell.Commands.Management project + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.Commands.Management @@ -15,19 +15,15 @@ - - - - @@ -37,39 +33,21 @@ - - - - - - - - portable - - - - $(DefineConstants);UNIX - - - - full - - - + diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs index 793bb0e6ba8..3e26b2e2e98 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -38,7 +37,7 @@ internal SessionBasedCmdletAdapter() private bool _disposed; /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// public void Dispose() { @@ -47,7 +46,7 @@ public void Dispose() } /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// protected virtual void Dispose(bool disposing) { @@ -61,6 +60,7 @@ protected virtual void Dispose(bool disposing) _parentJob = null; } } + _disposed = true; } } @@ -70,35 +70,38 @@ protected virtual void Dispose(bool disposing) #region Common parameters (AsJob, ThrottleLimit, Session) /// - /// Session to operate on + /// Session to operate on. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] protected TSession[] Session { - get { return _session ?? (_session = new TSession[] {this.DefaultSession}); } - set + get { - if (value == null) - { - throw new ArgumentNullException("value"); - } + return _session ??= new TSession[] { this.DefaultSession }; + } + set + { + ArgumentNullException.ThrowIfNull(value); _session = value; _sessionWasSpecified = true; } } + private TSession[] _session; private bool _sessionWasSpecified; /// - /// Whether to wrap and emit the whole operation as a background job + /// Whether to wrap and emit the whole operation as a background job. /// [Parameter] public SwitchParameter AsJob { get { return _asJob; } + set { _asJob = value; } } + private bool _asJob; /// @@ -114,17 +117,17 @@ public SwitchParameter AsJob /// /// Creates a object that performs a query against the wrapped object model. /// - /// Remote session to query - /// Query parameters + /// Remote session to query. + /// Query parameters. /// /// /// 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 CreateQueryJob(TSession session, QueryBuilder query); @@ -147,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; @@ -163,19 +167,19 @@ private StartableJob DoCreateQueryJob(TSession sessionForJob, QueryBuilder query /// /// Creates a object that invokes an instance method in the wrapped object model. /// - /// 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 + /// Remote session to invoke the method in. + /// The object on which to invoke the method. + /// Method invocation details. + /// 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); @@ -200,17 +204,17 @@ private StartableJob DoCreateInstanceMethodInvocationJob(TSession sessionForJob, /// /// Creates a object that invokes a static method in the wrapped object model. /// - /// Remote session to invoke the method in - /// Method invocation details + /// Remote session to invoke the method in. + /// Method invocation details. /// /// /// 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 CreateStaticMethodInvocationJob(TSession session, MethodInvocationInfo methodInvocationInfo); @@ -232,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; @@ -284,7 +285,7 @@ internal virtual TSession GetSessionOfOriginFromInstance(TObjectInstance instanc /// /// Returns default sessions to use when the user doesn't specify the -Session cmdlet parameter. /// - /// Default sessions to use when the user doesn't specify the -Session cmdlet parameter + /// Default sessions to use when the user doesn't specify the -Session cmdlet parameter. protected abstract TSession DefaultSession { get; } /// @@ -299,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(); @@ -320,39 +321,40 @@ private enum JobOutputs NonPipelineResults = Output | Error | Warning | Verbose | Debug | Progress, PipelineResults = Results, } + 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); } @@ -367,8 +369,8 @@ private static void DiscardJobOutputs(Job job, JobOutputs jobOutputsToDiscard) /// /// Queries for object instances in the object model. /// - /// Query parameters - /// A lazy evaluated collection of object instances + /// Query parameters. + /// A lazy evaluated collection of object instances. public override void ProcessRecord(QueryBuilder query) { _parentJob.DisableFlowControlForPendingCmdletActionsQueue(); @@ -390,11 +392,11 @@ public override void ProcessRecord(QueryBuilder query) } /// - /// Queries for instance and invokes an instance method + /// Queries for instance and invokes an instance method. /// - /// Query parameters - /// Method invocation details - /// true if successful method invocations should emit downstream the object instance being operated on + /// Query parameters. + /// Method invocation details. + /// 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(); @@ -407,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, @@ -489,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) @@ -524,6 +525,7 @@ private IEnumerable GetSessionsToActAgainst(MethodInvocationInfo metho associatedSessions.Add(associatedSession); } } + if (associatedSessions.Count == 1) { return associatedSessions; @@ -570,13 +572,14 @@ private TSession GetImpliedSession() /// /// Invokes an instance method in the object model. /// - /// The object on which to invoke the method - /// Method invocation details - /// true if successful method invocations should emit downstream the being operated on + /// The object on which to invoke the method. + /// Method invocation details. + /// 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("objectInstance"); - if (methodInvocationInfo == null) throw new ArgumentNullException("methodInvocationInfo"); + ArgumentNullException.ThrowIfNull(objectInstance); + + ArgumentNullException.ThrowIfNull(methodInvocationInfo); foreach (TSession sessionForJob in this.GetSessionsToActAgainst(objectInstance)) { @@ -598,10 +601,10 @@ public override void ProcessRecord(TObjectInstance objectInstance, MethodInvocat /// /// Invokes a static method in the object model. /// - /// Method invocation details + /// Method invocation details. public override void ProcessRecord(MethodInvocationInfo methodInvocationInfo) { - if (methodInvocationInfo == null) throw new ArgumentNullException("methodInvocationInfo"); + ArgumentNullException.ThrowIfNull(methodInvocationInfo); foreach (TSession sessionForJob in this.GetSessionsToActAgainst(methodInvocationInfo)) { @@ -637,6 +640,7 @@ public override void BeginProcessing() { conflictingParameter = "Confirm"; } + if (conflictingParameter != null) { string errorMessage = string.Format( @@ -680,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 0854414a440..935cc960c6c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs @@ -1,20 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; using System.Management.Automation; using System.Runtime.Serialization; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Represents an error during execution of a CIM job + /// Represents an error during execution of a CIM job. /// - [Serializable] public class CimJobException : SystemException, IContainsErrorRecord { #region Standard constructors and methods required for all exceptions @@ -49,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) - { - if (info == null) - { - throw new ArgumentNullException("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) + StreamingContext context) { - if (info == null) - { - throw new ArgumentNullException("info"); - } - - base.GetObjectData(info, context); - info.AddValue("errorRecord", _errorRecord); + throw new NotSupportedException(); } #endregion @@ -89,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; } @@ -103,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, @@ -126,6 +104,7 @@ internal static CimJobException CreateFromAnyException( errorId: "CimJob_" + inner.GetType().Name, errorCategory: ErrorCategory.NotSpecified); } + return cimJobException; } @@ -140,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; } @@ -154,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; } @@ -168,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; @@ -195,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); @@ -240,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; } } @@ -268,57 +247,57 @@ private static ErrorCategory ConvertCimNativeErrorCodeToErrorCategory(NativeErro case NativeErrorCode.Ok: return ErrorCategory.NotSpecified; case NativeErrorCode.Failed: - return ErrorCategory.NotSpecified; ; + return ErrorCategory.NotSpecified; case NativeErrorCode.AccessDenied: - return ErrorCategory.PermissionDenied; ; + return ErrorCategory.PermissionDenied; case NativeErrorCode.InvalidNamespace: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.InvalidParameter: - return ErrorCategory.InvalidArgument; ; + return ErrorCategory.InvalidArgument; case NativeErrorCode.InvalidClass: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.NotFound: - return ErrorCategory.ObjectNotFound; ; + return ErrorCategory.ObjectNotFound; case NativeErrorCode.NotSupported: - return ErrorCategory.NotImplemented; ; + return ErrorCategory.NotImplemented; case NativeErrorCode.ClassHasChildren: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.ClassHasInstances: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.InvalidSuperClass: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.AlreadyExists: - return ErrorCategory.ResourceExists; ; + return ErrorCategory.ResourceExists; case NativeErrorCode.NoSuchProperty: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.TypeMismatch: - return ErrorCategory.InvalidType; ; + return ErrorCategory.InvalidType; case NativeErrorCode.QueryLanguageNotSupported: - return ErrorCategory.NotImplemented; ; + return ErrorCategory.NotImplemented; case NativeErrorCode.InvalidQuery: - return ErrorCategory.InvalidArgument; ; + return ErrorCategory.InvalidArgument; case NativeErrorCode.MethodNotAvailable: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.MethodNotFound: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.NamespaceNotEmpty: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.InvalidEnumerationContext: - return ErrorCategory.MetadataError; ; + return ErrorCategory.MetadataError; case NativeErrorCode.InvalidOperationTimeout: - return ErrorCategory.InvalidArgument; ; + return ErrorCategory.InvalidArgument; case NativeErrorCode.PullHasBeenAbandoned: - return ErrorCategory.OperationStopped; ; + return ErrorCategory.OperationStopped; case NativeErrorCode.PullCannotBeAbandoned: - return ErrorCategory.CloseError; ; + return ErrorCategory.CloseError; case NativeErrorCode.FilteredEnumerationNotSupported: - return ErrorCategory.NotImplemented; ; + return ErrorCategory.NotImplemented; case NativeErrorCode.ContinuationOnErrorNotSupported: - return ErrorCategory.NotImplemented; ; + return ErrorCategory.NotImplemented; case NativeErrorCode.ServerLimitsExceeded: - return ErrorCategory.ResourceBusy; ; + return ErrorCategory.ResourceBusy; case NativeErrorCode.ServerIsShuttingDown: - return ErrorCategory.ResourceUnavailable; ; + return ErrorCategory.ResourceUnavailable; default: return ErrorCategory.NotSpecified; } @@ -353,14 +332,14 @@ public ErrorRecord ErrorRecord { get { return _errorRecord; } } + private ErrorRecord _errorRecord; 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; } @@ -371,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 93bb4f7932b..eb594dd4eea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs @@ -1,17 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of a CreateInstance intrinsic CIM method + /// Job wrapping invocation of a CreateInstance intrinsic CIM method. /// - internal class CreateInstanceJob : PropertySettingJob + internal sealed class CreateInstanceJob : PropertySettingJob { private CimInstance _resultFromCreateInstance; private CimInstance _resultFromGetInstance; @@ -66,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(); @@ -75,8 +76,8 @@ internal override IObservable GetCimOperation() else { #if DEBUG - Dbg.Assert(_createInstanceOperationGotStarted == true, "GetInstance should be started *after* CreateInstance"); - Dbg.Assert(_getInstanceOperationGotStarted == false, "Should not start GetInstance operation twice"); + Dbg.Assert(_createInstanceOperationGotStarted, "GetInstance should be started *after* CreateInstance"); + 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 58b981c0ce5..0432c6c9a8f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs @@ -1,17 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of a DeleteInstance intrinsic CIM method + /// 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 2ed6c186d06..569740d0d1b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs @@ -1,19 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; using System.Management.Automation; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server + /// 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; @@ -90,6 +91,7 @@ internal override void WriteObject(object outputObject) PSObject pso = PSObject.AsPSObject(outputObject); AddShowComputerNameMarker(pso); } + base.WriteObject(outputObject); } 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 366f31a76ae..9eaac2b3d7f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -8,14 +7,16 @@ using System.Globalization; using System.Linq; using System.Management.Automation; + using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Cim; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of an extrinsic CIM method + /// Job wrapping invocation of an extrinsic CIM method. /// internal abstract class ExtrinsicMethodInvocationJob : MethodInvocationJobBase { @@ -51,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) { @@ -74,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) @@ -104,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; @@ -131,6 +130,7 @@ private void OnNext(CimMethodResult methodResult) var tmp = new PSNoteProperty(element.Key, element.Value.Value); propertyBag.Properties.Add(tmp); } + this.WriteObject(propertyBag); } } @@ -180,6 +180,7 @@ private void WriteObject(object cmdletOutput, MethodParameter methodParameter) pso.TypeNames.Insert(0, methodParameter.ParameterTypeName); } } + this.WriteObject(cmdletOutput); } @@ -188,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 86adf0750e6..06a45933522 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs @@ -1,18 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of an extrinsic CIM method + /// 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 db81ea37936..84b13b82097 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/MethodInvocationJobBase.cs @@ -1,21 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Management.Automation; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; using Microsoft.PowerShell.Cim; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of an extrinsic CIM method + /// Job wrapping invocation of an extrinsic CIM method. /// internal abstract class MethodInvocationJobBase : CimChildJobBase { @@ -59,13 +60,14 @@ 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; } @@ -79,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) @@ -102,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 49a227dadcc..869002a55f4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs @@ -1,18 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of a ModifyInstance intrinsic CIM method + /// Job wrapping invocation of a ModifyInstance intrinsic CIM method. /// - internal class ModifyInstanceJob : PropertySettingJob + internal sealed class ModifyInstanceJob : PropertySettingJob { private CimInstance _resultFromModifyInstance; private bool _resultFromModifyInstanceHasBeenPassedThru; @@ -66,6 +67,7 @@ internal override object PassThruObject PSObject pso = PSObject.AsPSObject(_resultFromModifyInstance); AddShowComputerNameMarker(pso); } + _resultFromModifyInstanceHasBeenPassedThru = true; return _resultFromModifyInstance; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/PropertySettingJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/PropertySettingJob.cs index c51085292a2..f5bdc0745cd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/PropertySettingJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/PropertySettingJob.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Cim; @@ -8,7 +7,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of a CreateInstance or ModifyInstance intrinsic CIM method + /// Job wrapping invocation of a CreateInstance or ModifyInstance intrinsic CIM method. /// internal abstract class PropertySettingJob : MethodInvocationJobBase { 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 734f87e7e1b..7bd2bd17531 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs @@ -1,22 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; using System.Text; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server + /// 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; + internal QueryInstancesJob(CimJobContext jobContext, CimQuery cimQuery, string wqlCondition) : base(jobContext, cimQuery) { @@ -25,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 662d890b69c..b52c23c3dd4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs @@ -1,19 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Base job for queries + /// Base job for queries. /// internal abstract class QueryJobBase : CimChildJobBase { - private CimQuery _cimQuery; + private readonly CimQuery _cimQuery; internal QueryJobBase(CimJobContext jobContext, CimQuery cimQuery) : base(jobContext) @@ -37,6 +38,7 @@ public override void OnNext(CimInstance item) { return; } + this.WriteObject(item); }); } @@ -53,6 +55,7 @@ public override void OnCompleted() { errorId = errorId + "_" + notFoundError.PropertyName; } + CimJobException cimJobException = CimJobException.CreateWithFullControl( this.JobContext, notFoundError.ErrorMessageGenerator(this.Description, this.JobContext.ClassName), @@ -62,6 +65,7 @@ public override void OnCompleted() { cimJobException.ErrorRecord.SetTargetObject(notFoundError.PropertyValue); } + this.WriteError(cimJobException.ErrorRecord); } }); 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 f19fadc6678..3bc376ab3d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs @@ -1,17 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Job wrapping invocation of a static CIM method + /// 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 67035a15e2d..6b716fa5501 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -10,20 +9,22 @@ using System.Management.Automation.Remoting; using System.Runtime.CompilerServices; using System.Threading; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Tracks (per-session) terminating errors in a given cmdlet invocation + /// 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) { @@ -48,11 +49,11 @@ private static int GetNumberOfSessions(InvocationInfo invocationInfo) // - this translates into potentially unlimited number of CimSession we will work with return int.MaxValue; } + 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) @@ -63,6 +64,7 @@ private static int GetNumberOfSessions(InvocationInfo invocationInfo) numberOfSessionsAssociatedWithArgument); } } + return maxNumberOfSessionsIndicatedByCimInstanceArguments; } @@ -97,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) { @@ -111,6 +113,7 @@ internal bool DidSessionAlreadyPassedConnectivityTest(CimSession session) { return alreadyPassedConnectivityTest; } + return false; } @@ -162,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) { @@ -171,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; } @@ -192,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 cf2a78ca39e..6bd2bee7fee 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Concurrent; @@ -10,15 +9,17 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Internal; using System.Threading; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; using Microsoft.PowerShell.Cim; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// Base class for all child jobs that wrap CIM operations + /// Base class for all child jobs that wrap CIM operations. /// internal abstract class CimChildJobBase : StartableJob, @@ -26,6 +27,7 @@ internal abstract class CimChildJobBase : { private static long s_globalJobNumberCounter; private readonly long _myJobNumber = Interlocked.Increment(ref s_globalJobNumberCounter); + private const string CIMJobType = "CimJob"; internal CimJobContext JobContext @@ -35,6 +37,7 @@ internal CimJobContext JobContext return _jobContext; } } + private readonly CimJobContext _jobContext; internal CimChildJobBase(CimJobContext jobContext) @@ -54,7 +57,8 @@ internal CimChildJobBase(CimJobContext jobContext) _jobSpecificCustomOptions = new Lazy(this.CalculateJobSpecificCustomOptions); } - private CimSensitiveValueConverter _cimSensitiveValueConverter = new CimSensitiveValueConverter(); + private readonly CimSensitiveValueConverter _cimSensitiveValueConverter = new(); + internal CimSensitiveValueConverter CimSensitiveValueConverter { get { return _cimSensitiveValueConverter; } } internal abstract IObservable GetCimOperation(); @@ -80,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; } @@ -102,12 +105,13 @@ private static bool IsWsManQuotaReached(Exception exception) { return false; } + if (errorCodeProperty.CimType != CimType.UInt32) { 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: @@ -154,13 +158,16 @@ 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; + private const int MaxRetryDelayMs = 15 * 1000; private const int MinRetryDelayMs = 100; + private Timer _sleepAndRetryTimer; + private void SleepAndRetry_OnWakeup(object state) { this.ExceptionSafeWrapper( @@ -173,15 +180,18 @@ private void SleepAndRetry_OnWakeup(object state) _sleepAndRetryTimer.Dispose(); _sleepAndRetryTimer = null; } + if (_jobWasStopped) { this.SetCompletedJobState(JobState.Stopped, null); return; } } + this.StartJob(); }); } + private void SleepAndRetry() { int tmpRandomDelay = _random.Next(0, _sleepAndRetryDelayRangeMs); @@ -219,7 +229,7 @@ private void SleepAndRetry() } /// - /// Indicates a location where this job is running + /// Indicates a location where this job is running. /// public override string Location { @@ -239,7 +249,7 @@ public override string Location } /// - /// Status message associated with the Job + /// Status message associated with the Job. /// public override string StatusMessage { @@ -305,10 +315,7 @@ internal override void StartJob() this.ExceptionSafeWrapper(delegate { IObservable observable = this.GetCimOperation(); - if (observable != null) - { - observable.Subscribe(this); - } + observable?.Subscribe(this); }); }); } @@ -326,6 +333,7 @@ internal string GetDescription() } internal abstract string Description { get; } + internal abstract string FailSafeDescription { get; } internal void ExceptionSafeWrapper(Action action) @@ -359,10 +367,12 @@ internal void ExceptionSafeWrapper(Action action) { everythingIsOk = true; } + if (_alreadyReachedCompletedState && _jobHadErrors) { everythingIsOk = true; } + if (!everythingIsOk) { Dbg.Assert(false, "PSInvalidOperationException should only happen in certain job states"); @@ -398,7 +408,6 @@ internal CimOperationOptions CreateOperationOptions() operationOptions.SetOption("__MI_OPERATIONOPTIONS_IMPROVEDPERF_STREAMING", 1); - operationOptions.Flags |= this.JobContext.CmdletInvocationContext.CmdletDefinitionContext.SchemaConformanceLevel; if (this.JobContext.CmdletInvocationContext.CmdletDefinitionContext.ResourceUri != null) @@ -411,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 (( @@ -423,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 (( @@ -435,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) @@ -494,6 +503,7 @@ internal CimOperationOptions CreateOperationOptions() this.JobContext.CmdletizationModuleVersion, CimSensitiveValueConverter); } + CimOperationOptionsHelper.SetCustomOption( operationOptions, "MI_OPERATIONOPTIONS_POWERSHELL_CMDLETNAME", @@ -509,27 +519,25 @@ internal CimOperationOptions CreateOperationOptions() } CimCustomOptionsDictionary jobSpecificCustomOptions = this.GetJobSpecificCustomOptions(); - if (jobSpecificCustomOptions != null) - { - jobSpecificCustomOptions.Apply(operationOptions, CimSensitiveValueConverter); - } + jobSpecificCustomOptions?.Apply(operationOptions, CimSensitiveValueConverter); return operationOptions; } private readonly Lazy _jobSpecificCustomOptions; + internal abstract CimCustomOptionsDictionary CalculateJobSpecificCustomOptions(); + private CimCustomOptionsDictionary GetJobSpecificCustomOptions() { return _jobSpecificCustomOptions.Value; } - #endregion #region Controlling job state - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); /// /// Stops this job. @@ -542,6 +550,7 @@ public override void StopJob() { return; } + _jobWasStopped = true; if (!_jobWasStarted) @@ -559,10 +568,11 @@ public override void StopJob() this.SetJobState(JobState.Stopping); } } + _cancellationTokenSource.Cancel(); } - private readonly object _jobStateLock = new object(); + private readonly object _jobStateLock = new(); private bool _jobHadErrors; private bool _jobWasStarted; private bool _jobWasStopped; @@ -596,6 +606,7 @@ internal void ReportJobFailure(IContainsErrorRecord exception) out sessionWasAlreadyTerminated); } } + if (brokenSessionException != null) { string brokenSessionMessage = string.Format( @@ -612,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; @@ -713,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) { @@ -746,39 +756,40 @@ 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)) { activity = this.GetDescription(); } + if (string.IsNullOrEmpty(statusDescription)) { 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 { @@ -807,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 @@ -982,6 +993,7 @@ private CimResponseType PromptUserCallback(string message, CimPromptType promptT { _userRespondedYesToAtLeastOneShouldProcess = true; } + return result; } @@ -990,18 +1002,17 @@ 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; } + return true.Equals(psShowComputerNameProperty.Value); } 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; } @@ -1025,17 +1036,17 @@ internal override void WriteObject(object outputObject) { cimInstance = outputObject as CimInstance; } + if (cimInstance != null) { CimCmdletAdapter.AssociateSessionOfOriginWithInstance(cimInstance, this.JobContext.Session); CimCustomOptionsDictionary.AssociateCimInstanceWithCustomOptions(cimInstance, this.GetJobSpecificCustomOptions()); } + if (this.JobContext.ShowComputerName) { - if (pso == null) - { - pso = PSObject.AsPSObject(outputObject); - } + pso ??= PSObject.AsPSObject(outputObject); + AddShowComputerNameMarker(pso); if (cimInstance == null) { @@ -1055,12 +1066,15 @@ protected override void Dispose(bool disposing) { isCompleted = _alreadyReachedCompletedState; } + if (!isCompleted) { this.StopJob(); this.Finished.WaitOne(); } + _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 50b4d486975..8a4e4272561 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Globalization; using System.Management.Automation; + using Microsoft.Management.Infrastructure.Options; namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletDefinitionContext + internal sealed class CimCmdletDefinitionContext { internal CimCmdletDefinitionContext( string cmdletizationClassName, @@ -26,14 +26,20 @@ internal CimCmdletDefinitionContext( _privateData = privateData; } - public string CmdletizationClassName { get; private set; } - public string CmdletizationClassVersion { get; private set; } - public Version CmdletizationModuleVersion { get; private set; } - public bool SupportsShouldProcess { get; private set; } + public string CmdletizationClassName { get; } + + public string CmdletizationClassVersion { get; } + + public Version CmdletizationModuleVersion { get; } + + public bool SupportsShouldProcess { get; } + private readonly IDictionary _privateData; private const string QueryLanguageKey = "QueryDialect"; + private bool? _useEnumerateInstancesInsteadOfWql; + public bool UseEnumerateInstancesInsteadOfWql { get @@ -48,8 +54,10 @@ public bool UseEnumerateInstancesInsteadOfWql { newValue = true; } + _useEnumerateInstancesInsteadOfWql = newValue; } + return _useEnumerateInstancesInsteadOfWql.Value; } } @@ -106,6 +114,7 @@ public bool ClientSideShouldProcess private Uri _resourceUri; private bool _resourceUriHasBeenCalculated; + public Uri ResourceUri { get @@ -123,6 +132,7 @@ public Uri ResourceUri _resourceUriHasBeenCalculated = true; } + return _resourceUri; } } @@ -133,6 +143,7 @@ public bool SkipTestConnection } private CimOperationFlags? _schemaConformanceLevel; + public CimOperationFlags SchemaConformanceLevel { get @@ -165,6 +176,7 @@ public CimOperationFlags SchemaConformanceLevel _schemaConformanceLevel = newSchemaConformanceLevel; } + return _schemaConformanceLevel.Value; } } 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 75866b49e36..3bee74526fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs @@ -1,15 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletInvocationContext + internal sealed class CimCmdletInvocationContext { internal CimCmdletInvocationContext( CimCmdletDefinitionContext cmdletDefinitionContext, @@ -75,20 +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; } + + public MshCommandRuntime.ShouldProcessPossibleOptimization ShouldProcessOptimization { get; } + + public ActionPreference ErrorActionPreference { get; } + + public ActionPreference WarningActionPreference { get; } + + public ActionPreference VerboseActionPreference { get; } + + public ActionPreference DebugActionPreference { get; } - public InvocationInfo CmdletInvocationInfo { get; private set; } - public MshCommandRuntime.ShouldProcessPossibleOptimization ShouldProcessOptimization { get; private set; } - public ActionPreference ErrorActionPreference { get; private set; } - public ActionPreference WarningActionPreference { get; private set; } - public ActionPreference VerboseActionPreference { get; private set; } - public ActionPreference DebugActionPreference { get; private set; } - public string NamespaceOverride { get; private set; } + public string NamespaceOverride { get; } public bool IsRunningInBackground { @@ -106,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 9c6679697c1..da075286c34 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -11,21 +10,23 @@ using System.Net.Mail; using System.Net.NetworkInformation; using System.Reflection; +using System.Runtime.InteropServices; using System.Security; using System.Security.AccessControl; using System.Security.Cryptography.X509Certificates; using System.Xml; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; -using System.Runtime.InteropServices; // TODO/FIXME: Move this class to src/cimSupport/other directory (to map to the namespace it lives in and functionality it implements [cmdletization independent]) 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; @@ -49,19 +50,16 @@ internal SensitiveString(int numberOfCharacters) private unsafe void Copy(char* source, int offset, int charsToCopy) { - if ((offset < 0) || (offset >= _string.Length)) - { - throw new ArgumentOutOfRangeException("offset"); - } - if (offset + charsToCopy > _string.Length) - { - throw new ArgumentOutOfRangeException("charsToCopy"); - } + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, _string.Length); + ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + charsToCopy, _string.Length, nameof(charsToCopy)); fixed (char* target = _string) - for (int i = 0; i < charsToCopy; i++) { - target[offset + i] = source[i]; + for (int i = 0; i < charsToCopy; i++) + { + target[offset + i] = source[i]; + } } } @@ -98,7 +96,7 @@ internal void Copy(SecureString source, int offset) } /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// public void Dispose() { @@ -107,7 +105,7 @@ public void Dispose() } /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// private void Dispose(bool disposing) { @@ -125,10 +123,10 @@ private void Dispose(bool disposing) } } - private readonly List _trackedDisposables = new List(); + private readonly List _trackedDisposables = new(); /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// public void Dispose() { @@ -137,7 +135,7 @@ public void Dispose() } /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// private void Dispose(bool disposing) { @@ -147,6 +145,7 @@ private void Dispose(bool disposing) { d.Dispose(); } + _trackedDisposables.Clear(); } } @@ -172,6 +171,7 @@ internal object ConvertFromDotNetToCim(object dotNetObject) var sensitiveString = new SensitiveString(escapedUsername.Length + PSCredentialDelimiter.Length + credential.Password.Length); lock (_trackedDisposables) { _trackedDisposables.Add(sensitiveString); } + sensitiveString.Copy(escapedUsername, 0); sensitiveString.Copy(PSCredentialDelimiter, escapedUsername.Length); sensitiveString.Copy(credential.Password, escapedUsername.Length + PSCredentialDelimiter.Length); @@ -183,6 +183,7 @@ internal object ConvertFromDotNetToCim(object dotNetObject) SecureString secureString = (SecureString)psObject.BaseObject; var sensitiveString = new SensitiveString(secureString.Length); lock (_trackedDisposables) { _trackedDisposables.Add(sensitiveString); } + sensitiveString.Copy(secureString, 0); return sensitiveString.Value; } @@ -200,6 +201,7 @@ internal object ConvertFromDotNetToCim(object dotNetObject) object cimElement = ConvertFromDotNetToCim(dotNetArray.GetValue(i)); cimArray.SetValue(cimElement, i); } + return cimArray; } } @@ -225,7 +227,7 @@ internal static Type GetCimType(Type dotNetType) internal static class CimValueConverter { - /// The only kind of exception this method can throw + /// The only kind of exception this method can throw. internal static object ConvertFromDotNetToCim(object dotNetObject) { if (dotNetObject == null) @@ -243,10 +245,12 @@ internal static object ConvertFromDotNetToCim(object dotNetObject) { return psObject.BaseObject; } + if (typeof(CimInstance).IsAssignableFrom(dotNetType)) { return psObject.BaseObject; } + if (typeof(PSReference).IsAssignableFrom(dotNetType)) { PSReference psReference = (PSReference)psObject.BaseObject; @@ -274,6 +278,7 @@ internal static object ConvertFromDotNetToCim(object dotNetObject) object cimElement = ConvertFromDotNetToCim(dotNetArray.GetValue(i)); cimArray.SetValue(cimElement, i); } + return cimArray; } } @@ -338,10 +343,10 @@ internal static object ConvertFromDotNetToCim(object dotNetObject) CmdletizationResources.CimConversion_CimIntrinsicValue); } - /// The only kind of exception this method can throw + /// The only kind of exception this method can throw. internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDotNetType) { - if (expectedDotNetType == null) { throw new ArgumentNullException("expectedDotNetType"); } + ArgumentNullException.ThrowIfNull(expectedDotNetType); if (cimObject == null) { @@ -357,6 +362,7 @@ internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDot { return LanguagePrimitives.ConvertTo(cimObject, expectedDotNetType, CultureInfo.InvariantCulture); } + if (expectedDotNetType == typeof(CimInstance)) { return LanguagePrimitives.ConvertTo(cimObject, expectedDotNetType, CultureInfo.InvariantCulture); @@ -374,6 +380,7 @@ internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDot object dotNetElement = ConvertFromCimToDotNet(cimArray.GetValue(i), dotNetElementType); dotNetArray.SetValue(dotNetElement, i); } + return dotNetArray; } } @@ -386,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)) { @@ -418,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 }); } @@ -446,8 +455,8 @@ internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDot return exceptionSafeReturn(delegate { int indexOfLastColon = cimIntrinsicValue.LastIndexOf(':'); - int port = int.Parse(cimIntrinsicValue.Substring(indexOfLastColon + 1), NumberStyles.Integer, CultureInfo.InvariantCulture); - IPAddress address = IPAddress.Parse(cimIntrinsicValue.Substring(0, indexOfLastColon)); + int port = int.Parse(cimIntrinsicValue.AsSpan(indexOfLastColon + 1), NumberStyles.Integer, CultureInfo.InvariantCulture); + IPAddress address = IPAddress.Parse(cimIntrinsicValue.AsSpan(0, indexOfLastColon)); return new IPEndPoint(address, port); }); } @@ -483,6 +492,7 @@ internal static CimType GetCimTypeEnum(Type dotNetType) { return CimType.Reference; } + if (typeof(PSReference[]).IsAssignableFrom(dotNetType)) { return CimType.ReferenceArray; @@ -509,10 +519,12 @@ internal static Type GetCimType(Type dotNetType) { return dotNetType; } + if (dotNetType == typeof(CimInstance)) { return dotNetType; } + if (dotNetType == typeof(PSReference)) { return dotNetType; @@ -569,7 +581,7 @@ internal static Type GetCimType(Type dotNetType) } /// - /// Returns a type of CIM representation if conversion from/to CIM can be done purely with LanguagePrimitives.ConvertTo + /// Returns a type of CIM representation if conversion from/to CIM can be done purely with LanguagePrimitives.ConvertTo. /// /// /// @@ -614,6 +626,7 @@ internal static Type GetElementType(Type arrayType) { return null; } + Type elementType = arrayType.GetElementType(); if (elementType.IsArray) { @@ -666,4 +679,4 @@ internal static void AssertIntrinsicCimType(Type type) "Caller should verify that type is an intrinsic CIM type"); } } -} \ No newline at end of file +} 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 2cee2f22a88..27d48ccab9a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs @@ -1,15 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; using System.Management.Automation; + using Microsoft.Management.Infrastructure; namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimJobContext + internal sealed class CimJobContext { internal CimJobContext( CimCmdletInvocationContext cmdletInvocationContext, @@ -22,10 +22,11 @@ internal CimJobContext( this.TargetObject = targetObject ?? this.ClassName; } - public CimCmdletInvocationContext CmdletInvocationContext { get; private set; } + public CimCmdletInvocationContext CmdletInvocationContext { get; } + + public CimSession Session { get; } - public CimSession Session { get; private set; } - public object TargetObject { get; private set; } + public object TargetObject { get; } public string ClassName { @@ -43,6 +44,7 @@ public string ClassNameOrNullIfResourceUriIsUsed { return null; } + return this.ClassName; } } @@ -55,6 +57,7 @@ public string Namespace { return this.CmdletInvocationContext.NamespaceOverride; } + return GetCimNamespace(this.CmdletInvocationContext.CmdletDefinitionContext.CmdletizationClassName); } } 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 528dac6cdab..34c708b5f4c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimOperationOptionsHelper.cs @@ -1,22 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; using Microsoft.PowerShell.Cim; + using Dbg = System.Management.Automation.Diagnostics; 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) { @@ -41,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) { @@ -105,6 +106,7 @@ internal static CimCustomOptionsDictionary MergeOptions(CimCustomOptionsDictiona result = MergeOptions(result, instanceRelatedToThisOperation); } } + return result; } @@ -115,7 +117,7 @@ internal void Apply(CimOperationOptions cimOperationOptions, CimSensitiveValueCo } /// - /// CimQuery supports building of queries against CIM object model + /// CimQuery supports building of queries against CIM object model. /// internal static class CimOperationOptionsHelper { 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 4878cf51bf7..f08c2ab3c11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -17,9 +16,9 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// CimQuery supports building of queries against CIM object model + /// CimQuery supports building of queries against CIM object model. /// - internal class CimQuery : QueryBuilder, ISessionBoundQueryBuilder + internal sealed class CimQuery : QueryBuilder, ISessionBoundQueryBuilder { private readonly StringBuilder _wqlCondition; @@ -28,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() { @@ -53,7 +52,7 @@ private static string ObjectToWqlLiteral(object o) { if (LanguagePrimitives.IsNull(o)) { - return "null"; // based on an example at http://msdn.microsoft.com/en-us/library/aa394054(VS.85).aspx + return "null"; // based on an example at https://msdn.microsoft.com/library/aa394054(VS.85).aspx } o = CimValueConverter.ConvertFromDotNetToCim(o); @@ -96,9 +95,10 @@ private static string ObjectToWqlLiteral(object o) { if ((bool)LanguagePrimitives.ConvertTo(o, typeof(bool), CultureInfo.InvariantCulture)) { - return "TRUE"; // based on http://msdn.microsoft.com/en-us/library/aa394054(VS.85).aspx + return "TRUE"; // based on https://msdn.microsoft.com/library/aa394054(VS.85).aspx } - return "FALSE"; // based on http://msdn.microsoft.com/en-us/library/aa394054(VS.85).aspx + + return "FALSE"; // based on https://msdn.microsoft.com/library/aa394054(VS.85).aspx } throw CimValueConverter.GetInvalidCastException( @@ -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) { @@ -191,13 +191,13 @@ private static string GetMatchCondition(string propertyName, IEnumerable propert #region Public inputs from cmdletization /// - /// Modifies the query, so that it only returns objects with a given property value + /// Modifies the query, so that it only returns objects with a given property value. /// - /// Property name to query on - /// Property values to accept in the query + /// 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 @@ -214,13 +214,13 @@ public override void FilterByProperty(string propertyName, IEnumerable allowedPr } /// - /// Modifies the query, so that it does not return objects with a given property value + /// Modifies the query, so that it does not return objects with a given property value. /// - /// Property name to query on - /// Property values to reject in the query + /// 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 @@ -241,10 +241,10 @@ public override void ExcludeByProperty(string propertyName, IEnumerable excluded } /// - /// Modifies the query, so that it returns only objects that have a property value greater than or equal to a threshold + /// Modifies the query, so that it returns only objects that have a property value greater than or equal to a threshold. /// - /// Property name to query on - /// Minimum property value + /// Property name to query on. + /// Minimum property value. /// /// Describes how to handle filters that didn't match any objects /// @@ -265,10 +265,10 @@ public override void FilterByMinPropertyValue(string propertyName, object minPro } /// - /// Modifies the query, so that it returns only objects that have a property value less than or equal to a threshold + /// Modifies the query, so that it returns only objects that have a property value less than or equal to a threshold. /// - /// Property name to query on - /// Maximum property value + /// Property name to query on. + /// Maximum property value. /// /// Describes how to handle filters that didn't match any objects /// @@ -291,10 +291,10 @@ public override void FilterByMaxPropertyValue(string propertyName, object maxPro /// /// Modifies the query, so that it returns only objects associated with /// - /// object that query results have to be associated with - /// name of the association - /// name of the role that has in the association - /// name of the role that query results have in the association + /// Object that query results have to be associated with. + /// Name of the association. + /// Name of the role that has in the association. + /// Name of the role that query results have in the association. /// /// Describes how to handle filters that didn't match any objects /// @@ -308,20 +308,14 @@ public override void FilterByAssociatedInstance(object associatedInstance, strin } /// - /// Sets a query option + /// Sets a query option. /// /// /// public override void AddQueryOption(string optionName, object optionValue) { - if (string.IsNullOrEmpty(optionName)) - { - throw new ArgumentNullException("optionName"); - } - if (optionValue == null) - { - throw new ArgumentNullException("optionValue"); - } + ArgumentException.ThrowIfNullOrEmpty(optionName); + ArgumentNullException.ThrowIfNull(optionValue); this.queryOptions[optionName] = optionValue; } @@ -361,13 +355,14 @@ CimSession ISessionBoundQueryBuilder.GetTargetSession() { return CimCmdletAdapter.GetSessionOfOriginFromCimInstance(_associatedObject); } + return null; } /// - /// Returns a string that represents the current CIM query + /// Returns a string that represents the current CIM query. /// - /// A string that represents the current CIM query + /// A string that represents the current CIM query. public override string ToString() { return _wqlCondition.ToString(); 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 8a7bfa882a5..472046b192f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; @@ -9,13 +8,15 @@ using System.Management.Automation; using System.Runtime.CompilerServices; using System.Threading; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim { /// - /// CIM-specific ObjectModelWrapper + /// CIM-specific ObjectModelWrapper. /// public sealed class CimCmdletAdapter : SessionBasedCmdletAdapter, @@ -32,7 +33,7 @@ public sealed class CimCmdletAdapter : #region Changing Session parameter to CimSession /// - /// CimSession to operate on + /// CimSession to operate on. /// [Parameter] [ValidateNotNullOrEmpty] @@ -44,6 +45,7 @@ public CimSession[] CimSession { return base.Session; } + set { base.Session = value; @@ -65,12 +67,14 @@ public override int ThrottleLimit return this.CmdletDefinitionContext.DefaultThrottleLimit; } + set { base.ThrottleLimit = value; _throttleLimitIsSetExplicitly = true; } } + private bool _throttleLimitIsSetExplicitly; #endregion @@ -78,9 +82,9 @@ public override int ThrottleLimit #region ObjectModelWrapper overrides /// - /// Creates a query builder for CIM OM + /// Creates a query builder for CIM OM. /// - /// Query builder for CIM OM + /// Query builder for CIM OM. public override QueryBuilder GetQueryBuilder() { return new CimQuery(); @@ -90,31 +94,30 @@ 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()); } } + private CimCmdletInvocationContext _cmdletInvocationContext; 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; } } + private CimCmdletDefinitionContext _cmdletDefinitionContext; internal InvocationInfo CmdletInvocationInfo @@ -131,7 +134,7 @@ internal InvocationInfo CmdletInvocationInfo /// /// Returns a new job name to use for the parent job that handles throttling of the child jobs that actually perform querying and method invocation. /// - /// Job name + /// Job name. protected override string GenerateParentJobName() { return "CimJob" + Interlocked.Increment(ref CimCmdletAdapter.s_jobNumber).ToString(CultureInfo.InvariantCulture); @@ -140,7 +143,7 @@ protected override string GenerateParentJobName() /// /// Returns default sessions to use when the user doesn't specify the -Session cmdlet parameter. /// - /// Default sessions to use when the user doesn't specify the -Session cmdlet parameter + /// Default sessions to use when the user doesn't specify the -Session cmdlet parameter. protected override CimSession DefaultSession { get @@ -160,15 +163,14 @@ private CimJobContext CreateJobContext(CimSession session, object targetObject) /// /// Creates a object that performs a query against the wrapped object model. /// - /// Remote session to query - /// Query parameters - /// object that performs a query against the wrapped object model + /// Remote session to query. + /// Query parameters. + /// 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("baseQuery"); + throw new ArgumentNullException(nameof(baseQuery)); } TerminatingErrorTracker tracker = TerminatingErrorTracker.GetTracker(this.CmdletInvocationInfo, isStaticCmdlet: false); @@ -176,6 +178,7 @@ internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder b { return null; } + if (!IsSupportedSession(session, tracker)) { return null; @@ -190,10 +193,10 @@ internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder b /// /// Creates a object that invokes an instance method in the wrapped object model. /// - /// 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 + /// Remote session to invoke the method in. + /// The object on which to invoke the method. + /// Method invocation details. + /// if successful method invocations should emit downstream the being operated on. /// internal override StartableJob CreateInstanceMethodInvocationJob(CimSession session, CimInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru) { @@ -202,6 +205,7 @@ internal override StartableJob CreateInstanceMethodInvocationJob(CimSession sess { return null; } + if (!IsSupportedSession(session, tracker)) { return null; @@ -274,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, @@ -293,8 +297,8 @@ private bool IsSupportedSession(CimSession cimSession, TerminatingErrorTracker t /// (of the class named by ) /// in the wrapped object model. /// - /// Remote session to invoke the method in - /// Method invocation details + /// Remote session to invoke the method in. + /// Method invocation details. internal override StartableJob CreateStaticMethodInvocationJob(CimSession session, MethodInvocationInfo methodInvocationInfo) { TerminatingErrorTracker tracker = TerminatingErrorTracker.GetTracker(this.CmdletInvocationInfo, isStaticCmdlet: true); @@ -302,6 +306,7 @@ internal override StartableJob CreateStaticMethodInvocationJob(CimSession sessio { return null; } + if (!IsSupportedSession(session, tracker)) { return null; @@ -330,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) { @@ -345,6 +350,7 @@ internal static CimSession GetSessionOfOriginFromCimInstance(CimInstance instanc { s_cimInstanceToSessionOfOrigin.TryGetValue(instance, out result); } + return result; } @@ -358,6 +364,7 @@ internal override CimSession GetSessionOfOriginFromInstance(CimInstance instance #region Handling of dynamic parameters private RuntimeDefinedParameterDictionary _dynamicParameters; + private const string CimNamespaceParameter = "CimNamespace"; private string GetDynamicNamespace() @@ -384,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 683384b5a8d..c1a8552db8c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -8,8 +7,10 @@ using System.Globalization; using System.Linq; using System.Management.Automation; + using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Cim; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cmdletization.Cim @@ -19,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() { @@ -35,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); @@ -54,9 +54,11 @@ public NotFoundError(string propertyName, object propertyValue, bool wildcardsEn } } - public string PropertyName { get; private set; } - public object PropertyValue { get; private set; } - public Func ErrorMessageGenerator { get; private set; } + public string PropertyName { get; } + + public object PropertyValue { get; } + + public Func ErrorMessageGenerator { get; } private static string GetErrorMessageForNotFound(string queryDescription, string className) { @@ -150,8 +152,10 @@ 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; } } + protected void AddPropertyValueFilter(PropertyValueFilter propertyValueFilter) { _propertyValueFilters.Add(propertyValueFilter); @@ -171,11 +175,12 @@ protected override bool IsMatchCore(CimInstance cimInstance) } } } + return isMatch; } } - private class CimInstanceRegularFilter : CimInstancePropertyBasedFilter + private sealed class CimInstanceRegularFilter : CimInstancePropertyBasedFilter { public CimInstanceRegularFilter(string propertyName, IEnumerable allowedPropertyValues, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) { @@ -196,7 +201,7 @@ public CimInstanceRegularFilter(string propertyName, IEnumerable allowedProperty if (valueBehaviors.Count == 1) { - this.BehaviorOnNoMatch = valueBehaviors.Single(); + this.BehaviorOnNoMatch = valueBehaviors.First(); } else { @@ -217,7 +222,7 @@ public override bool ShouldReportErrorOnNoMatches_IfMultipleFilters() case BehaviorOnNoMatch.Default: default: return this.PropertyValueFilters - .Where(f => !f.HadMatch).Any(f => f.BehaviorOnNoMatch == BehaviorOnNoMatch.ReportErrors); + .Any(static f => !f.HadMatch && f.BehaviorOnNoMatch == BehaviorOnNoMatch.ReportErrors); } } @@ -241,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) { @@ -266,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) { @@ -287,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) { @@ -308,7 +313,7 @@ public CimInstanceMaxFilter(string propertyName, object minPropertyValue, Behavi } } - private class CimInstanceAssociationFilter : CimInstanceFilterBase + private sealed class CimInstanceAssociationFilter : CimInstanceFilterBase { public CimInstanceAssociationFilter(BehaviorOnNoMatch behaviorOnNoMatch) { @@ -346,10 +351,13 @@ public BehaviorOnNoMatch BehaviorOnNoMatch { _behaviorOnNoMatch = this.GetDefaultBehaviorWhenNoMatchesFound(this.CimTypedExpectedPropertyValue); } + return _behaviorOnNoMatch; } } + protected abstract BehaviorOnNoMatch GetDefaultBehaviorWhenNoMatchesFound(object cimTypedExpectedPropertyValue); + private BehaviorOnNoMatch _behaviorOnNoMatch; public string PropertyName { get; } @@ -372,6 +380,7 @@ public bool IsMatch(CimInstance o) { return false; } + object actualPropertyValue = propertyInfo.Value; if (CimTypedExpectedPropertyValue == null) @@ -395,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); } @@ -423,6 +432,7 @@ private static bool IsSameType(object actualPropertyValue, object expectedProper { return true; } + if (expectedPropertyValue == null) { return true; @@ -455,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; } @@ -492,8 +501,8 @@ private static bool NonWildcardEqual(string propertyName, object actualPropertyV expectedPropertyValue = expectedPropertyValue.ToString(); 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); @@ -511,15 +520,17 @@ private static bool WildcardEqual(string propertyName, object actualPropertyValu { return false; } + if (!LanguagePrimitives.TryConvertTo(expectedPropertyValue, out expectedPropertyValueAsString)) { return false; } + return WildcardPattern.Get(expectedPropertyValueAsString, WildcardOptions.IgnoreCase).IsMatch(actualPropertyValueAsString); } } - internal class PropertyValueExcludeFilter : PropertyValueRegularFilter + internal sealed class PropertyValueExcludeFilter : PropertyValueRegularFilter { public PropertyValueExcludeFilter(string propertyName, object expectedPropertyValue, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, wildcardsEnabled, behaviorOnNoMatch) @@ -537,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) @@ -558,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; } @@ -573,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) @@ -594,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; } @@ -612,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 @@ -644,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 f9784f9f5e0..0f9762084d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs @@ -1,12 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -14,7 +12,7 @@ namespace Microsoft.PowerShell.Commands /// A command that appends the specified content to the item at the specified path. /// [Cmdlet(VerbsCommon.Add, "Content", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113278")] + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2096489")] public class AddContentCommand : WriteContentCommandBase { #region protected members @@ -23,15 +21,12 @@ public class AddContentCommand : WriteContentCommandBase /// Seeks to the end of the writer stream in each of the writers in the /// content holders. /// - /// /// /// The content holders that contain the writers to be moved. /// - /// /// /// If calling Seek on the content writer throws an exception. /// - /// internal override void SeekContentPosition(List contentHolders) { foreach (ContentHolder holder in contentHolders) @@ -45,14 +40,13 @@ 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, holder.PathInfo.Path, e); - // Log a provider health event MshLog.LogProviderHealthEvent( @@ -65,20 +59,17 @@ internal override void SeekContentPosition(List contentHolders) } } } - } // SeekContentPosition + } /// /// Makes the call to ShouldProcess with appropriate action and target strings. /// - /// /// /// The path to the item on which the content will be added. /// - /// /// /// True if the action should continue or false otherwise. /// - /// internal override bool CallShouldProcess(string path) { string action = NavigationResources.AddContentAction; @@ -89,6 +80,5 @@ internal override bool CallShouldProcess(string path) } #endregion protected members - } // AddContentCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs index ad56f989ae7..688b6362e16 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.Reflection; @@ -76,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", "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))) { @@ -129,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", "wmiClassName"); + ArgumentException.ThrowIfNullOrEmpty(wmiClassName); var rv = new List(); @@ -141,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) { @@ -247,11 +248,11 @@ internal static class CIMExtensions /// /// An "overload" of the /// .QueryInstances - /// method that takes only the namespace and query string as a parameters + /// method that takes only the namespace and query string as a parameters. /// - /// The CimSession to be queried - /// A string containing the namespace to run the query against - /// A string containing the query to be run + /// The CimSession to be queried. + /// A string containing the namespace to run the query against. + /// A string containing the query to be run. /// /// An IEnumerable interface that can be used to enumerate the instances /// @@ -263,9 +264,9 @@ internal static IEnumerable QueryInstances(this CimSession session, /// /// Execute a CIM query and return only the first instance in the result. /// - /// The CimSession to be queried - /// A string containing the namespace to run the query against - /// A string containing the query to be run + /// The CimSession to be queried. + /// A string containing the namespace to run the query against. + /// A string containing the query to be run. /// /// A object /// representing the first instance in a query result if successful, null @@ -292,8 +293,8 @@ internal static CimInstance QueryFirstInstance(this CimSession session, string n /// /// Execute a CIM query and return only the first instance in the result. /// - /// The CimSession to be queried - /// A string containing the query to be run + /// The CimSession to be queried. + /// A string containing the query to be run. /// /// A object /// representing the first instance in a query result if successful, null diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs index f0e57fc3e44..bd6ba2ee726 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -11,7 +9,7 @@ namespace Microsoft.PowerShell.Commands /// A command that appends the specified content to the item at the specified path. /// [Cmdlet(VerbsCommon.Clear, "Content", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113282")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096807")] public class ClearContentCommand : ContentCommandBase { #region Command code @@ -62,11 +60,11 @@ protected override void ProcessRecord() pathNotFound)); } } - } // ProcessRecord + } #endregion Command code /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// protected override bool ProviderSupportsShouldProcess @@ -77,22 +75,18 @@ protected override bool ProviderSupportsShouldProcess } } - /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) @@ -102,7 +96,6 @@ internal override object GetDynamicParameters(CmdletProviderContext context) } return InvokeProvider.Content.ClearContentDynamicParameters(".", context); - } // GetDynamicParameters - } // ClearContentCommand -} // namespace Microsoft.PowerShell.Commands - + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs index b752936ac11..f63eb8eed91 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs @@ -1,24 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to clear the value of a property of an item at a specified path + /// A command to clear the value of a property of an item at a specified path. /// [Cmdlet(VerbsCommon.Clear, "ItemProperty", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113284")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096903")] public class ClearItemPropertyCommand : PassThroughItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -27,70 +25,66 @@ public string[] Path get { return paths; - } // get + } set { paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return paths; - } // get + } set { base.SuppressWildcardExpansion = true; paths = value; - } // set - } // LiteralPath + } + } /// - /// The properties to clear from the item + /// The properties to clear from the item. /// - /// [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string Name { get { return _property; - } // get + } set { _property = value; } - } // Name + } /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { - Collection propertyCollection = new Collection(); + Collection propertyCollection = new(); propertyCollection.Add(_property); if (Path != null && Path.Length > 0) @@ -102,11 +96,12 @@ internal override object GetDynamicParameters(CmdletProviderContext context) propertyCollection, context); } + return InvokeProvider.Property.ClearPropertyDynamicParameters( ".", propertyCollection, context); - } // GetDynamicParameters + } #endregion Parameters @@ -122,14 +117,14 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #region Command code /// - /// Clears the properties of an item at the specified path + /// Clears the properties of an item at the specified path. /// 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) @@ -170,9 +165,8 @@ protected override void ProcessRecord() pathNotFound)); } } - } // ProcessRecord + } #endregion Command code - - } // ClearItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs index 183c3712fbc..bc4e44220dd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs @@ -1,11 +1,15 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; using System.Management.Automation; using System.Runtime.InteropServices; -using System.IO; -using System.Globalization; -using System.ComponentModel; using System.Text.RegularExpressions; -using System.Diagnostics.CodeAnalysis; + +#if !UNIX namespace Microsoft.PowerShell.Commands { @@ -14,7 +18,7 @@ namespace Microsoft.PowerShell.Commands /// This cmdlet clear all files in the RecycleBin for the given DriveLetter. /// If not DriveLetter is specified, then the RecycleBin for all drives are cleared. /// - [Cmdlet(VerbsCommon.Clear, "RecycleBin", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=524082", ConfirmImpact = ConfirmImpact.High)] + [Cmdlet(VerbsCommon.Clear, "RecycleBin", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2109377", ConfirmImpact = ConfirmImpact.High)] public class ClearRecycleBinCommand : PSCmdlet { private string[] _drivesList; @@ -30,19 +34,21 @@ public class ClearRecycleBinCommand : PSCmdlet public string[] DriveLetter { get { return _drivesList; } + set { _drivesList = value; } } /// /// Property that sets force parameter. This will allow to clear the recyclebin. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return _force; } + set { _force = value; @@ -72,7 +78,7 @@ protected override void ProcessRecord() { WriteError(new ErrorRecord( new ArgumentException( - String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.InvalidDriveNameFormat, "C", "C:", "C:\\")), + string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.InvalidDriveNameFormat, "C", "C:", "C:\\")), "InvalidDriveNameFormat", ErrorCategory.InvalidArgument, drive)); @@ -106,7 +112,7 @@ private bool ValidDrivePath(string drivePath) { foreach (DriveInfo drive in _availableDrives) { - if (String.Compare(drive.Name, drivePath, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(drive.Name, drivePath, StringComparison.OrdinalIgnoreCase)) { actualDrive = drive; break; @@ -119,7 +125,7 @@ private bool ValidDrivePath(string drivePath) { WriteError(new ErrorRecord( new System.IO.DriveNotFoundException( - String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.DriveNotFound, drivePath, "Get-Volume")), + string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.DriveNotFound, drivePath, "Get-Volume")), "DriveNotFound", ErrorCategory.InvalidArgument, drivePath)); @@ -131,13 +137,15 @@ private bool ValidDrivePath(string drivePath) // The drive path exists, and the drive is 'fixed'. return true; } + WriteError(new ErrorRecord( new ArgumentException( - String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.InvalidDriveType, drivePath, "Get-Volume")), + string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.InvalidDriveType, drivePath, "Get-Volume")), "InvalidDriveType", ErrorCategory.InvalidArgument, drivePath)); } + return false; } @@ -146,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); } @@ -157,14 +165,14 @@ private bool IsValidPattern(string input) /// /// /// - private string GetDrivePath(string driveName) + private static string GetDrivePath(string driveName) { string drivePath; if (driveName.EndsWith(":\\", StringComparison.OrdinalIgnoreCase)) { drivePath = driveName; } - else if (driveName.EndsWith(":", StringComparison.OrdinalIgnoreCase)) + else if (driveName.EndsWith(':')) { drivePath = driveName + "\\"; } @@ -172,6 +180,7 @@ private string GetDrivePath(string driveName) { drivePath = driveName + ":\\"; } + return drivePath; } @@ -199,47 +208,36 @@ private void EmptyRecycleBin(string drivePath) { // If driveName is null, then clear the recyclebin for all drives; otherwise, just for the specified driveName. - string activity = String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinProgressActivity); + string activity = string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinProgressActivity); string statusDescription; if (drivePath == null) { - statusDescription = String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinStatusDescriptionForAllDrives); + statusDescription = string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinStatusDescriptionForAllDrives); } else { - statusDescription = String.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinStatusDescriptionByDrive, 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); + // no need to check result as a failure is returned only if recycle bin is already empty uint result = NativeMethod.SHEmptyRecycleBin(IntPtr.Zero, drivePath, NativeMethod.RecycleFlags.SHERB_NOCONFIRMATION | NativeMethod.RecycleFlags.SHERB_NOPROGRESSUI | NativeMethod.RecycleFlags.SHERB_NOSOUND); - int lastError = Marshal.GetLastWin32Error(); - - // update the progress bar to completed progress.PercentComplete = 100; progress.RecordType = ProgressRecordType.Completed; WriteProgress(progress); - - // 0 is for a successful operation - // 203 comes up when trying to empty an already emptied recyclebin - // 18 comes up when there are no more files in the given recyclebin - if (!(lastError == 0 || lastError == 203 || lastError == 18)) - { - Win32Exception exception = new Win32Exception(lastError); - WriteError(new ErrorRecord(exception, "FailedToClearRecycleBin", ErrorCategory.InvalidOperation, "RecycleBin")); - } } } } - internal static class NativeMethod + internal static partial class NativeMethod { // Internal code to SHEmptyRecycleBin internal enum RecycleFlags : uint @@ -248,7 +246,9 @@ internal enum RecycleFlags : uint SHERB_NOPROGRESSUI = 0x00000002, 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); } -} \ No newline at end of file +} +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs new file mode 100644 index 00000000000..77e1b497b95 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Microsoft.PowerShell.Commands.Internal +{ + internal static partial class Clipboard + { + private static bool? _clipboardSupported; + + // Used if an external clipboard is not available, e.g. if xclip is missing. + // This is useful for testing in CI as well. + private static string _internalClipboard; + + private static string StartProcess( + string tool, + string args, + string stdin = "", + bool readStdout = true) + { + ProcessStartInfo startInfo = new(); + startInfo.UseShellExecute = false; + startInfo.RedirectStandardInput = true; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.FileName = tool; + startInfo.Arguments = args; + string stdout = string.Empty; + + using (Process process = new()) + { + process.StartInfo = startInfo; + try + { + process.Start(); + } + catch (System.ComponentModel.Win32Exception) + { + _clipboardSupported = false; + return string.Empty; + } + + process.StandardInput.Write(stdin); + process.StandardInput.Close(); + + if (readStdout) + { + stdout = process.StandardOutput.ReadToEnd(); + } + + process.WaitForExit(250); + _clipboardSupported = process.ExitCode == 0; + } + + return stdout; + } + + public static string GetText() + { + if (_clipboardSupported == false) + { + return _internalClipboard ?? string.Empty; + } + + string tool = string.Empty; + string args = string.Empty; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string clipboardText = string.Empty; + ExecuteOnStaThread(() => GetTextImpl(out clipboardText)); + return clipboardText; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + tool = "xclip"; + args = "-selection clipboard -out"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + tool = "pbpaste"; + } + else + { + _clipboardSupported = false; + return string.Empty; + } + + return StartProcess(tool, args); + } + + public static void SetText(string text) + { + if (_clipboardSupported == false) + { + _internalClipboard = text; + return; + } + + string tool = string.Empty; + string args = string.Empty; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + ExecuteOnStaThread(() => SetClipboardData(Tuple.Create(text, CF_UNICODETEXT))); + return; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + tool = "xclip"; + if (string.IsNullOrEmpty(text)) + { + args = "-selection clipboard /dev/null"; + } + else + { + args = "-selection clipboard -in"; + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + tool = "pbcopy"; + } + else + { + _clipboardSupported = false; + return; + } + + StartProcess(tool, args, text, readStdout: false); + if (_clipboardSupported == false) + { + _internalClipboard = text; + } + } + + public static void SetRtf(string plainText, string rtfText) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + if (s_CF_RTF == 0) + { + s_CF_RTF = RegisterClipboardFormat("Rich Text Format"); + } + + ExecuteOnStaThread(() => SetClipboardData( + Tuple.Create(plainText, CF_UNICODETEXT), + Tuple.Create(rtfText, s_CF_RTF))); + } + + private const uint GMEM_MOVEABLE = 0x0002; + private const uint GMEM_ZEROINIT = 0x0040; + private const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT; + + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes); + + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalFree(IntPtr hMem); + + [LibraryImport("kernel32.dll")] + private static partial IntPtr GlobalLock(IntPtr hMem); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GlobalUnlock(IntPtr hMem); + + [LibraryImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] + private static partial void CopyMemory(IntPtr dest, IntPtr src, uint count); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool IsClipboardFormatAvailable(uint format); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool OpenClipboard(IntPtr hWndNewOwner); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CloseClipboard(); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool EmptyClipboard(); + + [LibraryImport("user32.dll")] + private static partial IntPtr GetClipboardData(uint format); + + [LibraryImport("user32.dll")] + private static partial IntPtr SetClipboardData(uint format, IntPtr data); + + [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; + + private static uint s_CF_RTF; + + private static bool GetTextImpl(out string text) + { + try + { + if (IsClipboardFormatAvailable(CF_UNICODETEXT)) + { + if (OpenClipboard(IntPtr.Zero)) + { + var data = GetClipboardData(CF_UNICODETEXT); + if (data != IntPtr.Zero) + { + data = GlobalLock(data); + text = Marshal.PtrToStringUni(data); + GlobalUnlock(data); + return true; + } + } + } + else if (IsClipboardFormatAvailable(CF_TEXT)) + { + if (OpenClipboard(IntPtr.Zero)) + { + var data = GetClipboardData(CF_TEXT); + if (data != IntPtr.Zero) + { + data = GlobalLock(data); + text = Marshal.PtrToStringAnsi(data); + GlobalUnlock(data); + return true; + } + } + } + } + catch + { + // Ignore exceptions + } + finally + { + CloseClipboard(); + } + + text = string.Empty; + return false; + } + + private static bool SetClipboardData(params Tuple[] data) + { + try + { + if (!OpenClipboard(IntPtr.Zero)) + { + return false; + } + + EmptyClipboard(); + + foreach (var d in data) + { + if (!SetSingleClipboardData(d.Item1, d.Item2)) + { + return false; + } + } + } + finally + { + CloseClipboard(); + } + + return true; + } + + private static bool SetSingleClipboardData(string text, uint format) + { + IntPtr hGlobal = IntPtr.Zero; + IntPtr data = IntPtr.Zero; + + try + { + uint bytes; + if (format == s_CF_RTF || format == CF_TEXT) + { + bytes = (uint)(text.Length + 1); + data = Marshal.StringToHGlobalAnsi(text); + } + else if (format == CF_UNICODETEXT) + { + bytes = (uint)((text.Length + 1) * 2); + data = Marshal.StringToHGlobalUni(text); + } + else + { + // Not yet supported format. + return false; + } + + if (data == IntPtr.Zero) + { + return false; + } + + hGlobal = GlobalAlloc(GHND, (UIntPtr)bytes); + if (hGlobal == IntPtr.Zero) + { + return false; + } + + IntPtr dataCopy = GlobalLock(hGlobal); + if (dataCopy == IntPtr.Zero) + { + return false; + } + + CopyMemory(dataCopy, data, bytes); + GlobalUnlock(hGlobal); + + if (SetClipboardData(format, hGlobal) != IntPtr.Zero) + { + // The clipboard owns this memory now, so don't free it. + hGlobal = IntPtr.Zero; + } + } + catch + { + // Ignore failures + } + finally + { + if (data != IntPtr.Zero) + { + Marshal.FreeHGlobal(data); + } + + if (hGlobal != IntPtr.Zero) + { + GlobalFree(hGlobal); + } + } + + return true; + } + + private static void ExecuteOnStaThread(Func action) + { + const int RetryCount = 5; + int tries = 0; + + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) + { + while (tries++ < RetryCount && !action()) + { + // wait until RetryCount or action + } + + return; + } + + Exception exception = null; + var thread = new Thread(() => + { + try + { + while (tries++ < RetryCount && !action()) + { + // wait until RetryCount or action + } + } + catch (Exception e) + { + exception = e; + } + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + if (exception != null) + { + throw exception; + } + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs index a1fd4a9ee47..79b1c862bfd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs @@ -1,11 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; using System.Collections.ObjectModel; using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -14,26 +13,27 @@ namespace Microsoft.PowerShell.Commands /// A command that adds the parent and child parts of a path together /// with the appropriate path separator. /// - [Cmdlet(VerbsCommon.Join, "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113347")] + [Cmdlet(VerbsCommon.Join, "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096811")] [OutputType(typeof(string))] public class JoinPathCommand : CoreCommandWithCredentialsBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [Alias("PSPath")] public string[] Path { get; set; } /// - /// Gets or sets the childPath parameter to the command + /// Gets or sets the childPath parameter to the command. /// [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. @@ -42,15 +42,30 @@ public class JoinPathCommand : CoreCommandWithCredentialsBase [AllowNull] [AllowEmptyString] [AllowEmptyCollection] - public string[] AdditionalChildPath { get; set; } = Utils.EmptyArray(); + public string[] AdditionalChildPath { get; set; } = Array.Empty(); /// - /// Determines if the path should be resolved after being joined + /// Determines if the path should be resolved after being joined. /// /// [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) @@ -204,7 +233,7 @@ protected override void ProcessRecord() pathNotFound)); continue; } - } // for each path + } } else { @@ -214,10 +243,8 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code - - } // JoinPathCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs index a23aa040ed9..94e923bfa6e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Commands public class CompleteTransactionCommand : PSCmdlet { /// - /// Commits the current transaction + /// Commits the current transaction. /// protected override void EndProcessing() { @@ -26,6 +26,5 @@ protected override void EndProcessing() this.Context.TransactionManager.Commit(); } } - } // CommitTransactionCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 744849d3d99..ea8c111531c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -1,8 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #if !UNIX -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ using System; using System.Collections; using System.Collections.Generic; @@ -11,47 +11,37 @@ 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; -using Microsoft.PowerShell.CoreClr.Stubs; - -// 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 + #region Restart-Computer /// - /// This exception is thrown when the timeout expires before a computer finishes restarting + /// 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 + /// 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. @@ -70,118 +60,66 @@ internal RestartComputerTimeoutException(string computerName, int timeout, strin } /// - /// Construct a RestartComputerTimeoutException + /// Construct a RestartComputerTimeoutException. /// public RestartComputerTimeoutException() : base() { } /// - /// Constructs a RestartComputerTimeoutException + /// Constructs a RestartComputerTimeoutException. /// - /// /// /// The message used in the exception. /// public RestartComputerTimeoutException(string message) : base(message) { } /// - /// Constructs a RestartComputerTimeoutException + /// Constructs a RestartComputerTimeoutException. /// - /// /// /// The message used in the exception. /// - /// /// /// 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("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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ComputerName", ComputerName); - info.AddValue("Timeout", Timeout); - } -#endregion Serialization } /// - /// Defines the services that Restart-Computer can wait on + /// Defines the services that Restart-Computer can wait on. /// [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] public enum WaitForServiceTypes { /// - /// Wait for the WMI service to be ready + /// Wait for the WMI service to be ready. /// Wmi = 0x0, /// - /// Wait for the WinRM service to be ready + /// Wait for the WinRM service to be ready. /// WinRM = 0x1, /// - /// Wait for the PowerShell to be ready + /// Wait for the PowerShell to be ready. /// PowerShell = 0x2, } /// - /// Restarts the computer + /// Restarts the computer. /// [Cmdlet(VerbsLifecycle.Restart, "Computer", SupportsShouldProcess = true, DefaultParameterSetName = DefaultParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135253", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097060", RemotingCapability = RemotingCapability.OwnedByCommand)] public class RestartComputerCommand : PSCmdlet, IDisposable { -#region "Parameters and PrivateData" + #region "Parameters and PrivateData" private const string DefaultParameterSet = "DefaultSet"; - private const int forcedReboot = 6; // see https://msdn.microsoft.com/en-us/library/aa394058(v=vs.85).aspx + private const int forcedReboot = 6; // see https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx /// - /// The authentication options for CIM_WSMan connection + /// The authentication options for CIM_WSMan connection. /// [Parameter(ParameterSetName = DefaultParameterSet)] [ValidateSet( @@ -205,17 +143,17 @@ public class RestartComputerCommand : PSCmdlet, IDisposable [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("CN", "__SERVER", "Server", "IPAddress")] - public String[] ComputerName { get; set; } = new string[] { "." }; + 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". /// Specifies a user account that has permission to perform this action. Type a /// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential - /// object, such as one from the Get-Credential cmdlet + /// object, such as one from the Get-Credential cmdlet. /// [Parameter(Position = 1)] [ValidateNotNullOrEmpty] @@ -231,7 +169,7 @@ public class RestartComputerCommand : PSCmdlet, IDisposable public SwitchParameter Force { get; set; } /// - /// Specify the Wait parameter. Prompt will be blocked is the Timeout is not 0 + /// Specify the Wait parameter. Prompt will be blocked is the Timeout is not 0. /// [Parameter(ParameterSetName = DefaultParameterSet)] public SwitchParameter Wait { get; set; } @@ -246,13 +184,18 @@ public class RestartComputerCommand : PSCmdlet, IDisposable [ValidateRange(-1, int.MaxValue)] public int Timeout { - get { return _timeout; } + get + { + return _timeout; + } + set { _timeout = value; _timeoutSpecified = true; } } + private int _timeout = -1; private bool _timeoutSpecified = false; @@ -263,13 +206,18 @@ public int Timeout [Parameter(ParameterSetName = DefaultParameterSet)] public WaitForServiceTypes For { - get { return _waitFor; } + get + { + return _waitFor; + } + set { _waitFor = value; _waitForSpecified = true; } } + private WaitForServiceTypes _waitFor = WaitForServiceTypes.PowerShell; private bool _waitForSpecified = false; @@ -278,21 +226,26 @@ 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 { _delay = value; _delaySpecified = true; } } + private int _delay = 5; private bool _delaySpecified = false; /// - /// Script to test if the PowerShell is ready + /// Script to test if the PowerShell is ready. /// private const string TestPowershellScript = @" $array = @($input) @@ -303,25 +256,28 @@ public Int16 Delay $arguments = @{ ComputerName = $computerName ScriptBlock = { $true } - SessionOption = NewPSSessionOption -NoMachineProfile + + SessionOption = New-PSSessionOption -NoMachineProfile ErrorAction = 'SilentlyContinue' } + if ( $null -ne $array[0] ) { $arguments['Credential'] = $array[0] } + $result[$computerName] = (Invoke-Command @arguments) -as [bool] } $result "; /// - /// The indicator to use when show progress + /// The indicator to use when show progress. /// - private string[] _indicator = { "|", "/", "-", "\\" }; + private readonly string[] _indicator = { "|", "/", "-", "\\" }; /// - /// The activity id + /// The activity id. /// private int _activityId; @@ -332,27 +288,27 @@ public Int16 Delay private const int SecondsToWaitForRestartToBegin = 25; /// - /// Actual time out in seconds + /// Actual time out in seconds. /// private int _timeoutInMilliseconds; /// - /// Indicate to exit + /// 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/en-us/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx + // 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(); // And for this, use PsUtils.GetHostname() - private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync("").Result.HostName; + private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync(string.Empty).Result.HostName; private int _percent; private string _status; @@ -365,12 +321,12 @@ public Int16 Delay private const string WinrmConnectionTest = "WinRM"; private const string PowerShellConnectionTest = "PowerShell"; -#endregion "parameters and PrivateData" + #endregion "parameters and PrivateData" -#region "IDisposable Members" + #region "IDisposable Members" /// - /// Dispose Method + /// Dispose Method. /// public void Dispose() { @@ -388,23 +344,16 @@ 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(); } } -#endregion "IDisposable Members" + #endregion "IDisposable Members" -#region "Private Methods" + #region "Private Methods" /// /// Validate parameters for 'DefaultSet' @@ -426,6 +375,7 @@ private void ValidateComputerNames() { WriteError(error); } + continue; } @@ -441,10 +391,10 @@ private void ValidateComputerNames() } // Force wait with a test hook even if we're on the local computer - if (! InternalTestHooks.TestWaitStopComputer && Wait && containLocalhost) + 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; } @@ -458,7 +408,7 @@ private void ValidateComputerNames() } /// - /// Write out progress + /// Write out progress. /// /// /// @@ -466,14 +416,14 @@ 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); } /// - /// Calculate the progress percentage + /// Calculate the progress percentage. /// /// /// @@ -500,7 +450,7 @@ private int CalculateProgressPercentage(string currentStage) } /// - /// Event handler for the timer + /// Event handler for the timer. /// /// private void OnTimedEvent(object s) @@ -516,7 +466,7 @@ private void OnTimedEvent(object s) } } - private class ComputerInfo + private sealed class ComputerInfo { internal string LastBootUpTime; internal bool RebootComplete; @@ -534,8 +484,12 @@ private List TestRestartStageUsingWsman(IEnumerable computerName { try { - if (token.IsCancellationRequested) { break; } - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, token, this)) + if (token.IsCancellationRequested) + { + break; + } + + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -549,7 +503,7 @@ private List TestRestartStageUsingWsman(IEnumerable computerName string newLastBootUpTime = os.CimInstanceProperties["LastBootUpTime"].Value.ToString(); string oldLastBootUpTime = _computerInfos[computer].LastBootUpTime; - if (string.Compare(newLastBootUpTime, oldLastBootUpTime, StringComparison.OrdinalIgnoreCase) != 0) + if (!string.Equals(newLastBootUpTime, oldLastBootUpTime, StringComparison.OrdinalIgnoreCase)) { _computerInfos[computer].RebootComplete = true; nextTestList.Add(computer); @@ -591,7 +545,7 @@ private List SetUpComputerInfoUsingWsman(IEnumerable computerNam { try { - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, token, this)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -650,17 +604,18 @@ private void WriteOutTimeoutError(IEnumerable computerNames) string errorMsg = StringUtil.Format(ComputerResources.RestartcomputerFailed, computer, ComputerResources.TimeoutError); var exception = new RestartComputerTimeoutException(computer, Timeout, errorMsg, errorId); var error = new ErrorRecord(exception, errorId, ErrorCategory.OperationTimeout, computer); - if (! InternalTestHooks.TestWaitStopComputer ) { - WriteError(error); + if (!InternalTestHooks.TestWaitStopComputer) + { + WriteError(error); } } } -#endregion "Private Methods" + #endregion "Private Methods" -#region "Internal Methods" + #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'"; @@ -674,8 +629,12 @@ internal static List TestWmiConnectionUsingWsman(List computerNa { try { - if (token.IsCancellationRequested) { break; } - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, token, cmdlet)) + if (token.IsCancellationRequested) + { + break; + } + + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, isLocalHost: false, cmdlet, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -716,7 +675,7 @@ internal static List TestWmiConnectionUsingWsman(List computerNa } /// - /// Test the PowerShell state for the restarting computer + /// Test the PowerShell state for the restarting computer. /// /// /// @@ -725,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 { @@ -771,9 +730,9 @@ internal static List TestPowerShell(List computerNames, List /// BeginProcessing method. @@ -783,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; @@ -810,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; } @@ -828,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)) { @@ -843,7 +805,7 @@ protected override void ProcessRecord() bool isLocal = false; string compname; - if (computer.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) + if (computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { compname = _shortLocalMachineName; isLocal = true; @@ -873,7 +835,7 @@ protected override void ProcessRecord() { _waitOnComputers.Add(computer); } - }//end foreach + } if (_waitOnComputers.Count > 0) { @@ -903,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) @@ -930,7 +896,11 @@ 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) { if (_waitOnComputers.Count == 1) @@ -939,12 +909,17 @@ protected override void ProcessRecord() _percent = CalculateProgressPercentage(StageVerification); WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing); } + List nextTestList = (isForWmi || isForPowershell) ? wmiTestList : winrmTestList; restartStageTestList = TestRestartStageUsingWsman(restartStageTestList, nextTestList, _cancel.Token); } // Test WMI service - if (_exit) { break; } + if (_exit) + { + break; + } + if (wmiTestList.Count > 0) { // This statement block executes for both CLRs. @@ -956,13 +931,22 @@ protected override void ProcessRecord() _percent = CalculateProgressPercentage(WmiConnectionTest); 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) { // This statement block executes for both CLRs. @@ -986,15 +970,26 @@ 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) { if (_waitOnComputers.Count == 1) @@ -1003,12 +998,16 @@ protected override void ProcessRecord() _percent = CalculateProgressPercentage(PowerShellConnectionTest); WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing); } + psTestList = TestPowerShell(psTestList, allDoneList, _powershell, this.Credential); } } 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) @@ -1036,6 +1035,7 @@ protected override void ProcessRecord() WriteProgress(_indicator[indicatorIndex % 4] + _activity, _status, 100, ProgressRecordType.Completed); _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } + break; } @@ -1044,33 +1044,54 @@ protected override void ProcessRecord() _status = StringUtil.Format(ComputerResources.WaitForMultipleComputers, machineCompleteRestart, _waitOnComputers.Count); _percent = machineCompleteRestart * 100 / _waitOnComputers.Count; } - }// end while(true) - + } if (_timeUp) { // The timeout expires. Write out timeout error messages for the computers that haven't finished restarting do { - if (restartStageTestList.Count > 0) { WriteOutTimeoutError(restartStageTestList); } - if (wmiTestList.Count > 0) { WriteOutTimeoutError(wmiTestList); } + if (restartStageTestList.Count > 0) + { + WriteOutTimeoutError(restartStageTestList); + } + + 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 (isForWinRm) { break; } + if (winrmTestList.Count > 0) + { + WriteOutTimeoutError(winrmTestList); + } + + 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); } - }// end if(waitOnComputer.Count > 0) - }//end DefaultParameter - }//End Processrecord + } + } + } /// - /// to implement ^C + /// To implement ^C. /// protected override void StopProcessing() { @@ -1078,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) { @@ -1090,31 +1108,32 @@ protected override void StopProcessing() } } -#endregion "Overrides" + #endregion "Overrides" } -#endregion Restart-Computer + #endregion Restart-Computer -#region Stop-Computer + #region Stop-Computer /// - /// cmdlet to stop computer + /// Cmdlet to stop computer. /// [Cmdlet(VerbsLifecycle.Stop, "Computer", SupportsShouldProcess = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135263", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097151", RemotingCapability = RemotingCapability.SupportedByCommand)] public sealed class StopComputerCommand : PSCmdlet, IDisposable { -#region Private Members + #region Private Members + + private readonly CancellationTokenSource _cancel = new(); - private readonly CancellationTokenSource _cancel = new CancellationTokenSource(); - private const int forcedShutdown = 5; // See https://msdn.microsoft.com/en-us/library/aa394058(v=vs.85).aspx + private const int forcedShutdown = 5; // See https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx -#endregion + #endregion -#region "Parameters" + #region "Parameters" /// - /// The authentication options for CIM_WSMan connection + /// The authentication options for CIM_WSMan connection. /// [Parameter] [ValidateSet( @@ -1137,14 +1156,13 @@ public sealed class StopComputerCommand : PSCmdlet, IDisposable [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("CN", "__SERVER", "Server", "IPAddress")] - public String[] ComputerName { get; set; } = new string[] { "." }; - + public string[] ComputerName { get; set; } = new string[] { "." }; /// /// The following is the definition of the input parameter "Credential". /// Specifies a user account that has permission to perform this action. Type a /// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential - /// object, such as one from the Get-Credential cmdlet + /// object, such as one from the Get-Credential cmdlet. /// [Parameter(Position = 1)] [ValidateNotNullOrEmpty] @@ -1152,33 +1170,29 @@ public sealed class StopComputerCommand : PSCmdlet, IDisposable public PSCredential Credential { get; set; } /// - /// Force the operation to take place if possible + /// Force the operation to take place if possible. /// [Parameter] public SwitchParameter Force { get; set; } = false; #endregion "parameters" -#region "IDisposable Members" + #region "IDisposable Members" /// - /// Dispose Method + /// Dispose Method. /// public void Dispose() { - try - { - _cancel.Dispose(); - } - catch (ObjectDisposedException) { } + _cancel.Dispose(); } -#endregion "IDisposable Members" + #endregion "IDisposable Members" -#region "Overrides" + #region "Overrides" /// - /// ProcessRecord + /// ProcessRecord. /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] protected override void ProcessRecord() @@ -1188,10 +1202,10 @@ protected override void ProcessRecord() flags[0] = forcedShutdown; ProcessWSManProtocol(flags); - }//End Processrecord + } /// - /// to implement ^C + /// To implement ^C. /// protected override void StopProcessing() { @@ -1203,9 +1217,9 @@ protected override void StopProcessing() catch (AggregateException) { } } -#endregion "Overrides" + #endregion "Overrides" -#region Private Methods + #region Private Methods private void ProcessWSManProtocol(object[] flags) { @@ -1215,9 +1229,12 @@ 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.CurrentCultureIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase))) { compname = Dns.GetHostName(); strLocal = "localhost"; @@ -1248,37 +1265,37 @@ private void ProcessWSManProtocol(object[] flags) } } -#endregion + #endregion } -#endregion + #endregion -#region Rename-Computer + #region Rename-Computer /// /// Renames a domain computer and its corresponding domain account or a /// 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=219990", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097054", RemotingCapability = RemotingCapability.SupportedByCommand)] + [OutputType(typeof(RenameComputerChangeInfo))] public class RenameComputerCommand : PSCmdlet { -#region Private Members + #region Private Members private bool _containsLocalHost = false; private string _newNameForLocalHost = null; private readonly string _shortLocalMachineName = Dns.GetHostName(); - private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync("").Result.HostName; + private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync(string.Empty).Result.HostName; -#endregion + #endregion -#region Parameters + #region Parameters /// - /// Target computers to rename + /// Target computers to rename. /// [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] @@ -1287,12 +1304,12 @@ public class RenameComputerCommand : PSCmdlet /// /// Emit the output. /// - //[Alias("Restart")] + // [Alias("Restart")] [Parameter] public SwitchParameter PassThru { get; set; } /// - /// The domain credential of the domain the target computer joined + /// The domain credential of the domain the target computer joined. /// [Parameter] [ValidateNotNullOrEmpty] @@ -1300,7 +1317,7 @@ public class RenameComputerCommand : PSCmdlet public PSCredential DomainCredential { get; set; } /// - /// The administrator credential of the target computer + /// The administrator credential of the target computer. /// [Parameter] [ValidateNotNullOrEmpty] @@ -1308,36 +1325,40 @@ public class RenameComputerCommand : PSCmdlet public PSCredential LocalCredential { get; set; } /// - /// New names for the target computers + /// New names for the target computers. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] public string NewName { get; set; } /// - /// Suppress the ShouldContinue + /// Suppress the ShouldContinue. /// [Parameter] public SwitchParameter Force { get { return _force; } + set { _force = value; } } + private bool _force; /// - /// To restart the target computer after rename it + /// To restart the target computer after rename it. /// [Parameter] public SwitchParameter Restart { get { return _restart; } + set { _restart = value; } } + private bool _restart; /// - /// The authentication options for CIM_WSMan connection + /// The authentication options for CIM_WSMan connection. /// [Parameter] [ValidateSet( @@ -1350,12 +1371,12 @@ public SwitchParameter Restart [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] public string WsmanAuthentication { get; set; } = "Default"; -#endregion + #endregion -#region "Private Methods" + #region "Private Methods" /// - /// Check to see if the target computer is the local machine + /// Check to see if the target computer is the local machine. /// private string ValidateComputerName() { @@ -1368,6 +1389,7 @@ private string ValidateComputerName() { WriteError(targetError); } + return null; } @@ -1377,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); @@ -1419,14 +1441,14 @@ private void DoRenameComputerWsman(string computer, string computerName, string try { - using (CancellationTokenSource cancelTokenSource = new CancellationTokenSource()) - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credToUse, WsmanAuthentication, cancelTokenSource.Token, this)) + using (CancellationTokenSource cancelTokenSource = new()) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credToUse, WsmanAuthentication, isLocalhost, this, cancelTokenSource.Token)) { var operationOptions = new CimOperationOptions { Timeout = TimeSpan.FromMilliseconds(10000), CancellationToken = cancelTokenSource.Token, - //This prefix works against all versions of the WinRM server stack, both win8 and win7 + // This prefix works against all versions of the WinRM server stack, both win8 and win7 ResourceUriPrefix = new Uri(ComputerWMIHelper.CimUriPrefix) }; @@ -1442,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); @@ -1481,7 +1503,7 @@ private void DoRenameComputerWsman(string computer, string computerName, string Microsoft.Management.Infrastructure.CimType.String, (dPassword == null) ? CimFlags.NullValue : CimFlags.None)); - if ( ! InternalTestHooks.TestRenameComputer ) + if (!InternalTestHooks.TestRenameComputer) { CimMethodResult result = cimSession.InvokeMethod( ComputerWMIHelper.CimOperatingSystemNamespace, @@ -1501,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 @@ -1536,28 +1558,28 @@ private void DoRenameComputerWsman(string computer, string computerName, string WriteWarning(StringUtil.Format(ComputerResources.RestartNeeded, null, computerName)); } } - } // end foreach - } // end using + } + } } 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); } } -#endregion "Private Methods" + #endregion "Private Methods" -#region "Override Methods" + #region "Override Methods" /// /// ProcessRecord method. @@ -1565,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) @@ -1581,21 +1606,24 @@ protected override void ProcessRecord() } /// - /// EndProcessing method + /// EndProcessing method. /// protected override void EndProcessing() { - if (!_containsLocalHost) return; + if (!_containsLocalHost) + { + return; + } DoRenameComputerAction("localhost", _newNameForLocalHost, true); } -#endregion "Override Methods" + #endregion "Override Methods" } -#endregion Rename-Computer + #endregion Rename-Computer -#region "Public API" + #region "Public API" /// /// The object returned by SAM Computer cmdlets representing the status of the target machine. /// @@ -1604,7 +1632,7 @@ public sealed class ComputerChangeInfo private const string MatchFormat = "{0}:{1}"; /// - /// The HasSucceeded which shows the operation was success or not + /// The HasSucceeded which shows the operation was success or not. /// public bool HasSucceeded { get; set; } @@ -1628,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); } @@ -1672,14 +1700,14 @@ 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); } } -#endregion "Public API" + #endregion "Public API" -#region Helper + #region Helper /// /// Helper Class used by Stop-Computer,Restart-Computer and Test-Connection /// Also Contain constants used by System Restore related Cmdlets. @@ -1687,27 +1715,27 @@ private string FormatLine(string HasSucceeded, string newcomputername, string ol internal static class ComputerWMIHelper { /// - /// The maximum length of a valid NetBIOS name + /// The maximum length of a valid NetBIOS name. /// internal const int NetBIOSNameMaxLength = 15; /// - /// System Restore Class used by Cmdlets + /// System Restore Class used by Cmdlets. /// internal const string WMI_Class_SystemRestore = "SystemRestore"; /// - /// OperatingSystem WMI class used by Cmdlets + /// OperatingSystem WMI class used by Cmdlets. /// internal const string WMI_Class_OperatingSystem = "Win32_OperatingSystem"; /// - /// Service WMI class used by Cmdlets + /// Service WMI class used by Cmdlets. /// internal const string WMI_Class_Service = "Win32_Service"; /// - /// Win32_ComputerSystem WMI class used by Cmdlets + /// Win32_ComputerSystem WMI class used by Cmdlets. /// internal const string WMI_Class_ComputerSystem = "Win32_ComputerSystem"; @@ -1717,12 +1745,12 @@ internal static class ComputerWMIHelper internal const string WMI_Class_PingStatus = "Win32_PingStatus"; /// - /// CIMV2 path + /// CIMV2 path. /// internal const string WMI_Path_CIM = "\\root\\cimv2"; /// - /// Default path + /// Default path. /// internal const string WMI_Path_Default = "\\root\\default"; @@ -1737,43 +1765,42 @@ internal static class ComputerWMIHelper internal const int ErrorCode_Service = 1056; /// - /// The name of the privilege to shutdown a local system + /// The name of the privilege to shutdown a local system. /// internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; /// - /// The name of the privilege to shutdown a remote system + /// The name of the privilege to shutdown a remote system. /// internal const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege"; /// - /// CimUriPrefix + /// CimUriPrefix. /// internal const string CimUriPrefix = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2"; /// - /// CimOperatingSystemNamespace + /// CimOperatingSystemNamespace. /// internal const string CimOperatingSystemNamespace = "root/cimv2"; /// - /// CimOperatingSystemShutdownMethod + /// CimOperatingSystemShutdownMethod. /// internal const string CimOperatingSystemShutdownMethod = "Win32shutdown"; /// - /// CimQueryDialect + /// CimQueryDialect. /// internal const string CimQueryDialect = "WQL"; /// - /// Local host name + /// Local host name. /// internal const string localhostStr = "localhost"; - /// - /// Get the local admin user name from a local NetworkCredential + /// Get the local admin user name from a local NetworkCredential. /// /// /// @@ -1783,20 +1810,20 @@ 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; } else { - int dotIndex = computerName.IndexOf(".", StringComparison.OrdinalIgnoreCase); + int dotIndex = computerName.IndexOf('.'); if (dotIndex == -1) { localUserName = computerName + "\\" + psLocalCredential.UserName; } else { - localUserName = computerName.Substring(0, dotIndex) + "\\" + psLocalCredential.UserName; + localUserName = string.Concat(computerName.AsSpan(0, dotIndex), "\\", psLocalCredential.UserName); } } @@ -1804,7 +1831,7 @@ internal static string GetLocalAdminUserName(string computerName, PSCredential p } /// - /// Generate a random password + /// Generate a random password. /// /// /// @@ -1827,16 +1854,15 @@ internal static string GetRandomPassword(int passwordLength) } /// - /// Gets the Scope - /// + /// Gets the Scope. /// /// /// /// internal static string GetScopeString(string computer, string namespaceParameter) { - StringBuilder returnValue = new StringBuilder("\\\\"); - if (computer.Equals("::1", StringComparison.CurrentCultureIgnoreCase) || computer.Equals("[::1]", StringComparison.CurrentCultureIgnoreCase)) + StringBuilder returnValue = new("\\\\"); + if (computer.Equals("::1", StringComparison.OrdinalIgnoreCase) || computer.Equals("[::1]", StringComparison.OrdinalIgnoreCase)) { returnValue.Append("localhost"); } @@ -1844,6 +1870,7 @@ internal static string GetScopeString(string computer, string namespaceParameter { returnValue.Append(computer); } + returnValue.Append(namespaceParameter); return returnValue.ToString(); } @@ -1860,10 +1887,11 @@ internal static bool IsValidDrive(string drive) { if (logicalDrive.DriveType.Equals(DriveType.Fixed)) { - if (drive.ToString().Equals(logicalDrive.Name.ToString(), System.StringComparison.OrdinalIgnoreCase)) + if (drive.Equals(logicalDrive.Name, System.StringComparison.OrdinalIgnoreCase)) return true; } } + return false; } @@ -1878,20 +1906,21 @@ internal static bool ContainsSystemDrive(string[] drives, string sysdrive) string driveApp; foreach (string drive in drives) { - if (!drive.EndsWith("\\", StringComparison.CurrentCultureIgnoreCase)) + if (!drive.EndsWith('\\')) { - driveApp = String.Concat(drive, "\\"); + driveApp = string.Concat(drive, "\\"); } else driveApp = drive; - if (driveApp.Equals(sysdrive, StringComparison.CurrentCultureIgnoreCase)) + if (driveApp.Equals(sysdrive, StringComparison.OrdinalIgnoreCase)) return true; } + return false; } /// - /// Returns the given computernames in a string + /// Returns the given computernames in a string. /// /// internal static string GetMachineNames(string[] computerNames) @@ -1908,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) { @@ -1921,7 +1950,7 @@ internal static string GetMachineNames(string[] computerNames) i++; } - if ((computer.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase))) { compname = Dns.GetHostName(); } @@ -1938,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) { @@ -1948,12 +1977,13 @@ internal static ComputerChangeInfo GetComputerStatusObject(int errorcode, string { computerchangeinfo.HasSucceeded = true; } + return computerchangeinfo; } 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) @@ -1964,77 +1994,49 @@ internal static RenameComputerChangeInfo GetRenameComputerStatusObject(int error { renamecomputerchangeinfo.HasSucceeded = true; } + return renamecomputerchangeinfo; } - internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, string computername) { - Win32Exception ex = new Win32Exception(errorcode); - string additionalmessage = String.Empty; + Win32Exception ex = new(errorcode); + string additionalmessage = string.Empty; if (ex.NativeErrorCode.Equals(0x00000035)) { additionalmessage = StringUtil.Format(ComputerResources.NetworkPathNotFound, computername); } + 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); } /// - /// Check whether the new computer name is valid + /// Check whether the new computer name is valid. /// /// /// 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') - { - allDigits = false; - continue; - } - else if (t >= '0' && t <= '9') - { - continue; - } - else if (t == '-') + if (char.IsAsciiLetter(t) || t is '-') { - allDigits = false; - continue; + hasAsciiLetterOrHyphen = true; } - 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; } /// @@ -2042,16 +2044,16 @@ internal static bool SkipSystemRestoreOperationForARMPlatform(PSCmdlet cmdlet) /// over a CIMSession. The flags parameter determines the type of shutdown operation /// such as shutdown, reboot, force etc. /// - /// Cmdlet host for reporting errors - /// True if local host computer - /// Target computer - /// Win32Shutdown flags - /// Optional credential - /// Optional authentication - /// Error message format string that takes two parameters - /// Fully qualified error Id - /// Cancel token - /// True on success + /// Cmdlet host for reporting errors. + /// True if local host computer. + /// Target computer. + /// Win32Shutdown flags. + /// Optional credential. + /// Optional authentication. + /// Error message format string that takes two parameters. + /// Fully qualified error Id. + /// Cancel token. + /// True on success. internal static bool InvokeWin32ShutdownUsingWsman( PSCmdlet cmdlet, bool isLocalhost, @@ -2074,7 +2076,7 @@ internal static bool InvokeWin32ShutdownUsingWsman( { Timeout = TimeSpan.FromMilliseconds(10000), CancellationToken = cancelToken, - //This prefix works against all versions of the WinRM server stack, both win8 and win7 + // This prefix works against all versions of the WinRM server stack, both win8 and win7 ResourceUriPrefix = new Uri(ComputerWMIHelper.CimUriPrefix) }; @@ -2086,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, cancelToken, cmdlet)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(targetMachine, credInUse, authInUse, isLocalhost, cmdlet, cancelToken)) { var methodParameters = new CimMethodParametersCollection(); int retVal; @@ -2107,14 +2109,31 @@ internal static bool InvokeWin32ShutdownUsingWsman( Microsoft.Management.Infrastructure.CimType.SInt32, CimFlags.None)); - if ( ! InternalTestHooks.TestStopComputer ) + if (!InternalTestHooks.TestStopComputer) { - CimMethodResult result = cimSession.InvokeMethod( - ComputerWMIHelper.CimOperatingSystemNamespace, - ComputerWMIHelper.WMI_Class_OperatingSystem, - ComputerWMIHelper.CimOperatingSystemShutdownMethod, - methodParameters, - operationOptions); + CimMethodResult result = null; + + if (isLocalhost) + { + // Win32_ComputerSystem is a singleton hence FirstOrDefault() return the only instance returned by EnumerateInstances. + var computerSystem = cimSession.EnumerateInstances(ComputerWMIHelper.CimOperatingSystemNamespace, ComputerWMIHelper.WMI_Class_OperatingSystem).FirstOrDefault(); + + result = cimSession.InvokeMethod( + ComputerWMIHelper.CimOperatingSystemNamespace, + computerSystem, + ComputerWMIHelper.CimOperatingSystemShutdownMethod, + methodParameters, + operationOptions); + } + else + { + result = cimSession.InvokeMethod( + ComputerWMIHelper.CimOperatingSystemNamespace, + ComputerWMIHelper.WMI_Class_OperatingSystem, + ComputerWMIHelper.CimOperatingSystemShutdownMethod, + methodParameters, + operationOptions); + } retVal = Convert.ToInt32(result.ReturnValue.Value, CultureInfo.CurrentCulture); } @@ -2127,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); } @@ -2140,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); } @@ -2164,11 +2183,11 @@ internal static bool InvokeWin32ShutdownUsingWsman( /// /// Returns valid computer name or null on failure. /// - /// Computer name to validate + /// Computer name to validate. /// /// /// - /// Valid computer name + /// Valid computer name. internal static string ValidateComputerName( string nameToCheck, string shortLocalMachineName, @@ -2189,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) { @@ -2223,6 +2241,7 @@ internal static string ValidateComputerName( return null; } + validatedComputerName = nameToCheck; } } @@ -2230,8 +2249,7 @@ internal static string ValidateComputerName( return validatedComputerName; } } -#endregion Helper - -}//End namespace + #endregion Helper +} #endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs new file mode 100644 index 00000000000..22092116330 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if UNIX + +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 + + /// + /// Cmdlet to restart computer. + /// + [Cmdlet(VerbsLifecycle.Restart, "Computer", SupportsShouldProcess = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097060", RemotingCapability = RemotingCapability.SupportedByCommand)] + public sealed class RestartComputerCommand : CommandLineCmdletBase + { + // TODO: Support remote computers? + +#region "Overrides" + + /// + /// BeginProcessing. + /// + protected override void BeginProcessing() + { + if (InternalTestHooks.TestStopComputer) + { + var retVal = InternalTestHooks.TestStopComputerResults; + if (retVal != 0) + { + string errMsg = StringUtil.Format("Command returned 0x{0:X}", retVal); + ErrorRecord error = new ErrorRecord( + new InvalidOperationException(errMsg), "CommandFailed", ErrorCategory.OperationStopped, "localhost"); + WriteError(error); + } + return; + } + + RunShutdown("-r now"); + } +#endregion "Overrides" + } +#endregion Restart-Computer + +#region Stop-Computer + + /// + /// Cmdlet to stop computer. + /// + [Cmdlet(VerbsLifecycle.Stop, "Computer", SupportsShouldProcess = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097151", RemotingCapability = RemotingCapability.SupportedByCommand)] + public sealed class StopComputerCommand : CommandLineCmdletBase + { + // TODO: Support remote computers? + +#region "Overrides" + + /// + /// BeginProcessing. + /// + protected override void BeginProcessing() + { + var args = "-P now"; + if (Platform.IsMacOS) + { + args = "now"; + } + if (InternalTestHooks.TestStopComputer) + { + var retVal = InternalTestHooks.TestStopComputerResults; + if (retVal != 0) + { + string errMsg = StringUtil.Format("Command returned 0x{0:X}", retVal); + ErrorRecord error = new ErrorRecord( + new InvalidOperationException(errMsg), "CommandFailed", ErrorCategory.OperationStopped, "localhost"); + WriteError(error); + } + return; + } + + RunShutdown(args); + } +#endregion "Overrides" + } + + /// + /// A base class for cmdlets that can run shell commands. + /// + public class CommandLineCmdletBase : PSCmdlet, IDisposable + { +#region Private Members + private Process? _process = null; +#endregion + +#region "IDisposable Members" + + /// + /// Releases all resources used by the . + /// + public void 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" + +#region "Overrides" + /// + /// To implement ^C. + /// + protected override void StopProcessing() + { + if (_process == null) { + return; + } + + try { + if (!_process.HasExited) { + _process.Kill(); + } + WriteObject(_process.ExitCode); + } + catch (InvalidOperationException) {} + catch (NotSupportedException) {} + } +#endregion "Overrides" + +#region "Internals" + + private static string? shutdownPath; + + /// + /// Run shutdown command. + /// + 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 = 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 + } +#endregion +} +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs index 87a32563161..e7b3ada4ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -8,33 +7,38 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// The base class for the */content commands + /// The base class for the */content commands. /// public class ContentCommandBase : CoreCommandWithCredentialsBase, IDisposable { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipelineByPropertyName = true)] public string[] Path { get; set; } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return Path; } + get + { + return Path; + } + set { base.SuppressWildcardExpansion = true; @@ -43,39 +47,41 @@ public string[] LiteralPath } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { get { return base.Filter; } + set { base.Filter = value; } } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { get { return base.Include; } + set { base.Include = value; } } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { get { return base.Exclude; } + set { base.Exclude = value; } } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -85,11 +91,11 @@ public override string[] Exclude /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { get { return base.Force; } + set { base.Force = value; } } @@ -105,29 +111,23 @@ 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 + /// Wraps the content into a PSObject and adds context information as notes. /// - /// /// /// The content being written out. /// - /// /// /// The number of blocks that have been read so far. /// - /// /// /// The context the content was retrieved from. /// - /// /// /// The context the command is being run under. /// - /// internal void WriteContentObject(object content, long readCount, PathInfo pathInfo, CmdletProviderContext context) { Dbg.Diagnostics.Assert( @@ -153,13 +153,10 @@ internal void WriteContentObject(object content, long readCount, PathInfo pathIn if (_currentContentItem != null && ((_currentContentItem.PathInfo == pathInfo) || - ( - String.Compare( + string.Equals( pathInfo.Path, _currentContentItem.PathInfo.Path, - StringComparison.OrdinalIgnoreCase) == 0) - ) - ) + StringComparison.OrdinalIgnoreCase))) { result = _currentContentItem.AttachNotes(result); } @@ -188,8 +185,9 @@ internal void WriteContentObject(object content, long readCount, PathInfo pathIn } else { - parentPath = SessionState.Path.ParseParent(pathInfo.Path, String.Empty, context); + parentPath = SessionState.Path.ParseParent(pathInfo.Path, string.Empty, context); } + note = new PSNoteProperty("PSParentPath", parentPath); result.Properties.Add(note, true); tracer.WriteLine("Attaching {0} = {1}", "PSParentPath", parentPath); @@ -233,7 +231,7 @@ internal void WriteContentObject(object content, long readCount, PathInfo pathIn result.Properties.Add(note, true); WriteObject(result); - } // WriteContentObject + } /// /// A cache of the notes that get added to the content items as they are written @@ -246,16 +244,14 @@ 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. /// - /// /// /// The path information for which the cache will be bound. /// - /// public ContentPathsCache(PathInfo pathInfo) { PathInfo = pathInfo; @@ -264,56 +260,47 @@ public ContentPathsCache(PathInfo pathInfo) /// /// The path information for the cached item. /// - /// public PathInfo PathInfo { get; } /// /// The cached PSPath of the item. /// - /// - public String PSPath { get; set; } + public string PSPath { get; set; } /// /// The cached parent path of the item. /// - /// - public String ParentPath { get; set; } + public string ParentPath { get; set; } /// /// The cached drive for the item. /// - /// public PSDriveInfo Drive { get; set; } /// /// The cached provider of the item. /// - /// public ProviderInfo Provider { get; set; } /// /// The cached child name of the item. /// - /// - public String ChildName { get; set; } + public string ChildName { get; set; } /// /// Attaches the cached notes to the specified PSObject. /// - /// /// /// The PSObject to attached the cached notes to. /// - /// /// /// The PSObject that was passed in with the cached notes added. /// - /// 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); @@ -345,16 +332,14 @@ public PSObject AttachNotes(PSObject content) tracer.WriteLine("Attaching {0} = {1}", "PSProvider", Provider); return content; - } // AttachNotes - } // ContentPathsCache - + } + } /// /// 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, @@ -363,39 +348,36 @@ internal ContentHolder( { if (pathInfo == null) { - throw PSTraceSource.NewArgumentNullException("pathInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(pathInfo)); } PathInfo = pathInfo; Reader = reader; Writer = writer; - } // constructor + } internal PathInfo PathInfo { get; } internal IContentReader Reader { get; } internal IContentWriter Writer { get; } - } // struct ContentHolder + } /// - /// Closes the content readers and writers in the content holder array + /// Closes the content readers and writers in the content holder array. /// internal void CloseContent(List contentHolders, bool disposing) { if (contentHolders == null) { - throw PSTraceSource.NewArgumentNullException("contentHolders"); + throw PSTraceSource.NewArgumentNullException(nameof(contentHolders)); } foreach (ContentHolder holder in contentHolders) { try { - if (holder.Writer != null) - { - holder.Writer.Close(); - } + holder.Writer?.Close(); } catch (Exception e) // Catch-all OK. 3rd party callout { @@ -403,14 +385,13 @@ internal void CloseContent(List contentHolders, bool disposing) // and write out an error. ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentCloseError", SessionStateStrings.ProviderContentCloseError, holder.PathInfo.Provider, holder.PathInfo.Path, e); - // Log a provider health event MshLog.LogProviderHealthEvent( @@ -430,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 { @@ -441,14 +419,13 @@ internal void CloseContent(List contentHolders, bool disposing) // and write out an error. ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentCloseError", SessionStateStrings.ProviderContentCloseError, holder.PathInfo.Provider, holder.PathInfo.Path, e); - // Log a provider health event MshLog.LogProviderHealthEvent( @@ -466,22 +443,19 @@ internal void CloseContent(List contentHolders, bool disposing) } } } - } // CloseContent + } /// /// Overridden by derived classes to support ShouldProcess with /// the appropriate information. /// - /// /// /// The path to the item from which the content writer will be /// retrieved. /// - /// /// /// True if the action should continue or false otherwise. /// - /// internal virtual bool CallShouldProcess(string path) { return true; @@ -490,11 +464,9 @@ internal virtual bool CallShouldProcess(string path) /// /// Gets the IContentReaders for the current path(s) /// - /// /// /// An array of IContentReaders for the current path(s) /// - /// internal List GetContentReaders( string[] readerPaths, CmdletProviderContext currentCommandContext) @@ -505,7 +477,7 @@ internal List GetContentReaders( // Create the results array - List results = new List(); + List results = new(); foreach (PathInfo pathInfo in pathInfos) { @@ -563,48 +535,42 @@ 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); } } - } // foreach pathInfo in pathInfos + } return results; - } // GetContentReaders + } /// - /// Resolves the specified paths to PathInfo objects + /// Resolves the specified paths to PathInfo objects. /// - /// /// /// The paths to be resolved. Each path may contain glob characters. /// - /// /// /// If true, resolves the path even if it doesn't exist. /// - /// /// /// If true, allows a wildcard that returns no results. /// - /// /// /// The context under which the command is running. /// - /// /// /// An array of PathInfo objects that are the resolved paths for the /// parameter. /// - /// internal Collection ResolvePaths( string[] pathsToResolve, bool allowNonexistingPaths, bool allowEmptyResult, CmdletProviderContext currentCommandContext) { - Collection results = new Collection(); + Collection results = new(); foreach (string path in pathsToResolve) { @@ -684,7 +650,7 @@ internal Collection ResolvePaths( out drive); PathInfo pathInfo = - new PathInfo( + new( drive, provider, unresolvedPath, @@ -696,8 +662,8 @@ internal Collection ResolvePaths( if (pathNotFoundErrorRecord == null) { // Detect if the path resolution failed to resolve to a file. - String error = StringUtil.Format(NavigationResources.ItemNotFound, Path); - Exception e = new Exception(error); + string error = StringUtil.Format(NavigationResources.ItemNotFound, Path); + Exception e = new(error); pathNotFoundErrorRecord = new ErrorRecord( e, @@ -712,7 +678,7 @@ internal Collection ResolvePaths( } return results; - } // ResolvePaths + } #endregion protected members @@ -728,7 +694,7 @@ internal void Dispose(bool isDisposing) } /// - /// Dispose method in IDisposable + /// Dispose method in IDisposable. /// public void Dispose() { @@ -736,14 +702,6 @@ public void Dispose() GC.SuppressFinalize(this); } - /// - /// Finalizer - /// - ~ContentCommandBase() - { - Dispose(false); - } #endregion IDisposable - - } // ContentCommandBase -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs index 5950e80b0f4..3734eb82b0c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -1,52 +1,53 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// 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.Globalization; -using Microsoft.Win32; -using System; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Collections.Generic; + +using Microsoft.Win32; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// - /// Represent a control panel item + /// Represent a control panel item. /// public sealed class ControlPanelItem { /// - /// Control panel applet name + /// Control panel applet name. /// public string Name { get; } /// - /// Control panel applet canonical name + /// Control panel applet canonical name. /// public string CanonicalName { get; } /// - /// Control panel applet category + /// Control panel applet category. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Category { get; } /// - /// Control panel applet description + /// Control panel applet description. /// public string Description { get; } /// - /// Control panel applet path + /// Control panel applet path. /// internal string Path { get; } /// - /// Internal constructor for ControlPanelItem + /// Internal constructor for ControlPanelItem. /// /// /// @@ -63,7 +64,7 @@ internal ControlPanelItem(string name, string canonicalName, string[] category, } /// - /// ToString method + /// ToString method. /// /// public override string ToString() @@ -73,7 +74,7 @@ public override string ToString() } /// - /// This class implements the base for ControlPanelItem commands + /// This class implements the base for ControlPanelItem commands. /// public abstract class ControlPanelItemBaseCommand : PSCmdlet { @@ -112,7 +113,7 @@ public abstract class ControlPanelItemBaseCommand : PSCmdlet internal ControlPanelItem[] ControlPanelItems = new ControlPanelItem[0]; /// - /// Get all executable control panel items + /// Get all executable control panel items. /// internal List AllControlPanelItems { @@ -141,6 +142,7 @@ internal List AllControlPanelItems break; } } + if (match) continue; } @@ -149,15 +151,17 @@ internal List AllControlPanelItems _allControlPanelItems.Add(item); } } + return _allControlPanelItems; } } + private List _allControlPanelItems; #region Cmdlet Overrides /// - /// Does the preprocessing for ControlPanelItem cmdlets + /// Does the preprocessing for ControlPanelItem cmdlets. /// protected override void BeginProcessing() { @@ -182,7 +186,7 @@ protected override void BeginProcessing() #endregion /// - /// Test if an item can be invoked + /// Test if an item can be invoked. /// /// /// @@ -192,7 +196,7 @@ private bool ContainVerbOpen(ShellFolderItem item) FolderItemVerbs verbs = item.Verbs(); foreach (FolderItemVerb verb in verbs) { - if (!String.IsNullOrEmpty(verb.Name) && + if (!string.IsNullOrEmpty(verb.Name) && (verb.Name.Equals(ControlPanelResources.VerbActionOpen, StringComparison.OrdinalIgnoreCase) || CompareVerbActionOpen(verb.Name))) { @@ -200,6 +204,7 @@ private bool ContainVerbOpen(ShellFolderItem item) break; } } + return result; } @@ -221,8 +226,8 @@ private static bool CompareVerbActionOpen(string verbActionName) foreach (ShellFolderItem item in allItems) { string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); - canonicalName = !String.IsNullOrEmpty(canonicalName) - ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + canonicalName = !string.IsNullOrEmpty(canonicalName) + ? canonicalName.Substring(0, canonicalName.IndexOf('\0')) : null; if (canonicalName != null && canonicalName.Equals(RegionCanonicalName, StringComparison.OrdinalIgnoreCase)) @@ -254,7 +259,7 @@ private bool IsServerCoreOrHeadLessServer() { Dbg.Assert(installation != null, "the CurrentVersion subkey should exist"); - string installationType = (string)installation.GetValue("InstallationType", ""); + string installationType = (string)installation.GetValue("InstallationType", string.Empty); if (installationType.Equals("Server Core")) { @@ -265,7 +270,7 @@ private bool IsServerCoreOrHeadLessServer() using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) { ps.AddScript(TestHeadlessServerScript); - Collection psObjectCollection = ps.Invoke(new object[0]); + Collection psObjectCollection = ps.Invoke(Array.Empty()); Dbg.Assert(psObjectCollection != null && psObjectCollection.Count == 1, "invoke should never return null, there should be only one return item"); if (LanguagePrimitives.IsTrue(PSObject.Base(psObjectCollection[0]))) { @@ -279,7 +284,7 @@ private bool IsServerCoreOrHeadLessServer() } /// - /// Get the category number and name map + /// Get the category number and name map. /// internal void GetCategoryMap() { @@ -295,14 +300,14 @@ 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); } } /// - /// Get control panel item by the category + /// Get control panel item by the category. /// /// /// @@ -354,7 +359,7 @@ internal List GetControlPanelItemByCategory(List - /// Get control panel item by the regular name + /// Get control panel item by the regular name. /// /// /// @@ -402,7 +407,7 @@ internal List GetControlPanelItemByName(List c } /// - /// Get control panel item by the canonical name + /// Get control panel item by the canonical name. /// /// /// @@ -430,10 +435,11 @@ internal List GetControlPanelItemByCanonicalName(List GetControlPanelItemByCanonicalName(List GetControlPanelItemByCanonicalName(List - /// Get control panel item by the ControlPanelItem instances + /// Get control panel item by the ControlPanelItem instances. /// /// /// @@ -540,7 +546,7 @@ internal List GetControlPanelItemsByInstance(List - /// Get all control panel items that is available in the "All Control Panel Items" category + /// Get all control panel items that is available in the "All Control Panel Items" category. /// [Cmdlet(VerbsCommon.Get, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=219982")] [OutputType(typeof(ControlPanelItem))] @@ -552,7 +558,7 @@ public sealed class GetControlPanelItemCommand : ControlPanelItemBaseCommand #region "Parameters" /// - /// Control panel item names + /// Control panel item names. /// [Parameter(Position = 0, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] @@ -560,16 +566,18 @@ public sealed class GetControlPanelItemCommand : ControlPanelItemBaseCommand public string[] Name { get { return RegularNames; } + set { RegularNames = value; _nameSpecified = true; } } + private bool _nameSpecified = false; /// - /// Canonical names of control panel items + /// Canonical names of control panel items. /// [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] [AllowNull] @@ -577,16 +585,18 @@ public string[] Name public string[] CanonicalName { get { return CanonicalNames; } + set { CanonicalNames = value; _canonicalNameSpecified = true; } } + private bool _canonicalNameSpecified = false; /// - /// Category of control panel items + /// Category of control panel items. /// [Parameter] [ValidateNotNullOrEmpty] @@ -594,18 +604,19 @@ public string[] CanonicalName public string[] Category { get { return CategoryNames; } + set { CategoryNames = value; _categorySpecified = true; } } + private bool _categorySpecified = false; #endregion "Parameters" /// - /// /// protected override void ProcessRecord() { @@ -629,7 +640,7 @@ protected override void ProcessRecord() string description = (string)item.ExtendedProperty("InfoTip"); string canonicalName = (string)item.ExtendedProperty("System.ApplicationName"); canonicalName = canonicalName != null - ? canonicalName.Substring(0, canonicalName.IndexOf("\0", StringComparison.OrdinalIgnoreCase)) + ? canonicalName.Substring(0, canonicalName.IndexOf('\0')) : null; int[] categories = (int[])item.ExtendedProperty("System.ControlPanel.Category"); string[] cateStrings = new string[categories.Length]; @@ -672,7 +683,7 @@ private static int CompareControlPanelItems(ControlPanelItem x, ControlPanelItem } /// - /// Show the specified control panel applet + /// Show the specified control panel applet. /// [Cmdlet(VerbsCommon.Show, "ControlPanelItem", DefaultParameterSetName = RegularNameParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=219983")] public sealed class ShowControlPanelItemCommand : ControlPanelItemBaseCommand @@ -684,7 +695,7 @@ public sealed class ShowControlPanelItemCommand : ControlPanelItemBaseCommand #region "Parameters" /// - /// Control panel item names + /// Control panel item names. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = RegularNameParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] @@ -692,11 +703,12 @@ public sealed class ShowControlPanelItemCommand : ControlPanelItemBaseCommand public string[] Name { get { return RegularNames; } + set { RegularNames = value; } } /// - /// Canonical names of control panel items + /// Canonical names of control panel items. /// [Parameter(Mandatory = true, ParameterSetName = CanonicalNameParameterSet)] [AllowNull] @@ -704,11 +716,12 @@ public string[] Name public string[] CanonicalName { get { return CanonicalNames; } + set { CanonicalNames = value; } } /// - /// Control panel items returned by Get-ControlPanelItem + /// Control panel items returned by Get-ControlPanelItem. /// [Parameter(Position = 0, ParameterSetName = ControlPanelItemParameterSet, ValueFromPipeline = true)] [ValidateNotNullOrEmpty] @@ -716,13 +729,13 @@ public string[] CanonicalName public ControlPanelItem[] InputObject { get { return ControlPanelItems; } + set { ControlPanelItems = value; } } #endregion "Parameters" /// - /// /// protected override void ProcessRecord() { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs index ef093e91071..33796b23378 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs @@ -1,10 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -13,14 +11,14 @@ namespace Microsoft.PowerShell.Commands /// a provider internal path. /// [Cmdlet(VerbsData.Convert, "Path", DefaultParameterSetName = "Path", SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113289", RemotingCapability = RemotingCapability.None)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096588", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(string))] public class ConvertPathCommand : CoreCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -29,40 +27,50 @@ public string[] Path get { return _paths; - } // get + } set { _paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return _paths; - } // get + } set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } + + /// + /// Gets or sets the force property. + /// + [Parameter] + public override SwitchParameter Force + { + get => base.Force; + set => base.Force = value; + } #endregion Parameters #region parameter data /// - /// The path(s) to the item(s) to convert + /// The path(s) to the item(s) to convert. /// private string[] _paths; @@ -123,10 +131,8 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - - } // ConvertPathCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs index a5392a7cafe..ff13448bc09 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -11,31 +9,36 @@ namespace Microsoft.PowerShell.Commands /// A command to copy a property on an item. /// [Cmdlet(VerbsCommon.Copy, "ItemProperty", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113293")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096589")] public class CopyItemPropertyCommand : PassThroughItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { get { return paths; } + set { paths = value; } } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } + set { base.SuppressWildcardExpansion = true; @@ -44,9 +47,8 @@ public string[] LiteralPath } /// - /// The name of the property to create on the item + /// The name of the property to create on the item. /// - /// [Parameter(Position = 2, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] public string Name { get; set; } @@ -54,7 +56,6 @@ public string[] LiteralPath /// /// The path to the destination item to copy the property to. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] public string Destination { get; set; } @@ -63,16 +64,13 @@ public string[] LiteralPath /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) @@ -84,13 +82,14 @@ internal override object GetDynamicParameters(CmdletProviderContext context) Name, context); } + return InvokeProvider.Property.CopyPropertyDynamicParameters( ".", Name, Destination, Name, context); - } // GetDynamicParameters + } #endregion Parameters @@ -101,7 +100,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #region Command code /// - /// Copies the property from one item to another + /// Copies the property from one item to another. /// protected override void ProcessRecord() { @@ -149,9 +148,8 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - - } // CopyItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs index 86194f41fe5..c667116fdd0 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -15,7 +14,7 @@ namespace Microsoft.PowerShell.Commands { #region GetEventLogCommand /// - /// This class implements the Get-EventLog command + /// This class implements the Get-EventLog command. /// /// /// The CLR EventLogEntryCollection class has problems with managing @@ -35,28 +34,28 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet(VerbsCommon.Get, "EventLog", DefaultParameterSetName = "LogName", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113314", RemotingCapability = RemotingCapability.SupportedByCommand)] - [OutputType(typeof(EventLog), typeof(EventLogEntry), typeof(String))] + [OutputType(typeof(EventLog), typeof(EventLogEntry), typeof(string))] public sealed class GetEventLogCommand : PSCmdlet { #region Parameters /// - /// Read eventlog entries from this log + /// Read eventlog entries from this log. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "LogName")] [Alias("LN")] public string LogName { get; set; } /// - /// Read eventlog entries from this computer + /// Read eventlog entries from this computer. /// - [Parameter()] - [ValidateNotNullOrEmpty()] + [Parameter] + [ValidateNotNullOrEmpty] [Alias("Cn")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] ComputerName { get; set; } = new string[0]; + public string[] ComputerName { get; set; } = Array.Empty(); /// - /// Read only this number of entries + /// Read only this number of entries. /// [Parameter(ParameterSetName = "LogName")] [ValidateRange(0, Int32.MaxValue)] @@ -70,6 +69,7 @@ public sealed class GetEventLogCommand : PSCmdlet public DateTime After { get { return _after; } + set { _after = value; @@ -77,6 +77,7 @@ public DateTime After _isFilterSpecified = true; } } + private DateTime _after; /// @@ -87,6 +88,7 @@ public DateTime After public DateTime Before { get { return _before; } + set { _before = value; @@ -94,6 +96,7 @@ public DateTime Before _isFilterSpecified = true; } } + private DateTime _before; /// @@ -102,101 +105,109 @@ public DateTime Before [Parameter(ParameterSetName = "LogName")] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] UserName + public string[] UserName { get { return _username; } + set { _username = value; _isFilterSpecified = true; } } - private String[] _username; + + private string[] _username; /// - /// match eventlog entries by the InstanceIds - /// gets or sets an array of instanceIds + /// Match eventlog entries by the InstanceIds + /// 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 { get { return _instanceIds; } + set { _instanceIds = value; _isFilterSpecified = true; } } - private long[] _instanceIds = null; + private long[] _instanceIds = null; /// - /// match eventlog entries by the Index - /// gets or sets an array of indexes + /// Match eventlog entries by the Index + /// 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 { get { return _indexes; } + set { _indexes = value; _isFilterSpecified = true; } } - private int[] _indexes = null; + private int[] _indexes = null; /// - /// match eventlog entries by the EntryType - /// gets or sets an array of EntryTypes + /// Match eventlog entries by the EntryType + /// 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")] public string[] EntryType { get { return _entryTypes; } + set { _entryTypes = value; _isFilterSpecified = true; } } + private string[] _entryTypes = null; /// - /// get or sets an array of Source + /// Get or sets an array of Source. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("ABO")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Source { get { return _sources; } + set { _sources = value; _isFilterSpecified = true; } } + private string[] _sources; /// - /// Get or Set Message string to searched in EventLog + /// Get or Set Message string to searched in EventLog. /// [Parameter(ParameterSetName = "LogName")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("MSG")] public string Message { @@ -204,29 +215,30 @@ public string Message { return _message; } + set { _message = value; _isFilterSpecified = true; } } + private string _message; /// - /// returns Log Entry as base object + /// Returns Log Entry as base object. /// [Parameter(ParameterSetName = "LogName")] public SwitchParameter AsBaseObject { get; set; } /// - /// Return the Eventlog objects rather than the log contents + /// Return the Eventlog objects rather than the log contents. /// [Parameter(ParameterSetName = "List")] public SwitchParameter List { get; set; } - /// - /// Return the log names rather than the EventLog objects + /// Return the log names rather than the EventLog objects. /// [Parameter(ParameterSetName = "List")] public SwitchParameter AsString @@ -235,25 +247,27 @@ public SwitchParameter AsString { return _asString; } + set { _asString = value; } } + private bool _asString /* = false */; #endregion Parameters #region Overrides /// - /// Sets true when Filter is Specified + /// Sets true when Filter is Specified. /// private bool _isFilterSpecified = false; private bool _isDateSpecified = false; private bool _isThrowError = true; /// - /// Process the specified logs + /// Process the specified logs. /// protected override void BeginProcessing() { @@ -311,7 +325,7 @@ protected override void BeginProcessing() } } } - } // ProcessRecord + } #endregion Overrides #region Private @@ -346,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", @@ -407,7 +425,8 @@ private void Process(EventLog log) + ": " + e.Message); throw; } - if ((null != entry) && + + if ((entry != null) && ((lastindex == Int32.MinValue || lastindex - entry.Index == 1))) { @@ -417,11 +436,12 @@ private void Process(EventLog log) if (!FiltersMatch(entry)) continue; } + if (!AsBaseObject) { - //wrapping in PSobject to insert into PStypesnames + // wrapping in PSobject to insert into PStypesnames PSObject logentry = new PSObject(entry); - //inserting at zero position in reverse order + // inserting at zero position in reverse order logentry.TypeNames.Insert(0, logentry.ImmediateBaseObject + "#" + log.Log + "/" + entry.Source); logentry.TypeNames.Insert(0, logentry.ImmediateBaseObject + "#" + log.Log + "/" + entry.Source + "/" + entry.InstanceId); WriteObject(logentry); @@ -432,18 +452,18 @@ private void Process(EventLog log) WriteObject(entry); matchesfound = true; } + processed++; } } + if (!matchesfound && _isThrowError) { - Exception Ex = new ArgumentException(StringUtil.Format(EventlogResources.NoEntriesFound, log.Log, "")); + Exception Ex = new ArgumentException(StringUtil.Format(EventlogResources.NoEntriesFound, log.Log, string.Empty)); WriteError(new ErrorRecord(Ex, "GetEventLogNoEntriesFound", ErrorCategory.ObjectNotFound, null)); } } - - private bool FiltersMatch(EventLogEntry entry) { if (_indexes != null) @@ -453,6 +473,7 @@ private bool FiltersMatch(EventLogEntry entry) return false; } } + if (_instanceIds != null) { if (!((IList)_instanceIds).Contains(entry.InstanceId)) @@ -460,19 +481,25 @@ private bool FiltersMatch(EventLogEntry entry) return false; } } + if (_entryTypes != null) { bool entrymatch = false; foreach (string type in _entryTypes) { - if (type.Equals(entry.EntryType.ToString(), StringComparison.CurrentCultureIgnoreCase)) + if (type.Equals(entry.EntryType.ToString(), StringComparison.OrdinalIgnoreCase)) { entrymatch = true; break; } } - if (!entrymatch) return entrymatch; + + if (!entrymatch) + { + return entrymatch; + } } + if (_sources != null) { bool sourcematch = false; @@ -482,6 +509,7 @@ private bool FiltersMatch(EventLogEntry entry) { _isThrowError = false; } + WildcardPattern wildcardpattern = WildcardPattern.Get(source, WildcardOptions.IgnoreCase); if (wildcardpattern.IsMatch(entry.Source)) { @@ -489,20 +517,27 @@ private bool FiltersMatch(EventLogEntry entry) break; } } - if (!sourcematch) return sourcematch; + + if (!sourcematch) + { + return sourcematch; + } } + if (_message != null) { if (WildcardPattern.ContainsWildcardCharacters(_message)) { _isThrowError = false; } + WildcardPattern wildcardpattern = WildcardPattern.Get(_message, WildcardOptions.IgnoreCase); if (!wildcardpattern.IsMatch(entry.Message)) { return false; } } + if (_username != null) { bool usernamematch = false; @@ -519,8 +554,13 @@ private bool FiltersMatch(EventLogEntry entry) } } } - if (!usernamematch) return usernamematch; + + if (!usernamematch) + { + return usernamematch; + } } + if (_isDateSpecified) { _isThrowError = false; @@ -554,10 +594,16 @@ private bool FiltersMatch(EventLogEntry entry) } } } - if (!datematch) return datematch; + + if (!datematch) + { + return datematch; + } } + return true; } + private List GetMatchingLogs(string pattern) { WildcardPattern wildcardPattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); @@ -588,16 +634,16 @@ private List GetMatchingLogs(string pattern) return matchingLogs; } - //private string ErrorBase = "EventlogResources"; + // private string ErrorBase = "EventlogResources"; private DateTime _initial = new DateTime(); #endregion Private - }//GetEventLogCommand + } #endregion GetEventLogCommand #region ClearEventLogCommand /// - /// This class implements the Clear-EventLog command + /// This class implements the Clear-EventLog command. /// [Cmdlet(VerbsCommon.Clear, "EventLog", SupportsShouldProcess = true, @@ -614,7 +660,6 @@ public sealed class ClearEventLogCommand : PSCmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LogName { get; set; } - /// /// Clear eventlog entries from these Computers. /// @@ -629,14 +674,14 @@ public sealed class ClearEventLogCommand : PSCmdlet #region Overrides /// - /// Does the processing + /// Does the processing. /// protected override void BeginProcessing() { string computer = string.Empty; foreach (string compName in ComputerName) { - if ((compName.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (compName.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((compName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (compName.Equals(".", StringComparison.OrdinalIgnoreCase))) { computer = "localhost"; } @@ -644,6 +689,7 @@ protected override void BeginProcessing() { computer = compName; } + foreach (string eventString in LogName) { try @@ -654,10 +700,12 @@ protected override void BeginProcessing() WriteError(er); continue; } + if (!ShouldProcess(StringUtil.Format(EventlogResources.ClearEventLogWarning, eventString, computer))) { continue; } + EventLog Log = new EventLog(eventString, compName); Log.Clear(); } @@ -683,15 +731,15 @@ protected override void BeginProcessing() } } - //beginprocessing + // beginprocessing #endregion Overrides - }//ClearEventLogCommand + } #endregion ClearEventLogCommand #region WriteEventLogCommand /// - /// This class implements the Write-EventLog command + /// This class implements the Write-EventLog command. /// [Cmdlet(VerbsCommunications.Write, "EventLog", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135281", RemotingCapability = RemotingCapability.SupportedByCommand)] @@ -699,14 +747,13 @@ public sealed class WriteEventLogCommand : PSCmdlet { #region Parameters /// - /// Write eventlog entries in this log + /// Write eventlog entries in this log. /// [Parameter(Position = 0, Mandatory = true)] [Alias("LN")] [ValidateNotNullOrEmpty] public string LogName { get; set; } - /// /// The source by which the application is registered on the specified computer. /// @@ -748,9 +795,8 @@ public sealed class WriteEventLogCommand : PSCmdlet [ValidateLength(0, 32766)] public string Message { get; set; } - /// - /// Write eventlog entries of this log + /// Write eventlog entries of this log. /// [Parameter] [Alias("RD")] @@ -759,7 +805,7 @@ public sealed class WriteEventLogCommand : PSCmdlet public byte[] RawData { get; set; } /// - /// Write eventlog entries of this log + /// Write eventlog entries of this log. /// [Parameter] [Alias("CN")] @@ -768,7 +814,7 @@ public sealed class WriteEventLogCommand : PSCmdlet public string ComputerName { get; set; } = "."; #endregion Parameters - # region private + #region private private void WriteNonTerminatingError(Exception exception, string errorId, string errorMessage, ErrorCategory category) @@ -781,12 +827,12 @@ private void WriteNonTerminatingError(Exception exception, string errorId, strin #region Overrides /// - /// Does the processing + /// Does the processing. /// protected override void BeginProcessing() { string _computerName = string.Empty; - if ((ComputerName.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (ComputerName.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((ComputerName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (ComputerName.Equals(".", StringComparison.OrdinalIgnoreCase))) { _computerName = "localhost"; } @@ -794,6 +840,7 @@ protected override void BeginProcessing() { _computerName = ComputerName; } + try { if (!(EventLog.SourceExists(Source, ComputerName))) @@ -831,15 +878,15 @@ protected override void BeginProcessing() { WriteNonTerminatingError(ex, "PathDoesNotExist", StringUtil.Format(EventlogResources.PathDoesNotExist, null, ComputerName, null), ErrorCategory.InvalidOperation); } - }//beginprocessing + } #endregion Overrides - }//WriteEventLogCommand + } #endregion WriteEventLogCommand #region LimitEventLogCommand /// - /// This class implements the Limit-EventLog command + /// This class implements the Limit-EventLog command. /// [Cmdlet(VerbsData.Limit, "EventLog", SupportsShouldProcess = true, @@ -875,12 +922,14 @@ public sealed class LimitEventLogCommand : PSCmdlet public Int32 RetentionDays { get { return _retention; } + set { _retention = value; _retentionSpecified = true; } } + private Int32 _retention; private bool _retentionSpecified = false; /// @@ -894,12 +943,14 @@ public Int32 RetentionDays public System.Diagnostics.OverflowAction OverflowAction { get { return _overflowaction; } + set { _overflowaction = value; _overflowSpecified = true; } } + private System.Diagnostics.OverflowAction _overflowaction; private bool _overflowSpecified = false; /// @@ -910,17 +961,19 @@ public System.Diagnostics.OverflowAction OverflowAction public Int64 MaximumSize { get { return _maximumKilobytes; } + set { _maximumKilobytes = value; _maxkbSpecified = true; } } + private Int64 _maximumKilobytes; private bool _maxkbSpecified = false; #endregion Parameters - # region private + #region private private void WriteNonTerminatingError(Exception exception, string resourceId, string errorId, ErrorCategory category, string _logName, string _compName) { @@ -933,7 +986,7 @@ private void WriteNonTerminatingError(Exception exception, string resourceId, st #region Overrides /// - /// Does the processing + /// Does the processing. /// protected override void @@ -942,7 +995,7 @@ protected override string computer = string.Empty; foreach (string compname in ComputerName) { - if ((compname.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (compname.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((compname.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (compname.Equals(".", StringComparison.OrdinalIgnoreCase))) { computer = "localhost"; } @@ -950,6 +1003,7 @@ protected override { computer = compname; } + foreach (string logname in LogName) { try @@ -1001,6 +1055,7 @@ protected override { newLog.ModifyOverflowPolicy(_overflowaction, _minRetention); } + if (_maxkbSpecified) { int kiloByte = 1024; @@ -1030,19 +1085,20 @@ protected override { WriteNonTerminatingError(ex, EventlogResources.ValueOutofRange, "ValueOutofRange", ErrorCategory.InvalidData, null, null); } + continue; } } } } - # endregion override + #endregion override } #endregion LimitEventLogCommand #region ShowEventLogCommand /// - /// This class implements the Show-EventLog command + /// This class implements the Show-EventLog command. /// [Cmdlet(VerbsCommon.Show, "EventLog", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135257", RemotingCapability = RemotingCapability.SupportedByCommand)] @@ -1051,7 +1107,7 @@ public sealed class ShowEventLogCommand : PSCmdlet #region Parameters /// - /// show eventviewer of this computer. + /// Show eventviewer of this computer. /// [Parameter(Position = 0)] [Alias("CN")] @@ -1064,7 +1120,7 @@ public sealed class ShowEventLogCommand : PSCmdlet #region Overrides /// - /// Does the processing + /// Does the processing. /// protected override void @@ -1097,11 +1153,11 @@ protected override WriteError(er); } } - # endregion override + #endregion override } #endregion ShowEventLogCommand - # region NewEventLogCommand + #region NewEventLogCommand /// /// This cmdlet creates the new event log .This cmdlet can also be used to /// configure a new source for writing entries to an event log on the local @@ -1125,75 +1181,74 @@ protected override [Cmdlet(VerbsCommon.New, "EventLog", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135235", RemotingCapability = RemotingCapability.SupportedByCommand)] public class NewEventLogCommand : PSCmdlet { - # region Parameter + #region Parameter /// /// The following is the definition of the input parameter "CategoryResourceFile". /// Specifies the path of the resource file that contains category strings for /// the source - /// Resource File is expected to be present in Local/Remote Machines + /// Resource File is expected to be present in Local/Remote Machines. /// [Parameter] [ValidateNotNullOrEmpty] [Alias("CRF")] - public String CategoryResourceFile { get; set; } + public string CategoryResourceFile { get; set; } /// /// The following is the definition of the input parameter "ComputerName". - /// Specify the Computer Name. The default is local computer + /// Specify the Computer Name. The default is local computer. /// [Parameter(Position = 2)] [ValidateNotNullOrEmpty] [Alias("CN")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName { get; set; } = { "." }; + public string[] ComputerName { get; set; } = { "." }; /// /// The following is the definition of the input parameter "LogName". - /// Specifies the name of the log - /// + /// Specifies the name of the log. /// [Parameter(Mandatory = true, Position = 0)] [ValidateNotNullOrEmpty] [Alias("LN")] - public String LogName { get; set; } + public string LogName { get; set; } /// /// The following is the definition of the input parameter "MessageResourceFile". /// Specifies the path of the message resource file that contains message /// formatting strings for the source - /// Resource File is expected to be present in Local/Remote Machines + /// Resource File is expected to be present in Local/Remote Machines. /// [Parameter] [ValidateNotNullOrEmpty] [Alias("MRF")] - public String MessageResourceFile { get; set; } + public string MessageResourceFile { get; set; } /// /// The following is the definition of the input parameter "ParameterResourceFile". /// Specifies the path of the resource file that contains message parameter /// strings for the source - /// Resource File is expected to be present in Local/Remote Machines + /// Resource File is expected to be present in Local/Remote Machines. /// [Parameter] [ValidateNotNullOrEmpty] [Alias("PRF")] - public String ParameterResourceFile { get; set; } + public string ParameterResourceFile { get; set; } /// /// The following is the definition of the input parameter "Source". - /// Specifies the Source of the EventLog + /// Specifies the Source of the EventLog. /// [Parameter(Mandatory = true, Position = 1)] [ValidateNotNullOrEmpty] [Alias("SRC")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Source { get; set; } + public string[] Source { get; set; } - # endregion Parameter + #endregion Parameter - # region private + #region private private void WriteNonTerminatingError(Exception exception, string resourceId, string errorId, ErrorCategory category, string _logName, string _compName, string _source, string _resourceFile) { @@ -1201,9 +1256,9 @@ private void WriteNonTerminatingError(Exception exception, string resourceId, st WriteError(new ErrorRecord(ex, errorId, category, null)); } - # endregion private + #endregion private - # region override + #region override /// /// BeginProcessing method. /// @@ -1212,7 +1267,7 @@ protected override void BeginProcessing() string computer = string.Empty; foreach (string compname in ComputerName) { - if ((compname.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (compname.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((compname.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (compname.Equals(".", StringComparison.OrdinalIgnoreCase))) { computer = "localhost"; } @@ -1220,6 +1275,7 @@ protected override void BeginProcessing() { computer = compname; } + try { foreach (string _sourceName in Source) @@ -1228,11 +1284,11 @@ protected override void BeginProcessing() { EventSourceCreationData newEventSource = new EventSourceCreationData(_sourceName, LogName); newEventSource.MachineName = compname; - if (!String.IsNullOrEmpty(MessageResourceFile)) + if (!string.IsNullOrEmpty(MessageResourceFile)) newEventSource.MessageResourceFile = MessageResourceFile; - if (!String.IsNullOrEmpty(ParameterResourceFile)) + if (!string.IsNullOrEmpty(ParameterResourceFile)) newEventSource.ParameterResourceFile = ParameterResourceFile; - if (!String.IsNullOrEmpty(CategoryResourceFile)) + if (!string.IsNullOrEmpty(CategoryResourceFile)) newEventSource.CategoryResourceFile = CategoryResourceFile; EventLog.CreateEventSource(newEventSource); } @@ -1262,9 +1318,9 @@ protected override void BeginProcessing() } } } - //End BeginProcessing() + // End BeginProcessing() #endregion override - }//End Class + } #endregion NewEventLogCommand #region RemoveEventLogCommand @@ -1281,18 +1337,18 @@ public class RemoveEventLogCommand : PSCmdlet { /// /// The following is the definition of the input parameter "ComputerName". - /// Specifies the Computer Name + /// Specifies the Computer Name. /// [Parameter(Position = 1)] [ValidateNotNull] [ValidateNotNullOrEmpty] [Alias("CN")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] ComputerName { get; set; } = { "." }; + public string[] ComputerName { get; set; } = { "." }; /// /// The following is the definition of the input parameter "LogName". - /// Specifies the Event Log Name + /// Specifies the Event Log Name. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Default")] @@ -1300,11 +1356,11 @@ public class RemoveEventLogCommand : PSCmdlet [ValidateNotNullOrEmpty] [Alias("LN")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] LogName { get; set; } + public string[] LogName { get; set; } /// /// 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. @@ -1316,8 +1372,7 @@ public class RemoveEventLogCommand : PSCmdlet [ValidateNotNullOrEmpty] [Alias("SRC")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Source { get; set; } - + public string[] Source { get; set; } /// /// BeginProcessing method. @@ -1329,7 +1384,7 @@ protected override void BeginProcessing() string computer = string.Empty; foreach (string compName in ComputerName) { - if ((compName.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) || (compName.Equals(".", StringComparison.OrdinalIgnoreCase))) + if ((compName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (compName.Equals(".", StringComparison.OrdinalIgnoreCase))) { computer = "localhost"; } @@ -1337,6 +1392,7 @@ protected override void BeginProcessing() { computer = compName; } + if (ParameterSetName.Equals("Default")) { foreach (string log in LogName) @@ -1349,6 +1405,7 @@ protected override void BeginProcessing() { continue; } + EventLog.Delete(log, compName); } else @@ -1378,11 +1435,12 @@ protected override void BeginProcessing() { continue; } + EventLog.DeleteEventSource(src, compName); } else { - ErrorRecord er = new ErrorRecord(new InvalidOperationException(StringUtil.Format(EventlogResources.SourceDoesNotExist, "", computer, src)), null, ErrorCategory.InvalidOperation, null); + ErrorRecord er = new ErrorRecord(new InvalidOperationException(StringUtil.Format(EventlogResources.SourceDoesNotExist, string.Empty, computer, src)), null, ErrorCategory.InvalidOperation, null); WriteError(er); continue; } @@ -1402,9 +1460,8 @@ protected override void BeginProcessing() ErrorRecord er = new ErrorRecord(ex, "NewEventlogException", ErrorCategory.SecurityError, null); WriteError(er); } - }//End BeginProcessing() - }//End Class + } + } #endregion RemoveEventLogCommand -}//Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs index 695648a1829..c049370e4b4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs @@ -1,22 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// /// The get-childitem command class. - /// This command lists the contents of a container + /// This command lists the contents of a container. /// - /// /// /// - /// - [Cmdlet(VerbsCommon.Get, "ChildItem", DefaultParameterSetName = "Items", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113308")] + [Cmdlet(VerbsCommon.Get, "ChildItem", DefaultParameterSetName = "Items", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096492")] public class GetChildItemCommand : CoreCommandBase { /// @@ -33,7 +30,7 @@ public class GetChildItemCommand : CoreCommandBase #region Command parameters /// - /// Gets or sets the path for the operation + /// Gets or sets the path for the operation. /// [Parameter(Position = 0, ParameterSetName = childrenSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -43,6 +40,7 @@ public string[] Path { return _paths; } + set { _paths = value; @@ -50,27 +48,27 @@ public string[] Path } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = literalChildrenSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return _paths; - } // get + } set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter(Position = 1)] public override string Filter @@ -79,6 +77,7 @@ public override string Filter { return base.Filter; } + set { base.Filter = value; @@ -86,7 +85,7 @@ public override string Filter } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include @@ -94,16 +93,16 @@ public override string[] Include get { return base.Include; - } // get + } set { base.Include = value; - } // set - } // Include + } + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude @@ -111,25 +110,26 @@ public override string[] Exclude get { return base.Exclude; - } // get + } set { base.Exclude = value; - } // set - } // Exclude + } + } /// - /// Gets or sets the recurse switch + /// Gets or sets the recurse switch. /// [Parameter] - [Alias("s")] + [Alias("s", "r")] public SwitchParameter Recurse { get { return _recurse; } + set { _recurse = value; @@ -149,6 +149,7 @@ public uint Depth { return _depth; } + set { _depth = value; @@ -157,9 +158,8 @@ public uint Depth } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -169,7 +169,6 @@ public uint Depth /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { @@ -177,14 +176,15 @@ public override SwitchParameter Force { return base.Force; } + set { base.Force = value; } - } // Force + } /// - /// Gets or sets the names switch + /// Gets or sets the names switch. /// [Parameter] public SwitchParameter Name @@ -193,6 +193,7 @@ public SwitchParameter Name { return _childNames; } + set { _childNames = value; @@ -204,20 +205,17 @@ public SwitchParameter Name /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { object result = null; - string path = String.Empty; + string path = string.Empty; if (_paths != null && _paths.Length > 0) { @@ -240,14 +238,16 @@ internal override object GetDynamicParameters(CmdletProviderContext context) { result = InvokeProvider.ChildItem.GetChildItemsDynamicParameters(path, Recurse, context); } + break; default: result = InvokeProvider.ChildItem.GetChildItemsDynamicParameters(path, Recurse, context); break; } + return result; - } // GetDynamicParameters + } #endregion Command parameters @@ -272,13 +272,12 @@ internal override object GetDynamicParameters(CmdletProviderContext context) private uint _depth = uint.MaxValue; /// - /// The flag that specifies whether to retrieve the child names or the child items + /// The flag that specifies whether to retrieve the child names or the child items. /// private bool _childNames = false; #endregion command data - #region command code /// @@ -290,7 +289,7 @@ protected override void ProcessRecord() if (_paths == null || _paths.Length == 0) { - _paths = new string[] { String.Empty }; + _paths = new string[] { string.Empty }; } foreach (string path in _paths) @@ -360,9 +359,8 @@ protected override void ProcessRecord() break; } } - } // ProcessRecord + } #endregion command code - } // class GetChildrenCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs index 176ee347211..a2b7f03ce70 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs @@ -1,202 +1,116 @@ -using System; -using System.Management.Automation; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; using System.Collections.Generic; -using System.Windows.Forms; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Drawing; -using System.Media; -using System.Runtime.InteropServices; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Collections.Specialized; +using System.Management.Automation; +using System.Management.Automation.Language; +using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands { - /// - /// Defines the different type supported by the clipboard. - /// - public enum ClipboardFormat - { - /// Text format as default. - Text = 0, - - /// File format. - FileDropList = 1, - - /// Image format. - Image = 2, - - /// Audio format. - Audio = 3, - }; - /// /// Defines the implementation of the 'Get-Clipboard' cmdlet. /// This cmdlet get the content from system clipboard. /// - [Cmdlet(VerbsCommon.Get, "Clipboard", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526219")] + [Cmdlet(VerbsCommon.Get, "Clipboard", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2109905")] [Alias("gcb")] - [OutputType(typeof(String), typeof(FileInfo), typeof(Image), typeof(Stream))] + [OutputType(typeof(string))] public class GetClipboardCommand : PSCmdlet { /// - /// Property that sets clipboard type. This will return the required format from clipboard - /// - [Parameter] - public ClipboardFormat Format { get; set; } - - /// - /// Property that sets format type when the return type is text. + /// Property that sets raw parameter. This will allow clipboard return text or file list as one string. /// [Parameter] - [ValidateNotNullOrEmpty] - public TextDataFormat TextFormatType + public SwitchParameter Raw { - get { return _textFormat; } - set + get { - _isTextFormatTypeSet = true; - _textFormat = value; + return _raw; } - } - private TextDataFormat _textFormat = TextDataFormat.UnicodeText; - private bool _isTextFormatTypeSet = false; - /// - /// Property that sets raw parameter. This will allow clipboard return text or file list as one string. - /// - [Parameter] - public SwitchParameter Raw - { - get { return _raw; } set { - _isRawSet = true; _raw = value; } } + + /// + /// 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; - private bool _isRawSet = false; /// - /// This method implements the ProcessRecord method for Get-Clipboard command + /// This method implements the ProcessRecord method for Get-Clipboard command. /// protected override void BeginProcessing() { - // TextFormatType should only combine with Text. - if (Format != ClipboardFormat.Text && _isTextFormatTypeSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - String.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidTypeCombine)), - "FailedToGetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - // Raw should only combine with Text or FileDropList. - if (Format != ClipboardFormat.Text && Format != ClipboardFormat.FileDropList && _isRawSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - String.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidRawCombine)), - "FailedToGetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - if (Format == ClipboardFormat.Text) - { - this.WriteObject(GetClipboardContentAsText(_textFormat), true); - } - else if (Format == ClipboardFormat.Image) - { - this.WriteObject(Clipboard.GetImage()); - } - else if (Format == ClipboardFormat.FileDropList) - { - if (_raw) - { - this.WriteObject(Clipboard.GetFileDropList(), true); - } - else - { - this.WriteObject(GetClipboardContentAsFileList()); - } - } - else if (Format == ClipboardFormat.Audio) - { - this.WriteObject(Clipboard.GetAudioStream()); - } + this.WriteObject(GetClipboardContentAsText(), true); } /// /// Returns the clipboard content as text format. /// - /// - /// - private List GetClipboardContentAsText(TextDataFormat textFormat) + /// Array of strings representing content from clipboard. + private List GetClipboardContentAsText() { - if (!Clipboard.ContainsText(textFormat)) + var result = new List(); + string textContent = null; + + try + { + textContent = Clipboard.GetText(); + } + catch (PlatformNotSupportedException) { - return null; + ThrowTerminatingError(new ErrorRecord(new InvalidOperationException(ClipboardResources.UnsupportedPlatform), "FailedToGetClipboardUnsupportedPlatform", ErrorCategory.InvalidOperation, "Clipboard")); } - List result = new List(); - // TextFormat default value is Text, by default it is same as Clipboard.GetText() - string textContent = Clipboard.GetText(textFormat); if (_raw) { result.Add(textContent); } 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 + { /// - /// Returns the clipboard content as file info. + /// Provides argument completion for the Delimiter parameter. /// - /// - private List GetClipboardContentAsFileList() + /// 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) { - if (!Clipboard.ContainsFileDropList()) - { - return null; - } - List result = new List(); - foreach (string filePath in Clipboard.GetFileDropList()) + wordToComplete ??= string.Empty; + var pattern = new WildcardPattern(wordToComplete + '*', WildcardOptions.IgnoreCase); + if (pattern.IsMatch("CRLF") || pattern.IsMatch("Windows")) { - FileInfo file = new FileInfo(filePath); - result.Add(WrapOutputInPSObject(file, filePath)); + yield return new CompletionResult("\"`r`n\"", "CRLF", CompletionResultType.ParameterValue, "Windows (CRLF)"); } - return result; - } - /// - /// Wraps the item in a PSObject and attaches some notes to the - /// object that deal with path information. - /// - /// - /// - /// - private PSObject WrapOutputInPSObject( - FileInfo item, - string path) - { - PSObject result = new PSObject(item); - - // Now get the parent path and child name - if (path != null) + if (pattern.IsMatch("LF") || pattern.IsMatch("Unix") || pattern.IsMatch("Linux")) { - // Get the parent path - string parentPath = Directory.GetParent(path).FullName; - result.AddOrSetProperty("PSParentPath", parentPath); - - // Get the child name - string childName = item.Name; - result.AddOrSetProperty("PSChildName", childName); + yield return new CompletionResult("\"`n\"", "LF", CompletionResultType.ParameterValue, "UNIX (LF)"); } - return result; } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index a4a0e4ff122..87f96c436ea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #if !UNIX using System; using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Reflection; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq.Expressions; using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; using Microsoft.Management.Infrastructure; using Microsoft.Win32; @@ -21,17 +21,17 @@ namespace Microsoft.PowerShell.Commands #region GetComputerInfoCommand cmdlet implementation /// - /// The Get=ComputerInfo cmdlet gathers and reports information + /// The Get-ComputerInfo cmdlet gathers and reports information /// about a computer. /// [Cmdlet(VerbsCommon.Get, "ComputerInfo", - HelpUri = "https://go.microsoft.com/fwlink/?LinkId=799466")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096810")] [Alias("gin")] [OutputType(typeof(ComputerInfo), typeof(PSObject))] 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,15 +59,15 @@ 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 UInt64? physicallyInstalledMemory; + public ulong? physicallyInstalledMemory; public string timeZone; public string logonServer; public FirmwareType? firmwareType; @@ -85,8 +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 ProgressRecord _progress = null; + private readonly string _machineName = localMachineName; // we might need to have cmdlet work on another machine /// /// Collection of property names from the Property parameter, @@ -101,7 +100,7 @@ private class MiscInfoGroup /// The Property parameter contains the names of properties to be retrieved. /// If this parameter is given, the cmdlet returns a PSCustomObject /// containing only the requested properties. - /// Wild-card patterns may be provided + /// Wild-card patterns may be provided. /// /// /// @@ -124,7 +123,7 @@ private class MiscInfoGroup #region Cmdlet Overrides /// - /// Perform any first-stage processing + /// Perform any first-stage processing. /// protected override void BeginProcessing() { @@ -144,7 +143,7 @@ protected override void BeginProcessing() } /// - /// Performs the cmdlet's work + /// Performs the cmdlet's work. /// protected override void ProcessRecord() { @@ -199,13 +198,13 @@ protected override void ProcessRecord() systemInfo.networkAdapters = GetNetworkAdapters(session); UpdateProgress(null); // close the progress bar - } // end of using(CimSession...) + } var infoOutput = CreateFullOutputObject(systemInfo, osInfo, miscInfo); if (_namedProperties != null) { - //var output = CreateCustomOutputObject(namedProperties, systemInfo, osInfo, miscInfo); + // var output = CreateCustomOutputObject(namedProperties, systemInfo, osInfo, miscInfo); var output = CreateCustomOutputObject(infoOutput, _namedProperties); WriteObject(output); @@ -219,24 +218,17 @@ protected override void ProcessRecord() #region Private Methods /// - /// Display progress + /// Display progress. /// /// /// Text to be displayed in status bar /// private void UpdateProgress(string status) { - if (_progress != null) - { - _progress.RecordType = ProgressRecordType.Completed; - WriteProgress(_progress); - } + ProgressRecord progress = new(0, activity, status ?? ComputerResources.ProgressStatusCompleted); + progress.RecordType = status == null ? ProgressRecordType.Completed : ProgressRecordType.Processing; - if (status != null) - { - _progress = new ProgressRecord(0, activity, status); - WriteProgress(_progress); - } + WriteProgress(progress); } /// @@ -256,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) @@ -295,7 +287,7 @@ private static NetworkAdapter[] GetNetworkAdapters(CimSession session) if (adapters != null && configs != null) { - var configDict = new Dictionary(); + var configDict = new Dictionary(); foreach (var config in configs) { @@ -412,11 +404,12 @@ private static bool CheckDeviceGuardLicense() // consider there to be no license. } } + return false; } /// - /// Retrieve information related to Device Guard + /// Retrieve information related to Device Guard. /// /// /// A object representing @@ -436,12 +429,14 @@ private static DeviceGuardInfo GetDeviceGuard(CimSession session) var wmiGuard = session.GetFirst(CIMHelper.DeviceGuardNamespace, CIMHelper.ClassNames.DeviceGuard); - if (wmiGuard != null) { + if (wmiGuard != null) + { var smartStatus = EnumConverter.Convert((int?)wmiGuard.VirtualizationBasedSecurityStatus ?? 0); if (smartStatus != null) { status = (DeviceGuardSmartStatus)smartStatus; } + guard = wmiGuard.AsOutputType; } } @@ -479,7 +474,7 @@ private static DeviceGuardInfo GetDeviceGuard(CimSession session) } /// - /// Retrieve information related to HyperVisor + /// Retrieve information related to HyperVisor. /// /// /// A object representing @@ -491,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; @@ -533,7 +528,7 @@ private static HyperVInfo GetHyperVisorInfo(CimSession session) } /// - /// Retrieve miscellaneous system information + /// Retrieve miscellaneous system information. /// /// /// A object representing @@ -550,7 +545,7 @@ private static MiscInfoGroup GetOtherInfo(CimSession session) // get platform role try { - //TODO: Local machine only. Check for that? + // TODO: Local machine only. Check for that? uint powerRole = Native.PowerDeterminePlatformRoleEx(Native.POWER_PLATFORM_ROLE_V2); if (powerRole >= (uint)PowerPlatformRole.MaximumEnumValue) rv.powerPlatformRole = PowerPlatformRole.Unspecified; @@ -564,14 +559,13 @@ private static MiscInfoGroup GetOtherInfo(CimSession session) } // get secure-boot info - //TODO: Local machine only? Check for that? + // TODO: Local machine only? Check for that? rv.firmwareType = GetFirmwareType(); // get amount of memory physically installed - //TODO: Local machine only. Check for that? + // TODO: Local machine only. Check for that? rv.physicallyInstalledMemory = GetPhysicallyInstalledSystemMemory(); - // get time zone // we'll use .Net's TimeZoneInfo for now. systeminfo uses Caption from Win32_TimeZone var tzi = TimeZoneInfo.Local; @@ -616,7 +610,7 @@ private static MiscInfoGroup GetOtherInfo(CimSession session) /// null if unsuccessful, otherwise FirmwareType enum specifying /// the firmware type. /// - private static Nullable GetFirmwareType() + private static FirmwareType? GetFirmwareType() { try { @@ -640,11 +634,11 @@ private static Nullable GetFirmwareType() /// /// null if unsuccessful, otherwise the amount of physically installed memory. /// - private static Nullable GetPhysicallyInstalledSystemMemory() + private static ulong? GetPhysicallyInstalledSystemMemory() { try { - UInt64 memory; + ulong memory; if (Native.GetPhysicallyInstalledSystemMemory(out memory)) return memory; } @@ -889,14 +883,13 @@ private static ComputerInfo CreateFullOutputObject(SystemInfoGroup systemInfo, O if (otherInfo.keyboards.Length > 0) { - //TODO: handle multiple keyboards? + // TODO: handle multiple keyboards? // there might be several keyboards found. For the moment // we display info for only one string layout = otherInfo.keyboards[0].Layout; - var culture = Conversion.MakeLocale(layout); - output.KeyboardLayout = culture == null ? layout : culture.Name; + output.KeyboardLayout = Conversion.GetLocaleName(layout); } if (otherInfo.hyperV != null) @@ -933,7 +926,7 @@ private static ComputerInfo CreateFullOutputObject(SystemInfoGroup systemInfo, O /// /// Create a new PSObject, containing only those properties named in the - /// namedProperties parameter + /// namedProperties parameter. /// /// /// A containing all the acquired system information @@ -988,7 +981,7 @@ private static List GetComputerInfoPropertyNames() } /// - /// Expand any wild-card patterns into known property names + /// Expand any wild-card patterns into known property names. /// /// /// List of known property names @@ -1049,9 +1042,9 @@ private static List CollectPropertyNames(string[] requestedProperties) // find a matching property name via case-insensitive string comparison Predicate pred = (s) => { - return string.Compare(s, + return string.Equals(s, name, - StringComparison.CurrentCultureIgnoreCase) == 0; + StringComparison.OrdinalIgnoreCase); }; var propertyName = availableProperties.Find(pred); @@ -1104,29 +1097,6 @@ internal static bool TryParseHex(string hexString, out uint value) } } - public static string LocaleIdToLocaleName(uint localeID) - { - // CoreCLR's System.Globalization.Culture does not appear to have a constructor - // that accepts an integer LocalID (LCID) value, so we'll PInvoke native code - // to get a locale name from an LCID value - - try - { - var sbName = new System.Text.StringBuilder(Native.LOCALE_NAME_MAX_LENGTH); - var len = Native.LCIDToLocaleName(localeID, sbName, sbName.Capacity, 0); - - if (len > 0 && sbName.Length > 0) - return sbName.ToString(); - } - catch (Exception) - { - // Probably failed to load the DLL or to file the function entry point. - // Fail silently - } - - return null; - } - /// /// Attempt to create a /// object from a locale string as retrieved from WMI. @@ -1143,38 +1113,33 @@ public static string LocaleIdToLocaleName(uint localeID) /// Failing that it attempts to retrieve the CultureInfo object /// using the locale string as passed. /// - internal static System.Globalization.CultureInfo MakeLocale(string locale) + internal static string GetLocaleName(string locale) { - System.Globalization.CultureInfo culture = null; + CultureInfo culture = null; if (locale != null) { try { - uint localeNum; - - if (TryParseHex(locale, out localeNum)) + // The "locale" must contain a hexadecimal value, with no + // 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 (uint.TryParse(locale, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint localeNum)) { - string localeName = LocaleIdToLocaleName(localeNum); - - if (localeName != null) - culture = new System.Globalization.CultureInfo(localeName); + culture = CultureInfo.GetCultureInfo((int)localeNum); } - if (culture == null) - { - // either the TryParseHex failed, or the LocaleIdToLocaleName - // failed, so we'll try using the original string - culture = new System.Globalization.CultureInfo(locale); - } + // If TryParse failed we'll try using the original string as culture name + culture ??= CultureInfo.GetCultureInfo(locale); } - catch (Exception/* ex*/) + catch (Exception) { culture = null; } } - return culture; + return culture?.Name; } /// @@ -1218,14 +1183,14 @@ internal static class EnumConverter where T : struct, IConvertible private static readonly Func s_convert = MakeConverter(); /// - /// Convert an integer to a Nullable enum of type T + /// Convert an integer to a Nullable enum of type T. /// /// /// The integer value to be converted to the specified enum type. /// /// /// 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. /// @@ -1263,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)) { @@ -1343,7 +1308,7 @@ internal abstract class WmiClassBase /// /// Get a language name from a language identifier. /// - /// + /// /// A nullable integer containing the language ID for the desired language. /// /// @@ -1351,17 +1316,26 @@ internal abstract class WmiClassBase /// the language parameter. If the language parameter is null or has a /// value that is not a valid language ID, the method returns null. /// - protected static string GetLanguageName(UInt32? language) + protected static string GetLanguageName(uint? lcid) { - if (language != null) - return Conversion.LocaleIdToLocaleName(language.Value); + if (lcid != null && lcid >= 0) + { + try + { + return CultureInfo.GetCultureInfo((int)lcid.Value).Name; + } + catch + { + } + } 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; @@ -1394,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; @@ -1406,7 +1380,7 @@ internal class WmiBios : WmiClassBase public byte? EmbeddedControllerMajorVersion; public byte? EmbeddedControllerMinorVersion; public string IdentificationCode; - public UInt16? InstallableLanguages; + public ushort? InstallableLanguages; public DateTime? InstallDate; public string LanguageEdition; public string[] ListOfLanguages; @@ -1417,77 +1391,77 @@ internal class WmiBios : WmiClassBase public DateTime? ReleaseDate; public string SerialNumber; public string SMBIOSBIOSVersion; - public UInt16? SMBIOSMajorVersion; - public UInt16? SMBIOSMinorVersion; + public ushort? SMBIOSMajorVersion; + public ushort? SMBIOSMinorVersion; public bool? SMBIOSPresent; - public UInt16? SoftwareElementState; + public ushort? SoftwareElementState; public string Status; public byte? SystemBiosMajorVersion; public byte? SystemBiosMinorVersion; - public UInt16? TargetOperatingSystem; + public ushort? TargetOperatingSystem; public string Version; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiComputerSystem + internal sealed class WmiComputerSystem { - public UInt16? AdminPasswordStatus; + public ushort? AdminPasswordStatus; public bool? AutomaticManagedPagefile; public bool? AutomaticResetBootOption; public bool? AutomaticResetCapability; - public UInt16? BootOptionOnLimit; - public UInt16? BootOptionOnWatchDog; + public ushort? BootOptionOnLimit; + public ushort? BootOptionOnWatchDog; public bool? BootROMSupported; public string BootupState; - public UInt16[] BootStatus; + public ushort[] BootStatus; public string Caption; - public UInt16? ChassisBootupState; + public ushort? ChassisBootupState; public string ChassisSKUNumber; - public Int16? CurrentTimeZone; + public short? CurrentTimeZone; public bool? DaylightInEffect; public string Description; public string DNSHostName; public string Domain; - public UInt16? DomainRole; + public ushort? DomainRole; public bool? EnableDaylightSavingsTime; - public UInt16? FrontPanelResetStatus; + public ushort? FrontPanelResetStatus; public bool? HypervisorPresent; public bool? InfraredSupported; public string InitialLoadInfo; public DateTime? InstallDate; - public UInt16? KeyboardPasswordStatus; + public ushort? KeyboardPasswordStatus; public string LastLoadInfo; public string Manufacturer; public string Model; public string Name; public bool? NetworkServerModeEnabled; - public UInt32? NumberOfLogicalProcessors; - public UInt32? NumberOfProcessors; + public uint? NumberOfLogicalProcessors; + public uint? NumberOfProcessors; public string[] OEMStringArray; public bool? PartOfDomain; - public Int64? PauseAfterReset; - public UInt16? PCSystemType; - public UInt16? PCSystemTypeEx; - public UInt16[] PowerManagementCapabilities; + public long? PauseAfterReset; + public ushort? PCSystemType; + public ushort? PCSystemTypeEx; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; - public UInt16? PowerOnPasswordStatus; - public UInt16? PowerState; - public UInt16? PowerSupplyState; + public ushort? PowerOnPasswordStatus; + public ushort? PowerState; + public ushort? PowerSupplyState; public string PrimaryOwnerContact; public string PrimaryOwnerName; - public UInt16? ResetCapability; - public Int16? ResetCount; - public Int16? ResetLimit; + public ushort? ResetCapability; + public short? ResetCount; + public short? ResetLimit; public string[] Roles; public string Status; public string[] SupportContactDescription; public string SystemFamily; public string SystemSKUNumber; public string SystemType; - public UInt16? ThermalState; - public UInt64? TotalPhysicalMemory; + public ushort? ThermalState; + public ulong? TotalPhysicalMemory; public string UserName; - public UInt16? WakeUpType; + public ushort? WakeUpType; public string Workgroup; public PowerManagementCapabilities[] GetPowerManagementCapabilities() @@ -1512,15 +1486,15 @@ 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 UInt32? CodeIntegrityPolicyEnforcementStatus; - public UInt32? UsermodeCodeIntegrityPolicyEnforcementStatus; - public UInt32[] RequiredSecurityProperties; - public UInt32[] SecurityServicesConfigured; - public UInt32[] SecurityServicesRunning; - public UInt32? VirtualizationBasedSecurityStatus; + public uint[] AvailableSecurityProperties; + public uint? CodeIntegrityPolicyEnforcementStatus; + public uint? UsermodeCodeIntegrityPolicyEnforcementStatus; + public uint[] RequiredSecurityProperties; + public uint[] SecurityServicesConfigured; + public uint[] SecurityServicesRunning; + public uint? VirtualizationBasedSecurityStatus; public DeviceGuard AsOutputType { @@ -1539,6 +1513,7 @@ public DeviceGuard AsOutputType if (temp != null) listHardware.Add(temp.Value); } + guard.RequiredSecurityProperties = listHardware.ToArray(); listHardware.Clear(); @@ -1549,6 +1524,7 @@ public DeviceGuard AsOutputType if (temp != null) listHardware.Add(temp.Value); } + guard.AvailableSecurityProperties = listHardware.ToArray(); var listSoftware = new List(); @@ -1559,6 +1535,7 @@ public DeviceGuard AsOutputType if (temp != null) listSoftware.Add(temp.Value); } + guard.SecurityServicesConfigured = listSoftware.ToArray(); listSoftware.Clear(); @@ -1569,6 +1546,7 @@ public DeviceGuard AsOutputType if (temp != null) listSoftware.Add(temp.Value); } + guard.SecurityServicesRunning = listSoftware.ToArray(); } @@ -1583,11 +1561,11 @@ 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 UInt16? Availability; + public ushort? Availability; public string Caption; - public UInt32? ConfigManagerErrorCode; + public uint? ConfigManagerErrorCode; public bool? ConfigManagerUserConfig; public string Description; public string DeviceID; @@ -1595,161 +1573,161 @@ internal class WmiKeyboard public string ErrorDescription; public DateTime? InstallDate; public bool? IsLocked; - public UInt32? LastErrorCode; + public uint? LastErrorCode; public string Layout; public string Name; - public UInt16? NumberOfFunctionKeys; - public UInt16? Password; + public ushort? NumberOfFunctionKeys; + public ushort? Password; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string Status; - public UInt16? StatusInfo; + public ushort? StatusInfo; public string SystemCreationClassName; public string SystemName; } [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 UInt32? TotalPhysicalMemory; + // 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; public DateTime? InstallDate; public string Name; public string Status; - public UInt16? Availability; - public UInt32? ConfigManagerErrorCode; + public ushort? Availability; + public uint? ConfigManagerErrorCode; public bool? ConfigManagerUserConfig; public string DeviceID; public bool? ErrorCleared; public string ErrorDescription; - public UInt32? LastErrorCode; + public uint? LastErrorCode; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; - public UInt16? StatusInfo; + public ushort? StatusInfo; public string SystemCreationClassName; public string SystemName; - public UInt64? Speed; - public UInt64? MaxSpeed; - public UInt64? RequestedSpeed; - public UInt16? UsageRestriction; - public UInt16? PortType; + public ulong? Speed; + public ulong? MaxSpeed; + public ulong? RequestedSpeed; + public ushort? UsageRestriction; + public ushort? PortType; public string OtherPortType; public string OtherNetworkPortType; - public UInt16? PortNumber; - public UInt16? LinkTechnology; + public ushort? PortNumber; + public ushort? LinkTechnology; public string OtherLinkTechnology; public string PermanentAddress; public string[] NetworkAddresses; public bool? FullDuplex; public bool? AutoSense; - public UInt64? SupportedMaximumTransmissionUnit; - public UInt64? ActiveMaximumTransmissionUnit; + public ulong? SupportedMaximumTransmissionUnit; + public ulong? ActiveMaximumTransmissionUnit; public string InterfaceDescription; public string InterfaceName; - public UInt64? NetLuid; + public ulong? NetLuid; public string InterfaceGuid; - public UInt32? InterfaceIndex; + public uint? InterfaceIndex; public string DeviceName; - public UInt32? NetLuidIndex; + public uint? NetLuidIndex; public bool? Virtual; public bool? Hidden; public bool? NotUserRemovable; public bool? IMFilter; - public UInt32? InterfaceType; + public uint? InterfaceType; public bool? HardwareInterface; public bool? WdmInterface; public bool? EndPointInterface; public bool? iSCSIInterface; - public UInt32? State; - public UInt32? NdisMedium; - public UInt32? NdisPhysicalMedium; - public UInt32? InterfaceOperationalStatus; + public uint? State; + public uint? NdisMedium; + public uint? NdisPhysicalMedium; + public uint? InterfaceOperationalStatus; public bool? OperationalStatusDownDefaultPortNotAuthenticated; public bool? OperationalStatusDownMediaDisconnected; public bool? OperationalStatusDownInterfacePaused; public bool? OperationalStatusDownLowPowerState; - public UInt32? InterfaceAdminStatus; - public UInt32? MediaConnectState; - public UInt32? MtuSize; - public UInt16? VlanID; - public UInt64? TransmitLinkSpeed; - public UInt64? ReceiveLinkSpeed; + public uint? InterfaceAdminStatus; + public uint? MediaConnectState; + public uint? MtuSize; + public ushort? VlanID; + public ulong? TransmitLinkSpeed; + public ulong? ReceiveLinkSpeed; public bool? PromiscuousMode; public bool? DeviceWakeUpEnable; public bool? ConnectorPresent; - public UInt32? MediaDuplexState; + public uint? MediaDuplexState; public string DriverDate; - public UInt64? DriverDateData; + public ulong? DriverDateData; public string DriverVersionString; public string DriverName; public string DriverDescription; - public UInt16? MajorDriverVersion; - public UInt16? MinorDriverVersion; + public ushort? MajorDriverVersion; + public ushort? MinorDriverVersion; public byte? DriverMajorNdisVersion; public byte? DriverMinorNdisVersion; 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 UInt16? AdapterTypeID; + public ushort? AdapterTypeID; public bool? AutoSense; - public UInt16? Availability; + public ushort? Availability; public string Caption; - public UInt32? ConfigManagerErrorCode; + public uint? ConfigManagerErrorCode; public bool? ConfigManagerUserConfig; public string Description; public string DeviceID; public bool? ErrorCleared; public string ErrorDescription; public string GUID; - public UInt32? Index; + public uint? Index; public DateTime? InstallDate; public bool? Installed; - public UInt32? InterfaceIndex; - public UInt32? LastErrorCode; + public uint? InterfaceIndex; + public uint? LastErrorCode; public string MACAddress; public string Manufacturer; - public UInt32? MaxNumberControlled; - public UInt64? MaxSpeed; + public uint? MaxNumberControlled; + public ulong? MaxSpeed; public string Name; public string NetConnectionID; - public UInt16? NetConnectionStatus; + public ushort? NetConnectionStatus; public bool? NetEnabled; public string[] NetworkAddresses; public string PermanentAddress; public bool? PhysicalAdapter; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string ProductName; public string ServiceName; - public UInt64? Speed; + public ulong? Speed; public string Status; - public UInt16? StatusInfo; + public ushort? StatusInfo; public string SystemCreationClassName; public string SystemName; public DateTime? TimeOfLastReset; } [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; @@ -1770,14 +1748,14 @@ internal class WmiNetworkAdapterConfiguration public string DNSHostName; public string[] DNSServerSearchOrder; public bool? DomainDNSRegistrationEnabled; - public UInt32? ForwardBufferMemory; + public uint? ForwardBufferMemory; public bool? FullDNSRegistrationEnabled; - public UInt16[] GatewayCostMetric; + public ushort[] GatewayCostMetric; public byte? IGMPLevel; - public UInt32? Index; - public UInt32? InterfaceIndex; + public uint? Index; + public uint? InterfaceIndex; public string[] IPAddress; - public UInt32? IPConnectionMetric; + public uint? IPConnectionMetric; public bool? IPEnabled; public bool? IPFilterSecurityEnabled; public bool? IPPortSecurityEnabled; @@ -1788,25 +1766,25 @@ internal class WmiNetworkAdapterConfiguration public bool? IPUseZeroBroadcast; public string IPXAddress; public bool? IPXEnabled; - public UInt32[] IPXFrameType; - public UInt32? IPXMediaType; + public uint[] IPXFrameType; + public uint? IPXMediaType; public string[] IPXNetworkNumber; public string IPXVirtualNetNumber; - public UInt32? KeepAliveInterval; - public UInt32? KeepAliveTime; + public uint? KeepAliveInterval; + public uint? KeepAliveTime; public string MACAddress; - public UInt32? MTU; - public UInt32? NumForwardPackets; + public uint? MTU; + public uint? NumForwardPackets; public bool? PMTUBHDetectEnabled; public bool? PMTUDiscoveryEnabled; public string ServiceName; public string SettingID; - public UInt32? TcpipNetbiosOptions; - public UInt32? TcpMaxConnectRetransmissions; - public UInt32? TcpMaxDataRetransmissions; - public UInt32? TcpNumConnections; + public uint? TcpipNetbiosOptions; + public uint? TcpMaxConnectRetransmissions; + public uint? TcpMaxDataRetransmissions; + public uint? TcpNumConnections; public bool? TcpUseRFC1122UrgentPointer; - public UInt16? TcpWindowSize; + public ushort? TcpWindowSize; public bool? WINSEnableLMHostsLookup; public string WINSHostLookupFile; public string WINSPrimaryServer; @@ -1815,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; @@ -1826,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; @@ -1834,47 +1812,47 @@ internal class WmiOperatingSystem : WmiClassBase public bool? Debug; public string Description; public bool? Distributed; - public UInt32? EncryptionLevel; + public uint? EncryptionLevel; public byte? ForegroundApplicationBoost; - public UInt64? FreePhysicalMemory; - public UInt64? FreeSpaceInPagingFiles; - public UInt64? FreeVirtualMemory; + public ulong? FreePhysicalMemory; + public ulong? FreeSpaceInPagingFiles; + public ulong? FreeVirtualMemory; public DateTime? InstallDate; public DateTime? LastBootUpTime; public DateTime? LocalDateTime; public string Locale; public string Manufacturer; - public UInt32? MaxNumberOfProcesses; - public UInt64? MaxProcessMemorySize; + public uint? MaxNumberOfProcesses; + public ulong? MaxProcessMemorySize; public string[] MUILanguages; public string Name; - public UInt32? NumberOfLicensedUsers; - public UInt32? NumberOfProcesses; - public UInt32? NumberOfUsers; - public UInt32? OperatingSystemSKU; + public uint? NumberOfLicensedUsers; + public uint? NumberOfProcesses; + public uint? NumberOfUsers; + public uint? OperatingSystemSKU; public string Organization; public string OSArchitecture; - public UInt32? OSLanguage; - public UInt32? OSProductSuite; - public UInt16? OSType; + public uint? OSLanguage; + public uint? OSProductSuite; + public ushort? OSType; public string OtherTypeDescription; public bool? PAEEnabled; public bool? PortableOperatingSystem; public bool? Primary; - public UInt32? ProductType; + public uint? ProductType; public string RegisteredUser; public string SerialNumber; - public UInt16? ServicePackMajorVersion; - public UInt16? ServicePackMinorVersion; - public UInt64? SizeStoredInPagingFiles; + public ushort? ServicePackMajorVersion; + public ushort? ServicePackMinorVersion; + public ulong? SizeStoredInPagingFiles; public string Status; - public UInt32? SuiteMask; + public uint? SuiteMask; public string SystemDevice; public string SystemDirectory; public string SystemDrive; - public UInt64? TotalSwapSpaceSize; - public UInt64? TotalVirtualMemorySize; - public UInt64? TotalVisibleMemorySize; + public ulong? TotalSwapSpaceSize; + public ulong? TotalVirtualMemorySize; + public ulong? TotalVisibleMemorySize; public string Version; public string WindowsDirectory; #endregion Fields @@ -1899,17 +1877,12 @@ public OSProductSuite[] Suites #region Public Methods public string GetLocale() { - System.Globalization.CultureInfo culture = null; - - if (Locale != null) - culture = Conversion.MakeLocale(Locale); - - return culture == null ? null : culture.Name; + return Conversion.GetLocaleName(Locale); } #endregion Public Methods #region Private Methods - private OSProductSuite[] MakeProductSuites(UInt32? suiteMask) + private static OSProductSuite[] MakeProductSuites(uint? suiteMask) { if (suiteMask == null) return null; @@ -1917,8 +1890,8 @@ private OSProductSuite[] MakeProductSuites(UInt32? 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(); @@ -1927,84 +1900,84 @@ private OSProductSuite[] MakeProductSuites(UInt32? suiteMask) } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiPageFileUsage + internal sealed class WmiPageFileUsage { - public UInt32? AllocatedBaseSize; + public uint? AllocatedBaseSize; public string Caption; - public UInt32? CurrentUsage; + public uint? CurrentUsage; public string Description; public DateTime? InstallDate; public string Name; - public UInt32? PeakUsage; + public uint? PeakUsage; public string Status; public bool? TempPageFile; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiProcessor + internal sealed class WmiProcessor { - public UInt16? AddressWidth; - public UInt16? Architecture; + public ushort? AddressWidth; + public ushort? Architecture; public string AssetTag; - public UInt16? Availability; + public ushort? Availability; public string Caption; - public UInt32? Characteristics; - public UInt32? ConfigManagerErrorCode; + public uint? Characteristics; + public uint? ConfigManagerErrorCode; public bool? ConfigManagerUserConfig; - public UInt16? CpuStatus; - public UInt32? CurrentClockSpeed; - public UInt16? CurrentVoltage; - public UInt16? DataWidth; + public ushort? CpuStatus; + public uint? CurrentClockSpeed; + public ushort? CurrentVoltage; + public ushort? DataWidth; public string Description; public string DeviceID; public bool? ErrorCleared; public string ErrorDescription; - public UInt32? ExtClock; - public UInt16? Family; + public uint? ExtClock; + public ushort? Family; public DateTime? InstallDate; - public UInt32? L2CacheSize; - public UInt32? L2CacheSpeed; - public UInt32? L3CacheSize; - public UInt32? L3CacheSpeed; - public UInt32? LastErrorCode; - public UInt16? Level; - public UInt16? LoadPercentage; + public uint? L2CacheSize; + public uint? L2CacheSpeed; + public uint? L3CacheSize; + public uint? L3CacheSpeed; + public uint? LastErrorCode; + public ushort? Level; + public ushort? LoadPercentage; public string Manufacturer; - public UInt32? MaxClockSpeed; + public uint? MaxClockSpeed; public string Name; - public UInt32? NumberOfCores; - public UInt32? NumberOfEnabledCore; - public UInt32? NumberOfLogicalProcessors; + public uint? NumberOfCores; + public uint? NumberOfEnabledCore; + public uint? NumberOfLogicalProcessors; public string OtherFamilyDescription; public string PartNumber; public string PNPDeviceID; - public UInt16[] PowerManagementCapabilities; + public ushort[] PowerManagementCapabilities; public bool? PowerManagementSupported; public string ProcessorId; - public UInt16? ProcessorType; - public UInt16? Revision; + public ushort? ProcessorType; + public ushort? Revision; public string Role; public bool? SecondLevelAddressTranslationExtensions; public string SerialNumber; public string SocketDesignation; public string Status; - public UInt16? StatusInfo; + public ushort? StatusInfo; public string Stepping; public string SystemName; - public UInt32? ThreadCount; + public uint? ThreadCount; public string UniqueId; - public UInt16? UpgradeMethod; + public ushort? UpgradeMethod; public string Version; public bool? VirtualizationFirmwareEnabled; public bool? VMMonitorModeExtensions; - public UInt32? VoltageCaps; + public uint? VoltageCaps; } #pragma warning restore 649 #endregion Intermediate WMI classes #region Other Intermediate classes - internal class RegWinNtCurrentVersion + internal sealed class RegWinNtCurrentVersion { public string BuildLabEx; public string CurrentVersion; @@ -2024,43 +1997,43 @@ internal class RegWinNtCurrentVersion #region Output components #region Classes comprising the output object /// - /// Provides information about Device Guard + /// Provides information about Device Guard. /// public class DeviceGuard { /// - /// Array of required security properties + /// Array of required security properties. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardHardwareSecure[] RequiredSecurityProperties { get; internal set; } /// - /// Array of available security properties + /// Array of available security properties. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardHardwareSecure[] AvailableSecurityProperties { get; internal set; } /// - /// Indicates which security services have been configured + /// Indicates which security services have been configured. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardSoftwareSecure[] SecurityServicesConfigured { get; internal set; } /// - /// Indicates which security services are running + /// Indicates which security services are running. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardSoftwareSecure[] SecurityServicesRunning { get; internal set; } /// - /// Indicates the status of the Device Guard Code Integrity policy + /// Indicates the status of the Device Guard Code Integrity policy. /// public DeviceGuardConfigCodeIntegrityStatus? CodeIntegrityPolicyEnforcementStatus { get; internal set; } /// - /// Indicates the status of the Device Guard user mode Code Integrity policy + /// Indicates the status of the Device Guard user mode Code Integrity policy. /// public DeviceGuardConfigCodeIntegrityStatus? UserModeCodeIntegrityPolicyEnforcementStatus { get; internal set; } } /// - /// Describes a Quick-Fix Engineering update + /// Describes a Quick-Fix Engineering update. /// public class HotFix { @@ -2075,7 +2048,7 @@ public string HotFixID } /// - /// Description of the update + /// Description of the update. /// public string Description { @@ -2085,7 +2058,7 @@ public string Description } /// - /// String containing the date that the update was installed + /// String containing the date that the update was installed. /// public string InstalledOn { @@ -2095,7 +2068,7 @@ public string InstalledOn } /// - /// Additional comments that relate to the update + /// Additional comments that relate to the update. /// public string FixComments { @@ -2106,30 +2079,30 @@ public string FixComments } /// - /// Provides information about a network adapter + /// Provides information about a network adapter. /// public class NetworkAdapter { /// - /// Description of the network adapter + /// Description of the network adapter. /// public string Description { get; internal set; } /// /// Name of the network connection as it appears in the Network - /// Connections Control Panel program + /// Connections Control Panel program. /// public string ConnectionID { get; internal set; } /// /// Indicates whether the DHCP server automatically assigns an IP address - /// to the computer system when establishing a network connection + /// to the computer system when establishing a network connection. /// public bool? DHCPEnabled { get; internal set; } /// - /// IP Address of the DHCP server + /// IP Address of the DHCP server. /// public string DHCPServer { get; internal set; } /// - /// State of the network adapter connection to the network + /// State of the network adapter connection to the network. /// public NetConnectionStatus ConnectionStatus { get; internal set; } /// @@ -2140,49 +2113,49 @@ public class NetworkAdapter } /// - /// Describes a processor on the computer + /// Describes a processor on the computer. /// public class Processor { /// - /// Name of the processor + /// Name of the processor. /// public string Name { get; internal set; } /// - /// Name of the processor manufacturer + /// Name of the processor manufacturer. /// public string Manufacturer { get; internal set; } /// - /// Description of the processor + /// Description of the processor. /// public string Description { get; internal set; } /// - /// Processor architecture used by the platform + /// Processor architecture used by the platform. /// public CpuArchitecture? Architecture { get; internal set; } /// - /// Address width of the processor + /// Address width of the processor. /// - public UInt16? AddressWidth { get; internal set; } + public ushort? AddressWidth { get; internal set; } /// - /// Data width of the processor + /// Data width of the processor. /// - public UInt16? DataWidth { get; internal set; } + public ushort? DataWidth { get; internal set; } /// - /// Maximum speed of the processor, in MHz + /// Maximum speed of the processor, in MHz. /// - public UInt32? MaxClockSpeed { get; internal set; } + public uint? MaxClockSpeed { get; internal set; } /// - /// Current speed of the processor, in MHz + /// Current speed of the processor, in MHz. /// - public UInt32? CurrentClockSpeed { get; internal set; } + public uint? CurrentClockSpeed { get; internal set; } /// /// Number of cores for the current instance of the processor. /// /// /// A core is a physical processor on the integrated circuit /// - public UInt32? NumberOfCores { get; internal set; } + public uint? NumberOfCores { get; internal set; } /// /// Number of logical processors for the current instance of the processor. /// @@ -2190,7 +2163,7 @@ public class Processor /// For processors capable of hyperthreading, this value includes only the /// processors which have hyperthreading enabled /// - public UInt32? NumberOfLogicalProcessors { get; internal set; } + public uint? NumberOfLogicalProcessors { get; internal set; } /// /// Processor information that describes the processor features. /// @@ -2206,35 +2179,35 @@ public class Processor /// public string ProcessorID { get; internal set; } /// - /// Type of chip socket used on the circuit + /// Type of chip socket used on the circuit. /// public string SocketDesignation { get; internal set; } /// - /// Primary function of the processor + /// Primary function of the processor. /// public ProcessorType? ProcessorType { get; internal set; } /// - /// Role of the processor + /// Role of the processor. /// public string Role { get; internal set; } /// - /// Current status of the processor + /// Current status of the processor. /// public string Status { get; internal set; } /// /// Current status of the processor. /// Status changes indicate processor usage, but not the physical - /// condition of the processor + /// condition of the processor. /// public CpuStatus? CpuStatus { get; internal set; } /// - /// Availability and status of the processor + /// Availability and status of the processor. /// public CpuAvailability? Availability { get; internal set; } } /// - /// The ComputerInfo class is output to the PowerShell pipeline. + /// The ComputerInfo class is output to the PowerShell pipeline. /// public class ComputerInfo { @@ -2303,21 +2276,21 @@ public class ComputerInfo #region BIOS /// /// Array of BIOS characteristics supported by the system as defined by - /// the System Management BIOS Reference Specification + /// 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 /// there can be several version strings that are stored in the registry - /// and represent the system BIOS information + /// and represent the system BIOS information. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] BiosBIOSVersion { get; internal set; } /// - /// Internal identifier for this compilation of the BIOS firmware + /// Internal identifier for this compilation of the BIOS firmware. /// public string BiosBuildNumber { get; internal set; } @@ -2327,29 +2300,29 @@ public class ComputerInfo public string BiosCaption { get; internal set; } /// - /// Code set used by the BIOS + /// Code set used by the BIOS. /// public string BiosCodeSet { get; internal set; } /// - /// Name of the current BIOS language + /// Name of the current BIOS language. /// public string BiosCurrentLanguage { get; internal set; } /// - /// Description of the BIOS + /// Description of the BIOS. /// public string BiosDescription { get; internal set; } /// - /// Major version of the embedded controller firmware + /// 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 + /// 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. @@ -2361,43 +2334,43 @@ public class ComputerInfo /// /// Manufacturer's identifier for this software element. - /// Often this will be a stock keeping unit (SKU) or a part number + /// Often this will be a stock keeping unit (SKU) or a part number. /// public string BiosIdentificationCode { get; internal set; } /// /// Number of languages available for installation on this system. - /// Language may determine properties such as the need for Unicode and bidirectional text + /// Language may determine properties such as the need for Unicode and bidirectional text. /// - public UInt16? BiosInstallableLanguages { get; internal set; } + public ushort? BiosInstallableLanguages { get; internal set; } /// /// Date and time the object was installed. /// - //TODO: do we want this? On my system this is null + // TODO: do we want this? On my system this is null public DateTime? BiosInstallDate { get; internal set; } /// /// Language edition of the BIOS firmware. /// The language codes defined in ISO 639 should be used. /// Where the software element represents a multilingual or international - /// version of a product, the string "multilingual" should be used + /// version of a product, the string "multilingual" should be used. /// public string BiosLanguageEdition { get; internal set; } /// - /// Array of names of available BIOS-installable languages + /// Array of names of available BIOS-installable languages. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] BiosListOfLanguages { get; internal set; } /// - /// Manufacturer of the BIOS + /// Manufacturer of the BIOS. /// public string BiosManufacturer { get; internal set; } /// - /// Name used to identify the BIOS + /// Name used to identify the BIOS. /// public string BiosName { get; internal set; } @@ -2406,139 +2379,141 @@ public class ComputerInfo /// the BiosTargetOperatingSystem property has a value of 1 (Other). /// When TargetOperatingSystem has a value of 1, BiosOtherTargetOS must /// have a nonnull value. For all other values of BiosTargetOperatingSystem, - /// BiosOtherTargetOS is NULL + /// BiosOtherTargetOS is NULL. /// public string BiosOtherTargetOS { get; internal set; } /// - /// If true, this is the primary BIOS of the computer system + /// If true, this is the primary BIOS of the computer system. /// public bool? BiosPrimaryBIOS { get; internal set; } /// - /// Release date of the Windows BIOS + /// Release date of the Windows BIOS. /// public DateTime? BiosReleaseDate { get; internal set; } /// - /// Assigned serial number of the BIOS + /// Assigned serial number of the BIOS. /// public string BiosSerialNumber { get; internal set; } /// - /// BIOS version as reported by SMBIOS + /// BIOS version as reported by SMBIOS. /// public string BiosSMBIOSBIOSVersion { get; internal set; } /// - /// SMBIOS major version number. This property is null if SMBIOS is not found + /// SMBIOS major version number. This property is null if SMBIOS is not found. /// - public UInt16? BiosSMBIOSMajorVersion { get; internal set; } + public ushort? BiosSMBIOSMajorVersion { get; internal set; } /// - /// SMBIOS minor version number. This property is null if SMBIOS is not found + /// SMBIOS minor version number. This property is null if SMBIOS is not found. /// - public UInt16? BiosSMBIOSMinorVersion { get; internal set; } + public ushort? BiosSMBIOSMinorVersion { get; internal set; } /// - /// If true, the SMBIOS is available on this computer system + /// If true, the SMBIOS is available on this computer system. /// public bool? BiosSMBIOSPresent { get; internal set; } /// - /// State of a BIOS software element + /// State of a BIOS software element. /// public SoftwareElementState? BiosSoftwareElementState { get; internal set; } /// - /// Status of the BIOS + /// Status of the BIOS. /// public string BiosStatus { get; internal set; } /// - /// Major elease of the System BIOS + /// Major elease of the System BIOS. /// - public UInt16? BiosSystemBiosMajorVersion { get; internal set; } + public ushort? BiosSystemBiosMajorVersion { get; internal set; } /// - /// Minor release of the System BIOS + /// Minor release of the System BIOS. /// - public UInt16? BiosSystemBiosMinorVersion { get; internal set; } + public ushort? BiosSystemBiosMinorVersion { get; internal set; } /// - /// Target operating system + /// Target operating system. /// - public UInt16? BiosTargetOperatingSystem { get; internal set; } + public ushort? BiosTargetOperatingSystem { get; internal set; } /// /// Version of the BIOS. - /// This string is created by the BIOS manufacturer + /// This string is created by the BIOS manufacturer. /// public string BiosVersion { get; internal set; } #endregion BIOS #region Computer System /// - /// System hardware security settings for administrator password status + /// System hardware security settings for administrator password status. /// - //public AdminPasswordStatus? CsAdminPasswordStatus { get; internal set; } + // public AdminPasswordStatus? CsAdminPasswordStatus { get; internal set; } + public HardwareSecurity? CsAdminPasswordStatus { get; internal set; } /// - /// If true, the system manages the page file + /// If true, the system manages the page file. /// public bool? CsAutomaticManagedPagefile { get; internal set; } /// - /// If True, the automatic reset boot option is enabled + /// If True, the automatic reset boot option is enabled. /// public bool? CsAutomaticResetBootOption { get; internal set; } /// - /// If True, the automatic reset is enabled + /// If True, the automatic reset is enabled. /// public bool? CsAutomaticResetCapability { get; internal set; } /// /// Boot option limit is ON. Identifies the system action when the - /// CsResetLimit value is reached + /// CsResetLimit value is reached. /// public BootOptionAction? CsBootOptionOnLimit { get; internal set; } /// - /// Type of reboot action after the time on the watchdog timer is elapsed + /// Type of reboot action after the time on the watchdog timer is elapsed. /// public BootOptionAction? CsBootOptionOnWatchDog { get; internal set; } /// - /// If true, indicates whether a boot ROM is supported + /// If true, indicates whether a boot ROM is supported. /// public bool? CsBootROMSupported { get; internal set; } /// - /// Status and Additional Data fields that identify the boot status + /// 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 + /// System is started. Fail-safe boot bypasses the user startup files—also called SafeBoot. /// public string CsBootupState { get; internal set; } /// - /// The name of this computer + /// The name of this computer. /// - public string CsCaption { get; internal set; } //TODO: remove this? Same as CsName??? + public string CsCaption { get; internal set; } // TODO: remove this? Same as CsName??? /// - /// Boot up state of the chassis + /// Boot up state of the chassis. /// - //public ChassisBootupState? CsChassisBootupState { get; internal set; } + // public ChassisBootupState? CsChassisBootupState { get; internal set; } + public SystemElementState? CsChassisBootupState { get; internal set; } /// - /// The chassis or enclosure SKU number as a string + /// The chassis or enclosure SKU number as a string. /// public string CsChassisSKUNumber { get; internal set; } @@ -2546,20 +2521,20 @@ 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 + /// If True, the daylight savings mode is ON. /// public bool? CsDaylightInEffect { get; internal set; } /// - /// Description of the computer system + /// Description of the computer system. /// public string CsDescription { get; internal set; } /// - /// Name of local computer according to the domain name server + /// Name of local computer according to the domain name server. /// public string CsDNSHostName { get; internal set; } @@ -2574,7 +2549,7 @@ public class ComputerInfo /// /// Role of a computer in an assigned domain workgroup. A domain workgroup /// is a collection of computers on the same network. For example, - /// a DomainRole property may show that a computer is a member workstation + /// a DomainRole property may show that a computer is a member workstation. /// public DomainRole? CsDomainRole { get; internal set; } @@ -2588,57 +2563,59 @@ public class ComputerInfo public bool? CsEnableDaylightSavingsTime { get; internal set; } /// - /// Hardware security setting for the reset button on a computer + /// Hardware security setting for the reset button on a computer. /// - //public FrontPanelResetStatus? CsFrontPanelResetStatus { get; internal set; } + // public FrontPanelResetStatus? CsFrontPanelResetStatus { get; internal set; } + public HardwareSecurity? CsFrontPanelResetStatus { get; internal set; } /// - /// If True, a hypervisor is present + /// If True, a hypervisor is present. /// public bool? CsHypervisorPresent { get; internal set; } /// - /// If True, an infrared port exists on a computer system + /// If True, an infrared port exists on a computer system. /// public bool? CsInfraredSupported { get; internal set; } /// - /// Data required to find the initial load device or boot service to request that the operating system start up + /// Data required to find the initial load device or boot service to request that the operating system start up. /// public string CsInitialLoadInfo { get; internal set; } /// - /// Object is installed. An object does not need a value to indicate that it is installed + /// Object is installed. An object does not need a value to indicate that it is installed. /// public DateTime? CsInstallDate { get; internal set; } /// - /// System hardware security setting for Keyboard Password Status + /// System hardware security setting for Keyboard Password Status. /// - //public KeyboardPasswordStatus? CsKeyboardPasswordStatus { get; internal set; } + // public KeyboardPasswordStatus? CsKeyboardPasswordStatus { get; internal set; } + public HardwareSecurity? CsKeyboardPasswordStatus { get; internal set; } /// /// Array entry of the CsInitialLoadInfo property that contains the data - /// to start the loaded operating system + /// to start the loaded operating system. /// public string CsLastLoadInfo { get; internal set; } /// - /// Name of the computer manufacturer + /// Name of the computer manufacturer. /// public string CsManufacturer { get; internal set; } /// - /// Product name that a manufacturer gives to a computer + /// Product name that a manufacturer gives to a computer. /// public string CsModel { get; internal set; } /// - /// Key of a CIM_System instance in an enterprise environment + /// Key of a CIM_System instance in an enterprise environment. /// - public string CsName { get; internal set; } //TODO: get rid of this? Is this about CIM rather than about the computer? + public string CsName { get; internal set; } /// /// An array of objects describing any @@ -2648,14 +2625,14 @@ public class ComputerInfo public NetworkAdapter[] CsNetworkAdapters { get; internal set; } /// - /// If True, the network Server Mode is enabled + /// If True, the network Server Mode is enabled. /// public bool? CsNetworkServerModeEnabled { get; internal set; } /// - /// Number of logical processors available on the computer + /// Number of logical processors available on the computer. /// - public UInt32? CsNumberOfLogicalProcessors { get; internal set; } + public uint? CsNumberOfLogicalProcessors { get; internal set; } /// /// Number of physical processors currently available on a system. @@ -2667,7 +2644,7 @@ public class ComputerInfo /// then the value of CsNumberOfProcessors is 2 and CsNumberOfLogicalProcessors /// is 4. The processors may be multicore or they may be hyperthreading processors /// - public UInt32? CsNumberOfProcessors { get; internal set; } + public uint? CsNumberOfProcessors { get; internal set; } /// /// Array of objects describing each processor on the system. @@ -2678,14 +2655,14 @@ public class ComputerInfo /// /// Array of free-form strings that an OEM defines. /// For example, an OEM defines the part numbers for system reference - /// documents, manufacturer contact information, and so on + /// documents, manufacturer contact information, and so on. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CsOEMStringArray { get; internal set; } /// /// If True, the computer is part of a domain. - /// If the value is NULL, the computer is not in a domain or the status is unknown + /// If the value is NULL, the computer is not in a domain or the status is unknown. /// public bool? CsPartOfDomain { get; internal set; } @@ -2693,12 +2670,12 @@ public class ComputerInfo /// Time delay before a reboot is initiated, in milliseconds. /// It is used after a system power cycle, local or remote system reset, /// and automatic system reset. A value of –1 (minus one) indicates that - /// the pause value is unknown + /// 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 + /// Type of the computer in use, such as laptop, desktop, or tablet. /// public PCSystemType? CsPCSystemType { get; internal set; } @@ -2708,7 +2685,7 @@ public class ComputerInfo public PCSystemTypeEx? CsPCSystemTypeEx { get; internal set; } /// - /// Array of the specific power-related capabilities of a logical device + /// Array of the specific power-related capabilities of a logical device. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PowerManagementCapabilities[] CsPowerManagementCapabilities { get; internal set; } @@ -2725,9 +2702,10 @@ public class ComputerInfo public bool? CsPowerManagementSupported { get; internal set; } /// - /// System hardware security setting for Power-On Password Status + /// System hardware security setting for Power-On Password Status. /// - //public PowerOnPasswordStatus? CsPowerOnPasswordStatus { get; internal set; } + // public PowerOnPasswordStatus? CsPowerOnPasswordStatus { get; internal set; } + public HardwareSecurity? CsPowerOnPasswordStatus { get; internal set; } /// @@ -2736,19 +2714,20 @@ public class ComputerInfo public PowerState? CsPowerState { get; internal set; } /// - /// State of the power supply or supplies when last booted + /// State of the power supply or supplies when last booted. /// - //public PowerSupplyState? CsPowerSupplyState { get; internal set; } + // public PowerSupplyState? CsPowerSupplyState { get; internal set; } + public SystemElementState? CsPowerSupplyState { get; internal set; } /// /// Contact information for the primary system owner. - /// For example, phone number, email address, and so on + /// For example, phone number, email address, and so on. /// public string CsPrimaryOwnerContact { get; internal set; } /// - /// Name of the primary system owner + /// Name of the primary system owner. /// public string CsPrimaryOwnerName { get; internal set; } @@ -2759,30 +2738,30 @@ public class ComputerInfo /// /// Number of automatic resets since the last reset. - /// A value of –1 (minus one) indicates that the count is unknown + /// 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 + /// 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 - /// technology environment + /// technology environment. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CsRoles { get; internal set; } /// - /// Statis pf the computer system + /// Statis pf the computer system. /// public string CsStatus { get; internal set; } /// - /// Array of the support contact information for the Windows operating system + /// Array of the support contact information for the Windows operating system. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CsSupportContactDescription { get; internal set; } @@ -2790,25 +2769,26 @@ public class ComputerInfo /// /// The family to which a particular computer belongs. /// A family refers to a set of computers that are similar but not - /// identical from a hardware or software point of view + /// identical from a hardware or software point of view. /// public string CsSystemFamily { get; internal set; } /// /// Identifies a particular computer configuration for sale. - /// It is sometimes also called a product ID or purchase order number + /// It is sometimes also called a product ID or purchase order number. /// public string CsSystemSKUNumber { get; internal set; } /// - /// System running on the Windows-based computer + /// System running on the Windows-based computer. /// public string CsSystemType { get; internal set; } /// - /// Thermal state of the system when last booted + /// Thermal state of the system when last booted. /// - //public ThermalState? CsThermalState { get; internal set; } + // public ThermalState? CsThermalState { get; internal set; } + public SystemElementState? CsThermalState { get; internal set; } /// @@ -2819,13 +2799,13 @@ public class ComputerInfo /// return an accurate value for the physical memory. For example, /// it is not accurate if the BIOS is using some of the physical memory /// - public UInt64? CsTotalPhysicalMemory { get; internal set; } + public ulong? CsTotalPhysicalMemory { get; internal set; } /// /// Size of physically installed memory, as reported by the Windows API - /// function GetPhysicallyInstalledSystemMemory + /// function GetPhysicallyInstalledSystemMemory. /// - public UInt64? CsPhysicallyInstalledMemory { get; internal set; } + public ulong? CsPhysicallyInstalledMemory { get; internal set; } /// /// Name of a user that is logged on currently. @@ -2838,83 +2818,83 @@ public class ComputerInfo public string CsUserName { get; internal set; } /// - /// Event that causes the system to power up + /// Event that causes the system to power up. /// public WakeUpType? CsWakeUpType { get; internal set; } /// - /// Name of the workgroup for this computer + /// Name of the workgroup for this computer. /// public string CsWorkgroup { get; internal set; } #endregion Computer System #region Operating System /// - /// Name of the operating system + /// Name of the operating system. /// public string OsName { get; internal set; } /// - /// Type of operating system + /// Type of operating system. /// public OSType? OsType { get; internal set; } /// - /// SKU number for the operating system + /// SKU number for the operating system. /// public OperatingSystemSKU? OsOperatingSystemSKU { get; internal set; } /// - /// Version number of the operating system + /// Version number of the operating system. /// public string OsVersion { get; internal set; } /// /// String that indicates the latest service pack installed on a computer. - /// If no service pack is installed, the string is NULL + /// If no service pack is installed, the string is NULL. /// public string OsCSDVersion { get; internal set; } /// - /// Build number of the operating system + /// Build number of the operating system. /// public string OsBuildNumber { get; internal set; } /// /// Array of objects containing information about /// any Quick-Fix Engineering patches (Hot Fixes) applied to the operating - /// system + /// system. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public HotFix[] OsHotFixes { get; internal set; } /// - /// Name of the disk drive from which the Windows operating system starts + /// Name of the disk drive from which the Windows operating system starts. /// public string OsBootDevice { get; internal set; } /// - /// Physical disk partition on which the operating system is installed + /// Physical disk partition on which the operating system is installed. /// public string OsSystemDevice { get; internal set; } /// - /// System directory of the operating system + /// System directory of the operating system. /// public string OsSystemDirectory { get; internal set; } /// - /// Letter of the disk drive on which the operating system resides + /// Letter of the disk drive on which the operating system resides. /// public string OsSystemDrive { get; internal set; } /// - /// Windows directory of the operating system + /// Windows directory of the operating system. /// public string OsWindowsDirectory { get; internal set; } /// - /// Code for the country/region that an operating system uses + /// Code for the country/region that an operating system uses. /// /// /// Values are based on international phone dialing prefixes—also @@ -2924,9 +2904,9 @@ public class ComputerInfo /// /// Number, in minutes, an operating system is offset from Greenwich - /// mean time (GMT). The number is positive, negative, or zero + /// 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. @@ -2940,70 +2920,70 @@ public class ComputerInfo public string OsLocaleID { get; internal set; } // From Win32_OperatingSystem.Locale /// - /// The culture name, such as "en-US", derived from the property + /// The culture name, such as "en-US", derived from the property. /// public string OsLocale { get; internal set; } /// - /// Operating system version of the local date and time-of-day + /// Operating system version of the local date and time-of-day. /// public DateTime? OsLocalDateTime { get; internal set; } /// - /// Date and time the operating system was last restarted + /// Date and time the operating system was last restarted. /// public DateTime? OsLastBootUpTime { get; internal set; } /// /// The interval between the time the operating system was last - /// restarted and the current time + /// restarted and the current time. /// public TimeSpan? OsUptime { get; internal set; } /// - /// Type of build used for the operating system + /// Type of build used for the operating system. /// public string OsBuildType { get; internal set; } /// - /// Code page value the operating system uses + /// Code page value the operating system uses. /// public string OsCodeSet { get; internal set; } /// - /// If true, then the data execution prevention hardware feature is available + /// If true, then the data execution prevention hardware feature is available. /// public bool? OsDataExecutionPreventionAvailable { get; internal set; } /// /// When the data execution prevention hardware feature is available, /// this property indicates that the feature is set to work for 32-bit - /// applications if true + /// applications if true. /// public bool? OsDataExecutionPrevention32BitApplications { get; internal set; } /// /// When the data execution prevention hardware feature is available, /// this property indicates that the feature is set to work for drivers - /// if true + /// if true. /// public bool? OsDataExecutionPreventionDrivers { get; internal set; } /// /// Indicates which Data Execution Prevention (DEP) setting is applied. /// The DEP setting specifies the extent to which DEP applies to 32-bit - /// applications on the system. DEP is always applied to the Windows kernel + /// applications on the system. DEP is always applied to the Windows kernel. /// public DataExecutionPreventionSupportPolicy? OsDataExecutionPreventionSupportPolicy { get; internal set; } /// - /// If true, the operating system is a checked (debug) build + /// If true, the operating system is a checked (debug) build. /// public bool? OsDebug { get; internal set; } /// /// If True, the operating system is distributed across several computer - /// system nodes. If so, these nodes should be grouped as a cluster + /// system nodes. If so, these nodes should be grouped as a cluster. /// public bool? OsDistributed { get; internal set; } @@ -3013,7 +2993,7 @@ public class ComputerInfo public OSEncryptionLevel? OsEncryptionLevel { get; internal set; } /// - /// Increased priority given to the foreground application + /// Increased priority given to the foreground application. /// public ForegroundApplicationBoost? OsForegroundApplicationBoost { get; internal set; } @@ -3026,30 +3006,30 @@ public class ComputerInfo /// physical memory, but what is reported to the operating system /// as available to it. /// - public UInt64? OsTotalVisibleMemorySize { get; internal set; } + public ulong? OsTotalVisibleMemorySize { get; internal set; } /// - /// Number, in kilobytes, of physical memory currently unused and available + /// Number, in kilobytes, of physical memory currently unused and available. /// - public UInt64? OsFreePhysicalMemory { get; internal set; } + public ulong? OsFreePhysicalMemory { get; internal set; } /// - /// Number, in kilobytes, of virtual memory + /// Number, in kilobytes, of virtual memory. /// - public UInt64? OsTotalVirtualMemorySize { get; internal set; } + public ulong? OsTotalVirtualMemorySize { get; internal set; } /// - /// Number, in kilobytes, of virtual memory currently unused and available + /// Number, in kilobytes, of virtual memory currently unused and available. /// - public UInt64? OsFreeVirtualMemory { get; internal set; } + public ulong? OsFreeVirtualMemory { get; internal set; } /// - /// Number, in kilobytes, of virtual memory currently in use + /// Number, in kilobytes, of virtual memory currently in use. /// - public UInt64? OsInUseVirtualMemory { get; internal set; } + public ulong? OsInUseVirtualMemory { get; internal set; } /// - /// Total swap space in kilobytes + /// Total swap space in kilobytes. /// /// /// This value may be NULL (unspecified) if the swap space is not @@ -3058,24 +3038,24 @@ public class ComputerInfo /// can be swapped out when the free page list falls and remains below /// a specified amount /// - public UInt64? OsTotalSwapSpaceSize { get; internal set; } + public ulong? OsTotalSwapSpaceSize { get; internal set; } /// /// Total number of kilobytes that can be stored in the operating system /// paging files—0 (zero) indicates that there are no paging files. /// Be aware that this number does not represent the actual physical - /// size of the paging file on disk + /// size of the paging file on disk. /// - public UInt64? OsSizeStoredInPagingFiles { get; internal set; } + public ulong? OsSizeStoredInPagingFiles { get; internal set; } /// /// Number, in kilobytes, that can be mapped into the operating system - /// paging files without causing any other pages to be swapped out + /// paging files without causing any other pages to be swapped out. /// - public UInt64? OsFreeSpaceInPagingFiles { get; internal set; } + 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; } @@ -3086,7 +3066,7 @@ public class ComputerInfo public string OsHardwareAbstractionLayer { get; internal set; } /// - /// Indicates the install date + /// Indicates the install date. /// public DateTime? OsInstallDate { get; internal set; } @@ -3097,18 +3077,18 @@ public class ComputerInfo public string OsManufacturer { get; internal set; } /// - /// Maximum number of process contexts the operating system can support + /// Maximum number of process contexts the operating system can support. /// - public UInt32? OsMaxNumberOfProcesses { get; internal set; } + public uint? OsMaxNumberOfProcesses { get; internal set; } /// - /// Maximum number, in kilobytes, of memory that can be allocated to a process + /// Maximum number, in kilobytes, of memory that can be allocated to a process. /// - public UInt64? OsMaxProcessMemorySize { get; internal set; } + public ulong? OsMaxProcessMemorySize { get; internal set; } /// /// Array of Multilingual User Interface Pack (MUI Pack) languages installed - /// on the computer + /// on the computer. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] OsMuiLanguages { get; internal set; } @@ -3116,195 +3096,195 @@ public class ComputerInfo /// /// Number of user licenses for the operating system. /// - public UInt32? OsNumberOfLicensedUsers { get; internal set; } + public uint? OsNumberOfLicensedUsers { get; internal set; } /// - /// Number of process contexts currently loaded or running on the operating system + /// Number of process contexts currently loaded or running on the operating system. /// - public UInt32? OsNumberOfProcesses { get; internal set; } + public uint? OsNumberOfProcesses { get; internal set; } /// /// Number of user sessions for which the operating system is storing - /// state information currently + /// state information currently. /// - public UInt32? OsNumberOfUsers { get; internal set; } + public uint? OsNumberOfUsers { get; internal set; } /// - /// Company name for the registered user of the operating system + /// Company name for the registered user of the operating system. /// public string OsOrganization { get; internal set; } /// - /// Architecture of the operating system, as opposed to the processor + /// Architecture of the operating system, as opposed to the processor. /// public string OsArchitecture { get; internal set; } /// - /// Language version of the operating system installed + /// Language version of the operating system installed. /// public string OsLanguage { get; internal set; } /// /// Array of objects indicating installed - /// and licensed product additions to the operating system + /// and licensed product additions to the operating system. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public OSProductSuite[] OsProductSuites { get; internal set; } /// - /// Additional description for the current operating system version + /// Additional description for the current operating system version. /// public string OsOtherTypeDescription { get; internal set; } /// /// If True, the physical address extensions (PAE) are enabled by the - /// operating system running on Intel processors + /// operating system running on Intel processors. /// public bool? OsPAEEnabled { get; internal set; } /// /// Specifies whether the operating system booted from an external USB device. /// If true, the operating system has detected it is booting on a supported - /// locally connected storage device + /// locally connected storage device. /// public bool? OsPortableOperatingSystem { get; internal set; } /// - /// Specifies whether this is the primary operating system + /// Specifies whether this is the primary operating system. /// public bool? OsPrimary { get; internal set; } /// - /// Additional system information + /// Additional system information. /// public ProductType? OsProductType { get; internal set; } /// - /// Name of the registered user of the operating system + /// Name of the registered user of the operating system. /// public string OsRegisteredUser { get; internal set; } /// - /// Operating system product serial identification number + /// Operating system product serial identification number. /// public string OsSerialNumber { get; internal set; } /// - /// Major version of the service pack installed on the computer system + /// Major version of the service pack installed on the computer system. /// - public UInt16? OsServicePackMajorVersion { get; internal set; } + public ushort? OsServicePackMajorVersion { get; internal set; } /// - /// Minor version of the service pack installed on the computer system + /// Minor version of the service pack installed on the computer system. /// - public UInt16? OsServicePackMinorVersion { get; internal set; } + public ushort? OsServicePackMinorVersion { get; internal set; } /// - /// Current status + /// Current status. /// public string OsStatus { get; internal set; } /// - /// Product suites available on the operating system + /// Product suites available on the operating system. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public OSProductSuite[] OsSuites { get; internal set; } /// - /// Server level of the operating system, if the operating system is a server + /// Server level of the operating system, if the operating system is a server. /// public ServerLevel? OsServerLevel { get; internal set; } #endregion Operating System #region Misc Info /// - /// Layout of the (first) keyboard attached to the system + /// Layout of the (first) keyboard attached to the system. /// public string KeyboardLayout { get; internal set; } /// - /// Name of the system's current time zone + /// Name of the system's current time zone. /// public string TimeZone { get; internal set; } /// - /// Path to the system's logon server + /// Path to the system's logon server. /// public string LogonServer { get; internal set; } /// - /// Power platform role + /// Power platform role. /// public PowerPlatformRole? PowerPlatformRole { get; internal set; } /// - /// If true, a HyperVisor was detected + /// If true, a HyperVisor was detected. /// public bool? HyperVisorPresent { get; internal set; } /// /// If a HyperVisor is not present, indicates the state of the - /// requirement that the Data Execution Prevention feature is available + /// requirement that the Data Execution Prevention feature is available. /// public bool? HyperVRequirementDataExecutionPreventionAvailable { get; internal set; } /// /// If a HyperVisor is not present, indicates the state of the /// requirement that the processor supports address translation - /// extensions used for virtualization + /// extensions used for virtualization. /// public bool? HyperVRequirementSecondLevelAddressTranslation { get; internal set; } /// /// If a HyperVisor is not present, indicates the state of the /// requirement that the firmware has enabled virtualization - /// extensions + /// extensions. /// public bool? HyperVRequirementVirtualizationFirmwareEnabled { get; internal set; } /// /// If a HyperVisor is not present, indicates the state of the /// requirement that the processor supports Intel or AMD Virtual - /// Machine Monitor extensions + /// Machine Monitor extensions. /// public bool? HyperVRequirementVMMonitorModeExtensions { get; internal set; } /// - /// Indicates the status of the Device Guard features + /// Indicates the status of the Device Guard features. /// public DeviceGuardSmartStatus? DeviceGuardSmartStatus { get; internal set; } /// - /// Required Device Guard security properties + /// Required Device Guard security properties. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardHardwareSecure[] DeviceGuardRequiredSecurityProperties { get; internal set; } /// - /// Available Device Guard security properties + /// Available Device Guard security properties. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardHardwareSecure[] DeviceGuardAvailableSecurityProperties { get; internal set; } /// - /// Configured Device Guard security services + /// Configured Device Guard security services. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardSoftwareSecure[] DeviceGuardSecurityServicesConfigured { get; internal set; } /// - /// Running Device Guard security services + /// Running Device Guard security services. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public DeviceGuardSoftwareSecure[] DeviceGuardSecurityServicesRunning { get; internal set; } /// - /// Status of the Device Guard Code Integrity policy enforcement + /// Status of the Device Guard Code Integrity policy enforcement. /// public DeviceGuardConfigCodeIntegrityStatus? DeviceGuardCodeIntegrityPolicyEnforcementStatus { get; internal set; } /// - /// Status of the Device Guard user mode Code Integrity policy enforcement + /// Status of the Device Guard user mode Code Integrity policy enforcement. /// public DeviceGuardConfigCodeIntegrityStatus? DeviceGuardUserModeCodeIntegrityPolicyEnforcementStatus { get; internal set; } #endregion Misc Info @@ -3313,311 +3293,311 @@ public class ComputerInfo #region Enums used in the output objects /// - /// System hardware security settings for administrator password status + /// System hardware security settings for administrator password status. /// public enum AdminPasswordStatus { /// - /// Feature is disabled + /// Feature is disabled. /// Disabled = 0, /// - /// Feature is Enabled + /// Feature is Enabled. /// Enabled = 1, /// - /// Feature is not implemented + /// Feature is not implemented. /// NotImplemented = 2, /// - /// Status is unknown + /// Status is unknown. /// Unknown = 3 } /// /// Actions related to the BootOptionOn* properties of the Win32_ComputerSystem - /// CIM class + /// CIM class. /// [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 - // - //Reserved = 0, + // + // This value is reserved + // + // Reserved = 0, /// - /// Boot into operating system + /// Boot into operating system. /// OperatingSystem = 1, /// - /// Boot into system utilities + /// Boot into system utilities. /// SystemUtilities = 2, /// - /// Do not reboot + /// Do not reboot. /// DoNotReboot = 3 } /// - /// Indicates the state of a system element + /// Indicates the state of a system element. /// [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 SystemElementState { /// - /// The element state is something other than those in this Enum + /// The element state is something other than those in this Enum. /// Other = 1, /// - /// The element state is unknown + /// The element state is unknown. /// Unknown = 2, /// - /// The element is in Safe state + /// The element is in Safe state. /// Safe = 3, /// - /// The element is in Warning state + /// The element is in Warning state. /// Warning = 4, /// - /// The element is in Critical state + /// The element is in Critical state. /// Critical = 5, /// - /// The element is in Non-Recoverable state + /// The element is in Non-Recoverable state. /// NonRecoverable = 6 } /// - /// Specifies the processor architecture + /// Specifies the processor architecture. /// public enum CpuArchitecture { /// - /// Architecture is Intel x86 + /// Architecture is Intel x86. /// x86 = 0, /// - /// Architecture is MIPS + /// Architecture is MIPS. /// MIPs = 1, /// - /// Architecture is DEC Alpha + /// Architecture is DEC Alpha. /// Alpha = 2, /// - /// Architecture is Motorola PowerPC + /// Architecture is Motorola PowerPC. /// PowerPC = 3, /// - /// Architecture is ARM + /// Architecture is ARM. /// ARM = 5, /// - /// Architecture is Itanium-based 64-bit + /// Architecture is Itanium-based 64-bit. /// ia64 = 6, /// - /// Architecture is Intel 64-bit + /// Architecture is Intel 64-bit. /// x64 = 9 } /// - /// Specifies a CPU's availability and status + /// Specifies a CPU's availability and status. /// [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 CpuAvailability { /// - /// A state other than those specified in CpuAvailability + /// A state other than those specified in CpuAvailability. /// Other = 1, /// - /// Availability status is unknown + /// Availability status is unknown. /// Unknown = 2, /// - /// The device is running or at full power + /// The device is running or at full power. /// RunningOrFullPower = 3, /// - /// Device is in a Warning state + /// Device is in a Warning state. /// Warning = 4, /// - /// Availability status is In Test + /// Availability status is In Test. /// InTest = 5, /// - /// Status is not applicable to this device + /// Status is not applicable to this device. /// NotApplicable = 6, /// - /// The device is powered off + /// The device is powered off. /// PowerOff = 7, /// - /// Availability status is Offline + /// Availability status is Offline. /// OffLine = 8, /// - /// Availability status is Off-Duty + /// Availability status is Off-Duty. /// OffDuty = 9, /// - /// Availability status is Degraded + /// Availability status is Degraded. /// Degraded = 10, /// - /// Availability status is Not Installed + /// Availability status is Not Installed. /// NotInstalled = 11, /// - /// Availability status is Install Error + /// Availability status is Install Error. /// InstallError = 12, /// - /// The device is known to be in a power save state, but its exact status is unknown + /// The device is known to be in a power save state, but its exact status is unknown. /// PowerSaveUnknown = 13, /// /// The device is in a power save state, but is still functioning, - /// and may exhibit decreased performance + /// and may exhibit decreased performance. /// PowerSaveLowPowerMode = 14, /// - /// The device is not functioning, but can be brought to full power quickly + /// The device is not functioning, but can be brought to full power quickly. /// PowerSaveStandby = 15, /// - /// The device is in a power-cycle state + /// The device is in a power-cycle state. /// PowerCycle = 16, /// - /// The device is in a warning state, though also in a power save state + /// The device is in a warning state, though also in a power save state. /// PowerSaveWarning = 17, /// - /// The device is paused + /// The device is paused. /// Paused = 18, /// - /// The device is not ready + /// The device is not ready. /// NotReady = 19, /// - /// The device is not configured + /// The device is not configured. /// NotConfigured = 20, /// - /// The device is quiet + /// The device is quiet. /// Quiesced = 21 } /// - /// Specifies that current status of the processor + /// Specifies that current status of the processor. /// [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Justification = "The underlying MOF definition is not a bit field.")] public enum CpuStatus { /// - /// CPU status is Unknown + /// CPU status is Unknown. /// Unknown = 0, /// - /// CPU status is Enabled + /// CPU status is Enabled. /// Enabled = 1, /// - /// CPU status is Disabled by User via BIOS Setup + /// CPU status is Disabled by User via BIOS Setup. /// DisabledByUser = 2, /// - /// CPU status is Disabled by BIOS + /// CPU status is Disabled by BIOS. /// DisabledByBIOS = 3, /// - /// CPU is Idle + /// CPU is Idle. /// Idle = 4, // // This value is reserved // - //Reserved_5 = 5, + // Reserved_5 = 5, // // This value is reserved // - //Reserved_6 = 6, + // Reserved_6 = 6, /// - /// CPU is in another state + /// CPU is in another state. /// Other = 7 } /// - /// Data Execution Prevention (DEP) settings + /// Data Execution Prevention (DEP) settings. /// public enum DataExecutionPreventionSupportPolicy { - //Unknown = -1, + // Unknown = -1, /// - /// DEP is turned off for all 32-bit applications on the computer with no exceptions + /// DEP is turned off for all 32-bit applications on the computer with no exceptions. /// AlwaysOff = 0, /// - /// DEP is enabled for all 32-bit applications on the computer + /// DEP is enabled for all 32-bit applications on the computer. /// AlwaysOn = 1, @@ -3626,311 +3606,326 @@ public enum DataExecutionPreventionSupportPolicy /// Windows-based services. However, it is off by default for all 32-bit /// applications. A user or administrator must explicitly choose either /// the Always On or the Opt Out setting before DEP can be applied to - /// 32-bit applications + /// 32-bit applications. /// OptIn = 2, /// /// DEP is enabled by default for all 32-bit applications. A user or /// administrator can explicitly remove support for a 32-bit - /// application by adding the application to an exceptions list + /// application by adding the application to an exceptions list. /// OptOut = 3 } /// - /// Status of the Device Guard feature + /// Status of the Device Guard feature. /// public enum DeviceGuardSmartStatus { /// - /// Device Guard is off + /// Device Guard is off. /// Off = 0, /// - /// Device Guard is Configured + /// Device Guard is Configured. /// Configured = 1, /// - /// Device Guard is Running + /// Device Guard is Running. /// Running = 2 } /// - /// Configuration status of the Device Guard Code Integrity + /// Configuration status of the Device Guard Code Integrity. /// public enum DeviceGuardConfigCodeIntegrityStatus { /// - /// Code Integrity is off + /// Code Integrity is off. /// Off = 0, /// - /// Code Integrity uses Audit mode + /// Code Integrity uses Audit mode. /// AuditMode = 1, /// - /// Code Integrity uses Enforcement mode + /// Code Integrity uses Enforcement mode. /// EnforcementMode = 2 } /// - /// Device Guard hardware security properties + /// Device Guard hardware security properties. /// [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 DeviceGuardHardwareSecure { /// - /// Base Virtualization Support + /// Base Virtualization Support. /// BaseVirtualizationSupport = 1, /// - /// Secure Boot + /// Secure Boot. /// SecureBoot = 2, /// - /// DMA Protection + /// DMA Protection. /// DMAProtection = 3, /// - /// Secure Memory Overwrite + /// Secure Memory Overwrite. + /// + SecureMemoryOverwrite = 4, + + /// + /// UEFI Code Readonly. + /// + UEFICodeReadonly = 5, + + /// + /// SMM Security Mitigations 1.0. /// - SecureMemoryOverwrite = 4 + SMMSecurityMitigations = 6, + + /// + /// Mode Based Execution Control. + /// + ModeBasedExecutionControl = 7 } /// - /// Device Guard software security properties + /// Device Guard software security properties. /// [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 DeviceGuardSoftwareSecure { /// - /// Credential Guard + /// Credential Guard. /// CredentialGuard = 1, /// - /// Hypervisor enforced Code Integrity + /// Hypervisor enforced Code Integrity. /// HypervisorEnforcedCodeIntegrity = 2 } /// - /// Role of a computer in an assigned domain workgroup + /// Role of a computer in an assigned domain workgroup. /// public enum DomainRole { /// - /// Standalone Workstation + /// Standalone Workstation. /// StandaloneWorkstation = 0, /// - /// Member Workstation + /// Member Workstation. /// MemberWorkstation = 1, /// - /// Standalone Server + /// Standalone Server. /// StandaloneServer = 2, /// - /// Member Server + /// Member Server. /// MemberServer = 3, /// - /// Backup Domain Controller + /// Backup Domain Controller. /// BackupDomainController = 4, /// - /// Primary Domain Controller + /// Primary Domain Controller. /// PrimaryDomainController = 5 } /// - /// Specifies a firmware type + /// Specifies a firmware type. /// public enum FirmwareType { /// - /// The firmware type is unknown + /// The firmware type is unknown. /// Unknown = 0, /// - /// The computer booted in legacy BIOS mode + /// The computer booted in legacy BIOS mode. /// Bios = 1, /// - /// The computer booted in UEFI mode + /// The computer booted in UEFI mode. /// Uefi = 2, /// - /// Not implemented + /// Not implemented. /// Max = 3 } /// - /// Increase in priority given to the foreground application + /// Increase in priority given to the foreground application. /// public enum ForegroundApplicationBoost { /// - /// The system boosts the quantum length by 6 + /// The system boosts the quantum length by 6. /// None = 0, /// - /// The system boosts the quantum length by 12 + /// The system boosts the quantum length by 12. /// Minimum = 1, /// - /// The system boosts the quantum length by 18 + /// The system boosts the quantum length by 18. /// Maximum = 2 } /// - /// hardware security settings for the reset button on a computer + /// Hardware security settings for the reset button on a computer. /// public enum FrontPanelResetStatus { /// - /// Reset button is disabled + /// Reset button is disabled. /// Disabled = 0, /// - /// Reset button is enabled + /// Reset button is enabled. /// Enabled = 1, /// - /// Hardware security settings are not implement + /// Hardware security settings are not implement. /// NotImplemented = 2, /// - /// Unknown security setting + /// Unknown security setting. /// Unknown = 3 } /// - /// Indicates a hardware security setting + /// Indicates a hardware security setting. /// public enum HardwareSecurity { /// - /// Hardware security is disabled + /// Hardware security is disabled. /// Disabled = 0, /// - /// Hardware security is enabled + /// Hardware security is enabled. /// Enabled = 1, /// - /// Hardware security is not implemented + /// Hardware security is not implemented. /// NotImplemented = 2, /// - /// Hardware security setting is unknown + /// Hardware security setting is unknown. /// Unknown = 3 } /// - /// State of the network adapter connection to the network + /// State of the network adapter connection to the network. /// public enum NetConnectionStatus { /// - /// Adapter is disconnected + /// Adapter is disconnected. /// Disconnected = 0, /// - /// Adapter is connecting + /// Adapter is connecting. /// Connecting = 1, /// - /// Adapter is connected + /// Adapter is connected. /// Connected = 2, /// - /// Adapter is disconnecting + /// Adapter is disconnecting. /// Disconnecting = 3, /// - /// Adapter hardware is not present + /// Adapter hardware is not present. /// HardwareNotPresent = 4, /// - /// Adapter hardware is disabled + /// Adapter hardware is disabled. /// HardwareDisabled = 5, /// - /// Adapter has a hardware malfunction + /// Adapter has a hardware malfunction. /// HardwareMalfunction = 6, /// - /// Media is disconnected + /// Media is disconnected. /// MediaDisconnected = 7, /// - /// Adapter is authenticating + /// Adapter is authenticating. /// Authenticating = 8, /// - /// Authentication has succeeded + /// Authentication has succeeded. /// AuthenticationSucceeded = 9, /// - /// Authentication has failed + /// Authentication has failed. /// AuthenticationFailed = 10, /// - /// Address is invalid + /// Address is invalid. /// InvalidAddress = 11, /// - /// Credentials are required + /// Credentials are required. /// CredentialsRequired = 12, /// - /// Other unspecified state + /// Other unspecified state. /// Other = 13 } @@ -3941,23 +3936,23 @@ public enum NetConnectionStatus public enum OSEncryptionLevel { /// - /// 40-bit encryption + /// 40-bit encryption. /// Encrypt40Bits = 0, /// - /// 128-bit encryption + /// 128-bit encryption. /// Encrypt128Bits = 1, /// - /// n-bit encryption + /// N-bit encryption. /// EncryptNBits = 2 } /// - /// Indicates installed and licensed system product additions to the operating system + /// Indicates installed and licensed system product additions to the operating system. /// [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "The underlying MOF definition does not contain a zero value. The converter method will handle it appropriately.")] [FlagsAttribute] @@ -3965,68 +3960,68 @@ public enum OSProductSuite { /// /// Microsoft Small Business Server was once installed, but may have - /// been upgraded to another version of Windows + /// been upgraded to another version of Windows. /// SmallBusinessServer = 0x0001, /// - /// Windows Server 2008 Enterprise is installed + /// Windows Server 2008 Enterprise is installed. /// Server2008Enterprise = 0x0002, /// - /// Windows BackOffice components are installed + /// Windows BackOffice components are installed. /// BackOfficeComponents = 0x0004, /// - /// Communication Server is installed + /// Communication Server is installed. /// CommunicationsServer = 0x0008, /// - /// Terminal Services is installed + /// Terminal Services is installed. /// TerminalServices = 0x0010, /// /// Microsoft Small Business Server is installed with the restrictive - /// client license + /// client license. /// SmallBusinessServerRestricted = 0x0020, /// - /// Windows Embedded is installed + /// Windows Embedded is installed. /// WindowsEmbedded = 0x0040, /// - /// A Datacenter edition is installed + /// A Datacenter edition is installed. /// DatacenterEdition = 0x0080, /// - /// Terminal Services is installed, but only one interactive session is supported + /// Terminal Services is installed, but only one interactive session is supported. /// TerminalServicesSingleSession = 0x0100, /// - /// Windows Home Edition is installed + /// Windows Home Edition is installed. /// HomeEdition = 0x0200, /// - /// Web Server Edition is installed + /// Web Server Edition is installed. /// WebServerEdition = 0x0400, /// - /// Storage Server Edition is installed + /// Storage Server Edition is installed. /// StorageServerEdition = 0x2000, /// - /// Compute Cluster Edition is installed + /// Compute Cluster Edition is installed. /// ComputeClusterEdition = 0x4000 } @@ -4037,147 +4032,147 @@ public enum OSProductSuite public enum OperatingSystemSKU { /// - /// The SKU is undefined + /// The SKU is undefined. /// Undefined = 0, /// - /// SKU is Ultimate Edition + /// SKU is Ultimate Edition. /// UltimateEdition = 1, /// - /// SKU is Home Basic Edition + /// SKU is Home Basic Edition. /// HomeBasicEdition = 2, /// - /// SKU is Home Premium Edition + /// SKU is Home Premium Edition. /// HomePremiumEdition = 3, /// - /// SKU is Enterprise Edition + /// SKU is Enterprise Edition. /// EnterpriseEdition = 4, /// - /// SKU is Home Basic N Edition + /// SKU is Home Basic N Edition. /// HomeBasicNEdition = 5, /// - /// SKU is Business Edition + /// SKU is Business Edition. /// BusinessEdition = 6, /// - /// SKU is Standard Server Edition + /// SKU is Standard Server Edition. /// StandardServerEdition = 7, /// - /// SKU is Datacenter Server Edition + /// SKU is Datacenter Server Edition. /// DatacenterServerEdition = 8, /// - /// SKU is Small Business Server Edition + /// SKU is Small Business Server Edition. /// SmallBusinessServerEdition = 9, /// - /// SKU is Enterprise Server Edition + /// SKU is Enterprise Server Edition. /// EnterpriseServerEdition = 10, /// - /// SKU is Starter Edition + /// SKU is Starter Edition. /// StarterEdition = 11, /// - /// SKU is Datacenter Server Core Edition + /// SKU is Datacenter Server Core Edition. /// DatacenterServerCoreEdition = 12, /// - /// SKU is Standard Server Core Edition + /// SKU is Standard Server Core Edition. /// StandardServerCoreEdition = 13, /// - /// SKU is Enterprise Server Core Edition + /// SKU is Enterprise Server Core Edition. /// EnterpriseServerCoreEdition = 14, /// - /// SKU is Enterprise Server IA64 Edition + /// SKU is Enterprise Server IA64 Edition. /// EnterpriseServerIA64Edition = 15, /// - /// SKU is Business N Edition + /// SKU is Business N Edition. /// BusinessNEdition = 16, /// - /// SKU is Web Server Edition + /// SKU is Web Server Edition. /// WebServerEdition = 17, /// - /// SKU is Cluster Server Edition + /// SKU is Cluster Server Edition. /// ClusterServerEdition = 18, /// - /// SKU is Home Server Edition + /// SKU is Home Server Edition. /// HomeServerEdition = 19, /// - /// SKU is Storage Express Server Edition + /// SKU is Storage Express Server Edition. /// StorageExpressServerEdition = 20, /// - /// SKU is Storage Standard Server Edition + /// SKU is Storage Standard Server Edition. /// StorageStandardServerEdition = 21, /// - /// SKU is Storage Workgroup Server Edition + /// SKU is Storage Workgroup Server Edition. /// StorageWorkgroupServerEdition = 22, /// - /// SKU is Storage Enterprise Server Edition + /// SKU is Storage Enterprise Server Edition. /// StorageEnterpriseServerEdition = 23, /// - /// SKU is Server For Small Business Edition + /// SKU is Server For Small Business Edition. /// ServerForSmallBusinessEdition = 24, /// - /// SKU is Small Business Server Premium Edition + /// SKU is Small Business Server Premium Edition. /// SmallBusinessServerPremiumEdition = 25, /// - /// SKU is to be determined + /// SKU is to be determined. /// TBD = 26, /// - /// SKU is Windows Enterprise + /// SKU is Windows Enterprise. /// WindowsEnterprise = 27, /// - /// SKU is Windows Ultimate + /// SKU is Windows Ultimate. /// WindowsUltimate = 28, @@ -4187,17 +4182,17 @@ public enum OperatingSystemSKU WebServerCore = 29, /// - /// SKU is Server Foundation + /// SKU is Server Foundation. /// ServerFoundation = 33, /// - /// SKU is Windows Home Server + /// SKU is Windows Home Server. /// WindowsHomeServer = 34, /// - /// SKU is Windows Server Standard without Hyper-V + /// SKU is Windows Server Standard without Hyper-V. /// WindowsServerStandardNoHyperVFull = 36, @@ -4227,7 +4222,7 @@ public enum OperatingSystemSKU WindowsServerEnterpriseNoHyperVCore = 41, /// - /// SKU is Microsoft Hyper-V Server + /// SKU is Microsoft Hyper-V Server. /// MicrosoftHyperVServer = 42, @@ -4252,7 +4247,7 @@ public enum OperatingSystemSKU StorageServerEnterpriseCore = 46, /// - /// SKU is Windows Small Business Server 2011 Essentials + /// SKU is Windows Small Business Server 2011 Essentials. /// WindowsSmallBusinessServer2011Essentials = 50, @@ -4262,597 +4257,597 @@ public enum OperatingSystemSKU SmallBusinessServerPremiumCore = 63, /// - /// SKU is Windows Server Hyper Core V + /// SKU is Windows Server Hyper Core V. /// WindowsServerHyperCoreV = 64, /// - /// SKU is Windows Thin PC + /// SKU is Windows Thin PC. /// WindowsThinPC = 87, /// - /// SKU is Windows Embedded Industry + /// SKU is Windows Embedded Industry. /// WindowsEmbeddedIndustry = 89, /// - /// SKU is Windows RT + /// SKU is Windows RT. /// WindowsRT = 97, /// - /// SKU is Windows Home + /// SKU is Windows Home. /// WindowsHome = 101, /// - /// SKU is Windows Professional with Media Center + /// SKU is Windows Professional with Media Center. /// WindowsProfessionalWithMediaCenter = 103, /// - /// SKU is Windows Mobile + /// SKU is Windows Mobile. /// WindowsMobile = 104, /// - /// SKU is Windows Embedded Handheld + /// SKU is Windows Embedded Handheld. /// WindowsEmbeddedHandheld = 118, /// - /// SKU is Windows IoT (Internet of Things) Core + /// SKU is Windows IoT (Internet of Things) Core. /// WindowsIotCore = 123 } /// - /// Type of operating system + /// Type of operating system. /// public enum OSType { /// - /// OS is unknown + /// OS is unknown. /// Unknown = 0, /// - /// OS is one other than covered by this Enum + /// OS is one other than covered by this Enum. /// Other = 1, /// - /// OS is MacOS + /// OS is MacOS. /// MACROS = 2, /// - /// OS is AT&T UNIX + /// OS is AT&T UNIX. /// ATTUNIX = 3, /// - /// OS is DG/UX + /// OS is DG/UX. /// DGUX = 4, /// - /// OS is DECNT + /// OS is DECNT. /// DECNT = 5, /// - /// OS is Digital UNIX + /// OS is Digital UNIX. /// DigitalUNIX = 6, /// - /// OS is OpenVMS + /// OS is OpenVMS. /// OpenVMS = 7, /// - /// OS is HP-UX + /// OS is HP-UX. /// HPUX = 8, /// - /// OS is AIX + /// OS is AIX. /// AIX = 9, /// - /// OS is MVS + /// OS is MVS. /// MVS = 10, /// - /// OS is OS/400 + /// OS is OS/400. /// OS400 = 11, /// - /// OS is OS/2 + /// OS is OS/2. /// OS2 = 12, /// - /// OS is Java Virtual Machine + /// OS is Java Virtual Machine. /// JavaVM = 13, /// - /// OS is MS-DOS + /// OS is MS-DOS. /// MSDOS = 14, /// - /// OS is Windows 3x + /// OS is Windows 3x. /// WIN3x = 15, /// - /// OS is Windows 95 + /// OS is Windows 95. /// WIN95 = 16, /// - /// OS is Windows 98 + /// OS is Windows 98. /// WIN98 = 17, /// - /// OS is Windows NT + /// OS is Windows NT. /// WINNT = 18, /// - /// OS is Windows CE + /// OS is Windows CE. /// WINCE = 19, /// - /// OS is NCR System 3000 + /// OS is NCR System 3000. /// NCR3000 = 20, /// - /// OS is NetWare + /// OS is NetWare. /// NetWare = 21, /// - /// OS is OSF + /// OS is OSF. /// OSF = 22, /// - /// OS is DC/OS + /// OS is DC/OS. /// DC_OS = 23, /// - /// OS is Reliant UNIX + /// OS is Reliant UNIX. /// ReliantUNIX = 24, /// - /// OS is SCO UnixWare + /// OS is SCO UnixWare. /// SCOUnixWare = 25, /// - /// OS is SCO OpenServer + /// OS is SCO OpenServer. /// SCOOpenServer = 26, /// - /// OS is Sequent + /// OS is Sequent. /// Sequent = 27, /// - /// OS is IRIX + /// OS is IRIX. /// IRIX = 28, /// - /// OS is Solaris + /// OS is Solaris. /// Solaris = 29, /// - /// OS is SunOS + /// OS is SunOS. /// SunOS = 30, /// - /// OS is U6000 + /// OS is U6000. /// U6000 = 31, /// - /// OS is ASERIES + /// OS is ASERIES. /// ASERIES = 32, /// - /// OS is Tandem NSK + /// OS is Tandem NSK. /// TandemNSK = 33, /// - /// OS is Tandem NT + /// OS is Tandem NT. /// TandemNT = 34, /// - /// OS is BS2000 + /// OS is BS2000. /// BS2000 = 35, /// - /// OS is Linux + /// OS is Linux. /// LINUX = 36, /// - /// OS is Lynx + /// OS is Lynx. /// Lynx = 37, /// - /// OS is XENIX + /// OS is XENIX. /// XENIX = 38, /// - /// OS is VM/ESA + /// OS is VM/ESA. /// VM_ESA = 39, /// - /// OS is Interactive UNIX + /// OS is Interactive UNIX. /// InteractiveUNIX = 40, /// - /// OS is BSD UNIX + /// OS is BSD UNIX. /// BSDUNIX = 41, /// - /// OS is FreeBSD + /// OS is FreeBSD. /// FreeBSD = 42, /// - /// OS is NetBSD + /// OS is NetBSD. /// NetBSD = 43, /// - /// OS is GNU Hurd + /// OS is GNU Hurd. /// GNUHurd = 44, /// - /// OS is OS 9 + /// OS is OS 9. /// OS9 = 45, /// - /// OS is Mach Kernel + /// OS is Mach Kernel. /// MACHKernel = 46, /// - /// OS is Inferno + /// OS is Inferno. /// Inferno = 47, /// - /// OS is QNX + /// OS is QNX. /// QNX = 48, /// - /// OS is EPOC + /// OS is EPOC. /// EPOC = 49, /// - /// OS is IxWorks + /// OS is IxWorks. /// IxWorks = 50, /// - /// OS is VxWorks + /// OS is VxWorks. /// VxWorks = 51, /// - /// OS is MiNT + /// OS is MiNT. /// MiNT = 52, /// - /// OS is BeOS + /// OS is BeOS. /// BeOS = 53, /// - /// OS is HP MPE + /// OS is HP MPE. /// HP_MPE = 54, /// - /// OS is NextStep + /// OS is NextStep. /// NextStep = 55, /// - /// OS is PalmPilot + /// OS is PalmPilot. /// PalmPilot = 56, /// - /// OS is Rhapsody + /// OS is Rhapsody. /// Rhapsody = 57, /// - /// OS is Windows 2000 + /// OS is Windows 2000. /// Windows2000 = 58, /// - /// OS is Dedicated + /// OS is Dedicated. /// Dedicated = 59, /// - /// OS is OS/390 + /// OS is OS/390. /// OS_390 = 60, /// - /// OS is VSE + /// OS is VSE. /// VSE = 61, /// - /// OS is TPF + /// OS is TPF. /// TPF = 62 } /// - /// Specifies the type of the computer in use, such as laptop, desktop, or Tablet + /// Specifies the type of the computer in use, such as laptop, desktop, or Tablet. /// public enum PCSystemType { /// - /// System type is unspecified + /// System type is unspecified. /// Unspecified = 0, /// - /// System is a desktop + /// System is a desktop. /// Desktop = 1, /// - /// System is a mobile device + /// System is a mobile device. /// Mobile = 2, /// - /// System is a workstation + /// System is a workstation. /// Workstation = 3, /// - /// System is an Enterprise Server + /// System is an Enterprise Server. /// EnterpriseServer = 4, /// - /// System is a Small Office and Home Office (SOHO) Server + /// System is a Small Office and Home Office (SOHO) Server. /// SOHOServer = 5, /// - /// System is an appliance PC + /// System is an appliance PC. /// AppliancePC = 6, /// - /// System is a performance server + /// System is a performance server. /// PerformanceServer = 7, /// - /// Maximum enum value + /// Maximum enum value. /// Maximum = 8 } /// /// Specifies the type of the computer in use, such as laptop, desktop, or Tablet. - /// This is an extended version of PCSystemType + /// This is an extended version of PCSystemType. /// - //TODO: conflate these two enums??? + // TODO: conflate these two enums??? public enum PCSystemTypeEx { /// - /// System type is unspecified + /// System type is unspecified. /// Unspecified = 0, /// - /// System is a desktop + /// System is a desktop. /// Desktop = 1, /// - /// System is a mobile device + /// System is a mobile device. /// Mobile = 2, /// - /// System is a workstation + /// System is a workstation. /// Workstation = 3, /// - /// System is an Enterprise Server + /// System is an Enterprise Server. /// EnterpriseServer = 4, /// - /// System is a Small Office and Home Office (SOHO) Server + /// System is a Small Office and Home Office (SOHO) Server. /// SOHOServer = 5, /// - /// System is an appliance PC + /// System is an appliance PC. /// AppliancePC = 6, /// - /// System is a performance server + /// System is a performance server. /// PerformanceServer = 7, /// - /// System is a Slate + /// System is a Slate. /// Slate = 8, /// - /// Maximum enum value + /// Maximum enum value. /// Maximum = 9 } /// - /// Specifies power-related capabilities of a logical device + /// Specifies power-related capabilities of a logical device. /// public enum PowerManagementCapabilities { /// - /// Unknown capability + /// Unknown capability. /// Unknown = 0, /// - /// Power management not supported + /// Power management not supported. /// NotSupported = 1, /// - /// Power management features are currently disabled + /// Power management features are currently disabled. /// Disabled = 2, /// /// The power management features are currently enabled, - /// but the exact feature set is unknown or the information is unavailable + /// but the exact feature set is unknown or the information is unavailable. /// Enabled = 3, /// - /// The device can change its power state based on usage or other criteria + /// The device can change its power state based on usage or other criteria. /// PowerSavingModesEnteredAutomatically = 4, /// - /// The power state may be set through the Win32_LogicalDevice class + /// The power state may be set through the Win32_LogicalDevice class. /// PowerStateSettable = 5, /// - /// Power may be done through the Win32_LogicalDevice class + /// Power may be done through the Win32_LogicalDevice class. /// PowerCyclingSupported = 6, /// - /// Timed power-on is supported + /// Timed power-on is supported. /// TimedPowerOnSupported = 7 } /// - /// Specified power states + /// Specified power states. /// public enum PowerState { /// - /// Power state is unknown + /// Power state is unknown. /// Unknown = 0, /// - /// Full power + /// Full power. /// FullPower = 1, /// - /// Power Save - Low Power mode + /// Power Save - Low Power mode. /// PowerSaveLowPowerMode = 2, /// - /// Power Save - Standby + /// Power Save - Standby. /// PowerSaveStandby = 3, /// - /// Unknown Power Save mode + /// Unknown Power Save mode. /// PowerSaveUnknown = 4, /// - /// Power Cycle + /// Power Cycle. /// PowerCycle = 5, /// - /// Power Off + /// Power Off. /// PowerOff = 6, /// - /// Power Save - Warning + /// Power Save - Warning. /// PowerSaveWarning = 7, /// - /// Power Save - Hibernate + /// Power Save - Hibernate. /// PowerSaveHibernate = 8, /// - /// Power Save - Soft off + /// Power Save - Soft off. /// PowerSaveSoftOff = 9 } /// - /// Specifies the primary function of a processor + /// Specifies the primary function of a processor. /// [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 ProcessorType { /// - /// Processor ype is other than provided in these enumeration values + /// Processor ype is other than provided in these enumeration values. /// Other = 1, /// - /// Processor type is + /// Processor type is. /// Unknown = 2, @@ -4862,7 +4857,7 @@ public enum ProcessorType CentralProcessor = 3, /// - /// Processor is a Math processor + /// Processor is a Math processor. /// MathProcessor = 4, @@ -4872,45 +4867,45 @@ public enum ProcessorType DSPProcessor = 5, /// - /// Processor is a Video processor + /// Processor is a Video processor. /// VideoProcessor = 6 } /// - /// Specifies a computer's reset capability + /// Specifies a computer's reset capability. /// [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 ResetCapability { /// - /// Capability is a value other than provided in these enumerated values + /// Capability is a value other than provided in these enumerated values. /// Other = 1, /// - /// Reset capability is unknown + /// Reset capability is unknown. /// Unknown = 2, /// - /// Capability is disabled + /// Capability is disabled. /// Disabled = 3, /// - /// Capability is enabled + /// Capability is enabled. /// Enabled = 4, /// - /// Capability is not implemented + /// Capability is not implemented. /// NotImplemented = 5 } /// - /// Specifies the kind of event that causes a computer to power up + /// Specifies the kind of event that causes a computer to power up. /// [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 WakeUpType @@ -4918,61 +4913,61 @@ public enum WakeUpType // // This value is reserved // - //Reserved = 0, + // Reserved = 0, /// - /// An event other than specified in this enumeration + /// An event other than specified in this enumeration. /// Other = 1, /// - /// Event type is unknown + /// Event type is unknown. /// Unknown = 2, /// - /// Event is APM timer + /// Event is APM timer. /// APMTimer = 3, /// - /// Event is a Modem Ring + /// Event is a Modem Ring. /// ModemRing = 4, /// - /// Event is a LAN Remove + /// Event is a LAN Remove. /// LANRemote = 5, /// - /// Event is a power switch + /// Event is a power switch. /// PowerSwitch = 6, /// - /// Event is a PCI PME# signal + /// Event is a PCI PME# signal. /// PCIPME = 7, /// - /// AC power was restored + /// AC power was restored. /// ACPowerRestored = 8 } /// - /// Indicates the OEM's preferred power management profile + /// Indicates the OEM's preferred power management profile. /// public enum PowerPlatformRole { /// - /// The OEM did not specify a specific role + /// The OEM did not specify a specific role. /// Unspecified = 0, /// - /// The OEM specified a desktop role + /// The OEM specified a desktop role. /// Desktop = 1, @@ -4982,120 +4977,120 @@ public enum PowerPlatformRole Mobile = 2, /// - /// The OEM specified a workstation role + /// The OEM specified a workstation role. /// Workstation = 3, /// - /// The OEM specified an enterprise server role + /// The OEM specified an enterprise server role. /// EnterpriseServer = 4, /// - /// The OEM specified a single office/home office (SOHO) server role + /// The OEM specified a single office/home office (SOHO) server role. /// SOHOServer = 5, /// - /// The OEM specified an appliance PC role + /// The OEM specified an appliance PC role. /// AppliancePC = 6, /// - /// The OEM specified a performance server role + /// The OEM specified a performance server role. /// PerformanceServer = 7, // v1 last supported /// - /// The OEM specified a tablet form factor role + /// The OEM specified a tablet form factor role. /// Slate = 8, // v2 last supported /// - /// Max enum value + /// Max enum value. /// MaximumEnumValue } /// - /// Additional system information, from Win32_OperatingSystem + /// Additional system information, from Win32_OperatingSystem. /// public enum ProductType { /// - /// Product type is unknown + /// Product type is unknown. /// Unknown = 0, // this value is not specified in Win32_OperatingSystem, but may prove useful /// - /// System is a workstation + /// System is a workstation. /// WorkStation = 1, /// - /// System is a domain controller + /// System is a domain controller. /// DomainController = 2, /// - /// System is a server + /// System is a server. /// Server = 3 } /// - /// Specifies the system server level + /// Specifies the system server level. /// public enum ServerLevel { /// - /// An unknown or unrecognized level was detected + /// An unknown or unrecognized level was detected. /// Unknown = 0, /// - /// Nano server + /// Nano server. /// NanoServer, /// - /// Server core + /// Server core. /// ServerCore, /// - /// Server core with management tools + /// Server core with management tools. /// ServerCoreWithManagementTools, /// - /// Full server + /// Full server. /// FullServer } /// - /// State of a software element + /// State of a software element. /// public enum SoftwareElementState { /// - /// Software element is deployable + /// Software element is deployable. /// Deployable = 0, /// - /// Software element is installable + /// Software element is installable. /// Installable = 1, /// - /// Software element is executable + /// Software element is executable. /// Executable = 2, /// - /// Software element is running + /// Software element is running. /// Running = 3 } @@ -5103,12 +5098,11 @@ public enum SoftwareElementState #endregion Output components #region Native - internal static class Native + internal static partial class Native { private static class PInvokeDllNames { public const string GetPhysicallyInstalledSystemMemoryDllName = "api-ms-win-core-sysinfo-l1-2-1.dll"; - public const string LCIDToLocaleNameDllName = "kernelbase.dll"; public const string PowerDeterminePlatformRoleExDllName = "api-ms-win-power-base-l1-1-0.dll"; public const string GetFirmwareTypeDllName = "api-ms-win-core-kernel32-legacy-l1-1-1"; } @@ -5117,66 +5111,46 @@ 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 + /// 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); + /// The version of the POWER_PLATFORM_ROLE enumeration for the platform. + /// POWER_PLATFORM_ROLE enumeration. + [LibraryImport(PInvokeDllNames.PowerDeterminePlatformRoleExDllName, EntryPoint = "PowerDeterminePlatformRoleEx")] + public static partial uint PowerDeterminePlatformRoleEx(uint version); /// - /// Retrieve the amount of RAM physically installed in the computer + /// 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 + /// Retrieve the firmware type of the local computer. /// /// /// A reference to a enumeration to contain /// the resultant firmware type /// /// - [DllImport(PInvokeDllNames.GetFirmwareTypeDllName, SetLastError = true)] + [LibraryImport(PInvokeDllNames.GetFirmwareTypeDllName)] [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetFirmwareType(out FirmwareType firmwareType); - - /// - /// Convert a Local Identifier to a Locale name - /// - /// The Locale ID (LCID) to be converted - /// Destination of the Locale name - /// Capacity of - /// - /// - [DllImport(PInvokeDllNames.LCIDToLocaleNameDllName, SetLastError = true, CharSet = CharSet.Unicode)] - public static extern int LCIDToLocaleName(uint localeID, System.Text.StringBuilder localeName, int localeNameSize, int flags); + public static partial bool GetFirmwareType(out FirmwareType firmwareType); /// /// Gets the data specified for the passed in property name from the - /// Software Licensing API + /// Software Licensing API. /// /// 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); - /* - * SLGetWindowsInformationDWORD function returns - * S_OK (0x00000000): If the method succeeds - * SL_E_RIGHT_NOT_GRANTED (0xC004F013): The caller does not have the permissions necessary to call this function. - * SL_E_DATATYPE_MISMATCHED (0xC004F013): The value portion of the name-value pair is not a DWORD. - [DllImport("Slc.dll", EntryPoint = "SLGetWindowsInformationDWORD", CharSet = CharSet.Unicode)] - public static extern UInt32 SLGetWindowsInformationDWORD(string pwszValueName, ref int pdwValue); - */ + [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 dee0e3835f5..a9d8772a5fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -9,14 +8,15 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to get the content of an item at a specified path + /// A command to get the content of an item at a specified path. /// - [Cmdlet(VerbsCommon.Get, "Content", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113310")] + [Cmdlet(VerbsCommon.Get, "Content", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096490")] public class GetContentCommand : ContentCommandBase { #region Parameters @@ -27,97 +27,62 @@ public class GetContentCommand : ContentCommandBase /// at a time. To read all blocks at once, set this value /// to a negative number. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] 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; - } // get - - set - { - _totalCount = value; - _totalCountSpecified = true; - } - } // TotalCount - 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 /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Content.GetContentReaderDynamicParameters(Path[0], context); } + return InvokeProvider.Content.GetContentReaderDynamicParameters(".", context); - } // GetDynamicParameters + } #endregion Parameters - #region parameter data - - /// - /// The number of content items to retrieve. - /// - private long _totalCount = -1; - - #endregion parameter data - #region Command code /// - /// Gets the content of an item at the specified path + /// Gets the content of an item at the specified path. /// 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; } @@ -139,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. @@ -166,7 +129,7 @@ protected override void ProcessRecord() catch (Exception e) { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentReadError", SessionStateStrings.ProviderContentReadError, holder.PathInfo.Provider, @@ -195,94 +158,83 @@ 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) && (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)); - } - } // foreach holder in contentStreams + } + } while (results != null && results.Count > 0 && (TotalCount == -1 || countRead < TotalCount)); + } } finally { - // close all the content readers + // Close all the content readers CloseContent(contentStreams, false); // Empty the content holder array contentStreams = new List(); } - } // ProcessRecord + } /// - /// Scan forwards to get the tail content + /// Scan forwards to get the tail content. /// /// /// /// - /// 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; @@ -295,7 +247,7 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur catch (Exception e) { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentReadError", SessionStateStrings.ProviderContentReadError, holder.PathInfo.Provider, @@ -325,7 +277,10 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur foreach (object entry in results) { if (tailResultQueue.Count == Tail) + { tailResultQueue.Dequeue(); + } + tailResultQueue.Enqueue(entry); } } @@ -339,39 +294,36 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur if (ReadCount <= 0 || (ReadCount >= tailResultQueue.Count && ReadCount != 1)) { count = tailResultQueue.Count; - ArrayList outputList = new ArrayList(); - while (tailResultQueue.Count > 0) - { - outputList.Add(tailResultQueue.Dequeue()); - } + // Write out the content as an array of objects - WriteContentObject(outputList.ToArray(), count, holder.PathInfo, currentContext); + WriteContentObject(tailResultQueue.ToArray(), count, holder.PathInfo, currentContext); } else if (ReadCount == 1) { // 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) { - ArrayList outputList = new ArrayList(); + 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) { - ArrayList outputList = new ArrayList(); - for (; remainder > 0; remainder--, count++) - outputList.Add(tailResultQueue.Dequeue()); // Write out the content as an array of objects - WriteContentObject(outputList.ToArray(), count, holder.PathInfo, currentContext); + WriteContentObject(tailResultQueue.ToArray(), count, holder.PathInfo, currentContext); } } } @@ -386,7 +338,7 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur } /// - /// Seek position to the right place + /// Seek position to the right place. /// /// /// reader should be able to be casted to FileSystemContentReader @@ -414,7 +366,7 @@ private bool SeekPositionForTail(IContentReader reader) } /// - /// Be sure to clean up + /// Be sure to clean up. /// protected override void EndProcessing() { @@ -422,6 +374,5 @@ protected override void EndProcessing() } #endregion Command code - } // GetContentCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs index 7cf2b94cd7d..7523a497390 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs @@ -1,24 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to get the property of an item at a specified path + /// A command to get the property of an item at a specified path. /// - [Cmdlet(VerbsCommon.Get, "ItemProperty", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113320")] + [Cmdlet(VerbsCommon.Get, "ItemProperty", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096493")] public class GetItemPropertyCommand : ItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -27,38 +25,37 @@ public string[] Path get { return paths; - } // get + } set { paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return paths; - } // get + } set { base.SuppressWildcardExpansion = true; paths = value; - } // set - } // LiteralPath + } + } /// - /// The properties to retrieve from the item + /// The properties to retrieve from the item. /// - /// [Parameter(Position = 1)] [Alias("PSProperty")] public string[] Name @@ -66,29 +63,26 @@ public string[] Name get { return _property; - } // get + } set { _property = value; } - } // Property + } /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) @@ -97,10 +91,11 @@ internal override object GetDynamicParameters(CmdletProviderContext context) Path[0], SessionStateUtilities.ConvertArrayToCollection(_property), context); } + return InvokeProvider.Property.GetPropertyDynamicParameters( ".", SessionStateUtilities.ConvertArrayToCollection(_property), context); - } // GetDynamicParameters + } #endregion Parameters @@ -116,7 +111,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #region Command code /// - /// Gets the properties of an item at the specified path + /// Gets the properties of an item at the specified path. /// protected override void ProcessRecord() { @@ -162,21 +157,21 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - } // GetItemPropertyCommand + } /// /// A command to get the property value of an item at a specified path. /// - [Cmdlet(VerbsCommon.Get, "ItemPropertyValue", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=389281")] + [Cmdlet(VerbsCommon.Get, "ItemPropertyValue", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096906")] public sealed class GetItemPropertyValueCommand : ItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = false, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] @@ -186,38 +181,37 @@ public string[] Path get { return paths; - } // get + } set { paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { get { return paths; - } // get + } set { base.SuppressWildcardExpansion = true; paths = value; - } // set - } // LiteralPath + } + } /// - /// The properties to retrieve from the item + /// The properties to retrieve from the item. /// - /// [Parameter(Position = 1, Mandatory = true)] [Alias("PSProperty")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] @@ -226,29 +220,26 @@ public string[] Name get { return _property; - } // get + } set { _property = value; } - } // Property + } /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) @@ -257,10 +248,11 @@ internal override object GetDynamicParameters(CmdletProviderContext context) Path[0], SessionStateUtilities.ConvertArrayToCollection(_property), context); } + return InvokeProvider.Property.GetPropertyDynamicParameters( ".", SessionStateUtilities.ConvertArrayToCollection(_property), context); - } // GetDynamicParameters + } #endregion Parameters @@ -284,6 +276,7 @@ protected override void ProcessRecord() { paths = new string[] { "." }; } + foreach (string path in Path) { try @@ -349,4 +342,4 @@ protected override void ProcessRecord() #endregion Command code } -} // 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 f7182e46d75..6b6551619f1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -21,6 +21,5 @@ protected override void EndProcessing() { WriteObject(this.Context.TransactionManager.GetCurrent()); } - } // GetTransactionCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs index e6b85ded55e..27f812c7a80 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs @@ -1,19 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; using System.Globalization; -using System.Management.Automation; using System.Management; +using System.Management.Automation; using System.Text; -using System.Collections; using System.Threading; namespace Microsoft.PowerShell.Commands { /// - /// A command to get WMI Objects + /// A command to get WMI Objects. /// [Cmdlet(VerbsCommon.Get, "WmiObject", DefaultParameterSetName = "query", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113337", RemotingCapability = RemotingCapability.OwnedByCommand)] @@ -21,41 +20,41 @@ public class GetWmiObjectCommand : WmiBaseCmdlet { #region Parameters - /// - /// The WMI class to query + /// The WMI class to query. /// [Alias("ClassName")] [Parameter(Position = 0, Mandatory = true, ParameterSetName = "query")] [Parameter(Position = 1, ParameterSetName = "list")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string Class { get; set; } /// - /// To specify whether to get the results recursively + /// To specify whether to get the results recursively. /// [Parameter(ParameterSetName = "list")] public SwitchParameter Recurse { get; set; } = false; /// - /// The WMI properties to retrieve + /// The WMI properties to retrieve. /// [Parameter(Position = 1, ParameterSetName = "query")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Property { get { return (string[])_property.Clone(); } + set { _property = value; } } /// - /// The filter to be used in the search + /// The filter to be used in the search. /// [Parameter(ParameterSetName = "query")] public string Filter { get; set; } /// - /// If Amended qualifier to use + /// If Amended qualifier to use. /// [Parameter] public SwitchParameter Amended { get; set; } @@ -68,13 +67,13 @@ public string[] Property public SwitchParameter DirectRead { get; set; } /// - /// The list of classes + /// The list of classes. /// [Parameter(ParameterSetName = "list")] public SwitchParameter List { get; set; } = false; /// - /// The query string to search for objects + /// The query string to search for objects. /// [Parameter(Mandatory = true, ParameterSetName = "WQLQuery")] public string Query { get; set; } @@ -90,19 +89,20 @@ public string[] Property #region Command code /// - /// Uses this.filter, this.wmiClass and this.property to retrieve the filter + /// Uses this.filter, this.wmiClass and this.property to retrieve the filter. /// internal string GetQueryString() { StringBuilder returnValue = new StringBuilder("select "); - returnValue.Append(String.Join(", ", _property)); + returnValue.Append(string.Join(", ", _property)); returnValue.Append(" from "); returnValue.Append(Class); - if (!String.IsNullOrEmpty(Filter)) + if (!string.IsNullOrEmpty(Filter)) { returnValue.Append(" where "); returnValue.Append(Filter); } + return returnValue.ToString(); } /// @@ -126,6 +126,7 @@ internal string GetFilterClassName() filterClass = filterClass.Replace('?', '_'); return filterClass; } + internal bool IsLocalizedNamespace(string sNamespace) { bool toReturn = false; @@ -133,8 +134,10 @@ internal bool IsLocalizedNamespace(string sNamespace) { toReturn = true; } + return toReturn; } + internal bool ValidateClassFormat() { string filterClass = this.Class; @@ -143,7 +146,7 @@ internal bool ValidateClassFormat() StringBuilder newClassName = new StringBuilder(); for (int i = 0; i < filterClass.Length; i++) { - if (Char.IsLetterOrDigit(filterClass[i]) || + if (char.IsLetterOrDigit(filterClass[i]) || filterClass[i].Equals('[') || filterClass[i].Equals(']') || filterClass[i].Equals('*') || filterClass[i].Equals('?') || filterClass[i].Equals('-')) @@ -158,14 +161,16 @@ internal bool ValidateClassFormat() newClassName.Append(']'); continue; } + return false; } + this.Class = newClassName.ToString(); return true; } /// - /// Gets the ManagementObjectSearcher object + /// Gets the ManagementObjectSearcher object. /// internal ManagementObjectSearcher GetObjectList(ManagementScope scope) { @@ -183,6 +188,7 @@ internal ManagementObjectSearcher GetObjectList(ManagementScope scope) queryStringBuilder.Append(filterClass); queryStringBuilder.Append("'"); } + ObjectQuery classQuery = new ObjectQuery(queryStringBuilder.ToString()); EnumerationOptions enumOptions = new EnumerationOptions(); @@ -192,7 +198,7 @@ internal ManagementObjectSearcher GetObjectList(ManagementScope scope) return searcher; } /// - /// Gets the properties of an item at the specified path + /// Gets the properties of an item at the specified path. /// protected override void BeginProcessing() { @@ -210,7 +216,7 @@ protected override void BeginProcessing() { ErrorRecord errorRecord = new ErrorRecord( new ArgumentException( - String.Format( + string.Format( Thread.CurrentThread.CurrentCulture, "Class", this.Class)), "INVALID_QUERY_IDENTIFIER", @@ -221,6 +227,7 @@ protected override void BeginProcessing() WriteError(errorRecord); return; } + foreach (string name in ComputerName) { if (this.Recurse.IsPresent) @@ -327,6 +334,7 @@ protected override void BeginProcessing() WriteError(errorRecord); continue; } + ManagementObjectSearcher searcher = this.GetObjectList(scope); if (searcher == null) continue; @@ -336,6 +344,7 @@ protected override void BeginProcessing() } } } + return; } @@ -371,19 +380,19 @@ protected override void BeginProcessing() if (e.ErrorCode.Equals(ManagementStatus.InvalidClass)) { string className = GetClassNameFromQuery(queryString); - string errorMsg = String.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, + string errorMsg = string.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, e.Message, className); errorRecord = new ErrorRecord(new ManagementException(errorMsg), "GetWMIManagementException", ErrorCategory.InvalidType, null); } else if (e.ErrorCode.Equals(ManagementStatus.InvalidQuery)) { - string errorMsg = String.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, + string errorMsg = string.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, e.Message, queryString); errorRecord = new ErrorRecord(new ManagementException(errorMsg), "GetWMIManagementException", ErrorCategory.InvalidArgument, null); } else if (e.ErrorCode.Equals(ManagementStatus.InvalidNamespace)) { - string errorMsg = String.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, + string errorMsg = string.Format(CultureInfo.InvariantCulture, WmiResources.WmiQueryFailure, e.Message, this.Namespace); errorRecord = new ErrorRecord(new ManagementException(errorMsg), "GetWMIManagementException", ErrorCategory.InvalidArgument, null); } @@ -401,12 +410,12 @@ protected override void BeginProcessing() WriteError(errorRecord); continue; } - } // foreach computerName + } } - } // BeginProcessing + } /// - /// Get the class name from a query string + /// Get the class name from a query string. /// /// /// @@ -427,6 +436,5 @@ private string GetClassNameFromQuery(string query) } #endregion Command code - } // GetWmiObjectCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs index 8f10d92d370..d0f15346396 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs @@ -1,41 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if !UNIX using System; -using System.Text; -using System.Text.RegularExpressions; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics; // Process class -using System.ComponentModel; // Win32Exception -using System.Globalization; -using System.Runtime.Serialization; -using System.Security.Permissions; -using System.Threading; +using System.Diagnostics.CodeAnalysis; using System.Management; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; -using System.Net; -using System.IO; -using System.Security; using System.Security.Principal; -using System.Security.AccessControl; -using Dbg = System.Management.Automation; - +using System.Text; namespace Microsoft.PowerShell.Commands { #region Get-HotFix /// - /// Cmdlet for Get-Hotfix Proxy + /// Cmdlet for Get-Hotfix Proxy. /// [Cmdlet(VerbsCommon.Get, "HotFix", DefaultParameterSetName = "Default", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135217", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2109716", RemotingCapability = RemotingCapability.SupportedByCommand)] [OutputType(@"System.Management.ManagementObject#root\cimv2\Win32_QuickFixEngineering")] public sealed class GetHotFixCommand : PSCmdlet, IDisposable { @@ -51,7 +35,7 @@ public sealed class GetHotFixCommand : PSCmdlet, IDisposable public string[] Id { get; set; } /// - /// To search on description of Hotfixes + /// To search on description of Hotfixes. /// [Parameter(ParameterSetName = "Description")] [ValidateNotNullOrEmpty] @@ -59,7 +43,7 @@ public sealed class GetHotFixCommand : PSCmdlet, IDisposable public string[] Description { get; set; } /// - /// Parameter to pass the Computer Name + /// Parameter to pass the Computer Name. /// [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] @@ -79,47 +63,64 @@ public sealed class GetHotFixCommand : PSCmdlet, IDisposable #region Overrides - private ManagementObjectSearcher _searchProcess; private bool _inputContainsWildcard = false; + private readonly ConnectionOptions _connectionOptions = new(); + /// - /// Get the List of HotFixes installed on the Local Machine. + /// Sets connection options. /// protected override void BeginProcessing() + { + _connectionOptions.Authentication = AuthenticationLevel.Packet; + _connectionOptions.Impersonation = ImpersonationLevel.Impersonate; + _connectionOptions.Username = Credential?.UserName; + _connectionOptions.SecurePassword = Credential?.Password; + } + + /// + /// Get the List of HotFixes installed on the Local Machine. + /// + protected override void ProcessRecord() { foreach (string computer in ComputerName) { bool foundRecord = false; - StringBuilder QueryString = new StringBuilder(); - ConnectionOptions conOptions = ComputerWMIHelper.GetConnectionOptions(AuthenticationLevel.Packet, ImpersonationLevel.Impersonate, this.Credential); - ManagementScope scope = new ManagementScope(ComputerWMIHelper.GetScopeString(computer, ComputerWMIHelper.WMI_Path_CIM), conOptions); + StringBuilder queryString = new(); + ManagementScope scope = new(ComputerWMIHelper.GetScopeString(computer, ComputerWMIHelper.WMI_Path_CIM), _connectionOptions); scope.Connect(); if (Id != null) { - QueryString.Append("Select * from Win32_QuickFixEngineering where ("); + queryString.Append("Select * from Win32_QuickFixEngineering where ("); for (int i = 0; i <= Id.Length - 1; i++) { - QueryString.Append("HotFixID= '"); - QueryString.Append(Id[i].ToString().Replace("'", "\\'")); - QueryString.Append("'"); + queryString.Append("HotFixID= '"); + queryString.Append(Id[i].Replace("'", "\\'")); + queryString.Append('\''); if (i < Id.Length - 1) - QueryString.Append(" Or "); + { + queryString.Append(" Or "); + } } - QueryString.Append(")"); + + queryString.Append(')'); } else { - QueryString.Append("Select * from Win32_QuickFixEngineering"); + queryString.Append("Select * from Win32_QuickFixEngineering"); foundRecord = true; } - _searchProcess = new ManagementObjectSearcher(scope, new ObjectQuery(QueryString.ToString())); + + _searchProcess = new ManagementObjectSearcher(scope, new ObjectQuery(queryString.ToString())); foreach (ManagementObject obj in _searchProcess.Get()) { if (Description != null) { if (!FilterMatch(obj)) + { continue; + } } else { @@ -129,54 +130,46 @@ protected override void BeginProcessing() // try to translate the SID to a more friendly username // just stick with the SID if anything goes wrong string installed = (string)obj["InstalledBy"]; - if (!String.IsNullOrEmpty(installed)) + if (!string.IsNullOrEmpty(installed)) { try { - SecurityIdentifier secObj = new SecurityIdentifier(installed); - obj["InstalledBy"] = secObj.Translate(typeof(NTAccount)); ; + SecurityIdentifier secObj = new(installed); + obj["InstalledBy"] = secObj.Translate(typeof(NTAccount)); } - catch (IdentityNotMappedException) // thrown by SecurityIdentifier.Translate + catch (IdentityNotMappedException) { + // thrown by SecurityIdentifier.Translate } - catch (SystemException) // thrown by SecurityIdentifier.constr + catch (SystemException) { + // thrown by SecurityIdentifier.constr } - //catch (ArgumentException) // thrown (indirectly) by SecurityIdentifier.constr (on XP only?) - //{ catch not needed - this is already caught as SystemException - //} - //catch (PlatformNotSupportedException) // thrown (indirectly) by SecurityIdentifier.Translate (on Win95 only?) - //{ catch not needed - this is already caught as SystemException - //} - //catch (UnauthorizedAccessException) // thrown (indirectly) by SecurityIdentifier.Translate - //{ catch not needed - this is already caught as SystemException - //} } WriteObject(obj); foundRecord = true; } + if (!foundRecord && !_inputContainsWildcard) { - Exception Ex = new ArgumentException(StringUtil.Format(HotFixResources.NoEntriesFound, computer)); - WriteError(new ErrorRecord(Ex, "GetHotFixNoEntriesFound", ErrorCategory.ObjectNotFound, null)); + Exception ex = new ArgumentException(StringUtil.Format(HotFixResources.NoEntriesFound, computer)); + WriteError(new ErrorRecord(ex, "GetHotFixNoEntriesFound", ErrorCategory.ObjectNotFound, null)); } + if (_searchProcess != null) { this.Dispose(); } } - }//end of BeginProcessing method + } /// - /// to implement ^C + /// To implement ^C. /// protected override void StopProcessing() { - if (_searchProcess != null) - { - _searchProcess.Dispose(); - } + _searchProcess?.Dispose(); } #endregion Overrides @@ -193,6 +186,7 @@ private bool FilterMatch(ManagementObject obj) { return true; } + if (WildcardPattern.ContainsWildcardCharacters(desc)) { _inputContainsWildcard = true; @@ -203,6 +197,7 @@ private bool FilterMatch(ManagementObject obj) { return false; } + return false; } @@ -211,32 +206,16 @@ 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" - }//end class + } #endregion -}//Microsoft.Powershell.commands +} + +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/InvokeWMIMethodCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/InvokeWMIMethodCommand.cs index fa275c3303a..df0c5775ca1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/InvokeWMIMethodCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/InvokeWMIMethodCommand.cs @@ -1,23 +1,22 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Management; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Management; -using System.Text; using System.Management.Automation.Provider; -using System.ComponentModel; -using System.Collections; -using System.Collections.ObjectModel; -using System.Security.AccessControl; using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; namespace Microsoft.PowerShell.Commands { /// - /// A command to Invoke WMI Method + /// A command to Invoke WMI Method. /// [Cmdlet(VerbsLifecycle.Invoke, "WmiMethod", DefaultParameterSetName = "class", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113346", RemotingCapability = RemotingCapability.OwnedByCommand)] @@ -25,45 +24,48 @@ public sealed class InvokeWmiMethod : WmiBaseCmdlet { #region Parameters /// - /// The WMI Object to use + /// The WMI Object to use. /// - /// [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = "object")] public ManagementObject InputObject { get { return _inputObject; } + set { _inputObject = value; } } /// - /// The WMI Path to use + /// The WMI Path to use. /// [Parameter(ParameterSetName = "path", Mandatory = true)] public string Path { get { return _path; } + set { _path = value; } } /// - /// The WMI class to use + /// The WMI class to use. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "class")] public string Class { get { return _className; } + set { _className = value; } } /// - /// The WMI Method to execute + /// The WMI Method to execute. /// [Parameter(Position = 1, Mandatory = true)] public string Name { get { return _methodName; } + set { _methodName = value; } } /// - /// The parameters to the method specified by MethodName + /// The parameters to the method specified by MethodName. /// [Parameter(ParameterSetName = "path")] [Parameter(Position = 2, ParameterSetName = "class")] @@ -72,6 +74,7 @@ public string Name public object[] ArgumentList { get { return _argumentList; } + set { _argumentList = value; } } @@ -96,6 +99,7 @@ protected override void ProcessRecord() RunAsJob("Invoke-WMIMethod"); return; } + if (_inputObject != null) { object result = null; @@ -114,6 +118,7 @@ protected override void ProcessRecord() inParamCount--; } } + if (!ShouldProcess( StringUtil.Format(WmiResources.WmiMethodNameForConfirmation, _inputObject["__CLASS"].ToString(), @@ -122,6 +127,7 @@ protected override void ProcessRecord() { return; } + result = _inputObject.InvokeMethod(_methodName, inputParameters, null); } catch (ManagementException e) @@ -134,10 +140,12 @@ protected override void ProcessRecord() ErrorRecord errorRecord = new ErrorRecord(e, "InvokeWMICOMException", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } + if (result != null) { WriteObject(result); } + return; } else @@ -149,13 +157,13 @@ protected override void ProcessRecord() if (_path != null) { mPath = new ManagementPath(_path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = this.Namespace; } else if (namespaceSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "NamespaceSpecifiedWithPath", @@ -165,20 +173,21 @@ protected override void ProcessRecord() if (mPath.Server != "." && serverNameSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "ComputerNameSpecifiedWithPath", ErrorCategory.InvalidOperation, ComputerName)); } - //If server name is specified loop through it. + // If server name is specified loop through it. if (!(mPath.Server == "." && serverNameSpecified)) { string[] serverName = new string[] { mPath.Server }; ComputerName = serverName; } } + foreach (string name in ComputerName) { result = null; @@ -197,6 +206,7 @@ protected override void ProcessRecord() ManagementObject mInstance = new ManagementObject(mPath); mObject = mInstance; } + ManagementScope mScope = new ManagementScope(mPath, options); mObject.Scope = mScope; } @@ -207,6 +217,7 @@ protected override void ProcessRecord() mObject = mClass; mObject.Scope = scope; } + ManagementBaseObject inputParameters = mObject.GetMethodParameters(_methodName); if (_argumentList != null) { @@ -224,9 +235,11 @@ protected override void ProcessRecord() { property.Value = argument; } + inParamCount--; } } + if (!ShouldProcess( StringUtil.Format(WmiResources.WmiMethodNameForConfirmation, mObject["__CLASS"].ToString(), @@ -235,6 +248,7 @@ protected override void ProcessRecord() { return; } + result = mObject.InvokeMethod(_methodName, inputParameters, null); } catch (ManagementException e) @@ -247,13 +261,14 @@ protected override void ProcessRecord() ErrorRecord errorRecord = new ErrorRecord(e, "InvokeWMICOMException", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } + if (result != null) { WriteObject(result); } } } - }//ProcessRecord + } /// /// Ensure that the argument is a collection containing no PSObjects. @@ -280,6 +295,7 @@ private static object MakeBaseObjectArray(object argument) break; } } + if (needCopy) { var copiedArgument = new object[listArgument.Count]; @@ -288,6 +304,7 @@ private static object MakeBaseObjectArray(object argument) { copiedArgument[index++] = argElement != null ? PSObject.Base(argElement) : null; } + return copiedArgument; } else @@ -297,5 +314,5 @@ private static object MakeBaseObjectArray(object argument) } #endregion Command code - }//InvokeWMIObject + } } 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 d51f40b7cae..a40cae64b9f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs @@ -1,42 +1,45 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to move a property on an item to another item + /// A command to move a property on an item to another item. /// [Cmdlet(VerbsCommon.Move, "ItemProperty", SupportsShouldProcess = true, DefaultParameterSetName = "Path", SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113351")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096817")] public class MoveItemPropertyCommand : PassThroughItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { get { return paths; } + set { paths = value; } } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } + set { base.SuppressWildcardExpansion = true; @@ -45,20 +48,21 @@ public string[] LiteralPath } /// - /// The name of the property to create on the item + /// The name of the property to create on the item. /// - /// [Parameter(Position = 2, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] public string[] Name { - get { return _property; } + get + { + return _property; + } + set { - if (value == null) - { - value = Utils.EmptyArray(); - } + value ??= Array.Empty(); + _property = value; } } @@ -66,7 +70,6 @@ public string[] Name /// /// The path to the destination item to copy the property to. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] public string Destination { get; set; } @@ -75,19 +78,16 @@ public string[] Name /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { - string propertyName = String.Empty; + string propertyName = string.Empty; if (Name != null && Name.Length > 0) { propertyName = Name[0]; @@ -97,13 +97,14 @@ internal override object GetDynamicParameters(CmdletProviderContext context) { return InvokeProvider.Property.MovePropertyDynamicParameters(Path[0], propertyName, Destination, propertyName, context); } + return InvokeProvider.Property.MovePropertyDynamicParameters( ".", propertyName, Destination, propertyName, context); - } // GetDynamicParameters + } #endregion Parameters @@ -112,14 +113,14 @@ internal override object GetDynamicParameters(CmdletProviderContext context) /// /// The property to be created. /// - private string[] _property = new string[0]; + private string[] _property = Array.Empty(); #endregion parameter data #region Command code /// - /// Creates the property on the item + /// Creates the property on the item. /// protected override void ProcessRecord() { @@ -165,9 +166,8 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code - - } // MoveItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index 7378d2bf508..6ab8f1d652d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -25,9 +25,8 @@ 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")] - internal static Dbg.PSTraceSource tracer = Dbg.PSTraceSource.GetTracer("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 @@ -40,7 +39,7 @@ internal virtual CmdletProviderContext CmdletProviderContext { get { - CmdletProviderContext coreCommandContext = new CmdletProviderContext(this); + CmdletProviderContext coreCommandContext = new(this); coreCommandContext.Force = Force; @@ -57,19 +56,14 @@ internal virtual CmdletProviderContext CmdletProviderContext return coreCommandContext; } - } // CmdletProviderContext + } internal virtual SwitchParameter SuppressWildcardExpansion { - get - { - return _suppressWildcardExpansion; - } - set - { - _suppressWildcardExpansion = value; - } + get => _suppressWildcardExpansion; + set => _suppressWildcardExpansion = value; } + private bool _suppressWildcardExpansion; /// @@ -77,45 +71,31 @@ internal virtual SwitchParameter SuppressWildcardExpansion /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// - internal virtual object GetDynamicParameters(CmdletProviderContext context) - { - return null; - } + internal virtual object GetDynamicParameters(CmdletProviderContext context) => null; /// /// Called by the base implementation that checks the SupportShouldProcess provider /// capability. This virtual method gives the /// derived cmdlet a chance query the CmdletProvider capabilities to determine - /// if the provider supports ShouldProcess + /// if the provider supports ShouldProcess. /// /// - protected virtual bool ProviderSupportsShouldProcess - { - get - { - return true; - } - } // ProviderSupportsShouldProcess + protected virtual bool ProviderSupportsShouldProcess => true; /// /// A helper for derived classes to call to determine if the paths specified - /// are for a provider that supports ShouldProcess + /// are for a provider that supports ShouldProcess. /// - /// /// /// The paths to check to see if the providers support ShouldProcess. /// - /// /// /// If the paths are to different providers, and any don't support /// ShouldProcess, then the return value is false. If they all @@ -127,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) { @@ -152,6 +132,7 @@ protected bool DoesProviderSupportShouldProcess(string[] paths) } } } + return result; } @@ -159,19 +140,11 @@ protected bool DoesProviderSupportShouldProcess(string[] paths) /// The dynamic parameters which have already been retrieved from the provider /// and bound by the command processor. /// - /// - protected internal object RetrievedDynamicParameters - { - get - { - return _dynamicParameters; - } // get - } // RetrievedDynamicParameters + protected internal object RetrievedDynamicParameters => _dynamicParameters; /// /// The dynamic parameters for the command. They are retrieved using the /// GetDynamicParameters virtual method. /// - /// private object _dynamicParameters; #endregion Protected members @@ -183,72 +156,58 @@ protected internal object RetrievedDynamicParameters /// CmdletProviderContext to tunnel the stop message to /// the provider instance. /// - /// protected override void StopProcessing() { foreach (CmdletProviderContext stopContext in stopContextCollection) { stopContext.StopProcessing(); } - } // StopProcessing + } + internal Collection stopContextCollection = - new Collection(); + new(); /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// - /// /// /// This is meant to be overridden by derived classes if /// they support the Filter parameter. This property is on /// the base class to simplify the creation of the CmdletProviderContext. /// - /// public virtual string Filter { get; set; } - /// - /// Gets or sets the include property + /// Gets or sets the include property. /// - /// /// /// This is meant to be overridden by derived classes if /// they support the Include parameter. This property is on /// the base class to simplify the creation of the CmdletProviderContext. /// - /// - public virtual string[] Include { get; -// get + public virtual string[] Include + { + get; set; -// set - } = new string[0]; - -// Include - + } = Array.Empty(); /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// - /// /// /// This is meant to be overridden by derived classes if /// they support the Exclude parameter. This property is on /// the base class to simplify the creation of the CmdletProviderContext. /// - /// - public virtual string[] Exclude { get; -// get + public virtual string[] Exclude + { + get; set; -// set - } = new string[0]; - -// Exclude - + } = Array.Empty(); /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -262,26 +221,18 @@ protected override void StopProcessing() /// they support the Force parameter. This property is on /// the base class to simplify the creation of the CmdletProviderContext. /// - /// public virtual SwitchParameter Force { - get - { - return _force; - } - set - { - _force = value; - } - } // Force - private bool _force; + get => _force; + set => _force = value; + } + private bool _force; /// /// Retrieves the dynamic parameters for the command from /// the provider. /// - /// public object GetDynamicParameters() { // Don't stream errors or Write* to the pipeline. @@ -306,22 +257,15 @@ public object GetDynamicParameters() } return _dynamicParameters; - } // GetDynamicParameters + } /// - /// Determines if the cmdlet and CmdletProvider supports ShouldProcess + /// Determines if the cmdlet and CmdletProvider supports ShouldProcess. /// - /// - public bool SupportsShouldProcess - { - get - { - return ProviderSupportsShouldProcess; - } - } // SupportsShouldProcess + public bool SupportsShouldProcess => ProviderSupportsShouldProcess; #endregion Public members - } // class CoreCommandBase + } #endregion CoreCommandBase @@ -336,11 +280,10 @@ public class CoreCommandWithCredentialsBase : CoreCommandBase #region Parameters /// - /// Gets or sets the credential parameter + /// Gets or sets the credential parameter. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] - [Credential()] + [Credential] public PSCredential Credential { get; set; } #endregion Parameters @@ -358,7 +301,7 @@ internal override CmdletProviderContext CmdletProviderContext { get { - CmdletProviderContext coreCommandContext = new CmdletProviderContext(this, Credential); + CmdletProviderContext coreCommandContext = new(this, Credential); coreCommandContext.Force = Force; Collection includeFilter = @@ -374,10 +317,10 @@ internal override CmdletProviderContext CmdletProviderContext return coreCommandContext; } - } // CmdletProviderContext + } #endregion Protected members - } // CoreCommandWithCredentialsBase + } #endregion CoreCommandWithCredentialsBase @@ -388,54 +331,34 @@ internal override CmdletProviderContext CmdletProviderContext /// This command does things like list the contents of a container, get /// an item at a given path, get the current working directory, etc. /// - /// /// /// - /// - [Cmdlet(VerbsCommon.Get, "Location", DefaultParameterSetName = "Location", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113321")] - [OutputType(typeof(PathInfo), ParameterSetName = new string[] { "locationSet" })] - [OutputType(typeof(PathInfoStack), ParameterSetName = new string[] { "Stack" })] + [Cmdlet(VerbsCommon.Get, "Location", DefaultParameterSetName = LocationParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096495")] + [OutputType(typeof(PathInfo), ParameterSetName = new string[] { LocationParameterSet })] + [OutputType(typeof(PathInfoStack), ParameterSetName = new string[] { StackParameterSet })] public class GetLocationCommand : DriveMatchingCoreCommandBase { - /// - /// The string declaration for the Location parameter set in this command. - /// - /// - /// The "Location" parameter set includes the following parameters: - /// -location - /// - private const string locationSet = "Location"; - - /// - /// The string declaration for the Stack parameter set in this command. - /// - /// - /// The "Stack" parameter set includes the following parameters: - /// -stack - /// - private const string stackSet = "Stack"; + private const string LocationParameterSet = "Location"; + private const string StackParameterSet = "Stack"; #region Command parameters #region Location parameter set parameters - /// /// Gets or sets the provider from which to get the current location. /// - /// - [Parameter(ParameterSetName = locationSet, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = LocationParameterSet, ValueFromPipelineByPropertyName = true)] public string[] PSProvider { - get { return _provider; } - set { _provider = value ?? Utils.EmptyArray(); } + get => _provider; + set => _provider = value ?? Array.Empty(); } /// /// Gets or sets the drive from which to get the current location. /// - /// - [Parameter(ParameterSetName = locationSet, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = LocationParameterSet, ValueFromPipelineByPropertyName = true)] public string[] PSDrive { get; set; } #endregion Location parameter set parameters @@ -444,41 +367,29 @@ public string[] PSProvider /// /// Gets or sets the Stack switch parameter which is used - /// to disambiguate parameter sets + /// to disambiguate parameter sets. /// /// - [Parameter(ParameterSetName = stackSet)] + [Parameter(ParameterSetName = StackParameterSet)] public SwitchParameter Stack { - get - { - return _stackSwitch; - } - set - { - _stackSwitch = value; - } + get => _stackSwitch; + set => _stackSwitch = value; } + private bool _stackSwitch; /// /// Gets or sets the stack ID for the location stack that will /// be retrieved. /// - /// - [Parameter(ParameterSetName = stackSet, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = StackParameterSet, ValueFromPipelineByPropertyName = true)] public string[] StackName { - get - { - return _stackNames; - } // get + get => _stackNames; - set - { - _stackNames = value; - } // set - } // StackName + set => _stackNames = value; + } #endregion Stack parameter set parameters @@ -486,13 +397,12 @@ public string[] StackName #region command data - #region Location parameter set data /// /// The name of the provider from which to return the current location. /// - private string[] _provider = new string[0]; + private string[] _provider = Array.Empty(); #endregion Location parameter set data @@ -505,10 +415,8 @@ public string[] StackName #endregion Stack parameter set data - #endregion command data - #region command code /// @@ -516,7 +424,7 @@ public string[] StackName /// the parameter set that is specified, the command can do many things. /// -locationSet gets the current working directory as a Monad path /// -stackSet gets the directory stack of directories that have been - /// pushed by the push-location command + /// pushed by the push-location command. /// protected override void ProcessRecord() { @@ -524,7 +432,7 @@ protected override void ProcessRecord() // want a case sensitive comparison in the current culture. switch (ParameterSetName) { - case locationSet: + case LocationParameterSet: PathInfo result = null; if (PSDrive != null && PSDrive.Length > 0) @@ -539,7 +447,7 @@ protected override void ProcessRecord() catch (DriveNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -550,7 +458,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingProvider", ErrorCategory.ObjectNotFound, @@ -561,7 +469,7 @@ protected override void ProcessRecord() catch (ArgumentException argException) { ErrorRecord errorRecord = - new ErrorRecord( + new( argException, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -615,7 +523,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingProvider", ErrorCategory.ObjectNotFound, @@ -669,9 +577,10 @@ protected override void ProcessRecord() // Get the current working directory using the core command API. WriteObject(SessionState.Path.CurrentLocation); } + break; - case stackSet: + case StackParameterSet: if (_stackNames != null) { foreach (string stackName in _stackNames) @@ -705,81 +614,60 @@ protected override void ProcessRecord() argException)); } } + 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; - } // case (ParameterSetName) - } // ProcessRecord + } + } #endregion command code - } // class GetLocationCommand + } #endregion GetLocationCommand - #region SetLocationCommand /// /// The core command for setting/changing location. /// This is the equivalent of cd command. /// - [Cmdlet(VerbsCommon.Set, "Location", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113397")] + [Cmdlet(VerbsCommon.Set, "Location", DefaultParameterSetName = PathParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097049")] [OutputType(typeof(PathInfo), typeof(PathInfoStack))] public class SetLocationCommand : CoreCommandBase { #region Command parameters + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + private const string StackParameterSet = "Stack"; /// - /// The string declaration for the Location parameter set in this command. - /// - private const string pathSet = "Path"; - - /// - /// The string declaration for the literal location parameter set in this command. - /// - private const string literalPathSet = "LiteralPath"; - - /// - /// The string declaration for the Stack parameter set in this command. - /// - private const string stackSet = "Stack"; - - /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = pathSet, + [Parameter(Position = 0, ParameterSetName = PathParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { - get - { - return _path; - } - set - { - _path = value; - } + get => _path; + set => _path = value; } /// - /// 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 = literalPathSet, + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string LiteralPath { - get - { - return _path; - } + get => _path; set { _path = value; base.SuppressWildcardExpansion = true; } - } // PSPath + } /// /// Gets or sets the parameter -passThru which states output from @@ -788,8 +676,8 @@ public string LiteralPath [Parameter] public SwitchParameter PassThru { - get { return _passThrough; } - set { _passThrough = value; } + get => _passThrough; + set => _passThrough = value; } /// @@ -797,7 +685,7 @@ public SwitchParameter PassThru /// to use for the push. If the parameter is missing or empty the default /// location stack is used. /// - [Parameter(ParameterSetName = stackSet, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = StackParameterSet, ValueFromPipelineByPropertyName = true)] public string StackName { get; set; } #endregion Command parameters @@ -805,9 +693,9 @@ public SwitchParameter PassThru #region Command data /// - /// The filter used when doing a dir + /// The filter used when doing a dir. /// - private string _path = String.Empty; + private string _path = string.Empty; /// /// Determines if output should be passed through for @@ -829,8 +717,8 @@ protected override void ProcessRecord() switch (ParameterSetName) { - case pathSet: - case literalPathSet: + case PathParameterSet: + case LiteralPathParameterSet: try { // Change the current working directory @@ -840,7 +728,7 @@ protected override void ProcessRecord() Path = SessionState.Internal.GetSingleProvider(Commands.FileSystemProvider.ProviderName).Home; } - result = SessionState.Path.SetLocation(Path, CmdletProviderContext); + result = SessionState.Path.SetLocation(Path, CmdletProviderContext, ParameterSetName == LiteralPathParameterSet); } catch (PSNotSupportedException notSupported) { @@ -877,9 +765,10 @@ protected override void ProcessRecord() argException.ErrorRecord, argException)); } + break; - case stackSet: + case StackParameterSet: try { @@ -907,10 +796,10 @@ protected override void ProcessRecord() { WriteObject(result); } - } // ProcessRecord + } #endregion Command code - } // SetLocationCommand + } #endregion SetLocationCommand @@ -920,48 +809,39 @@ protected override void ProcessRecord() /// The core command for setting/changing location and pushing it onto a location stack. /// This is the equivalent of the pushd command. /// - [Cmdlet(VerbsCommon.Push, "Location", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113370")] + [Cmdlet(VerbsCommon.Push, "Location", DefaultParameterSetName = PathParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097105")] public class PushLocationCommand : CoreCommandBase { #region Command parameters + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { - get - { - return _path; - } - set - { - _path = value; - } + get => _path; + set => _path = value; } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string LiteralPath { - get - { - return _path; - } // get - + get => _path; set { base.SuppressWildcardExpansion = true; _path = value; - } // set - } // LiteralPath - + } + } /// /// Gets or sets the parameter -passThru which states output from @@ -970,15 +850,9 @@ public string LiteralPath [Parameter] public SwitchParameter PassThru { - get - { - return _passThrough; - } // get - set - { - _passThrough = value; - } //set - } // PassThru + get => _passThrough; + set => _passThrough = value; + } /// /// Gets or sets the StackName parameter which determines which location stack @@ -988,24 +862,18 @@ public SwitchParameter PassThru [Parameter(ValueFromPipelineByPropertyName = true)] public string StackName { - get - { - return _stackName; - } // get - set - { - _stackName = value; - } //set - } // StackName + get => _stackName; + set => _stackName = value; + } #endregion Command parameters #region Command data /// - /// The filter used when doing a dir + /// The filter used when doing a dir. /// - private string _path = String.Empty; + private string _path = string.Empty; /// /// Determines if output should be passed through for @@ -1085,11 +953,11 @@ protected override void ProcessRecord() argException)); return; } - } // Path != null - } // ProcessRecord + } + } #endregion Command code - } // PushLocationCommand + } #endregion PushLocationCommand @@ -1099,7 +967,7 @@ protected override void ProcessRecord() /// The core command for pop-location. This is the equivalent of the popd command. /// It pops a container from the stack and sets the current location to that container. /// - [Cmdlet(VerbsCommon.Pop, "Location", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113369")] + [Cmdlet(VerbsCommon.Pop, "Location", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096907")] public class PopLocationCommand : CoreCommandBase { #region Command parameters @@ -1111,15 +979,9 @@ public class PopLocationCommand : CoreCommandBase [Parameter] public SwitchParameter PassThru { - get - { - return _passThrough; - } // get - set - { - _passThrough = value; - } //set - } // PassThru + get => _passThrough; + set => _passThrough = value; + } /// /// Gets or sets the StackName parameter which determines which location stack @@ -1129,15 +991,9 @@ public SwitchParameter PassThru [Parameter(ValueFromPipelineByPropertyName = true)] public string StackName { - get - { - return _stackName; - } // get - set - { - _stackName = value; - } //set - } // StackName + get => _stackName; + set => _stackName = value; + } #endregion Command parameters @@ -1156,10 +1012,8 @@ public string StackName #endregion Command data - #region Command code - /// /// Gets the top container from the location stack and sets the /// location to it. @@ -1209,10 +1063,10 @@ protected override void ProcessRecord() itemNotFound)); return; } - } // ProcessRecord + } #endregion Command code - } // PopLocationCommand + } #endregion PopLocationCommand @@ -1221,96 +1075,64 @@ 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, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113357")] + [Cmdlet(VerbsCommon.New, "PSDrive", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096815")] public class NewPSDriveCommand : CoreCommandWithCredentialsBase { #region Command parameters /// - /// Gets or sets the name of the drive + /// Gets or sets the name of the drive. /// - /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string Name { - get { return _name; } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - _name = value; - } + get => _name; + set => _name = value ?? throw PSTraceSource.NewArgumentNullException(nameof(value)); } /// - /// Gets or sets the provider ID + /// Gets or sets the provider ID. /// - /// [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string PSProvider { - get { return _provider; } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - _provider = value; - } + get => _provider; + set => _provider = value ?? throw PSTraceSource.NewArgumentNullException(nameof(value)); } /// /// Gets or sets the root of the drive. This path should be /// a namespace specific path. /// - /// [Parameter(Position = 2, Mandatory = true, ValueFromPipelineByPropertyName = true)] [AllowEmptyString] public string Root { - get { return _root; } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - _root = value; - } + get => _root; + set => _root = value ?? throw PSTraceSource.NewArgumentNullException(nameof(value)); } /// - /// Gets or sets the description of the drive + /// Gets or sets the description of the drive. /// [Parameter(ValueFromPipelineByPropertyName = true)] public string Description { - get { return _description; } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - _description = value; - } + get => _description; + set => _description = value ?? throw PSTraceSource.NewArgumentNullException(nameof(value)); } /// /// Gets or sets the scope identifier for the drive being created. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } +#if !UNIX /// /// Gets or sets the Persist Switch parameter. /// If this switch parameter is set then the created PSDrive @@ -1319,58 +1141,54 @@ public string Description [Parameter(ValueFromPipelineByPropertyName = true)] public SwitchParameter Persist { - get { return _persist; } - set { _persist = value; } + get => _persist; + set => _persist = value; } - private bool _persist = false; + private bool _persist = false; +#endif /// /// Gets the dynamic parameters for the new-psdrive cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { return SessionState.Drive.NewDriveDynamicParameters(PSProvider, context); } /// - /// new-psdrive always supports ShouldProcess + /// New-psdrive always supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get { return true; } - } + protected override bool ProviderSupportsShouldProcess => true; + #endregion Command parameters #region Command data /// - /// The name of the drive + /// The name of the drive. /// private string _name; /// - /// The provider ID for the drive + /// The provider ID for the drive. /// private string _provider; /// - /// The namespace specific path of the root of the drive + /// The namespace specific path of the root of the drive. /// private string _root; /// - /// A description for the drive + /// A description for the drive. /// private string _description; @@ -1379,7 +1197,7 @@ protected override bool ProviderSupportsShouldProcess #region Command code /// - /// Adds a new drive to the Monad namespace + /// Adds a new drive to the Monad namespace. /// protected override void ProcessRecord() { @@ -1407,7 +1225,7 @@ protected override void ProcessRecord() string resourceTemplate = NavigationResources.NewDriveConfirmResourceTemplate; string resource = - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, resourceTemplate, Name, @@ -1416,23 +1234,40 @@ protected override void ProcessRecord() if (ShouldProcess(resource, action)) { +#if !UNIX // -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); } + // Trimming forward and backward slash for FileSystem provider when -Persist is used. + if (Persist && provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) + { + Root = Root.TrimEnd('/', '\\'); + } + // Create the new drive PSDriveInfo newDrive = - new PSDriveInfo( + new( Name, provider, Root, Description, Credential, Persist); - +#else + // Create the new drive + PSDriveInfo newDrive = + new PSDriveInfo( + Name, + provider, + Root, + Description, + Credential, + persist: false); +#endif try { SessionState.Drive.New(newDrive, Scope, CmdletProviderContext); @@ -1487,7 +1322,7 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code } @@ -1506,43 +1341,35 @@ public class DriveMatchingCoreCommandBase : CoreCommandBase /// Globs on both the drive name and the provider name to get a list of Drives /// that match the glob filters. /// - /// /// /// The name of the drive(s) to returned. The name can contain glob characters. /// - /// /// /// The name of the provider(s) to return. The name can contain glob characters. /// - /// /// /// The scope to get the drives from. If this parameter is null or empty all drives /// will be retrieved. /// - /// /// /// A collection of the drives that match the filters. /// - /// /// /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal List GetMatchingDrives( string driveName, string[] providerNames, string scope) { - List results = new List(); + List results = new(); if (providerNames == null || providerNames.Length == 0) { @@ -1553,11 +1380,11 @@ internal List GetMatchingDrives( { tracer.WriteLine("ProviderName: {0}", providerName); - bool providerNameEmpty = String.IsNullOrEmpty(providerName); + bool providerNameEmpty = string.IsNullOrEmpty(providerName); bool providerNameContainsWildcardCharacters = WildcardPattern.ContainsWildcardCharacters(providerName); - bool driveNameEmpty = String.IsNullOrEmpty(driveName); + bool driveNameEmpty = string.IsNullOrEmpty(driveName); bool driveNameContainsWildcardCharacters = WildcardPattern.ContainsWildcardCharacters(driveName); @@ -1576,7 +1403,7 @@ internal List GetMatchingDrives( // exist. if (!driveNameEmpty && !driveNameContainsWildcardCharacters) { - if (String.IsNullOrEmpty(scope)) + if (string.IsNullOrEmpty(scope)) { SessionState.Drive.Get(driveName); } @@ -1586,7 +1413,6 @@ internal List GetMatchingDrives( } } - WildcardPattern providerMatcher = null; PSSnapinQualifiedName pssnapinQualifiedProviderName = null; @@ -1606,7 +1432,6 @@ internal List GetMatchingDrives( WildcardOptions.IgnoreCase); } - WildcardPattern nameMatcher = null; if (!driveNameEmpty) @@ -1623,12 +1448,12 @@ internal List GetMatchingDrives( if (base.SuppressWildcardExpansion) { - if (String.Equals(drive.Name, driveName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(drive.Name, driveName, StringComparison.OrdinalIgnoreCase)) addDrive = true; } else { - if (nameMatcher.IsMatch(drive.Name)) + if (nameMatcher != null && nameMatcher.IsMatch(drive.Name)) addDrive = true; } @@ -1639,64 +1464,58 @@ internal List GetMatchingDrives( { results.Add(drive); } - } // nameMatcher.IsMatch() - } // foreach Drive + } + } } + results.Sort(); return results; } - } // DriveMatchingCoreCommandBase + } #endregion DriveMatchingCoreCommandBase #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 = "Name", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113376")] + [Cmdlet(VerbsCommon.Remove, "PSDrive", DefaultParameterSetName = NameParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097050")] public class RemovePSDriveCommand : DriveMatchingCoreCommandBase { #region Command parameters + private const string NameParameterSet = "Name"; + private const string LiteralNameParameterSet = "LiteralName"; + /// /// Gets or sets the name of the drive to remove. /// - [Parameter(Position = 0, ParameterSetName = "Name", + [Parameter(Position = 0, ParameterSetName = NameParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [AllowNull] [AllowEmptyCollection] public string[] Name { - get - { - return _names; - } - set - { - _names = value; - } - } // Name + get => _names; + set => _names = value; + } /// - /// Gets or sets the literal name parameter to the command + /// Gets or sets the literal name parameter to the command. /// - [Parameter(Position = 0, ParameterSetName = "LiteralName", + [Parameter(Position = 0, ParameterSetName = LiteralNameParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] public string[] LiteralName { - get - { - return _names; - } // get - + get => _names; set { base.SuppressWildcardExpansion = true; _names = value; - } // set - } // LiteralName + } + } /// /// Gets or sets the name provider(s) for which the drives should be removed. @@ -1704,15 +1523,8 @@ public string[] LiteralName [Parameter(ValueFromPipelineByPropertyName = true)] public string[] PSProvider { - get { return _provider; } - set - { - if (value == null) - { - value = Utils.EmptyArray(); - } - _provider = value; - } + get => _provider; + set => _provider = value ?? Array.Empty(); } /// @@ -1722,28 +1534,25 @@ 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; } /// /// Gets or sets the force property which determines if the drive /// should be removed even if there were errors. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get { return true; } - } + protected override bool ProviderSupportsShouldProcess => true; #endregion Command parameters @@ -1757,7 +1566,7 @@ protected override bool ProviderSupportsShouldProcess /// /// The name of the provider(s) for which to remove all drives. /// - private string[] _provider = new string[0]; + private string[] _provider = Array.Empty(); #endregion Command data @@ -1789,7 +1598,7 @@ protected override void ProcessRecord() foreach (PSDriveInfo drive in GetMatchingDrives(driveName, PSProvider, Scope)) { string resource = - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, resourceTemplate, drive.Name, @@ -1802,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); @@ -1813,6 +1621,7 @@ protected override void ProcessRecord() invalidOperation)); continue; } + SessionState.Drive.Remove(drive.Name, Force, Scope, CmdletProviderContext); } } @@ -1829,65 +1638,60 @@ protected override void ProcessRecord() if (verifyMatch && !foundMatch) { - DriveNotFoundException e = new DriveNotFoundException( + DriveNotFoundException e = new( driveName, "DriveNotFound", SessionStateStrings.DriveNotFound); WriteError(new ErrorRecord(e.ErrorRecord, e)); } } - } // ProcessRecord + } #endregion Command code - } // RemovePSDriveCommand + } #endregion RemovePSDriveCommand #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 = "Name", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113327")] + [Cmdlet(VerbsCommon.Get, "PSDrive", DefaultParameterSetName = NameParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096494")] [OutputType(typeof(PSDriveInfo))] public class GetPSDriveCommand : DriveMatchingCoreCommandBase { #region Command parameters + private const string NameParameterSet = "Name"; + private const string LiteralNameParameterSet = "LiteralName"; + /// /// Gets or sets the drive name the user is looking for. /// - /// /// /// If the drive name is left empty, all drives will be /// returned. A globing or regular expression can also be /// supplied and any drive names that match the expression /// will be returned. /// - [Parameter(Position = 0, ParameterSetName = "Name", ValueFromPipelineByPropertyName = true)] + [Parameter(Position = 0, ParameterSetName = NameParameterSet, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] public string[] Name { - get { return _name; } - set - { - if (value == null) - { - value = new string[] { "*" }; - } - _name = value; - } + get => _name; + set => _name = value ?? new string[] { "*" }; } /// - /// Gets or sets the literal name parameter to the command + /// Gets or sets the literal name parameter to the command. /// - [Parameter(Position = 0, ParameterSetName = "LiteralName", + [Parameter(Position = 0, ParameterSetName = LiteralNameParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] public string[] LiteralName { - get { return _name; } + get => _name; set { base.SuppressWildcardExpansion = true; @@ -1898,15 +1702,14 @@ public string[] LiteralName /// /// Gets or sets the scope parameter to the command. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// /// Gets or sets the provider name for the /// drives that should be retrieved. /// - /// /// /// If the provider is left empty, all drives will be /// returned. A globing or regular expression can also be @@ -1916,15 +1719,8 @@ public string[] LiteralName [Parameter(ValueFromPipelineByPropertyName = true)] public string[] PSProvider { - get { return _provider; } - set - { - if (value == null) - { - value = Utils.EmptyArray(); - } - _provider = value; - } + get => _provider; + set => _provider = value ?? Array.Empty(); } #endregion Command parameters @@ -1939,7 +1735,7 @@ public string[] PSProvider /// /// The provider ID for the drives you want to see. /// - private string[] _provider = new string[0]; + private string[] _provider = Array.Empty(); #endregion Command data @@ -1982,7 +1778,7 @@ protected override void ProcessRecord() if (!WildcardPattern.ContainsWildcardCharacters(driveName)) { DriveNotFoundException driveNotFound = - new DriveNotFoundException( + new( driveName, "DriveNotFound", SessionStateStrings.DriveNotFound); @@ -1999,7 +1795,7 @@ protected override void ProcessRecord() catch (DriveNotFoundException driveNotFound) { ErrorRecord errorRecord = - new ErrorRecord( + new( driveNotFound, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -2009,7 +1805,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException providerNotFound) { ErrorRecord errorRecord = - new ErrorRecord( + new( providerNotFound, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -2031,10 +1827,10 @@ protected override void ProcessRecord() argException)); } } - } // ProcessRecord + } #endregion Command code - } // GetPSDriveCommand + } #endregion GetPSDriveCommand @@ -2047,102 +1843,74 @@ protected override void ProcessRecord() /// /// Gets the specified item using the namespace providers. /// - [Cmdlet(VerbsCommon.Get, "Item", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113319")] + [Cmdlet(VerbsCommon.Get, "Item", DefaultParameterSetName = PathParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096812")] public class GetItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// /// Gets or sets the path to item to get. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get - { - return _paths; - } // get - + get => _paths; set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get - { - return base.Filter; - } - set - { - base.Filter = value; - } + get => base.Filter; + set => base.Filter = value; } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get - { - return base.Include; - } // get - - set - { - base.Include = value; - } // set - } // Include + get => base.Include; + set => base.Include = value; + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get - { - return base.Exclude; - } // get - - set - { - base.Exclude = value; - } // set - } // Exclude + get => base.Exclude; + set => base.Exclude = value; + } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -2152,41 +1920,32 @@ public override string[] Exclude /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get - { - return base.Force; - } - set - { - base.Force = value; - } - } // Force + get => base.Force; + set => base.Force = value; + } /// /// Gets the dynamic parameters for the get-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.GetItemDynamicParameters(Path[0], context); } + return InvokeProvider.Item.GetItemDynamicParameters(".", context); - } // GetDynamicParameters + } #endregion Command parameters @@ -2240,10 +1999,10 @@ protected override void ProcessRecord() pathNotFound)); } } - } // ProcessRecord + } #endregion Command code - } // GetItemCommand + } #endregion GetItemCommand @@ -2252,48 +2011,47 @@ protected override void ProcessRecord() /// /// Creates the specified item using the namespace providers. /// - [Cmdlet(VerbsCommon.New, "Item", DefaultParameterSetName = "pathSet", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113353")] + [Cmdlet(VerbsCommon.New, "Item", DefaultParameterSetName = PathParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096592")] public class NewItemCommand : CoreCommandWithCredentialsBase { #region Command parameters - private const string nameSet = "nameSet"; - private const string pathSet = "pathSet"; + private const string NameParameterSet = "nameSet"; + private const string PathParameterSet = "pathSet"; /// /// Gets or sets the container path to create the item in. /// - [Parameter(Position = 0, ParameterSetName = "pathSet", Mandatory = true, ValueFromPipelineByPropertyName = true)] - [Parameter(Position = 0, ParameterSetName = "nameSet", Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Position = 0, ParameterSetName = NameParameterSet, Mandatory = false, ValueFromPipelineByPropertyName = true)] public string[] Path { get; set; } /// - /// Gets or sets the name of the item to create + /// Gets or sets the name of the item to create. /// - [Parameter(ParameterSetName = "nameSet", Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = NameParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [AllowNull] [AllowEmptyString] public string Name { get; set; } /// - /// Gets or sets the type of the item to create + /// Gets or sets the type of the item to create. /// [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] public string ItemType { get; set; } /// - /// Gets or sets the content of the item to create + /// Gets or sets the content of the item to create. /// [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [Alias("Target")] public object Value { get; set; } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -2303,51 +2061,42 @@ public class NewItemCommand : CoreCommandWithCredentialsBase /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } /// /// Gets the dynamic parameters for the new-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { // Path is only globbed if Name is specified. - if (String.IsNullOrEmpty(Name)) + if (string.IsNullOrEmpty(Name)) return InvokeProvider.Item.NewItemDynamicParameters(WildcardPattern.Escape(Path[0]), ItemType, Value, context); else return InvokeProvider.Item.NewItemDynamicParameters(Path[0], ItemType, Value, context); } + return InvokeProvider.Item.NewItemDynamicParameters(".", ItemType, Value, context); } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(Path); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(Path); #endregion Command parameters @@ -2364,7 +2113,7 @@ protected override void ProcessRecord() { if (Path == null || Path.Length == 0) { - Path = new string[] { String.Empty }; + Path = new string[] { string.Empty }; } foreach (string path in Path) @@ -2402,10 +2151,10 @@ protected override void ProcessRecord() pathNotFound)); } } - } // ProcessRecord + } #endregion Command code - } // NewItemCommand + } #endregion NewItemCommand @@ -2414,37 +2163,34 @@ protected override void ProcessRecord() /// /// Sets the specified item using the namespace providers. /// - [Cmdlet(VerbsCommon.Set, "Item", SupportsShouldProcess = true, DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113395")] + [Cmdlet(VerbsCommon.Set, "Item", SupportsShouldProcess = true, DefaultParameterSetName = PathParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097055")] public class SetItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// /// Gets or sets the path to item to set. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return _paths; } + get => _paths; set { base.SuppressWildcardExpansion = true; @@ -2453,15 +2199,14 @@ public string[] LiteralPath } /// - /// Gets or sets the value of the item to be set + /// Gets or sets the value of the item to be set. /// [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public object Value { get; set; } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -2471,12 +2216,11 @@ public string[] LiteralPath /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } /// @@ -2484,77 +2228,68 @@ public override SwitchParameter Force /// if the object that is set should be written to the pipeline. /// Defaults to false. /// - /// [Parameter] public SwitchParameter PassThru { - get { return _passThrough; } - set { _passThrough = value; } + get => _passThrough; + set => _passThrough = value; } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get { return base.Filter; } - set { base.Filter = value; } + get => base.Filter; + set => base.Filter = value; } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get { return base.Include; } - set { base.Include = value; } - } // Include + get => base.Include; + set => base.Include = value; + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get { return base.Exclude; } - set { base.Exclude = value; } + get => base.Exclude; + set => base.Exclude = value; } /// /// Gets the dynamic parameters for the set-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.SetItemDynamicParameters(Path[0], Value, context); } + return InvokeProvider.Item.SetItemDynamicParameters(".", Value, context); } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters #region Command data @@ -2619,10 +2354,10 @@ protected override void ProcessRecord() pathNotFound)); } } - } // ProcessRecord + } #endregion Command code - } // SetItemCommand + } #endregion SetItemCommand @@ -2631,118 +2366,84 @@ protected override void ProcessRecord() /// /// Removes the specified item using the namespace providers. /// - [Cmdlet(VerbsCommon.Remove, "Item", SupportsShouldProcess = true, DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113373")] + [Cmdlet(VerbsCommon.Remove, "Item", SupportsShouldProcess = true, DefaultParameterSetName = PathParameterSet, SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097103")] public class RemoveItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get - { - return _paths; - } // get - + get => _paths; set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get - { - return base.Filter; - } - set - { - base.Filter = value; - } + get => base.Filter; + set => base.Filter = value; } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get - { - return base.Include; - } // get - - set - { - base.Include = value; - } // set - } // Include + get => base.Include; + set => base.Include = value; + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get - { - return base.Exclude; - } // get - - set - { - base.Exclude = value; - } // set - } // Exclude + get => base.Exclude; + set => base.Exclude = value; + } /// - /// Gets or sets the recurse property + /// Gets or sets the recurse property. /// [Parameter] public SwitchParameter Recurse { - get - { - return _recurse; - } - set - { - _recurse = value; - } - } // Recurse + get => _recurse; + set => _recurse = value; + } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -2752,59 +2453,44 @@ public SwitchParameter Recurse /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get - { - return base.Force; - } - set - { - base.Force = value; - } - } // Force + get => base.Force; + set => base.Force = value; + } /// /// Gets the dynamic parameters for the remove-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.RemoveItemDynamicParameters(Path[0], Recurse, context); } + return InvokeProvider.Item.RemoveItemDynamicParameters(".", Recurse, context); - } // GetDynamicParameters + } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters #region Command data /// - /// The path used when doing a delete + /// The path used when doing a delete. /// private string[] _paths; @@ -2848,13 +2534,14 @@ protected override void ProcessRecord() new Collection(), null); } + 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); @@ -2952,8 +2639,7 @@ protected override void ProcessRecord() if (isCurrentLocationOrAncestor) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RemoveItemInUse, resolvedPath.Path); @@ -3015,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; @@ -3032,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 @@ -3041,6 +2727,7 @@ protected override void ProcessRecord() { continue; } + shouldRecurse = true; } @@ -3092,10 +2779,10 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code - } // RemoveItemCommand + } #endregion RemoveItemCommand @@ -3105,37 +2792,35 @@ protected override void ProcessRecord() /// Moves an item from the specified location to the specified destination using /// the namespace providers. /// - [Cmdlet(VerbsCommon.Move, "Item", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113350")] + [Cmdlet(VerbsCommon.Move, "Item", DefaultParameterSetName = PathParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096591")] public class MoveItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return _paths; } + get => _paths; set { base.SuppressWildcardExpansion = true; @@ -3144,15 +2829,14 @@ public string[] LiteralPath } /// - /// Gets or sets the destination property + /// Gets or sets the destination property. /// [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] public string Destination { get; set; } = "."; /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -3162,42 +2846,41 @@ public string[] LiteralPath /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get { return base.Filter; } - set { base.Filter = value; } + get => base.Filter; + set => base.Filter = value; } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get { return base.Include; } - set { base.Include = value; } + get => base.Include; + set => base.Include = value; } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get { return base.Exclude; } - set { base.Exclude = value; } + get => base.Exclude; + set => base.Exclude = value; } /// @@ -3205,47 +2888,38 @@ public override string[] Exclude /// if the object that is set should be written to the pipeline. /// Defaults to false. /// - /// [Parameter] public SwitchParameter PassThru { - get { return _passThrough; } - set { _passThrough = value; } + get => _passThrough; + set => _passThrough = value; } /// /// Gets the dynamic parameters for the move-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.MoveItemDynamicParameters(Path[0], Destination, context); } + return InvokeProvider.Item.MoveItemDynamicParameters(".", Destination, context); } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters @@ -3268,7 +2942,7 @@ protected override bool ProviderSupportsShouldProcess private Collection GetResolvedPaths(string path) { - Collection results = new Collection(); + Collection results = new(); try { results = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); @@ -3306,7 +2980,7 @@ private Collection GetResolvedPaths(string path) } /// - /// Moves the specified item to the specified destination + /// Moves the specified item to the specified destination. /// protected override void ProcessRecord() { @@ -3314,7 +2988,7 @@ protected override void ProcessRecord() { if (base.SuppressWildcardExpansion) { - MoveItem(path); + MoveItem(path, literalPath: true); } else { @@ -3323,210 +2997,202 @@ protected override void ProcessRecord() foreach (PathInfo resolvedPathInfo in resolvedPaths) { string resolvedPath = resolvedPathInfo.Path; - MoveItem(resolvedPath); + MoveItem(resolvedPath, literalPath: true); } } } - } // ProcessRecord + } - private void MoveItem(string path) + private void MoveItem(string path, bool literalPath = false) { CmdletProviderContext currentContext = CmdletProviderContext; + currentContext.SuppressWildcardExpansion = literalPath; - do + try { - try - { - string escapedPath = path; - if (!base.SuppressWildcardExpansion) { escapedPath = WildcardPattern.Escape(path); } - if (!InvokeProvider.Item.Exists(escapedPath, currentContext)) - { - PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( - NavigationResources.MoveItemDoesntExist, - path); - - WriteError( - new ErrorRecord( - invalidOperation.ErrorRecord, - invalidOperation)); - continue; - } - } - catch (PSNotSupportedException notSupported) - { - WriteError( - new ErrorRecord( - notSupported.ErrorRecord, - notSupported)); - continue; - } - catch (DriveNotFoundException driveNotFound) - { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; - } - catch (ItemNotFoundException pathNotFound) + if (!InvokeProvider.Item.Exists(path, currentContext)) { - WriteError( - new ErrorRecord( - pathNotFound.ErrorRecord, - pathNotFound)); - continue; - } - + PSInvalidOperationException invalidOperation = + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( + NavigationResources.MoveItemDoesntExist, + path); - // See if the item to be moved is in use. - bool isCurrentLocationOrAncestor = false; - try - { - isCurrentLocationOrAncestor = SessionState.Path.IsCurrentLocationOrAncestor(path, currentContext); - } - catch (PSNotSupportedException notSupported) - { - WriteError( - new ErrorRecord( - notSupported.ErrorRecord, - notSupported)); - continue; - } - catch (DriveNotFoundException driveNotFound) - { WriteError( new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; - } - catch (ItemNotFoundException pathNotFound) - { - WriteError( - new ErrorRecord( - pathNotFound.ErrorRecord, - pathNotFound)); - continue; + invalidOperation.ErrorRecord, + invalidOperation)); + return; } + } + catch (PSNotSupportedException notSupported) + { + WriteError( + new ErrorRecord( + notSupported.ErrorRecord, + notSupported)); + return; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + return; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + return; + } + catch (ItemNotFoundException pathNotFound) + { + WriteError( + new ErrorRecord( + pathNotFound.ErrorRecord, + pathNotFound)); + return; + } - if (isCurrentLocationOrAncestor) - { - PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( - NavigationResources.MoveItemInUse, - path); + // See if the item to be moved is in use. + bool isCurrentLocationOrAncestor = false; + try + { + isCurrentLocationOrAncestor = SessionState.Path.IsCurrentLocationOrAncestor(path, currentContext); + } + catch (PSNotSupportedException notSupported) + { + WriteError( + new ErrorRecord( + notSupported.ErrorRecord, + notSupported)); + return; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + return; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + return; + } + catch (ItemNotFoundException pathNotFound) + { + WriteError( + new ErrorRecord( + pathNotFound.ErrorRecord, + pathNotFound)); + return; + } - WriteError( - new ErrorRecord( - invalidOperation.ErrorRecord, - invalidOperation)); - continue; - } + if (isCurrentLocationOrAncestor) + { + PSInvalidOperationException invalidOperation = + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( + NavigationResources.MoveItemInUse, + path); - // Default to the CmdletProviderContext that will direct output to - // the pipeline. + WriteError( + new ErrorRecord( + invalidOperation.ErrorRecord, + invalidOperation)); + return; + } - CmdletProviderContext currentCommandContext = currentContext; - currentCommandContext.PassThru = PassThru; + // Default to the CmdletProviderContext that will direct output to + // the pipeline. - tracer.WriteLine("Moving {0} to {1}", path, Destination); + currentContext.PassThru = PassThru; - try - { - // Now do the move - string escapedPath = path; - if (!base.SuppressWildcardExpansion) { escapedPath = WildcardPattern.Escape(path); } - InvokeProvider.Item.Move(escapedPath, Destination, currentCommandContext); - } - catch (PSNotSupportedException notSupported) - { - WriteError( - new ErrorRecord( - notSupported.ErrorRecord, - notSupported)); - continue; - } - catch (DriveNotFoundException driveNotFound) - { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; - } - catch (ItemNotFoundException pathNotFound) - { - WriteError( - new ErrorRecord( - pathNotFound.ErrorRecord, - pathNotFound)); - continue; - } + tracer.WriteLine("Moving {0} to {1}", path, Destination); + + try + { + // Now do the move + InvokeProvider.Item.Move(path, Destination, currentContext); + } + catch (PSNotSupportedException notSupported) + { + WriteError( + new ErrorRecord( + notSupported.ErrorRecord, + notSupported)); + return; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + return; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + return; + } + catch (ItemNotFoundException pathNotFound) + { + WriteError( + new ErrorRecord( + pathNotFound.ErrorRecord, + pathNotFound)); + return; } - while (false); } #endregion Command code - } // MoveItemCommand + } #endregion MoveItemCommand #region RenameItemCommand /// - /// Renames a specified item to a new name using the namespace providers + /// Renames a specified item to a new name using the namespace providers. /// - [Cmdlet(VerbsCommon.Rename, "Item", SupportsShouldProcess = true, SupportsTransactions = true, DefaultParameterSetName = "ByPath", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113382")] + [Cmdlet(VerbsCommon.Rename, "Item", SupportsShouldProcess = true, SupportsTransactions = true, DefaultParameterSetName = ByPathParameterSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097153")] public class RenameItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + private const string ByPathParameterSet = "ByPath"; + private const string ByLiteralPathParameterSet = "ByLiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ByPathParameterSet)] public string Path { - get { return _path; } - set { _path = value; } + get => _path; + set => _path = value; } /// - /// Gets or sets the literal path property + /// Gets or sets the literal path property. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ByLiteralPathParameterSet)] + [Alias("PSPath", "LP")] public string LiteralPath { - get { return _path; } + get => _path; set { _path = value; @@ -3535,15 +3201,14 @@ public string LiteralPath } /// - /// Gets or sets the newName property + /// Gets or sets the newName property. /// [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string NewName { get; set; } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -3553,57 +3218,45 @@ public string LiteralPath /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } - /// /// Gets or sets the pass through property which determines /// if the object that is set should be written to the pipeline. /// Defaults to false. /// - /// [Parameter] public SwitchParameter PassThru { - get { return _passThrough; } - set { _passThrough = value; } + get => _passThrough; + set => _passThrough = value; } /// /// Gets the dynamic parameters for the rename-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { return InvokeProvider.Item.RenameItemDynamicParameters(Path, NewName, context); } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(new string[] { _path }); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(new string[] { _path }); #endregion Command parameters @@ -3624,22 +3277,85 @@ protected override bool ProviderSupportsShouldProcess #region Command code + private Collection GetResolvedPaths(string path) + { + Collection results = null; + try + { + results = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); + } + catch (PSNotSupportedException notSupported) + { + WriteError( + new ErrorRecord( + notSupported.ErrorRecord, + notSupported)); + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + } + catch (ItemNotFoundException pathNotFound) + { + WriteError( + new ErrorRecord( + pathNotFound.ErrorRecord, + pathNotFound)); + } + + return results; + } + /// - /// Moves the specified item to the specified destination + /// Moves the specified item to the specified destination. /// protected override void ProcessRecord() + { + if (SuppressWildcardExpansion) + { + RenameItem(Path, literalPath: true); + return; + } + + Collection resolvedPaths = GetResolvedPaths(Path); + if (resolvedPaths == null) + { + return; + } + + if (resolvedPaths.Count == 1) + { + RenameItem(resolvedPaths[0].Path, literalPath: true); + } + else + { + RenameItem(WildcardPattern.Unescape(Path), literalPath: true); + } + } + + private void RenameItem(string path, bool literalPath = false) { CmdletProviderContext currentContext = CmdletProviderContext; + currentContext.SuppressWildcardExpansion = literalPath; try { - if (!InvokeProvider.Item.Exists(Path, currentContext)) + if (!InvokeProvider.Item.Exists(path, currentContext)) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RenameItemDoesntExist, - Path); + path); WriteError( new ErrorRecord( @@ -3685,7 +3401,7 @@ protected override void ProcessRecord() bool isCurrentLocationOrAncestor = false; try { - isCurrentLocationOrAncestor = SessionState.Path.IsCurrentLocationOrAncestor(_path, currentContext); + isCurrentLocationOrAncestor = SessionState.Path.IsCurrentLocationOrAncestor(path, currentContext); } catch (PSNotSupportedException notSupported) { @@ -3723,10 +3439,9 @@ protected override void ProcessRecord() if (isCurrentLocationOrAncestor) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RenamedItemInUse, - Path); + path); WriteError( new ErrorRecord( @@ -3737,15 +3452,14 @@ protected override void ProcessRecord() // Default to the CmdletProviderContext that will direct output to // the pipeline. - currentContext.PassThru = PassThru; - tracer.WriteLine("Rename {0} to {1}", Path, NewName); + tracer.WriteLine("Rename {0} to {1}", path, NewName); try { // Now do the rename - InvokeProvider.Item.Rename(Path, NewName, currentContext); + InvokeProvider.Item.Rename(path, NewName, currentContext); } catch (PSNotSupportedException notSupported) { @@ -3779,49 +3493,47 @@ protected override void ProcessRecord() pathNotFound)); return; } - } // ProcessRecord + } #endregion Command code - } // RenameItemCommand + } #endregion RenameItemCommand #region CopyItemCommand /// - /// Copies a specified item to a new location using the namespace providers + /// Copies a specified item to a new location using the namespace providers. /// - [Cmdlet(VerbsCommon.Copy, "Item", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113292")] + [Cmdlet(VerbsCommon.Copy, "Item", DefaultParameterSetName = PathParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096990")] public class CopyItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return _paths; } + get => _paths; set { base.SuppressWildcardExpansion = true; @@ -3830,18 +3542,18 @@ public string[] LiteralPath } /// - /// Gets or sets the destination property + /// Gets or sets the destination property. /// [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] public string Destination { get; set; } /// - /// Gets or sets the container property + /// Gets or sets the container property. /// [Parameter] public SwitchParameter Container { - get { return _container; } + get => _container; set { _containerSpecified = true; @@ -3850,9 +3562,8 @@ public SwitchParameter Container } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -3862,56 +3573,54 @@ public SwitchParameter Container /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get { return base.Force; } - set { base.Force = value; } + get => base.Force; + set => base.Force = value; } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get { return base.Filter; } - set { base.Filter = value; } + get => base.Filter; + set => base.Filter = value; } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get { return base.Include; } - set { base.Include = value; } + get => base.Include; + set => base.Include = value; } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get { return base.Exclude; } - set { base.Exclude = value; } + get => base.Exclude; + set => base.Exclude = value; } /// - /// Gets or sets the recurse property + /// Gets or sets the recurse property. /// [Parameter] public SwitchParameter Recurse { - get { return _recurse; } + get => _recurse; set { _recurse = value; - // If -Container is not specified but -Recurse // is, then -Container takes on the same value // as -Recurse @@ -3927,47 +3636,38 @@ public SwitchParameter Recurse /// if the object that is set should be written to the pipeline. /// Defaults to false. /// - /// [Parameter] public SwitchParameter PassThru { - get { return _passThrough; } - set { _passThrough = value; } + get => _passThrough; + set => _passThrough = value; } /// /// Gets the dynamic parameters for the copy-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.CopyItemDynamicParameters(Path[0], Destination, Recurse, context); } + return InvokeProvider.Item.CopyItemDynamicParameters(".", Destination, Recurse, context); - } // GetDynamicParameters + } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters @@ -4001,7 +3701,7 @@ protected override bool ProviderSupportsShouldProcess #region Command code /// - /// Copies the specified item(s) to the specified destination + /// Copies the specified item(s) to the specified destination. /// protected override void ProcessRecord() { @@ -4052,64 +3752,57 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - } // CopyItemCommand + } #endregion CopyItemCommand #region ClearItemCommand /// - /// Clears an item at the specified location + /// Clears an item at the specified location. /// - [Cmdlet(VerbsCommon.Clear, "Item", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113283")] + [Cmdlet(VerbsCommon.Clear, "Item", DefaultParameterSetName = PathParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096491")] public class ClearItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get - { - return _paths; - } // get - + get => _paths; set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -4119,103 +3812,68 @@ public string[] LiteralPath /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { - get - { - return base.Force; - } - set - { - base.Force = value; - } - } // Force + get => base.Force; + set => base.Force = value; + } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get - { - return base.Filter; - } - set - { - base.Filter = value; - } - } // Filter + get => base.Filter; + set => base.Filter = value; + } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get - { - return base.Include; - } // get - - set - { - base.Include = value; - } // set - } // Include + get => base.Include; + set => base.Include = value; + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get - { - return base.Exclude; - } // get - - set - { - base.Exclude = value; - } // set - } // Exclude + get => base.Exclude; + set => base.Exclude = value; + } /// /// Gets the dynamic parameters for the clear-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.ClearItemDynamicParameters(Path[0], context); } + return InvokeProvider.Item.ClearItemDynamicParameters(".", context); - } // GetDynamicParameters + } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters @@ -4231,7 +3889,7 @@ protected override bool ProviderSupportsShouldProcess #region Command code /// - /// Clears the specified item + /// Clears the specified item. /// protected override void ProcessRecord() { @@ -4283,143 +3941,109 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord - #endregion Command code + } - } // ClearItemCommand + #endregion Command code + } #endregion ClearItemCommand #region InvokeItemCommand /// - /// Invokes an item at the specified location + /// Invokes an item at the specified location. /// - [Cmdlet(VerbsLifecycle.Invoke, "Item", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113345")] + [Cmdlet(VerbsLifecycle.Invoke, "Item", DefaultParameterSetName = PathParameterSet, SupportsShouldProcess = true, SupportsTransactions = true, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096590")] public class InvokeItemCommand : CoreCommandWithCredentialsBase { #region Command parameters + + private const string PathParameterSet = "Path"; + private const string LiteralPathParameterSet = "LiteralPath"; + /// - /// Gets or sets the path property + /// Gets or sets the path property. /// - [Parameter(Position = 0, ParameterSetName = "Path", + [Parameter(Position = 0, ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Path { - get - { - return _paths; - } - set - { - _paths = value; - } - } // Path + get => _paths; + set => _paths = value; + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// - [Parameter(ParameterSetName = "LiteralPath", + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get - { - return _paths; - } // get - + get => _paths; set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // LiteralPath + } + } /// - /// Gets or sets the filter property + /// Gets or sets the filter property. /// [Parameter] public override string Filter { - get - { - return base.Filter; - } - set - { - base.Filter = value; - } - } // Filter + get => base.Filter; + set => base.Filter = value; + } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include { - get - { - return base.Include; - } // get - - set - { - base.Include = value; - } // set - } // Include + get => base.Include; + set => base.Include = value; + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude { - get - { - return base.Exclude; - } // get - - set - { - base.Exclude = value; - } // set - } // Exclude + get => base.Exclude; + set => base.Exclude = value; + } /// /// Gets the dynamic parameters for the invoke-item cmdlet. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Item.InvokeItemDynamicParameters(Path[0], context); } + return InvokeProvider.Item.InvokeItemDynamicParameters(".", context); - } // GetDynamicParameters + } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// - protected override bool ProviderSupportsShouldProcess - { - get - { - return base.DoesProviderSupportShouldProcess(_paths); - } - } + protected override bool ProviderSupportsShouldProcess => DoesProviderSupportShouldProcess(_paths); #endregion Command parameters @@ -4435,7 +4059,7 @@ protected override bool ProviderSupportsShouldProcess #region Command code /// - /// Invokes the specified item + /// Invokes the specified item. /// protected override void ProcessRecord() { @@ -4481,10 +4105,10 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - } // InvokeItemCommand + } #endregion InvokeItemCommand @@ -4495,9 +4119,9 @@ protected override void ProcessRecord() #region GetProviderCommand /// - /// Gets a core command provider by name + /// Gets a core command provider by name. /// - [Cmdlet(VerbsCommon.Get, "PSProvider", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113329")] + [Cmdlet(VerbsCommon.Get, "PSProvider", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096816")] [OutputType(typeof(ProviderInfo))] public class GetPSProviderCommand : CoreCommandBase { @@ -4506,13 +4130,12 @@ public class GetPSProviderCommand : CoreCommandBase /// /// Gets or sets the provider that will be removed. /// - /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] PSProvider { - get { return _provider; } - set { _provider = value ?? Utils.EmptyArray(); } + get => _provider; + set => _provider = value ?? Array.Empty(); } #endregion Command parameters @@ -4521,7 +4144,7 @@ public string[] PSProvider /// /// The string ID of the provider to remove. /// - private string[] _provider = new string[0]; + private string[] _provider = Array.Empty(); #endregion Command data @@ -4590,13 +4213,12 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code - } // GetProviderCommand + } #endregion GetProviderCommand #endregion Provider commands -} // namespace Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs index 9d36ec244ed..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. All rights reserved. ---********************************************************************/ +// 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 { @@ -11,13 +13,13 @@ namespace Microsoft.PowerShell.Commands /// A command to create a new property on an object. /// [Cmdlet(VerbsCommon.New, "ItemProperty", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113354")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096813")] public class NewItemPropertyCommand : ItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true)] public string[] Path @@ -25,38 +27,37 @@ public string[] Path get { return paths; - } // get + } set { paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return paths; - } // get + } set { base.SuppressWildcardExpansion = true; paths = value; - } // set - } // LiteralPath + } + } /// - /// The name of the property to create on the item + /// The name of the property to create on the item. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] public string Name { get; set; } @@ -64,22 +65,22 @@ public string[] LiteralPath /// /// The type of the property to create on the item. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] +#if !UNIX + [ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))] +#endif public string PropertyType { get; set; } /// /// The value of the property to create on the item. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] public object Value { get; set; } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -89,7 +90,6 @@ public string[] LiteralPath /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { @@ -97,35 +97,34 @@ public override SwitchParameter Force { return base.Force; } + set { base.Force = value; } - } // Force + } /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Property.NewPropertyDynamicParameters(Path[0], Name, PropertyType, Value, context); } + return InvokeProvider.Property.NewPropertyDynamicParameters(".", Name, PropertyType, Value, context); - } // GetDynamicParameters + } #endregion Parameters @@ -136,7 +135,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #region Command code /// - /// Creates the property on the item + /// Creates the property on the item. /// protected override void ProcessRecord() { @@ -179,9 +178,118 @@ protected override void ProcessRecord() continue; } } - } // 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(); - } // NewItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + 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 1b096166a7b..ca616301ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs @@ -1,21 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; + 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(VerbsCommon.Split, "Path", DefaultParameterSetName = "ParentSet", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113404")] + [Cmdlet(VerbsCommon.Split, "Path", DefaultParameterSetName = "ParentSet", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097149")] [OutputType(typeof(string), ParameterSetName = new[] { leafSet, leafBaseSet, extensionSet, @@ -29,26 +29,25 @@ public class SplitPathCommand : CoreCommandWithCredentialsBase #region Parameters /// - /// The parameter set name to get the parent path + /// The parameter set name to get the parent path. /// private const string parentSet = "ParentSet"; /// - /// The parameter set name to get the leaf name + /// The parameter set name to get the leaf name. /// private const string leafSet = "LeafSet"; /// - /// The parameter set name to get the leaf base name + /// The parameter set name to get the leaf base name. /// private const string leafBaseSet = "LeafBaseSet"; /// - /// The parameter set name to get the extension + /// The parameter set name to get the extension. /// private const string extensionSet = "ExtensionSet"; - /// /// The parameter set name to get the qualifier set. /// @@ -70,7 +69,7 @@ public class SplitPathCommand : CoreCommandWithCredentialsBase private const string literalPathSet = "LiteralPathSet"; /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = parentSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [Parameter(Position = 0, ParameterSetName = leafSet, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -82,92 +81,80 @@ public class SplitPathCommand : CoreCommandWithCredentialsBase public string[] Path { get; set; } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPathSet", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return Path; - } // get + } set { base.SuppressWildcardExpansion = true; Path = value; - } // set - } // LiteralPath + } + } /// - /// Determines if the qualifier should be returned + /// Determines if the qualifier should be returned. /// - /// /// /// 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(Position = 1, ValueFromPipelineByPropertyName = true, ParameterSetName = qualifierSet, Mandatory = false)] + [Parameter(ParameterSetName = qualifierSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter Qualifier { get; set; } /// - /// Determines if the qualifier should be returned + /// Determines if the qualifier should be returned. /// - /// /// /// 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 = false, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = noQualifierSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter NoQualifier { get; set; } /// - /// Determines if the parent path should be returned + /// Determines if the parent path should be returned. /// - /// /// /// If true the parent of the path will be returned. /// - /// [Parameter(ParameterSetName = parentSet, Mandatory = false, ValueFromPipelineByPropertyName = true)] public SwitchParameter Parent { get; set; } = true; /// - /// Determines if the leaf name should be returned + /// Determines if the leaf name should be returned. /// - /// /// /// If true the leaf name of the path will be returned. /// - /// - [Parameter(ParameterSetName = leafSet, Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = leafSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter Leaf { get; set; } /// - /// Determines if the leaf base name (name without extension) should be returned + /// Determines if the leaf base name (name without extension) should be returned. /// - /// /// /// If true the leaf base name of the path will be returned. /// - /// - [Parameter(ParameterSetName = leafBaseSet, Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = leafBaseSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter LeafBase { get; set; } /// - /// Determines if the extension should be returned + /// Determines if the extension should be returned. /// - /// /// /// If true the extension of the path will be returned. /// - /// - [Parameter(ParameterSetName = extensionSet, Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = extensionSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] public SwitchParameter Extension { get; set; } /// @@ -180,14 +167,13 @@ public string[] LiteralPath /// /// Determines if the path is an absolute path. /// - [Parameter(ParameterSetName = isAbsoluteSet)] + [Parameter(ParameterSetName = isAbsoluteSet, Mandatory = true)] public SwitchParameter IsAbsolute { get; set; } #endregion Parameters #region parameter data - #endregion parameter data #region Command code @@ -198,7 +184,7 @@ public string[] LiteralPath /// protected override void ProcessRecord() { - StringCollection pathsToParse = new StringCollection(); + StringCollection pathsToParse = new(); if (Resolve) { @@ -303,158 +289,144 @@ 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(":", StringComparison.CurrentCulture); + 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) + if (SessionState.Path.IsProviderQualified(pathsToParse[index])) { - FormatException e = - new FormatException( - 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 (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); - } - } - break; + // The plus 2 is for the length of the provider separator + // which is "::" - 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); - } - } - 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]; + pathsToParse[index].Substring( + 0, + separatorIndex + 1); } - catch (DriveNotFoundException driveNotFound) + } + } + else if (Leaf || LeafBase || Extension) + { + try + { + result = + SessionState.Path.ParseChildName( + pathsToParse[index], + CmdletProviderContext, + true); + if (LeafBase) { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; + result = System.IO.Path.GetFileNameWithoutExtension(result); } - catch (ProviderNotFoundException providerNotFound) + else if (Extension) { - 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; - } // switch + } + 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) { WriteObject(result); } - } // for each path - } // ProcessRecord + } + } #endregion Command code /// /// Removes either the drive or provider qualifier or both from the path. /// - /// /// /// The path to strip the provider qualifier from. /// - /// /// /// The path without the qualifier. /// - /// private string RemoveQualifier(string path) { Dbg.Diagnostics.Assert( @@ -465,7 +437,7 @@ private string RemoveQualifier(string path) if (SessionState.Path.IsProviderQualified(path)) { - int index = path.IndexOf("::", StringComparison.CurrentCulture); + int index = path.IndexOf("::", StringComparison.Ordinal); if (index != -1) { @@ -475,7 +447,7 @@ private string RemoveQualifier(string path) } else { - string driveName = String.Empty; + string driveName = string.Empty; if (SessionState.Path.IsPSAbsolute(path, out driveName)) { @@ -489,7 +461,6 @@ private string RemoveQualifier(string path) } return result; - } // RemoveQualifier - } // SplitPathCommand -} // namespace Microsoft.PowerShell.Commands - + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs index cb70655f8bc..19bad39785b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs @@ -1,22 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// /// The base class for the */content commands that also take - /// a passthrough parameter + /// a passthrough parameter. /// public class PassThroughContentCommandBase : ContentCommandBase { #region Parameters /// - /// Gets or sets the passthrough parameter to the command + /// Gets or sets the passthrough parameter to the command. /// [Parameter] public SwitchParameter PassThru @@ -24,16 +22,16 @@ public SwitchParameter PassThru get { return _passThrough; - } // get + } set { _passThrough = value; - } // set - } // PassThru + } + } /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// protected override bool ProviderSupportsShouldProcess @@ -62,20 +60,17 @@ protected override bool ProviderSupportsShouldProcess /// Initializes a CmdletProviderContext instance to the current context of /// the command. /// - /// /// /// A CmdletProviderContext instance initialized to the context of the current /// command. /// - /// internal CmdletProviderContext GetCurrentContext() { CmdletProviderContext currentCommandContext = CmdletProviderContext; currentCommandContext.PassThru = PassThru; return currentCommandContext; - } // GetCurrentContext + } #endregion protected members - } // PassThroughContentCommandBase -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs index 5028632301b..8b76ac36786 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs @@ -1,22 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// /// The base class for the */property commands that also take - /// a passthrough parameter + /// a passthrough parameter. /// public class PassThroughItemPropertyCommandBase : ItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the passthrough parameter to the command + /// Gets or sets the passthrough parameter to the command. /// [Parameter] public SwitchParameter PassThru @@ -24,18 +22,17 @@ public SwitchParameter PassThru get { return _passThrough; - } // get + } set { _passThrough = value; - } // set - } // PassThru + } + } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -45,7 +42,6 @@ public SwitchParameter PassThru /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { @@ -53,11 +49,12 @@ public override SwitchParameter Force { return base.Force; } + set { base.Force = value; } - } // Force + } #endregion Parameters @@ -74,7 +71,7 @@ public override SwitchParameter Force #region protected members /// - /// Determines if the provider for the specified path supports ShouldProcess + /// Determines if the provider for the specified path supports ShouldProcess. /// /// protected override bool ProviderSupportsShouldProcess @@ -89,20 +86,17 @@ protected override bool ProviderSupportsShouldProcess /// Initializes a CmdletProviderContext instance to the current context of /// the command. /// - /// /// /// A CmdletProviderContext instance initialized to the context of the current /// command. /// - /// internal CmdletProviderContext GetCurrentContext() { CmdletProviderContext currentCommandContext = CmdletProviderContext; currentCommandContext.PassThru = PassThru; return currentCommandContext; - } // GetCurrentContext + } #endregion protected members - } // PassThroughItemPropertyCommandBase -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs deleted file mode 100644 index 66bbe573a17..00000000000 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs +++ /dev/null @@ -1,213 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Management.Automation; -using Dbg = System.Management.Automation; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// The valid values for the -PathType parameter for test-path - /// - public enum TestPathType - { - /// - /// If the item at the path exists, true will be returned. - /// - Any, - - /// - /// If the item at the path exists and is a container, true will be returned. - /// - Container, - - /// - /// If the item at the path exists and is not a container, true will be returned. - /// - Leaf - } - - /// - /// A command to determine if an item exists at a specified path - /// - [Cmdlet(VerbsDiagnostic.Test, "Path", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113418")] - [OutputType(typeof(bool))] - public class TestPathCommand : CoreCommandWithCredentialsBase - { - #region Parameters - - /// - /// Gets or sets the path parameter to the command - /// - [Parameter(Position = 0, ParameterSetName = "Path", - Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] - public string[] Path - { - get { return _paths; } - set { _paths = value; } - } - - /// - /// Gets or sets the literal path parameter to the command - /// - [Parameter(ParameterSetName = "LiteralPath", - Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] - public string[] LiteralPath - { - get { return _paths; } - set - { - base.SuppressWildcardExpansion = true; - _paths = value; - } - } - - /// - /// Gets or sets the filter property - /// - [Parameter] - public override string Filter - { - get { return base.Filter; } - set { base.Filter = value; } - } - - /// - /// Gets or sets the include property - /// - [Parameter] - public override string[] Include - { - get { return base.Include; } - set { base.Include = value; } - } - - /// - /// Gets or sets the exclude property - /// - [Parameter] - public override string[] Exclude - { - get { return base.Exclude; } - set { base.Exclude = value; } - } - - /// - /// Gets or sets the isContainer property - /// - [Parameter] - [Alias("Type")] - public TestPathType PathType { get; set; } = TestPathType.Any; - - /// - /// Gets or sets the IsValid parameter - /// - [Parameter] - public SwitchParameter IsValid { get; set; } = new SwitchParameter(); - - /// - /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets - /// that require dynamic parameters should override this method and return the - /// dynamic parameter object. - /// - /// - /// - /// The context under which the command is running. - /// - /// - /// - /// An object representing the dynamic parameters for the cmdlet or null if there - /// are none. - /// - /// - internal override object GetDynamicParameters(CmdletProviderContext context) - { - object result = null; - - if (this.PathType == TestPathType.Any && !IsValid) - { - if (Path != null && Path.Length > 0) - { - result = InvokeProvider.Item.ItemExistsDynamicParameters(Path[0], context); - } - else - { - result = InvokeProvider.Item.ItemExistsDynamicParameters(".", context); - } - } - return result; - } // GetDynamicParameters - - #endregion Parameters - - #region parameter data - - /// - /// The path to the item to ping - /// - private string[] _paths; - - #endregion parameter data - - #region Command code - - /// - /// Determines if an item at the specified path exists. - /// - protected override void ProcessRecord() - { - CmdletProviderContext currentContext = CmdletProviderContext; - - foreach (string path in _paths) - { - bool result = false; - - try - { - if (IsValid) - { - result = SessionState.Path.IsValid(path, currentContext); - } - else - { - if (this.PathType == TestPathType.Container) - { - 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); - } - } - } - // Any of the known exceptions means the path does not exist. - catch (PSNotSupportedException) - { - } - catch (DriveNotFoundException) - { - } - catch (ProviderNotFoundException) - { - } - catch (ItemNotFoundException) - { - } - - WriteObject(result); - } - } // ProcessRecord - #endregion Command code - - - } // PingPathCommand -} // namespace Microsoft.PowerShell.Commands - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index 30ef0e096ac..91efda12263 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -1,69 +1,59 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Diagnostics; // Process class using System.ComponentModel; // Win32Exception -using System.Runtime.ConstrainedExecution; -using System.Runtime.Serialization; -using System.Threading; -using System.Management.Automation; +using System.Diagnostics; // Process class using System.Diagnostics.CodeAnalysis; -using System.Net; using System.IO; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Net; using System.Runtime.InteropServices; -using System.Security; -using System.Security.Permissions; +using System.Runtime.Serialization; using System.Security.Principal; -using Microsoft.Win32.SafeHandles; -using System.Management.Automation.Internal; -using Microsoft.PowerShell.Commands.Internal; +using System.Text; +using System.Threading; using Microsoft.Management.Infrastructure; - -using FileNakedHandle = System.IntPtr; -using DWORD = System.UInt32; +using Microsoft.PowerShell.Commands.Internal; +using Microsoft.Win32.SafeHandles; namespace Microsoft.PowerShell.Commands { - // 2004/12/17-JonN ProcessNameGlobAttribute was deeply wrong. - // For example, if you pass in a single Process, it will match - // all processes with the same name. - // I have removed the globbing code. - #region ProcessBaseCommand /// - /// This class implements the base for process commands + /// This class implements the base for process commands. /// public abstract class ProcessBaseCommand : Cmdlet { #region Parameters /// - /// The various process selection modes + /// The various process selection modes. /// internal enum MatchMode { /// - /// Select all processes + /// Select all processes. /// All, /// - /// Select processes matching the supplied names + /// Select processes matching the supplied names. /// ByName, /// - /// Select the processes matching the id + /// Select the processes matching the id. /// ById, /// /// Select the processes specified as input. /// ByInput - }; + } /// /// The current process selection mode. /// @@ -97,12 +87,14 @@ public virtual Process[] InputObject { return _input; } + set { myMode = MatchMode.ByInput; _input = value; } } + private Process[] _input = null; #endregion Parameters @@ -110,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 @@ -139,24 +131,24 @@ internal List MatchingProcesses() // before being stopped. PM confirms that this is fine. _matchingProcesses.Sort(ProcessComparison); return _matchingProcesses; - } // MatchingProcesses + } /// - /// sort function to sort by Name first, then Id + /// Sort function to sort by Name first, then Id. /// - /// first Process object - /// second Process object + /// First Process object. + /// Second Process object. /// - /// as String.Compare: returns less than zero if x less than y, - /// greater than 0 if x greater than y, 0 if x == y + /// As string.Compare: returns less than zero if x less than y, + /// greater than 0 if x greater than y, 0 if x == y. /// private static int ProcessComparison(Process x, Process y) { - int diff = String.Compare( + int diff = string.Compare( SafeGetProcessName(x), SafeGetProcessName(y), - StringComparison.CurrentCultureIgnoreCase); - if (0 != diff) + StringComparison.OrdinalIgnoreCase); + if (diff != 0) return diff; return SafeGetProcessId(x) - SafeGetProcessId(y); } @@ -171,11 +163,12 @@ private static int ProcessComparison(Process x, Process y) /// private void RetrieveMatchingProcessesByProcessName() { - if (null == processNames) + if (processNames == null) { _matchingProcesses = new List(AllProcesses); return; } + foreach (string pattern in processNames) { WildcardPattern wildcard = @@ -188,20 +181,30 @@ private void RetrieveMatchingProcessesByProcessName() found = true; AddIdempotent(process); } + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) { + string errorText = ProcessResources.NoProcessFoundForGivenName; + string errorName = nameof(ProcessResources.NoProcessFoundForGivenName); + + if (int.TryParse(pattern, out int x) && x >= 0) + { + errorText = ProcessResources.RecommendIdTagForGivenName; + errorName = nameof(ProcessResources.RecommendIdTagForGivenName); + } + WriteNonTerminatingError( - pattern, - 0, - pattern, - null, - ProcessResources.NoProcessFoundForGivenName, - "NoProcessFoundForGivenName", - ErrorCategory.ObjectNotFound); + processName: pattern, + processId: 0, + targetObject: pattern, + innerException: null, + resourceId: errorText, + errorId: errorName, + category: ErrorCategory.ObjectNotFound); } } - } // MatchingProcessesByProcessName + } /// /// Retrieves the list of all processes matching the Id @@ -212,11 +215,12 @@ private void RetrieveMatchingProcessesByProcessName() /// private void RetrieveMatchingProcessesById() { - if (null == processIds) + if (processIds == null) { Diagnostics.Assert(false, "null processIds"); throw PSTraceSource.NewInvalidOperationException(); } + foreach (int processId in processIds) { Process process; @@ -228,7 +232,7 @@ private void RetrieveMatchingProcessesById() catch (ArgumentException) { WriteNonTerminatingError( - "", + string.Empty, processId, processId, null, @@ -238,7 +242,7 @@ private void RetrieveMatchingProcessesById() continue; } } - } // MatchingProcessesById + } /// /// Retrieves the list of all processes matching the InputObject @@ -247,41 +251,31 @@ private void RetrieveMatchingProcessesById() /// private void RetrieveProcessesByInput() { - if (null == InputObject) + if (InputObject == null) { Diagnostics.Assert(false, "null InputObject"); throw PSTraceSource.NewInvalidOperationException(); } + foreach (Process process in InputObject) { SafeRefresh(process); AddIdempotent(process); } - } // MatchingProcessesByInput + } /// - /// 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 (null == _allProcesses) - { - List processes = new List(); - processes.AddRange(Process.GetProcesses()); - _allProcesses = processes.ToArray(); - } - return _allProcesses; - } - } - private Process[] _allProcesses = null; + internal Process[] AllProcesses => _allProcesses ??= Process.GetProcesses(); + + private Process[] _allProcesses; /// /// Add to , @@ -289,7 +283,7 @@ internal Process[] AllProcesses /// We use a Dictionary to optimize the check whether the object /// is already in the list. /// - /// process to add to list + /// Process to add to list. private void AddIdempotent( Process process) { @@ -348,9 +342,9 @@ internal void WriteNonTerminatingError( string message = StringUtil.Format(resourceId, processName, processId, - (null == innerException) ? "" : innerException.Message); + (innerException == null) ? string.Empty : innerException.Message); ProcessCommandException exception = - new ProcessCommandException(message, innerException); + new(message, innerException); exception.ProcessName = processName; WriteError(new ErrorRecord( @@ -367,11 +361,11 @@ internal static string SafeGetProcessName(Process process) } catch (Win32Exception) { - return ""; + return string.Empty; } catch (InvalidOperationException) { - return ""; + return string.Empty; } } @@ -434,15 +428,15 @@ internal static bool TryHasExited(Process process) } #endregion Internal - }//ProcessBaseCommand + } #endregion ProcessBaseCommand #region GetProcessCommand /// - /// This class implements the get-process command + /// This class implements the get-process command. /// [Cmdlet(VerbsCommon.Get, "Process", DefaultParameterSetName = NameParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113324", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096814", RemotingCapability = RemotingCapability.SupportedByCommand)] [OutputType(typeof(ProcessModule), typeof(FileVersionInfo), typeof(Process))] public sealed class GetProcessCommand : ProcessBaseCommand { @@ -460,16 +454,19 @@ public sealed class GetProcessCommand : ProcessBaseCommand #region Parameters /// - /// Has the list of process names on which to this command will work + /// Has the list of process names on which to this command will work. /// - // [ProcessNameGlobAttribute] [Parameter(Position = 0, ParameterSetName = NameParameterSet, ValueFromPipelineByPropertyName = true)] [Parameter(Position = 0, ParameterSetName = NameWithUserNameParameterSet, ValueFromPipelineByPropertyName = true)] [Alias("ProcessName")] [ValidateNotNullOrEmpty] public string[] Name { - get { return processNames; } + get + { + return processNames; + } + set { myMode = MatchMode.ByName; @@ -478,7 +475,7 @@ public string[] Name } /// - /// gets/sets an array of process IDs + /// Gets/sets an array of process IDs. /// [Parameter(ParameterSetName = IdParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Parameter(ParameterSetName = IdWithUserNameParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] @@ -489,6 +486,7 @@ public int[] Id { return processIds; } + set { myMode = MatchMode.ById; @@ -497,7 +495,7 @@ public int[] Id } /// - /// Input is a stream of [collections of] Process objects + /// Input is a stream of [collections of] Process objects. /// [Parameter(ParameterSetName = InputObjectParameterSet, Mandatory = true, ValueFromPipeline = true)] [Parameter(ParameterSetName = InputObjectWithUserNameParameterSet, Mandatory = true, ValueFromPipeline = true)] @@ -507,6 +505,7 @@ public override Process[] InputObject { return base.InputObject; } + set { base.InputObject = value; @@ -514,31 +513,25 @@ public override Process[] InputObject } /// - /// Include the UserName + /// Include the UserName. /// [Parameter(ParameterSetName = NameWithUserNameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = IdWithUserNameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = InputObjectWithUserNameParameterSet, Mandatory = true)] - public SwitchParameter IncludeUserName - { - get { return _includeUserName; } - set { _includeUserName = value; } - } - private bool _includeUserName = false; - - /// - ///To display the modules of a process - /// + public SwitchParameter IncludeUserName { get; set; } + /// + /// To display the modules of a process. + /// [Parameter(ParameterSetName = NameParameterSet)] [Parameter(ParameterSetName = IdParameterSet)] [Parameter(ParameterSetName = InputObjectParameterSet)] [ValidateNotNull] public SwitchParameter Module { get; set; } - /// - ///To display the fileversioninfo of the main module of a process - /// + /// + /// To display the fileversioninfo of the main module of a process. + /// [Parameter(ParameterSetName = NameParameterSet)] [Parameter(ParameterSetName = IdParameterSet)] [Parameter(ParameterSetName = InputObjectParameterSet)] @@ -551,27 +544,12 @@ 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 + /// Write the process objects. /// protected override void ProcessRecord() { foreach (Process process in MatchingProcesses()) { - //if module and fileversion are to be displayed if (Module.IsPresent && FileVersionInfo.IsPresent) { ProcessModule tempmodule = null; @@ -580,7 +558,7 @@ protected override void ProcessRecord() ProcessModuleCollection modules = process.Modules; foreach (ProcessModule pmodule in modules) { - //assigning to tempmodule to rethrow for exceptions on 64 bit machines + // Assigning to tempmodule to rethrow for exceptions on 64 bit machines tempmodule = pmodule; WriteObject(pmodule.FileVersionInfo, true); } @@ -618,7 +596,6 @@ protected override void ProcessRecord() } else if (Module.IsPresent) { - //if only modules are to be displayed try { WriteObject(process.Modules, true); @@ -641,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); @@ -648,10 +629,13 @@ protected override void ProcessRecord() } else if (FileVersionInfo.IsPresent) { - //if fileversion of each process is to be displayed try { - WriteObject(PsUtils.GetMainModule(process).FileVersionInfo, true); + ProcessModule mainModule = process.MainModule; + if (mainModule != null) + { + WriteObject(mainModule.FileVersionInfo, true); + } } catch (InvalidOperationException exception) { @@ -667,7 +651,7 @@ protected override void ProcessRecord() { if (exception.HResult == 299) { - WriteObject(PsUtils.GetMainModule(process).FileVersionInfo, true); + WriteObject(process.MainModule?.FileVersionInfo, true); } else { @@ -686,22 +670,22 @@ protected override void ProcessRecord() } else { - WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : (object)process); + WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : process); } - }//for loop - } // ProcessRecord + } + } #endregion Overrides #region Privates /// - /// New PSTypeName added to the process object + /// New PSTypeName added to the process object. /// private const string TypeNameForProcessWithUserName = "System.Diagnostics.Process#IncludeUserName"; /// - /// Add the 'UserName' NoteProperty to the Process object + /// Add the 'UserName' NoteProperty to the Process object. /// /// /// @@ -711,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); @@ -719,9 +703,8 @@ private static PSObject AddUserNameToProcess(Process process) return processAsPsobj; } - /// - /// Retrieve the UserName through PInvoke + /// Retrieve the UserName through PInvoke. /// /// /// @@ -738,72 +721,57 @@ 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); - - // Set the default length to be 256, so it will be sufficient for most cases - int userNameLength = 256, domainNameLength = 256; - var userNameStr = new StringBuilder(userNameLength); - var domainNameStr = new StringBuilder(domainNameLength); - Win32Native.SID_NAME_USE accountType; - - if (!Win32Native.LookupAccountSid(null, tokenUser.User.Sid, userNameStr, ref userNameLength, domainNameStr, ref domainNameLength, out accountType)) + else { - error = Marshal.GetLastWin32Error(); - if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER) - { - userNameStr.EnsureCapacity(userNameLength); - domainNameStr.EnsureCapacity(domainNameLength); - - if (!Win32Native.LookupAccountSid(null, tokenUser.User.Sid, userNameStr, ref userNameLength, domainNameStr, ref domainNameLength, out accountType)) { break; } - } - else - { - break; - } + return null; } + } - userName = domainNameStr + "\\" + userNameStr; - } 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 + // 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 + // The Process has exited, Process.Handle will raise this exception. } catch (Win32Exception) { - // We might get an AccessDenied error + // We might get an AccessDenied error. } catch (Exception) { - // I don't expect to get other exceptions, + // I don't expect to get other exceptions. } finally { @@ -817,20 +785,20 @@ private static string RetrieveProcessUserName(Process process) Win32Native.CloseHandle(processTokenHandler); } } - #endif return userName; } #endregion Privates - }//GetProcessCommand + } #endregion GetProcessCommand #region WaitProcessCommand /// - /// This class implements the Wait-process command + /// This class implements the Wait-process command. /// - [Cmdlet(VerbsLifecycle.Wait, "Process", DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135277")] + [Cmdlet(VerbsLifecycle.Wait, "Process", DefaultParameterSetName = "Name", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097146")] + [OutputType(typeof(Process))] public sealed class WaitProcessCommand : ProcessBaseCommand { #region Parameters @@ -852,6 +820,7 @@ public int[] Id { return processIds; } + set { myMode = MatchMode.ById; @@ -860,7 +829,7 @@ public int[] Id } /// - /// Name of the processes to wait on for termination + /// Name of the processes to wait on for termination. /// [Parameter( ParameterSetName = "Name", @@ -871,7 +840,11 @@ public int[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return processNames; } + get + { + return processNames; + } + set { myMode = MatchMode.ByName; @@ -880,7 +853,7 @@ public string[] Name } /// - /// If specified, wait for this number of seconds + /// If specified, wait for this number of seconds. /// [Parameter(Position = 1)] [Alias("TimeoutSec")] @@ -892,33 +865,47 @@ public int Timeout { return _timeout; } + set { _timeout = value; _timeOutSpecified = true; } } + + /// + /// 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; - #endregion Parameters private bool _disposed = false; #region IDisposable /// - /// Dispose method of IDisposable interface. + /// Dispose method of IDisposable interface. /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { if (_waitHandle != null) { _waitHandle.Dispose(); _waitHandle = null; } + _disposed = true; } } @@ -929,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(); } } @@ -942,19 +926,18 @@ 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. + // Wait handle which is used by thread to sleep. private ManualResetEvent _waitHandle; private int _numberOfProcessesToWaitFor; - /// - /// gets the list of process + /// Gets the list of process. /// protected override void ProcessRecord() { - //adding the processes into the list + // adding the processes into the list foreach (Process process in MatchingProcesses()) { // Idle process has processid zero,so handle that because we cannot wait on it. @@ -965,17 +948,18 @@ 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; } + _processList.Add(process); } - } // ProcessRecord + } /// - /// Wait for the process to terminate + /// Wait for the process to terminate. /// protected override void EndProcessing() { @@ -984,10 +968,15 @@ 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 += new EventHandler(myProcess_Exited); + process.Exited += myProcess_Exited; if (!process.HasExited) { System.Threading.Interlocked.Increment(ref _numberOfProcessesToWaitFor); @@ -1000,54 +989,58 @@ protected override void EndProcessing() } } + bool hasTimedOut = false; if (_numberOfProcessesToWaitFor > 0) { if (_timeOutSpecified) { - _waitHandle.WaitOne(_timeout * 1000); + hasTimedOut = !_waitHandle.WaitOne(_timeout * 1000); } else { _waitHandle.WaitOne(); } } - foreach (Process process in _processList) + + if (hasTimedOut || (!Any && _numberOfProcessesToWaitFor > 0)) { - try + foreach (Process process in _processList) { - if (!process.HasExited) + try { - //write the error - 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); } } - } - /// - /// StopProcessing - /// - protected override void StopProcessing() - { - if (_waitHandle != null) + if (PassThru) { - _waitHandle.Set(); + WriteObject(_processList, enumerateCollection: true); } } + + /// + /// StopProcessing. + /// + protected override void StopProcessing() => _waitHandle?.Set(); + #endregion Overrides - }//WaitProcessCommand + } #endregion WaitProcessCommand #region StopProcessCommand /// - /// This class implements the stop-process command + /// This class implements the stop-process command. /// /// /// Processes will be sorted before being stopped. PM confirms @@ -1055,15 +1048,14 @@ protected override void StopProcessing() /// [Cmdlet(VerbsLifecycle.Stop, "Process", DefaultParameterSetName = "Id", - SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113412")] + SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097058")] [OutputType(typeof(Process))] public sealed class StopProcessCommand : ProcessBaseCommand { #region Parameters /// - /// Has the list of process names on which to this command will work + /// Has the list of process names on which to this command will work. /// - // [ProcessNameGlobAttribute] [Parameter( ParameterSetName = "Name", Mandatory = true, @@ -1075,6 +1067,7 @@ public string[] Name { return processNames; } + set { processNames = value; @@ -1083,7 +1076,7 @@ public string[] Name } /// - /// gets/sets an array of process IDs + /// Gets/sets an array of process IDs. /// [Parameter( Position = 0, @@ -1096,6 +1089,7 @@ public int[] Id { return processIds; } + set { myMode = MatchMode.ById; @@ -1104,7 +1098,7 @@ public int[] Id } /// - /// gets/sets an array of objects + /// Gets/sets an array of objects. /// [Parameter( Position = 0, @@ -1118,6 +1112,7 @@ public int[] Id { return base.InputObject; } + set { base.InputObject = value; @@ -1132,12 +1127,10 @@ public int[] Id public SwitchParameter PassThru { get { return _passThru; } + set { _passThru = value; } } - - //Addition by v-ramch Mar 18 2008 - //Added force parameter /// /// Specifies whether to force a process to kill /// even if it has dependent services. @@ -1172,7 +1165,10 @@ protected override void ProcessRecord() SafeGetProcessName(process), SafeGetProcessId(process)); - if (!ShouldProcess(targetString)) { continue; } + if (!ShouldProcess(targetString)) + { + continue; + } try { @@ -1196,7 +1192,6 @@ protected override void ProcessRecord() } catch (Win32Exception ex) { - // This process could not be stopped, so write a non-terminating error. WriteNonTerminatingError( process, ex, ProcessResources.CouldNotStopProcess, "CouldNotStopProcess", ErrorCategory.CloseError); @@ -1205,8 +1200,7 @@ protected override void ProcessRecord() try { - //check if the process is current process and kill it at last - if (Process.GetCurrentProcess().Id == SafeGetProcessId(process)) + if (Environment.ProcessId == SafeGetProcessId(process)) { _shouldKillCurrentProcess = true; continue; @@ -1214,7 +1208,6 @@ protected override void ProcessRecord() if (Platform.IsWindows && !Force) { - // Check if the process is owned by current user if (!IsProcessOwnedByCurrentUser(process)) { string message = StringUtil.Format( @@ -1228,13 +1221,12 @@ protected override void ProcessRecord() } } - //if the process is svchost stop all the dependent services before killing process - if (string.Equals(SafeGetProcessName(process), "SVCHOST", StringComparison.CurrentCultureIgnoreCase)) + // If the process is svchost stop all the dependent services before killing process + if (string.Equals(SafeGetProcessName(process), "SVCHOST", StringComparison.OrdinalIgnoreCase)) { StopDependentService(process); } - // kill the process if (!process.HasExited) { process.Kill(); @@ -1244,8 +1236,6 @@ protected override void ProcessRecord() { if (!TryHasExited(process)) { - // This process could not be stopped, - // so write a non-terminating error. WriteNonTerminatingError( process, exception, ProcessResources.CouldNotStopProcess, "CouldNotStopProcess", ErrorCategory.CloseError); @@ -1256,8 +1246,6 @@ protected override void ProcessRecord() { if (!TryHasExited(process)) { - // This process could not be stopped, - // so write a non-terminating error. WriteNonTerminatingError( process, exception, ProcessResources.CouldNotStopProcess, "CouldNotStopProcess", ErrorCategory.CloseError); @@ -1268,8 +1256,7 @@ protected override void ProcessRecord() if (PassThru) WriteObject(process); } - } // ProcessRecord - + } /// /// Kill the current process here. @@ -1280,31 +1267,31 @@ protected override void EndProcessing() { StopProcess(Process.GetCurrentProcess()); } - }//EndProcessing + } #endregion Overrides #region Private /// - /// should the current powershell process to be killed + /// Should the current powershell process to be killed. /// private bool _shouldKillCurrentProcess; /// - /// Boolean variables to display the warning using shouldcontinue + /// Boolean variables to display the warning using ShouldContinue. /// - private bool _yesToAll,_noToAll; + private bool _yesToAll, _noToAll; /// - /// Current windows user name + /// Current windows user name. /// private string _currentUserName; /// - /// gets the owner of the process + /// Gets the owner of the process. /// /// - /// returns the owner + /// Returns the owner. private bool IsProcessOwnedByCurrentUser(Process process) { const uint TOKEN_QUERY = 0x0008; @@ -1323,30 +1310,33 @@ private bool IsProcessOwnedByCurrentUser(Process process) using (var processUser = new WindowsIdentity(ph)) { - return string.Equals(processUser.Name, _currentUserName, StringComparison.CurrentCultureIgnoreCase); + return string.Equals(processUser.Name, _currentUserName, StringComparison.OrdinalIgnoreCase); } } } catch (IdentityNotMappedException) { - //Catching IdentityMappedException - //Need not throw error. + // Catching IdentityMappedException + // Need not throw error. } catch (ArgumentException) { - //Catching ArgumentException. In Win2k3 Token is zero - //Need not throw error. + // Catching ArgumentException. In Win2k3 Token is zero + // Need not throw error. } finally { - if (ph != IntPtr.Zero) { Win32Native.CloseHandle(ph); } + if (ph != IntPtr.Zero) + { + Win32Native.CloseHandle(ph); + } } return false; } /// - /// Stop the service that depends on the process and its child services + /// Stop the service that depends on the process and its child services. /// /// private void StopDependentService(Process process) @@ -1364,7 +1354,6 @@ private void StopDependentService(Process process) string serviceName = oService.CimInstanceProperties["Name"].Value.ToString(); using (var service = new System.ServiceProcess.ServiceController(serviceName)) { - //try stopping the service, if cant we are not writing exception try { service.Stop(); @@ -1386,10 +1375,10 @@ private void StopDependentService(Process process) } /// - /// stops the given process throws non terminating error if cant - /// process to be stopped - /// true if process stopped successfully else false + /// Stops the given process throws non terminating error if can't. /// + /// Process to be stopped. + /// True if process stopped successfully else false. private void StopProcess(Process process) { Exception exception = null; @@ -1409,7 +1398,7 @@ private void StopProcess(Process process) exception = e; } - if (null != exception) + if (exception != null) { if (!TryHasExited(process)) { @@ -1423,14 +1412,14 @@ private void StopProcess(Process process) } #endregion Private - }//StopProcessCommand + } #endregion StopProcessCommand #region DebugProcessCommand /// - /// This class implements the Debug-process command + /// This class implements the Debug-process command. /// - [Cmdlet(VerbsDiagnostic.Debug, "Process", DefaultParameterSetName = "Name", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135206")] + [Cmdlet(VerbsDiagnostic.Debug, "Process", DefaultParameterSetName = "Name", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096809")] public sealed class DebugProcessCommand : ProcessBaseCommand { #region Parameters @@ -1452,6 +1441,7 @@ public int[] Id { return processIds; } + set { myMode = MatchMode.ById; @@ -1460,7 +1450,7 @@ public int[] Id } /// - /// Name of the processes to wait on for termination + /// Name of the processes to wait on for termination. /// [Parameter( ParameterSetName = "Name", @@ -1471,7 +1461,11 @@ public int[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return processNames; } + get + { + return processNames; + } + set { myMode = MatchMode.ByName; @@ -1484,11 +1478,10 @@ public string[] Name #region Overrides /// - /// gets the list of process and attach the debugger to the processes + /// Gets the list of process and attach the debugger to the processes. /// protected override void ProcessRecord() { - //for the processes foreach (Process process in MatchingProcesses()) { string targetMessage = StringUtil.Format( @@ -1496,9 +1489,12 @@ 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. + // Sometimes Idle process has processid zero,so handle that because we cannot attach debugger to it. if (process.Id == 0) { WriteNonTerminatingError( @@ -1511,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) { @@ -1531,12 +1530,12 @@ protected override void ProcessRecord() AttachDebuggerToProcess(process); } - } // ProcessRecord + } #endregion Overrides /// - /// Attach debugger to the process + /// Attach debugger to the process. /// private void AttachDebuggerToProcess(Process process) { @@ -1562,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)), @@ -1575,20 +1573,22 @@ private void AttachDebuggerToProcess(Process process) } /// - /// Map the return code from 'AttachDebugger' to error message + /// 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; } } @@ -1597,38 +1597,35 @@ private string MapReturnCodeToErrorMessage(int returnCode) #region StartProcessCommand /// - /// This class implements the Start-process command + /// This class implements the Start-process command. /// - [Cmdlet(VerbsLifecycle.Start, "Process", DefaultParameterSetName = "Default", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135261")] + [Cmdlet(VerbsLifecycle.Start, "Process", DefaultParameterSetName = "Default", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097141")] [OutputType(typeof(Process))] public sealed class StartProcessCommand : PSCmdlet, IDisposable { - private ManualResetEvent _waithandle = null; + private readonly CancellationTokenSource _cancellationTokenSource = new(); private bool _isDefaultSetParameterSpecified = false; #region Parameters /// - /// Path/FileName of the process to start + /// Path/FileName of the process to start. /// [Parameter(Mandatory = true, Position = 0)] [ValidateNotNullOrEmpty] - [Alias("PSPath")] + [Alias("PSPath", "Path")] public string FilePath { get; set; } - /// - /// Arguments for the process + /// Arguments for the process. /// [Parameter(Position = 1)] [Alias("Args")] - [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ArgumentList { get; set; } - /// - /// Credentials for the process + /// Credentials for the process. /// [Parameter(ParameterSetName = "Default")] [Alias("RunAs")] @@ -1636,170 +1633,226 @@ public sealed class StartProcessCommand : PSCmdlet, IDisposable [Credential] public PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } + set { _credential = value; _isDefaultSetParameterSpecified = true; } } + private PSCredential _credential; /// - /// working directory of the process + /// Working directory of the process. /// [Parameter] [ValidateNotNullOrEmpty] public string WorkingDirectory { get; set; } - /// - /// load user profile from registry + /// Load user profile from registry. /// [Parameter(ParameterSetName = "Default")] [Alias("Lup")] public SwitchParameter LoadUserProfile { - get { return _loaduserprofile; } + get + { + return _loaduserprofile; + } + set { _loaduserprofile = value; _isDefaultSetParameterSpecified = true; } } + 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 { _nonewwindow = value; _isDefaultSetParameterSpecified = true; } } + private SwitchParameter _nonewwindow; /// - /// passthru parameter + /// PassThru parameter. /// [Parameter] public SwitchParameter PassThru { get; set; } - /// - /// Redirect error + /// Redirect error. /// [Parameter(ParameterSetName = "Default")] [Alias("RSE")] [ValidateNotNullOrEmpty] public string RedirectStandardError { - get { return _redirectstandarderror; } + get + { + return _redirectstandarderror; + } + set { _redirectstandarderror = value; _isDefaultSetParameterSpecified = true; } } - private string _redirectstandarderror; + private string _redirectstandarderror; /// - /// Redirect input + /// Redirect input. /// [Parameter(ParameterSetName = "Default")] [Alias("RSI")] [ValidateNotNullOrEmpty] public string RedirectStandardInput { - get { return _redirectstandardinput; } + get + { + return _redirectstandardinput; + } + set { _redirectstandardinput = value; _isDefaultSetParameterSpecified = true; } } - private string _redirectstandardinput; + private string _redirectstandardinput; /// - /// Redirect output + /// Redirect output. /// [Parameter(ParameterSetName = "Default")] [Alias("RSO")] [ValidateNotNullOrEmpty] public string RedirectStandardOutput { - get { return _redirectstandardoutput; } + get + { + return _redirectstandardoutput; + } + set { _redirectstandardoutput = value; _isDefaultSetParameterSpecified = true; } } + private string _redirectstandardoutput; /// - /// Verb + /// Verb. /// /// - /// The 'Verb' parameter is not supported in OneCore PowerShell - /// because 'UseShellExecute' is not supported in CoreCLR. + /// The 'Verb' parameter is only supported on Windows Desktop. /// [Parameter(ParameterSetName = "UseShellExecute")] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string Verb { get; set; } /// - /// Window style of the process window + /// Window style of the process window. /// [Parameter] [ValidateNotNullOrEmpty] public ProcessWindowStyle WindowStyle { - get { return _windowstyle; } + get + { + return _windowstyle; + } + set { _windowstyle = value; _windowstyleSpecified = true; } } + private ProcessWindowStyle _windowstyle = ProcessWindowStyle.Normal; private bool _windowstyleSpecified = false; /// - /// wait for th eprocess to terminate + /// Wait for the process to terminate. /// [Parameter] public SwitchParameter Wait { get; set; } /// - /// Default Environment + /// Default Environment. /// [Parameter(ParameterSetName = "Default")] public SwitchParameter UseNewEnvironment { - get { return _UseNewEnvironment; } + get + { + return _UseNewEnvironment; + } + set { _UseNewEnvironment = value; _isDefaultSetParameterSpecified = true; } } + 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 /// - /// BeginProcessing + /// BeginProcessing. /// protected override void BeginProcessing() { @@ -1812,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; } @@ -1830,17 +1883,16 @@ 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); } } - //create an instance of the ProcessStartInfo Class - ProcessStartInfo startInfo = new ProcessStartInfo(); - //use ShellExecute by default if we are running on full windows SKUs + ProcessStartInfo startInfo = new(); + // Use ShellExecute by default if we are running on full windows SKUs startInfo.UseShellExecute = Platform.IsWindowsDesktop; - //Path = Mandatory parameter -> Will not be empty. + // Path = Mandatory parameter -> Will not be empty. try { CommandInfo cmdinfo = CommandDiscovery.LookupCommandInfo( @@ -1851,40 +1903,46 @@ 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 + // so don't use ShellExecute if arguments are specified + + // Linux relies on `xdg-open` and macOS relies on `open` which behave differently than Windows ShellExecute when running console commands + // as a new console will be opened. So to avoid that, we only use ShellExecute on non-Windows if the filename is not an actual command (like a URI) + startInfo.UseShellExecute = (ArgumentList == null); +#endif } - //Arguments + if (ArgumentList != null) { - StringBuilder sb = new StringBuilder(); - foreach (string str in ArgumentList) - { - sb.Append(str); - sb.Append(' '); - } - startInfo.Arguments = sb.ToString(); ; + startInfo.Arguments = string.Join(' ', ArgumentList); } - - //WorkingDirectory if (WorkingDirectory != null) { - //WorkingDirectory -> Not Exist -> Throw Error + // WorkingDirectory -> Not Exist -> Throw Error WorkingDirectory = ResolveFilePath(WorkingDirectory); 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; } + startInfo.WorkingDirectory = WorkingDirectory; } else { - //Working Directory not specified -> Assign Current Path. - startInfo.WorkingDirectory = ResolveFilePath(this.SessionState.Path.CurrentFileSystemLocation.Path); - } + // 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")) { @@ -1893,32 +1951,34 @@ protected override void BeginProcessing() startInfo.UseShellExecute = false; } - //UseNewEnvironment 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); } - //WindowStyle startInfo.WindowStyle = _windowstyle; - //NewWindow - 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; } #if !UNIX - //LoadUserProfile. startInfo.LoadUserProfile = _loaduserprofile; #endif if (_credential != null) { - //Gets NetworkCredentials NetworkCredential nwcredential = _credential.GetNetworkCredential(); startInfo.UserName = nwcredential.UserName; - if (String.IsNullOrEmpty(nwcredential.Domain)) + if (string.IsNullOrEmpty(nwcredential.Domain)) { startInfo.Domain = "."; } @@ -1926,59 +1986,60 @@ protected override void BeginProcessing() { startInfo.Domain = nwcredential.Domain; } + startInfo.Password = _credential.Password; } - //RedirectionInput File Check -> Not Exist -> Throw Error + // RedirectionInput File Check -> Not Exist -> Throw Error if (_redirectstandardinput != null) { _redirectstandardinput = ResolveFilePath(_redirectstandardinput); 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; } } - //RedirectionInput == RedirectionOutput -> Throw Error + // RedirectionInput == RedirectionOutput -> Throw Error if (_redirectstandardinput != null && _redirectstandardoutput != null) { _redirectstandardinput = ResolveFilePath(_redirectstandardinput); _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); - if (_redirectstandardinput.Equals(_redirectstandardoutput, StringComparison.CurrentCultureIgnoreCase)) + 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; } } - //RedirectionInput == RedirectionError -> Throw Error + // RedirectionInput == RedirectionError -> Throw Error if (_redirectstandardinput != null && _redirectstandarderror != null) { _redirectstandardinput = ResolveFilePath(_redirectstandardinput); _redirectstandarderror = ResolveFilePath(_redirectstandarderror); - if (_redirectstandardinput.Equals(_redirectstandarderror, StringComparison.CurrentCultureIgnoreCase)) + 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; } } - //RedirectionOutput == RedirectionError -> Throw Error + // RedirectionOutput == RedirectionError -> Throw Error if (_redirectstandardoutput != null && _redirectstandarderror != null) { _redirectstandarderror = ResolveFilePath(_redirectstandarderror); _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); - if (_redirectstandardoutput.Equals(_redirectstandarderror, StringComparison.CurrentCultureIgnoreCase)) + 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; } @@ -1986,19 +2047,90 @@ protected override void BeginProcessing() } else if (ParameterSetName.Equals("UseShellExecute")) { - //Verb - if (Verb != null) { startInfo.Verb = Verb; } - //WindowStyle + 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; + } - //Starts the Process - 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 + } - //Wait and Passthru Implementation. if (PassThru.IsPresent) { if (process != null) @@ -2008,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); } } @@ -2020,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 += new EventHandler(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 } @@ -2044,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); } } @@ -2052,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) @@ -2112,48 +2217,23 @@ private void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary Env { processEnvironment.Remove(entry.Key.ToString()); } - if (entry.Key.ToString().Equals("PATH")) - { - 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) - { + if (entry.Value != null) + { + if (entry.Key.ToString().Equals("PATH")) + { #if UNIX - Process 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); - } - return process; + processEnvironment.Add(entry.Key.ToString(), entry.Value.ToString()); #else - Process process = null; - if (startInfo.UseShellExecute) - { - process = StartWithShellExecute(startInfo); - } - else - { - process = StartWithCreateProcess(startInfo); - } - return process; + 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()); + } + } + } } #if UNIX @@ -2162,7 +2242,7 @@ private Process Start(ProcessStartInfo startInfo) private void StdOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { - if (!String.IsNullOrEmpty(outLine.Data)) + if (!string.IsNullOrEmpty(outLine.Data)) { _outputWriter.WriteLine(outLine.Data); _outputWriter.Flush(); @@ -2171,7 +2251,7 @@ private void StdOutputHandler(object sendingProcess, DataReceivedEventArgs outLi private void StdErrorHandler(object sendingProcess, DataReceivedEventArgs outLine) { - if (!String.IsNullOrEmpty(outLine.Data)) + if (!string.IsNullOrEmpty(outLine.Data)) { _errorWriter.WriteLine(outLine.Data); _errorWriter.Flush(); @@ -2191,14 +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) @@ -2255,53 +2329,51 @@ private void WriteToStandardInput(Process p) string line = reader.ReadToEnd(); writer.WriteLine(line); } + 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) + 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) { - int error = Marshal.GetLastWin32Error(); - Win32Exception win32ex = new Win32Exception(error); + 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("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal); + bool flag = str.StartsWith('"') && str.EndsWith('"'); if (!flag) { - builder.Append("\""); + builder.Append('"'); } + builder.Append(str); if (!flag) { - builder.Append("\""); + builder.Append('"'); } + if (!string.IsNullOrEmpty(arguments)) { - builder.Append(" "); + builder.Append(' '); builder.Append(arguments); } + return builder; } @@ -2313,132 +2385,168 @@ 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'); } + builder.Append('\0'); // Use Unicode encoding bytes = Encoding.Unicode.GetBytes(builder.ToString()); - if (bytes.Length > 0xffff) + + return bytes; + } + + private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods.STARTUPINFO lpStartupInfo, ref int creationFlags) + { + // If we are starting a process using the current console window, we need to set its standard handles + // explicitly when they are not redirected because otherwise they won't be set and the new process will + // fail with the "invalid handle" error. + // + // However, if we are starting a process with a new console window, we should not explicitly set those + // standard handles when they are not redirected, but instead let Windows figure out the default to use + // when creating the process. Otherwise, the standard input handles of the current window and the new + // window will get weirdly tied together and cause problems. + bool hasRedirection = startinfo.CreateNoWindow + || _redirectstandardinput is not null + || _redirectstandardoutput is not null + || _redirectstandarderror is not null; + + // RedirectionStandardInput + if (_redirectstandardinput != null) { - throw new InvalidOperationException("EnvironmentBlockTooLong"); + startinfo.RedirectStandardInput = true; + _redirectstandardinput = ResolveFilePath(_redirectstandardinput); + lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, FileMode.Open); } - return bytes; + 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, 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, FileMode.Create); + } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdError = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-12), + ownsHandle: false); + } + + 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; + + // 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; + } + } + + // 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(); - string message = String.Empty; + GCHandle pinnedEnvironmentBlock = new(); + IntPtr AddressOfEnvironmentBlock = IntPtr.Zero; + string message = string.Empty; - //building the cmdline with the file name given and it's arguments + // building the cmdline with the file name given and it's arguments StringBuilder cmdLine = BuildCommandLine(startinfo.FileName, startinfo.Arguments); try { - //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); - } - //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); - } - //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); - } - //STARTF_USESTDHANDLES - lpStartupInfo.dwFlags = 0x100; - int creationFlags = 0; - 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; - - // 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; - } - } + SetStartupInfo(startinfo, ref lpStartupInfo, ref creationFlags); - // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. - creationFlags |= 0x00000004; - - IntPtr AddressOfEnvironmentBlock = IntPtr.Zero; - var environmentVars = startinfo.EnvironmentVariables; - if (environmentVars != null) + // We follow the logic: + // - Ignore `UseNewEnvironment` when we run a process as another user. + // Setting initial environment variables makes sense only for current user. + // - Set environment variables if they present in ProcessStartupInfo. + if (!UseNewEnvironment) { - if (this.UseNewEnvironment) + var environmentVars = startinfo.EnvironmentVariables; + if (environmentVars != null) { // All Windows Operating Systems that we support are Windows NT systems, so we use Unicode for environment. creationFlags |= 0x400; + pinnedEnvironmentBlock = GCHandle.Alloc(ConvertEnvVarsToByteArray(environmentVars), GCHandleType.Pinned); AddressOfEnvironmentBlock = pinnedEnvironmentBlock.AddrOfPinnedObject(); } } + bool flag; if (_credential != null) { + // Run process as another user. ProcessNativeMethods.LogonFlags logonFlags = 0; if (startinfo.LoadUserProfile) { @@ -2449,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(); @@ -2468,13 +2576,14 @@ 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); } + goto Label_03AE; } finally @@ -2484,28 +2593,40 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) Marshal.ZeroFreeCoTaskMemUnicode(password); } } - }//end of if + } + + // Run process as current user. + if (UseNewEnvironment) + { + // All Windows Operating Systems that we support are Windows NT systems, so we use Unicode for environment. + creationFlags |= 0x400; + + IntPtr token = WindowsIdentity.GetCurrent().Token; + if (!ProcessNativeMethods.CreateEnvironmentBlock(out AddressOfEnvironmentBlock, token, false)) + { + 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 { @@ -2513,11 +2634,15 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) { pinnedEnvironmentBlock.Free(); } + else + { + ProcessNativeMethods.DestroyEnvironmentBlock(AddressOfEnvironmentBlock); + } lpStartupInfo.Dispose(); - lpProcessInformation.Dispose(); } } +#endif /// /// This method will be used only on Windows full desktop. @@ -2532,138 +2657,134 @@ 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); } + return result; } -#endif #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; } - internal static class ProcessNativeMethods - { - // Fields - internal static UInt32 GENERIC_READ = 0x80000000; - internal static UInt32 GENERIC_WRITE = 0x40000000; - internal static UInt32 FILE_ATTRIBUTE_NORMAL = 0x80000000; - internal static UInt32 CREATE_ALWAYS = 2; - internal static UInt32 FILE_SHARE_WRITE = 0x00000002; - internal static UInt32 FILE_SHARE_READ = 0x00000001; - internal static UInt32 OF_READWRITE = 0x00000002; - internal static UInt32 OPEN_EXISTING = 3; + 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()); + } - // Methods - // static NativeMethods(); + public void Dispose() + { + Process.Dispose(); + Thread.Dispose(); + GC.SuppressFinalize(this); + } + ~ProcessInformation() => Dispose(); + } - [DllImport(PinvokeDllNames.GetStdHandleDllName, CharSet = CharSet.Ansi, SetLastError = true)] + internal static class ProcessNativeMethods + { + [DllImport(PinvokeDllNames.GetStdHandleDllName, SetLastError = true)] public static extern IntPtr GetStdHandle(int whichHandle); [DllImport(PinvokeDllNames.CreateProcessWithLogonWDllName, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CreateProcessWithLogonW(string userName, string domain, IntPtr password, @@ -2674,9 +2795,10 @@ 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)] public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, @@ -2686,22 +2808,18 @@ 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); + [DllImport("userenv.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); [Flags] internal enum LogonFlags @@ -2711,11 +2829,21 @@ 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; public bool bInheritHandle; + public SECURITY_ATTRIBUTES() { this.nLength = 12; @@ -2731,23 +2859,24 @@ internal SafeLocalMemHandle() : base(true) { } - [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() { return (LocalFree(base.handle) == IntPtr.Zero); } } - [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; @@ -2767,6 +2896,7 @@ internal class STARTUPINFO public SafeFileHandle hStdInput; public SafeFileHandle hStdOutput; public SafeFileHandle hStdError; + public STARTUPINFO() { this.lpReserved = IntPtr.Zero; @@ -2788,11 +2918,13 @@ public void Dispose(bool disposing) this.hStdInput.Dispose(); this.hStdInput = null; } + if ((this.hStdOutput != null) && !this.hStdOutput.IsInvalid) { this.hStdOutput.Dispose(); this.hStdOutput = null; } + if ((this.hStdError != null) && !this.hStdError.IsInvalid) { this.hStdError.Dispose(); @@ -2807,103 +2939,36 @@ 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 #region ProcessCommandException /// - /// Non-terminating errors occurring in the process noun commands + /// Non-terminating errors occurring in the process noun commands. /// - [Serializable] public class ProcessCommandException : SystemException { #region ctors /// - /// unimplemented standard constructor + /// Unimplemented standard constructor. /// - /// doesn't return + /// Doesn't return. public ProcessCommandException() : base() { throw new NotImplementedException(); } /// - /// standard constructor + /// Standard constructor. /// /// - /// constructed object + /// Constructed object. public ProcessCommandException(string message) : base(message) { } /// - /// standard constructor + /// Standard constructor. /// /// /// @@ -2915,53 +2980,36 @@ public ProcessCommandException(string message, Exception innerException) #region Serialization /// - /// serialization constructor + /// Serialization constructor. /// /// /// - /// constructed object + /// 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("info"); - info.AddValue("ProcessName", _processName); - } #endregion Serialization #region Properties /// - /// Name of the process which could not be found or operated upon + /// Name of the process which could not be found or operated upon. /// /// public string ProcessName { get { return _processName; } + set { _processName = value; } } - private string _processName = String.Empty; + + private string _processName = string.Empty; #endregion Properties - } // class ProcessCommandException + } #endregion ProcessCommandException -}//Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs index f64d33e18f7..edc0bbc3a91 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs @@ -1,21 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// The base class for the */property commands + /// The base class for the */property commands. /// public class ItemPropertyCommandBase : CoreCommandWithCredentialsBase { #region Parameters /// - /// Gets or sets the filter parameter + /// Gets or sets the filter parameter. /// [Parameter] public override string Filter @@ -23,16 +22,16 @@ public override string Filter get { return base.Filter; - } // get + } set { base.Filter = value; - } // set - } // Filter + } + } /// - /// Gets or sets the include property + /// Gets or sets the include property. /// [Parameter] public override string[] Include @@ -40,16 +39,16 @@ public override string[] Include get { return base.Include; - } // get + } set { base.Include = value; - } // set - } // Include + } + } /// - /// Gets or sets the exclude property + /// Gets or sets the exclude property. /// [Parameter] public override string[] Exclude @@ -57,22 +56,22 @@ public override string[] Exclude get { return base.Exclude; - } // get + } set { base.Exclude = value; - } // set - } // Exclude + } + } #endregion Parameters #region parameter data /// - /// The path to the item + /// The path to the item. /// - internal string[] paths = new string[0]; + internal string[] paths = Array.Empty(); #endregion parameter data - } // ItemPropertyCommandBase -} // 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 091e1903a45..0d547abfa9d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RegisterWMIEventCommand.cs @@ -1,16 +1,15 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; -using System.Management.Automation; using System.Collections.ObjectModel; using System.Diagnostics; -using System.Threading; using System.Management; +using System.Management.Automation; using System.Text; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -24,42 +23,41 @@ public class RegisterWmiEventCommand : ObjectEventRegistrationBase #region parameters /// - /// The WMI namespace to use + /// The WMI namespace to use. /// [Parameter] [Alias("NS")] public string Namespace { get; set; } = "root\\cimv2"; /// - /// The credential to use + /// The credential to use. /// [Parameter] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// - /// The ComputerName in which to query + /// The ComputerName in which to query. /// [Parameter] [Alias("Cn")] [ValidateNotNullOrEmpty] public string ComputerName { get; set; } = "localhost"; - /// - /// The WMI class to use + /// The WMI class to use. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "class")] public string Class { get; set; } = null; /// - /// The query string to search for objects + /// The query string to search for objects. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "query")] public string Query { get; set; } = null; /// - /// Timeout in milliseconds + /// Timeout in milliseconds. /// [Parameter] [Alias("TimeoutMSec")] @@ -69,6 +67,7 @@ public Int64 Timeout { return _timeOut; } + set { _timeOut = value; @@ -87,36 +86,36 @@ private string BuildEventQuery(string objectName) returnValue.Append(objectName); return returnValue.ToString(); } + private string GetScopeString(string computer, string namespaceParameter) { StringBuilder returnValue = new StringBuilder("\\\\"); returnValue.Append(computer); - returnValue.Append("\\"); + returnValue.Append('\\'); returnValue.Append(namespaceParameter); return returnValue.ToString(); } #endregion helper functions - /// - /// Returns the object that generates events to be monitored + /// Returns the object that generates events to be monitored. /// - protected override Object GetSourceObject() + protected override object GetSourceObject() { string wmiQuery = this.Query; if (this.Class != null) { - //Validate class format + // Validate class format for (int i = 0; i < this.Class.Length; i++) { - if (Char.IsLetterOrDigit(this.Class[i]) || this.Class[i].Equals('_')) + if (char.IsLetterOrDigit(this.Class[i]) || this.Class[i].Equals('_')) { continue; } ErrorRecord errorRecord = new ErrorRecord( new ArgumentException( - String.Format( + string.Format( Thread.CurrentThread.CurrentCulture, "Class", this.Class)), "INVALID_QUERY_IDENTIFIER", @@ -135,7 +134,7 @@ protected override Object GetSourceObject() if (this.Credential != null) { System.Net.NetworkCredential cred = this.Credential.GetNetworkCredential(); - if (String.IsNullOrEmpty(cred.Domain)) + if (string.IsNullOrEmpty(cred.Domain)) { conOptions.Username = cred.UserName; } @@ -143,6 +142,7 @@ protected override Object GetSourceObject() { conOptions.Username = cred.Domain + "\\" + cred.UserName; } + conOptions.Password = cred.Password; } @@ -159,9 +159,9 @@ protected override Object GetSourceObject() } /// - /// Returns the event name to be monitored on the input object + /// Returns the event name to be monitored on the input object. /// - protected override String GetSourceObjectEventName() + protected override string GetSourceObjectEventName() { return "EventArrived"; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs index b6c1b4b3e7d..cc60d91adbe 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs @@ -1,9 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -11,13 +10,13 @@ namespace Microsoft.PowerShell.Commands /// A command to remove a property from an item. /// [Cmdlet(VerbsCommon.Remove, "ItemProperty", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113374")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097013")] public class RemoveItemPropertyCommand : ItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -26,50 +25,49 @@ public string[] Path get { return paths; - } // get + } set { paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return paths; - } // get + } set { base.SuppressWildcardExpansion = true; paths = value; - } // set - } // LiteralPath + } + } /// - /// The name of the property to create on the item + /// The name of the property to create on the item. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] public string[] Name { get { return _property; } - set { _property = value ?? Utils.EmptyArray(); } + + set { _property = value ?? Array.Empty(); } } /// - /// Gets or sets the force property + /// Gets or sets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -79,11 +77,11 @@ public string[] Name /// the destination is read-only, if force is true, the provider should copy over /// the existing read-only file. If force is false, the provider should write an error. /// - /// [Parameter] public override SwitchParameter Force { get { return base.Force; } + set { base.Force = value; } } @@ -92,16 +90,13 @@ public override SwitchParameter Force /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { string propertyName = null; @@ -114,8 +109,9 @@ internal override object GetDynamicParameters(CmdletProviderContext context) { return InvokeProvider.Property.RemovePropertyDynamicParameters(Path[0], propertyName, context); } + return InvokeProvider.Property.RemovePropertyDynamicParameters(".", propertyName, context); - } // GetDynamicParameters + } #endregion Parameters @@ -124,14 +120,14 @@ internal override object GetDynamicParameters(CmdletProviderContext context) /// /// The property to be created. /// - private string[] _property = new string[0]; + private string[] _property = Array.Empty(); #endregion parameter data #region Command code /// - /// Removes the property from the item + /// Removes the property from the item. /// protected override void ProcessRecord() { @@ -177,9 +173,8 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } #endregion Command code - - } // RemoveItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RemoveWMIObjectCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemoveWMIObjectCommand.cs index 711cd7ea074..cce50ea1563 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RemoveWMIObjectCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemoveWMIObjectCommand.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management; @@ -9,7 +8,7 @@ namespace Microsoft.PowerShell.Commands { /// - /// A command to Remove WMI Object + /// A command to Remove WMI Object. /// [Cmdlet(VerbsCommon.Remove, "WmiObject", DefaultParameterSetName = "class", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113381", RemotingCapability = RemotingCapability.OwnedByCommand)] @@ -17,31 +16,33 @@ public class RemoveWmiObject : WmiBaseCmdlet { #region Parameters /// - /// The WMI Object to use + /// The WMI Object to use. /// - /// [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = "object")] public ManagementObject InputObject { get { return _inputObject; } + set { _inputObject = value; } } /// - /// The WMI Path to use + /// The WMI Path to use. /// [Parameter(Mandatory = true, ParameterSetName = "path")] public string Path { get { return _path; } + set { _path = value; } } /// - /// The WMI class to use + /// The WMI class to use. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "class")] public string Class { get { return _className; } + set { _className = value; } } @@ -64,6 +65,7 @@ protected override void ProcessRecord() RunAsJob("Remove-WMIObject"); return; } + if (_inputObject != null) { try @@ -72,6 +74,7 @@ protected override void ProcessRecord() { return; } + _inputObject.Delete(); } catch (ManagementException e) @@ -84,6 +87,7 @@ protected override void ProcessRecord() ErrorRecord errorRecord = new ErrorRecord(e, "RemoveWMICOMException", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } + return; } else @@ -94,13 +98,13 @@ protected override void ProcessRecord() if (_path != null) { mPath = new ManagementPath(_path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = this.Namespace; } else if (namespaceSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "NamespaceSpecifiedWithPath", @@ -110,19 +114,21 @@ protected override void ProcessRecord() if (mPath.Server != "." && serverNameSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "ComputerNameSpecifiedWithPath", ErrorCategory.InvalidOperation, this.ComputerName)); } + if (!(mPath.Server == "." && serverNameSpecified)) { string[] serverName = new string[] { mPath.Server }; ComputerName = serverName; } } + foreach (string name in ComputerName) { try @@ -140,6 +146,7 @@ protected override void ProcessRecord() ManagementObject mInstance = new ManagementObject(mPath); mObject = mInstance; } + ManagementScope mScope = new ManagementScope(mPath, options); mObject.Scope = mScope; } @@ -150,10 +157,12 @@ protected override void ProcessRecord() mObject = mClass; mObject.Scope = scope; } + if (!ShouldProcess(mObject["__PATH"].ToString())) { continue; } + mObject.Delete(); } catch (ManagementException e) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs index 5e3000ceb57..bdefc6b2c64 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs @@ -1,23 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to rename a property of an item at a specified path + /// A command to rename a property of an item at a specified path. /// [Cmdlet(VerbsCommon.Rename, "ItemProperty", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113383")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097152")] public class RenameItemPropertyCommand : PassThroughItemPropertyCommandBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -26,46 +24,44 @@ public string Path get { return _path; - } // get + } set { _path = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string LiteralPath { get { return _path; - } // get + } set { base.SuppressWildcardExpansion = true; _path = value; - } // set - } // LiteralPath + } + } /// - /// The properties to be renamed on the item + /// The properties to be renamed on the item. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] public string Name { get; set; } /// - /// The new name of the property on the item + /// The new name of the property on the item. /// - /// [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)] public string NewName { get; set; } @@ -74,24 +70,22 @@ public string LiteralPath /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null) { return InvokeProvider.Property.RenamePropertyDynamicParameters(Path, Name, NewName, context); } + return InvokeProvider.Property.RenamePropertyDynamicParameters(".", Name, NewName, context); - } // GetDynamicParameters + } #endregion Parameters @@ -146,9 +140,8 @@ protected override void ProcessRecord() pathNotFound.ErrorRecord, pathNotFound)); } - } // ProcessRecord + } #endregion Command code - - } // RenameItemPropertyCommand -} // 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 d1222aa6596..d0f5fecf495 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -1,26 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using Dbg = System.Management.Automation; using System.Collections.ObjectModel; +using 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=113384")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097143")] public class ResolvePathCommand : CoreCommandWithCredentialsBase { #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = "Path", Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -29,69 +27,153 @@ public string[] Path get { return _paths; - } // get + } set { _paths = value; - } // set - } // Path + } + } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = "LiteralPath", Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { get { return _paths; - } // get + } set { base.SuppressWildcardExpansion = true; _paths = value; - } // set - } // 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 { return _relative; - } // get + } set { _relative = value; - } // set - } // 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 /// - /// The path to resolve + /// The path to resolve. /// 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,19 +183,65 @@ 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) { - string adjustedPath = SessionState.Path.NormalizeRelativePath(currentPath.Path, - SessionState.Path.CurrentLocation.ProviderPath); - if (!adjustedPath.StartsWith(".", StringComparison.OrdinalIgnoreCase)) + // When result path and base path is on different PSDrive + // (../)*path should not go beyond the root of base path + if (currentPath.Drive != _relativeDrive && + _relativeDrive != null && + !currentPath.ProviderPath.StartsWith(_relativeDrive.Root, StringComparison.OrdinalIgnoreCase)) + { + WriteObject(currentPath.Path, enumerateCollection: false); + continue; + } + + 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) && + !adjustedPath.StartsWith('.')) { adjustedPath = SessionState.Path.Combine(".", adjustedPath); } - WriteObject(adjustedPath, false); + + leafIndex = adjustedPath.LastIndexOf(currentPath.Provider.ItemSeparator); + adjustedBaseCache = adjustedPath.AsSpan(0, leafIndex + 1); + + WriteObject(adjustedPath, enumerateCollection: false); } } } @@ -152,13 +280,11 @@ protected override void ProcessRecord() if (!_relative) { - WriteObject(result, true); + WriteObject(result, enumerateCollection: true); } } - } // ProcessRecord + } #endregion Command code - - } // ResolvePathCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs index 4f48e18b8e0..3d6fc27cc3c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -26,6 +26,5 @@ protected override void EndProcessing() this.Context.TransactionManager.Rollback(); } } - } // RollbackTransactionCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 0e4229762a7..a00f9583d6a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1,38 +1,38 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #if !UNIX // Not built on Unix 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 NakedWin32Handle = System.IntPtr; +using System.Runtime.Serialization; +using System.Security.AccessControl; +using System.ServiceProcess; +using Dbg = System.Management.Automation.Diagnostics; using DWORD = System.UInt32; +using NakedWin32Handle = System.IntPtr; namespace Microsoft.PowerShell.Commands { #region ServiceBaseCommand /// - /// This class implements the base for service commands + /// This class implements the base for service commands. /// public abstract class ServiceBaseCommand : Cmdlet { #region Internal /// - /// Confirm that the operation should proceed + /// Confirm that the operation should proceed. /// - /// service object to be acted on - /// true if operation should continue, false otherwise + /// Service object to be acted on. + /// True if operation should continue, false otherwise. protected bool ShouldProcessServiceOperation(ServiceController service) { return ShouldProcessServiceOperation( @@ -41,11 +41,11 @@ protected bool ShouldProcessServiceOperation(ServiceController service) } /// - /// Confirm that the operation should proceed + /// Confirm that the operation should proceed. /// - /// display name of service to be acted on - /// service name of service to be acted on - /// true if operation should continue, false otherwise + /// Display name of service to be acted on. + /// Service name of service to be acted on. + /// True if operation should continue, false otherwise. protected bool ShouldProcessServiceOperation( string displayName, string serviceName) { @@ -103,7 +103,7 @@ internal void WriteNonTerminatingError( string message = StringUtil.Format(errorMessage, serviceName, displayName, - (null == innerException) ? String.Empty : innerException.Message); + (innerException == null) ? category.ToString() : innerException.Message); var exception = new ServiceCommandException(message, innerException); exception.ServiceName = serviceName; @@ -111,42 +111,75 @@ internal void WriteNonTerminatingError( WriteError(new ErrorRecord(exception, errorId, category, targetObject)); } + internal void SetServiceSecurityDescriptor( + ServiceController service, + string securityDescriptorSddl, + NakedWin32Handle hService) + { + var rawSecurityDescriptor = new RawSecurityDescriptor(securityDescriptorSddl); + RawAcl rawDiscretionaryAcl = rawSecurityDescriptor.DiscretionaryAcl; + var discretionaryAcl = new DiscretionaryAcl(false, false, rawDiscretionaryAcl); + + byte[] rawDacl = new byte[discretionaryAcl.BinaryLength]; + discretionaryAcl.GetBinaryForm(rawDacl, 0); + rawSecurityDescriptor.DiscretionaryAcl = new RawAcl(rawDacl, 0); + byte[] securityDescriptorByte = new byte[rawSecurityDescriptor.BinaryLength]; + rawSecurityDescriptor.GetBinaryForm(securityDescriptorByte, 0); + + bool status = NativeMethods.SetServiceObjectSecurity( + hService, + SecurityInfos.DiscretionaryAcl, + securityDescriptorByte); + + if (!status) + { + int lastError = Marshal.GetLastWin32Error(); + Win32Exception exception = new(lastError); + bool accessDenied = exception.NativeErrorCode == NativeMethods.ERROR_ACCESS_DENIED; + WriteNonTerminatingError( + service, + exception, + nameof(ServiceResources.CouldNotSetServiceSecurityDescriptorSddl), + StringUtil.Format(ServiceResources.CouldNotSetServiceSecurityDescriptorSddl, service.ServiceName, exception.Message), + accessDenied ? ErrorCategory.PermissionDenied : ErrorCategory.InvalidOperation); + } + } #endregion Internal - } // class ServiceBaseCommand + } #endregion ServiceBaseCommand #region MultipleServiceCommandBase /// /// This class implements the base for service commands which can - /// operate on multiple services + /// operate on multiple services. /// public abstract class MultipleServiceCommandBase : ServiceBaseCommand { #region Parameters /// - /// The various process selection modes + /// The various process selection modes. /// internal enum SelectionMode { /// - /// Select all services + /// Select all services. /// Default = 0, /// - /// Select services matching the supplied names + /// Select services matching the supplied names. /// DisplayName = 1, /// - /// Select services based on pipeline input + /// Select services based on pipeline input. /// InputObject = 2, /// - /// Select services by Service name + /// Select services by Service name. /// ServiceName = 3 - }; + } /// /// Holds the selection mode setting. /// @@ -159,7 +192,7 @@ internal enum SelectionMode internal string[] serviceNames = null; /// - /// gets/sets an array of display names for services + /// Gets/sets an array of display names for services. /// [Parameter(ParameterSetName = "DisplayName", Mandatory = true)] public string[] DisplayName @@ -168,12 +201,14 @@ public string[] DisplayName { return displayNames; } + set { displayNames = value; selectionMode = SelectionMode.DisplayName; } } + internal string[] displayNames = null; /// @@ -190,11 +225,13 @@ public string[] Include { return include; } + set { include = value; } } + internal string[] include = null; /// @@ -211,11 +248,13 @@ public string[] Exclude { return exclude; } + set { exclude = value; } } + internal string[] exclude = null; // 1054295-2004/12/01-JonN This also works around 1054295. @@ -237,38 +276,33 @@ public ServiceController[] InputObject { return _inputObject; } + set { _inputObject = value; selectionMode = SelectionMode.InputObject; } } + private ServiceController[] _inputObject = null; #endregion Parameters #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 (null == _allServices) - { - _allServices = ServiceController.GetServices(); - } - return _allServices; - } - } - private ServiceController[] _allServices = null; + internal ServiceController[] AllServices => _allServices ??= ServiceController.GetServices(); + + private ServiceController[] _allServices; internal ServiceController GetOneService(string nameOfService) { @@ -313,12 +347,12 @@ internal List MatchingServices() // before being stopped. JimTru confirms that this is OK. matchingServices.Sort(ServiceComparison); return matchingServices; - } // MatchingServices + } // sort by servicename private static int ServiceComparison(ServiceController x, ServiceController y) { - return String.Compare(x.ServiceName, y.ServiceName, StringComparison.CurrentCultureIgnoreCase); + return string.Compare(x.ServiceName, y.ServiceName, StringComparison.OrdinalIgnoreCase); } /// @@ -337,14 +371,15 @@ private static int ServiceComparison(ServiceController x, ServiceController y) /// private List MatchingServicesByServiceName() { - List matchingServices = new List(); + List matchingServices = new(); - if (null == serviceNames) + if (serviceNames == null) { foreach (ServiceController service in AllServices) { IncludeExcludeAdd(matchingServices, service, false); } + return matchingServices; } @@ -377,7 +412,7 @@ private List MatchingServicesByServiceName() { WriteNonTerminatingError( pattern, - String.Empty, + string.Empty, pattern, null, "NoServiceFoundForGivenName", @@ -385,8 +420,9 @@ private List MatchingServicesByServiceName() ErrorCategory.ObjectNotFound); } } + return matchingServices; - } // MatchingServicesByServiceName + } /// /// Retrieves the list of all services matching the DisplayName, @@ -398,12 +434,13 @@ private List MatchingServicesByServiceName() /// private List MatchingServicesByDisplayName() { - List matchingServices = new List(); - if (null == DisplayName) + List matchingServices = new(); + if (DisplayName == null) { Diagnostics.Assert(false, "null DisplayName"); throw PSTraceSource.NewInvalidOperationException(); } + foreach (string pattern in DisplayName) { WildcardPattern wildcard = @@ -416,10 +453,11 @@ private List MatchingServicesByDisplayName() found = true; IncludeExcludeAdd(matchingServices, service, true); } + if (!found && !WildcardPattern.ContainsWildcardCharacters(pattern)) { WriteNonTerminatingError( - String.Empty, + string.Empty, pattern, pattern, null, @@ -428,8 +466,9 @@ private List MatchingServicesByDisplayName() ErrorCategory.ObjectNotFound); } } + return matchingServices; - } // MatchingServicesByDisplayName + } /// /// Retrieves the list of all services matching the InputObject, @@ -438,19 +477,21 @@ private List MatchingServicesByDisplayName() /// private List MatchingServicesByInput() { - List matchingServices = new List(); - if (null == InputObject) + List matchingServices = new(); + if (InputObject == null) { Diagnostics.Assert(false, "null InputObject"); throw PSTraceSource.NewInvalidOperationException(); } + foreach (ServiceController service in InputObject) { service.Refresh(); IncludeExcludeAdd(matchingServices, service, false); } + return matchingServices; - } // MatchingServicesByInput + } /// /// Add to , @@ -458,17 +499,17 @@ private List MatchingServicesByInput() /// and (if ) if it is not /// already on . /// - /// list of services - /// service to add to list - /// check list for duplicates + /// List of services. + /// Service to add to list. + /// Check list for duplicates. private void IncludeExcludeAdd( List list, ServiceController service, bool checkDuplicates) { - if (null != include && !Matches(service, include)) + if (include != null && !Matches(service, include)) return; - if (null != exclude && Matches(service, exclude)) + if (exclude != null && Matches(service, exclude)) return; if (checkDuplicates) { @@ -481,6 +522,7 @@ private void IncludeExcludeAdd( } } } + list.Add(service); } @@ -493,8 +535,8 @@ private void IncludeExcludeAdd( /// private bool Matches(ServiceController service, string[] matchList) { - if (null == matchList) - throw PSTraceSource.NewArgumentNullException("matchList"); + if (matchList == null) + throw PSTraceSource.NewArgumentNullException(nameof(matchList)); string serviceID = (selectionMode == SelectionMode.DisplayName) ? service.DisplayName : service.ServiceName; @@ -504,25 +546,26 @@ private bool Matches(ServiceController service, string[] matchList) if (wildcard.IsMatch(serviceID)) return true; } + return false; } #endregion Internal - } // class MultipleServiceCommandBase + } #endregion MultipleServiceCommandBase #region GetServiceCommand /// - /// This class implements the get-service command + /// This class implements the get-service command. /// [Cmdlet(VerbsCommon.Get, "Service", DefaultParameterSetName = "Default", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113332", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096496", RemotingCapability = RemotingCapability.SupportedByCommand)] [OutputType(typeof(ServiceController))] public sealed class GetServiceCommand : MultipleServiceCommandBase { #region Parameters /// - /// gets/sets an array of service names + /// Gets/sets an array of service names. /// /// /// The ServiceName parameter is declared in subclasses, @@ -537,6 +580,7 @@ public string[] Name { return serviceNames; } + set { serviceNames = value; @@ -551,7 +595,6 @@ public string[] Name [Alias("DS")] public SwitchParameter DependentServices { get; set; } - /// /// This returns the ServicesDependedOn of the specified service. /// @@ -563,139 +606,201 @@ public string[] Name #region Overrides /// - /// Write the service objects + /// Write the service objects. /// protected override void ProcessRecord() { - foreach (ServiceController service in MatchingServices()) + nint scManagerHandle = nint.Zero; + if (!DependentServices && !RequiredServices) { - if (!DependentServices.IsPresent && !RequiredServices.IsPresent) + // 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) { - WriteObject(AddProperties(service)); + 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(dependantserv); - } + WriteObject(AddProperties(scManagerHandle, service)); } - if (RequiredServices.IsPresent) + else { - foreach (ServiceController servicedependedon in service.ServicesDependedOn) + if (DependentServices.IsPresent) + { + foreach (ServiceController dependantserv in service.DependentServices) + { + WriteObject(dependantserv); + } + } + + 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) { - NakedWin32Handle hScManager = IntPtr.Zero; - NakedWin32Handle hService = IntPtr.Zero; - int lastError = 0; - PSObject serviceAsPSObj = PSObject.AsPSObject(service); + /// ServiceController as PSObject with UserName, Description and StartupType added. + private PSObject AddProperties(nint scManagerHandle, ServiceController 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) { - 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 (hService != nint.Zero) + { + 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) { - WriteNonTerminatingError( - service: service, - innerException: null, - errorId: "CouldNotGetServiceInfo", - errorMessage: ServiceResources.CouldNotGetServiceInfo, - category: ErrorCategory.PermissionDenied - ); + if (NativeMethods.QueryServiceConfig( + hService, + out NativeMethods.QUERY_SERVICE_CONFIG serviceInfo)) + { + binPath = serviceInfo.lpBinaryPathName; + startName = serviceInfo.lpServiceStartName; + if (isDelayedAutoStart.HasValue) + { + startupType = NativeMethods.GetServiceStartupType( + (ServiceStartMode)serviceInfo.dwStartType, + isDelayedAutoStart.Value); + } + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + } + else + { + // 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"); - } + 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"); - if (IntPtr.Zero != hScManager) { - bool succeeded = NativeMethods.CloseServiceHandle(hScManager); - if (!succeeded) { - Diagnostics.Assert(lastError != 0, "ErrorCode not success"); - } - } - } // finally return serviceAsPSObj; } } +#nullable disable #endregion GetServiceCommand #region ServiceOperationBaseCommand @@ -707,7 +812,7 @@ public abstract class ServiceOperationBaseCommand : MultipleServiceCommandBase { #region Parameters /// - /// gets/sets an array of service names + /// Gets/sets an array of service names. /// /// /// The ServiceName parameter is declared in subclasses, @@ -721,6 +826,7 @@ public string[] Name { return serviceNames; } + set { serviceNames = value; @@ -729,7 +835,7 @@ public string[] Name } /// - /// Service controller objects + /// Service controller objects. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "InputObject", ValueFromPipeline = true)] [ValidateNotNullOrEmpty] @@ -740,6 +846,7 @@ public string[] Name { return base.InputObject; } + set { base.InputObject = value; @@ -760,8 +867,8 @@ public string[] Name /// Waits forever for the service to reach the desired status, but /// writes a string to WriteWarning every 2 seconds. /// - /// service on which to operate - /// desired status + /// Service on which to operate. + /// Desired status. /// /// This is the expected status while the operation is incomplete. /// If the service is in some other state, this means that the @@ -777,7 +884,7 @@ public string[] Name /// /// errorMessage for a nonterminating error if operation fails /// - /// true if action succeeded + /// True if action succeeded. /// /// WriteWarning will throw this if the pipeline has been stopped. /// This means that the delay between hitting CTRL-C and stopping @@ -791,7 +898,7 @@ internal bool DoWaitForStatus( string errorId, string errorMessage) { - do + while (true) { try { @@ -817,6 +924,7 @@ internal bool DoWaitForStatus( ErrorCategory.OpenError); return false; } + string message = StringUtil.Format(resourceIdPending, serviceController.ServiceName, serviceController.DisplayName @@ -824,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 + /// Service to start. + /// True if-and-only-if the service was started. internal bool DoStartService(ServiceController serviceController) { Exception exception = null; @@ -841,19 +949,19 @@ 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 (null == eInner - || NativeMethods.ERROR_SERVICE_ALREADY_RUNNING != eInner.NativeErrorCode) + if (e.InnerException is not Win32Exception eInner + || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_ALREADY_RUNNING) { exception = e; } } - if (null != exception) + + if (exception != null) { // This service refused to accept the start command, // so write a non-terminating error. @@ -877,22 +985,23 @@ internal bool DoStartService(ServiceController serviceController) { return false; } + return true; } /// /// This will stop the service. /// - /// service to stop - /// stop dependent services + /// 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 @@ -956,20 +1065,19 @@ 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 (null == eInner - || NativeMethods.ERROR_SERVICE_NOT_ACTIVE != eInner.NativeErrorCode) + if (e.InnerException is not Win32Exception eInner + || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { exception = e; } } - if (null != exception) + + if (exception != null) { // This service refused to accept the stop command, // so write a non-terminating error. @@ -1009,46 +1117,35 @@ internal List DoStopService(ServiceController serviceControll } /// - /// Check if all dependent services are stopped + /// Check if all dependent services are stopped. /// /// /// /// True if all dependent services are stopped /// False if not all dependent services are stopped /// - private bool HaveAllDependentServicesStopped(ICollection dependentServices) + private static bool HaveAllDependentServicesStopped(ServiceController[] dependentServices) { - foreach (ServiceController service in dependentServices) - { - if (service.Status != ServiceControllerStatus.Stopped) - { - return false; - } - } - return true; + return Array.TrueForAll(dependentServices, static service => service.Status == ServiceControllerStatus.Stopped); } /// - /// This removes all services that are not stopped from a list of services + /// This removes all services that are not stopped from a list of services. /// - /// a list of services + /// A list of services. internal void RemoveNotStoppedServices(List services) { - foreach (ServiceController service in services) - { - if (service.Status != ServiceControllerStatus.Stopped && - service.Status != ServiceControllerStatus.StopPending) - { - services.Remove(service); - } - } + // You shall not modify a collection during enumeration. + services.RemoveAll(service => + service.Status != ServiceControllerStatus.Stopped && + service.Status != ServiceControllerStatus.StopPending); } /// /// This will pause the service. /// - /// service to pause - /// true iff the service was paused + /// Service to pause. + /// True if-and-only-if the service was paused. internal bool DoPauseService(ServiceController serviceController) { Exception exception = null; @@ -1059,23 +1156,25 @@ 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; } + exception = e; } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (null != eInner - && NativeMethods.ERROR_SERVICE_NOT_ACTIVE == eInner.NativeErrorCode) + if (e.InnerException is Win32Exception eInner + && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } + exception = e; } - if (null != exception) + + if (exception != null) { // This service refused to accept the pause command, // so write a non-terminating error. @@ -1125,8 +1224,8 @@ internal bool DoPauseService(ServiceController serviceController) /// /// This will resume the service. /// - /// service to resume - /// true iff the service was resumed + /// Service to resume. + /// True if-and-only-if the service was resumed. internal bool DoResumeService(ServiceController serviceController) { Exception exception = null; @@ -1137,23 +1236,25 @@ 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; } + exception = e; } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (null != eInner - && NativeMethods.ERROR_SERVICE_NOT_ACTIVE == eInner.NativeErrorCode) + if (e.InnerException is Win32Exception eInner + && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } + exception = e; } - if (null != exception) + + if (exception != null) { // This service refused to accept the continue command, // so write a non-terminating error. @@ -1205,13 +1306,13 @@ internal bool DoResumeService(ServiceController serviceController) #region StopServiceCommand /// - /// This class implements the stop-service command + /// This class implements the stop-service command. /// /// /// Note that the services will be sorted before being stopped. /// PM confirms that this is OK. /// - [Cmdlet(VerbsLifecycle.Stop, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113414")] + [Cmdlet(VerbsLifecycle.Stop, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097052")] [OutputType(typeof(ServiceController))] public sealed class StopServiceCommand : ServiceOperationBaseCommand { @@ -1223,7 +1324,7 @@ public sealed class StopServiceCommand : ServiceOperationBaseCommand public SwitchParameter Force { get; set; } /// - /// Specifies whether to wait for a service to reach the stopped state before returning + /// Specifies whether to wait for a service to reach the stopped state before returning. /// [Parameter] public SwitchParameter NoWait { get; set; } @@ -1256,20 +1357,20 @@ protected override void ProcessRecord() } } } - } // ProcessRecord - } // StopServiceCommand + } + } #endregion StopServiceCommand #region StartServiceCommand /// - /// This class implements the start-service command + /// This class implements the start-service command. /// - [Cmdlet(VerbsLifecycle.Start, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113406")] + [Cmdlet(VerbsLifecycle.Start, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097051")] [OutputType(typeof(ServiceController))] public sealed class StartServiceCommand : ServiceOperationBaseCommand { /// - /// Start the services + /// Start the services. /// protected override void ProcessRecord() { @@ -1288,20 +1389,20 @@ protected override void ProcessRecord() WriteObject(serviceController); } } - } // ProcessRecord - } // class StartServiceCommand + } + } #endregion StartServiceCommand #region SuspendServiceCommand /// - /// This class implements the suspend-service command + /// This class implements the suspend-service command. /// - [Cmdlet(VerbsLifecycle.Suspend, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113416")] + [Cmdlet(VerbsLifecycle.Suspend, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097053")] [OutputType(typeof(ServiceController))] public sealed class SuspendServiceCommand : ServiceOperationBaseCommand { /// - /// Start the services + /// Start the services. /// protected override void ProcessRecord() { @@ -1320,21 +1421,21 @@ protected override void ProcessRecord() WriteObject(serviceController); } } - } // ProcessRecord - } // class SuspendServiceCommand + } + } #endregion SuspendServiceCommand #region ResumeServiceCommand /// - /// This class implements the resume-service command + /// This class implements the resume-service command. /// [Cmdlet(VerbsLifecycle.Resume, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113386")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097150")] [OutputType(typeof(ServiceController))] public sealed class ResumeServiceCommand : ServiceOperationBaseCommand { /// - /// Start the services + /// Start the services. /// protected override void ProcessRecord() { @@ -1346,23 +1447,24 @@ protected override void ProcessRecord() { continue; } + if (DoResumeService(serviceController)) { if (PassThru) WriteObject(serviceController); } } - } // ProcessRecord - } // class ResumeServiceCommand + } + } #endregion ResumeServiceCommand #region RestartServiceCommand /// - /// This class implements the restart-service command + /// This class implements the restart-service command. /// [Cmdlet(VerbsLifecycle.Restart, "Service", DefaultParameterSetName = "InputObject", SupportsShouldProcess = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113385")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097059")] [OutputType(typeof(ServiceController))] public sealed class RestartServiceCommand : ServiceOperationBaseCommand { @@ -1391,7 +1493,7 @@ protected override void ProcessRecord() continue; } - //Set the NoWait parameter to false since we are not adding this switch to this cmdlet. + // Set the NoWait parameter to false since we are not adding this switch to this cmdlet. List stoppedServices = DoStopService(serviceController, Force, true); if (stoppedServices.Count > 0) @@ -1406,37 +1508,42 @@ protected override void ProcessRecord() } } } - } // ProcessRecord - } // RestartServiceCommand + } + } #endregion RestartServiceCommand #region SetServiceCommand /// - /// This class implements the set-service command + /// This class implements the set-service command. /// [Cmdlet(VerbsCommon.Set, "Service", SupportsShouldProcess = true, DefaultParameterSetName = "Name", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113399", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097148", RemotingCapability = RemotingCapability.SupportedByCommand)] [OutputType(typeof(ServiceController))] public class SetServiceCommand : ServiceOperationBaseCommand { #region Parameters /// - /// service name + /// Service name. /// /// [Parameter(Mandatory = true, ParameterSetName = "Name", Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [Alias("ServiceName", "SN")] - public new String Name + public new string Name { - get { return serviceName; } + get + { + return serviceName; + } + set { serviceName = value; } } - internal String serviceName = null; + + internal string serviceName = null; /// /// The following is the definition of the input parameter "InputObject". @@ -1456,66 +1563,90 @@ public class SetServiceCommand : ServiceOperationBaseCommand [Alias("DN")] public new string DisplayName { - get { return displayName; } + get + { + return displayName; + } + set { displayName = value; } } + internal string displayName = null; /// - /// Account under which the service should run + /// Account under which the service should run. /// /// [Parameter] [Credential()] public PSCredential Credential { get; set; } - - /// /// The following is the definition of the input parameter "Description". /// Specifies a new description for the service. /// The service description appears in Services in Computer Management. /// Description is not a property of the ServiceController object that - /// Get-Service retrieve + /// Get-Service retrieve. /// [Parameter] [ValidateNotNullOrEmpty] public string Description { - get { return description; } + get + { + return description; + } + set { description = value; } } - internal string description = null; + internal string description = null; /// /// The following is the definition of the input parameter "StartupType". + /// "Set-Service -StartType" sets ServiceController.InputObject.StartType. /// Changes the starting mode of the service. Valid values for StartupType are: /// -- Automatic: Start when the system starts. /// -- Manual : Starts only when started by a user or program. - /// -- Disabled : Can + /// -- Disabled : Can. /// [Parameter] - [Alias("StartMode", "SM", "ST")] + [Alias("StartMode", "SM", "ST", "StartType")] [ValidateNotNullOrEmpty] public ServiceStartupType StartupType { - get { return startupType; } + get + { + return startupType; + } + set { startupType = value; } } + // We set the initial value to an invalid value so that we can // distinguish when this is and is not set. internal ServiceStartupType startupType = ServiceStartupType.InvalidValue; + /// + /// Sets the SecurityDescriptorSddl of the service using a SDDL string. + /// + [Parameter] + [Alias("sd")] + [ValidateNotNullOrEmpty] + public string SecurityDescriptorSddl + { + get; + set; + } /// /// The following is the definition of the input parameter "Status". @@ -1523,24 +1654,38 @@ public ServiceStartupType StartupType /// Paused). If it is already in that state, do nothing. If it is not, do the /// appropriate action to bring about the desired result (start/stop/suspend the /// service) and issue an error if this cannot be achieved. - /// Status can be Paused , Running and Stopped + /// Status can be Paused , Running and Stopped. /// [Parameter] [ValidateSetAttribute(new string[] { "Running", "Stopped", "Paused" })] public string Status { - get { return serviceStatus; } + get + { + return serviceStatus; + } + set { serviceStatus = value; } } + internal string serviceStatus = null; + /// + /// The following is the definition of the input parameter "Force". + /// This parameter is useful only when parameter "Stop" is enabled. + /// If "Force" is enabled, it will also stop the dependent services. + /// If not, it will send an error when this service has dependent ones. + /// + [Parameter] + public SwitchParameter Force { get; set; } + /// /// This is not a parameter for this cmdlet. /// - //This has been shadowed from base class and removed parameter tag to fix gcm "Set-Service" -syntax + // This has been shadowed from base class and removed parameter tag to fix gcm "Set-Service" -syntax [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public new string[] Include { @@ -1548,17 +1693,19 @@ public string Status { return include; } + set { include = null; } } + internal new string[] include = null; /// /// This is not a parameter for this cmdlet. /// - //This has been shadowed from base class and removed parameter tag to fix gcm "Set-Service" -syntax + // This has been shadowed from base class and removed parameter tag to fix gcm "Set-Service" -syntax [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public new string[] Exclude { @@ -1566,19 +1713,19 @@ public string Status { return exclude; } + set { exclude = null; } } + internal new string[] exclude = null; #endregion Parameters #region Overrides /// - /// /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -1598,7 +1745,8 @@ protected override void ProcessRecord() service = new ServiceController(serviceName); objServiceShouldBeDisposed = true; } - Diagnostics.Assert(!String.IsNullOrEmpty(Name), "null ServiceName"); + + Diagnostics.Assert(!string.IsNullOrEmpty(Name), "null ServiceName"); // "new ServiceController" will succeed even if // there is no such service. This checks whether @@ -1607,15 +1755,15 @@ protected override void ProcessRecord() } catch (ArgumentException ex) { - //cannot use WriteNonterminatingError as service is null - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.ObjectNotFound, Name); + // cannot use WriteNonterminatingError as service is null + 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); + // cannot use WriteNonterminatingError as service is null + ErrorRecord er = new(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, Name); WriteError(er); return; } @@ -1635,15 +1783,15 @@ protected override void ProcessRecord() try { hScManager = NativeMethods.OpenSCManagerW( - String.Empty, + string.Empty, null, 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, @@ -1652,15 +1800,21 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied); 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 + 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, @@ -1670,8 +1824,8 @@ protected override void ProcessRecord() return; } // Modify startup type or display name or credential - if (!String.IsNullOrEmpty(DisplayName) - || ServiceStartupType.InvalidValue != StartupType || null != Credential) + if (!string.IsNullOrEmpty(DisplayName) + || StartupType != ServiceStartupType.InvalidValue || Credential != null) { DWORD dwStartType = NativeMethods.SERVICE_NO_CHANGE; if (!NativeMethods.TryGetNativeStartupType(StartupType, out dwStartType)) @@ -1684,11 +1838,12 @@ protected override void ProcessRecord() } string username = null; - if (null != Credential) + if (Credential != null) { username = Credential.UserName; password = Marshal.SecureStringToCoTaskMemUnicode(Credential.Password); } + bool succeeded = NativeMethods.ChangeServiceConfigW( hService, NativeMethods.SERVICE_NO_CHANGE, @@ -1705,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, @@ -1714,9 +1869,9 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied); return; } - } // modify startup type or display name + } - 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); @@ -1730,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, @@ -1740,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); @@ -1754,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, @@ -1773,38 +1928,31 @@ protected override void ProcessRecord() if (!service.Status.Equals(ServiceControllerStatus.Running)) { if (service.Status.Equals(ServiceControllerStatus.Paused)) - //resume service + // resume service DoResumeService(service); else - //start service + // start service DoStartService(service); } } - else if (Status.Equals("Stopped", StringComparison.CurrentCultureIgnoreCase)) + else if (Status.Equals("Stopped", StringComparison.OrdinalIgnoreCase)) { if (!service.Status.Equals(ServiceControllerStatus.Stopped)) { - //check for the dependent services as set-service dont have force parameter + // Check for the dependent services as set-service dont have force parameter ServiceController[] dependentServices = service.DependentServices; - if ((dependentServices != null) && (dependentServices.Length > 0)) + if ((!Force) && (dependentServices != null) && (dependentServices.Length > 0)) { WriteNonTerminatingError(service, null, "ServiceHasDependentServicesNoForce", ServiceResources.ServiceHasDependentServicesNoForce, ErrorCategory.InvalidOperation); return; } - ServiceController[] servicedependedon = service.ServicesDependedOn; - - if ((servicedependedon != null) && (servicedependedon.Length > 0)) - { - WriteNonTerminatingError(service, null, "ServiceIsDependentOnNoForce", ServiceResources.ServiceIsDependentOnNoForce, ErrorCategory.InvalidOperation); - return; - } // Stop service, pass 'true' to the force parameter as we have already checked for the dependent services. - DoStopService(service, force: true, waitForServiceToStop: true); + DoStopService(service, Force, waitForServiceToStop: true); } } - else if (Status.Equals("Paused", StringComparison.CurrentCultureIgnoreCase)) + else if (Status.Equals("Paused", StringComparison.OrdinalIgnoreCase)) { if (!service.Status.Equals(ServiceControllerStatus.Paused)) { @@ -1812,26 +1960,33 @@ protected override void ProcessRecord() } } } + + if (!string.IsNullOrEmpty(SecurityDescriptorSddl)) + { + SetServiceSecurityDescriptor(service, SecurityDescriptorSddl, hService); + } + 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, @@ -1841,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, @@ -1856,14 +2011,15 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied); } } - } // finally - } //End try + } + } finally { - if (IntPtr.Zero != password) + if (password != IntPtr.Zero) { Marshal.ZeroFreeCoTaskMemUnicode(password); } + if (objServiceShouldBeDisposed) { service.Dispose(); @@ -1872,20 +2028,20 @@ protected override void ProcessRecord() } #endregion Overrides - } // class SetServiceCommand + } #endregion SetServiceCommand #region NewServiceCommand /// - /// This class implements the set-service command + /// This class implements the New-Service command. /// - [Cmdlet(VerbsCommon.New, "Service", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113359")] + [Cmdlet(VerbsCommon.New, "Service", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096905")] [OutputType(typeof(ServiceController))] public class NewServiceCommand : ServiceBaseCommand { #region Parameters /// - /// Name of the service to create + /// Name of the service to create. /// /// [Parameter(Position = 0, Mandatory = true)] @@ -1893,24 +2049,29 @@ public class NewServiceCommand : ServiceBaseCommand public string Name { get { return serviceName; } + set { serviceName = value; } } + internal string serviceName = null; /// - /// The executable which implements this service + /// The executable which implements this service. /// /// [Parameter(Position = 1, Mandatory = true)] + [Alias("Path")] public string BinaryPathName { get { return binaryPathName; } + set { binaryPathName = value; } } + internal string binaryPathName = null; /// - /// DisplayName of the service to create + /// DisplayName of the service to create. /// /// [Parameter] @@ -1918,12 +2079,14 @@ public string BinaryPathName public string DisplayName { get { return displayName; } + set { displayName = value; } } + internal string displayName = null; /// - /// Description of the service to create + /// Description of the service to create. /// /// [Parameter] @@ -1931,8 +2094,10 @@ public string DisplayName public string Description { get { return description; } + set { description = value; } } + internal string description = null; /// @@ -1943,12 +2108,14 @@ public string Description public ServiceStartupType StartupType { get { return startupType; } + set { startupType = value; } } + internal ServiceStartupType startupType = ServiceStartupType.Automatic; /// - /// Account under which the service should run + /// Account under which the service should run. /// /// [Parameter] @@ -1956,38 +2123,54 @@ public ServiceStartupType StartupType public PSCredential Credential { get { return credential; } + set { credential = value; } } + internal PSCredential credential = null; /// - /// Other services on which the new service depends + /// Sets the SecurityDescriptorSddl of the service using a SDDL string. + /// + [Parameter] + [Alias("sd")] + [ValidateNotNullOrEmpty] + public string SecurityDescriptorSddl + { + get; + set; + } + + /// + /// Other services on which the new service depends. /// /// [Parameter] public string[] DependsOn { get { return dependsOn; } + set { dependsOn = value; } } + internal string[] dependsOn = null; #endregion Parameters #region Overrides /// - /// Create the service + /// Create the service. /// - [ArchitectureSensitive] protected override void BeginProcessing() { - Diagnostics.Assert(!String.IsNullOrEmpty(Name), + ServiceController service = null; + Diagnostics.Assert(!string.IsNullOrEmpty(Name), "null ServiceName"); - Diagnostics.Assert(!String.IsNullOrEmpty(BinaryPathName), + Diagnostics.Assert(!string.IsNullOrEmpty(BinaryPathName), "null BinaryPathName"); // confirm the operation first // this is always false if WhatIf is set - if (!ShouldProcessServiceOperation(DisplayName ?? String.Empty, Name)) + if (!ShouldProcessServiceOperation(DisplayName ?? string.Empty, Name)) { return; } @@ -2004,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, @@ -2018,6 +2201,7 @@ protected override void BeginProcessing() ErrorCategory.PermissionDenied); return; } + if (!NativeMethods.TryGetNativeStartupType(StartupType, out DWORD dwStartType)) { WriteNonTerminatingError(StartupType.ToString(), "New-Service", Name, @@ -2028,13 +2212,14 @@ protected override void BeginProcessing() } // set up the double-null-terminated lpDependencies parameter IntPtr lpDependencies = IntPtr.Zero; - if (null != DependsOn) + if (DependsOn != null) { int numchars = 1; // final null foreach (string dependedOn in DependsOn) { numchars += dependedOn.Length + 1; } + char[] doubleNullArray = new char[numchars]; int pos = 0; foreach (string dependedOn in DependsOn) @@ -2047,6 +2232,7 @@ protected override void BeginProcessing() pos += dependedOn.Length; doubleNullArray[pos++] = (char)0; // null terminator } + doubleNullArray[pos++] = (char)0; // double-null terminator Diagnostics.Assert(pos == numchars, "lpDependencies build error"); lpDependencies = Marshal.AllocHGlobal( @@ -2056,7 +2242,7 @@ protected override void BeginProcessing() // set up the Credential parameter string username = null; - if (null != Credential) + if (Credential != null) { username = Credential.UserName; password = Marshal.SecureStringToCoTaskMemUnicode(Credential.Password); @@ -2067,7 +2253,7 @@ protected override void BeginProcessing() hScManager, Name, DisplayName, - NativeMethods.SERVICE_CHANGE_CONFIG, + NativeMethods.SERVICE_CHANGE_CONFIG | NativeMethods.WRITE_DAC | NativeMethods.WRITE_OWNER, NativeMethods.SERVICE_WIN32_OWN_PROCESS, dwStartType, NativeMethods.SERVICE_ERROR_NORMAL, @@ -2078,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, @@ -2094,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); @@ -2108,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, @@ -2120,8 +2306,9 @@ 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(); + if (StartupType == ServiceStartupType.AutomaticDelayedStart) + { + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new(); ds.fDelayedAutostart = true; size = Marshal.SizeOf(ds); delayedAutoStartInfoBuffer = Marshal.AllocCoTaskMem(size); @@ -2135,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, @@ -2148,31 +2335,34 @@ protected override void BeginProcessing() } // write the ServiceController for the new service - using (ServiceController service = - new ServiceController(Name)) // ensure dispose + service = new ServiceController(Name); + + if (!string.IsNullOrEmpty(SecurityDescriptorSddl)) { - WriteObject(service); + SetServiceSecurityDescriptor(service, SecurityDescriptorSddl, hService); } + + WriteObject(service); } 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, @@ -2184,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, @@ -2204,20 +2394,20 @@ protected override void BeginProcessing() } } #endregion Overrides - } // class NewServiceCommand + } #endregion NewServiceCommand #region RemoveServiceCommand /// - /// This class implements the Remove-Service command + /// 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 /// - /// Name of the service to remove + /// Name of the service to remove. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Name")] [Alias("ServiceName", "SN")] @@ -2236,9 +2426,8 @@ public class RemoveServiceCommand : ServiceBaseCommand #region Overrides /// - /// Remove the service + /// Remove the service. /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -2256,7 +2445,8 @@ protected override void ProcessRecord() service = new ServiceController(Name); objServiceShouldBeDisposed = true; } - Diagnostics.Assert(!String.IsNullOrEmpty(Name), "null ServiceName"); + + Diagnostics.Assert(!string.IsNullOrEmpty(Name), "null ServiceName"); // "new ServiceController" will succeed even if there is no such service. // This checks whether the service actually exists. @@ -2265,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; } @@ -2291,14 +2481,14 @@ protected override void ProcessRecord() try { hScManager = NativeMethods.OpenSCManagerW( - lpMachineName: String.Empty, + lpMachineName: string.Empty, 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, @@ -2308,20 +2498,21 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied); return; } + hService = NativeMethods.OpenServiceW( hScManager, 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, "CouldNotRemoveService", - ServiceResources.CouldNotSetService, + ServiceResources.CouldNotRemoveService, ErrorCategory.PermissionDenied); return; } @@ -2331,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, @@ -2342,7 +2533,7 @@ protected override void ProcessRecord() } finally { - if (IntPtr.Zero != hService) + if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) @@ -2352,7 +2543,7 @@ protected override void ProcessRecord() } } - if (IntPtr.Zero != hScManager) + if (hScManager != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hScManager); if (!succeeded) @@ -2361,8 +2552,8 @@ protected override void ProcessRecord() Diagnostics.Assert(lastError != 0, "ErrorCode not success"); } } - } // Finally - } // End try + } + } finally { if (objServiceShouldBeDisposed) @@ -2372,21 +2563,20 @@ protected override void ProcessRecord() } } #endregion Overrides - } // class RemoveServiceCommand + } #endregion RemoveServiceCommand #region ServiceCommandException /// - /// Non-terminating errors occurring in the service noun commands + /// Non-terminating errors occurring in the service noun commands. /// - [Serializable] public class ServiceCommandException : SystemException { #region ctors /// - /// unimplemented standard constructor + /// Unimplemented standard constructor. /// - /// doesn't return + /// Doesn't return. public ServiceCommandException() : base() { @@ -2394,17 +2584,17 @@ public ServiceCommandException() } /// - /// standard constructor + /// Standard constructor. /// /// - /// constructed object + /// Constructed object. public ServiceCommandException(string message) : base(message) { } /// - /// standard constructor + /// Standard constructor. /// /// /// @@ -2416,52 +2606,34 @@ public ServiceCommandException(string message, Exception innerException) #region Serialization /// - /// serialization constructor + /// Serialization constructor. /// /// /// - /// constructed object + /// 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("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("info"); - } - base.GetObjectData(info, context); - info.AddValue("ServiceName", _serviceName); - } #endregion Serialization #region Properties /// - /// Name of the service which could not be found or operated upon + /// Name of the service which could not be found or operated upon. /// /// public string ServiceName { get { return _serviceName; } + set { _serviceName = value; } } - private string _serviceName = String.Empty; + + private string _serviceName = string.Empty; #endregion Properties - } // class ServiceCommandException + } #endregion ServiceCommandException #region NativeMethods @@ -2471,6 +2643,7 @@ internal static class NativeMethods internal const int ERROR_SERVICE_ALREADY_RUNNING = 1056; internal const int ERROR_SERVICE_NOT_ACTIVE = 1062; internal const int ERROR_INSUFFICIENT_BUFFER = 122; + internal const DWORD ERROR_ACCESS_DENIED = 0x5; internal const DWORD SC_MANAGER_CONNECT = 1; internal const DWORD SC_MANAGER_CREATE_SERVICE = 2; internal const DWORD SC_MANAGER_ALL_ACCESS = 0xf003f; @@ -2484,7 +2657,8 @@ internal static class NativeMethods internal const DWORD SERVICE_CONFIG_DESCRIPTION = 1; internal const DWORD SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3; internal const DWORD SERVICE_CONFIG_SERVICE_SID_INFO = 5; - + internal const DWORD WRITE_DAC = 262144; + internal const DWORD WRITE_OWNER = 524288; internal const DWORD SERVICE_WIN32_OWN_PROCESS = 0x10; internal const DWORD SERVICE_ERROR_NORMAL = 1; @@ -2565,7 +2739,7 @@ internal struct SERVICE_DESCRIPTIONW { [MarshalAs(UnmanagedType.LPWStr)] internal string lpDescription; - }; + } [StructLayout(LayoutKind.Sequential)] internal struct QUERY_SERVICE_CONFIG @@ -2579,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 @@ -2605,68 +2779,14 @@ NakedWin32Handle CreateServiceW( [In] IntPtr lpPassword ); - /// - /// 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(PinvokeDllNames.CreateJobObjectDllName, 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(PinvokeDllNames.AssignProcessToJobObjectDllName, CharSet = CharSet.Unicode)] + [DllImport(PinvokeDllNames.SetServiceObjectSecurityDllName, CharSet = CharSet.Unicode, SetLastError = true)] [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(PinvokeDllNames.QueryInformationJobObjectDllName, 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 extern + bool SetServiceObjectSecurity( + NakedWin32Handle hSCManager, + System.Security.AccessControl.SecurityInfos dwSecurityInformation, + byte[] lpSecurityDescriptor + ); internal static bool QueryServiceConfig(NakedWin32Handle hService, out NativeMethods.QUERY_SERVICE_CONFIG configStructure) { @@ -2679,7 +2799,8 @@ 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; } @@ -2699,6 +2820,7 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out NativeMet { Marshal.FreeCoTaskMem(lpBuffer); } + return status; } @@ -2715,7 +2837,8 @@ 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; } @@ -2736,11 +2859,12 @@ internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD inf { Marshal.FreeCoTaskMem(lpBuffer); } + return status; } /// - /// Get appropriate win32 StartupType + /// Get appropriate win32 StartupType. /// /// /// StartupType provided by the user. @@ -2774,13 +2898,14 @@ internal static bool TryGetNativeStartupType(ServiceStartupType StartupType, out success = false; break; } + return success; } internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startMode, bool delayedAutoStart) { ServiceStartupType result = ServiceStartupType.Disabled; - switch(startMode) + switch (startMode) { case ServiceStartMode.Automatic: result = delayedAutoStart ? ServiceStartupType.AutomaticDelayedStart : ServiceStartupType.Automatic; @@ -2792,25 +2917,27 @@ internal static ServiceStartupType GetServiceStartupType(ServiceStartMode startM result = ServiceStartupType.Disabled; break; } + return result; } } #endregion NativeMethods #region ServiceStartupType - /// - ///Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode - /// - public enum ServiceStartupType { - ///Invalid service + /// + /// Enum for usage with StartupType. Automatic, Manual and Disabled index matched from System.ServiceProcess.ServiceStartMode + /// + public enum ServiceStartupType + { + /// 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 a59d4a69670..49ab5d768f6 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs @@ -1,16 +1,13 @@ -using System; -using System.Management.Automation; +// Copyright (c) Microsoft Corporation. +// 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.Text; -using System.Windows.Forms; -using System.Collections.Specialized; -using System.Linq; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Text.RegularExpressions; +using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands { @@ -18,20 +15,18 @@ namespace Microsoft.PowerShell.Commands /// Defines the implementation of the 'Set-Clipboard' cmdlet. /// This cmdlet gets the content from system clipboard. /// - [Cmdlet(VerbsCommon.Set, "Clipboard", DefaultParameterSetName = "String", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526220")] + [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 const string ValueParameterSet = "Value"; - private const string PathParameterSet = "Path"; - private const string LiteralPathParameterSet = "LiteralPath"; + private readonly List _contentList = new(); /// /// Property that sets clipboard content. /// - [Parameter(ParameterSetName = ValueParameterSet, Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] - [AllowNull] + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [System.Management.Automation.AllowNull] [AllowEmptyCollection] [AllowEmptyString] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] @@ -44,40 +39,20 @@ public class SetClipboardCommand : PSCmdlet public SwitchParameter Append { get; set; } /// - /// Property that sets Path parameter. This will allow to set file formats to Clipboard. - /// - [Parameter(ParameterSetName = PathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Path { get; set; } - - /// - /// Property that sets LiteralPath parameter. This will allow to set file formats to Clipboard. + /// Gets or sets if the values sent down the pipeline. /// - [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] LiteralPath { get; set; } + [Parameter] + public SwitchParameter PassThru { get; set; } /// - /// Property that sets html parameter. This will allow html content rendered as html to clipboard. + /// Gets or sets whether to use OSC52 escape sequence to set the clipboard of host instead of target. /// [Parameter] - public SwitchParameter AsHtml - { - get { return _asHtml; } - set - { - _isHtmlSet = true; - _asHtml = value; - } - } - private bool _asHtml; - private bool _isHtmlSet = false; + [Alias("ToLocalhost")] + public SwitchParameter AsOSC52 { get; set; } /// - /// This method implements the BeginProcessing method for Set-Clipboard command + /// This method implements the BeginProcessing method for Set-Clipboard command. /// protected override void BeginProcessing() { @@ -85,83 +60,53 @@ protected override void BeginProcessing() } /// - /// This method implements the ProcessRecord method for Set-Clipboard command + /// This method implements the ProcessRecord method for Set-Clipboard command. /// protected override void ProcessRecord() { - // Html should only combine with Text content. - if (Value == null && _isHtmlSet) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException( - String.Format(CultureInfo.InvariantCulture, ClipboardResources.InvalidHtmlCombine)), - "FailedToSetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - if (Value != null) { _contentList.AddRange(Value); - } - else if (Path != null) - { - _contentList.AddRange(Path); - } - else if (LiteralPath != null) - { - _contentList.AddRange(LiteralPath); + + if (PassThru) + { + WriteObject(Value); + } } } /// - /// This method implements the EndProcessing method for Set-Clipboard command + /// This method implements the EndProcessing method for Set-Clipboard command. /// protected override void EndProcessing() { - if (LiteralPath != null) - { - CopyFilesToClipboard(_contentList, Append, true); - } - else if (Path != null) - { - CopyFilesToClipboard(_contentList, Append, false); - } - else - { - SetClipboardContent(_contentList, Append, _asHtml); - } + SetClipboardContent(_contentList, Append); } /// /// Set the clipboard content. /// - /// - /// - /// - private void SetClipboardContent(List contentList, bool append, bool asHtml) + /// The content to store into the clipboard. + /// If true, appends to clipboard instead of overwriting. + private void SetClipboardContent(List contentList, bool append) { string setClipboardShouldProcessTarget; if ((contentList == null || contentList.Count == 0) && !append) { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.ClipboardCleared); + setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.ClipboardCleared); if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) { - Clipboard.Clear(); + Clipboard.SetText(string.Empty); } + return; } - StringBuilder content = new StringBuilder(); + StringBuilder content = new(); if (append) { - if (!Clipboard.ContainsText()) - { - WriteVerbose(String.Format(CultureInfo.InvariantCulture, ClipboardResources.NoAppendableClipboardContent)); - append = false; - } - else - { - content.AppendLine(Clipboard.GetText()); - } + content.AppendLine(Clipboard.GetText()); } if (contentList != null) @@ -169,7 +114,6 @@ private void SetClipboardContent(List contentList, bool append, bool asH content.Append(string.Join(Environment.NewLine, contentList.ToArray(), 0, contentList.Count)); } - // Verbose output string verboseString = null; if (contentList != null) { @@ -177,244 +121,43 @@ private void SetClipboardContent(List contentList, bool append, bool asH if (verboseString.Length >= 20) { verboseString = verboseString.Substring(0, 20); - verboseString = verboseString + " ..."; + verboseString += " ..."; } } if (append) { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendClipboardContent, verboseString); + setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendClipboardContent, verboseString); } else { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.SetClipboardContent, verboseString); + setClipboardShouldProcessTarget = string.Format(CultureInfo.InvariantCulture, ClipboardResources.SetClipboardContent, verboseString); } if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) { - // Set the text data - Clipboard.Clear(); - if (asHtml) - Clipboard.SetText(GetHtmlDataString(content.ToString()), TextDataFormat.Html); - else - Clipboard.SetText(content.ToString()); + SetClipboardContent(content.ToString()); } } /// - /// Copy the file format to clipboard. + /// Set the clipboard content. /// - /// - /// - /// - private void CopyFilesToClipboard(List fileList, bool append, bool isLiteralPath) + /// The content to store into the clipboard. + private void SetClipboardContent(string content) { - int clipBoardContentLength = 0; - HashSet dropFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - - // Append the new file list after the file list exists in the clipboard. - if (append) + if (!AsOSC52) { - if (!Clipboard.ContainsFileDropList()) - { - WriteVerbose(String.Format(CultureInfo.InvariantCulture, ClipboardResources.NoAppendableClipboardContent)); - append = false; - } - else - { - StringCollection clipBoardContent = Clipboard.GetFileDropList(); - dropFiles = new HashSet(clipBoardContent.Cast().ToList(), StringComparer.OrdinalIgnoreCase); - - //we need the count of original files so we can get the accurate files number that has been appended. - clipBoardContentLength = clipBoardContent.Count; - } - } - - ProviderInfo provider = null; - for (int i = 0; i < fileList.Count; i++) - { - Collection newPaths = new Collection(); - - try - { - if (isLiteralPath) - { - newPaths.Add(Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(fileList[i])); - } - else - { - newPaths = Context.SessionState.Path.GetResolvedProviderPathFromPSPath(fileList[i], out provider); - } - } - catch (ItemNotFoundException exception) - { - WriteError(new ErrorRecord(exception, "FailedToSetClipboard", ErrorCategory.InvalidOperation, "Clipboard")); - } - - foreach (string fileName in newPaths) - { - // Avoid adding duplicated files. - if (!dropFiles.Contains(fileName)) - { - dropFiles.Add(fileName); - } - } - } - - if (dropFiles.Count == 0) + Clipboard.SetText(content); return; - - // Verbose output - string setClipboardShouldProcessTarget; - if ((dropFiles.Count - clipBoardContentLength) == 1) - { - if (append) - { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendSingleFileToClipboard, dropFiles.ElementAt(dropFiles.Count - 1)); - } - else - { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.SetSingleFileToClipboard, dropFiles.ElementAt(0)); - } - } - else - { - if (append) - { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.AppendMultipleFilesToClipboard, (dropFiles.Count - clipBoardContentLength)); - } - else - { - setClipboardShouldProcessTarget = String.Format(CultureInfo.InvariantCulture, ClipboardResources.SetMultipleFilesToClipboard, dropFiles.Count); - } - } - - if (ShouldProcess(setClipboardShouldProcessTarget, "Set-Clipboard")) - { - // Set file list formats to clipboard. - Clipboard.Clear(); - StringCollection fileDropList = new StringCollection(); - fileDropList.AddRange(dropFiles.ToArray()); - Clipboard.SetFileDropList(fileDropList); - } - } - - /// - /// Generate HTML fragment data string with header that is required for the clipboard. - /// - /// the html to generate for - /// the resulted string - private static string GetHtmlDataString(string html) - { - // The string contains index references to other spots in the string, so we need placeholders so we can compute the offsets. - // The "<<<<<<<<1,<<<<<<<<2, etc" strings are just placeholders. We'll back-patch them actual values within the header location afterwards. - const string Header = @"Version:0.9 -StartHTML:<<<<<<<<1 -EndHTML:<<<<<<<<2 -StartFragment:<<<<<<<<3 -EndFragment:<<<<<<<<4 -StartSelection:<<<<<<<<3 -EndSelection:<<<<<<<<4"; - - const string StartFragment = ""; - const string EndFragment = @""; - - var sb = new StringBuilder(); - sb.AppendLine(Header); - sb.AppendLine(@""); - - // if given html already provided the fragments we won't add them - int fragmentStart, fragmentEnd; - int fragmentStartIdx = html.IndexOf(StartFragment, StringComparison.OrdinalIgnoreCase); - int fragmentEndIdx = html.LastIndexOf(EndFragment, StringComparison.OrdinalIgnoreCase); - - // if html tag is missing add it surrounding the given html - //find the index of " 0 ? html.IndexOf('>', htmlOpenIdx) + 1 : -1; - //find the index of " 0 ? html.IndexOf('>', bodyOpenIdx) + 1 : -1; - - if (htmlOpenEndIdx < 0 && bodyOpenEndIdx < 0) - { - // the given html doesn't contain html or body tags so we need to add them and place start/end fragments around the given html only - sb.Append(""); - sb.Append(StartFragment); - fragmentStart = GetByteCount(sb); - sb.Append(html); - fragmentEnd = GetByteCount(sb); - sb.Append(EndFragment); - sb.Append(""); - } - else - { - // insert start/end fragments in the proper place (related to html/body tags if exists) so the paste will work correctly - //find the index of ""); - else - sb.Append(html, 0, htmlOpenEndIdx); - - if (bodyOpenEndIdx > -1) - sb.Append(html, htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0, bodyOpenEndIdx - (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0)); - - sb.Append(StartFragment); - fragmentStart = GetByteCount(sb); - - var innerHtmlStart = bodyOpenEndIdx > -1 ? bodyOpenEndIdx : (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0); - var innerHtmlEnd = bodyCloseIdx > 0 ? bodyCloseIdx : (htmlCloseIdx > 0 ? htmlCloseIdx : html.Length); - sb.Append(html, innerHtmlStart, innerHtmlEnd - innerHtmlStart); - - fragmentEnd = GetByteCount(sb); - sb.Append(EndFragment); - - if (innerHtmlEnd < html.Length) - sb.Append(html, innerHtmlEnd, html.Length - innerHtmlEnd); - - if (htmlCloseIdx <= 0) - sb.Append(""); - } - } - else - { - // directly return the cf_html - return html; } - // Back-patch offsets, the replace text area is restricted to header only from index 0 to header.Length - sb.Replace("<<<<<<<<1", Header.Length.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<2", GetByteCount(sb).ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<3", fragmentStart.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - sb.Replace("<<<<<<<<4", fragmentEnd.ToString("D9", CultureInfo.CreateSpecificCulture("en-US")), 0, Header.Length); - return sb.ToString(); - } + var bytes = System.Text.Encoding.UTF8.GetBytes(content); + var encoded = System.Convert.ToBase64String(bytes); + var osc = $"\u001B]52;;{encoded}\u0007"; - /// - /// Calculates the number of bytes produced by encoding the string in the string builder in UTF-8 and not .NET default string encoding. - /// - /// the string builder to count its string - /// optional: the start index to calculate from (default - start of string) - /// optional: the end index to calculate to (default - end of string) - /// the number of bytes required to encode the string in UTF-8 - private static int GetByteCount(StringBuilder sb, int start = 0, int end = -1) - { - char[] _byteCount = new char[1]; - int count = 0; - end = end > -1 ? end : sb.Length; - for (int i = start; i < end; i++) - { - _byteCount[0] = sb[i]; - count += Encoding.UTF8.GetByteCount(_byteCount); - } - return count; + 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 bd2584a3f7a..44f4d904d33 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetContentCommand.cs @@ -1,18 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to set the content of an item at a specified path + /// A command to set the content of an item at a specified path. /// [Cmdlet(VerbsCommon.Set, "Content", DefaultParameterSetName = "Path", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113392")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097142")] public class SetContentCommand : WriteContentCommandBase { #region protected members @@ -21,19 +19,17 @@ public class SetContentCommand : WriteContentCommandBase /// Called by the base class before the streams are open for the path. /// This override clears the content from the item. /// - /// /// /// The path to the items that will be opened for writing content. /// - /// internal override void BeforeOpenStreams(string[] paths) { if (paths == null || paths.Length == 0) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } - CmdletProviderContext context = new CmdletProviderContext(GetCurrentContext()); + CmdletProviderContext context = new(GetCurrentContext()); foreach (string path in paths) { @@ -66,24 +62,21 @@ internal override void BeforeOpenStreams(string[] paths) } catch (ItemNotFoundException) { - //If the item is not found then there is nothing to clear so ignore this exception. + // If the item is not found then there is nothing to clear so ignore this exception. continue; } } - } // BeforeOpenStreams + } /// /// Makes the call to ShouldProcess with appropriate action and target strings. /// - /// /// /// The path to the item on which the content will be set. /// - /// /// /// True if the action should continue or false otherwise. /// - /// internal override bool CallShouldProcess(string path) { string action = NavigationResources.SetContentAction; @@ -93,6 +86,5 @@ internal override bool CallShouldProcess(string path) return ShouldProcess(target, action); } #endregion protected members - } // SetContentCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs index f64bfaf3217..7c5f43aad2b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs @@ -1,18 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// A command to set the property of an item at a specified path + /// A command to set the property of an item at a specified path. /// [Cmdlet(VerbsCommon.Set, "ItemProperty", DefaultParameterSetName = "propertyValuePathSet", SupportsShouldProcess = true, SupportsTransactions = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113396")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097147")] public class SetItemPropertyCommand : PassThroughItemPropertyCommandBase { private const string propertyValuePathSet = "propertyValuePathSet"; @@ -23,7 +20,7 @@ public class SetItemPropertyCommand : PassThroughItemPropertyCommandBase #region Parameters /// - /// Gets or sets the path parameter to the command + /// Gets or sets the path parameter to the command. /// [Parameter(Position = 0, ParameterSetName = propertyPSObjectPathSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] @@ -32,20 +29,25 @@ public class SetItemPropertyCommand : PassThroughItemPropertyCommandBase public string[] Path { get { return paths; } + set { paths = value; } } /// - /// Gets or sets the literal path parameter to the command + /// Gets or sets the literal path parameter to the command. /// [Parameter(ParameterSetName = propertyValueLiteralPathSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] [Parameter(ParameterSetName = propertyPSObjectLiteralPathSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } + set { base.SuppressWildcardExpansion = true; @@ -58,26 +60,22 @@ public string[] LiteralPath /// /// The name of the property to set. /// - /// /// /// This value type is determined by the InvokeProvider. /// - /// [Parameter(Position = 1, ParameterSetName = propertyValuePathSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Parameter(Position = 1, ParameterSetName = propertyValueLiteralPathSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Alias("PSProperty")] - public string Name { get; set; } = String.Empty; + public string Name { get; set; } = string.Empty; /// /// The value of the property to set. /// - /// /// /// This value type is determined by the InvokeProvider. /// - /// [Parameter(Position = 2, ParameterSetName = propertyValuePathSet, Mandatory = true, ValueFromPipelineByPropertyName = true)] [Parameter(Position = 2, ParameterSetName = propertyValueLiteralPathSet, @@ -87,7 +85,7 @@ public string[] LiteralPath #endregion Property Value set - #region Shell Object set + #region Shell object set /// /// A PSObject that contains the properties and values to be set. @@ -101,23 +99,20 @@ public string[] LiteralPath ValueFromPipeline = true)] public PSObject InputObject { get; set; } - #endregion Shell Object set + #endregion Shell object set /// /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { PSObject mshObject = null; @@ -126,24 +121,26 @@ internal override object GetDynamicParameters(CmdletProviderContext context) { case propertyValuePathSet: case propertyValueLiteralPathSet: - if (!String.IsNullOrEmpty(Name)) + if (!string.IsNullOrEmpty(Name)) { mshObject = new PSObject(); mshObject.Properties.Add(new PSNoteProperty(Name, Value)); } + break; default: mshObject = InputObject; break; - } // switch + } if (Path != null && Path.Length > 0) { return InvokeProvider.Property.SetPropertyDynamicParameters(Path[0], mshObject, context); } + return InvokeProvider.Property.SetPropertyDynamicParameters(".", mshObject, context); - } // GetDynamicParameters + } #endregion Parameters @@ -154,7 +151,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) #region Command code /// - /// Sets the content of the item at the specified path + /// Sets the content of the item at the specified path. /// protected override void ProcessRecord() { @@ -183,7 +180,7 @@ protected override void ProcessRecord() false, "One of the parameter sets should have been resolved or an error should have been thrown by the command processor"); break; - } // switch + } foreach (string path in Path) { @@ -224,9 +221,8 @@ protected override void ProcessRecord() continue; } } - } // ProcessRecord + } #endregion Command code - - } // SetItemPropertyCommand -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetWMIInstanceCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetWMIInstanceCommand.cs index acdbea27d00..4b2deac8bef 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetWMIInstanceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetWMIInstanceCommand.cs @@ -1,23 +1,22 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Management; -using System.Text; -using System.Management.Automation.Provider; -using System.ComponentModel; using System.Collections; using System.Collections.ObjectModel; -using System.Security.AccessControl; -using System.Runtime.InteropServices; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Management; +using System.Management.Automation; +using System.Management.Automation.Provider; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; namespace Microsoft.PowerShell.Commands { /// - /// A command to Set WMI Instance + /// A command to Set WMI Instance. /// [Cmdlet(VerbsCommon.Set, "WmiInstance", DefaultParameterSetName = "class", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113402", RemotingCapability = RemotingCapability.OwnedByCommand)] @@ -25,26 +24,25 @@ public sealed class SetWmiInstance : WmiBaseCmdlet { #region Parameters /// - /// The WMI Object to use + /// The WMI Object to use. /// - /// [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = "object")] public ManagementObject InputObject { get; set; } = null; /// - /// The WMI Path to use + /// The WMI Path to use. /// [Parameter(ParameterSetName = "path", Mandatory = true)] public string Path { get; set; } = null; /// - /// The WMI class to use + /// The WMI class to use. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "class")] public string Class { get; set; } = null; /// - /// The property name /value pair + /// The property name /value pair. /// [Parameter(ParameterSetName = "path")] [Parameter(Position = 2, ParameterSetName = "class")] @@ -54,12 +52,13 @@ public sealed class SetWmiInstance : WmiBaseCmdlet public Hashtable Arguments { get; set; } = null; /// - /// The Flag to use + /// The Flag to use. /// [Parameter] public PutType PutType { get { return _putType; } + set { _putType = value; flagSpecified = true; } } @@ -81,6 +80,7 @@ protected override void ProcessRecord() RunAsJob("Set-WMIInstance"); return; } + if (InputObject != null) { object result = null; @@ -96,6 +96,7 @@ protected override void ProcessRecord() { return; } + mObj.Put(pOptions); } else @@ -103,6 +104,7 @@ protected override void ProcessRecord() InvalidOperationException exp = new InvalidOperationException(); throw exp; } + result = mObj; } catch (ManagementException e) @@ -115,14 +117,15 @@ protected override void ProcessRecord() ErrorRecord errorRecord = new ErrorRecord(e, "SetWMICOMException", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } + WriteObject(result); } else { ManagementPath mPath = null; - //If Class is specified only CreateOnly flag is supported + // If Class is specified only CreateOnly flag is supported mPath = this.SetWmiInstanceBuildManagementPath(); - //If server name is specified loop through it. + // If server name is specified loop through it. if (mPath != null) { if (!(mPath.Server == "." && serverNameSpecified)) @@ -131,6 +134,7 @@ protected override void ProcessRecord() ComputerName = serverName; } } + ConnectionOptions options = GetConnectionOption(); object result = null; ManagementObject mObject = null; @@ -148,6 +152,7 @@ protected override void ProcessRecord() { continue; } + mObject.Put(pOptions); } else @@ -155,6 +160,7 @@ protected override void ProcessRecord() InvalidOperationException exp = new InvalidOperationException(); throw exp; } + result = mObject; } catch (ManagementException e) @@ -167,6 +173,7 @@ protected override void ProcessRecord() ErrorRecord errorRecord = new ErrorRecord(e, "SetWMICOMException", ErrorCategory.InvalidOperation, null); WriteError(errorRecord); } + if (result != null) { WriteObject(result); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs index 4d91c4d5461..58dcfbd1daa 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -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 { @@ -26,6 +26,7 @@ public int Timeout { return (int)_timeout.TotalMinutes; } + set { // The transactions constructor treats a timeout of @@ -38,6 +39,7 @@ public int Timeout _timeoutSpecified = true; } } + private bool _timeoutSpecified = false; private TimeSpan _timeout = TimeSpan.MinValue; @@ -45,23 +47,27 @@ 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; } + set { _independent = value; } } + private SwitchParameter _independent; /// /// Gets or sets the rollback preference for this transaction. /// - [Parameter()] + [Parameter] public RollbackSeverity RollbackPreference { get { return _rollbackPreference; } + set { _rollbackPreference = value; } } + private RollbackSeverity _rollbackPreference = RollbackSeverity.Error; /// @@ -99,6 +105,5 @@ protected override void EndProcessing() } } } - } // StartTransactionCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs new file mode 100644 index 00000000000..2a4a453f8f7 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -0,0 +1,1254 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The implementation of the "Test-Connection" cmdlet. + /// + [Cmdlet(VerbsDiagnostic.Test, "Connection", DefaultParameterSetName = DefaultPingParameterSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097144")] + [OutputType(typeof(PingStatus), ParameterSetName = new string[] { DefaultPingParameterSet })] + [OutputType(typeof(PingStatus), ParameterSetName = new string[] { RepeatPingParameterSet, MtuSizeDetectParameterSet })] + [OutputType(typeof(bool), ParameterSetName = new string[] { DefaultPingParameterSet, RepeatPingParameterSet, TcpPortParameterSet })] + [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 + private const string DefaultPingParameterSet = "DefaultPing"; + private const string RepeatPingParameterSet = "RepeatPing"; + private const string TraceRouteParameterSet = "TraceRoute"; + private const string TcpPortParameterSet = "TcpPort"; + private const string MtuSizeDetectParameterSet = "MtuSizeDetect"; + + #endregion + + #region Cmdlet Defaults + + // Count of pings sent to each trace route hop. Default mimics Windows' defaults. + // If this value changes, we need to update 'ConsoleTraceRouteReply' resource string. + private const uint DefaultTraceRoutePingCount = 3; + + // Default size for the send buffer. + private const int DefaultSendBufferSize = 32; + + private const int DefaultMaxHops = 128; + + private const string TestConnectionExceptionId = "TestConnectionException"; + + #endregion + + #region Private Fields + + private static readonly byte[] s_DefaultSendBuffer = Array.Empty(); + + private readonly CancellationTokenSource _dnsLookupCancel = new(); + + private bool _disposed; + + private Ping? _sender; + + #endregion + + #region Parameters + + /// + /// Gets or sets whether to do ping test. + /// Default is true. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + public SwitchParameter Ping { get; set; } = true; + + /// + /// Gets or sets whether to force use of IPv4 protocol. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + public SwitchParameter IPv4 { get; set; } + + /// + /// Gets or sets whether to force use of IPv6 protocol. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + public SwitchParameter IPv6 { get; set; } + + /// + /// Gets or sets whether to do reverse DNS lookup to get names for IP addresses. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = MtuSizeDetectParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + public SwitchParameter ResolveDestination { get; set; } + + /// + /// Gets the source from which to run the selected test. + /// The default is localhost. + /// Remoting is not yet implemented internally in the cmdlet. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + public string Source { get; } = Dns.GetHostName(); + + /// + /// Gets or sets the number of times the Ping data packets can be forwarded by routers. + /// As gateways and routers transmit packets through a network, they decrement the Time-to-Live (TTL) + /// value found in the packet header. + /// The default (from Windows) is 128 hops. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TraceRouteParameterSet)] + [ValidateRange(1, DefaultMaxHops)] + [Alias("Ttl", "TimeToLive", "Hops")] + public int MaxHops { get; set; } = DefaultMaxHops; + + /// + /// Gets or sets the number of ping attempts. + /// The default (from Windows) is 4 times. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + [ValidateRange(ValidateRangeKind.Positive)] + public int Count { get; set; } = 4; + + /// + /// Gets or sets the delay between ping attempts. + /// The default (from Windows) is 1 second. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Parameter(ParameterSetName = TcpPortParameterSet)] + [ValidateRange(ValidateRangeKind.Positive)] + public int Delay { get; set; } = 1; + + /// + /// Gets or sets the buffer size to send with the ping packet. + /// The default (from Windows) is 32 bytes. + /// Max value is 65500 (limitation imposed by Windows API). + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + [Alias("Size", "Bytes", "BS")] + [ValidateRange(0, 65500)] + public int BufferSize { get; set; } = DefaultSendBufferSize; + + /// + /// Gets or sets whether to prevent fragmentation of the ICMP packets. + /// Currently CoreFX not supports this on Unix. + /// + [Parameter(ParameterSetName = DefaultPingParameterSet)] + [Parameter(ParameterSetName = RepeatPingParameterSet)] + public SwitchParameter DontFragment { get; set; } + + /// + /// 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; } + + /// + /// Gets or sets whether to enable quiet output mode, reducing output to a single simple value only. + /// By default, PingStatus, PingMtuStatus, or TraceStatus objects are emitted. + /// With this switch, standard ping and -Traceroute returns only true / false, and -MtuSize returns an integer. + /// + [Parameter] + public SwitchParameter Quiet { get; set; } + + /// + /// Gets or sets whether to enable detailed output mode while running a TCP connection test. + /// Without this flag, the TCP test will return a boolean result. + /// + [Parameter(ParameterSetName = TcpPortParameterSet)] + public SwitchParameter Detailed; + + /// + /// Gets or sets the timeout value for an individual ping in seconds. + /// If a response is not received in this time, no response is assumed. + /// The default (from Windows) is 5 seconds. + /// + [Parameter] + [ValidateRange(ValidateRangeKind.Positive)] + public int TimeoutSeconds { get; set; } = 5; + + /// + /// Gets or sets the destination hostname or IP address. + /// + [Parameter( + Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [Alias("ComputerName")] + public string[]? TargetName { get; set; } + + /// + /// Gets or sets whether to detect Maximum Transmission Unit size. + /// When selected, only a single ping result is returned, indicating the maximum buffer size + /// the route to the destination can support without fragmenting the ICMP packets. + /// + [Parameter(Mandatory = true, ParameterSetName = MtuSizeDetectParameterSet)] + [Alias("MtuSizeDetect")] + public SwitchParameter MtuSize { get; set; } + + /// + /// Gets or sets whether to perform a traceroute test. + /// + [Parameter(Mandatory = true, ParameterSetName = TraceRouteParameterSet)] + public SwitchParameter Traceroute { get; set; } + + /// + /// Gets or sets whether to perform a TCP connection test. + /// + [ValidateRange(0, 65535)] + [Parameter(Mandatory = true, ParameterSetName = TcpPortParameterSet)] + public int TcpPort { get; set; } + + #endregion Parameters + + /// + /// BeginProcessing implementation for TestConnectionCommand. + /// Sets Count for different types of tests unless specified explicitly. + /// + protected override void BeginProcessing() + { + if (Repeat) + { + Count = int.MaxValue; + } + else if (ParameterSetName == TcpPortParameterSet) + { + SetCountForTcpTest(); + } + } + + /// + /// Process a connection test. + /// + protected override void ProcessRecord() + { + if (TargetName == null) + { + return; + } + + foreach (var targetName in TargetName) + { + if (MtuSize) + { + ProcessMTUSize(targetName); + } + else if (Traceroute) + { + ProcessTraceroute(targetName); + } + else if (ParameterSetName == TcpPortParameterSet) + { + ProcessConnectionByTCPPort(targetName); + } + else + { + // None of the switch parameters are true: handle default ping or -Repeat + ProcessPing(targetName); + } + } + } + + /// + /// On receiving the StopProcessing() request, the cmdlet will immediately cancel any in-progress ping request. + /// This allows a cancellation to occur during a ping request without having to wait for the timeout. + /// + protected override void StopProcessing() + { + _sender?.SendAsyncCancel(); + _dnsLookupCancel.Cancel(); + } + + #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)) + { + if (Quiet.IsPresent) + { + WriteObject(false); + } + + return; + } + + int timeoutMilliseconds = TimeoutSeconds * 1000; + int delayMilliseconds = Delay * 1000; + + for (var i = 1; i <= Count; i++) + { + long latency = 0; + SocketError status = SocketError.SocketError; + + Stopwatch stopwatch = new Stopwatch(); + + using var client = new TcpClient(); + + try + { + stopwatch.Start(); + + if (client.ConnectAsync(targetAddress, TcpPort).Wait(timeoutMilliseconds, _dnsLookupCancel.Token)) + { + latency = stopwatch.ElapsedMilliseconds; + status = SocketError.Success; + } + else + { + status = SocketError.TimedOut; + } + } + 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 + )); + } + + if (i < Count) + { + Task.Delay(delayMilliseconds).Wait(_dnsLookupCancel.Token); + } + } + } + + #endregion ConnectionTest + + #region TracerouteTest + + private void ProcessTraceroute(string targetNameOrAddress) + { + byte[] buffer = GetSendBuffer(BufferSize); + + if (!TryResolveNameOrAddress(targetNameOrAddress, out string resolvedTargetName, out IPAddress? targetAddress)) + { + if (!Quiet.IsPresent) + { + WriteObject(false); + } + + return; + } + + int currentHop = 1; + PingOptions pingOptions = new(currentHop, DontFragment.IsPresent); + PingReply reply; + PingReply discoveryReply; + int timeout = TimeoutSeconds * 1000; + Stopwatch timer = new(); + + IPAddress hopAddress; + do + { + pingOptions.Ttl = currentHop; + +#if !UNIX + // Get intermediate hop target. This needs to be done first, so that we can target it properly + // and get useful responses. + var discoveryAttempts = 0; + bool addressIsValid = false; + do + { + discoveryReply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); + discoveryAttempts++; + addressIsValid = !(discoveryReply.Address.Equals(IPAddress.Any) + || discoveryReply.Address.Equals(IPAddress.IPv6Any)); + } + while (discoveryAttempts <= DefaultTraceRoutePingCount && addressIsValid); + + // If we aren't able to get a valid address, just re-target the final destination of the trace. + hopAddress = addressIsValid ? discoveryReply.Address : targetAddress; +#else + // Unix Ping API returns nonsense "TimedOut" for ALL intermediate hops. No way around this + // issue for traceroutes as we rely on information (intermediate addresses, etc.) that is + // simply not returned to us by the API. + // The only supported states on Unix seem to be Success and TimedOut. Workaround is to + // keep targeting the final address; at the very least we will be able to tell the user + // the required number of hops to reach the destination. + hopAddress = targetAddress; + discoveryReply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); +#endif + var hopAddressString = discoveryReply.Address.ToString(); + + string routerName = hopAddressString; + try + { + if (!TryResolveNameOrAddress(hopAddressString, out routerName, out _)) + { + routerName = hopAddressString; + } + } + catch + { + // Swallow hostname resolve exceptions and continue with traceroute + } + + // In traceroutes we don't use 'Count' parameter. + // If we change 'DefaultTraceRoutePingCount' we should change 'ConsoleTraceRouteReply' resource string. + for (uint i = 1; i <= DefaultTraceRoutePingCount; i++) + { + try + { + reply = SendCancellablePing(hopAddress, timeout, buffer, pingOptions, timer); + + if (!Quiet.IsPresent) + { + var status = new PingStatus( + Source, + routerName, + reply, + reply.Status == IPStatus.Success + ? reply.RoundtripTime + : timer.ElapsedMilliseconds, + + // 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, + status, + Source, + resolvedTargetName, + targetAddress)); + } + } + catch (PingException ex) + { + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + ex.Message); + Exception pingException = new PingException(message, ex.InnerException); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); + WriteError(errorRecord); + + continue; + } + + // We use short delay because it is impossible DoS with trace route. + Thread.Sleep(50); + timer.Reset(); + } + + currentHop++; + } while (currentHop <= MaxHops + && (discoveryReply.Status == IPStatus.TtlExpired + || discoveryReply.Status == IPStatus.TimedOut)); + + if (Quiet.IsPresent) + { + WriteObject(currentHop <= MaxHops); + } + else if (currentHop > MaxHops) + { + var message = StringUtil.Format( + TestConnectionResources.MaxHopsExceeded, + resolvedTargetName, + MaxHops); + var pingException = new PingException(message); + WriteError(new ErrorRecord( + pingException, + TestConnectionExceptionId, + ErrorCategory.ConnectionError, + targetAddress)); + } + } + + #endregion TracerouteTest + + #region MTUSizeTest + private void ProcessMTUSize(string targetNameOrAddress) + { + PingReply? reply, replyResult = null; + if (!TryResolveNameOrAddress(targetNameOrAddress, out string resolvedTargetName, out IPAddress? targetAddress)) + { + if (Quiet.IsPresent) + { + WriteObject(-1); + } + + return; + } + + // Caution! Algorithm is sensitive to changing boundary values. + int HighMTUSize = 10000; + int CurrentMTUSize = 1473; + int LowMTUSize = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 1280 : 68; + int timeout = TimeoutSeconds * 1000; + + PingReply? timeoutReply = null; + + try + { + PingOptions pingOptions = new(MaxHops, true); + int retry = 1; + + while (LowMTUSize < (HighMTUSize - 1)) + { + byte[] buffer = GetSendBuffer(CurrentMTUSize); + + WriteDebug(StringUtil.Format( + "LowMTUSize: {0}, CurrentMTUSize: {1}, HighMTUSize: {2}", + LowMTUSize, + CurrentMTUSize, + HighMTUSize)); + + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); + + if (reply.Status == IPStatus.PacketTooBig || reply.Status == IPStatus.TimedOut) + { + HighMTUSize = CurrentMTUSize; + timeoutReply = reply; + retry = 1; + } + else if (reply.Status == IPStatus.Success) + { + LowMTUSize = CurrentMTUSize; + replyResult = reply; + retry = 1; + } + else + { + // If the host didn't reply, try again up to the 'Count' value. + if (retry >= Count) + { + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + targetAddress, + reply.Status.ToString()); + Exception pingException = new PingException(message); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); + WriteError(errorRecord); + return; + } + else + { + retry++; + continue; + } + } + + CurrentMTUSize = (LowMTUSize + HighMTUSize) / 2; + + // Prevent DoS attack. + Thread.Sleep(100); + } + } + catch (PingException ex) + { + string message = StringUtil.Format(TestConnectionResources.NoPingResult, targetAddress, ex.Message); + Exception pingException = new PingException(message, ex.InnerException); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + targetAddress); + WriteError(errorRecord); + return; + } + + if (Quiet.IsPresent) + { + WriteObject(CurrentMTUSize); + } + else + { + 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)); + } + + } + } + + #endregion MTUSizeTest + + #region PingTest + + private void ProcessPing(string targetNameOrAddress) + { + if (!TryResolveNameOrAddress(targetNameOrAddress, out string resolvedTargetName, out IPAddress? targetAddress)) + { + if (Quiet.IsPresent) + { + WriteObject(false); + } + + return; + } + + bool quietResult = true; + byte[] buffer = GetSendBuffer(BufferSize); + + PingReply reply; + PingOptions pingOptions = new(MaxHops, DontFragment.IsPresent); + int timeout = TimeoutSeconds * 1000; + int delay = Delay * 1000; + + for (int i = 1; i <= Count; i++) + { + try + { + reply = SendCancellablePing(targetAddress, timeout, buffer, pingOptions); + } + catch (PingException ex) + { + string message = StringUtil.Format(TestConnectionResources.NoPingResult, resolvedTargetName, ex.Message); + Exception pingException = new PingException(message, ex.InnerException); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); + WriteError(errorRecord); + + quietResult = false; + continue; + } + + if (Quiet.IsPresent) + { + // Return 'true' only if all pings have completed successfully. + quietResult &= reply.Status == IPStatus.Success; + } + else + { + WriteObject(new PingStatus( + Source, + resolvedTargetName, + reply, + reply.RoundtripTime, + buffer.Length == 0 ? DefaultSendBufferSize : buffer.Length, + pingNum: (uint)i)); + } + + // Delay between pings, but not after last ping. + if (i < Count && Delay > 0) + { + Thread.Sleep(delay); + } + } + + if (Quiet.IsPresent) + { + WriteObject(quietResult); + } + } + + #endregion PingTest + + private bool TryResolveNameOrAddress( + string targetNameOrAddress, + out string resolvedTargetName, + [NotNullWhen(true)] + out IPAddress? targetAddress) + { + resolvedTargetName = targetNameOrAddress; + + IPHostEntry hostEntry; + if (IPAddress.TryParse(targetNameOrAddress, out targetAddress)) + { + if ((IPv4 && targetAddress.AddressFamily != AddressFamily.InterNetwork) + || (IPv6 && targetAddress.AddressFamily != AddressFamily.InterNetworkV6)) + { + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.TargetAddressAbsent); + Exception pingException = new PingException(message, null); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); + WriteError(errorRecord); + return false; + } + + if (ResolveDestination) + { + hostEntry = GetCancellableHostEntry(targetNameOrAddress); + resolvedTargetName = hostEntry.HostName; + } + else + { + resolvedTargetName = targetAddress.ToString(); + } + } + else + { + try + { + hostEntry = GetCancellableHostEntry(targetNameOrAddress); + + if (ResolveDestination) + { + resolvedTargetName = hostEntry.HostName; + hostEntry = GetCancellableHostEntry(hostEntry.HostName); + } + } + catch (PipelineStoppedException) + { + throw; + } + catch (Exception ex) + { + if (!Quiet.IsPresent) + { + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.CannotResolveTargetName); + Exception pingException = new PingException(message, ex); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); + WriteError(errorRecord); + } + + return false; + } + + if (IPv6 || IPv4) + { + targetAddress = GetHostAddress(hostEntry); + + if (targetAddress == null) + { + string message = StringUtil.Format( + TestConnectionResources.NoPingResult, + resolvedTargetName, + TestConnectionResources.TargetAddressAbsent); + Exception pingException = new PingException(message, null); + ErrorRecord errorRecord = new( + pingException, + TestConnectionExceptionId, + ErrorCategory.ResourceUnavailable, + resolvedTargetName); + WriteError(errorRecord); + return false; + } + } + else + { + targetAddress = hostEntry.AddressList[0]; + } + } + + return true; + } + + private IPHostEntry GetCancellableHostEntry(string targetNameOrAddress) + { + var task = Dns.GetHostEntryAsync(targetNameOrAddress); + var waitHandles = new[] { ((IAsyncResult)task).AsyncWaitHandle, _dnsLookupCancel.Token.WaitHandle }; + + // WaitAny() returns the index of the first signal it gets; 1 is our cancellation token. + if (WaitHandle.WaitAny(waitHandles) == 1) + { + throw new PipelineStoppedException(); + } + + return task.GetAwaiter().GetResult(); + } + + private IPAddress? GetHostAddress(IPHostEntry hostEntry) + { + AddressFamily addressFamily = IPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; + + foreach (var address in hostEntry.AddressList) + { + if (address.AddressFamily == addressFamily) + { + return address; + } + } + + return null; + } + + // 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 static byte[] GetSendBuffer(int bufferSize) + { + if (bufferSize == DefaultSendBufferSize) + { + return s_DefaultSendBuffer; + } + + byte[] sendBuffer = new byte[bufferSize]; + + for (int i = 0; i < bufferSize; i++) + { + sendBuffer[i] = (byte)((int)'a' + i % 23); + } + + return sendBuffer; + } + + /// + /// 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. + /// + /// + /// Specified as true when Dispose() was called, false if this is called from the finalizer. + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _sender?.Dispose(); + _dnsLookupCancel.Dispose(); + } + + _disposed = true; + } + } + + // Uses the SendAsync() method to send pings, so that Ctrl+C can halt the request early if needed. + private PingReply SendCancellablePing( + IPAddress targetAddress, + int timeout, + byte[] buffer, + PingOptions pingOptions, + Stopwatch? timer = null) + { + try + { + _sender = new Ping(); + + timer?.Start(); + // 'SendPingAsync' always uses the default synchronization context (threadpool). + // This is what we want to avoid the deadlock resulted by async work being scheduled back to the + // pipeline thread due to a change of the current synchronization context of the pipeline thread. + return _sender.SendPingAsync(targetAddress, timeout, buffer, pingOptions).GetAwaiter().GetResult(); + } + catch (PingException ex) when (ex.InnerException is TaskCanceledException) + { + // The only cancellation we have implemented is on pipeline stops via StopProcessing(). + throw new PipelineStoppedException(); + } + finally + { + timer?.Stop(); + _sender?.Dispose(); + _sender = null; + } + } + + /// + /// 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. + /// + public class PingStatus + { + /// + /// Initializes a new instance of the class. + /// This constructor allows manually specifying the initial values for the cases where the PingReply + /// object may be missing some information, specifically in the instances where PingReply objects are + /// utilised to perform a traceroute. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + /// The latency of the ping. + /// The buffer size. + /// The sequence number in the sequence of pings to the hop point. + internal PingStatus( + string source, + string destination, + PingReply reply, + long latency, + int bufferSize, + uint pingNum) + : this(source, destination, reply, pingNum) + { + _bufferSize = bufferSize; + _latency = latency; + } + + /// + /// Initializes a new instance of the class. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + /// The sequence number of the ping in the sequence of pings to the target. + internal PingStatus(string source, string destination, PingReply reply, uint pingNum) + { + Ping = pingNum; + Reply = reply; + Source = source; + Destination = destination; + } + + // These values can be set manually to skirt issues with the Ping API on Unix platforms + // so that we can return meaningful known data that is discarded by the API. + private readonly int _bufferSize = -1; + + private readonly long _latency = -1; + + /// + /// Gets the sequence number of this ping in the sequence of pings to the + /// + public uint Ping { get; } + + /// + /// Gets the source from which the ping was sent. + /// + public string Source { get; } + + /// + /// Gets the destination which was pinged. + /// + public string Destination { get; } + + /// + /// Gets the target address of the ping. + /// + public IPAddress? Address { get => Reply.Status == IPStatus.Success ? Reply.Address : null; } + + /// + /// Gets the target address of the ping if one is available, or "*" if it is not. + /// + public string DisplayAddress { get => Address?.ToString() ?? "*"; } + + /// + /// Gets the roundtrip time of the ping in milliseconds. + /// + public long Latency { get => _latency >= 0 ? _latency : Reply.RoundtripTime; } + + /// + /// Gets the returned status of the ping. + /// + public IPStatus Status { get => Reply.Status; } + + /// + /// Gets the size in bytes of the buffer data sent in the ping. + /// + public int BufferSize { get => _bufferSize >= 0 ? _bufferSize : Reply.Buffer.Length; } + + /// + /// Gets the reply object from this ping. + /// + public PingReply Reply { get; } + } + + /// + /// The class contains information about the source, the destination and ping results. + /// + public class PingMtuStatus : PingStatus + { + /// + /// Initializes a new instance of the class. + /// + /// The source machine name or IP of the ping. + /// The destination machine name of the ping. + /// The response from the ping attempt. + /// The buffer size from the successful ping attempt. + internal PingMtuStatus(string source, string destination, PingReply reply, int bufferSize) + : base(source, destination, reply, 1) + { + MtuSize = bufferSize; + } + + /// + /// Gets the maximum transmission unit size on the network path between the source and destination. + /// + public int MtuSize { get; } + } + + /// + /// The class contains an information about a trace route attempt. + /// + public class TraceStatus + { + /// + /// Initializes a new instance of the class. + /// + /// The hop number of this trace hop. + /// The PingStatus response from this trace hop. + /// The source computer name or IP address of the traceroute. + /// The target destination of the traceroute. + /// The target IPAddress of the overall traceroute. + internal TraceStatus( + int hop, + PingStatus status, + string source, + string destination, + IPAddress destinationAddress) + { + _status = status; + Hop = hop; + Source = source; + Target = destination; + TargetAddress = destinationAddress; + + if (_status.Address == IPAddress.Any + || _status.Address == IPAddress.IPv6Any) + { + Hostname = null; + } + else + { + Hostname = _status.Destination; + } + } + + private readonly PingStatus _status; + + /// + /// Gets the number of the current hop / router. + /// + public int Hop { get; } + + /// + /// Gets the hostname of the current hop point. + /// + /// + public string? Hostname { get; } + + /// + /// Gets the sequence number of the ping in the sequence of pings to the hop point. + /// + public uint Ping { get => _status.Ping; } + + /// + /// Gets the IP address of the current hop point. + /// + public IPAddress? HopAddress { get => _status.Address; } + + /// + /// Gets the latency values of each ping to the current hop point. + /// + public long Latency { get => _status.Latency; } + + /// + /// Gets the status of the traceroute hop. + /// + public IPStatus Status { get => _status.Status; } + + /// + /// Gets the source address of the traceroute command. + /// + public string Source { get; } + + /// + /// Gets the final destination hostname of the trace. + /// + public string Target { get; } + + /// + /// Gets the final destination IP address of the trace. + /// + public IPAddress TargetAddress { get; } + + /// + /// Gets the raw PingReply object received from the ping to this hop point. + /// + public PingReply Reply { get => _status.Reply; } + } + + /// + /// Finalizes an instance of the class. + /// + ~TestConnectionCommand() + { + Dispose(disposing: false); + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs new file mode 100644 index 00000000000..50765c0e0ae --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The valid values for the -PathType parameter for test-path. + /// + public enum TestPathType + { + /// + /// If the item at the path exists, true will be returned. + /// + Any, + + /// + /// If the item at the path exists and is a container, true will be returned. + /// + Container, + + /// + /// If the item at the path exists and is not a container, true will be returned. + /// + Leaf + } + + /// + /// A command to determine if an item exists at a specified path. + /// + [Cmdlet(VerbsDiagnostic.Test, "Path", DefaultParameterSetName = "Path", SupportsTransactions = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097057")] + [OutputType(typeof(bool))] + public class TestPathCommand : CoreCommandWithCredentialsBase + { + #region Parameters + + /// + /// Gets or sets the path parameter to the command. + /// + [Parameter(Position = 0, ParameterSetName = "Path", + Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [AllowNull] + [AllowEmptyCollection] + [AllowEmptyString] + public string[] Path + { + get { return _paths; } + + set { _paths = value; } + } + + /// + /// Gets or sets the literal path parameter to the command. + /// + [Parameter(ParameterSetName = "LiteralPath", + Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] + [Alias("PSPath", "LP")] + [AllowNull] + [AllowEmptyCollection] + [AllowEmptyString] + public string[] LiteralPath + { + get + { + return _paths; + } + + set + { + base.SuppressWildcardExpansion = true; + _paths = value; + } + } + + /// + /// Gets or sets the filter property. + /// + [Parameter] + public override string Filter + { + get { return base.Filter; } + + set { base.Filter = value; } + } + + /// + /// Gets or sets the include property. + /// + [Parameter] + public override string[] Include + { + get { return base.Include; } + + set { base.Include = value; } + } + + /// + /// Gets or sets the exclude property. + /// + [Parameter] + public override string[] Exclude + { + get { return base.Exclude; } + + set { base.Exclude = value; } + } + + /// + /// Gets or sets the isContainer property. + /// + [Parameter] + [Alias("Type")] + public TestPathType PathType { get; set; } = TestPathType.Any; + + /// + /// Gets or sets the IsValid parameter. + /// + [Parameter] + public SwitchParameter IsValid { get; set; } = new SwitchParameter(); + + /// + /// A virtual method for retrieving the dynamic parameters for a cmdlet. Derived cmdlets + /// that require dynamic parameters should override this method and return the + /// dynamic parameter object. + /// + /// + /// The context under which the command is running. + /// + /// + /// An object representing the dynamic parameters for the cmdlet or null if there + /// are none. + /// + internal override object GetDynamicParameters(CmdletProviderContext context) + { + object result = null; + + if (!IsValid) + { + if (Path != null && Path.Length > 0 && Path[0] != null) + { + result = InvokeProvider.Item.ItemExistsDynamicParameters(Path[0], context); + } + else + { + result = InvokeProvider.Item.ItemExistsDynamicParameters(".", context); + } + } + + return result; + } + + #endregion Parameters + + #region parameter data + + /// + /// The path to the item to ping. + /// + private string[] _paths; + + #endregion parameter data + + #region Command code + + /// + /// Determines if an item at the specified path exists. + /// + protected override void ProcessRecord() + { + if (_paths == null || _paths.Length == 0) + { + WriteError(new ErrorRecord( + new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection), + "NullPathNotPermitted", + ErrorCategory.InvalidArgument, + Path)); + + return; + } + + CmdletProviderContext currentContext = CmdletProviderContext; + + foreach (string path in _paths) + { + bool result = false; + + if (string.IsNullOrWhiteSpace(path)) + { + if (path is null) + { + WriteError(new ErrorRecord( + new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection), + "NullPathNotPermitted", + ErrorCategory.InvalidArgument, + Path)); + } + else + { + WriteObject(result); + } + + continue; + } + + try + { + if (IsValid) + { + result = SessionState.Path.IsValid(path, currentContext); + } + else + { + result = InvokeProvider.Item.Exists(path, currentContext); + + if (this.PathType == TestPathType.Container) + { + result &= InvokeProvider.Item.IsContainer(path, currentContext); + } + else if (this.PathType == TestPathType.Leaf) + { + result &= !InvokeProvider.Item.IsContainer(path, currentContext); + } + } + } + + // Any of the known exceptions means the path does not exist. + catch (PSNotSupportedException) + { + } + catch (DriveNotFoundException) + { + } + catch (ProviderNotFoundException) + { + } + catch (ItemNotFoundException) + { + } + + WriteObject(result); + } + } + #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 75b41682981..22a50e41176 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs @@ -1,11 +1,14 @@ +// 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.ComponentModel; -using System.Runtime.InteropServices; -using System.Globalization; using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation; +using System.Runtime.InteropServices; namespace Microsoft.PowerShell.Commands { @@ -13,11 +16,12 @@ namespace Microsoft.PowerShell.Commands /// A cmdlet to retrieve time zone information. /// [Cmdlet(VerbsCommon.Get, "TimeZone", DefaultParameterSetName = "Name", - HelpUri = "https://go.microsoft.com/fwlink/?LinkId=799468")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096904")] + [OutputType(typeof(TimeZoneInfo))] [Alias("gtz")] public class GetTimeZoneCommand : PSCmdlet { -#region Parameters + #region Parameters /// /// A list of the local time zone ids that the cmdlet should look up. @@ -40,17 +44,17 @@ public class GetTimeZoneCommand : PSCmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { get; set; } -#endregion Parameters + #endregion Parameters /// - /// Implementation of the ProcessRecord method for Get-TimeZone + /// Implementation of the ProcessRecord method for Get-TimeZone. /// 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); @@ -73,13 +77,13 @@ protected override void ProcessRecord() } else // ParameterSetName == "Name" { - if (null != Name) + if (Name != null) { // lookup each time zone name (or wildcard pattern) 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 @@ -117,17 +121,18 @@ protected override void ProcessRecord() [Cmdlet(VerbsCommon.Set, "TimeZone", SupportsShouldProcess = true, DefaultParameterSetName = "Name", - HelpUri = "https://go.microsoft.com/fwlink/?LinkId=799469")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097056")] + [OutputType(typeof(TimeZoneInfo))] [Alias("stz")] public class SetTimeZoneCommand : PSCmdlet { -#region string constants + #region string constants private const string TimeZoneTarget = "Local System"; -#endregion string constants + #endregion string constants -#region Parameters + #region Parameters /// /// The name of the local time zone that the system should use. @@ -148,15 +153,15 @@ public class SetTimeZoneCommand : PSCmdlet public string Name { get; set; } /// - /// Request return of the new local time zone as a TimeZoneInfo object + /// Request return of the new local time zone as a TimeZoneInfo object. /// [Parameter] public SwitchParameter PassThru { get; set; } -#endregion Parameters + #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() @@ -185,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); @@ -195,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); @@ -251,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(); @@ -317,7 +322,7 @@ protected override void ProcessRecord() } } -#region Helper functions + #region Helper functions /// /// True if the current process has access to change the time zone setting. @@ -340,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, @@ -370,7 +375,7 @@ protected bool HasAccess } /// - /// Set the SeTimeZonePrivilege, which controls access to the SetDynamicTimeZoneInformation API + /// Set the SeTimeZonePrivilege, which controls access to the SetDynamicTimeZoneInformation API. /// /// Set to true to enable (or false to disable) the privilege. protected void SetAccessToken(bool enable) @@ -387,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, @@ -421,20 +426,13 @@ protected void ThrowWin32Error() throw new Win32Exception(error); } -#endregion Helper functions + #endregion Helper functions -#region Win32 interop helper + #region Win32 interop helper - internal class NativeMethods + internal static class NativeMethods { - /// - /// Private constructor to prevent instantiation - /// - private NativeMethods() - { - } - -#region Native DLL locations + #region Native DLL locations private const string SetDynamicTimeZoneApiDllName = "api-ms-win-core-timezone-l1-1-0.dll"; private const string GetTimeZoneInformationForYearApiDllName = "api-ms-win-core-timezone-l1-1-0.dll"; @@ -446,12 +444,12 @@ private NativeMethods() private const string CloseHandleApiDllName = "api-ms-win-downlevel-kernel32-l1-1-0.dll"; private const string SendMessageTimeoutApiDllName = "ext-ms-win-rtcore-ntuser-window-ext-l1-1-0.dll"; -#endregion Native DLL locations + #endregion Native DLL locations -#region Win32 SetDynamicTimeZoneInformation imports + #region Win32 SetDynamicTimeZoneInformation imports /// - /// Used to marshal win32 SystemTime structure to managed code layer + /// Used to marshal win32 SystemTime structure to managed code layer. /// [StructLayout(LayoutKind.Sequential)] public struct SystemTime @@ -499,7 +497,7 @@ public struct SystemTime } /// - /// Used to marshal win32 DYNAMIC_TIME_ZONE_INFORMATION structure to managed code layer + /// Used to marshal win32 DYNAMIC_TIME_ZONE_INFORMATION structure to managed code layer. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DYNAMIC_TIME_ZONE_INFORMATION @@ -550,7 +548,7 @@ public struct DYNAMIC_TIME_ZONE_INFORMATION } /// - /// Used to marshal win32 TIME_ZONE_INFORMATION structure to managed code layer + /// Used to marshal win32 TIME_ZONE_INFORMATION structure to managed code layer. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct TIME_ZONE_INFORMATION @@ -591,49 +589,51 @@ public struct TIME_ZONE_INFORMATION } /// - /// PInvoke SetDynamicTimeZoneInformation API + /// PInvoke SetDynamicTimeZoneInformation API. /// /// A DYNAMIC_TIME_ZONE_INFORMATION structure representing the desired local time zone. /// [DllImport(SetDynamicTimeZoneApiDllName, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetDynamicTimeZoneInformation([In] ref DYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation); [DllImport(GetTimeZoneInformationForYearApiDllName, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetTimeZoneInformationForYear([In] ushort wYear, [In] ref DYNAMIC_TIME_ZONE_INFORMATION pdtzi, ref TIME_ZONE_INFORMATION ptzi); -#endregion Win32 SetDynamicTimeZoneInformation imports + #endregion Win32 SetDynamicTimeZoneInformation imports -#region Win32 AdjustTokenPrivilege imports + #region Win32 AdjustTokenPrivilege imports /// - /// Definition of TOKEN_QUERY constant from Win32 API + /// Definition of TOKEN_QUERY constant from Win32 API. /// public const int TOKEN_QUERY = 0x00000008; /// - /// Definition of TOKEN_ADJUST_PRIVILEGES constant from Win32 API + /// Definition of TOKEN_ADJUST_PRIVILEGES constant from Win32 API. /// public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; /// - /// Definition of SE_PRIVILEGE_ENABLED constant from Win32 API + /// Definition of SE_PRIVILEGE_ENABLED constant from Win32 API. /// public const int SE_PRIVILEGE_ENABLED = 0x00000002; /// - /// Definition of SE_TIME_ZONE_NAME constant from Win32 API + /// Definition of SE_TIME_ZONE_NAME constant from Win32 API. /// - public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; //http://msdn.microsoft.com/en-us/library/bb530716(VS.85).aspx + public const string SE_TIME_ZONE_NAME = "SeTimeZonePrivilege"; // https://msdn.microsoft.com/library/bb530716(VS.85).aspx /// - /// PInvoke GetCurrentProcess API + /// PInvoke GetCurrentProcess API. /// /// [DllImport(GetCurrentProcessApiDllName, ExactSpelling = true)] public static extern IntPtr GetCurrentProcess(); /// - /// PInvoke OpenProcessToken API + /// PInvoke OpenProcessToken API. /// /// /// @@ -644,7 +644,7 @@ public struct TIME_ZONE_INFORMATION public static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle); /// - /// PInvoke LookupPrivilegeValue API + /// PInvoke LookupPrivilegeValue API. /// /// /// @@ -655,7 +655,7 @@ public struct TIME_ZONE_INFORMATION public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref long lpLuid); /// - /// PInvoke PrivilegeCheck API + /// PInvoke PrivilegeCheck API. /// /// /// @@ -665,9 +665,8 @@ public struct TIME_ZONE_INFORMATION [return: MarshalAs(UnmanagedType.Bool)] public static extern bool PrivilegeCheck(IntPtr ClientToken, ref PRIVILEGE_SET RequiredPrivileges, ref bool pfResult); - /// - /// PInvoke AdjustTokenPrivilege API + /// PInvoke AdjustTokenPrivilege API. /// /// /// @@ -682,7 +681,7 @@ public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool Disable ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength); /// - /// PInvoke CloseHandle API + /// PInvoke CloseHandle API. /// /// /// @@ -691,7 +690,7 @@ public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool Disable public static extern bool CloseHandle(IntPtr hObject); /// - /// Used to marshal win32 PRIVILEGE_SET structure to managed code layer + /// Used to marshal win32 PRIVILEGE_SET structure to managed code layer. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PRIVILEGE_SET @@ -703,7 +702,7 @@ public struct PRIVILEGE_SET } /// - /// Used to marshal win32 TOKEN_PRIVILEGES structure to managed code layer + /// Used to marshal win32 TOKEN_PRIVILEGES structure to managed code layer. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TOKEN_PRIVILEGES @@ -713,27 +712,27 @@ public struct TOKEN_PRIVILEGES public int Attributes; } -#endregion Win32 AdjustTokenPrivilege imports + #endregion Win32 AdjustTokenPrivilege imports -#region Win32 SendMessage imports + #region Win32 SendMessage imports /// - /// Definition of WM_SETTINGCHANGE constant from Win32 API + /// Definition of WM_SETTINGCHANGE constant from Win32 API. /// public const int WM_SETTINGCHANGE = 0x001A; /// - /// Definition of HWND_BROADCAST constant from Win32 API + /// Definition of HWND_BROADCAST constant from Win32 API. /// public const int HWND_BROADCAST = (-1); /// - /// Definition of SMTO_ABORTIFHUNG constant from Win32 API + /// Definition of SMTO_ABORTIFHUNG constant from Win32 API. /// public const int SMTO_ABORTIFHUNG = 0x0002; /// - /// PInvoke SendMessageTimeout API + /// PInvoke SendMessageTimeout API. /// /// /// @@ -746,26 +745,26 @@ public struct TOKEN_PRIVILEGES [DllImport(SendMessageTimeoutApiDllName, SetLastError = true, CharSet = CharSet.Unicode)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, ref int lpdwResult); -#endregion Win32 SendMessage imports + #endregion Win32 SendMessage imports } -#endregion Win32 interop helper + #endregion Win32 interop helper } #endif /// - /// static Helper class for working with system time zones. + /// Static Helper class for working with system time zones. /// internal static class TimeZoneHelper { -#region Error Ids + #region Error Ids internal const string TimeZoneNotFoundError = "TimeZoneNotFound"; internal const string MultipleMatchingTimeZonesError = "MultipleMatchingTimeZones"; internal const string InsufficientPermissionsError = "InsufficientPermissions"; internal const string SetTimeZoneFailedError = "SetTimeZoneFailed"; -#endregion Error Ids + #endregion Error Ids /// /// Find the system time zone by checking first against StandardName and then, @@ -775,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 a36f997a144..67d3acee44a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; using System.Management.Automation.Internal; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -17,7 +17,7 @@ public class UseTransactionCommand : PSCmdlet { /// /// This parameter specifies the script block to run in the current - /// PowerShell transaction + /// PowerShell transaction. /// [Parameter(Position = 0, Mandatory = true)] public ScriptBlock TransactedScript @@ -26,15 +26,17 @@ public ScriptBlock TransactedScript { return _transactedScript; } + set { _transactedScript = value; } } + private ScriptBlock _transactedScript; /// - /// Commits the current transaction + /// Commits the current transaction. /// protected override void EndProcessing() { @@ -42,7 +44,7 @@ protected override void EndProcessing() { try { - var emptyArray = Utils.EmptyArray(); + var emptyArray = Array.Empty(); _transactedScript.InvokeUsingCmdlet( contextCmdlet: this, useLocalScope: false, @@ -87,6 +89,5 @@ protected override void EndProcessing() } } } - } // CommitTransactionCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs index 8cf7df9d61d..31670a7d632 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WMIHelper.cs @@ -1,23 +1,24 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Management.Automation; using System.Management; +using System.Management.Automation; using System.Management.Automation.Internal; -using System.Text; using System.Management.Automation.Provider; -using System.ComponentModel; -using System.Collections; -using System.Collections.ObjectModel; -using System.Security.AccessControl; +using System.Management.Automation.Remoting; using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; using System.Threading; -using System.Management.Automation.Remoting; -using System.Diagnostics.CodeAnalysis; + using Microsoft.PowerShell.Commands.Internal; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -26,13 +27,13 @@ namespace Microsoft.PowerShell.Commands /// /// Base class for all WMI helper classes. This is an abstract class - /// and the helpers need to derive from this + /// and the helpers need to derive from this. /// internal abstract class AsyncCmdletHelper : IThrottleOperation { /// /// Exception raised internally when any method of this class - /// is executed + /// is executed. /// internal Exception InternalException { @@ -41,6 +42,7 @@ internal Exception InternalException return internalException; } } + protected Exception internalException = null; } @@ -52,12 +54,12 @@ internal Exception InternalException internal class WmiAsyncCmdletHelper : AsyncCmdletHelper { /// - /// Internal Constructor + /// Internal Constructor. /// - /// Job associated with this operation - /// object associated with this operation - /// computer on which the operation is invoked - /// sink to get wmi objects + /// Job associated with this operation. + /// Object associated with this operation. + /// Computer on which the operation is invoked. + /// Sink to get wmi objects. internal WmiAsyncCmdletHelper(PSWmiChildJob childJob, Cmdlet wmiObject, string computerName, ManagementOperationObserver results) { _wmiObject = wmiObject; @@ -71,11 +73,11 @@ internal WmiAsyncCmdletHelper(PSWmiChildJob childJob, Cmdlet wmiObject, string c /// Internal Constructor. This variant takes a count parameter that determines how many times /// the WMI command is executed. /// - /// Job associated with this operation - /// Object associated with this operation - /// Computer on which the operation is invoked - /// Sink to return wmi objects - /// Number of times the WMI command is executed + /// Job associated with this operation. + /// Object associated with this operation. + /// Computer on which the operation is invoked. + /// Sink to return wmi objects. + /// Number of times the WMI command is executed. internal WmiAsyncCmdletHelper(PSWmiChildJob childJob, Cmdlet wmiObject, string computerName, ManagementOperationObserver results, int count) : this(childJob, wmiObject, computerName, results) { @@ -89,18 +91,19 @@ internal WmiAsyncCmdletHelper(PSWmiChildJob childJob, Cmdlet wmiObject, string c private int _cmdCount = 1; private PSWmiChildJob _job; /// - /// current operation state + /// Current operation state. /// internal WmiState State { get { return _state; } + set { _state = value; } } private WmiState _state; /// - /// Cancel WMI connection + /// Cancel WMI connection. /// internal override void StopOperation() { @@ -109,20 +112,21 @@ internal override void StopOperation() RaiseOperationCompleteEvent(null, OperationState.StopComplete); } /// - /// Uses this.filter, this.wmiClass and this.property to retrieve the filter + /// Uses this.filter, this.wmiClass and this.property to retrieve the filter. /// private string GetWmiQueryString() { GetWmiObjectCommand getObject = (GetWmiObjectCommand)_wmiObject; StringBuilder returnValue = new StringBuilder("select "); - returnValue.Append(String.Join(", ", getObject.Property)); + returnValue.Append(string.Join(", ", getObject.Property)); returnValue.Append(" from "); returnValue.Append(getObject.Class); - if (!String.IsNullOrEmpty(getObject.Filter)) + if (!string.IsNullOrEmpty(getObject.Filter)) { returnValue.Append(" where "); returnValue.Append(getObject.Filter); } + return returnValue.ToString(); } @@ -156,31 +160,31 @@ internal override void StartOperation() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + thread.IsBackground = true; - //thread.SetApartmentState( ApartmentState.STA); + thread.SetApartmentState(ApartmentState.STA); thread.Start(); } /// - /// /// internal override event EventHandler OperationComplete; private Cmdlet _wmiObject; /// - /// Raise operation completion event + /// Raise operation completion event. /// internal void RaiseOperationCompleteEvent(EventArgs baseEventArgs, OperationState state) { OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); operationStateEventArgs.OperationState = state; OperationComplete.SafeInvoke(this, operationStateEventArgs); - } // RaiseOperationCompleteEvent + } - /// + /// /// Raise WMI state changed event - /// + /// internal void RaiseWmiOperationState(EventArgs baseEventArgs, WmiState state) { WmiJobStateEventArgs wmiJobStateEventArgs = new WmiJobStateEventArgs(); @@ -202,10 +206,10 @@ private void ConnectSetWmi() try { PutOptions pOptions = new PutOptions(); - //Extra check + // Extra check if (setObject.InputObject.GetType() == typeof(ManagementClass)) { - //Check if Flag specified is CreateOnly or not + // Check if Flag specified is CreateOnly or not if (setObject.flagSpecified && setObject.PutType != PutType.CreateOnly) { InvalidOperationException e = new InvalidOperationException("CreateOnlyFlagNotSpecifiedWithClassPath"); @@ -214,12 +218,13 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + mObj = ((ManagementClass)setObject.InputObject).CreateInstance(); setObject.PutType = PutType.CreateOnly; } else { - //Check if Flag specified is Updateonly or UpdateOrCreateOnly or not + // Check if Flag specified is Updateonly or UpdateOrCreateOnly or not if (setObject.flagSpecified) { if (!(setObject.PutType == PutType.UpdateOnly || setObject.PutType == PutType.UpdateOrCreate)) @@ -238,6 +243,7 @@ private void ConnectSetWmi() mObj = (ManagementObject)setObject.InputObject.Clone(); } + if (setObject.Arguments != null) { IDictionaryEnumerator en = setObject.Arguments.GetEnumerator(); @@ -246,6 +252,7 @@ private void ConnectSetWmi() mObj[en.Key as string] = en.Value; } } + pOptions.Type = setObject.PutType; if (mObj != null) { @@ -281,7 +288,7 @@ private void ConnectSetWmi() else { ManagementPath mPath = null; - //If Class is specified only CreateOnly flag is supported + // If Class is specified only CreateOnly flag is supported if (setObject.Class != null) { if (setObject.flagSpecified && setObject.PutType != PutType.CreateOnly) @@ -292,12 +299,13 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + setObject.PutType = PutType.CreateOnly; } else { mPath = new ManagementPath(setObject.Path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = setObject.Namespace; } @@ -318,6 +326,7 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + if (mPath.IsClass) { if (setObject.flagSpecified && setObject.PutType != PutType.CreateOnly) @@ -328,6 +337,7 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + setObject.PutType = PutType.CreateOnly; } else @@ -349,7 +359,7 @@ private void ConnectSetWmi() } } } - //If server name is specified loop through it. + // If server name is specified loop through it. if (mPath != null) { if (!(mPath.Server == "." && setObject.serverNameSpecified)) @@ -357,6 +367,7 @@ private void ConnectSetWmi() _computerName = mPath.Server; } } + ConnectionOptions options = setObject.GetConnectionOption(); ManagementObject mObject = null; try @@ -373,7 +384,7 @@ private void ConnectSetWmi() } else { - //This can throw if path does not exist caller should catch it. + // This can throw if path does not exist caller should catch it. ManagementObject mInstance = new ManagementObject(mPath); mInstance.Scope = mScope; try @@ -389,6 +400,7 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + int namespaceIndex = setObject.Path.IndexOf(':'); if (namespaceIndex == -1) { @@ -397,6 +409,7 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + int classIndex = (setObject.Path.Substring(namespaceIndex)).IndexOf('.'); if (classIndex == -1) { @@ -405,13 +418,14 @@ private void ConnectSetWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } - //Get class object and create instance. + // Get class object and create instance. string newPath = setObject.Path.Substring(0, classIndex + namespaceIndex); ManagementPath classPath = new ManagementPath(newPath); ManagementClass mClass = new ManagementClass(classPath); mClass.Scope = mScope; mInstance = mClass.CreateInstance(); } + mObject = mInstance; } } @@ -422,6 +436,7 @@ private void ConnectSetWmi() mClass.Scope = scope; mObject = mClass.CreateInstance(); } + if (setObject.Arguments != null) { IDictionaryEnumerator en = setObject.Arguments.GetEnumerator(); @@ -430,6 +445,7 @@ private void ConnectSetWmi() mObject[en.Key as string] = en.Value; } } + PutOptions pOptions = new PutOptions(); pOptions.Type = setObject.PutType; if (mObject != null) @@ -491,6 +507,7 @@ private void ConnectInvokeWmi() inParamCount--; } } + invokeObject.InputObject.InvokeMethod(_results, invokeObject.Name, inputParameters, null); } catch (ManagementException e) @@ -511,6 +528,7 @@ private void ConnectInvokeWmi() _state = WmiState.Failed; RaiseOperationCompleteEvent(null, OperationState.StopComplete); } + return; } else @@ -521,7 +539,7 @@ private void ConnectInvokeWmi() if (invokeObject.Path != null) { mPath = new ManagementPath(invokeObject.Path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = invokeObject.Namespace; } @@ -542,7 +560,7 @@ private void ConnectInvokeWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } - //If server name is specified loop through it. + // If server name is specified loop through it. if (!(mPath.Server == "." && invokeObject.serverNameSpecified)) { _computerName = mPath.Server; @@ -583,6 +601,7 @@ private void ConnectInvokeWmi() ManagementObject mInstance = new ManagementObject(mPath); mObject = mInstance; } + ManagementScope mScope = new ManagementScope(mPath, options); mObject.Scope = mScope; } @@ -660,7 +679,7 @@ private void ConnectInvokeWmi() } /// - /// Check if we need to enable the shutdown privilege + /// Check if we need to enable the shutdown privilege. /// /// /// @@ -673,11 +692,11 @@ private bool NeedToEnablePrivilege(string computer, string methodName, ref bool { result = true; - // CLR 4.0 Port note - use https://msdn.microsoft.com/en-us/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx + // CLR 4.0 Port note - use https://msdn.microsoft.com/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx string localName = System.Net.Dns.GetHostName(); // And for this, use PsUtils.GetHostname() - string localFullName = System.Net.Dns.GetHostEntry("").HostName; + string localFullName = System.Net.Dns.GetHostEntry(string.Empty).HostName; if (computer.Equals(".") || computer.Equals("localhost", StringComparison.OrdinalIgnoreCase) || computer.Equals(localName, StringComparison.OrdinalIgnoreCase) || computer.Equals(localFullName, StringComparison.OrdinalIgnoreCase)) @@ -721,6 +740,7 @@ private void ConnectRemoveWmi() _state = WmiState.Failed; RaiseOperationCompleteEvent(null, OperationState.StopComplete); } + return; } else @@ -731,7 +751,7 @@ private void ConnectRemoveWmi() if (removeObject.Path != null) { mPath = new ManagementPath(removeObject.Path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = removeObject.Namespace; } @@ -752,11 +772,13 @@ private void ConnectRemoveWmi() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + if (!(mPath.Server == "." && removeObject.serverNameSpecified)) { _computerName = mPath.Server; } } + try { if (removeObject.Path != null) @@ -772,6 +794,7 @@ private void ConnectRemoveWmi() ManagementObject mInstance = new ManagementObject(mPath); mObject = mInstance; } + ManagementScope mScope = new ManagementScope(mPath, options); mObject.Scope = mScope; } @@ -782,6 +805,7 @@ private void ConnectRemoveWmi() mObject = mClass; mObject.Scope = scope; } + mObject.Delete(_results); } catch (ManagementException e) @@ -819,7 +843,7 @@ private void ConnectGetWMI() if (!getObject.ValidateClassFormat()) { ArgumentException e = new ArgumentException( - String.Format( + string.Format( Thread.CurrentThread.CurrentCulture, "Class", getObject.Class)); @@ -828,6 +852,7 @@ private void ConnectGetWMI() RaiseOperationCompleteEvent(null, OperationState.StopComplete); return; } + try { if (getObject.Recurse.IsPresent) @@ -851,6 +876,7 @@ private void ConnectGetWMI() namespaceArray.Add(connectNamespace + "\\" + obj["Name"]); } } + if (topNamespace) { topNamespace = false; @@ -860,6 +886,7 @@ private void ConnectGetWMI() { sinkArray.Add(_job.GetNewSink()); } + connectArray.Add(scope); currentNamespaceCount++; } @@ -882,6 +909,7 @@ private void ConnectGetWMI() currentNamespaceCount++; continue; } + if (topNamespace) { topNamespace = false; @@ -891,6 +919,7 @@ private void ConnectGetWMI() { searcher.Get((ManagementOperationObserver)sinkArray[currentNamespaceCount]); } + currentNamespaceCount++; } } @@ -922,8 +951,10 @@ private void ConnectGetWMI() _state = WmiState.Failed; RaiseOperationCompleteEvent(null, OperationState.StopComplete); } + return; } + string queryString = string.IsNullOrEmpty(getObject.Query) ? GetWmiQueryString() : getObject.Query; ObjectQuery query = new ObjectQuery(queryString.ToString()); try @@ -961,30 +992,30 @@ 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; } } /// - /// Enumerated type defining the state of the WMI operation + /// Enumerated type defining the state of the WMI operation. /// public enum WmiState { /// - /// The operation has not been started + /// The operation has not been started. /// NotStarted = 0, /// - /// The operation is executing + /// The operation is executing. /// Running = 1, /// @@ -1011,29 +1042,28 @@ 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(); } } #endregion Helper Classes - /// - /// A class to set WMI connection options + /// A class to set WMI connection options. /// public class WmiBaseCmdlet : Cmdlet { #region Parameters /// - /// Perform Async operation + /// Perform Async operation. /// [Parameter] public SwitchParameter AsJob { get; set; } = false; /// - /// The Impersonation level to use + /// The Impersonation level to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1043,7 +1073,7 @@ public class WmiBaseCmdlet : Cmdlet public ImpersonationLevel Impersonation { get; set; } = ImpersonationLevel.Impersonate; /// - /// The Authentication level to use + /// The Authentication level to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1053,7 +1083,7 @@ public class WmiBaseCmdlet : Cmdlet public AuthenticationLevel Authentication { get; set; } = AuthenticationLevel.PacketPrivacy; /// - /// The Locale to use + /// The Locale to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1063,7 +1093,7 @@ public class WmiBaseCmdlet : Cmdlet public string Locale { get; set; } = null; /// - /// If all Privileges are enabled + /// If all Privileges are enabled. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1073,7 +1103,7 @@ public class WmiBaseCmdlet : Cmdlet public SwitchParameter EnableAllPrivileges { get; set; } /// - /// The Authority to use + /// The Authority to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1083,24 +1113,24 @@ public class WmiBaseCmdlet : Cmdlet public string Authority { get; set; } = null; /// - /// The credential to use + /// The credential to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] [Parameter(ParameterSetName = "WQLQuery")] [Parameter(ParameterSetName = "query")] [Parameter(ParameterSetName = "list")] - [Credential()] + [Credential] public PSCredential Credential { get; set; } /// - /// The credential to use + /// The credential to use. /// [Parameter] public Int32 ThrottleLimit { get; set; } = s_DEFAULT_THROTTLE_LIMIT; /// - /// The ComputerName in which to query + /// The ComputerName in which to query. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1113,10 +1143,11 @@ public class WmiBaseCmdlet : Cmdlet public string[] ComputerName { get { return _computerName; } + set { _computerName = value; serverNameSpecified = true; } } /// - /// The WMI namespace to use + /// The WMI namespace to use. /// [Parameter(ParameterSetName = "path")] [Parameter(ParameterSetName = "class")] @@ -1127,6 +1158,7 @@ public string[] ComputerName public string Namespace { get { return _nameSpace; } + set { _nameSpace = value; namespaceSpecified = true; } } #endregion Parameters @@ -1137,7 +1169,7 @@ public string Namespace /// private string[] _computerName = new string[] { "localhost" }; /// - /// WMI namespace + /// WMI namespace. /// private string _nameSpace = "root\\cimv2"; /// @@ -1155,7 +1187,7 @@ public string Namespace #region Command code /// - /// Get connection options + /// Get connection options. /// internal ConnectionOptions GetConnectionOption() { @@ -1174,10 +1206,11 @@ internal ConnectionOptions GetConnectionOption() options.SecurePassword = this.Credential.Password; } } + return options; } /// - /// Set wmi instance helper + /// Set wmi instance helper. /// internal ManagementObject SetWmiInstanceGetObject(ManagementPath mPath, string serverName) { @@ -1198,7 +1231,7 @@ internal ManagementObject SetWmiInstanceGetObject(ManagementPath mPath, string s } else { - //This can throw if path does not exist caller should catch it. + // This can throw if path does not exist caller should catch it. ManagementObject mInstance = new ManagementObject(mPath); mInstance.Scope = mScope; try @@ -1211,23 +1244,26 @@ internal ManagementObject SetWmiInstanceGetObject(ManagementPath mPath, string s { throw; } + int namespaceIndex = setObject.Path.IndexOf(':'); if (namespaceIndex == -1) { throw; } + int classIndex = (setObject.Path.Substring(namespaceIndex)).IndexOf('.'); if (classIndex == -1) { throw; } - //Get class object and create instance. + // Get class object and create instance. string newPath = setObject.Path.Substring(0, classIndex + namespaceIndex); ManagementPath classPath = new ManagementPath(newPath); ManagementClass mClass = new ManagementClass(classPath); mClass.Scope = mScope; mInstance = mClass.CreateInstance(); } + mObject = mInstance; } } @@ -1238,6 +1274,7 @@ internal ManagementObject SetWmiInstanceGetObject(ManagementPath mPath, string s mClass.Scope = scope; mObject = mClass.CreateInstance(); } + if (setObject.Arguments != null) { IDictionaryEnumerator en = setObject.Arguments.GetEnumerator(); @@ -1247,10 +1284,11 @@ internal ManagementObject SetWmiInstanceGetObject(ManagementPath mPath, string s } } } + return mObject; } /// - /// Set wmi instance helper for building management path + /// Set wmi instance helper for building management path. /// internal ManagementPath SetWmiInstanceBuildManagementPath() { @@ -1258,30 +1296,31 @@ internal ManagementPath SetWmiInstanceBuildManagementPath() var wmiInstance = this as SetWmiInstance; if (wmiInstance != null) { - //If Class is specified only CreateOnly flag is supported + // If Class is specified only CreateOnly flag is supported if (wmiInstance.Class != null) { if (wmiInstance.flagSpecified && wmiInstance.PutType != PutType.CreateOnly) { - //Throw Terminating error + // Throw Terminating error ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "CreateOnlyFlagNotSpecifiedWithClassPath", ErrorCategory.InvalidOperation, wmiInstance.PutType)); } + wmiInstance.PutType = PutType.CreateOnly; } else { mPath = new ManagementPath(wmiInstance.Path); - if (String.IsNullOrEmpty(mPath.NamespacePath)) + if (string.IsNullOrEmpty(mPath.NamespacePath)) { mPath.NamespacePath = wmiInstance.Namespace; } else if (wmiInstance.namespaceSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "NamespaceSpecifiedWithPath", @@ -1291,24 +1330,26 @@ internal ManagementPath SetWmiInstanceBuildManagementPath() if (mPath.Server != "." && wmiInstance.serverNameSpecified) { - //ThrowTerminatingError + // ThrowTerminatingError ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "ComputerNameSpecifiedWithPath", ErrorCategory.InvalidOperation, wmiInstance.ComputerName)); } + if (mPath.IsClass) { if (wmiInstance.flagSpecified && wmiInstance.PutType != PutType.CreateOnly) { - //Throw Terminating error + // Throw Terminating error ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "CreateOnlyFlagNotSpecifiedWithClassPath", ErrorCategory.InvalidOperation, wmiInstance.PutType)); } + wmiInstance.PutType = PutType.CreateOnly; } else @@ -1317,7 +1358,7 @@ internal ManagementPath SetWmiInstanceBuildManagementPath() { if (!(wmiInstance.PutType == PutType.UpdateOnly || wmiInstance.PutType == PutType.UpdateOrCreate)) { - //Throw terminating error + // Throw terminating error ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "NonUpdateFlagSpecifiedWithInstancePath", @@ -1332,45 +1373,47 @@ internal ManagementPath SetWmiInstanceBuildManagementPath() } } } + return mPath; } /// - /// Set wmi instance helper for pipeline input + /// Set wmi instance helper for pipeline input. /// internal ManagementObject SetWmiInstanceGetPipelineObject() { - //Should only be called from Set-WMIInstance cmdlet + // Should only be called from Set-WMIInstance cmdlet ManagementObject mObj = null; var wmiInstance = this as SetWmiInstance; if (wmiInstance != null) { - //Extra check + // Extra check if (wmiInstance.InputObject != null) { if (wmiInstance.InputObject.GetType() == typeof(ManagementClass)) { - //Check if Flag specified is CreateOnly or not + // Check if Flag specified is CreateOnly or not if (wmiInstance.flagSpecified && wmiInstance.PutType != PutType.CreateOnly) { - //Throw terminating error + // Throw terminating error ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "CreateOnlyFlagNotSpecifiedWithClassPath", ErrorCategory.InvalidOperation, wmiInstance.PutType)); } + mObj = ((ManagementClass)wmiInstance.InputObject).CreateInstance(); wmiInstance.PutType = PutType.CreateOnly; } else { - //Check if Flag specified is Updateonly or UpdateOrCreateOnly or not + // Check if Flag specified is Updateonly or UpdateOrCreateOnly or not if (wmiInstance.flagSpecified) { if (!(wmiInstance.PutType == PutType.UpdateOnly || wmiInstance.PutType == PutType.UpdateOrCreate)) { - //Throw terminating error + // Throw terminating error ThrowTerminatingError(new ErrorRecord( new InvalidOperationException(), "NonUpdateFlagSpecifiedWithInstancePath", @@ -1385,6 +1428,7 @@ internal ManagementObject SetWmiInstanceGetPipelineObject() mObj = (ManagementObject)wmiInstance.InputObject.Clone(); } + if (wmiInstance.Arguments != null) { IDictionaryEnumerator en = wmiInstance.Arguments.GetEnumerator(); @@ -1395,6 +1439,7 @@ internal ManagementObject SetWmiInstanceGetPipelineObject() } } } + return mObj; } @@ -1408,6 +1453,7 @@ internal void RunAsJob(string cmdletName) { ((System.Management.Automation.Runspaces.LocalRunspace)_context.CurrentRunspace).JobRepository.Add(wmiJob); } + WriteObject(wmiJob); } // Get the PowerShell execution context if it's available at cmdlet creation time... @@ -1416,7 +1462,7 @@ internal void RunAsJob(string cmdletName) #endregion Command code } /// - /// A class to perform async operations for WMI cmdlets + /// A class to perform async operations for WMI cmdlets. /// internal class PSWmiJob : Job @@ -1424,7 +1470,7 @@ internal class PSWmiJob : Job #region internal constructor /// - ///Internal constructor for initializing WMI jobs + ///Internal constructor for initializing WMI jobs. /// internal PSWmiJob(Cmdlet cmds, string[] computerName, int throttleLimt, string command) : base(command, null) @@ -1438,6 +1484,7 @@ internal PSWmiJob(Cmdlet cmds, string[] computerName, int throttleLimt, string c job.JobUnblocked += new EventHandler(HandleJobUnblocked); ChildJobs.Add(job); } + CommonInit(throttleLimt); } @@ -1476,7 +1523,7 @@ internal PSWmiJob(Cmdlet cmds, string[] computerName, int throttleLimit, string private const string WMIJobType = "WmiJob"; /// - /// Handles the StateChanged event from each of the child job objects + /// Handles the StateChanged event from each of the child job objects. /// /// /// @@ -1494,14 +1541,15 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) return; } - //Ignore state changes which are not resulting in state change to finished. + // Ignore state changes which are not resulting in state change to finished. if ((!IsFinishedState(e.JobStateInfo.State)) || (e.JobStateInfo.State == JobState.NotStarted)) { return; } + if (e.JobStateInfo.State == JobState.Failed) { - //If any of the child job failed, we set status to failed + // If any of the child job failed, we set status to failed _atleastOneChildJobFailed = true; } @@ -1510,17 +1558,18 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) { _finishedChildJobsCount++; - //We are done + // We are done if (_finishedChildJobsCount == ChildJobs.Count) { allChildJobsFinished = true; } } + if (allChildJobsFinished) { - //if any child job failed, set status to failed - //If stop was called set, status to stopped - //else completed + // if any child job failed, set status to failed + // If stop was called set, status to stopped + // else completed if (_atleastOneChildJobFailed) { SetJobState(JobState.Failed); @@ -1539,7 +1588,7 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) private bool _stopIsCalled = false; private string _statusMessage; /// - /// Message indicating status of the job + /// Message indicating status of the job. /// public override string StatusMessage { @@ -1548,19 +1597,19 @@ public override string StatusMessage return _statusMessage; } } - //ISSUE: Implement StatusMessage + // ISSUE: Implement StatusMessage /// - /// Checks the status of remote command execution + /// Checks the status of remote command execution. /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] private void SetStatusMessage() { _statusMessage = "test"; - } // SetStatusMessage + } private bool _moreData = false; /// - /// indicates if more data is available + /// Indicates if more data is available. /// /// /// This has more data if any of the child jobs have more data. @@ -1570,10 +1619,10 @@ public override bool HasMoreData get { // moreData is set to false and will be set to true - //if at least one child is has more data. + // if at least one child is has more data. - //if ( (!moreData)) - //{ + // if ( (!moreData)) + // { bool atleastOneChildHasMoreData = false; for (int i = 0; i < ChildJobs.Count; i++) @@ -1586,14 +1635,14 @@ public override bool HasMoreData } _moreData = atleastOneChildHasMoreData; - //} + // } return _moreData; } } /// - /// Computers on which this job is running + /// Computers on which this job is running. /// public override string Location { @@ -1602,25 +1651,27 @@ public override string Location return ConstructLocation(); } } - private String ConstructLocation() + + private string ConstructLocation() { StringBuilder location = new StringBuilder(); foreach (PSWmiChildJob job in ChildJobs) { location.Append(job.Location); - location.Append(","); + location.Append(','); } + location.Remove(location.Length - 1, 1); return location.ToString(); } /// - /// Stop Job + /// Stop Job. /// public override void StopJob() { - //AssertNotDisposed(); + // AssertNotDisposed(); if (!IsFinishedState(JobStateInfo.State)) { @@ -1650,6 +1701,7 @@ protected override void Dispose(bool disposing) { StopJob(); } + _throttleManager.Dispose(); foreach (Job job in ChildJobs) { @@ -1666,12 +1718,12 @@ protected override void Dispose(bool disposing) private bool _isDisposed = false; /// - /// Initialization common to both constructors + /// Initialization common to both constructors. /// private void CommonInit(int throttleLimit) { - //Since no results are produced by any streams. We should - //close all the streams + // Since no results are produced by any streams. We should + // close all the streams base.CloseAllStreams(); // set status to "in progress" @@ -1683,9 +1735,9 @@ private void CommonInit(int throttleLimit) /// /// Handles JobUnblocked event from a child job and decrements /// count of blocked child jobs. When count reaches 0, sets the - /// state of the parent job to running + /// state of the parent job to running. /// - /// sender of this event, unused + /// Sender of this event, unused. /// event arguments, should be empty in this /// case private void HandleJobUnblocked(object sender, EventArgs eventArgs) @@ -1708,22 +1760,20 @@ private void HandleJobUnblocked(object sender, EventArgs eventArgs) } } - - private ThrottleManager _throttleManager = new ThrottleManager(); private object _syncObject = new object(); // sync object } /// - /// Class for WmiChildJob object. This job object Execute wmi cmdlet + /// Class for WmiChildJob object. This job object Execute wmi cmdlet. /// internal class PSWmiChildJob : Job { #region internal constructor /// - /// Internal constructor for initializing WMI jobs + /// Internal constructor for initializing WMI jobs. /// internal PSWmiChildJob(Cmdlet cmds, string computerName, ThrottleManager throttleManager) : base(null, null) @@ -1776,25 +1826,24 @@ internal PSWmiChildJob(Cmdlet cmds, string computerName, ThrottleManager throttl #endregion internal constructor private WmiAsyncCmdletHelper _helper; - //bool _bFinished; + // bool _bFinished; private ThrottleManager _throttleManager; private object _syncObject = new object(); // sync object private int _sinkCompleted; private bool _bJobFailed; private bool _bAtLeastOneObject; - private ArrayList _wmiSinkArray; /// /// Event raised by this job to indicate to its parent that - /// its now unblocked by the user + /// its now unblocked by the user. /// internal event EventHandler JobUnblocked; /// /// Set the state of the current job from blocked to /// running and raise an event indicating to this - /// parent job that this job is unblocked + /// parent job that this job is unblocked. /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void UnblockJob() @@ -1802,6 +1851,7 @@ internal void UnblockJob() SetJobState(JobState.Running, null); JobUnblocked.SafeInvoke(this, EventArgs.Empty); } + internal ManagementOperationObserver GetNewSink() { ManagementOperationObserver wmiSink = new ManagementOperationObserver(); @@ -1810,13 +1860,14 @@ internal ManagementOperationObserver GetNewSink() { _sinkCompleted++; } + wmiSink.ObjectReady += new ObjectReadyEventHandler(this.NewObject); wmiSink.Completed += new CompletedEventHandler(this.JobDone); return wmiSink; } /// - /// it receives Management objects + /// It receives Management objects. /// private void NewObject(object sender, ObjectReadyEventArgs obj) { @@ -1824,6 +1875,7 @@ private void NewObject(object sender, ObjectReadyEventArgs obj) { _bAtLeastOneObject = true; } + this.WriteObject(obj.NewObject); } @@ -1836,14 +1888,16 @@ private void JobDone(object sender, CompletedEventArgs obj) { _sinkCompleted--; } + if (obj.Status != ManagementStatus.NoError) { _bJobFailed = true; } + if (_sinkCompleted == 0) { - //Notify throttle manager and change the state to complete - //Two cases where _bFinished should be set to false. + // Notify throttle manager and change the state to complete + // Two cases where _bFinished should be set to false. // 1) Invalid class or some other condition so that after making a connection WMI is throwing an error // 2) We could not get any instance for the class. /*if(bAtLeastOneObject ) @@ -1863,7 +1917,7 @@ private void JobDone(object sender, CompletedEventArgs obj) } /// - /// It is called when the call to Win32shutdown is successfully completed + /// It is called when the call to Win32shutdown is successfully completed. /// private void JobDoneForWin32Shutdown(object sender, EventArgs arg) { @@ -1871,6 +1925,7 @@ private void JobDoneForWin32Shutdown(object sender, EventArgs arg) { _sinkCompleted--; } + if (_sinkCompleted == 0) { _helper.RaiseOperationCompleteEvent(null, OperationState.StopComplete); @@ -1880,14 +1935,13 @@ private void JobDoneForWin32Shutdown(object sender, EventArgs arg) } /// - /// Message indicating status of the job + /// Message indicating status of the job. /// public override string StatusMessage { get; } = "test"; - /// /// Indicates if there is more data available in - /// this Job + /// this Job. /// public override bool HasMoreData { @@ -1899,12 +1953,12 @@ public override bool HasMoreData /// /// Returns the computer on which this command is - /// running + /// running. /// public override string Location { get; } /// - /// Stops the job + /// Stops the job. /// public override void StopJob() { @@ -1936,10 +1990,11 @@ protected override void Dispose(bool disposing) } } } + private bool _isDisposed; /// - /// Handles operation complete event + /// Handles operation complete event. /// private void HandleOperationComplete(object sender, OperationStateEventArgs stateEventArgs) { @@ -1947,7 +2002,7 @@ private void HandleOperationComplete(object sender, OperationStateEventArgs stat if (helper.State == WmiState.NotStarted) { - //This is a case WMI operation was not started. + // This is a case WMI operation was not started. SetJobState(JobState.Stopped, helper.InternalException); } else if (helper.State == WmiState.Running) @@ -1968,7 +2023,7 @@ private void HandleOperationComplete(object sender, OperationStateEventArgs stat } } /// - /// Handles WMI state changed + /// Handles WMI state changed. /// private void HandleWMIState(object sender, WmiJobStateEventArgs stateEventArgs) { @@ -1995,15 +2050,15 @@ private void HandleWMIState(object sender, WmiJobStateEventArgs stateEventArgs) } /// - /// Handle a throttle complete event + /// Handle a throttle complete event. /// - /// sender of this event - /// not used in this method + /// Sender of this event. + /// Not used in this method. private void HandleThrottleComplete(object sender, EventArgs eventArgs) { if (_helper.State == WmiState.NotStarted) { - //This is a case WMI operation was not started. + // This is a case WMI operation was not started. SetJobState(JobState.Stopped, _helper.InternalException); } else if (_helper.State == WmiState.Running) @@ -2022,7 +2077,7 @@ private void HandleThrottleComplete(object sender, EventArgs eventArgs) { SetJobState(JobState.Stopped, _helper.InternalException); } - //Do Nothing - } // HandleThrottleComplete + // Do Nothing + } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs index 7ddb52ec825..3f91d597e57 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs @@ -1,39 +1,39 @@ -/********************************************************************-- -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.IO; -using System.Net; -using System.Xml; -using System.Text; using System.CodeDom; using System.CodeDom.Compiler; -using System.Web.Services; -using System.Web.Services.Description; -using System.Web.Services.Discovery; -using System.Management; -using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using Microsoft.CSharp; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using Dbg = System.Management.Automation; -using System.Runtime.InteropServices; +using System.IO; +using System.Management; +using System.Management.Automation; +using System.Net; +using System.Reflection; using System.Resources; -using Microsoft.Win32; +using System.Runtime.InteropServices; +using System.Text; using System.Text.RegularExpressions; +using System.Web.Services; +using System.Web.Services.Description; +using System.Web.Services.Discovery; +using System.Xml; +using Microsoft.CSharp; +using Microsoft.Win32; + +using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { #region New-WebServiceProxy /// - /// Cmdlet for new-WebService Proxy + /// Cmdlet for new-WebService Proxy. /// [Cmdlet(VerbsCommon.New, "WebServiceProxy", DefaultParameterSetName = "NoCredentials", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135238")] public sealed class NewWebServiceProxy : PSCmdlet @@ -41,7 +41,7 @@ public sealed class NewWebServiceProxy : PSCmdlet #region Parameters /// - /// URI of the web service + /// URI of the web service. /// [Parameter(Mandatory = true, Position = 0)] [ValidateNotNullOrEmpty] @@ -49,15 +49,17 @@ public sealed class NewWebServiceProxy : PSCmdlet public System.Uri Uri { get { return _uri; } + set { _uri = value; } } + private System.Uri _uri; /// - /// Parameter Class name + /// Parameter Class name. /// [Parameter(Position = 1)] [ValidateNotNullOrEmpty] @@ -65,15 +67,17 @@ public System.Uri Uri public string Class { get { return _class; } + set { _class = value; } } + private string _class; /// - /// namespace + /// Namespace. /// [Parameter(Position = 2)] [ValidateNotNullOrEmpty] @@ -81,15 +85,17 @@ public string Class public string Namespace { get { return _namespace; } + set { _namespace = value; } } + private string _namespace; /// - /// Credential + /// Credential. /// [Parameter(ParameterSetName = "Credential")] [ValidateNotNullOrEmpty] @@ -98,16 +104,17 @@ public string Namespace public PSCredential Credential { get { return _credential; } + set { _credential = value; } } - private PSCredential _credential; + private PSCredential _credential; /// - /// use default credential.. + /// Use default credential.. /// [Parameter(ParameterSetName = "UseDefaultCredential")] [ValidateNotNull] @@ -115,11 +122,13 @@ public PSCredential Credential public SwitchParameter UseDefaultCredential { get { return _usedefaultcredential; } + set { _usedefaultcredential = value; } } + private SwitchParameter _usedefaultcredential; #endregion @@ -136,17 +145,17 @@ public SwitchParameter UseDefaultCredential private static Dictionary s_srccodeCache = new Dictionary(); /// - /// holds the hash code of the source generated. + /// Holds the hash code of the source generated. /// private int _sourceHash; /// - /// Random class + /// Random class. /// private object _cachelock = new object(); private static Random s_rnd = new Random(); /// - /// BeginProcessing code + /// BeginProcessing code. /// protected override void BeginProcessing() { @@ -156,7 +165,7 @@ protected override void BeginProcessing() ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } - //check if system.web is available.This assembly is not available in win server core. + // check if system.web is available.This assembly is not available in win server core. string AssemblyString = "System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; try { @@ -175,7 +184,7 @@ protected override void BeginProcessing() { if (s_uriCache.ContainsKey(_uri)) { - //if uri is present in the cache + // if uri is present in the cache string ns; s_uriCache.TryGetValue(_uri, out ns); string[] data = ns.Split('|'); @@ -187,14 +196,16 @@ protected override void BeginProcessing() _class = data[1]; } } + sourceCache = Int32.Parse(data[2].ToString(), CultureInfo.InvariantCulture); } } + if (string.IsNullOrEmpty(_namespace)) { _namespace = "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy" + GenerateRandomName(); } - //if class is null,generate a name for it + // if class is null,generate a name for it if (string.IsNullOrEmpty(_class)) { _class = "MyClass" + GenerateRandomName(); @@ -203,9 +214,9 @@ protected override void BeginProcessing() Assembly webserviceproxy = GenerateWebServiceProxyAssembly(_namespace, _class); if (webserviceproxy == null) return; - Object instance = InstantiateWebServiceProxy(webserviceproxy); + object instance = InstantiateWebServiceProxy(webserviceproxy); - //to set the credentials into the generated webproxy Object + // to set the credentials into the generated webproxy Object PropertyInfo[] pinfo = instance.GetType().GetProperties(); foreach (PropertyInfo pr in pinfo) { @@ -217,6 +228,7 @@ protected override void BeginProcessing() pr.SetValue(instance, flag as object, null); } } + if (pr.Name.Equals("Credentials", StringComparison.OrdinalIgnoreCase)) { if (Credential != null) @@ -227,12 +239,13 @@ protected override void BeginProcessing() } } - //disposing the entries in a cache - //Adding to Cache + // disposing the entries in a cache + // Adding to Cache lock (s_uriCache) { s_uriCache.Remove(_uri); } + if (sourceCache > 0) { lock (_cachelock) @@ -240,18 +253,20 @@ protected override void BeginProcessing() s_srccodeCache.Remove(sourceCache); } } + string key = string.Join("|", new string[] { _namespace, _class, _sourceHash.ToString(System.Globalization.CultureInfo.InvariantCulture) }); lock (s_uriCache) { s_uriCache.Add(_uri, key); } + lock (_cachelock) { s_srccodeCache.Add(_sourceHash, instance); } WriteObject(instance, true); - }//End BeginProcessing() + } #endregion @@ -261,9 +276,9 @@ protected override void BeginProcessing() private static object s_sequenceNumberLock = new object(); /// - /// Generates a random name + /// Generates a random name. /// - /// string + /// String. private string GenerateRandomName() { string rndname = null; @@ -291,11 +306,12 @@ private string GenerateRandomName() { return (sequenceString + rndname.Substring(rndname.Length - 30)); } + return (sequenceString + rndname); } /// - /// Generates the Assembly + /// Generates the Assembly. /// /// /// @@ -305,11 +321,11 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN { DiscoveryClientProtocol dcp = new DiscoveryClientProtocol(); - //if paramset is defaultcredential, set the flag in wcclient + // if paramset is defaultcredential, set the flag in wcclient if (_usedefaultcredential.IsPresent) dcp.UseDefaultCredentials = true; - //if paramset is credential, assign the credentials + // if paramset is credential, assign the credentials if (ParameterSetName.Equals("Credential", StringComparison.OrdinalIgnoreCase)) dcp.Credentials = _credential.GetNetworkCredential(); @@ -339,7 +355,7 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN if (!string.IsNullOrEmpty(NameSpace)) codeNS.Name = NameSpace; - //create the class and add it to the namespace + // create the class and add it to the namespace if (!string.IsNullOrEmpty(ClassName)) { CodeTypeDeclaration codeClass = new CodeTypeDeclaration(ClassName); @@ -348,12 +364,12 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN codeNS.Types.Add(codeClass); } - //create a web reference to the uri docs + // create a web reference to the uri docs WebReference wref = new WebReference(dcp.Documents, codeNS); WebReferenceCollection wrefs = new WebReferenceCollection(); wrefs.Add(wref); - //create a codecompileunit and add the namespace to it + // create a codecompileunit and add the namespace to it CodeCompileUnit codecompileunit = new CodeCompileUnit(); codecompileunit.Namespaces.Add(codeNS); @@ -361,7 +377,7 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN wrefOptions.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateNewAsync | System.Xml.Serialization.CodeGenerationOptions.GenerateOldAsync | System.Xml.Serialization.CodeGenerationOptions.GenerateProperties; wrefOptions.Verbose = true; - //create a csharpprovider and compile it + // create a csharpprovider and compile it CSharpCodeProvider csharpprovider = new CSharpCodeProvider(); StringCollection Warnings = ServiceDescriptionImporter.GenerateWebReferences(wrefs, csharpprovider, codecompileunit, wrefOptions); @@ -376,10 +392,10 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN ErrorRecord er = new ErrorRecord(ex, "NotImplementedException", ErrorCategory.ObjectNotFound, _uri); WriteError(er); } - //generate the hashcode of the CodeCompileUnit + // generate the hashcode of the CodeCompileUnit _sourceHash = codegenerator.ToString().GetHashCode(); - //if the sourcehash matches the hashcode in the cache,the proxy hasnt changed and so + // if the sourcehash matches the hashcode in the cache,the proxy hasnt changed and so // return the instance of th eproxy in the cache if (s_srccodeCache.ContainsKey(_sourceHash)) { @@ -388,6 +404,7 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN WriteObject(obj, true); return null; } + CompilerParameters options = new CompilerParameters(); CompilerResults results = null; @@ -421,7 +438,7 @@ private Assembly GenerateWebServiceProxyAssembly(string NameSpace, string ClassN } /// - /// Function to add all the assemblies required to generate the web proxy + /// Function to add all the assemblies required to generate the web proxy. /// /// /// @@ -440,24 +457,28 @@ private void GetReferencedAssemblies(Assembly assembly, CompilerParameters param } /// /// Instantiates the object - /// if a type of WebServiceBindingAttribute is not found, throw an exception + /// if a type of WebServiceBindingAttribute is not found, throw an exception. /// /// /// private object InstantiateWebServiceProxy(Assembly assembly) { Type proxyType = null; - //loop through the types of the assembly and identify the type having + // loop through the types of the assembly and identify the type having // a web service binding attribute foreach (Type type in assembly.GetTypes()) { - Object[] obj = type.GetCustomAttributes(typeof(WebServiceBindingAttribute), false); + object[] obj = type.GetCustomAttributes(typeof(WebServiceBindingAttribute), false); if (obj.Length > 0) { proxyType = type; break; } - if (proxyType != null) break; + + if (proxyType != null) + { + break; + } } System.Management.Automation.Diagnostics.Assert( @@ -468,6 +489,6 @@ private object InstantiateWebServiceProxy(Assembly assembly) } #endregion - }//end class + } #endregion -}//Microsoft.Powershell.commands +} diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs index ec5d50ab3bf..a2a6387e9da 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -8,7 +7,6 @@ using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Provider; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -22,11 +20,9 @@ public class WriteContentCommandBase : PassThroughContentCommandBase /// /// The value of the content to set. /// - /// /// /// This value type is determined by the InvokeProvider. /// - /// [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [AllowNull] [AllowEmptyCollection] @@ -35,13 +31,13 @@ public object[] Value get { return _content; - } // get + } set { _content = value; } - } // Value + } #endregion Parameters @@ -60,7 +56,6 @@ public object[] Value /// This bool is used to determine if the path /// parameter was specified on the command line or via the pipeline. /// - /// private bool _pipingPaths; /// @@ -77,7 +72,7 @@ public object[] Value /// /// Determines if the paths are specified on the command line - /// or being piped in + /// or being piped in. /// protected override void BeginProcessing() { @@ -100,10 +95,7 @@ protected override void ProcessRecord() // Initialize the content - if (_content == null) - { - _content = new object[0]; - } + _content ??= Array.Empty(); if (_pipingPaths) { @@ -148,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, @@ -188,16 +180,15 @@ protected override void ProcessRecord() contentStreams = new List(); } } - } // ProcessRecord + } /// - /// Closes all the content writers + /// Closes all the content writers. /// - /// protected override void EndProcessing() { Dispose(true); - } // EndProcessing + } #endregion Command code @@ -208,24 +199,20 @@ protected override void EndProcessing() /// from the provider. If the current position needs to be changed before writing /// the content, this method should be overridden to do that. /// - /// /// /// The content holders that contain the writers to be moved. /// - /// internal virtual void SeekContentPosition(List contentHolders) { // default does nothing. - } // SeekContentPosition + } /// /// Called by the base class before the streams are open for the path. /// - /// /// /// The path to the items that will be opened for writing content. /// - /// internal virtual void BeforeOpenStreams(string[] paths) { } @@ -235,33 +222,29 @@ internal virtual void BeforeOpenStreams(string[] paths) /// that require dynamic parameters should override this method and return the /// dynamic parameter object. /// - /// /// /// The context under which the command is running. /// - /// /// /// An object representing the dynamic parameters for the cmdlet or null if there /// are none. /// - /// internal override object GetDynamicParameters(CmdletProviderContext context) { if (Path != null && Path.Length > 0) { return InvokeProvider.Content.GetContentWriterDynamicParameters(Path[0], context); } + return InvokeProvider.Content.GetContentWriterDynamicParameters(".", context); } /// /// Gets the IContentWriters for the current path(s) /// - /// /// /// An array of IContentWriters for the current path(s) /// - /// internal List GetContentWriters( string[] writerPaths, CmdletProviderContext currentCommandContext) @@ -272,7 +255,7 @@ internal List GetContentWriters( // Create the results array - List results = new List(); + List results = new(); foreach (PathInfo pathInfo in pathInfos) { @@ -325,27 +308,27 @@ 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); } } - } // foreach pathInfo in pathInfos + } return results; - } // GetContentWriters + } /// - /// Gets the list of paths accepted by the user + /// Gets the list of paths accepted by the user. /// - /// The list of unfiltered paths - /// The current context - /// The list of paths accepted by the user + /// The list of unfiltered paths. + /// The current context. + /// The list of paths accepted by the user. private string[] GetAcceptedPaths(string[] unfilteredPaths, CmdletProviderContext currentContext) { Collection pathInfos = ResolvePaths(unfilteredPaths, true, false, currentContext); - ArrayList paths = new ArrayList(); + var paths = new List(); foreach (PathInfo pathInfo in pathInfos) { @@ -355,9 +338,9 @@ private string[] GetAcceptedPaths(string[] unfilteredPaths, CmdletProviderContex } } - return (string[])paths.ToArray(typeof(String)); + return paths.ToArray(); } #endregion protected members - } // WriteContentCommandBase -} // namespace Microsoft.PowerShell.Commands + } +} diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx index e306a959940..9e6cbd55cc1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ClipboardResources.resx @@ -131,4 +131,22 @@ Html can only be combined with Html Text format. + + Only Text format is supported on this platform. + + + The clipboard is not supported on this platform. + + + The '-AsHtml' switch is not supported on this platform. + + + The '-TextFormatType' parameter only supports 'Text' on this platform. + + + The '-Path' parameter is not supported on this platform. + + + The '-LiteralPath' parameter is not supported on this platform. + diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/CmdletizationResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/CmdletizationResources.resx index 1a25e938a38..15bfadffe7b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/CmdletizationResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/CmdletizationResources.resx @@ -124,10 +124,6 @@ {1} is a placeholder for a server name. Example: "localhost". - - ..\..\..\..\src\cimSupport\cmdletization\xml\cmdlets-over-objects.xsd;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - {Locked} - CIM method {1} on the {0} CIM object {0} is a placeholder for a CIM path. Example: \\SERVER1\ROOT\cimv2:Win32_Process.Handle="11828" diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx index 87bc01c6da2..95685239fd7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ComputerResources.resx @@ -153,9 +153,6 @@ The command cannot locate the "{0}" restore point. Verify the "{0}" sequence number, and then try the command again. - - Testing connection to computer '{0}' failed: {1} - {0} ({1}) @@ -390,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/HotFixResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/HotFixResources.resx index 9002dddc00c..a058ae71a11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/HotFixResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/HotFixResources.resx @@ -1,4 +1,4 @@ - + @@ -132,9 +132,6 @@ Cannot stop service '{1} ({0})' because it has dependent services. - - Cannot stop service '{1} ({0})' because it is dependent on other services. - Service '{1} ({0})' cannot be stopped due to the following error: {2} @@ -162,15 +159,15 @@ 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} Service '{1} ({0})' automatic (delayed start) cannot be configured due to the following error: {2} + + Service '{0}' security descriptor cannot be configured due to the following error: {1} + Service '{1} ({0})' cannot be created due to the following error: {2} @@ -182,7 +179,7 @@ Service '{1} ({0})' cannot be removed due to the following error: {2} - + 'Cannot access dependent services of '{1} ({0})' @@ -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/resources/TestConnectionResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx new file mode 100644 index 00000000000..ddd4ba8f815 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TestConnectionResources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Testing connection to computer '{0}' failed: {1} + + + Cannot resolve the target name. + + + Target IPv4/IPv6 address absent. + + + Cannot complete traceroute to destination '{0}': Number of hops required to reach host exceeds MaxHops ({1}). + + diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/TestPathResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TestPathResources.resx new file mode 100644 index 00000000000..c60c4318a64 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TestPathResources.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 provided Path argument was null or an empty collection. + + diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/TransactionResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/TransactionResources.resx index d319d696faf..9d92573d86d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/TransactionResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/TransactionResources.resx @@ -1,4 +1,4 @@ - + - [Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommand.RandomNumberParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113446", RemotingCapability = RemotingCapability.None)] - [OutputType(typeof(Int32), typeof(Int64), typeof(Double))] - public class GetRandomCommand : PSCmdlet + [Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommandBase.RandomNumberParameterSet, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097016", RemotingCapability = RemotingCapability.None)] + [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 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 ((this.MyInvocation.ExpectingInput) && (this.Maximum == null) && (this.Minimum == null)) - { - _effectiveParameterSet = MyParameterSet.RandomListItem; - } - else if (ParameterSetName.Equals(GetRandomCommand.RandomListItemParameterSet, StringComparison.OrdinalIgnoreCase)) - { - _effectiveParameterSet = MyParameterSet.RandomListItem; - } - else if (this.ParameterSetName.Equals(GetRandomCommand.RandomNumberParameterSet, StringComparison.OrdinalIgnoreCase)) - { - if ((this.Maximum != null) && (this.Maximum.GetType().IsArray)) - { - this.InputObject = (object[])this.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 min, object max) - { - if (min == null) - { - throw PSTraceSource.NewArgumentNullException("min"); - } - - if (max == null) - { - throw PSTraceSource.NewArgumentNullException("max"); - } - - ErrorRecord errorRecord = new ErrorRecord( - new ArgumentException(String.Format( - CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, min, max)), - "MinGreaterThanOrEqualMax", - ErrorCategory.InvalidArgument, - null); - - this.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 = this.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) - { - this.Generator = new PolymorphicRandomNumberGenerator(); - } - } - - return _generator; - } - set - { - _generator = value; - Runspace myRunspace = this.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 + /// Seed used to reinitialize random numbers generator. /// [Parameter] [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)] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public object[] InputObject { get; set; } - - /// - /// Number of items to output (number of list items or of numbers) - /// - [Parameter(ParameterSetName = GetRandomCommand.RandomListItemParameterSet)] - [ValidateRange(1, int.MaxValue)] - public int Count { get; set; } - - #endregion - - #region Cmdlet processing methods - - private double GetRandomDouble(double min, double max) - { - double randomNumber; - double diff = max - min; - - // 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 = this.Generator.NextDouble(); - randomNumber = min + r * max - r * min; - } - while (randomNumber >= max); - } - else - { - do - { - double r = this.Generator.NextDouble(); - randomNumber = min + r * diff; - diff = diff * r; - } - while (randomNumber >= max); - } - - return randomNumber; - } - - /// - /// Get a random Int64 type number - /// - /// - /// - /// - private Int64 GetRandomInt64(Int64 min, Int64 max) - { - // Randomly generate eight bytes and convert the byte array to UInt64 - var buffer = new byte[sizeof(UInt64)]; - UInt64 randomUint64; - - BigInteger bigIntegerDiff = (BigInteger)max - (BigInteger)min; - - // When the difference is less than int.MaxValue, use Random.Next(int, int) - if (bigIntegerDiff <= int.MaxValue) - { - int randomDiff = this.Generator.Next(0, (int)(max - min)); - return min + 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 - this.Generator.NextBytes(buffer); - randomUint64 = BitConverter.ToUInt64(buffer, 0); - // Get the last 'bitsToRepresentDiff' number of randon bits - randomUint64 &= mask; - } while (uint64Diff <= randomUint64); - - double result = min * 1.0 + randomUint64 * 1.0; - return (Int64)result; - } - /// - /// This method implements the BeginProcessing method for get-random command + /// This method implements the BeginProcessing method for get-random command. /// protected override void BeginProcessing() { - if (this.SetSeed.HasValue) - { - this.Generator = new PolymorphicRandomNumberGenerator(this.SetSeed.Value); - } - - if (this.EffectiveParameterSet == MyParameterSet.RandomNumber) - { - object maxOperand = ProcessOperand(this.Maximum); - object minOperand = ProcessOperand(this.Minimum); - - if (IsInt(maxOperand) && IsInt(minOperand)) - { - int min = minOperand != null ? (int)minOperand : 0; - int max = maxOperand != null ? (int)maxOperand : int.MaxValue; - - if (min >= max) - { - this.ThrowMinGreaterThanOrEqualMax(min, max); - } - - int randomNumber = this.Generator.Next(min, max); - Debug.Assert(min <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < max, "random number < upper bound"); - - this.WriteObject(randomNumber); - } - else if ((IsInt64(maxOperand) || IsInt(maxOperand)) && (IsInt64(minOperand) || IsInt(minOperand))) - { - Int64 min = minOperand != null ? ((minOperand is Int64) ? (Int64)minOperand : (int)minOperand) : 0; - Int64 max = maxOperand != null ? ((maxOperand is Int64) ? (Int64)maxOperand : (int)maxOperand) : Int64.MaxValue; - - if (min >= max) - { - this.ThrowMinGreaterThanOrEqualMax(min, max); - } - - Int64 randomNumber = this.GetRandomInt64(min, max); - Debug.Assert(min <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < max, "random number < upper bound"); - - this.WriteObject(randomNumber); - } - else - { - double min = (minOperand is double) ? (double)minOperand : this.ConvertToDouble(this.Minimum, 0.0); - double max = (maxOperand is double) ? (double)maxOperand : this.ConvertToDouble(this.Maximum, double.MaxValue); - - if (min >= max) - { - this.ThrowMinGreaterThanOrEqualMax(min, max); - } - - double randomNumber = this.GetRandomDouble(min, max); - Debug.Assert(min <= randomNumber, "lower bound <= random number"); - Debug.Assert(randomNumber < max, "random number < upper bound"); - - this.WriteObject(randomNumber); - } - } - else if (this.EffectiveParameterSet == MyParameterSet.RandomListItem) - { - _chosenListItems = new List(); - _numberOfProcessedListItems = 0; - - if (this.Count == 0) // -Count not specified - { - this.Count = 1; // default to one random item by default - } - } - } - - // 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 this.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 this.chosenListItems - // (see (1) in the code below) - // B - probability that a particular element from this.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 this.chosenListItems - // (see (1) in the code below) - // - // probability that a particular item in this.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 this.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 (this.EffectiveParameterSet == MyParameterSet.RandomListItem) - { - foreach (object item in this.InputObject) - { - if (_numberOfProcessedListItems < this.Count) // (3) - { - Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in this.chosenListItems"); - _chosenListItems.Add(item); - } - else - { - Debug.Assert(_chosenListItems.Count == this.Count, "After processing K initial elements, the length of this.chosenItems should stay equal to K"); - if (this.Generator.Next(_numberOfProcessedListItems + 1) < this.Count) // (1) - { - int indexToReplace = this.Generator.Next(_chosenListItems.Count); // (2) - _chosenListItems[indexToReplace] = item; - } - } - - _numberOfProcessedListItems++; - } - } - } - - /// - /// This method implements the EndProcessing method for get-random command - /// - protected override void EndProcessing() - { - if (this.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 an item to go into the i-th position - int j = this.Generator.Next(i, n); - - // swap j-th item into i-th position - if (i != j) - { - object tmp = _chosenListItems[i]; - _chosenListItems[i] = _chosenListItems[j]; - _chosenListItems[j] = tmp; - } - } - - // output all items - foreach (object chosenItem in _chosenListItems) - { - this.WriteObject(chosenItem); - } - } - } - - #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: - /// http://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 result; - - // The CLR implementation just fudges - // Int32.MaxValue down to (Int32.MaxValue - 1). This implementation - // errs on the side of correctness. - do - { - result = InternalSample(); - } - while (result == Int32.MaxValue); - - if (result < 0) + if (SetSeed.HasValue) { - result += Int32.MaxValue; + Generator = new PolymorphicRandomNumberGenerator(SetSeed.Value); } - return result; - } - - /// - /// 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("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("minValue", GetRandomCommandStrings.MinGreaterThanOrEqualMaxApi); - } - - long range = (long)maxValue - (long)minValue; - if (range <= int.MaxValue) - { - return ((int)(NextDouble() * range) + minValue); - } - else - { - double largeSample = InternalSampleLargeRange() * (1.0 / (2 * ((uint)Int32.MaxValue))); - int result = (int)((long)(largeSample * range) + minValue); - - return result; - } - } - - /// - /// 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 result; - byte[] data = new byte[sizeof(int)]; - - NextBytes(data); - result = BitConverter.ToInt32(data, 0); - - return result; - } - - /// - /// 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 result; - - do - { - result = InternalSample(); - } while (result == Int32.MaxValue); - - result += Int32.MaxValue; - return result; + 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 65fc5cb7be9..10c050a4562 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRunspaceCommand.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell.Commands /// This cmdlet returns runspaces in the PowerShell session. /// [Cmdlet(VerbsCommon.Get, "Runspace", DefaultParameterSetName = GetRunspaceCommand.NameParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=403730")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096616")] [OutputType(typeof(Runspace))] public sealed class GetRunspaceCommand : PSCmdlet { @@ -73,7 +73,7 @@ public Guid[] InstanceId #region Overrides /// - /// Process record + /// Process record. /// protected override void ProcessRecord() { @@ -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/GetUICultureCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUICultureCommand.cs index 6f8693e4b35..53e8c5d3adf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUICultureCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUICultureCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; @@ -9,18 +8,16 @@ namespace Microsoft.PowerShell.Commands /// /// Returns the thread's current UI culture. /// - [Cmdlet(VerbsCommon.Get, "UICulture", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113334")] + [Cmdlet(VerbsCommon.Get, "UICulture", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096613")] [OutputType(typeof(System.Globalization.CultureInfo))] public sealed class GetUICultureCommand : PSCmdlet { /// - /// Output the current UI Culture info object + /// Output the current UI Culture info object. /// protected override void BeginProcessing() { WriteObject(Host.CurrentUICulture); - } // EndProcessing - } // GetUICultureCommand -} // Microsoft.PowerShell.Commands - - + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs index 4b9e68af770..09bc78d9693 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs @@ -1,29 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Globalization; namespace Microsoft.PowerShell.Commands { /// - /// /// [Cmdlet(VerbsCommon.Get, "Unique", DefaultParameterSetName = "AsString", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113335", RemotingCapability = RemotingCapability.None)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097028", RemotingCapability = RemotingCapability.None)] public sealed class GetUniqueCommand : PSCmdlet { #region Parameters /// - /// /// /// [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 @@ -34,10 +30,11 @@ public sealed class GetUniqueCommand : PSCmdlet public SwitchParameter AsString { get { return _asString; } + set { _asString = value; } } - private bool _asString; + private bool _asString; /// /// This parameter specifies that just the types of the objects @@ -48,19 +45,27 @@ public SwitchParameter AsString public SwitchParameter OnType { get { return _onType; } + set { _onType = value; } } + private bool _onType = false; + + /// + /// Gets or sets case insensitive switch for string comparison. + /// + [Parameter] + public SwitchParameter CaseInsensitive { get; set; } + #endregion Parameters #region Overrides /// - /// /// protected override void ProcessRecord() { bool isUnique = true; - if (null == _lastObject) + if (_lastObject == null) { // always write first object, but return nothing // on "MSH> get-unique" @@ -74,14 +79,12 @@ protected override void ProcessRecord() else if (AsString) { string inputString = InputObject.ToString(); - if (null == _lastObjectAsString) - { - _lastObjectAsString = _lastObject.ToString(); - } - if (0 == String.Compare( + _lastObjectAsString ??= _lastObject.ToString(); + + if (string.Equals( inputString, _lastObjectAsString, - StringComparison.CurrentCulture)) + CaseInsensitive.IsPresent ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture)) { isUnique = false; } @@ -92,14 +95,12 @@ protected override void ProcessRecord() } else // compare as objects { - if (null == _comparer) - { - _comparer = new ObjectCommandComparer( - true, // ascending (doesn't matter) - CultureInfo.CurrentCulture, - true); // case-sensitive - } - isUnique = (0 != _comparer.Compare(InputObject, _lastObject)); + _comparer ??= new ObjectCommandComparer( + ascending: true, + CultureInfo.CurrentCulture, + caseSensitive: !CaseInsensitive.IsPresent); + + isUnique = (_comparer.Compare(InputObject, _lastObject) != 0); } if (isUnique) @@ -117,4 +118,3 @@ protected override void ProcessRecord() #endregion Internal } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs index 609df1d6558..c21165301e2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; using System.Diagnostics; +using System.Management.Automation; using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { /// - /// This class implements Get-Uptime + /// This class implements Get-Uptime. /// [Cmdlet(VerbsCommon.Get, "Uptime", DefaultParameterSetName = TimespanParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?linkid=834862")] [OutputType(typeof(TimeSpan), ParameterSetName = new string[] { TimespanParameterSet })] @@ -18,16 +17,14 @@ namespace Microsoft.PowerShell.Commands public class GetUptimeCommand : PSCmdlet { /// - /// Since parameter - /// The system startup time + /// The system startup time. /// /// [Parameter(ParameterSetName = SinceParameterSet)] public SwitchParameter Since { get; set; } = new SwitchParameter(); /// - /// ProcessRecord() override - /// This is the main entry point for the cmdlet + /// This is the main entry point for the cmdlet. /// protected override void ProcessRecord() { @@ -37,18 +34,17 @@ protected override void ProcessRecord() // InternalTestHooks.StopwatchIsNotHighResolution is used as test hook. if (Stopwatch.IsHighResolution && !InternalTestHooks.StopwatchIsNotHighResolution) { - TimeSpan uptime = TimeSpan.FromSeconds(Stopwatch.GetTimestamp()/Stopwatch.Frequency); + TimeSpan uptime = TimeSpan.FromSeconds(Stopwatch.GetTimestamp() / Stopwatch.Frequency); - switch (ParameterSetName) + if (Since) + { + // Output the time of the last system boot. + WriteObject(DateTime.Now.Subtract(uptime)); + } + else { - 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 elapsed since the last system boot. + WriteObject(uptime); } } else @@ -68,5 +64,5 @@ protected override void ProcessRecord() /// Parameter set name for DateTime OutputType. /// private const string SinceParameterSet = "Since"; - } + } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs index 384c1030085..61a0fe1a390 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs @@ -1,30 +1,30 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Management.Automation; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Reflection; +using static System.Management.Automation.Verbs; namespace Microsoft.PowerShell.Commands { - /// - /// Implementation of the Get Verb Command + /// Implementation of the Get Verb Command. /// - [Cmdlet(VerbsCommon.Get, "Verb", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=160712")] + [Cmdlet(VerbsCommon.Get, "Verb", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097026")] [OutputType(typeof(VerbInfo))] public class GetVerbCommand : Cmdlet { /// - /// Optional Verb filter + /// Optional Verb filter. /// [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string[] Verb { get; set; } /// - /// Optional Group filter + /// Optional Group filter. /// [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, Position = 1)] [ValidateSet("Common", "Communications", "Data", "Diagnostic", "Lifecycle", "Other", "Security")] @@ -34,49 +34,13 @@ public string[] Group } /// - /// Returns a list of verbs + /// Returns a list of verbs. /// 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 new file mode 100644 index 00000000000..1f527258939 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs @@ -0,0 +1,553 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Text; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// PSTuple is a helper class used to create Tuple from an input array. + /// + internal static class PSTuple + { + /// + /// ArrayToTuple is a helper method used to create a tuple for the supplied input array. + /// + /// The first generic type parameter. + /// Input objects used to create a tuple. + /// Tuple object. + internal static object ArrayToTuple(IList inputObjects) + { + return ArrayToTuple(inputObjects, 0); + } + + /// + /// ArrayToTuple is a helper method used to create a tuple for the supplied input array. + /// + /// The first generic type parameter. + /// Input objects used to create a tuple. + /// Start index of the array from which the objects have to considered for the tuple creation. + /// Tuple object. + private static object ArrayToTuple(IList inputObjects, int startIndex) + { + Diagnostics.Assert(inputObjects != null, "inputObjects is null"); + Diagnostics.Assert(inputObjects.Count > 0, "inputObjects is empty"); + + switch (inputObjects.Count - startIndex) + { + case 0: + return null; + case 1: + return Tuple.Create(inputObjects[startIndex]); + case 2: + return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1]); + case 3: + return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2]); + case 4: + return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3]); + case 5: + return Tuple.Create( + inputObjects[startIndex], + inputObjects[startIndex + 1], + inputObjects[startIndex + 2], + inputObjects[startIndex + 3], + inputObjects[startIndex + 4]); + case 6: + return Tuple.Create( + inputObjects[startIndex], + inputObjects[startIndex + 1], + inputObjects[startIndex + 2], + inputObjects[startIndex + 3], + inputObjects[startIndex + 4], + inputObjects[startIndex + 5]); + case 7: + return Tuple.Create( + inputObjects[startIndex], + inputObjects[startIndex + 1], + inputObjects[startIndex + 2], + inputObjects[startIndex + 3], + inputObjects[startIndex + 4], + inputObjects[startIndex + 5], + inputObjects[startIndex + 6]); + case 8: + return Tuple.Create( + inputObjects[startIndex], + inputObjects[startIndex + 1], + inputObjects[startIndex + 2], + inputObjects[startIndex + 3], + inputObjects[startIndex + 4], + inputObjects[startIndex + 5], + inputObjects[startIndex + 6], + inputObjects[startIndex + 7]); + default: + return Tuple.Create( + inputObjects[startIndex], + inputObjects[startIndex + 1], + inputObjects[startIndex + 2], + inputObjects[startIndex + 3], + inputObjects[startIndex + 4], + inputObjects[startIndex + 5], + inputObjects[startIndex + 6], + ArrayToTuple(inputObjects, startIndex + 7)); + } + } + } + + /// + /// Emitted by Group-Object when the NoElement option is true. + /// + public sealed class GroupInfoNoElement : GroupInfo + { + internal GroupInfoNoElement(OrderByPropertyEntry groupValue) : base(groupValue) + { + } + + internal override void Add(PSObject groupValue) + { + Count++; + } + } + + /// + /// Emitted by Group-Object. + /// + [DebuggerDisplay("{Name} ({Count})")] + public class GroupInfo + { + internal GroupInfo(OrderByPropertyEntry groupValue) + { + Group = new Collection(); + this.Add(groupValue.inputObject); + GroupValue = groupValue; + Name = BuildName(groupValue.orderValues); + } + + internal virtual void Add(PSObject groupValue) + { + Group.Add(groupValue); + Count++; + } + + private static string BuildName(List propValues) + { + StringBuilder sb = new(); + foreach (ObjectCommandPropertyValue propValue in propValues) + { + var propValuePropertyValue = propValue?.PropertyValue; + if (propValuePropertyValue != null) + { + if (propValuePropertyValue is ICollection propertyValueItems) + { + sb.Append('{'); + var length = sb.Length; + + foreach (object item in propertyValueItems) + { + sb.Append(CultureInfo.CurrentCulture, $"{item}, "); + } + + sb = sb.Length > length ? sb.Remove(sb.Length - 2, 2) : sb; + sb.Append("}, "); + } + else + { + sb.Append(CultureInfo.CurrentCulture, $"{propValuePropertyValue}, "); + } + } + } + + return sb.Length >= 2 ? sb.Remove(sb.Length - 2, 2).ToString() : string.Empty; + } + + /// + /// Gets the values of the group. + /// + public ArrayList Values + { + get + { + ArrayList values = new(); + foreach (ObjectCommandPropertyValue propValue in GroupValue.orderValues) + { + values.Add(propValue.PropertyValue); + } + + return values; + } + } + + /// + /// Gets the number of objects in the group. + /// + public int Count { get; internal set; } + + /// + /// Gets the list of objects in this group. + /// + public Collection Group { get; } + + /// + /// Gets the name of the group. + /// + public string Name { get; } + + /// + /// Gets the OrderByPropertyEntry used to build this group object. + /// + internal OrderByPropertyEntry GroupValue { get; } + } + + /// + /// Group-Object implementation. + /// + [Cmdlet(VerbsData.Group, "Object", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096619", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(Hashtable), typeof(GroupInfo))] + public class GroupObjectCommand : ObjectBase + { + #region tracer + + /// + /// An instance of the PSTraceSource class used for trace output. + /// + [TraceSource("GroupObjectCommand", "Class that has group base implementation")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("GroupObjectCommand", "Class that has group base implementation"); + + #endregion tracer + + #region Command Line Switches + + /// + /// Gets or sets the NoElement parameter indicating of the groups should be flattened. + /// + /// + [Parameter] + public SwitchParameter NoElement { get; set; } + + /// + /// Gets or sets the AsHashTable parameter. + /// + /// + [Parameter(ParameterSetName = "HashTable")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "HashTable")] + [Alias("AHT")] + public SwitchParameter AsHashTable { get; set; } + + /// + /// Gets or sets the AsString parameter. + /// + [Parameter(ParameterSetName = "HashTable")] + public SwitchParameter AsString { get; set; } + + 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; + private Type[] _propertyTypesCandidate; + + #endregion + + #region utils + + /// + /// Utility function called by Group-Object to create Groups. + /// + /// Input object that needs to be grouped. + /// True if we are not accumulating objects. + /// List containing Groups. + /// Dictionary used to keep track of the groups with hash of the property values being the key. + /// The Comparer to be used while comparing to check if new group has to be created. + private static void DoGrouping( + OrderByPropertyEntry currentObjectEntry, + bool noElement, + List groups, + Dictionary groupInfoDictionary, + OrderByPropertyComparer orderByPropertyComparer) + { + var currentObjectOrderValues = currentObjectEntry.orderValues; + if (currentObjectOrderValues != null && currentObjectOrderValues.Count > 0) + { + object currentTupleObject = PSTuple.ArrayToTuple(currentObjectOrderValues); + + if (groupInfoDictionary.TryGetValue(currentTupleObject, out var currentGroupInfo)) + { + // add this inputObject to an existing group + currentGroupInfo.Add(currentObjectEntry.inputObject); + } + else + { + bool isCurrentItemGrouped = false; + + for (int groupsIndex = 0; groupsIndex < groups.Count; groupsIndex++) + { + // Check if the current input object can be converted to one of the already known types + // by looking up in the type to GroupInfo mapping. + if (orderByPropertyComparer.Compare(groups[groupsIndex].GroupValue, currentObjectEntry) == 0) + { + groups[groupsIndex].Add(currentObjectEntry.inputObject); + isCurrentItemGrouped = true; + break; + } + } + + if (!isCurrentItemGrouped) + { + // create a new group + s_tracer.WriteLine("Create a new group: {0}", currentObjectOrderValues); + GroupInfo newObjGrp = noElement ? new GroupInfoNoElement(currentObjectEntry) : new GroupInfo(currentObjectEntry); + groups.Add(newObjGrp); + + groupInfoDictionary.Add(currentTupleObject, newObjGrp); + } + } + } + } + + /// + /// Utility function called by Group-Object to create Groups. + /// + /// Input object that needs to be grouped. + /// True if we are not accumulating objects. + /// List containing Groups. + /// Dictionary used to keep track of the groups with hash of the property values being the key. + /// The Comparer to be used while comparing to check if new group has to be created. + private static void DoOrderedGrouping( + OrderByPropertyEntry currentObjectEntry, + bool noElement, + List groups, + Dictionary groupInfoDictionary, + OrderByPropertyComparer orderByPropertyComparer) + { + var currentObjectOrderValues = currentObjectEntry.orderValues; + if (currentObjectOrderValues != null && currentObjectOrderValues.Count > 0) + { + object currentTupleObject = PSTuple.ArrayToTuple(currentObjectOrderValues); + + if (groupInfoDictionary.TryGetValue(currentTupleObject, out var currentGroupInfo)) + { + // add this inputObject to an existing group + currentGroupInfo.Add(currentObjectEntry.inputObject); + } + else + { + bool isCurrentItemGrouped = false; + + if (groups.Count > 0) + { + var lastGroup = groups[groups.Count - 1]; + + // Check if the current input object can be converted to one of the already known types + // by looking up in the type to GroupInfo mapping. + if (orderByPropertyComparer.Compare(lastGroup.GroupValue, currentObjectEntry) == 0) + { + lastGroup.Add(currentObjectEntry.inputObject); + isCurrentItemGrouped = true; + } + } + + if (!isCurrentItemGrouped) + { + // create a new group + s_tracer.WriteLine("Create a new group: {0}", currentObjectOrderValues); + GroupInfo newObjGrp = noElement + ? new GroupInfoNoElement(currentObjectEntry) + : new GroupInfo(currentObjectEntry); + + groups.Add(newObjGrp); + + groupInfoDictionary.Add(currentTupleObject, newObjGrp); + } + } + } + } + + private void WriteNonTerminatingError(Exception exception, string resourceIdAndErrorId, ErrorCategory category) + { + Exception ex = new(StringUtil.Format(resourceIdAndErrorId), exception); + WriteError(new ErrorRecord(ex, resourceIdAndErrorId, category, null)); + } + + #endregion utils + + /// + /// Process every input object to group them. + /// + protected override void ProcessRecord() + { + if (InputObject != null && InputObject != AutomationNull.Value) + { + OrderByPropertyEntry currentEntry; + + if (!_hasProcessedFirstInputObject) + { + Property ??= OrderByProperty.GetDefaultKeyPropertySet(InputObject); + + _orderByProperty.ProcessExpressionParameter(this, Property); + + if (AsString && !AsHashTable) + { + 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(UtilityCommonStrings.GroupObjectSingleProperty); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.InvalidArgument, Property); + ThrowTerminatingError(er); + } + + currentEntry = _orderByProperty.CreateOrderByPropertyEntry(this, InputObject, CaseSensitive, _cultureInfo); + bool[] ascending = new bool[currentEntry.orderValues.Count]; + for (int index = 0; index < currentEntry.orderValues.Count; index++) + { + ascending[index] = true; + } + + _orderByPropertyComparer = new OrderByPropertyComparer(ascending, _cultureInfo, CaseSensitive); + + _hasProcessedFirstInputObject = true; + } + else + { + currentEntry = _orderByProperty.CreateOrderByPropertyEntry(this, InputObject, CaseSensitive, _cultureInfo); + } + + _entriesToOrder.Add(currentEntry); + + var currentEntryOrderValues = currentEntry.orderValues; + if (!_hasDifferentValueTypes) + { + UpdateOrderPropertyTypeInfo(currentEntryOrderValues); + } + } + } + + private void UpdateOrderPropertyTypeInfo(List currentEntryOrderValues) + { + if (_propertyTypesCandidate == null) + { + _propertyTypesCandidate = currentEntryOrderValues.Select(static c => PSObject.Base(c.PropertyValue)?.GetType()).ToArray(); + return; + } + + if (_propertyTypesCandidate.Length != currentEntryOrderValues.Count) + { + _hasDifferentValueTypes = true; + return; + } + + // check all the types we group on. + // if we find more than one set of types, _hasDifferentValueTypes is set to true, + // and we are forced to take a slower code path when we group our objects + for (int i = 0; i < _propertyTypesCandidate.Length; i++) + { + var candidateType = _propertyTypesCandidate[i]; + var propertyType = PSObject.Base(currentEntryOrderValues[i].PropertyValue)?.GetType(); + if (propertyType == null) + { + // we ignore properties without values. We can always compare against null. + continue; + } + + // if we haven't gotten a type for a property yet, update it when we do get a value + if (propertyType != candidateType) + { + if (candidateType == null) + { + _propertyTypesCandidate[i] = propertyType; + } + else + { + _hasDifferentValueTypes = true; + break; + } + } + } + } + + /// + /// Completes the processing of the gathered group objects. + /// + protected override void EndProcessing() + { + if (!_hasDifferentValueTypes) + { + // using OrderBy to get stable sort. + // fast path when we only have the same object types to group + foreach (var entry in _entriesToOrder.Order(_orderByPropertyComparer)) + { + DoOrderedGrouping(entry, NoElement, _groups, _tupleToGroupInfoMappingDictionary, _orderByPropertyComparer); + if (Stopping) + { + return; + } + } + } + else + { + foreach (var entry in _entriesToOrder) + { + DoGrouping(entry, NoElement, _groups, _tupleToGroupInfoMappingDictionary, _orderByPropertyComparer); + if (Stopping) + { + return; + } + } + } + + s_tracer.WriteLine(_groups.Count); + if (_groups.Count > 0) + { + if (AsHashTable.IsPresent) + { + StringComparer comparer = CaseSensitive.IsPresent + ? StringComparer.CurrentCulture + : StringComparer.CurrentCultureIgnoreCase; + var hashtable = new Hashtable(comparer); + try + { + if (AsString) + { + foreach (GroupInfo grp in _groups) + { + hashtable.Add(grp.Name, grp.Group); + } + } + else + { + foreach (GroupInfo grp in _groups) + { + hashtable.Add(PSObject.Base(grp.Values[0]), grp.Group); + } + } + } + catch (ArgumentException e) + { + WriteNonTerminatingError(e, UtilityCommonStrings.InvalidOperation, ErrorCategory.InvalidArgument); + return; + } + + WriteObject(hashtable); + } + else + { + WriteObject(_groups, true); + } + } + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs index e2217f688e7..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -1,55 +1,49 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation; -using System.Management.Automation.Language; using System.Management.Automation.Internal; +using System.Management.Automation.Language; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Xml; -using System.Security.Cryptography.X509Certificates; using Dbg = System.Management.Automation.Diagnostics; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "ImplicitRemotingStrings.resources", MessageId = "runspace")] -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "ImplicitRemotingStrings.resources", MessageId = "Runspace")] - namespace Microsoft.PowerShell.Commands { using PowerShell = System.Management.Automation.PowerShell; /// /// This class implements Export-PSSession cmdlet. - /// Spec: TBD + /// Spec: TBD. /// - [Cmdlet(VerbsData.Export, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135213")] + [Cmdlet(VerbsData.Export, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096604")] [OutputType(typeof(FileInfo))] public sealed class ExportPSSessionCommand : ImplicitRemotingCommandBase { /// /// Version of the script generator used (by this Export-PSSession cmdlet) to generate psm1 and psd1 files. /// Generated script checks this version to see if it needs to be regenerated. There are 2 situations where this is needed - /// 1. the script needs to be regenerated because a bug fix made previous versions incompatible with the rest of the system (i.e. with ObjectModelWrapper) - /// 2. ths script needs to be regenerated because a security vulnerability was found inside generated code (there is no way to service generated code, but we can service the dll that reports the version that the generated script checks against) + /// 1. the script needs to be regenerated because a bug fix made previous versions incompatible with the rest of the system (i.e. with ObjectModelWrapper). + /// 2. ths script needs to be regenerated because a security vulnerability was found inside generated code (there is no way to service generated code, but we can service the dll that reports the version that the generated script checks against). /// public static Version VersionOfScriptGenerator { get { return ImplicitRemotingCodeGenerator.VersionOfScriptWriter; } } #region Parameters /// - /// Mandatory file name to write to + /// Mandatory file name to write to. /// [Parameter(Mandatory = true, Position = 1)] [ValidateNotNullOrEmpty] @@ -66,31 +60,37 @@ public SwitchParameter Force { return new SwitchParameter(_force); } + set { _force = value.IsPresent; } } + private bool _force; /// - /// Encoding optional flag + /// Encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentCompletions( - EncodingConversion.Ascii, - EncodingConversion.BigEndianUnicode, - EncodingConversion.OEM, - EncodingConversion.Unicode, - EncodingConversion.Utf7, - EncodingConversion.Utf8, - EncodingConversion.Utf8Bom, - EncodingConversion.Utf8NoBom, - EncodingConversion.Utf32 - )] + [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 @@ -126,8 +126,8 @@ protected override void BeginProcessing() // Throw out terminating error if this is the case. if (IsModuleSpecified && IsFullyQualifiedModuleSpecified) { - string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Module", "FullyQualifiedModule"); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); + string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, nameof(Module), nameof(FullyQualifiedModule)); + ErrorRecord error = new(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(error); } @@ -163,9 +163,9 @@ protected override void BeginProcessing() /// /// This class implements Import-PSSession cmdlet. - /// Spec: http://cmdletdesigner/SpecViewer/Default.aspx?Project=PowerShell&Cmdlet=Import-Command + /// Spec: http://cmdletdesigner/SpecViewer/Default.aspx?Project=PowerShell&Cmdlet=Import-Command . /// - [Cmdlet(VerbsData.Import, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135221")] + [Cmdlet(VerbsData.Import, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096712")] [OutputType(typeof(PSModuleInfo))] public sealed class ImportPSSessionCommand : ImplicitRemotingCommandBase { @@ -200,7 +200,7 @@ private void RegisterModuleCleanUp(PSModuleInfo moduleInfo) { if (moduleInfo == null) { - throw PSTraceSource.NewArgumentNullException("moduleInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(moduleInfo)); } // Note: we are using this.Context.Events to make sure that the event handler @@ -251,14 +251,15 @@ private PSModuleInfo CreateModule(string manifestFile) #region Extra parameters /// - /// This parameter specified a prefix used to modify names of imported commands + /// This parameter specified a prefix used to modify names of imported commands. /// [Parameter] [ValidateNotNullOrEmpty] public new string Prefix { - set { base.Prefix = value; } get { return base.Prefix; } + + set { base.Prefix = value; } } /// @@ -270,6 +271,7 @@ private PSModuleInfo CreateModule(string manifestFile) public SwitchParameter DisableNameChecking { get { return _disableNameChecking; } + set { _disableNameChecking = value; } } @@ -286,8 +288,8 @@ protected override void BeginProcessing() // Throw out terminating error if this is the case. if (IsModuleSpecified && IsFullyQualifiedModuleSpecified) { - string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Module", "FullyQualifiedModule"); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); + string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, nameof(Module), nameof(FullyQualifiedModule)); + ErrorRecord error = new(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(error); } @@ -314,6 +316,7 @@ protected override void BeginProcessing() manifestFile = file; } } + Dbg.Assert(manifestFile != null, "A psd1 file should always be generated"); PSModuleInfo moduleInfo = this.CreateModule(manifestFile); @@ -324,7 +327,7 @@ protected override void BeginProcessing() } /// - /// Base class for implicit remoting cmdlets + /// Base class for implicit remoting cmdlets. /// public class ImplicitRemotingCommandBase : PSCmdlet { @@ -350,10 +353,9 @@ internal ImplicitRemotingCommandBase() #region related to Get-Command /// - /// Gets or sets the path(s) or name(s) of the commands to retrieve + /// Gets or sets the path(s) or name(s) of the commands to retrieve. /// [Parameter(Position = 2)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Alias("Name")] public string[] CommandName { @@ -361,6 +363,7 @@ public string[] CommandName { return _commandNameParameter; } + set { _commandNameParameter = value; @@ -370,11 +373,12 @@ public string[] CommandName WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase); } } + private string[] _commandNameParameter; private Collection _commandNamePatterns; // initialized to default value in the constructor /// - /// Allows shadowing and/or overwriting of existing local/client commands + /// Allows shadowing and/or overwriting of existing local/client commands. /// [Parameter] public SwitchParameter AllowClobber { get; set; } = new SwitchParameter(false); @@ -387,23 +391,24 @@ public string[] CommandName [AllowNull] [AllowEmptyCollection] [Alias("Args")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ArgumentList { get { return _commandArgs; } + set { _commandArgs = value; _commandParameterSpecified = true; } } + private object[] _commandArgs; /// - /// Gets or sets the type of the command to get + /// Gets or sets the type of the command to get. /// [Parameter] [Alias("Type")] @@ -413,6 +418,7 @@ public CommandTypes CommandType { return _commandType; } + set { _commandType = value; @@ -423,11 +429,9 @@ public CommandTypes CommandType private CommandTypes _commandType = CommandTypes.All & (~(CommandTypes.Application | CommandTypes.Script | CommandTypes.ExternalScript)); /// - /// Gets or sets the PSSnapin parameter to the cmdlet + /// Gets or sets the PSSnapin parameter to the cmdlet. /// [Parameter] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Snapin")] [Alias("PSSnapin")] [ValidateNotNull] public string[] Module @@ -439,22 +443,20 @@ public string[] Module set { - if (value == null) - { - value = new string[0]; - } + value ??= Array.Empty(); + _PSSnapins = value; _commandParameterSpecified = true; IsModuleSpecified = true; } } - private string[] _PSSnapins = new string[0]; + + private string[] _PSSnapins = Array.Empty(); internal bool IsModuleSpecified = false; /// - /// Gets or sets the FullyQualifiedModule parameter to the cmdlet + /// Gets or sets the FullyQualifiedModule parameter to the cmdlet. /// [Parameter] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [ValidateNotNull] public ModuleSpecification[] FullyQualifiedModule { @@ -469,11 +471,13 @@ public ModuleSpecification[] FullyQualifiedModule { _moduleSpecifications = value; } + _commandParameterSpecified = true; IsFullyQualifiedModuleSpecified = true; } } - 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 @@ -483,9 +487,8 @@ public ModuleSpecification[] FullyQualifiedModule #region related to F&O /// - /// Gets or sets the types for which we should get formatting and output data + /// Gets or sets the types for which we should get formatting and output data. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Position = 3)] public string[] FormatTypeName { @@ -493,6 +496,7 @@ public string[] FormatTypeName { return _formatTypeNameParameter; } + set { _formatTypeNameParameter = value; @@ -502,6 +506,7 @@ public string[] FormatTypeName WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase); } } + private string[] _formatTypeNameParameter; // initialized to default value in the constructor private Collection _formatTypeNamePatterns; private bool _formatTypeNamesSpecified; // initialized to default value in the constructor @@ -511,9 +516,9 @@ public string[] FormatTypeName #region Related to modules /// - /// This parameter specified a prefix used to modify names of imported commands + /// 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. @@ -525,11 +530,10 @@ public string[] FormatTypeName /// /// The PSSession object describing the remote runspace - /// using which the specified cmdlet operation will be performed + /// using which the specified cmdlet operation will be performed. /// [Parameter(Mandatory = true, Position = 0)] [ValidateNotNull] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] public PSSession Session { get; set; } #endregion Parameters @@ -540,7 +544,7 @@ internal ErrorDetails GetErrorDetails(string errorId, params object[] args) { if (string.IsNullOrEmpty(errorId)) { - throw PSTraceSource.NewArgumentNullException("errorId"); + throw PSTraceSource.NewArgumentNullException(nameof(errorId)); } return new ErrorDetails( @@ -552,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, @@ -570,14 +574,14 @@ private ErrorRecord GetErrorMalformedDataFromRemoteCommand(string commandName) { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -591,14 +595,14 @@ private ErrorRecord GetErrorCommandSkippedBecauseOfShadowing(string commandNames { if (string.IsNullOrEmpty(commandNames)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -612,14 +616,14 @@ private ErrorRecord GetErrorSkippedNonRequestedCommand(string commandName) { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -633,14 +637,14 @@ private ErrorRecord GetErrorSkippedNonRequestedTypeDefinition(string typeName) { if (string.IsNullOrEmpty(typeName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -654,14 +658,14 @@ private ErrorRecord GetErrorSkippedUnsafeCommandName(string commandName) { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -675,23 +679,25 @@ private ErrorRecord GetErrorSkippedUnsafeNameInMetadata(string commandName, stri { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("commandName"); + throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } + if (string.IsNullOrEmpty(nameType)) { - throw PSTraceSource.NewArgumentNullException("nameType"); + throw PSTraceSource.NewArgumentNullException(nameof(nameType)); } + Dbg.Assert(nameType.Equals("Alias") || nameType.Equals("ParameterSet") || nameType.Equals("Parameter"), "nameType matches resource names"); if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } string errorId = "ErrorSkippedUnsafe" + nameType + "Name"; ErrorDetails details = this.GetErrorDetails(errorId, commandName, name); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidData, @@ -705,11 +711,12 @@ private ErrorRecord GetErrorFromRemoteCommand(string commandName, RuntimeExcepti { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("commandName"); + throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } + if (runtimeException == null) { - throw PSTraceSource.NewArgumentNullException("runtimeException"); + throw PSTraceSource.NewArgumentNullException(nameof(runtimeException)); } string errorId; @@ -719,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))) { @@ -758,14 +764,14 @@ private ErrorRecord GetErrorCouldntResolvedAlias(string aliasName) { if (string.IsNullOrEmpty(aliasName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -779,14 +785,14 @@ private ErrorRecord GetErrorNoResultsFromRemoteEnd(string commandName) { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("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, @@ -796,13 +802,14 @@ private ErrorRecord GetErrorNoResultsFromRemoteEnd(string commandName) return errorRecord; } - private List _commandsSkippedBecauseOfShadowing = new List(); + private readonly List _commandsSkippedBecauseOfShadowing = new(); + private void ReportSkippedCommands() { if (_commandsSkippedBecauseOfShadowing.Count != 0) { string skippedCommands = string.Join(", ", _commandsSkippedBecauseOfShadowing.ToArray()); - ErrorRecord errorRecord = this.GetErrorCommandSkippedBecauseOfShadowing(skippedCommands.ToString()); + ErrorRecord errorRecord = this.GetErrorCommandSkippedBecauseOfShadowing(skippedCommands); this.WriteWarning(errorRecord.ErrorDetails.Message); } } @@ -828,6 +835,7 @@ private bool IsCommandNameMatchingParameters(string commandName) } private Dictionary _existingCommands; + private Dictionary ExistingCommands { get @@ -835,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, @@ -845,6 +853,7 @@ private Dictionary ExistingCommands _existingCommands[commandInfo.Name] = null; } } + return _existingCommands; } } @@ -853,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)) @@ -866,7 +875,7 @@ private bool IsShadowingExistingCommands(string commandName) } /// - /// Returns true if command doesn't shadow OR is in the -AllowShadowing parameter + /// Returns true if command doesn't shadow OR is in the -AllowShadowing parameter. /// /// /// @@ -874,7 +883,7 @@ private bool IsCommandNameAllowedForImport(string commandName) { if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentNullException("commandName"); + throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } if (this.AllowClobber.IsPresent) @@ -1011,6 +1020,7 @@ private T GetPropertyValue(string commandName, PSObject pso, string propertyN { this.ThrowTerminatingError(this.GetErrorMalformedDataFromRemoteCommand(commandName)); } + return ConvertTo(commandName, property.Value, nullOk); } @@ -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); } @@ -1090,10 +1098,10 @@ private Dictionary RehydrateDictionary(string commandName, PSObject /// /// Validates that a name or identifier is safe to use in generated code - /// (i.e. it can't be used for code injection attacks) + /// (i.e. it can't be used for code injection attacks). /// - /// name to validate - /// true if the name is safe; false otherwise + /// Name to validate. + /// if the name is safe; otherwise. private static bool IsSafeNameOrIdentifier(string name) { // '.' is needed for stuff like net.exe @@ -1108,21 +1116,21 @@ private static bool IsSafeNameOrIdentifier(string name) /// /// Validates that a parameter name is safe to use in generated code - /// (i.e. it can't be used for code injection attacks) + /// (i.e. it can't be used for code injection attacks). /// - /// parameter name to validate - /// true if the name is safe; false otherwise + /// Parameter name to validate. + /// if the name is safe; otherwise. private static bool IsSafeParameterName(string parameterName) { - return IsSafeNameOrIdentifier(parameterName) && !parameterName.Contains(":"); + return IsSafeNameOrIdentifier(parameterName) && !parameterName.Contains(':'); } /// /// Validates that a type can be safely used as a type constraint - /// (i.e. it doesn't introduce any side effects on the client) + /// (i.e. it doesn't introduce any side effects on the client). /// - /// type to validate - /// true if the type is safe; false otherwise + /// Type to validate. + /// if the type is safe; otherwise. private static bool IsSafeTypeConstraint(Type type) { if (type == null) @@ -1164,8 +1172,8 @@ private static bool IsSafeTypeConstraint(Type type) /// Validates that command metadata returned from the (potentially malicious) server is safe. /// 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 + /// Command metadata to verify. + /// 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) { @@ -1200,7 +1208,7 @@ private bool IsSafeCommandMetadata(CommandMetadata commandMetadata) Dbg.Assert(parameter.Attributes == null || parameter.Attributes.Count == 0, "Attributes shouldn't get rehydrated"); - // sanitize - remove type constraint that are not whitelisted + // sanitize - remove type constraint that are not allowed if (!IsSafeTypeConstraint(parameter.ParameterType)) { parameter.ParameterType = null; @@ -1271,7 +1279,7 @@ private ParameterMetadata RehydrateParameterMetadata(PSObject deserializedParame { if (deserializedParameterMetadata == null) { - throw PSTraceSource.NewArgumentNullException("deserializedParameterMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(deserializedParameterMetadata)); } string name = GetPropertyValue("Get-Command", deserializedParameterMetadata, "Name"); @@ -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; @@ -1312,7 +1330,7 @@ private CommandMetadata RehydrateCommandMetadata(PSObject deserializedCommandInf { if (deserializedCommandInfo == null) { - throw PSTraceSource.NewArgumentNullException("deserializedCommandInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(deserializedCommandInfo)); } string name = GetPropertyValue("Get-Command", deserializedCommandInfo, "Name"); @@ -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) { @@ -1362,7 +1380,6 @@ private int GetCommandTypePriority(CommandTypes commandType) case CommandTypes.Filter: case CommandTypes.Function: case CommandTypes.Script: - case CommandTypes.Workflow: return 20; case CommandTypes.Cmdlet: @@ -1380,12 +1397,12 @@ private int GetCommandTypePriority(CommandTypes commandType) } /// - /// Converts remote (deserialized) CommandInfo objects into CommandMetadata equivalents + /// Converts remote (deserialized) CommandInfo objects into CommandMetadata equivalents. /// - /// Dictionary where rehydrated CommandMetadata are going to be stored - /// Dictionary mapping alias names to resolved command names - /// Remote (deserialized) CommandInfo object - /// CommandMetadata equivalents + /// Dictionary where rehydrated CommandMetadata are going to be stored. + /// Dictionary mapping alias names to resolved command names. + /// Remote (deserialized) CommandInfo object. + /// CommandMetadata equivalents. private void AddRemoteCommandMetadata( Dictionary name2commandMetadata, Dictionary alias2resolvedCommandName, @@ -1401,15 +1418,18 @@ private void AddRemoteCommandMetadata( { return; } + if (resolvedCommandName != null && !IsSafeNameOrIdentifier(commandMetadata.Name)) { this.WriteError(this.GetErrorSkippedUnsafeCommandName(resolvedCommandName)); return; } + if (IsCommandSkippedByServerDeclaration(commandMetadata.Name)) { return; } + if (!IsCommandNameAllowedForImport(commandMetadata.Name)) { return; @@ -1418,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; @@ -1564,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); @@ -1577,9 +1596,9 @@ private PowerShell BuildPowerShellForGetFormatData() } /// - /// Gets CommandMetadata objects from remote runspace + /// Gets CommandMetadata objects from remote runspace. /// - /// (rehydrated) CommandMetadata objects + /// (rehydrated) CommandMetadata objects. internal List GetRemoteFormatData() { if ((this.FormatTypeName == null) || (this.FormatTypeName.Length == 0) || @@ -1606,18 +1625,19 @@ 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); this.DuplicatePowerShellStreams(powerShell); this.WriteProgress(startTime, ++numberOfReceivedObjects, expectedCount, ImplicitRemotingStrings.ProgressStatusGetFormatDataProgress); } + this.DuplicatePowerShellStreams(powerShell); powerShell.EndInvoke(asyncResult); @@ -1625,6 +1645,7 @@ internal List GetRemoteFormatData() { this.ThrowTerminatingError(this.GetErrorNoResultsFromRemoteEnd("Get-FormatData")); } + return result; } } @@ -1654,20 +1675,21 @@ private PowerShell BuildPowerShellForGetCommand() { powerShell.AddParameter("Name", this.CommandName); } - powerShell.AddParameter("Module", this.Module); + + powerShell.AddParameter(nameof(Module), this.Module); if (IsFullyQualifiedModuleSpecified) { - powerShell.AddParameter("FullyQualifiedModule", this.FullyQualifiedModule); + powerShell.AddParameter(nameof(FullyQualifiedModule), this.FullyQualifiedModule); } + powerShell.AddParameter("ArgumentList", this.ArgumentList); powerShell.Runspace = Session.Runspace; - powerShell.RemotePowerShell.HostCallReceived += new EventHandler>(HandleHostCallReceived); + powerShell.RemotePowerShell.HostCallReceived += HandleHostCallReceived; return powerShell; } /// - /// /// /// /// @@ -1677,13 +1699,13 @@ private void HandleHostCallReceived(object sender, RemoteDataEventArgs - /// Gets CommandMetadata objects from remote runspace + /// Gets CommandMetadata objects from remote runspace. /// - /// (rehydrated) CommandMetadata objects + /// (rehydrated) CommandMetadata objects. internal List 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) || @@ -1714,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); @@ -1741,6 +1763,7 @@ internal List GetRemoteCommandMetadata(out Dictionary GetRemoteCommandMetadata(out Dictionary(name2commandMetadata.Values); } } @@ -1790,10 +1814,11 @@ private void WriteProgress(string statusDescription, int? percentComplete, int? return; } } + _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); @@ -1840,17 +1865,17 @@ private void WriteProgress(DateTime startTime, int currentCount, int expectedCou /// /// Generates a proxy module in the given directory. /// - /// base directory for the module - /// fileName prefix for module files - /// encoding of generated files - /// whether to overwrite files - /// remote commands to generate proxies for - /// dictionary mapping alias names to resolved command names - /// remote format data to generate format.ps1xml for - /// Paths to generated files + /// Base directory for the module. + /// FileName prefix for module files. + /// Encoding of generated files. + /// Whether to overwrite files. + /// Remote commands to generate proxies for. + /// Dictionary mapping alias names to resolved command names. + /// Remote format data to generate format.ps1xml for. + /// Paths to generated files. internal List GenerateProxyModule( DirectoryInfo moduleRootDirectory, - String moduleNamePrefix, + string moduleNamePrefix, Encoding encoding, bool force, List listOfCommandMetadata, @@ -1868,7 +1893,7 @@ internal List GenerateProxyModule( } } - ImplicitRemotingCodeGenerator codeGenerator = new ImplicitRemotingCodeGenerator( + ImplicitRemotingCodeGenerator codeGenerator = new( this.Session, this.ModuleGuid, this.MyInvocation); @@ -1891,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, @@ -1907,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; @@ -1920,25 +1945,22 @@ internal ImplicitRemotingCodeGenerator( #region Code generation helpers /// - /// Gets a connection URI associated with the remote runspace + /// Gets a connection URI associated with the remote runspace. /// - /// Connection URI associated with the remote runspace + /// 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; } @@ -1953,23 +1975,24 @@ private string GetConnectionString() return null; } - private string EscapeFunctionNameForRemoteHelp(string name) + private static string EscapeFunctionNameForRemoteHelp(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + 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); } } + return result.ToString(); } @@ -1977,7 +2000,7 @@ private string EscapeFunctionNameForRemoteHelp(string name) ############################################################################## "; - private void GenerateSectionSeparator(TextWriter writer) + private static void GenerateSectionSeparator(TextWriter writer) { writer.Write(SectionSeparator); } @@ -2005,7 +2028,7 @@ private void GenerateManifest(TextWriter writer, string psm1fileName, string for { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } GenerateTopComment(writer); @@ -2054,7 +2077,6 @@ private void GenerateTopComment(TextWriter writer) throw '{3}' }} - $script:WriteHost = $executionContext.InvokeCommand.GetCommand('Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet) $script:WriteWarning = $executionContext.InvokeCommand.GetCommand('Write-Warning', [System.Management.Automation.CommandTypes]::Cmdlet) $script:WriteInformation = $executionContext.InvokeCommand.GetCommand('Write-Information', [System.Management.Automation.CommandTypes]::Cmdlet) @@ -2078,12 +2100,14 @@ private void GenerateModuleHeader(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(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, @@ -2110,11 +2134,12 @@ function Write-PSImplicitRemotingMessage try { & $script:WriteHost -Object $message -ErrorAction SilentlyContinue } catch { } } "; - private void GenerateHelperFunctionsWriteMessage(TextWriter writer) + + private static void GenerateHelperFunctionsWriteMessage(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } writer.Write(HelperFunctionsWriteMessage); @@ -2164,11 +2189,12 @@ function Set-PSImplicitRemotingSession if ($PSSessionOverride) {{ Set-PSImplicitRemotingSession $PSSessionOverride }} "; - private void GenerateHelperFunctionsSetImplicitRunspace(TextWriter writer) + + private static void GenerateHelperFunctionsSetImplicitRunspace(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } string runspaceNameTemplate = StringUtil.Format(ImplicitRemotingStrings.ProxyRunspaceNameTemplate); @@ -2195,11 +2221,12 @@ function Get-PSImplicitRemotingSessionOption }} }} "; + private void GenerateHelperFunctionsGetSessionOption(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } writer.Write( @@ -2220,59 +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.NoEncryption) { result.Append("-NoEncryption "); } - if (wsmanConnectionInfo.NoMachineProfile) { result.Append("-NoMachineProfile "); } - if (wsmanConnectionInfo.UseUTF16) { result.Append("-UseUTF16 "); } + if (!wsmanConnectionInfo.UseCompression) + { + result.Append("-NoCompression "); + } - if (wsmanConnectionInfo.SkipCACheck) { result.Append("-SkipCACheck "); } - if (wsmanConnectionInfo.SkipCNCheck) { result.Append("-SkipCNCheck "); } - if (wsmanConnectionInfo.SkipRevocationCheck) { result.Append("-SkipRevocationCheck "); } + if (wsmanConnectionInfo.NoEncryption) + { + result.Append("-NoEncryption "); + } + + if (wsmanConnectionInfo.NoMachineProfile) + { + result.Append("-NoMachineProfile "); + } + + if (wsmanConnectionInfo.UseUTF16) + { + result.Append("-UseUTF16 "); + } + + if (wsmanConnectionInfo.SkipCACheck) + { + result.Append("-SkipCACheck "); + } + + if (wsmanConnectionInfo.SkipCNCheck) + { + result.Append("-SkipCNCheck "); + } + + 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.AppendFormat( - CultureInfo.InvariantCulture, - "-ProxyAccessType {0} ", - wsmanConnectionInfo.ProxyAccessType.ToString()); - result.AppendFormat( - CultureInfo.InvariantCulture, - "-ProxyAuthentication {0} ", - wsmanConnectionInfo.ProxyAuthentication.ToString()); + result.Append(CultureInfo.InvariantCulture, $"-MaximumRedirection {wsmanConnectionInfo.MaximumConnectionRedirectionCount} "); + + result.Append(CultureInfo.InvariantCulture, $"-ProxyAccessType {wsmanConnectionInfo.ProxyAccessType} "); + result.Append(CultureInfo.InvariantCulture, $"-ProxyAuthentication {wsmanConnectionInfo.ProxyAuthentication} "); result.Append(this.GenerateProxyCredentialParameter(wsmanConnectionInfo)); } @@ -2282,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(") "); } @@ -2344,6 +2382,7 @@ function Get-PSImplicitRemotingSession -InstanceId {0} ` -ErrorAction SilentlyContinue ) }} + if (($null -ne $script:PSSession) -and ($script:PSSession.Runspace.RunspaceStateInfo.State -eq 'Disconnected')) {{ # If we are handed a disconnected session, try re-connecting it before creating a new session. @@ -2352,6 +2391,7 @@ function Get-PSImplicitRemotingSession -Session $script:PSSession ` -ErrorAction SilentlyContinue) }} + if (($null -eq $script:PSSession) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) {{ Write-PSImplicitRemotingMessage ('{1}' -f $commandName) @@ -2371,10 +2411,12 @@ function Get-PSImplicitRemotingSession {8} }} + if (($null -eq $script:PSSession) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) {{ throw '{3}' }} + return [Management.Automation.Runspaces.PSSession]$script:PSSession }} "; @@ -2383,7 +2425,7 @@ private void GenerateHelperFunctionsGetImplicitRunspace(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } string hashString; @@ -2392,7 +2434,7 @@ private void GenerateHelperFunctionsGetImplicitRunspace(TextWriter writer) out hashString, ImplicitRemotingCommandBase.ImplicitRemotingKey, ImplicitRemotingCommandBase.ImplicitRemotingHashKey); - hashString = hashString ?? string.Empty; + hashString ??= string.Empty; writer.Write( HelperFunctionsGetImplicitRunspaceTemplate, @@ -2414,13 +2456,14 @@ private void GenerateHelperFunctionsGetImplicitRunspace(TextWriter writer) }} -ErrorAction SilentlyContinue }} catch {{ }} "; + private string GenerateReimportingOfModules() { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); - if (_invocationInfo.BoundParameters.ContainsKey("Module")) + if (_invocationInfo.BoundParameters.ContainsKey(nameof(Module))) { - string[] moduleNames = (string[])_invocationInfo.BoundParameters["Module"]; + string[] moduleNames = (string[])_invocationInfo.BoundParameters[nameof(Module)]; foreach (string moduleName in moduleNames) { result.AppendFormat( @@ -2482,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( @@ -2491,12 +2533,11 @@ private string GenerateNewRunspaceExpression() NewVMRunspaceTemplate, /* 0 */ this.GenerateConnectionStringForNewRunspace(), /* 1 */ this.GenerateCredentialParameter(), - /* 2 */ String.IsNullOrEmpty(vmConfigurationName) ? String.Empty : String.Concat("-ConfigurationName ", vmConfigurationName)); + /* 2 */ string.IsNullOrEmpty(vmConfigurationName) ? string.Empty : string.Concat("-ConfigurationName ", vmConfigurationName)); } 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( @@ -2504,7 +2545,7 @@ private string GenerateNewRunspaceExpression() NewContainerRunspaceTemplate, /* 0 */ this.GenerateConnectionStringForNewRunspace(), /* 1 */ containerConnectionInfo.ContainerProc.RunAsAdmin ? "-RunAsAdministrator" : string.Empty, - /* 2 */ String.IsNullOrEmpty(containerConfigurationName) ? String.Empty : String.Concat("-ConfigurationName ", containerConfigurationName)); + /* 2 */ string.IsNullOrEmpty(containerConfigurationName) ? string.Empty : string.Concat("-ConfigurationName ", containerConfigurationName)); } else { @@ -2523,6 +2564,7 @@ private string GenerateNewRunspaceExpression() private const string ComputerNameParameterTemplate = @"-ComputerName '{0}' ` -ApplicationName '{1}' {2} {3} "; + private const string VMIdParameterTemplate = @"-VMId '{0}' "; private const string ContainerIdParameterTemplate = @"-ContainerId '{0}' "; @@ -2537,19 +2579,16 @@ private string GenerateNewRunspaceExpression() /// private string GenerateConnectionStringForNewRunspace() { - WSManConnectionInfo connectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (null == connectionInfo) + 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, @@ -2569,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; } @@ -2610,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; } @@ -2706,7 +2741,8 @@ function Get-PSImplicitRemotingClientSideParameters $proxyForCmdlet) $clientSideParameters = @{} - $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction' + + $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction', 'ProgressAction' Modify-PSImplicitRemotingParameters $clientSideParameters $PSBoundParameters 'AsJob' if ($proxyForCmdlet) @@ -2727,11 +2763,12 @@ function Get-PSImplicitRemotingClientSideParameters return $clientSideParameters } "; - private void GenerateHelperFunctionsClientSideParameters(TextWriter writer) + + private static void GenerateHelperFunctionsClientSideParameters(TextWriter writer) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } writer.Write(HelperFunctionsModifyParameters); @@ -2741,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 @@ -2776,17 +2813,19 @@ private void GenerateHelperFunctions(TextWriter writer) $null = $positionalArguments.Add( $PSBoundParameters[$parameterName] ) $null = $PSBoundParameters.Remove($parameterName) }} + $positionalArguments.AddRange($args) $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) @@ -2794,7 +2833,9 @@ private void GenerateHelperFunctions(TextWriter writer) throw }} }} + Process {{ {6} }} + End {{ {7} }} # .ForwardHelpTargetName {1} @@ -2803,15 +2844,15 @@ private void GenerateHelperFunctions(TextWriter writer) }} "; - private void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMetadata) + private static void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMetadata) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } string functionNameForString = CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name); - string functionNameForHelp = this.EscapeFunctionNameForRemoteHelp(commandMetadata.Name); + string functionNameForHelp = EscapeFunctionNameForRemoteHelp(commandMetadata.Name); writer.Write( CommandProxyTemplate, /* 0 */ functionNameForString, @@ -2825,18 +2866,19 @@ 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) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } + if (listOfCommandMetadata == null) { - throw PSTraceSource.NewArgumentNullException("listOfCommandMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(listOfCommandMetadata)); } - this.GenerateSectionSeparator(writer); + GenerateSectionSeparator(writer); foreach (CommandMetadata commandMetadata in listOfCommandMetadata) { GenerateCommandProxy(writer, commandMetadata); @@ -2851,60 +2893,63 @@ private void GenerateCommandProxy(TextWriter writer, IEnumerable listOfCommandMetadata) + private static void GenerateExportDeclaration(TextWriter writer, IEnumerable listOfCommandMetadata) { if (writer == null) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } + if (listOfCommandMetadata == null) { - throw PSTraceSource.NewArgumentNullException("listOfCommandMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(listOfCommandMetadata)); } - this.GenerateSectionSeparator(writer); + GenerateSectionSeparator(writer); List 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("listOfCommandMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(listOfCommandMetadata)); } - List listOfCommandNames = new List(); + List listOfCommandNames = new(); foreach (CommandMetadata commandMetadata in listOfCommandMetadata) { listOfCommandNames.Add(commandMetadata.Name); } + return listOfCommandNames; } - private string GenerateArrayString(IEnumerable listOfStrings) + private static string GenerateArrayString(IEnumerable listOfStrings) { if (listOfStrings == null) { - throw PSTraceSource.NewArgumentNullException("listOfStrings"); + throw PSTraceSource.NewArgumentNullException(nameof(listOfStrings)); } - StringBuilder arrayString = new StringBuilder(); + StringBuilder arrayString = new(); foreach (string s in listOfStrings) { if (arrayString.Length != 0) { arrayString.Append(", "); } + arrayString.Append('\''); arrayString.Append(CodeGeneration.EscapeSingleQuotedStringContent(s)); arrayString.Append('\''); } arrayString.Insert(0, "@("); - arrayString.Append(")"); + arrayString.Append(')'); return arrayString.ToString(); } @@ -2921,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) { @@ -2944,18 +2989,19 @@ 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) { - throw PSTraceSource.NewArgumentNullException("writer"); + throw PSTraceSource.NewArgumentNullException(nameof(writer)); } + if (listOfFormatData == null) { - throw PSTraceSource.NewArgumentNullException("listOfFormatData"); + throw PSTraceSource.NewArgumentNullException(nameof(listOfFormatData)); } - XmlWriterSettings settings = new XmlWriterSettings(); + XmlWriterSettings settings = new(); settings.CloseOutput = false; settings.ConformanceLevel = ConformanceLevel.Document; settings.Encoding = writer.Encoding; @@ -2971,18 +3017,18 @@ private void GenerateFormatFile(TextWriter writer, List /// /// Generates a proxy module in the given directory. /// - /// base directory for the module - /// filename prefix for module files - /// encoding of generated files - /// whether to overwrite files - /// remote commands to generate proxies for - /// dictionary mapping alias names to resolved command names - /// remote format data to generate format.ps1xml for - /// certificate with which to sign the format files - /// Path to the created files + /// Base directory for the module. + /// Filename prefix for module files. + /// Encoding of generated files. + /// Whether to overwrite files. + /// Remote commands to generate proxies for. + /// Dictionary mapping alias names to resolved command names. + /// Remote format data to generate format.ps1xml for. + /// Certificate with which to sign the format files. + /// Path to the created files. internal List GenerateProxyModule( DirectoryInfo moduleRootDirectory, - String fileNamePrefix, + string fileNamePrefix, Encoding encoding, bool force, List listOfCommandMetadata, @@ -2990,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"); @@ -3000,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); @@ -3021,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); @@ -3050,7 +3090,7 @@ internal List GenerateProxyModule( } else { - String currentFile = baseName + ".psm1"; + string currentFile = baseName + ".psm1"; try { SignatureHelper.SignFile(SigningOption.Default, currentFile, certificate, string.Empty, null); @@ -3066,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, @@ -3087,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 ea6fb4dc21e..c4e423ffd1d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -1,29 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// 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.Internal; using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Internal; +using System.Management.Automation.Security; namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "import-localizeddata" cmdlet + /// The implementation of the "import-localizeddata" cmdlet. /// - /// - [Cmdlet(VerbsData.Import, "LocalizedData", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113342")] + [Cmdlet(VerbsData.Import, "LocalizedData", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096710")] public sealed class ImportLocalizedData : PSCmdlet { #region Parameters /// - /// The path from which to import the aliases + /// The path from which to import the aliases. /// - /// [Parameter(Position = 0)] [Alias("Variable")] [ValidateNotNullOrEmpty] @@ -39,12 +38,12 @@ public string BindingVariable _bindingVariable = value; } } + private string _bindingVariable; /// /// The scope to import the aliases to. /// - /// [Parameter(Position = 1)] public string UICulture { @@ -58,12 +57,12 @@ public string UICulture _uiculture = value; } } + private string _uiculture; /// /// The scope to import the aliases to. /// - /// [Parameter] public string BaseDirectory { @@ -77,12 +76,12 @@ public string BaseDirectory _baseDirectory = value; } } + private string _baseDirectory; /// /// The scope to import the aliases to. /// - /// [Parameter] public string FileName { @@ -96,13 +95,14 @@ public string FileName _fileName = value; } } + private string _fileName; /// - /// The command allowed in the data file. If unspecified, then ConvertFrom-StringData - /// is allowed. + /// The command allowed in the data file. If unspecified, then ConvertFrom-StringData is allowed. /// [Parameter] + [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] SupportedCommand { @@ -110,12 +110,14 @@ public string[] SupportedCommand { return _commandsAllowed; } + set { _setSupportedCommand = true; _commandsAllowed = value; } } + private string[] _commandsAllowed = new string[] { "ConvertFrom-StringData" }; private bool _setSupportedCommand = false; @@ -126,7 +128,6 @@ public string[] SupportedCommand /// /// The main processing loop of the command. /// - /// protected override void ProcessRecord() { string path = GetFilePath(); @@ -147,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( @@ -157,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); @@ -187,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(); @@ -213,8 +221,15 @@ protected override void ProcessRecord() else { variable.Value = result; + + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + { + // Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in + // a module scope, if it's necessary. + ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(variable, scope, Context.EngineSessionState); + } } - } // end _bindingvariable != null + } // If binding variable is null, write the object to stream else @@ -233,13 +248,13 @@ protected override void ProcessRecord() } return; - } // ProcessRecord + } private string GetFilePath() { - if (String.IsNullOrEmpty(_fileName)) + if (string.IsNullOrEmpty(_fileName)) { - if (InvocationExtent == null || String.IsNullOrEmpty(InvocationExtent.File)) + if (InvocationExtent == null || string.IsNullOrEmpty(InvocationExtent.File)) { throw PSTraceSource.NewInvalidOperationException(ImportLocalizedDataStrings.NotCalledFromAScriptFile); } @@ -247,9 +262,9 @@ private string GetFilePath() string dir = _baseDirectory; - if (String.IsNullOrEmpty(dir)) + if (string.IsNullOrEmpty(dir)) { - if (InvocationExtent != null && !String.IsNullOrEmpty(InvocationExtent.File)) + if (InvocationExtent != null && !string.IsNullOrEmpty(InvocationExtent.File)) { dir = Path.GetDirectoryName(InvocationExtent.File); } @@ -262,13 +277,13 @@ private string GetFilePath() dir = PathUtils.ResolveFilePath(dir, this); string fileName = _fileName; - if (String.IsNullOrEmpty(fileName)) + if (string.IsNullOrEmpty(fileName)) { fileName = InvocationExtent.File; } else { - if (!String.IsNullOrEmpty(Path.GetDirectoryName(fileName))) + if (!string.IsNullOrEmpty(Path.GetDirectoryName(fileName))) { throw PSTraceSource.NewInvalidOperationException(ImportLocalizedDataStrings.FileNameParameterCannotHavePath); } @@ -276,7 +291,7 @@ private string GetFilePath() fileName = Path.GetFileNameWithoutExtension(fileName); - CultureInfo culture = null; + CultureInfo culture; if (_uiculture == null) { culture = CultureInfo.CurrentUICulture; @@ -293,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); + + if (File.Exists(filePath)) + { + return filePath; + } - currentCulture = currentCulture.Parent; + currentCulture = currentCulture.Parent; + } } filePath = Path.Combine(dir, fullFileName); @@ -334,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(); } @@ -374,6 +403,5 @@ private string GetScript(string filePath) } #endregion Command code - } // class ImportLocalizedData -}//Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs index 332742d08d7..657d00b4fc5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -13,10 +12,9 @@ namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "import-alias" cmdlet + /// The implementation of the "import-alias" cmdlet. /// - /// - [Cmdlet(VerbsData.Import, "Alias", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113339")] + [Cmdlet(VerbsData.Import, "Alias", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097125")] [OutputType(typeof(AliasInfo))] public class ImportAliasCommand : PSCmdlet { @@ -29,18 +27,16 @@ public class ImportAliasCommand : PSCmdlet #region Parameters /// - /// The path from which to import the aliases + /// The path from which to import the aliases. /// - /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")] public string Path { get; set; } /// - /// The literal path from which to import the aliases + /// The literal path from which to import the aliases. /// - /// [Parameter(Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = LiteralPathParameterSetName)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string LiteralPath { get @@ -57,16 +53,14 @@ public string LiteralPath /// /// The scope to import the aliases to. /// - /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// - /// If set to true, the alias that is set is passed to the - /// pipeline. + /// If set to true, the alias that is set is passed to the pipeline. /// - /// [Parameter] public SwitchParameter PassThru { @@ -80,13 +74,13 @@ public SwitchParameter PassThru _passThru = value; } } + private bool _passThru; /// /// If set to true and an existing alias of the same name exists /// and is ReadOnly, the alias will be overwritten. /// - /// [Parameter] public SwitchParameter Force { @@ -100,6 +94,7 @@ public SwitchParameter Force _force = value; } } + private bool _force; #endregion Parameters @@ -109,7 +104,6 @@ public SwitchParameter Force /// /// The main processing loop of the command. /// - /// protected override void ProcessRecord() { Collection importedAliases = GetAliasesFromFile(this.ParameterSetName.Equals(LiteralPathParameterSetName, @@ -132,7 +126,7 @@ protected override void ProcessRecord() if (!Force) { AliasInfo existingAlias = null; - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { existingAlias = SessionState.Internal.GetAlias(alias.Name); } @@ -161,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", @@ -179,7 +173,7 @@ protected override void ProcessRecord() { continue; } - } // if (!Force) + } // Set the alias in the specified scope or the // current scope. @@ -188,7 +182,7 @@ protected override void ProcessRecord() try { - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { result = SessionState.Internal.SetAliasItem(alias, Force, MyInvocation.CommandOrigin); } @@ -229,9 +223,10 @@ protected override void ProcessRecord() WriteObject(result); } } - } // ProcessRecord + } private Dictionary _existingCommands; + private Dictionary ExistingCommands { get @@ -239,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, @@ -259,13 +254,14 @@ private Dictionary ExistingCommands } } } + return _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; @@ -273,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", @@ -294,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) { @@ -332,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, @@ -357,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, @@ -369,21 +365,23 @@ private Collection GetAliasesFromFile(bool isLiteralPath) } AliasInfo newAlias = - new AliasInfo( + new( values[0], values[1], Context, options); - if (!String.IsNullOrEmpty(values[2])) + if (!string.IsNullOrEmpty(values[2])) { newAlias.Description = values[2]; } result.Add(newAlias); } + reader.Dispose(); } + return result; } @@ -430,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) @@ -454,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, @@ -478,9 +476,9 @@ private static bool OnlyContainsWhitespace(string line) result = false; break; } + return result; } #endregion Command code - } // class ImportAliasCommand -}//Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs index d0418892bdb..5661df24fa9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; @@ -12,39 +15,45 @@ namespace Microsoft.PowerShell.Commands HelpUri = "https://go.microsoft.com/fwlink/?LinkID=623621", RemotingCapability = RemotingCapability.None)] public class ImportPowerShellDataFileCommand : PSCmdlet { - bool _isLiteralPath; + private bool _isLiteralPath; /// - /// Path specified, using globbing to resolve + /// Path specified, using globbing to resolve. /// - [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ByPath")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ByPath")] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Path { get; set; } /// - /// Specifies a path to one or more locations, without globbing + /// Specifies a path to one or more locations, without globbing. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ByLiteralPath", ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { get { return _isLiteralPath ? Path : null; } + set { _isLiteralPath = true; Path = value; } } /// - /// For each path, resolve it, parse it and write all hashtables - /// to the output stream + /// 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. /// protected override void ProcessRecord() { foreach (var path in Path) { var resolved = PathUtils.ResolveFilePath(path, this, _isLiteralPath); - if(!string.IsNullOrEmpty(resolved) && System.IO.File.Exists(resolved)) + if (!string.IsNullOrEmpty(resolved) && System.IO.File.Exists(resolved)) { Token[] tokens; ParseError[] errors; @@ -55,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 { @@ -75,18 +84,18 @@ protected override void ProcessRecord() private void WritePathNotFoundError(string path) { - var errorId = "PathNotFound"; - var errorCategory = ErrorCategory.InvalidArgument; - var errorMessage = string.Format(UtilityResources.PathDoesNotExist, path); + 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); WriteError(errorRecord); } - void WriteInvalidDataFileError(string resolvedPath, string errorId) - { - var errorCategory = ErrorCategory.InvalidData; - var errorMessage = string.Format(UtilityResources.CouldNotParseAsPowerShellDataFile, resolvedPath); + private void WriteInvalidDataFileError(string resolvedPath, string errorId) + { + 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); WriteError(errorRecord); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeCommandCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeCommandCmdlet.cs deleted file mode 100644 index e415dc7de24..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeCommandCmdlet.cs +++ /dev/null @@ -1,57 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Management.Automation; -using System.Management.Automation.Internal; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// Class implementing Invoke-Expression - /// - [Cmdlet(VerbsLifecycle.Invoke, "Expression", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113343")] - public sealed - class - InvokeExpressionCommand : PSCmdlet - { - #region parameters - - /// - /// Command to execute. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] - public string Command { get; set; } - - #endregion parameters - - /// - /// For each record, execute it, and push the results into the - /// success stream. - /// - protected override void ProcessRecord() - { - Diagnostics.Assert(null != Command, "Command is null"); - - ScriptBlock myScriptBlock = InvokeCommand.NewScriptBlock(Command); - - // If the runspace has ever been in ConstrainedLanguage, lock down this - // invocation as well - it is too easy for the command to be negatively influenced - // by malicious input (such as ReadOnly + Constant variables) - if (Context.HasRunspaceEverUsedConstrainedLanguageMode) - { - myScriptBlock.LanguageMode = PSLanguageMode.ConstrainedLanguage; - } - - var emptyArray = Utils.EmptyArray(); - myScriptBlock.InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: false, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: emptyArray, - scriptThis: AutomationNull.Value, - args: emptyArray); - } - } -} // namespace Microsoft.PowerShell.Commands diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs new file mode 100644 index 00000000000..acbffeb66f4 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/InvokeExpressionCommand.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Security; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Class implementing Invoke-Expression. + /// + [Cmdlet(VerbsLifecycle.Invoke, "Expression", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097030")] + public sealed + class + InvokeExpressionCommand : PSCmdlet + { + #region parameters + + /// + /// Command to execute. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [ValidateTrustedData] + public string Command { get; set; } + + #endregion parameters + + /// + /// For each record, execute it, and push the results into the success stream. + /// + protected override void ProcessRecord() + { + Diagnostics.Assert(Command != null, "Command is null"); + + ScriptBlock myScriptBlock = InvokeCommand.NewScriptBlock(Command); + + // If the runspace has ever been in ConstrainedLanguage, lock down this + // invocation as well - it is too easy for the command to be negatively influenced + // by malicious input (such as ReadOnly + Constant variables) + if (Context.HasRunspaceEverUsedConstrainedLanguageMode) + { + 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, + useLocalScope: false, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: AutomationNull.Value, + input: emptyArray, + scriptThis: AutomationNull.Value, + args: emptyArray); + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs new file mode 100644 index 00000000000..80a04b07f17 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.Commands.Utility +{ + /// + /// Join-Object implementation. + /// + [Cmdlet(VerbsCommon.Join, "String", RemotingCapability = RemotingCapability.None, DefaultParameterSetName = "default")] + [OutputType(typeof(string))] + 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(DefaultOutputStringCapacity); + private CultureInfo _cultureInfo = CultureInfo.InvariantCulture; + private string _separator; + private char _quoteChar; + private bool _firstInputObject = true; + + /// + /// Gets or sets the property name or script block to use as the value to join. + /// + [Parameter(Position = 0)] + [ArgumentCompleter(typeof(PropertyNameCompleter))] + public PSPropertyExpression Property { get; set; } + + /// + /// Gets or sets the delimiter to join the output with. + /// + [Parameter(Position = 1)] + [ArgumentCompleter(typeof(SeparatorArgumentCompleter))] + [AllowEmptyString] + public string Separator + { + get => _separator ?? LanguagePrimitives.ConvertTo(GetVariableValue("OFS")); + set => _separator = value; + } + + /// + /// Gets or sets text to include before the joined input text. + /// + [Parameter] + [Alias("op")] + public string OutputPrefix { get; set; } + + /// + /// Gets or sets text to include after the joined input text. + /// + [Parameter] + [Alias("os")] + public string OutputSuffix { get; set; } + + /// + /// Gets or sets if the output items should we wrapped in single quotes. + /// + [Parameter(ParameterSetName = "SingleQuote")] + public SwitchParameter SingleQuote { get; set; } + + /// + /// Gets or sets if the output items should we wrapped in double quotes. + /// + [Parameter(ParameterSetName = "DoubleQuote")] + public SwitchParameter DoubleQuote { get; set; } + + /// + /// Gets or sets a format string that is applied to each input object. + /// + [Parameter(ParameterSetName = "Format")] + [ArgumentCompleter(typeof(FormatStringArgumentCompleter))] + public string FormatString { get; set; } + + /// + /// Gets or sets if the current culture should be used with formatting instead of the invariant culture. + /// + [Parameter] + public SwitchParameter UseCulture { get; set; } + + /// + /// Gets or sets the input object to join into text. + /// + [Parameter(ValueFromPipeline = true)] + public PSObject[] InputObject { get; set; } + + /// + protected override void BeginProcessing() + { + _quoteChar = SingleQuote ? '\'' : DoubleQuote ? '"' : char.MinValue; + _outputBuilder.Append(OutputPrefix); + if (UseCulture) + { + _cultureInfo = CultureInfo.CurrentCulture; + } + } + + /// + protected override void ProcessRecord() + { + if (InputObject != null) + { + foreach (PSObject inputObject in InputObject) + { + if (inputObject != null && inputObject != AutomationNull.Value) + { + var inputValue = Property == null + ? inputObject + : Property.GetValues(inputObject, false, true).FirstOrDefault()?.Result; + + // conversion to string always succeeds. + if (!LanguagePrimitives.TryConvertTo(inputValue, _cultureInfo, out var stringValue)) + { + throw new PSInvalidCastException("InvalidCastFromAnyTypeToString", ExtendedTypeSystem.InvalidCastCannotRetrieveString, null); + } + + if (_firstInputObject) + { + _firstInputObject = false; + } + else + { + _outputBuilder.Append(Separator); + } + + if (_quoteChar != char.MinValue) + { + _outputBuilder.Append(_quoteChar); + _outputBuilder.Append(stringValue); + _outputBuilder.Append(_quoteChar); + } + else if (string.IsNullOrEmpty(FormatString)) + { + _outputBuilder.Append(stringValue); + } + else + { + _outputBuilder.AppendFormat(_cultureInfo, FormatString, inputValue); + } + } + } + } + } + + /// + protected override void EndProcessing() + { + _outputBuilder.Append(OutputSuffix); + WriteObject(_outputBuilder.ToString()); + } + } + + /// + /// 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) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_separatorValues, + displayInfoMapper: SeparatorDisplayInfoMapper, + resultType: CompletionResultType.ParameterValue); + } + + /// + /// 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) + { + "[{0}]", + "{0:N2}", +#if UNIX + "`n `${0}", + "`n [string] `${0}", +#else + "`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 new file mode 100644 index 00000000000..cd18f33c41d --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; + +using Microsoft.PowerShell.MarkdownRender; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Class for implementing Set-MarkdownOption cmdlet. + /// + [Cmdlet( + VerbsCommon.Set, "MarkdownOption", + DefaultParameterSetName = IndividualSetting, + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2006265")] + [OutputType(typeof(Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo))] + public class SetMarkdownOptionCommand : PSCmdlet + { + /// + /// Gets or sets the VT100 escape sequence for Header Level 1. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header1Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for Header Level 2. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header2Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for Header Level 3. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header3Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for Header Level 4. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header4Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for Header Level 5. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header5Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for Header Level 6. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Header6Color { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for code block background. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string Code { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for image alt text foreground. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string ImageAltTextForegroundColor { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for link foreground. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string LinkForegroundColor { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for italics text foreground. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string ItalicsForegroundColor { get; set; } + + /// + /// Gets or sets the VT100 escape sequence for bold text foreground. + /// + [ValidatePattern(@"^\[*[0-9;]*?m{1}")] + [Parameter(ParameterSetName = IndividualSetting)] + public string BoldForegroundColor { get; set; } + + /// + /// Gets or sets the switch to PassThru the values set. + /// + [Parameter] + public SwitchParameter PassThru { get; set; } + + /// + /// Gets or sets the Theme. + /// + [ValidateNotNullOrEmpty] + [Parameter(ParameterSetName = ThemeParamSet, Mandatory = true)] + [ValidateSet(DarkThemeName, LightThemeName)] + public string Theme { get; set; } + + /// + /// Gets or sets InputObject. + /// + [ValidateNotNullOrEmpty] + [Parameter(ParameterSetName = InputObjectParamSet, Mandatory = true, ValueFromPipeline = true, Position = 0)] + public PSObject InputObject { get; set; } + + private const string IndividualSetting = "IndividualSetting"; + private const string InputObjectParamSet = "InputObject"; + private const string ThemeParamSet = "Theme"; + private const string LightThemeName = "Light"; + private const string DarkThemeName = "Dark"; + + /// + /// Override EndProcessing. + /// + protected override void EndProcessing() + { + PSMarkdownOptionInfo mdOptionInfo = null; + + switch (ParameterSetName) + { + case ThemeParamSet: + mdOptionInfo = new PSMarkdownOptionInfo(); + if (string.Equals(Theme, LightThemeName, StringComparison.OrdinalIgnoreCase)) + { + mdOptionInfo.SetLightTheme(); + } + else if (string.Equals(Theme, DarkThemeName, StringComparison.OrdinalIgnoreCase)) + { + mdOptionInfo.SetDarkTheme(); + } + + break; + + case InputObjectParamSet: + object baseObj = InputObject.BaseObject; + mdOptionInfo = baseObj as PSMarkdownOptionInfo; + + if (mdOptionInfo == null) + { + var errorMessage = StringUtil.Format(ConvertMarkdownStrings.InvalidInputObjectType, baseObj.GetType()); + + ErrorRecord errorRecord = new( + new ArgumentException(errorMessage), + "InvalidObject", + ErrorCategory.InvalidArgument, + InputObject); + } + + break; + + case IndividualSetting: + mdOptionInfo = new PSMarkdownOptionInfo(); + SetOptions(mdOptionInfo); + break; + } + + var setOption = PSMarkdownOptionInfoCache.Set(this.CommandInfo, mdOptionInfo); + + if (PassThru.IsPresent) + { + WriteObject(setOption); + } + } + + private void SetOptions(PSMarkdownOptionInfo mdOptionInfo) + { + if (!string.IsNullOrEmpty(Header1Color)) + { + mdOptionInfo.Header1 = Header1Color; + } + + if (!string.IsNullOrEmpty(Header2Color)) + { + mdOptionInfo.Header2 = Header2Color; + } + + if (!string.IsNullOrEmpty(Header3Color)) + { + mdOptionInfo.Header3 = Header3Color; + } + + if (!string.IsNullOrEmpty(Header4Color)) + { + mdOptionInfo.Header4 = Header4Color; + } + + if (!string.IsNullOrEmpty(Header5Color)) + { + mdOptionInfo.Header5 = Header5Color; + } + + if (!string.IsNullOrEmpty(Header6Color)) + { + mdOptionInfo.Header6 = Header6Color; + } + + if (!string.IsNullOrEmpty(Code)) + { + mdOptionInfo.Code = Code; + } + + if (!string.IsNullOrEmpty(ImageAltTextForegroundColor)) + { + mdOptionInfo.Image = ImageAltTextForegroundColor; + } + + if (!string.IsNullOrEmpty(LinkForegroundColor)) + { + mdOptionInfo.Link = LinkForegroundColor; + } + + if (!string.IsNullOrEmpty(ItalicsForegroundColor)) + { + mdOptionInfo.EmphasisItalics = ItalicsForegroundColor; + } + + if (!string.IsNullOrEmpty(BoldForegroundColor)) + { + mdOptionInfo.EmphasisBold = BoldForegroundColor; + } + } + } + + /// + /// Implements the cmdlet for getting the Markdown options that are set. + /// + [Cmdlet( + VerbsCommon.Get, "MarkdownOption", + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2006371")] + [OutputType(typeof(Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo))] + public class GetMarkdownOptionCommand : PSCmdlet + { + private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo"; + + /// + /// Override EndProcessing. + /// + protected override void EndProcessing() + { + WriteObject(PSMarkdownOptionInfoCache.Get(this.CommandInfo)); + } + } + + /// + /// The class manages whether we should use a module scope variable or concurrent dictionary for storing the set PSMarkdownOptions. + /// When we have a moduleInfo available we use the module scope variable. + /// In case of built-in modules, they are loaded as snapins when we are hosting PowerShell. + /// We use runspace Id as the key for the concurrent dictionary to have the functionality of separate settings per runspace. + /// Force loading the module does not unload the nested modules and hence we cannot use IModuleAssemblyCleanup to remove items from the dictionary. + /// Because of these reason, we continue using module scope variable when moduleInfo is available. + /// + internal static class PSMarkdownOptionInfoCache + { + private static readonly ConcurrentDictionary markdownOptionInfoCache; + + private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo"; + + static PSMarkdownOptionInfoCache() + { + markdownOptionInfoCache = new ConcurrentDictionary(); + } + + internal static PSMarkdownOptionInfo Get(CommandInfo command) + { + // If we have the moduleInfo then store are module scope variable + if (command.Module != null) + { + return command.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; + } + + // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + if (markdownOptionInfoCache.TryGetValue(Runspace.DefaultRunspace.InstanceId, out PSMarkdownOptionInfo cachedOption)) + { + // return the cached options for the runspaceId + return cachedOption; + } + else + { + // no option cache so cache and return the default PSMarkdownOptionInfo + var newOptionInfo = new PSMarkdownOptionInfo(); + return markdownOptionInfoCache.GetOrAdd(Runspace.DefaultRunspace.InstanceId, newOptionInfo); + } + } + + internal static PSMarkdownOptionInfo Set(CommandInfo command, PSMarkdownOptionInfo optionInfo) + { + // If we have the moduleInfo then store are module scope variable + if (command.Module != null) + { + command.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, optionInfo); + return optionInfo; + } + + // If we don't have a moduleInfo, like in PowerShell hosting scenarios with modules loaded as snapins, use a concurrent dictionary. + return markdownOptionInfoCache.AddOrUpdate(Runspace.DefaultRunspace.InstanceId, optionInfo, (key, oldvalue) => optionInfo); + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index 17adfd80f85..262fd44b30f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs @@ -1,18 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.Text.RegularExpressions; -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Globalization; -using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.RegularExpressions; namespace Microsoft.PowerShell.Commands { @@ -26,40 +24,33 @@ internal MatchInfoContext() } /// - /// Lines found before a match. + /// Gets or sets the lines found before a match. /// - - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] PreContext { get; set; } /// - /// Lines found after a match. + /// Gets or sets the lines found after a match. /// - - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] PostContext { get; set; } /// - /// Lines found before a match. Does not include + /// Gets or sets the lines found before a match. Does not include /// overlapping context and thus can be used to /// display contiguous match regions. /// - - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] DisplayPreContext { get; set; } /// - /// Lines found after a match. Does not include + /// Gets or sets the lines found after a match. Does not include /// overlapping context and thus can be used to /// display contiguous match regions. /// - - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] DisplayPostContext { get; set; } /// /// Produce a deep copy of this object. /// + /// A new object that is a copy of this instance. public object Clone() { return new MatchInfoContext() @@ -77,98 +68,136 @@ public object Clone() /// public class MatchInfo { - private static string s_inputStream = "InputStream"; + private static readonly string s_inputStream = "InputStream"; /// - /// Indicates if the match was done ignoring case. + /// Gets or sets a value indicating whether the match was done ignoring case. /// /// True if case was ignored. public bool IgnoreCase { get; set; } /// - /// Returns the number of the matching line. + /// 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; } /// - /// Returns the text of the matching line. + /// Gets or sets the text of the matching line. /// /// The text of the matching line. - public string Line { get; set; } = ""; + public string Line { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether the matched portion of the string is highlighted. + /// + /// Whether the matched portion of the string is highlighted with the negative VT sequence. + private readonly bool _emphasize; + + /// + /// Stores the starting index of each match within the line. + /// + private readonly IReadOnlyList _matchIndexes; + + /// + /// Stores the length of each match within the line. + /// + private readonly IReadOnlyList _matchLengths; + + /// + /// Initializes a new instance of the class with emphasis disabled. + /// + public MatchInfo() + { + this._emphasize = false; + } + + /// + /// Initializes a new instance of the class with emphasized matched text. + /// Used when virtual terminal sequences are supported. + /// + /// Sets the matchIndexes. + /// Sets the matchLengths. + public MatchInfo(IReadOnlyList matchIndexes, IReadOnlyList matchLengths) + { + this._emphasize = true; + this._matchIndexes = matchIndexes; + this._matchLengths = matchLengths; + } /// - /// Returns the base name of the file containing the matching line. + /// 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. + /// This is a readonly property calculated from the path . /// - /// - /// The file name + /// The file name. public string Filename { get { if (!_pathSet) + { return s_inputStream; - return _filename ?? (_filename = System.IO.Path.GetFileName(_path)); + } + + return _filename ??= System.IO.Path.GetFileName(_path); } } - private string _filename; + private string _filename; /// - /// The full path of the file containing the matching line. + /// 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 + /// The path name. public string Path { - get - { - if (!_pathSet) - return s_inputStream; - return _path; - } + get => _pathSet ? _path : s_inputStream; set { _path = value; _pathSet = true; } } + private string _path = s_inputStream; + private bool _pathSet; /// - /// Returns the pattern that was used in the match. + /// Gets or sets the pattern that was used in the match. /// - /// The pattern string + /// The pattern string. public string Pattern { get; set; } /// - /// The context for the match, or null if -context was not - /// specified. + /// Gets or sets context for the match, or null if -context was not specified. /// public MatchInfoContext Context { get; set; } /// /// 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 routine would return bar\baz.c . /// - /// /// The directory base the truncation on. /// The relative path that was produced. public string RelativePath(string directory) { if (!_pathSet) + { return this.Path; + } string relPath = _path; - if (!String.IsNullOrEmpty(directory)) + if (!string.IsNullOrEmpty(directory)) { if (relPath.StartsWith(directory, StringComparison.OrdinalIgnoreCase)) { @@ -176,12 +205,17 @@ public string RelativePath(string directory) if (offset < relPath.Length) { if (directory[offset - 1] == '\\' || directory[offset - 1] == '/') + { relPath = relPath.Substring(offset); + } else if (relPath[offset] == '\\' || relPath[offset] == '/') + { relPath = relPath.Substring(offset + 1); + } } } } + return relPath; } @@ -198,13 +232,13 @@ 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 + /// The string representation of the match object. public override string ToString() { return ToString(null); @@ -214,9 +248,22 @@ public override string ToString() /// Returns the string representation of the match object same format as ToString() /// but trims the path to be relative to the argument. /// - /// Directory to use as the root when calculating the relative path - /// The string representation of the match object + /// Directory to use as the root when calculating the relative path. + /// The string representation of the match object. public string ToString(string directory) + { + return ToString(directory, Line); + } + + /// + /// Returns the string representation of the match object with the matched line passed + /// in as and trims the path to be relative to + /// the argument. + /// + /// Directory to use as the root when calculating the relative path. + /// Line that the match occurs in. + /// The string representation of the match object. + private string ToString(string directory, string line) { string displayPath = (directory != null) ? RelativePath(directory) : _path; @@ -224,26 +271,81 @@ public string ToString(string directory) // enable context-tracking. if (Context == null) { - return FormatLine(Line, this.LineNumber, displayPath, EmptyPrefix); + return FormatLine(line, this.LineNumber, displayPath, EmptyPrefix); } // 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)); } - lines.Add(FormatLine(Line, displayLineNumber++, displayPath, MatchPrefix)); + lines.Add(FormatLine(line, displayLineNumber++, displayPath, MatchPrefix)); foreach (string contextLine in Context.DisplayPostContext) { lines.Add(FormatLine(contextLine, displayLineNumber++, displayPath, ContextPrefix)); } - return String.Join(System.Environment.NewLine, lines.ToArray()); + return string.Join(System.Environment.NewLine, lines.ToArray()); + } + + /// + /// Returns the string representation of the match object same format as ToString() + /// and inverts the color of the matched text if virtual terminal is supported. + /// + /// Directory to use as the root when calculating the relative path. + /// The string representation of the match object with matched text inverted. + public string ToEmphasizedString(string directory) + { + if (!_emphasize) + { + return ToString(directory); + } + + return ToString(directory, EmphasizeLine()); + } + + /// + /// Surrounds the matched text with virtual terminal sequences to invert it's color. Used in ToEmphasizedString. + /// + /// The matched line with matched text inverted. + private string EmphasizeLine() + { + 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; + int charsIndex = 0; + for (int i = 0; i < _matchIndexes.Count; i++) + { + // Adds characters before match + Line.CopyTo(lineIndex, chars, charsIndex, _matchIndexes[i] - lineIndex); + charsIndex += _matchIndexes[i] - lineIndex; + lineIndex = _matchIndexes[i]; + + // Adds opening vt sequence + invertColorsVT100.CopyTo(0, chars, charsIndex, invertColorsVT100.Length); + charsIndex += invertColorsVT100.Length; + + // Adds characters being emphasized + Line.CopyTo(lineIndex, chars, charsIndex, _matchLengths[i]); + lineIndex += _matchLengths[i]; + charsIndex += _matchLengths[i]; + + // Adds closing vt sequence + resetVT100.CopyTo(0, chars, charsIndex, resetVT100.Length); + charsIndex += resetVT100.Length; + } + + // Adds remaining characters in line + Line.CopyTo(lineIndex, chars, charsIndex, Line.Length - lineIndex); + + return new string(chars); } /// @@ -254,23 +356,22 @@ public string ToString(string directory) /// 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) { - if (_pathSet) - return StringUtil.Format(MatchFormat, prefix, displayPath, displayLineNumber, lineStr); - else - return StringUtil.Format(SimpleFormat, prefix, lineStr); + return _pathSet + ? StringUtil.Format(MatchFormat, prefix, displayPath, displayLineNumber, lineStr) + : StringUtil.Format(SimpleFormat, prefix, lineStr); } /// - /// A list of all Regex matches on the matching line. + /// Gets or sets a list of all Regex matches on the matching line. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Match[] Matches { get; set; } = new Match[] { }; + public Match[] Matches { get; set; } = Array.Empty(); /// /// Create a deep copy of this MatchInfo instance. /// + /// A new object that is a copy of this instance. internal MatchInfo Clone() { // Just do a shallow copy and then deep-copy the @@ -293,17 +394,27 @@ internal MatchInfo Clone() /// /// A cmdlet to search through strings and files for particular patterns. /// - [Cmdlet(VerbsCommon.Select, "String", DefaultParameterSetName = "File", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113388")] - [OutputType(typeof(MatchInfo), typeof(bool))] + [Cmdlet(VerbsCommon.Select, "String", DefaultParameterSetName = ParameterSetFile, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097119")] + [OutputType(typeof(bool), typeof(MatchInfo), ParameterSetName = new[] { ParameterSetFile, ParameterSetObject, ParameterSetLiteralFile })] + [OutputType(typeof(string), ParameterSetName = new[] { ParameterSetFileRaw, ParameterSetObjectRaw, ParameterSetLiteralFileRaw })] public sealed class SelectStringCommand : PSCmdlet { + private const string ParameterSetFile = "File"; + private const string ParameterSetFileRaw = "FileRaw"; + private const string ParameterSetObject = "Object"; + private const string ParameterSetObjectRaw = "ObjectRaw"; + private const string ParameterSetLiteralFile = "LiteralFile"; + private const string ParameterSetLiteralFileRaw = "LiteralFileRaw"; + /// /// A generic circular buffer. /// - private class CircularBuffer : ICollection + /// The type of items that are buffered. + private sealed class CircularBuffer : ICollection { // Ring of items - private T[] _items; + private readonly T[] _items; + // Current length, as opposed to the total capacity // Current start of the list. Starts at 0, but may // move forwards or wrap around back to 0 due to @@ -311,59 +422,46 @@ private class CircularBuffer : ICollection private int _firstIndex; /// - /// Construct a new buffer of the specified capacity. + /// 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("capacity"); + ArgumentOutOfRangeException.ThrowIfNegative(capacity); _items = new T[capacity]; Clear(); } /// - /// The maximum capacity of the buffer. If more items + /// Gets the maximum capacity of the buffer. If more items /// are added than the buffer has capacity for, then /// older items will be removed from the buffer with /// a first-in, first-out policy. /// - public int Capacity - { - get - { - return _items.Length; - } - } + public int Capacity => _items.Length; /// /// Whether or not the buffer is at capacity. /// - public bool IsFull - { - get - { - return Count == Capacity; - } - } + public bool IsFull => Count == Capacity; /// /// Convert from a 0-based index to a buffer index which /// 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) { if (Capacity == 0 || zeroBasedIndex < 0) { - throw new ArgumentOutOfRangeException("zeroBasedIndex"); + throw new ArgumentOutOfRangeException(nameof(zeroBasedIndex)); } return (zeroBasedIndex + _firstIndex) % Capacity; @@ -387,13 +485,7 @@ IEnumerator IEnumerable.GetEnumerator() #region ICollection implementation public int Count { get; private set; } - public bool IsReadOnly - { - get - { - return false; - } - } + public bool IsReadOnly => false; /// /// Adds an item to the buffer. If the buffer is already @@ -435,18 +527,15 @@ public bool Contains(T item) throw new NotImplementedException(); } - - [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] public void CopyTo(T[] array, int arrayIndex) { - if (array == null) - throw new ArgumentNullException("array"); - - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException("arrayIndex"); + ArgumentNullException.ThrowIfNull(array); + ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); if (Count > (array.Length - arrayIndex)) + { throw new ArgumentException("arrayIndex"); + } // Iterate through the buffer in correct order. foreach (T item in this) @@ -479,13 +568,14 @@ public T[] ToArray() /// internal ordering the buffer may be maintaining. /// /// The index of the item to access. + /// The buffered item at index . public T this[int index] { get { if (!(index >= 0 && index < Count)) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } return _items[WrapIndex(index)]; @@ -499,7 +589,7 @@ public T this[int index] private interface IContextTracker { /// - /// Matches with completed context information + /// Gets matches with completed context information /// that are ready to be emitted into the pipeline. /// IList EmitQueue { get; } @@ -527,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 { @@ -537,14 +627,14 @@ private enum ContextState } private ContextState _contextState = ContextState.InitialState; - private int _preContext = 0; - private int _postContext = 0; + private readonly int _preContext; + private readonly int _postContext; // The context leading up to the match. - private CircularBuffer _collectedPreContext = null; + private readonly CircularBuffer _collectedPreContext; // The context after the match. - private List _collectedPostContext = null; + private readonly List _collectedPostContext; // Current match info we are tracking postcontext for. // At any given time, if set, this value will not be @@ -552,10 +642,10 @@ private enum ContextState private MatchInfo _matchInfo = null; /// - /// Constructor for DisplayContextTracker. + /// Initializes a new instance of the class. /// - /// How much precontext to collect at most. - /// How much precontext to collect at most. + /// How much preContext to collect at most. + /// How much postContext to collect at most. public DisplayContextTracker(int preContext, int postContext) { _preContext = preContext; @@ -568,14 +658,9 @@ public DisplayContextTracker(int preContext, int postContext) } #region IContextTracker implementation - public IList EmitQueue - { - get - { - return _emitQueue; - } - } - private List _emitQueue = null; + public IList EmitQueue => _emitQueue; + + private readonly List _emitQueue; // Track non-matching line public void TrackLine(string line) @@ -596,6 +681,7 @@ public void TrackLine(string line) // Now we're done. UpdateQueue(); } + break; } } @@ -606,7 +692,9 @@ public void TrackMatch(MatchInfo match) // Update the queue in case we were in the middle // of collecting postcontext for an older match... if (_contextState == ContextState.CollectPost) + { UpdateQueue(); + } // Update the current matchInfo. _matchInfo = match; @@ -616,9 +704,13 @@ public void TrackMatch(MatchInfo match) // Otherwise, immediately move the match onto the queue // and let UpdateQueue update our state instead. if (_postContext > 0) + { _contextState = ContextState.CollectPost; + } else + { UpdateQueue(); + } } // Track having reached the end of the file. @@ -629,7 +721,9 @@ public void TrackEOF() // early since there are no more lines to track context // for. if (_contextState == ContextState.CollectPost) + { UpdateQueue(); + } } #endregion @@ -648,6 +742,7 @@ private void UpdateQueue() _matchInfo.Context.DisplayPreContext = _collectedPreContext.ToArray(); _matchInfo.Context.DisplayPostContext = _collectedPostContext.ToArray(); } + Reset(); } } @@ -677,17 +772,17 @@ private void Reset() /// a possibly-continuous set of matches by excluding /// overlapping context (lines will only appear once) /// and other matching lines (since they will appear - /// as their own match entries.) + /// 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 string Line = null; - public MatchInfo Match = null; + public readonly string Line; + public readonly MatchInfo Match; public ContextEntry(string line) { @@ -699,20 +794,18 @@ public ContextEntry(MatchInfo match) Match = match; } - public override string ToString() - { - return (Match != null) ? Match.Line : Line; - } + public override string ToString() => Match?.Line ?? Line; } // Whether or not early entries found // while still filling up the context buffer // have been added to the emit queue. // Used by UpdateQueue. - private bool _hasProcessedPreEntries = false; + private bool _hasProcessedPreEntries; + + private readonly int _preContext; + private readonly int _postContext; - private int _preContext; - private int _postContext; // A circular buffer tracking both precontext and postcontext. // // Essentially, the buffer is separated into regions: @@ -726,13 +819,13 @@ public override string ToString() // enough context to populate the Context properties of the // match. At that point, we will add the match object // to the emit queue. - private CircularBuffer _collectedContext = null; + private readonly CircularBuffer _collectedContext; /// - /// Constructor for LogicalContextTracker. + /// Initializes a new instance of the class. /// - /// How much precontext to collect at most. - /// How much postcontext to collect at most. + /// How much preContext to collect at most. + /// How much postContext to collect at most. public LogicalContextTracker(int preContext, int postContext) { _preContext = preContext; @@ -742,25 +835,20 @@ public LogicalContextTracker(int preContext, int postContext) } #region IContextTracker implementation - public IList EmitQueue - { - get - { - return _emitQueue; - } - } - private List _emitQueue = null; + public IList EmitQueue => _emitQueue; + + private readonly List _emitQueue; 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(); } @@ -777,8 +865,7 @@ public void TrackEOF() // If the buffer isn't full, then nothing will have // ever been emitted and everything is still waiting // on postcontext. So process the whole buffer. - - int startIndex = (_collectedContext.IsFull) ? _preContext + 1 : 0; + int startIndex = _collectedContext.IsFull ? _preContext + 1 : 0; EmitAllInRange(startIndex, _collectedContext.Count - 1); } #endregion @@ -786,7 +873,7 @@ public void TrackEOF() /// /// Add all matches found in the specified range /// to the emit queue, collecting as much context - /// as possible up to the limits specified in the ctor. + /// as possible up to the limits specified in the constructor. /// /// /// The range is inclusive; the entries at @@ -849,14 +936,13 @@ private void UpdateQueue() /// and adds it to the emit queue. /// /// - /// Context ranges must be within the bounds of the context - /// buffer. + /// Context ranges must be within the bounds of the context buffer. /// /// The match to operate on. - /// The start index of the precontext range. - /// The length of the precontext range. - /// The start index of the postcontext range. - /// The length of the precontext range. + /// The start index of the preContext range. + /// The length of the preContext range. + /// The start index of the postContext range. + /// The length of the postContext range. private void Emit(MatchInfo match, int preStartIndex, int preLength, int postStartIndex, int postLength) { if (match.Context != null) @@ -876,6 +962,7 @@ private void Emit(MatchInfo match, int preStartIndex, int preLength, int postSta /// /// The index to start at. /// The length of the range. + /// String representation of the collected context at the specified range. private string[] CopyContext(int startIndex, int length) { string[] result = new string[length]; @@ -892,16 +979,16 @@ 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 IContextTracker _displayTracker; - private IContextTracker _logicalTracker; + private readonly IContextTracker _displayTracker; + private readonly IContextTracker _logicalTracker; /// - /// Constructor for LogicalContextTracker. + /// Initializes a new instance of the class. /// - /// How much precontext to collect at most. - /// How much postcontext to collect at most. + /// How much preContext to collect at most. + /// How much postContext to collect at most. public ContextTracker(int preContext, int postContext) { _displayTracker = new DisplayContextTracker(preContext, postContext); @@ -948,7 +1035,6 @@ private void UpdateQueue() // time as the logical tracker, so we can // be sure the matches will have both logical // and display context already populated. - foreach (MatchInfo match in _logicalTracker.EmitQueue) { EmitQueue.Add(match); @@ -960,265 +1046,317 @@ private void UpdateQueue() } /// - /// This parameter specifies the current pipeline object + /// ContextTracker that does not work for the case when pre- and post context is 0. /// - [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = "Object")] - [AllowNull] - [AllowEmptyString] - public PSObject InputObject + private sealed class NoContextTracker : IContextTracker + { + private readonly IList _matches = new List(1); + + IList IContextTracker.EmitQueue => _matches; + + void IContextTracker.TrackLine(string line) + { + } + + void IContextTracker.TrackMatch(MatchInfo match) => _matches.Add(match); + + void IContextTracker.TrackEOF() + { + } + } + + /// + /// Gets or sets a culture name. + /// + [Parameter] + [ValidateSet(typeof(ValidateMatchStringCultureNamesGenerator))] + [ValidateNotNull] + public string Culture { get { - return _inputObject; + switch (_stringComparison) + { + case StringComparison.Ordinal: + case StringComparison.OrdinalIgnoreCase: + { + return OrdinalCultureName; + } + + case StringComparison.InvariantCulture: + case StringComparison.InvariantCultureIgnoreCase: + { + return InvariantCultureName; + } + + case StringComparison.CurrentCulture: + case StringComparison.CurrentCultureIgnoreCase: + { + return CurrentCultureName; + } + + default: + { + break; + } + } + + return _cultureName; } + set { - _inputObject = LanguagePrimitives.IsNull(value) ? PSObject.AsPSObject("") : value; + _cultureName = value; + InitCulture(); + } + } + + internal const string OrdinalCultureName = "Ordinal"; + internal const string InvariantCultureName = "Invariant"; + internal const string CurrentCultureName = "Current"; + + private string _cultureName = CultureInfo.CurrentCulture.Name; + private StringComparison _stringComparison = StringComparison.CurrentCultureIgnoreCase; + private CompareOptions _compareOptions = CompareOptions.IgnoreCase; + + private delegate int CultureInfoIndexOf(string source, string value, int startIndex, int count, CompareOptions options); + + private CultureInfoIndexOf _cultureInfoIndexOf = CultureInfo.CurrentCulture.CompareInfo.IndexOf; + + private void InitCulture() + { + _stringComparison = default; + + switch (_cultureName) + { + case OrdinalCultureName: + { + _stringComparison = CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + _compareOptions = CaseSensitive ? CompareOptions.Ordinal : CompareOptions.OrdinalIgnoreCase; + _cultureInfoIndexOf = CultureInfo.InvariantCulture.CompareInfo.IndexOf; + break; + } + + case InvariantCultureName: + { + _stringComparison = CaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase; + _compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + _cultureInfoIndexOf = CultureInfo.InvariantCulture.CompareInfo.IndexOf; + break; + } + + case CurrentCultureName: + { + _stringComparison = CaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; + _compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + _cultureInfoIndexOf = CultureInfo.CurrentCulture.CompareInfo.IndexOf; + break; + } + + default: + { + var _cultureInfo = CultureInfo.GetCultureInfo(_cultureName); + _compareOptions = CaseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase; + _cultureInfoIndexOf = _cultureInfo.CompareInfo.IndexOf; + break; + } } } + + /// + /// Gets or sets the current pipeline object. + /// + [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = ParameterSetObject)] + [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = ParameterSetObjectRaw)] + [AllowNull] + [AllowEmptyString] + public PSObject InputObject + { + get => _inputObject; + set => _inputObject = LanguagePrimitives.IsNull(value) ? PSObject.AsPSObject(string.Empty) : value; + } + private PSObject _inputObject = AutomationNull.Value; /// - /// String index to start from the beginning. - /// - /// If the value is negative, the length is counted from the - /// end of the string. + /// Gets or sets the patterns to find. /// - /// [Parameter(Mandatory = true, Position = 0)] public string[] Pattern { get; set; } private Regex[] _regexPattern; /// - /// file to read from - /// Globbing is done on these + /// Gets or sets files to read from. + /// Globbing is done on these. /// - [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "File")] + [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetFile)] + [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetFileRaw)] [FileinfoToString] public string[] Path { get; set; } /// - /// Literal file to read from - /// Globbing is not done on these + /// Gets or sets literal files to read from. + /// Globbing is not done on these. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "LiteralFile")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetLiteralFile)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetLiteralFileRaw)] [FileinfoToString] - [Alias("PSPath")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + [Alias("PSPath", "LP")] public string[] LiteralPath { - get - { - return Path; - } + get => Path; set { Path = value; _isLiteralPath = true; } } - private bool _isLiteralPath = false; - /// If set, match pattern string literally. - /// If not (default) search using pattern as a Regular Expression + private bool _isLiteralPath; + + /// + /// Gets or sets a value indicating if only string values containing matched lines should be returned. + /// If not (default) return MatchInfo (or bool objects, when Quiet is passed). + /// + [Parameter(Mandatory = true, ParameterSetName = ParameterSetObjectRaw)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetFileRaw)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetLiteralFileRaw)] + public SwitchParameter Raw { get; set; } + + /// + /// Gets or sets a value indicating if a pattern string should be matched literally. + /// If not (default) search using pattern as a Regular Expression. /// [Parameter] - public SwitchParameter SimpleMatch - { - get - { - return _simpleMatch; - } - set - { - _simpleMatch = value; - } - } - private bool _simpleMatch; + public SwitchParameter SimpleMatch { get; set; } - /// - /// If true, then do case-sensitive searches... + /// + /// Gets or sets a value indicating if the search is case sensitive.If true, then do case-sensitive searches. /// [Parameter] - public SwitchParameter CaseSensitive - { - get - { - return _caseSensitive; - } - set - { - _caseSensitive = value; - } - } - private bool _caseSensitive; + public SwitchParameter CaseSensitive { get; set; } /// - /// If true the cmdlet will stop processing at the first successful match and + /// Gets or sets a value indicating if the cmdlet will stop processing at the first successful match and /// return true. If both List and Quiet parameters are given, an exception is thrown. /// - [Parameter] - public SwitchParameter Quiet - { - get - { - return _quiet; - } - set - { - _quiet = value; - } - } - private bool _quiet; + [Parameter(ParameterSetName = ParameterSetObject)] + [Parameter(ParameterSetName = ParameterSetFile)] + [Parameter(ParameterSetName = ParameterSetLiteralFile)] + public SwitchParameter Quiet { get; set; } /// - /// list files where a match is found + /// Gets or sets a value indicating if matching files should be listed. /// This is the Unix functionality this switch is intended to mimic; /// the actual action of this option is to stop after the first match /// is found and returned from any particular file. /// [Parameter] - public SwitchParameter List - { - get - { - return _list; - } - set - { - _list = value; - } - } - private bool _list; + public SwitchParameter List { get; set; } /// - /// Lets you include particular files. Files not matching - /// one of these (if specified) are excluded. + /// Gets or sets a value indicating if highlighting should be disabled. + /// + [Parameter] + public SwitchParameter NoEmphasis { get; set; } + + /// + /// Gets or sets files to include. Files matching + /// one of these (if specified) are included. /// /// Invalid wildcard pattern was specified. [Parameter] [ValidateNotNullOrEmpty] public string[] Include { - get - { - return includeStrings; - } + get => _includeStrings; set { // null check is not needed (because of ValidateNotNullOrEmpty), // but we have to include it to silence OACR - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - includeStrings = value; + _includeStrings = value ?? throw PSTraceSource.NewArgumentNullException(nameof(value)); - this.include = new WildcardPattern[includeStrings.Length]; - for (int i = 0; i < includeStrings.Length; i++) + _include = new WildcardPattern[_includeStrings.Length]; + for (int i = 0; i < _includeStrings.Length; i++) { - this.include[i] = WildcardPattern.Get(includeStrings[i], WildcardOptions.IgnoreCase); + _include[i] = WildcardPattern.Get(_includeStrings[i], WildcardOptions.IgnoreCase); } } } - internal string[] includeStrings = null; - internal WildcardPattern[] include = null; + + private string[] _includeStrings; + + private WildcardPattern[] _include; /// - /// Lets you exclude particular files. Files matching + /// Gets or sets files to exclude. Files matching /// one of these (if specified) are excluded. /// [Parameter] [ValidateNotNullOrEmpty] public string[] Exclude { - get - { - return excludeStrings; - } + get => _excludeStrings; set { // null check is not needed (because of ValidateNotNullOrEmpty), // but we have to include it to silence OACR - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } + _excludeStrings = value ?? throw PSTraceSource.NewArgumentNullException("value"); - excludeStrings = value; - - this.exclude = new WildcardPattern[excludeStrings.Length]; - for (int i = 0; i < excludeStrings.Length; i++) + _exclude = new WildcardPattern[_excludeStrings.Length]; + for (int i = 0; i < _excludeStrings.Length; i++) { - this.exclude[i] = WildcardPattern.Get(excludeStrings[i], WildcardOptions.IgnoreCase); + _exclude[i] = WildcardPattern.Get(_excludeStrings[i], WildcardOptions.IgnoreCase); } } } - internal string[] excludeStrings; - internal WildcardPattern[] exclude; + + private string[] _excludeStrings; + + private WildcardPattern[] _exclude; /// - /// Only show lines which do not match. + /// Gets or sets a value indicating whether to only show lines which do not match. /// Equivalent to grep -v/findstr -v. /// [Parameter] - public SwitchParameter NotMatch - { - get - { - return _notMatch; - } - set - { - _notMatch = value; - } - } - private bool _notMatch; + public SwitchParameter NotMatch { get; set; } /// - /// If set, sets the Matches property of MatchInfo to the result - /// of calling System.Text.RegularExpressions.Regex.Matches() on + /// Gets or sets a value indicating whether the Matches property of MatchInfo should be set + /// to the result of calling System.Text.RegularExpressions.Regex.Matches() on /// the corresponding line. - /// /// Has no effect if -SimpleMatch is also specified. /// [Parameter] - public SwitchParameter AllMatches + public SwitchParameter AllMatches { get; set; } + + /// + /// Gets or sets the text encoding to process each file as. + /// + [Parameter] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] + [ValidateNotNullOrEmpty] + public Encoding Encoding { get { - return _allMatches; + return _encoding; } + set { - _allMatches = value; + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; } } - private bool _allMatches; - /// - /// The text encoding to process each file as. - /// - [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentCompletions( - EncodingConversion.Ascii, - EncodingConversion.BigEndianUnicode, - EncodingConversion.OEM, - EncodingConversion.Unicode, - EncodingConversion.Utf7, - EncodingConversion.Utf8, - EncodingConversion.Utf8Bom, - EncodingConversion.Utf8NoBom, - EncodingConversion.Utf32 - )] - [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + private Encoding _encoding = Encoding.Default; /// - /// The number of context lines to collect. If set to a + /// Gets or sets the number of context lines to collect. If set to a /// single integer value N, collects N lines each of pre- /// and post- context. If set to a 2-tuple B,A, collects B /// lines of pre- and A lines of post- context. @@ -1228,23 +1366,15 @@ public SwitchParameter AllMatches [Parameter] [ValidateNotNullOrEmpty] [ValidateCount(1, 2)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public new int[] Context { - get - { - return _context; - } + get => _context; set { // null check is not needed (because of ValidateNotNullOrEmpty), // but we have to include it to silence OACR - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - _context = value; + _context = value ?? throw PSTraceSource.NewArgumentNullException("value"); if (_context.Length == 1) { @@ -1258,10 +1388,18 @@ public SwitchParameter AllMatches } } } + private int[] _context; + private int _preContext = 0; + private int _postContext = 0; + // When we are in Raw mode or pre- and postcontext are zero, use the _noContextTracker, since we will not be needing trackedLines. + private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _postContext == 0)) + ? _noContextTracker + : new ContextTracker(_preContext, _postContext); + // This context tracker is only used for strings which are piped // directly into the cmdlet. File processing doesn't need // to track state between calls to ProcessRecord, and so @@ -1269,7 +1407,9 @@ public SwitchParameter AllMatches // use a single global tracker for both is that in the case of // a mixed list of strings and FileInfo, the context tracker // would get reset after each file. - private ContextTracker _globalContextTracker = null; + private IContextTracker _globalContextTracker; + + private IContextTracker _noContextTracker; /// /// This is used to handle the case were we're done processing input objects. @@ -1277,16 +1417,31 @@ public SwitchParameter AllMatches /// private bool _doneProcessing; - private int _inputRecordNumber; + private ulong _inputRecordNumber; /// /// Read command line parameters. /// protected override void BeginProcessing() { - if (!_simpleMatch) + if (this.MyInvocation.BoundParameters.ContainsKey(nameof(Culture)) && !this.MyInvocation.BoundParameters.ContainsKey(nameof(SimpleMatch))) + { + InvalidOperationException exception = new(MatchStringStrings.CannotSpecifyCultureWithoutSimpleMatch); + ErrorRecord errorRecord = new(exception, "CannotSpecifyCultureWithoutSimpleMatch", ErrorCategory.InvalidData, null); + this.ThrowTerminatingError(errorRecord); + } + + InitCulture(); + + string suppressVt = Environment.GetEnvironmentVariable("__SuppressAnsiEscapeSequences"); + if (!string.IsNullOrEmpty(suppressVt)) + { + NoEmphasis = true; + } + + if (!SimpleMatch) { - RegexOptions regexOptions = (_caseSensitive) ? RegexOptions.None : RegexOptions.IgnoreCase; + RegexOptions regexOptions = CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; _regexPattern = new Regex[Pattern.Length]; for (int i = 0; i < Pattern.Length; i++) { @@ -1302,61 +1457,68 @@ protected override void BeginProcessing() } } - _globalContextTracker = new ContextTracker(_preContext, _postContext); + _noContextTracker = new NoContextTracker(); + _globalContextTracker = GetContextTracker(); } + private readonly List _inputObjectFileList = new(1) { string.Empty }; + /// - /// process the input + /// Process the input. /// - /// - /// Does not return a value - /// - /// Regular expression parsing error, path error + /// Regular expression parsing error, path error. /// A file cannot be found. /// A file cannot be found. protected override void ProcessRecord() { if (_doneProcessing) + { return; + } + // We may only have directories when we have resolved wildcards + var expandedPathsMaybeDirectory = false; List expandedPaths = null; if (Path != null) { expandedPaths = ResolveFilePaths(Path, _isLiteralPath); if (expandedPaths == null) + { return; + } + + expandedPathsMaybeDirectory = true; } else { - FileInfo fileInfo = _inputObject.BaseObject as FileInfo; - if (fileInfo != null) + if (_inputObject.BaseObject is FileInfo fileInfo) { - expandedPaths = new List(); - expandedPaths.Add(fileInfo.FullName); + _inputObjectFileList[0] = fileInfo.FullName; + expandedPaths = _inputObjectFileList; } } if (expandedPaths != null) { - foreach (string filename in expandedPaths) + foreach (var filename in expandedPaths) { - if (Directory.Exists(filename)) + if (expandedPathsMaybeDirectory && Directory.Exists(filename)) { continue; } - bool foundMatch = ProcessFile(filename); - if (_quiet && foundMatch) + var foundMatch = ProcessFile(filename); + if (Quiet && foundMatch) + { return; + } } // No results in any files. - if (_quiet) + if (Quiet) { - if (_list) - WriteObject(null); - else - WriteObject(false); + var res = List ? null : Boxed.False; + WriteObject(res); } } else @@ -1367,16 +1529,15 @@ protected override void ProcessRecord() bool matched; MatchInfo result; MatchInfo matchInfo = null; - var line = _inputObject.BaseObject as string; - if (line != null) + if (_inputObject.BaseObject is string line) { - matched = doMatch(line, out result); + matched = DoMatch(line, out result); } else { matchInfo = _inputObject.BaseObject as MatchInfo; object objectToCheck = matchInfo ?? (object)_inputObject; - matched = doMatch(objectToCheck, out result, out line); + matched = DoMatch(objectToCheck, out result, out line); } if (matched) @@ -1386,6 +1547,7 @@ protected override void ProcessRecord() { result.LineNumber = _inputRecordNumber; } + // doMatch will have already set the pattern and line text... _globalContextTracker.TrackMatch(result); } @@ -1399,8 +1561,10 @@ protected override void ProcessRecord() { // If we're in quiet mode, go ahead and stop processing // now. - if (_quiet) + if (Quiet) + { _doneProcessing = true; + } } } } @@ -1413,7 +1577,7 @@ protected override void ProcessRecord() /// True if a match was found; otherwise false. private bool ProcessFile(string filename) { - ContextTracker contextTracker = new ContextTracker(_preContext, _postContext); + var contextTracker = GetContextTracker(); bool foundMatch = false; @@ -1421,15 +1585,17 @@ private bool ProcessFile(string filename) try { // see if the file is one the include exclude list... - if (!meetsIncludeExcludeCriteria(filename)) + if (!MeetsIncludeExcludeCriteria(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; + string line; + ulong lineNo = 0; // Read and display lines from the file until the end of // the file is reached. @@ -1437,9 +1603,7 @@ private bool ProcessFile(string filename) { lineNo++; - MatchInfo result; - - if (doMatch(line, out result)) + if (DoMatch(line, out MatchInfo result)) { result.Path = filename; result.LineNumber = lineNo; @@ -1460,15 +1624,12 @@ private bool ProcessFile(string filename) // this file. It's done this way so the file is closed before emitting // the result so the downstream cmdlet can actually manipulate the file // that was found. - - if (_quiet || _list) + if (Quiet || List) { break; } - else - { - FlushTrackerQueue(contextTracker); - } + + FlushTrackerQueue(contextTracker); } } } @@ -1480,7 +1641,9 @@ private bool ProcessFile(string filename) // our postcontext. contextTracker.TrackEOF(); if (FlushTrackerQueue(contextTracker)) + { foundMatch = true; + } } catch (System.NotSupportedException nse) { @@ -1503,23 +1666,30 @@ private bool ProcessFile(string filename) } /// - /// Emit any objects which have been queued up, and clear - /// the queue. + /// Emit any objects which have been queued up, and clear the queue. /// /// The context tracker to operate on. /// Whether or not any objects were emitted. - private bool FlushTrackerQueue(ContextTracker contextTracker) + private bool FlushTrackerQueue(IContextTracker contextTracker) { // Do we even have any matches to emit? if (contextTracker.EmitQueue.Count < 1) + { return false; + } - // If -quiet is specified but not -list return true on first match - if (_quiet && !_list) + if (Raw) + { + foreach (MatchInfo match in contextTracker.EmitQueue) + { + WriteObject(match.Line); + } + } + else if (Quiet && !List) { WriteObject(true); } - else if (_list) + else if (List) { WriteObject(contextTracker.EmitQueue[0]); } @@ -1544,15 +1714,17 @@ protected override void EndProcessing() // Check for a leftover match that was still tracking context. _globalContextTracker.TrackEOF(); if (!_doneProcessing) + { FlushTrackerQueue(_globalContextTracker); + } } - private bool doMatch(string operandString, out MatchInfo matchResult) + private bool DoMatch(string operandString, out MatchInfo matchResult) { - return doMatchWorker(operandString, null, out matchResult); + return DoMatchWorker(operandString, null, out matchResult); } - private bool doMatch(object operand, out MatchInfo matchResult, out string operandString) + private bool DoMatch(object operand, out MatchInfo matchResult, out string operandString) { MatchInfo matchInfo = operand as MatchInfo; if (matchInfo != null) @@ -1579,28 +1751,39 @@ private bool doMatch(object operand, out MatchInfo matchResult, out string opera operandString = (string)LanguagePrimitives.ConvertTo(operand, typeof(string), CultureInfo.InvariantCulture); } - return doMatchWorker(operandString, matchInfo, out matchResult); + return DoMatchWorker(operandString, matchInfo, out matchResult); } /// /// Check the operand and see if it matches, if this.quiet is not set, then - /// return a partially populated MatchInfo object with Line, Pattern, IgnoreCase - /// set. + /// return a partially populated MatchInfo object with Line, Pattern, IgnoreCase set. /// - /// - /// the match info object - this will be - /// null if this.quiet is set. - /// the result of converting operand to - /// a string. - /// true if the input object matched - private bool doMatchWorker(string operandString, MatchInfo matchInfo, out MatchInfo matchResult) + /// The result of converting operand to a string. + /// The input object in filter mode. + /// The match info object - this will be null if this.quiet is set. + /// True if the input object matched. + private bool DoMatchWorker(string operandString, MatchInfo matchInfo, out MatchInfo matchResult) { bool gotMatch = false; Match[] matches = null; int patternIndex = 0; matchResult = null; - if (!_simpleMatch) + List indexes = null; + List lengths = null; + + bool shouldEmphasize = !NoEmphasis && Host.UI.SupportsVirtualTerminal; + + // If Emphasize is set and VT is supported, + // the lengths and starting indexes of regex matches + // need to be passed in to the matchInfo object. + if (shouldEmphasize) + { + indexes = new List(); + lengths = new List(); + } + + if (!SimpleMatch) { while (patternIndex < Pattern.Length) { @@ -1609,13 +1792,23 @@ private bool doMatchWorker(string operandString, MatchInfo matchInfo, out MatchI // Only honor allMatches if notMatch is not set, // since it's a fairly expensive operation and // notMatch takes precedent over allMatch. - if (_allMatches && !_notMatch) + if (AllMatches && !NotMatch) { MatchCollection mc = r.Matches(operandString); if (mc.Count > 0) { matches = new Match[mc.Count]; ((ICollection)mc).CopyTo(matches, 0); + + if (shouldEmphasize) + { + foreach (Match match in matches) + { + indexes.Add(match.Index); + lengths.Add(match.Length); + } + } + gotMatch = true; } } @@ -1626,26 +1819,39 @@ private bool doMatchWorker(string operandString, MatchInfo matchInfo, out MatchI if (match.Success) { + if (shouldEmphasize) + { + indexes.Add(match.Index); + lengths.Add(match.Length); + } + matches = new Match[] { match }; } } if (gotMatch) + { break; + } patternIndex++; } } else { - StringComparison compareOption = _caseSensitive ? - StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; while (patternIndex < Pattern.Length) { string pat = Pattern[patternIndex]; - if (operandString.IndexOf(pat, compareOption) >= 0) + int index = _cultureInfoIndexOf(operandString, pat, 0, operandString.Length, _compareOptions); + if (index >= 0) { + if (shouldEmphasize) + { + indexes.Add(index); + lengths.Add(pat.Length); + } + gotMatch = true; break; } @@ -1654,9 +1860,10 @@ private bool doMatchWorker(string operandString, MatchInfo matchInfo, out MatchI } } - if (_notMatch) + if (NotMatch) { gotMatch = !gotMatch; + // If notMatch was specified with multiple // patterns, then *none* of the patterns // matched and any pattern could be picked @@ -1680,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 { @@ -1693,8 +1900,10 @@ private bool doMatchWorker(string operandString, MatchInfo matchInfo, out MatchI } // otherwise construct and populate a new MatchInfo object - matchResult = new MatchInfo(); - matchResult.IgnoreCase = !_caseSensitive; + matchResult = shouldEmphasize + ? new MatchInfo(indexes, lengths) + : new MatchInfo(); + matchResult.IgnoreCase = !CaseSensitive; matchResult.Line = operandString; matchResult.Pattern = Pattern[patternIndex]; @@ -1705,27 +1914,32 @@ 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; } + return false; - } // end doMatch + } + /// /// Get a list or resolved file paths. + /// + /// The filePaths to resolve. + /// True if the wildcard resolution should not be attempted. + /// The resolved (absolute) paths. private List ResolveFilePaths(string[] filePaths, bool isLiteralPath) { - ProviderInfo provider; - List allPaths = new List(); + List allPaths = new(); foreach (string path in filePaths) { Collection resolvedPaths; + ProviderInfo provider; if (isLiteralPath) { resolvedPaths = new Collection(); - PSDriveInfo drive; - string resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); + string resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out _); resolvedPaths.Add(resolvedPath); } else @@ -1733,12 +1947,13 @@ private List ResolveFilePaths(string[] filePaths, bool isLiteralPath) resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider); } - if (!provider.NameEquals(((PSCmdlet)this).Context.ProviderNames.FileSystem)) + if (!provider.NameEquals(base.Context.ProviderNames.FileSystem)) { // "The current provider ({0}) cannot open a file" WriteError(BuildErrorRecord(MatchStringStrings.FileOpenError, provider.FullName, "ProcessingFile", null)); continue; } + allPaths.AddRange(resolvedPaths); } @@ -1758,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); } @@ -1771,21 +1986,21 @@ 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) { object result = inputData; - PSObject mso = result as PSObject; - if (mso != null) + if (result is PSObject mso) + { result = mso.BaseObject; + } - IList argList = result as IList; FileInfo fileInfo; // Handle an array of elements... - if (argList != null) + if (result is IList argList) { object[] resultList = new object[argList.Count]; @@ -1795,21 +2010,23 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input mso = element as PSObject; if (mso != null) + { element = mso.BaseObject; + } fileInfo = element as FileInfo; - - if (fileInfo != null) - resultList[i] = fileInfo.FullName; - else resultList[i] = element; + resultList[i] = fileInfo?.FullName ?? element; } + return resultList; } // Handle the singleton case... fileInfo = result as FileInfo; if (fileInfo != null) + { return fileInfo.FullName; + } return inputData; } @@ -1820,16 +2037,16 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input /// That is - it's on the include list if there is one and not on /// the exclude list if there was one of those. /// - /// + /// The filename to test. /// True if the filename is acceptable. - private bool meetsIncludeExcludeCriteria(string filename) + private bool MeetsIncludeExcludeCriteria(string filename) { bool ok = false; // see if the file is on the include list... - if (this.include != null) + if (_include != null) { - foreach (WildcardPattern patternItem in this.include) + foreach (WildcardPattern patternItem in _include) { if (patternItem.IsMatch(filename)) { @@ -1844,12 +2061,14 @@ private bool meetsIncludeExcludeCriteria(string filename) } if (!ok) + { return false; + } // now see if it's on the exclude list... - if (this.exclude != null) + if (_exclude != null) { - foreach (WildcardPattern patternItem in this.exclude) + foreach (WildcardPattern patternItem in _exclude) { if (patternItem.IsMatch(filename)) { @@ -1861,6 +2080,26 @@ private bool meetsIncludeExcludeCriteria(string filename) return ok; } - } // end class SelectStringCommand -} + } + /// + /// Get list of valid culture names for ValidateSet attribute. + /// + public class ValidateMatchStringCultureNamesGenerator : IValidateSetValuesGenerator + { + string[] IValidateSetValuesGenerator.GetValidValues() + { + var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + var result = new List(cultures.Length + 3); + result.Add(SelectStringCommand.OrdinalCultureName); + result.Add(SelectStringCommand.InvariantCultureName); + result.Add(SelectStringCommand.CurrentCultureName); + foreach (var cultureInfo in cultures) + { + result.Add(cultureInfo.Name); + } + + return result.ToArray(); + } + } +} 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 06b77334d7a..1e741758270 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -8,78 +7,69 @@ using System.Globalization; using System.Management.Automation; using System.Management.Automation.Internal; -using Microsoft.PowerShell.Commands.Internal.Format; namespace Microsoft.PowerShell.Commands { /// - /// Class output by Measure-Object + /// Class output by Measure-Object. /// public abstract class MeasureInfo { /// - /// - /// property name - /// + /// Property name. /// - public string Property { get; set; } = null; + public string Property { get; set; } } /// - /// Class output by Measure-Object + /// Class output by Measure-Object. /// public sealed class GenericMeasureInfo : MeasureInfo { /// - /// default ctor + /// Initializes a new instance of the class. /// public GenericMeasureInfo() { - Average = Sum = Maximum = Minimum = null; + Average = Sum = Maximum = Minimum = StandardDeviation = null; } /// - /// - /// Keeping track of number of objects with a certain property - /// + /// Keeping track of number of objects with a certain property. /// public int Count { get; set; } /// - /// - /// The average of property values - /// + /// The average of property values. /// public double? Average { get; set; } /// - /// - /// The sum of property values - /// + /// The sum of property values. /// public double? Sum { get; set; } /// - /// - /// The max of property values - /// + /// The max of property values. /// public double? Maximum { get; set; } /// - /// - /// The min of property values - /// + /// The min of property values. /// public double? Minimum { get; set; } + + /// + /// The Standard Deviation of property values. + /// + public double? StandardDeviation { get; set; } } /// /// Class output by Measure-Object. /// /// - /// This class is created for fixing "Measure-Object -MAX -MIN should work with ANYTHING that supports CompareTo" - /// bug (Win8:343911). + /// This class is created to make 'Measure-Object -MAX -MIN' work with ANYTHING that supports 'CompareTo'. /// GenericMeasureInfo class is shipped with PowerShell V2. Fixing this bug requires, changing the type of /// Maximum and Minimum properties which would be a breaking change. Hence created a new class to not /// have an appcompat issues with PS V2. @@ -87,58 +77,54 @@ public GenericMeasureInfo() public sealed class GenericObjectMeasureInfo : MeasureInfo { /// - /// default ctor + /// Initializes a new instance of the class. + /// Default ctor. /// public GenericObjectMeasureInfo() { - Average = Sum = null; + Average = Sum = StandardDeviation = null; Maximum = Minimum = null; } /// - /// - /// Keeping track of number of objects with a certain property - /// + /// Keeping track of number of objects with a certain property. /// public int Count { get; set; } /// - /// - /// The average of property values - /// + /// The average of property values. /// public double? Average { get; set; } /// - /// - /// The sum of property values - /// + /// The sum of property values. /// public double? Sum { get; set; } /// - /// - /// The max of property values - /// + /// The max of property values. /// public object Maximum { get; set; } /// - /// - /// The min of property values - /// + /// The min of property values. /// public object Minimum { get; set; } - } + /// + /// The Standard Deviation of property values. + /// + public double? StandardDeviation { get; set; } + } /// - /// Class output by Measure-Object + /// Class output by Measure-Object. /// public sealed class TextMeasureInfo : MeasureInfo { /// - /// default ctor + /// Initializes a new instance of the class. + /// Default ctor. /// public TextMeasureInfo() { @@ -146,45 +132,40 @@ public TextMeasureInfo() } /// - /// - /// Keeping track of number of objects with a certain property - /// + /// Keeping track of number of objects with a certain property. /// public int? Lines { get; set; } /// - /// - /// The average of property values - /// + /// The average of property values. /// public int? Words { get; set; } /// - /// - /// The sum of property values - /// + /// The sum of property values. /// public int? Characters { get; set; } } /// - /// measure object cmdlet + /// Measure object cmdlet. /// [Cmdlet(VerbsDiagnostic.Measure, "Object", DefaultParameterSetName = GenericParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113349", RemotingCapability = RemotingCapability.None)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096617", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(GenericMeasureInfo), typeof(TextMeasureInfo), typeof(GenericObjectMeasureInfo))] public sealed class MeasureObjectCommand : PSCmdlet { /// - /// Dictionary to be used by Measure-Object implementation + /// 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() { /// - /// default ctor + /// Initializes a new instance of the class. + /// Default ctor. /// internal MeasureObjectDictionary() : base(StringComparer.OrdinalIgnoreCase) { @@ -196,16 +177,16 @@ internal MeasureObjectDictionary() : base(StringComparer.OrdinalIgnoreCase) /// the key with a new value created via the value type's /// default constructor. /// - /// The key to look up + /// The key to look up. /// /// 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; } @@ -218,15 +199,16 @@ 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; // Generic/Numeric statistics internal double sum = 0.0; + internal double sumPrevious = 0.0; + internal double variance = 0.0; internal object max = null; internal object min = null; @@ -237,7 +219,8 @@ private class Statistics } /// - /// default constructor + /// Initializes a new instance of the class. + /// Default constructor. /// public MeasureObjectCommand() : base() @@ -249,24 +232,43 @@ public MeasureObjectCommand() #region Common parameters in both sets /// - /// incoming object + /// Incoming object. /// /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// - /// Properties to be examined + /// Properties to be examined. /// /// [ValidateNotNullOrEmpty] [Parameter(Position = 0)] - public string[] Property { get; set; } = null; + public PSPropertyExpression[] Property { get; set; } #endregion Common parameters in both sets /// - /// Set to true is Sum is to be returned + /// Set to true if Standard Deviation is to be returned. + /// + [Parameter(ParameterSetName = GenericParameterSet)] + public SwitchParameter StandardDeviation + { + get + { + return _measureStandardDeviation; + } + + set + { + _measureStandardDeviation = value; + } + } + + private bool _measureStandardDeviation; + + /// + /// Set to true is Sum is to be returned. /// /// [Parameter(ParameterSetName = GenericParameterSet)] @@ -276,15 +278,37 @@ public SwitchParameter Sum { return _measureSum; } + set { _measureSum = value; } } + private bool _measureSum; /// - /// Set to true is Average is to be returned + /// Gets or sets the value indicating if all statistics should be returned. + /// + /// + [Parameter(ParameterSetName = GenericParameterSet)] + public SwitchParameter AllStats + { + get + { + return _allStats; + } + + set + { + _allStats = value; + } + } + + private bool _allStats; + + /// + /// Set to true is Average is to be returned. /// /// [Parameter(ParameterSetName = GenericParameterSet)] @@ -294,15 +318,17 @@ public SwitchParameter Average { return _measureAverage; } + set { _measureAverage = value; } } + private bool _measureAverage; /// - /// Set to true is Max is to be returned + /// Set to true is Max is to be returned. /// /// [Parameter(ParameterSetName = GenericParameterSet)] @@ -312,15 +338,17 @@ public SwitchParameter Maximum { return _measureMax; } + set { _measureMax = value; } } + private bool _measureMax; /// - /// Set to true is Min is to be returned + /// Set to true is Min is to be returned. /// /// [Parameter(ParameterSetName = GenericParameterSet)] @@ -330,16 +358,17 @@ public SwitchParameter Minimum { return _measureMin; } + set { _measureMin = value; } } + private bool _measureMin; #region TextMeasure ParameterSet /// - /// /// /// [Parameter(ParameterSetName = TextParameterSet)] @@ -349,15 +378,16 @@ public SwitchParameter Line { return _measureLines; } + set { _measureLines = value; } } + private bool _measureLines = false; /// - /// /// /// [Parameter(ParameterSetName = TextParameterSet)] @@ -367,15 +397,16 @@ public SwitchParameter Word { return _measureWords; } + set { _measureWords = value; } } + private bool _measureWords = false; /// - /// /// /// [Parameter(ParameterSetName = TextParameterSet)] @@ -385,15 +416,16 @@ public SwitchParameter Character { return _measureCharacters; } + set { _measureCharacters = value; } } + private bool _measureCharacters = false; /// - /// /// /// [Parameter(ParameterSetName = TextParameterSet)] @@ -403,17 +435,18 @@ public SwitchParameter IgnoreWhiteSpace { return _ignoreWhiteSpace; } + set { _ignoreWhiteSpace = value; } } + private bool _ignoreWhiteSpace; #endregion TextMeasure ParameterSet #endregion Command Line Switches - /// /// Which parameter set the Cmdlet is in. /// @@ -421,10 +454,25 @@ private bool IsMeasuringGeneric { get { - return String.Compare(ParameterSetName, GenericParameterSet, StringComparison.Ordinal) == 0; + return string.Equals(ParameterSetName, GenericParameterSet, StringComparison.Ordinal); } } + /// + /// Does the begin part of the cmdlet. + /// + protected override void BeginProcessing() + { + // Sets all other generic parameters to true to get all statistics. + if (_allStats) + { + _measureSum = _measureStandardDeviation = _measureAverage = _measureMax = _measureMin = true; + } + + // finally call the base class. + base.BeginProcessing(); + } + /// /// Collect data about each record that comes in. /// Side effects: Updates totalRecordCount. @@ -448,21 +496,20 @@ 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... - foreach (string p in Property) + foreach (var expression in Property) { - MshExpression expression = new MshExpression(p); - List resolvedNames = expression.ResolveNames(inObj); + List resolvedNames = expression.ResolveNames(inObj); if (resolvedNames == null || resolvedNames.Count == 0) { // Insert a blank entry so we can track @@ -479,7 +526,7 @@ private void AnalyzeObjectProperties(PSObject inObj) // Each property value can potentially refer // to multiple properties via globbing. Iterate over // the actual property names. - foreach (MshExpression resolvedName in resolvedNames) + foreach (PSPropertyExpression resolvedName in resolvedNames) { string propertyName = resolvedName.ToString(); // skip duplicated properties @@ -488,7 +535,7 @@ private void AnalyzeObjectProperties(PSObject inObj) continue; } - List tempExprRes = resolvedName.GetValues(inObj); + List tempExprRes = resolvedName.GetValues(inObj); if (tempExprRes == null || tempExprRes.Count == 0) { // Shouldn't happen - would somehow mean @@ -509,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); @@ -524,17 +570,17 @@ private void AnalyzeValue(string propertyName, object objValue) if (_measureCharacters || _measureWords || _measureLines) { - string strValue = (objValue == null) ? "" : objValue.ToString(); + string strValue = (objValue == null) ? string.Empty : objValue.ToString(); AnalyzeString(strValue, stat); } - if (_measureAverage || _measureSum) + if (_measureAverage || _measureSum || _measureStandardDeviation) { double numValue = 0.0; if (!LanguagePrimitives.TryConvertTo(objValue, out numValue)) { _nonNumericError = true; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewInvalidOperationException(MeasureObjectStrings.NonNumericInputObject, objValue), "NonNumericInputObject", ErrorCategory.InvalidType, @@ -546,7 +592,7 @@ private void AnalyzeValue(string propertyName, object objValue) AnalyzeNumber(numValue, stat); } - // Win8:343911 Measure-Object -MAX -MIN should work with ANYTHING that supports CompareTo + // Measure-Object -MAX -MIN should work with ANYTHING that supports CompareTo if (_measureMin) { stat.min = Compare(objValue, stat.min, true); @@ -572,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; @@ -588,36 +633,40 @@ 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; } /// - /// Class contains util static functions + /// Class contains util static functions. /// private static class TextCountUtilities { /// - /// count chars in inStr + /// Count chars in inStr. /// - /// string whose chars are counted - /// true to discount white space - /// number of chars in inStr + /// String whose chars are counted. + /// True to discount white space. + /// Number of chars in inStr. internal static int CountChar(string inStr, bool ignoreWhiteSpace) { - if (String.IsNullOrEmpty(inStr)) + if (string.IsNullOrEmpty(inStr)) { return 0; } + if (!ignoreWhiteSpace) { return inStr.Length; } + int len = 0; foreach (char c in inStr) { @@ -626,20 +675,22 @@ internal static int CountChar(string inStr, bool ignoreWhiteSpace) len++; } } + return len; } /// - /// count words in inStr + /// Count words in inStr. /// - /// string whose words are counted - /// number of words in inStr + /// String whose words are counted. + /// Number of words in inStr. internal static int CountWord(string inStr) { - if (String.IsNullOrEmpty(inStr)) + if (string.IsNullOrEmpty(inStr)) { return 0; } + int wordCount = 0; bool wasAWhiteSpace = true; foreach (char c in inStr) @@ -654,23 +705,26 @@ internal static int CountWord(string inStr) { wordCount++; } + wasAWhiteSpace = false; } } + return wordCount; } /// - /// count lines in inStr + /// Count lines in inStr. /// - /// string whose lines are counted - /// number of lines in inStr + /// String whose lines are counted. + /// Number of lines in inStr. internal static int CountLine(string inStr) { - if (String.IsNullOrEmpty(inStr)) + if (string.IsNullOrEmpty(inStr)) { return 0; } + int numberOfLines = 0; foreach (char c in inStr) { @@ -685,15 +739,16 @@ internal static int CountLine(string inStr) { numberOfLines++; } + return numberOfLines; } } /// /// Update text statistics. + /// /// The text to analyze. /// The Statistics object to update. - /// private void AnalyzeString(string strValue, Statistics stat) { if (_measureCharacters) @@ -706,27 +761,39 @@ 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) + if (_measureSum || _measureAverage || _measureStandardDeviation) + { + stat.sumPrevious = stat.sum; stat.sum += numValue; + } + + if (_measureStandardDeviation && stat.count > 1) + { + // Based off of iterative method of calculating variance on + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm + double avgPrevious = stat.sumPrevious / (stat.count - 1); + stat.variance *= (stat.count - 2.0) / (stat.count - 1); + stat.variance += (numValue - avgPrevious) * (numValue - avgPrevious) / stat.count; + } } /// - /// WriteError when a property is not found + /// WriteError when a property is not found. /// /// The missing property. /// The error ID to write. 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); @@ -752,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; } @@ -785,14 +855,15 @@ 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; double? average = null; + double? StandardDeviation = null; object max = null; object min = null; @@ -800,8 +871,14 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene { if (_measureSum) sum = stat.sum; + if (_measureAverage && stat.count > 0) average = stat.sum / stat.count; + + if (_measureStandardDeviation) + { + StandardDeviation = Math.Sqrt(stat.variance); + } } if (_measureMax) @@ -834,15 +911,17 @@ 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; - if (null != max) + gmi.StandardDeviation = StandardDeviation; + if (max != null) { gmi.Maximum = (double)max; } - if (null != min) + + if (min != null) { gmi.Minimum = (double)min; } @@ -851,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; @@ -864,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; @@ -882,15 +961,14 @@ 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. + /// 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. - /// If true, then average/sum will not be output. + /// If true, then average/sum/standard deviation will not be output. /// private bool _nonNumericError = false; @@ -915,4 +993,3 @@ private TextMeasureInfo CreateTextMeasureInfo(Statistics stat) private const string thisObject = "$_"; } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs new file mode 100644 index 00000000000..0ea312f2963 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs @@ -0,0 +1,551 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#region Using directives + +using System; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Management.Automation.Security; +using System.Reflection; +using System.Runtime.InteropServices; +#if !UNIX +using System.Threading; +#endif + +using Dbg = System.Management.Automation.Diagnostics; + +#endregion + +namespace Microsoft.PowerShell.Commands +{ + /// Create a new .net object + [Cmdlet(VerbsCommon.New, "Object", DefaultParameterSetName = netSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096620")] + public sealed class NewObjectCommand : PSCmdlet + { + #region parameters + + /// the number + [Parameter(ParameterSetName = netSetName, Mandatory = true, Position = 0)] + [ValidateTrustedData] + public string TypeName { get; set; } + +#if !UNIX + private Guid _comObjectClsId = Guid.Empty; + /// + /// The ProgID of the Com object. + /// + [Parameter(ParameterSetName = "Com", Mandatory = true, Position = 0)] + [ValidateTrustedData] + public string ComObject { get; set; } +#endif + + /// + /// The parameters for the constructor. + /// + /// + [Parameter(ParameterSetName = netSetName, Mandatory = false, Position = 1)] + [ValidateTrustedData] + [Alias("Args")] + public object[] ArgumentList { get; set; } + + /// + /// True if we should have an error when Com objects will use an interop assembly. + /// + [Parameter(ParameterSetName = "Com")] + public SwitchParameter Strict { get; set; } + + // Updated from Hashtable to IDictionary to support the work around ordered hashtables. + /// + /// Gets the properties to be set. + /// + [Parameter] + [ValidateTrustedData] + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public IDictionary Property { get; set; } + + #endregion parameters + + #region private + private object CallConstructor(Type type, ConstructorInfo[] constructors, object[] args) + { + object result = null; + try + { + result = DotNetAdapter.ConstructorInvokeDotNet(type, constructors, args); + } + catch (MethodException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "ConstructorInvokedThrowException", + ErrorCategory.InvalidOperation, null)); + } + // let other exceptions propagate + return result; + } + + private void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType) + { + string message = StringUtil.Format(NewObjectStrings.MemberNotFound, null, property.Key.ToString(), ParameterSet2ResourceString(ParameterSetName)); + + ThrowTerminatingError( + new ErrorRecord( + new InvalidOperationException(message), + "InvalidOperationException", + ErrorCategory.InvalidOperation, + null)); + } + + private void CreateMemberSetValueError(SetValueException e) + { + Exception ex = new(StringUtil.Format(NewObjectStrings.InvalidValue, e)); + ThrowTerminatingError( + new ErrorRecord(ex, "SetValueException", ErrorCategory.InvalidData, null)); + } + + private static string ParameterSet2ResourceString(string parameterSet) + { + if (parameterSet.Equals(netSetName, StringComparison.OrdinalIgnoreCase)) + { + return ".NET"; + } + else if (parameterSet.Equals("Com", StringComparison.OrdinalIgnoreCase)) + { + return "COM"; + } + else + { + Dbg.Assert(false, "Should never get here - unknown parameter set"); + return parameterSet; + } + } + + #endregion private + + #region Overrides + /// Create the object + protected override void BeginProcessing() + { + Type type = null; + PSArgumentException mshArgE = null; + + if (string.Equals(ParameterSetName, netSetName, StringComparison.Ordinal)) + { + object _newObject = null; + try + { + type = LanguagePrimitives.ConvertTo(TypeName, typeof(Type), CultureInfo.InvariantCulture) as Type; + } + catch (Exception e) + { + // these complications in Exception handling are aim to make error messages better. + if (e is InvalidCastException || e is ArgumentException) + { + if (e.InnerException != null && e.InnerException is TypeResolver.AmbiguousTypeException) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "AmbiguousTypeReference", + ErrorCategory.InvalidType, + targetObject: null)); + } + + mshArgE = PSTraceSource.NewArgumentException( + "TypeName", + NewObjectStrings.TypeNotFound, + TypeName); + + ThrowTerminatingError( + new ErrorRecord( + mshArgE, + "TypeNotFound", + ErrorCategory.InvalidType, + targetObject: null)); + } + + throw; + } + + Diagnostics.Assert(type != null, "LanguagePrimitives.TryConvertTo failed but returned true"); + + if (type.IsByRefLike) + { + ThrowTerminatingError( + new ErrorRecord( + PSTraceSource.NewInvalidOperationException( + NewObjectStrings.CannotInstantiateBoxedByRefLikeType, + type), + nameof(NewObjectStrings.CannotInstantiateBoxedByRefLikeType), + ErrorCategory.InvalidOperation, + targetObject: null)); + } + + switch (Context.LanguageMode) + { + case PSLanguageMode.ConstrainedLanguage: + if (!CoreTypes.Contains(type)) + { + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ThrowTerminatingError( + new ErrorRecord( + new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), + "CannotCreateTypeConstrainedLanguage", + ErrorCategory.PermissionDenied, + targetObject: null)); + } + + SystemPolicy.LogWDACAuditMessage( + context: Context, + title: NewObjectStrings.TypeWDACLogTitle, + message: StringUtil.Format(NewObjectStrings.TypeWDACLogMessage, type.FullName), + fqid: "NewObjectCmdletCannotCreateType", + dropIntoDebugger: true); + } + break; + + case PSLanguageMode.NoLanguage: + case PSLanguageMode.RestrictedLanguage: + if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce + && !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. + if (WinRTHelper.IsWinRTType(type) && ((typeof(System.Attribute)).IsAssignableFrom(type) || (typeof(System.Delegate)).IsAssignableFrom(type))) + { + ThrowTerminatingError(new ErrorRecord(new InvalidOperationException(NewObjectStrings.CannotInstantiateWinRTType), + "CannotInstantiateWinRTType", ErrorCategory.InvalidOperation, null)); + } + + if (ArgumentList == null || ArgumentList.Length == 0) + { + ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes); + if (ci != null && ci.IsPublic) + { + _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 + _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); + } + + WriteObject(_newObject); + return; + } + else if (type.IsValueType) + { + // This is for default parameterless struct ctor which is not returned by + // Type.GetConstructor(System.Type.EmptyTypes). + try + { + _newObject = Activator.CreateInstance(type); + if (_newObject != null && Property != null) + { + // Win8:649519 + _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); + } + } + catch (TargetInvocationException e) + { + ThrowTerminatingError( + new ErrorRecord( + e.InnerException ?? e, + "ConstructorCalledThrowException", + ErrorCategory.InvalidOperation, null)); + } + + WriteObject(_newObject); + return; + } + } + else + { + ConstructorInfo[] ctorInfos = type.GetConstructors(); + + if (ctorInfos.Length != 0) + { + _newObject = CallConstructor(type, ctorInfos, ArgumentList); + if (_newObject != null && Property != null) + { + // Win8:649519 + _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); + } + + WriteObject(_newObject); + return; + } + } + + mshArgE = PSTraceSource.NewArgumentException( + "TypeName", NewObjectStrings.CannotFindAppropriateCtor, TypeName); + ThrowTerminatingError( + new ErrorRecord( + mshArgE, + "CannotFindAppropriateCtor", + ErrorCategory.ObjectNotFound, null)); + } +#if !UNIX + else // Parameterset -Com + { + int result = NewObjectNativeMethods.CLSIDFromProgID(ComObject, out _comObjectClsId); + + // If we're in ConstrainedLanguage, do additional restrictions + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + { + bool isAllowed = false; + + // If it's a system-wide lockdown, we may allow additional COM types + var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); + if (systemLockdownPolicy == SystemEnforcementMode.Enforce || systemLockdownPolicy == SystemEnforcementMode.Audit) + { + isAllowed = (result >= 0) && SystemPolicy.IsClassInApprovedList(_comObjectClsId); + } + + if (!isAllowed) + { + 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); + } + } + + object comObject = CreateComObject(); + string comObjectTypeName = comObject.GetType().FullName; + if (!comObjectTypeName.Equals("System.__ComObject")) + { + mshArgE = PSTraceSource.NewArgumentException( + "TypeName", NewObjectStrings.ComInteropLoaded, comObjectTypeName); + WriteVerbose(mshArgE.Message); + if (Strict) + { + WriteError(new ErrorRecord( + mshArgE, + "ComInteropLoaded", + ErrorCategory.InvalidArgument, comObject)); + } + } + + if (comObject != null && Property != null) + { + // Win8:649519 + comObject = LanguagePrimitives.SetObjectProperties(comObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); + } + + WriteObject(comObject); + } +#endif + } + + #endregion Overrides + +#if !UNIX + #region Com + + private object SafeCreateInstance(Type t) + { + object result = null; + try + { + result = Activator.CreateInstance(t); + } + // Does not catch InvalidComObjectException because ComObject is obtained from GetTypeFromProgID + catch (ArgumentException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "CannotNewNonRuntimeType", + ErrorCategory.InvalidOperation, null)); + } + catch (NotSupportedException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "CannotNewTypeBuilderTypedReferenceArgIteratorRuntimeArgumentHandle", + ErrorCategory.InvalidOperation, null)); + } + catch (MethodAccessException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "CtorAccessDenied", + ErrorCategory.PermissionDenied, null)); + } + catch (MissingMethodException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "NoPublicCtorMatch", + ErrorCategory.InvalidOperation, null)); + } + catch (MemberAccessException e) + { + ThrowTerminatingError( + new ErrorRecord( + e, + "CannotCreateAbstractClass", + ErrorCategory.InvalidOperation, null)); + } + catch (COMException e) + { + if (e.HResult == RPC_E_CHANGED_MODE) + { + throw; + } + + ThrowTerminatingError( + new ErrorRecord( + e, + "NoCOMClassIdentified", + ErrorCategory.ResourceUnavailable, null)); + } + + return result; + } + + private sealed class ComCreateInfo + { + public object objectCreated; + public bool success; + public Exception e; + } + + private ComCreateInfo createInfo; + + private void STAComCreateThreadProc(object createstruct) + { + ComCreateInfo info = (ComCreateInfo)createstruct; + try + { + Type type = Type.GetTypeFromCLSID(_comObjectClsId); + if (type == null) + { + PSArgumentException mshArgE = PSTraceSource.NewArgumentException( + "ComObject", + NewObjectStrings.CannotLoadComObjectType, + ComObject); + + info.e = mshArgE; + info.success = false; + return; + } + + info.objectCreated = SafeCreateInstance(type); + info.success = true; + } + catch (Exception e) + { + info.e = e; + info.success = false; + } + } + + private object CreateComObject() + { + try + { + Type type = Marshal.GetTypeFromCLSID(_comObjectClsId); + if (type == null) + { + PSArgumentException mshArgE = PSTraceSource.NewArgumentException( + "ComObject", + NewObjectStrings.CannotLoadComObjectType, + ComObject); + + ThrowTerminatingError( + new ErrorRecord( + mshArgE, + "CannotLoadComObjectType", + ErrorCategory.InvalidType, + targetObject: null)); + } + + return SafeCreateInstance(type); + } + catch (COMException e) + { + // Check Error Code to see if Error is because of Com apartment Mismatch. + if (e.HResult == RPC_E_CHANGED_MODE) + { + createInfo = new ComCreateInfo(); + + Thread thread = new(new ParameterizedThreadStart(STAComCreateThreadProc)); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(createInfo); + + thread.Join(); + + if (createInfo.success) + { + return createInfo.objectCreated; + } + + ThrowTerminatingError( + new ErrorRecord(createInfo.e, "NoCOMClassIdentified", + ErrorCategory.ResourceUnavailable, null)); + } + else + { + ThrowTerminatingError( + new ErrorRecord( + e, + "NoCOMClassIdentified", + ErrorCategory.ResourceUnavailable, null)); + } + + return null; + } + } + + #endregion Com +#endif + + // HResult code '-2147417850' - Cannot change thread mode after it is set. + private const int RPC_E_CHANGED_MODE = unchecked((int)0x80010106); + private const string netSetName = "Net"; + } + + /// + /// Native methods for dealing with COM objects. + /// + internal static class 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 64f3d2934fc..8ae43a6e4fd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs @@ -1,18 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "new-alias" cmdlet + /// The implementation of the "new-alias" cmdlet. /// - /// - [Cmdlet(VerbsCommon.New, "Alias", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113352")] + [Cmdlet(VerbsCommon.New, "Alias", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097022")] [OutputType(typeof(AliasInfo))] public class NewAliasCommand : WriteAliasCommandBase { @@ -21,7 +19,6 @@ public class NewAliasCommand : WriteAliasCommandBase /// /// The main processing loop of the command. /// - /// protected override void ProcessRecord() { // If not force, then see if the alias already exists @@ -29,7 +26,7 @@ protected override void ProcessRecord() if (!Force) { AliasInfo existingAlias = null; - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { existingAlias = SessionState.Internal.GetAlias(Name); } @@ -38,7 +35,6 @@ protected override void ProcessRecord() existingAlias = SessionState.Internal.GetAliasAtScope(Name, Scope); } - if (existingAlias != null) { // Throw if alias exists and is private... @@ -47,7 +43,7 @@ protected override void ProcessRecord() // Since the alias already exists, write an error. SessionStateException aliasExists = - new SessionStateException( + new( Name, SessionStateCategory.Alias, "AliasAlreadyExists", @@ -65,7 +61,7 @@ protected override void ProcessRecord() // Create the alias info AliasInfo newAlias = - new AliasInfo( + new( Name, Value, Context, @@ -88,7 +84,7 @@ protected override void ProcessRecord() try { - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { result = SessionState.Internal.SetAliasItem(newAlias, Force, MyInvocation.CommandOrigin); } @@ -129,8 +125,7 @@ protected override void ProcessRecord() WriteObject(result); } } - } // ProcessRecord + } #endregion Command code - } // class NewAliasCommand -}//Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs new file mode 100644 index 00000000000..1e46241b2d8 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewEventCommand.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Generates a new event notification. + /// + [Cmdlet(VerbsCommon.New, "Event", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096708")] + [OutputType(typeof(PSEventArgs))] + public class NewEventCommand : PSCmdlet + { + #region parameters + + /// + /// Adds an event to the event queue. + /// + [Parameter(Position = 0, Mandatory = true)] + public string SourceIdentifier + { + get + { + return _sourceIdentifier; + } + + set + { + _sourceIdentifier = value; + } + } + + private string _sourceIdentifier = null; + + /// + /// Data relating to this event. + /// + [Parameter(Position = 1)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public PSObject Sender + { + get + { + return _sender; + } + + set + { + _sender = value; + } + } + + private PSObject _sender = null; + + /// + /// Data relating to this event. + /// + [Parameter(Position = 2)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public PSObject[] EventArguments + { + get + { + return _eventArguments; + } + + set + { + if (_eventArguments != null) + { + _eventArguments = value; + } + } + } + + private PSObject[] _eventArguments = Array.Empty(); + + /// + /// Data relating to this event. + /// + [Parameter(Position = 3)] + public PSObject MessageData + { + get + { + return _messageData; + } + + set + { + _messageData = value; + } + } + + private PSObject _messageData = null; + + #endregion parameters + + /// + /// Add the event to the event queue. + /// + protected override void EndProcessing() + { + object[] baseEventArgs = null; + + // Get the BaseObject from the event arguments + if (_eventArguments != null) + { + baseEventArgs = new object[_eventArguments.Length]; + int loopCounter = 0; + foreach (PSObject eventArg in _eventArguments) + { + if (eventArg != null) + baseEventArgs[loopCounter] = eventArg.BaseObject; + + loopCounter++; + } + } + + object messageSender = null; + 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 b4a80fd63fc..0e466f86d3f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs @@ -1,7 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#nullable enable using System; using System.Management.Automation; @@ -9,18 +9,50 @@ namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "new-guid" cmdlet + /// The implementation of the "new-guid" cmdlet. /// - [Cmdlet(VerbsCommon.New, "Guid", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526920")] + [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 2ea91339037..93b3eb209af 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs @@ -1,43 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.IO; using System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "New-TemporaryFile" cmdlet + /// The implementation of the "New-TemporaryFile" cmdlet. /// - [Cmdlet(VerbsCommon.New, "TemporaryFile", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526726")] + [Cmdlet(VerbsCommon.New, "TemporaryFile", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097032")] [OutputType(typeof(System.IO.FileInfo))] public class NewTemporaryFileCommand : Cmdlet { /// - /// returns a TemporaryFile + /// Returns a TemporaryFile. /// protected override void EndProcessing() { string filePath = null; - string tempPath = System.Environment.GetEnvironmentVariable("TEMP"); + string tempPath = Path.GetTempPath(); if (ShouldProcess(tempPath)) { try { filePath = Path.GetTempFileName(); } - catch (Exception e) + catch (IOException ioException) { - WriteError( + ThrowTerminatingError( new ErrorRecord( - e, + ioException, "NewTemporaryFileWriteError", ErrorCategory.WriteError, tempPath)); return; } + 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 71504e063b4..a5d784da9bb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs @@ -1,18 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// implementation for the new-timespan command + /// Implementation for the new-timespan command. /// [Cmdlet(VerbsCommon.New, "TimeSpan", DefaultParameterSetName = "Date", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113360", RemotingCapability = RemotingCapability.None)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096709", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(TimeSpan))] public sealed class NewTimeSpanCommand : PSCmdlet { @@ -20,7 +20,7 @@ public sealed class NewTimeSpanCommand : PSCmdlet /// /// This parameter indicates the date the time span begins; - /// it is used if two times are being compared + /// it is used if two times are being compared. /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Date")] [Alias("LastWriteTime")] @@ -30,16 +30,17 @@ public DateTime Start { return _start; } + set { _start = value; _startSpecified = true; } } + private DateTime _start; private bool _startSpecified; - /// /// This parameter indicates the end of a time span. It is used if two /// times are being compared. If one of the times is not specified, @@ -52,49 +53,53 @@ public DateTime End { return _end; } + set { _end = value; _endSpecified = true; } } + private DateTime _end; private bool _endSpecified = false; - /// - /// Allows the user to override the day + /// 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 + /// 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 + /// 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; } /// - /// Allows the user to override the second + /// Allows the user to override the millisecond. /// [Parameter(ParameterSetName = "Time")] - public int Seconds { get; set; } = 0; + public int Milliseconds { get; set; } #endregion #region methods /// - /// Calculate and write out the appropriate timespan + /// Calculate and write out the appropriate timespan. /// protected override void ProcessRecord() { @@ -110,6 +115,7 @@ protected override void ProcessRecord() { startTime = Start; } + if (_endSpecified) { endTime = End; @@ -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: @@ -128,10 +134,7 @@ protected override void ProcessRecord() } WriteObject(result); - } // EndProcessing - + } #endregion - } // NewTimeSpanCommand -} // namespace Microsoft.PowerShell.Commands - - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs index 77df4fa926a..13b0234a02b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; using System.Collections; -using System.Management.Automation; using System.Globalization; +using System.Management.Automation; #endregion @@ -17,10 +16,10 @@ namespace Microsoft.PowerShell.Commands /// /// Keeps the property value of inputObject. Because the value of a non-existing property is null, - /// isExistingProperty is needed to distinguish whether a property exists and its value is null or - /// the property does not exist at all. + /// 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() { } @@ -31,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. @@ -43,7 +42,6 @@ internal ObjectCommandPropertyValue(object propVal, bool isCaseSensitive, Cultur this.cultureInfo = cultureInfo; } - internal object PropertyValue { get; } internal bool IsExistingProperty { get; } @@ -67,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,35 +75,34 @@ internal CultureInfo Culture /// /// Input Object. /// True if both the objects are same or else returns false. - public override bool Equals(Object inputObject) + public override bool Equals(object inputObject) { - ObjectCommandPropertyValue objectCommandPropertyValueObject = inputObject as ObjectCommandPropertyValue; - if (objectCommandPropertyValueObject == null) + if (inputObject is not ObjectCommandPropertyValue objectCommandPropertyValueObject) + { return false; + } object baseObject = PSObject.Base(PropertyValue); object inComingbaseObjectPropertyValue = PSObject.Base(objectCommandPropertyValueObject.PropertyValue); - IComparable baseObjectComparable = baseObject as IComparable; - - if (baseObjectComparable != null) + if (baseObject is IComparable) { - return (LanguagePrimitives.Compare(baseObject, inComingbaseObjectPropertyValue, CaseSensitive, Culture) == 0); + var success = LanguagePrimitives.TryCompare(baseObject, inComingbaseObjectPropertyValue, CaseSensitive, Culture, out int result); + return success && result == 0; } - else + + if (baseObject == null && inComingbaseObjectPropertyValue == null) { - if (baseObject == null && inComingbaseObjectPropertyValue == null) - { - return true; - } - if (baseObject != null && inComingbaseObjectPropertyValue != null) - { - return baseObject.ToString().Equals(inComingbaseObjectPropertyValue.ToString(), StringComparison.OrdinalIgnoreCase); - } + return true; + } - // One of the property values being compared is null. - return false; + if (baseObject != null && inComingbaseObjectPropertyValue != null) + { + return baseObject.ToString().Equals(inComingbaseObjectPropertyValue.ToString(), StringComparison.OrdinalIgnoreCase); } + + // One of the property values being compared is null. + return false; } /// @@ -115,12 +112,17 @@ public override bool Equals(Object inputObject) public override int GetHashCode() { if (PropertyValue == null) + { return 0; + } object baseObject = PSObject.Base(PropertyValue); - IComparable baseObjectComparable = baseObject as IComparable; + if (baseObject == null) + { + return 0; + } - if (baseObjectComparable != null) + if (baseObject is IComparable) { return baseObject.GetHashCode(); } @@ -132,11 +134,12 @@ 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. @@ -154,7 +157,6 @@ private static bool IsValueNull(object value) return (val == null); } - internal int Compare(ObjectCommandPropertyValue first, ObjectCommandPropertyValue second) { if (first.IsExistingProperty && second.IsExistingProperty) @@ -173,70 +175,61 @@ internal int Compare(ObjectCommandPropertyValue first, ObjectCommandPropertyValu { return 1; } - //both are nonexisting + // both are nonexisting return 0; } /// - /// Main method that will compare first and second by - /// their keys considering case and order + /// Main method that will compare first and second by their keys considering case and order. /// /// - /// first object to extract value + /// First object to extract value. /// /// - /// second object to extract value + /// Second object to extract value. /// /// - /// 0 if they are the same, less than 0 if first is smaller, more than 0 if first is greater - /// + /// 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 // objects are considered the same - if (IsValueNull(first) && IsValueNull(second)) return 0; - + if (IsValueNull(first) && IsValueNull(second)) + { + return 0; + } - PSObject firstMsh = first as PSObject; - if (firstMsh != null) + if (first is PSObject firstMsh) { first = firstMsh.BaseObject; } - PSObject secondMsh = second as PSObject; - if (secondMsh != null) + if (second is PSObject secondMsh) { second = secondMsh.BaseObject; } - try - { - return LanguagePrimitives.Compare(first, second, !_caseSensitive, _cultureInfo) * (_ascendingOrder ? 1 : -1); - } - catch (InvalidCastException) - { - } - catch (ArgumentException) + if (!LanguagePrimitives.TryCompare(first, second, !_caseSensitive, _cultureInfo, out int result)) { // 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 - // or an Exception was raised win Compare - 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(); - return _cultureInfo.CompareInfo.Compare(firstString, secondString, _caseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase) * (_ascendingOrder ? 1 : -1); + result = _cultureInfo.CompareInfo.Compare(firstString, secondString, _caseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase); + } + + 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 6b726c1deac..596bbcaafc6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs @@ -1,20 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Globalization; + using Microsoft.PowerShell.Commands.Internal.Format; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.PowerShell.Commands { /// - /// definitions for hash table keys + /// Definitions for hash table keys. /// internal static class SortObjectParameterDefinitionKeys { @@ -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() { @@ -45,20 +45,23 @@ protected override void SetEntries() } /// - /// Base Cmdlet for cmdlets which deal with raw objects + /// Base Cmdlet for cmdlets which deal with raw objects. /// public class ObjectCmdletBase : PSCmdlet { #region Parameters /// - /// /// /// [Parameter] [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 { if (string.IsNullOrEmpty(value)) @@ -72,7 +75,7 @@ public string Culture if (trimmedValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { if ((trimmedValue.Length > 2) && - int.TryParse(trimmedValue.Substring(2), NumberStyles.AllowHexSpecifier, + int.TryParse(trimmedValue.AsSpan(2), NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out cultureNumber)) { _cultureInfo = new CultureInfo(cultureNumber); @@ -85,21 +88,24 @@ public string Culture _cultureInfo = new CultureInfo(cultureNumber); return; } + _cultureInfo = new CultureInfo(value); } } + internal CultureInfo _cultureInfo = null; /// - /// /// /// [Parameter] public SwitchParameter CaseSensitive { get { return _caseSensitive; } + set { _caseSensitive = value; } } + private bool _caseSensitive; #endregion Parameters } @@ -112,10 +118,9 @@ public abstract class ObjectBase : ObjectCmdletBase #region Parameters /// - /// /// [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. @@ -140,14 +145,16 @@ public class OrderObjectBase : ObjectBase internal SwitchParameter DescendingOrder { get { return !_ascending; } + set { _ascending = !value; } } + private bool _ascending = true; internal List InputObjects { get; } = new List(); /// - /// CultureInfo converted from the Culture Cmdlet parameter + /// CultureInfo converted from the Culture Cmdlet parameter. /// internal CultureInfo ConvertedCulture { @@ -160,9 +167,7 @@ internal CultureInfo ConvertedCulture #endregion Internal Properties /// - /// - /// Simply accumulates the incoming objects - /// + /// Simply accumulates the incoming objects. /// protected override void ProcessRecord() { @@ -178,11 +183,11 @@ internal sealed class OrderByProperty #region Internal properties /// - /// a logical matrix where each row is an input object and its property values specified by Properties + /// 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 { @@ -200,7 +205,7 @@ internal List MshParameterList // a string array and allows wildcard. // Yes, the Cmdlet is needed. It's used to get the TerminatingErrorContext, WriteError and WriteDebug. - #region process MshExpression and MshParameter + #region process PSPropertyExpression and MshParameter private static void ProcessExpressionParameter( List inputObjects, @@ -209,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()) : @@ -219,6 +224,7 @@ private static void ProcessExpressionParameter( { expr = GetDefaultKeyPropertySet(inputObjects[0]); } + if (expr != null) { List unexpandedParameterList = processor.ProcessParameters(expr, invocationContext); @@ -232,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()) : @@ -246,17 +252,14 @@ internal void ProcessExpressionParameter( foreach (MshParameter unexpandedParameter in _unexpandedParameterList) { - MshExpression mshExpression = (MshExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); + PSPropertyExpression mshExpression = (PSPropertyExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); if (!mshExpression.HasWildCardCharacters) // this special cases 1) script blocks and 2) wildcard-less strings { _mshParameterList.Add(unexpandedParameter); } else { - if (_unExpandedParametersWithWildCardPattern == null) - { - _unExpandedParametersWithWildCardPattern = new List(); - } + _unExpandedParametersWithWildCardPattern ??= new List(); _unExpandedParametersWithWildCardPattern.Add(unexpandedParameter); } @@ -269,20 +272,20 @@ 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) { foreach (MshParameter unexpandedParameter in unexpandedParameterList) { - MshExpression ex = (MshExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); + PSPropertyExpression ex = (PSPropertyExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); if (!ex.HasWildCardCharacters) // this special cases 1) script blocks and 2) wildcard-less strings { expandedParameterList.Add(unexpandedParameter); } else { - SortedDictionary expandedPropertyNames = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + SortedDictionary expandedPropertyNames = new(StringComparer.OrdinalIgnoreCase); if (inputObjects != null) { foreach (object inputObject in inputObjects) @@ -292,16 +295,16 @@ private static List ExpandExpressions(List inputObjects, continue; } - foreach (MshExpression resolvedName in ex.ResolveNames(PSObject.AsPSObject(inputObject))) + foreach (PSPropertyExpression resolvedName in ex.ResolveNames(PSObject.AsPSObject(inputObject))) { expandedPropertyNames[resolvedName.ToString()] = resolvedName; } } } - foreach (MshExpression expandedExpression in expandedPropertyNames.Values) + foreach (PSPropertyExpression expandedExpression in expandedPropertyNames.Values) { - MshParameter expandedParameter = new MshParameter(); + MshParameter expandedParameter = new(); expandedParameter.hash = (Hashtable)unexpandedParameter.hash.Clone(); expandedParameter.hash[FormatParameterDefinitionKeys.ExpressionEntryKey] = expandedExpression; @@ -322,22 +325,22 @@ private static void ExpandExpressions(PSObject inputObject, List U { foreach (MshParameter unexpandedParameter in UnexpandedParametersWithWildCardPattern) { - MshExpression ex = (MshExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); + PSPropertyExpression ex = (PSPropertyExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); - SortedDictionary expandedPropertyNames = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + SortedDictionary expandedPropertyNames = new(StringComparer.OrdinalIgnoreCase); if (inputObject == null) { continue; } - foreach (MshExpression resolvedName in ex.ResolveNames(PSObject.AsPSObject(inputObject))) + foreach (PSPropertyExpression resolvedName in ex.ResolveNames(PSObject.AsPSObject(inputObject))) { expandedPropertyNames[resolvedName.ToString()] = resolvedName; } - foreach (MshExpression expandedExpression in expandedPropertyNames.Values) + foreach (PSPropertyExpression expandedExpression in expandedPropertyNames.Values) { - MshParameter expandedParameter = new MshParameter(); + MshParameter expandedParameter = new(); expandedParameter.hash = (Hashtable)unexpandedParameter.hash.Clone(); expandedParameter.hash[FormatParameterDefinitionKeys.ExpressionEntryKey] = expandedExpression; @@ -354,18 +357,18 @@ 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; } + string[] props = new string[defaultKeys.ReferencedPropertyNames.Count]; defaultKeys.ReferencedPropertyNames.CopyTo(props, 0); return props; } - #endregion process MshExpression and MshParameter + #endregion process PSPropertyExpression and MshParameter internal static List CreateOrderMatrix( PSCmdlet cmdlet, @@ -373,24 +376,26 @@ 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) { cmdlet.WriteError(err); } + foreach (string debugMsg in propertyNotFoundMsgs) { cmdlet.WriteDebug(debugMsg); } + orderMatrixToCreate.Add(result); } @@ -413,10 +418,11 @@ private static OrderByPropertyComparer CreateComparer( { return null; } - Nullable[] ascendingOverrides = null; + + bool?[] ascendingOverrides = null; if (mshParameterList != null && mshParameterList.Count != 0) { - ascendingOverrides = new Nullable[mshParameterList.Count]; + ascendingOverrides = new bool?[mshParameterList.Count]; for (int k = 0; k < ascendingOverrides.Length; k++) { object ascendingVal = mshParameterList[k].GetEntry( @@ -447,6 +453,7 @@ private static OrderByPropertyComparer CreateComparer( } } } + OrderByPropertyComparer comparer = OrderByPropertyComparer.CreateComparer(orderMatrix, ascending, ascendingOverrides, cultureInfo, caseSensitive); @@ -471,7 +478,7 @@ bool caseSensitive } /// - /// OrderByProperty constructor. + /// Initializes a new instance of the class. /// internal OrderByProperty() { @@ -482,7 +489,7 @@ internal OrderByProperty() /// /// Utility function used to create OrderByPropertyEntry for the supplied input object. /// - /// PSCmdlet + /// PSCmdlet. /// Input Object. /// Indicates if the Property value comparisons need to be case sensitive or not. /// Culture Info that needs to be used for comparison. @@ -500,14 +507,15 @@ 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) { cmdlet.WriteError(err); } + foreach (string debugMsg in propertyNotFoundMsgs) { cmdlet.WriteDebug(debugMsg); @@ -519,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; @@ -535,7 +543,7 @@ internal static OrderByPropertyEntry ProcessObject(PSObject inputObject, List expressionResults = ex.GetValues(inputObject, false, true); + List expressionResults = ex.GetValues(inputObject, false, true); if (expressionResults.Count == 0) { @@ -583,9 +591,10 @@ private static void EvaluateSortingExpression( propertyNotFoundMsg = StringUtil.Format(SortObjectStrings.PropertyNotFound, ex.ToString()); return; } + propertyNotFoundMsg = null; // we obtained some results, enter them into the list - foreach (MshExpressionResult r in expressionResults) + foreach (PSPropertyExpressionResult r in expressionResults) { if (r.Exception == null) { @@ -593,7 +602,7 @@ private static void EvaluateSortingExpression( } else { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( r.Exception, "ExpressionEvaluation", ErrorCategory.InvalidResult, @@ -601,18 +610,19 @@ private static void EvaluateSortingExpression( errors.Add(errorRecord); orderValues.Add(ObjectCommandPropertyValue.ExistingNullProperty); } + comparable = true; } } } /// - /// This is the row of the OrderMatrix + /// This is the row of the OrderMatrix. /// internal sealed class OrderByPropertyEntry { internal PSObject inputObject = null; - internal 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; @@ -620,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) { @@ -651,7 +661,7 @@ public int Compare(OrderByPropertyEntry firstEntry, OrderByPropertyEntry secondE return order; } - internal static OrderByPropertyComparer CreateComparer(List orderMatrix, bool ascendingFlag, Nullable[] ascendingOverrides, CultureInfo cultureInfo, bool caseSensitive) + internal static OrderByPropertyComparer CreateComparer(List orderMatrix, bool ascendingFlag, bool?[] ascendingOverrides, CultureInfo cultureInfo, bool caseSensitive) { if (orderMatrix.Count == 0) return null; @@ -664,6 +674,7 @@ internal static OrderByPropertyComparer CreateComparer(List maxEntries) maxEntries = entry.orderValues.Count; } + if (maxEntries == 0) return null; @@ -685,10 +696,10 @@ internal static OrderByPropertyComparer CreateComparer(List + internal sealed class IndexedOrderByPropertyComparer : IComparer { internal IndexedOrderByPropertyComparer(OrderByPropertyComparer orderByPropertyComparer) { @@ -702,6 +713,7 @@ public int Compare(OrderByPropertyEntry lhs, OrderByPropertyEntry rhs) { return lhs.comparable.CompareTo(rhs.comparable) * -1; } + int result = _orderByPropertyComparer.Compare(lhs, rhs); // When items are identical according to the internal comparison, compare by index // to preserve the original order @@ -713,7 +725,6 @@ public int Compare(OrderByPropertyEntry lhs, OrderByPropertyEntry rhs) return result; } - 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/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 f37c274aadf..3fabe75de24 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs @@ -1,8 +1,6 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -15,21 +13,15 @@ namespace Microsoft.PowerShell.Commands { /// - /// /// Retrieves input from the host virtual console and writes it to the pipeline output. - /// /// - - [Cmdlet(VerbsCommunications.Read, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113371")] - [OutputType(typeof(String), typeof(SecureString))] + [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() { @@ -39,11 +31,8 @@ public sealed class ReadHostCommand : PSCmdlet #region Parameters /// - /// /// The objects to display on the host before collecting input. - /// /// - [Parameter(Position = 0, ValueFromRemainingArguments = true)] [AllowNull] public @@ -62,12 +51,9 @@ public sealed class ReadHostCommand : PSCmdlet } /// - /// - /// Set to no echo the input as is is typed. - /// + /// Gets or sets to no echo the input as is typed. If set then the cmdlet returns a secure string. /// - - [Parameter] + [Parameter(ParameterSetName = "AsSecureString")] public SwitchParameter AsSecureString @@ -82,16 +68,24 @@ public sealed class ReadHostCommand : PSCmdlet _safe = value; } } - #endregion Parameters + /// + /// 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 + SwitchParameter + MaskInput + { + get; + set; + } + #endregion Parameters #region Cmdlet Overrides /// - /// - /// Write the prompt, then collect a line of input from the host, then - /// output it to the output stream. - /// + /// Write the prompt, then collect a line of input from the host, then output it to the output stream. /// protected override void BeginProcessing() { @@ -103,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()) { @@ -113,7 +107,7 @@ protected override void BeginProcessing() string element = (string)LanguagePrimitives.ConvertTo(e.Current, typeof(string), CultureInfo.InvariantCulture); - if (!String.IsNullOrEmpty(element)) + if (!string.IsNullOrEmpty(element)) { // Prepend a space if the stringbuilder isn't empty... // We could consider using $OFS here but that's probably more @@ -123,6 +117,7 @@ protected override void BeginProcessing() sb.Append(element); } } + promptString = sb.ToString(); } else @@ -130,34 +125,41 @@ 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)); + fd.SetParameterType(typeof(string)); } - Collection fdc = new Collection(); + Collection fdc = new(); fdc.Add(fd); - Dictionary result = Host.UI.Prompt("", "", fdc); + Dictionary result = Host.UI.Prompt(string.Empty, string.Empty, fdc); // Result can be null depending on the host implementation. One typical // example of a null return is for a canceled dialog. if (result != null) { 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(); } @@ -165,16 +167,21 @@ protected override void BeginProcessing() { result = Host.UI.ReadLine(); } - WriteObject(result); + + if (MaskInput) + { + WriteObject(Utils.GetStringFromSecureString((SecureString)result)); + } + else + { + WriteObject(result); + } } } #endregion Cmdlet Overrides - - private object _prompt = null; - private Boolean _safe = false; + private bool _safe = false; } -} // namespace Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs index 01bc5af4424..d4e7dc784c1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs @@ -1,8 +1,6 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -10,14 +8,14 @@ namespace Microsoft.PowerShell.Commands /// /// Registers for an event on an object. /// - [Cmdlet(VerbsLifecycle.Register, "ObjectEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135244")] + [Cmdlet(VerbsLifecycle.Register, "ObjectEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096714")] [OutputType(typeof(PSEventJob))] public class RegisterObjectEventCommand : ObjectEventRegistrationBase { #region parameters /// - /// The object on which to subscribe + /// The object on which to subscribe. /// [Parameter(Mandatory = true, Position = 0)] public PSObject InputObject @@ -26,15 +24,17 @@ public PSObject InputObject { return _inputObject; } + set { _inputObject = value; } } + private PSObject _inputObject = null; /// - /// The event name to subscribe + /// The event name to subscribe. /// [Parameter(Mandatory = true, Position = 1)] public string EventName @@ -43,29 +43,31 @@ public string EventName { return _eventName; } + set { _eventName = value; } } + private string _eventName = null; #endregion parameters /// - /// Returns the object that generates events to be monitored + /// Returns the object that generates events to be monitored. /// - protected override Object GetSourceObject() + protected override object GetSourceObject() { return _inputObject; } /// - /// Returns the event name to be monitored on the input object + /// Returns the event name to be monitored on the input object. /// - protected override String GetSourceObjectEventName() + protected override string GetSourceObjectEventName() { return _eventName; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs index 48a3fe8a09c..6e4ced90760 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -10,12 +9,12 @@ namespace Microsoft.PowerShell.Commands /// /// Registers for an event coming from the engine. /// - [Cmdlet(VerbsLifecycle.Register, "EngineEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135243")] + [Cmdlet(VerbsLifecycle.Register, "EngineEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097128")] [OutputType(typeof(PSEventJob))] public class RegisterEngineEventCommand : ObjectEventRegistrationBase { /// - /// Parameter for an identifier for this event subscription + /// Parameter for an identifier for this event subscription. /// [Parameter(Mandatory = true, Position = 100)] public new string SourceIdentifier @@ -24,6 +23,7 @@ public class RegisterEngineEventCommand : ObjectEventRegistrationBase { return base.SourceIdentifier; } + set { base.SourceIdentifier = value; @@ -31,9 +31,9 @@ public class RegisterEngineEventCommand : ObjectEventRegistrationBase } /// - /// Returns the object that generates events to be monitored + /// Returns the object that generates events to be monitored. /// - protected override Object GetSourceObject() + protected override object GetSourceObject() { // If it's not a forwarded event, the user must specify // an action @@ -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, @@ -55,11 +55,11 @@ protected override Object GetSourceObject() } /// - /// Returns the event name to be monitored on the input object + /// Returns the event name to be monitored on the input object. /// - protected override String GetSourceObjectEventName() + protected override string GetSourceObjectEventName() { return null; } } -} \ No newline at end of file +} 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 286c79437a2..de324b33fb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs @@ -1,24 +1,27 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// This class implements Remove-PSBreakpoint + /// This class implements Remove-PSBreakpoint. /// - [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113375")] - public class RemovePSBreakpointCommand : PSBreakpointCommandBase + [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097134")] + public class RemovePSBreakpointCommand : PSBreakpointUpdaterCommandBase { + #region overrides + /// - /// Removes the given breakpoint + /// Removes the given breakpoint. /// protected override void ProcessBreakpoint(Breakpoint breakpoint) { - this.Context.Debugger.RemoveBreakpoint(breakpoint); + Runspace.Debugger.RemoveBreakpoint(breakpoint); } + + #endregion overrides } -} \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs index 92f0c1fbf2e..15c48efd847 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Management.Automation; using System.Management.Automation.Internal; @@ -7,32 +9,31 @@ 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 { - #region Parameters + #region Parameters /// /// The alias name to remove. - /// + /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Name { get; set; } - + /// - /// The scope parameter for the command determines - /// which scope the alias is removed from. - /// + /// The scope parameter for the command determines which scope the alias is removed from. + /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// /// If set to true and an existing alias of the same name exists /// and is ReadOnly, it will still be deleted. - /// + /// [Parameter] - public SwitchParameter Force { get; set; } + public SwitchParameter Force { get; set; } #endregion Parameters @@ -40,13 +41,13 @@ public class RemoveAliasCommand : PSCmdlet /// /// The main processing loop of the command. - /// + /// protected override void ProcessRecord() { - foreach(string aliasName in Name) - { - AliasInfo existingAlias = null; - if (String.IsNullOrEmpty(Scope)) + foreach (string aliasName in Name) + { + AliasInfo existingAlias = null; + if (string.IsNullOrEmpty(Scope)) { existingAlias = SessionState.Internal.GetAlias(aliasName); } @@ -61,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 e50431aaf63..4b8ade4a94a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -10,13 +9,13 @@ namespace Microsoft.PowerShell.Commands /// /// Removes an event from the event queue. /// - [Cmdlet(VerbsCommon.Remove, "Event", SupportsShouldProcess = true, DefaultParameterSetName = "BySource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135247")] + [Cmdlet(VerbsCommon.Remove, "Event", SupportsShouldProcess = true, DefaultParameterSetName = "BySource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096715")] public class RemoveEventCommand : PSCmdlet { #region parameters /// - /// A source identifier for this event subscription + /// A source identifier for this event subscription. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "BySource")] public string SourceIdentifier @@ -25,6 +24,7 @@ public string SourceIdentifier { return _sourceIdentifier; } + set { _sourceIdentifier = value; @@ -35,10 +35,11 @@ public string SourceIdentifier } } } + private string _sourceIdentifier = null; /// - /// An identifier for this event subscription + /// An identifier for this event subscription. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByIdentifier")] public int EventIdentifier @@ -47,11 +48,13 @@ public int EventIdentifier { return _eventIdentifier; } + set { _eventIdentifier = value; } } + private int _eventIdentifier = -1; #endregion parameters @@ -59,7 +62,7 @@ public int EventIdentifier private WildcardPattern _matchPattern; /// - /// Remove the event from the queue + /// Remove the event from the queue. /// protected override void ProcessRecord() { @@ -91,7 +94,7 @@ protected override void ProcessRecord() foundMatch = true; if (ShouldProcess( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.EventResource, currentEvent.SourceIdentifier), @@ -108,9 +111,9 @@ protected override void ProcessRecord() (!WildcardPattern.ContainsWildcardCharacters(_sourceIdentifier)) && (!foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.SourceIdentifierNotFound, _sourceIdentifier)), "INVALID_SOURCE_IDENTIFIER", @@ -121,9 +124,9 @@ protected override void ProcessRecord() } else if ((_eventIdentifier >= 0) && (!foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.EventIdentifierNotFound, _eventIdentifier)), "INVALID_EVENT_IDENTIFIER", @@ -134,4 +137,4 @@ protected override void ProcessRecord() } } } -} \ No newline at end of file +} 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 new file mode 100644 index 00000000000..e0b0bff07c5 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs @@ -0,0 +1,837 @@ +// 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.Internal; + +using Microsoft.PowerShell.Commands.Internal.Format; + +namespace Microsoft.PowerShell.Commands +{ + internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition + { + protected override void SetEntries() + { + this.hashEntries.Add(new ExpressionEntryDefinition()); + this.hashEntries.Add(new NameEntryDefinition()); + } + } + + /// + /// + [Cmdlet(VerbsCommon.Select, "Object", DefaultParameterSetName = "DefaultParameter", + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096716", RemotingCapability = RemotingCapability.None)] + public sealed class SelectObjectCommand : PSCmdlet + { + #region Command Line Switches + + /// + /// + /// + [Parameter(ValueFromPipeline = true)] + public PSObject InputObject { get; set; } = AutomationNull.Value; + + /// + /// + /// + [Parameter(Position = 0, ParameterSetName = "DefaultParameter")] + [Parameter(Position = 0, ParameterSetName = "SkipLastParameter")] + public object[] Property { get; set; } + + /// + /// + /// + [Parameter(ParameterSetName = "DefaultParameter")] + [Parameter(ParameterSetName = "SkipLastParameter")] + public string[] ExcludeProperty { get; set; } + + /// + /// + /// + [Parameter(ParameterSetName = "DefaultParameter")] + [Parameter(ParameterSetName = "SkipLastParameter")] + public string ExpandProperty { get; set; } + + /// + /// + /// + [Parameter] + public SwitchParameter Unique + { + get { return _unique; } + + set { _unique = value; } + } + + 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; } + + /// + /// + /// + [Parameter(ParameterSetName = "DefaultParameter")] + // NTRAID#Windows Out Of Band Releases-927878-2006/03/02 + // Allow zero + [ValidateRange(0, int.MaxValue)] + public int Last + { + get { return _last; } + + set { _last = value; _firstOrLastSpecified = true; } + } + + private int _last = 0; + + /// + /// + /// + [Parameter(ParameterSetName = "DefaultParameter")] + // NTRAID#Windows Out Of Band Releases-927878-2006/03/02 + // Allow zero + [ValidateRange(0, int.MaxValue)] + public int First + { + get { return _first; } + + set { _first = value; _firstOrLastSpecified = true; } + } + + private int _first = 0; + private bool _firstOrLastSpecified; + + /// + /// 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; } + + /// + /// Skip the specified number of items from end. + /// + [Parameter(ParameterSetName = "SkipLastParameter")] + [ValidateRange(0, int.MaxValue)] + public int SkipLast { get; set; } + + /// + /// With this switch present, the cmdlet won't "short-circuit" + /// (i.e. won't stop upstream cmdlets after it knows that no further objects will be emitted downstream). + /// + [Parameter(ParameterSetName = "DefaultParameter")] + [Parameter(ParameterSetName = "IndexParameter")] + public SwitchParameter Wait { get; set; } + + /// + /// Used to display the object at the specified index. + /// + /// + [Parameter(ParameterSetName = "IndexParameter")] + [ValidateRange(0, int.MaxValue)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public int[] Index + { + get + { + return _index; + } + + set + { + _index = value; + _indexSpecified = true; + _isIncludeIndex = true; + Array.Sort(_index); + } + } + + /// + /// Used to display all objects at the specified indices. + /// + /// + [Parameter(ParameterSetName = "SkipIndexParameter")] + [ValidateRange(0, int.MaxValue)] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public int[] SkipIndex + { + get + { + return _index; + } + + set + { + _index = value; + _indexSpecified = true; + _isIncludeIndex = false; + Array.Sort(_index); + } + } + + private int[] _index; + private bool _indexSpecified; + private bool _isIncludeIndex; + + #endregion + + private SelectObjectQueue _selectObjectQueue; + + private sealed class SelectObjectQueue : Queue + { + internal SelectObjectQueue(int first, int last, int skip, int skipLast, bool firstOrLastSpecified) + { + _first = first; + _last = last; + _skip = skip; + _skipLast = skipLast; + _firstOrLastSpecified = firstOrLastSpecified; + } + + public bool AllRequestedObjectsProcessed + { + get + { + return _firstOrLastSpecified && _last == 0 && _first != 0 && _streamedObjectCount >= _first; + } + } + + public new void Enqueue(PSObject obj) + { + if (_last > 0 && this.Count >= (_last + _skip) && _first == 0) + { + base.Dequeue(); + } + else if (_last > 0 && this.Count >= _last && _first != 0) + { + base.Dequeue(); + } + + base.Enqueue(obj); + } + + public PSObject StreamingDequeue() + { + // if skip parameter is not mentioned or there are no more objects to skip + if (_skip == 0) + { + if (_skipLast > 0) + { + // We are going to skip some items from end, but it's okay to process + // the early input objects once we have more items in queue than the + // specified 'skipLast' value. + if (this.Count > _skipLast) + { + return Dequeue(); + } + } + else + { + if (_streamedObjectCount < _first || !_firstOrLastSpecified) + { + Diagnostics.Assert(this.Count > 0, "Streaming an empty queue"); + _streamedObjectCount++; + return Dequeue(); + } + + if (_last == 0) + { + Dequeue(); + } + } + } + else + { + // if last parameter is not mentioned,remove the objects and decrement the skip + if (_last == 0) + { + Dequeue(); + _skip--; + } + else if (_first != 0) + { + _skip--; + Dequeue(); + } + } + + return null; + } + + private int _streamedObjectCount; + private readonly int _first; + private readonly int _last; + private int _skip; + private readonly int _skipLast; + private readonly bool _firstOrLastSpecified; + } + + /// + /// List of processed parameters obtained from the Expression array. + /// + private List _propertyMshParameterList; + + /// + /// Singleton list of process parameters obtained from ExpandProperty. + /// + private List _expandMshParameterList; + + private PSPropertyExpressionFilter _exclusionFilter; + + private sealed class UniquePSObjectHelper + { + internal UniquePSObjectHelper(PSObject o, int notePropertyCount) + { + WrittenObject = o; + NotePropertyCount = notePropertyCount; + } + + internal readonly PSObject WrittenObject; + + internal int NotePropertyCount { get; } + } + + private List _uniques = null; + + private void ProcessExpressionParameter() + { + TerminatingErrorContext invocationContext = new(this); + ParameterProcessor processor = + new(new SelectObjectExpressionParameterDefinition()); + if ((Property != null) && (Property.Length != 0)) + { + // Build property list taking into account the wildcards and @{name=;expression=} + + _propertyMshParameterList = processor.ProcessParameters(Property, invocationContext); + } + else + { + // Property don't exist + _propertyMshParameterList = new List(); + } + + if (!string.IsNullOrEmpty(ExpandProperty)) + { + _expandMshParameterList = processor.ProcessParameters(new string[] { ExpandProperty }, invocationContext); + } + + if (ExcludeProperty != null) + { + _exclusionFilter = new PSPropertyExpressionFilter(ExcludeProperty); + // ExcludeProperty implies -Property * for better UX + if ((Property == null) || (Property.Length == 0)) + { + Property = new object[] { "*" }; + _propertyMshParameterList = processor.ProcessParameters(Property, invocationContext); + } + } + } + + private void ProcessObject(PSObject inputObject) + { + if ((Property == null || Property.Length == 0) && string.IsNullOrEmpty(ExpandProperty)) + { + FilteredWriteObject(inputObject, new List()); + return; + } + + // If property parameter is mentioned + List matchedProperties = new(); + foreach (MshParameter p in _propertyMshParameterList) + { + ProcessParameter(p, inputObject, matchedProperties); + } + + if (string.IsNullOrEmpty(ExpandProperty)) + { + PSObject result = new(); + if (matchedProperties.Count != 0) + { + HashSet propertyNames = new(StringComparer.OrdinalIgnoreCase); + + foreach (PSNoteProperty noteProperty in matchedProperties) + { + try + { + if (!propertyNames.Contains(noteProperty.Name)) + { + propertyNames.Add(noteProperty.Name); + result.Properties.Add(noteProperty); + } + else + { + WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, + "AlreadyExistingUserSpecifiedPropertyNoExpand"); + } + } + catch (ExtendedTypeSystemException) + { + WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, + "AlreadyExistingUserSpecifiedPropertyNoExpand"); + } + } + } + + FilteredWriteObject(result, matchedProperties); + } + else + { + ProcessExpandParameter(_expandMshParameterList[0], inputObject, matchedProperties); + } + } + + private void ProcessParameter(MshParameter p, PSObject inputObject, List result) + { + string name = p.GetEntry(NameEntryDefinition.NameEntryKey) as string; + + PSPropertyExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as PSPropertyExpression; + 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; + } + + foreach (PSPropertyExpressionResult mshExpRes in tempExprResults) + { + expressionResults.Add(mshExpRes); + } + } + } + + // allow 'Select-Object -Property noexist-name' to return a PSObject with property noexist-name, + // unless noexist-name itself contains wildcards + if (expressionResults.Count == 0 && !ex.HasWildCardCharacters) + { + expressionResults.Add(new PSPropertyExpressionResult(null, ex, null)); + } + + // if we have an expansion, renaming is not acceptable + else if (!string.IsNullOrEmpty(name) && expressionResults.Count > 1) + { + string errorMsg = SelectObjectStrings.RenamingMultipleResults; + ErrorRecord errorRecord = new( + new InvalidOperationException(errorMsg), + "RenamingMultipleResults", + ErrorCategory.InvalidOperation, + inputObject); + WriteError(errorRecord); + return; + } + + foreach (PSPropertyExpressionResult r in expressionResults) + { + // filter the exclusions, if any + if (_exclusionFilter != null && _exclusionFilter.IsMatch(r.ResolvedExpression)) + continue; + + PSNoteProperty mshProp; + if (string.IsNullOrEmpty(name)) + { + string resolvedExpressionName = r.ResolvedExpression.ToString(); + if (string.IsNullOrEmpty(resolvedExpressionName)) + { + PSArgumentException mshArgE = PSTraceSource.NewArgumentException( + "Property", + SelectObjectStrings.EmptyScriptBlockAndNoName); + ThrowTerminatingError( + new ErrorRecord( + mshArgE, + "EmptyScriptBlockAndNoName", + ErrorCategory.InvalidArgument, null)); + } + + mshProp = new PSNoteProperty(resolvedExpressionName, r.Result); + } + else + { + mshProp = new PSNoteProperty(name, r.Result); + } + + result.Add(mshProp); + } + } + + private void ProcessExpandParameter(MshParameter p, PSObject inputObject, + List matchedProperties) + { + PSPropertyExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as PSPropertyExpression; + List expressionResults = ex.GetValues(inputObject); + + if (expressionResults.Count == 0) + { + ErrorRecord errorRecord = new( + PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.PropertyNotFound, ExpandProperty), + "ExpandPropertyNotFound", + ErrorCategory.InvalidArgument, + inputObject); + throw new SelectObjectException(errorRecord); + } + + if (expressionResults.Count > 1) + { + ErrorRecord errorRecord = new( + PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.MutlipleExpandProperties, ExpandProperty), + "MutlipleExpandProperties", + ErrorCategory.InvalidArgument, + inputObject); + throw new SelectObjectException(errorRecord); + } + + PSPropertyExpressionResult r = expressionResults[0]; + if (r.Exception == null) + { + // ignore the property value if it's null + if (r.Result == null) + { + return; + } + + System.Collections.IEnumerable results = LanguagePrimitives.GetEnumerable(r.Result); + if (results == null) + { + // add NoteProperties if there is any + // If r.Result is a base object, we don't want to associate the NoteProperty + // directly with it. We want the NoteProperty to be associated only with this + // particular PSObject, so that when the user uses the base object else where, + // its members remain the same as before the Select-Object command run. + PSObject expandedObject = PSObject.AsPSObject(r.Result, true); + AddNoteProperties(expandedObject, inputObject, matchedProperties); + + FilteredWriteObject(expandedObject, matchedProperties); + return; + } + + foreach (object expandedValue in results) + { + // ignore the element if it's null + if (expandedValue == null) + { + continue; + } + + // add NoteProperties if there is any + // If expandedValue is a base object, we don't want to associate the NoteProperty + // directly with it. We want the NoteProperty to be associated only with this + // particular PSObject, so that when the user uses the base object else where, + // its members remain the same as before the Select-Object command run. + PSObject expandedObject = PSObject.AsPSObject(expandedValue, true); + AddNoteProperties(expandedObject, inputObject, matchedProperties); + + FilteredWriteObject(expandedObject, matchedProperties); + } + } + else + { + ErrorRecord errorRecord = new( + r.Exception, + "PropertyEvaluationExpand", + ErrorCategory.InvalidResult, + inputObject); + throw new SelectObjectException(errorRecord); + } + } + + private void AddNoteProperties(PSObject expandedObject, PSObject inputObject, IEnumerable matchedProperties) + { + foreach (PSNoteProperty noteProperty in matchedProperties) + { + try + { + if (expandedObject.Properties[noteProperty.Name] != null) + { + WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, "AlreadyExistingUserSpecifiedPropertyExpand"); + } + else + { + expandedObject.Properties.Add(noteProperty); + } + } + catch (ExtendedTypeSystemException) + { + WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, "AlreadyExistingUserSpecifiedPropertyExpand"); + } + } + } + + private void WriteAlreadyExistingPropertyError(string name, object inputObject, string errorId) + { + ErrorRecord errorRecord = new( + PSTraceSource.NewArgumentException("Property", SelectObjectStrings.AlreadyExistingProperty, name), + errorId, + ErrorCategory.InvalidOperation, + inputObject); + WriteError(errorRecord); + } + + private void FilteredWriteObject(PSObject obj, List addedNoteProperties) + { + Diagnostics.Assert(obj != null, "This command should never write null"); + + if (!_unique) + { + if (obj != AutomationNull.Value) + { + SetPSCustomObject(obj, newPSObject: addedNoteProperties.Count > 0); + WriteObject(obj); + } + + return; + } + // if only unique is mentioned + else if ((_unique)) + { + bool isObjUnique = true; + foreach (UniquePSObjectHelper uniqueObj in _uniques) + { + ObjectCommandComparer comparer = new( + ascending: true, + CultureInfo.CurrentCulture, + caseSensitive: !CaseInsensitive.IsPresent); + + if ((comparer.Compare(obj.BaseObject, uniqueObj.WrittenObject.BaseObject) == 0) && + (uniqueObj.NotePropertyCount == addedNoteProperties.Count)) + { + bool found = true; + foreach (PSNoteProperty note in addedNoteProperties) + { + PSMemberInfo prop = uniqueObj.WrittenObject.Properties[note.Name]; + if (prop == null || comparer.Compare(prop.Value, note.Value) != 0) + { + found = false; + break; + } + } + + if (found) + { + isObjUnique = false; + break; + } + } + else + { + continue; + } + } + + if (isObjUnique) + { + SetPSCustomObject(obj, newPSObject: addedNoteProperties.Count > 0); + _uniques.Add(new UniquePSObjectHelper(obj, addedNoteProperties.Count)); + } + } + } + + private void SetPSCustomObject(PSObject psObj, bool newPSObject) + { + if (psObj.ImmediateBaseObject is PSCustomObject) + { + var typeName = "Selected." + InputObject.BaseObject.GetType().ToString(); + if (newPSObject || !psObj.TypeNames.Contains(typeName)) + { + psObj.TypeNames.Insert(0, typeName); + } + } + } + + private void ProcessObjectAndHandleErrors(PSObject pso) + { + Diagnostics.Assert(pso != null, "Caller should verify pso != null"); + + try + { + ProcessObject(pso); + } + catch (SelectObjectException e) + { + WriteError(e.ErrorRecord); + } + } + + /// + /// + protected override void BeginProcessing() + { + ProcessExpressionParameter(); + + if (_unique) + { + _uniques = new List(); + } + + _selectObjectQueue = new SelectObjectQueue(_first, _last, Skip, SkipLast, _firstOrLastSpecified); + } + + /// + /// Handles processing of InputObject. + /// + protected override void ProcessRecord() + { + if (InputObject != AutomationNull.Value && InputObject != null) + { + if (_indexSpecified) + { + ProcessIndexed(); + } + else + { + _selectObjectQueue.Enqueue(InputObject); + PSObject streamingInputObject = _selectObjectQueue.StreamingDequeue(); + if (streamingInputObject != null) + { + ProcessObjectAndHandleErrors(streamingInputObject); + } + + if (_selectObjectQueue.AllRequestedObjectsProcessed && !this.Wait) + { + this.EndProcessing(); + throw new StopUpstreamCommandsException(this); + } + } + } + } + + /// + /// The index of the active index filter. + /// + private int _currentFilterIndex; + + /// + /// The index of the object being processed. + /// + private int _currentObjectIndex; + + /// + /// Handles processing of InputObject if -Index or -SkipIndex is specified. + /// + private void ProcessIndexed() + { + if (_isIncludeIndex) + { + if (_currentFilterIndex < _index.Length) + { + int nextIndexToOutput = _index[_currentFilterIndex]; + if (_currentObjectIndex == nextIndexToOutput) + { + ProcessObjectAndHandleErrors(InputObject); + while ((_currentFilterIndex < _index.Length) && (_index[_currentFilterIndex] == nextIndexToOutput)) + { + _currentFilterIndex++; + } + } + } + + if (!Wait && _currentFilterIndex >= _index.Length) + { + EndProcessing(); + throw new StopUpstreamCommandsException(this); + } + + _currentObjectIndex++; + } + else + { + if (_currentFilterIndex < _index.Length) + { + int nextIndexToSkip = _index[_currentFilterIndex]; + if (_currentObjectIndex != nextIndexToSkip) + { + ProcessObjectAndHandleErrors(InputObject); + } + else + { + while ((_currentFilterIndex < _index.Length) && (_index[_currentFilterIndex] == nextIndexToSkip)) + { + _currentFilterIndex++; + } + } + } + else + { + ProcessObjectAndHandleErrors(InputObject); + } + + _currentObjectIndex++; + } + } + + /// + /// Completes the processing of Input. + /// + protected override void EndProcessing() + { + // We can skip this part for 'IndexParameter' and 'SkipLastParameter' sets because: + // 1. 'IndexParameter' set doesn't use selectObjectQueue. + // 2. 'SkipLastParameter' set should have processed all valid input in the ProcessRecord. + if (ParameterSetName == "DefaultParameter") + { + if (_first != 0) + { + while ((_selectObjectQueue.Count > 0)) + { + ProcessObjectAndHandleErrors(_selectObjectQueue.Dequeue()); + } + } + else + { + while ((_selectObjectQueue.Count > 0)) + { + int lenQueue = _selectObjectQueue.Count; + if (lenQueue > Skip) + { + ProcessObjectAndHandleErrors(_selectObjectQueue.Dequeue()); + } + else + { + break; + } + } + } + } + + if (_uniques != null) + { + foreach (UniquePSObjectHelper obj in _uniques) + { + if (obj.WrittenObject == null || obj.WrittenObject == AutomationNull.Value) + { + continue; + } + + WriteObject(obj.WrittenObject); + } + } + } + } + + /// + /// Used only internally for select-object. + /// + [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 sealed class SelectObjectException : SystemException + { + internal ErrorRecord ErrorRecord { get; } + + internal SelectObjectException(ErrorRecord errorRecord) + { + ErrorRecord = errorRecord; + } + } +} 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 df2014d9fc2..2598d953496 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs @@ -1,293 +1,188 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.Globalization; -using System.Net.Mail; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Management.Automation; - +using System.Net.Mail; +using System.Text; namespace Microsoft.PowerShell.Commands { #region SendMailMessage /// - /// implementation for the Send-MailMessage command + /// Implementation for the Send-MailMessage command. /// - [Cmdlet(VerbsCommunications.Send, "MailMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135256")] + [Obsolete("This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage at this time. See https://aka.ms/SendMailMessage for more information.")] + [Cmdlet(VerbsCommunications.Send, "MailMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097115")] public sealed class SendMailMessage : PSCmdlet { #region Command Line Parameters /// - /// Specifies the files names to be attached to the email. + /// Gets or sets the files names to be attached to the email. /// If the filename specified can not be found, then the relevant error /// message should be thrown. /// - [Parameter(ValueFromPipeline = true)] + [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("PsPath")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Attachments - { - get { return _attachments; } - set - { - _attachments = value; - } - } - private String[] _attachments; + public string[] Attachments { get; set; } /// - /// Specifies the address collection that contains the + /// Gets or sets the address collection that contains the /// blind carbon copy (BCC) recipients for the e-mail message. /// - [Parameter] + [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Bcc - { - get { return _bcc; } - set - { - _bcc = value; - } - } - private String[] _bcc; + public string[] Bcc { get; set; } /// - /// Specifies the body (content) of the message + /// Gets or sets the body (content) of the message. /// - [Parameter(Position = 2)] + [Parameter(Position = 2, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - public String Body - { - get { return _body; } - set - { - _body = value; - } - } - private String _body; + public string Body { get; set; } /// - /// Specifies a value indicating whether the mail message body is in Html. + /// Gets or sets the value indicating whether the mail message body is in Html. /// - [Parameter] + [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("BAH")] - public SwitchParameter BodyAsHtml + public SwitchParameter BodyAsHtml { get; set; } + + /// + /// Gets or sets the encoding used for the content of the body and also the subject. + /// This is set to ASCII to ensure there are no problems with any email server. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [Alias("BE")] + [ValidateNotNullOrEmpty] + [ArgumentEncodingCompletions] + [ArgumentToEncodingTransformation] + public Encoding Encoding { - get { return _bodyashtml; } + get + { + return _encoding; + } + set { - _bodyashtml = value; + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; } } - private SwitchParameter _bodyashtml; - /// - /// Specifies the encoding used for the content of the body and also the subject. - /// This is set to ASCII to ensure there are no problems with any email server - /// - [Parameter()] - [Alias("BE")] - [ValidateNotNullOrEmpty] - [ArgumentCompletions( - EncodingConversion.Ascii, - EncodingConversion.BigEndianUnicode, - EncodingConversion.OEM, - EncodingConversion.Unicode, - EncodingConversion.Utf7, - EncodingConversion.Utf8, - EncodingConversion.Utf8Bom, - EncodingConversion.Utf8NoBom, - EncodingConversion.Utf32 - )] - [ArgumentToEncodingTransformationAttribute()] - public Encoding Encoding { get; set; } = Encoding.ASCII; + private Encoding _encoding = Encoding.ASCII; /// - /// Specifies the address collection that contains the + /// Gets or sets the address collection that contains the /// carbon copy (CC) recipients for the e-mail message. /// - [Parameter] + [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Cc")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Cc - { - get { return _cc; } - set - { - _cc = value; - } - } - private String[] _cc; + public string[] Cc { get; set; } /// - /// Specifies the delivery notifications options for the e-mail message. The various - /// option available for this parameter are None, OnSuccess, OnFailure, Delay and Never + /// Gets or sets the delivery notifications options for the e-mail message. The various + /// options available for this parameter are None, OnSuccess, OnFailure, Delay and Never. /// - [Parameter()] + [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("DNO")] [ValidateNotNullOrEmpty] - public DeliveryNotificationOptions DeliveryNotificationOption - { - get { return _deliverynotification; } - set - { - _deliverynotification = value; - } - } - private DeliveryNotificationOptions _deliverynotification; + public DeliveryNotificationOptions DeliveryNotificationOption { get; set; } /// - /// Specifies the from address for this e-mail message. The default value for - /// this parameter is the email address of the currently logged on user + /// Gets or sets the from address for this e-mail message. The default value for + /// this parameter is the email address of the currently logged on user. /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - public String From - { - get { return _from; } - set - { - _from = value; - } - } - private String _from; + public string From { get; set; } /// - /// Specifies the name of the Host used to send the email. This host name will be assigned - /// to the Powershell variable PSEmailServer,if this host can not reached an appropriate error + /// Gets or sets the name of the Host used to send the email. This host name will be assigned + /// to the Powershell variable PSEmailServer, if this host can not reached an appropriate error. /// message will be displayed. /// - [Parameter(Position = 3)] + [Parameter(Position = 3, ValueFromPipelineByPropertyName = true)] [Alias("ComputerName")] [ValidateNotNullOrEmpty] - public String SmtpServer - { - get { return _smtpserver; } - set - { - _smtpserver = value; - } - } - private String _smtpserver; + public string SmtpServer { get; set; } /// - /// Specifies the priority of the email message. The valid values for this are Normal, High and Low + /// Gets or sets the priority of the email message. The valid values for this are Normal, High and Low. /// - [Parameter] + [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - public MailPriority Priority - { - get { return _priority; } - set - { - _priority = value; - } - } - private MailPriority _priority; + public MailPriority Priority { get; set; } /// - /// Specifies the subject of the email message. + /// Gets or sets the Reply-To field for this e-mail message. /// - [Parameter(Mandatory = true, Position = 1)] - [Alias("sub")] - [ValidateNotNullOrEmpty] - public String Subject - { - get { return _subject; } - set - { - _subject = value; - } - } - private String _subject; + [Parameter(ValueFromPipelineByPropertyName = true)] + public string[] ReplyTo { get; set; } + /// + /// Gets or sets the subject of the email message. + /// + [Parameter(Mandatory = false, Position = 1, ValueFromPipelineByPropertyName = true)] + [Alias("sub")] + public string Subject { get; set; } /// - /// Specifies the To address for this e-mail message. + /// Gets or sets the To address for this e-mail message. /// - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] To - { - get { return _to; } - set - { - _to = value; - } - } - private String[] _to; + public string[] To { get; set; } /// - /// Specifies the credential for this e-mail message. + /// Gets or sets the credential for this e-mail message. /// - [Parameter()] + [Parameter(ValueFromPipelineByPropertyName = true)] [Credential] [ValidateNotNullOrEmpty] - public PSCredential Credential - { - get { return _credential; } - set - { - _credential = value; - } - } - private PSCredential _credential; + public PSCredential Credential { get; set; } /// - /// Specifies if Secured layer is required or not + /// Gets or sets if Secured layer is required or not. /// - [Parameter()] - public SwitchParameter UseSsl - { - get { return _usessl; } - set - { - _usessl = value; - } - } - private SwitchParameter _usessl; + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter UseSsl { get; set; } /// - /// Specifies the Port to be used on the server. + /// Gets or sets the Port to be used on the server. /// /// /// Value must be greater than zero. /// - [Parameter()] - [ValidateRange(0, Int32.MaxValue)] - public int Port - { - get { return _port; } - set { _port = value; } - } - private int _port = 0; + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateRange(0, int.MaxValue)] + public int Port { get; set; } #endregion - - #region private variables and methods - + #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; /// - /// Add the input addresses which are either string or hashtable to the MailMessage - /// It returns true if the from parameter has more than one value + /// Add the input addresses which are either string or hashtable to the MailMessage. + /// It returns true if the from parameter has more than one value. /// /// /// - /// private void AddAddressesToMailMessage(object address, string param) { string[] objEmailAddresses = address as string[]; @@ -312,11 +207,16 @@ private void AddAddressesToMailMessage(object address, string param) _mMailMessage.Bcc.Add(new MailAddress(strEmailAddress)); break; } + case "replyTo": + { + _mMailMessage.ReplyToList.Add(new MailAddress(strEmailAddress)); + break; + } } } catch (FormatException e) { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, null); + ErrorRecord er = new(e, "FormatException", ErrorCategory.InvalidType, null); WriteError(er); continue; } @@ -328,110 +228,111 @@ private void AddAddressesToMailMessage(object address, string param) #region Overrides /// - /// ProcessRecord override + /// BeginProcessing override. /// - protected override - void - BeginProcessing() + protected override void BeginProcessing() { try { // Set the sender address of the mail message - _mMailMessage.From = new MailAddress(_from); + _mMailMessage.From = new MailAddress(From); } catch (FormatException e) { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, _from); + ErrorRecord er = new(e, "FormatException", ErrorCategory.InvalidType, From); ThrowTerminatingError(er); - // return; } // Set the recipient address of the mail message - AddAddressesToMailMessage(_to, "to"); + AddAddressesToMailMessage(To, "to"); // Set the BCC address of the mail message - if (_bcc != null) + if (Bcc != null) { - AddAddressesToMailMessage(_bcc, "bcc"); + AddAddressesToMailMessage(Bcc, "bcc"); } // Set the CC address of the mail message - if (_cc != null) + if (Cc != null) { - AddAddressesToMailMessage(_cc, "cc"); + AddAddressesToMailMessage(Cc, "cc"); } + // Set the Reply-To address of the mail message + if (ReplyTo != null) + { + AddAddressesToMailMessage(ReplyTo, "replyTo"); + } - - //set the delivery notification - _mMailMessage.DeliveryNotificationOptions = _deliverynotification; + // Set the delivery notification + _mMailMessage.DeliveryNotificationOptions = DeliveryNotificationOption; // Set the subject of the mail message - _mMailMessage.Subject = _subject; + _mMailMessage.Subject = Subject; // Set the body of the mail message - _mMailMessage.Body = _body; + _mMailMessage.Body = Body; - //set the subject and body encoding + // Set the subject and body encoding _mMailMessage.SubjectEncoding = Encoding; _mMailMessage.BodyEncoding = Encoding; // Set the format of the mail message body as HTML - _mMailMessage.IsBodyHtml = _bodyashtml; + _mMailMessage.IsBodyHtml = BodyAsHtml; // Set the priority of the mail message to normal - _mMailMessage.Priority = _priority; + _mMailMessage.Priority = Priority; - - //get the PowerShell environment variable - //globalEmailServer might be null if it is deleted by: PS> del variable:PSEmailServer + // Get the PowerShell environment variable + // globalEmailServer might be null if it is deleted by: PS> del variable:PSEmailServer PSVariable globalEmailServer = SessionState.Internal.GetVariable(SpecialVariables.PSEmailServer); - if (_smtpserver == null && globalEmailServer != null) + if (SmtpServer == null && globalEmailServer != null) { - _smtpserver = Convert.ToString(globalEmailServer.Value, CultureInfo.InvariantCulture); + SmtpServer = Convert.ToString(globalEmailServer.Value, CultureInfo.InvariantCulture); } - if (string.IsNullOrEmpty(_smtpserver)) + + 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); } - if (0 == _port) + if (Port == 0) { - _mSmtpClient = new SmtpClient(_smtpserver); + _mSmtpClient = new SmtpClient(SmtpServer); } else { - _mSmtpClient = new SmtpClient(_smtpserver, _port); + _mSmtpClient = new SmtpClient(SmtpServer, Port); } - if (_usessl) + if (UseSsl) { _mSmtpClient.EnableSsl = true; } - if (_credential != null) + if (Credential != null) { _mSmtpClient.UseDefaultCredentials = false; - _mSmtpClient.Credentials = _credential.GetNetworkCredential(); + _mSmtpClient.Credentials = Credential.GetNetworkCredential(); } - else if (!_usessl) + else if (!UseSsl) { _mSmtpClient.UseDefaultCredentials = true; } } /// - /// ProcessRecord override + /// ProcessRecord override. /// protected override void ProcessRecord() { - //add the attachments - if (_attachments != null) + // Add the attachments + if (Attachments != null) { string filepath = string.Empty; - foreach (string attachFile in _attachments) + foreach (string attachFile in Attachments) { try { @@ -439,17 +340,18 @@ protected override void ProcessRecord() } catch (ItemNotFoundException e) { - //NOTE: This will throw + // NOTE: This will throw PathUtils.ReportFileOpenFailure(this, filepath, e); } - Attachment mailAttachment = new Attachment(filepath); + + Attachment mailAttachment = new(filepath); _mMailMessage.Attachments.Add(mailAttachment); } } } /// - /// EndProcessing + /// EndProcessing override. /// protected override void EndProcessing() { @@ -460,39 +362,42 @@ 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 + { + _mSmtpClient.Dispose(); - //if we don't dispose the attachments, the sender can't modify or use the files sent. - _mMailMessage.Attachments.Dispose(); + // If we don't dispose the attachments, the sender can't modify or use the files sent. + _mMailMessage.Attachments.Dispose(); + } } #endregion } - #endregion } 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 1371eec048a..ccf17652c79 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; @@ -8,94 +7,85 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.Commands { /// /// This class implements Set-PSBreakpoint command. /// - [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = "Line", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113449")] - [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] - public class SetPSBreakpointCommand : PSCmdlet + [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096623")] + [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 /// - /// the action to take when hitting this breakpoint + /// Gets or sets the action to take when hitting this breakpoint. /// - [Parameter(ParameterSetName = "Command")] - [Parameter(ParameterSetName = "Line")] - [Parameter(ParameterSetName = "Variable")] - public ScriptBlock Action { get; set; } = null; + [Parameter(ParameterSetName = CommandParameterSetName)] + [Parameter(ParameterSetName = LineParameterSetName)] + [Parameter(ParameterSetName = VariableParameterSetName)] + public ScriptBlock Action { get; set; } /// - /// The column to set the breakpoint on + /// Gets or sets the column to set the breakpoint on. /// - [Parameter(Position = 2, ParameterSetName = "Line")] + [Parameter(Position = 2, ParameterSetName = LineParameterSetName)] [ValidateRange(1, int.MaxValue)] - public int Column - { - get - { - return _column ?? 0; - } - set - { - _column = value; - } - } - private int? _column = null; - + public int Column { get; set; } /// - /// the command(s) to set the breakpoint on + /// Gets or sets the command(s) to set the breakpoint on. /// [Alias("C")] - [Parameter(ParameterSetName = "Command", Mandatory = true)] - [ValidateNotNull] - public string[] Command { get; set; } = null; + [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)] + public string[] Command { get; set; } /// - /// the line to set the breakpoint on + /// Gets or sets the line to set the breakpoint on. /// - [Parameter(Position = 1, ParameterSetName = "Line", Mandatory = true)] - [ValidateNotNull] - public int[] Line { get; set; } = null; + [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)] + public int[] Line { get; set; } /// - /// the script to set the breakpoint on + /// Gets or sets the script to set the breakpoint on. /// - [Parameter(ParameterSetName = "Command", Position = 0)] - [Parameter(ParameterSetName = "Line", Mandatory = true, Position = 0)] - [Parameter(ParameterSetName = "Variable", Position = 0)] + [Parameter(ParameterSetName = CommandParameterSetName, Position = 0)] + [Parameter(ParameterSetName = LineParameterSetName, Mandatory = true, Position = 0)] + [Parameter(ParameterSetName = VariableParameterSetName, Position = 0)] [ValidateNotNull] - public string[] Script { get; set; } = null; + public string[] Script { get; set; } /// - /// the variables to set the breakpoint(s) on + /// Gets or sets the variables to set the breakpoint(s) on. /// [Alias("V")] - [Parameter(ParameterSetName = "Variable", Mandatory = true)] - [ValidateNotNull] - public string[] Variable { get; set; } = null; + [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)] + public string[] Variable { get; set; } /// - /// + /// Gets or sets the access type for variable breakpoints to break on. /// - [Parameter(ParameterSetName = "Variable")] + [Parameter(ParameterSetName = VariableParameterSetName)] public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; #endregion parameters + #region overrides + /// - /// verifies that debugging is supported + /// 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 && ((this.Context.CurrentRunspace == null) || (this.Context.CurrentRunspace.Debugger == null) || ((this.Context.CurrentRunspace.Debugger.DebugMode & DebugModes.RemoteScript) != DebugModes.RemoteScript) && @@ -128,14 +118,12 @@ protected override void BeginProcessing() } /// - /// set a new breakpoint + /// Set a new breakpoint. /// protected override void ProcessRecord() { - // // If there is a script, resolve its path - // - Collection scripts = new Collection(); + Collection scripts = new(); if (Script != null) { @@ -152,7 +140,7 @@ protected override void ProcessRecord() WriteError( new ErrorRecord( new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), - "SetPSBreakpoint:FileDoesNotExist", + "NewPSBreakpoint:FileDoesNotExist", ErrorCategory.InvalidArgument, null)); @@ -166,7 +154,7 @@ protected override void ProcessRecord() WriteError( new ErrorRecord( new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), - "SetPSBreakpoint:WrongExtension", + "NewPSBreakpoint:WrongExtension", ErrorCategory.InvalidArgument, null)); continue; @@ -177,10 +165,8 @@ protected override void ProcessRecord() } } - // // If it is a command breakpoint... - // - if (ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) + if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Command.Length; i++) { @@ -188,21 +174,19 @@ protected override void ProcessRecord() { foreach (string path in scripts) { - WriteObject( - Context.Debugger.NewCommandBreakpoint(path.ToString(), Command[i], Action)); + ProcessBreakpoint( + Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path)); } } else { - WriteObject( - Context.Debugger.NewCommandBreakpoint(Command[i], Action)); + ProcessBreakpoint( + Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path: null)); } } } - // // If it is a variable breakpoint... - // - else if (ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Variable.Length; i++) { @@ -210,23 +194,21 @@ protected override void ProcessRecord() { foreach (string path in scripts) { - WriteObject( - Context.Debugger.NewVariableBreakpoint(path.ToString(), Variable[i], Mode, Action)); + ProcessBreakpoint( + Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path)); } } else { - WriteObject( - Context.Debugger.NewVariableBreakpoint(Variable[i], Mode, Action)); + ProcessBreakpoint( + Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path: null)); } } } - // // Else it is the default parameter set (Line breakpoint)... - // else { - Debug.Assert(ParameterSetName.Equals("Line", StringComparison.OrdinalIgnoreCase)); + Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); for (int i = 0; i < Line.Length; i++) { @@ -244,19 +226,13 @@ protected override void ProcessRecord() foreach (string path in scripts) { - if (_column != null) - { - WriteObject( - Context.Debugger.NewStatementBreakpoint(path, Line[i], Column, Action)); - } - else - { - WriteObject( - Context.Debugger.NewLineBreakpoint(path, Line[i], Action)); - } + ProcessBreakpoint( + Runspace.Debugger.SetLineBreakpoint(path, Line[i], Column, Action)); } } } } + + #endregion overrides } -} \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs index ce54fa27ce0..6c0007fa2a2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs @@ -1,18 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { /// - /// The implementation of the "set-alias" cmdlet + /// The implementation of the "set-alias" cmdlet. /// - /// - [Cmdlet(VerbsCommon.Set, "Alias", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113390")] + [Cmdlet(VerbsCommon.Set, "Alias", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096625")] [OutputType(typeof(AliasInfo))] public class SetAliasCommand : WriteAliasCommandBase { @@ -21,13 +18,12 @@ public class SetAliasCommand : WriteAliasCommandBase /// /// The main processing loop of the command. /// - /// protected override void ProcessRecord() { // Create the alias info AliasInfo aliasToSet = - new AliasInfo( + new( Name, Value, Context, @@ -48,7 +44,7 @@ protected override void ProcessRecord() try { - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { result = SessionState.Internal.SetAliasItem(aliasToSet, Force, MyInvocation.CommandOrigin); } @@ -73,8 +69,7 @@ protected override void ProcessRecord() WriteObject(result); } } - } // ProcessRecord + } #endregion Command code - } // class SetAliasCommand -}//Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs index 1f7e91e8230..5dfe40fa9d4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs @@ -1,6 +1,6 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma warning disable 1634, 1691 using System; @@ -8,36 +8,35 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Runtime.InteropServices; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// implementation for the set-date command + /// Implementation for the set-date command. /// - [Cmdlet(VerbsCommon.Set, "Date", DefaultParameterSetName = "Date", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113393")] + [Cmdlet(VerbsCommon.Set, "Date", DefaultParameterSetName = "Date", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097133")] [OutputType(typeof(DateTime))] public sealed class SetDateCommand : PSCmdlet { #region parameters /// - /// Allows user to override the date/time object that will be processed + /// Allows user to override the date/time object that will be processed. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Date", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public DateTime Date { get; set; } - /// - /// Allows a use to specify a timespan with which to apply to the current time + /// Allows a use to specify a timespan with which to apply to the current time. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Adjust", ValueFromPipelineByPropertyName = true)] [AllowNull] public TimeSpan Adjust { get; set; } - /// - /// This option determines the default output format used to display the object set-date emits + /// This option determines the default output format used to display the object set-date emits. /// [Parameter] public DisplayHintType DisplayHint { get; set; } = DisplayHintType.DateTime; @@ -47,9 +46,8 @@ public sealed class SetDateCommand : PSCmdlet #region methods /// - /// set the date + /// Set the date. /// - [ArchitectureSensitive] protected override void ProcessRecord() { DateTime dateToUse; @@ -72,43 +70,60 @@ 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); + // output DateTime object wrapped in an PSObject with DisplayHint attached + PSObject outputObj = new(dateToUse); + PSNoteProperty note = new("DisplayHint", DisplayHint); outputObj.Properties.Add(note); - WriteObject(outputObj); - } // EndProcessing + // 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 @@ -119,22 +134,20 @@ 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)] + [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetLocalTime(ref SystemTime systime); - } // NativeMethods - + } #endregion - } // SetDateCommand -} // namespace Microsoft.PowerShell.Commands - - + } +} 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 e048249152e..60b05807d48 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs @@ -1,31 +1,29 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// 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.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using Microsoft.PowerShell.Commands.ShowCommandExtension; namespace Microsoft.PowerShell.Commands { - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Globalization; - using System.Management.Automation; - using System.Reflection; - using System.Runtime.InteropServices; - using System.Text; - using System.Threading; - using Microsoft.PowerShell.Commands.ShowCommandExtension; - /// /// Show-Command displays a GUI for a cmdlet, or for all cmdlets if no specific cmdlet is specified. /// - [Cmdlet(VerbsCommon.Show, "Command", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217448")] + [Cmdlet(VerbsCommon.Show, "Command", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109589")] public class ShowCommandCommand : PSCmdlet, IDisposable { #region Private Fields /// - /// Set to true when ProcessRecord is reached, since it will always open a window + /// Set to true when ProcessRecord is reached, since it will always open a window. /// private bool _hasOpenedWindow; @@ -45,14 +43,14 @@ public class ShowCommandCommand : PSCmdlet, IDisposable private List _commands; /// - /// List of modules that have been loaded indexed by module name + /// List of modules that have been loaded indexed by module name. /// private Dictionary _importedModules; /// /// Record the EndProcessing error. /// - private PSDataCollection _errors = new PSDataCollection(); + private PSDataCollection _errors = new(); /// /// Field used for the NoCommonParameter parameter. @@ -60,19 +58,11 @@ public class ShowCommandCommand : PSCmdlet, IDisposable private SwitchParameter _noCommonParameter; /// - /// Object used for ShowCommand with a command name that holds the view model created for the command + /// Object used for ShowCommand with a command name that holds the view model created for the command. /// 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. @@ -85,48 +75,50 @@ 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; } /// - /// Gets or sets a value indicating Common Parameters should not be displayed + /// Gets or sets a value indicating Common Parameters should not be displayed. /// [Parameter] public SwitchParameter NoCommonParameter { get { return _noCommonParameter; } + set { _noCommonParameter = value; } } /// - /// Gets or sets a value indicating errors should not cause a message window to be displayed + /// Gets or sets a value indicating errors should not cause a message window to be displayed. /// [Parameter] public SwitchParameter ErrorPopup { get; set; } /// - /// Gets or sets a value indicating the command should be sent to the pipeline as a string instead of run + /// Gets or sets a value indicating the command should be sent to the pipeline as a string instead of run. /// [Parameter] public SwitchParameter PassThru { get { return _passThrough; } + set { _passThrough = value; } - } // PassThru + } #endregion #region Public and Protected Methods /// /// Executes a PowerShell script, writing the output objects to the pipeline. /// - /// Script to execute + /// Script to execute. public void RunScript(string script) { if (_showCommandProxy == null || string.IsNullOrEmpty(script)) @@ -156,7 +148,8 @@ public void RunScript(string script) return; } - if (!ConsoleInputWithNativeMethods.AddToConsoleInputBuffer(script, true)) + // Don't send newline at end as PSReadLine shows it rather than executing + if (!ConsoleInputWithNativeMethods.AddToConsoleInputBuffer(script, newLine: false)) { this.WriteDebug(FormatAndOut_out_gridview.CannotWriteToConsoleInputBuffer); this.RunScriptSilentlyAndWithErrorHookup(script); @@ -164,7 +157,7 @@ public void RunScript(string script) } /// - /// Dispose method in IDisposable + /// Dispose method in IDisposable. /// public void Dispose() { @@ -181,8 +174,8 @@ protected override void BeginProcessing() if (_showCommandProxy.ScreenHeight < this.Height) { - ErrorRecord error = new ErrorRecord( - new NotSupportedException(String.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Height", _showCommandProxy.ScreenHeight)), + ErrorRecord error = new( + new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Height", _showCommandProxy.ScreenHeight)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, null); @@ -191,8 +184,8 @@ protected override void BeginProcessing() if (_showCommandProxy.ScreenWidth < this.Width) { - ErrorRecord error = new ErrorRecord( - new NotSupportedException(String.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Width", _showCommandProxy.ScreenWidth)), + ErrorRecord error = new( + new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Width", _showCommandProxy.ScreenWidth)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, null); @@ -216,7 +209,7 @@ protected override void ProcessRecord() } /// - /// Optionally displays errors in a message + /// Optionally displays errors in a message. /// protected override void EndProcessing() { @@ -225,8 +218,8 @@ protected override void EndProcessing() return; } - // We wait untill the window is loaded and then activate it - // to work arround the console window gaining activation somewhere + // We wait until the window is loaded and then activate it + // to work around the console window gaining activation somewhere // in the end of ProcessRecord, which causes the keyboard focus // (and use oif tab key to focus controls) to go away from the window _showCommandProxy.WindowLoaded.WaitOne(); @@ -240,7 +233,7 @@ protected override void EndProcessing() return; } - StringBuilder errorString = new StringBuilder(); + StringBuilder errorString = new(); for (int i = 0; i < _errors.Count; i++) { @@ -271,16 +264,16 @@ protected override void StopProcessing() /// Runs the script in a new PowerShell instance and hooks up error stream to potentially display error popup. /// This method has the inconvenience of not showing to the console user the script being executed. /// - /// script to be run + /// Script to be run. 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; - PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); ps.Streams.Error = _errors; ps.Commands.AddScript(script); @@ -289,12 +282,12 @@ private void RunScriptSilentlyAndWithErrorHookup(string script) } /// - /// Issues an error when this.commandName was not found + /// Issues an error when this.commandName was not found. /// private void IssueErrorForNoCommand() { - InvalidOperationException errorException = new InvalidOperationException( - String.Format( + InvalidOperationException errorException = new( + string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.CommandNotFound, Name)); @@ -302,12 +295,12 @@ private void IssueErrorForNoCommand() } /// - /// Issues an error when there is more than one command matching this.commandName + /// Issues an error when there is more than one command matching this.commandName. /// private void IssueErrorForMoreThanOneCommand() { - InvalidOperationException errorException = new InvalidOperationException( - String.Format( + InvalidOperationException errorException = new( + string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.MoreThanOneCommand, Name, @@ -316,10 +309,10 @@ private void IssueErrorForMoreThanOneCommand() } /// - /// Called from CommandProcessRecord to run the command that will get the CommandInfo and list of modules + /// Called from CommandProcessRecord to run the command that will get the CommandInfo and list of modules. /// - /// command to be retrieved - /// list of loaded modules + /// Command to be retrieved. + /// List of loaded modules. private void GetCommandInfoAndModules(out CommandInfo command, out Dictionary modules) { command = null; @@ -367,7 +360,7 @@ private void GetCommandInfoAndModules(out CommandInfo command, out Dictionary /// ProcessRecord when a command name is specified. /// - /// true if there was no exception processing this record + /// True if there was no exception processing this record. private bool CanProcessRecordForOneCommand() { CommandInfo commandInfo; @@ -376,7 +369,7 @@ private bool CanProcessRecordForOneCommand() try { - _commandViewModelObj = _showCommandProxy.GetCommandViewModel(new ShowCommandCommandInfo(commandInfo), _noCommonParameter.ToBool(), _importedModules, this.Name.IndexOf('\\') != -1); + _commandViewModelObj = _showCommandProxy.GetCommandViewModel(new ShowCommandCommandInfo(commandInfo), _noCommonParameter.ToBool(), _importedModules, this.Name.Contains('\\')); _showCommandProxy.ShowCommandWindow(_commandViewModelObj, _passThrough); } catch (TargetInvocationException ti) @@ -391,7 +384,7 @@ private bool CanProcessRecordForOneCommand() /// /// ProcessRecord when a command name is not specified. /// - /// true if there was no exception processing this record + /// True if there was no exception processing this record. private bool CanProcessRecordForAllCommands() { Collection rawCommands = this.InvokeCommand.InvokeScript(_showCommandProxy.GetShowAllModulesCommand()); @@ -413,11 +406,11 @@ private bool CanProcessRecordForAllCommands() } /// - /// Waits untill the window has been closed answering HelpNeeded events + /// Waits until the window has been closed answering HelpNeeded events. /// private void WaitForWindowClosedOrHelpNeeded() { - do + while (true) { int which = WaitHandle.WaitAny(new WaitHandle[] { _showCommandProxy.WindowClosed, _showCommandProxy.HelpNeeded, _showCommandProxy.ImportModuleNeeded }); @@ -451,33 +444,32 @@ private void WaitForWindowClosedOrHelpNeeded() _showCommandProxy.ImportModuleDone(_importedModules, _commands); continue; } - while (true); } /// - /// Writes the output of a script being run into the pipeline + /// Writes the output of a script being run into the pipeline. /// - /// output collection - /// output event + /// Output collection. + /// Output event. private void Output_DataAdded(object sender, DataAddedEventArgs e) { this.WriteObject(((PSDataCollection)sender)[e.Index]); } /// - /// Writes the errors of a script being run into the pipeline + /// Writes the errors of a script being run into the pipeline. /// - /// error collection - /// error event + /// Error collection. + /// Error event. private void Error_DataAdded(object sender, DataAddedEventArgs e) { this.WriteError(((PSDataCollection)sender)[e.Index]); } /// - /// Implements IDisposable logic + /// Implements IDisposable logic. /// - /// true if being called from Dispose + /// True if being called from Dispose. private void Dispose(bool isDisposing) { if (isDisposing) @@ -492,21 +484,21 @@ private void Dispose(bool isDisposing) #endregion /// - /// Wraps interop code for console input buffer + /// Wraps interop code for console input buffer. /// internal static class ConsoleInputWithNativeMethods { /// - /// Constant used in calls to GetStdHandle + /// Constant used in calls to GetStdHandle. /// internal const int STD_INPUT_HANDLE = -10; /// - /// Adds a string to the console input buffer + /// Adds a string to the console input buffer. /// - /// string to add to console input buffer - /// true to add Enter after the string - /// true if it was successful in adding all characters to console input buffer + /// String to add to console input buffer. + /// True to add Enter after the string. + /// True if it was successful in adding all characters to console input buffer. internal static bool AddToConsoleInputBuffer(string str, bool newLine) { IntPtr handle = ConsoleInputWithNativeMethods.GetStdHandle(ConsoleInputWithNativeMethods.STD_INPUT_HANDLE); @@ -554,21 +546,21 @@ internal static bool AddToConsoleInputBuffer(string str, bool newLine) } /// - /// Gets the console handle + /// Gets the console handle. /// - /// which console handle to get - /// the console handle + /// Which console handle to get. + /// The console handle. [DllImport("kernel32.dll", SetLastError = true)] internal static extern IntPtr GetStdHandle(int nStdHandle); /// - /// Writes to the console input buffer + /// Writes to the console input buffer. /// - /// console handle - /// inputs to be written - /// number of inputs to be written - /// returned number of inputs actually written - /// 0 if the function fails + /// Console handle. + /// Inputs to be written. + /// Number of inputs to be written. + /// Returned number of inputs actually written. + /// 0 if the function fails. [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool WriteConsoleInput( @@ -578,31 +570,31 @@ internal static extern bool WriteConsoleInput( out uint lpNumberOfEventsWritten); /// - /// A record to be added to the console buffer + /// A record to be added to the console buffer. /// internal struct INPUT_RECORD { /// - /// The proper event type for a KeyEvent KEY_EVENT_RECORD + /// The proper event type for a KeyEvent KEY_EVENT_RECORD. /// internal const int KEY_EVENT = 0x0001; /// - /// input buffer event type + /// Input buffer event type. /// internal ushort EventType; /// - /// The actual event. The original structure is a union of many others, but this is the largest of them - /// And we don't need other kinds of events + /// The actual event. The original structure is a union of many others, but this is the largest of them. + /// And we don't need other kinds of events. /// internal KEY_EVENT_RECORD KeyEvent; /// /// Sets the necessary fields of for a KeyDown event for the /// - /// input record to be set - /// character to set the record with + /// Input record to be set. + /// Character to set the record with. internal static void SetInputRecord(ref INPUT_RECORD inputRecord, char character) { inputRecord.EventType = INPUT_RECORD.KEY_EVENT; @@ -612,38 +604,38 @@ internal static void SetInputRecord(ref INPUT_RECORD inputRecord, char character } /// - /// Type of INPUT_RECORD which is a key + /// Type of INPUT_RECORD which is a key. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct KEY_EVENT_RECORD { /// - /// true for key down and false for key up, but only needed if wVirtualKeyCode is used + /// True for key down and false for key up, but only needed if wVirtualKeyCode is used. /// internal bool bKeyDown; /// - /// repeat count + /// Repeat count. /// internal ushort wRepeatCount; /// - /// virtual key code + /// Virtual key code. /// internal ushort wVirtualKeyCode; /// - /// virtual key scan code + /// Virtual key scan code. /// internal ushort wVirtualScanCode; /// - /// character in input. If this is specified, wVirtualKeyCode, and others don't need to be + /// Character in input. If this is specified, wVirtualKeyCode, and others don't need to be. /// internal char UnicodeChar; /// - /// State of keys like Shift and control + /// State of keys like Shift and control. /// internal uint dwControlKeyState; } 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 c6e6ae0457a..e2ba41fb3fc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs @@ -1,34 +1,28 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.ShowCommandExtension { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - /// - /// Implements a facade around CommandInfo and its deserialized counterpart + /// Implements a facade around CommandInfo and its deserialized counterpart. /// 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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.ModuleName = other.ModuleName; @@ -42,7 +36,7 @@ public ShowCommandCommandInfo(CommandInfo other) { this.ParameterSets = other.ParameterSets - .Select(x => new ShowCommandParameterSetInfo(x)) + .Select(static x => new ShowCommandParameterSetInfo(x)) .ToList() .AsReadOnly(); } @@ -66,18 +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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; this.ModuleName = other.Members["ModuleName"].Value as string; @@ -95,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); } @@ -105,9 +96,8 @@ public ShowCommandCommandInfo(PSObject other) } /// - /// Builds a strongly typed IEnumerable{object} out of an IEnumerable + /// Builds a strongly typed IEnumerable{object} out of an IEnumerable. /// - /// /// /// The object to enumerate. /// @@ -122,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; } } -} \ No newline at end of file +} 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 384c4c8bc6f..f31bc93525d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs @@ -1,56 +1,47 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.ShowCommandExtension { - using System; - using System.Management.Automation; - /// - /// Implements a facade around PSModuleInfo and its deserialized counterpart + /// Implements a facade around PSModuleInfo and its deserialized counterpart. /// 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 (null == other) - { - throw new ArgumentNullException("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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; } /// - /// Gets the name of this module + /// Gets the name of this module. /// - public string Name { get; private set; } + public string Name { get; } } -} \ No newline at end of file +} 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 95ce809a7e0..9bf79c5bd76 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs @@ -1,35 +1,28 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.ShowCommandExtension { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - - /// - /// Implements a facade around ShowCommandParameterInfo and its deserialized counterpart + /// Implements a facade around ShowCommandParameterInfo and its deserialized counterpart. /// 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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.IsMandatory = other.IsMandatory; @@ -37,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; @@ -46,18 +39,15 @@ public ShowCommandParameterInfo(CommandParameterInfo other) } /// - /// Creates an instance of the ShowCommandParameterInfo class based on a PSObject object + /// Initializes a new instance of the class. + /// Creates an instance of the ShowCommandParameterInfo class based on a PSObject object. /// - /// /// /// The object to wrap. /// public ShowCommandParameterInfo(PSObject other) { - if (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; this.IsMandatory = (bool)(other.Members["IsMandatory"].Value); @@ -74,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 + /// 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; } } -} \ No newline at end of file +} 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 0a1933e4427..c5ec1c74c08 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs @@ -1,74 +1,64 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.ShowCommandExtension { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - - /// - /// Implements a facade around CommandParameterSetInfo and its deserialized counterpart + /// Implements a facade around CommandParameterSetInfo and its deserialized counterpart. /// 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 (null == other) - { - throw new ArgumentNullException("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 (null == other) - { - throw new ArgumentNullException("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 + /// 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; } } -} \ No newline at end of file +} 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 9760a704688..01da285a1d7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs @@ -1,34 +1,27 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.ShowCommandExtension { - using System; - using System.Collections; - using System.Management.Automation; - - /// - /// Implements a facade around ShowCommandParameterInfo and its deserialized counterpart + /// Implements a facade around ShowCommandParameterInfo and its deserialized counterpart. /// 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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.FullName = other.FullName; if (other.IsEnum) @@ -47,18 +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 (null == other) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); this.IsEnum = (bool)(other.Members["IsEnum"].Value); this.FullName = other.Members["FullName"].Value as string; @@ -78,82 +68,82 @@ public ShowCommandParameterType(PSObject other) } /// - /// The full name of the outermost type + /// 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 + /// 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 + /// 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 + /// 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 + /// 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 + /// 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 + /// Whether or not this type is a string. /// public bool IsString { get { - return String.Equals(this.FullName, "System.String", StringComparison.OrdinalIgnoreCase); + return string.Equals(this.FullName, "System.String", StringComparison.OrdinalIgnoreCase); } } /// - /// Whether or not this type is an script block + /// Whether or not this type is an script block. /// public bool IsScriptBlock { get { - return String.Equals(this.FullName, "System.Management.Automation.ScriptBlock", StringComparison.OrdinalIgnoreCase); + return string.Equals(this.FullName, "System.Management.Automation.ScriptBlock", StringComparison.OrdinalIgnoreCase); } } /// - /// Whether or not this type is a bool + /// Whether or not this type is a bool. /// public bool IsBoolean { get { - return String.Equals(this.FullName, "System.Management.Automation.ScriptBlock", StringComparison.OrdinalIgnoreCase); + return string.Equals(this.FullName, "System.Management.Automation.ScriptBlock", StringComparison.OrdinalIgnoreCase); } } /// - /// Whether or not this type is a switch parameter + /// Whether or not this type is a switch parameter. /// public bool IsSwitch { get { - return String.Equals(this.FullName, "System.Management.Automation.SwitchParameter", StringComparison.OrdinalIgnoreCase); + return string.Equals(this.FullName, "System.Management.Automation.SwitchParameter", StringComparison.OrdinalIgnoreCase); } } /// - /// If this is an enum value, return the list of potential values + /// If this is an enum value, return the list of potential values. /// - public ArrayList EnumValues { get; private set; } + public ArrayList EnumValues { get; } } -} \ No newline at end of file +} 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 fcda24ca44f..ab8909bb590 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs @@ -1,30 +1,28 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// 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.Management.Automation.Internal; +using System.Threading; + +using Microsoft.PowerShell.Commands.ShowCommandExtension; namespace Microsoft.PowerShell.Commands { - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Management.Automation.Internal; - using System.Threading; - using System.Collections.ObjectModel; - using Microsoft.PowerShell.Commands.ShowCommandExtension; - /// /// Help show-command create WPF object and invoke WPF windows with the - /// Microsoft.PowerShell.Commands.ShowCommandInternal.ShowCommandHelperhelp type defined in Microsoft.PowerShell.GraphicalHost.dll + /// 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) { @@ -77,9 +75,9 @@ internal string GetShowAllModulesCommand() return (string)_graphicalHostReflectionWrapper.CallStaticMethod("GetShowAllModulesCommand", false, true); } - internal Dictionary GetImportedModulesDictionary(object[] moduleObjects) + internal Dictionary GetImportedModulesDictionary(object[] moduleObjects) { - return (Dictionary)_graphicalHostReflectionWrapper.CallStaticMethod("GetImportedModulesDictionary", new object[] { moduleObjects }); + return (Dictionary)_graphicalHostReflectionWrapper.CallStaticMethod("GetImportedModulesDictionary", new object[] { moduleObjects }); } internal List GetCommandList(object[] commandObjects) @@ -127,7 +125,6 @@ internal AutoResetEvent WindowLoaded } } - internal string CommandNeedingHelp { get @@ -149,7 +146,6 @@ internal void DisplayHelp(Collection helpResults) _graphicalHostReflectionWrapper.CallMethod("DisplayHelp", helpResults); } - internal string GetImportModuleCommand(string module) { return (string)_graphicalHostReflectionWrapper.CallStaticMethod("GetImportModuleCommand", module, false, true); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs new file mode 100644 index 00000000000..3f40ec3439e --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs @@ -0,0 +1,230 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Internal; + +using Microsoft.PowerShell.MarkdownRender; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Show the VT100EncodedString or Html property of on console or show. + /// VT100EncodedString will be displayed on console. + /// Html will be displayed in default browser. + /// + [Cmdlet( + VerbsCommon.Show, "Markdown", + DefaultParameterSetName = "Path", + HelpUri = "https://go.microsoft.com/fwlink/?linkid=2102329")] + [OutputType(typeof(string))] + public class ShowMarkdownCommand : PSCmdlet + { + /// + /// Gets or sets InputObject of type Microsoft.PowerShell.MarkdownRender.MarkdownInfo to display. + /// + [ValidateNotNullOrEmpty] + [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "InputObject")] + public PSObject InputObject { get; set; } + + /// + /// Gets or sets path to markdown file(s) to display. + /// + [ValidateNotNullOrEmpty] + [Parameter(Position = 0, Mandatory = true, + ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] + public string[] Path { get; set; } + + /// + /// Gets or sets the literal path parameter to markdown files(s) to display. + /// + [Parameter(ParameterSetName = "LiteralPath", + Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] + [Alias("PSPath", "LP")] + public string[] LiteralPath + { + get { return Path; } + + set { Path = value; } + } + + /// + /// Gets or sets the switch to view Html in default browser. + /// + [Parameter] + public SwitchParameter UseBrowser { get; set; } + + private System.Management.Automation.PowerShell _powerShell; + + /// + /// Override BeginProcessing. + /// + protected override void BeginProcessing() + { + _powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + } + + /// + /// Override ProcessRecord. + /// + protected override void ProcessRecord() + { + switch (ParameterSetName) + { + case "InputObject": + if (InputObject.BaseObject is MarkdownInfo markdownInfo) + { + ProcessMarkdownInfo(markdownInfo); + } + else + { + ConvertFromMarkdown("InputObject", InputObject.BaseObject); + } + + break; + + case "Path": + case "LiteralPath": + ConvertFromMarkdown(ParameterSetName, Path); + break; + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ConvertMarkdownStrings.InvalidParameterSet, ParameterSetName)); + } + } + + /// + /// Process markdown as path. + /// + /// Name of parameter to pass to `ConvertFrom-Markdown`. + /// Value of parameter. + private void ConvertFromMarkdown(string parameter, object input) + { + _powerShell.AddCommand("Microsoft.PowerShell.Utility\\ConvertFrom-Markdown").AddParameter(parameter, input); + if (!UseBrowser) + { + _powerShell.AddParameter("AsVT100EncodedString"); + } + + Collection output = _powerShell.Invoke(); + + if (_powerShell.HadErrors) + { + foreach (ErrorRecord errorRecord in _powerShell.Streams.Error) + { + WriteError(errorRecord); + } + } + + foreach (MarkdownInfo result in output) + { + ProcessMarkdownInfo(result); + } + } + + /// + /// Process markdown as input objects. + /// + /// Markdown object to process. + private void ProcessMarkdownInfo(MarkdownInfo markdownInfo) + { + if (UseBrowser) + { + var html = markdownInfo.Html; + + if (!string.IsNullOrEmpty(html)) + { + string tmpFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + ".html"); + + try + { + using (var writer = new StreamWriter(new FileStream(tmpFilePath, FileMode.Create, FileAccess.Write, FileShare.Write))) + { + writer.Write(html); + } + } + catch (Exception e) + { + var errorRecord = new ErrorRecord( + e, + "ErrorWritingTempFile", + ErrorCategory.WriteError, + tmpFilePath); + + WriteError(errorRecord); + return; + } + + if (InternalTestHooks.ShowMarkdownOutputBypass) + { + WriteObject(html); + return; + } + + try + { + ProcessStartInfo startInfo = new(); + startInfo.FileName = tmpFilePath; + startInfo.UseShellExecute = true; + Process.Start(startInfo); + } + catch (Exception e) + { + var errorRecord = new ErrorRecord( + e, + "ErrorLaunchingDefaultApplication", + ErrorCategory.InvalidOperation, + targetObject: null); + + WriteError(errorRecord); + return; + } + } + else + { + string errorMessage = StringUtil.Format(ConvertMarkdownStrings.MarkdownInfoInvalid, "Html"); + var errorRecord = new ErrorRecord( + new InvalidDataException(errorMessage), + "HtmlIsNullOrEmpty", + ErrorCategory.InvalidData, + html); + + WriteError(errorRecord); + } + } + else + { + var vt100String = markdownInfo.VT100EncodedString; + + if (!string.IsNullOrEmpty(vt100String)) + { + WriteObject(vt100String); + } + else + { + string errorMessage = StringUtil.Format(ConvertMarkdownStrings.MarkdownInfoInvalid, "VT100EncodedString"); + var errorRecord = new ErrorRecord( + new InvalidDataException(errorMessage), + "VT100EncodedStringIsNullOrEmpty", + ErrorCategory.InvalidData, + vt100String); + + WriteError(errorRecord); + } + } + } + + /// + /// Override EndProcessing. + /// + protected override void EndProcessing() + { + _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 new file mode 100644 index 00000000000..365a669c186 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// + [Cmdlet("Sort", + "Object", + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097038", + DefaultParameterSetName = "Default", + RemotingCapability = RemotingCapability.None)] + public sealed class SortObjectCommand : OrderObjectBase + { + #region Command Line Switches + + /// + /// Gets or sets a value indicating whether a stable sort is required. + /// + /// + /// + /// Items that are duplicates according to the sort algorithm will appear + /// in the same relative order in a stable sort. + /// + [Parameter(ParameterSetName = "Default")] + public SwitchParameter Stable { get; set; } + + /// + /// Gets or sets a value indicating whether the sort order is descending. + /// + [Parameter] + public SwitchParameter Descending + { + get { return DescendingOrder; } + + set { DescendingOrder = value; } + } + + /// + /// Gets or sets a value indicating whether the sort filters out any duplicate objects. + /// + /// + [Parameter] + public SwitchParameter Unique { get; set; } + + #endregion + + /// + /// Gets or sets the number of items to return in a Top N sort. + /// + [Parameter(ParameterSetName = "Top", Mandatory = true)] + [ValidateRange(1, int.MaxValue)] + 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; } + + /// + /// Moves unique entries to the front of the list. + /// + private int MoveUniqueEntriesToFront(List sortedData, OrderByPropertyComparer comparer) + { + // If we have sorted data then we know we have at least one unique item + int uniqueCount = sortedData.Count > 0 ? 1 : 0; + + // Move the first of each unique entry to the front of the list + for (int uniqueItemIndex = 0, nextUniqueItemIndex = 1; uniqueItemIndex < sortedData.Count && uniqueCount != Top; uniqueItemIndex++, nextUniqueItemIndex++) + { + // Identify the index of the next unique item + while (nextUniqueItemIndex < sortedData.Count && comparer.Compare(sortedData[uniqueItemIndex], sortedData[nextUniqueItemIndex]) == 0) + { + nextUniqueItemIndex++; + } + + // If there are no more unique items, break + if (nextUniqueItemIndex == sortedData.Count) + { + break; + } + + // Move the next unique item forward and increment the unique item counter + sortedData[uniqueItemIndex + 1] = sortedData[nextUniqueItemIndex]; + uniqueCount++; + } + + return uniqueCount; + } + + /// + /// Sort unsorted OrderByPropertyEntry data using a full sort. + /// + private int FullSort(List dataToSort, OrderByPropertyComparer comparer) + { + // Track how many items in the list are sorted + int sortedItemCount = dataToSort.Count; + + // Future: It may be worth comparing List.Sort with SortedSet when handling unique + // records in case SortedSet is faster (SortedSet was not an option in earlier + // versions of PowerShell). + dataToSort.Sort(comparer); + + if (Unique) + { + // Move unique entries to the front of the list (this is significantly faster + // than removing them) + sortedItemCount = MoveUniqueEntriesToFront(dataToSort, comparer); + } + + return sortedItemCount; + } + + /// + /// Sort unsorted OrderByPropertyEntry data using an indexed min-/max-heap sort. + /// + private int Heapify(List dataToSort, OrderByPropertyComparer orderByPropertyComparer) + { + // Instantiate the Heapify comparer, which takes index into account for sort stability + var comparer = new IndexedOrderByPropertyComparer(orderByPropertyComparer); + + // Identify how many items will be in the heap and the current number of items + int heapCount = 0; + int heapCapacity = Stable ? int.MaxValue + : Top > 0 ? Top : Bottom; + + // Identify the comparator (the value all comparisons will be made against based on whether we're + // doing a Top N or Bottom N sort) + // Note: All comparison results in the loop below are performed related to the value of the + // comparator. OrderByPropertyComparer.Compare will return -1 to indicate that the lhs is smaller + // if an ascending sort is being executed, or -1 to indicate that the lhs is larger if a descending + // sort is being executed. The comparator will be -1 if we're executing a Top N sort, or 1 if we're + // executing a Bottom N sort. These two pairs of states allow us to perform the proper comparison + // regardless of whether we're executing an ascending or descending Top N or Bottom N sort. This + // allows us to build a min-heap or max-heap for each of these sorts with the exact same logic. + // Min-heap: used for faster processing of a top N descending sort and a bottom N ascending sort + // Max-heap: used for faster processing of a top N ascending sort and a bottom N descending sort + int comparator = Top > 0 ? -1 : 1; + + // For unique sorts, use a sorted set to avoid adding unique items to the heap + SortedSet uniqueSet = Unique ? new SortedSet(orderByPropertyComparer) : null; + + // 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 + 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 + if (heapCount == heapCapacity && comparer.Compare(dataToSort[0], dataToSort[dataIndex]) == comparator) + { + continue; + } + + // If we're doing a unique sort and the entry is not unique, discard the duplicate entry + if (Unique && !uniqueSet.Add(dataToSort[dataIndex + discardedDuplicates])) + { + discardedDuplicates++; + 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) + { + int parentIndex = ((childIndex > (heapCapacity - 1) ? heapCapacity : childIndex) - 1) >> 1; + + // Min-heap: if the child item is larger than its parent, break + // Max-heap: if the child item is smaller than its parent, break + if (comparer.Compare(dataToSort[childIndex], dataToSort[parentIndex]) == comparator) + { + break; + } + + var temp = dataToSort[parentIndex]; + dataToSort[parentIndex] = dataToSort[childIndex]; + dataToSort[childIndex] = temp; + + childIndex = parentIndex; + } + + heapCount++; + + // If the heap size is too large, remove the root and rearrange the heap + if (heapCount > heapCapacity) + { + // Move the last item to the root and reset the heap count (this effectively removes the last item) + dataToSort[0] = dataToSort[dataIndex]; + heapCount = heapCapacity; + + // Bubble the root item down into the correct position + int parentIndex = 0; + int parentItemCount = heapCapacity >> 1; + while (parentIndex < parentItemCount) + { + // Min-heap: use the smaller of the two children in the comparison + // Max-heap: use the larger of the two children in the comparison + int leftChildIndex = (parentIndex << 1) + 1; + int rightChildIndex = leftChildIndex + 1; + childIndex = rightChildIndex == heapCapacity || comparer.Compare(dataToSort[leftChildIndex], dataToSort[rightChildIndex]) != comparator + ? leftChildIndex + : rightChildIndex; + + // Min-heap: if the smallest child is larger than or equal to its parent, break + // Max-heap: if the largest child is smaller than or equal to its parent, break + int childComparisonResult = comparer.Compare(dataToSort[childIndex], dataToSort[parentIndex]); + if (childComparisonResult == 0 || childComparisonResult == comparator) + { + break; + } + + var temp = dataToSort[childIndex]; + dataToSort[childIndex] = dataToSort[parentIndex]; + dataToSort[parentIndex] = temp; + + parentIndex = childIndex; + } + } + } + + dataToSort.Sort(0, heapCount, comparer); + + return heapCount; + } + + /// + /// + protected override void EndProcessing() + { + OrderByProperty orderByProperty = new( + this, InputObjects, Property, !Descending, ConvertedCulture, CaseSensitive); + + var dataToProcess = orderByProperty.OrderMatrix; + var comparer = orderByProperty.Comparer; + if (comparer == null || dataToProcess == null || dataToProcess.Count == 0) + { + return; + } + + // Track the number of items that will be output from the data once it is sorted + int sortedItemCount = dataToProcess.Count; + + // If -Stable, -Top & -Bottom were not used, invoke an in-place full sort + if (!Stable && Top == 0 && Bottom == 0) + { + sortedItemCount = FullSort(dataToProcess, comparer); + } + // Otherwise, use an indexed min-/max-heap to perform an in-place heap sort (heap + // sorts are inheritantly stable, meaning they will preserve the respective order + // of duplicate objects as they are sorted on the heap) + else + { + sortedItemCount = Heapify(dataToProcess, comparer); + } + + // Write out the portion of the processed data that was sorted + for (int index = 0; index < sortedItemCount; index++) + { + WriteObject(dataToProcess[index].inputObject); + } + } + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index fa12ccaa598..839a0b7c051 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; using System.Threading; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -12,86 +12,90 @@ namespace Microsoft.PowerShell.Commands /// /// Suspend shell, script, or runspace activity for the specified period of time. /// - [Cmdlet(VerbsLifecycle.Start, "Sleep", DefaultParameterSetName = "Seconds", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113407")] + [Cmdlet(VerbsLifecycle.Start, "Sleep", DefaultParameterSetName = "Seconds", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097041")] public sealed class StartSleepCommand : PSCmdlet, IDisposable { private bool _disposed = false; #region IDisposable /// - /// Dispose method of IDisposable interface. + /// Dispose method of IDisposable interface. /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { if (_waitHandle != null) { _waitHandle.Dispose(); _waitHandle = null; } + _disposed = true; } } #endregion - - #region parameters /// - /// Allows sleep time to be specified in seconds + /// Allows sleep time to be specified in seconds. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Seconds", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] - [ValidateRangeAttribute(0, int.MaxValue / 1000)] - public int Seconds { get; set; } - + [ValidateRange(0.0, (double)(int.MaxValue / 1000))] + public double Seconds { get; set; } /// - /// Allows sleep time to be specified in milliseconds + /// 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 - //Wait handle which is used by thread to sleep. + // Wait handle which is used by thread to sleep. private ManualResetEvent _waitHandle; - //object used for synchronizes pipeline thread and stop thread - //access to waitHandle - private object _syncObject = new object(); + // object used for synchronizes pipeline thread and stop thread + // access to waitHandle + private readonly object _syncObject = new(); - //this is set to true by stopProcessing + // this is set to true by stopProcessing private bool _stopping = false; /// - /// This method causes calling thread to sleep for - /// specified milliseconds + /// This method causes calling thread to sleep for specified milliseconds. /// 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); } /// - /// + /// ProcessRecord method. /// protected override void ProcessRecord() { @@ -100,39 +104,53 @@ protected override void ProcessRecord() switch (ParameterSetName) { case "Seconds": - sleepTime = Seconds * 1000; + sleepTime = (int)(Seconds * 1000); break; case "Milliseconds": sleepTime = Milliseconds; break; + case "FromTimeSpan": + if (Duration.TotalMilliseconds > int.MaxValue) + { + PSArgumentException argumentException = PSTraceSource.NewArgumentException( + nameof(Duration), + StartSleepStrings.MaximumDurationExceeded, + TimeSpan.FromMilliseconds(int.MaxValue), + Duration); + + ThrowTerminatingError( + new ErrorRecord( + argumentException, + "MaximumDurationExceeded", + ErrorCategory.InvalidArgument, + targetObject: null)); + } + + sleepTime = (int)Math.Floor(Duration.TotalMilliseconds); + break; + default: Dbg.Diagnostics.Assert(false, "Only one of the specified parameter sets should be called."); break; } Sleep(sleepTime); - } // EndProcessing + } /// - /// stopprocessing override + /// StopProcessing override. /// - protected override - void - StopProcessing() + protected override void StopProcessing() { lock (_syncObject) { _stopping = true; - if (_waitHandle != null) - { - _waitHandle.Set(); - } + _waitHandle?.Set(); } } #endregion - } // StartSleepCommand -} // namespace Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs new file mode 100644 index 00000000000..4a0f4831299 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; +using System.Text; + +using Microsoft.PowerShell.Commands.Internal.Format; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Class for Tee-object implementation. + /// + [Cmdlet("Tee", "Object", DefaultParameterSetName = "File", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097034")] + public sealed class TeeObjectCommand : PSCmdlet, IDisposable + { + /// + /// Object to process. + /// + [Parameter(ValueFromPipeline = true)] + public PSObject InputObject + { + get { return _inputObject; } + + set { _inputObject = value; } + } + + private PSObject _inputObject; + + /// + /// FilePath parameter. + /// + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "File")] + [Alias("Path")] + public string FilePath + { + get { return _fileName; } + + set { _fileName = value; } + } + + private string _fileName; + + /// + /// Literal FilePath parameter. + /// + [Parameter(Mandatory = true, ParameterSetName = "LiteralFile")] + [Alias("PSPath", "LP")] + public string LiteralPath + { + get + { + return _fileName; + } + + set + { + _fileName = value; + } + } + + /// + /// Append switch. + /// + [Parameter(ParameterSetName = "File")] + public SwitchParameter Append + { + get { return _append; } + + set { _append = value; } + } + + 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. + /// + [Parameter(Mandatory = true, ParameterSetName = "Variable")] + public string Variable + { + get { return _variable; } + + set { _variable = value; } + } + + private string _variable; + + /// + /// + protected override void BeginProcessing() + { + _commandWrapper = new CommandWrapper(); + if (string.Equals(ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) + { + _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 + { + // variable parameter set + _commandWrapper.Initialize(Context, "set-variable", typeof(SetVariableCommand)); + _commandWrapper.AddNamedParameter("name", _variable); + // Can't use set-var's passthru because it writes the var object to the pipeline, we want just + // the values to be written + } + } + + /// + /// + protected override void ProcessRecord() + { + _commandWrapper.Process(_inputObject); + WriteObject(_inputObject); + } + + /// + /// + protected override void EndProcessing() + { + _commandWrapper.ShutDown(); + } + + /// + /// Release all resources. + /// + public void Dispose() + { + if (!_alreadyDisposed) + { + _alreadyDisposed = true; + if (_commandWrapper != null) + { + _commandWrapper.Dispose(); + _commandWrapper = null; + } + } + } + + #region private + private CommandWrapper _commandWrapper; + private bool _alreadyDisposed; + #endregion private + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs new file mode 100644 index 00000000000..909cbff3c8f --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs @@ -0,0 +1,335 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net.Http; +using System.Security; +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 = JsonStringParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")] + [OutputType(typeof(bool))] + public class TestJsonCommand : PSCmdlet + { + #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, 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. + /// If the parameter is absent the cmdlet only attempts to parse the JSON string. + /// If the parameter present the cmdlet attempts to parse the JSON string and + /// 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, 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 containing schema to validate the JSON string against. + /// This is optional parameter. + /// + [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; } + + /// + /// Gets or sets JSON document options. + /// + [Parameter] + [ValidateNotNullOrEmpty] + [ValidateSet(IgnoreCommentsOption, AllowTrailingCommasOption)] + public string[] Options { get; set; } = Array.Empty(); + + #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 + { + if (Schema != null) + { + try + { + _jschema = JsonSchema.FromText(Schema); + } + catch (JsonException e) + { + Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, e); + WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, Schema)); + } + } + else if (SchemaFile != null) + { + try + { + resolvedpath = Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(SchemaFile); + _jschema = JsonSchema.FromFile(resolvedpath); + } + catch (JsonException e) + { + 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://learn.microsoft.com/dotnet/standard/io/handling-io-errors + e is IOException || + e is UnauthorizedAccessException || + e is NotSupportedException || + e is SecurityException + ) + { + Exception exception = new( + string.Format( + CultureInfo.CurrentUICulture, + TestJsonCmdletStrings.JsonSchemaFileOpenFailure, + resolvedpath), + e); + ThrowTerminatingError(new ErrorRecord(exception, "JsonSchemaFileOpenFailure", ErrorCategory.OpenError, resolvedpath)); + } + catch (Exception 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) + }; + } + + /// + /// Validate a JSON. + /// + protected override void ProcessRecord() + { + 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 + { + + var parsedJson = JsonNode.Parse(jsonToParse, nodeOptions: null, _documentOptions); + + if (_jschema != null) + { + EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical }); + result = evaluationResults.IsValid; + if (!result) + { + 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(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 7cbab7653be..24ed81995d1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TimeExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TimeExpressionCommand.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; using System.Management.Automation.Internal; - #endregion namespace Microsoft.PowerShell.Commands @@ -16,48 +15,44 @@ namespace Microsoft.PowerShell.Commands /// Implements a cmdlet that applies a script block /// to each element of the pipeline. /// - [Cmdlet(VerbsDiagnostic.Measure, "Command", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113348", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsDiagnostic.Measure, "Command", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097029", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(TimeSpan))] public sealed class MeasureCommandCommand : PSCmdlet { #region parameters /// - /// This parameter specifies the current pipeline object + /// 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 + /// 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 #region methods - /// - /// Output the timer + /// Output the timer. /// protected override void EndProcessing() { WriteObject(_stopWatch.Elapsed); - } // EndProcessing - + } /// - /// Execute the script block passing in the current pipeline object as - /// it's only parameter. + /// Execute the script block passing in the current pipeline object as it's only parameter. /// protected override void ProcessRecord() { @@ -68,15 +63,13 @@ protected override void ProcessRecord() useLocalScope: false, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: InputObject, // $_ - input: new object[0], // $input + input: Array.Empty(), // $input scriptThis: AutomationNull.Value, outputPipe: new Pipe { NullPipe = true }, invocationInfo: null); _stopWatch.Stop(); } - #endregion } -} // namespace Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 5578654c42c..6633a66f96f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -1,17 +1,20 @@ -#if !UNIX -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #region Using directives using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +#if UNIX +using System.Globalization; +using System.Management.Automation; +using System.Runtime.InteropServices; +#else using System.Management.Automation; using System.Management.Automation.Internal; +#endif #endregion @@ -19,11 +22,16 @@ namespace Microsoft.PowerShell.Commands { /// Removes the Zone.Identifier stream from a file. [Cmdlet(VerbsSecurity.Unblock, "File", DefaultParameterSetName = "ByPath", SupportsShouldProcess = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217450")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097033")] public sealed class UnblockFileCommand : PSCmdlet { +#if UNIX + private const string MacBlockAttribute = "com.apple.quarantine"; + private const int RemovexattrFollowSymLink = 0; +#endif + /// - /// The path of the file to unblock + /// The path of the file to unblock. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ByPath")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] @@ -33,6 +41,7 @@ public string[] Path { return _paths; } + set { _paths = value; @@ -40,10 +49,10 @@ public string[] Path } /// - /// The literal path of the file to unblock + /// The literal path of the file to unblock. /// [Parameter(Mandatory = true, ParameterSetName = "ByLiteralPath", ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath { @@ -51,6 +60,7 @@ public string[] LiteralPath { return _paths; } + set { _paths = value; @@ -64,10 +74,10 @@ 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)) + if (string.Equals(this.ParameterSetName, "ByLiteralPath", StringComparison.OrdinalIgnoreCase)) { foreach (string path in _paths) { @@ -100,7 +110,7 @@ protected override void ProcessRecord() { if (!WildcardPattern.ContainsWildcardCharacters(path)) { - ErrorRecord errorRecord = new ErrorRecord(e, + ErrorRecord errorRecord = new(e, "FileNotFound", ErrorCategory.ObjectNotFound, path); @@ -109,6 +119,7 @@ protected override void ProcessRecord() } } } +#if !UNIX // Unblock files foreach (string path in pathsToProcess) @@ -119,21 +130,36 @@ protected override void ProcessRecord() { AlternateDataStreamUtilities.DeleteFileStream(path, "Zone.Identifier"); } - catch (Win32Exception accessException) + catch (Exception e) { - // NativeErrorCode=2 - File not found. - // If the block stream not found the 'path' was not blocked and we successfully return. - if (accessException.NativeErrorCode != 2) - { - WriteError(new ErrorRecord(accessException, "RemoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); - } - else - { - WriteVerbose(StringUtil.Format(UtilityCommonStrings.NoZoneIdentifierFileStream, path)); - } + WriteError(new ErrorRecord(exception: e, errorId: "RemoveItemUnableToAccessFile", ErrorCategory.ResourceUnavailable, targetObject: path)); } } } +#else + if (Platform.IsLinux) + { + string errorMessage = UnblockFileStrings.LinuxNotSupported; + Exception e = new PlatformNotSupportedException(errorMessage); + ThrowTerminatingError(new ErrorRecord(exception: e, errorId: "LinuxNotSupported", ErrorCategory.NotImplemented, targetObject: null)); + return; + } + + foreach (string path in pathsToProcess) + { + if (IsBlocked(path)) + { + UInt32 result = RemoveXattr(path, MacBlockAttribute, RemovexattrFollowSymLink); + if (result != 0) + { + string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); + Exception e = new InvalidOperationException(errorMessage); + WriteError(new ErrorRecord(exception: e, errorId: "UnblockError", ErrorCategory.InvalidResult, targetObject: path)); + } + } + } + +#endif } /// @@ -154,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, @@ -163,12 +189,42 @@ private bool IsValidFileForUnblocking(string resolvedpath) } else { - isValidUnblockableFile = true; ; + isValidUnblockableFile = true; } } return isValidUnblockableFile; } + +#if UNIX + private static bool IsBlocked(string path) + { + const uint valueSize = 1024; + IntPtr value = Marshal.AllocHGlobal((int)valueSize); + try + { + var resultSize = GetXattr(path, MacBlockAttribute, value, valueSize, 0, RemovexattrFollowSymLink); + return resultSize != -1; + } + finally + { + Marshal.FreeHGlobal(value); + } + } + + // Ansi means UTF8 on Unix + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/RemoveXattr.2.html + [DllImport("libc", SetLastError = true, EntryPoint = "removexattr", CharSet = CharSet.Ansi)] + private static extern UInt32 RemoveXattr(string path, string name, int options); + + [DllImport("libc", EntryPoint = "getxattr", CharSet = CharSet.Ansi)] + private static extern long GetXattr( + [MarshalAs(UnmanagedType.LPStr)] string path, + [MarshalAs(UnmanagedType.LPStr)] string name, + IntPtr value, + ulong size, + uint position, + int options); +#endif } } -#endif diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs index 8ab72b1cd40..cf65a1f73b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -10,13 +9,13 @@ namespace Microsoft.PowerShell.Commands /// /// Unregisters from an event on an object. /// - [Cmdlet(VerbsLifecycle.Unregister, "Event", SupportsShouldProcess = true, DefaultParameterSetName = "BySource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135269")] + [Cmdlet(VerbsLifecycle.Unregister, "Event", SupportsShouldProcess = true, DefaultParameterSetName = "BySource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097037")] public class UnregisterEventCommand : PSCmdlet { #region parameters /// - /// An identifier for this event subscription + /// An identifier for this event subscription. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = "BySource")] public string SourceIdentifier @@ -25,6 +24,7 @@ public string SourceIdentifier { return _sourceIdentifier; } + set { _sourceIdentifier = value; @@ -35,19 +35,19 @@ public string SourceIdentifier } } } + private string _sourceIdentifier = null; /// - /// An identifier for this event subscription + /// An identifier for this event subscription. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = "ById")] public int SubscriptionId { get; set; } = -1; /// - /// Flag that determines if we should include subscriptions used to support - /// other subscriptions + /// Flag that determines if we should include subscriptions used to support other subscriptions. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get; set; } #endregion parameters @@ -56,7 +56,7 @@ public string SourceIdentifier private bool _foundMatch = false; /// - /// Unsubscribe from the event + /// Unsubscribe from the event. /// protected override void ProcessRecord() { @@ -80,7 +80,7 @@ protected override void ProcessRecord() _foundMatch = true; if (ShouldProcess( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.EventSubscription, subscriber.SourceIdentifier), @@ -97,9 +97,9 @@ protected override void ProcessRecord() (!WildcardPattern.ContainsWildcardCharacters(_sourceIdentifier)) && (!_foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.EventSubscriptionNotFound, _sourceIdentifier)), "INVALID_SOURCE_IDENTIFIER", @@ -111,9 +111,9 @@ protected override void ProcessRecord() else if ((SubscriptionId >= 0) && (!_foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingStrings.EventSubscriptionNotFound, SubscriptionId)), "INVALID_SUBSCRIPTION_IDENTIFIER", @@ -124,4 +124,4 @@ protected override void ProcessRecord() } } } -} \ No newline at end of file +} 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 36a90b93c83..4a3198d2911 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs @@ -1,43 +1,41 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; using System.Collections.ObjectModel; - +using System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// This is the base class for update-typedata and update-formatdata + /// This is the base class for update-typedata and update-formatdata. /// public class UpdateData : PSCmdlet { /// - /// File parameter set name + /// File parameter set name. /// protected const string FileParameterSet = "FileSet"; /// - /// Files to append to the existing set + /// Files to append to the existing set. /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = FileParameterSet)] [Alias("PSPath", "Path")] [ValidateNotNull] - public string[] AppendPath { set; get; } = Utils.EmptyArray(); + public string[] AppendPath { get; set; } = Array.Empty(); /// - /// Files to prepend to the existing set + /// Files to prepend to the existing set. /// [Parameter(ParameterSetName = FileParameterSet)] [ValidateNotNull] - public string[] PrependPath { set; get; } = Utils.EmptyArray(); + 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, @@ -47,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, @@ -56,7 +54,6 @@ private static void ReportWrongProviderType(string providerId, string errorId, P } /// - /// /// /// /// @@ -64,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; @@ -78,11 +75,13 @@ internal static Collection Glob(string[] files, string errorId, PSCmdlet cmdlet.WriteError(new ErrorRecord(e, errorId, ErrorCategory.InvalidOperation, file)); continue; } + if (!provider.NameEquals(cmdlet.Context.ProviderNames.FileSystem)) { ReportWrongProviderType(provider.FullName, errorId, cmdlet); continue; } + foreach (string providerPath in providerPaths) { if (!providerPath.EndsWith(".ps1xml", StringComparison.OrdinalIgnoreCase)) @@ -90,6 +89,7 @@ internal static Collection Glob(string[] files, string errorId, PSCmdlet ReportWrongExtension(providerPath, "WrongExtension", cmdlet); continue; } + retValue.Add(providerPath); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs new file mode 100644 index 00000000000..99507d0461b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-List.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// This cmdlet updates the property of incoming objects and passes them to the + /// pipeline. This cmdlet also returns a .NET object with properties that + /// defines the update action on a list. + /// This cmdlet is most helpful when the cmdlet author wants the user to do + /// update action on object list that are not directly exposed through + /// cmdlet parameter. One wants to update a property value which is a list + /// (multi-valued parameter for a cmdlet), without exposing the list. + /// + [Cmdlet(VerbsData.Update, "List", DefaultParameterSetName = "AddRemoveSet", + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109383", RemotingCapability = RemotingCapability.None)] + public class UpdateListCommand : PSCmdlet + { + /// + /// The following is the definition of the input parameter "Add". + /// Objects to add to the list. + /// + [Parameter(ParameterSetName = "AddRemoveSet")] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] + public object[] Add { get; set; } + + /// + /// The following is the definition of the input parameter "Remove". + /// Objects to be removed from the list. + /// + [Parameter(ParameterSetName = "AddRemoveSet")] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] + public object[] Remove { get; set; } + + /// + /// The following is the definition of the input parameter "Replace". + /// Objects in this list replace the objects in the target list. + /// + [Parameter(Mandatory = true, ParameterSetName = "ReplaceSet")] + [ValidateNotNullOrEmpty] + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] + public object[] Replace { get; set; } + + /// + /// The following is the definition of the input parameter "InputObject". + /// List of InputObjects where the updates needs to applied to the specific property. + /// + // [Parameter(ValueFromPipeline = true, ParameterSetName = "AddRemoveSet")] + // [Parameter(ValueFromPipeline = true, ParameterSetName = "ReplaceSet")] + [Parameter(ValueFromPipeline = true)] + [ValidateNotNullOrEmpty] + public PSObject InputObject { get; set; } + + /// + /// The following is the definition of the input parameter "Property". + /// Defines which property of the input object should be updated with Add and Remove actions. + /// + // [Parameter(Position = 0, ParameterSetName = "AddRemoveSet")] + // [Parameter(Position = 0, ParameterSetName = "ReplaceSet")] + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] + public string Property { get; set; } + + private PSListModifier _listModifier; + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + if (Property != null) + { + if (InputObject == null) + { + WriteError(NewError("MissingInputObjectParameter", "MissingInputObjectParameter", null)); + } + else + { + _listModifier ??= CreatePSListModifier(); + + PSMemberInfo memberInfo = InputObject.Members[Property]; + if (memberInfo != null) + { + try + { + _listModifier.ApplyTo(memberInfo.Value); + WriteObject(InputObject); + } + catch (PSInvalidOperationException e) + { + WriteError(new ErrorRecord(e, "ApplyFailed", ErrorCategory.InvalidOperation, null)); + } + } + else + { + WriteError(NewError("MemberDoesntExist", "MemberDoesntExist", InputObject, Property)); + } + } + } + } + + /// + /// EndProcessing method. + /// + protected override void EndProcessing() + { + if (Property == null) + { + if (InputObject != null) + { + ThrowTerminatingError(NewError("MissingPropertyParameter", "MissingPropertyParameter", null)); + } + else + { + WriteObject(CreateHashtable()); + } + } + } + + private Hashtable CreateHashtable() + { + Hashtable hash = new(2); + if (Add != null) + { + hash.Add("Add", Add); + } + + if (Remove != null) + { + hash.Add("Remove", Remove); + } + + if (Replace != null) + { + hash.Add("Replace", Replace); + } + + return hash; + } + + private PSListModifier CreatePSListModifier() + { + PSListModifier listModifier = new(); + if (Add != null) + { + foreach (object obj in Add) + { + listModifier.Add.Add(obj); + } + } + + if (Remove != null) + { + foreach (object obj in Remove) + { + listModifier.Remove.Add(obj); + } + } + + if (Replace != null) + { + foreach (object obj in Replace) + { + listModifier.Replace.Add(obj); + } + } + + return listModifier; + } + + private ErrorRecord NewError(string errorId, string resourceId, object targetObject, params object[] args) + { + ErrorDetails details = new(this.GetType().Assembly, "UpdateListStrings", resourceId, args); + ErrorRecord errorRecord = new( + new InvalidOperationException(details.Message), + errorId, + ErrorCategory.InvalidOperation, + targetObject); + return errorRecord; + } + } +} 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 b81a6501ded..b73d8570040 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs @@ -1,25 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; -using System.Reflection; -using System.Management.Automation; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; +using System.Reflection; +using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// /// This class implements update-typeData command. /// - [Cmdlet(VerbsData.Update, "TypeData", SupportsShouldProcess = true, DefaultParameterSetName = FileParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113421")] + [Cmdlet(VerbsData.Update, "TypeData", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + DefaultParameterSetName = FileParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097131")] public class UpdateTypeDataCommand : UpdateData { #region dynamic type set @@ -28,7 +28,8 @@ 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) { return !System.Object.ReferenceEquals(obj, s_notSpecified); @@ -37,7 +38,7 @@ private static bool HasBeenSpecified(object obj) private PSMemberTypes _memberType; private bool _isMemberTypeSet = false; /// - /// The member type of to be added + /// The member type of to be added. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] @@ -49,24 +50,29 @@ 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; /// - /// The name of the new member + /// The name of the new member. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string MemberName { - set { _memberName = value; } get { return _memberName; } + + set { _memberName = value; } } private object _value1 = s_notSpecified; @@ -77,8 +83,9 @@ public string MemberName [Parameter(ParameterSetName = DynamicTypeSet)] public object Value { - set { _value1 = value; } get { return _value1; } + + set { _value1 = value; } } private object _value2; @@ -90,142 +97,147 @@ public object Value [ValidateNotNull] public object SecondValue { - set { _value2 = value; } get { return _value2; } + + set { _value2 = value; } } private Type _typeConverter; /// - /// The type converter to be added + /// The type converter to be added. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNull] public Type TypeConverter { - set { _typeConverter = value; } get { return _typeConverter; } + + set { _typeConverter = value; } } private Type _typeAdapter; /// - /// The type adapter to be added + /// The type adapter to be added. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNull] public Type TypeAdapter { - set { _typeAdapter = value; } get { return _typeAdapter; } + + set { _typeAdapter = value; } } /// - /// SerializationMethod + /// SerializationMethod. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string SerializationMethod { - set { _serializationMethod = value; } get { return _serializationMethod; } - } + set { _serializationMethod = value; } + } /// - /// TargetTypeForDeserialization + /// TargetTypeForDeserialization. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNull] public Type TargetTypeForDeserialization { - set { _targetTypeForDeserialization = value; } get { return _targetTypeForDeserialization; } - } + set { _targetTypeForDeserialization = value; } + } /// - /// SerializationDepth + /// SerializationDepth. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNull] [ValidateRange(0, int.MaxValue)] public int SerializationDepth { - set { _serializationDepth = value; } get { return _serializationDepth; } - } + set { _serializationDepth = value; } + } /// - /// DefaultDisplayProperty + /// DefaultDisplayProperty. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string DefaultDisplayProperty { - set { _defaultDisplayProperty = value; } get { return _defaultDisplayProperty; } - } + set { _defaultDisplayProperty = value; } + } /// - /// InheritPropertySerializationSet + /// InheritPropertySerializationSet. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNull] - public Nullable InheritPropertySerializationSet + public bool? InheritPropertySerializationSet { - set { _inheritPropertySerializationSet = value; } get { return _inheritPropertySerializationSet; } - } + set { _inheritPropertySerializationSet = value; } + } /// - /// StringSerializationSource + /// StringSerializationSource. /// [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string StringSerializationSource { - set { _stringSerializationSource = value; } get { return _stringSerializationSource; } - } + set { _stringSerializationSource = value; } + } /// - /// DefaultDisplayPropertySet + /// DefaultDisplayPropertySet. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string[] DefaultDisplayPropertySet { - set { _defaultDisplayPropertySet = value; } get { return _defaultDisplayPropertySet; } + + set { _defaultDisplayPropertySet = value; } } /// - /// DefaultKeyPropertySet + /// DefaultKeyPropertySet. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string[] DefaultKeyPropertySet { - set { _defaultKeyPropertySet = value; } get { return _defaultKeyPropertySet; } - } + set { _defaultKeyPropertySet = value; } + } /// - /// PropertySerializationSet + /// PropertySerializationSet. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ParameterSetName = DynamicTypeSet)] [ValidateNotNullOrEmpty] public string[] PropertySerializationSet { - set { _propertySerializationSet = value; } get { return _propertySerializationSet; } + + set { _propertySerializationSet = value; } } // These members are represented as NoteProperty in types.ps1xml @@ -233,7 +245,7 @@ public string[] PropertySerializationSet private Type _targetTypeForDeserialization; private int _serializationDepth = int.MinValue; private string _defaultDisplayProperty; - private Nullable _inheritPropertySerializationSet; + private bool? _inheritPropertySerializationSet; // These members are represented as AliasProperty in types.ps1xml private string _stringSerializationSource; @@ -245,27 +257,29 @@ public string[] PropertySerializationSet private string _typeName; /// - /// The type name we want to update on + /// 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; /// - /// True if we should overwrite a possibly existing member + /// True if we should overwrite a possibly existing member. /// [Parameter(ParameterSetName = DynamicTypeSet)] [Parameter(ParameterSetName = TypeDataSet)] public SwitchParameter Force { - set { _force = value; } get { return _force; } + + set { _force = value; } } #endregion dynamic type set @@ -274,20 +288,21 @@ public SwitchParameter Force private TypeData[] _typeData; /// - /// The TypeData instances + /// The TypeData instances. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [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 /// - /// This method verify if the Type Table is shared and cannot be updated + /// This method verify if the Type Table is shared and cannot be updated. /// protected override void BeginProcessing() { @@ -299,7 +314,7 @@ protected override void BeginProcessing() } /// - /// This method implements the ProcessRecord method for update-typeData command + /// This method implements the ProcessRecord method for update-typeData command. /// protected override void ProcessRecord() { @@ -318,7 +333,7 @@ protected override void ProcessRecord() } /// - /// This method implements the EndProcessing method for update-typeData command + /// This method implements the EndProcessing method for update-typeData command. /// protected override void EndProcessing() { @@ -339,6 +354,7 @@ private void ProcessStrongTypeData() { continue; } + TypeData type = item.Copy(); // Set property IsOverride to be true if -Force parameter is specified @@ -355,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)); } } @@ -389,15 +405,16 @@ private void ProcessStrongTypeData() #region dynamic type processing /// - /// Process the dynamic type update + /// Process the dynamic type update. /// private void ProcessDynamicType() { - if (String.IsNullOrWhiteSpace(_typeName)) + if (string.IsNullOrWhiteSpace(_typeName)) { ThrowTerminatingError(NewError("TargetTypeNameEmpty", UpdateDataStrings.TargetTypeNameEmpty, _typeName)); } - TypeData type = new TypeData(_typeName) { IsOverride = _force }; + + TypeData type = new(_typeName) { IsOverride = _force }; GetMembers(type.Members); @@ -405,6 +422,7 @@ private void ProcessDynamicType() { type.TypeConverter = _typeConverter; } + if (_typeAdapter != null) { type.TypeAdapter = _typeAdapter; @@ -414,39 +432,47 @@ private void ProcessDynamicType() { type.SerializationMethod = _serializationMethod; } + if (_targetTypeForDeserialization != null) { type.TargetTypeForDeserialization = _targetTypeForDeserialization; } + if (_serializationDepth != int.MinValue) { type.SerializationDepth = (uint)_serializationDepth; } + if (_defaultDisplayProperty != null) { type.DefaultDisplayProperty = _defaultDisplayProperty; } + if (_inheritPropertySerializationSet != null) { type.InheritPropertySerializationSet = _inheritPropertySerializationSet.Value; } + if (_stringSerializationSource != null) { type.StringSerializationSource = _stringSerializationSource; } + 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; } @@ -469,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)); } } @@ -498,7 +524,7 @@ private void ProcessDynamicType() } /// - /// Get the members for the TypeData + /// Get the members for the TypeData. /// /// private void GetMembers(Dictionary members) @@ -511,6 +537,7 @@ private void GetMembers(Dictionary members) { ThrowTerminatingError(NewError("MemberTypeIsMissing", UpdateDataStrings.MemberTypeIsMissing, null)); } + return; } @@ -546,14 +573,14 @@ 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); } private void EnsureMemberNameHasBeenSpecified() { - if (String.IsNullOrEmpty(_memberName)) + if (string.IsNullOrEmpty(_memberName)) { ThrowTerminatingError(NewError("MemberNameShouldBeSpecified", UpdateDataStrings.ShouldBeSpecified, null, "MemberName", _memberType)); } @@ -571,10 +598,11 @@ private void EnsureValue1NotNullOrEmpty() { if (_value1 is string) { - if (String.IsNullOrEmpty((string)_value1)) + if (string.IsNullOrEmpty((string)_value1)) { ThrowTerminatingError(NewError("ValueShouldBeSpecified", UpdateDataStrings.ShouldNotBeNull, null, "Value", _memberType)); } + return; } @@ -601,10 +629,10 @@ private void EnsureValue1AndValue2AreNotBothNull() } /// - /// Check if the TypeData instance contains no members + /// Check if the TypeData instance contains no members. /// /// - /// false if empty, true if not + /// False if empty, true if not. private bool EnsureTypeDataIsNotEmpty(TypeData typeData) { if (typeData.Members.Count == 0 && typeData.StandardMembers.Count == 0 @@ -616,6 +644,7 @@ private bool EnsureTypeDataIsNotEmpty(TypeData typeData) this.WriteError(NewError("TypeDataEmpty", UpdateDataStrings.TypeDataEmpty, null, typeData.TypeName)); return false; } + return true; } @@ -641,6 +670,7 @@ private AliasPropertyData GetAliasProperty() alias = new AliasPropertyData(_memberName, referencedName, type); return alias; } + alias = new AliasPropertyData(_memberName, referencedName); return alias; } @@ -663,7 +693,7 @@ private ScriptPropertyData GetScriptProperty() value2ScriptBlock = GetParameterType(_value2); } - ScriptPropertyData scriptProperty = new ScriptPropertyData(_memberName, value1ScriptBlock, value2ScriptBlock); + ScriptPropertyData scriptProperty = new(_memberName, value1ScriptBlock, value2ScriptBlock); return scriptProperty; } @@ -685,7 +715,7 @@ private CodePropertyData GetCodeProperty() value2CodeReference = GetParameterType(_value2); } - CodePropertyData codeProperty = new CodePropertyData(_memberName, value1CodeReference, value2CodeReference); + CodePropertyData codeProperty = new(_memberName, value1CodeReference, value2CodeReference); return codeProperty; } @@ -696,7 +726,7 @@ private ScriptMethodData GetScriptMethod() EnsureValue2HasNotBeenSpecified(); ScriptBlock method = GetParameterType(_value1); - ScriptMethodData scriptMethod = new ScriptMethodData(_memberName, method); + ScriptMethodData scriptMethod = new(_memberName, method); return scriptMethod; } @@ -707,22 +737,22 @@ private CodeMethodData GetCodeMethod() EnsureValue2HasNotBeenSpecified(); MethodInfo codeReference = GetParameterType(_value1); - CodeMethodData codeMethod = new CodeMethodData(_memberName, codeReference); + CodeMethodData codeMethod = new(_memberName, codeReference); return codeMethod; } /// - /// Generate error record + /// Generate error record. /// /// /// /// /// /// - 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, @@ -762,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])); } } @@ -776,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); } } @@ -795,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)); } } @@ -819,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 { @@ -835,13 +861,14 @@ 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)); } + errors = new ConcurrentBag(); } } @@ -858,11 +885,12 @@ private void ProcessTypeFiles() /// /// This class implements update-typeData command. /// - [Cmdlet(VerbsData.Update, "FormatData", SupportsShouldProcess = true, DefaultParameterSetName = FileParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113420")] + [Cmdlet(VerbsData.Update, "FormatData", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + DefaultParameterSetName = FileParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097135")] public class UpdateFormatDataCommand : UpdateData { /// - /// This method verify if the Format database manager is shared and cannot be updated + /// This method verify if the Format database manager is shared and cannot be updated. /// protected override void BeginProcessing() { @@ -874,7 +902,7 @@ protected override void BeginProcessing() } /// - /// This method implements the ProcessRecord method for update-FormatData command + /// This method implements the ProcessRecord method for update-FormatData command. /// protected override void ProcessRecord() { @@ -940,14 +968,14 @@ protected override void ProcessRecord() if (ShouldProcess(formattedTarget, action)) { - if (!fullFileNameHash.Contains(appendPathTotalItem)) + if (fullFileNameHash.Add(appendPathTotalItem)) { - fullFileNameHash.Add(appendPathTotalItem); newFormats.Add(new SessionStateFormatEntry(appendPathTotalItem)); } } } + var originalFormats = Context.InitialSessionState.Formats; try { // Always rebuild the format information @@ -985,14 +1013,17 @@ protected override void ProcessRecord() if (entries.Count > 0) { Context.FormatDBManager.UpdateDataBase(entries, this.Context.AuthorizationManager, this.Context.EngineHostInterface, false); - FormatAndTypeDataHelper.ThrowExceptionOnError( "ErrorsUpdatingFormats", + FormatAndTypeDataHelper.ThrowExceptionOnError("ErrorsUpdatingFormats", null, entries, - RunspaceConfigurationCategory.Formats); - } + FormatAndTypeDataHelper.Category.Formats); + } } catch (RuntimeException e) { + // revert Formats if there is a failure + Context.InitialSessionState.Formats.Clear(); + Context.InitialSessionState.Formats.Add(originalFormats); this.WriteError(new ErrorRecord(e, "FormatXmlUpdateException", ErrorCategory.InvalidOperation, null)); } } @@ -1004,10 +1035,10 @@ protected override void ProcessRecord() } /// - /// Remove-TypeData cmdlet + /// Remove-TypeData cmdlet. /// [Cmdlet(VerbsCommon.Remove, "TypeData", SupportsShouldProcess = true, DefaultParameterSetName = RemoveTypeDataSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217038")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096622")] public class RemoveTypeDataCommand : PSCmdlet { private const string RemoveTypeSet = "RemoveTypeSet"; @@ -1015,21 +1046,24 @@ public class RemoveTypeDataCommand : PSCmdlet private const string RemoveTypeDataSet = "RemoveTypeDataSet"; private string _typeName; + /// - /// The target type to remove + /// The target type to remove. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = RemoveTypeSet)] - [ArgumentToTypeNameTransformationAttribute()] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { get { return _typeName; } + set { _typeName = value; } } private string[] _typeFiles; + /// - /// The type xml file to remove from the cache + /// The type xml file to remove from the cache. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(Mandatory = true, ParameterSetName = RemoveFileSet)] @@ -1037,17 +1071,20 @@ public string TypeName public string[] Path { get { return _typeFiles; } + set { _typeFiles = value; } } private TypeData _typeData; + /// - /// The TypeData to remove + /// The TypeData to remove. /// [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = RemoveTypeDataSet)] public TypeData TypeData { get { return _typeData; } + set { _typeData = value; } } @@ -1065,7 +1102,7 @@ private static void ConstructFileToIndexMap(string fileName, int index, Dictiona } /// - /// This method implements the ProcessRecord method for Remove-TypeData command + /// This method implements the ProcessRecord method for Remove-TypeData command. /// protected override void ProcessRecord() { @@ -1076,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 @@ -1119,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 @@ -1161,15 +1201,16 @@ protected override void ProcessRecord() } else { - if (String.IsNullOrWhiteSpace(_typeName)) + if (string.IsNullOrWhiteSpace(_typeName)) { ThrowTerminatingError(NewError("TargetTypeNameEmpty", UpdateDataStrings.TargetTypeNameEmpty, _typeName)); } + typeNameToRemove = _typeName; } - Dbg.Assert(!String.IsNullOrEmpty(typeNameToRemove), "TypeNameToRemove should be not null and not empty at this point"); - TypeData type = new TypeData(typeNameToRemove); + Dbg.Assert(!string.IsNullOrEmpty(typeNameToRemove), "TypeNameToRemove should be not null and not empty at this point"); + TypeData type = new(typeNameToRemove); string removeTypeFormattedTarget = string.Format(CultureInfo.InvariantCulture, removeTypeTarget, typeNameToRemove); if (ShouldProcess(removeTypeFormattedTarget, removeTypeAction)) @@ -1179,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)); } } @@ -1208,17 +1249,17 @@ protected override void ProcessRecord() } /// - /// This method implements the EndProcessing method for Remove-TypeData command + /// This method implements the EndProcessing method for Remove-TypeData command. /// 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, @@ -1228,17 +1269,16 @@ private ErrorRecord NewError(string errorId, string template, object targetObjec } /// - /// Get-TypeData cmdlet + /// Get-TypeData cmdlet. /// - [Cmdlet(VerbsCommon.Get, "TypeData", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217033")] + [Cmdlet(VerbsCommon.Get, "TypeData", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097018")] [OutputType(typeof(System.Management.Automation.PSObject))] public class GetTypeDataCommand : PSCmdlet { private WildcardPattern[] _filter; /// - /// Get Formatting information only for the specified - /// typename + /// Get Formatting information only for the specified typename. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [ValidateNotNullOrEmpty] @@ -1257,7 +1297,7 @@ private void ValidateTypeName() var exception = new InvalidOperationException(UpdateDataStrings.TargetTypeNameEmpty); foreach (string typeName in TypeName) { - if (String.IsNullOrWhiteSpace(typeName)) + if (string.IsNullOrWhiteSpace(typeName)) { WriteError( new ErrorRecord( @@ -1275,6 +1315,7 @@ private void ValidateTypeName() { typeNameInUse = type.FullName; } + typeNames.Add(typeNameInUse); } @@ -1287,15 +1328,13 @@ private void ValidateTypeName() } /// - /// Takes out the content from the database and writes them - /// out + /// Takes out the content from the database and writes it out. /// protected override void ProcessRecord() { ValidateTypeName(); Dictionary alltypes = Context.TypeTable.GetAllTypeData(); - Collection typedefs = new Collection(); foreach (string type in alltypes.Keys) { @@ -1303,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); - } } } @@ -1321,7 +1354,7 @@ protected override void ProcessRecord() /// To make it easier to specify a TypeName, we add an ArgumentTransformationAttribute here. /// * string: return the string /// * Type: return the Type.ToString() - /// * instance: return instance.GetType().ToString() + /// * instance: return instance.GetType().ToString() . /// internal sealed class ArgumentToTypeNameTransformationAttribute : ArgumentTransformationAttribute { @@ -1356,4 +1389,3 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input } } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs index c5988b3a450..1e3cc038750 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -1,21 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Text; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Management.Automation; +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 { /// - /// Don't use! The API is obsolete! + /// Don't use! The API is obsolete!. /// [Obsolete("This class is included in this SDK for completeness only. The members of this class cannot be used directly, nor should this class be used to derive other classes.", true)] public enum TextEncodingType @@ -45,6 +42,11 @@ public enum TextEncodingType /// BigEndianUnicode, + /// + /// Big Endian UTF32 encoding. + /// + BigEndianUTF32, + /// /// UTF8 encoding. /// @@ -62,8 +64,9 @@ public enum TextEncodingType } /// - /// Utility class to contain resources for the Microsoft.PowerShell.Utility module + /// Utility class to contain resources for the Microsoft.PowerShell.Utility module. /// + [Obsolete("This class is obsolete", true)] public static class UtilityResources { /// @@ -75,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. /// @@ -96,56 +94,199 @@ public static class UtilityResources public class ByteCollection { /// - /// ByteCollection constructor. + /// Initializes a new instance of the class. + /// + /// The Offset address to be used while displaying the bytes in the collection. + /// Underlying bytes stored in the collection. + /// Indicates the path of the file whose contents are wrapped in the ByteCollection. + [Obsolete("The constructor is deprecated.", true)] + public ByteCollection(uint offset, byte[] value, string path) + : this((ulong)offset, value, path) + { + } + + /// + /// Initializes a new instance of the class. /// /// The Offset address to be used while displaying the bytes in the collection. /// Underlying bytes stored in the collection. /// Indicates the path of the file whose contents are wrapped in the ByteCollection. - public ByteCollection(UInt32 offset, Byte[] value, string path) + public ByteCollection(ulong offset, byte[] value, string path) + { + if (value == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(value)); + } + + Offset64 = offset; + Bytes = value; + Path = path; + Label = path; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Offset address to be used while displaying the bytes in the collection. + /// Underlying bytes stored in the collection. + [Obsolete("The constructor is deprecated.", true)] + public ByteCollection(uint offset, byte[] value) + : this((ulong)offset, value) { - this.Offset = offset; - _initialOffSet = offset; - this.Bytes = value; - this.Path = path; } /// - /// ByteCollection constructor. + /// Initializes a new instance of the class. + /// + /// The Offset address to be used while displaying the bytes in the collection. + /// Underlying bytes stored in the collection. + public ByteCollection(ulong offset, byte[] value) + { + if (value == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(value)); + } + + Offset64 = offset; + Bytes = value; + } + + /// + /// Initializes a new instance of the class. /// /// The Offset address to be used while displaying the bytes in the collection. + /// + /// The label for the byte group. This may be a file path or a formatted identifying string for the group. + /// /// Underlying bytes stored in the collection. - public ByteCollection(UInt32 offset, Byte[] value) + public ByteCollection(ulong offset, string label, byte[] value) + : this(offset, value) { - this.Offset = offset; - _initialOffSet = offset; - this.Bytes = value; + Label = label; } /// - /// ByteCollection constructor. + /// Initializes a new instance of the class. /// /// Underlying bytes stored in the collection. - public ByteCollection(Byte[] value) + public ByteCollection(byte[] value) { - this.Bytes = value; + if (value == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(value)); + } + + Bytes = 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 uint Offset + { + get + { + return (uint)Offset64; + } + + private set + { + Offset64 = value; + } } /// - /// The Offset address to be used while displaying the bytes in the collection. + /// Gets the Offset address to be used while displaying the bytes in the collection. /// - public UInt32 Offset { get; private set; } - private UInt32 _initialOffSet = 0; + public ulong Offset64 { get; private set; } /// - /// Underlying bytes stored in the collection. + /// Gets underlying bytes stored in the collection. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Byte[] Bytes { get; private set; } + public byte[] Bytes { get; } /// - /// Indicates the path of the file whose contents are wrapped in the ByteCollection. + /// 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 => string.Create(CultureInfo.CurrentCulture, $"{Offset64:X16}"); + + /// + /// Gets the type of the input objects used to create the . + /// + public string Label { get; } + + private const int BytesPerLine = 16; + + private string _hexBytes = string.Empty; + + /// + /// Gets a space-delimited string of the in this + /// in hexadecimal format. + /// + public string HexBytes + { + get + { + if (_hexBytes == string.Empty) + { + StringBuilder line = new(BytesPerLine * 3); + + foreach (var currentByte in Bytes) + { + line.AppendFormat(CultureInfo.CurrentCulture, "{0:X2} ", currentByte); + } + + _hexBytes = line.ToString().Trim(); + } + + return _hexBytes; + } + } + + private string _ascii = string.Empty; + + /// + /// Gets the ASCII string representation of the in this . + /// + /// + public string Ascii + { + get + { + if (_ascii == string.Empty) + { + StringBuilder ascii = new(BytesPerLine); + + foreach (var currentByte in Bytes) + { + var currentChar = (char)currentByte; + if (currentChar == 0x0) + { + ascii.Append(' '); + } + else if (char.IsControl(currentChar)) + { + ascii.Append((char)0xFFFD); + } + else + { + ascii.Append(currentChar); + } + } + + _ascii = ascii.ToString(); + } + + return _ascii; + } + } /// /// Displays the hexadecimal format of the bytes stored in the collection. @@ -153,71 +294,83 @@ public ByteCollection(Byte[] value) /// public override string ToString() { - StringBuilder result = new StringBuilder(); - StringBuilder nextLine = new StringBuilder(); - StringBuilder asciiEnd = new StringBuilder(); + const int BytesPerLine = 16; + const string LineFormat = "{0:X16} "; + + // '16 + 3' comes from format "{0:X16} ". + // '16' comes from '[Uint64]::MaxValue.ToString("X").Length'. + StringBuilder nextLine = new(16 + 3 + (BytesPerLine * 3)); + StringBuilder asciiEnd = new(BytesPerLine); + + // '+1' comes from 'result.Append(nextLine.ToString() + " " + asciiEnd.ToString());' below. + StringBuilder result = new(nextLine.Capacity + asciiEnd.Capacity + 1); if (Bytes.Length > 0) { - UInt32 charCounter = 0; + long charCounter = 0; + + var currentOffset = Offset64; - // ToString() in invoked thrice by the F&O for the same content. - // Hence making sure that Offset is not getting incremented thrice for the same bytes being displayed. - Offset = _initialOffSet; + nextLine.AppendFormat(CultureInfo.InvariantCulture, LineFormat, currentOffset); - nextLine.AppendFormat("{0:X2} ", CultureInfo.InvariantCulture.TextInfo.ToUpper(Convert.ToString(Offset, 16)).PadLeft(8, '0')); - foreach (Byte currentByte in Bytes) + foreach (byte currentByte in Bytes) { // Display each byte, in 2-digit hexadecimal, and add that to the left-hand side. nextLine.AppendFormat("{0:X2} ", currentByte); // If the character is printable, add its ascii representation to // the right-hand side. Otherwise, add a dot to the right hand side. - if ((currentByte >= 0x20) && (currentByte <= 0xFE)) + var currentChar = (char)currentByte; + if (currentChar == 0x0) { - asciiEnd.Append((char)currentByte); + asciiEnd.Append(' '); + } + else if (char.IsControl(currentChar)) + { + asciiEnd.Append((char)0xFFFD); } else { - asciiEnd.Append('.'); + asciiEnd.Append(currentChar); } + charCounter++; // If we've hit the end of a line, combine the right half with the // left half, and start a new line. - if ((charCounter % 16) == 0) + if ((charCounter % BytesPerLine) == 0) { - result.Append(nextLine.ToString() + " " + asciiEnd.ToString()); + result.Append(nextLine).Append(' ').Append(asciiEnd); nextLine.Clear(); asciiEnd.Clear(); - Offset += 0x10; - nextLine.AppendFormat("{0:X2} ", CultureInfo.InvariantCulture.TextInfo.ToUpper(Convert.ToString(Offset, 16)).PadLeft(8, '0')); + currentOffset += BytesPerLine; + nextLine.AppendFormat(CultureInfo.InvariantCulture, LineFormat, currentOffset); // Adding a newline to support long inputs strings flowing through InputObject parameterset. - if ((charCounter <= Bytes.Length) && string.IsNullOrEmpty(this.Path)) + if ((charCounter <= Bytes.Length) && string.IsNullOrEmpty(Path)) { - result.Append("\r\n"); + result.AppendLine(); } } } // At the end of the file, we might not have had the chance to output - // the end of the line yet. Only do this if we didn't exit on the 16-byte + // the end of the line yet. Only do this if we didn't exit on the 16-byte // boundary, though. if ((charCounter % 16) != 0) { while ((charCounter % 16) != 0) { - nextLine.Append(" "); + nextLine.Append(' ', 3); asciiEnd.Append(' '); charCounter++; } - result.Append(nextLine.ToString() + " " + asciiEnd.ToString()); + + result.Append(nextLine).Append(' ').Append(asciiEnd); } } return result.ToString(); } } -} // namespace Microsoft.PowerShell.Commands - +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index e596e0cb64a..a41b284f568 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; @@ -13,11 +11,8 @@ namespace Microsoft.PowerShell.Commands { /// /// Base class for all variable commands. - /// - /// Because -Scope is defined in VariableCommandBase, all derived commands - /// must implement -Scope. + /// Because -Scope is defined in VariableCommandBase, all derived commands must implement -Scope. /// - public abstract class VariableCommandBase : PSCmdlet { #region Parameters @@ -27,14 +22,14 @@ public abstract class VariableCommandBase : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion parameters /// - /// The Include parameter for all the variable commands + /// The Include parameter for all the variable commands. /// - /// protected string[] IncludeFilters { get @@ -44,19 +39,17 @@ protected string[] IncludeFilters set { - if (value == null) - { - value = new string[0]; - } + value ??= Array.Empty(); + _include = value; } } - private string[] _include = new string[0]; + + private string[] _include = Array.Empty(); /// - /// The Exclude parameter for all the variable commands + /// The Exclude parameter for all the variable commands. /// - /// protected string[] ExcludeFilters { get @@ -66,15 +59,13 @@ protected string[] ExcludeFilters set { - if (value == null) - { - value = new string[0]; - } + value ??= Array.Empty(); + _exclude = value; } } - private string[] _exclude = new string[0]; + private string[] _exclude = Array.Empty(); #region helpers @@ -82,37 +73,30 @@ protected string[] ExcludeFilters /// Gets the matching variable for the specified name, using the /// Include, Exclude, and Scope parameters defined in the base class. /// - /// /// /// The name or pattern of the variables to retrieve. /// - /// /// - /// The scope to do the lookup in. If null or empty the normal scoping - /// rules apply. + /// The scope to do the lookup in. If null or empty the normal scoping rules apply. /// - /// /// /// True is returned if a variable exists of the given name but was filtered /// out via globbing, include, or exclude. /// - /// /// /// If true, don't report errors when trying to access private variables. /// - /// /// /// A collection of the variables matching the name, include, and exclude /// pattern in the specified scope. /// - /// internal List GetMatchingVariables(string name, string lookupScope, out bool wasFiltered, bool quiet) { wasFiltered = false; - List result = new List(); + List result = new(); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { name = "*"; } @@ -166,7 +150,7 @@ internal List GetMatchingVariables(string name, string lookupScope, // view. IDictionary variableTable = null; - if (String.IsNullOrEmpty(lookupScope)) + if (string.IsNullOrEmpty(lookupScope)) { variableTable = SessionState.Internal.GetVariableTable(); } @@ -223,6 +207,7 @@ internal List GetMatchingVariables(string name, string lookupScope, } } } + result.Add(entry.Value); } else @@ -238,24 +223,24 @@ internal List GetMatchingVariables(string name, string lookupScope, } } } + return result; } #endregion helpers } - /// /// Implements get-variable command. /// - [Cmdlet(VerbsCommon.Get, "Variable", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113336")] + [Cmdlet(VerbsCommon.Get, "Variable", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096711")] [OutputType(typeof(PSVariable))] public class GetVariableCommand : VariableCommandBase { #region parameters /// - /// Name of the PSVariable + /// Name of the PSVariable. /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty()] @@ -268,16 +253,13 @@ public string[] Name set { - if (value == null) - { - value = new string[] { "*" }; - } + value ??= new string[] { "*" }; + _name = value; } } - private string[] _name = new string[] { "*" }; - + private string[] _name = new string[] { "*" }; /// /// Output only the value(s) of the requested variable(s). @@ -289,18 +271,18 @@ public SwitchParameter ValueOnly { return _valueOnly; } + set { _valueOnly = value; } } - private bool _valueOnly; + private bool _valueOnly; /// - /// The Include parameter for all the variable commands + /// The Include parameter for all the variable commands. /// - /// [Parameter] public string[] Include { @@ -316,9 +298,8 @@ public string[] Include } /// - /// The Exclude parameter for all the variable commands + /// The Exclude parameter for all the variable commands. /// - /// [Parameter] public string[] Exclude { @@ -347,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) @@ -369,7 +347,7 @@ protected override void ProcessRecord() if (!matchFound && !wasFiltered) { ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); @@ -384,37 +362,37 @@ protected override void ProcessRecord() } /// - /// Class implementing new-variable command + /// Class implementing new-variable command. /// - [Cmdlet(VerbsCommon.New, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113361")] + [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 /// - /// Name of the PSVariable + /// Name of the PSVariable. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = true)] public string Name { get; set; } /// - /// Value of the PSVariable + /// Value of the PSVariable. /// [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public object Value { get; set; } /// - /// Description of the variable + /// Description of the variable. /// [Parameter] public string Description { get; set; } - /// /// The options for the variable to specify if the variable should /// be ReadOnly, Constant, and/or Private. /// - /// [Parameter] public ScopedItemOptions Option { get; set; } = ScopedItemOptions.None; @@ -434,6 +412,7 @@ public SessionStateEntryVisibility Visibility _visibility = value; } } + private SessionStateEntryVisibility? _visibility; /// @@ -452,6 +431,7 @@ public SwitchParameter Force _force = value; } } + private bool _force; /// @@ -464,11 +444,13 @@ public SwitchParameter PassThru { return _passThru; } + set { _passThru = value; } } + private bool _passThru; #endregion parameters @@ -478,7 +460,6 @@ public SwitchParameter PassThru /// take the place of the Value parameter if none was specified on the /// command line. /// - /// protected override void ProcessRecord() { // If Force is not specified, see if the variable already exists @@ -488,7 +469,7 @@ protected override void ProcessRecord() if (!Force) { PSVariable varFound = null; - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { varFound = SessionState.PSVariable.GetAtScope(Name, "local"); @@ -502,7 +483,7 @@ protected override void ProcessRecord() if (varFound != null) { SessionStateException sessionStateException = - new SessionStateException( + new( Name, SessionStateCategory.Variable, "VariableAlreadyExists", @@ -526,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) { @@ -540,7 +521,7 @@ protected override void ProcessRecord() try { - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { SessionState.Internal.NewVariable(newVariable, Force); } @@ -571,35 +552,33 @@ protected override void ProcessRecord() WriteObject(newVariable); } } - } // ProcessRecord - } // NewVariableCommand - + } + } /// - /// This class implements set-variable command + /// This class implements set-variable command. /// - [Cmdlet(VerbsCommon.Set, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113401")] + [Cmdlet(VerbsCommon.Set, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096624")] [OutputType(typeof(PSVariable))] public sealed class SetVariableCommand : VariableCommandBase { #region parameters /// - /// Name of the PSVariable(s) to set + /// Name of the PSVariable(s) to set. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = true)] public string[] Name { get; set; } /// - /// Value of the PSVariable + /// Value of the PSVariable. /// [Parameter(Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public object Value { get; set; } = AutomationNull.Value; /// - /// The Include parameter for all the variable commands + /// The Include parameter for all the variable commands. /// - /// [Parameter] public string[] Include { @@ -615,9 +594,8 @@ public string[] Include } /// - /// The Exclude parameter for all the variable commands + /// The Exclude parameter for all the variable commands. /// - /// [Parameter] public string[] Exclude { @@ -633,17 +611,15 @@ public string[] Exclude } /// - /// Description of the variable + /// Description of the variable. /// [Parameter] public string Description { get; set; } - /// /// The options for the variable to specify if the variable should /// be ReadOnly, Constant, and/or Private. /// - /// [Parameter] public ScopedItemOptions Option { @@ -651,12 +627,14 @@ public ScopedItemOptions Option { return (ScopedItemOptions)_options; } + set { _options = value; } } - private Nullable _options; + + private ScopedItemOptions? _options; /// /// Force the operation to make the best attempt at setting the variable. @@ -674,6 +652,7 @@ public SwitchParameter Force _force = value; } } + private bool _force; /// @@ -692,8 +671,8 @@ public SessionStateEntryVisibility Visibility _visibility = value; } } - private SessionStateEntryVisibility? _visibility; + private SessionStateEntryVisibility? _visibility; /// /// The variable object should be passed down the pipeline. @@ -705,20 +684,27 @@ public SwitchParameter PassThru { return _passThru; } + set { _passThru = value; } } + 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 /// - /// Checks to see if the name and value parameters were - /// bound as formal parameters. + /// Checks to see if the name and value parameters were bound as formal parameters. /// protected override void BeginProcessing() { @@ -731,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); + } + } + } } /// @@ -742,11 +755,20 @@ protected override void BeginProcessing() /// If name is not a formal parameter, then set /// the variable each time ProcessRecord is called. /// - /// protected override void ProcessRecord() { if (_nameIsFormalParameter && _valueIsFormalParameter) { + if (Append) + { + if (Value != AutomationNull.Value) + { + _valueList ??= new List(); + + _valueList.Add(Value); + } + } + return; } @@ -754,10 +776,8 @@ protected override void ProcessRecord() { if (Value != AutomationNull.Value) { - if (_valueList == null) - { - _valueList = new ArrayList(); - } + _valueList ??= new List(); + _valueList.Add(Value); } } @@ -766,7 +786,8 @@ protected override void ProcessRecord() SetVariable(Name, Value); } } - private ArrayList _valueList; + + private List _valueList; /// /// Sets the variable if the name was specified as a formal parameter @@ -778,7 +799,14 @@ protected override void EndProcessing() { if (_valueIsFormalParameter) { - SetVariable(Name, Value); + if (Append) + { + SetVariable(Name, _valueList); + } + else + { + SetVariable(Name, Value); + } } else { @@ -808,15 +836,12 @@ protected override void EndProcessing() /// /// Sets the variables of the given names to the specified value. /// - /// /// /// The name(s) of the variables to set. /// - /// /// /// The value to set the variable to. /// - /// private void SetVariable(string[] varNames, object varValue) { CommandOrigin origin = MyInvocation.CommandOrigin; @@ -825,11 +850,11 @@ private void SetVariable(string[] varNames, object varValue) { // First look for existing variables to set. - List matchingVariables = new List(); + List matchingVariables = new(); bool wasFiltered = false; - if (!String.IsNullOrEmpty(Scope)) + if (!string.IsNullOrEmpty(Scope)) { // We really only need to find matches if the scope was specified. // If the scope wasn't specified then we need to create the @@ -862,8 +887,8 @@ private void SetVariable(string[] varNames, object varValue) { ScopedItemOptions newOptions = ScopedItemOptions.None; - if (!String.IsNullOrEmpty(Scope) && - String.Equals("private", Scope, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(Scope) && + string.Equals("private", Scope, StringComparison.OrdinalIgnoreCase)) { newOptions = ScopedItemOptions.Private; } @@ -880,15 +905,13 @@ 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; // If visibility was specified, set it on the variable @@ -905,7 +928,7 @@ private void SetVariable(string[] varNames, object varValue) { object result = null; - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { result = SessionState.Internal.SetVariable(varToSet, Force, origin); @@ -974,8 +997,17 @@ private void SetVariable(string[] varNames, object varValue) if (varValue != AutomationNull.Value) { matchingVariable.Value = varValue; - } + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + { + // In 'ConstrainedLanguage' we want to monitor untrusted values assigned to 'Global:' variables + // and 'Script:' variables, because they may be set from 'ConstrainedLanguage' environment and + // referenced within trusted script block, and thus result in security issues. + // Here we are setting the value of an existing variable and don't know what scope this variable + // is from, so we mark the value as untrusted, regardless of the scope. + ExecutionContext.MarkObjectAsUntrusted(matchingVariable.Value); + } + } if (Description != null) { @@ -1027,27 +1059,26 @@ private void SetVariable(string[] varNames, object varValue) } } } - } // ProcessRecord - } // SetVariableCommand + } + } /// - /// The Remove-Variable cmdlet implementation + /// The Remove-Variable cmdlet implementation. /// - [Cmdlet(VerbsCommon.Remove, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113380")] + [Cmdlet(VerbsCommon.Remove, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097123")] public sealed class RemoveVariableCommand : VariableCommandBase { #region parameters /// - /// Name of the PSVariable(s) to set + /// Name of the PSVariable(s) to set. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = true)] public string[] Name { get; set; } /// - /// The Include parameter for all the variable commands + /// The Include parameter for all the variable commands. /// - /// [Parameter] public string[] Include { @@ -1063,9 +1094,8 @@ public string[] Include } /// - /// The Exclude parameter for all the variable commands + /// The Exclude parameter for all the variable commands. /// - /// [Parameter] public string[] Exclude { @@ -1081,9 +1111,8 @@ public string[] Exclude } /// - /// If true, the variable is removed even if it is ReadOnly + /// If true, the variable is removed even if it is ReadOnly. /// - /// [Parameter] public SwitchParameter Force { @@ -1091,29 +1120,26 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; #endregion parameters /// - /// Removes the matching variables from the specified scope + /// Removes the matching variables from the specified scope. /// - /// 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) { @@ -1129,7 +1155,7 @@ protected override void ProcessRecord() // characters were specified, write an error. ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); @@ -1155,7 +1181,7 @@ protected override void ProcessRecord() { try { - if (String.IsNullOrEmpty(Scope)) + if (string.IsNullOrEmpty(Scope)) { SessionState.Internal.RemoveVariable(matchingVariable, _force); } @@ -1181,28 +1207,27 @@ protected override void ProcessRecord() } } } - } // ProcessRecord - } // RemoveVariableCommand + } + } /// - /// This class implements set-variable command + /// This class implements set-variable command. /// - [Cmdlet(VerbsCommon.Clear, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113285")] + [Cmdlet(VerbsCommon.Clear, "Variable", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096923")] [OutputType(typeof(PSVariable))] public sealed class ClearVariableCommand : VariableCommandBase { #region parameters /// - /// Name of the PSVariable(s) to set + /// Name of the PSVariable(s) to set. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, Mandatory = true)] public string[] Name { get; set; } /// - /// The Include parameter for all the variable commands + /// The Include parameter for all the variable commands. /// - /// [Parameter] public string[] Include { @@ -1218,9 +1243,8 @@ public string[] Include } /// - /// The Exclude parameter for all the variable commands + /// The Exclude parameter for all the variable commands. /// - /// [Parameter] public string[] Exclude { @@ -1251,6 +1275,7 @@ public SwitchParameter Force _force = value; } } + private bool _force; /// @@ -1263,19 +1288,20 @@ public SwitchParameter PassThru { return _passThru; } + set { _passThru = value; } } + private bool _passThru; #endregion parameters /// - /// The implementation of the Clear-Variable command + /// The implementation of the Clear-Variable command. /// - /// protected override void ProcessRecord() { foreach (string varName in Name) @@ -1291,7 +1317,7 @@ protected override void ProcessRecord() // characters were specified, write an error. ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); @@ -1359,17 +1385,15 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } /// /// Clears the value of the variable using the PSVariable instance if the scope /// was specified or using standard variable lookup if the scope was not specified. /// - /// /// /// The variable that matched the name parameter(s). /// - /// private PSVariable ClearValue(PSVariable matchingVariable) { PSVariable result = matchingVariable; @@ -1382,8 +1406,8 @@ private PSVariable ClearValue(PSVariable matchingVariable) SessionState.PSVariable.Set(matchingVariable.Name, null); result = SessionState.PSVariable.Get(matchingVariable.Name); } + return result; } - } // ClearVariableCommand + } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs index a66bda09d81..3c4336f07d0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -11,14 +10,14 @@ namespace Microsoft.PowerShell.Commands /// /// Waits for a given event to arrive. /// - [Cmdlet(VerbsLifecycle.Wait, "Event", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135276")] + [Cmdlet(VerbsLifecycle.Wait, "Event", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097042")] [OutputType(typeof(PSEventArgs))] public class WaitEventCommand : PSCmdlet { #region parameters /// - /// An identifier for this event subscription + /// An identifier for this event subscription. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] public string SourceIdentifier @@ -27,12 +26,14 @@ public string SourceIdentifier { return _sourceIdentifier; } + set { _sourceIdentifier = value; _matchPattern = WildcardPattern.Get(value, WildcardOptions.IgnoreCase); } } + private string _sourceIdentifier = null; /// @@ -41,29 +42,31 @@ public string SourceIdentifier /// [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, Int32.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int Timeout { get { return _timeoutInSeconds; } + set { _timeoutInSeconds = value; } } + private int _timeoutInSeconds = -1; // -1: infinite, this default is to wait for as long as it takes. #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; /// - /// Wait for the event to arrive + /// Wait for the event to arrive. /// protected override void ProcessRecord() { @@ -102,14 +105,14 @@ protected override void ProcessRecord() } /// - /// Handle Control-C + /// Handle Control-C. /// protected override void StopProcessing() { _eventArrived.Set(); } - private void ReceivedEvents_PSEventReceived(Object sender, PSEventArgs e) + private void ReceivedEvents_PSEventReceived(object sender, PSEventArgs e) { // If they want to wait on just any event if (_sourceIdentifier == null) @@ -123,7 +126,6 @@ private void ReceivedEvents_PSEventReceived(Object sender, PSEventArgs e) } } - // Go through all the received events. If one matches the subscription identifier, // break. private void ScanEventQueue() @@ -158,4 +160,4 @@ private void NotifyEvent(PSEventArgs e) } } } -} \ No newline at end of file +} 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 c2899459a40..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,55 +1,47 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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 { /// - /// Response object for html content without DOM parsing + /// Response object for html content without DOM parsing. /// 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); } @@ -58,22 +50,28 @@ public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentSt #region Properties /// - /// gets or protected sets the Content property + /// Gets the text body content of this response. /// + /// + /// Content of the response body, decoded using , + /// if the Content-Type response header is a recognized text + /// type. Otherwise . + /// public new string Content { get; private set; } /// - /// Gets the Encoding that was used to decode the Content + /// Gets the encoding of the text body content of this response. /// /// - /// The Encoding used to decode the Content; otherwise, a null reference if the content is not text. + /// Encoding of the response body from the Content-Type header, + /// or if the encoding could not be determined. /// - public Encoding Encoding { get; private set; } + public Encoding? Encoding { get; private set; } - private WebCmdletElementCollection _inputFields; + private WebCmdletElementCollection? _inputFields; /// - /// gets the Fields property + /// Gets the HTML input field elements parsed from . /// public WebCmdletElementCollection InputFields { @@ -81,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); @@ -97,10 +93,10 @@ public WebCmdletElementCollection InputFields } } - private WebCmdletElementCollection _links; + private WebCmdletElementCollection? _links; /// - /// gets the Links property + /// Gets the HTML a link elements parsed from . /// public WebCmdletElementCollection Links { @@ -108,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")); @@ -124,10 +118,10 @@ public WebCmdletElementCollection Links } } - private WebCmdletElementCollection _images; + private WebCmdletElementCollection? _images; /// - /// gets the Images property + /// Gets the HTML img elements parsed from . /// public WebCmdletElementCollection Images { @@ -135,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")); @@ -158,26 +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); - this.Content = StreamHelper.DecodeStream(RawContentStream, characterSet, out encoding); - this.Encoding = encoding; + // Fill the Content buffer + string? characterSet = WebResponseHelper.GetCharacterSet(BaseResponse); + + 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)); @@ -187,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; @@ -276,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 82f3033b7c8..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. All rights reserved. ---********************************************************************/ +// 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; - } - - internal static Encoding GetDefaultEncoding() - { - return GetEncodingOrDefault((string)null); - } - - internal static Encoding GetEncoding(HttpResponseMessage response) - { - // ContentType may not exist in response header. - string charSet = response.Content.Headers.ContentType?.CharSet; - return GetEncodingOrDefault(charSet); - } + // 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 GetEncodingOrDefault(string characterSet) - { - // get the name of the codepage to use for response content - string codepage = (string.IsNullOrEmpty(characterSet) ? _defaultCodePage : characterSet); - Encoding encoding = null; + internal static string? GetContentType(HttpRequestMessage request) => request.Content?.Headers.ContentType?.MediaType; - try - { - encoding = Encoding.GetEncoding(codepage); - } - catch (ArgumentException) - { - // 0, default code page - encoding = Encoding.GetEncoding(0); - } + internal static Encoding GetDefaultEncoding() => Encoding.UTF8; - 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) @@ -92,15 +54,13 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) { continue; } + foreach (var header in headerCollection) { // Headers may have multiple entries with different values - foreach (var headerValue in header.Value) + foreach (string headerValue in header.Value) { - raw.Append(header.Key); - raw.Append(": "); - raw.Append(headerValue); - raw.AppendLine(); + raw.AppendLine($"{header.Key}: {headerValue}"); } } } @@ -109,74 +69,55 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) return raw; } - internal static bool IsJson(string contentType) + internal static bool IsJson([NotNullWhen(true)] 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) - { - if (String.IsNullOrEmpty(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)) + if (string.IsNullOrEmpty(contentType)) + { return false; + } - // any text, xml or json types are text + // Any text, xml or json types are text bool isText = contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) - || CheckIsXml(contentType) - || CheckIsJson(contentType); + || IsXml(contentType) + || IsJson(contentType); // Further content type analysis is available on Windows if (Platform.IsWindows && !isText) { // Media types registered with Windows as having a perceived type of text, are text - using (RegistryKey contentTypeKey = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + contentType)) + using (RegistryKey? contentTypeKey = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + contentType)) { if (contentTypeKey != null) { - string extension = contentTypeKey.GetValue("Extension") as string; - if (extension != null) + if (contentTypeKey.GetValue("Extension") is string extension) { - using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension)) + using (RegistryKey? extensionKey = Registry.ClassesRoot.OpenSubKey(extension)) { if (extensionKey != null) { - string perceivedType = extensionKey.GetValue("PerceivedType") as string; - isText = (perceivedType == "text"); + string? perceivedType = extensionKey.GetValue("PerceivedType") as string; + isText = perceivedType == "text"; } } } @@ -184,32 +125,28 @@ private static bool CheckIsText(string contentType) } } - return (isText); + return isText; } - private static bool CheckIsXml(string contentType) + internal static bool IsXml([NotNullWhen(true)] string? contentType) { - if (String.IsNullOrEmpty(contentType)) + 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 7c6a149cc7f..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,67 +1,57 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; -using System.Management.Automation; +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; using Newtonsoft.Json.Linq; -using System.Net.Http; -using System.Text; 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 + /// Enable automatic following of rel links. /// [Parameter] [Alias("FL")] public SwitchParameter FollowRelLink { - get { return base._followRelLink; } - set { base._followRelLink = value; } + get => base._followRelLink; + + set => base._followRelLink = value; } /// - /// gets or sets the maximum number of rel links to follow + /// Gets or sets the maximum number of rel links to follow. /// [Parameter] [Alias("ML")] - [ValidateRange(1, Int32.MaxValue)] + [ValidateRange(1, int.MaxValue)] public int MaximumFollowRelLink { - get { return base._maximumFollowRelLink; } - set { base._maximumFollowRelLink = value; } + get => base._maximumFollowRelLink; + + set => base._maximumFollowRelLink = value; } /// @@ -69,13 +59,138 @@ 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; } #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 bool TryProcessFeedStream(BufferingStreamReader responseStream) + 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; @@ -88,8 +203,8 @@ private bool TryProcessFeedStream(BufferingStreamReader responseStream) int readCount = 0; while ((readCount < 10) && reader.Read()) { - if (String.Equals("rss", reader.Name, StringComparison.OrdinalIgnoreCase) || - String.Equals("feed", reader.Name, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("rss", reader.Name, StringComparison.OrdinalIgnoreCase) || + string.Equals("feed", reader.Name, StringComparison.OrdinalIgnoreCase)) { isRssOrFeed = true; break; @@ -100,8 +215,9 @@ private bool TryProcessFeedStream(BufferingStreamReader 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) @@ -112,8 +228,8 @@ private bool TryProcessFeedStream(BufferingStreamReader 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 @@ -123,7 +239,10 @@ private bool TryProcessFeedStream(BufferingStreamReader responseStream) } } } - catch (XmlException) { } + catch (XmlException) + { + // Catch XmlException + } finally { responseStream.Seek(0, SeekOrigin.Begin); @@ -133,14 +252,14 @@ private bool TryProcessFeedStream(BufferingStreamReader 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; - //The XML data needs to be in conformance to the rules for a well-formed XML 1.0 document. + // The XML data needs to be in conformance to the rules for a well-formed XML 1.0 document. xrs.IgnoreProcessingInstructions = true; xrs.MaxCharactersFromEntities = 1024; xrs.DtdProcessing = DtdProcessing.Ignore; @@ -149,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); @@ -167,20 +286,20 @@ private bool TryConvertToXml(string xml, out object doc, ref Exception exRef) exRef = ex; doc = null; } - return (null != doc); + + 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 (null == obj) + if (obj == null) { - // This ensures that a null returned by ConvertFromJson() is the actual JSON null literal. + // This ensures that a null returned by ConvertFromJson() is the actual JSON null literal. // if not, the ArgumentException will be caught. JToken.Parse(json); } @@ -195,94 +314,84 @@ 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; } + return converted; } - #endregion + #endregion Helper Methods /// - /// enum for rest return type. + /// Enum for rest return type. /// public enum RestReturnType { /// /// Return type not defined in response, - /// best effort detect + /// best effort detect. /// Detect, /// - /// Json return type + /// Json return type. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] Json, /// - /// Xml return type + /// Xml return type. /// 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; } - set { _streamBuffer.Position = value; } + get => _streamBuffer.Position; + + set => _streamBuffer.Position = value; } public override int Read(byte[] buffer, int offset, int count) @@ -290,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) { @@ -345,126 +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=217034", 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 (null == response) { throw new ArgumentNullException("response"); } - - using (BufferingStreamReader responseStream = new BufferingStreamReader(StreamHelper.GetResponseStream(response))) - { - if (ShouldWriteToPipeline) - { - // 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); - } - - object obj = null; - Exception ex = null; - - string str = StreamHelper.DecodeStream(responseStream, ref encoding); - // NOTE: Tests use this verbose output to verify the encoding. - WriteVerbose(string.Format - ( - System.Globalization.CultureInfo.InvariantCulture, - "Content encoding: {0}", - string.IsNullOrEmpty(encoding.HeaderName) ? encoding.EncodingName : encoding.HeaderName) - ); - 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); - } - } - - if (ShouldSaveToOutFile) - { - StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this); - } - - 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 (null == response) { throw new ArgumentNullException("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 748732d7c6b..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 @@ -1,32 +1,33 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; using System.Management.Automation; using System.Net; -using System.IO; -using System.Text; -using System.Collections; -using System.Globalization; +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 Microsoft.Win32; -using System.Net.Http; -using System.Net.Http.Headers; +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 System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Linq; namespace Microsoft.PowerShell.Commands { /// - /// The valid values for the -Authentication parameter for Invoke-RestMethod and Invoke-WebRequest + /// The valid values for the -Authentication parameter for Invoke-RestMethod and Invoke-WebRequest. /// public enum WebAuthenticationType { @@ -36,17 +37,17 @@ public enum WebAuthenticationType None, /// - /// RFC-7617 Basic Authentication. Requires -Credential + /// RFC-7617 Basic Authentication. Requires -Credential. /// Basic, /// - /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token + /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token. /// Bearer, /// - /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token + /// RFC-6750 OAuth 2.0 Bearer Authentication. Requires -Token. /// OAuth, } @@ -54,37 +55,96 @@ public enum WebAuthenticationType // WebSslProtocol is used because not all SslProtocols are supported by HttpClientHandler. // Also SslProtocols.Default is not the "default" for HttpClientHandler as SslProtocols.Ssl3 is not supported. /// - /// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest + /// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest. /// [Flags] public enum WebSslProtocol { /// - /// No SSL protocol will be set and the system defaults will be used. + /// 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 + /// 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 @@ -96,83 +156,95 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet public virtual SwitchParameter UseBasicParsing { get; set; } = true; /// - /// gets or sets the Uri property + /// Gets or sets the Uri property. /// [Parameter(Position = 0, Mandatory = true)] [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 /// - /// gets or sets the Session property + /// Gets or sets the Session property. /// [Parameter] public virtual WebRequestSession WebSession { get; set; } /// - /// gets or sets the SessionVariable property + /// Gets or sets the SessionVariable property. /// [Parameter] [Alias("SV")] public virtual string SessionVariable { get; set; } - #endregion + #endregion Session #region Authorization and Credentials /// - /// Gets or sets the AllowUnencryptedAuthentication property + /// Gets or sets the AllowUnencryptedAuthentication property. /// [Parameter] 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 - /// OAuth/Bearer: Requires Token + /// Basic: Requires Credential. + /// OAuth/Bearer: Requires Token. /// [Parameter] public virtual WebAuthenticationType Authentication { get; set; } = WebAuthenticationType.None; /// - /// gets or sets the Credential property + /// Gets or sets the Credential property. /// [Parameter] [Credential] public virtual PSCredential Credential { get; set; } /// - /// gets or sets the UseDefaultCredentials property + /// Gets or sets the UseDefaultCredentials property. /// [Parameter] public virtual SwitchParameter UseDefaultCredentials { get; set; } /// - /// gets or sets the CertificateThumbprint property + /// Gets or sets the CertificateThumbprint property. /// [Parameter] [ValidateNotNullOrEmpty] public virtual string CertificateThumbprint { get; set; } /// - /// gets or sets the Certificate property + /// Gets or sets the Certificate property. /// [Parameter] [ValidateNotNull] public virtual X509Certificate Certificate { get; set; } /// - /// gets or sets the SkipCertificateCheck property + /// Gets or sets the SkipCertificateCheck property. /// [Parameter] public virtual SwitchParameter SkipCertificateCheck { get; set; } /// - /// Gets or sets the TLS/SSL protocol used by the Web Cmdlet + /// Gets or sets the TLS/SSL protocol used by the Web Cmdlet. /// [Parameter] public virtual WebSslProtocol SslProtocol { get; set; } = WebSslProtocol.Default; @@ -183,106 +255,164 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual SecureString Token { get; set; } - #endregion + #endregion Authorization and Credentials #region Headers /// - /// gets or sets the UserAgent property + /// Gets or sets the UserAgent property. /// [Parameter] public virtual string UserAgent { get; set; } /// - /// gets or sets the DisableKeepAlive property + /// Gets or sets the DisableKeepAlive property. /// [Parameter] 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 + /// Gets or sets the Headers property. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [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; } - } - private int _maximumRedirection = -1; + public virtual SwitchParameter AllowInsecureRedirect { get; set; } + + /// + /// 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, 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, int.MaxValue)] + public virtual int RetryIntervalSec { get; set; } = 5; - #endregion + #endregion Redirect #region Method /// - /// gets or sets the Method property + /// Gets or sets the Method property. /// [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 + /// Gets or sets the CustomMethod property. /// - [Parameter(Mandatory=true,ParameterSetName = "CustomMethod")] - [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] + [Parameter(Mandatory = true, ParameterSetName = "CustomMethod")] + [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 /// - /// gets or sets the NoProxy property + /// Gets or sets the NoProxy property. /// - [Parameter(Mandatory=true,ParameterSetName = "CustomMethodNoProxy")] - [Parameter(Mandatory=true,ParameterSetName = "StandardMethodNoProxy")] + [Parameter(Mandatory = true, ParameterSetName = "CustomMethodNoProxy")] + [Parameter(Mandatory = true, ParameterSetName = "StandardMethodNoProxy")] public virtual SwitchParameter NoProxy { get; set; } - #endregion + #endregion NoProxy #region Proxy /// - /// gets or sets the Proxy property + /// Gets or sets the Proxy property. /// [Parameter(ParameterSetName = "StandardMethod")] [Parameter(ParameterSetName = "CustomMethod")] public virtual Uri Proxy { get; set; } /// - /// gets or sets the ProxyCredential property + /// Gets or sets the ProxyCredential property. /// [Parameter(ParameterSetName = "StandardMethod")] [Parameter(ParameterSetName = "CustomMethod")] @@ -290,201 +420,446 @@ public virtual string CustomMethod public virtual PSCredential ProxyCredential { get; set; } /// - /// gets or sets the ProxyUseDefaultCredentials property + /// Gets or sets the ProxyUseDefaultCredentials property. /// [Parameter(ParameterSetName = "StandardMethod")] [Parameter(ParameterSetName = "CustomMethod")] public virtual SwitchParameter ProxyUseDefaultCredentials { get; set; } - #endregion + #endregion Proxy #region Input /// - /// gets or sets the Body property + /// Gets or sets the Body property. /// [Parameter(ValueFromPipeline = true)] public virtual object Body { get; set; } /// - /// Dictionary for use with RFC-7578 multipart/form-data submissions. + /// Dictionary for use with RFC-7578 multipart/form-data submissions. /// Keys are form fields and their respective values are form values. /// A value may be a collection of form values or single form value. /// [Parameter] - public virtual IDictionary Form {get; set;} + public virtual IDictionary Form { get; set; } /// - /// gets or sets the ContentType property + /// Gets or sets the ContentType property. /// [Parameter] public virtual string ContentType { get; set; } /// - /// gets or sets the TransferEncoding property + /// Gets or sets the TransferEncoding property. /// [Parameter] [ValidateSet("chunked", "compress", "deflate", "gzip", "identity", IgnoreCase = true)] public virtual string TransferEncoding { get; set; } /// - /// gets or sets the InFile property + /// Gets or sets the InFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string InFile { get; set; } /// - /// keep the original file path after the resolved provider path is - /// assigned to InFile + /// Keep the original file path after the resolved provider path is assigned to InFile. /// private string _originalFilePath; - #endregion + #endregion Input #region Output /// - /// gets or sets the OutFile property + /// Gets or sets the OutFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string OutFile { get; set; } /// - /// gets or sets the PassThrough property + /// Gets or sets the PassThrough property. /// [Parameter] public virtual SwitchParameter PassThru { get; set; } - #endregion + /// + /// Resumes downloading a partial or incomplete file. OutFile is required. + /// + [Parameter] + public virtual SwitchParameter Resume { get; set; } + + /// + /// Gets or sets whether to skip checking HTTP status for error codes. + /// + [Parameter] + public virtual SwitchParameter SkipHttpErrorCheck { get; set; } + + #endregion Output #endregion Virtual Properties + #region Helper Properties + + 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() + { + try + { + // 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); + } + } + + /// + /// 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 ((null != WebSession) && (null != SessionVariable)) + // Sessions + if (WebSession is not null && SessionVariable is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, - "WebCmdletSessionConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.SessionConflict, "WebCmdletSessionConflictException"); ThrowTerminatingError(error); } // Authentication - if (UseDefaultCredentials && (Authentication != WebAuthenticationType.None)) + if (UseDefaultCredentials && Authentication != WebAuthenticationType.None) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, - "WebCmdletAuthenticationConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationConflict, "WebCmdletAuthenticationConflictException"); ThrowTerminatingError(error); } - if ((Authentication != WebAuthenticationType.None) && (null != Token) && (null != Credential)) + + if (Authentication != WebAuthenticationType.None && Token is not null && Credential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, - "WebCmdletAuthenticationTokenConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenConflict, "WebCmdletAuthenticationTokenConflictException"); ThrowTerminatingError(error); } - if ((Authentication == WebAuthenticationType.Basic) && (null == Credential)) + + if (Authentication == WebAuthenticationType.Basic && Credential is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, - "WebCmdletAuthenticationCredentialNotSuppliedException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationCredentialNotSupplied, "WebCmdletAuthenticationCredentialNotSuppliedException"); ThrowTerminatingError(error); } - if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && (null == Token)) + + if ((Authentication == WebAuthenticationType.OAuth || Authentication == WebAuthenticationType.Bearer) && Token is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, - "WebCmdletAuthenticationTokenNotSuppliedException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.AuthenticationTokenNotSupplied, "WebCmdletAuthenticationTokenNotSuppliedException"); ThrowTerminatingError(error); } - if (!AllowUnencryptedAuthentication && (Authentication != WebAuthenticationType.None) && (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); } - if (!AllowUnencryptedAuthentication && (null != Credential || UseDefaultCredentials) && (Uri.Scheme != "https")) + + // Credentials + if (UseDefaultCredentials && Credential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.AllowUnencryptedAuthenticationRequired, - "WebCmdletAllowUnencryptedAuthenticationRequiredException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, "WebCmdletCredentialConflictException"); ThrowTerminatingError(error); } - // credentials - if (UseDefaultCredentials && (null != Credential)) + // Proxy server + if (ProxyUseDefaultCredentials && ProxyCredential is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.CredentialConflict, - "WebCmdletCredentialConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, "WebCmdletProxyCredentialConflictException"); ThrowTerminatingError(error); } - - // Proxy server - if (ProxyUseDefaultCredentials && (null != ProxyCredential)) - { - ErrorRecord error = GetValidationError(WebCmdletStrings.ProxyCredentialConflict, - "WebCmdletProxyCredentialConflictException"); - ThrowTerminatingError(error); - } - else if ((null == Proxy) && ((null != ProxyCredential) || 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 ((null != Body) && (null != InFile)) + // 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 ((null != Body) && (null != Form)) + + if (Body is not null && Form is not null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.BodyFormConflict, - "WebCmdletBodyFormConflictException"); + ErrorRecord error = GetValidationError(WebCmdletStrings.BodyFormConflict, "WebCmdletBodyFormConflictException"); ThrowTerminatingError(error); } - if ((null != InFile) && (null != Form)) + + 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; InFile = providerPaths[0]; } @@ -503,49 +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)); + ThrowTerminatingError(error); + } + + // Resume requires OutFile. + if (Resume.IsPresent && OutFile is null) { - ErrorRecord error = GetValidationError(WebCmdletStrings.OutFileMissing, - "WebCmdletOutFileMissingException"); + 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.ResumeNotFilePath, "WebCmdletResumeNotFilePathException", _qualifiedOutFile); ThrowTerminatingError(error); } } internal virtual void PrepareSession() { - // make sure we have a valid WebRequestSession object to work with - if (null == WebSession) - { - WebSession = new WebRequestSession(); - } + // Make sure we have a valid WebRequestSession object to work with + WebSession ??= new WebRequestSession(); - if (null != SessionVariable) + 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 (null != Credential && 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 ((null != Credential || null!= Token) && Authentication != WebAuthenticationType.None) + else if ((Credential is not null || Token is not null) && Authentication != WebAuthenticationType.None) { ProcessAuthentication(); } @@ -554,18 +939,17 @@ internal virtual void PrepareSession() WebSession.UseDefaultCredentials = true; } - - if (null != CertificateThumbprint) + 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) { X509Certificate certificate = (X509Certificate)tbCert; @@ -573,1049 +957,943 @@ internal virtual void PrepareSession() } } - if (null != Certificate) + if (Certificate is not null) { WebSession.AddCertificate(Certificate); } - // - // handle the user agent - // - if (null != UserAgent) + // Handle the user agent + if (UserAgent is not null) { - // store the UserAgent string + // Store the UserAgent string WebSession.UserAgent = UserAgent; } - if (null != Proxy) + // 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 (null != ProxyCredential) - { - 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 (-1 < MaximumRedirection) + if (MyInvocation.BoundParameters.ContainsKey(nameof(SslProtocol))) { - WebSession.MaximumRedirection = MaximumRedirection; + WebSession.SslProtocol = SslProtocol; } - // store the other supplied headers - if (null != Headers) + if (MaximumRedirection > -1) { - foreach (string key in Headers.Keys) - { - // add the header value (or overwrite it if already present) - WebSession.Headers[key] = Headers[key].ToString(); - } + WebSession.MaximumRedirection = MaximumRedirection; } - } - - #endregion Virtual Methods - - #region Helper Properties - - internal string QualifiedOutFile - { - get { return (QualifyFilePath(OutFile)); } - } - internal bool ShouldSaveToOutFile - { - get { return (!string.IsNullOrEmpty(OutFile)); } - } + WebSession.UnixSocket = UnixSocket; - internal bool ShouldWriteToPipeline - { - get { return (!ShouldSaveToOutFile || PassThru); } - } + WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent; - #endregion Helper Properties + // Store the other supplied headers + if (Headers is not null) + { + foreach (string key in Headers.Keys) + { + object value = Headers[key]; - #region Helper Methods + // null is not valid value for header. + // We silently ignore header if value is null. + if (value is not null) + { + // Add the header value (or overwrite it if already present). + WebSession.Headers[key] = value.ToString(); + } + } + } - private Uri PrepareUri(Uri uri) - { - uri = CheckProtocol(uri); + if (MaximumRetryCount > 0) + { + WebSession.MaximumRetryCount = MaximumRetryCount; - // 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 ((null != bodyAsDictionary) - && ((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; + // Only set retry interval if retry count is set. + WebSession.RetryIntervalInSeconds = RetryIntervalSec; } - return uri; + WebSession.ConnectionTimeout = ConvertTimeoutSecondsToTimeSpan(ConnectionTimeoutSeconds); } - private Uri CheckProtocol(Uri uri) + internal virtual HttpClient GetHttpClient(bool handleRedirect) { - if (null == uri) { throw new ArgumentNullException("uri"); } + HttpClient client = WebSession.GetHttpClient(handleRedirect, out bool clientWasReset); - if (!uri.IsAbsoluteUri) + if (clientWasReset) { - uri = new Uri("http://" + uri.OriginalString); + WriteVerbose(WebCmdletStrings.WebSessionConnectionRecreated); } - return (uri); - } - private string QualifyFilePath(string path) - { - string resolvedFilePath = PathUtils.ResolveFilePath(path, this, false); - return resolvedFilePath; + return client; } - private string FormatDictionary(IDictionary content) + internal virtual HttpRequestMessage GetRequest(Uri uri) { - if (content == null) - throw new ArgumentNullException("content"); + Uri requestUri = PrepareUri(uri); + HttpMethod httpMethod = string.IsNullOrEmpty(CustomMethod) ? GetHttpMethod(Method) : new HttpMethod(CustomMethod); - StringBuilder bodyBuilder = new StringBuilder(); - foreach (string key in content.Keys) - { - if (0 < bodyBuilder.Length) - { - bodyBuilder.Append("&"); - } + // Create the base WebRequest object + HttpRequestMessage request = new(httpMethod, requestUri); - object value = content[key]; + if (HttpVersion is not null) + { + request.Version = HttpVersion; + } - // URLEncode the key and value - string encodedKey = WebUtility.UrlEncode(key); - string encodedValue = String.Empty; - if (null != value) + // Pull in session data + if (WebSession.Headers.Count > 0) + { + WebSession.ContentHeaders.Clear(); + foreach (var entry in WebSession.Headers) { - encodedValue = WebUtility.UrlEncode(value.ToString()); + if (HttpKnownHeaderNames.ContentHeaders.Contains(entry.Key)) + { + WebSession.ContentHeaders.Add(entry.Key, entry.Value); + } + else + { + if (SkipHeaderValidation) + { + request.Headers.TryAddWithoutValidation(entry.Key, entry.Value); + } + else + { + request.Headers.Add(entry.Key, entry.Value); + } + } } - - 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() - { - string unencoded = String.Format("{0}:{1}", Credential.UserName, Credential.GetNetworkCredential().Password); - Byte[] bytes = Encoding.UTF8.GetBytes(unencoded); - return String.Format("Basic {0}", Convert.ToBase64String(bytes)); - } - - private string GetBearerAuthorizationHeader() - { - return String.Format("Bearer {0}", new NetworkCredential(String.Empty, Token).Password); - } - - private void ProcessAuthentication() - { - if(Authentication == WebAuthenticationType.Basic) + // Set 'Transfer-Encoding: chunked' if 'Transfer-Encoding' is specified + if (WebSession.Headers.ContainsKey(HttpKnownHeaderNames.TransferEncoding)) { - WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); + request.Headers.TransferEncodingChunked = true; } - else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) + + // Set 'User-Agent' if WebSession.Headers doesn't already contain it + if (WebSession.Headers.TryGetValue(HttpKnownHeaderNames.UserAgent, out string userAgent)) { - WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); + WebSession.UserAgent = userAgent; } else { - Diagnostics.Assert(false, String.Format("Unrecognized Authentication value: {0}", Authentication)); + if (SkipHeaderValidation) + { + request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.UserAgent, WebSession.UserAgent); + } + else + { + request.Headers.Add(HttpKnownHeaderNames.UserAgent, WebSession.UserAgent); + } } - } - - #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 - /// - private CancellationTokenSource _cancelToken = null; - - /// - /// Parse Rel Links - /// - internal bool _parseRelLink = false; - /// - /// Automatically follow Rel Links - /// - internal bool _followRelLink = false; + // Set 'Keep-Alive' to false. This means set the Connection to 'Close'. + if (DisableKeepAlive) + { + request.Headers.Add(HttpKnownHeaderNames.Connection, "Close"); + } - /// - /// Automatically follow Rel Links - /// - internal Dictionary _relationLink = null; + // Set 'Transfer-Encoding' + if (TransferEncoding is not null) + { + request.Headers.TransferEncodingChunked = true; + TransferCodingHeaderValue headerValue = new(TransferEncoding); + if (!request.Headers.TransferEncoding.Contains(headerValue)) + { + request.Headers.TransferEncoding.Add(headerValue); + } + } - /// - /// Maximum number of Rel Links to follow - /// - internal int _maximumFollowRelLink = Int32.MaxValue; + // If the file to resume downloading exists, create the Range request header using the file size. + // If not, create a Range to request the entire file. + if (Resume.IsPresent) + { + FileInfo fileInfo = new(QualifiedOutFile); - 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()); + if (fileInfo.Exists) + { + request.Headers.Range = new RangeHeaderValue(fileInfo.Length, null); + _resumeFileSize = fileInfo.Length; + } + else + { + request.Headers.Range = new RangeHeaderValue(0, null); + } } - } - #region Virtual Methods + return request; + } - // 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) + internal virtual void FillRequestStream(HttpRequestMessage request) { - // By default the HttpClientHandler will automatically decompress GZip and Deflate content - HttpClientHandler handler = new HttpClientHandler(); - handler.CookieContainer = WebSession.Cookies; + ArgumentNullException.ThrowIfNull(request); - // set the credentials used by this request - if (WebSession.UseDefaultCredentials) + // Set the request content type + if (ContentType is not null) { - // the UseDefaultCredentials flag overrides other supplied credentials - handler.UseDefaultCredentials = true; + WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType; } - else if (WebSession.Credentials != null) + else if (request.Method == HttpMethod.Post) { - handler.Credentials = WebSession.Credentials; + // Win8:545310 Invoke-WebRequest does not properly set MIME type for POST + WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out string contentType); + if (string.IsNullOrEmpty(contentType)) + { + WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = "application/x-www-form-urlencoded"; + } } - if (NoProxy) - { - handler.UseProxy = false; - } - else if (WebSession.Proxy != null) + if (Form is not null) { - handler.Proxy = WebSession.Proxy; - } + MultipartFormDataContent formData = new(); + foreach (DictionaryEntry formEntry in Form) + { + // AddMultipartContent will handle PSObject unwrapping, Object type determination and enumerateing top level IEnumerables. + AddMultipartContent(fieldName: formEntry.Key, fieldValue: formEntry.Value, formData: formData, enumerate: true); + } - if (null != WebSession.Certificates) - { - handler.ClientCertificates.AddRange(WebSession.Certificates); + SetRequestContent(request, formData); } - - if (SkipCertificateCheck) + else if (Body is not null) { - handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - } + // 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; - // This indicates GetResponse will handle redirects. - if (handleRedirect) - { - handler.AllowAutoRedirect = false; + switch (content) + { + 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 (WebSession.MaximumRedirection > -1) + else if (InFile is not null) { - if (WebSession.MaximumRedirection == 0) + // Copy InFile data + try { - handler.AllowAutoRedirect = false; + // Open the input file + SetRequestContent(request, new FileStream(InFile, FileMode.Open, FileAccess.Read, FileShare.Read)); } - else + catch (UnauthorizedAccessException) { - handler.MaxAutomaticRedirections = WebSession.MaximumRedirection; + string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, _originalFilePath); + + throw new UnauthorizedAccessException(msg); } } - handler.SslProtocols = (SslProtocols)SslProtocol; - - - HttpClient httpClient = new HttpClient(handler); - - // check timeout setting (in seconds instead of milliseconds as in HttpWebRequest) - if (TimeoutSec == 0) + // For other methods like Put where empty content has meaning, we need to fill in the content + if (request.Content is null) { - // A zero timeout means infinite - httpClient.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite); - } - else if (TimeoutSec > 0) - { - httpClient.Timeout = new TimeSpan(0, 0, TimeoutSec); - } - - return httpClient; - } + // 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; + } - internal virtual HttpRequestMessage GetRequest(Uri uri, bool stripAuthorization) - { - 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.ToString().ToUpperInvariant()); - } - break; + request.Content = new StringContent(string.Empty); + request.Content.Headers.Clear(); } - // create the base WebRequest object - var request = new HttpRequestMessage(httpMethod, requestUri); - - // pull in session data - if (WebSession.Headers.Count > 0) + foreach (KeyValuePair entry in WebSession.ContentHeaders) { - WebSession.ContentHeaders.Clear(); - foreach (var entry in WebSession.Headers) + if (!string.IsNullOrWhiteSpace(entry.Value)) { - if (HttpKnownHeaderNames.ContentHeaders.Contains(entry.Key)) + if (SkipHeaderValidation) { - WebSession.ContentHeaders.Add(entry.Key, entry.Value); + request.Content.Headers.TryAddWithoutValidation(entry.Key, entry.Value); } else { - if (stripAuthorization - && - String.Equals(entry.Key, HttpKnownHeaderNames.Authorization.ToString(), StringComparison.OrdinalIgnoreCase) - ) - { - continue; - } - - if (SkipHeaderValidation) + try { - request.Headers.TryAddWithoutValidation(entry.Key, entry.Value); + request.Content.Headers.Add(entry.Key, entry.Value); } - else + catch (FormatException ex) { - request.Headers.Add(entry.Key, entry.Value); + ValidationMetadataException outerEx = new(WebCmdletStrings.ContentTypeException, ex); + ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); + ThrowTerminatingError(er); } } } } + } - // Set 'Transfer-Encoding: chunked' if 'Transfer-Encoding' is specified - if (WebSession.Headers.ContainsKey(HttpKnownHeaderNames.TransferEncoding)) - { - request.Headers.TransferEncodingChunked = true; - } + internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool handleRedirect) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(request); - // Set 'User-Agent' if WebSession.Headers doesn't already contain it - string userAgent = null; - if (WebSession.Headers.TryGetValue(HttpKnownHeaderNames.UserAgent, out userAgent)) - { - WebSession.UserAgent = userAgent; - } - else + // Add 1 to account for the first request. + int totalRequests = WebSession.MaximumRetryCount + 1; + HttpRequestMessage currentRequest = request; + HttpResponseMessage response = null; + + do { - if (SkipHeaderValidation) + // Track the current URI being used by various requests and re-requests. + Uri currentUri = currentRequest.RequestUri; + + _cancelToken = new CancellationTokenSource(); + try { - request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.UserAgent, WebSession.UserAgent); + if (IsWriteVerboseEnabled()) + { + WriteWebRequestVerboseInfo(currentRequest); + } + + if (IsWriteDebugEnabled()) + { + WriteWebRequestDebugInfo(currentRequest); + } + + // codeql[cs/ssrf] - This is expected Poweshell behavior where user inputted Uri is supported for the context of this method. The user assumes trust for the Uri and invocation is done on the user's machine, not a web application. If there is concern for remoting, they should use restricted remoting. + response = client.SendAsync(currentRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + + if (IsWriteVerboseEnabled()) + { + WriteWebResponseVerboseInfo(response); + } + + if (IsWriteDebugEnabled()) + { + WriteWebResponseDebugInfo(response); + } } - else + catch (TaskCanceledException ex) { - request.Headers.Add(HttpKnownHeaderNames.UserAgent, WebSession.UserAgent); + 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 (_maximumRedirection > 0) + { + _maximumRedirection--; + } - // Set 'Keep-Alive' to false. This means set the Connection to 'Close'. - if (DisableKeepAlive) - { - request.Headers.Add(HttpKnownHeaderNames.Connection, "Close"); - } + // For selected redirects, GET must be used with the redirected Location. + if (RequestRequiresForceGet(response.StatusCode, currentRequest.Method) && !PreserveHttpMethodOnRedirect) + { + Method = WebRequestMethod.Get; + CustomMethod = string.Empty; + } - // Set 'Transfer-Encoding' - if (TransferEncoding != null) - { - request.Headers.TransferEncodingChunked = true; - var headerValue = new TransferCodingHeaderValue(TransferEncoding); - if (!request.Headers.TransferEncoding.Contains(headerValue)) + currentUri = new Uri(request.RequestUri, response.Headers.Location); + + // Continue to handle redirection + 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)) { - request.Headers.TransferEncoding.Add(headerValue); + _cancelToken.Cancel(); + + WriteVerbose(WebCmdletStrings.WebMethodResumeFailedVerboseMsg); + + // Disable the Resume switch so the subsequent calls to GetResponse() and FillRequestStream() + // are treated as a standard -OutFile request. This also disables appending local file. + Resume = new SwitchParameter(false); + + using (HttpRequestMessage requestWithoutRange = GetRequest(currentUri)) + { + FillRequestStream(requestWithoutRange); + + response.Dispose(); + response = GetResponse(client, requestWithoutRange, handleRedirect); + } + } + + _resumeSuccess = response.StatusCode == HttpStatusCode.PartialContent; + + // 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, + retryIntervalInSeconds, + response.StatusCode); + + WriteVerbose(retryMessage); + + _cancelToken = new CancellationTokenSource(); + Task.Delay(retryIntervalInSeconds * 1000, _cancelToken.Token).GetAwaiter().GetResult(); + _cancelToken.Cancel(); + _cancelToken = null; + + currentRequest.Dispose(); + currentRequest = GetRequest(currentUri); + FillRequestStream(currentRequest); } - } - // Some web sites (e.g. Twitter) will return exception on POST when Expect100 is sent - // Default behavior is continue to send body content anyway after a short period - // Here it send the two part as a whole. - request.Headers.ExpectContinue = false; + totalRequests--; + } + while (totalRequests > 0 && !response.IsSuccessStatusCode); - return (request); + return response; } - internal virtual void FillRequestStream(HttpRequestMessage request) + internal virtual void UpdateSession(HttpResponseMessage response) { - if (null == request) { throw new ArgumentNullException("request"); } + ArgumentNullException.ThrowIfNull(response); + } + #endregion Virtual Methods - // set the content type - if (ContentType != null) - { - WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = ContentType; - //request - } - // ContentType == null - else if (Method == WebRequestMethod.Post || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "POST")) - { - // Win8:545310 Invoke-WebRequest does not properly set MIME type for POST - string contentType = null; - WebSession.ContentHeaders.TryGetValue(HttpKnownHeaderNames.ContentType, out contentType); - if (string.IsNullOrEmpty(contentType)) - { - WebSession.ContentHeaders[HttpKnownHeaderNames.ContentType] = "application/x-www-form-urlencoded"; - } - } + #region Helper Methods +#nullable enable + internal static TimeSpan ConvertTimeoutSecondsToTimeSpan(int timeout) => timeout > 0 ? TimeSpan.FromSeconds(timeout) : Timeout.InfiniteTimeSpan; - if (null != Form) + private void WriteWebRequestVerboseInfo(HttpRequestMessage request) + { + try { - // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first - WebSession.ContentHeaders.Clear(); + // Typical Basic Example: 'WebRequest: v1.1 POST https://httpstat.us/200 with query length 6' + StringBuilder verboseBuilder = new(128); - var formData = new MultipartFormDataContent(); - foreach (DictionaryEntry formEntry in Form) + // "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) { - // AddMultipartContent will handle PSObject unwrapping, Object type determination and enumerateing top level IEnumerables. - AddMultipartContent(fieldName: formEntry.Key, fieldValue: formEntry.Value, formData: formData, enumerate: true); + verboseBuilder.Append($" with query length {request.RequestUri.Query.Length - 1}"); } - SetRequestContent(request, formData); - } - // coerce body into a usable form - else if (Body != 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) + string? requestContentType = ContentHelper.GetContentType(request); + if (requestContentType is not null) { - content = psBody.BaseObject; + verboseBuilder.Append($" with {requestContentType} payload"); } - if (content is FormObject) + long? requestContentLength = request.Content?.Headers?.ContentLength; + if (requestContentLength is not null) { - FormObject form = content as FormObject; - SetRequestContent(request, form.Fields); + verboseBuilder.Append($" with body size {ContentHelper.GetFriendlyContentLength(requestContentLength)}"); } - else if (content is IDictionary && request.Method != HttpMethod.Get) + if (OutFile is not null) { - IDictionary dictionary = content as IDictionary; - SetRequestContent(request, dictionary); + verboseBuilder.Append($" output to {QualifyFilePath(OutFile)}"); } - else if (content is XmlNode) + + 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)) { - XmlNode xmlNode = content as XmlNode; - SetRequestContent(request, xmlNode); + debugBuilder.Append(DebugHeaderPrefix).AppendLine("QUERY"); + string[] queryParams = request.RequestUri.Query.TrimStart('?').Split('&'); + debugBuilder + .AppendJoin(Environment.NewLine, queryParams) + .AppendLine() + .AppendLine(); } - else if (content is Stream) + + debugBuilder.Append(DebugHeaderPrefix).AppendLine("HEADERS"); + + foreach (var headerSet in new HttpHeaders?[] { request.Headers, request.Content?.Headers }) { - Stream stream = content as Stream; - SetRequestContent(request, stream); + if (headerSet is null) + { + continue; + } + + debugBuilder.AppendLine(headerSet.ToString()); } - else if (content is byte[]) + + if (request.Content is not null) { - byte[] bytes = content as byte[]; - SetRequestContent(request, bytes); + 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(); } - else if (content is MultipartFormDataContent multipartFormDataContent) + + WriteDebug(debugBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebRequest Debug Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebResponseVerboseInfo(HttpResponseMessage response) + { + try + { + // Typical basic example: WebResponse: 200 OK with text/plain payload body size 6 B (6 bytes) + StringBuilder verboseBuilder = new(128); + verboseBuilder.Append($"WebResponse: {(int)response.StatusCode} {response.ReasonPhrase ?? response.StatusCode.ToString()}"); + + string? responseContentType = ContentHelper.GetContentType(response); + if (responseContentType is not null) { - WebSession.ContentHeaders.Clear(); - SetRequestContent(request, multipartFormDataContent); + verboseBuilder.Append($" with {responseContentType} payload"); } - else + + long? responseContentLength = response.Content?.Headers?.ContentLength; + if (responseContentLength is not null) { - SetRequestContent(request, - (string)LanguagePrimitives.ConvertTo(content, typeof(string), CultureInfo.InvariantCulture)); + verboseBuilder.Append($" with body size {ContentHelper.GetFriendlyContentLength(responseContentLength)}"); } + + WriteVerbose(verboseBuilder.ToString().Trim()); } - else if (InFile != null) // copy InFile data + catch (Exception ex) { - try + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebResponse Verbose Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebResponseDebugInfo(HttpResponseMessage response) + { + try + { + // Typical basic example + // WebResponse Detail + // --- HEADERS + // Date: Fri, 09 May 2025 18:06:44 GMT + // Server: Kestrel + // Set-Cookie: ARRAffinity=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;Secure;Domain=httpstat.us, ARRAffinitySameSite=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;SameSite=None;Secure;Domain=httpstat.us + // Strict-Transport-Security: max-age=2592000 + // Request-Context: appId=cid-v1:3548b0f5-7f75-492f-82bb-b6eb0e864e53 + // Content-Length: 6 + // Content-Type: text/plain + // --- BODY + // 200 OK + StringBuilder debugBuilder = new("WebResponse Detail" + Environment.NewLine, 512); + + debugBuilder.Append(DebugHeaderPrefix).AppendLine("HEADERS"); + + foreach (var headerSet in new HttpHeaders?[] { response.Headers, response.Content?.Headers }) { - // open the input file - SetRequestContent(request, new FileStream(InFile, FileMode.Open)); + if (headerSet is null) + { + continue; + } + + debugBuilder.AppendLine(headerSet.ToString()); } - catch (UnauthorizedAccessException) + + if (response.Content is not null) { - string msg = string.Format(CultureInfo.InvariantCulture, WebCmdletStrings.AccessDenied, - _originalFilePath); - throw new UnauthorizedAccessException(msg); + debugBuilder.Append(DebugHeaderPrefix).AppendLine("BODY"); + + if (ContentHelper.IsTextBasedContentType(ContentHelper.GetContentType(response))) + { + debugBuilder.AppendLine( + response.Content.ReadAsStringAsync(_cancelToken.Token) + .GetAwaiter().GetResult()); + } + else + { + string friendlyContentLength = ContentHelper.GetFriendlyContentLength( + response.Content?.Headers?.ContentLength); + debugBuilder.AppendLine($"[Binary content: {friendlyContentLength}]"); + } } + + WriteDebug(debugBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebResponse Debug Info: {ex} {ex.StackTrace}"); } + } + + private Uri PrepareUri(Uri uri) + { + uri = CheckProtocol(uri); - // Add the content headers - if (request.Content != null) + // 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")) { - foreach (var entry in WebSession.ContentHeaders) + 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 { - request.Content.Headers.Add(entry.Key, entry.Value); + uriBuilder.Query = FormatDictionary(bodyAsDictionary); } + + uri = uriBuilder.Uri; + + // Set body to null to prevent later FillRequestStream + Body = null; } - } - // Returns true if the status code is one of the supported redirection codes. - static bool IsRedirectCode(HttpStatusCode code) - { - int intCode = (int) code; - return - ( - (intCode >= 300 && intCode < 304) - || - intCode == 307 - ); + return uri; } - // 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. - static bool IsRedirectToGet(HttpStatusCode code) + private static Uri CheckProtocol(Uri uri) { - return - ( - code == HttpStatusCode.Found - || - code == HttpStatusCode.Moved - || - code == HttpStatusCode.Redirect - || - code == HttpStatusCode.RedirectMethod - || - code == HttpStatusCode.TemporaryRedirect - || - code == HttpStatusCode.RedirectKeepVerb - || - code == HttpStatusCode.SeeOther - ); + ArgumentNullException.ThrowIfNull(uri); + + return uri.IsAbsoluteUri ? uri : new Uri("http://" + uri.OriginalString); } +#nullable restore - internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestMessage request, bool stripAuthorization) - { - if (client == null) { throw new ArgumentNullException("client"); } - if (request == null) { throw new ArgumentNullException("request"); } + private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); - _cancelToken = new CancellationTokenSource(); - HttpResponseMessage response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + private static string FormatDictionary(IDictionary content) + { + ArgumentNullException.ThrowIfNull(content); - if (stripAuthorization && IsRedirectCode(response.StatusCode)) + StringBuilder bodyBuilder = new(); + foreach (string key in content.Keys) { - _cancelToken.Cancel(); - _cancelToken = null; - - // if explicit count was provided, reduce it for this redirection. - if (WebSession.MaximumRedirection > 0) - { - WebSession.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)) + if (bodyBuilder.Length > 0) { - // See https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx - Method = WebRequestMethod.Get; + bodyBuilder.Append('&'); } - // recreate the HttpClient with redirection enabled since the first call suppressed redirection - using (client = GetHttpClient(false)) - using (HttpRequestMessage redirectRequest = GetRequest(response.Headers.Location, stripAuthorization:true)) - { - FillRequestStream(redirectRequest); - _cancelToken = new CancellationTokenSource(); - response = client.SendAsync(redirectRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); - } + 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 response; + + return bodyBuilder.ToString(); } - internal virtual void UpdateSession(HttpResponseMessage response) + private ErrorRecord GetValidationError(string msg, string errorId) { - if (response == null) { throw new ArgumentNullException("response"); } + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); } - #endregion Virtual Methods - - #region Overrides - - /// - /// the main execution method for cmdlets derived from WebRequestPSCmdlet. - /// - protected override void ProcessRecord() + private ErrorRecord GetValidationError(string msg, string errorId, params object[] args) { - try - { - // 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 stripAuthorization = null != WebSession - && - null != WebSession.Headers - && - !PreserveAuthorizationOnRedirect.IsPresent - && - WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization.ToString()); - - using (HttpClient client = GetHttpClient(stripAuthorization)) - { - int followedRelLink = 0; - Uri uri = Uri; - do - { - if (followedRelLink > 0) - { - string linkVerboseMsg = string.Format(CultureInfo.CurrentCulture, - WebCmdletStrings.FollowingRelLinkVerboseMsg, - uri.AbsoluteUri); - WriteVerbose(linkVerboseMsg); - } + msg = string.Format(CultureInfo.InvariantCulture, msg, args); + ValidationMetadataException ex = new(msg); + return new ErrorRecord(ex, errorId, ErrorCategory.InvalidArgument, this); + } - using (HttpRequestMessage request = GetRequest(uri, stripAuthorization:false)) - { - 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, stripAuthorization); - - string contentType = ContentHelper.GetContentType(response); - string respVerboseMsg = string.Format(CultureInfo.CurrentCulture, - WebCmdletStrings.WebResponseVerboseMsg, - response.Content.Headers.ContentLength, - contentType); - WriteVerbose(respVerboseMsg); - - if (!response.IsSuccessStatusCode) - { - 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 = ""; - 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(), "<[^>]*>",""); - } - catch (Exception) - { - // catch all - } - finally - { - if (reader != null) - { - reader.Dispose(); - } - } - if (!String.IsNullOrEmpty(detailMsg)) - { - er.ErrorDetails = new ErrorDetails(detailMsg); - } - ThrowTerminatingError(er); - } + 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)}"); + } - if (_parseRelLink || _followRelLink) - { - ParseLinkHeader(response, uri); - } - 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 (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); - } - ThrowTerminatingError(er); - } + private string GetBearerAuthorizationHeader() + { + return string.Create(CultureInfo.InvariantCulture, $"Bearer {new NetworkCredential(string.Empty, Token).Password}"); + } - if (_followRelLink) - { - if (!_relationLink.ContainsKey("next")) - { - return; - } - uri = new Uri(_relationLink["next"]); - followedRelLink++; - } - } - } - while (_followRelLink && (followedRelLink < _maximumFollowRelLink)); - } - } - catch (CryptographicException ex) + private void ProcessAuthentication() + { + if (Authentication == WebAuthenticationType.Basic) { - ErrorRecord er = new ErrorRecord(ex, "WebCmdletCertificateException", ErrorCategory.SecurityError, null); - ThrowTerminatingError(er); + WebSession.Headers["Authorization"] = GetBasicAuthorizationHeader(); } - catch (NotSupportedException ex) + else if (Authentication == WebAuthenticationType.Bearer || Authentication == WebAuthenticationType.OAuth) { - ErrorRecord er = new ErrorRecord(ex, "WebCmdletIEDomNotSupportedException", ErrorCategory.NotImplemented, null); - ThrowTerminatingError(er); + WebSession.Headers["Authorization"] = GetBearerAuthorizationHeader(); } - } - - /// - /// Implementing ^C, after start the BeginGetResponse - /// - protected override void StopProcessing() - { - if (_cancelToken != null) + else { - _cancelToken.Cancel(); + Diagnostics.Assert(false, string.Create(CultureInfo.InvariantCulture, $"Unrecognized Authentication value: {Authentication}")); } } - #endregion Overrides - - #region Helper Methods + 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 + /// 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("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); } /// /// 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 + /// 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("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. - var mediaTypeHeaderValue = new MediaTypeHeaderValue(ContentType); - if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) + try { - try + MediaTypeHeaderValue mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + if (!string.IsNullOrEmpty(mediaTypeHeaderValue.CharSet)) { encoding = Encoding.GetEncoding(mediaTypeHeaderValue.CharSet); } - catch (ArgumentException ex) + } + catch (Exception ex) when (ex is FormatException || ex is ArgumentException) + { + if (!SkipHeaderValidation) { - ErrorRecord er = new ErrorRecord(ex, "WebCmdletEncodingException", 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; + byte[] bytes = StreamHelper.EncodeToBytes(content, encoding); + 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("request"); + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(xmlNode); - if (xmlNode == null) - return 0; - - Byte[] bytes = null; + 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); } /// /// 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 + /// 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("request"); - if (contentStream == null) - throw new ArgumentNullException("contentStream"); + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(contentStream); - var streamContent = new StreamContent(contentStream); - request.Content = streamContent; - - 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 + /// 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("request"); - } - if (multipartContent == null) - { - throw new ArgumentNullException("multipartContent"); - } + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(multipartContent); - request.Content = multipartContent; + // Content headers will be set by MultipartFormDataContent which will throw unless we clear them first + WebSession.ContentHeaders.Clear(); - return multipartContent.Headers.ContentLength.Value; + request.Content = multipartContent; } - internal long SetRequestContent(HttpRequestMessage request, IDictionary content) + internal void SetRequestContent(HttpRequestMessage request, IDictionary content) { - if (request == null) - throw new ArgumentNullException("request"); - if (content == null) - throw new ArgumentNullException("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) { - _relationLink = new Dictionary(); + // Must ignore the case of relation links. See RFC 8288 (https://tools.ietf.org/html/rfc8288) + _relationLink = new Dictionary(StringComparer.OrdinalIgnoreCase); } else { _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 = "<(?.*?)>;\\srel=\"(?.*?)\""; - 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)) + if (url != string.Empty && rel != string.Empty && !_relationLink.ContainsKey(rel)) { - Uri absoluteUri = new Uri(requestUri, url); - _relationLink.Add(rel, absoluteUri.AbsoluteUri.ToString()); + Uri absoluteUri = new(requestUri, url); + _relationLink.Add(rel, absoluteUri.AbsoluteUri); } } } @@ -1624,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 (null == formData) - { - 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. @@ -1659,53 +1934,54 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF return; } - // Treat Strings and other single values as a StringContent. + // 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; } // Treat the value as a collection and enumerate it if enumeration is true - if (enumerate == true && fieldValue is IEnumerable items) + 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"); - 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"); - 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"); @@ -1713,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)); + 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 ee29d80e6f2..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,162 +1,113 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Text; using System.Net.Http; -using System.Collections.Generic; +using System.Text; +using System.Threading; namespace Microsoft.PowerShell.Commands { /// - /// WebResponseObject + /// WebResponseObject. /// - public partial class WebResponseObject + public class WebResponseObject { #region Properties /// - /// gets or protected sets the Content property + /// Gets or sets the BaseResponse property. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public byte[] Content { get; protected set; } + public HttpResponseMessage BaseResponse { get; set; } /// - /// gets the StatusCode property + /// Gets or protected sets the response body content. /// - public int StatusCode - { - get { return (WebResponseHelper.GetStatusCode(BaseResponse)); } - } + public byte[]? Content { get; protected set; } /// - /// gets the StatusDescription property + /// Gets the Headers property. /// - public string StatusDescription - { - get { return (WebResponseHelper.GetStatusDescription(BaseResponse)); } - } + public Dictionary> Headers => _headers ??= WebResponseHelper.GetHeadersDictionary(BaseResponse); + + private Dictionary>? _headers; - private MemoryStream _rawContentStream; /// - /// gets the RawContentStream property + /// Gets or protected sets the full response content. /// - public MemoryStream RawContentStream - { - get { return (_rawContentStream); } - } + /// + /// Full response content, including the HTTP status line, headers, and body. + /// + public string? RawContent { get; protected set; } /// - /// gets the RawContentLength property + /// Gets the length (in bytes) of . /// - public long RawContentLength - { - get { return (null == RawContentStream ? -1 : RawContentStream.Length); } - } + public long RawContentLength => RawContentStream is null ? -1 : RawContentStream.Length; /// - /// gets or protected sets the RawContent property + /// Gets or protected sets the response body content as a . /// - public string RawContent { get; protected set; } - - #endregion Properties - - #region Methods + public MemoryStream RawContentStream { get; protected set; } /// - /// Reads the response content from the web response. + /// Gets the RelationLink property. /// - 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 Dictionary? RelationLink { get; internal set; } /// - /// Returns the string representation of this web response. + /// Gets the response status code. /// - /// 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); - } - - #endregion Methods - } - - // TODO: Merge Partials - - /// - /// WebResponseObject - /// - public partial class WebResponseObject - { - #region Properties + 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); } @@ -165,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 (null == response) { throw new ArgumentNullException("response"); } + ArgumentNullException.ThrowIfNull(response); BaseResponse = response; - MemoryStream ms = contentStream as MemoryStream; - if (null != ms) + 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; + + // 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; + } + + 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 3718b0937c2..82e1277e00c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs @@ -1,19 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Management.Automation; -using System.Reflection; namespace Microsoft.PowerShell.Commands { /// - /// The ConvertFrom-Json command - /// This command convert a Json string representation to a JsonObject + /// The ConvertFrom-Json command. + /// This command converts a Json string representation to a JsonObject. /// - [Cmdlet(VerbsData.ConvertFrom, "Json", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217031", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsData.ConvertFrom, "Json", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096606", RemotingCapability = RemotingCapability.None)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] public class ConvertFromJsonCommand : Cmdlet { @@ -29,20 +27,40 @@ 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] + [ValidateRange(ValidateRangeKind.Positive)] + public int Depth { get; set; } = 1024; + + /// + /// Gets or sets the switch to prevent ConvertFrom-Json from unravelling collections during deserialization, instead passing them as a single + /// object through the pipeline. + /// + [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 /// - /// Buffers InputObjet contents available in the pipeline. + /// Buffers InputObjet contents available in the pipeline. /// protected override void ProcessRecord() { @@ -74,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) @@ -96,19 +114,19 @@ protected override void EndProcessing() /// /// ConvertFromJsonHelper is a helper method to convert to Json input to .Net Type. /// - /// Input String. + /// Input string. /// True if successfully converted, else returns false. private bool ConvertFromJsonHelper(string input) { ErrorRecord error = null; - object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, out error); + object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, DateKind, out error); if (error != null) { ThrowTerminatingError(error); } - WriteObject(result); + WriteObject(result, !NoEnumerate.IsPresent); return (result != 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 b5ccb144244..173d999b06d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs @@ -1,54 +1,48 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; using System.Management.Automation; -using System.Collections; -using System.Reflection; -using System.Text; -using System.Globalization; -using Dbg = System.Management.Automation; using System.Management.Automation.Internal; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Threading; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "WebCmdletStrings.resources", MessageId = "json")] -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "WebCmdletStrings.resources", MessageId = "Json")] +using Newtonsoft.Json; namespace Microsoft.PowerShell.Commands { /// - /// The ConvertTo-Json command - /// This command convert an object to a Json string representation + /// The ConvertTo-Json command. + /// This command converts an object to a Json string representation. /// - [Cmdlet(VerbsData.ConvertTo, "Json", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217032", RemotingCapability = RemotingCapability.None)] - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - public class ConvertToJsonCommand : PSCmdlet + [Cmdlet(VerbsData.ConvertTo, "Json", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096925", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(string))] + public class ConvertToJsonCommand : PSCmdlet, IDisposable { - #region parameters /// - /// gets or sets the InputObject property + /// Gets or sets the InputObject property. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] [AllowNull] public object InputObject { get; set; } private int _depth = 2; - private const int maxDepthAllowed = 100; + + private readonly CancellationTokenSource _cancellationSource = new(); /// - /// gets or sets the Depth property + /// Gets or sets the Depth property. /// [Parameter] - [ValidateRange(1, int.MaxValue)] - public int Depth { get { return _depth; } set { _depth = value; } } + [ValidateRange(0, 100)] + public int Depth + { + get { return _depth; } + set { _depth = value; } + } /// - /// gets or sets the Compress property. + /// Gets or sets the Compress property. /// If the Compress property is set to be true, the Json string will /// be output in the compressed way. Otherwise, the Json string will /// be output with indentations. @@ -57,632 +51,97 @@ public class ConvertToJsonCommand : PSCmdlet public SwitchParameter Compress { get; set; } /// - /// gets or sets the EnumsAsStrings property. + /// Gets or sets the EnumsAsStrings property. /// If the EnumsAsStrings property is set to true, enum values will /// be converted to their string equivalent. Otherwise, enum values /// will be converted to their numeric equivalent. /// - [Parameter()] + [Parameter] public SwitchParameter EnumsAsStrings { get; set; } - #endregion parameters - - #region overrides - - /// - /// Prerequisite checks - /// - protected override void BeginProcessing() - { - if (_depth > maxDepthAllowed) - { - string errorMessage = StringUtil.Format(WebCmdletStrings.ReachedMaximumDepthAllowed, maxDepthAllowed); - ThrowTerminatingError(new ErrorRecord( - new InvalidOperationException(errorMessage), - "ReachedMaximumDepthAllowed", - ErrorCategory.InvalidOperation, - null)); - } - } - - private List _inputObjects = new List(); - - /// - /// Caching the input objects for the convertto-json command - /// - protected override void ProcessRecord() - { - if (InputObject != null) - { - _inputObjects.Add(InputObject); - } - } - - /// - /// Do the conversion to json and write output - /// - protected override void EndProcessing() - { - if (_inputObjects.Count > 0) - { - object objectToProcess = (_inputObjects.Count > 1) ? (_inputObjects.ToArray() as object) : (_inputObjects[0]); - // 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. - object preprocessedObject = ProcessValue(objectToProcess, 0); - JsonSerializerSettings jsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024 }; - if (EnumsAsStrings) - { - jsonSettings.Converters.Add(new StringEnumConverter()); - } - if (!Compress) - { - jsonSettings.Formatting = Formatting.Indented; - } - string output = JsonConvert.SerializeObject(preprocessedObject, jsonSettings); - WriteObject(output); - } - } - - #endregion overrides - - #region convertOutputToPrettierFormat - - /// - /// Convert the Json string to a more readable format - /// - /// - /// - private string ConvertToPrettyJsonString(string json) - { - if (!json.StartsWith("{", StringComparison.OrdinalIgnoreCase) && !json.StartsWith("[", StringComparison.OrdinalIgnoreCase)) - { - return json; - } - - StringBuilder retStr = new StringBuilder(); - if (json.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - retStr.Append('{'); - ConvertDictionary(json, 1, retStr, "", 0); - } - else if (json.StartsWith("[", StringComparison.OrdinalIgnoreCase)) - { - retStr.Append('['); - ConvertList(json, 1, retStr, "", 0); - } - - return retStr.ToString(); - } - - /// - /// Convert a Json List, which starts with '['. - /// - /// - /// - /// - /// - /// - /// - private int ConvertList(string json, int index, StringBuilder result, string padString, int numberOfSpaces) - { - result.Append("\r\n"); - StringBuilder newPadString = new StringBuilder(); - newPadString.Append(padString); - AddSpaces(numberOfSpaces, newPadString); - AddIndentations(1, newPadString); - - bool headChar = true; - - for (int i = index; i < json.Length; i++) - { - switch (json[i]) - { - case '{': - result.Append(newPadString.ToString()); - result.Append(json[i]); - i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), 0); - headChar = false; - break; - case '[': - result.Append(newPadString.ToString()); - result.Append(json[i]); - i = ConvertList(json, i + 1, result, newPadString.ToString(), 0); - headChar = false; - break; - case ']': - result.Append("\r\n"); - result.Append(padString); - AddSpaces(numberOfSpaces, result); - result.Append(json[i]); - return i; - case '"': - if (headChar) - { - result.Append(newPadString.ToString()); - } - result.Append(json[i]); - i = ConvertQuotedString(json, i + 1, result); - headChar = false; - break; - case ',': - result.Append(json[i]); - result.Append("\r\n"); - headChar = true; - break; - default: - if (headChar) - { - result.Append(newPadString.ToString()); - } - result.Append(json[i]); - headChar = false; - break; - } - } - - Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'"); - ThrowTerminatingError(NewError()); - return -1; - } - /// - /// Convert the quoted string. + /// Gets or sets the AsArray property. + /// If the AsArray property is set to be true, the result JSON string will + /// be returned with surrounding '[', ']' chars. Otherwise, + /// the array symbols will occur only if there is more than one input object. /// - /// - /// - /// - /// - private int ConvertQuotedString(string json, int index, StringBuilder result) - { - for (int i = index; i < json.Length; i++) - { - result.Append(json[i]); - if (json[i] == '"') - { - // Ensure that the quote is not escaped by iteratively searching backwards for the backslash. - // Examples: - // "a \" b" --> here second quote is escaped - // "c:\\" --> here second quote is not escaped - // - var j = i; - var escaped = false; - while (j > 0 && json[--j] == '\\') - { - escaped = !escaped; - } - - if (!escaped) - { - return i; - } - } - } - - Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'"); - ThrowTerminatingError(NewError()); - return -1; - } + [Parameter] + public SwitchParameter AsArray { get; set; } /// - /// Convert a Json dictionary, which starts with '{'. + /// 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. /// - /// - /// - /// - /// - /// - /// - private int ConvertDictionary(string json, int index, StringBuilder result, string padString, int numberOfSpaces) - { - result.Append("\r\n"); - StringBuilder newPadString = new StringBuilder(); - newPadString.Append(padString); - AddSpaces(numberOfSpaces, newPadString); - AddIndentations(1, newPadString); - - bool headChar = true; - bool beforeQuote = true; - int newSpaceCount = 0; - const int spaceCountAfterQuoteMark = 1; - - for (int i = index; i < json.Length; i++) - { - switch (json[i]) - { - case '{': - result.Append(json[i]); - i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), newSpaceCount); - headChar = false; - break; - case '[': - result.Append(json[i]); - i = ConvertList(json, i + 1, result, newPadString.ToString(), newSpaceCount); - headChar = false; - break; - case '}': - result.Append("\r\n"); - result.Append(padString); - AddSpaces(numberOfSpaces, result); - result.Append(json[i]); - return i; - case '"': - if (headChar) - { - result.Append(newPadString.ToString()); - } - result.Append(json[i]); - int end = ConvertQuotedString(json, i + 1, result); - if (beforeQuote) - { - newSpaceCount = 0; - } - i = end; - headChar = false; - break; - case ':': - result.Append(json[i]); - AddSpaces(spaceCountAfterQuoteMark, result); - headChar = false; - beforeQuote = false; - break; - case ',': - result.Append(json[i]); - result.Append("\r\n"); - headChar = true; - beforeQuote = true; - newSpaceCount = 0; - break; - default: - if (headChar) - { - result.Append(newPadString.ToString()); - } - result.Append(json[i]); - if (beforeQuote) - { - newSpaceCount += 1; - } - headChar = false; - break; - } - } - - Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'"); - ThrowTerminatingError(NewError()); - return -1; - } + [Parameter] + public StringEscapeHandling EscapeHandling { get; set; } = StringEscapeHandling.Default; /// - /// Add tabs to result + /// IDisposable implementation, dispose of any disposable resources created by the cmdlet. /// - /// - /// - private void AddIndentations(int numberOfTabsToReturn, StringBuilder result) + public void Dispose() { - int realNumber = numberOfTabsToReturn * 2; - for (int i = 0; i < realNumber; i++) - { - result.Append(' '); - } + Dispose(disposing: true); + GC.SuppressFinalize(this); } /// - /// Add spaces to result + /// Implementation of IDisposable for both manual Dispose() and finalizer-called disposal of resources. /// - /// - /// - private void AddSpaces(int numberOfSpacesToReturn, StringBuilder result) + /// + /// Specified as true when Dispose() was called, false if this is called from the finalizer. + /// + protected virtual void Dispose(bool disposing) { - for (int i = 0; i < numberOfSpacesToReturn; i++) + if (disposing) { - result.Append(' '); + _cancellationSource.Dispose(); } } - private ErrorRecord NewError() - { - ErrorRecord errorRecord = new ErrorRecord( - new InvalidOperationException(WebCmdletStrings.JsonStringInBadFormat), - "JsonStringInBadFormat", - ErrorCategory.InvalidOperation, - InputObject); - return errorRecord; - } - - #endregion convertOutputToPrettierFormat - - /// - /// 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. - /// - /// Primitive types are returned verbatim. Aggregate types are processed recursively. - /// - /// The object to be processed - /// The current depth into the object graph - /// An object suitable for serializing to JSON - private object ProcessValue(object obj, int depth) - { - PSObject pso = obj as PSObject; - - if (pso != null) - obj = pso.BaseObject; - - Object rv = obj; - bool isPurePSObj = false; - bool isCustomObj = false; - - if (obj == null - || DBNull.Value.Equals(obj) - || obj is string - || obj is char - || obj is bool - || obj is DateTime - || obj is DateTimeOffset - || obj is Guid - || obj is Uri - || obj is double - || obj is float - || obj is decimal) - { - rv = obj; - } - else if (obj is Newtonsoft.Json.Linq.JObject jObject) - { - rv = jObject.ToObject>(); - } - else - { - TypeInfo t = obj.GetType().GetTypeInfo(); - - if (t.IsPrimitive) - { - rv = obj; - } - else if (t.IsEnum) - { - // 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))) - { - rv = obj.ToString(); - } - else - { - rv = obj; - } - } - else - { - if (depth > Depth) - { - if (pso != null && pso.immediateBaseObjectIsEmpty) - { - // The obj is a pure PSObject, we convert the original PSObject to a string, - // instead of its base object in this case - rv = LanguagePrimitives.ConvertTo(pso, typeof(string), - CultureInfo.InvariantCulture); - isPurePSObj = true; - } - else - { - rv = LanguagePrimitives.ConvertTo(obj, typeof(String), - CultureInfo.InvariantCulture); - } - } - else - { - IDictionary dict = obj as IDictionary; - if (dict != null) - { - rv = ProcessDictionary(dict, depth); - } - else - { - IEnumerable enumerable = obj as IEnumerable; - if (enumerable != null) - { - rv = ProcessEnumerable(enumerable, depth); - } - else - { - rv = ProcessCustomObject(obj, depth); - isCustomObj = true; - } - } - } - } - } - - rv = AddPsProperties(pso, rv, depth, isPurePSObj, isCustomObj); - - return rv; - } + private readonly List _inputObjects = new(); /// - /// Add to a base object any properties that might have been added to an object (via PSObject) through the Add-Member cmdlet. + /// Caching the input objects for the command. /// - /// The containing PSObject, or null if the base object was not contained in a PSObject - /// The base object that might have been decorated with additional properties - /// The current depth into the object graph - /// the processed object is a pure PSObject - /// the processed object is a custom object - /// - /// The original base object if no additional properties had been added, - /// otherwise a dictionary containing the value of the original base object in the "value" key - /// as well as the names and values of an additional properties. - /// - private object AddPsProperties(object psobj, object obj, int depth, bool isPurePSObj, bool isCustomObj) + protected override void ProcessRecord() { - PSObject pso = psobj as PSObject; - - if (pso == null) - return obj; - - // when isPurePSObj is true, the obj is guaranteed to be a string converted by LanguagePrimitives - if (isPurePSObj) - return obj; - - bool wasDictionary = true; - IDictionary dict = obj as IDictionary; - - if (dict == null) - { - wasDictionary = false; - dict = new Dictionary(); - dict.Add("value", obj); - } - - AppendPsProperties(pso, dict, depth, isCustomObj); - - if (wasDictionary == false && dict.Count == 1) - return obj; - - return dict; + _inputObjects.Add(InputObject); } /// - /// Append to a dictionary any properties that might have been added to an object (via PSObject) through the Add-Member cmdlet. - /// If the passed in object is a custom object (not a simple object, not a dictionary, not a list, get processed in ProcessCustomObject method), - /// we also take Adapted properties into account. Otherwise, we only consider the Extended properties. - /// When the object is a pure PSObject, it also gets processed in "ProcessCustomObject" before reaching this method, so we will - /// iterate both extended and adapted properties for it. Since it's a pure PSObject, there will be no adapted properties. + /// Do the conversion to json and write output. /// - /// The containing PSObject, or null if the base object was not contained in a PSObject - /// The dictionary to which any additional properties will be appended - /// The current depth into the object graph - /// The processed object is a custom object - private void AppendPsProperties(PSObject psobj, IDictionary receiver, int depth, bool isCustomObject) + protected override void EndProcessing() { - // serialize only Extended and Adapted properties.. - PSMemberInfoCollection srcPropertiesToSearch = - new PSMemberInfoIntegratingCollection(psobj, - isCustomObject ? PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted) : - PSObject.GetPropertyCollection(PSMemberViewTypes.Extended)); - - foreach (PSPropertyInfo prop in srcPropertiesToSearch) + if (_inputObjects.Count > 0) { - object value = null; - try - { - value = prop.Value; - } - catch (Exception) - { - } - - if (!receiver.Contains(prop.Name)) - { - receiver[prop.Name] = ProcessValue(value, depth + 1); - } - } - } + object objectToProcess = (_inputObjects.Count > 1 || AsArray) ? (_inputObjects.ToArray() as object) : _inputObjects[0]; - /// - /// Return an alternate representation of the specified dictionary that serializes the same JSON, except - /// that any contained properties that cannot be evaluated are treated as having the value null. - /// - /// - /// - /// - private object ProcessDictionary(IDictionary dict, int depth) - { - Dictionary result = new Dictionary(dict.Count); + var context = new JsonObject.ConvertToJsonContext( + Depth, + EnumsAsStrings.IsPresent, + Compress.IsPresent, + EscapeHandling, + targetCmdlet: this, + _cancellationSource.Token); - foreach (DictionaryEntry entry in dict) - { - string name = entry.Key as string; - if (name == null) + // 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. + string output = JsonObject.ConvertToJson(objectToProcess, in context); + if (output != null) { - // use the error string that matches the message from JavaScriptSerializer - var exception = - new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - WebCmdletStrings.NonStringKeyInDictionary, - dict.GetType().FullName)); - ThrowTerminatingError(new ErrorRecord(exception, "NonStringKeyInDictionary", ErrorCategory.InvalidOperation, dict)); + WriteObject(output); } - - result.Add(name, ProcessValue(entry.Value, depth + 1)); - } - - return result; - } - - /// - /// Return an alternate representation of the specified collection that serializes the same JSON, except - /// that any contained properties that cannot be evaluated are treated as having the value null. - /// - /// - /// - /// - private object ProcessEnumerable(IEnumerable enumerable, int depth) - { - List result = new List(); - - foreach (object o in enumerable) - { - result.Add(ProcessValue(o, depth + 1)); } - - return result; } /// - /// Return an alternate representation of the specified aggregate object that serializes the same JSON, except - /// that any contained properties that cannot be evaluated are treated as having the value null. - /// - /// The result is a dictionary in which all public fields and public gettable properties of the original object - /// are represented. If any exception occurs while retrieving the value of a field or property, that entity - /// is included in the output dictionary with a value of null. + /// Process the Ctrl+C signal. /// - /// - /// - /// - private object ProcessCustomObject(object o, int depth) + protected override void StopProcessing() { - Dictionary result = new Dictionary(); - Type t = o.GetType(); - - foreach (FieldInfo info in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) - { - if (!info.IsDefined(typeof(T), true)) - { - object value; - try - { - value = info.GetValue(o); - } - catch (Exception) - { - value = null; - } - - result.Add(info.Name, ProcessValue(value, depth + 1)); - } - } - - foreach (PropertyInfo info2 in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (!info2.IsDefined(typeof(T), true)) - { - MethodInfo getMethod = info2.GetGetMethod(); - if ((getMethod != null) && (getMethod.GetParameters().Length <= 0)) - { - object value; - try - { - value = getMethod.Invoke(o, new object[0]); - } - catch (Exception) - { - value = null; - } - - result.Add(info2.Name, ProcessValue(value, depth + 1)); - } - } - } - return result; + _cancellationSource.Cancel(); } } } 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 944df03eb4e..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,25 +1,34 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; using System.Collections.Generic; namespace Microsoft.PowerShell.Commands { - internal static partial class HttpKnownHeaderNames + 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"; @@ -30,6 +39,7 @@ internal static partial 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"; @@ -46,6 +56,7 @@ internal static partial 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"; @@ -54,6 +65,7 @@ internal static partial 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"; @@ -65,44 +77,49 @@ internal static partial 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; - internal static HashSet ContentHeaders - { - get - { - if (s_contentHeaderSet == null) - { - s_contentHeaderSet = new HashSet(StringComparer.OrdinalIgnoreCase); + private static readonly HashSet 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); - } + static HttpKnownHeaderNames() + { + // 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 f7d0c831845..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,29 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.IO; +using System.Threading; namespace Microsoft.PowerShell.Commands { /// - /// The Invoke-RestMethod command + /// The Invoke-WebRequest command. /// 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=217035", DefaultParameterSetName = "StandardMethod")] + [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; } /// @@ -32,18 +35,27 @@ public InvokeWebRequestCommand() : base() /// internal override void ProcessResponse(HttpResponseMessage response) { - if (null == response) { throw new ArgumentNullException("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; @@ -52,7 +64,11 @@ internal override void ProcessResponse(HttpResponseMessage response) if (ShouldSaveToOutFile) { - StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this); + 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 256db1888f5..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. All rights reserved. ---********************************************************************/ +// 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("address"); - } + ArgumentNullException.ThrowIfNull(address); _proxyAddress = address; } - public ICredentials Credentials - { - get { return _credentials; } - set { _credentials = value; } - } + public override bool Equals(object? obj) => Equals(obj as WebProxy); - internal bool BypassProxyOnLocal - { - get; set; - } + public override int GetHashCode() => HashCode.Combine(_proxyAddress, _credentials, BypassProxyOnLocal); - internal bool UseDefaultCredentials + public bool Equals(WebProxy? other) { - get - { - return _credentials == CredentialCache.DefaultCredentials; - } - set + if (other is null) { - _credentials = value ? CredentialCache.DefaultCredentials : null; + return false; } + + // _proxyAddress cannot be null as it is set in the constructor + return other._credentials == _credentials + && _proxyAddress.Equals(other._proxyAddress) + && BypassProxyOnLocal == other.BypassProxyOnLocal; } - public Uri GetProxy(Uri destination) + public ICredentials? Credentials { - if (destination == null) - { - throw new ArgumentNullException("destination"); - } + get => _credentials; - if (destination.IsLoopback) - { - return destination; - } + set => _credentials = value; + } - return _proxyAddress; + internal bool BypassProxyOnLocal { get; set; } + internal bool UseDefaultCredentials + { + get => _credentials == CredentialCache.DefaultCredentials; + + set => _credentials = value ? CredentialCache.DefaultCredentials : null; } - public bool IsBypassed(Uri host) + public Uri GetProxy(Uri destination) { - return host.IsLoopback; + ArgumentNullException.ThrowIfNull(destination); + + return destination.IsLoopback ? destination : _proxyAddress; } + + 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 0949b343176..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,21 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; using System.Collections.Generic; -using System.Net.Http; using System.Globalization; +using System.IO; +using System.Net.Http; namespace Microsoft.PowerShell.Commands { - internal static partial class WebResponseHelper + 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) { @@ -28,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) { @@ -39,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 2623da9ea32..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObjectFactory.CoreClr.cs +++ /dev/null @@ -1,28 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Net.Http; -using System.IO; -using System.Management.Automation; - -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 fcb17ce1753..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,38 +1,39 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System.Collections.Generic; namespace Microsoft.PowerShell.Commands { /// - /// FormObject used in HtmlWebResponseObject + /// FormObject used in HtmlWebResponseObject. /// 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. /// /// /// @@ -47,8 +48,7 @@ public FormObject(string id, string method, string action) internal void AddField(string key, string value) { - string test; - if (null != key && !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 23e9129caf2..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,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; using System.Collections.ObjectModel; @@ -8,20 +9,20 @@ namespace Microsoft.PowerShell.Commands { /// - /// FormObjectCollection used in HtmlWebResponseObject + /// FormObjectCollection used in HtmlWebResponseObject. /// public class FormObjectCollection : Collection { /// - /// Gets the FormObject from the key + /// Gets the FormObject from the key. /// /// /// - 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 +31,8 @@ public FormObject this[string key] break; } } - 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 f06514ab4af..6506f2bd2ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -1,131 +1,258 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Management.Automation; -using System.Text.RegularExpressions; +using System.Management.Automation.Language; +using System.Numerics; +using System.Reflection; +using System.Threading; + using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; -using System.Collections; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Management.Automation.Internal; -using System.Reflection; namespace Microsoft.PowerShell.Commands { /// - /// JsonObject class + /// JsonObject class. /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")] public static class JsonObject { - private const int maxDepthAllowed = 100; + #region HelperTypes + + /// + /// Context for convert-to-json operation. + /// + public readonly struct ConvertToJsonContext + { + /// + /// Gets the maximum depth for walking the object graph. + /// + public readonly int MaxDepth; + + /// + /// Gets the cancellation token. + /// + public readonly CancellationToken CancellationToken; + + /// + /// Gets the StringEscapeHandling setting. + /// + public readonly StringEscapeHandling StringEscapeHandling; + + /// + /// Gets the EnumsAsStrings setting. + /// + public readonly bool EnumsAsStrings; + + /// + /// Gets the CompressOutput setting. + /// + public readonly bool CompressOutput; + + /// + /// Gets the target cmdlet that is doing the convert-to-json operation. + /// + public readonly PSCmdlet Cmdlet; + + /// + /// Initializes a new instance of the struct. + /// + /// The maximum depth to visit the object. + /// 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, StringEscapeHandling.Default, targetCmdlet: null, CancellationToken.None) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// 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 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, + StringEscapeHandling stringEscapeHandling, + PSCmdlet targetCmdlet, + CancellationToken cancellationToken) + { + this.MaxDepth = maxDepth; + this.CancellationToken = cancellationToken; + this.StringEscapeHandling = stringEscapeHandling; + this.EnumsAsStrings = enumsAsStrings; + this.CompressOutput = compressOutput; + this.Cmdlet = targetCmdlet; + } + } + + private sealed class DuplicateMemberHashSet : HashSet + { + public DuplicateMemberHashSet(int capacity) + : base(capacity, StringComparer.OrdinalIgnoreCase) + { + } + } + + #endregion HelperTypes + + #region ConvertFromJson /// /// Convert a Json string back to an object of type PSObject. /// - /// - /// + /// The json text to convert. + /// An error record if the conversion failed. /// A PSObject. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")] public static object ConvertFromJson(string input, out ErrorRecord error) { - return ConvertFromJson(input, false, out error); + return ConvertFromJson(input, returnHashtable: false, out error); } /// - /// Convert a Json string back to an object of type PSObject or Hashtable depending on parameter . + /// Convert a Json string back to an object of type or + /// depending on parameter . /// - /// - /// - /// - /// A PSObject or a Hashtable if the parameter is true. - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - public static object ConvertFromJson(string input, bool returnHashTable, out ErrorRecord error) + /// The json text to convert. + /// True if the result should be returned as a + /// instead of a + /// 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")] + public static object ConvertFromJson(string input, bool returnHashtable, out ErrorRecord error) { - if (input == null) + return ConvertFromJson(input, returnHashtable, maxDepth: 1024, 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. + /// 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")] + 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) + { + ArgumentNullException.ThrowIfNull(input); + + DateParseHandling dateParseHandling; + DateTimeZoneHandling dateTimeZoneHandling; + switch (jsonDateKind) { - throw new ArgumentNullException("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; - object obj = 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/1321. - // 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, - obj = JsonConvert.DeserializeObject(input, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024 }); + // This TypeNameHandling setting is required to be secure. + TypeNameHandling = TypeNameHandling.None, + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + MaxDepth = maxDepth + }); - // JObject is a IDictionary - var dictionary = obj as JObject; - if (dictionary != null) + switch (obj) { - if (returnHashTable) - { - obj = PopulateHashTableFromJDictionary(dictionary, out error); - } - else - { - obj = PopulateFromJDictionary(dictionary, out error); - } - } - else - { - // JArray is a collection - var list = obj as JArray; - if (list != null) - { - if (returnHashTable) - { - obj = PopulateHashTableFromJArray(list, out error); - } - else - { - obj = PopulateFromJArray(list, out error); - } - } + case JObject dictionary: + // JObject is a IDictionary + return returnHashtable + ? PopulateHashTableFromJDictionary(dictionary, out error) + : PopulateFromJDictionary(dictionary, new DuplicateMemberHashSet(dictionary.Count), out error); + case JArray list: + return returnHashtable + ? PopulateHashTableFromJArray(list, out error) + : PopulateFromJArray(list, out error); + default: + return obj; } } catch (JsonException je) { var msg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.JsonDeserializationFailed, je.Message); + // the same as JavaScriptSerializer does throw new ArgumentException(msg, je); } - return obj; } // This function is a clone of PopulateFromDictionary using JObject as an input. - private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord error) + private static PSObject PopulateFromJDictionary(JObject entries, DuplicateMemberHashSet memberHashTracker, out ErrorRecord error) { error = null; - PSObject result = new PSObject(); + var result = new PSObject(entries.Count); foreach (var entry in entries) { if (string.IsNullOrEmpty(entry.Key)) { - string errorMsg = string.Format(CultureInfo.InvariantCulture, - WebCmdletStrings.EmptyKeyInJsonString); + var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.EmptyKeyInJsonString); error = new ErrorRecord( new InvalidOperationException(errorMsg), "EmptyKeyInJsonString", @@ -136,10 +263,10 @@ private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord // Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject // does not throw when encountering duplicates and just uses the last entry. - if (result.Properties.Any(psPropertyInfo => psPropertyInfo.Name.Equals(entry.Key, StringComparison.InvariantCulture))) + if (memberHashTracker.TryGetValue(entry.Key, out var maybePropertyName) + && string.Equals(entry.Key, maybePropertyName, StringComparison.Ordinal)) { - string errorMsg = string.Format(CultureInfo.InvariantCulture, - WebCmdletStrings.DuplicateKeysInJsonString, entry.Key); + var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.DuplicateKeysInJsonString, entry.Key); error = new ErrorRecord( new InvalidOperationException(errorMsg), "DuplicateKeysInJsonString", @@ -150,11 +277,9 @@ private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord // Compare case insensitive to tell the user to use the -AsHashTable option instead. // This is because PSObject cannot have keys with different casing. - PSPropertyInfo property = result.Properties[entry.Key]; - if (property != null) + if (memberHashTracker.TryGetValue(entry.Key, out var propertyName)) { - string errorMsg = string.Format(CultureInfo.InvariantCulture, - WebCmdletStrings.KeysWithDifferentCasingInJsonString, property.Name, entry.Key); + var errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.KeysWithDifferentCasingInJsonString, propertyName, entry.Key); error = new ErrorRecord( new InvalidOperationException(errorMsg), "KeysWithDifferentCasingInJsonString", @@ -163,37 +288,42 @@ private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord return null; } - // Array - else if (entry.Value is JArray) + switch (entry.Value) { - JArray list = entry.Value as JArray; - ICollection listResult = PopulateFromJArray(list, out error); - if (error != null) - { - return null; - } - result.Properties.Add(new PSNoteProperty(entry.Key, listResult)); - } + case JArray list: + { + // Array + var listResult = PopulateFromJArray(list, out error); + if (error != null) + { + return null; + } - // Dictionary - else if (entry.Value is JObject) - { - JObject dic = entry.Value as JObject; - PSObject dicResult = PopulateFromJDictionary(dic, out error); - if (error != null) - { - return null; - } - result.Properties.Add(new PSNoteProperty(entry.Key, dicResult)); - } + result.Properties.Add(new PSNoteProperty(entry.Key, listResult)); + break; + } + case JObject dic: + { + // Dictionary + var dicResult = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); + if (error != null) + { + return null; + } - // Value - else // (entry.Value is JValue) - { - JValue theValue = entry.Value as JValue; - result.Properties.Add(new PSNoteProperty(entry.Key, theValue.Value)); + result.Properties.Add(new PSNoteProperty(entry.Key, dicResult)); + break; + } + case JValue value: + { + result.Properties.Add(new PSNoteProperty(entry.Key, value.Value)); + break; + } } + + memberHashTracker.Add(entry.Key); } + return result; } @@ -201,56 +331,59 @@ private static PSObject PopulateFromJDictionary(JObject entries, out ErrorRecord private static ICollection PopulateFromJArray(JArray list, out ErrorRecord error) { error = null; - List result = new List(); + var result = new object[list.Count]; + var i = 0; foreach (var element in list) { - // Array - if (element is JArray) + switch (element) { - JArray subList = element as JArray; - ICollection listResult = PopulateFromJArray(subList, out error); - if (error != null) - { - return null; - } - result.Add(listResult); - } + case JArray subList: + // Array + result[i++] = PopulateFromJArray(subList, out error); + if (error != null) + { + return null; + } - // Dictionary - else if (element is JObject) - { - JObject dic = element as JObject; - PSObject dicResult = PopulateFromJDictionary(dic, out error); - if (error != null) - { - return null; - } - result.Add(dicResult); - } + break; - // Value - else // (element is JValue) - { - result.Add(((JValue)element).Value); + case JObject dic: + // Dictionary + result[i++] = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); + if (error != null) + { + return null; + } + + break; + + case JValue value: + if (value.Type != JTokenType.Comment) + { + result[i++] = value.Value; + } + + break; } } - return result.ToArray(); + + // 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(); + OrderedHashtable result = new(entries.Count); foreach (var entry in entries) { // Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject // does not throw when encountering duplicates and just uses the last entry. if (result.ContainsKey(entry.Key)) { - string errorMsg = string.Format(CultureInfo.InvariantCulture, - WebCmdletStrings.DuplicateKeysInJsonString, entry.Key); + string errorMsg = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.DuplicateKeysInJsonString, entry.Key); error = new ErrorRecord( new InvalidOperationException(errorMsg), "DuplicateKeysInJsonString", @@ -259,79 +392,459 @@ private static Hashtable PopulateHashTableFromJDictionary(JObject entries, out E return null; } - // Array - else if (entry.Value is JArray) + switch (entry.Value) + { + case JArray list: + { + // Array + var listResult = PopulateHashTableFromJArray(list, out error); + if (error != null) + { + return null; + } + + result.Add(entry.Key, listResult); + break; + } + case JObject dic: + { + // Dictionary + var dicResult = PopulateHashTableFromJDictionary(dic, out error); + if (error != null) + { + return null; + } + + result.Add(entry.Key, dicResult); + break; + } + case JValue value: + { + result.Add(entry.Key, value.Value); + break; + } + } + } + + return result; + } + + // This function is a clone of PopulateFromList using JArray as input. + private static ICollection PopulateHashTableFromJArray(JArray list, out ErrorRecord error) + { + error = null; + var result = new object[list.Count]; + var i = 0; + + foreach (var element in list) + { + switch (element) + { + case JArray subList: + // Array + result[i++] = PopulateHashTableFromJArray(subList, out error); + if (error != null) + { + return null; + } + + break; + + case JObject dic: + // Dictionary + result[i++] = PopulateHashTableFromJDictionary(dic, out error); + if (error != null) + { + return null; + } + + break; + + case JValue value: + if (value.Type != JTokenType.Comment) + { + result[i++] = value.Value; + } + + break; + } + } + + // 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 + + #region ConvertToJson + + /// + /// Convert an object to JSON string. + /// + public static string ConvertToJson(object objectToProcess, in ConvertToJsonContext context) + { + try + { + // 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 + { + // This TypeNameHandling setting is required to be secure. + TypeNameHandling = TypeNameHandling.None, + MaxDepth = 1024, + StringEscapeHandling = context.StringEscapeHandling + }; + + if (context.EnumsAsStrings) + { + jsonSettings.Converters.Add(new StringEnumConverter()); + } + + if (!context.CompressOutput) { - JArray list = entry.Value as JArray; - ICollection listResult = PopulateHashTableFromJArray(list, out error); - if (error != null) + jsonSettings.Formatting = Formatting.Indented; + } + + return JsonConvert.SerializeObject(preprocessedObject, jsonSettings); + } + catch (OperationCanceledException) + { + return null; + } + } + + 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. + /// Primitive types are returned verbatim. Aggregate types are processed recursively. + /// + /// The object to be processed. + /// The current depth into the object graph. + /// The context to use for the convert-to-json operation. + /// An object suitable for serializing to JSON. + private static object ProcessValue(object obj, int currentDepth, in ConvertToJsonContext context) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + if (LanguagePrimitives.IsNull(obj)) + { + return null; + } + + PSObject pso = obj as PSObject; + + if (pso != null) + { + obj = pso.BaseObject; + } + + object rv = obj; + bool isPurePSObj = false; + bool isCustomObj = false; + + if (obj == NullString.Value + || obj == DBNull.Value) + { + rv = null; + } + else if (obj is string + || obj is char + || obj is bool + || obj is DateTime + || obj is DateTimeOffset + || obj is Guid + || obj is Uri + || obj is double + || obj is float + || obj is decimal + || obj is BigInteger) + { + rv = obj; + } + else if (obj is Newtonsoft.Json.Linq.JObject jObject) + { + rv = jObject.ToObject>(); + } + else + { + Type t = obj.GetType(); + + if (t.IsPrimitive || (t.IsEnum && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSSerializeJSONLongEnumAsNumber))) + { + rv = obj; + } + else if (t.IsEnum) + { + // 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(long)) || enumUnderlyingType.Equals(typeof(ulong))) { - return null; + rv = obj.ToString(); + } + else + { + rv = obj; } - result.Add(entry.Key, listResult); } - - // Dictionary - else if (entry.Value is JObject) + else { - JObject dic = entry.Value as JObject; - Hashtable dicResult = PopulateHashTableFromJDictionary(dic, out error); - if (error != null) + if (currentDepth > context.MaxDepth) { - return null; + 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, + // instead of its base object in this case + rv = LanguagePrimitives.ConvertTo(pso, typeof(string), + CultureInfo.InvariantCulture); + isPurePSObj = true; + } + else + { + rv = LanguagePrimitives.ConvertTo(obj, typeof(string), + CultureInfo.InvariantCulture); + } } - result.Add(entry.Key, dicResult); + else + { + if (obj is IDictionary dict) + { + rv = ProcessDictionary(dict, currentDepth, in context); + } + else + { + if (obj is IEnumerable enumerable) + { + rv = ProcessEnumerable(enumerable, currentDepth, in context); + } + else + { + rv = ProcessCustomObject(obj, currentDepth, in context); + isCustomObj = true; + } + } + } + } + } + + rv = AddPsProperties(pso, rv, currentDepth, isPurePSObj, isCustomObj, in context); + + return rv; + } + + /// + /// Add to a base object any properties that might have been added to an object (via PSObject) through the Add-Member cmdlet. + /// + /// The containing PSObject, or null if the base object was not contained in a PSObject. + /// The base object that might have been decorated with additional properties. + /// The current depth into the object graph. + /// The processed object is a pure PSObject. + /// The processed object is a custom object. + /// The context for the operation. + /// + /// The original base object if no additional properties had been added, + /// otherwise a dictionary containing the value of the original base object in the "value" key + /// as well as the names and values of an additional properties. + /// + private static object AddPsProperties(object psObj, object obj, int depth, bool isPurePSObj, bool isCustomObj, in ConvertToJsonContext context) + { + if (psObj is not PSObject pso) + { + return obj; + } + + // when isPurePSObj is true, the obj is guaranteed to be a string converted by LanguagePrimitives + if (isPurePSObj) + { + return obj; + } + + bool wasDictionary = true; + + if (obj is not IDictionary dict) + { + wasDictionary = false; + dict = new Dictionary(); + dict.Add("value", obj); + } + + AppendPsProperties(pso, dict, depth, isCustomObj, in context); + + if (!wasDictionary && dict.Count == 1) + { + return obj; + } + + return dict; + } + + /// + /// Append to a dictionary any properties that might have been added to an object (via PSObject) through the Add-Member cmdlet. + /// If the passed in object is a custom object (not a simple object, not a dictionary, not a list, get processed in ProcessCustomObject method), + /// we also take Adapted properties into account. Otherwise, we only consider the Extended properties. + /// When the object is a pure PSObject, it also gets processed in "ProcessCustomObject" before reaching this method, so we will + /// iterate both extended and adapted properties for it. Since it's a pure PSObject, there will be no adapted properties. + /// + /// The containing PSObject, or null if the base object was not contained in a PSObject. + /// The dictionary to which any additional properties will be appended. + /// The current depth into the object graph. + /// The processed object is a custom object. + /// 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, + isCustomObject ? PSObject.GetPropertyCollection(PSMemberViewTypes.Extended | PSMemberViewTypes.Adapted) : + PSObject.GetPropertyCollection(PSMemberViewTypes.Extended)); + + foreach (PSPropertyInfo prop in srcPropertiesToSearch) + { + object value = null; + try + { + value = prop.Value; + } + catch (Exception) + { } - // Value - else // (entry.Value is JValue) + if (!receiver.Contains(prop.Name)) { - JValue theValue = entry.Value as JValue; - result.Add(entry.Key, theValue.Value); + receiver[prop.Name] = ProcessValue(value, depth + 1, in context); } } - return result; } - // This function is a clone of PopulateFromList using JArray as input. - private static ICollection PopulateHashTableFromJArray(JArray list, out ErrorRecord error) + /// + /// Return an alternate representation of the specified dictionary that serializes the same JSON, except + /// that any contained properties that cannot be evaluated are treated as having the value null. + /// + private static object ProcessDictionary(IDictionary dict, int depth, in ConvertToJsonContext context) { - error = null; - List result = new List(); + Dictionary result = new(dict.Count); - foreach (var element in list) + foreach (DictionaryEntry entry in dict) { - // Array - if (element is JArray) + string name = entry.Key as string; + if (name == null) { - JArray subList = element as JArray; - ICollection listResult = PopulateHashTableFromJArray(subList, out error); - if (error != null) + // use the error string that matches the message from JavaScriptSerializer + string errorMsg = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.NonStringKeyInDictionary, + dict.GetType().FullName); + + var exception = new InvalidOperationException(errorMsg); + if (context.Cmdlet != null) + { + var errorRecord = new ErrorRecord(exception, "NonStringKeyInDictionary", ErrorCategory.InvalidOperation, dict); + context.Cmdlet.ThrowTerminatingError(errorRecord); + } + else { - return null; + throw exception; } - result.Add(listResult); } - // Dictionary - else if (element is JObject) + result.Add(name, ProcessValue(entry.Value, depth + 1, in context)); + } + + return result; + } + + /// + /// Return an alternate representation of the specified collection that serializes the same JSON, except + /// that any contained properties that cannot be evaluated are treated as having the value null. + /// + private static object ProcessEnumerable(IEnumerable enumerable, int depth, in ConvertToJsonContext context) + { + List result = new(); + + foreach (object o in enumerable) + { + result.Add(ProcessValue(o, depth + 1, in context)); + } + + return result; + } + + /// + /// Return an alternate representation of the specified aggregate object that serializes the same JSON, except + /// that any contained properties that cannot be evaluated are treated as having the value null. + /// + /// The result is a dictionary in which all public fields and public gettable properties of the original object + /// are represented. If any exception occurs while retrieving the value of a field or property, that entity + /// is included in the output dictionary with a value of null. + /// + private static object ProcessCustomObject(object o, int depth, in ConvertToJsonContext context) + { + Dictionary result = new(); + Type t = o.GetType(); + + foreach (FieldInfo info in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if (!info.IsDefined(typeof(T), true)) { - JObject dic = element as JObject; - Hashtable dicResult = PopulateHashTableFromJDictionary(dic, out error); - if (error != null) + object value; + try { - return null; + value = info.GetValue(o); } - result.Add(dicResult); + catch (Exception) + { + value = null; + } + + result.Add(info.Name, ProcessValue(value, depth + 1, in context)); } + } - // Value - else // (element is JValue) + foreach (PropertyInfo info2 in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!info2.IsDefined(typeof(T), true)) { - result.Add(((JValue)element).Value); + MethodInfo getMethod = info2.GetGetMethod(); + if ((getMethod != null) && (getMethod.GetParameters().Length == 0)) + { + object value; + try + { + value = getMethod.Invoke(o, Array.Empty()); + } + catch (Exception) + { + value = null; + } + + result.Add(info2.Name, ProcessValue(value, depth + 1, in context)); + } } } - return result.ToArray(); + + return result; } + + #endregion ConvertToJson } } 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 7fd144c3e34..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,127 +1,54 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; +using System.Globalization; using System.Management.Automation; using System.Runtime.InteropServices; -using System.Globalization; using System.Text.RegularExpressions; namespace Microsoft.PowerShell.Commands { /// - /// Construct the Useragent string + /// Construct the Useragent string. /// 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) + /// 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) + /// 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) + /// 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) + /// 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) + /// 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 { @@ -129,12 +56,13 @@ internal static string PlatformName { if (Platform.IsWindows) { - // only generate the windows user agent once - if(s_windowsUserAgent == null){ - // find the version in the windows operating system description - Regex pattern = new Regex(@"\d+(\.\d+)+"); + // Only generate the windows user agent once + if (s_windowsUserAgent is null) + { + // 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}"; } @@ -150,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; + 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 7664652cb6c..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,15 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; -using System.Text; -using System.Text.RegularExpressions; +using System.Buffers; using System.IO; -using System.IO.Compression; using System.Management.Automation; using System.Management.Automation.Internal; using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.PowerShell.Commands { @@ -19,77 +22,52 @@ 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. /// - /// - /// - /// Owner cmdlet if any - internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet) - : base(initialCapacity) + /// Response stream. + /// Presize the memory stream. + /// Owner cmdlet if any. + /// 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 CanTimeout - { - get - { - return base.CanTimeout; - } - } + public override bool CanSeek => true; /// - /// /// - public override bool CanWrite - { - get - { - return true; - } - } + public override bool CanWrite => true; /// - /// /// public override long Length { @@ -101,20 +79,18 @@ public override long Length } /// - /// /// /// /// /// /// - public override System.Threading.Tasks.Task CopyToAsync(Stream destination, int bufferSize, System.Threading.CancellationToken cancellationToken) + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.CopyToAsync(destination, bufferSize, cancellationToken); } /// - /// /// /// /// @@ -127,21 +103,19 @@ public override int Read(byte[] buffer, int offset, int count) } /// - /// /// /// /// /// /// /// - public override System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.ReadAsync(buffer, offset, count, cancellationToken); } /// - /// /// /// public override int ReadByte() @@ -151,7 +125,6 @@ public override int ReadByte() } /// - /// /// /// public override void SetLength(long value) @@ -161,7 +134,6 @@ public override void SetLength(long value) } /// - /// /// /// public override byte[] ToArray() @@ -171,7 +143,6 @@ public override byte[] ToArray() } /// - /// /// /// /// @@ -183,21 +154,19 @@ public override void Write(byte[] buffer, int offset, int count) } /// - /// /// /// /// /// /// /// - public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - Initialize(); + Initialize(cancellationToken); return base.WriteAsync(buffer, offset, count, cancellationToken); } /// - /// /// /// public override void WriteByte(byte value) @@ -207,7 +176,6 @@ public override void WriteByte(byte value) } /// - /// /// /// public override void WriteTo(Stream stream) @@ -217,30 +185,45 @@ public override void WriteTo(Stream stream) } /// - /// /// 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 (null != _ownerCmdlet) + 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) @@ -249,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 @@ -284,75 +345,84 @@ 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) + internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, long? contentLength, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - byte[] data = new byte[ChunkSize]; + ArgumentNullException.ThrowIfNull(cmdlet); - int read = 0; - long totalWritten = 0; - do - { - if (cmdlet != null) - { - ProgressRecord record = new ProgressRecord(ActivityId, - WebCmdletStrings.WriteRequestProgressActivity, - StringUtil.Format(WebCmdletStrings.WriteRequestProgressStatus, totalWritten)); - cmdlet.WriteProgress(record); - } + Task copyTask = input.CopyToAsync(output, perReadTimeout, cancellationToken); - read = input.Read(data, 0, ChunkSize); + bool wroteProgress = false; + ProgressRecord record = new( + ActivityId, + WebCmdletStrings.WriteRequestProgressActivity, + WebCmdletStrings.WriteRequestProgressStatus); + string totalDownloadSize = contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)contentLength); - if (0 < read) + try + { + while (!copyTask.Wait(1000, cancellationToken)) { - output.Write(data, 0, read); - totalWritten += read; - } - } while (read != 0); + record.StatusDescription = StringUtil.Format( + WebCmdletStrings.WriteRequestProgressStatus, + Utils.DisplayHumanReadableFileSize(output.Position), + totalDownloadSize); + if (contentLength > 0) + { + record.PercentComplete = Math.Min((int)(output.Position * 100 / (long)contentLength), 100); + } - if (cmdlet != null) + cmdlet.WriteProgress(record); + wroteProgress = true; + } + } + catch (OperationCanceledException) { - ProgressRecord record = new ProgressRecord(ActivityId, - WebCmdletStrings.WriteRequestProgressActivity, - StringUtil.Format(WebCmdletStrings.WriteRequestComplete, totalWritten)); - record.RecordType = ProgressRecordType.Completed; - cmdlet.WriteProgress(record); } - - output.Flush(); - } - - internal static void WriteToStream(byte[] input, Stream output) - { - output.Write(input, 0, input.Length); - output.Flush(); + 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); + } + } } /// /// Saves content from stream into filePath. /// Caller need to ensure position is properly set. /// - /// - /// - /// - internal static void SaveStreamToFile(Stream stream, string filePath, 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, long? contentLength, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - using (FileStream output = File.Create(filePath)) - { - WriteToStream(stream, output, cmdlet); - } + // If the web cmdlet should resume, append the file instead of overwriting. + FileMode fileMode = cmdlet is WebRequestPSCmdlet webCmdlet && webCmdlet.ShouldResume ? FileMode.Append : FileMode.Create; + using FileStream output = new(filePath, fileMode, FileAccess.Write, FileShare.Read); + WriteToStream(stream, output, cmdlet, 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; @@ -361,141 +431,127 @@ 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; + while (!completed) + { + // 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(); - } - - internal static string DecodeStream(Stream stream, string characterSet, out Encoding encoding) - { - try - { - encoding = Encoding.GetEncoding(characterSet); + return result.ToString(); } - catch (ArgumentException) + finally { - encoding = null; + ArrayPool.Shared.Return(chars); + ArrayPool.Shared.Return(bytes); } - return DecodeStream(stream, ref encoding); } - internal static bool TryGetEncoding(string characterSet, out Encoding encoding) + internal static string DecodeStream(Stream stream, string? characterSet, out Encoding encoding, TimeSpan perReadTimeout, CancellationToken cancellationToken) { - bool result = false; - try - { - encoding = Encoding.GetEncoding(characterSet); - result = true; - } - catch (ArgumentException) + bool isDefaultEncoding = !TryGetEncoding(characterSet, out encoding); + + string content = StreamToString(stream, encoding, perReadTimeout, cancellationToken); + if (isDefaultEncoding) { - encoding = null; - } - return result; - } + // 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)); - static readonly Regex s_metaexp = new Regex(@"<]*charset\s*=\s*[""'\n]?(?[A-Za-z].[^\s""'\n<>]*)[\s""'\n>]"); + // Check for a charset attribute on the meta element to override the default + Match match = s_metaRegex.Match(substring); - internal static string DecodeStream(Stream stream, ref Encoding encoding) - { - bool isDefaultEncoding = false; - if (null == encoding) - { - // Use the default encoding if one wasn't provided - encoding = ContentHelper.GetDefaultEncoding(); - isDefaultEncoding = true; - } + // Check for a encoding attribute on the xml declaration to override the default + if (!match.Success) + { + match = s_xmlRegex.Match(substring); + } - 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; + characterSet = match.Groups["charset"].Value; - if (TryGetEncoding(characterSet, out localEncoding)) + if (TryGetEncoding(characterSet, out Encoding localEncoding)) { stream.Seek(0, SeekOrigin.Begin); - content = StreamToString(stream, localEncoding); - // report the encoding used. + content = StreamToString(stream, localEncoding, perReadTimeout, cancellationToken); encoding = localEncoding; } } - } while (false); + } return content; } - internal static Byte[] EncodeToBytes(String str, Encoding encoding) + internal static bool TryGetEncoding(string? characterSet, out Encoding encoding) { - if (null == encoding) + bool result = false; + try + { + encoding = Encoding.GetEncoding(characterSet!); + result = true; + } + catch (ArgumentException) { - // just use the default encoding if one wasn't provided + // Use the default encoding if one wasn't provided encoding = ContentHelper.GetDefaultEncoding(); } - return encoding.GetBytes(str); + return result; } - internal static Byte[] EncodeToBytes(String str) - { - return EncodeToBytes(str, null); - } + 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.NonBacktracking + ); - internal static Stream GetResponseStream(HttpResponseMessage response) - { - Stream responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); - var contentEncoding = response.Content.Headers.ContentEncoding; + 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 + ); - // 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); - } - } + internal static byte[] EncodeToBytes(string str, Encoding encoding) + { + // Just use the default encoding if one wasn't provided + encoding ??= ContentHelper.GetDefaultEncoding(); - return responseStream; + return encoding.GetBytes(str); } + 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 83044fabc9c..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,57 +1,45 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#nullable enable + +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Collections.Generic; namespace Microsoft.PowerShell.Commands { /// - /// WebCmdletElementCollection for elements in html web responses + /// WebCmdletElementCollection for elements in html web responses. /// public class WebCmdletElementCollection : ReadOnlyCollection { - internal WebCmdletElementCollection(IList list) - : base(list) + internal WebCmdletElementCollection(IList list) : base(list) { } /// - /// Finds the element with name or id + /// Finds the element with name or id. /// /// - /// - public PSObject Find(string nameOrId) - { - // try Id first - PSObject result = FindById(nameOrId) ?? FindByName(nameOrId); - - return (result); - } + /// Found element as PSObject. + public PSObject? Find(string nameOrId) => FindById(nameOrId) ?? FindByName(nameOrId); /// - /// Finds the element by id + /// Finds the element by id. /// /// - /// - public PSObject FindById(string id) - { - return Find(id, true); - } + /// Found element as PSObject. + public PSObject? FindById(string id) => Find(id, findById: true); /// - /// Finds the element by name + /// Finds the element by name. /// /// - /// - public PSObject FindByName(string name) - { - return Find(name, false); - } + /// Found element as PSObject. + 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 11afdf24b19..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,61 +1,62 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable namespace Microsoft.PowerShell.Commands { /// - /// enums for web request method. + /// Enums for web request method. /// public enum WebRequestMethod { /// - /// Default method + /// Default method. /// Default, /// - /// GET method + /// GET method. /// Get, /// - /// HEAD method + /// HEAD method. /// Head, /// - /// POST method + /// POST method. /// Post, /// - /// PUT method + /// PUT method. /// Put, /// - /// DELETE method + /// DELETE method. /// Delete, /// - /// TRACE method + /// TRACE method. /// Trace, /// - /// OPTIONS method + /// OPTIONS method. /// Options, /// - /// MERGE method + /// MERGE method. /// Merge, /// - /// PATCH method + /// PATCH method. /// Patch, } 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 b55639acd77..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,92 +1,163 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; -using System.Net; 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 + /// Gets or sets the Header property. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Dictionary Headers { get; set; } /// - /// gets or sets the content Headers when using HttpClient + /// Gets or sets the content Headers when using HttpClient. /// internal Dictionary ContentHeaders { get; set; } /// - /// gets or sets the Cookies property + /// 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 + /// 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 + /// 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 + /// 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 + /// Gets or sets the UserAgent property. /// public string UserAgent { get; set; } /// - /// gets or sets the Proxy property + /// Gets or sets the Proxy property. + /// + public IWebProxy? Proxy + { + get => _proxy; + set + { + SetClassVar(ref _proxy, value); + if (_proxy is not null) + { + NoProxy = false; + } + } + } + + /// + /// Gets or sets the MaximumRedirection property. + /// + public int MaximumRedirection { get => _maximumRedirection; set => SetStructVar(ref _maximumRedirection, value); } + + /// + /// Gets or sets the count of retries for request failures. /// - public IWebProxy Proxy { get; set; } + public int MaximumRetryCount { get; set; } /// - /// gets or sets the RedirectMax property + /// Gets or sets the interval in seconds between retries. /// - public int MaximumRedirection { get; set; } + 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; + } + } } /// @@ -95,11 +166,150 @@ public WebRequestSession() /// The certificate to be added. internal void AddCertificate(X509Certificate certificate) { - if (null == Certificates) + 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) + { + 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); + }; + } + + 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) { - Certificates = new X509CertificateCollection(); + if (disposing) + { + _client?.Dispose(); + } + + _disposed = true; } - Certificates.Add(certificate); + } + + /// + /// Dispose the WebRequestSession. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write-Object.cs index 2ac76d2c63a..2d7fc9d233d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write-Object.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; @@ -8,56 +7,38 @@ namespace Microsoft.PowerShell.Commands { #region WriteOutputCommand /// - /// This class implements Write-output command - /// + /// This class implements Write-Output command. /// - [Cmdlet(VerbsCommunications.Write, "Output", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113427", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsCommunications.Write, "Output", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097117", RemotingCapability = RemotingCapability.None)] public sealed class WriteOutputCommand : PSCmdlet { - private PSObject[] _inputObjects = null; - /// - /// Holds the list of objects to be Written + /// Holds the list of objects to be written. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromRemainingArguments = true)] [AllowNull] [AllowEmptyCollection] - public PSObject[] InputObject - { - get { return _inputObjects; } - set { _inputObjects = value; } - } + public PSObject InputObject { get; set; } /// - /// Prevents Write-Output from unravelling collections passed to the InputObject - /// parameter. + /// Prevents Write-Output from unravelling collections passed to the InputObject parameter. /// - [Parameter()] - public SwitchParameter NoEnumerate - { - get; - set; - } + [Parameter] + public SwitchParameter NoEnumerate { get; set; } /// - /// This method implements the ProcessRecord method for Write-output command + /// This method implements the ProcessRecord method for Write-output command. /// protected override void ProcessRecord() { - if (null == _inputObjects) + if (InputObject == null) { - WriteObject(_inputObjects); + WriteObject(InputObject); return; } - bool enumerate = true; - if (NoEnumerate.IsPresent) - { - enumerate = false; - } - - WriteObject(_inputObjects, enumerate); - }//processrecord - }//WriteOutputCommand + WriteObject(InputObject, !NoEnumerate.IsPresent); + } + } #endregion } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs new file mode 100644 index 00000000000..d20d7d8712b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs @@ -0,0 +1,463 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Runtime.Serialization; + +namespace Microsoft.PowerShell.Commands +{ + #region WriteDebugCommand + /// + /// This class implements Write-Debug command. + /// + [Cmdlet(VerbsCommunications.Write, "Debug", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097132", RemotingCapability = RemotingCapability.None)] + public sealed class WriteDebugCommand : PSCmdlet + { + /// + /// Message to be sent and processed if debug mode is on. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [AllowEmptyString] + [Alias("Msg")] + public string Message { get; set; } + + /// + /// This method implements the ProcessRecord method for Write-Debug command. + /// + protected override void ProcessRecord() + { + // + // The write-debug command must use the script's InvocationInfo rather than its own, + // 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(). + // + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) + { + DebugRecord record = new(Message); + + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) + { + record.SetInvocationInfo(invocationInfo); + } + + mshCommandRuntime.WriteDebug(record); + } + else + { + WriteDebug(Message); + } + } + } + #endregion WriteDebugCommand + + #region WriteVerboseCommand + /// + /// This class implements Write-Verbose command. + /// + [Cmdlet(VerbsCommunications.Write, "Verbose", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097043", RemotingCapability = RemotingCapability.None)] + public sealed class WriteVerboseCommand : PSCmdlet + { + /// + /// Message to be sent if verbose messages are being shown. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [AllowEmptyString] + [Alias("Msg")] + public string Message { get; set; } + + /// + /// This method implements the ProcessRecord method for Write-verbose command. + /// + protected override void ProcessRecord() + { + // + // The write-verbose command must use the script's InvocationInfo rather than its own, + // 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(). + // + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) + { + VerboseRecord record = new(Message); + + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) + { + record.SetInvocationInfo(invocationInfo); + } + + mshCommandRuntime.WriteVerbose(record); + } + else + { + WriteVerbose(Message); + } + } + } + #endregion WriteVerboseCommand + + #region WriteWarningCommand + /// + /// This class implements Write-Warning command. + /// + [Cmdlet(VerbsCommunications.Write, "Warning", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097044", RemotingCapability = RemotingCapability.None)] + public sealed class WriteWarningCommand : PSCmdlet + { + /// + /// Message to be sent if warning messages are being shown. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [AllowEmptyString] + [Alias("Msg")] + public string Message { get; set; } + + /// + /// This method implements the ProcessRecord method for Write-Warning command. + /// + protected override void ProcessRecord() + { + // + // The write-warning command must use the script's InvocationInfo rather than its own, + // 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(). + // + if (this.CommandRuntime is MshCommandRuntime mshCommandRuntime) + { + WarningRecord record = new(Message); + + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) + { + record.SetInvocationInfo(invocationInfo); + } + + mshCommandRuntime.WriteWarning(record); + } + else + { + WriteWarning(Message); + } + } + } + #endregion WriteWarningCommand + + #region WriteInformationCommand + /// + /// This class implements Write-Information command. + /// + [Cmdlet(VerbsCommunications.Write, "Information", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097040", RemotingCapability = RemotingCapability.None)] + public sealed class WriteInformationCommand : PSCmdlet + { + /// + /// Object to be sent to the Information stream. + /// + [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] + [Alias("Msg", "Message")] + [AllowNull] + public object MessageData { get; set; } + + /// + /// Any tags to be associated with this information. + /// + [Parameter(Position = 1)] + public string[] Tags { get; set; } + + /// + /// This method implements the processing of the Write-Information command. + /// + protected override void BeginProcessing() + { + if (Tags != null) + { + foreach (string tag in Tags) + { + if (tag.StartsWith("PS", StringComparison.OrdinalIgnoreCase)) + { + ErrorRecord er = new( + new InvalidOperationException(StringUtil.Format(UtilityCommonStrings.PSPrefixReservedInInformationTag, tag)), + "PSPrefixReservedInInformationTag", ErrorCategory.InvalidArgument, tag); + ThrowTerminatingError(er); + } + } + } + } + + /// + /// This method implements the ProcessRecord method for Write-Information command. + /// + protected override void ProcessRecord() + { + WriteInformation(MessageData, Tags); + } + } + + #endregion WriteInformationCommand + + #region WriteOrThrowErrorCommand + + /// + /// This class implements the Write-Error command. + /// + public class WriteOrThrowErrorCommand : PSCmdlet + { + /// + /// ErrorRecord.Exception -- if not specified, ErrorRecord.Exception is System.Exception. + /// + [Parameter(Position = 0, ParameterSetName = "WithException", Mandatory = true)] + public Exception Exception { get; set; } + + /// + /// If Exception is specified, this is ErrorRecord.ErrorDetails.Message; + /// otherwise, the Exception is System.Exception, and this is Exception.Message. + /// + [Parameter(Position = 0, ParameterSetName = "NoException", Mandatory = true, ValueFromPipeline = true)] + [Parameter(ParameterSetName = "WithException")] + [AllowNull] + [AllowEmptyString] + [Alias("Msg")] + 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(Position = 0, ParameterSetName = "ErrorRecord", Mandatory = true)] + public ErrorRecord ErrorRecord { get; set; } + + /// + /// ErrorRecord.CategoryInfo.Category. + /// + [Parameter(ParameterSetName = "NoException")] + [Parameter(ParameterSetName = "WithException")] + public ErrorCategory Category { get; set; } = ErrorCategory.NotSpecified; + + /// + /// ErrorRecord.ErrorId. + /// + [Parameter(ParameterSetName = "NoException")] + [Parameter(ParameterSetName = "WithException")] + public string ErrorId { get; set; } = string.Empty; + + /// + /// ErrorRecord.TargetObject. + /// + [Parameter(ParameterSetName = "NoException")] + [Parameter(ParameterSetName = "WithException")] + public object TargetObject { get; set; } + + /// + /// ErrorRecord.ErrorDetails.RecommendedAction. + /// + [Parameter] + public string RecommendedAction { get; set; } = string.Empty; + + /* 2005/01/25 removing throw-error + /// + /// If true, this is throw-error. Otherwise, this is write-error. + /// + internal bool _terminating = false; + */ + + /// + /// ErrorRecord.CategoryInfo.Activity. + /// + [Parameter] + [Alias("Activity")] + public string CategoryActivity { get; set; } = string.Empty; + + /// + /// ErrorRecord.CategoryInfo.Reason. + /// + [Parameter] + [Alias("Reason")] + public string CategoryReason { get; set; } = string.Empty; + + /// + /// ErrorRecord.CategoryInfo.TargetName. + /// + [Parameter] + [Alias("TargetName")] + public string CategoryTargetName { get; set; } = string.Empty; + + /// + /// ErrorRecord.CategoryInfo.TargetType. + /// + [Parameter] + [Alias("TargetType")] + public string CategoryTargetType { get; set; } = string.Empty; + + /// + /// Write an error to the output pipe, or throw a terminating error. + /// + protected override void ProcessRecord() + { + ErrorRecord errorRecord = this.ErrorRecord; + if (errorRecord != null) + { + // copy constructor + errorRecord = new ErrorRecord(errorRecord, null); + } + else + { + Exception e = this.Exception; + string msg = Message; + e ??= new WriteErrorException(msg); + + string errid = ErrorId; + if (string.IsNullOrEmpty(errid)) + { + errid = e.GetType().FullName; + } + + errorRecord = new ErrorRecord( + e, + errid, + Category, + TargetObject + ); + + if (this.Exception != null && !string.IsNullOrEmpty(msg)) + { + errorRecord.ErrorDetails = new ErrorDetails(msg); + } + } + + string recact = RecommendedAction; + if (!string.IsNullOrEmpty(recact)) + { + errorRecord.ErrorDetails ??= new ErrorDetails(errorRecord.ToString()); + + errorRecord.ErrorDetails.RecommendedAction = recact; + } + + if (!string.IsNullOrEmpty(CategoryActivity)) + errorRecord.CategoryInfo.Activity = CategoryActivity; + if (!string.IsNullOrEmpty(CategoryReason)) + errorRecord.CategoryInfo.Reason = CategoryReason; + if (!string.IsNullOrEmpty(CategoryTargetName)) + errorRecord.CategoryInfo.TargetName = CategoryTargetName; + if (!string.IsNullOrEmpty(CategoryTargetType)) + errorRecord.CategoryInfo.TargetType = CategoryTargetType; + + /* 2005/01/25 removing throw-error + if (_terminating) + { + ThrowTerminatingError(errorRecord); + } + else + { + */ + + // 2005/07/14-913791 "write-error output is confusing and misleading" + // set InvocationInfo to the script not the command + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo myInvocation) + { + errorRecord.SetInvocationInfo(myInvocation); + errorRecord.PreserveInvocationInfoOnce = true; + if (!string.IsNullOrEmpty(CategoryActivity)) + errorRecord.CategoryInfo.Activity = CategoryActivity; + else + errorRecord.CategoryInfo.Activity = "Write-Error"; + } + + WriteError(errorRecord); + /* + } + */ + } + } + + /// + /// This class implements Write-Error command. + /// + [Cmdlet(VerbsCommunications.Write, "Error", DefaultParameterSetName = "NoException", + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097039", RemotingCapability = RemotingCapability.None)] + public sealed class WriteErrorCommand : WriteOrThrowErrorCommand + { + /// + /// Initializes a new instance of the class. + /// + public WriteErrorCommand() + { + } + } + + /* 2005/01/25 removing throw-error + /// + /// This class implements Write-Error command. + /// + [Cmdlet("Throw", "Error", DefaultParameterSetName = "NoException")] + public sealed class ThrowErrorCommand : WriteOrThrowErrorCommand + { + /// + /// Constructor. + /// + public ThrowErrorCommand() + { + using (tracer.TraceConstructor(this)) + { + _terminating = true; + } + } + } + */ + + #endregion WriteOrThrowErrorCommand + + #region WriteErrorException + /// + /// The write-error cmdlet uses WriteErrorException + /// when the user only specifies a string and not + /// an Exception or ErrorRecord. + /// + public class WriteErrorException : SystemException + { + #region ctor + /// + /// Initializes a new instance of the class. + /// + /// Constructed object. + public WriteErrorException() + : base(StringUtil.Format(WriteErrorStrings.WriteErrorException)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Constructed object. + public WriteErrorException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// Constructed object. + public WriteErrorException(string message, + Exception innerException) + : base(message, innerException) + { + } + #endregion ctor + + #region Serialization + /// + /// 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) + { + throw new NotSupportedException(); + } + #endregion Serialization + } + #endregion WriteErrorException +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs index 9e195d3e30f..31670935fcf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs @@ -1,56 +1,46 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; using System.Management.Automation; -using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { /// - /// The base class for the SetAliasCommand and NewAliasCommand + /// The base class for the SetAliasCommand and NewAliasCommand. /// - /// public class WriteAliasCommandBase : PSCmdlet { #region Parameters /// - /// The Name parameter for the command + /// The Name parameter for the command. /// - /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string Name { get; set; } /// - /// The Value parameter for the command + /// The Value parameter for the command. /// - /// [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] public string Value { get; set; } /// /// The description for the alias. /// - /// [Parameter] - public string Description { get; set; } = String.Empty; + public string Description { get; set; } = string.Empty; /// /// The Option parameter allows the alias to be set to /// ReadOnly (for existing aliases) and/or Constant (only /// for new aliases). /// - /// [Parameter] public ScopedItemOptions Option { get; set; } = ScopedItemOptions.None; /// - /// If set to true, the alias that is set is passed to the - /// pipeline. + /// If set to true, the alias that is set is passed to the pipeline. /// - /// [Parameter] public SwitchParameter PassThru { @@ -64,22 +54,20 @@ public SwitchParameter PassThru _passThru = value; } } + private bool _passThru; /// - /// The scope parameter for the command determines - /// which scope the alias is set in. + /// The scope parameter for the command determines which scope the alias is set in. /// - /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } - /// /// If set to true and an existing alias of the same name exists /// and is ReadOnly, the alias will be overwritten. /// - /// [Parameter] public SwitchParameter Force { @@ -93,9 +81,8 @@ public SwitchParameter Force _force = value; } } + private bool _force; #endregion Parameters - - } // class WriteAliasCommandBase -}//Microsoft.PowerShell.Commands - + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs index c53dc13623d..48d84636ce2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs @@ -1,45 +1,29 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; -using System.Management.Automation; using System.Collections; +using System.Management.Automation; using System.Text; +using System.Xml; namespace Microsoft.PowerShell.Commands { /// - /// - /// Class comment - /// + /// WriteHost cmdlet. /// - - [Cmdlet(VerbsCommunications.Write, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113426", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsCommunications.Write, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097137", RemotingCapability = RemotingCapability.None)] public sealed class WriteHostCommand : ConsoleColorCmdlet { - // - // Parameters - // - - - /// - /// /// Object to be output. - /// /// - [Parameter(Position = 0, ValueFromRemainingArguments = true, ValueFromPipeline = true)] - public object Object { get; set; } = null; - + [Alias("Msg", "Message")] + public object Object { get; set; } /// - /// /// False to add a newline to the end of the output string, true if not. - /// /// - [Parameter] public SwitchParameter NoNewline { @@ -47,36 +31,28 @@ public SwitchParameter NoNewline { return _notAppendNewline; } + set { _notAppendNewline = value; } } - - /// - /// - /// The separator to print between objects - /// + /// Gets and sets the separator to print between objects. /// /// - [Parameter] public object Separator { get; set; } = " "; - // // Cmdlet Overrides // - 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) @@ -84,16 +60,20 @@ 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) { - if (printSeparator == true && Separator != null) + if (printSeparator && Separator != null) { result.Append(Separator.ToString()); } @@ -118,18 +98,14 @@ private string ProcessObject(object o) return null; } - - /// - /// - /// Outputs the object to the host console, with optional newline - /// + /// Outputs the object to the host console, with optional newline. /// protected override void ProcessRecord() { - string result = ProcessObject(Object) ?? ""; + string result = ProcessObject(Object) ?? string.Empty; - HostInformationMessage informationMessage = new HostInformationMessage(); + HostInformationMessage informationMessage = new(); informationMessage.Message = result; informationMessage.NoNewLine = NoNewline.IsPresent; @@ -145,9 +121,8 @@ protected override void ProcessRecord() } this.WriteInformation(informationMessage, new string[] { "PSHOST" }); - this.Host.UI.TranscribeResult(result); } - private Boolean _notAppendNewline = false; + private bool _notAppendNewline = false; } -} // namespace Microsoft.PowerShell.Commands \ No newline at end of file +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs index d9936e14229..0751954c54e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs @@ -1,48 +1,29 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; -using Dbg = System.Management.Automation.Diagnostics; - - - namespace Microsoft.PowerShell.Commands { /// - /// - /// Implements the write-progress cmdlet - /// + /// Implements the write-progress cmdlet. /// - - [Cmdlet(VerbsCommunications.Write, "Progress", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113428", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsCommunications.Write, "Progress", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097036", RemotingCapability = RemotingCapability.None)] public sealed class WriteProgressCommand : PSCmdlet { /// - /// /// Describes the activity for which progress is being reported. - /// /// - /// - [Parameter( Position = 0, - Mandatory = true, HelpMessageBaseName = HelpMessageBaseName, HelpMessageResourceId = "ActivityParameterHelpMessage")] public string Activity { get; set; } - /// - /// /// Describes the current state of the activity. - /// /// - /// - [Parameter( Position = 1, HelpMessageBaseName = HelpMessageBaseName, @@ -50,73 +31,43 @@ public sealed class WriteProgressCommand : PSCmdlet [ValidateNotNullOrEmpty] public string Status { get; set; } = WriteProgressResourceStrings.Processing; - /// - /// /// 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 - /// + /// Percentage completion of the activity, or -1 if n/a. /// - /// - [Parameter] [ValidateRange(-1, 100)] public int PercentComplete { get; set; } = -1; - /// - /// - /// Seconds remaining to complete the operation, or -1 if n/a - /// + /// Seconds remaining to complete the operation, or -1 if n/a. /// - /// - [Parameter] public int SecondsRemaining { get; set; } = -1; - /// - /// - /// Description of current operation in activity, empty if n/a - /// + /// Description of current operation in activity, empty if n/a. /// - /// - [Parameter] public string CurrentOperation { get; set; } - /// - /// /// 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; - /// - /// /// Identifies whether the activity has completed (and the display for it should be removed), /// or if it is proceeding (and the display for it should be shown). - /// /// - /// - [Parameter] public SwitchParameter Completed { @@ -124,36 +75,49 @@ public SwitchParameter Completed { return _completed; } + set { _completed = value; } } - - /// - /// /// Identifies the source of the record. - /// /// - /// - [Parameter] public int SourceId { get; set; } - /// - /// /// Writes a ProgressRecord created from the parameters. - /// /// - 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; @@ -163,11 +127,8 @@ protected override WriteProgress(SourceId, pr); } - private bool _completed; - private const string HelpMessageBaseName = "WriteProgressResourceStrings"; } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs index 33845a3f23e..cc3b1f2b251 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs @@ -1,12 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation; @@ -19,9 +17,9 @@ namespace Microsoft.PowerShell.Commands { /// - /// implementation for the Export-Clixml command + /// Implementation for the Export-Clixml command. /// - [Cmdlet(VerbsData.Export, "Clixml", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113297")] + [Cmdlet(VerbsData.Export, "Clixml", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096926")] public sealed class ExportClixmlCommand : PSCmdlet, IDisposable { #region Command Line Parameters @@ -30,39 +28,41 @@ public sealed class ExportClixmlCommand : PSCmdlet, IDisposable // implementation will need to be modified. /// - /// Depth of serialization + /// Depth of serialization. /// [Parameter] [ValidateRange(1, int.MaxValue)] - public int Depth { get; set; } = 0; + public int Depth { get; set; } /// - /// mandatory file name to write to + /// Mandatory file name to write to. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ByPath")] public string Path { get; set; } /// - /// mandatory file name to write to + /// Mandatory file name to write to. /// [Parameter(Mandatory = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Alias("PSPath", "LP")] public string LiteralPath { get { return Path; } + set { Path = value; _isLiteralPath = true; } } + private bool _isLiteralPath = false; /// - /// Input object to be exported + /// Input object to be exported. /// [Parameter(ValueFromPipeline = true, Mandatory = true)] [AllowNull] @@ -78,11 +78,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; /// @@ -96,39 +98,44 @@ public SwitchParameter NoClobber { return _noclobber; } + set { _noclobber = value; } } + private bool _noclobber; /// - /// Encoding optional flag + /// Encoding optional flag. /// - /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentCompletions( - EncodingConversion.Ascii, - EncodingConversion.BigEndianUnicode, - EncodingConversion.OEM, - EncodingConversion.Unicode, - EncodingConversion.Utf7, - EncodingConversion.Utf8, - EncodingConversion.Utf8Bom, - EncodingConversion.Utf8NoBom, - EncodingConversion.Utf32 - )] + [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 #region Overrides /// - /// BeginProcessing override + /// BeginProcessing override. /// protected override void @@ -137,15 +144,13 @@ protected override CreateFileStream(); } - /// - /// /// protected override void ProcessRecord() { - if (null != _serializer) + if (_serializer != null) { _serializer.Serialize(InputObject); _xw.Flush(); @@ -153,7 +158,6 @@ protected override } /// - /// /// protected override void @@ -164,11 +168,11 @@ protected override _serializer.Done(); _serializer = null; } + CleanUp(); } /// - /// /// protected override void StopProcessing() { @@ -181,22 +185,22 @@ protected override void StopProcessing() #region file /// - /// handle to file stream + /// Handle to file stream. /// private FileStream _fs; /// - /// stream writer used to write to file + /// Stream writer used to write to file. /// private XmlWriter _xw; /// - /// Serializer used for serialization + /// Serializer used for serialization. /// private Serializer _serializer; /// - /// FileInfo of file to clear read-only flag when operation is complete + /// FileInfo of file to clear read-only flag when operation is complete. /// private FileInfo _readOnlyFileInfo = null; @@ -204,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( @@ -222,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; @@ -249,6 +256,7 @@ private void CreateFileStream() _xw.Dispose(); _xw = null; } + _fs.Dispose(); _fs = null; } @@ -259,21 +267,22 @@ private void CreateFileStream() #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } + _disposed = true; } @@ -281,39 +290,39 @@ private void CreateFileStream() } /// - /// Implements Import-Clixml command + /// Implements Import-Clixml command. /// - [Cmdlet(VerbsData.Import, "Clixml", SupportsPaging = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113340")] + [Cmdlet(VerbsData.Import, "Clixml", SupportsPaging = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096618")] public sealed class ImportClixmlCommand : PSCmdlet, IDisposable { #region Command Line Parameters /// - /// mandatory file name to read from + /// Mandatory file name to read from. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByPath")] - public String[] Path { get; set; } + public string[] Path { get; set; } /// - /// mandatory file name to read from + /// Mandatory file name to read from. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] LiteralPath + [Alias("PSPath", "LP")] + public string[] LiteralPath { get { return Path; } + set { Path = value; _isLiteralPath = true; } } - private bool _isLiteralPath = false; + private bool _isLiteralPath = false; #endregion Command Line Parameters @@ -322,18 +331,18 @@ 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(); _helper = null; } + _disposed = true; } } @@ -343,7 +352,7 @@ public void Dispose() private ImportXmlHelper _helper; /// - /// ProcessRecord overload + /// ProcessRecord overload. /// protected override void ProcessRecord() { @@ -358,7 +367,6 @@ protected override void ProcessRecord() } /// - /// /// protected override void StopProcessing() { @@ -367,34 +375,30 @@ protected override void StopProcessing() } } - /// - /// implementation for the convertto-xml command + /// Implementation for the convertto-xml command. /// [Cmdlet(VerbsData.ConvertTo, "Xml", SupportsShouldProcess = false, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135204", RemotingCapability = RemotingCapability.None)] - [OutputType(typeof(XmlDocument), typeof(String))] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096603", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(XmlDocument), typeof(string))] public sealed class ConvertToXmlCommand : PSCmdlet, IDisposable { #region Command Line Parameters - /// - /// Depth of serialization + /// Depth of serialization. /// [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 + /// Input Object which is written to XML format. /// [Parameter(Position = 0, ValueFromPipeline = true, Mandatory = true)] [AllowNull] public PSObject InputObject { get; set; } - /// /// Property that sets NoTypeInformation parameter. /// @@ -405,11 +409,13 @@ public SwitchParameter NoTypeInformation { return _notypeinformation; } + set { _notypeinformation = value; } } + private bool _notypeinformation; /// @@ -422,11 +428,10 @@ public SwitchParameter NoTypeInformation #endregion Command Line Parameters - #region Overrides /// - /// BeginProcessing override + /// BeginProcessing override. /// protected override void BeginProcessing() { @@ -436,14 +441,13 @@ protected override void BeginProcessing() } else { - WriteObject(string.Format(CultureInfo.InvariantCulture, "", Encoding.UTF8.WebName)); + WriteObject(string.Create(CultureInfo.InvariantCulture, $"")); WriteObject(""); } } - /// - /// override ProcessRecord + /// Override ProcessRecord. /// protected override void ProcessRecord() { @@ -451,37 +455,33 @@ protected override void ProcessRecord() { CreateMemoryStream(); - if (null != _serializer) - _serializer.SerializeAsStream(InputObject); + _serializer?.SerializeAsStream(InputObject); - - if (null != _serializer) + if (_serializer != null) { _serializer.DoneAsStream(); _serializer = null; } - //Loading to the XML Document + // Loading to the XML Document _ms.Position = 0; - StreamReader read = new StreamReader(_ms); + StreamReader read = new(_ms); string data = read.ReadToEnd(); WriteObject(data); - //Cleanup + // Cleanup CleanUp(); } else { - if (null != _serializer) - _serializer.Serialize(InputObject); + _serializer?.Serialize(InputObject); } } /// - /// /// protected override void EndProcessing() { - if (null != _serializer) + if (_serializer != null) { _serializer.Done(); _serializer = null; @@ -493,29 +493,29 @@ protected override void EndProcessing() } else { - //Loading to the XML Document + // Loading to the XML Document _ms.Position = 0; 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); } } - //Cleaning up + // Cleaning up CleanUp(); } /// - /// + /// StopProcessing. /// protected override void StopProcessing() { @@ -527,17 +527,17 @@ protected override void StopProcessing() #region memory /// - /// XmlText writer + /// XmlText writer. /// private XmlWriter _xw; /// - /// Serializer used for serialization + /// Serializer used for serialization. /// private CustomSerialization _serializer; /// - /// Memory Stream used for serialization + /// Memory Stream used for serialization. /// private MemoryStream _ms; @@ -587,7 +587,7 @@ private void CreateMemoryStream() } /// - ///Cleaning up the MemoryStream + ///Cleaning up the MemoryStream. /// private void CleanUp() { @@ -609,45 +609,125 @@ private void CleanUp() #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } + _disposed = true; } #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 + /// Helper class to import single XML file. /// - internal class ImportXmlHelper : IDisposable + internal sealed class ImportXmlHelper : IDisposable { #region constructor /// - /// XML file to import + /// XML file to import. /// private readonly string _path; /// - /// Reference to cmdlet which is using this helper class + /// 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) { @@ -663,12 +743,12 @@ internal ImportXmlHelper(string fileName, PSCmdlet cmdlet, bool isLiteralPath) #region file /// - /// handle to file stream + /// Handle to file stream. /// internal FileStream _fs; /// - /// XmlReader used to read file + /// XmlReader used to read file. /// internal XmlReader _xr; @@ -710,19 +790,20 @@ private void CleanUp() #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } + _disposed = true; GC.SuppressFinalize(this); } @@ -742,7 +823,6 @@ internal void Import() _cmdlet.WriteObject(totalCount); } - ulong skip = _cmdlet.PagingParameters.Skip; ulong first = _cmdlet.PagingParameters.First; @@ -763,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) { @@ -801,63 +879,56 @@ internal void Import() } } - internal void Stop() - { - if (_deserializer != null) - { - _deserializer.Stop(); - } - } - } // ImportXmlHelper + internal void Stop() => _deserializer?.Stop(); + } #region Select-Xml - /// - ///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=135255")] + /// + /// 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 { - # region parameters + #region parameters /// - /// Specifies the path which contains the xml files. The default is the current - /// user directory + /// Specifies the path which contains the xml files. The default is the current user directory. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Path")] [ValidateNotNullOrEmpty] - public String[] Path { get; set; } + public string[] Path { get; set; } /// - /// Specifies the literal path which contains the xml files. The default is the current - /// user directory + /// Specifies the literal path which contains the xml files. The default is the current user directory. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "LiteralPath")] [ValidateNotNullOrEmpty] - [Alias("PSPath")] - public String[] LiteralPath + [Alias("PSPath", "LP")] + public string[] LiteralPath { - get { return Path; } + get + { + return Path; + } + set { Path = value; _isLiteralPath = true; } } + private bool _isLiteralPath = false; /// /// The following is the definition of the input parameter "XML". - /// Specifies the xml Node + /// Specifies the xml Node. /// [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = "Xml")] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")] [Alias("Node")] public System.Xml.XmlNode[] Xml { get; set; } @@ -865,7 +936,6 @@ public String[] LiteralPath /// The following is the definition of the input parameter in string format. /// Specifies the string format of a fully qualified xml. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "Content")] [ValidateNotNullOrEmpty] @@ -881,17 +951,15 @@ public String[] LiteralPath public string XPath { get; set; } /// - /// The following definition used to specify the - /// NameSpace of xml. + /// The following definition used to specify the NameSpace of xml. /// [Parameter] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Hashtable Namespace { get; set; } - # endregion parameters + #endregion parameters - # region private + #region private private void WriteResults(XmlNodeList foundXmlNodes, string filePath) { @@ -899,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; @@ -922,14 +990,15 @@ private void ProcessXmlNode(XmlNode xmlNode, string filePath) { xList = xmlNode.SelectNodes(XPath); } + this.WriteResults(xList, filePath); } private void ProcessXmlFile(string filePath) { - //Cannot use ImportXMLHelper because it will throw terminating error which will - //not be inline with Select-String - //So doing self processing of the file. + // Cannot use ImportXMLHelper because it will throw terminating error which will + // not be inline with Select-String + // So doing self processing of the file. try { XmlDocument xmlDocument = InternalDeserializer.LoadUnsafeXmlDocument( @@ -974,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); } @@ -1002,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); } } @@ -1018,7 +1087,7 @@ private XmlNamespaceManager AddNameSpaceTable(string parametersetname, XmlDocume return xmlns; } - # endregion private + #endregion private #region override @@ -1038,8 +1107,8 @@ protected override void ProcessRecord() (ParameterSetName.Equals("Path", StringComparison.OrdinalIgnoreCase) || (ParameterSetName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)))) { - //If any file not resolved, execution stops. this is to make consistent with select-string. - List fullresolvedPaths = new List(); + // If any file not resolved, execution stops. this is to make consistent with select-string. + List fullresolvedPaths = new(); foreach (string fpath in Path) { if (_isLiteralPath) @@ -1053,16 +1122,18 @@ protected override void ProcessRecord() Collection resolvedPaths = GetResolvedProviderPathFromPSPath(fpath, out provider); if (!provider.NameEquals(this.Context.ProviderNames.FileSystem)) { - //Cannot open File error + // 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; } + fullresolvedPaths.AddRange(resolvedPaths); } } + foreach (string file in fullresolvedPaths) { ProcessXmlFile(file); @@ -1090,10 +1161,9 @@ protected override void ProcessRecord() { Dbg.Assert(false, "Unrecognized parameterset"); } - }//End ProcessRecord() - + } #endregion overrides - }//End Class + } /// /// The object returned by Select-Xml representing the result of a match. @@ -1108,9 +1178,8 @@ public sealed class SelectXmlInfo private const string SimpleFormat = "{0}"; /// - /// The XmlNode that matches search + /// The XmlNode that matches search. /// - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")] public XmlNode Node { get; set; } /// @@ -1122,9 +1191,10 @@ public string Path { return _path; } + set { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { _path = inputStream; } @@ -1134,10 +1204,11 @@ public string Path } } } + private string _path; /// - /// The pattern used to search + /// The pattern used to search. /// public string Pattern { get; set; } @@ -1152,7 +1223,7 @@ public override string ToString() } /// - /// Return String representation of the object + /// Return String representation of the object. /// /// /// @@ -1168,7 +1239,7 @@ private string ToString(string directory) /// internal string GetNodeText() { - string nodeText = String.Empty; + string nodeText = string.Empty; if (Node != null) { if (Node.Value != null) @@ -1180,6 +1251,7 @@ internal string GetNodeText() nodeText = Node.InnerXml.Trim(); } } + return nodeText; } @@ -1197,7 +1269,7 @@ private string RelativePath(string directory) string relPath = _path; if (!relPath.Equals(inputStream)) { - if (relPath.StartsWith(directory, StringComparison.CurrentCultureIgnoreCase)) + if (relPath.StartsWith(directory, StringComparison.OrdinalIgnoreCase)) { int offset = directory.Length; if (offset < relPath.Length) @@ -1209,6 +1281,7 @@ private string RelativePath(string directory) } } } + return relPath; } @@ -1230,7 +1303,5 @@ private string FormatLine(string text, string displaypath) } } } - #endregion Select-Xml } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/compare-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/compare-object.cs deleted file mode 100644 index 0dd2e5b2c96..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/compare-object.cs +++ /dev/null @@ -1,485 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections; -using System.Management.Automation; -using Microsoft.PowerShell.Commands.Internal.Format; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// - /// - [Cmdlet(VerbsData.Compare, "Object", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113286", - RemotingCapability = RemotingCapability.None)] - public sealed class CompareObjectCommand : ObjectCmdletBase - { - #region Parameters - /// - /// - /// - [Parameter(Position = 0, Mandatory = true)] - [AllowEmptyCollection] - public PSObject[] ReferenceObject { get; set; } - - /// - /// - /// - [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyCollection] - public PSObject[] DifferenceObject { get; set; } - - /// - /// - /// - [Parameter] - [ValidateRange(0, Int32.MaxValue)] - public int SyncWindow { get; set; } = Int32.MaxValue; - - /// - /// - /// - /// - [Parameter] - public object[] Property { get; set; } - - /* not implemented - /// - /// - /// - [Parameter] - public SwitchParameter IgnoreWhiteSpace - { - get { return _ignoreWhiteSpace; } - set { _ignoreWhiteSpace = value; } - } - private bool _ignoreWhiteSpace = false; - */ - - /// - /// - /// - [Parameter] - public SwitchParameter ExcludeDifferent - { - get { return _excludeDifferent; } - set { _excludeDifferent = value; } - } - private bool _excludeDifferent /*=false*/; - - /// - /// - /// - [Parameter] - public SwitchParameter IncludeEqual - { - get { return _includeEqual; } - set - { - _isIncludeEqualSpecified = true; - _includeEqual = value; - } - } - private bool _includeEqual /* = false */; - private bool _isIncludeEqualSpecified /* = false */; - - /// - /// - /// - [Parameter] - public SwitchParameter PassThru - { - get { return _passThru; } - set { _passThru = value; } - } - private bool _passThru /* = false */; - #endregion Parameters - - #region Internal - private List _referenceEntries; - private List _referenceEntryBacklog - = new List(); - private List _differenceEntryBacklog - = new List(); - private OrderByProperty _orderByProperty = null; - private OrderByPropertyComparer _comparer = null; - - private int _referenceObjectIndex /* = 0 */; - - // These are programmatic strings, not subject to INTL - private const string SideIndicatorPropertyName = "SideIndicator"; - private const string SideIndicatorMatch = "=="; - private const string SideIndicatorReference = "<="; - private const string SideIndicatorDifference = "=>"; - private const string InputObjectPropertyName = "InputObject"; - - /// - /// The following is the matching algorithm: - /// Retrieve the incoming object (differenceEntry) if any - /// Retrieve the next reference object (referenceEntry) if any - /// If differenceEntry matches referenceEntry - /// Emit referenceEntry as a match - /// Return - /// If differenceEntry matches any entry in referenceEntryBacklog - /// Emit the backlog entry as a match - /// Remove the backlog entry from referenceEntryBacklog - /// Clear differenceEntry - /// If referenceEntry (if any) matches any entry in differenceEntryBacklog - /// Emit referenceEntry as a match - /// Remove the backlog entry from differenceEntryBacklog - /// Clear referenceEntry - /// If differenceEntry is still present - /// If SyncWindow is 0 - /// Emit differenceEntry as unmatched - /// Else - /// While there is no space in differenceEntryBacklog - /// Emit oldest entry in differenceEntryBacklog as unmatched - /// Remove oldest entry from differenceEntryBacklog - /// Add differenceEntry to differenceEntryBacklog - /// If referenceEntry is still present - /// If SyncWindow is 0 - /// Emit referenceEntry as unmatched - /// Else - /// While there is no space in referenceEntryBacklog - /// Emit oldest entry in referenceEntryBacklog as unmatched - /// Remove oldest entry from referenceEntryBacklog - /// Add referenceEntry to referenceEntryBacklog - /// - /// - private void Process(OrderByPropertyEntry differenceEntry) - { - Diagnostics.Assert(null != _referenceEntries, "null referenceEntries"); - - // Retrieve the next reference object (referenceEntry) if any - OrderByPropertyEntry referenceEntry = null; - if (_referenceObjectIndex < _referenceEntries.Count) - { - referenceEntry = _referenceEntries[_referenceObjectIndex++]; - } - - // If differenceEntry matches referenceEntry - // Emit referenceEntry as a match - // Return - // 2005/07/19 Switched order of referenceEntry and differenceEntry - // so that we cast differenceEntry to the type of referenceEntry. - if (null != referenceEntry && null != differenceEntry && - 0 == _comparer.Compare(referenceEntry, differenceEntry)) - { - EmitMatch(referenceEntry); - return; - } - - // If differenceEntry matches any entry in referenceEntryBacklog - // Emit the backlog entry as a match - // Remove the backlog entry from referenceEntryBacklog - // Clear differenceEntry - OrderByPropertyEntry matchingEntry = - MatchAndRemove(differenceEntry, _referenceEntryBacklog); - if (null != matchingEntry) - { - EmitMatch(matchingEntry); - differenceEntry = null; - } - - // If referenceEntry (if any) matches any entry in differenceEntryBacklog - // Emit referenceEntry as a match - // Remove the backlog entry from differenceEntryBacklog - // Clear referenceEntry - matchingEntry = - MatchAndRemove(referenceEntry, _differenceEntryBacklog); - if (null != matchingEntry) - { - EmitMatch(referenceEntry); - referenceEntry = null; - } - - // If differenceEntry is still present - // If SyncWindow is 0 - // Emit differenceEntry as unmatched - // Else - // While there is no space in differenceEntryBacklog - // Emit oldest entry in differenceEntryBacklog as unmatched - // Remove oldest entry from differenceEntryBacklog - // Add differenceEntry to differenceEntryBacklog - if (null != differenceEntry) - { - if (0 < SyncWindow) - { - while (_differenceEntryBacklog.Count >= SyncWindow) - { - EmitDifferenceOnly(_differenceEntryBacklog[0]); - _differenceEntryBacklog.RemoveAt(0); - } - _differenceEntryBacklog.Add(differenceEntry); - } - else - { - EmitDifferenceOnly(differenceEntry); - } - } - - // If referenceEntry is still present - // If SyncWindow is 0 - // Emit referenceEntry as unmatched - // Else - // While there is no space in referenceEntryBacklog - // Emit oldest entry in referenceEntryBacklog as unmatched - // Remove oldest entry from referenceEntryBacklog - // Add referenceEntry to referenceEntryBacklog - if (null != referenceEntry) - { - if (0 < SyncWindow) - { - while (_referenceEntryBacklog.Count >= SyncWindow) - { - EmitReferenceOnly(_referenceEntryBacklog[0]); - _referenceEntryBacklog.RemoveAt(0); - } - _referenceEntryBacklog.Add(referenceEntry); - } - else - { - EmitReferenceOnly(referenceEntry); - } - } - } - - private void InitComparer() - { - if (null != _comparer) - return; - - List referenceObjectList = new List(ReferenceObject); - _orderByProperty = new OrderByProperty( - this, referenceObjectList, Property, true, _cultureInfo, CaseSensitive); - Diagnostics.Assert(_orderByProperty.Comparer != null, "no comparer"); - Diagnostics.Assert( - _orderByProperty.OrderMatrix != null && - _orderByProperty.OrderMatrix.Count == ReferenceObject.Length, - "no OrderMatrix"); - if (_orderByProperty.Comparer == null || _orderByProperty.OrderMatrix == null || _orderByProperty.OrderMatrix.Count == 0) - { - return; - } - - _comparer = _orderByProperty.Comparer; - _referenceEntries = _orderByProperty.OrderMatrix; - } - - private OrderByPropertyEntry MatchAndRemove( - OrderByPropertyEntry match, - List list) - { - if (null == match || null == list) - return null; - Diagnostics.Assert(null != _comparer, "null comparer"); - for (int i = 0; i < list.Count; i++) - { - OrderByPropertyEntry listEntry = list[i]; - Diagnostics.Assert(null != listEntry, "null listEntry " + i); - if (0 == _comparer.Compare(match, listEntry)) - { - list.RemoveAt(i); - return listEntry; - } - } - return null; - } - - #region Emit - private void EmitMatch(OrderByPropertyEntry entry) - { - if (_includeEqual) - Emit(entry, SideIndicatorMatch); - } - - private void EmitDifferenceOnly(OrderByPropertyEntry entry) - { - if (!ExcludeDifferent) - Emit(entry, SideIndicatorDifference); - } - - private void EmitReferenceOnly(OrderByPropertyEntry entry) - { - if (!ExcludeDifferent) - Emit(entry, SideIndicatorReference); - } - - private void Emit(OrderByPropertyEntry entry, string sideIndicator) - { - Diagnostics.Assert(null != entry, "null entry"); - - PSObject mshobj; - if (PassThru) - { - mshobj = PSObject.AsPSObject(entry.inputObject); - } - else - { - mshobj = new PSObject(); - if (null == Property || 0 == Property.Length) - { - PSNoteProperty inputNote = new PSNoteProperty( - InputObjectPropertyName, entry.inputObject); - mshobj.Properties.Add(inputNote); - } - else - { - List mshParameterList = _orderByProperty.MshParameterList; - Diagnostics.Assert(null != mshParameterList, "null mshParameterList"); - Diagnostics.Assert(mshParameterList.Count == Property.Length, "mshParameterList.Count " + mshParameterList.Count); - - for (int i = 0; i < Property.Length; i++) - { - // 2005/07/05 This is the closest we can come to - // the string typed by the user - MshParameter mshParameter = mshParameterList[i]; - Diagnostics.Assert(null != mshParameter, "null mshParameter"); - Hashtable hash = mshParameter.hash; - Diagnostics.Assert(null != hash, "null hash"); - object prop = hash[FormatParameterDefinitionKeys.ExpressionEntryKey]; - Diagnostics.Assert(null != prop, "null prop"); - string propName = prop.ToString(); - PSNoteProperty propertyNote = new PSNoteProperty( - propName, - entry.orderValues[i].PropertyValue); - try - { - mshobj.Properties.Add(propertyNote); - } - catch (ExtendedTypeSystemException) - { - // this is probably a duplicate add - } - } - } - } - mshobj.Properties.Remove(SideIndicatorPropertyName); - PSNoteProperty sideNote = new PSNoteProperty( - SideIndicatorPropertyName, sideIndicator); - mshobj.Properties.Add(sideNote); - WriteObject(mshobj); - } - #endregion Emit - #endregion Internal - - #region Overrides - - /// - /// If the parameter 'ExcludeDifferent' is present, then we need to turn on the - /// 'IncludeEqual' switch unless it's turned off by the user specifically. - /// - protected override void BeginProcessing() - { - if (ExcludeDifferent) - { - if (_isIncludeEqualSpecified == false) - { - return; - } - if (_isIncludeEqualSpecified && !_includeEqual) - { - return; - } - - _includeEqual = true; - } - } - - /// - /// - /// - protected override void ProcessRecord() - { - if (ReferenceObject == null || ReferenceObject.Length == 0) - { - HandleDifferenceObjectOnly(); - return; - } - else if (DifferenceObject == null || DifferenceObject.Length == 0) - { - HandleReferenceObjectOnly(); - return; - } - - if (null == _comparer && 0 < DifferenceObject.Length) - { - InitComparer(); - } - - List differenceList = new List(DifferenceObject); - List differenceEntries = - OrderByProperty.CreateOrderMatrix( - this, differenceList, _orderByProperty.MshParameterList); - - foreach (OrderByPropertyEntry incomingEntry in differenceEntries) - { - Process(incomingEntry); - } - } - - /// - /// - /// - protected override void EndProcessing() - { - // Clear remaining reference objects if there are more - // reference objects than difference objects - if (_referenceEntries != null) - { - while (_referenceObjectIndex < _referenceEntries.Count) - { - Process(null); - } - } - - // emit all remaining backlogged objects - foreach (OrderByPropertyEntry differenceEntry in _differenceEntryBacklog) - { - EmitDifferenceOnly(differenceEntry); - } - _differenceEntryBacklog.Clear(); - foreach (OrderByPropertyEntry referenceEntry in _referenceEntryBacklog) - { - EmitReferenceOnly(referenceEntry); - } - _referenceEntryBacklog.Clear(); - } - #endregion Overrides - - private void HandleDifferenceObjectOnly() - { - if (DifferenceObject == null || DifferenceObject.Length == 0) - { - return; - } - - List differenceList = new List(DifferenceObject); - _orderByProperty = new OrderByProperty( - this, differenceList, Property, true, _cultureInfo, CaseSensitive); - List differenceEntries = - OrderByProperty.CreateOrderMatrix( - this, differenceList, _orderByProperty.MshParameterList); - - foreach (OrderByPropertyEntry entry in differenceEntries) - { - EmitDifferenceOnly(entry); - } - } - - private void HandleReferenceObjectOnly() - { - if (ReferenceObject == null || ReferenceObject.Length == 0) - { - return; - } - - InitComparer(); - Process(null); - } - } -} - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/convert-HTML.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/convert-HTML.cs deleted file mode 100644 index 89e61edbf98..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/convert-HTML.cs +++ /dev/null @@ -1,722 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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; -using System.Net; -using System.Text; -using Microsoft.PowerShell.Commands.Internal.Format; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// - /// Class comment - /// - /// - - [Cmdlet(VerbsData.ConvertTo, "Html", DefaultParameterSetName = "Page", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113290", RemotingCapability = RemotingCapability.None)] - public sealed - class ConvertToHtmlCommand : PSCmdlet - { - /// The incoming object - /// - [Parameter(ValueFromPipeline = true)] - public PSObject InputObject - { - get - { - return _inputObject; - } - set - { - _inputObject = value; - } - } - private PSObject _inputObject; - - /// - /// The list of properties to display - /// These take the form of an MshExpression - /// - /// - [Parameter(Position = 0)] - public object[] Property - { - get - { - return _property; - } - set - { - _property = value; - } - } - private object[] _property; - - /// - /// Text to go after the opening body tag - /// and before the table - /// - /// - [Parameter(ParameterSetName = "Page", Position = 3)] - public string[] Body - { - get - { - return _body; - } - set - { - _body = value; - } - } - private string[] _body; - - /// - /// Text to go into the head section - /// of the html doc - /// - /// - [Parameter(ParameterSetName = "Page", Position = 1)] - public string[] Head - { - get - { - return _head; - } - set - { - _head = value; - } - } - private string[] _head; - - /// - /// The string for the title tag - /// The title is also placed in the body of the document - /// before the table between h3 tags - /// If the -Head parameter is used, this parameter has no - /// effect. - /// - /// - [Parameter(ParameterSetName = "Page", Position = 2)] - [ValidateNotNullOrEmpty] - public string Title - { - get - { - return _title; - } - set - { - _title = value; - } - } - private string _title = "HTML TABLE"; - - /// - /// This specifies whether the objects should - /// be rendered as an HTML TABLE or - /// HTML LIST - /// - /// - [Parameter] - [ValidateNotNullOrEmpty] - [ValidateSet("Table", "List")] - public string As - { - get - { - return _as; - } - set - { - _as = value; - } - } - private string _as = "Table"; - - /// - /// This specifies a full or partial URI - /// for the CSS information. - /// The html should reference the css file specified - /// - [Parameter(ParameterSetName = "Page")] - [Alias("cu", "uri")] - [ValidateNotNullOrEmpty] - public Uri CssUri - { - get - { - return _cssuri; - } - set - { - _cssuri = value; - _cssuriSpecified = true; - } - } - private Uri _cssuri; - private bool _cssuriSpecified; - - /// - /// When this switch is specified generate only the - /// HTML representation of the incoming object - /// without the HTML,HEAD,TITLE,BODY,etc tags. - /// - [Parameter(ParameterSetName = "Fragment")] - [ValidateNotNullOrEmpty] - public SwitchParameter Fragment - { - get - { - return _fragment; - } - set - { - _fragment = value; - } - } - private SwitchParameter _fragment; - - /// - /// Specifies the text to include prior the - /// closing body tag of the HTML output - /// - [Parameter] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] PostContent - { - get - { - return _postContent; - } - set - { - _postContent = value; - } - } - private string[] _postContent; - - /// - /// Specifies the text to include after the - /// body tag of the HTML output - /// - [Parameter] - [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] PreContent - { - get - { - return _preContent; - } - set - { - _preContent = value; - } - } - private string[] _preContent; - - /// - /// Sets and Gets the meta property of the HTML head - /// - /// - [Parameter(ParameterSetName = "Page")] - [ValidateNotNullOrEmpty] - public Hashtable Meta - { - get - { - return _meta; - } - set - { - _meta = value; - _metaSpecified = true; - } - } - private Hashtable _meta; - private bool _metaSpecified = false; - - /// - /// Specifies the charset encoding for the HTML document - /// - [Parameter(ParameterSetName = "Page")] - [ValidateNotNullOrEmpty] - [ValidatePattern("^[A-Za-z0-9]\\w+\\S+[A-Za-z0-9]$")] - public string Charset - { - get - { - return _charset; - } - set - { - _charset = value; - _charsetSpecified = true; - } - } - private string _charset; - private bool _charsetSpecified = false; - - /// - /// When this switch statement is specified, - /// it will change the DOCTYPE to XHTML Transitional DTD - /// - /// - [Parameter(ParameterSetName = "Page")] - [ValidateNotNullOrEmpty] - public SwitchParameter Transitional - { - get - { - return _transitional; - } - set - { - _transitional = true; - } - } - private bool _transitional = false; - - /// - /// definitions for hash table keys - /// - internal static class ConvertHTMLParameterDefinitionKeys - { - internal const string LabelEntryKey = "label"; - internal const string AlignmentEntryKey = "alignment"; - internal const string WidthEntryKey = "width"; - } - - /// - /// This allows for @{e='foo';label='bar';alignment='center';width='20'} - /// - internal class ConvertHTMLExpressionParameterDefinition : CommandParameterDefinition - { - protected override void SetEntries() - { - this.hashEntries.Add(new ExpressionEntryDefinition()); - this.hashEntries.Add(new HashtableEntryDefinition(ConvertHTMLParameterDefinitionKeys.LabelEntryKey, new Type[] { typeof(string) })); - this.hashEntries.Add(new HashtableEntryDefinition(ConvertHTMLParameterDefinitionKeys.AlignmentEntryKey, new Type[] { typeof(string) })); - this.hashEntries.Add(new HashtableEntryDefinition(ConvertHTMLParameterDefinitionKeys.WidthEntryKey, new Type[] { typeof(string) })); - } - } - - /// - /// Create a list of MshParameter from properties - /// - /// can be a string, ScriptBlock, or Hashtable - /// - private List ProcessParameter(object[] properties) - { - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); - ParameterProcessor processor = - new ParameterProcessor(new ConvertHTMLExpressionParameterDefinition()); - if (properties == null) - { - properties = new object[] { "*" }; - } - return processor.ProcessParameters(properties, invocationContext); - } - - /// - /// Resolve all wildcards in user input Property into resolvedNameMshParameters - /// - private void InitializeResolvedNameMshParameters() - { - // temp list of properties with wildcards resolved - ArrayList resolvedNameProperty = new ArrayList(); - - foreach (MshParameter p in _propertyMshParameterList) - { - string label = p.GetEntry(ConvertHTMLParameterDefinitionKeys.LabelEntryKey) as string; - string alignment = p.GetEntry(ConvertHTMLParameterDefinitionKeys.AlignmentEntryKey) as string; - string width = p.GetEntry(ConvertHTMLParameterDefinitionKeys.WidthEntryKey) as string; - MshExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - List resolvedNames = ex.ResolveNames(_inputObject); - foreach (MshExpression resolvedName in resolvedNames) - { - Hashtable ht = CreateAuxPropertyHT(label, alignment, width); - ht.Add(FormatParameterDefinitionKeys.ExpressionEntryKey, resolvedName.ToString()); - resolvedNameProperty.Add(ht); - } - } - _resolvedNameMshParameters = ProcessParameter(resolvedNameProperty.ToArray()); - } - - private static Hashtable CreateAuxPropertyHT( - string label, - string alignment, - string width) - { - Hashtable ht = new Hashtable(); - if (label != null) - { - ht.Add(ConvertHTMLParameterDefinitionKeys.LabelEntryKey, label); - } - if (alignment != null) - { - ht.Add(ConvertHTMLParameterDefinitionKeys.AlignmentEntryKey, alignment); - } - if (width != null) - { - ht.Add(ConvertHTMLParameterDefinitionKeys.WidthEntryKey, width); - } - return ht; - } - - /// - /// calls ToString. If an exception occurs, eats it and return string.Empty - /// - /// - /// - private static string SafeToString(object obj) - { - if (obj == null) - { - return ""; - } - try - { - return obj.ToString(); - } - catch (Exception) - { - // eats exception if safe - } - return ""; - } - - - /// - /// - /// - 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"); - ThrowTerminatingError(er); - } - - _propertyMshParameterList = ProcessParameter(_property); - - if (!String.IsNullOrEmpty(_title)) - { - WebUtility.HtmlEncode(_title); - } - - - // This first line ensures w3c validation will succeed. However we are not specifying - // an encoding in the HTML because we don't know where the text will be written and - // if a particular encoding will be used. - - if (!_fragment) - { - if(!_transitional) - { - WriteObject(""); - } - else - { - WriteObject(""); - } - WriteObject(""); - WriteObject(""); - if(_charsetSpecified) - { - WriteObject(""); - } - if(_metaSpecified) - { - List useditems = new List(); - foreach(string s in _meta.Keys) - { - if(!useditems.Contains(s)){ - switch(s.ToLower()){ - case "content-type": - case "default-style": - case "x-ua-compatible": - WriteObject(""); - break; - case "application-name": - case "author": - case "description": - case "generator": - case "keywords": - case "viewport": - WriteObject(""); - break; - 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; - - if (invocationInfo != null) - { - record.SetInvocationInfo(invocationInfo); - } - mshCommandRuntime.WriteWarning(record); - WriteObject(""); - break; - } - useditems.Add(s); - } - } - } - WriteObject(_head ?? new string[] { "" + _title + "" }, true); - if (_cssuriSpecified) - { - WriteObject(""); - } - WriteObject(""); - if (_body != null) - { - WriteObject(_body, true); - } - } - if (_preContent != null) - { - WriteObject(_preContent, true); - } - WriteObject(""); - _isTHWritten = false; - _propertyCollector = new StringCollection(); - } - - /// - /// Reads Width and Alignment from Property and write Col tags - /// - /// - private void WriteColumns(List mshParams) - { - StringBuilder COLTag = new StringBuilder(); - - COLTag.Append(""); - foreach (MshParameter p in mshParams) - { - COLTag.Append(""); - } - - COLTag.Append(""); - - // The columngroup and col nodes will be printed in a single line. - WriteObject(COLTag.ToString()); - } - - /// - /// Writes the list entries when the As parameter has value List - /// - private void WriteListEntry() - { - foreach (MshParameter p in _resolvedNameMshParameters) - { - StringBuilder Listtag = new StringBuilder(); - Listtag.Append(""); - - //for writing the property value - Listtag.Append(""); - - WriteObject(Listtag.ToString()); - } - } - - /// - /// To write the Property name - /// - private void WritePropertyName(StringBuilder Listtag, MshParameter p) - { - //for writing the property name - string label = p.GetEntry(ConvertHTMLParameterDefinitionKeys.LabelEntryKey) as string; - if (label != null) - { - Listtag.Append(label); - } - else - { - MshExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - Listtag.Append(ex.ToString()); - } - } - - /// - /// To write the Property value - /// - private void WritePropertyValue(StringBuilder Listtag, MshParameter p) - { - MshExpression exValue = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - - // get the value of the property - List resultList = exValue.GetValues(_inputObject); - foreach (MshExpressionResult result in resultList) - { - // create comma sep list for multiple results - if (result.Result != null) - { - string htmlEncodedResult = WebUtility.HtmlEncode(SafeToString(result.Result)); - Listtag.Append(htmlEncodedResult); - } - Listtag.Append(", "); - } - if (Listtag.ToString().EndsWith(", ", StringComparison.Ordinal)) - { - Listtag.Remove(Listtag.Length - 2, 2); - } - } - - /// - /// To write the Table header for the object property names - /// - private void WriteTableHeader(StringBuilder THtag, List resolvedNameMshParameters) - { - //write the property names - foreach (MshParameter p in resolvedNameMshParameters) - { - THtag.Append(""); - } - } - - /// - /// To write the Table row for the object property values - /// - private void WriteTableRow(StringBuilder TRtag, List resolvedNameMshParameters) - { - //write the property values - foreach (MshParameter p in resolvedNameMshParameters) - { - TRtag.Append(""); - } - } - - //count of the objects - private int _numberObjects = 0; - - /// - /// - /// - /// - protected override void ProcessRecord() - { - // writes the table headers - // it is not in BeginProcessing because the first inputObject is needed for - // the number of columns and column name - if (_inputObject == null || _inputObject == AutomationNull.Value) - { - return; - } - _numberObjects++; - if (!_isTHWritten) - { - InitializeResolvedNameMshParameters(); - if (_resolvedNameMshParameters == null || _resolvedNameMshParameters.Count == 0) - { - return; - } - - //if the As parameter is given as List - if (_as.Equals("List", StringComparison.OrdinalIgnoreCase)) - { - //if more than one object,write the horizontal rule to put visual separator - if (_numberObjects > 1) - WriteObject(""); - WriteListEntry(); - } - else //if the As parameter is Table, first we have to write the property names - { - WriteColumns(_resolvedNameMshParameters); - - StringBuilder THtag = new StringBuilder(""); - - //write the table header - WriteTableHeader(THtag, _resolvedNameMshParameters); - - THtag.Append(""); - WriteObject(THtag.ToString()); - _isTHWritten = true; - } - } - //if the As parameter is Table, write the property values - if (_as.Equals("Table", StringComparison.OrdinalIgnoreCase)) - { - StringBuilder TRtag = new StringBuilder(""); - - //write the table row - WriteTableRow(TRtag, _resolvedNameMshParameters); - - TRtag.Append(""); - WriteObject(TRtag.ToString()); - } - } - - /// - /// - /// - protected override void EndProcessing() - { - //if fragment,end with table - WriteObject("
"); - - //for writing the property name - WritePropertyName(Listtag, p); - Listtag.Append(":"); - Listtag.Append(""); - WritePropertyValue(Listtag, p); - Listtag.Append("
"); - WritePropertyName(THtag, p); - THtag.Append(""); - WritePropertyValue(TRtag, p); - TRtag.Append("

"); - if (_postContent != null) - WriteObject(_postContent, true); - - //if not fragment end with body and html also - if (!_fragment) - { - WriteObject(""); - } - } - - #region private - - /// - /// list of incoming objects to compare - /// - private bool _isTHWritten; - private StringCollection _propertyCollector; - private List _propertyMshParameterList; - private List _resolvedNameMshParameters; - //private string ResourcesBaseName = "ConvertHTMLStrings"; - - #endregion private - } -} - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/group-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/group-object.cs deleted file mode 100644 index 60d316aecb0..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/group-object.cs +++ /dev/null @@ -1,410 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Text; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// PSTuple is a helper class used to create Tuple from an input array. - /// - internal static class PSTuple - { - /// - /// ArrayToTuple is a helper method used to create a tuple for the supplied input array. - /// - /// Input objects used to create a tuple. - /// Tuple object. - internal static object ArrayToTuple(object[] inputObjects) - { - Diagnostics.Assert(inputObjects != null, "inputObjects is null"); - Diagnostics.Assert(inputObjects.Length > 0, "inputObjects is empty"); - - return ArrayToTuple(inputObjects, 0); - } - - /// - /// ArrayToTuple is a helper method used to create a tuple for the supplied input array. - /// - /// Input objects used to create a tuple - /// Start index of the array from which the objects have to considered for the tuple creation. - /// Tuple object. - internal static object ArrayToTuple(object[] inputObjects, int startIndex) - { - Diagnostics.Assert(inputObjects != null, "inputObjects is null"); - Diagnostics.Assert(inputObjects.Length > 0, "inputObjects is empty"); - - switch (inputObjects.Length - startIndex) - { - case 0: - return null; - case 1: - return Tuple.Create(inputObjects[startIndex]); - case 2: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1]); - case 3: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2]); - case 4: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3]); - case 5: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3], inputObjects[startIndex + 4]); - case 6: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3], inputObjects[startIndex + 4], - inputObjects[startIndex + 5]); - case 7: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3], inputObjects[startIndex + 4], - inputObjects[startIndex + 5], inputObjects[startIndex + 6]); - case 8: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3], inputObjects[startIndex + 4], - inputObjects[startIndex + 5], inputObjects[startIndex + 6], inputObjects[startIndex + 7]); - default: - return Tuple.Create(inputObjects[startIndex], inputObjects[startIndex + 1], inputObjects[startIndex + 2], inputObjects[startIndex + 3], inputObjects[startIndex + 4], - inputObjects[startIndex + 5], inputObjects[startIndex + 6], ArrayToTuple(inputObjects, startIndex + 7)); - } - } - } - - /// - /// Emitted by Group-Object when the NoElement option is true - /// - public sealed class GroupInfoNoElement : GroupInfo - { - internal GroupInfoNoElement(OrderByPropertyEntry groupValue) - : base(groupValue) - { - } - - internal override void Add(PSObject groupValue) - { - Count++; - } - } - - /// - /// Emitted by Group-Object - /// - public class GroupInfo - { - internal GroupInfo(OrderByPropertyEntry groupValue) - { - Group = new Collection(); - this.Add(groupValue.inputObject); - GroupValue = groupValue; - Name = BuildName(groupValue.orderValues); - } - - internal virtual void Add(PSObject groupValue) - { - Group.Add(groupValue); - Count++; - } - - - - private static string BuildName(List propValues) - { - StringBuilder sb = new StringBuilder(); - foreach (ObjectCommandPropertyValue propValue in propValues) - { - if (propValue != null && propValue.PropertyValue != null) - { - var propertyValueItems = propValue.PropertyValue as ICollection; - if (propertyValueItems != null) - { - sb.Append("{"); - var length = sb.Length; - - foreach (object item in propertyValueItems) - { - sb.Append(string.Format(CultureInfo.InvariantCulture, "{0}, ", item.ToString())); - } - - sb = sb.Length > length ? sb.Remove(sb.Length - 2, 2) : sb; - sb.Append("}, "); - } - else - { - sb.Append(string.Format(CultureInfo.InvariantCulture, "{0}, ", propValue.PropertyValue.ToString())); - } - } - } - return sb.Length >= 2 ? sb.Remove(sb.Length - 2, 2).ToString() : string.Empty; - } - - - /// - /// - /// Values of the group - /// - /// - public ArrayList Values - { - get - { - ArrayList values = new ArrayList(); - foreach (ObjectCommandPropertyValue propValue in GroupValue.orderValues) - { - values.Add(propValue.PropertyValue); - } - return values; - } - } - - /// - /// - /// Number of objects in the group - /// - /// - public int Count { get; internal set; } - - /// - /// - /// The list of objects in this group - /// - /// - public Collection Group { get; } = null; - - /// - /// - /// The name of the group - /// - /// - public string Name { get; } = null; - - /// - /// - /// The OrderByPropertyEntry used to build this group object - /// - /// - internal OrderByPropertyEntry GroupValue { get; } = null; - } - - /// - /// - /// Group-Object implementation - /// - /// - [Cmdlet(VerbsData.Group, "Object", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113338", RemotingCapability = RemotingCapability.None)] - [OutputType(typeof(Hashtable), typeof(GroupInfo))] - public class GroupObjectCommand : ObjectBase - { - #region tracer - - /// - /// An instance of the PSTraceSource class used for trace output - /// - [TraceSourceAttribute( - "GroupObjectCommand", - "Class that has group base implementation")] - private static PSTraceSource s_tracer = - PSTraceSource.GetTracer("GroupObjectCommand", - "Class that has group base implementation"); - - #endregion tracer - - #region Command Line Switches - - /// - /// - /// Flatten the groups - /// - /// - /// - [Parameter] - public SwitchParameter NoElement - { - get { return _noElement; } - set { _noElement = value; } - } - private bool _noElement; - /// - /// the AsHashTable parameter - /// - /// - [Parameter(ParameterSetName = "HashTable")] - [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "HashTable")] - [Alias("AHT")] - public SwitchParameter AsHashTable { get; set; } - - /// - /// - /// - /// - [Parameter(ParameterSetName = "HashTable")] - public SwitchParameter AsString { get; set; } - - private List _groups = new List(); - private OrderByProperty _orderByProperty = new OrderByProperty(); - private bool _hasProcessedFirstInputObject; - private Dictionary _tupleToGroupInfoMappingDictionary = new Dictionary(); - private OrderByPropertyComparer _orderByPropertyComparer = null; - - #endregion - - #region utils - - /// - /// Utility function called by Group-Object to create Groups. - /// - /// Input object that needs to be grouped. - /// true if we are not accumulating objects - /// List containing Groups. - /// Dictionary used to keep track of the groups with hash of the property values being the key. - /// The Comparer to be used while comparing to check if new group has to be created. - internal static void DoGrouping(OrderByPropertyEntry currentObjectEntry, bool noElement, List groups, Dictionary groupInfoDictionary, - OrderByPropertyComparer orderByPropertyComparer) - { - if (currentObjectEntry != null && currentObjectEntry.orderValues != null && currentObjectEntry.orderValues.Count > 0) - { - object currentTupleObject = PSTuple.ArrayToTuple(currentObjectEntry.orderValues.ToArray()); - - GroupInfo currentGroupInfo = null; - if (groupInfoDictionary.TryGetValue(currentTupleObject, out currentGroupInfo)) - { - if (currentGroupInfo != null) - { - //add this inputObject to an existing group - currentGroupInfo.Add(currentObjectEntry.inputObject); - } - } - else - { - bool isCurrentItemGrouped = false; - - for (int groupsIndex = 0; groupsIndex < groups.Count; groupsIndex++) - { - // Check if the current input object can be converted to one of the already known types - // by looking up in the type to GroupInfo mapping. - if (orderByPropertyComparer.Compare(groups[groupsIndex].GroupValue, currentObjectEntry) == 0) - { - groups[groupsIndex].Add(currentObjectEntry.inputObject); - isCurrentItemGrouped = true; - break; - } - } - - if (!isCurrentItemGrouped) - { - // create a new group - s_tracer.WriteLine("Create a new group: {0}", currentObjectEntry.orderValues); - GroupInfo newObjGrp = noElement ? new GroupInfoNoElement(currentObjectEntry) : new GroupInfo(currentObjectEntry); - groups.Add(newObjGrp); - - groupInfoDictionary.Add(currentTupleObject, newObjGrp); - } - } - } - } - private void WriteNonTerminatingError(Exception exception, string resourceIdAndErrorId, - ErrorCategory category) - { - Exception ex = new Exception(StringUtil.Format(resourceIdAndErrorId), exception); - WriteError(new ErrorRecord(ex, resourceIdAndErrorId, category, null)); - } - #endregion utils - - /// - /// Process every input object to group them. - /// - protected override void ProcessRecord() - { - if (InputObject != null && InputObject != AutomationNull.Value) - { - OrderByPropertyEntry currentEntry = null; - - if (!_hasProcessedFirstInputObject) - { - if (Property == null) - { - Property = OrderByProperty.GetDefaultKeyPropertySet(InputObject); - } - _orderByProperty.ProcessExpressionParameter(this, Property); - - currentEntry = _orderByProperty.CreateOrderByPropertyEntry(this, InputObject, CaseSensitive, _cultureInfo); - bool[] ascending = new bool[currentEntry.orderValues.Count]; - for (int index = 0; index < currentEntry.orderValues.Count; index++) - { - ascending[index] = true; - } - _orderByPropertyComparer = new OrderByPropertyComparer(ascending, _cultureInfo, CaseSensitive); - - _hasProcessedFirstInputObject = true; - } - else - { - currentEntry = _orderByProperty.CreateOrderByPropertyEntry(this, InputObject, CaseSensitive, _cultureInfo); - } - - DoGrouping(currentEntry, this.NoElement, _groups, _tupleToGroupInfoMappingDictionary, _orderByPropertyComparer); - } - } - - /// - /// - /// - protected override void EndProcessing() - { - s_tracer.WriteLine(_groups.Count); - if (_groups.Count > 0) - { - if (AsHashTable) - { - Hashtable _table = CollectionsUtil.CreateCaseInsensitiveHashtable(); - try - { - foreach (GroupInfo _grp in _groups) - { - if (AsString) - { - _table.Add(_grp.Name, _grp.Group); - } - else - { - if (_grp.Values.Count == 1) - { - _table.Add(_grp.Values[0], _grp.Group); - } - else - { - ArgumentException ex = new ArgumentException(UtilityCommonStrings.GroupObjectSingleProperty); - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, Property); - ThrowTerminatingError(er); - } - } - } - } - catch (ArgumentException e) - { - WriteNonTerminatingError(e, UtilityCommonStrings.InvalidOperation, ErrorCategory.InvalidArgument); - return; - } - WriteObject(_table); - } - else - { - if (AsString) - { - ArgumentException ex = new ArgumentException(UtilityCommonStrings.GroupObjectWithHashTable); - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, AsString); - ThrowTerminatingError(er); - } - else - { - WriteObject(_groups, true); - } - } - } - } - } -} - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs deleted file mode 100644 index 89f7bccf977..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/new-object.cs +++ /dev/null @@ -1,493 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -#region Using directives - -using System; -using System.Reflection; -using System.Collections; -using System.Runtime.InteropServices; -using System.Threading; -using System.Management.Automation; -using System.Management.Automation.Security; -using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Management.Automation.Language; -using Dbg = System.Management.Automation.Diagnostics; - -#endregion - -namespace Microsoft.PowerShell.Commands -{ - /// Create a new .net object - [Cmdlet(VerbsCommon.New, "Object", DefaultParameterSetName = netSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113355")] - public sealed class NewObjectCommand : PSCmdlet - { - #region parameters - - /// the number - [Parameter(ParameterSetName = netSetName, Mandatory = true, Position = 0)] - public string TypeName { get; set; } = null; - -#if !UNIX - private Guid _comObjectClsId = Guid.Empty; - /// the ProgID of the Com object - [Parameter(ParameterSetName = "Com", Mandatory = true, Position = 0)] - public string ComObject { get; set; } = null; -#endif - - /// - /// The parameters for the constructor - /// - /// - [Parameter(ParameterSetName = netSetName, Mandatory = false, Position = 1)] - [Alias("Args")] - public object[] ArgumentList { get; set; } = null; - - /// - /// True if we should have an error when Com objects will use an interop assembly - /// - [Parameter(ParameterSetName = "Com")] - public SwitchParameter Strict { get; set; } - - // Updated from Hashtable to IDictionary to support the work around ordered hashtables. - /// - /// gets the properties to be set. - /// - [Parameter] - [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public IDictionary Property { get; set; } - - # endregion parameters - - #region private - private object CallConstructor(Type type, ConstructorInfo[] constructors, object[] args) - { - object result = null; - try - { - result = DotNetAdapter.ConstructorInvokeDotNet(type, constructors, args); - } - catch (MethodException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "ConstructorInvokedThrowException", - ErrorCategory.InvalidOperation, null)); - } - // let other exceptions propagate - return result; - } - - private void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType) - { - string message = StringUtil.Format(NewObjectStrings.MemberNotFound, null, property.Key.ToString(), ParameterSet2ResourceString(ParameterSetName)); - - ThrowTerminatingError( - new ErrorRecord( - new InvalidOperationException(message), - "InvalidOperationException", - ErrorCategory.InvalidOperation, - null)); - } - - private void CreateMemberSetValueError(SetValueException e) - { - Exception ex = new Exception(StringUtil.Format(NewObjectStrings.InvalidValue, e)); - ThrowTerminatingError( - new ErrorRecord(ex, "SetValueException", ErrorCategory.InvalidData, null)); - } - - - private static string ParameterSet2ResourceString(string parameterSet) - { - if (parameterSet.Equals(netSetName, StringComparison.OrdinalIgnoreCase)) - { - return ".NET"; - } - else if (parameterSet.Equals("Com", StringComparison.OrdinalIgnoreCase)) - { - return "COM"; - } - else - { - Dbg.Assert(false, "Should never get here - unknown parameter set"); - return parameterSet; - } - } - - #endregion private - - #region Overrides - /// Create the object - protected override void BeginProcessing() - { - Type type = null; - PSArgumentException mshArgE = null; - - if (string.Compare(ParameterSetName, netSetName, StringComparison.Ordinal) == 0) - { - object _newObject = null; - try - { - type = LanguagePrimitives.ConvertTo(TypeName, typeof(Type), CultureInfo.InvariantCulture) as Type; - } - catch (Exception e) - { - // these complications in Exception handling are aim to make error messages better. - if (e is InvalidCastException || e is ArgumentException) - { - if (e.InnerException != null && e.InnerException is TypeResolver.AmbiguousTypeException) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "AmbiguousTypeReference", - ErrorCategory.InvalidType, null)); - } - - mshArgE = PSTraceSource.NewArgumentException( - "TypeName", - NewObjectStrings.TypeNotFound, - TypeName); - ThrowTerminatingError( - new ErrorRecord( - mshArgE, - "TypeNotFound", - ErrorCategory.InvalidType, null)); - } - throw e; - } - - Diagnostics.Assert(type != null, "LanguagePrimitives.TryConvertTo failed but returned true"); - - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) - { - if (!CoreTypes.Contains(type)) - { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateTypeConstrainedLanguage", ErrorCategory.PermissionDenied, null)); - } - } - - //WinRT does not support creating instances of attribute & delegate WinRT types. - if (WinRTHelper.IsWinRTType(type) && ((typeof(System.Attribute)).IsAssignableFrom(type) || (typeof(System.Delegate)).IsAssignableFrom(type))) - { - ThrowTerminatingError(new ErrorRecord(new InvalidOperationException(NewObjectStrings.CannotInstantiateWinRTType), - "CannotInstantiateWinRTType", ErrorCategory.InvalidOperation, null)); - } - - if (ArgumentList == null || ArgumentList.Length == 0) - { - ConstructorInfo ci = type.GetConstructor(PSTypeExtensions.EmptyTypes); - if (ci != null && ci.IsPublic) - { - _newObject = CallConstructor(type, new ConstructorInfo[] { ci }, new object[] { }); - 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 - _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); - } - WriteObject(_newObject); - return; - } - else if (type.GetTypeInfo().IsValueType) - { - // This is for default parameterless struct ctor which is not returned by - // Type.GetConstructor(System.Type.EmptyTypes). - try - { - _newObject = Activator.CreateInstance(type); - if (_newObject != null && Property != null) - { - // Win8:649519 - _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); - } - } - catch (TargetInvocationException e) - { - ThrowTerminatingError( - new ErrorRecord( - e.InnerException ?? e, - "ConstructorCalledThrowException", - ErrorCategory.InvalidOperation, null)); - } - WriteObject(_newObject); - return; - } - } - else - { - ConstructorInfo[] ctorInfos = type.GetConstructors(); - - if (ctorInfos.Length != 0) - { - _newObject = CallConstructor(type, ctorInfos, ArgumentList); - if (_newObject != null && Property != null) - { - // Win8:649519 - _newObject = LanguagePrimitives.SetObjectProperties(_newObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); - } - WriteObject(_newObject); - return; - } - } - - mshArgE = PSTraceSource.NewArgumentException( - "TypeName", NewObjectStrings.CannotFindAppropriateCtor, TypeName); - ThrowTerminatingError( - new ErrorRecord( - mshArgE, - "CannotFindAppropriateCtor", - ErrorCategory.ObjectNotFound, null)); - } -#if !UNIX - else // Parameterset -Com - { - int result = NewObjectNativeMethods.CLSIDFromProgID(ComObject, out _comObjectClsId); - - // If we're in ConstrainedLanguage, do additional restrictions - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) - { - bool isAllowed = false; - - // If it's a system-wide lockdown, we may allow additional COM types - if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) - { - if ((result >= 0) && - SystemPolicy.IsClassInApprovedList(_comObjectClsId)) - { - isAllowed = true; - } - } - - if (!isAllowed) - { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateComTypeConstrainedLanguage", ErrorCategory.PermissionDenied, null)); - return; - } - } - - object comObject = CreateComObject(); - string comObjectTypeName = comObject.GetType().FullName; - if (!comObjectTypeName.Equals("System.__ComObject")) - { - mshArgE = PSTraceSource.NewArgumentException( - "TypeName", NewObjectStrings.ComInteropLoaded, comObjectTypeName); - WriteVerbose(mshArgE.Message); - if (Strict) - { - WriteError(new ErrorRecord( - mshArgE, - "ComInteropLoaded", - ErrorCategory.InvalidArgument, comObject)); - } - } - if (comObject != null && Property != null) - { - // Win8:649519 - comObject = LanguagePrimitives.SetObjectProperties(comObject, Property, type, CreateMemberNotFoundError, CreateMemberSetValueError, enableMethodCall: true); - } - WriteObject(comObject); - } -#endif - }//protected override void BeginProcessing() - - #endregion Overrides - -#if !UNIX - #region Com - - private object SafeCreateInstance(Type t, object[] args) - { - object result = null; - try - { - result = Activator.CreateInstance(t, args); - } - // Does not catch InvalidComObjectException because ComObject is obtained from GetTypeFromProgID - catch (ArgumentException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "CannotNewNonRuntimeType", - ErrorCategory.InvalidOperation, null)); - } - catch (NotSupportedException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "CannotNewTypeBuilderTypedReferenceArgIteratorRuntimeArgumentHandle", - ErrorCategory.InvalidOperation, null)); - } - catch (MethodAccessException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "CtorAccessDenied", - ErrorCategory.PermissionDenied, null)); - } - catch (MissingMethodException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "NoPublicCtorMatch", - ErrorCategory.InvalidOperation, null)); - } - catch (MemberAccessException e) - { - ThrowTerminatingError( - new ErrorRecord( - e, - "CannotCreateAbstractClass", - ErrorCategory.InvalidOperation, null)); - } - catch (COMException e) - { - if (e.HResult == RPC_E_CHANGED_MODE) - { - throw; - } - - ThrowTerminatingError( - new ErrorRecord( - e, - "NoCOMClassIdentified", - ErrorCategory.ResourceUnavailable, null)); - } - - return result; - } - -#if !CORECLR - private class ComCreateInfo - { - public Object objectCreated; - public bool success; - public Exception e; - } - - private ComCreateInfo createInfo; - - private void STAComCreateThreadProc(Object createstruct) - { - ComCreateInfo info = (ComCreateInfo)createstruct; - try - { - Type type = null; - PSArgumentException mshArgE = null; - - type = Type.GetTypeFromCLSID(_comObjectClsId); - if (type == null) - { - mshArgE = PSTraceSource.NewArgumentException( - "ComObject", - NewObjectStrings.CannotLoadComObjectType, - ComObject); - - info.e = mshArgE; - info.success = false; - return; - } - info.objectCreated = SafeCreateInstance(type, ArgumentList); - info.success = true; - } - catch (Exception e) - { - info.e = e; - info.success = false; - } - } -#endif - - private object CreateComObject() - { - Type type = null; - PSArgumentException mshArgE = null; - - try - { - type = Marshal.GetTypeFromCLSID(_comObjectClsId); - if (type == null) - { - mshArgE = PSTraceSource.NewArgumentException("ComObject", NewObjectStrings.CannotLoadComObjectType, ComObject); - ThrowTerminatingError( - new ErrorRecord(mshArgE, "CannotLoadComObjectType", ErrorCategory.InvalidType, null)); - } - return SafeCreateInstance(type, ArgumentList); - } - catch (COMException e) - { - //Check Error Code to see if Error is because of Com apartment Mismatch. - if (e.HResult == RPC_E_CHANGED_MODE) - { -#if CORECLR - ThrowTerminatingError( - new ErrorRecord( - new COMException(StringUtil.Format(NewObjectStrings.ApartmentNotSupported, e.Message), e), - "NoCOMClassIdentified", - ErrorCategory.ResourceUnavailable, null)); -#else - createInfo = new ComCreateInfo(); - - Thread thread = new Thread(new ParameterizedThreadStart(STAComCreateThreadProc)); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(createInfo); - - thread.Join(); - - if (createInfo.success == true) - { - return createInfo.objectCreated; - } - - ThrowTerminatingError( - new ErrorRecord(createInfo.e, "NoCOMClassIdentified", - ErrorCategory.ResourceUnavailable, null)); -#endif - } - else - { - ThrowTerminatingError( - new ErrorRecord( - e, - "NoCOMClassIdentified", - ErrorCategory.ResourceUnavailable, null)); - } - - return null; - } - } - - #endregion Com -#endif - - // HResult code '-2147417850' - Cannot change thread mode after it is set. - private const int RPC_E_CHANGED_MODE = unchecked((int)0x80010106); - private const string netSetName = "Net"; - }//internal class NewObjectCommand: PSCmdlet - - /// - /// Native methods for dealing with COM objects - /// - internal 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/neweventcommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/neweventcommand.cs deleted file mode 100644 index 29ac9dfa987..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/neweventcommand.cs +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// Generates a new event notification. - /// - [Cmdlet(VerbsCommon.New, "Event", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135234")] - [OutputType(typeof(PSEventArgs))] - public class NewEventCommand : PSCmdlet - { - #region parameters - - /// - /// Adds an event to the event queue - /// - [Parameter(Position = 0, Mandatory = true)] - public string SourceIdentifier - { - get - { - return _sourceIdentifier; - } - set - { - _sourceIdentifier = value; - } - } - private string _sourceIdentifier = null; - - /// - /// Data relating to this event - /// - [Parameter(Position = 1)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public PSObject Sender - { - get - { - return _sender; - } - set - { - _sender = value; - } - } - private PSObject _sender = null; - - /// - /// Data relating to this event - /// - [Parameter(Position = 2)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public PSObject[] EventArguments - { - get - { - return _eventArguments; - } - set - { - if (_eventArguments != null) - { - _eventArguments = value; - } - } - } - private PSObject[] _eventArguments = new PSObject[0]; - - /// - /// Data relating to this event - /// - [Parameter(Position = 3)] - public PSObject MessageData - { - get - { - return _messageData; - } - set - { - _messageData = value; - } - } - private PSObject _messageData = null; - - #endregion parameters - - - /// - /// Add the event to the event queue - /// - protected override void EndProcessing() - { - object[] baseEventArgs = null; - - // Get the BaseObject from the event arguments - if (_eventArguments != null) - { - baseEventArgs = new object[_eventArguments.Length]; - int loopCounter = 0; - foreach (PSObject eventArg in _eventArguments) - { - if (eventArg != null) - baseEventArgs[loopCounter] = eventArg.BaseObject; - - loopCounter++; - } - } - - Object messageSender = null; - if (_sender != null) { messageSender = _sender.BaseObject; } - - // And then generate the event - WriteObject(Events.GenerateEvent(_sourceIdentifier, messageSender, baseEventArgs, _messageData, true, false)); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/select-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/select-object.cs deleted file mode 100644 index b5c4f0a3581..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/select-object.cs +++ /dev/null @@ -1,776 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Management.Automation; -using Microsoft.PowerShell.Commands.Internal.Format; -using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// helper class to do wildcard matching on MshExpressions - /// - internal sealed class MshExpressionFilter - { - /// - /// construct the class, using an array of patterns - /// - /// array of pattern strings to use - internal MshExpressionFilter(string[] wildcardPatternsStrings) - { - if (wildcardPatternsStrings == null) - { - throw new ArgumentNullException("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 - /// - /// MshExpression to test against - /// true if there is a match, else false - internal bool IsMatch(MshExpression 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 - { - protected override void SetEntries() - { - this.hashEntries.Add(new ExpressionEntryDefinition()); - this.hashEntries.Add(new NameEntryDefinition()); - } - } - - /// - /// - /// - [Cmdlet(VerbsCommon.Select, "Object", DefaultParameterSetName = "DefaultParameter", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113387", RemotingCapability = RemotingCapability.None)] - public sealed class SelectObjectCommand : PSCmdlet - { - #region Command Line Switches - - /// - /// - /// - /// - [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; - - - /// - /// - /// - /// - [Parameter(Position = 0, ParameterSetName = "DefaultParameter")] - [Parameter(Position = 0, ParameterSetName = "SkipLastParameter")] - public object[] Property { get; set; } - - /// - /// - /// - /// - [Parameter(ParameterSetName = "DefaultParameter")] - [Parameter(ParameterSetName = "SkipLastParameter")] - public string[] ExcludeProperty { get; set; } = null; - - /// - /// - /// - /// - [Parameter(ParameterSetName = "DefaultParameter")] - [Parameter(ParameterSetName = "SkipLastParameter")] - public string ExpandProperty { get; set; } = null; - - /// - /// - /// - /// - [Parameter] - public SwitchParameter Unique - { - get { return _unique; } - set { _unique = value; } - } - private bool _unique; - - /// - /// - /// - /// - [Parameter(ParameterSetName = "DefaultParameter")] - // NTRAID#Windows Out Of Band Releases-927878-2006/03/02 - // Allow zero - [ValidateRange(0, int.MaxValue)] - public int Last - { - get { return _last; } - set { _last = value; _firstOrLastSpecified = true; } - } - private int _last = 0; - - /// - /// - /// - /// - [Parameter(ParameterSetName = "DefaultParameter")] - // NTRAID#Windows Out Of Band Releases-927878-2006/03/02 - // Allow zero - [ValidateRange(0, int.MaxValue)] - public int First - { - get { return _first; } - set { _first = value; _firstOrLastSpecified = true; } - } - private int _first = 0; - private bool _firstOrLastSpecified; - - - /// - /// Skips the specified number of items from top when used with First,from end when used with Last - /// - /// - [Parameter(ParameterSetName = "DefaultParameter")] - [ValidateRange(0, int.MaxValue)] - public int Skip { get; set; } = 0; - - /// - /// Skip the specified number of items from end. - /// - [Parameter(ParameterSetName = "SkipLastParameter")] - [ValidateRange(0, int.MaxValue)] - public int SkipLast { get; set; } = 0; - - /// - /// With this switch present, the cmdlet won't "short-circuit" - /// (i.e. won't stop upstream cmdlets after it knows that no further objects will be emitted downstream) - /// - [Parameter(ParameterSetName = "DefaultParameter")] - [Parameter(ParameterSetName = "IndexParameter")] - public SwitchParameter Wait { get; set; } - - /// - /// Used to display the object at specified index - /// - /// - [Parameter(ParameterSetName = "IndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public int[] Index - { - get - { - return _index; - } - set - { - _index = value; - _indexSpecified = true; - Array.Sort(_index); - } - } - private int[] _index; - private bool _indexSpecified; - - #endregion - - private SelectObjectQueue _selectObjectQueue; - - private class SelectObjectQueue : Queue - { - internal SelectObjectQueue(int first, int last, int skip, int skipLast, bool firstOrLastSpecified) - { - _first = first; - _last = last; - _skip = skip; - _skipLast = skipLast; - _firstOrLastSpecified = firstOrLastSpecified; - } - - public bool AllRequestedObjectsProcessed - { - get - { - return _firstOrLastSpecified && _last == 0 && _first != 0 && _streamedObjectCount >= _first; - } - } - - public new void Enqueue(PSObject obj) - { - if (_last > 0 && this.Count >= (_last + _skip) && _first == 0) - { - base.Dequeue(); - } - else if (_last > 0 && this.Count >= _last && _first != 0) - { - base.Dequeue(); - } - base.Enqueue(obj); - } - - public PSObject StreamingDequeue() - { - //if skip parameter is not mentioned or there are no more objects to skip - if (_skip == 0) - { - if (_skipLast > 0) - { - // We are going to skip some items from end, but it's okay to process - // the early input objects once we have more items in queue than the - // specified 'skipLast' value. - if (this.Count > _skipLast) - { - return Dequeue(); - } - } - else - { - if (_streamedObjectCount < _first || !_firstOrLastSpecified) - { - Diagnostics.Assert(this.Count > 0, "Streaming an empty queue"); - _streamedObjectCount++; - return Dequeue(); - } - - if (_last == 0) - { - Dequeue(); - } - } - } - else - { - //if last parameter is not mentioned,remove the objects and decrement the skip - if (_last == 0) - { - Dequeue(); - _skip--; - } - else if (_first != 0) - { - _skip--; - Dequeue(); - } - } - - return null; - } - - private int _streamedObjectCount; - private int _first,_last,_skip,_skipLast; - private bool _firstOrLastSpecified; - } - - /// - /// list of processed parameters obtained from the Expression array - /// - private List _propertyMshParameterList; - - /// - /// singleton list of process parameters obtained from ExpandProperty - /// - private List _expandMshParameterList; - - - - private MshExpressionFilter _exclusionFilter; - - private class UniquePSObjectHelper - { - internal UniquePSObjectHelper(PSObject o, int notePropertyCount) - { - WrittenObject = o; - NotePropertyCount = notePropertyCount; - } - internal readonly PSObject WrittenObject; - internal int NotePropertyCount { get; } - } - - private List _uniques = null; - - private void ProcessExpressionParameter() - { - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); - ParameterProcessor processor = - new ParameterProcessor(new SelectObjectExpressionParameterDefinition()); - if ((Property != null) && (Property.Length != 0)) - { - // Build property list taking into account the wildcards and @{name=;expression=} - _propertyMshParameterList = processor.ProcessParameters(Property, invocationContext); - } - else - { - // Property don't exist - _propertyMshParameterList = new List(); - } - - if (!string.IsNullOrEmpty(ExpandProperty)) - { - _expandMshParameterList = processor.ProcessParameters(new string[] { ExpandProperty }, invocationContext); - } - - if (ExcludeProperty != null) - { - _exclusionFilter = new MshExpressionFilter(ExcludeProperty); - // ExcludeProperty implies -Property * for better UX - if ((Property == null) || (Property.Length == 0)) - { - Property = new Object[]{"*"}; - _propertyMshParameterList = processor.ProcessParameters(Property, invocationContext); - } - } - } - - private void ProcessObject(PSObject inputObject) - { - if ((Property == null || Property.Length == 0) && string.IsNullOrEmpty(ExpandProperty)) - { - FilteredWriteObject(inputObject, new List()); - return; - } - - - //If property parameter is mentioned - List matchedProperties = new List(); - foreach (MshParameter p in _propertyMshParameterList) - { - ProcessParameter(p, inputObject, matchedProperties); - } - - if (string.IsNullOrEmpty(ExpandProperty)) - { - PSObject result = new PSObject(); - if (matchedProperties.Count != 0) - { - HashSet propertyNames = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (PSNoteProperty noteProperty in matchedProperties) - { - try - { - if (!propertyNames.Contains(noteProperty.Name)) - { - propertyNames.Add(noteProperty.Name); - result.Properties.Add(noteProperty); - } - else - { - WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, - "AlreadyExistingUserSpecifiedPropertyNoExpand"); - } - } - catch (ExtendedTypeSystemException) - { - WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, - "AlreadyExistingUserSpecifiedPropertyNoExpand"); - } - } - } - FilteredWriteObject(result, matchedProperties); - } - else - { - ProcessExpandParameter(_expandMshParameterList[0], inputObject, matchedProperties); - } - } - - - - private void ProcessParameter(MshParameter p, PSObject inputObject, List result) - { - string name = p.GetEntry(NameEntryDefinition.NameEntryKey) as string; - - MshExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - List expressionResults = new List(); - foreach (MshExpression resolvedName in ex.ResolveNames(inputObject)) - { - if (_exclusionFilter == null || !_exclusionFilter.IsMatch(resolvedName)) - { - List tempExprResults = resolvedName.GetValues(inputObject); - if (tempExprResults == null) continue; - foreach (MshExpressionResult mshExpRes in tempExprResults) - { - expressionResults.Add(mshExpRes); - } - } - } - - // allow 'Select-Object -Property noexist-name' to return a PSObject with property noexist-name, - // unless noexist-name itself contains wildcards - if (expressionResults.Count == 0 && !ex.HasWildCardCharacters) - { - expressionResults.Add(new MshExpressionResult(null, ex, null)); - } - - // if we have an expansion, renaming is not acceptable - else if (!string.IsNullOrEmpty(name) && expressionResults.Count > 1) - { - string errorMsg = SelectObjectStrings.RenamingMultipleResults; - ErrorRecord errorRecord = new ErrorRecord( - new InvalidOperationException(errorMsg), - "RenamingMultipleResults", - ErrorCategory.InvalidOperation, - inputObject); - WriteError(errorRecord); - return; - } - - foreach (MshExpressionResult r in expressionResults) - { - // filter the exclusions, if any - if (_exclusionFilter != null && _exclusionFilter.IsMatch(r.ResolvedExpression)) - continue; - - PSNoteProperty mshProp; - if (string.IsNullOrEmpty(name)) - { - string resolvedExpressionName = r.ResolvedExpression.ToString(); - if (string.IsNullOrEmpty(resolvedExpressionName)) - { - PSArgumentException mshArgE = PSTraceSource.NewArgumentException( - "Property", - SelectObjectStrings.EmptyScriptBlockAndNoName); - ThrowTerminatingError( - new ErrorRecord( - mshArgE, - "EmptyScriptBlockAndNoName", - ErrorCategory.InvalidArgument, null)); - } - mshProp = new PSNoteProperty(resolvedExpressionName, r.Result); - } - else - { - mshProp = new PSNoteProperty(name, r.Result); - } - result.Add(mshProp); - } - } - private void ProcessExpandParameter(MshParameter p, PSObject inputObject, - List matchedProperties) - { - MshExpression ex = p.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - List expressionResults = ex.GetValues(inputObject); - - - if (expressionResults.Count == 0) - { - ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.PropertyNotFound, ExpandProperty), - "ExpandPropertyNotFound", - ErrorCategory.InvalidArgument, - inputObject); - throw new SelectObjectException(errorRecord); - } - if (expressionResults.Count > 1) - { - ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.MutlipleExpandProperties, ExpandProperty), - "MutlipleExpandProperties", - ErrorCategory.InvalidArgument, - inputObject); - throw new SelectObjectException(errorRecord); - } - - MshExpressionResult r = expressionResults[0]; - if (r.Exception == null) - { - // ignore the property value if it's null - if (r.Result == null) { return; } - - System.Collections.IEnumerable results = LanguagePrimitives.GetEnumerable(r.Result); - if (results == null) - { - // add NoteProperties if there is any - // If r.Result is a base object, we don't want to associate the NoteProperty - // directly with it. We want the NoteProperty to be associated only with this - // particular PSObject, so that when the user uses the base object else where, - // its members remain the same as before the Select-Object command run. - PSObject expandedObject = PSObject.AsPSObject(r.Result, true); - AddNoteProperties(expandedObject, inputObject, matchedProperties); - - FilteredWriteObject(expandedObject, matchedProperties); - return; - } - - foreach (object expandedValue in results) - { - // ignore the element if it's null - if (expandedValue == null) { continue; } - - // add NoteProperties if there is any - // If expandedValue is a base object, we don't want to associate the NoteProperty - // directly with it. We want the NoteProperty to be associated only with this - // particular PSObject, so that when the user uses the base object else where, - // its members remain the same as before the Select-Object command run. - PSObject expandedObject = PSObject.AsPSObject(expandedValue, true); - AddNoteProperties(expandedObject, inputObject, matchedProperties); - - FilteredWriteObject(expandedObject, matchedProperties); - } - } - else - { - ErrorRecord errorRecord = new ErrorRecord( - r.Exception, - "PropertyEvaluationExpand", - ErrorCategory.InvalidResult, - inputObject); - throw new SelectObjectException(errorRecord); - } - } - - private void AddNoteProperties(PSObject expandedObject, PSObject inputObject, IEnumerable matchedProperties) - { - foreach (PSNoteProperty noteProperty in matchedProperties) - { - try - { - if (expandedObject.Properties[noteProperty.Name] != null) - { - WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, "AlreadyExistingUserSpecifiedPropertyExpand"); - } - else - { - expandedObject.Properties.Add(noteProperty); - } - } - catch (ExtendedTypeSystemException) - { - WriteAlreadyExistingPropertyError(noteProperty.Name, inputObject, "AlreadyExistingUserSpecifiedPropertyExpand"); - } - } - } - - private void WriteAlreadyExistingPropertyError(string name, object inputObject, string errorId) - { - ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("Property", SelectObjectStrings.AlreadyExistingProperty, name), - errorId, - ErrorCategory.InvalidOperation, - inputObject); - WriteError(errorRecord); - } - private void FilteredWriteObject(PSObject obj, List addedNoteProperties) - { - Diagnostics.Assert(obj != null, "This command should never write null"); - - if (!_unique) - { - if (obj != AutomationNull.Value) - { - SetPSCustomObject(obj); - WriteObject(obj); - } - return; - } - //if only unique is mentioned - else if ((_unique)) - { - bool isObjUnique = true; - foreach (UniquePSObjectHelper uniqueObj in _uniques) - { - ObjectCommandComparer comparer = new ObjectCommandComparer(true, CultureInfo.CurrentCulture, true); - if ((comparer.Compare(obj.BaseObject, uniqueObj.WrittenObject.BaseObject) == 0) && - (uniqueObj.NotePropertyCount == addedNoteProperties.Count)) - { - bool found = true; - foreach (PSNoteProperty note in addedNoteProperties) - { - PSMemberInfo prop = uniqueObj.WrittenObject.Properties[note.Name]; - if (prop == null || comparer.Compare(prop.Value, note.Value) != 0) - { - found = false; - break; - } - } - if (found) - { - isObjUnique = false; - break; - } - } - else - { - continue; - } - } - if (isObjUnique) - { - SetPSCustomObject(obj); - _uniques.Add(new UniquePSObjectHelper(obj, addedNoteProperties.Count)); - } - } - } - - private void SetPSCustomObject(PSObject psObj) - { - if (psObj.ImmediateBaseObject is PSCustomObject) - psObj.TypeNames.Insert(0, "Selected." + InputObject.BaseObject.GetType().ToString()); - } - - private void ProcessObjectAndHandleErrors(PSObject pso) - { - Diagnostics.Assert(pso != null, "Caller should verify pso != null"); - - try - { - ProcessObject(pso); - } - catch (SelectObjectException e) - { - WriteError(e.ErrorRecord); - } - } - - /// - /// - /// - protected override void BeginProcessing() - { - ProcessExpressionParameter(); - - if (_unique) - { - _uniques = new List(); - } - - _selectObjectQueue = new SelectObjectQueue(_first, _last, Skip, SkipLast, _firstOrLastSpecified); - } - - private int _indexOfCurrentObject = 0; - private int _indexCount = 0; - /// - /// - /// - protected override void ProcessRecord() - { - if (InputObject != AutomationNull.Value && InputObject != null) - { - if (!_indexSpecified) - { - _selectObjectQueue.Enqueue(InputObject); - PSObject streamingInputObject = _selectObjectQueue.StreamingDequeue(); - if (streamingInputObject != null) - { - ProcessObjectAndHandleErrors(streamingInputObject); - } - if (_selectObjectQueue.AllRequestedObjectsProcessed && !this.Wait) - { - this.EndProcessing(); - throw new StopUpstreamCommandsException(this); - } - } - else - { - if (_indexOfCurrentObject < _index.Length) - { - int currentlyRequestedIndex = _index[_indexOfCurrentObject]; - if (_indexCount == currentlyRequestedIndex) - { - ProcessObjectAndHandleErrors(InputObject); - while ((_indexOfCurrentObject < _index.Length) && (_index[_indexOfCurrentObject] == currentlyRequestedIndex)) - { - _indexOfCurrentObject++; - } - } - } - - if (!this.Wait && _indexOfCurrentObject >= _index.Length) - { - this.EndProcessing(); - throw new StopUpstreamCommandsException(this); - } - - _indexCount++; - } - } - } - - /// - /// - /// - protected override void EndProcessing() - { - // We can skip this part for 'IndexParameter' and 'SkipLastParameter' sets because: - // 1. 'IndexParameter' set doesn't use selectObjectQueue. - // 2. 'SkipLastParameter' set should have processed all valid input in the ProcessRecord. - if (ParameterSetName == "DefaultParameter") - { - if (_first != 0) - { - while ((_selectObjectQueue.Count > 0)) - { - ProcessObjectAndHandleErrors(_selectObjectQueue.Dequeue()); - } - } - else - { - while ((_selectObjectQueue.Count > 0)) - { - int lenQueue = _selectObjectQueue.Count; - if (lenQueue > Skip) - { - ProcessObjectAndHandleErrors(_selectObjectQueue.Dequeue()); - } - else - { - break; - } - } - } - } - - if (_uniques != null) - { - foreach (UniquePSObjectHelper obj in _uniques) - { - if (obj.WrittenObject == null || obj.WrittenObject == AutomationNull.Value) - { - continue; - } - - WriteObject(obj.WrittenObject); - } - } - } - } - - /// - /// Used only internally for select-object - /// - [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 ErrorRecord ErrorRecord { get; } - - internal SelectObjectException(ErrorRecord errorRecord) - { - ErrorRecord = errorRecord; - } - } -} - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/sort-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/sort-object.cs deleted file mode 100644 index b3288396a30..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/sort-object.cs +++ /dev/null @@ -1,264 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections.Generic; -using System.Management.Automation; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// - /// - [Cmdlet("Sort", - "Object", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113403", - DefaultParameterSetName="Default", - RemotingCapability = RemotingCapability.None)] - public sealed class SortObjectCommand : OrderObjectBase - { - #region Command Line Switches - /// - /// This param specifies if sort order is ascending. - /// - [Parameter] - public SwitchParameter Descending - { - get { return DescendingOrder; } - set { DescendingOrder = value; } - } - /// - /// This param specifies if only unique objects are filtered. - /// - /// - [Parameter] - public SwitchParameter Unique - { - get { return _unique; } - set { _unique = value; } - } - private bool _unique; - #endregion - - /// - /// This param specifies you only want the top N items returned. - /// - [Parameter(ParameterSetName="Default")] - [ValidateRange(1,int.MaxValue)] - public int Top { get; set; } = 0; - - /// - /// This param specifies you only want the bottom N items returned. - /// - [Parameter(ParameterSetName="Bottom", Mandatory=true)] - [ValidateRange(1,int.MaxValue)] - public int Bottom { get; set; } = 0; - - /// - /// Moves unique entries to the front of the list. - /// - private int MoveUniqueEntriesToFront(List sortedData, OrderByPropertyComparer comparer) - { - // If we have sorted data then we know we have at least one unique item - int uniqueCount = sortedData.Count > 0 ? 1 : 0; - - // Move the first of each unique entry to the front of the list - for (int uniqueItemIndex = 0, nextUniqueItemIndex = 1; uniqueItemIndex < sortedData.Count && uniqueCount != Top; uniqueItemIndex++, nextUniqueItemIndex++) - { - // Identify the index of the next unique item - while (nextUniqueItemIndex < sortedData.Count && comparer.Compare(sortedData[uniqueItemIndex], sortedData[nextUniqueItemIndex]) == 0) - { - nextUniqueItemIndex++; - } - - // If there are no more unique items, break - if (nextUniqueItemIndex == sortedData.Count) - { - break; - } - - // Move the next unique item forward and increment the unique item counter - sortedData[uniqueItemIndex + 1] = sortedData[nextUniqueItemIndex]; - uniqueCount++; - } - - return uniqueCount; - } - - /// - /// Sort unsorted OrderByPropertyEntry data using a full sort - /// - private int FullSort(List dataToSort, OrderByPropertyComparer comparer) - { - // Track how many items in the list are sorted - int sortedItemCount = dataToSort.Count; - - // Future: It may be worth comparing List.Sort with SortedSet when handling unique - // records in case SortedSet is faster (SortedSet was not an option in earlier - // versions of PowerShell). - dataToSort.Sort(comparer); - - if (_unique) - { - // Move unique entries to the front of the list (this is significantly faster - // than removing them) - sortedItemCount = MoveUniqueEntriesToFront(dataToSort, comparer); - } - - return sortedItemCount; - } - - /// - /// Sort unsorted OrderByPropertyEntry data using an indexed min-/max-heap sort - /// - private int Heapify(List dataToSort, OrderByPropertyComparer orderByPropertyComparer) - { - // Instantiate the Heapify comparer, which takes index into account for sort stability - var comparer = new IndexedOrderByPropertyComparer(orderByPropertyComparer); - - // Identify how many items will be in the heap and the current number of items - int heapCount = 0; - int heapCapacity = Top > 0 ? Top : Bottom; - - // Identify the comparator (the value all comparisons will be made against based on whether we're - // doing a Top N or Bottom N sort) - // Note: All comparison results in the loop below are performed related to the value of the - // comparator. OrderByPropertyComparer.Compare will return -1 to indicate that the lhs is smaller - // if an ascending sort is being executed, or -1 to indicate that the lhs is larger if a descending - // sort is being executed. The comparator will be -1 if we're executing a Top N sort, or 1 if we're - // executing a Bottom N sort. These two pairs of states allow us to perform the proper comparison - // regardless of whether we're executing an ascending or descending Top N or Bottom N sort. This - // allows us to build a min-heap or max-heap for each of these sorts with the exact same logic. - // Min-heap: used for faster processing of a top N descending sort and a bottom N ascending sort - // Max-heap: used for faster processing of a top N ascending sort and a bottom N descending sort - int comparator = Top > 0 ? -1 : 1; - - // For unique sorts, use a sorted set to avoid adding unique items to the heap - SortedSet uniqueSet = _unique ? new SortedSet(orderByPropertyComparer) : null; - - // 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++) - { - // 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 - if (heapCount == heapCapacity && comparer.Compare(dataToSort[0], dataToSort[dataIndex]) == comparator) - { - continue; - } - - // If we're doing a unique sort and the entry is not unique, discard the duplicate entry - if (_unique && !uniqueSet.Add(dataToSort[dataIndex])) - { - 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--; - } - continue; - } - - // Add the current item to the heap and bubble it up into the correct position - int childIndex = dataIndex; - while (childIndex > 0) - { - int parentIndex = ((childIndex > (heapCapacity - 1) ? heapCapacity : childIndex) - 1) >> 1; - - // Min-heap: if the child item is larger than its parent, break - // Max-heap: if the child item is smaller than its parent, break - if (comparer.Compare(dataToSort[childIndex], dataToSort[parentIndex]) == comparator) - { - break; - } - - var temp = dataToSort[parentIndex]; - dataToSort[parentIndex] = dataToSort[childIndex]; - dataToSort[childIndex] = temp; - - childIndex = parentIndex; - } - heapCount++; - - // If the heap size is too large, remove the root and rearrange the heap - if (heapCount > heapCapacity) - { - // Move the last item to the root and reset the heap count (this effectively removes the last item) - dataToSort[0] = dataToSort[dataIndex]; - heapCount = heapCapacity; - - // Bubble the root item down into the correct position - int parentIndex = 0; - int parentItemCount = heapCapacity >> 1; - while (parentIndex < parentItemCount) - { - // Min-heap: use the smaller of the two children in the comparison - // Max-heap: use the larger of the two children in the comparison - int leftChildIndex = (parentIndex << 1) + 1; - int rightChildIndex = leftChildIndex + 1; - childIndex = rightChildIndex == heapCapacity || comparer.Compare(dataToSort[leftChildIndex], dataToSort[rightChildIndex]) != comparator - ? leftChildIndex - : rightChildIndex; - - // Min-heap: if the smallest child is larger than or equal to its parent, break - // Max-heap: if the largest child is smaller than or equal to its parent, break - int childComparisonResult = comparer.Compare(dataToSort[childIndex], dataToSort[parentIndex]); - if (childComparisonResult == 0 || childComparisonResult == comparator) - { - break; - } - - var temp = dataToSort[childIndex]; - dataToSort[childIndex] = dataToSort[parentIndex]; - dataToSort[parentIndex] = temp; - - parentIndex = childIndex; - } - } - } - - dataToSort.Sort(0, heapCount, comparer); - - return heapCount; - } - - /// - /// - /// - protected override void EndProcessing() - { - OrderByProperty orderByProperty = new OrderByProperty( - this, InputObjects, Property, !Descending, ConvertedCulture, CaseSensitive); - - var dataToProcess = orderByProperty.OrderMatrix; - var comparer = orderByProperty.Comparer; - if (comparer == null || dataToProcess == null || dataToProcess.Count == 0) - { - return; - } - - // Track the number of items that will be output from the data once it is sorted - int sortedItemCount = dataToProcess.Count; - - // If -Top & -Bottom were not used, or if -Top or -Bottom would return all objects, invoke - // an in-place full sort - if ((Top == 0 && Bottom == 0) || Top >= dataToProcess.Count || Bottom >= dataToProcess.Count) - { - sortedItemCount = FullSort(dataToProcess, comparer); - } - // Otherwise, use an indexed min-/max-heap to perform an in-place sort of all objects - else - { - sortedItemCount = Heapify(dataToProcess, comparer); - } - - // Write out the portion of the processed data that was sorted - for (int index = 0; index < sortedItemCount; index++) - { - WriteObject(dataToProcess[index].inputObject); - } - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/tee-object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/tee-object.cs deleted file mode 100644 index b5520bbad74..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/tee-object.cs +++ /dev/null @@ -1,157 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Management.Automation; -using Microsoft.PowerShell.Commands.Internal.Format; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// Class for Tee-object implementation - /// - [Cmdlet("Tee", "Object", DefaultParameterSetName = "File", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113417")] - public sealed class TeeObjectCommand : PSCmdlet, IDisposable - { - /// - /// object to process - /// - [Parameter(ValueFromPipeline = true)] - public PSObject InputObject - { - get { return _inputObject; } - set { _inputObject = value; } - } - private PSObject _inputObject; - - /// - /// FilePath parameter - /// - [Parameter(Mandatory = true, Position = 0, ParameterSetName = "File")] - public string FilePath - { - get { return _fileName; } - set { _fileName = value; } - } - private string _fileName; - - /// - /// Literal FilePath parameter - /// - [Parameter(Mandatory = true, ParameterSetName = "LiteralFile")] - [Alias("PSPath")] - public string LiteralPath - { - get - { - return _fileName; - } - set - { - _fileName = value; - } - } - - /// - /// Append switch - /// - [Parameter(ParameterSetName = "File")] - public SwitchParameter Append - { - get { return _append; } - set { _append = value; } - } - private bool _append; - - /// - /// Variable parameter - /// - [Parameter(Mandatory = true, ParameterSetName = "Variable")] - public string Variable - { - get { return _variable; } - set { _variable = value; } - } - private string _variable; - - /// - /// - /// - protected override void BeginProcessing() - { - _commandWrapper = new CommandWrapper(); - if (String.Equals(ParameterSetName, "File", StringComparison.OrdinalIgnoreCase)) - { - _commandWrapper.Initialize(Context, "out-file", typeof(OutFileCommand)); - _commandWrapper.AddNamedParameter("filepath", _fileName); - _commandWrapper.AddNamedParameter("append", _append); - } - else if (String.Equals(ParameterSetName, "LiteralFile", StringComparison.OrdinalIgnoreCase)) - { - _commandWrapper.Initialize(Context, "out-file", typeof(OutFileCommand)); - _commandWrapper.AddNamedParameter("LiteralPath", _fileName); - _commandWrapper.AddNamedParameter("append", _append); - } - else - { - // variable parameter set - _commandWrapper.Initialize(Context, "set-variable", typeof(SetVariableCommand)); - _commandWrapper.AddNamedParameter("name", _variable); - // Can't use set-var's passthru because it writes the var object to the pipeline, we want just - // the values to be written - } - } - /// - /// - /// - protected override void ProcessRecord() - { - _commandWrapper.Process(_inputObject); - WriteObject(_inputObject); - } - - /// - /// - /// - protected override void EndProcessing() - { - _commandWrapper.ShutDown(); - } - - private void Dispose(bool isDisposing) - { - if (!_alreadyDisposed) - { - _alreadyDisposed = true; - if (isDisposing && _commandWrapper != null) - { - _commandWrapper.Dispose(); - _commandWrapper = null; - } - } - } - - /// - /// Dispose method in IDisposable - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Finalizer - /// - ~TeeObjectCommand() - { - Dispose(false); - } - #region private - private CommandWrapper _commandWrapper; - private bool _alreadyDisposed; - #endregion private - } -} - 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 8b9103f2923..491aaf85361 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/GetTracerCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Linq; using System.Management.Automation; @@ -8,17 +7,16 @@ namespace Microsoft.PowerShell.Commands { /// - /// A cmdlet that gets the TraceSource instances that are instantiated in the process + /// A cmdlet that gets the TraceSource instances that are instantiated in the process. /// - [Cmdlet(VerbsCommon.Get, "TraceSource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113333")] + [Cmdlet(VerbsCommon.Get, "TraceSource", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096707")] [OutputType(typeof(PSTraceSource))] public class GetTraceSourceCommand : TraceCommandBase { #region Parameters /// - /// Gets or sets the category parameter which determines - /// which trace switch to get. + /// Gets or sets the category parameter which determines which trace switch to get. /// /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] @@ -39,7 +37,8 @@ public string[] Name _names = value; } - } // TraceSource + } + private string[] _names = new string[] { "*" }; #endregion Parameters @@ -47,14 +46,14 @@ public string[] Name #region Cmdlet code /// - /// Gets the PSTraceSource for the specified category + /// Gets the PSTraceSource for the specified category. /// 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); - } // ProcessRecord + } #endregion Cmdlet code } 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 ae0af27e05b..b7e4ed279aa 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs @@ -1,12 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; -using System.Security.Permissions; using System.Management.Automation; using System.Management.Automation.Internal.Host; +using System.Text; namespace Microsoft.PowerShell.Commands { @@ -15,26 +13,24 @@ namespace Microsoft.PowerShell.Commands /// coming from a System.Management.Automation.TraceSwitch /// to be passed to the Msh host's RawUI methods. ///
- /// /// /// 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("") + : base(string.Empty) { if (cmdlet == null) { - throw new PSArgumentNullException("cmdlet"); + throw new PSArgumentNullException(nameof(cmdlet)); } Diagnostics.Assert( @@ -50,16 +46,11 @@ internal PSHostTraceListener(PSCmdlet cmdlet) } /// - /// Closes the TraceListenerDialog so that it no longer - /// receives trace output. + /// Closes the TraceListenerDialog so that it no longer receives trace output. /// - /// /// - /// true if the TraceListener is being disposed, false - /// otherwise. + /// True if the TraceListener is being disposed, false otherwise. /// - /// - [SecurityPermission(SecurityAction.LinkDemand)] protected override void Dispose(bool disposing) { try @@ -78,13 +69,11 @@ protected override void Dispose(bool disposing) #endregion TraceListener constructors and disposer /// - /// Sends the given output string to the host for processing + /// Sends the given output string to the host for processing. /// /// - /// The trace output to be written + /// The trace output to be written. /// - /// - [SecurityPermission(SecurityAction.LinkDemand)] public override void Write(string output) { try @@ -97,20 +86,21 @@ public override void Write(string output) // We don't want tracing to bring down the process. } } - private StringBuilder _cachedWrite = new StringBuilder(); + + private readonly StringBuilder _cachedWrite = new(); /// - /// Sends the given output string to the host for processing + /// Sends the given output string to the host for processing. /// /// - /// The trace output to be written + /// The trace output to be written. /// - [SecurityPermission(SecurityAction.LinkDemand)] public override void WriteLine(string output) { try { _cachedWrite.Append(output); + _cachedWrite.Insert(0, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff ")); _ui.WriteDebugLine(_cachedWrite.ToString()); _cachedWrite.Remove(0, _cachedWrite.Length); @@ -125,6 +115,6 @@ public override void WriteLine(string output) /// /// The host interface to write the debug line to. /// - private InternalHostUserInterface _ui; - } // class PSHostTraceListener -} // namespace System.Management.Automation + 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 49c4c51ffa3..9f8694ebd20 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Diagnostics; @@ -9,9 +8,9 @@ namespace Microsoft.PowerShell.Commands { /// - /// A cmdlet that sets the properties of the TraceSwitch instances that are instantiated in the process + /// A cmdlet that sets the properties of the TraceSwitch instances that are instantiated in the process. /// - [Cmdlet(VerbsCommon.Set, "TraceSource", DefaultParameterSetName = "optionsSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113400")] + [Cmdlet(VerbsCommon.Set, "TraceSource", DefaultParameterSetName = "optionsSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097129")] [OutputType(typeof(PSTraceSource))] public class SetTraceSourceCommand : TraceListenerCommandBase { @@ -21,38 +20,42 @@ public class SetTraceSourceCommand : TraceListenerCommandBase /// The TraceSource parameter determines which TraceSource categories the /// operation will take place on. ///
- /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string[] Name { get { return base.NameInternal; } + set { base.NameInternal = value; } } /// - /// The flags to be set on the TraceSource + /// The flags to be set on the TraceSource. /// - /// [Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ParameterSetName = "optionsSet")] public PSTraceSourceOptions Option { - get { return base.OptionsInternal; } + get + { + return base.OptionsInternal; + } + set { base.OptionsInternal = value; } - } // Flags - + } /// - /// The parameter which determines the options for output from the - /// trace listeners. + /// The parameter which determines the options for output from the trace listeners. /// - /// [Parameter(ParameterSetName = "optionsSet")] public TraceOptions ListenerOption { - get { return base.ListenerOptionsInternal; } + get + { + return base.ListenerOptionsInternal; + } + set { base.ListenerOptionsInternal = value; @@ -60,56 +63,56 @@ public TraceOptions ListenerOption } /// - /// Adds the file trace listener using the specified file + /// Adds the file trace listener using the specified file. /// /// [Parameter(ParameterSetName = "optionsSet")] - [Alias("PSPath")] + [Alias("PSPath", "Path")] public string FilePath { get { return base.FileListener; } + set { base.FileListener = value; } - } // File + } /// - /// Force parameter to control read-only files + /// Force parameter to control read-only files. /// [Parameter(ParameterSetName = "optionsSet")] public SwitchParameter Force { get { return base.ForceWrite; } + set { base.ForceWrite = value; } } /// - /// If this parameter is specified the Debugger trace listener - /// will be added. + /// If this parameter is specified the Debugger trace listener will be added. /// /// [Parameter(ParameterSetName = "optionsSet")] public SwitchParameter Debugger { get { return base.DebuggerListener; } + set { base.DebuggerListener = value; } - } // 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")] public SwitchParameter PSHost { get { return base.PSHostListener; } + set { base.PSHostListener = value; } - } // PSHost + } /// - /// If set, the specified listeners will be removed regardless - /// of their type. + /// If set, the specified listeners will be removed regardless of their type. /// - /// [Parameter(ParameterSetName = "removeAllListenersSet")] [ValidateNotNullOrEmpty] public string[] RemoveListener { get; set; } = new string[] { "*" }; @@ -117,7 +120,6 @@ public SwitchParameter PSHost /// /// If set, the specified file trace listeners will be removed. /// - /// [Parameter(ParameterSetName = "removeFileListenersSet")] [ValidateNotNullOrEmpty] public string[] RemoveFileListener { get; set; } = new string[] { "*" }; @@ -131,8 +133,10 @@ public SwitchParameter PSHost public SwitchParameter PassThru { get { return _passThru; } + set { _passThru = value; } - } // Passthru + } + private bool _passThru; #endregion Parameters @@ -140,7 +144,7 @@ public SwitchParameter PassThru #region Cmdlet code /// - /// Sets the TraceSource properties + /// Sets the TraceSource properties. /// protected override void ProcessRecord() { @@ -157,6 +161,7 @@ protected override void ProcessRecord() WriteObject(matchingSources, true); WriteObject(preconfiguredTraceSources, true); } + break; case "removeAllListenersSet": @@ -169,7 +174,7 @@ protected override void ProcessRecord() RemoveListenersByName(matchingSources, RemoveFileListener, true); break; } - } // ProcessRecord + } #endregion Cmdlet code } 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 292a69db892..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,39 +1,30 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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 { /// - /// A base class for cmdlets that has helper methods for globbing - /// trace source instances + /// A base class for cmdlets that has helper methods for globbing trace source instances. /// public class TraceCommandBase : PSCmdlet { /// - /// Gets the matching PSTraceSource instances for the - /// specified patterns. + /// Gets the matching PSTraceSource instances for the specified patterns. /// - /// /// /// The patterns used to match the PSTraceSource name. /// - /// /// /// If true and the pattern does not contain wildcard patterns and no /// match is found, then WriteError will be called. /// - /// /// /// A collection of the matching PSTraceSource instances. /// - /// internal Collection GetMatchingTraceSource( string[] patternsToMatch, bool writeErrorIfMatchNotFound) @@ -43,27 +34,21 @@ internal Collection GetMatchingTraceSource( } /// - /// Gets the matching PSTraceSource instances for the - /// specified patterns. + /// Gets the matching PSTraceSource instances for the specified patterns. /// - /// /// /// The patterns used to match the PSTraceSource name. /// - /// /// /// If true and the pattern does not contain wildcard patterns and no /// match is found, then WriteError will be called. /// - /// /// /// The patterns for which a match was not found. /// - /// /// /// A collection of the matching PSTraceSource instances. /// - /// internal Collection GetMatchingTraceSource( string[] patternsToMatch, bool writeErrorIfMatchNotFound, @@ -71,12 +56,12 @@ internal Collection GetMatchingTraceSource( { notMatched = new Collection(); - Collection results = new Collection(); + Collection results = new(); foreach (string patternToMatch in patternsToMatch) { bool matchFound = false; - if (String.IsNullOrEmpty(patternToMatch)) + if (string.IsNullOrEmpty(patternToMatch)) { notMatched.Add(patternToMatch); continue; @@ -87,7 +72,7 @@ internal Collection GetMatchingTraceSource( patternToMatch, WildcardOptions.IgnoreCase); - Dictionary traceCatalog = PSTraceSource.TraceCatalog; + Dictionary traceCatalog = PSTraceSource.TraceCatalog; foreach (PSTraceSource source in traceCatalog.Values) { @@ -117,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 bffa86fcc93..1eadd4934b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; @@ -10,7 +9,6 @@ using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Threading; -using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { @@ -18,42 +16,46 @@ namespace Microsoft.PowerShell.Commands /// A cmdlet that traces the specified categories and flags for the duration of the /// specified expression. /// - [Cmdlet(VerbsDiagnostic.Trace, "Command", DefaultParameterSetName = "expressionSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113419")] + [Cmdlet(VerbsDiagnostic.Trace, "Command", DefaultParameterSetName = "expressionSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097136")] public class TraceCommandCommand : TraceListenerCommandBase, IDisposable { #region Parameters /// - /// This parameter specifies the current pipeline object + /// 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 /// operation will take place on. /// - /// [Parameter(Position = 0, Mandatory = true)] public string[] Name { get { return base.NameInternal; } + set { base.NameInternal = value; } } /// - /// The flags to be set on the TraceSource + /// The flags to be set on the TraceSource. /// /// [Parameter(Position = 2)] public PSTraceSourceOptions Option { - get { return base.OptionsInternal; } + get + { + return base.OptionsInternal; + } + set { base.OptionsInternal = value; } - } // Options + } /// /// The parameter for the expression that should be traced. @@ -71,21 +73,23 @@ public PSTraceSourceOptions Option /// /// When set, this parameter is the arguments to pass to the command specified by - /// the -Command parameter + /// the -Command parameter. /// [Parameter(ParameterSetName = "commandSet", ValueFromRemainingArguments = true)] [Alias("Args")] public object[] ArgumentList { get; set; } /// - /// The parameter which determines the options for output from the - /// trace listeners. + /// The parameter which determines the options for output from the trace listeners. /// - /// [Parameter] public TraceOptions ListenerOption { - get { return base.ListenerOptionsInternal; } + get + { + return base.ListenerOptionsInternal; + } + set { base.ListenerOptionsInternal = value; @@ -93,51 +97,52 @@ public TraceOptions ListenerOption } /// - /// Adds the file trace listener using the specified file + /// Adds the file trace listener using the specified file. /// /// [Parameter] - [Alias("PSPath")] + [Alias("PSPath", "Path")] public string FilePath { get { return base.FileListener; } + set { base.FileListener = value; } - } // File + } /// - /// Force parameter to control read-only files + /// Force parameter to control read-only files. /// [Parameter] public SwitchParameter Force { get { return base.ForceWrite; } + set { base.ForceWrite = value; } } /// - /// If this parameter is specified the Debugger trace listener - /// will be added. + /// If this parameter is specified the Debugger trace listener will be added. /// /// [Parameter] public SwitchParameter Debugger { get { return base.DebuggerListener; } + set { base.DebuggerListener = value; } - } // Debugger + } /// - /// If this parameter is specified the Msh Host trace listener - /// will be added. + /// If this parameter is specified the Msh Host trace listener will be added. /// /// [Parameter] public SwitchParameter PSHost { get { return base.PSHostListener; } - set { base.PSHostListener = value; } - } // PSHost + set { base.PSHostListener = value; } + } #endregion Parameters @@ -153,7 +158,6 @@ protected override void BeginProcessing() Collection preconfiguredSources = null; _matchingSources = ConfigureTraceSource(base.NameInternal, false, out preconfiguredSources); - TurnOnTracing(_matchingSources, false); TurnOnTracing(preconfiguredSources, true); @@ -185,13 +189,13 @@ protected override void BeginProcessing() _pipeline.ExternalErrorOutput = new TracePipelineWriter(this, true, _matchingSources); _pipeline.ExternalSuccessOutput = new TracePipelineWriter(this, false, _matchingSources); } + ResetTracing(_matchingSources); } /// /// Executes the expression. - /// - /// Note, this was taken from apply-expression + /// Note, this was taken from apply-expression. /// protected override void ProcessRecord() { @@ -208,6 +212,7 @@ protected override void ProcessRecord() result = StepCommand(); break; } + ResetTracing(_matchingSources); if (result == null) @@ -215,13 +220,11 @@ protected override void ProcessRecord() return; } - if (!LanguagePrimitives.IsNull(result)) { WriteObject(result, true); } - } // ProcessRecord - + } /// /// Finishes running the command if specified and then sets the @@ -239,20 +242,14 @@ protected override void EndProcessing() WriteObject(results, true); } + this.Dispose(); } /// /// 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 @@ -264,7 +261,7 @@ private object RunExpression() dollarUnder: InputObject, input: new object[] { InputObject }, scriptThis: AutomationNull.Value, - args: Utils.EmptyArray()); + args: Array.Empty()); } private object StepCommand() @@ -273,6 +270,7 @@ private object StepCommand() { _pipeline.Step(InputObject); } + return null; } @@ -311,9 +309,11 @@ public void Dispose() fileStream.Dispose(); } } + GC.SuppressFinalize(this); } - } // Dispose + } + private bool _disposed; #endregion IDisposable } @@ -323,23 +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("cmdlet"); - } - - if (matchingSources == null) - { - throw new ArgumentNullException("matchingSources"); - } + ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(matchingSources); _cmdlet = cmdlet; _writeError = writeError; @@ -347,8 +339,7 @@ internal TracePipelineWriter( } /// - /// Get the wait handle signaled when buffer space is available - /// in the underlying stream. + /// Get the wait handle signaled when buffer space is available in the underlying stream. /// public override WaitHandle WaitHandle { @@ -372,7 +363,7 @@ public override bool IsOpen } /// - /// Returns the number of objects in the underlying stream + /// Returns the number of objects in the underlying stream. /// public override int Count { @@ -380,7 +371,7 @@ public override int Count } /// - /// Get the capacity of the stream + /// Get the capacity of the stream. /// /// /// The capacity of the stream. @@ -396,7 +387,7 @@ public override int MaxCapacity } /// - /// Close the stream + /// Close the stream. /// /// /// Causes subsequent calls to IsOpen to return false and calls to @@ -404,7 +395,7 @@ public override int MaxCapacity /// All calls to Close() after the first call are silently ignored. /// /// - /// The stream is already disposed + /// The stream is already disposed. /// public override void Close() { @@ -420,23 +411,23 @@ public override void Close() /// but disposed streams may not. /// /// - /// The underlying stream is disposed + /// The underlying stream is disposed. /// public override void Flush() { } /// - /// Write a single object into the underlying stream + /// Write a single object into the underlying stream. /// - /// The object to add to the stream + /// The object to add to the stream. /// /// One, if the write was successful, otherwise; /// zero if the stream was closed before the object could be written, /// or if the object was AutomationNull.Value. /// /// - /// The underlying stream is closed + /// The underlying stream is closed. /// public override int Write(object obj) { @@ -461,9 +452,9 @@ public override int Write(object obj) } /// - /// Write objects to the underlying stream + /// Write objects to the underlying stream. /// - /// object or enumeration to read from + /// Object or enumeration to read from. /// /// If enumerateCollection is true, and /// is an enumeration according to LanguagePrimitives.GetEnumerable, @@ -471,9 +462,9 @@ public override int Write(object obj) /// written separately. Otherwise, /// will be written as a single object. /// - /// The number of objects written + /// The number of objects written. /// - /// The underlying stream is closed + /// The underlying stream is closed. /// /// /// contains AutomationNull.Value @@ -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 34ddcc2e007..f1e4fe2dfa0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -10,13 +9,12 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Security; -using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// /// A base class for the trace cmdlets that allow you to specify - /// which trace listeners to add to a TraceSource + /// which trace listeners to add to a TraceSource. /// public class TraceListenerCommandBase : TraceCommandBase { @@ -26,23 +24,26 @@ public class TraceListenerCommandBase : TraceCommandBase /// The TraceSource parameter determines which TraceSource categories the /// operation will take place on. /// - /// - internal string[] NameInternal { get; set; } = new string[0]; - + internal string[] NameInternal { get; set; } = Array.Empty(); /// - /// The flags to be set on the TraceSource + /// The flags to be set on the TraceSource. /// /// internal PSTraceSourceOptions OptionsInternal { - get { return _options; } + get + { + return _options; + } + set { _options = value; optionsSpecified = true; } - } // Flags + } + private PSTraceSourceOptions _options = PSTraceSourceOptions.All; /// @@ -51,31 +52,34 @@ internal PSTraceSourceOptions OptionsInternal internal bool optionsSpecified; /// - /// The parameter which determines the options for output from the - /// trace listeners. + /// The parameter which determines the options for output from the trace listeners. /// - /// internal TraceOptions ListenerOptionsInternal { - get { return _traceOptions; } + get + { + return _traceOptions; + } + set { traceOptionsSpecified = true; _traceOptions = value; } } + private TraceOptions _traceOptions = TraceOptions.None; /// - /// True if the TraceOptions parameter was specified, or false otherwise + /// True if the TraceOptions parameter was specified, or false otherwise. /// internal bool traceOptionsSpecified; /// - /// Adds the file trace listener using the specified file + /// Adds the file trace listener using the specified file. /// /// - internal string FileListener { get; set; } // File + internal string FileListener { get; set; } /// /// Property that sets force parameter. This will clear the @@ -84,25 +88,25 @@ internal TraceOptions ListenerOptionsInternal /// /// Note that we do not attempt to reset the read-only attribute. /// - public bool ForceWrite { get; set; } // Force + public bool ForceWrite { get; set; } /// - /// If this parameter is specified the Debugger trace listener - /// will be added. + /// If this parameter is specified the Debugger trace listener will be added. /// /// - internal bool DebuggerListener { get; set; } // Debugger + internal bool DebuggerListener { get; set; } /// - /// If this parameter is specified the Msh Host trace listener - /// will be added. + /// If this parameter is specified the Msh Host trace listener will be added. /// /// internal SwitchParameter PSHostListener { get { return _host; } + set { _host = value; } - } // UseHost + } + private bool _host = false; #endregion Parameters @@ -136,7 +140,7 @@ internal Collection ConfigureTraceSource( foreach (string notMatchedName in notMatched) { - if (String.IsNullOrEmpty(notMatchedName)) + if (string.IsNullOrEmpty(notMatchedName)) { continue; } @@ -149,7 +153,7 @@ internal Collection ConfigureTraceSource( PSTraceSource newTraceSource = PSTraceSource.GetNewTraceSource( notMatchedName, - String.Empty, + string.Empty, true); preconfiguredSources.Add(newTraceSource); @@ -188,10 +192,8 @@ internal Collection ConfigureTraceSource( #region AddTraceListeners /// - /// Adds the console, debugger, file, or host listener - /// if requested. + /// Adds the console, debugger, file, or host listener if requested. /// - /// internal void AddTraceListenersToSources(Collection matchingSources) { if (DebuggerListener) @@ -204,6 +206,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc // Note, this is not meant to be localized. _defaultListener.Name = "Debug"; } + AddListenerToSources(matchingSources, _defaultListener); } @@ -217,6 +220,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc // Note, this is not meant to be localized. _hostListener.Name = "Host"; } + AddListenerToSources(matchingSources, _hostListener); } @@ -231,7 +235,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc try { - Collection resolvedPaths = new Collection(); + Collection resolvedPaths = new(); try { // Resolve the file path @@ -271,6 +275,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc FileListener, provider.FullName)); } + resolvedPaths.Add(path); } @@ -288,26 +293,26 @@ 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.. if ((fInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { - //Make sure the file is not read only + // Make sure the file is not read only fInfo.Attributes &= ~(FileAttributes.ReadOnly); } } } // 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; @@ -329,7 +334,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc if (fileOpenError != null) { ErrorRecord errorRecord = - new ErrorRecord( + new( fileOpenError, "FileListenerPathResolutionFailed", ErrorCategory.OpenError, @@ -354,7 +359,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc if (error != null) { ErrorRecord errorRecord = - new ErrorRecord( + new( error, "FileListenerPathResolutionFailed", ErrorCategory.InvalidArgument, @@ -370,14 +375,14 @@ internal void AddTraceListenersToSources(Collection matchingSourc } } } + private DefaultTraceListener _defaultListener; private PSHostTraceListener _hostListener; private Collection _fileListeners; /// - /// The file streams that were open by this command + /// The file streams that were open by this command. /// - /// internal Collection FileStreams { get; private set; } private static void AddListenerToSources(Collection matchingSources, TraceListener listener) @@ -394,9 +399,8 @@ private static void AddListenerToSources(Collection matchingSourc #region RemoveTraceListeners /// - /// Removes the tracelisteners from the specified trace sources + /// Removes the tracelisteners from the specified trace sources. /// - /// internal static void RemoveListenersByName( Collection matchingSources, string[] listenerNames, @@ -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 @@ -439,15 +443,14 @@ internal static void RemoveListenersByName( } } } - } // RemoveAllTraceListenersFromSource - + } #endregion RemoveTraceListeners #region SetTraceListenerOptions /// - /// Sets the trace listener options based on the ListenerOptions parameter + /// Sets the trace listener options based on the ListenerOptions parameter. /// internal void SetTraceListenerOptions(Collection matchingSources) { @@ -469,9 +472,8 @@ internal void SetTraceListenerOptions(Collection matchingSources) #region SetFlags /// - /// Sets the flags for all the specified TraceSources + /// Sets the flags for all the specified TraceSources. /// - /// internal void SetFlags(Collection matchingSources) { foreach (PSTraceSource structuredSource in matchingSources) @@ -484,8 +486,7 @@ internal void SetFlags(Collection matchingSources) #region TurnOnTracing /// - /// Turns on tracing for the TraceSources, flags, and listeners defined by - /// the parameters + /// Turns on tracing for the TraceSources, flags, and listeners defined by the parameters. /// internal void TurnOnTracing(Collection matchingSources, bool preConfigured) { @@ -496,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); @@ -596,11 +597,12 @@ protected void ClearStoredState() listener.Dispose(); } } + _storedTraceSourceState.Clear(); } - private Dictionary>> _storedTraceSourceState = - new Dictionary>>(); + private readonly Dictionary>> _storedTraceSourceState = + new(); #endregion stored state } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/update-list.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/update-list.cs deleted file mode 100644 index afff8f3b8d5..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/update-list.cs +++ /dev/null @@ -1,191 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections; -using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// This cmdlet updates the property of incoming objects and passes them to the - /// pipeline. This cmdlet also returns a .NET object with properties that - /// defines the update action on a list. - /// - /// This cmdlet is most helpful when the cmdlet author wants the user to do - /// update action on object list that are not directly exposed through - /// cmdlet parameter. One wants to update a property value which is a list - /// (multi-valued parameter for a cmdlet), without exposing the list. - /// - [Cmdlet(VerbsData.Update, "List", DefaultParameterSetName = "AddRemoveSet", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113447", RemotingCapability = RemotingCapability.None)] - public class UpdateListCommand : PSCmdlet - { - /// - /// The following is the definition of the input parameter "Add". - /// Objects to be add to the list - /// - [Parameter(ParameterSetName = "AddRemoveSet")] - [ValidateNotNullOrEmpty()] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - public object[] Add { get; set; } - - /// - /// The following is the definition of the input parameter "Remove". - /// Objects to be removed from the list - /// - [Parameter(ParameterSetName = "AddRemoveSet")] - [ValidateNotNullOrEmpty()] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - public object[] Remove { get; set; } - - /// - /// The following is the definition of the input parameter "Replace". - /// Objects in this list replace the objects in the target list. - /// - [Parameter(Mandatory = true, ParameterSetName = "ReplaceSet")] - [ValidateNotNullOrEmpty()] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - public object[] Replace { get; set; } - - /// - /// The following is the definition of the input parameter "InputObject". - /// List of InputObjects where the updates needs to applied to the - /// specific property - /// - //[Parameter(ValueFromPipeline = true, ParameterSetName = "AddRemoveSet")] - //[Parameter(ValueFromPipeline = true, ParameterSetName = "ReplaceSet")] - [Parameter(ValueFromPipeline = true)] - [ValidateNotNullOrEmpty()] - public PSObject InputObject { get; set; } - - /// - /// The following is the definition of the input parameter "Property". - /// Defines which property of the input object should be updated with Add and - /// Remove actions - /// - //[Parameter(Position = 0, ParameterSetName = "AddRemoveSet")] - //[Parameter(Position = 0, ParameterSetName = "ReplaceSet")] - [Parameter(Position = 0)] - [ValidateNotNullOrEmpty()] - public string Property { get; set; } - - private PSListModifier _listModifier; - - /// - /// ProcessRecord method. - /// - protected override void ProcessRecord() - { - if (Property != null) - { - if (InputObject == null) - { - WriteError(NewError("MissingInputObjectParameter", "MissingInputObjectParameter", null)); - } - else - { - if (_listModifier == null) - { - _listModifier = CreatePSListModifier(); - } - - PSMemberInfo memberInfo = InputObject.Members[Property]; - if (memberInfo != null) - { - try - { - _listModifier.ApplyTo(memberInfo.Value); - WriteObject(InputObject); - } - catch (PSInvalidOperationException e) - { - WriteError(new ErrorRecord(e, "ApplyFailed", ErrorCategory.InvalidOperation, null)); - } - } - else - { - WriteError(NewError("MemberDoesntExist", "MemberDoesntExist", InputObject, Property)); - } - } - } - } - - - /// - /// EndProcessing method. - /// - protected override void EndProcessing() - { - if (Property == null) - { - if (InputObject != null) - { - ThrowTerminatingError(NewError("MissingPropertyParameter", "MissingPropertyParameter", null)); - } - else - { - WriteObject(CreateHashtable()); - } - } - } - - private Hashtable CreateHashtable() - { - Hashtable hash = new Hashtable(2); - if (Add != null) - { - hash.Add("Add", Add); - } - if (Remove != null) - { - hash.Add("Remove", Remove); - } - if (Replace != null) - { - hash.Add("Replace", Replace); - } - return hash; - } - - private PSListModifier CreatePSListModifier() - { - PSListModifier listModifier = new PSListModifier(); - if (Add != null) - { - foreach (object obj in Add) - { - listModifier.Add.Add(obj); - } - } - if (Remove != null) - { - foreach (object obj in Remove) - { - listModifier.Remove.Add(obj); - } - } - if (Replace != null) - { - foreach (object obj in Replace) - { - listModifier.Replace.Add(obj); - } - } - return listModifier; - } - - 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( - new InvalidOperationException(details.Message), - errorId, - ErrorCategory.InvalidOperation, - targetObject); - return errorRecord; - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/write.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/write.cs deleted file mode 100644 index f1b1bcd51a7..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/write.cs +++ /dev/null @@ -1,497 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Runtime.Serialization; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.PowerShell.Commands -{ - #region WriteDebugCommand - /// - /// This class implements Write-Debug command - /// - /// - [Cmdlet(VerbsCommunications.Write, "Debug", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113424", RemotingCapability = RemotingCapability.None)] - public sealed class WriteDebugCommand : PSCmdlet - { - /// - /// Message to be sent and processed if debug mode is on. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyString] - [Alias("Msg")] - public string Message { get; set; } = null; - - - /// - /// This method implements the ProcessRecord method for Write-Debug command - /// - protected override void ProcessRecord() - { - // - // The write-debug command must use the script's InvocationInfo rather than its own, - // 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) - { - DebugRecord record = new DebugRecord(Message); - - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) - { - record.SetInvocationInfo(invocationInfo); - } - - mshCommandRuntime.WriteDebug(record); - } - else - { - WriteDebug(Message); - } - }//processrecord - }//WriteDebugCommand - #endregion WriteDebugCommand - - #region WriteVerboseCommand - /// - /// This class implements Write-Verbose command - /// - /// - [Cmdlet(VerbsCommunications.Write, "Verbose", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113429", RemotingCapability = RemotingCapability.None)] - public sealed class WriteVerboseCommand : PSCmdlet - { - /// - /// Message to be sent if verbose messages are being shown. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyString] - [Alias("Msg")] - public string Message { get; set; } = null; - - - /// - /// This method implements the ProcessRecord method for Write-verbose command - /// - protected override void ProcessRecord() - { - // - // The write-verbose command must use the script's InvocationInfo rather than its own, - // 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) - { - VerboseRecord record = new VerboseRecord(Message); - - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) - { - record.SetInvocationInfo(invocationInfo); - } - - mshCommandRuntime.WriteVerbose(record); - } - else - { - WriteVerbose(Message); - } - }//processrecord - }//WriteVerboseCommand - #endregion WriteVerboseCommand - - #region WriteWarningCommand - /// - /// This class implements Write-Warning command - /// - /// - [Cmdlet(VerbsCommunications.Write, "Warning", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113430", RemotingCapability = RemotingCapability.None)] - public sealed class WriteWarningCommand : PSCmdlet - { - /// - /// Message to be sent if warning messages are being shown. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyString] - [Alias("Msg")] - public string Message { get; set; } = null; - - - /// - /// This method implements the ProcessRecord method for Write-Warning command - /// - protected override void ProcessRecord() - { - // - // The write-warning command must use the script's InvocationInfo rather than its own, - // 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) - { - WarningRecord record = new WarningRecord(Message); - - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) - { - record.SetInvocationInfo(invocationInfo); - } - - mshCommandRuntime.WriteWarning(record); - } - else - { - WriteWarning(Message); - } - }//processrecord - }//WriteWarningCommand - #endregion WriteWarningCommand - - #region WriteInformationCommand - /// - /// This class implements Write-Information command - /// - /// - [Cmdlet(VerbsCommunications.Write, "Information", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=525909", RemotingCapability = RemotingCapability.None)] - public sealed class WriteInformationCommand : PSCmdlet - { - /// - /// Object to be sent to the Information stream. - /// - [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] - [Alias("Msg")] - public Object MessageData { get; set; } - - /// - /// Any tags to be associated with this information - /// - [Parameter(Position = 1)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] Tags { get; set; } - - /// - /// This method implements the processing of the Write-Information command - /// - protected override void BeginProcessing() - { - if (Tags != null) - { - foreach (string tag in Tags) - { - if (tag.StartsWith("PS", StringComparison.OrdinalIgnoreCase)) - { - ErrorRecord er = new ErrorRecord( - new InvalidOperationException(StringUtil.Format(UtilityCommonStrings.PSPrefixReservedInInformationTag, tag)), - "PSPrefixReservedInInformationTag", ErrorCategory.InvalidArgument, tag); - ThrowTerminatingError(er); - } - } - } - } - - /// - /// This method implements the ProcessRecord method for Write-Information command - /// - protected override void ProcessRecord() - { - WriteInformation(MessageData, Tags); - } - - }//WriteInformationCommand - - #endregion WriteInformationCommand - - - #region WriteOrThrowErrorCommand - - /// - /// This class implements the Write-Error command - /// - 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; - - /// - /// If Exception is specified, this is ErrorRecord.ErrorDetails.Message. - /// Otherwise, the Exception is System.Exception, and this is - /// Exception.Message. - /// - [Parameter(Position = 0, ParameterSetName = "NoException", Mandatory = true, ValueFromPipeline = true)] - [Parameter(ParameterSetName = "WithException")] - [AllowNull] - [AllowEmptyString] - [Alias("Msg")] - public string Message { get; set; } = null; - - /// - /// 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; - - /// - /// ErrorRecord.CategoryInfo.Category - /// - [Parameter(ParameterSetName = "NoException")] - [Parameter(ParameterSetName = "WithException")] - public ErrorCategory Category { get; set; } = ErrorCategory.NotSpecified; - - /// - /// ErrorRecord.ErrorId - /// - [Parameter(ParameterSetName = "NoException")] - [Parameter(ParameterSetName = "WithException")] - public string ErrorId { get; set; } = ""; - - /// - /// ErrorRecord.TargetObject - /// - [Parameter(ParameterSetName = "NoException")] - [Parameter(ParameterSetName = "WithException")] - public object TargetObject { get; set; } = null; - - /// - /// ErrorRecord.ErrorDetails.RecommendedAction - /// - [Parameter] - public string RecommendedAction { get; set; } = ""; - - /* 2005/01/25 removing throw-error - /// - /// If true, this is throw-error. Otherwise, this is write-error. - /// - internal bool _terminating = false; - */ - - /// - /// ErrorRecord.CategoryInfo.Activity - /// - [Parameter] - [Alias("Activity")] - public string CategoryActivity { get; set; } = ""; - - /// - /// ErrorRecord.CategoryInfo.Reason - /// - [Parameter] - [Alias("Reason")] - public string CategoryReason { get; set; } = ""; - - /// - /// ErrorRecord.CategoryInfo.TargetName - /// - [Parameter] - [Alias("TargetName")] - public string CategoryTargetName { get; set; } = ""; - - /// - /// ErrorRecord.CategoryInfo.TargetType - /// - [Parameter] - [Alias("TargetType")] - public string CategoryTargetType { get; set; } = ""; - - - /// - /// Write an error to the output pipe, or throw a terminating error. - /// - protected override void ProcessRecord() - { - ErrorRecord errorRecord = this.ErrorRecord; - if (null != errorRecord) - { - // copy constructor - errorRecord = new ErrorRecord(errorRecord, null); - } - else - { - Exception e = this.Exception; - string msg = Message; - if (null == e) - { - e = new WriteErrorException(msg); - } - string errid = ErrorId; - if (String.IsNullOrEmpty(errid)) - { - errid = e.GetType().FullName; - } - errorRecord = new ErrorRecord( - e, - errid, - Category, - TargetObject - ); - - if ((null != this.Exception && !String.IsNullOrEmpty(msg))) - { - errorRecord.ErrorDetails = new ErrorDetails(msg); - } - } - - string recact = RecommendedAction; - if (!String.IsNullOrEmpty(recact)) - { - if (null == errorRecord.ErrorDetails) - { - errorRecord.ErrorDetails = new ErrorDetails(errorRecord.ToString()); - } - errorRecord.ErrorDetails.RecommendedAction = recact; - } - - if (!String.IsNullOrEmpty(CategoryActivity)) - errorRecord.CategoryInfo.Activity = CategoryActivity; - if (!String.IsNullOrEmpty(CategoryReason)) - errorRecord.CategoryInfo.Reason = CategoryReason; - if (!String.IsNullOrEmpty(CategoryTargetName)) - errorRecord.CategoryInfo.TargetName = CategoryTargetName; - if (!String.IsNullOrEmpty(CategoryTargetType)) - errorRecord.CategoryInfo.TargetType = CategoryTargetType; - - /* 2005/01/25 removing throw-error - if (_terminating) - { - ThrowTerminatingError(errorRecord); - } - else - { - */ - - // 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 (null != myInvocation) - { - errorRecord.SetInvocationInfo(myInvocation); - errorRecord.PreserveInvocationInfoOnce = true; - if (!String.IsNullOrEmpty(CategoryActivity)) - errorRecord.CategoryInfo.Activity = CategoryActivity; - else - errorRecord.CategoryInfo.Activity = "Write-Error"; - } - - WriteError(errorRecord); - /* - } - */ - }//processrecord - }//WriteOrThrowErrorCommand - - /// - /// This class implements Write-Error command - /// - [Cmdlet(VerbsCommunications.Write, "Error", DefaultParameterSetName = "NoException", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113425", RemotingCapability = RemotingCapability.None)] - public sealed class WriteErrorCommand : WriteOrThrowErrorCommand - { - /// - /// constructor - /// - public WriteErrorCommand() - { - } - } - - /* 2005/01/25 removing throw-error - /// - /// This class implements Write-Error command - /// - [Cmdlet("Throw", "Error", DefaultParameterSetName = "NoException")] - public sealed class ThrowErrorCommand : WriteOrThrowErrorCommand - { - /// - /// constructor - /// - public ThrowErrorCommand() - { - using (tracer.TraceConstructor(this)) - { - _terminating = true; - } - } - } - */ - - #endregion WriteOrThrowErrorCommand - - #region WriteErrorException - /// - /// The write-error cmdlet uses WriteErrorException - /// when the user only specifies a string and not - /// an Exception or ErrorRecord. - /// - [Serializable] - public class WriteErrorException : SystemException - { - #region ctor - /// - /// Constructor for class WriteErrorException - /// - /// constructed object - public WriteErrorException() - : base(StringUtil.Format(WriteErrorStrings.WriteErrorException)) - { - } - - /// - /// Constructor for class WriteErrorException - /// - /// - /// constructed object - public WriteErrorException(string message) - : base(message) - { - } - - /// - /// Constructor for class WriteErrorException - /// - /// - /// - /// constructed object - public WriteErrorException(string message, - Exception innerException) - : base(message, innerException) - { - } - #endregion ctor - - #region Serialization - /// - /// Serialization constructor for class WriteErrorException - /// - /// serialization information - /// streaming context - /// constructed object - protected WriteErrorException(SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - #endregion Serialization - } // WriteErrorException - #endregion WriteErrorException -} //namespace - - - diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx index 51762730bac..6b178fb85eb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx @@ -117,17 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - {0}({1}) : {2} - - - 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. + + The source code was already compiled and loaded. Cannot add type. The "{0}" extension is not supported. @@ -135,15 +126,6 @@ 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 '{0}' language requires Microsoft .NET Framework {1}. - - - Cannot add type. The assembly name {0} matches both {1} and {2}. - Cannot add type. The assembly '{0}' could not be found. @@ -156,22 +138,22 @@ 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. - - Cannot add type due to the following exception: {0}. Verify that Microsoft .NET Framework {1} is installed. On x64-based versions of Windows, you must also install the WOW64 feature. - - - Cannot add type. The '{0}' parameter and the '{1}' parameter cannot both be specified. - Cannot add type. Definition of new types is not supported in this language mode. 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/ConvertFromStringData.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertFromStringData.resx index cf223fb649f..285dea7301e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertFromStringData.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertFromStringData.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 type of the input object '{0}' is invalid. + + + Only FileSystem Provider paths are supported. The file path is not supported: '{0}'. + + + The property {0} of the given object is null or empty. + + + Invalid parameter set name: {0}. + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertStringResources.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertStringResources.resx index f4994c36d50..9e65acdc23c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertStringResources.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertStringResources.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/GetUptimeStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/GetUptimeStrings.resx index 89b82e007c6..f59439da714 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/GetUptimeStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/GetUptimeStrings.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..cc607eda418 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 parse the JSON schema. + + + Cannot parse the JSON. + + + 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/UnblockFileStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx new file mode 100644 index 00000000000..b2af7eba67e --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 cmdlet does not support Linux. + + + There was an error unblocking {0}. + + 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 651d75d1a32..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,16 +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. - - '{0}' is not supported in this system. + + Cannot construct a security descriptor from the given SDDL due to the following error: {0} + + + Invoke-Expression Cmdlet - - The file is not blocked: {0} + + 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 0c50ddd38ca..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. @@ -144,6 +144,9 @@ The cmdlet cannot run because the following conflicting parameters are specified: InFile and Form. Specify either InFile or Form, then retry. + + The cmdlet cannot run because the -ContentType parameter is not a valid Content-Type header. Specify a valid Content-Type for -ContentType, then retry. To suppress header validation, supply the -SkipHeaderValidation parameter. + The cmdlet cannot run because the following conflicting parameters are specified: Credential and UseDefaultCredentials. Specify either Credential or UseDefaultCredentials, then retry. @@ -156,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. @@ -184,25 +184,28 @@ Path '{0}' is not a file system path. Please specify the path to a file in the file system. - The cmdlet cannot run because the following parameter is missing: OutFile. Provide a valid OutFile parameter value when using the PassThru parameter, then retry. + The cmdlet cannot run because the following parameter is missing: OutFile. Provide a valid OutFile parameter value when using the {0} parameter, then retry. + + + The file will not be re-downloaded because the remote file is the same size as the OutFile: {0} 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. @@ -213,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} @@ -243,10 +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 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}. - - received {0}-byte response of content type {1} + + 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/resources/WriteErrorStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/WriteErrorStrings.resx index 435fef2b837..13d2058e7b2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/WriteErrorStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/WriteErrorStrings.resx @@ -1,4 +1,4 @@ - + - $(DefineConstants);CORECLR + + + + @@ -24,16 +26,4 @@ - - portable - - - - $(DefineConstants);UNIX - - - - full - - diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs new file mode 100644 index 00000000000..8cc6bd8ab02 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.PowerShell +{ + internal static class ComInterfaces + { + [DllImport("kernel32.dll", SetLastError = false, EntryPoint = "GetStartupInfoW")] + internal static extern void GetStartupInfo(out StartUpInfo lpStartupInfo); + + /// + /// IntPtr is being used for the string fields to make the marshaller faster and + /// simpler. With IntPtr, all fields are blittable, and since we don't use the + /// string fields at all, nothing is lost. + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly struct StartUpInfo + { + public readonly uint cb; + private readonly IntPtr lpReserved; + public readonly IntPtr lpDesktop; + public readonly IntPtr lpTitle; + 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; + public readonly IntPtr hStdError; + } + + [ComImport] + [Guid("00021401-0000-0000-C000-000000000046")] + [ClassInterface(ClassInterfaceType.None)] + internal class CShellLink { } + + [ComImport] + [Guid("000214F9-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellLinkW + { + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxPath, + IntPtr pfd, + uint fFlags); + + void GetIDList(out IntPtr ppidl); + + void SetIDList(IntPtr pidl); + + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxName); + + void SetDescription( + [MarshalAs(UnmanagedType.LPWStr)] string pszName); + + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, + int cchMaxPath + ); + + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPWStr)] string pszDir); + + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, + int cchMaxPath); + + 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( + [Out(), MarshalAs(UnmanagedType.LPWStr)] out StringBuilder pszIconPath, + int cchIconPath, + out int iIcon); + + void SetIconLocation( + [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, + int iIcon); + + void SetRelativePath( + [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, + uint dwReserved); + + void Resolve(IntPtr hwnd, uint fFlags); + + void SetPath( + [MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } + + /// + /// A property store. + /// + [ComImport] + [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IPropertyStore + { + /// + /// Gets the number of properties contained in the property store. + /// + /// + /// + [PreserveSig] + HResult GetCount([Out] out uint propertyCount); + + /// + /// Get a property key located at a specific index. + /// + /// + /// + /// + [PreserveSig] + HResult GetAt([In] uint propertyIndex, out PropertyKey key); + + /// + /// Gets the value of a property from the store. + /// + /// + /// + /// + [PreserveSig] + HResult GetValue([In] in PropertyKey key, [Out] PropVariant pv); + + /// + /// Sets the value of a property in the store. + /// + /// + /// + /// + [PreserveSig] + HResult SetValue([In] in PropertyKey key, [In] PropVariant pv); + + /// + /// Commits the changes. + /// + /// + [PreserveSig] + HResult Commit(); + } + + [ComImport] + [Guid("6332DEBF-87B5-4670-90C0-5E57B408A49E")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface ICustomDestinationList + { + void SetAppID( + [MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + + [PreserveSig] + HResult BeginList( + out uint cMaxSlots, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + [PreserveSig] + HResult AppendCategory( + [MarshalAs(UnmanagedType.LPWStr)] string pszCategory, + [MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + + void AppendKnownCategory( + [MarshalAs(UnmanagedType.I4)] KnownDestinationCategory category); + + [PreserveSig] + HResult AddUserTasks( + [MarshalAs(UnmanagedType.Interface)] IObjectArray poa); + + void CommitList(); + + void GetRemovedDestinations( + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + void DeleteList( + [MarshalAs(UnmanagedType.LPWStr)] string pszAppID); + + void AbortList(); + } + + internal enum KnownDestinationCategory + { + Frequent = 1, + Recent + } + + [ComImport] + [Guid("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectArray + { + void GetCount(out uint cObjects); + + void GetAt( + uint iIndex, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + } + + [ComImport] + [Guid("5632B1A4-E38A-400A-928A-D4CD63230295")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IObjectCollection + { + // IObjectArray + void GetCount(out uint cObjects); + + void GetAt( + uint iIndex, + ref Guid riid, + [Out(), MarshalAs(UnmanagedType.Interface)] out object ppvObject); + + // IObjectCollection + void AddObject( + [MarshalAs(UnmanagedType.Interface)] object pvObject); + + void AddFromArray( + [MarshalAs(UnmanagedType.Interface)] IObjectArray poaSource); + + void RemoveObject(uint uiIndex); + + void Clear(); + } + + [ComImport] + [Guid("45e2b4ae-b1c3-11d0-b92f-00a0c90312e1"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellLinkDataListW + { + [PreserveSig] + int AddDataBlock(IntPtr pDataBlock); + + [PreserveSig] + int CopyDataBlock(uint dwSig, out IntPtr ppDataBlock); + + [PreserveSig] + int RemoveDataBlock(uint dwSig); + + void GetFlags(out uint pdwFlags); + + void SetFlags(uint dwFlags); + } + + [DllImport("ole32.Dll")] + internal static extern HResult CoCreateInstance(ref Guid clsid, + [MarshalAs(UnmanagedType.IUnknown)] object inner, + uint context, + ref Guid uuid, + [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject); + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs new file mode 100644 index 00000000000..4789bcef06f --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell +{ + /// + /// HRESULT Wrapper + /// + internal enum HResult + { + /// + /// S_OK + /// + Ok = 0x0000, + + /// + /// S_FALSE. + /// + False = 0x0001, + + /// + /// E_INVALIDARG. + /// + InvalidArguments = unchecked((int)0x80070057), + + /// + /// E_OUTOFMEMORY. + /// + OutOfMemory = unchecked((int)0x8007000E), + + /// + /// E_NOINTERFACE. + /// + NoInterface = unchecked((int)0x80004002), + + /// + /// E_FAIL. + /// + Fail = unchecked((int)0x80004005), + + /// + /// E_ELEMENTNOTFOUND. + /// + ElementNotFound = unchecked((int)0x80070490), + + /// + /// TYPE_E_ELEMENTNOTFOUND. + /// + TypeElementNotFound = unchecked((int)0x8002802B), + + /// + /// NO_OBJECT. + /// + NoObject = unchecked((int)0x800401E5), + + /// + /// Win32 Error code: ERROR_CANCELLED. + /// + Win32ErrorCanceled = 1223, + + /// + /// ERROR_CANCELLED. + /// + Canceled = unchecked((int)0x800704C7), + + /// + /// The requested resource is in use. + /// + ResourceInUse = unchecked((int)0x800700AA), + + /// + /// The requested resources is read-only. + /// + AccessDenied = unchecked((int)0x80030005) + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs new file mode 100644 index 00000000000..408c59c482a --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerShell +{ + /// + /// Represents the OLE struct PROPVARIANT. + /// This class is intended for internal use only. + /// + /// + /// Originally sourced from https://blogs.msdn.com/adamroot/pages/interop-with-propvariants-in-net.aspx + /// and modified to add ability to set values + /// + [StructLayout(LayoutKind.Explicit)] + 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 readonly ushort _valueType; + + [FieldOffset(8)] + private readonly IntPtr _ptr; + + /// + /// Set a string value. + /// + internal PropVariant(string value) + { + if (value == null) + { + throw new ArgumentException("PropVariantNullString", nameof(value)); + } + + _valueType = (ushort)VarEnum.VT_LPWSTR; + _ptr = Marshal.StringToCoTaskMemUni(value); + } + + /// + /// Disposes the object, calls the clear function. + /// + public void Dispose() + { + PropVariantNativeMethods.PropVariantClear(this); + + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~PropVariant() + { + Dispose(); + } + + 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 new file mode 100644 index 00000000000..93346424dc5 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerShell +{ + /// + /// Defines a unique key for a Shell Property. + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + internal readonly struct PropertyKey : IEquatable + { + #region Public Properties + /// + /// A unique GUID for the property. + /// + public Guid FormatId { get; } + + /// + /// Property identifier (PID) + /// + public int PropertyId { get; } + + #endregion + + #region Public Construction + + /// + /// PropertyKey Constructor. + /// + /// A unique GUID for the property. + /// Property identifier (PID). + internal PropertyKey(Guid formatId, int propertyId) + { + this.FormatId = formatId; + this.PropertyId = propertyId; + } + + #endregion + + #region IEquatable Members + + /// + /// Returns whether this object is equal to another. This is vital for performance of value types. + /// + /// The object to compare against. + /// Equality result. + public bool Equals(PropertyKey other) + { + return other.Equals((object)this); + } + + #endregion + + #region equality and hashing + + /// + /// Returns the hash code of the object. This is vital for performance of value types. + /// + /// + public override int GetHashCode() + { + return FormatId.GetHashCode() ^ PropertyId; + } + + /// + /// Returns whether this object is equal to another. This is vital for performance of value types. + /// + /// The object to compare against. + /// Equality result. + public override bool Equals(object obj) + { + if (obj == null) + return false; + + if (obj is not PropertyKey) + return false; + + PropertyKey other = (PropertyKey)obj; + return other.FormatId.Equals(FormatId) && (other.PropertyId == PropertyId); + } + + /// + /// Implements the == (equality) operator. + /// + /// First property key to compare. + /// Second property key to compare. + /// True if object a equals object b. false otherwise. + public static bool operator ==(PropertyKey propKey1, PropertyKey propKey2) + { + return propKey1.Equals(propKey2); + } + + /// + /// Implements the != (inequality) operator. + /// + /// First property key to compare. + /// Second property key to compare. + /// True if object a does not equal object b. false otherwise. + public static bool operator !=(PropertyKey propKey1, PropertyKey propKey2) + { + return !propKey1.Equals(propKey2); + } + + /// + /// Override ToString() to provide a user friendly string representation. + /// + /// String representing the property key. + public override string ToString() + { + return string.Format(System.Globalization.CultureInfo.InvariantCulture, + "PropertyKeyFormatString", + FormatId.ToString("B"), PropertyId); + } + + #endregion + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs new file mode 100644 index 00000000000..5606eabd567 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Management.Automation; +using System.Reflection; +using System.Threading; + +using static Microsoft.PowerShell.ComInterfaces; + +namespace Microsoft.PowerShell +{ + internal static class TaskbarJumpList + { + // Creating a JumpList entry takes around 55ms when the PowerShell process is interactive and + // owns the current window (otherwise it does a fast exit anyway). Since there is no 'GET' like API, + // we always have to execute this call because we do not know if it has been created yet. + // The JumpList does persist as long as the filepath of the executable does not change but there + // could be disruptions to it like e.g. the bi-annual Windows update, we decided to + // not over-optimize this and always create the JumpList as a non-blocking background STA thread instead. + internal static void CreateRunAsAdministratorJumpList() + { + // The STA apartment state is not supported on NanoServer and Windows IoT. + // Plus, there is not need to create jump list in those environment anyways. + if (!Platform.IsWindowsDesktop) + { + return; + } + + // Some COM APIs are implicitly STA only, therefore the executing thread must run in STA. + var thread = new Thread(() => + { + try + { + CreateElevatedEntry(ConsoleHostStrings.RunAsAdministrator); + } + 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. + } + }); + + try + { + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + catch (ThreadStartException) + { + // STA may not be supported on some platforms + } + } + + 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); + 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"); + + // Check for maximum available slots in JumpList and start creating the custom Destination List + var CLSID_DestinationList = new Guid(@"77f10cf0-3db5-4966-b520-b7c54fd35ed6"); + const uint CLSCTX_INPROC_SERVER = 1; + var IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + var hResult = CoCreateInstance(ref CLSID_DestinationList, null, CLSCTX_INPROC_SERVER, ref IID_IUnknown, out object pCustDestListobj); + if (hResult < 0) + { + Debug.Fail($"Creating ICustomDestinationList failed with HResult '{hResult}'."); + return; + } + + var pCustDestList = (ICustomDestinationList)pCustDestListobj; + hResult = pCustDestList.BeginList(out uint uMaxSlots, new Guid(@"92CA9DCD-5622-4BBA-A805-5E9F541BD8C9"), out object pRemovedItems); + if (hResult < 0) + { + Debug.Fail($"BeginList on ICustomDestinationList failed with HResult '{hResult}'."); + return; + } + + if (uMaxSlots >= 1) + { + // Create JumpListLink + var nativeShellLink = (IShellLinkW)new CShellLink(); + var nativePropertyStore = (IPropertyStore)nativeShellLink; + nativeShellLink.SetPath(cmdPath); + nativeShellLink.SetShowCmd(0); + var shellLinkDataList = (IShellLinkDataListW)nativeShellLink; + shellLinkDataList.GetFlags(out uint flags); + flags |= 0x00800000; // SLDF_ALLOW_LINK_TO_LINK + flags |= 0x00002000; // SLDF_RUNAS_USER + shellLinkDataList.SetFlags(flags); + var PKEY_TITLE = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 2); + hResult = nativePropertyStore.SetValue(in PKEY_TITLE, new PropVariant(title)); + if (hResult < 0) + { + pCustDestList.AbortList(); + Debug.Fail($"SetValue on IPropertyStore with title '{title}' failed with HResult '{hResult}'."); + return; + } + + hResult = nativePropertyStore.Commit(); + if (hResult < 0) + { + pCustDestList.AbortList(); + Debug.Fail($"Commit on IPropertyStore failed with HResult '{hResult}'."); + return; + } + + // Create collection and add JumpListLink + 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; + hResult = CoCreateInstance(ref CLSID_EnumerableObjectCollection, null, CLSCTX_INPROC, ref IID_IUnknown, out object instance); + if (hResult < 0) + { + pCustDestList.AbortList(); + Debug.Fail($"Creating IObjectCollection failed with HResult '{hResult}'."); + return; + } + + var pShortCutCollection = (IObjectCollection)instance; + pShortCutCollection.AddObject((IShellLinkW)nativePropertyStore); + + // Add collection to custom destination list and commit the result + hResult = pCustDestList.AddUserTasks((IObjectArray)pShortCutCollection); + if (hResult < 0) + { + pCustDestList.AbortList(); + Debug.Fail($"AddUserTasks on ICustomDestinationList failed with HResult '{hResult}'."); + return; + } + + pCustDestList.CommitList(); + } + } + } + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 840e8fb4d68..0fb85e740f4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -1,23 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#nullable enable using System; -using System.Text; -using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Globalization; using System.IO; -using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Configuration; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; -using System.Diagnostics; -using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation.Host; -using System.Collections.Generic; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Security; +using System.Text; + +using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell { @@ -28,27 +28,23 @@ namespace Microsoft.PowerShell internal class NullHostUserInterface : PSHostUserInterface { /// - /// RawUI + /// RawUI. /// - public override PSHostRawUserInterface RawUI - { - get { return null; } - } + public override PSHostRawUserInterface? RawUI + => null; /// - /// Prompt + /// Prompt. /// /// /// /// /// public override Dictionary Prompt(string caption, string message, Collection descriptions) - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// - /// PromptForChoice + /// PromptForChoice. /// /// /// @@ -56,12 +52,10 @@ 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 + /// PromptForCredential. /// /// /// @@ -69,12 +63,10 @@ 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 + /// PromptForCredential. /// /// /// @@ -84,37 +76,31 @@ 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 + /// ReadLine. /// /// public override string ReadLine() - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// - /// ReadLineAsSecureString + /// ReadLineAsSecureString. /// /// public override SecureString ReadLineAsSecureString() - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// - /// Write + /// Write. /// /// public override void Write(string value) { } /// - /// Write + /// Write. /// /// /// @@ -123,30 +109,28 @@ public override void Write(ConsoleColor foregroundColor, ConsoleColor background { } /// - /// WriteDebugLine + /// WriteDebugLine. /// /// public override void WriteDebugLine(string message) { } /// - /// WriteErrorLine + /// WriteErrorLine. /// /// public override void WriteErrorLine(string value) - { - Console.Out.WriteLine(value); - } + => Console.Out.WriteLine(value); /// - /// WriteLine + /// WriteLine. /// /// public override void WriteLine(string value) { } /// - /// WriteProgress + /// WriteProgress. /// /// /// @@ -154,14 +138,14 @@ public override void WriteProgress(long sourceId, ProgressRecord record) { } /// - /// WriteVerboseLine + /// WriteVerboseLine. /// /// public override void WriteVerboseLine(string message) { } /// - /// WriteWarningLine + /// WriteWarningLine. /// /// public override void WriteWarningLine(string message) @@ -170,53 +154,146 @@ public override void WriteWarningLine(string message) internal class CommandLineParameterParser { - internal static string[] validParameters = { - "version", - "nologo", - "noexit", -#if STAMODE + private const int MaxPipePathLengthLinux = 108; + private const int MaxPipePathLengthMacOS = 104; + + 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", -#endif - "noprofile", - "noninteractive", - "inputformat", - "outputformat", - "windowstyle", - "encodedcommand", + "command", + "commandwithargs", "configurationname", - "file", + "custompipename", + "encodedcommand", "executionpolicy", - "command", + "file", + "help", + "inputformat", + "login", + "noexit", + "nologo", + "noninteractive", + "noprofile", + "noprofileloadtime", + "outputformat", + "removeworkingdirectorytrailingcharacter", "settingsfile", - "help" + "version", + "windowstyle", + "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) { throw new PSArgumentNullException("hostUI"); } - _hostUI = hostUI; + 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 - _bannerText = bannerText; - _helpText = helpText; + internal ParameterBitmap ParametersUsed = 0; + + internal double ParametersUsedAsDouble + { + get { return (double)ParametersUsed; } } + [Conditional("DEBUG")] + private void AssertArgumentsParsed() + { + if (!_dirty) + { + throw new InvalidOperationException("Parse has not been called yet"); + } + } + + 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; } } @@ -225,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; } } @@ -244,8 +331,7 @@ internal bool NoExit { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _noExit; } } @@ -254,8 +340,7 @@ internal bool SkipProfiles { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _skipUserInit; } } @@ -264,8 +349,7 @@ internal uint ExitCode { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _exitCode; } } @@ -274,8 +358,7 @@ internal bool ExplicitReadCommandsFromStdin { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _explicitReadCommandsFromStdin; } } @@ -284,8 +367,7 @@ internal bool NoPrompt { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _noPrompt; } } @@ -294,226 +376,526 @@ internal Collection Args { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _collectedArgs; } } - internal string ConfigurationName + internal string? ConfigurationFile { - get { return _configurationName; } + get + { + AssertArgumentsParsed(); + return _configurationFile; + } + } + + internal string? 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 Serialization.DataFormat OutputFormat + internal string? CustomPipeName { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); + return _customPipeName; + } + } + internal Serialization.DataFormat OutputFormat + { + get + { + AssertArgumentsParsed(); return _outFormat; } } + internal bool OutputFormatSpecified + { + get + { + AssertArgumentsParsed(); + return _outputFormatSpecified; + } + } + 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; + } } - private void ShowHelp() + internal string? WorkingDirectory { - Dbg.Assert(_helpText != null, "_helpText should not be null"); - _hostUI.WriteLine(""); - _hostUI.Write(_helpText); - if (_showExtendedHelp) + get { - _hostUI.Write(ManagedEntranceStrings.ExtendedHelp); + AssertArgumentsParsed(); +#if !UNIX + if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory?.Length > 0) + { + return _workingDirectory.Remove(_workingDirectory.Length - 1); + } +#endif + return _workingDirectory; } - _hostUI.WriteLine(""); } - private void DisplayBanner() +#if !UNIX + internal bool RemoveWorkingDirectoryTrailingCharacter { - // If banner text is not supplied do nothing. - if (!String.IsNullOrEmpty(_bannerText)) + get { - _hostUI.WriteLine(_bannerText); - _hostUI.WriteLine(); + AssertArgumentsParsed(); + return _removeWorkingDirectoryTrailingCharacter; } } -#if STAMODE - internal bool StaMode + internal DateTimeOffset? UTCTimestamp + { + get + { + AssertArgumentsParsed(); + return _utcTimestamp; + } + } + + internal string? Token + { + get + { + AssertArgumentsParsed(); + return _token; + } + } + + internal bool V2SocketServerMode + { + get + { + AssertArgumentsParsed(); + return _v2SocketServerMode; + } + } +#endif + + #endregion Internal properties + + #region static methods + /// + /// Processes the -SettingFile Argument. + /// + /// + /// The command line parameters to be processed. + /// + /// + /// The index in args to the argument following '-SettingFile'. + /// + /// + /// Returns true if the argument was parsed successfully and false if not. + /// + private bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex) + { + if (settingFileArgIndex >= args.Length) + { + SetCommandLineError(CommandLineParameterParserStrings.MissingSettingsFileArgument); + return false; + } + + string configFile; + try + { + configFile = NormalizeFilePath(args[settingFileArgIndex]); + } + catch (Exception ex) + { + string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message); + SetCommandLineError(error); + return false; + } + + if (!System.IO.File.Exists(configFile)) + { + string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile); + SetCommandLineError(error); + return false; + } + + _settingsFile = configFile; + + return true; + } + + internal static string GetConfigurationNameFromGroupPolicy() + { + // Current user policy takes precedence. + var consoleSessionSetting = Utils.GetPolicySetting(Utils.CurrentUserThenSystemWideConfig); + + return (consoleSessionSetting?.EnableConsoleSessionConfiguration == true && !string.IsNullOrEmpty(consoleSessionSetting?.ConsoleSessionConfigurationName)) ? + consoleSessionSetting.ConsoleSessionConfigurationName : string.Empty; + } + + /// + /// Gets the word in a switch from the current argument or parses a file. + /// For example -foo, /foo, or --foo would return 'foo'. + /// + /// + /// The command line parameters to be processed. + /// + /// + /// The index in args to the argument to process. + /// + /// + /// 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. + /// + private (string switchKey, bool shouldBreak) GetSwitchKey(string[] args, ref int argIndex, ref bool noexitSeen) + { + string switchKey = args[argIndex].Trim(); + if (string.IsNullOrEmpty(switchKey)) + { + return (switchKey: string.Empty, shouldBreak: false); + } + + char firstChar = switchKey[0]; + if (!CharExtensions.IsDash(firstChar) && firstChar != '/') + { + // then it's a file + --argIndex; + ParseFile(args, ref argIndex, noexitSeen); + + return (switchKey: string.Empty, shouldBreak: true); + } + + // chop off the first character so that we're agnostic wrt specifying / or - + // in front of the switch name. + switchKey = switchKey.Substring(1); + + // chop off the second dash so we're agnostic wrt specifying - or -- + if (!string.IsNullOrEmpty(switchKey) && CharExtensions.IsDash(firstChar) && switchKey[0] == firstChar) + { + switchKey = switchKey.Substring(1); + } + + return (switchKey: switchKey, shouldBreak: false); + } + + internal static string NormalizeFilePath(string path) + { + // Normalize slashes + 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(!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 (switchKey.Length >= smallestUnambiguousMatch.Length + && match.StartsWith(switchKey, StringComparison.OrdinalIgnoreCase)); + } + + #endregion + + private void ShowError(PSHostUserInterface hostUI) + { + if (_error != null) + { + hostUI.WriteErrorLine(_error); + } + } + + private void ShowHelp(PSHostUserInterface hostUI, string? helpText) + { + if (helpText is null) + { + return; + } + + if (_showHelp) + { + hostUI.WriteLine(); + hostUI.Write(helpText); + if (_showExtendedHelp) + { + hostUI.Write(ManagedEntranceStrings.ExtendedHelp); + } + + hostUI.WriteLine(); + } + } + + 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) { - // Nano doesn't support STA COM apartment, so on Nano powershell has to use MTA as the default. - // return false; - // Win8: 182409 PowerShell 3.0 should run in STA mode by default - return true; + UpdatesNotification.ShowUpdateNotification(hostUI); } } } -#endif /// - /// /// Processes all the command line parameters to ConsoleHost. Returns the exit code to be used to terminate the process, or /// Success to indicate that the program should continue running. - /// /// /// - /// /// 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 static string GetConfigurationNameFromGroupPolicy() + private void ParseHelper(string[] args) { - // Current user policy takes precedence. - var consoleSessionSetting = Utils.GetPolicySetting(Utils.CurrentUserThenSystemWideConfig); - if (consoleSessionSetting != null) + if (args.Length == 0) { - if (consoleSessionSetting.EnableConsoleSessionConfiguration == true) - { - if (!string.IsNullOrEmpty(consoleSessionSetting.ConsoleSessionConfigurationName)) - { - return consoleSessionSetting.ConsoleSessionConfigurationName; - } - } + return; } - return string.Empty; - } - - private void ParseHelper(string[] args) - { - Dbg.Assert(args != null, "Argument 'args' to ParseHelper should never be null"); bool noexitSeen = false; for (int i = 0; i < args.Length; ++i) { - // Invariant culture used because command-line parameters are not localized. - - string switchKey = args[i].Trim().ToLowerInvariant(); - if (String.IsNullOrEmpty(switchKey)) - { - continue; - } - - if (!SpecialCharacters.IsDash(switchKey[0]) && switchKey[0] != '/') + (string switchKey, bool shouldBreak) switchKeyResults = GetSwitchKey(args, ref i, ref noexitSeen); + if (switchKeyResults.shouldBreak) { - // then its a file - - --i; - ParseFile(args, ref i, noexitSeen); break; } - // chop off the first character so that we're agnostic wrt specifying / or - - // in front of the switch name. - switchKey = switchKey.Substring(1); - - // chop off the second dash so we're agnostic wrt specifying - or -- - if (!String.IsNullOrEmpty(switchKey) && SpecialCharacters.IsDash(switchKey[0])) - { - switchKey = switchKey.Substring(1); - } + string switchKey = switchKeyResults.switchKey; // If version is in the commandline, don't continue to look at any other parameters if (MatchSwitch(switchKey, "version", "v")) @@ -523,62 +905,151 @@ private void ParseHelper(string[] args) _noInteractive = true; _skipUserInit = true; _noExit = false; + ParametersUsed |= ParameterBitmap.Version; break; } - else if (MatchSwitch(switchKey, "help", "h") || MatchSwitch(switchKey, "?", "?")) + + if (MatchSwitch(switchKey, "help", "h") || MatchSwitch(switchKey, "?", "?")) { _showHelp = true; _showExtendedHelp = true; _abortStartup = true; + ParametersUsed |= ParameterBitmap.Help; + } + else if (MatchSwitch(switchKey, "login", "l")) + { + // On Windows, '-Login' does nothing. + // On *nix, '-Login' is already handled much earlier to improve startup performance, so we do nothing here. + ParametersUsed |= ParameterBitmap.Login; } else if (MatchSwitch(switchKey, "noexit", "noe")) { _noExit = true; noexitSeen = true; + ParametersUsed |= ParameterBitmap.NoExit; } else if (MatchSwitch(switchKey, "noprofile", "nop")) { _skipUserInit = true; + ParametersUsed |= ParameterBitmap.NoProfile; } else if (MatchSwitch(switchKey, "nologo", "nol")) { _showBanner = false; + ParametersUsed |= ParameterBitmap.NoLogo; } else if (MatchSwitch(switchKey, "noninteractive", "noni")) { _noInteractive = true; + ParametersUsed |= ParameterBitmap.NonInteractive; } else if (MatchSwitch(switchKey, "socketservermode", "so")) { _socketServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.SocketServerMode; + } +#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) + { + SetCommandLineError( + CommandLineParameterParserStrings.MissingCustomPipeNameArgument); + break; + } + +#if UNIX + int maxNameLength = MaxNameLength(); + if (args[i].Length > maxNameLength) + { + 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")) { @@ -586,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")) @@ -622,78 +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", "w")) - { - // 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) && SpecialCharacters.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")) { @@ -702,6 +1137,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.EncodedCommand; } else if (MatchSwitch(switchKey, "encodedarguments", "encodeda") || MatchSwitch(switchKey, "ea", "ea")) { @@ -709,86 +1146,121 @@ private void ParseHelper(string[] args) { break; } - } - else if (MatchSwitch(switchKey, "settingsfile", "settings") ) + ParametersUsed |= ParameterBitmap.EncodedArgument; + } + else if (MatchSwitch(switchKey, "settingsfile", "settings")) { - ++i; - if (i >= args.Length) - { - WriteCommandLineError( - CommandLineParameterParserStrings.MissingSettingsFileArgument); - break; - } - string configFile = null; - try - { - configFile = NormalizeFilePath(args[i]); - } - catch (Exception ex) + // Parse setting file arg and write error + if (!TryParseSettingFileHelper(args, ++i)) { - string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[i], ex.Message); - WriteCommandLineError(error); break; } - if (!System.IO.File.Exists(configFile)) + ParametersUsed |= ParameterBitmap.SettingsFile; + } + else if (MatchSwitch(switchKey, "sta", "sta")) + { + if (!Platform.IsWindowsDesktop || !Platform.IsStaSupported) { - string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile); - WriteCommandLineError(error); + SetCommandLineError( + CommandLineParameterParserStrings.STANotImplemented); break; } - PowerShellConfig.Instance.SetSystemConfigFilePath(configFile); - } -#if STAMODE - // explicit setting of the ApartmentState Not supported on NanoServer - else if (MatchSwitch(switchKey, "sta", "s")) - { + if (_staMode.HasValue) { // -sta and -mta are mutually exclusive. - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MtaStaMutuallyExclusive); break; } _staMode = true; + ParametersUsed |= ParameterBitmap.STA; } - // Win8: 182409 PowerShell 3.0 should run in STA mode by default..so, consequently adding the switch -mta. - // Not deleting -sta for backward compatability reasons else if (MatchSwitch(switchKey, "mta", "mta")) { + if (!Platform.IsWindowsDesktop) + { + SetCommandLineError( + CommandLineParameterParserStrings.MTANotImplemented); + break; + } + 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) + { + 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(); + // default to filename being the next argument. + ParametersUsed |= ParameterBitmap.File; + } } Dbg.Assert( @@ -797,53 +1269,44 @@ 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); - _showHelp = showHelp; - _showBanner = showBanner; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + ShowError(hostUI); + ShowHelp(hostUI, helpText); + DisplayBanner(hostUI, bannerText); } - private bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch) + private void SetCommandLineError(string msg, bool showHelp = false, bool showBanner = false) { - 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"); - - if (match.Trim().ToLowerInvariant().IndexOf(switchKey, StringComparison.Ordinal) == 0) + if (_error != null) { - if (switchKey.Length >= smallestUnambiguousMatch.Length) - { - return true; - } + throw new ArgumentException(nameof(SetCommandLineError), nameof(_error)); } - return false; + _error = msg; + _showHelp = showHelp; + _showBanner = showBanner; + _abortStartup = true; + _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; } 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(ConsoleHostUserInterface.Crlf); + sb.Append(Environment.NewLine); } ++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; } @@ -853,69 +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]; } - private static string NormalizeFilePath(string path) - { - // Normalize slashes - path = path.Replace(StringLiterals.AlternatePathSeparator, - StringLiterals.DefaultPathSeparator); - - return Path.GetFullPath(path); - } - + // 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); @@ -943,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]); @@ -952,98 +1382,112 @@ 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; } if (!System.IO.File.Exists(_file)) { - if (args[i].StartsWith("-") && args[i].Length > 1) + 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); } } + 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) && SpecialCharacters.IsDash(arg[0])) + 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)); + } + + ++i; } - return true; } private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEncoded) @@ -1051,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; } @@ -1078,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; } } @@ -1097,39 +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) @@ -1147,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; } @@ -1178,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; } @@ -1190,41 +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; -#if STAMODE - // Win8: 182409 PowerShell 3.0 should run in STA mode by default - // -sta and -mta are mutually exclusive..so tracking them using nullable boolean - // if true, then sta is specified on the command line. - // if false, then mta is specified on the command line. - // if null, then none is specified on the command line..use default in this case - // default is sta. + private string? _customPipeName; private bool? _staMode = null; -#endif 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 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 84ae968d209..7bda4bc5688 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs @@ -1,8 +1,7 @@ -#if !UNIX -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#if !UNIX // Implementation notes: In the functions that take ConsoleHandle parameters, we only assert that the handle is valid and not // closed, as opposed to doing a check and throwing an exception. This is because the win32 APIs that those functions wrap will @@ -11,10 +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; @@ -43,16 +41,13 @@ 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 -#region structs + #region structs internal enum InputRecordEventTypes : ushort { @@ -114,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}"); } } @@ -140,6 +135,7 @@ internal struct CONSOLE_FONT_INFO_EX internal short FontHeight; internal int FontFamily; internal int FontWeight; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] internal string FontFace; } @@ -165,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}"); } } @@ -196,14 +192,14 @@ 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}"); } } [StructLayout(LayoutKind.Sequential)] internal struct FONTSIGNATURE { - //From public\sdk\inc\wingdi.h + // From public\sdk\inc\wingdi.h // fsUsb*: A 128-bit Unicode subset bitfield (USB) identifying up to 126 Unicode subranges internal DWORD fsUsb0; @@ -215,42 +211,7 @@ 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 + #region SentInput Data Structures [StructLayout(LayoutKind.Sequential)] internal struct INPUT @@ -279,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; @@ -295,7 +256,7 @@ internal struct MouseInput /// /// A set of bit flags that specify various aspects of mouse motion and button clicks. - /// See (http://msdn.microsoft.com/en-us/library/ms646273(VS.85).aspx) + /// See (https://msdn.microsoft.com/library/ms646273(VS.85).aspx) /// internal DWORD Flags; @@ -305,7 +266,7 @@ internal struct MouseInput internal DWORD Time; /// - /// An additional value associated with the mouse event. An application calls GetMessageExtraInfo to obtain this extra information + /// An additional value associated with the mouse event. An application calls GetMessageExtraInfo to obtain this extra information. /// internal IntPtr ExtraInfo; } @@ -366,33 +327,33 @@ internal struct HardwareInput internal enum VirtualKeyCode : ushort { /// - /// LEFT ARROW key + /// LEFT ARROW key. /// Left = 0x25, /// - /// ENTER key + /// ENTER key. /// Return = 0x0D, } /// - /// Specify the type of the input + /// Specify the type of the input. /// internal enum InputType : uint { /// - /// INPUT_MOUSE = 0x00 + /// INPUT_MOUSE = 0x00. /// Mouse = 0, /// - /// INPUT_KEYBOARD = 0x01 + /// INPUT_KEYBOARD = 0x01. /// Keyboard = 1, /// - /// INPUT_HARDWARE = 0x02 + /// INPUT_HARDWARE = 0x02. /// Hardware = 2, } @@ -421,11 +382,11 @@ internal enum KeyboardFlag : uint ScanCode = 0x0008 } -#endregion SentInput Data Structures + #endregion SentInput Data Structures -#endregion structs + #endregion structs -#region Window Visibility + #region Window Visibility [DllImport(PinvokeDllNames.GetConsoleWindowDllName)] internal static extern IntPtr GetConsoleWindow(); @@ -450,10 +411,11 @@ internal enum KeyboardFlag : uint /// Code to control the display properties of the a window... /// /// The window to show... - /// The command to do - /// true it it was successful + /// The command to do. + /// True if it was successful. [DllImport("user32.dll")] - internal static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow); + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); internal static void SetConsoleMode(ProcessWindowStyle style) { @@ -476,16 +438,13 @@ internal static void SetConsoleMode(ProcessWindowStyle style) } } #endif -#endregion + #endregion -#region Input break handler (Ctrl-C, Ctrl-Break) + #region Input break handler (Ctrl-C, Ctrl-Break) /// - /// - /// Types of control ConsoleBreakSignals received by break Win32Handler delegates - /// + /// 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 @@ -509,18 +468,17 @@ internal enum ConsoleBreakSignal : uint internal delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal); /// - /// Set the console's break handler + /// Set the console's break handler. /// /// /// /// 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(); @@ -531,17 +489,16 @@ internal static void AddBreakHandler(BreakHandler handlerDelegate) } /// - /// Set the console's break handler to null + /// Set the console's break handler to null. /// /// /// If Win32's SetConsoleCtrlHandler fails /// - internal static void RemoveBreakHandler() { bool result = NativeMethods.SetConsoleCtrlHandler(null, false); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -551,16 +508,15 @@ internal static void RemoveBreakHandler() } } -#endregion + #endregion -#region Win32Handles + #region Win32Handles private static readonly Lazy _keyboardInputHandle = new Lazy(() => { 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, @@ -626,14 +582,12 @@ internal static ConsoleHandle GetActiveScreenBufferHandle() return _outputHandle.Value; } -#endregion + #endregion -#region Mode + #region Mode /// - /// - /// flags used by ConsoleControl.GetMode and ConsoleControl.SetMode - /// + /// Flags used by ConsoleControl.GetMode and ConsoleControl.SetMode. /// [Flags] internal enum ConsoleModes : uint @@ -658,14 +612,11 @@ internal enum ConsoleModes : uint } /// - /// - /// Returns a mask of ConsoleModes flags describing the current modality of the console - /// + /// Returns a mask of ConsoleModes flags describing the current modality of the console. /// /// /// If Win32's GetConsoleMode fails /// - internal static ConsoleModes GetMode(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid"); @@ -674,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(); @@ -687,26 +638,17 @@ internal static ConsoleModes GetMode(ConsoleHandle consoleHandle) } /// - /// - /// Sets the current mode of the console device - /// + /// Sets the current mode of the console device. /// /// - /// /// Handle to the console device returned by GetInputHandle - /// /// /// - /// /// Mask of mode flags - /// /// /// - /// /// If Win32's SetConsoleMode fails - /// /// - internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) { Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid"); @@ -714,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(); @@ -724,61 +666,56 @@ internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) } } + #endregion -#endregion - -#region Input - - + #region Input /// - /// /// Reads input from the console device according to the mode in effect (see GetMode, SetMode) - /// /// /// - /// /// Handle to the console device returned by GetInputHandle - /// - /// - /// - /// Initial contents of the edit buffer, if any. charactersToRead should be at least as large as the length of this string. - /// + /// + /// Length of initial content of the edit buffer. Zero if no initial content exists. + /// Must be less than editBuffer length. + /// + /// + /// Edit buffer with optional initial content. + /// Caution! Last position in the edit buffer is for a null in native code. /// /// - /// /// Number of characters to read from the device. - /// + /// Must be less than editBuffer length. /// /// - /// - /// true to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key - /// + /// True to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key /// /// - /// - /// bit mask indicating the state of the control/shift keys at the point input was terminated. - /// + /// Bit mask indicating the state of the control/shift keys at the point input was terminated. + /// /// /// /// - /// /// If Win32's ReadConsole fails - /// /// - - internal static string ReadConsole(ConsoleHandle consoleHandle, string initialContent, - int charactersToRead, bool endOnTab, out uint keyState) + internal static string ReadConsole( + ConsoleHandle consoleHandle, + int initialContentLength, + Span editBuffer, + int charactersToRead, + bool endOnTab, + out uint keyState) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed"); - Dbg.Assert(initialContent != null, "if no initial content is desired, pass String.Empty"); + Dbg.Assert(initialContentLength < editBuffer.Length, "initialContentLength must be less than editBuffer.Length"); + Dbg.Assert(charactersToRead < editBuffer.Length, "charactersToRead must be less than editBuffer.Length"); keyState = 0; CONSOLE_READCONSOLE_CONTROL control = new CONSOLE_READCONSOLE_CONTROL(); control.nLength = (ULONG)Marshal.SizeOf(control); - control.nInitialChars = (ULONG)initialContent.Length; + control.nInitialChars = (ULONG)initialContentLength; control.dwControlKeyState = 0; if (endOnTab) { @@ -787,27 +724,34 @@ internal static string ReadConsole(ConsoleHandle consoleHandle, string initialCo control.dwCtrlWakeupMask = (1 << TAB); } - DWORD charsReadUnused = 0; - StringBuilder buffer = new StringBuilder(initialContent, charactersToRead); + DWORD charsReaded = 0; + bool result = NativeMethods.ReadConsole( consoleHandle.DangerousGetHandle(), - buffer, + editBuffer, (DWORD)charactersToRead, - out charsReadUnused, + out charsReaded, ref control); keyState = control.dwControlKeyState; - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); - HostException e = CreateHostException(err, "ReadConsole", - ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleExceptionTemplate); + HostException e = CreateHostException( + err, + "ReadConsole", + ErrorCategory.ReadError, + ConsoleControlStrings.ReadConsoleExceptionTemplate); throw e; } - if (charsReadUnused > (uint)buffer.Length) - charsReadUnused = (uint)buffer.Length; - return buffer.ToString(0, (int)charsReadUnused); + + if (charsReaded > (uint)charactersToRead) + { + charsReaded = (uint)charactersToRead; + } + + return editBuffer.Slice(0, (int)charsReaded).ToString(); } /// @@ -815,24 +759,17 @@ internal static string ReadConsole(ConsoleHandle consoleHandle, string initialCo /// Returns the number of records read in buffer. /// /// - /// /// handle for the console where input is read - /// /// /// - /// /// array where data read are stored - /// /// /// - /// /// actual number of input records read - /// /// /// /// If Win32's ReadConsoleInput fails /// - internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECORD[] buffer) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -845,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,31 +790,25 @@ internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECO ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleInputExceptionTemplate); throw e; } + return (int)recordsRead; } /// - /// Wraps Win32 PeekConsoleInput + /// Wraps Win32 PeekConsoleInput. /// /// - /// /// handle for the console where input is peeked - /// /// /// - /// /// array where data read are stored - /// /// /// - /// /// actual number of input records peeked - /// /// /// /// If Win32's PeekConsoleInput fails /// - internal static int PeekConsoleInput ( ConsoleHandle consoleHandle, @@ -895,7 +826,7 @@ ref INPUT_RECORD[] buffer (DWORD)buffer.Length, out recordsRead); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -908,22 +839,17 @@ ref INPUT_RECORD[] buffer } /// - /// Wraps Win32 GetNumberOfConsoleInputEvents + /// Wraps Win32 GetNumberOfConsoleInputEvents. /// /// - /// /// handle for the console where the number of console input events is obtained - /// /// /// - /// /// number of console input events - /// /// /// /// If Win32's GetNumberOfConsoleInputEvents fails /// - internal static int GetNumberOfConsoleInputEvents(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -932,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(); @@ -945,12 +871,10 @@ internal static int GetNumberOfConsoleInputEvents(ConsoleHandle consoleHandle) } /// - /// Wraps Win32 FlushConsoleInputBuffer + /// Wraps Win32 FlushConsoleInputBuffer. /// /// - /// /// handle for the console where the input buffer is flushed - /// /// /// /// If Win32's FlushConsoleInputBuffer fails @@ -964,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(); @@ -974,23 +898,19 @@ internal static void FlushConsoleInputBuffer(ConsoleHandle consoleHandle) } } -#endregion Input + #endregion Input -#region Buffer + #region Buffer /// /// Wraps Win32 GetConsoleScreenBufferInfo - /// Returns Console Screen Buffer Info + /// Returns Console Screen Buffer Info. /// /// - /// /// Handle for the console where the screen buffer info is obtained - /// /// /// - /// /// info about the screen buffer. See the definition of CONSOLE_SCREEN_BUFFER_INFO - /// /// /// /// If Win32's GetConsoleScreenBufferInfo fails @@ -1003,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(); @@ -1016,14 +936,13 @@ internal static CONSOLE_SCREEN_BUFFER_INFO GetConsoleScreenBufferInfo(ConsoleHan } /// - /// set the output buffer's size + /// Set the output buffer's size. /// /// /// /// /// If Win32's SetConsoleScreenBufferSize fails /// - internal static void SetConsoleScreenBufferSize(ConsoleHandle consoleHandle, Size newSize) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -1036,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(); @@ -1068,6 +987,7 @@ internal static bool IsConsoleColor(ConsoleColor c) case ConsoleColor.Yellow: return true; } + return false; } @@ -1093,19 +1013,13 @@ internal static WORD ColorToWORD(ConsoleColor foreground, ConsoleColor backgroun /// is constrained. /// /// - /// /// handle for the console where output is written - /// /// /// - /// /// location on screen buffer where writing starts - /// /// /// - /// /// 2D array of cells. Caller needs to ensure that the array is 2D. - /// /// /// /// If Win32's GetConsoleScreenBufferInfo fails @@ -1117,15 +1031,15 @@ 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"); Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed"); if (contents == null) { - throw PSTraceSource.NewArgumentNullException("contents"); + throw PSTraceSource.NewArgumentNullException(nameof(contents)); } + uint codePage; if (IsCJKOutputCodePage(out codePage)) { @@ -1150,11 +1064,11 @@ internal static void WriteConsoleOutput(ConsoleHandle consoleHandle, Coordinates screenRegion.Bottom - screenRegion.Top; #if DEBUG - //Check contents in contentsRegion + // Check contents in contentsRegion CheckWriteConsoleOutputContents(contents, contentsRegion); #endif - //Identify edges and areas of identical contiguous edges in contentsRegion + // Identify edges and areas of identical contiguous edges in contentsRegion List sameEdgeAreas = new List(); int firstLeftTrailingRow = -1, firstRightLeadingRow = -1; BuildEdgeTypeInfo(contentsRegion, contents, @@ -1211,11 +1125,13 @@ private static void BuildEdgeTypeInfo( { firstLeftTrailingRow = r; } + if (firstRightLeadingRow == -1 && ((range.Type & BufferCellArrayRowType.RightLeading) != 0)) { firstRightLeadingRow = r; } - for (;;) + + while (true) { r++; if (r > contentsRegion.Bottom) @@ -1224,6 +1140,7 @@ private static void BuildEdgeTypeInfo( sameEdgeAreas.Add(range); return; } + edgeType = GetEdgeType(contents[r, contentsRegion.Left], contents[r, contentsRegion.Right]); if (edgeType != range.Type) { @@ -1242,10 +1159,12 @@ private static BufferCellArrayRowType GetEdgeType(BufferCell left, BufferCell ri { edgeType |= BufferCellArrayRowType.LeftTrailing; } + if (right.BufferCellType == BufferCellType.Leading) { edgeType |= BufferCellArrayRowType.RightLeading; } + return edgeType; } @@ -1264,11 +1183,11 @@ private enum BufferCellArrayRowType : uint } /// - /// Check the existing screen columns left and right of areas to be written + /// Check the existing screen columns left and right of areas to be written. /// /// /// - /// must be within the screen buffer + /// Must be within the screen buffer. /// /// /// @@ -1295,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 @@ -1311,18 +1229,16 @@ 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}]")); } } } - //Check right edge + // Check right edge if (origin.X + (contentsRegion.Right - contentsRegion.Left) + 1 >= bufferInfo.BufferSize.X) { 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 @@ -1337,14 +1253,12 @@ 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}]")); } } } } - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called in CHK builds")] private static void CheckWriteConsoleOutputContents(BufferCell[,] contents, Rectangle contentsRegion) { @@ -1359,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) @@ -1369,11 +1283,12 @@ private static void CheckWriteConsoleOutputContents(BufferCell[,] contents, Rect { break; } + if (contents[r, c].Character != 0 || contents[r, c].BufferCellType != BufferCellType.Trailing) { // 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}]")); } } } @@ -1449,6 +1364,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat bufferSize.X--; writeRegion.Right--; } + CHAR_INFO[] characterBuffer = new CHAR_INFO[bufferSize.Y * bufferSize.X]; // copy characterBuffer to contents; @@ -1508,6 +1424,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat // We don't output anything for this cell if Raster font is in use, or if the last cell is not a leading byte characterBufferIndex--; } + lastCharIsLeading = false; } } @@ -1523,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, @@ -1535,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, @@ -1545,7 +1457,8 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat bufferCoord, ref writeRegion); } - if (result == false) + + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -1555,6 +1468,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleOutputExceptionTemplate); throw e; } + bufferLimit /= 2; if (cols == colsRemaining) { @@ -1569,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; } @@ -1595,6 +1509,7 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin tracer.WriteLine("contents passed in has 0 rows and columns"); return; } + int bufferLimit = 2 * 1024; // Limit is 8K bytes as each CHAR_INFO takes 4 bytes COORD bufferCoord; @@ -1671,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) @@ -1681,6 +1596,7 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleOutputExceptionTemplate); throw e; } + bufferLimit /= 2; if (cols == colsRemaining) { @@ -1695,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; } @@ -1717,30 +1633,21 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin /// is constrained. /// /// - /// /// handle for the console where output is read - /// /// /// - /// /// location on screen buffer where reading begins - /// /// /// - /// /// indicates the area in where the data read /// is stored. - /// /// /// - /// /// this is ref because the bounds and size of the array are needed. - /// /// /// /// If there is not enough memory to complete calls to Win32's ReadConsoleOutput /// - internal static void ReadConsoleOutput ( ConsoleHandle consoleHandle, @@ -1781,10 +1688,8 @@ 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); ReadConsoleOutputCJK(consoleHandle, codePage, checkOrigin, @@ -1804,10 +1709,10 @@ internal static void ReadConsoleOutput } } -#region ReadConsoleOutput CJK + #region ReadConsoleOutput CJK /// /// If an edge cell read is a blank, it is potentially part of a double width character. Hence, - /// at least one of the left and right edges should be checked + /// at least one of the left and right edges should be checked. /// /// /// @@ -1822,6 +1727,7 @@ private static bool ShouldCheck(int edge, BufferCell[,] contents, Rectangle cont return true; } } + return false; } @@ -1846,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, @@ -1859,6 +1763,7 @@ private static bool ReadConsoleOutputCJKSmall { return false; } + int characterBufferIndex = 0; for (int r = contentsRegion.Top; r <= contentsRegion.Bottom; r++) @@ -1912,11 +1817,12 @@ private static bool ReadConsoleOutputCJKSmall } } } + return true; } /// - /// Can handle reading CJK characters, but the left and right edges are not checked + /// Can handle reading CJK characters, but the left and right edges are not checked. /// /// /// @@ -2003,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) @@ -2030,12 +1936,13 @@ 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; } } } + colsRemaining -= bufferSize.X; readRegion.Left += bufferSize.X; if (colsRemaining > 0 && (bufferSize.Y == 1) && @@ -2044,6 +1951,7 @@ internal static void ReadConsoleOutputCJK colsRemaining++; readRegion.Left--; } + bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); } // column iteration @@ -2051,7 +1959,6 @@ internal static void ReadConsoleOutputCJK readRegion.Top += bufferSize.Y; } // row iteration - // The following nested loop set the value of the empty cells in contents: // character to ' ' // foreground color to console's foreground color @@ -2088,15 +1995,16 @@ out background { break; } + contents[rowIndex, colIndex] = new BufferCell( ' ', foreground, background, BufferCellType.Complete); colIndex++; } + rowIndex++; } } -#endregion ReadConsoleOutput CJK - + #endregion ReadConsoleOutput CJK private static void ReadConsoleOutputPlain ( @@ -2173,7 +2081,7 @@ private static void ReadConsoleOutputPlain bufferCoord, ref readRegion); - if (result == false) + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -2198,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; } @@ -2213,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, @@ -2223,6 +2130,7 @@ private static void ReadConsoleOutputPlain contents[r, c].BackgroundColor = bgColor; } } + colsRemaining -= bufferSize.X; readRegion.Left += bufferSize.X; bufferSize.X = (short)Math.Min(colsRemaining, bufferLimit); @@ -2268,38 +2176,31 @@ out background { break; } + contents[rowIndex, colIndex].Character = ' '; contents[rowIndex, colIndex].ForegroundColor = foreground; contents[rowIndex, colIndex].BackgroundColor = background; colIndex++; } + rowIndex++; } } - /// - /// Wraps Win32 FillConsoleOutputCharacter + /// Wraps Win32 FillConsoleOutputCharacter. /// /// - /// /// handle for the console where output is filled - /// /// /// - /// /// character to fill the console output - /// /// /// - /// /// number of times to write character - /// /// /// - /// /// location on screen buffer where writing starts - /// /// /// /// If Win32's FillConsoleOutputCharacter fails @@ -2320,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(); @@ -2341,27 +2241,19 @@ Coordinates origin } /// - /// Wraps Win32 FillConsoleOutputAttribute + /// Wraps Win32 FillConsoleOutputAttribute. /// /// - /// /// handle for the console where output is filled - /// /// /// - /// /// attribute to fill the console output - /// /// /// - /// /// number of times to write attribute - /// /// /// - /// /// location on screen buffer where writing starts - /// /// /// /// If Win32's FillConsoleOutputAttribute fails @@ -2382,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(); @@ -2402,37 +2293,26 @@ Coordinates origin } /// - /// Wrap Win32 ScrollConsoleScreenBuffer + /// Wrap Win32 ScrollConsoleScreenBuffer. /// /// - /// /// handle for the console where screen buffer is scrolled - /// /// /// - /// /// area to be scrolled - /// /// /// - /// /// area to be updated after scrolling - /// /// /// - /// /// location to which the top left corner of scrollRectangle move - /// /// /// - /// /// character and attribute to fill the area vacated by the scroll - /// /// /// /// If Win32's ScrollConsoleScreenBuffer fails /// - internal static void ScrollConsoleScreenBuffer ( ConsoleHandle consoleHandle, @@ -2452,7 +2332,7 @@ internal static void ScrollConsoleScreenBuffer destOrigin, ref fill); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2462,34 +2342,27 @@ internal static void ScrollConsoleScreenBuffer } } -#endregion Buffer + #endregion Buffer -#region Window + #region Window /// - /// Wraps Win32 SetConsoleWindowInfo + /// Wraps Win32 SetConsoleWindowInfo. /// /// - /// /// handle for the console where window info is set - /// /// /// - /// /// If this parameter is TRUE, the coordinates specify the new upper-left and /// lower-right corners of the window. If it is false, the coordinates are offsets /// to the current window-corner coordinates - /// /// /// - /// /// specify the size and position of the console screen buffer's window - /// /// /// /// If Win32's SetConsoleWindowInfo fails /// - internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool absolute, SMALL_RECT windowInfo) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2497,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(); @@ -2508,22 +2381,17 @@ internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool abso } /// - /// Wraps Win32 GetLargestConsoleWindowSize + /// Wraps Win32 GetLargestConsoleWindowSize. /// /// - /// /// handle for the console for which the largest window size is obtained - /// /// /// - /// /// the largest window size - /// /// /// /// If Win32's GetLargestConsoleWindowSize fails /// - internal static Size GetLargestConsoleWindowSize(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2543,160 +2411,195 @@ internal static Size GetLargestConsoleWindowSize(ConsoleHandle consoleHandle) return new Size(result.X, result.Y); } - - /// /// Wraps Win32 GetConsoleTitle. 1K is the safe limit experimentally. The 64K limit /// found in the docs is disregarded because it is essentially meaningless. /// /// - /// /// a string for the title of the window - /// /// /// /// 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 // the difference, we'll just return the empty string every time. if (result == 0) { - return String.Empty; + return string.Empty; } return consoleTitle.ToString(); } + private static bool s_dontsetConsoleWindowTitle; + /// - /// Wraps Win32 SetConsoleTitle + /// Wraps Win32 SetConsoleTitle. /// /// - /// /// a string for the title of the window - /// /// /// /// 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; } } -#endregion Window + #endregion Window /// - /// - /// Wrap Win32 WriteConsole - /// + /// Wrap Win32 WriteConsole. /// /// - /// - /// handle for the console where the string is written - /// + /// Handle for the console where the string is written. /// /// - /// - /// string that is written - /// + /// String that is written. + /// + /// + /// New line is written. /// /// - /// - /// if the Win32's WriteConsole fails - /// + /// If the Win32's WriteConsole fails. /// - - internal static void WriteConsole(ConsoleHandle consoleHandle, string output) + internal static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan output, bool newLine) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed"); - if (String.IsNullOrEmpty(output)) - return; + if (output.Length == 0) + { + if (newLine) + { + WriteConsole(consoleHandle, Environment.NewLine); + } - // Native WriteConsole doesn't support output buffer longer than 64K. - // We need to chop the output string if it is too long. + return; + } - int cursor = 0; // This records the chopping position in output string - const int maxBufferSize = 16383; // this is 64K/4 - 1 to account for possible width of each character. + // Native WriteConsole doesn't support output buffer longer than 64K, so we need to chop the output string if it is too long. + // This records the chopping position in output string. + int cursor = 0; + // This is 64K/4 - 1 to account for possible width of each character. + const int MaxBufferSize = 16383; + const int MaxStackAllocSize = 512; + ReadOnlySpan outBuffer; + + // In case that a new line is required, we try to write out the last chunk and the new-line string together, + // to avoid one extra call to 'WriteConsole' just for a new line string. + while (cursor + MaxBufferSize < output.Length) + { + outBuffer = output.Slice(cursor, MaxBufferSize); + cursor += MaxBufferSize; + WriteConsole(consoleHandle, outBuffer); + } - while (cursor < output.Length) + outBuffer = output.Slice(cursor); + if (!newLine) { - string outBuffer; + WriteConsole(consoleHandle, outBuffer); + return; + } + + char[] rentedArray = null; + string lineEnding = Environment.NewLine; + int size = outBuffer.Length + lineEnding.Length; - if (cursor + maxBufferSize < output.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.Substring(cursor, maxBufferSize); - cursor += maxBufferSize; + rentedArray = ArrayPool.Shared.Rent(size); + buffer = rentedArray.AsSpan().Slice(0, size); } - else + + outBuffer.CopyTo(buffer); + lineEnding.CopyTo(buffer.Slice(outBuffer.Length)); + WriteConsole(consoleHandle, buffer); + } + finally + { + if (rentedArray is not null) { - outBuffer = output.Substring(cursor); - cursor = output.Length; + ArrayPool.Shared.Return(rentedArray); } + } + } - DWORD charsWritten; - bool result = - NativeMethods.WriteConsole( - consoleHandle.DangerousGetHandle(), - outBuffer, - (DWORD)outBuffer.Length, - out charsWritten, - IntPtr.Zero); + private static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan buffer) + { + DWORD charsWritten; + bool result = + NativeMethods.WriteConsole( + consoleHandle.DangerousGetHandle(), + buffer, + (DWORD)buffer.Length, + out charsWritten, + IntPtr.Zero); - if (result == false) - { - int err = Marshal.GetLastWin32Error(); + if (!result) + { + int err = Marshal.GetLastWin32Error(); - HostException e = CreateHostException(err, "WriteConsole", - ErrorCategory.WriteError, ConsoleControlStrings.WriteConsoleExceptionTemplate); - throw e; - } + HostException e = CreateHostException( + err, + "WriteConsole", + ErrorCategory.WriteError, + ConsoleControlStrings.WriteConsoleExceptionTemplate); + throw e; } } /// - /// Wraps Win32 SetConsoleTextAttribute + /// Wraps Win32 SetConsoleTextAttribute. /// /// - /// /// handle for the console where text attribute is set - /// /// /// - /// /// text attribute to set the console - /// /// /// - /// /// if the Win32's SetConsoleTextAttribute fails - /// /// - internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD attribute) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2704,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(); @@ -2715,47 +2618,100 @@ internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD a } #endif -#region Dealing with CJK + #region Dealing with CJK // Return the length of a VT100 control sequence character in str starting // at the given offset. // - // This code only handles the most common formatting sequences, which are - // all of the pattern: - // ESC '[' digits+ (';' digits)* 'm' + // This code only handles the following formatting sequences, corresponding to + // the patterns: + // CSI params? 'm' // SGR: Select Graphics Rendition + // CSI params? '#' [{}pq] // XTPUSHSGR ('{'), XTPOPSGR ('}'), or their aliases ('p' and 'q') // - // There are many other VT100 escape sequences, but this simple pattern - // is sufficient for our formatting system. We won't handle cursor movements - // or other attempts at animation. + // Where: + // params: digit+ ((';' | ':') params)? + // CSI: C0_CSI | C1_CSI + // C0_CSI: \x001b '[' // ESC '[' + // C1_CSI: \x009b // - // Note that offset is adjusted past the escape sequence. + // There are many other VT100 escape sequences, but these text attribute sequences + // (color-related, underline, etc.) are sufficient for our formatting system. We + // won't handle cursor movements or other attempts at animation. + // + // Note that offset is adjusted past the escape sequence, or at least one + // character forward if there is no escape sequence at the specified position. internal static int ControlSequenceLength(string str, ref int offset) { var start = offset; - if (str[offset++] != (char)0x1B) + + // First, check for the CSI: + if ((str[offset] == (char)0x1b) && (str.Length > (offset + 1)) && (str[offset + 1] == '[')) + { + // C0 CSI + offset += 2; + } + else if (str[offset] == (char)0x9b) + { + // C1 CSI + offset += 1; + } + else + { + // No CSI at the current location, so we are done looking, but we still + // need to advance offset. + offset += 1; return 0; + } - if (offset >= str.Length || str[offset] != '[') + if (offset >= str.Length) + { return 0; + } - offset += 1; - while (offset < str.Length) + // Next, handle possible numeric arguments: + char c; + do { - var c = str[offset++]; - if (c == 'm') - break; + c = str[offset++]; + } + while ((offset < str.Length) && (char.IsDigit(c) || (c == ';') || (c == ':'))); - if (char.IsDigit(c) || c == ';') - continue; + // Finally, handle the command characters for the specific sequences we + // handle: + if (c == 'm') + { + // SGR: Select Graphics Rendition + return offset - start; + } + // Maybe XTPUSHSGR or XTPOPSGR, but we need to read another char. Offset is + // already positioned on the next char (or past the end). + if (offset >= str.Length) + { return 0; } - return offset - start; + if (c == '#') + { + // '{' : XTPUSHSGR + // '}' : XTPOPSGR + // 'p' : alias for XTPUSHSGR + // 'q' : alias for XTPOPSGR + c = str[offset++]; + if ((c == '{') || + (c == '}') || + (c == 'p') || + (c == 'q')) + { + return offset - start; + } + } + + return 0; } /// - /// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c + /// From IsConsoleFullWidth in \windows\core\ntcon\server\dbcs.c. /// /// [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", @@ -2782,13 +2738,42 @@ internal static int LengthInBufferCells(string str, int offset, bool checkEscape } } - return str.Length - offset - escapeSequenceAdjustment; + int length = 0; + foreach (char c in str) + { + length += LengthInBufferCells(c); + } + + return length - offset - escapeSequenceAdjustment; + } + + internal static int LengthInBufferCells(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 + bool isWide = c >= 0x1100 && + (c <= 0x115f || /* Hangul Jamo init. consonants */ + c == 0x2329 || c == 0x232a || + ((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) && + c != 0x303f) || /* CJK ... Yi */ + ((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */ + ((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */ + ((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */ + ((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */ + ((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */ + ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); + + // We can ignore these ranges because .Net strings use surrogate pairs + // for this range and we do not handle surrogate pairs. + // (c >= 0x20000 && c <= 0x2fffd) || + // (c >= 0x30000 && c <= 0x3fffd) + return 1 + (isWide ? 1 : 0); } #if !UNIX /// - /// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese + /// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese. /// /// /// @@ -2802,71 +2787,24 @@ internal static bool IsCJKOutputCodePage(out uint codePage) } #endif -#endregion Dealing with CJK + #endregion Dealing with CJK #if !UNIX -#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; - } - } - - + #region Cursor /// - /// Wraps Win32 GetConsoleCursorInfo + /// Wraps Win32 GetConsoleCursorInfo. /// /// - /// /// handle for the console where cursor info is obtained - /// /// /// - /// /// cursor info - /// /// /// /// If Win32's GetConsoleCursorInfo fails /// - internal static CONSOLE_CURSOR_INFO GetConsoleCursorInfo(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2876,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(); @@ -2884,6 +2822,7 @@ internal static CONSOLE_CURSOR_INFO GetConsoleCursorInfo(ConsoleHandle consoleHa ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetConsoleCursorInfoExceptionTemplate); throw e; } + return cursorInfo; } @@ -2896,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(); @@ -2904,26 +2843,22 @@ internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHan ErrorCategory.ResourceUnavailable, ConsoleControlStrings.GetConsoleFontInfoExceptionTemplate); throw e; } + return fontInfo; } /// - /// Wraps Win32 SetConsoleCursorInfo + /// Wraps Win32 SetConsoleCursorInfo. /// /// - /// /// handle for the console where cursor info is set - /// /// /// - /// /// cursor info to set the cursor - /// /// /// /// If Win32's SetConsoleCursorInfo fails /// - internal static void SetConsoleCursorInfo(ConsoleHandle consoleHandle, CONSOLE_CURSOR_INFO cursorInfo) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2931,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(); @@ -2941,12 +2876,12 @@ internal static void SetConsoleCursorInfo(ConsoleHandle consoleHandle, CONSOLE_C } } -#endregion Cursor + #endregion Cursor -#region helper + #region helper /// - /// Helper function to create the proper HostException + /// Helper function to create the proper HostException. /// /// /// @@ -2962,36 +2897,9 @@ private static HostException CreateHostException( return e; } -#endregion helper - -#region - - internal static int LengthInBufferCells(char c) - { - // The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c - // which is derived from http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt - - bool isWide = c >= 0x1100 && - (c <= 0x115f || /* Hangul Jamo init. consonants */ - c == 0x2329 || c == 0x232a || - (c >= 0x2e80 && c <= 0xa4cf && - c != 0x303f) || /* CJK ... Yi */ - (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */ - (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */ - (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */ - (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */ - (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */ - (c >= 0xffe0 && c <= 0xffe6)); - // We can ignore these ranges because .Net strings use surrogate pairs - // for this range and we do not handle surrogage pairs. - // (c >= 0x20000 && c <= 0x2fffd) || - // (c >= 0x30000 && c <= 0x3fffd) - return 1 + (isWide ? 1 : 0); - } - -#endregion + #endregion helper -#region SendInput + #region SendInput internal static void MimicKeyPress(INPUT[] inputs) { @@ -3008,21 +2916,19 @@ internal static void MimicKeyPress(INPUT[] inputs) } } -#endregion SendInput + #endregion SendInput /// - /// /// 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 + internal const int FontTypeMask = 0x06; internal const int TrueTypeFont = 0x04; -#region CreateFile + #region CreateFile [Flags] internal enum AccessQualifiers : uint @@ -3062,14 +2968,14 @@ internal static extern NakedWin32Handle CreateFile NakedWin32Handle templateFileWin32Handle ); -#endregion CreateFile + #endregion CreateFile -#region Code Page + #region Code Page [DllImport(PinvokeDllNames.GetConsoleOutputCPDllName, SetLastError = false, CharSet = CharSet.Unicode)] internal static extern uint GetConsoleOutputCP(); -#endregion Code Page + #endregion Code Page [DllImport(PinvokeDllNames.GetConsoleWindowDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern HWND GetConsoleWindow(); @@ -3081,9 +2987,11 @@ NakedWin32Handle templateFileWin32Handle internal static extern int ReleaseDC(HWND hwnd, HDC hdc); [DllImport(PinvokeDllNames.FlushConsoleInputBufferDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FlushConsoleInputBuffer(NakedWin32Handle consoleInput); [DllImport(PinvokeDllNames.FillConsoleOutputAttributeDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FillConsoleOutputAttribute ( NakedWin32Handle consoleOutput, @@ -3094,61 +3002,88 @@ out DWORD numberOfAttrsWritten ); [DllImport(PinvokeDllNames.FillConsoleOutputCharacterDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool FillConsoleOutputCharacter ( NakedWin32Handle consoleOutput, - Char character, + char character, DWORD length, COORD writeCoord, out DWORD numberOfCharsWritten ); [DllImport(PinvokeDllNames.WriteConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern bool WriteConsole + [return: MarshalAs(UnmanagedType.Bool)] + private static extern unsafe bool WriteConsole ( NakedWin32Handle consoleOutput, - string buffer, + char* buffer, DWORD numberOfCharsToWrite, out DWORD numberOfCharsWritten, IntPtr reserved ); + internal static unsafe bool WriteConsole + ( + NakedWin32Handle consoleOutput, + ReadOnlySpan buffer, + DWORD numberOfCharsToWrite, + out DWORD numberOfCharsWritten, + IntPtr reserved + ) + { + fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + return WriteConsole(consoleOutput, bufferPtr, numberOfCharsToWrite, out numberOfCharsWritten, reserved); + } + } + [DllImport(PinvokeDllNames.GetConsoleTitleDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern DWORD GetConsoleTitle(StringBuilder consoleTitle, DWORD size); [DllImport(PinvokeDllNames.SetConsoleTitleDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleTitle(string consoleTitle); [DllImport(PinvokeDllNames.GetConsoleModeDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetConsoleMode(NakedWin32Handle consoleHandle, out UInt32 mode); [DllImport(PinvokeDllNames.GetConsoleScreenBufferInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetConsoleScreenBufferInfo(NakedWin32Handle consoleHandle, out CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo); - - internal enum FileType - { - Unknown, - Disk, - Char, - Pipe - }; - [DllImport(PinvokeDllNames.GetLargestConsoleWindowSizeDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern COORD GetLargestConsoleWindowSize(NakedWin32Handle consoleOutput); [DllImport(PinvokeDllNames.ReadConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern bool ReadConsole + [return: MarshalAs(UnmanagedType.Bool)] + private static extern unsafe bool ReadConsole ( NakedWin32Handle consoleInput, - StringBuilder buffer, + char* lpBuffer, DWORD numberOfCharsToRead, out DWORD numberOfCharsRead, - // This magical parameter is not documented, but is the secret to tab-completion. ref CONSOLE_READCONSOLE_CONTROL controlData ); + internal static unsafe bool ReadConsole + ( + NakedWin32Handle consoleInput, + Span buffer, + DWORD numberOfCharsToRead, + out DWORD numberOfCharsRead, + ref CONSOLE_READCONSOLE_CONTROL controlData + ) + { + fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + return ReadConsole(consoleInput, bufferPtr, numberOfCharsToRead, out numberOfCharsRead, ref controlData); + } + } + [DllImport(PinvokeDllNames.PeekConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PeekConsoleInput ( NakedWin32Handle consoleInput, @@ -3158,27 +3093,31 @@ out DWORD numberOfEventsRead ); [DllImport(PinvokeDllNames.GetNumberOfConsoleInputEventsDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetNumberOfConsoleInputEvents(NakedWin32Handle consoleInput, out DWORD numberOfEvents); [DllImport(PinvokeDllNames.SetConsoleCtrlHandlerDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add); - [DllImport(PinvokeDllNames.SetConsoleCursorPositionDllName, SetLastError = true, CharSet = CharSet.Unicode)] - 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); [DllImport(PinvokeDllNames.SetConsoleScreenBufferSizeDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleScreenBufferSize(NakedWin32Handle consoleOutput, COORD size); [DllImport(PinvokeDllNames.SetConsoleTextAttributeDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleTextAttribute(NakedWin32Handle consoleOutput, WORD attributes); [DllImport(PinvokeDllNames.SetConsoleWindowInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleWindowInfo(NakedWin32Handle consoleHandle, bool absolute, ref SMALL_RECT windowInfo); [DllImport(PinvokeDllNames.WriteConsoleOutputDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool WriteConsoleOutput ( NakedWin32Handle consoleOutput, @@ -3189,6 +3128,7 @@ ref SMALL_RECT writeRegion ); [DllImport(PinvokeDllNames.ReadConsoleOutputDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ReadConsoleOutput ( NakedWin32Handle consoleOutput, @@ -3199,6 +3139,7 @@ ref SMALL_RECT readRegion ); [DllImport(PinvokeDllNames.ScrollConsoleScreenBufferDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ScrollConsoleScreenBuffer ( NakedWin32Handle consoleOutput, @@ -3209,19 +3150,23 @@ 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)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetCurrentConsoleFontEx(NakedWin32Handle consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo); [DllImport(PinvokeDllNames.GetConsoleCursorInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetConsoleCursorInfo(NakedWin32Handle consoleOutput, out CONSOLE_CURSOR_INFO consoleCursorInfo); [DllImport(PinvokeDllNames.SetConsoleCursorInfoDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetConsoleCursorInfo(NakedWin32Handle consoleOutput, ref CONSOLE_CURSOR_INFO consoleCursorInfo); [DllImport(PinvokeDllNames.ReadConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ReadConsoleInput ( NakedWin32Handle consoleInput, @@ -3237,38 +3182,8 @@ internal enum CHAR_INFO_Attributes : uint } } - private const string StringsResourceBaseName = "ConsoleControlStrings"; - /*private const string AddBreakHandlerTemplateResource = ConsoleControlStrings.AddBreakHandlerExceptionTemplate; - private const string RemoveBreakHandlerTemplateResource = ConsoleControlStrings.RemoveBreakHandlerExceptionTemplate; - private const string AttachToParentConsoleTemplateResource = ConsoleControlStrings.AttachToParentConsoleExceptionTemplate; - private const string DetachFromConsoleTemplateResource = ConsoleControlStrings.DetachFromConsoleExceptionTemplate; - private const string GetInputHandleTemplateResource = ConsoleControlStrings.GetInputModeExceptionTemplate; - private const string GetActiveScreenBufferHandleTemplateResource = ConsoleControlStrings.GetActiveScreenBufferHandleExceptionTemplate; - private const string GetModeTemplateResource = ConsoleControlStrings.GetModeExceptionTemplate; - private const string SetModeTemplateResource = ConsoleControlStrings.SetModeExceptionTemplate; - private const string ReadConsoleTemplateResource = ConsoleControlStrings.ReadConsoleExceptionTemplate; - private const string ReadConsoleInputTemplateResource = ConsoleControlStrings.ReadConsoleInputExceptionTemplate; - private const string PeekConsoleInputTemplateResource = ConsoleControlStrings.PeekConsoleInputExceptionTemplate; - private const string GetNumberOfConsoleInputEventsTemplateResource = ConsoleControlStrings.GetNumberOfConsoleInputEventsExceptionTemplate; - private const string FlushConsoleInputBufferTemplateResource = ConsoleControlStrings.FlushConsoleInputBufferExceptionTemplate; - private const string GetConsoleScreenBufferInfoTemplateResource = ConsoleControlStrings.GetConsoleScreenBufferInfoExceptionTemplate; - private const string SetConsoleScreenBufferSizeTemplateResource = ConsoleControlStrings.SetConsoleScreenBufferSizeExceptionTemplate; - private const string WriteConsoleOutputTemplateResource = ConsoleControlStrings.WriteConsoleOutputExceptionTemplate; - private const string ReadConsoleOutputTemplateResource = ConsoleControlStrings.ReadConsoleOutputExceptionTemplate; - private const string FillConsoleOutputCharacterTemplateResource = ConsoleControlStrings.FillConsoleOutputCharacterExceptionTemplate; - private const string FillConsoleOutputAttributeTemplateResource = ConsoleControlStrings.FillConsoleOutputAttributeExceptionTemplate; - private const string ScrollConsoleScreenBufferTemplateResource = ConsoleControlStrings.ScrollConsoleScreenBufferExceptionTemplate; - private const string SetConsoleWindowInfoTemplateResource = ConsoleControlStrings.SetConsoleWindowInfoExceptionTemplate; - private const string GetLargestConsoleWindowSizeTemplateResource = ConsoleControlStrings.GetLargestConsoleWindowSizeExceptionTemplate; - private const string SetConsoleWindowTitleTemplateResource = ConsoleControlStrings.SetConsoleWindowTitleExceptionTemplate; - private const string WriteConsoleTemplateResource = ConsoleControlStrings.WriteConsoleExceptionTemplate; - private const string SetConsoleTextAttributeTemplateResource = ConsoleControlStrings.SetConsoleTextAttributeExceptionTemplate; - private const string SetConsoleCursorPositionTemplateResource = ConsoleControlStrings.SetConsoleCursorPositionExceptionTemplate; - private const string GetConsoleCursorInfoTemplateResource = ConsoleControlStrings.GetConsoleCursorInfoExceptionTemplate; - private const string SetConsoleCursorInfoTemplateResource = ConsoleControlStrings.SetConsoleCursorInfoExceptionTemplate;*/ - [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 } -} // namespace +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 060fa942b56..daf69d50251 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -1,46 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -#pragma warning disable 1634, 1691 - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Diagnostics.CodeAnalysis; -using System.Text; -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.Reflection; using System.Management.Automation; +using System.Management.Automation.Configuration; using System.Management.Automation.Host; using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; +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.Threading; -using System.Runtime.InteropServices; -using System.Management.Automation.Language; - -using Dbg = System.Management.Automation.Diagnostics; -using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; -using NakedWin32Handle = System.IntPtr; +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.Commands; +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 { /// - /// /// Subclasses S.M.A.Host to implement a console-mode monad host. - /// /// - /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] internal sealed partial class ConsoleHost : @@ -54,37 +51,31 @@ internal sealed partial class ConsoleHost #region static methods internal const int ExitCodeSuccess = 0; - internal const int ExitCodeCtrlBreak = 128+21; // SIGBREAK + internal const int ExitCodeCtrlBreak = 128 + 21; // SIGBREAK internal const int ExitCodeInitFailure = 70; // Internal Software Error internal const int ExitCodeBadCommandLineParameter = 64; // Command Line Usage Error +#if UNIX + internal const string DECCKM_ON = "\x1b[?1h"; + internal const string DECCKM_OFF = "\x1b[?1l"; +#endif - // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 - // Removed HandleUnexpectedExceptions infrastructure /// - /// - /// internal Entry point in msh console host implementation - /// + /// Internal Entry point in msh console host implementation. /// - /// /// /// Banner text to be displayed by 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. /// - /// NTRAID#Windows OS Bugs-1036968-2005/01/20-sburns The behavior here is related to monitor work. The low word of the - /// exit code is available for the user. The high word is reserved for the shell and monitor. + /// The behavior here is related to monitor work. + /// The low word of the exit code is available for the user. The high word is reserved for the shell and monitor. /// /// The shell process needs to return: /// @@ -103,14 +94,13 @@ internal sealed partial class ConsoleHost /// or 0xFFFE0000 (user hit ctrl-break), the monitor should restart the shell.exe. Otherwise, the monitor should exit /// with the same exit code as the shell.exe. /// - /// Anyone checking the exit code of the shell or monitor can mask off the hiword to determine the exit code passed + /// 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) + bool issProvidedExternally) { #if DEBUG if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null) @@ -122,32 +112,52 @@ internal static int Start( } #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"); - if (path != null) + string pshome = Utils.DefaultPowerShellAppBase; + string dotnetToolsPathSegment = $"{Path.DirectorySeparatorChar}.store{Path.DirectorySeparatorChar}powershell{Path.DirectorySeparatorChar}"; + + int index = pshome.IndexOf(dotnetToolsPathSegment, StringComparison.Ordinal); + if (index > 0) { - string pshome = Utils.DefaultPowerShellAppBase; - if (!path.Contains(pshome)) - { - Environment.SetEnvironmentVariable("PATH", pshome + Path.PathSeparator + path); - } + // 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]; } - try + pshome += Path.PathSeparator; + + // To not impact startup perf, we don't remove duplicates, but we avoid adding a duplicate to the front + // we also don't handle the edge case where PATH only contains $PSHOME + if (string.IsNullOrEmpty(path)) { - string profileDir; -#if UNIX - profileDir = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE); -#else - profileDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + - @"\Microsoft\Windows\PowerShell"; + Environment.SetEnvironmentVariable("PATH", pshome); + } + else if (!path.StartsWith(pshome, StringComparison.Ordinal)) + { + Environment.SetEnvironmentVariable("PATH", pshome + path); + } - if (!Directory.Exists(profileDir)) + try + { + string profileDir = Platform.CacheDirectory; + if (!string.IsNullOrEmpty(profileDir)) { - Directory.CreateDirectory(profileDir); - } +#if !UNIX + if (!Directory.Exists(profileDir)) + { + Directory.CreateDirectory(profileDir); + } #endif - ClrFacade.SetProfileOptimizationRoot(profileDir); + ProfileOptimization.SetProfileRoot(profileDir); + } } catch { @@ -157,7 +167,7 @@ internal static int Start( uint exitCode = ExitCodeSuccess; - System.Threading.Thread.CurrentThread.Name = "ConsoleHost main thread"; + Thread.CurrentThread.Name = "ConsoleHost main thread"; try { @@ -173,64 +183,117 @@ internal static int Start( hostException = e; } - if (args == null) - { - args = new string[0]; - } - - s_cpp = new CommandLineParameterParser( - (s_theConsoleHost != null) ? s_theConsoleHost.UI : (new NullHostUserInterface()), - bannerText, helpText); - string[] tempArgs = new string[args.GetLength(0)]; - args.CopyTo(tempArgs, 0); - - s_cpp.Parse(tempArgs); + 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; } +#if !UNIX + TaskbarJumpList.CreateRunAsAdministratorJumpList(); +#endif // First check for and handle PowerShell running in a server mode. if (s_cpp.ServerMode) { - ClrFacade.StartProfileOptimization("StartupProfileData-ServerMode"); - System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-ServerMode"); + 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) { - ClrFacade.StartProfileOptimization("StartupProfileData-NamedPipeServerMode"); - System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode( - s_cpp.ConfigurationName); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode"); + RemoteSessionNamedPipeServer.RunServerMode( + configurationName: s_cpp.ConfigurationName); exitCode = 0; } - else if (s_cpp.SSHServerMode) +#if !UNIX + else if (s_cpp.V2SocketServerMode) { - ClrFacade.StartProfileOptimization("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) { - ClrFacade.StartProfileOptimization("StartupProfileData-SocketServerMode"); - System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(s_cpp.InitialCommand, - s_cpp.ConfigurationName); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode"); + HyperVSocketMediator.Run( + initialCommand: s_cpp.InitialCommand, + configurationName: s_cpp.ConfigurationName); exitCode = 0; } else @@ -242,28 +305,49 @@ internal static int Start( throw hostException; } + if (LoadPSReadline()) + { + ProfileOptimization.StartProfile("StartupProfileData-Interactive"); + + if (UpdatesNotification.CanNotifyUpdates) + { + // Start a task in the background to check for the update release. + _ = UpdatesNotification.CheckForUpdates(); + } + } + else + { + ProfileOptimization.StartProfile("StartupProfileData-NonInteractive"); + } + s_theConsoleHost.BindBreakHandler(); - PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; + IsStdOutputRedirected = Console.IsOutputRedirected; // Send startup telemetry for ConsoleHost startup - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry(); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal", s_cpp.ParametersUsedAsDouble); - ClrFacade.StartProfileOptimization( - s_theConsoleHost.LoadPSReadline() - ? "StartupProfileData-Interactive" - : "StartupProfileData-NonInteractive"); 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 @@ -271,15 +355,34 @@ internal static int Start( return (int)exitCode; } } - 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 /// - /// /// The break handler for the program. Dispatches a break event to the current Executor. - /// /// private static void MyBreakHandler(object sender, ConsoleCancelEventArgs args) { @@ -293,7 +396,7 @@ private static void MyBreakHandler(object sender, ConsoleCancelEventArgs args) case ConsoleSpecialKey.ControlBreak: if (s_cpp.NonInteractive) { - //ControlBreak mimics ControlC in Noninteractive shells + // ControlBreak mimics ControlC in Noninteractive shells SpinUpBreakHandlerThread(shouldEndSession: true); } else @@ -301,14 +404,13 @@ private static void MyBreakHandler(object sender, ConsoleCancelEventArgs args) // Break into script debugger. BreakIntoDebugger(); } + return; } } #else /// - /// /// The break handler for the program. Dispatches a break event to the current Executor. - /// /// /// /// @@ -319,7 +421,7 @@ private static bool MyBreakHandler(ConsoleControl.ConsoleBreakSignal signal) case ConsoleControl.ConsoleBreakSignal.CtrlBreak: if (s_cpp.NonInteractive) { - //ControlBreak mimics ControlC in Noninteractive shells + // ControlBreak mimics ControlC in Noninteractive shells SpinUpBreakHandlerThread(shouldEndSession: true); } else @@ -327,6 +429,7 @@ private static bool MyBreakHandler(ConsoleControl.ConsoleBreakSignal signal) // Break into script debugger. BreakIntoDebugger(); } + return true; // Run the break handler... @@ -366,6 +469,7 @@ private static bool BreakIntoDebugger() debugger = host._runspaceRef.Runspace.Debugger; } } + if (debugger != null) { debugger.SetDebuggerStepMode(true); @@ -376,20 +480,16 @@ private static bool BreakIntoDebugger() } /// - /// /// Spin up a new thread to cancel the current pipeline. This will allow subsequent break interrupts to be received even /// if the cancellation is blocked (which can be the case when the pipeline blocks and nothing implements Cmdlet.Stop /// properly). That is because the OS will not inject another thread when a break event occurs if one has already been /// injected and is running. - /// /// /// - /// /// 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(); } } } @@ -517,13 +614,10 @@ internal static ConsoleHost SingletonInstance #region overrides /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override string Name { get @@ -536,13 +630,10 @@ public override string Name } /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override System.Version Version { get @@ -553,19 +644,14 @@ public override System.Version Version } /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override System.Guid InstanceId { get; } = Guid.NewGuid(); /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -581,12 +667,19 @@ public override PSHostUserInterface UI /// /// See base class. /// - public void PushRunspace(Runspace newRunspace) + public void PushRunspace(Runspace runspace) { - if (_runspaceRef == null) { return; } - RemoteRunspace remoteRunspace = newRunspace as RemoteRunspace; - Dbg.Assert(remoteRunspace != null, "Expected remoteRunspace != null"); - remoteRunspace.StateChanged += new EventHandler(HandleRemoteRunspaceStateChanged); + if (_runspaceRef == null) + { + return; + } + + if (runspace is not RemoteRunspace remoteRunspace) + { + throw new ArgumentException(ConsoleHostStrings.PushRunspaceNotRemote, nameof(runspace)); + } + + remoteRunspace.StateChanged += HandleRemoteRunspaceStateChanged; // Unsubscribe the local session debugger. if (_runspaceRef.Runspace.Debugger != null) @@ -601,7 +694,7 @@ public void PushRunspace(Runspace newRunspace) } // Connect a disconnected command. - this.runningCmd = Microsoft.PowerShell.Commands.EnterPSSessionCommand.ConnectRunningPipeline(remoteRunspace); + this.runningCmd = EnterPSSessionCommand.ConnectRunningPipeline(remoteRunspace); // Push runspace. _runspaceRef.Override(remoteRunspace, hostGlobalLock, out _isRunspacePushed); @@ -609,7 +702,7 @@ public void PushRunspace(Runspace newRunspace) if (this.runningCmd != null) { - Microsoft.PowerShell.Commands.EnterPSSessionCommand.ContinueCommand( + EnterPSSessionCommand.ContinueCommand( remoteRunspace, this.runningCmd, this, @@ -623,10 +716,10 @@ public void PushRunspace(Runspace newRunspace) /// /// Handles state changed event of the remote runspace. If the remote runspace /// gets into a broken or closed state, writes a message and pops out the - /// runspace + /// runspace. /// - /// not sure - /// arguments describing this event + /// Not sure. + /// Arguments describing this event. private void HandleRemoteRunspaceStateChanged(object sender, RunspaceStateEventArgs eventArgs) { RunspaceState state = eventArgs.RunspaceStateInfo.State; @@ -645,6 +738,7 @@ private void HandleRemoteRunspaceStateChanged(object sender, RunspaceStateEventA { PopRunspace(); } + break; } } @@ -704,6 +798,7 @@ public bool IsRunspacePushed return _isRunspacePushed; } } + private bool _isRunspacePushed = false; /// @@ -713,7 +808,11 @@ public Runspace Runspace { get { - if (this.RunspaceRef == null) { return null; } + if (this.RunspaceRef == null) + { + return null; + } + return this.RunspaceRef.Runspace; } } @@ -726,119 +825,204 @@ internal LocalRunspace LocalRunspace { return RunspaceRef.OldRunspace as LocalRunspace; } - if (RunspaceRef == null) { return null; } + + if (RunspaceRef == null) + { + return null; + } + return RunspaceRef.Runspace as LocalRunspace; } } public class ConsoleColorProxy { - private ConsoleHostUserInterface _ui; + private readonly ConsoleHostUserInterface _ui; public ConsoleColorProxy(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException("ui"); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; } + public ConsoleColor FormatAccentColor + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + get + { + return _ui.FormatAccentColor; + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + set + { + _ui.FormatAccentColor = value; + } + } + + public ConsoleColor ErrorAccentColor + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + get + { + return _ui.ErrorAccentColor; + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + set + { + _ui.ErrorAccentColor = value; + } + } + public ConsoleColor ErrorForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ErrorForegroundColor; } + { + return _ui.ErrorForegroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ErrorForegroundColor = value; } + { + _ui.ErrorForegroundColor = value; + } } public ConsoleColor ErrorBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ErrorBackgroundColor; } + { + return _ui.ErrorBackgroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ErrorBackgroundColor = value; } + { + _ui.ErrorBackgroundColor = value; + } } public ConsoleColor WarningForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.WarningForegroundColor; } + { + return _ui.WarningForegroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.WarningForegroundColor = value; } + { + _ui.WarningForegroundColor = value; + } } public ConsoleColor WarningBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.WarningBackgroundColor; } + { + return _ui.WarningBackgroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.WarningBackgroundColor = value; } + { + _ui.WarningBackgroundColor = value; + } } public ConsoleColor DebugForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.DebugForegroundColor; } + { + return _ui.DebugForegroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.DebugForegroundColor = value; } + { + _ui.DebugForegroundColor = value; + } } public ConsoleColor DebugBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.DebugBackgroundColor; } + { + return _ui.DebugBackgroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.DebugBackgroundColor = value; } + { + _ui.DebugBackgroundColor = value; + } } public ConsoleColor VerboseForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.VerboseForegroundColor; } + { + return _ui.VerboseForegroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.VerboseForegroundColor = value; } + { + _ui.VerboseForegroundColor = value; + } } public ConsoleColor VerboseBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.VerboseBackgroundColor; } + { + return _ui.VerboseBackgroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.VerboseBackgroundColor = value; } + { + _ui.VerboseBackgroundColor = value; + } } public ConsoleColor ProgressForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ProgressForegroundColor; } + { + return _ui.ProgressForegroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ProgressForegroundColor = value; } + { + _ui.ProgressForegroundColor = value; + } } public ConsoleColor ProgressBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ProgressBackgroundColor; } + { + return _ui.ProgressBackgroundColor; + } + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ProgressBackgroundColor = value; } + { + _ui.ProgressBackgroundColor = value; + } } } @@ -850,22 +1034,22 @@ 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)); } } - private PSObject _consoleColorProxy; - + private PSObject _consoleColorProxy; /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override System.Globalization.CultureInfo CurrentCulture { get @@ -877,16 +1061,11 @@ public override System.Globalization.CultureInfo CurrentCulture } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override System.Globalization.CultureInfo CurrentUICulture { get @@ -899,10 +1078,8 @@ public override System.Globalization.CultureInfo CurrentUICulture } /// - /// /// /// - public override void SetShouldExit(int exitCode) { lock (hostGlobalLock) @@ -926,15 +1103,11 @@ public override void SetShouldExit(int exitCode) } /// - /// /// If an input loop is running, then starts a new, nested input loop. If an input loop is not running, /// throws an exception. - /// /// /// - /// /// If a nested prompt is entered while the host is not running at least one prompt loop. - /// /// public override void EnterNestedPrompt() { @@ -952,6 +1125,7 @@ public override void EnterNestedPrompt() { IsNested = oldCurrent != null || this.ui.IsCommandCompletionRunning; } + InputLoop.RunNewInputLoop(this, IsNested); } finally @@ -961,14 +1135,10 @@ public override void EnterNestedPrompt() } /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// If there is no nested prompt. - /// /// public override void ExitNestedPrompt() { @@ -979,26 +1149,22 @@ public override void ExitNestedPrompt() } /// - /// - /// See base class - /// + /// See base class. /// 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 } @@ -1006,26 +1172,33 @@ public override void NotifyBeginApplication() } /// - /// /// See base class - /// /// /// 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 } @@ -1041,23 +1214,25 @@ bool IHostProvidesTelemetryData.HostIsInteractive ((s_cpp.InitialCommand == null && s_cpp.File == null) || s_cpp.NoExit); } } + double IHostProvidesTelemetryData.ProfileLoadTimeInMS { get { return _profileLoadTimeInMS; } } + double IHostProvidesTelemetryData.ReadyForInputTimeInMS { get { return _readyForInputTimeInMS; } } + int IHostProvidesTelemetryData.InteractiveCommandCount { get { return _interactiveCommandCount; } } -#endif - private double _profileLoadTimeInMS; private double _readyForInputTimeInMS; private int _interactiveCommandCount; +#endif + + private double _profileLoadTimeInMS; #endregion overrides #region non-overrides /// - /// - /// Constructs a new instance - /// + /// Constructs a new instance. /// internal ConsoleHost() { @@ -1092,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 @@ -1127,19 +1306,7 @@ private void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArg } /// - /// - /// Finalizes the instance - /// - /// - ~ConsoleHost() - { - Dispose(false); - } - - /// - /// - /// Disposes of this instance, per the IDisposable pattern - /// + /// Disposes of this instance, per the IDisposable pattern. /// public void Dispose() { @@ -1152,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) { @@ -1166,14 +1332,10 @@ private void Dispose(bool isDisposingNotFinalizing) { StopTranscribing(); } - if (_outputSerializer != null) - { - _outputSerializer.End(); - } - if (_errorSerializer != null) - { - _errorSerializer.End(); - } + + _outputSerializer?.End(); + _errorSerializer?.End(); + if (_runspaceRef != null) { // NTRAID#Windows Out Of Band Releases-925297-2005/12/14 @@ -1185,6 +1347,7 @@ private void Dispose(bool isDisposingNotFinalizing) { } } + _runspaceRef = null; ui = null; } @@ -1194,16 +1357,20 @@ private void Dispose(bool isDisposingNotFinalizing) } /// - /// + /// 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 /// try to set it to false after is was set to true. - /// /// /// - /// /// true to shut down the session. false is only allowed if the property is already false. - /// /// internal bool ShouldEndSession { @@ -1219,13 +1386,14 @@ internal bool ShouldEndSession return result; } + set { lock (hostGlobalLock) { // If ShouldEndSession is already true, you can't set it back - Dbg.Assert(_shouldEndSession != true || value != false, + Dbg.Assert(!_shouldEndSession || value, "ShouldEndSession can only be set from false to true"); _shouldEndSession = value; @@ -1234,9 +1402,7 @@ internal bool ShouldEndSession } /// - /// /// The Runspace ref object being used by this Host instance. A host only opens one Runspace. - /// /// /// internal RunspaceRef RunspaceRef @@ -1249,6 +1415,7 @@ internal RunspaceRef RunspaceRef internal WrappedSerializer.DataFormat OutputFormat { get; private set; } + internal bool OutputFormatSpecified { get; private set; } internal WrappedSerializer.DataFormat InputFormat { get; private set; } @@ -1258,12 +1425,13 @@ internal WrappedDeserializer.DataFormat ErrorFormat { WrappedDeserializer.DataFormat format = OutputFormat; - //If this shell is invoked in minishell interop mode and error is redirected, - //always write data in error stream in xml format. - if (IsInteractive == false && Console.IsErrorRedirected && _wasInitialCommandEncoded) + // 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 && Console.IsErrorRedirected && _wasInitialCommandEncoded) { format = Serialization.DataFormat.XML; } + return format; } } @@ -1282,14 +1450,12 @@ 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; } } @@ -1298,14 +1464,12 @@ 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; } } @@ -1330,29 +1494,21 @@ internal TextWriter ConsoleTextWriter } /// - /// /// The main run loop of the program: processes command line parameters, and starts up a runspace. - /// /// - /// /// /// Commandline parameter parser. The commandline parameter parser is expected to parse all the /// arguments before calling this method. /// - /// /// /// Is there any warning at startup /// - /// /// - /// /// The process exit code to be returned by Main. - /// /// - private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) { - Dbg.Assert(null != cpp, "CommandLine parameter parser cannot be null."); + Dbg.Assert(cpp != null, "CommandLine parameter parser cannot be null."); uint exitCode = ExitCodeSuccess; do @@ -1377,6 +1533,7 @@ private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) } OutputFormat = cpp.OutputFormat; + OutputFormatSpecified = cpp.OutputFormatSpecified; InputFormat = cpp.InputFormat; _wasInitialCommandEncoded = cpp.WasInitialCommandEncoded; @@ -1388,20 +1545,22 @@ private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) #if !UNIX // See if we need to change the process-wide execution // policy - if (!String.IsNullOrEmpty(cpp.ExecutionPolicy)) + if (!string.IsNullOrEmpty(cpp.ExecutionPolicy)) { ExecutionPolicy executionPolicy = SecuritySupport.ParseExecutionPolicy(cpp.ExecutionPolicy); SecuritySupport.SetExecutionPolicy(ExecutionPolicyScope.Process, executionPolicy, null); } #endif + // If the debug pipe name was specified, create the custom IPC channel. + if (!string.IsNullOrEmpty(cpp.CustomPipeName)) + { + RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(cpp.CustomPipeName); + } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure -#if STAMODE - exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName); -#else - exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, false, cpp.ConfigurationName); -#endif + exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName, cpp.ConfigurationFile); } while (false); @@ -1409,42 +1568,30 @@ private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned) } /// - /// This method is retained to make V1 tests compatible with V2 as signature of this method - /// is slightly changed in v2. - /// - /// - /// - /// - /// - /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private uint Run(string bannerText, string helpText, bool isPrestartWarned, string[] args) - { - s_cpp = new CommandLineParameterParser(this.UI, bannerText, helpText); - s_cpp.Parse(args); - return Run(s_cpp, isPrestartWarned); - } - - /// - /// /// Loops over the Host's sole Runspace; opens the runspace, initializes it, then recycles it if the Runspace fails. - /// /// /// - /// /// 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) { @@ -1480,12 +1627,12 @@ private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection _runspaceRef.Runspace.Close(); _runspaceRef = null; -#if STAMODE - if (staMode) // don't recycle the Runspace in STA mode + + if (staMode) { + // don't continue the session in STA mode ShouldEndSession = true; } -#endif } return ExitCode; @@ -1493,7 +1640,7 @@ private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection private Exception InitializeRunspaceHelper(string command, Executor exec, Executor.ExecutionOptions options) { - Dbg.Assert(!String.IsNullOrEmpty(command), "command should have a value"); + Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); Dbg.Assert(exec != null, "non-null Executor instance needed"); s_runspaceInitTracer.WriteLine("running command {0}", command); @@ -1508,6 +1655,7 @@ private Exception InitializeRunspaceHelper(string command, Executor exec, Execut { exec.ExecuteCommand(command, out e, options); } + if (e != null) { ReportException(e, exec); @@ -1516,18 +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"); -#if STAMODE - DoCreateRunspace(args.InitialCommand, args.SkipProfiles, args.StaMode, args.ConfigurationName, args.InitialCommandArgs); -#else - DoCreateRunspace(args.InitialCommand, args.SkipProfiles, false, args.ConfigurationName, args.InitialCommandArgs); -#endif + Dbg.Assert(runspaceCreationArgs != null, "Arguments to CreateRunspace should not be null."); + DoCreateRunspace(runspaceCreationArgs); } catch (ConsoleHostStartupException startupException) { @@ -1536,21 +1678,12 @@ private void CreateRunspace(object runspaceCreationArgs) } } - /// - /// This method is here only to make V1 tests compatible with V2. DO NOT USE THIS FUNCTION! Use DoCreateRunspace instead - /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void InitializeRunspace(string initialCommand, bool skipProfiles, Collection initialCommandArgs) - { - DoCreateRunspace(initialCommand, skipProfiles, staMode: false, configurationName: null, initialCommandArgs: initialCommandArgs); - } - - 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 // - exception: when -noexit is specified, we will be interactive after the command/file finishes - // * -noninteractive: this should be obvious, they've asked that we don't every prompt + // * -noninteractive: this should be obvious, they've asked that we don't ever prompt // // Note that PSReadline doesn't support redirected stdin/stdout, but we don't check that here because // a future version might, and we should automatically load it at that unknown point in the future. @@ -1559,18 +1692,36 @@ 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; @@ -1584,8 +1735,8 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta // It's also important to have a scenario where PSReadline is not loaded so it can be updated, e.g. // 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()) + ReadOnlyCollection defaultImportModulesList = null; + if (!customConfigurationProvided && LoadPSReadline()) { // Create and open Runspace with PSReadline. defaultImportModulesList = DefaultInitialSessionState.Modules; @@ -1593,7 +1744,7 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); try { - OpenConsoleRunspace(consoleRunspace, staMode); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } catch (Exception) { @@ -1610,10 +1761,12 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta DefaultInitialSessionState.ClearPSModules(); DefaultInitialSessionState.ImportPSModule(defaultImportModulesList); } + consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); - OpenConsoleRunspace(consoleRunspace, staMode); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } + Runspace.PrimaryRunspace = consoleRunspace; _runspaceRef = new RunspaceRef(consoleRunspace); if (psReadlineFailed) @@ -1637,35 +1790,29 @@ private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool sta PSTask.PowershellConsoleStartup, PSKeyword.UseAlwaysOperational); } +#if LEGACYTELEMETRY // Record how long it took from process start to runspace open for telemetry. _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 - // staMode will have following values: - // On FullPS: 'true'/'false' = default('true'=STA) + possibility of overload through cmdline parameter '-mta' - // On NanoPS: always 'false' = default('false'=MTA) + NO possibility of overload through cmdline parameter '-mta' - // ThreadOptions should match on FullPS and NanoPS for corresponding ApartmentStates. - if (staMode) + if (staMode && Platform.IsWindowsDesktop) { - // we can't change ApartmentStates on CoreCLR runspace.ApartmentState = ApartmentState.STA; } -#endif + runspace.ThreadOptions = PSThreadOptions.ReuseThread; runspace.EngineActivityId = EtwActivity.GetActivityId(); - Runspace.PrimaryRunspace = runspace; s_runspaceInitTracer.WriteLine("Calling Runspace.Open"); - runspace.Open(); } - private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, string configurationName, Collection initialCommandArgs) + private void DoRunspaceInitialization(RunspaceCreationEventArgs args) { if (_runspaceRef.Runspace.Debugger != null) { @@ -1675,13 +1822,38 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, Executor exec = new Executor(this, false, false); - if (!string.IsNullOrEmpty(configurationName)) + // If working directory was specified, set it + if (s_cpp != null && s_cpp.WorkingDirectory != null) + { + Pipeline tempPipeline = exec.CreatePipeline(); + var command = new Command("Set-Location"); + command.Parameters.Add("LiteralPath", s_cpp.WorkingDirectory); + tempPipeline.Commands.Add(command); + + Exception exception; + if (IsRunningAsync) + { + exec.ExecuteCommandAsyncHelper(tempPipeline, out exception, Executor.ExecutionOptions.AddOutputter); + } + else + { + exec.ExecuteCommandHelper(tempPipeline, out exception, Executor.ExecutionOptions.AddOutputter); + } + + if (exception != null) + { + _lastRunspaceInitializationException = exception; + ReportException(exception, exec); + } + } + + 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); @@ -1695,13 +1867,39 @@ 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) - if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) + // 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) { - _runspaceRef.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; + 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); @@ -1719,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: @@ -1737,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); } @@ -1764,23 +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); } @@ -1788,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; } @@ -1837,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); } @@ -1865,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; @@ -1892,7 +2093,7 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, private void RunProfile(string profileFileName, Executor exec) { - if (!String.IsNullOrEmpty(profileFileName)) + if (!string.IsNullOrEmpty(profileFileName)) { s_runspaceInitTracer.WriteLine("checking profile" + profileFileName); @@ -1919,15 +2120,11 @@ private void RunProfile(string profileFileName, Executor exec) } } - /// - /// - /// Escapes backtick and tick characters with a backtick, returns the result - /// + /// Escapes backtick and tick characters with a backtick, returns the result. /// /// /// - internal static string EscapeSingleQuotes(string str) { // worst case we have to escape every character, so capacity is twice as large as input length @@ -1940,6 +2137,7 @@ internal static string EscapeSingleQuotes(string str) { sb.Append(c); } + sb.Append(c); } @@ -1950,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); @@ -1974,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; } @@ -1985,9 +2181,10 @@ private void ReportException(Exception e, Executor exec) error = (object)new ErrorRecord(e, "ConsoleHost.ReportException", ErrorCategory.NotSpecified, null); } - PSObject wrappedError = new PSObject(error); - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - wrappedError.Properties.Add(note); + PSObject wrappedError = new PSObject(error) + { + WriteStream = WriteStreamType.Error + }; Exception e1 = null; @@ -2004,25 +2201,18 @@ 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); } } /// - /// /// Reports an exception according to the exception reporting settings in effect. - /// /// /// - /// /// The exception to report. - /// /// /// - /// /// Optional header message. Empty or null means "no header" - /// /// private void ReportExceptionFallback(Exception e, string header) { @@ -2031,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) { @@ -2057,33 +2248,49 @@ 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; + } } /// - /// raised when the host pops a runspace + /// Raised when the host pops a runspace. /// internal event EventHandler RunspacePopped; /// - /// raised when the host pushes a runspace + /// Raised when the host pushes a runspace. /// internal event EventHandler RunspacePushed; - #endregion non-overrides #region debugger /// - /// Handler for debugger events + /// Handler for debugger events. /// 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; @@ -2095,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(); } // @@ -2107,7 +2311,7 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) if (_displayDebuggerBanner) { WriteDebuggerMessage(ConsoleHostStrings.EnteringDebugger); - WriteDebuggerMessage(""); + WriteDebuggerMessage(string.Empty); _displayDebuggerBanner = false; } @@ -2120,10 +2324,10 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) foreach (Breakpoint breakpoint in e.Breakpoints) { - WriteDebuggerMessage(String.Format(CultureInfo.CurrentCulture, format, breakpoint)); + WriteDebuggerMessage(string.Format(CultureInfo.CurrentCulture, format, breakpoint)); } - WriteDebuggerMessage(""); + WriteDebuggerMessage(string.Empty); } // @@ -2143,15 +2347,12 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) finally { _debuggerStopEventArgs = null; - if (baseLoop != null) - { - baseLoop.ResumeCommandOutput(); - } + baseLoop?.ResumeCommandOutput(); } } /// - /// Returns true if the host is in debug mode + /// Returns true if the host is in debug mode. /// private bool InDebugMode { get; set; } @@ -2219,7 +2420,7 @@ private void ExitDebugMode(DebuggerResumeAction resumeAction) } /// - /// Writes a line using the debugger colors + /// Writes a line using the debugger colors. /// private void WriteDebuggerMessage(string line) { @@ -2231,15 +2432,13 @@ private void WriteDebuggerMessage(string line) #region aux classes /// - /// /// InputLoop represents the prompt-input-execute loop of the interactive host. Input loops can be nested, meaning that /// one input loop can be interrupted and another started; when the second ends, the first resumes. /// /// 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) { @@ -2267,15 +2466,11 @@ internal static void RunNewInputLoop(ConsoleHost parent, bool isNested) // Presently, this will not work if the Run loop is blocked on a ReadLine call. Whether that's a // problem or not depends on when we expect calls to this function to be made. /// - /// /// /// True if next input loop is nested, False otherwise. /// - /// /// when there is no instanceStack.Count == 0 - /// /// - internal static bool ExitCurrentLoop() { if (s_instanceStack.Count == 0) @@ -2314,8 +2509,8 @@ private InputLoop(ConsoleHost parent, bool isNested) _parent = parent; _isNested = isNested; _isRunspacePushed = parent.IsRunspacePushed; - parent.RunspacePopped += new EventHandler(HandleRunspacePopped); - parent.RunspacePushed += new EventHandler(HandleRunspacePushed); + parent.RunspacePopped += HandleRunspacePopped; + parent.RunspacePushed += HandleRunspacePushed; _exec = new Executor(parent, isNested, false); _promptExec = new Executor(parent, isNested, true); } @@ -2331,10 +2526,10 @@ private void HandleRunspacePushed(object sender, EventArgs e) /// /// When a runspace is popped, we need to reevaluate the - /// prompt + /// prompt. /// - /// sender of this event, unused - /// arguments describing this event, unused + /// Sender of this event, unused. + /// Arguments describing this event, unused. private void HandleRunspacePopped(object sender, EventArgs eventArgs) { lock (_syncObject) @@ -2347,21 +2542,20 @@ private void HandleRunspacePopped(object sender, EventArgs eventArgs) // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure /// - /// /// Evaluates the prompt, displays it, gets a command from the console, and executes it. Repeats until the command /// is "exit", or until the shutdown flag is set. - /// /// 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) { @@ -2377,7 +2571,6 @@ internal void Run(bool inputLoopIsNested) if (inBlockMode) { // use a special prompt that denotes block mode - prompt = ">> "; } else @@ -2388,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 @@ -2398,13 +2591,21 @@ 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); @@ -2423,6 +2624,7 @@ internal void Run(bool inputLoopIsNested) // the output... ui.WriteLine(); } + inBlockMode = false; if (Console.IsInputRedirected) @@ -2456,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; } @@ -2470,7 +2672,7 @@ internal void Run(bool inputLoopIsNested) if (_parent.InDebugMode) { - DebuggerCommandResults results = ProcessDebugCommand(line.Trim(), out e); + DebuggerCommandResults results = ProcessDebugCommand(line, out e); if (results.ResumeAction != null) { @@ -2507,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); @@ -2523,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. @@ -2548,23 +2755,25 @@ e is RemoteException || } } +#if LEGACYTELEMETRY if (!inBlockMode) s_theConsoleHost._interactiveCommandCount += 1; +#endif } } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure finally { _parent._isRunningPromptLoop = false; } - } // end while + } } internal void BlockCommandOutput() { - RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline; - if (rCmdPipeline != null) + if (_parent.runningCmd is RemotePipeline rCmdPipeline) { rCmdPipeline.DrainIncomingData(); rCmdPipeline.SuspendIncomingData(); @@ -2577,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(); } @@ -2606,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); } @@ -2659,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) @@ -2668,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 { - ArrayList 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) { @@ -2717,10 +2909,9 @@ 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)) + if (string.IsNullOrEmpty(promptString)) { promptString = ConsoleHostStrings.DefaultPrompt; } @@ -2728,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); } @@ -2763,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; @@ -2789,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) { } } @@ -2844,16 +3017,16 @@ 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 System.Threading.Thread _breakHandlerThread; + 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 = ""; - private Version _ver = PSVersionInfo.PSVersion; + private string _savedWindowTitle = string.Empty; + private readonly Version _ver = PSVersionInfo.PSVersion; private int _exitCodeFromRunspace; private bool _noExit = true; private bool _setShouldExitCalled; @@ -2872,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; @@ -2885,56 +3058,50 @@ private class ConsoleHostStartupException : Exception private static ConsoleHost s_theConsoleHost; - 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); - } // ConsoleHost + } /// - /// Defines arguments passed to ConsoleHost.CreateRunspace + /// Defines arguments passed to ConsoleHost.CreateRunspace. /// internal sealed class RunspaceCreationEventArgs : EventArgs { /// - /// Constructs RunspaceCreationEventArgs + /// Constructs RunspaceCreationEventArgs. /// - /// - /// - /// - /// - /// - internal RunspaceCreationEventArgs(string initialCommand, - bool skipProfiles, - bool staMode, - string configurationName, - Collection initialCommandArgs) + internal RunspaceCreationEventArgs( + string initialCommand, + bool skipProfiles, + bool staMode, + string configurationName, + string configurationFilePath, + Collection initialCommandArgs) { InitialCommand = initialCommand; SkipProfiles = skipProfiles; -#if STAMODE StaMode = staMode; -#endif ConfigurationName = configurationName; + ConfigurationFilePath = configurationFilePath; InitialCommandArgs = initialCommandArgs; } internal string InitialCommand { get; set; } + internal bool SkipProfiles { get; set; } -#if STAMODE + internal bool StaMode { get; set; } -#endif + 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 af6f660c628..58630f5b3c0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs @@ -1,46 +1,34 @@ -#if !UNIX -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#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 - /// + /// Implementation of RawConsole for powershell. /// - internal sealed class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawUserInterface { /// - /// /// /// - /// /// If obtaining the buffer's foreground and background color failed - /// /// - internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base() { @@ -53,47 +41,33 @@ 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); } } }); } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If set to an invalid ConsoleColor - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute - /// /// - public override ConsoleColor ForegroundColor @@ -104,11 +78,11 @@ 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; } + set { if (ConsoleControl.IsConsoleColor(value)) @@ -131,27 +105,18 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If set to an invalid ConsoleColor - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute - /// /// - public override ConsoleColor BackgroundColor @@ -162,11 +127,11 @@ 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; } + set { if (ConsoleControl.IsConsoleColor(value)) @@ -189,25 +154,17 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If set to outside of the buffer - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleCursorPosition failed - /// /// public override Coordinates @@ -221,46 +178,37 @@ public override Coordinates c = new Coordinates(bufferInfo.CursorPosition.X, bufferInfo.CursorPosition.Y); return c; } + 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 + } } } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// Cursor size - /// /// /// - /// /// If set to under 0 or over 100 - /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// Win32's GetConsoleCursorInfo failed /// OR /// Win32's SetConsoleCursorInfo failed - /// /// - public override int CursorSize @@ -272,6 +220,7 @@ public override return size; } + set { const int MinCursorSize = 0; @@ -303,27 +252,18 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If set outside of the buffer - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleWindowInfo failed - /// /// - public override Coordinates WindowPosition @@ -337,6 +277,7 @@ public override return c; } + set { ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo; @@ -375,29 +316,20 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If setting to an invalid size - /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// obtaining information about the buffer failed /// OR /// Win32's SetConsoleScreenBufferSize failed - /// /// - public override Size BufferSize @@ -408,6 +340,7 @@ public override GetBufferInfo(out bufferInfo); return new Size(bufferInfo.BufferSize.X, bufferInfo.BufferSize.Y); } + set { // looking in windows/core/ntcon/server/output.c, it looks like the minimum size is 1 row X however many @@ -421,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, @@ -436,28 +368,19 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If setting width or height to less than 1, larger than the screen buffer, /// over the maximum window size allowed - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleWindowInfo failed - /// /// - public override Size WindowSize @@ -474,6 +397,7 @@ public override return s; } + set { ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo; @@ -523,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; @@ -570,20 +494,13 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining information about the buffer failed - /// /// - public override Size MaxWindowSize @@ -598,22 +515,15 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// Win32's GetLargestConsoleWindowSize failed - /// /// - public override Size MaxPhysicalWindowSize @@ -626,20 +536,20 @@ public override } /// - /// Helper method to create and trace PipelineStoppedException + /// Helper method to create and trace PipelineStoppedException. /// /// - private PipelineStoppedException NewPipelineStoppedException() + private static PipelineStoppedException NewPipelineStoppedException() { PipelineStoppedException e = new PipelineStoppedException(); return e; } /// - /// Used by ReadKey, cache KeyEvent based on if input.RepeatCount > 1 + /// Used by ReadKey, cache KeyEvent based on if input.RepeatCount > 1. /// - /// Input key event record - /// Cache key event + /// Input key event record. + /// Cache key event. private static void CacheKeyEvent(ConsoleControl.KEY_EVENT_RECORD input, ref ConsoleControl.KEY_EVENT_RECORD cache) { if (input.RepeatCount > 1) @@ -650,7 +560,6 @@ private static void CacheKeyEvent(ConsoleControl.KEY_EVENT_RECORD input, ref Con } /// - /// /// See base class /// This method unwraps the repeat count in KEY_EVENT_RECORD by caching repeated keystrokes /// in a logical queue. The implications are: @@ -659,33 +568,27 @@ private static void CacheKeyEvent(ConsoleControl.KEY_EVENT_RECORD input, ref Con /// key events: {Ctrl, KeyDown}, {Ctrl-c KeyDown}, {Ctrl, KeyUp}, {c, KeyUp} if Ctrl is released before c. /// In this case, {Ctrl, KeyUp}, {c, KeyUp} would be returned. /// 2) If the cache is non-empty, a call to ReadLine will not return the cached keys. This - /// behavior is the same as that of System.Console.ReadKey - /// + /// behavior is the same as that of System.Console.ReadKey. /// /// /// /// - /// /// If neither IncludeKeyDown or IncludeKeyUp is set in - /// /// /// - /// /// 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 ReadConsoleInput failed - /// /// - public override KeyInfo ReadKey(ReadKeyOptions options) { if ((options & (ReadKeyOptions.IncludeKeyDown | ReadKeyOptions.IncludeKeyUp)) == 0) { - throw PSTraceSource.NewArgumentException("options", ConsoleHostRawUserInterfaceStrings.InvalidReadKeyOptionsError); + throw PSTraceSource.NewArgumentException(nameof(options), ConsoleHostRawUserInterfaceStrings.InvalidReadKeyOptionsError); } // keyInfo is initialized in the below if-else statement @@ -709,6 +612,7 @@ public override cachedKeyEvent.RepeatCount = 0; } } + if (cachedKeyEvent.RepeatCount > 0) { KEY_EVENT_RECORDToKeyInfo(cachedKeyEvent, out keyInfo); @@ -734,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) == @@ -780,16 +683,12 @@ public override if ((options & ReadKeyOptions.NoEcho) == 0) { - parent.WriteToConsole( - keyInfo.Character.ToString(), - true); + parent.WriteToConsole(keyInfo.Character, transcribeResult: true); } return keyInfo; } - - private static void KEY_EVENT_RECORDToKeyInfo(ConsoleControl.KEY_EVENT_RECORD keyEventRecord, out KeyInfo keyInfo) @@ -801,21 +700,14 @@ private static keyEventRecord.KeyDown); } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// Win32's FlushConsoleInputBuffer failed - /// /// - public override void FlushInputBuffer() @@ -826,24 +718,17 @@ public override cachedKeyEvent.RepeatCount = 0; } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// Win32's GetNumberOfConsoleInputEvents failed /// OR /// Win32's PeekConsoleInput failed - /// /// - public override bool KeyAvailable @@ -876,6 +761,7 @@ public override // represent a keystroke. continue; } + return true; } } @@ -884,29 +770,20 @@ public override } } - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If set to null - /// /// /// - /// /// If set to a string whose length is not between 1 to 1023 - /// /// /// - /// /// If Win32's GetConsoleWindowTitle failed /// OR /// Win32's SetConsoleWindowTitle failed - /// /// public override string WindowTitle { @@ -914,6 +791,7 @@ public override string WindowTitle { return ConsoleControl.GetConsoleWindowTitle(); } + set { const int MaxWindowTitleLength = 1023; @@ -948,55 +826,43 @@ public override string WindowTitle } /// - /// /// See base class. - /// - /// /// /// - /// /// location on screen buffer where contents will be written - /// /// /// - /// /// array of info to be written - /// /// /// /// - /// /// If is outside of the screen buffer. /// OR /// is an ill-formed BufferCell array /// OR /// it is illegal to write at in the buffer - /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// obtaining information about the buffer failed /// OR /// there is not enough memory to complete calls to Win32's WriteConsoleOutput - /// /// - public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) { if (contents == null) { - PSTraceSource.NewArgumentNullException("contents"); + PSTraceSource.NewArgumentNullException(nameof(contents)); } // the origin must be within the window. ConsoleControl.CONSOLE_SCREEN_BUFFER_INFO bufferInfo; ConsoleHandle handle = GetBufferInfo(out bufferInfo); - CheckCoordinateWithinBuffer(ref origin, ref bufferInfo, "origin"); + CheckCoordinateWithinBuffer(ref origin, ref bufferInfo, nameof(origin)); // The output is clipped by the console subsystem, so we don't have to check that the array exceeds the buffer // boundaries. @@ -1004,48 +870,34 @@ public override ConsoleControl.WriteConsoleOutput(handle, origin, contents); } - - /// - /// /// If is completely outside of the screen buffer, it's a no-op. - /// /// /// - /// /// region with all elements = -1 means "entire screen buffer" - /// /// /// - /// /// character and attribute to fill the screen buffer - /// /// /// - /// /// Provided for clearing regions -- less chatty than passing an array of cells. /// Clear screen is: /// SetBufferContents(new Rectangle(-1, -1, -1, -1), ' ', ForegroundColor, BackgroundColor); /// CursorPosition = new Coordinates(0, 0); /// /// fill.Type is ignored - /// /// /// - /// /// If 's Left exceeds Right or Bottom exceeds Top /// OR /// it is illegal to set in the buffer with - /// /// /// - /// /// If Win32's CreateFile fails /// OR /// Win32's GetConsoleScreenBufferInfo fails /// OR /// there is not enough memory to complete calls to Win32's WriteConsoleOutput - /// /// public override void @@ -1054,14 +906,14 @@ public override // make sure the rect is valid if (region.Right < region.Left) { - throw PSTraceSource.NewArgumentException("region", + throw PSTraceSource.NewArgumentException(nameof(region), ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate, "region.Right", "region.Left"); } if (region.Bottom < region.Top) { - throw PSTraceSource.NewArgumentException("region", + throw PSTraceSource.NewArgumentException(nameof(region), ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate, "region.Bottom", "region.Top"); } @@ -1083,7 +935,7 @@ public override ConsoleControl.IsCJKOutputCodePage(out codePage) && LengthInBufferCells(fill.Character) == 2) { - throw PSTraceSource.NewArgumentException("fill"); + throw PSTraceSource.NewArgumentException(nameof(fill)); } int cells = bufferWidth * bufferHeight; @@ -1128,7 +980,7 @@ public override { if (leftExisting[r, 0].BufferCellType == BufferCellType.Leading) { - throw PSTraceSource.NewArgumentException("fill"); + throw PSTraceSource.NewArgumentException(nameof(fill)); } } } @@ -1137,7 +989,7 @@ public override { if (charLength == 2) { - throw PSTraceSource.NewArgumentException("fill"); + throw PSTraceSource.NewArgumentException(nameof(fill)); } } else @@ -1153,7 +1005,7 @@ public override { if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading) { - throw PSTraceSource.NewArgumentException("fill"); + throw PSTraceSource.NewArgumentException(nameof(fill)); } } } @@ -1163,16 +1015,18 @@ public override { if (rightExisting[r, 0].BufferCellType == BufferCellType.Leading ^ charLength == 2) { - throw PSTraceSource.NewArgumentException("fill"); + throw PSTraceSource.NewArgumentException(nameof(fill)); } } } } + if (lineLength % 2 == 1) { lineLength++; } } + for (int row = firstRow; row <= lastRow; ++row) { origin.Y = row; @@ -1185,53 +1039,40 @@ public override } } - - /// - /// /// See base class. /// If the rectangle is invalid, ie, Right exceeds Left OR Bottom exceeds Top, - /// /// /// - /// /// area on screen buffer to be read - /// /// /// - /// /// an array of BufferCell containing screen buffer contents - /// /// /// - /// /// If 's Left exceeds Right or Bottom exceeds Top. - /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// obtaining information about the buffer failed /// OR /// there is not enough memory to complete calls to Win32's ReadConsoleOutput - /// /// - public override BufferCell[,] GetBufferContents(Rectangle region) { // make sure the rect is valid if (region.Right < region.Left) { - throw PSTraceSource.NewArgumentException("region", + throw PSTraceSource.NewArgumentException(nameof(region), ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate, "region.Right", "region.Left"); } if (region.Bottom < region.Top) { - throw PSTraceSource.NewArgumentException("region", + throw PSTraceSource.NewArgumentException(nameof(region), ConsoleHostRawUserInterfaceStrings.InvalidRegionErrorTemplate, "region.Bottom", "region.Top"); } @@ -1271,39 +1112,25 @@ public override return contents; } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// area to be moved - /// /// /// - /// /// top left corner to which source to be moved - /// /// /// - /// /// area to be updated caused by the move - /// /// /// - /// /// character and attribute to fill the area vacated by the move - /// /// /// - /// /// If obtaining the active screen buffer failed /// OR /// Call to Win32's ScrollConsoleScreenBuffer failed - /// /// public override void @@ -1348,16 +1175,13 @@ BufferCell fill } /// - /// See base class + /// See base class. /// /// /// /// - /// /// If Win32's WideCharToMultiByte fails - /// /// - public override int LengthInBufferCells(string s) { @@ -1365,17 +1189,14 @@ int LengthInBufferCells(string s) } /// - /// See base class + /// See base class. /// /// /// /// /// - /// /// If Win32's WideCharToMultiByte fails - /// /// - public override int LengthInBufferCells(string s, int offset) { @@ -1383,35 +1204,28 @@ int LengthInBufferCells(string s, int offset) { throw PSTraceSource.NewArgumentNullException("str"); } + return ConsoleControl.LengthInBufferCells(s, offset, parent.SupportsVirtualTerminal); } - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// - /// /// If Win32's WideCharToMultiByte fails - /// /// - public override int LengthInBufferCells(char c) { return ConsoleControl.LengthInBufferCells(c); } -#region internal + #region internal /// - /// - /// Clear the ReadKey cache - /// + /// Clear the ReadKey cache. /// /// internal void ClearKeyCache() @@ -1419,14 +1233,12 @@ internal void ClearKeyCache() cachedKeyEvent.RepeatCount = 0; } -#endregion internal - -#region helpers + #endregion internal + #region helpers // pass-by-ref for speed. /// - /// /// /// /// @@ -1455,9 +1267,8 @@ private static } } - /// - /// Get output buffer info + /// Get output buffer info. /// /// /// @@ -1475,21 +1286,18 @@ private static return result; } + #endregion helpers + private readonly ConsoleColor defaultForeground = ConsoleColor.Gray; -#endregion helpers - - private 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 @@ -1511,12 +1319,12 @@ private static namespace Microsoft.PowerShell { - // this is all originally from https://msdn.microsoft.com/en-us/library/ee706570%28v=vs.85%29.aspx + // this is all originally from https://msdn.microsoft.com/library/ee706570%28v=vs.85%29.aspx internal sealed class ConsoleHostRawUserInterface : PSHostRawUserInterface { - private ConsoleHostUserInterface _parent = null; + private readonly ConsoleHostUserInterface _parent = null; internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base() { @@ -1530,6 +1338,7 @@ internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base public override ConsoleColor BackgroundColor { get { return Console.BackgroundColor; } + set { Console.BackgroundColor = value; } } @@ -1549,7 +1358,11 @@ public override Size BufferSize ? s_wrapSize : new Size(Console.BufferWidth, Console.BufferHeight); } - set { Console.SetBufferSize(value.Width, value.Height); } + + set + { + Console.SetBufferSize(value.Width, value.Height); + } } /// @@ -1557,7 +1370,11 @@ 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 { Console.SetCursorPosition(value.X < 0 ? 0 : value.X, @@ -1574,6 +1391,7 @@ public override int CursorSize // Future porting note: this API throws on Windows when output is // redirected, but never throws on Unix because it's fake. get { return Console.CursorSize; } + set { Console.CursorSize = value; } } @@ -1584,6 +1402,7 @@ public override int CursorSize public override ConsoleColor ForegroundColor { get { return Console.ForegroundColor; } + set { Console.ForegroundColor = value; } } @@ -1632,6 +1451,7 @@ public override Size MaxWindowSize public override Coordinates WindowPosition { get { return new Coordinates(Console.WindowLeft, Console.WindowTop); } + set { Console.SetWindowPosition(value.X, value.Y); } } @@ -1650,13 +1470,17 @@ public override Size WindowSize ? s_wrapSize : new Size(Console.WindowWidth, Console.WindowHeight); } - set { Console.SetWindowSize(value.Width, value.Height); } + + set + { + Console.SetWindowSize(value.Width, value.Height); + } } /// - /// Cached Window Title, for systems that needs it + /// Cached Window Title, for systems that needs it. /// - private string _title = String.Empty; + private string _title = string.Empty; /// /// Gets or sets the title of the displayed window. The example @@ -1706,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}"); } } @@ -1722,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}"); } } @@ -1774,16 +1598,15 @@ public override void ScrollBufferContents(Rectangle source, Coordinates destinat public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) { - //if there are no contents, there is nothing to set the buffer to + // if there are no contents, there is nothing to set the buffer to if (contents == null) { PSTraceSource.NewArgumentNullException("contents"); } - - //if the cursor is on the last line, we need to make more space to print the specified buffer + // if the cursor is on the last line, we need to make more space to print the specified buffer if (origin.Y == BufferSize.Height - 1 && origin.X >= BufferSize.Width) { - //for each row in the buffer, create a new line + // for each row in the buffer, create a new line int rows = contents.GetLength(0); ScrollBuffer(rows); // for each row in the buffer, move the cursor y up to the beginning of the created blank space @@ -1794,25 +1617,33 @@ public override void SetBufferContents(Coordinates origin, } } - //iterate through the buffer to set +#if UNIX + // Make sure that the physical cursor position matches where we think it is. + // This is a problem on *nix, because input that the user types is echoed + // and that moves the cursor. As a consequence, the cursor needs to be repositioned + // before we update the screen. + CursorPosition = origin; +#endif + + // iterate through the buffer to set foreach (var charitem in contents) { - //set the cursor to false to prevent cursor flicker + // set the cursor to false to prevent cursor flicker Console.CursorVisible = false; - //if x is exceeding buffer width, reset to the next line + // if x is exceeding buffer width, reset to the next line if (origin.X >= BufferSize.Width) { origin.X = 0; } - //write the character from contents + // write the character from contents Console.Out.Write(charitem.Character); } - //reset the cursor to the original position + // reset the cursor to the original position CursorPosition = origin; - //reset the cursor to visible + // reset the cursor to visible Console.CursorVisible = true; } @@ -1821,7 +1652,7 @@ public override void SetBufferContents(Coordinates origin, /// color to a region of the screen buffer. In this example this /// functionality is not needed so the method throws a /// NotImplementException exception./// - /// Defines the area to be filled. + /// Defines the area to be filled. /// Defines the fill character. public override void SetBufferContents(Rectangle rectangle, BufferCell fill) { @@ -1829,7 +1660,7 @@ public override void SetBufferContents(Rectangle rectangle, BufferCell fill) } /// - /// See base class + /// See base class. /// /// /// @@ -1841,7 +1672,7 @@ int LengthInBufferCells(string s) } /// - /// See base class + /// See base class. /// /// /// @@ -1854,6 +1685,7 @@ int LengthInBufferCells(string s, int offset) { throw PSTraceSource.NewArgumentNullException("str"); } + return ConsoleControl.LengthInBufferCells(s, offset, _parent.SupportsVirtualTerminal); } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs index b9143e878ea..3ac758ff86c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs @@ -1,30 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.IO; 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 + internal sealed partial class ConsoleHost : PSHost, IDisposable { - internal - bool - IsTranscribing + internal bool IsTranscribing { get { @@ -32,13 +18,14 @@ class ConsoleHost return _isTranscribing; } + set { _isTranscribing = value; } } - private bool _isTranscribing; + private bool _isTranscribing; /* internal void StartTranscribing(string transcriptFilename, bool shouldAppend) @@ -72,13 +59,9 @@ internal void StartTranscribing(string transcriptFilename, bool shouldAppend) } } */ - private string _transcriptFileName = String.Empty; + private readonly string _transcriptFileName = string.Empty; - - - internal - string - StopTranscribing() + internal string StopTranscribing() { lock (_transcriptionStateLock) { @@ -113,26 +96,35 @@ internal void StartTranscribing(string transcriptFilename, bool shouldAppend) } } + internal void WriteToTranscript(ReadOnlySpan text) + { + WriteToTranscript(text, newLine: false); + } + internal void WriteLineToTranscript(ReadOnlySpan text) + { + WriteToTranscript(text, newLine: true); + } - internal - void - WriteToTranscript(string text) + internal void WriteToTranscript(ReadOnlySpan text, bool newLine) { lock (_transcriptionStateLock) { if (_isTranscribing && _transcriptionWriter != null) { - _transcriptionWriter.Write(text); + if (newLine) + { + _transcriptionWriter.WriteLine(text); + } + else + { + _transcriptionWriter.Write(text); + } } } } - - 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 6d178849871..f0da0d99547 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1,18 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.IO; -using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; -using System.Management.Automation.Runspaces; -using System.Text; using System.Management.Automation; -using System.Management.Automation.Internal; using System.Management.Automation.Host; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; +using System.Runtime.CompilerServices; using System.Security; +using System.Text; + using Dbg = System.Management.Automation.Diagnostics; #if !UNIX using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; @@ -23,77 +24,113 @@ namespace Microsoft.PowerShell using PowerShell = System.Management.Automation.PowerShell; /// - /// - /// ConsoleHostUserInterface implements console-mode user interface for powershell - /// + /// ConsoleHostUserInterface implements console-mode user interface for powershell. /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] internal partial class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface { /// - /// Command completion implementation object + /// This is the char that is echoed to the console when the input is masked. This not localizable. + /// + private const char PrintToken = '*'; + + /// + /// Command completion implementation object. /// private PowerShell _commandCompletionPowerShell; /// /// 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 + /// Return true if the console supports a VT100 like virtual terminal. /// public override bool SupportsVirtualTerminal { get; } /// - /// - /// Constructs an instance - /// + /// Constructs an instance. /// /// /// - 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 } /// - /// /// Supplies an implementation of PSHostRawUserInterface that provides low-level console mode UI facilities. - /// /// /// /// - public override PSHostRawUserInterface RawUI { get @@ -113,10 +150,10 @@ public override PSHostRawUserInterface RawUI ///// ///// - //internal - //PSHost - //Parent - //{ + // internal + // PSHost + // Parent + // { // get // { // using (tracer.TraceProperty()) @@ -126,15 +163,11 @@ public override PSHostRawUserInterface RawUI // return parent; // } // } - //} - + // } /// - /// - /// true if command completion is currently running - /// + /// True if command completion is currently running. /// - internal bool IsCommandCompletionRunning { get @@ -145,31 +178,22 @@ internal bool IsCommandCompletionRunning } /// - /// - /// true if the Read* functions should read from the stdin stream instead of from the win32 console. - /// + /// 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. - /// + /// True if the host shouldn't write out prompts. /// - internal bool NoPrompt { get; set; } #region Line-oriented interaction /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If Win32's SetConsoleMode fails /// OR /// Win32's ReadConsole fails @@ -177,55 +201,42 @@ internal bool IsCommandCompletionRunning /// obtaining information about the buffer failed /// 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, "", out unused, true, true); + return ReadLine(false, string.Empty, out _, true, true); } - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// 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 SecureString ReadLineAsSecureString() { HandleThrowOnReadAndPrompt(); - const char printToken = '*'; // This is not localizable - // we lock here so that multiple threads won't interleave the various reads and writes here. object result = null; lock (_instanceLock) { - result = ReadLineSafe(true, printToken); + result = ReadLineSafe(true, PrintToken); } + SecureString secureResult = result as SecureString; System.Management.Automation.Diagnostics.Assert(secureResult != null, "ReadLineSafe did not return a SecureString"); @@ -233,7 +244,6 @@ public override SecureString ReadLineAsSecureString() } /// - /// /// Implementation based on NT CredUI's GetPasswdStr. /// Use Win32.ReadConsole to construct a SecureString. The advantage of ReadConsole over ReadKey is /// Alt-ddd where d is {0-9} is allowed. @@ -244,21 +254,15 @@ public override SecureString ReadLineAsSecureString() /// Secondary implementation for Unix based on Console.ReadKey(), where /// 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 - /// /// /// - /// /// string for output echo - /// /// /// /// - /// /// If obtaining a handle to the active screen buffer failed /// OR /// Win32's setting input buffer mode to disregard window and mouse input failed @@ -268,14 +272,10 @@ public override SecureString ReadLineAsSecureString() /// obtaining information about the buffer failed /// OR /// Win32's SetConsoleCursorPosition failed - /// /// /// - /// /// 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... @@ -303,7 +303,7 @@ private object ReadLineSafe(bool isSecureString, char? printToken) #else // Ensure that we're in the proper line-input mode. - ConsoleControl.ConsoleModes desiredMode = + const ConsoleControl.ConsoleModes DesiredMode = ConsoleControl.ConsoleModes.Extended | ConsoleControl.ConsoleModes.QuickEdit; @@ -313,43 +313,46 @@ private object ReadLineSafe(bool isSecureString, char? printToken) bool shouldUnsetMouseInput = shouldUnsetMode(ConsoleControl.ConsoleModes.MouseInput, ref m); bool shouldUnsetProcessInput = shouldUnsetMode(ConsoleControl.ConsoleModes.ProcessedInput, ref m); - if ((m & desiredMode) != desiredMode || + if ((m & DesiredMode) != DesiredMode || shouldUnsetMouseInput || shouldUnsetEchoInput || shouldUnsetLineInput || shouldUnsetProcessInput) { - m |= desiredMode; + m |= DesiredMode; ConsoleControl.SetMode(handle, m); } else { isModeChanged = false; } + _rawui.ClearKeyCache(); #endif 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 - uint unused = 0; - string key = ConsoleControl.ReadConsole(handle, string.Empty, 1, false, out unused); + string key = ConsoleControl.ReadConsole(handle, initialContentLength: 0, inputBuffer, charactersToRead: CharactersToRead, endOnTab: false, out _); #endif #if UNIX // 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(); @@ -358,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 { // @@ -369,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 { // @@ -387,9 +390,9 @@ private object ReadLineSafe(bool isSecureString, char? printToken) } } #if UNIX - else if (Char.IsControl(keyInfo.KeyChar)) + else if (char.IsControl(keyInfo.KeyChar)) { - // blacklist control characters + // deny list control characters continue; } #endif @@ -414,13 +417,13 @@ private object ReadLineSafe(bool isSecureString, char? printToken) result.Append(key); #endif } + if (!string.IsNullOrEmpty(printTokenString)) { WritePrintToken(printTokenString, ref originalCursorPos); } } } - while (true); } #if UNIX catch (InvalidOperationException) @@ -440,6 +443,7 @@ private object ReadLineSafe(bool isSecureString, char? printToken) } #endif } + WriteLineToConsole(); PostRead(result.ToString()); if (isSecureString) @@ -452,30 +456,20 @@ private object ReadLineSafe(bool isSecureString, char? printToken) } } - /// - /// - /// Handle writing print token with proper cursor adjustment for ReadLineSafe - /// + /// Handle writing print token with proper cursor adjustment for ReadLineSafe. /// /// - /// /// token output for each char input. It must be a one-char string - /// /// /// - /// /// it is the cursor position where ReadLineSafe begins - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleCursorPosition failed - /// /// - private void WritePrintToken( string printToken, ref Coordinates originalCursorPosition) @@ -497,29 +491,21 @@ private void WritePrintToken( originalCursorPosition.Y--; } } + WriteToConsole(printToken, false); } - - /// - /// - /// Handle backspace with proper cursor adjustment for ReadLineSafe - /// + /// Handle backspace with proper cursor adjustment for ReadLineSafe. /// /// - /// /// it is the cursor position where ReadLineSafe begins - /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleCursorPosition failed - /// /// - private void WriteBackSpace(Coordinates originalCursorPosition) { Coordinates cursorPosition = _rawui.CursorPosition; @@ -548,12 +534,10 @@ private void WriteBackSpace(Coordinates originalCursorPosition) // do nothing if cursorPosition.X is left of screen } - - /// /// Blank out at and move rawui.CursorPosition to /// - /// Position to blank out + /// Position to blank out. private void BlankAtCursor(Coordinates cursorPosition) { _rawui.CursorPosition = cursorPosition; @@ -561,26 +545,19 @@ private void BlankAtCursor(Coordinates cursorPosition) _rawui.CursorPosition = cursorPosition; } - #if !UNIX /// - /// /// If is set on , unset it and return true; - /// otherwise return false - /// + /// otherwise return false. /// /// - /// /// a flag in ConsoleControl.ConsoleModes to be unset in - /// /// /// /// /// - /// /// true if is set on /// false otherwise - /// /// private static bool shouldUnsetMode( ConsoleControl.ConsoleModes flagToUnset, @@ -591,29 +568,42 @@ private static bool shouldUnsetMode( m &= ~flagToUnset; return true; } + return false; } #endif #region WriteToConsole - internal void WriteToConsole(string value, bool transcribeResult) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteToConsole(char c, bool transcribeResult) + { + ReadOnlySpan value = [c]; + WriteToConsole(value, transcribeResult); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteToConsole(ReadOnlySpan value, bool transcribeResult) + { + WriteToConsole(value, transcribeResult, newLine: false); + } + + private void WriteToConsole(ReadOnlySpan value, bool transcribeResult, bool newLine) { #if !UNIX ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle(); // Ensure that we're in the proper line-output mode. We don't lock here as it does not matter if we // attempt to set the mode from multiple threads at once. - ConsoleControl.ConsoleModes m = ConsoleControl.GetMode(handle); - const ConsoleControl.ConsoleModes desiredMode = - ConsoleControl.ConsoleModes.ProcessedOutput + const ConsoleControl.ConsoleModes DesiredMode = + ConsoleControl.ConsoleModes.ProcessedOutput | ConsoleControl.ConsoleModes.WrapEndOfLine; - if ((m & desiredMode) != desiredMode) + if ((m & DesiredMode) != DesiredMode) { - m |= desiredMode; + m |= DesiredMode; ConsoleControl.SetMode(handle, m); } #endif @@ -621,21 +611,20 @@ internal void WriteToConsole(string value, bool transcribeResult) PreWrite(); // This is atomic, so we don't lock here... - #if !UNIX - ConsoleControl.WriteConsole(handle, value); + ConsoleControl.WriteConsole(handle, value, newLine); #else - Console.Out.Write(value); + ConsoleOutWriteHelper(value, newLine); #endif if (_isInteractiveTestToolListening && Console.IsOutputRedirected) { - Console.Out.Write(value); + ConsoleOutWriteHelper(value, newLine); } if (transcribeResult) { - PostWrite(value); + PostWrite(value, newLine); } else { @@ -643,52 +632,73 @@ internal void WriteToConsole(string value, bool transcribeResult) } } - - - private void WriteToConsole(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string text) + private void WriteToConsole(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string text, bool newLine = false) { - ConsoleColor fg = RawUI.ForegroundColor; - ConsoleColor bg = RawUI.BackgroundColor; + // Sync access so that we don't conflict on color settings if called from multiple threads. + lock (_instanceLock) + { + ConsoleColor fg = RawUI.ForegroundColor; + ConsoleColor bg = RawUI.BackgroundColor; - RawUI.ForegroundColor = foregroundColor; - RawUI.BackgroundColor = backgroundColor; + RawUI.ForegroundColor = foregroundColor; + RawUI.BackgroundColor = backgroundColor; - try + try + { + WriteToConsole(text, transcribeResult: true, newLine); + } + finally + { + RawUI.ForegroundColor = fg; + RawUI.BackgroundColor = bg; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ConsoleOutWriteHelper(ReadOnlySpan value, bool newLine) + { + if (newLine) { - WriteToConsole(text, true); + Console.Out.WriteLine(value); } - finally + else { - RawUI.ForegroundColor = fg; - RawUI.BackgroundColor = bg; + Console.Out.Write(value); } } - private void WriteLineToConsole(string text) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteLineToConsole(ReadOnlySpan value, bool transcribeResult) { - WriteToConsole(text, true); - WriteToConsole(Crlf, true); + WriteToConsole(value, transcribeResult, newLine: true); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLineToConsole(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string text) + { + WriteToConsole(foregroundColor, backgroundColor, text, newLine: true); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteLineToConsole(string text) + { + WriteLineToConsole(text, transcribeResult: true); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteLineToConsole() { - WriteToConsole(Crlf, true); + WriteToConsole(Environment.NewLine, transcribeResult: true, newLine: false); } #endregion WriteToConsole - - /// - /// /// See base class. - /// /// /// /// - /// /// If Win32's CreateFile fails /// OR /// Win32's GetConsoleMode fails @@ -696,47 +706,71 @@ private void WriteLineToConsole() /// Win32's SetConsoleMode fails /// OR /// Win32's WriteConsole fails - /// /// - public override void Write(string value) { - if (string.IsNullOrEmpty(value)) + lock (_instanceLock) { - // do nothing + 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) + { return; } // If the test hook is set, write to it and continue. - if (s_h != null) s_h.Write(value); + if (s_h != null) + { + if (newLine) + { + s_h.WriteLine(value); + } + else + { + s_h.Write(value); + } + } TextWriter writer = Console.IsOutputRedirected ? Console.Out : _parent.ConsoleTextWriter; + value = GetOutputString(value, SupportsVirtualTerminal); if (_parent.IsRunningAsync) { Dbg.Assert(writer == _parent.OutputSerializer.textWriter, "writers should be the same"); _parent.OutputSerializer.Serialize(value); + + if (newLine) + { + _parent.OutputSerializer.Serialize(Environment.NewLine); + } } else { - writer.Write(value); + if (newLine) + { + writer.WriteLine(value); + } + else + { + writer.Write(value); + } } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute @@ -748,13 +782,39 @@ public override void Write(string value) /// Win32's SetConsoleMode fails /// OR /// Win32's WriteConsole fails - /// /// - public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { - // Sync access so that we don't race on color settings if called from multiple threads. + Write(foregroundColor, backgroundColor, value, newLine: false); + } + + /// + /// See base class. + /// + /// + /// + /// + /// + /// If obtaining information about the buffer failed + /// OR + /// Win32's SetConsoleTextAttribute + /// OR + /// Win32's CreateFile fails + /// OR + /// Win32's GetConsoleMode fails + /// OR + /// Win32's SetConsoleMode fails + /// OR + /// Win32's WriteConsole fails + /// + public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) + { + Write(foregroundColor, backgroundColor, value, newLine: true); + } + private void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value, bool newLine) + { + // Sync access so that we don't conflict on color settings if called from multiple threads. lock (_instanceLock) { ConsoleColor fg = RawUI.ForegroundColor; @@ -765,7 +825,7 @@ public override void Write(ConsoleColor foregroundColor, ConsoleColor background try { - this.Write(value); + this.WriteImpl(value, newLine); } finally { @@ -775,16 +835,11 @@ public override void Write(ConsoleColor foregroundColor, ConsoleColor background } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// Win32's CreateFile fails /// OR /// Win32's GetConsoleMode fails @@ -792,26 +847,38 @@ public override void Write(ConsoleColor foregroundColor, ConsoleColor background /// Win32's SetConsoleMode fails /// OR /// Win32's WriteConsole fails - /// /// - public override void WriteLine(string value) { - // lock here so that the newline is written atomically with the value + lock (_instanceLock) + { + this.WriteImpl(value, newLine: true); + } + } + /// + /// See base class. + /// + /// + /// Win32's CreateFile fails + /// OR + /// Win32's GetConsoleMode fails + /// OR + /// Win32's SetConsoleMode fails + /// OR + /// Win32's WriteConsole fails + /// + public override void WriteLine() + { lock (_instanceLock) { - this.Write(value); - this.Write(Crlf); + this.WriteImpl(Environment.NewLine, newLine: false); } } #region Word Wrapping - - /// - /// /// This is a poor-man's word-wrapping routine. It breaks a single string into segments small enough to fit within a /// given number of cells. A break is determined by the last occurrence of whitespace that allows all prior characters /// on a line to be written within a given number of cells. If there is no whitespace found within that span, then the @@ -820,27 +887,19 @@ public override void WriteLine(string value) /// The problem is complicated by the fact that a single character may consume more than one cell. Conceptually, this /// is the same case as placing an upper bound on the length of a line while also having a strlen function that /// arbitrarily considers the length of any single character to be 1 or greater. - /// /// /// - /// /// Text to be emitted. /// Each tab character in the text is replaced with a space in the results. - /// /// /// - /// /// Max width, in buffer cells, of a single line. Note that a single character may consume more than one cell. The /// number of cells consumed is determined by calling ConsoleHostRawUserInterface.LengthInBufferCells. - /// /// /// - /// /// 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(); @@ -868,6 +927,7 @@ internal List WrapText(string text, int maxWidthInBufferCells) Dbg.Assert(RawUI.LengthInBufferCells(l) <= maxWidthInBufferCells, "line is too long"); result.Add(l); } + break; } @@ -913,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"); @@ -928,15 +988,9 @@ internal List WrapText(string text, int maxWidthInBufferCells) return result; } - - /// - /// - /// Struct used by WrapText - /// + /// Struct used by WrapText. /// - - [Flags] internal enum WordFlags { @@ -951,44 +1005,31 @@ internal struct Word internal WordFlags Flags; } - - /// - /// /// Chops text into "words," where a word is defined to be a sequence of whitespace characters, or a sequence of /// non-whitespace characters, each sequence being no longer than a given maximum. Therefore, in the text "this is a /// string" there are 7 words: 4 sequences of non-whitespace characters and 3 sequences of whitespace characters. /// /// Whitespace is considered to be spaces or tabs. Each tab character is replaced with a single space. - /// /// /// - /// /// The text to be chopped up. - /// /// /// - /// /// The maximum number of buffer cells that each word may consume. - /// /// /// - /// /// A list of words, in the same order they appear in the source text. - /// /// /// - /// /// 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(); - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) { return result; } @@ -1042,6 +1083,7 @@ internal List ChopTextIntoWords(string text, int maxWidthInBufferCells) AddWord(text, startIndex, wordEnd, maxWidthInBufferCells, inWs, ref result); startIndex = wordEnd; } + inWs = true; } else @@ -1053,6 +1095,7 @@ internal List ChopTextIntoWords(string text, int maxWidthInBufferCells) AddWord(text, startIndex, wordEnd, maxWidthInBufferCells, inWs, ref result); startIndex = wordEnd; } + inWs = false; } @@ -1063,48 +1106,32 @@ internal List ChopTextIntoWords(string text, int maxWidthInBufferCells) { AddWord(text, startIndex, text.Length, maxWidthInBufferCells, inWs, ref result); } + return result; } - - /// - /// /// Helper for ChopTextIntoWords. Takes a span of characters in a string and adds it to the word list, further /// subdividing the span as needed so that each subdivision fits within the limit. - /// /// /// - /// /// The string of characters in which the span is to be extracted. - /// /// /// - /// /// index into text of the start of the word to be added. - /// /// /// - /// /// index of the char after the last char to be included in the word. - /// /// /// - /// /// The maximum number of buffer cells that each word may consume. - /// /// /// - /// /// true if the span is whitespace, false if not. - /// /// /// - /// /// 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) { @@ -1123,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); @@ -1139,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); @@ -1162,23 +1189,20 @@ internal string WrapToCurrentWindowWidth(string text) sb.Append(s); if (++count != lines.Count) { - sb.Append(Crlf); + sb.Append(Environment.NewLine); } } return sb.ToString(); } -#endregion Word Wrapping + #endregion Word Wrapping /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute @@ -1190,38 +1214,37 @@ internal string WrapToCurrentWindowWidth(string text) /// Win32's SetConsoleMode fails /// OR /// Win32's WriteConsole fails - /// /// 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.) + // We should write debug to error stream only if debug is redirected.) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { _parent.ErrorSerializer.Serialize(message, "debug"); } 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)); + } } } /// - /// - /// See base class - /// + /// See base class. /// /// public override void WriteInformation(InformationRecord record) { - //We should write information to error stream only if redirected.) + // We should write information to error stream only if redirected.) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { _parent.ErrorSerializer.Serialize(record, "information"); @@ -1233,13 +1256,10 @@ public override void WriteInformation(InformationRecord record) } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute @@ -1251,15 +1271,9 @@ public override void WriteInformation(InformationRecord record) /// Win32's SetConsoleMode fails /// 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) { @@ -1267,22 +1281,25 @@ 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)); + } } } - /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// If obtaining information about the buffer failed /// OR /// Win32's SetConsoleTextAttribute @@ -1294,15 +1311,9 @@ public override void WriteVerboseLine(string message) /// Win32's SetConsoleMode fails /// 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) { @@ -1310,60 +1321,53 @@ 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); } } } - - public override void WriteErrorLine(string value) { if (string.IsNullOrEmpty(value)) { - // do nothing - return; } @@ -1375,47 +1379,61 @@ public override void WriteErrorLine(string value) { Dbg.Assert(writer == _parent.ErrorSerializer.textWriter, "writers should be the same"); - _parent.ErrorSerializer.Serialize(value + Crlf); + _parent.ErrorSerializer.Serialize(value + Environment.NewLine); } else { if (writer == _parent.ConsoleTextWriter) - WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + { + if (SupportsVirtualTerminal) + { + WriteLine(value); + } + else + { + WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + } + } else - Console.Error.Write(value + Crlf); + { + value = GetOutputString(value, SupportsVirtualTerminal); + Console.Error.WriteLine(value); + } } } - // Error colors + public ConsoleColor FormatAccentColor { get; set; } = ConsoleColor.Green; + + 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.Yellow; - public ConsoleColor ProgressBackgroundColor { get; set; } = ConsoleColor.DarkCyan; + public ConsoleColor ProgressForegroundColor { get; set; } = ConsoleColor.Black; - #endregion Line-oriented interaction + public ConsoleColor ProgressBackgroundColor { get; set; } = ConsoleColor.Yellow; + #endregion Line-oriented interaction #region implementation - - // We use System.Environment.NewLine because we are platform-agnostic - internal static string Crlf = System.Environment.NewLine; + internal static readonly string Crlf = System.Environment.NewLine; + private const string Tab = "\x0009"; internal enum ReadLineResult @@ -1426,51 +1444,35 @@ internal enum ReadLineResult endedOnBreak = 3 } - private const int maxInputLineLength = 8192; + private const int MaxInputLineLength = 1024; /// - /// /// Reads a line of input from the console. Returns when the user hits enter, a break key, a break event occurs. In /// the case that stdin has been redirected, reads from the stdin stream instead of the console. - /// /// /// - /// /// true to end input when the user hits the tab or shift-tab keys, false to only end on the enter key (or a break /// event). Ignored if not reading from the console device. - /// /// /// - /// /// The initial contents of the input buffer. Nice if you want to have a default result. Ignored if not reading from the /// console device. - /// /// /// - /// /// Receives an enum value indicating how input was ended. - /// /// /// - /// /// TBD - /// /// /// - /// /// true to include the results in any transcription that might be happening. - /// /// - /// /// - /// /// The string read from either the console or the stdin stream. null if: /// - stdin was read and EOF was reached on the stream, or /// - the console was read, and input was terminated with Ctrl-C, Ctrl-Break, or Close. - /// /// /// - /// /// If Win32's SetConsoleMode fails /// OR /// Win32's ReadConsole fails @@ -1478,15 +1480,16 @@ internal enum ReadLineResult /// obtaining information about the buffer failed /// 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; @@ -1531,16 +1534,24 @@ 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(); } + break; } @@ -1573,15 +1584,15 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca ConsoleHandle handle = ConsoleControl.GetConioDeviceHandle(); ConsoleControl.ConsoleModes m = ConsoleControl.GetMode(handle); - const ConsoleControl.ConsoleModes desiredMode = + const ConsoleControl.ConsoleModes DesiredMode = ConsoleControl.ConsoleModes.LineInput | ConsoleControl.ConsoleModes.EchoInput | ConsoleControl.ConsoleModes.ProcessedInput; - if ((m & desiredMode) != desiredMode || (m & ConsoleControl.ConsoleModes.MouseInput) > 0) + if ((m & DesiredMode) != DesiredMode || (m & ConsoleControl.ConsoleModes.MouseInput) > 0) { m &= ~ConsoleControl.ConsoleModes.MouseInput; - m |= desiredMode; + m |= DesiredMode; ConsoleControl.SetMode(handle, m); } #endif @@ -1613,7 +1624,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca { ConsoleKeyInfo keyInfo; - string s = ""; + string s = string.Empty; int index = 0; int cursorLeft = Console.CursorLeft; int cursorCurrent = cursorLeft; @@ -1622,14 +1633,20 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca #else _rawui.ClearKeyCache(); uint keyState = 0; - string s = ""; + string s = string.Empty; + Span inputBuffer = stackalloc char[MaxInputLineLength + 1]; + if (initialContent.Length > 0) + { + initialContent.AsSpan().CopyTo(inputBuffer); + } + #endif - do - { + while (true) + { #if UNIX keyInfo = Console.ReadKey(true); #else - s += ConsoleControl.ReadConsole(handle, initialContent, maxInputLineLength, endOnTab, out keyState); + s += ConsoleControl.ReadConsole(handle, initialContent.Length, inputBuffer, MaxInputLineLength, endOnTab, out keyState); Dbg.Assert(s != null, "s should never be null"); #endif @@ -1639,34 +1656,35 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca #else if (s.Length == 0) #endif - { - result = ReadLineResult.endedOnBreak; - s = null; + { + result = ReadLineResult.endedOnBreak; + s = null; - if (calledFromPipeline) - { - // make sure that the pipeline that called us is stopped + if (calledFromPipeline) + { + // make sure that the pipeline that called us is stopped - throw new PipelineStoppedException(); - } - break; + throw new PipelineStoppedException(); } + break; + } + #if UNIX if (keyInfo.Key == ConsoleKey.Enter) #else - if (s.EndsWith(Crlf, StringComparison.CurrentCulture)) + if (s.EndsWith(Environment.NewLine, StringComparison.Ordinal)) #endif - { - result = ReadLineResult.endedOnEnter; + { + result = ReadLineResult.endedOnEnter; #if UNIX // We're intercepting characters, so we need to echo the newline Console.Out.WriteLine(); #else - s = s.Remove(s.Length - Crlf.Length); + s = s.Remove(s.Length - Environment.NewLine.Length); #endif - break; - } + break; + } #if UNIX if (keyInfo.Key == ConsoleKey.Tab) @@ -1675,7 +1693,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca continue; } #else - int i = s.IndexOf(Tab, StringComparison.CurrentCulture); + int i = s.IndexOf(Tab, StringComparison.Ordinal); if (endOnTab && i != -1) { @@ -1738,6 +1756,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.Out.Write(s.PadRight(length)); Console.CursorLeft = cursorCurrent - 1; } + continue; } @@ -1753,6 +1772,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.Out.Write(s.PadRight(length)); Console.CursorLeft = cursorCurrent; } + continue; } @@ -1764,6 +1784,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.CursorLeft--; index--; } + continue; } @@ -1775,6 +1796,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.CursorLeft++; index++; } + continue; } @@ -1807,7 +1829,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca { Console.CursorLeft = cursorLeft; index = s.Length; - s = ""; + s = string.Empty; continue; } @@ -1818,17 +1840,24 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca continue; } - if (Char.IsControl(keyInfo.KeyChar)) + if (char.IsControl(keyInfo.KeyChar)) { - // blacklist control characters + // deny list control characters continue; } + // Handle case where terminal gets reset and the index is outside of the buffer + if (index > s.Length) + { + index = s.Length; + } + // Modify string - if (!insertMode) // then overwrite mode + if (!insertMode && index < s.Length) // then overwrite mode { s = s.Remove(index, 1); } + s = s.Insert(index, keyInfo.KeyChar.ToString()); index++; @@ -1838,15 +1867,14 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.Out.Write(s); Console.CursorLeft = cursorCurrent + 1; #endif - } - while (true); + } - Dbg.Assert( - (s == null && result == ReadLineResult.endedOnBreak) - || (s != null && result != ReadLineResult.endedOnBreak), - "s should only be null if input ended with a break"); + Dbg.Assert( + (s == null && result == ReadLineResult.endedOnBreak) + || (s != null && result != ReadLineResult.endedOnBreak), + "s should only be null if input ended with a break"); - return s; + return s; #if UNIX } finally @@ -1860,7 +1888,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca /// /// Get the character at the cursor when the user types 'tab' in the middle of line. /// - /// the cursor position where 'tab' is hit + /// The cursor position where 'tab' is hit. /// private char GetCharacterUnderCursor(Coordinates cursorPosition) { @@ -1886,54 +1914,51 @@ 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 to process. + /// The string with any '\0' characters removed. + private static string RemoveNulls(string input) { - if (input.IndexOf('\0') == -1) + 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(); } - /// - /// /// Reads a line, and completes the input for the user if they hit tab. - /// /// /// - /// /// The Executor instance on which to run any pipelines that are needed to find matches - /// /// - /// /// - /// /// null on a break event /// the completed line otherwise - /// /// internal string ReadLineWithTabCompletion(Executor exec) { string input = null; - string lastInput = ""; + string lastInput = string.Empty; ReadLineResult rlResult = ReadLineResult.endedOnEnter; #if !UNIX ConsoleHandle handle = ConsoleControl.GetActiveScreenBufferHandle(); - string lastCompletion = ""; + string lastCompletion = string.Empty; Size screenBufferSize = RawUI.BufferSize; // Save the cursor position at the end of the prompt string so that we can restore it later to write the @@ -1945,7 +1970,7 @@ internal string ReadLineWithTabCompletion(Executor exec) string completionInput = null; #endif - do + while (true) { if (TryInvokeUserDefinedReadLine(out input)) { @@ -1973,7 +1998,7 @@ internal string ReadLineWithTabCompletion(Executor exec) if (rlResult == ReadLineResult.endedOnTab || rlResult == ReadLineResult.endedOnShiftTab) { - int tabIndex = input.IndexOf(Tab, StringComparison.CurrentCulture); + int tabIndex = input.IndexOf(Tab, StringComparison.Ordinal); Dbg.Assert(tabIndex != -1, "tab should appear in the input"); string restOfLine = string.Empty; @@ -1987,6 +2012,7 @@ internal string ReadLineWithTabCompletion(Executor exec) input = input.Remove(input.Length - 1); restOfLine = input.Substring(tabIndex + 1); } + input = input.Remove(tabIndex); if (input != lastCompletion || commandCompletion == null) @@ -1998,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 { @@ -2011,9 +2036,9 @@ internal string ReadLineWithTabCompletion(Executor exec) completedInput += restOfLine; } - if (completedInput.Length > (maxInputLineLength - 2)) + if (completedInput.Length > (MaxInputLineLength - 2)) { - completedInput = completedInput.Substring(0, maxInputLineLength - 2); + completedInput = completedInput.Substring(0, MaxInputLineLength - 2); } // Remove any nulls from the string... @@ -2076,7 +2101,6 @@ internal string ReadLineWithTabCompletion(Executor exec) } #endif } - while (true); // Since we did not transcribe any call to ReadLine, transcribe the results here. @@ -2084,33 +2108,33 @@ internal string ReadLineWithTabCompletion(Executor exec) { // Reads always terminate with the enter key, so add that. - _parent.WriteToTranscript(input + Crlf); + _parent.WriteLineToTranscript(input); } return input; } #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; @@ -2161,6 +2185,7 @@ private CommandCompletion GetNewCompletionResults(string input) } private const string CustomReadlineCommand = "PSConsoleHostReadLine"; + private bool TryInvokeUserDefinedReadLine(out string input) { // We're using GetCommands instead of GetCommand so we don't auto-load a module should the command exist, but isn't loaded. @@ -2206,10 +2231,10 @@ 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. + // If this is true, class throws on read or prompt method which require + // access to console. internal bool ThrowOnReadAndPrompt { set @@ -2217,6 +2242,7 @@ internal bool ThrowOnReadAndPrompt _throwOnReadAndPrompt = value; } } + private bool _throwOnReadAndPrompt; internal void HandleThrowOnReadAndPrompt() @@ -2229,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 6c9350802cc..1c9d37ea9fb 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceProgress.cs @@ -1,27 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; -using Dbg = System.Management.Automation.Diagnostics; +using System.Management.Automation.Host; using System.Threading; +using Dbg = System.Management.Automation.Diagnostics; 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() @@ -51,22 +46,25 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt _progPane.Hide(); _progPane = null; } + _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\\"); + } } } - - /// - /// /// 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,7 +92,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt progPaneUpdateFlag = 1; // The timer will be auto restarted every 'UpdateTimerThreshold' ms - _progPaneUpdateTimer = new Timer( new TimerCallback(ProgressPaneUpdateTimerElapsed), null, UpdateTimerThreshold, UpdateTimerThreshold); + _progPaneUpdateTimer = new Timer(new TimerCallback(ProgressPaneUpdateTimerElapsed), null, UpdateTimerThreshold, UpdateTimerThreshold); } } @@ -102,18 +100,34 @@ 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); } } - - /// - /// /// TimerCallback for '_progPaneUpdateTimer' to update 'progPaneUpdateFlag' - /// /// - private void ProgressPaneUpdateTimerElapsed(object sender) @@ -125,29 +139,19 @@ 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 void - PostWrite(string value) + PostWrite(ReadOnlySpan value, bool newLine) { PostWrite(); @@ -155,7 +159,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt { try { - _parent.WriteToTranscript(value); + _parent.WriteToTranscript(value, newLine); } catch (Exception) { @@ -164,32 +168,20 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt } } - - private void PreRead() { - if (_progPane != null) - { - _progPane.Hide(); - } + _progPane?.Hide(); } - - private void PostRead() { - if (_progPane != null) - { - _progPane.Show(); - } + _progPane?.Show(); } - - private void PostRead(string value) @@ -201,7 +193,7 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt try { // Reads always terminate with the enter key, so add that. - _parent.WriteToTranscript(value + Crlf); + _parent.WriteLineToTranscript(value); } catch (Exception) { @@ -210,16 +202,13 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt } } - - private ProgressPane _progPane = null; private PendingProgress _pendingProgress = null; // The timer set up 'progPaneUpdateFlag' every 'UpdateTimerThreshold' milliseconds to update 'ProgressPane' private Timer _progPaneUpdateTimer = null; + private const int UpdateTimerThreshold = 200; + 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 660cd741436..5895dcf2b83 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs @@ -1,24 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Globalization; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; +using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Internal; using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Security; +using System.Text; + using Dbg = System.Management.Automation.Diagnostics; using InternalHostUserInterface = System.Management.Automation.Internal.Host.InternalHostUserInterface; - - namespace Microsoft.PowerShell { internal partial @@ -26,26 +22,24 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt { /// /// Used by Prompt to indicate any common errors when converting the user input string to - /// the type of the parameter + /// the type of the parameter. /// private enum PromptCommonInputErrors { /// - /// No error or not an error prompt handles + /// No error or not an error prompt handles. /// None, /// - /// Format error + /// Format error. /// Format, /// - /// Overflow error + /// Overflow error. /// Overflow } - - private static bool AtLeastOneHelpMessageIsPresent(Collection descriptions) @@ -54,7 +48,7 @@ private static { if (fd != null) { - if (!String.IsNullOrEmpty(fd.HelpMessage)) + if (!string.IsNullOrEmpty(fd.HelpMessage)) { return true; } @@ -64,48 +58,35 @@ private static return false; } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// /// /// - /// /// If is null /// OR /// at least one FieldDescription in is null - /// /// /// - /// /// If count is less than 1 /// OR /// at least one FieldDescription.AssemblyFullName in is /// null or empty - /// /// /// - /// /// If a FieldDescription in specifies one of SecureString or /// PSCredential and the type can not be loaded. /// OR /// at least one FieldDescription in specifies an array /// whose rank is less than 1. - /// /// /// - /// /// 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) @@ -115,12 +96,12 @@ public override if (descriptions == null) { - throw PSTraceSource.NewArgumentNullException("descriptions"); + throw PSTraceSource.NewArgumentNullException(nameof(descriptions)); } if (descriptions.Count < 1) { - throw PSTraceSource.NewArgumentException("descriptions", + throw PSTraceSource.NewArgumentException(nameof(descriptions), ConsoleHostUserInterfaceStrings.PromptEmptyDescriptionsErrorTemplate, "descriptions"); } @@ -130,17 +111,17 @@ public override { Dictionary results = new Dictionary(); - Boolean cancelInput = false; + bool cancelInput = false; - if (!String.IsNullOrEmpty(caption)) + if (!string.IsNullOrEmpty(caption)) { // Should be a skin lookup WriteLineToConsole(); - WriteToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); - WriteLineToConsole(); + WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); } - if (!String.IsNullOrEmpty(message)) + + if (!string.IsNullOrEmpty(message)) { WriteLineToConsole(WrapToCurrentWindowWidth(message)); } @@ -157,22 +138,21 @@ public override descIndex++; if (desc == null) { - throw PSTraceSource.NewArgumentException("descriptions", + 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); } @@ -199,7 +179,7 @@ public override // assigned to an array // if the field is an array, the element type can be found; else, use Object - Type elementType = typeof(Object); + Type elementType = typeof(object); if (fieldType.IsArray) { elementType = fieldType.GetElementType(); @@ -210,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; } @@ -219,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)); - Boolean inputListEnd = 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) + fieldPromptList.Append(CultureInfo.InvariantCulture, $"{inputList.Count}]: "); + bool endListInput = false; + object convertedObj = null; + _ = PromptForSingleItem( + elementType, + fieldPromptList.ToString(), + fieldPrompt, + caption, + message, + desc, + fieldEchoOnPrompt: true, + listInput: true, + out endListInput, + out cancelInput, + out convertedObj); + + if (cancelInput || endListInput) { break; } @@ -260,16 +248,27 @@ public override string printFieldPrompt = StringUtil.Format(ConsoleHostUserInterfaceStrings.PromptFieldPromptInputSeparatorTemplate, fieldPrompt); // field is not a list - Object convertedObj = null; - Boolean dummy = false; + object convertedObj = null; + + _ = PromptForSingleItem( + fieldType, + printFieldPrompt, + fieldPrompt, + caption, + message, + desc, + fieldEchoOnPrompt: true, + listInput: false, + endListInput: out _, + out cancelInput, + out convertedObj); - PromptForSingleItem(fieldType, printFieldPrompt, fieldPrompt, caption, message, desc, - fieldEchoOnPrompt, false, out dummy, out cancelInput, out convertedObj); if (!cancelInput) { inputPSObject = PSObject.AsPSObject(convertedObj); } } + if (cancelInput) { s_tracer.WriteLine("Prompt canceled"); @@ -277,8 +276,10 @@ public override results.Clear(); break; } + results.Add(desc.Name, PSObject.AsPSObject(inputPSObject)); } + return results; } } @@ -348,13 +349,13 @@ out object convertedObj /// /// Called by Prompt. Reads user input and processes tilde commands. /// - /// prompt written to host for the field - /// the field to be read - /// 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 - /// processed input string to be converted with LanguagePrimitives.ConvertTo + /// Prompt written to host for the field. + /// The field to be read. + /// 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 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) { @@ -381,6 +382,7 @@ private string PromptReadInput(string fieldPrompt, FieldDescription desc, bool f System.Management.Automation.Diagnostics.Assert(userInputString != null, "ReadLineSafe did not return a string"); rawInputString = userInputString; } + if (rawInputString == null) { // processedInputString is null as well. No need to assign null to it. @@ -388,7 +390,7 @@ private string PromptReadInput(string fieldPrompt, FieldDescription desc, bool f break; } else - if (rawInputString.StartsWith(PromptCommandPrefix, StringComparison.CurrentCulture)) + if (!string.IsNullOrEmpty(desc.Label) && rawInputString.StartsWith(PromptCommandPrefix, StringComparison.Ordinal)) { processedInputString = PromptCommandMode(rawInputString, desc, out inputDone); } @@ -398,10 +400,12 @@ private string PromptReadInput(string fieldPrompt, FieldDescription desc, bool f { endListInput = true; } + processedInputString = rawInputString; break; } } + return processedInputString; } @@ -409,12 +413,12 @@ private string PromptReadInput(string fieldPrompt, FieldDescription desc, bool f /// Uses LanguagePrimitives.ConvertTo to parse inputString for fieldType. Handles two most common parse /// exceptions: OverflowException and FormatException. /// - /// the type that inputString is to be interpreted - /// is the call coming from a remote host - /// the string to be converted + /// The type that inputString is to be interpreted. + /// Is the call coming from a remote host. + /// The string to be converted. /// if there's no error in the conversion, the converted object will be assigned here; /// otherwise, this will be the same as the inputString - /// an object of type fieldType that inputString represents + /// An object of type fieldType that inputString represents. private PromptCommonInputErrors PromptTryConvertTo(Type fieldType, bool isFromRemoteHost, string inputString, out object convertedObj) { Dbg.Assert(fieldType != null, "fieldType should never be null when PromptTryConvertTo is called"); @@ -456,6 +460,7 @@ private PromptCommonInputErrors PromptTryConvertTo(Type fieldType, bool isFromRe WrapToCurrentWindowWidth( string.Format(CultureInfo.CurrentCulture, errMsgTemplate, fieldType, inputString))); } + return PromptCommonInputErrors.Format; } else @@ -466,6 +471,7 @@ private PromptCommonInputErrors PromptTryConvertTo(Type fieldType, bool isFromRe { } } + return PromptCommonInputErrors.None; } @@ -478,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); @@ -497,6 +502,7 @@ private string PromptCommandMode(string input, FieldDescription desc, out bool i { return command; } + if (command.Length == 1) { if (command[0] == '?') @@ -517,17 +523,20 @@ private string PromptCommandMode(string input, FieldDescription desc, out bool i { ReportUnrecognizedPromptCommand(input); } + inputDone = false; return null; } + if (command.Length == 2) { - if (0 == string.Compare(command, "\"\"", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(command, "\"\"", StringComparison.OrdinalIgnoreCase)) { return string.Empty; } } - if (0 == string.Compare(command, "$null", StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(command, "$null", StringComparison.OrdinalIgnoreCase)) { return null; } @@ -549,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 369a9385443..7e3dcfd70e0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs @@ -1,30 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Globalization; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; +using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Internal; 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 { internal partial class ConsoleHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleChoiceSelection { /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -32,45 +24,36 @@ internal partial class ConsoleHostUserInterface : PSHostUserInterface, IHostUISu /// /// /// - /// /// If is null. - /// /// /// - /// /// If .Count is 0. - /// /// /// - /// /// If is greater than /// the length of . - /// /// /// - /// /// when prompt is canceled by, for example, Ctrl-c. - /// /// - public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) { HandleThrowOnReadAndPrompt(); if (choices == null) { - throw PSTraceSource.NewArgumentNullException("choices"); + throw PSTraceSource.NewArgumentNullException(nameof(choices)); } if (choices.Count == 0) { - throw PSTraceSource.NewArgumentException("choices", + throw PSTraceSource.NewArgumentException(nameof(choices), ConsoleHostUserInterfaceStrings.EmptyChoicesErrorTemplate, "choices"); } if ((defaultChoice < -1) || (defaultChoice >= choices.Count)) { - throw PSTraceSource.NewArgumentOutOfRangeException("defaultChoice", defaultChoice, + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(defaultChoice), defaultChoice, ConsoleHostUserInterfaceStrings.InvalidDefaultChoiceErrorTemplate, "defaultChoice", "choice"); } @@ -83,8 +66,7 @@ public override int PromptForChoice(string caption, string message, Collection PromptForChoice(string caption, if (choices == null) { - throw PSTraceSource.NewArgumentNullException("choices"); + throw PSTraceSource.NewArgumentNullException(nameof(choices)); } if (choices.Count == 0) { - throw PSTraceSource.NewArgumentException("choices", + throw PSTraceSource.NewArgumentException(nameof(choices), ConsoleHostUserInterfaceStrings.EmptyChoicesErrorTemplate, "choices"); } Dictionary defaultChoiceKeys = new Dictionary(); - if (null != defaultChoices) + if (defaultChoices != null) { foreach (int defaultChoice in defaultChoices) { @@ -213,10 +194,7 @@ public Collection PromptForChoice(string caption, defaultChoice); } - if (!defaultChoiceKeys.ContainsKey(defaultChoice)) - { - defaultChoiceKeys.Add(defaultChoice, true); - } + defaultChoiceKeys.TryAdd(defaultChoice, true); } } @@ -229,8 +207,7 @@ public Collection PromptForChoice(string caption, { // Should be a skin lookup WriteLineToConsole(); - WriteToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); - WriteLineToConsole(); + WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); } // write message if (!string.IsNullOrEmpty(message)) @@ -249,14 +226,14 @@ public 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); WriteToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(choiceMsg)); ReadLineResult rlResult; - string response = ReadLine(false, "", out rlResult, true, true); + string response = ReadChoiceResponse(out rlResult); if (rlResult == ReadLineResult.endedOnBreak) { @@ -275,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) @@ -304,7 +281,6 @@ public Collection PromptForChoice(string caption, } // prompt for multiple choices } - while (true); return result; } @@ -321,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) { @@ -333,7 +309,7 @@ private void WriteChoicePrompt(string[,] hotkeysAndPlainLabels, } string choice = - String.Format( + string.Format( CultureInfo.InvariantCulture, choiceTemplate, hotkeysAndPlainLabels[0, i], @@ -356,10 +332,10 @@ private void WriteChoicePrompt(string[,] hotkeysAndPlainLabels, WriteLineToConsole(); } - string defaultPrompt = ""; + string defaultPrompt = string.Empty; if (defaultChoiceKeys.Count > 0) { - string prepend = ""; + string prepend = string.Empty; StringBuilder defaultChoicesBuilder = new StringBuilder(); foreach (int defaultChoice in defaultChoiceKeys.Keys) { @@ -369,10 +345,10 @@ 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 = ","; } + string defaultChoices = defaultChoicesBuilder.ToString(); if (defaultChoiceKeys.Count == 1) @@ -415,6 +391,19 @@ private void WriteChoiceHelper(string text, ConsoleColor fg, ConsoleColor bg, re WriteToConsole(fg, bg, trimEnd ? text.TrimEnd(null) : text); } + private string ReadChoiceResponse(out ReadLineResult result) + { + result = ReadLineResult.endedOnEnter; + return InternalTestHooks.ForcePromptForChoiceDefaultOption + ? string.Empty + : ReadLine( + endOnTab: false, + initialContent: string.Empty, + result: out result, + calledFromPipeline: true, + transcribeResult: true); + } + private void ShowChoiceHelp(Collection choices, string[,] hotkeysAndPlainLabels) { Dbg.Assert(choices != null, "choices: expected a value"); @@ -437,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}"))); } } @@ -476,4 +465,3 @@ private ConsoleColor DefaultPromptColor } } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index b945d24dc21..1e934553f18 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -1,20 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System; -using System.Security; using System.Management.Automation; using System.Management.Automation.Internal; -using Microsoft.Win32; -using System.Globalization; +using System.Security; namespace Microsoft.PowerShell { /// - /// - /// ConsoleHostUserInterface implements console-mode user interface for powershell - /// + /// ConsoleHostUserInterface implements console-mode user interface for powershell. /// internal partial class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface @@ -25,19 +19,12 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt /// In future, when we have Credential object from the security team, /// this function will be modified to prompt using secure-path /// if so configured. - /// /// - /// name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. - /// - /// name of the target for which creds are being collected - /// - /// message to be displayed. - /// - /// caption for the message. - /// - /// PSCredential object - /// - + /// Name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. + /// Name of the target for which creds are being collected. + /// Message to be displayed. + /// Caption for the message. + /// PSCredential object. public override PSCredential PromptForCredential( string caption, string message, @@ -55,21 +42,13 @@ public override PSCredential PromptForCredential( /// /// Prompt for credentials. /// - /// name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. - /// - /// name of the target for which creds are being collected - /// - /// message to be displayed. - /// - /// caption for the message. - /// - /// 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) - /// - + /// Name of the user whose creds are to be prompted for. If set to null or empty string, the function will prompt for user name first. + /// Name of the target for which creds are being collected. + /// Message to be displayed. + /// Caption for the message. + /// 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, @@ -88,8 +67,7 @@ public override PSCredential PromptForCredential( // Should be a skin lookup WriteLineToConsole(); - WriteToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); - WriteLineToConsole(); + WriteLineToConsole(PromptColor, RawUI.BackgroundColor, WrapToCurrentWindowWidth(caption)); } if (!string.IsNullOrEmpty(message)) @@ -119,16 +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(); + } + + if (!string.IsNullOrEmpty(targetName)) + { + userName = StringUtil.Format("{0}\\{1}", targetName, userName); } - WriteLineToConsole(); cred = new PSCredential(userName, password); @@ -136,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 89703712e50..ce5d5ecaef4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs @@ -1,6 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System.Management.Automation; using System.Management.Automation.Runspaces; @@ -8,27 +9,73 @@ 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 + public static class ConsoleShell { /// Entry point in to ConsoleShell. This method is called by main of minishell. - /// Banner text to be displayed by ConsoleHost + /// Banner text to be displayed by ConsoleHost. /// 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 StartImpl( + initialSessionState: InitialSessionState.CreateDefault2(), + bannerText, + helpText, + args, + issProvided: false); + } + + /// Entry point in 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. + /// An integer value which should be used as exit code for the process. + public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string[] args) + { + return StartImpl( + initialSessionState, + bannerText, + helpText, + args, + issProvided: true); + } + + /// + /// Implementation of entry point to ConsoleShell. + /// Used to create a custom Powershell console application. + /// + /// InitialSessionState to be used by the ConsoleHost. + /// Banner text to be displayed by ConsoleHost. + /// Help text for the shell. + /// Commandline parameters specified by user. + /// True when the InitialSessionState object is provided by caller. + /// An integer value which should be used as exit code for the process. + private static int StartImpl( + InitialSessionState initialSessionState, + string? bannerText, + string? helpText, + string[] args, + bool issProvided) + { + if (initialSessionState == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); + } + if (args == null) { - throw PSTraceSource.NewArgumentNullException("args"); + throw PSTraceSource.NewArgumentNullException(nameof(args)); } - return ConsoleHost.Start(bannerText, helpText, args); + ConsoleHost.ParseCommandLine(args); + ConsoleHost.DefaultInitialSessionState = initialSessionState; + + 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 7e14ca9d8cd..a82156ce1c8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs @@ -1,19 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; using System.IO; +using System.Text; using Dbg = System.Management.Automation.Diagnostics; -using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; -using HRESULT = System.UInt32; -using DWORD = System.UInt32; -using NakedWin32Handle = System.IntPtr; - - namespace Microsoft.PowerShell { @@ -22,16 +13,13 @@ 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"); _ui = ui; } - - public override Encoding Encoding @@ -42,55 +30,63 @@ public override } } - - public override void Write(string value) { - _ui.WriteToConsole(value, true); + _ui.WriteToConsole(value, transcribeResult: true); } - + public override + void + Write(ReadOnlySpan value) + { + _ui.WriteToConsole(value, transcribeResult: true); + } public override void WriteLine(string value) { - this.Write(value + ConsoleHostUserInterface.Crlf); + _ui.WriteLineToConsole(value, transcribeResult: true); } - - public override void - Write(Boolean b) + WriteLine(ReadOnlySpan value) { - this.Write(b.ToString()); + _ui.WriteLineToConsole(value, transcribeResult: true); } - - public override void - Write(Char c) + Write(bool b) { - this.Write(new String(c, 1)); + if (b) + { + _ui.WriteToConsole(bool.TrueString, transcribeResult: true); + } + else + { + _ui.WriteToConsole(bool.FalseString, transcribeResult: true); + } } - - public override void - Write(Char[] a) + Write(char c) { - this.Write(new String(a)); + ReadOnlySpan value = [c]; + _ui.WriteToConsole(value, transcribeResult: true); } + public override + void + Write(char[] a) + { + _ui.WriteToConsole(a, transcribeResult: true); + } - - private ConsoleHostUserInterface _ui; + private readonly ConsoleHostUserInterface _ui; } -} // namespace - - +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index b3ba10f1ec3..354c61fb8f3 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -1,31 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Runspaces; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; 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] @@ -38,26 +34,18 @@ internal enum ExecutionOptions } /// - /// - /// Constructs a new instance - /// + /// Constructs a new instance. /// /// - /// /// A reference to the parent ConsoleHost that created this instance. - /// /// /// - /// /// true if the executor is supposed to use nested pipelines; false if not. - /// /// /// - /// /// 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) { @@ -79,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. @@ -91,10 +79,7 @@ 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 @@ -102,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. @@ -114,35 +99,31 @@ 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) { - p.StateChanged += new EventHandler(PipelineStateChangedHandler); + p.StateChanged += PipelineStateChangedHandler; } internal void Wait() @@ -161,19 +142,31 @@ 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) { Dbg.Assert(!useNestedPipelines, "can't async invoke a nested pipeline"); - Dbg.Assert(!String.IsNullOrEmpty(command), "command should have a value"); + Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); bool addToHistory = (options & ExecutionOptions.AddToHistory) > 0; Pipeline tempPipeline = _parent.RunspaceRef.CreatePipeline(command, addToHistory, false); ExecuteCommandAsyncHelper(tempPipeline, out exceptionThrown, options); } + /// + /// Executes a pipeline in the console when we are running asnyc. + /// + /// + /// The pipeline to execute. + /// + /// + /// Any exception thrown trying to run the pipeline. + /// + /// + /// The options to use to execute the pipeline. + /// internal void ExecuteCommandAsyncHelper(Pipeline tempPipeline, out Exception exceptionThrown, ExecutionOptions options) { Dbg.Assert(!_isPromptFunctionExecutor, "should not async invoke the prompt"); @@ -204,9 +197,16 @@ internal void ExecuteCommandAsyncHelper(Pipeline tempPipeline, out Exception exc tempPipeline.Commands.Add(outDefault); } - tempPipeline.Output.DataReady += new EventHandler(OutputObjectStreamHandler); - tempPipeline.Error.DataReady += new EventHandler(ErrorObjectStreamHandler); - PipelineFinishedWaitHandle waiterThereIsAFlyInMySoup = new PipelineFinishedWaitHandle(tempPipeline); + tempPipeline.Output.DataReady += OutputObjectStreamHandler; + tempPipeline.Error.DataReady += ErrorObjectStreamHandler; + PipelineFinishedWaitHandle pipelineWaiter = new PipelineFinishedWaitHandle(tempPipeline); + + // close the input pipeline so the command will do something + // if we are not reading input + if ((options & Executor.ExecutionOptions.ReadInputObjects) == 0) + { + tempPipeline.Input.Close(); + } tempPipeline.InvokeAsync(); if ((options & ExecutionOptions.ReadInputObjects) > 0 && Console.IsInputRedirected) @@ -227,30 +227,31 @@ 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 - //hence the Input pipe. + // 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; } - }; + } des.End(); } + tempPipeline.Input.Close(); - waiterThereIsAFlyInMySoup.Wait(); + pipelineWaiter.Wait(); - //report error if pipeline failed + // report error if pipeline failed if (tempPipeline.PipelineStateInfo.State == PipelineState.Failed && tempPipeline.PipelineStateInfo.Reason != null) { if (_parent.OutputFormat == Serialization.DataFormat.Text) { - //Report the exception using normal error reporting + // Report the exception using normal error reporting exceptionThrown = tempPipeline.PipelineStateInfo.Reason; } else { - //serialize the error record + // serialize the error record AsyncPipelineFailureHandler(tempPipeline.PipelineStateInfo.Reason); } } @@ -285,52 +286,42 @@ internal Pipeline CreatePipeline() internal Pipeline CreatePipeline(string command, bool addToHistory) { - Dbg.Assert(!String.IsNullOrEmpty(command), "command should have a value"); + Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); return _parent.RunspaceRef.CreatePipeline(command, addToHistory, useNestedPipelines); } /// - /// /// 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"); + Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); 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, @@ -425,7 +416,6 @@ internal Collection ExecuteCommandHelper(Pipeline tempPipeline, out Ex return results; } - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Needed by ProfileTests as mentioned in bug 140572")] internal Collection ExecuteCommand(string command) { @@ -439,6 +429,7 @@ internal Collection ExecuteCommand(string command) { break; } + if (result == null) { break; @@ -449,27 +440,19 @@ 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. - /// /// /// - /// /// The string representation of the first result object returned, or null if an exception was thrown or no objects were /// returned by the command. - /// /// internal string ExecuteCommandAndGetResultAsString(string command, out Exception exceptionThrown) { @@ -493,13 +476,12 @@ internal string ExecuteCommandAndGetResultAsString(string command, out Exception // we got back one or more objects. Pick off the first result. if (streamResults[0] == null) - return String.Empty; + return string.Empty; // 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(); @@ -510,62 +492,45 @@ 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 Nullable ExecuteCommandAndGetResultAsBool(string command) + internal bool? ExecuteCommandAndGetResultAsBool(string command) { - Exception unused = null; - - Nullable 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. - /// /// /// - /// /// 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 Nullable ExecuteCommandAndGetResultAsBool(string command, out Exception exceptionThrown) + internal bool? ExecuteCommandAndGetResultAsBool(string command, out Exception exceptionThrown) { exceptionThrown = null; - Dbg.Assert(!String.IsNullOrEmpty(command), "command should have a value"); + Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value"); - Nullable result = null; + bool? result = null; do { @@ -591,10 +556,8 @@ internal Nullable ExecuteCommandAndGetResultAsBool(string command, out Exc } /// - /// - /// 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() { @@ -618,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(); @@ -632,17 +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() { @@ -654,18 +612,13 @@ private void Reset() } /// - /// /// Makes the given instance the "current" instance, that is, the instance that will receive a Cancel call if the break /// 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 /// /// null is current @@ -698,7 +651,6 @@ private void Reset() /// Summary: /// ExecuteCommand always saves/sets/restores CurrentExecutor /// Host.EnterNestedPrompt always saves/clears/restores CurrentExecutor - /// /// internal static Executor CurrentExecutor { @@ -713,6 +665,7 @@ internal static Executor CurrentExecutor return result; } + set { lock (s_staticStateLock) @@ -725,10 +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() { @@ -739,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 b82b277b7bc..acfdea07153 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs @@ -1,49 +1,70 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System; -using System.Reflection; +using System.ComponentModel; +using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Internal; +using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; -using System.Globalization; -using System.Threading; using System.Runtime.InteropServices; +using System.Threading; 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 /// -#pragma warning disable 1573 - public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string[] args, int argc) -#pragma warning restore 1573 + /// + /// Length of the passed in argument array. + /// + [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) { - System.Management.Automation.Runspaces.EarlyStartup.Init(); + return Start(args, argc); + } - // Set ETW activity Id - Guid activityId = EtwActivity.GetActivityId(); + /// + /// 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 (activityId == Guid.Empty) +#if DEBUG + if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0]!.Equals("-isswait", StringComparison.OrdinalIgnoreCase)) { - EtwActivity.SetActivityId(EtwActivity.CreateActivityId()); - } + Console.WriteLine("Attach the debugger to continue..."); + while (!System.Diagnostics.Debugger.IsAttached) + { + Thread.Sleep(100); + } - PSEtwLog.LogOperationalInformation(PSEventId.Perftrack_ConsoleStartupStart, PSOpcode.WinStart, - PSTask.PowershellConsoleStartup, PSKeyword.UseAlwaysOperational); + 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 @@ -55,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); - } - System.Diagnostics.Debugger.Break(); - } -#endif - ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); + 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(); int exitCode = 0; try { - var banner = ManagedEntranceStrings.ShellBannerNonWindowsPowerShell; - var formattedBanner = string.Format(CultureInfo.InvariantCulture, banner, PSVersionInfo.GitCommitId); - exitCode = Microsoft.PowerShell.ConsoleShell.Start( - formattedBanner, - ManagedEntranceStrings.UsageHelp, - args); + string banner = string.Format( + CultureInfo.InvariantCulture, + ManagedEntranceStrings.ShellBannerPowerShell, + PSVersionInfo.GitCommitId); + + ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); + + exitCode = ConsoleHost.Start( + bannerText: banner, + helpText: ManagedEntranceStrings.UsageHelp, + issProvidedExternally: false); } - catch (System.Management.Automation.Host.HostException e) + catch (HostException e) { - if (e.InnerException != null && e.InnerException.GetType() == typeof(System.ComponentModel.Win32Exception)) + if (e.InnerException is Win32Exception win32e) { - System.ComponentModel.Win32Exception win32e = e.InnerException as System.ComponentModel.Win32Exception; - // These exceptions are caused by killing conhost.exe // 1236, network connection aborted by local system // 0x6, invalid console handle @@ -91,14 +110,15 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray return exitCode; } } + System.Environment.FailFast(e.Message, e); } catch (Exception e) { System.Environment.FailFast(e.Message, e); } + return exitCode; } } } - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs index c2be475e02c..b16fb5ee147 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs @@ -1,8 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -12,11 +9,9 @@ using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell { /// - /// /// Represents all of the outstanding progress activities received by the host, and includes methods to update that state /// upon receipt of new ProgressRecords, and to render that state into an array of strings such that ProgressPane can /// display it. @@ -28,36 +23,27 @@ 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 { #region Updating Code /// - /// /// Update the data structures that represent the outstanding progress records reported so far. - /// /// /// - /// /// Identifier of the source of the event. This is used as part of the "key" for matching newly received records with /// records that have already been received. For a record to match (meaning that they refer to the same activity), both /// the source and activity identifiers need to match. - /// /// /// - /// /// 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"); @@ -84,6 +70,7 @@ class PendingProgress RemoveNodeAndPromoteChildren(listWhereFound, indexWhereFound); break; } + if (record.ParentActivityId == foundNode.ParentActivityId) { // record is an update to an existing activity. Copy the record data into the found node, and @@ -132,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; @@ -157,8 +141,6 @@ class PendingProgress AgeNodesAndResetStyle(); } - - private void EvictNode() @@ -183,23 +165,15 @@ class PendingProgress } } - /// - /// /// Removes a node from the tree. - /// /// /// - /// /// List in the tree from which the node is to be removed. - /// /// /// - /// /// Index into the list of the node to be removed. - /// /// - private void RemoveNode(ArrayList nodes, int indexToRemove) @@ -221,7 +195,6 @@ class PendingProgress #endif } - private void RemoveNodeAndPromoteChildren(ArrayList nodes, int indexToRemove) @@ -267,24 +240,15 @@ class PendingProgress } } - - /// - /// /// Adds a node to the tree, first removing the oldest node if the tree is too large. - /// /// /// - /// /// List in the tree where the node is to be added. - /// /// /// - /// /// Node to be added. - /// /// - private void AddNode(ArrayList nodes, ProgressNode nodeToAdd) @@ -298,10 +262,7 @@ class PendingProgress #endif } - - - private - class FindOldestNodeVisitor : NodeVisitor + private sealed class FindOldestNodeVisitor : NodeVisitor { internal override bool @@ -318,30 +279,25 @@ internal override return true; } - - internal ProgressNode FoundNode; - - internal ArrayList ListWhereFound; - - internal int IndexWhereFound = -1; - 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) @@ -367,7 +323,6 @@ internal override return v.FoundNode; } - private ProgressNode FindOldestLeafmostNode(out ArrayList listWhereFound, out int indexWhereFound) @@ -378,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) @@ -389,22 +344,17 @@ internal override // search the subtree for the oldest child treeToSearch = result.Children; - } while (true); + } return result; } - - /// - /// /// Convenience overload. - /// /// - private ProgressNode - FindNodeById(Int64 sourceId, int activityId) + FindNodeById(long sourceId, int activityId) { ArrayList listWhereFound = null; int indexWhereFound = -1; @@ -412,20 +362,15 @@ 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; } - - internal override bool Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound) @@ -437,70 +382,48 @@ internal override this.IndexWhereFound = indexWhereFound; return false; } + return true; } - - internal ProgressNode FoundNode; - - internal ArrayList ListWhereFound; - - internal int IndexWhereFound = -1; - - - private int _idToFind = -1; - private Int64 _sourceIdToFind; + private readonly int _idToFind = -1; + private readonly long _sourceIdToFind; } - - /// - /// /// Finds a node with a given ActivityId in provided set of nodes. Recursively walks the set of nodes and their children. - /// /// /// - /// /// Identifier of the source of the record. - /// /// /// - /// /// ActivityId to search for. - /// /// /// - /// /// Receives reference to the List where the found node was located, or null if no suitable node was found. - /// /// /// - /// /// Receives the index into listWhereFound that indicating where in the list the node was located, or -1 if /// no suitable node was found. - /// /// /// - /// /// 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; @@ -521,36 +444,21 @@ internal override return v.FoundNode; } - - /// - /// /// Finds the oldest node with a given rendering style that is at least as old as a given age. - /// /// /// - /// /// List of nodes to search. Child lists of each node in this list will also be searched. - /// /// /// - /// /// The minimum age of the node to be located. To find the oldest node, pass 0. - /// /// - /// /// The rendering style of the node to be located. - /// /// /// - /// /// 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) { @@ -593,32 +501,28 @@ 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; } } - - /// - /// /// Increments the age of each of the nodes in the given list, and all their children. Also sets the rendering /// style of each node to "full." /// /// All nodes are aged every time a new ProgressRecord is received. - /// /// - private void AgeNodesAndResetStyle() @@ -627,43 +531,28 @@ internal override NodeVisitor.VisitNodes(_topLevelNodes, arsv); } - - #endregion // Updating Code #region Rendering Code - - /// - /// /// Generates an array of strings representing as much of the outstanding progress activities as possible within the given /// space. As more outstanding activities are collected, nodes are "compressed" (i.e. rendered in an increasing terse /// fashion) in order to display as many as possible. Ultimately, some nodes may be compressed to the point of /// invisibility. The oldest nodes are compressed first. - /// /// /// - /// /// The maximum width (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The maximum height (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// /// - /// /// An array of strings containing the textual representation of the outstanding progress activities. - /// /// - internal string[] Render(int maxWidth, int maxHeight, PSHostRawUserInterface rawUI) @@ -682,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); @@ -715,42 +611,25 @@ internal override return (string[])result.ToArray(typeof(string)); } - - /// - /// /// Helper function for Render(). Recursively renders nodes. - /// /// /// - /// /// The rendered strings so far. Additional rendering will be appended. - /// /// /// - /// /// The nodes to be rendered. All child nodes will also be rendered. - /// /// /// - /// /// The current indentation level (in BufferCells). - /// /// /// - /// /// The maximum number of BufferCells that the rendering can consume, horizontally. - /// /// /// - /// /// 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"); @@ -777,10 +656,7 @@ internal override } } - - - private - class HeightTallyer : NodeVisitor + private sealed class HeightTallyer : NodeVisitor { internal HeightTallyer(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) { @@ -804,40 +680,28 @@ 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; } /// - /// /// Tallies up the number of BufferCells vertically that will be required to show all the ProgressNodes in the given /// list, and all of their children. - /// /// /// - /// /// The maximum height (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// /// - /// /// The vertical height (in BufferCells) that will be required to show all of the nodes in the given list. - /// /// /// - /// - /// - /// /// - private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) { HeightTallyer ht = new HeightTallyer(rawUi, maxHeight, maxWidth); @@ -845,21 +709,15 @@ private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidt return ht.Tally; } - #if DEBUG || ASSERTIONS_TRACE /// - /// /// Debugging code. Verifies that all of the nodes in the given list have the given style. - /// /// /// /// /// - - private - bool - AllNodesHaveGivenStyle(ArrayList nodes, ProgressNode.RenderStyle style) + private static bool AllNodesHaveGivenStyle(ArrayList nodes, ProgressNode.RenderStyle style) { if (nodes == null) { @@ -875,6 +733,7 @@ private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidt { return false; } + if (node.Children != null) { if (!AllNodesHaveGivenStyle(node.Children, style)) @@ -887,14 +746,9 @@ private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidt return true; } - - /// - /// /// Debugging code. NodeVisitor that counts up the number of nodes in the tree. - /// /// - private class CountingNodeVisitor : NodeVisitor @@ -912,19 +766,12 @@ internal override Count; } - - /// - /// /// Debugging code. Counts the number of nodes in the tree of nodes. - /// /// /// - /// /// The number of nodes in the tree. - /// /// - private int CountNodes() @@ -936,52 +783,34 @@ internal override #endif - /// - /// /// Helper function to CompressToFit. Considers compressing nodes from one level to another. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// /// - /// /// The maximum height (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The maximum width (in BufferCells) that the rendering may consume. - /// /// /// - /// /// Receives the number of nodes that were compressed. If the result of the method is false, then this will be the total /// number of nodes being tracked (i.e. all of them will have been compressed). - /// /// /// - /// /// The rendering style (e.g. "compression level") that the nodes are expected to currently have. - /// /// /// - /// /// The new rendering style that a node will have when it is compressed. If the result of the method is false, then all /// nodes will have this rendering style. - /// /// /// - /// /// true to indicate that the nodes are compressed to the point that their rendering will fit within the constraint, or /// 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( @@ -994,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. @@ -1012,28 +839,13 @@ 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; } - - /// - /// /// "Compresses" the nodes representing the outstanding progress activities until their rendering will fit within a /// "given height, or until they are compressed to a given level. The oldest nodes are compressed first. /// @@ -1041,29 +853,20 @@ internal override /// tree and change their rendering style to a more compact style. As soon as the rendering of the nodes will fit within /// the maxHeight, we stop. The result is that the most recent nodes will be the least compressed, the idea being that /// the rendering should show the most recently updated activities with the most complete rendering for them possible. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// /// - /// /// The maximum height (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The maximum width (in BufferCells) that the rendering may consume. - /// /// /// - /// /// The number of nodes that were made invisible during the compression. /// - /// - + /// private int CompressToFit(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) @@ -1125,49 +928,31 @@ internal override return nodesCompressed; } - Dbg.Assert(false, "with all nodes invisible, we should never reach this point."); - return 0; } - - - #endregion // Rendering Code #region Utility Code - - private abstract class NodeVisitor { /// - /// /// Called for each node in the tree. - /// /// /// - /// /// The node being visited. - /// /// /// - /// /// The list in which the node resides. - /// /// /// - /// /// The index into listWhereFound of the node. - /// /// /// - /// /// true to continue visiting nodes, false if not. - /// /// - internal abstract bool Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound); @@ -1199,16 +984,11 @@ 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 159a1d5c78a..4f51820524c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs @@ -1,37 +1,30 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; +using System.Text; -using Dbg = System.Management.Automation.Diagnostics; +using Microsoft.PowerShell.Commands.Internal.Format; +using Dbg = System.Management.Automation.Diagnostics; 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,7 +35,7 @@ namespace Microsoft.PowerShell /// /// Allocate only one line for displaying the StatusDescription or the CurrentOperation, - /// truncate the rest if the StatusDescription or CurrentOperation doesn't fit in one line + /// truncate the rest if the StatusDescription or CurrentOperation doesn't fit in one line. /// Full = 3, @@ -50,20 +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"); @@ -72,39 +64,30 @@ namespace Microsoft.PowerShell this.PercentComplete = Math.Min(record.PercentComplete, 100); this.SecondsRemaining = record.SecondsRemaining; this.RecordType = record.RecordType; - this.Style = RenderStyle.FullPlus; - this.SourceId = sourceId; - } + this.Style = IsMinimalProgressRenderingEnabled() + ? RenderStyle.Ansi + : this.Style = RenderStyle.FullPlus; + this.SourceId = sourceId; + } /// - /// /// Renders a single progress node as strings of text according to that node's style. The text is appended to the /// supplied list of strings. - /// /// /// - /// /// List of strings to which the node's rendering will be appended. - /// /// /// - /// /// The indentation level (in BufferCells) at which the node should be rendered. - /// /// /// - /// /// The maximum number of BufferCells that the rendering is allowed to consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// - internal void Render(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -127,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; @@ -137,36 +123,23 @@ namespace Microsoft.PowerShell } /// - /// /// Renders a node in the "Full" style. - /// /// /// - /// /// List of strings to which the node's rendering will be appended. - /// /// /// - /// /// The indentation level (in BufferCells) at which the node should be rendered. - /// /// /// - /// /// The maximum number of BufferCells that the rendering is allowed to consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// /// - /// /// Indicate if the full StatusDescription and CurrentOperation should be displayed. - /// /// - private void RenderFull(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI, bool isFullPlus) @@ -233,7 +206,7 @@ namespace Microsoft.PowerShell // Fifth and Sixth lines: The current operation - if (!String.IsNullOrEmpty(CurrentOperation)) + if (!string.IsNullOrEmpty(CurrentOperation)) { strCollection.Add(" "); RenderFullDescription(this.CurrentOperation, indent, maxWidth, rawUI, strCollection, isFullPlus); @@ -262,31 +235,20 @@ private static void RenderFullDescription(string description, string indent, int } /// - /// /// Renders a node in the "Compact" style. - /// /// /// - /// /// List of strings to which the node's rendering will be appended. - /// /// /// - /// /// The indentation level (in BufferCells) at which the node should be rendered. - /// /// /// - /// /// The maximum number of BufferCells that the rendering is allowed to consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// - private void RenderCompact(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -305,13 +267,13 @@ private static void RenderFullDescription(string description, string indent, int // Second line: the status description with percentage and time remaining, if applicable. - string percent = ""; + string percent = string.Empty; if (PercentComplete >= 0) { percent = StringUtil.Format("{0}% ", PercentComplete); } - string secRemain = ""; + string secRemain = string.Empty; if (SecondsRemaining >= 0) { TimeSpan span = new TimeSpan(0, 0, SecondsRemaining); @@ -331,7 +293,7 @@ private static void RenderFullDescription(string description, string indent, int // Third line: The current operation - if (!String.IsNullOrEmpty(CurrentOperation)) + if (!string.IsNullOrEmpty(CurrentOperation)) { strCollection.Add( StringUtil.TruncateToBufferCellWidth( @@ -340,34 +302,21 @@ private static void RenderFullDescription(string description, string indent, int } } - - /// - /// /// Renders a node in the "Minimal" style. - /// /// /// - /// /// List of strings to which the node's rendering will be appended. - /// /// /// - /// /// The indentation level (in BufferCells) at which the node should be rendered. - /// /// /// - /// /// The maximum number of BufferCells that the rendering is allowed to consume. - /// /// /// - /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. - /// /// - private void RenderMinimal(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -376,13 +325,13 @@ private static void RenderFullDescription(string description, string indent, int // First line: Everything mushed into one line - string percent = ""; + string percent = string.Empty; if (PercentComplete >= 0) { percent = StringUtil.Format("{0}% ", PercentComplete); } - string secRemain = ""; + string secRemain = string.Empty; if (SecondsRemaining >= 0) { TimeSpan span = new TimeSpan(0, 0, SecondsRemaining); @@ -402,22 +351,116 @@ private static void RenderFullDescription(string description, string indent, int maxWidth)); } + internal static bool IsMinimalProgressRenderingEnabled() + { + return PSStyle.Instance.Progress.View == ProgressView.Minimal; + } + + /// + /// 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; - - /// - /// /// The "age" of the node. A node's age is incremented by PendingProgress.Update each time a new ProgressRecord is /// received by the host. A node's age is reset when a corresponding ProgressRecord is received. Thus, the age of /// a node reflects the number of ProgressRecord that have been received since the node was last updated. @@ -426,45 +469,29 @@ private static void RenderFullDescription(string description, string indent, int /// display has finite size, it may be possible to have many more outstanding progress activities than will fit in that /// 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; - - /// - /// /// The style in which this node should be rendered. - /// /// - internal RenderStyle Style = RenderStyle.FullPlus; - - /// - /// /// 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"); @@ -486,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; @@ -494,14 +524,10 @@ internal int LinesRequiredMethod(PSHostRawUserInterface rawUi, int maxWidth) return 0; } - /// - /// /// 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. @@ -531,11 +557,13 @@ private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int max { ++lines; } + if (SecondsRemaining >= 0) { ++lines; } - if (!String.IsNullOrEmpty(CurrentOperation)) + + if (!string.IsNullOrEmpty(CurrentOperation)) { if (isFullPlus) { @@ -553,14 +581,10 @@ private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int max return lines; } - /// - /// /// The number of vertical BufferCells that are required to render the node in the Compact style. - /// /// /// - private int LinesRequiredInCompactStyle @@ -575,7 +599,7 @@ private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int max // Start with 1 for the Activity, and 1 for the Status. int lines = 2; - if (!String.IsNullOrEmpty(CurrentOperation)) + if (!string.IsNullOrEmpty(CurrentOperation)) { ++lines; } @@ -585,5 +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 b1d20c83140..030a359c2d8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs @@ -1,59 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Management.Automation; using System.Management.Automation.Host; using Dbg = System.Management.Automation.Diagnostics; - 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 { /// - /// /// Constructs a new instance. - /// /// /// - /// /// An implementation of the PSHostRawUserInterface with which the pane will be shown and hidden. - /// /// - internal ProgressPane(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException("ui"); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; _rawui = ui.RawUI; } - - /// - /// /// Indicates whether the pane is visible on the screen buffer or not. - /// /// /// - /// /// true if the pane is visible, false if not. /// - /// - + /// internal bool IsShowing @@ -64,15 +48,10 @@ 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() @@ -92,102 +71,144 @@ 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); - for (int i = 0; i < rows; i++) - { - Console.Out.Write('\n'); - } - if (scrollRows > 0) - { - _location.Y -= scrollRows; - _savedCursor.Y -= scrollRows; - } + // if cursor is not on left edge already move down one line + if (_rawui.CursorPosition.X != 0) + { + _location.Y++; + _rawui.CursorPosition = _location; + } - //create cleared region to clear progress bar later - _savedRegion = tempProgressRegion; - for(int row = 0; row < rows; row++) - { - for(int col = 0; col < cols; col++) + // 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) { - _savedRegion[row, col].Character = ' '; + // 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; } - } - //put cursor back to where output should be - _rawui.CursorPosition = _location; -#else - _location = _rawui.WindowPosition; + // create cleared region to clear progress bar later + _savedRegion = tempProgressRegion; + if (PSStyle.Instance.Progress.View != ProgressView.Minimal) + { + 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; - // 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); + } } } - - /// - /// /// 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; } } - - /// - /// /// Updates the pane with the rendering of the supplied PendingProgress, and shows it. - /// /// /// - /// /// A PendingProgress instance that represents the outstanding activities that should be shown. - /// /// - internal void Show(PendingProgress pendingProgress) @@ -202,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. @@ -212,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) @@ -237,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; @@ -248,27 +280,70 @@ class ProgressPane { Hide(); } + Show(); } 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 9eefb0e3b97..5181ae63672 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs @@ -1,63 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.IO; using System.Management.Automation; using System.Xml; - using Dbg = System.Management.Automation.Diagnostics; - - 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 format -- i.e. stream text just as out-default would display it. /// - Text = 0, /// - /// - /// XML-serialized format - /// + /// XML-serialized format. /// - XML = 1, /// - /// /// Indicates that the data should be discarded instead of processed. - /// /// None = 2 } - - protected Serialization(DataFormat dataFormat, string streamName) { @@ -67,23 +47,18 @@ internal enum DataFormat this.streamName = streamName; } - - protected static string XmlCliTag = "#< CLIXML"; protected string streamName; protected DataFormat format; } - - internal 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"); @@ -106,8 +81,6 @@ class WrappedSerializer : Serialization } } - - internal void Serialize(object o) @@ -129,6 +102,7 @@ class WrappedSerializer : Serialization _firstCall = false; textWriter.WriteLine(Serialization.XmlCliTag); } + _xmlSerializer.Serialize(o, streamName); break; case DataFormat.Text: @@ -138,7 +112,6 @@ class WrappedSerializer : Serialization } } - internal void End() @@ -162,22 +135,18 @@ class WrappedSerializer : Serialization } } - internal TextWriter textWriter; - private XmlWriter _xmlWriter; + private readonly XmlWriter _xmlWriter; private Serializer _xmlSerializer; private bool _firstCall = true; } - - internal 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"); @@ -187,7 +156,7 @@ class WrappedDeserializer : Serialization textReader = input; _firstLine = textReader.ReadLine(); - if (String.Compare(_firstLine, Serialization.XmlCliTag, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(_firstLine, Serialization.XmlCliTag, StringComparison.OrdinalIgnoreCase)) { // format should be XML @@ -197,7 +166,7 @@ class WrappedDeserializer : Serialization switch (format) { case DataFormat.XML: - _xmlReader = XmlReader.Create(textReader, new XmlReaderSettings { XmlResolver = null }); + _xmlReader = XmlReader.Create(textReader, new XmlReaderSettings { XmlResolver = null }); _xmlDeserializer = new Deserializer(_xmlReader); break; case DataFormat.Text: @@ -208,8 +177,6 @@ class WrappedDeserializer : Serialization } } - - internal object Deserialize() @@ -222,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: @@ -232,6 +198,7 @@ class WrappedDeserializer : Serialization { return null; } + if (_firstLine != null) { o = _firstLine; @@ -245,13 +212,13 @@ class WrappedDeserializer : Serialization _atEnd = true; } } + break; } + return o; } - - internal bool AtEnd @@ -275,12 +242,11 @@ class WrappedDeserializer : Serialization result = _atEnd; break; } + return result; } } - - internal void End() @@ -297,13 +263,10 @@ 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 6739e770736..18725b5ddb7 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs @@ -1,8 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; @@ -10,28 +7,21 @@ using System.Management.Automation; using System.Management.Automation.Internal; - namespace Microsoft.PowerShell.Commands { /// - /// - /// Implements the start-transcript cmdlet - /// + /// Implements the start-transcript cmdlet. /// - - [Cmdlet(VerbsLifecycle.Start, "Transcript", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113408")] - [OutputType(typeof(String))] + [Cmdlet(VerbsLifecycle.Start, "Transcript", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096485")] + [OutputType(typeof(string))] public sealed class StartTranscriptCommand : PSCmdlet { /// - /// /// The name of the file in which to write the transcript. If not provided, the file indicated by the variable /// $TRANSCRIPT is used. If neither the filename is supplied or $TRANSCRIPT is not set, the filename shall be $HOME/My - /// Documents/PowerShell_transcript.YYYYMMDDmmss.txt - /// + /// Documents/PowerShell_transcript.YYYYMMDDmmss.txt. /// /// - [Parameter(Position = 0, ParameterSetName = "ByPath")] [ValidateNotNullOrEmpty] public string Path @@ -40,6 +30,7 @@ public string Path { return _outFilename; } + set { _isFilenameSet = true; @@ -51,7 +42,7 @@ public string Path /// The literal name of the file in which to write the transcript. /// [Parameter(Position = 0, ParameterSetName = "ByLiteralPath")] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [ValidateNotNullOrEmpty] public string LiteralPath { @@ -59,6 +50,7 @@ public string LiteralPath { return _outFilename; } + set { _isFilenameSet = true; @@ -66,6 +58,7 @@ public string LiteralPath _isLiteralPath = true; } } + private bool _isLiteralPath = false; /// @@ -79,12 +72,9 @@ public string OutputDirectory } /// - /// /// Describes the current state of the activity. - /// /// /// - [Parameter] public SwitchParameter Append { @@ -92,6 +82,7 @@ public SwitchParameter Append { return _shouldAppend; } + set { _shouldAppend = value; @@ -105,25 +96,26 @@ public SwitchParameter Append /// /// The read-only attribute will not be replaced when the transcript is done. /// - - [Parameter()] + [Parameter] public SwitchParameter Force { get { return _force; } + set { _force = value; } } + private bool _force; /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -131,26 +123,35 @@ public SwitchParameter NoClobber { return _noclobber; } + set { _noclobber = value; } } + private bool _noclobber; /// /// Whether to include command invocation time headers between commands. /// - [Parameter()] + [Parameter] public SwitchParameter IncludeInvocationHeader { get; set; } + /// + /// Gets or sets whether to use minimal transcript header. + /// + [Parameter] + public SwitchParameter UseMinimalHeader + { + get; set; + } /// - /// - /// Starts the transcription + /// Starts the transcription. /// protected override void BeginProcessing() { @@ -176,7 +177,7 @@ protected override void BeginProcessing() } else { - _outFilename = (string)value; + _outFilename = (string)PSObject.Base(value); } } @@ -209,13 +210,13 @@ protected override void BeginProcessing() // Save some disk write time by checking whether file is readonly.. if (Force) { - //Make sure the file is not read only + // Make sure the file is not read only // Note that we will not clear the ReadOnly flag later fInfo.Attributes &= ~(FileAttributes.ReadOnly); } else { - string errorMessage = String.Format( + string errorMessage = string.Format( System.Globalization.CultureInfo.CurrentCulture, TranscriptStrings.TranscriptFileReadOnly, effectiveFilePath); @@ -224,16 +225,16 @@ protected override void BeginProcessing() } } - // If they didn't specify -Append, delete the file + // If they didn't specify -Append, empty the file if (!_shouldAppend) { - System.IO.File.Delete(effectiveFilePath); + System.IO.File.WriteAllText(effectiveFilePath, string.Empty); } } System.Management.Automation.Remoting.PSSenderInfo psSenderInfo = this.SessionState.PSVariable.GetValue("PSSenderInfo") as System.Management.Automation.Remoting.PSSenderInfo; - Host.UI.StartTranscribing(effectiveFilePath, psSenderInfo, IncludeInvocationHeader.ToBool()); + Host.UI.StartTranscribing(effectiveFilePath, psSenderInfo, IncludeInvocationHeader.ToBool(), UseMinimalHeader.IsPresent); // ch.StartTranscribing(effectiveFilePath, Append); @@ -255,7 +256,7 @@ protected override void BeginProcessing() { } - string errorMessage = String.Format( + string errorMessage = string.Format( System.Globalization.CultureInfo.CurrentCulture, TranscriptStrings.CannotStartTranscription, e.Message); @@ -301,6 +302,7 @@ private string ResolveFilePath(string filePath, bool isLiteralPath) { path = null; } + if (string.IsNullOrEmpty(path)) { CmdletProviderContext cmdletProviderContext = new CmdletProviderContext(this); @@ -315,6 +317,7 @@ private string ResolveFilePath(string filePath, bool isLiteralPath) ReportWrongProviderType(provider.FullName); } } + return path; } @@ -343,6 +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 8c058a5e787..093f7c147dc 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs @@ -1,35 +1,31 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; using System.Management.Automation.Internal; - namespace Microsoft.PowerShell.Commands { /// - /// - /// Implements the stop-transcript cmdlet - /// + /// Implements the stop-transcript cmdlet. /// - - [Cmdlet(VerbsLifecycle.Stop, "Transcript", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113415")] - [OutputType(typeof(String))] + [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(); @@ -49,6 +45,3 @@ protected override } } } - - - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Telemetry.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Telemetry.cs deleted file mode 100644 index ac035ba7305..00000000000 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Telemetry.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.DataContracts; -using Microsoft.ApplicationInsights.Extensibility; -using System.Management.Automation; -using System.Security.Cryptography; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.InteropServices; -using System.IO; - -namespace Microsoft.PowerShell -{ - /// - /// send up telemetry for startup - /// - internal static class ApplicationInsightsTelemetry - { - // The semaphore file which indicates whether telemetry should be sent - // This is temporary code waiting on the acceptance and implementation of the configuration spec - // The name of the file by when present in $PSHOME will enable telemetry. - // If this file is not present, no telemetry will be sent. - private const string TelemetrySemaphoreFilename = "DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY"; - private const string TelemetryOptoutEnvVar = "POWERSHELL_TELEMETRY_OPTOUT"; - - // The path to the semaphore file which enables telemetry - private static string TelemetrySemaphoreFilePath = Path.Combine( - Utils.DefaultPowerShellAppBase, - TelemetrySemaphoreFilename); - - // Telemetry client to be reused when we start sending more telemetry - private static TelemetryClient _telemetryClient = null; - - // Set this to true to reduce the latency of sending the telemetry - private static bool _developerMode = false; - - // PSCoreInsight2 telemetry key - private const string _psCoreTelemetryKey = "ee4b2115-d347-47b0-adb6-b19c2c763808"; - - static ApplicationInsightsTelemetry() - { - TelemetryConfiguration.Active.InstrumentationKey = _psCoreTelemetryKey; - TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = _developerMode; - } - - private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) { - var str = Environment.GetEnvironmentVariable(name); - if (string.IsNullOrEmpty(str)) - { - return defaultValue; - } - - switch (str.ToLowerInvariant()) - { - case "true": - case "1": - case "yes": - return true; - case "false": - case "0": - case "no": - return false; - default: - return defaultValue; - } - } - - /// - /// Send the telemetry - /// - private static void SendTelemetry(string eventName, Dictionarypayload) - { - try - { - // if the semaphore file exists, try to send telemetry - var enabled = Utils.NativeFileExists(TelemetrySemaphoreFilePath) && !GetEnvironmentVariableAsBool(TelemetryOptoutEnvVar, false); - - if (!enabled) - { - return; - } - - if (_telemetryClient == null) - { - _telemetryClient = new TelemetryClient(); - } - _telemetryClient.TrackEvent(eventName, payload, null); - } - catch (Exception) - { - ; // Do nothing, telemetry can't be sent - } - } - - /// - /// Create the startup payload and send it up - /// - internal static void SendPSCoreStartupTelemetry() - { - var properties = new Dictionary(); - properties.Add("GitCommitID", PSVersionInfo.GitCommitId); - properties.Add("OSDescription", RuntimeInformation.OSDescription); - SendTelemetry("ConsoleHostStartup", properties); - } - } -} diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs new file mode 100644 index 00000000000..eb4557c04d2 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -0,0 +1,463 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.NetworkInformation; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.PowerShell +{ + /// + /// A Helper class for printing notification on PowerShell startup when there is a new update. + /// + /// + /// For the detailed design, please take a look at the corresponding RFC. + /// + internal static class UpdatesNotification + { + private const string UpdateCheckEnvVar = "POWERSHELL_UPDATECHECK"; + private const string LTSBuildInfoURL = "https://aka.ms/pwsh-buildinfo-lts"; + 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', + /// while 's_updateFileNamePattern' holds the pattern of this file name. + /// + private static readonly string s_updateFileNameTemplate, s_updateFileNamePattern; + + /// + /// For each notification type, we need two files to achieve the synchronization for the update check: + /// `_sentinel{notification-type}_` -- held by 's_sentinelFileName'; + /// `sentinel{notification-type}-{year}-{month}-{day}.done` + /// -- held by 's_doneFileNameTemplate', while 's_doneFileNamePattern' holds the pattern of this file name. + /// The {notification-type} part will be the integer value of the corresponding `NotificationType` member. + /// The {year}-{month}-{day} part will be filled with the date of current day when the update check runs. + /// + private static readonly string s_sentinelFileName, s_doneFileNameTemplate, s_doneFileNamePattern; + + private static readonly string s_cacheDirectory; + private static readonly EnumerationOptions s_enumOptions; + private static readonly NotificationType s_notificationType; + + /// + /// Gets a value indicating whether update notification should be done. + /// + internal static readonly bool CanNotifyUpdates; + + static UpdatesNotification() + { + s_notificationType = GetNotificationType(); + CanNotifyUpdates = s_notificationType != NotificationType.Off + && Platform.TryDeriveFromCache(PSVersionInfo.GitCommitId, out s_cacheDirectory); + + if (CanNotifyUpdates) + { + s_enumOptions = new EnumerationOptions(); + + // Build the template/pattern strings for the configured notification type. + string typeNum = ((int)s_notificationType).ToString(); + s_sentinelFileName = $"_sentinel{typeNum}_"; + s_doneFileNameTemplate = $"sentinel{typeNum}-{{0}}-{{1}}-{{2}}.done"; + s_doneFileNamePattern = $"sentinel{typeNum}-*.done"; + s_updateFileNameTemplate = $"update{typeNum}_{{0}}_{{1}}"; + s_updateFileNamePattern = $"update{typeNum}_v*.*.*_????-??-??"; + } + } + + // Maybe we shouldn't do update check and show notification when it's from a mini-shell, meaning when + // 'ConsoleShell.Start' is not called by 'ManagedEntrance.Start'. + // But it seems so unusual that it's probably not worth bothering. Also, a mini-shell probably should + // just disable the update notification feature by setting the opt-out environment variable. + + internal static void ShowUpdateNotification(PSHostUserInterface hostUI) + { + if (!Directory.Exists(s_cacheDirectory)) + { + return; + } + + if (TryParseUpdateFile( + updateFilePath: out _, + out SemanticVersion lastUpdateVersion, + 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 + : string.IsNullOrEmpty(lastUpdateVersion.PreReleaseLabel) + ? ManagedEntranceStrings.StableUpdateNotificationMessage + : ManagedEntranceStrings.PreviewUpdateNotificationMessage; + + string notificationColor = string.Empty; + string resetColor = string.Empty; + + string line2Padding = string.Empty; + string line3Padding = string.Empty; + + // We calculate how much whitespace we need to make it look nice + if (hostUI.SupportsVirtualTerminal) + { + // Swaps foreground and background colors. + notificationColor = "\x1B[7m"; + resetColor = "\x1B[0m"; + + // The first line is longest, if the message changes, this needs to be updated + int line1Length = notificationMsgTemplate.IndexOf('\n'); + int line2Length = notificationMsgTemplate.IndexOf('\n', line1Length + 1); + int line3Length = notificationMsgTemplate.IndexOf('\n', line2Length + 1); + line3Length -= line2Length + 1; + line2Length -= line1Length + 1; + + line2Padding = line2Padding.PadRight(line1Length - line2Length + releaseTag.Length); + // 3 represents the extra placeholder in the template + line3Padding = line3Padding.PadRight(line1Length - line3Length + 3); + } + + string notificationMsg = string.Format(CultureInfo.CurrentCulture, notificationMsgTemplate, releaseTag, notificationColor, resetColor, line2Padding, line3Padding); + + hostUI.WriteLine(); + hostUI.WriteLine(notificationMsg); + } + } + + internal static async Task CheckForUpdates() + { + // Delay the update check for 3 seconds so that it has the minimal impact on startup. + await Task.Delay(3000); + + // A self-built pwsh for development purpose has the SHA1 commit hash baked in 'GitCommitId', + // which is 40 characters long. So we can quickly check the length of 'GitCommitId' to tell + // if this is a self-built pwsh, and skip the update check if so. + if (PSVersionInfo.GitCommitId.Length > 40) + { + return; + } + + // Daily builds do not support update notifications + string preReleaseLabel = PSVersionInfo.PSCurrentVersion.PreReleaseLabel; + if (preReleaseLabel != null && preReleaseLabel.StartsWith("daily", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + // If the host is not connect to a network, skip the rest of the check. + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return; + } + + // Create the update cache directory if it doesn't exists + if (!Directory.Exists(s_cacheDirectory)) + { + Directory.CreateDirectory(s_cacheDirectory); + } + + bool parseSuccess = TryParseUpdateFile( + out string updateFilePath, + out SemanticVersion lastUpdateVersion, + out DateTime lastUpdateDate); + + DateTime today = DateTime.UtcNow; + 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. + return; + } + + // Construct the sentinel file paths for today's check. + string todayDoneFileName = string.Format( + CultureInfo.InvariantCulture, + s_doneFileNameTemplate, + today.Year.ToString(), + today.Month.ToString(), + today.Day.ToString()); + + string todayDoneFilePath = Path.Combine(s_cacheDirectory, todayDoneFileName); + if (File.Exists(todayDoneFilePath)) + { + // A successful update check has been done today. + // We can skip this update check. + return; + } + + try + { + // Use 's_sentinelFileName' as the file lock. + // The update-check tasks started by every 'pwsh' process of the same version will compete on holding this file. + string sentinelFilePath = Path.Combine(s_cacheDirectory, s_sentinelFileName); + using (new FileStream(sentinelFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.DeleteOnClose)) + { + if (File.Exists(todayDoneFilePath)) + { + // After acquiring the file lock, it turns out a successful check has already been done for today. + // Then let's skip this update check. + return; + } + + // Now it's guaranteed that this is the only process that reaches here. + // Clean up the old '.done' file, there should be only one of it. + foreach (string oldFile in Directory.EnumerateFiles(s_cacheDirectory, s_doneFileNamePattern, s_enumOptions)) + { + File.Delete(oldFile); + } + + if (!parseSuccess) + { + // The update file is corrupted, either because more than one update files were found unexpectedly, + // or because the update file name failed to be parsed into a release version and a publish date. + // This is **very unlikely** to happen unless the file is accidentally altered manually. + // We try to recover here by cleaning up all update files for the configured notification type. + foreach (string file in Directory.EnumerateFiles(s_cacheDirectory, s_updateFileNamePattern, s_enumOptions)) + { + File.Delete(file); + } + } + + // Do the real update check: + // - Send HTTP request to query for the new release/pre-release; + // - If there is a valid new release that should be reported to the user, + // create the file `update__` when no `update` file exists, + // or rename the existing file to `update__`. + SemanticVersion baselineVersion = lastUpdateVersion ?? PSVersionInfo.PSCurrentVersion; + Release release = await QueryNewReleaseAsync(baselineVersion); + + if (release != null) + { + // The date part of the string is 'YYYY-MM-DD'. + const int dateLength = 10; + string newUpdateFileName = string.Format( + CultureInfo.InvariantCulture, + s_updateFileNameTemplate, + release.TagName, + release.PublishAt.Substring(0, dateLength)); + + string newUpdateFilePath = Path.Combine(s_cacheDirectory, newUpdateFileName); + + if (updateFilePath == null) + { + new FileStream(newUpdateFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None).Close(); + } + else + { + File.Move(updateFilePath, newUpdateFilePath); + } + } + + // Finally, create the `todayDoneFilePath` file as an indicator that a successful update check has finished today. + new FileStream(todayDoneFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None).Close(); + } + } + catch (Exception) + { + // There are 2 possible reason for the exception: + // 1. An update check initiated from another `pwsh` process is in progress. + // It's OK to just return and let that update check to finish the work. + // 2. The update check failed (ex. internet connectivity issue, GitHub service failure). + // It's OK to just return and let another `pwsh` do the check at later time. + } + } + + /// + /// Check for the existence of the update file and parse the file name if it exists. + /// + /// Get the exact update file path. + /// Get the version of the new release. + /// Get the publish date of the new release. + /// + /// False, when + /// 1. found more than one update files that matched the pattern; OR + /// 2. found only one update file, but failed to parse its name for version and publish date. + /// True, when + /// 1. no update file was found, namely no new updates yet; + /// 2. found only one update file, and succeeded to parse its name for version and publish date. + /// + private static bool TryParseUpdateFile( + out string updateFilePath, + out SemanticVersion lastUpdateVersion, + out DateTime lastUpdateDate) + { + updateFilePath = null; + lastUpdateVersion = null; + lastUpdateDate = default; + + var files = Directory.EnumerateFiles(s_cacheDirectory, s_updateFileNamePattern, s_enumOptions); + var enumerator = files.GetEnumerator(); + + if (!enumerator.MoveNext()) + { + // It's OK that an update file doesn't exist. This could happen when there is no new updates yet. + return true; + } + + updateFilePath = enumerator.Current; + if (enumerator.MoveNext()) + { + // More than 1 files were found that match the pattern. This is a corrupted state. + // Theoretically, there should be only one update file at any point of time. + updateFilePath = null; + return false; + } + + // OK, only found one update file for the configured notification type, which is expected. + // Now let's parse the file name. + string updateFileName = Path.GetFileName(updateFilePath); + int dateStartIndex = updateFileName.LastIndexOf('_') + 1; + + if (!DateTime.TryParse( + updateFileName.AsSpan(dateStartIndex), + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeLocal, + out lastUpdateDate)) + { + updateFilePath = null; + return false; + } + + int versionStartIndex = updateFileName.IndexOf('_') + 2; + int versionLength = dateStartIndex - versionStartIndex - 1; + string versionString = updateFileName.Substring(versionStartIndex, versionLength); + + if (SemanticVersion.TryParse(versionString, out lastUpdateVersion)) + { + return true; + } + + updateFilePath = null; + lastUpdateDate = default; + return false; + } + + private static async Task QueryNewReleaseAsync(SemanticVersion baselineVersion) + { + bool isStableRelease = string.IsNullOrEmpty(PSVersionInfo.PSCurrentVersion.PreReleaseLabel); + string[] queryUris = s_notificationType switch + { + NotificationType.LTS => new[] { LTSBuildInfoURL }, + NotificationType.Default => isStableRelease + ? new[] { StableBuildInfoURL } + : new[] { StableBuildInfoURL, PreviewBuildInfoURL }, + _ => Array.Empty() + }; + + using var client = new HttpClient(); + + string userAgent = string.Create(CultureInfo.InvariantCulture, $"PowerShell {PSVersionInfo.GitCommitId}"); + client.DefaultRequestHeaders.Add("User-Agent", userAgent); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + Release releaseToReturn = null; + SemanticVersion highestVersion = baselineVersion; + var settings = new JsonSerializerSettings() { DateParseHandling = DateParseHandling.None }; + var serializer = JsonSerializer.Create(settings); + + foreach (string queryUri in queryUris) + { + // Query the GitHub Rest API and throw if the query fails. + HttpResponseMessage response = await client.GetAsync(queryUri); + response.EnsureSuccessStatusCode(); + + using var stream = await response.Content.ReadAsStreamAsync(); + using var reader = new StreamReader(stream); + using var jsonReader = new JsonTextReader(reader); + + JObject release = serializer.Deserialize(jsonReader); + var tagName = release["ReleaseTag"].ToString(); + var version = SemanticVersion.Parse(tagName.Substring(1)); + + if (version > highestVersion) + { + highestVersion = version; + var publishAt = release["ReleaseDate"].ToString(); + releaseToReturn = new Release(publishAt, tagName); + } + } + + return releaseToReturn; + } + + /// + /// Get the notification type setting. + /// + private static NotificationType GetNotificationType() + { + string str = Environment.GetEnvironmentVariable(UpdateCheckEnvVar); + if (string.IsNullOrEmpty(str)) + { + return NotificationType.Default; + } + + if (Enum.TryParse(str, ignoreCase: true, out NotificationType type)) + { + return type; + } + + return NotificationType.Default; + } + + /// + /// Notification type that can be configured. + /// + private enum NotificationType + { + /// + /// Turn off the update notification. + /// + Off = 0, + + /// + /// Give you the default behaviors: + /// - the preview version 'pwsh' checks for the new preview version and the new GA version. + /// - the GA version 'pwsh' checks for the new GA version only. + /// + Default = 1, + + /// + /// Both preview and GA version 'pwsh' checks for the new LTS version only. + /// + LTS = 2 + } + + private sealed class Release + { + internal Release(string publishAt, string tagName) + { + PublishAt = publishAt; + TagName = tagName; + } + + /// + /// The datetime stamp is in UTC. For example: 2019-03-28T18:42:02Z. + /// + internal string PublishAt { get; } + + /// + /// The release tag name. + /// + internal string TagName { get; } + } + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx index d600de01d91..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. @@ -136,15 +133,18 @@ '-' was specified as the argument to -Command but standard input has not been redirected for this process. - The command cannot be run because no argument has been supplied for the OutputFormat parameter. Specify one of the following formats for this parameter. + The command cannot be run because no argument has been supplied for the OutputFormat parameter. +Specify one of the following formats for this parameter: {0} - Cannot process the command because the -InputFormat parameter requires an argument. Specify a valid format argument for this parameter. Valid formats are: + Cannot process the command because the -InputFormat parameter requires an argument. Specify a valid format argument for this parameter. +Valid formats are: {0} - Cannot process the command because of an incorrect parameter value. "{0}" is not a valid format. Valid formats are: + Cannot process the command because of an incorrect parameter value. "{0}" is not a valid format. +Valid formats are: {1} @@ -184,10 +184,19 @@ 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. + + + Cannot process the command because -CustomPipeName specified is too long. Pipe names on this platform can be up to {0} characters long. Your pipe name '{1}' is {2} characters. - Cannot process the command because -SettingsFile requires a file path. Supply a path for the SettingsFile parameter and then try the command again. + Cannot process the command because -SettingsFile requires an argument that is a file path. Processing -SettingsFile '{0}' failed: {1}. Specify a valid path for the -SettingsFile parameter. @@ -201,4 +210,22 @@ Parameter -WindowStyle is not implemented on this platform. + + Cannot process the command because -WorkingDirectory requires an argument that is a directory path. + + + Parameter -MTA is not supported on this platform. + + + 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 e7a3d10ec30..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. @@ -188,4 +179,13 @@ The current session does not support debugging; execution will continue. Loading personal and system profiles took {0}ms. + + 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/ConsoleHostUserInterfaceSecurityResources.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx index b556e9a78f3..6c383d5052f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostUserInterfaceSecurityResources.resx @@ -1,4 +1,4 @@ - + - + diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs new file mode 100644 index 00000000000..356cde68152 --- /dev/null +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/GlobalToolShim.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerShell.GlobalTool.Shim +{ + /// + /// Shim layer to chose the appropriate runtime for PowerShell DotNet Global tool. + /// + public static class EntryPoint + { + private const string PwshDllName = "pwsh.dll"; + + private const string WinFolderName = "win"; + + private const string UnixFolderName = "unix"; + + /// + /// Entry point for the global tool. + /// + /// Arguments passed to the global tool.' + /// Exit code returned by pwsh. + public static int Main(string[] args) + { + var currentPath = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location).Directory.FullName; + var isWindows = OperatingSystem.IsWindows(); + + string platformFolder = isWindows ? WinFolderName : UnixFolderName; + + var arguments = new List(args.Length + 1); + var pwshPath = Path.Combine(currentPath, platformFolder, PwshDllName); + arguments.Add(pwshPath); + arguments.AddRange(args); + + if (File.Exists(pwshPath)) + { + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + }; + + var process = System.Diagnostics.Process.Start("dotnet", arguments); + process.WaitForExit(); + return process.ExitCode; + } + else + { + throw new FileNotFoundException(pwshPath); + } + } + } +} diff --git a/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj b/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj new file mode 100644 index 00000000000..d0203344cc2 --- /dev/null +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/Microsoft.PowerShell.GlobalTool.Shim.csproj @@ -0,0 +1,18 @@ + + + + + Shim for global tool to select appropriate runtime + 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 new file mode 100644 index 00000000000..4a5e3e367ec --- /dev/null +++ b/src/Microsoft.PowerShell.GlobalTool.Shim/runtimeconfig.template.json @@ -0,0 +1,4 @@ +// This is required to roll forward to supported minor.patch versions of the runtime. +{ + "rollForwardOnNoCandidateFx": 1 +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs index 65540314dcc..59cb3067efc 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -11,7 +14,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -40,8 +42,10 @@ public class AddLocalGroupMemberCommand : PSCmdlet public Microsoft.PowerShell.Commands.LocalGroup Group { get { return this.group;} + set { this.group = value; } } + private Microsoft.PowerShell.Commands.LocalGroup group; /// @@ -59,8 +63,10 @@ public Microsoft.PowerShell.Commands.LocalGroup Group public Microsoft.PowerShell.Commands.LocalPrincipal[] Member { get { return this.member;} + set { this.member = value; } } + private Microsoft.PowerShell.Commands.LocalPrincipal[] member; /// @@ -74,8 +80,10 @@ public Microsoft.PowerShell.Commands.LocalPrincipal[] Member public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -89,12 +97,13 @@ public string Name public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -104,7 +113,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -125,7 +133,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -203,13 +210,13 @@ private LocalPrincipal MakePrincipal(string groupId, LocalPrincipal member) } } } + if (CheckShouldProcess(principal, groupId)) return principal; return null; } - /// /// Determine if a principal should be processed. /// Just a wrapper around Cmdlet.ShouldProcess, with localized string @@ -245,10 +252,10 @@ private void ProcessGroup(LocalGroup group) foreach (var member in this.Member) { LocalPrincipal principal = MakePrincipal(groupId, member); - if (null != principal) + if (principal != null) { var ex = sam.AddLocalGroupMember(group, principal); - if (null != ex) + if (ex != null) { WriteError(ex.MakeErrorRecord()); } @@ -279,10 +286,10 @@ private void ProcessSid(SecurityIdentifier groupSid) foreach (var member in this.Member) { LocalPrincipal principal = MakePrincipal(groupSid.ToString(), member); - if (null != principal) + if (principal != null) { var ex = sam.AddLocalGroupMember(groupSid, principal); - if (null != ex) + if (ex != null) { WriteError(ex.MakeErrorRecord()); } @@ -291,7 +298,6 @@ private void ProcessSid(SecurityIdentifier groupSid) } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs index 9d7d4c5ca3c..592c77726fe 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -10,7 +13,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -48,8 +50,10 @@ public class DisableLocalUserCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalUser[] InputObject { get { return this.inputobject; } + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalUser[] inputobject; /// @@ -67,8 +71,10 @@ public Microsoft.PowerShell.Commands.LocalUser[] InputObject public string[] Name { get { return this.name; } + set { this.name = value; } } + private string[] name; /// @@ -86,12 +92,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -101,7 +108,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -119,7 +125,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -135,7 +140,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process users requested by -Name + /// Process users requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -161,7 +166,7 @@ private void ProcessNames() } /// - /// Process users requested by -SID + /// Process users requested by -SID. /// private void ProcessSids() { @@ -183,7 +188,7 @@ private void ProcessSids() } /// - /// Process users requested by -InputObject + /// Process users requested by -InputObject. /// private void ProcessUsers() { @@ -209,7 +214,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionDisableUser); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs index cf0562f23f5..88627ba2e33 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -10,7 +13,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -48,8 +50,10 @@ public class EnableLocalUserCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalUser[] InputObject { get { return this.inputobject; } + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalUser[] inputobject; /// @@ -67,8 +71,10 @@ public Microsoft.PowerShell.Commands.LocalUser[] InputObject public string[] Name { get { return this.name; } + set { this.name = value; } } + private string[] name; /// @@ -86,14 +92,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -103,7 +108,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -121,7 +125,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -137,7 +140,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process users requested by -Name + /// Process users requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -163,7 +166,7 @@ private void ProcessNames() } /// - /// Process users requested by -SID + /// Process users requested by -SID. /// private void ProcessSids() { @@ -185,7 +188,7 @@ private void ProcessSids() } /// - /// Process users requested by -InputObject + /// Process users requested by -InputObject. /// private void ProcessUsers() { @@ -211,7 +214,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionEnableUser); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs index bd93d56fa6e..4c11a3c4c36 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -39,8 +41,10 @@ public class GetLocalGroupCommand : Cmdlet public string[] Name { get { return this.name; } + set { this.name = value; } } + private string[] name; /// @@ -56,14 +60,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -73,7 +76,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -91,7 +93,6 @@ protected override void ProcessRecord() ProcessSids(); } - /// /// EndProcessing method. /// @@ -107,7 +108,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process groups requested by -Name + /// Process groups requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -144,7 +145,7 @@ private void ProcessNames() } /// - /// Process groups requested by -SID + /// Process groups requested by -SID. /// private void ProcessSids() { @@ -164,7 +165,6 @@ private void ProcessSids() } } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs index 0acb4d03ded..0b3625f6ef7 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -8,7 +11,6 @@ using System.Management.Automation.SecurityAccountsManager.Extensions; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -38,8 +40,10 @@ public class GetLocalGroupMemberCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalGroup Group { get { return this.group;} + set { this.group = value; } } + private Microsoft.PowerShell.Commands.LocalGroup group; /// @@ -53,8 +57,10 @@ public Microsoft.PowerShell.Commands.LocalGroup Group public string Member { get { return this.member;} + set { this.member = value; } } + private string member; /// @@ -70,8 +76,10 @@ public string Member public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -87,12 +95,13 @@ public string Name public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -102,7 +111,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -128,7 +136,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -150,12 +157,12 @@ private IEnumerable ProcessesMembership(IEnumerable(membership); } else { - //var rv = new List(); + // var rv = new List(); rv = new List(); if (WildcardPattern.ContainsWildcardCharacters(Member)) @@ -203,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; } @@ -223,7 +230,6 @@ private IEnumerable ProcessSid(SecurityIdentifier groupSid) return ProcessesMembership(sam.GetLocalGroupMembers(groupSid)); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs index e37acc70611..9f8d3b9311b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -8,7 +11,6 @@ using System.Management.Automation.SecurityAccountsManager.Extensions; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -44,6 +46,7 @@ public string[] Name set { this.name = value; } } + private string[] name; /// @@ -59,13 +62,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid; } + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -75,7 +78,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -93,7 +95,6 @@ protected override void ProcessRecord() ProcessSids(); } - /// /// EndProcessing method. /// @@ -109,7 +110,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process users requested by -Name + /// Process users requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -146,7 +147,7 @@ private void ProcessNames() } /// - /// Process users requested by -SID + /// Process users requested by -SID. /// private void ProcessSids() { @@ -166,7 +167,6 @@ private void ProcessSids() } } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs index 35a6272de96..b709d22a485 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -35,8 +37,10 @@ public class NewLocalGroupCommand : Cmdlet public string Description { get { return this.description;} + set { this.description = value; } } + private string description; /// @@ -52,13 +56,13 @@ public string Description public string Name { get { return this.name;} + set { this.name = value; } } + private string name; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -68,7 +72,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -93,7 +96,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -113,7 +115,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionNewGroup); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs index 70690eb8584..d3402b5a4c9 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -48,8 +50,10 @@ public class NewLocalUserCommand : PSCmdlet public System.DateTime AccountExpires { get { return this.accountexpires;} + set { this.accountexpires = value; } } + private System.DateTime accountexpires; // This parameter added by hand (copied from SetLocalUserCommand), not by Cmdlet Designer @@ -61,8 +65,10 @@ public System.DateTime AccountExpires public System.Management.Automation.SwitchParameter AccountNeverExpires { get { return this.accountneverexpires;} + set { this.accountneverexpires = value; } } + private System.Management.Automation.SwitchParameter accountneverexpires; /// @@ -74,8 +80,10 @@ public System.Management.Automation.SwitchParameter AccountNeverExpires public string Description { get { return this.description;} + set { this.description = value; } } + private string description; /// @@ -86,8 +94,10 @@ public string Description public System.Management.Automation.SwitchParameter Disabled { get { return this.disabled;} + set { this.disabled = value; } } + private System.Management.Automation.SwitchParameter disabled; /// @@ -100,8 +110,10 @@ public System.Management.Automation.SwitchParameter Disabled public string FullName { get { return this.fullname;} + set { this.fullname = value; } } + private string fullname; /// @@ -118,8 +130,10 @@ public string FullName public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -134,8 +148,10 @@ public string Name public System.Security.SecureString Password { get { return this.password;} + set { this.password = value; } } + private System.Security.SecureString password; /// @@ -148,8 +164,10 @@ public System.Security.SecureString Password public System.Management.Automation.SwitchParameter NoPassword { get { return this.nopassword; } + set { this.nopassword = value; } } + private System.Management.Automation.SwitchParameter nopassword; /// @@ -161,8 +179,10 @@ public System.Management.Automation.SwitchParameter NoPassword public System.Management.Automation.SwitchParameter PasswordNeverExpires { get { return this.passwordneverexpires; } + set { this.passwordneverexpires = value; } } + private System.Management.Automation.SwitchParameter passwordneverexpires; /// @@ -174,13 +194,13 @@ public System.Management.Automation.SwitchParameter PasswordNeverExpires public System.Management.Automation.SwitchParameter UserMayNotChangePassword { get { return this.usermaynotchangepassword;} + set { this.usermaynotchangepassword = value; } } + private System.Management.Automation.SwitchParameter usermaynotchangepassword; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -192,10 +212,10 @@ protected override void BeginProcessing() InvalidParametersException ex = new InvalidParametersException("AccountExpires", "AccountNeverExpires"); ThrowTerminatingError(ex.MakeErrorRecord()); } + sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -250,7 +270,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -270,7 +289,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionNewUser); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs index cb93071ca5d..981643cf04c 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -9,7 +12,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -41,8 +43,10 @@ public class RemoveLocalGroupCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalGroup[] InputObject { get { return this.inputobject; } + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalGroup[] inputobject; /// @@ -60,8 +64,10 @@ public Microsoft.PowerShell.Commands.LocalGroup[] InputObject public string[] Name { get { return this.name; } + set { this.name = value; } } + private string[] name; /// @@ -79,12 +85,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid; } + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -94,7 +101,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -112,7 +118,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -128,7 +133,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process groups requested by -Name + /// Process groups requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -154,7 +159,7 @@ private void ProcessNames() } /// - /// Process groups requested by -SID + /// Process groups requested by -SID. /// private void ProcessSids() { @@ -176,7 +181,7 @@ private void ProcessSids() } /// - /// Process groups given through -InputObject + /// Process groups given through -InputObject. /// private void ProcessGroups() { @@ -202,7 +207,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionRemoveGroup); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs index b8c87bef98a..47d0a77784e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Collections.Generic; @@ -11,7 +14,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -40,8 +42,10 @@ public class RemoveLocalGroupMemberCommand : PSCmdlet public Microsoft.PowerShell.Commands.LocalGroup Group { get { return this.group;} + set { this.group = value; } } + private Microsoft.PowerShell.Commands.LocalGroup group; /// @@ -59,8 +63,10 @@ public Microsoft.PowerShell.Commands.LocalGroup Group public Microsoft.PowerShell.Commands.LocalPrincipal[] Member { get { return this.member;} + set { this.member = value; } } + private Microsoft.PowerShell.Commands.LocalPrincipal[] member; /// @@ -74,8 +80,10 @@ public Microsoft.PowerShell.Commands.LocalPrincipal[] Member public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -89,12 +97,13 @@ public string Name public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -104,7 +113,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -125,7 +133,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -244,10 +251,10 @@ private void ProcessGroup(LocalGroup group) foreach (var member in this.Member) { LocalPrincipal principal = MakePrincipal(groupId, member); - if (null != principal) + if (principal != null) { var ex = sam.RemoveLocalGroupMember(group, principal); - if (null != ex) + if (ex != null) { WriteError(ex.MakeErrorRecord()); } @@ -278,10 +285,10 @@ private void ProcessSid(SecurityIdentifier groupSid) foreach (var member in this.Member) { LocalPrincipal principal = MakePrincipal(groupSid.ToString(), member); - if (null != principal) + if (principal != null) { var ex = sam.RemoveLocalGroupMember(groupSid, principal); - if (null != ex) + if (ex != null) { WriteError(ex.MakeErrorRecord()); } @@ -289,7 +296,6 @@ private void ProcessSid(SecurityIdentifier groupSid) } } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs index cbcef6ef48f..6d6081fef7e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -9,7 +12,6 @@ using System.Diagnostics.CodeAnalysis; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -42,8 +44,10 @@ public class RemoveLocalUserCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalUser[] InputObject { get { return this.inputobject;} + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalUser[] inputobject; /// @@ -61,8 +65,10 @@ public Microsoft.PowerShell.Commands.LocalUser[] InputObject public string[] Name { get { return this.name; } + set { this.name = value; } } + private string[] name; /// @@ -80,13 +86,13 @@ public string[] Name public System.Security.Principal.SecurityIdentifier[] SID { get { return this.sid; } + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier[] sid; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -96,7 +102,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -114,7 +119,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -130,7 +134,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process users requested by -Name + /// Process users requested by -Name. /// /// /// All arguments to -Name will be treated as names, @@ -156,7 +160,7 @@ private void ProcessNames() } /// - /// Process users requested by -SID + /// Process users requested by -SID. /// private void ProcessSids() { @@ -178,7 +182,7 @@ private void ProcessSids() } /// - /// Process users given through -InputObject + /// Process users given through -InputObject. /// private void ProcessUsers() { @@ -204,7 +208,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionRemoveUser); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs index 25254cf05fc..c594fccb889 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -40,8 +42,10 @@ public class RenameLocalGroupCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalGroup InputObject { get { return this.inputobject;} + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalGroup inputobject; /// @@ -58,8 +62,10 @@ public Microsoft.PowerShell.Commands.LocalGroup InputObject public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -73,8 +79,10 @@ public string Name public string NewName { get { return this.newname;} + set { this.newname = value; } } + private string newname; /// @@ -90,12 +98,13 @@ public string NewName public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -105,7 +114,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -123,7 +131,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -139,7 +146,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process group requested by -Name + /// Process group requested by -Name. /// /// /// Arguments to -Name will be treated as names, @@ -162,7 +169,7 @@ private void ProcessName() } /// - /// Process group requested by -SID + /// Process group requested by -SID. /// private void ProcessSid() { @@ -181,7 +188,7 @@ private void ProcessSid() } /// - /// Process group given through -InputObject + /// Process group given through -InputObject. /// private void ProcessGroup() { @@ -220,7 +227,6 @@ private bool CheckShouldProcess(string groupName, string newName) return ShouldProcess(groupName, msg); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs index 4a48b659e62..c23cf41ac61 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -40,8 +42,10 @@ public class RenameLocalUserCommand : Cmdlet public Microsoft.PowerShell.Commands.LocalUser InputObject { get { return this.inputobject;} + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalUser inputobject; /// @@ -58,8 +62,10 @@ public Microsoft.PowerShell.Commands.LocalUser InputObject public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -73,8 +79,10 @@ public string Name public string NewName { get { return this.newname;} + set { this.newname = value; } } + private string newname; /// @@ -90,13 +98,13 @@ public string NewName public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -106,7 +114,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -124,7 +131,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -140,7 +146,7 @@ protected override void EndProcessing() #region Private Methods /// - /// Process user requested by -Name + /// Process user requested by -Name. /// /// /// Arguments to -Name will be treated as names, @@ -163,7 +169,7 @@ private void ProcessName() } /// - /// Process user requested by -SID + /// Process user requested by -SID. /// private void ProcessSid() { @@ -182,7 +188,7 @@ private void ProcessSid() } /// - /// Process group given through -InputObject + /// Process group given through -InputObject. /// private void ProcessUser() { @@ -221,7 +227,6 @@ private bool CheckShouldProcess(string userName, string newName) return ShouldProcess(userName, msg); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs index eeaf701bffd..49ae31dacc7 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -35,8 +37,10 @@ public class SetLocalGroupCommand : Cmdlet public string Description { get { return this.description;} + set { this.description = value; } } + private string description; /// @@ -53,8 +57,10 @@ public string Description public Microsoft.PowerShell.Commands.LocalGroup InputObject { get { return this.inputobject;} + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalGroup inputobject; /// @@ -71,8 +77,10 @@ public Microsoft.PowerShell.Commands.LocalGroup InputObject public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -88,13 +96,13 @@ public string Name public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; #endregion Parameter Properties - - #region Cmdlet Overrides /// /// BeginProcessing method. @@ -104,7 +112,6 @@ protected override void BeginProcessing() sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -148,7 +155,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -168,7 +174,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionSetGroup); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs index 4ae4e422d39..3bfdc24ac03 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #region Using directives using System; using System.Management.Automation; @@ -8,7 +11,6 @@ using Microsoft.PowerShell.LocalAccounts; #endregion - namespace Microsoft.PowerShell.Commands { /// @@ -52,8 +54,10 @@ public class SetLocalUserCommand : PSCmdlet public System.DateTime AccountExpires { get { return this.accountexpires;} + set { this.accountexpires = value; } } + private System.DateTime accountexpires; /// @@ -64,8 +68,10 @@ public System.DateTime AccountExpires public System.Management.Automation.SwitchParameter AccountNeverExpires { get { return this.accountneverexpires;} + set { this.accountneverexpires = value; } } + private System.Management.Automation.SwitchParameter accountneverexpires; /// @@ -77,8 +83,10 @@ public System.Management.Automation.SwitchParameter AccountNeverExpires public string Description { get { return this.description;} + set { this.description = value; } } + private string description; /// @@ -91,8 +99,10 @@ public string Description public string FullName { get { return this.fullname;} + set { this.fullname = value; } } + private string fullname; /// /// The following is the definition of the input parameter "InputObject". @@ -108,8 +118,10 @@ public string FullName public Microsoft.PowerShell.Commands.LocalUser InputObject { get { return this.inputobject;} + set { this.inputobject = value; } } + private Microsoft.PowerShell.Commands.LocalUser inputobject; /// @@ -125,8 +137,10 @@ public Microsoft.PowerShell.Commands.LocalUser InputObject public string Name { get { return this.name;} + set { this.name = value; } } + private string name; /// @@ -138,8 +152,10 @@ public string Name public System.Security.SecureString Password { get { return this.password;} + set { this.password = value; } } + private System.Security.SecureString password; /// @@ -147,12 +163,14 @@ public System.Security.SecureString Password /// Specifies that the password will not expire. /// [Parameter] - public System.Boolean PasswordNeverExpires + public bool PasswordNeverExpires { get { return this.passwordneverexpires; } + set { this.passwordneverexpires = value; } } - private System.Boolean passwordneverexpires; + + private bool passwordneverexpires; /// /// The following is the definition of the input parameter "SID". @@ -167,8 +185,10 @@ public System.Boolean PasswordNeverExpires public System.Security.Principal.SecurityIdentifier SID { get { return this.sid;} + set { this.sid = value; } } + private System.Security.Principal.SecurityIdentifier sid; /// @@ -177,15 +197,15 @@ public System.Security.Principal.SecurityIdentifier SID /// account. The default value is True. /// [Parameter] - public System.Boolean UserMayChangePassword + public bool UserMayChangePassword { get { return this.usermaychangepassword;} + set { this.usermaychangepassword = value; } } - private System.Boolean usermaychangepassword; - #endregion Parameter Properties - + private bool usermaychangepassword; + #endregion Parameter Properties #region Cmdlet Overrides /// @@ -198,10 +218,10 @@ protected override void BeginProcessing() InvalidParametersException ex = new InvalidParametersException("AccountExpires", "AccountNeverExpires"); ThrowTerminatingError(ex.MakeErrorRecord()); } + sam = new Sam(); } - /// /// ProcessRecord method. /// @@ -278,7 +298,6 @@ protected override void ProcessRecord() } } - /// /// EndProcessing method. /// @@ -298,7 +317,6 @@ private bool CheckShouldProcess(string target) return ShouldProcess(target, Strings.ActionSetUser); } #endregion Private Methods - }//End Class - -}//End namespace + } +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs index a9f7193efe4..1c7a7630ea3 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs @@ -1,7 +1,11 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Management.Automation; using System.Management.Automation.SecurityAccountsManager; using System.Runtime.Serialization; + using Microsoft.PowerShell.LocalAccounts; namespace Microsoft.PowerShell.Commands @@ -61,23 +65,23 @@ internal LocalAccountsException(string message, object target, ErrorCategory err } /// - /// Compliance Constructor + /// Compliance Constructor. /// public LocalAccountsException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public LocalAccountsException(String message) : base(message) { } + public LocalAccountsException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public LocalAccountsException(String message, Exception ex) : base(message, ex) { } + public LocalAccountsException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -121,22 +125,22 @@ internal InternalException(UInt32 ntStatus, } /// - /// Compliance Constructor + /// Compliance Constructor. /// public InternalException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public InternalException(String message) : base(message) { } + public InternalException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public InternalException(String message, Exception ex) : base(message, ex) { } + public InternalException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -181,22 +185,22 @@ internal Win32InternalException(int errorCode, } /// - /// Compliance Constructor + /// Compliance Constructor. /// public Win32InternalException() : base() {} /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public Win32InternalException(String message) : base(message) { } + public Win32InternalException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public Win32InternalException(String message, Exception ex) : base(message, ex) { } + public Win32InternalException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -235,13 +239,13 @@ public InvalidPasswordException(uint errorCode) } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public InvalidPasswordException(String message, Exception ex) : base(message, ex) { } + public InvalidPasswordException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -268,17 +272,17 @@ internal InvalidParametersException(string parameterA, string parameterB) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public InvalidParametersException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public InvalidParametersException(String message, Exception ex) : base(message, ex) { } + public InvalidParametersException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -296,22 +300,22 @@ internal AccessDeniedException(object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public AccessDeniedException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public AccessDeniedException(String message) : base(message) { } + public AccessDeniedException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public AccessDeniedException(String message, Exception ex) : base(message, ex) { } + public AccessDeniedException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -329,22 +333,22 @@ internal InvalidNameException(string name, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public InvalidNameException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public InvalidNameException(String message) : base(message) { } + public InvalidNameException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public InvalidNameException(String message, Exception ex) : base(message, ex) { } + public InvalidNameException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -362,22 +366,22 @@ internal NameInUseException(string name, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public NameInUseException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public NameInUseException(String message) : base(message) { } + public NameInUseException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public NameInUseException(String message, Exception ex) : base(message, ex) { } + public NameInUseException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -396,22 +400,22 @@ internal NotFoundException(string message, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public NotFoundException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public NotFoundException(String message) : base(message) { } + public NotFoundException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public NotFoundException(String message, Exception ex) : base(message, ex) { } + public NotFoundException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -419,7 +423,7 @@ protected NotFoundException(SerializationInfo info, StreamingContext ctx) : base } /// - /// Exception indicating that a principal was not Found + /// Exception indicating that a principal was not Found. /// public class PrincipalNotFoundException : NotFoundException { @@ -429,22 +433,22 @@ internal PrincipalNotFoundException(string principal, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public PrincipalNotFoundException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public PrincipalNotFoundException(String message) : base(message) { } + public PrincipalNotFoundException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public PrincipalNotFoundException(String message, Exception ex) : base(message, ex) { } + public PrincipalNotFoundException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -462,22 +466,22 @@ internal GroupNotFoundException(string group, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public GroupNotFoundException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public GroupNotFoundException(String message) : base(message) { } + public GroupNotFoundException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public GroupNotFoundException(String message, Exception ex) : base(message, ex) { } + public GroupNotFoundException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -495,22 +499,22 @@ internal UserNotFoundException(string user, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public UserNotFoundException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public UserNotFoundException(String message) : base(message) { } + public UserNotFoundException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public UserNotFoundException(String message, Exception ex) : base(message, ex) { } + public UserNotFoundException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -528,22 +532,22 @@ internal MemberNotFoundException(string member, string group) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public MemberNotFoundException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public MemberNotFoundException(String message) : base(message) { } + public MemberNotFoundException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public MemberNotFoundException(String message, Exception ex) : base(message, ex) { } + public MemberNotFoundException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -562,22 +566,22 @@ internal ObjectExistsException(string message, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public ObjectExistsException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public ObjectExistsException(String message) : base(message) { } + public ObjectExistsException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public ObjectExistsException(String message, Exception ex) : base(message, ex) { } + public ObjectExistsException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -595,22 +599,22 @@ internal GroupExistsException(string group, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public GroupExistsException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public GroupExistsException(String message) : base(message) { } + public GroupExistsException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public GroupExistsException(String message, Exception ex) : base(message, ex) { } + public GroupExistsException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -628,22 +632,22 @@ internal UserExistsException(string user, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public UserExistsException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public UserExistsException(String message) : base(message) { } + public UserExistsException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public UserExistsException(String message, Exception ex) : base(message, ex) { } + public UserExistsException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// @@ -661,22 +665,22 @@ internal MemberExistsException(string member, string group, object target) } /// - /// Compliance Constructor + /// Compliance Constructor. /// public MemberExistsException() : base() { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// - public MemberExistsException(String message) : base(message) { } + public MemberExistsException(string message) : base(message) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// - public MemberExistsException(String message, Exception ex) : base(message, ex) { } + public MemberExistsException(string message, Exception ex) : base(message, ex) { } /// - /// Compliance Constructor + /// Compliance Constructor. /// /// /// diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs index 86ef63e3ef7..007966cb0a8 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Text.RegularExpressions; @@ -9,7 +12,7 @@ namespace System.Management.Automation.SecurityAccountsManager.Extensions { /// - /// Provides extension methods for the Cmdlet class + /// Provides extension methods for the Cmdlet class. /// internal static class CmdletExtensions { @@ -30,7 +33,7 @@ internal static SecurityIdentifier TrySid(this Cmdlet cmdlet, bool allowSidConstants = false) { if (!allowSidConstants) - if (!(s.Length > 2 && s.StartsWith("S-", StringComparison.Ordinal) && Char.IsDigit(s[2]))) + if (!(s.Length > 2 && s.StartsWith("S-", StringComparison.Ordinal) && char.IsDigit(s[2]))) return null; SecurityIdentifier sid = null; @@ -49,12 +52,12 @@ internal static SecurityIdentifier TrySid(this Cmdlet cmdlet, } /// - /// Provides extension methods for the PSCmdlet class + /// Provides extension methods for the PSCmdlet class. /// internal static class PSExtensions { /// - /// Determine if a given parameter was provided to the cmdlet + /// Determine if a given parameter was provided to the cmdlet. /// /// /// The object to check. diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs index 170493522fb..b43904fb773 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using Microsoft.PowerShell.LocalAccounts; @@ -14,7 +17,7 @@ public class LocalGroup : LocalPrincipal /// /// A short description of the Group. /// - public String Description { get; set; } + public string Description { get; set; } #endregion Public Properties #region Construction @@ -37,7 +40,7 @@ public LocalGroup(string name) } /// - /// Construct a new LocalGroup object that is a copy of another + /// Construct a new LocalGroup object that is a copy of another. /// /// private LocalGroup(LocalGroup other) @@ -47,7 +50,6 @@ private LocalGroup(LocalGroup other) } #endregion Construction - #region Public Methods /// /// Provides a string representation of the LocalGroup object. @@ -68,10 +70,6 @@ public override string ToString() /// public LocalGroup Clone() { - if (null == this) - { - throw new NullReferenceException(); - } return new LocalGroup(this); } #endregion Public Methods diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs index 94e5972d265..dcfec24631b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs @@ -1,14 +1,17 @@ -using System.Security.Principal; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Security.Principal; namespace Microsoft.PowerShell.Commands { /// - /// Defines the source of a Principal + /// Defines the source of a Principal. /// public enum PrincipalSource { /// - /// The principal source is unknown or could not be determined + /// The principal source is unknown or could not be determined. /// Unknown = 0, diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs index fb27679feac..9cad9777cac 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using Microsoft.PowerShell.LocalAccounts; @@ -52,7 +55,6 @@ public class LocalUser : LocalPrincipal /// public bool UserMayChangePassword { get; set; } - /// /// Indicates whether the user must have a password (true) or not (false). /// @@ -69,7 +71,6 @@ public class LocalUser : LocalPrincipal public DateTime? LastLogon { get; set; } #endregion Public Properties - #region Construction /// /// Initializes a new LocalUser object. @@ -134,10 +135,6 @@ public override string ToString() /// public LocalUser Clone() { - if (null == this) - { - throw new NullReferenceException(); - } return new LocalUser(this); } #endregion Public Methods diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs index 4a085d7867f..c34dbcd64d8 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Runtime.InteropServices; using System.Text; @@ -123,7 +126,7 @@ internal struct UNICODE_STRING public UNICODE_STRING(string s) { - buffer = String.IsNullOrEmpty(s) ? String.Empty : s; + buffer = string.IsNullOrEmpty(s) ? string.Empty : s; Length = (UInt16)(2 * buffer.Length); MaximumLength = Length; } @@ -134,7 +137,7 @@ public override string ToString() // often have buffers that point to junk if Length = 0, or that // point to non-null-terminated strings, resulting in marshaled // String objects that have more characters than they should. - return Length == 0 ? String.Empty + return Length == 0 ? string.Empty : buffer.Substring(0, Length / 2); } } @@ -162,7 +165,6 @@ public void Dispose() } } - // These structures are filled in by Marshalling, so fields will be initialized // invisibly to the C# compiler, and some fields will not be used in C# code. #pragma warning disable 0649, 0169 @@ -195,7 +197,6 @@ internal static class Win32 internal const UInt32 STANDARD_RIGHTS_WRITE = READ_CONTROL; internal const UInt32 STANDARD_RIGHTS_EXECUTE = READ_CONTROL; - internal const UInt32 STANDARD_RIGHTS_ALL = 0x001F0000; internal const UInt32 SPECIFIC_RIGHTS_ALL = 0x0000FFFF; @@ -209,7 +210,6 @@ internal static class Win32 internal const UInt32 GENERIC_EXECUTE = 0x20000000; internal const UInt32 GENERIC_ALL = 0x10000000; - // These constants control the behavior of the FormatMessage Windows API function internal const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; internal const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; @@ -335,7 +335,6 @@ internal static class Win32 internal const int NERR_LastAdmin = NERR_BASE + 352; // This operation is not allowed on the last administrative account. #endregion Win32 Error Codes - #region SECURITY_DESCRIPTOR Control Flags internal const UInt16 SE_DACL_PRESENT = 0x0004; internal const UInt16 SE_SELF_RELATIVE = 0x8000; @@ -348,6 +347,7 @@ internal static class Win32 #region Win32 Functions [DllImport(PInvokeDllNames.LookupAccountSidDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LookupAccountSid(string systemName, byte[] accountSid, StringBuilder accountName, @@ -357,6 +357,7 @@ internal static extern bool LookupAccountSid(string systemName, out SID_NAME_USE use); [DllImport(PInvokeDllNames.LookupAccountNameDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LookupAccountName(string systemName, string accountName, [MarshalAs(UnmanagedType.LPArray)] @@ -366,7 +367,6 @@ internal static extern bool LookupAccountName(string systemName, ref uint domainNameLength, out SID_NAME_USE peUse); - [DllImport(PInvokeDllNames.GetSecurityDescriptorDaclDllName, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetSecurityDescriptorDacl(IntPtr pSecurityDescriptor, diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs index 59e00f0b252..654d221a69b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Diagnostics.CodeAnalysis; namespace System.Management.Automation.SecurityAccountsManager.Native @@ -18,7 +20,6 @@ internal static class NtStatus public const UInt32 STATUS_SEVERITY_INFORMATIONAL = 0x1; public const UInt32 STATUS_SEVERITY_ERROR = 0x3; - public const UInt32 STATUS_SUCCESS = 0x00000000; // // MessageText: @@ -28,11 +29,6 @@ internal static class NtStatus public const UInt32 STATUS_MORE_ENTRIES = 0x00000105; - - - - - ///////////////////////////////////////////////////////////////////////// // // Standard Information values @@ -430,7 +426,7 @@ internal static class NtStatus #region Public Methods /// - /// Determine if an NTSTATUS value indicates Success + /// Determine if an NTSTATUS value indicates Success. /// /// The NTSTATUS value returned from native functions. /// @@ -442,7 +438,7 @@ public static bool IsSuccess(UInt32 ntstatus) } /// - /// Determine if an NTSTATUS value indicates an Error + /// Determine if an NTSTATUS value indicates an Error. /// /// The NTSTATUS value returned from native functions. /// @@ -453,9 +449,8 @@ public static bool IsError(UInt32 ntstatus) return Severity(ntstatus) == STATUS_SEVERITY_ERROR; } - /// - /// Determine if an NTSTATUS value indicates a Warning + /// Determine if an NTSTATUS value indicates a Warning. /// /// The NTSTATUS value returned from native functions. /// @@ -480,9 +475,8 @@ public static bool IsInformational(UInt32 ntstatus) return Severity(ntstatus) == STATUS_SEVERITY_INFORMATIONAL; } - /// - /// Return the Severity part of an NTSTATUS value + /// Return the Severity part of an NTSTATUS value. /// /// The NTSTATUS value returned from native functions. /// @@ -493,9 +487,8 @@ public static uint Severity(UInt32 ntstatus) return ntstatus >> 30; } - /// - /// Return the Facility part of an NSTATUS value + /// Return the Facility part of an NSTATUS value. /// /// The NTSTATUS value returned from native functions. /// @@ -508,9 +501,8 @@ public static uint Facility(UInt32 ntstatus) return (ntstatus >> 16) & 0x0FFF; } - /// - /// Return the Code part of an NTSTATUS value + /// Return the Code part of an NTSTATUS value. /// /// The NTSTATUS value returned from native functions. /// diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs index 6a4688584f1..68a7d31e833 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs index b74184e1685..3e6bbcafd10 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs @@ -1,8 +1,11 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; -using System.ComponentModel; using Microsoft.PowerShell.Commands; using System.Management.Automation.SecurityAccountsManager.Extensions; @@ -35,7 +38,7 @@ internal enum Enabling internal class SamRidEnumeration { #region Original struct members - public String Name; + public string Name; public UInt32 RelativeId; #endregion Original struct members @@ -44,7 +47,6 @@ internal class SamRidEnumeration #endregion Additional members } - /// /// Provides methods for manipulating local Users and Groups. /// @@ -176,7 +178,6 @@ internal enum DomainAccess : uint Max = Win32.MAXIMUM_ALLOWED } - /// /// The operation under way. Used in the class. /// @@ -213,11 +214,11 @@ 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; - public Object target; + public object target; public string objectId; public string memberId; @@ -257,7 +258,7 @@ public Context(ContextOperation operation, } /// - /// Default constructor + /// Default constructor. /// public Context() { @@ -307,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; @@ -368,7 +369,7 @@ public override string ToString() private IntPtr localDomainHandle = IntPtr.Zero; private IntPtr builtinDomainHandle = IntPtr.Zero; private Context context = null; - private string machineName = String.Empty; + private string machineName = string.Empty; #endregion Instance Data #region Construction @@ -394,7 +395,7 @@ public string StripMachineName(string name) } #region Local Groups /// - /// Retrieve a named local group + /// Retrieve a named local group. /// /// Name of the desired local group. /// @@ -416,7 +417,7 @@ internal LocalGroup GetLocalGroup(string groupName) } /// - /// Retrieve a local group by SID + /// Retrieve a local group by SID. /// /// /// A object identifying the desired group. @@ -461,7 +462,7 @@ internal LocalGroup CreateLocalGroup(LocalGroup group) } /// - /// Update a local group with new property values + /// Update a local group with new property values. /// /// /// A object representing the group to be updated. @@ -745,7 +746,7 @@ internal Exception RemoveLocalGroupMember(SecurityIdentifier groupSid, LocalPrin #region Local Users /// - /// Retrieve a named local user + /// Retrieve a named local user. /// /// Name of the desired local user. /// @@ -767,7 +768,7 @@ internal LocalUser GetLocalUser(string userName) } /// - /// Retrieve a local user by SID + /// Retrieve a local user by SID. /// /// /// A object identifying the desired user. @@ -791,7 +792,7 @@ internal LocalUser GetLocalUser(SecurityIdentifier sid) } /// - /// Create a local user + /// Create a local user. /// /// A object containing /// information about the local user to be created. @@ -855,7 +856,6 @@ internal void RemoveLocalUser(LocalUser user) RemoveUser(user.SID); } - /// /// Rename a local user. /// @@ -900,7 +900,7 @@ internal void RenameLocalUser(LocalUser user, string newName) } /// - /// Enable or disable a Local User + /// Enable or disable a Local User. /// /// /// A object identifying the user to enable or disable. @@ -920,7 +920,7 @@ internal void EnableLocalUser(SecurityIdentifier sid, Enabling enable) } /// - /// Enable or disable a Local User + /// Enable or disable a Local User. /// /// /// A object representing the user to enable or disable. @@ -1277,6 +1277,7 @@ private LocalUser CreateUser(LocalUser userInfo, System.Security.SecureString pa { SamApi.SamDeleteUser(userHandle); } + throw; } finally @@ -1289,7 +1290,7 @@ private LocalUser CreateUser(LocalUser userInfo, System.Security.SecureString pa } /// - /// Remove a group identified by SID + /// Remove a group identified by SID. /// /// /// A object identifying the @@ -1374,6 +1375,7 @@ private void RenameGroup(SecurityIdentifier sid, string newName) Marshal.DestroyStructure(buffer); Marshal.FreeHGlobal(buffer); } + if (aliasHandle != IntPtr.Zero) status = SamApi.SamCloseHandle(aliasHandle); } @@ -1617,12 +1619,12 @@ private LocalUser MakeLocalUserObject(SamRidEnumeration sre, IntPtr userHandle) FullName = allInfo.FullName.ToString(), Description = allInfo.AdminComment.ToString(), - //TODO: why is this coming up as 864000000000 (number of ticks per day)? + // TODO: why is this coming up as 864000000000 (number of ticks per day)? PasswordChangeableDate = DateTimeFromSam(allInfo.PasswordCanChange.QuadPart), PasswordExpires = DateTimeFromSam(allInfo.PasswordMustChange.QuadPart), - //TODO: why is this coming up as 0X7FFFFFFFFFFFFFFF (largest signed 64-bit, and well out of range of DateTime)? + // TODO: why is this coming up as 0X7FFFFFFFFFFFFFFF (largest signed 64-bit, and well out of range of DateTime)? AccountExpires = DateTimeFromSam(allInfo.AccountExpires.QuadPart), LastLogon = DateTimeFromSam(allInfo.LastLogon.QuadPart), PasswordLastSet = DateTimeFromSam(allInfo.PasswordLastSet.QuadPart), @@ -1763,6 +1765,7 @@ private void RenameUser(SecurityIdentifier sid, string newName) Marshal.DestroyStructure(buffer); Marshal.FreeHGlobal(buffer); } + if (userHandle != IntPtr.Zero) status = SamApi.SamCloseHandle(userHandle); } @@ -1998,6 +2001,7 @@ private void UpdateGroup(LocalGroup group, LocalGroup changed) Marshal.DestroyStructure(buffer); Marshal.FreeHGlobal(buffer); } + if (aliasHandle != IntPtr.Zero) status = SamApi.SamCloseHandle(aliasHandle); } @@ -2200,11 +2204,13 @@ private void SetUserData(IntPtr userHandle, ? sourceUser.AccountExpires.Value.ToFileTime() : 0L; } + if (setFlags.HasFlag(UserProperties.Description)) { which |= SamApi.USER_ALL_ADMINCOMMENT; info.AdminComment = new UNICODE_STRING(sourceUser.Description); } + if (setFlags.HasFlag(UserProperties.Enabled)) { which |= SamApi.USER_ALL_USERACCOUNTCONTROL; @@ -2213,6 +2219,7 @@ private void SetUserData(IntPtr userHandle, else uac |= SamApi.USER_ACCOUNT_DISABLED; } + if (setFlags.HasFlag(UserProperties.FullName)) { which |= SamApi.USER_ALL_FULLNAME; @@ -2365,6 +2372,7 @@ private RawAcl GetSamDacl(IntPtr objectHandle) if (IntPtr.Zero != securityObject) status = SamApi.SamFreeMemory(securityObject); } + return rv; } @@ -2657,6 +2665,7 @@ private SecurityIdentifier RidToSid(IntPtr domainHandle, uint rid) if (IntPtr.Zero != sidBytes) status = SamApi.SamFreeMemory(sidBytes); } + return sid; } @@ -2769,7 +2778,7 @@ private AccountInfo LookupAccountInfo(string accountName) { // Bug: 7407413 : // If accountname is in the format domain1\user1, - //then AccountName.ToString() will return domain1\domain1\user1 + // then AccountName.ToString() will return domain1\domain1\user1 // Ideally , accountname should be processed to hold only account name (without domain) // as we are keeping the domain in 'DomainName' variable. @@ -2778,6 +2787,7 @@ private AccountInfo LookupAccountInfo(string accountName) { accountName = accountName.Substring(index + 1); } + return new AccountInfo { AccountName = accountName, @@ -2818,7 +2828,7 @@ private LocalPrincipal MakeLocalPrincipalObject(AccountInfo info) switch (info.Use) { - case SID_NAME_USE.SidTypeAlias: //TODO: is this the right thing to do??? + case SID_NAME_USE.SidTypeAlias: // TODO: is this the right thing to do??? case SID_NAME_USE.SidTypeGroup: case SID_NAME_USE.SidTypeWellKnownGroup: rv.ObjectClass = Strings.ObjectClassGroup; @@ -2938,7 +2948,7 @@ private Exception MakeException(UInt32 ntStatus, Context context = null) return new UserNotFoundException(context.ObjectName, context.target); case NtStatus.STATUS_SPECIAL_GROUP: // The group specified is a special group and cannot be operated on in the requested fashion. - //case NtStatus.STATUS_SPECIAL_ALIAS: // referred to in source for SAM api, but not in ntstatus.h!!! + // case NtStatus.STATUS_SPECIAL_ALIAS: // referred to in source for SAM api, but not in ntstatus.h!!! return new InvalidOperationException(StringUtil.Format(Strings.InvalidForGroup, context.ObjectName)); @@ -2974,7 +2984,7 @@ private Exception MakeException(UInt32 ntStatus, Context context = null) case NtStatus.STATUS_PASSWORD_RESTRICTION: return new InvalidPasswordException(Native.Win32.RtlNtStatusToDosError(ntStatus)); - //TODO: do we want to handle these? + // TODO: do we want to handle these? // they appear to be returned only in functions we are not calling case NtStatus.STATUS_INVALID_SID: // member sid is corrupted case NtStatus.STATUS_INVALID_MEMBER: // member has wrong account type @@ -3125,7 +3135,7 @@ internal struct OSVERSIONINFOEX private static volatile OperatingSystem localOs; /// - /// It only contains the properties that get used in powershell + /// It only contains the properties that get used in powershell. /// internal sealed class OperatingSystem { @@ -3135,15 +3145,14 @@ internal sealed class OperatingSystem internal OperatingSystem(Version version, string servicePack) { - if (version == null) - throw new ArgumentNullException("version"); + ArgumentNullException.ThrowIfNull(version); _version = version; _servicePack = servicePack; } /// - /// OS version + /// OS version. /// public Version Version { @@ -3151,7 +3160,7 @@ public Version Version } /// - /// VersionString + /// VersionString. /// public string VersionString { @@ -3196,6 +3205,7 @@ private OperatingSystem GetOperatingSystem() Version ver = new Version(osviex.MajorVersion, osviex.MinorVersion, osviex.BuildNumber, (osviex.ServicePackMajor << 16) | osviex.ServicePackMinor); localOs = new OperatingSystem(ver, osviex.CSDVersion); } + return localOs; #else return Environment.OSVersion; diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs index e0c686ea7c7..c2d9bc7f95b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -193,7 +196,7 @@ internal struct USER_ADMIN_COMMENT_INFORMATION [StructLayout(LayoutKind.Sequential)] internal struct USER_EXPIRES_INFORMATION { - //LARGE_INTEGER AccountExpires; + // LARGE_INTEGER AccountExpires; public Int64 AccountExpires; } @@ -210,7 +213,6 @@ internal struct USER_LOGON_HOURS_INFORMATION public LOGON_HOURS LogonHours; } - [StructLayout(LayoutKind.Sequential)] internal struct POLICY_PRIMARY_DOMAIN_INFO { @@ -273,7 +275,6 @@ internal static class SamApi internal const UInt32 SAM_USER_ENUMERATION_FILTER_INTERNET = 0x00000002; internal const UInt32 SAM_SERVER_LOOKUP_DOMAIN = 0x0020; - // // Bits to be used in UserAllInformation's WhichFields field (to indicate // which items were queried or set). @@ -311,7 +312,6 @@ internal static class SamApi internal const UInt32 USER_ALL_UNDEFINED_MASK = 0xC0000000; - // // Bit masks for the UserAccountControl member of the USER_ALL_INFORMATION structure // @@ -351,7 +351,6 @@ public static extern UInt32 SamConnect(ref UNICODE_STRING serverName, UInt32 desiredAccess, ref OBJECT_ATTRIBUTES objectAttributes); - [DllImport("samlib.dll")] internal static extern UInt32 SamRidToSid(IntPtr objectHandle, UInt32 rid, out IntPtr sid); @@ -444,7 +443,6 @@ internal static extern UInt32 SamCreateUser2InDomain(IntPtr domainHandle, out UInt32 grantedAccess, out UInt32 relativeId); - [DllImport("samlib.dll")] internal static extern UInt32 SamQueryInformationUser(IntPtr userHandle, USER_INFORMATION_CLASS userInformationClass, diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs index afc20f12612..25bbeb49329 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs @@ -1,15 +1,18 @@ -using System.Globalization; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Globalization; using System.Management.Automation.SecurityAccountsManager.Native; namespace System.Management.Automation.SecurityAccountsManager { /// - /// Contains utility functions for formatting localizable strings + /// Contains utility functions for formatting localizable strings. /// 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() { @@ -35,6 +38,7 @@ internal static string Format(string fmt, uint p0) { return string.Format(CultureInfo.CurrentCulture, fmt, p0); } + internal static string Format(string fmt, int p0) { return string.Format(CultureInfo.CurrentCulture, fmt, p0); diff --git a/src/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.csproj b/src/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.csproj index ebd69091ebe..a85b06d4f90 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.csproj +++ b/src/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.csproj @@ -1,9 +1,9 @@ - + - PowerShell Core's Microsoft.PowerShell.LocalAccounts project + PowerShell's Microsoft.PowerShell.LocalAccounts project Microsoft.PowerShell.LocalAccounts @@ -11,20 +11,4 @@ - - $(DefineConstants);CORECLR - - - - portable - - - - $(DefineConstants);UNIX - - - - full - - diff --git a/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx b/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx index 0c6fa1de8f7..ff1c4a17427 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx +++ b/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx @@ -1,4 +1,4 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - Accept the input or move to the next line if input is missing a closing token. - - - Move the cursor to the next line without attempting to execute the input - - - Move the cursor back one character - - - Delete the character before the cursor - - - Delete text from the cursor to the start of the line - - - Move the text from the cursor to the beginning of the line to the kill ring - - - Move to the first item in the history - - - Move the cursor to the beginning of the line - - - Abort editing the current line and re-evaluate the prompt - - - Remove all items from the kill ring - - - Remove all items from the command line history (not PowerShell history) - - - Complete the input if there is a single completion, otherwise complete the input with common prefix for all completions. Show possible completions if pressed a second time. - - - Delete the character under the cursor - - - Move the cursor to the beginning of the current or previous token or start of the line - - - String is not used in the UI - - - String is not used in the UI - - - Move to the last item (the current input) in the history - - - Move the cursor to the end of the line - - - Mark the location of the cursor and move the cursor to the position of the previous mark - - - Move the cursor forward one character - - - Delete text from the cursor to the end of the line - - - Move the cursor to the beginning of the next token or end of line - - - Search for the previous item in the history that starts with the current input - like PreviousHistory if the input is empty - - - Search for the next item in the history that starts with the current input - like NextHistory if the input is empty - - - String is not used in the UI - - - Move the text from the cursor to the start of the current or previous token to the kill ring - - - Move the text from the cursor to the end of the input to the kill ring - - - Move the text from the cursor to the end of the current or next token to the kill ring - - - Replace the input with the next item in the history - - - Paste text from the system clipboard - - - Display the possible completions without changing the input - - - Replace the input with the previous item in the history - - - Redo an undo - - - Equivalent to undo all edits (clears the line except lines imported from history) - - - Mark the location of the cursor - - - Complete the input using the next completion - - - Complete the input using the previous completion - - - Undo a previous edit - - - Copy the text from the current kill ring position to the input - - - Replace the previously yanked text with the text from the next kill ring position - - - 'start' cannot be less than zero or greater than the length of the buffer - - - length is too big - - - Display all {0} possibilities? (y or n) _ - - - Clear the screen and redraw the current line at the top of the screen - - - Go to matching brace - - - Abort the current operation, e.g. incremental history search - - - Search history forward interactively - - - Search history backwards interactively - - - Move the text from the start of the current or previous word to the cursor to the kill ring - - - Move the cursor to the beginning of the current or previous word - - - Move the cursor forward to the end of the current word, or if between words, to the end of the next word. - - - Move the text from the cursor to the end of the current or next word to the kill ring - - - Move the cursor forward to the start of the next word - - - Move the text from the cursor to the start of the current or previous whitespace delimited word to the kill ring - - - Read a character and move the cursor to the previous occurence of that character - - - Read a character and move the cursor to the next occurence of that character - - - Start or accumulate a numeric argument to other functions - - - Copy the text of the last argument to the input - - - Copy the text of the first argument to the input - - - Accept the current line and recall the next line from history after the current line finishes executing - - - Key is unbound - - - Insert the key typed - - - Show all key bindings - - - Show the key binding for the next chord entered - - - Copy selected region to the system clipboard. If no region is selected, copy the whole line - - - Delete selected region placing deleted text in the system clipboard - - - Kill the text between the cursor and the mark - - - Adjust the current selection to include the previous character - - - Adjust the current selection to include the previous word - - - Adjust the current selection to include the next character - - - Adjust the current selection to include the next word using ForwardWord - - - Adjust the current selection to include the next word - - - Adjust the current selection to include the previous word using ShellBackwardWord - - - Adjust the current selection to include the next word using ShellForwardWord - - - Allows you to select multiple lines from the console using Shift+UpArrow/DownArrow and copy the selected lines to clipboard by pressing Enter. - - - Erases the current prompt and calls the prompt function to redisplay the prompt - - - Scroll the display down one screen - - - Scroll the display down one line - - - Scroll the display to the cursor - - - Scroll the display to the top - - - Scroll the display up one screen - - - Scroll the display up one line - - - Adjust the current selection to include the next word using ShellNextWord - - - Move the cursor to the end of the current token - - - Adjust the current selection to include from the cursor to the end of the line - - - Adjust the current selection to include from the cursor to the start of the line - - - Select the entire line. Moves the cursor to the end of the line - - - Either copy selected text to the clipboard, or if no text is selected, cancel editing the line with CancelLine. - - - Complete the input if there is a single completion, otherwise complete the input by selecting from a menu of possible completions. - - - -Oops, something went wrong. Please report this bug with the details below. -Report on GitHub: https://github.com/lzybkr/PSReadLine/issues/new - - - ----------------------------------------------------------------------- -Last {0} Keys: -{1} - -Exception: -{2} ------------------------------------------------------------------------ - - - Move the cursor to the next line if the input has multiple lines. - - - Move the cursor to the previous line if the input has multiple lines. - - - Command '{0}' cannot be found. - - - Accept the input or move to the next line if input is missing a closing token. -If there are other parse errors, unresolved commands, or incorrect parameters, show the error and continue editing. - - - Delete the character under the cursor, or if the line is empty, exit the process. - - - Unable to translate '{0}' to virtual key code: {1}. - - - Chord can have at most two keys. - - - Duplicate or invalid modifier token '{0}' for key '{1}'. - - - Invalid sequence '{0}'. - - - Unrecognized key '{0}'. Please use a character literal or a well-known key name from the System.ConsoleKey enumeration. - - - Accept the line and switch to Vi's insert mode. - - - Delete the previous word in the line. - - - Switch to VI's command mode. - - - Move to the end of the line. - - - Switch to insert mode, appending at the current line position. - - - Swap the current character with the character before it. - - - Repeats the last search. - - - Searches backward for the prescribed character. - - - Searches for the prescribed character in the prescribed direction. - - - Switches to insert mode after positioning the cursor past the end of the line. - - - Deletes all of the line except for leading whitespace. - - - Replaces the character in front of the cursor. - - - Replaces the line left of the cursor and all of the way to the beginning. - - - Replaces the line left of the cursor and all but one character to the beginning of the line. - - - Inserts the entered character at the beginning and accepts the line. - - - Deletes all characters between the cursor and the matching brace. - - - Deletes the current line. - - - Deletes from the cursor to the end of the line. - - - Deletes from the cursor to the end of the current word. - - - Deletes the current word. - - - Positions the cursor at the first non-blank character. - - - Moves the cursor forward to the end of the next word. - - - Moves the cursor to the prescribed column. - - - Switches to insert mode. - - - Moves the cursor to the beginning of the line and switches to insert mode. - - - Moves the cursor to the end of the line and switches to insert mode. - - - Deletes the current character and switches to insert mode. - - - Inverts the case of the current character and advances the cursor. - - - Repeats the last modification command. - - - Repeat the last search, but in the opposite direction. - - - Repeat the last search. - - - Replace all characters between the current brace character and it's matching partner. - - - Replace the current character with the next set of characters typed. - - - Replace the current character with only one character. - - - Repace the current line with the next set of characters typed. - - - Replace the characters from the cursor position to the end of the line. - - - Replace the current character until an escape is entered or the line is accepted. - - - Replace the current word. - - - Delete the current character and insert the next character. - - - Starts a new search backward in the history. - - - Transposes the current character with the next character in the line. - - - Undoes all commands for this line. - - - Prompts for a search string and initiates a search upon AcceptLine. - - - Repeat the last recorded character search in the opposite direction. - - - Repeat the last recorded character search. - - - Move to the previous occurrence of the specified character. - - - Move to the previous occurrence of the specified character and then forward one character. - - - Move to the next occurrence of the specified character. - - - Move to he next occurrence of the specified character and then back one character. - - - Replace the previous word. - - - Delete backward to the beginning of the previous word, as delimited by white space and common delimiters, and enter insert mode. - - - Write the contents of the local clipboard after the cursor. - - - Move the cursor to the beginning of the next word, as delimited by white space and common delimiters. - - - Move the cursor to the beginning of the previous word, as delimited by white space. - - - Move the cursor to the end this word, as delimited by white space. - - - Write the contents of the local clipboard before the cursor. - - - Move the cursor to the beginning of the next word, as delimited by white space. - - - Move the cursor to the matching brace. - - - Delete backward to the beginning of the previous word, as delimited by white space. - - - Delete the current word, as delimited by white space. - - - Delete to the end of the current word, as delimited by white space and common delimiters. - - - Delete to the end of this word, as delimited by white space. - - - Delete backward to the beginning of the previous word, as delimited by white space, and enter insert mode. - - - Delete to the beginning of the next word, as delimited by white space, and enter insert mode. - - - Delete to the end of the word, as delimited by white space and common delimiters, and enter insert mode. - - - Delete to the end of the word, as delimited by white space, and enter insert mode. - - - Place all characters in the current line into the local clipboard. - - - Place all characters at and after the cursor into the local clipboard. - - - Place all characters from before the cursor to the beginning of the previous word, as delimited by white space and common delimiters, into the local clipboard. - - - Place all characters from before the cursor to the beginning of the previous word, as delimited by white space, into the local clipboard. - - - Place all characters from the cursor to the end of the word, as delimited by white space and common delimiters, into the local clipboard. - - - Place all characters from the cursor to the end of the word, as delimited by white space, into the local clipboard. - - - Place the characters from the cursor to the end of the next word, as delimited by white space and common delimiters, into the local clipboard. - - - Place the characters from the cursor to the end of the next white space delimited word into the local clipboard. - - - Place the character to the left of the cursor into the local clipboard. - - - Place the character at the cursor into the local clipboard. - - - Place the characters before the cursor into the local clipboard. - - - Place all characters before the cursor and to the 1st non-white space character into the local clipboard. - - - Place all characters between the matching brace and the cursor into the local clipboard. - - - Deletes all characters between the cursor position and the matching brace. - - - If the line is empty, exit, otherwise accept the line as input. - - - Handles the processing of a number argument after the first key of a chord. - - - Exit the shell. - - - Invokes TabCompleteNext after doing some vi-specific clean up. - - - Invokes TabCompletePrevious after doing some vi-specific clean up. - - - Invokes the console compatible editor specified by $env:VISUAL or $env:$EDITOR on the current command line. - - - Appends a new multi-line edit mode line to the current line. - - - Inserts a new multi-line edit mode line in front of the current line. - - - Joins the current multi-line edit mode line with the next. - - - The -ViMode parameter was used, but the current EditMode is not Vi. - - - Inserts a new empty line above the current line without attempting to execute the input - - - Inserts a new empty line below the current line without attempting to execute the input - - - Error reading or writing history file '{0}': {1} - - - This error will not be reported again in this session. Consider using a different path with: - Set-PSReadlineOption -HistorySavePath <Path> -Or not saving history with: - Set-PSReadlineOption -HistorySaveStyle SaveNothing - - - An exception occurred in custom key handler, see $error for more information: {0} - - diff --git a/src/Microsoft.PowerShell.PSReadLine/PublicAPI.cs b/src/Microsoft.PowerShell.PSReadLine/PublicAPI.cs deleted file mode 100644 index 2db274ec43b..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/PublicAPI.cs +++ /dev/null @@ -1,250 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; - - -namespace Microsoft.PowerShell -{ - namespace Internal - { -#pragma warning disable 1591 - - [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - public interface IPSConsoleReadLineMockableMethods - { - void Ding(); - CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options, System.Management.Automation.PowerShell powershell); - bool RunspaceIsRemote(Runspace runspace); - - } - - [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - public interface IConsole - { - object GetConsoleInputMode(); - void SetConsoleInputMode(object mode); - void RestoreConsoleInputMode(object mode); - ConsoleKeyInfo ReadKey(); - bool KeyAvailable { get; } - int CursorLeft { get; set; } - int CursorTop { get; set;} - int CursorSize { get; set; } - int BufferWidth { get; set; } - int BufferHeight { get; set;} - int WindowWidth { get; set; } - int WindowHeight { get; set; } - int WindowTop { get; set; } - ConsoleColor BackgroundColor { get; set; } - ConsoleColor ForegroundColor { get; set; } - void SetWindowPosition(int left, int top); - void SetCursorPosition(int left, int top); - void WriteLine(string s); - void Write(string s); - void WriteBufferLines(BufferChar[] buffer, ref int top); - void WriteBufferLines(BufferChar[] buffer, ref int top, bool ensureBottomLineVisible); - void ScrollBuffer(int lines); - BufferChar[] ReadBufferLines(int top, int count); - - void StartRender(); - int LengthInBufferCells(char c); - void EndRender(); -#if UNIX - void Clear(); -#endif - } - -#pragma warning restore 1591 - } - - /// - public partial class PSConsoleReadLine - { - /// - /// Insert a character at the current position. Supports undo. - /// - /// Character to insert - public static void Insert(char c) - { - _singleton.SaveEditItem(EditItemInsertChar.Create(c, _singleton._current)); - - // Use Append if possible because Insert at end makes StringBuilder quite slow. - if (_singleton._current == _singleton._buffer.Length) - { - _singleton._buffer.Append(c); - } - else - { - _singleton._buffer.Insert(_singleton._current, c); - } - _singleton._current += 1; - _singleton.Render(); - } - - /// - /// Insert a string at the current position. Supports undo. - /// - /// String to insert - public static void Insert(string s) - { - _singleton.SaveEditItem(EditItemInsertString.Create(s, _singleton._current)); - - // Use Append if possible because Insert at end makes StringBuilder quite slow. - if (_singleton._current == _singleton._buffer.Length) - { - _singleton._buffer.Append(s); - } - else - { - _singleton._buffer.Insert(_singleton._current, s); - } - _singleton._current += s.Length; - _singleton.Render(); - } - - /// - /// Delete some text at the given position. Supports undo. - /// - /// The start position to delete - /// The length to delete - public static void Delete(int start, int length) - { - Replace(start, length, null); - } - - /// - /// Replace some text at the given position. Supports undo. - /// - /// The start position to replace - /// The length to replace - /// The replacement text - /// The action that initiated the replace (used for undo) - /// The argument to the action that initiated the replace (used for undo) - public static void Replace(int start, int length, string replacement, Action instigator = null, object instigatorArg = null) - { - if (start < 0 || start > _singleton._buffer.Length) - { - throw new ArgumentException(PSReadLineResources.StartOutOfRange, "start"); - } - if (length > (_singleton._buffer.Length - start)) - { - throw new ArgumentException(PSReadLineResources.ReplacementLengthTooBig, "length"); - } - - bool useEditGroup = (_singleton._editGroupStart == -1); - - if (useEditGroup) - { - _singleton.StartEditGroup(); - } - - var str = _singleton._buffer.ToString(start, length); - _singleton.SaveEditItem(EditItemDelete.Create(str, start)); - _singleton._buffer.Remove(start, length); - if (replacement != null) - { - _singleton.SaveEditItem(EditItemInsertString.Create(replacement, start)); - _singleton._buffer.Insert(start, replacement); - _singleton._current = start + replacement.Length; - } - else - { - _singleton._current = start; - } - - if (useEditGroup) - { - _singleton.EndEditGroup(instigator, instigatorArg); // Instigator is needed for VI undo - _singleton.Render(); - } - } - - /// - /// Get the state of the buffer - the current input and the position of the cursor - /// - public static void GetBufferState(out string input, out int cursor) - { - input = _singleton._buffer.ToString(); - cursor = _singleton._current; - } - - /// - /// Get the state of the buffer - the ast, tokens, errors, and position of the cursor - /// - public static void GetBufferState(out Ast ast, out Token[] tokens, out ParseError[] parseErrors, out int cursor) - { - _singleton.ParseInput(); - ast = _singleton._ast; - tokens = _singleton._tokens; - parseErrors = _singleton._parseErrors; - cursor = _singleton._current; - } - - /// - /// Get the selection state of the buffer - /// - /// The start of the current selection or -1 if nothing is selected. - /// The length of the current selection or -1 if nothing is selected. - public static void GetSelectionState(out int start, out int length) - { - if (_singleton._visualSelectionCommandCount == 0) - { - start = -1; - length = -1; - } - else - { - _singleton.GetRegion(out start, out length); - } - } - - /// - /// Set the position of the cursor. - /// - public static void SetCursorPosition(int cursor) - { - if (cursor > _singleton._buffer.Length + ViEndOfLineFactor) - { - cursor = _singleton._buffer.Length + ViEndOfLineFactor; - } - if (cursor < 0) - { - cursor = 0; - } - - _singleton._current = cursor; - _singleton.PlaceCursor(); - } - - /// - /// A helper method when your function expects an optional int argument (e.g. from DigitArgument) - /// If there is not argument (it's null), returns true and sets numericArg to defaultNumericArg. - /// Dings and returns false if the argument is not an int (no conversion is attempted) - /// Otherwise returns true, and numericArg has the result. - /// - public static bool TryGetArgAsInt(object arg, out int numericArg, int defaultNumericArg) - { - if (arg == null) - { - numericArg = defaultNumericArg; - return true; - } - - if (arg is int) - { - numericArg = (int)arg; - return true; - } - - Ding(); - numericArg = 0; - return false; - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs b/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs deleted file mode 100644 index 0aede96f921..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/ReadLine.cs +++ /dev/null @@ -1,957 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Management.Automation.Runspaces; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using Microsoft.PowerShell.Commands; -using Microsoft.PowerShell.Internal; - -[module: SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant")] - -namespace Microsoft.PowerShell -{ - [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")] - [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")] - class ExitException : Exception { } - - public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods - { - private static readonly PSConsoleReadLine _singleton = new PSConsoleReadLine(); - - private bool _delayedOneTimeInitCompleted; - - private IPSConsoleReadLineMockableMethods _mockableMethods; - private IConsole _console; - - private EngineIntrinsics _engineIntrinsics; -#if !UNIX - private static GCHandle _breakHandlerGcHandle; -#endif - private Thread _readKeyThread; - private AutoResetEvent _readKeyWaitHandle; - private AutoResetEvent _keyReadWaitHandle; - private ManualResetEvent _closingWaitHandle; - private WaitHandle[] _threadProcWaitHandles; - private WaitHandle[] _requestKeyWaitHandles; - private object _savedConsoleInputMode; - - private readonly StringBuilder _buffer; - private readonly StringBuilder _statusBuffer; - private bool _statusIsErrorMessage; - private string _statusLinePrompt; - private List _edits; - private int _editGroupStart; - private int _undoEditIndex; - private int _mark; - private bool _inputAccepted; - private readonly Queue _queuedKeys; - private Stopwatch _lastRenderTime; - private static Stopwatch _readkeyStopwatch = new Stopwatch(); - - // Save a fixed # of keys so we can reconstruct a repro after a crash - private readonly static HistoryQueue _lastNKeys = new HistoryQueue(200); - - // Tokens etc. - private Token[] _tokens; - private Ast _ast; - private ParseError[] _parseErrors; - - bool IPSConsoleReadLineMockableMethods.RunspaceIsRemote(Runspace runspace) - { - return runspace != null && runspace.ConnectionInfo != null; - } - - private void ReadOneOrMoreKeys() - { - _readkeyStopwatch.Restart(); - while (_console.KeyAvailable) - { - var key = _console.ReadKey(); - _lastNKeys.Enqueue(key); - _queuedKeys.Enqueue(key); - if (_readkeyStopwatch.ElapsedMilliseconds > 2) - { - // Don't spend too long in this loop if there are lots of queued keys - break; - } - } - - if (_queuedKeys.Count == 0) - { - var key = _console.ReadKey(); - _lastNKeys.Enqueue(key); - _queuedKeys.Enqueue(key); - } - } - - private void ReadKeyThreadProc() - { - while (true) - { - // Wait until ReadKey tells us to read a key (or it's time to exit). - int handleId = WaitHandle.WaitAny(_singleton._threadProcWaitHandles); - if (handleId == 1) // It was the _closingWaitHandle that was signaled. - break; - - ReadOneOrMoreKeys(); - - // One or more keys were read - let ReadKey know we're done. - _keyReadWaitHandle.Set(); - } - } - - private static ConsoleKeyInfo ReadKey() - { - // Reading a key is handled on a different thread. During process shutdown, - // PowerShell will wait in it's ConsoleCtrlHandler until the pipeline has completed. - // If we're running, we're most likely blocked waiting for user input. - // This is a problem for two reasons. First, exiting takes a long time (5 seconds - // on Win8) because PowerShell is waiting forever, but the OS will forcibly terminate - // the console. Also - if there are any event handlers for the engine event - // PowerShell.Exiting, those handlers won't get a chance to run. - // - // By waiting for a key on a different thread, our pipeline execution thread - // (the thread Readline is called from) avoid being blocked in code that can't - // be unblocked and instead blocks on events we control. - - // First, set an event so the thread to read a key actually attempts to read a key. - _singleton._readKeyWaitHandle.Set(); - - int handleId; - System.Management.Automation.PowerShell ps = null; - - try - { - while (true) - { - // Next, wait for one of three things: - // - a key is pressed - // - the console is exiting - // - 300ms - to process events if we're idle - - handleId = WaitHandle.WaitAny(_singleton._requestKeyWaitHandles, 300); - if (handleId != WaitHandle.WaitTimeout) - break; - if (_singleton._engineIntrinsics == null) - continue; - - // If we timed out, check for event subscribers (which is just - // a hint that there might be an event waiting to be processed.) - var eventSubscribers = _singleton._engineIntrinsics.Events.Subscribers; - if (eventSubscribers.Count > 0) - { - bool runPipelineForEventProcessing = false; - foreach (var sub in eventSubscribers) - { - if (sub.SourceIdentifier.Equals("PowerShell.OnIdle", StringComparison.OrdinalIgnoreCase)) - { - // There is an OnIdle event. We're idle because we timed out. Normally - // PowerShell generates this event, but PowerShell assumes the engine is not - // idle because it called PSConsoleHostReadline which isn't returning. - // So we generate the event instead. - _singleton._engineIntrinsics.Events.GenerateEvent("PowerShell.OnIdle", null, null, null); - runPipelineForEventProcessing = true; - break; - } - - // If there are any event subscribers that have an action (which might - // write to the console) and have a source object (i.e. aren't engine - // events), run a tiny useless bit of PowerShell so that the events - // can be processed. - if (sub.Action != null && sub.SourceObject != null) - { - runPipelineForEventProcessing = true; - break; - } - } - - if (runPipelineForEventProcessing) - { - if (ps == null) - { - ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddScript("0"); - } - - // To detect output during possible event processing, see if the cursor moved - // and rerender if so. - var console = _singleton._console; - var y = console.CursorTop; - ps.Invoke(); - if (y != console.CursorTop) - { - _singleton._initialY = console.CursorTop; - _singleton.Render(); - } - } - } - } - } - finally - { - if (ps != null) { ps.Dispose(); } - } - - if (handleId == 1) - { - // The console is exiting - throw an exception to unwind the stack to the point - // where we can return from ReadLine. - if (_singleton.Options.HistorySaveStyle == HistorySaveStyle.SaveAtExit) - { - _singleton.SaveHistoryAtExit(); - } - _singleton._historyFileMutex.Dispose(); - - throw new OperationCanceledException(); - } - - var key = _singleton._queuedKeys.Dequeue(); - return key; - } - - private void PrependQueuedKeys(ConsoleKeyInfo key) - { - if (_queuedKeys.Count > 0) - { - // This should almost never happen so being inefficient is fine. - var list = new List(_queuedKeys); - _queuedKeys.Clear(); - _queuedKeys.Enqueue(key); - list.ForEach(k => _queuedKeys.Enqueue(k)); - } - else - { - _queuedKeys.Enqueue(key); - } - } - - private bool BreakHandler(ConsoleBreakSignal signal) - { - if (signal == ConsoleBreakSignal.Close || signal == ConsoleBreakSignal.Shutdown) - { - // Set the event so ReadKey throws an exception to unwind. - _closingWaitHandle.Set(); - } - - return false; - } - - /// - /// Entry point - called from the PowerShell function PSConsoleHostReadline - /// after the prompt has been displayed. - /// - /// The complete command line. - public static string ReadLine(Runspace runspace, EngineIntrinsics engineIntrinsics) - { - var console = _singleton._console; - - // If either stdin or stdout is redirected, PSReadline doesn't really work, so throw - // and let PowerShell call Console.ReadLine or do whatever else it decides to do. - if (Console.IsInputRedirected || Console.IsOutputRedirected) - { - throw new NotSupportedException(); - } - - _singleton._savedConsoleInputMode = _singleton._console.GetConsoleInputMode(); - bool firstTime = true; - while (true) - { - try - { - _singleton._console.SetConsoleInputMode(_singleton._savedConsoleInputMode); - - if (firstTime) - { - firstTime = false; - _singleton.Initialize(runspace, engineIntrinsics); - } - - return _singleton.InputLoop(); - } - catch (OperationCanceledException) - { - // Console is exiting - return value isn't too critical - null or 'exit' could work equally well. - return ""; - } - catch (ExitException) - { - return "exit"; - } - catch (CustomHandlerException e) - { - var oldColor = console.ForegroundColor; - console.ForegroundColor = ConsoleColor.Red; - console.WriteLine( - string.Format(CultureInfo.CurrentUICulture, PSReadLineResources.OopsCustomHandlerException, e.InnerException.Message)); - console.ForegroundColor = oldColor; - - var lineBeforeCrash = _singleton._buffer.ToString(); - _singleton.Initialize(runspace, _singleton._engineIntrinsics); - InvokePrompt(); - Insert(lineBeforeCrash); - } - catch (Exception e) - { - // If we're running tests, just throw. - if (_singleton._mockableMethods != _singleton) - { - throw; - } - - while (e.InnerException != null) - { - e = e.InnerException; - } - var oldColor = console.ForegroundColor; - console.ForegroundColor = ConsoleColor.Red; - console.WriteLine(PSReadLineResources.OopsAnErrorMessage1); - console.ForegroundColor = oldColor; - var sb = new StringBuilder(); - for (int i = 0; i < _lastNKeys.Count; i++) - { - sb.Append(' '); - sb.Append(_lastNKeys[i].ToGestureString()); - - KeyHandler handler; - if (_singleton._dispatchTable.TryGetValue(_lastNKeys[i], out handler) && - "AcceptLine".Equals(handler.BriefDescription, StringComparison.OrdinalIgnoreCase)) - { - // Make it a little easier to see the keys - sb.Append('\n'); - } - } - - console.WriteLine(string.Format(CultureInfo.CurrentUICulture, PSReadLineResources.OopsAnErrorMessage2, _lastNKeys.Count, sb, e)); - var lineBeforeCrash = _singleton._buffer.ToString(); - _singleton.Initialize(runspace, _singleton._engineIntrinsics); - InvokePrompt(); - Insert(lineBeforeCrash); - } - finally - { - _singleton._console.RestoreConsoleInputMode(_singleton._savedConsoleInputMode); - } - } - } - - private string InputLoop() - { - ProcessViVisualEditing(); - - while (true) - { - var killCommandCount = _killCommandCount; - var yankCommandCount = _yankCommandCount; - var tabCommandCount = _tabCommandCount; - var searchHistoryCommandCount = _searchHistoryCommandCount; - var recallHistoryCommandCount = _recallHistoryCommandCount; - var yankLastArgCommandCount = _yankLastArgCommandCount; - var visualSelectionCommandCount = _visualSelectionCommandCount; - var movingAtEndOfLineCount = _moveToLineCommandCount; - - var key = ReadKey(); - ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null); - if (_inputAccepted) - { - return MaybeAddToHistory(_buffer.ToString(), _edits, _undoEditIndex, readingHistoryFile: false, fromDifferentSession: false); - } - - if (killCommandCount == _killCommandCount) - { - // Reset kill command count if it didn't change - _killCommandCount = 0; - } - if (yankCommandCount == _yankCommandCount) - { - // Reset yank command count if it didn't change - _yankCommandCount = 0; - } - if (yankLastArgCommandCount == _yankLastArgCommandCount) - { - // Reset yank last arg command count if it didn't change - _yankLastArgCommandCount = 0; - _yankLastArgState = null; - } - if (tabCommandCount == _tabCommandCount) - { - // Reset tab command count if it didn't change - _tabCommandCount = 0; - _tabCompletions = null; - } - if (searchHistoryCommandCount == _searchHistoryCommandCount) - { - if (_searchHistoryCommandCount > 0) - { - _emphasisStart = -1; - _emphasisLength = 0; - Render(); - _currentHistoryIndex = _history.Count; - } - _searchHistoryCommandCount = 0; - _searchHistoryPrefix = null; - } - if (recallHistoryCommandCount == _recallHistoryCommandCount) - { - if (_recallHistoryCommandCount > 0) - { - _currentHistoryIndex = _history.Count; - } - _recallHistoryCommandCount = 0; - } - if (searchHistoryCommandCount == _searchHistoryCommandCount && - recallHistoryCommandCount == _recallHistoryCommandCount) - { - _hashedHistory = null; - } - if (visualSelectionCommandCount == _visualSelectionCommandCount && _visualSelectionCommandCount > 0) - { - _visualSelectionCommandCount = 0; - Render(); // Clears the visual selection - } - if (movingAtEndOfLineCount == _moveToLineCommandCount) - { - _moveToLineCommandCount = 0; - } - } - } - - T CalloutUsingDefaultConsoleMode(Func func) - { - var currentMode = _console.GetConsoleInputMode(); - try - { - _console.RestoreConsoleInputMode(_savedConsoleInputMode); - return func(); - } - finally - { - _console.RestoreConsoleInputMode(currentMode); - } - } - - void CalloutUsingDefaultConsoleMode(Action action) - { - CalloutUsingDefaultConsoleMode(() => { action(); return null; }); - } - - void ProcessOneKey(ConsoleKeyInfo key, Dictionary dispatchTable, bool ignoreIfNoAction, object arg) - { - KeyHandler handler; - - if (!dispatchTable.TryGetValue(key, out handler)) - { - // If we see a control character where Ctrl wasn't used but shift was, treat that like - // shift hadn't be pressed. This cleanly allows Shift+Backspace without adding a key binding. - if (key.KeyChar > 0 && char.IsControl(key.KeyChar) && key.Modifiers == ConsoleModifiers.Shift) - { - key = new ConsoleKeyInfo(key.KeyChar, key.Key, false, false, false); - dispatchTable.TryGetValue(key, out handler); - } - } - - if (handler != null) - { - if (handler.ScriptBlock != null) - { - CalloutUsingDefaultConsoleMode(() => handler.Action(key, arg)); - } - else - { - handler.Action(key, arg); - } - } - else if (!ignoreIfNoAction && key.KeyChar != 0) - { - SelfInsert(key, arg); - } - } - - private PSConsoleReadLine() - { - _mockableMethods = this; -#if UNIX - _console = new TTYConsole(); -#else - _console = new ConhostConsole(); -#endif - - _buffer = new StringBuilder(8 * 1024); - _statusBuffer = new StringBuilder(256); - _savedCurrentLine = new HistoryItem(); - _queuedKeys = new Queue(); - - string hostName = null; - // This works mostly by luck - we're not doing anything to guarantee the constructor for our - // singleton is called on a thread with a runspace, but it is happening by coincidence. - using (var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - try - { - ps.AddCommand("Get-Variable").AddParameter("Name", "host").AddParameter("ValueOnly"); - var results = ps.Invoke(); - dynamic host = results.Count == 1 ? results[0] : null; - if (host != null) - { - hostName = host.Name as string; - } - } - catch - { - } - } - if (hostName == null) - { - hostName = "PSReadline"; - } - _options = new PSConsoleReadlineOptions(hostName); - SetDefaultBindings(_options.EditMode); - } - - private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics) - { - _engineIntrinsics = engineIntrinsics; - _runspace = runspace; - - if (!_delayedOneTimeInitCompleted) - { - DelayedOneTimeInitialize(); - _delayedOneTimeInitCompleted = true; - } - - _buffer.Clear(); - _edits = new List(); - _undoEditIndex = 0; - _editGroupStart = -1; - _current = 0; - _mark = 0; - _emphasisStart = -1; - _emphasisLength = 0; - _tokens = null; - _parseErrors = null; - _inputAccepted = false; - _initialX = _console.CursorLeft; - _initialY = _console.CursorTop - Options.ExtraPromptLineCount; - _initialBackgroundColor = _console.BackgroundColor; - _initialForegroundColor = _console.ForegroundColor; - _space = new BufferChar - { - UnicodeChar = ' ', - BackgroundColor = _initialBackgroundColor, - ForegroundColor = _initialForegroundColor - }; - _bufferWidth = _console.BufferWidth; - _killCommandCount = 0; - _yankCommandCount = 0; - _yankLastArgCommandCount = 0; - _tabCommandCount = 0; - _visualSelectionCommandCount = 0; - _statusIsErrorMessage = false; - - -#if UNIX // TODO: not necessary if ReadBufferLines worked, or if rendering worked on spans instead of complete lines - string newPrompt = GetPrompt(); - int index = newPrompt.LastIndexOf('\n'); - if (index != -1) - { - // The prompt text could be a multi-line string, and in such case - // we only want the part of it that is shown on the input line. - newPrompt = newPrompt.Substring(index + 1); - } - var bufferLineCount = (newPrompt.Length) / (_console.BufferWidth) + 1; - _consoleBuffer = ReadBufferLines(_initialY, bufferLineCount); - - for (int i=0; i 0) - { - _currentHistoryIndex = _getNextHistoryIndex; - UpdateFromHistory(moveCursor: true); - _getNextHistoryIndex = 0; - if (_searchHistoryCommandCount > 0) - { - _searchHistoryPrefix = ""; - if (Options.HistoryNoDuplicates) - { - _hashedHistory = new Dictionary(); - } - } - } - else - { - _searchHistoryCommandCount = 0; - } - } - - private void DelayedOneTimeInitialize() - { - // Delayed initialization is needed so that options can be set - // after the constuctor but have an affect before the user starts - // editing their first command line. For example, if the user - // specifies a custom history save file, we don't want to try reading - // from the default one. - - if (_engineIntrinsics != null) - { - var historyCountVar = _engineIntrinsics.SessionState.PSVariable.Get("MaximumHistoryCount"); - if (historyCountVar != null && historyCountVar.Value is int) - { - _options.MaximumHistoryCount = (int)historyCountVar.Value; - } - } - -#if UNIX - _historyFileMutex = new Mutex(false); -#else - _historyFileMutex = new Mutex(false, GetHistorySaveFileMutexName()); -#endif - - _history = new HistoryQueue(Options.MaximumHistoryCount); - _currentHistoryIndex = 0; - - bool readHistoryFile = true; - try - { - if (_options.HistorySaveStyle == HistorySaveStyle.SaveNothing && Runspace.DefaultRunspace != null) - { - using (var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - ps.AddCommand("Microsoft.PowerShell.Core\\Get-History"); - foreach (var historyInfo in ps.Invoke()) - { - AddToHistory(historyInfo.CommandLine); - } - readHistoryFile = false; - } - } - } - catch - { - } - - if (readHistoryFile) - { - ReadHistoryFile(); - } - - _killIndex = -1; // So first add indexes 0. - _killRing = new List(Options.MaximumKillRingCount); - -#if !UNIX - _breakHandlerGcHandle = GCHandle.Alloc(new BreakHandler(_singleton.BreakHandler)); - NativeMethods.SetConsoleCtrlHandler((BreakHandler)_breakHandlerGcHandle.Target, true); -#endif - _singleton._readKeyWaitHandle = new AutoResetEvent(false); - _singleton._keyReadWaitHandle = new AutoResetEvent(false); - _singleton._closingWaitHandle = new ManualResetEvent(false); - _singleton._requestKeyWaitHandles = new WaitHandle[] {_singleton._keyReadWaitHandle, _singleton._closingWaitHandle}; - _singleton._threadProcWaitHandles = new WaitHandle[] {_singleton._readKeyWaitHandle, _singleton._closingWaitHandle}; - -#if !CORECLR - // This is for a "being hosted in an alternate appdomain scenario" (the - // DomainUnload event is not raised for the default appdomain). It allows us - // to exit cleanly when the appdomain is unloaded but the process is not going - // away. - if (!AppDomain.CurrentDomain.IsDefaultAppDomain()) - { - AppDomain.CurrentDomain.DomainUnload += (x, y) => - { - _singleton._closingWaitHandle.Set(); - _singleton._readKeyThread.Join(); // may need to wait for history to be written - }; - } -#endif - - _singleton._readKeyThread = new Thread(_singleton.ReadKeyThreadProc) {IsBackground = true}; - _singleton._readKeyThread.Start(); - } - - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void Chord(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue) - { - throw new ArgumentNullException("key"); - } - - Dictionary secondKeyDispatchTable; - if (_singleton._chordDispatchTable.TryGetValue(key.Value, out secondKeyDispatchTable)) - { - var secondKey = ReadKey(); - _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); - } - } - - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void Ignore(ConsoleKeyInfo? key = null, object arg = null) - { - } - - private static void ExecuteOnSTAThread(Action action) - { -#if CORECLR - action(); -#else - if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) - { - action(); - return; - } - - Exception exception = null; - var thread = new Thread(() => - { - try - { - action(); - } - catch (Exception e) - { - exception = e; - } - }); - - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - thread.Join(); - - if (exception != null) - { - throw exception; - } -#endif - } - - #region Miscellaneous bindable functions - - /// - /// Abort current action, e.g. incremental history search - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void Abort(ConsoleKeyInfo? key = null, object arg = null) - { - } - - /// - /// Start a new digit argument to pass to other functions - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void DigitArgument(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue || char.IsControl(key.Value.KeyChar)) - { - Ding(); - return; - } - - if (_singleton._options.EditMode == EditMode.Vi && key.Value.KeyChar == '0') - { - BeginningOfLine(); - return; - } - - bool sawDigit = false; - _singleton._statusLinePrompt = "digit-argument: "; - var argBuffer = _singleton._statusBuffer; - argBuffer.Append(key.Value.KeyChar); - if (key.Value.KeyChar == '-') - { - argBuffer.Append('1'); - } - else - { - sawDigit = true; - } - - _singleton.Render(); // Render prompt - while (true) - { - var nextKey = ReadKey(); - KeyHandler handler; - if (_singleton._dispatchTable.TryGetValue(nextKey, out handler)) - { - if (handler.Action == DigitArgument) - { - if (nextKey.KeyChar == '-') - { - if (argBuffer[0] == '-') - { - argBuffer.Remove(0, 1); - } - else - { - argBuffer.Insert(0, '-'); - } - _singleton.Render(); // Render prompt - continue; - } - - if (nextKey.KeyChar >= '0' && nextKey.KeyChar <= '9') - { - if (!sawDigit && argBuffer.Length > 0) - { - // Buffer is either '-1' or '1' from one or more Alt+- keys - // but no digits yet. Remove the '1'. - argBuffer.Length -= 1; - } - sawDigit = true; - argBuffer.Append(nextKey.KeyChar); - _singleton.Render(); // Render prompt - continue; - } - } - else if (handler.Action == Abort || - handler.Action == CancelLine || - handler.Action == CopyOrCancelLine) - { - break; - } - } - - int intArg; - if (int.TryParse(argBuffer.ToString(), out intArg)) - { - _singleton.ProcessOneKey(nextKey, _singleton._dispatchTable, ignoreIfNoAction: false, arg: intArg); - } - else - { - Ding(); - } - break; - } - - // Remove our status line - argBuffer.Clear(); - _singleton.ClearStatusMessage(render: true); - } - - - /// - /// Erases the current prompt and calls the prompt function to redisplay - /// the prompt. Useful for custom key handlers that change state, e.g. - /// change the current directory. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null) - { - var currentBuffer = _singleton._buffer.ToString(); - var currentPos = _singleton._current; - _singleton._buffer.Clear(); - _singleton._current = 0; - for (int i = 0; i < _singleton._consoleBuffer.Length; i++) - { - _singleton._consoleBuffer[i].UnicodeChar = ' '; - _singleton._consoleBuffer[i].ForegroundColor = _singleton._console.ForegroundColor; - _singleton._consoleBuffer[i].BackgroundColor = _singleton._console.BackgroundColor; - } - _singleton.Render(); - _singleton._console.CursorLeft = 0; - _singleton._console.CursorTop = _singleton._initialY - _singleton.Options.ExtraPromptLineCount; - - string newPrompt = GetPrompt(); - _singleton._console.Write(newPrompt); - - _singleton._initialX = _singleton._console.CursorLeft; - _singleton._consoleBuffer = ReadBufferLines(_singleton._initialY, 1 + _singleton.Options.ExtraPromptLineCount); -#if UNIX // TODO: ReadBufferLines only needed for extra line, but doesn't work on Linux - for (int i=0; i - /// Gets the current prompt as possibly defined by the user through the - /// prompt function, and returns a default prompt if no other is - /// available. Also handles remote prompts. - /// - public static string GetPrompt() - { - string newPrompt; - var runspaceIsRemote = _singleton._mockableMethods.RunspaceIsRemote(_singleton._runspace); - - if ((_singleton._runspace.Debugger != null) && _singleton._runspace.Debugger.InBreakpoint) - { - // Run prompt command in debugger API to ensure it is run correctly on the runspace. - // This handles remote runspace debugging and nested debugger scenarios. - PSDataCollection results = new PSDataCollection(); - var command = new PSCommand(); - command.AddCommand(PromptCommand); - _singleton._runspace.Debugger.ProcessCommand( - command, - results); - - newPrompt = (results.Count == 1) ? (results[0].BaseObject as string) : DefaultPrompt; - } - else - { - System.Management.Automation.PowerShell ps; - if (!runspaceIsRemote) - { - ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); - } - else - { - ps = System.Management.Automation.PowerShell.Create(); - ps.Runspace = _singleton._runspace; - } - using (ps) - { - ps.AddCommand(PromptCommand); - var result = ps.Invoke(); - newPrompt = result.Count == 1 ? result[0] : DefaultPrompt; - } - } - - if (string.IsNullOrEmpty(newPrompt)) - { - newPrompt = DefaultPrompt; - } - - if (runspaceIsRemote) - { - var connectionInfo = _singleton._runspace.ConnectionInfo; - if (!string.IsNullOrEmpty(connectionInfo.ComputerName)) - { - newPrompt = string.Format(CultureInfo.InvariantCulture, "[{0}]: {1}", connectionInfo.ComputerName, newPrompt); - } - } - - return newPrompt; - } - - #endregion Miscellaneous bindable functions - - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/ReadLine.vi.cs b/src/Microsoft.PowerShell.PSReadLine/ReadLine.vi.cs deleted file mode 100644 index 304663f06a4..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/ReadLine.vi.cs +++ /dev/null @@ -1,1247 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - /// - /// Remembers last history search direction. - /// - private bool _searchHistoryBackward = true; - - private class ViCharacterSearcher - { - private char searchChar = '\0'; - private bool wasBackward = false; - private bool wasBackoff = false; - - public static ViCharacterSearcher instance = new ViCharacterSearcher(); - - public static bool IsRepeatable - { - get { return instance.searchChar != '\0'; } - } - - public static char SearchChar - { - get { return instance.searchChar; } - } - - public static bool WasBackward - { - get { return instance.wasBackward; } - } - - public static bool WasBackoff - { - get { return instance.wasBackoff; } - } - - public static void Set(char theChar, bool isBackward = false, bool isBackoff = false) - { - instance.searchChar = theChar; - instance.wasBackward = isBackward; - instance.wasBackoff = isBackoff; - } - - - public static void Search(char keyChar, object arg, bool backoff) - { - int qty = (arg is int) ? (int) arg : 1; - - for (int i = _singleton._current + 1; i < _singleton._buffer.Length; i++) - { - if (_singleton._buffer[i] == keyChar) - { - qty -= 1; - if (qty == 0) - { - _singleton._current = backoff ? i - 1 : i; - _singleton.PlaceCursor(); - return; - } - } - } - Ding(); - } - - public static bool SearchDelete(char keyChar, object arg, bool backoff, Action instigator) - { - int qty = (arg is int) ? (int) arg : 1; - - for (int i = _singleton._current + 1; i < _singleton._buffer.Length; i++) - { - if (_singleton._buffer[i] == keyChar) - { - qty -= 1; - if (qty == 0) - { - DeleteToEndPoint(arg, backoff ? i : i + 1, instigator); - return true; - } - } - } - Ding(); - return false; - } - - public static void SearchBackward(char keyChar, object arg, bool backoff) - { - Set(keyChar, isBackward: true, isBackoff: backoff); - int qty = (arg is int) ? (int) arg : 1; - - for (int i = _singleton._current - 1; i >= 0; i--) - { - if (_singleton._buffer[i] == keyChar) - { - qty -= 1; - if (qty == 0) - { - _singleton._current = backoff ? i + 1 : i; - _singleton.PlaceCursor(); - return; - } - } - } - Ding(); - } - - public static bool SearchBackwardDelete(char keyChar, object arg, bool backoff, Action instigator) - { - Set(keyChar, isBackward: true, isBackoff: backoff); - int qty = (arg is int) ? (int) arg : 1; - - for (int i = _singleton._current - 1; i >= 0; i--) - { - if (_singleton._buffer[i] == keyChar) - { - qty -= 1; - if (qty == 0) - { - DeleteBackwardToEndPoint(arg, backoff ? i + 1 : i, instigator); - return true; - } - } - } - Ding(); - return false; - } - } - - /// - /// Repeat the last recorded character search. - /// - public static void RepeatLastCharSearch(ConsoleKeyInfo? key = null, object arg = null) - { - if (!ViCharacterSearcher.IsRepeatable) - { - Ding(); - return; - } - - if (ViCharacterSearcher.WasBackward) - { - ViCharacterSearcher.SearchBackward(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); - } - else - { - ViCharacterSearcher.Search(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); - } - } - - /// - /// Repeat the last recorded character search, but in the opposite direction. - /// - public static void RepeatLastCharSearchBackwards(ConsoleKeyInfo? key = null, object arg = null) - { - if (!ViCharacterSearcher.IsRepeatable) - { - Ding(); - return; - } - - if (ViCharacterSearcher.WasBackward) - { - ViCharacterSearcher.Search(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); - } - else - { - ViCharacterSearcher.SearchBackward(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); - } - } - - /// - /// Read the next character and then find it, going forward, and then back off a character. - /// This is for 't' functionality. - /// - public static void SearchChar(ConsoleKeyInfo? key = null, object arg = null) - { - char keyChar = ReadKey().KeyChar; - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: false); - ViCharacterSearcher.Search(keyChar, arg, backoff: false); - } - - /// - /// Read the next character and then find it, going backward, and then back off a character. - /// This is for 'T' functionality. - /// - public static void SearchCharBackward(ConsoleKeyInfo? key = null, object arg = null) - { - char keyChar = ReadKey().KeyChar; - ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: false); - ViCharacterSearcher.SearchBackward(keyChar, arg, backoff: false); - } - - /// - /// Read the next character and then find it, going forward, and then back off a character. - /// This is for 't' functionality. - /// - public static void SearchCharWithBackoff(ConsoleKeyInfo? key = null, object arg = null) - { - char keyChar = ReadKey().KeyChar; - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: true); - ViCharacterSearcher.Search(keyChar, arg, backoff: true); - } - - /// - /// Read the next character and then find it, going backward, and then back off a character. - /// This is for 'T' functionality. - /// - public static void SearchCharBackwardWithBackoff(ConsoleKeyInfo? key = null, object arg = null) - { - char keyChar = ReadKey().KeyChar; - ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: true); - ViCharacterSearcher.SearchBackward(keyChar, arg, backoff: true); - } - - /// - /// Exits the shell. - /// - public static void ViExit(ConsoleKeyInfo? key = null, object arg = null) - { - throw new ExitException(); - } - - /// - /// Delete to the end of the line. - /// - public static void DeleteToEnd(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._current >= _singleton._buffer.Length) - { - Ding(); - return; - } - - _singleton._clipboard = _singleton._buffer.ToString(_singleton._current, _singleton._buffer.Length - _singleton._current); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - _singleton._current, - DeleteToEnd, - arg - )); - _singleton._buffer.Remove(_singleton._current, _singleton._buffer.Length - _singleton._current); - _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); - _singleton.Render(); - } - - /// - /// Delete the next word. - /// - public static void DeleteWord(ConsoleKeyInfo? key = null, object arg = null) - { - int qty = (arg is int) ? (int)arg : 1; - int endPoint = _singleton._current; - for (int i = 0; i < qty; i++) - { - endPoint = _singleton.ViFindNextWordPoint(endPoint, _singleton.Options.WordDelimiters); - } - - if (endPoint <= _singleton._current) - { - Ding(); - return; - } - - DeleteToEndPoint(arg, endPoint, DeleteWord); - } - - private static void DeleteToEndPoint(object arg, int endPoint, Action instigator) - { - _singleton.SaveToClipboard(_singleton._current, endPoint - _singleton._current); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - _singleton._current, - instigator, - arg - )); - _singleton._buffer.Remove(_singleton._current, endPoint - _singleton._current); - if (_singleton._current >= _singleton._buffer.Length) - { - _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); - } - _singleton.Render(); - } - - private static void DeleteBackwardToEndPoint(object arg, int endPoint, Action instigator) - { - int deleteLength = _singleton._current - endPoint + 1; - - _singleton.SaveToClipboard(endPoint, deleteLength); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - endPoint, - instigator, - arg - )); - _singleton._buffer.Remove(endPoint, deleteLength); - _singleton._current = endPoint; - _singleton.Render(); - } - - /// - /// Delete the next glob (white space delimited word). - /// - public static void ViDeleteGlob(ConsoleKeyInfo? key = null, object arg = null) - { - int qty = (arg is int) ? (int)arg : 1; - int endPoint = _singleton._current; - while (qty-- > 0) - { - endPoint = _singleton.ViFindNextGlob(endPoint); - } - int length = endPoint - _singleton._current; - - _singleton.SaveToClipboard(_singleton._current, length); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - _singleton._current, - ViDeleteGlob, - arg - )); - _singleton._buffer.Remove(_singleton._current, length); - if (_singleton._current >= _singleton._buffer.Length) - { - _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); - } - _singleton.Render(); - } - - /// - /// Delete to the end of the word. - /// - public static void DeleteEndOfWord(ConsoleKeyInfo? key = null, object arg = null) - { - int qty = (arg is int) ? (int)arg : 1; - int endPoint = _singleton._current; - for (int i = 0; i < qty; i++) - { - endPoint = _singleton.ViFindNextWordEnd(endPoint, _singleton.Options.WordDelimiters); - } - - if (endPoint <= _singleton._current) - { - Ding(); - return; - } - _singleton.SaveToClipboard(_singleton._current, 1 + endPoint - _singleton._current); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - _singleton._current, - DeleteEndOfWord, - arg - )); - _singleton._buffer.Remove(_singleton._current, 1 + endPoint - _singleton._current); - if (_singleton._current >= _singleton._buffer.Length) - { - _singleton._current = Math.Max(0,_singleton._buffer.Length - 1); - } - _singleton.Render(); - } - - /// - /// Delete to the end of the word. - /// - public static void ViDeleteEndOfGlob(ConsoleKeyInfo? key = null, object arg = null) - { - int qty = (arg is int) ? (int)arg : 1; - int endPoint = _singleton._current; - for (int i = 0; i < qty; i++) - { - endPoint = _singleton.ViFindGlobEnd(endPoint); - } - - _singleton.SaveToClipboard(_singleton._current, 1 + endPoint - _singleton._current); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - _singleton._current, - ViDeleteEndOfGlob, - arg - )); - _singleton._buffer.Remove(_singleton._current, 1 + endPoint - _singleton._current); - if (_singleton._current >= _singleton._buffer.Length) - { - _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); - } - _singleton.Render(); - } - - /// - /// Deletes until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViDeleteToChar(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViDeleteToChar(keyChar, key, arg); - } - - private static void ViDeleteToChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: false); - ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViDeleteToChar(keyChar, _key, _arg)); - } - - /// - /// Deletes until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViDeleteToCharBackward(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViDeleteToCharBack(keyChar, key, arg); - } - - private static void ViDeleteToCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViDeleteToCharBack(keyChar, _key, _arg)); - } - - /// - /// Deletes until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViDeleteToBeforeChar(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViDeleteToBeforeChar(keyChar, key, arg); - } - - private static void ViDeleteToBeforeChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: true); - ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViDeleteToBeforeChar(keyChar, _key, _arg)); - } - - /// - /// Deletes until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViDeleteToBeforeCharBackward(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViDeleteToBeforeCharBack(keyChar, key, arg); - } - - private static void ViDeleteToBeforeCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: true); - ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViDeleteToBeforeCharBack(keyChar, _key, _arg)); - } - - /// - /// Ring the bell. - /// - private static void Ding(ConsoleKeyInfo? key = null, object arg = null) - { - Ding(); - } - - /// - /// Switch the current operating mode from Vi-Insert to Vi-Command. - /// - public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._editGroupStart >= 0) - { - _singleton._groupUndoHelper.EndGroup(); - } - _singleton._dispatchTable = _viCmdKeyMap; - _singleton._chordDispatchTable = _viCmdChordTable; - BackwardChar(); - _singleton.PlaceCursor(); - _singleton.ViIndicateCommandMode(); - } - - /// - /// Switch to Insert mode. - /// - public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._dispatchTable = _viInsKeyMap; - _singleton._chordDispatchTable = _viInsChordTable; - _singleton.ViIndicateInsertMode(); - } - - /// - /// Temporarily swap in Vi-Command dispatch tables. Used for setting handlers. - /// - internal static IDisposable UseViCommandModeTables() - { - var oldDispatchTable = _singleton._dispatchTable; - var oldChordDispatchTable = _singleton._chordDispatchTable; - - _singleton._dispatchTable = _viCmdKeyMap; - _singleton._chordDispatchTable = _viCmdChordTable; - - return new Disposable( () => - { - _singleton._dispatchTable = oldDispatchTable; - _singleton._chordDispatchTable = oldChordDispatchTable; - } ); - } - - /// - /// Temporarily swap in Vi-Insert dispatch tables. Used for setting handlers. - /// - internal static IDisposable UseViInsertModeTables() - { - var oldDispatchTable = _singleton._dispatchTable; - var oldChordDispatchTable = _singleton._chordDispatchTable; - - _singleton._dispatchTable = _viInsKeyMap; - _singleton._chordDispatchTable = _viInsChordTable; - - return new Disposable( () => - { - _singleton._dispatchTable = oldDispatchTable; - _singleton._chordDispatchTable = oldChordDispatchTable; - } ); - } - - private void ViIndicateCommandMode() - { - if (_options.ViModeIndicator == ViModeStyle.Cursor) - { - _console.CursorSize = _normalCursorSize < 50 ? 100 : 25; - } - else if (_options.ViModeIndicator == ViModeStyle.Prompt) - { - ConsoleColor savedBackground = _console.BackgroundColor; - _console.BackgroundColor = AlternateBackground(_console.BackgroundColor); - InvokePrompt(); - _console.BackgroundColor = savedBackground; - } - } - - private void ViIndicateInsertMode() - { - if (_options.ViModeIndicator == ViModeStyle.Cursor) - { - _console.CursorSize = _normalCursorSize; - } - else if (_options.ViModeIndicator == ViModeStyle.Prompt) - { - InvokePrompt(); - } - } - - /// - /// Switch to Insert mode and position the cursor at the beginning of the line. - /// - public static void ViInsertAtBegining(ConsoleKeyInfo? key = null, object arg = null) - { - ViInsertMode(key, arg); - BeginningOfLine(key, arg); - } - - /// - /// Switch to Insert mode and position the cursor at the end of the line. - /// - public static void - ViInsertAtEnd(ConsoleKeyInfo? key = null, object arg = null) - { - ViInsertMode(key, arg); - EndOfLine(key, arg); - } - - /// - /// Append from the current line position. - /// - public static void ViInsertWithAppend(ConsoleKeyInfo? key = null, object arg = null) - { - ViInsertMode(key, arg); - ForwardChar(key, arg); - } - - /// - /// Delete the current character and switch to Insert mode. - /// - public static void ViInsertWithDelete(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ViInsertWithDelete, arg); - DeleteChar(key, arg); - ViInsertMode(key, arg); - } - - /// - /// Accept the line and switch to Insert mode. - /// - public static void ViAcceptLine(ConsoleKeyInfo? key = null, object arg = null) - { - ViInsertMode(key, arg); - AcceptLine(key, arg); - } - - /// - /// Prepend a '#' and accept the line. - /// - public static void PrependAndAccept(ConsoleKeyInfo? key = null, object arg = null) - { - BeginningOfLine(key, arg); - SelfInsert(key, arg); - ViAcceptLine(key, arg); - } - - /// - /// Invert the case of the current character and move to the next one. - /// - public static void InvertCase(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._current >= _singleton._buffer.Length) - { - Ding(); - return; - } - - int qty = (arg is int) ? (int) arg : 1; - - for (; qty > 0 && _singleton._current < _singleton._buffer.Length; qty--) - { - char c = _singleton._buffer[_singleton._current]; - if (Char.IsLetter(c)) - { - char newChar = Char.IsUpper(c) ? Char.ToLower(c) : char.ToUpper(c); - EditItem delEditItem = EditItemDelete.Create(c.ToString(), _singleton._current); - EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current); - _singleton.SaveEditItem(GroupedEdit.Create(new List - { - delEditItem, - insEditItem - }, - InvertCase, - arg - )); - - _singleton._buffer[_singleton._current] = newChar; - } - _singleton._current = Math.Min(_singleton._current + 1, _singleton._buffer.Length); - _singleton.PlaceCursor(); - } - _singleton.Render(); - } - - /// - /// Swap the current character and the one before it. - /// - public static void SwapCharacters(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._current <= 0 || _singleton._current >= _singleton._buffer.Length) - { - Ding(); - return; - } - - char current = _singleton._buffer[_singleton._current]; - char previous = _singleton._buffer[_singleton._current - 1]; - - _singleton.StartEditGroup(); - _singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer.ToString(_singleton._current - 1, 2), _singleton._current - 1)); - _singleton.SaveEditItem(EditItemInsertChar.Create(current, _singleton._current - 1)); - _singleton.SaveEditItem(EditItemInsertChar.Create(previous, _singleton._current)); - _singleton.EndEditGroup(); - - _singleton._buffer[_singleton._current] = previous; - _singleton._buffer[_singleton._current - 1] = current; - _singleton._current = Math.Min(_singleton._current + 1, _singleton._buffer.Length - 1); - _singleton.PlaceCursor(); - _singleton.Render(); - } - - /// - /// Deletes text from the cursor to the first non-blank character of the line, - /// - public static void DeleteLineToFirstChar(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._current > 0) - { - int i = 0; - for (; i < _singleton._current; i++) - { - if (!Char.IsWhiteSpace(_singleton._buffer[i])) - { - break; - } - } - - _singleton.SaveToClipboard(i, _singleton._current - i); - _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, i, DeleteLineToFirstChar)); - - _singleton._buffer.Remove(i, _singleton._current - i); - _singleton._current = i; - _singleton.Render(); - } - else - { - Ding(); - } - } - - /// - /// Deletes the current line, enabling undo. - /// - public static void DeleteLine(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._clipboard = _singleton._buffer.ToString(); - _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, 0)); - _singleton._current = 0; - _singleton._buffer.Remove(0, _singleton._buffer.Length); - _singleton.Render(); - } - - /// - /// Deletes the previous word. - /// - public static void BackwardDeleteWord(ConsoleKeyInfo? key = null, object arg = null) - { - int qty = (arg is int) ? (int) arg : 1; - int deletePoint = _singleton._current; - for (int i = 0; i < qty; i++) - { - deletePoint = _singleton.ViFindPreviousWordPoint(deletePoint, _singleton.Options.WordDelimiters); - } - if (deletePoint == _singleton._current) - { - Ding(); - return; - } - _singleton._clipboard = _singleton._buffer.ToString(deletePoint, _singleton._current - deletePoint); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - deletePoint, - BackwardDeleteWord, - arg - )); - _singleton._buffer.Remove(deletePoint, _singleton._current - deletePoint); - _singleton._current = deletePoint; - _singleton.Render(); - } - - /// - /// Deletes the previous word, using only white space as the word delimiter. - /// - public static void ViBackwardDeleteGlob(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._current == 0) - { - Ding(); - return; - } - int qty = (arg is int) ? (int)arg : 1; - int deletePoint = _singleton._current; - for (int i = 0; i < qty && deletePoint > 0; i++) - { - deletePoint = _singleton.ViFindPreviousGlob(deletePoint - 1); - } - if (deletePoint == _singleton._current) - { - Ding(); - return; - } - _singleton._clipboard = _singleton._buffer.ToString(deletePoint, _singleton._current - deletePoint); - _singleton.SaveEditItem(EditItemDelete.Create( - _singleton._clipboard, - deletePoint, - BackwardDeleteWord, - arg - )); - _singleton._buffer.Remove(deletePoint, _singleton._current - deletePoint); - _singleton._current = deletePoint; - _singleton.Render(); - } - - /// - /// Find the matching brace, paren, or square bracket and delete all contents within, including the brace. - /// - public static void ViDeleteBrace(ConsoleKeyInfo? key = null, object arg = null) - { - int newCursor = _singleton.ViFindBrace(_singleton._current); - - if (_singleton._current < newCursor) - { - DeleteRange(_singleton._current, newCursor, ViDeleteBrace); - } - else if (newCursor < _singleton._current) - { - DeleteRange(newCursor, _singleton._current, ViDeleteBrace); - } - else - { - Ding(); - } - } - - /// - /// Delete all characters included in the supplied range. - /// - /// Index of where to begin the delete. - /// Index of where to end the delete. - /// Action that generated this request, used for repeat command ('.'). - private static void DeleteRange(int first, int last, Action action) - { - int length = last - first + 1; - - _singleton.SaveToClipboard(first, length); - _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, first, action)); - _singleton._current = first; - _singleton._buffer.Remove(first, length); - _singleton.Render(); - } - - - /// - /// Prompts for a search string and initiates search upon AcceptLine. - /// - public static void ViSearchHistoryBackward(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue || char.IsControl(key.Value.KeyChar)) - { - Ding(); - return; - } - - _singleton.StartSearch(backward: true); - } - - /// - /// Prompts for a search string and initiates search upon AcceptLine. - /// - public static void SearchForward(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue || char.IsControl(key.Value.KeyChar)) - { - Ding(); - return; - } - - _singleton.StartSearch(backward: false); - } - - /// - /// Repeat the last search in the same direction as before. - /// - public static void RepeatSearch(ConsoleKeyInfo? key = null, object arg = null) - { - if (string.IsNullOrEmpty(_singleton._searchHistoryPrefix)) - { - Ding(); - return; - } - - _singleton.HistorySearch(); - } - - /// - /// Repeat the last search in the same direction as before. - /// - public static void RepeatSearchBackward(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._searchHistoryBackward = !_singleton._searchHistoryBackward; - RepeatSearch(); - _singleton._searchHistoryBackward = !_singleton._searchHistoryBackward; - } - - /// - /// Prompts for a string for history searching. - /// - /// True for searching backward in the history. - private void StartSearch(bool backward) - { - _statusLinePrompt = "find: "; - var argBuffer = _statusBuffer; - Render(); // Render prompt - - while (true) - { - var nextKey = ReadKey(); - if (nextKey.Key == Keys.Enter.Key || nextKey.Key == Keys.Tab.Key) - { - _searchHistoryPrefix = argBuffer.ToString(); - _searchHistoryBackward = backward; - HistorySearch(); - break; - } - if (nextKey.Key == Keys.Escape.Key) - { - break; - } - if (nextKey.Key == Keys.Backspace.Key) - { - if (argBuffer.Length > 0) - { - argBuffer.Remove(argBuffer.Length - 1, 1); - Render(); // Render prompt - continue; - } - break; - } - argBuffer.Append(nextKey.KeyChar); - Render(); // Render prompt - } - - // Remove our status line - argBuffer.Clear(); - _statusLinePrompt = null; - Render(); // Render prompt - } - - /// - /// Searches line history. - /// - private void HistorySearch() - { - _searchHistoryCommandCount++; - - int incr = _searchHistoryBackward ? -1 : +1; - for (int i = _currentHistoryIndex + incr; i >= 0 && i < _history.Count; i += incr) - { - if (Options.HistoryStringComparison.HasFlag(StringComparison.OrdinalIgnoreCase)) - { - if (_history[i]._line.ToLower().Contains(_searchHistoryPrefix.ToLower())) - { - _currentHistoryIndex = i; - UpdateFromHistory(moveCursor: Options.HistorySearchCursorMovesToEnd); - return; - } - } - else - { - if (_history[i]._line.Contains(_searchHistoryPrefix)) - { - _currentHistoryIndex = i; - UpdateFromHistory(moveCursor: Options.HistorySearchCursorMovesToEnd); - return; - } - } - } - - Ding(); - } - - /// - /// Repeat the last text modification. - /// - public static void RepeatLastCommand(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._undoEditIndex > 0) - { - EditItem editItem = _singleton._edits[_singleton._undoEditIndex - 1]; - if (editItem._instigator != null) - { - editItem._instigator(key, editItem._instigatorArg); - return; - } - } - Ding(); - } - - /// - /// Chords in vi needs special handling because a numeric argument can be input between the 1st and 2nd key. - /// - /// - /// - private static void ViChord(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue) - { - throw new ArgumentNullException("key"); - } - if (arg != null) - { - Chord(key, arg); - return; - } - - Dictionary secondKeyDispatchTable; - if (_singleton._chordDispatchTable.TryGetValue(key.Value, out secondKeyDispatchTable)) - { - //if (_singleton._demoMode) - //{ - // // Render so the first key of the chord appears in the demo window - // _singleton.Render(); - //} - var secondKey = ReadKey(); - KeyHandler handler; - if (secondKeyDispatchTable.TryGetValue(secondKey, out handler)) - { - _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); - } - else if (!IsNumberic(secondKey)) - { - _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); - } - else - { - var argBuffer = _singleton._statusBuffer; - argBuffer.Clear(); - _singleton._statusLinePrompt = "digit-argument: "; - while (IsNumberic(secondKey)) - { - argBuffer.Append(secondKey.KeyChar); - _singleton.Render(); - secondKey = ReadKey(); - } - int numericArg = int.Parse(argBuffer.ToString()); - if (secondKeyDispatchTable.TryGetValue(secondKey, out handler)) - { - _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: numericArg); - } - else - { - Ding(); - } - argBuffer.Clear(); - _singleton.ClearStatusMessage(render: true); - } - } - } - - private static bool IsNumberic(ConsoleKeyInfo key) - { - return char.IsNumber(key.KeyChar); - } - - /// - /// Start a new digit argument to pass to other functions while in one of vi's chords. - /// - public static void ViDigitArgumentInChord(ConsoleKeyInfo? key = null, object arg = null) - { - if (!key.HasValue || char.IsControl(key.Value.KeyChar)) - { - Ding(); - return; - } - - #region VI special case - if (_singleton._options.EditMode == EditMode.Vi && key.Value.KeyChar == '0') - { - BeginningOfLine(); - return; - } - #endregion VI special case - - bool sawDigit = false; - _singleton._statusLinePrompt = "digit-argument: "; - var argBuffer = _singleton._statusBuffer; - argBuffer.Append(key.Value.KeyChar); - if (key.Value.KeyChar == '-') - { - argBuffer.Append('1'); - } - else - { - sawDigit = true; - } - - _singleton.Render(); // Render prompt - while (true) - { - var nextKey = ReadKey(); - KeyHandler handler; - if (_singleton._dispatchTable.TryGetValue(nextKey, out handler) && handler.Action == DigitArgument) - { - if (nextKey.KeyChar == '-') - { - if (argBuffer[0] == '-') - { - argBuffer.Remove(0, 1); - } - else - { - argBuffer.Insert(0, '-'); - } - _singleton.Render(); // Render prompt - continue; - } - - if (nextKey.KeyChar >= '0' && nextKey.KeyChar <= '9') - { - if (!sawDigit && argBuffer.Length > 0) - { - // Buffer is either '-1' or '1' from one or more Alt+- keys - // but no digits yet. Remove the '1'. - argBuffer.Length -= 1; - } - sawDigit = true; - argBuffer.Append(nextKey.KeyChar); - _singleton.Render(); // Render prompt - continue; - } - } - - int intArg; - if (int.TryParse(argBuffer.ToString(), out intArg)) - { - _singleton.ProcessOneKey(nextKey, _singleton._dispatchTable, ignoreIfNoAction: false, arg: intArg); - } - else - { - Ding(); - } - break; - } - } - - /// - /// Like DeleteCharOrExit in Emacs mode, but accepts the line instead of deleting a character. - /// - public static void ViAcceptLineOrExit(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._buffer.Length > 0) - { - _singleton.AcceptLineImpl(false); - } - else - { - ViExit(key, arg); - } - } - - /// - /// A new line is inserted above the current line. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ViInsertLine(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ViInsertLine, arg); - _singleton.MoveToBeginningOfPhrase(); - _singleton._buffer.Insert(_singleton._current, '\n'); - //_singleton._current = Math.Max(0, _singleton._current - 1); - _singleton.SaveEditItem(EditItemInsertChar.Create( '\n', _singleton._current)); - _singleton.Render(); - ViInsertMode(); - } - - private void MoveToBeginningOfPhrase() - { - while (!IsAtBeginningOfPhrase()) - { - _current--; - } - } - - private bool IsAtBeginningOfPhrase() - { - if (_current == 0) - { - return true; - } - if (_buffer[_current - 1] == '\n') - { - return true; - } - return false; - } - - /// - /// A new line is inserted below the current line. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ViAppendLine(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ViInsertLine, arg); - _singleton.MoveToEndOfPhrase(); - int insertPoint = 0; - if (_singleton.IsAtEndOfLine(_singleton._current)) - { - insertPoint = _singleton._buffer.Length; - _singleton._buffer.Append('\n'); - _singleton._current = insertPoint; - } - else - { - insertPoint = _singleton._current + 1; - _singleton._buffer.Insert(insertPoint, '\n'); - } - _singleton.SaveEditItem(EditItemInsertChar.Create('\n', insertPoint)); - _singleton.Render(); - ViInsertWithAppend(); - } - - private void MoveToEndOfPhrase() - { - while (!IsAtEndOfPhrase()) - { - _current++; - } - } - - private bool IsAtEndOfPhrase() - { - if (_buffer.Length == 0 || _current == _buffer.Length + ViEndOfLineFactor) - { - return true; - } - if (_buffer[_current] == '\n') - { - return true; - } - return false; - } - - /// - /// Joins 2 lines. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ViJoinLines(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton.MoveToEndOfPhrase(); - if (_singleton.IsAtEndOfLine(_singleton._current)) - { - Ding(); - } - else - { - _singleton._buffer[_singleton._current] = ' '; - _singleton._groupUndoHelper.StartGroup(ViJoinLines, arg); - _singleton.SaveEditItem(EditItemDelete.Create("\n", _singleton._current)); - _singleton.SaveEditItem(EditItemInsertChar.Create(' ', _singleton._current)); - _singleton._groupUndoHelper.EndGroup(); - _singleton.Render(); - } - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/Render.cs b/src/Microsoft.PowerShell.PSReadLine/Render.cs deleted file mode 100644 index 632fdeb9cc3..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/Render.cs +++ /dev/null @@ -1,815 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Management.Automation.Language; -using Microsoft.PowerShell.Internal; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { -#if UNIX - private const ConsoleColor UnknownColor = (ConsoleColor) (-1); -#endif - private BufferChar[] _consoleBuffer; - private int _initialX; - private int _initialY; - private int _bufferWidth; - private ConsoleColor _initialBackgroundColor; - private ConsoleColor _initialForegroundColor; - private BufferChar _space; - private int _current; - private int _emphasisStart; - private int _emphasisLength; - - private class SavedTokenState - { - internal Token[] Tokens { get; set; } - internal int Index { get; set; } - internal ConsoleColor BackgroundColor { get; set; } - internal ConsoleColor ForegroundColor { get; set; } - } - - private void MaybeParseInput() - { - if (_tokens == null) - { - ParseInput(); - } - } - - private string ParseInput() - { - var text = _buffer.ToString(); - _ast = Parser.ParseInput(text, out _tokens, out _parseErrors); - return text; - } - - private void ClearStatusMessage(bool render) - { - _statusBuffer.Clear(); - _statusLinePrompt = null; - _statusIsErrorMessage = false; - if (render) - { - Render(); - } - } - - private void Render() - { - // If there are a bunch of keys queued up, skip rendering if we've rendered - // recently. - if (_queuedKeys.Count > 10 && (_lastRenderTime.ElapsedMilliseconds < 50)) - { - // We won't render, but most likely the tokens will be different, so make - // sure we don't use old tokens. - _tokens = null; - _ast = null; - return; - } - - ReallyRender(); - } - - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults")] - private void ReallyRender() - { - var text = ParseInput(); - - int statusLineCount = GetStatusLineCount(); - int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount); - var backgroundColor = _initialBackgroundColor; - var foregroundColor = _initialForegroundColor; - bool afterLastToken = false; - int totalBytes = j; - int bufferWidth = _console.BufferWidth; - - var tokenStack = new Stack(); - tokenStack.Push(new SavedTokenState - { - Tokens = _tokens, - Index = 0, - BackgroundColor = _initialBackgroundColor, - ForegroundColor = _initialForegroundColor - }); - - int bufferLineCount; - - try - { - _console.StartRender(); - bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount; - if (_consoleBuffer.Length != bufferLineCount * bufferWidth) - { - var newBuffer = new BufferChar[bufferLineCount * bufferWidth]; - Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); - if (_consoleBuffer.Length > bufferLineCount * bufferWidth) - { - int consoleBufferOffset = ConvertOffsetToConsoleBufferOffset(text.Length, _initialX + (Options.ExtraPromptLineCount * _bufferWidth)); - // Need to erase the extra lines that we won't draw again - for (int i = consoleBufferOffset; i < _consoleBuffer.Length; i++) - { - _consoleBuffer[i] = _space; - } - _console.WriteBufferLines(_consoleBuffer, ref _initialY); - } - _consoleBuffer = newBuffer; - } - - for (int i = 0; i < text.Length; i++) - { - totalBytes = totalBytes % bufferWidth; - if (!afterLastToken) - { - // Figure out the color of the character - if it's in a token, - // use the tokens color otherwise use the initial color. - var state = tokenStack.Peek(); - var token = state.Tokens[state.Index]; - - if (i == token.Extent.EndOffset) - { - if (token == state.Tokens[state.Tokens.Length - 1]) - { - tokenStack.Pop(); - if (tokenStack.Count == 0) - { - afterLastToken = true; - token = null; - foregroundColor = _initialForegroundColor; - backgroundColor = _initialBackgroundColor; - } - else - { - state = tokenStack.Peek(); - } - } - - if (!afterLastToken) - { - foregroundColor = state.ForegroundColor; - backgroundColor = state.BackgroundColor; - token = state.Tokens[++state.Index]; - } - } - - if (!afterLastToken && i == token.Extent.StartOffset) - { - GetTokenColors(token, out foregroundColor, out backgroundColor); - - var stringToken = token as StringExpandableToken; - if (stringToken != null) - { - // We might have nested tokens. - if (stringToken.NestedTokens != null && stringToken.NestedTokens.Any()) - { - var tokens = new Token[stringToken.NestedTokens.Count + 1]; - stringToken.NestedTokens.CopyTo(tokens, 0); - // NestedTokens doesn't have an "EOS" token, so we use - // the string literal token for that purpose. - tokens[tokens.Length - 1] = stringToken; - - tokenStack.Push(new SavedTokenState - { - Tokens = tokens, - Index = 0, - BackgroundColor = backgroundColor, - ForegroundColor = foregroundColor - }); - - if (i == tokens[0].Extent.StartOffset) - { - GetTokenColors(tokens[0], out foregroundColor, out backgroundColor); - } - } - } - } - } - - var charToRender = text[i]; - if (charToRender == '\n') - { - while ((j % bufferWidth) != 0) - { - _consoleBuffer[j++] = _space; - } - - for (int k = 0; k < Options.ContinuationPrompt.Length; k++, j++) - { - _consoleBuffer[j].UnicodeChar = Options.ContinuationPrompt[k]; - _consoleBuffer[j].ForegroundColor = Options.ContinuationPromptForegroundColor; - _consoleBuffer[j].BackgroundColor = Options.ContinuationPromptBackgroundColor; - } - } - else - { - int size = LengthInBufferCells(charToRender); - totalBytes += size; - - //if there is no enough space for the character at the edge, fill in spaces at the end and - //put the character to next line. - int filling = totalBytes > bufferWidth ? (totalBytes - bufferWidth) % size : 0; - for (int f = 0; f < filling; f++) - { - _consoleBuffer[j++] = _space; - totalBytes++; - } - - if (char.IsControl(charToRender)) - { - _consoleBuffer[j].UnicodeChar = '^'; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - _consoleBuffer[j].UnicodeChar = (char)('@' + charToRender); - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - - } -#if !UNIX - else if (size > 1) - { - _consoleBuffer[j].UnicodeChar = charToRender; - _consoleBuffer[j].IsLeadByte = true; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - _consoleBuffer[j].UnicodeChar = charToRender; - _consoleBuffer[j].IsTrailByte = true; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - } -#endif - else - { - _consoleBuffer[j].UnicodeChar = charToRender; - MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor); - } - } - } - } - finally - { - _console.EndRender(); - } - - for (; j < (_consoleBuffer.Length - (statusLineCount * _bufferWidth)); j++) - { - _consoleBuffer[j] = _space; - } - - if (_statusLinePrompt != null) - { - foregroundColor = _statusIsErrorMessage ? Options.ErrorForegroundColor : _console.ForegroundColor; - backgroundColor = _statusIsErrorMessage ? Options.ErrorBackgroundColor : _console.BackgroundColor; - - for (int i = 0; i < _statusLinePrompt.Length; i++, j++) - { - _consoleBuffer[j].UnicodeChar = _statusLinePrompt[i]; - _consoleBuffer[j].ForegroundColor = foregroundColor; - _consoleBuffer[j].BackgroundColor = backgroundColor; - } - for (int i = 0; i < _statusBuffer.Length; i++, j++) - { - _consoleBuffer[j].UnicodeChar = _statusBuffer[i]; - _consoleBuffer[j].ForegroundColor = foregroundColor; - _consoleBuffer[j].BackgroundColor = backgroundColor; - } - - for (; j < _consoleBuffer.Length; j++) - { - _consoleBuffer[j] = _space; - } - } - - bool rendered = false; - if (_parseErrors.Length > 0) - { - int promptChar = _initialX - 1 + (_bufferWidth * Options.ExtraPromptLineCount); - - while (promptChar >= 0) - { - var c = _consoleBuffer[promptChar].UnicodeChar; - if (char.IsWhiteSpace(c)) - { - promptChar -= 1; - continue; - } - - ConsoleColor prevColor = _consoleBuffer[promptChar].ForegroundColor; - _consoleBuffer[promptChar].ForegroundColor = ConsoleColor.Red; - _console.WriteBufferLines(_consoleBuffer, ref _initialY); - rendered = true; - _consoleBuffer[promptChar].ForegroundColor = prevColor; - break; - } - } - - if (!rendered) - { - _console.WriteBufferLines(_consoleBuffer, ref _initialY); - } - - PlaceCursor(); - - if ((_initialY + bufferLineCount) > (_console.WindowTop + _console.WindowHeight)) - { -#if !UNIX // TODO: verify this isn't necessary for LINUX - _console.WindowTop = _initialY + bufferLineCount - _console.WindowHeight; -#endif - } - - _lastRenderTime.Restart(); - } - - private int LengthInBufferCells(char c) - { - int length = Char.IsControl(c) ? 1 : 0; - if (c < 256) - { - return length + 1; - } - return _console.LengthInBufferCells(c); - } - - private static void WriteBlankLines(int count, int top) - { - var console = _singleton._console; - var blanks = new BufferChar[count * console.BufferWidth]; - for (int i = 0; i < blanks.Length; i++) - { - blanks[i].BackgroundColor = console.BackgroundColor; - blanks[i].ForegroundColor = console.ForegroundColor; - blanks[i].UnicodeChar = ' '; - } - console.WriteBufferLines(blanks, ref top); - } - - private static BufferChar[] ReadBufferLines(int top, int count) - { - return _singleton._console.ReadBufferLines(top, count); - } - - private void GetTokenColors(Token token, out ConsoleColor foregroundColor, out ConsoleColor backgroundColor) - { - switch (token.Kind) - { - case TokenKind.Comment: - foregroundColor = _options.CommentForegroundColor; - backgroundColor = _options.CommentBackgroundColor; - return; - - case TokenKind.Parameter: - foregroundColor = _options.ParameterForegroundColor; - backgroundColor = _options.ParameterBackgroundColor; - return; - - case TokenKind.Variable: - case TokenKind.SplattedVariable: - foregroundColor = _options.VariableForegroundColor; - backgroundColor = _options.VariableBackgroundColor; - return; - - case TokenKind.StringExpandable: - case TokenKind.StringLiteral: - case TokenKind.HereStringExpandable: - case TokenKind.HereStringLiteral: - foregroundColor = _options.StringForegroundColor; - backgroundColor = _options.StringBackgroundColor; - return; - - case TokenKind.Number: - foregroundColor = _options.NumberForegroundColor; - backgroundColor = _options.NumberBackgroundColor; - return; - } - - if ((token.TokenFlags & TokenFlags.CommandName) != 0) - { - foregroundColor = _options.CommandForegroundColor; - backgroundColor = _options.CommandBackgroundColor; - return; - } - - if ((token.TokenFlags & TokenFlags.Keyword) != 0) - { - foregroundColor = _options.KeywordForegroundColor; - backgroundColor = _options.KeywordBackgroundColor; - return; - } - - if ((token.TokenFlags & (TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.AssignmentOperator)) != 0) - { - foregroundColor = _options.OperatorForegroundColor; - backgroundColor = _options.OperatorBackgroundColor; - return; - } - - if ((token.TokenFlags & TokenFlags.TypeName) != 0) - { - foregroundColor = _options.TypeForegroundColor; - backgroundColor = _options.TypeBackgroundColor; - return; - } - - if ((token.TokenFlags & TokenFlags.MemberName) != 0) - { - foregroundColor = _options.MemberForegroundColor; - backgroundColor = _options.MemberBackgroundColor; - return; - } - - foregroundColor = _options.DefaultTokenForegroundColor; - backgroundColor = _options.DefaultTokenBackgroundColor; - } - - private void GetRegion(out int start, out int length) - { - if (_mark < _current) - { - start = _mark; - length = _current - start; - } - else - { - start = _current; - length = _mark - start; - } - } - - private bool InRegion(int i) - { - int start, end; - if (_mark > _current) - { - start = _current; - end = _mark; - } - else - { - start = _mark; - end = _current; - } - return i >= start && i < end; - } - - private void MaybeEmphasize(ref BufferChar charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor) - { - if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength)) - { - backgroundColor = _options.EmphasisBackgroundColor; - foregroundColor = _options.EmphasisForegroundColor; - } - else if (_visualSelectionCommandCount > 0 && InRegion(i)) - { - // We can't quite emulate real console selection because it inverts - // based on actual screen colors, our palette is limited. The choice - // to invert only the lower 3 bits to change the color is somewhat - // but looks best with the 2 default color schemes - starting PowerShell - // from it's shortcut or from a cmd shortcut. -#if UNIX // TODO: set Inverse attribute and let render choose what to do. - ConsoleColor tempColor = (foregroundColor == UnknownColor) ? ConsoleColor.White : foregroundColor; - foregroundColor = (backgroundColor == UnknownColor) ? ConsoleColor.Black : backgroundColor; - backgroundColor = tempColor; -#else - foregroundColor = (ConsoleColor)((int)foregroundColor ^ 7); - backgroundColor = (ConsoleColor)((int)backgroundColor ^ 7); -#endif - } - - charInfo.ForegroundColor = foregroundColor; - charInfo.BackgroundColor = backgroundColor; - } - - private void PlaceCursor(int x, ref int y) - { - int statusLineCount = GetStatusLineCount(); - if ((y + statusLineCount) >= _console.BufferHeight) - { - _console.ScrollBuffer((y + statusLineCount) - _console.BufferHeight + 1); - y = _console.BufferHeight - 1; - } - _console.SetCursorPosition(x, y); - } - - private void PlaceCursor() - { - var coordinates = ConvertOffsetToCoordinates(_current); - int y = coordinates.Y; - PlaceCursor(coordinates.X, ref y); - } - - private COORD ConvertOffsetToCoordinates(int offset) - { - int x = _initialX; - int y = _initialY + Options.ExtraPromptLineCount; - - int bufferWidth = _console.BufferWidth; - var continuationPromptLength = Options.ContinuationPrompt.Length; - - for (int i = 0; i < offset; i++) - { - char c = _buffer[i]; - if (c == '\n') - { - y += 1; - x = continuationPromptLength; - } - else - { - int size = LengthInBufferCells(c); - x += size; - // Wrap? No prompt when wrapping - if (x >= bufferWidth) - { - int offsize = x - bufferWidth; - if (offsize % size == 0) - { - x -= bufferWidth; - } - else - { - x = size; - } - y += 1; - } - } - } - - //if the next character has bigger size than the remain space on this line, - //the cursor goes to next line where the next character is. - if (_buffer.Length > offset) - { - int size = LengthInBufferCells(_buffer[offset]); - // next one is Wrapped to next line - if (x + size > bufferWidth && (x + size - bufferWidth) % size != 0) - { - x = 0; - y++; - } - } - - return new COORD {X = (short)x, Y = (short)y}; - } - - private int ConvertOffsetToConsoleBufferOffset(int offset, int startIndex) - { - int j = startIndex; - for (int i = 0; i < offset; i++) - { - var c = _buffer[i]; - if (c == '\n') - { - for (int k = 0; k < Options.ContinuationPrompt.Length; k++) - { - j++; - } - } - else if (LengthInBufferCells(c) > 1) - { - j += 2; - } - else - { - j++; - } - } - return j; - } - - private int ConvertLineAndColumnToOffset(COORD coord) - { - int offset; - int x = _initialX; - int y = _initialY + Options.ExtraPromptLineCount; - - int bufferWidth = _console.BufferWidth; - var continuationPromptLength = Options.ContinuationPrompt.Length; - for (offset = 0; offset < _buffer.Length; offset++) - { - // If we are on the correct line, return when we find - // the correct column - if (coord.Y == y && coord.X <= x) - { - return offset; - } - char c = _buffer[offset]; - if (c == '\n') - { - // If we are about to move off of the correct line, - // the line was shorter than the column we wanted so return. - if (coord.Y == y) - { - return offset; - } - y += 1; - x = continuationPromptLength; - } - else - { - int size = LengthInBufferCells(c); - x += size; - // Wrap? No prompt when wrapping - if (x >= bufferWidth) - { - int offsize = x - bufferWidth; - if (offsize % size == 0) - { - x -= bufferWidth; - } - else - { - x = size; - } - y += 1; - } - } - } - - // Return -1 if y is out of range, otherwise the last line was shorter - // than we wanted, but still in range so just return the last offset. - return (coord.Y == y) ? offset : -1; - } - - private bool LineIsMultiLine() - { - for (int i = 0; i < _buffer.Length; i++) - { - if (_buffer[i] == '\n') - return true; - } - return false; - } - - private int GetStatusLineCount() - { - if (_statusLinePrompt == null) - return 0; - - return (_statusLinePrompt.Length + _statusBuffer.Length) / _console.BufferWidth + 1; - } - - [ExcludeFromCodeCoverage] - void IPSConsoleReadLineMockableMethods.Ding() - { - switch (Options.BellStyle) - { - case BellStyle.None: - break; - case BellStyle.Audible: -#if UNIX - Console.Beep(); -#else - Console.Beep(Options.DingTone, Options.DingDuration); -#endif - break; - case BellStyle.Visual: - // TODO: flash prompt? command line? - break; - } - } - - /// - /// Notify the user based on their preference for notification. - /// - public static void Ding() - { - _singleton._mockableMethods.Ding(); - } - - private bool PromptYesOrNo(string s) - { - _statusLinePrompt = s; - Render(); - - var key = ReadKey(); - - _statusLinePrompt = null; - Render(); - return key.Key == ConsoleKey.Y; - } - -#region Screen scrolling - -#if !UNIX - /// - /// Scroll the display up one screen. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayUp(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - TryGetArgAsInt(arg, out numericArg, +1); - var console = _singleton._console; - var newTop = console.WindowTop - (numericArg * console.WindowHeight); - if (newTop < 0) - { - newTop = 0; - } - console.SetWindowPosition(0, newTop); - } - - /// - /// Scroll the display up one line. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayUpLine(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - TryGetArgAsInt(arg, out numericArg, +1); - var console = _singleton._console; - var newTop = console.WindowTop - numericArg; - if (newTop < 0) - { - newTop = 0; - } - console.SetWindowPosition(0, newTop); - } - - /// - /// Scroll the display down one screen. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayDown(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - TryGetArgAsInt(arg, out numericArg, +1); - var console = _singleton._console; - var newTop = console.WindowTop + (numericArg * console.WindowHeight); - if (newTop > (console.BufferHeight - console.WindowHeight)) - { - newTop = (console.BufferHeight - console.WindowHeight); - } - console.SetWindowPosition(0, newTop); - } - - /// - /// Scroll the display down one line. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayDownLine(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - TryGetArgAsInt(arg, out numericArg, +1); - var console = _singleton._console; - var newTop = console.WindowTop + numericArg; - if (newTop > (console.BufferHeight - console.WindowHeight)) - { - newTop = (console.BufferHeight - console.WindowHeight); - } - console.SetWindowPosition(0, newTop); - } - - /// - /// Scroll the display to the top. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayTop(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._console.SetWindowPosition(0, 0); - } - - /// - /// Scroll the display to the cursor. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void ScrollDisplayToCursor(ConsoleKeyInfo? key = null, object arg = null) - { - // Ideally, we'll put the last input line at the bottom of the window - var coordinates = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length); - - var console = _singleton._console; - var newTop = coordinates.Y - console.WindowHeight + 1; - - // If the cursor is already visible, and we're on the first - // page-worth of the buffer, then just scroll to the top (we can't - // scroll to before the beginning of the buffer). - // - // Note that we don't want to just return, because the window may - // have been scrolled way past the end of the content, so we really - // do need to set the new window top to 0 to bring it back into - // view. - if (newTop < 0) - { - newTop = 0; - } - - // But if the cursor won't be visible, make sure it is. - if (newTop > console.CursorTop) - { - // Add 10 for some extra context instead of putting the - // cursor on the bottom line. - newTop = console.CursorTop - console.WindowHeight + 10; - } - - // But we can't go past the end of the buffer. - if (newTop > (console.BufferHeight - console.WindowHeight)) - { - newTop = (console.BufferHeight - console.WindowHeight); - } - console.SetWindowPosition(0, newTop); - } - -#endif -#endregion Screen scrolling - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/Replace.vi.cs b/src/Microsoft.PowerShell.PSReadLine/Replace.vi.cs deleted file mode 100644 index c4960e5b703..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/Replace.vi.cs +++ /dev/null @@ -1,350 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Language; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private static void ViReplaceUntilEsc(ConsoleKeyInfo? key, object arg) - { - if (_singleton._current >= _singleton._buffer.Length) - { - Ding(); - return; - } - - int startingCursor = _singleton._current; - int maxDeleteLength = _singleton._buffer.Length - _singleton._current; - StringBuilder deletedStr = new StringBuilder(); - - ConsoleKeyInfo nextKey = ReadKey(); - while (nextKey.Key != ConsoleKey.Escape && nextKey.Key != ConsoleKey.Enter) - { - if (nextKey.Key != ConsoleKey.Backspace && nextKey.KeyChar != '\u0000') - { - if (_singleton._current >= _singleton._buffer.Length) - { - _singleton._buffer.Append(nextKey.KeyChar); - } - else - { - deletedStr.Append(_singleton._buffer[_singleton._current]); - _singleton._buffer[_singleton._current] = nextKey.KeyChar; - } - _singleton._current++; - _singleton.Render(); - } - if (nextKey.Key == ConsoleKey.Backspace) - { - if (_singleton._current == startingCursor) - { - Ding(); - } - else - { - if (deletedStr.Length == _singleton._current - startingCursor) - { - _singleton._buffer[_singleton._current - 1] = deletedStr[deletedStr.Length - 1]; - deletedStr.Remove(deletedStr.Length - 1, 1); - } - else - { - _singleton._buffer.Remove(_singleton._current - 1, 1); - } - _singleton._current--; - _singleton.Render(); - } - } - nextKey = ReadKey(); - } - - if (_singleton._current > startingCursor) - { - _singleton.StartEditGroup(); - string insStr = _singleton._buffer.ToString(startingCursor, _singleton._current - startingCursor); - _singleton.SaveEditItem(EditItemDelete.Create(deletedStr.ToString(), startingCursor)); - _singleton.SaveEditItem(EditItemInsertString.Create(insStr, startingCursor)); - _singleton.EndEditGroup(); - } - - if (nextKey.Key == ConsoleKey.Enter) - { - ViAcceptLine(nextKey); - } - } - - private static void ViReplaceBrace(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceBrace, arg); - ViDeleteBrace(key, arg); - ViInsertMode(key, arg); - } - - private static void ViBackwardReplaceLineToFirstChar(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViBackwardReplaceLineToFirstChar, arg); - DeleteLineToFirstChar(key, arg); - ViInsertMode(key, arg); - } - - private static void ViBackwardReplaceLine(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViBackwardReplaceLine, arg); - BackwardDeleteLine(key, arg); - ViInsertMode(key, arg); - } - - private static void BackwardReplaceChar(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(BackwardReplaceChar, arg); - BackwardDeleteChar(key, arg); - ViInsertMode(key, arg); - } - - private static void ViBackwardReplaceWord(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViBackwardReplaceWord, arg); - BackwardDeleteWord(key, arg); - ViInsertMode(key, arg); - } - - private static void ViBackwardReplaceGlob(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViBackwardReplaceGlob, arg); - ViBackwardDeleteGlob(key, arg); - ViInsertMode(key, arg); - } - - private static void ViReplaceToEnd(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceToEnd, arg); - DeleteToEnd(key, arg); - _singleton._current = Math.Min(_singleton._buffer.Length, _singleton._current + 1); - _singleton.PlaceCursor(); - ViInsertMode(key, arg); - } - - private static void ViReplaceLine(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceLine, arg); - DeleteLine(key, arg); - ViInsertMode(key, arg); - } - - private static void ViReplaceWord(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceWord, arg); - _singleton._lastWordDelimiter = char.MinValue; - _singleton._shouldAppend = false; - DeleteWord(key, arg); - if (_singleton._current < _singleton._buffer.Length - 1) - { - if (char.IsWhiteSpace(_singleton._lastWordDelimiter)) - { - Insert(_singleton._lastWordDelimiter); - _singleton._current--; - } - _singleton._lastWordDelimiter = char.MinValue; - _singleton.PlaceCursor(); - } - if (_singleton._current == _singleton._buffer.Length - 1 - && !_singleton.IsDelimiter(_singleton._lastWordDelimiter, _singleton.Options.WordDelimiters) - && _singleton._shouldAppend) - { - ViInsertWithAppend(key, arg); - } - else - { - ViInsertMode(key, arg); - } - } - - private static void ViReplaceGlob(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceGlob, arg); - ViDeleteGlob(key, arg); - if (_singleton._current < _singleton._buffer.Length - 1) - { - Insert(' '); - _singleton._current--; - _singleton.PlaceCursor(); - } - if (_singleton._current == _singleton._buffer.Length - 1) - { - ViInsertWithAppend(key, arg); - } - else - { - ViInsertMode(key, arg); - } - } - - private static void ViReplaceEndOfWord(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceEndOfWord, arg); - DeleteEndOfWord(key, arg); - if (_singleton._current == _singleton._buffer.Length - 1) - { - ViInsertWithAppend(key, arg); - } - else - { - ViInsertMode(key, arg); - } - } - - private static void ViReplaceEndOfGlob(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ViReplaceEndOfGlob, arg); - ViDeleteEndOfGlob(key, arg); - if (_singleton._current == _singleton._buffer.Length - 1) - { - ViInsertWithAppend(key, arg); - } - else - { - ViInsertMode(key, arg); - } - } - - private static void ReplaceChar(ConsoleKeyInfo? key, object arg) - { - _singleton._groupUndoHelper.StartGroup(ReplaceChar, arg); - DeleteChar(key, arg); - ViInsertMode(key, arg); - } - - /// - /// Replaces the current character with the next character typed. - /// - private static void ReplaceCharInPlace(ConsoleKeyInfo? key, object arg) - { - ConsoleKeyInfo nextKey = ReadKey(); - if (_singleton._buffer.Length > 0 && nextKey.KeyChar > 0 && nextKey.Key != ConsoleKey.Escape && nextKey.Key != ConsoleKey.Enter) - { - _singleton.StartEditGroup(); - _singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer[_singleton._current].ToString(), _singleton._current)); - _singleton.SaveEditItem(EditItemInsertString.Create(nextKey.KeyChar.ToString(), _singleton._current)); - _singleton.EndEditGroup(); - - _singleton._buffer[_singleton._current] = nextKey.KeyChar; - _singleton.Render(); - } - else - { - Ding(); - } - } - - /// - /// Deletes until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViReplaceToChar(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViReplaceToChar(keyChar, key, arg); - } - - private static void ViReplaceToChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - bool shouldAppend = _singleton._current > 0; - - _singleton._groupUndoHelper.StartGroup(ReplaceChar, arg); - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: false); - if (ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViReplaceToChar(keyChar, _key, _arg))) - { - if (shouldAppend) - { - ViInsertWithAppend(key, arg); - } - else - { - ViInsertMode(key, arg); - } - } - } - - /// - /// Replaces until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViReplaceToCharBackward(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViReplaceToCharBack(keyChar, key, arg); - } - - private static void ViReplaceToCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ReplaceChar, arg); - if (ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViReplaceToCharBack(keyChar, _key, _arg))) - { - ViInsertMode(key, arg); - } - } - - /// - /// Replaces until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViReplaceToBeforeChar(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViReplaceToBeforeChar(keyChar, key, arg); - } - - private static void ViReplaceToBeforeChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ReplaceChar, arg); - ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: true); - if (ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViReplaceToBeforeChar(keyChar, _key, _arg))) - { - ViInsertMode(key, arg); - } - } - - /// - /// Replaces until given character - /// - /// - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - private static void ViReplaceToBeforeCharBackward(ConsoleKeyInfo? key = null, object arg = null) - { - var keyChar = ReadKey().KeyChar; - ViReplaceToBeforeCharBack(keyChar, key, arg); - } - - private static void ViReplaceToBeforeCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) - { - _singleton._groupUndoHelper.StartGroup(ReplaceChar, arg); - if (ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViReplaceToBeforeCharBack(keyChar, _key, _arg))) - { - ViInsertMode(key, arg); - } - } - - - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/SamplePSReadlineProfile.ps1 b/src/Microsoft.PowerShell.PSReadLine/SamplePSReadlineProfile.ps1 deleted file mode 100644 index cfde8a0c817..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/SamplePSReadlineProfile.ps1 +++ /dev/null @@ -1,491 +0,0 @@ - -# This is an example profile for PSReadline. -# -# This is roughly what I use so there is some emphasis on emacs bindings, -# but most of these bindings make sense in Windows mode as well. - -Import-Module PSReadLine - -Set-PSReadLineOption -EditMode Emacs - -# Searching for commands with up/down arrow is really handy. The -# option "moves to end" is useful if you want the cursor at the end -# of the line while cycling through history like it does w/o searching, -# without that option, the cursor will remain at the position it was -# when you used up arrow, which can be useful if you forget the exact -# string you started the search on. -Set-PSReadLineOption -HistorySearchCursorMovesToEnd -Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward -Set-PSReadlineKeyHandler -Key DownArrow -Function HistorySearchForward - -# This key handler shows the entire or filtered history using Out-GridView. The -# typed text is used as the substring pattern for filtering. A selected command -# is inserted to the command line without invoking. Multiple command selection -# is supported, e.g. selected by Ctrl + Click. -Set-PSReadlineKeyHandler -Key F7 ` - -BriefDescription History ` - -LongDescription 'Show command history' ` - -ScriptBlock { - $pattern = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$pattern, [ref]$null) - if ($pattern) - { - $pattern = [regex]::Escape($pattern) - } - - $history = [System.Collections.ArrayList]@( - $last = '' - $lines = '' - foreach ($line in [System.IO.File]::ReadLines((Get-PSReadlineOption).HistorySavePath)) - { - if ($line.EndsWith('`')) - { - $line = $line.Substring(0, $line.Length - 1) - $lines = if ($lines) - { - "$lines`n$line" - } - else - { - $line - } - continue - } - - if ($lines) - { - $line = "$lines`n$line" - $lines = '' - } - - if (($line -cne $last) -and (!$pattern -or ($line -match $pattern))) - { - $last = $line - $line - } - } - ) - $history.Reverse() - - $command = $history | Out-GridView -Title History -PassThru - if ($command) - { - [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() - [Microsoft.PowerShell.PSConsoleReadLine]::Insert(($command -join "`n")) - } -} - -# This is an example of a macro that you might use to execute a command. -# This will add the command to history. -Set-PSReadlineKeyHandler -Key Ctrl+B ` - -BriefDescription BuildCurrentDirectory ` - -LongDescription "Build the current directory" ` - -ScriptBlock { - [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("msbuild") - [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() -} - -# In Emacs mode - Tab acts like in bash, but the Windows style completion -# is still useful sometimes, so bind some keys so we can do both -Set-PSReadlineKeyHandler -Key Ctrl+Q -Function TabCompleteNext -Set-PSReadlineKeyHandler -Key Ctrl+Shift+Q -Function TabCompletePrevious - -# Clipboard interaction is bound by default in Windows mode, but not Emacs mode. -Set-PSReadlineKeyHandler -Key Shift+Ctrl+C -Function Copy -Set-PSReadlineKeyHandler -Key Ctrl+V -Function Paste - -# CaptureScreen is good for blog posts or email showing a transaction -# of what you did when asking for help or demonstrating a technique. -Set-PSReadlineKeyHandler -Chord 'Ctrl+D,Ctrl+C' -Function CaptureScreen - -# The built-in word movement uses character delimiters, but token based word -# movement is also very useful - these are the bindings you'd use if you -# prefer the token based movements bound to the normal emacs word movement -# key bindings. -Set-PSReadlineKeyHandler -Key Alt+D -Function ShellKillWord -Set-PSReadlineKeyHandler -Key Alt+Backspace -Function ShellBackwardKillWord -Set-PSReadlineKeyHandler -Key Alt+B -Function ShellBackwardWord -Set-PSReadlineKeyHandler -Key Alt+F -Function ShellForwardWord -Set-PSReadlineKeyHandler -Key Shift+Alt+B -Function SelectShellBackwardWord -Set-PSReadlineKeyHandler -Key Shift+Alt+F -Function SelectShellForwardWord - -#region Smart Insert/Delete - -# The next four key handlers are designed to make entering matched quotes -# parens, and braces a nicer experience. I'd like to include functions -# in the module that do this, but this implementation still isn't as smart -# as ReSharper, so I'm just providing it as a sample. - -Set-PSReadlineKeyHandler -Key '"',"'" ` - -BriefDescription SmartInsertQuote ` - -LongDescription "Insert paired quotes if not already on a quote" ` - -ScriptBlock { - param($key, $arg) - - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - - if ($line[$cursor] -eq $key.KeyChar) { - # Just move the cursor - [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) - } - else { - # Insert matching quotes, move cursor to be in between the quotes - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)" * 2) - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1) - } -} - -Set-PSReadlineKeyHandler -Key '(','{','[' ` - -BriefDescription InsertPairedBraces ` - -LongDescription "Insert matching braces" ` - -ScriptBlock { - param($key, $arg) - - $closeChar = switch ($key.KeyChar) - { - <#case#> '(' { [char]')'; break } - <#case#> '{' { [char]'}'; break } - <#case#> '[' { [char]']'; break } - } - - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar") - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1) -} - -Set-PSReadlineKeyHandler -Key ')',']','}' ` - -BriefDescription SmartCloseBraces ` - -LongDescription "Insert closing brace or skip" ` - -ScriptBlock { - param($key, $arg) - - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - - if ($line[$cursor] -eq $key.KeyChar) - { - [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) - } - else - { - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)") - } -} - -Set-PSReadlineKeyHandler -Key Backspace ` - -BriefDescription SmartBackspace ` - -LongDescription "Delete previous character or matching quotes/parens/braces" ` - -ScriptBlock { - param($key, $arg) - - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - - if ($cursor -gt 0) - { - $toMatch = $null - if ($cursor -lt $line.Length) - { - switch ($line[$cursor]) - { - <#case#> '"' { $toMatch = '"'; break } - <#case#> "'" { $toMatch = "'"; break } - <#case#> ')' { $toMatch = '('; break } - <#case#> ']' { $toMatch = '['; break } - <#case#> '}' { $toMatch = '{'; break } - } - } - - if ($null -ne $toMatch -and $line[$cursor-1] -eq $toMatch) - { - [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2) - } - else - { - [Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg) - } - } -} - -#endregion Smart Insert/Delete - -# Sometimes you enter a command but realize you forgot to do something else first. -# This binding will let you save that command in the history so you can recall it, -# but it doesn't actually execute. It also clears the line with RevertLine so the -# undo stack is reset - though redo will still reconstruct the command line. -Set-PSReadlineKeyHandler -Key Alt+w ` - -BriefDescription SaveInHistory ` - -LongDescription "Save current line in history but do not execute" ` - -ScriptBlock { - param($key, $arg) - - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - [Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory($line) - [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() -} - -# Insert text from the clipboard as a here string -Set-PSReadlineKeyHandler -Key Ctrl+Shift+v ` - -BriefDescription PasteAsHereString ` - -LongDescription "Paste the clipboard text as a here string" ` - -ScriptBlock { - param($key, $arg) - - Add-Type -Assembly PresentationCore - if ([System.Windows.Clipboard]::ContainsText()) - { - # Get clipboard text - remove trailing spaces, convert \r\n to \n, and remove the final \n. - $text = ([System.Windows.Clipboard]::GetText() -replace "\p{Zs}*`r?`n","`n").TrimEnd() - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("@'`n$text`n'@") - } - else - { - [Microsoft.PowerShell.PSConsoleReadLine]::Ding() - } -} - -# Sometimes you want to get a property of invoke a member on what you've entered so far -# but you need parens to do that. This binding will help by putting parens around the current selection, -# or if nothing is selected, the whole line. -Set-PSReadlineKeyHandler -Key 'Alt+(' ` - -BriefDescription ParenthesizeSelection ` - -LongDescription "Put parenthesis around the selection or entire line and move the cursor to after the closing parenthesis" ` - -ScriptBlock { - param($key, $arg) - - $selectionStart = $null - $selectionLength = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength) - - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - if ($selectionStart -ne -1) - { - [Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, '(' + $line.SubString($selectionStart, $selectionLength) + ')') - [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) - } - else - { - [Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')') - [Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine() - } -} - -# Each time you press Alt+', this key handler will change the token -# under or before the cursor. It will cycle through single quotes, double quotes, or -# no quotes each time it is invoked. -Set-PSReadlineKeyHandler -Key "Alt+'" ` - -BriefDescription ToggleQuoteArgument ` - -LongDescription "Toggle quotes on the argument under the cursor" ` - -ScriptBlock { - param($key, $arg) - - $ast = $null - $tokens = $null - $errors = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor) - - $tokenToChange = $null - foreach ($token in $tokens) - { - $extent = $token.Extent - if ($extent.StartOffset -le $cursor -and $extent.EndOffset -ge $cursor) - { - $tokenToChange = $token - - # If the cursor is at the end (it's really 1 past the end) of the previous token, - # we only want to change the previous token if there is no token under the cursor - if ($extent.EndOffset -eq $cursor -and $foreach.MoveNext()) - { - $nextToken = $foreach.Current - if ($nextToken.Extent.StartOffset -eq $cursor) - { - $tokenToChange = $nextToken - } - } - break - } - } - - if ($null -ne $tokenToChange) - { - $extent = $tokenToChange.Extent - $tokenText = $extent.Text - if ($tokenText[0] -eq '"' -and $tokenText[-1] -eq '"') - { - # Switch to no quotes - $replacement = $tokenText.Substring(1, $tokenText.Length - 2) - } - elseif ($tokenText[0] -eq "'" -and $tokenText[-1] -eq "'") - { - # Switch to double quotes - $replacement = '"' + $tokenText.Substring(1, $tokenText.Length - 2) + '"' - } - else - { - # Add single quotes - $replacement = "'" + $tokenText + "'" - } - - [Microsoft.PowerShell.PSConsoleReadLine]::Replace( - $extent.StartOffset, - $tokenText.Length, - $replacement) - } -} - -# This example will replace any aliases on the command line with the resolved commands. -Set-PSReadlineKeyHandler -Key "Alt+%" ` - -BriefDescription ExpandAliases ` - -LongDescription "Replace all aliases with the full command" ` - -ScriptBlock { - param($key, $arg) - - $ast = $null - $tokens = $null - $errors = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor) - - $startAdjustment = 0 - foreach ($token in $tokens) - { - if ($token.TokenFlags -band [System.Management.Automation.Language.TokenFlags]::CommandName) - { - $alias = $ExecutionContext.InvokeCommand.GetCommand($token.Extent.Text, 'Alias') - if ($null -ne $alias) - { - $resolvedCommand = $alias.ResolvedCommandName - if ($null -ne $resolvedCommand) - { - $extent = $token.Extent - $length = $extent.EndOffset - $extent.StartOffset - [Microsoft.PowerShell.PSConsoleReadLine]::Replace( - $extent.StartOffset + $startAdjustment, - $length, - $resolvedCommand) - - # Our copy of the tokens won't have been updated, so we need to - # adjust by the difference in length - $startAdjustment += ($resolvedCommand.Length - $length) - } - } - } - } -} - -# F1 for help on the command line - naturally -Set-PSReadlineKeyHandler -Key F1 ` - -BriefDescription CommandHelp ` - -LongDescription "Open the help window for the current command" ` - -ScriptBlock { - param($key, $arg) - - $ast = $null - $tokens = $null - $errors = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor) - - $commandAst = $ast.FindAll( { - $node = $args[0] - $node -is [System.Management.Automation.Language.CommandAst] -and - $node.Extent.StartOffset -le $cursor -and - $node.Extent.EndOffset -ge $cursor - }, $true) | Select-Object -Last 1 - - if ($null -ne $commandAst) - { - $commandName = $commandAst.GetCommandName() - if ($null -ne $commandName) - { - $command = $ExecutionContext.InvokeCommand.GetCommand($commandName, 'All') - if ($command -is [System.Management.Automation.AliasInfo]) - { - $commandName = $command.ResolvedCommandName - } - - if ($null -ne $commandName) - { - Get-Help $commandName -ShowWindow - } - } - } -} - - -# -# Ctrl+Shift+j then type a key to mark the current directory. -# Ctrj+j then the same key will change back to that directory without -# needing to type cd and won't change the command line. - -# -$global:PSReadlineMarks = @{} - -Set-PSReadlineKeyHandler -Key Ctrl+Shift+j ` - -BriefDescription MarkDirectory ` - -LongDescription "Mark the current directory" ` - -ScriptBlock { - param($key, $arg) - - $key = [Console]::ReadKey($true) - $global:PSReadlineMarks[$key.KeyChar] = $pwd -} - -Set-PSReadlineKeyHandler -Key Ctrl+j ` - -BriefDescription JumpDirectory ` - -LongDescription "Goto the marked directory" ` - -ScriptBlock { - param($key, $arg) - - $key = [Console]::ReadKey() - $dir = $global:PSReadlineMarks[$key.KeyChar] - if ($dir) - { - cd $dir - [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() - } -} - -Set-PSReadlineKeyHandler -Key Alt+j ` - -BriefDescription ShowDirectoryMarks ` - -LongDescription "Show the currently marked directories" ` - -ScriptBlock { - param($key, $arg) - - $global:PSReadlineMarks.GetEnumerator() | ForEach-Object { - [PSCustomObject]@{Key = $_.Key; Dir = $_.Value} } | - Format-Table -AutoSize | Out-Host - - [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() -} - -Set-PSReadlineOption -CommandValidationHandler { - param([System.Management.Automation.Language.CommandAst]$CommandAst) - - switch ($CommandAst.GetCommandName()) - { - 'git' { - $gitCmd = $CommandAst.CommandElements[1].Extent - switch ($gitCmd.Text) - { - 'cmt' { - [Microsoft.PowerShell.PSConsoleReadLine]::Replace( - $gitCmd.StartOffset, $gitCmd.EndOffset - $gitCmd.StartOffset, 'commit') - } - } - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/ScreenCapture.cs b/src/Microsoft.PowerShell.PSReadLine/ScreenCapture.cs deleted file mode 100644 index 4404c4866b4..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/ScreenCapture.cs +++ /dev/null @@ -1,309 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -#if !UNIX -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.PowerShell.Internal; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private static void InvertLines(int start, int count) - { - var buffer = ReadBufferLines(start, count); - for (int i = 0; i < buffer.Length; i++) - { - buffer[i].ForegroundColor = (ConsoleColor)((int)buffer[i].ForegroundColor ^ 7); - buffer[i].BackgroundColor = (ConsoleColor)((int)buffer[i].BackgroundColor ^ 7); - } - _singleton._console.WriteBufferLines(buffer, ref start, false); - } - - /// - /// Start interactive screen capture - up/down arrows select lines, enter copies - /// selected text to clipboard as text and html - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void CaptureScreen(ConsoleKeyInfo? key = null, object arg = null) - { - int selectionTop = _singleton._console.CursorTop; - int selectionHeight = 1; - int currentY = selectionTop; - Internal.IConsole console = _singleton._console; - - // We'll keep the current selection line (currentY) at least 4 lines - // away from the top or bottom of the window. - const int margin = 5; - Func tooCloseToTop = () => { return (currentY - console.WindowTop) < margin; }; - Func tooCloseToBottom = () => { return ((console.WindowTop + console.WindowHeight) - currentY) < margin; }; - - // Current lines starts out selected - InvertLines(selectionTop, selectionHeight); - bool done = false; - while (!done) - { - var k = ReadKey(); - switch (k.Key) - { - case ConsoleKey.K: - case ConsoleKey.UpArrow: - if (tooCloseToTop()) - ScrollDisplayUpLine(); - - if (currentY > 0) - { - currentY -= 1; - if ((k.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) - { - if (currentY < selectionTop) - { - // Extend selection up, only invert newly selected line. - InvertLines(currentY, 1); - selectionTop = currentY; - selectionHeight += 1; - } - else if (currentY >= selectionTop) - { - // Selection shortend 1 line, invert unselected line. - InvertLines(currentY + 1, 1); - selectionHeight -= 1; - } - break; - } - goto updateSelectionCommon; - } - break; - - case ConsoleKey.J: - case ConsoleKey.DownArrow: - if (tooCloseToBottom()) - ScrollDisplayDownLine(); - - if (currentY < (console.BufferHeight - 1)) - { - currentY += 1; - if ((k.Modifiers & ConsoleModifiers.Shift) == ConsoleModifiers.Shift) - { - if (currentY == (selectionTop + selectionHeight)) - { - // Extend selection down, only invert newly selected line. - InvertLines(selectionTop + selectionHeight, 1); - selectionHeight += 1; - } - else if (currentY == (selectionTop + 1)) - { - // Selection shortend 1 line, invert unselected line. - InvertLines(selectionTop, 1); - selectionTop = currentY; - selectionHeight -= 1; - } - break; - } - goto updateSelectionCommon; - } - break; - - updateSelectionCommon: - // Shift not pressed - unselect current selection - InvertLines(selectionTop, selectionHeight); - selectionTop = currentY; - selectionHeight = 1; - InvertLines(selectionTop, selectionHeight); - break; - - case ConsoleKey.Enter: - InvertLines(selectionTop, selectionHeight); - DumpScreenToClipboard(selectionTop, selectionHeight); - ScrollDisplayToCursor(); - return; - - case ConsoleKey.Escape: - done = true; - continue; - - case ConsoleKey.C: - case ConsoleKey.G: - if (k.Modifiers == ConsoleModifiers.Control) - { - done = true; - continue; - } - Ding(); - break; - default: - Ding(); - break; - } - } - InvertLines(selectionTop, selectionHeight); - ScrollDisplayToCursor(); - } - - private const string CmdColorTable = @" -\red0\green0\blue0; -\red0\green0\blue128; -\red0\green128\blue0; -\red0\green128\blue128; -\red128\green0\blue0; -\red128\green0\blue128; -\red128\green128\blue0; -\red192\green192\blue192; -\red128\green128\blue128; -\red0\green0\blue255; -\red0\green255\blue0; -\red0\green255\blue255; -\red255\green0\blue0; -\red255\green0\blue255; -\red255\green255\blue0; -\red255\green255\blue255; -"; - - private const string PowerShellColorTable = @" -\red1\green36\blue86; -\red0\green0\blue128; -\red0\green128\blue0; -\red0\green128\blue128; -\red128\green0\blue0; -\red1\green36\blue86; -\red238\green237\blue240; -\red192\green192\blue192; -\red128\green128\blue128; -\red0\green0\blue255; -\red0\green255\blue0; -\red0\green255\blue255; -\red255\green0\blue0; -\red255\green0\blue255; -\red255\green255\blue0; -\red255\green255\blue255; -"; - - private static string GetRTFColorFromColorRef(NativeMethods.COLORREF colorref) - { - return string.Concat("\\red", colorref.R.ToString("D"), - "\\green", colorref.G.ToString("D"), - "\\blue", colorref.B.ToString("D"), ";"); - } - - private static string GetColorTable() - { - var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output); - var csbe = new NativeMethods.CONSOLE_SCREEN_BUFFER_INFO_EX - { - cbSize = Marshal.SizeOf() - }; - if (NativeMethods.GetConsoleScreenBufferInfoEx(handle, ref csbe)) - { - return GetRTFColorFromColorRef(csbe.Black) + - GetRTFColorFromColorRef(csbe.DarkBlue) + - GetRTFColorFromColorRef(csbe.DarkGreen) + - GetRTFColorFromColorRef(csbe.DarkCyan) + - GetRTFColorFromColorRef(csbe.DarkRed) + - GetRTFColorFromColorRef(csbe.DarkMagenta) + - GetRTFColorFromColorRef(csbe.DarkYellow) + - GetRTFColorFromColorRef(csbe.Gray) + - GetRTFColorFromColorRef(csbe.DarkGray) + - GetRTFColorFromColorRef(csbe.Blue) + - GetRTFColorFromColorRef(csbe.Green) + - GetRTFColorFromColorRef(csbe.Cyan) + - GetRTFColorFromColorRef(csbe.Red) + - GetRTFColorFromColorRef(csbe.Magenta) + - GetRTFColorFromColorRef(csbe.Yellow) + - GetRTFColorFromColorRef(csbe.White); - } - - // A bit of a hack if the above failed - assume PowerShell's color scheme if the - // background color is Magenta, otherwise we assume the default scheme. - return _singleton._console.BackgroundColor == ConsoleColor.DarkMagenta - ? PowerShellColorTable - : CmdColorTable; - } - - private static void DumpScreenToClipboard(int top, int count) - { - var buffer = ReadBufferLines(top, count); - var bufferWidth = _singleton._console.BufferWidth; - - var textBuffer = new StringBuilder(buffer.Length + count); - - var rtfBuffer = new StringBuilder(); - rtfBuffer.Append(@"{\rtf\ansi{\fonttbl{\f0 Consolas;}}"); - - var colorTable = GetColorTable(); - rtfBuffer.AppendFormat(@"{{\colortbl;{0}}}{1}", colorTable, Environment.NewLine); - rtfBuffer.Append(@"\f0 \fs18 "); - - var charInfo = buffer[0]; - var fgColor = (int)charInfo.ForegroundColor; - var bgColor = (int)charInfo.BackgroundColor; - rtfBuffer.AppendFormat(@"{{\cf{0}\chshdng0\chcbpat{1} ", fgColor + 1, bgColor + 1); - for (int i = 0; i < count; i++) - { - var spaces = 0; - var rtfSpaces = 0; - for (int j = 0; j < bufferWidth; j++) - { - charInfo = buffer[i * bufferWidth + j]; - if ((int)charInfo.ForegroundColor != fgColor || (int)charInfo.BackgroundColor != bgColor) - { - if (rtfSpaces > 0) - { - rtfBuffer.Append(' ', rtfSpaces); - rtfSpaces = 0; - } - fgColor = (int)charInfo.ForegroundColor; - bgColor = (int)charInfo.BackgroundColor; - rtfBuffer.AppendFormat(@"}}{{\cf{0}\chshdng0\chcbpat{1} ", fgColor + 1, bgColor + 1); - } - - var c = (char)charInfo.UnicodeChar; - if (c == ' ') - { - // Trailing spaces are skipped, we'll add them back if we find a non-space - // before the end of line - ++spaces; - ++rtfSpaces; - } - else - { - if (spaces > 0) - { - textBuffer.Append(' ', spaces); - spaces = 0; - } - if (rtfSpaces > 0) - { - rtfBuffer.Append(' ', rtfSpaces); - rtfSpaces = 0; - } - - textBuffer.Append(c); - switch (c) - { - case '\\': rtfBuffer.Append(@"\\"); break; - case '\t': rtfBuffer.Append(@"\tab"); break; - case '{': rtfBuffer.Append(@"\{"); break; - case '}': rtfBuffer.Append(@"\}"); break; - default: rtfBuffer.Append(c); break; - } - } - } - rtfBuffer.AppendFormat(@"\shading0 \cbpat{0} \par{1}", bgColor + 1, Environment.NewLine); - textBuffer.Append(Environment.NewLine); - } - rtfBuffer.Append("}}"); - -#if !CORECLR // TODO: break dependency on Window.Forms w/ p/invokes to clipboard directly, for now, just silently skip the copy. - var dataObject = new System.Windows.Forms.DataObject(); - dataObject.SetData(System.Windows.Forms.DataFormats.Text, textBuffer.ToString()); - dataObject.SetData(System.Windows.Forms.DataFormats.Rtf, rtfBuffer.ToString()); - ExecuteOnSTAThread(() => System.Windows.Forms.Clipboard.SetDataObject(dataObject, copy: true)); -#endif - } - } -} -#endif diff --git a/src/Microsoft.PowerShell.PSReadLine/UndoRedo.cs b/src/Microsoft.PowerShell.PSReadLine/UndoRedo.cs deleted file mode 100644 index e25aa8ad85f..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/UndoRedo.cs +++ /dev/null @@ -1,242 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private void RemoveEditsAfterUndo() - { - // If there is some sort of edit after an undo, forget - // any edit items that were undone. - int removeCount = _edits.Count - _undoEditIndex; - if (removeCount > 0) - { - _edits.RemoveRange(_undoEditIndex, removeCount); - if (_editGroupStart >= 0) - { - // Adjust the edit group start if we are started a group. - _editGroupStart -= removeCount; - } - } - } - - private void SaveEditItem(EditItem editItem) - { - if (_statusIsErrorMessage) - { - // After an edit, clear the error message - ClearStatusMessage(render: true); - } - - RemoveEditsAfterUndo(); - - _edits.Add(editItem); - _undoEditIndex = _edits.Count; - } - - private void StartEditGroup() - { - if (_editGroupStart != -1) - { - // Nesting not supported. - throw new InvalidOperationException(); - } - - RemoveEditsAfterUndo(); - _editGroupStart = _edits.Count; - } - - private void EndEditGroup(Action instigator = null, object instigatorArg = null) - { - var groupEditCount = _edits.Count - _editGroupStart; - var groupedEditItems = _edits.GetRange(_editGroupStart, groupEditCount); - _edits.RemoveRange(_editGroupStart, groupEditCount); - SaveEditItem(GroupedEdit.Create(groupedEditItems, instigator, instigatorArg)); - _editGroupStart = -1; - } - - /// - /// Undo a previous edit. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void Undo(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._undoEditIndex > 0) - { - if (_singleton._statusIsErrorMessage) - { - // After an edit, clear the error message - _singleton.ClearStatusMessage(render: false); - } - _singleton._edits[_singleton._undoEditIndex - 1].Undo(); - _singleton._undoEditIndex--; - if (_singleton._options.EditMode == EditMode.Vi && _singleton._current >= _singleton._buffer.Length) - { - _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); - } - _singleton.Render(); - } - else - { - Ding(); - } - } - - /// - /// Undo an undo. - /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] - public static void Redo(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._undoEditIndex < _singleton._edits.Count) - { - _singleton._edits[_singleton._undoEditIndex].Redo(); - _singleton._undoEditIndex++; - _singleton.Render(); - } - else - { - Ding(); - } - } - - abstract class EditItem - { - public Action _instigator = null; - public object _instigatorArg = null; - - public abstract void Undo(); - public abstract void Redo(); - } - - [DebuggerDisplay("Insert '{_insertedCharacter}' ({_insertStartPosition})")] - class EditItemInsertChar : EditItem - { - // The character inserted is not needed for undo, only for redo - private char _insertedCharacter; - private int _insertStartPosition; - - public static EditItem Create(char character, int position) - { - return new EditItemInsertChar - { - _insertedCharacter = character, - _insertStartPosition = position - }; - } - - public override void Undo() - { - Debug.Assert(_singleton._buffer[_insertStartPosition] == _insertedCharacter, "Character to undo is not what it should be"); - _singleton._buffer.Remove(_insertStartPosition, 1); - _singleton._current = _insertStartPosition; - } - - public override void Redo() - { - _singleton._buffer.Insert(_insertStartPosition, _insertedCharacter); - _singleton._current++; - } - } - - [DebuggerDisplay("Insert '{_insertedString}' ({_insertStartPosition})")] - class EditItemInsertString : EditItem - { - // The string inserted tells us the length to delete on undo. - // The contents of the string are only needed for redo. - private string _insertedString; - private int _insertStartPosition; - - public static EditItem Create(string str, int position) - { - return new EditItemInsertString - { - _insertedString = str, - _insertStartPosition = position - }; - } - - public override void Undo() - { - Debug.Assert(_singleton._buffer.ToString(_insertStartPosition, _insertedString.Length).Equals(_insertedString), - "Character to undo is not what it should be"); - _singleton._buffer.Remove(_insertStartPosition, _insertedString.Length); - _singleton._current = _insertStartPosition; - } - - public override void Redo() - { - _singleton._buffer.Insert(_insertStartPosition, _insertedString); - _singleton._current += _insertedString.Length; - } - } - - [DebuggerDisplay("Delete '{_deletedString}' ({_deleteStartPosition})")] - class EditItemDelete : EditItem - { - private string _deletedString; - private int _deleteStartPosition; - - public static EditItem Create(string str, int position, Action instigator = null, object instigatorArg = null) - { - return new EditItemDelete - { - _deletedString = str, - _deleteStartPosition = position, - _instigator = instigator, - _instigatorArg = instigatorArg - }; - } - - public override void Undo() - { - _singleton._buffer.Insert(_deleteStartPosition, _deletedString); - _singleton._current = _deleteStartPosition + _deletedString.Length; - } - - public override void Redo() - { - _singleton._buffer.Remove(_deleteStartPosition, _deletedString.Length); - _singleton._current = _deleteStartPosition; - } - } - - class GroupedEdit : EditItem - { - internal List _groupedEditItems; - - public static EditItem Create(List groupedEditItems, Action instigator = null, object instigatorArg = null) - { - return new GroupedEdit - { - _groupedEditItems = groupedEditItems, - _instigator = instigator, - _instigatorArg = instigatorArg - }; - } - - public override void Undo() - { - for (int i = _groupedEditItems.Count - 1; i >= 0; i--) - { - _groupedEditItems[i].Undo(); - } - } - - public override void Redo() - { - foreach (var editItem in _groupedEditItems) - { - editItem.Redo(); - } - } - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/UndoRedo.vi.cs b/src/Microsoft.PowerShell.PSReadLine/UndoRedo.vi.cs deleted file mode 100644 index ed0d127f028..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/UndoRedo.vi.cs +++ /dev/null @@ -1,70 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private class GroupUndoHelper - { - public Action _instigator = null; - public object _instigatorArg = null; - - public GroupUndoHelper() - { - _instigator = null; - _instigatorArg = null; - } - - public void StartGroup(Action instigator, object instigatorArg) - { - _instigator = instigator; - _instigatorArg = instigatorArg; - _singleton.StartEditGroup(); - } - - public void Clear() - { - _instigator = null; - _instigatorArg = null; - } - - public void EndGroup() - { - if (_singleton._editGroupStart >= 0) - { - _singleton.EndEditGroup(_instigator, _instigatorArg); - } - Clear(); - } - } - private GroupUndoHelper _groupUndoHelper = new GroupUndoHelper(); - - /// - /// Undo all previous edits for line. - /// - public static void UndoAll(ConsoleKeyInfo? key = null, object arg = null) - { - if (_singleton._undoEditIndex > 0) - { - while (_singleton._undoEditIndex > 0) - { - _singleton._edits[_singleton._undoEditIndex - 1].Undo(); - _singleton._undoEditIndex--; - } - _singleton.Render(); - } - else - { - Ding(); - } - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/VisualEditing.vi.cs b/src/Microsoft.PowerShell.PSReadLine/VisualEditing.vi.cs deleted file mode 100644 index b93a6f36b4a..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/VisualEditing.vi.cs +++ /dev/null @@ -1,111 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private string _visualEditTemporaryFilename = null; - private Func _savedAddToHistoryHandler = null; - - /// - /// Edit the command line in a text editor specified by $env:EDITOR or $env:VISUAL - /// - public static void ViEditVisually(ConsoleKeyInfo? key = null, object arg = null) - { - string editorOfChoice = GetPreferredEditor(); - if (string.IsNullOrWhiteSpace(editorOfChoice)) - { - Ding(); - return; - } - - _singleton._visualEditTemporaryFilename = GetTemporaryPowerShellFile(); - using (FileStream fs = File.OpenWrite(_singleton._visualEditTemporaryFilename)) - { - using (TextWriter tw = new StreamWriter(fs)) - { - tw.Write(_singleton._buffer.ToString()); - } - } - - _singleton._savedAddToHistoryHandler = _singleton.Options.AddToHistoryHandler; - _singleton.Options.AddToHistoryHandler = ((string s) => - { - return false; - }); - - _singleton._buffer.Clear(); - _singleton._current = 0; - _singleton.Render(); - _singleton._buffer.Append(editorOfChoice + " \'" + _singleton._visualEditTemporaryFilename + "\'"); - AcceptLine(); - } - - private static string GetTemporaryPowerShellFile() - { - string filename; - do - { - filename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".ps1"); - } while (File.Exists(filename) || Directory.Exists(filename)); - - return filename; - } - - private void ProcessViVisualEditing() - { - if (_visualEditTemporaryFilename == null) - { - return; - } - - Options.AddToHistoryHandler = _savedAddToHistoryHandler; - _savedAddToHistoryHandler = null; - - string editedCommand = null; - using (TextReader tr = File.OpenText(_visualEditTemporaryFilename)) - { - editedCommand = tr.ReadToEnd(); - } - File.Delete(_visualEditTemporaryFilename); - _visualEditTemporaryFilename = null; - - if (!string.IsNullOrWhiteSpace(editedCommand)) - { - while (editedCommand.Length > 0 && char.IsWhiteSpace(editedCommand[editedCommand.Length - 1])) - { - editedCommand = editedCommand.Substring(0, editedCommand.Length - 1); - } - editedCommand = editedCommand.Replace(Environment.NewLine, "\n"); - _buffer.Clear(); - _buffer.Append(editedCommand); - _current = _buffer.Length - 1; - Render(); - //_queuedKeys.Enqueue(Keys.Enter); - } - } - - private static string GetPreferredEditor() - { - string[] names = {"VISUAL", "EDITOR"}; - foreach (string name in names) - { - string editor = Environment.GetEnvironmentVariable(name); - if (!string.IsNullOrWhiteSpace(editor)) - { - return editor; - } - } - - return null; - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/Words.cs b/src/Microsoft.PowerShell.PSReadLine/Words.cs deleted file mode 100644 index fe8f3c8ecc9..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/Words.cs +++ /dev/null @@ -1,204 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections.Generic; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private enum FindTokenMode - { - CurrentOrNext, - Next, - Previous, - } - - static bool OffsetWithinToken(int offset, Token token) - { - return offset < token.Extent.EndOffset && offset >= token.Extent.StartOffset; - } - - private Token FindNestedToken(int offset, IList tokens, FindTokenMode mode) - { - Token token = null; - bool foundNestedToken = false; - int i; - for (i = tokens.Count - 1; i >= 0; i--) - { - if (OffsetWithinToken(offset, tokens[i])) - { - token = tokens[i]; - var strToken = token as StringExpandableToken; - if (strToken != null && strToken.NestedTokens != null) - { - var nestedToken = FindNestedToken(offset, strToken.NestedTokens, mode); - if (nestedToken != null) - { - token = nestedToken; - foundNestedToken = true; - } - } - break; - } - if (offset >= tokens[i].Extent.EndOffset) - { - break; - } - } - - switch (mode) - { - case FindTokenMode.CurrentOrNext: - if (token == null && (i + 1) < tokens.Count) - { - token = tokens[i + 1]; - } - break; - case FindTokenMode.Next: - if (!foundNestedToken) - { - // If there is no next token, return null (happens with nested - // tokens where there is no EOF/EOS token). - token = ((i + 1) < tokens.Count) ? tokens[i + 1] : null; - } - break; - case FindTokenMode.Previous: - if (token == null) - { - if (i >= 0) - { - token = tokens[i]; - } - } - else if (offset == token.Extent.StartOffset) - { - token = i > 0 ? tokens[i - 1] : null; - } - break; - } - - return token; - } - - private Token FindToken(int current, FindTokenMode mode) - { - MaybeParseInput(); - return FindNestedToken(current, _tokens, mode); - } - - private bool InWord(int index, string wordDelimiters) - { - char c = _buffer[index]; - return !char.IsWhiteSpace(c) && wordDelimiters.IndexOf(c) < 0; - } - - /// - /// Find the end of the current/next word as defined by wordDelimiters and whitespace. - /// - private int FindForwardWordPoint(string wordDelimiters) - { - int i = _current; - if (i == _buffer.Length) - { - return i; - } - - if (!InWord(i, wordDelimiters)) - { - // Scan to end of current non-word region - while (i < _buffer.Length) - { - if (InWord(i, wordDelimiters)) - { - break; - } - i += 1; - } - } - while (i < _buffer.Length) - { - if (!InWord(i, wordDelimiters)) - { - break; - } - i += 1; - } - return i; - } - - /// - /// Find the start of the next word. - /// - private int FindNextWordPoint(string wordDelimiters) - { - int i = _singleton._current; - if (i == _singleton._buffer.Length) - { - return i; - } - - if (InWord(i, wordDelimiters)) - { - // Scan to end of current word region - while (i < _singleton._buffer.Length) - { - if (!InWord(i, wordDelimiters)) - { - break; - } - i += 1; - } - } - - while (i < _singleton._buffer.Length) - { - if (InWord(i, wordDelimiters)) - { - break; - } - i += 1; - } - return i; - } - - /// - /// Find the beginning of the previous word. - /// - private int FindBackwardWordPoint(string wordDelimiters) - { - int i = _current - 1; - if (i < 0) - { - return 0; - } - - if (!InWord(i, wordDelimiters)) - { - // Scan backwards until we are at the end of the previous word. - while (i > 0) - { - if (InWord(i, wordDelimiters)) - { - break; - } - i -= 1; - } - } - while (i > 0) - { - if (!InWord(i, wordDelimiters)) - { - i += 1; - break; - } - i -= 1; - } - return i; - } - - - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/Words.vi.cs b/src/Microsoft.PowerShell.PSReadLine/Words.vi.cs deleted file mode 100644 index 219499737c0..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/Words.vi.cs +++ /dev/null @@ -1,488 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections.Generic; -using System.Management.Automation.Language; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private char _lastWordDelimiter = char.MinValue; - private bool _shouldAppend = false; - - /// - /// Returns the position of the beginning of the next word as delimited by white space and delimiters. - /// - private int ViFindNextWordPoint(string wordDelimiters) - { - return ViFindNextWordPoint(_current, wordDelimiters); - } - - /// - /// Returns the position of the beginning of the next word as delimited by white space and delimiters. - /// - private int ViFindNextWordPoint(int i, string wordDelimiters) - { - if (IsAtEndOfLine(i)) - { - return i; - } - if (InWord(i, wordDelimiters)) - { - return ViFindNextWordFromWord(i, wordDelimiters); - } - if (IsDelimiter(i, wordDelimiters)) - { - return ViFindNextWordFromDelimiter(i, wordDelimiters); - } - return ViFindNextWordFromWhiteSpace(i, wordDelimiters); - } - - private int ViFindNextWordFromWhiteSpace(int i, string wordDelimiters) - { - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - return i; - } - - private int ViFindNextWordFromDelimiter(int i, string wordDelimiters) - { - while (!IsAtEndOfLine(i) && IsDelimiter(i, wordDelimiters)) - { - i++; - } - if (IsAtEndOfLine(i)) - { - if (IsDelimiter(i, wordDelimiters)) - { - _shouldAppend = true; - return i + 1; - } - return i; - } - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - return i; - } - - private bool IsAtEndOfLine(int i) - { - return i >= (_buffer.Length - 1); - } - - private bool IsPastEndOfLine(int i) - { - return i > (_buffer.Length - 1); - } - - private int ViFindNextWordFromWord(int i, string wordDelimiters) - { - while (!IsAtEndOfLine(i) && InWord(i, wordDelimiters)) - { - i++; - } - if (IsAtEndOfLine(i) && InWord(i, wordDelimiters)) - { - _shouldAppend = true; - return i + 1; - } - if (IsDelimiter(i, wordDelimiters)) - { - _lastWordDelimiter = _buffer[i]; - return i; - } - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - if (IsAtEndOfLine(i) && !InWord(i, wordDelimiters)) - { - return i + 1; - } - _lastWordDelimiter = _buffer[i-1]; - return i; - } - - /// - /// Returns true of the character at the given position is white space. - /// - private bool IsWhiteSpace(int i) - { - return char.IsWhiteSpace(_buffer[i]); - } - - /// - /// Returns the beginning of the current/next word as defined by wordDelimiters and whitespace. - /// - private int ViFindPreviousWordPoint(string wordDelimiters) - { - return ViFindPreviousWordPoint(_current, wordDelimiters); - } - - /// - /// Returns the beginning of the current/next word as defined by wordDelimiters and whitespace. - /// - /// Current cursor location. - /// Characters used to delimit words. - /// Location of the beginning of the previous word. - private int ViFindPreviousWordPoint(int i, string wordDelimiters) - { - if (i == 0) - { - return i; - } - - if (IsWhiteSpace(i)) - { - return FindPreviousWordFromWhiteSpace(i, wordDelimiters); - } - else if (InWord(i, wordDelimiters)) - { - return FindPreviousWordFromWord(i, wordDelimiters); - } - return FindPreviousWordFromDelimiter(i, wordDelimiters); - } - - /// - /// Knowing that you're starting with a word, find the previous start of the next word. - /// - private int FindPreviousWordFromWord(int i, string wordDelimiters) - { - i--; - if (InWord(i, wordDelimiters)) - { - while (i > 0 && InWord(i, wordDelimiters)) - { - i--; - } - if (i == 0 && InWord(i, wordDelimiters)) - { - return i; - } - return i + 1; - } - if (IsWhiteSpace(i)) - { - while (i > 0 && IsWhiteSpace(i)) - { - i--; - } - if (i == 0) - { - return i; - } - if (InWord(i, wordDelimiters) && InWord(i-1, wordDelimiters)) - { - return FindPreviousWordFromWord(i, wordDelimiters); - } - if (IsDelimiter(i - 1, wordDelimiters)) - { - FindPreviousWordFromDelimiter(i, wordDelimiters); - } - return i; - } - while (i > 0 && IsDelimiter(i, wordDelimiters)) - { - i--; - } - if (i == 0 && IsDelimiter(i, wordDelimiters)) - { - return i; - } - return i + 1; - } - - /// - /// Returns true if the cursor is on a word delimiter - /// - private bool IsDelimiter(int i, string wordDelimiters) - { - return wordDelimiters.IndexOf(_buffer[i]) >= 0; - } - - /// - /// Returns true if is in the set of . - /// - private bool IsDelimiter(char c, string wordDelimiters) - { - foreach (char delimiter in wordDelimiters) - { - if (c == delimiter) - { - return true; - } - } - return false; - } - - /// - /// Returns the cursor position of the beginning of the previous word when starting on a delimiter - /// - private int FindPreviousWordFromDelimiter(int i, string wordDelimiters) - { - i--; - if (IsDelimiter(i, wordDelimiters)) - { - while (i > 0 && IsDelimiter(i, wordDelimiters)) - { - i--; - } - if (i == 0 && !IsDelimiter(i, wordDelimiters)) - { - return i + 1; - } - if (!IsWhiteSpace(i)) - { - return i + 1; - } - return i; - } - return ViFindPreviousWordPoint(i, wordDelimiters); - } - - - /// - /// Returns the cursor position of the beginning of the previous word when starting on white space - /// - private int FindPreviousWordFromWhiteSpace(int i, string wordDelimiters) - { - while (IsWhiteSpace(i) && i > 0) - { - i--; - } - int j = i - 1; - if (j < 0 || !InWord(i, wordDelimiters) || char.IsWhiteSpace(_buffer[j])) - { - return i; - } - return (ViFindPreviousWordPoint(i, wordDelimiters)); - } - - /// - /// Returns the cursor position of the previous word, ignoring all delimiters other what white space - /// - private int ViFindPreviousGlob() - { - int i = _current; - if (i == 0) - { - return 0; - } - i--; - - return ViFindPreviousGlob(i); - } - - /// - /// Returns the cursor position of the previous word from i, ignoring all delimiters other what white space - /// - private int ViFindPreviousGlob(int i) - { - if (i <= 0) - { - return 0; - } - - if (!IsWhiteSpace(i)) - { - while (i > 0 && !IsWhiteSpace(i)) - { - i--; - } - if (!IsWhiteSpace(i)) - { - return i; - } - return i + 1; - } - while (i > 0 && IsWhiteSpace(i)) - { - i--; - } - if (i == 0) - { - return i; - } - return ViFindPreviousGlob(i); - } - - /// - /// Finds the next work, using only white space as the word delimiter. - /// - private int ViFindNextGlob() - { - int i = _current; - return ViFindNextGlob(i); - } - - private int ViFindNextGlob(int i) - { - if (i >= _buffer.Length) - { - return i; - } - while (!IsAtEndOfLine(i) && !IsWhiteSpace(i)) - { - i++; - } - if (IsAtEndOfLine(i) && !IsWhiteSpace(i)) - { - return i + 1; - } - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - if (IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - return i + 1; - } - return i; - } - - /// - /// Finds the end of the current/next word as defined by whitespace. - /// - private int ViFindEndOfGlob() - { - return ViFindGlobEnd(_current); - } - - /// - /// Find the end of the current/next word as defined by wordDelimiters and whitespace. - /// - private int ViFindNextWordEnd(string wordDelimiters) - { - int i = _current; - - return ViFindNextWordEnd(i, wordDelimiters); - } - - /// - /// Find the end of the current/next word as defined by wordDelimiters and whitespace. - /// - private int ViFindNextWordEnd(int i, string wordDelimiters) - { - if (IsAtEndOfLine(i)) - { - return i; - } - - if (IsDelimiter(i, wordDelimiters) && !IsDelimiter(i + 1, wordDelimiters)) - { - i++; - if (IsAtEndOfLine(i)) - { - return i; - } - } - else if (InWord(i, wordDelimiters) && !InWord(i + 1, wordDelimiters)) - { - i++; - if (IsAtEndOfLine(i)) - { - return i; - } - } - - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - - if (IsAtEndOfLine(i)) - { - return i; - } - - if (IsDelimiter(i, wordDelimiters)) - { - while (!IsAtEndOfLine(i) && IsDelimiter(i, wordDelimiters)) - { - i++; - } - if (!IsDelimiter(i, wordDelimiters)) - { - return i - 1; - } - } - else - { - while (!IsAtEndOfLine(i) && InWord(i, wordDelimiters)) - { - i++; - } - if (!InWord(i, wordDelimiters)) - { - return i - 1; - } - } - - return i; - } - - /// - /// Return the last character in a white space defined word after skipping contiguous white space. - /// - private int ViFindGlobEnd(int i) - { - if (IsAtEndOfLine(i)) - { - return i; - } - i++; - if (IsAtEndOfLine(i)) - { - return i; - } - while (!IsAtEndOfLine(i) && IsWhiteSpace(i)) - { - i++; - } - if (IsAtEndOfLine(i)) - { - return i; - } - while (!IsAtEndOfLine(i) && !IsWhiteSpace(i)) - { - i++; - } - if (IsWhiteSpace(i)) - { - return i - 1; - } - return i; - } - - private int ViFindEndOfPreviousGlob() - { - int i = _current; - - return ViFindEndOfPreviousGlob(i); - } - - private int ViFindEndOfPreviousGlob(int i) - { - if (IsWhiteSpace(i)) - { - while (i > 0 && IsWhiteSpace(i)) - { - i--; - } - return i; - } - - while (i > 0 && !IsWhiteSpace(i)) - { - i--; - } - return ViFindEndOfPreviousGlob(i); - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/YankPaste.vi.cs b/src/Microsoft.PowerShell.PSReadLine/YankPaste.vi.cs deleted file mode 100644 index 1f894a72fea..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/YankPaste.vi.cs +++ /dev/null @@ -1,338 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; - -namespace Microsoft.PowerShell -{ - public partial class PSConsoleReadLine - { - private string _clipboard = string.Empty; - - /// - /// Paste the clipboard after the cursor, moving the cursor to the end of the pasted text. - /// - public static void PasteAfter(ConsoleKeyInfo? key = null, object arg = null) - { - if (string.IsNullOrEmpty(_singleton._clipboard)) - { - Ding(); - return; - } - - _singleton.PasteAfterImpl(); - } - - /// - /// Paste the clipboard before the cursor, moving the cursor to the end of the pasted text. - /// - public static void PasteBefore(ConsoleKeyInfo? key = null, object arg = null) - { - if (string.IsNullOrEmpty(_singleton._clipboard)) - { - Ding(); - return; - } - _singleton.PasteBeforeImpl(); - } - - private void PasteAfterImpl() - { - if (_current < _buffer.Length) - { - _current++; - } - Insert(_clipboard); - _current--; - Render(); - } - - private void PasteBeforeImpl() - { - Insert(_clipboard); - _current--; - Render(); - } - - private void SaveToClipboard(int startIndex, int length) - { - _clipboard = _buffer.ToString(startIndex, length); - } - - /// - /// Yank the entire buffer. - /// - public static void ViYankLine(ConsoleKeyInfo? key = null, object arg = null) - { - _singleton.SaveToClipboard(0, _singleton._buffer.Length); - } - - /// - /// Yank character(s) under and to the right of the cursor. - /// - public static void ViYankRight(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int start = _singleton._current; - int length = 0; - - while (numericArg-- > 0) - { - length++; - } - - _singleton.SaveToClipboard(start, length); - } - - /// - /// Yank character(s) to the left of the cursor. - /// - public static void ViYankLeft(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int start = _singleton._current; - if (start == 0) - { - _singleton.SaveToClipboard(start, 1); - return; - } - - int length = 0; - - while (numericArg-- > 0) - { - if (start > 0) - { - start--; - length++; - } - } - - _singleton.SaveToClipboard(start, length); - } - - /// - /// Yank from the cursor to the end of the buffer. - /// - public static void ViYankToEndOfLine(ConsoleKeyInfo? key = null, object arg = null) - { - int start = _singleton._current; - int length = _singleton._buffer.Length - _singleton._current; - _singleton.SaveToClipboard(start, length); - } - - /// - /// Yank the word(s) before the cursor. - /// - public static void ViYankPreviousWord(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int start = _singleton._current; - - while (numericArg-- > 0) - { - start = _singleton.ViFindPreviousWordPoint(start, _singleton.Options.WordDelimiters); - } - - int length = _singleton._current - start; - if (length > 0) - { - _singleton.SaveToClipboard(start, length); - } - } - - /// - /// Yank the word(s) after the cursor. - /// - public static void ViYankNextWord(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int end = _singleton._current; - - while (numericArg-- > 0) - { - end = _singleton.ViFindNextWordPoint(end, _singleton.Options.WordDelimiters); - } - - int length = end - _singleton._current; - //if (_singleton.IsAtEndOfLine(end)) - //{ - // length++; - //} - if (length > 0) - { - _singleton.SaveToClipboard(_singleton._current, length); - } - } - - /// - /// Yank from the cursor to the end of the word(s). - /// - public static void ViYankEndOfWord(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int end = _singleton._current; - - while (numericArg-- > 0) - { - end = _singleton.ViFindNextWordEnd(end, _singleton.Options.WordDelimiters); - } - - int length = 1 + end - _singleton._current; - if (length > 0) - { - _singleton.SaveToClipboard(_singleton._current, length); - } - } - - /// - /// Yank from the cursor to the end of the WORD(s). - /// - public static void ViYankEndOfGlob(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int end = _singleton._current; - - while (numericArg-- > 0) - { - end = _singleton.ViFindGlobEnd(end); - } - - int length = 1 + end - _singleton._current; - if (length > 0) - { - _singleton.SaveToClipboard(_singleton._current, length); - } - } - - /// - /// Yank from the beginning of the buffer to the cursor. - /// - public static void ViYankBeginningOfLine(ConsoleKeyInfo? key = null, object arg = null) - { - int length = _singleton._current; - if (length > 0) - { - _singleton.SaveToClipboard(0, length); - } - } - - /// - /// Yank from the first non-whitespace character to the cursor. - /// - public static void ViYankToFirstChar(ConsoleKeyInfo? key = null, object arg = null) - { - int start = 0; - while (_singleton.IsWhiteSpace(start)) - { - start++; - } - if (start == _singleton._current) - { - return; - } - - int length = _singleton._current - start; - if (length > 0) - { - _singleton.SaveToClipboard(start, length); - } - } - - /// - /// Yank to/from matching brace. - /// - public static void ViYankPercent(ConsoleKeyInfo? key = null, object arg = null) - { - int start = _singleton.ViFindBrace(_singleton._current); - if (_singleton._current < start) - { - _singleton.SaveToClipboard(_singleton._current, start - _singleton._current + 1); - } - else if (start < _singleton._current) - { - _singleton.SaveToClipboard(start, _singleton._current - start + 1); - } - else - { - Ding(); - } - } - - /// - /// Yank from beginning of the WORD(s) to cursor. - /// - public static void ViYankPreviousGlob(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int start = _singleton._current; - while (numericArg-- > 0) - { - start = _singleton.ViFindPreviousGlob(start - 1); - } - if (start < _singleton._current) - { - _singleton.SaveToClipboard(start, _singleton._current - start); - } - else - { - Ding(); - } - } - - /// - /// Yank from cursor to the start of the next WORD(s). - /// - public static void ViYankNextGlob(ConsoleKeyInfo? key = null, object arg = null) - { - int numericArg; - if (!TryGetArgAsInt(arg, out numericArg, 1)) - { - return; - } - - int end = _singleton._current; - while (numericArg-- > 0) - { - end = _singleton.ViFindNextGlob(end); - } - _singleton.SaveToClipboard(_singleton._current, end - _singleton._current); - } - } -} diff --git a/src/Microsoft.PowerShell.PSReadLine/en-US/PSReadline.md b/src/Microsoft.PowerShell.PSReadLine/en-US/PSReadline.md deleted file mode 100644 index 2d433a94552..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/en-US/PSReadline.md +++ /dev/null @@ -1,513 +0,0 @@ -# Get-PSReadlineKeyHandler - -## SYNOPSIS -Gets the key bindings for the PSReadline module. - -## DESCRIPTION -Gets the key bindings for the PSReadline module. - -If neither -Bound nor -Unbound is specified, returns all bound keys and unbound functions. - -If -Bound is specified and -Unbound is not specified, only bound keys are returned. - -If -Unbound is specified and -Bound is not specified, only unbound keys are returned. - -If both -Bound and -Unbound are specified, returns all bound keys and unbound functions. - -## PARAMETERS - -### Bound [switch] = True - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Include functions that are bound. - - -### Unbound [switch] = True - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Include functions that are unbound. - - - -## INPUTS -### None -You cannot pipe objects to Get-PSReadlineKeyHandler - -## OUTPUTS -### Microsoft.PowerShell.KeyHandler - -Returns one entry for each key binding (or chord) for bound functions and/or one entry for each unbound function - - - -## RELATED LINKS - -[about_PSReadline]() - -# Get-PSReadlineOption - -## SYNOPSIS -Returns the values for the options that can be configured. - -## DESCRIPTION -Get-PSReadlineOption returns the current state of the settings that can be configured by Set-PSReadlineOption. - -The object returned can be used to change PSReadline options. -This provides a slightly simpler way of setting syntax coloring options for multiple kinds of tokens. - -## PARAMETERS - - -## INPUTS -### None -You cannot pipe objects to Get-PSReadlineOption - -## OUTPUTS -### - - - - - -## RELATED LINKS - -[about_PSReadline]() - -# Set-PSReadlineKeyHandler - -## SYNOPSIS -Binds or rebinds keys to user defined or PSReadline provided key handlers. - -## DESCRIPTION -This cmdlet is used to customize what happens when a particular key or sequence of keys is pressed while PSReadline is reading input. - -With user defined key bindings, you can do nearly anything that is possible from a PowerShell script. -Typically you might just edit the command line in some novel way, but because the handlers are just PowerShell scripts, you can do interesting things like change directories, launch programs, etc. - -## PARAMETERS - -### Chord [String[]] - -```powershell -[Parameter( - Mandatory = $true, - Position = 0)] -``` - -The key or sequence of keys to be bound to a Function or ScriptBlock. -A single binding is specified with a single string. -If the binding is a sequence of keys, the keys are separated with a comma, e.g. "Ctrl+X,Ctrl+X". -Note that this parameter accepts multiple strings. -Each string is a separate binding, not a sequence of keys for a single binding. - - -### ScriptBlock [ScriptBlock] - -```powershell -[Parameter( - Mandatory = $true, - Position = 1, - ParameterSetName = 'Set 1')] -``` - -The ScriptBlock is called when the Chord is entered. -The ScriptBlock is passed one or sometimes two arguments. -The first argument is the key pressed (a ConsoleKeyInfo.) The second argument could be any object depending on the context. - - -### BriefDescription [String] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -A brief description of the key binding. -Used in the output of cmdlet Get-PSReadlineKeyHandler. - - -### Description [String] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -A more verbose description of the key binding. -Used in the output of the cmdlet Get-PSReadlineKeyHandler. - - -### Function [String] - -```powershell -[Parameter( - Mandatory = $true, - Position = 1, - ParameterSetName = 'Set 2')] -``` - -The name of an existing key handler provided by PSReadline. -This parameter allows one to rebind existing key bindings or to bind a handler provided by PSReadline that is currently unbound. - -Using the ScriptBlock parameter, one can achieve equivalent functionality by calling the method directly from the ScriptBlock. -This parameter is preferred though - it makes it easier to determine which functions are bound and unbound. - - - -## INPUTS -### None -You cannot pipe objects to Set-PSReadlineKeyHandler - -## OUTPUTS -### - - - - -## EXAMPLES -### -------------- Example 1 -------------- - -```powershell -PS C:\> Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward -``` -This command binds the up arrow key to the function HistorySearchBackward which will use the currently entered command line as the beginning of the search string when searching through history. -### -------------- Example 2 -------------- - -```powershell -PS C:\> Set-PSReadlineKeyHandler -Chord Shift+Ctrl+B -ScriptBlock { - [PSConsoleUtilities.PSConsoleReadLine]::RevertLine() - [PSConsoleUtilities.PSConsoleReadLine]::Insert('build') ->>> [PSConsoleUtilities.PSConsoleReadLine]::AcceptLine() -} -``` -This example binds the key Ctrl+Shift+B to a script block that clears the line, inserts build, then accepts the line. -This example shows how a single key can be used to execute a command. - -## RELATED LINKS - -[about_PSReadline]() - -# Set-PSReadlineOption - -## SYNOPSIS -Customizes the behavior of command line editing in PSReadline. - -## DESCRIPTION -The Set-PSReadlineOption cmdlet is used to customize the behavior of the PSReadline module when editing the command line. - -## PARAMETERS - -### EditMode [EditMode] = Windows - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the command line editing mode. -This will reset any key bindings set by Set-PSReadlineKeyHandler. - -Valid values are: - --- Windows: Key bindings emulate PowerShell/cmd with some bindings emulating Visual Studio. - --- Emacs: Key bindings emulate Bash or Emacs. - - -### ContinuationPrompt [String] = >>> - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the string displayed at the beginning of the second and subsequent lines when multi-line input is being entered. -Defaults to '\>\>\> '. -The empty string is valid. - - -### ContinuationPromptForegroundColor [ConsoleColor] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the foreground color of the continuation prompt. - - -### ContinuationPromptBackgroundColor [ConsoleColor] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the background color of the continuation prompt. - - -### EmphasisForegroundColor [ConsoleColor] = Cyan - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the foreground color used for emphasis, e.g. -to highlight search text. - - -### EmphasisBackgroundColor [ConsoleColor] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the background color used for emphasis, e.g. -to highlight search text. - - -### ErrorForegroundColor [ConsoleColor] = Red - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the foreground color used for errors. - - -### ErrorBackgroundColor [ConsoleColor] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the background color used for errors. - - -### HistoryNoDuplicates [switch] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies that duplicate commands should not be added to PSReadline history. - - -### AddToHistoryHandler [Func[String, Boolean]] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies a ScriptBlock that can be used to control which commands get added to PSReadline history. - - -### ValidationHandler [Func[String, Object]] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies a ScriptBlock that is called from ValidateAndAcceptLine. -If a non-null object is returned or an exception is thrown, validation fails and the error is reported. -If the object returned/thrown has a Message property, it's value is used in the error message, and if there is an Offset property, the cursor is moved to that offset after reporting the error. -If there is no Message property, the ToString method is called to report the error. - - -### HistorySearchCursorMovesToEnd [switch] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - - - - -### MaximumHistoryCount [Int32] = 1024 - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the maximum number of commands to save in PSReadline history. -Note that PSReadline history is separate from PowerShell history. - - -### MaximumKillRingCount [Int32] = 10 - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the maximum number of items stored in the kill ring. - - -### ResetTokenColors [switch] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Restore the token colors to the default settings. - - -### ShowToolTips [switch] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -When displaying possible completions, show tooltips in the list of completions. - - -### ExtraPromptLineCount [Int32] = 0 - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Use this option if your prompt spans more than one line and you want the extra lines to appear when PSReadline displays the prompt after showing some output, e.g. -when showing a list of completions. - - -### DingTone [Int32] = 1221 - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -When BellStyle is set to Audible, specifies the tone of the beep. - - -### DingDuration [Int32] = 50ms - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -When BellStyle is set to Audible, specifies the duration of the beep. - - -### BellStyle [BellStyle] = Audible - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies how PSReadline should respond to various error and ambiguous conditions. - -Valid values are: - --- Audible: a short beep - --- Visible: a brief flash is performed - --- None: no feedback - - -### CompletionQueryItems [Int32] = 100 - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the maximum number of completion items that will be shown without prompting. -If the number of items to show is greater than this value, PSReadline will prompt y/n before displaying the completion items. - - -### WordDelimiters [string] = ;:,.[]{}()/\|^&*-=+ - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the characters that delimit words for functions like ForwardWord or KillWord. - - -### HistorySearchCaseSensitive [switch] - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the searching history is case sensitive in functions like ReverseSearchHistory or HistorySearchBackward. - - -### HistorySaveStyle [HistorySaveStyle] = SaveIncrementally - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies how PSReadline should save history. - -Valid values are: - --- SaveIncrementally: save history after each command is executed - and share across multiple instances of PowerShell - --- SaveAtExit: append history file when PowerShell exits - --- SaveNothing: don't use a history file - - -### HistorySavePath [String] = ~\AppData\Roaming\PSReadline\$($host.Name)_history.txt - -```powershell -[Parameter(ParameterSetName = 'Set 1')] -``` - -Specifies the path to the history file. - - -### TokenKind [TokenClassification] - -```powershell -[Parameter( - Mandatory = $true, - Position = 0, - ParameterSetName = 'Set 2')] -``` - -Specifies the kind of token when setting token coloring options with the -ForegroundColor and -BackgroundColor parameters. - - -### ForegroundColor [ConsoleColor] - -```powershell -[Parameter( - Position = 1, - ParameterSetName = 'Set 2')] -``` - -Specifies the foreground color for the token kind specified by the parameter -TokenKind. - - -### BackgroundColor [ConsoleColor] - -```powershell -[Parameter( - Position = 2, - ParameterSetName = 'Set 2')] -``` - -Specifies the background color for the token kind specified by the parameter -TokenKind. - - - -## INPUTS -### None -You cannot pipe objects to Set-PSReadlineOption - - -## OUTPUTS -### None -This cmdlet does not generate any output. - - - - -## RELATED LINKS - -[about_PSReadline]() - - diff --git a/src/Microsoft.PowerShell.PSReadLine/en-US/about_PSReadline.help.txt b/src/Microsoft.PowerShell.PSReadLine/en-US/about_PSReadline.help.txt deleted file mode 100644 index 3e1ff65b0c7..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/en-US/about_PSReadline.help.txt +++ /dev/null @@ -1,628 +0,0 @@ -TOPIC - about_PSReadline - -SHORT DESCRIPTION - - PSReadline provides an improved command line editing experience in - the PowerShell console. - -LONG DESCRIPTION - - PSReadline provides a powerful command line editing experience for the - PowerShell console. It provides: - - * Syntax coloring of the command line - * A visual indication of syntax errors - * A better multi-line experience (both editing and history) - * Customizable key bindings - * Cmd and Emacs modes - * Many configuration options - * Bash style completion (optional in Cmd mode, default in Emacs mode) - * Emacs yank/kill ring - * PowerShell token based "word" movement and kill - - The following functions are available in the class [Microsoft.PowerShell.PSConsoleReadLine]: - - Cursor movement - --------------- - - EndOfLine (Cmd: Emacs: or ) - - If the input has multiple lines, move to the end of the current line, - or if already at the end of the line, move to the end of the input. - If the input has a single line, move to the end of the input. - - BeginningOfLine (Cmd: Emacs: or ) - - If the input has multiple lines, move to the start of the current line, - or if already at the start of the line, move to the start of the input. - If the input has a single line, move to the start of the input. - - NextLine (Cmd: unbound Emacs: unbound) - - Move the cursor to the next line if the input has multiple lines. - - PreviousLine (Cmd: unbound Emacs: unbound) - - Move the cursor to the previous line if the input has multiple lines. - - ForwardChar (Cmd: Emacs: or ) - - Move the cursor one character to the right. This may move the cursor to the next - line of multi-line input. - - BackwardChar (Cmd: Emacs: or ) - - Move the cursor one character to the left. This may move the cursor to the previous - line of multi-line input. - - ForwardWord (Cmd: unbound Emacs: ) - - Move the cursor forward to the end of the current word, or if between words, - to the end of the next word. Word delimiter characters can be set with: - - Set-PSReadlineOption -WordDelimiters - - NextWord (Cmd: Emacs: unbound) - - Move the cursor forward to the start of the next word. Word delimiter characters - can be set with: - - Set-PSReadlineOption -WordDelimiters - - BackwardWord (Cmd: Emacs: ) - - Move the cursor back to the start of the current word, or if between words, - the start of the previous word. Word delimiter characters - can be set with: - - Set-PSReadlineOption -WordDelimiters - - ShellForwardWord (Cmd: unbound Emacs: unbound) - - Like ForwardWord except word boundaries are defined by PowerShell token boundaries. - - ShellNextWord (Cmd: unbound Emacs: unbound) - - Like NextWord except word boundaries are defined by PowerShell token boundaries. - - ShellBackwardWord (Cmd: unbound Emacs: unbound) - - Like BackwardWord except word boundaries are defined by PowerShell token boundaries. - - GotoBrace (Cmd: Emacs: unbound) - - Go to the matching parenthesis, curly brace, or square bracket. - - AddLine (Cmd: Emacs: ) - - The continuation prompt is displayed on the next line and PSReadline waits for - keys to edit the current input. This is useful to enter multi-line input as - a single command even when a single line is complete input by itself. - - Basic editing - ------------- - - CancelLine (Cmd: unbound Emacs: unbound) - - Cancel all editing to the line, leave the line of input on the screen but - return from PSReadline without executing the input. - - RevertLine (Cmd: Emacs: ) - - Reverts all of the input since the last input was accepted and executed. - This is equivalent to doing Undo until there is nothing left to undo. - - BackwardDeleteChar (Cmd: Emacs: or ) - - Delete the character before the cursor. - - DeleteChar (Cmd: Emacs: ) - - Delete the character under the cursor. - - DeleteCharOrExit (Cmd: unbound Emacs: ) - - Like DeleteChar, unless the line is empty, in which case exit the process. - - AcceptLine (Cmd: Emacs: or ) - - Attempt to execute the current input. If the current input is incomplete (for - example there is a missing closing parenthesis, bracket, or quote, then the - continuation prompt is displayed on the next line and PSReadline waits for - keys to edit the current input. - - AcceptAndGetNext (Cmd: unbound Emacs: ) - - Like AcceptLine, but after the line completes, start editing the next line - from history. - - ValidateAndAcceptLine (Cmd: unbound Emacs: unbound) - - Like AcceptLine but performs additional validation including: - - * Check for additional parse errors - * Validate command names are all found - * If using PowerShell V4 or greater, validate the parameters and arguments - - If there are any errors, the error message is displayed and not accepted nor added - to the history unless you either correct the command line or execute AcceptLine or - ValidateAndAcceptLine again while the error message is displayed. - - BackwardDeleteLine (Cmd: Emacs: unbound) - - Delete the text from the start of the input to the cursor. - - ForwardDeleteLine (Cmd: Emacs: unbound) - - Delete the text from the cursor to the end of the input. - - SelectBackwardChar (Cmd: Emacs: ) - - Adjust the current selection to include the previous character. - - SelectForwardChar (Cmd: Emacs: ) - - Adjust the current selection to include the next character. - - SelectBackwardWord (Cmd: Emacs: ) - - Adjust the current selection to include the previous word. - - SelectForwardWord (Cmd: unbound Emacs: ) - - Adjust the current selection to include the next word using ForwardWord. - - SelectNextWord (Cmd: Emacs: unbound) - - Adjust the current selection to include the next word using NextWord. - - SelectShellForwardWord (Cmd: unbound Emacs: unbound) - - Adjust the current selection to include the next word using ShellForwardWord. - - SelectShellNextWord (Cmd: unbound Emacs: unbound) - - Adjust the current selection to include the next word using ShellNextWord. - - SelectShellBackwardWord (Cmd: unbound Emacs: unbound) - - Adjust the current selection to include the previous word using ShellBackwardWord. - - SelectBackwardsLine (Cmd: Emacs: ) - - Adjust the current selection to include from the cursor to the start of the line. - - SelectLine (Cmd: Emacs: ) - - Adjust the current selection to include from the cursor to the end of the line. - - SelectAll (Cmd: Emacs: unbound) - - Select the entire line. Moves the cursor to the end of the line. - - SelfInsert (Cmd: , , ... Emacs: , , ...) - - Insert the key entered. - - Redo (Cmd: Emacs: unbound) - - Redo an insertion or deletion that was undone by Undo. - - Undo (Cmd: Emacs: ) - - Undo a previous insertion or deletion. - - History - ------- - - ClearHistory (Cmd: Alt+F7 Emacs: unbound) - - Clears history in PSReadline. This does not affect PowerShell history. - - PreviousHistory (Cmd: Emacs: or ) - - Replace the current input with the 'previous' item from PSReadline history. - - NextHistory (Cmd: Emacs: or ) - - Replace the current input with the 'next' item from PSReadline history. - - ForwardSearchHistory (Cmd: Emacs: ) - - Search forward from the current history line interactively. - - ReverseSearchHistory (Cmd: Emacs: ) - - Search backward from the current history line interactively. - - HistorySearchBackward (Cmd: Emacs: unbound) - - Replace the current input with the 'previous' item from PSReadline history - that matches the characters between the start and the input and the cursor. - - HistorySearchForward (Cmd: Emacs: unbound) - - Replace the current input with the 'next' item from PSReadline history - that matches the characters between the start and the input and the cursor. - - BeginningOfHistory (Cmd: unbound Emacs: ) - - Replace the current input with the last item from PSReadline history. - - EndOfHistory (Cmd: unbound Emacs: >) - - Replace the current input with the last item in PSReadline history, which - is the possibly empty input that was entered before any history commands. - - Tab Completion - -------------- - - TabCompleteNext (Cmd: Emacs: unbound) - - Attempt to complete the text surrounding the cursor with the next - available completion. - - TabCompletePrevious (Cmd: Emacs: unbound) - - Attempt to complete the text surrounding the cursor with the next - previous completion. - - Complete (Cmd: unbound Emacs: ) - - Attempt to perform completion on the text surrounding the cursor. - If there are multiple possible completions, the longest unambiguous - prefix is used for completion. If trying to complete the longest - unambiguous completion, a list of possible completions is displayed. - - MenuComplete (Cmd: Emacs: ) - - Attempt to perform completion on the text surrounding the cursor. - If there are multiple possible completions, a list of possible - completions is displayed and you can select the correct completion - using the arrow keys or Tab/Shift+Tab. Escape and Ctrl+G cancel - the menu selection and reverts the line to the state before invoking - MenuComplete. - - PossibleCompletions (Cmd: unbound Emacs: ) - - Display the list of possible completions. - - SetMark (Cmd: unbound Emacs: ) - - Mark the current location of the cursor for use in a subsequent editing command. - - ExchangePointAndMark (Cmd: unbound Emacs: ) - - The cursor is placed at the location of the mark and the mark is moved - to the location of the cursor. - - Kill/Yank - --------- - - Kill and yank operate on a clipboard in the PSReadline module. There is a ring - buffer called the kill ring - killed text will be added to the kill ring up - and yank will copy text from the most recent kill. YankPop will cycle through - items in the kill ring. When the kill ring is full, new items will replace the - oldest items. A kill operation that is immediately preceded by another kill operation - will append the previous kill instead of adding a new item or replacing an item - in the kill ring. This is how you can cut a part of a line, say for example with multiple - KillWord operations, then yank them back elsewhere as a single yank. - - KillLine (Cmd: unbound Emacs: ) - - Clear the input from the cursor to the end of the line. The cleared text is placed - in the kill ring. - - BackwardKillLine (Cmd: unbound Emacs: or ) - - Clear the input from the start of the input to the cursor. The cleared text is placed - in the kill ring. - - KillWord (Cmd: unbound Emacs: ) - - Clear the input from the cursor to the end of the current word. If the cursor - is between words, the input is cleared from the cursor to the end of the next word. - The cleared text is placed in the kill ring. - - BackwardKillWord (Cmd: unbound Emacs: ) - - Clear the input from the start of the current word to the cursor. If the cursor - is between words, the input is cleared from the start of the previous word to the - cursor. The cleared text is placed in the kill ring. - - ShellKillWord (Cmd: unbound Emacs: unbound) - - Like KillWord except word boundaries are defined by PowerShell token boundaries. - - ShellBackwardKillWord (Cmd: unbound Emacs: unbound) - - Like BackwardKillWord except word boundaries are defined by PowerShell token boundaries. - - UnixWordRubout (Cmd: unbound Emacs: ) - - Like BackwardKillWord except word boundaries are defined by whitespace. - - KillRegion (Cmd: unbound Emacs: unbound) - - Kill the text between the cursor and the mark. - - Copy (Cmd: Emacs: unbound) - - Copy selected region to the system clipboard. If no region is selected, copy the whole line. - - CopyOrCancelLine (Cmd: Emacs: ) - - Either copy selected text to the clipboard, or if no text is selected, cancel editing - the line with CancelLine. - - Cut (Cmd: Emacs: unbound) - - Delete selected region placing deleted text in the system clipboard. - - Yank (Cmd: unbound Emacs: ) - - Add the most recently killed text to the input. - - YankPop (Cmd: unbound Emacs: ) - - If the previous operation was Yank or YankPop, replace the previously yanked - text with the next killed text from the kill ring. - - ClearKillRing (Cmd: unbound Emacs: unbound) - - The contents of the kill ring are cleared. - - Paste (Cmd: Emacs: unbound) - - This is similar to Yank, but uses the system clipboard instead of the kill ring. - - YankLastArg (Cmd: Emacs: , ) - - Insert the last argument from the previous command in history. Repeated operations - will replace the last inserted argument with the last argument from the previous - command (so Alt+. Alt+. will insert the last argument of the second to last history - line.) - - With an argument, the first time YankLastArg behaves like YankNthArg. A negative - argument on subsequent YankLastArg calls will change the direction while going - through history. For example, if you hit Alt+. one too many times, you can type - Alt+- Alt+. to reverse the direction. - - Arguments are based on PowerShell tokens. - - YankNthArg (Cmd: unbound Emacs: ) - - Insert the first argument (not the command name) of the previous command in history. - - With an argument, insert the nth argument where 0 is typically the command. Negative - arguments start from the end. - - Arguments are based on PowerShell tokens. - - Miscellaneous - ------------- - - Abort (Cmd: unbound Emacs: ) - - Abort the current action, e.g. stop interactive history search. - Does not cancel input like CancelLine. - - CharacterSearch (Cmd: Emacs: ) - - Read a key and search forwards for that character. With an argument, search - forwards for the nth occurrence of that argument. With a negative argument, - searches backwards. - - CharacterSearchBackward (Cmd: Emacs: ) - - Like CharacterSearch, but searches backwards. With a negative argument, searches - forwards. - - ClearScreen (Cmd: Emacs: ) - - Clears the screen and displays the current prompt and input at the top of the screen. - - DigitArgument (Cmd: unbound Emacs: ,,) - - Used to pass numeric arguments to functions like CharacterSearch or YankNthArg. - Alt+- toggles the argument to be negative/non-negative. To enter 80 '*' characters, - you could type Alt+8 Alt+0 *. - - CaptureScreen (Cmd: unbound Emacs: unbound) - - Copies selected lines to the clipboard in both text and rtf formats. Use up/down - arrow keys to the first line to select, then Shift+UpArrow/Shift+DownArrow to select - multiple lines. After selecting, press Enter to copy the text. Escape/Ctrl+C/Ctrl+G - will cancel so nothing is copied to the clipboard. - - InvokePrompt (Cmd: unbound Emacs: unbound) - - Erases the current prompt and calls the prompt function to redisplay - the prompt. Useful for custom key handlers that change state, e.g. - change the current directory. - - WhatIsKey (Cmd: Emacs: ) - - Read a key or chord and display the key binding. - - ShowKeyBindings (Cmd: Emacs: ) - - Show all of the currently bound keys. - - ScrollDisplayUp (Cmd: Emacs: ) - - Scroll the display up one screen. - - ScrollDisplayUpLine (Cmd: Emacs: ) - - Scroll the display up one line. - - ScrollDisplayDown (Cmd: Emacs: ) - - Scroll the display down one screen. - - ScrollDisplayDownLine (Cmd: Emacs: ) - - Scroll the display down one line. - - ScrollDisplayTop (Cmd: unbound Emacs: ) - - Scroll the display to the top. - - ScrollDisplayToCursor (Cmd: unbound Emacs: ) - - Scroll the display to the cursor. - - Custom Key Bindings - ------------------- - - PSReadline supports custom key bindings using the cmdlet Set-PSReadlineKeyHandler. Most - custom key bindings will call one of the above functions, for example: - - Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward - - You can bind a ScriptBlock to a key. The ScriptBlock can do pretty much anything you want. - Some useful examples include: - - * edit the command line - * opening a new window (e.g. help) - * change directories without changing the command line - - The ScriptBlock is passed two arguments: - - * $key - A [ConsoleKeyInfo] that is the key that triggered the custom binding. If you bind - the same ScriptBlock to multiple keys and need to perform different actions depending - on the key, you can check $key. Many custom bindings ignore this argument. - * $arg - An arbitrary argument. Most often, this would be an integer argument that the user - passes from the key bindings DigitArgument. If your binding doesn't accept arguments, - it's reasonable to ignore this argument. - - Let's take a look at an example that adds a command line to history without executing it. This is - useful when you realize you forgot to do something, but don't want to re-enter the command line - you've already entered. - - Set-PSReadlineKeyHandler -Key Alt+w ` - -BriefDescription SaveInHistory ` - -LongDescription "Save current line in history but do not execute" ` - -ScriptBlock { - param($key, $arg) # The arguments are ignored in this example - - # We need the command line, GetBufferState gives us that (with the cursor position) - $line = $null - $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) - - # AddToHistory saves the line in history, but does not execute the line. - [Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory($line) - - # RevertLine is like pressing Escape. - [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine() - } - - You can see many more examples in the file SamplePSReadlineProfile.ps1 which is installed in the - PSReadline module folder. - - Most key bindings will want to take advantage of some helper functions for editing the command - line those APIs are documented in the next section. - - Custom Key Binding Support APIs - ------------------------------- - - The following functions are public in Microsoft.PowerShell.PSConsoleReadline, but cannot be directly - bound to a key. Most are useful in custom key bindings. - - void AddToHistory(string command) - - Add a command line to history without executing it. - - void ClearKillRing() - - Clear the kill ring. This is mostly used for testing. - - void Delete(int start, int length) - - Delete length characters from start. This operation supports undo/redo. - - void Ding() - - Perform the Ding action based on the users preference. - - void GetBufferState([ref] string input, [ref] int cursor) - void GetBufferState([ref] Ast ast, [ref] Token[] tokens, [ref] ParseError[] parseErrors, [ref] int cursor) - - These two functions retrieve useful information about the current state of - the input buffer. The first is more commonly used for simple cases. The - second is used if your binding is doing something more advanced with the Ast. - - IEnumerable[Microsoft.PowerShell.KeyHandler] GetKeyHandlers(bool includeBound, bool includeUnbound) - - This function is used by Get-PSReadlineKeyHandler and probably isn't useful in a custom - key binding. - - Microsoft.PowerShell.PSConsoleReadlineOptions GetOptions() - - This function is used by Get-PSReadlineOption and probably isn't too useful in a custom - key binding. - - void GetSelectionState([ref] int start, [ref] int length) - - If there is no selection on the command line, -1 will be returned in both start and length. - If there is a selection on the command line, the start and length of the selection are returned. - - void Insert(char c) - void Insert(string s) - - Insert a character or string at the cursor. This operation supports undo/redo. - - string ReadLine(runspace remoteRunspace, System.Management.Automation.EngineIntrinsics engineIntrinsics) - - This is the main entry point to PSReadline. It does not support recursion, so is not useful - in a custom key binding. - - void RemoveKeyHandler(string[] key) - - This function is used by Remove-PSReadlineKeyHandler and probably isn't too useful in a - custom key binding. - - void Replace(int start, int length, string replacement) - - Replace some of the input. This operation supports undo/redo. - This is preferred over Delete followed by Insert because it is treated as a single action - for undo. - - void SetCursorPosition(int cursor) - - Move the cursor to the given offset. Cursor movement is not tracked for undo. - - void SetOptions(Microsoft.PowerShell.SetPSReadlineOption options) - - This function is a helper method used by the cmdlet Set-PSReadlineOption, but might be - useful to a custom key binding that wants to temporarily change a setting. - - bool TryGetArgAsInt(System.Object arg, [ref] int numericArg, int defaultNumericArg) - - This helper method is used for custom bindings that honor DigitArgument. A typical call - looks like: - - [int]$numericArg = 0 - [Microsoft.PowerShell.PSConsoleReadLine]::TryGetArgAsInt($arg, [ref]$numericArg, 1) - -POWERSHELL COMPATIBILITY - - PSReadline requires PowerShell version 3 or greater and the console host. It - will not work in the ISE. - -FEEDBACK - - https://github.com/lzybkr/PSReadline - -CONTRIBUTING TO PSREADLINE - - Feel free to submit a pull request or submit feedback on the github page. - -SEE ALSO - - PSReadline is heavily influenced by the GNU Readline library: - - http://tiswww.case.edu/php/chet/readline/rltop.html diff --git a/src/Microsoft.PowerShell.PSReadLine/packages.config b/src/Microsoft.PowerShell.PSReadLine/packages.config deleted file mode 100644 index 0dc84546ba9..00000000000 --- a/src/Microsoft.PowerShell.PSReadLine/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 267db6412c9..50d431225c7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -1,4 +1,4 @@ - + PowerShell SDK metapackage @@ -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 8be32293508..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -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 bf61ff71491..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs +++ /dev/null @@ -1,1279 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using System.Runtime.Serialization; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.IO; -using System.ComponentModel; -using System.Security.Permissions; -using System.Management.Automation.Host; - -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); - } // lock - } - - /// - /// 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 826b4cfdc75..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs +++ /dev/null @@ -1,2580 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Globalization; -using System.IO; -using System.Management.Automation.Tracing; -using System.Text.RegularExpressions; -using System.Security.Permissions; - -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 (System.Threading.ThreadAbortException 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 25ad74822b1..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobOptions.cs +++ /dev/null @@ -1,393 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.Serialization; -using System.Management.Automation; -using System.Security.Permissions; - -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 37d3eab4b23..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobSourceAdapter.cs +++ /dev/null @@ -1,1084 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.IO; -using System.Globalization; -using System.Runtime.Serialization; - -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 b73dbc7432b..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobStore.cs +++ /dev/null @@ -1,683 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using System.IO; -using System.Globalization; -using System.Management.Automation; -using System.Security.AccessControl; -using System.Security.Principal; - -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 1ac15ed476d..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobTrigger.cs +++ /dev/null @@ -1,880 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using System.Runtime.Serialization; -using System.Management.Automation; -using System.Globalization; -using System.Threading; -using Microsoft.Management.Infrastructure; -using System.Security.Permissions; - -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 e89df8051fe..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobWTS.cs +++ /dev/null @@ -1,947 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using TaskScheduler; -using System.Diagnostics; -using System.Globalization; -using System.Management.Automation; -using System.Runtime.InteropServices; -using System.Security.AccessControl; - -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 0bfb6d8ae5b..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/AddJobTrigger.cs +++ /dev/null @@ -1,139 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; - -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 a84de3eacea..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinition.cs +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 73553266f6b..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobDefinitionBase.cs +++ /dev/null @@ -1,149 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 e8d73e93f73..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/DisableJobTrigger.cs +++ /dev/null @@ -1,36 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; - -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 fc1e2ef5530..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableDisableCmdletBase.cs +++ /dev/null @@ -1,94 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -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 3344785d844..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobDefinition.cs +++ /dev/null @@ -1,32 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 54b8790aa17..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/EnableJobTrigger.cs +++ /dev/null @@ -1,36 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; - -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 a59869d902c..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobDefinition.cs +++ /dev/null @@ -1,96 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; - -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 469176d0f42..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetJobTrigger.cs +++ /dev/null @@ -1,140 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; - -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 be12fb0e3e9..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/GetScheduledJobOption.cs +++ /dev/null @@ -1,98 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 cabb4e3b66b..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/NewJobTrigger.cs +++ /dev/null @@ -1,327 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; - -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("RepetitionInterval") || MyInvocation.BoundParameters.ContainsKey("RepetitionDuration") || - MyInvocation.BoundParameters.ContainsKey("RepeatIndefinitely")) - { - if (MyInvocation.BoundParameters.ContainsKey("RepeatIndefinitely")) - { - if (MyInvocation.BoundParameters.ContainsKey("RepetitionDuration")) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepeatIndefinitelyParams); - } - if (!MyInvocation.BoundParameters.ContainsKey("RepetitionInterval")) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidRepetitionRepeatParams); - } - _repDuration = TimeSpan.MaxValue; - } - else if (!MyInvocation.BoundParameters.ContainsKey("RepetitionInterval") || !MyInvocation.BoundParameters.ContainsKey("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 224c11a8b5d..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/NewScheduledJobOption.cs +++ /dev/null @@ -1,45 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 b451a9fc168..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/RegisterJobDefinition.cs +++ /dev/null @@ -1,379 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using Microsoft.PowerShell.Commands; -using System.Diagnostics.CodeAnalysis; - -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)] - [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("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("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 757e52f39b9..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/RemoveJobTrigger.cs +++ /dev/null @@ -1,143 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Threading; -using System.Diagnostics; -using System.Globalization; - -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 25509d8cc5e..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SchedJobCmdletBase.cs +++ /dev/null @@ -1,467 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 135bcc9cb37..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/ScheduledJobOptionCmdletBase.cs +++ /dev/null @@ -1,194 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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("IdleTimeout") && - _idleTimeout < TimeSpan.Zero) - { - throw new PSArgumentException(ScheduledJobErrorStrings.InvalidIdleTimeout); - } - - if (MyInvocation.BoundParameters.ContainsKey("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 ca3f42809ba..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobDefinition.cs +++ /dev/null @@ -1,521 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; - -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)] - [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("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("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("RunAs32")) - { - if (newParameters.ContainsKey(ScheduledJobInvocationInfo.RunAs32Parameter)) - { - newParameters[ScheduledJobInvocationInfo.RunAs32Parameter] = RunAs32.ToBool(); - } - else - { - newParameters.Add(ScheduledJobInvocationInfo.RunAs32Parameter, RunAs32.ToBool()); - } - } - - // Authentication - if (MyInvocation.BoundParameters.ContainsKey("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 bfe72b9e073..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetJobTrigger.cs +++ /dev/null @@ -1,900 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation; -using System.Diagnostics; - -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(_paramAtStartup)) - { - switchParamList.Add(TriggerFrequency.AtStartup); - } - if (MyInvocation.BoundParameters.ContainsKey(_paramAtLogon)) - { - switchParamList.Add(TriggerFrequency.AtLogon); - } - if (MyInvocation.BoundParameters.ContainsKey(_paramOnce)) - { - switchParamList.Add(TriggerFrequency.Once); - } - if (MyInvocation.BoundParameters.ContainsKey(_paramDaily)) - { - switchParamList.Add(TriggerFrequency.Daily); - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeekly)) - { - 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(_paramDaysInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidAtTime, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramUser)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) || MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) || - MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerStartUpType); - WriteValidationError(msg); - return false; - } - - return true; - } - - private bool ValidateLogonParams() - { - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidAtTime, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) || MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) || - MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerLogonType); - WriteValidationError(msg); - return false; - } - - return true; - } - - private bool ValidateOnceParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramUser)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - _repDuration = TimeSpan.MaxValue; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) || MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) || - MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - // 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(_paramAt)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerOnceType); - WriteValidationError(msg); - return false; - } - } - - return true; - } - - private bool ValidateDailyParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval) && - _daysInterval < 1) - { - WriteValidationError(ScheduledJobErrorStrings.InvalidDaysIntervalParam); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidWeeksInterval, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramUser)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysOfWeek, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) || MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) || - MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - - if (trigger != null) - { - if (trigger.At == null && !MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - } - - return true; - } - - private bool ValidateWeeklyParams(ScheduledJobTrigger trigger = null) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidDaysInterval, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval) && - _weeksInterval < 1) - { - WriteValidationError(ScheduledJobErrorStrings.InvalidWeeksIntervalParam); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramUser)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidUser, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) || MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) || - MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInfiniteDuration)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.InvalidSetTriggerRepetition, ScheduledJobErrorStrings.TriggerWeeklyType); - WriteValidationError(msg); - return false; - } - - if (trigger != null) - { - if (trigger.At == null && !MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - string msg = StringUtil.Format(ScheduledJobErrorStrings.MissingAtTime, ScheduledJobErrorStrings.TriggerDailyType); - WriteValidationError(msg); - return false; - } - if ((trigger.DaysOfWeek == null || trigger.DaysOfWeek.Count == 0) && - !MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - 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(_paramRandomDelay)) - { - trigger.RandomDelay = _randomDelay; - } - } - - private void ModifyLogonTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramRandomDelay)) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramUser)) - { - trigger.User = string.IsNullOrEmpty(_user) ? ScheduledJobTrigger.AllUsers : _user; - } - } - - private void ModifyOnceTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramRandomDelay)) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval)) - { - trigger.RepetitionInterval = _repInterval; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration)) - { - trigger.RepetitionDuration = _repDuration; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - trigger.At = _atTime; - } - } - - private void ModifyDailyTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramRandomDelay)) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - trigger.At = _atTime; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval)) - { - trigger.Interval = _daysInterval; - } - } - - private void ModifyWeeklyTrigger(ScheduledJobTrigger trigger) - { - if (MyInvocation.BoundParameters.ContainsKey(_paramRandomDelay)) - { - trigger.RandomDelay = _randomDelay; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramAt)) - { - trigger.At = _atTime; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval)) - { - trigger.Interval = _weeksInterval; - } - - if (MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek)) - { - 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(_paramRandomDelay) ? _randomDelay : randomDelay; - trigger.User = MyInvocation.BoundParameters.ContainsKey(_paramUser) ? _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(_paramRandomDelay) ? _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(_paramRandomDelay) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(_paramAt) ? _atTime : atTime; - trigger.RepetitionInterval = MyInvocation.BoundParameters.ContainsKey(_paramRepetitionInterval) ? _repInterval : repInterval; - trigger.RepetitionDuration = MyInvocation.BoundParameters.ContainsKey(_paramRepetitionDuration) ? _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(_paramRandomDelay) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(_paramAt) ? _atTime : atTime; - trigger.Interval = MyInvocation.BoundParameters.ContainsKey(_paramDaysInterval) ? _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(_paramRandomDelay) ? _randomDelay : randomDelay; - trigger.At = MyInvocation.BoundParameters.ContainsKey(_paramAt) ? _atTime : atTime; - trigger.Interval = MyInvocation.BoundParameters.ContainsKey(_paramWeeksInterval) ? _weeksInterval : interval; - trigger.DaysOfWeek = MyInvocation.BoundParameters.ContainsKey(_paramDaysOfWeek) ? 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 - - #region Private Members - - private string _paramAtStartup = "AtStartup"; - private string _paramAtLogon = "AtLogon"; - private string _paramOnce = "Once"; - private string _paramDaily = "Daily"; - private string _paramWeekly = "Weekly"; - // - private string _paramDaysInterval = "DaysInterval"; - private string _paramWeeksInterval = "WeeksInterval"; - private string _paramRandomDelay = "RandomDelay"; - private string _paramRepetitionInterval = "RepetitionInterval"; - private string _paramRepetitionDuration = "RepetitionDuration"; - private string _paramRepetitionInfiniteDuration = "RepeatIndefinitely"; - private string _paramAt = "At"; - private string _paramUser = "User"; - private string _paramDaysOfWeek = "DaysOfWeek"; - - #endregion - } -} diff --git a/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs b/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs deleted file mode 100644 index ef329895bd2..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/SetScheduledJobOption.cs +++ /dev/null @@ -1,136 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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("StartIfOnBattery")) - { - _jobOptions.StartIfOnBatteries = StartIfOnBattery; - } - - if (MyInvocation.BoundParameters.ContainsKey("ContinueIfGoingOnBattery")) - { - _jobOptions.StopIfGoingOnBatteries = !ContinueIfGoingOnBattery; - } - - if (MyInvocation.BoundParameters.ContainsKey("WakeToRun")) - { - _jobOptions.WakeToRun = WakeToRun; - } - - if (MyInvocation.BoundParameters.ContainsKey("StartIfIdle")) - { - _jobOptions.StartIfNotIdle = !StartIfIdle; - } - - if (MyInvocation.BoundParameters.ContainsKey("StopIfGoingOffIdle")) - { - _jobOptions.StopIfGoingOffIdle = StopIfGoingOffIdle; - } - - if (MyInvocation.BoundParameters.ContainsKey("RestartOnIdleResume")) - { - _jobOptions.RestartOnIdleResume = RestartOnIdleResume; - } - - if (MyInvocation.BoundParameters.ContainsKey("HideInTaskScheduler")) - { - _jobOptions.ShowInTaskScheduler = !HideInTaskScheduler; - } - - if (MyInvocation.BoundParameters.ContainsKey("RunElevated")) - { - _jobOptions.RunElevated = RunElevated; - } - - if (MyInvocation.BoundParameters.ContainsKey("RequireNetwork")) - { - _jobOptions.RunWithoutNetwork = !RequireNetwork; - } - - if (MyInvocation.BoundParameters.ContainsKey("DoNotAllowDemandStart")) - { - _jobOptions.DoNotAllowDemandStart = DoNotAllowDemandStart; - } - - if (MyInvocation.BoundParameters.ContainsKey("IdleDuration")) - { - _jobOptions.IdleDuration = IdleDuration; - } - - if (MyInvocation.BoundParameters.ContainsKey("IdleTimeout")) - { - _jobOptions.IdleTimeout = IdleTimeout; - } - - if (MyInvocation.BoundParameters.ContainsKey("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 8eea6a6b00a..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/commands/UnregisterJobDefinition.cs +++ /dev/null @@ -1,150 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; - -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 56ea7888180..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 occured while attempting to rename scheduled job from {0} to {1}. - - - An error occured 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 cb9b17f03ba..a6dadabfed2 100644 --- a/src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj +++ b/src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj @@ -1,8 +1,8 @@ - + - PowerShell Core's Microsoft.PowerShell.Security project - $(NoWarn);CS1570 + PowerShell's Microsoft.PowerShell.Security project + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.Security @@ -10,10 +10,6 @@ - - $(DefineConstants);CORECLR - - @@ -21,16 +17,4 @@ - - portable - - - - $(DefineConstants);UNIX - - - - full - - 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/CmsCommands.resx b/src/Microsoft.PowerShell.Security/resources/CmsCommands.resx index 33eabae0556..9f47134f147 100644 --- a/src/Microsoft.PowerShell.Security/resources/CmsCommands.resx +++ b/src/Microsoft.PowerShell.Security/resources/CmsCommands.resx @@ -1,4 +1,4 @@ - + - + @@ -20,16 +21,4 @@ - - portable - - - - $(DefineConstants);UNIX - - - - full - - diff --git a/src/Microsoft.WSMan.Management/NewWSManSession.cs b/src/Microsoft.WSMan.Management/NewWSManSession.cs index ab730bcdd26..09b22af9924 100644 --- a/src/Microsoft.WSMan.Management/NewWSManSession.cs +++ b/src/Microsoft.WSMan.Management/NewWSManSession.cs @@ -1,21 +1,19 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; -using System.Reflection; -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Management.Automation; -using System.Management.Automation.Provider; -using System.Xml; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Provider; using System.Net; - - +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Xml; namespace Microsoft.WSMan.Management { @@ -25,15 +23,13 @@ namespace Microsoft.WSMan.Management /// Get-WSManInstance /// Set-WSManInstance /// Invoke-WSManAction - /// Connect-WSMan + /// Connect-WSMan. /// - - - [Cmdlet(VerbsCommon.New, "WSManSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141449")] + [Cmdlet(VerbsCommon.New, "WSManSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096845")] + [OutputType(typeof(SessionOption))] public class NewWSManSessionOptionCommand : PSCmdlet { /// - /// /// [Parameter] [ValidateNotNullOrEmpty] @@ -43,11 +39,13 @@ public ProxyAccessType ProxyAccessType { return _proxyaccesstype; } + set { _proxyaccesstype = value; } } + private ProxyAccessType _proxyaccesstype; /// @@ -57,18 +55,23 @@ public ProxyAccessType ProxyAccessType /// - Negotiate: Use the default authentication (ad defined by the underlying /// protocol) for establishing a remote connection. /// - Basic: Use basic authentication for establishing a remote connection - /// - Digest: Use Digest authentication for establishing a remote connection + /// - Digest: Use Digest authentication for establishing a remote connection. /// [Parameter] [ValidateNotNullOrEmpty] public ProxyAuthentication ProxyAuthentication { - get { return proxyauthentication; } + get + { + return proxyauthentication; + } + set { proxyauthentication = value; } } + private ProxyAuthentication proxyauthentication; /// @@ -79,14 +82,18 @@ public ProxyAuthentication ProxyAuthentication [Credential] public PSCredential ProxyCredential { - get { return _proxycredential; } + get + { + return _proxycredential; + } + set { _proxycredential = value; } } - private PSCredential _proxycredential; + private PSCredential _proxycredential; /// /// The following is the definition of the input parameter "SkipCACheck". @@ -94,104 +101,134 @@ public PSCredential ProxyCredential /// certificate is signed by a trusted certificate authority (CA). Use only when /// the remote computer is trusted by other means, for example, if the remote /// computer is part of a network that is physically secure and isolated or the - /// remote computer is listed as a trusted host in WinRM configuration + /// remote computer is listed as a trusted host in WinRM configuration. /// [Parameter] public SwitchParameter SkipCACheck { - get { return skipcacheck; } + get + { + return skipcacheck; + } + set { skipcacheck = value; } } + private bool skipcacheck; /// /// The following is the definition of the input parameter "SkipCNCheck". /// Indicates that certificate common name (CN) of the server need not match the /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines + /// option should only be used for trusted machines. /// [Parameter] public SwitchParameter SkipCNCheck { - get { return skipcncheck; } + get + { + return skipcncheck; + } + set { skipcncheck = value; } } + private bool skipcncheck; /// /// The following is the definition of the input parameter "SkipRevocation". /// Indicates that certificate common name (CN) of the server need not match the /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines + /// option should only be used for trusted machines. /// [Parameter] public SwitchParameter SkipRevocationCheck { - get { return skiprevocationcheck; } + get + { + return skiprevocationcheck; + } + set { skiprevocationcheck = value; } } + private bool skiprevocationcheck; /// /// The following is the definition of the input parameter "SPNPort". /// Appends port number to the connection Service Principal Name SPN of the /// remote server. - /// SPN is used when authentication mechanism is Kerberos or Negotiate + /// SPN is used when authentication mechanism is Kerberos or Negotiate. /// [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 { spnport = value; } } - private Int32 spnport; + + private int spnport; /// /// The following is the definition of the input parameter "Timeout". - /// Defines the timeout in ms for the wsman operation + /// Defines the timeout in ms for the wsman operation. /// [Parameter] [Alias("OperationTimeoutMSec")] - [ValidateRange(0, Int32.MaxValue)] - public Int32 OperationTimeout + [ValidateRange(0, int.MaxValue)] + public int OperationTimeout { - get { return operationtimeout; } + get + { + return operationtimeout; + } + set { operationtimeout = value; } } - private Int32 operationtimeout; + + private int operationtimeout; /// /// The following is the definition of the input parameter "UnEncrypted". /// Specifies that no encryption will be used when doing remote operations over /// http. Unencrypted traffic is not allowed by default and must be enabled in - /// the local configuration + /// the local configuration. /// [Parameter] public SwitchParameter NoEncryption { - get { return noencryption; } + get + { + return noencryption; + } + set { noencryption = value; } } + private bool noencryption; /// @@ -203,17 +240,18 @@ public SwitchParameter NoEncryption [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] public SwitchParameter UseUTF16 { - get { return useutf16; } + get + { + return useutf16; + } + set { useutf16 = value; } } - private bool useutf16; - - - + private bool useutf16; /// /// BeginProcessing method. @@ -241,9 +279,7 @@ protected override void BeginProcessing() return; } - - - //Creating the Session Object + // Creating the Session Object SessionOption objSessionOption = new SessionOption(); objSessionOption.SPNPort = spnport; @@ -252,7 +288,7 @@ protected override void BeginProcessing() objSessionOption.SkipCACheck = skipcacheck; objSessionOption.OperationTimeout = operationtimeout; objSessionOption.SkipRevocationCheck = skiprevocationcheck; - //Proxy Settings + // Proxy Settings objSessionOption.ProxyAccessType = _proxyaccesstype; objSessionOption.ProxyAuthentication = proxyauthentication; @@ -260,13 +296,14 @@ protected override void BeginProcessing() { objSessionOption.UseEncryption = false; } + if (_proxycredential != null) { NetworkCredential nwCredentials = _proxycredential.GetNetworkCredential(); objSessionOption.ProxyCredential = nwCredentials; } - WriteObject(objSessionOption); - }//End BeginProcessing() - }//End Class + WriteObject(objSessionOption); + } + } } diff --git a/src/Microsoft.WSMan.Management/PingWSMan.cs b/src/Microsoft.WSMan.Management/PingWSMan.cs index 21e9bd95695..88b443a6ef0 100644 --- a/src/Microsoft.WSMan.Management/PingWSMan.cs +++ b/src/Microsoft.WSMan.Management/PingWSMan.cs @@ -1,54 +1,57 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; -using System.Reflection; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Management.Automation; using System.Management.Automation.Provider; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Xml; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - namespace Microsoft.WSMan.Management { - #region Test-WSMAN /// /// Issues an operation against the remote machine to ensure that the wsman - /// service is running + /// service is running. /// - - [Cmdlet(VerbsDiagnostic.Test, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141464")] + [Cmdlet(VerbsDiagnostic.Test, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097114")] + [OutputType(typeof(XmlElement))] public class TestWSManCommand : AuthenticatingWSManCommand, IDisposable { /// /// The following is the definition of the input parameter "ComputerName". /// Executes the management operation on the specified computer. The default is /// the local computer. Type the fully qualified domain name, NETBIOS name or IP - /// address to indicate the remote host + /// address to indicate the remote host. /// [Parameter(Position = 0, ValueFromPipeline = true)] [Alias("cn")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "Authentication". @@ -73,13 +76,18 @@ public String ComputerName [Alias("auth", "am")] public override AuthenticationMechanism Authentication { - get { return authentication; } + get + { + return authentication; + } + set { authentication = value; ValidateSpecifiedAuthentication(); } } + private AuthenticationMechanism authentication = AuthenticationMechanism.None; /// @@ -88,13 +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". @@ -107,8 +117,10 @@ public Int32 Port public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } + private SwitchParameter usessl; /// @@ -117,38 +129,38 @@ public SwitchParameter UseSSL /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - public String ApplicationName + public string ApplicationName { get { return applicationname; } + set { applicationname = value; } } - private String applicationname = null; + private string applicationname = null; /// /// ProcessRecord method. /// protected override void ProcessRecord() { - WSManHelper helper = new WSManHelper(this); IWSManEx wsmanObject = (IWSManEx)new WSManClass(); - string connectionStr = String.Empty; + string connectionStr = string.Empty; connectionStr = helper.CreateConnectionString(null, port, computername, applicationname); IWSManSession m_SessionObj = null; try { m_SessionObj = helper.CreateSessionObject(wsmanObject, Authentication, null, Credential, connectionStr, CertificateThumbprint, usessl.IsPresent); - m_SessionObj.Timeout = 1000; //1 sec. we are putting this low so that Test-WSMan can return promptly if the server goes unresponsive. + m_SessionObj.Timeout = 1000; // 1 sec. we are putting this low so that Test-WSMan can return promptly if the server goes unresponsive. XmlDocument xmldoc = new XmlDocument(); xmldoc.LoadXml(m_SessionObj.Identify(0)); WriteObject(xmldoc.DocumentElement); } - catch(Exception) + catch (Exception) { try { - if (!String.IsNullOrEmpty(m_SessionObj.Error)) + if (!string.IsNullOrEmpty(m_SessionObj.Error)) { XmlDocument ErrorDoc = new XmlDocument(); ErrorDoc.LoadXml(m_SessionObj.Error); @@ -157,30 +169,30 @@ protected override void ProcessRecord() this.WriteError(er); } } - catch(Exception) - {} + catch (Exception) + { } } finally { if (m_SessionObj != null) Dispose(m_SessionObj); } - }//End BeginProcessing() + } #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -191,7 +203,6 @@ protected override void ProcessRecord() } #endregion IDisposable Members - - }//End Class + } #endregion } diff --git a/src/Microsoft.WSMan.Management/Set-QuickConfig.cs b/src/Microsoft.WSMan.Management/Set-QuickConfig.cs index 6edb2b0d0ed..9ad9e39c332 100644 --- a/src/Microsoft.WSMan.Management/Set-QuickConfig.cs +++ b/src/Microsoft.WSMan.Management/Set-QuickConfig.cs @@ -1,19 +1,18 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; -using System.Reflection; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Management.Automation; using System.Management.Automation.Provider; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Xml; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - namespace Microsoft.WSMan.Management { @@ -28,49 +27,56 @@ namespace Microsoft.WSMan.Management /// 2. Set the WinRM service type to auto start /// 3. Create a listener to accept request on any IP address. By default /// transport is http - /// 4. Enable firewall exception for WS-Management traffic + /// 4. Enable firewall exception for WS-Management traffic. /// - [Cmdlet(VerbsCommon.Set, "WSManQuickConfig", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141463")] + [Cmdlet(VerbsCommon.Set, "WSManQuickConfig", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097112")] + [OutputType(typeof(string))] public class SetWSManQuickConfigCommand : PSCmdlet, IDisposable { /// /// The following is the definition of the input parameter "UseSSL". /// Indicates a https listener to be created. If this switch is not specified - /// then by default a http listener will be created + /// then by default a http listener will be created. /// [Parameter] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")] public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } + private SwitchParameter usessl; - //helper variable + // helper variable private WSManHelper helper; /// /// Property that sets force parameter. This will allow /// configuring WinRM without prompting the user. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return force; } + set { force = value; } } + private bool force = false; /// /// Property that will allow configuring WinRM with Public profile exception enabled. /// - [Parameter()] + [Parameter] public SwitchParameter SkipNetworkProfileCheck { get { return skipNetworkProfileCheck; } + set { skipNetworkProfileCheck = value; } } + private bool skipNetworkProfileCheck = false; /// @@ -78,24 +84,22 @@ public SwitchParameter SkipNetworkProfileCheck /// protected override void BeginProcessing() { - //If not running elevated, then throw an "elevation required" error message. + // If not running elevated, then throw an "elevation required" error message. WSManHelper.ThrowIfNotAdministrator(); helper = new WSManHelper(this); - String query = helper.GetResourceMsgFromResourcetext("QuickConfigContinueQuery"); - String caption = helper.GetResourceMsgFromResourcetext("QuickConfigContinueCaption"); + string query = helper.GetResourceMsgFromResourcetext("QuickConfigContinueQuery"); + string caption = helper.GetResourceMsgFromResourcetext("QuickConfigContinueCaption"); if (!force && !ShouldContinue(query, caption)) { return; } + QuickConfigRemoting(true); QuickConfigRemoting(false); - }//End BeginProcessing() - - + } #region private - private void QuickConfigRemoting(bool serviceonly) { IWSManSession m_SessionObj = null; @@ -112,7 +116,6 @@ private void QuickConfigRemoting(bool serviceonly) string xpathStatus = string.Empty; string xpathResult = string.Empty; - if (!usessl) { transport = "http"; @@ -129,12 +132,11 @@ private void QuickConfigRemoting(bool serviceonly) } else { - string openAllProfiles = skipNetworkProfileCheck ? "" : String.Empty; + string openAllProfiles = skipNetworkProfileCheck ? "" : string.Empty; analysisInputXml = @"" + transport + "" + openAllProfiles + ""; action = "Analyze"; } - string analysisOutputXml = m_SessionObj.Invoke(action, "winrm/config/service", analysisInputXml, 0); XmlDocument resultopxml = new XmlDocument(); resultopxml.LoadXml(analysisOutputXml); @@ -152,8 +154,6 @@ private void QuickConfigRemoting(bool serviceonly) xpathUpdate = "/cfg:Analyze_OUTPUT/cfg:EnableRemoting_INPUT"; } - - XmlNamespaceManager nsmgr = new XmlNamespaceManager(resultopxml.NameTable); nsmgr.AddNamespace("cfg", "http://schemas.microsoft.com/wbem/wsman/1/config/service"); string enabled = resultopxml.SelectSingleNode(xpathEnabled, nsmgr).InnerText; @@ -163,10 +163,11 @@ private void QuickConfigRemoting(bool serviceonly) { source = sourceAttribute.Value; } - string rxml = ""; + + string rxml = string.Empty; if (enabled.Equals("true")) { - string Err_Msg = ""; + string Err_Msg = string.Empty; if (serviceonly) { Err_Msg = WSManResourceLoader.GetResourceString("L_QuickConfigNoServiceChangesNeeded_Message"); @@ -181,6 +182,7 @@ private void QuickConfigRemoting(bool serviceonly) WriteObject(Err_Msg); return; } + if (!enabled.Equals("false")) { ArgumentException e = new ArgumentException(WSManResourceLoader.GetResourceString("L_QuickConfig_InvalidBool_0_ErrorMessage")); @@ -190,9 +192,9 @@ private void QuickConfigRemoting(bool serviceonly) } string resultAction = resultopxml.SelectSingleNode(xpathText, nsmgr).InnerText; - if ( source != null && source.Equals("GPO")) + if (source != null && source.Equals("GPO")) { - String Info_Msg = WSManResourceLoader.GetResourceString("L_QuickConfig_RemotingDisabledbyGP_00_ErrorMessage"); + string Info_Msg = WSManResourceLoader.GetResourceString("L_QuickConfig_RemotingDisabledbyGP_00_ErrorMessage"); Info_Msg += " " + resultAction; ArgumentException e = new ArgumentException(Info_Msg); WriteError(new ErrorRecord(e, "NotSpecified", ErrorCategory.NotSpecified, null)); @@ -200,7 +202,7 @@ private void QuickConfigRemoting(bool serviceonly) } string inputXml = resultopxml.SelectSingleNode(xpathUpdate, nsmgr).OuterXml; - if (resultAction.Equals("") || inputXml.Equals("")) + if (resultAction.Equals(string.Empty) || inputXml.Equals(string.Empty)) { ArgumentException e = new ArgumentException(WSManResourceLoader.GetResourceString("L_ERR_Message") + WSManResourceLoader.GetResourceString("L_QuickConfig_MissingUpdateXml_0_ErrorMessage")); ErrorRecord er = new ErrorRecord(e, "InvalidOperation", ErrorCategory.InvalidOperation, null); @@ -216,6 +218,7 @@ private void QuickConfigRemoting(bool serviceonly) { action = "EnableRemoting"; } + rxml = m_SessionObj.Invoke(action, "winrm/config/service", inputXml, 0); XmlDocument finalxml = new XmlDocument(); finalxml.LoadXml(rxml); @@ -230,7 +233,8 @@ private void QuickConfigRemoting(bool serviceonly) xpathStatus = "/cfg:EnableRemoting_OUTPUT/cfg:Status"; xpathResult = "/cfg:EnableRemoting_OUTPUT/cfg:Results"; } - if (finalxml.SelectSingleNode(xpathStatus, nsmgr).InnerText.ToString().Equals("succeeded")) + + if (finalxml.SelectSingleNode(xpathStatus, nsmgr).InnerText.Equals("succeeded")) { if (serviceonly) { @@ -240,6 +244,7 @@ private void QuickConfigRemoting(bool serviceonly) { WriteObject(WSManResourceLoader.GetResourceString("L_QuickConfigUpdated_Message")); } + WriteObject(finalxml.SelectSingleNode(xpathResult, nsmgr).InnerText); } else @@ -249,13 +254,13 @@ private void QuickConfigRemoting(bool serviceonly) } finally { - if (!String.IsNullOrEmpty(m_SessionObj.Error)) + if (!string.IsNullOrEmpty(m_SessionObj.Error)) { helper.AssertError(m_SessionObj.Error, true, null); } + if (m_SessionObj != null) Dispose(m_SessionObj); - } } #endregion private @@ -263,17 +268,17 @@ private void QuickConfigRemoting(bool serviceonly) #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -284,7 +289,6 @@ private void QuickConfigRemoting(bool serviceonly) } #endregion IDisposable Members - - }//End Class + } #endregion Set-WsManQuickConfig } diff --git a/src/Microsoft.WSMan.Management/WSManConnections.cs b/src/Microsoft.WSMan.Management/WSManConnections.cs index dd8b14e20cf..cac5196740f 100644 --- a/src/Microsoft.WSMan.Management/WSManConnections.cs +++ b/src/Microsoft.WSMan.Management/WSManConnections.cs @@ -1,21 +1,21 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; -using System.Reflection; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Management.Automation; using System.Management.Automation.Provider; -using System.Xml; -using System.Collections; -using System.Collections.Generic; using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; -using Dbg = System.Management.Automation; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Xml; +using Dbg = System.Management.Automation; namespace Microsoft.WSMan.Management { @@ -23,7 +23,7 @@ namespace Microsoft.WSMan.Management /// /// Common base class for all WSMan cmdlets that - /// take Authentication, CertificateThumbprint and Credential parameters + /// take Authentication, CertificateThumbprint and Credential parameters. /// public class AuthenticatingWSManCommand : PSCmdlet { @@ -38,13 +38,18 @@ public class AuthenticatingWSManCommand : PSCmdlet [Alias("cred", "c")] public virtual PSCredential Credential { - get { return credential; } + get + { + return credential; + } + set { credential = value; ValidateSpecifiedAuthentication(); } } + private PSCredential credential; /// @@ -67,13 +72,18 @@ public virtual PSCredential Credential [Alias("auth", "am")] public virtual AuthenticationMechanism Authentication { - get { return authentication; } + get + { + return authentication; + } + set { authentication = value; ValidateSpecifiedAuthentication(); } } + private AuthenticationMechanism authentication = AuthenticationMechanism.Default; /// @@ -84,13 +94,18 @@ public virtual AuthenticationMechanism Authentication [ValidateNotNullOrEmpty] public virtual string CertificateThumbprint { - get { return thumbPrint; } + get + { + return thumbPrint; + } + set { thumbPrint = value; ValidateSpecifiedAuthentication(); } } + private string thumbPrint = null; internal void ValidateSpecifiedAuthentication() @@ -106,12 +121,11 @@ internal void ValidateSpecifiedAuthentication() #region Connect-WsMan /// - /// connect wsman cmdlet + /// Connect wsman cmdlet. /// - [Cmdlet(VerbsCommunications.Connect, "WSMan", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141437")] + [Cmdlet(VerbsCommunications.Connect, "WSMan", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096841")] public class ConnectWSManCommand : AuthenticatingWSManCommand { - #region Parameters /// @@ -120,12 +134,14 @@ public class ConnectWSManCommand : AuthenticatingWSManCommand /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - public String ApplicationName + public string ApplicationName { get { return applicationname; } + set { applicationname = value; } } - private String applicationname = null; + + private string applicationname = null; /// /// The following is the definition of the input parameter "ComputerName". @@ -135,19 +151,24 @@ public String ApplicationName /// [Parameter(ParameterSetName = "ComputerName", Position = 0)] [Alias("cn")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "ConnectionURI". @@ -161,8 +182,10 @@ public String ComputerName public Uri ConnectionURI { get { return connectionuri; } + set { connectionuri = value; } } + private Uri connectionuri; /// @@ -177,8 +200,10 @@ public Uri ConnectionURI public Hashtable OptionSet { get { return optionset; } + set { optionset = value; } } + private Hashtable optionset; /// @@ -188,18 +213,20 @@ 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". /// Defines a set of extended options for the WSMan session. This hashtable can - /// be created using New-WSManSessionOption + /// be created using New-WSManSessionOption. /// [Parameter] [ValidateNotNullOrEmpty] @@ -208,8 +235,10 @@ public Int32 Port public SessionOption SessionOption { get { return sessionoption; } + set { sessionoption = value; } } + private SessionOption sessionoption; /// @@ -223,11 +252,11 @@ public SessionOption SessionOption public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } - private SwitchParameter usessl; - + private SwitchParameter usessl; #endregion @@ -236,15 +265,14 @@ public SwitchParameter UseSSL /// protected override void BeginProcessing() { - WSManHelper helper = new WSManHelper(this); if (connectionuri != null) { try { - //always in the format http://server:port/applicationname - string[] constrsplit = connectionuri.OriginalString.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // always in the format http://server:port/applicationname + string[] constrsplit = connectionuri.OriginalString.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } catch (IndexOutOfRangeException) @@ -252,30 +280,27 @@ protected override void BeginProcessing() helper.AssertError(helper.GetResourceMsgFromResourcetext("NotProperURI"), false, connectionuri); } } - string crtComputerName = computername; - if (crtComputerName == null) - { - crtComputerName = "localhost"; - } - if (this.SessionState.Path.CurrentProviderLocation(WSManStringLiterals.rootpath).Path.StartsWith(this.SessionState.Drive.Current.Name + ":" + WSManStringLiterals.DefaultPathSeparator + crtComputerName, StringComparison.CurrentCultureIgnoreCase)) + + string crtComputerName = computername ?? "localhost"; + + if (this.SessionState.Path.CurrentProviderLocation(WSManStringLiterals.rootpath).Path.StartsWith(this.SessionState.Drive.Current.Name + ":" + WSManStringLiterals.DefaultPathSeparator + crtComputerName, StringComparison.OrdinalIgnoreCase)) { helper.AssertError(helper.GetResourceMsgFromResourcetext("ConnectFailure"), false, computername); } - helper.CreateWsManConnection(ParameterSetName, connectionuri, port, computername, applicationname, usessl.IsPresent, Authentication, sessionoption, Credential, CertificateThumbprint); - }//End BeginProcessing() - }//end class + helper.CreateWsManConnection(ParameterSetName, connectionuri, port, computername, applicationname, usessl.IsPresent, Authentication, sessionoption, Credential, CertificateThumbprint); + } + } #endregion - # region Disconnect-WSMAN + #region Disconnect-WSMAN /// /// The following is the definition of the input parameter "ComputerName". /// Executes the management operation on the specified computer(s). The default /// 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=141439")] + [Cmdlet(VerbsCommunications.Disconnect, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096839")] public class DisconnectWSManCommand : PSCmdlet, IDisposable { /// @@ -285,35 +310,39 @@ public class DisconnectWSManCommand : PSCmdlet, IDisposable /// IP address to indicate the remote host(s) /// [Parameter(Position = 0)] - public String ComputerName + public string ComputerName { - get { return computername; } - set + get { + return computername; + } + set + { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -321,7 +350,6 @@ public String ComputerName { session = null; this.Dispose(); - } #endregion IDisposable Members @@ -332,15 +360,14 @@ public String ComputerName protected override void BeginProcessing() { WSManHelper helper = new WSManHelper(this); - if (computername == null) - { - computername = "localhost"; - } - if (this.SessionState.Path.CurrentProviderLocation(WSManStringLiterals.rootpath).Path.StartsWith(WSManStringLiterals.rootpath + ":" + WSManStringLiterals.DefaultPathSeparator + computername, StringComparison.CurrentCultureIgnoreCase)) + computername ??= "localhost"; + + if (this.SessionState.Path.CurrentProviderLocation(WSManStringLiterals.rootpath).Path.StartsWith(WSManStringLiterals.rootpath + ":" + WSManStringLiterals.DefaultPathSeparator + computername, StringComparison.OrdinalIgnoreCase)) { helper.AssertError(helper.GetResourceMsgFromResourcetext("DisconnectFailure"), false, computername); } - if (computername.Equals("localhost", StringComparison.CurrentCultureIgnoreCase)) + + if (computername.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { helper.AssertError(helper.GetResourceMsgFromResourcetext("LocalHost"), false, computername); } @@ -354,10 +381,7 @@ protected override void BeginProcessing() { helper.AssertError(helper.GetResourceMsgFromResourcetext("InvalidComputerName"), false, computername); } - }//End BeginProcessing() - - - - }//End Class + } + } #endregion Disconnect-WSMAN } diff --git a/src/Microsoft.WSMan.Management/WSManInstance.cs b/src/Microsoft.WSMan.Management/WSManInstance.cs index 96dfa8e8434..c96b002123d 100644 --- a/src/Microsoft.WSMan.Management/WSManInstance.cs +++ b/src/Microsoft.WSMan.Management/WSManInstance.cs @@ -1,22 +1,22 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; -using System.Reflection; -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; -using System.Management.Automation; -using System.Management.Automation.Provider; -using System.Xml; using System.Collections; using System.Collections.Generic; -using System.Management.Automation.Runspaces; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using Dbg = System.Management.Automation; using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Provider; +using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Xml; +using Dbg = System.Management.Automation; namespace Microsoft.WSMan.Management { @@ -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=141444")] + [Cmdlet(VerbsCommon.Get, "WSManInstance", DefaultParameterSetName = "GetInstance", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096627")] + [OutputType(typeof(XmlElement))] public class GetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { #region parameter @@ -39,32 +39,42 @@ public class GetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable /// [Parameter(ParameterSetName = "GetInstance")] [Parameter(ParameterSetName = "Enumerate")] - public String ApplicationName + public string ApplicationName { - get { return applicationname; } + get + { + return applicationname; + } + set { { applicationname = value; } } } - private String applicationname = null; + + private string applicationname = null; /// /// The following is the definition of the input parameter "BasePropertiesOnly". /// Enumerate only those properties that are part of the base class /// specification in the Resource URI. When - /// Shallow is specified then this flag has no effect + /// Shallow is specified then this flag has no effect. /// [Parameter(ParameterSetName = "Enumerate")] [Alias("UBPO", "Base")] public SwitchParameter BasePropertiesOnly { - get { return basepropertiesonly; } + get + { + return basepropertiesonly; + } + set { { basepropertiesonly = value; } } } + private SwitchParameter basepropertiesonly; /// @@ -76,19 +86,24 @@ public SwitchParameter BasePropertiesOnly [Parameter(ParameterSetName = "GetInstance")] [Parameter(ParameterSetName = "Enumerate")] [Alias("CN")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "ConnectionURI". @@ -96,7 +111,6 @@ public String ComputerName /// remote machine. The format of this string is: /// transport://server:port/Prefix. /// - [Parameter( ParameterSetName = "GetInstance")] [Parameter( @@ -105,80 +119,103 @@ public String ComputerName [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] public Uri ConnectionURI { - get { return connectionuri; } + get + { + return connectionuri; + } + set { { connectionuri = value; } } } + private Uri connectionuri; /// /// The following is the definition of the input parameter "Dialect". - /// Defines the dialect for the filter predicate + /// Defines the dialect for the filter predicate. /// [Parameter] public Uri Dialect { - get { return dialect; } + get + { + return dialect; + } + set { { dialect = value; } } } + private Uri dialect; /// /// The following is the definition of the input parameter "Enumerate". /// Switch indicates list all instances of a management resource. Equivalent to - /// WSManagement Enumerate + /// WSManagement Enumerate. /// - [Parameter(Mandatory = true, ParameterSetName = "Enumerate")] public SwitchParameter Enumerate { - get { return enumerate; } + get + { + return enumerate; + } + set { { enumerate = value; } } } + private SwitchParameter enumerate; /// /// The following is the definition of the input parameter "Filter". - /// Indicates the filter expression for the enumeration + /// Indicates the filter expression for the enumeration. /// [Parameter(ParameterSetName = "Enumerate")] [ValidateNotNullOrEmpty] - public String Filter + public string Filter { - get { return filter; } + get + { + return filter; + } + set { { filter = value; } } } - private String filter; + + private string filter; /// /// The following is the definition of the input parameter "Fragment". /// Specifies a section inside the instance that is to be updated or retrieved - /// for the given operation + /// for the given operation. /// - [Parameter(ParameterSetName = "GetInstance")] [ValidateNotNullOrEmpty] - public String Fragment + public string Fragment { - get { return fragment; } + get + { + return fragment; + } + set { { fragment = value; } } } - private String fragment; + + private string fragment; /// /// The following is the definition of the input parameter "OptionSet". @@ -192,12 +229,17 @@ public String Fragment [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Hashtable OptionSet { - get { return optionset; } + get + { + return optionset; + } + set { { optionset = value; } } } + private Hashtable optionset; /// @@ -206,37 +248,46 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "Enumerate")] [Parameter(ParameterSetName = "GetInstance")] - public Int32 Port + public int Port { - get { return port; } + get + { + return port; + } + set { { port = value; } } } - private Int32 port = 0; + + private int port = 0; /// /// The following is the definition of the input parameter "Associations". /// Associations indicates retrieval of association instances as opposed to /// associated instances. This can only be used when specifying the Dialect as - /// Association + /// Association. /// - [Parameter(ParameterSetName = "Enumerate")] public SwitchParameter Associations { - get { return associations; } + get + { + return associations; + } + set { { associations = value; } } } + private SwitchParameter associations; /// /// The following is the definition of the input parameter "ResourceURI". - /// URI of the resource class/instance representation + /// URI of the resource class/instance representation. /// [Parameter(Mandatory = true, Position = 0, @@ -247,12 +298,17 @@ public SwitchParameter Associations [Alias("RURI")] public Uri ResourceURI { - get { return resourceuri; } + get + { + return resourceuri; + } + set { { resourceuri = value; } } } + private Uri resourceuri; /// @@ -265,28 +321,33 @@ public Uri ResourceURI /// are returned. EPRs contain information about the Resource URI and selectors /// for the instance /// If ObjectAndEPR is specified, then both the object and the associated EPRs - /// are returned + /// are returned. /// [Parameter(ParameterSetName = "Enumerate")] [ValidateNotNullOrEmpty] - [ValidateSetAttribute(new string[] { "object", "epr", "objectandepr" })] + [ValidateSet(new string[] { "object", "epr", "objectandepr" })] [Alias("RT")] - public String ReturnType + public string ReturnType { - get { return returntype; } + get + { + return returntype; + } + set { { returntype = value; } } } - private String returntype="object"; + + private string returntype = "object"; /// /// 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 - /// class + /// management resource if there are more than 1 instance of the resource + /// class. /// [Parameter( ParameterSetName = "GetInstance")] @@ -294,18 +355,23 @@ public String ReturnType [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Hashtable SelectorSet { - get { return selectorset; } + get + { + return selectorset; + } + set { { selectorset = value; } } } + private Hashtable selectorset; /// /// The following is the definition of the input parameter "SessionOption". /// Defines a set of extended options for the WSMan session. This can be - /// created by using the cmdlet New-WSManSessionOption + /// created by using the cmdlet New-WSManSessionOption. /// [Parameter] [ValidateNotNullOrEmpty] @@ -313,30 +379,40 @@ public Hashtable SelectorSet [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public SessionOption SessionOption { - get { return sessionoption; } + get + { + return sessionoption; + } + set { { sessionoption = value; } } } + private SessionOption sessionoption; /// /// The following is the definition of the input parameter "Shallow". /// Enumerate only instances of the base class specified in the resource URI. If /// this flag is not specified, instances of the base class specified in the URI - /// and all its derived classes are returned + /// and all its derived classes are returned. /// [Parameter(ParameterSetName = "Enumerate")] public SwitchParameter Shallow { - get { return shallow; } + get + { + return shallow; + } + set { { shallow = value; } } } + private SwitchParameter shallow; /// @@ -352,37 +428,45 @@ public SwitchParameter Shallow [Alias("SSL")] public SwitchParameter UseSSL { - get { return usessl; } + get + { + return usessl; + } + set { { usessl = value; } } } + private SwitchParameter usessl; #endregion parameter - # region private - WSManHelper helper; + #region private + private WSManHelper helper; + private string GetFilter() { string name; string value; - string[] Split = filter.Trim().Split(new Char[] { '=', ';' }); - if ((Split.Length)%2 != 0) + string[] Split = filter.Trim().Split(new char[] { '=', ';' }); + if ((Split.Length) % 2 != 0) { - //mismatched property name/value pair + // mismatched property name/value pair return null; } + filter = ""; - for (int i = 0; i" + value + ""; } - filter = filter + ""; - return (filter.ToString()); + + filter += ""; + return (filter); } private void ReturnEnumeration(IWSManEx wsmanObject, IWSManResourceLocator wsmanResourceLocator, IWSManSession wsmanSession) @@ -394,17 +478,17 @@ private void ReturnEnumeration(IWSManEx wsmanObject, IWSManResourceLocator wsman IWSManEnumerator obj; if (returntype != null) { - if (returntype.Equals("object", StringComparison.CurrentCultureIgnoreCase)) + if (returntype.Equals("object", StringComparison.OrdinalIgnoreCase)) { flags = wsmanObject.EnumerationFlagReturnObject(); } - else if (returntype.Equals("epr", StringComparison.CurrentCultureIgnoreCase)) + else if (returntype.Equals("epr", StringComparison.OrdinalIgnoreCase)) { - flags = wsmanObject.EnumerationFlagReturnEPR(); + flags = wsmanObject.EnumerationFlagReturnEPR(); } else { - flags = wsmanObject.EnumerationFlagReturnObjectAndEPR(); + flags = wsmanObject.EnumerationFlagReturnObjectAndEPR(); } } @@ -420,44 +504,46 @@ private void ReturnEnumeration(IWSManEx wsmanObject, IWSManResourceLocator wsman { flags |= wsmanObject.EnumerationFlagHierarchyDeep(); } + if (dialect != null && filter != null) { - - if (dialect.ToString().Equals(helper.ALIAS_WQL, StringComparison.CurrentCultureIgnoreCase) || dialect.ToString().Equals(helper.URI_WQL_DIALECT, StringComparison.CurrentCultureIgnoreCase)) + if (dialect.ToString().Equals(helper.ALIAS_WQL, StringComparison.OrdinalIgnoreCase) || dialect.ToString().Equals(helper.URI_WQL_DIALECT, StringComparison.OrdinalIgnoreCase)) { fragment = helper.URI_WQL_DIALECT; dialect = new Uri(fragment); } - else if (dialect.ToString().Equals(helper.ALIAS_ASSOCIATION, StringComparison.CurrentCultureIgnoreCase) || dialect.ToString().Equals(helper.URI_ASSOCIATION_DIALECT, StringComparison.CurrentCultureIgnoreCase)) + else if (dialect.ToString().Equals(helper.ALIAS_ASSOCIATION, StringComparison.OrdinalIgnoreCase) || dialect.ToString().Equals(helper.URI_ASSOCIATION_DIALECT, StringComparison.OrdinalIgnoreCase)) { - if (associations) - { - flags |= wsmanObject.EnumerationFlagAssociationInstance(); - } - else - { - flags |= wsmanObject.EnumerationFlagAssociatedInstance(); - } - fragment = helper.URI_ASSOCIATION_DIALECT; - dialect = new Uri(fragment); + if (associations) + { + flags |= wsmanObject.EnumerationFlagAssociationInstance(); + } + else + { + flags |= wsmanObject.EnumerationFlagAssociatedInstance(); + } + + fragment = helper.URI_ASSOCIATION_DIALECT; + dialect = new Uri(fragment); } - else if (dialect.ToString().Equals(helper.ALIAS_SELECTOR, StringComparison.CurrentCultureIgnoreCase) || dialect.ToString().Equals(helper.URI_SELECTOR_DIALECT, StringComparison.CurrentCultureIgnoreCase)) + else if (dialect.ToString().Equals(helper.ALIAS_SELECTOR, StringComparison.OrdinalIgnoreCase) || dialect.ToString().Equals(helper.URI_SELECTOR_DIALECT, StringComparison.OrdinalIgnoreCase)) { - filter = GetFilter(); - fragment = helper.URI_SELECTOR_DIALECT; - dialect = new Uri(fragment); + filter = GetFilter(); + fragment = helper.URI_SELECTOR_DIALECT; + dialect = new Uri(fragment); } + obj = (IWSManEnumerator)wsmanSession.Enumerate(wsmanResourceLocator, filter, dialect.ToString(), flags); } else if (filter != null) { - fragment = helper.URI_WQL_DIALECT; - dialect = new Uri(fragment); - obj = (IWSManEnumerator)wsmanSession.Enumerate(wsmanResourceLocator, filter, dialect.ToString(), flags); + fragment = helper.URI_WQL_DIALECT; + dialect = new Uri(fragment); + obj = (IWSManEnumerator)wsmanSession.Enumerate(wsmanResourceLocator, filter, dialect.ToString(), flags); } else { - obj = (IWSManEnumerator)wsmanSession.Enumerate(wsmanResourceLocator, filter, null, flags); + obj = (IWSManEnumerator)wsmanSession.Enumerate(wsmanResourceLocator, filter, null, flags); } while (!obj.AtEndOfStream) { @@ -472,8 +558,8 @@ private void ReturnEnumeration(IWSManEx wsmanObject, IWSManResourceLocator wsman WriteError(er); } } - # endregion private - # region override + #endregion private + #region override /// /// ProcessRecord method. /// @@ -489,17 +575,17 @@ protected override void ProcessRecord() { try { - //in the format http(s)://server[:port/applicationname] - string[] constrsplit = connectionuri.OriginalString.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // in the format http(s)://server[:port/applicationname] + string[] constrsplit = connectionuri.OriginalString.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } catch (IndexOutOfRangeException) { helper.AssertError(helper.GetResourceMsgFromResourcetext("NotProperURI"), false, connectionuri); } - } + try { IWSManResourceLocator m_resource = helper.InitializeResourceLocator(optionset, selectorset, fragment, dialect, m_wsmanObject, resourceuri); @@ -512,7 +598,7 @@ protected override void ProcessRecord() { xmldoc.LoadXml(m_session.Get(m_resource, 0)); } - catch(XmlException ex) + catch (XmlException ex) { helper.AssertError(ex.Message, false, computername); } @@ -537,18 +623,19 @@ protected override void ProcessRecord() helper.AssertError(ex.Message, false, computername); } } - } finally { - if (!String.IsNullOrEmpty(m_wsmanObject.Error)) + if (!string.IsNullOrEmpty(m_wsmanObject.Error)) { helper.AssertError(m_wsmanObject.Error, true, resourceuri); } - if (!String.IsNullOrEmpty(m_session.Error)) + + if (!string.IsNullOrEmpty(m_session.Error)) { helper.AssertError(m_session.Error, true, resourceuri); } + if (m_session != null) Dispose(m_session); } @@ -557,17 +644,17 @@ protected override void ProcessRecord() #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -579,17 +666,13 @@ protected override void ProcessRecord() #endregion IDisposable Members - /// /// BeginProcessing method. /// protected override void EndProcessing() { - helper.CleanUp(); } - - } #endregion @@ -602,10 +685,10 @@ protected override void EndProcessing() /// Set-WSManInstance -Action StartService -ResourceURI wmicimv2/Win32_Service /// -SelectorSet {Name=Spooler} /// - [Cmdlet(VerbsCommon.Set, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141458")] + [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 /// /// The following is the definition of the input parameter "ApplicationName". @@ -613,12 +696,14 @@ public class SetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - public String ApplicationName + public string ApplicationName { get { return applicationname; } + set { applicationname = value; } } - private String applicationname = null; + + private string applicationname = null; /// /// The following is the definition of the input parameter "ComputerName". @@ -628,19 +713,24 @@ public String ApplicationName /// [Parameter(ParameterSetName = "ComputerName")] [Alias("cn")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "ConnectionURI". @@ -654,60 +744,67 @@ public String ComputerName public Uri ConnectionURI { get { return connectionuri; } + set { connectionuri = value; } } + private Uri connectionuri; /// /// The following is the definition of the input parameter "Dialect". - /// Defines the dialect for the filter predicate + /// Defines the dialect for the filter predicate. /// [Parameter] [ValidateNotNullOrEmpty] public Uri Dialect { get { return dialect; } + set { dialect = value; } } + private Uri dialect; /// /// The following is the definition of the input parameter "FilePath". /// Updates the management resource specified by the ResourceURI and SelectorSet - /// via this input file + /// via this input file. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [Alias("Path")] [ValidateNotNullOrEmpty] public string FilePath { get { return filepath; } + set { filepath = value; } } - private string filepath; + private string filepath; /// /// The following is the definition of the input parameter "Fragment". /// Specifies a section inside the instance that is to be updated or retrieved - /// for the given operation + /// for the given operation. /// [Parameter(ParameterSetName = "ComputerName")] [Parameter(ParameterSetName = "URI")] [ValidateNotNullOrEmpty] - public String Fragment + public string Fragment { get { return fragment; } + set { fragment = value; } } - private String fragment; + + private string fragment; /// /// The following is the definition of the input parameter "OptionSet". /// OptionSet is a hahs table which help modify or refine the nature of the /// request. These are similar to switches used in command line shells in that - /// they are service-specific + /// they are service-specific. /// - [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [Alias("os")] @@ -715,8 +812,10 @@ public String Fragment public Hashtable OptionSet { get { return optionset; } + set { optionset = value; } } + private Hashtable optionset; /// @@ -725,19 +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 + /// URI of the resource class/instance representation. /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Resourceuri")] @@ -747,15 +847,17 @@ public Int32 Port public Uri ResourceURI { get { return resourceuri; } + set { resourceuri = value; } } + private 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 - /// class + /// management resource if there are more than 1 instance of the resource + /// class. /// [Parameter(Position = 1, ValueFromPipeline = true, @@ -765,14 +867,16 @@ public Uri ResourceURI public Hashtable SelectorSet { get { return selectorset; } + set { selectorset = value; } } + private Hashtable selectorset; /// /// The following is the definition of the input parameter "SessionOption". /// Defines a set of extended options for the WSMan session. This can be created - /// by using the cmdlet New-WSManSessionOption + /// by using the cmdlet New-WSManSessionOption. /// [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] @@ -781,8 +885,10 @@ public Hashtable SelectorSet public SessionOption SessionOption { get { return sessionoption; } + set { sessionoption = value; } } + private SessionOption sessionoption; /// @@ -797,8 +903,10 @@ public SessionOption SessionOption public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } + private SwitchParameter usessl; /// @@ -812,15 +920,15 @@ public SwitchParameter UseSSL public Hashtable ValueSet { get { return valueset; } + set { valueset = value; } } - private Hashtable valueset; - + private Hashtable valueset; #endregion - private WSManHelper helper ; + private WSManHelper helper; /// /// ProcessRecord method. /// @@ -831,36 +939,35 @@ protected override void ProcessRecord() helper.WSManOp = "set"; IWSManSession m_session = null; - if (dialect != null) { - if (dialect.ToString().Equals(helper.ALIAS_WQL, StringComparison.CurrentCultureIgnoreCase)) + if (dialect.ToString().Equals(helper.ALIAS_WQL, StringComparison.OrdinalIgnoreCase)) dialect = new Uri(helper.URI_WQL_DIALECT); - if (dialect.ToString().Equals(helper.ALIAS_SELECTOR, StringComparison.CurrentCultureIgnoreCase)) + if (dialect.ToString().Equals(helper.ALIAS_SELECTOR, StringComparison.OrdinalIgnoreCase)) dialect = new Uri(helper.URI_SELECTOR_DIALECT); - if (dialect.ToString().Equals(helper.ALIAS_ASSOCIATION, StringComparison.CurrentCultureIgnoreCase)) + if (dialect.ToString().Equals(helper.ALIAS_ASSOCIATION, StringComparison.OrdinalIgnoreCase)) dialect = new Uri(helper.URI_ASSOCIATION_DIALECT); } try { - string connectionStr = String.Empty; + string connectionStr = string.Empty; connectionStr = helper.CreateConnectionString(connectionuri, port, computername, applicationname); if (connectionuri != null) { try { - //in the format http(s)://server[:port/applicationname] - string[] constrsplit = connectionuri.OriginalString.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // in the format http(s)://server[:port/applicationname] + string[] constrsplit = connectionuri.OriginalString.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } catch (IndexOutOfRangeException) { helper.AssertError(helper.GetResourceMsgFromResourcetext("NotProperURI"), false, connectionuri); } - } + IWSManResourceLocator m_resource = helper.InitializeResourceLocator(optionset, selectorset, fragment, dialect, m_wsmanObject, resourceuri); m_session = helper.CreateSessionObject(m_wsmanObject, Authentication, sessionoption, Credential, connectionStr, CertificateThumbprint, usessl.IsPresent); string rootNode = helper.GetRootNodeName(helper.WSManOp, m_resource.ResourceUri, null); @@ -871,7 +978,7 @@ protected override void ProcessRecord() { xmldoc.LoadXml(m_session.Put(m_resource, input, 0)); } - catch(XmlException ex) + catch (XmlException ex) { helper.AssertError(ex.Message, false, computername); } @@ -882,44 +989,45 @@ protected override void ProcessRecord() { foreach (XmlNode node in xmldoc.DocumentElement.ChildNodes) { - if (node.Name.Equals(fragment, StringComparison.CurrentCultureIgnoreCase)) + if (node.Name.Equals(fragment, StringComparison.OrdinalIgnoreCase)) WriteObject(node.Name + " = " + node.InnerText); } } } else WriteObject(xmldoc.DocumentElement); - } finally { - if (!String.IsNullOrEmpty(m_wsmanObject.Error)) + if (!string.IsNullOrEmpty(m_wsmanObject.Error)) { helper.AssertError(m_wsmanObject.Error, true, resourceuri); } - if (!String.IsNullOrEmpty(m_session.Error)) + + if (!string.IsNullOrEmpty(m_session.Error)) { helper.AssertError(m_session.Error, true, resourceuri); } + if (m_session != null) Dispose(m_session); } - }//End ProcessRecord() + } #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -938,14 +1046,10 @@ protected override void EndProcessing() { helper.CleanUp(); } - } - - #endregion - #region Remove-WsManInstance /// @@ -955,10 +1059,9 @@ protected override void EndProcessing() /// Set-WSManInstance -Action StartService -ResourceURI wmicimv2/Win32_Service /// -SelectorSet {Name=Spooler} /// - [Cmdlet(VerbsCommon.Remove, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141453")] + [Cmdlet(VerbsCommon.Remove, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096721")] public class RemoveWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { - #region Parameters /// /// The following is the definition of the input parameter "ApplicationName". @@ -966,12 +1069,14 @@ public class RemoveWSManInstanceCommand : AuthenticatingWSManCommand, IDisposabl /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - public String ApplicationName + public string ApplicationName { get { return applicationname; } + set { applicationname = value; } } - private String applicationname = null; + + private string applicationname = null; /// /// The following is the definition of the input parameter "ComputerName". @@ -981,19 +1086,24 @@ public String ApplicationName /// [Parameter(ParameterSetName = "ComputerName")] [Alias("cn")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "ConnectionURI". @@ -1007,17 +1117,18 @@ public String ComputerName public Uri ConnectionURI { get { return connectionuri; } + set { connectionuri = value; } } + private Uri connectionuri; /// /// The following is the definition of the input parameter "OptionSet". /// OptionSet is a hahs table which help modify or refine the nature of the /// request. These are similar to switches used in command line shells in that - /// they are service-specific + /// they are service-specific. /// - [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [Alias("os")] @@ -1025,8 +1136,10 @@ public Uri ConnectionURI public Hashtable OptionSet { get { return optionset; } + set { optionset = value; } } + private Hashtable optionset; /// @@ -1035,19 +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 + /// URI of the resource class/instance representation. /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Resourceuri")] @@ -1057,15 +1171,17 @@ public Int32 Port public Uri ResourceURI { get { return resourceuri; } + set { resourceuri = value; } } + private 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 - /// class + /// management resource if there are more than 1 instance of the resource + /// class. /// [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, @@ -1075,14 +1191,16 @@ public Uri ResourceURI public Hashtable SelectorSet { get { return selectorset; } + set { selectorset = value; } } + private Hashtable selectorset; /// /// The following is the definition of the input parameter "SessionOption". /// Defines a set of extended options for the WSMan session. This can be created - /// by using the cmdlet New-WSManSessionOption + /// by using the cmdlet New-WSManSessionOption. /// [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] @@ -1091,8 +1209,10 @@ public Hashtable SelectorSet public SessionOption SessionOption { get { return sessionoption; } + set { sessionoption = value; } } + private SessionOption sessionoption; /// @@ -1107,16 +1227,14 @@ public SessionOption SessionOption public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } - private SwitchParameter usessl; - - + private SwitchParameter usessl; #endregion - /// /// ProcessRecord method. /// @@ -1128,23 +1246,23 @@ protected override void ProcessRecord() IWSManSession m_session = null; try { - string connectionStr = String.Empty; + string connectionStr = string.Empty; connectionStr = helper.CreateConnectionString(connectionuri, port, computername, applicationname); if (connectionuri != null) { try { - //in the format http(s)://server[:port/applicationname] - string[] constrsplit = connectionuri.OriginalString.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // in the format http(s)://server[:port/applicationname] + string[] constrsplit = connectionuri.OriginalString.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } catch (IndexOutOfRangeException) { helper.AssertError(helper.GetResourceMsgFromResourcetext("NotProperURI"), false, connectionuri); } - } + IWSManResourceLocator m_resource = helper.InitializeResourceLocator(optionset, selectorset, null, null, m_wsmanObject, resourceuri); m_session = helper.CreateSessionObject(m_wsmanObject, Authentication, sessionoption, Credential, connectionStr, CertificateThumbprint, usessl.IsPresent); string ResourceURI = helper.GetURIWithFilter(resourceuri.ToString(), null, selectorset, helper.WSManOp); @@ -1156,39 +1274,38 @@ protected override void ProcessRecord() { helper.AssertError(ex.Message, false, computername); } - } finally { - if (!String.IsNullOrEmpty(m_session.Error)) + if (!string.IsNullOrEmpty(m_session.Error)) { helper.AssertError(m_session.Error, true, resourceuri); } - if (!String.IsNullOrEmpty(m_wsmanObject.Error)) + + if (!string.IsNullOrEmpty(m_wsmanObject.Error)) { helper.AssertError(m_wsmanObject.Error, true, resourceuri); } + if (m_session != null) Dispose(m_session); - } - - }//End ProcessRecord() + } #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -1199,20 +1316,17 @@ protected override void ProcessRecord() } #endregion IDisposable Members - - } - #endregion #region New-WsManInstance /// /// Creates an instance of a management resource identified by the resource URI - /// using specified ValueSet or input File + /// using specified ValueSet or input File. /// - - [Cmdlet(VerbsCommon.New, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=141448")] + [Cmdlet(VerbsCommon.New, "WSManInstance", DefaultParameterSetName = "ComputerName", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096933")] + [OutputType(typeof(XmlElement))] public class NewWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable { /// @@ -1221,12 +1335,14 @@ public class NewWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable /// [Parameter(ParameterSetName = "ComputerName")] [ValidateNotNullOrEmpty] - public String ApplicationName + public string ApplicationName { get { return applicationname; } + set { applicationname = value; } } - private String applicationname = null; + + private string applicationname = null; /// /// The following is the definition of the input parameter "ComputerName". @@ -1236,19 +1352,24 @@ public String ApplicationName /// [Parameter(ParameterSetName = "ComputerName")] [Alias("cn")] - public String ComputerName + public string ComputerName { - get { return computername; } + get + { + return computername; + } + set { computername = value; - if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.CurrentCultureIgnoreCase))) + if ((string.IsNullOrEmpty(computername)) || (computername.Equals(".", StringComparison.OrdinalIgnoreCase))) { computername = "localhost"; } } } - private String computername = null; + + private string computername = null; /// /// The following is the definition of the input parameter "ConnectionURI". @@ -1263,23 +1384,28 @@ public String ComputerName public Uri ConnectionURI { get { return connectionuri; } + set { connectionuri = value; } } + private Uri connectionuri; /// /// The following is the definition of the input parameter "FilePath". /// Updates the management resource specified by the ResourceURI and SelectorSet - /// via this input file + /// via this input file. /// [Parameter] [ValidateNotNullOrEmpty] - public String FilePath + [Alias("Path")] + public string FilePath { get { return filepath; } + set { filepath = value; } } - private String filepath; + + private string filepath; /// /// The following is the definition of the input parameter "OptionSet". @@ -1293,8 +1419,10 @@ public String FilePath public Hashtable OptionSet { get { return optionset; } + set { optionset = value; } } + private Hashtable optionset; /// @@ -1303,17 +1431,19 @@ 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 + /// URI of the resource class/instance representation. /// [Parameter(Mandatory = true, Position = 0)] [ValidateNotNullOrEmpty] @@ -1322,15 +1452,17 @@ public Int32 Port public Uri ResourceURI { get { return resourceuri; } + set { resourceuri = value; } } + private 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 - /// class + /// management resource if there are more than 1 instance of the resource + /// class. /// [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)] @@ -1339,8 +1471,10 @@ public Uri ResourceURI public Hashtable SelectorSet { get { return selectorset; } + set { selectorset = value; } } + private Hashtable selectorset; /// @@ -1354,8 +1488,10 @@ public Hashtable SelectorSet public SessionOption SessionOption { get { return sessionoption; } + set { sessionoption = value; } } + private SessionOption sessionoption; /// @@ -1369,8 +1505,10 @@ public SessionOption SessionOption public SwitchParameter UseSSL { get { return usessl; } + set { usessl = value; } } + private SwitchParameter usessl; /// @@ -1383,41 +1521,40 @@ public SwitchParameter UseSSL public Hashtable ValueSet { get { return valueset; } + set { valueset = value; } } + private Hashtable valueset; private WSManHelper helper; - IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); - IWSManSession m_session = null; - string connectionStr = String.Empty; + private readonly IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); + private IWSManSession m_session = null; + private string connectionStr = string.Empty; /// /// BeginProcessing method. /// protected override void BeginProcessing() { - helper = new WSManHelper(this ); + helper = new WSManHelper(this); helper.WSManOp = "new"; connectionStr = helper.CreateConnectionString(connectionuri, port, computername, applicationname); if (connectionuri != null) { try { - //in the format http(s)://server[:port/applicationname] - string[] constrsplit = connectionuri.OriginalString.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // in the format http(s)://server[:port/applicationname] + string[] constrsplit = connectionuri.OriginalString.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } catch (IndexOutOfRangeException) { helper.AssertError(helper.GetResourceMsgFromResourcetext("NotProperURI"), false, connectionuri); } - } - - }//End BeginProcessing() - + } /// /// ProcessRecord method. @@ -1427,7 +1564,7 @@ protected override void ProcessRecord() try { IWSManResourceLocator m_resource = helper.InitializeResourceLocator(optionset, selectorset, null, null, m_wsmanObject, resourceuri); - //create the session object + // create the session object m_session = helper.CreateSessionObject(m_wsmanObject, Authentication, sessionoption, Credential, connectionStr, CertificateThumbprint, usessl.IsPresent); string rootNode = helper.GetRootNodeName(helper.WSManOp, m_resource.ResourceUri, null); string input = helper.ProcessInput(m_wsmanObject, filepath, helper.WSManOp, rootNode, valueset, m_resource, m_session); @@ -1446,36 +1583,37 @@ protected override void ProcessRecord() } finally { - if (!String.IsNullOrEmpty(m_wsmanObject.Error)) + if (!string.IsNullOrEmpty(m_wsmanObject.Error)) { helper.AssertError(m_wsmanObject.Error, true, resourceuri); } - if (!String.IsNullOrEmpty(m_session.Error)) + + if (!string.IsNullOrEmpty(m_session.Error)) { helper.AssertError(m_session.Error, true, resourceuri); } + if (m_session != null) { Dispose(m_session); } } - - }//End ProcessRecord() + } #region IDisposable Members /// - /// public dispose method + /// Public dispose method. /// public void Dispose() { - //CleanUp(); + // CleanUp(); GC.SuppressFinalize(this); } /// - /// public dispose method + /// Public dispose method. /// public void @@ -1493,9 +1631,8 @@ protected override void ProcessRecord() protected override void EndProcessing() { helper.CleanUp(); - - }//End EndProcessing() - }//End Class + } + } #endregion } diff --git a/src/Microsoft.WSMan.Management/WsManHelper.cs b/src/Microsoft.WSMan.Management/WsManHelper.cs index f0843fad94a..9249c7f4b88 100644 --- a/src/Microsoft.WSMan.Management/WsManHelper.cs +++ b/src/Microsoft.WSMan.Management/WsManHelper.cs @@ -1,37 +1,35 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Text.RegularExpressions; using System.Collections; -using System.Xml; -using System.Runtime.InteropServices; -using System.Reflection; -using System.Resources; -using System.Globalization; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Microsoft.Win32; +using System.Globalization; +using System.IO; using System.Management.Automation; using System.Management.Automation.Provider; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; -#if CORECLR -using System.Xml.XPath; -#endif +using System.Xml; + +using Microsoft.Win32; namespace Microsoft.WSMan.Management { [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] - internal class WSManHelper + internal sealed class WSManHelper { - //regular expressions + // regular expressions private const string PTRN_URI_LAST = @"([a-z_][-a-z0-9._]*)$"; private const string PTRN_OPT = @"^-([a-z]+):(.*)"; private const string PTRN_HASH_TOK = @"\s*([\w:]+)\s*=\s*(\$null|""([^""]*)"")\s*"; - //schemas + // schemas private const string URI_IPMI = @"http://schemas.dmtf.org/wbem/wscim/1/cim-schema"; private const string URI_WMI = @"http://schemas.microsoft.com/wbem/wsman/1/wmi"; private const string NS_IPMI = @"http://schemas.dmtf.org/wbem/wscim/1/cim-schema"; @@ -44,7 +42,7 @@ internal class WSManHelper private const string ALIAS_XPATH = @"xpath"; private const string URI_XPATH_DIALECT = @"http://www.w3.org/TR/1999/REC-xpath-19991116"; - //credSSP strings + // credSSP strings internal string CredSSP_RUri = "winrm/config/client/auth"; internal string CredSSP_XMLNmsp = "http://schemas.microsoft.com/wbem/wsman/1/config/client/auth"; internal string CredSSP_SNode = "/cfg:Auth/cfg:CredSSP"; @@ -58,18 +56,18 @@ internal class WSManHelper internal string Service_CredSSP_Uri = "winrm/config/service/auth"; internal string Service_CredSSP_XMLNmsp = "http://schemas.microsoft.com/wbem/wsman/1/config/service/auth"; - //gpo registry path and keys + // gpo registry path and keys internal string Registry_Path_Credentials_Delegation = @"SOFTWARE\Policies\Microsoft\Windows"; internal string Key_Allow_Fresh_Credentials = "AllowFreshCredentials"; internal string Key_Concatenate_Defaults_AllowFresh = "ConcatenateDefaults_AllowFresh"; internal string Delegate = "delegate"; internal string keyAllowcredssp = "AllowCredSSP"; - //'Constants for MS-XML + // 'Constants for MS-XML private const string NODE_ATTRIBUTE = "2"; private const int NODE_TEXT = 3; - //strings for dialects + // strings for dialects internal string ALIAS_WQL = @"wql"; internal string ALIAS_ASSOCIATION = @"association"; internal string ALIAS_SELECTOR = @"selector"; @@ -77,39 +75,38 @@ internal class WSManHelper internal string URI_SELECTOR_DIALECT = @"http://schemas.dmtf.org/wbem/wsman/1/wsman/SelectorFilter"; internal string URI_ASSOCIATION_DIALECT = @" http://schemas.dmtf.org/wbem/wsman/1/cimbinding/associationFilter"; - //string for operation + // 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 + // Below class is just a static container which would release sessions in case this DLL is unloaded. + internal sealed class Sessions { /// - /// dictionary object to store the connection + /// Dictionary object to store the connection. /// - internal static Dictionary SessionObjCache = new Dictionary(); + internal static readonly Dictionary SessionObjCache = new Dictionary(); ~Sessions() { ReleaseSessions(); } } - internal static Sessions AutoSession = new Sessions(); + + internal static readonly Sessions AutoSession = new Sessions(); // // // - internal static void ReleaseSessions() { lock (Sessions.SessionObjCache) @@ -124,10 +121,12 @@ internal static void ReleaseSessions() } catch (ArgumentException) { - //Somehow the object was a null reference. Ignore the error + // Somehow the object was a null reference. Ignore the error } - sessionobj=null; + + sessionobj = null; } + Sessions.SessionObjCache.Clear(); } } @@ -162,7 +161,7 @@ internal string GetResourceMsgFromResourcetext(string rscname) return _resourceMgr.GetString(rscname); } - static internal string FormatResourceMsgFromResourcetextS(string rscname, + internal static string FormatResourceMsgFromResourcetextS(string rscname, params object[] args) { return FormatResourceMsgFromResourcetextS(_resourceMgr, rscname, args); @@ -174,39 +173,32 @@ internal string FormatResourceMsgFromResourcetext(string resourceName, return FormatResourceMsgFromResourcetextS(_resourceMgr, resourceName, args); } - static private string FormatResourceMsgFromResourcetextS( + private static string FormatResourceMsgFromResourcetextS( ResourceManager resourceManager, string resourceName, object[] args) { - if (resourceManager == null) - { - throw new ArgumentNullException("resourceManager"); - } - - if (String.IsNullOrEmpty(resourceName)) - { - throw new ArgumentNullException("resourceName"); - } + ArgumentNullException.ThrowIfNull(resourceManager); + ArgumentException.ThrowIfNullOrEmpty(resourceName); string template = resourceManager.GetString(resourceName); string result = null; - if (null != template) + if (template != null) { - result = String.Format(CultureInfo.CurrentCulture, + result = string.Format(CultureInfo.CurrentCulture, template, args); } + return result; } - /// - /// add a session to dictionary + /// Add a session to dictionary. /// - /// connection string - /// session object - internal void AddtoDictionary(string key, Object value) + /// Connection string. + /// Session object. + internal void AddtoDictionary(string key, object value) { key = key.ToLowerInvariant(); lock (Sessions.SessionObjCache) @@ -225,13 +217,13 @@ internal void AddtoDictionary(string key, Object value) } catch (ArgumentException) { - //Somehow the object was a null reference. Ignore the error + // Somehow the object was a null reference. Ignore the error } + Sessions.SessionObjCache.Remove(key); Sessions.SessionObjCache.Add(key, value); } } - } internal object RemoveFromDictionary(string computer) @@ -249,11 +241,13 @@ internal object RemoveFromDictionary(string computer) } catch (ArgumentException) { - //Somehow the object was a null reference. Ignore the error + // Somehow the object was a null reference. Ignore the error } + Sessions.SessionObjCache.Remove(computer); } } + return objsession; } @@ -283,6 +277,7 @@ internal static Dictionary GetSessionObjCache() catch (COMException) { } + return Sessions.SessionObjCache; } @@ -301,7 +296,7 @@ internal string GetRootNodeName(string operation, string resourceUri, string act if (operation.Equals("invoke", StringComparison.OrdinalIgnoreCase)) { sfx = "_INPUT"; - resultStr = String.Concat(actionStr, sfx); + resultStr = string.Concat(actionStr, sfx); } else { @@ -310,9 +305,10 @@ internal string GetRootNodeName(string operation, string resourceUri, string act } else { - //error + // error } } + return resultStr; } @@ -331,21 +327,19 @@ internal string ReadFile(string path) { throw new ArgumentException(GetResourceMsgFromResourcetext("InvalidFileName")); } + string strOut = null; try { - _fs = new FileStream(path, FileMode.Open, FileAccess.Read); // create stream Reader _sr = new StreamReader(_fs); strOut = _sr.ReadToEnd(); } - catch (ArgumentNullException e) { ErrorRecord er = new ErrorRecord(e, "ArgumentNullException", ErrorCategory.InvalidArgument, null); cmdletname.ThrowTerminatingError(er); - } catch (UnauthorizedAccessException e) { @@ -369,32 +363,25 @@ 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; } internal string ProcessInput(IWSManEx wsman, string filepath, string operation, string root, Hashtable valueset, IWSManResourceLocator resourceUri, IWSManSession sessionObj) { - string resultString = null; - //if file path is given + // if file path is given if (!string.IsNullOrEmpty(filepath) && valueset == null) { if (!File.Exists(filepath)) { throw new FileNotFoundException(_resourceMgr.GetString("InvalidFileName")); } + resultString = ReadFile(filepath); return resultString; } @@ -407,7 +394,7 @@ internal string ProcessInput(IWSManEx wsman, string filepath, string operation, string parameters = null, nilns = null; string xmlns = GetXmlNs(resourceUri.ResourceUri); - //if valueset is given, i.e hashtable + // if valueset is given, i.e hashtable if (valueset != null) { foreach (DictionaryEntry entry in valueset) @@ -418,9 +405,11 @@ internal string ProcessInput(IWSManEx wsman, string filepath, string operation, parameters = parameters + " " + ATTR_NIL; nilns = " " + NS_XSI; } + parameters = parameters + ">" + entry.Value.ToString() + ""; } } + resultString = "" + parameters + ""; break; @@ -438,7 +427,7 @@ internal string ProcessInput(IWSManEx wsman, string filepath, string operation, xpathString = @"/*/*[local-name()=""" + entry.Key + @"""]"; if (entry.Key.ToString().Equals("location", StringComparison.OrdinalIgnoreCase)) { - //'Ignore cim:Location + // 'Ignore cim:Location xpathString = @"/*/*[local-name()=""" + entry.Key + @""" and namespace-uri() != """ + NS_CIMBASE + @"""]"; } @@ -462,21 +451,22 @@ 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")); } } } + if (string.IsNullOrEmpty(entry.Key.ToString())) { - //XmlNode newnode = xmlfile.CreateNode(XmlNodeType.Attribute, ATTR_NIL_NAME, NS_XSI_URI); - XmlAttribute newnode = xmlfile.CreateAttribute(XmlNodeType.Attribute.ToString(), ATTR_NIL_NAME, NS_XSI_URI); + // XmlNode newnode = xmlfile.CreateNode(XmlNodeType.Attribute, ATTR_NIL_NAME, NS_XSI_URI); + XmlAttribute newnode = xmlfile.CreateAttribute(nameof(XmlNodeType.Attribute), ATTR_NIL_NAME, NS_XSI_URI); newnode.Value = "true"; node.Attributes.Append(newnode); - //(newnode.Attributes.Item(0).FirstChild ); - node.Value = ""; + // (newnode.Attributes.Item(0).FirstChild ); + node.Value = string.Empty; } else { @@ -484,12 +474,13 @@ internal string ProcessInput(IWSManEx wsman, string filepath, string operation, node.InnerText = entry.Value.ToString(); } } + } + } - }//end for - }//end if valueset resultString = xmlfile.OuterXml; break; - }//end switch + } + return resultString; } @@ -508,6 +499,7 @@ internal XmlNode GetXmlNode(string xmlString, string xpathpattern, string xmlnam { nsmgr.AddNamespace("cfg", xmlnamespace); } + node = xDoc.SelectSingleNode(xpathpattern, nsmgr); return node; } @@ -534,26 +526,27 @@ internal string CreateConnectionString(Uri ConnUri, int port, string computernam { ConnectionString = ConnectionString + ":" + port; } + if (applicationname != null) { ConnectionString = ConnectionString + "/" + applicationname; } } - return ConnectionString; + return ConnectionString; } internal IWSManResourceLocator InitializeResourceLocator(Hashtable optionset, Hashtable selectorset, string fragment, Uri dialect, IWSManEx wsmanObj, Uri resourceuri) { - string resource = null; if (resourceuri != null) { resource = resourceuri.ToString(); } + if (selectorset != null) { - resource = resource + "?"; + resource += "?"; int i = 0; foreach (DictionaryEntry entry in selectorset) { @@ -563,12 +556,12 @@ internal IWSManResourceLocator InitializeResourceLocator(Hashtable optionset, Ha resource += "+"; } } + IWSManResourceLocator m_resource = null; try { m_resource = (IWSManResourceLocator)wsmanObj.CreateResourceLocator(resource); - if (optionset != null) { foreach (DictionaryEntry entry in optionset) @@ -598,6 +591,7 @@ internal IWSManResourceLocator InitializeResourceLocator(Hashtable optionset, Ha { AssertError(ex.Message, false, null); } + return m_resource; } @@ -612,12 +606,12 @@ internal IWSManResourceLocator InitializeResourceLocator(Hashtable optionset, Ha /// /// If there is ambiguity as specified above. /// - static internal void ValidateSpecifiedAuthentication(AuthenticationMechanism authentication, PSCredential credential, string certificateThumbprint) + internal static void ValidateSpecifiedAuthentication(AuthenticationMechanism authentication, PSCredential credential, string certificateThumbprint) { if ((credential != null) && (certificateThumbprint != null)) { - String message = FormatResourceMsgFromResourcetextS( - "AmbiguosAuthentication", + string message = FormatResourceMsgFromResourcetextS( + "AmbiguousAuthentication", "CertificateThumbPrint", "credential"); throw new InvalidOperationException(message); @@ -627,8 +621,8 @@ static internal void ValidateSpecifiedAuthentication(AuthenticationMechanism aut (authentication != AuthenticationMechanism.ClientCertificate) && (certificateThumbprint != null)) { - String message = FormatResourceMsgFromResourcetextS( - "AmbiguosAuthentication", + string message = FormatResourceMsgFromResourcetextS( + "AmbiguousAuthentication", "CertificateThumbPrint", authentication.ToString()); throw new InvalidOperationException(message); @@ -646,46 +640,51 @@ 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)) { sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseBasic | (int)WSManSessionFlags.WSManFlagCredUserNamePassword; } + 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)) { sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseDigest | (int)WSManSessionFlags.WSManFlagCredUserNamePassword; } + if (authentication.Equals(AuthenticationMechanism.Credssp)) { sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseCredSsp | (int)WSManSessionFlags.WSManFlagCredUserNamePassword; } + if (authentication.Equals(AuthenticationMechanism.ClientCertificate)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseClientCertificate; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseClientCertificate; } - } IWSManConnectionOptionsEx2 connObject = (IWSManConnectionOptionsEx2)wsmanObject.CreateConnectionOptions(); if (credential != null) { - //connObject = (IWSManConnectionOptionsEx2)wsmanObject.CreateConnectionOptions(); + // connObject = (IWSManConnectionOptionsEx2)wsmanObject.CreateConnectionOptions(); System.Net.NetworkCredential nwCredential = new System.Net.NetworkCredential(); if (credential.UserName != null) { nwCredential = credential.GetNetworkCredential(); - if (String.IsNullOrEmpty(nwCredential.Domain)) + if (string.IsNullOrEmpty(nwCredential.Domain)) { - if ( authentication.Equals(AuthenticationMechanism.Digest) || authentication.Equals(AuthenticationMechanism.Basic) ) + if (authentication.Equals(AuthenticationMechanism.Digest) || authentication.Equals(AuthenticationMechanism.Basic)) { connObject.UserName = nwCredential.UserName; } @@ -699,10 +698,11 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM { connObject.UserName = nwCredential.Domain + "\\" + nwCredential.UserName; } + 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; } } } @@ -710,12 +710,11 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM if (certificateThumbprint != null) { connObject.CertificateThumbprint = certificateThumbprint; - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseClientCertificate; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseClientCertificate; } if (sessionoption != null) { - if (sessionoption.ProxyAuthentication != 0) { int ProxyAccessflags = 0; @@ -749,6 +748,7 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM { ProxyAuthenticationFlags = connObject.ProxyAuthenticationUseDigest(); } + if (sessionoption.ProxyCredential != null) { try @@ -764,48 +764,52 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM { connObject.SetProxy((int)sessionoption.ProxyAccessType, (int)sessionoption.ProxyAuthentication, null, null); } - - } + 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; + // If UseUtf16 is false, then default Encoding is Utf8 + 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; + // If SessionOption is null then, default Encoding is Utf8 + sessionFlags |= (int)WSManSessionFlags.WSManFlagUtf8; } if (usessl) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseSsl; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseSsl; } IWSManSession m_SessionObj = null; @@ -824,24 +828,23 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM { AssertError(ex.Message, false, null); } + return m_SessionObj; } internal void CleanUp() { - - if (_sr != null) { _sr.Dispose(); _sr = null; } + if (_fs != null) { _fs.Dispose(); _fs = null; } - } internal string GetFilterString(Hashtable seletorset) @@ -852,11 +855,12 @@ 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('+'); } } + filter.Remove(filter.ToString().Length - 1, 1); return filter.ToString(); } @@ -897,12 +901,12 @@ 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)) { sburi.Append(GetFilterString(selectorset)); - if (sburi.ToString().EndsWith("?", StringComparison.OrdinalIgnoreCase)) + if (sburi.ToString().EndsWith('?')) { sburi.Remove(sburi.Length - 1, 1); } @@ -912,7 +916,7 @@ internal string GetURIWithFilter(string uri, string filter, Hashtable selectorse } /// - /// This method is used by Connect-WsMan Cmdlet and New-Item of WsMan Provider to create connection to WsMan + /// This method is used by Connect-WsMan Cmdlet and New-Item of WsMan Provider to create connection to WsMan. /// /// /// @@ -932,18 +936,16 @@ internal void CreateWsManConnection(string ParameterSetName, Uri connectionuri, string connectionStr = CreateConnectionString(connectionuri, port, computername, applicationname); if (connectionuri != null) { - //in the format http(s)://server[:port/applicationname] - string[] constrsplit = connectionStr.Split(new string[] { ":" + port + "/" + applicationname }, StringSplitOptions.None); - string[] constrsplit1 = constrsplit[0].Split(new string[] { "//" }, StringSplitOptions.None); + // in the format http(s)://server[:port/applicationname] + string[] constrsplit = connectionStr.Split(":" + port + "/" + applicationname, StringSplitOptions.None); + string[] constrsplit1 = constrsplit[0].Split("//", StringSplitOptions.None); computername = constrsplit1[1].Trim(); } + IWSManSession m_session = CreateSessionObject(m_wsmanObject, authentication, sessionoption, credential, connectionStr, certificateThumbprint, usessl); m_session.Identify(0); - string key = computername; - if (key == null) - { - key = "localhost"; - } + string key = computername ?? "localhost"; + AddtoDictionary(key, m_session); } catch (IndexOutOfRangeException) @@ -956,11 +958,10 @@ internal void CreateWsManConnection(string ParameterSetName, Uri connectionuri, } finally { - if (!String.IsNullOrEmpty(m_wsmanObject.Error)) + if (!string.IsNullOrEmpty(m_wsmanObject.Error)) { AssertError(m_wsmanObject.Error, true, computername); } - } } @@ -996,17 +997,14 @@ internal bool ValidateCredSSPRegistry(bool AllowFreshCredentialsValueShouldBePre { RegistryKey rGPOLocalMachineKey = Registry.LocalMachine.OpenSubKey( Registry_Path_Credentials_Delegation + @"\CredentialsDelegation", -#if !CORECLR RegistryKeyPermissionCheck.ReadWriteSubTree, -#endif System.Security.AccessControl.RegistryRights.FullControl); if (rGPOLocalMachineKey != null) { - rGPOLocalMachineKey = rGPOLocalMachineKey.OpenSubKey(Key_Allow_Fresh_Credentials, -#if !CORECLR + rGPOLocalMachineKey = rGPOLocalMachineKey.OpenSubKey( + Key_Allow_Fresh_Credentials, RegistryKeyPermissionCheck.ReadWriteSubTree, -#endif System.Security.AccessControl.RegistryRights.FullControl); if (rGPOLocalMachineKey == null) { @@ -1014,7 +1012,7 @@ internal bool ValidateCredSSPRegistry(bool AllowFreshCredentialsValueShouldBePre } string[] valuenames = rGPOLocalMachineKey.GetValueNames(); - if (valuenames.Length <= 0) + if (valuenames.Length == 0) { return !AllowFreshCredentialsValueShouldBePresent; } @@ -1064,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); @@ -1078,54 +1073,49 @@ internal static void LoadResourceData() while (!_sr.EndOfStream) { string Line = _sr.ReadLine(); - if (Line.Contains("=")) + if (Line.Contains('=')) { - string[] arr = Line.Split(new char[] { '=' }, 2); + string[] arr = Line.Split('=', count: 2); if (!ResourceValueCache.ContainsKey(arr[0].Trim())) { - string value = arr[1].TrimStart(new char[] { '"' }).TrimEnd(new char[] { '"' }); + string value = arr[1].Trim('"'); ResourceValueCache.Add(arr[0].Trim(), value.Trim()); } } } } } - - catch (IOException e) + catch (IOException) { - - throw (e); + throw; } - - } /// /// Get the resource value from WinRm.ini - /// from %windir%\system32\winrm\[Hexadecimal Language Folder]\winrm.ini + /// from %windir%\system32\winrm\[Hexadecimal Language Folder]\winrm.ini. /// /// /// internal static string GetResourceString(string Key) { - //Checks whether resource values already loaded and loads. - if (ResourceValueCache.Count <= 0) + // Checks whether resource values already loaded and loads. + if (ResourceValueCache.Count == 0) { LoadResourceData(); } - string value = ""; + + string value = string.Empty; if (ResourceValueCache.ContainsKey(Key.Trim())) { ResourceValueCache.TryGetValue(Key.Trim(), out value); } + return value.Trim(); } /// - /// /// - private static Dictionary ResourceValueCache = new Dictionary(); - - + private static readonly Dictionary ResourceValueCache = new Dictionary(); } } diff --git a/src/Microsoft.WSMan.Management/WsManSnapin.cs b/src/Microsoft.WSMan.Management/WsManSnapin.cs index b47e57f424f..4093caf12db 100644 --- a/src/Microsoft.WSMan.Management/WsManSnapin.cs +++ b/src/Microsoft.WSMan.Management/WsManSnapin.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; -using System.Text; using System.Management; using System.Management.Automation; -using System.ComponentModel; -using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.WSMan.Management { 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 fd0bd37eb74..10702e5ccea 100644 --- a/src/Microsoft.WSMan.Management/resources/WsManResources.txt +++ b/src/Microsoft.WSMan.Management/resources/WsManResources.txt @@ -1,9 +1,9 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. +# Copyright (c) Microsoft Corporation. # {0} = Delegate Vendor=Microsoft -Description=This Windows PowerShell snap-in contains cmdlets (such as Get-WSManInstance and Set-WSManInstance) that are used by the Windows PowerShell host to manage WSMan operations. +Description=This PowerShell snap-in contains cmdlets (such as Get-WSManInstance and Set-WSManInstance) that are used by the PowerShell host to manage WSMan operations. InvalidComputerName=The WinRM client cannot complete the operation.Check if the computer name is valid. InvalidFileName=This command cannot be used because file does not exist.Check the file existence and run your command. InvalidPath=This command cannot be used because root path does not exist.Check the root path and run your command. @@ -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. @@ -65,4 +65,4 @@ SetItemWhatIfAndConfirmText= "Set-Item" on the WinRM configuration setting "{0}" SetItemWarnigForPPQ=The updated configuration is effective only if it is less than or equal to the value of global quota {0}. Verify the value for the global quota using the PowerShell cmdlet "Get-Item {0}". SetItemWarningForGlobalQuota=The updated configuration might affect the operation of the plugins having a per plugin quota value greater than {0}. Verify the configuration of all the registered plugins and change the per plugin quota values for the affected plugins. SetItemServiceRestartWarning= The configuration changes you made will only be effective after the WinRM service is restarted. To restart the WinRM service, run the following command: 'Restart-Service winrm' -SetItemServiceRestartWarningRemote= The configuration changes you made will only be effective after the WinRM service is restarted on {0}. \ No newline at end of file +SetItemServiceRestartWarningRemote= The configuration changes you made will only be effective after the WinRM service is restarted on {0}. diff --git a/src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj b/src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj index 0715bacb694..2a9439c0274 100644 --- a/src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj +++ b/src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj @@ -1,24 +1,8 @@ - + - PowerShell Core's Microsoft.WSMan.Runtime project + PowerShell's Microsoft.WSMan.Runtime project Microsoft.WSMan.Runtime - - $(DefineConstants);CORECLR - - - - portable - - - - $(DefineConstants);UNIX - - - - full - - diff --git a/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs b/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs index 0a5a9c2268d..13dc8bbea2c 100644 --- a/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs +++ b/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs @@ -1,212 +1,111 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -using System; -using System.IO; -using System.Xml; -using System.Net; -using System.Resources; -using System.Reflection; -using System.ComponentModel; - -using System.Collections; -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.InteropServices; +using System; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - - - - +using System.Net; [assembly: CLSCompliant(true)] + namespace Microsoft.WSMan.Management { - /// - /// Session option class + /// Session option class. /// - public sealed class SessionOption { - /// - /// property - /// - public bool SkipCACheck - { - get { return _SkipCACheck; } - set - { - _SkipCACheck = value; - } - } - private bool _SkipCACheck; + /// Property. + /// + public bool SkipCACheck { get; set; } /// - /// property - /// - public bool SkipCNCheck - { - get { return _SkipCNCheck; } - set - { - _SkipCNCheck = value; - } - } - private bool _SkipCNCheck; + /// Property. + /// + public bool SkipCNCheck { get; set; } /// - /// property + /// 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; + /// Property. + /// + public bool UseEncryption { get; set; } = true; /// - /// property - /// - public bool UseUtf16 - { - get { return _UTF16; } - set - { - _UTF16 = value; - } - } - private bool _UTF16; + /// Property. + /// + public bool UseUtf16 { get; set; } /// - /// property - /// - public ProxyAuthentication ProxyAuthentication - { - get { return _ProxyAuthentication; } - set - { - _ProxyAuthentication = value; - } - } - private ProxyAuthentication _ProxyAuthentication; + /// Property. + /// + public ProxyAuthentication ProxyAuthentication { get; set; } /// - /// property + /// 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; + /// Property. + /// + public int OperationTimeout { get; set; } /// - /// property - /// - public NetworkCredential ProxyCredential - { - get { return _ProxyCredential; } - set - { - _ProxyCredential = value; - } - } - private NetworkCredential _ProxyCredential; + /// Property. + /// + public NetworkCredential ProxyCredential { get; set; } /// - /// property + /// Property. /// - public ProxyAccessType ProxyAccessType - { - get { return _proxyaccesstype; } - set - { - _proxyaccesstype = value; - } - } - - private ProxyAccessType _proxyaccesstype; + public ProxyAccessType ProxyAccessType { get; set; } } /// - /// property + /// Property. /// public enum ProxyAccessType { /// - /// property + /// Property. /// ProxyIEConfig = 0, /// - /// property + /// Property. /// ProxyWinHttpConfig = 1, /// - /// property + /// Property. /// ProxyAutoDetect = 2, /// - /// property + /// Property. /// ProxyNoProxyServer = 3 } /// - /// property + /// Property. /// [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] public enum ProxyAuthentication { /// - /// property + /// Property. /// Negotiate = 1, /// - /// property + /// Property. /// Basic = 2, /// - /// property + /// Property. /// Digest = 4 } diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj new file mode 100644 index 00000000000..3903b164b09 --- /dev/null +++ b/src/Modules/PSGalleryModules.csproj @@ -0,0 +1,22 @@ + + + + PowerShell + Microsoft Corporation + (c) Microsoft Corporation. + + net11.0 + + true + + + + + + + + + + + + diff --git a/src/Modules/README.md b/src/Modules/README.md index ccc66161d0e..5911951a5ad 100644 --- a/src/Modules/README.md +++ b/src/Modules/README.md @@ -11,11 +11,8 @@ Content files includes: These files are copied as-is by `dotnet` -- Shared (shared between all variations) -- Windows+Unix-Core -- Windows-Core -- Windows-Core+Full -- Windows-Full +- Shared (shared between Windows and Unix) +- Windows - Unix Notes 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 88a0034539f..3c2581795f7 100644 --- a/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 +++ b/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 @@ -1,14 +1,14 @@ @{ GUID="56D66100-99A0-4FFC-A12D-EEE9A6718AEF" -Author="Microsoft Corporation" +Author="PowerShell" CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") PowerShellVersion="3.0" -CLRVersion="4.0" -AliasesToExport = @() FunctionsToExport = @() CmdletsToExport="Start-Transcript", "Stop-Transcript" +AliasesToExport = @() NestedModules="Microsoft.PowerShell.ConsoleHost.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855956' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 b/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 deleted file mode 100644 index cd915abe7ae..00000000000 --- a/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 +++ /dev/null @@ -1,174 +0,0 @@ - -## Converts a SDDL string into an object-based representation of a security -## descriptor -function ConvertFrom-SddlString -{ - [CmdletBinding(HelpUri = "https://go.microsoft.com/fwlink/?LinkId=623636")] - param( - ## The string representing the security descriptor in SDDL syntax - [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] - [String] $Sddl, - - ## The type of rights that this SDDL string represents, if any. - [Parameter()] - [ValidateSet( - "FileSystemRights", "RegistryRights", "ActiveDirectoryRights", - "MutexRights", "SemaphoreRights", "CryptoKeyRights", - "EventWaitHandleRights")] - $Type - ) - - Begin - { - # On CoreCLR CryptoKeyRights and ActiveDirectoryRights are not supported. - if ($PSEdition -eq "Core" -and ($Type -eq "CryptoKeyRights" -or $Type -eq "ActiveDirectoryRights")) - { - $errorId = "TypeNotSupported" - $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::TypeNotSupported -f $Type - $exception = [System.ArgumentException]::New($errorMessage) - $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - ## Translates a SID into a NT Account - function ConvertTo-NtAccount - { - param($Sid) - - if($Sid) - { - $securityIdentifier = [System.Security.Principal.SecurityIdentifier] $Sid - - try - { - $ntAccount = $securityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() - } - catch{} - - $ntAccount - } - } - - ## Gets the access rights that apply to an access mask, preferring right types - ## of 'Type' if specified. - function Get-AccessRights - { - param($AccessMask, $Type) - - if ($PSEdition -eq "Core") - { - ## All the types of access rights understood by .NET Core - $rightTypes = [Ordered] @{ - "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] - "RegistryRights" = [System.Security.AccessControl.RegistryRights] - "MutexRights" = [System.Security.AccessControl.MutexRights] - "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] - "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] - } - } - else - { - ## All the types of access rights understood by .NET - $rightTypes = [Ordered] @{ - "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] - "RegistryRights" = [System.Security.AccessControl.RegistryRights] - "ActiveDirectoryRights" = [System.DirectoryServices.ActiveDirectoryRights] - "MutexRights" = [System.Security.AccessControl.MutexRights] - "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] - "CryptoKeyRights" = [System.Security.AccessControl.CryptoKeyRights] - "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] - } - } - $typesToExamine = $rightTypes.Values - - ## If they know the access mask represents a certain type, prefer its names - ## (i.e.: CreateLink for the registry over CreateDirectories for the filesystem) - if($Type) - { - $typesToExamine = @($rightTypes[$Type]) + $typesToExamine - } - - - ## Stores the access types we've found that apply - $foundAccess = @() - - ## Store the access types we've already seen, so that we don't report access - ## flags that are essentially duplicate. Many of the access values in the different - ## enumerations have the same value but with different names. - $foundValues = @{} - - ## Go through the entries in the different right types, and see if they apply to the - ## provided access mask. If they do, then add that to the result. - foreach($rightType in $typesToExamine) - { - foreach($accessFlag in [Enum]::GetNames($rightType)) - { - $longKeyValue = [long] $rightType::$accessFlag - if(-not $foundValues.ContainsKey($longKeyValue)) - { - $foundValues[$longKeyValue] = $true - if(($AccessMask -band $longKeyValue) -eq ($longKeyValue)) - { - $foundAccess += $accessFlag - } - } - } - } - - $foundAccess | Sort-Object - } - - ## Converts an ACE into a string representation - function ConvertTo-AceString - { - param( - [Parameter(ValueFromPipeline)] - $Ace, - $Type - ) - - process - { - foreach($aceEntry in $Ace) - { - $AceString = (ConvertTo-NtAccount $aceEntry.SecurityIdentifier) + ": " + $aceEntry.AceQualifier - if($aceEntry.AceFlags -ne "None") - { - $AceString += " " + $aceEntry.AceFlags - } - - if($aceEntry.AccessMask) - { - $foundAccess = Get-AccessRights $aceEntry.AccessMask $Type - - if($foundAccess) - { - $AceString += " ({0})" -f ($foundAccess -join ", ") - } - } - - $AceString - } - } - } - } - - Process - { - $rawSecurityDescriptor = [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) - - $owner = ConvertTo-NtAccount $rawSecurityDescriptor.Owner - $group = ConvertTo-NtAccount $rawSecurityDescriptor.Group - $discretionaryAcl = ConvertTo-AceString $rawSecurityDescriptor.DiscretionaryAcl $Type - $systemAcl = ConvertTo-AceString $rawSecurityDescriptor.SystemAcl $Type - - [PSCustomObject] @{ - Owner = $owner - Group = $group - DiscretionaryAcl = @($discretionaryAcl) - SystemAcl = @($systemAcl) - RawDescriptor = $rawSecurityDescriptor - } - } -} diff --git a/src/Modules/Shared/PSReadLine/PSReadLine.psd1 b/src/Modules/Shared/PSReadLine/PSReadLine.psd1 deleted file mode 100644 index a634495d19d..00000000000 --- a/src/Modules/Shared/PSReadLine/PSReadLine.psd1 +++ /dev/null @@ -1,17 +0,0 @@ -@{ -RootModule = 'PSReadLine.psm1' -NestedModules = @("Microsoft.PowerShell.PSReadLine.dll") -ModuleVersion = '1.2' -GUID = '5714753b-2afd-4492-a5fd-01d9e2cff8b5' -Author = 'Microsoft Corporation' -CompanyName = 'Microsoft Corporation' -Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.' -Description = 'Great command line editing in the PowerShell console host' -PowerShellVersion = '3.0' -DotNetFrameworkVersion = '4.0' -CLRVersion = '4.0' -FunctionsToExport = 'PSConsoleHostReadline' -CmdletsToExport = 'Get-PSReadlineKeyHandler','Set-PSReadlineKeyHandler','Remove-PSReadlineKeyHandler', - 'Get-PSReadlineOption','Set-PSReadlineOption' -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855966' -} diff --git a/src/Modules/Shared/PSReadLine/PSReadLine.psm1 b/src/Modules/Shared/PSReadLine/PSReadLine.psm1 deleted file mode 100644 index 4f2bf37fe89..00000000000 --- a/src/Modules/Shared/PSReadLine/PSReadLine.psm1 +++ /dev/null @@ -1,5 +0,0 @@ -function PSConsoleHostReadline -{ - Microsoft.PowerShell.Core\Set-StrictMode -Off - [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext) -} 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 c270ed53848..21563c1da7c 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -1,14 +1,15 @@ @{ GUID="EEFCB906-B326-4E99-9F54-8B4BB6EF3C6D" -Author="Microsoft Corporation" +Author="PowerShell" CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855958' -AliasesToExport = @("gtz") +HelpInfoURI = 'https://aka.ms/powershell75-help' FunctionsToExport = @() +AliasesToExport = @("gcb", "gtz", "scb") CmdletsToExport=@("Add-Content", "Clear-Content", "Clear-ItemProperty", @@ -16,6 +17,8 @@ CmdletsToExport=@("Add-Content", "Convert-Path", "Copy-ItemProperty", "Get-ChildItem", + "Get-Clipboard", + "Set-Clipboard", "Get-Content", "Get-ItemProperty", "Get-ItemPropertyValue", @@ -45,10 +48,13 @@ CmdletsToExport=@("Add-Content", "Wait-Process", "Debug-Process", "Start-Process", + "Test-Connection", "Remove-ItemProperty", "Rename-ItemProperty", "Resolve-Path", "Set-Content", "Set-ItemProperty", - "Get-TimeZone") + "Get-TimeZone", + "Stop-Computer", + "Restart-Computer") } 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 20df5b5ea26..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,13 +1,14 @@ @{ -GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -AliasesToExport = @() +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" FunctionsToExport = @() -CmdletsToExport="Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-PfxCertificate" -NestedModules="Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855959' +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/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 65f4c80fc5b..df841837696 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -1,30 +1,35 @@ @{ -GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" -PowerShellVersion="3.0" -CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", - "Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", - "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", - "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", "New-Guid", - "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", - "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", - "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", - "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", - "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", - "Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData", - "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", - "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", - "Clear-Variable", "Export-Clixml", "Import-Clixml", "Import-PowerShellDataFile", "ConvertTo-Xml", "Select-Xml", "Write-Debug", - "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", - "Get-PSBreakpoint", "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", - "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash", - "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", - "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "New-TemporaryFile", "Get-Verb", "Format-Hex", "Remove-Alias" -FunctionsToExport= "Import-PowerShellDataFile" -AliasesToExport= "fhx" -NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' +GUID = "1DA87E53-152B-403E-98DC-74D7B4D63D59" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation." +ModuleVersion = "7.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion = "3.0" +CmdletsToExport = @( + 'Export-Alias', 'Get-Alias', 'Import-Alias', 'New-Alias', 'Remove-Alias', 'Set-Alias', 'Export-Clixml', + 'Import-Clixml', 'Measure-Command', 'Trace-Command', 'ConvertFrom-Csv', 'ConvertTo-Csv', 'Export-Csv', + 'Import-Csv', 'Get-Culture', 'Format-Custom', 'Get-Date', 'Set-Date', 'Write-Debug', 'Wait-Debugger', + 'Register-EngineEvent', 'Write-Error', 'Get-Event', 'New-Event', 'Remove-Event', 'Unregister-Event', + 'Wait-Event', 'Get-EventSubscriber', 'Invoke-Expression', 'Out-File', 'Get-FileHash', 'Export-FormatData', + 'Get-FormatData', 'Update-FormatData', 'New-Guid', 'Format-Hex', 'Get-Host', 'Read-Host', 'Write-Host', + 'ConvertTo-Html', 'Write-Information', 'ConvertFrom-Json', 'ConvertTo-Json', 'Test-Json', 'Format-List', + 'Import-LocalizedData', 'Send-MailMessage', 'ConvertFrom-Markdown', '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', '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', 'ConvertTo-CliXml', + 'ConvertFrom-CliXml' +) +FunctionsToExport = @() +AliasesToExport = @('fhx') +NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows-Core+Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows-Core+Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 deleted file mode 100644 index 34343b00634..00000000000 --- a/src/Modules/Windows-Core+Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ /dev/null @@ -1,14 +0,0 @@ -@{ -GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -CLRVersion="4.0" -AliasesToExport = @() -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" -NestedModules="Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855959' -} diff --git a/src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 b/src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 deleted file mode 100644 index 52c5e2ab83a..00000000000 --- a/src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 +++ /dev/null @@ -1,15 +0,0 @@ -@{ -GUID="766204A6-330E-4263-A7AB-46C87AFC366C" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -CLRVersion="4.0" -AliasesToExport = @() -FunctionsToExport = @() -CmdletsToExport="Disable-WSManCredSSP", "Enable-WSManCredSSP", "Get-WSManCredSSP", "Set-WSManQuickConfig", "Test-WSMan", "Invoke-WSManAction", "Connect-WSMan", "Disconnect-WSMan", "Get-WSManInstance", "Set-WSManInstance", "Remove-WSManInstance", "New-WSManInstance", "New-WSManSessionOption" -NestedModules="Microsoft.WSMan.Management.dll" -FormatsToProcess="WSMan.format.ps1xml" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855961' -} diff --git a/src/Modules/Windows-Core+Full/PSDiagnostics/PSDiagnostics.psd1 b/src/Modules/Windows-Core+Full/PSDiagnostics/PSDiagnostics.psd1 deleted file mode 100644 index a84bd87f3c6..00000000000 --- a/src/Modules/Windows-Core+Full/PSDiagnostics/PSDiagnostics.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -@{ - GUID="c61d6278-02a3-4618-ae37-a524d40a7f44 " - Author="Microsoft Corporation" - CompanyName="Microsoft Corporation" - Copyright="Copyright (c) Microsoft Corporation. All rights reserved." - ModuleVersion="1.0.0.0" - PowerShellVersion="2.0" - CLRVersion="2.0.50727" - ModuleToProcess="PSDiagnostics.psm1" - AliasesToExport = @() - CmdletsToExport = @() - FunctionsToExport="Disable-PSTrace","Disable-PSWSManCombinedTrace","Disable-WSManTrace","Enable-PSTrace","Enable-PSWSManCombinedTrace","Enable-WSManTrace","Get-LogProperties","Set-LogProperties","Start-Trace","Stop-Trace" -} diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml b/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml deleted file mode 100644 index aeab08d3a46..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - Counter - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet - - - - - - 25 - left - - - - left - 100 - - - - - - - - Timestamp - - - Readings - - - - - - - - Counter - - Microsoft.PowerShell.Commands.GetCounter.CounterFileInfo - - - - - 30 - left - - - 30 - left - - - 30 - left - - - - - - - - OldestRecord - - - NewestRecord - - - SampleCount - - - - - - - - diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml b/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml deleted file mode 100644 index d4e78a3d313..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Default - - System.Diagnostics.Eventing.Reader.EventLogRecord - - - ProviderName - - - - - - 25 - - - 8 - right - - - 16 - - - - - - - - - TimeCreated - - - Id - - - LevelDisplayName - - - Message - - - - - - - - - Default - - System.Diagnostics.Eventing.Reader.EventLogConfiguration - - - - - - - 9 - - - - 18 - right - - - - 11 - right - - - - - - - - LogMode - - - MaximumSizeInBytes - - - RecordCount - - - LogName - - - - - - - - Default - - System.Diagnostics.Eventing.Reader.ProviderMetadata - - - - - - - Name - - - LogLinks - - - Opcodes - - - Tasks - - - - - - - - - diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml b/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml deleted file mode 100644 index 150f184c164..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - System.Diagnostics.Eventing.Reader.EventLogConfiguration - - - PSStandardMembers - - - DefaultDisplayPropertySet - - LogName - MaximumSizeInBytes - RecordCount - LogMode - - - - - - - - System.Diagnostics.Eventing.Reader.EventLogRecord - - - PSStandardMembers - - - DefaultDisplayPropertySet - - TimeCreated - ProviderName - Id - Message - - - - - - - - System.Diagnostics.Eventing.Reader.ProviderMetadata - - - ProviderName - Name - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Name - LogLinks - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.CounterSet - - - Counter - Paths - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Path - InstanceName - CookedValue - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Timestamp - Readings - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet - - - Readings - - $strPaths = "" - foreach ($ctr in $this.CounterSamples) - { - $strPaths += ($ctr.Path + " :" + "`n") - $strPaths += ($ctr.CookedValue.ToString() + "`n`n") - } - return $strPaths - - - - - diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 deleted file mode 100644 index af936717b68..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -@{ -GUID="CA046F10-CA64-4740-8FF9-2565DBA61A4F" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -CmdletsToExport="Get-WinEvent", "New-WinEvent" # Counter CmdLets Disabled #4272: "Get-Counter", "Import-Counter", "Export-Counter" -NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" -TypesToProcess="GetEvent.types.ps1xml" -FormatsToProcess="Event.format.ps1xml", "Diagnostics.format.ps1xml" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855954' -} diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Windows-Core/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 deleted file mode 100644 index 4c48be5e2a8..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ /dev/null @@ -1,68 +0,0 @@ -@{ -GUID="EEFCB906-B326-4E99-9F54-8B4BB6EF3C6D" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" -PowerShellVersion="3.0" -NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855958' -AliasesToExport = @("gin", "gtz", "stz") -FunctionsToExport = @() -CmdletsToExport=@("Add-Content", - "Clear-Content", - "Clear-ItemProperty", - "Join-Path", - "Convert-Path", - "Copy-ItemProperty", - "Get-ChildItem", - "Get-Content", - "Get-ItemProperty", - "Get-ItemPropertyValue", - "Move-ItemProperty", - "Get-Location", - "Set-Location", - "Push-Location", - "Pop-Location", - "New-PSDrive", - "Remove-PSDrive", - "Get-PSDrive", - "Get-Item", - "New-Item", - "Set-Item", - "Remove-Item", - "Move-Item", - "Rename-Item", - "Copy-Item", - "Clear-Item", - "Invoke-Item", - "Get-PSProvider", - "New-ItemProperty", - "Split-Path", - "Test-Path", - "Get-Process", - "Stop-Process", - "Wait-Process", - "Debug-Process", - "Start-Process", - "Remove-ItemProperty", - "Rename-ItemProperty", - "Resolve-Path", - "Get-Service", - "Stop-Service", - "Start-Service", - "Suspend-Service", - "Resume-Service", - "Restart-Service", - "Set-Service", - "New-Service", - "Remove-Service", - "Set-Content", - "Set-ItemProperty", - "Restart-Computer", - "Stop-Computer", - "Rename-Computer", - "Get-ComputerInfo", - "Get-TimeZone", - "Set-TimeZone") -} diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 deleted file mode 100644 index 6aa5b03d76a..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ /dev/null @@ -1,30 +0,0 @@ -@{ -GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" -PowerShellVersion="3.0" -CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", - "Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", - "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", - "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", "New-Guid", - "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", - "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", - "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", - "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", - "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", - "Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData", - "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", - "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", - "Clear-Variable", "Export-Clixml", "Import-Clixml", "Import-PowerShellDataFile","ConvertTo-Xml", "Select-Xml", "Write-Debug", - "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", - "Get-PSBreakpoint", "Remove-PSBreakpoint", "New-TemporaryFile", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", - "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash", - "Unblock-File", "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", - "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "Get-Verb", "Format-Hex", "Remove-Alias" -FunctionsToExport= "ConvertFrom-SddlString" -AliasesToExport= "fhx" -NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' -} diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml b/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml deleted file mode 100644 index c52b8e6c2c2..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - System.Diagnostics.Eventing.Reader.EventLogConfiguration - - - PSStandardMembers - - - DefaultDisplayPropertySet - - LogName - MaximumSizeInBytes - RecordCount - LogMode - - - - - - - - System.Diagnostics.Eventing.Reader.EventLogRecord - - - PSStandardMembers - - - DefaultDisplayPropertySet - - TimeCreated - ProviderName - Id - Message - - - - - - - - System.Diagnostics.Eventing.Reader.ProviderMetadata - - - ProviderName - Name - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Name - LogLinks - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.CounterSet - - - Counter - Paths - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Path - InstanceName - CookedValue - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet - - - PSStandardMembers - - - DefaultDisplayPropertySet - - Timestamp - Readings - - - - - - - - Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet - - - Readings - - $strPaths = "" - foreach ($ctr in $this.CounterSamples) - { - $strPaths += ($ctr.Path + " :" + "`n") - $strPaths += ($ctr.CookedValue.ToString() + "`n`n") - } - return $strPaths - - - - - diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 deleted file mode 100644 index 5d95cfb3a7d..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 +++ /dev/null @@ -1,16 +0,0 @@ -@{ -GUID="CA046F10-CA64-4740-8FF9-2565DBA61A4F" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -CLRVersion="4.0" -AliasesToExport = @() -FunctionsToExport = @() -CmdletsToExport="Get-WinEvent", "Get-Counter", "Import-Counter", "Export-Counter", "New-WinEvent" -NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" -TypesToProcess="GetEvent.types.ps1xml" -FormatsToProcess="Event.format.ps1xml","Diagnostics.format.ps1xml" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855954' -} diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml b/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml deleted file mode 100644 index 91b3b358a23..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - Microsoft.PowerShell.Commands.LocalUser - - Microsoft.PowerShell.Commands.LocalUser - - - - - - - - - - - - - - - Name - - - Enabled - - - Description - - - - - - - - Microsoft.PowerShell.Commands.LocalGroup - - Microsoft.PowerShell.Commands.LocalGroup - - - - - - - - - - - - - Name - - - Description - - - - - - - - Microsoft.PowerShell.Commands.LocalPrincipal - - Microsoft.PowerShell.Commands.LocalPrincipal - - - - - - - - - - - - - - - ObjectClass - - - Name - - - PrincipalSource - - - - - - - - \ No newline at end of file diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1 deleted file mode 100644 index 8f682e53675..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1 +++ /dev/null @@ -1,32 +0,0 @@ -@{ -RootModule = 'Microsoft.Powershell.LocalAccounts' -ModuleVersion = '1.0.0.0' -GUID = '8e362604-2c0b-448f-a414-a6a690a644e2' -Author = 'Microsoft Corporation' -CompanyName = 'Microsoft Corporation' -Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.' -Description = 'Provides cmdlets to work with local users and local groups' -PowerShellVersion = '3.0' -CLRVersion = '4.0' -FormatsToProcess = @('LocalAccounts.format.ps1xml') -CmdletsToExport = @( - 'Add-LocalGroupMember', - 'Disable-LocalUser', - 'Enable-LocalUser', - 'Get-LocalGroup', - 'Get-LocalGroupMember', - 'Get-LocalUser', - 'New-LocalGroup', - 'New-LocalUser', - 'Remove-LocalGroup', - 'Remove-LocalGroupMember', - 'Remove-LocalUser', - 'Rename-LocalGroup', - 'Rename-LocalUser', - 'Set-LocalGroup', - 'Set-LocalUser' - ) -AliasesToExport= @( "algm", "dlu", "elu", "glg", "glgm", "glu", "nlg", "nlu", "rlg", "rlgm", "rlu", "rnlg", "rnlu", "slg", "slu") -HelpInfoURI = 'https://go.microsoft.com/fwlink/?LinkId=717973' -} - diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 deleted file mode 100644 index 1f68c7822b5..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ /dev/null @@ -1,103 +0,0 @@ -@{ -GUID="EEFCB906-B326-4E99-9F54-8B4BB6EF3C6D" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" -PowerShellVersion="3.0" -CLRVersion="4.0" -NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855958' -AliasesToExport = @("gcb", "scb", "gin", "gtz", "stz") -FunctionsToExport = @() -CmdletsToExport=@("Add-Content", - "Clear-Content", - "Clear-ItemProperty", - "Join-Path", - "Convert-Path", - "Copy-ItemProperty", - "Get-EventLog", - "Clear-EventLog", - "Write-EventLog", - "Limit-EventLog", - "Show-EventLog", - "New-EventLog", - "Remove-EventLog", - "Get-ChildItem", - "Get-Content", - "Get-ItemProperty", - "Get-ItemPropertyValue", - "Get-WmiObject", - "Invoke-WmiMethod", - "Move-ItemProperty", - "Get-Location", - "Set-Location", - "Push-Location", - "Pop-Location", - "New-PSDrive", - "Remove-PSDrive", - "Get-PSDrive", - "Get-Item", - "New-Item", - "Set-Item", - "Remove-Item", - "Move-Item", - "Rename-Item", - "Copy-Item", - "Clear-Item", - "Invoke-Item", - "Get-PSProvider", - "New-ItemProperty", - "Split-Path", - "Test-Path", - "Get-Process", - "Stop-Process", - "Wait-Process", - "Debug-Process", - "Start-Process", - "Remove-ItemProperty", - "Remove-WmiObject", - "Rename-ItemProperty", - "Register-WmiEvent", - "Resolve-Path", - "Get-Service", - "Stop-Service", - "Start-Service", - "Suspend-Service", - "Resume-Service", - "Restart-Service", - "Set-Service", - "New-Service", - "Remove-Service", - "Set-Content", - "Set-ItemProperty", - "Set-WmiInstance", - "Get-Transaction", - "Start-Transaction", - "Complete-Transaction", - "Undo-Transaction", - "Use-Transaction", - "New-WebServiceProxy", - "Get-HotFix", - "Test-Connection", - "Enable-ComputerRestore", - "Disable-ComputerRestore", - "Checkpoint-Computer", - "Get-ComputerRestorePoint", - "Restart-Computer", - "Stop-Computer", - "Restore-Computer", - "Add-Computer", - "Remove-Computer", - "Test-ComputerSecureChannel", - "Reset-ComputerMachinePassword", - "Rename-Computer", - "Get-ControlPanelItem", - "Show-ControlPanelItem", - "Clear-Recyclebin", - "Get-Clipboard", - "Set-Clipboard", - "Get-ComputerInfo", - "Get-TimeZone", - "Set-TimeZone") -} diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 deleted file mode 100644 index 0c0921821d9..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataAdapter.ps1 +++ /dev/null @@ -1,2070 +0,0 @@ -Import-LocalizedData LocalizedData -FileName Microsoft.PowerShell.ODataUtilsStrings.psd1 - - -# Add .NET classes used by the module -Add-Type -TypeDefinition $global:BaseClassDefinitions - -######################################################### -# Generates PowerShell module containing client side -# proxy cmdlets that can be used to interact with an -# OData based server side endpoint. -######################################################### -function ExportODataEndpointProxy -{ - param - ( - [string] $Uri, - [string] $OutputModule, - [string] $MetadataUri, - [PSCredential] $Credential, - [string] $CreateRequestMethod, - [string] $UpdateRequestMethod, - [string] $CmdletAdapter, - [Hashtable] $ResourceNameMapping, - [switch] $Force, - [Hashtable] $CustomData, - [switch] $AllowClobber, - [switch] $AllowUnsecureConnection, - [Hashtable] $Headers, - [string] $ProgressBarStatus, - [System.Management.Automation.PSCmdlet] $PSCmdlet - ) - - [xml] $metadataXML = GetMetaData $MetadataUri $PSCmdlet $Credential $Headers - - [ODataUtils.Metadata] $metaData = ParseMetadata $metadataXML $MetadataUri $CmdletAdapter $PSCmdlet - - VerifyMetaData $MetadataUri $metaData $AllowClobber.IsPresent $PSCmdlet $progressBarStatus $CmdletAdapter $CustomData $ResourceNameMapping - - GenerateClientSideProxyModule $metaData $MetadataUri $Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $ResourceNameMapping $CustomData $ProgressBarStatus $PSCmdlet -} - -######################################################### -# ParseMetaData is a helper function used to parse the -# metadata to convert it in to an object structure for -# further consumption during proxy generation. -######################################################### -function ParseMetaData -{ - param - ( - [xml] $metadataXml, - [string] $metaDataUri, - [string] $cmdletAdapter, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - # $metaDataUri is already validated at the cmdlet layer. - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "ParseMetadata") } - - if($null -eq $metadataXml) - { - $errorMessage = ($LocalizedData.InValidXmlInMetadata -f $metaDataUri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadataUriFormat" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - Write-Verbose $LocalizedData.VerboseParsingMetadata - - # Check the OData version in the fetched metadata to make sure that - # OData version (and hence the protocol) used in the metadata is - # supported by the adapter used for executing the generated - # proxy cmdlets. - if(($null -ne $metadataXML) -and ($null -ne $metadataXML.Edmx)) - { - if($null -eq $metadataXML.Edmx.Version) - { - $errorMessage = ($LocalizedData.ODataVersionNotFound -f $MetadataUri) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyODataVersionNotFound" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - $metaDataVersion = New-Object -TypeName System.Version -ArgumentList @($metadataXML.Edmx.Version) - - # When we support plug-in model, We would have to fetch the - # $minSupportedVersionString & $maxSupportedVersionString - # from the plug-in instead of using an hardcoded value. - $minSupportedVersionString = '1.0' - $maxSupportedVersionString = '3.0' - $minSupportedVersion = New-Object -TypeName System.Version -ArgumentList @($minSupportedVersionString) - $maxSupportedVersion = New-Object -TypeName System.Version -ArgumentList @($maxSupportedVersionString) - - $minVersionComparisonResult = $minSupportedVersion.CompareTo($metaDataVersion) - $maxVersionComparisonResult = $maxSupportedVersion.CompareTo($metaDataVersion) - - if(-not($minVersionComparisonResult -lt $maxVersionComparisonResult)) - { - $errorMessage = ($LocalizedData.ODataVersionNotSupported -f $metadataXML.Edmx.Version, $MetadataUri, $minSupportedVersionString, $maxSupportedVersionString, $CmdletAdapter) - $exception = [System.NotSupportedException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyODataVersionNotSupported" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - else - { - $errorMessage = ($LocalizedData.InValidMetadata -f $MetadataUri) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadata" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - foreach ($schema in $MetadataXML.Edmx.DataServices.Schema) - { - if (($null -ne $schema) -and [string]::IsNullOrEmpty($schema.NameSpace )) - { - $callerPSCmdlet = $callerPSCmdlet -as [System.Management.Automation.PSCmdlet] - $errorMessage = ($LocalizedData.InValidSchemaNamespace -f $metaDataUri) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidSchemaNamespace" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - - $metaData = New-Object -TypeName ODataUtils.Metadata - - # this is a processing queue for those types that require base types that haven't been defined yet - $entityAndComplexTypesQueue = @{} - - foreach ($schema in $metadataXml.Edmx.DataServices.Schema) - { - if ($null -eq $schema) - { - Write-Error $LocalizedData.EmptySchema - continue - } - - if ($null -eq $metadata.Namespace) - { - $metaData.Namespace = $schema.Namespace - } - - foreach ($entityType in $schema.EntityType) - { - $baseType = $null - - if ($null -ne $entityType.BaseType) - { - # add it to the processing queue - $baseType = GetBaseType $entityType $metaData - if ($null -eq $baseType) - { - $entityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='EntityType'; value=$entityType}) - continue - } - } - - [ODataUtils.EntityType] $newType = ParseMetadataTypeDefinition $entityType $baseType $metaData $schema.Namespace $true - $metaData.EntityTypes += $newType - AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $schema.Namespace - } - - foreach ($complexType in $schema.ComplexType) - { - $baseType = $null - - if ($null -ne $complexType.BaseType) - { - # add it to the processing queue - $baseType = GetBaseType $complexType $metaData - if ($null -eq $baseType) - { - $entityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='ComplexType'; value=$complexType}) - continue - } - } - - [ODataUtils.EntityType] $newType = ParseMetadataTypeDefinition $complexType $baseType $metaData $schema.Namespace $false - $metaData.ComplexTypes += $newType - AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $schema.Namespace - } - } - - foreach ($schema in $metadataXml.Edmx.DataServices.Schema) - { - foreach ($entityContainer in $schema.EntityContainer) - { - if ($entityContainer.IsDefaultEntityContainer) - { - $metaData.DefaultEntityContainerName = $entityContainer.Name - } - - $entityTypeToEntitySetMapping = @{}; - foreach ($entitySet in $entityContainer.EntitySet) - { - $entityType = $metaData.EntityTypes | Where-Object { $_.Name -eq $entitySet.EntityType.Split('.')[-1] } - $entityTypeName = $entityType.Name - - if($entityTypeToEntitySetMapping.ContainsKey($entityTypeName)) - { - $existingEntitySetName = $entityTypeToEntitySetMapping[$entityTypeName] - - $errorMessage = ($LocalizedData.EntityNameConflictError -f $metaDataUri, $existingEntitySetName, $entitySet.Name, $entityTypeName) - $exception = [System.NotSupportedException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyEntityTypeMappingError" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - else - { - $entityTypeToEntitySetMapping.Add($entityTypeName, $entitySet.Name) - } - - $newEntitySet = [ODataUtils.EntitySet] @{ - "Namespace" = $schema.Namespace; - "Name" = $entitySet.Name; - "Type" = $entityType; - } - - $metaData.EntitySets += $newEntitySet - } - } - } - - foreach ($schema in $metadataXml.Edmx.DataServices.Schema) - { - foreach ($association in $schema.Association) - { - $newAssociationType = [ODataUtils.AssociationType] @{ - "Namespace" = $schema.Namespace; - "EndType1" = $metaData.EntityTypes | Where-Object { $_.Name -eq $association.End[0].Type.Split('.')[-1] }; - "NavPropertyName1" = $association.End[0].Role; - "Multiplicity1" = $association.End[0].Multiplicity; - - "EndType2" = $metaData.EntityTypes | Where-Object { $_.Name -eq $association.End[1].Type.Split('.')[-1] }; - "NavPropertyName2" = $association.End[1].Role; - "Multiplicity2" = $association.End[1].Multiplicity; - } - - $newAssociation = [ODataUtils.AssociationSet] @{ - "Namespace" = $schema.Namespace; - "Name" = $association.Name; - "Type" = $newAssociationType; - } - - $metaData.Associations += $newAssociation - } - } - - foreach ($schema in $metadataXml.Edmx.DataServices.Schema) - { - foreach ($action in $schema.EntityContainer.FunctionImport) - { - # HttpMethod is only used for legacy Service Operations - if ($null -eq $action.HttpMethod) - { - if ($null -ne $action.IsSideEffecting) - { - $isSideEffecting = $action.IsSideEffecting - } - else - { - $isSideEffecting = $true - } - - $newAction = [ODataUtils.Action] @{ - "Namespace" = $schema.Namespace; - "Verb" = $action.Name; - "IsSideEffecting" = $isSideEffecting; - "IsBindable" = $action.IsBindable; - # we don't care about IsAlwaysBindable, since we populate actions information from $metaData - # so we can't know the state of the entity - } - - # Actions are always SideEffecting, otherwise it's an OData function - if ($newAction.IsSideEffecting -ne $false) - { - foreach ($parameter in $action.Parameter) - { - if ($null -ne $parameter.Nullable) - { - $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable); - } - - $newParameter = [ODataUtils.TypeProperty] @{ - "Name" = $parameter.Name; - "TypeName" = $parameter.Type; - "IsNullable" = $parameterIsNullable - } - - $newAction.Parameters += $newParameter - } - - # IsBindable means it operates on Entity/ies - if ($newAction.IsBindable) - { - $regex = "Collection\((.+)\)" - - if ($newAction.Parameters[0].TypeName -match $regex) - { - # action operating on a collection of entities - $insideTypeName = Convert-ODataTypeToCLRType $Matches[1] - - $newAction.EntitySet = $metaData.EntitySets | Where-Object { ($_.Type.Namespace + "." + $_.Type.Name) -eq $insideTypeName } - $newAction.IsSingleInstance = $false - } - else - { - # actions operating on a single instance - $newAction.EntitySet = $metaData.EntitySets | Where-Object { ($_.Type.Namespace + "." + $_.Type.Name) -eq $newAction.Parameters[0].TypeName } - - $newAction.IsSingleInstance = $true - } - } - - $metaData.Actions += $newAction - } - } - } - } - - $metaData -} - -######################################################### -# VerifyMetaData is a helper function used to validate -# the processed metadata to make sure client side proxy -# can be created for the supplied metadata. -######################################################### -function VerifyMetaData -{ - param - ( - [string] $metaDataUri, - [ODataUtils.Metadata] $metaData, - [boolean] $allowClobber, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet, - [string] $progressBarStatus, - [string] $cmdletAdapter, - [Hashtable] $customData, - [Hashtable] $resourceNameMapping - ) - - # $metaDataUri & $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "VerifyMetaData") } - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "VerifyMetaData") } - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "VerifyMetaData") } - - Write-Verbose $LocalizedData.VerboseVerifyingMetadata - - if ($metadata.EntitySets.Count -le 0) - { - $errorMessage = ($LocalizedData.NoEntitySets -f $metaDataUri) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - if ($metadata.EntityTypes.Count -le 0) - { - $errorMessage = ($LocalizedData.NoEntityTypes -f $metaDataUri) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - # All the generated proxy cmdlets would have the following parameters added. - # The ODataAdapter has the default implementation on how to handle the - # scenario when these parameters are used during proxy invocations. - # The default implementation can be overridden using adapter derivation model. - $reservedProperties = @("Filter", "IncludeTotalResponseCount", "OrderBy", "Select", "Skip", "Top", "ConnectionUri", "CertificateThumbprint", "Credential") - $validEntitySets = @() - $sessionCommands = Get-Command -All - - foreach ($entitySet in $metaData.EntitySets) - { - if ($null -eq $entitySet.Type) - { - $errorMessage = ($LocalizedData.EntitySetUndefinedType -f $metaDataUri, $entitySet.Name) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metaDataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - if ($cmdletAdapter -eq "NetworkControllerAdapter" -And $customData -And $customData.Contains($entitySet.Name) -eq $false) - { - continue - } - - $hasConflictingProperty = $false - $hasConflictingCommand = $false - - $entityAndNavigationProperties = (GetAllProperties $entitySet.Type) + (GetAllProperties $entitySet.Type -IncludeOnlyNavigationProperties) - foreach($entityProperty in $entityAndNavigationProperties) - { - if($reservedProperties.Contains($entityProperty.Name)) - { - $hasConflictingProperty = $true - if(!$allowClobber) - { - # Write Error message and skip current Entity Set. - $errorMessage = ($LocalizedData.SkipEntitySetProxyCreation -f $entitySet.Name, $entitySet.Type.Name, $entityProperty.Name) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointDefaultPropertyCollision" $null ([System.Management.Automation.ErrorCategory]::InvalidOperation) $exception $metaDataUri - $callerPSCmdlet.WriteError($errorRecord) - } - else - { - $warningMessage = ($LocalizedData.EntitySetProxyCreationWithWarning -f $entitySet.Name, $entityProperty.Name, $entitySet.Type.Name) - $callerPSCmdlet.WriteWarning($warningMessage) - } - } - } - - foreach($currentCommand in $sessionCommands) - { - # The generated command Noun can be set using ResourceNameMapping - $generatedCommandName = $entitySet.Name - if ($resourceNameMapping -And $resourceNameMapping.Contains($entitySet.Name)) { - $generatedCommandName = $resourceNameMapping[$entitySet.Name] - } - - if(($null -ne $currentCommand.Noun -and $currentCommand.Noun -eq $generatedCommandName) -and - ($currentCommand.Verb -eq "Get" -or - $currentCommand.Verb -eq "Set" -or - $currentCommand.Verb -eq "New" -or - $currentCommand.Verb -eq "Remove")) - { - $hasConflictingCommand = $true - VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` - $LocalizedData.EntitySetConflictCommandCreationWithWarning ` - $entitySet.Name $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet - } - } - - foreach($currentAction in $metaData.Actions) - { - $actionCommand = "Invoke-" + "$($entitySet.Name)$($currentAction.Verb)" - - foreach($currentCommand in $sessionCommands) - { - if($actionCommand -eq $currentCommand.Name) - { - $hasConflictingCommand = $true - VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` - $LocalizedData.EntitySetConflictCommandCreationWithWarning $entitySet.Name ` - $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet - } - } - } - - if(!($hasConflictingProperty -or $hasConflictingCommand)-or $allowClobber) - { - $validEntitySets += $entitySet - } - } - - if ($cmdletAdapter -ne "NetworkControllerAdapter") { - - $metaData.EntitySets = $validEntitySets - - $validServiceActions = @() - $hasConflictingServiceActionCommand = $true - foreach($currentAction in $metaData.Actions) - { - $serviceActionCommand = "Invoke-" + "$($currentAction.Verb)" - - foreach($currentCommand in $sessionCommands) - { - if($serviceActionCommand -eq $currentCommand.Name) - { - $hasConflictingServiceActionCommand = $true - VerifyMetadataHelper $LocalizedData.SkipConflictServiceActionCommandCreation ` - $LocalizedData.ConflictServiceActionCommandCreationWithWarning $entitySet.Name ` - $currentCommand.Name $metaDataUri $allowClobber $callerPSCmdlet - } - } - - if(!$hasConflictingServiceActionCommand -or $allowClobber) - { - $validServiceActions += $currentAction - } - } - - $metaData.Actions = $validServiceActions - } - - # Update Progress bar. - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 5 20 1 1 -} - -######################################################### -# GenerateClientSideProxyModule is a helper function used -# to generate a PowerShell module that serves as a client -# side proxy for interacting with the server side -# OData endpoint. The proxy module contains proxy cmdlets -# implemented in CDXML modules and they are exposed -# through module manifest as nested modules. -# One CDXML module is created for each EntitySet -# described in the metadata. Each CDXML module contains -# CRUD & Service Action specific proxy cmdlets targeting -# the underlying EntityType. There is 1:M mapping between -# EntitySet & its underlying EntityTypes (i.e., all -# entities with in the single EntitySet will be of the -# same EntityType but there can be multiple entities -# of the same type with in an EntitySet). -######################################################### -function GenerateClientSideProxyModule -{ - param - ( - [ODataUtils.Metadata] $metaData, - [string] $metaDataUri, - [string] $uri, - [string] $outputModule, - [string] $createRequestMethod, - [string] $updateRequestMethod, - [string] $cmdletAdapter, - [Hashtable] $resourceNameMapping, - [Hashtable] $customData, - [string] $progressBarStatus, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - # $uri, $outputModule, $metaDataUri, $createRequestMethod, $updateRequestMethod, & $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") } - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateClientSideProxyModule") } - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") } - - # This function performs the following set of tasks - # while creating the client side proxy module: - # 1. If the server side endpoint exposes complex types, - # the client side proxy complex types are created - # as C# class in ComplexTypeDefinitions.psm1 - # 2. Creates proxy cmdlets for CRUD operations. - # 3. Creates proxy cmdlets for Service action operations. - # 4. Creates module manifest. - - Write-Verbose ($LocalizedData.VerboseSavingModule -f $outputModule) - - $typeDefinitionFileName = "ComplexTypeDefinitions.psm1" - $complexTypeMapping = GenerateComplexTypeDefinition $metaData $metaDataUri $outputModule $typeDefinitionFileName $cmdletAdapter $callerPSCmdlet - - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 20 20 1 1 - - $complexTypeFileDefinitionPath = Join-Path -Path $outputModule -ChildPath $typeDefinitionFileName - - if(Test-Path -Path $complexTypeFileDefinitionPath) - { - $proxyFile = New-Object -TypeName System.IO.FileInfo -ArgumentList $complexTypeFileDefinitionPath | Get-Item - if($null -ne $callerPSCmdlet) - { - $callerPSCmdlet.WriteObject($proxyFile) - } - } - - $currentEntryCount = 0 - foreach ($entitySet in $metaData.EntitySets) - { - $currentEntryCount += 1 - if ($cmdletAdapter -eq "NetworkControllerAdapter" -And $customData -And $customData.Contains($entitySet.Name) -eq $false) - { - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $metaData.EntitySets.Count $currentEntryCount - continue - } - - GenerateCRUDProxyCmdlet $entitySet $metaData $uri $outputModule $createRequestMethod $updateRequestMethod $cmdletAdapter $resourceNameMapping $customData $complexTypeMapping "Export-ODataEndpointProxy" $progressBarStatus 40 20 $metaData.EntitySets.Count $currentEntryCount $callerPSCmdlet - } - - GenerateServiceActionProxyCmdlet $metaData $uri "$outputModule\ServiceActions.cdxml" $complexTypeMapping $progressBarStatus $callerPSCmdlet - - $moduleDirInfo = [System.IO.DirectoryInfo]::new($outputModule) - $moduleManifestName = $moduleDirInfo.Name + ".psd1" - GenerateModuleManifest $metaData $outputModule\$moduleManifestName @($typeDefinitionFileName, 'ServiceActions.cdxml') $resourceNameMapping $progressBarStatus $callerPSCmdlet -} - -######################################################### -# GenerateCRUDProxyCmdlet is a helper function used -# to generate Get, Set, New & Remove proxy cmdlet. -# The proxy cmdlet is generated in the CDXML -# compliant format. -######################################################### -function GenerateCRUDProxyCmdlet -{ - param - ( - [ODataUtils.EntitySet] $entitySet, - [ODataUtils.Metadata] $metaData, - [string] $uri, - [string] $outputModule, - [string] $createRequestMethod, - [string] $UpdateRequestMethod, - [string] $cmdletAdapter, - [Hashtable] $resourceNameMapping, - [Hashtable] $customData, - [Hashtable] $complexTypeMapping, - [string] $progressBarActivityName, - [string] $progressBarStatus, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight, - [int] $totalNumberofEntries, - [int] $currentEntryCount, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - # $uri, $outputModule, $metaDataUri, $createRequestMethod, $updateRequestMethod, & $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $entitySet) { throw ($LocalizedData.ArguementNullError -f "EntitySet", "GenerateClientSideProxyModule") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") } - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateClientSideProxyModule") } - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") } - - $entitySetName = $entitySet.Name - if(($null -ne $resourceNameMapping) -and - $resourceNameMapping.ContainsKey($entitySetName)) - { - $entitySetName = $resourceNameMapping[$entitySetName] - } - else - { - $entitySetName = $entitySet.Type.Name - } - - $Path = "$OutputModule\$entitySetName.cdxml" - - $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) - - if ($null -eq $xmlWriter) - { - throw ($LocalizedData.XmlWriterInitializationError -f $entitySet.Name) - } - - $xmlWriter = SaveCDXMLHeader $xmlWriter $uri $entitySet.Name $entitySetName $cmdletAdapter - - # Get the keys depending on whether the url contains variables or not - if ($CmdletAdapter -ne "NetworkControllerAdapter") - { - $keys = (GetAllProperties $entitySet.Type) | Where-Object { $_.IsKey } - } - else - { - $name = $entitySet.Name - $keys = GetKeys $entitySet $customData.$name 'Get' - } - - $navigationProperties = GetAllProperties $entitySet.Type -IncludeOnlyNavigationProperties - - GenerateGetProxyCmdlet $xmlWriter $metaData $keys $navigationProperties $cmdletAdapter $complexTypeMapping - - $nonKeyProperties = (GetAllProperties $entitySet.Type) | Where-Object { -not $_.isKey } - $nullableProperties = $nonKeyProperties | Where-Object { $_.isNullable } - $nonNullableProperties = $nonKeyProperties | Where-Object { -not $_.isNullable } - - $xmlWriter.WriteStartElement('StaticCmdlets') - - $keyProperties = $keys - - # Do operations specifically needed for NetworkController cmdlets - if ($CmdletAdapter -eq "NetworkControllerAdapter") - { - $keyProperties = GetKeys $entitySet $customData.$name 'New' - $additionalProperties = GetNetworkControllerAdditionalProperties $navigationProperties $metaData - $nullableProperties = UpdateNetworkControllerSpecificProperties $nullableProperties $additionalProperties $keyProperties $true - $nonNullableProperties = UpdateNetworkControllerSpecificProperties $nonNullableProperties $additionalProperties $keyProperties $false - } - - GenerateNewProxyCmdlet $xmlWriter $metaData $keyProperties $nonNullableProperties $nullableProperties $navigationProperties $cmdletAdapter $complexTypeMapping - - if ($CmdletAdapter -ne "NetworkControllerAdapter") - { - GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $complexTypeMapping - } - - if ($CmdletAdapter -eq "NetworkControllerAdapter") - { - $keyProperties = GetKeys $entitySet $customData.$name 'Remove' - } - - GenerateRemoveProxyCmdlet $xmlWriter $metaData $keyProperties $navigationProperties $cmdletAdapter $complexTypeMapping - - $entityActions = $metaData.Actions | Where-Object { ($_.EntitySet.Namespace -eq $entitySet.Namespace) -and ($_.EntitySet.Name -eq $entitySet.Name) } - - if ($entityActions.Length -gt 0) - { - foreach($action in $entityActions) - { - $xmlWriter = GenerateActionProxyCmdlet $xmlWriter $metaData $action $entitySet.Name $true $keys $complexTypeMapping - } - } - - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntityTypeName') - $xmlWriter.WriteString("$($entitySet.Type.Namespace).$($entitySet.Type.Name)") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntitySetName') - $xmlWriter.WriteString("$($entitySet.Namespace).$($entitySet.Name)") - $xmlWriter.WriteEndElement() - - # Add the customUri to privateData - if ($CmdletAdapter -eq "NetworkControllerAdapter") - { - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', "CustomUriSuffix") - $xmlWriter.WriteString($CustomData.$name) - $xmlWriter.WriteEndElement() - } - - # Add CreateRequestMethod and UpdateRequestMethod to privateData - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod') - $xmlWriter.WriteString("$CreateRequestMethod") - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod') - $xmlWriter.WriteString("$UpdateRequestMethod") - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - SaveCDXMLFooter $xmlWriter - - ProcessStreamHelper ($LocalizedData.VerboseSavedCDXML -f $($entitySetName), $Path) $progressBarActivityName $progressBarStatus $previousSegmentWeight $currentSegmentWeight $totalNumberofEntries $currentEntryCount $Path $callerPSCmdlet -} - -######################################################### -# GenerateGetProxyCmdlet is a helper function used -# to generate Get-* proxy cmdlet. The proxy cmdlet is -# generated in the CDXML compliant format. -######################################################### -function GenerateGetProxyCmdlet -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.Metadata] $metaData, - [object[]] $keys, - [object[]] $navigationProperties, - [string] $cmdletAdapter, - [Hashtable] $complexTypeMapping - ) - - # $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateGetProxyCmdlet") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateGetProxyCmdlet") } - - $xmlWriter.WriteStartElement('InstanceCmdlets') - $xmlWriter.WriteStartElement('GetCmdletParameters') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - - # adding key parameters and association parameters to QueryableProperties, each in a different parameter set - # to be used by GET cmdlet - if (($null -ne $keys -and $keys.Length -gt 0) -or (($null -ne $navigationProperties -and $navigationProperties.Length -gt 0) -and $cmdletAdapter -ne "NetworkControllerAdapter")) - { - $xmlWriter.WriteStartElement('QueryableProperties') - $position = 0 - - $keys | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', $_.Name) - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $_.Name) - $xmlWriter.WriteAttributeString('CmdletParameterSets', 'Default') - $xmlWriter.WriteAttributeString('IsMandatory', $_.IsMandatory.ToString().ToLower()) - $xmlWriter.WriteAttributeString('Position', $position) - if($_.IsMandatory) - { - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $position++ - } - - # This behaviour is different for NetworkController specific cmdlets. - if ($CmdletAdapter -ne "NetworkControllerAdapter") - { - $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - $associatedType = GetAssociatedType $metaData $_ - $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType - $nvgProperty = $_ - - (GetAllProperties $associatedType) | Where-Object { $_.IsKey } | ForEach-Object { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', $associatedEntitySet.Name + ':' + $_.Name + ':Key') - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', 'Associated' + $nvgProperty.Name + $_.Name) - $xmlWriter.WriteAttributeString('CmdletParameterSets', $nvgProperty.AssociationName) - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - } - - - # Add Query Parameters (i.e., Top, Skip, OrderBy, Filter) to the generated Get-* cmdlets. - $queryParameters = - @{ - "Filter" = "Edm.String"; - "IncludeTotalResponseCount" = "switch"; - "OrderBy" = "Edm.String"; - "Select" = "Edm.String"; - "Skip" = "Edm.Int32"; - "Top" = "Edm.Int32"; - } - - foreach($currentQueryParameter in $queryParameters.Keys) - { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', "QueryOption:" + $currentQueryParameter) - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $queryParameters[$currentQueryParameter] - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $currentQueryParameter) - - if($queryParameters[$currentQueryParameter] -eq "Edm.String") - { - $xmlWriter.WriteStartElement('ValidateNotNullOrEmpty') - $xmlWriter.WriteEndElement() - } - - if($queryParameters[$currentQueryParameter] -eq "Edm.Int32") - { - $minValue = 1 - # For Skip Query parameter we want to support 0 as the - # minimum skip value in order to support client side paging. - if($currentQueryParameter -eq 'Skip') - { - $minValue = 0 - } - $xmlWriter.WriteStartElement('ValidateRange') - $xmlWriter.WriteAttributeString('Min', $minValue) - $xmlWriter.WriteAttributeString('Max', [int]::MaxValue) - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - } - - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('GetCmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Get') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() -} - -######################################################### -# GenerateNewProxyCmdlet is a helper function used -# to generate New-* proxy cmdlet. The proxy cmdlet is -# generated in the CDXML compliant format. -######################################################### -function GenerateNewProxyCmdlet -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.Metadata] $metaData, - [object[]] $keyProperties, - [object[]] $nonNullableProperties, - [object[]] $nullableProperties, - [object[]] $navigationProperties, - [string] $cmdletAdapter, - [Hashtable] $complexTypeMapping - ) - - # $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateNewProxyCmdlet") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateNewProxyCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'New') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', 'Create') - $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') - - AddParametersNode $xmlWriter $keyProperties $nonNullableProperties $nullableProperties $null $true $true $complexTypeMapping - $xmlWriter.WriteEndElement() - - # This behaviour is different for NetworkControllerCmdlets - if ($CmdletAdapter -ne "NetworkControllerAdapter") - { - $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - $associatedType = GetAssociatedType $metaData $_ - $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Association:Create:$($associatedEntitySet.Name)") - $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) - - $associatedKeys = ((GetAllProperties $associatedType) | Where-Object { $_.isKey }) - - AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping - $xmlWriter.WriteEndElement() - } - } - - $xmlWriter.WriteEndElement() -} - -######################################################### -# GenerateRemoveProxyCmdlet is a helper function used -# to generate Remove-* proxy cmdlet. The proxy cmdlet is -# generated in the CDXML compliant format. -######################################################### -function GenerateRemoveProxyCmdlet -{ - param - ( - - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.Metadata] $metaData, - [object[]] $keyProperties, - [object[]] $navigationProperties, - [string] $cmdletAdapter, - [Hashtable] $complexTypeMapping - ) - - # $metaData, $cmdletAdapter & $cmdletAdapter are already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateRemoveProxyCmdlet") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateRemoveProxyCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Remove') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', 'Delete') - $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') - - # This behaviour is different for NetworkControllerCmdlets - if ($CmdletAdapter -eq "NetworkControllerAdapter") - { - # Add etag for NetworkControllerCmdlets - $otherProperties = @([ODataUtils.TypeProperty] @{ - "Name" = "Etag"; - "TypeName" = "Edm.String"; - "IsNullable" = $true; - }) - - AddParametersNode $xmlWriter $keyProperties $null $otherProperties $null $true $true $complexTypeMapping - } - else - { - AddParametersNode $xmlWriter $keyProperties $null $null $null $true $true $complexTypeMapping - } - - $xmlWriter.WriteEndElement() - - # This behaviour is different for NetworkControllerCmdlets - if ($CmdletAdapter -ne "NetworkControllerAdapter") - { - $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - - $associatedType = GetAssociatedType $metaData $_ - $associatedEntitySet = GetEntitySetForEntityType $metaData $associatedType - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Association:Delete:$($associatedEntitySet.Name)") - $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) - - $associatedType = GetAssociatedType $metaData $_ - $associatedKeys = ((GetAllProperties $associatedType) | Where-Object { $_.isKey }) - - AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping - $xmlWriter.WriteEndElement() - } - } - $xmlWriter.WriteEndElement() -} - -######################################################### -# GenerateActionProxyCmdlet is a helper function used -# to generate Invoke-* proxy cmdlet. These proxy cmdlets -# support Instance/Service level actions. They are -# generated in the CDXML compliant format. -######################################################### -function GenerateActionProxyCmdlet -{ - param - ( - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.Metadata] $metaData, - [ODataUtils.Action] $action, - [string] $noun, - [bool] $isInstanceAction, - [ODataUtils.TypeProperty] $keys, - [Hashtable] $complexTypeMapping - ) - - # $metaData is already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateActionProxyCmdlet") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateActionProxyCmdlet") } - if($null -eq $action) { throw ($LocalizedData.ArguementNullError -f "Action", "GenerateActionProxyCmdlet") } - if($null -eq $noun) { throw ($LocalizedData.ArguementNullError -f "Noun", "GenerateActionProxyCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Invoke') - $xmlWriter.WriteAttributeString('Noun', "$($noun)$($action.Verb)") - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Action:$($action.Verb):$($action.EntitySet.Name)") - - $xmlWriter.WriteStartElement('Parameters') - - $keys | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key') - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $_.Name) - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - $i = -1 - foreach ($parameter in $action.Parameters) - { - $i++ - - # for Instance actions, first parameter is Entity Set which we refer to using keys - if ($isInstanceAction -and ($i -eq 0)) - { - continue - } - - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name) - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $parameter.TypeName - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $parameter.Name) - if (-not $parameter.IsNullable) - { - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - # Add -Force parameter to Service Action cmdlets. - AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - $xmlWriter -} - -######################################################### -# GenerateServiceActionProxyCmdlet is a helper function -# used to generate Invoke-* proxy cmdlet. These proxy -# cmdlets support all Service-level actions. They are -# generated in the CDXML compliant format. -######################################################### -function GenerateServiceActionProxyCmdlet -{ - param - ( - [Parameter(Mandatory=$true)] - [ODataUtils.Metadata] $metaData, - [Parameter(Mandatory=$true)] - [string] $uri, - [Parameter(Mandatory=$true)] - [string] $path, - [Hashtable] $complexTypeMapping, - [string] $progressBarStatus, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - # $uri is already validated at the cmdlet layer. - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateServiceActionProxyCmdlet") } - - $xmlWriter = New-Object System.XMl.XmlTextWriter($path,$Null) - - if ($null -eq $xmlWriter) - { - throw $LocalizedData.XmlWriterInitializationError -f "ServiceActions" - } - - $xmlWriter = SaveCDXMLHeader $xmlWriter $uri 'ServiceActions' 'ServiceActions' - - $actions = $metaData.Actions | Where-Object { $null -eq $_.EntitySet } - - if ($actions.Length -gt 0) - { - $xmlWriter.WriteStartElement('StaticCmdlets') - - foreach ($action in $actions) - { - $xmlWriter = GenerateActionProxyCmdlet $xmlWriter $metaData $action '' $false $null $complexTypeMapping - } - - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'Namespace') - $xmlWriter.WriteString("$($EntitySet.Namespace)") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - SaveCDXMLFooter $xmlWriter - - ProcessStreamHelper ($LocalizedData.VerboseSavedServiceActions -f $path) "Export-ODataEndpointProxy" $progressBarStatus 60 20 1 1 $path $callerPSCmdlet -} - -######################################################### -# GenerateModuleManifest is a helper function used -# to generate a wrapper module manifest file. The -# generated module manifest is persisted to the disk at -# the specified OutPutModule path. When the module -# manifest is imported, the following commands will -# be imported: -# 1. Get, Set, New & Remove proxy cmdlets. -# 2. If the server side Odata endpoint exposes complex -# types, then the corresponding client side proxy -# complex types imported. -# 3. Service Action proxy cmdlets. -######################################################### -function GenerateModuleManifest -{ - param - ( - [ODataUtils.Metadata] $metaData, - [String] $modulePath, - [string[]] $additionalModules, - [Hashtable] $resourceNameMapping, - [string] $progressBarStatus, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateModuleManifest") } - if($null -eq $modulePath) { throw ($LocalizedData.ArguementNullError -f "ModulePath", "GenerateModuleManifest") } - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateModuleManifest") } - - $NestedModules = @() - foreach ($entitySet in $metaData.EntitySets) - { - $entitySetName = $entitySet.Name - if(($null -ne $resourceNameMapping) -and - $resourceNameMapping.ContainsKey($entitySetName)) - { - $entitySetName = $resourceNameMapping[$entitySetName] - } - else - { - $entitySetName = $entitySet.Type.Name - } - - $NestedModules += "$OutputModule\$($entitySetName).cdxml" - } - - New-ModuleManifest -Path $modulePath -NestedModules ($AdditionalModules + $NestedModules) - - ProcessStreamHelper ($LocalizedData.VerboseSavedModuleManifest -f $modulePath) "Export-ODataEndpointProxy" $progressBarStatus 80 20 1 1 $modulePath $callerPSCmdlet -} - -######################################################### -# GetBaseType is a helper function used to fetch the -# base type of the given type. -######################################################### -function GetBaseType -{ - param - ( - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.Metadata] $metaData - ) - - if ($null -ne $metadataEntityDefinition -and - $null -ne $metaData -and - $null -ne $metadataEntityDefinition.BaseType) - { - $baseType = $metaData.EntityTypes | Where-Object {$_.Namespace+"."+$_.Name -eq $metadataEntityDefinition.BaseType} - if ($null -eq $baseType) - { - $baseType = $metaData.ComplexTypes | Where-Object {$_.Namespace+"."+$_.Name -eq $metadataEntityDefinition.BaseType} - } - } - - if ($null -ne $baseType) - { - $baseType[0] - } -} - -######################################################### -# AddDerivedTypes is a helper function used to process -# derived types of a newly added type, that were -# previously waiting in the queue. -######################################################### -function AddDerivedTypes -{ - param - ( - [ODataUtils.EntityType] $baseType, - [Hashtable]$entityAndComplexTypesQueue, - [ODataUtils.Metadata] $metaData, - [string] $namespace - ) - - # $metaData is already validated at the cmdlet layer. - if($null -eq $baseType) { throw ($LocalizedData.ArguementNullError -f "BaseType", "AddDerivedTypes") } - if($null -eq $entityAndComplexTypesQueue) { throw ($LocalizedData.ArguementNullError -f "EntityAndComplexTypesQueue", "AddDerivedTypes") } - if($null -eq $namespace) { throw ($LocalizedData.ArguementNullError -f "Namespace", "AddDerivedTypes") } - - $baseTypeFullName = $baseType.Namespace + '.' + $baseType.Name - - if ($entityAndComplexTypesQueue.ContainsKey($baseTypeFullName)) - { - foreach ($type in $entityAndComplexTypesQueue[$baseTypeFullName]) - { - if ($type.type -eq 'EntityType') - { - $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metaData $namespace $true - $metaData.EntityTypes += $newType - } - else - { - $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metaData $namespace $false - $metaData.ComplexTypes += $newType - } - - AddDerivedTypes $newType $entityAndComplexTypesQueue $metaData $namespace - } - } -} - -######################################################### -# ParseMetadataTypeDefinition is a helper function used -# to parse types definitions element of metadata xml. -######################################################### -function ParseMetadataTypeDefinition -{ - param - ( - [Parameter(Mandatory=$true)] - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.EntityType] $baseType, - [ODataUtils.Metadata] $metaData, - [string] $namespace, - [bool] $isEntity - ) - - # $metaData is already validated at the cmdlet layer. - if($null -eq $metadataEntityDefinition) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") } - if($null -eq $namespace) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") } - - $newEntityType = [ODataUtils.EntityType] @{ - "Namespace" = $namespace; - "Name" = $metadataEntityDefinition.Name; - "IsEntity" = $isEntity; - "BaseType" = $baseType; - } - - # properties defined on EntityType - $newEntityType.EntityProperties = $metadataEntityDefinition.Property | ForEach-Object { - if ($null -ne $_) - { - if ($null -ne $_.Nullable) - { - $newPropertyIsNullable = [System.Convert]::ToBoolean($_.Nullable) - } - else - { - $newPropertyIsNullable = $true - } - - [ODataUtils.TypeProperty] @{ - "Name" = $_.Name; - "TypeName" = $_.Type; - "IsNullable" = $newPropertyIsNullable; - } - } - } - - # navigation properties defined on EntityType - $newEntityType.NavigationProperties = $metadataEntityDefinition.NavigationProperty | ForEach-Object { - if ($null -ne $_) - { - ($AssociationNamespace, $AssociationName) = SplitNamespaceAndName $_.Relationship - [ODataUtils.NavigationProperty] @{ - "Name" = $_.Name; - "FromRole" = $_.FromRole; - "ToRole" = $_.ToRole; - "AssociationNamespace" = $AssociationNamespace; - "AssociationName" = $AssociationName; - } - } - } - - foreach ($entityTypeKey in $metadataEntityDefinition.Key.PropertyRef) - { - ((GetAllProperties $newEntityType) | Where-Object { $_.Name -eq $entityTypeKey.Name }).IsKey = $true - } - - $newEntityType -} - -######################################################### -# GetAllProperties is a helper function used to fetch -# the entity properties or navigation properties of -# the entity type as well as that of complete base -# type hierarchy. -######################################################### -function GetAllProperties -{ - param - ( - [ODataUtils.EntityType] $entityType, - [switch] $IncludeOnlyNavigationProperties - ) - - if($null -eq $entityType) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetAllProperties") } - - $requestedProperties = @() - - # Populate EntityType property from current EntityType as well - # as from the corresponding base types recursively if - # $IncludeOnlyNavigationProperties switch parameter is used then follow - # the same routine for navigation properties. - $currentEntityType = $entityType - while($null -ne $currentEntityType) - { - if($IncludeOnlyNavigationProperties.IsPresent) - { - $chosenProperties = $currentEntityType.NavigationProperties - } - else - { - $chosenProperties = $currentEntityType.EntityProperties - } - - $requestedProperties += $chosenProperties - $currentEntityType = $currentEntityType.BaseType - } - - return $requestedProperties -} - -######################################################### -# SplitNamespaceAndName is a helper function used -# to split Namespace and actual Name. -# e.g. "a.b.c" is namespace "a.b" and name "c" -######################################################### -function SplitNamespaceAndName -{ - param - ( - [string] $fullyQualifiedName - ) - - if($null -eq $fullyQualifiedName) { throw ($LocalizedData.ArguementNullError -f "FUllyQualifiedName", "SplitNamespaceAndName") } - - $sa = $fullyQualifiedName -split "(.*)\.(.*)" - - if ($sa.Length -gt 1) - { - # return Namespace - $sa[1] - - # return Name - $sa[2] - } - else - { - # return Namespace - "" - - # return Name - $sa[0] - } -} - -######################################################### -# GetEntitySetForEntityType is a helper function used -# to fetch EntitySet for a given EntityType by -# searching the inheritance hierarchy in the -# supplied metadata. -######################################################### -function GetEntitySetForEntityType -{ - param - ( - [ODataUtils.Metadata] $metaData, - [ODataUtils.EntityType] $entityType - ) - - # $metaData is already validated at the cmdlet layer. - if($null -eq $entityType) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetEntitySetForEntityType") } - - $result = $metaData.EntitySets | Where-Object { ($_.Type.Namespace -eq $entityType.Namespace) -and ($_.Type.Name -eq $entityType.Name) } - - if (($result.Count -eq 0) -and ($null -ne $entityType.BaseType)) - { - GetEntitySetForEntityType $metaData $entityType.BaseType - } - elseif ($result.Count -gt 1) - { - throw ($LocalizedData.WrongCountEntitySet -f (($entityType.Namespace + "." + $entityType.Name), $result.Count)) - } - - $result -} - -######################################################### -# ProcessStreamHelper is a helper function that performs -# the following utility tasks: -# 1. Writes verbose messages to the stream. -# 2. Writes FileInfo objects for the proxy modules -# saved to the disk. This is done to keep the user -# experience in consistent with Export-PSSession. -# 3. Updates progress bar. -######################################################### -function ProcessStreamHelper -{ - param - ( - [string] $verboseMessage, - [string] $progressBarActivityName, - [string] $status, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight, - [int] $totalNumberofEntries, - [int] $currentEntryCount, - [string] $path, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - Write-Verbose -Message $verboseMessage - ProgressBarHelper $progressBarActivityName $status $previousSegmentWeight $currentSegmentWeight $totalNumberofEntries $currentEntryCount - $proxyFile = New-Object -TypeName System.IO.FileInfo -ArgumentList $path | Get-Item - if($null -ne $callerPSCmdlet) - { - $callerPSCmdlet.WriteObject($proxyFile) - } -} - -######################################################### -# GetAssociatedType is a helper function used -# to fetch associated instance's EntityType -# for a given Navigation property in the -# supplied metadata. -######################################################### -function GetAssociatedType -{ - param - ( - [ODataUtils.Metadata] $Metadata, - [ODataUtils.NavigationProperty] $navProperty - ) - - # $metaData is already validated at the cmdlet layer. - if($null -eq $navProperty) { throw ($LocalizedData.ArguementNullError -f "NavigationProperty", "GetAssociatedType") } - - $associationName = $navProperty.AssociationName - $association = $Metadata.Associations | Where-Object { $_.Name -eq $associationName } - $associationType = $association.Type - - if ($associationType.Count -lt 1) - { - throw ($LocalizedData.AssociationNotFound -f $associationName) - } - elseif ($associationType.Count -gt 1) - { - throw ($LocalizedData.TooManyMatchingAssociationTypes -f $associationType.Count, $associationName) - } - - if ($associationType.NavPropertyName1 -eq $navProperty.ToRole) - { - $associatedType = $associationType.EndType1 - } - elseif ($associationType.NavPropertyName2 -eq $navProperty.ToRole) - { - $associatedType = $associationType.EndType2 - } - else - { - throw ($LocalizedData.ZeroMatchingAssociationTypes -f $navProperty.ToRole, $association.Name) - } - - # return associated EntityType - $associatedType -} - -######################################################### -# AddParametersNode is a helper function used -# to add parameters to the generated proxy cmdlet, -# based on mandatoryProperties and otherProperties. -# PrefixForKeys is used by associations to append a -# prefix to PowerShell parameter name. -######################################################### -function AddParametersNode -{ - param - ( - [Parameter(Mandatory=$true)] - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.TypeProperty[]] $keyProperties, - [ODataUtils.TypeProperty[]] $mandatoryProperties, - [ODataUtils.TypeProperty[]] $otherProperties, - [string] $prefixForKeys, - [boolean] $addForceParameter, - [boolean] $addParametersElement, - [Hashtable] $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "AddParametersNode") } - - if(($keyProperties.Length -gt 0) -or - ($mandatoryProperties.Length -gt 0) -or - ($otherProperties.Length -gt 0) -or - ($addForceParameter)) - { - if($addParametersElement) - { - $xmlWriter.WriteStartElement('Parameters') - } - - $pos = 0 - - if ($null -ne $keyProperties) - { - $pos = AddParametersCDXML $xmlWriter $keyProperties $pos $true $prefixForKeys ":Key" $complexTypeMapping - } - - if ($null -ne $mandatoryProperties) - { - $pos = AddParametersCDXML $xmlWriter $mandatoryProperties $pos $true $null $null $complexTypeMapping - } - - if ($null -ne $otherProperties) - { - $pos = AddParametersCDXML $xmlWriter $otherProperties $pos $false $null $null $complexTypeMapping - } - - if($addForceParameter) - { - $forceParameter = [ODataUtils.TypeProperty] @{ - "Name" = "Force"; - "TypeName" = "switch"; - "IsNullable" = $false - } - - $pos = AddParametersCDXML $xmlWriter $forceParameter $pos $false $null $null $complexTypeMapping - } - - if($addParametersElement) - { - $xmlWriter.WriteEndElement() - } - } -} - -######################################################### -# AddParametersNode is a helper function used -# to add Parameter node to CDXML based on properties. -# Prefix is appended to PS parameter names, used for -# associations. Suffix is appended to all parameter -# names, for ex. to differentiate keys. returns new $pos -######################################################### -function AddParametersCDXML -{ - param - ( - [Parameter(Mandatory=$true)] - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.TypeProperty[]] $properties, - [Parameter(Mandatory=$true)] - [int] $pos, - [bool] $isMandatory, - [string] $prefix, - [string] $suffix, - [Hashtable] $complexTypeMapping - ) - - $properties | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $_.Name + $suffix) - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $prefix + $_.Name) - $xmlWriter.WriteAttributeString('IsMandatory', ($isMandatory).ToString().ToLowerInvariant()) - $xmlWriter.WriteAttributeString('Position', $pos) - if($isMandatory) - { - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $pos++ - } - - $pos -} - -######################################################### -# GenerateComplexTypeDefinition is a helper function used -# to generate complex type definition from the metadata. -######################################################### -function GenerateComplexTypeDefinition -{ - param - ( - [ODataUtils.Metadata] $metaData, - [string] $metaDataUri, - [string] $OutputModule, - [string] $typeDefinitionFileName, - [string] $cmdletAdapter, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - #metadataUri, $OutputModule & $cmdletAdapter are already validated at the cmdlet layer. - if($null -eq $typeDefinationFileName) { throw ($LocalizedData.ArguementNullError -f "TypeDefinationFileName", "GenerateComplexTypeDefination") } - if($null -eq $metaData) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateComplexTypeDefination") } - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GenerateComplexTypeDefination") } - - $Path = "$OutputModule\$typeDefinitionFileName" - - # We are currently generating classes for EntityType & ComplexType - # definition exposed in the metadata. - $typesToBeGenerated = $metaData.EntityTypes+$metadata.ComplexTypes - - if($null -ne $typesToBeGenerated -and $typesToBeGenerated.Count -gt 0) - { - $complexTypeMapping = @{} - $entityTypeNameSpaceMapping = @{} - - foreach ($entityType in $typesToBeGenerated) - { - if ($null -ne $entityType) - { - $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name - if(!$complexTypeMapping.ContainsKey($entityTypeFullName)) - { - $complexTypeMapping.Add($entityTypeFullName, $entityType.Name) - } - - if(!$entityTypeNameSpaceMapping.ContainsKey($entityType.Namespace)) - { - $entityTypes = @() - $entityTypeNameSpaceMapping.Add($entityType.Namespace, $entityTypes) - } - - $entityTypeNameSpaceMapping[$entityType.Namespace] += $entityType - } - } - - if($entityTypeNameSpaceMapping.Count -gt 0) - { -$output = @" -`$typeDefinitions = @" -using System; -using System.Management.Automation; - -"@ - - foreach($currentNameSpace in $entityTypeNameSpaceMapping.Keys) - { - $entityTypes = $entityTypeNameSpaceMapping[$currentNameSpace] - - $output += "`r`nnamespace $(ValidateComplexTypeIdentifier $currentNameSpace $true $metaDataUri $callerPSCmdlet)`r`n{" - - foreach ($entityType in $entityTypes) - { - $entityTypeFullName = (ValidateComplexTypeIdentifier $entityType.Namespace $true $metaDataUri $callerPSCmdlet) + '.' + $entityType.Name - Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $entityTypeFullName, "$OutputModule\$typeDefinationFileName") - - if($null -ne $entityType.BaseType) - { - $entityBaseFullName = (ValidateComplexTypeIdentifier $entityType.BaseType.Namespace $true $metaDataUri $callerPSCmdlet) + '.' + (ValidateComplexTypeIdentifier $entityType.BaseType.Name $false $metaDataUri $callerPSCmdlet) - $output += "`r`n public class $(ValidateComplexTypeIdentifier $entityType.Name $false $metaDataUri $callerPSCmdlet) : $($entityBaseFullName)`r`n {" - } - else - { - $output += "`r`n public class $(ValidateComplexTypeIdentifier $entityType.Name $false $metaDataUri $callerPSCmdlet)`r`n {" - } - - $properties = $null - - for($index = 0; $index -lt $entityType.EntityProperties.Count; $index++) - { - $property = $entityType.EntityProperties[$index] - $typeName = Convert-ODataTypeToCLRType $property.TypeName $complexTypeMapping - $properties += "`r`n public $typeName $(ValidateComplexTypeIdentifier $property.Name $false $metaDataUri $callerPSCmdlet);" - } - - # Navigation properties are treated like any other property for NetworkController scenario. - if ($cmdletAdapter -eq "NetworkControllerAdapter") - { - for($index = 0; $index -lt $entityType.NavigationProperties.Count; $index++) - { - $property = $entityType.NavigationProperties[$index] - $navigationTypeName = GetNavigationPropertyTypeName $property $metaData - $typeName = Convert-ODataTypeToCLRType $navigationTypeName $complexTypeMapping - $properties += "`r`n public $typeName $(ValidateComplexTypeIdentifier $property.Name $false $metaDataUri $callerPSCmdlet);" - } - } - - $output += $properties - $output += "`r`n }`r`n" - } - - $output += "}`r`n" - } - $output += """@`r`n" - - $output += "Add-Type -TypeDefinition `$typeDefinitions `r`n" - $output | Out-File -FilePath $Path - Write-Verbose ($LocalizedData.VerboseSavedTypeDefinationModule -f $typeDefinationFileName, $OutputModule) - } - } - - return $complexTypeMapping -} - -# Creating a single instance of CSharpCodeProvider that would be used -# for Identifier validation in the ValidateComplexTypeIdentifier helper method. -$cSharpCodeProvider = [Microsoft.CSharp.CSharpCodeProvider]::new() - -######################################################### -# ValidateComplexTypeIdentifier is a helper function to -# make sure that the type names defined in the -# metadata are valid C# Identifier names. This validation -# is performed to make sure that there are no security -# threat from importing the generated complex type -# (which is created using the metadata file). -# This method return the identifier name if its a -# valid identifier, else a terminating error in thrown. -######################################################### -function ValidateComplexTypeIdentifier -{ - param - ( - [string] $identifierName, - [bool] $isNameSpaceName, - [string] $metaDataUri, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - if($null -eq $callerPSCmdletl) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "ValidateComplexTypeIdentifier") } - - if($isNameSpaceName) - { - $independentIdentifiers = $identifierName.Split('.') - $result = $true - foreach($currentIdentifier in $independentIdentifiers) - { - if(![System.CodeDom.Compiler.CodeGenerator]::IsValidLanguageIndependentIdentifier($currentIdentifier)) - { - $result = $false - break - } - } - } - else - { - $result = $cSharpCodeProvider.IsValidIdentifier($identifierName) - } - - if(!$result) - { - $errorMessage = ($LocalizedData.InValidIdentifierInMetadata -f $metaDataUri, $identifierName) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidIdentifier" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidData) $null $identifierName - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - else - { - return $identifierName - } -} - -######################################################### -# GetKeys is a helper function used to -# return the keys for the entity if customUri -# is specified. -######################################################### -function GetKeys -{ - param - ( - [ODataUtils.EntitySet] $entitySet, - [string] $customUri, - [string] $actionName - ) - - # Get the original keys - $key = (GetAllProperties $entitySet.Type) | Where-Object { $_.IsKey } - - # Get the keys with delimiters - $keys = $customUri -split "/" | ForEach-Object { - if ($_ -match '{*}') - { - [ODataUtils.TypeProperty] @{ - "Name" = $_.Substring($_.IndexOf('{')+1,$_.IndexOf('}')-$_.IndexOf('{')-1); - "TypeName" = "Edm.String"; - "IsNullable" = $false; - "IsMandatory" = $true; - } - } - elseif ($_ -match '\[*\]') - { - if ($ActionName -eq 'Get') { - [ODataUtils.TypeProperty] @{ - "Name" = $_.Substring($_.IndexOf('[')+1,$_.IndexOf(']')-$_.IndexOf('[')-1); - "TypeName" = "Edm.String"; - "IsNullable" = $false; - "IsMandatory" = $false; - } - } - else { - [ODataUtils.TypeProperty] @{ - "Name" = $_.Substring($_.IndexOf('[')+1,$_.IndexOf(']')-$_.IndexOf('[')-1); - "TypeName" = "Edm.String"; - "IsNullable" = $false; - "IsMandatory" = $true; - } - } - } - } - - # Now combine the two keys and avoid duplication - # Make a list of names already present in the new keys - # Foreach old key check if that key is present in the new keyList - # Else add the key to new key list - $keyParams = $keys | ForEach-Object {$_.Name} - - if ($null -eq $keyParams -Or $keyParams.Count -eq 0) { - $keys = $key - } - else { - if ($keyParams.Count -eq 1) { - $keys = @($keys) - } - - $key | ForEach-Object { - if ($keyParams.Contains($_.Name) -eq $false) - { - $keys += $_ - } - } - } - - $keys -} - -######################################################### -# GetNetworkControllerAdditionalProperties is a helper -# function used to fetch network controller specific -# additional properties. -######################################################### -function GetNetworkControllerAdditionalProperties -{ - param - ( - $navigationProperties, - $metaData - ) - - # Additional properties contains the types present as navigation properties - - $additionalProperties = $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - $typeName = GetNavigationPropertyTypeName $_ $metaData - - if ($_.Name -eq "Properties") { - $isNullable = $false - } - else { - $isNullable = $true - } - - [ODataUtils.TypeProperty] @{ - "Name" = $_.Name; - "TypeName" = $typeName - "IsNullable" = $isNullable; - } - } - - # Add etag to the additionalProperties - - if ($null -ne $additionalProperties) - { - if ($additionalProperties.Count -eq 1) { - $additionalProperties = @($additionalProperties) - } - - $additionalProperties += [ODataUtils.TypeProperty] @{ - "Name" = "Etag"; - "TypeName" = "Edm.String"; - "IsNullable" = $true; - } - } - else - { - $additionalProperties = [ODataUtils.TypeProperty] @{ - "Name" = "Etag"; - "TypeName" = "Edm.String"; - "IsNullable" = $true; - } - } - - $additionalProperties -} - -######################################################### -# UpdateNetworkControllerSpecificProperties is a -# helper function used to append additionalProperties -# to nullable/nonNullable Properties. This is network controller -# specific logic. -######################################################### -function UpdateNetworkControllerSpecificProperties -{ - param - ( - $nullableProperties, - $additionalProperties, - $keyProperties, - $isNullable - ) - - if ($isNullable) { - $additionalProperties = $additionalProperties | Where-Object { $_.isNullable } - } - else { - $additionalProperties = $additionalProperties | Where-Object { -not $_.isNullable } - } - - if ($null -eq $nullableProperties) - { - $nullableProperties = $additionalProperties - } - else { - if ($nullableProperties.Count -eq 1) { - $nullableProperties = @($nullableProperties) - } - if ($null -ne $additionalProperties) { - $nullableProperties += $additionalProperties - } - } - - if ($null -ne $nullableProperties -And $null -ne $keyProperties) - { - if ($keyProperties.Count -eq 1) { - $keyProperties = @($keyProperties) - } - - $keys = $keyProperties | ForEach-Object {$_.Name} - - if ($keys.Count -eq 1) { - $keys = @($keys) - } - - $nullableProperties = $nullableProperties | Where-Object {$keys.Contains($_.Name) -eq $false} - } - - $nullableProperties -} - -######################################################### -# GetNavigationPropertyTypeName is a -# helper function used to fetch the type corresponding -# to navigation property in this metadata. This is -# network controller specific logic. -######################################################### -function GetNavigationPropertyTypeName -{ - param - ( - $navigationProperty, - $metaData - ) - - foreach($association in $metaData.Associations) - { - if ($association.Name -ne $navigationProperty.AssociationName -Or $association.Namespace -ne $navigationProperty.AssociationNamespace) - { - continue - } - - # Now get the type for this association - - if ($association.Type.NavPropertyName1 -eq $navigationProperty.Name) - { - $type = $association.Type.EndType1 - $multiplicity = $association.Type.Multiplicity1 - } - elseif ($associationType.NavPropertyName2 -eq $navigationProperty.Name) - { - $type = $association.Type.EndType2 - $multiplicity = $association.Type.Multiplicity2 - } - - break - } - - $fullName = $type.Namespace + '.' + $type.Name - - # Check the multiplicity and convert to array if needed - if ($multiplicity -eq "*") - { - $fullName = "Collection($fullName)" - } - - $fullName -} diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 deleted file mode 100644 index 4f1b1e47012..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psd1 +++ /dev/null @@ -1,240 +0,0 @@ -# -# Module manifest for module 'Microsoft.PowerShell.ODataUtils' -# -# Generated on: 8/15/2013 -# - -@{ - -# Script module or binary module file associated with this manifest. -RootModule = 'Microsoft.PowerShell.ODataUtils.psm1' - -# Version number of this module. -ModuleVersion = '1.0' - -# ID used to uniquely identify this module -GUID = 'fa1606d1-94cb-4264-bfb6-def714420084' - -# Author of this module -Author = 'Microsoft Corporation' - -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' - -# Copyright statement for this module -Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.' - -# Description of the functionality provided by this module -# Description = '' - -# Minimum version of the Windows PowerShell engine required by this module -# PowerShellVersion = '' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module -FunctionsToExport = @('Export-ODataEndpointProxy') - -# Cmdlets to export from this module -CmdletsToExport = '' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -HelpInfoURI = 'https://go.microsoft.com/fwlink/?LinkId=509916' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - -} - - -# SIG # Begin signature block -# MIIavwYJKoZIhvcNAQcCoIIasDCCGqwCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB -# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR -# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU5qxxE9NAWfUb5Y4oxi1pHiiU -# j+GgghWCMIIEwzCCA6ugAwIBAgITMwAAAHQNgGQOfWd9owAAAAAAdDANBgkqhkiG -# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G -# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw -# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTUwMzIwMTczMjA1 -# WhcNMTYwNjIwMTczMjA1WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp -# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw -# b3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNO -# OjdEMkUtMzc4Mi1CMEY3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT -# ZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4NFrifjVvo5Y -# gN/jD+4M6zszXn3GnmZHP9AerBSCDRiftpwnIvG2hpREQXSJkW8X9t+Y5jbLX3iS -# 6XJ+S7kExWIUc3HGf2NBW+tk8r1cVWJGzA9ewQnEr9nxvyV94BegUO4lqkXl48Z+ -# vxBZqcGPPtn77GQbY1u1p7jq681X6xtD9WWRv1D1+cEGvH2qzDfnBqmgzLH1M8wN -# ssh1ZgDRbTCTR8+OomdEXhoTf/McHucPncG8SPyBgW1UauJpE8bO9ZdnMmxIyhHC -# VjrW3Dpi9PwQl2RIC4pc8RbClfDLYBukA5sMyfe7kr8Ac2czHKJ673VKGUZaDH6a -# W6A6HVQ16wIDAQABo4IBCTCCAQUwHQYDVR0OBBYEFCUsOGYFtEU5DmC29u69PuDd -# r4wNMB8GA1UdIwQYMBaAFCM0+NlSRnAK7UD7dvuzK7DDNbMPMFQGA1UdHwRNMEsw -# SaBHoEWGQ2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3Rz -# L01pY3Jvc29mdFRpbWVTdGFtcFBDQS5jcmwwWAYIKwYBBQUHAQEETDBKMEgGCCsG -# AQUFBzAChjxodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jv -# c29mdFRpbWVTdGFtcFBDQS5jcnQwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI -# hvcNAQEFBQADggEBAEEG50j6xJHcMBMNInjC0iPTszPL+yYh1978CncY+4Nyzu/U -# LIaP4xXj1RICZ1xbN9MDe02RW0FTZgn9457fLHgJORo2HYqBocllfJx7kbIPSptB -# 3cdEC2EFyUwu8rRrKKoIR+4IrGZUF1aQiMbpddAhEDh5yT+7VTDFpjmmU7/NXFbS -# ThcUvGISy+lL8MWR3J2EypjWDttWFGht21OLMM+6J2V1oDFvk6N1EGDqqu7uduvl -# jAup0655zzS+SR8i0MT1o+/zrjDcjohGI4ygqjyXrwfbdug2VN+Ls4mewOospGBr -# 8d/DthI6rzM4elFxNTXm5AjiUZaC+b7hG4N8e2cwggTsMIID1KADAgECAhMzAAAA -# ymzVMhI1xOFVAAEAAADKMA0GCSqGSIb3DQEBBQUAMHkxCzAJBgNVBAYTAlVTMRMw -# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN -# aWNyb3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBDb2RlIFNp -# Z25pbmcgUENBMB4XDTE0MDQyMjE3MzkwMFoXDTE1MDcyMjE3MzkwMFowgYMxCzAJ -# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k -# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIx -# HjAcBgNVBAMTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEB -# BQADggEPADCCAQoCggEBAJZxXe0GRvqEy51bt0bHsOG0ETkDrbEVc2Cc66e2bho8 -# P/9l4zTxpqUhXlaZbFjkkqEKXMLT3FIvDGWaIGFAUzGcbI8hfbr5/hNQUmCVOlu5 -# WKV0YUGplOCtJk5MoZdwSSdefGfKTx5xhEa8HUu24g/FxifJB+Z6CqUXABlMcEU4 -# LYG0UKrFZ9H6ebzFzKFym/QlNJj4VN8SOTgSL6RrpZp+x2LR3M/tPTT4ud81MLrs -# eTKp4amsVU1Mf0xWwxMLdvEH+cxHrPuI1VKlHij6PS3Pz4SYhnFlEc+FyQlEhuFv -# 57H8rEBEpamLIz+CSZ3VlllQE1kYc/9DDK0r1H8wQGcCAwEAAaOCAWAwggFcMBMG -# A1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBQfXuJdUI1Whr5KPM8E6KeHtcu/ -# gzBRBgNVHREESjBIpEYwRDENMAsGA1UECxMETU9QUjEzMDEGA1UEBRMqMzE1OTUr -# YjQyMThmMTMtNmZjYS00OTBmLTljNDctM2ZjNTU3ZGZjNDQwMB8GA1UdIwQYMBaA -# FMsR6MrStBZYAck3LjMWFrlMmgofMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j -# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY0NvZFNpZ1BDQV8w -# OC0zMS0yMDEwLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 -# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljQ29kU2lnUENBXzA4LTMx -# LTIwMTAuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQB3XOvXkT3NvXuD2YWpsEOdc3wX -# yQ/tNtvHtSwbXvtUBTqDcUCBCaK3cSZe1n22bDvJql9dAxgqHSd+B+nFZR+1zw23 -# VMcoOFqI53vBGbZWMrrizMuT269uD11E9dSw7xvVTsGvDu8gm/Lh/idd6MX/YfYZ -# 0igKIp3fzXCCnhhy2CPMeixD7v/qwODmHaqelzMAUm8HuNOIbN6kBjWnwlOGZRF3 -# CY81WbnYhqgA/vgxfSz0jAWdwMHVd3Js6U1ZJoPxwrKIV5M1AHxQK7xZ/P4cKTiC -# 095Sl0UpGE6WW526Xxuj8SdQ6geV6G00DThX3DcoNZU6OJzU7WqFXQ4iEV57MIIF -# vDCCA6SgAwIBAgIKYTMmGgAAAAAAMTANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZIm -# iZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQD -# EyRNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTAwODMx -# MjIxOTMyWhcNMjAwODMxMjIyOTMyWjB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMSMwIQYDVQQDExpNaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBD -# QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJyWVwZMGS/HZpgICBC -# mXZTbD4b1m/My/Hqa/6XFhDg3zp0gxq3L6Ay7P/ewkJOI9VyANs1VwqJyq4gSfTw -# aKxNS42lvXlLcZtHB9r9Jd+ddYjPqnNEf9eB2/O98jakyVxF3K+tPeAoaJcap6Vy -# c1bxF5Tk/TWUcqDWdl8ed0WDhTgW0HNbBbpnUo2lsmkv2hkL/pJ0KeJ2L1TdFDBZ -# +NKNYv3LyV9GMVC5JxPkQDDPcikQKCLHN049oDI9kM2hOAaFXE5WgigqBTK3S9dP -# Y+fSLWLxRT3nrAgA9kahntFbjCZT6HqqSvJGzzc8OJ60d1ylF56NyxGPVjzBrAlf -# A9MCAwEAAaOCAV4wggFaMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMsR6MrS -# tBZYAck3LjMWFrlMmgofMAsGA1UdDwQEAwIBhjASBgkrBgEEAYI3FQEEBQIDAQAB -# MCMGCSsGAQQBgjcVAgQWBBT90TFO0yaKleGYYDuoMW+mPLzYLTAZBgkrBgEEAYI3 -# FAIEDB4KAFMAdQBiAEMAQTAfBgNVHSMEGDAWgBQOrIJgQFYnl+UlE/wq4QpTlVnk -# pDBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp -# L2NybC9wcm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEE -# SDBGMEQGCCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl -# cnRzL01pY3Jvc29mdFJvb3RDZXJ0LmNydDANBgkqhkiG9w0BAQUFAAOCAgEAWTk+ -# fyZGr+tvQLEytWrrDi9uqEn361917Uw7LddDrQv+y+ktMaMjzHxQmIAhXaw9L0y6 -# oqhWnONwu7i0+Hm1SXL3PupBf8rhDBdpy6WcIC36C1DEVs0t40rSvHDnqA2iA6VW -# 4LiKS1fylUKc8fPv7uOGHzQ8uFaa8FMjhSqkghyT4pQHHfLiTviMocroE6WRTsgb -# 0o9ylSpxbZsa+BzwU9ZnzCL/XB3Nooy9J7J5Y1ZEolHN+emjWFbdmwJFRC9f9Nqu -# 1IIybvyklRPk62nnqaIsvsgrEA5ljpnb9aL6EiYJZTiU8XofSrvR4Vbo0HiWGFzJ -# NRZf3ZMdSY4tvq00RBzuEBUaAF3dNVshzpjHCe6FDoxPbQ4TTj18KUicctHzbMrB -# 7HCjV5JXfZSNoBtIA1r3z6NnCnSlNu0tLxfI5nI3EvRvsTxngvlSso0zFmUeDord -# EN5k9G/ORtTTF+l5xAS00/ss3x+KnqwK+xMnQK3k+eGpf0a7B2BHZWBATrBC7E7t -# s3Z52Ao0CW0cgDEf4g5U3eWh++VHEK1kmP9QFi58vwUheuKVQSdpw5OPlcmN2Jsh -# rg1cnPCiroZogwxqLbt2awAdlq3yFnv2FoMkuYjPaqhHMS+a3ONxPdcAfmJH0c6I -# ybgY+g5yjcGjPa8CQGr/aZuW4hCoELQ3UAjWwz0wggYHMIID76ADAgECAgphFmg0 -# AAAAAAAcMA0GCSqGSIb3DQEBBQUAMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAX -# BgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290 -# IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wNzA0MDMxMjUzMDlaFw0yMTA0MDMx -# MzAzMDlaMHcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD -# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAf -# BgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQTCCASIwDQYJKoZIhvcNAQEB -# BQADggEPADCCAQoCggEBAJ+hbLHf20iSKnxrLhnhveLjxZlRI1Ctzt0YTiQP7tGn -# 0UytdDAgEesH1VSVFUmUG0KSrphcMCbaAGvoe73siQcP9w4EmPCJzB/LMySHnfL0 -# Zxws/HvniB3q506jocEjU8qN+kXPCdBer9CwQgSi+aZsk2fXKNxGU7CG0OUoRi4n -# rIZPVVIM5AMs+2qQkDBuh/NZMJ36ftaXs+ghl3740hPzCLdTbVK0RZCfSABKR2YR -# JylmqJfk0waBSqL5hKcRRxQJgp+E7VV4/gGaHVAIhQAQMEbtt94jRrvELVSfrx54 -# QTF3zJvfO4OToWECtR0Nsfz3m7IBziJLVP/5BcPCIAsCAwEAAaOCAaswggGnMA8G -# A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCM0+NlSRnAK7UD7dvuzK7DDNbMPMAsG -# A1UdDwQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADCBmAYDVR0jBIGQMIGNgBQOrIJg -# QFYnl+UlE/wq4QpTlVnkpKFjpGEwXzETMBEGCgmSJomT8ixkARkWA2NvbTEZMBcG -# CgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMkTWljcm9zb2Z0IFJvb3Qg -# Q2VydGlmaWNhdGUgQXV0aG9yaXR5ghB5rRahSqClrUxzWPQHEy5lMFAGA1UdHwRJ -# MEcwRaBDoEGGP2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1 -# Y3RzL21pY3Jvc29mdHJvb3RjZXJ0LmNybDBUBggrBgEFBQcBAQRIMEYwRAYIKwYB -# BQUHMAKGOGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z -# b2Z0Um9vdENlcnQuY3J0MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEB -# BQUAA4ICAQAQl4rDXANENt3ptK132855UU0BsS50cVttDBOrzr57j7gu1BKijG1i -# uFcCy04gE1CZ3XpA4le7r1iaHOEdAYasu3jyi9DsOwHu4r6PCgXIjUji8FMV3U+r -# kuTnjWrVgMHmlPIGL4UD6ZEqJCJw+/b85HiZLg33B+JwvBhOnY5rCnKVuKE5nGct -# xVEO6mJcPxaYiyA/4gcaMvnMMUp2MT0rcgvI6nA9/4UKE9/CCmGO8Ne4F+tOi3/F -# NSteo7/rvH0LQnvUU3Ih7jDKu3hlXFsBFwoUDtLaFJj1PLlmWLMtL+f5hYbMUVbo -# nXCUbKw5TNT2eb+qGHpiKe+imyk0BncaYsk9Hm0fgvALxyy7z0Oz5fnsfbXjpKh0 -# NbhOxXEjEiZ2CzxSjHFaRkMUvLOzsE1nyJ9C/4B5IYCeFTBm6EISXhrIniIh0EPp -# K+m79EjMLNTYMoBMJipIJF9a6lbvpt6Znco6b72BJ3QGEe52Ib+bgsEnVLaxaj2J -# oXZhtG6hE6a/qkfwEm/9ijJssv7fUciMI8lmvZ0dhxJkAj0tr1mPuOQh5bWwymO0 -# eFQF1EEuUKyUsKV4q7OglnUa2ZKHE3UiLzKoCG6gW4wlv6DvhMoh1useT8ma7kng -# 9wFlb4kLfchpyOZu6qeXzjEp/w7FW1zYTRuh2Povnj8uVRZryROj/TGCBKcwggSj -# AgEBMIGQMHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD -# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xIzAh -# BgNVBAMTGk1pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBAhMzAAAAymzVMhI1xOFV -# AAEAAADKMAkGBSsOAwIaBQCggcAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFFNt -# DhScbWHVDFMkTLRd2qRP8wFeMGAGCisGAQQBgjcCAQwxUjBQoCaAJABXAGkAbgBk -# AG8AdwBzACAAUABvAHcAZQByAFMAaABlAGwAbKEmgCRodHRwOi8vd3d3Lm1pY3Jv -# c29mdC5jb20vcG93ZXJzaGVsbCAwDQYJKoZIhvcNAQEBBQAEggEAddyT//E7Ysbi -# U9kfhQrYkjrhhZCKOzQPVAZSNxWx246MveRe1aj3A+Kr868dYH3x8or8g7MpeJig -# 0WOx4O+mw4fUCdTT6fLqo+W8Q+5qNpWjpfpP5eq7firhhh5D8jB1h7tJWI7fkvHN -# VwadYG4t4BxGIFgsn6YIgPn8ZipmOLb8zvCaDPpg9Xr5U5YKKUrA3sgiuW+zf0aK -# r506K+pfuC56XItbX25VEvf+hjazJr2UasFTweV4mCgKHoAG1UluKUZaX8B+KaKB -# DGMUJ3pCAqyt9RCTSQC9xZxWyK+g0byzn2dpCNxWDXHI7SxCs8ejBhp5yYtlBXl7 -# vkGAXdlJ9aGCAigwggIkBgkqhkiG9w0BCQYxggIVMIICEQIBATCBjjB3MQswCQYD -# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe -# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEwHwYDVQQDExhNaWNyb3Nv -# ZnQgVGltZS1TdGFtcCBQQ0ECEzMAAAB0DYBkDn1nfaMAAAAAAHQwCQYFKw4DAhoF -# AKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1 -# MDUxMTE4MTE1M1owIwYJKoZIhvcNAQkEMRYEFJl30ao3ese0x5O4lPs3SVZVIOdq -# MA0GCSqGSIb3DQEBBQUABIIBAESrx9oY0vI7nyBV0AwzpSYTA/jQF/+MGVokP7ak -# 5i7x25UMc7+RRMROW9VxhzYpzPtotmF8H4rfZsJLxPRlbFF2pu+8MKiNAKiP851m -# tsD1Cw8AN7T31LG8Syk3yKtEvsvnc3yzZy6sXUbkn02yjHNp0PMsrQJNw9ALRc/p -# s3mHzZTqYkmFeHUHzsRa97ByExmjPnP4vcfK2HdZ+oq2EiLjGICooqimt2ys/BPy -# 7nYZaeHaKaNJtnOQHM2BqN38OcH7X7K4IzxCNceXEION6gZE6wqvp+dkvpN5wasL -# OkQhubomAuN/S2TGKwjx3H45G1dpl3LXqihqtqF/Sed7MZs= -# SIG # End signature block diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 deleted file mode 100644 index 9d5e5254726..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtils.psm1 +++ /dev/null @@ -1,231 +0,0 @@ -Import-LocalizedData LocalizedData -FileName Microsoft.PowerShell.ODataUtilsStrings.psd1 - - -# This module doesn't support Arm because of Add-Type cmdlet -$ProcessorArchitecture = (Get-WmiObject -query "Select Architecture from Win32_Processor").Architecture - -# 0 = x86 -# 1 = MIPS -# 2 = Alpha -# 3 = PowerPC -# 5 = ARM -# 6 = Itanium -# 9 = x64 -if ($ProcessorArchitecture -eq 5) -{ - throw $LocalizedData.ArchitectureNotSupported -f "ARM" -} - -. "$PSScriptRoot\Microsoft.PowerShell.ODataUtilsHelper.ps1" - -######################################################### -# Generates PowerShell module containing client side -# proxy cmdlets that can be used to interact with an -# OData based server side endpoint. -######################################################### -function Export-ODataEndpointProxy -{ - [CmdletBinding( - DefaultParameterSetName='CDXML', - SupportsShouldProcess=$true, - HelpUri="https://go.microsoft.com/fwlink/?LinkId=510069")] - [OutputType([System.IO.FileInfo])] - param - ( - [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [string] $Uri, - - [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [string] $OutputModule, - - [Parameter(Position=2, ValueFromPipelineByPropertyName=$true)] - [ValidateNotNullOrEmpty()] - [string] $MetadataUri, - - [Parameter(Position=3, ValueFromPipelineByPropertyName=$true)] - [PSCredential] $Credential, - - [Parameter(Position=4, ValueFromPipelineByPropertyName=$true)] - [ValidateSet('Put', 'Post', 'Patch')] - [string] $CreateRequestMethod='Post', - - [Parameter(Position=5, ValueFromPipelineByPropertyName=$true)] - [ValidateSet('Put', 'Post', 'Patch')] - [string] $UpdateRequestMethod='Patch', - - [Parameter(Position=6, ValueFromPipelineByPropertyName=$true)] - [ValidateSet('ODataAdapter', 'NetworkControllerAdapter', 'ODataV4Adapter')] - [string] $CmdletAdapter='ODataAdapter', - - [Parameter(Position=7, ValueFromPipelineByPropertyName=$true)] - [Hashtable] $ResourceNameMapping, - - [parameter (Position=8,ValueFromPipelineByPropertyName=$true)] - [switch] $Force, - - [Parameter(Position=9, ValueFromPipelineByPropertyName=$true)] - [Hashtable] $CustomData, - - [parameter (Position=10,ValueFromPipelineByPropertyName=$true)] - [switch] $AllowClobber, - - [parameter (Position=11,ValueFromPipelineByPropertyName=$true)] - [switch] $AllowUnsecureConnection, - - [parameter (Position=12,ValueFromPipelineByPropertyName=$true)] - [ValidateNotNull()] - [Hashtable] $Headers - ) - - BEGIN - { - if (!$MetadataUri) - { - $Uri = $Uri.TrimEnd('/') - $MetadataUri = $Uri + '/$metadata' - $PSBoundParameters["MetadataUri"] = $MetadataUri - } - - # Validate to make sure that a valid URI is supplied as input. - try - { - $connectionUri = [System.Uri]::new($Uri) - } - catch - { - $errorMessage = ($LocalizedData.InValidUri -f $Uri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidUriFormat" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $Uri - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - # Block Redfish-support for in-box version of the module and advise user to use a version from PS Gallery instead. - # According to Redfish specification DSP0266v1.0.1 Redfish Service Metadata Document and Redfish Service Root URIs (used by Export-ODataEndpointProxy) are required to start with '/redfish/v1' (section "6.3 Redfish-Defined URIs and Relative URI Rules"). - # We use this as indicator of whether Export-ODataEndpointProxy was attempted against a Redfish endpoint. - if($connectionUri.AbsolutePath.StartsWith('/redfish/',[StringComparison]::OrdinalIgnoreCase)) - { - $errorMessage = $LocalizedData.RedfishNotEnabled - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyRedfishNotEnabled" $errorMessage ([System.Management.Automation.ErrorCategory]::NotEnabled) $exception $Uri - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - if($connectionUri.Scheme -eq "http" -and !$AllowUnsecureConnection.IsPresent) - { - $errorMessage = ($LocalizedData.AllowUnsecureConnectionMessage -f $PSCmdlet.MyInvocation.MyCommand.Name, $Uri, "Uri") - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyUnSecureConnection" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $Uri - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - $OutputModuleExists = Test-Path -Path $OutputModule -PathType Container - - if($OutputModuleExists -and ($Force -eq $false)) - { - $errorMessage = ($LocalizedData.ModuleAlreadyExistsAndForceParameterIsNotSpecified -f $OutputModule) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyOutputModuleExists" $errorMessage ([System.Management.Automation.ErrorCategory]::ResourceExists) $null $OutputModule - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - $isWhatIf = $psboundparameters.ContainsKey("WhatIf") - - if(!$OutputModuleExists) - { - if(!$isWhatIf) - { - $OutputModule = (New-Item -Path $OutputModule -ItemType Directory).FullName - } - } - else - { - $resolvedOutputModulePath = Resolve-Path -Path $OutputModule -ErrorAction Stop -Verbose - if($resolvedOutputModulePath.Count -gt 1) - { - $errorMessage = ($LocalizedData.OutputModulePathIsNotUnique -f $OutputModule) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyOutputModulePathIsNotUnique" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $OutputModule - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - # Make sure that the path specified is a valid file system directory path. - if([system.IO.Directory]::Exists($resolvedOutputModulePath)) - { - $OutputModule = $resolvedOutputModulePath - } - else - { - $errorMessage = ($LocalizedData.OutputModulePathIsNotFileSystemPath -f $OutputModule) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyPathIsNotFileSystemPath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $OutputModule - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - } - - $rootDir = [System.IO.Directory]::GetDirectoryRoot($OutputModule) - - if($rootDir -eq $OutputModule) - { - $errorMessage = ($LocalizedData.InvalidOutputModulePath -f $OutputModule) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidOutputModulePath" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $OutputModule - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - if(!$isWhatIf) - { - $progressBarStatus = ($LocalizedData.ProgressBarMessage -f $Uri) - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 0 100 100 1 - } - - # Add parameters to $PSBoundParameters, which were not passed by user, but the default value is set - $parametersWithDefaultValue = @("CreateRequestMethod", "UpdateRequestMethod", "CmdletAdapter") - - foreach ($parameterWithDefaultValue in $parametersWithDefaultValue) - { - if (!$PSBoundParameters.ContainsKey($parameterWithDefaultValue)) - { - $PSBoundParameters.Add($parameterWithDefaultValue, (Get-Variable $parameterWithDefaultValue).Value) - } - } - } - - END - { - if($pscmdlet.ShouldProcess($Uri)) - { - try - { - $PSBoundParameters.Add("ProgressBarStatus", $progressBarStatus) - $PSBoundParameters.Add("PSCmdlet", $PSCmdlet) - - # Import module based on selected CmdletAdapter - $adapterToImport = $CmdletAdapter - - # NetworkControllerAdapter relies on ODataAdapter - if ($CmdletAdapter -eq 'NetworkControllerAdapter') - { - $adapterToImport = 'ODataAdapter' - } - - Write-Debug ($LocalizedData.SelectedAdapter -f $adapterPSScript) - - $adapterPSScript = "$PSScriptRoot\Microsoft.PowerShell." + $adapterToImport + ".ps1" - - . $adapterPSScript - ExportODataEndpointProxy @PSBoundParameters - } - catch - { - $errorMessage = ($LocalizedData.InValidMetadata -f $Uri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidInput" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $Uri - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - finally - { - Write-Progress -Activity "Export-ODataEndpointProxy" -Completed - } - } - } -} - -Export-ModuleMember -Function @('Export-ODataEndpointProxy') diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 deleted file mode 100644 index 473d39909e2..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataUtilsHelper.ps1 +++ /dev/null @@ -1,782 +0,0 @@ -# Base class definitions used by the actual Adapter modules -$global:BaseClassDefinitions = @" -using System; - -namespace ODataUtils -{ - public class TypeProperty - { - public String Name; - - // OData Type Name, e.g. Edm.Int32 - public String TypeName; - - public bool IsKey; - public bool IsMandatory; - public bool? IsNullable; - } - - public class NavigationProperty - { - public String Name; - - public String FromRole; - public String ToRole; - public String AssociationNamespace; - public String AssociationName; - } - - public class EntityTypeBase - { - public String Namespace; - public String Name; - public TypeProperty[] EntityProperties; - public bool IsEntity; - public EntityTypeBase BaseType; - } - - public class AssociationType - { - public String Namespace; - - public EntityTypeBase EndType1; - public String Multiplicity1; - public String NavPropertyName1; - - public EntityTypeBase EndType2; - public String Multiplicity2; - public String NavPropertyName2; - } - - public class EntitySet - { - public String Namespace; - public String Name; - public EntityTypeBase Type; - } - - public class AssociationSet - { - public String Namespace; - public String Name; - public AssociationType Type; - } - - public class Action - { - public String Namespace; - public String Verb; - public EntitySet EntitySet; - public Boolean IsSideEffecting; - public Boolean IsBindable; - public Boolean IsSingleInstance; - public TypeProperty[] Parameters; - } - - public class MetadataBase - { - // Desired destination namespace - public String Namespace; - } - - public class CmdletParameter - { - public CmdletParameter() - { - } - - public CmdletParameter(String type, String name) - { - this.Type = type; - this.Name = name; - this.Qualifiers = new String[] { "Parameter(ValueFromPipelineByPropertyName=`$true)" }; - } - - public String[] Qualifiers; - public String Type; - public String Name; - } - - public class CmdletParameters - { - public CmdletParameter[] Parameters; - } - - public class ReferentialConstraint - { - public String Property; - public String ReferencedProperty; - } - - public class OnDelete - { - public String Action; - } - - public class NavigationPropertyV4 - { - public String Name; - public String Type; - public bool Nullable; - public String Partner; - public bool ContainsTarget; - public ReferentialConstraint[] ReferentialConstraints; - public OnDelete OnDelete; - } - - public class NavigationPropertyBinding - { - public String Path; - public String Target; - } - - public class EntityTypeV4 : EntityTypeBase - { - public String Alias; - public NavigationPropertyV4[] NavigationProperties; - public String BaseTypeStr; - } - - public class SingletonType - { - public String Namespace; - public String Alias; - public String Name; - public String Type; - public NavigationPropertyBinding[] NavigationPropertyBindings; - } - - public class EntitySetV4 - { - public String Namespace; - public String Alias; - public String Name; - public EntityTypeV4 Type; - } - - public class EnumMember - { - public String Name; - public String Value; - } - - public class EnumType - { - public String Namespace; - public String Alias; - public String Name; - public String UnderlyingType; - public bool IsFlags; - public EnumMember[] Members; - } - - public class ActionV4 - { - public String Namespace; - public String Alias; - public String Name; - public String Action; - public EntitySetV4 EntitySet; - public TypeProperty[] Parameters; - } - - public class FunctionV4 - { - public String Namespace; - public String Alias; - public String Name; - public bool Function; - public String EntitySet; - public String ReturnType; - public Parameter[] Parameters; - } - - public class Parameter - { - public String Name; - public String Type; - public bool Nullable; - } - - public class ReferenceInclude - { - public String Namespace; - public String Alias; - } - - public class Reference - { - public String Uri; - } - - public class MetadataV4 : MetadataBase - { - public string ODataVersion; - public string Uri; - public string MetadataUri; - public string Alias; - public Reference[] References; - public string DefaultEntityContainerName; - public EntitySetV4[] EntitySets; - public EntityTypeV4[] EntityTypes; - public SingletonType[] SingletonTypes; - public EntityTypeV4[] ComplexTypes; - public EntityTypeV4[] TypeDefinitions; - public EnumType[] EnumTypes; - public ActionV4[] Actions; - public FunctionV4[] Functions; - } - - public class ReferencedMetadata - { - public System.Collections.ArrayList References; - } - - public class ODataEndpointProxyParameters - { - public String Uri; - public String MetadataUri; - public System.Management.Automation.PSCredential Credential; - public String OutputModule; - - public bool Force; - public bool AllowClobber; - public bool AllowUnsecureConnection; - } - - public class EntityType : EntityTypeBase - { - public NavigationProperty[] NavigationProperties; - } - - public class Metadata : MetadataBase - { - public String DefaultEntityContainerName; - public EntitySet[] EntitySets; - public EntityType[] EntityTypes; - public EntityType[] ComplexTypes; - public AssociationSet[] Associations; - public Action[] Actions; - } -} -"@ - -######################################################### -# GetMetaData is a helper function used to fetch metadata -# from the specified file or web URL. -######################################################### -function GetMetaData -{ - param - ( - [string] $metaDataUri, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet, - [PSCredential] $credential, - [Hashtable] $headers - ) - - # $metaDataUri is already validated at the cmdlet layer. - if($null -eq $callerPSCmdletl) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "GetMetaData") } - Write-Verbose ($LocalizedData.VerboseReadingMetadata -f $metaDataUri) - - try - { - $uri = [System.Uri]::new($metadataUri) - - # By default, proxy generation is supported on secured Uri (i.e., https). - # However if the user trusts the unsecure http uri, then they can override - # the security check by specifying -AllowSecureConnection parameter during - # proxy generation. - if($uri.Scheme -eq "http" -and !$AllowUnsecureConnection.IsPresent) - { - $errorMessage = ($LocalizedData.AllowUnsecureConnectionMessage -f $callerPSCmdlet.MyInvocation.MyCommand.Name, $uri, "MetaDataUri") - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyUnSecureConnection" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $uri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - catch - { - $errorMessage = ($LocalizedData.InValidMetadata -f $MetadataUri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadataUriFormat" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - if($uri.IsFile) - { - if ($null -ne $credential) - { - $fileExists = Test-Path -Path $metaDataUri -PathType Leaf -Credential $credential -ErrorAction Stop - } - else - { - $fileExists = Test-Path -Path $metaDataUri -PathType Leaf -ErrorAction Stop - } - - if($fileExists) - { - $metaData = Get-Content -Path $metaDataUri -ErrorAction Stop - } - else - { - $errorMessage = ($LocalizedData.MetadataUriDoesNotExist -f $MetadataUri) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyMetadataFileDoesNotExist" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - else - { - try - { - $cmdParams = @{'Uri'= $metaDataUri ; 'UseBasicParsing'=$true; 'ErrorAction'= 'Stop'} - - if ($null -ne $credential) - { - $cmdParams.Add('Credential', $credential) - } - - if ($null -ne $headers) - { - $cmdParams.Add('Headers', $headers) - } - - $webResponse = Invoke-WebRequest @cmdParams - } - catch - { - $errorMessage = ($LocalizedData.MetadataUriDoesNotExist -f $MetadataUri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyMetadataUriDoesNotExist" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - if($null -ne $webResponse) - { - if ($webResponse.StatusCode -eq 200) - { - $metaData = $webResponse.Content - - if ($null -eq $metadata) - { - $errorMessage = ($LocalizedData.EmptyMetadata -f $MetadataUri) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyMetadataIsEmpty" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - else - { - $errorMessage = ($LocalizedData.InvalidEndpointAddress -f $MetadataUri, $webResponse.StatusCode) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidEndpointAddress" $errorMessage ([System.Management.Automation.ErrorCategory]::InvalidArgument) $null $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - } - - if($null -ne $metaData) - { - try - { - [xml] $metadataXML = $metaData - } - catch - { - $errorMessage = ($LocalizedData.InValidMetadata -f $MetadataUri) - $exception = [System.InvalidOperationException]::new($errorMessage, $_.Exception) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetadata" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $exception $MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - } - - return $metadataXML -} - -######################################################### -# VerifyMetadataHelper is a helper function used to -# validate if Error/Warning message has to be displayed -# during command collision. -######################################################### -function VerifyMetadataHelper -{ - param - ( - [string] $localizedDataErrorString, - [string] $localizedDataWarningString, - [string] $entitySetName, - [string] $currentCommandName, - [string] $metaDataUri, - [boolean] $allowClobber, - [System.Management.Automation.PSCmdlet] $callerPSCmdlet - ) - - if($null -eq $localizedDataErrorString) { throw ($LocalizedData.ArguementNullError -f "localizedDataErrorString", "VerifyMetadataHelper") } - if($null -eq $localizedDataWarningString) { throw ($LocalizedData.ArguementNullError -f "localizedDataWarningString", "VerifyMetadataHelper") } - - if(!$allowClobber) - { - # Write Error message and skip current Entity Set. - $errorMessage = ($localizedDataErrorString -f $entitySetName, $currentCommandName) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointDefaultPropertyCollision" $null ([System.Management.Automation.ErrorCategory]::InvalidOperation) $exception $metaDataUri - $callerPSCmdlet.WriteError($errorRecord) - } - else - { - $warningMessage = ($localizedDataWarningString -f $entitySetName, $currentCommandName) - $callerPSCmdlet.WriteWarning($warningMessage) - } -} - -######################################################### -# CreateErrorRecordHelper is a helper function used to -# create an error record. -######################################################### -function CreateErrorRecordHelper -{ - param - ( - [string] $errorId, - [string] $errorMessage, - [System.Management.Automation.ErrorCategory] $errorCategory, - [Exception] $exception, - [object] $targetObject - ) - - if($null -eq $exception) - { - $exception = New-Object System.IO.IOException $errorMessage - } - - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $targetObject - return $errorRecord -} - -######################################################### -# ProgressBarHelper is a helper function used to -# used to display progress message. -######################################################### -function ProgressBarHelper -{ - param - ( - [string] $cmdletName, - [string] $status, - [double] $previousSegmentWeight, - [double] $currentSegmentWeight, - [int] $totalNumberofEntries, - [int] $currentEntryCount - ) - - if($null -eq $cmdletName) { throw ($LocalizedData.ArguementNullError -f "CmdletName", "ProgressBarHelper") } - if($null -eq $status) { throw ($LocalizedData.ArguementNullError -f "Status", "ProgressBarHelper") } - - if($currentEntryCount -gt 0 -and - $totalNumberofEntries -gt 0 -and - $previousSegmentWeight -ge 0 -and - $currentSegmentWeight -gt 0) - { - $entryDefaultWeight = $currentSegmentWeight/[double]$totalNumberofEntries - $percentComplete = $previousSegmentWeight + ($entryDefaultWeight * $currentEntryCount) - Write-Progress -Activity $cmdletName -Status $status -PercentComplete $percentComplete - } -} - -######################################################### -# Convert-ODataTypeToCLRType is a helper function used to -# Convert OData type to its CLR equivalent. -######################################################### -function Convert-ODataTypeToCLRType -{ - param - ( - [string] $typeName, - [Hashtable] $complexTypeMapping - ) - - if($null -eq $typeName) { throw ($LocalizedData.ArguementNullError -f "TypeName", "Convert-ODataTypeToCLRType ") } - - switch ($typeName) - { - "Edm.Binary" {"Byte[]"} - "Edm.Boolean" {"Boolean"} - "Edm.Byte" {"Byte"} - "Edm.DateTime" {"DateTime"} - "Edm.Decimal" {"Decimal"} - "Edm.Double" {"Double"} - "Edm.Single" {"Single"} - "Edm.Guid" {"Guid"} - "Edm.Int16" {"Int16"} - "Edm.Int32" {"Int32"} - "Edm.Int64" {"Int64"} - "Edm.SByte" {"SByte"} - "Edm.String" {"String"} - "Edm.PropertyPath" {"String"} - "switch" {"switch"} - "Edm.DateTimeOffset" {"DateTimeOffset"} - default - { - if($null -ne $complexTypeMapping -and - $complexTypeMapping.Count -gt 0 -and - $complexTypeMapping.ContainsKey($typeName)) - { - $typeName - } - else - { - $regex = "Collection\((.+)\)" - if ($typeName -match $regex) - { - $insideTypeName = Convert-ODataTypeToCLRType $Matches[1] $complexTypeMapping - "$insideTypeName[]" - } - else - { - "PSObject" - } - } - } - } -} - -######################################################### -# SaveCDXMLHeader is a helper function used -# to save CDXML headers common to all -# PSODataUtils modules. -######################################################### -function SaveCDXMLHeader -{ - param - ( - [System.Xml.XmlWriter] $xmlWriter, - [string] $uri, - [string] $className, - [string] $defaultNoun, - [string] $cmdletAdapter - ) - - # $uri & $cmdletAdapter are already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLHeader") } - if($null -eq $defaultNoun) { throw ($LocalizedData.ArguementNullError -f "DefaultNoun", "SaveCDXMLHeader") } - - if ($className -eq 'ServiceActions' -Or $cmdletAdapter -eq "NetworkControllerAdapter") - { - $entityName = '' - } - else - { - $entityName = $className - } - - if ($uri[-1] -ne '/') - { - $fullName = "$uri/$entityName" - } - else - { - $fullName = "$uri$entityName" - } - - $xmlWriter.Formatting = 'Indented' - $xmlWriter.Indentation = 2 - $xmlWriter.IndentChar = ' ' - - $xmlWriter.WriteStartDocument() - - $today=Get-Date - $xmlWriter.WriteComment("This module was autogenerated by PSODataUtils on $today.") - - $xmlWriter.WriteStartElement('PowerShellMetadata') - $xmlWriter.WriteAttributeString('xmlns', 'http://schemas.microsoft.com/cmdlets-over-objects/2009/11') - - $xmlWriter.WriteStartElement('Class') - $xmlWriter.WriteAttributeString('ClassName', $fullName) - $xmlWriter.WriteAttributeString('ClassVersion', '1.0.0') - - $DotNetAdapter = 'Microsoft.PowerShell.Cmdletization.OData.ODataCmdletAdapter' - - if ($CmdletAdapter -eq "NetworkControllerAdapter") { - $DotNetAdapter = 'Microsoft.PowerShell.Cmdletization.OData.NetworkControllerCmdletAdapter' - } - elseif ($CmdletAdapter -eq "ODataV4Adapter") { - $DotNetAdapter = 'Microsoft.PowerShell.Cmdletization.OData.ODataV4CmdletAdapter' - } - - $xmlWriter.WriteAttributeString('CmdletAdapter', $DotNetAdapter + ', Microsoft.PowerShell.Cmdletization.OData, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35') - - $xmlWriter.WriteElementString('Version', '1.0') - $xmlWriter.WriteElementString('DefaultNoun', $defaultNoun) - - $xmlWriter -} - -######################################################### -# SaveCDXMLFooter is a helper function used -# to save CDXML closing attributes corresponding -# to SaveCDXMLHeader function. -######################################################### -function SaveCDXMLFooter -{ - param - ( - [System.Xml.XmlWriter] $xmlWriter - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLFooter") } - - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndDocument() - - $xmlWriter.Flush() - $xmlWriter.Close() -} - -######################################################### -# AddParametersNode is a helper function used -# to add parameters to the generated proxy cmdlet, -# based on mandatoryProperties and otherProperties. -# PrefixForKeys is used by associations to append a -# prefix to PowerShell parameter name. -######################################################### -function AddParametersNode -{ - param - ( - [Parameter(Mandatory=$true)] - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.TypeProperty[]] $keyProperties, - [ODataUtils.TypeProperty[]] $mandatoryProperties, - [ODataUtils.TypeProperty[]] $otherProperties, - [string] $prefixForKeys, - [boolean] $addForceParameter, - [boolean] $addParametersElement, - [Hashtable] $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "AddParametersNode") } - - if(($keyProperties.Length -gt 0) -or - ($mandatoryProperties.Length -gt 0) -or - ($otherProperties.Length -gt 0) -or - ($addForceParameter)) - { - if($addParametersElement) - { - $xmlWriter.WriteStartElement('Parameters') - } - - $pos = 0 - - if ($null -ne $keyProperties) - { - $pos = AddParametersCDXML $xmlWriter $keyProperties $pos $true $prefixForKeys ":Key" $complexTypeMapping - } - - if ($null -ne $mandatoryProperties) - { - $pos = AddParametersCDXML $xmlWriter $mandatoryProperties $pos $true $null $null $complexTypeMapping - } - - if ($null -ne $otherProperties) - { - $pos = AddParametersCDXML $xmlWriter $otherProperties $pos $false $null $null $complexTypeMapping - } - - if($addForceParameter) - { - $forceParameter = [ODataUtils.TypeProperty] @{ - "Name" = "Force"; - "TypeName" = "switch"; - "IsNullable" = $false - } - - $pos = AddParametersCDXML $xmlWriter $forceParameter $pos $false $null $null $complexTypeMapping - } - - if($addParametersElement) - { - $xmlWriter.WriteEndElement() - } - } -} - -######################################################### -# AddParametersCDXML is a helper function used -# to add Parameter node to CDXML based on properties. -# Prefix is appended to PS parameter names, used for -# associations. Suffix is appended to all parameter -# names, for ex. to differentiate keys. returns new $pos -######################################################### -function AddParametersCDXML -{ - param - ( - [Parameter(Mandatory=$true)] - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.TypeProperty[]] $properties, - [Parameter(Mandatory=$true)] - [int] $pos, - [bool] $isMandatory, - [string] $prefix, - [string] $suffix, - [Hashtable] $complexTypeMapping - ) - - $properties | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $_.Name + $suffix) - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $prefix + $_.Name) - $xmlWriter.WriteAttributeString('IsMandatory', ($isMandatory).ToString().ToLowerInvariant()) - $xmlWriter.WriteAttributeString('Position', $pos) - if($isMandatory) - { - $xmlWriter.WriteAttributeString('ValueFromPipelineByPropertyName', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $pos++ - } - - $pos -} - -######################################################### -# GenerateSetProxyCmdlet is a helper function used -# to generate Set-* proxy cmdlet. The proxy cmdlet is -# generated in the CDXML compliant format. -######################################################### -function GenerateSetProxyCmdlet -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [object[]] $keyProperties, - [object[]] $nonKeyProperties, - [Hashtable] $complexTypeMapping - ) - - # $cmdletAdapter is already validated at the cmdlet layer. - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "GenerateSetProxyCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Set') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', 'Update') - $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') - - AddParametersNode $xmlWriter $keyProperties $null $nonKeyProperties $null $true $true $complexTypeMapping - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() -} diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 deleted file mode 100644 index 9ecd112b2e8..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/Microsoft.PowerShell.ODataV4Adapter.ps1 +++ /dev/null @@ -1,2668 +0,0 @@ -Import-LocalizedData LocalizedData -FileName Microsoft.PowerShell.ODataUtilsStrings.psd1 - -# Add .NET classes used by the module -Add-Type -TypeDefinition $global:BaseClassDefinitions - -######################################################### -# Generates PowerShell module containing client side -# proxy cmdlets that can be used to interact with an -# OData based server side endpoint. -######################################################### -function ExportODataEndpointProxy -{ - param - ( - [string] $Uri, - [string] $OutputModule, - [string] $MetadataUri, - [PSCredential] $Credential, - [string] $CreateRequestMethod, - [string] $UpdateRequestMethod, - [string] $CmdletAdapter, - [Hashtable] $ResourceNameMapping, - [switch] $Force, - [Hashtable] $CustomData, - [switch] $AllowClobber, - [switch] $AllowUnsecureConnection, - [Hashtable] $Headers, - [string] $ProgressBarStatus, - [System.Management.Automation.PSCmdlet] $PSCmdlet - ) - - # Record of all metadata XML files which have been opened for parsing - # used to avoid parsing the same file twice, if referenced in multiple - # metadata files - $script:processedFiles = @() - - # Record of all referenced and parsed metadata files (including entry point metadata) - $script:GlobalMetadata = New-Object System.Collections.ArrayList - - # The namespace name might have invalid characters or might be conflicting with class names in inheritance scenarios - # We will be normalizing these namespaces and saving them into $normalizedNamespaces, where key is the original namespace and value is normalized namespace - $script:normalizedNamespaces = @{} - - # This information will be used during recursive referenced metadata files loading - $ODataEndpointProxyParameters = [ODataUtils.ODataEndpointProxyParameters] @{ - "MetadataUri" = $MetadataUri; - "Uri" = $Uri; - "Credential" = $Credential; - "OutputModule" = $OutputModule; - "Force" = $Force; - "AllowClobber" = $AllowClobber; - "AllowUnsecureConnection" = $AllowUnsecureConnection; - } - - # Recursively fetch all metadatas (referenced by entry point metadata) - $GlobalMetadata = GetTypeInfo -callerPSCmdlet $pscmdlet -MetadataUri $MetadataUri -ODataEndpointProxyParameters $ODataEndpointProxyParameters -Headers $Headers - # Now that we are done with recursive metadata references parsing we can get rid of this variable - $script:GlobalMetadata = $null - - VerifyMetadata $GlobalMetadata $AllowClobber.IsPresent $PSCmdlet $ProgressBarStatus - - # Get Uri Resource path key format. It can be either 'EmbeddedKey' or 'SeparateKey'. - # If not provided, default value will be set to 'EmbeddedKey'. - $UriResourcePathKeyFormat = 'EmbeddedKey' - if ($CustomData -and $CustomData.ContainsKey("UriResourcePathKeyFormat")) - { - $UriResourcePathKeyFormat = $CustomData."UriResourcePathKeyFormat" - } - - GenerateClientSideProxyModule $GlobalMetadata $ODataEndpointProxyParameters $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $ResourceNameMapping $CustomData $UriResourcePathKeyFormat $ProgressBarStatus $script:normalizedNamespaces -} - -######################################################### -# GetTypeInfo is a helper method used to get all the types -# from metadata files in a recursive manner -######################################################### -function GetTypeInfo -{ - param - ( - [System.Management.Automation.PSCmdlet] $callerPSCmdlet, - [string] $MetadataUri, - [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, - [Hashtable] $Headers - ) - - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "callerPSCmdlet", "GetTypeInfo") } - - $metadataSet = New-Object System.Collections.ArrayList - $metadataXML = GetMetaData $MetadataUri $callerPSCmdlet $ODataEndpointProxyParameters.Credential $Headers $ODataEndpointProxyParameters.AllowUnsecureConnection - $script:processedFiles += $MetadataUri - - # parses all referenced metadata XML files recursively - foreach ($reference in $metadataXML.Edmx.Reference) - { - if (-not $script:processedFiles.Contains($reference.Uri)) - { - $tmpMetadataSet = $null - $tmpMetadataSet = GetTypeInfo -callerPSCmdlet $callerPSCmdlet -MetadataUri $reference.Uri -ODataEndpointProxyParameters $ODataEndpointProxyParameters - AddMetadataToMetadataSet -Metadatas $metadataSet -NewMetadata $tmpMetadataSet - } - } - - $metadatas = ParseMetadata -MetadataXML $metadataXML -ODataVersion $metadataXML.Edmx.Version -MetadataUri $MetadataUri -Uri $ODataEndpointProxyParameters.Uri -MetadataSet $script:GlobalMetadata - AddMetadataToMetadataSet -Metadatas $script:GlobalMetadata -NewMetadata $metadatas - AddMetadataToMetadataSet -Metadatas $metadataSet -NewMetadata $metadatas - - return $metadataSet -} - -function AddMetadataToMetadataSet -{ - param - ( - [System.Collections.ArrayList] $Metadatas, - $NewMetadata - ) - - if($null -eq $NewMetadata) { throw ($LocalizedData.ArguementNullError -f "NewMetadata", "AddMetadataToMetadataSet") } - - if ($NewMetadata.GetType().Name -eq 'MetadataV4') - { - $Metadatas.Add($NewMetadata) | Out-Null - } - else - { - $Metadatas.AddRange($NewMetadata) | Out-Null - } -} - -######################################################### -# Normalization of Namespace name will be required in following scenarios: -# 1. Namespace name contains combination of dots and numbers -# 2. Namespace name collides with Class name (EntityType, EntitySet, etc.) -# If normalization is needed, all dots will be replaced with underscores and Ns suffix added -# User will receive warning notifying her about the namespace name change -######################################################### -function NormalizeNamespace -{ - param - ( - [string] $MetadataNamespace, - [string] $MetadataUri, - [Hashtable] $NormalizedNamespaces, - [boolean] $DoesNamespaceConflictsWithClassName - ) - - $doesNamespaceContainsInvalidChars = $false - - # Check if namespace name contains invalid combination if dots and numbers - if ($MetadataNamespace -match '\.[0-9]' -or $MetadataNamespace -match '[0-9]\.') - { - # Normalization needed - $doesNamespaceContainsInvalidChars = $true - } - - # Normalize if needed - if ($doesNamespaceContainsInvalidChars -or $DoesNamespaceConflictsWithClassName) - { - if ($NormalizedNamespaces.ContainsKey($MetadataNamespace)) - { - # It's possible we've already attempted to normalize that namespace. In that case we'll update normalized name. - $NormalizedNamespaces[$MetadataNamespace] = NormalizeNamespaceHelper $NormalizedNamespaces[$MetadataNamespace] $doesNamespaceContainsInvalidChars $DoesNamespaceConflictsWithClassName - } - else - { - $NormalizedNamespaces.Add($MetadataNamespace, (NormalizeNamespaceHelper $MetadataNamespace $doesNamespaceContainsInvalidChars $DoesNamespaceConflictsWithClassName)) - } - } - - # Print warning - if ($doesNamespaceContainsInvalidChars) - { - # Normalization needed - $warningMessage = ($LocalizedData.InValidSchemaNamespaceContainsInvalidChars -f $MetadataUri, $MetadataNamespace, $NormalizedNamespaces[$MetadataNamespace]) - Write-Warning $warningMessage - } - if ($DoesNamespaceConflictsWithClassName) - { - # Collision between namespace name and type name detected (example: namespace TaskService { class Service : Service.BasicService { ... } ... }) - # Normalization needed - $warningMessage = ($LocalizedData.InValidSchemaNamespaceConflictWithClassName -f $MetadataUri, $MetadataNamespace, $NormalizedNamespaces[$MetadataNamespace]) - Write-Warning $warningMessage - } -} - -function NormalizeNamespaceCollisionWithClassName -{ - param - ( - [string] $InheritingType, - [string] $BaseTypeName, - [string] $MetadataUri - ) - - if (![string]::IsNullOrEmpty($BaseTypeName)) - { - $dotNetNamespace = '' - if ($BaseTypeName.LastIndexOf(".") -gt 0) - { - # BaseTypeStr contains Namespace and TypeName. Extract Namespace name. - $dotNetNamespace = $BaseTypeName.SubString(0, $BaseTypeName.LastIndexOf(".")) - } - - if (![string]::IsNullOrEmpty($dotNetNamespace) -and $InheritingType -eq $dotNetNamespace) - { - # Collision between namespace name and type name detected (example: namespace TaskService { class Service : Service.BasicService { ... } ... }) - # Normalization needed - NormalizeNamespace $dotNetNamespace $MetadataUri $script:normalizedNamespaces $true - break - } - } -} - -######################################################### -# This helper method is used by functions, -# writing directly to CDXML files or to .Net namespace/class definitions ComplexTypes file -######################################################### -function GetNamespace -{ - param - ( - [string] $Namespace, - $NormalizedNamespaces, - [boolean] $isClassNameIncluded = $false - ) - - $dotNetNamespace = $Namespace - $dotNetClassName = '' - - # Extract only namespace name - if ($isClassNameIncluded) - { - if ($Namespace.LastIndexOf(".") -gt 0) - { - # For example, from following namespace (Namespace.TypeName) Service.1.0.0.Service we'll extract only namespace name, which is Service.1.0.0 - $dotNetNamespace = $Namespace.SubString(0, $Namespace.LastIndexOf(".")) - $dotNetClassName = $Namespace.SubString($Namespace.LastIndexOf(".") + 1, $Namespace.Length - $Namespace.LastIndexOf(".") - 1) - } - } - - # Check if the namespace has to be normalized. - if ($NormalizedNamespaces.ContainsKey($dotNetNamespace)) - { - $dotNetNamespace = $NormalizedNamespaces.Get_Item($dotNetNamespace) - } - - if (![string]::IsNullOrEmpty($dotNetClassName)) - { - return ($dotNetNamespace + "." + $dotNetClassName) - } - else - { - return $dotNetNamespace - } -} - -function NormalizeNamespaceHelper -{ - param - ( - [string] $Namespace, - [boolean] $DoesNamespaceContainsInvalidChars, - [boolean] $DoesNamespaceConflictsWithClassName - ) - - # For example, following namespace: Service.1.0.0 - # Will change to: Service_1_0_0 - # Ns postfix in Namespace name will allow to differentiate between this namespace - # and a colliding type name from different namespace - $updatedNs = $Namespace - if ($DoesNamespaceContainsInvalidChars) - { - $updatedNs = $updatedNs.Replace('.', '_') - } - if ($DoesNamespaceConflictsWithClassName) - { - $updatedNs = $updatedNs + "Ns" - } - - $updatedNs -} - -######################################################### -# Processes EntityTypes (OData V4 schema) from plain text -# xml metadata into our custom structure -######################################################### -function ParseEntityTypes -{ - param - ( - [System.Xml.XmlElement] $SchemaXML, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [hashtable] $EntityAndComplexTypesQueue, - [string] $CustomNamespace, - [AllowEmptyString()] - [string] $Alias - ) - - if($null -eq $SchemaXML) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseEntityTypes") } - - foreach ($entityType in $SchemaXML.EntityType) - { - $baseType = $null - - if ($null -ne $entityType.BaseType) - { - # add it to the processing queue - $baseType = GetBaseType $entityType $Metadata $SchemaXML.Namespace $GlobalMetadata - if ($null -eq $baseType) - { - $EntityAndComplexTypesQueue[$entityType.BaseType] += @(@{type='EntityType'; value=$entityType}) - } - - # Check if Namespace has to be normalized because of the collision with the inheriting Class name - NormalizeNamespaceCollisionWithClassName -InheritingType $entityType.Name -BaseTypeName $entityType.BaseType -MetadataUri $Metadata.Uri - } - - [ODataUtils.EntityTypeV4] $newType = ParseMetadataTypeDefinition $entityType $baseType $Metadata $schema.Namespace $Alias $true $entityType.BaseType - $Metadata.EntityTypes += $newType - AddDerivedTypes $newType $entityAndComplexTypesQueue $Metadata $SchemaXML.Namespace - } -} - -######################################################### -# Processes ComplexTypes from plain text xml metadata -# into our custom structure -######################################################### -function ParseComplexTypes -{ - param - ( - [System.Xml.XmlElement] $SchemaXML, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [hashtable] $EntityAndComplexTypesQueue, - [string] $CustomNamespace, - [AllowEmptyString()] - [string] $Alias - ) - - if($null -eq $SchemaXMLl) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseComplexTypes") } - - foreach ($complexType in $SchemaXML.ComplexType) - { - $baseType = $null - - if ($null -ne $complexType.BaseType) - { - # add it to the processing queue - $baseType = GetBaseType $complexType $metadata $SchemaXML.Namespace $GlobalMetadata - if ($null -eq $baseType -and $null -ne $entityAndComplexTypesQueue -and $entityAndComplexTypesQueue.ContainsKey($complexType.BaseType)) - { - $entityAndComplexTypesQueue[$complexType.BaseType] += @(@{type='ComplexType'; value=$complexType}) - continue - } - - # Check if Namespace has to be normalized because of the collision with the inheriting Class name - NormalizeNamespaceCollisionWithClassName -InheritingType $complexType.Name -BaseTypeName $complexType.BaseType -MetadataUri $Metadata.Uri - } - - [ODataUtils.EntityTypeV4] $newType = ParseMetadataTypeDefinition $complexType $baseType $Metadata $schema.Namespace -Alias $Alias $false $complexType.BaseType - $Metadata.ComplexTypes += $newType - AddDerivedTypes $newType $entityAndComplexTypesQueue $metadata $schema.Namespace - } -} - -######################################################### -# Processes TypeDefinition from plain text xml metadata -# into our custom structure -######################################################### -function ParseTypeDefinitions -{ - param - ( - [System.Xml.XmlElement] $SchemaXML, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [string] $CustomNamespace, - [AllowEmptyString()] - [string] $Alias - ) - - if($null -eq $SchemaXML) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseTypeDefinitions") } - - - foreach ($typeDefinition in $SchemaXML.TypeDefinition) - { - $newType = [ODataUtils.EntityTypeV4] @{ - "Namespace" = $Metadata.Namespace; - "Alias" = $Metadata.Alias; - "Name" = $typeDefinition.Name; - "BaseTypeStr" = $typeDefinition.UnderlyingType; - } - $Metadata.TypeDefinitions += $newType - } -} - -######################################################### -# Processes EnumTypes from plain text xml metadata -# into our custom structure -######################################################### -function ParseEnumTypes -{ - param - ( - [System.Xml.XmlElement] $SchemaXML, - [ODataUtils.MetadataV4] $Metadata - ) - - if($null -eq $SchemaXML) { throw ($LocalizedData.ArguementNullError -f "SchemaXML", "ParseEnumTypes") } - - foreach ($enum in $SchemaXML.EnumType) - { - $newEnumType = [ODataUtils.EnumType] @{ - "Namespace" = $Metadata.Namespace; - "Alias" = $Metadata.Alias; - "Name" = $enum.Name; - "UnderlyingType" = $enum.UnderlyingType; - "IsFlags" = $enum.IsFlags; - "Members" = @() - } - - if (!$newEnumType.UnderlyingType) - { - # If no type specified set the default type which is Edm.Int32 - $newEnumType.UnderlyingType = "Edm.Int32" - } - - if ($null -eq $newEnumType.IsFlags) - { - # If no value is specified for IsFlags, its value defaults to false. - $newEnumType.IsFlags = $false - } - - $enumValue = 0 - $currentEnumValue = 0 - - # Now parse EnumType elements - foreach ($element in $enum.Member) - { - - if ($element.Value -eq "" -and $newEnumType.IsFlags -eq $true) - { - # When IsFlags set to true each edm:Member element MUST specify a non-negative integer Value in the value attribute - $errorMessage = ($LocalizedData.InValidMetadata) - $detailedErrorMessage = "When IsFlags set to true each edm:Member element MUST specify a non-negative integer Value in the value attribute in " + $newEnumType.Name + " EnumType" - $exception = [System.InvalidOperationException]::new($errorMessage, $detailedErrorMessage) - $errorRecord = CreateErrorRecordHelper "InValidMetadata" $null ([System.Management.Automation.ErrorCategory]::InvalidData) $detailedErrorMessage nu - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - elseif (($null -eq $element.Value) -or ($element.Value.GetType().Name -eq "Int32" -and $element.Value -eq "")) - { - # If no values are specified, the members are assigned consecutive integer values in the order of their appearance, - # starting with zero for the first member. - $currentEnumValue = $enumValue - } - else - { - $currentEnumValue = $element.Value - } - - $tmp = [ODataUtils.EnumMember] @{ - "Name" = $element.Name; - "Value" = $currentEnumValue; - } - - $newEnumType.Members += $tmp - $enumValue++ - } - - $Metadata.EnumTypes += $newEnumType - } -} - -######################################################### -# Processes SingletonTypes from plain text xml metadata -# into our custom structure -######################################################### -function ParseSingletonTypes -{ - param - ( - [System.Xml.XmlElement] $SchemaEntityContainerXML, - [ODataUtils.MetadataV4] $Metadata - ) - - if($null -eq $SchemaEntityContainerXML) { throw ($LocalizedData.ArguementNullError -f "SchemaEntityContainerXML", "ParseSingletonTypes") } - - foreach ($singleton in $SchemaEntityContainerXML.Singleton) - { - $navigationPropertyBindings = @() - - foreach ($navigationPropertyBinding in $singleton.NavigationPropertyBinding) - { - $tmp = [ODataUtils.NavigationPropertyBinding] @{ - "Path" = $navigationPropertyBinding.Path; - "Target" = $navigationPropertyBinding.Target; - } - - $navigationPropertyBindings += $tmp - } - - $newSingletonType = [ODataUtils.SingletonType] @{ - "Namespace" = $Metadata.Namespace; - "Alias" = $Metadata.Alias; - "Name" = $singleton.Name; - "Type" = $singleton.Type; - "NavigationPropertyBindings" = $navigationPropertyBindings; - } - - $Metadata.SingletonTypes += $newSingletonType - } -} - -######################################################### -# Processes EntitySets from plain text xml metadata -# into our custom structure -######################################################### -function ParseEntitySets -{ - param - ( - [System.Xml.XmlElement] $SchemaEntityContainerXML, - [ODataUtils.MetadataV4] $Metadata, - [string] $Namespace, - [AllowEmptyString()] - [string] $Alias - ) - - if($null -eq $SchemaEntityContainerXML) { throw ($LocalizedData.ArguementNullError -f "SchemaEntityContainerXML", "ParseEntitySets") } - - $entityTypeToEntitySetMapping = @{}; - foreach ($entitySet in $SchemaEntityContainerXML.EntitySet) - { - $entityType = $metadata.EntityTypes | Where-Object { $_.Name -eq $entitySet.EntityType.Split('.')[-1] } - $entityTypeName = $entityType.Name - - if($entityTypeToEntitySetMapping.ContainsKey($entityTypeName)) - { - $existingEntitySetName = $entityTypeToEntitySetMapping[$entityTypeName] - throw ($LocalizedData.EntityNameConflictError -f $entityTypeName, $existingEntitySetName, $entitySet.Name, $entityTypeName ) - } - else - { - $entityTypeToEntitySetMapping.Add($entityTypeName, $entitySet.Name) - } - - $newEntitySet = [ODataUtils.EntitySetV4] @{ - "Namespace" = $Namespace; - "Alias" = $Alias; - "Name" = $entitySet.Name; - "Type" = $entityType; - } - - $Metadata.EntitySets += $newEntitySet - } -} - -######################################################### -# Processes Actions from plain text xml metadata -# into our custom structure -######################################################### -function ParseActions -{ - param - ( - [System.Object[]] $SchemaActionsXML, - [ODataUtils.MetadataV4] $Metadata - ) - - if($null -eq $SchemaActionsXML) { throw ($LocalizedData.ArguementNullError -f "SchemaActionsXML", "ParseActions") } - - foreach ($action in $SchemaActionsXML) - { - # HttpMethod is only used for legacy Service Operations - if ($null -eq $action.HttpMethod) - { - $newAction = [ODataUtils.ActionV4] @{ - "Namespace" = $Metadata.Namespace; - "Alias" = $Metadata.Alias; - "Name" = $action.Name; - "Action" = $Metadata.Namespace + '.' + $action.Name; - } - - # Actions are always SideEffecting, otherwise it's an OData function - foreach ($parameter in $action.Parameter) - { - if ($null -ne $parameter.Nullable) - { - $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable); - } - else - { - $parameterIsNullable = $true - } - - $newParameter = [ODataUtils.TypeProperty] @{ - "Name" = $parameter.Name; - "TypeName" = $parameter.Type; - "IsNullable" = $parameterIsNullable; - } - - $newAction.Parameters += $newParameter - } - - if ($null -ne $action.EntitySet) - { - $newAction.EntitySet = $metadata.EntitySets | Where-Object { $_.Name -eq $action.EntitySet } - } - - $Metadata.Actions += $newAction - } - } -} - -######################################################### -# Processes Functions from plain text xml metadata -# into our custom structure -######################################################### -function ParseFunctions -{ - param - ( - [System.Object[]] $SchemaFunctionsXML, - [ODataUtils.MetadataV4] $Metadata - ) - - if($null -eq $SchemaFunctionsXML) { throw ($LocalizedData.ArguementNullError -f "SchemaFunctionsXML", "ParseFunctions") } - - foreach ($function in $SchemaFunctionsXML) - { - # HttpMethod is only used for legacy Service Operations - if ($null -eq $function.HttpMethod) - { - $newFunction = [ODataUtils.FunctionV4] @{ - "Namespace" = $Metadata.Namespace; - "Alias" = $Metadata.Alias; - "Name" = $function.Name; - "Function" = $Metadata.Namespace + '.' + $function.Name; - "EntitySet" = $function.EntitySetPath; - "ReturnType" = $function.ReturnType; - } - - # Future TODO - consider removing this hack once all the service we run against fix this issue - # Hack - sometimes service does not return ReturnType, however this information can be found in InnerXml - if ($newFunction.ReturnType -eq '' -or $newFunction.ReturnType -eq 'System.Xml.XmlElement') - { - try - { - [xml] $innerXML = '' + $function.InnerXml + '' - $newFunction.Returntype = $innerXML.Params.ReturnType.Type - } - catch - { - # Do nothing - } - } - - # Keep only EntityType name - $newFunction.ReturnType = $newFunction.ReturnType.Replace('Collection(', '') - $newFunction.ReturnType = $newFunction.ReturnType.Replace(')', '') - - # Actions are always SideEffecting, otherwise it's an OData function - foreach ($parameter in $function.Parameter) - { - if ($null -ne $parameter.Nullable) - { - $parameterIsNullable = [System.Convert]::ToBoolean($parameter.Nullable); - } - - $newParameter = [ODataUtils.Parameter] @{ - "Name" = $parameter.Name; - "Type" = $parameter.Type; - "Nullable" = $parameterIsNullable; - } - - $newFunction.Parameters += $newParameter - } - - $Metadata.Functions += $newFunction - } - } -} - -######################################################### -# Processes plain text xml metadata (OData V4 schema version) into our custom structure -# MetadataSet contains all parsed so far referenced Metadatas (for base class lookup) -######################################################### -function ParseMetadata -{ - param - ( - [xml] $MetadataXML, - [string] $ODataVersion, - [string] $MetadataUri, - [string] $Uri, - [System.Collections.ArrayList] $MetadataSet - ) - - if($null -eq $MetadataXML) { throw ($LocalizedData.ArguementNullError -f "MetadataXML", "ParseMetadata") } - - # This is a processing queue for those types that require base types that haven't been defined yet - $entityAndComplexTypesQueue = @{} - [System.Collections.ArrayList] $metadatas = [System.Collections.ArrayList]::new() - - foreach ($schema in $MetadataXML.Edmx.DataServices.Schema) - { - if ($null -eq $schema) - { - Write-Error $LocalizedData.EmptySchema - continue - } - - [ODataUtils.MetadataV4] $metadata = [ODataUtils.MetadataV4]::new() - $metadata.ODataVersion = $ODataVersion - $metadata.MetadataUri = $MetadataUri - $metadata.Uri = $Uri - $metadata.Namespace = $schema.Namespace - $metadata.Alias = $schema.Alias - - ParseEntityTypes -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -EntityAndComplexTypesQueue $entityAndComplexTypesQueue -CustomNamespace $CustomNamespace -Alias $metadata.Alias - ParseComplexTypes -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -EntityAndComplexTypesQueue $entityAndComplexTypesQueue -CustomNamespace $CustomNamespace -Alias $metadata.Alias - ParseTypeDefinitions -SchemaXML $schema -metadata $metadata -GlobalMetadata $MetadataSet -CustomNamespace $CustomNamespace -Alias $metadata.Alias - ParseEnumTypes -SchemaXML $schema -metadata $metadata - - foreach ($entityContainer in $schema.EntityContainer) - { - if ($entityContainer.IsDefaultEntityContainer) - { - $metadata.DefaultEntityContainerName = $entityContainer.Name - } - - ParseSingletonTypes -SchemaEntityContainerXML $entityContainer -Metadata $metadata - ParseEntitySets -SchemaEntityContainerXML $entityContainer -Metadata $metadata -Namespace $schema.Namespace -Alias $schema.Alias - } - - if ($schema.Action) - { - ParseActions -SchemaActionsXML $schema.Action -Metadata $metadata - } - - if ($schema.Function) - { - ParseFunctions -SchemaFunctionsXML $schema.Function -Metadata $metadata - } - - # In this call we check if the Namespace or Alias have to be normalized because it has invalid combination of dots and numbers. - # Note: In ParseEntityTypes and ParseComplexTypes we check for scenario where namespace/alias collide with inheriting class name. - NormalizeNamespace $metadata.Namespace $metadata.Uri $script:normalizedNamespaces $false - NormalizeNamespace $metadata.Alias $metadata.Uri $script:normalizedNamespaces $false - - $metadatas.Add($metadata) | Out-Null - } - - $metadatas -} - -######################################################### -# Verifies processed metadata for correctness -######################################################### -function VerifyMetadata -{ - param - ( - [System.Collections.ArrayList] $metadataSet, - [boolean] $allowClobber, - $callerPSCmdlet, - [string] $progressBarStatus - ) - - if($null -eq $callerPSCmdlet) { throw ($LocalizedData.ArguementNullError -f "PSCmdlet", "VerifyMetaData") } - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "VerifyMetaData") } - - Write-Verbose $LocalizedData.VerboseVerifyingMetadata - - $reservedProperties = @("Filter", "OrderBy", "Skip", "Top", "ConnectionUri", "CertificateThumbPrint", "Credential") - $validEntitySets = @() - $sessionCommands = Get-Command -All - - - foreach ($metadata in $metadataSet) - { - foreach ($entitySet in $metadata.EntitySets) - { - if ($null -eq $entitySet.Type) - { - $errorMessage = ($LocalizedData.EntitySetUndefinedType -f $metadata.MetadataUri, $entitySet.Name) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointProxyInvalidMetaDataUri" $null ([System.Management.Automation.ErrorCategory]::InvalidArgument) $exception $metadata.MetadataUri - $callerPSCmdlet.ThrowTerminatingError($errorRecord) - } - - $hasConflictingProperty = $false - $hasConflictingCommand = $false - $entityAndNavigationProperties = $entitySet.Type.EntityProperties + $entitySet.Type.NavigationProperties - foreach($entityProperty in $entityAndNavigationProperties) - { - if($reservedProperties.Contains($entityProperty.Name)) - { - $hasConflictingProperty = $true - if(!$allowClobber) - { - # Write Error message and skip current Entity Set. - $errorMessage = ($LocalizedData.SkipEntitySetProxyCreation -f $entitySet.Name, $entitySet.Type.Name, $entityProperty.Name) - $exception = [System.InvalidOperationException]::new($errorMessage) - $errorRecord = CreateErrorRecordHelper "ODataEndpointDefaultPropertyCollision" $null ([System.Management.Automation.ErrorCategory]::InvalidOperation) $exception $metadata.MetadataUri - $callerPSCmdlet.WriteError($errorRecord) - } - else - { - $warningMessage = ($LocalizedData.EntitySetProxyCreationWithWarning -f $entitySet.Name, $entityProperty.Name, $entitySet.Type.Name) - $callerPSCmdlet.WriteWarning($warningMessage) - } - } - } - - foreach($currentCommand in $sessionCommands) - { - if(($null -ne $currentCommand.Noun -and $currentCommand.Noun -eq $entitySet.Name) -and - ($currentCommand.Verb -eq "Get" -or - $currentCommand.Verb -eq "Set" -or - $currentCommand.Verb -eq "New" -or - $currentCommand.Verb -eq "Remove")) - { - $hasConflictingCommand = $true - VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` - $LocalizedData.EntitySetConflictCommandCreationWithWarning ` - $entitySet.Name $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet - } - } - - foreach($currentAction in $metadata.Actions) - { - $actionCommand = "Invoke-" + "$($entitySet.Name)$($currentAction.Verb)" - - foreach($currentCommand in $sessionCommands) - { - if($actionCommand -eq $currentCommand.Name) - { - $hasConflictingCommand = $true - VerifyMetadataHelper $LocalizedData.SkipEntitySetConflictCommandCreation ` - $LocalizedData.EntitySetConflictCommandCreationWithWarning $entitySet.Name ` - $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet - } - } - } - - if(!($hasConflictingProperty -or $hasConflictingCommand)-or $allowClobber) - { - $validEntitySets += $entitySet - } - } - - $metadata.EntitySets = $validEntitySets - - $validServiceActions = @() - $hasConflictingServiceActionCommand - foreach($currentAction in $metadata.Actions) - { - $serviceActionCommand = "Invoke-" + "$($currentAction.Verb)" - - foreach($currentCommand in $sessionCommands) - { - if($serviceActionCommand -eq $currentCommand.Name) - { - $hasConflictingServiceActionCommand = $true - VerifyMetadataHelper $LocalizedData.SkipConflictServiceActionCommandCreation ` - $LocalizedData.ConflictServiceActionCommandCreationWithWarning $entitySet.Name ` - $currentCommand.Name $metadata.MetadataUri $allowClobber $callerPSCmdlet - } - } - - if(!$hasConflictingServiceActionCommand -or $allowClobber) - { - $validServiceActions += $currentAction - } - } - - $metadata.Actions = $validServiceActions - } - - # Update Progress bar. - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 5 20 1 1 -} - -######################################################### -# Takes xml definition of a class from metadata document, -# plus existing metadata structure and finds its base class -######################################################### -function GetBaseType -{ - param - ( - [System.Xml.XmlElement] $MetadataEntityDefinition, - [ODataUtils.MetadataV4] $Metadata, - [string] $Namespace, - [System.Collections.ArrayList] $GlobalMetadata - ) - - if ($null -ne $metadataEntityDefinition -and - $null -ne $metaData -and - $null -ne $MetadataEntityDefinition.BaseType) - { - $baseType = $Metadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType } - if ($null -eq $baseType) - { - $baseType = $Metadata.ComplexTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType } - } - - if ($null -eq $baseType) - { - # Look in other metadatas, since the class can be defined in referenced metadata - foreach ($referencedMetadata in $GlobalMetadata) - { - if ($null -ne ($baseType = $referencedMetadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType }) -or - $null -ne ($baseType = $referencedMetadata.ComplexTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $MetadataEntityDefinition.BaseType -or $_.Alias + "." + $_.Name -eq $MetadataEntityDefinition.BaseType })) - { - # Found base class - break - } - } - } - } - - if ($null -ne $baseType) - { - $baseType[0] - } -} - -######################################################### -# Takes base class name and global metadata structure -# and finds its base class -######################################################### -function GetBaseTypeByName -{ - param - ( - [String] $BaseTypeStr, - [System.Collections.ArrayList] $GlobalMetadata - ) - - if ($null -ne $BaseTypeStr) - { - - # Look for base class definition in all referenced metadatas (including entry point) - foreach ($referencedMetadata in $GlobalMetadata) - { - if ($null -ne ($baseType = $referencedMetadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $BaseTypeStr -or $_.Alias + "." + $_.Name -eq $BaseTypeStr }) -or - $null -ne ($baseType = $referencedMetadata.ComplexTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $BaseTypeStr -or $_.Alias + "." + $_.Name -eq $BaseTypeStr })) - { - # Found base class - break - } - } - } - - if ($null -ne $baseType) - { - $baseType[0] - } - else - { - $null - } -} - -######################################################### -# Processes derived types of a newly added type, -# that were previously waiting in the queue -######################################################### -function AddDerivedTypes { - param( - [ODataUtils.EntityTypeV4] $baseType, - $entityAndComplexTypesQueue, - [ODataUtils.MetadataV4] $metadata, - [string] $namespace - ) - - if($null -eq $baseType) { throw ($LocalizedData.ArguementNullError -f "BaseType", "AddDerivedTypes") } - if($null -eq $entityAndComplexTypesQueue) { throw ($LocalizedData.ArguementNullError -f "EntityAndComplexTypesQueue", "AddDerivedTypes") } - if($null -eq $namespace) { throw ($LocalizedData.ArguementNullError -f "Namespace", "AddDerivedTypes") } - - $baseTypeFullName = $baseType.Namespace + '.' + $baseType.Name - $baseTypeShortName = $baseType.Alias + '.' + $baseType.Name - - if ($entityAndComplexTypesQueue.ContainsKey($baseTypeFullName) -or $entityAndComplexTypesQueue.ContainsKey($baseTypeShortName)) - { - $types = $entityAndComplexTypesQueue[$baseTypeFullName] + $entityAndComplexTypesQueue[$baseTypeShortName] - - foreach ($type in $types) - { - if ($type.type -eq 'EntityType') - { - $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metadata $namespace $true - $metadata.EntityTypes += $newType - } - else - { - $newType = ParseMetadataTypeDefinition ($type.value) $baseType $metadata $namespace $false - $metadata.ComplexTypes += $newType - } - - AddDerivedTypes $newType $entityAndComplexTypesQueue $metadata $namespace - } - } -} - -######################################################### -# Parses types definitions element of metadata xml -######################################################### -function ParseMetadataTypeDefinitionHelper -{ - param - ( - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.EntityTypeV4] $baseType, - [string] $baseTypeStr, - [ODataUtils.MetadataV4] $metadata, - [string] $namespace, - [AllowEmptyString()] - [string] $alias, - [bool] $isEntity - ) - - if($null -eq $metadataEntityDefinition) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") } - if($null -eq $namespace) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") } - - [ODataUtils.EntityTypeV4] $newEntityType = CreateNewEntityType -metadataEntityDefinition $metadataEntityDefinition -baseType $baseType -baseTypeStr $baseTypeStr -namespace $namespace -alias $alias -isEntity $isEntity - - if ($null -ne $baseType) - { - # Add properties inherited from BaseType - ParseMetadataBaseTypeDefinitionHelper $newEntityType $baseType - } - - # properties defined on EntityType - $newEntityType.EntityProperties += $metadataEntityDefinition.Property | ForEach-Object { - if ($null -ne $_) - { - if ($null -ne $_.Nullable) - { - $newPropertyIsNullable = [System.Convert]::ToBoolean($_.Nullable) - } - else - { - $newPropertyIsNullable = $true - } - - [ODataUtils.TypeProperty] @{ - "Name" = $_.Name; - "TypeName" = $_.Type; - "IsNullable" = $newPropertyIsNullable; - } - } - } - - # odataId property will be inherited from base type, if it exists. - # Otherwise, it should be added to current type - if ($null -eq $baseType) - { - # @odata.Id property (renamed to odataId) is required for dynamic Uri creation - # This property is only available when user executes auto-generated cmdlet with -AllowAdditionalData, - # but ODataAdapter needs it to construct Uri to access navigation properties. - # Thus, we need to fetch this info for scenario when -AllowAdditionalData isn't used. - $newEntityType.EntityProperties += [ODataUtils.TypeProperty] @{ - "Name" = "odataId"; - "TypeName" = "Edm.String"; - "IsNullable" = $True; - } - } - - # Property name can't be identical to entity type name. - # If such property exists, "Property" suffix will be added to its name. - foreach ($property in $newEntityType.EntityProperties) - { - if ($property.Name -eq $newEntityType.Name) - { - $property.Name += "Property" - } - } - - if ($null -ne $metadataEntityDefinition -and $null -ne $metadataEntityDefinition.Key) - { - foreach ($entityTypeKey in $metadataEntityDefinition.Key.PropertyRef) - { - ($newEntityType.EntityProperties | Where-Object { $_.Name -eq $entityTypeKey.Name }).IsKey = $true - } - } - - $newEntityType -} - -######################################################### -# Add base class entity and navigation properties to inheriting class -######################################################### -function ParseMetadataBaseTypeDefinitionHelper -{ - param - ( - [ODataUtils.EntityTypeV4] $EntityType, - [ODataUtils.EntityTypeV4] $BaseType - ) - - if ($null -ne $EntityType -and $null -ne $BaseType) - { - # Add properties inherited from BaseType - $EntityType.EntityProperties += $BaseType.EntityProperties - $EntityType.NavigationProperties += $BaseType.NavigationProperties - } -} - -######################################################### -# Create new EntityType object -######################################################### -function CreateNewEntityType -{ - param - ( - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.EntityTypeV4] $baseType, - [string] $baseTypeStr, - [string] $namespace, - [AllowEmptyString()] - [string] $alias, - [bool] $isEntity - ) - $newEntityType = [ODataUtils.EntityTypeV4] @{ - "Namespace" = $namespace; - "Alias" = $alias; - "Name" = $metadataEntityDefinition.Name; - "IsEntity" = $isEntity; - "BaseType" = $baseType; - "BaseTypeStr" = $baseTypeStr; - } - - $newEntityType -} - -######################################################### -# Parses navigation properties from metadata xml -######################################################### -function ParseMetadataTypeDefinitionNavigationProperties -{ - param - ( - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.EntityTypeV4] $entityType - ) - - # navigation properties defined on EntityType - $newEntityType.NavigationProperties = @{} - $newEntityType.NavigationProperties.Clear() - - foreach ($navigationProperty in $metadataEntityDefinition.NavigationProperty) - { - $tmp = [ODataUtils.NavigationPropertyV4] @{ - "Name" = $navigationProperty.Name; - "Type" = $navigationProperty.Type; - "Nullable" = $navigationProperty.Nullable; - "Partner" = $navigationProperty.Partner; - "ContainsTarget" = $navigationProperty.ContainsTarget; - "OnDelete" = $navigationProperty.OnDelete; - } - - $referentialConstraints = @{} - foreach ($constraint in $navigationProperty.ReferentialConstraints) - { - $tmp = [ODataUtils.ReferencedConstraint] @{ - "Property" = $constraint.Property; - "ReferencedProperty" = $constraint.ReferencedProperty; - } - } - - $newEntityType.NavigationProperties += $tmp - } -} - -######################################################### -# Parses types definitions element of metadata xml for OData V4 schema -######################################################### -function ParseMetadataTypeDefinition -{ - param - ( - [System.Xml.XmlElement] $metadataEntityDefinition, - [ODataUtils.EntityTypeV4] $baseType, - [ODataUtils.MetadataV4] $metadata, - [string] $namespace, - [AllowEmptyString()] - [string] $alias, - [bool] $isEntity, - [string] $baseTypeStr - ) - - if($null -eq $metadataEntityDefinition) { throw ($LocalizedData.ArguementNullError -f "MetadataEntityDefinition", "ParseMetadataTypeDefinition") } - if($null -eq $namespace) { throw ($LocalizedData.ArguementNullError -f "Namespace", "ParseMetadataTypeDefinition") } - - [ODataUtils.EntityTypeV4] $newEntityType = ParseMetadataTypeDefinitionHelper -metadataEntityDefinition $metadataEntityDefinition -baseType $baseType -baseTypeStr $baseTypeStr -metadata $metadata -namespace $namespace -alias $alias -isEntity $isEntity - ParseMetadataTypeDefinitionNavigationProperties -metadataEntityDefinition $metadataEntityDefinition -entityType $newEntityType - - $newEntityType -} - -######################################################### -# Create psd1 and cdxml files required to auto-generate -# cmdlets for given service. -######################################################### -function GenerateClientSideProxyModule -{ - param - ( - [System.Collections.ArrayList] $GlobalMetadata, - [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, - [string] $OutputModule, - [string] $CreateRequestMethod, - [string] $UpdateRequestMethod, - [string] $CmdletAdapter, - [Hashtable] $resourceNameMappings, - [Hashtable] $CustomData, - [string] $UriResourcePathKeyFormat, - [string] $progressBarStatus, - $NormalizedNamespaces - ) - - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "ProgressBarStatus", "GenerateClientSideProxyModule") } - - Write-Verbose ($LocalizedData.VerboseSavingModule -f $OutputModule) - - # Save ComplexTypes for all metadata schemas in single file - $typeDefinitionFileName = "ComplexTypeDefinitions.psm1" - $complexTypeMapping = GenerateComplexTypeDefinition $GlobalMetadata $OutputModule $typeDefinitionFileName $NormalizedNamespaces - - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 20 20 1 1 - - $actions = @() - $functions = @() - - $currentEntryCount = 0 - foreach ($Metadata in $GlobalMetadata) - { - foreach ($entitySet in $Metadata.EntitySets) - { - $currentEntryCount += 1 - SaveCDXML $entitySet $Metadata $GlobalMetadata $Metadata.Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $resourceNameMappings $CustomData $complexTypeMapping $UriResourcePathKeyFormat $NormalizedNamespaces - - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $Metadata.EntitySets.Count $currentEntryCount - } - - $currentEntryCount = 0 - foreach ($singleton in $Metadata.SingletonTypes) - { - $currentEntryCount += 1 - SaveCDXMLSingletonCmdlets $singleton $Metadata $GlobalMetadata $Metadata.Uri $OutputModule $CreateRequestMethod $UpdateRequestMethod $CmdletAdapter $resourceNameMappings $CustomData $complexTypeMapping $NormalizedNamespaces - - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 40 20 $Metadata.Singletons.Count $currentEntryCount - } - - $actions += $Metadata.Actions | Where-Object { $_.EntitySet -eq '' -or $null -eq $_.EntitySet } - $functions += $Metadata.Functions | Where-Object { $_.EntitySet -eq '' -or $null -eq $_.EntitySet} - } - - if ($actions.Count -gt 0 -or $functions.Count -gt 0) - { - # Save Service Actions for all metadata schemas in single file - SaveServiceActionsCDXML $GlobalMetadata $ODataEndpointProxyParameters "$OutputModule\ServiceActions.cdxml" $complexTypeMapping $progressBarStatus $CmdletAdapter - } - - $moduleDirInfo = [System.IO.DirectoryInfo]::new($OutputModule) - $moduleManifestName = $moduleDirInfo.Name + ".psd1" - - if ($actions.Count -gt 0 -or $functions.Count -gt 0) - { - $additionalModules = @($typeDefinitionFileName, 'ServiceActions.cdxml') - } - else - { - $additionalModules = @($typeDefinitionFileName) - } - - GenerateModuleManifest $GlobalMetadata $OutputModule\$moduleManifestName $additionalModules $resourceNameMappings $progressBarStatus -} - -######################################################### -# Generates CDXML module for a specific OData EntitySet -######################################################### -function SaveCDXML -{ - param - ( - [ODataUtils.EntitySetV4] $EntitySet, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [string] $Uri, - [string] $OutputModule, - [string] $CreateRequestMethod, - [string] $UpdateRequestMethod, - [string] $CmdletAdapter, - [Hashtable] $resourceNameMappings, - [Hashtable] $CustomData, - [Hashtable] $complexTypeMapping, - [string] $UriResourcePathKeyFormat, - $normalizedNamespaces - ) - - if($null -eq $EntitySet) { throw ($LocalizedData.ArguementNullError -f "EntitySet", "GenerateClientSideProxyModule") } - if($null -eq $Metadata) { throw ($LocalizedData.ArguementNullError -f "metadata", "GenerateClientSideProxyModule") } - - $entitySetName = $EntitySet.Name - if(($null -ne $resourceNameMappings) -and - $resourceNameMappings.ContainsKey($entitySetName)) - { - $entitySetName = $resourceNameMappings[$entitySetName] - } - else - { - $entitySetName = $EntitySet.Type.Name - } - - $Path = "$OutputModule\$entitySetName.cdxml" - - $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) - - if ($null -eq $xmlWriter) - { - throw ($LocalizedData.XmlWriterInitializationError -f $EntitySet.Name) - } - - $xmlWriter = SaveCDXMLHeader $xmlWriter $Uri $EntitySet.Name $entitySetName $CmdletAdapter - - # Get the keys - $keys = $EntitySet.Type.EntityProperties | Where-Object { $_.IsKey } - - $navigationProperties = $EntitySet.Type.NavigationProperties - - SaveCDXMLInstanceCmdlets $xmlWriter $Metadata $GlobalMetadata $EntitySet.Type $keys $navigationProperties $CmdletAdapter $complexTypeMapping $false - - $nonKeyProperties = $EntitySet.Type.EntityProperties | Where-Object { -not $_.isKey } - $nullableProperties = $nonKeyProperties | Where-Object { $_.isNullable } - $nonNullableProperties = $nonKeyProperties | Where-Object { -not $_.isNullable } - - $xmlWriter.WriteStartElement('StaticCmdlets') - - $keyProperties = $keys - - SaveCDXMLNewCmdlet $xmlWriter $Metadata $GlobalMetadata $keyProperties $nonNullableProperties $nullableProperties $navigationProperties $CmdletAdapter $complexTypeMapping - - GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $complexTypeMapping - - SaveCDXMLRemoveCmdlet $xmlWriter $Metadata $GlobalMetadata $keyProperties $navigationProperties $CmdletAdapter $complexTypeMapping - - $entityActions = $Metadata.Actions | Where-Object { ($_.EntitySet.Namespace -eq $EntitySet.Namespace) -and ($_.EntitySet.Name -eq $EntitySet.Name) } - - if ($entityActions.Length -gt 0) - { - foreach($action in $entityActions) - { - $xmlWriter = SaveCDXMLAction $xmlWriter $Metadata $action $EntitySet.Name $true $keys $complexTypeMapping - } - } - - $entityFunctions = $Metadata.Functions | Where-Object { ($_.EntitySet.Namespace -eq $EntitySet.Namespace) -and ($_.EntitySet.Name -eq $EntitySet.Name) } - - if ($entityFunctions.Length -gt 0) - { - foreach($function in $entityFunctions) - { - $xmlWriter = SaveCDXMLFunction $xmlWriter $Metadata $function $EntitySet.Name $true $keys $complexTypeMapping - } - } - - $xmlWriter.WriteEndElement() - - $normalizedDotNetNamespace = GetNamespace $EntitySet.Type.Namespace $normalizedNamespaces - $normalizedDotNetAlias = GetNamespace $EntitySet.Alias $normalizedNamespaces - $normalizedDotNetEntitySetNamespace = $normalizedDotNetNamespace - - $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntityTypeName') - $xmlWriter.WriteString("$($normalizedDotNetNamespace).$($EntitySet.Type.Name)") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntityTypeAliasName') - if (!$EntitySet.Alias) - { - $xmlWriter.WriteString("") - } - else - { - $xmlWriter.WriteString("$($normalizedDotNetAlias).$($EntitySet.Type.Name)") - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntitySetName') - $xmlWriter.WriteString("$($normalizedDotNetEntitySetNamespace).$($EntitySet.Name)") - $xmlWriter.WriteEndElement() - - # Add URI resource path format (webservice.svc/ResourceName/ResourceId vs webservice.svc/ResourceName(QueryKeyName=ResourceId)) - if ($null -ne $UriResourcePathKeyFormat -and $UriResourcePathKeyFormat -ne [string]::Empty) - { - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'UriResourcePathKeyFormat') - $xmlWriter.WriteString("$UriResourcePathKeyFormat") - $xmlWriter.WriteEndElement() - } - - # Add information about navigation properties and their types - # Used in scenario where user requests navigation property in -Select query - foreach ($navProperty in $navigationProperties) - { - if ($navProperty) - { - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', $navProperty.Name + 'NavigationProperty') - $xmlWriter.WriteString($navProperty.Type) - $xmlWriter.WriteEndElement() - } - } - - # Add CreateRequestMethod and UpdateRequestMethod to privateData - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod') - $xmlWriter.WriteString("$CreateRequestMethod") - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod') - $xmlWriter.WriteString("$UpdateRequestMethod") - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - SaveCDXMLFooter $xmlWriter - - Write-Verbose ($LocalizedData.VerboseSavedCDXML -f $($entitySetName), $Path) -} - -######################################################### -# Save Singleton Cmdlets to CDXML -######################################################### -function SaveCDXMLSingletonCmdlets -{ - param - ( - [ODataUtils.SingletonType] $Singleton, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [string] $Uri, - [string] $OutputModule, - [string] $CreateRequestMethod, - [string] $UpdateRequestMethod, - [string] $CmdletAdapter, - [Hashtable] $resourceNameMappings, - [Hashtable] $CustomData, - [Hashtable] $complexTypeMapping, - $normalizedNamespaces - ) - - if($null -eq $Singleton) { throw ($LocalizedData.ArguementNullError -f "Singleton", "SaveCDXMLSingletonCmdlets") } - if($null -eq $Metadata) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLSingletonCmdlets") } - - $singletonName = $singleton.Name - $singletonType = $singleton.Type - - $Path = "$OutputModule\$singletonName" + "Singleton" + ".cdxml" - - $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) - - if ($null -eq $xmlWriter) - { - throw ($LocalizedData.XmlWriterInitializationError -f $singletonName) - } - - # Get associated EntityType - $associatedEntityType = $Metadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $singletonType -or $_.Alias + "." + $_.Name -eq $singletonType} - - if ($null -eq $associatedEntityType) - { - # Look in other metadatas, since the class can be defined in referenced metadata - foreach ($referencedMetadata in $GlobalMetadata) - { - if ($null -ne ($associatedEntityType = $referencedMetadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $singletonType -or $_.Alias + "." + $_.Name -eq $singletonType })) - { - # Found associated class - break - } - } - } - - if ($null -ne $associatedEntityType) - { - $xmlWriter = SaveCDXMLHeader $xmlWriter $Uri $singletonName $singletonName $CmdletAdapter - - if ($null -eq $associatedEntityType.BaseType -and $null -ne $associatedEntityType.BaseTypeStr -and $associatedEntityType.BaseTypeStr -ne '') - { - $associatedEntitybaseType = GetBaseTypeByName $associatedEntityType.BaseTypeStr $GlobalMetadata - - # Make another pass on base class to make sure its properties were added to associated entity type - ParseMetadataBaseTypeDefinitionHelper $associatedEntityType $associatedEntitybaseType - } - - # Get the keys depending on whether the url contains variables or not - $keys = $associatedEntityType.EntityProperties | Where-Object { $_.IsKey } - - $navigationProperties = $associatedEntityType.NavigationProperties - - SaveCDXMLInstanceCmdlets $xmlWriter $Metadata $GlobalMetadata $associatedEntityType $keys $navigationProperties $CmdletAdapter $complexTypeMapping $true - - $nonKeyProperties = $associatedEntityType.EntityProperties | Where-Object { -not $_.isKey } - $nullableProperties = $nonKeyProperties | Where-Object { $_.isNullable } - $nonNullableProperties = $nonKeyProperties | Where-Object { -not $_.isNullable } - - $xmlWriter.WriteStartElement('StaticCmdlets') - - $keyProperties = $keys - - GenerateSetProxyCmdlet $xmlWriter $keyProperties $nonKeyProperties $complexTypeMapping - - $entityActions = $Metadata.Actions | Where-Object { $_.EntitySet.Name -eq $associatedEntityType.Name } - - if ($entityActions.Length -gt 0) - { - foreach($action in $entityActions) - { - $xmlWriter = SaveCDXMLAction $xmlWriter $Metadata $action $EntitySet.Name $true $keys $complexTypeMapping - } - } - - $entityFunctions = $Metadata.Functions | Where-Object { $_.EntitySet.Name -eq $associatedEntityType.Name } - - if ($entityFunctions.Length -gt 0) - { - foreach($function in $entityFunctions) - { - $xmlWriter = SaveCDXMLFunction $xmlWriter $Metadata $function $associatedEntityType.Name $true $keys $complexTypeMapping - } - } - - $xmlWriter.WriteEndElement() - - $normalizedDotNetNamespace = GetNamespace $associatedEntityType.Namespace $normalizedNamespaces - $normalizedDotNetAlias = GetNamespace $associatedEntityType.Alias $normalizedNamespaces - - $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntityTypeAliasName') - if (!$associatedEntityType.Alias) - { - $xmlWriter.WriteString("") - } - else - { - $xmlWriter.WriteString("$($normalizedDotNetAlias).$($associatedEntityType.Name)") - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'EntityTypeName') - $xmlWriter.WriteString("$($normalizedDotNetNamespace).$($associatedEntityType.Name)") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'IsSingleton') - $xmlWriter.WriteString("True") - $xmlWriter.WriteEndElement() - - # Add information about navigation properties and their types - # Used in scenario where user requests navigation property in -Select query - foreach ($navProperty in $navigationProperties) - { - if ($navProperty) - { - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', $navProperty.Name + 'NavigationProperty') - $xmlWriter.WriteString($navProperty.Type) - $xmlWriter.WriteEndElement() - } - } - - # Add UpdateRequestMethod to privateData - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'UpdateRequestMethod') - $xmlWriter.WriteString("$UpdateRequestMethod") - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - SaveCDXMLFooter $xmlWriter - - Write-Verbose ($LocalizedData.VerboseSavedCDXML -f $($associatedEntityType.Name), $Path) - } -} - -######################################################### -# Saves InstanceCmdlets node to CDXML -######################################################### -function SaveCDXMLInstanceCmdlets -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [ODataUtils.EntityTypeV4] $EntityType, - $keys, - $navigationProperties, - $CmdletAdapter, - [Hashtable] $complexTypeMapping, - [bool] $isSingleton - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLInstanceCmdlets") } - if($null -eq $Metadata) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLInstanceCmdlets") } - - $xmlWriter.WriteStartElement('InstanceCmdlets') - $xmlWriter.WriteStartElement('GetCmdletParameters') - # adding key parameters and association parameters to QueryableProperties, each in a different parameter set - # to be used by GET cmdlet - if (($keys.Length -gt 0) -or ($navigationProperties.Length -gt 0)) - { - $queryableNavProperties = @{} - - if ($isSingleton -eq $false) - { - foreach ($navProperty in $navigationProperties) - { - if ($null -ne $navProperty) - { - $associatedType = GetAssociatedType $Metadata $GlobalMetadata $navProperty - $associatedTypeKeyProperties = $associatedType.EntityProperties | Where-Object { $_.IsKey } - - # Make sure associated parameter (based on navigation property) has EntitySet or Singleton, which makes it accessible from the service root - # Otherwise the Uri for associated navigation property won't be valid - if ($associatedTypeKeyProperties.Length -gt 0 -and (ShouldBeAssociatedParameter $GlobalMetadata $EntityType $associatedType $isSingleton)) - { - $queryableNavProperties.Add($navProperty, $associatedTypeKeyProperties) - } - } - } - } - - $defaultCmdletParameterSet = 'Default' - if ($isSingleton -eq $true -and $queryableNavProperties.Count -gt 0) - { - foreach($item in $queryableNavProperties.GetEnumerator()) - { - $defaultCmdletParameterSet = $item.Key.Name - break - } - } - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', $defaultCmdletParameterSet) - - - $xmlWriter.WriteStartElement('QueryableProperties') - - $position = 0 - - if ($isSingleton -eq $false) - { - $keys | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', $_.Name) - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $_.Name) - $xmlWriter.WriteAttributeString('CmdletParameterSets', 'Default') - $xmlWriter.WriteAttributeString('IsMandatory', $_.IsMandatory.ToString().ToLower()) - $xmlWriter.WriteAttributeString('Position', $position) - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $position++ - } - } - - if ($queryableNavProperties.Count -gt 0) - { - foreach($item in $queryableNavProperties.GetEnumerator()) - { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', $item.Key.Name + ':' + $item.Value.Name + ':Key') - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $item.Value.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', 'Associated' + $item.Key.Name + $item.Value.Name) - $xmlWriter.WriteAttributeString('CmdletParameterSets', $item.Key.Name) - $xmlWriter.WriteAttributeString('IsMandatory', 'false') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - } - - if ($isSingleton -eq $false) - { - # Add Query Parameters (i.e., Top, Skip, OrderBy, Filter) to the generated Get-* cmdlets. - $queryParameters = - @{ - "Filter" = "Edm.String"; - "IncludeTotalResponseCount" = "switch"; - "OrderBy" = "Edm.String"; - "Select" = "Edm.String"; - "Skip" = "Edm.Int32"; - "Top" = "Edm.Int32"; - } - } - else - { - $queryParameters = - @{ - "Select" = "Edm.String"; - } - } - - foreach($currentQueryParameter in $queryParameters.Keys) - { - $xmlWriter.WriteStartElement('Property') - $xmlWriter.WriteAttributeString('PropertyName', "QueryOption:" + $currentQueryParameter) - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $queryParameters[$currentQueryParameter] - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('RegularQuery') - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $currentQueryParameter) - - if($queryParameters[$currentQueryParameter] -eq "Edm.String") - { - $xmlWriter.WriteStartElement('ValidateNotNullOrEmpty') - $xmlWriter.WriteEndElement() - } - - if($queryParameters[$currentQueryParameter] -eq "Edm.Int32") - { - $xmlWriter.WriteStartElement('ValidateRange') - $xmlWriter.WriteAttributeString('Min', "1") - $xmlWriter.WriteAttributeString('Max', [int]::MaxValue) - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('GetCmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Get') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() -} - -# Helper Method -# Returns true if navigation property of $AssociatedType has corresponding EntitySet or Singleton -# If yes, then it should become an associated parameter in CDXML -function ShouldBeAssociatedParameter -{ - param - ( - [System.Collections.ArrayList] $GlobalMetadata, - [ODataUtils.EntityTypeV4] $EntityType, - [ODataUtils.EntityTypeV4] $AssociatedType - ) - - # Check if associated type has navigation property, which links back to current type - $associatedTypeNavProperties = $AssociatedType.NavigationProperties | Where-Object { - $_.Type -eq ($EntityType.Namespace + "." + $EntityType.Name) -or - $_.Type -eq ($EntityType.Alias + "." + $EntityType.Name) -or - $_.Type -eq ("Collection(" + $EntityType.Namespace + "." + $EntityType.Name + ")") -or - $_.Type -eq ("Collection(" + $EntityType.Alias + "." + $EntityType.Name + ")") - } - - if ($associatedTypeNavProperties.Length -lt 1) - { - return $false - } - - # Now check if associated parameter type (i.e, type of navigation property) has corresponding EntitySet or Singleton, - # which makes it accessible from the service root. - # Otherwise the Uri for associated navigation property won't be valid - foreach ($currentMetadata in $GlobalMetadata) - { - # Look for EntitySet with given type - foreach ($currentEntitySet in $currentMetadata.EntitySets) - { - if ($currentEntitySet.Type.Namespace -eq $EntityType.Namespace -and - $currentEntitySet.Type.Name -eq $EntityType.Name) - { - return $true - } - } - - # Look for Singleton with given type - foreach ($currentSingleton in $currentMetadata.Singletons) - { - if ($currentSingleton.Type.Namespace -eq $EntityType.Namespace -and - $currentSingleton.Type.Name -eq $EntityType.Name) - { - return $true - } - } - } - - return $false -} - -######################################################### -# Saves NewCmdlet node to CDXML -######################################################### -function SaveCDXMLNewCmdlet -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - $keyProperties, - $nonNullableProperties, - $nullableProperties, - $navigationProperties, - $CmdletAdapter, - $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLNewCmdlet") } - if($null -eq $Metadata) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLNewCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'New') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', 'Create') - $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') - - AddParametersNode $xmlWriter $keyProperties $nonNullableProperties $nullableProperties $null $true $true $complexTypeMapping - - $xmlWriter.WriteEndElement() - - $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ - $associatedEntitySet = GetEntitySetForEntityType $Metadata $associatedType - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Association:Create:$($associatedEntitySet.Name)") - $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) - - $associatedKeys = ($associatedType.EntityProperties | Where-Object { $_.isKey }) - - AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping - - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteEndElement() -} - -######################################################### -# Get corresponding EntityType for given EntitySet -######################################################### -function GetEntitySetForEntityType { - param( - [ODataUtils.MetadataV4] $Metadata, - [ODataUtils.EntityTypeV4] $entityType - ) - - if($null -eq $entityType) { throw ($LocalizedData.ArguementNullError -f "EntityType", "GetEntitySetForEntityType") } - - $result = $Metadata.EntitySets | Where-Object { ($_.Type.Namespace -eq $entityType.Namespace) -and ($_.Type.Name -eq $entityType.Name) } - - if (($result.Count -eq 0) -and ($null -ne $entityType.BaseType)) - { - GetEntitySetForEntityType $Metadata $entityType.BaseType - } - elseif ($result.Count -gt 1) - { - throw ($LocalizedData.WrongCountEntitySet -f (($entityType.Namespace + "." + $entityType.Name), $result.Count)) - } - - $result -} - -######################################################### -# Saves RemoveCmdlet node to CDXML -######################################################### -function SaveCDXMLRemoveCmdlet -{ - param - ( - [System.XMl.XmlTextWriter] $xmlWriter, - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - $keyProperties, - $navigationProperties, - $CmdletAdapter, - $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLRemoveCmdlet") } - if($null -eq $Metadata) { throw ($LocalizedData.ArguementNullError -f "Metadata", "SaveCDXMLRemoveCmdlet") } - - $xmlWriter.WriteStartElement('Cmdlet') - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Remove') - $xmlWriter.WriteAttributeString('DefaultCmdletParameterSet', 'Default') - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', 'Delete') - $xmlWriter.WriteAttributeString('CmdletParameterSet', 'Default') - - AddParametersNode $xmlWriter $keyProperties $nul $null $null $true $true $complexTypeMapping - - $xmlWriter.WriteEndElement() - - $navigationProperties | Where-Object { $null -ne $_ } | ForEach-Object { - - $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ - $associatedEntitySet = GetEntitySetForEntityType $Metadata $associatedType - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Association:Delete:$($associatedEntitySet.Name)") - $xmlWriter.WriteAttributeString('CmdletParameterSet', $_.Name) - - $associatedType = GetAssociatedType $Metadata $GlobalMetadata $_ - $associatedKeys = ($associatedType.EntityProperties | Where-Object { $_.isKey }) - - AddParametersNode $xmlWriter $associatedKeys $keyProperties $null "Associated$($_.Name)" $true $true $complexTypeMapping - - $xmlWriter.WriteEndElement() - } - $xmlWriter.WriteEndElement() -} - -######################################################### -# Gets associated instance's EntityType for a given navigation property -######################################################### -function GetAssociatedType { - param( - [ODataUtils.MetadataV4] $Metadata, - [System.Collections.ArrayList] $GlobalMetadata, - [ODataUtils.NavigationPropertyV4] $navProperty - ) - - $associationType = $navProperty.Type - $associationType = $associationType.Replace($Metadata.Namespace + ".", "") - $associationType = $associationType.Replace($Metadata.Alias + ".", "") - $associationType = $associationType.Replace("Collection(", "") - $associationType = $associationType.Replace(")", "") - - $associatedType = $Metadata.EntityTypes | Where-Object { $_.Name -eq $associationType } - - if (!$associatedType -and $null -ne $GlobalMetadata) - { - $associationFullTypeName = $navProperty.Type.Replace("Collection(", "").Replace(")", "") - - foreach ($referencedMetadata in $GlobalMetadata) - { - if ($null -ne ($associatedType = $referencedMetadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName }) -or - $null -ne ($associatedType = $referencedMetadata.ComplexTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName }) -or - $null -ne ($associatedType = $referencedMetadata.EnumTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $associationFullTypeName -or $_.Alias + "." + $_.Name -eq $associationFullTypeName })) - { - # Found associated class - break - } - } - } - - if ($associatedType.Count -lt 1) - { - throw ($LocalizedData.AssociationNotFound -f $associationType) - } - elseif ($associatedType.Count -gt 1) - { - throw ($LocalizedData.TooManyMatchingAssociationTypes -f $associatedType.Count, $associationType) - } - - # return associated EntityType - $associatedType -} - -######################################################### -# Saves CDXML for Instance/Service level actions -######################################################### -function SaveCDXMLAction -{ - param - ( - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.ActionV4] $action, - [AllowEmptyString()] - [string] $noun, - [bool] $isInstanceAction, - [ODataUtils.TypeProperty] $keys, - [Hashtable] $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLAction") } - if($null -eq $action) { throw ($LocalizedData.ArguementNullError -f "action", "SaveCDXMLAction") } - - $xmlWriter.WriteStartElement('Cmdlet') - - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Invoke') - $xmlWriter.WriteAttributeString('Noun', "$($noun)$($action.Name)") - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - $xmlWriter.WriteAttributeString('MethodName', "Action:$($action.Name):$($action.EntitySet.Name)") - - $xmlWriter.WriteStartElement('Parameters') - - $keys | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key') - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $_.Name) - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - $i = -1 - foreach ($parameter in $action.Parameters) - { - $i++ - - # for Instance actions, first parameter is Entity Set which we refer to using keys - if ($isInstanceAction -and ($i -eq 0)) - { - continue - } - - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name) - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $parameter.TypeName - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $parameter.Name) - if (-not $parameter.IsNullable) - { - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - # Add -Force parameter to Service Action cmdlets. - AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping - - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - $xmlWriter -} - -######################################################### -# Saves CDXML for Instance/Service level functions -######################################################### -function SaveCDXMLFunction -{ - param - ( - [System.Xml.XmlWriter] $xmlWriter, - [ODataUtils.FunctionV4] $function, - [AllowEmptyString()] - [string] $noun, - [bool] $isInstanceAction, - [ODataUtils.TypeProperty] $keys, - [Hashtable] $complexTypeMapping - ) - - if($null -eq $xmlWriter) { throw ($LocalizedData.ArguementNullError -f "xmlWriter", "SaveCDXMLFunction") } - if($null -eq $function) { throw ($LocalizedData.ArguementNullError -f "function", "SaveCDXMLFunction") } - - $xmlWriter.WriteStartElement('Cmdlet') - - $xmlWriter.WriteStartElement('CmdletMetadata') - $xmlWriter.WriteAttributeString('Verb', 'Invoke') - $xmlWriter.WriteAttributeString('Noun', "$($noun)$($function.Name)") - $xmlWriter.WriteAttributeString('ConfirmImpact', 'Medium') - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Method') - if (!$function.EntitySet) - { - $xmlWriter.WriteAttributeString('MethodName', "Action:$($function.Name):$($function.ReturnType)") - } - else - { - $xmlWriter.WriteAttributeString('MethodName', "Action:$($function.Name):$($function.EntitySet)") - } - - $xmlWriter.WriteStartElement('Parameters') - - $keys | Where-Object { $null -ne $_ } | ForEach-Object { - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $_.Name + ':Key') - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $_.TypeName $complexTypeMapping - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $_.Name) - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - $i = -1 - foreach ($parameter in $function.Parameters) - { - $i++ - - # for Instance actions, first parameter is Entity Set which we refer to using keys - if ($isInstanceAction -and ($i -eq 0)) - { - continue - } - - $xmlWriter.WriteStartElement('Parameter') - $xmlWriter.WriteAttributeString('ParameterName', $parameter.Name) - - $xmlWriter.WriteStartElement('Type') - $PSTypeName = Convert-ODataTypeToCLRType $parameter.Type - $xmlWriter.WriteAttributeString('PSType', $PSTypeName) - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('CmdletParameterMetadata') - $xmlWriter.WriteAttributeString('PSName', $parameter.Name) - if (-not $parameter.IsNullable) - { - $xmlWriter.WriteAttributeString('IsMandatory', 'true') - } - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - } - - # Add -Force parameter to Service Function cmdlets. - AddParametersNode $xmlWriter $null $null $null $null $true $false $complexTypeMapping - - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteEndElement() - - $xmlWriter -} - -######################################################### -# Saves CDXML for Service-level actions and functions -######################################################### -function SaveServiceActionsCDXML -{ - param - ( - [System.Collections.ArrayList] $GlobalMetadata, - [ODataUtils.ODataEndpointProxyParameters] $ODataEndpointProxyParameters, - [string] $Path, - [Hashtable] $complexTypeMapping, - [string] $progressBarStatus, - [string] $CmdletAdapter - ) - - $xmlWriter = New-Object System.XMl.XmlTextWriter($Path,$Null) - - if ($null -eq $xmlWriter) - { - throw $LocalizedData.XmlWriterInitializationError -f "ServiceActions" - } - - $xmlWriter = SaveCDXMLHeader $xmlWriter $ODataEndpointProxyParameters.Uri 'ServiceActions' 'ServiceActions' -CmdletAdapter $CmdletAdapter - - $actions = @() - $functions = @() - - foreach ($Metadata in $GlobalMetadata) - { - $actions += $Metadata.Actions | Where-Object { $_.EntitySet -eq [string]::Empty -or $null -eq $_.EntitySet } - $functions += $Metadata.Functions | Where-Object { $_.EntitySet -eq [string]::Empty -or $null -eq $_.EntitySet} - } - - if ($actions.Length -gt 0 -or $functions.Length -gt 0) - { - $xmlWriter.WriteStartElement('StaticCmdlets') - } - - # Save actions - if ($actions.Length -gt 0) - { - foreach ($action in $actions) - { - if ($null -ne $action) - { - $xmlWriter = SaveCDXMLAction $xmlWriter $action '' $false $null $complexTypeMapping - } - } - } - - # Save functions - if ($functions.Length -gt 0) - { - foreach ($function in $functions) - { - if ($null -ne $function) - { - $xmlWriter = SaveCDXMLFunction $xmlWriter $function '' $false $null $complexTypeMapping - } - } - } - - if ($actions.Length -gt 0 -or $functions.Length -gt 0) - { - $xmlWriter.WriteEndElement() - } - - $xmlWriter.WriteStartElement('CmdletAdapterPrivateData') - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'Namespace') - $xmlWriter.WriteString("$($EntitySet.Namespace)") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'Alias') - if (!$EntitySet.Alias) - { - $xmlWriter.WriteString("") - } - else - { - $xmlWriter.WriteString("$($EntitySet.Alias)") - } - $xmlWriter.WriteEndElement() - - $xmlWriter.WriteStartElement('Data') - $xmlWriter.WriteAttributeString('Name', 'CreateRequestMethod') - $xmlWriter.WriteString("Post") - $xmlWriter.WriteEndElement() - $xmlWriter.WriteEndElement() - - SaveCDXMLFooter $xmlWriter - - Write-Verbose ($LocalizedData.VerboseSavedServiceActions -f $Path) - - # Write progress bar message - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 60 20 1 1 -} - -######################################################### -# GenerateModuleManifest is a helper function used -# to generate a wrapper module manifest file. The -# generated module manifest is persisted to the disk at -# the specified OutputModule path. When the module -# manifest is imported, the following commands will -# be imported: -# 1. Get, Set, New & Remove proxy cmdlets for entity -# sets and singletons. -# 2. If the server side Odata endpoint exposes complex -# types, enum types, type definitions, then the corresponding -# client side proxy types imported. -# 3. Service Action/Function proxy cmdlets. -######################################################### -function GenerateModuleManifest -{ - param - ( - [System.Collections.ArrayList] $GlobalMetadata, - [String] $ModulePath, - [string[]] $AdditionalModules, - [Hashtable] $resourceNameMappings, - [string] $progressBarStatus - ) - - if($null -eq $progressBarStatus) { throw ($LocalizedData.ArguementNullError -f "progressBarStatus", "GenerateModuleManifest") } - - $NestedModules = @() - - foreach ($Metadata in $GlobalMetadata) - { - foreach ($entitySet in $Metadata.EntitySets) - { - $entitySetName = $entitySet.Name - if(($null -ne $resourceNameMappings) -and - $resourceNameMappings.ContainsKey($entitySetName)) - { - $entitySetName = $resourceNameMappings[$entitySetName] - } - else - { - $entitySetName = $entitySet.Type.Name - } - - $NestedModules += "$OutputModule\$($entitySetName).cdxml" - } - - foreach ($singleton in $Metadata.SingletonTypes) - { - $singletonName = $singleton.Name - $NestedModules += "$OutputModule\$($singletonName)" + "Singleton" + ".cdxml" - } - } - - New-ModuleManifest -Path $ModulePath -NestedModules ($AdditionalModules + $NestedModules) - - Write-Verbose ($LocalizedData.VerboseSavedModuleManifest -f $ModulePath) - - # Update the Progress Bar. - ProgressBarHelper "Export-ODataEndpointProxy" $progressBarStatus 80 20 1 1 -} - -######################################################### -# This is a helper function used to generate complex -# type definition from the metadata. -######################################################### -function GenerateComplexTypeDefinition -{ - param - ( - [System.Collections.ArrayList] $GlobalMetadata, - [string] $OutputModule, - [string] $typeDefinationFileName, - $normalizedNamespaces - ) - - $Path = "$OutputModule\$typeDefinationFileName" - $date = Get-Date - - $output = @" -# This module was generated by PSODataUtils on $date. - -`$typeDefinitions = @" -using System; -using System.Management.Automation; -using System.ComponentModel; - -"@ - # We are currently generating classes for EntityType & ComplexType - # definition exposed in the metadata. - - $complexTypeMapping = @{} - - # First, create complex type mappings for all metadata files at once - foreach ($metadata in $GlobalMetadata) - { - $typesToBeGenerated = $metadata.EntityTypes+$metadata.ComplexTypes - $enumTypesToBeGenerated = $metadata.EnumTypes - $typeDefinitionsToBeGenerated = $metadata.TypeDefinitions - - foreach ($entityType in $typesToBeGenerated) - { - if ($null -ne $entityType) - { - $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name - if(!$complexTypeMapping.ContainsKey($entityTypeFullName)) - { - $complexTypeMapping.Add($entityTypeFullName, $entityType.Name) - } - - # In short name we use Alias instead of Namespace - # We will add short name to $complexTypeMapping to enable Alias based search - if ($null -ne $entityType.Alias -and $entityType.Alias -ne "") - { - $entityTypeShortName = $entityType.Alias + '.' + $entityType.Name - if(!$complexTypeMapping.ContainsKey($entityTypeShortName)) - { - $complexTypeMapping.Add($entityTypeShortName, $entityType.Name) - } - } - } - } - - foreach ($enumType in $enumTypesToBeGenerated) - { - if ($null -ne $enumType) - { - $enumTypeFullName = $enumType.Namespace + '.' + $enumType.Name - if(!$complexTypeMapping.ContainsKey($enumTypeFullName)) - { - $complexTypeMapping.Add($enumTypeFullName, $enumType.Name) - } - - if (($null -ne $enumType.Alias -and $enumType.Alias -ne "") -or ($null -ne $metadata.Alias -and $metadata.Alias -ne [string]::Empty)) - { - if ($null -ne $enumType.Alias -and $enumType.Alias -ne "") - { - $alias = $enumType.Alias - } - else - { - $alias = $metadata.Alias - } - - $enumTypeShortName = $alias + '.' + $enumType.Name - if(!$complexTypeMapping.ContainsKey($enumTypeShortName)) - { - $complexTypeMapping.Add($enumTypeShortName, $enumType.Name) - } - } - } - } - - foreach ($typeDefinition in $typeDefinitionsToBeGenerated) - { - if ($null -ne $typeDefinition) - { - $typeDefinitionFullName = $typeDefinition.Namespace + '.' + $typeDefinition.Name - if(!$complexTypeMapping.ContainsKey($typeDefinitionFullName)) - { - $complexTypeMapping.Add($typeDefinitionFullName, $typeDefinition.Name) - } - - # In short name we use Alias instead of Namespace - # We will add short name to $complexTypeMapping to enable Alias based search - if ($typeDefinition.Alias) - { - $typeDefinitionShortName = $typeDefinition.Alias + '.' + $typeDefinition.Name - if(!$complexTypeMapping.ContainsKey($typeDefinitionShortName)) - { - $complexTypeMapping.Add($typeDefinitionShortName, $typeDefinition.Name) - } - } - } - } - } - - # Now classes definitions will be generated - foreach ($metadata in $GlobalMetadata) - { - $typesToBeGenerated = $metadata.EntityTypes+$metadata.ComplexTypes - $enumTypesToBeGenerated = $metadata.EnumTypes - $typeDefinitionsToBeGenerated = $metadata.TypeDefinitions - - if($typesToBeGenerated.Count -gt 0 -or $enumTypesToBeGenerated.Count -gt 0) - { - if ($null -ne $metadata.Alias -and $metadata.Alias -ne [string]::Empty) - { - # Check if this namespace has to be normalized in the .Net namespace/class definitions file. - $dotNetAlias = GetNamespace $metadata.Alias $normalizedNamespaces - - $output += @" - -namespace $($dotNetAlias) -{ -"@ - } - else - { - # Check if this namespace has to be normalized in the .Net namespace/class definitions file. - $dotNetNamespace = GetNamespace $metadata.Namespace $normalizedNamespaces - - $output += @" - -namespace $($dotNetNamespace) -{ -"@ - } - - foreach ($typeDefinition in $typeDefinitionsToBeGenerated) - { - if ($null -ne $typeDefinition) - { - Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $typeDefinitionFullName, "$OutputModule\$typeDefinationFileName") - - $output += "`n public class $($typeDefinition.Name)`n {" - $typeName = Convert-ODataTypeToCLRType $typeDefinition.BaseTypeStr $complexTypeMapping - $dotNetPropertyNamespace = GetNamespace $typeName $normalizedNamespaces $true - $output += "`n public $dotNetPropertyNamespace value;" - $output += @" -`n } -"@ - } - } - - $DotNETKeywords = ("abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "partial", "remove", "select", "set", "value", "var", "where", "yield") - - foreach ($enumType in $enumTypesToBeGenerated) - { - if ($null -ne $enumType) - { - $enumTypeFullName = $enumType.Namespace + '.' + $enumType.Name - - Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $enumTypeFullName, "$OutputModule\$typeDefinationFileName") - - $output += "`n public enum $($enumType.Name)`n {" - - $properties = $null - - for($index = 0; $index -lt $enumType.Members.Count; $index++) - { - $memberName = $enumType.Members[$index].Name - $formattedMemberName = [System.Text.RegularExpressions.Regex]::Replace($memberName, "[^0-9a-zA-Z]", "_"); - $memberValue = $enumType.Members[$index].Value - - if ($DotNETKeywords -contains $formattedMemberName) - { - # If member name is a known keyword in .Net, add '@' prefix - $formattedMemberName = '@' + $formattedMemberName - } - - if ($formattedMemberName -match "^[0-9]*$") - { - # If member name is a numeric value, add 'm_' prefix - $formattedMemberName = 'm_' + $formattedMemberName - } - - if ($memberName -ne $formattedMemberName -or $formattedMemberName -like '@*' -or $formattedMemberName -like 'm_*') - { - # Add Description attribute to preserve original value - $properties += "`n [Description(`"$($memberName)`")]$formattedMemberName" - } - else - { - $properties += "`n $memberName" - } - - if ($memberValue) - { - $properties += " = $memberValue," - } - else - { - $properties += "," - } - } - - $output += $properties - $output += @" -`n } -"@ - } - } - - foreach ($entityType in $typesToBeGenerated) - { - if ($null -ne $entityType) - { - $entityTypeFullName = $entityType.Namespace + '.' + $entityType.Name - - Write-Verbose ($LocalizedData.VerboseAddingTypeDefinationToGeneratedModule -f $entityTypeFullName, "$OutputModule\$typeDefinationFileName") - - if ($null -ne $entityType.BaseTypeStr -and $entityType.BaseTypeStr -ne '' -and $null -eq $entityType.BaseType) - { - # This class inherits from another class, but we were not able to find base class during Parsing. - # We'll make another attempt. - foreach ($referencedMetadata in $GlobalMetadata) - { - if ($null -ne ($baseType = $referencedMetadata.EntityTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $entityType.BaseTypeStr -or $_.Alias + "." + $_.Name -eq $entityType.BaseTypeStr }) -or - $null -ne ($baseType = $referencedMetadata.ComplexTypes | Where-Object { $_.Namespace + "." + $_.Name -eq $entityType.BaseTypeStr -or $_.Alias + "." + $_.Name -eq $entityType.BaseTypeStr })) - { - # Found base class - $entityType.BaseType = $baseType - break - } - } - } - - if($null -ne $entityType.BaseType) - { - if ((![string]::IsNullOrEmpty($entityType.BaseType.Alias) -and $entityType.BaseType.Alias -eq $entityType.Alias) -or - (![string]::IsNullOrEmpty($entityType.BaseType.Namespace) -and $entityType.BaseType.Namespace -eq $entityType.Namespace)) - { - $fullBaseTypeName = $entityType.BaseType.Name - } - else - { - # Base type can be defined in different namespace. For that reason we include namespace or alias. - if (![string]::IsNullOrEmpty($entityType.BaseType.Alias)) - { - # Check if derived alias has to be normalized. - $normalizedDotNetAlias = GetNamespace $entityType.BaseType.Alias $normalizedNamespaces - $fullBaseTypeName = $normalizedDotNetAlias + "." + $entityType.BaseType.Name - } - else - { - # Check if derived namespace has to be normalized. - $normalizedDotNetNamespace = GetNamespace $entityType.BaseType.Namespace $normalizedNamespaces - $fullBaseTypeName = $normalizedDotNetNamespace + "." + $entityType.BaseType.Name - } - } - - $output += "`n public class $($entityType.Name) : $($fullBaseTypeName)`n {" - } - else - { - $output += "`n public class $($entityType.Name)`n {" - } - - $properties = $null - - for($index = 0; $index -lt $entityType.EntityProperties.Count; $index++) - { - $property = $entityType.EntityProperties[$index] - $typeName = Convert-ODataTypeToCLRType $property.TypeName $complexTypeMapping - - if ($typeName.StartsWith($metadata.Namespace + ".")) - { - $dotNetPropertyNamespace = $typeName.Replace($metadata.Namespace + ".", "") - } - elseif ($typeName.StartsWith($metadata.Alias + ".")) - { - $dotNetPropertyNamespace = $typeName.Replace($metadata.Alias + ".", "") - } - else - { - $dotNetPropertyNamespace = GetNamespace $typeName $normalizedNamespaces $true - } - - $properties += "`n public $dotNetPropertyNamespace $($property.Name);" - } - - $output += $properties - $output += @" -`n } -"@ - } - } - - $output += "`n}`n" - } - } - $output += """@`n" - $output += "Add-Type -TypeDefinition `$typeDefinitions -IgnoreWarnings`n" - $output | Out-File -FilePath $Path - Write-Verbose ($LocalizedData.VerboseSavedTypeDefinationModule -f $typeDefinationFileName, $OutputModule) - - return $complexTypeMapping -} \ No newline at end of file diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 deleted file mode 100644 index 971b8333595..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.ODataUtils/en-US/Microsoft.PowerShell.ODataUtilsStrings.psd1 +++ /dev/null @@ -1,55 +0,0 @@ -# Localized PSODataUtils.psd1 - -ConvertFrom-StringData @' -###PSLOC -SelectedAdapter=Dot sourcing '{0}'. -ArchitectureNotSupported=This module is not supported on your processor architecture ({0}). -ArguementNullError=Failed to generate proxy as '{0}' is pointing to $null in '{1}'. -EmptyMetadata=Read metadata was empty. Url: {0}. -InvalidEndpointAddress=Invalid endpoint address ({0}). Web response with status code '{1}' was obtained while accessing this endpoint address. -NoEntitySets=Metadata from URI '{0}' does not contain Entity Sets. No output will be written. -NoEntityTypes=Metadata from URI '{0}' does not contain Entity Types. No output will be written. -MetadataUriDoesNotExist=Metadata specified at the URI '{0}' does not exist. No output will be written. -InValidIdentifierInMetadata=Metadata specified at URI '{0}' contains an invalid Identifier '{1}'. Only valid C# identifiers are supported in the generated complex types during the proxy creation. -InValidMetadata=Failed to process metadata specified at URI '{0}'. No output will be written. -InValidXmlInMetadata=Metadata specified at URI '{0}' contains an invalid XML. No output will be written. -ODataVersionNotFound=Metadata specified at URI '{0}' does not contain the OData Version. No output will be written. -ODataVersionNotSupported=The OData version '{0}' specified in the metadata located at the URI '{1}' is not supported. Only OData versions between '{2}' and '{3}' are supported by '{4}' during proxy generation. No output will be written. -InValidSchemaNamespace=Metadata specified at URI '{0}' is invalid. NULL or Empty values are not supported for Namespace attribute in the schema. -InValidSchemaNamespaceConflictWithClassName=Metadata specified at URI '{0}' contains invalid Namespace {1} name, which conflicts with another type name. To avoid compilation error {1} will be changed to {2}. -InValidSchemaNamespaceContainsInvalidChars=Metadata specified at URI '{0}' contains invalid Namespace name {1} with a combination of dots and numbers in it, which is not allowed in .Net. To avoid compilation error {1} will be changed to {2}. -InValidUri=URI '{0}' is invalid. No output will be written. -RedfishNotEnabled=This version of Microsoft.PowerShell.ODataUtils doesn’t support Redfish, please run: ‘update-module Microsoft.PowerShell.ODataUtils’ to get Redfish support. -EntitySetUndefinedType=Metadata from URI '{0}' does not contain the Type for Entity Set '{1}'. No output will be written. -XmlWriterInitializationError=There was an error initiating XmlWriter for writing the {0} CDXML module. -EmptySchema=Edmx.DataServices.Schema node should not be null. -VerboseReadingMetadata=Reading metadata from uri {0}. -VerboseParsingMetadata=Parsing metadata... -VerboseVerifyingMetadata=Verifying metadata... -VerboseSavingModule=Saving output module to path {0}. -VerboseSavedCDXML=Saved CDXML module for {0} to {1}. -VerboseSavedServiceActions=Saved Service Actions CDXML module for to {0}. -VerboseSavedModuleManifest=Saved module manifest at {0}. -AssociationNotFound=Association {0} not found in Metadata.Associations. -TooManyMatchingAssociationTypes=Found {0} {1} associations in Metadata.Associations. Expected only one. -ZeroMatchingAssociationTypes=Navigation property {0} not found on association {1}. -WrongCountEntitySet=Expected one EntitySet for EntityType {0}, but got {1}. -EntityNameConflictError=Proxy creation is not supported when multiple EntitySets are mapped to the same EntityType. The metadata located at the URI '{0}' contains EntitySets '{1}' and '{2}' that are mapped to the same EntityType '{3}'. -VerboseSavedTypeDefinationModule=Saved Type definition module '{0}' at '{1}'. -VerboseAddingTypeDefinationToGeneratedModule=Adding Type definition for '{0}' to '{1}' module. -OutputPathNotFound=Could not find a part of the path '{0}'. -ModuleAlreadyExistsAndForceParameterIsNotSpecified=The directory '{0}' already exists. Use the -Force parameter if you want to overwrite the directory and files within the directory. -InvalidOutputModulePath=Path '{0}' specified to -OutputModule parameter does not contain the module name. -OutputModulePathIsNotUnique=Path '{0}' specified to -OutputModule parameter resolves to multiple paths in the file system. Provide a unique file system path to -OutputModule parameter. -OutputModulePathIsNotFileSystemPath=Path '{0}' specified to -OutputModule parameter is not a file system. Provide a unique file system path to -OutputModule parameter. -SkipEntitySetProxyCreation=CDXML module creation has been skipped for the Entity Set '{0}' because its Entity Type '{1}' contains a property '{2}' that collides with one of the default properties of the generated cmdlets. -EntitySetProxyCreationWithWarning=CDXML module creation for the Entity Set '{0}' succeeded but contains a property '{1}' in the Entity Type '{2}' that collides with one of the default properties of the generated cmdlets. -SkipEntitySetConflictCommandCreation=CDXML module creation has been skipped for the Entity Set '{0}' because the exported command '{1}' conflicts with the inbox command. -EntitySetConflictCommandCreationWithWarning=CDXML module creation for the Entity Set '{0}' succeeded but contains a command '{1}' that collides with the inbox command. -SkipConflictServiceActionCommandCreation=CDXML module creation has been skipped for the Service Action '{0}' because the exported command '{1}' conflicts with the inbox command. -ConflictServiceActionCommandCreationWithWarning=CDXML module creation for the Service Action '{0}' succeeded but contains a command '{1}' that collides with the inbox command. -AllowUnsecureConnectionMessage=The cmdlet '{0}' is trying to establish an Unsecure connection with the OData endpoint through the URI '{1}'. Either supply a secure URI to the -{2} parameter or use -AllowUnsecureConnection switch parameter if you intend to use the current URI. -ProgressBarMessage=Creating proxy for the OData endpoint at the URI '{0}'. -###PSLOC - -'@ \ No newline at end of file diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows-Full/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 deleted file mode 100644 index 4a48f4ec1e7..00000000000 --- a/src/Modules/Windows-Full/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ /dev/null @@ -1,32 +0,0 @@ -@{ -GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="3.1.0.0" -PowerShellVersion="3.0" -CLRVersion="4.0" -CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", - "Out-File", "Out-Printer", "Out-String", - "Out-GridView", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", - "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", - "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", "New-Guid", - "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", - "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", - "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", - "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", - "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", - "Start-Sleep", "Tee-Object", "Measure-Command", "Update-List", "Update-TypeData", "Update-FormatData", - "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", - "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", - "Clear-Variable", "Export-Clixml", "Import-Clixml", "Import-PowerShellDataFile", "ConvertTo-Xml", "Select-Xml", "Write-Debug", - "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", "Get-PSBreakpoint", - "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", - "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Show-Command", "Unblock-File", "Get-FileHash", - "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", "Get-RunspaceDebug", "Wait-Debugger", - "ConvertFrom-String", "Convert-String" , "Get-Uptime", "New-TemporaryFile", "Get-Verb", "Format-Hex" -FunctionsToExport= "ConvertFrom-SddlString" -AliasesToExport= "CFS", "fhx" -NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' -} diff --git a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.Format.ps1xml b/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.Format.ps1xml deleted file mode 100644 index 09820341fbe..00000000000 --- a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.Format.ps1xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - ScheduledJobTrigger - - Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger - - - - - - 10 - left - - - - 15 - left - - - - 22 - left - - - - 23 - left - - - - 10 - left - - - - - - - Id - - - Frequency - - - At - - - DaysOfWeek - - - Enabled - - - - - - - - ScheduledJobDefinition - - Microsoft.PowerShell.ScheduledJob.ScheduledJobDefinition - - - - - - 10 - left - - - - 15 - left - - - - 15 - left - - - - 40 - left - - - - 10 - left - - - - - - - Id - - - Name - - - $_.JobTriggers.Count - - - Command - - - Enabled - - - - - - - - diff --git a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.psd1 b/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.psd1 deleted file mode 100644 index a8ed0ab830d..00000000000 --- a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.psd1 +++ /dev/null @@ -1,33 +0,0 @@ -@{ - -ModuleToProcess = 'Microsoft.PowerShell.ScheduledJob.dll' - -ModuleVersion = '1.1.0.0' - -GUID = '50cdb55f-5ab7-489f-9e94-4ec21ff51e59' - -Author = 'Microsoft Corporation' - -CompanyName = 'Microsoft Corporation' - -Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.' - -PowerShellVersion = '3.0' - -CLRVersion = '4.0' - -TypesToProcess = 'PSScheduledJob.types.ps1xml' - -FormatsToProcess="PSScheduledJob.Format.ps1xml" - -CmdletsToExport = 'New-JobTrigger', 'Add-JobTrigger', 'Remove-JobTrigger', - 'Get-JobTrigger', 'Set-JobTrigger', 'Enable-JobTrigger', - 'Disable-JobTrigger', 'New-ScheduledJobOption', 'Get-ScheduledJobOption', - 'Set-ScheduledJobOption', 'Register-ScheduledJob', 'Get-ScheduledJob', - 'Set-ScheduledJob', 'Unregister-ScheduledJob', 'Enable-ScheduledJob', - 'Disable-ScheduledJob' -AliasesToExport = @() -FunctionsToExport = @() - -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=390816' -} diff --git a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.types.ps1xml b/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.types.ps1xml deleted file mode 100644 index f8822f2c9d6..00000000000 --- a/src/Modules/Windows-Full/PSScheduledJob/PSScheduledJob.types.ps1xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Microsoft.Management.Infrastructure.CimInstance - - Microsoft.PowerShell.ScheduledJob.JobTriggerToCimInstanceConverter, Microsoft.PowerShell.ScheduledJob, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 - - - diff --git a/src/Modules/Windows-Core+Full/CimCmdlets/CimCmdlets.psd1 b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 similarity index 78% rename from src/Modules/Windows-Core+Full/CimCmdlets/CimCmdlets.psd1 rename to src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 index 049f89c7d10..734fe45016d 100644 --- a/src/Modules/Windows-Core+Full/CimCmdlets/CimCmdlets.psd1 +++ b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 @@ -1,18 +1,18 @@ @{ GUID="{Fb6cc51d-c096-4b38-b78d-0fed6277096a}" -Author="Microsoft Corporation" +Author="PowerShell" CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="1.0.0.0" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") PowerShellVersion="3.0" -CLRVersion="4.0" RootModule="Microsoft.Management.Infrastructure.CimCmdlets" RequiredAssemblies="Microsoft.Management.Infrastructure.CimCmdlets.dll","Microsoft.Management.Infrastructure.Dll" +FunctionsToExport = @() CmdletsToExport= "Get-CimAssociatedInstance", "Get-CimClass", "Get-CimInstance", "Get-CimSession", "Invoke-CimMethod", "New-CimInstance","New-CimSession","New-CimSessionOption","Register-CimIndicationEvent","Remove-CimInstance", "Remove-CimSession","Set-CimInstance", "Export-BinaryMiLog","Import-BinaryMiLog" AliasesToExport = "gcim","scim","ncim", "rcim","icim","gcai","rcie","ncms","rcms","gcms","ncso","gcls" -FunctionsToExport = @() -HelpInfoUri="https://go.microsoft.com/fwlink/?linkid=855946" +HelpInfoUri="https://aka.ms/powershell75-help" } diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml similarity index 100% rename from src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml rename to src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml similarity index 100% rename from src/Modules/Windows-Full/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml rename to src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml diff --git a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml new file mode 100644 index 00000000000..e63a9b56d94 --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml @@ -0,0 +1,139 @@ + + + + + + System.Diagnostics.Eventing.Reader.EventLogConfiguration + + + PSStandardMembers + + + DefaultDisplayPropertySet + + LogName + MaximumSizeInBytes + RecordCount + LogMode + + + + + + + + System.Diagnostics.Eventing.Reader.EventLogRecord + + + PSStandardMembers + + + DefaultDisplayPropertySet + + TimeCreated + ProviderName + Id + Message + + + + + + + + System.Diagnostics.Eventing.Reader.ProviderMetadata + + + ProviderName + Name + + + PSStandardMembers + + + DefaultDisplayPropertySet + + Name + LogLinks + + + + + + + + Microsoft.PowerShell.Commands.GetCounter.CounterSet + + + Counter + Paths + + + + + Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample + + + PSStandardMembers + + + DefaultDisplayPropertySet + + Path + InstanceName + CookedValue + + + + + + + + Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet + + + PSStandardMembers + + + DefaultDisplayPropertySet + + Timestamp + Readings + + + + + + + + Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet + + + Readings + + $strPaths = "" + foreach ($ctr in $this.CounterSamples) + { + $strPaths += ($ctr.Path + " :" + "`n") + $strPaths += ($ctr.CookedValue.ToString() + "`n`n") + } + return $strPaths + + + + + diff --git a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 new file mode 100644 index 00000000000..7f77777b137 --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 @@ -0,0 +1,16 @@ +@{ +GUID="CA046F10-CA64-4740-8FF9-2565DBA61A4F" +Author="PowerShell" +CompanyName="Microsoft Corporation" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion="3.0" +FunctionsToExport = @() +CmdletsToExport="Get-WinEvent", "New-WinEvent", "Get-Counter" +AliasesToExport = @() +NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" +TypesToProcess="GetEvent.types.ps1xml" +FormatsToProcess="Event.format.ps1xml", "Diagnostics.format.ps1xml" +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 new file mode 100644 index 00000000000..f7582920935 --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -0,0 +1,74 @@ +@{ +GUID="EEFCB906-B326-4E99-9F54-8B4BB6EF3C6D" +Author="PowerShell" +CompanyName="Microsoft Corporation" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion="3.0" +NestedModules="Microsoft.PowerShell.Commands.Management.dll" +HelpInfoURI = 'https://aka.ms/powershell75-help' +FunctionsToExport = @() +AliasesToExport = @("gcb", "gin", "gtz", "scb", "stz") +CmdletsToExport=@("Add-Content", + "Clear-Content", + "Get-Clipboard", + "Set-Clipboard", + "Clear-ItemProperty", + "Join-Path", + "Convert-Path", + "Copy-ItemProperty", + "Get-ChildItem", + "Get-Content", + "Get-ItemProperty", + "Get-ItemPropertyValue", + "Move-ItemProperty", + "Get-Location", + "Set-Location", + "Push-Location", + "Pop-Location", + "New-PSDrive", + "Remove-PSDrive", + "Get-PSDrive", + "Get-Item", + "New-Item", + "Set-Item", + "Remove-Item", + "Move-Item", + "Rename-Item", + "Copy-Item", + "Clear-Item", + "Invoke-Item", + "Get-PSProvider", + "New-ItemProperty", + "Split-Path", + "Test-Path", + "Test-Connection", + "Get-Process", + "Stop-Process", + "Wait-Process", + "Debug-Process", + "Start-Process", + "Remove-ItemProperty", + "Rename-ItemProperty", + "Resolve-Path", + "Get-Service", + "Stop-Service", + "Start-Service", + "Suspend-Service", + "Resume-Service", + "Restart-Service", + "Set-Service", + "New-Service", + "Remove-Service", + "Set-Content", + "Set-ItemProperty", + "Restart-Computer", + "Stop-Computer", + "Rename-Computer", + "Get-ComputerInfo", + "Get-TimeZone", + "Set-TimeZone", + "Get-HotFix", + "Clear-RecycleBin") +} diff --git a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 new file mode 100644 index 00000000000..0953b2d1cca --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -0,0 +1,18 @@ +@{ +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" +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" +AliasesToExport = @() +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 new file mode 100644 index 00000000000..2043543a8a5 --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -0,0 +1,33 @@ +@{ +GUID = "1DA87E53-152B-403E-98DC-74D7B4D63D59" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation." +ModuleVersion = "7.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion = "3.0" +CmdletsToExport = @( + 'Export-Alias', 'Get-Alias', 'Import-Alias', 'New-Alias', 'Remove-Alias', 'Set-Alias', 'Export-Clixml', 'Import-Clixml', + 'Measure-Command', 'Trace-Command', 'ConvertFrom-Csv', 'ConvertTo-Csv', 'Export-Csv', 'Import-Csv', 'Get-Culture', + 'Format-Custom', 'Get-Date', 'Set-Date', 'Write-Debug', 'Wait-Debugger', 'Register-EngineEvent', 'Write-Error', + 'Get-Event', 'New-Event', 'Remove-Event', 'Unregister-Event', 'Wait-Event', 'Get-EventSubscriber', 'Invoke-Expression', + 'Out-File', 'Unblock-File', 'Get-FileHash', 'Export-FormatData', 'Get-FormatData', 'Update-FormatData', 'New-Guid', + 'Format-Hex', 'Get-Host', 'Read-Host', 'Write-Host', 'ConvertTo-Html', 'Write-Information', 'ConvertFrom-Json', + 'ConvertTo-Json', 'Test-Json', 'Format-List', 'Import-LocalizedData', 'Send-MailMessage', 'ConvertFrom-Markdown', + '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', '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', 'ConvertTo-CliXml', 'ConvertFrom-CliXml' +) +FunctionsToExport = @() +AliasesToExport = @('fhx') +NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") +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 new file mode 100644 index 00000000000..ced706c9fde --- /dev/null +++ b/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 @@ -0,0 +1,15 @@ +@{ +GUID="766204A6-330E-4263-A7AB-46C87AFC366C" +Author="PowerShell" +CompanyName="Microsoft Corporation" +Copyright="Copyright (c) Microsoft Corporation." +ModuleVersion="7.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion="3.0" +FunctionsToExport = @() +CmdletsToExport="Disable-WSManCredSSP", "Enable-WSManCredSSP", "Get-WSManCredSSP", "Set-WSManQuickConfig", "Test-WSMan", "Invoke-WSManAction", "Connect-WSMan", "Disconnect-WSMan", "Get-WSManInstance", "Set-WSManInstance", "Remove-WSManInstance", "New-WSManInstance", "New-WSManSessionOption" +AliasesToExport = @() +NestedModules="Microsoft.WSMan.Management.dll" +FormatsToProcess="WSMan.format.ps1xml" +HelpInfoURI = 'https://aka.ms/powershell75-help' +} diff --git a/src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/WSMan.format.ps1xml b/src/Modules/Windows/Microsoft.WSMan.Management/WSMan.format.ps1xml similarity index 97% rename from src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/WSMan.format.ps1xml rename to src/Modules/Windows/Microsoft.WSMan.Management/WSMan.format.ps1xml index 246a7a307dc..bbee94971d5 100644 --- a/src/Modules/Windows-Core+Full/Microsoft.WSMan.Management/WSMan.format.ps1xml +++ b/src/Modules/Windows/Microsoft.WSMan.Management/WSMan.format.ps1xml @@ -1,11 +1,11 @@ + - + + + + + + + + + - 64 - - - - true - - 1048985600 - - + + + + + + + + + + + + + + @@ -4691,7 +4869,7 @@ /> - + @@ -5385,7 +5567,11 @@ id="PS_PROVIDER.keyword.K_PSWORKFLOW.message" value="PSWorkflow Hosting And Execution Layer" /> - + @@ -5449,6 +5635,26 @@ id="PS_PROVIDER.task.T_ScheduledJob.message" value="PowerShell Scheduled Jobs" /> + + + + + - + @@ -5611,23 +5821,47 @@ /> + + + + + + diff --git a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.nuspec b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.nuspec deleted file mode 100644 index 3d27acf63b8..00000000000 --- a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.nuspec +++ /dev/null @@ -1,12 +0,0 @@ - - - - PowerShell.Core.Instrumentation - 6.0.0-beta.10 - Microsoft - Microsoft - false - PowerShell Core ETW resource binary - (c) Microsoft Corporation. All rights reserved. - - diff --git a/src/PowerShell.Core.Instrumentation/README.md b/src/PowerShell.Core.Instrumentation/README.md new file mode 100644 index 00000000000..4701116036b --- /dev/null +++ b/src/PowerShell.Core.Instrumentation/README.md @@ -0,0 +1,7 @@ +# PowerShell.Core.Instrumentation + +The `PowerShell.Core.Instrumentation.man` has actually been migrated to [PowerShell-Native](https://github.com/PowerShell/PowerShell-Native/tree/master/src/PowerShell.Core.Instrumentation) repository. +The corresponding manifest resource DLL `PowerShell.Core.Instrumentation.dll` is now produced in the release build of `PowerShell-Native` and is shipped in the `Microsoft.PowerShell.Native` NuGet package. + +However, we still need to keep `PowerShell.Core.Instrumentation.man` in the PowerShell repository because the PowerShell packages for Windows ship the manifest file and `RegisterManifest.ps1` for users to register the manifest resource DLL if they want to. +Therefore, when making changes to `PowerShell.Core.Instrumentation.man`, make sure your changes are made to both repositories -- **this file needs to be kept in sync between those 2 repositories**. diff --git a/src/PowerShell.Core.Instrumentation/RegisterManifest.ps1 b/src/PowerShell.Core.Instrumentation/RegisterManifest.ps1 index 4c5246ff53a..992c7b1c159 100644 --- a/src/PowerShell.Core.Instrumentation/RegisterManifest.ps1 +++ b/src/PowerShell.Core.Instrumentation/RegisterManifest.ps1 @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + <# .Synopsis Registers or unregisters the PowerShell ETW manifest @@ -21,7 +24,7 @@ param [switch] $Unregister ) -Set-StrictMode -Version Latest +Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' function Start-NativeExecution([scriptblock]$sb, [switch]$IgnoreExitcode) diff --git a/src/PowerShell.Core.Instrumentation/version.rc b/src/PowerShell.Core.Instrumentation/version.rc deleted file mode 100644 index 7b797f36032..00000000000 --- a/src/PowerShell.Core.Instrumentation/version.rc +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (C) Microsoft. All rights reserved. -// -#include -#include - -#define VER_FILETYPE VFT_DLL -#define VER_FILESUBTYPE VFT2_UNKNOWN -#define VER_FILEDESCRIPTION_STR "PowerShellCore" -#define VER_INTERNALNAME_STR "PowerShell.Core.Instrumentation.dll" -#define VER_ORIGINALFILENAME_STR "PowerShell.Core.Instrumentation.dll" - -#include "common.ver" - -#include "PowerShell.Core.Instrumentation.rc" diff --git a/src/ResGen/Program.cs b/src/ResGen/Program.cs index 8bc235b14a0..a69fd05e64f 100644 --- a/src/ResGen/Program.cs +++ b/src/ResGen/Program.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -44,8 +47,15 @@ public static void Main(string[] args) foreach (string resxPath in Directory.EnumerateFiles(resourcePath, fileFilter)) { + string accessModifier = "internal"; string className = Path.GetFileNameWithoutExtension(resxPath); - string sourceCode = GetStronglyTypeCsFileForResx(resxPath, moduleName, className); + if (className.StartsWith("public.", StringComparison.InvariantCultureIgnoreCase)) + { + accessModifier = "public"; + className = className.Substring(className.IndexOf('.') + 1); + } + + string sourceCode = GetStronglyTypeCsFileForResx(resxPath, moduleName, className, accessModifier); string outPath = Path.Combine(genFolder, className + ".cs"); Console.WriteLine("ResGen for " + outPath); File.WriteAllText(outPath, sourceCode); @@ -54,14 +64,13 @@ public static void Main(string[] args) } } - private static string GetStronglyTypeCsFileForResx(string xmlPath, string moduleName, string className) + private static string GetStronglyTypeCsFileForResx(string xmlPath, string moduleName, string className, string accessModifier) { // Example // // className = Full.Name.Of.The.ClassFoo // shortClassName = ClassFoo // namespaceName = Full.Name.Of.The - string shortClassName = className; string namespaceName = null; int lastIndexOfDot = className.LastIndexOf('.'); @@ -77,10 +86,10 @@ private static string GetStronglyTypeCsFileForResx(string xmlPath, string module { string value = data.Value.Replace("\n", "\n ///"); string name = data.Attribute("name").Value.Replace(' ', '_'); - entries.AppendFormat(ENTRY, name, value); + entries.AppendFormat(ENTRY, name, value, accessModifier); } - string bodyCode = string.Format(BODY, shortClassName, moduleName, entries.ToString(), className); + string bodyCode = string.Format(BODY, shortClassName, moduleName, entries.ToString(), className, accessModifier, accessModifier.Equals("public", StringComparison.InvariantCultureIgnoreCase) ? "public." : string.Empty); if (namespaceName != null) { bodyCode = string.Format(NAMESPACE, namespaceName, bodyCode); @@ -109,6 +118,7 @@ namespace {0} {{ {1} }} "; + private static readonly string BODY = @" using System; using System.Reflection; @@ -120,26 +130,28 @@ namespace {0} {{ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] -internal class {0} {{ +{4} class {0} {{ private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; + /// constructor [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(""Microsoft.Performance"", ""CA1811:AvoidUncalledPrivateCode"")] - internal {0}() {{ + {4} {0}() {{ }} /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager {{ + {4} static global::System.Resources.ResourceManager ResourceManager {{ get {{ - if (object.ReferenceEquals(resourceMan, null)) {{ - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(""{1}.resources.{3}"", typeof({0}).GetTypeInfo().Assembly); + if (resourceMan is null) {{ + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(""{1}.resources.{5}{3}"", typeof({0}).Assembly); resourceMan = temp; }} + return resourceMan; }} }} @@ -149,10 +161,11 @@ internal class {0} {{ /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture {{ + {4} static global::System.Globalization.CultureInfo Culture {{ get {{ return resourceCulture; }} + set {{ resourceCulture = value; }} @@ -166,12 +179,11 @@ internal class {0} {{ /// /// Looks up a localized string similar to {1} /// - internal static string {0} {{ + {2} static string {0} {{ get {{ return ResourceManager.GetString(""{0}"", resourceCulture); }} }} "; - } } diff --git a/src/ResGen/ResGen.csproj b/src/ResGen/ResGen.csproj index 87315226e6e..954038cfa51 100644 --- a/src/ResGen/ResGen.csproj +++ b/src/ResGen/ResGen.csproj @@ -1,11 +1,13 @@ - + Generates C# typed bindings for .resx files - netcoreapp2.0 + net11.0 resgen Exe - win7-x86;win7-x64;osx.10.12-x64;linux-x64 + true + true + win-x86;win-x64;osx-x64;linux-x64 diff --git a/src/Schemas/Format.xsd b/src/Schemas/Format.xsd new file mode 100644 index 00000000000..4a8039823c7 --- /dev/null +++ b/src/Schemas/Format.xsd @@ -0,0 +1,758 @@ + + + + + Represents the top-level element of a formatting file. + + + + + + Optional element. Defines common settings that apply to all the views of the formatting file. + + + + + Optional element. Defines the common sets of .NET objects that can be used by all views of the formatting file. + + + + + Optional element. Defines the common controls that can be used by all views of the formatting file. + + + + + Optional element. Defines the views used to display objects. + + + + + + + + Defines common settings that apply to all the views of the formatting file. Common settings include displaying errors, wrapping text in tables, defining how collections are expanded, and more. + + + + + Optional element. Specifies that the full error record is displayed when an error occurs while displaying a piece of data. + + + + + Optional element. Specifies that the string #ERR is displayed when an error occurs while displaying a piece of data. + + + + + Optional element. Specifies the minimum number of properties that an object must have to display the object in a table view. + + + + + Optional element. Specifies that data in a table is moved to the next line if it does not fit into the width of the column. + + + + + Optional element. Defines the different ways that .NET objects are expanded when they are displayed in a view. + + + + + + + Defines how .NET collection objects are expanded when they are displayed in a view. + + + + + Optional element. Defines the specific .NET collection objects that are expanded when they are displayed in a view. + + + + + + + Defines how specific .NET collection objects are expanded when they are displayed in a view. + + + + + Optional element. Defines which .NET collection objects are expanded by this definition. + + + + + Specifies how the collection object is expanded for this definition. + + + + + + + Specifies how the collection object is expanded for this definition. + + + + + + + + + + Defines the views used to display .NET objects. These views can display the properties and script values of an object in a table format, list format, wide format, and custom control format. + + + + + Defines a view that is used to display one or more .NET objects. + + + + + + + Defines a view that displays one or more .NET objects. There is no limit to the number of views that can be defined in a formatting file. + + + + + + + + Optional element. Defines a table format for the view. + + + + + Optional element. Defines a list format for the view. + + + + + Optional element. Defines a wide (single value) list format for the view. + + + + + Optional element. Defines a custom control format for the view. + + + + + + + + + + + + + + + + + + + Defines the .NET objects that are displayed by the view. Each view must specify at least one .NET object. + + + + + Optional element. Specifies a set of .NET objects that are displayed by the view. + + + + + Optional element. Specifies a .NET object that is displayed by the view. + + + + + + + Defines a table format for a view. + + + + + Optional element. Specifies whether the column size and the number of columns are adjusted based on the size of the data. + + + + + Optional element. Indicates whether the header of the table is not displayed. + + + + + Required element. Defines the labels, the widths, and the alignment of the data for the columns of the table view. + + + + + Optional element. Provides the definitions of the table view. + + + + + + + Defines the headers for the columns of a table. + + + + + Optional element. Defines the label, the width, and the alignment of the data for a column of a table view. + + + + + + + Defines the label, the width of the column, and the alignment of the label for a column of the table. + + + + + Optional element. Defines the label that is displayed at the top of the column. If no label is specified, the name of the property whose value is displayed in the rows is used. + + + + + Required element. Specifies the width (in characters) of the column. + + + + + Optional element. Specifies how the label of the column is displayed. If no alignment is specified, the label is aligned on the left. + + + + + + + Defines the properties or scripts whose values are displayed in a row. + + + + + Required element. Defines the property or script whose value is displayed in a column of the row. + + + + + + + Defines the property or script whose value is displayed in the column of the row. + + + + + + + Specifies a format pattern that is used to format the data in the column of the row. + + + + + + Optional element. Defines how the data in a column of the row is displayed. + + + + + + + + + + + + + Defines how the data in a column header is displayed. + + + + + + + + + + Defines the rows of the table. + + + + + Required element. Defines the data that is displayed in a row of the table. + + + + + + + Defines the data that is displayed in a row of the table. + + + + + Required element. Defines the objects whose property values are displayed in the row. + + + + + Required element. Defines the properties or scripts whose values are displayed. + + + + + Optional element. Specifies that text that exceeds the column width is displayed on the next line. + + + + + + + Defines a list format for the view. + + + + + Required element. Provides the definitions of the list view. + + + + + + + Provides the definitions of the list view. The list view must specify one or more definitions. + + + + + Provides a definition of the list view. + + + + + + + Provides a definition of the list view. + + + + + Optional element. Defines the .NET objects that use this list view definition or the condition that must exist for this definition to be used. + + + + + Required element. Defines the properties and scripts whose values are displayed by the list view. + + + + + + + Defines the properties and scripts whose values are displayed in the rows of the list view. + + + + + Required element. Defines the property or script whose value is displayed by the list view. + + + + + + + Defines the property or script whose value is displayed in a row of the list view. + + + + + Optional element Specifies the label that is displayed to the left of the property or script value in the row. + + + + + Optional element. Defines the condition that must exist for this list item to be used. + + + + + + Optional element. Specifies a format string that defines how the property or script value is displayed. + + + + + + + Defines a wide (single value) list format for the view. This view displays a single property value or script value for each object. + + + + + + Optional element. Specifies whether the column size and the number of columns are adjusted based on the size of the data. + + + + + Optional element. Specifies the number of columns displayed in the wide view. + + + + + + Required element. Provides the definitions of the wide view. + + + + + + + Provides the definitions of the wide view. The wide view must specify one or more definitions. + + + + + Provides a definition of the wide view. + + + + + + + Provides a definition of the wide view. + + + + + + Optional element. Defines the .NET types that use this wide entry definition or the condition that must exist for this definition to be used. + + + + + Required element. Defines the property or script whose value is displayed. + + + + + + + Defines the property or script whose value is displayed. + + + + + + Optional element. Specifies a format pattern that defines how the property or script value is displayed in the view. + + + + + + + Defines the common controls that can be used by all views of the formatting file. + + + + + Required element. Defines a common control that can be used by all views of the formatting file. + + + + + + + Defines a common control that can be used by all the views of the formatting file and the name that is used to reference the control. + + + + + Required element. Specifies the name used to reference the control. + + + + + Required element. Defines the control. + + + + + + + Defines the custom control that displays the new group. + + + + + Required element. Provides the definitions for the control. + + + + + + + Provides the definitions of a common control. This element is used when defining a common control that can be used by all the views in the formatting file. + + + + + Provides a definition of the common control. + + + + + + + Provides a definition of the common control. This element is used when defining a common control that can be used by all the views in the formatting file. + + + + + Optional element. Defines the .NET types that use the definition of the common control or the condition that must exist for this control to be used. + + + + + Required element. Defines what data is displayed by the control and how it is displayed. + + + + + + + Defines what data is displayed by the custom control view and how it is displayed. This element is used when defining how a new group of objects is displayed. + + + + + Optional element. Defines the data that is displayed by the control. + + + + + Optional element. Adds a blank line to the display of the control. + + + + + Optional element. Specifies additional text to the data displayed by the control. + + + + + Optional element. Defines what data is displayed by the custom control view and how it is displayed. + + + + + + + Defines the data that is displayed by the control. This element is used when defining a common control that can be used by all the views in the formatting file. + + + + + + Optional element. Specified that the elements of collections are displayed by the control. + + + + + Optional element. Defines the condition that must exist for this common control to be used. + + + + + Optional element. Defines a control that is used by this control. + + + + + Optional element. Specifies the name of a common control or a view control. + + + + + + + Defines the .NET objects that are in the selection set. + + + + + Required element. Specifies the .NET object that belongs to the selection set. + + + + + + + Defines the .NET types that use this custom entry or the condition that must exist for this entry to be used. + + + + + Optional element. Specifies a set of .NET types that use this definition of the control view. + + + + + Optional element. Specifies a .NET type that uses this definition of the control view. + + + + + Optional element. Defines the condition that must exist for this definition to be used. + + + + + + + Defines the condition that must exist for this definition to be used. There is no limit to the number of selection conditions that can be specified for a wide entry definition. + + + + + + Optional element. Specifies the set of .NET types that triggers the condition. + + + + + Optional element. Specifies a .NET type that triggers the condition. + + + + + + + + + Defines the common sets of .NET objects that can be used by all views of the formatting file. The views and controls of the formatting file can reference the complete set of objects by using only the name of the selection set. + + + + + Required element. Defines a single set of .NET objects that can be referenced by the name of the set. + + + + + + + Defines a set of .NET objects that can be referenced by the name of the set. + + + + + Required element. Specifies the name used to reference the selection set. + + + + + Required element. Defines the .NET objects that are in the selection set. + + + + + + + Defines how a new group of objects is displayed. This element is used when defining a table, list, wide, or custom control view. + + + + + + Optional element. Specifies a label that is displayed when a new group is encountered. + + + + + + Optional element. Defines the custom control that display new groups. + + + + + Optional element. Specifies the name of a control that is used to display the new group. + + + + + + + + Defines how the data is displayed, such as shifting the data to the left or right. This element is used when defining a custom control view. + + + + + Optional element. Specifies how many characters the data is shifted away from the left margin. + + + + + Optional element. Specifies how many characters the data is shifted away from the right margin. + + + + + + Optional element. Specifies how many characters the first line of data is shifted to the left. + + + + + Optional element. Specifies how many characters the first line of data is shifted to the right. + + + + + + Required Element + + + + + + + Defines the condition that must exist for this control to be used. This element is used when defining a common control that can be used by all the views in the formatting file. + + + + + + Specifies text that is added to the data that is displayed by the control, such as a label, brackets to enclose the data, and spaces to indent the data. This element is used when defining a common control that can be used by all the views in the formatting file. + + + + + + + + + + + + + + + + + + + + Adds a blank line to the display of the control. This element is used when defining how a new group of objects is displayed. + + + diff --git a/src/Schemas/PSMaml/Maml_HTML_Style.xsl b/src/Schemas/PSMaml/Maml_HTML_Style.xsl index 3f66ee3f968..955966526cc 100644 --- a/src/Schemas/PSMaml/Maml_HTML_Style.xsl +++ b/src/Schemas/PSMaml/Maml_HTML_Style.xsl @@ -172,7 +172,7 @@ + this cell is the table header, footer or body --> bottom @@ -381,4 +381,4 @@ - \ No newline at end of file + diff --git a/src/Schemas/PSMaml/ManagedDeveloper.xsd b/src/Schemas/PSMaml/ManagedDeveloper.xsd index 53c9383efdb..92a2ca6ba3b 100644 --- a/src/Schemas/PSMaml/ManagedDeveloper.xsd +++ b/src/Schemas/PSMaml/ManagedDeveloper.xsd @@ -4,8 +4,8 @@ This schema describes MAML, the Microsoft Assistance Markup Language. - MAML is intended for software documentation. In particular, it is - intended to accomodate the needs of Microsoft documentation. + MAML is intended for software documentation. In particular, it is + intended to accommodate the needs of Microsoft documentation. The schema is broken into three main areas: end user, developer and diff --git a/src/Schemas/PSMaml/ManagedDeveloperStructure.xsd b/src/Schemas/PSMaml/ManagedDeveloperStructure.xsd index 7580fd6b831..2f9d8dd5a22 100644 --- a/src/Schemas/PSMaml/ManagedDeveloperStructure.xsd +++ b/src/Schemas/PSMaml/ManagedDeveloperStructure.xsd @@ -4,8 +4,8 @@ This schema describes MAML, the Microsoft Assistance Markup Language. - MAML is intended for software documentation. In particular, it is - intended to accomodate the needs of Microsoft documentation. + MAML is intended for software documentation. In particular, it is + intended to accommodate the needs of Microsoft documentation. The schema is broken into three main areas: end user, developer and diff --git a/src/Schemas/PSMaml/ProviderHelp.xsd b/src/Schemas/PSMaml/ProviderHelp.xsd index 19e042c6d43..418897fa849 100644 --- a/src/Schemas/PSMaml/ProviderHelp.xsd +++ b/src/Schemas/PSMaml/ProviderHelp.xsd @@ -1,4 +1,4 @@ - + diff --git a/src/Schemas/PSMaml/developerDscResource.xsd b/src/Schemas/PSMaml/developerDscResource.xsd index f884e4d9991..c3903058dad 100644 --- a/src/Schemas/PSMaml/developerDscResource.xsd +++ b/src/Schemas/PSMaml/developerDscResource.xsd @@ -1,4 +1,4 @@ - + @@ -42,4 +42,4 @@ - \ No newline at end of file + diff --git a/src/Schemas/Types.xsd b/src/Schemas/Types.xsd new file mode 100644 index 00000000000..fbf494114dc --- /dev/null +++ b/src/Schemas/Types.xsd @@ -0,0 +1,167 @@ + + + + + The `<Types>` tag encloses all of the types that are defined in the file. There should be only one pair of `<Types>` tags. + + + + + + Each .NET Framework type mentioned in the file should be represented by a pair of `<Type>` tags. + + + + + + + + + + A pair of `<Name>` tags that enclose the name of the affected .NET Framework type. + + + + + + + + + + + + Defines a property with a static value. + + + + + Defines a new name for an existing property. + + + + + Defines a property whose value is the output of a script. + + + + + References a static method of a .NET Framework class. + + + + + Defines a method whose value is the output of a script. + + + + + References a static method of a .NET Framework class. + + + + + Defines a collection of properties of the object. + + + + + Defines a collection of members (properties and methods). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A pair of `<Members>` tags that enclose the tags for the new properties and methods that are defined for the .NET Framework type. + + + + + diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs index b652d90d536..e265bb453c8 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -1,51 +1,17 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Reflection; using System.Resources; +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(@"System.Management.Automation.Help")] -[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(@"Export-Command")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.PowerShellLanguageService")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.GraphicalHost")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.GPowerShell")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ISECommon")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Editor")] -[assembly: InternalsVisibleTo(@"powershell_ise")] -#else -[assembly: InternalsVisibleTo(@"System.Management.Automation.Help" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [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(@"Export-Command" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.PowerShellLanguageService" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.GraphicalHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.GPowerShell" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ISECommon" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Editor" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -[assembly: InternalsVisibleTo(@"powershell_ise" + @",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 bbc4f565c28..5a15df53ca8 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsAssemblyLoadContext.cs @@ -1,27 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -#if CORECLR - -using System.IO; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; using System.Runtime.Loader; -using System.Text; -using System.Linq; +using Microsoft.PowerShell.Telemetry; namespace System.Management.Automation { /// - /// The powershell custom AssemblyLoadContext implementation + /// The powershell custom AssemblyLoadContext implementation. /// - internal partial class PowerShellAssemblyLoadContext + internal sealed partial class PowerShellAssemblyLoadContext { #region Resource_Strings @@ -40,22 +35,27 @@ internal partial class PowerShellAssemblyLoadContext #region Constructor /// - /// Initialize a singleton of 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; } } /// - /// Constructor + /// Constructor. /// /// /// Base directory paths that are separated by semicolon ';'. They will be the default paths to probe assemblies. @@ -75,15 +75,16 @@ private PowerShellAssemblyLoadContext(string basePaths) } else { - _probingPaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + _probingPaths = basePaths.Split(';', StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < _probingPaths.Length; i++) { string basePath = _probingPaths[i]; if (!Directory.Exists(basePath)) { string message = string.Format(CultureInfo.CurrentCulture, BaseFolderDoesNotExist, basePath); - throw new ArgumentException(message, "basePaths"); + throw new ArgumentException(message, nameof(basePaths)); } + _probingPaths[i] = basePath.Trim(); } } @@ -93,15 +94,22 @@ private PowerShellAssemblyLoadContext(string basePaths) _availableDotNetAssemblyNames = new Lazy>( () => new HashSet(_coreClrTypeCatalog.Values, StringComparer.Ordinal)); - // LAST: Register 'Resolving' handler on the default load context. + // LAST: Register the 'Resolving' handler and 'ResolvingUnmanagedDll' handler on the default load context. AssemblyLoadContext.Default.Resolving += Resolve; + + // Add last resort native dll resolver. + // Default order: + // 1. System.Runtime.InteropServices.DllImportResolver callbacks + // 2. AssemblyLoadContext.LoadUnmanagedDll() + // 3. AssemblyLoadContext.Default.ResolvingUnmanagedDll handlers + AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeDllHandler; } #endregion Constructor #region Fields - private readonly static 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 @@ -110,6 +118,9 @@ private PowerShellAssemblyLoadContext(string basePaths) private readonly Dictionary _coreClrTypeCatalog; private readonly Lazy> _availableDotNetAssemblyNames; + private readonly HashSet _denyListedAssemblies = + new(StringComparer.OrdinalIgnoreCase) { "System.Windows.Forms" }; + #if !UNIX private string _winDir; private string _gacPathMSIL; @@ -118,7 +129,7 @@ private PowerShellAssemblyLoadContext(string basePaths) #endif /// - /// Assembly cache across the AppDomain + /// Assembly cache across the AppDomain. /// /// /// We user the assembly short name (AssemblyName.Name) as the key. @@ -132,14 +143,14 @@ 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 #region Properties /// - /// Singleton instance of PowerShellAssemblyLoadContext + /// Singleton instance of PowerShellAssemblyLoadContext. /// internal static PowerShellAssemblyLoadContext Instance { @@ -147,7 +158,7 @@ internal static PowerShellAssemblyLoadContext Instance } /// - /// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell Core. + /// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell. /// This is used for type name auto-completion in PS engine. /// internal IEnumerable AvailableDotNetTypeNames @@ -156,7 +167,7 @@ internal IEnumerable AvailableDotNetTypeNames } /// - /// Get the assembly names of all available .NET Core assemblies shipped with PowerShell Core. + /// Get the assembly names of all available .NET Core assemblies shipped with PowerShell. /// This is used for type name auto-completion in PS engine. /// internal HashSet AvailableDotNetAssemblyNames @@ -169,7 +180,7 @@ internal HashSet AvailableDotNetAssemblyNames #region Internal_Methods /// - /// Get the current loaded assemblies + /// Get the current loaded assemblies. /// internal IEnumerable GetAssembly(string namespaceQualifiedTypeName) { @@ -192,19 +203,50 @@ internal IEnumerable GetAssembly(string namespaceQualifiedTypeName) } /// - /// Set the profile optimization root on the appropriate load context + /// If a managed dll has native dependencies the handler will try to find these native dlls. + /// 1. Gets the managed.dll location (folder) + /// 2. Based on OS name and architecture name builds subfolder name where it is expected the native dll resides: + /// 3. Loads the native dll + /// + /// managed.dll folder + /// | + /// |--- 'win-x64' subfolder + /// | |--- native.dll + /// | + /// |--- 'win-x86' subfolder + /// | |--- native.dll + /// | + /// |--- 'win-arm' subfolder + /// | |--- native.dll + /// | + /// |--- 'win-arm64' subfolder + /// | |--- native.dll + /// | + /// |--- 'linux-x64' subfolder + /// | |--- native.so + /// | + /// |--- 'linux-x86' subfolder + /// | |--- native.so + /// | + /// |--- 'linux-arm' subfolder + /// | |--- native.so + /// | + /// |--- 'linux-arm64' subfolder + /// | |--- native.so + /// | + /// |--- 'osx-x64' subfolder + /// | |--- native.dylib + /// | + /// |--- 'osx-arm64' subfolder + /// | |--- native.dylib /// - internal void SetProfileOptimizationRootImpl(string directoryPath) + internal static IntPtr NativeDllHandler(Assembly assembly, string libraryName) { - AssemblyLoadContext.Default.SetProfileOptimizationRoot(directoryPath); - } + s_nativeDllSubFolder ??= GetNativeDllSubFolderName(out s_nativeDllExtension); + string folder = Path.GetDirectoryName(assembly.Location); + string fullName = Path.Combine(folder, s_nativeDllSubFolder, libraryName) + s_nativeDllExtension; - /// - /// Start the profile optimization on the appropriate load context - /// - internal void StartProfileOptimizationImpl(string profile) - { - AssemblyLoadContext.Default.StartProfileOptimization(profile); + return NativeLibrary.TryLoad(fullName, out IntPtr pointer) ? pointer : IntPtr.Zero; } #endregion Internal_Methods @@ -212,7 +254,7 @@ internal void StartProfileOptimizationImpl(string profile) #region Private_Methods /// - /// The handler for the Resolving event + /// The handler for the Resolving event. /// private Assembly Resolve(AssemblyLoadContext loadContext, AssemblyName assemblyName) { @@ -266,10 +308,10 @@ private Assembly Resolve(AssemblyLoadContext loadContext, AssemblyName assemblyN if (!isAssemblyFileFound || !isAssemblyFileMatching) { #if !UNIX - //Try loading from GAC + // Try loading from GAC if (!TryFindInGAC(assemblyName, out asmFilePath)) { - return null; + return null; } #else return null; @@ -296,48 +338,51 @@ private Assembly Resolve(AssemblyLoadContext loadContext, AssemblyName assemblyN private bool TryFindInGAC(AssemblyName assemblyName, out string assemblyFilePath) { assemblyFilePath = null; - if (Internal.InternalTestHooks.DisableGACLoading) + if (_denyListedAssemblies.Contains(assemblyName.Name)) { + // DotNet catches and throws a new exception with no inner exception + // We cannot change the message DotNet returns. return false; } - bool assemblyFound = false; - char dirSeparator = IO.Path.DirectorySeparatorChar; + if (Internal.InternalTestHooks.DisableGACLoading) + { + return false; + } - if (String.IsNullOrEmpty(_winDir)) + if (string.IsNullOrEmpty(_winDir)) { - //cache value of '_winDir' folder in member variable. + // cache value of '_winDir' folder in member variable. _winDir = Environment.GetEnvironmentVariable("winDir"); } - if (String.IsNullOrEmpty(_gacPathMSIL)) + if (string.IsNullOrEmpty(_gacPathMSIL)) { - //cache value of '_gacPathMSIL' folder in member variable. - _gacPathMSIL = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_MSIL"; + // cache value of '_gacPathMSIL' folder in member variable. + _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)) + 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; } else { - if (String.IsNullOrEmpty(_gacPath32)) + 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; @@ -350,25 +395,24 @@ 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(); + // Enumerate all directories, sort by name and select the last. This selects the latest version. + var chosenVersionDirectory = Directory.EnumerateDirectories(tempAssemblyDirPath).Order().LastOrDefault(); - if (!String.IsNullOrEmpty(chosenVersionDirectory)) + 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(); + // Select first or default as the directory will contain only one assembly. If nothing then default is null; + var foundAssemblyPath = Directory.EnumerateFiles(chosenVersionDirectory, $"{assemblyName.Name}*").FirstOrDefault(); - if (!String.IsNullOrEmpty(foundAssemblyPath)) + if (!string.IsNullOrEmpty(foundAssemblyPath)) { AssemblyName asmNameFound = AssemblyLoadContext.GetAssemblyName(foundAssemblyPath); if (IsAssemblyMatching(assemblyName, asmNameFound)) @@ -385,9 +429,9 @@ private bool FindInGac(string gacRoot, AssemblyName assemblyName, out string ass #endif /// - /// Try to get the specified assembly from cache + /// 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)) { @@ -406,12 +450,12 @@ private bool TryGetAssemblyFromCache(AssemblyName assemblyName, out Assembly asm } /// - /// Check if the loaded assembly matches the request + /// Check if the loaded assembly matches the request. /// - /// AssemblyName of the requested assembly - /// AssemblyName of the loaded assembly + /// 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: @@ -458,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. @@ -466,36 +510,64 @@ 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; } /// - /// Throw FileLoadException + /// 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); } /// - /// Throw FileNotFoundException + /// 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); } + private static string s_nativeDllSubFolder; + private static string s_nativeDllExtension; + + private static string GetNativeDllSubFolderName(out string ext) + { + string folderName = string.Empty; + ext = string.Empty; + var processArch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + + if (Platform.IsWindows) + { + folderName = "win-" + processArch; + ext = ".dll"; + } + else if (Platform.IsLinux) + { + folderName = "linux-" + processArch; + ext = ".so"; + } + else if (Platform.IsMacOS) + { + folderName = "osx-" + processArch; + ext = ".dylib"; + } + + return folderName; + } + #endregion Private_Methods } /// /// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. /// - public class PowerShellAssemblyLoadContextInitializer + public static class PowerShellAssemblyLoadContextInitializer { /// /// Create a singleton of PowerShellAssemblyLoadContext. @@ -509,15 +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("basePaths"); + ArgumentException.ThrowIfNullOrEmpty(basePaths); - PowerShellAssemblyLoadContext.InitializeSingleton(basePaths); + // Disallow calling this method from native code for more than once. + PowerShellAssemblyLoadContext.InitializeSingleton(basePaths, throwOnReentry: true); } } -} -#endif + /// + /// 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; + } + 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 4a21405f653..4cbb346fb14 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -1,23 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using System.Runtime.InteropServices; using System.ComponentModel; -using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; using System.IO; +using System.Runtime.InteropServices; +using System.Management.Automation.Internal; +using Microsoft.Win32; namespace System.Management.Automation { /// - /// These are platform abstractions and platform specific implementations + /// 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")) @@ -123,75 +126,120 @@ public static bool IsIoT } /// - /// True if it is the inbox powershell for NanoServer or IoT. + /// True if underlying system is Windows Desktop. /// - internal static bool IsInbox + public static bool IsWindowsDesktop { get { #if UNIX return false; #else - if (_isInbox.HasValue) { return _isInbox.Value; } - - _isInbox = false; - if (IsNanoServer || IsIoT) + if (_isWindowsDesktop.HasValue) { - _isInbox = string.Equals( - Utils.DefaultPowerShellAppBase, - Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID), - StringComparison.OrdinalIgnoreCase); + return _isWindowsDesktop.Value; } - return _isInbox.Value; + _isWindowsDesktop = !IsNanoServer && !IsIoT; + return _isWindowsDesktop.Value; #endif } } /// - /// True if underlying system is Windows Desktop. + /// Gets a value indicating whether the underlying system supports single-threaded apartment. /// - public static bool IsWindowsDesktop + public static bool IsStaSupported { get { #if UNIX return false; #else - if (_isWindowsDesktop.HasValue) { return _isWindowsDesktop.Value; } - - _isWindowsDesktop = !IsNanoServer && !IsIoT; - return _isWindowsDesktop.Value; + return _isStaSupported.Value; #endif } } -#if !UNIX +#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 = 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? _isInbox = null; private static bool? _isWindowsDesktop = null; #endif - // format files - internal static 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 - /// names in different OS platforms + /// names in different OS platforms. /// internal static class CommonEnvVariableNames { @@ -202,42 +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 (null == _tempDirectory) + 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 (null != _tempDirectory) + 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 + { + s_tempHome = Path.Combine(Path.GetTempPath(), StringUtil.Format(tempHomeFolderName, Environment.UserName)); + Directory.CreateDirectory(s_tempHome); + } + catch (UnauthorizedAccessException) { - return _tempDirectory; + // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts. + // Return an empty string in this case so the process working directory will be used. + s_tempHome = string.Empty; } - _tempDirectory = PsUtils.GetTemporaryDirectory(); - return _tempDirectory; + return s_tempHome; } -#if UNIX /// /// X Desktop Group configuration type enum. /// @@ -258,219 +312,99 @@ public enum XDG_Type } /// - /// function for choosing directory location of PowerShell for profile loading + /// 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 + // TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060 + + 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 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 (null == envHome) - { - envHome = GetTemporaryDirectory(); - } 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 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 + case XDG_Type.CACHE: + // Use 'XDG_CACHE_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgcachehome)) { - try - { - Directory.CreateDirectory(xdgCacheDefault); - } - catch (UnauthorizedAccessException) - { - //service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + Directory.CreateDirectory(xdgCacheDefault); + return xdgCacheDefault; } - return xdgCacheDefault; - } - - else - { - if (!Directory.Exists(Path.Combine(xdgcachehome, "powershell"))) - { - try - { - Directory.CreateDirectory(Path.Combine(xdgcachehome, "powershell")); - } - catch (UnauthorizedAccessException) - { - //service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } - } + string cachePath = Path.Combine(xdgcachehome, "powershell"); + Directory.CreateDirectory(cachePath); + return cachePath; - return Path.Combine(xdgcachehome, "powershell"); - } - - case Platform.XDG_Type.DEFAULT: - //default for profile location - return xdgConfigHomeDefault; - - 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. + /// Mimic 'Environment.GetFolderPath(folder)' on Unix. /// - internal static string GetFolderPath(System.Environment.SpecialFolder folder) + internal static string GetFolderPath(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. - /// - /// - /// The path to the specified system special folder, if that folder physically exists on your computer. - /// Otherwise, an empty string (""). - /// - private static string InternalGetFolderPath(System.Environment.SpecialFolder folder) - { - string folderPath = null; #if UNIX - string envHome = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); - if (null == envHome) + 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: @@ -483,32 +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 bool NonWindowsIsSymLink(FileSystemInfo fileInfo) - { - return Unix.NativeMethods.IsSymLink(fileInfo.FullName); - } - - internal static string NonWindowsInternalGetTarget(SafeFileHandle handle) - { - // SafeHandle is a Windows concept. Use the string version instead. - throw new PlatformNotSupportedException(); - } - - internal static string NonWindowsInternalGetTarget(string path) - { - return Unix.NativeMethods.FollowSymLink(path); - } - internal static string NonWindowsGetUserFromPid(int path) { return Unix.NativeMethods.GetUserFromPid(path); @@ -516,7 +429,7 @@ internal static string NonWindowsGetUserFromPid(int path) internal static string NonWindowsInternalGetLinkType(FileSystemInfo fileInfo) { - if (NonWindowsIsSymLink(fileInfo)) + if (fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint)) { return "SymbolicLink"; } @@ -546,48 +459,14 @@ internal static unsafe bool NonWindowsSetDate(DateTime dateToUse) return Unix.NativeMethods.SetDate(&tm) == 0; } - internal static string NonWindowsGetDomainName() - { - string name = Unix.NativeMethods.GetFullyQualifiedName(); - if (!string.IsNullOrEmpty(name)) - { - // name is hostname.domainname, so extract domainname - int index = name.IndexOf('.'); - if (index >= 0) - { - return name.Substring(index + 1); - } - } - // if the domain name could not be found, do not throw, just return empty - return string.Empty; - } - - // Hostname in this context seems to be the FQDN - internal static string NonWindowsGetHostName() - { - return Unix.NativeMethods.GetFullyQualifiedName() ?? string.Empty; - } - - internal static bool NonWindowsIsFile(string path) - { - return Unix.NativeMethods.IsFile(path); - } - - internal static bool NonWindowsIsDirectory(string path) - { - return Unix.NativeMethods.IsDirectory(path); - } - internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathTwo) { 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; @@ -608,60 +487,276 @@ internal static int NonWindowsGetProcessParentPid(int pid) return IsMacOS ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid); } - // Unix specific implementations of required functionality - // + 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. // No manual mapping is required. .NET Core maps the Linux errno // to a PAL value and calls strerror_r underneath to generate the message. - internal static class Unix + + /// Unix specific implementations of required functionality. + internal static partial class Unix { - // This is a helper that attempts to map errno into a PowerShell ErrorCategory - internal static ErrorCategory GetErrorCategory(int errno) + private static readonly Dictionary usernameCache = new(); + private static readonly Dictionary groupnameCache = new(); + + /// The type of a Unix file system item. + public enum ItemType { - return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno); + /// The item is a Directory. + Directory, + + /// The item is a File. + File, + + /// The item is a Symbolic Link. + SymbolicLink, + + /// The item is a Block Device. + BlockDevice, + + /// The item is a Character Device. + CharacterDevice, + + /// The item is a Named Pipe. + NamedPipe, + + /// The item is a Socket. + Socket, + } + + /// The mask to use to retrieve specific mode bits from the mode value in the stat class. + public enum StatMask + { + /// The mask to collect the owner mode. + OwnerModeMask = 0x1C0, + + /// The mask to get the owners read bit. + OwnerRead = 0x100, + + /// The mask to get the owners write bit. + OwnerWrite = 0x080, + + /// The mask to get the owners execute bit. + OwnerExecute = 0x040, + + /// The mask to get the group mode. + GroupModeMask = 0x038, + + /// The mask to get the group mode. + GroupRead = 0x20, + + /// The mask to get the group mode. + GroupWrite = 0x10, + + /// The mask to get the group mode. + GroupExecute = 0x8, + + /// The mask to get the "other" mode. + OtherModeMask = 0x007, + + /// The mask to get the "other" read bit. + OtherRead = 0x004, + + /// The mask to get the "other" write bit. + OtherWrite = 0x002, + + /// The mask to get the "other" execute bit. + OtherExecute = 0x001, + + /// The mask to retrieve the sticky bit. + SetStickyMask = 0x200, + + /// The mask to retrieve the setgid bit. + SetGidMask = 0x400, + + /// The mask to retrieve the setuid bit. + SetUidMask = 0x800, } - private static string s_userName; - public static string UserName + /// The Common Stat class. + public class CommonStat { - get + /// The inode of the filesystem item. + public long Inode; + + /// The Mode of the filesystem item. + public int Mode; + + /// The user id of the filesystem item. + public int UserId; + + /// The group id of the filesystem item. + public int GroupId; + + /// The number of hard links for the filesystem item. + public int HardlinkCount; + + /// The size in bytes of the filesystem item. + public long Size; + + /// The last access time of the filesystem item. + public DateTime AccessTime; + + /// The last modified time for the filesystem item. + public DateTime ModifiedTime; + + /// The last time the status changes for the filesystem item. + public DateTime StatusChangeTime; + + /// The block size of the filesystem. + public long BlockSize; + + /// The device id of the filesystem item. + public int DeviceId; + + /// The number of blocks used by the filesystem item. + public int NumberOfBlocks; + + /// The type of the filesystem item. + public ItemType ItemType; + + /// Whether the filesystem item has the setuid bit enabled. + public bool IsSetUid; + + /// Whether the filesystem item has the setgid bit enabled. + public bool IsSetGid; + + /// Whether the filesystem item has the sticky bit enabled. This is only available for directories. + public bool IsSticky; + + private const char CanRead = 'r'; + private const char CanWrite = 'w'; + private const char CanExecute = 'x'; + 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 static readonly Dictionary itemTypeTable = new() { - if (string.IsNullOrEmpty(s_userName)) + { ItemType.BlockDevice, 'b' }, + { ItemType.CharacterDevice, 'c' }, + { 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() + { + // On an Ubuntu system (docker), these 3 are roughly 70% of all the permissions + if ((Mode & 0xFFF) == 292) { - s_userName = NativeMethods.GetUserName(); + return OwnerReadGroupReadOtherRead; } - return s_userName ?? string.Empty; + + if ((Mode & 0xFFF) == 420) + { + return OwnerReadWriteGroupReadOtherRead; + } + + 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); } - } - public static string TemporaryDirectory - { - get + /// + /// Get the user name. This is used in formatting, but we shouldn't + /// do the pinvoke this unless we're going to use it. + /// + /// The user name. + public string GetUserName() { - // POSIX temporary directory environment variables - string[] environmentVariables = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; - string dir = string.Empty; - foreach (string s in environmentVariables) + if (usernameCache.TryGetValue(UserId, out string username)) { - dir = System.Environment.GetEnvironmentVariable(s); - if (!string.IsNullOrEmpty(dir)) - { - return dir; - } + return username; } - return "/tmp"; + + // Get and add the user name to the cache so we don't need to + // have a pinvoke for each file. + username = NativeMethods.GetPwUid(UserId); + usernameCache.Add(UserId, username); + + return username; + } + + /// + /// Get the group name. This is used in formatting, but we shouldn't + /// do the pinvoke this unless we're going to use it. + /// + /// The name of the group. + public string GetGroupName() + { + if (groupnameCache.TryGetValue(GroupId, out string groupname)) + { + return groupname; + } + + // Get and add the group name to the cache so we don't need to + // have a pinvoke for each file. + groupname = NativeMethods.GetGrGid(GroupId); + groupnameCache.Add(GroupId, groupname); + + return groupname; } } - public static bool IsHardLink(ref IntPtr handle) + // This is a helper that attempts to map errno into a PowerShell ErrorCategory + internal static ErrorCategory GetErrorCategory(int errno) { - // TODO:PSL implement using fstat to query inode refcount to see if it is a hard link - return false; + return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno); } - + /// 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. public static bool IsHardLink(FileSystemInfo fs) { if (!fs.Exists || (fs.Attributes & FileAttributes.Directory) == FileAttributes.Directory) @@ -676,29 +771,163 @@ public static bool IsHardLink(FileSystemInfo fs) { return count > 1; } + + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + /// + /// Create a managed replica of the native stat structure. + /// + /// The common stat structure from which we copy. + /// A managed common stat class instance. + private static CommonStat CopyStatStruct(NativeMethods.CommonStatStruct css) + { + CommonStat cs = new(); + cs.Inode = css.Inode; + cs.Mode = css.Mode; + cs.UserId = css.UserId; + cs.GroupId = css.GroupId; + cs.HardlinkCount = css.HardlinkCount; + cs.Size = css.Size; + + // These can sometime throw if we get too large a number back (seen on Raspbian). + // As a fallback, set the time to UnixEpoch. + try + { + cs.AccessTime = DateTime.UnixEpoch.AddSeconds(css.AccessTime).ToLocalTime(); + } + catch + { + cs.AccessTime = DateTime.UnixEpoch.ToLocalTime(); + } + + try + { + cs.ModifiedTime = DateTime.UnixEpoch.AddSeconds(css.ModifiedTime).ToLocalTime(); + } + catch + { + cs.ModifiedTime = DateTime.UnixEpoch.ToLocalTime(); + } + + try + { + cs.StatusChangeTime = DateTime.UnixEpoch.AddSeconds(css.StatusChangeTime).ToLocalTime(); + } + catch + { + cs.StatusChangeTime = DateTime.UnixEpoch.ToLocalTime(); + } + + cs.BlockSize = css.BlockSize; + cs.DeviceId = css.DeviceId; + cs.NumberOfBlocks = css.NumberOfBlocks; + + if (css.IsDirectory == 1) + { + cs.ItemType = ItemType.Directory; + } + else if (css.IsFile == 1) + { + cs.ItemType = ItemType.File; + } + else if (css.IsSymbolicLink == 1) + { + cs.ItemType = ItemType.SymbolicLink; + } + else if (css.IsBlockDevice == 1) + { + cs.ItemType = ItemType.BlockDevice; + } + else if (css.IsCharacterDevice == 1) + { + cs.ItemType = ItemType.CharacterDevice; + } + else if (css.IsNamedPipe == 1) + { + cs.ItemType = ItemType.NamedPipe; + } else { - throw new Win32Exception(Marshal.GetLastWin32Error()); + cs.ItemType = ItemType.Socket; + } + + cs.IsSetUid = css.IsSetUid == 1; + cs.IsSetGid = css.IsSetGid == 1; + cs.IsSticky = css.IsSticky == 1; + + return cs; + } + + /// Get the lstat info from a path. + /// The path to the lstat information. + /// An instance of the CommonStat for the path. + public static CommonStat GetLStat(string path) + { + NativeMethods.CommonStatStruct css; + if (NativeMethods.GetCommonLStat(path, out css) == 0) + { + return CopyStatStruct(css); } + + throw new Win32Exception(Marshal.GetLastWin32Error()); } + /// Get the stat info from a path. + /// The path to the stat information. + /// An instance of the CommonStat for the path. + public static CommonStat GetStat(string path) + { + NativeMethods.CommonStatStruct css; + if (NativeMethods.GetCommonStat(path, out css) == 0) + { + return CopyStatStruct(css); + } + + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + /// Read the /proc file system for information about the parent. + /// The process id used to get the parent process. + /// The process id. 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(new[] { ' ' }, 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) { @@ -706,57 +935,72 @@ public static int GetProcFSParentPid(int pid) } } - internal static class NativeMethods + /// The native methods class. + 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 marshalled as I1 - // C bools are 1 byte and so must be marshaled 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, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.LPStr)] - internal static extern string GetUserName(); + [LibraryImport(psLib)] + internal static partial int GetPPid(int pid); - [DllImport(psLib)] - internal static extern int GetPPid(int pid); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static partial int GetLinkCount(string filePath, out int linkCount); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - internal static extern int GetLinkCount([MarshalAs(UnmanagedType.LPStr)]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 IsSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath); + internal static partial bool IsExecutable(string filePath); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsExecutable([MarshalAs(UnmanagedType.LPStr)]string filePath); + [LibraryImport(psLib)] + internal static partial uint GetCurrentThreadId(); - [DllImport(psLib, CharSet = CharSet.Ansi)] - internal static extern uint GetCurrentThreadId(); + [LibraryImport(psLib)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool KillProcess(int pid); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.LPStr)] - internal static extern string GetFullyQualifiedName(); + [LibraryImport(psLib)] + internal static partial int WaitPid(int pid, [MarshalAs(UnmanagedType.Bool)] bool nohang); - // This is a struct tm from - [StructLayout(LayoutKind.Sequential)] + // 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 { - public int tm_sec; /* Seconds (0-60) */ - public int tm_min; /* Minutes (0-59) */ - public int tm_hour; /* Hours (0-23) */ - public int tm_mday; /* Day of the month (1-31) */ - public int tm_mon; /* Month (0-11) */ - public int tm_year; /* Year - 1900 */ - public int tm_wday; /* Day of the week (0-6, Sunday = 0) */ - public int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */ - public int tm_isdst; /* Daylight saving time */ + /// Seconds (0-60). + internal int tm_sec; + + /// Minutes (0-59). + internal int tm_min; + + /// Hours (0-23). + internal int tm_hour; + + /// Day of the month (1-31). + internal int tm_mday; + + /// Month (0-11). + internal int tm_mon; + + /// The year - 1900. + internal int tm_year; + + /// Day of the week (0-6, Sunday = 0). + internal int tm_wday; + + /// Day in the year (0-365, 1 Jan = 0). + internal int tm_yday; + + /// Daylight saving time. + internal int tm_isdst; } + // We need a way to convert a DateTime to a unix date. internal static UnixTm DateTimeToUnixTm(DateTime date) { UnixTm tm; @@ -772,42 +1016,114 @@ 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); + [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 FollowSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath); + internal static partial string GetUserFromPid(int pid); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.LPStr)] - internal static extern string GetUserFromPid(int pid); - - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsFile([MarshalAs(UnmanagedType.LPStr)]string filePath); + internal static partial bool IsSameFileSystemItem(string filePathOne, string filePathTwo); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsDirectory([MarshalAs(UnmanagedType.LPStr)]string filePath); + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetInodeData(string path, out ulong device, out ulong inode); - [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] - [return: MarshalAs(UnmanagedType.I1)] - internal static extern bool IsSameFileSystemItem([MarshalAs(UnmanagedType.LPStr)]string filePathOne, - [MarshalAs(UnmanagedType.LPStr)]string filePathTwo); + /// + /// This is a struct from getcommonstat.h in the native library. + /// It presents each member of the stat structure as the largest type of that member across + /// all stat structures on the platforms we support. This allows us to present a common + /// stat structure for all our platforms. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct CommonStatStruct + { + /// The inode of the filesystem item. + internal long Inode; + + /// The mode of the filesystem item. + internal int Mode; + + /// The user id of the filesystem item. + internal int UserId; + + /// The group id of the filesystem item. + internal int GroupId; + + /// The number of hard links to the filesystem item. + internal int HardlinkCount; + + /// The size in bytes of the filesystem item. + internal long Size; + + /// The time of the last access for the filesystem item. + internal long AccessTime; + + /// The time of the last modification for the filesystem item. + internal long ModifiedTime; + + /// The time of the last status change for the filesystem item. + internal long StatusChangeTime; + + /// The size in bytes of the file system. + internal long BlockSize; + + /// The device id for the filesystem item. + internal int DeviceId; + + /// The number of filesystem blocks that the filesystem item uses. + internal int NumberOfBlocks; + + /// This filesystem item is a directory. + internal int IsDirectory; + + /// This filesystem item is a file. + internal int IsFile; + + /// This filesystem item is a symbolic link. + internal int IsSymbolicLink; + + /// This filesystem item is a block device. + internal int IsBlockDevice; + + /// This filesystem item is a character device. + internal int IsCharacterDevice; + + /// This filesystem item is a named pipe. + internal int IsNamedPipe; + + /// This filesystem item is a socket. + internal int IsSocket; + + /// This filesystem item will run as the owner if executed. + internal int IsSetUid; + + /// 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; + } + + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static unsafe partial int GetCommonLStat(string filePath, out CommonStatStruct cs); + + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)] + internal static unsafe partial int GetCommonStat(string filePath, out CommonStatStruct cs); + + [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)] + internal static partial string GetPwUid(int id); - [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 string GetGrGid(int id); } } } -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs index 3170b545884..e439cc30ff2 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -1,98 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Globalization; using System.Collections.Generic; +using System.Globalization; + using Microsoft.Win32; #pragma warning disable 1591, 1572, 1571, 1573, 1587, 1570, 0067 -#region CLR_STUBS - -// This namespace contains stubs for some .NET types that are not in CoreCLR, such as ISerializable and SerializableAttribute. -// We use the stubs in this namespace to reduce #if/def in the code as much as possible. -namespace Microsoft.PowerShell.CoreClr.Stubs -{ - #region SystemManagementStubs - - // Summary: - // Describes the authentication level to be used to connect to WMI. This is - // used for the COM connection to WMI. - public enum AuthenticationLevel - { - // Summary: - // Authentication level should remain as it was before. - Unchanged = -1, - // - // Summary: - // The default COM authentication level. WMI uses the default Windows Authentication - // setting. - Default = 0, - // - // Summary: - // No COM authentication. - None = 1, - // - // Summary: - // Connect-level COM authentication. - Connect = 2, - // - // Summary: - // Call-level COM authentication. - Call = 3, - // - // Summary: - // Packet-level COM authentication. - Packet = 4, - // - // Summary: - // Packet Integrity-level COM authentication. - PacketIntegrity = 5, - // - // Summary: - // Packet Privacy-level COM authentication. - PacketPrivacy = 6, - } - - // Summary: - // Describes the impersonation level to be used to connect to WMI. - public enum ImpersonationLevel - { - // Summary: - // Default impersonation. - Default = 0, - // - // Summary: - // Anonymous COM impersonation level that hides the identity of the caller. - // Calls to WMI may fail with this impersonation level. - Anonymous = 1, - // - // Summary: - // Identify-level COM impersonation level that allows objects to query the credentials - // of the caller. Calls to WMI may fail with this impersonation level. - Identify = 2, - // - // Summary: - // Impersonate-level COM impersonation level that allows objects to use the - // credentials of the caller. This is the recommended impersonation level for - // WMI calls. - Impersonate = 3, - // - // Summary: - // Delegate-level COM impersonation level that allows objects to permit other - // objects to use the credentials of the caller. This level, which will work - // with WMI calls but may constitute an unnecessary security risk, is supported - // only under Windows 2000. - Delegate = 4, - } - - #endregion -} - -#endregion CLR_STUBS - #region PS_STUBS // Include PS types that are not needed for PowerShell on CSS @@ -102,11 +18,12 @@ namespace System.Management.Automation /// /// We don't need PSTransaction related types on CSS because System.Transactions - /// namespace is not available in CoreCLR + /// namespace is not available in CoreCLR. /// public sealed class PSTransactionContext : IDisposable { internal PSTransactionContext(Internal.PSTransactionManager transactionManager) { } + public void Dispose() { } } @@ -117,49 +34,29 @@ public void Dispose() { } public enum RollbackSeverity { /// - /// Non-terminating errors or worse + /// Non-terminating errors or worse. /// Error, /// - /// Terminating errors or worse + /// Terminating errors or worse. /// TerminatingError, /// - /// Do not rollback the transaction on error + /// Do not rollback the transaction on error. /// Never } #endregion PSTransaction - - #region ApartmentState - - internal enum ApartmentState - { - // - // Summary: - // The System.Threading.Thread will create and enter a single-threaded apartment. - STA = 0, - // - // Summary: - // The System.Threading.Thread will create and enter a multithreaded apartment. - MTA = 1, - // - // Summary: - // The System.Threading.Thread.ApartmentState property has not been set. - Unknown = 2 - } - - #endregion ApartmentState } namespace System.Management.Automation.Internal { /// /// We don't need PSTransaction related types on CSS because System.Transactions - /// namespace is not available in CoreCLR + /// namespace is not available in CoreCLR. /// internal sealed class PSTransactionManager : IDisposable { @@ -200,7 +97,7 @@ internal bool IsLastTransactionRolledBack } /// - /// Gets the rollback preference for the active transaction + /// Gets the rollback preference for the active transaction. /// internal RollbackSeverity RollbackPreference { @@ -234,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(Boolean); - 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) { - - // *** 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 *** - - return true; - } - - return false; - } - } -#pragma warning restore 618 -} - namespace Microsoft.PowerShell.Commands.Internal { using System.Security.AccessControl; @@ -501,6 +151,7 @@ public void SetValue(string name, object value, RegistryValueKind valueKind) { throw new NotImplementedException("SetValue(string name, obj value, RegistryValueKind valueKind) is not implemented. TransactedRegistry related APIs should not be used."); } + public string[] GetValueNames() { throw new NotImplementedException("GetValueNames() is not implemented. TransactedRegistry related APIs should not be used."); @@ -552,6 +203,7 @@ public void Close() } public abstract string Name { get; } + public abstract int SubKeyCount { get; } public void SetAccessControl(ObjectSecurity securityDescriptor) @@ -689,29 +341,130 @@ internal string GetResourceStringIndirect( #endregion } +#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 { /// - /// We don't need Windows Lockdown Policy related types on CSS because CSS is - /// amd64 only and is used internally. + /// Application white listing security policies only affect Windows OSs. /// - internal sealed class SystemPolicy + public sealed class SystemPolicy { private SystemPolicy() { } /// - /// Gets the system lockdown policy + /// 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; } /// - /// Gets lockdown policy as applied to a file + /// 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; @@ -721,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 + /// How the policy is being enforced. /// - internal enum SystemEnforcementMode + public enum SystemEnforcementMode { /// Not enforced at all None = 0, @@ -737,10 +504,38 @@ 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, -#if UNIX + /// + /// 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 namespace System.Management.Automation.Tracing @@ -749,13 +544,11 @@ namespace System.Management.Automation.Tracing using System.Management.Automation.Internal; /// - /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] public abstract class EtwActivity { /// - /// /// /// /// @@ -765,7 +558,6 @@ public static bool SetActivityId(Guid activityId) } /// - /// /// /// public static Guid CreateActivityId() @@ -774,7 +566,6 @@ public static Guid CreateActivityId() } /// - /// /// /// public static Guid GetActivityId() @@ -786,27 +577,27 @@ public static Guid GetActivityId() public enum PowerShellTraceTask { /// - /// None + /// None. /// None = 0, /// - /// CreateRunspace + /// CreateRunspace. /// CreateRunspace = 1, /// - /// ExecuteCommand + /// ExecuteCommand. /// ExecuteCommand = 2, /// - /// Serialization + /// Serialization. /// Serialization = 3, /// - /// PowerShellConsoleStartup + /// PowerShellConsoleStartup. /// PowerShellConsoleStartup = 4, } @@ -819,67 +610,64 @@ public enum PowerShellTraceTask public enum PowerShellTraceKeywords : ulong { /// - /// None + /// None. /// None = 0, /// - /// Runspace + /// Runspace. /// Runspace = 0x1, /// - /// Pipeline + /// Pipeline. /// Pipeline = 0x2, /// - /// Protocol + /// Protocol. /// Protocol = 0x4, /// - /// Transport + /// Transport. /// Transport = 0x8, /// - /// Host + /// Host. /// Host = 0x10, /// - /// Cmdlets + /// Cmdlets. /// Cmdlets = 0x20, /// - /// Serializer + /// Serializer. /// Serializer = 0x40, /// - /// Session + /// Session. /// Session = 0x80, /// - /// ManagedPlugIn + /// ManagedPlugIn. /// ManagedPlugIn = 0x100, /// - /// /// UseAlwaysDebug = 0x2000000000000000, /// - /// /// UseAlwaysOperational = 0x8000000000000000, /// - /// /// UseAlwaysAnalytic = 0x4000000000000000, } @@ -951,13 +739,12 @@ public void Dispose() { } - public bool WriteMessage(String message) + public bool WriteMessage(string message) { return false; } /// - /// /// /// /// @@ -968,7 +755,6 @@ public bool WriteMessage(string message1, string message2) } /// - /// /// /// /// @@ -978,9 +764,7 @@ public bool WriteMessage(string message, Guid instanceId) return false; } - /// - /// /// /// /// @@ -994,7 +778,6 @@ public void WriteMessage(string className, string methodName, Guid workflowId, s } /// - /// /// /// /// diff --git a/src/System.Management.Automation/CoreCLR/EventResource.cs b/src/System.Management.Automation/CoreCLR/EventResource.cs index 9ff6f38d402..178602e7da4 100755 --- a/src/System.Management.Automation/CoreCLR/EventResource.cs +++ b/src/System.Management.Automation/CoreCLR/EventResource.cs @@ -1,7 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #if UNIX /* - This code was generated by the tools\ResxGen\ResxGen.ps1 run against PowerShell-Core-Instrumentation.man. - To add or change logged events and the associated resources, edit PowerShell-Core-Instrumentation.man + This code was generated by the tools\ResxGen\ResxGen.ps1 run against PowerShell.Core.Instrumentation.man. + To add or change logged events and the associated resources, edit PowerShell.Core.Instrumentation.man then rerun ResxGen.ps1 to produce an updated CS and Resx file. */ using System.Collections.Generic; @@ -34,10 +37,10 @@ public static string GetMissingEventMessage(out int parameterCount) } /// - /// Gets the message resource id for the specified event id + /// Gets the message resource id for the specified event id. /// /// The event id for the message resource to retrieve. - /// The number of parameters required by the message resource + /// The number of parameters required by the message resource. /// The string resource id of the associated event message; otherwise, a null reference if the event id is not valid. public static string GetMessage(int eventId, out int parameterCount) { @@ -130,6 +133,12 @@ public static string GetMessage(int eventId, out int parameterCount) case 12039: parameterCount = 0; return "PS_PROVIDEReventE_A_RUNSPACEPOOL_TRANSFER"; + case 12289: + parameterCount = 2; + return "PS_PROVIDEReventE_O_ExperimentalFeatureInvalidName"; + case 12290: + parameterCount = 3; + return "PS_PROVIDEReventE_O_ExperimentalFeatureReadConfigError"; case 24577: parameterCount = 1; return "PS_PROVIDEReventE_O_ISEExecuteScript"; @@ -608,6 +617,7 @@ public static string GetMessage(int eventId, out int parameterCount) parameterCount = 3; return "PS_PROVIDEReventE_O_REMOTE_NAMEDPIPE_DISCONNECT"; } + parameterCount = 0; return null; } diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index 8aac81ca451..283ec1bcad8 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -1,7 +1,6 @@ -using Microsoft.Management.Infrastructure; -using Microsoft.Management.Infrastructure.Generic; -using Microsoft.Management.Infrastructure.Serialization; -using Microsoft.PowerShell.Commands; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; using System.Collections; using System.Collections.Generic; @@ -13,22 +12,28 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using System.Runtime.InteropServices; using System.Reflection; -using System.Text; +using System.Runtime.InteropServices; using System.Security; +using System.Text; + +using Microsoft.Management.Infrastructure; +using Microsoft.Management.Infrastructure.Generic; +using Microsoft.Management.Infrastructure.Serialization; +using Microsoft.PowerShell.Commands; + +using static Microsoft.PowerShell.SecureStringHelper; namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal { /// - /// /// [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] public static class DscRemoteOperationsClass { /// - /// Convert Cim Instance representing Resource desired state to Powershell Class Object + /// Convert Cim Instance representing Resource desired state to Powershell Class Object. /// public static object ConvertCimInstanceToObject(Type targetType, CimInstance instance, string moduleName) { @@ -38,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); @@ -56,6 +59,7 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins { innerException = powerShell.Streams.Error[0].Exception; } + errorMessage = string.Format(CultureInfo.CurrentCulture, ParserStrings.InstantiatePSClassObjectFailed, className); var invalidOperationException = new InvalidOperationException(errorMessage, innerException); throw invalidOperationException; @@ -69,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); @@ -90,7 +96,7 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins if (cimPropertyInstance != null && cimPropertyInstance.CimClass != null && cimPropertyInstance.CimClass.CimSystemProperties != null && - String.Equals( + string.Equals( cimPropertyInstance.CimClass.CimSystemProperties.ClassName, "MSFT_Credential", StringComparison.OrdinalIgnoreCase)) { @@ -100,11 +106,13 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins { targetValue = ConvertCimInstanceToObject(memberType, cimPropertyInstance, moduleName); } + if (targetValue == null) { return null; } } + break; case CimType.InstanceArray: { @@ -131,11 +139,14 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins { return null; } + targetArray.SetValue(obj, i); } + targetValue = targetArray; } } + break; default: targetValue = LanguagePrimitives.ConvertTo(property.Value, memberType, CultureInfo.InvariantCulture); @@ -153,17 +164,19 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins { ((FieldInfo)member).SetValue(targetObject, targetValue); } + if (member is PropertyInfo) { ((PropertyInfo)member).SetValue(targetObject, targetValue); } } } + return targetObject; } /// - /// Convert hashtable from Ciminstance to hashtable primitive type + /// Convert hashtable from Ciminstance to hashtable primitive type. /// /// /// @@ -186,6 +199,7 @@ private static object ConvertCimInstanceHashtable(string providerName, CimInstan var invalidOperationException = new InvalidOperationException(errorMessage); throw invalidOperationException; } + result.Add(LanguagePrimitives.ConvertTo(key.Value), LanguagePrimitives.ConvertTo(value.Value)); } } @@ -199,7 +213,7 @@ private static object ConvertCimInstanceHashtable(string providerName, CimInstan return result; } /// - /// Convert CIM instance to PS Credential + /// Convert CIM instance to PS Credential. /// /// /// @@ -247,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); @@ -271,7 +280,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration public sealed class ArgumentToConfigurationDataTransformationAttribute : ArgumentTransformationAttribute { /// - /// convert a file of ConfigurationData into a hashtable + /// Convert a file of ConfigurationData into a hashtable. /// /// /// @@ -286,7 +295,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input if (engineIntrinsics == null) { - throw PSTraceSource.NewArgumentNullException("engineIntrinsics"); + throw PSTraceSource.NewArgumentNullException(nameof(engineIntrinsics)); } return PsUtils.EvaluatePowerShellDataFileAsModuleManifest( @@ -308,10 +317,10 @@ 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) { @@ -320,7 +329,6 @@ internal CimDSCParser(CimMofDeserializer.OnClassNeeded onClassNeeded) } /// - /// /// internal CimDSCParser(CimMofDeserializer.OnClassNeeded onClassNeeded, Microsoft.Management.Infrastructure.Serialization.MofDeserializerSchemaValidationOption validationOptions) { @@ -330,7 +338,6 @@ internal CimDSCParser(CimMofDeserializer.OnClassNeeded onClassNeeded, Microsoft. } /// - /// /// /// /// @@ -355,7 +362,7 @@ internal List ParseInstanceMof(string filePath) } /// - /// Read file content to byte array + /// Read file content to byte array. /// /// /// @@ -363,7 +370,7 @@ internal static byte[] GetFileContent(string fullFilePath) { if (string.IsNullOrEmpty(fullFilePath)) { - throw PSTraceSource.NewArgumentNullException("fullFilePath"); + throw PSTraceSource.NewArgumentNullException(nameof(fullFilePath)); } if (!File.Exists(fullFilePath)) @@ -383,13 +390,22 @@ internal static byte[] GetFileContent(string fullFilePath) internal List ParseSchemaMofFileBuffer(string mof) { uint offset = 0; - var buffer = Encoding.Unicode.GetBytes(mof); +#if UNIX + // OMI only supports UTF-8 without BOM + var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); +#else + // This is what we traditionally use with Windows + // DSC asked to keep it UTF-32 for Windows + var encoding = new UnicodeEncoding(); +#endif + + var buffer = encoding.GetBytes(mof); + var result = new List(_deserializer.DeserializeClasses(buffer, ref offset, null, null, null, _onClassNeeded, null)); return result; } /// - /// /// /// /// @@ -406,6 +422,7 @@ internal List ParseSchemaMof(string filePath) { fileNameDefiningClass = fileNameDefiningClass.Substring(0, dotIndex); } + var result = new List(_deserializer.DeserializeClasses(buffer, ref offset, null, null, null, _onClassNeeded, null)); foreach (CimClass c in result) { @@ -413,7 +430,7 @@ internal List ParseSchemaMof(string filePath) string className = c.CimSystemProperties.ClassName; if ((superClassName != null) && (superClassName.Equals("OMI_BaseResource", StringComparison.OrdinalIgnoreCase))) { - //Get the name of the file without schema.mof extension + // Get the name of the file without schema.mof extension if (!(className.Equals(fileNameDefiningClass, StringComparison.OrdinalIgnoreCase))) { PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( @@ -436,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) @@ -444,7 +461,7 @@ internal void ValidateInstanceText(string classText) uint offset = 0; byte[] bytes = null; - if (Platform.IsLinux) + if (Platform.IsLinux || Platform.IsMacOS) { bytes = System.Text.Encoding.UTF8.GetBytes(classText); } @@ -461,70 +478,104 @@ internal void ValidateInstanceText(string classText) namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal { /// - /// + /// + [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", + Justification = "Needed Internal use only")] + internal class DscClassCacheEntry + { + /// + /// Store the RunAs Credentials that this DSC resource will use. + /// + public DSCResourceRunAsCredential DscResRunAsCred; + + /// + /// If we have implicitly imported this resource, we will set this field to true. This will + /// only happen to InBox resources. + /// + public bool IsImportedImplicitly; + + /// + /// A CimClass instance for this resource. + /// + public Microsoft.Management.Infrastructure.CimClass CimClassInstance; + + /// + /// Initializes variables with default values. + /// + public DscClassCacheEntry() : this(DSCResourceRunAsCredential.Default, false, null) { } + + /// + /// Initializes all values. + /// + /// + /// + /// + public DscClassCacheEntry(DSCResourceRunAsCredential aDSCResourceRunAsCredential, bool aIsImportedImplicitly, Microsoft.Management.Infrastructure.CimClass aCimClassInstance) + { + DscResRunAsCred = aDSCResourceRunAsCredential; + IsImportedImplicitly = aIsImportedImplicitly; + CimClassInstance = aCimClassInstance; + } + } + + /// /// [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] 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; private const int IndexModuleVersion = 1; private const int IndexClassName = 2; + private const int IndexFriendlyName = 3; - //Create a list of classes which are not actual DSC resources similar to what we do inside PSDesiredStateConfiguration.psm1 + // 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); + // 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(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); + // 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(StringComparer.OrdinalIgnoreCase); /// /// DSC class cache for this runspace. - /// Cache stores the DSCRunAsBehavior for the class along with actual cim class. + /// Cache stores the DSCRunAsBehavior, cim class and boolean to indicate if an Inbox resource has been implicitly imported. /// - private static Dictionary> ClassCache + private static Dictionary ClassCache { get { - if (t_classCache == null) - { - t_classCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + t_classCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + return t_classCache; } } [ThreadStatic] - private static Dictionary> t_classCache; + private static Dictionary t_classCache; /// - /// DSC classname to source module mapper + /// DSC classname to source module mapper. /// private static Dictionary> ByClassModuleCache { get { - if (t_byClassModuleCache == null) - { - t_byClassModuleCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + t_byClassModuleCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); + return t_byClassModuleCache; } } @@ -533,16 +584,14 @@ private static Dictionary> ByClassModuleCache private static Dictionary> t_byClassModuleCache; /// - /// DSC filename to defined class mapper + /// DSC filename to defined class mapper. /// private static Dictionary> ByFileClassCache { get { - if (t_byFileClassCache == null) - { - t_byFileClassCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + t_byFileClassCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); + return t_byFileClassCache; } } @@ -551,16 +600,14 @@ private static Dictionary> ByClassModuleCache private static Dictionary> t_byFileClassCache; /// - /// Filenames from which we have imported script dynamic keywords + /// Filenames from which we have imported script dynamic keywords. /// private static HashSet ScriptKeywordFileCache { get { - if (t_scriptKeywordFileCache == null) - { - t_scriptKeywordFileCache = new HashSet(StringComparer.OrdinalIgnoreCase); - } + t_scriptKeywordFileCache ??= new HashSet(StringComparer.OrdinalIgnoreCase); + return t_scriptKeywordFileCache; } } @@ -569,20 +616,22 @@ private static HashSet ScriptKeywordFileCache private static HashSet t_scriptKeywordFileCache; /// - /// Default ModuleName and ModuleVersion to use + /// 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 + /// 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. @@ -592,12 +641,14 @@ private static HashSet ScriptKeywordFileCache /// [ThreadStatic] private static bool t_cacheResourcesFromMultipleModuleVersions; + private static bool CacheResourcesFromMultipleModuleVersions { get { return t_cacheResourcesFromMultipleModuleVersions; } + set { t_cacheResourcesFromMultipleModuleVersions = value; @@ -616,12 +667,12 @@ public static void Initialize() /// Initialize the class cache with the default classes in $ENV:SystemDirectory\Configuration. /// /// Collection of any errors encountered during initialization. - /// List of module path from where DSC PS modules will be loaded + /// List of module path from where DSC PS modules will be loaded. public static void Initialize(Collection errors, List modulePathList) { s_tracer.WriteLine("Initializing DSC class cache force={0}"); - if (System.Management.Automation.Platform.IsLinux) + if (Platform.IsLinux || Platform.IsMacOS) { // // Load the base schema files. @@ -635,10 +686,10 @@ public static void Initialize(Collection errors, List moduleP throw new DirectoryNotFoundException("Unable to find DSC schema store at " + dscConfigurationDirectory + ". Please ensure PS DSC for Linux is installed."); } - var resourceBaseFile = Path.Combine(dscConfigurationDirectory, "baseregistration/baseresource.schema.mof"); + var resourceBaseFile = Path.Combine(dscConfigurationDirectory, "BaseRegistration/BaseResource.schema.mof"); ImportClasses(resourceBaseFile, s_defaultModuleInfoForResource, errors); - var metaConfigFile = Path.Combine(dscConfigurationDirectory, "baseregistration/MSFT_DSCMetaConfiguration.mof"); + var metaConfigFile = Path.Combine(dscConfigurationDirectory, "BaseRegistration/MSFT_DSCMetaConfiguration.mof"); ImportClasses(metaConfigFile, s_defaultModuleInfoForResource, errors); var allResourceRoots = new string[] { dscConfigurationDirectory }; @@ -654,7 +705,8 @@ 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); } @@ -664,8 +716,19 @@ public static void Initialize(Collection errors, List moduleP } else { - var systemResourceRoot = Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.System), "Configuration"); - var programFilesDirectory = Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + // DSC SxS scenario + var configSystemPath = Utils.DefaultPowerShellAppBase; + var systemResourceRoot = Path.Combine(configSystemPath, "Configuration"); + var inboxModulePath = "Modules\\PSDesiredStateConfiguration"; + + if (!Directory.Exists(systemResourceRoot)) + { + configSystemPath = Environment.GetFolderPath(Environment.SpecialFolder.System); + systemResourceRoot = Path.Combine(configSystemPath, "Configuration"); + inboxModulePath = InboxDscResourceModulePath; + } + + 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"); @@ -674,13 +737,13 @@ public static void Initialize(Collection errors, List moduleP // Load the base schema files. // ClearCache(); - var resourceBaseFile = Path.Combine(systemResourceRoot, "BaseRegistration\\BaseResource.Schema.mof"); + var resourceBaseFile = Path.Combine(systemResourceRoot, "BaseRegistration\\BaseResource.schema.mof"); ImportClasses(resourceBaseFile, s_defaultModuleInfoForResource, errors); var metaConfigFile = Path.Combine(systemResourceRoot, "BaseRegistration\\MSFT_DSCMetaConfiguration.mof"); ImportClasses(metaConfigFile, s_defaultModuleInfoForResource, errors); - var metaConfigExtensionFile = Path.Combine(systemResourceRoot, "BaseRegistration\\MSFT_MetaConfigurationExtensionClasses.Schema.mof"); + var metaConfigExtensionFile = Path.Combine(systemResourceRoot, "BaseRegistration\\MSFT_MetaConfigurationExtensionClasses.schema.mof"); ImportClasses(metaConfigExtensionFile, DefaultModuleInfoForMetaConfigResource, errors); // @@ -694,19 +757,20 @@ 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); } } // Load Regular and DSC PS modules - bool isInboxResource = false; - List modulePaths = new List(); + bool importInBoxResourcesImplicitly = false; + List modulePaths = new(); if (modulePathList == null || modulePathList.Count == 0) { - modulePaths.Add(Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.System), InboxDscResourceModulePath)); - isInboxResource = true; + modulePaths.Add(Path.Combine(configSystemPath, inboxModulePath)); + importInBoxResourcesImplicitly = true; } else { @@ -724,7 +788,7 @@ public static void Initialize(Collection errors, List moduleP } } - LoadDSCResourceIntoCache(errors, modulePaths, isInboxResource); + LoadDSCResourceIntoCache(errors, modulePaths, importInBoxResourcesImplicitly); } } @@ -732,17 +796,20 @@ public static void Initialize(Collection errors, List moduleP /// Load DSC resources into Cache from moduleFolderPath. /// /// Collection of any errors encountered during initialization. - /// Module path from where DSC PS modules will be loaded - /// + /// Module path from where DSC PS modules will be loaded. + /// /// if module is inbox. /// - private static void LoadDSCResourceIntoCache(Collection errors, List modulePathList, bool isInboxResource) + private static void LoadDSCResourceIntoCache(Collection errors, List modulePathList, bool importInBoxResourcesImplicitly) { foreach (string moduleDir in modulePathList) { - if (!Directory.Exists(moduleDir)) continue; + if (!Directory.Exists(moduleDir)) + { + continue; + } - var dscResourcesPath = Path.Combine(moduleDir, "DSCResources"); + var dscResourcesPath = Path.Combine(moduleDir, "DscResources"); if (Directory.Exists(dscResourcesPath)) { foreach (string resourceDir in Directory.EnumerateDirectories(dscResourcesPath)) @@ -753,7 +820,7 @@ private static void LoadDSCResourceIntoCache(Collection errors, List< continue; } - Tuple moduleInfo = GetModuleInfoHelper(moduleDir, isInboxResource, isPsProviderModule: false); + Tuple moduleInfo = GetModuleInfoHelper(moduleDir, importInBoxResourcesImplicitly, isPsProviderModule: false); if (moduleInfo == null) { continue; @@ -761,7 +828,7 @@ private static void LoadDSCResourceIntoCache(Collection errors, List< foreach (string schemaFile in schemaFiles) { - ImportClasses(schemaFile, moduleInfo, errors); + ImportClasses(schemaFile, moduleInfo, errors, importInBoxResourcesImplicitly); } } } @@ -769,25 +836,26 @@ private static void LoadDSCResourceIntoCache(Collection errors, List< } /// - /// Get the module name and module version + /// Get the module name and module version. /// /// /// Path to the module folder /// - /// - /// if module is inbox. + /// + /// if module is inbox and we are importing resources implicitly /// /// /// Indicate a internal DSC module /// /// - private static Tuple GetModuleInfoHelper(string moduleFolderPath, bool isInboxResource, bool isPsProviderModule) + private static Tuple GetModuleInfoHelper(string moduleFolderPath, bool importInBoxResourcesImplicitly, bool isPsProviderModule) { string moduleName = "PsDesiredStateConfiguration"; - if (!isInboxResource) + if (!importInBoxResourcesImplicitly) { moduleName = Path.GetFileName(moduleFolderPath); } + string manifestPath = Path.Combine(moduleFolderPath, moduleName + ".psd1"); s_tracer.WriteLine("DSC GetModuleVersion: Try retrieving module version information from file: {0}.", manifestPath); @@ -845,15 +913,15 @@ private static Tuple GetModuleInfoHelper(string moduleFolderPat return null; } - //Callback implementation... + // Callback implementation... private static CimClass MyClassCallback(string serverName, string namespaceName, string className) { - foreach (KeyValuePair> cimClass in ClassCache) + foreach (KeyValuePair cimClass in ClassCache) { - string cachedClassName = cimClass.Key.Split(Utils.Separators.Backslash)[IndexClassName]; - if (string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreCase) == 0) + string cachedClassName = cimClass.Key.Split('\\')[IndexClassName]; + if (string.Equals(cachedClassName, className, StringComparison.OrdinalIgnoreCase)) { - return cimClass.Value.Item2; + return cimClass.Value.CimClassInstance; } } @@ -861,17 +929,32 @@ private static CimClass MyClassCallback(string serverName, string namespaceName, } /// - /// Import CIM classes from the given file + /// 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. /// /// /// /// + /// /// - public static List ImportClasses(string path, Tuple moduleInfo, Collection errors) + public static List ImportClasses(string path, Tuple moduleInfo, Collection errors, bool importInBoxResourcesImplicitly = false) { if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } s_tracer.WriteLine("DSC ClassCache: importing file: {0}", path); @@ -887,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) @@ -899,41 +979,50 @@ public static List ImportClasses(string path, Tuple m { // Only add the class once... var className = c.CimSystemProperties.ClassName; - string moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleInfo.Item1, moduleInfo.Item2.ToString(), className); - Tuple cimClassInfo; + string alias = GetFriendlyName(c); + var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; + string moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleInfo.Item1, moduleInfo.Item2.ToString(), className, friendlyName); + DscClassCacheEntry cimClassInfo; if (ClassCache.TryGetValue(moduleQualifiedResourceName, out cimClassInfo)) { - CimClass cimClass = cimClassInfo.Item2; + CimClass cimClass = cimClassInfo.CimClassInstance; + // If this is a nested object and we already have exactly same nested object, we will // 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); } } + if (s_hiddenResourceCache.Contains(className)) { continue; } + if (!CacheResourcesFromMultipleModuleVersions) { // Find & remove the previous version of the resource. - List>> resourceList = FindResourceInCache(moduleInfo.Item1, className); + List> resourceList = FindResourceInCache(moduleInfo.Item1, className, friendlyName); if (resourceList.Count > 0 && !string.IsNullOrEmpty(resourceList[0].Key)) { ClassCache.Remove(resourceList[0].Key); + + // keyword is already defined and it is a Inbox resource, remove it + if (DynamicKeyword.ContainsKeyword(friendlyName) && resourceList[0].Value.IsImportedImplicitly) + { + DynamicKeyword.RemoveKeyword(friendlyName); + } } } - ClassCache[moduleQualifiedResourceName] = new Tuple(DSCResourceRunAsCredential.Default, c); + + ClassCache[moduleQualifiedResourceName] = new DscClassCacheEntry(DSCResourceRunAsCredential.Default, importInBoxResourcesImplicitly, c); ByClassModuleCache[className] = moduleInfo; } @@ -941,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()); @@ -956,13 +1045,13 @@ public static List ImportClasses(string path, Tuple m } /// - /// get text from SecureString + /// Get text from SecureString. /// - /// value of SecureString - /// decoded string + /// Value of SecureString. + /// Decoded string. public static string GetStringFromSecureString(SecureString value) { - string passwordValueToAdd = String.Empty; + string passwordValueToAdd = string.Empty; if (value != null) { @@ -993,54 +1082,58 @@ public static void ClearCache() /// /// /// + /// /// - private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className) + private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className, string resourceName) { - return String.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}", moduleName, moduleVersion, className); + return string.Create(CultureInfo.InvariantCulture, $"{moduleName}\\{moduleVersion}\\{className}\\{resourceName}"); } /// /// Finds resources in the that which matches the specified class and module name. /// - /// Module name - /// Resource type name + /// Module name. + /// Resource type name. + /// Resource friendly name. /// List of found resources in the form of Dictionary{moduleQualifiedName, cimClass}, otherwise empty list. - private static List>> FindResourceInCache(string moduleName, string className) + 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] - where string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreCase) == 0 - && string.Compare(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase) == 0 + let cachedResourceName = splittedName[IndexFriendlyName] + where (string.Equals(cachedResourceName, resourceName, StringComparison.OrdinalIgnoreCase) + || (string.Equals(cachedClassName, className, StringComparison.OrdinalIgnoreCase) + && string.Equals(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase))) select cacheEntry).ToList(); } /// - /// /// /// - public static List> GetCachedClasses() + private static List GetCachedClasses() { return ClassCache.Values.ToList(); } /// - /// Find cached cim classes defined under specified module + /// Find cached cim classes defined under specified module. /// /// - /// List of cached cim classes + /// 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()); - foreach (var pair in ClassCache) + List cachedClasses = new(); + var moduleQualifiedName = string.Create(CultureInfo.InvariantCulture, $"{module.Name}\\{module.Version}"); + foreach (var dscClassCacheEntry in ClassCache) { - if (pair.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase)) + if (dscClassCacheEntry.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase)) { - cachedClasses.Add(pair.Value.Item2); + cachedClasses.Add(dscClassCacheEntry.Value.CimClassInstance); } } + return cachedClasses; } @@ -1051,12 +1144,12 @@ where string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreC /// public static List GetFileDefiningClass(string className) { - List files = new List(); + List files = new(); foreach (var pair in ByFileClassCache) { var file = pair.Key; var classList = pair.Value; - if (classList != null && classList.FirstOrDefault((CimClass c) => string.Equals(c.CimSystemProperties.ClassName, className, StringComparison.OrdinalIgnoreCase)) != null) + if (classList != null && classList.Find((CimClass c) => string.Equals(c.CimSystemProperties.ClassName, className, StringComparison.OrdinalIgnoreCase)) != null) { files.Add(file); } @@ -1083,7 +1176,7 @@ public static List GetCachedClassByFileName(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { - throw PSTraceSource.NewArgumentNullException("fileName"); + throw PSTraceSource.NewArgumentNullException(nameof(fileName)); } List listCimClass; @@ -1101,7 +1194,7 @@ public static List GetCachedClassByModuleName(string moduleName) { if (string.IsNullOrWhiteSpace(moduleName)) { - throw PSTraceSource.NewArgumentNullException("moduleName"); + throw PSTraceSource.NewArgumentNullException(nameof(moduleName)); } var moduleFileName = moduleName + ".schema.mof"; @@ -1118,7 +1211,7 @@ public static List ImportInstances(string path) { if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback); @@ -1137,9 +1230,9 @@ public static List ImportInstances(string path, int schemaValidatio { if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentNullException("path"); - throw new ArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } + if (schemaValidationOption < (int)Microsoft.Management.Infrastructure.Serialization.MofDeserializerSchemaValidationOption.Default || schemaValidationOption > (int)Microsoft.Management.Infrastructure.Serialization.MofDeserializerSchemaValidationOption.Ignore) { @@ -1160,7 +1253,7 @@ public static void ValidateInstanceText(string instanceText) { if (string.IsNullOrEmpty(instanceText)) { - throw PSTraceSource.NewArgumentNullException("instanceText"); + throw PSTraceSource.NewArgumentNullException(nameof(instanceText)); } var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback); @@ -1168,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 @@ -1187,8 +1273,9 @@ private static string GetFriendlyName(CimClass cimClass) } catch (Microsoft.Management.Infrastructure.CimException) { - //exception means no DSCAlias + // exception means no DSCAlias } + return null; } @@ -1197,20 +1284,21 @@ private static string GetFriendlyName(CimClass cimClass) /// public static Collection GetCachedKeywords() { - Collection keywords = new Collection(); + Collection keywords = new(); - foreach (KeyValuePair> cachedClass in ClassCache) + 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]; - var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.Item2, null, cachedClass.Value.Item1); + var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.CimClassInstance, null, cachedClass.Value.DscResRunAsCred); if (keyword != null) { keywords.Add(keyword); } } + return keywords; } @@ -1221,7 +1309,7 @@ public static Collection GetCachedKeywords() /// /// /// If true, don't define the keywords, just create the functions. - /// To Specify RunAsBehavior of the class + /// To Specify RunAsBehavior of the class. private static void CreateAndRegisterKeywordFromCimClass(string moduleName, Version moduleVersion, Microsoft.Management.Infrastructure.CimClass cimClass, Dictionary functionsToDefine, DSCResourceRunAsCredential runAsBehavior) { var keyword = CreateKeywordFromCimClass(moduleName, moduleVersion, cimClass, functionsToDefine, runAsBehavior); @@ -1259,7 +1347,7 @@ private static void CreateAndRegisterKeywordFromCimClass(string moduleName, Vers /// /// /// If true, don't define the keywords, just create the functions. - /// To specify RunAs behavior of the class + /// To specify RunAs behavior of the class. private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Version moduleVersion, Microsoft.Management.Infrastructure.CimClass cimClass, Dictionary functionsToDefine, DSCResourceRunAsCredential runAsBehavior) { var resourceName = cimClass.CimSystemProperties.ClassName; @@ -1269,10 +1357,12 @@ 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; } + var keyword = new DynamicKeyword() { BodyMode = DynamicKeywordBodyMode.Hashtable, @@ -1284,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; } @@ -1335,14 +1425,14 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi if (runAsBehavior == DSCResourceRunAsCredential.NotSupported) { - if (String.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) { // skip adding PsDscRunAsCredential to the dynamic word for the dsc resource. continue; } } // If it's one of our reserved properties, save it for error reporting - if (System.Text.RegularExpressions.Regex.Match(prop.Name, reservedProperties, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedProperty(prop.Name)) { keyword.HasReservedProperties = true; continue; @@ -1400,7 +1490,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi // set the property to mandatory is specified for the resource. if (runAsBehavior == DSCResourceRunAsCredential.Mandatory) { - if (String.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase)) { keyProp.Mandatory = true; } @@ -1441,23 +1531,44 @@ 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); } /// - /// update range restriction for meta configuration keywords + /// Update range restriction for meta configuration keywords /// the restrictions are for /// ConfigurationModeFrequency: 15-44640 - /// RefreshFrequency: 30-44640 + /// RefreshFrequency: 30-44640. /// /// private static void UpdateKnownRestriction(DynamicKeyword keyword) { if ( - string.Compare(keyword.ResourceName, "MSFT_DSCMetaConfigurationV2", - StringComparison.OrdinalIgnoreCase) == 0 + string.Equals(keyword.ResourceName, "MSFT_DSCMetaConfigurationV2", + StringComparison.OrdinalIgnoreCase) || - string.Compare(keyword.ResourceName, "MSFT_DSCMetaConfiguration", - StringComparison.OrdinalIgnoreCase) == 0) + string.Equals(keyword.ResourceName, "MSFT_DSCMetaConfiguration", + StringComparison.OrdinalIgnoreCase)) { if (keyword.Properties["RefreshFrequencyMins"] != null) { @@ -1488,7 +1599,7 @@ public static void LoadDefaultCimKeywords() /// /// Load the default system CIM classes and create the corresponding keywords. /// - /// List of module path from where DSC PS modules will be loaded + /// List of module path from where DSC PS modules will be loaded. public static void LoadDefaultCimKeywords(List modulePathList) { LoadDefaultCimKeywords(null, null, modulePathList, false); @@ -1505,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); @@ -1525,10 +1636,10 @@ public static void LoadDefaultCimKeywords(Collection errors, bool cac /// /// 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. /// Collection of any errors encountered while loading keywords. - /// List of module path from where DSC PS modules will be loaded - /// Allow caching the resources from multiple versions of modules + /// List of module path from where DSC PS modules will be loaded. + /// Allow caching the resources from multiple versions of modules. private static void LoadDefaultCimKeywords(Dictionary functionsToDefine, Collection errors, List modulePathList, bool cacheResourcesFromMultipleModuleVersions) { @@ -1543,9 +1654,9 @@ private static void LoadDefaultCimKeywords(Dictionary funct foreach (var cimClass in GetCachedClasses()) { - var className = cimClass.Item2.CimSystemProperties.ClassName; + var className = cimClass.CimClassInstance.CimSystemProperties.ClassName; var moduleInfo = ByClassModuleCache[className]; - CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.Item2, functionsToDefine, cimClass.Item1); + CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.CimClassInstance, functionsToDefine, cimClass.DscResRunAsCred); } // And add the Node keyword definitions @@ -1779,16 +1890,14 @@ 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", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceInsideNode))); break; } + keywordAst = Ast.GetAncestorAst(keywordAst.Parent); } @@ -1805,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) @@ -1838,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) { @@ -1873,10 +1981,10 @@ private static ParseError[] CheckMandatoryPropertiesPresent(DynamicKeywordStatem /// /// Load DSC resources from specified module. /// - /// Script statement loading the module, can be null + /// Script statement loading the module, can be null. /// Module information, can be null. - /// Name of the resource to be loaded from module - /// List of errors reported by the method + /// Name of the resource to be loaded from module. + /// List of errors reported by the method. public static void LoadResourcesFromModule(IScriptExtent scriptExtent, ModuleSpecification[] moduleSpecifications, string[] resourceNames, @@ -1925,11 +2033,12 @@ 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))); } + return; } } @@ -1960,7 +2069,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, { var dscResourcesPath = Path.Combine(moduleInfo.ModuleBase, "DscResources"); - var resourcesFound = new List(); + var resourcesFound = new List(); LoadPowerShellClassResourcesFromModule(moduleInfo, moduleInfo, resourcesToImport, resourcesFound, errorList, null, true, scriptExtent); if (Directory.Exists(dscResourcesPath)) @@ -2032,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) { @@ -2041,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); } @@ -2063,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", @@ -2135,6 +2243,7 @@ private static void LoadPowerShellClassResourcesFromModule(PSModuleInfo primaryM { scriptPath = moduleInfo.Path; } + ImportKeywordsFromScriptFile(scriptPath, primaryModuleInfo, resourcesToImport, resourcesFound, functionsToDefine, errorList, extent); } @@ -2154,7 +2263,7 @@ private static Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sende if (moduleInfo != null && !string.IsNullOrEmpty(moduleInfo.Path)) { - String asmToCheck = Path.GetDirectoryName(moduleInfo.Path) + "\\" + name.Name + ".dll"; + string asmToCheck = Path.GetDirectoryName(moduleInfo.Path) + "\\" + name.Name + ".dll"; if (File.Exists(asmToCheck)) { return Assembly.ReflectionOnlyLoadFrom(asmToCheck); @@ -2172,7 +2281,6 @@ private static Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sende #endif /// - /// /// /// /// @@ -2201,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; @@ -2225,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"; @@ -2245,17 +2352,18 @@ 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"); } + sb.Append("\n{\n"); ProcessMembers(sb, embeddedInstanceTypes, typeAst, className); - Queue bases = new Queue(); + Queue bases = new(); foreach (var b in typeAst.BaseTypes) { bases.Enqueue(b); @@ -2264,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) @@ -2280,6 +2386,7 @@ private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder s bases.Enqueue(b1); } } + continue; } } @@ -2288,7 +2395,7 @@ private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder s if (type != null) { ProcessMembers(type, sb, embeddedInstanceTypes, className); - var t = type.GetTypeInfo().BaseType; + var t = type.BaseType; if (t != null) { bases.Enqueue(t); @@ -2300,7 +2407,7 @@ private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder s } /// - /// Gets the line no for DSC Class Resource Get/Set/Test methods + /// Gets the line no for DSC Class Resource Get/Set/Test methods. /// /// /// @@ -2310,11 +2417,10 @@ private static bool GetResourceMethodsLineNumber(TypeDefinitionAst typeDefinitio const string setMethodName = "Set"; const string testMethodName = "Test"; - methodsLinePosition = new Dictionary(); ; + 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)) { @@ -2336,7 +2442,7 @@ private static bool GetResourceMethodsLineNumber(TypeDefinitionAst typeDefinitio } /// - /// Gets the line no for DSC Class Resource Get/Set/Test methods + /// Gets the line no for DSC Class Resource Get/Set/Test methods. /// /// /// @@ -2352,14 +2458,15 @@ public static bool GetResourceMethodsLinePosition(PSModuleInfo moduleInfo, strin } IEnumerable resourceDefinitions; - List moduleFiles = new List(); + List moduleFiles = new(); if (moduleInfo.RootModule != null) { moduleFiles.Add(moduleInfo.Path); } + 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); } @@ -2393,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; @@ -2422,7 +2527,7 @@ private static void ProcessMembers(StringBuilder sb, List embeddedInstan mofType = MapTypeToMofType(memberType, member.Name, className, out isArrayType, out embeddedInstanceType, embeddedInstanceTypes); - if (memberType.GetTypeInfo().IsEnum) + if (memberType.IsEnum) { enumNames = Enum.GetNames(memberType); } @@ -2434,19 +2539,17 @@ private static void ProcessMembers(StringBuilder sb, List embeddedInstan out isArrayType, out embeddedInstanceType, embeddedInstanceTypes, ref enumNames); } - string arrayAffix = isArrayType ? "[]" : String.Empty; - sb.AppendFormat(CultureInfo.InvariantCulture, - " {0}{1} {2}{3};\n", - MapAttributesToMof(enumNames, attributes, embeddedInstanceType), - mofType, - member.Name, - arrayAffix); + string mofAttr = MapAttributesToMof(enumNames, attributes, embeddedInstanceType); + string arrayAffix = isArrayType ? "[]" : string.Empty; + + sb.Append( + CultureInfo.InvariantCulture, + $" {mofAttr}{mofType} {member.Name}{arrayAffix};\n"); } } /// - /// /// /// /// @@ -2476,7 +2579,7 @@ private static bool GetResourceDefinitionsFromModule(string fileName, out IEnume return false; } - //BUGBUG - need to fix up how the module gets set. + // BUGBUG - need to fix up how the module gets set. Token[] tokens; ParseError[] errors; var ast = Parser.ParseFile(fileName, out tokens, out errors); @@ -2485,28 +2588,33 @@ 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()); } + errorList.Add(new ParseError(extent, "FailToParseModuleScriptFile", string.Format(CultureInfo.CurrentCulture, ParserStrings.FailToParseModuleScriptFile, fileName, string.Join(Environment.NewLine, errorMessages)))); } + return false; } 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; + } } } + return false; }, false); @@ -2514,7 +2622,6 @@ private static bool GetResourceDefinitionsFromModule(string fileName, out IEnume } /// - /// /// /// /// @@ -2558,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; @@ -2568,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; } } } @@ -2588,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" }, @@ -2622,11 +2728,13 @@ private static bool AreQualifiersSame(CimReadOnlyKeyedCollection o { return false; } + if ((qual.CimType != newQual.CimType) || (qual.Flags != newQual.Flags)) { return false; } + if ((qual.Value == null && newQual.Value != null) || (qual.Value != null && newQual.Value == null) || (qual.Value != null && newQual.Value != null && @@ -2637,8 +2745,10 @@ private static bool AreQualifiersSame(CimReadOnlyKeyedCollection o return false; } } + return true; } + private static bool ArePropertiesSame(CimReadOnlyKeyedCollection oldProperties, CimReadOnlyKeyedCollection newProperties) { if (oldProperties.Count != newProperties.Count) @@ -2660,13 +2770,16 @@ private static bool ArePropertiesSame(CimReadOnlyKeyedCollection embeddedInstanceTypes) + internal static string MapTypeToMofType(Type type, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes) { isArrayType = false; - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { type = Nullable.GetUnderlyingType(type) ?? type; } - if (type.GetTypeInfo().IsEnum) + if (type.IsEnum) { embeddedInstanceType = null; return "string"; @@ -2738,24 +2851,23 @@ internal static string MapTypeToMofType(Type type, String memberName, String cla } } - bool supported = false; bool missingDefaultConstructor = false; - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { if (s_mapPrimitiveDotNetTypeToMof.ContainsKey(type)) { supported = true; } } - else if (!type.GetTypeInfo().IsAbstract) + else if (!type.IsAbstract) { // Must have default constructor, at least 1 public property/field, and no base classes - if (type.GetConstructor(PSTypeExtensions.EmptyTypes) == null) + if (type.GetConstructor(Type.EmptyTypes) == null) { missingDefaultConstructor = true; } - else if (type.GetTypeInfo().BaseType == typeof(object) && + else if (type.BaseType == typeof(object) && (type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 0 || type.GetFields(BindingFlags.Instance | BindingFlags.Public).Length > 0)) { @@ -2798,48 +2910,50 @@ 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) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}key", needComma ? ", " : ""); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}key", needComma ? ", " : string.Empty); needComma = true; } + if (dscProperty.Mandatory) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}required", needComma ? ", " : ""); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}required", needComma ? ", " : string.Empty); needComma = true; } + if (dscProperty.NotConfigurable) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}read", needComma ? ", " : ""); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}read", needComma ? ", " : string.Empty); needComma = true; } + continue; } - var validateSet = attr as ValidateSetAttribute; - if (validateSet != null) + if (attr is ValidateSetAttribute validateSet) { bool valueMapComma = false; - StringBuilder sbValues = new StringBuilder(", Values{"); - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}ValueMap{{", needComma ? ", " : ""); + StringBuilder sbValues = new(", Values{"); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}ValueMap{{", needComma ? ", " : string.Empty); needComma = true; foreach (var value in validateSet.ValidValues) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", valueMapComma ? ", " : "", value); - sbValues.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", valueMapComma ? ", " : "", value); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", valueMapComma ? ", " : string.Empty, value); + sbValues.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", valueMapComma ? ", " : string.Empty, value); valueMapComma = true; } - sb.Append("}"); + + sb.Append('}'); sb.Append(sbValues); - sb.Append("}"); + sb.Append('}'); } } @@ -2852,32 +2966,34 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable if (enumNames != null) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}ValueMap{{", needComma ? ", " : ""); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}ValueMap{{", needComma ? ", " : string.Empty); needComma = false; foreach (var name in enumNames) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", needComma ? ", " : "", name); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", needComma ? ", " : string.Empty, name); needComma = true; } + sb.Append("}, Values{"); needComma = false; foreach (var name in enumNames) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", needComma ? ", " : "", name); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\"{1}\"", needComma ? ", " : string.Empty, name); needComma = true; } - sb.Append("}"); + + sb.Append('}'); } else if (embeddedInstanceType != null) { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}EmbeddedInstance(\"{1}\")", needComma ? ", " : "", embeddedInstanceType); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}EmbeddedInstance(\"{1}\")", needComma ? ", " : string.Empty, embeddedInstanceType); } - sb.Append("]"); + + sb.Append(']'); return sb.ToString(); } /// - /// /// /// /// @@ -2924,6 +3040,7 @@ private static void ProcessEmbeddedInstanceTypes(List embeddedInstanceTy { GenerateMofForAst((TypeDefinitionAst)batchedTypes[i], nestedSb, embeddedInstanceTypes); } + nestedSb.Append('\n'); } @@ -2935,12 +3052,13 @@ 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.GetTypeInfo().GetCustomAttributes().Any()) + if (type.GetCustomAttributes().Any()) { sb.Append(" : OMI_BaseResource"); } + sb.Append("\n{\n"); ProcessMembers(type, sb, embeddedInstanceTypes, className); @@ -2949,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; } @@ -2969,25 +3087,26 @@ private static void ProcessMembers(Type type, StringBuilder sb, List emb { continue; } + memberType = propertyInfo.PropertyType; } // TODO - validate type and name - bool isArrayType; - string embeddedInstanceType; - string mofType = MapTypeToMofType(memberType, member.Name, className, out isArrayType, out embeddedInstanceType, - embeddedInstanceTypes); - string arrayAffix = isArrayType ? "[]" : String.Empty; - - var enumNames = memberType.GetTypeInfo().IsEnum - ? Enum.GetNames(memberType) - : null; - sb.AppendFormat(CultureInfo.InvariantCulture, - " {0}{1} {2}{3};\n", - MapAttributesToMof(enumNames, member.GetCustomAttributes(true), embeddedInstanceType), - mofType, + string mofType = MapTypeToMofType( + memberType, member.Name, - arrayAffix); + 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; + + sb.Append( + CultureInfo.InvariantCulture, + $" {mofAttr}{mofType} {member.Name}{arrayAffix};\n"); } } @@ -3002,7 +3121,7 @@ private static bool ImportKeywordsFromAssembly(PSModuleInfo module, var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback); IEnumerable resourceDefinitions = - assembly.GetTypes().Where(t => t.GetTypeInfo().GetCustomAttributes().Any()); + assembly.GetTypes().Where(static t => t.GetCustomAttributes().Any()); foreach (var r in resourceDefinitions) { @@ -3018,7 +3137,10 @@ private static bool ImportKeywordsFromAssembly(PSModuleInfo module, } } - if (skip) continue; + if (skip) + { + continue; + } var mof = GenerateMofForType(r); @@ -3034,19 +3156,27 @@ private static void ProcessMofForDynamicKeywords(PSModuleInfo module, ICollectio foreach (var c in parser.ParseSchemaMofFileBuffer(mof)) { var className = c.CimSystemProperties.ClassName; + string alias = GetFriendlyName(c); + var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; if (!CacheResourcesFromMultipleModuleVersions) { // Find & remove the previous version of the resource. - List>> resourceList = - FindResourceInCache(module.Name, className); + List> resourceList = FindResourceInCache(module.Name, className, friendlyName); if (resourceList.Count > 0 && !string.IsNullOrEmpty(resourceList[0].Key)) { ClassCache.Remove(resourceList[0].Key); + + // keyword is already defined and it is a Inbox resource, remove it + if (DynamicKeyword.ContainsKeyword(friendlyName) && resourceList[0].Value.IsImportedImplicitly) + { + DynamicKeyword.RemoveKeyword(friendlyName); + } } } - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className); - ClassCache[moduleQualifiedResourceName] = new Tuple(runAsBehavior, c); + + var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); + ClassCache[moduleQualifiedResourceName] = new DscClassCacheEntry(runAsBehavior, false, c); ByClassModuleCache[className] = new Tuple(module.Name, module.Version); resourcesFound.Add(className); CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, runAsBehavior); @@ -3085,22 +3215,22 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou /// /// Full path of the loaded schema file... /// - /// error reported during deserialization + /// Error reported during deserialization. /// public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resourceName, out string schemaFilePath, Dictionary functionsToDefine, Collection errors) { if (module == null) { - throw PSTraceSource.NewArgumentNullException("module"); + throw PSTraceSource.NewArgumentNullException(nameof(module)); } if (resourceName == null) { - throw PSTraceSource.NewArgumentNullException("resourceName"); + throw PSTraceSource.NewArgumentNullException(nameof(resourceName)); } - string dscResourcesPath = Path.Combine(module.ModuleBase, "DSCResources"); - schemaFilePath = Path.Combine(Path.Combine(dscResourcesPath, resourceName), resourceName + ".Schema.mof"); + string dscResourcesPath = Path.Combine(module.ModuleBase, "DscResources"); + schemaFilePath = Path.Combine(Path.Combine(dscResourcesPath, resourceName), resourceName + ".schema.mof"); if (File.Exists(schemaFilePath)) { @@ -3114,26 +3244,29 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou foreach (var c in classes) { CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, DSCResourceRunAsCredential.Default); + ClearImplicitlyImportedFlagFromResourceInClassCache(module, c); } } + return true; } else if (Directory.Exists(dscResourcesPath)) { // // Cannot find the schema file, then resourceName may be a friendly name, - // try to search all DSCResources' schemas under DSCResources folder + // try to search all DscResources' schemas under DscResources folder // 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) { @@ -3143,9 +3276,10 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou foreach (var c in classes) { var alias = GetFriendlyName(c); - if (String.Equals(alias, resourceName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(alias, resourceName, StringComparison.OrdinalIgnoreCase)) { CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, DSCResourceRunAsCredential.Default); + ClearImplicitlyImportedFlagFromResourceInClassCache(module, c); return true; } } @@ -3163,6 +3297,21 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou return false; } + + /// + /// Clear the 'IsImportedImplicitly' flag when explicitly importing a resource. + /// + /// + /// + private static void ClearImplicitlyImportedFlagFromResourceInClassCache(PSModuleInfo module, CimClass cimClass) + { + var className = cimClass.CimSystemProperties.ClassName; + var alias = GetFriendlyName(cimClass); + var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; + var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); + ClassCache[moduleQualifiedResourceName].IsImportedImplicitly = false; + } + /// /// Imports configuration keywords from a .psm1 file. /// @@ -3185,15 +3334,15 @@ public static bool ImportScriptKeywordsFromModule(PSModuleInfo module, string re { if (module == null) { - throw PSTraceSource.NewArgumentNullException("module"); + throw PSTraceSource.NewArgumentNullException(nameof(module)); } if (resourceName == null) { - throw PSTraceSource.NewArgumentNullException("resourceName"); + throw PSTraceSource.NewArgumentNullException(nameof(resourceName)); } - schemaFilePath = Path.Combine(Path.Combine(Path.Combine(module.ModuleBase, "DSCResources"), resourceName), resourceName + ".Schema.psm1"); + schemaFilePath = Path.Combine(Path.Combine(Path.Combine(module.ModuleBase, "DscResources"), resourceName), resourceName + ".Schema.psm1"); if (File.Exists(schemaFilePath) && !s_currentImportingScriptFiles.Contains(schemaFilePath)) { @@ -3203,8 +3352,8 @@ public static bool ImportScriptKeywordsFromModule(PSModuleInfo module, string re if (!ScriptKeywordFileCache.Contains(schemaFilePath)) { // Parsing the file is all that needs to be done to add the keywords - //BUGBUG - need to fix up how the module gets set. - //BUGBUG - should fail somehow if errors is not empty + // BUGBUG - need to fix up how the module gets set. + // BUGBUG - should fail somehow if errors is not empty Token[] tokens; ParseError[] errors; s_currentImportingScriptFiles.Add(schemaFilePath); Parser.ParseFile(schemaFilePath, out tokens, out errors); @@ -3219,10 +3368,10 @@ public static bool ImportScriptKeywordsFromModule(PSModuleInfo module, string re } /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list + /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. /// - /// the malformed resource - /// The referencing resource instance + /// The malformed resource. + /// The referencing resource instance. /// public static ErrorRecord GetBadlyFormedRequiredResourceIdErrorRecord(string badDependsOnReference, string definingResource) { @@ -3234,10 +3383,10 @@ public static ErrorRecord GetBadlyFormedRequiredResourceIdErrorRecord(string bad } /// - /// Returns an error record to use in the case of a malformed resource reference in the exclusive resources list + /// Returns an error record to use in the case of a malformed resource reference in the exclusive resources list. /// - /// the malformed resource - /// The referencing resource instance + /// The malformed resource. + /// The referencing resource instance. /// public static ErrorRecord GetBadlyFormedExclusiveResourceIdErrorRecord(string badExclusiveResourcereference, string definingResource) { @@ -3249,7 +3398,7 @@ public static ErrorRecord GetBadlyFormedExclusiveResourceIdErrorRecord(string ba } /// - /// if a partial configuration is in 'Pull' Mode, it needs a configuration source + /// If a partial configuration is in 'Pull' Mode, it needs a configuration source. /// /// /// @@ -3277,10 +3426,10 @@ public static ErrorRecord DisabledRefreshModeNotValidForPartialConfig(string res } /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list + /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. /// - /// The duplicate resource identifier - /// the node being defined. + /// The duplicate resource identifier. + /// The node being defined. /// The error record to use. public static ErrorRecord DuplicateResourceIdInNodeStatementErrorRecord(string duplicateResourceId, string nodeName) { @@ -3292,7 +3441,7 @@ public static ErrorRecord DuplicateResourceIdInNodeStatementErrorRecord(string d } /// - /// Returns an error record to use in the case of a configuration name is invalid + /// Returns an error record to use in the case of a configuration name is invalid. /// /// /// @@ -3306,7 +3455,7 @@ public static ErrorRecord InvalidConfigurationNameErrorRecord(string configurati } /// - /// Returns an error record to use in the case of the given value for a property is invalid + /// Returns an error record to use in the case of the given value for a property is invalid. /// /// /// @@ -3323,7 +3472,7 @@ public static ErrorRecord InvalidValueForPropertyErrorRecord(string propertyName } /// - /// Returns an error record to use in case the given property is not valid LocalConfigurationManager property + /// Returns an error record to use in case the given property is not valid LocalConfigurationManager property. /// /// /// @@ -3338,7 +3487,7 @@ public static ErrorRecord InvalidLocalConfigurationManagerPropertyErrorRecord(st } /// - /// Returns an error record to use in the case of the given value for a property is not supported + /// Returns an error record to use in the case of the given value for a property is not supported. /// /// /// @@ -3355,7 +3504,7 @@ public static ErrorRecord UnsupportedValueForPropertyErrorRecord(string property } /// - /// Returns an error record to use in the case of no value is provided for a mandatory property + /// Returns an error record to use in the case of no value is provided for a mandatory property. /// /// /// @@ -3371,7 +3520,7 @@ public static ErrorRecord MissingValueForMandatoryPropertyErrorRecord(string key } /// - /// Returns an error record to use in the case of more than one values are provided for DebugMode property + /// Returns an error record to use in the case of more than one values are provided for DebugMode property. /// /// public static ErrorRecord DebugModeShouldHaveOneValue() @@ -3384,7 +3533,7 @@ public static ErrorRecord DebugModeShouldHaveOneValue() } /// - /// Return an error to indicate a value is out of range for a dynamic keyword property + /// Return an error to indicate a value is out of range for a dynamic keyword property. /// /// /// @@ -3402,9 +3551,9 @@ public static ErrorRecord ValueNotInRangeErrorRecord(string property, string nam } /// - /// Returns an error record to use when composite resource and its resource instances both has PsDscRunAsCredentials value + /// Returns an error record to use when composite resource and its resource instances both has PsDscRunAsCredentials value. /// - /// resourceId of resource + /// ResourceId of resource. /// public static ErrorRecord PsDscRunAsCredentialMergeErrorForCompositeResources(string resourceId) { @@ -3470,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)) { @@ -3497,7 +3646,7 @@ public static string GetDSCResourceUsageString(DynamicKeyword keyword) } } - usageString.Append("}"); + usageString.Append('}'); return usageString.ToString(); } @@ -3511,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)) { @@ -3537,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; } @@ -3559,11 +3708,12 @@ 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); } } + private static ScriptBlock s_cimKeywordImplementationFunction; + private const string CimKeywordImplementationFunctionText = @" param ( [Parameter(Mandatory)] @@ -3613,6 +3763,7 @@ function Test-DependsOn $DependsOnVar } } + $value['DependsOn']= $updatedDependsOn if($null -ne $DependsOn) @@ -3731,7 +3882,6 @@ function Test-DependsOn } } - if($null -ne $value['ExclusiveResources']) { # make sure the references are well-formed @@ -3899,7 +4049,6 @@ function Test-DependsOn } } - # Generate the MOF text for this resource instance. # when generate mof text for OMI_ConfigurationDocument we handle below two cases: # 1. we will add versioning related property based on meta configuration instance already process @@ -3925,5 +4074,3 @@ function Test-DependsOn "; } } - - diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs index 7b0a917c11a..22945778c12 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Certificate_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -13,10 +11,9 @@ internal static IEnumerable GetFormatData() { var SignatureTypes_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@"split-path $_.Path") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); @@ -51,10 +48,12 @@ private static IEnumerable ViewsOf_System_Security_Cryptog TableControl.Create() .GroupByProperty("PSParentPath") .AddHeader(width: 41) - .AddHeader() + .AddHeader(width: 20) + .AddHeader(label: "EnhancedKeyUsageList") .StartRowDefinition() .AddPropertyColumn("Thumbprint") .AddPropertyColumn("Subject") + .AddScriptBlockColumn("$_.EnhancedKeyUsageList.FriendlyName") .EndRowDefinition() .EndTable()); } @@ -84,16 +83,16 @@ private static IEnumerable ViewsOf_CertificateProviderType yield return new FormatViewDefinition("ThumbprintWide", WideControl.Create() .GroupByProperty("PSParentPath") - .AddPropertyEntry("ThumbPrint") + .AddPropertyEntry("Thumbprint") .EndWideControl()); yield return new FormatViewDefinition("PathOnly", ListControl.Create() .StartEntry() - .AddItemProperty(@"PSPathPath") + .AddItemProperty(@"PSPath") .EndEntry() .StartEntry(entrySelectedByType: new[] { "Microsoft.PowerShell.Commands.X509StoreLocation" }) - .AddItemProperty(@"PSPathPath") + .AddItemProperty(@"PSPath") .EndEntry() .StartEntry(entrySelectedByType: new[] { "System.Security.Cryptography.X509Certificates.X509Store" }) .AddItemProperty(@"PSPath") @@ -108,10 +107,12 @@ private static IEnumerable ViewsOf_System_Management_Autom .GroupByScriptBlock("split-path $_.Path", customControl: sharedControls[0]) .AddHeader(label: "SignerCertificate", width: 41) .AddHeader() + .AddHeader() .AddHeader(label: "Path") .StartRowDefinition() .AddScriptBlockColumn("$_.SignerCertificate.Thumbprint") .AddPropertyColumn("Status") + .AddPropertyColumn("StatusMessage") .AddScriptBlockColumn("split-path $_.Path -leaf") .EndRowDefinition() .EndTable()); diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Diagnostics_Format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Diagnostics_Format_ps1xml.cs index 6a733afd4d8..e08c7357e63 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Diagnostics_Format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Diagnostics_Format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -24,7 +22,7 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co { yield return new FormatViewDefinition("Counter", TableControl.Create() - .AddHeader(Alignment.Left, label: "Timestamp", width: 25) + .AddHeader(Alignment.Left, label: "Timestamp", width: 26) .AddHeader(Alignment.Left, label: "CounterSamples", width: 100) .StartRowDefinition(wrap: true) .AddPropertyColumn("Timestamp") diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/DotNetTypes_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/DotNetTypes_format_ps1xml.cs index 6e7d3d7eebb..6407a2d7012 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/DotNetTypes_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/DotNetTypes_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -282,6 +280,10 @@ internal static IEnumerable GetFormatData() yield return new ExtendedTypeDefinition( "Microsoft.Management.Infrastructure.CimInstance#__PartialCIMInstance", ViewsOf_Microsoft_Management_Infrastructure_CimInstance___PartialCIMInstance()); + + yield return new ExtendedTypeDefinition( + "System.Threading.Tasks.Task", + ViewsOf_System_Threading_Tasks_Task()); } private static IEnumerable ViewsOf_System_CodeDom_Compiler_CompilerError() @@ -514,7 +516,7 @@ private static IEnumerable ViewsOf_Semantic_Version_With_L .AddHeader(width: 6) .AddHeader(width: 6) .AddHeader(width: 6) - .AddHeader(width: 9) + .AddHeader(width: 15) .AddHeader(width: 11) .StartRowDefinition() .AddPropertyColumn("Major") @@ -879,6 +881,7 @@ private static IEnumerable ViewsOf_Microsoft_Management_In $sourceName = $_.PSComputerName; if($sourceName -eq ""."") {$sourceName = $env:COMPUTERNAME;} + return $sourceName; ") .AddPropertyColumn("Address") @@ -909,26 +912,35 @@ private static IEnumerable ViewsOf_System_Management_Manag $eventType = $_.EventType; if($_.EventType -eq 100) {$eventType = ""BEGIN_SYSTEM_CHANGE"";} + if($_.EventType -eq 101) {$eventType = ""END_SYSTEM_CHANGE"";} + if($_.EventType -eq 102) {$eventType = ""BEGIN_NESTED_SYSTEM_CHANGE"";} + if($_.EventType -eq 103) {$eventType = ""END_NESTED_SYSTEM_CHANGE"";} + return $eventType; ") .AddScriptBlockColumn(@" $RestorePointType = $_.RestorePointType; if($_.RestorePointType -eq 0) { $RestorePointType = ""APPLICATION_INSTALL"";} + if($_.RestorePointType -eq 1) { $RestorePointType = ""APPLICATION_UNINSTALL"";} + if($_.RestorePointType -eq 10) { $RestorePointType = ""DEVICE_DRIVER_INSTALL"";} + if($_.RestorePointType -eq 12) { $RestorePointType = ""MODIFY_SETTINGS"";} + if($_.RestorePointType -eq 13) { $RestorePointType = ""CANCELLED_OPERATION"";} + return $RestorePointType; ") .EndRowDefinition() @@ -954,26 +966,35 @@ private static IEnumerable ViewsOf_Microsoft_Management_In $eventType = $_.EventType; if($_.EventType -eq 100) {$eventType = ""BEGIN_SYSTEM_CHANGE"";} + if($_.EventType -eq 101) {$eventType = ""END_SYSTEM_CHANGE"";} + if($_.EventType -eq 102) {$eventType = ""BEGIN_NESTED_SYSTEM_CHANGE"";} + if($_.EventType -eq 103) {$eventType = ""END_NESTED_SYSTEM_CHANGE"";} + return $eventType; ") .AddScriptBlockColumn(@" $RestorePointType = $_.RestorePointType; if($_.RestorePointType -eq 0) { $RestorePointType = ""APPLICATION_INSTALL"";} + if($_.RestorePointType -eq 1) { $RestorePointType = ""APPLICATION_UNINSTALL"";} + if($_.RestorePointType -eq 10) { $RestorePointType = ""DEVICE_DRIVER_INSTALL"";} + if($_.RestorePointType -eq 12) { $RestorePointType = ""MODIFY_SETTINGS"";} + if($_.RestorePointType -eq 13) { $RestorePointType = ""CANCELLED_OPERATION"";} + return $RestorePointType; ") .EndRowDefinition() @@ -988,7 +1009,7 @@ private static IEnumerable ViewsOf_System_Management_Manag .AddHeader(label: "Description", width: 16) .AddHeader(label: "HotFixID", width: 13) .AddHeader(label: "InstalledBy", width: 20) - .AddHeader(label: "InstalledOn", width: 25) + .AddHeader(label: "InstalledOn", width: 26) .StartRowDefinition() .AddPropertyColumn("__SERVER") .AddPropertyColumn("Description") @@ -1007,7 +1028,7 @@ private static IEnumerable ViewsOf_Microsoft_Management_In .AddHeader(label: "Description", width: 16) .AddHeader(label: "HotFixID", width: 13) .AddHeader(label: "InstalledBy", width: 20) - .AddHeader(label: "InstalledOn", width: 25) + .AddHeader(label: "InstalledOn", width: 26) .StartRowDefinition() .AddPropertyColumn("ComputerName") .AddPropertyColumn("Description") @@ -1692,5 +1713,51 @@ private static IEnumerable ViewsOf_Microsoft_Management_In .EndEntry() .EndControl()); } + + private static IEnumerable ViewsOf_System_Threading_Tasks_Task() + { + // Avoid referencing the Result property in these views to avoid potential + // deadlocks that may occur. Result should only be referenced once the task + // is actually completed. + yield return new FormatViewDefinition( + "System.Threading.Tasks.Task", + TableControl + .Create() + .AddHeader(label: "Id") + .AddHeader(label: "IsCompleted") + .AddHeader(label: "Status") + .StartRowDefinition() + .AddPropertyColumn("Id") + .AddPropertyColumn("IsCompleted") + .AddPropertyColumn("Status") + .EndRowDefinition() + .EndTable()); + + yield return new FormatViewDefinition( + "System.Threading.Tasks.Task", + ListControl + .Create() + .StartEntry() + .AddItemProperty(@"AsyncState") + .AddItemProperty(@"AsyncWaitHandle") + .AddItemProperty(@"CompletedSynchronously") + .AddItemProperty(@"CreationOptions") + .AddItemProperty(@"Exception") + .AddItemProperty(@"Id") + .AddItemProperty(@"IsCanceled") + .AddItemProperty(@"IsCompleted") + .AddItemProperty(@"IsCompletedSuccessfully") + .AddItemProperty(@"IsFaulted") + .AddItemScriptBlock( + @" + if ($_.IsCompleted) { + $_.Result + } + ", + label: "Result") + .AddItemProperty(@"Status") + .EndEntry() + .EndList()); + } } } diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Event_Format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Event_Format_ps1xml.cs index 317cd605172..8b92e3ac391 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Event_Format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Event_Format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -29,7 +27,7 @@ private static IEnumerable ViewsOf_System_Diagnostics_Even yield return new FormatViewDefinition("Default", TableControl.Create() .GroupByProperty("ProviderName", label: "ProviderName") - .AddHeader(width: 25) + .AddHeader(width: 26) .AddHeader(Alignment.Right, width: 8) .AddHeader(width: 16) .AddHeader() 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 f2db072630b..4c8d23971af 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -1,9 +1,8 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; namespace System.Management.Automation.Runspaces { @@ -13,12 +12,11 @@ internal static IEnumerable GetFormatData() { var FileSystemTypes_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@" $_.PSParentPath.Replace(""Microsoft.PowerShell.Core\FileSystem::"", """") ") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); @@ -44,28 +42,57 @@ internal static IEnumerable GetFormatData() private static IEnumerable ViewsOf_FileSystemTypes(CustomControl[] sharedControls) { - const string LengthScriptBlock = -@"if ($_ -is [System.IO.DirectoryInfo]) { return '' } -if ($_.Attributes -band [System.IO.FileAttributes]::Offline) -{ - return '({0})' -f $_.Length -} -return $_.Length"; +#if UNIX + 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", TableControl.Create() .GroupByProperty("PSParentPath", customControl: sharedControls[0]) .AddHeader(Alignment.Left, label: "Mode", width: 7) - .AddHeader(Alignment.Right, label: "LastWriteTime", width: 25) + .AddHeader(Alignment.Right, label: "LastWriteTime", width: 26) .AddHeader(Alignment.Right, label: "Length", width: 14) - .AddHeader() + .AddHeader(Alignment.Left, label: "Name") + .StartRowDefinition(wrap: true) + .AddPropertyColumn("ModeWithoutHardLink") + .AddPropertyColumn("LastWriteTimeString") + .AddPropertyColumn("LengthString") + .AddPropertyColumn("NameString") + .EndRowDefinition() + .EndTable()); + + yield return new FormatViewDefinition("childrenWithHardlink", + TableControl.Create() + .GroupByProperty("PSParentPath", customControl: sharedControls[0]) + .AddHeader(Alignment.Left, label: "Mode", width: 7) + .AddHeader(Alignment.Right, label: "LastWriteTime", width: 26) + .AddHeader(Alignment.Right, label: "Length", width: 14) + .AddHeader(Alignment.Left, label: "Name") .StartRowDefinition(wrap: true) .AddPropertyColumn("Mode") - .AddScriptBlockColumn(@" - [String]::Format(""{0,10} {1,8}"", $_.LastWriteTime.ToString(""d""), $_.LastWriteTime.ToString(""t"")) - ") - .AddScriptBlockColumn(LengthScriptBlock) - .AddPropertyColumn("Name") + .AddPropertyColumn("LastWriteTimeString") + .AddPropertyColumn("LengthString") + .AddPropertyColumn("NameString") .EndRowDefinition() .EndTable()); @@ -74,7 +101,7 @@ private static IEnumerable ViewsOf_FileSystemTypes(CustomC .GroupByProperty("PSParentPath", customControl: sharedControls[0]) .StartEntry(entrySelectedByType: new[] { "System.IO.FileInfo" }) .AddItemProperty(@"Name") - .AddItemScriptBlock(LengthScriptBlock, label: "Length") + .AddItemProperty("LengthString", label: "Length") .AddItemProperty(@"CreationTime") .AddItemProperty(@"LastWriteTime") .AddItemProperty(@"LastAccessTime") 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 b92f990b9c4..fc5ddc5ab9a 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/HelpV3_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Internal; @@ -102,9 +100,11 @@ internal static IEnumerable GetFormatData() .AddScriptBlockExpressionBinding(@"function GetParam { if(-not $_.Parameters) { return $null } + $_.Parameters.Parameter | ForEach-Object { if($_.type) { $param = ""[$($_.type.name)] `$$($_.name), "" } else { $param = ""[object] `$$($_.name), "" } + $params += $param } @@ -130,7 +130,7 @@ internal static IEnumerable GetFormatData() .AddScriptBlockExpressionBinding(StringUtil.Format(@"Set-StrictMode -Off if (($_.relatedLinks -ne $()) -and ($_.relatedLinks.navigationLink -ne $()) -and ($_.relatedLinks.navigationLink.Length -ne 0)) {{ - "" {0}`""get-help $($_.Details.Name) -online`"""" + "" {0}`""Get-Help $($_.Details.Name) -Online`"""" }}", HelpDisplayStrings.RelatedLinksHelpInfo)) .EndFrame() .EndEntry() @@ -139,7 +139,8 @@ internal static IEnumerable GetFormatData() var MamlParameterControl = CustomControl.Create() .StartEntry() .AddScriptBlockExpressionBinding( -@"$optional = $_.required -ne 'true' +@"Set-StrictMode -Off +$optional = $_.required -ne 'true' $positional = (($_.position -ne $()) -and ($_.position -ne '') -and ($_.position -notmatch 'named') -and ([int]$_.position -ne $())) $parameterValue = if ($null -ne $_.psobject.Members['ParameterValueGroup']) { "" {$($_.ParameterValueGroup.ParameterValue -join ' | ')}"" @@ -174,7 +175,7 @@ internal static IEnumerable GetFormatData() .EndControl(); var sharedControls = new CustomControl[] { - null,//MamlParameterValueGroupControl, + null, //MamlParameterValueGroupControl, MamlParameterControl, MamlTypeControl, MamlParameterValueControl, @@ -256,19 +257,11 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo( .EndEntry() .EndControl(); - var control6 = CustomControl.Create() - .StartEntry() - .AddText(StringUtil.Format("[{0}]", HelpDisplayStrings.CommonWorkflowParameters)) - .EndEntry() - .EndControl(); - var control5 = CustomControl.Create() .StartEntry() .AddPropertyExpressionBinding(@"name") .AddText(" ") .AddPropertyExpressionBinding(@"Parameter", enumerateCollection: true, customControl: sharedControls[1]) - .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.WorkflowCommonParameters -eq $true", customControl: control6) - .AddText(" ") .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.CommonParameters -eq $true", customControl: control7) .AddNewline() .AddNewline() @@ -342,18 +335,6 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .EndEntry() .EndControl(); - var control15 = CustomControl.Create() - .StartEntry() - .AddText(HelpDisplayStrings.CommonWorkflowParameters) - .AddNewline() - .StartFrame(leftIndent: 4) - .AddText(HelpDisplayStrings.BaseWorkflowCmdletInformation) - .EndFrame() - .AddNewline() - .AddNewline() - .EndEntry() - .EndControl(); - var control14 = CustomControl.Create() .StartEntry() .AddText("-") @@ -378,21 +359,11 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .EndEntry() .EndControl(); - var control11 = CustomControl.Create() - .StartEntry() - .AddText("[") - .AddText(HelpDisplayStrings.CommonWorkflowParameters) - .AddText("]") - .EndEntry() - .EndControl(); - var control10 = CustomControl.Create() .StartEntry() .AddPropertyExpressionBinding(@"name") .AddText(" ") .AddPropertyExpressionBinding(@"Parameter", enumerateCollection: true, customControl: sharedControls[1]) - .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.WorkflowCommonParameters -eq $true", customControl: control11) - .AddText(" ") .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.CommonParameters -eq $true", customControl: control12) .AddNewline() .AddNewline() @@ -431,7 +402,6 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddNewline() .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"Parameters", customControl: control13) - .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.WorkflowCommonParameters -eq $true", customControl: control15) .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.CommonParameters -eq $true", customControl: control16) .AddScriptBlockExpressionBinding(@" ", selectedByScript: "($_.CommonParameters -eq $false) -and ($_.parameters.parameter.count -eq 0)", customControl: control17) .EndFrame() @@ -501,18 +471,6 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .EndEntry() .EndControl(); - var control29 = CustomControl.Create() - .StartEntry() - .AddText(HelpDisplayStrings.CommonWorkflowParameters) - .AddNewline() - .StartFrame(leftIndent: 4) - .AddText(HelpDisplayStrings.BaseWorkflowCmdletInformation) - .EndFrame() - .AddNewline() - .AddNewline() - .EndEntry() - .EndControl(); - var control28 = CustomControl.Create() .StartEntry() .AddText(HelpDisplayStrings.NamedParameter) @@ -567,6 +525,9 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddText(HelpDisplayStrings.ParameterIsDynamic) .AddPropertyExpressionBinding(@"isDynamic") .AddNewline() + .AddText(HelpDisplayStrings.AcceptsWildCardCharacters) + .AddPropertyExpressionBinding(@"globbing") + .AddNewline() .AddNewline() .EndFrame() .EndEntry() @@ -586,21 +547,11 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .EndEntry() .EndControl(); - var control21 = CustomControl.Create() - .StartEntry() - .AddText("[") - .AddText(HelpDisplayStrings.CommonWorkflowParameters) - .AddText("]") - .EndEntry() - .EndControl(); - var control20 = CustomControl.Create() .StartEntry() .AddPropertyExpressionBinding(@"name") .AddText(" ") .AddPropertyExpressionBinding(@"Parameter", enumerateCollection: true, customControl: sharedControls[1]) - .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.WorkflowCommonParameters -eq $true", customControl: control21) - .AddText(" ") .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.CommonParameters -eq $true", customControl: control22) .AddNewline() .AddNewline() @@ -639,7 +590,6 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddNewline() .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"Parameters", customControl: control23) - .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.WorkflowCommonParameters -eq $true", customControl: control29) .AddScriptBlockExpressionBinding(@" ", selectedByScript: "$_.CommonParameters -eq $true", customControl: control30) .AddScriptBlockExpressionBinding(@" ", selectedByScript: "($_.CommonParameters -eq $false) -and ($_.parameters.parameter.count -eq 0)", customControl: control31) .AddNewline() @@ -764,6 +714,9 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddText(HelpDisplayStrings.ParameterIsDynamic) .AddPropertyExpressionBinding(@"isDynamic") .AddNewline() + .AddText(HelpDisplayStrings.AcceptsWildCardCharacters) + .AddPropertyExpressionBinding(@"globbing") + .AddNewline() .AddNewline() .EndFrame() .EndEntry() @@ -931,18 +884,18 @@ private static IEnumerable ViewsOf_DscResourceHelpInfo_Ful .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() @@ -1041,18 +994,18 @@ private static IEnumerable ViewsOf_DscResourceHelpInfo_Det .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() @@ -1116,18 +1069,18 @@ private static IEnumerable ViewsOf_DscResourceHelpInfo(Cus .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() @@ -1225,18 +1178,18 @@ private static IEnumerable ViewsOf_PSClassHelpInfo_FullVie .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() @@ -1341,18 +1294,18 @@ private static IEnumerable ViewsOf_PSClassHelpInfo_Detaile .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() @@ -1418,18 +1371,18 @@ private static IEnumerable ViewsOf_PSClassHelpInfo(CustomC .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Name + "" -Full""") + .AddText(@"""") .AddNewline() .AddCustomControlExpressionBinding(sharedControls[10]) .EndFrame() 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 3e1fb82f9df..6825ec14e6c 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Internal; @@ -142,23 +140,13 @@ internal static IEnumerable GetFormatData() .EndEntry() .EndControl(); - var CommonWorkflowParametersControl = CustomControl.Create() - .StartEntry() - .AddScriptBlockExpressionBinding(StringUtil.Format(@"$wfp = $_.psobject.Properties['WorkflowCommonParameters'] -if ($null -ne $wfp -and $wfp.Value) -{{ - '[{0}] ' -}}", HelpDisplayStrings.CommonWorkflowParameters)) - .EndEntry() - .EndControl(); - var RelatedLinksHelpInfoControl = CustomControl.Create() .StartEntry() .StartFrame(leftIndent: 4) .AddScriptBlockExpressionBinding(StringUtil.Format(@"Set-StrictMode -Off if (($_.relatedLinks -ne $()) -and ($_.relatedLinks.navigationLink -ne $()) -and ($_.relatedLinks.navigationLink.Length -ne 0)) {{ - "" {0}`""get-help $($_.Details.Name) -online`"""" + "" {0}`""Get-Help $($_.Details.Name) -Online`"""" }}", HelpDisplayStrings.RelatedLinksHelpInfo)) .EndFrame() .EndEntry() @@ -272,7 +260,6 @@ internal static IEnumerable GetFormatData() .AddPropertyExpressionBinding(@"name") .AddText(" ") .AddPropertyExpressionBinding(@"Parameter", enumerateCollection: true, customControl: MamlParameterControl) - .AddCustomControlExpressionBinding(CommonWorkflowParametersControl) .AddText("[" + HelpDisplayStrings.CommonParameters + "]") .AddNewline(2) .EndEntry() @@ -363,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") @@ -371,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() @@ -415,7 +405,6 @@ internal static IEnumerable GetFormatData() MamlPossibleValueControl, MamlTrueFalseShortControl, MamlIndentedSyntaxControl, - CommonWorkflowParametersControl, MamlSyntaxControl, MamlTypeWithDescriptionControl, RelatedLinksHelpInfoControl, @@ -580,26 +569,26 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo(Cus .AddText(HelpDisplayStrings.RelatedLinks) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[20]) + .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[19]) .EndFrame() .AddNewline() .AddText(HelpDisplayStrings.RemarksSection) .AddNewline() .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo + "\"") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText("\".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo + "\"") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText("\".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() - .AddCustomControlExpressionBinding(sharedControls[19]) + .AddCustomControlExpressionBinding(sharedControls[18]) .EndFrame() .EndEntry() .EndControl()); @@ -636,7 +625,6 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Det .AddNewline() .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"Parameters", customControl: control9) - .AddCustomControlExpressionBinding(sharedControls[16]) .AddText(HelpDisplayStrings.CommonParameters) .AddNewline() .StartFrame(leftIndent: 4) @@ -653,20 +641,20 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Det .StartFrame(leftIndent: 4) .AddText(HelpDisplayStrings.ExampleHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -examples""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Examples""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.VerboseHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -detailed""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Detailed""") + .AddText(@"""") .AddNewline() .AddText(HelpDisplayStrings.FullHelpInfo) .AddText(@"""") - .AddScriptBlockExpressionBinding(@"""get-help "" + $_.Details.Name + "" -full""") - .AddText(@""".") + .AddScriptBlockExpressionBinding(@"""Get-Help "" + $_.Details.Name + "" -Full""") + .AddText(@"""") .AddNewline() - .AddCustomControlExpressionBinding(sharedControls[19]) + .AddCustomControlExpressionBinding(sharedControls[18]) .EndFrame() .EndEntry() .EndControl()); @@ -692,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() @@ -713,7 +698,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .AddText(HelpDisplayStrings.NonHyphenTerminatingErrors) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"nonTerminatingError", enumerateCollection: true, customControl: sharedControls[21]) + .AddPropertyExpressionBinding(@"nonTerminatingError", enumerateCollection: true, customControl: sharedControls[20]) .EndFrame() .EndEntry() .EndControl(); @@ -723,20 +708,20 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .AddText(HelpDisplayStrings.TerminatingErrors) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"terminatingError", enumerateCollection: true, customControl: sharedControls[21]) + .AddPropertyExpressionBinding(@"terminatingError", enumerateCollection: true, customControl: sharedControls[20]) .EndFrame() .EndEntry() .EndControl(); var control12 = CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"ReturnValue", enumerateCollection: true, customControl: sharedControls[18]) + .AddPropertyExpressionBinding(@"ReturnValue", enumerateCollection: true, customControl: sharedControls[17]) .EndEntry() .EndControl(); var control11 = CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"InputType", enumerateCollection: true, customControl: sharedControls[18]) + .AddPropertyExpressionBinding(@"InputType", enumerateCollection: true, customControl: sharedControls[17]) .EndEntry() .EndControl(); @@ -749,7 +734,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .AddText(HelpDisplayStrings.Parameters) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"Parameters", customControl: sharedControls[23]) + .AddPropertyExpressionBinding(@"Parameters", customControl: sharedControls[22]) .EndFrame() .AddText(HelpDisplayStrings.InputType) .AddNewline() @@ -780,7 +765,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .AddText(HelpDisplayStrings.RelatedLinks) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[20]) + .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[19]) .EndFrame() .EndEntry() .EndControl()); @@ -948,7 +933,7 @@ private static IEnumerable ViewsOf_ProviderHelpInfo(Custom .AddText(HelpDisplayStrings.RelatedLinks) .AddNewline() .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[20]) + .AddPropertyExpressionBinding(@"relatedLinks", customControl: sharedControls[19]) .EndFrame() .EndEntry() .EndControl()); @@ -1087,7 +1072,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Par CustomControl.Create() .StartEntry() .StartFrame(leftIndent: 4) - .AddCustomControlExpressionBinding(sharedControls[23]) + .AddCustomControlExpressionBinding(sharedControls[22]) .EndFrame() .EndEntry() .EndControl()); @@ -1098,7 +1083,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Par yield return new FormatViewDefinition("MamlCommandParameterView", CustomControl.Create() .StartEntry() - .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[22]) + .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[21]) .EndEntry() .EndControl()); } @@ -1108,7 +1093,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Syn yield return new FormatViewDefinition("MamlCommandSyntax", CustomControl.Create() .StartEntry() - .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[17]) + .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[16]) .EndEntry() .EndControl()); } @@ -1128,7 +1113,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_inp yield return new FormatViewDefinition("MamlInputTypes", CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"InputType", enumerateCollection: true, customControl: sharedControls[18]) + .AddPropertyExpressionBinding(@"InputType", enumerateCollection: true, customControl: sharedControls[17]) .EndEntry() .EndControl()); } @@ -1138,7 +1123,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_non yield return new FormatViewDefinition("MamlNonTerminatingErrors", CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"nonTerminatingError", enumerateCollection: true, customControl: sharedControls[21]) + .AddPropertyExpressionBinding(@"nonTerminatingError", enumerateCollection: true, customControl: sharedControls[20]) .EndEntry() .EndControl()); } @@ -1148,7 +1133,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_ter yield return new FormatViewDefinition("MamlTerminatingErrors", CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"terminatingError", enumerateCollection: true, customControl: sharedControls[21]) + .AddPropertyExpressionBinding(@"terminatingError", enumerateCollection: true, customControl: sharedControls[20]) .EndEntry() .EndControl()); } @@ -1158,7 +1143,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_rel yield return new FormatViewDefinition("MamlRelatedLinks", CustomControl.Create() .StartEntry() - .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[20]) + .AddScriptBlockExpressionBinding(@"$_", customControl: sharedControls[19]) .EndEntry() .EndControl()); } @@ -1168,7 +1153,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_ret yield return new FormatViewDefinition("MamlReturnTypes", CustomControl.Create() .StartEntry() - .AddPropertyExpressionBinding(@"ReturnValue", enumerateCollection: true, customControl: sharedControls[18]) + .AddPropertyExpressionBinding(@"ReturnValue", enumerateCollection: true, customControl: sharedControls[17]) .EndEntry() .EndControl()); } 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 e19d0dc9a55..24d99d4dd4b 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -13,29 +11,15 @@ internal static IEnumerable GetFormatData() { var AvailableModules_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) + .StartFrame() .AddText(FileSystemProviderStrings.DirectoryDisplayGrouping) .AddScriptBlockExpressionBinding(@"Split-Path -Parent $_.Path | ForEach-Object { if([Version]::TryParse((Split-Path $_ -Leaf), [ref]$null)) { Split-Path -Parent $_} else {$_} } | Split-Path -Parent") - .AddNewline() - .EndFrame() - .EndEntry() - .EndControl(); - - var ByteCollection_GroupHeader = CustomControl.Create() - .StartEntry() - .StartFrame() - .AddScriptBlockExpressionBinding(@" - $header = "" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"" - if($_.Path) { $header = "" "" + [Microsoft.PowerShell.Commands.UtilityResources]::FormatHexPathPrefix + $_.Path + ""`r`n`r`n"" + $header } - $header - ") .EndFrame() .EndEntry() .EndControl(); var sharedControls = new CustomControl[] { - AvailableModules_GroupingFormat, - ByteCollection_GroupHeader + AvailableModules_GroupingFormat }; yield return new ExtendedTypeDefinition( @@ -62,6 +46,10 @@ internal static IEnumerable GetFormatData() "Microsoft.PowerShell.Commands.MatchInfo", ViewsOf_Microsoft_PowerShell_Commands_MatchInfo()); + yield return new ExtendedTypeDefinition( + "Deserialized.Microsoft.PowerShell.Commands.MatchInfo", + ViewsOf_Deserialized_Microsoft_PowerShell_Commands_MatchInfo()); + yield return new ExtendedTypeDefinition( "System.Management.Automation.PSVariable", ViewsOf_System_Management_Automation_PSVariable()); @@ -145,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()); @@ -153,9 +149,16 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.ScriptBlock", ViewsOf_System_Management_Automation_ScriptBlock()); - yield return new ExtendedTypeDefinition( + var extendedError = new ExtendedTypeDefinition( + "System.Management.Automation.ErrorRecord#PSExtendedError", + ViewsOf_System_Management_Automation_GetError()); + extendedError.TypeNames.Add("System.Exception#PSExtendedError"); + yield return extendedError; + + var errorRecord_Exception = new ExtendedTypeDefinition( "System.Management.Automation.ErrorRecord", ViewsOf_System_Management_Automation_ErrorRecord()); + yield return errorRecord_Exception; yield return new ExtendedTypeDefinition( "System.Management.Automation.WarningRecord", @@ -169,14 +172,6 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.InformationRecord", ViewsOf_System_Management_Automation_InformationRecord()); - yield return new ExtendedTypeDefinition( - "Microsoft.PowerShell.Commands.ByteCollection", - ViewsOf_Microsoft_PowerShell_Commands_ByteCollection(sharedControls)); - - yield return new ExtendedTypeDefinition( - "System.Exception", - ViewsOf_System_Exception()); - yield return new ExtendedTypeDefinition( "System.Management.Automation.CommandParameterSetInfo", ViewsOf_System_Management_Automation_CommandParameterSetInfo()); @@ -232,6 +227,10 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.PSModuleInfo", ViewsOf_System_Management_Automation_PSModuleInfo()); + yield return new ExtendedTypeDefinition( + "System.Management.Automation.ExperimentalFeature", + ViewsOf_System_Management_Automation_ExperimentalFeature()); + var td46 = new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject", ViewsOf_Microsoft_PowerShell_Commands_BasicHtmlWebResponseObject()); @@ -248,6 +247,54 @@ internal static IEnumerable GetFormatData() yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.PSRunspaceDebug", ViewsOf_Microsoft_PowerShell_Commands_PSRunspaceDebug()); + + yield return new ExtendedTypeDefinition( + "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()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus()); + + yield return new ExtendedTypeDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus", + ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TraceStatus()); + + 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() @@ -335,9 +382,26 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co yield return new FormatViewDefinition("history", TableControl.Create() .AddHeader(Alignment.Right, width: 4) + .AddHeader(Alignment.Right, label: "Duration", width: 12) .AddHeader() .StartRowDefinition() .AddPropertyColumn("Id") + .AddScriptBlockColumn(@" + if ($_.Duration.TotalHours -ge 10) { + return ""{0}:{1:mm}:{1:ss}.{1:fff}"" -f [int]$_.Duration.TotalHours, $_.Duration + } + elseif ($_.Duration.TotalHours -ge 1) { + $formatString = ""h\:mm\:ss\.fff"" + } + elseif ($_.Duration.TotalMinutes -ge 1) { + $formatString = ""m\:ss\.fff"" + } + else { + $formatString = ""s\.fff"" + } + + $_.Duration.ToString($formatString) + ") .AddPropertyColumn("CommandLine") .EndRowDefinition() .EndTable()); @@ -353,7 +417,17 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co yield return new FormatViewDefinition("MatchInfo", CustomControl.Create() .StartEntry() - .AddScriptBlockExpressionBinding(@"$_.ToString(((get-location).path))") + .AddScriptBlockExpressionBinding(@"$_.ToEmphasizedString(((get-location).path))") + .EndEntry() + .EndControl()); + } + + private static IEnumerable ViewsOf_Deserialized_Microsoft_PowerShell_Commands_MatchInfo() + { + yield return new FormatViewDefinition("MatchInfo", + CustomControl.Create() + .StartEntry() + .AddScriptBlockExpressionBinding(@"$_.Line") .EndEntry() .EndControl()); } @@ -641,7 +715,6 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemProperty(@"Description") .AddItemProperty(@"Capabilities") .AddItemProperty(@"ImplementingType") - .AddItemProperty(@"AssemblyInfo") .EndEntry() .EndList()); } @@ -655,7 +728,6 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemProperty(@"CommandType") .AddItemProperty(@"Definition") .AddItemProperty(@"Path") - .AddItemProperty(@"AssemblyInfo") .AddItemProperty(@"DLL") .AddItemProperty(@"HelpFile") .AddItemProperty(@"ParameterSets") @@ -692,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", @@ -713,33 +818,253 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndControl()); } + // This generates a custom view for ErrorRecords and Exceptions making + // specific nested types defined in $expandTypes visible. It also handles + // IEnumerable types. Nested types are indented by 4 spaces. + private static IEnumerable ViewsOf_System_Management_Automation_GetError() + { + yield return new FormatViewDefinition("GetErrorInstance", + CustomControl.Create() + .GroupByProperty("PSErrorIndex", label: "ErrorIndex") + .StartEntry() + .AddScriptBlockExpressionBinding(@" + Set-StrictMode -Off + + $maxDepth = 10 + $ellipsis = ""`u{2026}"" + $resetColor = '' + $errorColor = '' + $accentColor = '' + + 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 + + $expandTypes = @( + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) + + # if object is an Exception, add an ExceptionType property + if ($obj -is [Exception]) { + $obj | Add-Member -NotePropertyName Type -NotePropertyValue $obj.GetType().FullName -ErrorAction Ignore + } + + # first find the longest property so we can indent properly + $propLength = 0 + foreach ($prop in $obj.PSObject.Properties) { + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { + $propLength = $prop.Name.Length + } + } + + $addedProperty = $false + foreach ($prop in $obj.PSObject.Properties) { + + # don't show empty properties or our added property for $error[index] + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { + $addedProperty = $true + $null = $output.Append($prefix) + $null = $output.Append($accentColor) + $null = $output.Append($prop.Name) + $propNameIndent = ' ' * ($propLength - $prop.Name.Length) + $null = $output.Append($propNameIndent) + $null = $output.Append(' : ') + $null = $output.Append($resetColor) + + $newIndent = $indent + 4 + + # only show nested objects that are Exceptions, ErrorRecords, or types defined in $expandTypes and types not in $ignoreTypes + if ($prop.Value -is [Exception] -or $prop.Value -is [System.Management.Automation.ErrorRecord] -or + $expandTypes -contains $prop.TypeNameOfValue -or ($prop.TypeNames -ne $null -and $expandTypes -contains $prop.TypeNames[0])) { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $prop.Value $newIndent ($depth + 1))) + } + } + # `TargetSite` has many members that are not useful visually, so we have a reduced view of the relevant members + elseif ($prop.Name -eq 'TargetSite' -and $prop.Value.GetType().Name -eq 'RuntimeMethodInfo') { + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $targetSite = [PSCustomObject]@{ + Name = $prop.Value.Name + DeclaringType = $prop.Value.DeclaringType + MemberType = $prop.Value.MemberType + Module = $prop.Value.Module + } + + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $targetSite $newIndent ($depth + 1))) + } + } + # `StackTrace` is handled specifically because the lines are typically long but necessary so they are left justified without additional indentation + elseif ($prop.Name -eq 'StackTrace') { + # for a stacktrace which is usually quite wide with info, we left justify it + $null = $output.Append($newline) + $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 -is [System.Collections.IDictionary]) { + $isFirstElement = $true + foreach ($key in ($prop.Value.Keys | Sort-Object)) { + if ($isFirstElement) { + $null = $output.Append($newline) + } + + if ($key -eq 'Authorization') { + $null = $output.Append(""${prefix} ${accentColor}${key} : ${resetColor}${ellipsis}${newline}"") + } + else { + $null = $output.Append(""${prefix} ${accentColor}${key} : ${resetColor}$($prop.Value[$key])${newline}"") + } + + $isFirstElement = $false + } + } + # if the object implements IEnumerable and not a string, we try to show each object + # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here + elseif (!($prop.Value -is [System.String]) -and $prop.Value.GetType().GetInterface('IEnumerable') -ne $null -and $prop.Name -ne 'Data') { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $isFirstElement = $true + foreach ($value in $prop.Value) { + $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))) + } + $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 ' : ' + $valueIndent = ' ' * ($propLength + 3) + # need to trim any extra whitespace already in the text + foreach ($line in $value.Split($newline)) { + if (!$isFirstLine) { + $null = $output.Append(""${newline}${prefix}${valueIndent}"") + } + $null = $output.Append($line.Trim()) + $isFirstLine = $false + } + } + else { + $null = $output.Append($value) + } + } + } + + $null = $output.Append($newline) + } + } + + # if we had added nested properties, we need to remove the last newline + if ($addedProperty) { + $null = $output.Remove($output.Length - $newline.Length, $newline.Length) + } + + $output.ToString() + } + + # Add back original typename and remove PSExtendedError + if ($_.PSObject.TypeNames.Contains('System.Management.Automation.ErrorRecord#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Management.Automation.ErrorRecord') + $null = $_.PSObject.TypeNames.Remove('System.Management.Automation.ErrorRecord#PSExtendedError') + } + elseif ($_.PSObject.TypeNames.Contains('System.Exception#PSExtendedError')) { + $_.PSObject.TypeNames.Add('System.Exception') + $null = $_.PSObject.TypeNames.Remove('System.Exception#PSExtendedError') + } + + Show-ErrorRecord $_ + ") + .EndEntry() + .EndControl()); + } + private static IEnumerable ViewsOf_System_Management_Automation_ErrorRecord() { yield return new FormatViewDefinition("ErrorInstance", CustomControl.Create(outOfBand: true) .StartEntry() - .AddScriptBlockExpressionBinding(@" - if (($_.FullyQualifiedErrorId -ne ""NativeCommandErrorMessage"" -and $_.FullyQualifiedErrorId -ne ""NativeCommandError"") -and $ErrorView -ne ""CategoryView"") + .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) { if ($myinv.MyCommand.Path) { - $myinv.MyCommand.Path + "" : "" + $myinv.MyCommand.Path + ' : ' } + break } + ([System.Management.Automation.CommandTypes]::Script) { if ($myinv.MyCommand.ScriptBlock) { - $myinv.MyCommand.ScriptBlock.ToString() + "" : "" + $myinv.MyCommand.ScriptBlock.ToString() + ' : ' } + break } default @@ -748,78 +1073,360 @@ private static IEnumerable ViewsOf_System_Management_Autom { if ($myinv.MyCommand.Name) { - $myinv.MyCommand.Name + "" : "" + $myinv.MyCommand.Name + ' : ' } } else { - $myinv.InvocationName + "" : "" + $myinv.InvocationName + ' : ' } + break } } } elseif ($myinv -and $myinv.InvocationName) { - $myinv.InvocationName + "" : "" + $myinv.InvocationName + ' : ' } } - ") - .AddScriptBlockExpressionBinding(@" - if ($_.FullyQualifiedErrorId -eq ""NativeCommandErrorMessage"" -or $_.FullyQualifiedErrorId -eq ""NativeCommandError"") { - $_.Exception.Message - } - else - { - $myinv = $_.InvocationInfo - if ($myinv -and ($myinv.MyCommand -or ($_.CategoryInfo.Category -ne 'ParserError'))) { - $posmsg = $myinv.PositionMessage - } else { - $posmsg = """" + + $errorColor + $commandPrefix + """) + .AddScriptBlockExpressionBinding( + """ + Set-StrictMode -Off + $ErrorActionPreference = 'Stop' + trap { 'Error found in error view definition: ' + $_.Exception.Message } + $newline = [Environment]::Newline + + $resetColor = '' + $errorColor = '' + $accentColor = '' + + if ($Host.UI.SupportsVirtualTerminal -and ([string]::IsNullOrEmpty($env:__SuppressAnsiEscapeSequences))) { + $resetColor = $PSStyle.Reset + $errorColor = $PSStyle.Formatting.Error + $accentColor = $PSStyle.Formatting.ErrorAccent + } + + function Get-ConciseViewPositionMessage { + + # returns a string cut to last whitespace + function Get-TruncatedString($string, [int]$length) { + + if ($string.Length -le $length) { + return $string + } + + return ($string.Substring(0,$length) -split '\s',-2)[0] } - if ($posmsg -ne """") - { - $posmsg = ""`n"" + $posmsg + $posmsg = '' + $headerWhitespace = '' + $offsetWhitespace = '' + $message = '' + $prefix = '' + + # 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 + + # - 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}" + } + else { + $posmsg = "${resetcolor}$($myinv.ScriptName):$($myinv.ScriptLineNumber)${newline}" + } + } + else { + $posmsg = "${newline}" + } + + if ($useTargetObject) { + $scriptLineNumber = $err.TargetObject.Line + $scriptLineNumberLength = $err.TargetObject.Line.ToString().Length + } + else { + $scriptLineNumber = $myinv.ScriptLineNumber + $scriptLineNumberLength = $myinv.ScriptLineNumber.ToString().Length + } + + if ($scriptLineNumberLength -gt 4) { + $headerWhitespace = ' ' * ($scriptLineNumberLength - 4) + } + + $lineWhitespace = '' + if ($scriptLineNumberLength -lt 4) { + $lineWhitespace = ' ' * (4 - $scriptLineNumberLength) + } + + $verticalBar = '|' + $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) + $line = $positionMessage[1].Substring(1) # skip the '+' at the start + $highlightLine = $positionMessage[$positionMessage.Count - 1].Substring(1) + $offsetLength = $highlightLine.Trim().Length + $offsetInLine = $highlightLine.IndexOf('~') + } + + if (-not $line.EndsWith($newline)) { + $line += $newline + } + + # don't color the whole line + if ($offsetLength -lt $line.Length - 1) { + $line = $line.Insert($offsetInLine + $offsetLength, $resetColor).Insert($offsetInLine, $accentColor) + } + + $posmsg += "${accentColor}${lineWhitespace}${ScriptLineNumber} ${verticalBar} ${resetcolor}${line}" + $offsetWhitespace = ' ' * $offsetInLine + $prefix = "${accentColor}${headerWhitespace} ${verticalBar} ${errorColor}" + if ($highlightLine -ne '') { + $posMsg += "${prefix}${highlightLine}${newline}" + } + $message = "${prefix}" + } + + if (! $err.ErrorDetails -or ! $err.ErrorDetails.Message) { + 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] + } + elseif ($err.Exception) { + $message += $err.Exception.Message + } + elseif ($err.Message) { + $message += $err.Message + } + else { + $message += $err.ToString() + } } + else { + $message += $err.ErrorDetails.Message + } + + # 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 = [System.Management.Automation.Internal.StringDecorated]::new($prefix).ContentLength + $prefixVtLength = $prefix.Length - $prefixLength + + # replace newlines in message so it lines up correct + $message = $message.Replace($newline, ' ').Replace("`n", ' ').Replace("`t", ' ') + + $windowWidth = 120 + if ($Host.UI.RawUI -ne $null) { + $windowWidth = $Host.UI.RawUI.WindowSize.Width + } + + if ($windowWidth -gt 0 -and ($message.Length - $prefixVTLength) -gt $windowWidth) { + $sb = [Text.StringBuilder]::new() + $substring = Get-TruncatedString -string $message -length ($windowWidth + $prefixVTLength) + $null = $sb.Append($substring) + $remainingMessage = $message.Substring($substring.Length).Trim() + $null = $sb.Append($newline) + while (($remainingMessage.Length + $prefixLength) -gt $windowWidth) { + $subMessage = $prefix + $remainingMessage + $substring = Get-TruncatedString -string $subMessage -length ($windowWidth + $prefixVtLength) + + if ($substring.Length - $prefix.Length -gt 0) + { + $null = $sb.Append($substring) + $null = $sb.Append($newline) + $remainingMessage = $remainingMessage.Substring($substring.Length - $prefix.Length).Trim() + } + else + { + break + } + } + $null = $sb.Append($prefix + $remainingMessage.Trim()) + $message = $sb.ToString() + } - if ( & { Set-StrictMode -Version 1; $_.PSMessageDetails } ) { - $posmsg = "" : "" + $_.PSMessageDetails + $posmsg + $message += $newline } - $indent = 4 + $posmsg += "${errorColor}" + $message - $errorCategoryMsg = & { Set-StrictMode -Version 1; $_.ErrorCategory_Message } - if ($null -ne $errorCategoryMsg) - { - $indentString = ""+ CategoryInfo : "" + $_.ErrorCategory_Message + $reason = 'Error' + if ($err.Exception -and $err.Exception.WasThrownFromThrowStatement) { + $reason = 'Exception' } - else + # 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 $myinv.MyCommand.Name -and (Get-Command -Name $myinv.MyCommand -ErrorAction Ignore)) { - $indentString = ""+ CategoryInfo : "" + $_.CategoryInfo + $reason = $myinv.MyCommand + } + # If it's a scriptblock, better to show the command in the scriptblock that had the error + elseif ($err.CategoryInfo.Activity) { + $reason = $err.CategoryInfo.Activity + } + elseif ($myinv.MyCommand) { + $reason = $myinv.MyCommand + } + elseif ($myinv.InvocationName) { + $reason = $myinv.InvocationName + } + elseif ($err.CategoryInfo.Category) { + $reason = $err.CategoryInfo.Category + } + elseif ($err.CategoryInfo.Reason) { + $reason = $err.CategoryInfo.Reason } - $posmsg += ""`n"" + $indentString - $indentString = ""+ FullyQualifiedErrorId : "" + $_.FullyQualifiedErrorId - $posmsg += ""`n"" + $indentString + $errorMsg = 'Error' - $originInfo = & { Set-StrictMode -Version 1; $_.OriginInfo } - if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) - { - $indentString = ""+ PSComputerName : "" + $originInfo.PSComputerName - $posmsg += ""`n"" + $indentString + "${errorColor}${reason}: ${posmsg}${resetcolor}" + } + + $myinv = $_.InvocationInfo + $err = $_ + if (!$myinv -and $_.ErrorRecord -and $_.ErrorRecord.InvocationInfo) { + $err = $_.ErrorRecord + $myinv = $err.InvocationInfo + } + + if ($err.FullyQualifiedErrorId -eq 'NativeCommandErrorMessage' -or $err.FullyQualifiedErrorId -eq 'NativeCommandError') { + return "${errorColor}$($err.Exception.Message)${resetcolor}" + } + + 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 ($ErrorView -eq ""CategoryView"") { - $_.CategoryInfo.GetMessage() + if ($ErrorView -eq 'ConciseView') { + $recommendedAction = $_.ErrorDetails.RecommendedAction + if (-not [String]::IsNullOrWhiteSpace($recommendedAction)) { + $recommendedAction = $newline + + ${errorColor} + + ' Recommendation: ' + + $recommendedAction + + ${resetcolor} } - elseif (! $_.ErrorDetails -or ! $_.ErrorDetails.Message) { - $_.Exception.Message + $posmsg + ""`n "" - } else { - $_.ErrorDetails.Message + $posmsg + + if ($err.PSMessageDetails) { + $posmsg = "${errorColor}${posmsg}" } - } - ") + return $posmsg + $recommendedAction + } + + $indent = 4 + + $errorCategoryMsg = $err.ErrorCategory_Message + + if ($null -ne $errorCategoryMsg) + { + $indentString = '+ CategoryInfo : ' + $err.ErrorCategory_Message + } + else + { + $indentString = '+ CategoryInfo : ' + $err.CategoryInfo + } + + $posmsg += $newline + $indentString + + $indentString = "+ FullyQualifiedErrorId : " + $err.FullyQualifiedErrorId + $posmsg += $newline + $indentString + + $originInfo = $err.OriginInfo + + if (($null -ne $originInfo) -and ($null -ne $originInfo.PSComputerName)) + { + $indentString = "+ PSComputerName : " + $originInfo.PSComputerName + $posmsg += $newline + $indentString + } + + $finalMsg = if ($err.ErrorDetails.Message) { + $err.ErrorDetails.Message + $posmsg + } else { + $err.Exception.Message + $posmsg + } + + "${errorColor}${finalMsg}${resetcolor}" + """) .EndEntry() .EndControl()); } @@ -854,29 +1461,6 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndControl()); } - private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_ByteCollection(CustomControl[] sharedControls) - { - yield return new FormatViewDefinition("ByteCollection", - CustomControl.Create() - .GroupByScriptBlock("if($_.Path) { $_.Path } else { $_.GetHashCode() }", customControl: sharedControls[1]) - .StartEntry() - .StartFrame() - .AddScriptBlockExpressionBinding(@"$_.ToString()") - .EndFrame() - .EndEntry() - .EndControl()); - } - - private static IEnumerable ViewsOf_System_Exception() - { - yield return new FormatViewDefinition("Exception", - CustomControl.Create(outOfBand: true) - .StartEntry() - .AddScriptBlockExpressionBinding(@"$_.Message") - .EndEntry() - .EndControl()); - } - private static IEnumerable ViewsOf_System_Management_Automation_CommandParameterSetInfo() { var FmtParameterAttributes = CustomControl.Create() @@ -969,7 +1553,7 @@ private static IEnumerable ViewsOf_System_Management_Autom } ") .AddScriptBlockColumn(@" - if ($_.ConnectionInfo -is [System.Management.Automation.Runspaces.WSManConnectionInfo]) + if ($null -ne $_.ConnectionInfo) { ""Remote"" } @@ -1186,6 +1770,14 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co .EndTable()); } + private const string PreReleaseStringScriptBlock = @" + if ($_.PrivateData -and + $_.PrivateData.ContainsKey('PSData') -and + $_.PrivateData.PSData.ContainsKey('PreRelease')) + { + $_.PrivateData.PSData.PreRelease + }"; + private static IEnumerable ViewsOf_ModuleInfoGrouping(CustomControl[] sharedControls) { yield return new FormatViewDefinition("Module", @@ -1193,12 +1785,29 @@ private static IEnumerable ViewsOf_ModuleInfoGrouping(Cust .GroupByScriptBlock("Split-Path -Parent $_.Path | ForEach-Object { if([Version]::TryParse((Split-Path $_ -Leaf), [ref]$null)) { Split-Path -Parent $_} else {$_} } | Split-Path -Parent", customControl: sharedControls[0]) .AddHeader(Alignment.Left, width: 10) .AddHeader(Alignment.Left, width: 10) + .AddHeader(Alignment.Left, label: "PreRelease", width: 10) .AddHeader(Alignment.Left, width: 35) + .AddHeader(Alignment.Left, width: 9, label: "PSEdition") .AddHeader(Alignment.Left, label: "ExportedCommands") .StartRowDefinition() .AddPropertyColumn("ModuleType") .AddPropertyColumn("Version") + .AddScriptBlockColumn(PreReleaseStringScriptBlock) .AddPropertyColumn("Name") + .AddScriptBlockColumn(@" + $result = [System.Collections.ArrayList]::new() + $editions = $_.CompatiblePSEditions + if (-not $editions) + { + $editions = @('Desktop') + } + + foreach ($edition in $editions) + { + $result += $edition.Substring(0,4) + } + + ($result | Sort-Object) -join ','") .AddScriptBlockColumn("$_.ExportedCommands.Keys") .EndRowDefinition() .EndTable()); @@ -1210,11 +1819,13 @@ private static IEnumerable ViewsOf_System_Management_Autom TableControl.Create() .AddHeader(Alignment.Left, width: 10) .AddHeader(Alignment.Left, width: 10) + .AddHeader(Alignment.Left, label: "PreRelease", width: 10) .AddHeader(Alignment.Left, width: 35) .AddHeader(Alignment.Left, label: "ExportedCommands") .StartRowDefinition() .AddPropertyColumn("ModuleType") .AddPropertyColumn("Version") + .AddScriptBlockColumn(PreReleaseStringScriptBlock) .AddPropertyColumn("Name") .AddScriptBlockColumn("$_.ExportedCommands.Keys") .EndRowDefinition() @@ -1233,6 +1844,9 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemProperty(@"Description") .AddItemProperty(@"ModuleType") .AddItemProperty(@"Version") + .AddItemScriptBlock( + PreReleaseStringScriptBlock, + label: "PreRelease") .AddItemProperty(@"NestedModules") .AddItemScriptBlock(@"$_.ExportedFunctions.Keys", label: "ExportedFunctions") .AddItemScriptBlock(@"$_.ExportedCmdlets.Keys", label: "ExportedCmdlets") @@ -1242,6 +1856,33 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndList()); } + private static IEnumerable ViewsOf_System_Management_Automation_ExperimentalFeature() + { + yield return new FormatViewDefinition("ExperimentalFeature", + TableControl.Create() + .AddHeader(Alignment.Left, width: 35) + .AddHeader(Alignment.Right, width: 7) + .AddHeader(Alignment.Left, width: 35) + .AddHeader(Alignment.Left) + .StartRowDefinition() + .AddPropertyColumn("Name") + .AddPropertyColumn("Enabled") + .AddPropertyColumn("Source") + .AddPropertyColumn("Description") + .EndRowDefinition() + .EndTable()); + + yield return new FormatViewDefinition("ExperimentalFeature", + ListControl.Create() + .StartEntry() + .AddItemProperty("Name") + .AddItemProperty("Enabled") + .AddItemProperty("Source") + .AddItemProperty("Description") + .EndEntry() + .EndList()); + } + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_BasicHtmlWebResponseObject() { yield return new FormatViewDefinition("Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject", @@ -1252,13 +1893,15 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co .AddItemScriptBlock(@" $result = $_.Content $result = $result.Substring(0, [Math]::Min($result.Length, 200) ) - if($result.Length -eq 200) { $result += ""..."" } + if($result.Length -eq 200) { $result += ""`u{2026}"" } + $result ", label: "Content") .AddItemScriptBlock(@" $result = $_.RawContent $result = $result.Substring(0, [Math]::Min($result.Length, 200) ) - if($result.Length -eq 200) { $result += ""..."" } + if($result.Length -eq 200) { $result += ""`u{2026}"" } + $result ", label: "RawContent") .AddItemProperty(@"Headers") @@ -1282,7 +1925,8 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co .AddItemScriptBlock(@" $result = $_.RawContent $result = $result.Substring(0, [Math]::Min($result.Length, 200) ) - if($result.Length -eq 200) { $result += ""..."" } + if($result.Length -eq 200) { $result += ""`u{2026}"" } + $result ", label: "RawContent") .AddItemProperty(@"Headers") @@ -1323,5 +1967,365 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co .EndRowDefinition() .EndTable()); } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_MarkdownRender_MarkdownOptionInfo() + { + yield return new FormatViewDefinition("Microsoft.PowerShell.MarkdownRender.PSMarkdownOptionInfo", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header1')", label: "Header1") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header2')", label: "Header2") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header3')", label: "Header3") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header4')", label: "Header4") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header5')", label: "Header5") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Header6')", label: "Header6") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Code')", label: "Code") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Link')", label: "Link") + .AddItemScriptBlock(@"$_.AsEscapeSequence('Image')", label: "Image") + .AddItemScriptBlock(@"$_.AsEscapeSequence('EmphasisBold')", label: "EmphasisBold") + .AddItemScriptBlock(@"$_.AsEscapeSequence('EmphasisItalics')", label: "EmphasisItalics") + .EndEntry() + .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( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Ping", width: 4) + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 25) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Right, label: "BufferSize(B)", width: 10) + .AddHeader(Alignment.Left, label: "Status", width: 16) + .StartRowDefinition() + .AddPropertyColumn("Ping") + .AddPropertyColumn("Source") + .AddPropertyColumn("DisplayAddress") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.BufferSize + } + ") + .AddPropertyColumn("Status") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_PingMtuStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+PingMtuStatus", + TableControl.Create() + .AddHeader(Alignment.Left, label: "Source", width: 16) + .AddHeader(Alignment.Left, label: "Address", width: 25) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Left, label: "Status", width: 16) + .AddHeader(Alignment.Right, label: "MtuSize(B)", width: 7) + .StartRowDefinition() + .AddPropertyColumn("Source") + .AddPropertyColumn("DisplayAddress") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("Status") + .AddPropertyColumn("MtuSize") + .EndRowDefinition() + .GroupByProperty("Destination") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_TestConnectionCommand_TraceStatus() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.TestConnectionCommand+TraceStatus", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Hop", width: 3) + .AddHeader(Alignment.Left, label: "Hostname", width: 25) + .AddHeader(Alignment.Right, label: "Ping", width: 4) + .AddHeader(Alignment.Right, label: "Latency(ms)", width: 7) + .AddHeader(Alignment.Left, label: "Status", width: 16) + .AddHeader(Alignment.Left, label: "Source", width: 12) + .AddHeader(Alignment.Left, label: "TargetAddress", width: 15) + .StartRowDefinition() + .AddPropertyColumn("Hop") + .AddScriptBlockColumn(@" + if ($_.Hostname) { + $_.HostName + } + else { + '*' + } + ") + .AddPropertyColumn("Ping") + .AddScriptBlockColumn(@" + if ($_.Status -eq 'TimedOut') { + '*' + } + else { + $_.Latency + } + ") + .AddPropertyColumn("Status") + .AddPropertyColumn("Source") + .AddPropertyColumn("TargetAddress") + .EndRowDefinition() + .GroupByProperty("Target") + .EndTable()); + } + + private static IEnumerable ViewsOf_Microsoft_PowerShell_Commands_ByteCollection() + { + yield return new FormatViewDefinition( + "Microsoft.PowerShell.Commands.ByteCollection", + TableControl.Create() + .AddHeader(Alignment.Right, label: "Offset", width: 16) + .AddHeader(Alignment.Left, label: "Bytes\n00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F", width: 47) + .AddHeader(Alignment.Left, label: "Ascii", width: 16) + .StartRowDefinition() + .AddPropertyColumn("HexOffset") + .AddPropertyColumn("HexBytes") + .AddPropertyColumn("Ascii") + .EndRowDefinition() + .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/DefaultFormatters/PowerShellTrace_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellTrace_format_ps1xml.cs index 03dd42b3eb2..c958ac9f49d 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellTrace_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellTrace_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs index 05f07cb6c55..c1498af0bf1 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Registry_format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -13,10 +11,9 @@ internal static IEnumerable GetFormatData() { var Registry_GroupingFormat = CustomControl.Create() .StartEntry() - .StartFrame(leftIndent: 4) - .AddText("Hive: ") + .StartFrame() + .AddText(" Hive: ") .AddScriptBlockExpressionBinding(@"$_.PSParentPath.Replace(""Microsoft.PowerShell.Core\Registry::"", """")") - .AddNewline() .EndFrame() .EndEntry() .EndControl(); @@ -47,7 +44,8 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider | Format-List | Out-String | Sort).Trim() $result = $result.Substring(0, [Math]::Min($result.Length, 5000) ) - if($result.Length -eq 5000) { $result += ""..."" } + if($result.Length -eq 5000) { $result += ""`u{2026}"" } + $result ") .EndRowDefinition() diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/WSMan_Format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/WSMan_Format_ps1xml.cs index cec51adf5d1..3ac1cdf35b1 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/WSMan_Format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/WSMan_Format_ps1xml.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs index 736f5339997..e59cae40146 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs @@ -1,50 +1,49 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; using System.Collections.Generic; +using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Language; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// facade class to provide context information to process - /// exceptions + /// Facade class to provide context information to process + /// exceptions. /// internal sealed class TerminatingErrorContext { internal TerminatingErrorContext(PSCmdlet command) { if (command == null) - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); _command = command; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(ErrorRecord errorRecord) { _command.ThrowTerminatingError(errorRecord); } - private PSCmdlet _command; + private readonly PSCmdlet _command; } - /// - /// helper class to invoke a command in a secondary pipeline. + /// Helper class to invoke a command in a secondary pipeline. /// NOTE: this implementation does not return any error messages - /// that invoked pipelines might generate + /// that invoked pipelines might generate. /// internal sealed class CommandWrapper : IDisposable { /// - /// Initialize the command before executing + /// Initialize the command before executing. /// - /// ExecutionContext used to create sub pipeline - /// name of the command to run - /// Type of the command to run + /// ExecutionContext used to create sub pipeline. + /// Name of the command to run. + /// Type of the command to run. internal void Initialize(ExecutionContext execContext, string nameOfCommand, Type typeOfCommand) { _context = execContext; @@ -53,11 +52,11 @@ internal void Initialize(ExecutionContext execContext, string nameOfCommand, Typ } /// - /// add a parameter to the command invocation. - /// It needs to be called before any execution takes place + /// Add a parameter to the command invocation. + /// It needs to be called before any execution takes place. /// - /// name of the parameter - /// value of the parameter + /// Name of the parameter. + /// Value of the parameter. internal void AddNamedParameter(string parameterName, object parameterValue) { _commandParameterList.Add( @@ -67,12 +66,11 @@ internal void AddNamedParameter(string parameterName, object parameterValue) false)); } - /// - /// send an object to the pipeline + /// Send an object to the pipeline. /// - /// object to process - /// Array of objects out of the success pipeline + /// Object to process. + /// Array of objects out of the success pipeline. internal Array Process(object o) { if (_pp == null) @@ -86,16 +84,16 @@ internal Array Process(object o) } /// - /// shut down the pipeline + /// Shut down the pipeline. /// - /// Array of objects out of the success pipeline + /// Array of objects out of the success pipeline. internal Array ShutDown() { if (_pp == null) { // if Process() never got called, no sub pipeline // ever got created, hence we just return an empty array - return new object[0]; + return Array.Empty(); } PipelineProcessor ppTemp = _pp; @@ -121,7 +119,7 @@ private void DelayedInternalInitialize() } /// - /// just dispose the pipeline processor + /// Just dispose the pipeline processor. /// public void Dispose() { @@ -136,29 +134,29 @@ 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; } /// - /// base class for the command-let's we expose + /// Base class for the command-let's we expose /// it contains a reference to the implementation - /// class it wraps + /// class it wraps. /// public abstract class FrontEndCommandBase : PSCmdlet, IDisposable { #region Command Line Switches /// - /// This parameter specifies the current pipeline object + /// 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 /// - /// hook up the calls from the implementation object + /// Hook up the calls from the implementation object /// and then call the implementation's Begin() /// protected override void BeginProcessing() @@ -174,7 +172,7 @@ protected override void BeginProcessing() } /// - /// call the implementation + /// Call the implementation. /// protected override void ProcessRecord() { @@ -182,7 +180,7 @@ protected override void ProcessRecord() } /// - /// call the implementation + /// Call the implementation. /// protected override void EndProcessing() { @@ -190,7 +188,7 @@ protected override void EndProcessing() } /// - /// call the implementation + /// Call the implementation. /// protected override void StopProcessing() { @@ -198,18 +196,18 @@ protected override void StopProcessing() } /// - /// callback for the implementation to obtain a reference to the Cmdlet object + /// Callback for the implementation to obtain a reference to the Cmdlet object. /// - /// Cmdlet reference + /// Cmdlet reference. protected virtual PSCmdlet OuterCmdletCall() { return this; } /// - /// callback for the implementation to get the current pipeline object + /// Callback for the implementation to get the current pipeline object. /// - /// current object from the pipeline + /// Current object from the pipeline. protected virtual PSObject InputObjectCall() { // just bind to the input object parameter @@ -217,9 +215,9 @@ protected virtual PSObject InputObjectCall() } /// - /// callback for the implementation to write objects + /// Callback for the implementation to write objects. /// - /// object to be written + /// Object to be written. protected virtual void WriteObjectCall(object value) { // just call Monad API @@ -227,16 +225,15 @@ protected virtual void WriteObjectCall(object value) } /// - /// reference to the implementation command that this class - /// is wrapping + /// Reference to the implementation command that this class + /// is wrapping. /// internal ImplementationCommandBase implementation = null; - #region IDisposable Implementation /// - /// default implementation just delegates to internal helper + /// Default implementation just delegates to internal helper. /// /// This method calls GC.SuppressFinalize public void Dispose() @@ -247,7 +244,7 @@ public void Dispose() } /// - /// Dispose pattern implementation + /// Dispose pattern implementation. /// /// protected virtual void Dispose(bool disposing) @@ -259,7 +256,7 @@ protected virtual void Dispose(bool disposing) } /// - /// Do-nothing implementation: derived classes will override as see fit + /// Do-nothing implementation: derived classes will override as see fit. /// protected virtual void InternalDispose() { @@ -273,41 +270,41 @@ protected virtual void InternalDispose() } /// - /// implementation class to be called by the outer command - /// In order to properly work, the callbacks have to be properly set by the outer command + /// Implementation class to be called by the outer command + /// In order to properly work, the callbacks have to be properly set by the outer command. /// internal class ImplementationCommandBase : IDisposable { /// - /// inner version of CommandBase.BeginProcessing() + /// Inner version of CommandBase.BeginProcessing() /// internal virtual void BeginProcessing() { } /// - /// inner version of CommandBase.ProcessRecord() + /// Inner version of CommandBase.ProcessRecord() /// internal virtual void ProcessRecord() { } /// - /// inner version of CommandBase.EndProcessing() + /// Inner version of CommandBase.EndProcessing() /// internal virtual void EndProcessing() { } /// - /// inner version of CommandBase.StopProcessing() + /// Inner version of CommandBase.StopProcessing() /// internal virtual void StopProcessing() { } /// - /// retrieve the current input pipeline object + /// Retrieve the current input pipeline object. /// internal virtual PSObject ReadObject() { @@ -317,9 +314,9 @@ internal virtual PSObject ReadObject() } /// - /// write an object to the pipeline + /// Write an object to the pipeline. /// - /// object to write to the pipeline + /// Object to write to the pipeline. internal virtual void WriteObject(object o) { // delegate to the front end object @@ -329,7 +326,7 @@ internal virtual void WriteObject(object o) // callback methods to get to the outer Monad Cmdlet /// - /// get a hold of the Monad outer Cmdlet + /// Get a hold of the Monad outer Cmdlet. /// /// internal virtual PSCmdlet OuterCmdlet() @@ -347,41 +344,40 @@ internal void CreateTerminatingErrorContext() } /// - /// delegate definition to get to the outer command-let + /// Delegate definition to get to the outer command-let. /// internal delegate PSCmdlet OuterCmdletCallback(); /// - /// callback to get to the outer command-let + /// Callback to get to the outer command-let. /// internal OuterCmdletCallback OuterCmdletCall; // callback to the methods to get an object and write an object /// - /// delegate definition to get to the current pipeline input object + /// Delegate definition to get to the current pipeline input object. /// internal delegate PSObject InputObjectCallback(); /// - /// delegate definition to write object + /// Delegate definition to write object. /// internal delegate void WriteObjectCallback(object o); /// - /// callback to read object + /// Callback to read object. /// internal InputObjectCallback InputObjectCall; /// - /// callback to write object + /// Callback to write object. /// internal WriteObjectCallback WriteObjectCall; - #region IDisposable Implementation /// - /// default implementation just delegates to internal helper + /// Default implementation just delegates to internal helper. /// /// This method calls GC.SuppressFinalize public void Dispose() @@ -400,7 +396,7 @@ private void Dispose(bool disposing) } /// - /// Do-nothing implementation: derived classes will override as see fit + /// Do-nothing implementation: derived classes will override as see fit. /// protected virtual void InternalDispose() { @@ -409,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 14ca8b34fdf..be31a1db9a8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -1,15 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.IO; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -20,7 +20,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal class InnerFormatShapeCommandBase : ImplementationCommandBase { /// - /// constructor to set up the formatting context + /// Constructor to set up the formatting context. /// internal InnerFormatShapeCommandBase() { @@ -28,24 +28,24 @@ internal InnerFormatShapeCommandBase() } /// - /// enum listing the possible states the context is in + /// Enum listing the possible states the context is in. /// internal enum FormattingContextState { none, document, group } /// - /// context manager: stack to keep track in which context - /// the formatter is + /// Context manager: stack to keep track in which context + /// the formatter is. /// protected Stack contextManager = new Stack(); } /// - /// core inner implementation for format/xxx commands + /// Core inner implementation for format/xxx commands. /// internal class InnerFormatShapeCommand : InnerFormatShapeCommandBase { /// - /// constructor to glue to the CRO + /// Constructor to glue to the CRO. /// internal InnerFormatShapeCommand(FormatShape shape) { @@ -60,8 +60,9 @@ internal static int FormatEnumerationLimit() // Win8: 192504 if (LocalPipeline.GetExecutionContextFromTLS() != null) { - enumLimitVal = LocalPipeline.GetExecutionContextFromTLS().SessionState.PSVariable. - GetValue("global:" + InitialSessionState.FormatEnumerationLimit); + enumLimitVal = LocalPipeline.GetExecutionContextFromTLS() + .SessionState.PSVariable + .GetValue("global:" + InitialSessionState.FormatEnumerationLimit); } } // Eat the following exceptions, enumerationLimit will use the default value @@ -83,13 +84,13 @@ internal override void BeginProcessing() // Get the Format Enumeration Limit. _enumerationLimit = InnerFormatShapeCommand.FormatEnumerationLimit(); - _expressionFactory = new MshExpressionFactory(); + _expressionFactory = new PSPropertyExpressionFactory(); _formatObjectDeserializer = new FormatObjectDeserializer(this.TerminatingErrorContext); } /// - /// execution entry point + /// Execution entry point. /// internal override void ProcessRecord() { @@ -118,6 +119,7 @@ internal override void ProcessRecord() ProcessObject(PSObjectHelper.AsPSObject(obj)); } } + break; case EnumerableExpansion.Both: { @@ -129,12 +131,14 @@ internal override void ProcessRecord() ProcessObject(PSObjectHelper.AsPSObject(obj)); } } + break; default: { // do not enumerate at all (CoreOnly) ProcessObject(so); } + break; } } @@ -169,18 +173,22 @@ private void ProcessCoreOutOfBand(PSObject so, int count) { msg = FormatAndOut_format_xxx.IEnum_NoObjects; } + break; case 1: { msg = FormatAndOut_format_xxx.IEnum_OneObject; } + break; default: { msg = StringUtil.Format(FormatAndOut_format_xxx.IEnum_ManyObjects, count); } + break; } + SendCommentOutOfBand(msg); } @@ -194,9 +202,9 @@ private void SendCommentOutOfBand(string msg) } /// - /// execute formatting on a single object + /// Execute formatting on a single object. /// - /// object to process + /// Object to process. private void ProcessObject(PSObject so) { // we do protect against reentrancy, assuming @@ -255,6 +263,9 @@ private void ProcessObject(PSObject so) } else if (transition == GroupTransition.startNew) { + // Add newline before each group except first + WriteNewLineObject(); + // double transition PopGroup(); // exit the current one PushGroup(so); // start a sibling group @@ -266,6 +277,23 @@ private void ProcessObject(PSObject so) } } + private void WriteNewLineObject() + { + FormatEntryData fed = new FormatEntryData(); + fed.outOfBand = true; + + ComplexViewEntry cve = new ComplexViewEntry(); + FormatEntry fe = new FormatEntry(); + cve.formatValueList.Add(fe); + + // Formating system writes newline before each object + // so no need to add newline here like: + // fe.formatValueList.Add(new FormatNewLine()); + fed.formatEntryInfo = cve; + + this.WriteObject(fed); + } + private bool ShouldProcessOutOfBand { get @@ -274,6 +302,7 @@ private bool ShouldProcessOutOfBand { return true; } + return !_parameters.forceFormattingAlsoOnOutOfBand; } } @@ -377,7 +406,6 @@ private void WriteErrorRecords(List errorRecordList) } } - internal override void EndProcessing() { // need to pop all the contexts, in case the transmission sequence @@ -401,7 +429,7 @@ internal override void EndProcessing() this.WriteObject(endFormat); contextManager.Pop(); } - } // while + } } internal void SetCommandLineParameters(FormattingCommandLineParameters commandLineParameters) @@ -411,18 +439,18 @@ internal void SetCommandLineParameters(FormattingCommandLineParameters commandLi } /// - /// group transitions: + /// Group transitions: /// none: stay in the same group /// enter: start a new group - /// exit: exit from the current group + /// exit: exit from the current group. /// private enum GroupTransition { none, enter, exit, startNew } /// - /// compute the group transition, given an input object + /// Compute the group transition, given an input object. /// - /// object received from the input pipeline - /// GroupTransition enumeration + /// Object received from the input pipeline. + /// GroupTransition enumeration. private GroupTransition ComputeGroupTransition(PSObject so) { // check if we have to start a group @@ -447,17 +475,16 @@ private void WriteFormatStartData(PSObject so) this.WriteObject(startFormat); } - /// - /// write a payplad object by properly wrapping it into - /// a FormatEntry object + /// Write a payplad object by properly wrapping it into + /// a FormatEntry object. /// - /// object to process + /// Object to process. private void WritePayloadObject(PSObject so) { Diagnostics.Assert(so != null, "object so cannot be null"); FormatEntryData fed = _viewManager.ViewGenerator.GeneratePayload(so, _enumerationLimit); - fed.SetStreamTypeFromPSObject(so); + fed.writeStream = so.WriteStream; this.WriteObject(fed); List errors = _viewManager.ViewGenerator.ErrorManager.DrainFailedResultList(); @@ -465,8 +492,8 @@ private void WritePayloadObject(PSObject so) } /// - /// inject the start group information - /// and push group context on stack + /// Inject the start group information + /// and push group context on stack. /// /// current pipeline object /// that is starting the group @@ -478,8 +505,8 @@ private void PushGroup(PSObject firstObjectInGroup) } /// - /// inject the end group information - /// and pop group context out of stack + /// Inject the end group information + /// and pop group context out of stack. /// private void PopGroup() { @@ -489,9 +516,9 @@ private void PopGroup() } /// - /// the formatting shape this formatter emits + /// The formatting shape this formatter emits. /// - private FormatShape _shape; + private readonly FormatShape _shape; #region expression factory @@ -503,8 +530,7 @@ internal ScriptBlock CreateScriptBlock(string scriptText) return scriptBlock; } - - private MshExpressionFactory _expressionFactory; + private PSPropertyExpressionFactory _expressionFactory; #endregion private FormatObjectDeserializer _formatObjectDeserializer; @@ -512,35 +538,33 @@ 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; } /// - /// /// public class OuterFormatShapeCommandBase : FrontEndCommandBase { #region Command Line Switches /// - /// optional, non positional parameter to specify the - /// group by property + /// Optional, non positional parameter to specify the + /// group by property. /// [Parameter] public object GroupBy { get; set; } = null; - /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] public string View { get; set; } = null; /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -552,13 +576,17 @@ public SwitchParameter ShowError return showErrorsAsMessages.Value; return false; } - set { showErrorsAsMessages = value; } + + set + { + showErrorsAsMessages = value; + } } - internal Nullable showErrorsAsMessages = null; + internal bool? showErrorsAsMessages = null; /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -570,24 +598,31 @@ public SwitchParameter DisplayError return showErrorsInFormattedOutput.Value; return false; } - set { showErrorsInFormattedOutput = value; } + + set + { + showErrorsInFormattedOutput = value; + } } - internal Nullable showErrorsInFormattedOutput = null; + + internal bool? showErrorsInFormattedOutput = null; /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] public SwitchParameter Force { get { return _forceFormattingAlsoOnOutOfBand; } + set { _forceFormattingAlsoOnOutOfBand = value; } } + private bool _forceFormattingAlsoOnOutOfBand; /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -596,11 +631,11 @@ public SwitchParameter Force EnumerableExpansionConversion.BothString, IgnoreCase = true)] public string Expand { get; set; } = null; - internal Nullable expansion = null; + internal EnumerableExpansion? expansion = null; - internal Nullable ProcessExpandParameter() + internal EnumerableExpansion? ProcessExpandParameter() { - Nullable retVal = null; + EnumerableExpansion? retVal = null; if (string.IsNullOrEmpty(Expand)) { return null; @@ -614,6 +649,7 @@ internal Nullable ProcessExpandParameter() // NOTE: this is an exception that should never be triggered throw PSTraceSource.NewArgumentException("Expand", FormatAndOut_MshParameter.IllegalEnumerableExpansionValue); } + retVal = temp; return retVal; } @@ -636,11 +672,9 @@ internal MshParameter ProcessGroupByParameter() return null; } - #endregion /// - /// /// protected override void BeginProcessing() { @@ -656,11 +690,11 @@ protected override void BeginProcessing() } /// - /// it reads the command line switches and collects them into a + /// It reads the command line switches and collects them into a /// FormattingCommandLineParameters instance, ready to pass to the - /// inner format command + /// inner format command. /// - /// parameters collected in unified manner + /// Parameters collected in unified manner. internal virtual FormattingCommandLineParameters GetCommandLineParameters() { return null; @@ -682,7 +716,6 @@ internal void ReportCannotSpecifyViewAndProperty() } /// - /// /// public class OuterFormatTableAndListBase : OuterFormatShapeCommandBase { @@ -695,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() @@ -719,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; @@ -733,27 +785,33 @@ 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.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - parameters.viewName = this.View; } } } /// - /// /// public class OuterFormatTableBase : OuterFormatTableAndListBase { #region Command Line Switches /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -765,12 +823,23 @@ public SwitchParameter AutoSize return _autosize.Value; return false; } - set { _autosize = value; } + + set + { + _autosize = value; + } } - private Nullable _autosize = null; + + private bool? _autosize = null; + + /// + /// Gets or sets if header is repeated per screen. + /// + [Parameter] + public SwitchParameter RepeatHeader { get; set; } /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -782,12 +851,17 @@ public SwitchParameter HideTableHeaders return _hideHeaders.Value; return false; } - set { _hideHeaders = value; } + + set + { + _hideHeaders = value; + } } - private Nullable _hideHeaders = null; + + private bool? _hideHeaders = null; /// - /// optional, non positional parameter + /// Optional, non positional parameter. /// /// [Parameter] @@ -799,10 +873,14 @@ public SwitchParameter Wrap return _multiLine.Value; return false; } - set { _multiLine = value; } + + set + { + _multiLine = value; + } } - private Nullable _multiLine = null; + private bool? _multiLine = null; #endregion internal override FormattingCommandLineParameters GetCommandLineParameters() @@ -821,6 +899,11 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() if (_autosize.HasValue) parameters.autosize = _autosize.Value; + if (RepeatHeader) + { + parameters.repeatHeader = true; + } + parameters.groupByParameter = this.ProcessGroupByParameter(); TableSpecificParameters tableParameters = new TableSpecificParameters(); @@ -835,9 +918,8 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() { tableParameters.multiLine = _multiLine.Value; } + return parameters; } } } - - diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs index 74ad087336f..498f6098a24 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -21,54 +20,65 @@ internal sealed class FormattingCommandLineParameters { /// /// MshParameter collection, as specified by metadata - /// the list can be empty of no data is specified + /// the list can be empty of no data is specified. /// internal List mshParameterList = new List(); /// - /// name of the group by property, it can be null + /// Name of the group by property, it can be null. /// internal MshParameter groupByParameter = null; /// - /// name of a view from format.ps1xml, it can be null + /// Name of a view from format.ps1xml, it can be null. /// internal string viewName = null; /// - /// flag to force a shape even on out of band objects + /// Flag to force a shape even on out of band objects. /// internal bool forceFormattingAlsoOnOutOfBand = false; /// - /// autosize formatting flag. If true, the output command is instructed - /// to get the "best fit" for the device screen + /// Autosize formatting flag. If true, the output command is instructed + /// to get the "best fit" for the device screen. /// - internal Nullable autosize = null; + internal bool? autosize = null; /// - /// errors are shown as out of band messages + /// If true, the header for a table is repeated after each screen full + /// of content. /// - internal Nullable showErrorsAsMessages = null; + internal bool repeatHeader = false; /// - /// errors are shown in the formatted output + /// Errors are shown as out of band messages. /// - internal Nullable showErrorsInFormattedOutput = null; + internal bool? showErrorsAsMessages = null; /// - /// expand IEnumerable flag. + /// Errors are shown in the formatted output. /// - internal Nullable expansion = null; + internal bool? showErrorsInFormattedOutput = null; /// - /// extension mechanism for shape specific parameters + /// Expand IEnumerable flag. + /// + internal EnumerableExpansion? expansion = null; + + /// + /// Extension mechanism for shape specific parameters. /// internal ShapeSpecificParameters shapeParameters = null; + + /// + /// Filter for excluding properties from formatting. + /// + internal PSPropertyExpressionFilter excludePropertyFilter = null; } /// - /// class to derive from to pass shepe specific data + /// Class to derive from to pass shepe specific data. /// internal abstract class ShapeSpecificParameters { @@ -76,19 +86,19 @@ internal abstract class ShapeSpecificParameters internal sealed class TableSpecificParameters : ShapeSpecificParameters { - internal Nullable hideHeaders = null; - internal Nullable multiLine = null; + internal bool? hideHeaders = null; + internal bool? multiLine = null; } internal sealed class WideSpecificParameters : ShapeSpecificParameters { - internal Nullable columns = null; + internal int? columns = null; } internal sealed class ComplexSpecificParameters : ShapeSpecificParameters { /// - /// options for class info display on objects + /// Options for class info display on objects. /// internal enum ClassInfoDisplay { none, fullName, shortName } @@ -97,7 +107,7 @@ internal enum ClassInfoDisplay { none, fullName, shortName } internal const int maxDepthAllowable = 5; /// - /// max depth of recursion on sub objects + /// Max depth of recursion on sub objects. /// internal int maxDepth = maxDepthAllowable; } @@ -107,7 +117,7 @@ internal enum ClassInfoDisplay { none, fullName, shortName } #region MshParameter metadata /// - /// specialized class for the "expression" property + /// Specialized class for the "expression" property. /// internal class ExpressionEntryDefinition : HashtableEntryDefinition { @@ -166,34 +176,35 @@ internal override object Verify(object val, { if (val == null) { - throw PSTraceSource.NewArgumentNullException("val"); + throw PSTraceSource.NewArgumentNullException(nameof(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) { - MshExpression ex = new MshExpression(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)) { ProcessEmptyStringError(originalParameterWasHashTable, invocationContext); } - MshExpression ex = new MshExpression(s); + + PSPropertyExpression ex = new PSPropertyExpression(s); if (_noGlobbing) { if (ex.HasWildCardCharacters) ProcessGlobbingCharactersError(originalParameterWasHashTable, s, invocationContext); } + return ex; } - PSTraceSource.NewArgumentException("val"); + + PSTraceSource.NewArgumentException(nameof(val)); return null; } @@ -241,7 +252,7 @@ private void ProcessGlobbingCharactersError(bool originalParameterWasHashTable, #endregion - private bool _noGlobbing; + private readonly bool _noGlobbing; } internal class AlignmentEntryDefinition : HashtableEntryDefinition @@ -399,13 +410,13 @@ internal override object Verify(object val, // this should never happen throw PSTraceSource.NewInvalidOperationException(); } + return LanguagePrimitives.IsTrue(val); } } - /// - /// definitions for hash table keys + /// Definitions for hash table keys. /// internal static class FormatParameterDefinitionKeys { @@ -427,7 +438,6 @@ internal static class FormatParameterDefinitionKeys internal const string DepthEntryKey = "depth"; } - internal class FormatGroupByParameterDefinition : CommandParameterDefinition { protected override void SetEntries() @@ -482,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 bb64bde1db0..113acf3fa6a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Collections.Specialized; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Internal; @@ -13,13 +11,13 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// /// OutCommand base implementation /// it manages the formatting protocol and it writes to a generic - /// screen host + /// screen host. /// internal class OutCommandInner : ImplementationCommandBase { #region tracer [TraceSource("format_out_OutCommandInner", "OutCommandInner")] - internal static PSTraceSource tracer = PSTraceSource.GetTracer("format_out_OutCommandInner", "OutCommandInner"); + internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("format_out_OutCommandInner", "OutCommandInner"); #endregion tracer internal override void BeginProcessing() @@ -38,10 +36,10 @@ internal override void BeginProcessing() } /// - /// execution entry point override + /// Execution entry point override /// we assume that a LineOutput interface instance already has been acquired /// - /// IMPORTANT: it assumes the presence of a pre-processing formatting command + /// IMPORTANT: it assumes the presence of a pre-processing formatting command. /// internal override void ProcessRecord() { @@ -119,27 +117,21 @@ private void DrainCache() } } - private bool ProcessObject(PSObject so) { object o = _formatObjectDeserializer.Deserialize(so); - //Console.WriteLine("OutCommandInner.Execute() retrieved object {0}, of type {1}", o.ToString(), o.GetType()); + // Console.WriteLine("OutCommandInner.Execute() retrieved object {0}, of type {1}", o.ToString(), o.GetType()); if (NeedsPreprocessing(o)) { return false; } // 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 @@ -151,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)) { @@ -162,7 +153,7 @@ private bool ProcessObject(PSObject so) } } - //Console.WriteLine("OutCommandInner.Execute() calling ctxManager.Process({0})",o.ToString()); + // Console.WriteLine("OutCommandInner.Execute() calling ctxManager.Process({0})",o.ToString()); List info = _cache.Add((PacketInfoData)o); if (info != null) @@ -170,11 +161,12 @@ private bool ProcessObject(PSObject so) for (int k = 0; k < info.Count; k++) _ctxManager.Process(info[k]); } + return true; } /// - /// helper to return what shape we have to use to format the output + /// Helper to return what shape we have to use to format the output. /// private FormatShape ActiveFormattingShape { @@ -182,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) @@ -214,56 +206,56 @@ protected override void InternalDispose() } } - /// - /// enum describing the state for the output finite state machine + /// Enum describing the state for the output finite state machine. /// private enum FormattingState { /// - /// we are in the clear state: no formatting in process + /// We are in the clear state: no formatting in process. /// Reset, /// - /// we received a Format Start message, but we are not inside a group + /// We received a Format Start message, but we are not inside a group. /// Formatting, /// - /// we are inside a group because we received a Group Start + /// We are inside a group because we received a Group Start. /// InsideGroup } /// - /// toggle to signal if we are in a formatting sequence + /// Toggle to signal if we are in a formatting sequence. /// private FormattingState _currentFormattingState = FormattingState.Reset; - /// - /// instance of a command wrapper to execute the - /// default formatter when needed + /// Instance of a command wrapper to execute the + /// default formatter when needed. /// private CommandWrapper _command; - /// - /// enumeration to drive the preprocessing stage + /// Enumeration to drive the preprocessing stage. /// private enum PreprocessingState { raw, processed, error } + private const int DefaultConsoleWidth = 120; + private const int DefaultConsoleHeight = int.MaxValue; + internal const int StackAllocThreshold = 120; + /// - /// test if an object coming from the pipeline needs to be - /// preprocessed by the default formatter + /// Test if an object coming from the pipeline needs to be + /// preprocessed by the default formatter. /// - /// object to examine for formatting - /// whether the object needs to be shunted to preprocessing + /// Object to examine for formatting. + /// 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) @@ -271,6 +263,7 @@ private bool NeedsPreprocessing(object o) // we allow out of band data in any state ValidateCurrentFormattingState(FormattingState.InsideGroup, o); } + return false; } else if (o is FormatStartData) @@ -325,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) { @@ -361,10 +353,10 @@ private void ValidateCurrentFormattingState(FormattingState expectedFormattingSt } /// - /// shunt object to the formatting pipeline for preprocessing + /// Shunt object to the formatting pipeline for preprocessing. /// - /// object to be preprocessed - /// array of objects returned by the preprocessing step + /// Object to be preprocessed. + /// Array of objects returned by the preprocessing step. private Array ApplyFormatting(object o) { if (_command == null) @@ -377,27 +369,25 @@ private Array ApplyFormatting(object o) } /// - /// class factory for output context + /// Class factory for output context. /// - /// parent context in the stack - /// fromat info data received from the pipeline + /// Parent context in the stack. + /// Fromat info data received from the pipeline. /// 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; @@ -431,8 +421,10 @@ private FormatMessagesContextManager.OutputContext CreateOutputContext( { Diagnostics.Assert(false, "Invalid shape. This should never happen"); } + break; } + goc.Initialize(); return goc; } @@ -441,76 +433,80 @@ private FormatMessagesContextManager.OutputContext CreateOutputContext( } /// - /// callback for Fs processing + /// Callback for Fs processing. /// - /// the context containing the Fs entry + /// The context containing the Fs entry. private void ProcessFormatStart(FormatMessagesContextManager.OutputContext c) { // we just add an empty line to the display - this.LineOutput.WriteLine(""); + this.LineOutput.WriteLine(string.Empty); } /// - /// callback for Fe processing + /// Callback for Fe processing. /// - /// Fe notification message - /// current context, with Fs in it + /// Fe notification message. + /// 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(""); + 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); } /// - /// callback for Gs processing + /// Callback for Gs processing. /// - /// the context containing the Gs entry + /// The context containing the Gs entry. private void ProcessGroupStart(FormatMessagesContextManager.OutputContext c) { - //Console.WriteLine("ProcessGroupStart"); + // Console.WriteLine("ProcessGroupStart"); GroupOutputContext goc = (GroupOutputContext)c; - if (goc.Data.groupingEntry != null) { - _lo.WriteLine(""); - ComplexWriter writer = new ComplexWriter(); writer.Initialize(_lo, _lo.ColumnNumber); writer.WriteObject(goc.Data.groupingEntry.formatValueList); - - this.LineOutput.WriteLine(""); + _lo.WriteLine(string.Empty); } + goc.GroupStart(); } /// - /// callback for Ge processing + /// Callback for Ge processing. /// - /// Ge notification message - /// current context, with Gs in it + /// Ge notification message. + /// Current context, with Gs in it. private void ProcessGroupEnd(GroupEndData ge, FormatMessagesContextManager.OutputContext c) { - //Console.WriteLine("ProcessGroupEnd"); + // Console.WriteLine("ProcessGroupEnd"); GroupOutputContext goc = (GroupOutputContext)c; goc.GroupEnd(); - this.LineOutput.WriteLine(""); } /// - /// process the current payload object + /// Process the current payload object. /// - /// FormatEntryData to process - /// currently active context + /// FormatEntryData to process. + /// Currently active context. private void ProcessPayload(FormatEntryData fed, FormatMessagesContextManager.OutputContext c) { // we assume FormatEntryData as a standard wrapper if (fed == null) { - PSTraceSource.NewArgumentNullException("fed"); + PSTraceSource.NewArgumentNullException(nameof(fed)); } + if (fed.formatEntryInfo == null) { PSTraceSource.NewArgumentNullException("fed.formatEntryInfo"); @@ -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,33 +566,33 @@ 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(); - _lo.WriteLine(""); + _lo.WriteLine(string.Empty); string[] properties = ListOutputContext.GetProperties(lve); listWriter.Initialize(properties, _lo.ColumnNumber, _lo.DisplayCells); string[] values = ListOutputContext.GetValues(lve); listWriter.WriteProperties(values, _lo); - _lo.WriteLine(""); + _lo.WriteLine(string.Empty); return; } } /// - /// the screen host associated with this outputter + /// The screen host associated with this outputter. /// private LineOutput _lo = null; internal LineOutput LineOutput { - set { _lo = value; } get { return _lo; } + + set { _lo = value; } } private ShapeInfo ShapeInfoOnFormatContext @@ -613,10 +608,9 @@ private ShapeInfo ShapeInfoOnFormatContext } } - /// - /// retrieve the active FormatOutputContext on the stack - /// by walking up to the top of the stack + /// Retrieve the active FormatOutputContext on the stack + /// by walking up to the top of the stack. /// private FormatOutputContext FormatContext { @@ -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; } @@ -634,17 +626,16 @@ private FormatOutputContext FormatContext } } - /// - /// context manager instance to guide the message traversal + /// Context manager instance to guide the message traversal. /// - private FormatMessagesContextManager _ctxManager = new FormatMessagesContextManager(); + private readonly FormatMessagesContextManager _ctxManager = new FormatMessagesContextManager(); private FormattedObjectsCache _cache = null; /// - /// handler for processing the caching notification and responsible for - /// setting the value of the formatting hint + /// Handler for processing the caching notification and responsible for + /// setting the value of the formatting hint. /// /// /// @@ -652,17 +643,13 @@ private void ProcessCachedGroup(FormatStartData formatStartData, List int cellCount; // scratch variable foreach (PacketInfoData o in objects) { - FormatEntryData fed = o as FormatEntryData; - - if (fed == null) - continue; - - TableRowEntry tre = fed.formatEntryInfo as TableRowEntry; - int kk = 0; - - foreach (FormatPropertyField fpf in tre.formatPropertyFieldList) + if (o is FormatEntryData fed) { - cellCount = _lo.DisplayCells.Length(fpf.propertyValue); - if (widths[kk] < cellCount) - widths[kk] = cellCount; + TableRowEntry tre = fed.formatEntryInfo as TableRowEntry; + int kk = 0; - kk++; + foreach (FormatPropertyField fpf in tre.formatPropertyFieldList) + { + cellCount = _lo.DisplayCells.Length(fpf.propertyValue); + if (widths[kk] < cellCount) + widths[kk] = cellCount; + + kk++; + } } } @@ -729,19 +714,17 @@ private void ProcessCachedGroupOnWide(WideViewHeaderInfo wvhi, List maxLen) - maxLen = cellCount; + WideViewEntry wve = fed.formatEntryInfo as WideViewEntry; + FormatPropertyField fpf = wve.formatPropertyField as FormatPropertyField; + + if (!string.IsNullOrEmpty(fpf.propertyValue)) + { + cellCount = _lo.DisplayCells.Length(fpf.propertyValue); + if (cellCount > maxLen) + maxLen = cellCount; + } } } @@ -752,14 +735,72 @@ private void ProcessCachedGroupOnWide(WideViewHeaderInfo wvhi, List - /// base class for all the formatting hints + /// Tables and Wides need to use spaces for padding to maintain table look even if console window is resized. + /// For all other output, we use int.MaxValue if the user didn't explicitly specify a width. + /// If we detect that int.MaxValue is used, first we try to get the current console window width. + /// However, if we can't read that (for example, implicit remoting has no console window), we default + /// to something reasonable: 120 columns. + /// + private static int GetConsoleWindowWidth(int columnNumber) + { + if (InternalTestHooks.SetConsoleWidthToZero) + { + return DefaultConsoleWidth; + } + + if (columnNumber == int.MaxValue) + { + try + { + // if Console width is set to 0, the default width is returned so that the output string is not null. + // This can happen in environments where TERM is not set. + return (Console.WindowWidth != 0) ? Console.WindowWidth : DefaultConsoleWidth; + } + catch + { + return DefaultConsoleWidth; + } + } + + return columnNumber; + } + + /// + /// Return the console height.null If not available (like when remoting), treat as Int.MaxValue. + /// + private static int GetConsoleWindowHeight(int rowNumber) + { + if (InternalTestHooks.SetConsoleHeightToZero) + { + return DefaultConsoleHeight; + } + + if (rowNumber <= 0) + { + try + { + // if Console height is set to 0, the default height is returned. + // This can happen in environments where TERM is not set. + return (Console.WindowHeight > 0) ? Console.WindowHeight : DefaultConsoleHeight; + } + catch + { + return DefaultConsoleHeight; + } + } + + return rowNumber; + } + + /// + /// Base class for all the formatting hints. /// private abstract class FormattingHint { } /// - /// hint for format-table + /// Hint for format-table. /// private sealed class TableFormattingHint : FormattingHint { @@ -767,7 +808,7 @@ private sealed class TableFormattingHint : FormattingHint } /// - /// hint for format-wide + /// Hint for format-wide. /// private sealed class WideFormattingHint : FormattingHint { @@ -775,12 +816,12 @@ private sealed class WideFormattingHint : FormattingHint } /// - /// variable holding the autosize hint (set by the caching code and reset by the hint consumer + /// Variable holding the autosize hint (set by the caching code and reset by the hint consumer. /// private FormattingHint _formattingHint = null; /// - /// helper for consuming the formatting hint + /// Helper for consuming the formatting hint. /// /// private FormattingHint RetrieveFormattingHint() @@ -792,17 +833,16 @@ private FormattingHint RetrieveFormattingHint() private FormatObjectDeserializer _formatObjectDeserializer; - /// - /// context for the outer scope of the format sequence + /// 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 + /// Construct a context to push on the stack. /// - /// parent context in the stack - /// format data to put in the context + /// Parent context in the stack. + /// Format data to put in the context. internal FormatOutputContext(FormatMessagesContextManager.OutputContext parentContext, FormatStartData formatData) : base(parentContext) { @@ -810,18 +850,18 @@ internal FormatOutputContext(FormatMessagesContextManager.OutputContext parentCo } /// - /// retrieve the format data in the context + /// Retrieve the format data in the context. /// internal FormatStartData Data { get; } = null; } /// - /// context for the currently active group + /// Context for the currently active group. /// private abstract class GroupOutputContext : FormatMessagesContextManager.OutputContext { /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// internal GroupOutputContext(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, @@ -833,47 +873,46 @@ internal GroupOutputContext(OutCommandInner cmd, } /// - /// called at creation time, overrides will initialize here, e.g. + /// Called at creation time, overrides will initialize here, e.g. /// column widths, etc. /// internal virtual void Initialize() { } /// - /// called when a group of data is started, overridden will do + /// Called when a group of data is started, overridden will do /// things such as headers, etc... /// internal virtual void GroupStart() { } /// - /// called when the end of a group is reached, overrides will do - /// things such as group footers + /// Called when the end of a group is reached, overrides will do + /// things such as group footers. /// internal virtual void GroupEnd() { } /// - /// called when there is an entry to process, overrides will do - /// things such as writing a row in a table + /// Called when there is an entry to process, overrides will do + /// things such as writing a row in a table. /// - /// FormatEntryData to process + /// FormatEntryData to process. internal virtual void ProcessPayload(FormatEntryData fed) { } /// - /// retrieve the format data in the context + /// Retrieve the format data in the context. /// internal GroupStartData Data { get; } = null; - protected OutCommandInner InnerCommand { get; } } private class TableOutputContextBase : GroupOutputContext { /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// - /// reference to the OutCommandInner instance who owns this instance - /// parent context in the stack - /// format data to put in the context + /// Reference to the OutCommandInner instance who owns this instance. + /// Parent context in the stack. + /// Format data to put in the context. internal TableOutputContextBase(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, GroupStartData formatData) @@ -882,61 +921,61 @@ internal TableOutputContextBase(OutCommandInner cmd, } /// - /// Get the table writer for this context + /// Get the table writer for this context. /// protected TableWriter Writer { get { return _tableWriter; } } /// - /// helper class to properly write a table using text output + /// 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 { + private int _rowCount = 0; + private int _consoleHeight = -1; + private int _consoleWidth = -1; + + private const int WhitespaceAndPagerLineCount = 2; + + private readonly bool _repeatHeader = false; + /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// - /// reference to the OutCommandInner instance who owns this instance - /// parent context in the stack - /// format data to put in the context + /// Reference to the OutCommandInner instance who owns this instance. + /// Parent context in the stack. + /// Format data to put in the context. internal TableOutputContext(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, GroupStartData formatData) : base(cmd, parentContext, formatData) { + if (parentContext is FormatOutputContext foc) + { + if (foc.Data.shapeInfo is TableHeaderInfo thi) + { + _repeatHeader = thi.repeatHeader; + } + } } /// - /// initialize column widths + /// Initialize column widths. /// internal override void Initialize() { - TableFormattingHint tableHint = this.InnerCommand.RetrieveFormattingHint() as TableFormattingHint; int[] columnWidthsHint = null; + // We expect that console width is less than 120. - if (tableHint != null) + if (this.InnerCommand.RetrieveFormattingHint() is TableFormattingHint tableHint) { columnWidthsHint = tableHint.columnWidths; } - int columnsOnTheScreen = this.InnerCommand._lo.ColumnNumber; - // Tables need to use spaces for padding to maintain table look even if console window is resized. - // For all other output, we use int.MaxValue if the user didn't explicitly specify a width. - // If we detect that int.MaxValue is used, first we try to get the current console window width. - // However, if we can't read that (for example, implicit remoting has no console window), we default - // to something reasonable: 120 columns. - if (columnsOnTheScreen == int.MaxValue) - { - try - { - columnsOnTheScreen = Console.WindowWidth; - } - catch - { - columnsOnTheScreen = 120; - } - } + _consoleHeight = GetConsoleWindowHeight(this.InnerCommand._lo.RowNumber); + _consoleWidth = GetConsoleWindowWidth(this.InnerCommand._lo.ColumnNumber); int columns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count; if (columns == 0) @@ -945,21 +984,24 @@ internal override void Initialize() } // create arrays for widths and alignment - int[] columnWidths = new int[columns]; - int[] alignment = new int[columns]; - int k = 0; + 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, columnsOnTheScreen, columnWidths, alignment, this.CurrentTableHeaderInfo.hideHeader); + + this.Writer.Initialize(0, _consoleWidth, columnWidths, alignment, headerMatchesProperty, this.CurrentTableHeaderInfo.hideHeader); } /// - /// write the headers + /// Write the headers. /// internal override void GroupStart() { @@ -976,13 +1018,14 @@ internal override void GroupStart() { properties[k++] = tci.label ?? tci.propertyName; } - this.Writer.GenerateHeader(properties, this.InnerCommand._lo); + + _rowCount += this.Writer.GenerateHeader(properties, this.InnerCommand._lo); } /// - /// write a row into the table + /// Write a row into the table. /// - /// FormatEntryData to process + /// FormatEntryData to process. internal override void ProcessPayload(FormatEntryData fed) { int headerColumns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count; @@ -992,11 +1035,17 @@ internal override void ProcessPayload(FormatEntryData fed) return; } + if (_repeatHeader && _rowCount >= _consoleHeight - WhitespaceAndPagerLineCount) + { + this.InnerCommand._lo.WriteLine(string.Empty); + _rowCount = this.Writer.GenerateHeader(null, this.InnerCommand._lo); + } + TableRowEntry tre = fed.formatEntryInfo as TableRowEntry; // need to make sure we have matching counts: the header count will have to prevail string[] values = new string[headerColumns]; - int[] alignment = new int[headerColumns]; + Span alignment = headerColumns <= StackAllocThreshold ? stackalloc int[headerColumns] : new int[headerColumns]; int fieldCount = tre.formatPropertyFieldList.Count; @@ -1009,11 +1058,13 @@ internal override void ProcessPayload(FormatEntryData fed) } else { - values[k] = ""; + values[k] = string.Empty; alignment[k] = TextAlignment.Left; // hard coded default } } - this.Writer.GenerateRow(values, this.InnerCommand._lo, tre.multiLine, alignment, InnerCommand._lo.DisplayCells); + + this.Writer.GenerateRow(values, this.InnerCommand._lo, tre.multiLine, alignment, InnerCommand._lo.DisplayCells, generatedRows: null); + _rowCount++; } private TableHeaderInfo CurrentTableHeaderInfo @@ -1028,11 +1079,11 @@ private TableHeaderInfo CurrentTableHeaderInfo private sealed class ListOutputContext : GroupOutputContext { /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// - /// reference to the OutCommandInner instance who owns this instance - /// parent context in the stack - /// format data to put in the context + /// Reference to the OutCommandInner instance who owns this instance. + /// Parent context in the stack. + /// Format data to put in the context. internal ListOutputContext(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, GroupStartData formatData) @@ -1041,7 +1092,7 @@ internal ListOutputContext(OutCommandInner cmd, } /// - /// initialize column widths + /// Initialize column widths. /// internal override void Initialize() { @@ -1055,74 +1106,81 @@ 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); - } - 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); - } - 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; + } /// - /// write the headers + /// Write the headers. /// internal override void GroupStart() { - this.InnerCommand._lo.WriteLine(""); } /// - /// write a row into the list + /// Write a row into the list. /// - /// FormatEntryData to process + /// FormatEntryData to process. internal override void ProcessPayload(FormatEntryData fed) { ListViewEntry lve = fed.formatEntryInfo as ListViewEntry; InternalInitialize(lve); string[] values = GetValues(lve); _listWriter.WriteProperties(values, this.InnerCommand._lo); - this.InnerCommand._lo.WriteLine(""); + this.InnerCommand._lo.WriteLine(string.Empty); } /// - /// property list currently active + /// Property list currently active. /// private string[] _properties = null; /// - /// writer to do the actual formatting + /// Writer to do the actual formatting. /// - private ListWriter _listWriter = new ListWriter(); + private readonly ListWriter _listWriter = new ListWriter(); } private sealed class WideOutputContext : TableOutputContextBase { /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// - /// reference to the OutCommandInner instance who owns this instance - /// parent context in the stack - /// format data to put in the context + /// Reference to the OutCommandInner instance who owns this instance. + /// Parent context in the stack. + /// Format data to put in the context. internal WideOutputContext(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, GroupStartData formatData) @@ -1133,20 +1191,20 @@ internal WideOutputContext(OutCommandInner cmd, private StringValuesBuffer _buffer = null; /// - /// initialize column widths + /// Initialize column widths. /// 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, this.InnerCommand._lo.ColumnNumber); + itemsPerRow = TableWriter.ComputeWideViewBestItemsPerRowFit(hint.maxWidth, columnsOnTheScreen); } else if (this.CurrentWideHeaderInfo.columns > 0) { @@ -1157,8 +1215,8 @@ internal override void Initialize() _buffer = new StringValuesBuffer(itemsPerRow); // initialize the writer - int[] columnWidths = new int[itemsPerRow]; - int[] alignment = new int[itemsPerRow]; + Span columnWidths = itemsPerRow <= StackAllocThreshold ? stackalloc int[itemsPerRow] : new int[itemsPerRow]; + Span alignment = itemsPerRow <= StackAllocThreshold ? stackalloc int[itemsPerRow] : new int[itemsPerRow]; for (int k = 0; k < itemsPerRow; k++) { @@ -1166,20 +1224,19 @@ internal override void Initialize() alignment[k] = TextAlignment.Left; } - this.Writer.Initialize(0, this.InnerCommand._lo.ColumnNumber, columnWidths, alignment, false); + this.Writer.Initialize(leftMarginIndent: 0, columnsOnTheScreen, columnWidths, alignment, headerMatchesProperty: null, suppressHeader: false, screenRows: GetConsoleWindowHeight(this.InnerCommand._lo.RowNumber)); } /// - /// write the headers + /// Write the headers. /// internal override void GroupStart() { - this.InnerCommand._lo.WriteLine(""); } /// - /// called when the end of a group is reached, flush the - /// write buffer + /// Called when the end of a group is reached, flush the + /// write buffer. /// internal override void GroupEnd() { @@ -1187,9 +1244,9 @@ internal override void GroupEnd() } /// - /// write a row into the table + /// Write a row into the table. /// - /// FormatEntryData to process + /// FormatEntryData to process. internal override void ProcessPayload(FormatEntryData fed) { WideViewEntry wve = fed.formatEntryInfo as WideViewEntry; @@ -1222,40 +1279,40 @@ private void WriteStringBuffer() if (k < _buffer.CurrentCount) values[k] = _buffer[k]; else - values[k] = ""; + values[k] = string.Empty; } - this.Writer.GenerateRow(values, this.InnerCommand._lo, false, null, InnerCommand._lo.DisplayCells); + + this.Writer.GenerateRow(values, this.InnerCommand._lo, false, null, InnerCommand._lo.DisplayCells, generatedRows: null); _buffer.Reset(); } - /// - /// helper class to accumulate the display values so that when the end - /// of a line is reached, a full line can be composed + /// 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 + /// Construct the buffer. /// - /// number of entries to cache + /// Number of entries to cache. internal StringValuesBuffer(int size) { _arr = new string[size]; Reset(); } /// - /// get the size of the buffer + /// Get the size of the buffer. /// internal int Length { get { return _arr.Length; } } /// - /// get the current number of entries in the buffer + /// Get the current number of entries in the buffer. /// internal int CurrentCount { get { return _lastEmptySpot; } } /// - /// check if the buffer is full + /// Check if the buffer is full. /// internal bool IsFull { @@ -1263,7 +1320,7 @@ internal bool IsFull } /// - /// check if the buffer is empty + /// Check if the buffer is empty. /// internal bool IsEmpty { @@ -1271,21 +1328,21 @@ internal bool IsEmpty } /// - /// indexer to access the k-th item in the buffer + /// Indexer to access the k-th item in the buffer. /// internal string this[int k] { get { return _arr[k]; } } /// - /// add an item to the buffer + /// Add an item to the buffer. /// - /// string to add + /// String to add. internal void Add(string s) { _arr[_lastEmptySpot++] = s; } /// - /// reset the buffer + /// Reset the buffer. /// internal void Reset() { @@ -1294,7 +1351,7 @@ internal void Reset() _arr[k] = null; } - private string[] _arr; + private readonly string[] _arr; private int _lastEmptySpot; } } @@ -1302,11 +1359,11 @@ internal void Reset() private sealed class ComplexOutputContext : GroupOutputContext { /// - /// construct a context to push on the stack + /// Construct a context to push on the stack. /// - /// reference to the OutCommandInner instance who owns this instance - /// parent context in the stack - /// format data to put in the context + /// Reference to the OutCommandInner instance who owns this instance. + /// Parent context in the stack. + /// Format data to put in the context. internal ComplexOutputContext(OutCommandInner cmd, FormatMessagesContextManager.OutputContext parentContext, GroupStartData formatData) @@ -1320,21 +1377,19 @@ internal override void Initialize() this.InnerCommand._lo.ColumnNumber); } - /// - /// write a row into the list + /// Write a row into the list. /// - /// FormatEntryData to process + /// 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 56687b2ae7c..68ccc7ebc59 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs @@ -1,21 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class providing an algorithm for automatic resizing - /// of table columns + /// Class providing an algorithm for automatic resizing + /// of table columns. /// internal sealed class ColumnWidthManager { /// - /// class providing an algorithm for automatic resizing + /// Class providing an algorithm for automatic resizing. /// - /// overall width of the table in characters - /// minimum usable column width - /// number of separator characters + /// Overall width of the table in characters. + /// Minimum usable column width. + /// Number of separator characters. internal ColumnWidthManager(int tableWidth, int minimumColumnWidth, int separatorWidth) { _tableWidth = tableWidth; @@ -24,13 +25,13 @@ internal ColumnWidthManager(int tableWidth, int minimumColumnWidth, int separato } /// - /// calculate the widths by applying some heuristics to get them to fit on the + /// Calculate the widths by applying some heuristics to get them to fit on the /// allotted table width. It first assigns widths to the columns that do not have a specified /// width, then it checks if the total width exceeds the screen widths. If so, it proceeds - /// with column elimination, starting from the right most column + /// with column elimination, starting from the right most column. /// - /// array of column widths to appropriately size - internal void CalculateColumnWidths(int[] columnWidths) + /// Array of column widths to appropriately size. + internal void CalculateColumnWidths(Span columnWidths) { if (AssignColumnWidths(columnWidths)) { @@ -43,12 +44,12 @@ internal void CalculateColumnWidths(int[] columnWidths) } /// - /// do not remove columns, just assign widths to columns that have a zero width + /// Do not remove columns, just assign widths to columns that have a zero width /// (meaning unassigned) /// - /// columns to process - /// true if there was a fit, false if there is need for trimming - private bool AssignColumnWidths(int[] columnWidths) + /// Columns to process. + /// True if there was a fit, false if there is need for trimming. + private bool AssignColumnWidths(Span columnWidths) { // run a quick check to see if all the columns have a specified width, // if so, we are done @@ -119,16 +120,16 @@ private bool AssignColumnWidths(int[] columnWidths) if (availableWidth == 0) break; } - } // while + } return true; // we fit } /// - /// trim columns if the total column width is too much for the screen. + /// Trim columns if the total column width is too much for the screen. /// - /// column widths to trim - private void TrimToFit(int[] columnWidths) + /// Column widths to trim. + private void TrimToFit(Span columnWidths) { while (true) { @@ -163,11 +164,11 @@ private void TrimToFit(int[] columnWidths) } /// - /// computes the total table width from the column width array + /// Computes the total table width from the column width array. /// - /// column widths array + /// Column widths array. /// - private int CurrentTableWidth(int[] columnWidths) + private int CurrentTableWidth(Span columnWidths) { int sum = 0; int visibleColumns = 0; @@ -185,11 +186,11 @@ private int CurrentTableWidth(int[] columnWidths) } /// - /// get the last visible column (i.e. with a width >= 0) + /// Get the last visible column (i.e. with a width >= 0) /// - /// column widths array - /// index of the last visible column, -1 if none - private static int GetLastVisibleColumn(int[] columnWidths) + /// Column widths array. + /// Index of the last visible column, -1 if none. + private static int GetLastVisibleColumn(Span columnWidths) { for (int k = 0; k < columnWidths.Length; k++) { @@ -200,8 +201,8 @@ private static int GetLastVisibleColumn(int[] 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 210754dbe2b..a69ad41c965 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -1,27 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Text; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Internal; +using System.Text; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// writer class to handle Complex Object formatting + /// Writer class to handle Complex Object formatting. /// internal sealed class ComplexWriter { /// - /// initialization method to be called before any other operation + /// Initialization method to be called before any other operation. /// - /// LineOutput interfaces to write to - /// number of columns used to write out + /// LineOutput interfaces to write to. + /// Number of columns used to write out. internal void Initialize(LineOutput lineOutput, int numberOfTextColumns) { _lo = lineOutput; @@ -29,7 +29,7 @@ internal void Initialize(LineOutput lineOutput, int numberOfTextColumns) } /// - /// Writes a string + /// Writes a string. /// /// internal void WriteString(string s) @@ -42,9 +42,9 @@ internal void WriteString(string s) } /// - /// it interprets a list of format value tokens and outputs it + /// It interprets a list of format value tokens and outputs it. /// - /// list of FormatValue tokens to interpret + /// List of FormatValue tokens to interpret. internal void WriteObject(List formatValueList) { // we always start with no indentation @@ -61,16 +61,15 @@ internal void WriteObject(List formatValueList) } /// - /// operate on a single entry + /// Operate on a single entry. /// - /// entry to process - /// current depth of recursion + /// Entry to process. + /// Current depth of recursion. 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) { @@ -89,21 +88,23 @@ private void GenerateFormatEntryDisplay(FormatEntry fe, int currentDepth) GenerateFormatEntryDisplay(feChild, currentDepth + 1); } } + continue; } + if (obj is FormatNewLine) { this.WriteToScreen(); 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); } @@ -111,16 +112,16 @@ private void GenerateFormatEntryDisplay(FormatEntry fe, int currentDepth) } /// - /// add a string to the current buffer, waiting for a FlushBuffer() + /// Add a string to the current buffer, waiting for a FlushBuffer() /// - /// string to add to buffer + /// String to add to buffer. private void AddToBuffer(string s) { _stringBuffer.Append(s); } /// - /// write to the output interface + /// Write to the output interface. /// private void WriteToScreen() { @@ -143,11 +144,10 @@ 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; } - // compute the first line indentation or hanging int firstLineWidth = _textColumns - rightIndentation - leftIndentation; int followingLinesWidth = firstLineWidth; @@ -163,7 +163,7 @@ private void WriteToScreen() followingLinesWidth += firstLineIndentation; } - //error checking on invalid values + // error checking on invalid values // generate the lines using the computed widths StringCollection sc = StringManipulationHelper.GenerateLines(_lo.DisplayCells, _stringBuffer.ToString(), @@ -202,29 +202,28 @@ private void WriteToScreen() } /// - /// helper object to manage the frame-based indentation and margins + /// 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 + /// Buffer to accumulate partially constructed text. /// private StringBuilder _stringBuffer = new StringBuilder(); /// - /// interface to write to + /// Interface to write to. /// private LineOutput _lo; /// - /// number of columns for the output device + /// Number of columns for the output device. /// private int _textColumns; private const int maxRecursionDepth = 50; } - internal sealed class IndentationManager { private sealed class IndentationStackFrame : IDisposable @@ -236,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() @@ -288,7 +284,6 @@ internal int FirstLineIndentation } } - private int ComputeRightIndentation() { int val = 0; @@ -296,6 +291,7 @@ private int ComputeRightIndentation() { val += fi.rightIndentation; } + return val; } @@ -306,30 +302,33 @@ private int ComputeLeftIndentation() { val += fi.leftIndentation; } + return val; } - private Stack _frameInfoStack = new Stack(); + private readonly Stack _frameInfoStack = new Stack(); } /// - /// Result of GetWords + /// Result of GetWords. /// internal struct GetWordsResult { internal string Word; internal string Delim; + internal bool VtResetAdded; } /// - /// collection of helper functions for string formatting + /// Collection of helper functions for string formatting. /// 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() { @@ -346,32 +345,76 @@ static StringManipulationHelper() /// TODO: we might be able to improve this function in the future /// so that we do not break paths etc. /// - /// input string - /// a collection of words + /// Input string. + /// A collection of words. 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 @@ -380,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) @@ -410,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; } @@ -430,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; @@ -438,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; @@ -447,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) { @@ -461,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 @@ -470,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; } } @@ -494,6 +552,7 @@ internal void AddLine(string s) { _addedFirstLine = true; } + _retVal.Add(s); } @@ -507,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) @@ -525,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) { @@ -540,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); @@ -586,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) + { + vtSeqs = new StringBuilder(); + vtRanges = valueStrDec.EscapeSequenceRanges; + } + + bool hasEscSeqs = false; + for (int i = 0; i < wordToAdd.Length; i++) { - char charToAdd = c; - int charWidth = displayCells.Length(c); + if (vtRanges?.TryGetValue(i, out int len) == true) + { + var vtSpan = wordToAdd.AsSpan(i, len); + singleLine.Append(vtSpan); + vtSeqs.Append(vtSpan); - // 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 + 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 = '?'; @@ -603,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) { @@ -627,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) { @@ -653,74 +741,112 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe } /// - /// split a multiline string into an array of strings - /// by honoring both \n and \r\n + /// Split a multiline string into an array of strings + /// by honoring both \n and \r\n. /// - /// string to split - /// string array with the values - internal static string[] SplitLines(string s) + /// String to split. + /// String array with the values. + internal static List SplitLines(string s) { - if (string.IsNullOrEmpty(s)) - return new string[1] { s }; + if (string.IsNullOrEmpty(s) || !s.Contains('\n')) + { + return new List(capacity: 1) { s?.Replace("\r", string.Empty) }; + } StringBuilder sb = new StringBuilder(); + List list = new List(); - foreach (char c in s) + StringBuilder vtSeqs = null; + Dictionary vtRanges = null; + + var valueStrDec = new ValueStringDecorated(s); + if (valueStrDec.IsDecorated) { - if (c != '\r') - sb.Append(c); + vtSeqs = new StringBuilder(); + vtRanges = valueStrDec.EscapeSequenceRanges; } - return sb.ToString().Split(s_newLineChar); - } - -#if false - internal static string StripNewLines (string s) - { - if (string.IsNullOrEmpty (s)) - return s; + 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)) - return s; + { + return string.Empty; + } - int lineBreak = s.IndexOfAny(s_lineBreakChars); + int lineBreak = s.AsSpan().IndexOfAny('\n', '\r'); if (lineBreak < 0) + { return s; + } - return s.Substring(0, lineBreak) + PSObjectHelper.ellipses; + return s.Substring(0, lineBreak) + PSObjectHelper.Ellipsis; } 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 2cca282bf39..2ae4ac2626e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs @@ -1,38 +1,38 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; -using System.Globalization; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; -using System.Runtime.Serialization; -using System.IO; using System.Diagnostics.CodeAnalysis; -using Microsoft.PowerShell.Commands.Internal.Format; +using System.Globalization; +using System.IO; +using System.Linq; using System.Management.Automation.Host; using System.Management.Automation.Internal; +using System.Runtime.Serialization; using System.Security.Permissions; +using Microsoft.PowerShell.Commands.Internal.Format; + 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,13 +43,12 @@ public FormatTableLoadException() : base() /// /// A localized error message. /// - public FormatTableLoadException(string message) : base(message) + public FormatTableLoadException(string message) + : base(message) { SetDefaultErrorRecord(); } - - /// /// This constructor takes a localized message and an inner exception. /// @@ -70,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(); @@ -84,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("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("info"); - } - - base.GetObjectData(info, context); - // If there are simple fields, serialize them with info.AddValue - if (null != _errors) - { - 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. /// @@ -156,21 +113,21 @@ public Collection Errors } /// - /// A class that keeps the information from format.ps1xml files in a cache table + /// A class that keeps the information from format.ps1xml files in a cache table. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FormatTable")] public sealed class FormatTable { #region Private Data - private TypeInfoDataBaseManager _formatDBMgr; + private readonly TypeInfoDataBaseManager _formatDBMgr; #endregion #region Constructor /// - /// Default Constructor + /// Default Constructor. /// internal FormatTable() { @@ -209,7 +166,7 @@ public FormatTable(IEnumerable formatFiles) : this(formatFiles, null, nu public void AppendFormatData(IEnumerable formatData) { if (formatData == null) - throw PSTraceSource.NewArgumentNullException("formatData"); + throw PSTraceSource.NewArgumentNullException(nameof(formatData)); _formatDBMgr.AddFormatData(formatData, false); } @@ -228,7 +185,7 @@ public void AppendFormatData(IEnumerable formatData) public void PrependFormatData(IEnumerable formatData) { if (formatData == null) - throw PSTraceSource.NewArgumentNullException("formatData"); + throw PSTraceSource.NewArgumentNullException(nameof(formatData)); _formatDBMgr.AddFormatData(formatData, true); } @@ -253,9 +210,9 @@ public void PrependFormatData(IEnumerable formatData) /// internal FormatTable(IEnumerable formatFiles, AuthorizationManager authorizationManager, PSHost host) { - if (null == formatFiles) + if (formatFiles == null) { - throw PSTraceSource.NewArgumentNullException("formatFiles"); + throw PSTraceSource.NewArgumentNullException(nameof(formatFiles)); } _formatDBMgr = new TypeInfoDataBaseManager(formatFiles, true, authorizationManager, host); @@ -270,7 +227,6 @@ internal TypeInfoDataBaseManager FormatDBManager get { return _formatDBMgr; } } - /// /// Adds the to the current FormatTable's file list. /// The FormatTable will not reflect the change until Update is called. @@ -301,7 +257,7 @@ internal void Remove(string formatFile) /// /// Returns a format table instance with all default - /// format files loaded + /// format files loaded. /// /// public static FormatTable LoadDefaultFormatFiles() diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs index a48c9bac7f3..9ebc79a762a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs @@ -1,15 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Xml; +using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Internal; using System.Management.Automation.Host; -using System.Globalization; +using System.Management.Automation.Internal; using System.Text; +using System.Xml; /* SUMMARY: this file contains a general purpose, reusable framework for @@ -23,74 +22,72 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// base exception to be used for all the exceptions that this framework will generate + /// Base exception to be used for all the exceptions that this framework will generate. /// internal abstract class TypeInfoDataBaseLoaderException : SystemException { } /// - /// exception thrown by the loader when the maximum number of errors is exceeded + /// Exception thrown by the loader when the maximum number of errors is exceeded. /// internal class TooManyErrorsException : TypeInfoDataBaseLoaderException { /// - /// error count that triggered the exception + /// Error count that triggered the exception. /// internal int errorCount; } - /// - /// entry logged by the loader and made available to external consumers + /// Entry logged by the loader and made available to external consumers. /// internal class XmlLoaderLoggerEntry { - internal enum EntryType { Error, Trace }; + internal enum EntryType { Error, Trace } /// - /// type of information being logged + /// Type of information being logged. /// internal EntryType entryType; /// - /// path of the file the info refers to + /// Path of the file the info refers to. /// internal string filePath = null; /// - /// XPath location inside the file + /// XPath location inside the file. /// internal string xPath = null; /// - /// message to be displayed to the user + /// Message to be displayed to the user. /// internal string message = null; /// - /// indicate whether we fail to load the file due to the security reason + /// Indicate whether we fail to load the file due to the security reason. /// internal bool failToLoadFile = false; } - /// - /// logger object used by the loader (class XmlLoaderBase) to write log entries. - /// It logs to a memory buffer and (optionally) to a text file + /// Logger object used by the loader (class XmlLoaderBase) to write log entries. + /// It logs to a memory buffer and (optionally) to a text file. /// internal class XmlLoaderLogger : IDisposable { #region tracer - //PSS/end-user 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 /// - /// log an entry + /// Log an entry. /// - /// entry to log + /// Entry to log. internal void LogEntry(XmlLoaderLoggerEntry entry) { if (entry.entryType == XmlLoaderLoggerEntry.EntryType.Error) @@ -103,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) { @@ -116,7 +113,7 @@ private void WriteToTracer(XmlLoaderLoggerEntry entry) } /// - /// IDisposable implementation + /// IDisposable implementation. /// /// This method calls GC.SuppressFinalize public void Dispose() @@ -150,35 +147,34 @@ internal bool HasErrors } /// - /// if true, log entries to memory + /// If true, log entries to memory. /// - private bool _saveInMemory = true; + private readonly bool _saveInMemory = true; /// - /// list of entries logged if saveInMemory is 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 + /// True if we ever logged an error. /// private bool _hasErrors = false; } - /// - /// base class providing XML loading basic functionality (stack management and logging facilities) - /// NOTE: you need to implement to load an actual XML document and traverse it as see fit + /// Base class providing XML loading basic functionality (stack management and logging facilities) + /// NOTE: you need to implement to load an actual XML document and traverse it as see fit. /// 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 /// - /// class representing a stack frame for the XML document tree traversal + /// Class representing a stack frame for the XML document tree traversal. /// private sealed class XmlLoaderStackFrame : IDisposable { @@ -190,7 +186,7 @@ internal XmlLoaderStackFrame(XmlLoaderBase loader, XmlNode n, int index) } /// - /// IDisposable implementation + /// IDisposable implementation. /// public void Dispose() { @@ -202,24 +198,24 @@ public void Dispose() } /// - /// back pointer to the loader, used to pop a stack frame + /// Back pointer to the loader, used to pop a stack frame. /// private XmlLoaderBase _loader; /// - /// node the stack frame refers to + /// Node the stack frame refers to. /// internal XmlNode node; /// - /// node index for enumerations, valid only if != -1 + /// Node index for enumerations, valid only if != -1 /// NOTE: this allows to express the XPath construct "foo[0]" /// internal int index = -1; } /// - /// IDisposable implementation + /// IDisposable implementation. /// /// This method calls GC.SuppressFinalize public void Dispose() @@ -242,7 +238,7 @@ protected virtual void Dispose(bool disposing) } /// - /// get the list of log entries + /// Get the list of log entries. /// /// list of entries logged during a load internal List LogEntries @@ -254,7 +250,7 @@ internal List LogEntries } /// - /// check if there were errors + /// Check if there were errors. /// /// true of the log entry list has errors internal bool HasErrors @@ -266,23 +262,23 @@ internal bool HasErrors } /// - /// to be called when starting a stack frame. - /// The returned IDisposable should be used in a using(){...} block + /// To be called when starting a stack frame. + /// The returned IDisposable should be used in a using(){...} block. /// - /// node to push on the stack - /// object to dispose when exiting the frame + /// Node to push on the stack. + /// Object to dispose when exiting the frame. protected IDisposable StackFrame(XmlNode n) { return StackFrame(n, -1); } /// - /// to be called when starting a stack frame. - /// The returned IDisposable should be used in a using(){...} block + /// To be called when starting a stack frame. + /// The returned IDisposable should be used in a using(){...} block. /// - /// node to push on the stack - /// index of the node of the same name in a collection - /// object to dispose when exiting the frame + /// Node to push on the stack. + /// Index of the node of the same name in a collection. + /// Object to dispose when exiting the frame. protected IDisposable StackFrame(XmlNode n, int index) { XmlLoaderStackFrame sf = new XmlLoaderStackFrame(this, n, index); @@ -294,8 +290,8 @@ protected IDisposable StackFrame(XmlNode n, int index) } /// - /// called by the Dispose code of the XmlLoaderStackFrame object - /// to pop a frame off the stack + /// Called by the Dispose code of the XmlLoaderStackFrame object + /// to pop a frame off the stack. /// private void RemoveStackFrame() { @@ -332,14 +328,14 @@ protected bool VerifyNodeHasNoChildren(XmlNode n) if (n.ChildNodes[0] is XmlText) return true; } - //Error at XPath {0} in file {1}: Node {2} cannot have children. + // Error at XPath {0} in file {1}: Node {2} cannot have children. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoChildrenAllowed, ComputeCurrentXPath(), FilePath, n.Name)); return false; } internal string GetMandatoryInnerText(XmlNode n) { - if (String.IsNullOrEmpty(n.InnerText)) + if (string.IsNullOrEmpty(n.InnerText)) { this.ReportEmptyNode(n); return null; @@ -350,7 +346,7 @@ internal string GetMandatoryInnerText(XmlNode n) internal string GetMandatoryAttributeValue(XmlAttribute a) { - if (String.IsNullOrEmpty(a.Value)) + if (string.IsNullOrEmpty(a.Value)) { this.ReportEmptyAttribute(a); return null; @@ -360,42 +356,38 @@ internal string GetMandatoryAttributeValue(XmlAttribute a) } /// - /// helper to compare node names, e.g. "foo" in + /// Helper to compare node names, e.g. "foo" in /// it uses case sensitive, culture invariant compare. /// This is because XML tags are case sensitive. /// - /// XmlNode whose name is to compare - /// string to compare the node name to - /// if true, accept the presence of attributes on the node - /// true if there is a match + /// XmlNode whose name is to compare. + /// String to compare the node name to. + /// If true, accept the presence of attributes on the node. + /// True if there is a match. private bool MatchNodeNameHelper(XmlNode n, string s, bool allowAttributes) { bool match = false; - if (String.Equals(n.Name, s, StringComparison.Ordinal)) + if (string.Equals(n.Name, s, StringComparison.Ordinal)) { // we have a case sensitive match match = true; } - else if (String.Equals(n.Name, s, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(n.Name, s, StringComparison.OrdinalIgnoreCase)) { // try a case insensitive match // 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; @@ -413,87 +405,88 @@ internal bool MatchNodeName(XmlNode n, string s) internal bool MatchAttributeName(XmlAttribute a, string s) { - if (String.Equals(a.Name, s, StringComparison.Ordinal)) + if (string.Equals(a.Name, s, StringComparison.Ordinal)) { // we have a case sensitive match return true; } - else if (String.Equals(a.Name, s, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(a.Name, s, StringComparison.OrdinalIgnoreCase)) { // try a case insensitive match // 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; } + return false; } internal void ProcessDuplicateNode(XmlNode n) { - //Error at XPath {0} in file {1}: Duplicated node. + // Error at XPath {0} in file {1}: Duplicated node. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.DuplicatedNode, ComputeCurrentXPath(), FilePath), XmlLoaderLoggerEntry.EntryType.Error); } internal void ProcessDuplicateAlternateNode(string node1, string node2) { - //Error at XPath {0} in file {1}: {2} and {3} are mutually exclusive. + // Error at XPath {0} in file {1}: {2} and {3} are mutually exclusive. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.MutuallyExclusiveNode, ComputeCurrentXPath(), FilePath, node1, node2), XmlLoaderLoggerEntry.EntryType.Error); } internal void ProcessDuplicateAlternateNode(XmlNode n, string node1, string node2) { - //Error at XPath {0} in file {1}: {2}, {3} and {4} are mutually exclusive. + // Error at XPath {0} in file {1}: {2}, {3} and {4} are mutually exclusive. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.ThreeMutuallyExclusiveNode, ComputeCurrentXPath(), FilePath, n.Name, node1, node2), XmlLoaderLoggerEntry.EntryType.Error); } private void ReportIllegalXmlNode(XmlNode n) { - //UnknownNode=Error at XPath {0} in file {1}: {2} is an unknown node. + // UnknownNode=Error at XPath {0} in file {1}: {2} is an unknown node. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.UnknownNode, ComputeCurrentXPath(), FilePath, n.Name), XmlLoaderLoggerEntry.EntryType.Error); } private void ReportIllegalXmlAttribute(XmlAttribute a) { - //Error at XPath {0} in file {1}: {2} is an unknown attribute. + // Error at XPath {0} in file {1}: {2} is an unknown attribute. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.UnknownAttribute, ComputeCurrentXPath(), FilePath, a.Name), XmlLoaderLoggerEntry.EntryType.Error); } protected void ReportMissingAttribute(string name) { - //Error at XPath {0} in file {1}: {2} is a missing attribute. + // Error at XPath {0} in file {1}: {2} is a missing attribute. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingAttribute, ComputeCurrentXPath(), FilePath, name), XmlLoaderLoggerEntry.EntryType.Error); } protected void ReportMissingNode(string name) { - //Error at XPath {0} in file {1}: Missing Node {2}. + // Error at XPath {0} in file {1}: Missing Node {2}. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingNode, ComputeCurrentXPath(), FilePath, name), XmlLoaderLoggerEntry.EntryType.Error); } protected void ReportMissingNodes(string[] names) { - //Error at XPath {0} in file {1}: Missing Node from {2}. + // Error at XPath {0} in file {1}: Missing Node from {2}. string namesString = string.Join(", ", names); ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingNodeFromList, ComputeCurrentXPath(), FilePath, namesString), XmlLoaderLoggerEntry.EntryType.Error); } protected void ReportEmptyNode(XmlNode n) { - //Error at XPath {0} in file {1}: {2} is an empty node. + // Error at XPath {0} in file {1}: {2} is an empty node. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.EmptyNode, ComputeCurrentXPath(), FilePath, n.Name), XmlLoaderLoggerEntry.EntryType.Error); } protected void ReportEmptyAttribute(XmlAttribute a) { - //EmptyAttribute=Error at XPath {0} in file {1}: {2} is an empty attribute. + // EmptyAttribute=Error at XPath {0} in file {1}: {2} is an empty attribute. ReportLogEntryHelper(StringUtil.Format(FormatAndOutXmlLoadingStrings.EmptyAttribute, ComputeCurrentXPath(), FilePath, a.Name), XmlLoaderLoggerEntry.EntryType.Error); } /// - /// For tracing purposes only, don't add to log + /// For tracing purposes only, don't add to log. /// /// /// trace message, non-localized string is OK. @@ -555,7 +548,7 @@ private void ReportLogEntryHelper(string message, XmlLoaderLoggerEntry.EntryType } /// - /// Report error when loading formatting data from object model + /// Report error when loading formatting data from object model. /// /// /// @@ -603,14 +596,14 @@ 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 { path.Insert(1, sf.node.Name); } } + return path.Length > 0 ? path.ToString() : null; } @@ -666,9 +659,8 @@ protected XmlDocument LoadXmlDocumentFromFileLoadingInfo(AuthorizationManager au #endregion /// - /// file system path for the file we are loading from + /// File system path for the file we are loading from. /// - /// protected string FilePath { get @@ -677,22 +669,23 @@ protected string FilePath } } - protected void SetDatabaseLoadingInfo(XmlFileLoadInfo info) { _loadingInfo.fileDirectory = info.fileDirectory; _loadingInfo.filePath = info.filePath; } + protected void SetLoadingInfoIsFullyTrusted(bool isFullyTrusted) { _loadingInfo.isFullyTrusted = isFullyTrusted; } + protected void SetLoadingInfoIsProductCode(bool isProductCode) { _loadingInfo.isProductCode = isProductCode; } - private DatabaseLoadingInfo _loadingInfo = new DatabaseLoadingInfo(); + private readonly DatabaseLoadingInfo _loadingInfo = new DatabaseLoadingInfo(); protected DatabaseLoadingInfo LoadingInfo { @@ -707,20 +700,19 @@ protected DatabaseLoadingInfo LoadingInfo } } - protected MshExpressionFactory expressionFactory; + protected PSPropertyExpressionFactory expressionFactory; protected DisplayResourceManagerCache displayResourceManagerCache; - 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 bb3e60a368a..acf540246d5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; + using Microsoft.PowerShell.Commands.Internal.Format; namespace Microsoft.PowerShell.Commands @@ -16,23 +16,25 @@ internal static class EnumerableExpansionConversion internal static bool Convert(string expansionString, out EnumerableExpansion expansion) { expansion = EnumerableExpansion.EnumOnly; - if (String.Equals(expansionString, CoreOnlyString, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(expansionString, CoreOnlyString, StringComparison.OrdinalIgnoreCase)) { expansion = EnumerableExpansion.CoreOnly; return true; } - if (String.Equals(expansionString, EnumOnlyString, StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(expansionString, EnumOnlyString, StringComparison.OrdinalIgnoreCase)) { expansion = EnumerableExpansion.EnumOnly; return true; } - if (String.Equals(expansionString, BothString, StringComparison.OrdinalIgnoreCase)) + + if (string.Equals(expansionString, BothString, StringComparison.OrdinalIgnoreCase)) { expansion = EnumerableExpansion.Both; return true; } + return false; } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs index db4b84f55dd..0190a31ddc9 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -16,17 +15,17 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal enum EnumerableExpansion { /// - /// process core only, ignore IEumerable + /// Process core only, ignore IEumerable. /// CoreOnly, /// - /// process IEnumerable, ignore core + /// Process IEnumerable, ignore core. /// EnumOnly, /// - /// process both core and IEnumerable, core first + /// Process both core and IEnumerable, core first. /// Both, } @@ -41,9 +40,8 @@ internal sealed partial class TypeInfoDataBase internal ViewDefinitionsSection viewDefinitionsSection = new ViewDefinitionsSection(); internal FormatControlDefinitionHolder formatControlDefinitionHolder = new FormatControlDefinitionHolder(); - /// - /// cache for resource strings in format.ps1xml + /// Cache for resource strings in format.ps1xml. /// internal DisplayResourceManagerCache displayResourceManagerCache = new DisplayResourceManagerCache(); } @@ -59,7 +57,6 @@ internal sealed class DatabaseLoadingInfo } #endregion - #region Default Settings #if _LATER @@ -74,7 +71,7 @@ internal void f(T x) { Nullable y = x; this._value = y; - //this._value = (Nullable)x; + // this._value = (Nullable)x; } internal T Value @@ -105,6 +102,13 @@ internal sealed class DefaultSettingsSection { internal bool MultilineTables { + get + { + if (_multilineTables.HasValue) + return _multilineTables.Value; + return false; + } + set { if (!_multilineTables.HasValue) @@ -112,15 +116,9 @@ internal bool MultilineTables _multilineTables = value; } } - get - { - if (_multilineTables.HasValue) - return _multilineTables.Value; - return false; - } } - private bool? _multilineTables; + private bool? _multilineTables; internal FormatErrorPolicy formatErrorPolicy = new FormatErrorPolicy(); internal ShapeSelectionDirectives shapeSelectionDirectives = new ShapeSelectionDirectives(); @@ -130,10 +128,17 @@ internal bool MultilineTables internal sealed class FormatErrorPolicy { /// - /// if true, display error messages + /// If true, display error messages. /// internal bool ShowErrorsAsMessages { + get + { + if (_showErrorsAsMessages.HasValue) + return _showErrorsAsMessages.Value; + return false; + } + set { if (!_showErrorsAsMessages.HasValue) @@ -141,21 +146,23 @@ internal bool ShowErrorsAsMessages _showErrorsAsMessages = value; } } - get - { - if (_showErrorsAsMessages.HasValue) - return _showErrorsAsMessages.Value; - return false; - } } + private bool? _showErrorsAsMessages; /// - /// if true, display an error string in the formatted display + /// If true, display an error string in the formatted display /// (e.g. cell in a table) /// internal bool ShowErrorsInFormattedOutput { + get + { + if (_showErrorsInFormattedOutput.HasValue) + return _showErrorsInFormattedOutput.Value; + return false; + } + set { if (!_showErrorsInFormattedOutput.HasValue) @@ -163,33 +170,34 @@ internal bool ShowErrorsInFormattedOutput _showErrorsInFormattedOutput = value; } } - get - { - if (_showErrorsInFormattedOutput.HasValue) - return _showErrorsInFormattedOutput.Value; - return false; - } } + private bool? _showErrorsInFormattedOutput; /// - /// string to display in the formatted display (e.g. cell in a table) - /// when the evaluation of an MshExpression fails + /// String to display in the formatted display (e.g. cell in a table) + /// when the evaluation of a PSPropertyExpression fails. /// internal string errorStringInFormattedOutput = "#ERR"; /// - /// string to display in the formatted display (e.g. cell in a table) - /// when a format operation on a value fails + /// String to display in the formatted display (e.g. cell in a table) + /// when a format operation on a value fails. /// internal string formatErrorStringInFormattedOutput = "#FMTERR"; } - internal sealed class ShapeSelectionDirectives { internal int PropertyCountForTable { + get + { + if (_propertyCountForTable.HasValue) + return _propertyCountForTable.Value; + return 4; + } + set { if (!_propertyCountForTable.HasValue) @@ -197,13 +205,8 @@ internal int PropertyCountForTable _propertyCountForTable = value; } } - get - { - if (_propertyCountForTable.HasValue) - return _propertyCountForTable.Value; - return 4; - } } + private int? _propertyCountForTable; internal List formatShapeSelectionOnTypeList = new List(); @@ -229,7 +232,6 @@ internal sealed class EnumerableExpansionDirective #endregion - #region Type Groups Definitions internal sealed class TypeGroupsSection @@ -248,7 +250,7 @@ internal abstract class TypeOrGroupReference internal string name; /// - /// optional expression for conditional binding + /// Optional expression for conditional binding. /// internal ExpressionToken conditionToken = null; } @@ -261,10 +263,8 @@ internal sealed class TypeGroupReference : TypeOrGroupReference { } - #endregion - #region Elementary Tokens internal abstract class FormatToken @@ -285,12 +285,12 @@ internal sealed class NewLineToken : FormatToken internal sealed class FrameToken : FormatToken { /// - /// item associated with this frame definition + /// Item associated with this frame definition. /// internal ComplexControlItemDefinition itemDefinition = new ComplexControlItemDefinition(); /// - /// frame info associated with this frame definition + /// Frame info associated with this frame definition. /// internal FrameInfoDefinition frameInfoDefinition = new FrameInfoDefinition(); } @@ -298,19 +298,19 @@ internal sealed class FrameToken : FormatToken internal sealed class FrameInfoDefinition { /// - /// left indentation for a frame is relative to the parent frame. - /// it must be a value >=0 + /// Left indentation for a frame is relative to the parent frame. + /// it must be a value >=0. /// internal int leftIndentation = 0; /// - /// right indentation for a frame is relative to the parent frame. - /// it must be a value >=0 + /// Right indentation for a frame is relative to the parent frame. + /// it must be a value >=0. /// internal int rightIndentation = 0; /// - /// it can have the following values: + /// It can have the following values: /// 0 : ignore /// greater than 0 : it represents the indentation for the first line (i.e. "first line indent"). /// The first line will be indented by the indicated number of characters. @@ -337,7 +337,7 @@ internal ExpressionToken(string expressionValue, bool isScriptBlock) internal abstract class PropertyTokenBase : FormatToken { /// - /// optional expression for conditional binding + /// Optional expression for conditional binding. /// internal ExpressionToken conditionToken = null; @@ -348,7 +348,7 @@ internal abstract class PropertyTokenBase : FormatToken internal sealed class CompoundPropertyToken : PropertyTokenBase { /// - /// an inline control or a reference to a control definition + /// An inline control or a reference to a control definition. /// internal ControlBase control = null; } @@ -361,15 +361,15 @@ internal sealed class FieldPropertyToken : PropertyTokenBase internal sealed class FieldFormattingDirective { internal string formatString = null; // optional + internal bool isTable = false; } #endregion Elementary Tokens - #region Control Definitions: common data /// - /// root class for all the control types + /// Root class for all the control types. /// internal abstract class ControlBase { @@ -377,21 +377,25 @@ internal static string GetControlShapeName(ControlBase control) { if (control is TableControlBody) { - return FormatShape.Table.ToString(); + return nameof(FormatShape.Table); } + if (control is ListControlBody) { - return FormatShape.List.ToString(); + return nameof(FormatShape.List); } + if (control is WideControlBody) { - return FormatShape.Wide.ToString(); + return nameof(FormatShape.Wide); } + if (control is ComplexControlBody) { - return FormatShape.Complex.ToString(); + return nameof(FormatShape.Complex); } - return ""; + + return string.Empty; } /// @@ -407,53 +411,57 @@ internal virtual ControlBase Copy() } /// - /// reference to a control + /// Reference to a control. /// internal sealed class ControlReference : ControlBase { /// - /// name of the control we refer to, it cannot be null + /// Name of the control we refer to, it cannot be null. /// internal string name = null; /// - /// type of the control we refer to, it cannot be null + /// Type of the control we refer to, it cannot be null. /// internal Type controlType = null; } /// - /// base class for all control definitions + /// Base class for all control definitions /// NOTE: this is an extensibility point, if a new control - /// needs to be created, it has to be derived from this class + /// needs to be created, it has to be derived from this class. /// internal abstract class ControlBody : ControlBase { /// - /// RULE: valid only for table and wide only + /// RULE: valid only for table and wide only. /// internal bool? autosize = null; + + /// + /// RULE: only valid for table. + /// + internal bool repeatHeader = false; } /// - /// class to hold a definition of a control + /// Class to hold a definition of a control. /// internal sealed class ControlDefinition { /// - /// name of the control we define, it cannot be null + /// Name of the control we define, it cannot be null. /// internal string name = null; /// - /// body of the control we define, it cannot be null + /// Body of the control we define, it cannot be null. /// internal ControlBody controlBody = null; } #endregion - #region View Definitions: common data internal sealed class ViewDefinitionsSection { @@ -462,7 +470,7 @@ internal sealed class ViewDefinitionsSection internal sealed partial class AppliesTo { - //it can contain either a type or type group reference + // it can contain either a type or type group reference internal List referenceList = new List(); } @@ -477,71 +485,70 @@ internal sealed class GroupBy } - internal sealed class StartGroup { /// - /// expression to be used to select the grouping + /// Expression to be used to select the grouping. /// internal ExpressionToken expression = null; /// - /// an inline control or a reference to a control definition + /// An inline control or a reference to a control definition. /// internal ControlBase control = null; /// - /// alternative (and simplified) representation for the control - /// RULE: if the control object is null, use this one + /// Alternative (and simplified) representation for the control + /// RULE: if the control object is null, use this one. /// internal TextToken labelTextToken = null; } /// - /// container for control definitions + /// Container for control definitions. /// internal sealed class FormatControlDefinitionHolder { /// - /// list of control definitions + /// List of control definitions. /// internal List controlDefinitionList = new List(); } /// - /// definition of a view + /// Definition of a view. /// internal sealed class ViewDefinition { internal DatabaseLoadingInfo loadingInfo; /// - /// the name of this view. Must not be null + /// The name of this view. Must not be null. /// internal string name; /// - /// applicability of the view. Mandatory + /// Applicability of the view. Mandatory. /// internal AppliesTo appliesTo = new AppliesTo(); /// - /// optional grouping directive + /// Optional grouping directive. /// internal GroupBy groupBy; /// - /// container for optional local formatting directives + /// Container for optional local formatting directives. /// internal FormatControlDefinitionHolder formatControlDefinitionHolder = new FormatControlDefinitionHolder(); /// - /// main control for the view (e.g. reference to a control or a control body + /// Main control for the view (e.g. reference to a control or a control body. /// internal ControlBase mainControl; /// - /// RULE: only valid for list and complex + /// RULE: only valid for list and complex. /// internal bool outOfBand; @@ -560,7 +567,7 @@ internal ViewDefinition() } /// - /// base class for all the "shape"-Directive classes + /// Base class for all the "shape"-Directive classes. /// internal abstract class FormatDirective { @@ -585,7 +592,7 @@ internal sealed class StringResourceReference namespace System.Management.Automation { /// - /// Specifies additional type definitions for an object + /// Specifies additional type definitions for an object. /// public sealed class ExtendedTypeDefinition { @@ -607,13 +614,13 @@ public string TypeName /// /// The formatting view definition for - /// the specified type + /// the specified type. /// public List FormatViewDefinition { get; internal set; } /// /// Overloaded to string method for - /// better display + /// better display. /// /// public override string ToString() @@ -622,16 +629,16 @@ public override string ToString() } /// - /// Constructor for the ExtendedTypeDefinition + /// Constructor for the ExtendedTypeDefinition. /// /// /// public ExtendedTypeDefinition(string typeName, IEnumerable viewDefinitions) : this() { - if (String.IsNullOrEmpty(typeName)) - throw PSTraceSource.NewArgumentNullException("typeName"); + if (string.IsNullOrEmpty(typeName)) + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); if (viewDefinitions == null) - throw PSTraceSource.NewArgumentNullException("viewDefinitions"); + throw PSTraceSource.NewArgumentNullException(nameof(viewDefinitions)); TypeNames.Add(typeName); foreach (FormatViewDefinition definition in viewDefinitions) @@ -641,13 +648,13 @@ public ExtendedTypeDefinition(string typeName, IEnumerable } /// - /// Initiate an instance of ExtendedTypeDefinition with the type name + /// Initiate an instance of ExtendedTypeDefinition with the type name. /// /// public ExtendedTypeDefinition(string typeName) : this() { - if (String.IsNullOrEmpty(typeName)) - throw PSTraceSource.NewArgumentNullException("typeName"); + if (string.IsNullOrEmpty(typeName)) + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); TypeNames.Add(typeName); } @@ -660,16 +667,16 @@ internal ExtendedTypeDefinition() } /// - /// Defines a formatting view for a particular type + /// Defines a formatting view for a particular type. /// [DebuggerDisplay("{Name}")] 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; } @@ -684,18 +691,19 @@ internal FormatViewDefinition(string name, PSControl control, Guid instanceid) /// public FormatViewDefinition(string name, PSControl control) { - if (String.IsNullOrEmpty(name)) - throw PSTraceSource.NewArgumentNullException("name"); + if (string.IsNullOrEmpty(name)) + throw PSTraceSource.NewArgumentNullException(nameof(name)); if (control == null) - throw PSTraceSource.NewArgumentNullException("control"); + throw PSTraceSource.NewArgumentNullException(nameof(control)); Name = name; Control = control; + InstanceId = Guid.NewGuid(); } } /// - /// Defines a control for the formatting types defined by PowerShell + /// Defines a control for the formatting types defined by PowerShell. /// public abstract class PSControl { @@ -743,12 +751,12 @@ public sealed class PSControlGroupBy public DisplayEntry Expression { get; set; } /// - /// Optional - used to specify a label for the header of a group + /// Optional - used to specify a label for the header of a group. /// public string Label { get; set; } /// - /// Optional - used to format the header of a group + /// Optional - used to format the header of a group. /// public CustomControl CustomControl { get; set; } @@ -767,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 }; } @@ -791,9 +799,9 @@ internal DisplayEntry() { } /// Public constructor for DisplayEntry public DisplayEntry(string value, DisplayEntryValueType type) { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) if (value == null || type == DisplayEntryValueType.Property) - throw PSTraceSource.NewArgumentNullException("value"); + throw PSTraceSource.NewArgumentNullException(nameof(value)); Value = value; ValueType = type; @@ -810,7 +818,7 @@ internal DisplayEntry(ExpressionToken expression) Value = expression.expressionValue; ValueType = expression.isScriptBlock ? DisplayEntryValueType.ScriptBlock : DisplayEntryValueType.Property; - if (String.IsNullOrEmpty(Value)) + if (string.IsNullOrEmpty(Value)) if (Value == null || ValueType == DisplayEntryValueType.Property) throw PSTraceSource.NewArgumentNullException("value"); } @@ -854,6 +862,7 @@ internal static EntrySelectedBy Get(IEnumerable entrySelectedByType, IEn if (result.TypeNames.Count > 0) isEmpty = false; } + if (entrySelectedByCondition != null) { result.SelectionCondition = new List(entrySelectedByCondition); @@ -878,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; @@ -887,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); } @@ -918,43 +927,43 @@ internal bool CompatibleWithOldPowerShell() } /// - /// Specifies possible alignment enumerations for display cells + /// Specifies possible alignment enumerations for display cells. /// public enum Alignment { /// - /// not defined + /// Not defined. /// Undefined = 0, /// - /// left of the cell, contents will trail with a ... if exceeded - ex "Display..." + /// Left of the cell, contents will trail with a ... if exceeded - ex "Display..." /// Left = 1, /// - /// center of the cell + /// Center of the cell. /// Center = 2, /// - /// right of the cell, contents will lead with a ... if exceeded - ex "...456" + /// Right of the cell, contents will lead with a ... if exceeded - ex "...456" /// Right = 3, } /// - /// Specifies the type of entry value + /// Specifies the type of entry value. /// public enum DisplayEntryValueType { /// - /// The value is a property. Look for a property with the specified name + /// The value is a property. Look for a property with the specified name. /// Property = 0, /// - /// The value is a scriptblock. Evaluate the script block and fill the entry with the result + /// The value is a scriptblock. Evaluate the script block and fill the entry with the result. /// ScriptBlock = 1, } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionDataMethods.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionDataMethods.cs index fc6e3f71720..fd321e14b80 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionDataMethods.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionDataMethods.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace Microsoft.PowerShell.Commands.Internal.Format { 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 642b1d5b899..822703fde21 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Complex.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -14,18 +13,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format #region Complex View Definitions /// - /// in line definition of a complex control + /// In line definition of a complex control. /// internal sealed class ComplexControlBody : ControlBody { /// - /// default list entry definition - /// It's mandatory + /// Default list entry definition + /// It's mandatory. /// internal ComplexControlEntryDefinition defaultEntry; /// - /// optional list of list entry definition overrides. It can be empty if there are no overrides + /// Optional list of list entry definition overrides. It can be empty if there are no overrides. /// internal List optionalEntryList = new List(); } @@ -33,13 +32,13 @@ internal sealed class ComplexControlBody : ControlBody internal sealed class ComplexControlEntryDefinition { /// - /// applicability clause - /// Only valid if not the default definition + /// Applicability clause + /// Only valid if not the default definition. /// internal AppliesTo appliesTo = null; /// - /// item associated with this entry definition + /// Item associated with this entry definition. /// internal ComplexControlItemDefinition itemDefinition = new ComplexControlItemDefinition(); } @@ -47,7 +46,7 @@ internal sealed class ComplexControlEntryDefinition internal sealed class ComplexControlItemDefinition { /// - /// list of tokens the item can contain + /// List of tokens the item can contain. /// internal List formatTokenList = new List(); } @@ -180,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 { @@ -203,15 +200,16 @@ internal static CustomItemBase Create(FormatToken token) { frame.FirstLineHanging = (uint)-firstLine; } + foreach (var frameItemToken in frameToken.itemDefinition.formatTokenList) { frame.CustomItems.Add(CustomItemBase.Create(frameItemToken)); } + return frame; } - var cpt = token as CompoundPropertyToken; - if (cpt != null) + if (token is CompoundPropertyToken cpt) { var cie = new CustomItemExpression { EnumerateCollection = cpt.enumerateCollection }; @@ -225,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; @@ -419,13 +412,13 @@ public CustomEntryBuilder StartFrame(uint leftIndent = 0, uint rightIndent = 0, // Mutually exclusive if (leftIndent != 0 && rightIndent != 0) { - throw PSTraceSource.NewArgumentException("leftIndent"); + throw PSTraceSource.NewArgumentException(nameof(leftIndent)); } // Mutually exclusive if (firstLineHanging != 0 && firstLineIndent != 0) { - throw PSTraceSource.NewArgumentException("firstLineHanging"); + throw PSTraceSource.NewArgumentException(nameof(firstLineHanging)); } var frame = new CustomItemFrame @@ -447,6 +440,7 @@ public CustomEntryBuilder EndFrame() { throw PSTraceSource.NewInvalidOperationException(); } + _entryStack.Pop(); return this; } @@ -458,6 +452,7 @@ public CustomControlBuilder EndEntry() { throw PSTraceSource.NewInvalidOperationException(); } + _entryStack.Pop(); return _controlBuilder; } 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 acad22df755..293b4c5b82e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_List.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -14,18 +13,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format #region List View Definitions /// - /// in line definition of a list control + /// In line definition of a list control. /// internal sealed class ListControlBody : ControlBody { /// - /// default list entry definition - /// It's mandatory + /// Default list entry definition + /// It's mandatory. /// internal ListControlEntryDefinition defaultEntryDefinition = null; /// - /// optional list of list entry definition overrides. It can be empty if there are no overrides + /// Optional list of list entry definition overrides. It can be empty if there are no overrides. /// internal List optionalEntryList = new List(); @@ -33,7 +32,7 @@ internal override ControlBase Copy() { ListControlBody result = new ListControlBody(); result.autosize = this.autosize; - if (null != defaultEntryDefinition) + if (defaultEntryDefinition != null) { result.defaultEntryDefinition = this.defaultEntryDefinition.Copy(); } @@ -48,19 +47,19 @@ internal override ControlBase Copy() } /// - /// definition of the data to be displayed in a list entry + /// Definition of the data to be displayed in a list entry. /// internal sealed class ListControlEntryDefinition { /// - /// applicability clause - /// Only valid if not the default definition + /// Applicability clause + /// Only valid if not the default definition. /// internal AppliesTo appliesTo = null; /// - /// mandatory list of list view items. - /// It cannot be empty + /// Mandatory list of list view items. + /// It cannot be empty. /// internal List itemDefinitionList = new List(); @@ -82,24 +81,24 @@ internal ListControlEntryDefinition Copy() } /// - /// cell definition inside a row + /// Cell definition inside a row. /// internal sealed class ListControlItemDefinition { /// - /// optional expression for conditional binding + /// Optional expression for conditional binding. /// internal ExpressionToken conditionToken; /// - /// optional label + /// Optional label /// If not present, use the name of the property from the matching - /// mandatory item description + /// mandatory item description. /// internal TextToken label = null; /// - /// format directive body telling how to format the cell + /// Format directive body telling how to format the cell /// RULE: the body can only contain /// * TextToken /// * PropertyToken @@ -114,7 +113,7 @@ internal sealed class ListControlItemDefinition namespace System.Management.Automation { /// - /// Defines a list control + /// Defines a list control. /// public sealed class ListControl : PSControl { @@ -174,7 +173,7 @@ public ListControl(IEnumerable entries) : this() { if (entries == null) - throw PSTraceSource.NewArgumentNullException("entries"); + throw PSTraceSource.NewArgumentNullException(nameof(entries)); foreach (ListControlEntry entry in entries) { this.Entries.Add(entry); @@ -197,7 +196,7 @@ internal override bool CompatibleWithOldPowerShell() } /// - /// Defines one entry in a list control + /// Defines one entry in a list control. /// public sealed class ListControlEntry { @@ -209,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; } } @@ -231,6 +229,7 @@ internal ListControlEntry(ListControlEntryDefinition entrydefn) { EntrySelectedBy = EntrySelectedBy.Get(entrydefn.appliesTo.referenceList); } + foreach (ListControlItemDefinition itemdefn in entrydefn.itemDefinitionList) { Items.Add(new ListControlEntryItem(itemdefn)); @@ -242,7 +241,7 @@ public ListControlEntry(IEnumerable listItems) : this() { if (listItems == null) - throw PSTraceSource.NewArgumentNullException("listItems"); + throw PSTraceSource.NewArgumentNullException(nameof(listItems)); foreach (ListControlEntryItem item in listItems) { this.Items.Add(item); @@ -253,9 +252,9 @@ public ListControlEntry(IEnumerable listItems) public ListControlEntry(IEnumerable listItems, IEnumerable selectedBy) { if (listItems == null) - throw PSTraceSource.NewArgumentNullException("listItems"); + throw PSTraceSource.NewArgumentNullException(nameof(listItems)); if (selectedBy == null) - throw PSTraceSource.NewArgumentNullException("selectedBy"); + throw PSTraceSource.NewArgumentNullException(nameof(selectedBy)); EntrySelectedBy = new EntrySelectedBy { TypeNames = new List(selectedBy) }; foreach (ListControlEntryItem item in listItems) @@ -288,14 +287,14 @@ internal bool CompatibleWithOldPowerShell() } /// - /// Defines one row in a list control entry + /// Defines one row in a list control entry. /// public sealed class ListControlEntryItem { /// /// Gets the label for this List Control Entry Item /// If nothing is specified, then it uses the - /// property name + /// property name. /// public string Label { get; internal set; } @@ -318,8 +317,8 @@ 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) { @@ -336,7 +335,7 @@ internal ListControlEntryItem(ListControlItemDefinition definition) /// /// Public constructor for ListControlEntryItem - /// Label and Entry could be null + /// Label and Entry could be null. /// /// /// diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Misc.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Misc.cs index 7bebf09eda4..cb1cc5cbf30 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Misc.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Misc.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -8,7 +7,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// in line definition of a format string control + /// In line definition of a format string control. /// internal sealed class FieldControlBody : ControlBody { 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 6a74c5b9b30..026b9b82f73 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -14,9 +13,9 @@ namespace Microsoft.PowerShell.Commands.Internal.Format #region Table View Definitions /// - /// alignment values + /// Alignment values /// NOTE: we do not use an enum because this will have to be - /// serialized and ERS/serialization do not support enumerations + /// serialized and ERS/serialization do not support enumerations. /// internal static class TextAlignment { @@ -27,23 +26,23 @@ internal static class TextAlignment } /// - /// definition of a table control + /// Definition of a table control. /// internal sealed class TableControlBody : ControlBody { /// - /// optional, if not present, use data off the default table row definition + /// Optional, if not present, use data off the default table row definition. /// internal TableHeaderDefinition header = new TableHeaderDefinition(); /// - /// default row definition - /// It's mandatory + /// Default row definition + /// It's mandatory. /// internal TableRowDefinition defaultDefinition; /// - /// optional list of row definition overrides. It can be empty if there are no overrides + /// Optional list of row definition overrides. It can be empty if there are no overrides. /// internal List optionalDefinitionList = new List(); @@ -54,7 +53,7 @@ internal override ControlBase Copy() autosize = this.autosize, header = this.header.Copy() }; - if (null != defaultDefinition) + if (defaultDefinition != null) { result.defaultDefinition = this.defaultDefinition.Copy(); } @@ -68,20 +67,19 @@ internal override ControlBase Copy() } } - /// - /// information about the table header - /// NOTE: if an instance of this class is present, the list must not be empty + /// Information about the table header + /// NOTE: if an instance of this class is present, the list must not be empty. /// internal sealed class TableHeaderDefinition { /// - /// if true, direct the outputter to suppress table header printing + /// If true, direct the outputter to suppress table header printing. /// internal bool hideHeader; /// - /// mandatory list of column header definitions + /// Mandatory list of column header definitions. /// internal List columnHeaderDefinitionList = new List(); @@ -105,45 +103,45 @@ internal TableHeaderDefinition Copy() internal sealed class TableColumnHeaderDefinition { /// - /// optional label + /// Optional label /// If not present, use the name of the property from the matching - /// mandatory row description + /// mandatory row description. /// internal TextToken label = null; /// - /// general alignment for the column + /// General alignment for the column /// If not present, either use the one from the row definition - /// or the data driven heuristics + /// or the data driven heuristics. /// internal int alignment = TextAlignment.Undefined; /// - /// width of the column + /// Width of the column. /// internal int width = 0; // undefined } /// - /// definition of the data to be displayed in a table row + /// Definition of the data to be displayed in a table row. /// internal sealed class TableRowDefinition { /// - /// applicability clause - /// Only valid if not the default definition + /// Applicability clause + /// Only valid if not the default definition. /// internal AppliesTo appliesTo; /// - /// if true, the current table row should be allowed - /// to wrap to multiple lines, else truncated + /// If true, the current table row should be allowed + /// to wrap to multiple lines, else truncated. /// internal bool multiLine; /// - /// mandatory list of column items. - /// It cannot be empty + /// Mandatory list of column items. + /// It cannot be empty. /// internal List rowItemDefinitionList = new List(); @@ -168,17 +166,17 @@ internal TableRowDefinition Copy() } /// - /// cell definition inside a row + /// Cell definition inside a row. /// internal sealed class TableRowItemDefinition { /// - /// optional alignment to override the default one at the header level + /// Optional alignment to override the default one at the header level. /// internal int alignment = TextAlignment.Undefined; /// - /// format directive body telling how to format the cell + /// Format directive body telling how to format the cell /// RULE: the body can only contain /// * TextToken /// * PropertyToken @@ -193,7 +191,7 @@ internal sealed class TableRowItemDefinition namespace System.Management.Automation { /// - /// Defines a table control + /// Defines a table control. /// public sealed class TableControl : PSControl { @@ -229,9 +227,9 @@ internal override void WriteToXml(FormatXmlWriter writer) } /// - /// Determines if this object is safe to be written + /// Determines if this object is safe to be written. /// - /// true if safe, false otherwise + /// True if safe, false otherwise. internal override bool SafeForExport() { if (!base.SafeForExport()) @@ -265,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); @@ -287,7 +285,7 @@ internal TableControl(TableControlBody tcb, ViewDefinition viewDefinition) : thi } /// - /// public constructor for TableControl that only takes 'tableControlRows'. + /// Public constructor for TableControl that only takes 'tableControlRows'. /// /// public TableControl(TableControlRow tableControlRow) : this() @@ -299,7 +297,7 @@ public TableControl(TableControlRow tableControlRow) : this() } /// - /// public constructor for TableControl that takes both 'tableControlRows' and 'tableControlColumnHeaders'. + /// Public constructor for TableControl that takes both 'tableControlRows' and 'tableControlColumnHeaders'. /// /// /// @@ -308,7 +306,7 @@ public TableControl(TableControlRow tableControlRow, IEnumerable - /// Defines the header for a particular column in a table control + /// Defines the header for a particular column in a table control. /// public sealed class TableControlColumnHeader { @@ -338,6 +336,7 @@ internal TableControlColumnHeader(TableColumnHeaderDefinition colheaderdefinitio { Label = colheaderdefinition.label.text; } + Alignment = (Alignment)colheaderdefinition.alignment; Width = colheaderdefinition.width; } @@ -350,13 +349,13 @@ public TableControlColumnHeader() /// /// Public constructor for TableControlColumnHeader. /// - /// Could be null if no label to specify - /// The Value should be non-negative - /// The default value is Alignment.Undefined + /// Could be null if no label to specify. + /// The Value should be non-negative. + /// The default value is Alignment.Undefined. public TableControlColumnHeader(string label, int width, Alignment alignment) { if (width < 0) - throw PSTraceSource.NewArgumentOutOfRangeException("width", width); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(width), width); this.Label = label; this.Width = width; @@ -366,7 +365,7 @@ public TableControlColumnHeader(string label, int width, Alignment alignment) /// /// Defines a particular column within a row - /// in a table control + /// in a table control. /// public sealed class TableControlColumn { @@ -380,7 +379,7 @@ public sealed class TableControlColumn public string FormatString { get; internal set; } /// - /// Returns the value of the entry + /// Returns the value of the entry. /// /// public override string ToString() @@ -418,7 +417,7 @@ internal bool SafeForExport() } /// - /// Defines a single row in a table control + /// Defines a single row in a table control. /// public sealed class TableControlRow { @@ -444,12 +443,12 @@ internal TableControlRow(TableRowDefinition rowdefinition) : this() { SelectedBy = EntrySelectedBy.Get(rowdefinition.appliesTo.referenceList); } + 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); @@ -467,7 +466,7 @@ internal TableControlRow(TableRowDefinition rowdefinition) : this() public TableControlRow(IEnumerable columns) : this() { if (columns == null) - throw PSTraceSource.NewArgumentNullException("columns"); + throw PSTraceSource.NewArgumentNullException(nameof(columns)); foreach (TableControlColumn column in columns) { Columns.Add(column); @@ -507,7 +506,7 @@ internal TableRowDefinitionBuilder(TableControlBuilder tcb, TableControlRow tcr) private TableRowDefinitionBuilder AddItem(string value, DisplayEntryValueType entryType, Alignment alignment, string format) { if (string.IsNullOrEmpty(value)) - throw PSTraceSource.NewArgumentException("value"); + throw PSTraceSource.NewArgumentException(nameof(value)); var tableControlColumn = new TableControlColumn(alignment, new DisplayEntry(value, entryType)) { @@ -535,7 +534,7 @@ public TableRowDefinitionBuilder AddPropertyColumn(string propertyName, Alignmen } /// - /// Complete a row definition + /// Complete a row definition. /// public TableControlBuilder EndRowDefinition() { @@ -547,6 +546,7 @@ public TableControlBuilder EndRowDefinition() public sealed class TableControlBuilder { internal readonly TableControl _table; + internal TableControlBuilder(TableControl table) { _table = table; @@ -594,11 +594,13 @@ public TableRowDefinitionBuilder StartRowDefinition(bool wrap = false, IEnumerab { row.SelectedBy.TypeNames = new List(entrySelectedByType); } + if (entrySelectedByCondition != null) { row.SelectedBy.SelectionCondition = new List(entrySelectedByCondition); } } + _table.Rows.Add(row); return new TableRowDefinitionBuilder(this, row); } 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 c5560edf1f8..1f5b42fd262 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // this file contains the data structures for the in memory database // containing display and formatting information @@ -14,40 +13,40 @@ namespace Microsoft.PowerShell.Commands.Internal.Format #region Wide View Definitions /// - /// in line definition of a wide control + /// In line definition of a wide control. /// internal sealed class WideControlBody : ControlBody { /// - /// number of columns to use for wide display + /// Number of columns to use for wide display. /// internal int columns = 0; /// - /// default wide entry definition - /// It's mandatory + /// Default wide entry definition + /// It's mandatory. /// internal WideControlEntryDefinition defaultEntryDefinition = null; /// - /// optional list of list entry definition overrides. It can be empty if there are no overrides + /// Optional list of list entry definition overrides. It can be empty if there are no overrides. /// internal List optionalEntryList = new List(); } /// - /// definition of the data to be displayed in a list entry + /// Definition of the data to be displayed in a list entry. /// internal sealed class WideControlEntryDefinition { /// - /// applicability clause - /// Only valid if not the default definition + /// Applicability clause + /// Only valid if not the default definition. /// internal AppliesTo appliesTo = null; /// - /// format directive body telling how to format the cell + /// Format directive body telling how to format the cell /// RULE: the body can only contain /// * TextToken /// * PropertyToken @@ -62,7 +61,7 @@ internal sealed class WideControlEntryDefinition namespace System.Management.Automation { /// - /// Defines a list control + /// Defines a list control. /// public sealed class WideControl : PSControl { @@ -89,9 +88,9 @@ internal override void WriteToXml(FormatXmlWriter writer) /// /// Indicates if this control does not have - /// any script blocks and is safe to export + /// any script blocks and is safe to export. /// - /// true if exportable, false otherwise + /// True if exportable, false otherwise. internal override bool SafeForExport() { if (!base.SafeForExport()) @@ -131,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)); @@ -146,7 +145,7 @@ internal WideControl(WideControlBody widecontrolbody, ViewDefinition viewDefinit public WideControl(IEnumerable wideEntries) : this() { if (wideEntries == null) - throw PSTraceSource.NewArgumentNullException("wideEntries"); + throw PSTraceSource.NewArgumentNullException(nameof(wideEntries)); foreach (WideControlEntryItem entryItem in wideEntries) { @@ -158,12 +157,13 @@ public WideControl(IEnumerable wideEntries) : this() public WideControl(IEnumerable wideEntries, uint columns) : this() { if (wideEntries == null) - throw PSTraceSource.NewArgumentNullException("wideEntries"); + throw PSTraceSource.NewArgumentNullException(nameof(wideEntries)); foreach (WideControlEntryItem entryItem in wideEntries) { this.Entries.Add(entryItem); } + this.Columns = columns; } @@ -175,7 +175,7 @@ public WideControl(uint columns) : this() } /// - /// Defines one item in a wide control entry + /// Defines one item in a wide control entry. /// public sealed class WideControlEntryItem { @@ -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; @@ -224,7 +222,7 @@ internal WideControlEntryItem(WideControlEntryDefinition definition) : this() public WideControlEntryItem(DisplayEntry entry) : this() { if (entry == null) - throw PSTraceSource.NewArgumentNullException("entry"); + throw PSTraceSource.NewArgumentNullException(nameof(entry)); this.DisplayEntry = entry; } @@ -234,9 +232,9 @@ public WideControlEntryItem(DisplayEntry entry) : this() public WideControlEntryItem(DisplayEntry entry, IEnumerable selectedBy) : this() { if (entry == null) - throw PSTraceSource.NewArgumentNullException("entry"); + throw PSTraceSource.NewArgumentNullException(nameof(entry)); if (selectedBy == null) - throw PSTraceSource.NewArgumentNullException("selectedBy"); + throw PSTraceSource.NewArgumentNullException(nameof(selectedBy)); this.DisplayEntry = entry; this.EntrySelectedBy = EntrySelectedBy.Get(selectedBy, null); @@ -259,6 +257,7 @@ internal bool CompatibleWithOldPowerShell() public sealed class WideControlBuilder { private readonly WideControl _control; + internal WideControlBuilder(WideControl control) { _control = control; @@ -316,4 +315,4 @@ public WideControl EndWideControl() return _control; } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs index c4f4e237fdc..b5eb4d456e8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs @@ -1,19 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; +using System.Management.Automation; using System.Reflection; using System.Resources; -using System.Management.Automation; namespace Microsoft.PowerShell.Commands.Internal.Format { 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) { @@ -23,6 +23,7 @@ internal string GetTextTokenString(TextToken tt) if (resString != null) return resString; } + return tt.text; } @@ -80,7 +81,7 @@ private string GetStringHelper(StringResourceReference resourceReference, out Lo else { resourceReference.assemblyLocation = loadResult.a.Location; - }; + } // load now the resource from the resource manager cache try @@ -110,11 +111,12 @@ private string GetStringHelper(StringResourceReference resourceReference, out Lo Diagnostics.Assert(false, "ResourceManagerCache.GetResourceString unexpected exception " + e.GetType().FullName); throw; } + return null; } /// - /// Get a reference to an assembly object by looking up the currently loaded assemblies + /// Get a reference to an assembly object by looking up the currently loaded assemblies. /// /// the string resource reference object containing /// the name of the assembly to load @@ -129,6 +131,7 @@ private Assembly LoadAssemblyFromResourceReference(StringResourceReference resou foundInGac = false; // it always be false, since we return already loaded assemblies return _assemblyNameResolver.ResolveAssemblyName(resourceReference.assemblyName); } + private sealed class AssemblyLoadResult { internal Assembly a; @@ -136,13 +139,13 @@ private sealed class AssemblyLoadResult } /// - /// helper class to resolve an assembly name to an assembly reference - /// The class caches previous results for faster lookup + /// 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 + /// Resolve the assembly name against the set of loaded assemblies. /// /// /// @@ -173,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; @@ -202,7 +205,7 @@ private Assembly ResolveAssemblyNameInLoadedAssemblies(string assemblyName, bool continue; } - String nameToCompare = fullName ? aName.FullName : aName.Name; + string nameToCompare = fullName ? aName.FullName : aName.Name; if (string.Equals(nameToCompare, assemblyName, StringComparison.Ordinal)) { @@ -213,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 838ed276f19..629e419d5a2 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Concurrent; @@ -8,22 +7,22 @@ using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; -using System.Management.Automation.Internal; using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Threading; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to manage the database instances, do the reloading, etc. + /// Class to manage the database instances, do the reloading, etc. /// internal sealed class TypeInfoDataBaseManager { #region Private Data /// - /// instance of the object holding the format.ps1xml in memory database + /// Instance of the object holding the format.ps1xml in memory database. /// internal TypeInfoDataBase Database { get; private set; } @@ -34,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; } @@ -49,7 +48,6 @@ internal TypeInfoDataBaseManager() } /// - /// /// /// /// @@ -59,7 +57,7 @@ internal TypeInfoDataBaseManager() /// /// Host passed to . Can be null if no interactive questions should be asked. /// - /// + /// /// /// 1. FormatFile is not rooted. /// @@ -81,7 +79,7 @@ internal TypeInfoDataBaseManager( { if (string.IsNullOrEmpty(formatFile) || (!Path.IsPathRooted(formatFile))) { - throw PSTraceSource.NewArgumentException("formatFiles", FormatAndOutXmlLoadingStrings.FormatFileNotRooted, formatFile); + throw PSTraceSource.NewArgumentException(nameof(formatFiles), FormatAndOutXmlLoadingStrings.FormatFileNotRooted, formatFile); } PSSnapInTypeAndFormatErrors fileToLoad = new PSSnapInTypeAndFormatErrors(string.Empty, formatFile); @@ -90,7 +88,7 @@ internal TypeInfoDataBaseManager( _formatFileList.Add(formatFile); } - MshExpressionFactory expressionFactory = new MshExpressionFactory(); + PSPropertyExpressionFactory expressionFactory = new PSPropertyExpressionFactory(); List logEntries = null; // load the files @@ -98,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); } @@ -124,7 +122,7 @@ internal void Add(string formatFile, bool shouldPrepend) { if (string.IsNullOrEmpty(formatFile) || (!Path.IsPathRooted(formatFile))) { - throw PSTraceSource.NewArgumentException("formatFile", FormatAndOutXmlLoadingStrings.FormatFileNotRooted, formatFile); + throw PSTraceSource.NewArgumentException(nameof(formatFile), FormatAndOutXmlLoadingStrings.FormatFileNotRooted, formatFile); } lock (_formatFileList) @@ -184,6 +182,7 @@ internal void AddFormatData(IEnumerable formatData, bool return; } } + lock (_formatFileList) { foreach (string formatFile in _formatFileList) @@ -193,6 +192,7 @@ internal void AddFormatData(IEnumerable formatData, bool filesToLoad.Add(fileToLoad); } } + if (!shouldPrepend) { foreach (ExtendedTypeDefinition typeDefinition in formatData) @@ -208,14 +208,14 @@ internal void AddFormatData(IEnumerable formatData, bool } } - MshExpressionFactory expressionFactory = new MshExpressionFactory(); + PSPropertyExpressionFactory expressionFactory = new PSPropertyExpressionFactory(); List logEntries = null; // load the formatting data 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); } @@ -260,9 +260,9 @@ internal void Update(AuthorizationManager authorizationManager, PSHost host) /// /// Update the format data database. If there is any error in loading the format xml files, /// the old database is unchanged. - /// The reference returned should NOT be modified by any means by the caller + /// The reference returned should NOT be modified by any means by the caller. /// - /// files to be loaded and errors to be updated + /// Files to be loaded and errors to be updated. /// /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) /// @@ -273,7 +273,7 @@ internal void Update(AuthorizationManager authorizationManager, PSHost host) /// True if the format data has been pre-validated (build time, manual testing, etc) so that validation can be /// skipped at runtime. /// - /// database instance + /// Database instance. internal void UpdateDataBase( Collection mshsnapins, AuthorizationManager authorizationManager, @@ -291,18 +291,18 @@ bool preValidated throw PSTraceSource.NewInvalidOperationException(FormatAndOutXmlLoadingStrings.SharedFormatTableCannotBeUpdated); } - MshExpressionFactory expressionFactory = new MshExpressionFactory(); + PSPropertyExpressionFactory expressionFactory = new PSPropertyExpressionFactory(); List logEntries = null; LoadFromFile(mshsnapins, expressionFactory, false, authorizationManager, host, preValidated, out logEntries); } /// - /// load the database - /// NOTE: need to be protected by lock since not thread safe per se + /// Load the database + /// NOTE: need to be protected by lock since not thread safe per se. /// - /// *.formal.xml files to be loaded - /// expression factory to validate script blocks - /// if true, load the database even if there are loading errors + /// *.formal.xml files to be loaded. + /// Expression factory to validate script blocks. + /// If true, load the database even if there are loading errors. /// /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) /// @@ -314,10 +314,10 @@ bool preValidated /// skipped at runtime. /// /// Trace and error logs from loading the format Xml files. - /// true if we had a successful load + /// True if we had a successful load. internal bool LoadFromFile( Collection files, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, bool acceptLoadingErrors, AuthorizationManager authorizationManager, PSHost host, @@ -355,14 +355,15 @@ internal bool LoadFromFile( } } } + return success; } /// - /// it loads a database from file(s). + /// It loads a database from file(s). /// - /// *.formal.xml files to be loaded - /// expression factory to validate script blocks + /// *.formal.xml files to be loaded. + /// Expression factory to validate script blocks. /// /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) /// @@ -373,12 +374,12 @@ internal bool LoadFromFile( /// True if the format data has been pre-validated (build time, manual testing, etc) so that validation can be /// skipped at runtime. /// - /// list of logger entries (errors, etc.) to return to the caller - /// true if no error occurred - /// a database instance loaded from file(s) + /// List of logger entries (errors, etc.) to return to the caller. + /// True if no error occurred. + /// A database instance loaded from file(s). private static TypeInfoDataBase LoadFromFileHelper( Collection files, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, AuthorizationManager authorizationManager, PSHost host, bool preValidated, @@ -408,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)) { @@ -427,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... @@ -435,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 @@ -446,7 +456,7 @@ private static TypeInfoDataBase LoadFromFileHelper( private static void LoadFormatDataHelper( ExtendedTypeDefinition formatData, - MshExpressionFactory expressionFactory, List logEntries, ref bool success, + PSPropertyExpressionFactory expressionFactory, List logEntries, ref bool success, PSSnapInTypeAndFormatErrors file, TypeInfoDataBase db, bool isBuiltInFormatData, bool isForHelp) @@ -472,6 +482,7 @@ private static void LoadFormatDataHelper( } private delegate IEnumerable TypeGenerator(); + private static Dictionary> s_builtinGenerators; private static Tuple GetBuiltin(bool isForHelp, TypeGenerator generator) @@ -482,7 +493,7 @@ private static Tuple GetBuiltin(bool isForHelp, TypeGenerat private static bool ProcessBuiltin( PSSnapInTypeAndFormatErrors file, TypeInfoDataBase db, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, List logEntries, ref bool success) { @@ -518,7 +529,7 @@ private static bool ProcessBuiltin( private static void ProcessBuiltinFormatViewDefinitions( IEnumerable views, TypeInfoDataBase db, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, PSSnapInTypeAndFormatErrors file, List logEntries, bool isForHelp, @@ -531,18 +542,18 @@ 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 + /// Db being initialized. private static void AddPreLoadIntrinsics(TypeInfoDataBase db) { // NOTE: nothing to add for the time being. Add here if needed. } /// - /// helper to to add any post-load intrinsics to the db + /// Helper to add any post-load intrinsics to the db. /// - /// db being initialized + /// Db being initialized. private static void AddPostLoadIntrinsics(TypeInfoDataBase db) { // add entry for the output of update-formatdata @@ -557,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 3467716071a..de6df333951 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs @@ -1,23 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; using System.Globalization; using System.Management.Automation; +using System.Text; namespace Microsoft.PowerShell.Commands.Internal.Format { internal static class DisplayCondition { - internal static bool Evaluate(PSObject obj, MshExpression ex, out MshExpressionResult expressionResult) + internal static bool Evaluate(PSObject obj, PSPropertyExpression ex, out PSPropertyExpressionResult expressionResult) { expressionResult = null; - List res = ex.GetValues(obj); + List res = ex.GetValues(obj); if (res.Count == 0) return false; if (res[0].Exception != null) @@ -25,15 +24,15 @@ internal static bool Evaluate(PSObject obj, MshExpression ex, out MshExpressionR expressionResult = res[0]; return false; } + return LanguagePrimitives.IsTrue(res[0].Result); } } - /// - /// helper object holding a generic object and the related + /// Helper object holding a generic object and the related /// "applies to" object. - /// It is used in by the inheritance based type match algorithm + /// It is used in by the inheritance based type match algorithm. /// internal sealed class TypeMatchItem { @@ -42,6 +41,7 @@ internal TypeMatchItem(object obj, AppliesTo a) Item = obj; AppliesTo = a; } + internal TypeMatchItem(object obj, AppliesTo a, PSObject currentObject) { Item = obj; @@ -50,13 +50,15 @@ internal TypeMatchItem(object obj, AppliesTo a, PSObject currentObject) } internal object Item { get; } + internal AppliesTo AppliesTo { get; } + internal PSObject CurrentObject { get; } } /// - /// algorithm to execute a type match on a list of entities - /// having an "applies to" associated object + /// Algorithm to execute a type match on a list of entities + /// having an "applies to" associated object. /// internal sealed class TypeMatch { @@ -80,13 +82,14 @@ internal static void SetTracer(PSTraceSource t) { s_activeTracer = t; } + internal static void ResetTracer() { s_activeTracer = s_classTracer; } #endregion tracer - internal TypeMatch(MshExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) + internal TypeMatch(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) { _expressionFactory = expressionFactory; _db = db; @@ -94,7 +97,7 @@ internal TypeMatch(MshExpressionFactory expressionFactory, TypeInfoDataBase db, _useInheritance = true; } - internal TypeMatch(MshExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames, bool useInheritance) + internal TypeMatch(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames, bool useInheritance) { _expressionFactory = expressionFactory; _db = db; @@ -114,6 +117,7 @@ internal bool PerfectMatch(TypeMatchItem item) _bestMatchIndex = match; _bestMatchItem = item; } + return _bestMatchIndex == BestMatchIndexPerfect; } @@ -132,16 +136,15 @@ private int ComputeBestMatch(AppliesTo appliesTo, PSObject currentObject) int best = BestMatchIndexUndefined; foreach (TypeOrGroupReference r in appliesTo.referenceList) { - MshExpression ex = null; + PSPropertyExpression ex = null; if (r.conditionToken != null) { ex = _expressionFactory.CreateFromExpressionToken(r.conditionToken); } 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); @@ -160,6 +163,7 @@ private int ComputeBestMatch(AppliesTo appliesTo, PSObject currentObject) currentMatch = ComputeBestMatchInGroup(tgd, currentObject, ex); } } + if (currentMatch == BestMatchIndexPerfect) return currentMatch; @@ -172,7 +176,7 @@ private int ComputeBestMatch(AppliesTo appliesTo, PSObject currentObject) return best; } - private int ComputeBestMatchInGroup(TypeGroupDefinition tgd, PSObject currentObject, MshExpression ex) + private int ComputeBestMatchInGroup(TypeGroupDefinition tgd, PSObject currentObject, PSPropertyExpression ex) { int best = BestMatchIndexUndefined; int k = 0; @@ -186,12 +190,14 @@ private int ComputeBestMatchInGroup(TypeGroupDefinition tgd, PSObject currentObj { best = currentMatch; } + k++; } + return best; } - private int MatchTypeIndex(string typeName, PSObject currentObject, MshExpression ex) + private int MatchTypeIndex(string typeName, PSObject currentObject, PSPropertyExpression ex) { if (string.IsNullOrEmpty(typeName)) return BestMatchIndexUndefined; @@ -203,35 +209,30 @@ private int MatchTypeIndex(string typeName, PSObject currentObject, MshExpressio { return k; } + if (k == 0 && !_useInheritance) break; k++; } + return BestMatchIndexUndefined; } - - private bool MatchCondition(PSObject currentObject, MshExpression ex) + private static bool MatchCondition(PSObject currentObject, PSPropertyExpression ex) { if (ex == null) return true; - MshExpressionResult expressionResult; + PSPropertyExpressionResult expressionResult; bool retVal = DisplayCondition.Evaluate(currentObject, ex, out expressionResult); - if (expressionResult != null && expressionResult.Exception != null) - { - _failedResultsList.Add(expressionResult); - } + return retVal; } - - private MshExpressionFactory _expressionFactory; - private TypeInfoDataBase _db; - private Collection _typeNameHierarchy; - private bool _useInheritance; - - private List _failedResultsList = new List(); + private readonly PSPropertyExpressionFactory _expressionFactory; + private readonly TypeInfoDataBase _db; + private readonly Collection _typeNameHierarchy; + private readonly bool _useInheritance; private int _bestMatchIndex = BestMatchIndexUndefined; private TypeMatchItem _bestMatchItem; @@ -262,13 +263,14 @@ internal static void SetTracer(PSTraceSource t) { s_activeTracer = t; } + internal static void ResetTracer() { s_activeTracer = s_classTracer; } #endregion tracer - internal static EnumerableExpansion GetEnumerableExpansionFromType(MshExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) + internal static EnumerableExpansion GetEnumerableExpansionFromType(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) { TypeMatch match = new TypeMatch(expressionFactory, db, typeNames); foreach (EnumerableExpansionDirective expansionDirective in db.defaultSettingsSection.enumerableExpansionDirectiveList) @@ -278,6 +280,7 @@ internal static EnumerableExpansion GetEnumerableExpansionFromType(MshExpression return expansionDirective.enumerableExpansion; } } + if (match.BestMatch != null) { return ((EnumerableExpansionDirective)(match.BestMatch)).enumerableExpansion; @@ -285,7 +288,7 @@ internal static EnumerableExpansion GetEnumerableExpansionFromType(MshExpression else { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { EnumerableExpansion result = GetEnumerableExpansionFromType(expressionFactory, db, typesWithoutPrefix); return result; @@ -296,7 +299,7 @@ internal static EnumerableExpansion GetEnumerableExpansionFromType(MshExpression } } - internal static FormatShape GetShapeFromType(MshExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) + internal static FormatShape GetShapeFromType(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) { ShapeSelectionDirectives shapeDirectives = db.defaultSettingsSection.shapeSelectionDirectives; @@ -308,6 +311,7 @@ internal static FormatShape GetShapeFromType(MshExpressionFactory expressionFact return shapeSelOnType.formatShape; } } + if (match.BestMatch != null) { return ((FormatShapeSelectionOnType)(match.BestMatch)).formatShape; @@ -315,7 +319,7 @@ internal static FormatShape GetShapeFromType(MshExpressionFactory expressionFact else { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { FormatShape result = GetShapeFromType(expressionFactory, db, typesWithoutPrefix); return result; @@ -334,8 +338,7 @@ internal static FormatShape GetShapeFromPropertyCount(TypeInfoDataBase db, int p return FormatShape.List; } - - internal static ViewDefinition GetViewByShapeAndType(MshExpressionFactory expressionFactory, TypeInfoDataBase db, + internal static ViewDefinition GetViewByShapeAndType(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, FormatShape shape, Collection typeNames, string viewName) { if (shape == FormatShape.Undefined) @@ -365,10 +368,11 @@ internal static ViewDefinition GetViewByShapeAndType(MshExpressionFactory expres Diagnostics.Assert(false, "unknown shape: this should never happen unless a new shape is added"); return null; } + return GetView(expressionFactory, db, t, typeNames, viewName); } - internal static ViewDefinition GetOutOfBandView(MshExpressionFactory expressionFactory, + internal static ViewDefinition GetOutOfBandView(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) { TypeMatch match = new TypeMatch(expressionFactory, db, typeNames); @@ -387,10 +391,10 @@ internal static ViewDefinition GetOutOfBandView(MshExpressionFactory expressionF // 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 (null == result) + if (result == null) { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { result = GetOutOfBandView(expressionFactory, db, typesWithoutPrefix); } @@ -399,7 +403,7 @@ internal static ViewDefinition GetOutOfBandView(MshExpressionFactory expressionF return result; } - private static ViewDefinition GetView(MshExpressionFactory expressionFactory, TypeInfoDataBase db, System.Type mainControlType, Collection typeNames, string viewName) + private static ViewDefinition GetView(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, System.Type mainControlType, Collection typeNames, string viewName) { TypeMatch match = new TypeMatch(expressionFactory, db, typeNames); foreach (ViewDefinition vd in db.viewDefinitionsSection.viewDefinitionList) @@ -408,9 +412,10 @@ private static ViewDefinition GetView(MshExpressionFactory expressionFactory, Ty { ActiveTracer.WriteLine( "NOT MATCH {0} NAME: {1}", - ControlBase.GetControlShapeName(vd.mainControl), (null != vd ? vd.name : string.Empty)); + ControlBase.GetControlShapeName(vd.mainControl), (vd != null ? vd.name : string.Empty)); continue; } + if (IsOutOfBandView(vd)) { ActiveTracer.WriteLine( @@ -418,6 +423,7 @@ private static ViewDefinition GetView(MshExpressionFactory expressionFactory, Ty ControlBase.GetControlShapeName(vd.mainControl), vd.name); continue; } + if (vd.appliesTo == null) { ActiveTracer.WriteLine( @@ -450,6 +456,7 @@ private static ViewDefinition GetView(MshExpressionFactory expressionFactory, Ty { TypeMatch.ResetTracer(); } + TraceHelper(vd, false); } @@ -459,10 +466,10 @@ private static ViewDefinition GetView(MshExpressionFactory expressionFactory, Ty // 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 (null == result) + if (result == null) { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { result = GetView(expressionFactory, db, mainControlType, typesWithoutPrefix, viewName); } @@ -478,19 +485,27 @@ 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()); } } @@ -503,10 +518,11 @@ private static ViewDefinition GetBestMatch(TypeMatch match) { TraceHelper(bestMatchedVD, true); } + return bestMatchedVD; } - private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) + private static ViewDefinition GetDefaultView(PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, Collection typeNames) { TypeMatch match = new TypeMatch(expressionFactory, db, typeNames); @@ -522,6 +538,7 @@ private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFact ControlBase.GetControlShapeName(vd.mainControl), vd.name); continue; } + if (vd.appliesTo == null) { ActiveTracer.WriteLine( @@ -529,6 +546,7 @@ private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFact ControlBase.GetControlShapeName(vd.mainControl), vd.name); continue; } + try { TypeMatch.SetTracer(ActiveTracer); @@ -542,6 +560,7 @@ private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFact { TypeMatch.ResetTracer(); } + TraceHelper(vd, false); } // this is the best match we had @@ -549,10 +568,10 @@ private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFact // 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 (null == result) + if (result == null) { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { result = GetDefaultView(expressionFactory, db, typesWithoutPrefix); } @@ -561,38 +580,33 @@ private static ViewDefinition GetDefaultView(MshExpressionFactory expressionFact return result; } - private static bool IsOutOfBandView(ViewDefinition vd) { return (vd.mainControl is ComplexControlBody || vd.mainControl is ListControlBody) && vd.outOfBand; } /// - /// given an appliesTo list, it finds all the types that are contained (following type + /// Given an appliesTo list, it finds all the types that are contained (following type /// group references) /// - /// database to use - /// object to lookup + /// Database to use. + /// Object to lookup. /// internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo appliesTo) { - Hashtable allTypes = new Hashtable(StringComparer.OrdinalIgnoreCase); + var allTypes = new HashSet(StringComparer.OrdinalIgnoreCase); 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.ContainsKey(tr.name)) - allTypes.Add(tr.name, null); + 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 @@ -604,22 +618,20 @@ internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo a // we found the group, go over it foreach (TypeReference x in tgd.typeReferenceList) { - if (!allTypes.ContainsKey(x.name)) - allTypes.Add(x.name, null); + allTypes.Add(x.name); } } } AppliesTo retVal = new AppliesTo(); - foreach (DictionaryEntry x in allTypes) + foreach (string x in allTypes) { - retVal.AddAppliesToType(x.Key as string); + retVal.AddAppliesToType(x); } return retVal; } - internal static TypeGroupDefinition FindGroupDefinition(TypeInfoDataBase db, string groupName) { foreach (TypeGroupDefinition tgd in db.typeGroupSection.typeGroupDefinitionList) @@ -651,9 +663,10 @@ private static ControlBody ResolveControlReferenceInList(ControlReference contro { if (x.controlBody.GetType() != controlReference.controlType) continue; - if (String.Compare(controlReference.name, x.name, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(controlReference.name, x.name, StringComparison.OrdinalIgnoreCase)) return x.controlBody; } + return null; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs index 1e2b3edb761..b8a25e5e207 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Xml; using System.Globalization; +using System.Xml; using System.Management.Automation; using System.Management.Automation.Host; @@ -19,6 +18,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal sealed class XmlFileLoadInfo { internal XmlFileLoadInfo() { } + internal XmlFileLoadInfo(string dir, string path, ConcurrentBag errors, string psSnapinName) { fileDirectory = dir; @@ -26,16 +26,16 @@ internal XmlFileLoadInfo(string dir, string path, ConcurrentBag errors, this.errors = errors; this.psSnapinName = psSnapinName; } + internal string fileDirectory = null; internal string filePath = null; internal ConcurrentBag errors; internal string psSnapinName; } - /// - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -43,11 +43,11 @@ 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 /// - /// table of XML node tags used in the file format + /// Table of XML node tags used in the file format. /// private static class XmlTags { @@ -151,7 +151,7 @@ private static class XmlTags } /// - /// table of miscellanea string constant values for XML nodes + /// Table of miscellanea string constant values for XML nodes. /// private static class XMLStringValues { @@ -167,13 +167,12 @@ private static class XMLStringValues // processing pre-validated type / formatting information. private bool _suppressValidation = false; - /// - /// entry point for the loader algorithm + /// Entry point for the loader algorithm. /// - /// information needed to load the file - /// database instance to load the file into - /// expression factory to validate script blocks + /// Information needed to load the file. + /// Database instance to load the file into. + /// Expression factory to validate script blocks. /// /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) /// @@ -184,26 +183,26 @@ private static class XMLStringValues /// True if the format data has been pre-validated (build time, manual testing, etc) so that validation can be /// skipped at runtime. /// - /// true if successful + /// True if successful. internal bool LoadXmlFile( XmlFileLoadInfo info, TypeInfoDataBase db, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, AuthorizationManager authorizationManager, PSHost host, bool preValidated) { if (info == null) - throw PSTraceSource.NewArgumentNullException("info"); + throw PSTraceSource.NewArgumentNullException(nameof(info)); if (info.filePath == null) throw PSTraceSource.NewArgumentNullException("info.filePath"); if (db == null) - throw PSTraceSource.NewArgumentNullException("db"); + throw PSTraceSource.NewArgumentNullException(nameof(db)); if (expressionFactory == null) - throw PSTraceSource.NewArgumentNullException("expressionFactory"); + throw PSTraceSource.NewArgumentNullException(nameof(expressionFactory)); if (SecuritySupport.IsProductBinary(info.filePath)) { @@ -252,10 +251,12 @@ internal bool LoadXmlFile( } catch (Exception e) // will rethrow { - //Error in file {0}: {1} + // Error in file {0}: {1} + this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ErrorInFile, FilePath, e.Message)); throw; } + if (this.HasErrors) { return false; @@ -271,29 +272,29 @@ internal bool LoadXmlFile( } /// - /// entry point for the loader algorithm to load formatting data from ExtendedTypeDefinition + /// Entry point for the loader algorithm to load formatting data from ExtendedTypeDefinition. /// - /// 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)? - /// true when the view is for help output + /// 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 language mode)? + /// True when the view is for help output. /// internal bool LoadFormattingData( ExtendedTypeDefinition typeDefinition, TypeInfoDataBase db, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, bool isBuiltInFormatData, bool isForHelp) { if (typeDefinition == null) - throw PSTraceSource.NewArgumentNullException("typeDefinition"); + throw PSTraceSource.NewArgumentNullException(nameof(typeDefinition)); if (typeDefinition.TypeName == null) throw PSTraceSource.NewArgumentNullException("typeDefinition.TypeName"); if (db == null) - throw PSTraceSource.NewArgumentNullException("db"); + throw PSTraceSource.NewArgumentNullException(nameof(db)); if (expressionFactory == null) - throw PSTraceSource.NewArgumentNullException("expressionFactory"); + throw PSTraceSource.NewArgumentNullException(nameof(expressionFactory)); this.expressionFactory = expressionFactory; this.ReportTrace("loading ExtendedTypeDefinition started"); @@ -311,11 +312,13 @@ internal bool LoadFormattingData( } catch (Exception e) // will rethrow { - //Error in formatting data "{0}": {1} + // Error in formatting data "{0}": {1} + this.ReportErrorForLoadingFromObjectModel( StringUtil.Format(FormatAndOutXmlLoadingStrings.ErrorInFormattingData, typeDefinition.TypeName, e.Message), typeDefinition.TypeName); throw; } + if (this.HasErrors) { return false; @@ -326,18 +329,18 @@ internal bool LoadFormattingData( } /// - /// load the content of the XML document into the data instance. - /// It assumes that the XML document has been successfully loaded + /// Load the content of the XML document into the data instance. + /// It assumes that the XML document has been successfully loaded. /// - /// XML document to load from, cannot be null - /// instance of the databaseto load into + /// XML document to load from, cannot be null. + /// Instance of the databaseto load into. private void LoadData(XmlDocument doc, TypeInfoDataBase db) { if (doc == null) - throw PSTraceSource.NewArgumentNullException("doc"); + throw PSTraceSource.NewArgumentNullException(nameof(doc)); if (db == null) - throw PSTraceSource.NewArgumentNullException("db"); + throw PSTraceSource.NewArgumentNullException(nameof(db)); // create a new instance of the database to be loaded XmlElement documentElement = doc.DocumentElement; @@ -347,7 +350,7 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) bool viewDefinitionsFound = false; bool controlDefinitionsFound = false; - if (MatchNodeName(documentElement, XmlTags.ConfigurationNode)) + if (MatchNodeNameWithAttributes(documentElement, XmlTags.ConfigurationNode)) { // load the various sections using (this.StackFrame(documentElement)) @@ -369,6 +372,7 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) { ProcessDuplicateNode(n); } + typeGroupsFound = true; LoadTypeGroups(db, n); } @@ -378,6 +382,7 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) { ProcessDuplicateNode(n); } + viewDefinitionsFound = true; LoadViewDefinitions(db, n); } @@ -387,6 +392,7 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) { ProcessDuplicateNode(n); } + controlDefinitionsFound = true; LoadControlDefinitions(n, db.formatControlDefinitionHolder.controlDefinitionList); } @@ -394,8 +400,8 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) { ProcessUnknownNode(n); } - } // foreach - } // using + } + } } else { @@ -406,23 +412,23 @@ private void LoadData(XmlDocument doc, TypeInfoDataBase db) #region load formatting data from FormatViewDefinition /// - /// load the content of the ExtendedTypeDefinition instance into the db. + /// Load the content of the ExtendedTypeDefinition instance into the db. /// Only support following view controls: /// TableControl /// ListControl /// WideControl - /// CustomControl + /// CustomControl. /// - /// ExtendedTypeDefinition instances to load from, cannot be null - /// instance of the database to load into - /// true if the formatter is used for formatting help objects + /// ExtendedTypeDefinition instances to load from, cannot be null. + /// Instance of the database to load into. + /// True if the formatter is used for formatting help objects. private void LoadData(ExtendedTypeDefinition typeDefinition, TypeInfoDataBase db, bool isForHelpOutput) { if (typeDefinition == null) throw PSTraceSource.NewArgumentNullException("viewDefinition"); if (db == null) - throw PSTraceSource.NewArgumentNullException("db"); + throw PSTraceSource.NewArgumentNullException(nameof(db)); int viewIndex = 0; foreach (FormatViewDefinition formatView in typeDefinition.FormatViewDefinition) @@ -430,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); @@ -445,9 +454,9 @@ private void LoadData(ExtendedTypeDefinition typeDefinition, TypeInfoDataBase db } /// - /// Load the view into a ViewDefinition + /// Load the view into a ViewDefinition. /// - /// the TypeName tag under SelectedBy tag + /// The TypeName tag under SelectedBy tag. /// /// /// @@ -509,6 +518,7 @@ private ViewDefinition LoadViewFromObjectModel(List typeNames, FormatVie { view.groupBy.startGroup.labelTextToken = new TextToken { text = control.GroupBy.Label }; } + if (control.GroupBy.CustomControl != null) { view.groupBy.startGroup.control = LoadCustomControlFromObjectModel(control.GroupBy.CustomControl, viewIndex, firstTypeName); @@ -558,7 +568,7 @@ private ControlBase LoadTableControlFromObjectModel(TableControl table, int view if (tableBody.header.columnHeaderDefinitionList.Count != tableBody.defaultDefinition.rowItemDefinitionList.Count) { - //Error at XPath {0} in file {1}: Header item count = {2} does not match default row item count = {3}. + // Error at XPath {0} in file {1}: Header item count = {2} does not match default row item count = {3}. this.ReportErrorForLoadingFromObjectModel( StringUtil.Format(FormatAndOutXmlLoadingStrings.IncorrectHeaderItemCountInFormattingData, typeName, viewIndex, tableBody.header.columnHeaderDefinitionList.Count, @@ -575,11 +585,11 @@ private ControlBase LoadTableControlFromObjectModel(TableControl table, int view } /// - /// Load the headers defined for columns + /// Load the headers defined for columns. /// /// /// - private void LoadHeadersSectionFromObjectModel(TableControlBody tableBody, List headers) + private static void LoadHeadersSectionFromObjectModel(TableControlBody tableBody, List headers) { foreach (TableControlColumnHeader header in headers) { @@ -589,7 +599,7 @@ private void LoadHeadersSectionFromObjectModel(TableControlBody tableBody, List< // Label --- Label cardinality 0..1 // Width --- Width cardinality 0..1 // Alignment --- Alignment cardinality 0..1 - if (!String.IsNullOrEmpty(header.Label)) + if (!string.IsNullOrEmpty(header.Label)) { TextToken tt = new TextToken(); tt.text = header.Label; @@ -650,7 +660,7 @@ private void LoadRowEntriesSectionFromObjectModel(TableControlBody tableBody, Li } /// - /// Load the column items into the TableRowDefinition + /// Load the column items into the TableRowDefinition. /// /// /// @@ -673,6 +683,7 @@ private void LoadColumnEntriesFromObjectModel(TableRowDefinition trd, List - /// Load the expression information from DisplayEntry + /// Load the expression information from DisplayEntry. /// /// /// @@ -735,10 +746,10 @@ private ExpressionToken LoadExpressionFromObjectModel(DisplayEntry displayEntry, } /// - /// Load EntrySelectedBy (TypeName) into AppliesTo + /// Load EntrySelectedBy (TypeName) into AppliesTo. /// /// - private AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, List condition) + private static AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, List condition) { AppliesTo appliesTo = new AppliesTo(); @@ -746,7 +757,7 @@ private AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, L { foreach (string type in selectedBy) { - if (String.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(type)) return null; TypeReference tr = new TypeReference { name = type }; appliesTo.referenceList.Add(tr); @@ -767,7 +778,7 @@ private AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, L #region Load ListControl /// - /// Load LoisControl into the ListControlBody + /// Load LoisControl into the ListControlBody. /// /// /// @@ -783,6 +794,7 @@ private ListControlBody LoadListControlFromObjectModel(ListControl list, int vie { return null; // fatal error } + return listBody; } @@ -809,7 +821,7 @@ private void LoadListControlEntriesFromObjectModel(ListControlBody listBody, Lis } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportErrorForLoadingFromObjectModel( StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntryInFormattingData, typeName, viewIndex, XmlTags.ListEntryNode), typeName); listBody.defaultEntryDefinition = null; @@ -831,7 +843,7 @@ private void LoadListControlEntriesFromObjectModel(ListControlBody listBody, Lis } /// - /// Load ListEntry into ListControlEntryDefinition + /// Load ListEntry into ListControlEntryDefinition. /// /// /// @@ -859,7 +871,7 @@ private ListControlEntryDefinition LoadListControlEntryDefinitionFromObjectModel } /// - /// Load ListItems into ListControlItemDefinition + /// Load ListItems into ListControlItemDefinition. /// /// /// @@ -882,13 +894,14 @@ private void LoadListControlItemDefinitionsFromObjectModel(ListControlEntryDefin lved.itemDefinitionList = null; return; // fatal } + FieldPropertyToken fpt = new FieldPropertyToken(); fpt.expression = expression; fpt.fieldFormattingDirective.formatString = listItem.FormatString; lvid.formatTokenList.Add(fpt); } - if (!String.IsNullOrEmpty(listItem.Label)) + if (!string.IsNullOrEmpty(listItem.Label)) { TextToken tt = new TextToken(); tt.text = listItem.Label; @@ -901,11 +914,11 @@ private void LoadListControlItemDefinitionsFromObjectModel(ListControlEntryDefin // we must have at least a definition in th elist if (lved.itemDefinitionList.Count == 0) { - //Error: At least one list view item must be specified. + // Error: At least one list view item must be specified. this.ReportErrorForLoadingFromObjectModel( StringUtil.Format(FormatAndOutXmlLoadingStrings.NoListViewItemInFormattingData, typeName, viewIndex), typeName); lved.itemDefinitionList = null; - return; //fatal + return; // fatal } } @@ -914,7 +927,7 @@ private void LoadListControlItemDefinitionsFromObjectModel(ListControlEntryDefin #region Load WideControl /// - /// Load the WideControl into the WideControlBody + /// Load the WideControl into the WideControlBody. /// /// /// @@ -938,11 +951,12 @@ private WideControlBody LoadWideControlFromObjectModel(WideControl wide, int vie // if we have no default entry definition, it means there was a failure return null; } + return wideBody; } /// - /// Load WideEntries + /// Load WideEntries. /// /// /// @@ -970,7 +984,7 @@ private void LoadWideControlEntriesFromObjectModel(WideControlBody wideBody, Lis } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportErrorForLoadingFromObjectModel( StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntryInFormattingData, typeName, viewIndex, XmlTags.WideEntryNode), typeName); wideBody.defaultEntryDefinition = null; @@ -982,6 +996,7 @@ private void LoadWideControlEntriesFromObjectModel(WideControlBody wideBody, Lis wideBody.optionalEntryList.Add(wved); } } + if (wideBody.defaultEntryDefinition == null) { this.ReportErrorForLoadingFromObjectModel( @@ -990,7 +1005,7 @@ private void LoadWideControlEntriesFromObjectModel(WideControlBody wideBody, Lis } /// - /// Load WideEntry into WieControlEntryDefinition + /// Load WideEntry into WieControlEntryDefinition. /// /// /// @@ -1015,6 +1030,7 @@ private WideControlEntryDefinition LoadWideControlEntryFromObjectModel(WideContr { return null; // fatal } + FieldPropertyToken fpt = new FieldPropertyToken(); fpt.expression = expression; fpt.fieldFormattingDirective.formatString = wideItem.FormatString; @@ -1058,29 +1074,28 @@ private ComplexControlEntryDefinition LoadComplexControlEntryDefinitionFromObjec { cced.appliesTo = LoadAppliesToSectionFromObjectModel(entry.SelectedBy.TypeNames, entry.SelectedBy.SelectionCondition); } + foreach (var item in entry.CustomItems) { cced.itemDefinition.formatTokenList.Add(LoadFormatTokenFromObjectModel(item, viewIndex, typeName)); } + return cced; } 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 }; @@ -1102,15 +1117,14 @@ private FormatToken LoadFormatTokenFromObjectModel(CustomItemBase item, int view return cpt; } - var frame = (CustomItemFrame)item; var frameToken = new FrameToken { 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 } }; @@ -1118,6 +1132,7 @@ private FormatToken LoadFormatTokenFromObjectModel(CustomItemBase item, int view { frameToken.itemDefinition.formatTokenList.Add(LoadFormatTokenFromObjectModel(i, viewIndex, typeName)); } + return frameToken; } @@ -1148,6 +1163,7 @@ private void LoadDefaultSettings(TypeInfoDataBase db, XmlNode defaultSettingsNod { ProcessDuplicateNode(n); } + showErrorsAsMessagesFound = true; if (ReadBooleanNode(n, out tempVal)) db.defaultSettingsSection.formatErrorPolicy.ShowErrorsAsMessages = tempVal; @@ -1158,6 +1174,7 @@ private void LoadDefaultSettings(TypeInfoDataBase db, XmlNode defaultSettingsNod { ProcessDuplicateNode(n); } + showErrorsInFormattedOutputFound = true; if (ReadBooleanNode(n, out tempVal)) db.defaultSettingsSection.formatErrorPolicy.ShowErrorsInFormattedOutput = tempVal; @@ -1177,7 +1194,7 @@ private void LoadDefaultSettings(TypeInfoDataBase db, XmlNode defaultSettingsNod } else { - //Error at XPath {0} in file {1}: Invalid {2} value. + // Error at XPath {0} in file {1}: Invalid {2} value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNodeValue, ComputeCurrentXPath(), FilePath, XmlTags.PropertyCountForTableNode)); } } @@ -1195,7 +1212,7 @@ private void LoadDefaultSettings(TypeInfoDataBase db, XmlNode defaultSettingsNod } else { - //Error at XPath {0} in file {1}: Invalid {2} value. + // Error at XPath {0} in file {1}: Invalid {2} value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNodeValue, ComputeCurrentXPath(), FilePath, XmlTags.MultilineTablesNode)); } } @@ -1205,6 +1222,7 @@ private void LoadDefaultSettings(TypeInfoDataBase db, XmlNode defaultSettingsNod { ProcessDuplicateNode(n); } + enumerableExpansionsFound = true; db.defaultSettingsSection.enumerableExpansionDirectiveList = LoadEnumerableExpansionDirectiveList(n); @@ -1231,10 +1249,11 @@ private List LoadEnumerableExpansionDirectiveList( EnumerableExpansionDirective eed = LoadEnumerableExpansionDirective(n, k++); if (eed == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.EnumerableExpansionNode)); return null; // fatal error } + retVal.Add(eed); } else @@ -1243,6 +1262,7 @@ private List LoadEnumerableExpansionDirectiveList( } } } + return retVal; } @@ -1262,7 +1282,7 @@ private EnumerableExpansionDirective LoadEnumerableExpansionDirective(XmlNode di if (appliesToNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } appliesToNodeFound = true; @@ -1273,21 +1293,22 @@ private EnumerableExpansionDirective LoadEnumerableExpansionDirective(XmlNode di if (expandNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } expandNodeFound = true; string s = GetMandatoryInnerText(n); if (s == null) { - return null; //fatal + return null; // fatal } + bool success = EnumerableExpansionConversion.Convert(s, out eed.enumerableExpansion); if (!success) { - //Error at XPath {0} in file {1}: Invalid {2} value. + // Error at XPath {0} in file {1}: Invalid {2} value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNodeValue, ComputeCurrentXPath(), FilePath, XmlTags.ExpandNode)); - return null; //fatal + return null; // fatal } } else @@ -1295,13 +1316,13 @@ private EnumerableExpansionDirective LoadEnumerableExpansionDirective(XmlNode di this.ProcessUnknownNode(n); } } + return eed; } } #endregion - #region Type Groups Loading private void LoadTypeGroups(TypeInfoDataBase db, XmlNode typeGroupsNode) @@ -1320,8 +1341,8 @@ private void LoadTypeGroups(TypeInfoDataBase db, XmlNode typeGroupsNode) { ProcessUnknownNode(n); } - } // for each - } //using + } + } } private void LoadTypeGroup(TypeInfoDataBase db, XmlNode typeGroupNode, int index) @@ -1362,7 +1383,7 @@ private void LoadTypeGroup(TypeInfoDataBase db, XmlNode typeGroupNode, int index // finally add to the list db.typeGroupSection.typeGroupDefinitionList.Add(typeGroupDefinition); - } // using + } } private void LoadTypeGroupTypeRefs(XmlNode typesNode, TypeGroupDefinition typeGroupDefinition) @@ -1381,7 +1402,7 @@ private void LoadTypeGroupTypeRefs(XmlNode typesNode, TypeGroupDefinition typeGr tr.name = GetMandatoryInnerText(n); typeGroupDefinition.typeReferenceList.Add(tr); - } // using + } } else { @@ -1393,7 +1414,6 @@ private void LoadTypeGroupTypeRefs(XmlNode typesNode, TypeGroupDefinition typeGr #endregion - #region AppliesTo Loading private AppliesTo LoadAppliesToSection(XmlNode appliesToNode, bool allowSelectionCondition) @@ -1447,13 +1467,13 @@ private AppliesTo LoadAppliesToSection(XmlNode appliesToNode, bool allowSelectio { this.ProcessUnknownNode(n); } - } // using + } } if (appliesTo.referenceList.Count == 0) { // we do not accept an empty list - //Error at XPath {0} in file {1}: No type or condition is specified for applying the view. + // Error at XPath {0} in file {1}: No type or condition is specified for applying the view. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.EmptyAppliesTo, ComputeCurrentXPath(), FilePath)); return null; } @@ -1472,6 +1492,7 @@ private TypeReference LoadTypeReference(XmlNode n) tr.name = val; return tr; } + return null; } @@ -1485,6 +1506,7 @@ private TypeGroupReference LoadTypeGroupReference(XmlNode n) tgr.name = val; return tgr; } + return null; } @@ -1511,6 +1533,7 @@ private TypeOrGroupReference LoadSelectionConditionNode(XmlNode selectionConditi this.ProcessDuplicateAlternateNode(n, XmlTags.SelectionSetNameNode, XmlTags.TypeNameNode); return null; } + typeGroupFound = true; TypeGroupReference tgr = LoadTypeGroupReference(n); if (tgr != null) @@ -1529,6 +1552,7 @@ private TypeOrGroupReference LoadSelectionConditionNode(XmlNode selectionConditi this.ProcessDuplicateAlternateNode(n, XmlTags.SelectionSetNameNode, XmlTags.TypeNameNode); return null; } + typeFound = true; TypeReference tr = LoadTypeReference(n); if (tr != null) @@ -1547,6 +1571,7 @@ private TypeOrGroupReference LoadSelectionConditionNode(XmlNode selectionConditi this.ProcessDuplicateNode(n); return null; // fatal error } + expressionNodeFound = true; if (!expressionMatch.ProcessNode(n)) return null; // fatal error @@ -1557,10 +1582,9 @@ private TypeOrGroupReference LoadSelectionConditionNode(XmlNode selectionConditi } } - if (typeFound && typeGroupFound) { - //Error at XPath {0} in file {1}: Cannot have SelectionSetName and TypeName at the same time. + // Error at XPath {0} in file {1}: Cannot have SelectionSetName and TypeName at the same time. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.SelectionSetNameAndTypeName, ComputeCurrentXPath(), FilePath)); return null; // fatal error } @@ -1580,10 +1604,11 @@ private TypeOrGroupReference LoadSelectionConditionNode(XmlNode selectionConditi { return null; // fatal error } + return retVal; } // failure: expression is mandatory - //Error at XPath {0} in file {1}: An expression is expected. + // Error at XPath {0} in file {1}: An expression is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectExpression, ComputeCurrentXPath(), FilePath)); return null; } @@ -1617,6 +1642,7 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) this.ProcessDuplicateNode(n); return null; // fatal error } + expressionNodeFound = true; if (!expressionMatch.ProcessNode(n)) return null; // fatal error @@ -1628,6 +1654,7 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) this.ProcessDuplicateAlternateNode(n, XmlTags.ComplexControlNode, XmlTags.ComplexControlNameNode); return null; } + controlFound = true; if (!controlMatch.ProcessNode(n)) return null; // fatal error @@ -1639,6 +1666,7 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) this.ProcessDuplicateAlternateNode(n, XmlTags.ComplexControlNode, XmlTags.ComplexControlNameNode); return null; } + labelFound = true; labelTextToken = LoadLabel(n); @@ -1655,7 +1683,7 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) if (controlFound && labelFound) { - //Error at XPath {0} in file {1}: Cannot have control and label at the same time. + // Error at XPath {0} in file {1}: Cannot have control and label at the same time. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ControlAndLabel, ComputeCurrentXPath(), FilePath)); return null; // fatal error } @@ -1664,10 +1692,11 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) { if (!expressionNodeFound) { - //Error at XPath {0} in file {1}: Cannot have control or label without an expression. + // Error at XPath {0} in file {1}: Cannot have control or label without an expression. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ControlLabelWithoutExpression, ComputeCurrentXPath(), FilePath)); return null; // fatal error } + if (controlFound) { groupBy.startGroup.control = controlMatch.Control; @@ -1686,19 +1715,18 @@ private GroupBy LoadGroupBySection(XmlNode groupByNode) { return null; // fatal error } + groupBy.startGroup.expression = expression; return groupBy; } - // failure: expression is mandatory - //Error at XPath {0} in file {1}: An expression is expected. + // Error at XPath {0} in file {1}: An expression is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectExpression, ComputeCurrentXPath(), FilePath)); return null; } } - private TextToken LoadLabel(XmlNode textNode) { using (this.StackFrame(textNode)) @@ -1715,6 +1743,7 @@ private TextToken LoadTextToken(XmlNode n) { return null; } + if (tt.resource != null) { // inner text is optional @@ -1734,11 +1763,10 @@ 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. + // Error at XPath {0} in file {1}: Node should be an XmlElement. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NonXmlElementNode, ComputeCurrentXPath(), FilePath)); return false; } @@ -1792,11 +1820,13 @@ private StringResourceReference LoadResourceAttributes(XmlAttributeCollection at ReportMissingAttribute(XmlTags.AssemblyNameAttribute); return null; } + if (resource.baseName == null) { ReportMissingAttribute(XmlTags.BaseNameAttribute); return null; } + if (resource.resourceId == null) { ReportMissingAttribute(XmlTags.ResourceIdAttribute); @@ -1806,7 +1836,6 @@ private StringResourceReference LoadResourceAttributes(XmlAttributeCollection at // success in loading resource.loadingInfo = this.LoadingInfo; - // optional pre-load and binding verification if (this.VerifyStringResources) { @@ -1834,17 +1863,20 @@ private void ReportStringResourceFailure(StringResourceReference resource, { assemblyDisplayName = resource.assemblyLocation; } + break; case DisplayResourceManagerCache.AssemblyBindingStatus.FoundInGac: { - //"(Global Assembly Cache) {0}" + // "(Global Assembly Cache) {0}" assemblyDisplayName = StringUtil.Format(FormatAndOutXmlLoadingStrings.AssemblyInGAC, resource.assemblyName); } + break; default: { assemblyDisplayName = resource.assemblyName; } + break; } @@ -1853,24 +1885,28 @@ private void ReportStringResourceFailure(StringResourceReference resource, { case DisplayResourceManagerCache.LoadingResult.AssemblyNotFound: { - //Error at XPath {0} in file {1}: Assembly {2} is not found. + // Error at XPath {0} in file {1}: Assembly {2} is not found. msg = StringUtil.Format(FormatAndOutXmlLoadingStrings.AssemblyNotFound, ComputeCurrentXPath(), FilePath, assemblyDisplayName); } + break; case DisplayResourceManagerCache.LoadingResult.ResourceNotFound: { - //Error at XPath {0} in file {1}: Resource {2} in assembly {3} is not found. + // Error at XPath {0} in file {1}: Resource {2} in assembly {3} is not found. msg = StringUtil.Format(FormatAndOutXmlLoadingStrings.ResourceNotFound, ComputeCurrentXPath(), FilePath, resource.baseName, assemblyDisplayName); } + break; case DisplayResourceManagerCache.LoadingResult.StringNotFound: { - //Error at XPath {0} in file {1}: String {2} from resource {3} in assembly {4} is not found. + // Error at XPath {0} in file {1}: String {2} from resource {3} in assembly {4} is not found. msg = StringUtil.Format(FormatAndOutXmlLoadingStrings.StringResourceNotFound, ComputeCurrentXPath(), FilePath, resource.resourceId, resource.baseName, assemblyDisplayName); } + break; } + this.ReportError(msg); } @@ -1878,11 +1914,11 @@ private void ReportStringResourceFailure(StringResourceReference resource, #region Expression Loading /// - /// helper to verify the text of a string block and - /// log an error if an exception is thrown + /// Helper to verify the text of a string block and + /// log an error if an exception is thrown. /// - /// script block string to verify - /// true if parsed correctly, false if failed + /// Script block string to verify. + /// True if parsed correctly, false if failed. internal bool VerifyScriptBlock(string scriptBlockText) { try @@ -1891,7 +1927,7 @@ internal bool VerifyScriptBlock(string scriptBlockText) } catch (ParseException e) { - //Error at XPath {0} in file {1}: Invalid script block "{2}". + // Error at XPath {0} in file {1}: Invalid script block "{2}". this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidScriptBlock, ComputeCurrentXPath(), FilePath, e.Message)); return false; } @@ -1900,11 +1936,12 @@ internal bool VerifyScriptBlock(string scriptBlockText) Diagnostics.Assert(false, "TypeInfoBaseLoader.VerifyScriptBlock unexpected exception " + e.GetType().FullName); throw; } + return true; } /// - /// helper class to wrap the loading of a script block/property name alternative tag + /// Helper class to wrap the loading of a script block/property name alternative tag. /// private sealed class ExpressionNodeMatch { @@ -1912,6 +1949,7 @@ internal ExpressionNodeMatch(TypeInfoDataBaseLoader loader) { _loader = loader; } + internal bool MatchNode(XmlNode n) { return _loader.MatchNodeName(n, XmlTags.PropertyNameNode) || _loader.MatchNodeName(n, XmlTags.ScriptBlockNode); @@ -1934,11 +1972,12 @@ internal bool ProcessNode(XmlNode n) _token.expressionValue = _loader.GetMandatoryInnerText(n); if (_token.expressionValue == null) { - //Error at XPath {0} in file {1}: Missing property. + // Error at XPath {0} in file {1}: Missing property. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoProperty, _loader.ComputeCurrentXPath(), _loader.FilePath)); _fatalError = true; return false; // fatal error } + return true; } else if (_loader.MatchNodeName(n, XmlTags.ScriptBlockNode)) @@ -1957,7 +1996,7 @@ internal bool ProcessNode(XmlNode n) _token.expressionValue = _loader.GetMandatoryInnerText(n); if (_token.expressionValue == null) { - //Error at XPath {0} in file {1}: Missing script block text. + // Error at XPath {0} in file {1}: Missing script block text. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoScriptBlockText, _loader.ComputeCurrentXPath(), _loader.FilePath)); _fatalError = true; return false; // fatal error @@ -1968,6 +2007,7 @@ internal bool ProcessNode(XmlNode n) _fatalError = true; return false; // fatal error } + return true; } // this should never happen if the API is used correctly @@ -1990,17 +2030,18 @@ internal ExpressionToken GenerateExpressionToken() _loader.ReportMissingNodes(new string[] { XmlTags.PropertyNameNode, XmlTags.ScriptBlockNode }); return null; } + return _token; } - private TypeInfoDataBaseLoader _loader; + private readonly TypeInfoDataBaseLoader _loader; private ExpressionToken _token; private bool _fatalError = false; } /// - /// helper class to wrap the loading of an expression (using ExpressionNodeMatch) - /// plus the formatting string and an alternative text node + /// Helper class to wrap the loading of an expression (using ExpressionNodeMatch) + /// plus the formatting string and an alternative text node. /// private sealed class ViewEntryNodeMatch { @@ -2012,7 +2053,7 @@ internal ViewEntryNodeMatch(TypeInfoDataBaseLoader loader) internal bool ProcessExpressionDirectives(XmlNode containerNode, List unprocessedNodes) { if (containerNode == null) - throw PSTraceSource.NewArgumentNullException("containerNode"); + throw PSTraceSource.NewArgumentNullException(nameof(containerNode)); string formatString = null; TextToken textToken = null; @@ -2031,6 +2072,7 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u _loader.ProcessDuplicateNode(n); return false; // fatal error } + expressionNodeFound = true; if (!expressionMatch.ProcessNode(n)) return false; // fatal error @@ -2047,7 +2089,7 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u formatString = _loader.GetMandatoryInnerText(n); if (formatString == null) { - //Error at XPath {0} in file {1}: Missing a format string. + // Error at XPath {0} in file {1}: Missing a format string. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoFormatString, _loader.ComputeCurrentXPath(), _loader.FilePath)); return false; // fatal error } @@ -2059,11 +2101,12 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u _loader.ProcessDuplicateNode(n); return false; // fatal error } + textNodeFound = true; textToken = _loader.LoadText(n); if (textToken == null) { - //Error at XPath {0} in file {1}: Invalid {2}. + // Error at XPath {0} in file {1}: Invalid {2}. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNode, _loader.ComputeCurrentXPath(), _loader.FilePath, XmlTags.TextNode)); return false; // fatal error } @@ -2073,14 +2116,14 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u // for further processing by calling context unprocessedNodes.Add(n); } - } // foreach + } if (expressionNodeFound) { // RULE: cannot have a text node and an expression at the same time if (textNodeFound) { - //Error at XPath {0} in file {1}: {2} cannot be specified with an expression. + // Error at XPath {0} in file {1}: {2} cannot be specified with an expression. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NodeWithExpression, _loader.ComputeCurrentXPath(), _loader.FilePath, XmlTags.TextNode)); return false; // fatal error @@ -2097,6 +2140,7 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u { _formatString = formatString; } + _expression = expression; } else @@ -2104,7 +2148,7 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u // RULE: we cannot have a format string without an expression node if (formatStringNodeFound) { - //Error at XPath {0} in file {1}: {2} cannot be specified without an expression. + // Error at XPath {0} in file {1}: {2} cannot be specified without an expression. _loader.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NodeWithoutExpression, _loader.ComputeCurrentXPath(), _loader.FilePath, XmlTags.FormatStringNode)); return false; // fatal error @@ -2121,14 +2165,16 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u } internal string FormatString { get { return _formatString; } } + internal TextToken TextToken { get { return _textToken; } } + internal ExpressionToken Expression { get { return _expression; } } private string _formatString; private TextToken _textToken; private ExpressionToken _expression; - private TypeInfoDataBaseLoader _loader; + private readonly TypeInfoDataBaseLoader _loader; } #endregion @@ -2163,6 +2209,7 @@ internal bool ProcessNode(XmlNode n) { return false; } + ControlReference controlRef = new ControlReference(); controlRef.name = name; controlRef.controlType = typeof(ComplexControlBody); @@ -2180,11 +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_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Complex.cs index cb1fe4b71b5..3c9cecaed1b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Complex.cs @@ -1,17 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Xml; using System.Management.Automation.Internal; +using System.Xml; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -53,6 +52,7 @@ private ComplexControlBody LoadComplexControl(XmlNode controlNode) this.ReportMissingNode(XmlTags.ComplexEntriesNode); return null; // fatal error } + return complexBody; } } @@ -70,7 +70,7 @@ private void LoadComplexControlEntries(XmlNode complexControlEntriesNode, Comple ComplexControlEntryDefinition cced = LoadComplexControlEntryDefinition(n, entryIndex++); if (cced == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.ComplexEntryNode)); complexBody.defaultEntry = null; return; // fatal error @@ -84,7 +84,7 @@ private void LoadComplexControlEntries(XmlNode complexControlEntriesNode, Comple } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.ComplexEntryNode)); complexBody.defaultEntry = null; return; // fatal error @@ -100,9 +100,10 @@ private void LoadComplexControlEntries(XmlNode complexControlEntriesNode, Comple this.ProcessUnknownNode(n); } } + if (complexBody.defaultEntry == null) { - //Error at XPath {0} in file {1}: There must be at least one default {2}. + // Error at XPath {0} in file {1}: There must be at least one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.ComplexEntryNode)); } } @@ -124,7 +125,7 @@ private ComplexControlEntryDefinition LoadComplexControlEntryDefinition(XmlNode if (appliesToNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } appliesToNodeFound = true; @@ -137,8 +138,9 @@ private ComplexControlEntryDefinition LoadComplexControlEntryDefinition(XmlNode if (bodyNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } + bodyNodeFound = true; cced.itemDefinition.formatTokenList = LoadComplexControlTokenListDefinitions(n); } @@ -150,7 +152,7 @@ private ComplexControlEntryDefinition LoadComplexControlEntryDefinition(XmlNode if (cced.itemDefinition.formatTokenList == null) { - //MissingNode=Error at XPath {0} in file {1}: Missing Node {2}. + // MissingNode=Error at XPath {0} in file {1}: Missing Node {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingNode, ComputeCurrentXPath(), FilePath, XmlTags.ComplexItemNode)); return null; } @@ -178,7 +180,7 @@ private List LoadComplexControlTokenListDefinitions(XmlNode bodyNod if (cpt == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.ExpressionBindingNode)); return null; } @@ -191,7 +193,7 @@ private List LoadComplexControlTokenListDefinitions(XmlNode bodyNod if (nlt == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.NewLineNode)); return null; } @@ -204,7 +206,7 @@ private List LoadComplexControlTokenListDefinitions(XmlNode bodyNod if (tt == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.TextNode)); return null; } @@ -217,7 +219,7 @@ private List LoadComplexControlTokenListDefinitions(XmlNode bodyNod if (frame == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.FrameNode)); return null; } @@ -232,7 +234,7 @@ private List LoadComplexControlTokenListDefinitions(XmlNode bodyNod if (formatTokenList.Count == 0) { - //Error at XPath {0} in file {1}: Empty custom control token list. + // Error at XPath {0} in file {1}: Empty custom control token list. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.EmptyCustomControlList, ComputeCurrentXPath(), FilePath)); return null; } @@ -260,6 +262,7 @@ private bool LoadPropertyBaseHelper(XmlNode propertyBaseNode, PropertyTokenBase this.ProcessDuplicateNode(n); return false; // fatal error } + expressionNodeFound = true; if (!expressionMatch.ProcessNode(n)) return false; // fatal error @@ -283,6 +286,7 @@ private bool LoadPropertyBaseHelper(XmlNode propertyBaseNode, PropertyTokenBase this.ProcessDuplicateNode(n); return false; } + itemSelectionConditionNodeFound = true; condition = LoadItemSelectionCondition(n); if (condition == null) @@ -295,8 +299,7 @@ private bool LoadPropertyBaseHelper(XmlNode propertyBaseNode, PropertyTokenBase if (!IsFilteredOutNode(n)) unprocessedNodes.Add(n); } - } // foreach - + } if (expressionNodeFound) { @@ -369,6 +372,7 @@ private CompoundPropertyToken LoadCompoundProperty(XmlNode compoundPropertyNode, this.ProcessDuplicateAlternateNode(n, XmlTags.ComplexControlNode, XmlTags.ComplexControlNameNode); return null; } + complexControlFound = true; if (!controlMatch.ProcessNode(n)) return null; // fatal error @@ -380,6 +384,7 @@ private CompoundPropertyToken LoadCompoundProperty(XmlNode compoundPropertyNode, this.ProcessDuplicateAlternateNode(n, XmlTags.ComplexControlNode, XmlTags.ComplexControlNameNode); return null; // fatal error } + fieldControlFound = true; fieldControlBody = new FieldControlBody(); fieldControlBody.fieldFormattingDirective.formatString = GetMandatoryInnerText(n); @@ -428,6 +433,7 @@ private NewLineToken LoadNewLine(XmlNode newLineNode, int index) { return null; } + NewLineToken nlt = new NewLineToken(); return nlt; @@ -460,18 +466,19 @@ private int LoadIntegerValue(XmlNode node, out bool success) { return retVal; } + string val = this.GetMandatoryInnerText(node); if (val == null) { - //Error at XPath {0} in file {1}: Missing inner text value. + // Error at XPath {0} in file {1}: Missing inner text value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingInnerText, ComputeCurrentXPath(), FilePath)); return retVal; } if (!int.TryParse(val, out retVal)) { - //Error at XPath {0} in file {1}: An integer is expected. + // Error at XPath {0} in file {1}: An integer is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectInteger, ComputeCurrentXPath(), FilePath)); return retVal; } @@ -490,10 +497,11 @@ private int LoadPositiveOrZeroIntegerValue(XmlNode node, out bool success) { if (val < 0) { - //Error at XPath {0} in file {1}: A non-negative integer is expected. + // Error at XPath {0} in file {1}: A non-negative integer is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectNaturalNumber, ComputeCurrentXPath(), FilePath)); success = false; } + return val; } } @@ -520,7 +528,7 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) if (leftIndentFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } leftIndentFound = true; @@ -535,7 +543,7 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) if (rightIndentFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } rightIndentFound = true; @@ -552,6 +560,7 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) this.ProcessDuplicateAlternateNode(n, XmlTags.FirstLineIndentNode, XmlTags.FirstLineHangingNode); return null; } + firstLineIndentFound = true; frame.frameInfoDefinition.firstLine = LoadPositiveOrZeroIntegerValue(n, out success); @@ -565,6 +574,7 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) this.ProcessDuplicateAlternateNode(n, XmlTags.FirstLineIndentNode, XmlTags.FirstLineHangingNode); return null; } + firstLineHangingFound = true; frame.frameInfoDefinition.firstLine = LoadPositiveOrZeroIntegerValue(n, out success); @@ -578,8 +588,9 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) if (itemNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } + itemNodeFound = true; frame.itemDefinition.formatTokenList = LoadComplexControlTokenListDefinitions(n); } @@ -594,12 +605,14 @@ private FrameToken LoadFrameDefinition(XmlNode frameNode, int index) this.ProcessDuplicateAlternateNode(XmlTags.FirstLineIndentNode, XmlTags.FirstLineHangingNode); return null; // fatal error } + if (frame.itemDefinition.formatTokenList == null) { - //MissingNode=Error at XPath {0} in file {1}: Missing Node {2}. + // MissingNode=Error at XPath {0} in file {1}: Missing Node {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.MissingNode, ComputeCurrentXPath(), FilePath, XmlTags.ComplexItemNode)); return null; } + return frame; } } @@ -612,6 +625,7 @@ private bool ReadBooleanNode(XmlNode collectionElement, out bool val) { return false; } + string s = collectionElement.InnerText; if (string.IsNullOrEmpty(s)) @@ -630,7 +644,7 @@ private bool ReadBooleanNode(XmlNode collectionElement, out bool val) val = true; return true; } - //Error at XPath {0} in file {1}: A Boolean value is expected. + // Error at XPath {0} in file {1}: A Boolean value is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectBoolean, ComputeCurrentXPath(), FilePath)); return false; diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_List.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_List.cs index 6c2bdf898c6..323c94454b2 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_List.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using System.Xml; using System.Management.Automation.Internal; +using System.Xml; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -52,6 +51,7 @@ private ListControlBody LoadListControl(XmlNode controlNode) this.ReportMissingNode(XmlTags.ListEntriesNode); return null; // fatal error } + return listBody; } } @@ -69,7 +69,7 @@ private void LoadListControlEntries(XmlNode listViewEntriesNode, ListControlBody ListControlEntryDefinition lved = LoadListControlEntryDefinition(n, entryIndex++); if (lved == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.ListEntryNode)); listBody.defaultEntryDefinition = null; return; // fatal error @@ -83,7 +83,7 @@ private void LoadListControlEntries(XmlNode listViewEntriesNode, ListControlBody } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.ListEntryNode)); listBody.defaultEntryDefinition = null; return; // fatal error @@ -99,9 +99,10 @@ private void LoadListControlEntries(XmlNode listViewEntriesNode, ListControlBody this.ProcessUnknownNode(n); } } + if (listBody.optionalEntryList == null) { - //Error at XPath {0} in file {1}: There must be at least one default {2}. + // Error at XPath {0} in file {1}: There must be at least one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.ListEntryNode)); } } @@ -123,7 +124,7 @@ private ListControlEntryDefinition LoadListControlEntryDefinition(XmlNode listVi if (appliesToNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } appliesToNodeFound = true; @@ -136,7 +137,7 @@ private ListControlEntryDefinition LoadListControlEntryDefinition(XmlNode listVi if (bodyNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } bodyNodeFound = true; @@ -150,7 +151,7 @@ private ListControlEntryDefinition LoadListControlEntryDefinition(XmlNode listVi if (lved.itemDefinitionList == null) { - //Error at XPath {0} in file {1}: Missing definition list. + // Error at XPath {0} in file {1}: Missing definition list. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoDefinitionList, ComputeCurrentXPath(), FilePath)); return null; } @@ -173,11 +174,12 @@ private void LoadListControlItemDefinitions(ListControlEntryDefinition lved, Xml ListControlItemDefinition lvid = LoadListControlItemDefinition(n); if (lvid == null) { - //Error at XPath {0} in file {1}: Invalid property entry. + // Error at XPath {0} in file {1}: Invalid property entry. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidPropertyEntry, ComputeCurrentXPath(), FilePath)); lved.itemDefinitionList = null; - return; //fatal + return; // fatal } + lved.itemDefinitionList.Add(lvid); } else @@ -189,13 +191,14 @@ private void LoadListControlItemDefinitions(ListControlEntryDefinition lved, Xml // we must have at least a definition in th elist if (lved.itemDefinitionList.Count == 0) { - //Error at XPath {0} in file {1}: At least one list view item must be specified. + // Error at XPath {0} in file {1}: At least one list view item must be specified. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoListViewItem, ComputeCurrentXPath(), FilePath)); lved.itemDefinitionList = null; - return; //fatal + return; // fatal } } } + private ListControlItemDefinition LoadListControlItemDefinition(XmlNode propertyEntryNode) { using (this.StackFrame(propertyEntryNode)) @@ -223,6 +226,7 @@ private ListControlItemDefinition LoadListControlItemDefinition(XmlNode property this.ProcessDuplicateNode(n); return null; // fatal error } + itemSelectionConditionNodeFound = true; condition = LoadItemSelectionCondition(n); if (condition == null) @@ -237,6 +241,7 @@ private ListControlItemDefinition LoadListControlItemDefinition(XmlNode property this.ProcessDuplicateNode(n); return null; // fatal error } + labelNodeFound = true; labelToken = LoadLabel(n); if (labelToken == null) @@ -259,7 +264,7 @@ private ListControlItemDefinition LoadListControlItemDefinition(XmlNode property // add condition lvid.conditionToken = condition; - // add either the text token or the MshExpression with optional format string + // add either the text token or the PSPropertyExpression with optional format string if (match.TextToken != null) { lvid.formatTokenList.Add(match.TextToken); @@ -271,6 +276,7 @@ private ListControlItemDefinition LoadListControlItemDefinition(XmlNode property fpt.fieldFormattingDirective.formatString = match.FormatString; lvid.formatTokenList.Add(fpt); } + return lvid; } } @@ -291,6 +297,7 @@ private ExpressionToken LoadItemSelectionCondition(XmlNode itemNode) this.ProcessDuplicateNode(n); return null; // fatal error } + expressionNodeFound = true; if (!expressionMatch.ProcessNode(n)) return null; diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Table.cs index de895ffd890..acc60a3e3e5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Table.cs @@ -1,17 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Xml; using System.Management.Automation.Internal; +using System.Xml; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -38,7 +37,7 @@ private ControlBase LoadTableControl(XmlNode controlNode) hideHeadersNodeFound = true; if (!this.ReadBooleanNode(n, out tableBody.header.hideHeader)) { - return null; //fatal error + return null; // fatal error } } else if (MatchNodeName(n, XmlTags.AutoSizeNode)) @@ -48,12 +47,14 @@ private ControlBase LoadTableControl(XmlNode controlNode) this.ProcessDuplicateNode(n); return null; // fatal error } + autosizeNodeFound = true; bool tempVal; if (!this.ReadBooleanNode(n, out tempVal)) { return null; // fatal error } + tableBody.autosize = tempVal; } else if (MatchNodeName(n, XmlTags.TableHeadersNode)) @@ -111,7 +112,7 @@ private ControlBase LoadTableControl(XmlNode controlNode) if (tableBody.header.columnHeaderDefinitionList.Count != tableBody.defaultDefinition.rowItemDefinitionList.Count) { - //Error at XPath {0} in file {1}: Header item count = {2} does not match default row item count = {3}. + // Error at XPath {0} in file {1}: Header item count = {2} does not match default row item count = {3}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.IncorrectHeaderItemCount, ComputeCurrentXPath(), FilePath, tableBody.header.columnHeaderDefinitionList.Count, tableBody.defaultDefinition.rowItemDefinitionList.Count)); @@ -129,13 +130,14 @@ private ControlBase LoadTableControl(XmlNode controlNode) if (trd.rowItemDefinitionList.Count != tableBody.defaultDefinition.rowItemDefinitionList.Count) { - //Error at XPath {0} in file {1}: Row item count = {2} on alternative set #{3} does not match default row item count = {4}. + // Error at XPath {0} in file {1}: Row item count = {2} on alternative set #{3} does not match default row item count = {4}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.IncorrectRowItemCount, ComputeCurrentXPath(), FilePath, trd.rowItemDefinitionList.Count, tableBody.defaultDefinition.rowItemDefinitionList.Count, k + 1)); return null; // fatal error } + k++; } } @@ -159,7 +161,7 @@ private void LoadHeadersSection(TableControlBody tableBody, XmlNode headersNode) tableBody.header.columnHeaderDefinitionList.Add(chd); else { - //Error at XPath {0} in file {1}: Column header definition is invalid; all headers are discarded. + // Error at XPath {0} in file {1}: Column header definition is invalid; all headers are discarded. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidColumnHeader, ComputeCurrentXPath(), FilePath)); tableBody.header.columnHeaderDefinitionList = null; return; // fatal error @@ -217,9 +219,9 @@ private TableColumnHeaderDefinition LoadColumnHeaderDefinition(XmlNode columnHea } else { - //Error at XPath {0} in file {1}: Invalid {2} value. + // Error at XPath {0} in file {1}: Invalid {2} value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNodeValue, ComputeCurrentXPath(), FilePath, XmlTags.WidthNode)); - return null; //fatal error + return null; // fatal error } } else if (MatchNodeName(n, XmlTags.AlignmentNode)) @@ -240,9 +242,10 @@ private TableColumnHeaderDefinition LoadColumnHeaderDefinition(XmlNode columnHea { this.ProcessUnknownNode(n); } - } // foreach + } + return chd; - } // using + } } private bool ReadPositiveIntegerValue(XmlNode n, out int val) @@ -254,10 +257,11 @@ private bool ReadPositiveIntegerValue(XmlNode n, out int val) bool isInteger = int.TryParse(text, out val); if (!isInteger || val <= 0) { - //Error at XPath {0} in file {1}: A positive integer is expected. + // Error at XPath {0} in file {1}: A positive integer is expected. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ExpectPositiveInteger, ComputeCurrentXPath(), FilePath)); return false; } + return true; } @@ -284,10 +288,11 @@ private bool LoadAlignmentValue(XmlNode n, out int alignmentValue) } else { - //Error at XPath {0} in file {1}: "{2}" is not an valid alignment value. + // Error at XPath {0} in file {1}: "{2}" is not an valid alignment value. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidAlignmentValue, ComputeCurrentXPath(), FilePath, alignmentString)); return false; // fatal error } + return true; } @@ -303,7 +308,7 @@ private void LoadRowEntriesSection(TableControlBody tableBody, XmlNode rowEntrie TableRowDefinition trd = LoadRowEntryDefinition(n, rowEntryIndex++); if (trd == null) { - //Error at XPath {0} in file {1}: {2} failed to load. + // Error at XPath {0} in file {1}: {2} failed to load. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.LoadTagFailed, ComputeCurrentXPath(), FilePath, XmlTags.TableRowEntryNode)); tableBody.defaultDefinition = null; return; // fatal error @@ -318,7 +323,7 @@ private void LoadRowEntriesSection(TableControlBody tableBody, XmlNode rowEntrie } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.TableRowEntryNode)); tableBody.defaultDefinition = null; return; // fatal error @@ -334,9 +339,10 @@ private void LoadRowEntriesSection(TableControlBody tableBody, XmlNode rowEntrie this.ProcessUnknownNode(n); } } + if (tableBody.defaultDefinition == null) { - //Error at XPath {0} in file {1}: There must be at least one default {2}. + // Error at XPath {0} in file {1}: There must be at least one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.TableRowEntryNode)); } } @@ -371,8 +377,9 @@ private TableRowDefinition LoadRowEntryDefinition(XmlNode rowEntryNode, int inde if (columnEntriesNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } + LoadColumnEntries(n, trd); if (trd.rowItemDefinitionList == null) { @@ -384,12 +391,13 @@ private TableRowDefinition LoadRowEntryDefinition(XmlNode rowEntryNode, int inde if (multiLineFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } + multiLineFound = true; if (!this.ReadBooleanNode(n, out trd.multiLine)) { - return null; //fatal error + return null; // fatal error } } else @@ -397,6 +405,7 @@ private TableRowDefinition LoadRowEntryDefinition(XmlNode rowEntryNode, int inde this.ProcessUnknownNode(n); } } + return trd; } } @@ -470,7 +479,7 @@ private TableRowItemDefinition LoadColumnEntry(XmlNode columnEntryNode, int inde } // finally build the item to return - // add either the text token or the MshExpression with optional format string + // add either the text token or the PSPropertyExpression with optional format string if (match.TextToken != null) { rid.formatTokenList.Add(match.TextToken); @@ -484,7 +493,7 @@ private TableRowItemDefinition LoadColumnEntry(XmlNode columnEntryNode, int inde } return rid; - } // using + } } } } 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 4e1052281cb..c6d191284e6 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader_Views.cs @@ -1,18 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using System.Management.Automation; -using System.Xml; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Internal; +using System.Xml; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -28,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); } @@ -44,8 +45,6 @@ private void LoadViewDefinitions(TypeInfoDataBase db, XmlNode viewDefinitionsNod } } - - private ViewDefinition LoadView(XmlNode viewNode, int index) { using (this.StackFrame(viewNode, index)) @@ -57,7 +56,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) if (!success) { - //Error at XPath {0} in file {1}: View cannot be loaded. + // Error at XPath {0} in file {1}: View cannot be loaded. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.ViewNotLoaded, ComputeCurrentXPath(), FilePath)); return null; // fatal error } @@ -86,6 +85,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) ProcessDuplicateNode(n); return null; } + mainControlFound = true; view.mainControl = LoadTableControl(n); } @@ -96,10 +96,10 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) ProcessDuplicateNode(n); return null; } + mainControlFound = true; view.mainControl = LoadListControl(n); } - else if (MatchNodeName(n, XmlTags.WideControlNode)) { if (mainControlFound) @@ -107,6 +107,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) ProcessDuplicateNode(n); return null; } + mainControlFound = true; view.mainControl = LoadWideControl(n); } @@ -117,6 +118,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) ProcessDuplicateNode(n); return null; } + mainControlFound = true; view.mainControl = LoadComplexControl(n); } @@ -124,7 +126,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) { secondPassUnprocessedNodes.Add(n); } - } // foreach + } if (view.mainControl == null) { @@ -140,7 +142,7 @@ private ViewDefinition LoadView(XmlNode viewNode, int index) if (view.outOfBand && (view.groupBy != null)) { // we cannot have grouping and out of band at the same time - //Error at XPath {0} in file {1}: An Out Of Band view cannot have GroupBy. + // Error at XPath {0} in file {1}: An Out Of Band view cannot have GroupBy. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.OutOfBandGroupByConflict, ComputeCurrentXPath(), FilePath)); return null; // fatal } @@ -169,9 +171,10 @@ 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. + // Error at XPath {0} in file {1}: Out Of Band views can only have CustomControl or ListControl. ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidControlForOutOfBandView, ComputeCurrentXPath(), FilePath)); return false; } @@ -192,16 +195,17 @@ private bool LoadMainControlDependentData(List unprocessedNodes, ViewDe ProcessUnknownNode(n); } } + return true; } private bool LoadCommonViewData(XmlNode viewNode, ViewDefinition view, List unprocessedNodes) { if (viewNode == null) - throw PSTraceSource.NewArgumentNullException("viewNode"); + throw PSTraceSource.NewArgumentNullException(nameof(viewNode)); if (view == null) - throw PSTraceSource.NewArgumentNullException("view"); + throw PSTraceSource.NewArgumentNullException(nameof(view)); // set loading information view.loadingInfo = this.LoadingInfo; @@ -266,7 +270,7 @@ private bool LoadCommonViewData(XmlNode viewNode, ViewDefinition view, List - /// class to load the XML document into data structures. - /// It encapsulates the file format specific code + /// Class to load the XML document into data structures. + /// It encapsulates the file format specific code. /// internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase { @@ -35,12 +34,14 @@ private WideControlBody LoadWideControl(XmlNode controlNode) this.ProcessDuplicateAlternateNode(n, XmlTags.AutoSizeNode, XmlTags.ColumnNumberNode); return null; // fatal error } + autosizeNodeFound = true; bool tempVal; if (!this.ReadBooleanNode(n, out tempVal)) { return null; // fatal error } + wideBody.autosize = tempVal; } else if (MatchNodeName(n, XmlTags.ColumnNumberNode)) @@ -50,6 +51,7 @@ private WideControlBody LoadWideControl(XmlNode controlNode) this.ProcessDuplicateAlternateNode(n, XmlTags.AutoSizeNode, XmlTags.ColumnNumberNode); return null; // fatal error } + columnsNodeFound = true; if (!ReadPositiveIntegerValue(n, out wideBody.columns)) @@ -86,11 +88,13 @@ private WideControlBody LoadWideControl(XmlNode controlNode) this.ProcessDuplicateAlternateNode(XmlTags.AutoSizeNode, XmlTags.ColumnNumberNode); return null; // fatal error } + if (!wideViewEntriesFound) { this.ReportMissingNode(XmlTags.WideEntriesNode); return null; // fatal error } + return wideBody; } } @@ -108,7 +112,7 @@ private void LoadWideControlEntries(XmlNode wideControlEntriesNode, WideControlB WideControlEntryDefinition wved = LoadWideControlEntry(n, entryIndex++); if (wved == null) { - //Error at XPath {0} in file {1}: Invalid {2}. + // Error at XPath {0} in file {1}: Invalid {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNode, ComputeCurrentXPath(), FilePath, XmlTags.WideEntryNode)); return; } @@ -121,7 +125,7 @@ private void LoadWideControlEntries(XmlNode wideControlEntriesNode, WideControlB } else { - //Error at XPath {0} in file {1}: There cannot be more than one default {2}. + // Error at XPath {0} in file {1}: There cannot be more than one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.TooManyDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.WideEntryNode)); wideBody.defaultEntryDefinition = null; return; // fatal error @@ -137,9 +141,10 @@ private void LoadWideControlEntries(XmlNode wideControlEntriesNode, WideControlB this.ProcessUnknownNode(n); } } + if (wideBody.defaultEntryDefinition == null) { - //Error at XPath {0} in file {1}: There must be at least one default {2}. + // Error at XPath {0} in file {1}: There must be at least one default {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.NoDefaultShapeEntry, ComputeCurrentXPath(), FilePath, XmlTags.WideEntryNode)); } } @@ -161,7 +166,7 @@ private WideControlEntryDefinition LoadWideControlEntry(XmlNode wideControlEntry if (appliesToNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } appliesToNodeFound = true; @@ -172,16 +177,16 @@ private WideControlEntryDefinition LoadWideControlEntry(XmlNode wideControlEntry if (propertyEntryNodeFound) { this.ProcessDuplicateNode(n); - return null; //fatal + return null; // fatal } propertyEntryNodeFound = true; wved.formatTokenList = LoadPropertyEntry(n); if (wved.formatTokenList == null) { - //Error at XPath {0} in file {1}: Invalid {2}. + // Error at XPath {0} in file {1}: Invalid {2}. this.ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidNode, ComputeCurrentXPath(), FilePath, XmlTags.WideItemNode)); - return null; //fatal + return null; // fatal } } else @@ -192,10 +197,11 @@ private WideControlEntryDefinition LoadWideControlEntry(XmlNode wideControlEntry if (wved.formatTokenList.Count == 0) { - //Error at XPath {0} in file {1}: Missing WideItem. + // Error at XPath {0} in file {1}: Missing WideItem. this.ReportMissingNode(XmlTags.WideItemNode); - return null; //fatal error + return null; // fatal error } + return wved; } } @@ -222,7 +228,7 @@ private List LoadPropertyEntry(XmlNode propertyEntryNode) // finally build the item to return List formatTokenList = new List(); - // add either the text token or the MshExpression with optional format string + // add either the text token or the PSPropertyExpression with optional format string if (match.TextToken != null) { formatTokenList.Add(match.TextToken); @@ -234,6 +240,7 @@ private List LoadPropertyEntry(XmlNode propertyEntryNode) fpt.fieldFormattingDirective.formatString = match.FormatString; formatTokenList.Add(fpt); } + return formatTokenList; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs index 23081707058..a4e2a74f622 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs @@ -1,27 +1,26 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; +using System.Globalization; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Globalization; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// internal class to manage the grouping algorithm for the - /// format-xxx commands + /// Internal class to manage the grouping algorithm for the + /// format-xxx commands. /// internal sealed class GroupingInfoManager { /// - /// Initialize with the grouping property data + /// Initialize with the grouping property data. /// - /// name of the grouping property - /// display name of the property - internal void Initialize(MshExpression groupingExpression, string displayLabel) + /// Name of the grouping property. + /// Display name of the property. + internal void Initialize(PSPropertyExpression groupingExpression, string displayLabel) { _groupingKeyExpression = groupingExpression; _label = displayLabel; @@ -43,16 +42,16 @@ internal string GroupingKeyDisplayName } /// - /// compute the string value of the grouping property + /// Compute the string value of the grouping property. /// - /// object to use to compute the property value - /// true if there was an update + /// Object to use to compute the property value. + /// True if there was an update. internal bool UpdateGroupingKeyValue(PSObject so) { if (_groupingKeyExpression == null) return false; - List results = _groupingKeyExpression.GetValues(so); + List results = _groupingKeyExpression.GetValues(so); // if we have more that one match, we have to select the first one if (results.Count > 0 && results[0].Exception == null) @@ -71,6 +70,7 @@ internal bool UpdateGroupingKeyValue(PSObject so) { _groupingKeyDisplayName = results[0].ResolvedExpression.ToString(); } + return update; } @@ -84,46 +84,40 @@ internal bool UpdateGroupingKeyValue(PSObject so) private static bool IsEqual(object first, object second) { - try - { - return LanguagePrimitives.Compare(first, second, true, CultureInfo.CurrentCulture) == 0; - } - catch (InvalidCastException) + if (LanguagePrimitives.TryCompare(first, second, true, CultureInfo.CurrentCulture, out int result)) { - } - catch (ArgumentException) - { - // Note that this will occur if the objects do not support - // IComparable. We fall back to comparing as strings. + return result == 0; } + // 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 // or an Exception was raised win Compare string firstString = PSObject.AsPSObject(first).ToString(); string secondString = PSObject.AsPSObject(second).ToString(); - return string.Compare(firstString, secondString, StringComparison.CurrentCultureIgnoreCase) == 0; + return string.Equals(firstString, secondString, StringComparison.CurrentCultureIgnoreCase); } /// - /// value of the display label passed in. + /// Value of the display label passed in. /// private string _label = null; /// - /// value of the current active grouping key + /// Value of the current active grouping key. /// private string _groupingKeyDisplayName = null; /// - /// name of the current grouping key + /// Name of the current grouping key. /// - private MshExpression _groupingKeyExpression = null; + private PSPropertyExpression _groupingKeyExpression = null; /// - /// the current value of the grouping key + /// The current value of the grouping key. /// 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 24f9badc412..1018936d2c1 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -20,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 @@ -34,40 +38,37 @@ internal class FormatMessagesContextManager internal GroupEndCallback ge = null; internal PayloadCallback payload = null; - /// /// The current output context, as determined by the - /// sequence of formatting messages in the object stream + /// sequence of formatting messages in the object stream. /// internal abstract class OutputContext { /// - /// /// - /// parent context in the stack, it can be null + /// Parent context in the stack, it can be null. internal OutputContext(OutputContext parentContextInStack) { ParentContext = parentContextInStack; } /// - /// the outer context: the context object pushed onto the + /// The outer context: the context object pushed onto the /// stack before the current one. For the first object pushed onto - /// the stack it will be null + /// the stack it will be null. /// internal OutputContext ParentContext { get; } } /// - /// process an object from an input stream. It manages the context stack and - /// calls back on the specified event delegates + /// Process an object from an input stream. It manages the context stack and + /// calls back on the specified event delegates. /// - /// object to process + /// Object to process. 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; @@ -98,7 +99,7 @@ internal void Process(object o) } else if (formatDataIsGroupStartData) { - //GroupStartData gsd = (GroupStartData) formatData; + // GroupStartData gsd = (GroupStartData) formatData; // notify for Gs this.gs(oc); } @@ -120,15 +121,15 @@ internal void Process(object o) // notify for Fe, passing the Fe info, before a Pop() this.ge(ged, oc); } + _stack.Pop(); } } } } - /// - /// access the active context (top of the stack). It can be null. + /// Access the active context (top of the stack). It can be null. /// internal OutputContext ActiveOutputContext { @@ -136,8 +137,8 @@ internal OutputContext ActiveOutputContext } /// - /// internal stack to manage context + /// 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 18841851b48..963b5a0f88b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -1,9 +1,8 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// 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; @@ -11,12 +10,12 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// base class for the various types of formatting shapes + /// Base class for the various types of formatting shapes. /// internal abstract class ViewGenerator { internal virtual void Initialize(TerminatingErrorContext terminatingErrorContext, - MshExpressionFactory mshExpressionFactory, + PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) @@ -37,7 +36,7 @@ internal virtual void Initialize(TerminatingErrorContext terminatingErrorContext } internal virtual void Initialize(TerminatingErrorContext terminatingErrorContext, - MshExpressionFactory mshExpressionFactory, + PSPropertyExpressionFactory mshExpressionFactory, PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters formatParameters) @@ -65,6 +64,7 @@ private void InitializeHelper() InitializeFormatErrorManager(); InitializeGroupBy(); InitializeAutoSize(); + InitializeRepeatHeader(); } private void InitializeFormatErrorManager() @@ -78,6 +78,7 @@ private void InitializeFormatErrorManager() { formatErrorPolicy.ShowErrorsAsMessages = this.dataBaseInfo.db.defaultSettingsSection.formatErrorPolicy.ShowErrorsAsMessages; } + if (parameters != null && parameters.showErrorsInFormattedOutput.HasValue) { formatErrorPolicy.ShowErrorsInFormattedOutput = parameters.showErrorsInFormattedOutput.Value; @@ -96,7 +97,7 @@ private void InitializeGroupBy() if (parameters != null && parameters.groupByParameter != null) { // get the expression to use - MshExpression groupingKeyExpression = parameters.groupByParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; + PSPropertyExpression groupingKeyExpression = parameters.groupByParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as PSPropertyExpression; // set the label string label = null; @@ -105,6 +106,7 @@ private void InitializeGroupBy() { label = labelKey as string; } + _groupingManager = new GroupingInfoManager(); _groupingManager.Initialize(groupingKeyExpression, label); return; @@ -118,12 +120,13 @@ private void InitializeGroupBy() { return; } + if (gb.startGroup == null || gb.startGroup.expression == null) { return; } - MshExpression ex = this.expressionFactory.CreateFromExpressionToken(gb.startGroup.expression, this.dataBaseInfo.view.loadingInfo); + PSPropertyExpression ex = this.expressionFactory.CreateFromExpressionToken(gb.startGroup.expression, this.dataBaseInfo.view.loadingInfo); _groupingManager = new GroupingInfoManager(); _groupingManager.Initialize(ex, null); @@ -139,13 +142,18 @@ 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; + } + } + + private void InitializeRepeatHeader() + { + if (parameters != null) + { + _repeatHeader = parameters.repeatHeader; } } @@ -157,6 +165,7 @@ internal virtual FormatStartData GenerateStartData(PSObject so) { startFormat.autosizeInfo = new AutosizeInfo(); } + return startFormat; } @@ -208,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) @@ -217,7 +226,6 @@ internal GroupStartData GenerateGroupStartData(PSObject firstObjectInGroup, int } } - FormatEntry fe = new FormatEntry(); startGroup.groupingEntry.formatValueList.Add(fe); @@ -257,14 +265,15 @@ internal GroupStartData GenerateGroupStartData(PSObject firstObjectInGroup, int controlGenerator.GenerateFormatEntries(maxTreeDepth, control, firstObjectInGroup, startGroup.groupingEntry.formatValueList); } + return startGroup; } /// - /// update the current value of the grouping key + /// Update the current value of the grouping key. /// - /// object to use for the update - /// true if the value of the key changed + /// Object to use for the update. + /// True if the value of the key changed. internal bool UpdateGroupingKeyValue(PSObject so) { if (_groupingManager == null) @@ -272,7 +281,6 @@ internal bool UpdateGroupingKeyValue(PSObject so) return _groupingManager.UpdateGroupingKeyValue(so); } - internal GroupEndData GenerateGroupEndData() { return new GroupEndData(); @@ -297,26 +305,34 @@ 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 (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { result = IsObjectApplicable(typesWithoutPrefix); } } + return result; } - private GroupingInfoManager _groupingManager = null; protected bool AutoSize { get { return _autosize; } } + private bool _autosize = false; + protected bool RepeatHeader + { + get { return _repeatHeader; } + } + + private bool _repeatHeader = false; + protected class DataBaseInfo { internal TypeInfoDataBase db = null; @@ -328,22 +344,64 @@ protected class DataBaseInfo protected FormattingCommandLineParameters parameters; - protected MshExpressionFactory expressionFactory; + protected PSPropertyExpressionFactory expressionFactory; 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; + } - protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, MshExpression ex, + return associationList + .Where(item => !excludeFilter.IsMatch(item.ResolvedExpression)) + .ToList(); + } + + protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, FieldFormattingDirective directive) { - MshExpressionResult resolvedExpression; + PSPropertyExpressionResult resolvedExpression; return GetExpressionDisplayValue(so, enumerationLimit, ex, directive, out resolvedExpression); } - protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, MshExpression ex, - FieldFormattingDirective directive, out MshExpressionResult expressionResult) + protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, + FieldFormattingDirective directive, out PSPropertyExpressionResult expressionResult) { StringFormatError formatErrorObject = null; if (_errorManager.DisplayFormatErrorString) @@ -361,7 +419,7 @@ protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, Ms // we obtained a result, check if there is an error if (expressionResult.Exception != null) { - _errorManager.LogMshExpressionFailedResult(expressionResult, so); + _errorManager.LogPSPropertyExpressionFailedResult(expressionResult, so); if (_errorManager.DisplayErrorStrings) { retVal = _errorManager.ErrorString; @@ -369,7 +427,7 @@ protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, Ms } 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) @@ -378,6 +436,7 @@ protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, Ms } } } + return retVal; } @@ -386,18 +445,18 @@ protected bool EvaluateDisplayCondition(PSObject so, ExpressionToken conditionTo if (conditionToken == null) return true; - MshExpression ex = this.expressionFactory.CreateFromExpressionToken(conditionToken, this.dataBaseInfo.view.loadingInfo); - MshExpressionResult expressionResult; + PSPropertyExpression ex = this.expressionFactory.CreateFromExpressionToken(conditionToken, this.dataBaseInfo.view.loadingInfo); + PSPropertyExpressionResult expressionResult; bool retVal = DisplayCondition.Evaluate(so, ex, out expressionResult); if (expressionResult != null && expressionResult.Exception != null) { - _errorManager.LogMshExpressionFailedResult(expressionResult, so); + _errorManager.LogPSPropertyExpressionFailedResult(expressionResult, so); } + return retVal; } - internal FormatErrorManager ErrorManager { get { return _errorManager; } @@ -405,39 +464,36 @@ internal FormatErrorManager ErrorManager private FormatErrorManager _errorManager; - #region helpers protected FormatPropertyField GenerateFormatPropertyField(List formatTokenList, PSObject so, int enumerationLimit) { - MshExpressionResult result; + PSPropertyExpressionResult result; return GenerateFormatPropertyField(formatTokenList, so, enumerationLimit, out result); } - protected FormatPropertyField GenerateFormatPropertyField(List formatTokenList, PSObject so, int enumerationLimit, out MshExpressionResult result) + protected FormatPropertyField GenerateFormatPropertyField(List formatTokenList, PSObject so, int enumerationLimit, out PSPropertyExpressionResult result) { result = null; FormatPropertyField fpf = new FormatPropertyField(); if (formatTokenList.Count != 0) { FormatToken token = formatTokenList[0]; - FieldPropertyToken fpt = token as FieldPropertyToken; - if (fpt != null) + if (token is FieldPropertyToken fpt) { - MshExpression ex = this.expressionFactory.CreateFromExpressionToken(fpt.expression, this.dataBaseInfo.view.loadingInfo); + 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 { - fpf.propertyValue = ""; + fpf.propertyValue = string.Empty; } + return fpf; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs index e657ea86099..b81c0c0f860 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -13,11 +12,10 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { internal sealed class ComplexViewGenerator : ViewGenerator { - internal override void Initialize(TerminatingErrorContext errorContext, MshExpressionFactory expressionFactory, + internal override void Initialize(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; } internal override FormatStartData GenerateStartData(PSObject so) @@ -41,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) @@ -67,14 +65,14 @@ private ComplexViewEntry GenerateComplexViewEntryFromDataBaseInfo(PSObject so, i } /// - /// class to process a complex control directive and generate - /// the corresponding formatting tokens + /// Class to process a complex control directive and generate + /// the corresponding formatting tokens. /// internal sealed class ComplexControlGenerator { internal ComplexControlGenerator(TypeInfoDataBase dataBase, DatabaseLoadingInfo loadingInfo, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, List controlDefinitionList, FormatErrorManager resultErrorManager, int enumerationLimit, @@ -94,7 +92,7 @@ internal void GenerateFormatEntries(int maxTreeDepth, ControlBase control, { if (control == null) { - throw PSTraceSource.NewArgumentNullException("control"); + throw PSTraceSource.NewArgumentNullException(nameof(control)); } ExecuteFormatControl(new TraversalInfo(0, maxTreeDepth), control, @@ -108,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( @@ -130,6 +127,7 @@ private bool ExecuteFormatControl(TraversalInfo level, ControlBase control, ExecuteFormatControlBody(level, so, complexBody, formatValueList); return true; } + return false; } @@ -150,11 +148,12 @@ private ComplexControlEntryDefinition GetActiveComplexControlEntryDefinition(Com TypeMatch match = new TypeMatch(_expressionFactory, _db, typeNames); foreach (ComplexControlEntryDefinition x in complexBody.optionalEntryList) { - if (match.PerfectMatch(new TypeMatchItem(x, x.appliesTo))) + if (match.PerfectMatch(new TypeMatchItem(x, x.appliesTo, so))) { return x; } } + if (match.BestMatch != null) { return match.BestMatch as ComplexControlEntryDefinition; @@ -162,7 +161,7 @@ private ComplexControlEntryDefinition GetActiveComplexControlEntryDefinition(Com else { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { match = new TypeMatch(_expressionFactory, _db, typesWithoutPrefix); foreach (ComplexControlEntryDefinition x in complexBody.optionalEntryList) @@ -172,6 +171,7 @@ private ComplexControlEntryDefinition GetActiveComplexControlEntryDefinition(Com return x; } } + if (match.BestMatch != null) { return match.BestMatch as ComplexControlEntryDefinition; @@ -188,7 +188,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, { if (so == null) { - throw PSTraceSource.NewArgumentNullException("so"); + throw PSTraceSource.NewArgumentNullException(nameof(so)); } // guard against infinite loop @@ -203,25 +203,25 @@ 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); fe.formatValueList.Add(ftf); continue; } - var newline = t as NewLineToken; - if (newline != null) + + if (t is NewLineToken newline) { for (int i = 0; i < newline.count; i++) { fe.formatValueList.Add(new FormatNewLine()); } + 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(); @@ -240,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)) { @@ -260,29 +259,26 @@ private void ExecuteFormatTokenList(TraversalInfo level, } else { - MshExpression ex = _expressionFactory.CreateFromExpressionToken(cpt.expression, _loadingInfo); - List resultList = ex.GetValues(so); + PSPropertyExpression ex = _expressionFactory.CreateFromExpressionToken(cpt.expression, _loadingInfo); + List resultList = ex.GetValues(so); if (resultList.Count > 0) { val = resultList[0].Result; if (resultList[0].Exception != null) { - _errorManager.LogMshExpressionFailedResult(resultList[0], so); + _errorManager.LogPSPropertyExpressionFailedResult(resultList[0], so); } } } - // if the token is has a formatting string, it's a leaf node, // do the formatting and we will be done if (cpt.control == null || cpt.control is FieldControlBody) { // Since it is a leaf node we just consider it an empty string and go // on with formatting - if (val == null) - { - val = ""; - } + val ??= string.Empty; + FieldFormattingDirective fieldFormattingDirective = null; StringFormatError formatErrorObject = null; if (cpt.control != null) @@ -305,6 +301,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, // nothing to process continue; } + fpf = new FormatPropertyField(); fpf.propertyValue = PSObjectHelper.FormatField(fieldFormattingDirective, x, _enumerationLimit, formatErrorObject, _expressionFactory); @@ -318,6 +315,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, fpf.propertyValue = PSObjectHelper.FormatField(fieldFormattingDirective, val, _enumerationLimit, formatErrorObject, _expressionFactory); fe.formatValueList.Add(fpf); } + if (formatErrorObject != null && formatErrorObject.exception != null) { _errorManager.LogStringFormatError(formatErrorObject); @@ -331,6 +329,7 @@ private void ExecuteFormatTokenList(TraversalInfo level, { continue; } + IEnumerable e = PSObjectHelper.GetEnumerable(val); if (cpt.enumerateCollection && e != null) { @@ -363,24 +362,25 @@ private bool EvaluateDisplayCondition(PSObject so, ExpressionToken conditionToke if (conditionToken == null) return true; - MshExpression ex = _expressionFactory.CreateFromExpressionToken(conditionToken, _loadingInfo); - MshExpressionResult expressionResult; + PSPropertyExpression ex = _expressionFactory.CreateFromExpressionToken(conditionToken, _loadingInfo); + PSPropertyExpressionResult expressionResult; bool retVal = DisplayCondition.Evaluate(so, ex, out expressionResult); if (expressionResult != null && expressionResult.Exception != null) { - _errorManager.LogMshExpressionFailedResult(expressionResult, so); + _errorManager.LogPSPropertyExpressionFailedResult(expressionResult, so); } + return retVal; } - private TypeInfoDataBase _db; - private DatabaseLoadingInfo _loadingInfo; - private MshExpressionFactory _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 @@ -392,6 +392,7 @@ internal TraversalInfo(int level, int maxDepth) } internal int Level { get { return _level; } } + internal int MaxDepth { get { return _maxDepth; } } internal TraversalInfo NextLevel @@ -402,16 +403,16 @@ internal TraversalInfo NextLevel } } - private int _level; - private int _maxDepth; + private readonly int _level; + private readonly int _maxDepth; } /// - /// class to generate a complex view from properties + /// Class to generate a complex view from properties. /// internal sealed class ComplexViewObjectBrowser { - internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, MshExpressionFactory mshExpressionFactory, int enumerationLimit) + internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, PSPropertyExpressionFactory mshExpressionFactory, int enumerationLimit) { _errorManager = resultErrorManager; _expressionFactory = mshExpressionFactory; @@ -419,21 +420,22 @@ internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, MshExpr } /// - /// given an object, generate a tree-like view - /// of the object + /// Given an object, generate a tree-like view + /// of the object. /// - /// object to process - /// parameters from the command line - /// complex view entry to send to the output command - internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters inputParameters) + /// Object to process. + /// Parameters from the command line. + /// Complex view entry to send to the output command. + 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(); @@ -484,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) @@ -493,18 +495,17 @@ private void DisplayRawObject(PSObject so, List formatValueList) } } - formatValueList.Add(fpf); formatValueList.Add(new FormatNewLine()); } /// - /// recursive call to display an object + /// Recursive call to display an object. /// - /// object to display - /// current level in the traversal - /// list of parameters from the command line - /// list of format tokens to add to + /// Object to display. + /// Current level in the traversal. + /// List of parameters from the command line. + /// List of format tokens to add to. private void DisplayObject(PSObject so, TraversalInfo currentLevel, List parameterList, List formatValueList) { @@ -512,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); @@ -539,21 +543,21 @@ private void ProcessActiveAssociationList(PSObject so, formatValueList.Add(ftf); // compute the value of the entry - List resList = a.ResolvedExpression.GetValues(so); + List resList = a.ResolvedExpression.GetValues(so); object val = null; if (resList.Count >= 1) { - MshExpressionResult result = resList[0]; + PSPropertyExpressionResult result = resList[0]; if (result.Exception != null) { - _errorManager.LogMshExpressionFailedResult(result, so); + _errorManager.LogPSPropertyExpressionFailedResult(result, so); if (_errorManager.DisplayErrorStrings) { val = _errorManager.ErrorString; } else { - val = ""; + val = string.Empty; } } else @@ -562,7 +566,6 @@ private void ProcessActiveAssociationList(PSObject so, } } - // extract the optional max depth TraversalInfo level = currentLevel; if (a.OriginatingParameter != null) @@ -596,15 +599,15 @@ private void ProcessActiveAssociationList(PSObject so, DisplayObject(PSObject.AsPSObject(val), level.NextLevel, null, AddIndentationLevel(formatValueList)); } - } // for each + } } /// - /// recursive call to display an object + /// Recursive call to display an object. /// - /// enumeration to display - /// current level in the traversal - /// list of format tokens to add to + /// Enumeration to display. + /// Current level in the traversal. + /// List of format tokens to add to. private void DisplayEnumeration(IEnumerable e, TraversalInfo level, List formatValueList) { AddPrologue(formatValueList, "[", null); @@ -623,15 +626,18 @@ private void DisplayEnumerationInner(IEnumerable e, TraversalInfo level, List= 0) { if (_enumerationLimit == enumCount) { - DisplayLeaf(PSObjectHelper.ellipses, formatValueList); + DisplayLeaf(PSObjectHelper.Ellipsis, formatValueList); break; } + enumCount++; } + if (TreatAsLeafNode(x, level)) { DisplayLeaf(x, formatValueList); @@ -655,10 +661,10 @@ private void DisplayEnumerationInner(IEnumerable e, TraversalInfo level, List - /// display a leaf value + /// Display a leaf value. /// - /// object to display - /// list of format tokens to add to + /// Object to display. + /// List of format tokens to add to. private void DisplayLeaf(object val, List formatValueList) { FormatPropertyField fpf = new FormatPropertyField(); @@ -669,10 +675,10 @@ private void DisplayLeaf(object val, List formatValueList) } /// - /// determine if we have to stop the expansion + /// Determine if we have to stop the expansion. /// - /// object to verify - /// current level of recursion + /// Object to verify. + /// Current level of recursion. /// private static bool TreatAsLeafNode(object val, TraversalInfo level) { @@ -683,10 +689,10 @@ private static bool TreatAsLeafNode(object val, TraversalInfo level) } /// - /// treat as scalar check + /// Treat as scalar check. /// - /// name of the type to check - /// true if it has to be treated as a scalar + /// Name of the type to check. + /// True if it has to be treated as a scalar. private static bool TreatAsScalarType(Collection typeNames) { return DefaultScalarTypes.IsTypeInList(typeNames); @@ -706,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]; } @@ -755,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 + /// 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 MshExpressionFactory _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 f90d04d1dc2..35287d7c2e4 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -14,26 +13,31 @@ internal sealed class ListViewGenerator : ViewGenerator // tableBody to use for this instance of the ViewGenerator; private ListControlBody _listBody; - internal override void Initialize(TerminatingErrorContext terminatingErrorContext, MshExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) + internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) { base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters); - if ((null != this.dataBaseInfo) && (null != this.dataBaseInfo.view)) + if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null)) { _listBody = (ListControlBody)this.dataBaseInfo.view.mainControl; } } - internal override void Initialize(TerminatingErrorContext errorContext, MshExpressionFactory expressionFactory, + internal override void Initialize(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - if ((null != this.dataBaseInfo) && (null != this.dataBaseInfo.view)) + if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null)) { _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); } /// @@ -43,10 +47,10 @@ internal override void Initialize(TerminatingErrorContext errorContext, MshExpre /// internal override void PrepareForRemoteObjects(PSObject so) { - Diagnostics.Assert(null != so, "so cannot be null"); + Diagnostics.Assert(so != null, "so cannot be null"); // make sure computername property exists. - Diagnostics.Assert(null != so.Properties[RemotingConstants.ComputerNameNoteProperty], + Diagnostics.Assert(so.Properties[RemotingConstants.ComputerNameNoteProperty] != null, "PrepareForRemoteObjects cannot be called when the object does not contain ComputerName property."); if ((dataBaseInfo != null) && (dataBaseInfo.view != null) && (dataBaseInfo.view.mainControl != null)) @@ -60,7 +64,6 @@ internal override void PrepareForRemoteObjects(PSObject so) fpt.expression = new ExpressionToken(RemotingConstants.ComputerNameNoteProperty, false); cnListItemDefinition.formatTokenList.Add(fpt); - _listBody.defaultEntryDefinition.itemDefinitionList.Add(cnListItemDefinition); } } @@ -96,7 +99,7 @@ private ListViewEntry GenerateListViewEntryFromDataBaseInfo(PSObject so, int enu continue; ListViewField lvf = new ListViewField(); - MshExpressionResult result; + PSPropertyExpressionResult result; lvf.formatPropertyField = GenerateFormatPropertyField(listItem.formatTokenList, so, enumerationLimit, out result); // we need now to provide a label @@ -114,26 +117,25 @@ private ListViewEntry GenerateListViewEntryFromDataBaseInfo(PSObject so, int enu { // we did fail getting a result (i.e. property does not exist on the object) - // we try to fall back and see if we have an un-resolved MshExpression + // 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) { - MshExpression ex = this.expressionFactory.CreateFromExpressionToken(fpt.expression, this.dataBaseInfo.view.loadingInfo); + PSPropertyExpression ex = this.expressionFactory.CreateFromExpressionToken(fpt.expression, this.dataBaseInfo.view.loadingInfo); - // use the un-resolved MshExpression string as a label + // 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); } } + lve.listViewFieldList.Add(lvf); } + return lve; } @@ -149,6 +151,7 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr return x; } } + if (match.BestMatch != null) { return match.BestMatch as ListControlEntryDefinition; @@ -156,7 +159,7 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr else { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { match = new TypeMatch(expressionFactory, this.dataBaseInfo.db, typesWithoutPrefix); foreach (ListControlEntryDefinition x in listBody.optionalEntryList) @@ -166,6 +169,7 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr return x; } } + if (match.BestMatch != null) { return match.BestMatch as ListControlEntryDefinition; @@ -179,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) @@ -215,23 +216,11 @@ private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enume { directive = a.OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; } + 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 23450e53734..3b14c0754ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -15,65 +14,70 @@ internal sealed class TableViewGenerator : ViewGenerator // tableBody to use for this instance of the ViewGenerator; private TableControlBody _tableBody; - internal override void Initialize(TerminatingErrorContext terminatingErrorContext, MshExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) + private List _activeAssociationList; + + internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) { base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters); - if ((null != this.dataBaseInfo) && (null != this.dataBaseInfo.view)) + if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null)) { _tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl; } } - internal override void Initialize(TerminatingErrorContext errorContext, MshExpressionFactory expressionFactory, + internal override void Initialize(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - if ((null != this.dataBaseInfo) && (null != this.dataBaseInfo.view)) + if ((this.dataBaseInfo != null) && (this.dataBaseInfo.view != null)) { _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) + // try to get properties from the default property set of the object + 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, - new MshExpression(RemotingConstants.ComputerNameNoteProperty))); + 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(); } /// @@ -83,10 +87,10 @@ internal override void Initialize(TerminatingErrorContext errorContext, MshExpre /// internal override void PrepareForRemoteObjects(PSObject so) { - Diagnostics.Assert(null != so, "so cannot be null"); + Diagnostics.Assert(so != null, "so cannot be null"); // make sure computername property exists. - Diagnostics.Assert(null != so.Properties[RemotingConstants.ComputerNameNoteProperty], + Diagnostics.Assert(so.Properties[RemotingConstants.ComputerNameNoteProperty] != null, "PrepareForRemoteObjects cannot be called when the object does not contain ComputerName property."); if ((dataBaseInfo != null) && (dataBaseInfo.view != null) && (dataBaseInfo.view.mainControl != null)) @@ -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 (list.Count <= maxCount) + { + return list; + } - if (activeAssociationList.Count > nMax) + var result = new List(maxCount); + for (int k = 0; k < maxCount; k++) { - List tmp = this.activeAssociationList; - this.activeAssociationList = new List(); - for (int k = 0; k < nMax; k++) - this.activeAssociationList.Add(tmp[k]); + result.Add(list[k]); } - return; + return result; } private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) @@ -157,6 +160,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) bool dummy; List activeRowItemDefinitionList = GetActiveTableRowDefinition(_tableBody, so, out dummy); thi.hideHeader = this.HideHeaders; + thi.repeatHeader = this.RepeatHeader; int col = 0; foreach (TableRowItemDefinition rowItem in activeRowItemDefinitionList) @@ -171,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) @@ -186,29 +197,25 @@ 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 { - ci.label = ""; + ci.label = string.Empty; } } thi.tableColumnInfoList.Add(ci); col++; } + return thi; } @@ -217,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 @@ -230,10 +238,8 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) if (key != AutomationNull.Value) 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) @@ -269,10 +275,10 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) thi.tableColumnInfoList.Add(ci); } + return thi; } - private bool HideHeaders { get @@ -291,13 +297,27 @@ private bool HideHeaders { return _tableBody.header.hideHeader; } + return false; } } - private static int ComputeDefaultAlignment(PSObject so, MshExpression ex) + private bool RepeatHeaders { - List rList = ex.GetValues(so); + get + { + if (this.parameters != null) + { + return this.parameters.repeatHeader; + } + + return false; + } + } + + private static int ComputeDefaultAlignment(PSObject so, PSPropertyExpression ex) + { + List rList = ex.GetValues(so); if ((rList.Count == 0) || (rList[0].Exception != null)) return TextAlignment.Left; @@ -333,6 +353,7 @@ internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLi // get the global setting for multiline tre.multiLine = this.dataBaseInfo.db.defaultSettingsSection.MultilineTables; } + fed.formatEntryInfo = tre; // override from command line, if there @@ -344,6 +365,7 @@ internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLi tre.multiLine = tableSpecific.multiLine.Value; } } + return fed; } @@ -371,15 +393,13 @@ private List GetActiveTableRowDefinition(TableControlBod break; } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; if (matchingRowDefinition == null) { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { match = new TypeMatch(expressionFactory, this.dataBaseInfo.db, typesWithoutPrefix); @@ -391,10 +411,8 @@ private List GetActiveTableRowDefinition(TableControlBod break; } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; } } @@ -424,6 +442,7 @@ private List GetActiveTableRowDefinition(TableControlBod // use the override activeRowItemDefinitionList.Add(rowItem); } + col++; } @@ -452,19 +471,26 @@ private TableRowEntry GenerateTableRowEntryFromDataBaseInfo(PSObject so, int enu private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int enumerationLimit) { TableRowEntry tre = new TableRowEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { FormatPropertyField fpf = new FormatPropertyField(); FieldFormattingDirective directive = null; - if (activeAssociationList[k].OriginatingParameter != null) + if (_activeAssociationList[k].OriginatingParameter != null) { - directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; + directive = _activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; } - fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive); + + if (directive is null) + { + directive = new FieldFormattingDirective(); + directive.isTable = true; + } + + fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, _activeAssociationList[k].ResolvedExpression, directive); tre.formatPropertyFieldList.Add(fpf); } + return tre; } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs index b7b2d7df749..bfa364cc450 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -10,11 +9,44 @@ namespace Microsoft.PowerShell.Commands.Internal.Format { internal sealed class WideViewGenerator : ViewGenerator { - internal override void Initialize(TerminatingErrorContext errorContext, MshExpressionFactory expressionFactory, + internal override void Initialize(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, 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) @@ -24,7 +56,6 @@ internal override FormatStartData GenerateStartData(PSObject so) WideViewHeaderInfo wideViewHeaderInfo = new WideViewHeaderInfo(); startFormat.shapeInfo = wideViewHeaderInfo; - if (!this.AutoSize) { // autosize overrides columns @@ -83,7 +114,7 @@ private WideViewEntry GenerateWideViewEntryFromDataBaseInfo(PSObject so, int enu WideViewEntry wve = new WideViewEntry(); wve.formatPropertyField = GenerateFormatPropertyField(activeWideControlEntryDefinition.formatTokenList, so, enumerationLimit); - //wve.alignment = activeWideViewEntryDefinition.alignment; + // wve.alignment = activeWideViewEntryDefinition.alignment; return wve; } @@ -100,6 +131,7 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr return x; } } + if (match.BestMatch != null) { return match.BestMatch as WideControlEntryDefinition; @@ -107,7 +139,7 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr else { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null != typesWithoutPrefix) + if (typesWithoutPrefix != null) { match = new TypeMatch(expressionFactory, this.dataBaseInfo.db, typesWithoutPrefix); foreach (WideControlEntryDefinition x in wideBody.optionalEntryList) @@ -117,6 +149,7 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr return x; } } + if (match.BestMatch != null) { return match.BestMatch as WideControlEntryDefinition; @@ -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 - MshExpression 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 adca8c0bc42..891dfce6829 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -9,6 +8,8 @@ using System.Management.Automation.Internal; using System.Text; +using Microsoft.PowerShell.Commands; + namespace Microsoft.PowerShell.Commands.Internal.Format { internal static class DefaultScalarTypes @@ -30,6 +31,7 @@ internal static bool IsTypeInList(Collection typeNames) // check if the type is derived from a System.Enum // e.g. in C# // enum Foo { Red, Black, Green} + if (PSObjectHelper.PSObjectIsEnum(typeNames)) return true; @@ -61,17 +63,16 @@ static DefaultScalarTypes() } /// - /// class to manage the selection of a desired view type and - /// manage state associated to the selected view + /// Class to manage the selection of a desired view type and + /// manage state associated to the selected view. /// 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) { // if so is not null, its TypeNames will not be null @@ -84,11 +85,11 @@ private static string PSObjectTypeName(PSObject so) } } - return ""; + return string.Empty; } internal void Initialize(TerminatingErrorContext errorContext, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, PSObject so, FormatShape shape, @@ -112,6 +113,7 @@ internal void Initialize(TerminatingErrorContext errorContext, { view = DisplayDataQuery.GetViewByShapeAndType(expressionFactory, db, shape, typeNames, null); } + if (view != null) { // we got a matching view from the database @@ -152,6 +154,7 @@ internal void Initialize(TerminatingErrorContext errorContext, { view = DisplayDataQuery.GetViewByShapeAndType(expressionFactory, db, shape, typeNames, parameters.viewName); } + if (view != null) { _viewGenerator = SelectViewGeneratorFromViewDefinition( @@ -163,6 +166,7 @@ internal void Initialize(TerminatingErrorContext errorContext, s_formatViewBindingTracer.WriteLine(viewFound); return; } + s_formatViewBindingTracer.WriteLine(viewNotFound); // illegal input, we have to terminate ProcessUnknownViewName(errorContext, parameters.viewName, so, db, shape); @@ -173,6 +177,7 @@ internal void Initialize(TerminatingErrorContext errorContext, { view = DisplayDataQuery.GetViewByShapeAndType(expressionFactory, db, shape, typeNames, null); } + if (view != null) { _viewGenerator = SelectViewGeneratorFromViewDefinition( @@ -186,6 +191,7 @@ internal void Initialize(TerminatingErrorContext errorContext, return; } + s_formatViewBindingTracer.WriteLine(viewNotFound); // we just select properties out of the object itself _viewGenerator = SelectViewGeneratorFromProperties(shape, so, errorContext, expressionFactory, db, parameters); @@ -228,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 && @@ -270,14 +276,14 @@ private static void ProcessUnknownViewName(TerminatingErrorContext errorContext, foreach (TypeOrGroupReference currentTypeOrGroupReference in currentViewDefinition.appliesTo.referenceList) { if (!string.IsNullOrEmpty(currentTypeOrGroupReference.name) && - String.Equals(currentObjectTypeName, currentTypeOrGroupReference.name, StringComparison.OrdinalIgnoreCase)) + string.Equals(currentObjectTypeName, currentTypeOrGroupReference.name, StringComparison.OrdinalIgnoreCase)) { if (currentViewDefinition.mainControl.GetType() == formatType) { validViews.Append(currentViewDefinition.name); validViews.Append(separator); } - else if (String.Equals(viewName, currentViewDefinition.name, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(viewName, currentViewDefinition.name, StringComparison.OrdinalIgnoreCase)) { string cmdletFormatName = null; if (currentViewDefinition.mainControl is TableControlBody) @@ -328,9 +334,9 @@ private static void ProcessUnknownViewName(TerminatingErrorContext errorContext, StringBuilder unKnowViewFormatStringBuilder = new StringBuilder(); if (validViewFormats.Length > 0) { - //unKnowViewFormatStringBuilder.Append(StringUtil.Format(FormatAndOut_format_xxx.UnknownViewNameError, viewName)); + // 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 { @@ -338,7 +344,7 @@ private static void ProcessUnknownViewName(TerminatingErrorContext errorContext, unKnowViewFormatStringBuilder.Append(StringUtil.Format(FormatAndOut_format_xxx.NonExistingViewNameError, formatTypeName, so.BaseObject.GetType())); } - msg = unKnowViewFormatStringBuilder.ToString(); ; + msg = unKnowViewFormatStringBuilder.ToString(); } ErrorRecord errorRecord = new ErrorRecord( @@ -360,10 +366,9 @@ internal ViewGenerator ViewGenerator } } - private static ViewGenerator SelectViewGeneratorFromViewDefinition( TerminatingErrorContext errorContext, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters parameters) @@ -393,7 +398,7 @@ private static ViewGenerator SelectViewGeneratorFromViewDefinition( private static ViewGenerator SelectViewGeneratorFromProperties(FormatShape shape, PSObject so, TerminatingErrorContext errorContext, - MshExpressionFactory expressionFactory, + PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { @@ -408,7 +413,7 @@ private static ViewGenerator SelectViewGeneratorFromProperties(FormatShape shape { // check if we can have a table: // we want to get the # of properties we are going to display - List expressionList = PSObjectHelper.GetDefaultPropertySet(so); + List expressionList = PSObjectHelper.GetDefaultPropertySet(so); if (expressionList.Count == 0) { // we failed to get anything from a property set @@ -441,84 +446,39 @@ private static ViewGenerator SelectViewGeneratorFromProperties(FormatShape shape { viewGenerator = new ComplexViewGenerator(); } + Diagnostics.Assert(viewGenerator != null, "viewGenerator != null"); viewGenerator.Initialize(errorContext, expressionFactory, so, db, parameters); return viewGenerator; } - - /// - /// the view generator that produced data for a selected shape + /// The view generator that produced data for a selected shape. /// private ViewGenerator _viewGenerator = null; } /// - /// class to manage the selection of a desired view type - /// for out of band objects + /// Class to manage the selection of a desired view type + /// for out of band objects. /// internal static class OutOfBandFormatViewManager { - internal static bool IsPropertyLessObject(PSObject so) + private static bool IsNotRemotingProperty(string name) { - List allProperties = AssociationManager.ExpandAll(so); - - if (allProperties.Count == 0) - { - return true; - } - if (allProperties.Count == 3) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - if (allProperties.Count == 4) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) - && !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - if (allProperties.Count == 5) - { - foreach (MshResolvedExpressionParameterAssociation property in allProperties) - { - if (!property.ResolvedExpression.ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase) && - !property.ResolvedExpression.ToString().Equals(RemotingConstants.SourceLength, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } + var isRemotingPropertyName = name.Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase) + || name.Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase); + return !isRemotingPropertyName; + } - return true; - } + private static readonly MemberNamePredicate NameIsNotRemotingProperty = IsNotRemotingProperty; - return false; - } + internal static bool HasNonRemotingProperties(PSObject so) => so.GetFirstPropertyOrDefault(NameIsNotRemotingProperty) != null; - internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext errorContext, MshExpressionFactory expressionFactory, + internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory, TypeInfoDataBase db, PSObject so, int enumerationLimit, bool useToStringFallback, out List errors) { errors = null; @@ -538,12 +498,13 @@ internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext er { outOfBandViewGenerator = new ListViewGenerator(); } + outOfBandViewGenerator.Initialize(errorContext, expressionFactory, db, view, null); } else { - if (DefaultScalarTypes.IsTypeInList(typeNames) || - IsPropertyLessObject(so)) + if (DefaultScalarTypes.IsTypeInList(typeNames) + || !HasNonRemotingProperties(so)) { // we force a ToString() on well known types return GenerateOutOfBandObjectAsToString(so); @@ -555,7 +516,7 @@ internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext er } // we must check we have enough properties for a list view - if (new MshExpression("*").ResolveNames(so).Count <= 0) + if (new PSPropertyExpression("*").ResolveNames(so).Count == 0) { return null; } @@ -568,7 +529,7 @@ internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext er FormatEntryData fed = outOfBandViewGenerator.GeneratePayload(so, enumerationLimit); fed.outOfBand = true; - fed.SetStreamTypeFromPSObject(so); + fed.writeStream = so.WriteStream; errors = outOfBandViewGenerator.ErrorManager.DrainFailedResultList(); @@ -588,14 +549,13 @@ internal static FormatEntryData GenerateOutOfBandObjectAsToString(PSObject so) } } - /// /// Helper class to manage the logging of errors resulting from - /// evaluations of MshExpression instances + /// evaluations of PSPropertyExpression instances /// - /// Depending on settings, it queues the failing MshExpressionResult + /// Depending on settings, it queues the failing PSPropertyExpressionResult /// instances and generates a list of out-of-band FormatEntryData - /// objects to be sent to the output pipeline + /// objects to be sent to the output pipeline. /// internal sealed class FormatErrorManager { @@ -605,24 +565,24 @@ internal FormatErrorManager(FormatErrorPolicy formatErrorPolicy) } /// - /// log a failed evaluation of an MshExpression + /// Log a failed evaluation of an PSPropertyExpression. /// - /// MshExpressionResult containing the failed evaluation data - /// object used to evaluate the MshExpression - internal void LogMshExpressionFailedResult(MshExpressionResult result, object sourceObject) + /// PSPropertyExpressionResult containing the failed evaluation data. + /// Object used to evaluate the PSPropertyExpression. + internal void LogPSPropertyExpressionFailedResult(PSPropertyExpressionResult result, object sourceObject) { if (!_formatErrorPolicy.ShowErrorsAsMessages) return; - MshExpressionError error = new MshExpressionError(); + PSPropertyExpressionError error = new PSPropertyExpressionError(); error.result = result; error.sourceObject = sourceObject; _formattingErrorList.Add(error); } /// - /// log a failed formatting operation + /// Log a failed formatting operation. /// - /// string format error object + /// String format error object. internal void LogStringFormatError(StringFormatError error) { if (!_formatErrorPolicy.ShowErrorsAsMessages) @@ -655,11 +615,11 @@ internal string FormatErrorString } /// - /// provide a list of ErrorRecord entries + /// Provide a list of ErrorRecord entries /// to be written to the error pipeline and clear the list of pending - /// errors + /// errors. /// - /// list of ErrorRecord objects + /// List of ErrorRecord objects. internal List DrainFailedResultList() { if (!_formatErrorPolicy.ShowErrorsAsMessages) @@ -672,35 +632,34 @@ internal List DrainFailedResultList() if (errorRecord != null) retVal.Add(errorRecord); } + _formattingErrorList.Clear(); return retVal; } /// - /// Conversion between an error internal representation and ErrorRecord + /// Conversion between an error internal representation and ErrorRecord. /// - /// internal error object - /// corresponding ErrorRecord instance + /// Internal error object. + /// Corresponding ErrorRecord instance. private static ErrorRecord GenerateErrorRecord(FormattingError error) { ErrorRecord errorRecord = null; string msg = null; - MshExpressionError mshExpressionError = error as MshExpressionError; - if (mshExpressionError != null) + if (error is PSPropertyExpressionError psPropertyExpressionError) { errorRecord = new ErrorRecord( - mshExpressionError.result.Exception, - "mshExpressionError", + psPropertyExpressionError.result.Exception, + "PSPropertyExpressionError", ErrorCategory.InvalidArgument, - mshExpressionError.sourceObject); + psPropertyExpressionError.sourceObject); - msg = StringUtil.Format(FormatAndOut_format_xxx.MshExpressionError, - mshExpressionError.result.ResolvedExpression.ToString()); + msg = StringUtil.Format(FormatAndOut_format_xxx.PSPropertyExpressionError, + psPropertyExpressionError.result.ResolvedExpression.ToString()); errorRecord.ErrorDetails = new ErrorDetails(msg); } - StringFormatError formattingError = error as StringFormatError; - if (formattingError != null) + if (error is StringFormatError formattingError) { errorRecord = new ErrorRecord( formattingError.exception, @@ -712,16 +671,15 @@ private static ErrorRecord GenerateErrorRecord(FormattingError error) formattingError.formatString); errorRecord.ErrorDetails = new ErrorDetails(msg); } + return errorRecord; } - - private FormatErrorPolicy _formatErrorPolicy; + private readonly FormatErrorPolicy _formatErrorPolicy; /// - /// current list of failed MsExpression evaluations + /// 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 b679bb9ddfa..c8b077918fe 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatXMLWriter.cs @@ -1,20 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.IO; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.Management.Automation; using System.Xml; namespace Microsoft.PowerShell.Commands { /// - /// Helper class for writing formatting directives to XML + /// Helper class for writing formatting directives to XML. /// - internal class FormatXmlWriter + internal sealed class FormatXmlWriter { private XmlWriter _writer; private bool _exportScriptBlock; @@ -22,15 +21,15 @@ internal class FormatXmlWriter private FormatXmlWriter() { } /// - /// Writes a collection of format view definitions to XML file + /// Writes a collection of format view definitions to XML file. /// - /// collection of PSTypeDefinition - /// path to XML file - /// cmdlet from which this si used - /// true - to force write the file - /// true - to export scriptblocks - /// true - do not overwrite the file - /// true - bypass wildcard expansion on the file name + /// Collection of PSTypeDefinition. + /// Path to XML file. + /// Cmdlet from which this si used. + /// True - to force write the file. + /// True - to export scriptblocks. + /// True - do not overwrite the file. + /// True - bypass wildcard expansion on the file name. internal static void WriteToPs1Xml(PSCmdlet cmdlet, List typeDefinitions, string filepath, bool force, bool noclobber, bool writeScriptBlock, bool isLiteralPath) { @@ -42,7 +41,12 @@ internal static void WriteToPs1Xml(PSCmdlet cmdlet, List try { - using (XmlWriter xmlWriter = XmlWriter.Create(streamWriter)) + var settings = new XmlWriterSettings(); + settings.Indent = true; + settings.IndentChars = " "; + settings.NewLineOnAttributes = true; + + using (XmlWriter xmlWriter = XmlWriter.Create(streamWriter, settings)) { var writer = new FormatXmlWriter { @@ -85,6 +89,7 @@ internal void WriteToXml(IEnumerable typeDefinitions) { formatdefs.Add(viewdefinition.InstanceId, viewdefinition); } + viewList.Add(typedefinition); } } @@ -104,6 +109,7 @@ internal void WriteToXml(IEnumerable typeDefinitions) { _writer.WriteElementString("TypeName", definition.TypeName); } + _writer.WriteEndElement(/**/); var groupBy = formatdef.Control.GroupBy; @@ -115,20 +121,24 @@ internal void WriteToXml(IEnumerable typeDefinitions) { _writer.WriteElementString("Label", groupBy.Label); } + if (groupBy.CustomControl != null) { WriteCustomControl(groupBy.CustomControl); } + _writer.WriteEndElement(/**/); } + if (formatdef.Control.OutOfBand) { - _writer.WriteElementString("OutOfBand", ""); + _writer.WriteElementString("OutOfBand", string.Empty); } formatdef.Control.WriteToXml(this); _writer.WriteEndElement(/**/); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); @@ -139,11 +149,12 @@ internal void WriteTableControl(TableControl tableControl) _writer.WriteStartElement("TableControl"); if (tableControl.AutoSize) { - _writer.WriteElementString("AutoSize", ""); + _writer.WriteElementString("AutoSize", string.Empty); } + if (tableControl.HideTableHeaders) { - _writer.WriteElementString("HideTableHeaders", ""); + _writer.WriteElementString("HideTableHeaders", string.Empty); } _writer.WriteStartElement("TableHeaders"); @@ -154,16 +165,20 @@ internal void WriteTableControl(TableControl tableControl) { _writer.WriteElementString("Label", columnheader.Label); } + if (columnheader.Width > 0) { _writer.WriteElementString("Width", columnheader.Width.ToString(CultureInfo.InvariantCulture)); } + if (columnheader.Alignment != Alignment.Undefined) { _writer.WriteElementString("Alignment", columnheader.Alignment.ToString()); } + _writer.WriteEndElement(/**/); } + _writer.WriteEndElement(/**/); _writer.WriteStartElement("TableRowEntries"); @@ -175,10 +190,12 @@ internal void WriteTableControl(TableControl tableControl) _writer.WriteStartElement("Wrap"); _writer.WriteEndElement(/**/); } + if (row.SelectedBy != null) { WriteEntrySelectedBy(row.SelectedBy); } + _writer.WriteStartElement("TableColumnItems"); foreach (TableControlColumn coldefn in row.Columns) { @@ -187,10 +204,12 @@ internal void WriteTableControl(TableControl tableControl) { _writer.WriteElementString("Alignment", coldefn.Alignment.ToString()); } + if (!string.IsNullOrEmpty(coldefn.FormatString)) { _writer.WriteElementString("FormatString", coldefn.FormatString); } + WriteDisplayEntry(coldefn.DisplayEntry); _writer.WriteEndElement(/**/); } @@ -198,6 +217,7 @@ internal void WriteTableControl(TableControl tableControl) _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); @@ -273,6 +293,7 @@ private void WriteEntrySelectedBy(EntrySelectedBy entrySelectedBy) _writer.WriteElementString("TypeName", typename); } } + if (entrySelectedBy.SelectionCondition != null) { foreach (var condition in entrySelectedBy.SelectionCondition) @@ -282,6 +303,7 @@ private void WriteEntrySelectedBy(EntrySelectedBy entrySelectedBy) _writer.WriteEndElement(/**/); } } + _writer.WriteEndElement(/**/); } } @@ -297,7 +319,7 @@ internal void WriteWideControl(WideControl wideControl) if (wideControl.AutoSize) { - _writer.WriteElementString("AutoSize", ""); + _writer.WriteElementString("AutoSize", string.Empty); } _writer.WriteStartElement("WideEntries"); @@ -313,10 +335,12 @@ internal void WriteWideControl(WideControl wideControl) { _writer.WriteElementString("FormatString", entry.FormatString); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); @@ -350,39 +374,39 @@ internal void WriteCustomControl(CustomControl customControl) { WriteCustomItem(item); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); } 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++) { - _writer.WriteElementString("NewLine", ""); + _writer.WriteElementString("NewLine", string.Empty); } + 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) { - _writer.WriteElementString("EnumerateCollection", ""); + _writer.WriteElementString("EnumerateCollection", string.Empty); } if (expr.ItemSelectionCondition != null) @@ -422,6 +446,7 @@ internal void WriteCustomItem(CustomItemBase item) { WriteCustomItem(frameItem); } + _writer.WriteEndElement(/**/); _writer.WriteEndElement(/**/); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs index 7660eb45f48..4af1ee54d81 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjects.cs @@ -1,6 +1,6 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // This file contains the definitions for the objects // used in the communication protocol between formatting // and output commands. the format/xxx commands instantiate @@ -18,39 +18,38 @@ // 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. // using System.Collections.Generic; - +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.Internal.Format { #region Root of Class Hierarchy /// - /// base class from which all the formatting objects + /// Base class from which all the formatting objects /// will derive from. /// It provides the mechanism to preserve type information. /// internal abstract partial class FormatInfoData { /// - /// name of the "get" property that allows access to CLSID information. - /// This is needed by the ERS API's + /// Name of the "get" property that allows access to CLSID information. + /// This is needed by the ERS API's. /// internal const string classidProperty = "ClassId2e4f51ef21dd47e99d3c952918aff9cd"; /// - /// string containing a GUID, to be set by each derived class + /// String containing a GUID, to be set by each derived class /// "get" property to get CLSID information. /// It is named with a GUID like name to avoid potential collisions with - /// properties of payload objects + /// properties of payload objects. /// public abstract string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get; } } - #endregion #region Top Level Messages @@ -62,7 +61,7 @@ internal abstract class PacketInfoData : FormatInfoData internal abstract partial class ControlInfoData : PacketInfoData { /// - /// null by default, present only if grouping specified + /// Null by default, present only if grouping specified. /// public GroupingEntry groupingEntry = null; } @@ -70,14 +69,14 @@ internal abstract partial class ControlInfoData : PacketInfoData internal abstract partial class StartData : ControlInfoData { /// - /// it needs to be either on FormatStartData or GroupStartData + /// It needs to be either on FormatStartData or GroupStartData /// but not both or neither. /// public ShapeInfo shapeInfo; } /// - /// sequence start: the very first message sent + /// Sequence start: the very first message sent. /// internal sealed partial class FormatStartData : StartData { @@ -86,17 +85,17 @@ internal sealed partial class FormatStartData : StartData public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } /// - /// optional + /// Optional. /// public PageHeaderEntry pageHeaderEntry; /// - /// optional + /// Optional. /// public PageFooterEntry pageFooterEntry; /// - /// autosize formatting directive. If present, the output command is instructed + /// Autosize formatting directive. If present, the output command is instructed /// to get the autosize "best fit" for the device screen according to the flags /// this object contains. /// @@ -104,7 +103,7 @@ internal sealed partial class FormatStartData : StartData } /// - /// sequence end: the very last message sent + /// Sequence end: the very last message sent. /// internal sealed class FormatEndData : ControlInfoData { @@ -114,7 +113,7 @@ internal sealed class FormatEndData : ControlInfoData } /// - /// group start: message marking the beginning of a group + /// Group start: message marking the beginning of a group. /// internal sealed class GroupStartData : StartData { @@ -124,7 +123,7 @@ internal sealed class GroupStartData : StartData } /// - /// group end: message marking the end of a group + /// Group end: message marking the end of a group. /// internal sealed class GroupEndData : ControlInfoData { @@ -134,7 +133,7 @@ internal sealed class GroupEndData : ControlInfoData } /// - /// generic entry containing payload data and related formatting info + /// Generic entry containing payload data and related formatting info. /// internal sealed partial class FormatEntryData : PacketInfoData { @@ -143,48 +142,14 @@ internal sealed partial class FormatEntryData : PacketInfoData public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } /// - /// mandatory, but depending on the shape we send in - /// it must match what got sent in the format start message + /// Mandatory, but depending on the shape we send in + /// it must match what got sent in the format start message. /// public FormatEntryInfo formatEntryInfo = null; public bool outOfBand = false; public WriteStreamType writeStream = WriteStreamType.None; internal bool isHelpObject = false; - - /// - /// Helper method to set the WriteStreamType property - /// based on note properties of a PSObject object. - /// - /// PSObject - internal void SetStreamTypeFromPSObject( - System.Management.Automation.PSObject so) - { - if (PSObjectHelper.IsWriteErrorStream(so)) - { - writeStream = WriteStreamType.Error; - } - else if (PSObjectHelper.IsWriteWarningStream(so)) - { - writeStream = WriteStreamType.Warning; - } - else if (PSObjectHelper.IsWriteVerboseStream(so)) - { - writeStream = WriteStreamType.Verbose; - } - else if (PSObjectHelper.IsWriteDebugStream(so)) - { - writeStream = WriteStreamType.Debug; - } - else if (PSObjectHelper.IsWriteInformationStream(so)) - { - writeStream = WriteStreamType.Information; - } - else - { - writeStream = WriteStreamType.None; - } - } } #endregion @@ -201,7 +166,7 @@ internal sealed partial class WideViewHeaderInfo : ShapeInfo public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } /// - /// desired number of columns on the screen. + /// Desired number of columns on the screen. /// Advisory, the outputter can decide otherwise /// /// A zero value signifies let the outputter get the @@ -222,6 +187,7 @@ public TableHeaderInfo() public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } public bool hideHeader; + public bool repeatHeader; public List tableColumnInfoList; } @@ -232,18 +198,18 @@ internal sealed partial class TableColumnInfo : FormatInfoData public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } /// - /// width of the column: + /// Width of the column: /// == 0 -> let the outputter decide - /// > 0 -> user provided value + /// > 0 -> user provided value. /// public int width = 0; public int alignment = TextAlignment.Left; public string label = null; public string propertyName = null; + public bool HeaderMatchesProperty = true; } - internal sealed class ListViewHeaderInfo : ShapeInfo { internal const string CLSID = "830bdcb24c1642258724e441512233a4"; @@ -258,7 +224,6 @@ internal sealed class ComplexViewHeaderInfo : ShapeInfo public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } } - #endregion #region Formatting Entries Classes @@ -355,9 +320,9 @@ internal sealed partial class AutosizeInfo : FormatInfoData public override string ClassId2e4f51ef21dd47e99d3c952918aff9cd { get { return CLSID; } } /// - /// number of objects to compute the best fit. + /// Number of objects to compute the best fit. /// Zero: all the objects - /// a positive number N: use the first N + /// a positive number N: use the first N. /// public int objectCount = 0; } @@ -410,7 +375,7 @@ public FormatEntry() public List formatValueList; /// - /// optional information of frame data (indentation, etc.) + /// Optional information of frame data (indentation, etc.) /// public FrameInfo frameInfo; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs index 869f1e919c9..7ae144702ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs @@ -1,27 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Reflection; using System.Collections; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Internal; - +using System.Reflection; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class to deserialize property bags into formatting objects - /// by using ERS functionality + /// Class to deserialize property bags into formatting objects + /// by using ERS functionality. /// internal sealed class FormatObjectDeserializer { - internal TerminatingErrorContext TerminatingErrorContext { get; private set; } + internal TerminatingErrorContext TerminatingErrorContext { get; } /// - /// expansion of TAB character to the following string + /// Expansion of TAB character to the following string. /// private const string TabExpansionString = " "; @@ -32,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 || @@ -49,7 +46,6 @@ fid is GroupEndData || return false; } - // check the type of the object by // 1) verifying the type name information // 2) trying to access the property containing CLSID information @@ -57,9 +53,8 @@ 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; @@ -81,17 +76,16 @@ fid is GroupEndData || } /// - /// given a raw object out of the pipeline, it deserializes it accordingly to + /// Given a raw object out of the pipeline, it deserializes it accordingly to /// its type. /// If the object is not one of the well known ones (i.e. derived from FormatInfoData) - /// it just returns the object unchanged + /// it just returns the object unchanged. /// - /// object to deserialize - /// deserialized object or null + /// Object to deserialize. + /// 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 || @@ -115,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 @@ -144,7 +136,7 @@ private void ProcessUnknownInvalidClassId(string classId, object obj, string err string msg = StringUtil.Format(FormatAndOut_format_xxx.FOD_ClassIdInvalid, classId); ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("classId"), + PSTraceSource.NewArgumentException(nameof(classId)), errorId, ErrorCategory.InvalidData, obj); @@ -156,7 +148,7 @@ private void ProcessUnknownInvalidClassId(string classId, object obj, string err #region Helper Methods private static bool IsClass(string x, string y) { - return string.Compare(x, y, StringComparison.OrdinalIgnoreCase) == 0; + return string.Equals(x, y, StringComparison.OrdinalIgnoreCase); } #if _UNUSED @@ -164,11 +156,11 @@ private static bool IsClass(string x, string y) it. We retail it because future schema extensions might require it /// - /// ERS helper to reconstitute a string[] out of IEnumerable property + /// ERS helper to reconstitute a string[] out of IEnumerable property. /// - /// object to process - /// property to look up - /// string[] representation of the property + /// Object to process. + /// Property to look up. + /// String[] representation of the property. private static string[] ReadStringArrayHelper (object rawObject, string propertyName) { // throw if the property is not there @@ -219,7 +211,7 @@ internal FormatInfoData DeserializeMemberObject(PSObject so, string property) string msg = StringUtil.Format(FormatAndOut_format_xxx.FOD_RecursiveProperty, property); ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("property"), + PSTraceSource.NewArgumentException(nameof(property)), "FormatObjectDeserializerRecursiveProperty", ErrorCategory.InvalidData, so); @@ -227,14 +219,17 @@ internal FormatInfoData DeserializeMemberObject(PSObject so, string property) errorRecord.ErrorDetails = new ErrorDetails(msg); this.TerminatingErrorContext.ThrowTerminatingError(errorRecord); } + return DeserializeObject(PSObject.AsPSObject(memberRaw)); } + internal FormatInfoData DeserializeMandatoryMemberObject(PSObject so, string property) { FormatInfoData fid = DeserializeMemberObject(so, property); VerifyDataNotNull(fid, property); return fid; } + private object DeserializeMemberVariable(PSObject so, string property, System.Type t, bool cannotBeNull) { object objRaw = GetProperty(so, property); @@ -246,7 +241,7 @@ private object DeserializeMemberVariable(PSObject so, string property, System.Ty string msg = StringUtil.Format(FormatAndOut_format_xxx.FOD_InvalidPropertyType, t.Name, property); ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("property"), + PSTraceSource.NewArgumentException(nameof(property)), "FormatObjectDeserializerInvalidPropertyType", ErrorCategory.InvalidData, so); @@ -254,26 +249,27 @@ private object DeserializeMemberVariable(PSObject so, string property, System.Ty errorRecord.ErrorDetails = new ErrorDetails(msg); this.TerminatingErrorContext.ThrowTerminatingError(errorRecord); } + return objRaw; } /// /// Deserialization of string without TAB expansion (RAW) /// - /// object whose the property belongs to - /// name of the string property - /// string out of the MsObject + /// Object whose the property belongs to. + /// Name of the string property. + /// String out of the MsObject. internal string DeserializeStringMemberVariableRaw(PSObject so, string property) { return (string)DeserializeMemberVariable(so, property, typeof(string), false /* cannotBeNull */); } /// - /// Deserialization of string performing TAB expansion + /// Deserialization of string performing TAB expansion. /// - /// object whose the property belongs to - /// name of the string property - /// string out of the MsObject + /// Object whose the property belongs to. + /// Name of the string property. + /// String out of the MsObject. internal string DeserializeStringMemberVariable(PSObject so, string property) { string val = (string)DeserializeMemberVariable(so, property, typeof(string), false /* cannotBeNull */); @@ -288,9 +284,11 @@ internal int DeserializeIntMemberVariable(PSObject so, string property) { return (int)DeserializeMemberVariable(so, property, typeof(int), true /* cannotBeNull */); } - internal bool DeserializeBoolMemberVariable(PSObject so, string property) + + internal bool DeserializeBoolMemberVariable(PSObject so, string property, bool cannotBeNull = true) { - return (bool)DeserializeMemberVariable(so, property, typeof(bool), true /* cannotBeNull */); + var val = DeserializeMemberVariable(so, property, typeof(bool), cannotBeNull); + return val != null && (bool)val; } internal WriteStreamType DeserializeWriteStreamTypeMemberVariable(PSObject so) @@ -325,9 +323,7 @@ internal WriteStreamType DeserializeWriteStreamTypeMemberVariable(PSObject so) internal FormatInfoData DeserializeObject(PSObject so) { FormatInfoData fid = FormatInfoDataClassFactory.CreateInstance(so, this); - - if (fid != null) - fid.Deserialize(so, this); + fid?.Deserialize(so, this); return fid; } @@ -355,31 +351,31 @@ static FormatInfoDataClassFactory() { s_constructors = new Dictionary> { - {FormatStartData.CLSID, () => new FormatStartData()}, - {FormatEndData.CLSID, () => new FormatEndData()}, - {GroupStartData.CLSID, () => new GroupStartData()}, - {GroupEndData.CLSID, () => new GroupEndData()}, - {FormatEntryData.CLSID, () => new FormatEntryData()}, - {WideViewHeaderInfo.CLSID, () => new WideViewHeaderInfo()}, - {TableHeaderInfo.CLSID, () => new TableHeaderInfo()}, - {TableColumnInfo.CLSID, () => new TableColumnInfo()}, - {ListViewHeaderInfo.CLSID, () => new ListViewHeaderInfo()}, - {ListViewEntry.CLSID, () => new ListViewEntry()}, - {ListViewField.CLSID, () => new ListViewField()}, - {TableRowEntry.CLSID, () => new TableRowEntry()}, - {WideViewEntry.CLSID, () => new WideViewEntry()}, - {ComplexViewHeaderInfo.CLSID, () => new ComplexViewHeaderInfo()}, - {ComplexViewEntry.CLSID, () => new ComplexViewEntry()}, - {GroupingEntry.CLSID, () => new GroupingEntry()}, - {PageHeaderEntry.CLSID, () => new PageHeaderEntry()}, - {PageFooterEntry.CLSID, () => new PageFooterEntry()}, - {AutosizeInfo.CLSID, () => new AutosizeInfo()}, - {FormatNewLine.CLSID, () => new FormatNewLine()}, - {FrameInfo.CLSID, () => new FrameInfo()}, - {FormatTextField.CLSID, () => new FormatTextField()}, - {FormatPropertyField.CLSID, () => new FormatPropertyField()}, - {FormatEntry.CLSID, () => new FormatEntry()}, - {RawTextFormatEntry.CLSID, () => new RawTextFormatEntry()} + {FormatStartData.CLSID, static () => new FormatStartData()}, + {FormatEndData.CLSID, static () => new FormatEndData()}, + {GroupStartData.CLSID, static () => new GroupStartData()}, + {GroupEndData.CLSID, static () => new GroupEndData()}, + {FormatEntryData.CLSID, static () => new FormatEntryData()}, + {WideViewHeaderInfo.CLSID, static () => new WideViewHeaderInfo()}, + {TableHeaderInfo.CLSID, static () => new TableHeaderInfo()}, + {TableColumnInfo.CLSID, static () => new TableColumnInfo()}, + {ListViewHeaderInfo.CLSID, static () => new ListViewHeaderInfo()}, + {ListViewEntry.CLSID, static () => new ListViewEntry()}, + {ListViewField.CLSID, static () => new ListViewField()}, + {TableRowEntry.CLSID, static () => new TableRowEntry()}, + {WideViewEntry.CLSID, static () => new WideViewEntry()}, + {ComplexViewHeaderInfo.CLSID, static () => new ComplexViewHeaderInfo()}, + {ComplexViewEntry.CLSID, static () => new ComplexViewEntry()}, + {GroupingEntry.CLSID, static () => new GroupingEntry()}, + {PageHeaderEntry.CLSID, static () => new PageHeaderEntry()}, + {PageFooterEntry.CLSID, static () => new PageFooterEntry()}, + {AutosizeInfo.CLSID, static () => new AutosizeInfo()}, + {FormatNewLine.CLSID, static () => new FormatNewLine()}, + {FrameInfo.CLSID, static () => new FrameInfo()}, + {FormatTextField.CLSID, static () => new FormatTextField()}, + {FormatPropertyField.CLSID, static () => new FormatPropertyField()}, + {FormatEntry.CLSID, static () => new FormatEntry()}, + {RawTextFormatEntry.CLSID, static () => new RawTextFormatEntry()} }; } @@ -388,7 +384,7 @@ internal static FormatInfoData CreateInstance(PSObject so, FormatObjectDeseriali { if (so == null) { - throw PSTraceSource.NewArgumentNullException("so"); + throw PSTraceSource.NewArgumentNullException(nameof(so)); } // look for the property that defines the type of object @@ -406,20 +402,21 @@ internal static FormatInfoData CreateInstance(PSObject so, FormatObjectDeseriali errorRecord.ErrorDetails = new ErrorDetails(msg); deserializer.TerminatingErrorContext.ThrowTerminatingError(errorRecord); } + FormatInfoData fid = CreateInstance(classId, deserializer); return fid; } - // returns null on failure private static FormatInfoData CreateInstance(string clsid, FormatObjectDeserializer deserializer) { Func ctor; if (!s_constructors.TryGetValue(clsid, out ctor)) { - CreateInstanceError(PSTraceSource.NewArgumentException("clsid"), clsid, deserializer); + CreateInstanceError(PSTraceSource.NewArgumentException(nameof(clsid)), clsid, deserializer); return null; } + try { FormatInfoData fid = ctor(); @@ -460,6 +457,7 @@ private static FormatInfoData CreateInstance(string clsid, FormatObjectDeseriali + e.GetType().FullName); throw; } + return null; } @@ -480,8 +478,6 @@ private static void CreateInstanceError(Exception e, string clsid, FormatObjectD private static readonly Dictionary> s_constructors; } - - internal static class FormatInfoDataListDeserializer where T : FormatInfoData { private static void ReadListHelper(IEnumerable en, List lst, FormatObjectDeserializer deserializer) @@ -495,18 +491,19 @@ private static void ReadListHelper(IEnumerable en, List lst, FormatObjectDese lst.Add(entry); } } + internal static void ReadList(PSObject so, string property, List lst, FormatObjectDeserializer deserializer) { if (lst == null) { - throw PSTraceSource.NewArgumentNullException("lst"); + throw PSTraceSource.NewArgumentNullException(nameof(lst)); } + object memberRaw = FormatObjectDeserializer.GetProperty(so, property); ReadListHelper(PSObjectHelper.GetEnumerable(memberRaw), lst, deserializer); } } - #region Formatting Objects Deserializer internal abstract partial class FormatInfoData @@ -519,7 +516,7 @@ internal abstract partial class ControlInfoData : PacketInfoData internal override void Deserialize(PSObject so, FormatObjectDeserializer deserializer) { base.Deserialize(so, deserializer); - //optional + // optional this.groupingEntry = (GroupingEntry)deserializer.DeserializeMemberObject(so, "groupingEntry"); } } @@ -554,6 +551,7 @@ internal override void Deserialize(PSObject so, FormatObjectDeserializer deseria this.autosizeInfo = (AutosizeInfo)deserializer.DeserializeMemberObject(so, "autosizeInfo"); } } + internal sealed partial class FormatEntryData : PacketInfoData { internal override void Deserialize(PSObject so, FormatObjectDeserializer deserializer) @@ -581,6 +579,10 @@ internal sealed partial class TableHeaderInfo : ShapeInfo internal override void Deserialize(PSObject so, FormatObjectDeserializer deserializer) { base.Deserialize(so, deserializer); + + // The "repeatHeader" property was added later (V5, V6) and presents an incompatibility when remoting to older version PowerShell sessions. + // When the property is missing from the serialized object, let the deserialized property be false. + this.repeatHeader = deserializer.DeserializeBoolMemberVariable(so, "repeatHeader", cannotBeNull: false); this.hideHeader = deserializer.DeserializeBoolMemberVariable(so, "hideHeader"); FormatInfoDataListDeserializer.ReadList(so, "tableColumnInfoList", this.tableColumnInfoList, deserializer); } @@ -673,6 +675,7 @@ internal override void Deserialize(PSObject so, FormatObjectDeserializer deseria this.alignment = deserializer.DeserializeIntMemberVariable(so, "alignment"); } } + internal sealed partial class FormatEntry : FormatValue { internal override void Deserialize(PSObject so, FormatObjectDeserializer deserializer) @@ -682,6 +685,7 @@ internal override void Deserialize(PSObject so, FormatObjectDeserializer deseria this.frameInfo = (FrameInfo)deserializer.DeserializeMemberObject(so, "frameInfo"); } } + internal sealed partial class FrameInfo : FormatInfoData { internal override void Deserialize(PSObject so, FormatObjectDeserializer deserializer) @@ -694,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 0919fa17a20..7bca25ea828 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs @@ -1,11 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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; -using System.Globalization; // interfaces for host interaction @@ -13,72 +15,158 @@ 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) { - return str.Length - offset; - } + if (string.IsNullOrEmpty(str)) + { + return 0; + } - internal virtual int Length(char character) { return 1; } + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) + { + str = valueStrDec.ToString(OutputRendering.PlainText); + } - internal virtual int GetHeadSplitLength(string str, int displayCells) + int length = 0; + for (; offset < str.Length; offset++) + { + length += CharLengthInBufferCells(str[offset]); + } + + return length; + } + + /// + /// 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 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 + bool isWide = c >= 0x1100 && + (c <= 0x115f || /* Hangul Jamo init. consonants */ + c == 0x2329 || c == 0x232a || + ((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) && + c != 0x303f) || /* CJK ... Yi */ + ((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */ + ((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */ + ((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */ + ((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */ + ((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */ + ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); + + // We can ignore these ranges because .Net strings use surrogate pairs + // for this range and we do not handle surrogate pairs. + // (c >= 0x20000 && c <= 0x2fffd) || + // (c >= 0x30000 && c <= 0x3fffd) + return 1 + (isWide ? 1 : 0); + } + /// /// 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 - /// offset inside the string - /// number of display cells - /// 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) + /// characters would fit starting from the beginning or end of the string. + /// + /// 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--). + /// Number of characters that would fit. + 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]); @@ -87,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++; @@ -98,25 +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 - } - - /// - /// Specifies special stream write processing. - /// - internal enum WriteStreamType - { - None, - Error, - Warning, - Verbose, - Debug, - Information + #endregion } /// @@ -133,37 +210,35 @@ internal enum WriteStreamType /// - Fixed pitch font: layout done in terms of character cells /// - character cell layout not affected by bold, reverse screen, color, etc. /// - returned values might change from call to call if the specific underlying - /// implementation allows window resizing + /// implementation allows window resizing. /// internal abstract class LineOutput { /// - /// whether the device requires full buffering of formatting - /// objects before any processing + /// Whether the device requires full buffering of formatting + /// objects before any processing. /// internal virtual bool RequiresBuffering { get { return false; } } /// - /// delegate the implementor of ExecuteBufferPlayBack should - /// call to cause the playback to happen when ready to execute + /// Delegate the implementor of ExecuteBufferPlayBack should + /// call to cause the playback to happen when ready to execute. /// internal delegate void DoPlayBackCall(); - /// - /// if RequiresBuffering = true, this call will be made to - /// start the playback + /// If RequiresBuffering = true, this call will be made to + /// start the playback. /// internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { } - /// /// - /// The number of columns the current device has + /// The number of columns the current device has. /// internal abstract int ColumnNumber { get; } /// - /// The number of rows the current device has + /// The number of rows the current device has. /// internal abstract int RowNumber { get; } @@ -175,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; @@ -182,13 +264,14 @@ internal WriteStreamType WriteStream } /// - /// handle the stop processing signal. - /// Set a flag that will be checked during operations + /// Handle the stop processing signal. + /// Set a flag that will be checked during operations. /// internal void StopProcessing() { _isStopping = true; } + private bool _isStopping; internal void CheckStopProcessing() @@ -199,7 +282,7 @@ internal void CheckStopProcessing() } /// - /// return an instance of the display helper tear off + /// Return an instance of the display helper tear off. /// /// internal virtual DisplayCells DisplayCells @@ -213,59 +296,59 @@ internal virtual DisplayCells DisplayCells } /// - /// singleton used for the default implementation. + /// Singleton used for the default implementation. /// NOTE: derived classes may chose to provide a different - /// implementation by overriding + /// implementation by overriding. /// protected static DisplayCells _displayCellsDefault = new DisplayCells(); } /// - /// helper class to provide line breaking (based on device width) + /// Helper class to provide line breaking (based on device width) /// and embedded newline processing - /// It needs to be provided with two callbacks for line processing + /// It needs to be provided with two callbacks for line processing. /// internal class WriteLineHelper { #region callbacks /// - /// delegate definition + /// Delegate definition. /// - /// string to write + /// String to write. internal delegate void WriteCallback(string s); /// - /// instance of the delegate previously defined - /// for line that has EXACTLY this.ncols characters + /// 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 + /// 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 + /// Construct an instance, given the two callbacks /// NOTE: if the underlying device treats the two cases as the - /// same, the same delegate can be passed twice + /// same, the same delegate can be passed twice. /// - /// true if we require line wrapping - /// delegate for WriteLine(), must ben non null - /// delegate for Write(), if null, use the first parameter - /// helper object for manipulating strings + /// True if we require line wrapping. + /// Delegate for WriteLine(), must ben non null. + /// Delegate for Write(), if null, use the first parameter. + /// Helper object for manipulating strings. internal WriteLineHelper(bool lineWrap, WriteCallback wlc, WriteCallback wc, DisplayCells displayCells) { if (wlc == null) - throw PSTraceSource.NewArgumentNullException("wlc"); + throw PSTraceSource.NewArgumentNullException(nameof(wlc)); if (displayCells == null) - throw PSTraceSource.NewArgumentNullException("displayCells"); + throw PSTraceSource.NewArgumentNullException(nameof(displayCells)); _displayCells = displayCells; _writeLineCall = wlc; @@ -274,20 +357,20 @@ internal WriteLineHelper(bool lineWrap, WriteCallback wlc, WriteCallback wc, Dis } /// - /// main entry point to process a line + /// Main entry point to process a line. /// - /// string to process - /// width of the device + /// String to process. + /// Width of the device. internal void WriteLine(string s, int cols) { WriteLineInternal(s, cols); } /// - /// internal helper, needed because it might make recursive calls to itself + /// Internal helper, needed because it might make recursive calls to itself. /// - /// string to process - /// width of the device + /// String to process. + /// Width of the device. private void WriteLineInternal(string val, int cols) { if (string.IsNullOrEmpty(val)) @@ -304,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]); @@ -333,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 @@ -348,20 +431,20 @@ 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 + /// TextWriter abstract class. /// - internal class TextWriterLineOutput : LineOutput + internal sealed class TextWriterLineOutput : LineOutput { #region ILineOutput methods /// - /// get the columns on the screen - /// for files, it is settable at creation time + /// Get the columns on the screen + /// for files, it is settable at creation time. /// internal override int ColumnNumber { @@ -373,8 +456,8 @@ internal override int ColumnNumber } /// - /// get the # of rows on the screen: for files - /// we return -1, meaning infinite + /// Get the # of rows on the screen: for files + /// we return -1, meaning infinite. /// internal override int RowNumber { @@ -386,12 +469,23 @@ internal override int RowNumber } /// - /// write a line by delegating to the writer underneath + /// Write a line by delegating to the writer underneath. /// /// 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); @@ -401,14 +495,15 @@ internal override void WriteLine(string s) _writer.WriteLine(s); } } + #endregion /// - /// initialization of the object. It must be called before - /// attempting any operation + /// Initialization of the object. It must be called before + /// attempting any operation. /// - /// TextWriter to write to - /// max columns widths for the text + /// TextWriter to write to. + /// Max columns widths for the text. internal TextWriterLineOutput(TextWriter writer, int columns) { _writer = writer; @@ -416,46 +511,46 @@ internal TextWriterLineOutput(TextWriter writer, int columns) } /// - /// initialization of the object. It must be called before - /// attempting any operation + /// Initialization of the object. It must be called before + /// attempting any operation. /// - /// TextWriter to write to - /// max columns widths for the text - /// false to add a newline to the end of the output string, true if not + /// TextWriter to write to. + /// Max columns widths for the text. + /// False to add a newline to the end of the output string, true if not. internal TextWriterLineOutput(TextWriter writer, int columns, bool suppressNewline) : this(writer, columns) { _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; } /// /// TextWriter to generate data for the Monad pipeline in a streaming fashion: - /// the provided callback will be called each time a line is written + /// the provided callback will be called each time a line is written. /// 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 /// - /// create an instance by passing a delegate + /// Create an instance by passing a delegate. /// - /// delegate to write to - /// culture for this TextWriter + /// Delegate to write to. + /// Culture for this TextWriter. internal StreamingTextWriter(WriteLineCallback writeCall, CultureInfo culture) : base(culture) { if (writeCall == null) - throw PSTraceSource.NewArgumentNullException("writeCall"); + throw PSTraceSource.NewArgumentNullException(nameof(writeCall)); _writeCall = writeCall; } @@ -472,14 +567,14 @@ public override void WriteLine(string s) #endregion /// - /// delegate definition + /// Delegate definition. /// - /// string to write + /// String to write. internal delegate void WriteLineCallback(string s); /// - /// instance of the delegate previously defined + /// 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 83fa285d323..988b1133748 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs @@ -1,40 +1,47 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// 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 { /// - /// class to write object properties in list form by using - /// the host screen interfaces + /// Class to write object properties in list form by using + /// the host screen interfaces. /// internal class ListWriter { /// - /// labels already padded with blanks, separator characters, etc. + /// Labels already padded with blanks, separator characters, etc. /// private string[] _propertyLabels; /// - /// display length of the property labels in the array (all the same length) + /// Display length of the property labels in the array (all the same length) /// private int _propertyLabelsDisplayLength = 0; /// - /// column width of the screen + /// Column width of the screen. /// private int _columnWidth = 0; /// - /// + /// A cached string builder used within this type to reduce creation of temporary strings. /// - /// names of the properties to display - /// column width of the screen - /// instance of the DisplayCells helper object + private readonly StringBuilder _cachedBuilder = new(); + + /// + /// + /// Names of the properties to display. + /// Column width of the screen. + /// Instance of the DisplayCells helper object. internal void Initialize(string[] propertyNames, int screenColumnWidth, DisplayCells dc) { _columnWidth = screenColumnWidth; @@ -60,12 +67,16 @@ 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 // cache the cell lengths for each property - int[] propertyNameCellCounts = new int[propertyNames.Length]; + Span propertyNameCellCounts = propertyNames.Length <= OutCommandInner.StackAllocThreshold ? stackalloc int[propertyNames.Length] : new int[propertyNames.Length]; for (int k = 0; k < propertyNames.Length; k++) { Debug.Assert(propertyNames[k] != null, "propertyNames[k] is null"); @@ -84,31 +95,33 @@ 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; } + _propertyLabelsDisplayLength += Separator.Length; } /// - /// write the values of the properties of an object + /// Write the values of the properties of an object. /// - /// array with the values in form of formatted strings - /// LineOutput interface to write to + /// Array with the values in form of formatted strings. + /// LineOutput interface to write to. internal void WriteProperties(string[] values, LineOutput lo) { if (_disabled) @@ -120,7 +133,7 @@ internal void WriteProperties(string[] values, LineOutput lo) // we have nothing, but we have to create an empty array valuesToPrint = new string[_propertyLabels.Length]; for (int k = 0; k < _propertyLabels.Length; k++) - valuesToPrint[k] = ""; + valuesToPrint[k] = string.Empty; } else if (values.Length < _propertyLabels.Length) { @@ -131,7 +144,7 @@ internal void WriteProperties(string[] values, LineOutput lo) if (k < values.Length) valuesToPrint[k] = values[k]; else - valuesToPrint[k] = ""; + valuesToPrint[k] = string.Empty; } } else if (values.Length > _propertyLabels.Length) @@ -156,24 +169,23 @@ internal void WriteProperties(string[] values, LineOutput lo) } /// - /// helper, writing a single property to the screen. - /// It wraps the value of the property if it is tool long to fit + /// Helper, writing a single property to the screen. + /// It wraps the value of the property if it is tool long to fit. /// - /// index of property to write - /// string value of the property to write - /// LineOutput interface to write to + /// Index of property to write. + /// String value of the property to write. + /// LineOutput interface to write to. private void WriteProperty(int k, string propertyValue, LineOutput lo) { - if (propertyValue == null) - propertyValue = ""; + 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; } @@ -192,16 +203,15 @@ private void WriteProperty(int k, string propertyValue, LineOutput lo) } /// - /// internal helper to split a line that is too long to fit and pad it to the left - /// with a given string + /// Internal helper to split a line that is too long to fit and pad it to the left + /// with a given string. /// - /// string to add to the left - /// line to print - /// LineOuput to write to + /// String to add to the left. + /// Line to print. + /// LineOutput to write to. private void WriteSingleLineHelper(string prependString, string line, LineOutput lo) { - if (line == null) - line = ""; + line ??= string.Empty; // compute the width of the field for the value string (in screen cells) int fieldCellCount = _columnWidth - _propertyLabelsDisplayLength; @@ -209,37 +219,69 @@ 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()); } } /// - /// set to true when the width of the screen is too small to do anything useful + /// Set to true when the width of the screen is too small to do anything useful. /// private bool _disabled = false; private const string Separator = " : "; /// - /// minimum width for the property label field + /// Minimum width for the property label field. /// private const int MinLabelWidth = 1; /// - /// minimum width for the property value field + /// Minimum width for the property value field. /// private const int MinFieldWidth = 1; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs b/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs index cd4adf2c332..a551ef89bed 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/OutputManager.cs @@ -1,30 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Collections.Specialized; using System.Collections.Generic; +using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// inner command class used to manage the sub pipelines + /// Inner command class used to manage the sub pipelines /// it determines which command should process the incoming objects /// based on the object type /// - /// This class is the implementation class for out-console and out-file + /// This class is the implementation class for out-console and out-file. /// internal sealed class OutputManagerInner : ImplementationCommandBase { #region tracer [TraceSource("format_out_OutputManagerInner", "OutputManagerInner")] - internal static PSTraceSource tracer = PSTraceSource.GetTracer("format_out_OutputManagerInner", "OutputManagerInner"); + internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("format_out_OutputManagerInner", "OutputManagerInner"); #endregion tracer - #region LineOutput internal LineOutput LineOutput { @@ -46,8 +44,8 @@ internal LineOutput LineOutput #endregion /// - /// handler for processing each object coming through the pipeline - /// it forwards the call to the pipeline manager object + /// Handler for processing each object coming through the pipeline + /// it forwards the call to the pipeline manager object. /// internal override void ProcessRecord() { @@ -88,30 +86,26 @@ internal override void ProcessRecord() } /// - /// handler for processing shut down. It forwards the call to the - /// pipeline manager object + /// Handler for processing shut down. It forwards the call to the + /// pipeline manager object. /// 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; } } /// - /// make sure we dispose of the sub pipeline manager + /// Make sure we dispose of the sub pipeline manager. /// protected override void InternalDispose() { @@ -123,48 +117,46 @@ protected override void InternalDispose() } } - /// - /// instance of the pipeline manager object + /// Instance of the pipeline manager object. /// private SubPipelineManager _mgr = null; /// - /// True if the cmdlet has been stopped + /// True if the cmdlet has been stopped. /// private bool _isStopped = false; /// - /// Lock object + /// Lock object. /// - private object _syncRoot = new object(); + private readonly object _syncRoot = new object(); } /// - /// object managing the sub-pipelines that execute + /// Object managing the sub-pipelines that execute /// different output commands (or different instances of the /// default one) /// internal sealed class SubPipelineManager : IDisposable { /// - /// entry defining a command to be run in a separate pipeline + /// Entry defining a command to be run in a separate pipeline. /// private sealed class CommandEntry : IDisposable { /// - /// instance of pipeline wrapper object + /// Instance of pipeline wrapper object. /// internal CommandWrapper command = new CommandWrapper(); /// - /// /// - /// ETS type name of the object to process - /// true if there is a match + /// ETS type name of the object to process. + /// True if there is a match. internal bool AppliesToType(string typeName) { - foreach (String s in _applicableTypes) + foreach (string s in _applicableTypes) { if (string.Equals(s, typeName, StringComparison.OrdinalIgnoreCase)) return true; @@ -174,7 +166,7 @@ internal bool AppliesToType(string typeName) } /// - /// just dispose of the inner command wrapper + /// Just dispose of the inner command wrapper. /// public void Dispose() { @@ -186,16 +178,16 @@ public void Dispose() } /// - /// ordered list of ETS type names this object is handling + /// Ordered list of ETS type names this object is handling. /// - private StringCollection _applicableTypes = new StringCollection(); + private readonly StringCollection _applicableTypes = new StringCollection(); } /// - /// Initialize the pipeline manager before any object is processed + /// Initialize the pipeline manager before any object is processed. /// - /// LineOutput to pass to the child pipelines - /// ExecutionContext to pass to the child pipelines + /// LineOutput to pass to the child pipelines. + /// ExecutionContext to pass to the child pipelines. internal void Initialize(LineOutput lineOutput, ExecutionContext context) { _lo = lineOutput; @@ -203,9 +195,9 @@ internal void Initialize(LineOutput lineOutput, ExecutionContext context) } /// - /// hard wired registration helper for specialized types + /// Hard wired registration helper for specialized types. /// - /// ExecutionContext to pass to the child pipeline + /// ExecutionContext to pass to the child pipeline. private void InitializeCommandsHardWired(ExecutionContext context) { // set the default handler @@ -216,10 +208,10 @@ private void InitializeCommandsHardWired(ExecutionContext context) additional types. Adding a handler here would cause a new sub-pipeline to be created. - For example, the following line would add a new handler named "out-foobar" - to be invoked when the incoming object type is "MyNamespace.Whatever.FooBar" + For example, the following line would add a new handler named "out-example" + to be invoked when the incoming object type is "MyNamespace.Whatever.Example" - RegisterCommandForTypes (context, "out-foobar", new string[] { "MyNamespace.Whatever.FooBar" }); + RegisterCommandForTypes (context, "out-example", new string[] { "MyNamespace.Whatever.Example" }); And the method can be like this: private void RegisterCommandForTypes (ExecutionContext context, string commandName, Type commandType, string[] types) @@ -239,11 +231,11 @@ private void RegisterCommandForTypes (ExecutionContext context, string commandNa } /// - /// register the default output command + /// Register the default output command. /// - /// ExecutionContext to pass to the child pipeline - /// name of the command to execute - /// Type of the command to execute + /// ExecutionContext to pass to the child pipeline. + /// Name of the command to execute. + /// Type of the command to execute. private void RegisterCommandDefault(ExecutionContext context, string commandName, Type commandType) { CommandEntry ce = new CommandEntry(); @@ -254,9 +246,9 @@ private void RegisterCommandDefault(ExecutionContext context, string commandName } /// - /// process an incoming parent pipeline object + /// Process an incoming parent pipeline object. /// - /// pipeline object to process + /// Pipeline object to process. internal void Process(PSObject so) { // select which pipeline should handle the object @@ -269,7 +261,7 @@ internal void Process(PSObject so) } /// - /// shut down the child pipelines + /// Shut down the child pipelines. /// internal void ShutDown() { @@ -302,11 +294,11 @@ public void Dispose() } /// - /// it selects the applicable out command (it can be the default one) - /// to process the current pipeline object + /// It selects the applicable out command (it can be the default one) + /// to process the current pipeline object. /// - /// pipeline object to be processed - /// applicable command entry + /// Pipeline object to be processed. + /// Applicable command entry. private CommandEntry GetActiveCommandEntry(PSObject so) { string typeName = PSObjectHelper.PSObjectIsOfExactType(so.InternalTypeNames); @@ -323,12 +315,12 @@ private CommandEntry GetActiveCommandEntry(PSObject so) private LineOutput _lo = null; /// - /// list of command entries, each with a set of applicable types + /// 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 + /// Default command entry to be executed when all type matches fail. /// private CommandEntry _defaultCommandEntry = new CommandEntry(); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs b/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs index 46c15e02e59..333b0b42689 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs @@ -1,25 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Diagnostics; using System.Collections.Generic; +using System.Diagnostics; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// queue to provide sliding window capabilities for auto size functionality + /// Queue to provide sliding window capabilities for auto size functionality /// It provides caching capabilities (either the first N objects in a group /// or all the objects in a group) /// internal sealed class OutputGroupQueue { /// - /// create a grouping cache + /// Create a grouping cache. /// - /// notification callback to be called when the desired number of objects is reached - /// max number of objects to be cached + /// Notification callback to be called when the desired number of objects is reached. + /// Max number of objects to be cached. internal OutputGroupQueue(FormattedObjectsCache.ProcessCachedGroupNotification callBack, int objectCount) { _notificationCallBack = callBack; @@ -27,26 +26,24 @@ internal OutputGroupQueue(FormattedObjectsCache.ProcessCachedGroupNotification c } /// - /// create a time-bounded grouping cache + /// Create a time-bounded grouping cache. /// - /// notification callback to be called when the desired number of objects is reached - /// max amount of time to cache of objects + /// Notification callback to be called when the desired number of objects is reached. + /// Max amount of time to cache of objects. internal OutputGroupQueue(FormattedObjectsCache.ProcessCachedGroupNotification callBack, TimeSpan groupingDuration) { _notificationCallBack = callBack; _groupingDuration = groupingDuration; } - /// - /// add an object to the cache + /// Add an object to the cache. /// - /// object to add - /// objects the cache needs to return. It can be null + /// Object to add. + /// 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; @@ -122,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() @@ -141,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); @@ -152,9 +146,9 @@ private void Notify() } /// - /// remove a single object from the queue + /// Remove a single object from the queue. /// - /// object retrieved, null if queue is empty + /// Object retrieved, null if queue is empty. internal PacketInfoData Dequeue() { if (_queue.Count == 0) @@ -164,63 +158,63 @@ internal PacketInfoData Dequeue() } /// - /// queue to store the currently cached objects + /// 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. + /// Number of objects to compute the best fit. /// Zero: all the objects - /// a positive number N: use the first N + /// 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 + /// 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 + /// Reference kept to be used during notification. /// private FormatStartData _formatStartData = null; /// - /// state flag to signal we are queuing + /// State flag to signal we are queuing. /// private bool _processingGroup = false; /// - /// current object count + /// Current object count. /// private int _currentObjectCount = 0; } /// - /// facade class managing the front end and the autosize cache + /// Facade class managing the front end and the autosize cache. /// internal sealed class FormattedObjectsCache { /// - /// delegate to allow notifications when the autosize queue is about to be drained + /// Delegate to allow notifications when the autosize queue is about to be drained. /// - /// current Fs control message - /// enumeration of PacketInfoData objects + /// Current Fs control message. + /// Enumeration of PacketInfoData objects. internal delegate void ProcessCachedGroupNotification(FormatStartData formatStartData, List objects); /// - /// decide right away if we need a front end cache (e.g. printing) + /// Decide right away if we need a front end cache (e.g. printing) /// - /// if true, create a front end cache object + /// If true, create a front end cache object. internal FormattedObjectsCache(bool cacheFrontEnd) { if (cacheFrontEnd) @@ -228,10 +222,10 @@ internal FormattedObjectsCache(bool cacheFrontEnd) } /// - /// if needed, add a back end autosize (grouping) cache + /// If needed, add a back end autosize (grouping) cache. /// - /// notification callback to be called when the desired number of objects is reached - /// max number of objects to be cached + /// Notification callback to be called when the desired number of objects is reached. + /// Max number of objects to be cached. internal void EnableGroupCaching(ProcessCachedGroupNotification callBack, int objectCount) { if (callBack != null) @@ -239,10 +233,10 @@ internal void EnableGroupCaching(ProcessCachedGroupNotification callBack, int ob } /// - /// if needed, add a back end autosize (grouping) cache + /// If needed, add a back end autosize (grouping) cache. /// - /// notification callback to be called when the desired number of objects is reached - /// max amount of time to cache of objects + /// Notification callback to be called when the desired number of objects is reached. + /// Max amount of time to cache of objects. internal void EnableGroupCaching(ProcessCachedGroupNotification callBack, TimeSpan groupingDuration) { if (callBack != null) @@ -250,11 +244,11 @@ internal void EnableGroupCaching(ProcessCachedGroupNotification callBack, TimeSp } /// - /// add an object to the cache. the behavior depends on the object added, the - /// objects already in the cache and the cache settings + /// Add an object to the cache. the behavior depends on the object added, the + /// objects already in the cache and the cache settings. /// - /// object to add - /// list of objects the cache is flushing + /// Object to add. + /// List of objects the cache is flushing. internal List Add(PacketInfoData o) { // if neither there, pass thru @@ -277,9 +271,9 @@ internal List Add(PacketInfoData o) } /// - /// remove all the objects from the cache + /// Remove all the objects from the cache. /// - /// all the objects that were in the cache + /// All the objects that were in the cache. internal List Drain() { // if neither there,we did not cache at all @@ -326,16 +320,14 @@ internal List Drain() return retVal; } - /// - /// front end queue (if present, cache ALL, if not, bypass) + /// Front end queue (if present, cache ALL, if not, bypass) /// - private Queue _frontEndQueue; + private readonly Queue _frontEndQueue; /// - /// back end grouping queue + /// Back end grouping queue. /// 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 27cfa6d5a8c..49b9005a88e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/TableWriter.cs @@ -1,30 +1,36 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; +using Microsoft.PowerShell.Commands.Internal.Format; + namespace Microsoft.PowerShell.Commands.Internal.Format { internal class TableWriter { /// - /// Information about each column boundaries + /// 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 + /// Class containing information about the tabular layout. /// - private class ScreenInfo + private sealed class ScreenInfo { internal int screenColumns = 0; + internal int screenRows = 0; internal const int separatorCharacterCount = 1; @@ -37,6 +43,8 @@ private class ScreenInfo private ScreenInfo _si; + private List _header; + internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenColumns) { if (stringLen <= 0 || screenColumns < 1) @@ -68,49 +76,37 @@ internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenC } } - /// - /// Initialize the table specifying the width of each column + /// Initialize the table specifying the width of each column. /// - /// left margin indentation - /// number of character columns on the screen - /// array of specified column widths - /// array of alignment flags - /// if true, suppress header printing - internal void Initialize(int leftMarginIndent, int screenColumns, int[] columnWidths, int[] alignment, bool suppressHeader) + /// Left margin indentation. + /// 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, ReadOnlySpan headerMatchesProperty, bool suppressHeader, int screenRows = int.MaxValue) { - //Console.WriteLine(" 1 2 3 4 5 6 7"); - //Console.WriteLine("01234567890123456789012345678901234567890123456789012345678901234567890123456789"); - - if (screenColumns == int.MaxValue) - { - try - { - screenColumns = System.Console.WindowWidth; - } - catch - { - screenColumns = 120; - } - } - if (leftMarginIndent < 0) { leftMarginIndent = 0; } + if (screenColumns - leftMarginIndent < ScreenInfo.minimumScreenColumns) { _disabled = true; return; } - _startColumn = leftMarginIndent; + _startColumn = leftMarginIndent; _hideHeader = suppressHeader; // make sure the column widths are correct; if not, take appropriate action - ColumnWidthManager manager = new ColumnWidthManager(screenColumns - leftMarginIndent, - ScreenInfo.minimumColumnWidth, - ScreenInfo.separatorCharacterCount); + ColumnWidthManager manager = new ColumnWidthManager( + screenColumns - leftMarginIndent, + ScreenInfo.minimumColumnWidth, + ScreenInfo.separatorCharacterCount); manager.CalculateColumnWidths(columnWidths); @@ -135,6 +131,7 @@ internal void Initialize(int leftMarginIndent, int screenColumns, int[] columnWi // now set the run time data structures _si = new ScreenInfo(); _si.screenColumns = screenColumns; + _si.screenRows = screenRows; _si.columnInfo = new ColumnInfo[columnWidths.Length]; int startCol = _startColumn; @@ -144,31 +141,48 @@ internal void Initialize(int leftMarginIndent, int screenColumns, int[] columnWi _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; - //Console.WriteLine("start = {0} width = {1}", si.columnInfo[k].startCol, si.columnInfo[k].width); } } - internal void GenerateHeader(string[] values, LineOutput lo) + internal int GenerateHeader(string[] values, LineOutput lo) { - if (_disabled) - return; + if (_disabled || _hideHeader) + { + return 0; + } + else if (_header != null) + { + string style = PSStyle.Instance.Formatting.TableHeader; + string reset = PSStyle.Instance.Reset; - if (_hideHeader) - return; + foreach (string line in _header) + { + lo.WriteLine(line); + } + + return _header.Count; + } + + _header = new List(); // generate the row with the header labels - GenerateRow(values, lo, true, null, lo.DisplayCells); + GenerateRow(values, lo, true, null, lo.DisplayCells, _header, isHeader: true); // generate an array of "--" as header markers below // the column header labels string[] breakLine = new string[values.Length]; - for (int k = 0; k < _si.columnInfo.Length; k++) + for (int k = 0; k < breakLine.Length; k++) { // the column can be hidden if (_si.columnInfo[k].width <= 0) { - breakLine[k] = ""; + breakLine[k] = string.Empty; continue; } // the title can be larger than the width @@ -182,58 +196,69 @@ internal void GenerateHeader(string[] values, LineOutput lo) // NOTE: we can do this because "-" is a single cell character // on all devices. If changed to some other character, this assumption // would be invalidated - breakLine[k] = new string('-', count); + breakLine[k] = StringUtil.DashPadding(count); } - GenerateRow(breakLine, lo, false, null, lo.DisplayCells); + + GenerateRow(breakLine, lo, false, null, lo.DisplayCells, _header, isHeader: true); + return _header.Count; } - internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, int[] alignment, DisplayCells dc) + 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; - int[] currentAlignment = new int[cols]; + Span currentAlignment = cols <= OutCommandInner.StackAllocThreshold ? stackalloc int[cols] : new int[cols]; - if (alignment == null) + if (alignment.IsEmpty) { - for (int i = 0; i < cols; i++) + for (int i = 0; i < currentAlignment.Length; i++) { currentAlignment[i] = _si.columnInfo[i].alignment; } } else { - for (int i = 0; i < cols; i++) + 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) { - string[] lines = GenerateTableRow(values, currentAlignment, lo.DisplayCells); - - for (int k = 0; k < lines.Length; k++) + foreach (string line in GenerateTableRow(values, currentAlignment, lo.DisplayCells, isHeader)) { - lo.WriteLine(lines[k]); + generatedRows?.Add(line); + lo.WriteLine(line); } } else { - lo.WriteLine(GenerateRow(values, currentAlignment, dc)); + string line = GenerateRow(values, currentAlignment, dc, isHeader); + generatedRows?.Add(line); + lo.WriteLine(line); } } - private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells ds) + private string[] GenerateTableRow(string[] values, ReadOnlySpan alignment, DisplayCells ds, bool isHeader) { // select the active columns (skip hidden ones) - int[] validColumnArray = new int[_si.columnInfo.Length]; + Span validColumnArray = _si.columnInfo.Length <= OutCommandInner.StackAllocThreshold ? stackalloc int[_si.columnInfo.Length] : new int[_si.columnInfo.Length]; int validColumnCount = 0; for (int k = 0; k < _si.columnInfo.Length; k++) { @@ -244,21 +269,28 @@ private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells } if (validColumnCount == 0) + { return null; - + } StringCollection[] scArray = new StringCollection[validColumnCount]; + bool addPadding = true; for (int k = 0; k < scArray.Length; k++) { + // for the last column, don't pad it with trailing spaces + if (k == scArray.Length - 1) + { + addPadding = false; + } + // obtain a set of tokens for each field - scArray[k] = GenerateMultiLineRowField(values[validColumnArray[k]], validColumnArray[k], - alignment[validColumnArray[k]], ds); + 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 if (k > 0) { - // skipping the first ones, add a separator for catenation + // skipping the first ones, add a separator for concatenation for (int j = 0; j < scArray[k].Count; j++) { scArray[k][j] = StringUtil.Padding(ScreenInfo.separatorCharacterCount) + scArray[k][j]; @@ -283,70 +315,148 @@ private string[] GenerateTableRow(string[] values, int[] alignment, DisplayCells 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: + // + // Long Header2 Head + // Head er3 + // er + // ---- ------- ---- + // 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++) + { + for (int col = 0; col < scArray.Length; col++) + { + if (scArray[col].Count > row) + { + lastColWithContent[row] = col; + } + } } // add padding for the columns that are shorter for (int col = 0; col < scArray.Length; col++) { - int paddingBlanks = _si.columnInfo[validColumnArray[col]].width; - if (col > 0) - paddingBlanks += ScreenInfo.separatorCharacterCount; - else + int paddingBlanks = 0; + + // don't pad if last column + if (col < scArray.Length - 1) { - paddingBlanks += _startColumn; + paddingBlanks = _si.columnInfo[validColumnArray[col]].width; + if (col > 0) + { + paddingBlanks += ScreenInfo.separatorCharacterCount; + } + else + { + paddingBlanks += _startColumn; + } } + int paddingEntries = screenRows - scArray[col].Count; if (paddingEntries > 0) { - for (int j = 0; j < paddingEntries; j++) + for (int row = screenRows - paddingEntries; row < screenRows; row++) { - scArray[col].Add(StringUtil.Padding(paddingBlanks)); + // if the column is beyond the last column with content, just use empty string + if (col > lastColWithContent[row]) + { + scArray[col].Add(string.Empty); + } + else + { + scArray[col].Add(StringUtil.Padding(paddingBlanks)); + } } } } // finally, build an array of strings string[] rows = new string[screenRows]; - for (int row = 0; row < rows.Length; row++) + for (int row = 0; row < screenRows; row++) { StringBuilder sb = new StringBuilder(); - // for a give row, walk the columns + + // for a given row, walk the columns for (int col = 0; col < scArray.Length; col++) { - sb.Append(scArray[col][row]); + 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) + { + value = value.TrimEnd(); + } + + if (isHeader) + { + 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); + } } + rows[row] = sb.ToString(); } + return rows; } - private StringCollection GenerateMultiLineRowField(string val, int k, int alignment, DisplayCells dc) + private StringCollection GenerateMultiLineRowField(string val, int k, int alignment, DisplayCells dc, bool addPadding) { StringCollection sc = StringManipulationHelper.GenerateLines(dc, val, _si.columnInfo[k].width, _si.columnInfo[k].width); - // if length is shorter, do some padding - for (int col = 0; col < sc.Count; col++) + if (addPadding || alignment == TextAlignment.Right || alignment == TextAlignment.Center) { - if (dc.Length(sc[col]) < _si.columnInfo[k].width) - sc[col] = GenerateRowField(sc[col], _si.columnInfo[k].width, alignment, dc); + // if length is shorter, do some padding + for (int col = 0; col < sc.Count; col++) + { + if (dc.Length(sc[col]) < _si.columnInfo[k].width) + sc[col] = GenerateRowField(sc[col], _si.columnInfo[k].width, alignment, dc, addPadding); + } } + return sc; } - - private string GenerateRow(string[] values, int[] alignment, DisplayCells dc) + private string GenerateRow(string[] values, ReadOnlySpan alignment, DisplayCells dc, bool isHeader) { StringBuilder sb = new StringBuilder(); + bool addPadding = true; for (int k = 0; k < _si.columnInfo.Length; k++) { + // don't pad the last column + if (k == _si.columnInfo.Length - 1) + { + addPadding = false; + } + if (_si.columnInfo[k].width <= 0) { // skip columns that are not at least a single character wide continue; } - int newRowIndex = sb.Length; // NOTE: the following padding operations assume that we // pad with a blank (or any character that ALWAYS maps to a single screen cell @@ -362,24 +472,35 @@ private string GenerateRow(string[] values, int[] alignment, DisplayCells dc) sb.Append(StringUtil.Padding(_startColumn)); } } - sb.Append(GenerateRowField(values[k], _si.columnInfo[k].width, alignment[k], dc)); + + 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(PSStyle.Instance.Reset); + } } + return sb.ToString(); } - - private static string GenerateRowField(string val, int width, int alignment, DisplayCells dc) + private static string GenerateRowField(string val, int width, int alignment, DisplayCells dc, bool addPadding) { // make sure the string does not have any embedded in it - string s = StringManipulationHelper.TruncateAtNewLine(val) ?? ""; - - string currentValue = s; - int currentValueDisplayLength = dc.Length(currentValue); + string s = StringManipulationHelper.TruncateAtNewLine(val); + 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) { @@ -387,6 +508,7 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { s = StringUtil.Padding(padCount) + s; } + break; case TextAlignment.Center: @@ -395,15 +517,24 @@ private static string GenerateRowField(string val, int width, int alignment, Dis int padLeft = padCount / 2; int padRight = padCount - padLeft; - s = StringUtil.Padding(padLeft) + s + StringUtil.Padding(padRight); + s = StringUtil.Padding(padLeft) + s; + if (addPadding) + { + s += StringUtil.Padding(padRight); + } } + break; default: { - // left align is the default - s += StringUtil.Padding(padCount); + if (addPadding) + { + // left align is the default + s += StringUtil.Padding(padCount); + } } + break; } } @@ -411,7 +542,7 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { // the string is longer than the width of the column // truncate and add ellipsis if it's too long - int truncationDisplayLength = width - ellipsis.Length; + int truncationDisplayLength = width - EllipsisSize; if (truncationDisplayLength > 0) { @@ -421,58 +552,48 @@ 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 = ellipsis + s; + s = s.VtSubstring( + startOffset: dc.TruncateHead(s, truncationDisplayLength), + prependStr: PSObjectHelper.EllipsisStr, + appendStr: null); } - break; - case TextAlignment.Center: - { - // get from "abcdef" to "a..." - s = s.Substring(0, dc.GetHeadSplitLength(s, truncationDisplayLength)); - s += ellipsis; - } break; default: { // left align is the default // get from "abcdef" to "a..." - s = s.Substring(0, dc.GetHeadSplitLength(s, truncationDisplayLength)); - s += ellipsis; + s = s.VtSubstring( + startOffset: 0, + length: dc.TruncateTail(s, truncationDisplayLength), + prependStr: null, + appendStr: PSObjectHelper.EllipsisStr); } + break; } } 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); + s = s.VtSubstring(startOffset: dc.TruncateHead(s, width)); } - break; - case TextAlignment.Center: - { - // get from "abcdef" to "a" - s = s.Substring(0, dc.GetHeadSplitLength(s, len)); - } break; default: { // 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; } } @@ -486,34 +607,42 @@ private static string GenerateRowField(string val, int width, int alignment, Dis { return s; } - // we have to pad - System.Diagnostics.Debug.Assert(finalValueDisplayLength == width - 1, "padding is not correct"); + switch (alignment) { case TextAlignment.Right: { s = " " + s; } + break; case TextAlignment.Center: { - s += " "; + if (addPadding) + { + s += " "; + } } + break; default: { // left align is the default - s += " "; + if (addPadding) + { + s += " "; + } } + break; } return s; } - private const string ellipsis = "..."; + private const int EllipsisSize = 1; private bool _disabled = false; diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs index 37b42baf771..ea200bcb5f5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs @@ -1,26 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Text; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using System.Globalization; using System.Reflection; +using System.Text; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// class containing miscellaneous helpers to deal with - /// PSObject manipulation + /// Class containing miscellaneous helpers to deal with + /// PSObject manipulation. /// internal static class PSObjectHelper { - internal const string ellipses = "..."; + #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) { @@ -33,95 +39,20 @@ internal static bool PSObjectIsEnum(Collection typeNames) { if (typeNames.Count < 2 || string.IsNullOrEmpty(typeNames[1])) return false; - return String.Equals(typeNames[1], "System.Enum", StringComparison.Ordinal); - } - - /// - /// WriteError adds a note property called WriteErrorStream to the error - /// record wrapped in an PSObject and set its value to true. When F and O detects - /// this note exists and its value is set to true, WriteErrorLine will be used - /// to emit the error; otherwise, F and O actions are regular. - /// - /// - /// - internal static bool IsWriteErrorStream(PSObject so) - { - return IsStreamType(so, "WriteErrorStream"); - } - - /// - /// Checks for WriteWarningStream property on object, indicating that - /// it is a warning stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteWarningStream(PSObject so) - { - return IsStreamType(so, "WriteWarningStream"); - } - - /// - /// Checks for WriteVerboseStream property on object, indicating that - /// it is a verbose stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteVerboseStream(PSObject so) - { - return IsStreamType(so, "WriteVerboseStream"); - } - - /// - /// Checks for WriteDebugStream property on object, indicating that - /// it is a debug stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteDebugStream(PSObject so) - { - return IsStreamType(so, "WriteDebugStream"); - } - - /// - /// Checks for WriteInformationStream property on object, indicating that - /// it is an informational stream. Used by F and O. - /// - /// - /// - internal static bool IsWriteInformationStream(PSObject so) - { - return IsStreamType(so, "WriteInformationStream"); - } - - internal static bool IsStreamType(PSObject so, string streamFlag) - { - try - { - PSPropertyInfo streamProperty = so.Properties[streamFlag]; - if (streamProperty != null && streamProperty.Value is bool) - { - return (bool)streamProperty.Value; - } - - return false; - } - catch (ExtendedTypeSystemException) - { - return false; - } + return string.Equals(typeNames[1], "System.Enum", StringComparison.Ordinal); } /// /// Retrieve the display name. It looks for a well known property and, - /// if not found, it uses some heuristics to get a "close" match + /// if not found, it uses some heuristics to get a "close" match. /// - /// shell object to process - /// expression factory to create MshExpression - /// resolved MshExpression; null if no match was found - internal static MshExpression GetDisplayNameExpression(PSObject target, MshExpressionFactory expressionFactory) + /// Shell object to process. + /// Expression factory to create PSPropertyExpression. + /// Resolved PSPropertyExpression; null if no match was found. + internal static PSPropertyExpression GetDisplayNameExpression(PSObject target, PSPropertyExpressionFactory expressionFactory) { // first try to get the expression from the object (types.ps1xml data) - MshExpression expressionFromObject = GetDefaultNameExpression(target); + PSPropertyExpression expressionFromObject = GetDefaultNameExpression(target); if (expressionFromObject != null) { return expressionFromObject; @@ -136,8 +67,8 @@ internal static MshExpression GetDisplayNameExpression(PSObject target, MshExpre // go over the patterns, looking for the first match foreach (string pattern in knownPatterns) { - MshExpression ex = new MshExpression(pattern); - List exprList = ex.ResolveNames(target); + PSPropertyExpression ex = new PSPropertyExpression(pattern); + List exprList = ex.ResolveNames(target); while ((exprList.Count > 0) && ( exprList[0].ToString().Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase) || @@ -160,20 +91,20 @@ internal static MshExpression GetDisplayNameExpression(PSObject target, MshExpre } /// - /// it gets the display name value + /// It gets the display name value. /// - /// shell object to process - /// expression factory to create MshExpression - /// MshExpressionResult if successful; null otherwise - internal static MshExpressionResult GetDisplayName(PSObject target, MshExpressionFactory expressionFactory) + /// Shell object to process. + /// Expression factory to create PSPropertyExpression. + /// PSPropertyExpressionResult if successful; null otherwise. + internal static PSPropertyExpressionResult GetDisplayName(PSObject target, PSPropertyExpressionFactory expressionFactory) { // get the expression to evaluate - MshExpression ex = GetDisplayNameExpression(target, expressionFactory); + PSPropertyExpression ex = GetDisplayNameExpression(target, expressionFactory); if (ex == null) return null; // evaluate the expression - List resList = ex.GetValues(target); + List resList = ex.GetValues(target); if (resList.Count == 0 || resList[0].Exception != null) { @@ -187,11 +118,10 @@ internal static MshExpressionResult GetDisplayName(PSObject target, MshExpressio /// /// This is necessary only to consider IDictionaries as IEnumerables, since LanguagePrimitives.GetEnumerable does not. /// - /// object to extract the IEnumerable from + /// 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; } @@ -204,9 +134,9 @@ internal static IEnumerable GetEnumerable(object obj) return LanguagePrimitives.GetEnumerable(obj); } - private static string GetSmartToStringDisplayName(object x, MshExpressionFactory expressionFactory) + private static string GetSmartToStringDisplayName(object x, PSPropertyExpressionFactory expressionFactory) { - MshExpressionResult r = PSObjectHelper.GetDisplayName(PSObjectHelper.AsPSObject(x), expressionFactory); + PSPropertyExpressionResult r = PSObjectHelper.GetDisplayName(PSObjectHelper.AsPSObject(x), expressionFactory); if ((r != null) && (r.Exception == null)) { return PSObjectHelper.AsPSObject(r.Result).ToString(); @@ -217,7 +147,7 @@ private static string GetSmartToStringDisplayName(object x, MshExpressionFactory } } - private static string GetObjectName(object x, MshExpressionFactory expressionFactory) + private static string GetObjectName(object x, PSPropertyExpressionFactory expressionFactory) { string objName; @@ -237,7 +167,7 @@ private static string GetObjectName(object x, MshExpressionFactory expressionFac } else { - MethodInfo toStringMethod = x.GetType().GetMethod("ToString", PSTypeExtensions.EmptyTypes); + MethodInfo toStringMethod = x.GetType().GetMethod("ToString", Type.EmptyTypes); // TODO:CORECLR double check with CORE CLR that x.GetType() == toStringMethod.ReflectedType // Check if the given object "x" implements "toString" method. Do that by comparing "DeclaringType" which 'Gets the class that declares this member' and the object type if (toStringMethod.DeclaringType == x.GetType()) @@ -246,10 +176,10 @@ private static string GetObjectName(object x, MshExpressionFactory expressionFac } else { - MshExpressionResult r = PSObjectHelper.GetDisplayName(PSObjectHelper.AsPSObject(x), expressionFactory); + PSPropertyExpressionResult r = PSObjectHelper.GetDisplayName(PSObjectHelper.AsPSObject(x), expressionFactory); if ((r != null) && (r.Exception == null)) { - objName = PSObjectHelper.AsPSObject(r.Result).ToString(); ; + objName = PSObjectHelper.AsPSObject(r.Result).ToString(); } else { @@ -270,18 +200,19 @@ private static string GetObjectName(object x, MshExpressionFactory expressionFac } /// - /// helper to convert an PSObject into a string + /// Helper to convert an PSObject into a string /// It takes into account enumerations (use display name) /// - /// shell object to process - /// expression factory to create MshExpression - /// limit on IEnumerable enumeration - /// stores errors during string conversion - /// string representation - internal static string SmartToString(PSObject so, MshExpressionFactory expressionFactory, int enumerationLimit, StringFormatError formatErrorObject) + /// Shell object to process. + /// 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, bool formatFloat = false) { if (so == null) - return ""; + return string.Empty; try { @@ -289,15 +220,14 @@ internal static string SmartToString(PSObject so, MshExpressionFactory expressio 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)) { @@ -305,13 +235,15 @@ internal static string SmartToString(PSObject so, MshExpressionFactory expressio { throw new PipelineStoppedException(); } + if (enumerationLimit >= 0) { if (enumCount == enumerationLimit) { - sb.Append(ellipses); + sb.Append(Ellipsis); break; } + enumCount++; } @@ -319,6 +251,7 @@ internal static string SmartToString(PSObject so, MshExpressionFactory expressio { sb.Append(", "); } + sb.Append(GetObjectName(be.Current, expressionFactory)); if (first) first = false; @@ -332,13 +265,15 @@ internal static string SmartToString(PSObject so, MshExpressionFactory expressio { throw new PipelineStoppedException(); } + if (enumerationLimit >= 0) { if (enumCount == enumerationLimit) { - sb.Append(ellipses); + sb.Append(Ellipsis); break; } + enumCount++; } @@ -346,33 +281,54 @@ internal static string SmartToString(PSObject so, MshExpressionFactory expressio { sb.Append(", "); } + sb.Append(GetObjectName(x, expressionFactory)); if (first) first = false; } } } - 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; formatErrorObject.exception = e; } - return ""; + + return string.Empty; } } - private static readonly PSObject s_emptyPSObject = new PSObject(""); + private static readonly PSObject s_emptyPSObject = new PSObject(string.Empty); internal static PSObject AsPSObject(object obj) { @@ -380,63 +336,69 @@ internal static PSObject AsPSObject(object obj) } /// - /// format an object using a provided format string directive + /// Format an object using a provided format string directive. /// - /// format directive object to use - /// object to format - /// limit on IEnumerable enumeration - /// formatting error object, if present - /// expression factory to create MshExpression - /// string representation + /// Format directive object to use. + /// Object to format. + /// Limit on IEnumerable enumeration. + /// Formatting error object, if present. + /// Expression factory to create PSPropertyExpression. + /// String representation. internal static string FormatField(FieldFormattingDirective directive, object val, int enumerationLimit, - StringFormatError formatErrorObject, MshExpressionFactory expressionFactory) + 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 ""; + // 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) { - Diagnostics.Assert(null != so, "Shell Object to process cannot be null"); + Diagnostics.Assert(so != null, "Shell object to process cannot be null"); var typeNames = so.InternalTypeNames; Collection typeNamesWithoutDeserializedPrefix = Deserializer.MaskDeserializationPrefix(typeNames); - if (null == typeNamesWithoutDeserializedPrefix) + if (typeNamesWithoutDeserializedPrefix == null) { return null; } @@ -452,36 +414,33 @@ private static PSMemberSet MaskDeserializedAndGetStandardMembers(PSObject so) return members[TypeTable.PSStandardMembers] as PSMemberSet; } - private static List GetDefaultPropertySet(PSMemberSet standardMembersSet) + private static List GetDefaultPropertySet(PSMemberSet standardMembersSet) { - if (null != standardMembersSet) + if (standardMembersSet != null && standardMembersSet.Members[TypeTable.DefaultDisplayPropertySet] is PSPropertySet defaultDisplayPropertySet) { - PSPropertySet defaultDisplayPropertySet = standardMembersSet.Members[TypeTable.DefaultDisplayPropertySet] as PSPropertySet; - if (null != defaultDisplayPropertySet) + 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 MshExpression(prop)); - } + retVal.Add(new PSPropertyExpression(prop)); } - return retVal; } + + return retVal; } - return new List(); + return new List(); } /// - /// helper to retrieve the default property set of a shell object + /// Helper to retrieve the default property set of a shell object. /// - /// shell object to process - /// resolved expression; empty list if not found - internal static List GetDefaultPropertySet(PSObject so) + /// Shell object to process. + /// Resolved expression; empty list if not found. + internal static List GetDefaultPropertySet(PSObject so) { - List retVal = GetDefaultPropertySet(so.PSStandardMembers); + List retVal = GetDefaultPropertySet(so.PSStandardMembers); if (retVal.Count == 0) { retVal = GetDefaultPropertySet(MaskDeserializedAndGetStandardMembers(so)); @@ -490,70 +449,67 @@ internal static List GetDefaultPropertySet(PSObject so) return retVal; } - private static MshExpression GetDefaultNameExpression(PSMemberSet standardMembersSet) + private static PSPropertyExpression GetDefaultNameExpression(PSMemberSet standardMembersSet) { - if (null != standardMembersSet) + if (standardMembersSet != null && standardMembersSet.Members[TypeTable.DefaultDisplayProperty] is PSNoteProperty defaultDisplayProperty) { - PSNoteProperty defaultDisplayProperty = standardMembersSet.Members[TypeTable.DefaultDisplayProperty] as PSNoteProperty; - if (null != defaultDisplayProperty) + 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 MshExpression(expressionString); - } + // invalid data, the PSObject is empty + return null; + } + else + { + return new PSPropertyExpression(expressionString); } } return null; } - private static MshExpression GetDefaultNameExpression(PSObject so) + private static PSPropertyExpression GetDefaultNameExpression(PSObject so) { - MshExpression retVal = GetDefaultNameExpression(so.PSStandardMembers) ?? + PSPropertyExpression retVal = GetDefaultNameExpression(so.PSStandardMembers) ?? GetDefaultNameExpression(MaskDeserializedAndGetStandardMembers(so)); return retVal; } /// - /// helper to retrieve the value of an MshExpression and to format it + /// Helper to retrieve the value of an PSPropertyExpression and to format it. /// - /// shell object to process - /// limit on IEnumerable enumeration - /// expression to use for retrieval - /// format directive to use for formatting + /// Shell object to process. + /// Limit on IEnumerable enumeration. + /// Expression to use for retrieval. + /// Format directive to use for formatting. /// - /// expression factory to create MshExpression - /// not null if an error condition arose - /// formatted string + /// Expression factory to create PSPropertyExpression. + /// Not null if an error condition arose. + /// Formatted string. internal static string GetExpressionDisplayValue( PSObject so, int enumerationLimit, - MshExpression ex, + PSPropertyExpression ex, FieldFormattingDirective directive, StringFormatError formatErrorObject, - MshExpressionFactory expressionFactory, - out MshExpressionResult result) + PSPropertyExpressionFactory expressionFactory, + out PSPropertyExpressionResult result) { result = null; - List resList = ex.GetValues(so); + List resList = ex.GetValues(so); if (resList.Count == 0) { - return ""; + return string.Empty; } result = resList[0]; if (result.Exception != null) { - return ""; + return string.Empty; } + return PSObjectHelper.FormatField(directive, result.Result, enumerationLimit, formatErrorObject, expressionFactory); } @@ -565,7 +521,7 @@ internal static string GetExpressionDisplayValue( internal static bool ShouldShowComputerNameProperty(PSObject so) { bool result = false; - if (null != so) + if (so != null) { try { @@ -574,7 +530,7 @@ internal static bool ShouldShowComputerNameProperty(PSObject so) // if computer name property exists then this must be a remote object. see // if it can be displayed. - if ((null != computerNameProperty) && (null != showComputerNameProperty)) + if ((computerNameProperty != null) && (showComputerNameProperty != null)) { LanguagePrimitives.TryConvertTo(showComputerNameProperty.Value, out result); } @@ -600,12 +556,11 @@ internal abstract class FormattingError internal object sourceObject; } - internal sealed class MshExpressionError : FormattingError + internal sealed class PSPropertyExpressionError : FormattingError { - internal MshExpressionResult result; + internal PSPropertyExpressionResult result; } - internal sealed class StringFormatError : FormattingError { internal string formatString; @@ -615,9 +570,9 @@ internal sealed class StringFormatError : FormattingError internal delegate ScriptBlock CreateScriptBlockFromString(string scriptBlockString); /// - /// helper class to create MshExpression's from format.ps1xml data structures + /// Helper class to create PSPropertyExpression's from format.ps1xml data structures. /// - internal sealed class MshExpressionFactory + internal sealed class PSPropertyExpressionFactory { /// internal void VerifyScriptBlockText(string scriptText) @@ -626,31 +581,31 @@ internal void VerifyScriptBlockText(string scriptText) } /// - /// create an expression from an expression token + /// Create an expression from an expression token. /// - /// expression token to use - /// constructed expression + /// Expression token to use. + /// Constructed expression. /// - internal MshExpression CreateFromExpressionToken(ExpressionToken et) + internal PSPropertyExpression CreateFromExpressionToken(ExpressionToken et) { return CreateFromExpressionToken(et, null); } /// - /// create an expression from an expression token + /// Create an expression from an expression token. /// - /// expression token to use - /// The context from which the file was loaded - /// constructed expression + /// Expression token to use. + /// The context from which the file was loaded. + /// Constructed expression. /// - internal MshExpression CreateFromExpressionToken(ExpressionToken et, DatabaseLoadingInfo loadingInfo) + internal PSPropertyExpression CreateFromExpressionToken(ExpressionToken et, DatabaseLoadingInfo loadingInfo) { if (et.isScriptBlock) { // we cache script blocks from expression tokens if (_expressionCache != null) { - MshExpression value; + PSPropertyExpression value; if (_expressionCache.TryGetValue(et, out value)) { // got a hit on the cache, just return @@ -659,7 +614,7 @@ internal MshExpression CreateFromExpressionToken(ExpressionToken et, DatabaseLoa } else { - _expressionCache = new Dictionary(); + _expressionCache = new Dictionary(); } bool isFullyTrusted = false; @@ -679,7 +634,7 @@ internal MshExpression CreateFromExpressionToken(ExpressionToken et, DatabaseLoa sb.LanguageMode = PSLanguageMode.FullLanguage; } - MshExpression ex = new MshExpression(sb); + PSPropertyExpression ex = new PSPropertyExpression(sb); _expressionCache.Add(et, ex); @@ -687,10 +642,9 @@ internal MshExpression CreateFromExpressionToken(ExpressionToken et, DatabaseLoa } // we do not cache if it is just a property name - return new MshExpression(et.expressionValue); + return new PSPropertyExpression(et.expressionValue); } - private Dictionary _expressionCache; + 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 956892e4592..e7d7b9e75ed 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs @@ -1,22 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Management.Automation.Internal; using System.Collections; using System.Collections.Generic; -using System.Text; using System.Diagnostics.CodeAnalysis; - +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Text; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// normalized parameter class to be constructed from the command line parameters + /// Normalized parameter class to be constructed from the command line parameters /// using the metadata information provided by an instance of CommandParameterDefinition - /// it's basically the hash table with the normalized values + /// it's basically the hash table with the normalized values. /// internal class MshParameter { @@ -34,6 +32,7 @@ internal object GetEntry(string key) internal class NameEntryDefinition : HashtableEntryDefinition { internal const string NameEntryKey = "name"; + internal NameEntryDefinition() : base(NameEntryKey, new string[] { FormatParameterDefinitionKeys.LabelEntryKey }, new Type[] { typeof(string) }, false) { @@ -41,9 +40,9 @@ internal NameEntryDefinition() } /// - /// metadata base class for hashtable entry definitions + /// Metadata base class for hashtable entry definitions /// it contains the key name and the allowable types - /// it also provides hooks for type expansion + /// it also provides hooks for type expansion. /// internal class HashtableEntryDefinition { @@ -115,7 +114,7 @@ internal virtual object ComputeDefaultValue() } /// - /// metadata abstract base class to contain hash entries definitions + /// Metadata abstract base class to contain hash entries definitions. /// internal abstract class CommandParameterDefinition { @@ -130,19 +129,19 @@ internal CommandParameterDefinition() internal virtual MshParameter CreateInstance() { return new MshParameter(); } /// - /// for a key name, verify it is a legal entry: + /// For a key name, verify it is a legal entry: /// 1. it must match (partial match allowed) /// 2. it must be unambiguous (if partial match) - /// If an error condition occurs, an exception will be thrown + /// If an error condition occurs, an exception will be thrown. /// - /// key to verify - /// invocation context for error reporting - /// matching hash table entry + /// Key to verify. + /// Invocation context for error reporting. + /// Matching hash table entry. /// internal HashtableEntryDefinition MatchEntry(string keyName, TerminatingErrorContext invocationContext) { if (string.IsNullOrEmpty(keyName)) - PSTraceSource.NewArgumentNullException("keyName"); + PSTraceSource.NewArgumentNullException(nameof(keyName)); HashtableEntryDefinition matchingEntry = null; for (int k = 0; k < this.hashEntries.Count; k++) @@ -179,7 +178,7 @@ internal static bool FindPartialMatch(string key, string normalizedKey) if (key.Length < normalizedKey.Length) { // shorter, could be an abbreviation - if (string.Equals(key, normalizedKey.Substring(0, key.Length), StringComparison.OrdinalIgnoreCase)) + if (key.AsSpan().Equals(normalizedKey.AsSpan(0, key.Length), StringComparison.OrdinalIgnoreCase)) { // found abbreviation return true; @@ -195,7 +194,6 @@ internal static bool FindPartialMatch(string key, string normalizedKey) return false; } - #region Error Processing private static void ProcessAmbiguousKey(TerminatingErrorContext invocationContext, @@ -221,17 +219,16 @@ private static void ProcessIllegalKey(TerminatingErrorContext invocationContext, internal List hashEntries = new List(); } - /// - /// engine to process a generic object[] from the command line and + /// Engine to process a generic object[] from the command line and /// generate a list of MshParameter objects , given the metadata provided by - /// a class derived from CommandParameterDefinition + /// a class derived from CommandParameterDefinition. /// internal sealed class ParameterProcessor { #region tracer [TraceSource("ParameterProcessor", "ParameterProcessor")] - internal static PSTraceSource tracer = PSTraceSource.GetTracer("ParameterProcessor", "ParameterProcessor"); + internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ParameterProcessor", "ParameterProcessor"); #endregion tracer internal static void ThrowParameterBindingException(TerminatingErrorContext invocationContext, @@ -248,7 +245,6 @@ internal static void ThrowParameterBindingException(TerminatingErrorContext invo invocationContext.ThrowTerminatingError(errorRecord); } - internal ParameterProcessor(CommandParameterDefinition p) { _paramDef = p; @@ -337,7 +333,6 @@ private Hashtable VerifyHashTable(IDictionary hash, TerminatingErrorContext invo // now the key is verified, need to check the type bool matchType = false; - if (def.AllowedTypes == null || def.AllowedTypes.Length == 0) { // we match on any type, it will be up to the entry to further check @@ -365,6 +360,7 @@ private Hashtable VerifyHashTable(IDictionary hash, TerminatingErrorContext invo // bad type error ProcessIllegalHashTableKeyValue(invocationContext, currentStringKey, e.Value.GetType(), def.AllowedTypes); } + retVal.Add(def.KeyName, e.Value); } @@ -412,7 +408,6 @@ private void VerifyAndNormalizeParameter(MshParameter parameter, #region Error Processing - private static void ProcessUnknownParameterType(TerminatingErrorContext invocationContext, object actualObject, Type[] allowedTypes) { string allowedTypesList = CatenateTypeArray(allowedTypes); @@ -428,6 +423,7 @@ private static void ProcessUnknownParameterType(TerminatingErrorContext invocati msg = StringUtil.Format(FormatAndOut_MshParameter.NullParameterTypeError, allowedTypesList); } + ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyUnknownType", msg); } @@ -493,7 +489,6 @@ private static void ProcessMissingMandatoryKey(TerminatingErrorContext invocatio ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyMandatoryEntry", msg); } - #endregion #region Utilities @@ -505,28 +500,30 @@ private static string CatenateTypeArray(Type[] arr) { strings[k] = arr[k].FullName; } + return CatenateStringArray(strings); } 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) { sb.Append(", "); } + 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 e571c5a00a4..a373f18ee81 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs @@ -1,45 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Management.Automation; namespace Microsoft.PowerShell.Commands.Internal.Format { /// - /// helper class to hold a resolved expression and its - /// originating parameter + /// Helper class to hold a resolved expression and its + /// originating parameter. /// internal sealed class MshResolvedExpressionParameterAssociation { #region tracer [TraceSource("MshResolvedExpressionParameterAssociation", "MshResolvedExpressionParameterAssociation")] - internal static PSTraceSource tracer = PSTraceSource.GetTracer("MshResolvedExpressionParameterAssociation", + internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("MshResolvedExpressionParameterAssociation", "MshResolvedExpressionParameterAssociation"); #endregion tracer - internal MshResolvedExpressionParameterAssociation(MshParameter parameter, MshExpression expression) + internal MshResolvedExpressionParameterAssociation(MshParameter parameter, PSPropertyExpression expression) { if (expression == null) - throw PSTraceSource.NewArgumentNullException("expression"); + throw PSTraceSource.NewArgumentNullException(nameof(expression)); OriginatingParameter = parameter; ResolvedExpression = expression; } - internal MshExpression ResolvedExpression { get; } + internal PSPropertyExpression ResolvedExpression { get; } internal MshParameter OriginatingParameter { get; } } - internal static class AssociationManager { internal static List SetupActiveProperties(List rawMshParameterList, - PSObject target, MshExpressionFactory expressionFactory) + PSObject target, PSPropertyExpressionFactory expressionFactory) { // check if we received properties from the command line if (rawMshParameterList != null && rawMshParameterList.Count > 0) @@ -48,7 +46,7 @@ internal static List SetupActivePrope } // we did not get any properties: - //try to get properties from the default property set of the object + // try to get properties from the default property set of the object List activeAssociationList = AssociationManager.ExpandDefaultPropertySet(target, expressionFactory); if (activeAssociationList.Count > 0) @@ -58,7 +56,7 @@ internal static List SetupActivePrope if (PSObjectHelper.ShouldShowComputerNameProperty(target)) { activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, - new MshExpression(RemotingConstants.ComputerNameNoteProperty))); + new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); } return activeAssociationList; @@ -79,8 +77,8 @@ internal static List ExpandTableParam foreach (MshParameter par in parameters) { - MshExpression expression = par.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - List expandedExpressionList = expression.ResolveNames(target); + PSPropertyExpression expression = par.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as PSPropertyExpression; + List expandedExpressionList = expression.ResolveNames(target); if (!expression.HasWildCardCharacters && expandedExpressionList.Count == 0) { @@ -88,7 +86,7 @@ internal static List ExpandTableParam retVal.Add(new MshResolvedExpressionParameterAssociation(par, expression)); } - foreach (MshExpression ex in expandedExpressionList) + foreach (PSPropertyExpression ex in expandedExpressionList) { retVal.Add(new MshResolvedExpressionParameterAssociation(par, ex)); } @@ -103,10 +101,10 @@ internal static List ExpandParameters foreach (MshParameter par in parameters) { - MshExpression expression = par.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as MshExpression; - List expandedExpressionList = expression.ResolveNames(target); + PSPropertyExpression expression = par.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey) as PSPropertyExpression; + List expandedExpressionList = expression.ResolveNames(target); - foreach (MshExpression ex in expandedExpressionList) + foreach (PSPropertyExpression ex in expandedExpressionList) { retVal.Add(new MshResolvedExpressionParameterAssociation(par, ex)); } @@ -115,12 +113,12 @@ internal static List ExpandParameters return retVal; } - internal static List ExpandDefaultPropertySet(PSObject target, MshExpressionFactory expressionFactory) + internal static List ExpandDefaultPropertySet(PSObject target, PSPropertyExpressionFactory expressionFactory) { List retVal = new List(); - List expandedExpressionList = PSObjectHelper.GetDefaultPropertySet(target); + List expandedExpressionList = PSObjectHelper.GetDefaultPropertySet(target); - foreach (MshExpression ex in expandedExpressionList) + foreach (PSPropertyExpression ex in expandedExpressionList) { retVal.Add(new MshResolvedExpressionParameterAssociation(null, ex)); } @@ -144,6 +142,7 @@ private static List GetPropertyNamesFromView(PSObject source, PSMemberVi { retVal.Add(member.Name); } + return retVal; } @@ -160,13 +159,13 @@ internal static List ExpandAll(PSObje List retVal = new List(); foreach (string property in displayedProperties) { - if (!duplicatesFinder.ContainsKey(property)) + if (duplicatesFinder.TryAdd(property, null)) { - duplicatesFinder.Add(property, null); - MshExpression expr = new MshExpression(property, true); + PSPropertyExpression expr = new PSPropertyExpression(property, true); retVal.Add(new MshResolvedExpressionParameterAssociation(null, expr)); } } + return retVal; } @@ -182,7 +181,7 @@ internal static List ExpandAll(PSObje /// internal static void HandleComputerNameProperties(PSObject so, List activeAssociationList) { - if (null != so.Properties[RemotingConstants.ShowComputerNameNoteProperty]) + if (so.Properties[RemotingConstants.ShowComputerNameNoteProperty] != null) { // always remove PSShowComputerName for the display. This is an internal property // that should never be visible to the user. @@ -201,7 +200,7 @@ internal static void HandleComputerNameProperties(PSObject so, List In this case we want to show // PSComputerName - if ((null != so.Properties[RemotingConstants.ComputerNameNoteProperty]) && + if ((so.Properties[RemotingConstants.ComputerNameNoteProperty] != null) && (!PSObjectHelper.ShouldShowComputerNameProperty(so))) { foreach (MshResolvedExpressionParameterAssociation cpProp in activeAssociationList) @@ -226,4 +225,3 @@ internal static void HandleComputerNameProperties(PSObject so, List - /// class to hold results - /// NOTE: we should make it an PSObject eventually + /// Class that represents the results from evaluating a PSPropertyExpression against an object. /// - internal class MshExpressionResult + public class PSPropertyExpressionResult { - internal MshExpressionResult(object res, MshExpression re, Exception e) + /// + /// Create a property expression result containing the original object, matching property expression + /// and any exception generated during the match process. + /// + public PSPropertyExpressionResult(object res, PSPropertyExpression re, Exception e) { Result = res; ResolvedExpression = re; Exception = e; } - internal object Result { get; } = null; + /// + /// The value of the object property matched by this property expression. + /// + public object Result { get; } = null; - internal MshExpression ResolvedExpression { get; } = null; + /// + /// The original property expression fully resolved. + /// + public PSPropertyExpression ResolvedExpression { get; } = null; - internal Exception Exception { get; } = null; + /// + /// Any exception thrown while evaluating the expression. + /// + public Exception Exception { get; } = null; } - - internal class MshExpression + /// + /// PSPropertyExpression class. This class is used to get the names and/or values of properties + /// on an object. A property expression can be constructed using either a wildcard expression string + /// or a scriptblock to use to get the property value. + /// + public class PSPropertyExpression { /// - /// constructor + /// Constructor. /// - /// expression + /// Expression. /// - internal MshExpression(string s) + public PSPropertyExpression(string s) : this(s, false) { } /// - /// constructor + /// Create a property expression with a wildcard pattern. /// - /// expression - /// true if no further attempts should be made to resolve wildcards + /// Property name pattern to match. + /// if no further attempts should be made to resolve wildcards. /// - internal MshExpression(string s, bool isResolved) + public PSPropertyExpression(string s, bool isResolved) { if (string.IsNullOrEmpty(s)) { - throw PSTraceSource.NewArgumentNullException("s"); + throw PSTraceSource.NewArgumentNullException(nameof(s)); } + _stringValue = s; _isResolved = isResolved; } /// - /// constructor + /// Create a property expression with a ScriptBlock. /// - /// + /// ScriptBlock to evaluate when retrieving the property value from an object. /// - internal MshExpression(ScriptBlock scriptBlock) + public PSPropertyExpression(ScriptBlock scriptBlock) { if (scriptBlock == null) { - throw PSTraceSource.NewArgumentNullException("scriptBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } + Script = scriptBlock; } + /// + /// The ScriptBlock for this expression to use when matching. + /// public ScriptBlock Script { get; } = null; + /// + /// ToString() implementation for the property expression. + /// public override string ToString() { if (Script != null) @@ -84,12 +110,20 @@ public override string ToString() return _stringValue; } - internal List ResolveNames(PSObject target) + /// + /// Resolve the names matched by the expression. + /// + /// The object to apply the expression against. + public List ResolveNames(PSObject target) { return ResolveNames(target, true); } - internal bool HasWildCardCharacters + /// + /// Indicates if the pattern has wildcard characters in it. If the supplied pattern was + /// a scriptblock, this will be false. + /// + public bool HasWildCardCharacters { get { @@ -99,9 +133,14 @@ internal bool HasWildCardCharacters } } - internal List ResolveNames(PSObject target, bool expand) + /// + /// Resolve the names matched by the expression. + /// + /// The object to apply the expression against. + /// If the matched properties are property sets, expand them. + public List ResolveNames(PSObject target, bool expand) { - List retVal = new List(); + List retVal = new List(); if (_isResolved) { @@ -112,31 +151,60 @@ internal List ResolveNames(PSObject target, bool expand) if (Script != null) { // script block, just add it to the list and be done - MshExpression ex = new MshExpression(Script); + PSPropertyExpression ex = new PSPropertyExpression(Script); ex._isResolved = true; retVal.Add(ex); return retVal; } + // If the object passed in is a hashtable, then turn it into a PSCustomObject so + // that property expressions can work on it. + var wrappedTarget = IfHashtableWrapAsPSCustomObject(target, out bool wasHashtable); + // we have a string value - IEnumerable members = null; + IEnumerable members; if (HasWildCardCharacters) { // get the members first: this will expand the globbing on each parameter - members = target.Members.Match(_stringValue, - PSMemberTypes.Properties | PSMemberTypes.PropertySet); + members = wrappedTarget.Members.Match( + _stringValue, + PSMemberTypes.Properties | PSMemberTypes.PropertySet | PSMemberTypes.Dynamic); + + // if target was a hashtable and no result is found from the keys, then use property value if available + if (wasHashtable && !members.Any()) + { + members = target.Members.Match( + _stringValue, + PSMemberTypes.Properties | PSMemberTypes.PropertySet | PSMemberTypes.Dynamic); + } } else { // we have no globbing: try an exact match, because this is quicker. - PSMemberInfo x = target.Members[_stringValue]; + PSMemberInfo x = wrappedTarget.Members[_stringValue]; + + if (x == null) + { + if (wasHashtable) + { + x = target.Members[_stringValue]; + } + else if (wrappedTarget.BaseObject is System.Dynamic.IDynamicMetaObjectProvider) + { + // We could check if GetDynamicMemberNames includes the name... but + // GetDynamicMemberNames is only a hint, not a contract, so we'd want + // to attempt the binding whether it's in there or not. + x = new PSDynamicMember(_stringValue); + } + } List temp = new List(); if (x != null) { temp.Add(x); } + members = temp; } @@ -147,8 +215,7 @@ internal 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) { @@ -167,59 +234,70 @@ internal List ResolveNames(PSObject target, bool expand) } } } - continue; } // it can be a property - if (member is PSPropertyInfo) + else if (member is PSPropertyInfo) + { + temporaryMemberList.Add(member); + } + // it can be a dynamic member + else if (member is PSDynamicMember) { temporaryMemberList.Add(member); } } - Hashtable hash = new Hashtable(); + var allMembers = new HashSet(); // build the list of unique values: remove the possible duplicates // from property set expansion foreach (PSMemberInfo m in temporaryMemberList) { - if (!hash.ContainsKey(m.Name)) + if (!allMembers.Contains(m.Name)) { - MshExpression ex = new MshExpression(m.Name); + PSPropertyExpression ex = new PSPropertyExpression(m.Name); ex._isResolved = true; retVal.Add(ex); - hash.Add(m.Name, null); + allMembers.Add(m.Name); } } return retVal; } - internal List GetValues(PSObject target) + /// + /// Gets the values of the object properties matched by this expression. + /// + /// The object to match against. + public List GetValues(PSObject target) { return GetValues(target, true, true); } - internal List GetValues(PSObject target, bool expand, bool eatExceptions) + /// + /// Gets the values of the object properties matched by this expression. + /// + /// The object to match against. + /// If the matched properties are parameter sets, expand them. + /// If true, any exceptions that occur during the match process are ignored. + public List GetValues(PSObject target, bool expand, bool eatExceptions) { - List retVal = new List(); + List retVal = new List(); // process the script case if (Script != null) { - MshExpression scriptExpression = new MshExpression(Script); - MshExpressionResult r = scriptExpression.GetValue(target, eatExceptions); + PSPropertyExpression scriptExpression = new PSPropertyExpression(Script); + PSPropertyExpressionResult r = scriptExpression.GetValue(target, eatExceptions); retVal.Add(r); return retVal; } - // process the expression - List resolvedExpressionList = this.ResolveNames(target, expand); - - foreach (MshExpression re in resolvedExpressionList) + foreach (PSPropertyExpression resolvedName in ResolveNames(target, expand)) { - MshExpressionResult r = re.GetValue(target, eatExceptions); - retVal.Add(r); + PSPropertyExpressionResult result = resolvedName.GetValue(target, eatExceptions); + retVal.Add(result); } return retVal; @@ -227,11 +305,13 @@ internal List GetValues(PSObject target, bool expand, bool #region Private Members - private MshExpressionResult GetValue(PSObject target, bool eatExceptions) + private CallSite> _getValueDynamicSite; + + private PSPropertyExpressionResult GetValue(PSObject target, bool eatExceptions) { try { - object result; + object result = null; if (Script != null) { @@ -241,25 +321,27 @@ private MshExpressionResult GetValue(PSObject target, bool eatExceptions) dollarUnder: target, input: AutomationNull.Value, scriptThis: AutomationNull.Value, - args: Utils.EmptyArray()); + args: Array.Empty()); } else { - PSMemberInfo member = target.Properties[_stringValue]; - if (member == null) - { - return new MshExpressionResult(null, this, null); - } - result = member.Value; + _getValueDynamicSite ??= + CallSite>.Create( + PSGetMemberBinder.Get( + _stringValue, + classScope: (Type)null, + @static: false)); + + result = _getValueDynamicSite.Target.Invoke(_getValueDynamicSite, target); } - return new MshExpressionResult(result, this, null); + return new PSPropertyExpressionResult(result, this, null); } catch (RuntimeException e) { if (eatExceptions) { - return new MshExpressionResult(null, this, e); + return new PSPropertyExpressionResult(null, this, e); } else { @@ -268,12 +350,67 @@ private MshExpressionResult GetValue(PSObject target, bool eatExceptions) } } + private static PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out bool wrapped) + { + wrapped = false; + + // If the object passed in is a hashtable, then turn it into a PSCustomObject so + // that property expressions can work on it. + if (PSObject.Base(target) is Hashtable targetAsHash) + { + wrapped = true; + return (PSObject)(LanguagePrimitives.ConvertPSObjectToType( + targetAsHash, + typeof(PSObject), + recursion: false, + formatProvider: null, + ignoreUnknownMembers: true)); + } + + return target; + } // 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 5d4c05dd51f..7d7a993d539 100644 --- a/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs +++ b/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs @@ -1,20 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation; + using Microsoft.PowerShell.Commands.Internal.Format; namespace Microsoft.PowerShell.Commands { /// - /// implementation for the format-default command + /// Implementation for the format-default command. /// [Cmdlet(VerbsCommon.Format, "Default")] public class FormatDefaultCommand : FrontEndCommandBase { /// - /// constructor to set the inner command + /// Constructor to set the inner command. /// public FormatDefaultCommand() { @@ -22,5 +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 48081074267..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. All rights reserved. ---********************************************************************/ - -// NOTE: define this if you want to test the output on US machine and ASCII -// characters -//#define TEST_MULTICELL_ON_SINGLE_CELL_LOCALE +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; @@ -17,141 +14,87 @@ 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 + /// 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 (HostException) + + 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 (HostException) + catch { - //thrown when external host rawui is not implemented, in which case - //we will fallback to the default value. + // 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) { try { return _rawUserInterface.LengthInBufferCells(character); } - catch (HostException) + catch { - //thrown when external host rawui is not implemented, in which case - //we will fallback to the default value. + // 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; } - - - /// - /// Implementation of the LineOutput interface on top of Console and RawConsole + /// Implementation of the LineOutput interface on top of Console and RawConsole. /// internal sealed class ConsoleLineOutput : LineOutput { #region tracer [TraceSource("ConsoleLineOutput", "ConsoleLineOutput")] - internal static PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleLineOutput", "ConsoleLineOutput"); + 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 + /// The # of columns is just the width of the screen buffer (not the /// width of the window) /// /// @@ -172,17 +115,18 @@ internal override int ColumnNumber { return _forceNewLine ? raw.BufferSize.Width - 1 : raw.BufferSize.Width; } - catch (HostException) + catch { - //thrown when external host rawui is not implemented, in which case - //we will fallback to the default value. + // thrown when external host rawui is not implemented, in which case + // we will fallback to the default value. } + return _forceNewLine ? _fallbackRawConsoleColumnNumber - 1 : _fallbackRawConsoleColumnNumber; } } /// - /// the # of rows is the # of rows visible in the window (and not the # of + /// The # of rows is the # of rows visible in the window (and not the # of /// rows in the screen buffer) /// /// @@ -197,22 +141,24 @@ internal override int RowNumber { return raw.WindowSize.Height; } - catch (HostException) + catch { - //thrown when external host rawui is not implemented, in which case - //we will fallback to the default value. + // thrown when external host rawui is not implemented, in which case + // we will fallback to the default value. } + return _fallbackRawConsoleRowNumber; } } /// - /// write a line to the output device + /// Write a line to the output device. /// - /// line to write + /// Line to write. internal override void WriteLine(string s) { CheckStopProcessing(); + // delegate the action to the helper, // that will properly break the string into // screen lines @@ -224,53 +170,52 @@ 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 /// - /// constructor for the ConsoleLineOutput + /// Constructor for the ConsoleLineOutput. /// - /// PSHostUserInterface to wrap - /// true if we require prompting for page breaks - /// error context to throw exceptions - internal ConsoleLineOutput(PSHostUserInterface hostConsole, bool paging, TerminatingErrorContext errorContext) + /// PSHostUserInterface to wrap. + /// True if we require prompting for page breaks. + /// Error context to throw exceptions. + internal ConsoleLineOutput(PSHost host, bool paging, TerminatingErrorContext errorContext) { - if (hostConsole == null) - throw PSTraceSource.NewArgumentNullException("hostConsole"); + if (host == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(host)); + } + if (errorContext == null) - throw PSTraceSource.NewArgumentNullException("errorContext"); + { + 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 @@ -288,14 +233,11 @@ internal ConsoleLineOutput(PSHostUserInterface hostConsole, bool paging, Termina } /// - /// callback to be called when ILineOutput.WriteLine() is called by WriteLineHelper + /// Callback to be called when ILineOutput.WriteLine() is called by WriteLineHelper. /// - /// string to write + /// 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); @@ -333,16 +275,13 @@ private void OnWriteLine(string s) } /// - /// callback to be called when ILineOutput.Write() is called by WriteLineHelper + /// Callback to be called when ILineOutput.Write() is called by WriteLineHelper /// This is called when the WriteLineHelper needs to write a line whose length - /// is the same as the width of the screen buffer + /// is the same as the width of the screen buffer. /// - /// string to write + /// 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: @@ -370,7 +309,7 @@ private void OnWrite(string s) } /// - /// called when a line was written to console + /// Called when a line was written to console. /// private void LineWrittenEvent() { @@ -401,12 +340,14 @@ private void LineWrittenEvent() // reset the counter, since we are starting a new page _linesWritten = 0; } + break; case PromptHandler.PromptResponse.NextLine: { // roll back the counter by one, since we allow one more line _linesWritten--; } + break; case PromptHandler.PromptResponse.Quit: // 1021203-2005/05/09-JonN @@ -418,7 +359,7 @@ private void LineWrittenEvent() } /// - /// check if we need to put out a prompt + /// Check if we need to put out a prompt. /// /// true if we need to prompt private bool NeedToPrompt @@ -452,29 +393,29 @@ private bool NeedToPrompt #region Private Members /// - /// object to manage prompting + /// Object to manage prompting. /// - private class PromptHandler + private sealed class PromptHandler { /// - /// prompt handler with the given prompt + /// Prompt handler with the given prompt. /// - /// prompt string to be used - /// the Cmdlet using this prompt handler + /// Prompt string to be used. + /// The Cmdlet using this prompt handler. internal PromptHandler(string s, ConsoleLineOutput cmdlet) { if (string.IsNullOrEmpty(s)) - throw PSTraceSource.NewArgumentNullException("s"); + throw PSTraceSource.NewArgumentNullException(nameof(s)); _promptString = s; _callingCmdlet = cmdlet; } /// - /// determine how many rows the prompt should take. + /// Determine how many rows the prompt should take. /// - /// current number of columns on the screen - /// string manipulation helper + /// Current number of columns on the screen. + /// String manipulation helper. /// internal int ComputePromptLines(DisplayCells displayCells, int cols) { @@ -484,7 +425,7 @@ internal int ComputePromptLines(DisplayCells displayCells, int cols) } /// - /// options returned by the PromptUser() call + /// Options returned by the PromptUser() call. /// internal enum PromptResponse { @@ -494,9 +435,9 @@ internal enum PromptResponse } /// - /// do the actual prompting + /// Do the actual prompting. /// - /// PSHostUserInterface instance to prompt to + /// PSHostUserInterface instance to prompt to. internal PromptResponse PromptUser(PSHostUserInterface console) { // NOTE: assume the values passed to ComputePromptLines are still valid @@ -538,70 +479,70 @@ internal PromptResponse PromptUser(PSHostUserInterface console) } /// - /// cached string(s) valid during a sequence of ComputePromptLines()/PromptUser() + /// Cached string(s) valid during a sequence of ComputePromptLines()/PromptUser() /// private StringCollection _actualPrompt; /// - /// prompt string as passed at initialization + /// Prompt string as passed at initialization. /// - private string _promptString; + private readonly string _promptString; /// - /// The cmdlet that uses this prompt helper + /// The cmdlet that uses this prompt helper. /// - private ConsoleLineOutput _callingCmdlet = null; + private readonly ConsoleLineOutput _callingCmdlet = null; } /// - /// flag to force new lines in CMD.EXE by limiting the + /// Flag to force new lines in CMD.EXE by limiting the /// 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; + /// Use this if IRawConsole is null; /// - private int _fallbackRawConsoleColumnNumber = 80; + private readonly int _fallbackRawConsoleColumnNumber = 80; /// - /// use this if IRawConsole is null; + /// 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 + /// 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; /// - /// flag to avoid reentrancy on prompting + /// Flag to avoid reentrancy on prompting. /// 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 + /// Msh host specific string manipulation helper. /// - private DisplayCells _displayCellsPSHost; + private readonly DisplayCells _displayCellsHost; /// - /// reference to error context to throw Msh exceptions + /// 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 823c5b5ea61..20fcf54e593 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs @@ -1,33 +1,31 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Internal; + +using Microsoft.PowerShell.Commands.Internal.Format; namespace Microsoft.PowerShell.Commands { - using System; - using System.Collections; - using System.Management.Automation; - using System.Management.Automation.Host; - using System.Management.Automation.Internal; - using Microsoft.PowerShell.Commands.Internal.Format; - - /// /// - /// Null sink to absorb pipeline output + /// Null sink to absorb pipeline output. /// - [CmdletAttribute("Out", "Null", SupportsShouldProcess = false, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113366", RemotingCapability = RemotingCapability.None)] + [Cmdlet("Out", "Null", SupportsShouldProcess = false, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096792", RemotingCapability = RemotingCapability.None)] public class OutNullCommand : PSCmdlet { /// - /// This parameter specifies the current pipeline object + /// 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 + /// Do nothing. /// protected override void ProcessRecord() { @@ -37,12 +35,12 @@ protected override void ProcessRecord() } /// - /// implementation for the out-default command + /// Implementation for the out-default command /// this command it implicitly inject by the /// powershell host at the end of the pipeline as the /// default sink (display to console screen) /// - [Cmdlet(VerbsData.Out, "Default", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113362", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsData.Out, "Default", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096486", RemotingCapability = RemotingCapability.None)] public class OutDefaultCommand : FrontEndCommandBase { /// @@ -51,11 +49,11 @@ 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; } /// - /// set inner command + /// Set inner command. /// public OutDefaultCommand() { @@ -63,18 +61,15 @@ public OutDefaultCommand() } /// - /// just hook up the LineOutput interface + /// Just hook up the LineOutput interface. /// 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; } @@ -90,12 +85,12 @@ protected override void BeginProcessing() if (Context.CurrentCommandProcessor.CommandRuntime.OutVarList != null) { - _outVarResults = new ArrayList(); + _outVarResults = new List(); } } /// - /// Process the OutVar, if set + /// Process the OutVar, if set. /// protected override void ProcessRecord() { @@ -108,14 +103,13 @@ protected override void ProcessRecord() // doesn't actually write pipeline objects. if (_outVarResults != null) { - Object inputObjectBase = PSObject.Base(InputObject); + 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); } @@ -134,10 +128,11 @@ protected override void EndProcessing() if ((_outVarResults != null) && (_outVarResults.Count > 0)) { Context.CurrentCommandProcessor.CommandRuntime.OutVarList.Clear(); - foreach (Object item in _outVarResults) + foreach (object item in _outVarResults) { Context.CurrentCommandProcessor.CommandRuntime.OutVarList.Add(item); } + _outVarResults = null; } @@ -145,7 +140,7 @@ protected override void EndProcessing() } /// - /// Revert transcription state on Dispose + /// Revert transcription state on Dispose. /// protected override void InternalDispose() { @@ -163,27 +158,27 @@ protected override void InternalDispose() } } - private ArrayList _outVarResults = null; + private List _outVarResults = null; private IDisposable _transcribeOnlyCookie = null; } /// - /// implementation for the out-host command + /// Implementation for the out-host command. /// - [Cmdlet(VerbsData.Out, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113365", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsData.Out, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096863", RemotingCapability = RemotingCapability.None)] public class OutHostCommand : FrontEndCommandBase { #region Command Line Parameters /// - /// non positional parameter to specify paging + /// Non positional parameter to specify paging. /// private bool _paging; #endregion /// - /// constructor of OutHostCommand + /// Constructor of OutHostCommand. /// public OutHostCommand() { @@ -191,27 +186,27 @@ public OutHostCommand() } /// - /// optional, non positional parameter to specify paging + /// Optional, non positional parameter to specify paging /// FALSE: names only - /// TRUE: full info + /// TRUE: full info. /// [Parameter] public SwitchParameter Paging { get { return _paging; } + set { _paging = value; } } /// - /// just hook up the LineOutput interface + /// Just hook up the LineOutput interface. /// 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(); } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs b/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs index a72e34844ab..f520df5787b 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -11,29 +10,29 @@ namespace Microsoft.PowerShell.Commands { /// - /// implementation for the out-lineoutput command + /// Implementation for the out-lineoutput command /// it provides a wrapper for the OutCommandInner class, - /// which is the general purpose output command + /// which is the general purpose output command. /// [Cmdlet(VerbsData.Out, "LineOutput")] public class OutLineOutputCommand : FrontEndCommandBase { /// - /// command line switch for ILineOutput communication channel + /// Command line switch for ILineOutput communication channel. /// /// [Parameter(Mandatory = true, Position = 0)] public object LineOutput { get { return _lineOutput; } + set { _lineOutput = value; } } private object _lineOutput = null; - /// - /// set inner command + /// Set inner command. /// public OutLineOutputCommand() { @@ -41,7 +40,6 @@ public OutLineOutputCommand() } /// - /// /// protected override void BeginProcessing() { @@ -55,6 +53,7 @@ protected override void BeginProcessing() { ProcessWrongTypeLineOutput(_lineOutput); } + ((OutCommandInner)this.implementation).LineOutput = lo; base.BeginProcessing(); @@ -91,7 +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 6b23a7f377c..f3e1d0dd9e8 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -1,25 +1,48 @@ - + - PowerShell Core's System.Management.Automation project - $(NoWarn);CS1570;CS1734 + PowerShell's System.Management.Automation project + $(NoWarn);CS1570;CS1734;CA1416;CA2022 System.Management.Automation + + + true + gen\SourceGenerated + + + + + + + + + + + + + + + - + + + - - - - - - + + + + + + - - + + + + @@ -27,97 +50,23 @@ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - @@ -129,16 +78,20 @@ - - portable - - - - $(DefineConstants);UNIX - - - - full - + + + + + + + + + + + + + + + diff --git a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs index 9247b13fff5..77328bf0257 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; +using System.Management.Automation; using System.Reflection; using System.Reflection.Emit; + using Microsoft.PowerShell.Cmdletization.Xml; -using System.Management.Automation; namespace Microsoft.PowerShell.Cmdletization { @@ -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) { @@ -42,7 +42,7 @@ internal static void Compile(EnumMetadataEnum enumMetadata) } else { - underlyingType = typeof(Int32); + underlyingType = typeof(int); } ModuleBuilder mb = s_moduleBuilder.Value; @@ -54,7 +54,7 @@ internal static void Compile(EnumMetadataEnum enumMetadata) if (enumMetadata.BitwiseFlagsSpecified && enumMetadata.BitwiseFlags) { - var cab = new CustomAttributeBuilder(typeof(FlagsAttribute).GetConstructor(PSTypeExtensions.EmptyTypes), new object[0]); + var cab = new CustomAttributeBuilder(typeof(FlagsAttribute).GetConstructor(Type.EmptyTypes), Array.Empty()); eb.SetCustomAttribute(cab); } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs b/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs index 110d3a53ce4..c2d30f6610e 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -15,15 +14,15 @@ namespace Microsoft.PowerShell.Cmdletization public sealed class MethodInvocationInfo { /// - /// Creates a new instance of MethodInvocationInfo + /// Creates a new instance of 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) + /// Name of the method to invoke. + /// Method parameters. + /// 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("name"); - if (parameters == null) throw new ArgumentNullException("parameters"); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(parameters); // returnValue can be null MethodName = name; @@ -39,49 +38,50 @@ public MethodInvocationInfo(string name, IEnumerable parameters } /// - /// Name of the method to invoke + /// Name of the method to invoke. /// public string MethodName { get; } /// - /// Method parameters + /// Method 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); } } + continue; } } + return result; } } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs b/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs index b5599516327..f044d4d422f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; @@ -13,7 +12,7 @@ namespace Microsoft.PowerShell.Cmdletization public enum MethodParameterBindings { /// - /// Bind value of a method parameter based on arguments of a cmdlet parameter + /// Bind value of a method parameter based on arguments of a cmdlet parameter. /// In = 1, @@ -34,7 +33,7 @@ public enum MethodParameterBindings public sealed class MethodParameter { /// - /// Name of the method parameter + /// Name of the method parameter. /// public string Name { get; set; } @@ -44,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; } @@ -55,13 +54,13 @@ public sealed class MethodParameter public MethodParameterBindings Bindings { get; set; } /// - /// Value of the argument of the method parameter + /// Value of the argument of the method parameter. /// public object Value { get; set; } /// /// Whether the value is 1) an explicit default (*) or 2) has been bound from cmdlet parameter - /// (*) explicit default = whatever was in DefaultValue attribute in Cmdletization XML + /// (*) explicit default = whatever was in DefaultValue attribute in Cmdletization XML. /// public bool IsValuePresent { get; set; } // TODO/FIXME: this should be renamed to ValueExplicitlySpecified or something like this diff --git a/src/System.Management.Automation/cimSupport/cmdletization/MethodParametersCollection.cs b/src/System.Management.Automation/cimSupport/cmdletization/MethodParametersCollection.cs index fa7d0ce519d..f52021fb2b0 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodParametersCollection.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodParametersCollection.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; namespace Microsoft.PowerShell.Cmdletization { - using System; - using System.Collections.ObjectModel; - /// /// Collection of method parameters and their arguments /// used to invoke a method in an object model wrapped by @@ -14,7 +13,7 @@ namespace Microsoft.PowerShell.Cmdletization internal sealed class MethodParametersCollection : KeyedCollection { /// - /// Creates an empty collection of method parameters + /// Creates an empty collection of method parameters. /// public MethodParametersCollection() : base(StringComparer.Ordinal, 5) @@ -22,7 +21,7 @@ public MethodParametersCollection() } /// - /// Gets key for a method parameter + /// Gets key for a method parameter. /// /// /// diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs b/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs index 2dc843d567f..082d7218023 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -18,46 +17,32 @@ public abstract class CmdletAdapter { internal void Initialize(PSCmdlet cmdlet, string className, string classVersion, IDictionary privateData) { - if (cmdlet == null) - { - throw new ArgumentNullException("cmdlet"); - } - if (string.IsNullOrEmpty(className)) - { - throw new ArgumentNullException("className"); - } - if (classVersion == null) // possible and ok to have classVersion==string.Empty - { - throw new ArgumentNullException("classVersion"); - } - if (privateData == null) - { - throw new ArgumentNullException("privateData"); - } + ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentException.ThrowIfNullOrEmpty(className); + + // 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(); }; } } /// - /// Class constructor + /// Class constructor. /// /// /// @@ -72,9 +57,9 @@ public void Initialize(PSCmdlet cmdlet, string className, string classVersion, V } /// - /// When overridden in the derived class, creates a query builder for a given object model + /// When overridden in the derived class, creates a query builder for a given object model. /// - /// Query builder for a given object model + /// Query builder for a given object model. public virtual QueryBuilder GetQueryBuilder() { throw new NotImplementedException(); @@ -83,8 +68,8 @@ public virtual QueryBuilder GetQueryBuilder() /// /// Queries for object instances in the object model. /// - /// Query parameters - /// A lazy evaluated collection of object instances + /// Query parameters. + /// A lazy evaluated collection of object instances. public virtual void ProcessRecord(QueryBuilder query) { throw new NotImplementedException(); @@ -123,9 +108,9 @@ public virtual void StopProcessing() /// /// Invokes an instance method in the object model. /// - /// The object on which to invoke the method - /// Method invocation details - /// true if successful method invocations should emit downstream the being operated on + /// The object on which to invoke the method. + /// Method invocation details. + /// if successful method invocations should emit downstream the being operated on. public virtual void ProcessRecord(TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru) { throw new NotImplementedException(); @@ -134,9 +119,9 @@ public virtual void ProcessRecord(TObjectInstance objectInstance, MethodInvocati /// /// Combines and . /// - /// Query parameters - /// Method invocation details - /// true if successful method invocations should emit downstream the object instance being operated on + /// Query parameters. + /// Method invocation details. + /// 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(); @@ -145,7 +130,7 @@ public virtual void ProcessRecord(QueryBuilder query, MethodInvocationInfo metho /// /// Invokes a static method in the object model. /// - /// Method invocation details + /// Method invocation details. public virtual void ProcessRecord( MethodInvocationInfo methodInvocationInfo) { @@ -162,10 +147,11 @@ public PSCmdlet Cmdlet return _cmdlet; } } + private PSCmdlet _cmdlet; /// - /// Name of the class (from the object model handled by this ObjectModelWrapper) that is wrapped by the currently executing cmdlet + /// Name of the class (from the object model handled by this ObjectModelWrapper) that is wrapped by the currently executing cmdlet. /// public string ClassName { @@ -174,11 +160,12 @@ public string ClassName return _className; } } + private 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 { @@ -187,10 +174,11 @@ public string ClassVersion return _classVersion; } } + private string _classVersion; /// - /// Module version + /// Module version. /// public Version ModuleVersion { @@ -199,6 +187,7 @@ public Version ModuleVersion return _moduleVersion; } } + private Version _moduleVersion; /// @@ -211,6 +200,7 @@ public IDictionary PrivateData return _privateData; } } + private IDictionary _privateData; } } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs index 5f796332538..e71e4c7c04f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -8,7 +7,7 @@ namespace Microsoft.PowerShell.Cmdletization { /// - /// Describes whether to report errors when a given filter doesnt match any objects + /// Describes whether to report errors when a given filter doesnt match any objects. /// public enum BehaviorOnNoMatch { @@ -27,34 +26,34 @@ public enum BehaviorOnNoMatch /// are treated as wildcards /// - Associations /// () - /// are treated as not a wildcard + /// are treated as not a wildcard. /// Default = 0, /// - /// ReportErrors forces reporting of errors that in other circumstances would be reported if no objects matched the filters + /// ReportErrors forces reporting of errors that in other circumstances would be reported if no objects matched the filters. /// ReportErrors, /// - /// SilentlyContinue suppresses errors that in other circumstances would be reported if no objects matched the filters + /// SilentlyContinue suppresses errors that in other circumstances would be reported if no objects matched the filters. /// SilentlyContinue, } /// - /// QueryBuilder supports building of object model queries in an object-model-agnostic way + /// QueryBuilder supports building of object model queries in an object-model-agnostic way. /// public abstract class QueryBuilder { /// - /// Modifies the query, so that it only returns objects with a given property value + /// Modifies the query, so that it only returns objects with a given property value. /// - /// Property name to query on - /// Property values to accept in the query + /// 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 @@ -65,13 +64,13 @@ public virtual void FilterByProperty(string propertyName, IEnumerable allowedPro } /// - /// Modifies the query, so that it does not return objects with a given property value + /// Modifies the query, so that it does not return objects with a given property value. /// - /// Property name to query on - /// Property values to reject in the query + /// 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 @@ -82,10 +81,10 @@ public virtual void ExcludeByProperty(string propertyName, IEnumerable excludedP } /// - /// Modifies the query, so that it returns only objects that have a property value greater than or equal to a threshold + /// Modifies the query, so that it returns only objects that have a property value greater than or equal to a threshold. /// - /// Property name to query on - /// Minimum property value + /// Property name to query on. + /// Minimum property value. /// /// Describes how to handle filters that didn't match any objects /// @@ -95,10 +94,10 @@ public virtual void FilterByMinPropertyValue(string propertyName, object minProp } /// - /// Modifies the query, so that it returns only objects that have a property value less than or equal to a threshold + /// Modifies the query, so that it returns only objects that have a property value less than or equal to a threshold. /// - /// Property name to query on - /// Maximum property value + /// Property name to query on. + /// Maximum property value. /// /// Describes how to handle filters that didn't match any objects /// @@ -110,10 +109,10 @@ public virtual void FilterByMaxPropertyValue(string propertyName, object maxProp /// /// Modifies the query, so that it returns only objects associated with /// - /// object that query results have to be associated with - /// name of the association - /// name of the role that has in the association - /// name of the role that query results have in the association + /// Object that query results have to be associated with. + /// Name of the association. + /// Name of the role that has in the association. + /// Name of the role that query results have in the association. /// /// Describes how to handle filters that didn't match any objects /// @@ -123,7 +122,7 @@ public virtual void FilterByAssociatedInstance(object associatedInstance, string } /// - /// Sets a query option + /// Sets a query option. /// /// /// diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs index a244064ebc5..e0bbcaa3969 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs @@ -1,16 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -24,7 +21,6 @@ namespace Microsoft.PowerShell.Cmdletization { - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] internal sealed class ScriptWriter { #region Static code reused for reading cmdletization xml @@ -48,7 +44,7 @@ static ScriptWriter() ScriptWriter.s_xmlReaderSettings.MaxCharactersInDocument = 128 * 1024 * 1024; // generous guess for the upper bound #if CORECLR // The XML Schema file 'cmdlets-over-objects.xsd' is missing in Github, and it's likely the resource string - //'CmdletizationCoreResources.Xml_cmdletsOverObjectsXsd' needs to be reworked to work in .NET Core. + // 'CmdletizationCoreResources.Xml_cmdletsOverObjectsXsd' needs to be reworked to work in .NET Core. ScriptWriter.s_xmlReaderSettings.DtdProcessing = DtdProcessing.Ignore; #else ScriptWriter.s_xmlReaderSettings.DtdProcessing = DtdProcessing.Parse; // Allowing DTD parsing with limits of MaxCharactersFromEntities/MaxCharactersInDocument @@ -104,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; } @@ -132,8 +126,7 @@ internal ScriptWriter( string objectModelWrapperName = _cmdletizationMetadata.Class.CmdletAdapter ?? defaultObjectModelWrapper; _objectModelWrapper = (Type)LanguagePrimitives.ConvertTo(objectModelWrapperName, typeof(Type), CultureInfo.InvariantCulture); - TypeInfo objectModelWrapperTypeInfo = _objectModelWrapper.GetTypeInfo(); - if (objectModelWrapperTypeInfo.IsGenericType) + if (_objectModelWrapper.IsGenericType) { string message = string.Format( CultureInfo.CurrentCulture, @@ -141,11 +134,11 @@ internal ScriptWriter( objectModelWrapperName); throw new XmlException(message); } + Type baseType = _objectModelWrapper; - TypeInfo baseTypeInfo = objectModelWrapperTypeInfo; - while ((!baseTypeInfo.IsGenericType) || baseTypeInfo.GetGenericTypeDefinition() != typeof(CmdletAdapter<>)) + while ((!baseType.IsGenericType) || baseType.GetGenericTypeDefinition() != typeof(CmdletAdapter<>)) { - baseType = baseTypeInfo.BaseType; + baseType = baseType.BaseType; if (baseType == typeof(object)) { string message = string.Format( @@ -155,8 +148,8 @@ internal ScriptWriter( typeof(CmdletAdapter<>).FullName); throw new XmlException(message); } - baseTypeInfo = baseType.GetTypeInfo(); } + _objectInstanceType = baseType.GetGenericArguments()[0]; _moduleName = moduleName; @@ -239,17 +232,13 @@ private string GetCmdletName(CommonCmdletMetadata cmdletMetadata) return verb + "-" + noun; } - private readonly List _aliasesToExport = new List(); - private readonly List _functionsToExport = new List(); - - 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))) + "')]"); - _aliasesToExport.AddRange(cmdletMetadata.Aliases); + attributes.Append("[Alias('" + string.Join("','", cmdletMetadata.Aliases.Select(static alias => CodeGeneration.EscapeSingleQuotedStringContent(alias))) + "')]"); } if (cmdletMetadata.Obsolete != null) @@ -258,19 +247,20 @@ 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(); } 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) @@ -310,6 +300,7 @@ private Dictionary GetCommonParameters() psetMetadata.ValueFromPipelineByPropertyName = false; psetMetadata.ValueFromRemainingArguments = false; } + commonParameters.Add(parameterMetadata.Name, parameterMetadata); } @@ -322,6 +313,7 @@ private Dictionary GetCommonParameters() _objectModelWrapper.FullName); throw new XmlException(message); } + foreach (ParameterMetadata parameter in commonParameters.Values) { if ((parameter.ParameterSets.Count == 1) && (parameter.ParameterSets.ContainsKey(ParameterAttribute.AllParameterSets))) @@ -341,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) @@ -358,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; } @@ -371,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) { @@ -385,13 +377,14 @@ private List GetMethodParameterSets(StaticCmdletMetadata staticCmdlet) parameterSetName); throw new XmlException(message); } + parameterSetNames.Add(parameterSetName, null); } return new List(parameterSetNames.Keys); } - private Dictionary _staticMethodMetadataToUniqueId = new Dictionary(); + private readonly Dictionary _staticMethodMetadataToUniqueId = new(); private string GetMethodParameterSet(CommonMethodMetadata methodMetadata) { @@ -409,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); @@ -441,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; @@ -464,6 +457,7 @@ private List GetQueryParameterSets(InstanceCmdletMetadata instanceCmdlet } } } + if (getCmdletParameters.QueryableAssociations != null) { foreach (Association association in getCmdletParameters.QueryableAssociations) @@ -478,6 +472,7 @@ private List GetQueryParameterSets(InstanceCmdletMetadata instanceCmdlet } } } + if (getCmdletParameters.QueryOptions != null) { foreach (QueryOption option in getCmdletParameters.QueryOptions) @@ -520,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)); @@ -558,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)) { @@ -640,6 +675,7 @@ private ParameterMetadata GetParameter( { elementType = parameterType.HasElementType ? parameterType.GetElementType() : parameterType; } + object min = LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateRange.Min, elementType, CultureInfo.InvariantCulture); object max = LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateRange.Max, elementType, CultureInfo.InvariantCulture); parameterMetadata.Attributes.Add(new ValidateRangeAttribute(min, max)); @@ -647,11 +683,12 @@ private ParameterMetadata GetParameter( if (parameterCmdletization.ValidateSet != null) { - List allowedValues = new List(); + List allowedValues = new(); foreach (string allowedValue in parameterCmdletization.ValidateSet) { allowedValues.Add(allowedValue); } + parameterMetadata.Attributes.Add(new ValidateSetAttribute(allowedValues.ToArray())); } } @@ -670,14 +707,17 @@ private ParameterMetadata GetParameter( parameterFlags |= ParameterSetMetadata.ParameterFlags.Mandatory; } } + if (isValueFromPipeline) { parameterFlags |= ParameterSetMetadata.ParameterFlags.ValueFromPipeline; } + if (isValueFromPipelineByPropertyName) { parameterFlags |= ParameterSetMetadata.ParameterFlags.ValueFromPipelineByPropertyName; } + parameterMetadata.ParameterSets.Add(parameterSetName, new ParameterSetMetadata(position, parameterFlags, null)); return parameterMetadata; @@ -718,6 +758,7 @@ private ParameterMetadata GetParameter( { queryParameterSets = parameterCmdletization.CmdletParameterSets; } + foreach (string parameterSetName in queryParameterSets) { if (parameterSetName.Equals(ScriptWriter.InputObjectQueryParameterSetName, StringComparison.OrdinalIgnoreCase)) @@ -785,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, @@ -826,16 +866,17 @@ 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); } } + return result.ToString(); } @@ -846,13 +887,14 @@ 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); } + return result; } else @@ -861,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); } @@ -917,13 +959,12 @@ private static void EnsureOrderOfPositionalParameters( } } + private const string StaticCommonParameterSetTemplate = "{1}"; // "{0}::{1}"; + private const string StaticMethodParameterSetTemplate = "{0}"; // "{1}::{0}"; - private const string StaticCommonParameterSetTemplate = "{1}"; //"{0}::{1}"; - private const string StaticMethodParameterSetTemplate = "{0}"; //"{1}::{0}"; - - private const string InstanceCommonParameterSetTemplate = "{1}"; //"{0}::{1}::{2}"; - private const string InstanceQueryParameterSetTemplate = "{0}"; //"{1}::{0}::{2}"; - private const string InstanceMethodParameterSetTemplate = "{2}"; //"{1}::{2}::{0}"; + private const string InstanceCommonParameterSetTemplate = "{1}"; // "{0}::{1}::{2}"; + private const string InstanceQueryParameterSetTemplate = "{0}"; // "{1}::{0}::{2}"; + private const string InstanceMethodParameterSetTemplate = "{2}"; // "{1}::{2}::{0}"; private const string InputObjectQueryParameterSetName = "InputObject (cdxml)"; private const string SingleQueryParameterSetName = "Query (cdxml)"; @@ -937,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) @@ -960,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) @@ -984,6 +1025,7 @@ private static MethodParameterBindings GetMethodParameterKind(InstanceMethodPara { bindings |= MethodParameterBindings.In; } + if (methodParameter.CmdletOutputMetadata != null) { if (methodParameter.CmdletOutputMetadata.ErrorCode == null) @@ -1008,6 +1050,7 @@ private static MethodParameterBindings GetMethodParameterKind(StaticMethodParame { bindings |= MethodParameterBindings.In; } + if (methodParameter.CmdletOutputMetadata != null) { if (methodParameter.CmdletOutputMetadata.ErrorCode == null) @@ -1080,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"); @@ -1108,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); } @@ -1133,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(); @@ -1144,6 +1187,7 @@ private void GenerateMethodParametersProcessing( { output.WriteLine(" switch -exact ($PSCmdlet.ParameterSetName) { "); } + foreach (StaticMethodMetadata method in staticCmdlet.Method) { if (multipleMethods) @@ -1155,15 +1199,20 @@ string parameterSetName in MultiplyParameterSets( GetMethodParameterSet(method), StaticMethodParameterSetTemplate, commonParameterSets)) { - 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 (StaticMethodParameterMetadata methodParameter in method.Parameters) @@ -1214,13 +1263,14 @@ 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); } } } + if (method.ReturnValue != null) { MethodParameterBindings methodParameterBindings = GetMethodParameterKind(method.ReturnValue); @@ -1237,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); @@ -1274,6 +1324,7 @@ string parameterSetName in } } } + if (multipleMethods) { output.WriteLine(" }"); @@ -1293,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) { "); @@ -1303,14 +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) @@ -1352,13 +1408,14 @@ 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); } } } + if (method.ReturnValue != null) { MethodParameterBindings methodParameterBindings = GetMethodParameterKind(method.ReturnValue); @@ -1375,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); @@ -1432,7 +1489,7 @@ private void GenerateMethodParametersProcessing( } } - private void GenerateIfBoundParameter( + private static void GenerateIfBoundParameter( IEnumerable commonParameterSets, IEnumerable methodParameterSets, ParameterMetadata cmdletParameterMetadata, @@ -1443,10 +1500,15 @@ 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)); } + output.WriteLine(") -contains $PSCmdlet.ParameterSetName )) {"); } @@ -1472,6 +1534,7 @@ private ParameterMetadata GenerateQueryClause( { cmdletParameterMetadata.ParameterType = typeof(object); } + cmdletParameterMetadata.ParameterType = cmdletParameterMetadata.ParameterType.MakeArrayType(); } @@ -1492,6 +1555,7 @@ private ParameterMetadata GenerateQueryClause( localVariableName, CodeGeneration.EscapeVariableName(cmdletParameterMetadata.Name)); } + output.Write( " $__cmdletization_queryBuilder.{0}('{1}', ${2}", queryBuilderMethodName, @@ -1525,6 +1589,7 @@ private static BehaviorOnNoMatch GetBehaviorWhenNoMatchesFound(CmdletParameterMe { return BehaviorOnNoMatch.Default; } + if (cmdletParameterMetadata.ErrorOnNoMatch) { return BehaviorOnNoMatch.ReportErrors; @@ -1599,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++) { @@ -1665,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); @@ -1709,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) @@ -1740,14 +1805,15 @@ private void GenerateQueryParametersProcessing( _objectInstanceType.FullName, _cmdletizationMetadata.Class.ClassName); } + inputObjectParameter.Attributes.Add(new PSTypeNameAttribute(psTypeNameOfInputObjectElements)); 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); } @@ -1762,6 +1828,7 @@ private void GenerateQueryParametersProcessing( {1} {2} {3} + param( {4}) @@ -1841,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, @@ -1856,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); @@ -1899,8 +1966,6 @@ private void WriteCmdlet(TextWriter output, StaticCmdletMetadata staticCmdlet) CmdletEndBlockTemplate, /* 0 */ this.GetHelpDirectiveForExternalHelp(), /* 1 */ CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name)); - - _functionsToExport.Add(commandMetadata.Name); } private static void AddPassThruParameter(IDictionary commonParameters, InstanceCmdletMetadata instanceCmdletMetadata) @@ -1920,6 +1985,7 @@ private static void AddPassThruParameter(IDictionary } } } + if (instanceCmdletMetadata.Method.ReturnValue != null) { if ((instanceCmdletMetadata.Method.ReturnValue.CmdletOutputMetadata != null) && @@ -1931,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); @@ -1942,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); @@ -1966,8 +2032,9 @@ private void WriteCmdlet(TextWriter output, InstanceCmdletMetadata instanceCmdle } else if (queryParameterSets.Count == 1) { - commandMetadata.DefaultParameterSetName = queryParameterSets.Single(); + commandMetadata.DefaultParameterSetName = queryParameterSets[0]; } + AddPassThruParameter(commonParameters, instanceCmdlet); MultiplyParameterSets(commonParameters, InstanceCommonParameterSetTemplate, queryParameterSets, methodParameterSets); MultiplyParameterSets(queryParameters, InstanceQueryParameterSetTemplate, commonParameterSets, methodParameterSets); @@ -1995,13 +2062,11 @@ private void WriteCmdlet(TextWriter output, InstanceCmdletMetadata instanceCmdle CmdletEndBlockTemplate, /* 0 */ this.GetHelpDirectiveForExternalHelp(), /* 1 */ CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name)); - - _functionsToExport.Add(commandMetadata.Name); } private string GetOutputAttributeForGetCmdlet() { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); result.AppendFormat( CultureInfo.InvariantCulture, "[OutputType([{0}])]", @@ -2030,6 +2095,7 @@ private CommonCmdletMetadata GetGetCmdletMetadata() cmdletMetadata.Noun = _cmdletizationMetadata.Class.DefaultNoun; cmdletMetadata.Verb = VerbsCommon.Get; } + Dbg.Assert(cmdletMetadata != null, "xsd should ensure that cmdlet metadata element is always present"); return cmdletMetadata; } @@ -2038,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); @@ -2049,13 +2115,14 @@ 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)) { commandMetadata.DefaultParameterSetName = getCmdletParameters.DefaultCmdletParameterSet; } + MultiplyParameterSets(commonParameters, InstanceCommonParameterSetTemplate, queryParameterSets, methodParameterSets); MultiplyParameterSets(queryParameters, InstanceQueryParameterSetTemplate, commonParameterSets, methodParameterSets); EnsureOrderOfPositionalParameters(commonParameters, queryParameters); @@ -2080,11 +2147,10 @@ private void WriteGetCmdlet(TextWriter output) CmdletEndBlockTemplate, /* 0 */ this.GetHelpDirectiveForExternalHelp(), /* 1 */ CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name)); - - _functionsToExport.Add(commandMetadata.Name); } - private static object s_enumCompilationLock = new object(); + private static readonly object s_enumCompilationLock = new(); + private static void CompileEnum(EnumMetadataEnum enumMetadata) { try @@ -2159,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; } @@ -2186,17 +2252,17 @@ 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)); } } + if (_cmdletizationMetadata.Class.StaticCmdlets != null) { cmdletMetadatas = cmdletMetadatas.Concat( - _cmdletizationMetadata.Class.StaticCmdlets.Select(c => c.CmdletMetadata)); + _cmdletizationMetadata.Class.StaticCmdlets.Select(static c => c.CmdletMetadata)); } - foreach (CommonCmdletMetadata cmdletMetadata in cmdletMetadatas) { if (cmdletMetadata.Aliases != null) diff --git a/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs b/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs index c5a9695e258..48a47da7289 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs @@ -1,19 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using System.Text; // TODO/FIXME: move this to Microsoft.PowerShell.Cim namespace (and move in source depot folder as well) namespace Microsoft.PowerShell.Cmdletization.Cim { - using System.Management.Automation; - using System.Text; - /// /// Translates a into a like-operand for WQL. /// /// - /// Documentation on MSDN (http://msdn.microsoft.com/en-us/library/aa392263(VS.85).aspx) is + /// Documentation on MSDN (https://msdn.microsoft.com/library/aa392263(VS.85).aspx) is /// 1) rather slim / incomplete /// 2) sometimes incorrect (i.e. says that '=' is used for character ranges, when it should have said '-') /// @@ -21,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) @@ -80,12 +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; @@ -99,6 +99,7 @@ protected override void AppendCharacterRangeToBracketExpression(char startOfChar startOfCharacterRange = (char)44; _needClientSideFiltering = true; } + if (endOfCharacterRange == 45) { endOfCharacterRange = (char)46; 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 c9f404c87ee..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 @@ -1,3 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Xml.Serialization; + #pragma warning disable //------------------------------------------------------------------------------ // @@ -12,19 +17,16 @@ // // This source code was auto-generated by xsd, Version=4.0.30319.17929. // + namespace Microsoft.PowerShell.Cmdletization.Xml { - using System.Xml.Serialization; - - /// [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; private EnumMetadataEnum[] _enumsField; @@ -36,6 +38,7 @@ public ClassMetadata Class { return this._classField; } + set { this._classField = value; @@ -43,13 +46,14 @@ public ClassMetadata Class } /// - [System.Xml.Serialization.XmlArrayItemAttribute("Enum", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Enum", IsNullable = false)] public EnumMetadataEnum[] Enums { get { return this._enumsField; } + set { this._enumsField = value; @@ -60,10 +64,9 @@ 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; private string _defaultNounField; @@ -87,6 +90,7 @@ public string Version { return this._versionField; } + set { this._versionField = value; @@ -100,6 +104,7 @@ public string DefaultNoun { return this._defaultNounField; } + set { this._defaultNounField = value; @@ -113,6 +118,7 @@ public ClassMetadataInstanceCmdlets InstanceCmdlets { return this._instanceCmdletsField; } + set { this._instanceCmdletsField = value; @@ -120,13 +126,14 @@ public ClassMetadataInstanceCmdlets InstanceCmdlets } /// - [System.Xml.Serialization.XmlArrayItemAttribute("Cmdlet", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Cmdlet", IsNullable = false)] public StaticCmdletMetadata[] StaticCmdlets { get { return this._staticCmdletsField; } + set { this._staticCmdletsField = value; @@ -134,13 +141,14 @@ public StaticCmdletMetadata[] StaticCmdlets } /// - [System.Xml.Serialization.XmlArrayItemAttribute("Data", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Data", IsNullable = false)] public ClassMetadataData[] CmdletAdapterPrivateData { get { return this._cmdletAdapterPrivateDataField; } + set { this._cmdletAdapterPrivateDataField = value; @@ -148,13 +156,14 @@ public ClassMetadataData[] CmdletAdapterPrivateData } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletAdapter { get { return this._cmdletAdapterField; } + set { this._cmdletAdapterField = value; @@ -162,13 +171,14 @@ public string CmdletAdapter } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassName { get { return this._classNameField; } + set { this._classNameField = value; @@ -176,13 +186,14 @@ public string ClassName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassVersion { get { return this._classVersionField; } + set { this._classVersionField = value; @@ -193,10 +204,9 @@ 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; private GetCmdletMetadata _getCmdletField; @@ -210,6 +220,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -223,6 +234,7 @@ public GetCmdletMetadata GetCmdlet { return this._getCmdletField; } + set { this._getCmdletField = value; @@ -230,13 +242,14 @@ public GetCmdletMetadata GetCmdlet } /// - [System.Xml.Serialization.XmlElementAttribute("Cmdlet")] + [System.Xml.Serialization.XmlElement("Cmdlet")] public InstanceCmdletMetadata[] Cmdlet { get { return this._cmdletField; } + set { this._cmdletField = value; @@ -247,10 +260,9 @@ 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; private Association[] _queryableAssociationsField; @@ -260,13 +272,14 @@ 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 { return this._queryablePropertiesField; } + set { this._queryablePropertiesField = value; @@ -274,13 +287,14 @@ public PropertyMetadata[] QueryableProperties } /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem(IsNullable = false)] public Association[] QueryableAssociations { get { return this._queryableAssociationsField; } + set { this._queryableAssociationsField = value; @@ -288,13 +302,14 @@ public Association[] QueryableAssociations } /// - [System.Xml.Serialization.XmlArrayItemAttribute("Option", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Option", IsNullable = false)] public QueryOption[] QueryOptions { get { return this._queryOptionsField; } + set { this._queryOptionsField = value; @@ -302,13 +317,14 @@ public QueryOption[] QueryOptions } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultCmdletParameterSet { get { return this._defaultCmdletParameterSetField; } + set { this._defaultCmdletParameterSetField = value; @@ -319,10 +335,9 @@ 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; private PropertyQuery[] _itemsField; @@ -338,6 +353,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -345,17 +361,18 @@ 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 { return this._itemsField; } + set { this._itemsField = value; @@ -363,14 +380,15 @@ 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 { return this._itemsElementNameField; } + set { this._itemsElementNameField = value; @@ -378,13 +396,14 @@ public ItemsChoiceType[] ItemsElementName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PropertyName { get { return this._propertyNameField; } + set { this._propertyNameField = value; @@ -395,22 +414,22 @@ 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; private string _eTSTypeField; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSType { get { return this._pSTypeField; } + set { this._pSTypeField = value; @@ -418,13 +437,14 @@ public string PSType } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ETSType { get { return this._eTSTypeField; } + set { this._eTSTypeField = value; @@ -435,10 +455,9 @@ 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; private string _association1Field; @@ -454,6 +473,7 @@ public AssociationAssociatedInstance AssociatedInstance { return this._associatedInstanceField; } + set { this._associatedInstanceField = value; @@ -461,13 +481,14 @@ public AssociationAssociatedInstance AssociatedInstance } /// - [System.Xml.Serialization.XmlAttributeAttribute("Association")] + [System.Xml.Serialization.XmlAttribute("Association")] public string Association1 { get { return this._association1Field; } + set { this._association1Field = value; @@ -475,13 +496,14 @@ public string Association1 } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string SourceRole { get { return this._sourceRoleField; } + set { this._sourceRoleField = value; @@ -489,13 +511,14 @@ public string SourceRole } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ResultRole { get { return this._resultRoleField; } + set { this._resultRoleField = value; @@ -506,10 +529,9 @@ 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; private CmdletParameterMetadataForGetCmdletFilteringParameter _cmdletParameterMetadataField; @@ -521,6 +543,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -534,6 +557,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -544,22 +568,22 @@ 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; private bool _errorOnNoMatchFieldSpecified; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ErrorOnNoMatch { get { return this._errorOnNoMatchField; } + set { this._errorOnNoMatchField = value; @@ -567,13 +591,14 @@ public bool ErrorOnNoMatch } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ErrorOnNoMatchSpecified { get { return this._errorOnNoMatchFieldSpecified; } + set { this._errorOnNoMatchFieldSpecified = value; @@ -582,13 +607,12 @@ 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; private bool _valueFromPipelineFieldSpecified; @@ -600,13 +624,14 @@ internal partial class CmdletParameterMetadataForGetCmdletParameter : CmdletPara private string[] _cmdletParameterSetsField; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get { return this._valueFromPipelineField; } + set { this._valueFromPipelineField = value; @@ -614,13 +639,14 @@ public bool ValueFromPipeline } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get { return this._valueFromPipelineFieldSpecified; } + set { this._valueFromPipelineFieldSpecified = value; @@ -628,13 +654,14 @@ public bool ValueFromPipelineSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -642,13 +669,14 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -656,13 +684,14 @@ public bool ValueFromPipelineByPropertyNameSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] CmdletParameterSets { get { return this._cmdletParameterSetsField; } + set { this._cmdletParameterSetsField = value; @@ -671,16 +700,15 @@ 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; private object _allowEmptyStringField; @@ -718,6 +746,7 @@ public object AllowEmptyCollection { return this._allowEmptyCollectionField; } + set { this._allowEmptyCollectionField = value; @@ -731,6 +760,7 @@ public object AllowEmptyString { return this._allowEmptyStringField; } + set { this._allowEmptyStringField = value; @@ -744,6 +774,7 @@ public object AllowNull { return this._allowNullField; } + set { this._allowNullField = value; @@ -757,6 +788,7 @@ public object ValidateNotNull { return this._validateNotNullField; } + set { this._validateNotNullField = value; @@ -770,6 +802,7 @@ public object ValidateNotNullOrEmpty { return this._validateNotNullOrEmptyField; } + set { this._validateNotNullOrEmptyField = value; @@ -783,6 +816,7 @@ public CmdletParameterMetadataValidateCount ValidateCount { return this._validateCountField; } + set { this._validateCountField = value; @@ -796,6 +830,7 @@ public CmdletParameterMetadataValidateLength ValidateLength { return this._validateLengthField; } + set { this._validateLengthField = value; @@ -809,6 +844,7 @@ public CmdletParameterMetadataValidateRange ValidateRange { return this._validateRangeField; } + set { this._validateRangeField = value; @@ -816,13 +852,14 @@ public CmdletParameterMetadataValidateRange ValidateRange } /// - [System.Xml.Serialization.XmlArrayItemAttribute("AllowedValue", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("AllowedValue", IsNullable = false)] public string[] ValidateSet { get { return this._validateSetField; } + set { this._validateSetField = value; @@ -836,6 +873,7 @@ public ObsoleteAttributeMetadata Obsolete { return this._obsoleteField; } + set { this._obsoleteField = value; @@ -843,13 +881,14 @@ public ObsoleteAttributeMetadata Obsolete } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool IsMandatory { get { return this._isMandatoryField; } + set { this._isMandatoryField = value; @@ -857,13 +896,14 @@ public bool IsMandatory } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool IsMandatorySpecified { get { return this._isMandatoryFieldSpecified; } + set { this._isMandatoryFieldSpecified = value; @@ -871,13 +911,14 @@ public bool IsMandatorySpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get { return this._aliasesField; } + set { this._aliasesField = value; @@ -885,13 +926,14 @@ public string[] Aliases } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get { return this._pSNameField; } + set { this._pSNameField = value; @@ -899,13 +941,14 @@ public string PSName } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Position { get { return this._positionField; } + set { this._positionField = value; @@ -916,22 +959,22 @@ 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; private string _maxField; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get { return this._minField; } + set { this._minField = value; @@ -939,13 +982,14 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get { return this._maxField; } + set { this._maxField = value; @@ -956,22 +1000,22 @@ 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; private string _maxField; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get { return this._minField; } + set { this._minField = value; @@ -979,13 +1023,14 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get { return this._maxField; } + set { this._maxField = value; @@ -996,22 +1041,22 @@ 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; private string _maxField; /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Min { get { return this._minField; } + set { this._minField = value; @@ -1019,13 +1064,14 @@ public string Min } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Max { get { return this._maxField; } + set { this._maxField = value; @@ -1036,20 +1082,20 @@ 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 { return this._messageField; } + set { this._messageField = value; @@ -1060,22 +1106,22 @@ 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; private bool _valueFromPipelineByPropertyNameFieldSpecified; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -1083,13 +1129,14 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -1100,10 +1147,9 @@ 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; private bool _valueFromPipelineFieldSpecified; @@ -1113,13 +1159,14 @@ internal partial class CmdletParameterMetadataForStaticMethodParameter : CmdletP private bool _valueFromPipelineByPropertyNameFieldSpecified; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get { return this._valueFromPipelineField; } + set { this._valueFromPipelineField = value; @@ -1127,13 +1174,14 @@ public bool ValueFromPipeline } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get { return this._valueFromPipelineFieldSpecified; } + set { this._valueFromPipelineFieldSpecified = value; @@ -1141,13 +1189,14 @@ public bool ValueFromPipelineSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -1155,13 +1204,14 @@ public bool ValueFromPipelineByPropertyName } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -1172,10 +1222,9 @@ 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; private CmdletParameterMetadataForGetCmdletParameter _cmdletParameterMetadataField; @@ -1189,6 +1238,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1202,6 +1252,7 @@ public CmdletParameterMetadataForGetCmdletParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1209,13 +1260,14 @@ public CmdletParameterMetadataForGetCmdletParameter CmdletParameterMetadata } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string OptionName { get { return this._optionNameField; } + set { this._optionNameField = value; @@ -1226,10 +1278,9 @@ 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; private GetCmdletParameters _getCmdletParametersField; @@ -1241,6 +1292,7 @@ public CommonCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1254,6 +1306,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -1264,10 +1317,9 @@ 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; private string _verbField; @@ -1289,6 +1341,7 @@ public ObsoleteAttributeMetadata Obsolete { return this._obsoleteField; } + set { this._obsoleteField = value; @@ -1296,13 +1349,14 @@ public ObsoleteAttributeMetadata Obsolete } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Verb { get { return this._verbField; } + set { this._verbField = value; @@ -1310,13 +1364,14 @@ public string Verb } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Noun { get { return this._nounField; } + set { this._nounField = value; @@ -1324,13 +1379,14 @@ public string Noun } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get { return this._aliasesField; } + set { this._aliasesField = value; @@ -1338,13 +1394,14 @@ public string[] Aliases } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public ConfirmImpact ConfirmImpact { get { return this._confirmImpactField; } + set { this._confirmImpactField = value; @@ -1352,13 +1409,14 @@ public ConfirmImpact ConfirmImpact } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ConfirmImpactSpecified { get { return this._confirmImpactFieldSpecified; } + set { this._confirmImpactFieldSpecified = value; @@ -1366,13 +1424,14 @@ public bool ConfirmImpactSpecified } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "anyURI")] + [System.Xml.Serialization.XmlAttribute(DataType = "anyURI")] public string HelpUri { get { return this._helpUriField; } + set { this._helpUriField = value; @@ -1382,10 +1441,9 @@ 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 { - /// None, @@ -1402,10 +1460,9 @@ 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; private StaticMethodMetadata[] _methodField; @@ -1417,6 +1474,7 @@ public StaticCmdletMetadataCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1424,13 +1482,14 @@ public StaticCmdletMetadataCmdletMetadata CmdletMetadata } /// - [System.Xml.Serialization.XmlElementAttribute("Method")] + [System.Xml.Serialization.XmlElement("Method")] public StaticMethodMetadata[] Method { get { return this._methodField; } + set { this._methodField = value; @@ -1441,20 +1500,20 @@ 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 { return this._defaultCmdletParameterSetField; } + set { this._defaultCmdletParameterSetField = value; @@ -1465,22 +1524,22 @@ 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; private string _cmdletParameterSetField; /// - [System.Xml.Serialization.XmlArrayItemAttribute("Parameter", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Parameter", IsNullable = false)] public StaticMethodParameterMetadata[] Parameters { get { return this._parametersField; } + set { this._parametersField = value; @@ -1488,13 +1547,14 @@ public StaticMethodParameterMetadata[] Parameters } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletParameterSet { get { return this._cmdletParameterSetField; } + set { this._cmdletParameterSetField = value; @@ -1505,10 +1565,9 @@ 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; private CmdletOutputMetadata _cmdletOutputMetadataField; @@ -1520,6 +1579,7 @@ public CmdletParameterMetadataForStaticMethodParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1533,6 +1593,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1543,10 +1604,9 @@ 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; private string _pSNameField; @@ -1558,6 +1618,7 @@ public object ErrorCode { return this._errorCodeField; } + set { this._errorCodeField = value; @@ -1565,13 +1626,14 @@ public object ErrorCode } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get { return this._pSNameField; } + set { this._pSNameField = value; @@ -1580,14 +1642,13 @@ 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; private string _parameterNameField; @@ -1601,6 +1662,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1608,13 +1670,14 @@ public TypeMetadata Type } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ParameterName { get { return this._parameterNameField; } + set { this._parameterNameField = value; @@ -1622,13 +1685,14 @@ public string ParameterName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultValue { get { return this._defaultValueField; } + set { this._defaultValueField = value; @@ -1639,10 +1703,9 @@ 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; private CmdletOutputMetadata _cmdletOutputMetadataField; @@ -1654,6 +1717,7 @@ public CmdletParameterMetadataForInstanceMethodParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1667,6 +1731,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1675,14 +1740,13 @@ 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; private string _methodNameField; @@ -1694,6 +1758,7 @@ public CommonMethodMetadataReturnValue ReturnValue { return this._returnValueField; } + set { this._returnValueField = value; @@ -1701,13 +1766,14 @@ public CommonMethodMetadataReturnValue ReturnValue } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string MethodName { get { return this._methodNameField; } + set { this._methodNameField = value; @@ -1718,10 +1784,9 @@ 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; private CmdletOutputMetadata _cmdletOutputMetadataField; @@ -1733,6 +1798,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1746,6 +1812,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1756,20 +1823,20 @@ 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 { return this._parametersField; } + set { this._parametersField = value; @@ -1780,10 +1847,9 @@ 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; private InstanceMethodMetadata _methodField; @@ -1797,6 +1863,7 @@ public CommonCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1810,6 +1877,7 @@ public InstanceMethodMetadata Method { return this._methodField; } + set { this._methodField = value; @@ -1823,6 +1891,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -1831,13 +1900,12 @@ 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; /// @@ -1847,6 +1915,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1857,22 +1926,22 @@ 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; private bool _allowGlobbingFieldSpecified; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool AllowGlobbing { get { return this._allowGlobbingField; } + set { this._allowGlobbingField = value; @@ -1880,13 +1949,14 @@ public bool AllowGlobbing } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool AllowGlobbingSpecified { get { return this._allowGlobbingFieldSpecified; } + set { this._allowGlobbingFieldSpecified = value; @@ -1896,10 +1966,9 @@ 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 { - /// ExcludeQuery, @@ -1916,22 +1985,22 @@ 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; private string _valueField; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get { return this._nameField; } + set { this._nameField = value; @@ -1939,13 +2008,14 @@ public string Name } /// - [System.Xml.Serialization.XmlTextAttribute()] + [System.Xml.Serialization.XmlText()] public string Value { get { return this._valueField; } + set { this._valueField = value; @@ -1956,10 +2026,9 @@ 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; private string _enumNameField; @@ -1971,13 +2040,14 @@ internal partial class EnumMetadataEnum private bool _bitwiseFlagsFieldSpecified; /// - [System.Xml.Serialization.XmlElementAttribute("Value")] + [System.Xml.Serialization.XmlElement("Value")] public EnumMetadataEnumValue[] Value { get { return this._valueField; } + set { this._valueField = value; @@ -1985,13 +2055,14 @@ public EnumMetadataEnumValue[] Value } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string EnumName { get { return this._enumNameField; } + set { this._enumNameField = value; @@ -1999,13 +2070,14 @@ public string EnumName } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string UnderlyingType { get { return this._underlyingTypeField; } + set { this._underlyingTypeField = value; @@ -2013,13 +2085,14 @@ public string UnderlyingType } /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool BitwiseFlags { get { return this._bitwiseFlagsField; } + set { this._bitwiseFlagsField = value; @@ -2027,13 +2100,14 @@ public bool BitwiseFlags } /// - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool BitwiseFlagsSpecified { get { return this._bitwiseFlagsFieldSpecified; } + set { this._bitwiseFlagsFieldSpecified = value; @@ -2044,22 +2118,22 @@ 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; private string _valueField; /// - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get { return this._nameField; } + set { this._nameField = value; @@ -2067,13 +2141,14 @@ public string Name } /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Value { get { return this._valueField; } + set { this._valueField = value; 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 a0143cac911..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 @@ -1,18 +1,18 @@ -#if CORECLR +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #if CORECLR -#pragma warning disable +using System; +using System.Collections; +using System.Globalization; +using System.Xml; +using System.Xml.Schema; +#pragma warning disable namespace Microsoft.PowerShell.Cmdletization.Xml { - - using System; - using System.Collections; - using System.Globalization; - using System.Xml; - using System.Xml.Schema; - internal class XmlSerializationReader1 { #region Copy_From_XmlSerializationReader @@ -26,7 +26,6 @@ internal class XmlSerializationReader1 // 3. ReadTypedPrimitive(XmlQualifiedName type) and ReadTypedNull(XmlQualifiedName type). See the comments // in them for more information. - #region "Constructor" internal XmlSerializationReader1(XmlReader reader) @@ -59,7 +58,6 @@ internal XmlSerializationReader1(XmlReader reader) #endregion "Constructor" - #region "Field Definition" XmlReader _r; @@ -145,7 +143,6 @@ internal XmlSerializationReader1(XmlReader reader) #endregion "Field Definition" - #region "Property Definition" internal XmlReader Reader @@ -177,6 +174,7 @@ internal bool DecodeName { return _decodeName; } + set { _decodeName = value; @@ -191,13 +189,13 @@ protected XmlDocument Document { _d = new XmlDocument(_r.NameTable); } + return _d; } } #endregion "Property Definition" - #region "Method Definition" internal void InitPrimitiveIDs() @@ -291,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) @@ -306,6 +304,7 @@ protected Array ShrinkArray(Array a, int length, Type elementType, bool isNullab if (isNullable) return null; return Array.CreateInstance(elementType, 0); } + if (a.Length == length) return a; Array b = Array.CreateInstance(elementType, length); Array.Copy(a, b, length); @@ -416,9 +415,10 @@ internal XmlQualifiedName ToXmlQualifiedName(string value, bool decodeName) prefix = XmlConvert.DecodeName(prefix); localName = XmlConvert.DecodeName(localName); } + if (prefix == null || prefix.Length == 0) { - return new XmlQualifiedName(_r.NameTable.Add(value), _r.LookupNamespace(String.Empty)); + return new XmlQualifiedName(_r.NameTable.Add(value), _r.LookupNamespace(string.Empty)); } else { @@ -426,8 +426,9 @@ 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); } } @@ -451,6 +452,7 @@ protected XmlQualifiedName GetXsiType() return null; } } + return ToXmlQualifiedName(type, false); } @@ -465,6 +467,7 @@ protected bool GetNullAttr() if (isNull == null) isNull = _r.GetAttribute(_nullID, _instanceNs1999ID); } + if (isNull == null || !XmlConvert.ToBoolean(isNull)) return false; return true; } @@ -477,6 +480,7 @@ protected bool ReadNull() _r.Skip(); return true; } + _r.ReadStartElement(); int whileIterations = 0; int readerCount = ReaderCount; @@ -485,6 +489,7 @@ protected bool ReadNull() UnknownNode(null); CheckReaderCount(ref whileIterations, ref readerCount); } + ReadEndElement(); return true; } @@ -534,7 +539,6 @@ protected object ReadTypedNull(XmlQualifiedName type) #endregion "Method Definition" - #endregion Copy_From_XmlSerializationReader public object Read50_PowerShellMetadata() @@ -556,6 +560,7 @@ public object Read50_PowerShellMetadata() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:PowerShellMetadata"); } + return (object)o; } @@ -578,6 +583,7 @@ public object Read51_ClassMetadata() { UnknownNode(null, @":ClassMetadata"); } + return (object)o; } @@ -600,6 +606,7 @@ public object Read52_ClassMetadataInstanceCmdlets() { UnknownNode(null, @":ClassMetadataInstanceCmdlets"); } + return (object)o; } @@ -622,6 +629,7 @@ public object Read53_GetCmdletParameters() { UnknownNode(null, @":GetCmdletParameters"); } + return (object)o; } @@ -644,6 +652,7 @@ public object Read54_PropertyMetadata() { UnknownNode(null, @":PropertyMetadata"); } + return (object)o; } @@ -666,6 +675,7 @@ public object Read55_TypeMetadata() { UnknownNode(null, @":TypeMetadata"); } + return (object)o; } @@ -688,6 +698,7 @@ public object Read56_Association() { UnknownNode(null, @":Association"); } + return (object)o; } @@ -710,6 +721,7 @@ public object Read57_AssociationAssociatedInstance() { UnknownNode(null, @":AssociationAssociatedInstance"); } + return (object)o; } @@ -732,6 +744,7 @@ public object Read58_CmdletParameterMetadata() { UnknownNode(null, @":CmdletParameterMetadata"); } + return (object)o; } @@ -754,6 +767,7 @@ public object Read59_Item() { UnknownNode(null, @":CmdletParameterMetadataForGetCmdletParameter"); } + return (object)o; } @@ -776,6 +790,7 @@ public object Read60_Item() { UnknownNode(null, @":CmdletParameterMetadataForGetCmdletFilteringParameter"); } + return (object)o; } @@ -798,6 +813,7 @@ public object Read61_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateCount"); } + return (object)o; } @@ -820,6 +836,7 @@ public object Read62_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateLength"); } + return (object)o; } @@ -842,6 +859,7 @@ public object Read63_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateRange"); } + return (object)o; } @@ -864,6 +882,7 @@ public object Read64_ObsoleteAttributeMetadata() { UnknownNode(null, @":ObsoleteAttributeMetadata"); } + return (object)o; } @@ -886,6 +905,7 @@ public object Read65_Item() { UnknownNode(null, @":CmdletParameterMetadataForInstanceMethodParameter"); } + return (object)o; } @@ -908,6 +928,7 @@ public object Read66_Item() { UnknownNode(null, @":CmdletParameterMetadataForStaticMethodParameter"); } + return (object)o; } @@ -930,6 +951,7 @@ public object Read67_QueryOption() { UnknownNode(null, @":QueryOption"); } + return (object)o; } @@ -952,6 +974,7 @@ public object Read68_GetCmdletMetadata() { UnknownNode(null, @":GetCmdletMetadata"); } + return (object)o; } @@ -974,6 +997,7 @@ public object Read69_CommonCmdletMetadata() { UnknownNode(null, @":CommonCmdletMetadata"); } + return (object)o; } @@ -998,6 +1022,7 @@ public object Read70_ConfirmImpact() { UnknownNode(null, @":ConfirmImpact"); } + return (object)o; } @@ -1020,6 +1045,7 @@ public object Read71_StaticCmdletMetadata() { UnknownNode(null, @":StaticCmdletMetadata"); } + return (object)o; } @@ -1042,6 +1068,7 @@ public object Read72_Item() { UnknownNode(null, @":StaticCmdletMetadataCmdletMetadata"); } + return (object)o; } @@ -1064,6 +1091,7 @@ public object Read73_CommonMethodMetadata() { UnknownNode(null, @":CommonMethodMetadata"); } + return (object)o; } @@ -1086,6 +1114,7 @@ public object Read74_StaticMethodMetadata() { UnknownNode(null, @":StaticMethodMetadata"); } + return (object)o; } @@ -1108,6 +1137,7 @@ public object Read75_CommonMethodParameterMetadata() { UnknownNode(null, @":CommonMethodParameterMetadata"); } + return (object)o; } @@ -1130,6 +1160,7 @@ public object Read76_StaticMethodParameterMetadata() { UnknownNode(null, @":StaticMethodParameterMetadata"); } + return (object)o; } @@ -1152,6 +1183,7 @@ public object Read77_CmdletOutputMetadata() { UnknownNode(null, @":CmdletOutputMetadata"); } + return (object)o; } @@ -1174,6 +1206,7 @@ public object Read78_Item() { UnknownNode(null, @":InstanceMethodParameterMetadata"); } + return (object)o; } @@ -1196,6 +1229,7 @@ public object Read79_Item() { UnknownNode(null, @":CommonMethodMetadataReturnValue"); } + return (object)o; } @@ -1218,6 +1252,7 @@ public object Read80_InstanceMethodMetadata() { UnknownNode(null, @":InstanceMethodMetadata"); } + return (object)o; } @@ -1240,6 +1275,7 @@ public object Read81_InstanceCmdletMetadata() { UnknownNode(null, @":InstanceCmdletMetadata"); } + return (object)o; } @@ -1262,6 +1298,7 @@ public object Read82_PropertyQuery() { UnknownNode(null, @":PropertyQuery"); } + return (object)o; } @@ -1284,6 +1321,7 @@ public object Read83_WildcardablePropertyQuery() { UnknownNode(null, @":WildcardablePropertyQuery"); } + return (object)o; } @@ -1308,6 +1346,7 @@ public object Read84_ItemsChoiceType() { UnknownNode(null, @":ItemsChoiceType"); } + return (object)o; } @@ -1330,6 +1369,7 @@ public object Read85_ClassMetadataData() { UnknownNode(null, @":ClassMetadataData"); } + return (object)o; } @@ -1352,6 +1392,7 @@ public object Read86_EnumMetadataEnum() { UnknownNode(null, @":EnumMetadataEnum"); } + return (object)o; } @@ -1374,6 +1415,7 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @":EnumMetadataEnumValue"); } + return (object)o; } @@ -1390,6 +1432,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue(); @@ -1411,12 +1454,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name, :Value"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations0 = 0; @@ -1425,15 +1470,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations0, ref readerCount0); } + ReadEndElement(); return o; } @@ -1451,6 +1498,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum(); @@ -1480,6 +1528,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":EnumName, :UnderlyingType, :BitwiseFlags"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -1487,6 +1536,7 @@ public object Read87_EnumMetadataEnumValue() o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations1 = 0; @@ -1508,9 +1558,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Value"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations1, ref readerCount1); } + o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); ReadEndElement(); return o; @@ -1529,6 +1581,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue(); @@ -1550,12 +1603,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name, :Value"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations2 = 0; @@ -1564,15 +1619,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations2, ref readerCount2); } + ReadEndElement(); return o; } @@ -1590,6 +1647,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData(); @@ -1606,12 +1664,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations3 = 0; @@ -1621,7 +1681,7 @@ public object Read87_EnumMetadataEnumValue() string tmp = null; if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else if (Reader.NodeType == System.Xml.XmlNodeType.Text || Reader.NodeType == System.Xml.XmlNodeType.CDATA || @@ -1633,11 +1693,13 @@ public object Read87_EnumMetadataEnumValue() } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations3, ref readerCount3); } + ReadEndElement(); return o; } @@ -1667,6 +1729,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery(); @@ -1684,12 +1747,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":AllowGlobbing"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations4 = 0; @@ -1712,9 +1777,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations4, ref readerCount4); } + ReadEndElement(); return o; } @@ -1732,6 +1799,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter(); @@ -1801,6 +1869,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName, :CmdletParameterSets, :ErrorOnNoMatch"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -1809,6 +1878,7 @@ public object Read87_EnumMetadataEnumValue() o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations5 = 0; @@ -1892,11 +1962,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations6, ref readerCount6); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -1914,9 +1987,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations5, ref readerCount5); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); ReadEndElement(); @@ -1936,6 +2011,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata(); @@ -1952,12 +2028,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Message"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations7 = 0; @@ -1966,15 +2044,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations7, ref readerCount7); } + ReadEndElement(); return o; } @@ -1992,6 +2072,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange(); @@ -2013,12 +2094,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations8 = 0; @@ -2027,15 +2110,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations8, ref readerCount8); } + ReadEndElement(); return o; } @@ -2053,6 +2138,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength(); @@ -2074,12 +2160,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations9 = 0; @@ -2088,15 +2176,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations9, ref readerCount9); } + ReadEndElement(); return o; } @@ -2114,6 +2204,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount(); @@ -2135,12 +2226,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations10 = 0; @@ -2149,15 +2242,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations10, ref readerCount10); } + ReadEndElement(); return o; } @@ -2174,6 +2269,7 @@ public object Read87_EnumMetadataEnumValue() if (xsiType != null) return (global::System.Object)ReadTypedNull(xsiType); else return null; } + if (xsiType == null) { return ReadTypedPrimitive(new System.Xml.XmlQualifiedName("anyType", "http://www.w3.org/2001/XMLSchema")); @@ -2291,13 +2387,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations11, ref readerCount11); } + ReadEndElement(); } + a = (global::System.String[])ShrinkArray(z_0_0, cz_0_0, typeof(global::System.String), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id70_ArrayOfPropertyMetadata && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2334,13 +2434,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Property"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations12, ref readerCount12); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id72_ArrayOfAssociation && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2377,13 +2481,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Association"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations13, ref readerCount13); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.Association[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.Association), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id73_ArrayOfQueryOption && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2420,13 +2528,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Option"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations14, ref readerCount14); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id23_ConfirmImpact && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2470,13 +2582,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations15, ref readerCount15); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id77_Item && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2513,13 +2629,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations16, ref readerCount16); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id78_ArrayOfStaticCmdletMetadata && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2556,13 +2676,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations17, ref readerCount17); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id80_ArrayOfClassMetadataData && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2599,13 +2723,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Data"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations18, ref readerCount18); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id82_ArrayOfEnumMetadataEnum && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -2642,22 +2770,27 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enum"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations19, ref readerCount19); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum), false); } + return a; } else return ReadTypedPrimitive((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::System.Object o; o = new global::System.Object(); - bool[] paramsRead = new bool[0]; + bool[] paramsRead = Array.Empty(); while (Reader.MoveToNextAttribute()) { if (!IsXmlnsAttribute(Reader.Name)) @@ -2665,12 +2798,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations20 = 0; @@ -2679,15 +2814,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations20, ref readerCount20); } + ReadEndElement(); return o; } @@ -2705,6 +2842,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum(); @@ -2734,6 +2872,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":EnumName, :UnderlyingType, :BitwiseFlags"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -2741,6 +2880,7 @@ public object Read87_EnumMetadataEnumValue() o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations21 = 0; @@ -2762,9 +2902,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Value"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations21, ref readerCount21); } + o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); ReadEndElement(); return o; @@ -2783,6 +2925,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData(); @@ -2799,12 +2942,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations22 = 0; @@ -2814,7 +2959,7 @@ public object Read87_EnumMetadataEnumValue() string tmp = null; if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else if (Reader.NodeType == System.Xml.XmlNodeType.Text || Reader.NodeType == System.Xml.XmlNodeType.CDATA || @@ -2826,11 +2971,13 @@ public object Read87_EnumMetadataEnumValue() } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations22, ref readerCount22); } + ReadEndElement(); return o; } @@ -2848,6 +2995,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata(); @@ -2861,6 +3009,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -2868,6 +3017,7 @@ public object Read87_EnumMetadataEnumValue() o.@Method = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations23 = 0; @@ -2894,9 +3044,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Method"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations23, ref readerCount23); } + o.@Method = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata), true); ReadEndElement(); return o; @@ -2915,6 +3067,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata(); @@ -2938,12 +3091,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName, :CmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations24 = 0; @@ -2990,11 +3145,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations25, ref readerCount25); } + ReadEndElement(); } + o.@Parameters = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata), false); } } @@ -3007,9 +3165,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations24, ref readerCount24); } + ReadEndElement(); return o; } @@ -3027,6 +3187,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata(); @@ -3048,12 +3209,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations26 = 0; @@ -3086,9 +3249,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations26, ref readerCount26); } + ReadEndElement(); return o; } @@ -3106,6 +3271,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletOutputMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletOutputMetadata(); @@ -3122,12 +3288,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PSName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations27 = 0; @@ -3150,9 +3318,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ErrorCode"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations27, ref readerCount27); } + ReadEndElement(); return o; } @@ -3170,6 +3340,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForStaticMethodParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForStaticMethodParameter(); @@ -3222,6 +3393,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3229,6 +3401,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations28 = 0; @@ -3312,11 +3485,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations29, ref readerCount29); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -3334,9 +3510,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations28, ref readerCount28); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -3355,6 +3533,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata(); @@ -3376,12 +3555,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PSType, :ETSType"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations30 = 0; @@ -3390,15 +3571,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations30, ref readerCount30); } + ReadEndElement(); return o; } @@ -3416,6 +3599,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue(); @@ -3427,12 +3611,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations31 = 0; @@ -3460,9 +3646,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations31, ref readerCount31); } + ReadEndElement(); return o; } @@ -3480,6 +3668,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata(); @@ -3528,6 +3717,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri, :DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3535,6 +3725,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations32 = 0; @@ -3557,9 +3748,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations32, ref readerCount32); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -3590,6 +3783,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata(); @@ -3611,12 +3805,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations33 = 0; @@ -3649,9 +3845,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations33, ref readerCount33); } + ReadEndElement(); return o; } @@ -3669,6 +3867,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForInstanceMethodParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForInstanceMethodParameter(); @@ -3715,6 +3914,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipelineByPropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3722,6 +3922,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations34 = 0; @@ -3805,11 +4006,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations35, ref readerCount35); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -3827,9 +4031,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations34, ref readerCount34); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -3848,6 +4054,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption(); @@ -3864,12 +4071,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":OptionName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations36 = 0; @@ -3897,9 +4106,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations36, ref readerCount36); } + ReadEndElement(); return o; } @@ -3919,6 +4130,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletParameter(); @@ -3982,6 +4194,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName, :CmdletParameterSets"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3990,6 +4203,7 @@ public object Read87_EnumMetadataEnumValue() o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations37 = 0; @@ -4073,11 +4287,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations38, ref readerCount38); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -4095,9 +4312,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations37, ref readerCount37); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); ReadEndElement(); @@ -4117,6 +4336,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.Association o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.Association(); @@ -4143,12 +4363,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Association, :SourceRole, :ResultRole"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations39 = 0; @@ -4171,9 +4393,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AssociatedInstance"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations39, ref readerCount39); } + ReadEndElement(); return o; } @@ -4191,6 +4415,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance(); @@ -4202,12 +4427,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations40 = 0; @@ -4235,9 +4462,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations40, ref readerCount40); } + ReadEndElement(); return o; } @@ -4255,6 +4484,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata(); @@ -4275,6 +4505,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -4283,6 +4514,7 @@ public object Read87_EnumMetadataEnumValue() o.@ItemsElementName = (global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType[])ShrinkArray(choice_a_1, cchoice_a_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations41 = 0; @@ -4325,9 +4557,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:MaxValueQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:RegularQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ExcludeQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:MinValueQuery"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations41, ref readerCount41); } + o.@Items = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery), true); o.@ItemsElementName = (global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType[])ShrinkArray(choice_a_1, cchoice_a_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType), true); ReadEndElement(); @@ -4349,6 +4583,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery(); @@ -4360,12 +4595,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations42 = 0; @@ -4388,9 +4625,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations42, ref readerCount42); } + ReadEndElement(); return o; } @@ -4416,6 +4655,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadata(); @@ -4456,6 +4696,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -4463,6 +4704,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations43 = 0; @@ -4546,11 +4788,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations44, ref readerCount44); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -4568,9 +4813,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations43, ref readerCount43); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -4589,6 +4836,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters(); @@ -4611,12 +4859,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations45 = 0; @@ -4658,11 +4908,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Property"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations46, ref readerCount46); } + ReadEndElement(); } + o.@QueryableProperties = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata[])ShrinkArray(a_0_0, ca_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata), false); } } @@ -4699,11 +4952,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Association"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations47, ref readerCount47); } + ReadEndElement(); } + o.@QueryableAssociations = (global::Microsoft.PowerShell.Cmdletization.Xml.Association[])ShrinkArray(a_1_0, ca_1_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.Association), false); } } @@ -4740,11 +4996,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Option"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations48, ref readerCount48); } + ReadEndElement(); } + o.@QueryOptions = (global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption), false); } } @@ -4757,9 +5016,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryableProperties, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryableAssociations, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryOptions"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations45, ref readerCount45); } + ReadEndElement(); return o; } @@ -4777,6 +5038,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata(); @@ -4825,6 +5087,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri, :DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -4832,6 +5095,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations49 = 0; @@ -4854,9 +5118,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations49, ref readerCount49); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -4877,6 +5143,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata(); @@ -4920,6 +5187,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -4927,6 +5195,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations50 = 0; @@ -4949,9 +5218,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations50, ref readerCount50); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -4970,6 +5241,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletMetadata(); @@ -4981,12 +5253,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations51 = 0; @@ -5014,9 +5288,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations51, ref readerCount51); } + ReadEndElement(); return o; } @@ -5034,6 +5310,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodMetadata(); @@ -5052,12 +5329,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations52 = 0; @@ -5104,11 +5383,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations53, ref readerCount53); } + ReadEndElement(); } + o.@Parameters = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata), false); } } @@ -5121,9 +5403,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations52, ref readerCount52); } + ReadEndElement(); return o; } @@ -5145,6 +5429,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadata(); @@ -5161,12 +5446,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations54 = 0; @@ -5189,9 +5476,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations54, ref readerCount54); } + ReadEndElement(); return o; } @@ -5213,6 +5502,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodParameterMetadata(); @@ -5234,12 +5524,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations55 = 0; @@ -5262,9 +5554,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations55, ref readerCount55); } + ReadEndElement(); return o; } @@ -5282,6 +5576,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata(); @@ -5293,12 +5588,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations56 = 0; @@ -5331,9 +5628,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Method, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations56, ref readerCount56); } + ReadEndElement(); return o; } @@ -5351,6 +5650,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata(); @@ -5381,12 +5681,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":CmdletAdapter, :ClassName, :ClassVersion"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations57 = 0; @@ -5400,6 +5702,7 @@ public object Read87_EnumMetadataEnumValue() { o.@Version = Reader.ReadElementContentAsString(); } + paramsRead[0] = true; } else if (!paramsRead[1] && ((object)Reader.LocalName == (object)_id116_DefaultNoun && (object)Reader.NamespaceURI == (object)_id2_Item)) @@ -5407,6 +5710,7 @@ public object Read87_EnumMetadataEnumValue() { o.@DefaultNoun = Reader.ReadElementContentAsString(); } + paramsRead[1] = true; } else if (!paramsRead[2] && ((object)Reader.LocalName == (object)_id117_InstanceCmdlets && (object)Reader.NamespaceURI == (object)_id2_Item)) @@ -5447,11 +5751,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations58, ref readerCount58); } + ReadEndElement(); } + o.@StaticCmdlets = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata[])ShrinkArray(a_3_0, ca_3_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata), false); } } @@ -5488,11 +5795,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Data"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations59, ref readerCount59); } + ReadEndElement(); } + o.@CmdletAdapterPrivateData = (global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData[])ShrinkArray(a_4_0, ca_4_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData), false); } } @@ -5505,9 +5815,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Version, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:DefaultNoun, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:InstanceCmdlets, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:StaticCmdlets, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletAdapterPrivateData"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations57, ref readerCount57); } + ReadEndElement(); return o; } @@ -5525,6 +5837,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets(); @@ -5538,6 +5851,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5545,6 +5859,7 @@ public object Read87_EnumMetadataEnumValue() o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations60 = 0; @@ -5576,9 +5891,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdlet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations60, ref readerCount60); } + o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); ReadEndElement(); return o; @@ -5597,6 +5914,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets(); @@ -5610,6 +5928,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5617,6 +5936,7 @@ public object Read87_EnumMetadataEnumValue() o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations61 = 0; @@ -5648,9 +5968,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdlet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations61, ref readerCount61); } + o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); ReadEndElement(); return o; @@ -5669,6 +5991,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance(); @@ -5680,12 +6003,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations62 = 0; @@ -5713,9 +6038,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations62, ref readerCount62); } + ReadEndElement(); return o; } @@ -5733,6 +6060,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount(); @@ -5754,12 +6082,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations63 = 0; @@ -5768,15 +6098,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations63, ref readerCount63); } + ReadEndElement(); return o; } @@ -5794,6 +6126,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength(); @@ -5815,12 +6148,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations64 = 0; @@ -5829,15 +6164,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations64, ref readerCount64); } + ReadEndElement(); return o; } @@ -5855,6 +6192,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange(); @@ -5876,12 +6214,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations65 = 0; @@ -5890,15 +6230,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations65, ref readerCount65); } + ReadEndElement(); return o; } @@ -5916,6 +6258,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue(); @@ -5927,12 +6270,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations66 = 0; @@ -5960,9 +6305,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations66, ref readerCount66); } + ReadEndElement(); return o; } @@ -5980,6 +6327,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata(); @@ -5993,12 +6341,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations67 = 0; @@ -6045,11 +6395,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enum"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations68, ref readerCount68); } + ReadEndElement(); } + o.@Enums = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum[])ShrinkArray(a_1_0, ca_1_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum), false); } } @@ -6062,9 +6415,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Class, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enums"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations67, ref readerCount67); } + ReadEndElement(); return o; } @@ -6232,7 +6587,7 @@ private void InitIDs() _id1_PowerShellMetadata = Reader.NameTable.Add(@"PowerShellMetadata"); _id98_HelpUri = Reader.NameTable.Add(@"HelpUri"); _id91_DefaultValue = Reader.NameTable.Add(@"DefaultValue"); - _id4_Item = Reader.NameTable.Add(@""); + _id4_Item = Reader.NameTable.Add(string.Empty); _id32_Item = Reader.NameTable.Add(@"CommonMethodMetadataReturnValue"); _id43_EnumName = Reader.NameTable.Add(@"EnumName"); _id122_Enums = Reader.NameTable.Add(@"Enums"); @@ -6323,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(); @@ -6334,6 +6686,3 @@ internal object Deserialize(XmlReader reader) } } #endif - - -#endif 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 b1236bc59a0..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 @@ -1,4 +1,7 @@ -// PLEASE DO NOT EDIT THIS FILE BY HAND!!! +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// PLEASE DO NOT EDIT THIS FILE BY HAND!!! // // This file has been generated // by D:\bluedev\admin\monad\src\cimSupport\cmdletization\xml\generate.ps1 @@ -24,7 +27,6 @@ namespace Microsoft.PowerShell.Cmdletization.Xml { using System.Xml.Serialization; - /// [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.SerializableAttribute()] @@ -45,6 +47,7 @@ public ClassMetadata Class { return this._classField; } + set { this._classField = value; @@ -59,6 +62,7 @@ public EnumMetadataEnum[] Enums { return this._enumsField; } + set { this._enumsField = value; @@ -97,6 +101,7 @@ public string Version { return this._versionField; } + set { this._versionField = value; @@ -110,6 +115,7 @@ public string DefaultNoun { return this._defaultNounField; } + set { this._defaultNounField = value; @@ -123,6 +129,7 @@ public ClassMetadataInstanceCmdlets InstanceCmdlets { return this._instanceCmdletsField; } + set { this._instanceCmdletsField = value; @@ -137,6 +144,7 @@ public StaticCmdletMetadata[] StaticCmdlets { return this._staticCmdletsField; } + set { this._staticCmdletsField = value; @@ -151,6 +159,7 @@ public ClassMetadataData[] CmdletAdapterPrivateData { return this._cmdletAdapterPrivateDataField; } + set { this._cmdletAdapterPrivateDataField = value; @@ -165,6 +174,7 @@ public string CmdletAdapter { return this._cmdletAdapterField; } + set { this._cmdletAdapterField = value; @@ -179,6 +189,7 @@ public string ClassName { return this._classNameField; } + set { this._classNameField = value; @@ -193,6 +204,7 @@ public string ClassVersion { return this._classVersionField; } + set { this._classVersionField = value; @@ -221,6 +233,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -234,6 +247,7 @@ public GetCmdletMetadata GetCmdlet { return this._getCmdletField; } + set { this._getCmdletField = value; @@ -248,6 +262,7 @@ public InstanceCmdletMetadata[] Cmdlet { return this._cmdletField; } + set { this._cmdletField = value; @@ -279,6 +294,7 @@ public PropertyMetadata[] QueryableProperties { return this._queryablePropertiesField; } + set { this._queryablePropertiesField = value; @@ -293,6 +309,7 @@ public Association[] QueryableAssociations { return this._queryableAssociationsField; } + set { this._queryableAssociationsField = value; @@ -307,6 +324,7 @@ public QueryOption[] QueryOptions { return this._queryOptionsField; } + set { this._queryOptionsField = value; @@ -321,6 +339,7 @@ public string DefaultCmdletParameterSet { return this._defaultCmdletParameterSetField; } + set { this._defaultCmdletParameterSetField = value; @@ -351,6 +370,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -369,6 +389,7 @@ public PropertyQuery[] Items { return this._itemsField; } + set { this._itemsField = value; @@ -384,6 +405,7 @@ public ItemsChoiceType[] ItemsElementName { return this._itemsElementNameField; } + set { this._itemsElementNameField = value; @@ -398,6 +420,7 @@ public string PropertyName { return this._propertyNameField; } + set { this._propertyNameField = value; @@ -425,6 +448,7 @@ public string PSType { return this._pSTypeField; } + set { this._pSTypeField = value; @@ -439,6 +463,7 @@ public string ETSType { return this._eTSTypeField; } + set { this._eTSTypeField = value; @@ -469,6 +494,7 @@ public AssociationAssociatedInstance AssociatedInstance { return this._associatedInstanceField; } + set { this._associatedInstanceField = value; @@ -483,6 +509,7 @@ public string Association1 { return this._association1Field; } + set { this._association1Field = value; @@ -497,6 +524,7 @@ public string SourceRole { return this._sourceRoleField; } + set { this._sourceRoleField = value; @@ -511,6 +539,7 @@ public string ResultRole { return this._resultRoleField; } + set { this._resultRoleField = value; @@ -537,6 +566,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -550,6 +580,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -577,6 +608,7 @@ public bool ErrorOnNoMatch { return this._errorOnNoMatchField; } + set { this._errorOnNoMatchField = value; @@ -591,6 +623,7 @@ public bool ErrorOnNoMatchSpecified { return this._errorOnNoMatchFieldSpecified; } + set { this._errorOnNoMatchFieldSpecified = value; @@ -625,6 +658,7 @@ public bool ValueFromPipeline { return this._valueFromPipelineField; } + set { this._valueFromPipelineField = value; @@ -639,6 +673,7 @@ public bool ValueFromPipelineSpecified { return this._valueFromPipelineFieldSpecified; } + set { this._valueFromPipelineFieldSpecified = value; @@ -653,6 +688,7 @@ public bool ValueFromPipelineByPropertyName { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -667,6 +703,7 @@ public bool ValueFromPipelineByPropertyNameSpecified { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -681,6 +718,7 @@ public string[] CmdletParameterSets { return this._cmdletParameterSetsField; } + set { this._cmdletParameterSetsField = value; @@ -737,6 +775,7 @@ public object AllowEmptyCollection { return this._allowEmptyCollectionField; } + set { this._allowEmptyCollectionField = value; @@ -750,6 +789,7 @@ public object AllowEmptyString { return this._allowEmptyStringField; } + set { this._allowEmptyStringField = value; @@ -763,6 +803,7 @@ public object AllowNull { return this._allowNullField; } + set { this._allowNullField = value; @@ -776,6 +817,7 @@ public object ValidateNotNull { return this._validateNotNullField; } + set { this._validateNotNullField = value; @@ -789,6 +831,7 @@ public object ValidateNotNullOrEmpty { return this._validateNotNullOrEmptyField; } + set { this._validateNotNullOrEmptyField = value; @@ -802,6 +845,7 @@ public CmdletParameterMetadataValidateCount ValidateCount { return this._validateCountField; } + set { this._validateCountField = value; @@ -815,6 +859,7 @@ public CmdletParameterMetadataValidateLength ValidateLength { return this._validateLengthField; } + set { this._validateLengthField = value; @@ -828,6 +873,7 @@ public CmdletParameterMetadataValidateRange ValidateRange { return this._validateRangeField; } + set { this._validateRangeField = value; @@ -842,6 +888,7 @@ public string[] ValidateSet { return this._validateSetField; } + set { this._validateSetField = value; @@ -855,6 +902,7 @@ public ObsoleteAttributeMetadata Obsolete { return this._obsoleteField; } + set { this._obsoleteField = value; @@ -869,6 +917,7 @@ public bool IsMandatory { return this._isMandatoryField; } + set { this._isMandatoryField = value; @@ -883,6 +932,7 @@ public bool IsMandatorySpecified { return this._isMandatoryFieldSpecified; } + set { this._isMandatoryFieldSpecified = value; @@ -897,6 +947,7 @@ public string[] Aliases { return this._aliasesField; } + set { this._aliasesField = value; @@ -911,6 +962,7 @@ public string PSName { return this._pSNameField; } + set { this._pSNameField = value; @@ -925,6 +977,7 @@ public string Position { return this._positionField; } + set { this._positionField = value; @@ -952,6 +1005,7 @@ public string Min { return this._minField; } + set { this._minField = value; @@ -966,6 +1020,7 @@ public string Max { return this._maxField; } + set { this._maxField = value; @@ -993,6 +1048,7 @@ public string Min { return this._minField; } + set { this._minField = value; @@ -1007,6 +1063,7 @@ public string Max { return this._maxField; } + set { this._maxField = value; @@ -1034,6 +1091,7 @@ public string Min { return this._minField; } + set { this._minField = value; @@ -1048,6 +1106,7 @@ public string Max { return this._maxField; } + set { this._maxField = value; @@ -1073,6 +1132,7 @@ public string Message { return this._messageField; } + set { this._messageField = value; @@ -1100,6 +1160,7 @@ public bool ValueFromPipelineByPropertyName { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -1114,6 +1175,7 @@ public bool ValueFromPipelineByPropertyNameSpecified { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -1145,6 +1207,7 @@ public bool ValueFromPipeline { return this._valueFromPipelineField; } + set { this._valueFromPipelineField = value; @@ -1159,6 +1222,7 @@ public bool ValueFromPipelineSpecified { return this._valueFromPipelineFieldSpecified; } + set { this._valueFromPipelineFieldSpecified = value; @@ -1173,6 +1237,7 @@ public bool ValueFromPipelineByPropertyName { return this._valueFromPipelineByPropertyNameField; } + set { this._valueFromPipelineByPropertyNameField = value; @@ -1187,6 +1252,7 @@ public bool ValueFromPipelineByPropertyNameSpecified { return this._valueFromPipelineByPropertyNameFieldSpecified; } + set { this._valueFromPipelineByPropertyNameFieldSpecified = value; @@ -1215,6 +1281,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1228,6 +1295,7 @@ public CmdletParameterMetadataForGetCmdletParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1242,6 +1310,7 @@ public string OptionName { return this._optionNameField; } + set { this._optionNameField = value; @@ -1268,6 +1337,7 @@ public CommonCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1281,6 +1351,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -1317,6 +1388,7 @@ public ObsoleteAttributeMetadata Obsolete { return this._obsoleteField; } + set { this._obsoleteField = value; @@ -1331,6 +1403,7 @@ public string Verb { return this._verbField; } + set { this._verbField = value; @@ -1345,6 +1418,7 @@ public string Noun { return this._nounField; } + set { this._nounField = value; @@ -1359,6 +1433,7 @@ public string[] Aliases { return this._aliasesField; } + set { this._aliasesField = value; @@ -1373,6 +1448,7 @@ public ConfirmImpact ConfirmImpact { return this._confirmImpactField; } + set { this._confirmImpactField = value; @@ -1387,6 +1463,7 @@ public bool ConfirmImpactSpecified { return this._confirmImpactFieldSpecified; } + set { this._confirmImpactFieldSpecified = value; @@ -1401,6 +1478,7 @@ public string HelpUri { return this._helpUriField; } + set { this._helpUriField = value; @@ -1446,6 +1524,7 @@ public StaticCmdletMetadataCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1460,6 +1539,7 @@ public StaticMethodMetadata[] Method { return this._methodField; } + set { this._methodField = value; @@ -1485,6 +1565,7 @@ public string DefaultCmdletParameterSet { return this._defaultCmdletParameterSetField; } + set { this._defaultCmdletParameterSetField = value; @@ -1512,6 +1593,7 @@ public StaticMethodParameterMetadata[] Parameters { return this._parametersField; } + set { this._parametersField = value; @@ -1526,6 +1608,7 @@ public string CmdletParameterSet { return this._cmdletParameterSetField; } + set { this._cmdletParameterSetField = value; @@ -1552,6 +1635,7 @@ public CmdletParameterMetadataForStaticMethodParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1565,6 +1649,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1591,6 +1676,7 @@ public object ErrorCode { return this._errorCodeField; } + set { this._errorCodeField = value; @@ -1605,6 +1691,7 @@ public string PSName { return this._pSNameField; } + set { this._pSNameField = value; @@ -1635,6 +1722,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1649,6 +1737,7 @@ public string ParameterName { return this._parameterNameField; } + set { this._parameterNameField = value; @@ -1663,6 +1752,7 @@ public string DefaultValue { return this._defaultValueField; } + set { this._defaultValueField = value; @@ -1689,6 +1779,7 @@ public CmdletParameterMetadataForInstanceMethodParameter CmdletParameterMetadata { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1702,6 +1793,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1730,6 +1822,7 @@ public CommonMethodMetadataReturnValue ReturnValue { return this._returnValueField; } + set { this._returnValueField = value; @@ -1744,6 +1837,7 @@ public string MethodName { return this._methodNameField; } + set { this._methodNameField = value; @@ -1770,6 +1864,7 @@ public TypeMetadata Type { return this._typeField; } + set { this._typeField = value; @@ -1783,6 +1878,7 @@ public CmdletOutputMetadata CmdletOutputMetadata { return this._cmdletOutputMetadataField; } + set { this._cmdletOutputMetadataField = value; @@ -1808,6 +1904,7 @@ public InstanceMethodParameterMetadata[] Parameters { return this._parametersField; } + set { this._parametersField = value; @@ -1836,6 +1933,7 @@ public CommonCmdletMetadata CmdletMetadata { return this._cmdletMetadataField; } + set { this._cmdletMetadataField = value; @@ -1849,6 +1947,7 @@ public InstanceMethodMetadata Method { return this._methodField; } + set { this._methodField = value; @@ -1862,6 +1961,7 @@ public GetCmdletParameters GetCmdletParameters { return this._getCmdletParametersField; } + set { this._getCmdletParametersField = value; @@ -1887,6 +1987,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta { return this._cmdletParameterMetadataField; } + set { this._cmdletParameterMetadataField = value; @@ -1914,6 +2015,7 @@ public bool AllowGlobbing { return this._allowGlobbingField; } + set { this._allowGlobbingField = value; @@ -1928,6 +2030,7 @@ public bool AllowGlobbingSpecified { return this._allowGlobbingFieldSpecified; } + set { this._allowGlobbingFieldSpecified = value; @@ -1974,6 +2077,7 @@ public string Name { return this._nameField; } + set { this._nameField = value; @@ -1988,6 +2092,7 @@ public string Value { return this._valueField; } + set { this._valueField = value; @@ -2021,6 +2126,7 @@ public EnumMetadataEnumValue[] Value { return this._valueField; } + set { this._valueField = value; @@ -2035,6 +2141,7 @@ public string EnumName { return this._enumNameField; } + set { this._enumNameField = value; @@ -2049,6 +2156,7 @@ public string UnderlyingType { return this._underlyingTypeField; } + set { this._underlyingTypeField = value; @@ -2063,6 +2171,7 @@ public bool BitwiseFlags { return this._bitwiseFlagsField; } + set { this._bitwiseFlagsField = value; @@ -2077,6 +2186,7 @@ public bool BitwiseFlagsSpecified { return this._bitwiseFlagsFieldSpecified; } + set { this._bitwiseFlagsFieldSpecified = value; @@ -2104,6 +2214,7 @@ public string Name { return this._nameField; } + set { this._nameField = value; @@ -2118,6 +2229,7 @@ public string Value { return this._valueField; } + set { this._valueField = value; @@ -2125,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 eb89c9f371a..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 @@ -1,4 +1,7 @@ -// PLEASE DO NOT EDIT THIS FILE BY HAND!!! +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// PLEASE DO NOT EDIT THIS FILE BY HAND!!! // // This file has been generated // by D:\bluedev\admin\monad\src\cimSupport\cmdletization\xml\generate.ps1 @@ -8,11 +11,8 @@ #pragma warning disable #if _DYNAMIC_XMLSERIALIZER_COMPILATION - - #endif - namespace Microsoft.PowerShell.Cmdletization.Xml { [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] @@ -26,6 +26,7 @@ public void Write50_PowerShellMetadata(object o) WriteEmptyTag(@"PowerShellMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); return; } + TopLevelElement(); Write39_PowerShellMetadata(@"PowerShellMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata)o), false, false); } @@ -35,9 +36,10 @@ public void Write51_ClassMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"ClassMetadata", @""); + WriteNullTagLiteral(@"ClassMetadata", string.Empty); return; } + TopLevelElement(); Write36_ClassMetadata(@"ClassMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata)o), true, false); } @@ -47,9 +49,10 @@ public void Write52_ClassMetadataInstanceCmdlets(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"ClassMetadataInstanceCmdlets", @""); + WriteNullTagLiteral(@"ClassMetadataInstanceCmdlets", string.Empty); return; } + TopLevelElement(); Write40_ClassMetadataInstanceCmdlets(@"ClassMetadataInstanceCmdlets", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets)o), true, false); } @@ -59,9 +62,10 @@ public void Write53_GetCmdletParameters(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"GetCmdletParameters", @""); + WriteNullTagLiteral(@"GetCmdletParameters", string.Empty); return; } + TopLevelElement(); Write19_GetCmdletParameters(@"GetCmdletParameters", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters)o), true, false); } @@ -71,9 +75,10 @@ public void Write54_PropertyMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"PropertyMetadata", @""); + WriteNullTagLiteral(@"PropertyMetadata", string.Empty); return; } + TopLevelElement(); Write15_PropertyMetadata(@"PropertyMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata)o), true, false); } @@ -83,9 +88,10 @@ public void Write55_TypeMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"TypeMetadata", @""); + WriteNullTagLiteral(@"TypeMetadata", string.Empty); return; } + TopLevelElement(); Write2_TypeMetadata(@"TypeMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata)o), true, false); } @@ -95,9 +101,10 @@ public void Write56_Association(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"Association", @""); + WriteNullTagLiteral(@"Association", string.Empty); return; } + TopLevelElement(); Write17_Association(@"Association", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.Association)o), true, false); } @@ -107,9 +114,10 @@ public void Write57_AssociationAssociatedInstance(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"AssociationAssociatedInstance", @""); + WriteNullTagLiteral(@"AssociationAssociatedInstance", string.Empty); return; } + TopLevelElement(); Write41_AssociationAssociatedInstance(@"AssociationAssociatedInstance", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance)o), true, false); } @@ -119,9 +127,10 @@ public void Write58_CmdletParameterMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadata", @""); + WriteNullTagLiteral(@"CmdletParameterMetadata", string.Empty); return; } + TopLevelElement(); Write10_CmdletParameterMetadata(@"CmdletParameterMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadata)o), true, false); } @@ -131,9 +140,10 @@ public void Write59_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataForGetCmdletParameter", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataForGetCmdletParameter", string.Empty); return; } + TopLevelElement(); Write11_Item(@"CmdletParameterMetadataForGetCmdletParameter", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletParameter)o), true, false); } @@ -143,9 +153,10 @@ public void Write60_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataForGetCmdletFilteringParameter", string.Empty); return; } + TopLevelElement(); Write12_Item(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter)o), true, false); } @@ -155,9 +166,10 @@ public void Write61_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataValidateCount", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataValidateCount", string.Empty); return; } + TopLevelElement(); Write42_Item(@"CmdletParameterMetadataValidateCount", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount)o), true, false); } @@ -167,9 +179,10 @@ public void Write62_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataValidateLength", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataValidateLength", string.Empty); return; } + TopLevelElement(); Write43_Item(@"CmdletParameterMetadataValidateLength", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength)o), true, false); } @@ -179,9 +192,10 @@ public void Write63_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataValidateRange", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataValidateRange", string.Empty); return; } + TopLevelElement(); Write44_Item(@"CmdletParameterMetadataValidateRange", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange)o), true, false); } @@ -191,9 +205,10 @@ public void Write64_ObsoleteAttributeMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"ObsoleteAttributeMetadata", @""); + WriteNullTagLiteral(@"ObsoleteAttributeMetadata", string.Empty); return; } + TopLevelElement(); Write7_ObsoleteAttributeMetadata(@"ObsoleteAttributeMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o), true, false); } @@ -203,9 +218,10 @@ public void Write65_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataForInstanceMethodParameter", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataForInstanceMethodParameter", string.Empty); return; } + TopLevelElement(); Write9_Item(@"CmdletParameterMetadataForInstanceMethodParameter", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForInstanceMethodParameter)o), true, false); } @@ -215,9 +231,10 @@ public void Write66_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletParameterMetadataForStaticMethodParameter", @""); + WriteNullTagLiteral(@"CmdletParameterMetadataForStaticMethodParameter", string.Empty); return; } + TopLevelElement(); Write8_Item(@"CmdletParameterMetadataForStaticMethodParameter", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForStaticMethodParameter)o), true, false); } @@ -227,9 +244,10 @@ public void Write67_QueryOption(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"QueryOption", @""); + WriteNullTagLiteral(@"QueryOption", string.Empty); return; } + TopLevelElement(); Write18_QueryOption(@"QueryOption", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption)o), true, false); } @@ -239,9 +257,10 @@ public void Write68_GetCmdletMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"GetCmdletMetadata", @""); + WriteNullTagLiteral(@"GetCmdletMetadata", string.Empty); return; } + TopLevelElement(); Write22_GetCmdletMetadata(@"GetCmdletMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletMetadata)o), true, false); } @@ -251,9 +270,10 @@ public void Write69_CommonCmdletMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CommonCmdletMetadata", @""); + WriteNullTagLiteral(@"CommonCmdletMetadata", string.Empty); return; } + TopLevelElement(); Write21_CommonCmdletMetadata(@"CommonCmdletMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata)o), true, false); } @@ -263,9 +283,10 @@ public void Write70_ConfirmImpact(object o) WriteStartDocument(); if (o == null) { - WriteEmptyTag(@"ConfirmImpact", @""); + WriteEmptyTag(@"ConfirmImpact", string.Empty); return; } + WriteElementString(@"ConfirmImpact", @"", Write20_ConfirmImpact(((global::Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact)o))); } @@ -274,9 +295,10 @@ public void Write71_StaticCmdletMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"StaticCmdletMetadata", @""); + WriteNullTagLiteral(@"StaticCmdletMetadata", string.Empty); return; } + TopLevelElement(); Write34_StaticCmdletMetadata(@"StaticCmdletMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata)o), true, false); } @@ -286,9 +308,10 @@ public void Write72_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"StaticCmdletMetadataCmdletMetadata", @""); + WriteNullTagLiteral(@"StaticCmdletMetadataCmdletMetadata", string.Empty); return; } + TopLevelElement(); Write45_Item(@"StaticCmdletMetadataCmdletMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata)o), true, false); } @@ -298,9 +321,10 @@ public void Write73_CommonMethodMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CommonMethodMetadata", @""); + WriteNullTagLiteral(@"CommonMethodMetadata", string.Empty); return; } + TopLevelElement(); Write29_CommonMethodMetadata(@"CommonMethodMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadata)o), true, false); } @@ -310,9 +334,10 @@ public void Write74_StaticMethodMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"StaticMethodMetadata", @""); + WriteNullTagLiteral(@"StaticMethodMetadata", string.Empty); return; } + TopLevelElement(); Write28_StaticMethodMetadata(@"StaticMethodMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata)o), true, false); } @@ -322,9 +347,10 @@ public void Write75_CommonMethodParameterMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CommonMethodParameterMetadata", @""); + WriteNullTagLiteral(@"CommonMethodParameterMetadata", string.Empty); return; } + TopLevelElement(); Write26_CommonMethodParameterMetadata(@"CommonMethodParameterMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodParameterMetadata)o), true, false); } @@ -334,9 +360,10 @@ public void Write76_StaticMethodParameterMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"StaticMethodParameterMetadata", @""); + WriteNullTagLiteral(@"StaticMethodParameterMetadata", string.Empty); return; } + TopLevelElement(); Write27_StaticMethodParameterMetadata(@"StaticMethodParameterMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata)o), true, false); } @@ -346,9 +373,10 @@ public void Write77_CmdletOutputMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CmdletOutputMetadata", @""); + WriteNullTagLiteral(@"CmdletOutputMetadata", string.Empty); return; } + TopLevelElement(); Write23_CmdletOutputMetadata(@"CmdletOutputMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletOutputMetadata)o), true, false); } @@ -358,9 +386,10 @@ public void Write78_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"InstanceMethodParameterMetadata", @""); + WriteNullTagLiteral(@"InstanceMethodParameterMetadata", string.Empty); return; } + TopLevelElement(); Write25_Item(@"InstanceMethodParameterMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata)o), true, false); } @@ -370,9 +399,10 @@ public void Write79_Item(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"CommonMethodMetadataReturnValue", @""); + WriteNullTagLiteral(@"CommonMethodMetadataReturnValue", string.Empty); return; } + TopLevelElement(); Write46_Item(@"CommonMethodMetadataReturnValue", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue)o), true, false); } @@ -382,9 +412,10 @@ public void Write80_InstanceMethodMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"InstanceMethodMetadata", @""); + WriteNullTagLiteral(@"InstanceMethodMetadata", string.Empty); return; } + TopLevelElement(); Write30_InstanceMethodMetadata(@"InstanceMethodMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodMetadata)o), true, false); } @@ -394,9 +425,10 @@ public void Write81_InstanceCmdletMetadata(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"InstanceCmdletMetadata", @""); + WriteNullTagLiteral(@"InstanceCmdletMetadata", string.Empty); return; } + TopLevelElement(); Write31_InstanceCmdletMetadata(@"InstanceCmdletMetadata", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata)o), true, false); } @@ -406,9 +438,10 @@ public void Write82_PropertyQuery(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"PropertyQuery", @""); + WriteNullTagLiteral(@"PropertyQuery", string.Empty); return; } + TopLevelElement(); Write14_PropertyQuery(@"PropertyQuery", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)o), true, false); } @@ -418,9 +451,10 @@ public void Write83_WildcardablePropertyQuery(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"WildcardablePropertyQuery", @""); + WriteNullTagLiteral(@"WildcardablePropertyQuery", string.Empty); return; } + TopLevelElement(); Write13_WildcardablePropertyQuery(@"WildcardablePropertyQuery", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery)o), true, false); } @@ -430,9 +464,10 @@ public void Write84_ItemsChoiceType(object o) WriteStartDocument(); if (o == null) { - WriteEmptyTag(@"ItemsChoiceType", @""); + WriteEmptyTag(@"ItemsChoiceType", string.Empty); return; } + WriteElementString(@"ItemsChoiceType", @"", Write3_ItemsChoiceType(((global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType)o))); } @@ -441,9 +476,10 @@ public void Write85_ClassMetadataData(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"ClassMetadataData", @""); + WriteNullTagLiteral(@"ClassMetadataData", string.Empty); return; } + TopLevelElement(); Write47_ClassMetadataData(@"ClassMetadataData", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData)o), true, false); } @@ -453,9 +489,10 @@ public void Write86_EnumMetadataEnum(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"EnumMetadataEnum", @""); + WriteNullTagLiteral(@"EnumMetadataEnum", string.Empty); return; } + TopLevelElement(); Write48_EnumMetadataEnum(@"EnumMetadataEnum", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum)o), true, false); } @@ -465,9 +502,10 @@ public void Write87_EnumMetadataEnumValue(object o) WriteStartDocument(); if (o == null) { - WriteNullTagLiteral(@"EnumMetadataEnumValue", @""); + WriteNullTagLiteral(@"EnumMetadataEnumValue", string.Empty); return; } + TopLevelElement(); Write49_EnumMetadataEnumValue(@"EnumMetadataEnumValue", @"", ((global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue)o), true, false); } @@ -476,9 +514,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -490,8 +533,13 @@ private void Write49_EnumMetadataEnumValue(string n, string ns, global::Microsof throw CreateUnknownTypeException(o); } } + 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); @@ -501,9 +549,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -515,8 +568,13 @@ private void Write48_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow throw CreateUnknownTypeException(o); } } + 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) @@ -533,9 +591,11 @@ private void Write48_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow } } } + if (o.@BitwiseFlagsSpecified) { } + WriteEndElement(o); } @@ -543,9 +603,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -557,8 +622,13 @@ private void Write37_EnumMetadataEnumValue(string n, string ns, global::Microsof throw CreateUnknownTypeException(o); } } + 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); @@ -568,9 +638,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -582,13 +657,19 @@ private void Write47_ClassMetadataData(string n, string ns, global::Microsoft.Po throw CreateUnknownTypeException(o); } } + 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) { WriteValue(((global::System.String)o.@Value)); } + WriteEndElement(o); } @@ -603,6 +684,7 @@ private string Write3_ItemsChoiceType(global::Microsoft.PowerShell.Cmdletization case global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@RegularQuery: s = @"RegularQuery"; break; default: throw CreateInvalidEnumValueException(((System.Int64)v).ToString(System.Globalization.CultureInfo.InvariantCulture), @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType"); } + return s; } @@ -610,9 +692,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -624,16 +711,23 @@ private void Write13_WildcardablePropertyQuery(string n, string ns, global::Micr throw CreateUnknownTypeException(o); } } + 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))); } + Write12_Item(@"CmdletParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter)o.@CmdletParameterMetadata), false, false); if (o.@AllowGlobbingSpecified) { } + WriteEndElement(o); } @@ -641,9 +735,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -655,8 +754,13 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + 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))); @@ -665,22 +769,29 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); 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); } + Writer.WriteEndAttribute(); } } + WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); WriteAttribute(@"Position", @"", ((global::System.String)o.@Position)); if (o.@ValueFromPipelineSpecified) { WriteAttribute(@"ValueFromPipeline", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipeline))); } + if (o.@ValueFromPipelineByPropertyNameSpecified) { WriteAttribute(@"ValueFromPipelineByPropertyName", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipelineByPropertyName))); @@ -689,20 +800,27 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@CmdletParameterSets; if (a != null) { - Writer.WriteStartAttribute(null, @"CmdletParameterSets", @""); + Writer.WriteStartAttribute(null, @"CmdletParameterSets", string.Empty); 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); } + Writer.WriteEndAttribute(); } } + if (o.@ErrorOnNoMatchSpecified) { WriteAttribute(@"ErrorOnNoMatch", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ErrorOnNoMatch))); } + Write1_Object(@"AllowEmptyCollection", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyCollection), false, false); Write1_Object(@"AllowEmptyString", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyString), false, false); Write1_Object(@"AllowNull", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowNull), false, false); @@ -720,22 +838,28 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl { WriteElementString(@"AllowedValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.String)a[ia])); } + WriteEndElement(); } } + Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@IsMandatorySpecified) { } + if (o.@ValueFromPipelineSpecified) { } + if (o.@ValueFromPipelineByPropertyNameSpecified) { } + if (o.@ErrorOnNoMatchSpecified) { } + WriteEndElement(o); } @@ -743,9 +867,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -757,8 +886,13 @@ private void Write7_ObsoleteAttributeMetadata(string n, string ns, global::Micro throw CreateUnknownTypeException(o); } } + 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); } @@ -767,9 +901,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -781,8 +920,13 @@ private void Write6_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle throw CreateUnknownTypeException(o); } } + 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); @@ -792,9 +936,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -806,8 +955,13 @@ private void Write5_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle throw CreateUnknownTypeException(o); } } + 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); @@ -817,9 +971,14 @@ 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; } + if (!needType) { System.Type t = o.GetType(); @@ -831,8 +990,13 @@ private void Write4_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle throw CreateUnknownTypeException(o); } } + 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); @@ -845,6 +1009,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1048,6 +1213,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1065,6 +1231,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1082,6 +1249,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1099,6 +1267,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1124,6 +1293,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1141,6 +1311,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1158,6 +1329,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1175,6 +1347,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1192,6 +1365,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is } } } + Writer.WriteEndElement(); return; } @@ -1201,6 +1375,7 @@ private void Write1_Object(string n, string ns, global::System.Object o, bool is return; } } + WriteStartElement(n, ns, o, false, null); WriteEndElement(o); } @@ -1212,6 +1387,7 @@ private void Write38_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1223,6 +1399,7 @@ private void Write38_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"EnumName", @"", ((global::System.String)o.@EnumName)); @@ -1241,9 +1418,11 @@ private void Write38_EnumMetadataEnum(string n, string ns, global::Microsoft.Pow } } } + if (o.@BitwiseFlagsSpecified) { } + WriteEndElement(o); } @@ -1254,6 +1433,7 @@ private void Write35_ClassMetadataData(string n, string ns, global::Microsoft.Po if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1265,6 +1445,7 @@ private void Write35_ClassMetadataData(string n, string ns, global::Microsoft.Po throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Name", @"", ((global::System.String)o.@Name)); @@ -1272,6 +1453,7 @@ private void Write35_ClassMetadataData(string n, string ns, global::Microsoft.Po { WriteValue(((global::System.String)o.@Value)); } + WriteEndElement(o); } @@ -1282,6 +1464,7 @@ private void Write34_StaticCmdletMetadata(string n, string ns, global::Microsoft if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1293,6 +1476,7 @@ private void Write34_StaticCmdletMetadata(string n, string ns, global::Microsoft throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"StaticCmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write33_Item(@"CmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata)o.@CmdletMetadata), false, false); @@ -1306,6 +1490,7 @@ private void Write34_StaticCmdletMetadata(string n, string ns, global::Microsoft } } } + WriteEndElement(o); } @@ -1316,6 +1501,7 @@ private void Write28_StaticMethodMetadata(string n, string ns, global::Microsoft if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1327,6 +1513,7 @@ private void Write28_StaticMethodMetadata(string n, string ns, global::Microsoft throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"StaticMethodMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"MethodName", @"", ((global::System.String)o.@MethodName)); @@ -1341,9 +1528,11 @@ private void Write28_StaticMethodMetadata(string n, string ns, global::Microsoft { Write27_StaticMethodParameterMetadata(@"Parameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata)a[ia]), false, false); } + WriteEndElement(); } } + WriteEndElement(o); } @@ -1354,6 +1543,7 @@ private void Write27_StaticMethodParameterMetadata(string n, string ns, global:: if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1365,6 +1555,7 @@ private void Write27_StaticMethodParameterMetadata(string n, string ns, global:: throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"StaticMethodParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"ParameterName", @"", ((global::System.String)o.@ParameterName)); @@ -1382,6 +1573,7 @@ private void Write23_CmdletOutputMetadata(string n, string ns, global::Microsoft if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1393,6 +1585,7 @@ private void Write23_CmdletOutputMetadata(string n, string ns, global::Microsoft throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletOutputMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); @@ -1407,6 +1600,7 @@ private void Write8_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1418,6 +1612,7 @@ private void Write8_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataForStaticMethodParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); if (o.@IsMandatorySpecified) @@ -1428,26 +1623,30 @@ private void Write8_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); WriteAttribute(@"Position", @"", ((global::System.String)o.@Position)); if (o.@ValueFromPipelineSpecified) { WriteAttribute(@"ValueFromPipeline", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipeline))); } + if (o.@ValueFromPipelineByPropertyNameSpecified) { WriteAttribute(@"ValueFromPipelineByPropertyName", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipelineByPropertyName))); } + Write1_Object(@"AllowEmptyCollection", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyCollection), false, false); Write1_Object(@"AllowEmptyString", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyString), false, false); Write1_Object(@"AllowNull", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowNull), false, false); @@ -1465,19 +1664,24 @@ private void Write8_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle { WriteElementString(@"AllowedValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.String)a[ia])); } + WriteEndElement(); } } + Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@IsMandatorySpecified) { } + if (o.@ValueFromPipelineSpecified) { } + if (o.@ValueFromPipelineByPropertyNameSpecified) { } + WriteEndElement(o); } @@ -1488,6 +1692,7 @@ private void Write2_TypeMetadata(string n, string ns, global::Microsoft.PowerShe if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1499,6 +1704,7 @@ private void Write2_TypeMetadata(string n, string ns, global::Microsoft.PowerShe throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"TypeMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"PSType", @"", ((global::System.String)o.@PSType)); @@ -1513,6 +1719,7 @@ private void Write24_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1524,6 +1731,7 @@ private void Write24_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write2_TypeMetadata(@"Type", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata)o.@Type), false, false); @@ -1538,6 +1746,7 @@ private void Write33_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1549,6 +1758,7 @@ private void Write33_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Verb", @"", ((global::System.String)o.@Verb)); @@ -1557,26 +1767,30 @@ private void Write33_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + if (o.@ConfirmImpactSpecified) { WriteAttribute(@"ConfirmImpact", @"", Write20_ConfirmImpact(((global::Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact)o.@ConfirmImpact))); } + WriteAttribute(@"HelpUri", @"", ((global::System.String)o.@HelpUri)); WriteAttribute(@"DefaultCmdletParameterSet", @"", ((global::System.String)o.@DefaultCmdletParameterSet)); Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@ConfirmImpactSpecified) { } + WriteEndElement(o); } @@ -1591,6 +1805,7 @@ private string Write20_ConfirmImpact(global::Microsoft.PowerShell.Cmdletization. case global::Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact.@High: s = @"High"; break; default: throw CreateInvalidEnumValueException(((System.Int64)v).ToString(System.Globalization.CultureInfo.InvariantCulture), @"Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact"); } + return s; } @@ -1601,6 +1816,7 @@ private void Write25_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1612,6 +1828,7 @@ private void Write25_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"InstanceMethodParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"ParameterName", @"", ((global::System.String)o.@ParameterName)); @@ -1629,6 +1846,7 @@ private void Write9_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1640,6 +1858,7 @@ private void Write9_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataForInstanceMethodParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); if (o.@IsMandatorySpecified) @@ -1650,22 +1869,25 @@ private void Write9_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); WriteAttribute(@"Position", @"", ((global::System.String)o.@Position)); if (o.@ValueFromPipelineByPropertyNameSpecified) { WriteAttribute(@"ValueFromPipelineByPropertyName", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipelineByPropertyName))); } + Write1_Object(@"AllowEmptyCollection", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyCollection), false, false); Write1_Object(@"AllowEmptyString", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyString), false, false); Write1_Object(@"AllowNull", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowNull), false, false); @@ -1683,16 +1905,20 @@ private void Write9_Item(string n, string ns, global::Microsoft.PowerShell.Cmdle { WriteElementString(@"AllowedValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.String)a[ia])); } + WriteEndElement(); } } + Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@IsMandatorySpecified) { } + if (o.@ValueFromPipelineByPropertyNameSpecified) { } + WriteEndElement(o); } @@ -1703,6 +1929,7 @@ private void Write18_QueryOption(string n, string ns, global::Microsoft.PowerShe if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1714,6 +1941,7 @@ private void Write18_QueryOption(string n, string ns, global::Microsoft.PowerShe throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"QueryOption", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"OptionName", @"", ((global::System.String)o.@OptionName)); @@ -1729,6 +1957,7 @@ private void Write11_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1745,6 +1974,7 @@ private void Write11_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataForGetCmdletParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); if (o.@IsMandatorySpecified) @@ -1755,22 +1985,25 @@ private void Write11_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); WriteAttribute(@"Position", @"", ((global::System.String)o.@Position)); if (o.@ValueFromPipelineSpecified) { WriteAttribute(@"ValueFromPipeline", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipeline))); } + if (o.@ValueFromPipelineByPropertyNameSpecified) { WriteAttribute(@"ValueFromPipelineByPropertyName", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@ValueFromPipelineByPropertyName))); @@ -1779,16 +2012,18 @@ private void Write11_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@CmdletParameterSets; if (a != null) { - Writer.WriteStartAttribute(null, @"CmdletParameterSets", @""); + Writer.WriteStartAttribute(null, @"CmdletParameterSets", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + Write1_Object(@"AllowEmptyCollection", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyCollection), false, false); Write1_Object(@"AllowEmptyString", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyString), false, false); Write1_Object(@"AllowNull", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowNull), false, false); @@ -1806,19 +2041,24 @@ private void Write11_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl { WriteElementString(@"AllowedValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.String)a[ia])); } + WriteEndElement(); } } + Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@IsMandatorySpecified) { } + if (o.@ValueFromPipelineSpecified) { } + if (o.@ValueFromPipelineByPropertyNameSpecified) { } + WriteEndElement(o); } @@ -1829,6 +2069,7 @@ private void Write17_Association(string n, string ns, global::Microsoft.PowerShe if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1840,6 +2081,7 @@ private void Write17_Association(string n, string ns, global::Microsoft.PowerShe throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"Association", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Association", @"", ((global::System.String)o.@Association1)); @@ -1856,6 +2098,7 @@ private void Write16_AssociationAssociatedInstance(string n, string ns, global:: if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1867,6 +2110,7 @@ private void Write16_AssociationAssociatedInstance(string n, string ns, global:: throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write2_TypeMetadata(@"Type", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata)o.@Type), false, false); @@ -1881,6 +2125,7 @@ private void Write15_PropertyMetadata(string n, string ns, global::Microsoft.Pow if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1892,6 +2137,7 @@ private void Write15_PropertyMetadata(string n, string ns, global::Microsoft.Pow throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"PropertyMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"PropertyName", @"", ((global::System.String)o.@PropertyName)); @@ -1905,6 +2151,7 @@ private void Write15_PropertyMetadata(string n, string ns, global::Microsoft.Pow { throw CreateInvalidChoiceIdentifierValueException(@"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType", @"ItemsElementName"); } + for (int ia = 0; ia < a.Length; ia++) { global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery ai = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)a[ia]; @@ -1912,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) @@ -1938,6 +2185,7 @@ private void Write15_PropertyMetadata(string n, string ns, global::Microsoft.Pow } } } + WriteEndElement(o); } @@ -1948,6 +2196,7 @@ private void Write14_PropertyQuery(string n, string ns, global::Microsoft.PowerS if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -1964,6 +2213,7 @@ private void Write14_PropertyQuery(string n, string ns, global::Microsoft.PowerS throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"PropertyQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write12_Item(@"CmdletParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter)o.@CmdletParameterMetadata), false, false); @@ -1977,6 +2227,7 @@ private void Write10_CmdletParameterMetadata(string n, string ns, global::Micros if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2008,6 +2259,7 @@ private void Write10_CmdletParameterMetadata(string n, string ns, global::Micros throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); if (o.@IsMandatorySpecified) @@ -2018,16 +2270,18 @@ private void Write10_CmdletParameterMetadata(string n, string ns, global::Micros global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + WriteAttribute(@"PSName", @"", ((global::System.String)o.@PSName)); WriteAttribute(@"Position", @"", ((global::System.String)o.@Position)); Write1_Object(@"AllowEmptyCollection", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.Object)o.@AllowEmptyCollection), false, false); @@ -2047,13 +2301,16 @@ private void Write10_CmdletParameterMetadata(string n, string ns, global::Micros { WriteElementString(@"AllowedValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::System.String)a[ia])); } + WriteEndElement(); } } + Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@IsMandatorySpecified) { } + WriteEndElement(o); } @@ -2064,6 +2321,7 @@ private void Write19_GetCmdletParameters(string n, string ns, global::Microsoft. if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2075,6 +2333,7 @@ private void Write19_GetCmdletParameters(string n, string ns, global::Microsoft. throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"GetCmdletParameters", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"DefaultCmdletParameterSet", @"", ((global::System.String)o.@DefaultCmdletParameterSet)); @@ -2087,6 +2346,7 @@ private void Write19_GetCmdletParameters(string n, string ns, global::Microsoft. { Write15_PropertyMetadata(@"Property", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata)a[ia]), false, false); } + WriteEndElement(); } } @@ -2099,6 +2359,7 @@ private void Write19_GetCmdletParameters(string n, string ns, global::Microsoft. { Write17_Association(@"Association", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.Association)a[ia]), false, false); } + WriteEndElement(); } } @@ -2111,9 +2372,11 @@ private void Write19_GetCmdletParameters(string n, string ns, global::Microsoft. { Write18_QueryOption(@"Option", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption)a[ia]), false, false); } + WriteEndElement(); } } + WriteEndElement(o); } @@ -2124,6 +2387,7 @@ private void Write45_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2135,6 +2399,7 @@ private void Write45_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"StaticCmdletMetadataCmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Verb", @"", ((global::System.String)o.@Verb)); @@ -2143,26 +2408,30 @@ private void Write45_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + if (o.@ConfirmImpactSpecified) { WriteAttribute(@"ConfirmImpact", @"", Write20_ConfirmImpact(((global::Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact)o.@ConfirmImpact))); } + WriteAttribute(@"HelpUri", @"", ((global::System.String)o.@HelpUri)); WriteAttribute(@"DefaultCmdletParameterSet", @"", ((global::System.String)o.@DefaultCmdletParameterSet)); Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@ConfirmImpactSpecified) { } + WriteEndElement(o); } @@ -2173,6 +2442,7 @@ private void Write21_CommonCmdletMetadata(string n, string ns, global::Microsoft if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2189,6 +2459,7 @@ private void Write21_CommonCmdletMetadata(string n, string ns, global::Microsoft throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CommonCmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Verb", @"", ((global::System.String)o.@Verb)); @@ -2197,25 +2468,29 @@ private void Write21_CommonCmdletMetadata(string n, string ns, global::Microsoft global::System.String[] a = (global::System.String[])o.@Aliases; if (a != null) { - Writer.WriteStartAttribute(null, @"Aliases", @""); + Writer.WriteStartAttribute(null, @"Aliases", string.Empty); for (int i = 0; i < a.Length; i++) { global::System.String ai = (global::System.String)a[i]; if (i != 0) Writer.WriteString(" "); WriteValue(ai); } + Writer.WriteEndAttribute(); } } + if (o.@ConfirmImpactSpecified) { WriteAttribute(@"ConfirmImpact", @"", Write20_ConfirmImpact(((global::Microsoft.PowerShell.Cmdletization.Xml.ConfirmImpact)o.@ConfirmImpact))); } + WriteAttribute(@"HelpUri", @"", ((global::System.String)o.@HelpUri)); Write7_ObsoleteAttributeMetadata(@"Obsolete", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata)o.@Obsolete), false, false); if (o.@ConfirmImpactSpecified) { } + WriteEndElement(o); } @@ -2226,6 +2501,7 @@ private void Write22_GetCmdletMetadata(string n, string ns, global::Microsoft.Po if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2237,6 +2513,7 @@ private void Write22_GetCmdletMetadata(string n, string ns, global::Microsoft.Po throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"GetCmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write21_CommonCmdletMetadata(@"CmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata)o.@CmdletMetadata), false, false); @@ -2251,6 +2528,7 @@ private void Write30_InstanceMethodMetadata(string n, string ns, global::Microso if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2262,6 +2540,7 @@ private void Write30_InstanceMethodMetadata(string n, string ns, global::Microso throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"InstanceMethodMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"MethodName", @"", ((global::System.String)o.@MethodName)); @@ -2275,9 +2554,11 @@ private void Write30_InstanceMethodMetadata(string n, string ns, global::Microso { Write25_Item(@"Parameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata)a[ia]), false, false); } + WriteEndElement(); } } + WriteEndElement(o); } @@ -2288,6 +2569,7 @@ private void Write29_CommonMethodMetadata(string n, string ns, global::Microsoft if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2309,6 +2591,7 @@ private void Write29_CommonMethodMetadata(string n, string ns, global::Microsoft throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CommonMethodMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"MethodName", @"", ((global::System.String)o.@MethodName)); @@ -2323,6 +2606,7 @@ private void Write26_CommonMethodParameterMetadata(string n, string ns, global:: if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2344,6 +2628,7 @@ private void Write26_CommonMethodParameterMetadata(string n, string ns, global:: throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CommonMethodParameterMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"ParameterName", @"", ((global::System.String)o.@ParameterName)); @@ -2359,6 +2644,7 @@ private void Write31_InstanceCmdletMetadata(string n, string ns, global::Microso if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2370,6 +2656,7 @@ private void Write31_InstanceCmdletMetadata(string n, string ns, global::Microso throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"InstanceCmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write21_CommonCmdletMetadata(@"CmdletMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata)o.@CmdletMetadata), false, false); @@ -2385,6 +2672,7 @@ private void Write36_ClassMetadata(string n, string ns, global::Microsoft.PowerS if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2396,6 +2684,7 @@ private void Write36_ClassMetadata(string n, string ns, global::Microsoft.PowerS throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"ClassMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"CmdletAdapter", @"", ((global::System.String)o.@CmdletAdapter)); @@ -2413,6 +2702,7 @@ private void Write36_ClassMetadata(string n, string ns, global::Microsoft.PowerS { Write34_StaticCmdletMetadata(@"Cmdlet", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata)a[ia]), false, false); } + WriteEndElement(); } } @@ -2425,9 +2715,11 @@ private void Write36_ClassMetadata(string n, string ns, global::Microsoft.PowerS { Write35_ClassMetadataData(@"Data", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData)a[ia]), false, false); } + WriteEndElement(); } } + WriteEndElement(o); } @@ -2438,6 +2730,7 @@ private void Write32_ClassMetadataInstanceCmdlets(string n, string ns, global::M if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2449,6 +2742,7 @@ private void Write32_ClassMetadataInstanceCmdlets(string n, string ns, global::M throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write19_GetCmdletParameters(@"GetCmdletParameters", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters)o.@GetCmdletParameters), false, false); @@ -2463,6 +2757,7 @@ private void Write32_ClassMetadataInstanceCmdlets(string n, string ns, global::M } } } + WriteEndElement(o); } @@ -2473,6 +2768,7 @@ private void Write40_ClassMetadataInstanceCmdlets(string n, string ns, global::M if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2484,6 +2780,7 @@ private void Write40_ClassMetadataInstanceCmdlets(string n, string ns, global::M throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"ClassMetadataInstanceCmdlets", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write19_GetCmdletParameters(@"GetCmdletParameters", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters)o.@GetCmdletParameters), false, false); @@ -2498,6 +2795,7 @@ private void Write40_ClassMetadataInstanceCmdlets(string n, string ns, global::M } } } + WriteEndElement(o); } @@ -2508,6 +2806,7 @@ private void Write41_AssociationAssociatedInstance(string n, string ns, global:: if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2519,6 +2818,7 @@ private void Write41_AssociationAssociatedInstance(string n, string ns, global:: throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"AssociationAssociatedInstance", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write2_TypeMetadata(@"Type", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata)o.@Type), false, false); @@ -2533,6 +2833,7 @@ private void Write42_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2544,6 +2845,7 @@ private void Write42_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataValidateCount", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); @@ -2558,6 +2860,7 @@ private void Write43_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2569,6 +2872,7 @@ private void Write43_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataValidateLength", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); @@ -2583,6 +2887,7 @@ private void Write44_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2594,6 +2899,7 @@ private void Write44_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CmdletParameterMetadataValidateRange", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); WriteAttribute(@"Min", @"", ((global::System.String)o.@Min)); @@ -2608,6 +2914,7 @@ private void Write46_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2619,6 +2926,7 @@ private void Write46_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(@"CommonMethodMetadataReturnValue", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write2_TypeMetadata(@"Type", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata)o.@Type), false, false); @@ -2633,6 +2941,7 @@ private void Write39_PowerShellMetadata(string n, string ns, global::Microsoft.P if (isNullable) WriteNullTagLiteral(n, ns); return; } + if (!needType) { System.Type t = o.GetType(); @@ -2644,6 +2953,7 @@ private void Write39_PowerShellMetadata(string n, string ns, global::Microsoft.P throw CreateUnknownTypeException(o); } } + WriteStartElement(n, ns, o, false, null); if (needType) WriteXsiType(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); Write36_ClassMetadata(@"Class", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata)o.@Class), false, false); @@ -2656,9 +2966,11 @@ private void Write39_PowerShellMetadata(string n, string ns, global::Microsoft.P { Write38_EnumMetadataEnum(@"Enum", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum)a[ia]), false, false); } + WriteEndElement(); } } + WriteEndElement(o); } @@ -2689,6 +3001,7 @@ public object Read50_PowerShellMetadata() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:PowerShellMetadata"); } + return (object)o; } @@ -2711,6 +3024,7 @@ public object Read51_ClassMetadata() { UnknownNode(null, @":ClassMetadata"); } + return (object)o; } @@ -2733,6 +3047,7 @@ public object Read52_ClassMetadataInstanceCmdlets() { UnknownNode(null, @":ClassMetadataInstanceCmdlets"); } + return (object)o; } @@ -2755,6 +3070,7 @@ public object Read53_GetCmdletParameters() { UnknownNode(null, @":GetCmdletParameters"); } + return (object)o; } @@ -2777,6 +3093,7 @@ public object Read54_PropertyMetadata() { UnknownNode(null, @":PropertyMetadata"); } + return (object)o; } @@ -2799,6 +3116,7 @@ public object Read55_TypeMetadata() { UnknownNode(null, @":TypeMetadata"); } + return (object)o; } @@ -2821,6 +3139,7 @@ public object Read56_Association() { UnknownNode(null, @":Association"); } + return (object)o; } @@ -2843,6 +3162,7 @@ public object Read57_AssociationAssociatedInstance() { UnknownNode(null, @":AssociationAssociatedInstance"); } + return (object)o; } @@ -2865,6 +3185,7 @@ public object Read58_CmdletParameterMetadata() { UnknownNode(null, @":CmdletParameterMetadata"); } + return (object)o; } @@ -2887,6 +3208,7 @@ public object Read59_Item() { UnknownNode(null, @":CmdletParameterMetadataForGetCmdletParameter"); } + return (object)o; } @@ -2909,6 +3231,7 @@ public object Read60_Item() { UnknownNode(null, @":CmdletParameterMetadataForGetCmdletFilteringParameter"); } + return (object)o; } @@ -2931,6 +3254,7 @@ public object Read61_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateCount"); } + return (object)o; } @@ -2953,6 +3277,7 @@ public object Read62_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateLength"); } + return (object)o; } @@ -2975,6 +3300,7 @@ public object Read63_Item() { UnknownNode(null, @":CmdletParameterMetadataValidateRange"); } + return (object)o; } @@ -2997,6 +3323,7 @@ public object Read64_ObsoleteAttributeMetadata() { UnknownNode(null, @":ObsoleteAttributeMetadata"); } + return (object)o; } @@ -3019,6 +3346,7 @@ public object Read65_Item() { UnknownNode(null, @":CmdletParameterMetadataForInstanceMethodParameter"); } + return (object)o; } @@ -3041,6 +3369,7 @@ public object Read66_Item() { UnknownNode(null, @":CmdletParameterMetadataForStaticMethodParameter"); } + return (object)o; } @@ -3063,6 +3392,7 @@ public object Read67_QueryOption() { UnknownNode(null, @":QueryOption"); } + return (object)o; } @@ -3085,6 +3415,7 @@ public object Read68_GetCmdletMetadata() { UnknownNode(null, @":GetCmdletMetadata"); } + return (object)o; } @@ -3107,6 +3438,7 @@ public object Read69_CommonCmdletMetadata() { UnknownNode(null, @":CommonCmdletMetadata"); } + return (object)o; } @@ -3131,6 +3463,7 @@ public object Read70_ConfirmImpact() { UnknownNode(null, @":ConfirmImpact"); } + return (object)o; } @@ -3153,6 +3486,7 @@ public object Read71_StaticCmdletMetadata() { UnknownNode(null, @":StaticCmdletMetadata"); } + return (object)o; } @@ -3175,6 +3509,7 @@ public object Read72_Item() { UnknownNode(null, @":StaticCmdletMetadataCmdletMetadata"); } + return (object)o; } @@ -3197,6 +3532,7 @@ public object Read73_CommonMethodMetadata() { UnknownNode(null, @":CommonMethodMetadata"); } + return (object)o; } @@ -3219,6 +3555,7 @@ public object Read74_StaticMethodMetadata() { UnknownNode(null, @":StaticMethodMetadata"); } + return (object)o; } @@ -3241,6 +3578,7 @@ public object Read75_CommonMethodParameterMetadata() { UnknownNode(null, @":CommonMethodParameterMetadata"); } + return (object)o; } @@ -3263,6 +3601,7 @@ public object Read76_StaticMethodParameterMetadata() { UnknownNode(null, @":StaticMethodParameterMetadata"); } + return (object)o; } @@ -3285,6 +3624,7 @@ public object Read77_CmdletOutputMetadata() { UnknownNode(null, @":CmdletOutputMetadata"); } + return (object)o; } @@ -3307,6 +3647,7 @@ public object Read78_Item() { UnknownNode(null, @":InstanceMethodParameterMetadata"); } + return (object)o; } @@ -3329,6 +3670,7 @@ public object Read79_Item() { UnknownNode(null, @":CommonMethodMetadataReturnValue"); } + return (object)o; } @@ -3351,6 +3693,7 @@ public object Read80_InstanceMethodMetadata() { UnknownNode(null, @":InstanceMethodMetadata"); } + return (object)o; } @@ -3373,6 +3716,7 @@ public object Read81_InstanceCmdletMetadata() { UnknownNode(null, @":InstanceCmdletMetadata"); } + return (object)o; } @@ -3395,6 +3739,7 @@ public object Read82_PropertyQuery() { UnknownNode(null, @":PropertyQuery"); } + return (object)o; } @@ -3417,6 +3762,7 @@ public object Read83_WildcardablePropertyQuery() { UnknownNode(null, @":WildcardablePropertyQuery"); } + return (object)o; } @@ -3441,6 +3787,7 @@ public object Read84_ItemsChoiceType() { UnknownNode(null, @":ItemsChoiceType"); } + return (object)o; } @@ -3463,6 +3810,7 @@ public object Read85_ClassMetadataData() { UnknownNode(null, @":ClassMetadataData"); } + return (object)o; } @@ -3485,6 +3833,7 @@ public object Read86_EnumMetadataEnum() { UnknownNode(null, @":EnumMetadataEnum"); } + return (object)o; } @@ -3507,6 +3856,7 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @":EnumMetadataEnumValue"); } + return (object)o; } @@ -3523,6 +3873,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue(); @@ -3544,12 +3895,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name, :Value"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations0 = 0; @@ -3558,15 +3911,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations0, ref readerCount0); } + ReadEndElement(); return o; } @@ -3584,6 +3939,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum(); @@ -3613,6 +3969,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":EnumName, :UnderlyingType, :BitwiseFlags"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3620,6 +3977,7 @@ public object Read87_EnumMetadataEnumValue() o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations1 = 0; @@ -3641,9 +3999,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Value"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations1, ref readerCount1); } + o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); ReadEndElement(); return o; @@ -3662,6 +4022,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue(); @@ -3683,12 +4044,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name, :Value"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations2 = 0; @@ -3697,15 +4060,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations2, ref readerCount2); } + ReadEndElement(); return o; } @@ -3723,6 +4088,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData(); @@ -3739,12 +4105,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations3 = 0; @@ -3754,7 +4122,7 @@ public object Read87_EnumMetadataEnumValue() string tmp = null; if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else if (Reader.NodeType == System.Xml.XmlNodeType.Text || Reader.NodeType == System.Xml.XmlNodeType.CDATA || @@ -3766,11 +4134,13 @@ public object Read87_EnumMetadataEnumValue() } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations3, ref readerCount3); } + ReadEndElement(); return o; } @@ -3800,6 +4170,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery(); @@ -3817,12 +4188,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":AllowGlobbing"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations4 = 0; @@ -3845,9 +4218,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations4, ref readerCount4); } + ReadEndElement(); return o; } @@ -3865,6 +4240,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletFilteringParameter(); @@ -3934,6 +4310,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName, :CmdletParameterSets, :ErrorOnNoMatch"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -3942,6 +4319,7 @@ public object Read87_EnumMetadataEnumValue() o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations5 = 0; @@ -4025,11 +4403,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations6, ref readerCount6); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -4047,9 +4428,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations5, ref readerCount5); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); ReadEndElement(); @@ -4069,6 +4452,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ObsoleteAttributeMetadata(); @@ -4085,12 +4469,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Message"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations7 = 0; @@ -4099,15 +4485,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations7, ref readerCount7); } + ReadEndElement(); return o; } @@ -4125,6 +4513,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange(); @@ -4146,12 +4535,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations8 = 0; @@ -4160,15 +4551,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations8, ref readerCount8); } + ReadEndElement(); return o; } @@ -4186,6 +4579,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength(); @@ -4207,12 +4601,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations9 = 0; @@ -4221,15 +4617,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations9, ref readerCount9); } + ReadEndElement(); return o; } @@ -4247,6 +4645,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount(); @@ -4268,12 +4667,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations10 = 0; @@ -4282,15 +4683,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations10, ref readerCount10); } + ReadEndElement(); return o; } @@ -4307,6 +4710,7 @@ public object Read87_EnumMetadataEnumValue() if (xsiType != null) return (global::System.Object)ReadTypedNull(xsiType); else return null; } + if (xsiType == null) { return ReadTypedPrimitive(new System.Xml.XmlQualifiedName("anyType", "http://www.w3.org/2001/XMLSchema")); @@ -4424,13 +4828,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations11, ref readerCount11); } + ReadEndElement(); } + a = (global::System.String[])ShrinkArray(z_0_0, cz_0_0, typeof(global::System.String), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id70_ArrayOfPropertyMetadata && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4467,13 +4875,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Property"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations12, ref readerCount12); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id72_ArrayOfAssociation && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4510,13 +4922,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Association"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations13, ref readerCount13); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.Association[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.Association), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id73_ArrayOfQueryOption && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4553,13 +4969,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Option"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations14, ref readerCount14); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id23_ConfirmImpact && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4603,13 +5023,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations15, ref readerCount15); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id77_Item && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4646,13 +5070,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations16, ref readerCount16); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id78_ArrayOfStaticCmdletMetadata && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4689,13 +5117,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations17, ref readerCount17); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id80_ArrayOfClassMetadataData && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4732,13 +5164,17 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Data"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations18, ref readerCount18); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData), false); } + return a; } else if (((object)((System.Xml.XmlQualifiedName)xsiType).Name == (object)_id82_ArrayOfEnumMetadataEnum && (object)((System.Xml.XmlQualifiedName)xsiType).Namespace == (object)_id2_Item)) @@ -4775,22 +5211,27 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enum"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations19, ref readerCount19); } + ReadEndElement(); } + a = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum[])ShrinkArray(z_0_0, cz_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum), false); } + return a; } else return ReadTypedPrimitive((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::System.Object o; o = new global::System.Object(); - bool[] paramsRead = new bool[0]; + bool[] paramsRead = Array.Empty(); while (Reader.MoveToNextAttribute()) { if (!IsXmlnsAttribute(Reader.Name)) @@ -4798,12 +5239,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations20 = 0; @@ -4812,15 +5255,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations20, ref readerCount20); } + ReadEndElement(); return o; } @@ -4838,6 +5283,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum(); @@ -4867,6 +5313,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":EnumName, :UnderlyingType, :BitwiseFlags"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -4874,6 +5321,7 @@ public object Read87_EnumMetadataEnumValue() o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations21 = 0; @@ -4895,9 +5343,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Value"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations21, ref readerCount21); } + o.@Value = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue[])ShrinkArray(a_0, ca_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue), true); ReadEndElement(); return o; @@ -4916,6 +5366,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData(); @@ -4932,12 +5383,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Name"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations22 = 0; @@ -4947,7 +5400,7 @@ public object Read87_EnumMetadataEnumValue() string tmp = null; if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else if (Reader.NodeType == System.Xml.XmlNodeType.Text || Reader.NodeType == System.Xml.XmlNodeType.CDATA || @@ -4959,11 +5412,13 @@ public object Read87_EnumMetadataEnumValue() } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations22, ref readerCount22); } + ReadEndElement(); return o; } @@ -4981,6 +5436,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata(); @@ -4994,6 +5450,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5001,6 +5458,7 @@ public object Read87_EnumMetadataEnumValue() o.@Method = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations23 = 0; @@ -5027,9 +5485,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Method"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations23, ref readerCount23); } + o.@Method = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata), true); ReadEndElement(); return o; @@ -5048,6 +5508,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodMetadata(); @@ -5071,12 +5532,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName, :CmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations24 = 0; @@ -5123,11 +5586,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations25, ref readerCount25); } + ReadEndElement(); } + o.@Parameters = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata), false); } } @@ -5140,9 +5606,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations24, ref readerCount24); } + ReadEndElement(); return o; } @@ -5160,6 +5628,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticMethodParameterMetadata(); @@ -5181,12 +5650,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations26 = 0; @@ -5219,9 +5690,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations26, ref readerCount26); } + ReadEndElement(); return o; } @@ -5239,6 +5712,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletOutputMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletOutputMetadata(); @@ -5255,12 +5729,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PSName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations27 = 0; @@ -5283,9 +5759,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ErrorCode"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations27, ref readerCount27); } + ReadEndElement(); return o; } @@ -5303,6 +5781,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForStaticMethodParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForStaticMethodParameter(); @@ -5355,6 +5834,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5362,6 +5842,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations28 = 0; @@ -5445,11 +5926,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations29, ref readerCount29); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -5467,9 +5951,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations28, ref readerCount28); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -5488,6 +5974,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.TypeMetadata(); @@ -5509,12 +5996,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PSType, :ETSType"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations30 = 0; @@ -5523,15 +6012,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations30, ref readerCount30); } + ReadEndElement(); return o; } @@ -5549,6 +6040,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue(); @@ -5560,12 +6052,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations31 = 0; @@ -5593,9 +6087,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations31, ref readerCount31); } + ReadEndElement(); return o; } @@ -5613,6 +6109,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata(); @@ -5661,6 +6158,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri, :DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5668,6 +6166,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations32 = 0; @@ -5690,9 +6189,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations32, ref readerCount32); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -5723,6 +6224,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata(); @@ -5744,12 +6246,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations33 = 0; @@ -5782,9 +6286,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations33, ref readerCount33); } + ReadEndElement(); return o; } @@ -5802,6 +6308,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForInstanceMethodParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForInstanceMethodParameter(); @@ -5848,6 +6355,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipelineByPropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -5855,6 +6363,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations34 = 0; @@ -5938,11 +6447,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations35, ref readerCount35); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -5960,9 +6472,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations34, ref readerCount34); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -5981,6 +6495,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption(); @@ -5997,12 +6512,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":OptionName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations36 = 0; @@ -6030,9 +6547,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations36, ref readerCount36); } + ReadEndElement(); return o; } @@ -6052,6 +6571,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletParameter o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataForGetCmdletParameter(); @@ -6115,6 +6635,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position, :ValueFromPipeline, :ValueFromPipelineByPropertyName, :CmdletParameterSets"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -6123,6 +6644,7 @@ public object Read87_EnumMetadataEnumValue() o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations37 = 0; @@ -6206,11 +6728,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations38, ref readerCount38); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -6228,9 +6753,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations37, ref readerCount37); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); o.@CmdletParameterSets = (global::System.String[])ShrinkArray(a_16, ca_16, typeof(global::System.String), true); ReadEndElement(); @@ -6250,6 +6777,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.Association o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.Association(); @@ -6276,12 +6804,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Association, :SourceRole, :ResultRole"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations39 = 0; @@ -6304,9 +6834,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AssociatedInstance"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations39, ref readerCount39); } + ReadEndElement(); return o; } @@ -6324,6 +6856,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance(); @@ -6335,12 +6868,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations40 = 0; @@ -6368,9 +6903,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations40, ref readerCount40); } + ReadEndElement(); return o; } @@ -6388,6 +6925,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata(); @@ -6408,6 +6946,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":PropertyName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -6416,6 +6955,7 @@ public object Read87_EnumMetadataEnumValue() o.@ItemsElementName = (global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType[])ShrinkArray(choice_a_1, cchoice_a_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations41 = 0; @@ -6458,9 +6998,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:MaxValueQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:RegularQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ExcludeQuery, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:MinValueQuery"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations41, ref readerCount41); } + o.@Items = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery[])ShrinkArray(a_1, ca_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery), true); o.@ItemsElementName = (global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType[])ShrinkArray(choice_a_1, cchoice_a_1, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType), true); ReadEndElement(); @@ -6482,6 +7024,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery(); @@ -6493,12 +7036,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations42 = 0; @@ -6521,9 +7066,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations42, ref readerCount42); } + ReadEndElement(); return o; } @@ -6549,6 +7096,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadata(); @@ -6589,6 +7137,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":IsMandatory, :Aliases, :PSName, :Position"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -6596,6 +7145,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations43 = 0; @@ -6679,11 +7229,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowedValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations44, ref readerCount44); } + ReadEndElement(); } + o.@ValidateSet = (global::System.String[])ShrinkArray(a_8_0, ca_8_0, typeof(global::System.String), false); } } @@ -6701,9 +7254,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyCollection, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowEmptyString, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:AllowNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNull, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateNotNullOrEmpty, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateCount, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateLength, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateRange, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ValidateSet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations43, ref readerCount43); } + o.@Aliases = (global::System.String[])ShrinkArray(a_11, ca_11, typeof(global::System.String), true); ReadEndElement(); return o; @@ -6722,6 +7277,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletParameters(); @@ -6744,12 +7300,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations45 = 0; @@ -6791,11 +7349,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Property"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations46, ref readerCount46); } + ReadEndElement(); } + o.@QueryableProperties = (global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata[])ShrinkArray(a_0_0, ca_0_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PropertyMetadata), false); } } @@ -6832,11 +7393,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Association"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations47, ref readerCount47); } + ReadEndElement(); } + o.@QueryableAssociations = (global::Microsoft.PowerShell.Cmdletization.Xml.Association[])ShrinkArray(a_1_0, ca_1_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.Association), false); } } @@ -6873,11 +7437,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Option"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations48, ref readerCount48); } + ReadEndElement(); } + o.@QueryOptions = (global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.QueryOption), false); } } @@ -6890,9 +7457,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryableProperties, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryableAssociations, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:QueryOptions"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations45, ref readerCount45); } + ReadEndElement(); return o; } @@ -6910,6 +7479,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadataCmdletMetadata(); @@ -6958,6 +7528,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri, :DefaultCmdletParameterSet"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -6965,6 +7536,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations49 = 0; @@ -6987,9 +7559,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations49, ref readerCount49); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -7010,6 +7584,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonCmdletMetadata(); @@ -7053,6 +7628,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Verb, :Noun, :Aliases, :ConfirmImpact, :HelpUri"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -7060,6 +7636,7 @@ public object Read87_EnumMetadataEnumValue() o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations50 = 0; @@ -7082,9 +7659,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Obsolete"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations50, ref readerCount50); } + o.@Aliases = (global::System.String[])ShrinkArray(a_3, ca_3, typeof(global::System.String), true); ReadEndElement(); return o; @@ -7103,6 +7682,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.GetCmdletMetadata(); @@ -7114,12 +7694,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations51 = 0; @@ -7147,9 +7729,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations51, ref readerCount51); } + ReadEndElement(); return o; } @@ -7167,6 +7751,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodMetadata(); @@ -7185,12 +7770,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations52 = 0; @@ -7237,11 +7824,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameter"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations53, ref readerCount53); } + ReadEndElement(); } + o.@Parameters = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata[])ShrinkArray(a_2_0, ca_2_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceMethodParameterMetadata), false); } } @@ -7254,9 +7844,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Parameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations52, ref readerCount52); } + ReadEndElement(); return o; } @@ -7278,6 +7870,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadata(); @@ -7294,12 +7887,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":MethodName"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations54 = 0; @@ -7322,9 +7917,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:ReturnValue"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations54, ref readerCount54); } + ReadEndElement(); return o; } @@ -7346,6 +7943,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodParameterMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodParameterMetadata(); @@ -7367,12 +7965,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":ParameterName, :DefaultValue"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations55 = 0; @@ -7395,9 +7995,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations55, ref readerCount55); } + ReadEndElement(); return o; } @@ -7415,6 +8017,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata(); @@ -7426,12 +8029,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations56 = 0; @@ -7464,9 +8069,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletMetadata, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Method, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations56, ref readerCount56); } + ReadEndElement(); return o; } @@ -7484,6 +8091,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata(); @@ -7514,12 +8122,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":CmdletAdapter, :ClassName, :ClassVersion"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations57 = 0; @@ -7533,6 +8143,7 @@ public object Read87_EnumMetadataEnumValue() { o.@Version = Reader.ReadElementString(); } + paramsRead[0] = true; } else if (!paramsRead[1] && ((object)Reader.LocalName == (object)_id116_DefaultNoun && (object)Reader.NamespaceURI == (object)_id2_Item)) @@ -7540,6 +8151,7 @@ public object Read87_EnumMetadataEnumValue() { o.@DefaultNoun = Reader.ReadElementString(); } + paramsRead[1] = true; } else if (!paramsRead[2] && ((object)Reader.LocalName == (object)_id117_InstanceCmdlets && (object)Reader.NamespaceURI == (object)_id2_Item)) @@ -7580,11 +8192,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations58, ref readerCount58); } + ReadEndElement(); } + o.@StaticCmdlets = (global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata[])ShrinkArray(a_3_0, ca_3_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.StaticCmdletMetadata), false); } } @@ -7621,11 +8236,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Data"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations59, ref readerCount59); } + ReadEndElement(); } + o.@CmdletAdapterPrivateData = (global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData[])ShrinkArray(a_4_0, ca_4_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataData), false); } } @@ -7638,9 +8256,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Version, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:DefaultNoun, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:InstanceCmdlets, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:StaticCmdlets, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletAdapterPrivateData"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations57, ref readerCount57); } + ReadEndElement(); return o; } @@ -7658,6 +8278,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets(); @@ -7671,6 +8292,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -7678,6 +8300,7 @@ public object Read87_EnumMetadataEnumValue() o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations60 = 0; @@ -7709,9 +8332,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdlet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations60, ref readerCount60); } + o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); ReadEndElement(); return o; @@ -7730,6 +8355,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadataInstanceCmdlets(); @@ -7743,6 +8369,7 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { @@ -7750,6 +8377,7 @@ public object Read87_EnumMetadataEnumValue() o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations61 = 0; @@ -7781,9 +8409,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdletParameters, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:GetCmdlet, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Cmdlet"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations61, ref readerCount61); } + o.@Cmdlet = (global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata[])ShrinkArray(a_2, ca_2, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.InstanceCmdletMetadata), true); ReadEndElement(); return o; @@ -7802,6 +8432,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.AssociationAssociatedInstance(); @@ -7813,12 +8444,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations62 = 0; @@ -7846,9 +8479,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletParameterMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations62, ref readerCount62); } + ReadEndElement(); return o; } @@ -7866,6 +8501,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateCount(); @@ -7887,12 +8523,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations63 = 0; @@ -7901,15 +8539,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations63, ref readerCount63); } + ReadEndElement(); return o; } @@ -7927,6 +8567,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateLength(); @@ -7948,12 +8589,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations64 = 0; @@ -7962,15 +8605,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations64, ref readerCount64); } + ReadEndElement(); return o; } @@ -7988,6 +8633,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CmdletParameterMetadataValidateRange(); @@ -8009,12 +8655,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o, @":Min, :Max"); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations65 = 0; @@ -8023,15 +8671,17 @@ public object Read87_EnumMetadataEnumValue() { if (Reader.NodeType == System.Xml.XmlNodeType.Element) { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } else { - UnknownNode((object)o, @""); + UnknownNode((object)o, string.Empty); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations65, ref readerCount65); } + ReadEndElement(); return o; } @@ -8049,6 +8699,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.CommonMethodMetadataReturnValue(); @@ -8060,12 +8711,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations66 = 0; @@ -8093,9 +8746,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Type, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:CmdletOutputMetadata"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations66, ref readerCount66); } + ReadEndElement(); return o; } @@ -8113,6 +8768,7 @@ public object Read87_EnumMetadataEnumValue() else throw CreateUnknownTypeException((System.Xml.XmlQualifiedName)xsiType); } + if (isNull) return null; global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata o; o = new global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata(); @@ -8126,12 +8782,14 @@ public object Read87_EnumMetadataEnumValue() UnknownNode((object)o); } } + Reader.MoveToElement(); if (Reader.IsEmptyElement) { Reader.Skip(); return o; } + Reader.ReadStartElement(); Reader.MoveToContent(); int whileIterations67 = 0; @@ -8178,11 +8836,14 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode(null, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enum"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations68, ref readerCount68); } + ReadEndElement(); } + o.@Enums = (global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum[])ShrinkArray(a_1_0, ca_1_0, typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnum), false); } } @@ -8195,9 +8856,11 @@ public object Read87_EnumMetadataEnumValue() { UnknownNode((object)o, @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Class, http://schemas.microsoft.com/cmdlets-over-objects/2009/11:Enums"); } + Reader.MoveToContent(); CheckReaderCount(ref whileIterations67, ref readerCount67); } + ReadEndElement(); return o; } @@ -8369,7 +9032,7 @@ protected override void InitIDs() _id1_PowerShellMetadata = Reader.NameTable.Add(@"PowerShellMetadata"); _id98_HelpUri = Reader.NameTable.Add(@"HelpUri"); _id91_DefaultValue = Reader.NameTable.Add(@"DefaultValue"); - _id4_Item = Reader.NameTable.Add(@""); + _id4_Item = Reader.NameTable.Add(string.Empty); _id32_Item = Reader.NameTable.Add(@"CommonMethodMetadataReturnValue"); _id43_EnumName = Reader.NameTable.Add(@"EnumName"); _id122_Enums = Reader.NameTable.Add(@"Enums"); @@ -8463,6 +9126,7 @@ protected override System.Xml.Serialization.XmlSerializationReader CreateReader( { return new XmlSerializationReader1(); } + protected override System.Xml.Serialization.XmlSerializationWriter CreateWriter() { return new XmlSerializationWriter1(); @@ -8472,7 +9136,7 @@ protected override System.Xml.Serialization.XmlSerializationWriter CreateWriter( [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class PowerShellMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { return xmlReader.IsStartElement(@"PowerShellMetadata", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); } @@ -8491,9 +9155,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ClassMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ClassMetadata", @""); + return xmlReader.IsStartElement(@"ClassMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8510,9 +9174,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ClassMetadataInstanceCmdletsSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ClassMetadataInstanceCmdlets", @""); + return xmlReader.IsStartElement(@"ClassMetadataInstanceCmdlets", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8529,9 +9193,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class GetCmdletParametersSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"GetCmdletParameters", @""); + return xmlReader.IsStartElement(@"GetCmdletParameters", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8548,9 +9212,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class PropertyMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"PropertyMetadata", @""); + return xmlReader.IsStartElement(@"PropertyMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8567,9 +9231,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class TypeMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"TypeMetadata", @""); + return xmlReader.IsStartElement(@"TypeMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8586,9 +9250,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class AssociationSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"Association", @""); + return xmlReader.IsStartElement(@"Association", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8605,9 +9269,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class AssociationAssociatedInstanceSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"AssociationAssociatedInstance", @""); + return xmlReader.IsStartElement(@"AssociationAssociatedInstance", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8624,9 +9288,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadata", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8643,9 +9307,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataForGetCmdletParameterSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataForGetCmdletParameter", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataForGetCmdletParameter", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8662,9 +9326,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataForGetCmdletFilteringParameterSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataForGetCmdletFilteringParameter", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8681,9 +9345,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataValidateCountSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateCount", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateCount", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8700,9 +9364,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataValidateLengthSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateLength", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateLength", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8719,9 +9383,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataValidateRangeSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateRange", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataValidateRange", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8738,9 +9402,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ObsoleteAttributeMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ObsoleteAttributeMetadata", @""); + return xmlReader.IsStartElement(@"ObsoleteAttributeMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8757,9 +9421,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataForInstanceMethodParameterSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataForInstanceMethodParameter", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataForInstanceMethodParameter", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8776,9 +9440,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletParameterMetadataForStaticMethodParameterSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletParameterMetadataForStaticMethodParameter", @""); + return xmlReader.IsStartElement(@"CmdletParameterMetadataForStaticMethodParameter", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8795,9 +9459,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class QueryOptionSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"QueryOption", @""); + return xmlReader.IsStartElement(@"QueryOption", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8814,9 +9478,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class GetCmdletMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"GetCmdletMetadata", @""); + return xmlReader.IsStartElement(@"GetCmdletMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8833,9 +9497,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CommonCmdletMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CommonCmdletMetadata", @""); + return xmlReader.IsStartElement(@"CommonCmdletMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8852,9 +9516,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ConfirmImpactSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ConfirmImpact", @""); + return xmlReader.IsStartElement(@"ConfirmImpact", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8871,9 +9535,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class StaticCmdletMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"StaticCmdletMetadata", @""); + return xmlReader.IsStartElement(@"StaticCmdletMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8890,9 +9554,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class StaticCmdletMetadataCmdletMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"StaticCmdletMetadataCmdletMetadata", @""); + return xmlReader.IsStartElement(@"StaticCmdletMetadataCmdletMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8909,9 +9573,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CommonMethodMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CommonMethodMetadata", @""); + return xmlReader.IsStartElement(@"CommonMethodMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8928,9 +9592,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class StaticMethodMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"StaticMethodMetadata", @""); + return xmlReader.IsStartElement(@"StaticMethodMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8947,9 +9611,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CommonMethodParameterMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CommonMethodParameterMetadata", @""); + return xmlReader.IsStartElement(@"CommonMethodParameterMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8966,9 +9630,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class StaticMethodParameterMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"StaticMethodParameterMetadata", @""); + return xmlReader.IsStartElement(@"StaticMethodParameterMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -8985,9 +9649,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CmdletOutputMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CmdletOutputMetadata", @""); + return xmlReader.IsStartElement(@"CmdletOutputMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9004,9 +9668,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class InstanceMethodParameterMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"InstanceMethodParameterMetadata", @""); + return xmlReader.IsStartElement(@"InstanceMethodParameterMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9023,9 +9687,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class CommonMethodMetadataReturnValueSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"CommonMethodMetadataReturnValue", @""); + return xmlReader.IsStartElement(@"CommonMethodMetadataReturnValue", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9042,9 +9706,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class InstanceMethodMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"InstanceMethodMetadata", @""); + return xmlReader.IsStartElement(@"InstanceMethodMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9061,9 +9725,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class InstanceCmdletMetadataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"InstanceCmdletMetadata", @""); + return xmlReader.IsStartElement(@"InstanceCmdletMetadata", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9080,9 +9744,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class PropertyQuerySerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"PropertyQuery", @""); + return xmlReader.IsStartElement(@"PropertyQuery", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9099,9 +9763,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class WildcardablePropertyQuerySerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"WildcardablePropertyQuery", @""); + return xmlReader.IsStartElement(@"WildcardablePropertyQuery", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9118,9 +9782,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ItemsChoiceTypeSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ItemsChoiceType", @""); + return xmlReader.IsStartElement(@"ItemsChoiceType", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9137,9 +9801,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class ClassMetadataDataSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"ClassMetadataData", @""); + return xmlReader.IsStartElement(@"ClassMetadataData", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9156,9 +9820,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class EnumMetadataEnumSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"EnumMetadataEnum", @""); + return xmlReader.IsStartElement(@"EnumMetadataEnum", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9175,9 +9839,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR [System.CodeDom.Compiler.GeneratedCodeAttribute("sgen", "4.0")] internal sealed class EnumMetadataEnumValueSerializer : XmlSerializer1 { - public override System.Boolean CanDeserialize(System.Xml.XmlReader xmlReader) + public override bool CanDeserialize(System.Xml.XmlReader xmlReader) { - return xmlReader.IsStartElement(@"EnumMetadataEnumValue", @""); + return xmlReader.IsStartElement(@"EnumMetadataEnumValue", string.Empty); } protected override void Serialize(object objectToSerialize, System.Xml.Serialization.XmlSerializationWriter writer) @@ -9195,7 +9859,9 @@ protected override object Deserialize(System.Xml.Serialization.XmlSerializationR internal class XmlSerializerContract : global::System.Xml.Serialization.XmlSerializerImplementation { public override global::System.Xml.Serialization.XmlSerializationReader Reader { get { return new XmlSerializationReader1(); } } + public override global::System.Xml.Serialization.XmlSerializationWriter Writer { get { return new XmlSerializationWriter1(); } } + private System.Collections.Hashtable _readMethods = null; public override System.Collections.Hashtable ReadMethods { @@ -9244,9 +9910,11 @@ public override System.Collections.Hashtable ReadMethods _tmp[@"Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue::"] = @"Read87_EnumMetadataEnumValue"; if (_readMethods == null) _readMethods = _tmp; } + return _readMethods; } } + private System.Collections.Hashtable _writeMethods = null; public override System.Collections.Hashtable WriteMethods { @@ -9295,9 +9963,11 @@ public override System.Collections.Hashtable WriteMethods _tmp[@"Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue::"] = @"Write87_EnumMetadataEnumValue"; if (_writeMethods == null) _writeMethods = _tmp; } + return _writeMethods; } } + private System.Collections.Hashtable _typedSerializers = null; public override System.Collections.Hashtable TypedSerializers { @@ -9346,10 +10016,12 @@ public override System.Collections.Hashtable TypedSerializers _tmp.Add(@"Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery::", new WildcardablePropertyQuerySerializer()); if (_typedSerializers == null) _typedSerializers = _tmp; } + return _typedSerializers; } } - public override System.Boolean CanSerialize(System.Type type) + + public override bool CanSerialize(System.Type type) { if (type == typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata)) return true; if (type == typeof(global::Microsoft.PowerShell.Cmdletization.Xml.ClassMetadata)) return true; @@ -9391,6 +10063,7 @@ public override System.Boolean CanSerialize(System.Type type) if (type == typeof(global::Microsoft.PowerShell.Cmdletization.Xml.EnumMetadataEnumValue)) return true; return false; } + public override System.Xml.Serialization.XmlSerializer GetSerializer(System.Type type) { if (type == typeof(global::Microsoft.PowerShell.Cmdletization.Xml.PowerShellMetadata)) return new PowerShellMetadataSerializer(); @@ -9435,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 new file mode 100644 index 00000000000..67ee8b0b0dc --- /dev/null +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xsd @@ -0,0 +1,1050 @@ + + + + + + + + + + + + + + + + + + + + +]> + + + + + 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 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 + a module manifest (a ".psd1" file) and then importing the ".psd1" file by Import-Module cmdlet. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + (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), + long (System.Int64), + ulong (System.UInt64). + + + + + + + + BitwiseFlags attribute specifies if the .NET enum will be decorated with a System.FlagsAttribute. + + + + + + + + + + + + + + + + + + + + + + Version element is semantically equivalent to the ModuleVersion entry in a module manifest (psd1) file. + + + + + + + + DefaultNoun element specified the default noun for cmdlets defined in this document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + The class specified here has to be derived from Microsoft.PowerShell.Cmdletization.CmdletAdapter class. + + + + + + + + ClassName attribute specified the class that the cmdlets work against. + + Example: "root/cimv2/Win32_Process" + + + + + + + + ClassVersion attribute describes the version of the implementation of the class from the ClassName attribute. + + Contents of the ClassVersion attribute are passed without interpretation inside + WMI's custom operation option named "MI_OPERATIONOPTIONS_PROVIDERVERSION". + WMI infrastructure will compare this value against the contents of the [ClassVersion] qualifier of the WMI class + and provide descriptive error message if it cannot invoke the WMI provider - i.e. if the client attempts to use a non-existant method, property or parameter). + If WMI infrastructure can invoke the WMI provider, then the provider is responsible for further versioning decisions. + + + + + + + + + + + + + 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 + 1) cmdlet parameters defined through GetCmdletParameters elements + 2) cmdlet parameters mapped to input parameters of the method defined by Method element + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + 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) + - To define aliases for the cmdlet + - To use obsolete attribute for the cmdlet + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + 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. + + + + + + + + Aliases attribute specifies a white-space separated list of aliases for the cmdlet. + + + + + + + + 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. + + + + + + + + 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" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + "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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GetCmdletParameters element defines cmdlet parameters used to select object instances. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + SourceRole attribute specifies the role of the cmdlet argument (in the association between the cmdlet argument and the instances the cmdlet acts against). + + SourceRole attribute is equivalent to the sourceRole parameter of EnumerateAssociatedInstances method of Microsoft.Management.Infrastructure.CimSession class. + SourceRole should be the name of a property on the class specified in the Association attribute. + + + + + + + + ResultRole attribute specifies the role of the cmdlet argument (in the association between the cmdlet argument and the instances the cmdlet acts against). + + ResultRole attribute is equivalent to the resultRole parameter of EnumerateAssociatedInstances method of Microsoft.Management.Infrastructure.CimSession class. + ResultRole should be the name of a property on the class specified in the Association attribute. + + + + + + + + + + + + + + + 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)) + + Example for <RegularQuery AllowGlobbing="false" > element that is applied to a Name property: + The following cmdlet invocation: + Get-MyObject -LiteralName p*,q* + will be translated into the following WQL query: + 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* + will be translated into the following WQL query: + 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)) + + + + + + + + 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 + will be translated into the following WQL query: + SELECT * FROM MyObject WHERE (WorkingSet >= 123) + + + + + + + + MaxValueQuery element defines a cmdlet parameter that limits which objects will be processed by the cmdlet + - only objects with a property value less than or equal to the cmdlet parameter argument will be processed. + + Example for <MaxValueQuery> element that is applied to an WorkingSet property: + The following cmdlet invocation: + Get-MyObject -MaxWorkingSet 123 + will be translated into the following WQL query: + SELECT * FROM MyObject WHERE (WorkingSet =< 123) + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Aliases attribute specifies a white-space separated list of aliases for the cmdlet parameter. + + + + + + + + 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"> + ... + <!-- "Name" is used as the cmdlet parameter name --> + <RegularQuery AllowGlobbing="true" /> + <!-- "LiteralName" is used as the cmdlet parameter name --> + <RegularQuery AllowGlobbing="false"> + <CmdletParameterMetadata PSName="LiteralName" /> + </RegularQuery> + </Property> + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + 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" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Represents a version number that consist of two to four components: major, minor, build, and revision. + String representation of a version is "major.minor[.build[.revision]]" (optional components are shown in square brackets). + All defined components MUST be integers greater than or equal to 0. + For example, if the major number is 6, the minor number is 2, the build number is 1, and the revision number is 3, then string representation of the version would be "6.2.1.3". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs index 9623adb1af3..202c3e98e48 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -8,7 +7,9 @@ using System.Globalization; using System.Management.Automation; using System.Reflection; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Cim @@ -20,16 +21,14 @@ 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; + // propertyToAdd.adapter = this; return propertyToAdd; } @@ -49,22 +48,20 @@ 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; + // psComputerNameProperty.adapter = this; return psComputerNameProperty; } /// - /// /// /// /// public override System.Collections.ObjectModel.Collection GetProperties(object baseObject) { // baseObject should never be null - CimInstance cimInstance = baseObject as CimInstance; - if (null == cimInstance) + 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) { @@ -97,7 +94,6 @@ public override System.Collections.ObjectModel.Collection Get } /// - /// /// /// /// @@ -106,12 +102,11 @@ public override PSAdaptedProperty GetProperty(object baseObject, string property { if (propertyName == null) { - throw new PSArgumentNullException("propertyName"); + throw new PSArgumentNullException(nameof(propertyName)); } // baseObject should never be null - CimInstance cimInstance = baseObject as CimInstance; - if (null == cimInstance) + if (baseObject is not CimInstance cimInstance) { string msg = string.Format(CultureInfo.InvariantCulture, CimInstanceTypeAdapterResources.BaseObjectNotCimInstance, @@ -136,6 +131,43 @@ public override PSAdaptedProperty GetProperty(object baseObject, string property return null; } + /// + public override PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) + { + if (predicate == null) + { + throw new PSArgumentNullException(nameof(predicate)); + } + + // baseObject should never be null + if (baseObject is not CimInstance cimInstance) + { + string msg = string.Format( + CultureInfo.InvariantCulture, + CimInstanceTypeAdapterResources.BaseObjectNotCimInstance, + "baseObject", + typeof(CimInstance).ToString()); + throw new PSInvalidOperationException(msg); + } + + if (predicate(RemotingConstants.ComputerNameNoteProperty)) + { + PSAdaptedProperty prop = GetPSComputerNameAdapter(cimInstance); + return prop; + } + + foreach (CimProperty cimProperty in cimInstance.CimInstanceProperties) + { + if (cimProperty != null && predicate(cimProperty.Name)) + { + PSAdaptedProperty prop = GetCimPropertyAdapter(cimProperty, baseObject, cimProperty.Name); + return prop; + } + } + + return null; + } + internal static string CimTypeToTypeNameDisplayString(CimType cimType) { switch (cimType) @@ -155,19 +187,14 @@ internal static string CimTypeToTypeNameDisplayString(CimType cimType) } /// - /// /// /// /// public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty) { - if (null == adaptedProperty) - { - throw new ArgumentNullException("adaptedProperty"); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); - CimProperty cimProperty = adaptedProperty.Tag as CimProperty; - if (cimProperty != null) + if (adaptedProperty.Tag is CimProperty cimProperty) { return CimTypeToTypeNameDisplayString(cimProperty.CimType); } @@ -177,23 +204,18 @@ public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty) return ToStringCodeMethods.Type(typeof(string)); } - throw new ArgumentNullException("adaptedProperty"); + throw new ArgumentNullException(nameof(adaptedProperty)); } /// - /// /// /// /// public override object GetPropertyValue(PSAdaptedProperty adaptedProperty) { - if (null == adaptedProperty) - { - throw new ArgumentNullException("adaptedProperty"); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); - CimProperty cimProperty = adaptedProperty.Tag as CimProperty; - if (cimProperty != null) + if (adaptedProperty.Tag is CimProperty cimProperty) { return cimProperty.Value; } @@ -204,28 +226,23 @@ public override object GetPropertyValue(PSAdaptedProperty adaptedProperty) return cimInstance.GetCimSessionComputerName(); } - throw new ArgumentNullException("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) @@ -240,20 +257,19 @@ private List GetInheritanceChain(CimInstance cimInstance) break; } } + return inheritanceChain; } /// - /// /// /// /// public override Collection GetTypeNameHierarchy(object baseObject) { - var cimInstance = baseObject as CimInstance; - if (null == cimInstance) + if (baseObject is not CimInstance cimInstance) { - throw new ArgumentNullException("baseObject"); + throw new ArgumentNullException(nameof(baseObject)); } var typeNamesWithNamespace = new List(); @@ -287,7 +303,7 @@ public override Collection GetTypeNameHierarchy(object baseObject) if (baseObject != null) { - for (Type type = baseObject.GetType(); type != null; type = type.GetTypeInfo().BaseType) + for (Type type = baseObject.GetType(); type != null; type = type.BaseType) { result.Add(type.FullName); } @@ -297,7 +313,6 @@ public override Collection GetTypeNameHierarchy(object baseObject) } /// - /// /// /// /// @@ -312,7 +327,6 @@ public override bool IsGettable(PSAdaptedProperty adaptedProperty) } /// - /// /// /// /// @@ -324,33 +338,28 @@ public override bool IsSettable(PSAdaptedProperty adaptedProperty) return writeQualifierValue; */ - if (null == adaptedProperty) + if (adaptedProperty == null) { 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; } /// - /// /// /// /// public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value) { - if (null == adaptedProperty) - { - throw new ArgumentNullException("adaptedProperty"); - } + ArgumentNullException.ThrowIfNull(adaptedProperty); if (!IsSettable(adaptedProperty)) { @@ -379,6 +388,7 @@ public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object Dbg.Assert(paramType != null, "'default' case should only be used for well-defined CimType->DotNetType conversions"); break; } + valueToSet = Adapter.PropertySetAndMethodArgumentConvertTo( value, paramType, CultureInfo.InvariantCulture); } diff --git a/src/System.Management.Automation/engine/AliasInfo.cs b/src/System.Management.Automation/engine/AliasInfo.cs index 2095b707248..94acdecf628 100644 --- a/src/System.Management.Automation/engine/AliasInfo.cs +++ b/src/System.Management.Automation/engine/AliasInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -15,29 +14,23 @@ public class AliasInfo : CommandInfo #region ctor /// - /// Creates an instance of the AliasInfo class with the specified name and referenced command + /// Creates an instance of the AliasInfo class with the specified name and referenced command. /// - /// /// /// The name of the command. /// - /// /// /// The token that the alias refers to. /// - /// /// /// The execution context for this engine, used to lookup the current session state. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// internal AliasInfo(string name, string definition, ExecutionContext context) : base(name, CommandTypes.Alias) { _definition = definition; @@ -50,33 +43,26 @@ internal AliasInfo(string name, string definition, ExecutionContext context) : b } /// - /// Creates an instance of the AliasInfo class with the specified name and referenced command + /// Creates an instance of the AliasInfo class with the specified name and referenced command. /// - /// /// /// The name of the command. /// - /// /// /// The token that the alias refers to. /// - /// /// /// The execution context for this engine instance, used to look up session state. /// - /// /// /// The options to set on the alias. Note, Constant can only be set at creation time. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// internal AliasInfo( string name, string definition, @@ -131,18 +117,16 @@ public CommandInfo ReferencedCommand return referencedCommand; } - } // ReferencedCommand + } /// /// Gets the command information for the command that /// the alias eventually resolves to. /// - /// /// /// An alias may reference another alias. This property follows the reference /// chain of aliases to its end. /// - /// /// - [DebuggerDisplay("Command = {commandInfo}")] + [DebuggerDisplay("Command = {_commandInfo}")] public abstract class InternalCommand { #region private_members @@ -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); } } /// @@ -85,11 +84,15 @@ internal InvocationInfo MyInvocation internal PSObject currentObjectInPipeline = AutomationNull.Value; /// - /// Gets or sets the current pipeline object under consideration + /// Gets or sets the current pipeline object under consideration. /// internal PSObject CurrentPipelineObject { - get { return currentObjectInPipeline; } + get + { + return currentObjectInPipeline; + } + set { currentObjectInPipeline = value; @@ -103,16 +106,17 @@ internal PSHost PSHostInternal { get { return _CBhost; } } + private PSHost _CBhost; /// - /// Internal helper to get to SessionState + /// Internal helper to get to SessionState. /// - /// internal SessionState InternalState { get { return _state; } } + private SessionState _state; /// @@ -127,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. /// @@ -137,6 +148,7 @@ internal bool IsStopping internal CommandInfo CommandInfo { get { return _commandInfo; } + set { _commandInfo = value; } } @@ -152,13 +164,18 @@ internal CommandInfo CommandInfo /// internal ExecutionContext Context { - get { return _context; } + get + { + return _context; + } + set { if (value == null) { throw PSTraceSource.NewArgumentNullException("Context"); } + _context = value; Diagnostics.Assert(_context.EngineHostInterface is InternalHost, "context.EngineHostInterface is not an InternalHost"); _CBhost = (InternalHost)_context.EngineHostInterface; @@ -168,6 +185,7 @@ internal ExecutionContext Context _state = new SessionState(_context.EngineSessionState); } } + private ExecutionContext _context; /// @@ -178,6 +196,7 @@ public CommandOrigin CommandOrigin { get { return CommandOriginInternal; } } + internal CommandOrigin CommandOriginInternal = CommandOrigin.Internal; #endregion public_properties @@ -220,11 +239,17 @@ internal virtual void DoStopProcessing() { } - #endregion Override + /// + /// When overridden in the derived class, performs clean-up after the command execution. + /// + internal virtual void DoCleanResource() + { + } + #endregion Override /// - /// throws if the pipeline is stopping + /// Throws if the pipeline is stopping. /// /// internal void ThrowIfStopping() @@ -237,7 +262,7 @@ internal void ThrowIfStopping() /// /// IDisposable implementation - /// When the command is complete, release the associated members + /// When the command is complete, release the associated members. /// /// /// Using InternalDispose instead of Dispose pattern because this @@ -258,9 +283,48 @@ 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. + /// + public enum ErrorView + { + /// Existing all red multi-line output. + NormalView = 0, + + /// Only show category information. + CategoryView = 1, + + /// 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 + #region ActionPreference /// /// Defines the Action Preference options. These options determine @@ -272,17 +336,25 @@ namespace System.Management.Automation public enum ActionPreference { /// Ignore this event and continue - SilentlyContinue, + SilentlyContinue = 0, + /// Stop the command - Stop, + Stop = 1, + /// Handle this event as normal and continue - Continue, + Continue = 2, + /// Ask whether to stop or continue - Inquire, + Inquire = 3, + /// Ignore the event completely (not even logging it to the target stream) - Ignore, - /// Suspend the command for further diagnosis. Supported only for workflows. - Suspend, + Ignore = 4, + + /// Reserved for future use. + Suspend = 5, + + /// Enter the debugger. + Break = 6, } // enum ActionPreference #endregion ActionPreference @@ -319,7 +391,7 @@ public enum ConfirmImpact /// confirmed by default unless otherwise specified. /// High, - } // enum ConfirmImpact + } #endregion ConfirmImpact /// @@ -331,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. /// @@ -376,7 +448,7 @@ public SessionState SessionState return this.InternalState; } } - } // SessionState + } /// /// Gets the event manager for the current runspace. @@ -390,10 +462,10 @@ public PSEventManager Events return this.Context.Events; } } - } // Events + } /// - /// Repository for jobs + /// Repository for jobs. /// public JobRepository JobRepository { @@ -421,7 +493,7 @@ public JobManager JobManager } /// - /// Repository for runspaces + /// Repository for runspaces. /// internal RunspaceRepository RunspaceRepository { @@ -440,10 +512,10 @@ public ProviderIntrinsics InvokeProvider { using (PSTransactionManager.GetEngineProtectionScope()) { - return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); + return _invokeProvider ??= new ProviderIntrinsics(this); } } - } // InvokeProvider + } #region Provider wrappers @@ -454,7 +526,7 @@ public PathInfo CurrentProviderLocation(string providerId) { if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("providerId"); + throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } PathInfo result = SessionState.Path.CurrentProviderLocation(providerId); @@ -470,7 +542,7 @@ public string GetUnresolvedProviderPathFromPSPath(string path) { return SessionState.Path.GetUnresolvedProviderPathFromPSPath(path); } - } // GetUnresolvedProviderPathFromPSPath + } /// public Collection GetResolvedProviderPathFromPSPath(string path, out ProviderInfo provider) @@ -479,7 +551,7 @@ public Collection GetResolvedProviderPathFromPSPath(string path, out Pro { return SessionState.Path.GetResolvedProviderPathFromPSPath(path, out provider); } - } // GetResolvedProviderPathFromPSPath + } #endregion Provider wrappers #endregion internal_members @@ -510,17 +582,16 @@ public object GetVariableValue(string name) { return this.SessionState.PSVariable.GetValue(name); } - } // GetVariableValue + } /// - public object GetVariableValue(string name, object defaultValue) { using (PSTransactionManager.GetEngineProtectionScope()) { return this.SessionState.PSVariable.GetValue(name, defaultValue); } - } // GetVariableValue + } #endregion PSVariable APIs @@ -529,6 +600,5 @@ public object GetVariableValue(string name, object defaultValue) #endregion Parameter methods #endregion public_methods - } // PSCmdlet + } } - diff --git a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs index 814f671f265..9956cf9fa92 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs @@ -1,18 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections.ObjectModel; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; +using System.Collections.ObjectModel; 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 @@ -57,7 +53,8 @@ public CommandCompletion(Collection matches, int currentMatchI [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Collection CompletionMatches { get; set; } - internal static readonly IList EmptyCompletionResult = Utils.EmptyArray(); + internal static readonly IList EmptyCompletionResult = Array.Empty(); + private static readonly CommandCompletion s_emptyCommandCompletion = new CommandCompletion( new Collection(EmptyCompletionResult), -1, -1, -1); @@ -66,7 +63,6 @@ public CommandCompletion(Collection matches, int currentMatchI #region public methods /// - /// /// /// /// @@ -75,7 +71,7 @@ public static Tuple MapStringInputToParsedInput(s { if (cursorIndex > input.Length) { - throw PSTraceSource.NewArgumentException("cursorIndex"); + throw PSTraceSource.NewArgumentException(nameof(cursorIndex)); } Token[] tokens; @@ -88,15 +84,14 @@ public static Tuple MapStringInputToParsedInput(s } /// - /// /// - /// The input to complete - /// The index of the cursor in the input - /// Optional options to configure how completion is performed + /// The input to complete. + /// The index of the cursor in the input. + /// Optional options to configure how completion is performed. /// public static CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options) { - if (input == null) + if (input == null || input.Length == 0) { return s_emptyCommandCompletion; } @@ -106,28 +101,32 @@ public static CommandCompletion CompleteInput(string input, int cursorIndex, Has } /// - /// /// - /// Ast for pre-parsed input - /// Tokens for pre-parsed input + /// Ast for pre-parsed input. + /// Tokens for pre-parsed input. /// - /// Optional options to configure how completion is performed + /// Optional options to configure how completion is performed. /// public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options) { if (ast == null) { - throw PSTraceSource.NewArgumentNullException("ast"); + throw PSTraceSource.NewArgumentNullException(nameof(ast)); } if (tokens == null) { - throw PSTraceSource.NewArgumentNullException("tokens"); + throw PSTraceSource.NewArgumentNullException(nameof(tokens)); } if (positionOfCursor == null) { - throw PSTraceSource.NewArgumentNullException("positionOfCursor"); + throw PSTraceSource.NewArgumentNullException(nameof(positionOfCursor)); + } + + if (ast.Extent.Text.Length == 0) + { + return s_emptyCommandCompletion; } return CompleteInputImpl(ast, tokens, positionOfCursor, options); @@ -135,33 +134,32 @@ public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPo /// /// 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 + /// The input script to complete. + /// The offset in where completion is requested. /// Optional parameter that specifies configurable options for completion. - /// The powershell to use to invoke the script function TabExpansion2 + /// The powershell to use to invoke the script function TabExpansion2. /// A collection of completions with the replacement start and length. [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; } if (cursorIndex > input.Length) { - throw PSTraceSource.NewArgumentException("cursorIndex"); + throw PSTraceSource.NewArgumentException(nameof(cursorIndex)); } if (powershell == null) { - throw PSTraceSource.NewArgumentNullException("powershell"); + throw PSTraceSource.NewArgumentNullException(nameof(powershell)); } // 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); @@ -184,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; } } } @@ -208,39 +196,43 @@ 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 + /// The ast for pre-parsed input. /// /// - /// Optional options to configure how completion is performed - /// The powershell to use to invoke the script function TabExpansion2 + /// Optional options to configure how completion is performed. + /// The powershell to use to invoke the script function TabExpansion2. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "powershell")] public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options, PowerShell powershell) { if (ast == null) { - throw PSTraceSource.NewArgumentNullException("ast"); + throw PSTraceSource.NewArgumentNullException(nameof(ast)); } if (tokens == null) { - throw PSTraceSource.NewArgumentNullException("tokens"); + throw PSTraceSource.NewArgumentNullException(nameof(tokens)); } if (cursorPosition == null) { - throw PSTraceSource.NewArgumentNullException("cursorPosition"); + throw PSTraceSource.NewArgumentNullException(nameof(cursorPosition)); } if (powershell == null) { - throw PSTraceSource.NewArgumentNullException("powershell"); + 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); @@ -259,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); } } @@ -339,12 +317,12 @@ internal static CommandCompletion CompleteInputInDebugger(string input, int curs if (cursorIndex > input.Length) { - throw PSTraceSource.NewArgumentException("cursorIndex"); + throw PSTraceSource.NewArgumentException(nameof(cursorIndex)); } if (debugger == null) { - throw PSTraceSource.NewArgumentNullException("debugger"); + throw PSTraceSource.NewArgumentNullException(nameof(debugger)); } Command cmd = new Command("TabExpansion2"); @@ -358,32 +336,32 @@ internal static CommandCompletion CompleteInputInDebugger(string input, int curs /// /// Command completion while in debug break mode. /// - /// The ast for pre-parsed input + /// The ast for pre-parsed input. /// /// - /// Optional options to configure how completion is performed - /// Current debugger - /// Command completion + /// Optional options to configure how completion is performed. + /// Current debugger. + /// Command completion. internal static CommandCompletion CompleteInputInDebugger(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options, Debugger debugger) { if (ast == null) { - throw PSTraceSource.NewArgumentNullException("ast"); + throw PSTraceSource.NewArgumentNullException(nameof(ast)); } if (tokens == null) { - throw PSTraceSource.NewArgumentNullException("tokens"); + throw PSTraceSource.NewArgumentNullException(nameof(tokens)); } if (cursorPosition == null) { - throw PSTraceSource.NewArgumentNullException("cursorPosition"); + throw PSTraceSource.NewArgumentNullException(nameof(cursorPosition)); } if (debugger == null) { - throw PSTraceSource.NewArgumentNullException("debugger"); + throw PSTraceSource.NewArgumentNullException(nameof(debugger)); } // For remote debugging just pass string input. @@ -520,803 +498,65 @@ private static CommandCompletion CallScriptWithAstParameterSet(Ast ast, Token[] // This is the start of the real implementation of autocomplete/intellisense/tab completion private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options) { +#if LEGACYTELEMETRY + // We could start collecting telemetry at a later date. + // We will leave the #if to remind us that we did this once. var sw = new Stopwatch(); sw.Start(); - +#endif using (var powershell = PowerShell.Create(RunspaceMode.CurrentRunspace)) { 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; - sw.Stop(); -#if LEGACYTELEMETRY - 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; + int replacementIndex = -1; + int replacementLength = -1; + List results = null; - var aliasInfo = executionContext.EngineSessionState.GetAlias("TabExpansion"); - if (aliasInfo != null) - return true; - - return false; - } - - private static List InvokeLegacyTabExpansion(PowerShell powershell, string input, int cursorIndex, bool remoteToWin7, out int replacementIndex, out int replacementLength) - { - List results = null; - - var legacyInput = (cursorIndex != input.Length) ? input.Substring(0, cursorIndex) : input; - char quote; - var lastword = LastWordFinder.FindLastWord(legacyInput, out replacementIndex, out quote); - replacementLength = legacyInput.Length - replacementIndex; - var helper = new PowerShellExecutionHelper(powershell); - - powershell.AddCommand("TabExpansion").AddArgument(legacyInput).AddArgument(lastword); - - Exception exceptionThrown; - var oldResults = helper.ExecuteCurrentPowerShell(out exceptionThrown); - if (oldResults != null) - { - results = new List(); - foreach (var oldResult in oldResults) { - var completionResult = PSObject.Base(oldResult) as CompletionResult; - if (completionResult == null) + // 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.Compare( - commandAndName.CommandName.ShortName, - nextCommandAndName.CommandName.ShortName, - StringComparison.OrdinalIgnoreCase) == 0) - { - AddCommandResult(commandAndName, true, completingAtStartOfLine, quote, results); - previousMatched = true; - } - else + try { - AddCommandResult(commandAndName, previousMatched, completingAtStartOfLine, quote, results); - previousMatched = false; + var completionAnalysis = new CompletionAnalysis(ast, tokens, positionOfCursor, options); + results = completionAnalysis.GetResults(powershell, out replacementIndex, out replacementLength); } - i++; - } - } - - #endregion "Handle Command" - - #region "Handle File Names" - - internal static List PSv2GenerateMatchSetOfFiles(PowerShellExecutionHelper helper, string lastWord, bool completingAtStartOfLine, string quote) - { - var results = new List(); - - // lastWord is treated as an PSPath. The match set includes those items that match that - // path, namely, the union of: - // (S1) the sorted set of items matching the last word - // (S2) the sorted set of items matching the last word + * - // If the last word contains no wildcard characters, then S1 is the empty set. S1 is always - // a subset of S2, but we want to present S1 first, then (S2 - S1) next. The idea is that - // if the user typed some wildcard characters, they'd prefer to see those matches before - // all of the rest. - - // Determine if we need to quote the paths we parse - - lastWord = lastWord ?? string.Empty; - bool isLastWordEmpty = String.IsNullOrEmpty(lastWord); - bool lastCharIsStar = !isLastWordEmpty && lastWord.EndsWith("*", StringComparison.Ordinal); - 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; - } + var completionResults = results ?? EmptyCompletionResult; - private static string AddQuoteIfNecessary(string completionText, string quote, bool completingAtStartOfLine) - { - if (completionText.IndexOfAny(s_charsRequiringQuotedString) != -1) - { - bool needAmpersand = quote.Length == 0 && completingAtStartOfLine; - string quoteInUse = quote.Length == 0 ? "'" : quote; - completionText = quoteInUse == "'" ? completionText.Replace("'", "''") : completionText; - completionText = quoteInUse + completionText + quoteInUse; - completionText = needAmpersand ? "& " + completionText : completionText; - } - else - { - completionText = quote + completionText + quote; - } - - return completionText; - } - - private static IEnumerable CombineMatchSets(List s1, List s2) - { - if (s1 == null || s1.Count < 1) - { - // only s2 contains results; which may be null or empty - return s2; - } - - if (s2 == null || s2.Count < 1) - { - // only s1 contains results - return s1; - } - - // s1 and s2 contain results - Diagnostics.Assert(s1 != null && s1.Count > 0, "s1 should have results"); - Diagnostics.Assert(s2 != null && s2.Count > 0, "if s1 has results, s2 must also"); - Diagnostics.Assert(s1.Count <= s2.Count, "s2 should always be larger than s1"); - - var result = new List(); - - // 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.Compare(s2[i].Path, s1[j].Path, StringComparison.CurrentCultureIgnoreCase) == 0) - { - ++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("~", StringComparison.OrdinalIgnoreCase) || - lastWord.StartsWith("\\", StringComparison.OrdinalIgnoreCase) || - lastWord.StartsWith("/", StringComparison.OrdinalIgnoreCase)) - { - 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 7dbee431f2e..9c4a61a6833 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1,8 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -10,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 { @@ -21,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) @@ -96,26 +108,32 @@ private static bool IsCursorOutsideOfExtent(IScriptPosition cursor, IScriptExten return cursor.Offset < extent.StartOffset || cursor.Offset > extent.EndOffset; } - - internal CompletionContext CreateCompletionContext(PowerShell powerShell) - { - var typeInferenceContext = new TypeInferenceContext(powerShell); - return InitializeCompletionContext(typeInferenceContext); - } - internal CompletionContext CreateCompletionContext(TypeInferenceContext typeInferenceContext) + internal readonly struct AstAnalysisContext { - return InitializeCompletionContext(typeInferenceContext); + internal AstAnalysisContext(Token tokenAtCursor, Token tokenBeforeCursor, List relatedAsts, int replacementIndex) + { + TokenAtCursor = tokenAtCursor; + TokenBeforeCursor = tokenBeforeCursor; + RelatedAsts = relatedAsts; + ReplacementIndex = replacementIndex; + } + + 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; @@ -127,29 +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, @@ -157,16 +208,33 @@ 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) { @@ -183,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 " @@ -194,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) @@ -259,6 +540,7 @@ private static bool CompleteAgainstStatementFlags(Ast scriptAst, Ast lastAst, To { errorStatement = last as ErrorStatementAst; if (errorStatement != null) { break; } + last = last.Parent; } @@ -277,6 +559,7 @@ private static bool CompleteAgainstStatementFlags(Ast scriptAst, Ast lastAst, To return true; } } + break; default: @@ -312,12 +595,16 @@ internal List GetResults(PowerShell powerShell, out int replac try { // Tab expansion is called from a trusted function - we should apply ConstrainedLanguage if necessary. - if (ExecutionContext.HasEverUsedConstrainedLanguage) + if (completionContext.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode) { previousLanguageMode = completionContext.ExecutionContext.LanguageMode; 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 { @@ -328,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; @@ -356,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) @@ -386,7 +679,7 @@ internal List GetResultHelper(CompletionContext completionCont } // Handle scenarios like this: dir -path: - if (completionContext.WordToComplete.EndsWith(":", StringComparison.Ordinal)) + if (completionContext.WordToComplete.EndsWith(':')) { replacementIndex = tokenAtCursor.Extent.EndScriptPosition.Offset; replacementLength = 0; @@ -398,27 +691,59 @@ internal List GetResultHelper(CompletionContext completionCont { result = CompletionCompleters.CompleteCommandParameter(completionContext); } + break; case TokenKind.Dot: case TokenKind.ColonColon: + 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: case TokenKind.StringLiteral: - result = GetResultForString(completionContext, ref replacementIndex, ref replacementLength, isQuotedString); + // Search to see if we're looking at an assignment + if (lastAst.Parent is CommandExpressionAst + && lastAst.Parent.Parent is AssignmentStatementAst assignmentAst) + { + // Handle scenarios like `$ErrorActionPreference = '` + if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst, out List completions)) + { + 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); break; case TokenKind.RBracket: @@ -442,18 +767,30 @@ internal List GetResultHelper(CompletionContext completionCont select new CompletionResult(completionText, entry.ListItemText, entry.ResultType, entry.ToolTip)).ToList(); } } + 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 { // @@ -466,9 +803,9 @@ 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; case TokenKind.AtCurly: // Handle scenarios such as 'Sort-Object @{' and 'gci | Format-Table @{' @@ -477,6 +814,17 @@ internal List GetResultHelper(CompletionContext completionCont replacementLength = 0; break; + case TokenKind.Semi: + // Handle scenarios such as 'gci | Format-Table @{Label=...;' + if (lastAst is HashtableAst) + { + result = GetResultForHashtable(completionContext); + replacementIndex += 1; + replacementLength = 0; + } + + break; + case TokenKind.Number: // Handle scenarios such as Get-Process -Id 5 || Get-Process -Id 5210, 3 || Get-Process -Id: 5210, 3 if (lastAst is ConstantExpressionAst && @@ -490,6 +838,16 @@ internal List GetResultHelper(CompletionContext completionCont replacementIndex = completionContext.ReplacementIndex; replacementLength = completionContext.ReplacementLength; } + else if (lastAst.Parent is CommandExpressionAst + && lastAst.Parent.Parent is AssignmentStatementAst assignmentAst2) + { + // Handle scenarios like '[ValidateSet(11,22)][int]$i = 11; $i = 2' + if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst2, out List completions)) + { + result = completions; + } + } + break; case TokenKind.Redirection: @@ -501,13 +859,14 @@ internal List GetResultHelper(CompletionContext completionCont completionContext.ReplacementLength = replacementLength = 0; result = new List(CompletionCompleters.CompleteFilename(completionContext)); } + break; case TokenKind.Minus: // Handle operator completion: 55 - || "string" - || (Get-Something) - if (CompleteOperator(tokenAtCursor, lastAst)) { - result = CompletionCompleters.CompleteOperator(""); + result = CompletionCompleters.CompleteOperator(string.Empty); break; } @@ -518,6 +877,7 @@ internal List GetResultHelper(CompletionContext completionCont result = CompletionCompleters.CompleteStatementFlags(statementKind, completionContext.WordToComplete); break; } + break; case TokenKind.DynamicKeyword: @@ -541,10 +901,39 @@ 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 not DynamicKeywordStatementAst && CheckForPendingAssignment(hashTableAst)) + { + // Handle scenarios such as 'gci | Format-Table @{Label=' if incomplete parsing of the assignment. + return null; + } + else if (lastAst is AssignmentStatementAst assignmentAst2) + { + completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length; + completionContext.ReplacementLength = replacementLength = 0; + + // Handle scenarios like '$ErrorActionPreference =' + if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst2, out List completions)) + { + 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 auto completion for enum/dependson property of DSC resource, + // Handle scenarios such as 'configuration foo { File ab { Attributes =' + // (auto completion for enum/dependson property of DSC resource), // cursor is right after '=', '(' or '@(' // // Configuration config @@ -555,24 +944,93 @@ 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; } - default: - if ((tokenAtCursor.TokenFlags & TokenFlags.Keyword) != 0) - { - completionContext.WordToComplete = tokenAtCursor.Text; - // Handle the file name completion - result = CompleteFileNameAsCommand(completionContext); + 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; - // Handle the command name completion - var commandNameResult = CompletionCompleters.CompleteCommand(completionContext); - if (commandNameResult != null && commandNameResult.Count > 0) - { + 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; + + // Handle the file name completion + result = CompleteFileNameAsCommand(completionContext); + + // Handle the command name completion + var commandNameResult = CompletionCompleters.CompleteCommand(completionContext); + if (commandNameResult != null && commandNameResult.Count > 0) + { result.AddRange(commandNameResult); } } @@ -581,13 +1039,14 @@ internal List GetResultHelper(CompletionContext completionCont replacementIndex = -1; replacementLength = -1; } + break; } } else { IScriptPosition cursor = completionContext.CursorPosition; - bool isCursorLineEmpty = String.IsNullOrWhiteSpace(cursor.Line); + bool isCursorLineEmpty = string.IsNullOrWhiteSpace(cursor.Line); var tokenBeforeCursor = completionContext.TokenBeforeCursor; bool isLineContinuationBeforeCursor = false; if (tokenBeforeCursor != null) @@ -606,10 +1065,10 @@ internal List GetResultHelper(CompletionContext completionCont // isLineContinuationBeforeCursor = completionContext.TokenBeforeCursor.Kind == TokenKind.LineContinuation; } + 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) || @@ -648,7 +1107,7 @@ internal List GetResultHelper(CompletionContext completionCont replacementLength = completionContext.ReplacementLength; } } - else if (!isQuotedString) + else { // // Handle completion of empty line within configuration statement @@ -683,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) { @@ -706,24 +1234,98 @@ internal List GetResultHelper(CompletionContext completionCont case TokenKind.Equals: case TokenKind.Comma: case TokenKind.AtParen: + case TokenKind.LParen: + { + if (lastAst is AssignmentStatementAst assignmentAst) + { + // Handle scenarios like '$ErrorActionPreference = ' + if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst, out result)) + { + break; + } + } + + 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.Break: + case TokenKind.Continue: { - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out unused); + if ((lastAst is BreakStatementAst breakStatement && breakStatement.Label is null) + || (lastAst is ContinueStatementAst continueStatement && continueStatement.Label is null)) + { + result = CompleteLoopLabel(completionContext); + } break; } - case TokenKind.LParen: - if (lastAst is AttributeAst) + + 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) { - completionContext.ReplacementLength = replacementLength = 0; - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + return result; } - else + 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) { - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, - ref replacementIndex, ref replacementLength, out unused); + // Handles index expression with whitespace between lbracket and cursor like: $PSVersionTable[ ] + completionContext.WordToComplete = string.Empty; + result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target); } break; + default: break; } @@ -778,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. @@ -785,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) @@ -812,66 +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) + var tokenBeforeOrAtCursor = completionContext.TokenBeforeCursor ?? completionContext.TokenAtCursor; + if (tokenBeforeOrAtCursor.Kind != TokenKind.Semi) { - // Handle only empty line - if (String.IsNullOrWhiteSpace(cursor.Line)) - { - // Check if the cursor outside of last child of hashtable and within the hashtable - if (cursor.Offset > lastChildofHashtableAst.Extent.EndOffset && - cursor.Offset <= hashTableAst.Extent.EndOffset) - { - tempHashtableAst = hashTableAst; - } - } + return null; } } - } - hashTableAst = tempHashtableAst; - if (hashTableAst != null) - { completionContext.ReplacementIndex = completionContext.CursorPosition.Offset; completionContext.ReplacementLength = 0; - return CompletionCompleters.CompleteHashtableKey(completionContext, hashTableAst); + return CompletionCompleters.CompleteHashtableKey(completionContext, hashtableAst); } + return null; } // 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) { @@ -910,90 +1522,448 @@ internal static TypeName FindTypeNameToComplete(ITypeName type, IScriptPosition if (typeName != null) return typeName; } + return null; } - var arrayTypeName = type as ArrayTypeName; - if (arrayTypeName != null) + var arrayTypeName = type as ArrayTypeName; + if (arrayTypeName != null) + { + return FindTypeNameToComplete(arrayTypeName.ElementType, cursor) ?? null; + } + + return null; + } + + private static string GetFirstLineSubString(string stringToComplete, out bool hasNewLine) + { + hasNewLine = false; + if (!string.IsNullOrEmpty(stringToComplete)) + { + var index = stringToComplete.AsSpan().IndexOfAny('\r', '\n'); + if (index >= 0) + { + stringToComplete = stringToComplete.Substring(0, index); + hasNewLine = true; + } + } + + return stringToComplete; + } + + private static Tuple GetHashEntryContainsCursor( + IScriptPosition cursor, + HashtableAst hashTableAst, + bool isCursorInString) + { + Tuple keyValuePairWithCursor = null; + foreach (var kvp in hashTableAst.KeyValuePairs) + { + if (IsCursorWithinOrJustAfterExtent(cursor, kvp.Item2.Extent)) + { + keyValuePairWithCursor = kvp; + break; + } + + if (!isCursorInString) + { + // + // Handle following case, cursor is after '=' but before next key value pair, + // next key value pair will be treated as kvp.Item2 of 'Ensure' key + // + // configuration foo + // { + // File foo + // { + // DestinationPath = "\foo.txt" + // Ensure = | + // DependsOn =@("[User]x") + // } + // } + // + if (kvp.Item2.Extent.StartLineNumber > kvp.Item1.Extent.EndLineNumber && + IsCursorAfterExtentAndInTheSameLine(cursor, kvp.Item1.Extent)) + { + keyValuePairWithCursor = kvp; + break; + } + + // + // If cursor is not within a string, then handle following two cases, + // + // #1) cursor is after '=', in the same line of previous key value pair + // configuration test{File testfile{DestinationPath='c:\test'; Ensure = | + // + // #2) cursor is after '=', in the separate line of previous key value pair + // configuration test{File testfile{DestinationPath='c:\test'; + // Ensure = | + // + if (!IsCursorBeforeExtent(cursor, kvp.Item1.Extent) && + IsCursorAfterExtentAndInTheSameLine(cursor, kvp.Item2.Extent)) + { + keyValuePairWithCursor = kvp; + } + } + } + + return keyValuePairWithCursor; + } + + // Pulls the variable out of an assignment's LHS expression + // Also brings back the innermost type constraint if there is one + private static VariableExpressionAst GetVariableFromExpressionAst( + ExpressionAst expression, + ref Type typeConstraint, + ref ValidateSetAttribute setConstraint) + { + switch (expression) + { + // $x = ... + case VariableExpressionAst variableExpression: + return variableExpression; + + // [type]$x = ... + case ConvertExpressionAst convertExpression: + typeConstraint = convertExpression.Type.TypeName.GetReflectionType(); + return GetVariableFromExpressionAst(convertExpression.Child, ref typeConstraint, ref setConstraint); + + // [attribute()][type]$x = ... + case AttributedExpressionAst attributedExpressionAst: + + try + { + setConstraint = attributedExpressionAst.Attribute.GetAttribute() as ValidateSetAttribute; + } + catch + { + // Do nothing, just prevent fallout from an unsuccessful attribute conversion + } + + return GetVariableFromExpressionAst(attributedExpressionAst.Child, ref typeConstraint, ref setConstraint); + + // Something else, like `MemberExpressionAst` $a.p = which isn't currently handled + default: + return null; + } + } + + // Gets any type constraints or validateset constraints on a given variable + private static bool TryGetTypeConstraintOnVariable( + CompletionContext completionContext, + string variableName, + out Type typeConstraint, + out ValidateSetAttribute setConstraint) + { + typeConstraint = null; + setConstraint = null; + + PSVariable variable = completionContext.ExecutionContext.EngineSessionState.GetVariable(variableName); + + if (variable == null || variable.Attributes.Count == 0) + { + return false; + } + + foreach (Attribute attribute in variable.Attributes) + { + if (attribute is ArgumentTypeConverterAttribute typeConverterAttribute) + { + typeConstraint = typeConverterAttribute.TargetType; + continue; + } + + if (attribute is ValidateSetAttribute validateSetAttribute) + { + setConstraint = validateSetAttribute; + } + } + + 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, + out List completions) + { + bool TryGetResultForEnum(Type typeConstraint, CompletionContext completionContext, out List completions) + { + completions = null; + + if (typeConstraint != null && typeConstraint.IsEnum) + { + completions = GetResultForEnum(typeConstraint, completionContext); + return true; + } + + return false; + } + + bool TryGetResultForSet(Type typeConstraint, ValidateSetAttribute setConstraint, CompletionContext completionContext1, out List completions) + { + completions = null; + + if (setConstraint?.ValidValues != null) + { + completions = GetResultForSet(typeConstraint, setConstraint.ValidValues, completionContext); + return true; + } + + 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 + Type typeConstraint = null; + ValidateSetAttribute setConstraint = null; + VariableExpressionAst variableAst = GetVariableFromExpressionAst(assignmentAst.Left, ref typeConstraint, ref setConstraint); + + if (variableAst == null) + { + return false; + } + + // Assignment constraints override any existing ones, so try them first + + // Check any [ValidateSet()] constraint first since it's likely to be narrow + if (TryGetResultForSet(typeConstraint, setConstraint, completionContext, out completions)) + { + return true; + } + + // Then try to complete for an enum type + if (TryGetResultForEnum(typeConstraint, completionContext, out completions)) + { + return true; + } + + // If the assignment itself was unconstrained, the variable still might be + if (!TryGetTypeConstraintOnVariable(completionContext, variableAst.VariablePath.UserPath, out typeConstraint, out setConstraint)) + { + return TryGetInferredCompletionsForAssignment(variableAst, completionContext, out completions); + } + + // Again try the [ValidateSet()] constraint first + if (TryGetResultForSet(typeConstraint, setConstraint, completionContext, out completions)) + { + return true; + } + + // Then try to complete for an enum type again + if (TryGetResultForEnum(typeConstraint, completionContext, out completions)) + { + return true; + } + + return false; + } + + private static List GetResultForSet( + Type typeConstraint, + IList validValues, + CompletionContext completionContext) + { + var allValues = new List(); + foreach (string value in validValues) + { + if (typeConstraint != null && (typeConstraint == typeof(string) || typeConstraint.IsEnum)) + { + allValues.Add(GetQuotedString(value, completionContext)); + } + else + { + allValues.Add(value); + } + } + + return GetMatchedResults(allValues, completionContext); + } + + private static List GetMatchedResults( + List allValues, + CompletionContext completionContext) + { + var stringToComplete = string.Empty; + if (completionContext.TokenAtCursor != null && completionContext.TokenAtCursor.Kind != TokenKind.Equals) + { + stringToComplete = completionContext.TokenAtCursor.Text; + } + + IEnumerable matchedResults = null; + + if (!string.IsNullOrEmpty(stringToComplete)) + { + string matchString = stringToComplete + "*"; + var wildcardPattern = WildcardPattern.Get(matchString, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + + matchedResults = allValues.Where(r => wildcardPattern.IsMatch(r)); + } + else + { + matchedResults = allValues; + } + + var result = new List(); + foreach (var match in matchedResults) { - return FindTypeNameToComplete(arrayTypeName.ElementType, cursor) ?? null; + result.Add(new CompletionResult(match)); } - return null; + return result; } - private static string GetFirstLineSubString(string stringToComplete, out bool hasNewLine) + private static string GetQuotedString( + string value, + CompletionContext completionContext) { - hasNewLine = false; - if (!String.IsNullOrEmpty(stringToComplete)) + var stringToComplete = string.Empty; + if (completionContext.TokenAtCursor != null) { - var index = stringToComplete.IndexOfAny(Utils.Separators.CrLf); - if (index >= 0) - { - stringToComplete = stringToComplete.Substring(0, index); - hasNewLine = true; - } + stringToComplete = completionContext.TokenAtCursor.Text; } - return stringToComplete; + + var quote = stringToComplete.StartsWith('"') ? "\"" : "'"; + return quote + value + quote; } - private Tuple GetHashEntryContainsCursor( - IScriptPosition cursor, - HashtableAst hashTableAst, - bool isCursorInString) + private static List GetResultForEnum( + Type type, + CompletionContext completionContext) { - Tuple keyValuePairWithCursor = null; - foreach (var kvp in hashTableAst.KeyValuePairs) + var allNames = new List(); + foreach (var name in Enum.GetNames(type)) { - if (IsCursorWithinOrJustAfterExtent(cursor, kvp.Item2.Extent)) - { - keyValuePairWithCursor = kvp; - break; - } - if (!isCursorInString) - { - // - // Handle following case, cursor is after '=' but before next key value pair, - // next key value pair will be treated as kvp.Item2 of 'Ensure' key - // - // configuration foo - // { - // File foo - // { - // DestinationPath = "\foo.txt" - // Ensure = | - // DependsOn =@("[User]x") - // } - // } - // - if (kvp.Item2.Extent.StartLineNumber > kvp.Item1.Extent.EndLineNumber && - IsCursorAfterExtentAndInTheSameLine(cursor, kvp.Item1.Extent)) - { - keyValuePairWithCursor = kvp; - break; - } - - // - // If cursor is not within a string, then handle following two cases, - // - // #1) cursor is after '=', in the same line of previous key value pair - // configuration test{File testfile{DestinationPath='c:\test'; Ensure = | - // - // #2) cursor is after '=', in the separate line of previous key value pair - // configuration test{File testfile{DestinationPath='c:\test'; - // Ensure = | - // - if (!IsCursorBeforeExtent(cursor, kvp.Item1.Extent) && - IsCursorAfterExtentAndInTheSameLine(cursor, kvp.Item2.Extent)) - { - keyValuePairWithCursor = kvp; - } - } + allNames.Add(GetQuotedString(name, completionContext)); } - return keyValuePairWithCursor; + + allNames.Sort(); + + return GetMatchedResults(allNames, completionContext); } - private List GetResultForEnumPropertyValueOfDSCResource( + private static List GetResultForEnumPropertyValueOfDSCResource( CompletionContext completionContext, string stringToComplete, ref int replacementIndex, @@ -1023,16 +1993,16 @@ private List GetResultForEnumPropertyValueOfDSCResource( DynamicKeywordProperty property; if (keywordAst.Keyword.Properties.TryGetValue(propertyNameAst.Value, out property)) { - List existingValues = null; + List existingValues = null; WildcardPattern wildcardPattern = null; - bool isDependsOnProperty = String.Equals(property.Name, @"DependsOn", StringComparison.OrdinalIgnoreCase); + bool isDependsOnProperty = string.Equals(property.Name, @"DependsOn", StringComparison.OrdinalIgnoreCase); bool hasNewLine = false; string stringQuote = (completionContext.TokenAtCursor is StringExpandableToken) ? "\"" : "'"; if ((property.ValueMap != null && property.ValueMap.Count > 0) || isDependsOnProperty) { shouldContinue = false; - existingValues = new List(); - if (String.Equals(property.TypeConstraint, "StringArray", StringComparison.OrdinalIgnoreCase)) + existingValues = new List(); + if (string.Equals(property.TypeConstraint, "StringArray", StringComparison.OrdinalIgnoreCase)) { var arrayAst = Ast.GetAncestorAst(lastAst); if (arrayAst != null && arrayAst.Elements.Count > 0) @@ -1068,6 +2038,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( { replacementIndex = completionContext.CursorPosition.Offset - replacementLength; } + completionContext.ReplacementIndex = replacementIndex; string matchString = stringToComplete + "*"; wildcardPattern = WildcardPattern.Get(matchString, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); @@ -1077,18 +2048,19 @@ 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()) { // Fallback to all allowed values matchedResults = orderedValues; } + foreach (var value in matchedResults) { string completionText = isCursorInString ? value : stringQuote + value + stringQuote; if (hasNewLine) - completionText = completionText + stringQuote; + completionText += stringQuote; result.Add(new CompletionResult( completionText, value, @@ -1110,13 +2082,13 @@ private List GetResultForEnumPropertyValueOfDSCResource( var dynamicKeywordAst = statementAst as DynamicKeywordStatementAst; if (dynamicKeywordAst != null && dynamicKeywordAst != keywordAst && - !String.Equals(dynamicKeywordAst.Keyword.Keyword, @"Node", StringComparison.OrdinalIgnoreCase)) + !string.Equals(dynamicKeywordAst.Keyword.Keyword, @"Node", StringComparison.OrdinalIgnoreCase)) { - if (!String.IsNullOrEmpty(dynamicKeywordAst.ElementName)) + if (!string.IsNullOrEmpty(dynamicKeywordAst.ElementName)) { 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) && @@ -1127,6 +2099,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( } } } + var matchedResults = allResources.Where(r => wildcardPattern.IsMatch(r)); if (matchedResults == null || !matchedResults.Any()) { @@ -1138,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, @@ -1153,114 +2126,69 @@ 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); - int subReplaceIndex, subReplaceLength; - var subResult = analysis.GetResultHelper(subContext, out subReplaceIndex, out subReplaceLength, 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; - string prefix = subInput.Substring(0, subReplaceIndex); - - foreach (CompletionResult entry in subResult) - { - string completionText = prefix + entry.CompletionText; - 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.IndexOf('-') != -1) + 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); } } } @@ -1270,13 +2198,13 @@ private List GetResultForString(CompletionContext completionCo } /// - /// Find the configuration statement contains current cursor + /// Find the configuration statement contains current cursor. /// /// /// /// /// - private ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( + private static ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( IScriptPosition cursorPosition, Ast ast, out DynamicKeywordStatementAst keywordAst) @@ -1289,6 +2217,7 @@ private ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( { configureAst = Ast.GetAncestorAst(configureAst.Parent); } + return configureAst; } @@ -1305,14 +2234,13 @@ private ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( /// Us^ /// } /// } - /// /// /// /// /// /// /// - private List GetResultForIdentifierInConfiguration( + private static List GetResultForIdentifierInConfiguration( CompletionContext completionContext, ConfigurationDefinitionAst configureAst, DynamicKeywordStatementAst keywordAst, @@ -1323,7 +2251,7 @@ private List GetResultForIdentifierInConfiguration( IEnumerable keywords = configureAst.DefinedKeywords.Where( k => // Node is special case, legal in both Resource and Meta configuration - String.Compare(k.Keyword, @"Node", StringComparison.OrdinalIgnoreCase) == 0 || + string.Equals(k.Keyword, @"Node", StringComparison.OrdinalIgnoreCase) || ( // Check compatibility between Resource and Configuration Type k.IsCompatibleWithConfigurationType(configureAst.ConfigurationType) && @@ -1337,7 +2265,7 @@ private List GetResultForIdentifierInConfiguration( if (keywords != null && keywords.Any()) { - string commandName = (completionContext.WordToComplete ?? String.Empty) + "*"; + string commandName = (completionContext.WordToComplete ?? string.Empty) + "*"; var wildcardPattern = WildcardPattern.Get(commandName, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); // Filter by name @@ -1354,11 +2282,19 @@ 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) + { + usageString = dscSubsystem.GetDSCResourceUsageString(keyword); + } + else { - results = new List(); + usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); } + + results ??= new List(); + results.Add(new CompletionResult( keyword.Keyword, keyword.Keyword, @@ -1370,21 +2306,40 @@ 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) { if (strConst.Value.Equals("$", StringComparison.Ordinal)) { - completionContext.WordToComplete = ""; + completionContext.WordToComplete = string.Empty; return CompletionCompleters.CompleteVariable(completionContext); } else @@ -1398,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: @@ -1408,6 +2368,7 @@ private List GetResultForIdentifier(CompletionContext completi StringLiterals.PowerShellDataFileExtension, StringLiterals.PowerShellNgenAssemblyExtension, StringLiterals.PowerShellILAssemblyExtension, + StringLiterals.PowerShellILExecutableExtension, StringLiterals.PowerShellCmdletizationFileExtension }; result = CompletionCompleters.CompleteFilename(completionContext, false, moduleExtensions).ToList(); @@ -1432,75 +2393,102 @@ 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; + + completionContext.ReplacementIndex = replacementIndex; + completionContext.ReplacementLength = replacementLength; } - return result; } - 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) @@ -1510,7 +2498,7 @@ private List GetResultForIdentifier(CompletionContext completi string expandedString = null; var expandableStringAst = new ExpandableStringExpressionAst(strConst.Extent, strConst.Value, StringConstantType.BareWord); if (CompletionCompleters.IsPathSafelyExpandable(expandableStringAst: expandableStringAst, - extraText: String.Empty, + extraText: string.Empty, executionContext: completionContext.ExecutionContext, expandedString: out expandedString)) { @@ -1558,6 +2546,7 @@ private List GetResultForIdentifier(CompletionContext completi { result.AddRange(keywordResult); } + return result; } @@ -1569,19 +2558,35 @@ 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) { return res; } } + return CompletionCompleters.CompleteCommandArgument(completionContext); } 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) @@ -1632,18 +2637,19 @@ 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 // which case we should try to complete as an argument. - if (result.Any()) + if (result.Count > 0) { if (!isWildcard && memberOperator != TokenKind.Unknown) { replacementIndex += tokenAtCursorText.Length; replacementLength = 0; } + return result; } } @@ -1651,30 +2657,27 @@ private List GetResultForIdentifier(CompletionContext completi if (lastAst.Parent is HashtableAst) { result = CompletionCompleters.CompleteHashtableKey(completionContext, (HashtableAst)lastAst.Parent); - if (result != null && result.Any()) + if (result != null && result.Count > 0) { return result; } } - // 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)) { string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(lastAst as CommandElementAst, String.Empty, completionContext); + CompletionCompleters.ConcatenateStringPathArguments(lastAst as CommandElementAst, string.Empty, completionContext); if (wordToComplete != null) { needFileCompletion = true; 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.Any()) + if (command != null && command.Redirections.Count > 0) { var fileRedirection = command.Redirections[0] as FileRedirectionAst; if (fileRedirection != null && @@ -1705,7 +2708,7 @@ private List GetResultForIdentifier(CompletionContext completi else { string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(lastAst as CommandElementAst, String.Empty, completionContext); + CompletionCompleters.ConcatenateStringPathArguments(lastAst as CommandElementAst, string.Empty, completionContext); if (wordToComplete != null) { completionContext.WordToComplete = wordToComplete; @@ -1715,53 +2718,72 @@ 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 + // 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 pro in propertyInfos) + foreach (PropertyInfo property in propertyInfos) { - //Ignore TypeId (all attributes inherit it) - if (pro.Name != "TypeId" && (pro.Name.StartsWith(argName, StringComparison.OrdinalIgnoreCase))) + // Ignore getter-only properties and properties that have already been set. + if (!property.CanWrite || existingArguments.Contains(property.Name)) { - result.Add(new CompletionResult(pro.Name, pro.Name, CompletionResultType.Property, - pro.PropertyType.ToString() + " " + pro.Name)); + continue; + } + + if (property.Name.StartsWith(argName, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult(property.Name, property.Name, CompletionResultType.Property, + property.PropertyType.ToString() + " " + property.Name)); } } + return result; } + return null; } /// - /// Complete file name as command + /// Complete file name as command. /// /// /// @@ -1809,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 39a9860df95..80cead788d3 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1,10 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +using System.Buffers; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Diagnostics.CodeAnalysis; @@ -13,23 +13,24 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; -using System.Collections.Generic; +using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; using Microsoft.PowerShell; using Microsoft.PowerShell.Cim; using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { /// - /// /// public static class CompletionCompleters { @@ -49,7 +50,6 @@ private static void UpdateTypeCacheOnAssemblyLoad(object sender, AssemblyLoadEve #region Command Names /// - /// /// /// /// @@ -59,13 +59,11 @@ 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; @@ -90,9 +88,8 @@ 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); - commandName += "*"; List commandResults = null; if (commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) == -1) @@ -105,45 +102,27 @@ private static List CompleteCommand(CompletionContext context, lastAst = context.RelatedAsts.Last(); } - var powershell = context.Helper - .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand)) - .AddParameter("All") - .AddParameter("Name", commandName); - - if (moduleName != null) - powershell.AddParameter("Module", moduleName); - if (!types.Equals(CommandTypes.All)) - powershell.AddParameter("CommandType", types); - - Exception exceptionThrown; - var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); - - if (commandInfos != null && commandInfos.Count > 1) - { - // OrderBy is using stable sorting - var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer()); - commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote); - } - else - { - commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote); - } + commandResults = ExecuteGetCommandCommand(useModulePrefix: false); if (lastAst != null) { + // We need to add the wildcard to the end so the regex is built correctly. + commandName += "*"; + // Search the asts for function definitions that we might be calling var findFunctionsVisitor = new FindFunctionsVisitor(); while (lastAst.Parent != null) { lastAst = lastAst.Parent; } + lastAst.Visit(findFunctionsVisitor); WildcardPattern commandNamePattern = WildcardPattern.Get(commandName, WildcardOptions.IgnoreCase); foreach (var defn in findFunctionsVisitor.FunctionDefinitions) { if (commandNamePattern.IsMatch(defn.Name) - && !commandResults.Where(cr => cr.CompletionText.Equals(defn.Name, StringComparison.OrdinalIgnoreCase)).Any()) + && !commandResults.Any(cr => cr.CompletionText.Equals(defn.Name, StringComparison.OrdinalIgnoreCase))) { // Results found in the current script are prepended to show up at the top of the list. commandResults.Insert(0, GetCommandNameCompletionResult(defn.Name, defn, addAmpersandIfNecessary, quote)); @@ -163,35 +142,75 @@ private static List CompleteCommand(CompletionContext context, moduleName = commandName.Substring(0, indexOfFirstBackslash); commandName = commandName.Substring(indexOfFirstBackslash + 1); - var powershell = context.Helper + commandResults = ExecuteGetCommandCommand(useModulePrefix: true); + } + } + + return commandResults; + + List ExecuteGetCommandCommand(bool useModulePrefix) + { + var powershell = context.Helper + .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand)) + .AddParameter("All") + .AddParameter("Name", commandName + "*"); + + if (moduleName != null) + { + powershell.AddParameter("Module", moduleName); + } + + if (!types.Equals(CommandTypes.All)) + { + powershell.AddParameter("CommandType", types); + } + + // Exception is ignored, the user simply does not get any completion results if the pipeline fails + Exception exceptionThrown; + var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); + + if (commandInfos == null || commandInfos.Count == 0) + { + powershell.Commands.Clear(); + powershell .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand)) .AddParameter("All") .AddParameter("Name", commandName) - .AddParameter("Module", moduleName); - - if (!types.Equals(CommandTypes.All)) - powershell.AddParameter("CommandType", types); - - Exception exceptionThrown; - var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); + .AddParameter("UseAbbreviationExpansion"); - if (commandInfos != null && commandInfos.Count > 1) + if (moduleName != null) { - var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer()); - commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote); + powershell.AddParameter("Module", moduleName); } - else + + if (!types.Equals(CommandTypes.All)) { - commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote); + powershell.AddParameter("CommandType", types); } + + commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); } - } - return commandResults; + List completionResults = null; + + if (commandInfos != null && commandInfos.Count > 1) + { + // OrderBy is using stable sorting + var sortedCommandInfos = commandInfos.Order(new CommandNameComparer()); + completionResults = MakeCommandsUnique(sortedCommandInfos, useModulePrefix, addAmpersandIfNecessary, quote); + } + else + { + completionResults = MakeCommandsUnique(commandInfos, useModulePrefix, addAmpersandIfNecessary, quote); + } + + return completionResults; + } } private static readonly HashSet s_keywordsToExcludeFromAddingAmpersand - = new HashSet(StringComparer.OrdinalIgnoreCase) { TokenKind.InlineScript.ToString(), TokenKind.Configuration.ToString() }; + = new HashSet(StringComparer.OrdinalIgnoreCase) { nameof(TokenKind.InlineScript), nameof(TokenKind.Configuration) }; + internal static CompletionResult GetCommandNameCompletionResult(string name, object command, bool addAmpersandIfNecessary, string quote) { string syntax = name, listItem = name; @@ -214,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; @@ -227,6 +246,7 @@ internal static CompletionResult GetCommandNameCompletionResult(string name, obj name = name.Replace("`", "``"); name = name.Replace("$", "`$"); } + name = quoteInUse + name + quoteInUse; } else @@ -243,6 +263,7 @@ internal static CompletionResult GetCommandNameCompletionResult(string name, obj { name = "& " + name; } + return new CompletionResult(name, listItem, CompletionResultType.Command, syntax); } @@ -265,6 +286,7 @@ internal static List MakeCommandsUnique(IEnumerable { // Skip the private commands if (commandInfo.Visibility == SessionStateEntryVisibility.Private) { continue; } + name = commandInfo.Name; if (includeModulePrefix && !string.IsNullOrEmpty(commandInfo.ModuleName)) { @@ -274,7 +296,7 @@ internal static List MakeCommandsUnique(IEnumerable // --> command 'Get-PowerShellFoo' in the global session state (prefixed commandInfo) // command 'Get-Foo' in the module session state (un-prefixed commandInfo) // in that case, we should not add the module name qualification because it doesn't work - if (String.IsNullOrEmpty(commandInfo.Prefix) || !ModuleCmdletBase.IsPrefixedCommand(commandInfo)) + if (string.IsNullOrEmpty(commandInfo.Prefix) || !ModuleCmdletBase.IsPrefixedCommand(commandInfo)) { name = commandInfo.ModuleName + "\\" + commandInfo.Name; } @@ -306,52 +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)); - // 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++) + if (commandInfoList.Count == 0) { - var commandInfo = commandList[index] as CommandInfo; - Diagnostics.Assert(commandInfo != null, "Elements should always be CommandInfo"); + continue; + } + + int moduleCount = modulesWithCommand.Count; + modulesWithCommand.Clear(); + int index; + if (commandInfoList[0].CommandType == CommandTypes.Application + || importedModules.Count == 1 + || moduleCount < 2) + { + // We can use the short name for this command because there's no ambiguity about which command it resolves to. + // If the first element is an application then we know there's no conflicting commands/aliases (because of the command precedence). + // If there's just 1 module imported then the short name refers to that module (and it will be the first element in the list) + // If there's less than 2 unique modules exporting that command then we can use the short name because it can only refer to that module. + index = 1; + results.Add(GetCommandNameCompletionResult(keyValuePair.Key, commandInfoList[0], addAmpersandIfNecessary, quote)); + modulesWithCommand.Add(commandInfoList[0].ModuleName); + } + else + { + 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)); } } } @@ -364,28 +406,24 @@ internal static List MakeCommandsUnique(IEnumerable if (!includeModulePrefix) { var commandInfo = keyValuePair.Value as CommandInfo; - if (commandInfo != null && !String.IsNullOrEmpty(commandInfo.Prefix)) + if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix)) { - Diagnostics.Assert(!String.IsNullOrEmpty(commandInfo.ModuleName), "the module name should exist if commandInfo.Prefix is not an empty string"); + 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; } } } + results.Add(GetCommandNameCompletionResult(completionName, keyValuePair.Value, addAmpersandIfNecessary, quote)); } } - 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(); @@ -400,49 +438,99 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun #region Module Names - internal static List CompleteModuleName(CompletionContext context, bool loadedModulesOnly) + 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("*", StringComparison.Ordinal)) + 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); + + // -SkipEditionCheck should only be set or apply to -ListAvailable + if (skipEditionCheck) + { + powershell.AddParameter("SkipEditionCheck", true); + } } - 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)); } } @@ -452,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) { @@ -466,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; } @@ -479,13 +566,13 @@ internal static List CompleteCommandParameter(CompletionContex // If parent is DynamicKeywordStatementAst - 'Import-DscResource', // then customize the auto completion results - if (keywordAst != null && String.Equals(keywordAst.Keyword.Keyword, "Import-DscResource", StringComparison.OrdinalIgnoreCase) - && !String.IsNullOrWhiteSpace(context.WordToComplete) && context.WordToComplete.StartsWith("-", StringComparison.OrdinalIgnoreCase)) + if (keywordAst != null && string.Equals(keywordAst.Keyword.Keyword, "Import-DscResource", StringComparison.OrdinalIgnoreCase) + && !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)) @@ -494,26 +581,28 @@ internal static List CompleteCommandParameter(CompletionContex result.Add(new CompletionResult("-" + parameterName, parameterName, CompletionResultType.ParameterName, tooltip)); } } + if (result.Count > 0) { context.ReplacementLength = context.WordToComplete.Length; context.ReplacementIndex = lastAst.Extent.StartOffset; } + return result; } + bool bindPositionalParameters = true; if (parameterAst != null) { // Parent must be a command commandAst = (CommandAst)parameterAst.Parent; partialName = parameterAst.ParameterName; - withColon = context.WordToComplete.EndsWith(":", StringComparison.Ordinal); + withColon = context.WordToComplete.EndsWith(':'); } 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; @@ -521,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) @@ -555,7 +658,7 @@ internal static List CompleteCommandParameter(CompletionContex } /// - /// Get the parameter completion results when the pseudo binding was successful + /// Get the parameter completion results when the pseudo binding was successful. /// /// /// @@ -566,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) { @@ -573,7 +681,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -595,8 +704,10 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); } + return result; } @@ -609,8 +720,10 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.BoundParameters.Values, - withColon); + withColon, + commandAssembly); } + return result; } @@ -633,6 +746,7 @@ private static List GetParameterCompletionResults(string param return result; } } + break; case AstParameterArgumentType.Fake: { @@ -642,6 +756,7 @@ private static List GetParameterCompletionResults(string param matchedParameterName = entry.Key; } } + break; case AstParameterArgumentType.Switch: { @@ -651,6 +766,7 @@ private static List GetParameterCompletionResults(string param matchedParameterName = entry.Key; } } + break; case AstParameterArgumentType.AstArray: case AstParameterArgumentType.PipeObject: @@ -661,42 +777,116 @@ 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 + /// Get the parameter completion results by using the given valid parameter sets and available parameters. /// /// /// /// /// + /// 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(); @@ -712,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; @@ -727,32 +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)); + } + } } } @@ -761,17 +965,18 @@ where pattern.IsMatch(alias) { result.AddRange(commonParamResult); } + return result; } /// /// Get completion results for operators that start with /// - /// The starting text of the operator to complete - /// A list of completion results + /// The starting text of the operator to complete. + /// A list of completion results. public static List CompleteOperator(string wordToComplete) { - if (wordToComplete.StartsWith("-", StringComparison.Ordinal)) + if (wordToComplete.StartsWith('-')) { wordToComplete = wordToComplete.Substring(1); } @@ -784,7 +989,7 @@ orderby op private static string GetOperatorDescription(string op) { - return ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).GetTypeInfo().Assembly, + return ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).Assembly, "System.Management.Automation.resources.TabCompletionStrings", op + "OperatorDescription"); } @@ -810,10 +1015,10 @@ internal static List CompleteCommandArgument(CompletionContext { commandAst = (CommandAst)expressionAst.Parent; - if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(",", StringComparison.Ordinal)) + if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(',')) { context.WordToComplete = string.Empty; - //BUGBUG context.CursorPosition = expressionAst.Extent.StartScriptPosition; + // BUGBUG context.CursorPosition = expressionAst.Extent.StartScriptPosition; } else if (commandAst.CommandElements.Count == 1 || context.WordToComplete == string.Empty) { @@ -842,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; @@ -859,7 +1064,7 @@ internal static List CompleteCommandArgument(CompletionContext context.ReplacementIndex = ((InternalScriptPosition)secondToLastAst.Extent.StartScriptPosition).Offset; context.ReplacementLength += ((InternalScriptPosition)secondToLastAst.Extent.EndScriptPosition).Offset - context.ReplacementIndex; context.WordToComplete = fullPath; - //context.CursorPosition = secondToLastAst.Extent.StartScriptPosition; + // context.CursorPosition = secondToLastAst.Extent.StartScriptPosition; } else if (secondToLastArrayAst != null) { @@ -942,11 +1147,11 @@ internal static List CompleteCommandArgument(CompletionContext else if (expressionAst.Parent is CommandParameterAst && expressionAst.Parent.Parent is CommandAst) { commandAst = (CommandAst)expressionAst.Parent.Parent; - if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(",", StringComparison.Ordinal)) + if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(',')) { // dir -Path: a.txt, context.WordToComplete = string.Empty; - //context.CursorPosition = expressionAst.Extent.StartScriptPosition; + // context.CursorPosition = expressionAst.Extent.StartScriptPosition; } else if (context.WordToComplete == string.Empty) { @@ -1027,6 +1232,7 @@ internal static List CompleteCommandArgument(CompletionContext result = GetArgumentCompletionResultsWithFailedPseudoBinding(context, argLocation, commandAst); break; } + parsedArgumentsProvidesMatch = true; } } @@ -1127,7 +1333,7 @@ internal static List CompleteCommandArgument(CompletionContext var tryCmdletCompletion = false; var clearLiteralPathsKey = TurnOnLiteralPathOption(context); - if (context.WordToComplete.IndexOf('-') != -1) + if (context.WordToComplete.Contains('-')) { tryCmdletCompletion = true; } @@ -1144,6 +1350,7 @@ internal static List CompleteCommandArgument(CompletionContext fileCompletionResults.AddRange(cmdletCompletionResults); } } + return fileCompletionResults; } finally @@ -1171,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; @@ -1180,16 +1387,17 @@ internal static List CompleteCommandArgument(CompletionContext result.Add(new CompletionResult(completionText, entry.ListItemText, entry.ResultType, entry.ToolTip)); } + return result; } } // Handle member completion with wildcard: echo $a.* - if (pathAst.Value.IndexOf('*') != -1 && secondToLastMemberAst != null && + if (pathAst.Value.Contains('*') && secondToLastMemberAst != null && secondToLastMemberAst.Extent.EndLineNumber == pathAst.Extent.StartLineNumber && secondToLastMemberAst.Extent.EndColumnNumber == pathAst.Extent.StartColumnNumber) { - var memberName = pathAst.Value.EndsWith("*", StringComparison.Ordinal) + var memberName = pathAst.Value.EndsWith('*') ? pathAst.Value : pathAst.Value + "*"; var targetExpr = secondToLastMemberAst.Expression; @@ -1219,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) @@ -1289,7 +1497,8 @@ internal static List CompleteCommandArgument(CompletionContext context.Options.Remove("LiteralPaths"); } - if (context.WordToComplete != string.Empty && context.WordToComplete.IndexOf('-') != -1) + // 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) @@ -1305,7 +1514,7 @@ internal static string ConcatenateStringPathArguments(CommandElementAst stringAs var constantPathAst = stringAst as StringConstantExpressionAst; if (constantPathAst != null) { - string quote = String.Empty; + string quote = string.Empty; switch (constantPathAst.StringConstantType) { case StringConstantType.SingleQuoted: @@ -1317,6 +1526,7 @@ internal static string ConcatenateStringPathArguments(CommandElementAst stringAs default: break; } + return quote + constantPathAst.Value + partialPath + quote; } else @@ -1337,7 +1547,7 @@ internal static string ConcatenateStringPathArguments(CommandElementAst stringAs } /// - /// Get the argument completion results when the pseudo binding was not successful + /// Get the argument completion results when the pseudo binding was not successful. /// private static List GetArgumentCompletionResultsWithFailedPseudoBinding( CompletionContext context, @@ -1391,7 +1601,7 @@ private static List GetArgumentCompletionResultsWithFailedPseu } /// - /// Get the argument completion results when the pseudo binding was successful + /// Get the argument completion results when the pseudo binding was successful. /// private static List GetArgumentCompletionResultsWithSuccessfulPseudoBinding( CompletionContext context, @@ -1451,6 +1661,7 @@ private static List GetArgumentCompletionResultsWithSuccessful MergedCompiledCommandParameter parameter = bindingInfo.BoundParameters[param]; ProcessParameter(bindingInfo.CommandName, commandAst, context, result, parameter, bindingInfo.BoundArguments); } + return result; } else if (!lastPositionalGetBound) @@ -1514,7 +1725,7 @@ private static List GetArgumentCompletionResultsWithSuccessful } /// - /// Get the positional argument completion results based on the position it's in the command line + /// Get the positional argument completion results based on the position it's in the command line. /// private static void CompletePositionalArgument( string commandName, @@ -1532,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 @@ -1550,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.Any(); 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) @@ -1611,14 +1838,13 @@ private static void CompletePositionalArgument( } /// - /// Process a parameter to get the argument completion results + /// Process a parameter to get the argument completion results. /// - /// /// /// 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 @@ -1641,32 +1867,93 @@ private static void ProcessParameter( parameterType = parameterType.GetElementType(); } - if (parameterType.GetTypeInfo().IsEnum) + foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) { - RemoveLastNullCompletionResult(result); - - string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(parameterType); - string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator; - string[] enumArray = enumString.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + 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 enumList = new List(); + var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + var setList = new List(); - foreach (string value in enumArray) - { - if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase)) + foreach (string value in setAtt.ValidValues) { - string completionText = quote == string.Empty ? value : quote + value + quote; - fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterValue, value); - continue; + if (value == string.Empty) + { + continue; + } + + 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)) + { + setList.Add(value); + } + } + + if (fullMatch != null) + { + result.Add(fullMatch); + } + + setList.Sort(); + foreach (string entry in setList) + { + string realEntry = entry; + string completionText = entry; + + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); + + result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); + } + + result.Add(CompletionResult.Null); + return; + } + } + + if (parameterType.IsEnum) + { + RemoveLastNullCompletionResult(result); + + IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType); + + // Exclude values not accepted by ValidateRange-attributes + foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) + { + if (att is ValidateRangeAttribute rangeAtt) + { + enumValues = rangeAtt.GetValidatedElements(enumValues); + } + } + + string wordToComplete = context.WordToComplete ?? string.Empty; + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); + + var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + var enumList = new List(); + + foreach (Enum value in enumValues) + { + string name = value.ToString(); + if (wordToComplete.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + string completionText = quote == string.Empty ? name : quote + name + quote; + fullMatch = new CompletionResult(completionText, name, CompletionResultType.ParameterValue, name); + continue; } - if (pattern.IsMatch(value)) + if (pattern.IsMatch(name)) { - enumList.Add(value); + enumList.Add(name); } } @@ -1698,69 +1985,6 @@ private static void ProcessParameter( return; } - foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) - { - if (att is ValidateSetAttribute) - { - RemoveLastNullCompletionResult(result); - - var setAtt = (ValidateSetAttribute)att; - - string wordToComplete = context.WordToComplete; - string quote = HandleDoubleAndSingleQuote(ref wordToComplete); - - var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - var setList = new List(); - - foreach (string value in setAtt.ValidValues) - { - 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)) - { - setList.Add(value); - } - } - - if (fullMatch != null) - { - result.Add(fullMatch); - } - - setList.Sort(); - foreach (string entry in setList) - { - 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; - } - - result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); - } - result.Add(CompletionResult.Null); - return; - } - } - NativeCommandArgumentCompletion(commandName, parameter.Parameter, result, commandAst, context, boundArguments); } @@ -1789,6 +2013,7 @@ private static IEnumerable NativeCommandArgumentCompletion_InferType AstPair astPair = (AstPair)astParameterArgumentPair; argumentAst = astPair.Argument; } + break; case AstParameterArgumentType.PipeObject: @@ -1809,6 +2034,7 @@ private static IEnumerable NativeCommandArgumentCompletion_InferType } } } + break; default: @@ -1849,11 +2075,13 @@ 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()); } } + yield break; } } @@ -1929,6 +2157,7 @@ internal static IList NativeCommandArgumentCompletion_ExtractSecondaryAr break; } } + break; } default: @@ -1946,16 +2175,27 @@ private static void NativeCommandArgumentCompletion( CompletionContext context, Dictionary boundArguments = null) { - if (string.IsNullOrEmpty(commandName)) + 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 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() + : commandName; + + if (string.IsNullOrEmpty(actualCommandName)) { return; } - var parameterName = parameter.Name; - var customCompleter = GetCustomArgumentCompleter( - "CustomArgumentCompleters", - new[] { commandName + ":" + parameterName, parameterName }, - context); + string parameterFullName = $"{actualCommandName}:{parameterName}"; + + ScriptBlock customCompleter = GetCustomArgumentCompleter( + "CustomArgumentCompleters", + new[] { parameterFullName, parameterName }, + context); + if (customCompleter != null) { if (InvokeScriptArgumentCompleter( @@ -1972,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 @@ -2025,6 +2263,13 @@ private static void NativeCommandArgumentCompletion( NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result); 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"); @@ -2040,6 +2285,7 @@ private static void NativeCommandArgumentCompletion( { NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result); } + break; } @@ -2062,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)) @@ -2070,6 +2332,7 @@ private static void NativeCommandArgumentCompletion( if (commandResults != null) result.AddRange(commandResults); } + break; } case "Clear-EventLog": @@ -2103,17 +2366,19 @@ private static void NativeCommandArgumentCompletion( case "Get-Module": { bool loadedModulesOnly = boundArguments == null || !boundArguments.ContainsKey("ListAvailable"); - NativeCompletionModuleCommands(context, parameterName, loadedModulesOnly, /* isImportModule: */ false, result); + bool skipEditionCheck = !loadedModulesOnly && boundArguments.ContainsKey("SkipEditionCheck"); + NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly, skipEditionCheck: skipEditionCheck); break; } case "Remove-Module": { - NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ true, /* isImportModule: */ false, result); + NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly: true); break; } case "Import-Module": { - NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ false, /* isImportModule: */ true, result); + bool skipEditionCheck = boundArguments != null && boundArguments.ContainsKey("SkipEditionCheck"); + NativeCompletionModuleCommands(context, parameterName, result, isImportModule: true, skipEditionCheck: skipEditionCheck); break; } case "Debug-Process": @@ -2212,23 +2477,43 @@ private static void NativeCommandArgumentCompletion( { if (parameterName.Equals("MemberName", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName], propertiesOnly: false); } + break; } case "Group-Object": 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; } case "Select-Object": @@ -2237,8 +2522,9 @@ 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; } @@ -2248,6 +2534,7 @@ private static void NativeCommandArgumentCompletion( { NativeCompletionTypeName(context, result); } + break; } @@ -2257,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; } @@ -2292,8 +2593,10 @@ private static Hashtable GetBoundArgumentsAsHashtable(CompletionContext context) { result[boundArgument.Key] = value; } + continue; } + var switchPair = boundArgument.Value as SwitchPair; if (switchPair != null) { @@ -2307,6 +2610,7 @@ private static Hashtable GetBoundArgumentsAsHashtable(CompletionContext context) } } } + return result; } @@ -2334,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) { @@ -2347,12 +2650,18 @@ 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; } - private static bool InvokeScriptArgumentCompleter( ScriptBlock scriptBlock, string commandName, @@ -2366,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, @@ -2388,25 +2700,49 @@ private static bool InvokeScriptArgumentCompleter( { } - if (customResults == null || !customResults.Any()) + if (customResults == null || customResults.Count == 0) { 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 @@ -2414,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); } @@ -2427,7 +2763,9 @@ private static void NativeCompletionCimCommands( Dictionary boundArguments, List result, CommandAst commandAst, - CompletionContext context) + CompletionContext context, + HashSet excludedValues, + string commandName) { if (boundArguments != null) { @@ -2448,6 +2786,7 @@ private static void NativeCompletionCimCommands( } } + RemoveLastNullCompletionResult(result); if (parameter.Equals("Namespace", StringComparison.OrdinalIgnoreCase)) { NativeCompletionCimNamespace(result, context); @@ -2493,13 +2832,24 @@ 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); + } } } + result.Add(CompletionResult.Null); } } - private static ConcurrentDictionary> s_cimNamespaceAndClassNameToAssociationResultClassNames = + private static readonly ConcurrentDictionary> s_cimNamespaceAndClassNameToAssociationResultClassNames = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); private static IEnumerable NativeCompletionCimAssociationResultClassName_GetResultClassNames( @@ -2528,11 +2878,12 @@ 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; } } + resultClassNames.Sort(StringComparer.OrdinalIgnoreCase); return resultClassNames.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); @@ -2556,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( @@ -2587,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; @@ -2595,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) { @@ -2609,12 +2960,14 @@ private static void NativeCompletionCimMethodName( { tooltipText.Append(", "); } + if (outParameter) { tooltipText.Append("[out] "); } + tooltipText.Append(CimInstanceAdapter.CimTypeToTypeNameDisplayString(methodParameter.CimType)); - tooltipText.Append(" "); + tooltipText.Append(' '); tooltipText.Append(methodParameter.Name); if (outParameter) @@ -2622,15 +2975,92 @@ private static void NativeCompletionCimMethodName( continue; } } - 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) @@ -2646,6 +3076,7 @@ private static IEnumerable NativeCompletionCimClassName_GetClassNames(st result.Add(className); } } + return result; } @@ -2698,10 +3129,10 @@ private static void NativeCompletionCimNamespace( CompletionContext context) { string containerNamespace = "root"; - string prefixOfChildNamespace = ""; + 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); @@ -2727,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; } @@ -2746,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) @@ -2773,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); + } + } - var modules = new HashSet(StringComparer.OrdinalIgnoreCase); - var moduleResults = CompleteModuleName(context, loadedModulesOnly: true); - if (moduleResults != null) - { - foreach (CompletionResult moduleResult in moduleResults) - { - if (!modules.Contains(moduleResult.ToolTip)) - { - modules.Add(moduleResult.ToolTip); - result.Add(moduleResult); - } + 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) + { + foreach (CompletionResult moduleResult in moduleResults) + { + if (!modules.Contains(moduleResult.ToolTip)) + { + 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) @@ -2845,12 +3282,13 @@ 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("*", StringComparison.Ordinal)) + if (!logName.EndsWith('*')) { logName += "*"; } + var pattern = WildcardPattern.Get(logName, WildcardOptions.IgnoreCase); var powerShellExecutionHelper = context.Helper; @@ -2866,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)) { @@ -2895,12 +3323,13 @@ 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("*", StringComparison.Ordinal)) + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } + var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); var paramIsName = paramName.Equals("Name", StringComparison.OrdinalIgnoreCase); @@ -2956,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)); } @@ -2981,12 +3400,13 @@ 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("*", StringComparison.Ordinal)) + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } + var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); var powerShellExecutionHelper = context.Helper; @@ -3030,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)); } @@ -3049,7 +3459,13 @@ private static void NativeCompletionScheduledJobCommands(CompletionContext conte } } - private static void NativeCompletionModuleCommands(CompletionContext context, string paramName, bool loadedModulesOnly, bool isImportModule, List result) + private static void NativeCompletionModuleCommands( + CompletionContext context, + string paramName, + List result, + bool loadedModulesOnly = false, + bool isImportModule = false, + bool skipEditionCheck = false) { if (string.IsNullOrEmpty(paramName)) { @@ -3068,9 +3484,10 @@ private static void NativeCompletionModuleCommands(CompletionContext context, st StringLiterals.PowerShellDataFileExtension, StringLiterals.PowerShellNgenAssemblyExtension, StringLiterals.PowerShellILAssemblyExtension, + StringLiterals.PowerShellILExecutableExtension, StringLiterals.PowerShellCmdletizationFileExtension }; - var moduleFilesResults = new List(CompleteFilename(context, /* containerOnly: */ false, moduleExtensions)); + var moduleFilesResults = new List(CompleteFilename(context, containerOnly: false, moduleExtensions)); if (moduleFilesResults.Count > 0) result.AddRange(moduleFilesResults); @@ -3082,7 +3499,7 @@ private static void NativeCompletionModuleCommands(CompletionContext context, st } } - var moduleResults = CompleteModuleName(context, loadedModulesOnly); + var moduleResults = CompleteModuleName(context, loadedModulesOnly, skipEditionCheck); if (moduleResults != null && moduleResults.Count > 0) result.AddRange(moduleResults); @@ -3107,9 +3524,9 @@ 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("*", StringComparison.Ordinal)) + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } @@ -3163,16 +3580,12 @@ 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 = CompletionHelpers.QuoteCompletionText(completionText, quote); + + // on macOS, system processes names will be empty if PowerShell isn't run as `sudo` + if (string.IsNullOrEmpty(listItemText)) { - completionText = quote + completionText + quote; + continue; } result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); @@ -3192,10 +3605,9 @@ 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("*", StringComparison.Ordinal)) + if (!providerName.EndsWith('*')) { providerName += "*"; } @@ -3211,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)); } @@ -3237,9 +3639,9 @@ 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("*", StringComparison.Ordinal)) + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } @@ -3259,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)); } @@ -3284,9 +3676,9 @@ 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("*", StringComparison.Ordinal)) + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } @@ -3310,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)); } @@ -3341,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)); } @@ -3371,8 +3743,8 @@ private static void NativeCompletionVariableCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var variableName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref variableName); - if (!variableName.EndsWith("*", StringComparison.Ordinal)) + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref variableName); + if (!variableName.EndsWith('*')) { variableName += "*"; } @@ -3397,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 == "'") @@ -3430,9 +3802,9 @@ 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("*", StringComparison.Ordinal)) + if (!commandName.EndsWith('*')) { commandName += "*"; } @@ -3447,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)); } @@ -3491,9 +3853,9 @@ 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("*", StringComparison.Ordinal)) + if (!traceSourceName.EndsWith('*')) { traceSourceName += "*"; } @@ -3510,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)); } @@ -3539,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)) { @@ -3562,7 +3914,7 @@ private static void NativeCompletionSetLocationCommand(CompletionContext context } /// - /// Provides completion results for NewItemCommand + /// Provides completion results for NewItemCommand. /// /// Completion context. /// Name of the parameter whose value needs completion. @@ -3585,12 +3937,12 @@ private static void NativeCompletionNewItemCommand(CompletionContext context, st var isFileSystem = provider != null && provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase); - //AutoComplete only if filesystem provider. + // AutoComplete only if filesystem provider. if (isFileSystem) { if (paramName.Equals("ItemType", StringComparison.OrdinalIgnoreCase)) { - if (!String.IsNullOrEmpty(context.WordToComplete)) + if (!string.IsNullOrEmpty(context.WordToComplete)) { WildcardPattern patternEvaluator = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.IgnoreCase); @@ -3645,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 @@ -3676,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)) { @@ -3698,43 +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, 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); } @@ -3742,8 +4183,8 @@ private static void NativeCompletionTypeName(CompletionContext context, List 0 && (wordToComplete[0].IsSingleQuote() || wordToComplete[0].IsDoubleQuote()); - string prefix = ""; - string suffix = ""; + string prefix = string.Empty; + string suffix = string.Empty; if (isQuoted) { prefix = suffix = wordToComplete.Substring(0, 1); @@ -3751,7 +4192,8 @@ private static void NativeCompletionTypeName(CompletionContext context, List 1) && wordToComplete[wordToComplete.Length - 1] == wordToComplete[0]; wordToComplete = wordToComplete.Substring(1, wordToComplete.Length - (endQuoted ? 2 : 1)); } - if (wordToComplete.IndexOf('[') != -1) + + if (wordToComplete.Contains('[')) { var cursor = (InternalScriptPosition)context.CursorPosition; cursor = cursor.CloneWithNewOffset(cursor.Offset - context.TokenAtCursor.Extent.StartOffset - (isQuoted ? 1 : 0)); @@ -3767,6 +4209,7 @@ private static void NativeCompletionTypeName(CompletionContext context, List - /// Find the positional argument at the specific position from the parsed argument list + /// Find the positional argument at the specific position from the parsed argument list. /// /// /// @@ -3859,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 }; } } @@ -3873,10 +4319,13 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection abc return GenerateArgumentLocation(prevArg, position); } + position++; } + prevArg = arg; } + break; case AstParameterArgumentType.Fake: case AstParameterArgumentType.Switch: @@ -3885,8 +4334,10 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection - /// /// - /// the argument that is right before the 'tab' location - /// the number of positional arguments before the 'tab' location + /// The argument that is right before the 'tab' location. + /// The number of positional arguments before the 'tab' location. /// private static ArgumentLocation GenerateArgumentLocation(AstParameterArgumentPair prev, int position) { @@ -3920,8 +4370,9 @@ private static ArgumentLocation GenerateArgumentLocation(AstParameterArgumentPai if (!prev.ParameterSpecified) return new ArgumentLocation() { Argument = null, IsPositional = true, Position = position }; - return prev.Parameter.Extent.Text.EndsWith(":", StringComparison.Ordinal) + return prev.Parameter.Extent.Text.EndsWith(':') ? new ArgumentLocation() { Argument = prev, IsPositional = false, Position = -1 } + : new ArgumentLocation() { Argument = null, IsPositional = true, Position = position }; case AstParameterArgumentType.Fake: return new ArgumentLocation() { Argument = prev, IsPositional = false, Position = -1 }; @@ -3966,6 +4417,7 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection - /// /// /// /// @@ -4024,691 +4477,1788 @@ internal static IEnumerable CompleteFilename(CompletionContext internal static IEnumerable CompleteFilename(CompletionContext context, bool containerOnly, HashSet extension) { var wordToComplete = context.WordToComplete; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); - var results = new List(); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); - // First, try to match \\server\share - var shareMatch = Regex.Match(wordToComplete, "^\\\\\\\\([^\\\\]+)\\\\([^\\\\]*)$"); + // Matches file shares with and without the provider name and with either slash direction. + // Avoids matching Windows device paths like \\.\CDROM0 and \\?\Volume{b8f3fc1c-5cd6-4553-91e2-d6814c4cd375}\ + var shareMatch = s_shareMatch.Match(wordToComplete); if (shareMatch.Success) { // Only match share names, no filenames. - var server = shareMatch.Groups[1].Value; - var sharePattern = WildcardPattern.Get(shareMatch.Groups[2].Value + "*", WildcardOptions.IgnoreCase); + var provider = shareMatch.Groups[1].Value; + var server = shareMatch.Groups[2].Value; + var sharePattern = WildcardPattern.Get(shareMatch.Groups[3].Value + "*", WildcardOptions.IgnoreCase); var ignoreHidden = context.GetOption("IgnoreHiddenShares", @default: false); var shares = GetFileShares(server, ignoreHidden); + if (shares.Count == 0) + { + return CommandCompletion.EmptyCompletionResult; + } + + var shareResults = new List(shares.Count); foreach (var share in shares) { if (sharePattern.IsMatch(share)) { - string shareFullPath = "\\\\" + server + "\\" + share; - if (quote != string.Empty) + string sharePath = $"\\\\{server}\\{share}"; + string completionText; + if (quote == string.Empty) + { + completionText = share.Contains(' ') + ? $"'{provider}{sharePath}'" + : $"{provider}{sharePath}"; + } + else { - shareFullPath = quote + shareFullPath + quote; + completionText = $"{quote}{provider}{sharePath}{quote}"; } - results.Add(new CompletionResult(shareFullPath, shareFullPath, CompletionResultType.ProviderContainer, shareFullPath)); + + shareResults.Add(new CompletionResult(completionText, share, CompletionResultType.ProviderContainer, sharePath)); } } + + return shareResults; + } + + string filter; + string basePath; + int providerSeparatorIndex = -1; + bool defaultRelativePath = false; + bool inputUsedHomeChar = false; + + if (string.IsNullOrEmpty(wordToComplete)) + { + filter = "*"; + basePath = "."; + defaultRelativePath = true; } else { - // We want to prefer relative paths in a completion result unless the user has already - // specified a drive or portion of the path. - var executionContext = context.ExecutionContext; - var defaultRelative = string.IsNullOrWhiteSpace(wordToComplete) - || (wordToComplete.IndexOfAny(Utils.Separators.Directory) != 0 && - !Regex.Match(wordToComplete, @"^~[\\/]+.*").Success && - !executionContext.LocationGlobber.IsAbsolutePath(wordToComplete, out _)); - var relativePaths = context.GetOption("RelativePaths", @default: defaultRelative); - var useLiteralPath = context.GetOption("LiteralPaths", @default: false); - - if (useLiteralPath && LocationGlobber.StringContainsGlobCharacters(wordToComplete)) - { - wordToComplete = WildcardPattern.Escape(wordToComplete, Utils.Separators.StarOrQuestion); - } + providerSeparatorIndex = wordToComplete.IndexOf("::", StringComparison.Ordinal); + int pathStartOffset = providerSeparatorIndex == -1 ? 0 : providerSeparatorIndex + 2; + inputUsedHomeChar = pathStartOffset + 2 <= wordToComplete.Length + && wordToComplete[pathStartOffset] is '~' + && wordToComplete[pathStartOffset + 1] is '/' or '\\'; - if (!defaultRelative && wordToComplete.Length >= 2 && wordToComplete[1] == ':' && char.IsLetter(wordToComplete[0]) && executionContext != null) + // This simple analysis is quick but doesn't handle scenarios where a separator character is not actually a separator + // For example "\" or ":" in *nix filenames. This is only a problem if it appears to be the last separator though. + int lastSeparatorIndex = wordToComplete.LastIndexOfAny(Utils.Separators.DirectoryOrDrive); + if (lastSeparatorIndex == -1) { - // We don't actually need the drive, but the drive must be "mounted" in PowerShell before completion - // can succeed. This call will mount the drive if it wasn't already. - executionContext.SessionState.Drive.GetAtScope(wordToComplete.Substring(0, 1), "global"); + // Input is a simple word with no path separators like: "Program Files" + filter = $"{wordToComplete}*"; + basePath = "."; + defaultRelativePath = true; } - - var powerShellExecutionHelper = context.Helper; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") - .AddParameter("Path", wordToComplete + "*"); - - Exception exceptionThrown; - var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - - if (psobjs != null) + else { - var isFileSystem = false; - var wordContainsProviderId = ProviderSpecified(wordToComplete); - - if (psobjs.Count > 0) + if (lastSeparatorIndex + 1 == wordToComplete.Length) { - dynamic firstObj = psobjs[0]; - var provider = firstObj.Provider as ProviderInfo; - isFileSystem = provider != null && - provider.Name.Equals(FileSystemProvider.ProviderName, - StringComparison.OrdinalIgnoreCase); + // Input ends with a separator like: "./", "filesystem::" or "C:" + filter = "*"; + basePath = wordToComplete; } else { - try - { - ProviderInfo provider; - if (defaultRelative) - { - provider = executionContext.EngineSessionState.CurrentDrive.Provider; - } - else - { - executionContext.LocationGlobber.GetProviderPath(wordToComplete, out provider); - } - isFileSystem = provider != null && - provider.Name.Equals(FileSystemProvider.ProviderName, - StringComparison.OrdinalIgnoreCase); - } - catch (Exception) - { - } + // Input contains a separator, but doesn't end with one like: "C:\Program Fil" or "Registry::HKEY_LOC" + filter = $"{wordToComplete.Substring(lastSeparatorIndex + 1)}*"; + basePath = wordToComplete.Substring(0, lastSeparatorIndex + 1); } - if (isFileSystem) + if (!inputUsedHomeChar && basePath[0] is not '/' and not '\\') { - bool hiddenFilesAreHandled = false; - - if (psobjs.Count > 0 && !LocationGlobber.StringContainsGlobCharacters(wordToComplete)) - { - string leaf = null; - string pathWithoutProvider = wordContainsProviderId - ? wordToComplete.Substring(wordToComplete.IndexOf(':') + 2) - : wordToComplete; + defaultRelativePath = !context.ExecutionContext.LocationGlobber.IsAbsolutePath(wordToComplete, out _); + } + } + } - try - { - leaf = Path.GetFileName(pathWithoutProvider); - } - catch (Exception) - { - } + StringConstantType stringType; + switch (quote) + { + case "": + stringType = StringConstantType.BareWord; + break; - var notHiddenEntries = new HashSet(StringComparer.OrdinalIgnoreCase); - string providerPath = null; + case "\"": + stringType = StringConstantType.DoubleQuoted; + break; - foreach (dynamic entry in psobjs) - { - providerPath = entry.ProviderPath; - if (string.IsNullOrEmpty(providerPath)) - { - // This is unexpected. ProviderPath should never be null or an empty string - leaf = null; - break; - } + default: + stringType = StringConstantType.SingleQuoted; + break; + } - if (!notHiddenEntries.Contains(providerPath)) - { - notHiddenEntries.Add(providerPath); - } - } + var useLiteralPath = context.GetOption("LiteralPaths", @default: false); + if (useLiteralPath) + { + basePath = EscapePath(basePath, stringType, useLiteralPath, out _); + } - if (leaf != null) - { - leaf = leaf + "*"; - var parentPath = Path.GetDirectoryName(providerPath); + PowerShell currentPS = context.Helper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") + .AddParameter("Path", basePath); - // ProviderPath should be absolute path for FileSystem entries - if (!string.IsNullOrEmpty(parentPath)) - { - string[] entries = null; - try - { - entries = Directory.GetFileSystemEntries(parentPath, leaf); - } - catch (Exception) - { - } + 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 (entries != null) - { - hiddenFilesAreHandled = true; + 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; + } - 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); - if ((fileInfo.Attributes & FileAttributes.Hidden) != 0) - { - PSObject wrapper = PSObject.AsPSObject(entry); - psobjs.Add(wrapper); - } - } - } - } - } - } - } + var resolvedPaths = context.Helper.ExecuteCurrentPowerShell(out _); + if (resolvedPaths is null || resolvedPaths.Count == 0) + { + return CommandCompletion.EmptyCompletionResult; + } - if (!hiddenFilesAreHandled) - { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") - .AddParameter("Path", wordToComplete + "*") - .AddParameter("Hidden", 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}::"; + } - var hiddenItems = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (hiddenItems != null && hiddenItems.Count > 0) - { - foreach (var hiddenItem in hiddenItems) + List results; + switch (resolvedProvider.Name) + { + case FileSystemProvider.ProviderName: + results = GetFileSystemProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + extension, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType, + relativeBasePath); + break; + + default: + results = GetDefaultProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType); + break; + } + + return results.OrderBy(x => x.ToolTip); + } + + /// + /// Helper method for generating path completion results for the file system provider. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static List GetFileSystemProviderResults( + CompletionContext context, + ProviderInfo provider, + Collection resolvedPaths, + string filterText, + HashSet includedExtensions, + bool containersOnly, + bool relativePaths, + bool literalPaths, + bool inputUsedHome, + string providerPrefix, + StringConstantType stringType, + string relativeBasePath) + { +#if DEBUG + Diagnostics.Assert(provider.Name.Equals(FileSystemProvider.ProviderName), "Provider should be filesystem provider."); +#endif + var enumerationOptions = _enumerationOptions; + var results = new List(); + string homePath = inputUsedHome && !string.IsNullOrEmpty(provider.Home) ? provider.Home : null; + + WildcardPattern wildcardFilter; + if (WildcardPattern.ContainsRangeWildcard(filterText)) + { + wildcardFilter = WildcardPattern.Get(filterText, WildcardOptions.IgnoreCase); + filterText = "*"; + } + else + { + wildcardFilter = null; + } + + foreach (var item in resolvedPaths) + { + var pathInfo = (PathInfo)item.BaseObject; + var dirInfo = new DirectoryInfo(pathInfo.ProviderPath); + + bool baseQuotesNeeded = false; + string basePath; + if (!relativePaths) + { + if (pathInfo.Drive is null) + { + basePath = dirInfo.FullName; + } + else + { + int stringStartIndex = pathInfo.Drive.Root.EndsWith(provider.ItemSeparator) && pathInfo.Drive.Root.Length > 1 + ? pathInfo.Drive.Root.Length - 1 + : pathInfo.Drive.Root.Length; + + basePath = pathInfo.Drive.VolumeSeparatedByColon + ? string.Concat(pathInfo.Drive.Name, ":", dirInfo.FullName.AsSpan(stringStartIndex)) + : string.Concat(pathInfo.Drive.Name, dirInfo.FullName.AsSpan(stringStartIndex)); + } + + basePath = basePath.EndsWith(provider.ItemSeparator) + ? providerPrefix + basePath + : providerPrefix + basePath + provider.ItemSeparator; + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + } + else + { + basePath = null; + } + IEnumerable fileSystemObjects = containersOnly + ? dirInfo.EnumerateDirectories(filterText, enumerationOptions) + : dirInfo.EnumerateFileSystemInfos(filterText, enumerationOptions); + + foreach (var entry in fileSystemObjects) + { + bool isContainer = entry.Attributes.HasFlag(FileAttributes.Directory); + if (!isContainer && includedExtensions is not null && !includedExtensions.Contains(entry.Extension)) + { + continue; + } + + var entryName = entry.Name; + if (wildcardFilter is not null && !wildcardFilter.IsMatch(entryName)) + { + continue; + } + + if (basePath is null) + { + basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( + entry.FullName, + relativeBasePath); + if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) + { + basePath = $".{provider.ItemSeparator}{basePath}"; + } + + basePath = basePath.Remove(basePath.Length - entry.Name.Length); + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + } + + var resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; + + bool leafQuotesNeeded; + var completionText = NewPathCompletionText( + basePath, + EscapePath(entryName, stringType, literalPaths, out leafQuotesNeeded), + stringType, + containsNestedExpressions: false, + forceQuotes: baseQuotesNeeded || leafQuotesNeeded, + addAmpersand: false); + results.Add(new CompletionResult(completionText, entryName, resultType, entry.FullName)); + } + } + + return results; + } + + /// + /// Helper method for generating path completion results standard providers that don't need any special treatment. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static List GetDefaultProviderResults( + CompletionContext context, + ProviderInfo provider, + Collection resolvedPaths, + string filterText, + bool containersOnly, + bool relativePaths, + bool literalPaths, + bool inputUsedHome, + string providerPrefix, + StringConstantType stringType) + { + string homePath = inputUsedHome && !string.IsNullOrEmpty(provider.Home) + ? provider.Home + : null; + + var pattern = WildcardPattern.Get(filterText, WildcardOptions.IgnoreCase); + var results = new List(); + + foreach (var item in resolvedPaths) + { + var pathInfo = (PathInfo)item.BaseObject; + string baseTooltip = pathInfo.ProviderPath.Equals(string.Empty, StringComparison.Ordinal) + ? pathInfo.Path + : pathInfo.ProviderPath; + if (baseTooltip[^1] is not '\\' and not '/' and not ':') + { + baseTooltip += provider.ItemSeparator; + } + + _ = context.Helper.CurrentPowerShell + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") + .AddParameter("LiteralPath", pathInfo.Path); + + bool hadErrors; + var childItemOutput = context.Helper.ExecuteCurrentPowerShell(out _, out hadErrors); + + if (childItemOutput.Count == 1 && + (pathInfo.Provider.FullName + "::" + pathInfo.ProviderPath).EqualsOrdinalIgnoreCase(childItemOutput[0].Properties["PSPath"].Value as string)) + { + // Get-ChildItem returned the item itself instead of the children so there must be no child items to complete. + continue; + } + + var childrenInfoTable = new Dictionary(childItemOutput.Count); + var childNameList = new List(childItemOutput.Count); + + if (hadErrors) + { + // Get-ChildItem failed to get some items (Access denied or something) + // Save relevant info and try again to get just the names. + foreach (dynamic child in childItemOutput) + { + childrenInfoTable.Add(GetChildNameFromPsObject(child, provider.ItemSeparator), child.PSIsContainer); + } + + _ = context.Helper.CurrentPowerShell + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") + .AddParameter("LiteralPath", pathInfo.Path) + .AddParameter("Name"); + childItemOutput = context.Helper.ExecuteCurrentPowerShell(out _); + foreach (var child in childItemOutput) + { + var childName = (string)child.BaseObject; + childNameList.Add(childName); + } + } + else + { + foreach (dynamic child in childItemOutput) + { + var childName = GetChildNameFromPsObject(child, provider.ItemSeparator); + childrenInfoTable.Add(childName, child.PSIsContainer); + childNameList.Add(childName); + } + } + + if (childNameList.Count == 0) + { + continue; + } + + string basePath = providerPrefix.Length > 0 + ? string.Concat(providerPrefix, pathInfo.Path.AsSpan(providerPrefix.Length)) + : pathInfo.Path; + if (basePath[^1] is not '\\' and not '/' and not ':') + { + basePath += provider.ItemSeparator; + } + + if (relativePaths) + { + basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( + basePath + childNameList[0], context.ExecutionContext.SessionState.Internal.CurrentLocation.ProviderPath); + if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) + { + basePath = $".{provider.ItemSeparator}{basePath}"; + } + + basePath = basePath.Remove(basePath.Length - childNameList[0].Length); + } + + bool baseQuotesNeeded; + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + + foreach (var childName in childNameList) + { + if (!pattern.IsMatch(childName)) + { + continue; + } + + CompletionResultType resultType; + if (childrenInfoTable.TryGetValue(childName, out bool isContainer)) + { + if (containersOnly && !isContainer) + { + continue; + } + + resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; + } + else + { + resultType = CompletionResultType.Text; + } + + bool leafQuotesNeeded; + var completionText = NewPathCompletionText( + basePath, + EscapePath(childName, stringType, literalPaths, out leafQuotesNeeded), + stringType, + containsNestedExpressions: false, + forceQuotes: baseQuotesNeeded || leafQuotesNeeded, + addAmpersand: false); + results.Add(new CompletionResult(completionText, childName, resultType, baseTooltip + childName)); + } + } + + return results; + } + + private static string GetChildNameFromPsObject(dynamic psObject, char separator) + { + if (((PSObject)psObject).BaseObject is string result) + { + // The "Get-ChildItem" call for this provider returned a string that we assume is the child name. + // This is what the SCCM provider returns. + return result; + } + + string childName = psObject.PSChildName; + if (childName is not null) + { + return childName; + } + + // Some providers (Like the variable provider) don't include a PSChildName property + // so we get the child name from the path instead. + childName = psObject.PSPath ?? string.Empty; + int ProviderSeparatorIndex = childName.IndexOf("::", StringComparison.Ordinal); + childName = childName.Substring(ProviderSeparatorIndex + 2); + int indexOfName = childName.LastIndexOf(separator); + if (indexOfName == -1 || indexOfName + 1 == childName.Length) + { + return childName; + } + + return childName.Substring(indexOfName + 1); + } + + /// + /// Takes a path and rebuilds it with the specified variable replacements. + /// Also escapes special characters as needed. + /// + private static string RebuildPathWithVars( + string path, + string homePath, + StringConstantType stringType, + bool literalPath, + out bool quotesAreNeeded) + { + var sb = new StringBuilder(path.Length); + int homeIndex = string.IsNullOrEmpty(homePath) + ? -1 + : path.IndexOf(homePath, StringComparison.OrdinalIgnoreCase); + quotesAreNeeded = false; + bool useSingleQuoteEscapeRules = stringType is StringConstantType.SingleQuoted or StringConstantType.BareWord; + + for (int i = 0; i < path.Length; i++) + { + // on Windows, we need to preserve the expanded home path as native commands don't understand it +#if UNIX + if (i == homeIndex) + { + _ = sb.Append('~'); + i += homePath.Length - 1; + continue; + } +#endif + + EscapeCharIfNeeded(sb, path, i, stringType, literalPath, useSingleQuoteEscapeRules, ref quotesAreNeeded); + _ = sb.Append(path[i]); + } + + return sb.ToString(); + } + + private static string EscapePath(string path, StringConstantType stringType, bool literalPath, out bool quotesAreNeeded) + { + var sb = new StringBuilder(path.Length); + bool useSingleQuoteEscapeRules = stringType is StringConstantType.SingleQuoted or StringConstantType.BareWord; + quotesAreNeeded = false; + + for (int i = 0; i < path.Length; i++) + { + EscapeCharIfNeeded(sb, path, i, stringType, literalPath, useSingleQuoteEscapeRules, ref quotesAreNeeded); + _ = sb.Append(path[i]); + } + + return sb.ToString(); + } + + private static void EscapeCharIfNeeded( + StringBuilder sb, + string path, + int index, + StringConstantType stringType, + bool literalPath, + bool useSingleQuoteEscapeRules, + ref bool quotesAreNeeded) + { + switch (path[index]) + { + case '#': + case '-': + case '@': + if (index == 0 && stringType == StringConstantType.BareWord) + { + // Chars that would start a new token when used as the first char in a bareword argument. + quotesAreNeeded = true; + } + break; + + case ' ': + case ',': + case ';': + case '(': + case ')': + case '{': + case '}': + case '|': + case '&': + if (stringType == StringConstantType.BareWord) + { + // Chars that would start a new token when used anywhere in a bareword argument. + quotesAreNeeded = true; + } + break; + + case '[': + case ']': + if (!literalPath) + { + // Wildcard characters that need to be escaped. + int backtickCount; + if (useSingleQuoteEscapeRules) + { + backtickCount = 1; + } + else + { + backtickCount = sb[^1] == '`' ? 4 : 2; + } + + _ = sb.Append('`', backtickCount); + quotesAreNeeded = true; + } + break; + + case '`': + // Literal backtick needs to be escaped to not be treated as an escape character + if (useSingleQuoteEscapeRules) + { + if (!literalPath) + { + _ = sb.Append('`'); + } + } + else + { + int backtickCount = !literalPath && sb[^1] == '`' ? 3 : 1; + _ = sb.Append('`', backtickCount); + } + + if (stringType is StringConstantType.BareWord or StringConstantType.DoubleQuoted) + { + quotesAreNeeded = true; + } + break; + + case '$': + // $ needs to be escaped so following chars are not parsed as a variable/subexpression + if (!useSingleQuoteEscapeRules) + { + _ = sb.Append('`'); + } + + if (stringType is StringConstantType.BareWord or StringConstantType.DoubleQuoted) + { + quotesAreNeeded = true; + } + break; + + default: + if (useSingleQuoteEscapeRules) + { + // Bareword or singlequoted input string. + if (path[index].IsSingleQuote()) + { + // SingleQuotes are escaped with more single quotes. quotesAreNeeded is set so bareword strings can quoted. + _ = sb.Append('\''); + quotesAreNeeded = true; + } + else if (!quotesAreNeeded && stringType == StringConstantType.BareWord && path[index].IsDoubleQuote()) + { + // Bareword string with double quote inside. Make sure to quote it so we don't need to escape it. + quotesAreNeeded = true; + } + } + else if (path[index].IsDoubleQuote()) + { + // Double quoted or bareword with variables input string. Need to escape double quotes. + _ = sb.Append('`'); + quotesAreNeeded = true; + } + break; + } + } + + private static string NewPathCompletionText(string parent, string leaf, StringConstantType stringType, bool containsNestedExpressions, bool forceQuotes, bool addAmpersand) + { + string result; + if (stringType == StringConstantType.SingleQuoted) + { + result = addAmpersand ? $"& '{parent}{leaf}'" : $"'{parent}{leaf}'"; + } + else if (stringType == StringConstantType.DoubleQuoted) + { + result = addAmpersand ? $"& \"{parent}{leaf}\"" : $"\"{parent}{leaf}\""; + } + else + { + if (forceQuotes) + { + if (containsNestedExpressions) + { + result = addAmpersand ? $"& \"{parent}{leaf}\"" : $"\"{parent}{leaf}\""; + } + else + { + result = addAmpersand ? $"& '{parent}{leaf}'" : $"'{parent}{leaf}'"; + } + } + else + { + result = string.Concat(parent, leaf); + } + } + + return result; + } + + private static readonly Regex s_shareMatch = new( + @"(^Microsoft\.PowerShell\.Core\\FileSystem::|^FileSystem::|^)(?:\\\\|//)(?![.|?])([^\\/]+)(?:\\|/)([^\\/]*)$", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct SHARE_INFO_1 + { + public string netname; + public int type; + public string remark; + } + + private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior + }; + + internal static List GetFileShares(string machine, bool ignoreHidden) + { +#if UNIX + return new List(); +#else + nint shBuf = nint.Zero; + uint numEntries = 0; + uint totalEntries; + uint resumeHandle = 0; + try + { + int result = Interop.Windows.NetShareEnum( + machine, + level: 1, + out shBuf, + Interop.Windows.MAX_PREFERRED_LENGTH, + out numEntries, + out totalEntries, + ref resumeHandle); + + var shares = new List(); + if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + { + for (int i = 0; i < numEntries; ++i) + { + nint curInfoPtr = shBuf + (Marshal.SizeOf() * i); + SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + + if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + { + continue; + } + + if (ignoreHidden && shareInfo.netname.EndsWith('$')) + { + continue; + } + + shares.Add(shareInfo.netname); + } + } + + return shares; + } + finally + { + if (shBuf != nint.Zero) + { + Interop.Windows.NetApiBufferFree(shBuf); + } + } +#endif + } + + private static bool CheckFileExtension(string path, HashSet extension) + { + if (extension == null || extension.Count == 0) + return true; + + var ext = System.IO.Path.GetExtension(path); + return ext == null || extension.Contains(ext); + } + + #endregion Filenames + + #region Variable + + /// + /// + /// + /// + public static IEnumerable CompleteVariable(string variableName) + { + var runspace = Runspace.DefaultRunspace; + if (runspace == null) + { + // No runspace, just return no results. + return CommandCompletion.EmptyCompletionResult; + } + + var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); + } + + private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:", "Using:" }; + + private static readonly SearchValues s_charactersRequiringQuotes = SearchValues.Create("-`&@'\"#{}()$,;|<> .\\/ \t^"); + + private static bool ContainsCharactersRequiringQuotes(ReadOnlySpan text) + => text.ContainsAny(s_charactersRequiringQuotes); + + internal static List CompleteVariable(CompletionContext context) + { + HashSet hashedResults = new(StringComparer.OrdinalIgnoreCase); + List results = new(); + List tempResults = new(); + + var wordToComplete = context.WordToComplete; + string scopePrefix = string.Empty; + var colon = wordToComplete.IndexOf(':'); + if (colon >= 0) + { + scopePrefix = wordToComplete.Remove(colon + 1); + wordToComplete = wordToComplete.Substring(colon + 1); + } + + var lastAst = context.RelatedAsts?[^1]; + var variableAst = lastAst as VariableExpressionAst; + if (lastAst is PropertyMemberAst || + (lastAst is not null && lastAst.Parent is ParameterAst parameter && parameter.DefaultValue != lastAst)) + { + // User is adding a new parameter or a class member, variable tab completion is not useful. + return results; + } + var prefix = variableAst != null && variableAst.Splatted ? "@" : "$"; + bool tokenAtCursorUsedBraces = context.TokenAtCursor is not null && context.TokenAtCursor.Text.StartsWith("${"); + + // Look for variables in the input (e.g. parameters, etc.) before checking session state - these + // variables might not exist in session state yet. + var wildcardPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + if (lastAst is not null) + { + Ast parent = lastAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor + { + CompletionVariableAst = lastAst, + StopSearchOffset = lastAst.Extent.StartOffset, + Context = context.TypeInferenceContext + }; + while (parent != null) + { + if (parent is IParameterMetadataProvider) + { + findVariablesVisitor.Top = parent; + parent.Visit(findVariablesVisitor); + } + + parent = parent.Parent; + } + + foreach (string varName in findVariablesVisitor.FoundVariables) + { + if (!wildcardPattern.IsMatch(varName)) + { + continue; + } + + VariableInfo varInfo = findVariablesVisitor.VariableInfoTable[varName]; + PSTypeName varType = varInfo.LastDeclaredConstraint ?? varInfo.LastAssignedType; + string toolTip; + if (varType is null) + { + toolTip = varName; + } + else + { + toolTip = varType.Type is not null + ? StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(varType.Type, dropNamespaces: true), varName) + : varType.Name; + } + + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(varName) + ? prefix + scopePrefix + varName + : prefix + "{" + scopePrefix + varName + "}"; + AddUniqueVariable(hashedResults, results, completionText, varName, toolTip); + } + } + + if (colon == -1) + { + var allVariables = context.ExecutionContext.SessionState.Internal.GetVariableTable(); + foreach (var key in allVariables.Keys) + { + if (wildcardPattern.IsMatch(key)) + { + var variable = allVariables[key]; + var name = variable.Name; + var value = variable.Value; + var toolTip = value is null + ? key + : StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(value.GetType(), dropNamespaces: true), key); + if (!string.IsNullOrEmpty(variable.Description)) + { + toolTip += $" - {variable.Description}"; + } + + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + name + : prefix + "{" + name + "}"; + AddUniqueVariable(hashedResults, tempResults, completionText, key, toolTip); + } + } + + if (tempResults.Count > 0) + { + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + tempResults.Clear(); + } + } + else + { + string pattern; + if (s_variableScopes.Contains(scopePrefix, StringComparer.OrdinalIgnoreCase)) + { + pattern = string.Concat("variable:", wordToComplete, "*"); + } + else + { + pattern = scopePrefix + wordToComplete + "*"; + } + + var powerShellExecutionHelper = context.Helper; + powerShellExecutionHelper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern) + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); + + var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _); + + if (psobjs is not null) + { + foreach (dynamic psobj in psobjs) + { + var name = psobj.Name as string; + if (!string.IsNullOrEmpty(name)) + { + var tooltip = name; + var variable = PSObject.Base(psobj) as PSVariable; + if (variable != null) + { + var value = variable.Value; + if (value != null) { - psobjs.Add(hiddenItem); + tooltip = StringUtil.Format("[{0}]${1}", + ToStringCodeMethods.Type(value.GetType(), + dropNamespaces: true), name); + } + + if (!string.IsNullOrEmpty(variable.Description)) + { + tooltip += $" - {variable.Description}"; } } + + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + scopePrefix + name + : prefix + "{" + scopePrefix + name + "}"; + AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + } + } + } + } + + if (colon == -1 && "env".StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + { + var envVars = Environment.GetEnvironmentVariables(); + foreach (var key in envVars.Keys) + { + var name = "env:" + key; + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + name + : prefix + "{" + name + "}"; + AddUniqueVariable(hashedResults, tempResults, completedName, name, "[string]" + name); + } + + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + tempResults.Clear(); + } + + if (colon == -1) + { + // Return variables already in session state first, because we can sometimes give better information, + // like the variables type. + foreach (var specialVariable in s_specialVariablesCache.Value) + { + if (wildcardPattern.IsMatch(specialVariable)) + { + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(specialVariable) + ? prefix + specialVariable + : prefix + "{" + specialVariable + "}"; + + AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + } + } + + var allDrives = context.ExecutionContext.SessionState.Drive.GetAll(); + foreach (var drive in allDrives) + { + if (drive.Name.Length < 2 + || !wildcardPattern.IsMatch(drive.Name) + || !drive.Provider.ImplementingType.IsAssignableTo(typeof(IContentCmdletProvider))) + { + continue; + } + + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(drive.Name) + ? prefix + drive.Name + ":" + : prefix + "{" + drive.Name + ":}"; + var tooltip = string.IsNullOrEmpty(drive.Description) + ? drive.Name + : drive.Description; + AddUniqueVariable(hashedResults, tempResults, completedName, drive.Name, tooltip); + } + + if (tempResults.Count > 0) + { + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + } + + foreach (var scope in s_variableScopes) + { + if (wildcardPattern.IsMatch(scope)) + { + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(scope) + ? prefix + scope + : prefix + "{" + scope + "}"; + AddUniqueVariable(hashedResults, results, completedName, scope, scope); + } + } + } + + 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) + { + PSTypeName type; + switch (ast) + { + case ConstantExpressionAst constant: + type = new PSTypeName(constant.StaticType); + break; + + case ExpandableStringExpressionAst: + type = new PSTypeName(typeof(string)); + break; + + case ConvertExpressionAst convertExpression: + type = new PSTypeName(convertExpression.Type.TypeName); + break; + + case HashtableAst: + type = new PSTypeName(typeof(Hashtable)); + break; + + case ArrayExpressionAst: + case ArrayLiteralAst: + type = new PSTypeName(typeof(object[])); + break; + + case ScriptBlockExpressionAst: + type = new PSTypeName(typeof(ScriptBlock)); + break; + + default: + type = null; + break; + } + + return type; + } + + private void SaveVariableInfo(string variableName, PSTypeName variableType, bool isConstraint) + { + if (VariableInfoTable.TryGetValue(variableName, out VariableInfo varInfo)) + { + if (isConstraint) + { + varInfo.LastDeclaredConstraint = variableType; + } + else + { + varInfo.LastAssignedType = variableType; + } + } + else + { + varInfo = isConstraint + ? new VariableInfo() { LastDeclaredConstraint = variableType } + : new VariableInfo() { LastAssignedType = variableType }; + VariableInfoTable.Add(variableName, varInfo); + FoundVariables.Add(variableName); + } + } + + public override AstVisitAction DefaultVisit(Ast ast) + { + if (ast.Extent.StartOffset > StopSearchOffset) + { + // When visiting do while/until statements, the condition will be visited before the statement block. + // The condition itself may not be interesting if it's after the cursor, but the statement block could be. + return ast is PipelineBaseAst && ast.Parent is DoUntilStatementAst or DoWhileStatementAst + ? AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; + } + + 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; } - // Sorting the results by the path - var sortedPsobjs = psobjs.OrderBy(a => a, new ItemPathComparer()); - - foreach (PSObject psobj in sortedPsobjs) + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); + } + else if (left is ArrayLiteralAst array) + { + foreach (ExpressionAst expression in array.Elements) { - object baseObj = PSObject.Base(psobj); - string path = null, providerPath = null; - - // 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; - } - } + ProcessAssignmentLeftSide(expression, right); + } + } + else if (left is ParenExpressionAst parenExpression) + { + ExpressionAst pureExpression = parenExpression.Pipeline.GetPureExpression(); + if (pureExpression is not null) + { + ProcessAssignmentLeftSide(pureExpression, right); + } + } + } - if (path == null) continue; - if (isFileSystem && providerPath == null) continue; + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + if (commandAst.Extent.StartOffset > StopSearchOffset) + { + return AstVisitAction.StopVisit; + } - string completionText; - if (relativePaths) + var commandName = commandAst.GetCommandName(); + if (commandName is not null && s_varModificationCommands.Contains(commandName)) + { + StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, resolve: false, s_varModificationParameters); + if (bindingResult is not null + && bindingResult.BoundParameters.TryGetValue("Name", out ParameterBindingResult variableName)) + { + var nameValue = variableName.ConstantValue as string; + if (nameValue is not null) { - try + PSTypeName variableType; + if (bindingResult.BoundParameters.TryGetValue("Value", out ParameterBindingResult variableValue)) { - var sessionStateInternal = executionContext.EngineSessionState; - completionText = sessionStateInternal.NormalizeRelativePath(path, sessionStateInternal.CurrentLocation.ProviderPath); - string parentDirectory = ".." + Path.DirectorySeparatorChar; - if (!completionText.StartsWith(parentDirectory, StringComparison.Ordinal)) - completionText = Path.Combine(".", completionText); + variableType = GetInferredVarTypeFromAst(variableValue.Value); } - catch (Exception) + else { - // The object at the specified path is not accessable, such as c:\hiberfil.sys (for hibernation) or c:\pagefile.sys (for paging) - // We ignore those files - continue; + variableType = null; } - } - else - { - completionText = path; - } - if (ProviderSpecified(completionText) && !wordContainsProviderId) - { - // Remove the provider id from the path: cd \\scratch2\scratch\dongbw - var index = completionText.IndexOf(':'); - completionText = completionText.Substring(index + 2); + SaveVariableInfo(nameValue, variableType, isConstraint: false); } + } + } - if (CompletionRequiresQuotes(completionText, !useLiteralPath)) + var bindResult = StaticParameterBinder.BindCommand(commandAst, resolve: false); + if (bindResult is not null) + { + foreach (var parameterName in s_outVarParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult outVarBind)) { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") + var varName = outVarBind.ConstantValue as string; + if (varName is not null) { - 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("$", "`$"); + SaveVariableInfo(varName, new PSTypeName(typeof(ArrayList)), isConstraint: false); } + } + } - if (!useLiteralPath) + if (commandAst.Parent is PipelineAst pipeline && pipeline.Extent.EndOffset > CompletionVariableAst.Extent.StartOffset) + { + foreach (var parameterName in s_pipelineVariableParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult pipeVarBind)) { - if (quoteInUse == "'") - { - completionText = completionText.Replace("[", "`["); - completionText = completionText.Replace("]", "`]"); - } - else + var varName = pipeVarBind.ConstantValue as string; + if (varName is not null) { - completionText = completionText.Replace("[", "``["); - completionText = completionText.Replace("]", "``]"); + var inferredTypes = AstTypeInference.InferTypeOf(commandAst, Context, TypeInferenceRuntimePermissions.AllowSafeEval); + PSTypeName varType = inferredTypes.Count == 0 + ? null + : inferredTypes[0]; + SaveVariableInfo(varName, varType, isConstraint: false); } } - - completionText = quoteInUse + completionText + quoteInUse; - } - else if (quote != string.Empty) - { - completionText = quote + completionText + quote; } + } + } - if (isFileSystem) + foreach (RedirectionAst redirection in commandAst.Redirections) + { + if (redirection is FileRedirectionAst fileRedirection + && fileRedirection.Location is StringConstantExpressionAst redirectTarget + && redirectTarget.Value.StartsWith("variable:", StringComparison.OrdinalIgnoreCase) + && redirectTarget.Value.Length > "variable:".Length) + { + string varName = redirectTarget.Value.Substring("variable:".Length); + PSTypeName varType; + switch (fileRedirection.FromStream) { - // Use .NET APIs directly to reduce the time overhead - var isContainer = Directory.Exists(providerPath); - if (containerOnly && !isContainer) - continue; + case RedirectionStream.Error: + varType = new PSTypeName(typeof(ErrorRecord)); + break; - if (!containerOnly && !isContainer && !CheckFileExtension(providerPath, extension)) - continue; + case RedirectionStream.Warning: + varType = new PSTypeName(typeof(WarningRecord)); + break; + + 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; - string tooltip = providerPath, listItemText = Path.GetFileName(providerPath); - results.Add(new CompletionResult(completionText, listItemText, - isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem, - tooltip)); + default: + varType = null; + break; } - 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; - } - if (string.IsNullOrEmpty(listItemText)) - { - // For provider items that don't have PSChildName values, such as variable::error - listItemText = item.Name; - } - 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)); - } - } // End of not filesystem case - } // End of foreach - } // End of 'if (psobjs != null)' + SaveVariableInfo(varName, varType, isConstraint: false); + } + } + + return AstVisitAction.Continue; } - return results; - } + public override AstVisitAction VisitParameter(ParameterAst parameterAst) + { + if (parameterAst.Extent.StartOffset > StopSearchOffset) + { + return AstVisitAction.StopVisit; + } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct SHARE_INFO_1 - { - public string netname; - public int type; - public string remark; - } + VariableExpressionAst variableExpression = parameterAst.Name; + if (variableExpression == CompletionVariableAst) + { + return AstVisitAction.Continue; + } - private const int MAX_PREFERRED_LENGTH = -1; - private const int NERR_Success = 0; - private const int ERROR_MORE_DATA = 234; - private const int STYPE_DISKTREE = 0; - private const int STYPE_MASK = 0x000000FF; + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(parameterAst.StaticType), isConstraint: true); - [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); + return AstVisitAction.Continue; + } - internal static List GetFileShares(string machine, bool ignoreHidden) - { -#if UNIX - return new List(); -#else - IntPtr shBuf; - uint numEntries; - uint totalEntries; - uint resumeHandle = 0; - int result = NetShareEnum(machine, 1, out shBuf, - MAX_PREFERRED_LENGTH, out numEntries, out totalEntries, - ref resumeHandle); + 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; + } - var shares = new List(); - if (result == NERR_Success || result == ERROR_MORE_DATA) + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { - for (int i = 0; i < numEntries; ++i) + // Attributes can't assign values to variables so they aren't interesting. + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + if (scriptBlockExpressionAst == Top) { - IntPtr curInfoPtr = (IntPtr)((long)shBuf + (Marshal.SizeOf() * i)); - SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + return AstVisitAction.Continue; + } - if ((shareInfo.type & STYPE_MASK) != STYPE_DISKTREE) - continue; - if (ignoreHidden && shareInfo.netname.EndsWith("$", StringComparison.Ordinal)) - continue; - shares.Add(shareInfo.netname); + Ast parent = scriptBlockExpressionAst.Parent; + // This loop checks if the scriptblock is used as a command, or an argument for a command, eg: ForEach-Object -Process {$Var1 = "Hello"}, {Var2 = $true} + while (true) + { + if (parent is CommandAst cmdAst) + { + string cmdName = cmdAst.GetCommandName(); + return s_localScopeCommandNames.Contains(cmdName) + || (cmdAst.CommandElements[0] is ScriptBlockExpressionAst && cmdAst.InvocationOperator == TokenKind.Dot) + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + + if (parent is not CommandExpressionAst and not PipelineAst and not StatementBlockAst and not ArrayExpressionAst and not ArrayLiteralAst) + { + return AstVisitAction.SkipChildren; + } + + parent = parent.Parent; } } - return shares; -#endif - } - private static bool CheckFileExtension(string path, HashSet extension) - { - if (extension == null || extension.Count == 0) - return true; + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) + { + if (dataStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } - var ext = System.IO.Path.GetExtension(path); - return ext == null || extension.Contains(ext); + if (dataStatementAst.Variable is not null) + { + SaveVariableInfo(dataStatementAst.Variable, variableType: null, isConstraint: false); + } + + return AstVisitAction.SkipChildren; + } } - #endregion Filenames + private static readonly Lazy> s_specialVariablesCache = new(BuildSpecialVariablesCache); - #region Variable + 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)); + } + } - /// - /// - /// - /// - /// - public static IEnumerable CompleteVariable(string variableName) + return result; + } + + internal static PSTypeName GetLastDeclaredTypeConstraint(VariableExpressionAst variableAst, TypeInferenceContext typeInferenceContext) { - var runspace = Runspace.DefaultRunspace; - if (runspace == null) + Ast parent = variableAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor() { - // No runspace, just return no results. - return CommandCompletion.EmptyCompletionResult; + CompletionVariableAst = variableAst, + StopSearchOffset = variableAst.Extent.StartOffset, + Context = typeInferenceContext + }; + while (parent != null) + { + if (parent is IParameterMetadataProvider) + { + findVariablesVisitor.Top = parent; + parent.Visit(findVariablesVisitor); + } + + if (findVariablesVisitor.VariableInfoTable.TryGetValue(variableAst.VariablePath.UserPath, out VariableInfo varInfo) + && varInfo.LastDeclaredConstraint is not null) + { + return varInfo.LastDeclaredConstraint; + } + + parent = parent.Parent; } - var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); - var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; - return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); + return null; } - private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" }; - private static readonly char[] s_charactersRequiringQuotes = new char[] { - '-', '`', '&', '@', '\'', '"', '#', '{', '}', '(', ')', '$', ',', ';', '|', '<', '>', ' ', '.', '\\', '/', '\t', '^', - }; + #endregion Variables - internal static List CompleteVariable(CompletionContext context) + #region Comments + + internal static List CompleteComment(CompletionContext context, ref int replacementIndex, ref int replacementLength) { - HashSet hashedResults = new HashSet(StringComparer.OrdinalIgnoreCase); - List results = new List(); + if (context.WordToComplete.StartsWith("<#", StringComparison.Ordinal)) + { + return CompleteCommentHelp(context, ref replacementIndex, ref replacementLength); + } - var wordToComplete = context.WordToComplete; - var colon = wordToComplete.IndexOf(':'); + // Complete #requires statements + if (context.WordToComplete.StartsWith("#requires ", StringComparison.OrdinalIgnoreCase)) + { + return CompleteRequires(context, ref replacementIndex, ref replacementLength); + } - var prefix = "$"; - var lastAst = context.RelatedAsts.Last(); - var variableAst = lastAst as VariableExpressionAst; - if (variableAst != null && variableAst.Splatted) + var results = new List(); + + // Complete the history entries + Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$"); + if (!matchResult.Success) { - prefix = "@"; + return results; } - // 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) + string wordToComplete = matchResult.Groups[1].Value; + Collection psobjs; + + int entryId; + if (Regex.IsMatch(wordToComplete, @"^[0-9]+$") && LanguagePrimitives.TryConvertTo(wordToComplete, out entryId)) { - Ast parent = lastAst.Parent; - var findVariablesVisitor = new FindVariablesVisitor { CompletionVariableAst = lastAst }; - while (parent != null) + context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)).AddParameter("Id", entryId); + psobjs = context.Helper.ExecuteCurrentPowerShell(out _); + + if (psobjs != null && psobjs.Count == 1) { - if (parent is IParameterMetadataProvider) + var historyInfo = PSObject.Base(psobjs[0]) as HistoryInfo; + if (historyInfo != null) { - findVariablesVisitor.Top = parent; - parent.Visit(findVariablesVisitor); + 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)); } - parent = parent.Parent; } + } - foreach (Tuple varAst in findVariablesVisitor.VariableSources) - { - Ast astTarget = null; - string userPath = null; + return results; + } - VariableExpressionAst variableDefinitionAst = varAst.Item2 as VariableExpressionAst; - if (variableDefinitionAst != null) - { - userPath = varAst.Item1; - astTarget = varAst.Item2.Parent; - } - else - { - CommandAst commandParameterAst = varAst.Item2 as CommandAst; - if (commandParameterAst != null) - { - userPath = varAst.Item1; - astTarget = varAst.Item2; - } - } + private static List CompleteRequires(CompletionContext context, ref int replacementIndex, ref int replacementLength) + { + var results = new List(); - if (String.IsNullOrEmpty(userPath)) - { - Diagnostics.Assert(false, "Found a variable source but it was an unknown AST type."); - } + int cursorIndex = context.CursorPosition.ColumnNumber - 1; + string lineToCursor = context.CursorPosition.Line.Substring(0, cursorIndex); - if (wildcardPattern.IsMatch(userPath)) - { - var completedName = (userPath.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + userPath - : prefix + "{" + userPath + "}"; - var tooltip = userPath; - var ast = astTarget; + // RunAsAdministrator must be the last parameter in a Requires statement so no completion if the cursor is after the parameter. + if (lineToCursor.Contains(" -RunAsAdministrator", StringComparison.OrdinalIgnoreCase)) + { + return results; + } - while (ast != null) - { - var parameterAst = ast as ParameterAst; - if (parameterAst != null) - { - var typeConstraint = parameterAst.Attributes.OfType().FirstOrDefault(); - if (typeConstraint != null) - { - tooltip = StringUtil.Format("{0}${1}", typeConstraint.Extent.Text, userPath); - } + // Regex to find parameter like " -Parameter1" or " -" + MatchCollection hashtableKeyMatches = Regex.Matches(lineToCursor, @"\s+-([A-Za-z]+|$)"); + if (hashtableKeyMatches.Count == 0) + { + return results; + } - break; - } + Group currentParameterMatch = hashtableKeyMatches[^1].Groups[1]; - var assignmentAst = ast.Parent as AssignmentStatementAst; - if (assignmentAst != null) - { - if (assignmentAst.Left == ast) - { - tooltip = ast.Extent.Text; - } - break; - } + // Complete the parameter if the cursor is at a parameter + if (currentParameterMatch.Index + currentParameterMatch.Length == cursorIndex) + { + string currentParameterPrefix = currentParameterMatch.Value; - 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; - } + replacementIndex = context.CursorPosition.Offset - currentParameterPrefix.Length; + replacementLength = currentParameterPrefix.Length; - ast = ast.Parent; - } - AddUniqueVariable(hashedResults, results, completedName, userPath, tooltip); + // Produce completions for all parameters that begin with the prefix we've found, + // but which haven't already been specified in the line we need to complete + foreach (string parameter in s_requiresParameters) + { + if (parameter.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) + && !context.CursorPosition.Line.Contains($" -{parameter}", StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetRequiresParametersToolTip(parameter); + results.Add(new CompletionResult(parameter, parameter, CompletionResultType.ParameterName, toolTip)); } } + + return results; } - string pattern; - string provider; - if (colon == -1) + // Regex to find parameter values (any text that appears after various delimiters) + hashtableKeyMatches = Regex.Matches(lineToCursor, @"(\s+|,|;|{|\""|'|=)(\w+|$)"); + string currentValue; + if (hashtableKeyMatches.Count == 0) { - pattern = "variable:" + wordToComplete + "*"; - provider = ""; + currentValue = string.Empty; } else { - provider = wordToComplete.Substring(0, colon + 1); - if (s_variableScopes.Contains(provider, StringComparer.OrdinalIgnoreCase)) - { - pattern = "variable:" + wordToComplete.Substring(colon + 1) + "*"; - } - else - { - pattern = wordToComplete + "*"; - } + currentValue = hashtableKeyMatches[^1].Groups[2].Value; } - var powerShellExecutionHelper = context.Helper; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern) - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); + replacementIndex = context.CursorPosition.Offset - currentValue.Length; + replacementLength = currentValue.Length; - Exception exceptionThrown; - var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) + // Complete PSEdition parameter values + if (currentParameterMatch.Value.Equals("PSEdition", StringComparison.OrdinalIgnoreCase)) { - foreach (dynamic psobj in psobjs) + foreach (string psEditionEntry in s_requiresPSEditions) { - var name = psobj.Name as string; - if (!string.IsNullOrEmpty(name)) + if (psEditionEntry.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - var tooltip = name; - var variable = PSObject.Base(psobj) as PSVariable; - if (variable != null) - { - var value = variable.Value; - if (value != null) - { - tooltip = StringUtil.Format("[{0}]${1}", - ToStringCodeMethods.Type(value.GetType(), - dropNamespaces: true), name); - } - } - - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + provider + name - : prefix + "{" + provider + name + "}"; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + string toolTip = GetRequiresPsEditionsToolTip(psEditionEntry); + results.Add(new CompletionResult(psEditionEntry, psEditionEntry, CompletionResultType.ParameterValue, toolTip)); } } + + return results; } - if (colon == -1 && "env".StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + // Complete Modules module specification values + if (currentParameterMatch.Value.Equals("Modules", StringComparison.OrdinalIgnoreCase)) { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", "env:*") - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Key"); + int hashtableStart = lineToCursor.LastIndexOf("@{"); + int hashtableEnd = lineToCursor.LastIndexOf('}'); + + bool insideHashtable = hashtableStart != -1 && (hashtableEnd == -1 || hashtableEnd < hashtableStart); - psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) + // If not inside a hashtable, try to complete a module simple name + if (!insideHashtable) { - foreach (dynamic psobj in psobjs) - { - var name = psobj.Name as string; - if (!string.IsNullOrEmpty(name)) - { - name = "env:" + name; - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + name - : prefix + "{" + name + "}"; - AddUniqueVariable(hashedResults, results, completedName, name, "[string]" + name); - } - } + context.WordToComplete = currentValue; + return CompleteModuleName(context, true); } - } - // Return variables already in session state first, because we can sometimes give better information, - // like the variables type. - foreach (var specialVariable in s_specialVariablesCache.Value) - { - if (wildcardPattern.IsMatch(specialVariable)) + 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) { - var completedName = (specialVariable.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + specialVariable - : prefix + "{" + specialVariable + "}"; + string existingHashtableKey = existingHashtableKeyMatch.Value.TrimStart(s_hashtableKeyPrefixes); + + if (string.IsNullOrEmpty(existingHashtableKey)) + { + continue; + } + + // Remove the existing key we just saw + moduleSpecKeysToComplete.Remove(existingHashtableKey); - AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + // 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; + } } - } - if (colon == -1) - { - // 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) + 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) { - foreach (var psobj in psobjs) + if (sawModuleNameLast) { - var driveInfo = PSObject.Base(psobj) as PSDriveInfo; - if (driveInfo != null) - { - var name = driveInfo.Name; - if (name != null && !string.IsNullOrWhiteSpace(name) && name.Length > 1) - { - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + name + ":" - : prefix + "{" + name + ":}"; - - var tooltip = string.IsNullOrEmpty(driveInfo.Description) ? name : driveInfo.Description; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); - } - } + context.WordToComplete = currentValue; + return CompleteModuleName(context, true); } + + return results; } - var scopePattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); - foreach (var scope in s_variableScopes) + // Now try to complete hashtable keys + foreach (string moduleSpecKey in moduleSpecKeysToComplete) { - if (scopePattern.IsMatch(scope)) + if (moduleSpecKey.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - var completedName = (scope.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + scope - : prefix + "{" + scope + "}"; - AddUniqueVariable(hashedResults, results, completedName, scope, scope); + string toolTip = GetRequiresModuleSpecKeysToolTip(moduleSpecKey); + results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, toolTip)); } } } @@ -4716,160 +6266,332 @@ internal static List CompleteVariable(CompletionContext contex return results; } - private static void AddUniqueVariable(HashSet hashedResults, List results, string completionText, string listItemText, string tooltip) + private static readonly string[] s_requiresParameters = new string[] { - if (!hashedResults.Contains(completionText)) - { - hashedResults.Add(completionText); - results.Add(new CompletionResult(completionText, listItemText, CompletionResultType.Variable, tooltip)); - } - } + "Modules", + "PSEdition", + "RunAsAdministrator", + "Version" + }; - private class FindVariablesVisitor : AstVisitor + private static string GetRequiresParametersToolTip(string name) => name switch { - internal Ast Top; - internal Ast CompletionVariableAst; - internal readonly List> VariableSources = new List>(); + "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; } - public override AstVisitAction VisitCommand(CommandAst commandAst) + if (lineKeyword is null) { - // 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" }; + return null; + } + + // Cursor is within or at the start/end of the keyword + if (context.CursorPosition.Offset <= lineKeyword.Index + lineKeyword.Length + context.TokenAtCursor.Extent.StartOffset) + { + replacementIndex = context.TokenAtCursor.Extent.StartOffset + lineKeyword.Index; + replacementLength = lineKeyword.Value.Length; - 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); + } + + var result = new List(); + foreach (string parameter in parametersToShow) + { + result.Add(new CompletionResult(parameter)); } - return results; + return result.Count > 0 ? result : null; } #endregion Comments @@ -4881,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: @@ -4898,29 +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 = LastAstAsMemberExpression.Member; + } + + 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) { - memberNameCandidateAst = lastAstAsMemberExpr.Member; + memberName = $"{stringExpression.Value}*"; } - targetExpr = lastAstAsMemberExpr.Expression; } 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)) @@ -4928,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) @@ -4945,6 +6675,7 @@ internal static List CompleteMember(CompletionContext context, break; } } + var nextToLastAst = commandAst.CommandElements[i - 1]; var nextToLastExtent = nextToLastAst.Extent; var lastExtent = lastAst.Extent; @@ -4954,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") @@ -4965,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) @@ -5015,10 +6793,15 @@ 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 - CompleteMemberByInferredType(context, inferredTypes, results, memberName, filter: null, isStatic: @static); + CompleteMemberByInferredType(context.TypeInferenceContext, inferredTypes, results, memberName, filter: null, isStatic: @static); } else { @@ -5071,32 +6854,81 @@ 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 + /// 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)); + } + } } /// - /// Verify if an expression Ast is representing the $ConfigurationData variable + /// Verify if an expression Ast is representing the $ConfigurationData variable. /// private static bool IsConfigurationDataVariable(ExpressionAst targetExpr) { @@ -5115,29 +6947,150 @@ private static bool IsConfigurationDataVariable(ExpressionAst targetExpr) } /// - /// Verify if an expression Ast is within a configuration definition + /// Verify if an expression Ast is within a configuration definition. /// private static bool IsInDscContext(ExpressionAst expression) { return Ast.GetAncestorAst(expression) != null; } - private static void CompleteMemberByInferredType(CompletionContext 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); - var members = context.TypeInferenceContext.GetMembersByInferredType(psTypeName, isStatic, filter); + + 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' @@ -5145,7 +7098,7 @@ private static void CompleteMemberByInferredType(CompletionContext context, IEnu { // Complete extension methods 'Where' and 'ForEach' for Enumerable types extensionMethodsAdded = true; - CompleteExtensionMethods(memberNamePattern, results); + CompleteExtensionMethods(memberNamePattern, results, addMethodParenthesis); } } @@ -5157,14 +7110,13 @@ private static void CompleteMemberByInferredType(CompletionContext context, IEnu .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; @@ -5174,9 +7126,10 @@ private static void AddInferredMember(object member, WildcardPattern memberNameP { memberName = propertyInfo.Name; getToolTip = () => ToStringCodeMethods.Type(propertyInfo.PropertyType) + " " + memberName - + " { " + (propertyInfo.GetGetMethod() != null ? "get; " : "") - + (propertyInfo.GetSetMethod() != null ? "set; " : "") + "}"; + + " { " + (propertyInfo.GetGetMethod() != null ? "get; " : string.Empty) + + (propertyInfo.GetSetMethod() != null ? "set; " : string.Empty) + "}"; } + var fieldInfo = member as FieldInfo; if (fieldInfo != null) { @@ -5189,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; @@ -5208,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())); } @@ -5246,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; }"); } @@ -5264,10 +7241,16 @@ private static bool IsWriteablePropertyMember(object member) return psPropertyInfo.IsSettable; } + if (member is PropertyMemberAst) + { + // Properties in PowerShell classes are always writeable + return true; + } + return false; } - private static bool IsPropertyMember(object member) + internal static bool IsPropertyMember(object member) { return member is PropertyInfo || member is FieldInfo @@ -5284,7 +7267,7 @@ private static bool IsMemberHidden(object member) var memberInfo = member as MemberInfo; if (memberInfo != null) - return memberInfo.GetCustomAttributes(typeof(HiddenAttribute), false).Any(); + return memberInfo.GetCustomAttributes(typeof(HiddenAttribute), false).Length > 0; var propertyMemberAst = member as PropertyMemberAst; if (propertyMemberAst != null) @@ -5312,7 +7295,6 @@ private static bool IsConstructor(object member) return false; } - #endregion Members #region Types @@ -5320,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) @@ -5358,9 +7341,11 @@ internal string ShortTypeName ? FullTypeName.Substring(lastPlusIndex + 1) : FullTypeName.Substring(lastDotIndex + 1); } + return _shortTypeName; } } + private string _shortTypeName; /// @@ -5375,13 +7360,15 @@ internal string Namespace int lastDotIndex = FullTypeName.LastIndexOf('.'); _namespace = FullTypeName.Substring(0, lastDotIndex); } + return _namespace; } } + private string _namespace; /// - /// Construct the CompletionResult based on the information of this instance + /// Construct the CompletionResult based on the information of this instance. /// internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix) { @@ -5389,7 +7376,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string } /// - /// Construct the CompletionResult based on the information of this instance + /// Construct the CompletionResult based on the information of this instance. /// internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove) { @@ -5411,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. @@ -5426,13 +7413,15 @@ private int GenericArgumentCount var argCount = FullTypeName.Substring(backtick + 1); _genericArgumentCount = LanguagePrimitives.ConvertTo(argCount); } + return _genericArgumentCount; } } + private int _genericArgumentCount = 0; /// - /// Construct the CompletionResult based on the information of this instance + /// Construct the CompletionResult based on the information of this instance. /// internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix) { @@ -5440,7 +7429,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string } /// - /// Construct the CompletionResult based on the information of this instance + /// Construct the CompletionResult based on the information of this instance. /// internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove) { @@ -5461,8 +7450,9 @@ 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(']'); return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip.ToString()); @@ -5478,20 +7468,18 @@ private class TypeCompletion : TypeCompletionBase protected string GetTooltipPrefix() { - TypeInfo typeInfo = Type.GetTypeInfo(); - if (typeof(Delegate).IsAssignableFrom(Type)) return "Delegate "; - if (typeInfo.IsInterface) + if (Type.IsInterface) return "Interface "; - if (typeInfo.IsClass) + if (Type.IsClass) return "Class "; - if (typeInfo.IsEnum) + if (Type.IsEnum) return "Enum "; if (typeof(ValueType).IsAssignableFrom(Type)) return "Struct "; - return ""; // what other interesting types are there? + return string.Empty; // what other interesting types are there? } internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix) @@ -5506,7 +7494,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string // If the completion included a namespace and ToStringCodeMethods.Type found // an accelerator, then just use the type's FullName instead because the user // probably didn't want the accelerator. - if (keyMatched.IndexOf('.') != -1 && completion.IndexOf('.') == -1) + if (keyMatched.Contains('.') && !completion.Contains('.')) { completion = Type.FullName; } @@ -5527,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) { @@ -5554,6 +7542,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string if (i != 0) tooltip.Append(", "); tooltip.Append(genericParameters[i].Name); } + tooltip.Append(']'); return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip.ToString()); @@ -5563,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; @@ -5575,6 +7564,7 @@ internal override CompletionResult GetCompletionResult(string keyMatched, string { listItemText = listItemText.Substring(dotIndex + 1); } + return new CompletionResult(prefix + Namespace + suffix, listItemText, CompletionResultType.Namespace, "Namespace " + Namespace); } @@ -5584,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; @@ -5592,6 +7582,7 @@ private class TypeCompletionMapping } private static TypeCompletionMapping[][] s_typeCache; + private static TypeCompletionMapping[][] InitializeTypeCache() { #region Process_TypeAccelerators @@ -5648,6 +7639,7 @@ private static TypeCompletionMapping[][] InitializeTypeCache() entry = new TypeCompletionMapping { Key = shortTypeName }; entries.Add(shortTypeName, entry); } + entry.Completions.Add(typeCompletionInstance); } @@ -5691,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) { @@ -5703,10 +7695,10 @@ private static TypeCompletionMapping[][] InitializeTypeCache() } /// - /// Handle namespace when initializing the type cache + /// Handle namespace when initializing the type cache. /// - /// The TypeCompletionMapping dictionary - /// The namespace + /// The TypeCompletionMapping dictionary. + /// The namespace. private static void HandleNamespace(Dictionary entryCache, string @namespace) { if (string.IsNullOrEmpty(@namespace)) @@ -5740,12 +7732,12 @@ private static void HandleNamespace(Dictionary en } /// - /// Handle a type when initializing the type cache + /// Handle a type when initializing the type cache. /// - /// The TypeCompletionMapping dictionary - /// The full type name - /// The short type name - /// The actual type object. It may be null if we are handling type information from the CoreCLR TypeCatalog + /// The TypeCompletionMapping dictionary. + /// The full type name. + /// The short type name. + /// The actual type object. It may be null if we are handling type information from the CoreCLR TypeCatalog. private static void HandleType(Dictionary entryCache, string fullTypeName, string shortTypeName, Type actualType) { if (string.IsNullOrEmpty(fullTypeName)) { return; } @@ -5764,6 +7756,7 @@ private static void HandleType(Dictionary entryCa typeCompletionBase = actualType != null ? (TypeCompletionBase)new GenericTypeCompletion { Type = actualType } + : new GenericTypeCompletionInStringFormat { FullTypeName = fullTypeName }; // Remove the backtick, we only want 1 generic in our results for types like Func or Action. @@ -5774,6 +7767,7 @@ private static void HandleType(Dictionary entryCa { typeCompletionBase = actualType != null ? (TypeCompletionBase)new TypeCompletion { Type = actualType } + : new TypeCompletionInStringFormat { FullTypeName = fullTypeName }; } @@ -5796,6 +7790,7 @@ private static void HandleType(Dictionary entryCa entry = new TypeCompletionMapping { Key = shortTypeName }; entryCache.Add(shortTypeName, entry); } + entry.Completions.Add(typeCompletionBase); } } @@ -5805,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; @@ -5820,12 +7815,13 @@ internal static List CompleteNamespace(CompletionContext conte results.Add(completion.GetCompletionResult(entry.Key, prefix, suffix)); } } - 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; } /// - /// Complete a typename + /// Complete a typename. /// /// /// @@ -5848,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; @@ -5875,29 +7871,35 @@ internal static List CompleteType(CompletionContext context, s } } - //this is a temporary fix. Only the type defined in the same script get complete. Need to use using Module when that is available. - var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0]; - var typeAsts = scriptBlockAst.FindAll(ast => ast is TypeDefinitionAst, false).Cast(); - foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name))) + // this is a temporary fix. Only the type defined in the same script get complete. Need to use using Module when that is available. + if (context.RelatedAsts != null && context.RelatedAsts.Count > 0) { - string toolTipPrefix = String.Empty; - if (typeAst.IsInterface) - toolTipPrefix = "Interface "; - else if (typeAst.IsClass) - toolTipPrefix = "Class "; - else if (typeAst.IsEnum) - toolTipPrefix = "Enum "; + var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0]; + 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; + if (typeAst.IsInterface) + toolTipPrefix = "Interface "; + else if (typeAst.IsClass) + toolTipPrefix = "Class "; + else if (typeAst.IsEnum) + toolTipPrefix = "Enum "; - results.Add(new CompletionResult(prefix + typeAst.Name + suffix, typeAst.Name, CompletionResultType.Type, toolTipPrefix + typeAst.Name)); + results.Add(new CompletionResult(prefix + typeAst.Name + suffix, typeAst.Name, CompletionResultType.Type, toolTipPrefix + typeAst.Name)); + } } - 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; } private static string GetNamespaceToRemove(CompletionContext context, TypeCompletionBase completion) { - if (completion is NamespaceCompletion) { return null; } + if (completion is NamespaceCompletion || context.RelatedAsts == null || context.RelatedAsts.Count == 0) + { + return null; + } var typeCompletion = completion as TypeCompletion; string typeNameSpace = typeCompletion != null @@ -5910,7 +7912,7 @@ private static string GetNamespaceToRemove(CompletionContext context, TypeComple && typeNameSpace != null && typeNameSpace.StartsWith(s.Name.Value, StringComparison.OrdinalIgnoreCase)); - string ns = String.Empty; + string ns = string.Empty; foreach (var nsState in matchingNsStates) { if (nsState.Name.Extent.Text.Length > ns.Length) @@ -5928,51 +7930,31 @@ private static string GetNamespaceToRemove(CompletionContext context, TypeComple internal static List CompleteHelpTopics(CompletionContext context) { - var results = new List(); - var dirPath = Utils.GetApplicationBase(Utils.DefaultPowerShellShellID) + Path.DirectorySeparatorChar + CultureInfo.CurrentCulture.Name; - 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 file in Directory.GetFiles(dirPath)) + if (helpProviders[i] is HelpFileHelpProvider provider) { - 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)); } } @@ -5989,14 +7971,12 @@ internal static List CompleteStatementFlags(TokenKind kind, st { case TokenKind.Switch: - Diagnostics.Assert(!String.IsNullOrEmpty(wordToComplete) && wordToComplete[0].IsDash(), "the word to complete should start with '-'"); + Diagnostics.Assert(!string.IsNullOrEmpty(wordToComplete) && wordToComplete[0].IsDash(), "the word to complete should start with '-'"); wordToComplete = wordToComplete.Substring(1); - bool withColon = wordToComplete.EndsWith(":", StringComparison.Ordinal); + 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(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + string[] enumArray = LanguagePrimitives.EnumSingleTypeConverter.GetEnumNames(typeof(SwitchFlags)); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List(); @@ -6055,7 +8035,6 @@ internal static List CompleteStatementFlags(TokenKind kind, st /// D^ /// } /// } - /// /// /// /// @@ -6123,156 +8102,473 @@ 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, 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); - string parameterName = null; - foreach (var boundArg in binding.BoundArguments) + if (binding is null) + { + return null; + } + + 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, 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 @@ -6283,6 +8579,7 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand // Expand the string if its type is DoubleQuoted or BareWord var constType = expandableStringAst.StringConstantType; if (constType == StringConstantType.DoubleQuotedHereString) { return false; } + Diagnostics.Assert( constType == StringConstantType.BareWord || (constType == StringConstantType.DoubleQuoted && expandableStringAst.Extent.Text[0].IsDoubleQuote()), @@ -6291,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) @@ -6305,8 +8601,8 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand } } - var formattedString = String.Format(CultureInfo.InvariantCulture, expandableStringAst.FormatExpression, varValues.ToArray()); - string quote = (constType == StringConstantType.DoubleQuoted) ? "\"" : String.Empty; + var formattedString = string.Format(CultureInfo.InvariantCulture, expandableStringAst.FormatExpression, varValues.ToArray()); + string quote = (constType == StringConstantType.DoubleQuoted) ? "\"" : string.Empty; expandedString = quote + formattedString + extraText + quote; return true; @@ -6315,65 +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().GetTypeInfo().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) @@ -6383,6 +8683,7 @@ internal static bool IsSplattedVariable(Ast targetExpr) // It's splatted variable, member expansion is not useful return true; } + return false; } @@ -6396,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 }; @@ -6415,17 +8716,18 @@ 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; } - members = PSObject.dotNetStaticAdapter.BaseGetMembers(type); + + members = PSObject.DotNetStaticAdapter.BaseGetMembers(type); } else { members = PSObject.AsPSObject(value).Members; } + var sortedMembers = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _, members); foreach (var member in sortedMembers) @@ -6439,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 + "'"; @@ -6455,14 +8757,15 @@ internal static void CompleteMemberHelper( } string tooltip = memberInfo.ToString(); - if (tooltip.IndexOf("),", StringComparison.OrdinalIgnoreCase) != -1) + if (tooltip.Contains("),", StringComparison.Ordinal)) { - var overloads = tooltip.Split(new[] { ")," }, StringSplitOptions.RemoveEmptyEntries); + var overloads = tooltip.Split("),", StringSplitOptions.RemoveEmptyEntries); var newTooltip = new StringBuilder(); foreach (var overload in overloads) { newTooltip.Append(overload.Trim() + ")\r\n"); } + newTooltip.Remove(newTooltip.Length - 3, 3); tooltip = newTooltip.ToString(); } @@ -6479,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 + "'"; @@ -6506,7 +8808,7 @@ internal static void CompleteMemberHelper( } /// - /// Check if a value is treated as Enumerable in powershell + /// Check if a value is treated as Enumerable in powershell. /// private static bool IsValueEnumerable(object value) { @@ -6527,7 +8829,7 @@ private static bool IsValueEnumerable(object value) } /// - /// Check if a strong type is treated as Enumerable in powershell + /// Check if a strong type is treated as Enumerable in powershell. /// private static bool IsStaticTypeEnumerable(Type type) { @@ -6544,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(':'); @@ -6609,7 +8885,7 @@ private static bool TurnOnLiteralPathOption(CompletionContext completionContext) } /// - /// Return whether we need to add ampersand when it's necessary + /// Return whether we need to add ampersand when it's necessary. /// /// /// @@ -6632,10 +8908,11 @@ internal static bool IsAmpersandNeeded(CompletionContext context, bool defaultCh defaultChoice = !defaultChoice; } } + return defaultChoice; } - private class ItemPathComparer : IComparer + private sealed class ItemPathComparer : IComparer { public int Compare(PSObject x, PSObject y) { @@ -6666,11 +8943,11 @@ public int Compare(PSObject x, PSObject y) if (string.IsNullOrEmpty(xPath) || string.IsNullOrEmpty(yPath)) Diagnostics.Assert(false, "Base object of item PSObject should be either PathInfo or FileSystemInfo"); - return String.Compare(xPath, yPath, StringComparison.CurrentCultureIgnoreCase); + return string.Compare(xPath, yPath, StringComparison.CurrentCultureIgnoreCase); } } - private class CommandNameComparer : IComparer + private sealed class CommandNameComparer : IComparer { public int Compare(PSObject x, PSObject y) { @@ -6689,7 +8966,7 @@ public int Compare(PSObject x, PSObject y) if (xName == null || yName == null) Diagnostics.Assert(false, "Base object of Command PSObject should be either CommandInfo or string"); - return String.Compare(xName, yName, StringComparison.OrdinalIgnoreCase); + return string.Compare(xName, yName, StringComparison.OrdinalIgnoreCase); } } @@ -6697,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. @@ -6715,7 +8992,7 @@ internal static bool TrySafeEval(ExpressionAst ast, ExecutionContext executionCo try { // ConstrainedLanguage has already been applied as necessary when we construct CompletionContext - Diagnostics.Assert(!(ExecutionContext.HasEverUsedConstrainedLanguage && executionContext.LanguageMode != PSLanguageMode.ConstrainedLanguage), + Diagnostics.Assert(!(executionContext.HasRunspaceEverUsedConstrainedLanguageMode && executionContext.LanguageMode != PSLanguageMode.ConstrainedLanguage), "If the runspace has ever used constrained language mode, then the current language mode should already be set to constrained language"); // We're passing 'true' here for isTrustedInput, because SafeExprEvaluator ensures that the AST @@ -6736,56 +9013,98 @@ internal static bool TrySafeEval(ExpressionAst ast, ExecutionContext executionCo } public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return false; } + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return false; } + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return false; } + public object VisitParamBlock(ParamBlockAst paramBlockAst) { return false; } + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { return false; } + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return false; } + public object VisitAttribute(AttributeAst attributeAst) { return false; } + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return false; } + public object VisitParameter(ParameterAst parameterAst) { return false; } + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return false; } + public object VisitIfStatement(IfStatementAst ifStmtAst) { return false; } + public object VisitTrap(TrapStatementAst trapStatementAst) { return false; } + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return false; } + public object VisitDataStatement(DataStatementAst dataStatementAst) { return false; } + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return false; } + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return false; } + public object VisitForStatement(ForStatementAst forStatementAst) { return false; } + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { return false; } + public object VisitCatchClause(CatchClauseAst catchClauseAst) { return false; } + public object VisitTryStatement(TryStatementAst tryStatementAst) { return false; } + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { return false; } + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return false; } + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return false; } + public object VisitExitStatement(ExitStatementAst exitStatementAst) { return false; } + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return false; } + 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; } + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return false; } + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { return false; } + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return false; } + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return false; } + public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return false; } + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return false; } + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { return false; } + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; } + public object VisitUsingExpression(UsingExpressionAst usingExpressionAst) { return false; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; } + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; } + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; } + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; } + public object VisitUsingStatement(UsingStatementAst usingStatementAst) { return false; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) { return false; } + + public object VisitPipelineChain(PipelineChainAst pipelineChainAst) { return false; } + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return configurationDefinitionAst.Body.Accept(this); } - public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) - { - return false; - } public object VisitStatementBlock(StatementBlockAst statementBlockAst) { @@ -6802,6 +9121,13 @@ public object VisitPipeline(PipelineAst pipelineAst) return expr != null && (bool)expr.Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return (bool)binaryExpressionAst.Left.Accept(this) && (bool)binaryExpressionAst.Right.Accept(this); @@ -6871,6 +9197,7 @@ public object VisitHashtable(HashtableAst hashtableAst) if (!(bool)keyValuePair.Item2.Accept(this)) return false; } + return true; } @@ -6884,4 +9211,79 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) return parenExpressionAst.Pipeline.Accept(this); } } + + /// + /// Completes with the property names of the InputObject. + /// + internal class PropertyNameCompleter : IArgumentCompleter + { + private readonly string _parameterNameOfInput; + + /// + /// Initializes a new instance of the class. + /// + public PropertyNameCompleter() + { + _parameterNameOfInput = "InputObject"; + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the property of the input object for which to complete with property names. + public PropertyNameCompleter(string parameterNameOfInput) + { + _parameterNameOfInput = parameterNameOfInput; + } + + IEnumerable IArgumentCompleter.CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + { + 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; + } + } + + var typeInferenceContext = new TypeInferenceContext(); + IEnumerable prevType; + if (i == 0) + { + var parameterAst = (CommandParameterAst)commandAst.Find(ast => ast is CommandParameterAst cpa && cpa.ParameterName == "PropertyName", false); + var pseudoBinding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion); + if (!pseudoBinding.BoundArguments.TryGetValue(_parameterNameOfInput, out var pair) || !pair.ArgumentSpecified) + { + return null; + } + + if (pair is AstPair astPair && astPair.Argument != null) + { + prevType = AstTypeInference.InferTypeOf(astPair.Argument, typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); + } + + return null; + } + else + { + prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); + } + + var result = new List(); + + CompletionCompleters.CompleteMemberByInferredType(typeInferenceContext, prevType, result, wordToComplete + "*", filter: CompletionCompleters.IsPropertyMember, isStatic: false); + return result; + } + } } 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 2109c6b2d0e..0554a52ba17 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs @@ -1,16 +1,10 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// -// Implements CompletionResult. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; namespace System.Management.Automation { - using System; - /// /// Possible types of CompletionResults. /// @@ -71,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. @@ -104,6 +98,7 @@ public string CompletionText { throw PSTraceSource.NewInvalidOperationException(TabCompletionStrings.NoAccessToProperties); } + return _completionText; } } @@ -119,6 +114,7 @@ public string ListItemText { throw PSTraceSource.NewInvalidOperationException(TabCompletionStrings.NoAccessToProperties); } + return _listItemText; } } @@ -134,6 +130,7 @@ public CompletionResultType ResultType { throw PSTraceSource.NewInvalidOperationException(TabCompletionStrings.NoAccessToProperties); } + return _resultType; } } @@ -149,6 +146,7 @@ public string ToolTip { throw PSTraceSource.NewInvalidOperationException(TabCompletionStrings.NoAccessToProperties); } + return _toolTip; } } @@ -170,24 +168,13 @@ 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("completionText"); - } - - if (String.IsNullOrEmpty(listItemText)) - { - throw PSTraceSource.NewArgumentNullException("listItemText"); - } + ArgumentException.ThrowIfNullOrEmpty(completionText); + ArgumentException.ThrowIfNullOrEmpty(listItemText); + ArgumentException.ThrowIfNullOrEmpty(toolTip); if (resultType < CompletionResultType.Text || resultType > CompletionResultType.DynamicKeyword) { - throw PSTraceSource.NewArgumentOutOfRangeException("resultType", resultType); - } - - if (String.IsNullOrEmpty(toolTip)) - { - throw PSTraceSource.NewArgumentNullException("toolTip"); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(resultType), resultType); } _completionText = completionText; @@ -196,7 +183,6 @@ public CompletionResult(string completionText, string listItemText, CompletionRe _resultType = resultType; } - /// /// Initializes a new instance of this class internally if the result out of TabExpansion is a string. /// @@ -209,7 +195,6 @@ public CompletionResult(string completionText) /// /// An null instance of CompletionResult. /// - /// /// /// This can be used in argument completion, to indicate that the completion attempt has gone through the /// native command argument completion methods. diff --git a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs index 337a2dde206..c63a8e7f92d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs @@ -1,7 +1,5 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -9,15 +7,16 @@ using System.Linq; using System.Management.Automation.Language; - namespace System.Management.Automation { /// /// This attribute is used to specify an argument completer for a parameter to a cmdlet or function. /// + /// /// [Parameter()] /// [ArgumentCompleter(typeof(NounArgumentCompleter))] /// public string Noun { get; set; } + /// /// /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] @@ -25,40 +24,62 @@ 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("type"); + throw PSTraceSource.NewArgumentException(nameof(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("scriptBlock"); + 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 { /// @@ -83,78 +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; } } } @@ -171,24 +292,24 @@ 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 + /// Initializes a new instance of the ArgumentCompletionsAttribute class. /// - /// list of complete values - /// for null arguments - /// for invalid arguments + /// List of complete values. + /// For null arguments. + /// For invalid arguments. public ArgumentCompletionsAttribute(params string[] completions) { if (completions == null) { - throw PSTraceSource.NewArgumentNullException("completions"); + throw PSTraceSource.NewArgumentNullException(nameof(completions)); } if (completions.Length == 0) { - throw PSTraceSource.NewArgumentOutOfRangeException("completions", completions); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(completions), completions); } _completions = completions; diff --git a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs index 5954739f62d..2e7457cd812 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs @@ -1,23 +1,21 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; -using System.Collections.ObjectModel; -using System.Collections.Generic; using System.Management.Automation.Host; -using System.Text; -using System.Reflection; using System.Management.Automation.Runspaces; +using System.Reflection; +using System.Text; namespace System.Management.Automation.Language { #region "AstArgumentPair" /// - /// The types for AstParameterArgumentPair + /// The types for AstParameterArgumentPair. /// internal enum AstParameterArgumentType { @@ -29,55 +27,55 @@ internal enum AstParameterArgumentType } /// - /// The base class for parameter argument pair + /// The base class for parameter argument pair. /// internal abstract class AstParameterArgumentPair { /// - /// The parameter Ast + /// The parameter Ast. /// public CommandParameterAst Parameter { get; protected set; } /// - /// The argument type + /// The argument type. /// public AstParameterArgumentType ParameterArgumentType { get; protected set; } /// - /// Indicate if the parameter is specified + /// Indicate if the parameter is specified. /// public bool ParameterSpecified { get; protected set; } = false; /// - /// Indicate if the parameter is specified + /// Indicate if the parameter is specified. /// public bool ArgumentSpecified { get; protected set; } = false; /// - /// The parameter name + /// The parameter name. /// public string ParameterName { get; protected set; } /// - /// The parameter text + /// The parameter text. /// public string ParameterText { get; protected set; } /// - /// The argument type + /// The argument type. /// public Type ArgumentType { get; protected set; } } /// - /// Represent a parameter argument pair. The argument is a pipeline input object + /// Represent a parameter argument pair. The argument is a pipeline input object. /// internal sealed class PipeObjectPair : AstParameterArgumentPair { internal PipeObjectPair(string parameterName, Type pipeObjType) { if (parameterName == null) - throw PSTraceSource.NewArgumentNullException("parameterName"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterName)); Parameter = null; ParameterArgumentType = AstParameterArgumentType.PipeObject; @@ -98,9 +96,9 @@ internal sealed class AstArrayPair : AstParameterArgumentPair internal AstArrayPair(string parameterName, ICollection arguments) { if (parameterName == null) - throw PSTraceSource.NewArgumentNullException("parameterName"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterName)); if (arguments == null || arguments.Count == 0) - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); Parameter = null; ParameterArgumentType = AstParameterArgumentType.AstArray; @@ -114,7 +112,7 @@ internal AstArrayPair(string parameterName, ICollection arguments } /// - /// Get the argument + /// Get the argument. /// public ExpressionAst[] Argument { get; } = null; } @@ -127,7 +125,7 @@ internal sealed class FakePair : AstParameterArgumentPair internal FakePair(CommandParameterAst parameterAst) { if (parameterAst == null) - throw PSTraceSource.NewArgumentNullException("parameterAst"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterAst)); Parameter = parameterAst; ParameterArgumentType = AstParameterArgumentType.Fake; @@ -147,7 +145,7 @@ internal sealed class SwitchPair : AstParameterArgumentPair internal SwitchPair(CommandParameterAst parameterAst) { if (parameterAst == null) - throw PSTraceSource.NewArgumentNullException("parameterAst"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterAst)); Parameter = parameterAst; ParameterArgumentType = AstParameterArgumentType.Switch; @@ -159,7 +157,7 @@ internal SwitchPair(CommandParameterAst parameterAst) } /// - /// Get the argument + /// Get the argument. /// public bool Argument { @@ -177,7 +175,7 @@ internal sealed class AstPair : AstParameterArgumentPair internal AstPair(CommandParameterAst parameterAst) { if (parameterAst == null || parameterAst.Argument == null) - throw PSTraceSource.NewArgumentException("parameterAst"); + throw PSTraceSource.NewArgumentException(nameof(parameterAst)); Parameter = parameterAst; ParameterArgumentType = AstParameterArgumentType.AstPair; @@ -194,18 +192,18 @@ internal AstPair(CommandParameterAst parameterAst) internal AstPair(CommandParameterAst parameterAst, ExpressionAst argumentAst) { if (parameterAst != null && parameterAst.Argument != null) - throw PSTraceSource.NewArgumentException("parameterAst"); + throw PSTraceSource.NewArgumentException(nameof(parameterAst)); if (parameterAst == null && argumentAst == null) - throw PSTraceSource.NewArgumentNullException("argumentAst"); + throw PSTraceSource.NewArgumentNullException(nameof(argumentAst)); Parameter = parameterAst; 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; @@ -214,10 +212,10 @@ internal AstPair(CommandParameterAst parameterAst, ExpressionAst argumentAst) internal AstPair(CommandParameterAst parameterAst, CommandElementAst argumentAst) { if (parameterAst != null && parameterAst.Argument != null) - throw PSTraceSource.NewArgumentException("parameterAst"); + throw PSTraceSource.NewArgumentException(nameof(parameterAst)); if (parameterAst == null || argumentAst == null) - throw PSTraceSource.NewArgumentNullException("argumentAst"); + throw PSTraceSource.NewArgumentNullException(nameof(argumentAst)); Parameter = parameterAst; ParameterArgumentType = AstParameterArgumentType.AstPair; @@ -233,17 +231,17 @@ internal AstPair(CommandParameterAst parameterAst, CommandElementAst argumentAst } /// - /// Indicate if the argument is contained in the CommandParameterAst + /// Indicate if the argument is contained in the CommandParameterAst. /// public bool ParameterContainsArgument { get; } = false; /// - /// Indicate if the argument is of type CommandParameterAst + /// Indicate if the argument is of type CommandParameterAst. /// public bool ArgumentIsCommandParameterAst { get; } = false; /// - /// Get the argument + /// Get the argument. /// public CommandElementAst Argument { get; } = null; } @@ -253,23 +251,21 @@ internal AstPair(CommandParameterAst parameterAst, CommandElementAst argumentAst /// /// Runs the PowerShell parameter binding algorithm against a CommandAst, /// returning information about which parameters were bound. - /// /// public static class StaticParameterBinder { /// - /// Bind a CommandAst to one of PowerShell's built-in commands + /// Bind a CommandAst to one of PowerShell's built-in commands. /// /// The CommandAst that represents the command invocation. /// The StaticBindingResult that represents the binding. public static StaticBindingResult BindCommand(CommandAst commandAst) { - bool resolve = true; - return BindCommand(commandAst, resolve); + return BindCommand(commandAst, resolve: true); } /// - /// Bind a CommandAst to the specified command + /// Bind a CommandAst to the specified command. /// /// The CommandAst that represents the command invocation. /// Boolean to determine whether binding should be syntactic, or should attempt @@ -282,7 +278,7 @@ public static StaticBindingResult BindCommand(CommandAst commandAst, bool resolv } /// - /// Bind a CommandAst to the specified command + /// Bind a CommandAst to the specified command. /// /// The CommandAst that represents the command invocation. /// Boolean to determine whether binding should be syntactic, or should attempt @@ -335,16 +331,17 @@ public static StaticBindingResult BindCommand(CommandAst commandAst, bool resolv { // Handle static binding from a non-PowerShell / C# application // DefaultRunspace is a thread static field, so race condition will not happen because different threads will access different instances of "DefaultRunspace" - if (s_bindCommandRunspace == null) + if (t_bindCommandRunspace == null) { // Create a mini runspace by remove the types and formats InitialSessionState minimalState = InitialSessionState.CreateDefault2(); minimalState.Types.Clear(); minimalState.Formats.Clear(); - s_bindCommandRunspace = RunspaceFactory.CreateRunspace(minimalState); - s_bindCommandRunspace.Open(); + t_bindCommandRunspace = RunspaceFactory.CreateRunspace(minimalState); + t_bindCommandRunspace.Open(); } - Runspace.DefaultRunspace = s_bindCommandRunspace; + + Runspace.DefaultRunspace = t_bindCommandRunspace; // Static binding always does argument binding (not argument or parameter completion). pseudoBinding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, null, PseudoParameterBinder.BindingType.ArgumentBinding); Runspace.DefaultRunspace = null; @@ -357,8 +354,9 @@ public static StaticBindingResult BindCommand(CommandAst commandAst, bool resolv return new StaticBindingResult(commandAst, pseudoBinding); } + [ThreadStatic] - static Runspace s_bindCommandRunspace = null; + private static Runspace t_bindCommandRunspace = null; } /// @@ -485,7 +483,7 @@ private void CreateBindingResultForSuccessfulBind(CommandAst commandAst, PseudoB { CompiledCommandParameter parameter = item.Value.Parameter; CommandElementAst value = null; - Object constantValue = null; + object constantValue = null; // This is a single argument AstPair argumentAstPair = bindingInfo.BoundArguments[item.Key] as AstPair; @@ -526,7 +524,7 @@ private void CreateBindingResultForSuccessfulBind(CommandAst commandAst, PseudoB if (parameter.Type == typeof(SwitchParameter)) { if ((value != null) && - (String.Equals("$false", value.Extent.Text, StringComparison.OrdinalIgnoreCase))) + (string.Equals("$false", value.Extent.Text, StringComparison.OrdinalIgnoreCase))) { continue; } @@ -589,13 +587,14 @@ private void AddDuplicateParameterBindingException(CommandParameterAst duplicate null, null, ParameterBinderStrings.ParameterAlreadyBound, - "ParameterAlreadyBound"); + nameof(ParameterBinderStrings.ParameterAlreadyBound)); // if the duplicated Parameter Name appears more than twice, we will ignore as we already have similar bindingException. if (!BindingExceptions.ContainsKey(duplicateParameter.ParameterName)) { BindingExceptions.Add(duplicateParameter.ParameterName, new StaticBindingError(duplicateParameter, bindingException)); } } + private PseudoBindingInfo _bindingInfo = null; private void CreateBindingResultForSyntacticBind(CommandAst commandAst) @@ -697,22 +696,20 @@ private void AddSwitch(string currentParameter, ParameterBindingResult bindingRe } /// - /// /// public Dictionary BoundParameters { get; } /// - /// /// public Dictionary BindingExceptions { get; } } /// - /// Represents the binding of a parameter to its argument + /// Represents the binding of a parameter to its argument. /// public class ParameterBindingResult { - internal ParameterBindingResult(CompiledCommandParameter parameter, CommandElementAst value, Object constantValue) + internal ParameterBindingResult(CompiledCommandParameter parameter, CommandElementAst value, object constantValue) { this.Parameter = new ParameterMetadata(parameter); this.Value = value; @@ -724,16 +721,18 @@ internal ParameterBindingResult() } /// - /// /// public ParameterMetadata Parameter { get; internal set; } /// - /// /// - public Object ConstantValue + public object ConstantValue { - get { return _constantValue; } + get + { + return _constantValue; + } + internal set { if (value != null) @@ -742,14 +741,18 @@ internal set } } } + private object _constantValue; /// - /// /// public CommandElementAst Value { - get { return _value; } + get + { + return _value; + } + internal set { _value = value; @@ -761,19 +764,20 @@ internal set } } } + private CommandElementAst _value; } /// - /// Represents the exception generated by the static parameter binding process + /// Represents the exception generated by the static parameter binding process. /// public class StaticBindingError { /// - /// Creates a StaticBindingException + /// Creates a StaticBindingException. /// - /// The element associated with the exception - /// The parameter binding exception that got raised + /// The element associated with the exception. + /// The parameter binding exception that got raised. internal StaticBindingError(CommandElementAst commandElement, ParameterBindingException exception) { this.CommandElement = commandElement; @@ -783,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" @@ -802,7 +806,7 @@ internal enum PseudoBindingInfoType internal sealed class PseudoBindingInfo { /// - /// The pseudo binding succeeded + /// The pseudo binding succeeded. /// /// /// @@ -849,7 +853,7 @@ internal PseudoBindingInfo( } /// - /// The pseudo binding failed with parameter set confliction + /// The pseudo binding failed with parameter set confliction. /// /// /// @@ -908,7 +912,7 @@ internal class PseudoParameterBinder { /* /// - /// Get the parameter binding metadata + /// Get the parameter binding metadata. /// /// /// @@ -923,34 +927,35 @@ public Dictionary GetPseudoParameterBinding(ou internal enum BindingType { /// - /// Caller is binding a parameter argument + /// Caller is binding a parameter argument. /// ArgumentBinding = 0, /// - /// Caller is performing completion on a parameter argument + /// Caller is performing completion on a parameter argument. /// ArgumentCompletion, /// - /// Caller is performing completion on a parameter name + /// Caller is performing completion on a parameter name. /// ParameterCompletion } /// - /// Get the parameter binding metadata + /// Get the parameter binding metadata. /// /// - /// Indicate the type of the piped-in argument - /// The CommandParameterAst the cursor is pointing at + /// 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. - /// PseudoBindingInfo - internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType) + /// Indicates if the pseudo binding should bind positional parameters + /// PseudoBindingInfo. + internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType, bool bindPositional = true) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } // initialize/reset the private members @@ -971,12 +976,13 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip try { // Tab expansion is called from a trusted function - we should apply ConstrainedLanguage if necessary. - if (ExecutionContext.HasEverUsedConstrainedLanguage) + if (executionContext.HasRunspaceEverUsedConstrainedLanguageMode) { previousLanguageMode = executionContext.LanguageMode; executionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - _bindingEffective = PrepareCommandElements(executionContext); + + _bindingEffective = PrepareCommandElements(executionContext, paramAstAtCursor); } finally { @@ -994,6 +1000,7 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip { _pipelineInputType = pipeArgumentType; } + _bindingEffective = ParseParameterArguments(paramAstAtCursor); if (_bindingEffective) @@ -1002,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) @@ -1070,9 +1080,9 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip } /// - /// Sets a temporary default host on the ExecutionContext + /// Sets a temporary default host on the ExecutionContext. /// - /// ExecutionContext + /// ExecutionContext. private void SetTemporaryDefaultHost(ExecutionContext executionContext) { if (executionContext.EngineHostInterface.IsHostRefSet) @@ -1094,7 +1104,7 @@ private void SetTemporaryDefaultHost(ExecutionContext executionContext) /// /// Restores original ExecutionContext host state. /// - /// ExecutionContext + /// ExecutionContext. private void RestoreHost(ExecutionContext executionContext) { // Remove temporary host and revert to original. @@ -1138,7 +1148,7 @@ private void RestoreHost(ExecutionContext executionContext) private Dictionary _bindingExceptions; /// - /// Initialize collection/dictionary members when it's necessary + /// Initialize collection/dictionary members when it's necessary. /// private void InitializeMembers() { @@ -1150,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(); @@ -1170,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; @@ -1188,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) { @@ -1201,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 @@ -1225,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)); } } } @@ -1249,7 +1304,6 @@ private bool PrepareCommandElements(ExecutionContext context) _function = false; if (implementsDynamicParameters) { - ParameterBinderController.AddArgumentsToCommandProcessor(commandProcessor, argumentsToGetDynamicParameters.ToArray()); bool retryWithNoArgs = false, alreadyRetried = false; do @@ -1333,6 +1387,7 @@ private bool PrepareCommandElements(ExecutionContext context) _pipelineInputType = typeof(object); break; } + preCmdBaseAst = cmdBase; } } @@ -1349,8 +1404,8 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string { ast = ast.Parent; } - ast.Visit(exportVisitor); + ast.Visit(exportVisitor); CommandProcessorBase commandProcessor = null; resolvedCommandName = _commandAst.GetCommandName(); @@ -1374,6 +1429,7 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string commandProcessor = CommandDiscovery.CreateCommandProcessorForScript(scriptBlock, context, true, context.EngineSessionState); } } + return commandProcessor; } @@ -1382,7 +1438,6 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string /// specified. We always eat the error (such as parameter without value) and continue /// to do the binding. /// - /// /// /// For parameter completion, if the cursor is pointing at a CommandParameterAst, we /// should not try exact matching for that CommandParameterAst. This is to handle the @@ -1412,7 +1467,7 @@ private bool ParseParameterArguments(CommandParameterAst paramAstAtCursor) Diagnostics.Assert(argument.ParameterSpecified && !argument.ArgumentSpecified, "At this point, the parameters should have no arguments"); - //Now check the parameter name with the bindable parameters + // Now check the parameter name with the bindable parameters string parameterName = argument.ParameterName; MergedCompiledCommandParameter matchingParameter = null; @@ -1839,6 +1894,7 @@ private static bool IsTypeEquivalent(Type argType, Type paramType) { result = true; } + return result; } @@ -1853,11 +1909,19 @@ 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; break; } + nonPositionalArguments.Add(argument); } @@ -1914,6 +1978,7 @@ private Collection BindRemainingParameters(Collection< _boundArguments.Add(parameterName, new AstArrayPair(parameterName, argList)); unboundArguments.Clear(); } + result = true; break; } @@ -1971,6 +2036,7 @@ private void BindPipelineParameters() { _boundArguments.Add(parameterName, new PipeObjectPair(parameterName, _pipelineInputType)); } + result = true; break; } 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 395078354a6..9ca87f4c834 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -8,27 +7,29 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Security; -using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Host; + using Microsoft.PowerShell.Commands; using Microsoft.Win32; +using Dbg = System.Management.Automation.Diagnostics; + namespace System.Management.Automation { /// - /// EventArgs for the ScriptCmdletVariableUpdate event + /// EventArgs for the ScriptCmdletVariableUpdate event. /// public class CommandLookupEventArgs : EventArgs { /// - /// Constructor for event args object + /// Constructor for event args object. /// - /// The name of the command we're searching for - /// The origin of the command internal or runspace (external) - /// The execution context for this command + /// The name of the command we're searching for. + /// The origin of the command internal or runspace (external). + /// The execution context for this command. internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, ExecutionContext context) { CommandName = commandName; @@ -36,10 +37,10 @@ internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, _context = context; } - private ExecutionContext _context; + private readonly ExecutionContext _context; /// - /// The name of the command we're looking for + /// The name of the command we're looking for. /// public string CommandName { get; } @@ -54,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; } @@ -64,7 +65,10 @@ internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, /// public ScriptBlock CommandScriptBlock { - get { return _scriptBlock; } + get + { + return _scriptBlock; + } set { @@ -82,11 +86,12 @@ public ScriptBlock CommandScriptBlock } } } + private ScriptBlock _scriptBlock; } /// - /// Defines the preference options for the Module Auto-loading feature + /// Defines the preference options for the Module Auto-loading feature. /// public enum PSModuleAutoLoadingPreference { @@ -113,7 +118,7 @@ public enum PSModuleAutoLoadingPreference internal class CommandDiscovery { [TraceSource("CommandDiscovery", "Traces the discovery of cmdlets, scripts, functions, applications, etc.")] - internal static PSTraceSource discoveryTracer = + internal static readonly PSTraceSource discoveryTracer = PSTraceSource.GetTracer( "CommandDiscovery", "Traces the discovery of cmdlets, scripts, functions, applications, etc.", @@ -124,44 +129,30 @@ internal class CommandDiscovery /// /// Default constructor... /// - /// /// /// If is null. /// - /// internal CommandDiscovery(ExecutionContext context) { if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } Context = context; discoveryTracer.ShowHeaders = false; } - private void AddCmdletToCache(CmdletConfigurationEntry entry) - { - if (!IsSpecialCmdlet(entry.ImplementingType)) - { - CmdletInfo newCmdletInfo = NewCmdletInfo(entry, SessionStateEntryVisibility.Public); - AddCmdletInfoToCache(newCmdletInfo.Name, newCmdletInfo, isGlobal: true); - } - } - /// /// Determines if the cmdlet is a cmdlet that shouldn't be in the discovery list. /// - /// /// /// Type implementing the cmdlet /// - /// /// /// 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 @@ -169,13 +160,6 @@ private bool IsSpecialCmdlet(Type implementingType) return implementingType == typeof(OutLineOutputCommand) || implementingType == typeof(FormatDefaultCommand); } - private CmdletInfo NewCmdletInfo(CmdletConfigurationEntry entry, SessionStateEntryVisibility visibility) - { - CmdletInfo ci = new CmdletInfo(entry.Name, entry.ImplementingType, entry.HelpFileName, entry.PSSnapIn, Context); - ci.Visibility = visibility; - return ci; - } - private CmdletInfo NewCmdletInfo(SessionStateCmdletEntry entry) { return NewCmdletInfo(entry, Context); @@ -204,29 +188,24 @@ internal static AliasInfo NewAliasInfo(SessionStateAliasEntry entry, ExecutionCo /// /// Adds the CmdletInfo to the cmdlet cache in the current scope object. /// - /// /// /// The name of the cmdlet to add. /// - /// /// /// The CmdletInfo to add. /// - /// /// /// If true, the cmdlet is added to the Module Scope of the session state. /// - /// /// /// If a cmdlet with the same module and cmdlet name already exists /// but has a different implementing type. /// - /// internal CmdletInfo AddCmdletInfoToCache(string name, CmdletInfo newCmdletInfo, bool isGlobal) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (newCmdletInfo == null) @@ -274,43 +253,44 @@ internal void AddSessionStateCmdletEntryToCache(SessionStateCmdletEntry entry, b /// /// Look up a command named by the argument string and return its CommandProcessorBase. /// - /// /// /// The command name to lookup. /// - /// - /// Location where the command was dispatched from. - /// + /// Location where the command was dispatched from. /// /// True if command processor should use local scope to execute the command, /// 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. + /// /// - /// /// - /// /// /// If the command, , could not be found. /// - /// /// /// 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); - CommandProcessorBase processor = LookupCommandProcessor(commandInfo, commandOrigin, useLocalScope, null); - // commandInfo.Name might be different than commandName - restore the original invocation name - processor.Command.MyInvocation.InvocationName = commandName; + if (commandInfo != 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; + } 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) @@ -325,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); @@ -340,109 +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); + ScriptRequiresException sre = + new ScriptRequiresException( + scriptInfo.Name, + string.Empty, + string.Empty, + "RequiresShellIDInvalidForSingleShell"); - 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; - } + throw sre; } - else - { - // If there were no PSSnapins required but there is a shellID required, then we need - // to error - if (!String.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) - { - ScriptRequiresException sre = - new ScriptRequiresException( - scriptInfo.Name, - String.Empty, - String.Empty, - "RequiresShellIDInvalidForSingleShell"); - - 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.Count() == 0) - { - 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) @@ -451,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( @@ -484,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); @@ -511,89 +424,32 @@ internal static void VerifyElevatedPrivileges(ExternalScriptInfo scriptInfo) } } - - #region comment out RequiresNetFrameworkVersion feature 8/10/2010 - /* - * The "#requires -NetFrameworkVersion" feature is CUT OFF. - * This method will be reenabled will be CUT OFF too - /* - internal static void VerifyNetFrameworkVersion(ExternalScriptInfo scriptInfo) - { - Version requiresNetFrameworkVersion = scriptInfo.RequiresNetFrameworkVersion; - - if (requiresNetFrameworkVersion != null) - { - if (!Utils.IsNetFrameworkVersionSupported(requiresNetFrameworkVersion)) - { - ScriptRequiresException scriptRequiresException = - new ScriptRequiresException( - scriptInfo.Name, - scriptInfo.NetFrameworkVersionLineNumber, - requiresNetFrameworkVersion, - "ScriptRequiresUnmatchedNetFrameworkVersion"); - throw scriptRequiresException; - } - } - } - */ - #endregion - - - /// - /// 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. /// - /// /// /// The commandInfo for the command to lookup. /// - /// - /// Location where the command was dispatched from. + /// Location where the command was dispatched from. /// /// True if command processor should use local scope to execute the command, /// 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. /// - /// /// - /// /// /// If the command, , could not be found. /// - /// /// /// 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; @@ -639,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) { @@ -647,10 +503,10 @@ internal CommandProcessorBase LookupCommandProcessor(CommandInfo commandInfo, new CommandNotFoundException(reqSyntaxException.Message, reqSyntaxException); throw e; } + break; case CommandTypes.Filter: case CommandTypes.Function: - case CommandTypes.Workflow: case CommandTypes.Configuration: FunctionInfo functionInfo = (FunctionInfo)commandInfo; processor = CreateCommandProcessorForScript(functionInfo, Context, useLocalScope ?? true, sessionState); @@ -677,11 +533,11 @@ internal CommandProcessorBase LookupCommandProcessor(CommandInfo commandInfo, processor.Command.MyInvocation.InvocationName = commandInfo.Name; return processor; - } // LookupCommandProcessor + } internal static void ShouldRun(ExecutionContext context, PSHost host, CommandInfo commandInfo, CommandOrigin commandOrigin) { - //ShouldRunInternal throws PSSecurityException if run is not allowed + // ShouldRunInternal throws PSSecurityException if run is not allowed try { if (commandOrigin == CommandOrigin.Runspace && commandInfo.Visibility != SessionStateEntryVisibility.Public) @@ -709,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) { @@ -721,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) { @@ -733,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) { @@ -745,11 +601,11 @@ 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) { - FunctionInfo fi = new FunctionInfo("", scriptblock, context); + FunctionInfo fi = new FunctionInfo(string.Empty, scriptblock, context); return GetScriptAsCmdletProcessor(fi, context, useNewScope, false, sessionState); } @@ -763,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); } @@ -771,21 +627,17 @@ private static CommandProcessorBase GetScriptAsCmdletProcessor(IScriptCommandInf /// /// Look up a command and return its CommandInfo. /// - /// /// /// The command name to lookup. /// - /// /// /// An instance of a CommandInfo object that represents the /// command. If the command is resolved as an alias, an AliasInfo /// is returned with the ReferencedCommand info intact. /// - /// /// /// If the command, , could not be found. /// - /// internal CommandInfo LookupCommandInfo(string commandName) { return LookupCommandInfo(commandName, CommandOrigin.Internal); @@ -798,7 +650,7 @@ internal CommandInfo LookupCommandInfo(string commandName, CommandOrigin command internal static CommandInfo LookupCommandInfo(string commandName, CommandOrigin commandOrigin, ExecutionContext context) { - return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.None, commandOrigin, context); + return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.ResolveLiteralThenPathPatterns, commandOrigin, context); } internal static CommandInfo LookupCommandInfo( @@ -808,7 +660,7 @@ internal static CommandInfo LookupCommandInfo( CommandOrigin commandOrigin, ExecutionContext context) { - if (String.IsNullOrEmpty(commandName)) + if (string.IsNullOrEmpty(commandName)) { return null; } @@ -845,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 { @@ -877,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 @@ -926,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 = @@ -945,6 +794,11 @@ internal static CommandInfo LookupCommandInfo( internal static void AutoloadModulesWithJobSourceAdapters(System.Management.Automation.ExecutionContext context, CommandOrigin commandOrigin) { + /* This function is used by *-Job cmdlets (JobCmdletBase.BeginProcessing(), StartJobCommand.BeginProcessing()) + It attempts to load modules from a fixed ModulesWithJobSourceAdapters list that currently has only `PSScheduledJob` module that is not PS-Core compatible. + Because this function does not check the result of a (currently failing) `PSScheduledJob` module autoload, it provides no value. + After discussion it was decided to comment out this code as it may be useful if ModulesWithJobSourceAdapters list changes in the future. + if (!context.IsModuleWithJobSourceAdapterLoaded) { PSModuleAutoLoadingPreference moduleAutoLoadingPreference = GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference"); @@ -963,10 +817,11 @@ internal static void AutoloadModulesWithJobSourceAdapters(System.Management.Auto AutoloadSpecifiedModule(module, context, cmdletInfo.Visibility, out unUsedException); } } + context.IsModuleWithJobSourceAdapterLoaded = true; } } - } + }*/ } internal static Collection AutoloadSpecifiedModule(string moduleName, ExecutionContext context, SessionStateEntryVisibility visibility, out Exception exception) @@ -998,13 +853,12 @@ internal static Collection AutoloadSpecifiedModule(string moduleNa { exception = e; discoveryTracer.WriteLine("Encountered error importing module: {0}", e.Message); - //Call-out to user code, catch-all OK + // Call-out to user code, catch-all OK } return matchingModules; } - private static CommandInfo InvokeCommandNotFoundHandler(string commandName, ExecutionContext context, string originalCommandName, CommandOrigin commandOrigin) { CommandInfo result = null; @@ -1025,6 +879,7 @@ private static CommandInfo InvokeCommandNotFoundHandler(string commandName, Exec } finally { context.CommandDiscovery.UnregisterLookupCommandInfoAction("ActiveCommandNotFound", originalCommandName); } } + return result; } @@ -1049,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", @@ -1089,6 +944,7 @@ private static CommandInfo TryNormalSearch(string commandName, { lastError = metadataException; } + return result; } @@ -1104,65 +960,63 @@ 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(); - var defaultAvailableModuleFiles = ModuleUtils.GetDefaultAvailableModuleFiles(true, true, context); + var defaultAvailableModuleFiles = ModuleUtils.GetDefaultAvailableModuleFiles(isForAutoDiscovery: true, context); if (etwEnabled) CommandDiscoveryEventSource.Log.SearchingForModuleFilesStop(); foreach (string modulePath in defaultAvailableModuleFiles) { // 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)) + // Skip if module only has class or other types and no commands. + 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); @@ -1174,7 +1028,6 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, } } - // TODO: this causes AppVeyor builds to fail due to invalid XML being output #if !CORECLR // Close the progress pane that may have popped up from analyzing UNC paths. if (context.CurrentCommandProcessor != null) @@ -1194,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); @@ -1210,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 @@ -1221,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); @@ -1242,7 +1091,7 @@ private static CommandInfo TryModuleAutoLoading(string commandName, ExecutionCon } } - if (String.IsNullOrEmpty(moduleName) || String.IsNullOrEmpty(moduleCommandName) || moduleName.EndsWith(".", StringComparison.Ordinal)) + if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(moduleCommandName) || moduleName.EndsWith('.')) return null; bool etwEnabled = CommandDiscoveryEventSource.Log.IsEnabled(); @@ -1315,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) @@ -1332,53 +1179,25 @@ 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); - - - /// - /// Gets a CommandPathSearch constructed with the specified patterns and - /// using the PATH as the lookup directories - /// - /// - /// - /// The patterns to search for. These patterns must be in the form taken - /// by DirectoryInfo.GetFiles(). - /// - /// - /// - /// An instance of CommandPathSearch that is initialized with the specified - /// patterns and using the PATH as the lookup directories. - /// - internal IEnumerable GetCommandPathSearcher(IEnumerable patterns) - { - // Get the PATH environment variable - IEnumerable lookupPathArray = GetLookupDirectoryPaths(); - - // Construct the CommandPathSearch object and return it. - return new CommandPathSearch(patterns, lookupPathArray, Context); - } // GetCommandPathSearcher + 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 /// variable. /// - /// /// /// The contents of the PATH environment variable split on System.IO.Path.PathSeparator. /// - /// /// /// The result is an ordered list of paths with paths starting with "." unresolved until lookup time. /// - /// - internal IEnumerable GetLookupDirectoryPaths() + internal LookupPathCollection GetLookupDirectoryPaths() { LookupPathCollection result = new LookupPathCollection(); @@ -1390,7 +1209,7 @@ internal IEnumerable GetLookupDirectoryPaths() bool isPathCacheValid = path != null && - String.Equals(_pathCacheKey, path, StringComparison.OrdinalIgnoreCase) && + string.Equals(_pathCacheKey, path, StringComparison.OrdinalIgnoreCase) && _cachedPath != null; if (!isPathCacheValid) @@ -1404,12 +1223,27 @@ internal IEnumerable 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) { string tempDir = directory.TrimStart(); + if (tempDir.EqualsOrdinalIgnoreCase("~")) + { + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify); + } + else if (tempDir.StartsWith("~" + Path.DirectorySeparatorChar)) + { + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify) + + Path.DirectorySeparatorChar + + tempDir.Substring(2); + } + _cachedPath.Add(tempDir); result.Add(tempDir); } @@ -1421,8 +1255,8 @@ internal IEnumerable GetLookupDirectoryPaths() } // Cache the new lookup paths - return _cachedLookupPaths ?? (_cachedLookupPaths = result); - } // GetLookupDirectoryPaths + return _cachedLookupPaths ??= result; + } /// /// The cached list of lookup paths. It can be invalidated by @@ -1436,7 +1270,7 @@ internal IEnumerable GetLookupDirectoryPaths() private string _pathCacheKey; /// - /// The cache of the tokenized PATH directories + /// The cache of the tokenized PATH directories. /// private Collection _cachedPath; @@ -1447,7 +1281,6 @@ internal IEnumerable GetLookupDirectoryPaths() /// /// Gets the PATHEXT environment variable extensions and tokenizes them. /// - /// internal static string[] PathExtensionsWithPs1Prepended { get @@ -1461,13 +1294,12 @@ internal static string[] PathExtensionsWithPs1Prepended } return s_cachedPathExtCollectionWithPs1; - } // get - } // PathExtensions + } + } /// /// Gets the PATHEXT environment variable extensions and tokenizes them. /// - /// internal static string[] PathExtensions { get @@ -1481,16 +1313,16 @@ internal static string[] PathExtensions } return s_cachedPathExtCollection; - } // get - } // PathExtensions + } + } private static void InitPathExtCache(string pathExt) { lock (s_lockObject) { s_cachedPathExtCollection = pathExt != null - ? pathExt.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries) - : Utils.EmptyArray(); + ? pathExt.ToLower().Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + : Array.Empty(); s_cachedPathExtCollectionWithPs1 = new string[s_cachedPathExtCollection.Length + 1]; s_cachedPathExtCollectionWithPs1[0] = StringLiterals.PowerShellScriptFileExtension; Array.Copy(s_cachedPathExtCollection, 0, s_cachedPathExtCollectionWithPs1, 1, s_cachedPathExtCollection.Length); @@ -1503,34 +1335,29 @@ 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; - /// /// Gets the cmdlet information for the specified name. /// - /// /// /// The name of the cmdlet to return the information for. /// /// /// True if we should search all scopes, false if we should stop after finding the first. /// - /// /// /// The CmdletInfo for the cmdlet for all the cmdlets with the specified name. /// - /// /// /// If is null or empty. /// - /// internal IEnumerator GetCmdletInfo(string cmdletName, bool searchAllScopes) { - Dbg.Assert(!String.IsNullOrEmpty(cmdletName), "Caller should verify the cmdletName"); + Dbg.Assert(!string.IsNullOrEmpty(cmdletName), "Caller should verify the cmdletName"); PSSnapinQualifiedName commandName = PSSnapinQualifiedName.GetInstance(cmdletName); @@ -1541,7 +1368,6 @@ internal IEnumerator GetCmdletInfo(string cmdletName, bool searchAll // Check the current cmdlet cache then check the top level // if we aren't already at the top level. - SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(Context.EngineSessionState.CurrentScope); @@ -1555,9 +1381,9 @@ internal IEnumerator GetCmdletInfo(string cmdletName, bool searchAll foreach (var cmdletInfo in cmdlets) { - if (!String.IsNullOrEmpty(commandName.PSSnapInName)) + if (!string.IsNullOrEmpty(commandName.PSSnapInName)) { - if (String.Equals(cmdletInfo.ModuleName, commandName.PSSnapInName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(cmdletInfo.ModuleName, commandName.PSSnapInName, StringComparison.OrdinalIgnoreCase)) { yield return cmdletInfo; if (!searchAllScopes) @@ -1567,11 +1393,11 @@ 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)) { - if (String.Equals( + if (string.Equals( cmdletInfo.ModuleName, InitialSessionState.GetNestedModuleDllName(commandName.PSSnapInName), StringComparison.OrdinalIgnoreCase)) @@ -1594,51 +1420,6 @@ internal IEnumerator GetCmdletInfo(string cmdletName, bool searchAll } } } - } // GetCmdletInfo - - /// - /// Removes a cmdlet from the cmdlet cache. - /// - /// - /// - /// The configuration entry for the cmdlet which is being removed. - /// - /// - private void RemoveCmdletFromCache(CmdletConfigurationEntry entry) - { - IDictionary> cmdletTable = Context.EngineSessionState.GetCmdletTable(); - List cacheEntry; - if (cmdletTable.TryGetValue(entry.Name, out cacheEntry)) - { - int removalIndex = GetCmdletRemovalIndex(cacheEntry, entry.PSSnapIn == null ? String.Empty : entry.PSSnapIn.Name); - - if (removalIndex >= 0) - { - string name = cacheEntry[removalIndex].Name; - cacheEntry.RemoveAt(removalIndex); - Context.EngineSessionState.RemoveCmdlet(name, removalIndex, true); - } - - // Remove the entry from the cache if there are no more cmdlets - if (cacheEntry.Count == 0) - { - Context.EngineSessionState.RemoveCmdletEntry(entry.Name, true); - } - } - } - - private int GetCmdletRemovalIndex(List cacheEntry, string PSSnapin) - { - int removalIndex = -1; - for (int index = 0; index < cacheEntry.Count; ++index) - { - if (String.Equals(cacheEntry[index].ModuleName, PSSnapin, StringComparison.OrdinalIgnoreCase)) - { - removalIndex = index; - break; - } - } - return removalIndex; } internal ExecutionContext Context { get; } @@ -1651,7 +1432,7 @@ internal static PSModuleAutoLoadingPreference GetCommandDiscoveryPreference(Exec if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } // check the PSVariable @@ -1665,8 +1446,8 @@ internal static PSModuleAutoLoadingPreference GetCommandDiscoveryPreference(Exec } // check the environment variable - String psEnvironmentVariable = Environment.GetEnvironmentVariable(environmentVariable); - if (!String.IsNullOrEmpty(psEnvironmentVariable)) + string psEnvironmentVariable = Environment.GetEnvironmentVariable(environmentVariable); + if (!string.IsNullOrEmpty(psEnvironmentVariable)) { return LanguagePrimitives.ConvertTo(psEnvironmentVariable); } @@ -1689,7 +1470,7 @@ internal static PSModuleAutoLoadingPreference GetCommandDiscoveryPreference(Exec internal class LookupPathCollection : Collection { /// - /// Default constructor + /// Default constructor. /// internal LookupPathCollection() : base() { } @@ -1697,11 +1478,9 @@ internal LookupPathCollection() : base() { } /// Constructs a LookupPathCollection object and adds all the items /// in the supplied collection to it. /// - /// /// /// A set of items to be added to the collection. /// - /// internal LookupPathCollection(IEnumerable collection) : base() { foreach (string item in collection) @@ -1714,15 +1493,12 @@ internal LookupPathCollection(IEnumerable collection) : base() /// Adds the specified string to the collection if its not already /// a member of the collection. /// - /// /// /// The string to add to the collection. /// - /// /// /// The index at which the string was added or -1 if it was not added. /// - /// public new int Add(string item) { int result = -1; @@ -1731,21 +1507,19 @@ internal LookupPathCollection(IEnumerable collection) : base() base.Add(item); result = base.IndexOf(item); } + return result; } /// - /// Adds all the strings in the specified collection to this collection + /// Adds all the strings in the specified collection to this collection. /// - /// /// /// The collection of strings to add. /// - /// /// /// Only the strings that are not already in the collection will be added. /// - /// internal void AddRange(ICollection collection) { foreach (string name in collection) @@ -1758,38 +1532,34 @@ internal void AddRange(ICollection collection) /// Determines if the string already exists in the collection /// using a invariant culture case insensitive comparison. /// - /// /// /// The string to check for existence. /// - /// /// /// True if the string already exists in the collection. /// - /// public new bool Contains(string item) { bool result = false; foreach (string name in this) { - if (String.Equals(item, name, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(item, name, StringComparison.OrdinalIgnoreCase)) { result = true; break; } } + return result; } /// /// Returns a collection of all the indexes that are relative paths. /// - /// /// /// A collection of all the indexes that are relative paths. /// - /// internal Collection IndexOfRelativePath() { Collection result = new Collection(); @@ -1797,72 +1567,80 @@ internal Collection IndexOfRelativePath() for (int index = 0; index < this.Count; ++index) { string path = this[index]; - if (!String.IsNullOrEmpty(path) && - path.StartsWith(".", StringComparison.CurrentCulture)) + if (!string.IsNullOrEmpty(path) && + path.StartsWith('.')) { result.Add(index); } } + return result; - } // IndexOfRelativePath + } /// /// Finds the first index of the specified string. The string /// is compared in the invariant culture using a case-insensitive comparison. /// - /// /// /// The string to look for. /// - /// /// /// The index of the string in the collection or -1 if it was not found. /// - /// /// /// If is null or empty. /// - /// public new int IndexOf(string item) { - if (String.IsNullOrEmpty(item)) + if (string.IsNullOrEmpty(item)) { - throw PSTraceSource.NewArgumentException("item"); + throw PSTraceSource.NewArgumentException(nameof(item)); } int result = -1; for (int index = 0; index < this.Count; ++index) { - if (String.Equals(this[index], item, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(this[index], item, StringComparison.OrdinalIgnoreCase)) { result = index; break; } } + return result; } - } // LookupPathCollection + } // Guid is {ea9e8155-5042-5537-0b73-8c0e6b53f398} + [EventSource(Name = "Microsoft-PowerShell-CommandDiscovery")] internal class CommandDiscoveryEventSource : EventSource { - internal static CommandDiscoveryEventSource Log = new CommandDiscoveryEventSource(); + internal static readonly CommandDiscoveryEventSource Log = new CommandDiscoveryEventSource(); public void CommandLookupStart(string CommandName) { WriteEvent(1, CommandName); } + public void CommandLookupStop(string CommandName) { WriteEvent(2, CommandName); } + public void ModuleAutoLoadingStart(string CommandName) { WriteEvent(3, CommandName); } + public void ModuleAutoLoadingStop(string CommandName) { WriteEvent(4, CommandName); } + public void ModuleAutoDiscoveryStart(string CommandName) { WriteEvent(5, CommandName); } + public void ModuleAutoDiscoveryStop(string CommandName) { WriteEvent(6, CommandName); } + public void SearchingForModuleFilesStart() { WriteEvent(7); } + public void SearchingForModuleFilesStop() { WriteEvent(8); } + public void GetModuleExportedCommandsStart(string ModulePath) { WriteEvent(9, ModulePath); } + public void GetModuleExportedCommandsStop(string ModulePath) { WriteEvent(10, ModulePath); } + public void ModuleManifestAnalysisResult(string ModulePath, bool Success) { WriteEvent(11, ModulePath, Success); } + 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 5f3f60db556..eb5fbf70f3b 100644 --- a/src/System.Management.Automation/engine/CommandInfo.cs +++ b/src/System.Management.Automation/engine/CommandInfo.cs @@ -1,50 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Reflection; using System.Runtime.ExceptionServices; -using Microsoft.PowerShell.Commands; +using System.Text; +using Microsoft.PowerShell.Commands; 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 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 - /// - /// - /// + /// 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, /// @@ -53,45 +46,33 @@ 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, /// - /// A script that is built into the runspace configuration + /// A script that is built into the runspace configuration. /// Script = 0x0040, /// - /// A workflow - /// - Workflow = 0x0080, - - - /// - /// A Configuration + /// A Configuration. /// Configuration = 0x0100, /// /// 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 | Workflow | Configuration, + All = Alias | Function | Filter | Cmdlet | Script | ExternalScript | Application | Configuration, } /// @@ -103,60 +84,48 @@ public abstract class CommandInfo : IHasSessionStateEntryVisibility #region ctor /// - /// Creates an instance of the CommandInfo class with the specified name and type + /// Creates an instance of the CommandInfo class with the specified name and type. /// - /// /// /// The name of the command. /// - /// /// /// The type of the command. /// - /// /// /// If is null. /// - /// 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("name"); - } + ArgumentNullException.ThrowIfNull(name); Name = name; CommandType = type; - } // CommandInfo ctor + } /// - /// Creates an instance of the CommandInfo class with the specified name and type + /// Creates an instance of the CommandInfo class with the specified name and type. /// - /// /// /// The name of the command. /// - /// /// /// The type of the command. /// - /// /// /// The execution context for the command. /// - /// /// /// If is null. /// - /// internal CommandInfo(string name, CommandTypes type, ExecutionContext context) : this(name, type) { this.Context = context; - } // CommandInfo ctor + } /// /// This is a copy constructor, used primarily for get-command. @@ -164,9 +133,9 @@ internal CommandInfo(string name, CommandTypes type, ExecutionContext context) internal CommandInfo(CommandInfo other) { // Computed fields not copied: - //this._externalCommandMetadata = other._externalCommandMetadata; - //this._moduleName = other._moduleName; - //this.parameterSets = other.parameterSets; + // this._externalCommandMetadata = other._externalCommandMetadata; + // this._moduleName = other._moduleName; + // this.parameterSets = other.parameterSets; this.Module = other.Module; _visibility = other._visibility; Arguments = other.Arguments; @@ -191,16 +160,16 @@ internal CommandInfo(string name, CommandInfo other) /// /// Gets the name of the command. /// - public string Name { get; private set; } = String.Empty; + public string Name { get; private set; } = string.Empty; -// Name + // Name /// - /// Gets the type of the command + /// Gets the type of the command. /// public CommandTypes CommandType { get; private set; } = CommandTypes.Application; -// CommandType + // CommandType /// /// Gets the source of the command (shown by default in Get-Command) @@ -225,9 +194,10 @@ public virtual Version Version // Manifest module (.psd1) Module.SetVersion(ModuleIntrinsics.GetManifestModuleVersion(Module.Path)); } - else if (Module.Path.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + else if (Module.Path.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + Module.Path.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { - // Binary module (.dll) + // Binary module (.dll or .exe) Module.SetVersion(AssemblyName.GetAssemblyName(Module.Path).Version); } } @@ -247,7 +217,11 @@ public virtual Version Version /// internal ExecutionContext Context { - get { return _context; } + get + { + return _context; + } + set { _context = value; @@ -257,6 +231,7 @@ internal ExecutionContext Context } } } + private ExecutionContext _context; /// @@ -280,45 +255,33 @@ internal void SetCommandType(CommandTypes newType) CommandType = newType; } - internal const int HasWorkflowKeyWord = 0x0008; - internal const int IsCimCommand = 0x0010; - internal const int IsFile = 0x0020; - /// /// A string representing the definition of the command. /// - /// /// /// This is overridden by derived classes to return specific /// information for the command type. /// public abstract string Definition { get; } - /// - /// This is required for renaming aliases, functions, and filters + /// This is required for renaming aliases, functions, and filters. /// - /// /// /// The new name for the command. /// - /// /// /// If is null or empty. /// - /// internal void Rename(string newName) { - if (String.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException("newName"); - } + ArgumentException.ThrowIfNullOrEmpty(newName); Name = newName; } /// - /// for diagnostic purposes + /// For diagnostic purposes. /// /// public override string ToString() @@ -336,6 +299,7 @@ public virtual SessionStateEntryVisibility Visibility { return CopiedCommand == null ? _visibility : CopiedCommand.Visibility; } + set { if (CopiedCommand == null) @@ -353,6 +317,7 @@ public virtual SessionStateEntryVisibility Visibility } } } + private SessionStateEntryVisibility _visibility = SessionStateEntryVisibility.Public; /// @@ -367,7 +332,7 @@ internal virtual CommandMetadata CommandMetadata } /// - /// Returns the syntax of a command + /// Returns the syntax of a command. /// internal virtual string Syntax { @@ -459,8 +424,11 @@ private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely() // that can mess up the runspace our CommandInfo object came from. var runspace = (RunspaceBase)_context.CurrentRunspace; - if (!runspace.RunActionIfNoRunningPipelinesWithThreadCheck( - () => GetMergedCommandParameterMetadata(out result))) + if (runspace.CanRunActionInCurrentPipeline()) + { + GetMergedCommandParameterMetadata(out result); + } + else { _context.Events.SubscribeEvent( source: null, @@ -483,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; } @@ -497,7 +462,7 @@ private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely() return result; } - private class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs + private sealed class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs { public MergedCommandParameterMetadata Result; public ExceptionDispatchInfo Exception; @@ -545,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; @@ -602,9 +567,11 @@ 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; } } + private CommandMetadata _externalCommandMetadata; /// @@ -613,7 +580,7 @@ internal CommandMetadata ExternalCommandMetadata /// algorithm. /// /// The name of the parameter to resolve. - /// The parameter that matches this name + /// The parameter that matches this name. public ParameterMetadata ResolveParameter(string name) { MergedCommandParameterMetadata merged = GetMergedCommandParameterMetadataSafely(); @@ -636,9 +603,11 @@ public ReadOnlyCollection ParameterSets _parameterSets = new ReadOnlyCollection(parameterSetInfo); } + return _parameterSets; } - } // ParameterSets + } + internal ReadOnlyCollection _parameterSets; /// @@ -654,10 +623,9 @@ public ReadOnlyCollection ParameterSets internal bool IsImported { get; set; } = false; /// - /// The prefix that was used when importing this command + /// The prefix that was used when importing this command. /// - internal string Prefix { get; set; } = ""; - + internal string Prefix { get; set; } = string.Empty; /// /// Create a copy of commandInfo for GetCommandCommand so that we can generate parameter @@ -669,32 +637,26 @@ internal virtual CommandInfo CreateGetCommandCopy(object[] argumentList) } /// - /// Generates the parameter and parameter set info from the cmdlet metadata + /// Generates the parameter and parameter set info from the cmdlet metadata. /// - /// /// /// A collection of CommandParameterSetInfo representing the cmdlet metadata. /// - /// /// /// The type name is invalid or the length of the type name /// exceeds 1024 characters. /// - /// /// /// The caller does not have the required permission to load the assembly /// or create the type. /// - /// /// /// If more than int.MaxValue parameter-sets are defined for the command. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal Collection GenerateCommandParameterSetInfo() { Collection result; @@ -707,6 +669,7 @@ internal Collection GenerateCommandParameterSetInfo() { result = GetCacheableMetadata(CommandMetadata); } + return result; } @@ -770,8 +733,8 @@ internal static Collection GetParameterMetadata(Command } return result; - } // GetParameterMetadata - } // CommandInfo + } + } /// /// Represents , but can be used where a real type @@ -782,7 +745,7 @@ public class PSTypeName /// /// This constructor is used when the type exists and is currently loaded. /// - /// The type + /// The type. public PSTypeName(Type type) { _type = type; @@ -795,13 +758,24 @@ public PSTypeName(Type type) /// /// This constructor is used when the type may not exist, or is not loaded. /// - /// The name of the type + /// The name of the type. public PSTypeName(string name) { Name = name; _type = null; } + /// + /// This constructor is used when the creating a PSObject with a custom typename. + /// + /// The name of the type. + /// The real type. + public PSTypeName(string name, Type type) + { + Name = name; + _type = type; + } + /// /// This constructor is used when the type is defined in PowerShell. /// @@ -810,7 +784,7 @@ public PSTypeName(TypeDefinitionAst typeDefinitionAst) { if (typeDefinitionAst == null) { - throw PSTraceSource.NewArgumentNullException("typeDefinitionAst"); + throw PSTraceSource.NewArgumentNullException(nameof(typeDefinitionAst)); } TypeDefinitionAst = typeDefinitionAst; @@ -824,7 +798,7 @@ public PSTypeName(ITypeName typeName) { if (typeName == null) { - throw PSTraceSource.NewArgumentNullException("typeName"); + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); } _type = typeName.GetReflectionType(); @@ -849,7 +823,7 @@ public PSTypeName(ITypeName typeName) } /// - /// Return the name of the type + /// Return the name of the type. /// public string Name { get; } @@ -874,12 +848,13 @@ public Type Type TypeResolver.TryResolveType(Name, out _type); } } + if (_type == null) { // We ignore the exception. if (Name != null && - Name.StartsWith("[", StringComparison.OrdinalIgnoreCase) && - Name.EndsWith("]", StringComparison.OrdinalIgnoreCase)) + Name.StartsWith('[') && + Name.EndsWith(']')) { string tmp = Name.Substring(1, Name.Length - 2); TypeResolver.TryResolveType(tmp, out _type); @@ -892,26 +867,118 @@ public Type Type return _type; } } + private 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; /// /// Returns a String that represents the current PSTypeName. /// - /// String that represents the current PSTypeName. + /// String that represents the current PSTypeName. public override string ToString() { return Name ?? string.Empty; } } + [DebuggerDisplay("{PSTypeName} {Name}")] + internal readonly struct PSMemberNameAndType + { + public readonly string Name; + + public readonly PSTypeName PSTypeName; + + public readonly object Value; + + public PSMemberNameAndType(string name, PSTypeName typeName, object value = null) + { + Name = name; + PSTypeName = typeName; + Value = value; + } + } + + /// + /// Represents dynamic types such as , + /// 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 sealed class PSSyntheticTypeName : PSTypeName + { + internal static PSSyntheticTypeName Create(string typename, IList membersTypes) => Create(new PSTypeName(typename), membersTypes); + + internal static PSSyntheticTypeName Create(Type type, IList membersTypes) => Create(new PSTypeName(type), membersTypes); + + internal static PSSyntheticTypeName Create(PSTypeName typename, IList membersTypes) + { + var typeName = GetMemberTypeProjection(typename.Name, membersTypes); + var members = new List(); + members.AddRange(membersTypes); + members.Sort(static (c1, c2) => string.Compare(c1.Name, c2.Name, StringComparison.OrdinalIgnoreCase)); + return new PSSyntheticTypeName(typeName, typename.Type, members); + } + + private PSSyntheticTypeName(string typeName, Type type, IList membersTypes) + : base(typeName, type) + { + Members = membersTypes; + if (type != typeof(PSObject)) + { + return; + } + + for (int i = 0; i < Members.Count; i++) + { + var psMemberNameAndType = Members[i]; + if (IsPSTypeName(psMemberNameAndType)) + { + Members.RemoveAt(i); + break; + } + } + } + + private static bool IsPSTypeName(in PSMemberNameAndType member) => member.Name.Equals(nameof(PSTypeName), StringComparison.OrdinalIgnoreCase); + + private static string GetMemberTypeProjection(string typename, IList members) + { + if (typename == typeof(PSObject).FullName) + { + foreach (var mem in members) + { + if (IsPSTypeName(mem)) + { + typename = mem.Value.ToString(); + } + } + } + + var builder = new StringBuilder(typename, members.Count * 7); + builder.Append('#'); + foreach (var m in members.OrderBy(static m => m.Name)) + { + if (!IsPSTypeName(m)) + { + builder.Append(m.Name).Append(':'); + } + } + + builder.Length--; + return builder.ToString(); + } + + public IList Members { get; } + } + +#nullable enable internal interface IScriptCommandInfo { ScriptBlock ScriptBlock { get; } } -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/engine/CommandMetadata.cs b/src/System.Management.Automation/engine/CommandMetadata.cs index d9ddfb3cf2e..df767f714b0 100644 --- a/src/System.Management.Automation/engine/CommandMetadata.cs +++ b/src/System.Management.Automation/engine/CommandMetadata.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Globalization; using System.Management.Automation.Internal; using System.Management.Automation.Language; -using System.Globalization; -using System.Text; using System.Reflection; +using System.Text; + using Microsoft.PowerShell.Commands; using Dbg = System.Diagnostics.Debug; @@ -19,7 +19,7 @@ namespace System.Management.Automation { /// - /// Defines session capabilities provided by a PowerShell session + /// Defines session capabilities provided by a PowerShell session. /// /// /// @@ -35,7 +35,7 @@ public enum SessionCapabilities RemoteServer = 0x1, /// - /// Include language capabilities + /// Include language capabilities. /// Language = 0x4 } @@ -68,7 +68,7 @@ public CommandMetadata(Type commandType) } /// - /// Construct a CommandMetadata object for the given commandInfo + /// Construct a CommandMetadata object for the given commandInfo. /// /// /// The commandInfo object to construct CommandMetadata for @@ -86,7 +86,7 @@ public CommandMetadata(CommandInfo commandInfo) } /// - /// Construct a CommandMetadata object for the given commandInfo + /// Construct a CommandMetadata object for the given commandInfo. /// /// /// The commandInfo object to construct CommandMetadata for @@ -105,7 +105,7 @@ public CommandMetadata(CommandInfo commandInfo, bool shouldGenerateCommonParamet { if (commandInfo == null) { - throw PSTraceSource.NewArgumentNullException("commandInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(commandInfo)); } while (commandInfo is AliasInfo) { @@ -157,12 +157,12 @@ public CommandMetadata(string path) /// A copy constructor that creates a deep copy of the CommandMetadata object. /// Instances of Attribute and Type classes are copied by reference. /// - /// object to copy + /// Object to copy. public CommandMetadata(CommandMetadata other) { if (other == null) { - throw PSTraceSource.NewArgumentNullException("other"); + throw PSTraceSource.NewArgumentNullException(nameof(other)); } Name = other.Name; @@ -207,7 +207,7 @@ public CommandMetadata(CommandMetadata other) } /// - /// Constructor used by implicit remoting + /// Constructor used by implicit remoting. /// internal CommandMetadata( string name, @@ -286,45 +286,36 @@ private void Init(ScriptBlock scriptBlock, string name, bool shouldGenerateCommo /// Gets the metadata for the specified cmdlet from the cache or creates /// a new instance if its not in the cache. /// - /// /// /// The name of the command that this metadata represents. /// - /// /// /// The cmdlet to get the metadata for. /// - /// /// /// The current engine context. /// - /// /// /// The CommandMetadata for the specified cmdlet. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If more than int.MaxValue parameter-sets are defined for the command. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal static CommandMetadata Get(string commandName, Type cmdletType, ExecutionContext context) { - if (String.IsNullOrEmpty(commandName)) + if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentException("commandName"); + throw PSTraceSource.NewArgumentException(nameof(commandName)); } CommandMetadata result = null; @@ -347,45 +338,38 @@ internal static CommandMetadata Get(string commandName, Type cmdletType, Executi } return result; - } // Get + } /// - /// Constructs an instance of CommandMetadata using reflection against a bindable object + /// Constructs an instance of CommandMetadata using reflection against a bindable object. /// - /// /// /// The name of the command that this metadata represents. /// - /// /// /// An instance of an object type that can be used to bind MSH parameters. A type is /// considered bindable if it has at least one field and/or property that is decorated /// with the ParameterAttribute. /// - /// /// /// The current engine context. If null, the command and type metadata will be generated /// and will not be cached. /// - /// /// /// If is null. /// - /// /// /// If more than int.MaxValue parameter-sets are defined for the command. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal CommandMetadata(string commandName, Type cmdletType, ExecutionContext context) { - if (String.IsNullOrEmpty(commandName)) + if (string.IsNullOrEmpty(commandName)) { - throw PSTraceSource.NewArgumentException("commandName"); + throw PSTraceSource.NewArgumentException(nameof(commandName)); } Name = commandName; @@ -423,13 +407,12 @@ internal CommandMetadata(string commandName, Type cmdletType, ExecutionContext c /// By the time this constructor is called, information about CmdletAttribute /// and RuntimeDefinedParameters for the script block has been setup with /// the scriptblock object. - /// /// internal CommandMetadata(ScriptBlock scriptblock, string commandName, ExecutionContext context) { if (scriptblock == null) { - throw PSTraceSource.NewArgumentException("scriptblock"); + throw PSTraceSource.NewArgumentException(nameof(scriptblock)); } CmdletBindingAttribute cmdletBindingAttribute = scriptblock.CmdletBindingAttribute; @@ -464,9 +447,9 @@ internal CommandMetadata(ScriptBlock scriptblock, string commandName, ExecutionC #region Public Properties /// - /// Gets the name of the command this metadata represents + /// Gets the name of the command this metadata represents. /// - public string Name { get; set; } = String.Empty; + public string Name { get; set; } = string.Empty; /// /// The Type which this CommandMetadata represents. @@ -477,11 +460,15 @@ internal CommandMetadata(ScriptBlock scriptblock, string commandName, ExecutionC private ScriptBlock _scriptBlock; /// - /// Gets/Sets the default parameter set name + /// Gets/Sets the default parameter set name. /// public string DefaultParameterSetName { - get { return _defaultParameterSetName; } + get + { + return _defaultParameterSetName; + } + set { if (string.IsNullOrEmpty(value)) @@ -492,6 +479,7 @@ public string DefaultParameterSetName _defaultParameterSetName = value; } } + private string _defaultParameterSetName = ParameterAttribute.AllParameterSets; /// @@ -520,10 +508,10 @@ public string DefaultParameterSetName public bool SupportsTransactions { get; set; } /// - /// Related link URI for Get-Help -Online + /// Related link URI for Get-Help -Online. /// [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] - public string HelpUri { get; set; } = String.Empty; + public string HelpUri { get; set; } = string.Empty; /// /// The remoting capabilities of this cmdlet, when exposed in a context @@ -542,8 +530,13 @@ public RemotingCapability RemotingCapability return _remotingCapability; } - set { _remotingCapability = value; } + + set + { + _remotingCapability = value; + } } + private RemotingCapability _remotingCapability = RemotingCapability.PowerShell; /// @@ -556,7 +549,7 @@ public RemotingCapability RemotingCapability public ConfirmImpact ConfirmImpact { get; set; } = ConfirmImpact.Medium; /// - /// Gets the parameter data for this command + /// Gets the parameter data for this command. /// public Dictionary Parameters { @@ -589,16 +582,18 @@ public Dictionary Parameters return _parameters; } + private set { _parameters = value; } } + private Dictionary _parameters; private bool _shouldGenerateCommonParameters; /// - /// Gets or sets the obsolete attribute on the command + /// Gets or sets the obsolete attribute on the command. /// /// internal ObsoleteAttribute Obsolete { get; set; } @@ -609,7 +604,7 @@ private set /// /// Gets the merged metadata for the command including cmdlet declared parameters, - /// common parameters, and (optionally) ShouldProcess and Transactions parameters + /// common parameters, and (optionally) ShouldProcess and Transactions parameters. /// /// internal MergedCommandParameterMetadata StaticCommandParameterMetadata @@ -619,27 +614,30 @@ internal MergedCommandParameterMetadata StaticCommandParameterMetadata return _staticCommandParameterMetadata; } } + private readonly MergedCommandParameterMetadata _staticCommandParameterMetadata; /// - /// True if the cmdlet implements dynamic parameters, or false otherwise + /// True if the cmdlet implements dynamic parameters, or false otherwise. /// /// internal bool ImplementsDynamicParameters { get { return _implementsDynamicParameters; } } + private bool _implementsDynamicParameters; /// /// Gets the bit in the parameter set map for the default parameter set. /// - /// internal uint DefaultParameterSetFlag { get { return _defaultParameterSetFlag; } + set { _defaultParameterSetFlag = value; } } + private uint _defaultParameterSetFlag; /// @@ -655,6 +653,7 @@ internal uint DefaultParameterSetFlag // The CommandType for a script cmdlet is not CommandTypes.Cmdlet, yet // proxy generation needs to know the difference between script and script cmdlet. private bool _wrappedAnyCmdlet; + internal bool WrappedAnyCmdlet { get { return _wrappedAnyCmdlet; } @@ -676,11 +675,9 @@ internal CommandTypes WrappedCommandType /// Constructs the command metadata by using reflection against the /// CLR type. /// - /// /// /// If more than int.MaxValue parameter-sets are defined for the command. /// - /// private void ConstructCmdletMetadataUsingReflection() { Diagnostics.Assert( @@ -689,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) { @@ -698,7 +695,7 @@ private void ConstructCmdletMetadataUsingReflection() // Process the attributes on the cmdlet - var customAttributes = CommandType.GetTypeInfo().GetCustomAttributes(false); + var customAttributes = CommandType.GetCustomAttributes(false); foreach (Attribute attribute in customAttributes) { @@ -717,29 +714,25 @@ private void ConstructCmdletMetadataUsingReflection() _otherAttributes.Add(attribute); } } - } // ConstructCmdletMetadataUsingReflection + } /// - /// Extracts the cmdlet data from the CmdletAttribute + /// Extracts the cmdlet data from the CmdletAttribute. /// - /// /// /// The CmdletAttribute to process /// - /// /// /// If is null. /// - /// /// /// If more than int.MaxValue parameter-sets are defined for the command. /// - /// private void ProcessCmdletAttribute(CmdletCommonMetadataAttribute attribute) { if (attribute == null) { - throw PSTraceSource.NewArgumentNullException("attribute"); + throw PSTraceSource.NewArgumentNullException(nameof(attribute)); } // Process the default parameter set name @@ -769,7 +762,7 @@ private void ProcessCmdletAttribute(CmdletCommonMetadataAttribute attribute) { PositionalBinding = cmdletBindingAttribute.PositionalBinding; } - } // ProcessCmdletAttribute + } /// /// Merges parameter metadata from different sources: those that are coming from Type, @@ -840,67 +833,62 @@ private MergedCommandParameterMetadata MergeParameterMetadata(ExecutionContext c } return staticCommandParameterMetadata; - } // MergeParameterMetadata + } #endregion helper methods #region Proxy Command generation /// - /// Gets the ScriptCmdlet in string format + /// Gets the ScriptCmdlet in string format. /// /// internal string GetProxyCommand(string helpComment, bool generateDynamicParameters) { 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; + 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; } internal string GetDecl() { - string result = ""; - string separator = ""; + string result = string.Empty; + string separator = string.Empty; if (_wrappedAnyCmdlet) { StringBuilder decl = new StringBuilder("[CmdletBinding("); @@ -910,7 +898,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("DefaultParameterSetName='"); decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(_defaultParameterSetName)); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -922,7 +910,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("ConfirmImpact='"); decl.Append(ConfirmImpact); - decl.Append("'"); + decl.Append('\''); } if (SupportsPaging) @@ -939,7 +927,7 @@ internal string GetDecl() separator = ", "; } - if (PositionalBinding == false) + if (!PositionalBinding) { decl.Append(separator); decl.Append("PositionalBinding=$false"); @@ -951,7 +939,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("HelpUri='"); decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(HelpUri)); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -960,7 +948,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("RemotingCapability='"); decl.Append(_remotingCapability); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -998,7 +986,8 @@ internal string GetParamBlock() return parameters.ToString(); } - return ""; + + return string.Empty; } internal string GetBeginBlock() @@ -1017,49 +1006,44 @@ internal string GetBeginBlock() // be subject to the runspace restrictions if (_wrappedCommandType == CommandTypes.Function) { - commandOrigin = ""; + 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)) {{ $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; @@ -1067,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($_) @@ -1078,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) {{ @@ -1095,14 +1085,13 @@ internal string GetDynamicParamBlock() $paramDictionary.Add($param.Name, $dynParam) }} }} + return $paramDictionary }} }} catch {{ throw }} -", - CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand), - _wrappedCommandType); +"); } internal string GetEndBlock() @@ -1116,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 @@ -1231,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); @@ -1260,7 +1261,7 @@ private static CommandMetadata GetRestrictedSelectObject() ParameterSetMetadata.ParameterFlags.ValueFromPipeline | ParameterSetMetadata.ParameterFlags.Mandatory, null)); // no help message - return GetRestrictedCmdlet("Select-Object", null, "https://go.microsoft.com/fwlink/?LinkID=113387", propertyParameter, inputParameter); + return GetRestrictedCmdlet("Select-Object", null, "https://go.microsoft.com/fwlink/?LinkID=2096716", propertyParameter, inputParameter); } private static CommandMetadata GetRestrictedMeasureObject() @@ -1301,7 +1302,7 @@ private static CommandMetadata GetRestrictedExitPSSession() // remote Exit-PSSession is not called by PowerShell, but is needed so that users // can exit an interactive remoting session - return GetRestrictedCmdlet("Exit-PSSession", null, "https://go.microsoft.com/fwlink/?LinkID=135210"); // no parameters are used + return GetRestrictedCmdlet("Exit-PSSession", null, "https://go.microsoft.com/fwlink/?LinkID=2096787"); // no parameters are used } /// @@ -1313,7 +1314,6 @@ private static CommandMetadata GetRestrictedExitPSSession() /// (included in ) /// doesn't use all parameters of Get-Help /// and uses only a limited set of argument values for the parameters it does use. - /// /// can be passed to method to generate /// a body of a proxy function that forwards calls to the actual cmdlet, while exposing only the parameters /// listed in . Exposing only the restricted proxy function while making @@ -1326,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()); } @@ -1336,6 +1336,7 @@ public static Dictionary GetRestrictedCommands(SessionC { result.Add(restrictedCommand.Name, restrictedCommand); } + return result; } @@ -1454,7 +1455,7 @@ private static Collection GetRestrictedJobCommands() ParameterMetadata passThruParameter = new ParameterMetadata("PassThru", typeof(SwitchParameter)); ParameterMetadata anyParameter = new ParameterMetadata("Any", typeof(SwitchParameter)); - CommandMetadata stopJob = GetRestrictedCmdlet("Stop-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=113413", nameParameter, + CommandMetadata stopJob = GetRestrictedCmdlet("Stop-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=2096795", nameParameter, instanceIdParameter, idParameter, stateParameter, filterParameter, jobParameter, passThruParameter); restrictedJobCommands.Add(stopJob); @@ -1463,7 +1464,7 @@ private static Collection GetRestrictedJobCommands() ParameterMetadata timeoutParameter = new ParameterMetadata("Timeout", typeof(int)); timeoutParameter.Attributes.Add(new ValidateRangeAttribute(-1, Int32.MaxValue)); - CommandMetadata waitJob = GetRestrictedCmdlet("Wait-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=113422", nameParameter, + CommandMetadata waitJob = GetRestrictedCmdlet("Wait-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=2096902", nameParameter, instanceIdParameter, idParameter, jobParameter, stateParameter, filterParameter, anyParameter, timeoutParameter); restrictedJobCommands.Add(waitJob); @@ -1502,7 +1503,7 @@ private static Collection GetRestrictedJobCommands() ParameterMetadata writeJobParameter = new ParameterMetadata("WriteJobInResults", typeof(SwitchParameter)); ParameterMetadata autoRemoveParameter = new ParameterMetadata("AutoRemoveJob", typeof(SwitchParameter)); - CommandMetadata receiveJob = GetRestrictedCmdlet("Receive-Job", "Location", "https://go.microsoft.com/fwlink/?LinkID=113372", nameParameter, + CommandMetadata receiveJob = GetRestrictedCmdlet("Receive-Job", "Location", "https://go.microsoft.com/fwlink/?LinkID=2096965", nameParameter, instanceIdParameter, idParameter, stateParameter, jobParameter2, computerNameParameter, locationParameter, @@ -1513,7 +1514,7 @@ private static Collection GetRestrictedJobCommands() // Remove-Job cmdlet ParameterMetadata forceParameter = new ParameterMetadata("Force", typeof(SwitchParameter)); - CommandMetadata removeJob = GetRestrictedCmdlet("Remove-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=113377", + CommandMetadata removeJob = GetRestrictedCmdlet("Remove-Job", JobCmdletBase.SessionIdParameterSet, "https://go.microsoft.com/fwlink/?LinkID=2096868", nameParameter, instanceIdParameter, idParameter, stateParameter, filterParameter, jobParameter, forceParameter); @@ -1542,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 ecd23002eda..1c58bb87e29 100644 --- a/src/System.Management.Automation/engine/CommandParameter.cs +++ b/src/System.Management.Automation/engine/CommandParameter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using System.Management.Automation.Language; @@ -13,13 +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,11 +29,17 @@ private class Argument private Parameter _parameter; private Argument _argument; private bool _spaceAfterParameter; + private bool _fromHashtableSplatting; + + internal bool SpaceAfterParameter => _spaceAfterParameter; + + internal bool ParameterNameSpecified => _parameter != null; + + internal bool ArgumentSpecified => _argument != null; + + internal bool ParameterAndArgumentSpecified => ParameterNameSpecified && ArgumentSpecified; - internal bool SpaceAfterParameter { get { return _spaceAfterParameter; } } - internal bool ParameterNameSpecified { get { return _parameter != null; } } - internal bool ArgumentSpecified { get { return _argument != null; } } - internal bool ParameterAndArgumentSpecified { get { return ParameterNameSpecified && ArgumentSpecified; } } + internal bool FromHashtableSplatting => _fromHashtableSplatting; /// /// Gets and sets the string that represents parameter name, which does not include the '-' (dash). @@ -45,6 +51,7 @@ internal string ParameterName Diagnostics.Assert(ParameterNameSpecified, "Caller must verify parameter name was specified"); return _parameter.parameterName; } + set { Diagnostics.Assert(ParameterNameSpecified, "Caller must verify parameter name was specified"); @@ -107,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; } } /// @@ -117,10 +124,8 @@ 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; } @@ -195,20 +200,23 @@ internal static CommandParameterInternal CreateArgument( /// The text of the parameter, as it did, or would, appear in script. /// The ast of the argument value in the script. /// The argument value. - /// Used in native commands to correctly handle -foo:bar vs. -foo: bar + /// 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 b53a665fdbb..12c494ad8ea 100644 --- a/src/System.Management.Automation/engine/CommandPathSearch.cs +++ b/src/System.Management.Automation/engine/CommandPathSearch.cs @@ -1,55 +1,53 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// Used to enumerate the commands on the system that match the specified - /// command name + /// command name. /// - internal class CommandPathSearch : IEnumerable, IEnumerator + internal class CommandPathSearch : IEnumerable, IEnumerator { [TraceSource("CommandSearch", "CommandSearch")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("CommandSearch", "CommandSearch"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("CommandSearch", "CommandSearch"); /// /// Constructs a command searching enumerator that resolves the location /// of a command using the PATH environment variable. /// - /// - /// - /// The patterns to search for in the path. + /// + /// The command name to search for in the path. /// - /// /// /// The paths to directories in which to lookup the command. + /// Ex.null: paths from PATH environment variable. /// - /// /// /// The execution context for the current engine instance. /// - internal CommandPathSearch( - IEnumerable patterns, - IEnumerable lookupPaths, - ExecutionContext context) - { - Init(patterns, lookupPaths, context); - } - + /// + /// The patterns to search for in the paths. + /// + /// + /// The fuzzy matcher to use for fuzzy searching. + /// internal CommandPathSearch( string commandName, - IEnumerable lookupPaths, + LookupPathCollection lookupPaths, ExecutionContext context, - Collection acceptableCommandNames) + Collection? acceptableCommandNames, + FuzzyMatcher? fuzzyMatcher) { + _fuzzyMatcher = fuzzyMatcher; string[] commandPatterns; if (acceptableCommandNames != null) { @@ -67,6 +65,7 @@ internal CommandPathSearch( // called with the .ps1 extension, so that 'script.ps1' can be called by 'script'. commandPatterns = new[] { commandName, commandName + ".ps1" }; } + _postProcessEnumeratedFiles = CheckAgainstAcceptableCommandNames; _acceptableCommandNames = acceptableCommandNames; } @@ -76,28 +75,26 @@ internal CommandPathSearch( _postProcessEnumeratedFiles = JustCheckExtensions; } - Init(commandPatterns, lookupPaths, context); - _orderedPathExt = CommandDiscovery.PathExtensionsWithPs1Prepended; - } - - private void Init(IEnumerable commandPatterns, IEnumerable searchPath, ExecutionContext context) - { // Note, discovery must be set before resolving the current directory - _context = context; _patterns = commandPatterns; - - _lookupPaths = new LookupPathCollection(searchPath); + _lookupPaths = lookupPaths; ResolveCurrentDirectoryInLookupPaths(); - this.Reset(); + _orderedPathExt = CommandDiscovery.PathExtensionsWithPs1Prepended; + + // The same as in this.Reset() + _lookupPathsEnumerator = _lookupPaths.GetEnumerator(); + _patternEnumerator = _patterns.GetEnumerator(); + _currentDirectoryResults = Array.Empty(); + _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator(); + _justReset = true; } /// /// Ensures that all the paths in the lookupPaths member are absolute /// file system paths. /// - /// private void ResolveCurrentDirectoryInLookupPaths() { var indexesToRemove = new SortedDictionary(); @@ -113,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; @@ -121,8 +128,8 @@ private void ResolveCurrentDirectoryInLookupPaths() foreach (int index in _lookupPaths.IndexOfRelativePath()) { - string resolvedDirectory = null; - string resolvedPath = null; + string? resolvedDirectory = null; + string? resolvedPath = null; CommandDiscovery.discoveryTracer.WriteLine( "Lookup directory \"{0}\" appears to be a relative path. Attempting resolution...", @@ -152,10 +159,9 @@ private void ResolveCurrentDirectoryInLookupPaths() _lookupPaths[index]); } - // Note, if the directory resolves to multiple paths, only the first is used. - if (!String.IsNullOrEmpty(resolvedPath)) + if (!string.IsNullOrEmpty(resolvedPath)) { CommandDiscovery.discoveryTracer.TraceError( "The relative path resolved to: {0}", @@ -229,42 +235,36 @@ private void ResolveCurrentDirectoryInLookupPaths() int indexToRemove = indexesToRemove[removeIndex - 1]; _lookupPaths.RemoveAt(indexToRemove); } - } // ResolveCurrentDirectoryInLookupPaths + } /// - /// Gets an instance of a command enumerator + /// Gets an instance of a command enumerator. /// - /// /// /// An instance of this class as IEnumerator. /// - /// IEnumerator IEnumerable.GetEnumerator() { return this; - } // GetEnumerator + } /// - /// Gets an instance of a command enumerator + /// Gets an instance of a command enumerator. /// - /// /// /// An instance of this class as IEnumerator. /// - /// IEnumerator IEnumerable.GetEnumerator() { return this; - } // GetEnumerator + } /// - /// Moves the enumerator to the next command match + /// Moves the enumerator to the next command match. /// - /// /// /// true if there was another command that matches, false otherwise. /// - /// public bool MoveNext() { bool result = false; @@ -288,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 @@ -319,7 +319,7 @@ public bool MoveNext() } // Since we have reset the results, loop again to find the next result. - } while (true); + } if (result) { @@ -346,33 +346,34 @@ public bool MoveNext() } GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current); - } while (true); + } return result; - } // MoveNext + } /// - /// Resets the enumerator to before the first command match + /// Resets the enumerator to before the first command match. /// public void Reset() { + _lookupPathsEnumerator.Dispose(); _lookupPathsEnumerator = _lookupPaths.GetEnumerator(); + _patternEnumerator.Dispose(); _patternEnumerator = _patterns.GetEnumerator(); - _currentDirectoryResults = Utils.EmptyArray(); + _currentDirectoryResults = Array.Empty(); + _currentDirectoryResultsEnumerator.Dispose(); _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator(); _justReset = true; - } // Reset + } /// /// Gets the path to the current command match. /// /// - /// /// /// The enumerator is positioned before the first element of /// the collection or after the last element. /// - /// string IEnumerator.Current { get @@ -384,7 +385,7 @@ string IEnumerator.Current return _currentDirectoryResultsEnumerator.Current; } - } // Current + } object IEnumerator.Current { @@ -409,18 +410,15 @@ public void Dispose() /// Gets the matching files in the specified directories and resets /// the currentDirectoryResultsEnumerator to this new set of results. /// - /// /// /// The pattern used to find the matching files in the specified directory. /// - /// /// /// The path to the directory to find the files in. /// - /// private void GetNewDirectoryResults(string pattern, string directory) { - IEnumerable result = null; + IEnumerable? result = null; try { CommandDiscovery.discoveryTracer.WriteLine("Looking for {0} in {1}", pattern, directory); @@ -436,11 +434,29 @@ private void GetNewDirectoryResults(string pattern, string directory) // to forcefully use null if pattern is "." if (pattern.Length != 1 || pattern[0] != '.') { - var matchingFiles = Directory.EnumerateFiles(directory, pattern); - - result = _postProcessEnumeratedFiles != null - ? _postProcessEnumeratedFiles(matchingFiles.ToArray()) - : matchingFiles; + 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)) + { + files.Add(file); + } + } + + result = _postProcessEnumeratedFiles != null + ? _postProcessEnumeratedFiles(files.ToArray()) + : files; + } + else + { + var matchingFiles = Directory.EnumerateFiles(directory, pattern); + result = _postProcessEnumeratedFiles != null + ? _postProcessEnumeratedFiles(matchingFiles.ToArray()) + : matchingFiles; + } } } } @@ -464,11 +480,11 @@ private void GetNewDirectoryResults(string pattern, string directory) // accessible } - _currentDirectoryResults = result ?? Utils.EmptyArray(); + _currentDirectoryResults = result ?? Array.Empty(); _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator(); - } // GetMatchingPathsInDirectory + } - private IEnumerable CheckAgainstAcceptableCommandNames(string[] fileNames) + private IEnumerable? CheckAgainstAcceptableCommandNames(string[] fileNames) { var baseNames = fileNames.Select(Path.GetFileName).ToArray(); @@ -477,8 +493,8 @@ private IEnumerable CheckAgainstAcceptableCommandNames(string[] fileName // Porting note: allow files with executable bit on non-Windows platforms - Collection result = null; - if (baseNames.Length > 0) + Collection? result = null; + if (baseNames.Length > 0 && _acceptableCommandNames != null) { foreach (var name in _acceptableCommandNames) { @@ -487,8 +503,7 @@ private IEnumerable CheckAgainstAcceptableCommandNames(string[] fileName 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; } @@ -499,14 +514,14 @@ private IEnumerable CheckAgainstAcceptableCommandNames(string[] fileName return result; } - private IEnumerable JustCheckExtensions(string[] fileNames) + private IEnumerable? JustCheckExtensions(string[] fileNames) { // Warning: pretty duplicated code // Result must be ordered by PATHEXT order of precedence. // Porting note: allow files with executable bit on non-Windows platforms - Collection result = null; + Collection? result = null; foreach (var allowedExt in _orderedPathExt) { foreach (var fileName in fileNames) @@ -514,8 +529,7 @@ private IEnumerable JustCheckExtensions(string[] fileNames) if (fileName.EndsWith(allowedExt, StringComparison.OrdinalIgnoreCase) || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(fileName))) { - if (result == null) - result = new Collection(); + result ??= new Collection(); result.Add(fileName); } } @@ -526,12 +540,12 @@ private IEnumerable JustCheckExtensions(string[] fileNames) /// /// The directory paths in which to look for commands. - /// This is derived from the PATH environment variable + /// This is derived from the PATH environment variable. /// - private LookupPathCollection _lookupPaths; + private readonly LookupPathCollection _lookupPaths; /// - /// The enumerator for the lookup paths + /// The enumerator for the lookup paths. /// private IEnumerator _lookupPathsEnumerator; @@ -542,24 +556,24 @@ private IEnumerable JustCheckExtensions(string[] fileNames) private IEnumerable _currentDirectoryResults; /// - /// The enumerator for the list of results + /// The enumerator for the list of results. /// private IEnumerator _currentDirectoryResultsEnumerator; /// - /// The command name to search for + /// The command name to search for. /// - private IEnumerable _patterns; + private readonly IEnumerable _patterns; /// - /// The enumerator for the patterns + /// The enumerator for the patterns. /// private IEnumerator _patternEnumerator; /// - /// A reference to the execution context for this runspace + /// 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 @@ -568,14 +582,15 @@ private IEnumerable JustCheckExtensions(string[] fileNames) private bool _justReset; /// - /// If not null, called with the enumerated files for further processing + /// 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 readonly FuzzyMatcher? _fuzzyMatcher; #endregion private members - } // CommandSearch + } } - diff --git a/src/System.Management.Automation/engine/CommandProcessor.cs b/src/System.Management.Automation/engine/CommandProcessor.cs index 9fec56133d5..d1ef250d717 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; -using System.Linq.Expressions; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Linq.Expressions; using System.Management.Automation.Internal; + using Microsoft.PowerShell.Commands; -using Dbg = System.Management.Automation.Diagnostics; +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { @@ -20,7 +20,7 @@ namespace System.Management.Automation /// internal class CommandProcessor : CommandProcessorBase { -#region ctor + #region ctor static CommandProcessor() { @@ -46,19 +46,15 @@ static CommandProcessor() /// /// Initializes the new instance of CommandProcessor class. /// - /// /// /// The information about the cmdlet. /// - /// /// /// PowerShell engine execution context for this command. /// - /// /// /// If there was a failure creating an instance of the cmdlet type. /// - /// internal CommandProcessor(CmdletInfo cmdletInfo, ExecutionContext context) : base(cmdletInfo) { this._context = context; @@ -76,7 +72,7 @@ internal CommandProcessor(CmdletInfo cmdletInfo, ExecutionContext context) : bas /// /// /// - /// True when the script to be executed came from a file (as opposed to a function, or interactive input) + /// True when the script to be executed came from a file (as opposed to a function, or interactive input). internal CommandProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext context, bool useLocalScope, bool fromScriptFile, SessionStateInternal sessionState) : base(scriptCommandInfo as CommandInfo) { @@ -87,33 +83,27 @@ internal CommandProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext Init(scriptCommandInfo); } - // CommandProcessor + #endregion ctor -#endregion ctor + #region internal members -#region internal members /// - /// Returns a CmdletParameterBinderController for the specified command + /// Returns a CmdletParameterBinderController for the specified command. /// - /// /// /// The cmdlet to bind parameters to. /// - /// /// /// A new instance of a CmdletParameterBinderController. /// - /// /// /// if is not a Cmdlet. /// - /// internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - Cmdlet cmdlet = command as Cmdlet; - if (cmdlet == null) + if (command is not Cmdlet cmdlet) { - throw PSTraceSource.NewArgumentException("command"); + throw PSTraceSource.NewArgumentException(nameof(command)); } ParameterBinderBase parameterBinder; @@ -140,34 +130,34 @@ internal CmdletParameterBinderController CmdletParameterBinderController { NewParameterBinderController(this.Command); } + return _cmdletParameterBinderController; } } + private CmdletParameterBinderController _cmdletParameterBinderController; /// - /// Get the ObsoleteAttribute of the current command + /// Get the ObsoleteAttribute of the current command. /// internal override ObsoleteAttribute ObsoleteAttribute { get { return _obsoleteAttribute; } } + private ObsoleteAttribute _obsoleteAttribute; /// - /// Binds the specified command-line parameters to the target + /// Binds the specified command-line parameters to the target. /// - /// /// /// true if encode succeeds otherwise false. /// - /// /// /// If any parameters fail to bind, /// or /// If any mandatory parameters are missing. /// - /// /// /// If there is an error generating the metadata for dynamic parameters. /// @@ -222,7 +212,45 @@ internal override void Prepare(IDictionary psDefaultParameterValues) this.Command != null, "CommandProcessor did not initialize Command\n" + this.CommandInfo.Name); - BindCommandLineParameters(); + PSLanguageMode? oldLanguageMode = null; + bool? oldLangModeTransitionStatus = null; + try + { + var scriptCmdletInfo = this.CommandInfo as IScriptCommandInfo; + if (scriptCmdletInfo != null && + scriptCmdletInfo.ScriptBlock.LanguageMode.HasValue && + scriptCmdletInfo.ScriptBlock.LanguageMode != Context.LanguageMode) + { + // Set the language mode before parameter binding if it's necessary for a script cmdlet, so that the language + // mode is appropriately applied for evaluating parameter defaults and argument type conversion. + oldLanguageMode = Context.LanguageMode; + 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; + Context.LanguageModeTransitionInParameterBinding = true; + } + } + + BindCommandLineParameters(); + } + finally + { + if (oldLanguageMode.HasValue) + { + // Revert to the original language mode after doing the parameter binding + Context.LanguageMode = oldLanguageMode.Value; + } + + if (oldLangModeTransitionStatus.HasValue) + { + // Revert the transition state to old value after doing the parameter binding + Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus.Value; + } + } } protected override void OnSetCurrentScope() @@ -248,7 +276,7 @@ protected override void OnRestorePreviousScope() } /// - /// Execute BeginProcessing part of command + /// Execute BeginProcessing part of command. /// internal override void DoBegin() { @@ -282,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); } @@ -299,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); } } @@ -339,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) @@ -368,24 +392,25 @@ internal override void ProcessRecord() { throw; } + exceptionToThrow = rte; } 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 { _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; } + if (exceptionToThrow != null) { // This cmdlet threw an exception, so @@ -395,29 +420,26 @@ internal override void ProcessRecord() } } -#endregion public_methods + #endregion public_methods -#region helper_methods + #region helper_methods /// - /// Tells whether it is the first call to Read + /// Tells whether it is the first call to Read. /// private bool _firstCallToRead = true; /// - /// Tells whether to bail out in the next call to Read + /// Tells whether to bail out in the next call to Read. /// private bool _bailInNextCall; - /// /// Populates the parameters specified from the pipeline. /// - /// /// /// A bool indicating whether read succeeded. /// - /// /// /// If a parameter fails to bind. /// or @@ -426,7 +448,6 @@ internal override void ProcessRecord() /// /// The pipeline was already stopped. /// - /// // 2003/10/07-JonN was public, now internal internal sealed override bool Read() { @@ -502,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 @@ -563,31 +584,26 @@ internal sealed override bool Read() /// Writes an ErrorRecord to the commands error pipe because the specified /// input object was not bound to the command. /// - /// /// /// The pipeline input object that was not bound. /// - /// /// /// The error message. /// - /// /// /// The resource ID of the error message is also used as error ID /// of the ErrorRecord. /// - /// /// /// Additional arguments to be formatted into the error message that represented in . /// - /// private void WriteInputObjectError( object inputObject, string resourceString, string errorId, params object[] args) { - Type inputObjectType = (inputObject == null) ? null : inputObject.GetType(); + Type inputObjectType = inputObject?.GetType(); ParameterBindingException bindingException = new ParameterBindingException( ErrorCategory.InvalidArgument, @@ -610,27 +626,23 @@ private void WriteInputObjectError( errorRecord.SetInvocationInfo(this.Command.MyInvocation); this.commandRuntime._WriteErrorSkipAllowCheck(errorRecord); - } // WriteIgnoredInputObjectError + } /// - /// Reads an object from an input pipeline and attempts to bind the parameters + /// Reads an object from an input pipeline and attempts to bind the parameters. /// - /// /// /// The pipeline input object to be processed. /// - /// /// /// False the pipeline input object was not bound in any way to the command. /// - /// /// /// If a ShouldProcess parameter is specified but the cmdlet does not support /// ShouldProcess. /// or /// If an error occurred trying to bind a parameter from the pipeline object. /// - /// private bool ProcessInputPipelineObject(object inputObject) { PSObject inputToOperateOn = null; @@ -643,12 +655,14 @@ private bool ProcessInputPipelineObject(object inputObject) { inputToOperateOn = PSObject.AsPSObject(inputObject); } + Command.CurrentPipelineObject = inputToOperateOn; return this.CmdletParameterBinderController.BindPipelineParameters(inputToOperateOn); } private static readonly ConcurrentDictionary> s_constructInstanceCache; + private static Cmdlet ConstructInstance(Type type) { // Call the default constructor if type derives from Cmdlet. @@ -664,26 +678,21 @@ private static Cmdlet ConstructInstance(Type type) } /// - /// Initializes the command's request object + /// Initializes the command's request object. /// - /// /// /// The information about the cmdlet. /// - /// /// /// 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. /// - /// /// /// If refers to a type that is invalid. /// - /// private void Init(CmdletInfo cmdletInformation) { Diagnostics.Assert(cmdletInformation != null, "Constructor should throw exception if LookupCommand returned null."); @@ -727,7 +736,8 @@ private void Init(CmdletInfo cmdletInformation) throw commandException; } - if (null != initError) + + if (initError != null) { // Log a command health event MshLog.LogCommandHealthEvent( @@ -770,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); } } @@ -808,9 +818,9 @@ private void InitCommon() /// Checks if user has requested help (for example passing "-?" parameter for a cmdlet) /// and if yes, then returns the help target to display. /// - /// help target to request - /// help category to request - /// true if user requested help; false otherwise + /// Help target to request. + /// Help category to request. + /// if user requested help; otherwise. internal override bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory) { if (this.arguments != null) @@ -852,8 +862,6 @@ internal override bool IsHelpRequested(out string helpTarget, out HelpCategory h return base.IsHelpRequested(out helpTarget, out helpCategory); } -#endregion helper_methods + #endregion helper_methods } } - - diff --git a/src/System.Management.Automation/engine/CommandProcessorBase.cs b/src/System.Management.Automation/engine/CommandProcessorBase.cs index f691b6d73e8..ddadf9f7516 100644 --- a/src/System.Management.Automation/engine/CommandProcessorBase.cs +++ b/src/System.Management.Automation/engine/CommandProcessorBase.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; -using System.Management.Automation.Language; using System.Collections.ObjectModel; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation.Diagnostics; +using System.Management.Automation.Security; +using System.Runtime.InteropServices; namespace System.Management.Automation { @@ -19,28 +18,44 @@ internal abstract class CommandProcessorBase : IDisposable #region ctor /// - /// Default constructor + /// Default constructor. /// - /// - internal CommandProcessorBase() { } /// - /// Initializes the base command processor class with the command metadata + /// Initializes the base command processor class with the command metadata. /// - /// /// /// The metadata about the command to run. /// - /// - internal CommandProcessorBase( - CommandInfo commandInfo) + internal CommandProcessorBase(CommandInfo commandInfo) { if (commandInfo == null) { - throw PSTraceSource.NewArgumentNullException("commandInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(commandInfo)); + } + + if (commandInfo is IScriptCommandInfo scriptCommand) + { + ExperimentalAttribute expAttribute = scriptCommand.ScriptBlock.ExperimentalAttribute; + if (expAttribute != null && expAttribute.ToHide) + { + string errorTemplate = expAttribute.ExperimentAction == ExperimentAction.Hide + ? DiscoveryExceptions.ScriptDisabledWhenFeatureOn + : DiscoveryExceptions.ScriptDisabledWhenFeatureOff; + + string errorMsg = StringUtil.Format(errorTemplate, expAttribute.ExperimentName); + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(errorMsg), + "ScriptCommandDisabled", + ErrorCategory.InvalidOperation, + commandInfo); + throw new CmdletInvocationException(errorRecord); + } + + HasCleanBlock = scriptCommand.ScriptBlock.HasCleanBlock; } CommandInfo = commandInfo; @@ -52,26 +67,33 @@ internal CommandProcessorBase( private InternalCommand _command; - // marker of whether BeginProcessing() has already run, - // also used by CommandProcessor + // Marker of whether BeginProcessing() has already run, + // also used by CommandProcessor. internal bool RanBeginAlready; - // marker of whether this command has already been added to - // a PipelineProcessor. It is an error to add the same command + // Marker of whether this command has already been added to + // a PipelineProcessor. It is an error to add the same command // more than once. internal bool AddedToPipelineAlready { get { return _addedToPipelineAlready; } + set { _addedToPipelineAlready = value; } } + internal bool _addedToPipelineAlready; /// - /// Gets the CommandInfo for the command this command processor represents + /// Gets the CommandInfo for the command this command processor represents. /// /// 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. @@ -90,14 +112,14 @@ internal bool AddedToPipelineAlready /// kill current powershell session. /// public bool FromScriptFile { get { return _fromScriptFile; } } + protected bool _fromScriptFile = false; /// /// If this flag is true, the commands in this Pipeline will redirect /// the global error output pipe to the command's error output pipe. - /// - /// (see the comment in Pipeline.RedirectShellErrorOutputPipe for an - /// explanation of why this flag is needed) + /// (See the comment in Pipeline.RedirectShellErrorOutputPipe for an + /// explanation of why this flag is needed). /// internal bool RedirectShellErrorOutputPipe { get; set; } = false; @@ -106,7 +128,11 @@ internal bool AddedToPipelineAlready /// internal InternalCommand Command { - get { return _command; } + get + { + return _command; + } + set { // The command runtime needs to be set up... @@ -121,17 +147,19 @@ internal InternalCommand Command if (value.Context == null && _context != null) value.Context = _context; } + _command = value; } } /// - /// Get the ObsoleteAttribute of the current command + /// Get the ObsoleteAttribute of the current command. /// internal virtual ObsoleteAttribute ObsoleteAttribute { get { return null; } } + // Full Qualified ID for the obsolete command warning private const string FQIDCommandObsolete = "CommandObsolete"; @@ -139,9 +167,11 @@ internal virtual ObsoleteAttribute ObsoleteAttribute /// The command runtime used for this instance of a command processor. /// protected MshCommandRuntime commandRuntime; + internal MshCommandRuntime CommandRuntime { get { return commandRuntime; } + set { commandRuntime = value; } } @@ -153,19 +183,22 @@ internal MshCommandRuntime CommandRuntime internal bool UseLocalScope { get { return _useLocalScope; } + set { _useLocalScope = value; } } + protected bool _useLocalScope; /// /// Ensures that the provided script block is compatible with the current language mode - to /// be used when a script block is being dotted. /// - /// The script block being dotted - /// The current language mode - /// The invocation info about the command - protected static void ValidateCompatibleLanguageMode(ScriptBlock scriptBlock, - PSLanguageMode languageMode, + /// The script block being dotted. + /// The current execution context. + /// The invocation info about the command. + protected static void ValidateCompatibleLanguageMode( + ScriptBlock scriptBlock, + ExecutionContext context, InvocationInfo invocationInfo) { // If we are in a constrained language mode (Core or Restricted), block it. @@ -174,10 +207,11 @@ protected static void ValidateCompatibleLanguageMode(ScriptBlock scriptBlock, // 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. @@ -193,14 +227,24 @@ protected static void ValidateCompatibleLanguageMode(ScriptBlock scriptBlock, 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); } } } @@ -209,14 +253,16 @@ protected static void ValidateCompatibleLanguageMode(ScriptBlock scriptBlock, /// The execution context used by the system. /// protected ExecutionContext _context; + internal ExecutionContext Context { get { return _context; } + set { _context = value; } } /// - /// Etw activity for this pipeline + /// Etw activity for this pipeline. /// internal Guid PipelineActivityId { get; set; } = Guid.Empty; @@ -230,9 +276,9 @@ internal ExecutionContext Context /// Checks if user has requested help (for example passing "-?" parameter for a cmdlet) /// and if yes, then returns the help target to display. /// - /// help target to request - /// help category to request - /// true if user requested help; false otherwise + /// Help target to request. + /// Help category to request. + /// if user requested help; otherwise. internal virtual bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory) { // by default we don't handle "-?" parameter at all @@ -243,12 +289,12 @@ internal virtual bool IsHelpRequested(out string helpTarget, out HelpCategory he } /// - /// Creates a command processor for "get-help [helpTarget]" + /// Creates a command processor for "get-help [helpTarget]". /// - /// context for the command processor - /// help target - /// help category - /// command processor for "get-help [helpTarget]" + /// Context for the command processor. + /// Help target. + /// Help category. + /// Command processor for "get-help [helpTarget]". internal static CommandProcessorBase CreateGetHelpCommandProcessor( ExecutionContext context, string helpTarget, @@ -256,11 +302,12 @@ internal static CommandProcessorBase CreateGetHelpCommandProcessor( { if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } + if (string.IsNullOrEmpty(helpTarget)) { - throw PSTraceSource.NewArgumentNullException("helpTarget"); + throw PSTraceSource.NewArgumentNullException(nameof(helpTarget)); } CommandProcessorBase helpCommandProcessor = context.CreateCommand("get-help", false); @@ -295,7 +342,7 @@ internal bool IsPipelineInputExpected() internal SessionStateInternal CommandSessionState { get; set; } /// - /// Gets sets the session state scope for this command processor object + /// Gets or sets the session state scope for this command processor object. /// protected internal SessionStateScope CommandScope { get; protected set; } @@ -316,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; @@ -336,20 +380,16 @@ internal void SetCurrentScopeToExecutionScope() /// Restores the current session state scope to the scope which was active when SetCurrentScopeToExecutionScope /// was called. /// - /// internal void RestorePreviousScope() { OnRestorePreviousScope(); 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; @@ -360,7 +400,6 @@ internal void RestorePreviousScope() /// host interfaces. These will be sent to the parameter binder controller /// for processing. /// - /// internal Collection arguments = new Collection(); /// @@ -373,7 +412,7 @@ internal void AddParameter(CommandParameterInternal parameter) { Diagnostics.Assert(parameter != null, "Caller to verify parameter argument"); arguments.Add(parameter); - } // AddParameter + } /// /// Prepares the command for execution. @@ -382,18 +421,18 @@ internal void AddParameter(CommandParameterInternal parameter) internal abstract void Prepare(IDictionary psDefaultParameterValues); /// - /// Write warning message for an obsolete command + /// Write warning message for an obsolete command. /// /// private void HandleObsoleteCommand(ObsoleteAttribute obsoleteAttr) { string commandName = - String.IsNullOrEmpty(CommandInfo.Name) + string.IsNullOrEmpty(CommandInfo.Name) ? "script block" - : String.Format(System.Globalization.CultureInfo.InvariantCulture, + : string.Format(System.Globalization.CultureInfo.InvariantCulture, CommandBaseStrings.ObsoleteCommand, CommandInfo.Name); - string warningMsg = String.Format( + string warningMsg = string.Format( System.Globalization.CultureInfo.InvariantCulture, CommandBaseStrings.UseOfDeprecatedCommandWarning, commandName, obsoleteAttr.Message); @@ -425,15 +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); - } - throw; + // This type of exception could be thrown from parameter binding. + string msg = StringUtil.Format(ParserStrings.InvalidComObjectException, e.Message); + var newEx = new RuntimeException(msg, e); + + newEx.SetErrorId("InvalidComObjectException"); + throw newEx; } finally { @@ -480,25 +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) @@ -527,7 +563,6 @@ internal virtual void DoBegin() /// the ProcessRecord abstract method that derived command processors /// override. /// - /// internal void DoExecute() { ExecutionContext.CheckStackDepth(); @@ -551,7 +586,7 @@ internal void DoExecute() /// Internally it calls EndProcessing() of the InternalCommand. /// /// - /// a terminating error occurred, or the pipeline was otherwise stopped + /// A terminating error occurred, or the pipeline was otherwise stopped. /// internal virtual void Complete() { @@ -561,28 +596,21 @@ 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); } - } // Complete + } /// - /// Calls the virtual Complete method after setting the appropriate session state scope + /// Calls the virtual Complete method after setting the appropriate session state scope. /// - /// internal void DoComplete() { Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe; @@ -604,52 +632,127 @@ 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; + _context.CurrentCommandProcessor = this; SetCurrentScopeToExecutionScope(); Complete(); } finally { - OnRestorePreviousScope(); - _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; _context.CurrentCommandProcessor = oldCurrentCommandProcessor; - // Destroy the local scope at this point if there is one... - if (_useLocalScope && CommandScope != null) - { - CommandSessionState.RemoveScope(CommandScope); - } + RestorePreviousScope(); + } + } - // and the previous scope... - if (_previousScope != null) + protected virtual void CleanResource() + { + try + { + using (commandRuntime.AllowThisCommandToWrite(permittedToWriteToPipeline: true)) + using (ParameterBinderBase.bindingTracer.TraceScope("CALLING CleanResource")) { - // Restore the scope but use the same session state instance we - // got it from because the command may have changed the execution context - // session state... - CommandSessionState.CurrentScope = _previousScope; + Command.DoCleanResource(); } + } + catch (HaltCommandException) + { + throw; + } + catch (FlowControlException) + { + throw; + } + catch (Exception e) + { + // This cmdlet threw an exception, so wrap it and bubble it up. + throw ManageInvocationException(e); + } + } + + internal void DoCleanup() + { + // The property 'PropagateExceptionsToEnclosingStatementBlock' controls whether a general exception + // (an exception thrown from a .NET method invocation, or an expression like '1/0') will be turned + // into a terminating error, which will be propagated up and thus stop the rest of the running script. + // It is usually used by TryStatement and TrapStatement, which makes the general exception catch-able. + // + // For the 'Clean' block, we don't want to bubble up the general exception when the command is enclosed + // in a TryStatement or has TrapStatement accompanying, because no exception can escape from 'Clean' and + // thus it's pointless to bubble up the general exception in this case. + // + // Therefore we set this property to 'false' here to mask off the previous setting that could be from a + // TryStatement or TrapStatement. Example: + // PS:1> function b { end {} clean { 1/0; Write-Host 'clean' } } + // PS:2> b + // RuntimeException: Attempted to divide by zero. + // clean + // ## Note that, outer 'try/trap' doesn't affect the general exception happens in 'Clean' block. + // ## so its behavior is consistent regardless of whether the command is enclosed by 'try/catch' or not. + // PS:3> try { b } catch { 'outer catch' } + // RuntimeException: Attempted to divide by zero. + // clean + // + // Be noted that, this doesn't affect the TryStatement/TrapStatement within the 'Clean' block. Example: + // ## 'try/trap' within 'Clean' block makes the general exception catch-able. + // PS:3> function a { end {} clean { try { 1/0; Write-Host 'clean' } catch { Write-Host "caught: $_" } } } + // PS:4> a + // caught: Attempted to divide by zero. + bool oldExceptionPropagationState = _context.PropagateExceptionsToEnclosingStatementBlock; + _context.PropagateExceptionsToEnclosingStatementBlock = false; + + Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe; + CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor; - // Restore the previous session state - if (_previousCommandSessionState != null) + try + { + if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null) { - Context.EngineSessionState = _previousCommandSessionState; + _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe; } + + _context.CurrentCommandProcessor = this; + SetCurrentScopeToExecutionScope(); + CleanResource(); + } + finally + { + _context.PropagateExceptionsToEnclosingStatementBlock = oldExceptionPropagationState; + _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; + _context.CurrentCommandProcessor = oldCurrentCommandProcessor; + + RestorePreviousScope(); } } + internal void ReportCleanupError(Exception exception) + { + var error = exception is IContainsErrorRecord icer + ? icer.ErrorRecord + : new ErrorRecord(exception, "Clean.ReportException", ErrorCategory.NotSpecified, targetObject: null); + + PSObject errorWrap = PSObject.AsPSObject(error); + errorWrap.WriteStream = WriteStreamType.Error; + + var errorPipe = commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output + ? commandRuntime.OutputPipe + : commandRuntime.ErrorOutputPipe; + + errorPipe.Add(errorWrap); + _context.QuestionMarkVariableValue = false; + } + /// - /// for diagnostic purposes + /// For diagnostic purposes. /// - /// public override string ToString() { - if (null != CommandInfo) + if (CommandInfo != null) return CommandInfo.ToString(); return ""; // does not require localization } @@ -665,13 +768,11 @@ public override string ToString() /// /// This default implementation reads the next pipeline object and sets /// it as the CurrentPipelineObject in the InternalCommand. + /// Does not throw. /// - /// /// /// True if read succeeds. /// - /// - /// does not throw internal virtual bool Read() { // Prepare the default value parameter list if this is the first call to Read @@ -705,17 +806,14 @@ internal virtual bool Read() /// PipelineProcessor.SynchronousExecute, and writes it to /// the error variable. /// - /// /// /// The exception to wrap in a CmdletInvocationException or /// CmdletProviderInvocationException. /// - /// /// /// Always returns PipelineStoppedException. The caller should /// throw this exception. /// - /// /// /// Almost all exceptions which occur during pipeline invocation /// are wrapped in CmdletInvocationException before they are stored @@ -751,27 +849,20 @@ internal PipelineStoppedException ManageInvocationException(Exception e) { try { - if (null != Command) + if (Command != null) { 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 @@ -791,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 @@ -862,11 +951,9 @@ internal PipelineStoppedException ManageInvocationException(Exception e) /// PipelineProcessor.SynchronousExecute, and writes it to /// the error variable. /// - /// /// /// The exception which occurred during script execution /// - /// /// /// ManageScriptException throws PipelineStoppedException if-and-only-if /// the exception is a RuntimeException, otherwise it returns. @@ -874,13 +961,13 @@ internal PipelineStoppedException ManageInvocationException(Exception e) /// internal void ManageScriptException(RuntimeException e) { - if (null != Command && null != commandRuntime.PipelineProcessor) + if (Command != null && commandRuntime.PipelineProcessor != null) { commandRuntime.PipelineProcessor.RecordFailure(e, Command); // 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 @@ -893,7 +980,7 @@ internal void ManageScriptException(RuntimeException e) /// internal void ForgetScriptException() { - if (null != Command && null != commandRuntime.PipelineProcessor) + if (Command != null && commandRuntime.PipelineProcessor != null) { commandRuntime.PipelineProcessor.ForgetFailure(); } @@ -911,7 +998,7 @@ internal void ForgetScriptException() /// IDisposable implementation /// When the command is complete, the CommandProcessorBase should be disposed. /// This enables cmdlets to reliably release file handles etc. - /// without waiting for garbage collection + /// without waiting for garbage collection. /// /// We use the standard IDispose pattern public void Dispose() @@ -923,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 (null != id) + 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(); } @@ -940,15 +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 5c0dc3418d2..638ccd4ac79 100644 --- a/src/System.Management.Automation/engine/CommandSearcher.cs +++ b/src/System.Management.Automation/engine/CommandSearcher.cs @@ -1,18 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Management.Automation.Internal; +using System.Management.Automation.Security; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// Used to enumerate the commands on the system that match the specified - /// command name + /// command name. /// internal class CommandSearcher : IEnumerable, IEnumerator { @@ -20,36 +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. + /// The fuzzy matcher to use for fuzzy searching. /// - /// - /// 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. - /// - /// + /// 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"); @@ -58,37 +47,34 @@ internal CommandSearcher( _context = context; _commandResolutionOptions = options; _commandTypes = commandTypes; + _fuzzyMatcher = fuzzyMatcher; // Initialize the enumerators this.Reset(); } /// - /// Gets an instance of a command enumerator + /// Gets an instance of a command enumerator. /// - /// /// /// An instance of this class as IEnumerator. /// - /// IEnumerator IEnumerable.GetEnumerator() { return this; - } // GetEnumerator + } IEnumerator IEnumerable.GetEnumerator() { return this; - } // GetEnumerator + } /// - /// Moves the enumerator to the next command match. Public for IEnumerable + /// Moves the enumerator to the next command match. Public for IEnumerable. /// - /// /// /// true if there was another command that matches, false otherwise. /// - /// public bool MoveNext() { _currentMatch = null; @@ -165,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)) @@ -210,7 +196,7 @@ public bool MoveNext() _currentState = SearchState.QualifiedFileSystemPath; return true; } - } // SearchState.Reset + } if (_currentState == SearchState.PowerShellPathResolution) { @@ -222,7 +208,7 @@ public bool MoveNext() { return true; } - } // SearchState.PowerShellPathResolution + } // Search using CommandPathSearch @@ -235,7 +221,7 @@ public bool MoveNext() { return true; } - } // SearchState.QualifiedFileSystemPath || SearchState.PathSearch + } if (_currentState == SearchState.PathSearch) { @@ -250,26 +236,27 @@ public bool MoveNext() } return false; - } // MoveNext + } - private CommandInfo SearchForAliases() + private CommandInfo? SearchForAliases() { - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; if (_context.EngineSessionState != null && (_commandTypes & CommandTypes.Alias) != 0) { currentMatch = GetNextAlias(); } + return currentMatch; } - private CommandInfo SearchForFunctions() + private CommandInfo? SearchForFunctions() { - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; if (_context.EngineSessionState != null && - (_commandTypes & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Workflow | CommandTypes.Configuration)) != 0) + (_commandTypes & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Configuration)) != 0) { currentMatch = GetNextFunction(); } @@ -277,9 +264,9 @@ private CommandInfo SearchForFunctions() return currentMatch; } - private CommandInfo SearchForCmdlets() + private CommandInfo? SearchForCmdlets() { - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; if ((_commandTypes & CommandTypes.Cmdlet) != 0) { @@ -289,9 +276,9 @@ private CommandInfo SearchForCmdlets() return currentMatch; } - private CommandInfo ProcessBuiltinScriptState() + private CommandInfo? ProcessBuiltinScriptState() { - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; // Check to see if the path is qualified @@ -305,9 +292,9 @@ private CommandInfo ProcessBuiltinScriptState() return currentMatch; } - private CommandInfo ProcessPathResolutionState() + private CommandInfo? ProcessPathResolutionState() { - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; try { @@ -347,7 +334,7 @@ private CommandInfo ProcessPathResolutionState() return currentMatch; } - private CommandInfo ProcessQualifiedFileSystemState() + private CommandInfo? ProcessQualifiedFileSystemState() { try { @@ -364,13 +351,14 @@ private CommandInfo ProcessQualifiedFileSystemState() throw; } - CommandInfo currentMatch = null; + CommandInfo? currentMatch = null; _currentState = SearchState.PathSearch; if (_canDoPathLookup) { try { - while (currentMatch == null && _pathSearcher.MoveNext()) + // the previous call to setupPathSearcher ensures _pathSearcher != null + while (currentMatch == null && _pathSearcher!.MoveNext()) { currentMatch = GetInfoFromPath(((IEnumerator)_pathSearcher).Current); } @@ -380,15 +368,16 @@ private CommandInfo ProcessQualifiedFileSystemState() // The enumerator may throw if there are no more matches } } + return currentMatch; } - private CommandInfo ProcessPathSearchState() + private CommandInfo? ProcessPathSearchState() { - CommandInfo currentMatch = null; - string path = DoPowerShellRelativePathLookup(); + CommandInfo? currentMatch = null; + string? path = DoPowerShellRelativePathLookup(); - if (!String.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path)) { currentMatch = GetInfoFromPath(path); } @@ -396,17 +385,14 @@ private CommandInfo ProcessPathSearchState() return currentMatch; } - /// /// Gets the CommandInfo representing the current command match. /// /// - /// /// /// The enumerator is positioned before the first element of /// the collection or after the last element. /// - /// CommandInfo IEnumerator.Current { get @@ -420,8 +406,7 @@ CommandInfo IEnumerator.Current return _currentMatch; } - } // Current - + } object IEnumerator.Current { @@ -450,16 +435,14 @@ public void Dispose() #region private members /// - /// Gets the next command info using the command name as a path + /// Gets the next command info using the command name as a path. /// - /// /// /// A CommandInfo for the next command if it exists as a path, or null otherwise. /// - /// - private CommandInfo GetNextFromPath() + private CommandInfo? GetNextFromPath() { - CommandInfo result = null; + CommandInfo? result = null; do // false loop { @@ -471,52 +454,33 @@ private CommandInfo GetNextFromPath() "Trying to resolve the path as an PSPath"); // Find the match if it is. - - Collection resolvedPaths = new Collection(); - - try - { - Provider.CmdletProvider providerInstance; - ProviderInfo provider; - resolvedPaths = - _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(_commandName, false, out provider, out providerInstance); - } - catch (ItemNotFoundException) - { - CommandDiscovery.discoveryTracer.TraceError( - "The path could not be found: {0}", - _commandName); - } - catch (DriveNotFoundException) - { - CommandDiscovery.discoveryTracer.TraceError( - "A drive could not be found for the path: {0}", - _commandName); - } - catch (ProviderNotFoundException) - { - CommandDiscovery.discoveryTracer.TraceError( - "A provider could not be found for the path: {0}", - _commandName); - } - catch (InvalidOperationException) + // Try literal path resolution if it is set to run first + if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns)) { - CommandDiscovery.discoveryTracer.TraceError( - "The path specified a home directory, but the provider home directory was not set. {0}", - _commandName); + var path = GetNextLiteralPathThatExistsAndHandleExceptions(_commandName, out _); + + if (path != null) + { + return GetInfoFromPath(path); + } } - catch (ProviderInvocationException providerException) + + Collection resolvedPaths = new Collection(); + if (WildcardPattern.ContainsWildcardCharacters(_commandName)) { - CommandDiscovery.discoveryTracer.TraceError( - "The provider associated with the path '{0}' encountered an error: {1}", - _commandName, - providerException.Message); + resolvedPaths = GetNextFromPathUsingWildcards(_commandName, out _); } - catch (PSNotSupportedException) + + // Try literal path resolution if wildcards are enable first and wildcard search failed + if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) && + resolvedPaths.Count == 0) { - CommandDiscovery.discoveryTracer.TraceError( - "The provider associated with the path '{0}' does not implement ContainerCmdletProvider", - _commandName); + string? path = GetNextLiteralPathThatExistsAndHandleExceptions(_commandName, out _); + + if (path != null) + { + return GetInfoFromPath(path); + } } if (resolvedPaths.Count > 1) @@ -543,6 +507,64 @@ private CommandInfo GetNextFromPath() return result; } + /// + /// Gets the next path using WildCards. + /// + /// + /// The command to search for. + /// + /// The provider that the command was found in. + /// + /// A collection of full paths to the commands which were found. + /// + private Collection GetNextFromPathUsingWildcards(string? command, out ProviderInfo? provider) + { + try + { + return _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(path: command, allowNonexistingPaths: false, provider: out provider, providerInstance: out _); + } + catch (ItemNotFoundException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The path could not be found: {0}", + command); + } + catch (DriveNotFoundException) + { + CommandDiscovery.discoveryTracer.TraceError( + "A drive could not be found for the path: {0}", + command); + } + catch (ProviderNotFoundException) + { + CommandDiscovery.discoveryTracer.TraceError( + "A provider could not be found for the path: {0}", + command); + } + catch (InvalidOperationException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The path specified a home directory, but the provider home directory was not set. {0}", + command); + } + catch (ProviderInvocationException providerException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The provider associated with the path '{0}' encountered an error: {1}", + command, + providerException.Message); + } + catch (PSNotSupportedException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The provider associated with the path '{0}' does not implement ContainerCmdletProvider", + command); + } + + provider = null; + return new Collection(); + } + private static bool checkPath(string path, string commandName) { return path.StartsWith(commandName, StringComparison.OrdinalIgnoreCase); @@ -551,44 +573,38 @@ private static bool checkPath(string path, string commandName) /// /// Gets the appropriate CommandInfo instance given the specified path. /// - /// /// /// The path to create the CommandInfo for. /// - /// /// /// An instance of the appropriate CommandInfo derivative given the specified path. /// - /// /// /// The refers to a cmdlet, or cmdletprovider /// and it could not be loaded as an XML document. /// - /// /// /// The refers to a cmdlet, or cmdletprovider /// that does not adhere to the appropriate file format for its extension. /// - /// /// /// If refers to a cmdlet file that /// contains invalid metadata. /// - /// - private CommandInfo GetInfoFromPath(string path) + private CommandInfo? GetInfoFromPath(string path) { - CommandInfo result = null; + CommandInfo? result = null; do // false loop { - if (!Utils.NativeFileExists(path)) + if (!File.Exists(path)) { CommandDiscovery.discoveryTracer.TraceError("The path does not exist: {0}", path); break; } // Now create the appropriate CommandInfo using the extension - string extension = null; + string? extension = null; try { @@ -611,7 +627,7 @@ private CommandInfo GetInfoFromPath(string path) break; } - if (String.Equals(extension, StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(extension, StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase)) { if ((_commandTypes & CommandTypes.ExternalScript) != 0) { @@ -627,10 +643,10 @@ private CommandInfo GetInfoFromPath(string path) result = new ExternalScriptInfo(scriptName, path, _context); break; } + break; } - if ((_commandTypes & CommandTypes.Application) != 0) { // Anything else is treated like an application @@ -654,19 +670,17 @@ private CommandInfo GetInfoFromPath(string path) } return result; - } // GetNextFromPath + } /// - /// Gets the next matching alias + /// Gets the next matching alias. /// - /// /// /// A CommandInfo representing the next matching alias if found, otherwise null. /// - /// - private CommandInfo GetNextAlias() + private CommandInfo? GetNextAlias() { - CommandInfo result = null; + CommandInfo? result = null; if ((_commandResolutionOptions & SearchResolutionOptions.ResolveAliasPatterns) != 0) { @@ -683,14 +697,15 @@ private CommandInfo GetNextAlias() foreach (KeyValuePair aliasEntry in _context.EngineSessionState.GetAliasTable()) { - if (aliasMatcher.IsMatch(aliasEntry.Key)) + if (aliasMatcher.IsMatch(aliasEntry.Key) || + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName))) { matchingAliases.Add(aliasEntry.Value); } } // Process alias from modules - AliasInfo c = GetAliasFromModules(_commandName); + AliasInfo? c = GetAliasFromModules(_commandName); if (c != null) { matchingAliases.Add(c); @@ -733,26 +748,25 @@ private CommandInfo GetNextAlias() result.Name, result.Definition); } + return result; - } // GetNextAlias + } /// - /// Gets the next matching function + /// Gets the next matching function. /// - /// /// /// A CommandInfo representing the next matching function if found, otherwise null. /// - /// - private CommandInfo GetNextFunction() + private CommandInfo? GetNextFunction() { - CommandInfo result = null; + CommandInfo? result = null; - if ((_commandResolutionOptions & SearchResolutionOptions.ResolveFunctionPatterns) != 0) + if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveFunctionPatterns)) { if (_matchingFunctionEnumerator == null) { - Collection matchingFunction = new Collection(); + Collection matchingFunction = new Collection(); // Generate the enumerator of matching function names WildcardPattern functionMatcher = @@ -760,19 +774,27 @@ private CommandInfo GetNextFunction() _commandName, WildcardOptions.IgnoreCase); - foreach (DictionaryEntry functionEntry in _context.EngineSessionState.GetFunctionTable()) + foreach ((string functionName, FunctionInfo functionInfo) in _context.EngineSessionState.GetFunctionTable()) { - if (functionMatcher.IsMatch((string)functionEntry.Key)) + if (functionMatcher.IsMatch(functionName) || + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(functionName, _commandName))) + { + matchingFunction.Add(functionInfo); + } + else if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion)) { - matchingFunction.Add((CommandInfo)functionEntry.Value); + if (_commandName.Equals(ModuleUtils.AbbreviateName(functionName), StringComparison.OrdinalIgnoreCase)) + { + matchingFunction.Add(functionInfo); + } } } // Process functions from modules - CommandInfo c = GetFunctionFromModules(_commandName); - if (c != null) + CommandInfo? cmdInfo = GetFunctionFromModules(_commandName); + if (cmdInfo != null) { - matchingFunction.Add(c); + matchingFunction.Add(cmdInfo); } _matchingFunctionEnumerator = matchingFunction.GetEnumerator(); @@ -811,18 +833,28 @@ private CommandInfo GetNextFunction() // 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. @@ -843,67 +875,61 @@ private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo resul return false; } - private AliasInfo GetAliasFromModules(string command) + private AliasInfo? GetAliasFromModules(string command) { - AliasInfo result = null; + AliasInfo? result = null; if (command.IndexOf('\\') > 0) { // See if it's a module qualified alias... - PSSnapinQualifiedName qualifiedName = PSSnapinQualifiedName.GetInstance(command); + PSSnapinQualifiedName? qualifiedName = PSSnapinQualifiedName.GetInstance(command); if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName)) { - PSModuleInfo module = GetImportedModuleByName(qualifiedName.PSSnapInName); + PSModuleInfo? module = GetImportedModuleByName(qualifiedName.PSSnapInName); - if (module != null) - { - module.ExportedAliases.TryGetValue(qualifiedName.ShortName, out result); - } + module?.ExportedAliases.TryGetValue(qualifiedName.ShortName, out result); } } + return result; } - private CommandInfo GetFunctionFromModules(string command) + private CommandInfo? GetFunctionFromModules(string command) { - FunctionInfo result = null; + FunctionInfo? result = null; if (command.IndexOf('\\') > 0) { // See if it's a module qualified function call... - PSSnapinQualifiedName qualifiedName = PSSnapinQualifiedName.GetInstance(command); + PSSnapinQualifiedName? qualifiedName = PSSnapinQualifiedName.GetInstance(command); if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName)) { - PSModuleInfo module = GetImportedModuleByName(qualifiedName.PSSnapInName); + PSModuleInfo? module = GetImportedModuleByName(qualifiedName.PSSnapInName); - if (module != null) - { - module.ExportedFunctions.TryGetValue(qualifiedName.ShortName, out result); - } + module?.ExportedFunctions.TryGetValue(qualifiedName.ShortName, out result); } } + return result; } - private PSModuleInfo GetImportedModuleByName(string moduleName) + private PSModuleInfo? GetImportedModuleByName(string moduleName) { - PSModuleInfo module = null; + PSModuleInfo? module = null; List modules = _context.Modules.GetModules(new string[] { moduleName }, false); if (modules != null && modules.Count > 0) { foreach (PSModuleInfo m in modules) { - if (_context.previousModuleImported.ContainsKey(m.Name) && ((string)_context.previousModuleImported[m.Name] == m.Path)) + if (_context.previousModuleImported.ContainsKey(m.Name) && ((string?)_context.previousModuleImported[m.Name] == m.Path)) { module = m; break; } } - if (module == null) - { - module = modules[0]; - } + + module ??= modules[0]; } return module; @@ -912,81 +938,69 @@ private PSModuleInfo GetImportedModuleByName(string moduleName) /// /// Gets the FunctionInfo or FilterInfo for the specified function name. /// - /// /// /// The name of the function/filter to retrieve. /// - /// /// /// A FunctionInfo if the function name exists and is a function, a FilterInfo if /// the filter name exists and is a filter, or null otherwise. /// - /// - private CommandInfo GetFunction(string function) + private CommandInfo? GetFunction(string function) { - CommandInfo result = _context.EngineSessionState.GetFunction(function); + CommandInfo? result = _context.EngineSessionState.GetFunction(function); 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 { result = GetFunctionFromModules(function); } + return result; - } // GetFunction + } /// /// Gets the next cmdlet from the collection of matching cmdlets. /// If the collection doesn't exist yet it is created and the /// enumerator is moved to the first item in the collection. /// - /// /// /// A CmdletInfo for the next matching Cmdlet or null if there are /// no more matches. /// - /// - private CmdletInfo GetNextCmdlet() + private CmdletInfo? GetNextCmdlet() { - CmdletInfo result = null; + CmdletInfo? result = null; + bool useAbbreviationExpansion = _commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion); if (_matchingCmdlet == null) { - if ((_commandResolutionOptions & SearchResolutionOptions.CommandNameIsPattern) != 0) + if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.CommandNameIsPattern) || useAbbreviationExpansion) { Collection matchingCmdletInfo = new Collection(); - PSSnapinQualifiedName PSSnapinQualifiedCommandName = + PSSnapinQualifiedName? PSSnapinQualifiedCommandName = PSSnapinQualifiedName.GetInstance(_commandName); - if (PSSnapinQualifiedCommandName == null) + if (!useAbbreviationExpansion && PSSnapinQualifiedCommandName == null) { return null; } - WildcardPattern cmdletMatcher = - WildcardPattern.Get( - PSSnapinQualifiedCommandName.ShortName, - WildcardOptions.IgnoreCase); + string? moduleName = PSSnapinQualifiedCommandName?.PSSnapInName; + + var cmdletShortName = PSSnapinQualifiedCommandName?.ShortName; + WildcardPattern? cmdletMatcher = cmdletShortName != null + ? WildcardPattern.Get(cmdletShortName, WildcardOptions.IgnoreCase) + : null; SessionStateInternal ss = _context.EngineSessionState; @@ -994,16 +1008,22 @@ private CmdletInfo GetNextCmdlet() { foreach (CmdletInfo cmdlet in cmdletList) { - if (cmdletMatcher.IsMatch(cmdlet.Name)) + if ((cmdletMatcher is not null && cmdletMatcher.IsMatch(cmdlet.Name)) || + (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName))) { - if (string.IsNullOrEmpty(PSSnapinQualifiedCommandName.PSSnapInName) || - (PSSnapinQualifiedCommandName.PSSnapInName.Equals( - cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase))) + if (string.IsNullOrEmpty(moduleName) || moduleName.Equals(cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase)) { // If PSSnapin is specified, make sure they match matchingCmdletInfo.Add(cmdlet); } } + else if (useAbbreviationExpansion) + { + if (_commandName.Equals(ModuleUtils.AbbreviateName(cmdlet.Name), StringComparison.OrdinalIgnoreCase)) + { + matchingCmdletInfo.Add(cmdlet); + } + } } } @@ -1012,7 +1032,7 @@ private CmdletInfo GetNextCmdlet() else { _matchingCmdlet = _context.CommandDiscovery.GetCmdletInfo(_commandName, - (_commandResolutionOptions & SearchResolutionOptions.SearchAllScopes) != 0); + _commandResolutionOptions.HasFlag(SearchResolutionOptions.SearchAllScopes)); } } @@ -1030,9 +1050,11 @@ private CmdletInfo GetNextCmdlet() return traceResult(result); } - private IEnumerator _matchingCmdlet; - private static CmdletInfo traceResult(CmdletInfo result) + private IEnumerator? _matchingCmdlet; + + [return: NotNullIfNotNull("result")] + private static CmdletInfo? traceResult(CmdletInfo? result) { if (result != null) { @@ -1041,15 +1063,16 @@ private static CmdletInfo traceResult(CmdletInfo result) result.Name, result.ImplementingType); } + return result; } - private string DoPowerShellRelativePathLookup() + private string? DoPowerShellRelativePathLookup() { - string result = null; + 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 @@ -1061,7 +1084,9 @@ private string DoPowerShellRelativePathLookup() // 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", @@ -1071,40 +1096,44 @@ private string DoPowerShellRelativePathLookup() } } } + return result; - } // DoPowerShellRelativePathLookup + } /// /// Resolves the given path as an PSPath and ensures that it was resolved - /// by the FileSystemProvider + /// by the FileSystemProvider. /// - /// /// /// The path to resolve. /// - /// /// /// The path that was resolved. Null if the path couldn't be resolved or was /// not resolved by the FileSystemProvider. /// - /// - private string ResolvePSPath(string path) + private string? ResolvePSPath(string? path) { - string result = null; + string? result = null; try { - ProviderInfo provider = null; - string resolvedPath = null; - if (WildcardPattern.ContainsWildcardCharacters(path)) + ProviderInfo? provider = null; + string? resolvedPath = null; + + // Try literal path resolution if it is set to run first + if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns)) + { + // Cannot return early as this code path only expects + // The file system provider and the final check for that + // must verify this before we return. + resolvedPath = GetNextLiteralPathThatExists(path, out provider); + } + + if (WildcardPattern.ContainsWildcardCharacters(path) && + ((resolvedPath == null) || (provider == null))) { // Let PowerShell resolve relative path with wildcards. - Provider.CmdletProvider providerInstance; - Collection resolvedPaths = _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath( - path, - false, - out provider, - out providerInstance); + Collection resolvedPaths = GetNextFromPathUsingWildcards(path, out provider); if (resolvedPaths.Count == 0) { @@ -1128,14 +1157,15 @@ private string ResolvePSPath(string path) } } - // Revert to previous path resolver if wildcards produces no results. - if ((resolvedPath == null) || (provider == null)) + // Try literal path resolution if wildcards are enabled first and wildcard search failed + if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) && + ((resolvedPath == null) || (provider == null))) { - resolvedPath = _context.LocationGlobber.GetProviderPath(path, out provider); + resolvedPath = GetNextLiteralPathThatExists(path, out provider); } // Verify the path was resolved to a file system path - if (provider.NameEquals(_context.ProviderNames.FileSystem)) + if (provider != null && provider.NameEquals(_context.ProviderNames.FileSystem)) { result = resolvedPath; @@ -1178,16 +1208,103 @@ private string ResolvePSPath(string path) } return result; - } // ResolvePSPath + } + /// + /// Gets the next literal path. + /// Filtering to ones that exist for the filesystem. + /// Handles Exceptions + /// + /// + /// The command to search for. + /// + /// The provider that the command was found in. + /// + /// Full path to the command. + /// + private string? GetNextLiteralPathThatExistsAndHandleExceptions(string command, out ProviderInfo? provider) + { + try + { + return GetNextLiteralPathThatExists(command, out provider); + } + catch (ItemNotFoundException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The path could not be found: {0}", + _commandName); + } + catch (DriveNotFoundException) + { + // This can be because we think a scope or a url is a drive + // and need to continue searching. + // Although, scope does not work through get-command + CommandDiscovery.discoveryTracer.TraceError( + "A drive could not be found for the path: {0}", + _commandName); + } + catch (ProviderNotFoundException) + { + CommandDiscovery.discoveryTracer.TraceError( + "A provider could not be found for the path: {0}", + _commandName); + } + catch (InvalidOperationException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The path specified a home directory, but the provider home directory was not set. {0}", + _commandName); + } + catch (ProviderInvocationException providerException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The provider associated with the path '{0}' encountered an error: {1}", + _commandName, + providerException.Message); + } + catch (PSNotSupportedException) + { + CommandDiscovery.discoveryTracer.TraceError( + "The provider associated with the path '{0}' does not implement ContainerCmdletProvider", + _commandName); + } + + provider = null; + return null; + } /// - /// Creates a collection of patterns used to find the command + /// Gets the next literal path. + /// Filtering to ones that exist for the filesystem. + /// + /// + /// The command to search for. + /// + /// The provider that the command was found in. + /// + /// Full path to the command. + /// + private string? GetNextLiteralPathThatExists(string? command, out ProviderInfo? provider) + { + string resolvedPath = _context.LocationGlobber.GetProviderPath(command, out provider); + + if (provider.NameEquals(_context.ProviderNames.FileSystem) + && !File.Exists(resolvedPath) + && !Directory.Exists(resolvedPath)) + { + provider = null; + return null; + } + + return resolvedPath; + } + + /// + /// Creates a collection of patterns used to find the command. /// - /// /// /// The name of the command to search for. /// - /// get names for command discovery + /// Get names for command discovery. /// /// A collection of the patterns used to find the command. /// The patterns are as follows: @@ -1198,32 +1315,25 @@ private string ResolvePSPath(string path) /// [commandName].[extension] /// x+1. [commandName] /// - /// /// /// If contains one or more of the /// invalid characters defined in InvalidPathChars. /// - internal Collection ConstructSearchPatternsFromName(string name, bool commandDiscovery = false) + internal LookupPathCollection ConstructSearchPatternsFromName(string name, bool commandDiscovery = false) { - Dbg.Assert( - !String.IsNullOrEmpty(name), - "Caller should verify name"); - - Collection result = new Collection(); + var result = new LookupPathCollection(); // First check to see if the commandName has an extension, if so // look for that first + bool commandNameAddedFirst = Path.HasExtension(name); - bool commandNameAddedFirst = false; - - if (!String.IsNullOrEmpty(Path.GetExtension(name))) + if (commandNameAddedFirst) { result.Add(name); - commandNameAddedFirst = true; } // Add the extensions for script, module and data files in that order... - if ((_commandTypes & CommandTypes.ExternalScript) != 0) + if (_commandTypes.HasFlag(CommandTypes.ExternalScript)) { result.Add(name + StringLiterals.PowerShellScriptFileExtension); if (!commandDiscovery) @@ -1233,44 +1343,39 @@ internal Collection ConstructSearchPatternsFromName(string name, bool co result.Add(name + StringLiterals.PowerShellDataFileExtension); } } - - if ((_commandTypes & CommandTypes.Application) != 0) +#if !UNIX + if (_commandTypes.HasFlag(CommandTypes.Application)) { // Now add each extension from the PATHEXT environment variable - foreach (string extension in CommandDiscovery.PathExtensions) { result.Add(name + extension); } } - - // Now add the commandName by itself if it wasn't added as the first - // pattern - +#endif + // Now add the commandName by itself if it wasn't added as the first pattern if (!commandNameAddedFirst) { result.Add(name); } + return result; - } // ConstructSearchPatternsFromName + } /// /// Determines if the given command name is a qualified PowerShell path. /// - /// /// /// The name of the command. /// - /// /// /// True if the command name is either a provider-qualified or PowerShell drive-qualified /// path. False otherwise. /// - /// private static bool IsQualifiedPSPath(string commandName) { Dbg.Assert( - !String.IsNullOrEmpty(commandName), + !string.IsNullOrEmpty(commandName), "The caller should have verified the commandName"); bool result = @@ -1280,7 +1385,7 @@ private static bool IsQualifiedPSPath(string commandName) LocationGlobber.IsProviderDirectPath(commandName); return result; - } // IsQualifiedPSPath + } private enum CanDoPathLookupResult { @@ -1296,16 +1401,13 @@ private enum CanDoPathLookupResult /// characters which would require resolution. If so, /// path lookup will not succeed. /// - /// /// /// The command name (or possible path) to look for the special characters. /// - /// /// /// True if the command name does not contain any special /// characters. False otherwise. /// - /// private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) { CanDoPathLookupResult result = CanDoPathLookupResult.Yes; @@ -1337,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; @@ -1346,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; @@ -1354,18 +1456,17 @@ private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) } while (false); return result; - } // CanDoPathLookup - + } /// - /// The command name to search for + /// The command name to search for. /// private string _commandName; /// /// Determines which command types will be globbed. /// - private SearchResolutionOptions _commandResolutionOptions; + private readonly SearchResolutionOptions _commandResolutionOptions; /// /// Determines which types of commands to look for. @@ -1376,23 +1477,26 @@ private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) /// The enumerator that uses the Path to /// search for commands. /// - private CommandPathSearch _pathSearcher; + private CommandPathSearch? _pathSearcher; /// /// The execution context instance for the current engine... /// - private ExecutionContext _context; + private readonly ExecutionContext _context; + + /// + /// The fuzzy matcher to use for fuzzy searching. + /// + private readonly FuzzyMatcher? _fuzzyMatcher; /// /// A routine to initialize the path searcher... /// - /// /// /// If the commandName used to construct this object /// contains one or more of the invalid characters defined /// in InvalidPathChars. /// - /// private void setupPathSearcher() { // If it's already set up, just return... @@ -1416,7 +1520,8 @@ private void setupPathSearcher() _commandName, _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, - acceptableCommandNames: null); + acceptableCommandNames: null, + _fuzzyMatcher); } else { @@ -1431,14 +1536,15 @@ private void setupPathSearcher() _commandName, _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, - ConstructSearchPatternsFromName(_commandName, commandDiscovery: true)); + ConstructSearchPatternsFromName(_commandName, commandDiscovery: true), + fuzzyMatcher: null); } else if (_canDoPathLookupResult == CanDoPathLookupResult.PathIsRooted) { _canDoPathLookup = true; - string directory = Path.GetDirectoryName(_commandName); - var directoryCollection = new[] { directory }; + string? directory = Path.GetDirectoryName(_commandName); + var directoryCollection = new LookupPathCollection { directory }; CommandDiscovery.discoveryTracer.WriteLine( "The path is rooted, so only doing the lookup in the specified directory: {0}", @@ -1446,7 +1552,7 @@ private void setupPathSearcher() string fileName = Path.GetFileName(_commandName); - if (!String.IsNullOrEmpty(fileName)) + if (!string.IsNullOrEmpty(fileName)) { fileName = fileName.TrimEnd(Utils.Separators.PathSearchTrimEnd); _pathSearcher = @@ -1454,7 +1560,8 @@ private void setupPathSearcher() fileName, directoryCollection, _context, - ConstructSearchPatternsFromName(fileName, commandDiscovery: true)); + ConstructSearchPatternsFromName(fileName, commandDiscovery: true), + fuzzyMatcher: null); } else { @@ -1468,7 +1575,7 @@ private void setupPathSearcher() // We must try to resolve the path as an PSPath or else we can't do // path lookup for relative paths. - string directory = Path.GetDirectoryName(_commandName); + string? directory = Path.GetDirectoryName(_commandName); directory = ResolvePSPath(directory); CommandDiscovery.discoveryTracer.WriteLine( @@ -1481,11 +1588,11 @@ private void setupPathSearcher() } else { - var directoryCollection = new[] { directory }; + var directoryCollection = new LookupPathCollection { directory }; string fileName = Path.GetFileName(_commandName); - if (!String.IsNullOrEmpty(fileName)) + if (!string.IsNullOrEmpty(fileName)) { fileName = fileName.TrimEnd(Utils.Separators.PathSearchTrimEnd); _pathSearcher = @@ -1493,7 +1600,8 @@ private void setupPathSearcher() fileName, directoryCollection, _context, - ConstructSearchPatternsFromName(fileName, commandDiscovery: true)); + ConstructSearchPatternsFromName(fileName, commandDiscovery: true), + fuzzyMatcher: null); } else { @@ -1505,7 +1613,7 @@ private void setupPathSearcher() } /// - /// Resets the enumerator to before the first command match, public for IEnumerable + /// Resets the enumerator to before the first command match, public for IEnumerable. /// public void Reset() { @@ -1520,43 +1628,43 @@ public void Reset() _commandTypes &= ~CommandTypes.ExternalScript; } - if (_pathSearcher != null) - { - _pathSearcher.Reset(); - } + _pathSearcher?.Reset(); + _currentMatch = null; _currentState = SearchState.SearchingAliases; _matchingAlias = null; _matchingCmdlet = null; - } // Reset + } internal CommandOrigin CommandOrigin { get { return _commandOrigin; } + set { _commandOrigin = value; } } + private CommandOrigin _commandOrigin = CommandOrigin.Internal; /// - /// An enumerator of the matching aliases + /// An enumerator of the matching aliases. /// - private IEnumerator _matchingAlias; + private IEnumerator? _matchingAlias; /// - /// An enumerator of the matching functions + /// An enumerator of the matching functions. /// - private IEnumerator _matchingFunctionEnumerator; + private IEnumerator? _matchingFunctionEnumerator; /// /// The CommandInfo that references the command that matches the pattern. /// - private CommandInfo _currentMatch; + private CommandInfo? _currentMatch; private bool _canDoPathLookup; private CanDoPathLookupResult _canDoPathLookupResult = CanDoPathLookupResult.Yes; /// - /// The current state of the enumerator + /// The current state of the enumerator. /// private SearchState _currentState = SearchState.SearchingAliases; @@ -1594,10 +1702,10 @@ private enum SearchState // No more matches can be found NoMoreMatches, - } // SearchState + } #endregion private members - } // CommandSearcher + } /// /// Determines which types of commands should be globbed using the specified @@ -1611,5 +1719,15 @@ internal enum SearchResolutionOptions ResolveFunctionPatterns = 0x02, CommandNameIsPattern = 0x04, SearchAllScopes = 0x08, + + /// + /// Enable searching for cmdlets/functions by abbreviation expansion. + /// + UseAbbreviationExpansion = 0x10, + + /// + /// Enable resolving wildcard in paths. + /// + ResolveLiteralThenPathPatterns = 0x20 } } diff --git a/src/System.Management.Automation/engine/CommonCommandParameters.cs b/src/System.Management.Automation/engine/CommonCommandParameters.cs index b1af3628221..9dc92d817aa 100644 --- a/src/System.Management.Automation/engine/CommonCommandParameters.cs +++ b/src/System.Management.Automation/engine/CommonCommandParameters.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Management.Automation.Remoting; @@ -16,67 +15,32 @@ public sealed class CommonParameters #region ctor /// - /// Constructs an instance with the specified command instance + /// Constructs an instance with the specified command instance. /// - /// /// /// The instance of the command that the parameters should set the /// user feedback properties on when the parameters get bound. /// - /// /// /// If is null. /// - /// internal CommonParameters(MshCommandRuntime commandRuntime) { if (commandRuntime == null) { - throw PSTraceSource.NewArgumentNullException("commandRuntime"); + throw PSTraceSource.NewArgumentNullException(nameof(commandRuntime)); } + _commandRuntime = commandRuntime; - } // ctor - #endregion ctor + } - internal static string[] CommonWorkflowParameters = { "PSComputerName", "JobName", "PSApplicationName", "PSCredential", "PSPort", "PSConfigurationName", - "PSConnectionURI", "PSSessionOption", "PSAuthentication", "PSAuthenticationLevel", "PSCertificateThumbprint", - "PSConnectionRetryCount", "PSConnectionRetryIntervalSec", "PSRunningTimeoutSec", "PSElapsedTimeoutSec", - "PSPersist", "PSPrivateMetadata", "InputObject", "PSParameterCollection", - "AsJob", "PSUseSSL", "PSAllowRedirection" }; - -#if !CORECLR // Workflow Not Supported On CSS - internal static Type[] CommonWorkflowParameterTypes = { - /* PSComputerName */ typeof(string[]), - /* JobName */ typeof(string), - /* PSApplicationName */ typeof(string), - /* PSCredential */ typeof(PSCredential), - /* PSPort */ typeof(uint), - /* PSConfigurationName */ typeof(string), - /* PSConnectionURI */ typeof(string[]), - /* PSSessionOption */ typeof(PSSessionOption), - /* PSAuthentication */ typeof(AuthenticationMechanism), - /* PSAuthenticationLevel */ typeof(AuthenticationLevel), - /* PSCertificateThumbprint */ typeof(string), - /* PSConnectionRetryCount */ typeof(uint), - /* PSConnectionRetryIntervalSec */ typeof(uint), - /* ??? PSRunningTimeoutSec */ typeof(int), - /* ??? PSElapsedTimeoutSec */ typeof(int), - /* PSPersist */ typeof(bool), - /* ??? PSPrivateMetadata */ typeof(object), - /* ??? InputObject */ typeof(object), - /* ??? PSParameterCollection */ typeof(Hashtable), - /* AsJob */ typeof(bool), - /* PSUseSSL */ typeof(bool), - /* PSAllowRedirection */ typeof(bool), - }; -#endif + #endregion ctor #region parameters /// /// Gets or sets the value of the Verbose parameter for the cmdlet. /// - /// /// /// This parameter /// tells the command to articulate the actions it performs while executing. @@ -86,16 +50,16 @@ internal CommonParameters(MshCommandRuntime commandRuntime) public SwitchParameter Verbose { get { return _commandRuntime.Verbose; } + set { _commandRuntime.Verbose = value; } - } //Verbose + } /// /// Gets or sets the value of the Debug parameter for the cmdlet. /// - /// /// /// 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] @@ -103,13 +67,13 @@ public SwitchParameter Verbose public SwitchParameter Debug { get { return _commandRuntime.Debug; } + set { _commandRuntime.Debug = value; } - } //Debug + } /// /// Gets or sets the value of the ErrorAction parameter for the cmdlet. /// - /// /// /// This parameter tells the command what to do when an error occurs. /// @@ -118,13 +82,13 @@ public SwitchParameter Debug public ActionPreference ErrorAction { get { return _commandRuntime.ErrorAction; } + set { _commandRuntime.ErrorAction = value; } - } //ErrorAction + } /// /// Gets or sets the value of the WarningAction parameter for the cmdlet. /// - /// /// /// This parameter tells the command what to do when a warning /// occurs. @@ -134,33 +98,59 @@ public ActionPreference ErrorAction public ActionPreference WarningAction { get { return _commandRuntime.WarningPreference; } + set { _commandRuntime.WarningPreference = value; } - } //WarningAction + } /// /// Gets or sets the value of the InformationAction parameter for the cmdlet. /// - /// /// /// This parameter tells the command what to do when an informational record occurs. /// + /// [Parameter] [Alias("infa")] public ActionPreference InformationAction { get { return _commandRuntime.InformationPreference; } + set { _commandRuntime.InformationPreference = value; } - } //InformationAction + } + + /// + /// 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. /// - /// /// /// This parameter tells the command which variable to populate with the errors. /// Use +varname to append to the variable rather than clearing it. /// - /// /// public override int GetHashCode() @@ -880,6 +817,7 @@ public override int GetHashCode() } private PSNoteProperty _noteProperty; + internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) { if (_noteProperty == null) @@ -887,8 +825,8 @@ internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) Interlocked.CompareExchange(ref _noteProperty, new PSNoteProperty(name, this), null); } + return _noteProperty; } - }//Class PSDriveInfo + } } - diff --git a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs index 127e7798b6a..3ea2fd00fff 100644 --- a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs +++ b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs @@ -1,21 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Management.Automation.Provider; using System.Reflection; -using System.Linq; using System.Threading; + using Dbg = System.Management.Automation; -using System.Collections.Generic; namespace System.Management.Automation { /// - /// Information about a loaded Cmdlet Provider + /// Information about a loaded Cmdlet Provider. /// - /// /// /// A cmdlet provider may want to derive from this class to provide their /// own public members to expose to the user or to cache information related to the provider. @@ -30,13 +29,15 @@ public class ProviderInfo /// /// Gets the help file path for the provider. /// - public string HelpFile { get; } = ""; + public string HelpFile { get; } = string.Empty; /// /// The instance of session state the provider belongs to. /// - private SessionState _sessionState; + private readonly SessionState _sessionState; + private string _fullName; + private string _cachedModuleName; /// /// Gets the name of the provider. @@ -44,35 +45,46 @@ public class ProviderInfo public string Name { get; } /// - /// Gets the full name of the provider including the pssnapin name if available + /// Gets the full name of the provider including the module name if available. /// - /// internal string FullName { get { - string result = this.Name; - if (!String.IsNullOrEmpty(this.PSSnapInName)) + static string GetFullName(string name, string psSnapInName, string moduleName) { - result = - String.Format( - System.Globalization.CultureInfo.InvariantCulture, - "{0}\\{1}", - this.PSSnapInName, - this.Name); + string result = name; + if (!string.IsNullOrEmpty(psSnapInName)) + { + result = + string.Format( + System.Globalization.CultureInfo.InvariantCulture, + "{0}\\{1}", + psSnapInName, + name); + } + + // After converting core snapins to load as modules, the providers will have Module property populated + else if (!string.IsNullOrEmpty(moduleName)) + { + result = + string.Format( + System.Globalization.CultureInfo.InvariantCulture, + "{0}\\{1}", + moduleName, + name); + } + + return result; } - // After converting core snapins to load as modules, the providers will have Module property populated - else if (!string.IsNullOrEmpty(this.ModuleName)) + if (_fullName != null && ModuleName.Equals(_cachedModuleName, StringComparison.Ordinal)) { - result = - String.Format( - System.Globalization.CultureInfo.InvariantCulture, - "{0}\\{1}", - this.ModuleName, - this.Name); + return _fullName; } - return result; + + _cachedModuleName = ModuleName; + return _fullName = GetFullName(Name, PSSnapInName, ModuleName); } } @@ -84,7 +96,6 @@ internal string FullName /// /// Gets the pssnapin name that the provider is implemented in. /// - /// internal string PSSnapInName { get @@ -94,6 +105,7 @@ internal string PSSnapInName { result = PSSnapIn.Name; } + return result; } } @@ -111,6 +123,7 @@ internal string ApplicationBase { psHome = null; } + return psHome; } } @@ -126,7 +139,7 @@ public string ModuleName return PSSnapIn.Name; if (Module != null) return Module.Name; - return String.Empty; + return string.Empty; } } @@ -138,12 +151,13 @@ public string ModuleName internal void SetModule(PSModuleInfo module) { Module = module; + _fullName = null; } /// - /// Gets or sets the description for the provider + /// Gets or sets the description for the provider. /// - public String Description { get; set; } + public string Description { get; set; } /// /// Gets the capabilities that are implemented by the provider. @@ -174,21 +188,22 @@ public Provider.ProviderCapabilities Capabilities // Assume no capabilities for now } } + return _capabilities; - } // get - } // Capabilities + } + } + private ProviderCapabilities _capabilities = ProviderCapabilities.None; private bool _capabilitiesRead; /// /// Gets or sets the home for the provider. /// - /// /// /// 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; } // Home + public string Home { get; set; } /// /// Gets an enumeration of drives that are available for @@ -199,32 +214,30 @@ public Collection Drives get { return _sessionState.Drive.GetAllForProvider(FullName); - } // get - } // 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 /// for setting a location to a provider-qualified path. /// - /// internal PSDriveInfo HiddenDrive { get { return _hiddenDrive; - } // get - } // HiddenDrive + } + } /// /// Gets the string representation of the instance which is the name of the provider. /// - /// /// /// The name of the provider. If single-shell, the name is pssnapin-qualified. If custom-shell, /// the name is just the provider name. @@ -250,26 +263,33 @@ public override string ToString() /// are separated by a colon or not. /// /// This is true for all PSDrives on all platforms, except for filesystems on - /// non-windows platforms + /// non-windows platforms. /// public bool VolumeSeparatedByColon { get; internal set; } = true; + /// + /// Gets the default item separator character for this provider. + /// + public char ItemSeparator { get; private set; } + + /// + /// Gets the alternate item separator character for this provider. + /// + public char AltItemSeparator { get; private set; } + /// /// Constructs an instance of the class using an existing reference /// as a template. /// - /// /// /// The provider information to copy to this instance. /// - /// /// /// This constructor should be used by derived types to easily copying /// the base class members from an existing ProviderInfo. /// This is designed for use by a /// during calls to their method. /// - /// /// /// If is null. /// @@ -277,7 +297,7 @@ protected ProviderInfo(ProviderInfo providerInfo) { if (providerInfo == null) { - throw PSTraceSource.NewArgumentNullException("providerInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInfo)); } Name = providerInfo.Name; @@ -290,96 +310,78 @@ protected ProviderInfo(ProviderInfo providerInfo) PSSnapIn = providerInfo.PSSnapIn; _sessionState = providerInfo._sessionState; VolumeSeparatedByColon = providerInfo.VolumeSeparatedByColon; + ItemSeparator = providerInfo.ItemSeparator; + AltItemSeparator = providerInfo.AltItemSeparator; } /// /// Constructor for the ProviderInfo class. /// - /// /// /// The instance of session state that the provider is being added to. /// - /// /// /// The type that implements the provider /// - /// /// /// The name of the provider. /// - /// /// /// The help file for the provider. /// - /// /// /// The Snap-In name for the provider. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If is null. /// - /// internal ProviderInfo( SessionState sessionState, Type implementingType, string name, string helpFile, PSSnapInInfo psSnapIn) - : this(sessionState, implementingType, name, String.Empty, String.Empty, helpFile, psSnapIn) + : this(sessionState, implementingType, name, string.Empty, string.Empty, helpFile, psSnapIn) { } - /// /// Constructor for the ProviderInfo class. /// - /// /// /// The instance of session state that the provider is being added to. /// - /// /// /// The type that implements the provider /// - /// /// /// The alternate name to use for the provider instead of the one specified /// in the .cmdletprovider file. /// - /// /// /// 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. /// - /// /// /// The Snap-In for the provider. /// - /// /// /// If or is null. /// - /// /// /// If is null or empty. /// - /// internal ProviderInfo( SessionState sessionState, Type implementingType, @@ -392,17 +394,17 @@ internal ProviderInfo( // Verify parameters if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } if (implementingType == null) { - throw PSTraceSource.NewArgumentNullException("implementingType"); + throw PSTraceSource.NewArgumentNullException(nameof(implementingType)); } - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } _sessionState = sessionState; @@ -421,8 +423,8 @@ internal ProviderInfo( new PSDriveInfo( this.FullName, this, - "", - "", + string.Empty, + string.Empty, null); _hiddenDrive.Hidden = true; @@ -439,15 +441,12 @@ internal ProviderInfo( /// Determines if the passed in name is either the fully-qualified pssnapin name or /// short name of the provider. /// - /// /// /// The name to compare with the provider name. /// - /// /// /// True if the name is the fully-qualified pssnapin name or the short name of the provider. /// - /// internal bool NameEquals(string providerName) { PSSnapinQualifiedName qualifiedProviderName = PSSnapinQualifiedName.GetInstance(providerName); @@ -458,24 +457,25 @@ internal bool NameEquals(string providerName) // If the pssnapin name and provider name are specified, then both must match do // false loop { - if (!String.IsNullOrEmpty(qualifiedProviderName.PSSnapInName)) + if (!string.IsNullOrEmpty(qualifiedProviderName.PSSnapInName)) { // After converting core snapins to load as modules, the providers will have Module property populated - if (!String.Equals(qualifiedProviderName.PSSnapInName, this.PSSnapInName, StringComparison.OrdinalIgnoreCase) && - !String.Equals(qualifiedProviderName.PSSnapInName, this.ModuleName, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(qualifiedProviderName.PSSnapInName, this.PSSnapInName, StringComparison.OrdinalIgnoreCase) && + !string.Equals(qualifiedProviderName.PSSnapInName, this.ModuleName, StringComparison.OrdinalIgnoreCase)) { break; } } - result = String.Equals(qualifiedProviderName.ShortName, this.Name, StringComparison.OrdinalIgnoreCase); + result = string.Equals(qualifiedProviderName.ShortName, this.Name, StringComparison.OrdinalIgnoreCase); } while (false); } else { // If only the provider name is specified, then only the name must match - result = String.Equals(providerName, Name, StringComparison.OrdinalIgnoreCase); + result = string.Equals(providerName, Name, StringComparison.OrdinalIgnoreCase); } + return result; } @@ -505,7 +505,7 @@ internal bool IsMatch(WildcardPattern namePattern, PSSnapinQualifiedName psSnapi { if (namePattern == null) { - if (String.Equals(Name, psSnapinQualifiedName.ShortName, StringComparison.OrdinalIgnoreCase) && + if (string.Equals(Name, psSnapinQualifiedName.ShortName, StringComparison.OrdinalIgnoreCase) && IsPSSnapinNameMatch(psSnapinQualifiedName)) { result = true; @@ -516,6 +516,7 @@ internal bool IsMatch(WildcardPattern namePattern, PSSnapinQualifiedName psSnapi result = true; } } + return result; } @@ -523,8 +524,8 @@ private bool IsPSSnapinNameMatch(PSSnapinQualifiedName psSnapinQualifiedName) { bool result = false; - if (String.IsNullOrEmpty(psSnapinQualifiedName.PSSnapInName) || - String.Equals(psSnapinQualifiedName.PSSnapInName, PSSnapInName, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(psSnapinQualifiedName.PSSnapInName) || + string.Equals(psSnapinQualifiedName.PSSnapInName, PSSnapInName, StringComparison.OrdinalIgnoreCase)) { result = true; } @@ -533,18 +534,15 @@ private bool IsPSSnapinNameMatch(PSSnapinQualifiedName psSnapinQualifiedName) } /// - /// Creates an instance of the provider + /// Creates an instance of the provider. /// - /// /// /// An instance of the provider or null if one could not be created. /// - /// /// /// If an instance of the provider could not be created because the /// type could not be found in the assembly. /// - /// internal Provider.CmdletProvider CreateInstance() { // It doesn't really seem that using thread local storage to store an @@ -623,10 +621,13 @@ internal Provider.CmdletProvider CreateInstance() "ProviderNotFoundInAssembly", SessionStateStrings.ProviderNotFoundInAssembly); } + throw e; } Provider.CmdletProvider result = providerInstance as Provider.CmdletProvider; + ItemSeparator = result.ItemSeparator; + AltItemSeparator = result.AltItemSeparator; Dbg.Diagnostics.Assert( result != null, @@ -650,12 +651,14 @@ internal void GetOutputTypes(string cmdletname, List listToAppend) { continue; } + List l; if (!_providerOutputType.TryGetValue(outputType.ProviderCmdlet, out l)) { l = new List(); _providerOutputType[outputType.ProviderCmdlet] = l; } + l.AddRange(outputType.Type); } } @@ -666,9 +669,11 @@ internal void GetOutputTypes(string cmdletname, List listToAppend) listToAppend.AddRange(cmdletOutputType); } } + private Dictionary> _providerOutputType; private PSNoteProperty _noteProperty; + internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) { if (_noteProperty == null) @@ -676,9 +681,8 @@ internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) Interlocked.CompareExchange(ref _noteProperty, new PSNoteProperty(name, this), null); } + return _noteProperty; } - } // class ProviderInfo -} // namespace System.Management.Automation - - + } +} diff --git a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs index 4e63b0f96b7..746d809d831 100644 --- a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs +++ b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs @@ -1,8 +1,7 @@ -#pragma warning disable 1634, 1691 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#pragma warning disable 1634, 1691 using System.Collections; using System.Collections.Generic; @@ -15,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("outputList"); + ArgumentNullException.ThrowIfNull(outputList); _output = outputList; } @@ -31,21 +29,21 @@ public DefaultCommandRuntime(List outputList) /// /// Return the instance of PSHost - null by default. /// - public PSHost Host { set; get; } + public PSHost Host { get; set; } #region Write /// /// Implementation of WriteDebug - just discards the input. /// - /// Text to write - public void WriteDebug(string text) {; } + /// Text to write. + public void WriteDebug(string text) { } /// /// Default implementation of WriteError - if the error record contains /// an exception then that exception will be thrown. If not, then an /// InvalidOperationException will be constructed and thrown. /// - /// Error record instance to process + /// Error record instance to process. public void WriteError(ErrorRecord errorRecord) { if (errorRecord.Exception != null) @@ -58,7 +56,7 @@ public void WriteError(ErrorRecord errorRecord) /// Default implementation of WriteObject - adds the object to the list /// passed to the objects constructor. /// - /// Object to write + /// Object to write. public void WriteObject(object sendToPipeline) { _output.Add(sendToPipeline); @@ -66,9 +64,9 @@ 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 + /// Object to write. /// If true, the collection is enumerated, otherwise /// it's written as a scalar. /// @@ -96,41 +94,41 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// - /// progress record to write. - public void WriteProgress(ProgressRecord progressRecord) {; } + /// Progress record to write. + public void WriteProgress(ProgressRecord progressRecord) { } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// - /// Source ID to write for - /// record to write. - public void WriteProgress(Int64 sourceId, ProgressRecord progressRecord) {; } + /// Source ID to write for. + /// Record to write. + public void WriteProgress(Int64 sourceId, ProgressRecord progressRecord) { } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// /// Text to write. - public void WriteVerbose(string text) {; } + public void WriteVerbose(string text) { } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// /// Text to write. - public void WriteWarning(string text) {; } + public void WriteWarning(string text) { } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// /// Text to write. - public void WriteCommandDetail(string text) {; } + public void WriteCommandDetail(string text) { } /// - /// Default implementation - just discards it's arguments + /// Default implementation - just discards it's arguments. /// /// Record to write. - public void WriteInformation(InformationRecord informationRecord) {; } + public void WriteInformation(InformationRecord informationRecord) { } #endregion Write @@ -138,64 +136,64 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) /// /// Default implementation - always returns true. /// - /// ignored - /// true + /// Ignored. + /// True. public bool ShouldProcess(string target) { return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// True. public bool ShouldProcess(string target, string action) { return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// Ignored. + /// True. public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) { return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// Ignored. + /// Ignored. + /// True. public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason) { shouldProcessReason = ShouldProcessReason.None; return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// True. public bool ShouldContinue(string query, string caption) { return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// Ignored. + /// Ignored. + /// True. public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) { return true; } /// /// Default implementation - always returns true. /// - /// ignored - /// ignored - /// ignored - /// ignored - /// ignored - /// true + /// Ignored. + /// Ignored. + /// Ignored. + /// Ignored. + /// Ignored. + /// True. public bool ShouldContinue(string query, string caption, bool hasSecurityImpact, ref bool yesToAll, ref bool noToAll) { return true; } #endregion Should @@ -208,7 +206,7 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// public PSTransactionContext CurrentPSTransaction { @@ -230,7 +228,8 @@ public PSTransactionContext CurrentPSTransaction /// does what the base implementation does anyway - rethrow the exception /// if it exists, otherwise throw an invalid operation exception. /// - /// The error record to throw + /// 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 ed40ef004a6..c93e47d8c1c 100644 --- a/src/System.Management.Automation/engine/DriveInterfaces.cs +++ b/src/System.Management.Automation/engine/DriveInterfaces.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -16,37 +16,33 @@ public sealed class DriveManagementIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private DriveManagementIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // DriveManagementIntrinsics private - + } /// - /// Constructs a Drive management facade + /// Constructs a Drive management facade. /// - /// /// /// The instance of session state that facade wraps. /// - /// /// /// If is null. /// - /// internal DriveManagementIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // DriveManagementIntrinsics internal + } #endregion Constructors @@ -55,7 +51,6 @@ internal DriveManagementIntrinsics(SessionStateInternal sessionState) /// /// Gets the drive information for the current working drive. /// - /// /// /// This property is readonly. To set the current drive use the /// SetLocation method. @@ -69,48 +64,40 @@ public PSDriveInfo Current "The only constructor for this class should always set the sessionState field"); return _sessionState.CurrentDrive; - } // get - } // Current + } + } #region New /// - /// Creates a new MSH drive in session state + /// Creates a new PSDrive in session state. /// - /// /// /// The drive to be created. /// - /// /// /// The ID of the scope to create the drive in. This may be one of the scope /// keywords like global or local, or it may be an numeric offset of the scope /// generation relative to the current scope. /// If the scopeID is null or empty the local scope is used. /// - /// /// /// The drive that was created. /// - /// /// /// If is null. /// - /// /// /// If the drive already exists, /// or /// If .Name contains one or more invalid characters; ~ / \\ . : /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// The provider for the could not be found. /// - /// /// /// If the provider threw an exception or returned null. /// @@ -123,49 +110,40 @@ public PSDriveInfo New(PSDriveInfo drive, string scope) // Parameter validation is done in the session state object return _sessionState.NewDrive(drive, scope); - } // New + } /// - /// Creates a new MSH drive in session state + /// Creates a new MSH drive in session state. /// - /// /// /// The drive to be created. /// - /// /// /// The ID of the scope to create the drive in. This may be one of the scope /// keywords like global or local, or it may be an numeric offset of the scope /// generation relative to the current scope. /// If the scopeID is null or empty the local scope is used. /// - /// /// /// The context under which this command is running. /// - /// /// /// Nothing. The drive that is created is written to the context. /// - /// /// /// If or is null. /// - /// /// /// If the drive already exists /// or /// If .Name contains one or more invalid characters; ~ / \\ . : /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// The provider for the could not be found. /// - /// /// /// If the provider threw an exception or returned null. /// @@ -181,34 +159,28 @@ internal void New( // Parameter validation is done in the session state object _sessionState.NewDrive(drive, scope, context); - } // New + } /// /// Gets an object that defines the additional parameters for the NewDrive implementation /// for a provider. /// - /// /// /// The provider ID for the drive that is being created. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the is not a DriveCmdletProvider. /// - /// /// /// If does not exist. /// - /// internal object NewDriveDynamicParameters( string providerId, CmdletProviderContext context) @@ -220,7 +192,7 @@ internal object NewDriveDynamicParameters( // Parameter validation is done in the session state object return _sessionState.NewDriveDynamicParameters(providerId, context); - } // NewDriveDynamicParameters + } #endregion New @@ -229,15 +201,12 @@ internal object NewDriveDynamicParameters( /// /// Removes the specified drive. /// - /// /// /// The name of the drive to be removed. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope to remove the drive from. This may be one of the scope /// keywords like global or local, or it may be an numeric offset of the scope @@ -253,31 +222,26 @@ public void Remove(string driveName, bool force, string scope) // Parameter validation is done in the session state object _sessionState.RemoveDrive(driveName, force, scope); - } // Remove + } /// /// Removes the specified drive. /// - /// /// /// The name of the drive to be removed. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope to remove the drive from. This may be one of the scope /// keywords like global or local, or it may be an numeric offset of the scope /// generation relative to the current scope. /// If the scopeID is null or empty the local scope is used. /// - /// /// /// The context under which this command is running. /// - /// internal void Remove( string driveName, bool force, @@ -291,7 +255,7 @@ internal void Remove( // Parameter validation is done in the session state object _sessionState.RemoveDrive(driveName, force, scope, context); - } // RemoveDrive + } #endregion Remove @@ -300,19 +264,15 @@ internal void Remove( /// /// Gets the drive information for the drive specified by name. /// - /// /// /// The name of the drive to get the drive information for. /// - /// /// /// The drive information that represents the drive of the specified name. /// - /// /// /// If is null. /// - /// /// /// If there is no drive with . /// @@ -325,36 +285,30 @@ public PSDriveInfo Get(string driveName) // Parameter validation is done in the session state object return _sessionState.GetDrive(driveName); - } // GetDrive + } /// /// Gets the drive information for the drive specified by name. /// - /// /// /// The name of the drive to get the drive information for. /// - /// /// /// The ID of the scope to get the drive from. This may be one of the scope /// keywords like global or local, or it may be an numeric offset of the scope /// generation relative to the current scope. /// If the scopeID is null or empty the local scope is used. /// - /// /// /// The drive information that represents the drive of the specified name. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. @@ -368,10 +322,10 @@ public PSDriveInfo GetAtScope(string driveName, string scope) // Parameter validation is done in the session state object return _sessionState.GetDrive(driveName, scope); - } // GetAtScope + } /// - /// Retrieves all the drives in the specified scope + /// Retrieves all the drives in the specified scope. /// public Collection GetAll() { @@ -380,22 +334,19 @@ public Collection GetAll() "The only constructor for this class should always set the sessionState field"); return _sessionState.Drives(null); - } // GetAll + } /// - /// Retrieves all the drives in the specified scope + /// Retrieves all the drives in the specified scope. /// - /// /// /// The scope to retrieve the drives from. If null, the /// drives in all the scopes will be returned. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. @@ -407,16 +358,14 @@ public Collection GetAllAtScope(string scope) "The only constructor for this class should always set the sessionState field"); return _sessionState.Drives(scope); - } // GetAllAtScope + } /// - /// Gets all the drives for the specified provider + /// Gets all the drives for the specified provider. /// - /// /// /// The name of the provider to get the drives for. /// - /// /// /// All the drives in all the scopes for the given provider. /// @@ -429,7 +378,7 @@ public Collection GetAllForProvider(string providerName) // Parameter validation is done in the session state object return _sessionState.GetDrivesForProvider(providerName); - } // GetAllForProvider + } #endregion GetDrive @@ -438,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 - } // DriveIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/DriveNames.cs b/src/System.Management.Automation/engine/DriveNames.cs index 4c0ec5e6eaf..2f737bbc53d 100644 --- a/src/System.Management.Automation/engine/DriveNames.cs +++ b/src/System.Management.Automation/engine/DriveNames.cs @@ -1,34 +1,37 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { /// /// Holds the strings used as the default drive names for all the - /// default providers + /// default providers. /// - /// internal static class DriveNames { /// - /// The default VariableProvider drive name + /// The default VariableProvider drive name. /// internal const string VariableDrive = "Variable"; /// - /// The default EnvironmentProvider drive name + /// The default EnvironmentProvider drive name. /// internal const string EnvironmentDrive = "Env"; /// - /// The default AliasProvider drive name + /// The default AliasProvider drive name. /// internal const string AliasDrive = "Alias"; /// - /// The default FunctionProvider drive name + /// The default FunctionProvider drive name. /// internal const string FunctionDrive = "Function"; + + /// + /// The Temp drive name. + /// + internal const string TempDrive = "Temp"; } } diff --git a/src/System.Management.Automation/engine/DscResourceInfo.cs b/src/System.Management.Automation/engine/DscResourceInfo.cs index 79ca58f1db2..12e81f5becf 100644 --- a/src/System.Management.Automation/engine/DscResourceInfo.cs +++ b/src/System.Management.Automation/engine/DscResourceInfo.cs @@ -1,8 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -10,38 +7,38 @@ namespace System.Management.Automation { /// - /// Enumerated values for DSC resource implementation type + /// Enumerated values for DSC resource implementation type. /// public enum ImplementedAsType { /// - /// DSC resource implementation type not known + /// DSC resource implementation type not known. /// None = 0, /// - /// DSC resource is implemented using PowerShell module + /// DSC resource is implemented using PowerShell module. /// PowerShell = 1, /// - /// DSC resource is implemented using a CIM provider + /// DSC resource is implemented using a CIM provider. /// Binary = 2, /// - /// DSC resource is a composite and implemented using configuration keyword + /// DSC resource is a composite and implemented using configuration keyword. /// Composite = 3 } /// - /// Contains a DSC resource information + /// Contains a DSC resource information. /// public class DscResourceInfo { /// - /// Initializes a new instance of the DscResourceInfo class + /// Initializes a new instance of the DscResourceInfo class. /// /// Name of the DscResource. /// FriendlyName of the DscResource. @@ -60,22 +57,22 @@ 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 + /// Gets or sets resource type name. /// public string ResourceType { get; set; } /// - /// Gets or sets friendly name defined for the resource + /// Gets or sets friendly name defined for the resource. /// public string FriendlyName { get; set; } /// /// Gets or sets of the file which implements the resource. For the resources which are defined using /// MOF file, this will be path to a module which resides in the same folder where schema.mof file is present. - /// For composite resources, this will be the module which implements the resource + /// For composite resources, this will be the module which implements the resource. /// public string Path { get; set; } @@ -87,24 +84,24 @@ internal DscResourceInfo(string name, string friendlyName, string path, string p public string ParentPath { get; set; } /// - /// Gets or sets a value which indicate how DSC resource is implemented + /// Gets or sets a value which indicate how DSC resource is implemented. /// public ImplementedAsType ImplementedAs { get; set; } /// - /// Gets or sets company which owns this resource + /// Gets or sets company which owns this resource. /// public string CompanyName { get; set; } /// - /// Gets or sets properties of the resource + /// Gets or sets properties of the resource. /// public ReadOnlyCollection Properties { get; private set; } /// - /// Updates properties of the resource + /// Updates properties of the resource. /// - /// Updated properties + /// Updated properties. public void UpdateProperties(IList properties) { if (properties != null) @@ -119,19 +116,18 @@ public void UpdateProperties(IList properties) /// /// Gets the help file path for the cmdlet. /// - public string HelpFile { get; internal set; } = String.Empty; + public string HelpFile { get; internal set; } = string.Empty; -// HelpFile + // HelpFile } - /// - /// Contains a DSC resource property information + /// Contains a DSC resource property information. /// public sealed class DscResourcePropertyInfo { /// - /// Initializes a new instance of the DscResourcePropertyInfo class + /// Initializes a new instance of the DscResourcePropertyInfo class. /// internal DscResourcePropertyInfo() { @@ -139,22 +135,22 @@ internal DscResourcePropertyInfo() } /// - /// Gets or sets name of the property + /// Gets or sets name of the property. /// public string Name { get; set; } /// - /// Gets or sets type of the property + /// Gets or sets type of the property. /// public string PropertyType { get; set; } /// - /// Gets or sets a value indicating whether the property is mandatory or not + /// Gets or sets a value indicating whether the property is mandatory or not. /// public bool IsMandatory { get; set; } /// - /// Gets Values for a resource property + /// Gets Values for a resource property. /// public ReadOnlyCollection Values { get; private set; } @@ -164,4 +160,4 @@ internal void UpdateValues(IList values) this.Values = new ReadOnlyCollection(values); } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/DscResourceSearcher.cs b/src/System.Management.Automation/engine/DscResourceSearcher.cs index 87e5106e3ae..bfd406f56ff 100644 --- a/src/System.Management.Automation/engine/DscResourceSearcher.cs +++ b/src/System.Management.Automation/engine/DscResourceSearcher.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -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; @@ -65,7 +65,7 @@ IEnumerator IEnumerable.GetEnumerator() } /// - /// Get the Enumerator + /// Get the Enumerator. /// /// IEnumerator IEnumerable.GetEnumerator() @@ -88,7 +88,7 @@ public bool MoveNext() } /// - /// Return the current DscResource + /// Return the current DscResource. /// DscResourceInfo IEnumerator.Current { @@ -99,7 +99,7 @@ DscResourceInfo IEnumerator.Current } /// - /// Return the current DscResource as object + /// Return the current DscResource as object. /// object IEnumerator.Current { @@ -147,7 +147,6 @@ private DscResourceInfo GetNextDscResource() _context ); - resourceInfo.FriendlyName = resource.FriendlyName; resourceInfo.CompanyName = resource.CompanyName; @@ -186,15 +185,15 @@ private DscResourceInfo GetNextDscResource() _matchingResourceList.Add(resourceInfo); matchFound = true; - } //if - }//if - }// foreach + } + } + } if (matchFound) _matchingResource = _matchingResourceList.GetEnumerator(); else return null; - }//if + } if (!_matchingResource.MoveNext()) { diff --git a/src/System.Management.Automation/engine/EngineIntrinsics.cs b/src/System.Management.Automation/engine/EngineIntrinsics.cs index 1b4d2738cbc..e2a63a527a9 100644 --- a/src/System.Management.Automation/engine/EngineIntrinsics.cs +++ b/src/System.Management.Automation/engine/EngineIntrinsics.cs @@ -1,21 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Host; + using Dbg = System.Management.Automation; namespace System.Management.Automation { /// - /// Exposes the Engine APIs for a particular instance of the engine + /// Exposes the Engine APIs for a particular instance of the engine. /// public class EngineIntrinsics { #region Constructors /// - /// Hide the default constructor since we always require an instance of ExecutionContext + /// Hide the default constructor since we always require an instance of ExecutionContext. /// private EngineIntrinsics() { @@ -27,21 +27,15 @@ private EngineIntrinsics() /// /// The internal constructor for this object. It should be the only one that gets called. /// - /// /// /// An instance of ExecutionContext that the APIs should work against. /// - /// /// /// If is null. /// - /// internal EngineIntrinsics(ExecutionContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } + ArgumentNullException.ThrowIfNull(context); _context = context; _host = context.EngineHostInterface; @@ -52,7 +46,7 @@ internal EngineIntrinsics(ExecutionContext context) #region Public methods /// - /// Gets engine APIs to access the host + /// Gets engine APIs to access the host. /// public PSHost Host { @@ -63,22 +57,22 @@ public PSHost Host "The only constructor for this class should always set the host field"); return _host; - } // get - } // Host + } + } /// - /// Gets engine APIs to access the event manager + /// Gets engine APIs to access the event manager. /// public PSEventManager Events { get { return _context.Events; - } // get - } // Host + } + } /// - /// Gets the engine APIs to access providers + /// Gets the engine APIs to access providers. /// public ProviderIntrinsics InvokeProvider { @@ -86,10 +80,10 @@ public ProviderIntrinsics InvokeProvider { return _context.EngineSessionState.InvokeProvider; } - } // InvokeProvider + } /// - /// Gets the engine APIs to access session state + /// Gets the engine APIs to access session state. /// public SessionState SessionState { @@ -97,24 +91,23 @@ public SessionState SessionState { return _context.EngineSessionState.PublicSessionState; } - } // SessionState + } /// - /// Gets the engine APIs to invoke a command + /// Gets the engine APIs to invoke a command. /// 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 - } // EngineIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs b/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs index 0a5e5e9f288..056372ac63e 100644 --- a/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs +++ b/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using System.Text; -using System.Reflection; using System.Diagnostics; -using Dbg = System.Management.Automation; using System.Globalization; +using System.Reflection; +using System.Text; + +using Dbg = System.Management.Automation; namespace System.Management.Automation { @@ -27,7 +27,7 @@ public sealed class FlagsExpression where T : struct, IConvertible /// public FlagsExpression(string expression) { - if (!typeof(T).GetTypeInfo().IsEnum) + if (!typeof(T).IsEnum) { throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException), null, "InvalidGenericType", EnumExpressionEvaluatorStrings.InvalidGenericType); @@ -35,7 +35,7 @@ public FlagsExpression(string expression) _underType = Enum.GetUnderlyingType(typeof(T)); - if (String.IsNullOrWhiteSpace(expression)) + if (string.IsNullOrWhiteSpace(expression)) { throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException), null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString); @@ -59,7 +59,7 @@ public FlagsExpression(string expression) /// public FlagsExpression(object[] expression) { - if (!typeof(T).GetTypeInfo().IsEnum) + if (!typeof(T).IsEnum) { throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException), null, "InvalidGenericType", EnumExpressionEvaluatorStrings.InvalidGenericType); @@ -67,7 +67,7 @@ public FlagsExpression(object[] expression) _underType = Enum.GetUnderlyingType(typeof(T)); - if (null == expression) + if (expression == null) { throw InterpreterError.NewInterpreterException(null, typeof(ArgumentNullException), null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString); @@ -75,7 +75,7 @@ public FlagsExpression(object[] expression) foreach (string inputClause in expression) { - if (String.IsNullOrWhiteSpace(inputClause)) + if (string.IsNullOrWhiteSpace(inputClause)) { throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException), null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString); @@ -131,7 +131,7 @@ internal Token(TokenKind kind) Text = "NOT"; break; default: - Debug.Assert(false, "Invalid token kind passed in."); + Debug.Fail("Invalid token kind passed in."); break; } } @@ -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); } @@ -244,6 +245,7 @@ public object OperandValue { return _operandValue; } + set { _operandValue = value; @@ -281,6 +283,7 @@ internal override bool Eval(object val) long operandValue = (long)LanguagePrimitives.ConvertTo(_operandValue, typeof(long), CultureInfo.InvariantCulture); satisfy = (operandValue == (valueToCheck & operandValue)); } + return satisfy; } @@ -302,10 +305,11 @@ internal override bool ExistEnum(object enumVal) long operandValue = (long)LanguagePrimitives.ConvertTo(_operandValue, typeof(long), CultureInfo.InvariantCulture); exist = valueToCheck == (valueToCheck & operandValue); } + 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)); } @@ -315,7 +319,7 @@ private bool isUnsigned(Type type) #region private members - private Type _underType = null; + private readonly Type _underType = null; #endregion @@ -382,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; @@ -395,6 +399,7 @@ private List TokenizeInput(string input) tokenList.Add(GetNextToken(input, ref _offset)); } } + return tokenList; } @@ -407,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) { @@ -434,11 +439,11 @@ 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; - //bool doubleQuoted = false; + // bool singleQuoted = false; + // bool doubleQuoted = false; bool readingIdentifier = false; while (_offset < input.Length) { @@ -453,6 +458,7 @@ private Token GetNextToken(string input, ref int _offset) { _offset--; } + break; } else @@ -460,7 +466,7 @@ private Token GetNextToken(string input, ref int _offset) sb.Append(cc); readingIdentifier = true; } - }//while + } string result = sb.ToString().Trim(); // If resulting identifier is enclosed in paired quotes, @@ -475,7 +481,7 @@ private Token GetNextToken(string input, ref int _offset) result = result.Trim(); // possible empty token because white spaces are enclosed in quotation marks. - if (String.IsNullOrWhiteSpace(result)) + if (string.IsNullOrWhiteSpace(result)) { throw InterpreterError.NewInterpreterException(input, typeof(RuntimeException), null, "EmptyTokenString", EnumExpressionEvaluatorStrings.EmptyTokenString, @@ -490,6 +496,7 @@ private Token GetNextToken(string input, ref int _offset) null, "NoIdentifierGroupingAllowed", EnumExpressionEvaluatorStrings.NoIdentifierGroupingAllowed); } } + if (result.Equals(",")) { return (new Token(TokenKind.Or)); @@ -515,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; @@ -559,6 +566,7 @@ private void CheckSyntaxError(List tokenList) string text = token.Text; token.Text = EnumMinimumDisambiguation.EnumDisambiguate(text, typeof(T)); } + previous = token.Kind; } } @@ -569,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(); @@ -600,7 +608,7 @@ private Node ConstructExpressionTree(List tokenList) } else if (kind == TokenKind.And) { - ; // do nothing + // do nothing } else if (kind == TokenKind.Or) { @@ -613,9 +621,10 @@ private Node ConstructExpressionTree(List tokenList) andNode.Operand1 = andQueue.Dequeue(); andCurrent = andNode; } + orQueue.Enqueue(andCurrent); } - }//foreach + } // Dequeue all nodes from OR queue, // create the OR tree (final expression tree) @@ -626,6 +635,7 @@ private Node ConstructExpressionTree(List tokenList) orNode.Operand1 = orQueue.Dequeue(); orCurrent = orNode; } + return orCurrent; } diff --git a/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs b/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs index 3f9d88fc87e..5c166c69a63 100644 --- a/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs +++ b/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Text; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -31,9 +31,9 @@ static EnumMinimumDisambiguation() #endregion /// - /// Perform disambiguation on enum names + /// Perform disambiguation on enum names. /// - /// complete enum name after disambiguation + /// Complete enum name after disambiguation. internal static string EnumDisambiguate(string text, Type enumType) { // Get all enum names in the given enum type @@ -81,16 +81,17 @@ internal static string EnumDisambiguate(string text, Type enumType) { return tName; } - }//foreach - }//if + } + } // 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); matchListSB.Append(namesWithMatchingPrefix[i]); } + throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, "MultipleEnumNameMatch", EnumExpressionEvaluatorStrings.MultipleEnumNameMatch, text, matchListSB.ToString()); @@ -105,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) { @@ -114,11 +115,13 @@ internal static string EnumAllValues(Type enumType) returnValue.Append(names[i]); returnValue.Append(separator); } + returnValue.Remove(returnValue.Length - separator.Length, separator.Length); } + 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 44c275e6886..6dfaf34fbec 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 @@ -19,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 @@ -29,72 +28,62 @@ 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, /// - /// /// OpenError = 1, /// - /// /// CloseError = 2, /// - /// /// DeviceError = 3, /// - /// /// DeadlockDetected = 4, /// - /// /// InvalidArgument = 5, /// - /// /// InvalidData = 6, /// - /// /// InvalidOperation = 7, /// - /// /// InvalidResult = 8, /// - /// /// InvalidType = 9, /// - /// /// MetadataError = 10, /// - /// /// NotImplemented = 11, /// - /// /// NotInstalled = 12, @@ -104,68 +93,61 @@ public enum ErrorCategory ObjectNotFound = 13, /// - /// /// OperationStopped = 14, /// - /// /// OperationTimeout = 15, /// - /// /// SyntaxError = 16, /// - /// /// ParserError = 17, /// - /// Operation not permitted + /// Operation not permitted. /// PermissionDenied = 18, /// - /// /// ResourceBusy = 19, /// - /// /// ResourceExists = 20, /// - /// /// ResourceUnavailable = 21, /// - /// /// ReadError = 22, /// - /// /// 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, /// - /// Used for security exceptions + /// Used for security exceptions. /// SecurityError = 25, @@ -204,7 +186,7 @@ public enum ErrorCategory /// disabled. /// NotEnabled = 31, - } // enum ErrorCategory + } /// /// Contains auxiliary information about an @@ -215,8 +197,8 @@ public class ErrorCategoryInfo #region ctor internal ErrorCategoryInfo(ErrorRecord errorRecord) { - if (null == errorRecord) - throw new ArgumentNullException("errorRecord"); + ArgumentNullException.ThrowIfNull(errorRecord); + _errorRecord = errorRecord; } #endregion ctor @@ -225,15 +207,14 @@ internal ErrorCategoryInfo(ErrorRecord errorRecord) /// /// /// for this error - /// public ErrorCategory Category { get { return _errorRecord._category; } } /// - /// text description of the operation which - /// encountered the error + /// Text description of the operation which + /// encountered the error. /// /// text description of the operation /// @@ -245,18 +226,22 @@ public string Activity { get { - if (!String.IsNullOrEmpty(_errorRecord._activityOverride)) + if (!string.IsNullOrEmpty(_errorRecord._activityOverride)) + { return _errorRecord._activityOverride; + } - if (null != _errorRecord.InvocationInfo + if (_errorRecord.InvocationInfo != null && (_errorRecord.InvocationInfo.MyCommand is CmdletInfo || _errorRecord.InvocationInfo.MyCommand is IScriptCommandInfo) - && !String.IsNullOrEmpty(_errorRecord.InvocationInfo.MyCommand.Name) + && !string.IsNullOrEmpty(_errorRecord.InvocationInfo.MyCommand.Name) ) { return _errorRecord.InvocationInfo.MyCommand.Name; } - return ""; + + return string.Empty; } + set { _errorRecord._activityOverride = value; @@ -264,7 +249,7 @@ public string Activity } /// - /// text description of the error + /// Text description of the error. /// /// text description of the error /// @@ -277,15 +262,20 @@ public string Reason get { _reasonIsExceptionType = false; - if (!String.IsNullOrEmpty(_errorRecord._reasonOverride)) + if (!string.IsNullOrEmpty(_errorRecord._reasonOverride)) + { return _errorRecord._reasonOverride; - if (null != _errorRecord.Exception) + } + + if (_errorRecord.Exception != null) { _reasonIsExceptionType = true; return _errorRecord.Exception.GetType().Name; } - return ""; + + return string.Empty; } + set { _errorRecord._reasonOverride = value; @@ -295,7 +285,7 @@ public string Reason private bool _reasonIsExceptionType; /// - /// text description of the target object + /// Text description of the target object. /// /// text description of the target object /// @@ -308,9 +298,12 @@ public string TargetName { get { - if (!String.IsNullOrEmpty(_errorRecord._targetNameOverride)) + if (!string.IsNullOrEmpty(_errorRecord._targetNameOverride)) + { return _errorRecord._targetNameOverride; - if (null != _errorRecord.TargetObject) + } + + if (_errorRecord.TargetObject != null) { string targetInString; try @@ -324,8 +317,10 @@ public string TargetName return ErrorRecord.NotNull(targetInString); } - return ""; + + return string.Empty; } + set { _errorRecord._targetNameOverride = value; @@ -333,7 +328,7 @@ public string TargetName } /// - /// text description of the type of the target object + /// Text description of the type of the target object. /// /// text description of the type of the target object /// @@ -346,14 +341,19 @@ public string TargetType { get { - if (!String.IsNullOrEmpty(_errorRecord._targetTypeOverride)) + if (!string.IsNullOrEmpty(_errorRecord._targetTypeOverride)) + { return _errorRecord._targetTypeOverride; - if (null != _errorRecord.TargetObject) + } + + if (_errorRecord.TargetObject != null) { return _errorRecord.TargetObject.GetType().Name; } - return ""; + + return string.Empty; } + set { _errorRecord._targetTypeOverride = value; @@ -364,10 +364,10 @@ public string TargetType #region Methods /// - /// concise text description based on + /// Concise text description based on /// /// - /// concise text description + /// Concise text description. /// /// GetMessage returns a concise string which categorizes the error, /// based on @@ -386,7 +386,7 @@ public string TargetType public string GetMessage() { /* Remoting not in E12 - if (!String.IsNullOrEmpty (_errorRecord._serializedErrorCategoryMessageOverride)) + if (!string.IsNullOrEmpty (_errorRecord._serializedErrorCategoryMessageOverride)) return _errorRecord._serializedErrorCategoryMessageOverride; */ @@ -394,11 +394,11 @@ public string GetMessage() } /// - /// concise text description based on + /// Concise text description based on /// /// - /// Culture in which to display message - /// concise text description + /// Culture in which to display message. + /// Concise text description. /// /// GetMessage returns a concise string which categorizes the error, /// based on @@ -418,19 +418,21 @@ public string GetMessage(CultureInfo uiCultureInfo) { // get template text string errorCategoryString = Category.ToString(); - if (String.IsNullOrEmpty(errorCategoryString)) + if (string.IsNullOrEmpty(errorCategoryString)) { // this probably indicates an invalid ErrorCategory value - errorCategoryString = ErrorCategory.NotSpecified.ToString(); + errorCategoryString = nameof(ErrorCategory.NotSpecified); } + string templateText = ErrorCategoryStrings.ResourceManager.GetString(errorCategoryString, uiCultureInfo); - if (String.IsNullOrEmpty(templateText)) + if (string.IsNullOrEmpty(templateText)) { // this probably indicates an invalid ErrorCategory value templateText = ErrorCategoryStrings.NotSpecified; } - Diagnostics.Assert(!String.IsNullOrEmpty(templateText), + + Diagnostics.Assert(!string.IsNullOrEmpty(templateText), "ErrorCategoryStrings.resx resource failure"); string activityInUse = Ellipsize(uiCultureInfo, Activity); @@ -443,7 +445,7 @@ public string GetMessage(CultureInfo uiCultureInfo) // assemble final string try { - return String.Format(uiCultureInfo, templateText, + return string.Format(uiCultureInfo, templateText, activityInUse, targetNameInUse, targetTypeInUse, @@ -454,7 +456,7 @@ public string GetMessage(CultureInfo uiCultureInfo) { templateText = ErrorCategoryStrings.InvalidErrorCategory; - return String.Format(uiCultureInfo, templateText, + return string.Format(uiCultureInfo, templateText, activityInUse, targetNameInUse, targetTypeInUse, @@ -467,7 +469,7 @@ public string GetMessage(CultureInfo uiCultureInfo) /// Same as /// /// - /// developer-readable identifier + /// Developer-readable identifier. public override string ToString() { return GetMessage(CultureInfo.CurrentUICulture); @@ -476,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 @@ -484,30 +486,34 @@ public override string ToString() /// control the maximum length of the GetMessage() string, we /// ellipsize these strings. The current heuristic is to take /// strings longer than 40 characters and ellipsize them to - /// the first and last 15 characters plus "..." in the middle. + /// the first and last 19 characters plus "..." in the middle. /// - /// culture to retrieve template if needed - /// original string - /// Ellipsized version of string + /// Culture to retrieve template if needed. + /// Original string. + /// Ellipsized version of string. /// /// "Please do not make this public as ellipsize is not a word." /// internal static string Ellipsize(CultureInfo uiCultureInfo, string original) { - if (40 >= original.Length) + if (original.Length <= 40) { return original; } - string first = original.Substring(0, 15); - string last = original.Substring(original.Length - 15, 15); + + // We are splitting a string > 40 chars in half, so left and right can be + // at most 19 characters to include the ellipsis in the middle. + const int MaxHalfWidth = 19; + string first = original.Substring(0, MaxHalfWidth); + string last = original.Substring(original.Length - MaxHalfWidth, MaxHalfWidth); return string.Format(uiCultureInfo, ErrorPackage.Ellipsize, first, last); } #endregion Private - } // class ErrorCategoryInfo + } /// - /// additional details about an + /// Additional details about an /// /// /// @@ -521,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 @@ -548,7 +553,7 @@ public ErrorDetails(string message) /// Creates an instance of ErrorDetails specifying a Message. /// This variant is used by cmdlets. /// - /// cmdlet containing the template string + /// Cmdlet containing the template string. /// by default, the /// /// name @@ -557,7 +562,7 @@ public ErrorDetails(string message) /// /// /// - /// + /// /// insertion parameters /// /// @@ -579,7 +584,7 @@ public ErrorDetails(string message) /// by overriding virtual method /// . /// This constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( Cmdlet cmdlet, @@ -605,7 +610,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -632,7 +637,7 @@ public ErrorDetails( /// will implement /// . /// The constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( IResourceSupplier resourceSupplier, @@ -658,7 +663,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -673,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, @@ -699,9 +704,9 @@ internal ErrorDetails(ErrorDetails errorDetails) /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// Serialization information. + /// Streaming context. + /// Constructed object. protected ErrorDetails(SerializationInfo info, StreamingContext context) { @@ -713,9 +718,8 @@ protected ErrorDetails(SerializationInfo info, /// /// Serializer for /// - /// serialization information - /// streaming context - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] + /// Serialization information. + /// Streaming context. public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info != null) @@ -733,7 +737,6 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte /// in /// /// - /// /// /// When an instance of /// @@ -752,43 +755,50 @@ public string Message { get { return ErrorRecord.NotNull(_message); } } - private string _message = ""; + + private readonly string _message = string.Empty; /// /// Text describing the recommended action in the event that this error /// occurs. This is empty unless the code which generates the error /// specifies it explicitly. /// - /// /// /// This should be a grammatically correct localized text string. /// This may be left empty. /// public string RecommendedAction { - get { return ErrorRecord.NotNull(_recommendedAction); } + get + { + return ErrorRecord.NotNull(_recommendedAction); + } + set { _recommendedAction = value; } } - private string _recommendedAction = ""; + + private string _recommendedAction = string.Empty; #endregion Public Properties #region Internal Properties internal Exception TextLookupError { get { return _textLookupError; } + set { _textLookupError = value; } } + private Exception _textLookupError /* = null */; #endregion Internal Properties #region ToString /// - /// As + /// As /// - /// developer-readable identifier + /// Developer-readable identifier. public override string ToString() { return Message; @@ -802,16 +812,22 @@ private string BuildMessage( string resourceId, params object[] args) { - if (null == cmdlet) - throw PSTraceSource.NewArgumentNullException("cmdlet"); + if (cmdlet == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); + } - if (String.IsNullOrEmpty(baseName)) - throw PSTraceSource.NewArgumentNullException("baseName"); + if (string.IsNullOrEmpty(baseName)) + { + throw PSTraceSource.NewArgumentNullException(nameof(baseName)); + } - if (String.IsNullOrEmpty(resourceId)) - throw PSTraceSource.NewArgumentNullException("resourceId"); + if (string.IsNullOrEmpty(resourceId)) + { + throw PSTraceSource.NewArgumentNullException(nameof(resourceId)); + } - string template = ""; + string template = string.Empty; try { @@ -820,31 +836,39 @@ private string BuildMessage( catch (MissingManifestResourceException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } catch (ArgumentException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } + return BuildMessage(template, baseName, resourceId, args); - } // BuildMessage + } + private string BuildMessage( IResourceSupplier resourceSupplier, string baseName, string resourceId, params object[] args) { - if (null == resourceSupplier) - throw PSTraceSource.NewArgumentNullException("resourceSupplier"); + if (resourceSupplier == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(resourceSupplier)); + } - if (String.IsNullOrEmpty(baseName)) - throw PSTraceSource.NewArgumentNullException("baseName"); + if (string.IsNullOrEmpty(baseName)) + { + throw PSTraceSource.NewArgumentNullException(nameof(baseName)); + } - if (String.IsNullOrEmpty(resourceId)) - throw PSTraceSource.NewArgumentNullException("resourceId"); + if (string.IsNullOrEmpty(resourceId)) + { + throw PSTraceSource.NewArgumentNullException(nameof(resourceId)); + } - string template = ""; + string template = string.Empty; try { @@ -853,31 +877,39 @@ private string BuildMessage( catch (MissingManifestResourceException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } catch (ArgumentException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } + return BuildMessage(template, baseName, resourceId, args); - } // BuildMessage + } + private string BuildMessage( System.Reflection.Assembly assembly, string baseName, string resourceId, params object[] args) { - if (null == assembly) - throw PSTraceSource.NewArgumentNullException("assembly"); + if (assembly == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(assembly)); + } - if (String.IsNullOrEmpty(baseName)) - throw PSTraceSource.NewArgumentNullException("baseName"); + if (string.IsNullOrEmpty(baseName)) + { + throw PSTraceSource.NewArgumentNullException(nameof(baseName)); + } - if (String.IsNullOrEmpty(resourceId)) - throw PSTraceSource.NewArgumentNullException("resourceId"); + if (string.IsNullOrEmpty(resourceId)) + { + throw PSTraceSource.NewArgumentNullException(nameof(resourceId)); + } - string template = ""; + string template = string.Empty; ResourceManager manager = ResourceManagerCache.GetResourceManager( @@ -891,10 +923,12 @@ private string BuildMessage( catch (MissingManifestResourceException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } + return BuildMessage(template, baseName, resourceId, args); - } // BuildMessage + } + private string BuildMessage( string template, string baseName, @@ -907,12 +941,12 @@ private string BuildMessage( ErrorPackage.ErrorDetailsEmptyTemplate, baseName, resourceId); - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } try { - return String.Format( + return string.Format( CultureInfo.CurrentCulture, template, args); @@ -920,13 +954,12 @@ private string BuildMessage( catch (FormatException e) { _textLookupError = e; - return ""; // fallback to Exception.Message + return string.Empty; // fallback to Exception.Message } - } // BuildMessage + } #endregion Private - } // class ErrorDetails - + } /// /// Represents an error. @@ -952,7 +985,6 @@ private string BuildMessage( /// . /// rather than the actual exception, to avoid the mutual references. /// - [Serializable] public class ErrorRecord : ISerializable { #region Constructor @@ -988,14 +1020,14 @@ public ErrorRecord( ErrorCategory errorCategory, object targetObject) { - if (null == exception) - throw PSTraceSource.NewArgumentNullException("exception"); + if (exception == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(exception)); + } - if (errorId == null) - errorId = ""; + errorId ??= string.Empty; // targetObject may be null - _error = exception; _errorId = errorId; _category = errorCategory; @@ -1023,9 +1055,9 @@ public ErrorRecord( /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// Serialization information. + /// Streaming context. + /// Constructed object. /// /// ErrorRecord instances which are serialized using /// @@ -1041,9 +1073,8 @@ protected ErrorRecord(SerializationInfo info, /// /// Deserializer for /// - /// serialization information - /// streaming context - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] + /// Serialization information. + /// Streaming context. public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info != null) @@ -1058,26 +1089,17 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte } #endregion Serialization - - #region Remoting /// - /// isSerialized is set to true if this error record is serialized. + /// IsSerialized is set to true if this error record is serialized. /// private bool _isSerialized = false; /// /// Is this instance serialized. /// - /// - internal bool IsSerialized - { - get - { - return _isSerialized; - } - } + internal bool IsSerialized { get => _isSerialized; } /// /// Value for FullyQualifiedErrorId in case of serialized error record. @@ -1085,7 +1107,7 @@ internal bool IsSerialized private string _serializedFullyQualifiedErrorId = null; /// - /// Message overridee for CategoryInfo.GetMessage method + /// Message overridee for CategoryInfo.GetMessage method. /// internal string _serializedErrorCategoryMessageOverride = null; @@ -1104,8 +1126,7 @@ internal bool IsSerialized /// /// /// - internal ErrorRecord - ( + internal ErrorRecord( Exception exception, object targetObject, string fullyQualifiedErrorId, @@ -1116,15 +1137,16 @@ internal ErrorRecord string errorCategory_TargetType, string errorCategory_Message, string errorDetails_Message, - string errorDetails_RecommendedAction - ) + string errorDetails_RecommendedAction) { - PopulateProperties(exception, targetObject, fullyQualifiedErrorId, errorCategory, errorCategory_Activity, - errorCategory_Reason, errorCategory_TargetName, errorCategory_TargetType, - errorCategory_Message, errorDetails_Message, errorDetails_RecommendedAction, null); + PopulateProperties( + exception, targetObject, fullyQualifiedErrorId, errorCategory, errorCategory_Activity, + errorCategory_Reason, errorCategory_TargetName, errorCategory_TargetType, + errorCategory_Message, errorDetails_Message, errorDetails_RecommendedAction, null); } - private void PopulateProperties(Exception exception, + private void PopulateProperties( + Exception exception, object targetObject, string fullyQualifiedErrorId, ErrorCategory errorCategory, @@ -1139,14 +1161,15 @@ private void PopulateProperties(Exception exception, { if (exception == null) { - throw PSTraceSource.NewArgumentNullException("exception"); + throw PSTraceSource.NewArgumentNullException(nameof(exception)); } + if (fullyQualifiedErrorId == null) { - throw PSTraceSource.NewArgumentNullException("fullyQualifiedErrorId"); + throw PSTraceSource.NewArgumentNullException(nameof(fullyQualifiedErrorId)); } - //Mark this error record as serialized + // Mark this error record as serialized _isSerialized = true; _error = exception; _target = targetObject; @@ -1159,12 +1182,13 @@ private void PopulateProperties(Exception exception, _serializedErrorCategoryMessageOverride = errorCategory_Message; if (errorDetails_Message != null) { - _errorDetails = new ErrorDetails(errorDetails_Message); + ErrorDetails = new ErrorDetails(errorDetails_Message); if (errorDetails_RecommendedAction != null) { - _errorDetails.RecommendedAction = errorDetails_RecommendedAction; + ErrorDetails.RecommendedAction = errorDetails_RecommendedAction; } } + _scriptStackTrace = errorDetails_ScriptStackTrace; } @@ -1179,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) @@ -1204,40 +1228,32 @@ 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); } } /// - /// Gets the value for note from mshObject + /// Gets the value for note from mshObject. /// - /// /// /// PSObject from which value is fetched. /// - /// /// /// name of note whose value is fetched /// /// /// value of note /// - /// - private static object GetNoteValue - ( - PSObject mshObject, - string note - ) + private static object GetNoteValue(PSObject mshObject, string note) { - PSNoteProperty property = mshObject.Properties[note] as PSNoteProperty; - if (property != null) + if (mshObject.Properties[note] is PSNoteProperty p) { - return property.Value; + return p.Value; } else { @@ -1250,25 +1266,16 @@ string note /// serializedErrorRecord PSObject is in the format returned /// by ToPSObjectForRemoting method. /// - /// /// /// PSObject to convert to ErrorRecord /// - /// - /// /// /// ErrorRecord convert from mshObject. /// - /// - /// /// /// Thrown if mshObject parameter is null. /// - /// - internal static ErrorRecord FromPSObjectForRemoting - ( - PSObject serializedErrorRecord - ) + internal static ErrorRecord FromPSObjectForRemoting(PSObject serializedErrorRecord) { ErrorRecord er = new ErrorRecord(); er.ConstructFromPSObjectForRemoting(serializedErrorRecord); @@ -1279,13 +1286,13 @@ private void ConstructFromPSObjectForRemoting(PSObject serializedErrorRecord) { if (serializedErrorRecord == null) { - throw PSTraceSource.NewArgumentNullException("serializedErrorRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(serializedErrorRecord)); } - //Get Exception + // Get Exception PSObject serializedException = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "Exception"); - //Get Target object + // Get Target object object targetObject = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "TargetObject"); string exceptionMessage = null; @@ -1298,27 +1305,27 @@ private void ConstructFromPSObjectForRemoting(PSObject serializedErrorRecord) } } - //Get FullyQualifiedErrorId + // Get FullyQualifiedErrorId string fullyQualifiedErrorId = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "FullyQualifiedErrorId") ?? "fullyQualifiedErrorId"; - //Get ErrorCategory... + // Get ErrorCategory... ErrorCategory errorCategory = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "errorCategory_Category"); - //Get Various ErrorCategory fileds + // Get Various ErrorCategory fileds string errorCategory_Activity = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "ErrorCategory_Activity"); string errorCategory_Reason = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "ErrorCategory_Reason"); string errorCategory_TargetName = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "ErrorCategory_TargetName"); string errorCategory_TargetType = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "ErrorCategory_TargetType"); string errorCategory_Message = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "ErrorCategory_Message"); - //Get InvocationInfo (optional property) + // Get InvocationInfo (optional property) PSObject invocationInfo = Microsoft.PowerShell.DeserializingTypeConverter.GetPropertyValue( serializedErrorRecord, "InvocationInfo", Microsoft.PowerShell.DeserializingTypeConverter.RehydrationFlags.MissingPropertyOk); - //Get Error Detail (these note properties are optional, so can't right now use RemotingDecoder...) + // Get Error Detail (these note properties are optional, so can't right now use RemotingDecoder...) string errorDetails_Message = GetNoteValue(serializedErrorRecord, "ErrorDetails_Message") as string; @@ -1328,9 +1335,9 @@ 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 + // Create ErrorRecord PopulateProperties( re, targetObject, @@ -1358,7 +1365,7 @@ private void ConstructFromPSObjectForRemoting(PSObject serializedErrorRecord) _invocationInfo = new InvocationInfo(serializedErrorRecord); ArrayList iterationInfo = RemotingDecoder.GetPropertyValue(serializedErrorRecord, "PipelineIterationInfo"); - if (null != iterationInfo) + if (iterationInfo != null) { _pipelineIterationInfo = new ReadOnlyCollection((int[])iterationInfo.ToArray(typeof(Int32))); } @@ -1376,7 +1383,7 @@ private void ConstructFromPSObjectForRemoting(PSObject serializedErrorRecord) /// exception which already has an ErrorRecord /// ErrorCategoryInfo and ErrorDetails are deep-copied, other fields are not. /// - /// wrapped ErrorRecord + /// Wrapped ErrorRecord. /// /// If the wrapped exception contains a ParentContainsErrorRecordException, the new /// ErrorRecord should have this exception as its Exception instead. @@ -1386,10 +1393,10 @@ public ErrorRecord(ErrorRecord errorRecord, { if (errorRecord == null) { - throw new PSArgumentNullException("errorRecord"); + throw new PSArgumentNullException(nameof(errorRecord)); } - if (null != replaceParentContainsErrorRecordException + if (replaceParentContainsErrorRecordException != null && (errorRecord.Exception is ParentContainsErrorRecordException)) { _error = replaceParentContainsErrorRecordException; @@ -1398,6 +1405,7 @@ public ErrorRecord(ErrorRecord errorRecord, { _error = errorRecord.Exception; } + _target = errorRecord.TargetObject; _errorId = errorRecord._errorId; _category = errorRecord._category; @@ -1405,8 +1413,11 @@ public ErrorRecord(ErrorRecord errorRecord, _reasonOverride = errorRecord._reasonOverride; _targetNameOverride = errorRecord._targetNameOverride; _targetTypeOverride = errorRecord._targetTypeOverride; - if (null != errorRecord.ErrorDetails) - _errorDetails = new ErrorDetails(errorRecord.ErrorDetails); + if (errorRecord.ErrorDetails != null) + { + ErrorDetails = new ErrorDetails(errorRecord.ErrorDetails); + } + SetInvocationInfo(errorRecord._invocationInfo); _scriptStackTrace = errorRecord._scriptStackTrace; _serializedFullyQualifiedErrorId = errorRecord._serializedFullyQualifiedErrorId; @@ -1442,21 +1453,21 @@ public Exception Exception { get { - Diagnostics.Assert(null != _error, "_error is null"); + Diagnostics.Assert(_error != null, "_error is null"); return _error; } } + private Exception _error /* = null */; /// /// The object against which the error occurred. /// /// may be null - public object TargetObject - { - get { return _target; } - } + public object TargetObject { get => _target; } + private object _target /* = null */; + internal void SetTargetObject(object target) { _target = target; @@ -1468,14 +1479,12 @@ internal void SetTargetObject(object target) /// for that ErrorCategory. /// /// never null - public ErrorCategoryInfo CategoryInfo - { - get { return _categoryInfo ?? (_categoryInfo = new ErrorCategoryInfo(this)); } - } + public ErrorCategoryInfo CategoryInfo { get => _categoryInfo ??= new ErrorCategoryInfo(this); } + private ErrorCategoryInfo _categoryInfo; /// - /// String which uniquely identifies this error condition + /// String which uniquely identifies this error condition. /// /// never null /// @@ -1490,13 +1499,15 @@ public string FullyQualifiedErrorId get { if (_serializedFullyQualifiedErrorId != null) + { return _serializedFullyQualifiedErrorId; + } string typeName = GetInvocationTypeName(); string delimiter = - (String.IsNullOrEmpty(typeName) - || String.IsNullOrEmpty(_errorId)) - ? "" : ","; + (string.IsNullOrEmpty(typeName) || string.IsNullOrEmpty(_errorId)) + ? string.Empty + : ","; return NotNull(_errorId) + delimiter + NotNull(typeName); } } @@ -1510,22 +1521,15 @@ public string FullyQualifiedErrorId /// contains a replacement message which should be displayed instead of /// Exception.Message. /// - public ErrorDetails ErrorDetails - { - get { return _errorDetails; } - set { _errorDetails = value; } - } - private ErrorDetails _errorDetails; + public ErrorDetails ErrorDetails { get; set; } /// /// Identifies the cmdlet, script, or other command which caused /// the error. /// /// may be null - public InvocationInfo InvocationInfo - { - get { return _invocationInfo; } - } + public InvocationInfo InvocationInfo { get => _invocationInfo; } + private InvocationInfo _invocationInfo /* = null */; internal void SetInvocationInfo(InvocationInfo invocationInfo) @@ -1570,20 +1574,13 @@ internal void SetInvocationInfo(InvocationInfo invocationInfo) } // 2005/07/14-913791 "write-error output is confusing and misleading" - internal bool PreserveInvocationInfoOnce - { - get { return _preserveInvocationInfoOnce; } - set { _preserveInvocationInfoOnce = value; } - } - private bool _preserveInvocationInfoOnce /* = false */; + internal bool PreserveInvocationInfoOnce { get; set; } /// /// The script stack trace for the error. /// - public string ScriptStackTrace - { - get { return _scriptStackTrace; } - } + public string ScriptStackTrace { get => _scriptStackTrace; } + private string _scriptStackTrace; internal void LockScriptStackTrace() @@ -1605,6 +1602,7 @@ internal void LockScriptStackTrace() { sb.Append(Environment.NewLine); } + first = false; sb.Append(frame.ToString()); } @@ -1616,35 +1614,26 @@ internal void LockScriptStackTrace() /// /// The status of the pipeline when this record was created. /// - public ReadOnlyCollection PipelineIterationInfo - { - get - { - return _pipelineIterationInfo; - } - } + public ReadOnlyCollection PipelineIterationInfo { get => _pipelineIterationInfo; } + private ReadOnlyCollection _pipelineIterationInfo = Utils.EmptyReadOnlyCollection(); /// - /// Whether to serialize the InvocationInfo during remote calls + /// Whether to serialize the InvocationInfo during remote calls. /// internal bool SerializeExtendedInfo { - get - { - return _serializeExtendedInfo; - } - set - { - _serializeExtendedInfo = value; - } + get => _serializeExtendedInfo; + + set => _serializeExtendedInfo = value; } + private bool _serializeExtendedInfo = false; #endregion Public Properties #region Private - private string _errorId; + private readonly string _errorId; #region Exposed by ErrorCategoryInfo internal ErrorCategory _category; @@ -1654,25 +1643,33 @@ internal bool SerializeExtendedInfo internal string _targetTypeOverride; #endregion Exposed by ErrorCategoryInfo - internal static string NotNull(string s) - { - return s ?? ""; - } + internal static string NotNull(string s) => s ?? string.Empty; private string GetInvocationTypeName() { InvocationInfo invocationInfo = this.InvocationInfo; - if (null == invocationInfo) - return ""; + if (invocationInfo == null) + { + return string.Empty; + } + CommandInfo commandInfo = invocationInfo.MyCommand; - if (null == commandInfo) - return ""; + if (commandInfo == null) + { + return string.Empty; + } + IScriptCommandInfo scriptInfo = commandInfo as IScriptCommandInfo; if (scriptInfo != null) + { return commandInfo.Name; - CmdletInfo cmdletInfo = commandInfo as CmdletInfo; - if (null == cmdletInfo) - return ""; + } + + if (commandInfo is not CmdletInfo cmdletInfo) + { + return string.Empty; + } + return cmdletInfo.ImplementingType.FullName; } @@ -1680,29 +1677,39 @@ private string GetInvocationTypeName() #region ToString /// - /// As + /// As /// - /// developer-readable identifier + /// Developer-readable identifier. public override string ToString() { - if (null != ErrorDetails - && !String.IsNullOrEmpty(ErrorDetails.Message)) + if (ErrorDetails != null && !string.IsNullOrEmpty(ErrorDetails.Message)) { return ErrorDetails.Message; } - if (null != Exception) + + if (Exception != null) { - if (!String.IsNullOrEmpty(Exception.Message)) - { - return Exception.Message; - } - return Exception.ToString(); + return Exception.Message ?? Exception.ToString(); } + return base.ToString(); } #endregion ToString - } // class ErrorRecord + } + + /// + /// Dummy generic class for type inference purposes on typed catch blocks. + /// + /// Anything that inherits Exception. + internal class ErrorRecord : ErrorRecord where TException : Exception + { + public new TException Exception { get; } + + public ErrorRecord(Exception exception, string errorId, ErrorCategory errorCategory, object targetObject) : base(exception, errorId, errorCategory, targetObject) + { + } + } /// /// Implemented by exception classes which contain additional @@ -1710,10 +1717,10 @@ public override string ToString() /// 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 @@ -1748,6 +1755,7 @@ public override string ToString() /// /// is no longer available. /// +#nullable enable public interface IContainsErrorRecord { /// @@ -1774,9 +1782,9 @@ public interface IContainsErrorRecord /// /// as the root exception. /// - /// ErrorRecord ErrorRecord { get; } } +#nullable restore /// /// Objects implementing this interface can be used by @@ -1790,7 +1798,6 @@ public interface IContainsErrorRecord /// the custom class to be used in the /// . /// constructor. - /// /// contains special constructor /// /// reducing the steps which localizable code generally has to duplicate when it @@ -1799,6 +1806,7 @@ public interface IContainsErrorRecord /// since the improved /// information about the error may help enable future scenarios. /// +#nullable enable public interface IResourceSupplier { /// @@ -1815,15 +1823,15 @@ public interface IResourceSupplier /// if you want more complex behavior. /// /// Insertions will be inserted into the string with - /// + /// /// to generate the final error message in /// . /// - /// the base resource name - /// the resource id - /// the error message template string corresponding to baseName and resourceId + /// The base resource name. + /// The resource id. + /// The error message template string corresponding to baseName and resourceId. string GetResourceString(string baseName, string resourceId); } -} // namespace System.Management.Automation +} #pragma warning restore 56506 diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index 590da2eb901..1c5de607321 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -1,6 +1,6 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #pragma warning disable 1634, 1691 using System.Collections; @@ -13,12 +13,6 @@ using System.Reflection.Emit; using System.Threading; -#if !CORECLR -// StackFrame and SymbolStore related types are not available in CoreCLR. -using System.Diagnostics.SymbolStore; -using System.Diagnostics; -#endif - namespace System.Management.Automation { /// @@ -31,7 +25,7 @@ public abstract class PSEventManager private int _nextEventId = 1; /// - /// Returns a sequential event ID + /// Returns a sequential event ID. /// protected int GetNextEventId() { @@ -50,7 +44,7 @@ protected int GetNextEventId() /// /// Creates a PowerShell event. - /// + /// /// /// An optional identifier that identifies the source event /// @@ -63,12 +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 /// @@ -81,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); @@ -89,7 +81,7 @@ public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[ /// /// Generate a PowerShell event. - /// + /// /// /// An optional identifier that identifies the source event /// @@ -109,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) { @@ -120,7 +111,7 @@ public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[ } /// - /// Adds a forwarded event to the current event manager + /// Adds a forwarded event to the current event manager. /// internal abstract void AddForwardedEvent(PSEventArgs forwardedEvent); @@ -142,16 +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 /// @@ -173,13 +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 /// @@ -205,13 +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 /// @@ -233,14 +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 /// @@ -266,13 +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 /// @@ -302,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, @@ -317,18 +301,16 @@ internal virtual PSEventSubscriber SubscribeEvent(object source, return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount); } - /// /// Unsubscribes from an event on an object. - /// + /// /// /// The subscriber associated with the event subscription /// - /// public abstract void UnsubscribeEvent(PSEventSubscriber subscriber); /// - /// This event is raised by the event manager to forward events + /// This event is raised by the event manager to forward events. /// internal abstract event EventHandler ForwardEvent; } @@ -349,21 +331,18 @@ 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 private AssemblyBuilder _eventAssembly = null; private ModuleBuilder _eventModule = null; private int _typeId = 0; -#if !CORECLR - private bool debugMode = false; -#endif /// /// Gets the list of event subscribers. @@ -388,7 +367,7 @@ public override List Subscribers /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -410,16 +389,15 @@ 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) + public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent) { return SubscribeEvent(source, eventName, sourceIdentifier, data, action, supportEvent, forwardEvent, 0); } /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -445,9 +423,8 @@ 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) + public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount) { // Record this subscriber. This may just be a registration for engine events. PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, action, supportEvent, forwardEvent, maxTriggerCount); @@ -459,7 +436,7 @@ public override PSEventSubscriber SubscribeEvent(Object source, string eventName /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -489,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, @@ -508,7 +484,7 @@ internal override PSEventSubscriber SubscribeEvent(object source, /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -530,17 +506,15 @@ 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) + public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent) { return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, 0); } - /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -566,9 +540,8 @@ 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) + public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount) { // Record this subscriber. This may just be a registration for engine events. PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount); @@ -578,14 +551,12 @@ public override PSEventSubscriber SubscribeEvent(Object source, string eventName return subscriber; } - #region OnIdleProcessing private Timer _timer = null; private bool _timerInitialized = false; private bool _isTimerActive = false; /// - /// /// We sample every 100ms to check if the engine is idle (currentlyRunningPipeline == null). If it's "idle" /// in four consecutive samples, then we believe it's actually idle. In this way we can avoid capturing possible /// pipeline transitions. @@ -593,12 +564,11 @@ public override PSEventSubscriber SubscribeEvent(Object source, string eventName private int _consecutiveIdleSamples = 0; /// - /// /// Send on-idle event if the engine is idle. The property "AutoReset" of the timer is always false, /// so only one handler will be running at anytime. The timer will be enabled again if we can meet /// the following two conditions. /// 1. No PowerShell.OnIdle event is sent out - /// 2. A PowerShell.OnIdle event is sent out, and there are still subscribers to the on-idle event + /// 2. A PowerShell.OnIdle event is sent out, and there are still subscribers to the on-idle event. /// private void OnElapsedEvent(object source) { @@ -629,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 @@ -670,29 +640,18 @@ private void EnableTimer() #endregion OnIdleProcessing - private static Dictionary s_generatedEventHandlers = new Dictionary(); - private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent) + 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) { Delegate handlerDelegate = null; if (_eventAssembly == null) { -#if !CORECLR - // Define the assembly that will hold our event handlers - StackFrame callStack = new StackFrame(0, true); - debugMode = (callStack.GetFileName() != null); -#endif _eventAssembly = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName("PSEventHandler"), AssemblyBuilderAccess.Run); - } - if (_eventModule == null) - { -#if CORECLR _eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule"); -#else - _eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule", debugMode); -#endif } string engineEventSourceIdentifier = null; @@ -708,27 +667,27 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s { string errorMessage = StringUtil.Format(EventingResources.ReservedIdentifier, sourceIdentifier); - throw new ArgumentException(errorMessage, "sourceIdentifier"); + throw new ArgumentException(errorMessage, nameof(sourceIdentifier)); } EventInfo eventInfo = null; Type sourceType = source as Type ?? source.GetType(); - //PowerShell does not support WinRT events. + // PowerShell does not support WinRT events. if (WinRTHelper.IsWinRTType(sourceType)) { throw new InvalidOperationException(EventingResources.WinRTEventsNotSupported); } // 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 if (eventInfo == null) { string errorMessage = StringUtil.Format(EventingResources.CouldNotFindEvent, eventName); - throw new ArgumentException(errorMessage, "eventName"); + throw new ArgumentException(errorMessage, nameof(eventName)); } // Try to set the EnableRaisingEvents property if it defines one @@ -752,14 +711,7 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s } } } -#if !CORECLR - // If it is a ManagementEventWatcher, enable it - ManagementEventWatcher eventWatcher = source as ManagementEventWatcher; - if (eventWatcher != null) - { - eventWatcher.Start(); - } -#endif + // Get its invoke method, and register ourselves as a handler MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke"); @@ -771,8 +723,7 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s if (invokeMethod.ReturnType != typeof(void)) { string errorMessage = EventingResources.NonVoidDelegateNotSupported; - - throw new ArgumentException(errorMessage, "eventName"); + throw new ArgumentException(errorMessage, nameof(eventName)); } // Cache generated event handlers (by type and event name) so that they don't bloat our @@ -792,8 +743,8 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s // And create an instance of the type ConstructorInfo constructor = - handlerType.GetConstructor(new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) }); - Object handler = constructor.Invoke(new object[] { this, source, sourceIdentifier, data }); + handlerType.GetConstructor(new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) }); + object handler = constructor.Invoke(new object[] { this, source, sourceIdentifier, data }); MethodInfo eventDelegate = handlerType.GetMethod("EventDelegate", BindingFlags.Public | BindingFlags.Instance); handlerDelegate = eventDelegate.CreateDelegate(eventInfo.EventHandlerType, handler); @@ -804,7 +755,7 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s if (PSEngineEvent.EngineEvents.Contains(sourceIdentifier)) { engineEventSourceIdentifier = sourceIdentifier; - isOnIdleEvent = String.Equals(engineEventSourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase); + isOnIdleEvent = string.Equals(engineEventSourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase); } } @@ -830,6 +781,7 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, Object source, s subscribers = new List(); _engineEventSubscribers.Add(engineEventSourceIdentifier, subscribers); } + subscribers.Add(subscriber); // This subscriber is the only one in the idle event list, we enable the timer @@ -843,36 +795,29 @@ 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); } - /// /// 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("subscriber"); - } + ArgumentNullException.ThrowIfNull(subscriber); Delegate existingSubscriber = null; lock (_eventSubscribers) @@ -882,6 +827,7 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) // Already unsubscribed by another thread or the subscriber doesn't exist return; } + subscriber.IsBeingUnsubscribed = true; } @@ -895,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)) @@ -913,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) { @@ -933,7 +876,7 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) /// /// Creates a PowerShell event. - /// + /// /// /// An optional identifier that identifies the source event /// @@ -946,14 +889,13 @@ 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); } /// - /// Adds a forwarded event to the current event manager + /// Adds a forwarded event to the current event manager. /// internal override void AddForwardedEvent(PSEventArgs forwardedEvent) { @@ -999,16 +941,13 @@ protected internal override void ProcessNewEvent(PSEventArgs newEvent, { this.ProcessPendingActions(); } + waitHandle.Dispose(); } } else { - ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object unused) - { - ProcessNewEventImplementation(newEvent, false); - })); + ThreadPool.QueueUserWorkItem(new WaitCallback((_) => ProcessNewEventImplementation(newEvent, false))); } } @@ -1041,6 +980,7 @@ private void ProcessNewEventImplementation(PSEventArgs newEvent, bool processSyn { actionsHandledInCurrentThread.Add(subscriber); } + capturedEvent = true; } else @@ -1085,6 +1025,7 @@ private void AddAction(EventAction action, bool processSynchronously) // This mutex will get set after the event is processed. action.Args.EventProcessed = new ManualResetEventSlim(); } + lock (((System.Collections.ICollection)_actionQueue).SyncRoot) { // If the engine isn't active, pulse the pipeline. @@ -1116,7 +1057,6 @@ private void PulseEngine() /// To prevent starvation of the foreground script, we throttle the number of events /// that we process while the parser is waiting. If the parser is not waiting, we /// do not throttle the event processing. - /// /// internal void ProcessPendingActions() { @@ -1187,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(); @@ -1197,7 +1137,7 @@ private void ProcessPendingActionsImpl() } /// - /// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero + /// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero. /// private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber) { @@ -1218,7 +1158,7 @@ private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber) } } - private object _actionProcessingLock = new Object(); + private readonly object _actionProcessingLock = new object(); private EventAction _processingAction = null; /// @@ -1292,6 +1232,7 @@ private void InvokeAction(EventAction nextAction, out bool addActionBack) { _context.EngineSessionState = nextAction.Sender.Action.ScriptBlock.SessionStateInternal; } + Runspace oldDefault = Runspace.DefaultRunspace; try @@ -1331,6 +1272,7 @@ private void InvokeAction(EventAction nextAction, out bool addActionBack) { eventProcessed.Set(); } + Runspace.DefaultRunspace = oldDefault; _context.EngineSessionState = oldSessionState; _processingAction = null; @@ -1338,7 +1280,6 @@ private void InvokeAction(EventAction nextAction, out bool addActionBack) } } - internal bool IsExecutingEventAction { get { return (_processingAction != null); } @@ -1346,11 +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); @@ -1370,7 +1310,7 @@ private IEnumerable GetEventSubscribers(string sourceIdentifi foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys) { bool takeActionForEvent = false; - if (String.Equals(currentSubscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(currentSubscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase)) { if (forNewEventProcessing) { @@ -1430,16 +1370,7 @@ private IEnumerable GetEventSubscribers(string sourceIdentifi private Type GenerateEventHandler(MethodInfo invokeSignature) { int parameterCount = invokeSignature.GetParameters().Length; -#if !CORECLR - StackFrame callStack = new StackFrame(0, true); - // Get the filename to associate with the debug symbols - ISymbolDocumentWriter doc = null; - if (debugMode) - { - doc = _eventModule.DefineDocument(callStack.GetFileName(), Guid.Empty, Guid.Empty, Guid.Empty); - } -#endif // Define the type that will respond to the event. It // derives from PSEventHandler so that complex // functionality can go into its base class. @@ -1450,29 +1381,14 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) // Retrieve the existing constructor ConstructorInfo existingConstructor = typeof(PSEventHandler).GetConstructor( - new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) }); + new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) }); -#if !CORECLR - if (debugMode) - { - // Mark as debuggable - Type debugAttributeType = typeof(DebuggableAttribute); - ConstructorInfo debugAttributeCtor = debugAttributeType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) }); - - CustomAttributeBuilder debugAttributeBuilder = new CustomAttributeBuilder(debugAttributeCtor, - new object[] { - DebuggableAttribute.DebuggingModes.DisableOptimizations | - DebuggableAttribute.DebuggingModes.Default - }); - _eventAssembly.SetCustomAttribute(debugAttributeBuilder); - } -#endif // Define the new constructor // public TestEventHandler(PSEventManager eventManager, Object sender, string sourceIdentifier, PSObject extraData) // : base(eventManager, sender, sourceIdentifier, extraData) ConstructorBuilder eventConstructor = eventType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, - new Type[] { typeof(PSEventManager), typeof(Object), typeof(string), typeof(PSObject) }); + new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) }); ILGenerator extendedConstructor = eventConstructor.GetILGenerator(); extendedConstructor.Emit(OpCodes.Ldarg_0); extendedConstructor.Emit(OpCodes.Ldarg_1); @@ -1491,7 +1407,7 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) parameterCounter++; } - // public void EventDelegate(Object sender, FileSystemEventArgs e) + // public void EventDelegate(object sender, FileSystemEventArgs e) MethodBuilder eventMethod = eventType.DefineMethod("EventDelegate", MethodAttributes.Public, CallingConventions.Standard, invokeSignature.ReturnType, parameterTypes); @@ -1506,41 +1422,25 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) ILGenerator methodContents = eventMethod.GetILGenerator(); - // Object[] args = - LocalBuilder argsBuilder = methodContents.DeclareLocal(typeof(object[])); - -#if !CORECLR - if (debugMode) - { - argsBuilder.SetLocalSymInfo("args"); + // Declare a local variable of the type 'object[]' at index 0, say 'object[] args' + methodContents.DeclareLocal(typeof(object[])); - // new Object[ invokeSignature.GetParameters().Length ] - methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100); - } -#endif methodContents.Emit(OpCodes.Ldc_I4, parameterCount); - methodContents.Emit(OpCodes.Newarr, typeof(Object)); + methodContents.Emit(OpCodes.Newarr, typeof(object)); - // Retrieve the args variable from local variable index 0 + // Store the new array to the local variable 'args' methodContents.Emit(OpCodes.Stloc_0); // Inline, this converts into a series of setting args[n] to // the argument at the same parameter index for (int counter = 1; counter <= parameterCount; counter++) { -#if !CORECLR - if (debugMode) - { - // args[n] = argument[n] - methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100); - } -#endif methodContents.Emit(OpCodes.Ldloc_0); methodContents.Emit(OpCodes.Ldc_I4, counter - 1); methodContents.Emit(OpCodes.Ldarg, counter); // Box the value type if necessary - if (parameterTypes[counter - 1].GetTypeInfo().IsValueType) + if (parameterTypes[counter - 1].IsValueType) { methodContents.Emit(OpCodes.Box, parameterTypes[counter - 1]); } @@ -1579,16 +1479,9 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) // Finally, invoke the method MethodInfo generateEventMethod = typeof(PSEventManager).GetMethod( - "GenerateEvent", - new Type[] { typeof(string), typeof(object), typeof(object[]), typeof(PSObject) } - ); -#if !CORECLR - if (debugMode) - { - // GenerateEvent(sourceIdentifier, args, extraData); - methodContents.MarkSequencePoint(doc, callStack.GetFileLineNumber() - 1, 1, callStack.GetFileLineNumber(), 100); - } -#endif + nameof(PSEventManager.GenerateEvent), + new Type[] { typeof(string), typeof(object), typeof(object[]), typeof(PSObject) }); + methodContents.Emit(OpCodes.Callvirt, generateEventMethod); // Discard the return value, and return @@ -1599,36 +1492,21 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) } /// - /// This event is raised by the event manager to forward events + /// This event is raised by the event manager to forward events. /// internal override event EventHandler ForwardEvent; /// - /// Raises the ForwardEvent event + /// Raises the ForwardEvent event. /// 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); } /// /// Disposes the EventManager class. /// - /// public void Dispose() { Dispose(true); @@ -1638,22 +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()) { @@ -1662,24 +1535,32 @@ public void Dispose(bool disposing) } } } + + /// + /// Finalizes an instance of the class. + /// + ~PSLocalEventManager() + { + Dispose(false); + } } /// - /// Implementation of PSEventManager for remote runspaces + /// Implementation of PSEventManager for remote runspaces. /// 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 + /// Creates an event manager for the given runspace. /// - /// Computer on which the event was generated - /// Runspace on which the event was generated + /// Computer on which the event was generated. + /// Runspace on which the event was generated. internal PSRemoteEventManager(string computerName, Guid runspaceId) { _computerName = computerName; @@ -1699,7 +1580,7 @@ public override List Subscribers /// /// Creates a PowerShell event. - /// + /// /// /// An optional identifier that identifies the source event /// @@ -1712,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 @@ -1720,7 +1600,7 @@ protected override PSEventArgs CreateEvent(string sourceIdentifier, object sende } /// - /// Adds a forwarded event to the current event manager + /// Adds a forwarded event to the current event manager. /// internal override void AddForwardedEvent(PSEventArgs forwardedEvent) { @@ -1768,11 +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); @@ -1780,7 +1659,7 @@ public override IEnumerable GetEventSubscribers(string source /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -1802,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) { @@ -1811,7 +1689,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -1837,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) { @@ -1846,7 +1723,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -1868,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) { @@ -1877,7 +1753,7 @@ public override PSEventSubscriber SubscribeEvent(object source, string eventName /// /// Subscribes to an event on an object. - /// + /// /// /// The source object that defines the event /// @@ -1903,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) { @@ -1912,37 +1787,31 @@ 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); } /// - /// This event is raised by the event manager to forward events + /// This event is raised by the event manager to forward events. /// internal override event EventHandler ForwardEvent; /// - /// Raises the ForwardEvent event + /// Raises the ForwardEvent event. /// protected virtual void OnForwardEvent(PSEventArgs e) { - EventHandler eh = ForwardEvent; - - if (eh != null) - { - eh(this, e); - } + ForwardEvent?.Invoke(this, e); } } /// - /// Constants that represent PowerShell engine events + /// Constants that represent PowerShell engine events. /// // Note: If you generate a new engine event that happens frequently, // (i.e.: variable changes), the user should be required to enable @@ -1952,47 +1821,46 @@ public sealed class PSEngineEvent private PSEngineEvent() { } /// - /// Called when the PowerShell engine is exiting + /// Called when the PowerShell engine is exiting. /// public const string Exiting = "PowerShell.Exiting"; /// - /// Call when the PowerShell engine is idle + /// Call when the PowerShell engine is idle. /// public const string OnIdle = "PowerShell.OnIdle"; /// - /// Called when a workflow job is started from a PowerShell script. + /// Called when Debug-Runspace has attached a debugger to the current runspace. /// - public const string WorkflowJobStartEvent = "PowerShell.WorkflowJobStartEvent"; + public const string OnDebugAttach = "PowerShell.OnDebugAttach"; /// - /// Called during scriptblock invocation + /// Called during scriptblock invocation. /// internal const string OnScriptBlockInvoke = "PowerShell.OnScriptBlockInvoke"; /// - /// Called during scriptblock invocation + /// Called during scriptblock invocation. /// internal const string GetCommandInfoParameterMetadata = "PowerShell.GetCommandInfoParameterMetadata"; /// - /// A HashSet that contains all engine event names + /// 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 }; } - /// - /// Represents a subscriber to an event + /// Represents a subscriber to an event. /// public class PSEventSubscriber : IEquatable { /// /// Creates an instance of the PSEventSubscriber class for a given source object, event name, - /// and optional source identifier + /// and optional source identifier. /// - internal PSEventSubscriber(ExecutionContext context, int id, Object source, + internal PSEventSubscriber(ExecutionContext context, int id, object source, string eventName, string sourceIdentifier, bool supportEvent, bool forwardEvent, int maxTriggerCount) { _context = context; @@ -2023,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) @@ -2037,7 +1913,7 @@ internal PSEventSubscriber(ExecutionContext context, int id, Object source, internal void RegisterJob() { - // And this event subscriber to the job repository if it's not a support event. + // Add this event subscriber to the job repository if it's not a support event. if (!SupportEvent) { if (this.Action != null) @@ -2052,17 +1928,25 @@ 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 + /// Create a bound script block. /// private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) { @@ -2078,43 +1962,42 @@ private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) } /// - /// Get the identifier of this event subscription + /// Get the identifier of this event subscription. /// public int SubscriptionId { get; set; } /// - /// The object to which this event subscription applies + /// The object to which this event subscription applies. /// - public Object SourceObject { get; } + public object SourceObject { get; } /// - /// The event object to which this event subscription applies + /// The event object to which this event subscription applies. /// public string EventName { get; } /// - /// The identifier that identifies the source of these events + /// The identifier that identifies the source of these events. /// public string SourceIdentifier { get; } /// - /// The action invoked when this event arrives + /// The action invoked when this event arrives. /// public PSEventJob Action { get; } /// - /// The delegate invoked when this event arrives + /// The delegate invoked when this event arrives. /// public PSEventReceivedEventHandler HandlerDelegate { get; } = null; - /// - /// Get the flag that marks this event as a supporting event + /// Get the flag that marks this event as a supporting event. /// public bool SupportEvent { get; } /// - /// Gets whether to forward the event to the PowerShell client during a remote execution + /// Gets whether to forward the event to the PowerShell client during a remote execution. /// public bool ForwardEvent { get; } @@ -2124,9 +2007,9 @@ private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) internal bool ShouldProcessInExecutionThread { get; set; } /// - /// Gets whether the event should be unregistered + /// 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. @@ -2144,23 +2027,36 @@ private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) internal int RemainingActionsToProcess { get; set; } /// - /// Indicate if the subscriber is being subscribed by a thread + /// Indicate if the subscriber is being subscribed by a thread. /// internal bool IsBeingUnsubscribed { get; set; } /// - /// The event generated when this event subscriber is unregistered + /// The event generated when this event subscriber is unregistered. /// public event PSEventUnsubscribedEventHandler Unsubscribed; #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) @@ -2168,11 +2064,11 @@ public bool Equals(PSEventSubscriber other) return false; } - return (String.Equals(SubscriptionId, other.SubscriptionId)); + return (string.Equals(SubscriptionId, other.SubscriptionId)); } /// - /// Gets the hashcode that represents this PSEventSubscriber instance + /// Gets the hashcode that represents this PSEventSubscriber instance. /// public override int GetHashCode() { @@ -2180,16 +2076,12 @@ public override int GetHashCode() } #endregion - internal void OnPSEventUnsubscribed(Object sender, PSEventUnsubscribedEventArgs e) + internal void OnPSEventUnsubscribed(object sender, PSEventUnsubscribedEventArgs e) { - if (Unsubscribed != null) - { - Unsubscribed(sender, e); - } + Unsubscribed?.Invoke(sender, e); } } - /// /// The generic event handler from which specific event handlers extend. When possible, /// add functionality to this class instead of the IL generated by the GenerateEventHandler() @@ -2199,7 +2091,7 @@ internal void OnPSEventUnsubscribed(Object sender, PSEventUnsubscribedEventArgs public class PSEventHandler { /// - /// Creates a new instance of the PsEventHandler class + /// Creates a new instance of the PsEventHandler class. /// public PSEventHandler() { @@ -2208,7 +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. /// @@ -2222,8 +2114,7 @@ public PSEventHandler() /// /// Any additional data you wish to attach to the event /// - /// - public PSEventHandler(PSEventManager eventManager, Object sender, string sourceIdentifier, PSObject extraData) + public PSEventHandler(PSEventManager eventManager, object sender, string sourceIdentifier, PSObject extraData) { this.eventManager = eventManager; this.sender = sender; @@ -2232,25 +2123,25 @@ public PSEventHandler(PSEventManager eventManager, Object sender, string sourceI } /// - /// The event manager to which we forward events + /// The event manager to which we forward events. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] protected PSEventManager eventManager; /// - /// The sender of the event + /// The sender of the event. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] - protected Object sender; + protected object sender; /// - /// An optional identifier that identifies the source of the event + /// An optional identifier that identifies the source of the event. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] protected string sourceIdentifier = null; /// - /// Any additional data you wish to attach to the event + /// Any additional data you wish to attach to the event. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] protected PSObject extraData = null; @@ -2267,7 +2158,7 @@ internal ForwardedEventArgs(PSObject serializedRemoteEventArgs) } /// - /// Serialized event arguments from the event fired in a remote runspace + /// Serialized event arguments from the event fired in a remote runspace. /// public PSObject SerializedRemoteEventArgs { get; } @@ -2285,14 +2176,14 @@ internal static bool IsRemoteSourceEventArgs(object argument) internal class PSEventArgs : EventArgs { /// - /// Event arguments + /// Event arguments. /// internal T Args; /// - /// Class constructor + /// Class constructor. /// - /// event arguments + /// Event arguments. public PSEventArgs(T args) { Args = args; @@ -2300,14 +2191,13 @@ public PSEventArgs(T args) } /// - /// The event arguments associated with an event + /// The event arguments associated with an event. /// public class PSEventArgs : EventArgs { /// - /// Create a new instance of the PSEventArgs type + /// Create a new instance of the PSEventArgs type. /// - /// /// /// Computer on which this event was generated /// @@ -2329,12 +2219,12 @@ public class PSEventArgs : EventArgs /// /// Additional data attached by the user to this event. /// - internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, string sourceIdentifier, Object sender, Object[] originalArgs, PSObject additionalData) + internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, string sourceIdentifier, object sender, object[] originalArgs, PSObject additionalData) { // Capture the first EventArgs as SourceEventArgs if (originalArgs != null) { - foreach (Object argument in originalArgs) + foreach (object argument in originalArgs) { EventArgs sourceEventArgs = argument as EventArgs; if (sourceEventArgs != null) @@ -2368,52 +2258,54 @@ internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, public string ComputerName { get; internal set; } /// - /// Gets the unique identifier of this event + /// Gets the unique identifier of this event. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] public Guid RunspaceId { get; internal set; } /// - /// Gets the unique identifier of this event + /// Gets the unique identifier of this event. /// public int EventIdentifier { get; internal set; } /// - /// Gets the object that generated this event + /// Gets the object that generated this event. /// - public Object Sender { get; } + public object Sender { get; } /// /// Gets the first argument from the original event source that - /// derives from EventArgs + /// derives from EventArgs. /// public EventArgs SourceEventArgs { get; } /// - /// Gets the list of arguments captured by the original event source + /// Gets the list of arguments captured by the original event source. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Object[] SourceArgs { get; } + public object[] SourceArgs { get; } /// - /// Gets the identifier associated with the source of this event + /// Gets the identifier associated with the source of this event. /// - public String SourceIdentifier { get; } + public string SourceIdentifier { get; } /// - /// Gets the time and date that this event was generated + /// Gets the time and date that this event was generated. /// - public DateTime TimeGenerated { get; internal set; -// internal setter using during deserialization + public DateTime TimeGenerated + { + // internal setter using during deserialization + get; internal set; } /// - /// Gets the additional user data associated with this event + /// Gets the additional user data associated with this event. /// public PSObject MessageData { get; } /// - /// Gets whether to forward the event to the PowerShell client during a remote execution + /// Gets whether to forward the event to the PowerShell client during a remote execution. /// internal bool ForwardEvent { get; set; } @@ -2425,20 +2317,18 @@ internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, /// /// The delegate that handles notifications of new events - /// added to the collection + /// added to the collection. /// - public delegate void PSEventReceivedEventHandler(Object sender, PSEventArgs e); - + public delegate void PSEventReceivedEventHandler(object sender, PSEventArgs e); /// - /// The event arguments associated with unsubscribing from an event + /// The event arguments associated with unsubscribing from an event. /// public class PSEventUnsubscribedEventArgs : EventArgs { /// - /// Create a new instance of the PSEventUnsubscribedEventArgs type + /// Create a new instance of the PSEventUnsubscribedEventArgs type. /// - /// /// /// The event subscriber being unregistered /// @@ -2448,16 +2338,15 @@ internal PSEventUnsubscribedEventArgs(PSEventSubscriber eventSubscriber) } /// - /// The event subscriber being unregistered + /// The event subscriber being unregistered. /// public PSEventSubscriber EventSubscriber { get; internal set; } } /// - /// The delegate that handles notifications of the event being unsubscribed + /// The delegate that handles notifications of the event being unsubscribed. /// - public delegate void PSEventUnsubscribedEventHandler(Object sender, PSEventUnsubscribedEventArgs e); - + public delegate void PSEventUnsubscribedEventHandler(object sender, PSEventUnsubscribedEventArgs e); /// /// This class contains the collection of events received by the @@ -2466,25 +2355,22 @@ internal PSEventUnsubscribedEventArgs(PSEventSubscriber eventSubscriber) public class PSEventArgsCollection : IEnumerable { /// - /// The event generated when a new event is received + /// The event generated when a new event is received. /// public event PSEventReceivedEventHandler PSEventReceived; - private List _eventCollection = new List(); + + private readonly List _eventCollection = new List(); /// - /// Add add an event to the collection + /// Add add an event to the collection. /// - /// /// /// The PSEventArgs instance that represents this event /// /// Don't add events to the collection directly; use the EventManager instead internal void Add(PSEventArgs eventToAdd) { - if (eventToAdd == null) - { - throw new ArgumentNullException("eventToAdd"); - } + ArgumentNullException.ThrowIfNull(eventToAdd); _eventCollection.Add(eventToAdd); @@ -2492,7 +2378,7 @@ internal void Add(PSEventArgs eventToAdd) } /// - /// Removes an item at a specific index from the collection + /// Removes an item at a specific index from the collection. /// public int Count { @@ -2503,7 +2389,7 @@ public int Count } /// - /// Removes an item at a specific index from the collection + /// Removes an item at a specific index from the collection. /// public void RemoveAt(int index) { @@ -2511,7 +2397,7 @@ public void RemoveAt(int index) } /// - /// Gets an item at a specific index from the collection + /// Gets an item at a specific index from the collection. /// public PSEventArgs this[int index] { @@ -2521,17 +2407,13 @@ public PSEventArgs this[int index] } } - private void OnPSEventReceived(Object sender, PSEventArgs e) + private void OnPSEventReceived(object sender, PSEventArgs e) { - PSEventReceivedEventHandler eventHandler = PSEventReceived; - if (eventHandler != null) - { - eventHandler(sender, e); - } + PSEventReceived?.Invoke(sender, e); } /// - /// Get the enumerator of this collection + /// Get the enumerator of this collection. /// public IEnumerator GetEnumerator() { @@ -2539,7 +2421,7 @@ public IEnumerator GetEnumerator() } /// - /// Get the enumerator of this collection + /// Get the enumerator of this collection. /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { @@ -2547,7 +2429,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() } /// - /// Get the synchronization root for this collection + /// Get the synchronization root for this collection. /// public object SyncRoot { get; } = new object(); } @@ -2555,7 +2437,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() /// /// The combination of an event subscriber, and the event that was fired. /// This is to support the arguments to script blocks that we invoke automatically - /// as a response to some events + /// as a response to some events. /// internal class EventAction { @@ -2577,13 +2459,13 @@ public EventAction(PSEventSubscriber sender, PSEventArgs args) } /// - /// A class to give a job-like interface to event actions + /// A class to give a job-like interface to event actions. /// public class PSEventJob : Job { /// /// Creates a new instance of the PSEventJob class. - /// + /// /// /// The event manager that controls the event subscriptions /// @@ -2596,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("eventManager"); - if (subscriber == null) - throw new ArgumentNullException("subscriber"); + ArgumentNullException.ThrowIfNull(eventManager); + + ArgumentNullException.ThrowIfNull(subscriber); UsesResultsCollection = true; ScriptBlock = action; @@ -2611,12 +2495,12 @@ 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; /// - /// Gets dynamic module where the action is invoked + /// Gets dynamic module where the action is invoked. /// public PSModuleInfo Module { @@ -2624,7 +2508,7 @@ public PSModuleInfo Module } /// - /// Stop Job + /// Stop Job. /// public override void StopJob() { @@ -2632,12 +2516,12 @@ public override void StopJob() } /// - /// Message indicating status of the job + /// Message indicating status of the job. /// public override string StatusMessage { get; } = null; /// - /// indicates if more data is available + /// Indicates if more data is available. /// /// /// This has more data if any of the child jobs have more data. @@ -2649,11 +2533,11 @@ public override bool HasMoreData return _moreData; } } - private bool _moreData = false; + private bool _moreData = false; /// - /// Location in which this job is running + /// Location in which this job is running. /// public override string Location { @@ -2664,20 +2548,19 @@ public override string Location } /// - /// The scriptblock that defines the action + /// The scriptblock that defines the action. /// internal ScriptBlock ScriptBlock { get; } /// /// 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)) @@ -2718,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); @@ -2767,4 +2650,4 @@ private void LogErrorsAndOutput(List results, SessionState actionState) } } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index e67e8fcb394..ac39eade17b 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -1,20 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Internal.Host; +using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Runtime.CompilerServices; -using Microsoft.PowerShell; using System.Reflection; +using System.Runtime.CompilerServices; using System.Security; -using System.Diagnostics.CodeAnalysis; + +using Microsoft.PowerShell; using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation @@ -29,19 +30,20 @@ internal class ExecutionContext #region Properties /// - /// The events received by this runspace + /// The events received by this runspace. /// internal PSLocalEventManager Events { get; private set; } - internal HashSet AutoLoadingModuleInProgress { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); + internal HashSet AutoLoadingModuleInProgress { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); /// - /// The debugger for the interpreter + /// The debugger for the interpreter. /// internal ScriptDebugger Debugger { get { return _debugger; } } + internal ScriptDebugger _debugger; internal int _debuggingMode; @@ -51,20 +53,12 @@ internal ScriptDebugger Debugger /// internal void ResetManagers() { - if (_debugger != null) - { - _debugger.ResetDebugger(); - } + _debugger?.ResetDebugger(); - if (Events != null) - { - Events.Dispose(); - } + Events?.Dispose(); Events = new PSLocalEventManager(this); - if (this.transactionManager != null) - { - this.transactionManager.Dispose(); - } + + this.transactionManager?.Dispose(); this.transactionManager = new PSTransactionManager(); } /// @@ -78,8 +72,13 @@ internal int PSDebugTraceLevel // Pretend that tracing is off if ignoreScriptDebug is true return IgnoreScriptDebug ? 0 : _debugTraceLevel; } - set { _debugTraceLevel = value; } + + set + { + _debugTraceLevel = value; + } } + private int _debugTraceLevel; /// @@ -93,20 +92,21 @@ internal bool PSDebugTraceStep // Pretend that tracing is off if ignoreScriptDebug is true return !IgnoreScriptDebug && _debugTraceStep; } - set { _debugTraceStep = value; } + + set + { + _debugTraceStep = value; + } } + private bool _debugTraceStep; // 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(); - } - return (context != null) - ? context.IsStrictVersion(majorVersion) - : false; + context ??= LocalPipeline.GetExecutionContextFromTLS(); + + return (context != null) && context.IsStrictVersion(majorVersion); } /// /// Check to see a specific version of strict mode is enabled. The check is always scoped, @@ -131,6 +131,7 @@ internal bool IsStrictVersion(int majorVersion) { break; } + scope = scope.Parent; } @@ -162,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. @@ -191,41 +192,8 @@ 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 + /// Authorization manager for this runspace. /// internal AuthorizationManager AuthorizationManager { get; private set; } @@ -234,18 +202,16 @@ internal void ReleaseResponsibilityForModuleAnalysisAppDomain() /// providers based on the type of the shell /// (single shell or custom shell). /// - /// internal ProviderNames ProviderNames { get { - if (_providerNames == null) - { - _providerNames = new SingleShellProviderNames(); - } + _providerNames ??= new SingleShellProviderNames(); + return _providerNames; } } + private ProviderNames _providerNames; /// @@ -264,7 +230,7 @@ internal string ShellID { // Use the ShellID from PSAuthorizationManager before everything else because that's what's used // to check execution policy... - if (AuthorizationManager is PSAuthorizationManager && !String.IsNullOrEmpty(AuthorizationManager.ShellId)) + if (AuthorizationManager is PSAuthorizationManager && !string.IsNullOrEmpty(AuthorizationManager.ShellId)) { _shellId = AuthorizationManager.ShellId; } @@ -274,15 +240,16 @@ internal string ShellID _shellId = Utils.DefaultPowerShellShellID; } } + return _shellId; } } + private string _shellId; /// - /// Session State with which this instance of engine works + /// Session State with which this instance of engine works. /// - /// internal SessionStateInternal EngineSessionState { get; set; } /// @@ -292,9 +259,8 @@ internal string ShellID internal SessionStateInternal TopLevelSessionState { get; private set; } /// - /// Get the SessionState facade for the internal session state APIs + /// Get the SessionState facade for the internal session state APIs. /// - /// internal SessionState SessionState { get @@ -304,7 +270,7 @@ internal SessionState SessionState } /// - /// Get/set constraints for this execution environment + /// Get/set constraints for this execution environment. /// internal PSLanguageMode LanguageMode { @@ -312,22 +278,45 @@ internal PSLanguageMode LanguageMode { return _languageMode; } + set { // If we're moving to ConstrainedLanguage, invalidate the binding // caches. After that, the binding rules encode the language mode. if (value == PSLanguageMode.ConstrainedLanguage) { - ExecutionContext.HasEverUsedConstrainedLanguage = true; HasRunspaceEverUsedConstrainedLanguageMode = true; - System.Management.Automation.Language.PSSetMemberBinder.InvalidateCache(); - System.Management.Automation.Language.PSInvokeMemberBinder.InvalidateCache(); - System.Management.Automation.Language.PSConvertBinder.InvalidateCache(); - System.Management.Automation.Language.PSBinaryOperationBinder.InvalidateCache(); - System.Management.Automation.Language.PSGetIndexBinder.InvalidateCache(); - System.Management.Automation.Language.PSSetIndexBinder.InvalidateCache(); - System.Management.Automation.Language.PSCreateInstanceBinder.InvalidateCache(); + // If 'ExecutionContext.HasEverUsedConstrainedLanguage' is already set to True, then we have + // already invalidated all cached binders, and binders already started to generate code with + // consideration of 'LanguageMode'. In such case, we don't need to invalidate cached binders + // again. + // Note that when executing script blocks marked as 'FullLanguage' in a 'ConstrainedLanguage' + // environment, we will set and Restore 'context.LanguageMode' very often. But we should not + // invalidate the cached binders every time we restore to 'ConstrainedLanguage'. + if (!ExecutionContext.HasEverUsedConstrainedLanguage) + { + lock (lockObject) + { + // If another thread has already set 'ExecutionContext.HasEverUsedConstrainedLanguage' + // while we are waiting on the lock, then nothing needs to be done. + if (!ExecutionContext.HasEverUsedConstrainedLanguage) + { + PSSetMemberBinder.InvalidateCache(); + PSInvokeMemberBinder.InvalidateCache(); + PSConvertBinder.InvalidateCache(); + PSBinaryOperationBinder.InvalidateCache(); + PSGetIndexBinder.InvalidateCache(); + PSSetIndexBinder.InvalidateCache(); + PSCreateInstanceBinder.InvalidateCache(); + + // Set 'HasEverUsedConstrainedLanguage' at the very end to guarantee other threads to wait until + // all invalidation operations are done. + UntrustedObjects = new ConditionalWeakTable(); + ExecutionContext.HasEverUsedConstrainedLanguage = true; + } + } + } } // Conversion caches don't have version info / binding rules, so must be @@ -337,37 +326,132 @@ internal PSLanguageMode LanguageMode _languageMode = value; } } + private PSLanguageMode _languageMode = PSLanguageMode.FullLanguage; /// - /// True if this runspace has ever used constrained language mode + /// True if this runspace has ever used constrained language mode. /// internal bool HasRunspaceEverUsedConstrainedLanguageMode { get; private set; } + /// + /// Indicate if a parameter binding is happening that transitions the execution from ConstrainedLanguage + /// mode to a trusted FullLanguage command. + /// + internal bool LanguageModeTransitionInParameterBinding { get; set; } + /// /// True if we've ever used ConstrainedLanguage. If this is the case, then the binding restrictions /// need to also validate against the language mode. /// internal static bool HasEverUsedConstrainedLanguage { get; private set; } + #region Variable Tracking + /// - /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode + /// Initialized when 'ConstrainedLanguage' is applied. + /// The objects contained in this table are considered to be untrusted. + /// + private static ConditionalWeakTable UntrustedObjects { get; set; } + + /// + /// Helper for checking if the given value is marked as untrusted. + /// + internal static bool IsMarkedAsUntrusted(object value) + { + bool result = false; + var baseValue = PSObject.Base(value); + if (baseValue != null && baseValue != NullString.Value) + { + result = UntrustedObjects.TryGetValue(baseValue, out _); + } + + return result; + } + + /// + /// Helper for marking a value as untrusted. + /// + internal static void MarkObjectAsUntrusted(object value) + { + // If the value is a PSObject, then we mark its base object untrusted + var baseValue = PSObject.Base(value); + if (baseValue != null && baseValue != NullString.Value) + { + // It's actually setting a key value pair when the key doesn't exist + UntrustedObjects.GetValue(baseValue, static key => null); + + try + { + // If it's a PSReference object, we need to also mark the value it's holding on. + // This could result in a recursion if psRef.Value points to itself directly or indirectly, so we check if psRef.Value is already + // marked before making a recursive call. The additional check adds extra overhead for handling PSReference object, but it should + // be rare in practice. + var psRef = baseValue as PSReference; + if (psRef != null && !IsMarkedAsUntrusted(psRef.Value)) + { + MarkObjectAsUntrusted(psRef.Value); + } + } + catch { /* psRef.Value may call PSVariable.Value under the hood, which may throw arbitrary exception */ } + } + } + + /// + /// Helper for setting the untrusted value of an assignment to either a 'Global:' variable, or a 'Script:' variable in a module scope. + /// + /// + /// This method is for tracking assignment to global variables and module script scope varaibles in ConstrainedLanguage mode. Those variables + /// can go across boundaries between ConstrainedLanguage and FullLanguage, and make it easy for a trusted script to use data from an untrusted + /// environment. Therefore, in ConstrainedLanguage mode, we need to mark the value objects assigned to those variables as untrusted. + /// + internal static void MarkObjectAsUntrustedForVariableAssignment(PSVariable variable, SessionStateScope scope, SessionStateInternal sessionState) + { + if (scope.Parent == null || // If it's the global scope, OR + (sessionState.Module != null && // it's running in a module AND + scope.ScriptScope == scope && scope.Parent.Parent == null)) // it's the module's script scope (scope.Parent is global scope and scope.ScriptScope points to itself) + { + // We are setting value for either a 'Global:' variable, or a 'Script:' variable within a module in 'ConstrainedLanguage' mode. + // Global variable may be referenced within trusted script block (scriptBlock.LanguageMode == 'FullLanguage'), and users could + // also set a 'Script:' variable in a trusted module scope from 'ConstrainedLanguage' environment via '& $mo { $script: }'. + // So we need to mark the value as untrusted. + MarkObjectAsUntrusted(variable.Value); + } + } + + /// + /// The result object is assumed generated by operating on the original object. + /// So if the original object is from an untrusted input source, we mark the result object as untrusted. + /// + internal static void PropagateInputSource(object originalObject, object resultObject, PSLanguageMode currentLanguageMode) + { + // The untrusted flag is populated only in FullLanguage mode and ConstrainedLanguage has been used in the process before. + if (ExecutionContext.HasEverUsedConstrainedLanguage && currentLanguageMode == PSLanguageMode.FullLanguage && IsMarkedAsUntrusted(originalObject)) + { + MarkObjectAsUntrusted(resultObject); + } + } + + #endregion + + /// + /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode. /// internal bool UseFullLanguageModeInDebugger { get { - return InitialSessionState != null ? InitialSessionState.UseFullLanguageModeInDebugger : false; + return InitialSessionState != null && InitialSessionState.UseFullLanguageModeInDebugger; } } - internal static List ModulesWithJobSourceAdapters = new List + internal static readonly List ModulesWithJobSourceAdapters = new List { Utils.ScheduledJobModuleName, }; /// - /// Is true the PSScheduledJob and PSWorkflow modules are loaded for this runspace + /// Is true if the PSScheduledJob module is loaded for this runspace. /// internal bool IsModuleWithJobSourceAdapterLoaded { @@ -378,7 +462,6 @@ internal bool IsModuleWithJobSourceAdapterLoaded /// Gets the location globber for the session state for /// this instance of the runspace. /// - /// internal LocationGlobber LocationGlobber { get @@ -387,25 +470,21 @@ internal LocationGlobber LocationGlobber return _locationGlobber; } } + private LocationGlobber _locationGlobber; /// - /// The assemblies that have been loaded for this runspace + /// The assemblies that have been loaded for this runspace. /// - /// internal Dictionary AssemblyCache { get; private set; } - #endregion Properties #region Engine State - - /// /// The state for current engine that is running. /// /// - /// internal EngineState EngineState { get; set; } = EngineState.None; #endregion @@ -424,13 +503,11 @@ internal object GetVariableValue(VariablePath path) /// /// Get a variable out of session state. This calls GetVariable(name) and returns the - /// value unless it is null in which case it returns the defaultValue provided by the caller + /// value unless it is null in which case it returns the defaultValue provided by the caller. /// 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; } /// @@ -439,27 +516,16 @@ internal object GetVariableValue(VariablePath path, object defaultValue) internal void SetVariable(VariablePath path, object newValue) { EngineSessionState.SetVariable(path, newValue, true, CommandOrigin.Internal); - } // SetVariable + } internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPref, out bool defaultUsed) { - CmdletProviderContext context = null; - SessionStateScope scope = null; - object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out context, out scope); + object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _); if (val is T) { - // We don't want to support "Ignore" as action preferences, as it leads to bad - // scripting habits. They are only supported as cmdlet overrides. - if (val is ActionPreference) + if (val is ActionPreference actionPreferenceValue) { - ActionPreference preference = (ActionPreference)val; - if ((preference == ActionPreference.Ignore) || (preference == ActionPreference.Suspend)) - { - // Reset the variable value - EngineSessionState.SetVariableValue(preferenceVariablePath.UserPath, defaultPref); - string message = StringUtil.Format(ErrorPackage.UnsupportedPreferenceError, preference); - throw new NotSupportedException(message); - } + CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref); } T convertedResult = (T)val; @@ -486,6 +552,11 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr result = (T)PSObject.Base(val); defaultUsed = false; } + + if (result is ActionPreference actionPreferenceValue) + { + CheckActionPreference(preferenceVariablePath, actionPreferenceValue, defaultPref); + } } catch (InvalidCastException) { @@ -500,8 +571,20 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr return result; } + private void CheckActionPreference(VariablePath preferenceVariablePath, ActionPreference preference, object defaultValue) + { + if (preference == ActionPreference.Suspend) + { + // ActionPreference.Suspend is reserved for future use. When it is used, reset + // the variable to its default. + string message = StringUtil.Format(ErrorPackage.ReservedActionPreferenceReplacedError, preference, preferenceVariablePath.UserPath, defaultValue); + EngineSessionState.SetVariable(preferenceVariablePath, defaultValue, true, CommandOrigin.Internal); + throw new NotSupportedException(message); + } + } + /// - /// Same as GetEnumPreference, but for boolean values + /// Same as GetEnumPreference, but for boolean values. /// /// /// @@ -509,18 +592,15 @@ internal T GetEnumPreference(VariablePath preferenceVariablePath, T defaultPr /// 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 @@ -532,32 +612,35 @@ 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; #endregion #region FormatAndOutput - internal Object FormatInfo { get; set; } + internal object FormatInfo { get; set; } #endregion internal Dictionary CustomArgumentCompleters { get; set; } + internal Dictionary NativeArgumentCompleters { get; set; } /// /// Routine to create a command(processor) instance using the factory. /// - /// The name of the command to lookup + /// The name of the command to lookup. /// - /// The command processor object - internal CommandProcessorBase CreateCommand(string command, bool dotSource) + /// + /// The command processor object. + internal CommandProcessorBase CreateCommand(string command, bool dotSource, bool forCompletion = false) { CommandOrigin commandOrigin = this.EngineSessionState.CurrentScope.ScopeOrigin; CommandProcessorBase commandProcessor = - CommandDiscovery.LookupCommandProcessor(command, commandOrigin, !dotSource); - // Reset the command origin for script commands... //BUGBUG - dotting can get around command origin checks??? + 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) { commandProcessor.Command.CommandOriginInternal = CommandOrigin.Internal; @@ -572,7 +655,6 @@ internal CommandProcessorBase CreateCommand(string command, bool dotSource) /// Reference to command discovery internal CommandProcessorBase CurrentCommandProcessor { get; set; } - /// /// Redirect to the CommandDiscovery in the engine. /// @@ -585,13 +667,13 @@ internal CommandDiscovery CommandDiscovery } } - /// - /// Interface that should be used for interaction with host + /// Interface that should be used for interaction with host. /// - internal InternalHost EngineHostInterface { get; private set; - + internal InternalHost EngineHostInterface + { // set not provided: it's not meaningful to change the host post-construction. + get; private set; } /// @@ -605,44 +687,44 @@ internal InternalHost InternalHost get { return EngineHostInterface; } } - /// - /// Interface to the public API for the engine + /// Interface to the public API for the engine. /// internal EngineIntrinsics EngineIntrinsics { - get { return _engineIntrinsics ?? (_engineIntrinsics = new EngineIntrinsics(this)); } + get { return _engineIntrinsics ??= new EngineIntrinsics(this); } } + private EngineIntrinsics _engineIntrinsics; /// - /// Log context cache + /// Log context cache. /// internal LogContextCache LogContextCache { get; } = new LogContextCache(); #region Output pipes /// - /// The PipelineWriter provided by the connection object for success output + /// The PipelineWriter provided by the connection object for success output. /// internal PipelineWriter ExternalSuccessOutput { get; set; } /// - /// The PipelineWriter provided by the connection object for error output + /// The PipelineWriter provided by the connection object for error output. /// internal PipelineWriter ExternalErrorOutput { get; set; } /// - /// The PipelineWriter provided by the connection object for progress output + /// The PipelineWriter provided by the connection object for progress output. /// internal PipelineWriter ExternalProgressOutput { get; set; } 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) { @@ -664,7 +746,7 @@ public void RestoreContextData(ExecutionContext context) } /// - /// Host uses this to saves context data when entering a nested prompt + /// Host uses this to saves context data when entering a nested prompt. /// /// internal SavedContextData SaveContextData() @@ -683,10 +765,6 @@ internal Pipe RedirectErrorPipe(Pipe newPipe) ShellFunctionErrorOutputPipe = 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 @@ -740,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 (null == arraylist) + if (DollarErrorVariable is not ArrayList arraylist) { Diagnostics.Assert(false, "$error should be a global constant ArrayList"); return; @@ -771,14 +847,15 @@ 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, numToErase); } + arraylist.Insert(0, obj); - } // AppendDollarError + } #endregion #region Scope or Commands (in pipeline) Depth Count @@ -804,16 +881,17 @@ internal static void CheckStackDepth() #endregion /// - /// The current connection object + /// The current connection object. /// private Runspace _currentRunspace; - //This should be internal, but it need to be friend of remoting dll. + // This should be internal, but it need to be friend of remoting dll. /// - /// The current connection object + /// The current connection object. /// internal Runspace CurrentRunspace { get { return _currentRunspace; } + set { _currentRunspace = value; } } @@ -834,7 +912,7 @@ internal void PushPipelineProcessor(PipelineProcessor pp) /// /// Each pipeline has a stack of pipeline processor. This method pops the - /// top item from the stack + /// top item from the stack. /// internal void PopPipelineProcessor(bool fromSteppablePipeline) { @@ -880,7 +958,7 @@ internal bool CurrentPipelineStopping internal bool QuestionMarkVariableValue { get; set; } = true; /// - /// Shortcut to get at $error + /// Shortcut to get at $error. /// /// The current value of $global:error internal object DollarErrorVariable @@ -904,6 +982,7 @@ internal object DollarErrorVariable return resultItem; } + set { EngineSessionState.SetVariable( @@ -916,11 +995,12 @@ internal ActionPreference DebugPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.DebugPreferenceVarPath, - InitialSessionState.defaultDebugPreference, + InitialSessionState.DefaultDebugPreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -936,11 +1016,12 @@ internal ActionPreference VerbosePreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.VerbosePreferenceVarPath, - InitialSessionState.defaultVerbosePreference, + InitialSessionState.DefaultVerbosePreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -956,11 +1037,12 @@ internal ActionPreference ErrorActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.ErrorActionPreferenceVarPath, - InitialSessionState.defaultErrorActionPreference, + InitialSessionState.DefaultErrorActionPreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -976,11 +1058,12 @@ internal ActionPreference WarningActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.WarningPreferenceVarPath, - InitialSessionState.defaultWarningPreference, + InitialSessionState.DefaultWarningPreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -996,11 +1079,12 @@ internal ActionPreference InformationActionPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.InformationPreferenceVarPath, - InitialSessionState.defaultInformationPreference, + InitialSessionState.DefaultInformationPreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -1025,6 +1109,7 @@ internal object WhatIfPreferenceVariable return resultItem; } + set { this.EngineSessionState.SetVariable( @@ -1040,11 +1125,12 @@ internal ConfirmImpact ConfirmPreferenceVariable get { bool defaultUsed = false; - return this.GetEnumPreference( + return this.GetEnumPreference( SpecialVariables.ConfirmPreferenceVarPath, - InitialSessionState.defaultConfirmPreference, + InitialSessionState.DefaultConfirmPreference, out defaultUsed); } + set { this.EngineSessionState.SetVariable( @@ -1059,19 +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; } @@ -1087,8 +1166,10 @@ internal TypeTable TypeTable _typeTable = new TypeTable(); _typeTableWeakReference = new WeakReference(_typeTable); } + return _typeTable; } + set { _typeTable = value; @@ -1107,6 +1188,7 @@ internal WeakReference TypeTableWeakReference { var unused = TypeTable; } + return _typeTableWeakReference; } } @@ -1134,6 +1216,7 @@ internal TypeInfoDataBaseManager FormatDBManager _formatDBManager.DisableFormatTableUpdates = this.InitialSessionState.DisableFormatUpdates; } } + return _formatDBManager; } @@ -1142,6 +1225,7 @@ internal TypeInfoDataBaseManager FormatDBManager _formatDBManager = value; } } + private TypeInfoDataBaseManager _formatDBManager; /// @@ -1155,56 +1239,154 @@ internal PSTransactionManager TransactionManager return transactionManager; } } + internal PSTransactionManager transactionManager; - internal Assembly AddAssembly(string name, string filename, out Exception error) + /// + /// 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) { - Assembly loadedAssembly = LoadAssembly(name, filename, out error); - - if (loadedAssembly == null) - return null; - - if (AssemblyCache.ContainsKey(loadedAssembly.FullName)) + // 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); } } - [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) + /// + /// 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); + } + + 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) @@ -1229,7 +1411,7 @@ internal static Assembly LoadAssembly(string name, string filename, out Exceptio } // Then we try to load the assembly based on the given name - if (!String.IsNullOrEmpty(name)) + if (!string.IsNullOrEmpty(name)) { string fixedName = null; // Remove the '.dll' if it's there... @@ -1263,19 +1445,20 @@ internal static Assembly LoadAssembly(string name, string filename, out Exceptio } } - // If the assembly is loaded, we ignore error as it may come from the filepath loading. + // If the assembly is loaded, we ignore error as it may come from the filepath loading. if (loadedAssembly != null) { error = null; } + return loadedAssembly; } /// /// Report an initialization-time error. /// - /// resource string - /// arguments + /// Resource string. + /// Arguments. internal void ReportEngineStartupError(string resourceString, params object[] arguments) { try @@ -1290,9 +1473,17 @@ internal void ReportEngineStartupError(string resourceString, params object[] ar else { PSHost host = EngineHostInterface; - if (null == host) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (null == ui) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine( StringUtil.Format(resourceString, arguments)); } @@ -1303,9 +1494,9 @@ internal void ReportEngineStartupError(string resourceString, params object[] ar } /// - /// Report an initialization-time error + /// Report an initialization-time error. /// - /// error to report + /// Error to report. internal void ReportEngineStartupError(string error) { try @@ -1320,9 +1511,17 @@ internal void ReportEngineStartupError(string error) else { PSHost host = EngineHostInterface; - if (null == host) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (null == ui) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(error); } } @@ -1332,7 +1531,7 @@ internal void ReportEngineStartupError(string error) } /// - /// Report an initialization-time error + /// Report an initialization-time error. /// /// internal void ReportEngineStartupError(Exception e) @@ -1355,9 +1554,17 @@ internal void ReportEngineStartupError(Exception e) else { PSHost host = EngineHostInterface; - if (null == host) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (null == ui) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(e.Message); } } @@ -1367,7 +1574,7 @@ internal void ReportEngineStartupError(Exception e) } /// - /// Report an initialization-time error + /// Report an initialization-time error. /// /// internal void ReportEngineStartupError(ErrorRecord errorRecord) @@ -1375,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 (null == host) return; + if (host == null) + { + return; + } + PSHostUserInterface ui = host.UI; - if (null == ui) return; + if (ui == null) + { + return; + } + ui.WriteErrorLine(errorRecord.ToString()); } } @@ -1402,14 +1616,14 @@ private bool IsModuleCommandCurrentlyRunning(out Cmdlet command, out string erro if (this.CurrentCommandProcessor != null) { CommandInfo cmdletInfo = this.CurrentCommandProcessor.CommandInfo; - if ((String.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase) || - String.Equals(cmdletInfo.Name, "Remove-Module", StringComparison.OrdinalIgnoreCase)) && + if ((string.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase) || + string.Equals(cmdletInfo.Name, "Remove-Module", StringComparison.OrdinalIgnoreCase)) && cmdletInfo.CommandType.Equals(CommandTypes.Cmdlet) && InitialSessionState.CoreModule.Equals(cmdletInfo.ModuleName, StringComparison.OrdinalIgnoreCase)) { result = true; command = (Cmdlet)this.CurrentCommandProcessor.Command; - errorId = String.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase) + errorId = string.Equals(cmdletInfo.Name, "Import-Module", StringComparison.OrdinalIgnoreCase) ? "Module_ImportModuleError" : "Module_RemoveModuleError"; } @@ -1419,9 +1633,8 @@ private bool IsModuleCommandCurrentlyRunning(out Cmdlet command, out string erro } /// - /// Constructs an Execution context object for Automation Engine + /// Constructs an Execution context object for Automation Engine. /// - /// /// /// Engine that hosts this execution context /// @@ -1442,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); @@ -1466,56 +1662,25 @@ 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); } -#if !CORECLR // System.AppDomain is not in CoreCLR - private static bool _assemblyEventHandlerSet = false; - private static object lockObject = new Object(); - - /// - /// 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(); } /// /// Enum that defines state of monad engine. /// - /// internal enum EngineState { /// @@ -1524,23 +1689,23 @@ internal enum EngineState None = 0, /// - /// Engine available + /// Engine available. /// Available = 1, /// - /// Engine service is degraded + /// Engine service is degraded. /// Degraded = 2, /// - /// Engine is out of service + /// Engine is out of service. /// OutOfService = 3, /// - /// Engine is stopped + /// 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 new file mode 100644 index 00000000000..af09f427253 --- /dev/null +++ b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for Enable/Disable-ExperimentalFeature cmdlet. + /// + public class EnableDisableExperimentalFeatureCommandBase : PSCmdlet + { + /// + /// Gets or sets the feature names. + /// + [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)] + [ArgumentCompleter(typeof(ExperimentalFeatureNameCompleter))] + public string[] Name { get; set; } + + /// + /// Gets or sets the scope of persistence of updating the PowerShell configuration json. + /// + [Parameter] + public ConfigScope Scope { get; set; } = ConfigScope.CurrentUser; + + /// + /// EndProcessing method. + /// + protected override void EndProcessing() + { + WriteWarning(ExperimentalFeatureStrings.ExperimentalFeaturePending); + } + } + + /// + /// Implements Enable-ExperimentalFeature cmdlet. + /// + [Cmdlet(VerbsLifecycle.Enable, "ExperimentalFeature", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2046964")] + public class EnableExperimentalFeatureCommand : EnableDisableExperimentalFeatureCommandBase + { + /// + /// ProcessRecord method of this cmdlet. + /// + protected override void ProcessRecord() + { + ExperimentalFeatureConfigHelper.UpdateConfig(this, Name, Scope, enable: true); + } + } + + /// + /// Implements Enable-ExperimentalFeature cmdlet. + /// + [Cmdlet(VerbsLifecycle.Disable, "ExperimentalFeature", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2046963")] + public class DisableExperimentalFeatureCommand : EnableDisableExperimentalFeatureCommandBase + { + /// + /// ProcessRecord method of this cmdlet. + /// + protected override void ProcessRecord() + { + ExperimentalFeatureConfigHelper.UpdateConfig(this, Name, Scope, enable: false); + } + } + + internal static class ExperimentalFeatureConfigHelper + { + internal static void UpdateConfig(PSCmdlet cmdlet, string[] name, ConfigScope scope, bool enable) + { + IEnumerable namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(name, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + GetExperimentalFeatureCommand getExperimentalFeatureCommand = new GetExperimentalFeatureCommand(); + getExperimentalFeatureCommand.Context = cmdlet.Context; + bool foundFeature = false; + foreach (ExperimentalFeature feature in getExperimentalFeatureCommand.GetAvailableExperimentalFeatures(namePatterns)) + { + foundFeature = true; + if (!cmdlet.ShouldProcess(feature.Name)) + { + return; + } + + PowerShellConfig.Instance.SetExperimentalFeatures(scope, feature.Name, enable); + } + + if (!foundFeature) + { + string errMsg = string.Format(CultureInfo.InvariantCulture, ExperimentalFeatureStrings.ExperimentalFeatureNameNotFound, name); + cmdlet.WriteError(new ErrorRecord(new ItemNotFoundException(errMsg), "ItemNotFoundException", ErrorCategory.ObjectNotFound, name)); + return; + } + } + } + + /// + /// Provides argument completion for ExperimentalFeature names. + /// + public class ExperimentalFeatureNameCompleter : IArgumentCompleter + { + /// + /// Returns completion results for experimental feature names used as arguments to experimental feature cmdlets. + /// + /// 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) + { + SortedSet expirmentalFeatures = new(StringComparer.OrdinalIgnoreCase); + + foreach (ExperimentalFeature feature in GetExperimentalFeatures()) + { + expirmentalFeatures.Add(feature.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 new file mode 100644 index 00000000000..7e17ec43137 --- /dev/null +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -0,0 +1,392 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; +using System.Management.Automation.Tracing; +using System.Runtime.CompilerServices; +using Microsoft.PowerShell.Telemetry; + +namespace System.Management.Automation +{ + /// + /// Support experimental features in PowerShell. + /// + public class ExperimentalFeature + { + #region Const Members + + internal const string EngineSource = "PSEngine"; + internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); + internal const string PSProfileDSCResource = "PSProfileDSCResource"; + + #endregion + + #region Instance Members + + /// + /// Name of an experimental feature. + /// + public string Name { get; } + + /// + /// Description of an experimental feature. + /// + public string Description { get; } + + /// + /// Source of an experimental feature. + /// + public string Source { get; } + + /// + /// Indicate whether the feature is enabled. + /// + public bool Enabled { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the experimental feature. + /// A description of the experimental feature. + /// The source where the experimental feature is defined. + /// Indicate whether the experimental feature is enabled. + internal ExperimentalFeature(string name, string description, string source, bool isEnabled) + { + Name = name; + Description = description; + Source = source; + Enabled = isEnabled; + } + + /// + /// Initializes a new instance of the class. + /// This is a private constructor only for declaring new experimental features within this type. + /// + /// The name of the experimental feature. + /// A description of the experimental feature. + private ExperimentalFeature(string name, string description) + : this(name, description, source: EngineSource, isEnabled: false) + { + } + + #endregion + + #region Static Members + + /// + /// All available engine experimental features. + /// + internal static readonly ReadOnlyCollection EngineExperimentalFeatures; + + /// + /// A dictionary of all available engine experimental features. Feature name is the key. + /// + internal static readonly ReadOnlyDictionary EngineExperimentalFeatureMap; + + /// + /// Experimental feature names that are enabled in the config file. + /// + internal static readonly ReadOnlyBag EnabledExperimentalFeatureNames; + + /// + /// Type initializer. Initialize the engine experimental feature list. + /// + static ExperimentalFeature() + { + // Initialize the readonly collection 'EngineExperimentalFeatures'. + var engineFeatures = new ExperimentalFeature[] { + /* Register engine experimental features here. Follow the same pattern as the example: + new ExperimentalFeature( + name: "PSFileSystemProviderV2", + description: "Replace the old FileSystemProvider with cleaner design and faster code"), + */ + new ExperimentalFeature( + name: "PSLoadAssemblyFromNativeCode", + description: "Expose an API to allow assembly loading from native code"), + new ExperimentalFeature( + name: PSSerializeJSONLongEnumAsNumber, + description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ), + new ExperimentalFeature( + name: PSProfileDSCResource, + description: "DSC v3 resources for managing PowerShell profile." + ) + }; + + EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); + + // Initialize the readonly dictionary 'EngineExperimentalFeatureMap'. + var engineExpFeatureMap = engineFeatures.ToDictionary(static f => f.Name, StringComparer.OrdinalIgnoreCase); + EngineExperimentalFeatureMap = new ReadOnlyDictionary(engineExpFeatureMap); + + // Initialize the readonly hashset 'EnabledExperimentalFeatureNames'. + // The initialization of 'EnabledExperimentalFeatureNames' is deliberately made in the type initializer so that: + // 1. 'EnabledExperimentalFeatureNames' can be declared as readonly; + // 2. No need to deal with initialization from multiple threads; + // 3. We don't need to decide where/when to read the config file for the enabled experimental features, + // instead, it will be done when the type is used for the first time, which is always earlier than + // any experimental features take effect. + string[] enabledFeatures = Array.Empty(); + try + { + enabledFeatures = PowerShellConfig.Instance.GetExperimentalFeatures(); + } + catch (Exception e) when (LogException(e)) { } + + 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 + /// return an ReadOnlyBag of the valid enabled feature names. + /// + private static ReadOnlyBag ProcessEnabledFeatures(string[] enabledFeatures) + { + if (enabledFeatures.Length == 0) + { + return ReadOnlyBag.Empty; + } + + var list = new List(enabledFeatures.Length); + foreach (string name in enabledFeatures) + { + if (IsModuleFeatureName(name)) + { + list.Add(name); + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalModuleFeatureActivation, name); + } + else if (IsEngineFeatureName(name)) + { + if (EngineExperimentalFeatureMap.TryGetValue(name, out ExperimentalFeature feature)) + { + feature.Enabled = true; + list.Add(name); + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalEngineFeatureActivation, name); + } + else + { + string message = StringUtil.Format(Logging.EngineExperimentalFeatureNotFound, name); + LogError(PSEventId.ExperimentalFeature_InvalidName, name, message); + } + } + else + { + string message = StringUtil.Format(Logging.InvalidExperimentalFeatureName, name); + LogError(PSEventId.ExperimentalFeature_InvalidName, name, message); + } + } + + ReadOnlyBag features = new(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + SendTelemetryForDeactivatedFeatures(features); + return features; + } + + /// + /// Log the exception without rewinding the stack. + /// + private static bool LogException(Exception e) + { + LogError(PSEventId.ExperimentalFeature_ReadConfig_Error, e.GetType().FullName, e.Message, e.StackTrace); + return false; + } + + /// + /// Log an error message. + /// + private static void LogError(PSEventId eventId, params object[] args) + { + PSEtwLog.LogOperationalError(eventId, PSOpcode.Constructor, PSTask.ExperimentalFeature, PSKeyword.UseAlwaysOperational, args); + } + + /// + /// Check if the name follows the engine experimental feature name convention. + /// Convention: prefix 'PS' to the feature name -- 'PSFeatureName'. + /// + internal static bool IsEngineFeatureName(string featureName) + { + return featureName.Length > 2 && !featureName.Contains('.') && featureName.StartsWith("PS", StringComparison.Ordinal); + } + + /// + /// Check if the name follows the module experimental feature name convention. + /// Convention: prefix the module name to the feature name -- 'ModuleName.FeatureName'. + /// + /// The feature name to check. + /// When specified, we check if the feature name matches the module name. + internal static bool IsModuleFeatureName(string featureName, string moduleName = null) + { + // Feature names cannot start with a dot + if (featureName.StartsWith('.')) + { + return false; + } + + // Feature names must contain a dot, but not at the end + int lastDotIndex = featureName.LastIndexOf('.'); + if (lastDotIndex == -1 || lastDotIndex == featureName.Length - 1) + { + return false; + } + + if (moduleName == null) + { + return true; + } + + // If the module name is given, it must match the prefix of the feature name (up to the last dot). + var moduleNamePart = featureName.AsSpan(0, lastDotIndex); + return moduleNamePart.Equals(moduleName.AsSpan(), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determine the action to take for the specified experiment name and action. + /// + internal static ExperimentAction GetActionToTake(string experimentName, ExperimentAction experimentAction) + { + if (experimentName == null || experimentAction == ExperimentAction.None) + { + // If either the experiment name or action is not defined, then return 'Show' by default. + // This could happen to 'ParameterAttribute' when no experimental related field is declared. + return ExperimentAction.Show; + } + + ExperimentAction action = experimentAction; + if (!IsEnabled(experimentName)) + { + action = (action == ExperimentAction.Hide) ? ExperimentAction.Show : ExperimentAction.Hide; + } + + return action; + } + + /// + /// Check if the specified experimental feature has been enabled. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEnabled(string featureName) + { + return EnabledExperimentalFeatureNames.Contains(featureName); + } + + #endregion + } + + /// + /// Indicates the action to take on the cmdlet/parameter that has the attribute declared. + /// + public enum ExperimentAction + { + /// + /// Represent an undefined action, used as the default value. + /// + None = 0, + + /// + /// Hide the cmdlet/parameter when the corresponding experimental feature is enabled. + /// + Hide = 1, + + /// + /// Show the cmdlet/parameter when the corresponding experimental feature is enabled. + /// + Show = 2 + } + + /// + /// The attribute that applies to cmdlet/function/parameter to define what the engine should do with it. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExperimentalAttribute : ParsingBaseAttribute + { + /// + /// Get name of the experimental feature this attribute is associated with. + /// + public string ExperimentName { get; } + + /// + /// Get action for engine to take when the experimental feature is enabled. + /// + public ExperimentAction ExperimentAction { get; } + + /// + /// Initializes a new instance of the ExperimentalAttribute class. + /// + public ExperimentalAttribute(string experimentName, ExperimentAction experimentAction) + { + ValidateArguments(experimentName, experimentAction); + ExperimentName = experimentName; + ExperimentAction = experimentAction; + } + + /// + /// Initialize an instance that represents the none-value. + /// + private ExperimentalAttribute() { } + + /// + /// An instance that represents the none-value. + /// + internal static readonly ExperimentalAttribute None = new ExperimentalAttribute(); + + /// + /// Validate arguments for the constructor. + /// + internal static void ValidateArguments(string experimentName, ExperimentAction experimentAction) + { + if (string.IsNullOrEmpty(experimentName)) + { + const string paramName = nameof(experimentName); + throw PSTraceSource.NewArgumentNullException(paramName, Metadata.ArgumentNullOrEmpty, paramName); + } + + if (experimentAction == 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; + + /// + /// Get effective action to take at run time. + /// + private ExperimentAction EffectiveAction + { + get + { + if (_effectiveAction == ExperimentAction.None) + { + _effectiveAction = ExperimentalFeature.GetActionToTake(ExperimentName, ExperimentAction); + } + + return _effectiveAction; + } + } + + private ExperimentAction _effectiveAction = ExperimentAction.None; + } +} diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs new file mode 100644 index 00000000000..38525cb54ef --- /dev/null +++ b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Internal; + +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 + { + /// + /// Get and set the feature names. + /// + [Parameter(ValueFromPipeline = true, Position = 0)] + [ArgumentCompleter(typeof(ExperimentalFeatureNameCompleter))] + [ValidateNotNullOrEmpty] + public string[] Name { get; set; } + + /// + /// ProcessRecord method of this cmdlet. + /// + protected override void ProcessRecord() + { + const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; + IEnumerable namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(Name, wildcardOptions); + + foreach (ExperimentalFeature feature in GetAvailableExperimentalFeatures(namePatterns).OrderBy(GetSortingString)) + { + WriteObject(feature); + } + } + + /// + /// Construct the string for sorting experimental feature records. + /// + /// + /// Engine features come before module features. + /// Within engine features and module features, features are ordered by name. + /// + private static (int, string) GetSortingString(ExperimentalFeature feature) + { + return ExperimentalFeature.EngineSource.Equals(feature.Source, StringComparison.OrdinalIgnoreCase) + ? (0, feature.Name) + : (1, feature.Name); + } + + /// + /// Get available experimental features based on the specified name patterns. + /// + internal IEnumerable GetAvailableExperimentalFeatures(IEnumerable namePatterns) + { + foreach (ExperimentalFeature feature in ExperimentalFeature.EngineExperimentalFeatures) + { + if (SessionStateUtilities.MatchesAnyWildcardPattern(feature.Name, namePatterns, defaultValue: true)) + { + yield return feature; + } + } + + foreach (string moduleFile in GetValidModuleFiles(moduleNamesToFind: null)) + { + ExperimentalFeature[] features = ModuleIntrinsics.GetExperimentalFeature(moduleFile); + foreach (var feature in features) + { + if (SessionStateUtilities.MatchesAnyWildcardPattern(feature.Name, namePatterns, defaultValue: true)) + { + yield return feature; + } + } + } + } + + /// + /// Get valid module files from module paths. + /// + private IEnumerable GetValidModuleFiles(HashSet moduleNamesToFind) + { + var modulePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (string path in ModuleIntrinsics.GetModulePath(includeSystemModulePath: false, Context)) + { + string uniquePath = path.TrimEnd(Utils.Separators.Directory); + 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 (moduleNamesToFind != null) + { + string currentModuleName = ModuleIntrinsics.GetModuleName(moduleFile); + 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 e9271edffb4..06271728e00 100644 --- a/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs +++ b/src/System.Management.Automation/engine/ExtendedTypeSystemException.cs @@ -1,81 +1,85 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; using System.Management.Automation.Internal; +using System.Runtime.Serialization; using System.Security.Permissions; namespace System.Management.Automation { /// - /// Defines the exception thrown for all Extended type system related errors + /// Defines the exception thrown for all Extended type system related errors. /// - [Serializable] public class ExtendedTypeSystemException : RuntimeException { #region ctor /// /// Initializes a new instance of ExtendedTypeSystemException with the message set - /// to typeof(ExtendedTypeSystemException).FullName + /// to typeof(ExtendedTypeSystemException).FullName. /// - public ExtendedTypeSystemException() : base(typeof(ExtendedTypeSystemException).FullName) + public ExtendedTypeSystemException() + : base(typeof(ExtendedTypeSystemException).FullName) { } /// - /// Initializes a new instance of ExtendedTypeSystemException setting the message + /// Initializes a new instance of ExtendedTypeSystemException setting the message. /// - /// the exception's message - public ExtendedTypeSystemException(string message) : base(message) + /// The exception's message. + public ExtendedTypeSystemException(string message) + : base(message) { } /// - /// Initializes a new instance of ExtendedTypeSystemException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public ExtendedTypeSystemException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { SetErrorId(errorId); } - #region Serialization /// - /// Initializes a new instance of ExtendedTypeSystemException with serialization parameters + /// Initializes a new instance of ExtendedTypeSystemException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // ExtendedTypeSystemException - + } /// - /// Defines the exception thrown for Method related errors + /// Defines the exception thrown for Method related errors. /// - [Serializable] public class MethodException : ExtendedTypeSystemException { internal const string MethodArgumentCountExceptionMsg = "MethodArgumentCountException"; @@ -87,63 +91,68 @@ public class MethodException : ExtendedTypeSystemException #region ctor /// /// Initializes a new instance of MethodException with the message set - /// to typeof(MethodException).FullName + /// to typeof(MethodException).FullName. /// - public MethodException() : base(typeof(MethodException).FullName) + public MethodException() + : base(typeof(MethodException).FullName) { } /// - /// Initializes a new instance of MethodException setting the message + /// Initializes a new instance of MethodException setting the message. /// - /// the exception's message - public MethodException(string message) : base(message) + /// The exception's message. + public MethodException(string message) + : base(message) { } /// - /// Initializes a new instance of MethodException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public MethodException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of MethodException with serialization parameters + /// Initializes a new instance of MethodException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // MethodException + } /// - /// Defines the exception thrown for Method invocation exceptions + /// Defines the exception thrown for Method invocation exceptions. /// - [Serializable] public class MethodInvocationException : MethodException { internal const string MethodInvocationExceptionMsg = "MethodInvocationException"; @@ -153,63 +162,68 @@ public class MethodInvocationException : MethodException #region ctor /// /// Initializes a new instance of MethodInvocationException with the message set - /// to typeof(MethodInvocationException).FullName + /// to typeof(MethodInvocationException).FullName. /// - public MethodInvocationException() : base(typeof(MethodInvocationException).FullName) + public MethodInvocationException() + : base(typeof(MethodInvocationException).FullName) { } /// - /// Initializes a new instance of MethodInvocationException setting the message + /// Initializes a new instance of MethodInvocationException setting the message. /// - /// the exception's message - public MethodInvocationException(string message) : base(message) + /// The exception's message. + public MethodInvocationException(string message) + : base(message) { } /// - /// Initializes a new instance of MethodInvocationException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public MethodInvocationException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of MethodInvocationException with serialization parameters + /// Initializes a new instance of MethodInvocationException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // GetValueInvocationException + } /// - /// Defines the exception thrown for errors getting the value of properties + /// Defines the exception thrown for errors getting the value of properties. /// - [Serializable] public class GetValueException : ExtendedTypeSystemException { internal const string GetWithoutGetterExceptionMsg = "GetWithoutGetterException"; @@ -217,70 +231,74 @@ public class GetValueException : ExtendedTypeSystemException #region ctor /// /// Initializes a new instance of GetValueException with the message set - /// to typeof(GetValueException).FullName + /// to typeof(GetValueException).FullName. /// - public GetValueException() : base(typeof(GetValueException).FullName) + public GetValueException() + : base(typeof(GetValueException).FullName) { } /// - /// Initializes a new instance of GetValueException setting the message + /// Initializes a new instance of GetValueException setting the message. /// - /// the exception's message - public GetValueException(string message) : base(message) + /// The exception's message. + public GetValueException(string message) + : base(message) { } /// - /// Initializes a new instance of GetValueException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public GetValueException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of GetValueException with serialization parameters + /// Initializes a new instance of GetValueException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // GetValueException - + } /// - /// Defines the exception thrown for errors getting the value of properties + /// Defines the exception thrown for errors getting the value of properties. /// - [Serializable] public class PropertyNotFoundException : ExtendedTypeSystemException { #region ctor /// /// Initializes a new instance of GetValueException with the message set - /// to typeof(GetValueException).FullName + /// to typeof(GetValueException).FullName. /// public PropertyNotFoundException() : base(typeof(PropertyNotFoundException).FullName) @@ -288,58 +306,59 @@ public PropertyNotFoundException() } /// - /// Initializes a new instance of GetValueException setting the message + /// Initializes a new instance of GetValueException setting the message. /// - /// the exception's message + /// The exception's message. public PropertyNotFoundException(string message) : base(message) { } /// - /// Initializes a new instance of GetValueException setting the message and innerException + /// Initializes a new instance of GetValueException setting the message and innerException. /// - /// the exception's message - /// the exceptions's inner exception + /// The exception's message. + /// The exception's inner exception. public PropertyNotFoundException(string message, Exception innerException) : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of GetValueException with serialization parameters + /// Initializes a new instance of GetValueException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 - } // PropertyNotFoundException + } /// - /// Defines the exception thrown for exceptions thrown by property getters + /// Defines the exception thrown for exceptions thrown by property getters. /// - [Serializable] public class GetValueInvocationException : GetValueException { internal const string ExceptionWhenGettingMsg = "ExceptionWhenGetting"; @@ -347,240 +366,238 @@ public class GetValueInvocationException : GetValueException #region ctor /// /// Initializes a new instance of GetValueInvocationException with the message set - /// to typeof(GetValueInvocationException).FullName + /// to typeof(GetValueInvocationException).FullName. /// - public GetValueInvocationException() : base(typeof(GetValueInvocationException).FullName) + public GetValueInvocationException() + : base(typeof(GetValueInvocationException).FullName) { } /// - /// Initializes a new instance of GetValueInvocationException setting the message + /// Initializes a new instance of GetValueInvocationException setting the message. /// - /// the exception's message - public GetValueInvocationException(string message) : base(message) + /// The exception's message. + public GetValueInvocationException(string message) + : base(message) { } /// - /// Initializes a new instance of GetValueInvocationException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public GetValueInvocationException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of GetValueInvocationException with serialization parameters + /// Initializes a new instance of GetValueInvocationException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // GetValueInvocationException + } /// - /// Defines the exception thrown for errors setting the value of properties + /// Defines the exception thrown for errors setting the value of properties. /// - [Serializable] public class SetValueException : ExtendedTypeSystemException { #region ctor /// /// Initializes a new instance of SetValueException with the message set - /// to typeof(SetValueException).FullName + /// to typeof(SetValueException).FullName. /// - public SetValueException() : base(typeof(SetValueException).FullName) + public SetValueException() + : base(typeof(SetValueException).FullName) { } /// - /// Initializes a new instance of SetValueException setting the message + /// Initializes a new instance of SetValueException setting the message. /// - /// the exception's message - public SetValueException(string message) : base(message) + /// The exception's message. + public SetValueException(string message) + : base(message) { } /// - /// Initializes a new instance of SetValueException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public SetValueException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of SetValueException with serialization parameters + /// Initializes a new instance of SetValueException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // SetValueException + } /// - /// Defines the exception thrown for exceptions thrown by property setters + /// Defines the exception thrown for exceptions thrown by property setters. /// - [Serializable] public class SetValueInvocationException : SetValueException { #region ctor /// /// Initializes a new instance of SetValueInvocationException with the message set - /// to typeof(SetValueInvocationException).FullName + /// to typeof(SetValueInvocationException).FullName. /// - public SetValueInvocationException() : base(typeof(SetValueInvocationException).FullName) + public SetValueInvocationException() + : base(typeof(SetValueInvocationException).FullName) { } /// - /// Initializes a new instance of SetValueInvocationException setting the message + /// Initializes a new instance of SetValueInvocationException setting the message. /// - /// the exception's message - public SetValueInvocationException(string message) : base(message) + /// The exception's message. + public SetValueInvocationException(string message) + : base(message) { } /// - /// Initializes a new instance of SetValueInvocationException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public SetValueInvocationException(string message, Exception innerException) + : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// 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) + /// String that uniquely identifies each thrown Exception. + /// 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) { } - #region Serialization /// - /// Initializes a new instance of SetValueInvocationException with serialization parameters + /// Initializes a new instance of SetValueInvocationException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // SetValueInvocationException + } /// - /// Defines the exception thrown for type conversion errors + /// 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. + /// Initializes a new instance of PSInvalidCastException with serialization parameters. /// - /// The to populate with data. - /// The destination for this serialization. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected PSInvalidCastException(SerializationInfo info, StreamingContext context) { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } - /// - /// Initializes a new instance of PSInvalidCastException with serialization parameters - /// - /// serialization information - /// streaming context - protected PSInvalidCastException(SerializationInfo info, StreamingContext context) : base(info, context) - { - _errorId = info.GetString("ErrorId"); - } - - #endregion Serialization /// /// Initializes a new instance of PSInvalidCastException with the message set - /// to typeof(PSInvalidCastException).FullName + /// 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 + /// Initializes a new instance of PSInvalidCastException setting the message. /// - /// the exception's message - public PSInvalidCastException(string message) : base(message) + /// The exception's message. + public PSInvalidCastException(string message) + : base(message) { } /// - /// Initializes a new instance of PSInvalidCastException setting the message and innerException + /// 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 message. + /// The exception's inner exception. + public PSInvalidCastException(string message, Exception innerException) + : base(message, innerException) { } @@ -590,32 +607,35 @@ 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) { } /// - /// Gets the ErrorRecord associated with this exception + /// Gets the ErrorRecord associated with this exception. /// public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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 399ec441a4a..e8c8c2b1d54 100644 --- a/src/System.Management.Automation/engine/ExternalScriptInfo.cs +++ b/src/System.Management.Automation/engine/ExternalScriptInfo.cs @@ -1,20 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; -using System.Text; -using System.Collections.ObjectModel; -using System.Management.Automation.Runspaces; -using Microsoft.PowerShell.Commands; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Management.Automation.Security; +using System.Text; + +using Microsoft.PowerShell.Commands; 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 @@ -24,33 +24,27 @@ public class ExternalScriptInfo : CommandInfo, IScriptCommandInfo /// /// Creates an instance of the ExternalScriptInfo class with the specified name, and path. /// - /// /// /// The name of the script. /// - /// /// /// The path to the script /// - /// /// /// The context of the currently running engine. /// - /// /// /// If is null. /// - /// /// /// If is null or empty. /// - /// internal ExternalScriptInfo(string name, string path, ExecutionContext context) : base(name, CommandTypes.ExternalScript, context) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } Diagnostics.Assert(IO.Path.IsPathRooted(path), "Caller makes sure that 'path' is already resolved."); @@ -62,25 +56,22 @@ internal ExternalScriptInfo(string name, string path, ExecutionContext context) /// /// Creates an instance of ExternalScriptInfo that has no ExecutionContext. - /// This is used exclusively to pass it to the AuthorizationManager that just uses the path parameter + /// This is used exclusively to pass it to the AuthorizationManager that just uses the path parameter. /// /// /// The name of the script. /// - /// /// /// The path to the script /// - /// /// /// If is null or empty. /// - /// internal ExternalScriptInfo(string name, string path) : base(name, CommandTypes.ExternalScript) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } Diagnostics.Assert(IO.Path.IsPathRooted(path), "Caller makes sure that 'path' is already resolved."); @@ -101,7 +92,7 @@ internal ExternalScriptInfo(ExternalScriptInfo other) } /// - /// Common initialization for all constructors + /// Common initialization for all constructors. /// private void CommonInitialization() { @@ -112,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) - { - this.DefiningLanguageMode = PSLanguageMode.FullLanguage; - } - else + switch (SystemPolicy.GetLockdownPolicy(_path, null)) { - 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; } } } @@ -148,7 +146,8 @@ public string Path { get { return _path; } } - private readonly string _path = String.Empty; + + private readonly string _path = string.Empty; /// /// Gets the path to the script file. @@ -159,7 +158,7 @@ public override string Definition } /// - /// Gets the source of this command + /// Gets the source of this command. /// public override string Source { @@ -167,7 +166,7 @@ public override string Source } /// - /// Returns the syntax of a command + /// Returns the syntax of a command. /// internal override string Syntax { @@ -178,7 +177,7 @@ internal override string Syntax foreach (CommandParameterSetInfo parameterSet in ParameterSets) { synopsis.AppendLine( - String.Format( + string.Format( Globalization.CultureInfo.CurrentCulture, "{0} {1}", Name, @@ -196,17 +195,23 @@ 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(); + } } /// - /// The script block that represents the external script + /// The script block that represents the external script. /// - /// public ScriptBlock ScriptBlock { get @@ -222,12 +227,13 @@ public ScriptBlock ScriptBlock } // parse the script into an expression tree... - ScriptBlock newScriptBlock = ScriptBlock.Create(new Parser(), _path, ScriptContents); + ScriptBlock newScriptBlock = ParseScriptContents(new Parser(), _path, ScriptContents, DefiningLanguageMode); this.ScriptBlock = newScriptBlock; } return _scriptBlock; } + private set { _scriptBlock = value; @@ -237,9 +243,35 @@ private set } } } + private ScriptBlock _scriptBlock; private ScriptBlockAst _scriptBlockAst; + private static ScriptBlock ParseScriptContents(Parser parser, string fileName, string fileContents, PSLanguageMode? definingLanguageMode) + { + // If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need + // to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors + // such as "Configuration keyword not allowed". + if (definingLanguageMode.HasValue && (definingLanguageMode == PSLanguageMode.FullLanguage)) + { + var context = LocalPipeline.GetExecutionContextFromTLS(); + if ((context != null) && (context.LanguageMode == PSLanguageMode.ConstrainedLanguage)) + { + context.LanguageMode = PSLanguageMode.FullLanguage; + try + { + return ScriptBlock.Create(parser, fileName, fileContents); + } + finally + { + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + } + } + } + + return ScriptBlock.Create(parser, fileName, fileContents); + } + internal ScriptBlockAst GetScriptBlockAst() { var scriptContents = ScriptContents; @@ -247,30 +279,53 @@ internal ScriptBlockAst GetScriptBlockAst() { this.ScriptBlock = ScriptBlock.TryGetCachedScriptBlock(_path, scriptContents); } + if (_scriptBlock != null) { return (ScriptBlockAst)_scriptBlock.Ast; } + if (_scriptBlockAst == null) { ParseError[] errors; Parser parser = new Parser(); - _scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default); + + // If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need + // to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors + // such as "Configuration or Class keyword not allowed". + var context = LocalPipeline.GetExecutionContextFromTLS(); + if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + DefiningLanguageMode == PSLanguageMode.FullLanguage) + { + context.LanguageMode = PSLanguageMode.FullLanguage; + try + { + _scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default); + } + finally + { + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + } + } + else + { + _scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default); + } + if (errors.Length == 0) { this.ScriptBlock = new ScriptBlock(_scriptBlockAst, isFilter: false); ScriptBlock.CacheScriptBlock(_scriptBlock.Clone(), _path, scriptContents); } } + return _scriptBlockAst; } /// - /// Validates the external script info + /// Validates the external script info. /// - /// /// - /// public void ValidateScriptInfo(Host.PSHost host) { if (!_signatureChecked) @@ -293,7 +348,7 @@ public void ValidateScriptInfo(Host.PSHost host) } /// - /// The output type(s) is specified in the script block + /// The output type(s) is specified in the script block. /// public override ReadOnlyCollection OutputType { @@ -304,6 +359,7 @@ internal bool SignatureChecked { set { _signatureChecked = value; } } + private bool _signatureChecked; #region Internal @@ -315,11 +371,11 @@ 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()); } } + private CommandMetadata _commandMetadata; /// @@ -338,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; @@ -359,7 +415,7 @@ internal string RequiresApplicationID get { var data = GetRequiresData(); - return data == null ? null : data.RequiredApplicationId; + return data?.RequiredApplicationId; } } @@ -373,7 +429,7 @@ internal Version RequiresPSVersion get { var data = GetRequiresData(); - return data == null ? null : data.RequiredPSVersion; + return data?.RequiredPSVersion; } } @@ -382,7 +438,7 @@ internal IEnumerable RequiresPSEditions get { var data = GetRequiresData(); - return data == null ? null : data.RequiredPSEditions; + return data?.RequiredPSEditions; } } @@ -391,7 +447,7 @@ internal IEnumerable RequiresModules get { var data = GetRequiresData(); - return data == null ? null : data.RequiredModules; + return data?.RequiredModules; } } @@ -400,7 +456,7 @@ internal bool RequiresElevation get { var data = GetRequiresData(); - return data == null ? false : data.IsElevationRequired; + return data != null && data.IsElevationRequired; } } @@ -409,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. /// @@ -433,6 +480,7 @@ public string ScriptContents return _scriptContents; } } + private string _scriptContents; /// @@ -450,6 +498,7 @@ public Encoding OriginalEncoding return _originalEncoding; } } + private Encoding _originalEncoding; private void ReadScriptContents() @@ -467,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) + // Check this file against any system wide enforcement policies. + SystemScriptFileEnforcement filePolicyEnforcement = SystemPolicy.GetFilePolicyEnforcement(_path, readerStream); + switch (filePolicyEnforcement) { - SystemEnforcementMode scriptSpecificPolicy = SystemPolicy.GetLockdownPolicy(_path, safeFileHandle); - if (scriptSpecificPolicy != SystemEnforcementMode.Enforce) - { - this.DefiningLanguageMode = PSLanguageMode.FullLanguage; - } - else - { - this.DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage; - } - } - else - { - 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)); } } } @@ -525,7 +595,7 @@ private static void ThrowCommandNotFoundException(Exception innerException) CommandNotFoundException cmdE = new CommandNotFoundException(innerException.Message, innerException); throw cmdE; } - } // ExternalScriptInfo + } /// /// Thrown when fail to parse #requires statements. Caught by CommandDiscovery. @@ -539,9 +609,8 @@ internal ScriptRequiresSyntaxException(string message) } /// - /// Defines the name and version tuple of a PSSnapin + /// Defines the name and version tuple of a PSSnapin. /// - [Serializable] public class PSSnapInSpecification { internal PSSnapInSpecification(string psSnapinName) @@ -561,5 +630,4 @@ internal PSSnapInSpecification(string psSnapinName) /// public Version Version { get; internal set; } } -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/engine/ExtraAdapter.cs b/src/System.Management.Automation/engine/ExtraAdapter.cs index 6df54ba872d..dd6ba0c0784 100644 --- a/src/System.Management.Automation/engine/ExtraAdapter.cs +++ b/src/System.Management.Automation/engine/ExtraAdapter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; + using Microsoft.PowerShell; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -15,7 +15,7 @@ namespace System.Management.Automation { /// - /// Deals with DirectoryEntry objects + /// Deals with DirectoryEntry objects. /// internal class DirectoryEntryAdapter : DotNetAdapter { @@ -31,15 +31,18 @@ internal class DirectoryEntryAdapter : DotNetAdapter #region member - internal override bool SiteBinderCanOptimize { get { return false; } } + internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) + { + return false; + } /// /// Returns null if memberName is not a member in the adapter or - /// the corresponding PSMemberInfo + /// the corresponding PSMemberInfo. /// - /// object to retrieve the PSMemberInfo from - /// name of the member to be retrieved - /// The PSMemberInfo corresponding to memberName from obj + /// Object to retrieve the PSMemberInfo from. + /// Name of the member to be retrieved. + /// The PSMemberInfo corresponding to memberName from obj. protected override T GetMember(object obj, string memberName) { PSProperty property; @@ -63,7 +66,7 @@ protected override T GetMember(object obj, string memberName) object invokeGetValue = entry.InvokeGet(memberName); // if entry.Properties[memberName] returns empty value and invokeGet non-empty // value..take invokeGet's value. This will fix bug Windows Bug 121188. - if ((null == collection) || ((null == collection.Value) && (null != invokeGetValue))) + if ((collection == null) || ((collection.Value == null) && (invokeGetValue != null))) { valueToTake = invokeGetValue; } @@ -116,6 +119,7 @@ protected override T GetMember(object obj, string memberName) } } } + return null; } @@ -129,8 +133,8 @@ protected override T GetMember(object obj, string memberName) /// In the case of the DirectoryEntry adapter, this could be a cache of the objectClass /// to the properties available in it. /// - /// object to get all the member information from - /// all members in obj + /// Object to get all the member information from. + /// All members in obj. protected override PSMemberInfoInternalCollection GetMembers(object obj) { DirectoryEntry entry = (DirectoryEntry)obj; @@ -161,6 +165,7 @@ protected override PSMemberInfoInternalCollection GetMembers(object obj) members.Add(new PSProperty(property.PropertyName, this, obj, property) as T); } } + return members; } @@ -169,26 +174,26 @@ protected override PSMemberInfoInternalCollection GetMembers(object obj) #region property /// - /// Returns the value from a property coming from a previous call to GetMember + /// Returns the value from a property coming from a previous call to GetMember. /// - /// PSProperty coming from a previous call to GetMember - /// The value of the property + /// PSProperty coming from a previous call to GetMember. + /// The value of the property. protected override object PropertyGet(PSProperty property) { return property.adapterData; } /// - /// Sets the value of a property coming from a previous call to GetMember + /// Sets the value of a property coming from a previous call to GetMember. /// - /// PSProperty coming from a previous call to GetMember - /// value to set the property with - /// instructs the adapter to convert before setting, if the adapter supports conversion + /// PSProperty coming from a previous call to GetMember. + /// Value to set the property with. + /// Instructs the adapter to convert before setting, if the adapter supports conversion. protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible) { PropertyValueCollection values = property.adapterData as PropertyValueCollection; - if (null != values) + if (values != null) { // This means GetMember returned PropertyValueCollection try @@ -247,31 +252,31 @@ protected override void PropertySet(PSProperty property, object setValue, bool c } /// - /// Returns true if the property is settable + /// Returns true if the property is settable. /// - /// property to check - /// true if the property is settable + /// Property to check. + /// True if the property is settable. protected override bool PropertyIsSettable(PSProperty property) { return true; } /// - /// Returns true if the property is gettable + /// Returns true if the property is gettable. /// - /// property to check - /// true if the property is gettable + /// Property to check. + /// True if the property is gettable. protected override bool PropertyIsGettable(PSProperty property) { return true; } /// - /// Returns the name of the type corresponding to the property's value + /// Returns the name of the type corresponding to the property's value. /// - /// PSProperty obtained in a previous GetMember - /// True if the result is for display purposes only - /// the name of the type corresponding to the member + /// PSProperty obtained in a previous GetMember. + /// True if the result is for display purposes only. + /// The name of the type corresponding to the member. protected override string PropertyType(PSProperty property, bool forDisplay) { object value = null; @@ -282,6 +287,7 @@ protected override string PropertyType(PSProperty property, bool forDisplay) catch (GetValueException) { } + var type = value == null ? typeof(object) : value.GetType(); return forDisplay ? ToStringCodeMethods.Type(type) : type.FullName; } @@ -297,18 +303,18 @@ protected override object MethodInvoke(PSMethod method, PSMethodInvocationConstr /// /// Called after a non null return from GetMember to try to call - /// the method with the arguments + /// the method with the arguments. /// - /// the non empty return from GetMethods - /// the arguments to use - /// the return value for the method + /// The non empty return from GetMethods. + /// The arguments to use. + /// The return value for the method. protected override object MethodInvoke(PSMethod method, object[] arguments) { ParameterInformation[] parameters = new ParameterInformation[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { - parameters[i] = new ParameterInformation(typeof(System.Object), false, null, false); + parameters[i] = new ParameterInformation(typeof(object), false, null, false); } MethodInformation[] methodInformation = new MethodInformation[1]; @@ -349,7 +355,7 @@ protected override object MethodInvoke(PSMethod method, object[] arguments) // this code is reached only on exception // check if there is a dotnet method, invoke the dotnet method if available PSMethod dotNetmethod = s_dotNetAdapter.GetDotNetMethod(method.baseObject, method.name); - if (null != dotNetmethod) + if (dotNetmethod != null) { return dotNetmethod.Invoke(arguments); } @@ -358,9 +364,9 @@ protected override object MethodInvoke(PSMethod method, object[] arguments) } /// - /// Returns the string representation of the method in the object + /// Returns the string representation of the method in the object. /// - /// the string representation of the method in the object + /// The string representation of the method in the object. protected override string MethodToString(PSMethod method) { StringBuilder returnValue = new StringBuilder(); @@ -369,6 +375,7 @@ protected override string MethodToString(PSMethod method) returnValue.Append(overload); returnValue.Append(", "); } + returnValue.Remove(returnValue.Length - 2, 2); return returnValue.ToString(); } diff --git a/src/System.Management.Automation/engine/FilterInfo.cs b/src/System.Management.Automation/engine/FilterInfo.cs index 7492b320ff8..247aa0d5f6b 100644 --- a/src/System.Management.Automation/engine/FilterInfo.cs +++ b/src/System.Management.Automation/engine/FilterInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -12,120 +11,96 @@ public class FilterInfo : FunctionInfo #region ctor /// - /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock + /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the filter. /// - /// /// /// The ScriptBlock for the filter /// - /// /// /// The ExecutionContext for the filter. /// - /// /// /// If is null. /// - /// internal FilterInfo(string name, ScriptBlock filter, ExecutionContext context) : this(name, filter, context, null) { - } // FilterInfo ctor + } /// - /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock + /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the filter. /// - /// /// /// The ScriptBlock for the filter /// - /// /// /// The ExecutionContext for the filter. /// - /// /// /// The help file for the filter. /// - /// /// /// If is null. /// - /// internal FilterInfo(string name, ScriptBlock filter, ExecutionContext context, string helpFile) : base(name, filter, context, helpFile) { SetCommandType(CommandTypes.Filter); - } // FilterInfo ctor + } /// - /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock + /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the filter. /// - /// /// /// The ScriptBlock for the filter /// - /// /// /// The options to set on the function. Note, Constant can only be set at creation time. /// - /// /// /// The execution context for the filter. /// - /// /// /// If is null. /// - /// internal FilterInfo(string name, ScriptBlock filter, ScopedItemOptions options, ExecutionContext context) : this(name, filter, options, context, null) { - } // FilterInfo ctor + } /// - /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock + /// Creates an instance of the FilterInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the filter. /// - /// /// /// The ScriptBlock for the filter /// - /// /// /// The options to set on the function. Note, Constant can only be set at creation time. /// - /// /// /// The execution context for the filter. /// - /// /// /// The help file for the filter. /// - /// /// /// If is null. /// - /// internal FilterInfo(string name, ScriptBlock filter, ScopedItemOptions options, ExecutionContext context, string helpFile) : base(name, filter, options, context, helpFile) { SetCommandType(CommandTypes.Filter); - } // FilterInfo ctor + } /// /// This is a copy constructor, used primarily for get-command. @@ -161,5 +136,5 @@ internal override HelpCategory HelpCategory { get { return HelpCategory.Filter; } } - } // FilterInfo -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/FunctionInfo.cs b/src/System.Management.Automation/engine/FunctionInfo.cs index 7786655552f..70ab80e8ca4 100644 --- a/src/System.Management.Automation/engine/FunctionInfo.cs +++ b/src/System.Management.Automation/engine/FunctionInfo.cs @@ -1,10 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Text; -using System.Management.Automation.Runspaces; using System.Collections.ObjectModel; +using System.Management.Automation.Runspaces; +using System.Text; namespace System.Management.Automation { @@ -16,58 +15,47 @@ public class FunctionInfo : CommandInfo, IScriptCommandInfo #region ctor /// - /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock + /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the function. /// - /// /// /// The ScriptBlock for the function /// - /// /// /// The execution context for the function. /// - /// /// /// If is null. /// - /// internal FunctionInfo(string name, ScriptBlock function, ExecutionContext context) : this(name, function, context, null) { - } // FunctionInfo ctor + } /// - /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock + /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the function. /// - /// /// /// The ScriptBlock for the function /// - /// /// /// The execution context for the function. /// - /// /// /// The name of the help file associated with the function. /// - /// /// /// If is null. /// - /// internal FunctionInfo(string name, ScriptBlock function, ExecutionContext context, string helpFile) : base(name, CommandTypes.Function, context) { if (function == null) { - throw PSTraceSource.NewArgumentNullException("function"); + throw PSTraceSource.NewArgumentNullException(nameof(function)); } _scriptBlock = function; @@ -76,69 +64,56 @@ internal FunctionInfo(string name, ScriptBlock function, ExecutionContext contex this.Module = function.Module; _helpFile = helpFile; - } // FunctionInfo ctor + } /// - /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock + /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the function. /// - /// /// /// The ScriptBlock for the function /// - /// /// /// The options to set on the function. Note, Constant can only be set at creation time. /// - /// /// /// The execution context for the function. /// - /// /// /// If is null. /// - /// internal FunctionInfo(string name, ScriptBlock function, ScopedItemOptions options, ExecutionContext context) : this(name, function, options, context, null) { - } // FunctionInfo ctor + } /// - /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock + /// Creates an instance of the FunctionInfo class with the specified name and ScriptBlock. /// - /// /// /// The name of the function. /// - /// /// /// The ScriptBlock for the function /// - /// /// /// The options to set on the function. Note, Constant can only be set at creation time. /// - /// /// /// The execution context for the function. /// - /// /// /// The name of the help file associated with the function. /// - /// /// /// If is null. /// - /// internal FunctionInfo(string name, ScriptBlock function, ScopedItemOptions options, ExecutionContext context, string helpFile) : this(name, function, context, helpFile) { _options = options; - } // FunctionInfo ctor + } /// /// This is a copy constructor, used primarily for get-command. @@ -189,36 +164,34 @@ internal override HelpCategory HelpCategory } /// - /// Gets the ScriptBlock which is the implementation of the function + /// Gets the ScriptBlock which is the implementation of the function. /// public ScriptBlock ScriptBlock { get { return _scriptBlock; } } + private ScriptBlock _scriptBlock; /// /// Updates a function. /// - /// /// /// The script block that the function should represent. /// - /// /// /// If true, the script block will be applied even if the filter is ReadOnly. /// - /// /// /// Any options to set on the new function, null if none. /// /// /// If is null. /// - /// internal void Update(ScriptBlock newFunction, bool force, ScopedItemOptions options) { Update(newFunction, force, options, null); + this.DefiningLanguageMode = newFunction.LanguageMode; } /// @@ -230,27 +203,21 @@ protected internal virtual void Update(FunctionInfo newFunction, bool force, Sco /// /// Updates a function. /// - /// /// /// The script block that the function should represent. /// - /// /// /// If true, the script block will be applied even if the filter is ReadOnly. /// - /// /// /// Any options to set on the new function, null if none. /// - /// /// /// The helpfile for this function. /// - /// /// /// If is null. /// - /// internal void Update(ScriptBlock newFunction, bool force, ScopedItemOptions options, string helpFile) { if (newFunction == null) @@ -298,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 { @@ -310,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 { @@ -329,7 +296,6 @@ public string DefaultParameterSet /// /// Gets or sets the scope options for the function. /// - /// /// /// If the trying to set a function that is constant or /// if the value trying to be set is ScopedItemOptions.Constant @@ -402,10 +368,11 @@ public ScopedItemOptions Options } } } + private ScopedItemOptions _options = ScopedItemOptions.None; /// - /// Gets or sets the description associated with the function + /// Gets or sets the description associated with the function. /// public string Description { @@ -426,10 +393,11 @@ public string Description } } } + private string _description = null; /// - /// Gets the verb of the function + /// Gets the verb of the function. /// public string Verb { @@ -437,8 +405,9 @@ public string Verb { return _verb; } - } // Verb - private string _verb = String.Empty; + } + + private string _verb = string.Empty; /// /// Gets the noun of the function. @@ -449,8 +418,9 @@ public string Noun { return _noun; } - } // Noun - private string _noun = String.Empty; + } + + private string _noun = string.Empty; /// /// Gets the help file path for the function. @@ -461,15 +431,17 @@ public string HelpFile { return _helpFile; } + internal set { _helpFile = value; } - } // HelpFile - private string _helpFile = String.Empty; + } + + private string _helpFile = string.Empty; /// - /// Returns the syntax of a command + /// Returns the syntax of a command. /// internal override string Syntax { @@ -481,11 +453,11 @@ internal override string Syntax { synopsis.AppendLine(); synopsis.AppendLine( - String.Format( + string.Format( Globalization.CultureInfo.CurrentCulture, "{0} {1}", Name, - parameterSet.ToString((this.CommandType & CommandTypes.Workflow) == CommandTypes.Workflow))); + parameterSet.ToString())); } return synopsis.ToString(); @@ -501,25 +473,25 @@ internal override bool ImplementsDynamicParameters } /// - /// The command metadata for the function or filter + /// The command metadata for the function or filter. /// 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()); } } + private CommandMetadata _commandMetadata; /// - /// The output type(s) is specified in the script block + /// The output type(s) is specified in the script block. /// public override ReadOnlyCollection OutputType { get { return ScriptBlock.OutputType; } } - } // FunctionInfo -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index 369fc9deed2..10e8334021b 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -1,19 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Globalization; -using System.Linq; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation; -using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Internal; using System.Management.Automation.Language; -using System.Reflection; +using static System.Management.Automation.Verbs; using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -23,19 +22,17 @@ namespace Microsoft.PowerShell.Commands /// commands of the given name. It returns an instance of CommandInfo for each /// command that is found. /// - /// - [Cmdlet(VerbsCommon.Get, "Command", DefaultParameterSetName = "CmdletSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113309")] + [Cmdlet(VerbsCommon.Get, "Command", DefaultParameterSetName = "CmdletSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096579")] [OutputType(typeof(AliasInfo), typeof(ApplicationInfo), typeof(FunctionInfo), typeof(CmdletInfo), typeof(ExternalScriptInfo), typeof(FilterInfo), - typeof(WorkflowInfo), typeof(string), typeof(PSObject))] + typeof(string), typeof(PSObject))] public sealed class GetCommandCommand : PSCmdlet { #region Definitions of cmdlet parameters /// - /// Gets or sets the path(s) or name(s) of the commands to retrieve + /// Gets or sets the path(s) or name(s) of the commands to retrieve. /// - /// [Parameter( Position = 0, ValueFromPipeline = true, @@ -66,38 +63,37 @@ public string[] Name } } } - } // Path + } + private string[] _names; private bool _nameContainsWildcard; /// - /// Gets or sets the verb parameter to the cmdlet + /// Gets or sets the verb parameter to the cmdlet. /// - /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string[] Verb { get { return _verbs; - } // get + } set { - if (value == null) - { - value = Utils.EmptyArray(); - } + value ??= Array.Empty(); + _verbs = value; _verbPatterns = null; - } // set - } // Verb - private string[] _verbs = Utils.EmptyArray(); + } + } + + private string[] _verbs = Array.Empty(); /// - /// Gets or sets the noun parameter to the cmdlet + /// Gets or sets the noun parameter to the cmdlet. /// - /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")] [ArgumentCompleter(typeof(NounArgumentCompleter))] public string[] Noun @@ -105,25 +101,22 @@ public string[] Noun get { return _nouns; - } // get + } set { - if (value == null) - { - value = Utils.EmptyArray(); - } + value ??= Array.Empty(); + _nouns = value; _nounPatterns = null; - } // set - } // Noun - private string[] _nouns = Utils.EmptyArray(); + } + } + + private string[] _nouns = Array.Empty(); /// - /// Gets or sets the PSSnapin/Module parameter to the cmdlet + /// Gets or sets the PSSnapin/Module parameter to the cmdlet. /// - /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("PSSnapin")] public string[] Module @@ -131,28 +124,47 @@ public string[] Module get { return _modules; - } // get + } set { - if (value == null) - { - value = Utils.EmptyArray(); - } + value ??= Array.Empty(); + _modules = value; _modulePatterns = null; _isModuleSpecified = true; - } // set + } } - private string[] _modules = Utils.EmptyArray(); + + private string[] _modules = Array.Empty(); private bool _isModuleSpecified = false; /// - /// Gets or sets the FullyQualifiedModule parameter to the cmdlet + /// 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. /// - /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ValueFromPipelineByPropertyName = true)] public ModuleSpecification[] FullyQualifiedModule { @@ -167,16 +179,17 @@ public ModuleSpecification[] FullyQualifiedModule { _moduleSpecifications = value; } + _isFullyQualifiedModuleSpecified = true; } } - private ModuleSpecification[] _moduleSpecifications = Utils.EmptyArray(); + + private ModuleSpecification[] _moduleSpecifications = Array.Empty(); private bool _isFullyQualifiedModuleSpecified = false; /// - /// Gets or sets the type of the command to get + /// Gets or sets the type of the command to get. /// - /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")] [Alias("Type")] public CommandTypes CommandType @@ -184,14 +197,15 @@ public CommandTypes CommandType get { return _commandType; - } // get + } set { _commandType = value; _isCommandTypeSpecified = true; - } // set - } // Noun + } + } + private CommandTypes _commandType = CommandTypes.All; private bool _isCommandTypeSpecified = false; @@ -200,7 +214,6 @@ public CommandTypes CommandType /// be returned. If negative, all matching commands that are found will /// be returned. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] public int TotalCount { get; set; } = -1; @@ -208,7 +221,6 @@ public CommandTypes CommandType /// The parameter that determines if the CommandInfo or the string /// definition of the command is output. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] public SwitchParameter Syntax { @@ -222,6 +234,7 @@ public SwitchParameter Syntax _usage = value; } } + private bool _usage; /// @@ -235,7 +248,6 @@ public SwitchParameter Syntax /// The parameter that all additional arguments get bound to. These arguments are used /// when retrieving dynamic parameters from cmdlets that support them. /// - /// [Parameter(Position = 1, ValueFromRemainingArguments = true)] [AllowNull] [AllowEmptyCollection] @@ -246,20 +258,20 @@ public SwitchParameter Syntax /// The parameter that determines if additional matching commands should be returned. /// (Additional matching functions and aliases are returned from module tables) /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] public SwitchParameter All { get { return _all; } + set { _all = value; } } + private bool _all; /// /// The parameter that determines if additional matching commands from available modules should be returned. /// If set to true, only those commands currently in the session are returned. /// - /// [Parameter(ValueFromPipelineByPropertyName = true)] public SwitchParameter ListImported { @@ -273,23 +285,24 @@ public SwitchParameter ListImported _listImported = value; } } + private bool _listImported; /// - /// The parameter that filters commands returned to only include commands that have a parameter with a name that matches one of the ParameterName's arguments + /// The parameter that filters commands returned to only include commands that have a parameter with a name that matches one of the ParameterName's arguments. /// [Parameter] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] 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( @@ -297,28 +310,26 @@ public string[] ParameterName WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase); } } + private Collection _parameterNameWildcards; private string[] _parameterNames; private HashSet _matchedParameterNames; /// - /// The parameter that filters commands returned to only include commands that have a parameter of a type that matches one of the ParameterType's arguments + /// The parameter that filters commands returned to only include commands that have a parameter of a type that matches one of the ParameterType's arguments. /// [Parameter] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PSTypeName[] ParameterType { get { return _parameterTypes; } + 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); @@ -330,29 +341,61 @@ public PSTypeName[] ParameterType { continue; } + if ((i != 0) && (ptn.Type != null) && (ptn.Type.Equals(typeof(object)))) { continue; } + filteredParameterTypes.Add(ptn); } + _parameterTypes = filteredParameterTypes.ToArray(); } } + private PSTypeName[] _parameterTypes; + /// + /// Gets or sets the parameter that enables using fuzzy matching. + /// + [Parameter(ParameterSetName = "AllCommandSet")] + public SwitchParameter UseFuzzyMatching { get; set; } + + /// + /// 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. + /// This means it matches cmdlets where the uppercase characters for the noun match + /// the given characters. i.e., g-sgc would match Get-SomeGreatCmdlet. + /// + [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")] + public SwitchParameter UseAbbreviationExpansion { get; set; } + #endregion Definitions of cmdlet parameters #region Overrides /// - /// Begin Processing + /// Begin Processing. /// protected override void BeginProcessing() { +#if LEGACYTELEMETRY _timer.Start(); - - base.BeginProcessing(); +#endif + if (UseFuzzyMatching) + { + _fuzzyMatcher = new FuzzyMatcher(FuzzyMinimumDistance); + _commandScores = new List(); + } if (ShowCommandInfo.IsPresent && Syntax.IsPresent) { @@ -366,25 +409,24 @@ protected override void BeginProcessing() } /// - /// method that implements get-command + /// Method that implements get-command. /// - /// protected override void ProcessRecord() { + _commandsWritten.Clear(); + // Module and FullyQualifiedModule should not be specified at the same time. // Throw out terminating error if this is the case. if (_isModuleSpecified && _isFullyQualifiedModuleSpecified) { - string errMsg = String.Format(CultureInfo.InvariantCulture, SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Module", "FullyQualifiedModule"); + string errMsg = string.Format(CultureInfo.InvariantCulture, SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Module", "FullyQualifiedModule"); ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(error); } // 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) { @@ -405,14 +447,13 @@ protected override void ProcessRecord() } /// - /// Writes out the accumulated matching commands + /// Writes out the accumulated matching commands. /// - /// protected override void EndProcessing() { // We do not show the pithy aliases (not of the format Verb-Noun) and applications by default. // We will show them only if the Name, All and totalCount are not specified. - if ((this.Name == null) && (!_all) && TotalCount == -1) + if ((this.Name == null) && (!_all) && TotalCount == -1 && !UseFuzzyMatching) { CommandTypes commandTypesToIgnore = 0; @@ -443,6 +484,7 @@ protected override void EndProcessing() { continue; } + if (_matchedParameterNames.Contains(requestedParameterName)) { continue; @@ -463,13 +505,13 @@ 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); object pssenderInfo = Context.GetVariableValue(SpecialVariables.PSSenderInfoVarPath); - if ((null != pssenderInfo) && (pssenderInfo is System.Management.Automation.Remoting.PSSenderInfo)) + if ((pssenderInfo != null) && (pssenderInfo is System.Management.Automation.Remoting.PSSenderInfo)) { // Win8: 593295. Exchange has around 1000 cmdlets. During Import-PSSession, // Get-Command | select-object ..,HelpURI,... is run. HelpURI is a script property @@ -490,12 +532,17 @@ protected override void EndProcessing() private void OutputResultsHelper(IEnumerable results) { - CommandOrigin origin = this.MyInvocation.CommandOrigin; + CommandOrigin origin = MyInvocation.CommandOrigin; + + if (UseFuzzyMatching) + { + _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)) { @@ -503,11 +550,9 @@ private void OutputResultsHelper(IEnumerable results) // otherwise just return the object... if (Syntax) { - if (!String.IsNullOrEmpty(result.Syntax)) + if (!string.IsNullOrEmpty(result.Syntax)) { - PSObject syntax = PSObject.AsPSObject(result.Syntax); - - syntax.IsHelpObject = true; + PSObject syntax = GetSyntaxObject(result); WriteObject(syntax); } @@ -522,17 +567,29 @@ 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 _timer.Stop(); -#if LEGACYTELEMETRY - // We want telementry on commands people look for but don't exist - this should give us an idea + // No telemetry here - capturing the name of a command which we are not familiar with + // may be confidential customer information + // 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. @@ -541,13 +598,87 @@ private void OutputResultsHelper(IEnumerable results) Telemetry.Internal.TelemetryAPI.ReportGetCommandFailed(Name, _timer.ElapsedMilliseconds); } #endif + } + + /// + /// Creates the syntax output based on if the command is an alias, script, application or command. + /// + /// + /// CommandInfo object containing the syntax to be output. + /// + /// + /// Syntax string cast as a PSObject for outputting. + /// + private PSObject GetSyntaxObject(CommandInfo command) + { + PSObject syntax = PSObject.AsPSObject(command.Syntax); + + // This is checking if the command name that's been passed in is one that was specified by a user, + // if not then we have to assume they specified an alias or a wildcard and do some extra formatting for those, + // if it is then just go with the default formatting. + // So if a user runs Get-Command -Name del -Syntax the code will find del and the command it resolves to as Remove-Item + // and attempt to return that, but as the user specified del we want to fiddle with the output a bit to make it clear + // that's an alias but still give the Remove-Item syntax. + if (this.Name != null && !Array.Exists(this.Name, name => name.Equals(command.Name, StringComparison.InvariantCultureIgnoreCase))) + { + string aliasName = _nameContainsWildcard ? command.Name : this.Name[0]; + + IDictionary aliasTable = SessionState.Internal.GetAliasTable(); + foreach (KeyValuePair tableEntry in aliasTable) + { + if ((Array.Exists(this.Name, name => name.Equals(tableEntry.Key, StringComparison.InvariantCultureIgnoreCase)) && + tableEntry.Value.Definition == command.Name) || + (_nameContainsWildcard && tableEntry.Value.Definition == command.Name)) + { + aliasName = tableEntry.Key; + break; + } + } + + string replacedSyntax = string.Empty; + switch (command) + { + case ExternalScriptInfo externalScript: + replacedSyntax = string.Format( + "{0} (alias) -> {1}{2}{3}", + aliasName, + string.Format("{0}{1}", externalScript.Path, Environment.NewLine), + Environment.NewLine, + command.Syntax.Replace(command.Name, aliasName)); + break; + case ApplicationInfo app: + replacedSyntax = app.Path; + break; + default: + if (aliasName.Equals(command.Name)) + { + replacedSyntax = command.Syntax; + } + else + { + replacedSyntax = string.Format( + "{0} (alias) -> {1}{2}{3}", + aliasName, + command.Name, + Environment.NewLine, + command.Syntax.Replace(command.Name, aliasName)); + } + break; + } + + syntax = PSObject.AsPSObject(replacedSyntax); + } + + syntax.IsHelpObject = true; + + return syntax; } /// - /// The comparer to sort CommandInfo objects in the result list + /// 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 @@ -568,14 +699,14 @@ public int Compare(CommandInfo x, CommandInfo y) } else { - return String.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); + return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } } } private void AccumulateMatchingCmdlets() { - _commandType = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Alias | CommandTypes.Workflow | CommandTypes.Configuration; + _commandType = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Alias | CommandTypes.Configuration; Collection commandNames = new Collection(); commandNames.Add("*"); @@ -588,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( @@ -616,7 +748,7 @@ private bool IsNounVerbMatch(CommandInfo command) } else { - if (_modulePatterns.Count > 0 || _moduleSpecifications.Any()) + if (_modulePatterns.Count > 0 || _moduleSpecifications.Length > 0) { // Its not a match if we are filtering on a PSSnapin/Module name but the cmdlet doesn't have one. break; @@ -655,7 +787,7 @@ private bool IsNounVerbMatch(CommandInfo command) } /// - /// Writes out the commands for the AllCommandSet using the specified CommandType + /// Writes out the commands for the AllCommandSet using the specified CommandType. /// private void AccumulateMatchingCommands() { @@ -666,6 +798,7 @@ private void AccumulateMatchingCommands() { commandNames.Add("*"); } + AccumulateMatchingCommands(commandNames); } @@ -679,12 +812,17 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) options = SearchResolutionOptions.SearchAllScopes; } + if (UseAbbreviationExpansion) + { + options |= SearchResolutionOptions.UseAbbreviationExpansion; + } + if ((this.CommandType & CommandTypes.Alias) != 0) { options |= SearchResolutionOptions.ResolveAliasPatterns; } - if ((this.CommandType & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Workflow | CommandTypes.Configuration)) != 0) + if ((this.CommandType & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Configuration)) != 0) { options |= SearchResolutionOptions.ResolveFunctionPatterns; } @@ -706,7 +844,7 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) moduleName = this.Module[0]; } - bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName); + bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName) || UseAbbreviationExpansion || UseFuzzyMatching; if (isPattern) { options |= SearchResolutionOptions.CommandNameIsPattern; @@ -724,10 +862,10 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) // If the command name had no wildcards or was module-qualified, // import the module so that we can return the fully structured data. // This uses the same code path as module auto-loading. - if ((!isPattern) || (!String.IsNullOrEmpty(moduleName))) + if ((!isPattern) || (!string.IsNullOrEmpty(moduleName))) { string tempCommandName = commandName; - if ((!isModuleQualified) && (!String.IsNullOrEmpty(moduleName))) + if ((!isModuleQualified) && (!string.IsNullOrEmpty(moduleName))) { tempCommandName = moduleName + "\\" + commandName; } @@ -748,7 +886,34 @@ private void AccumulateMatchingCommands(IEnumerable commandNames) { if (TotalCount < 0 || count < TotalCount) { - foreach (CommandInfo command in System.Management.Automation.Internal.ModuleUtils.GetMatchingCommands(plainCommandName, this.Context, this.MyInvocation.CommandOrigin, rediscoverImportedModules: true, moduleVersionRequired: _isFullyQualifiedModuleSpecified)) + IEnumerable commands; + if (UseFuzzyMatching) + { + foreach (var commandScore in ModuleUtils.GetFuzzyMatchingCommands( + plainCommandName, + Context, + MyInvocation.CommandOrigin, + _fuzzyMatcher, + rediscoverImportedModules: true, + moduleVersionRequired: _isFullyQualifiedModuleSpecified)) + { + _commandScores.Add(commandScore); + } + + commands = _commandScores.Select(static x => x.Command); + } + else + { + commands = ModuleUtils.GetMatchingCommands( + plainCommandName, + Context, + MyInvocation.CommandOrigin, + rediscoverImportedModules: true, + moduleVersionRequired: _isFullyQualifiedModuleSpecified, + useAbbreviationExpansion: UseAbbreviationExpansion); + } + + foreach (CommandInfo command in commands) { // Cannot pass in "command" by ref (foreach iteration variable) CommandInfo current = command; @@ -803,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 { @@ -828,6 +993,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { WriteError(new ErrorRecord(argumentException, "GetCommandInvalidArgument", ErrorCategory.SyntaxError, null)); } + continue; } catch (PathTooLongException pathTooLong) @@ -836,6 +1002,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { WriteError(new ErrorRecord(pathTooLong, "GetCommandInvalidArgument", ErrorCategory.SyntaxError, null)); } + continue; } catch (FileLoadException fileLoadException) @@ -844,6 +1011,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { WriteError(new ErrorRecord(fileLoadException, "GetCommandFileLoadError", ErrorCategory.ReadError, null)); } + continue; } catch (MetadataException metadataException) @@ -852,6 +1020,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { WriteError(new ErrorRecord(metadataException, "GetCommandMetadataError", ErrorCategory.MetadataError, null)); } + continue; } catch (FormatException formatException) @@ -860,6 +1029,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { WriteError(new ErrorRecord(formatException, "GetCommandBadFileFormat", ErrorCategory.InvalidData, null)); } + continue; } @@ -889,6 +1059,14 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN break; } + if (UseFuzzyMatching) + { + if (_fuzzyMatcher.IsFuzzyMatch(current.Name, commandName, out int score)) + { + _commandScores.Add(new CommandScore(current, score)); + } + } + _accumulatedResults.Add(current); if (ArgumentList != null) @@ -898,7 +1076,6 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN } } - // Only for this case, the loop should exit // Get-Command Foo if (isPattern || All || TotalCount != -1 || _isCommandTypeSpecified || _isModuleSpecified || _isFullyQualifiedModuleSpecified) @@ -910,8 +1087,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN break; } } - } while (true); - + } if (All) { @@ -931,6 +1107,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN { break; } + _accumulatedResults.Add(c); } // Make sure we don't exceed the TotalCount parameter @@ -945,15 +1122,12 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN /// Determines if the specific command information has already been /// written out based on the path or definition. /// - /// /// /// The command information to check for duplication. /// - /// /// /// true if the command has already been written out. /// - /// private bool IsDuplicate(CommandInfo info) { bool result = false; @@ -1012,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 @@ -1031,6 +1202,7 @@ private bool IsParameterMatch(CommandInfo commandInfo) // ignore all exceptions when getting parameter metadata (i.e. parse exceptions, dangling alias exceptions) // and proceed as if there was no parameter metadata } + if (commandParameters == null) { // do not match commands which have not been imported yet / for which we don't have parameter metadata yet @@ -1047,6 +1219,7 @@ private bool IsParameterMatch(CommandInfo commandInfo) // not breaking out of the loop early, to ensure that _matchedParameterNames gets populated for all command parameters } } + return foundMatchingParameter; } } @@ -1111,14 +1284,13 @@ private bool IsCommandMatch(ref CommandInfo current, out bool isDuplicate) isCommandMatch = true; } - // If the command in question is a cmdlet or (a function/filter/workflow/configuration/alias and we are filtering on nouns or verbs), - // then do the verb/moun check + // If the command in question is a cmdlet or (a function/filter/configuration/alias and we are filtering on nouns or verbs), + // then do the verb/noun check if (current.CommandType == CommandTypes.Cmdlet || ((_verbs.Length > 0 || _nouns.Length > 0) && (current.CommandType == CommandTypes.Function || current.CommandType == CommandTypes.Filter || - current.CommandType == CommandTypes.Workflow || current.CommandType == CommandTypes.Configuration || current.CommandType == CommandTypes.Alias))) { @@ -1129,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; @@ -1157,33 +1336,39 @@ private bool IsCommandMatch(ref CommandInfo current, out bool isDuplicate) if (isCommandMatch) { - if (ArgumentList != null) + if (Syntax.IsPresent && current is AliasInfo ai) { - AliasInfo ai = current as AliasInfo; - if (ai != null) + // If the matching command was an alias, then use the resolved command + // instead of the alias... + current = ai.ResolvedCommand ?? CommandDiscovery.LookupCommandInfo( + ai.UnresolvedCommandName, + this.MyInvocation.CommandOrigin, + this.Context); + + // there are situations where both ResolvedCommand and UnresolvedCommandName + // are both null (often due to multiple versions of modules with aliases) + // therefore we need to exit early. + if (current == null) { - // If the matching command was an alias, then use the resolved command - // instead of the alias... - current = ai.ResolvedCommand; - if (current == null) - { - return false; - } - } - else if (!(current is CmdletInfo || current is IScriptCommandInfo)) - { - // If current is not a cmdlet or script, we need to throw a terminating error. - ThrowTerminatingError( - new ErrorRecord( - PSTraceSource.NewArgumentException( - "ArgumentList", - DiscoveryExceptions.CommandArgsOnlyForSingleCmdlet), - "CommandArgsOnlyForSingleCmdlet", - ErrorCategory.InvalidArgument, - current)); + return false; } } + 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( + new ErrorRecord( + PSTraceSource.NewArgumentException( + "ArgumentList", + DiscoveryExceptions.CommandArgsOnlyForSingleCmdlet), + "CommandArgsOnlyForSingleCmdlet", + ErrorCategory.InvalidArgument, + current)); + } + // If the command implements dynamic parameters // then we must make a copy of the CommandInfo which merges the // dynamic parameter metadata with the statically defined parameter @@ -1248,21 +1433,19 @@ private bool IsCommandMatch(ref CommandInfo current, out bool isDuplicate) { isDuplicate = true; } + return isCommandMatch; } /// - /// Gets matching commands from the module tables + /// Gets matching commands from the module tables. /// - /// /// /// The commandname to look for /// - /// /// /// IEnumerable of CommandInfo objects /// - /// private IEnumerable GetMatchingCommandsFromModules(string commandName) { WildcardPattern matcher = WildcardPattern.Get( @@ -1274,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"); } @@ -1297,15 +1480,13 @@ private IEnumerable GetMatchingCommandsFromModules(string commandNa // Look in function table if ((this.CommandType & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Configuration)) != 0) { - foreach (DictionaryEntry function in module.SessionState.Internal.GetFunctionTable()) + foreach ((string functionName, FunctionInfo functionInfo) in module.SessionState.Internal.GetFunctionTable()) { - FunctionInfo func = (FunctionInfo)function.Value; - - if (matcher.IsMatch((string)function.Key) && func.IsImported) + if (matcher.IsMatch(functionName) && functionInfo.IsImported) { // make sure function doesn't come from the current module's nested module - if (func.Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) - yield return (CommandInfo)function.Value; + if (functionInfo.Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) + yield return functionInfo; } } } @@ -1331,38 +1512,45 @@ private IEnumerable GetMatchingCommandsFromModules(string commandNa /// /// Determines if the specific command information has already been - /// added to the result from CommandSearcher + /// added to the result from CommandSearcher. /// - /// /// /// The command information to check for duplication. /// - /// /// /// true if the command is present in the result. /// - /// private bool IsCommandInResult(CommandInfo command) { bool isPresent = false; - bool commandHasModule = command.Module != null; - foreach (CommandInfo commandInfo in _accumulatedResults) - { - if ((command.CommandType == commandInfo.CommandType && - (String.Compare(command.Name, commandInfo.Name, StringComparison.CurrentCultureIgnoreCase) == 0 || - // 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.Compare(ModuleCmdletBase.RemovePrefixFromCommandName(commandInfo.Name, commandInfo.Prefix), command.Name, StringComparison.CurrentCultureIgnoreCase) == 0) - ) && 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; + } } } + return isPresent; } @@ -1370,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(); @@ -1379,9 +1567,11 @@ private bool IsCommandInResult(CommandInfo command) private Collection _verbPatterns; private Collection _nounPatterns; private Collection _modulePatterns; + private Collection _excludedModulePatterns; +#if LEGACYTELEMETRY private Stopwatch _timer = new Stopwatch(); - +#endif #endregion #region ShowCommandInfo support @@ -1425,7 +1615,7 @@ private static PSObject[] GetParameterSets(CommandInfo cmdInfo) if (parameterSets == null) { - return Utils.EmptyArray(); + return Array.Empty(); } List returnParameterSets = new List(cmdInfo.ParameterSets.Count); @@ -1457,12 +1647,13 @@ private static PSObject[] GetParameterInfo(ReadOnlyCollection validValues = new List(); - var validateSetAttribute = parameter.Attributes.Where(x => (x is ValidateSetAttribute)).Cast().LastOrDefault(); + var validateSetAttribute = parameter.Attributes.OfType().LastOrDefault(); if (validateSetAttribute != null) { hasParameterSet = true; validValues = validateSetAttribute.ValidValues; } + parameterObj.Properties.Add(new PSNoteProperty("HasParameterSet", hasParameterSet)); parameterObj.Properties.Add(new PSNoteProperty("ValidParamSetValues", validValues)); @@ -1475,8 +1666,8 @@ private static PSObject[] GetParameterInfo(ReadOnlyCollection 0) : false; + bool hasFlagAttribute = (isArray) && ((parameterType.GetCustomAttributes(typeof(FlagsAttribute), true)).Length > 0); returnParameterType.Properties.Add(new PSNoteProperty("HasFlagAttribute", hasFlagAttribute)); // Recurse into array elements. @@ -1504,42 +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("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/GetEvent_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/GetEvent_Types_Ps1Xml.cs deleted file mode 100644 index 783dfd098c1..00000000000 --- a/src/System.Management.Automation/engine/GetEvent_Types_Ps1Xml.cs +++ /dev/null @@ -1,86 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Management.Automation; -using System.Reflection; - -namespace System.Management.Automation.Runspaces -{ - internal sealed class GetEvent_Types_Ps1Xml - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - static MethodInfo GetMethodInfo(string typeName, string method) - { - var type = LanguagePrimitives.ConvertTo(typeName); - return GetMethodInfo(type, method); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - static MethodInfo GetMethodInfo(Type type, string method) - { - return type.GetMethod(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase); - } - - static ScriptBlock GetScriptBlock(string s) - { - var sb = ScriptBlock.CreateDelayParsedScriptBlock(s, isProductCode: true); - sb.LanguageMode = PSLanguageMode.FullLanguage; - return sb; - } - - public static IEnumerable Get() - { - - var td1 = new TypeData(@"System.Diagnostics.Eventing.Reader.EventLogConfiguration", true); - td1.DefaultDisplayPropertySet = - new PropertySetData(new [] { "LogName", "MaximumSizeInBytes", "RecordCount", "LogMode" }) { Name = "DefaultDisplayPropertySet" }; - yield return td1; - - var td2 = new TypeData(@"System.Diagnostics.Eventing.Reader.EventLogRecord", true); - td2.DefaultDisplayPropertySet = - new PropertySetData(new [] { "TimeCreated", "ProviderName", "Id", "Message" }) { Name = "DefaultDisplayPropertySet" }; - yield return td2; - - var td3 = new TypeData(@"System.Diagnostics.Eventing.Reader.ProviderMetadata", true); - td3.Members.Add("ProviderName", - new AliasPropertyData("ProviderName", "Name")); - td3.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "LogLinks" }) { Name = "DefaultDisplayPropertySet" }; - yield return td3; - -#if !CORECLR - var td4 = new TypeData(@"Microsoft.PowerShell.Commands.GetCounter.CounterSet", true); - td4.Members.Add("Counter", - new AliasPropertyData("Counter", "Paths")); - yield return td4; - - var td5 = new TypeData(@"Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample", true); - td5.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Path", "InstanceName", "CookedValue" }) { Name = "DefaultDisplayPropertySet" }; - yield return td5; - - var td6 = new TypeData(@"Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet", true); - td6.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Timestamp", "Readings" }) { Name = "DefaultDisplayPropertySet" }; - yield return td6; - - var td7 = new TypeData(@"Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet", true); - td7.Members.Add("Readings", - new ScriptPropertyData(@"Readings", GetScriptBlock(@"$strPaths = """" - foreach ($ctr in $this.CounterSamples) - { - $strPaths += ($ctr.Path + "" :"" + ""`n"") - $strPaths += ($ctr.CookedValue.ToString() + ""`n`n"") - } - return $strPaths"), null)); - yield return td7; -#endif - } - } -} diff --git a/src/System.Management.Automation/engine/ICommandRuntime.cs b/src/System.Management.Automation/engine/ICommandRuntime.cs index ef6bdc2cff8..49b1104e047 100644 --- a/src/System.Management.Automation/engine/ICommandRuntime.cs +++ b/src/System.Management.Automation/engine/ICommandRuntime.cs @@ -1,6 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using System.Management.Automation.Host; @@ -14,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 @@ -27,12 +28,12 @@ public interface ICommandRuntime /// /// Returns an instance of the PSHost implementation for this environment. /// - PSHost Host { get; } + PSHost? Host { get; } #region Write /// - /// Display debug information + /// Display debug information. /// - /// debug output + /// Debug output. /// /// This API is called by the cmdlet to display debug information on the inner workings /// of the Cmdlet. An implementation of this interface should display this information in @@ -52,7 +53,7 @@ public interface ICommandRuntime /// a /// rather than the real exception. /// - /// error + /// Error. void WriteError(ErrorRecord errorRecord); /// @@ -66,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. @@ -84,12 +85,12 @@ 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 + /// Called by the cmdlet to display progress information. /// - /// progress information + /// Progress information. /// /// Use WriteProgress to display progress information about /// the activity of your Task, when the operation of your Task @@ -109,7 +110,7 @@ public interface ICommandRuntime void WriteProgress(ProgressRecord progressRecord); /// - /// Displays progress output if enabled + /// Displays progress output if enabled. /// /// /// Identifies which command is reporting progress @@ -125,9 +126,9 @@ public interface ICommandRuntime void WriteProgress(Int64 sourceId, ProgressRecord progressRecord); /// - /// Called when the cmdlet want to display verbose information + /// Called when the cmdlet want to display verbose information. /// - /// verbose output + /// Verbose output. /// /// Cmdlets use WriteVerbose to display more detailed information about /// the activity of the Cmdlet. By default, verbose output will @@ -145,9 +146,9 @@ public interface ICommandRuntime void WriteVerbose(string text); /// - /// Called by the cmdlet to display warning information + /// Called by the cmdlet to display warning information. /// - /// warning output + /// Warning output. /// /// Use WriteWarning to display warnings about /// the activity of your Cmdlet. By default, warning output will @@ -167,13 +168,13 @@ public interface ICommandRuntime /// /// Write text into pipeline execution log. /// - /// text to be written to log + /// Text to be written to log. /// /// Use WriteCommandDetail to write important information about cmdlet execution to /// 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" /// /// /// @@ -220,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 @@ -266,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 @@ -320,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 @@ -380,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. @@ -437,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. @@ -456,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. /// /// @@ -502,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 @@ -514,12 +515,11 @@ public interface ICommandRuntime /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// - PSTransactionContext CurrentPSTransaction { get; } + PSTransactionContext? CurrentPSTransaction { get; } #endregion Transaction Support - #region Misc #region ThrowTerminatingError /// @@ -551,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 @@ -591,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. /// /// @@ -616,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); } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/InformationRecord.cs b/src/System.Management.Automation/engine/InformationRecord.cs index e6bee00af5c..87a3d9cda9d 100644 --- a/src/System.Management.Automation/engine/InformationRecord.cs +++ b/src/System.Management.Automation/engine/InformationRecord.cs @@ -1,49 +1,41 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; using System.Collections.Generic; +using System.Runtime.Serialization; namespace System.Management.Automation { /// - /// /// Defines a data structure used to represent informational context destined for the host or user. - /// /// /// - /// - /// InformationRecords are passed to , + /// InformationRecords are passed to , /// which, according to host or user preference, forwards that information on to the host for rendering to the user. - /// /// - /// - - [DataContract()] + /// + [DataContract] public class InformationRecord { /// - /// /// Initializes a new instance of the InformationRecord class. - /// /// /// The object to be transmitted to the host. - /// The source of the message (i.e.: script path, function name, etc.) - public InformationRecord(Object messageData, string source) + /// The source of the message (i.e.: script path, function name, etc.). + public InformationRecord(object messageData, string source) { this.MessageData = messageData; this.Source = source; this.TimeGenerated = DateTime.Now; this.NativeThreadId = PsUtils.GetNativeThreadId(); - this.ManagedThreadId = (uint)System.Threading.Thread.CurrentThread.ManagedThreadId; + this.ManagedThreadId = (uint)Environment.CurrentManagedThreadId; } private InformationRecord() { } /// - /// Copy constructor + /// Copy constructor. /// internal InformationRecord(InformationRecord baseRecord) { @@ -65,10 +57,10 @@ internal InformationRecord(InformationRecord baseRecord) // are that way because they are fundamental properties of the record itself. /// - /// The message data for this informational record + /// The message data for this informational record. /// [DataMember] - public Object MessageData { get; internal set; } + public object MessageData { get; internal set; } /// /// The source of this informational record (script path, function name, etc.) @@ -89,47 +81,55 @@ 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; } } + private List _tags; /// - /// The user that generated this informational record + /// The user that generated this informational record. /// [DataMember] 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 = Platform.Unix.UserName; + Environment.UserName; #else - this._user = System.Security.Principal.WindowsIdentity.GetCurrent().Name; + Environment.UserDomainName + "\\" + Environment.UserName; #endif - } + return _user; } - set { _user = value; } + + set + { + _user = value; + } } + private string _user; /// - /// The computer that generated this informational record + /// The computer that generated this informational record. /// [DataMember] public string Computer { - get { return this._computerName ?? (this._computerName = PsUtils.GetHostName()); } + get { return this._computerName ??= PsUtils.GetHostName(); } + set { this._computerName = value; } } + private string _computerName; /// - /// The process that generated this informational record + /// The process that generated this informational record. /// [DataMember] public uint ProcessId @@ -138,21 +138,27 @@ 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; /// - /// The native thread that generated this informational record + /// The native thread that generated this informational record. /// public uint NativeThreadId { get; set; } /// - /// The managed thread that generated this informational record + /// The managed thread that generated this informational record. /// [DataMember] public uint ManagedThreadId { get; set; } @@ -177,7 +183,7 @@ internal static InformationRecord FromPSObjectForRemoting(PSObject inputObject) { InformationRecord informationRecord = new InformationRecord(); - informationRecord.MessageData = RemotingDecoder.GetPropertyValue(inputObject, "MessageData"); + informationRecord.MessageData = RemotingDecoder.GetPropertyValue(inputObject, "MessageData"); informationRecord.Source = RemotingDecoder.GetPropertyValue(inputObject, "Source"); informationRecord.TimeGenerated = RemotingDecoder.GetPropertyValue(inputObject, "TimeGenerated"); @@ -201,7 +207,7 @@ internal static InformationRecord FromPSObjectForRemoting(PSObject inputObject) /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// - /// This object as a PSObject property bag + /// This object as a PSObject property bag. internal PSObject ToPSObjectForRemoting() { PSObject informationAsPSObject = RemotingEncoder.CreateEmptyPSObject(); @@ -227,28 +233,27 @@ internal PSObject ToPSObjectForRemoting() public class HostInformationMessage { /// - /// The message being output by the host + /// The message being output by the host. /// public string Message { get; set; } /// - /// 'True' if the host should not append a NewLine to the message output + /// 'True' if the host should not append a NewLine to the message output. /// public bool? NoNewLine { get; set; } /// - /// The foreground color of the message + /// The foreground color of the message. /// public ConsoleColor? ForegroundColor { get; set; } /// - /// The background color of the message + /// The background color of the message. /// public ConsoleColor? BackgroundColor { get; set; } - /// - /// Returns a string-based representation of the host information message + /// Returns a string-based representation of the host information message. /// /// public override string ToString() diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 9cdd6656c2f..a6d50f47443 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; @@ -10,20 +9,24 @@ using System.Diagnostics.Tracing; using System.IO; using System.Linq; +using System.Management.Automation.Host; using System.Management.Automation.Internal; -using System.Management.Automation.Provider; using System.Management.Automation.Language; +using System.Management.Automation.Provider; +using System.Management.Automation.Security; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; using System.Threading; +using System.Threading.Tasks; + using Microsoft.PowerShell.Commands; + using Debug = System.Management.Automation.Diagnostics; -using System.Management.Automation.Host; -using System.Text; -using System.Threading.Tasks; namespace System.Management.Automation.Runspaces { - internal class EarlyStartup + internal static class EarlyStartup { internal static void Init() { @@ -40,34 +43,29 @@ internal static void Init() // * have high disk cost // We shouldn't create too many tasks. - - // This task takes awhile, so it gets it's own task - Task.Run(() => - { - // Building the catalog is expensive, so force that to happen early on a background thread, and do so - // on a file we are very likely to read anyway. - var pshome = Utils.DefaultPowerShellAppBase; - var unused = SecuritySupport.IsProductBinary(Path.Combine(pshome, "Modules", "Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Utility.psm1")); - }); +#if !UNIX + // 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. - var unused0 = RunspaceInit.OutputEncodingDescription; - - // Amsi initialize can also be a little slow - if (Platform.IsWindows) - { - AmsiUtils.Init(); - } + // 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. - var unused1 = 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. - var unused2 = LanguagePrimitives.GetEnumerator(null); + // We will access 'TypeAccelerators' when auto-loading the PSReadLine module, which happens last. + _ = TypeAccelerators.builtinTypeAccelerators; }); } } @@ -79,7 +77,7 @@ internal static void Init() public abstract class InitialSessionStateEntry { /// - /// ctor so that each derived class has a name + /// The ctor so that each derived class has a name. /// /// protected InitialSessionStateEntry(string name) @@ -88,12 +86,12 @@ protected InitialSessionStateEntry(string name) } /// - /// The name of this entry + /// The name of this entry. /// public string Name { get; internal set; } /// - /// The SnapIn to load from initially + /// The SnapIn to load from initially. /// public PSSnapInInfo PSSnapIn { get; private set; } @@ -103,7 +101,7 @@ internal void SetPSSnapIn(PSSnapInInfo psSnapIn) } /// - /// The SnapIn to load from initially + /// The SnapIn to load from initially. /// public PSModuleInfo Module { get; private set; } @@ -113,19 +111,18 @@ internal void SetModule(PSModuleInfo module) } /// - /// Shallow-clone this object + /// Shallow-clone this object. /// /// The cloned object... public abstract InitialSessionStateEntry Clone(); } /// - /// Class to constrain session state entries + /// Class to constrain session state entries. /// public abstract class ConstrainedSessionStateEntry : InitialSessionStateEntry { /// - /// /// /// /// @@ -136,12 +133,10 @@ protected ConstrainedSessionStateEntry(string name, SessionStateEntryVisibility } /// - /// /// public SessionStateEntryVisibility Visibility { get; set; } } - /// /// Command class so that all the commands can derive off this one. /// Adds the flexibility of adding additional derived class, @@ -160,7 +155,6 @@ protected SessionStateCommandEntry(string name) } /// - /// /// /// /// @@ -178,7 +172,7 @@ protected internal SessionStateCommandEntry(string name, SessionStateEntryVisibi /// /// Is internal so it can be set by the engine code... /// This is used to specify whether this command was imported or not - /// If noClobber is specified during Import-Module, it is set to false + /// If noClobber is specified during Import-Module, it is set to false. /// internal bool _isImported = true; } @@ -197,14 +191,14 @@ public SessionStateTypeEntry(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { - throw PSTraceSource.NewArgumentException("fileName"); + throw PSTraceSource.NewArgumentException(nameof(fileName)); } FileName = fileName.Trim(); } /// - /// Loads all the types specified in the typeTable + /// Loads all the types specified in the typeTable. /// /// public SessionStateTypeEntry(TypeTable typeTable) @@ -212,13 +206,14 @@ public SessionStateTypeEntry(TypeTable typeTable) { if (typeTable == null) { - throw PSTraceSource.NewArgumentNullException("typeTable"); + throw PSTraceSource.NewArgumentNullException(nameof(typeTable)); } + TypeTable = typeTable; } /// - /// Loads all entries from the typeData + /// Loads all entries from the typeData. /// /// /// @@ -227,16 +222,17 @@ public SessionStateTypeEntry(TypeData typeData, bool isRemove) { if (typeData == null) { - throw PSTraceSource.NewArgumentNullException("typeData"); + throw PSTraceSource.NewArgumentNullException(nameof(typeData)); } + TypeData = typeData; IsRemove = isRemove; } /// - /// Shallow-clone this object + /// Shallow-clone this object. /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateTypeEntry entry; @@ -282,10 +278,10 @@ public override InitialSessionStateEntry Clone() /// public bool IsRemove { get; } - //So that we can specify the type information on the fly, - //without using Types.ps1xml file - //public SessionStateTypeEntry(string name, xmlreader definition); - //public string Definition { get; } + // So that we can specify the type information on the fly, + // without using Types.ps1xml file + // public SessionStateTypeEntry(string name, xmlreader definition); + // public string Definition { get; } } /// @@ -294,7 +290,7 @@ public override InitialSessionStateEntry Clone() public sealed class SessionStateFormatEntry : InitialSessionStateEntry { /// - /// Loads the entire formats file + /// Loads the entire formats file. /// /// public SessionStateFormatEntry(string fileName) @@ -302,14 +298,14 @@ public SessionStateFormatEntry(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { - throw PSTraceSource.NewArgumentException("fileName"); + throw PSTraceSource.NewArgumentException(nameof(fileName)); } FileName = fileName.Trim(); } /// - /// Loads all the format data specified in the formatTable + /// Loads all the format data specified in the formatTable. /// /// public SessionStateFormatEntry(FormatTable formattable) @@ -317,13 +313,14 @@ public SessionStateFormatEntry(FormatTable formattable) { if (formattable == null) { - throw PSTraceSource.NewArgumentNullException("formattable"); + throw PSTraceSource.NewArgumentNullException(nameof(formattable)); } + Formattable = formattable; } /// - /// Loads all the format data specified in the typeDefinition + /// Loads all the format data specified in the typeDefinition. /// /// public SessionStateFormatEntry(ExtendedTypeDefinition typeDefinition) @@ -331,15 +328,16 @@ public SessionStateFormatEntry(ExtendedTypeDefinition typeDefinition) { if (typeDefinition == null) { - throw PSTraceSource.NewArgumentNullException("typeDefinition"); + throw PSTraceSource.NewArgumentNullException(nameof(typeDefinition)); } + FormatData = typeDefinition; } /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateFormatEntry entry; @@ -374,15 +372,15 @@ public override InitialSessionStateEntry Clone() public FormatTable Formattable { get; } /// - /// The FormatData specified with constructor. This can be null if - /// FileName or FormatTable constructor is used + /// The FormatData specified with constructor. + /// This can be null if the FileName or FormatTable constructors are used. /// public ExtendedTypeDefinition FormatData { get; } - //So that we can specify the format information on the fly, - //without using Format.ps1xml file - //public SessionStateFormatEntry(string name, xmlreader definition); - //public string Definition { get; } + // So that we can specify the format information on the fly, + // without using Format.ps1xml file + // public SessionStateFormatEntry(string name, xmlreader definition); + // public string Definition { get; } } /// @@ -394,8 +392,8 @@ public sealed class SessionStateAssemblyEntry : InitialSessionStateEntry /// Create a named entry for the assembly to load with both the /// name and the path to the assembly as a backup. /// - /// The name of the assembly to load - /// The path to the assembly to use as an alternative + /// The name of the assembly to load. + /// The path to the assembly to use as an alternative. public SessionStateAssemblyEntry(string name, string fileName) : base(name) { @@ -404,22 +402,21 @@ public SessionStateAssemblyEntry(string name, string fileName) /// /// Create a named entry for the assembly to load, specifying - /// just the name + /// just the name. /// - /// The name of the assembly to load + /// The name of the assembly to load. public SessionStateAssemblyEntry(string name) : base(name) { - ; } /// - /// Shallow-clone this object + /// Shallow-clone this object. /// - /// The cloned object + /// 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; @@ -432,12 +429,11 @@ public override InitialSessionStateEntry Clone() } /// - /// List a cmdlet to add to this session state entry + /// List a cmdlet to add to this session state entry. /// public sealed class SessionStateCmdletEntry : SessionStateCommandEntry { /// - /// /// /// /// @@ -450,9 +446,7 @@ public SessionStateCmdletEntry(string name, Type implementingType, string helpFi CommandType = CommandTypes.Cmdlet; } - /// - /// /// /// /// @@ -479,23 +473,19 @@ public override InitialSessionStateEntry Clone() } /// - /// /// public Type ImplementingType { get; } /// - /// /// public string HelpFileName { get; } } /// - /// /// public sealed class SessionStateProviderEntry : ConstrainedSessionStateEntry { /// - /// /// /// /// @@ -514,11 +504,10 @@ internal SessionStateProviderEntry(string name, Type implementingType, string he HelpFileName = helpFileName; } - /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateProviderEntry entry = new SessionStateProviderEntry(Name, ImplementingType, HelpFileName, this.Visibility); @@ -528,25 +517,22 @@ public override InitialSessionStateEntry Clone() } /// - /// /// public Type ImplementingType { get; } /// - /// /// public string HelpFileName { get; } } /// - /// /// public sealed class SessionStateScriptEntry : SessionStateCommandEntry { /// /// Create a session state command entry instance. /// - /// The path to the script + /// The path to the script. public SessionStateScriptEntry(string path) : base(path, SessionStateEntryVisibility.Public) { @@ -557,7 +543,7 @@ public SessionStateScriptEntry(string path) /// /// Create a session state command entry instance with the specified visibility. /// - /// The path to the script + /// The path to the script. /// Visibility of the script. internal SessionStateScriptEntry(string path, SessionStateEntryVisibility visibility) : base(path, visibility) @@ -569,7 +555,7 @@ internal SessionStateScriptEntry(string path, SessionStateEntryVisibility visibi /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateScriptEntry entry = new SessionStateScriptEntry(Path, Visibility); @@ -578,21 +564,19 @@ public override InitialSessionStateEntry Clone() } /// - /// /// public string Path { get; } } /// - /// /// public sealed class SessionStateAliasEntry : SessionStateCommandEntry { /// - /// Define an alias entry to add to the initial session state + /// Define an alias entry to add to the initial session state. /// - /// Name of the alias - /// The name of the command it resolves to + /// The name of the alias entry to add. + /// The name of the command it resolves to. public SessionStateAliasEntry(string name, string definition) : base(name, SessionStateEntryVisibility.Public) { @@ -601,10 +585,10 @@ public SessionStateAliasEntry(string name, string definition) } /// - /// Define an alias entry to add to the initial session state + /// Define an alias entry to add to the initial session state. /// - /// Name of the alias - /// The name of the command it resolves to + /// The name of the alias entry to add. + /// The name of the command it resolves to. /// A description of the purpose of the alias. public SessionStateAliasEntry(string name, string definition, string description) : base(name, SessionStateEntryVisibility.Public) @@ -615,12 +599,12 @@ public SessionStateAliasEntry(string name, string definition, string description } /// - /// Define an alias entry to add to the initial session state + /// Define an alias entry to add to the initial session state. /// - /// Name of the alias - /// The name of the command it resolves to + /// The name of the alias entry to add. + /// The name of the command it resolves to. /// A description of the purpose of the alias. - /// Options defining the scope visibility, readonly and constant + /// Options defining the scope visibility, readonly and constant. public SessionStateAliasEntry(string name, string definition, string description, ScopedItemOptions options) : base(name, SessionStateEntryVisibility.Public) { @@ -631,15 +615,14 @@ public SessionStateAliasEntry(string name, string definition, string description } /// - /// Define an alias entry to add to the initial session state + /// Define an alias entry to add to the initial session state. /// - /// Name of the alias - /// The name of the command it resolves to + /// The name of the alias entry to add. + /// The name of the command it resolves to. /// A description of the purpose of the alias. - /// Options defining the scope visibility, readonly and constant + /// Options defining the scope visibility, readonly and constant. /// - internal SessionStateAliasEntry(string name, string definition, string description, - ScopedItemOptions options, SessionStateEntryVisibility visibility) + internal SessionStateAliasEntry(string name, string definition, string description, ScopedItemOptions options, SessionStateEntryVisibility visibility) : base(name, visibility) { Definition = definition; @@ -650,7 +633,7 @@ internal SessionStateAliasEntry(string name, string definition, string descripti /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateAliasEntry entry = new SessionStateAliasEntry(Name, Definition, Description, Options, Visibility); @@ -666,16 +649,15 @@ public override InitialSessionStateEntry Clone() /// /// A string describing this alias... /// - public string Description { get; } = String.Empty; + 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; } /// - /// /// public sealed class SessionStateApplicationEntry : SessionStateCommandEntry { @@ -683,7 +665,7 @@ public sealed class SessionStateApplicationEntry : SessionStateCommandEntry /// Used to define a permitted script in this session state. If the path is /// "*", then any path is permitted. /// - /// The full path to the application + /// The full path to the application. public SessionStateApplicationEntry(string path) : base(path, SessionStateEntryVisibility.Public) { @@ -695,7 +677,7 @@ public SessionStateApplicationEntry(string path) /// Used to define a permitted script in this session state. If the path is /// "*", then any path is permitted. /// - /// The full path to the application + /// The full path to the application. /// Sets the external visibility of the path. internal SessionStateApplicationEntry(string path, SessionStateEntryVisibility visibility) : base(path, visibility) @@ -707,7 +689,7 @@ internal SessionStateApplicationEntry(string path, SessionStateEntryVisibility v /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateApplicationEntry entry = new SessionStateApplicationEntry(Path, Visibility); @@ -722,17 +704,16 @@ public override InitialSessionStateEntry Clone() } /// - /// /// public sealed class SessionStateFunctionEntry : SessionStateCommandEntry { /// /// Represents a function definition in an Initial session state object. /// - /// The name of the function - /// The definition of the function - /// Options controlling scope-related elements of this object - /// The name of the help file associated with the function + /// The name of the function. + /// The definition of the function. + /// Options controlling scope-related elements of this object. + /// The name of the help file associated with the function. public SessionStateFunctionEntry(string name, string definition, ScopedItemOptions options, string helpFile) : base(name, SessionStateEntryVisibility.Public) { @@ -748,9 +729,9 @@ public SessionStateFunctionEntry(string name, string definition, ScopedItemOptio /// /// Represents a function definition in an Initial session state object. /// - /// The name of the function - /// The definition of the function - /// The name of the help file associated with the function + /// The name of the function. + /// The definition of the function. + /// The name of the help file associated with the function. public SessionStateFunctionEntry(string name, string definition, string helpFile) : this(name, definition, ScopedItemOptions.None, helpFile) { @@ -759,8 +740,8 @@ public SessionStateFunctionEntry(string name, string definition, string helpFile /// /// Represents a function definition in an Initial session state object. /// - /// The name of the function - /// The definition of the function + /// The name of the function. + /// The definition of the function. public SessionStateFunctionEntry(string name, string definition) : this(name, definition, ScopedItemOptions.None, null) { @@ -780,23 +761,28 @@ internal SessionStateFunctionEntry(string name, string definition, ScopedItemOpt HelpFile = helpFile; } + internal static SessionStateFunctionEntry GetDelayParsedFunctionEntry(string name, string definition, bool isProductCode, PSLanguageMode languageMode) + { + var fnEntry = GetDelayParsedFunctionEntry(name, definition, isProductCode); + fnEntry.ScriptBlock.LanguageMode = languageMode; + return fnEntry; + } + internal static SessionStateFunctionEntry GetDelayParsedFunctionEntry(string name, string definition, bool isProductCode) { var sb = ScriptBlock.CreateDelayParsedScriptBlock(definition, isProductCode); - return new SessionStateFunctionEntry(name, definition, ScopedItemOptions.None, - SessionStateEntryVisibility.Public, sb, null); + return new SessionStateFunctionEntry(name, definition, ScopedItemOptions.None, SessionStateEntryVisibility.Public, sb, null); } internal static SessionStateFunctionEntry GetDelayParsedFunctionEntry(string name, string definition, ScriptBlock sb) { - return new SessionStateFunctionEntry(name, definition, ScopedItemOptions.None, - SessionStateEntryVisibility.Public, sb, null); + return new SessionStateFunctionEntry(name, definition, ScopedItemOptions.None, SessionStateEntryVisibility.Public, sb, null); } /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { SessionStateFunctionEntry entry = new SessionStateFunctionEntry(Name, Definition, Options, Visibility, ScriptBlock, HelpFile); @@ -823,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; @@ -834,7 +820,6 @@ internal void SetHelpFile(string help) } /// - /// /// public sealed class SessionStateVariableEntry : ConstrainedSessionStateEntry { @@ -844,8 +829,8 @@ public sealed class SessionStateVariableEntry : ConstrainedSessionStateEntry /// then the clone will contain a reference to the original object /// not a clone of it. /// - /// The name of the variable - /// The value to set the variable to + /// The name of the variable. + /// The value to set the variable to. /// A descriptive string to attach to the variable. public SessionStateVariableEntry(string name, object value, string description) : base(name, SessionStateEntryVisibility.Public) @@ -860,8 +845,8 @@ public SessionStateVariableEntry(string name, object value, string description) /// then the clone will contain a reference to the original object /// not a clone of it. /// - /// The name of the variable - /// The value to set the variable to + /// The name of the variable. + /// The value to set the variable to. /// A descriptive string to attach to the variable. /// Options like readonly, constant, allscope, etc. public SessionStateVariableEntry(string name, object value, string description, ScopedItemOptions options) @@ -878,13 +863,12 @@ public SessionStateVariableEntry(string name, object value, string description, /// then the clone will contain a reference to the original object /// not a clone of it. /// - /// The name of the variable - /// The value to set the variable to + /// The name of the variable. + /// The value to set the variable to. /// A descriptive string to attach to the variable. /// Options like readonly, constant, allscope, etc. /// A list of attributes to attach to the variable. - public SessionStateVariableEntry(string name, object value, string description, - ScopedItemOptions options, Collection attributes) + public SessionStateVariableEntry(string name, object value, string description, ScopedItemOptions options, Collection attributes) : base(name, SessionStateEntryVisibility.Public) { Value = value; @@ -899,13 +883,12 @@ public SessionStateVariableEntry(string name, object value, string description, /// then the clone will contain a reference to the original object /// not a clone of it. /// - /// The name of the variable - /// The value to set the variable to + /// The name of the variable. + /// The value to set the variable to. /// A descriptive string to attach to the variable. /// Options like readonly, constant, allscope, etc. /// A single attribute to attach to the variable. - public SessionStateVariableEntry(string name, object value, string description, - ScopedItemOptions options, Attribute attribute) + public SessionStateVariableEntry(string name, object value, string description, ScopedItemOptions options, Attribute attribute) : base(name, SessionStateEntryVisibility.Public) { Value = value; @@ -921,14 +904,13 @@ public SessionStateVariableEntry(string name, object value, string description, /// then the clone will contain a reference to the original object /// not a clone of it. /// - /// The name of the variable - /// The value to set the variable to + /// The name of the variable. + /// The value to set the variable to. /// A descriptive string to attach to the variable. /// Options like readonly, constant, allscope, etc. /// A single attribute to attach to the variable. /// - internal SessionStateVariableEntry(string name, object value, string description, - ScopedItemOptions options, Collection attributes, SessionStateEntryVisibility visibility) + internal SessionStateVariableEntry(string name, object value, string description, ScopedItemOptions options, Collection attributes, SessionStateEntryVisibility visibility) : base(name, visibility) { Value = value; @@ -940,13 +922,16 @@ internal SessionStateVariableEntry(string name, object value, string description /// /// Shallow-clone this object... /// - /// The cloned object + /// The cloned object. public override InitialSessionStateEntry Clone() { // Copy the attribute collection if necessary... Collection attrs = null; if (_attributes != null && _attributes.Count > 0) + { attrs = new Collection(_attributes); + } + return new SessionStateVariableEntry(Name, Value, Description, Options, attrs, Visibility); } @@ -958,7 +943,7 @@ public override InitialSessionStateEntry Clone() /// /// The description associated with this variable. /// - public string Description { get; } = String.Empty; + public string Description { get; } = string.Empty; /// /// The options associated with this variable (e.g. readonly, allscope, etc.) @@ -970,14 +955,13 @@ public override InitialSessionStateEntry Clone() /// public Collection Attributes { - get { return _attributes ?? (_attributes = new Collection()); } + get { return _attributes ??= new Collection(); } } + private Collection _attributes; } - /// - /// /// /// public sealed class InitialSessionStateEntryCollection : IEnumerable where T : InitialSessionStateEntry @@ -996,10 +980,7 @@ public InitialSessionStateEntryCollection() /// public InitialSessionStateEntryCollection(IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException("items"); - } + ArgumentNullException.ThrowIfNull(items); _internalCollection = new Collection(); @@ -1010,9 +991,9 @@ public InitialSessionStateEntryCollection(IEnumerable items) } /// - /// Clone this collection + /// Clone this collection. /// - /// The cloned object + /// The cloned collection. public InitialSessionStateEntryCollection Clone() { InitialSessionStateEntryCollection result; @@ -1025,11 +1006,12 @@ public InitialSessionStateEntryCollection Clone() result.Add((T)item.Clone()); } } + return result; } /// - /// Reset the collection + /// Reset the collection. /// public void Reset() { @@ -1048,7 +1030,6 @@ public int Count } /// - /// /// /// /// @@ -1061,17 +1042,15 @@ public T this[int index] { result = _internalCollection[index]; } + return result; } } - //To find the entries based on name. - //Why collection - Different SnapIn/modules and same entity names. - //If used on command collection entry, then for the same name, one can have multiple output /// /// To find the entries based on name. /// Why collection - Different SnapIn/modules and same entity names. - /// If used on command collection entry, then for the same name, one can have multiple output + /// If used on command collection entry, then for the same name, one can have multiple output. /// /// /// @@ -1090,6 +1069,7 @@ public Collection this[string name] } } } + return result; } } @@ -1101,7 +1081,10 @@ public Collection this[string name] /// internal Collection LookUpByName(string name) { - if (name == null) { throw new PSArgumentNullException("name"); } + if (name == null) + { + throw new PSArgumentNullException(nameof(name)); + } Collection result = new Collection(); WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); @@ -1120,7 +1103,6 @@ internal Collection LookUpByName(string name) } /// - /// /// /// public void RemoveItem(int index) @@ -1158,7 +1140,6 @@ public void Clear() } } - /// /// This overload exists so that we can remove items based on the item name, rather than /// its position in the collection. The type argument can be null but we'll throw an error if @@ -1166,12 +1147,11 @@ public void Clear() /// and the type hasn't been specified. /// BUGBUG - brucepay - the throw thing is not implemented yet... /// - /// The name of the element to remove + /// The name of the element to remove. /// 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("name"); + ArgumentNullException.ThrowIfNull(name); lock (_syncObject) { @@ -1187,9 +1167,12 @@ public void Remove(string name, object type) { T element = _internalCollection[i]; if (element == null) + { continue; + } + if ((objType == null || element.GetType() == objType) && - String.Equals(element.Name, name, StringComparison.OrdinalIgnoreCase)) + string.Equals(element.Name, name, StringComparison.OrdinalIgnoreCase)) { _internalCollection.RemoveAt(i); } @@ -1203,8 +1186,7 @@ public void Remove(string name, object type) /// The item to add... public void Add(T item) { - if (item == null) - throw new ArgumentNullException("item"); + ArgumentNullException.ThrowIfNull(item); lock (_syncObject) { @@ -1218,8 +1200,7 @@ public void Add(T item) /// public void Add(IEnumerable items) { - if (items == null) - throw new ArgumentNullException("items"); + ArgumentNullException.ThrowIfNull(items); lock (_syncObject) { @@ -1230,27 +1211,6 @@ public void Add(IEnumerable items) } } - /// - /// Special add for TypeTable type entries that removes redundant file entries. - /// - internal void AddTypeTableTypesInfo(IEnumerable items) - { - if (typeof(T) != typeof(SessionStateTypeEntry)) { throw new PSInvalidOperationException(); } - - lock (_syncObject) - { - foreach (var element in items) - { - var typeEntry = element as SessionStateTypeEntry; - if (typeEntry.TypeData != null) - { - // Skip type file entries. - _internalCollection.Add(element); - } - } - } - } - /// /// Get enumerator for this collection. /// @@ -1281,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(); + // object to use for locking + private readonly object _syncObject = new object(); } /// @@ -1343,34 +1303,72 @@ private static void MakeDisallowedEntriesPrivate(InitialSessionStateEntryColl } /// - /// Creates an initial session state from a PSSC configuration file + /// Creates an initial session state from a PSSC configuration file. /// - /// The path to the PSSC session configuration file - /// + /// The path to the PSSC session configuration file. + /// InitialSessionState object. public static InitialSessionState CreateFromSessionConfigurationFile(string path) { return CreateFromSessionConfigurationFile(path, null); } /// - /// Creates an initial session state from a PSSC configuration file + /// Creates an initial session state from a PSSC configuration file. /// - /// The path to the PSSC session 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(). /// - /// - public static InitialSessionState CreateFromSessionConfigurationFile(string path, Func roleVerifier) + /// InitialSessionState object. + public static InitialSessionState CreateFromSessionConfigurationFile( + string path, + Func roleVerifier) + { + return CreateFromSessionConfigurationFile(path, roleVerifier, validateFile: false); + } + + /// + /// Creates an initial session state from a PSSC configuration file. + /// + /// The path to the PSSC session configuration file. + /// + /// The verifier that PowerShell should call to determine if groups in the Role entry apply to the + /// target session. If you have a WindowsPrincipal for a user, for example, create a Function that + /// checks windowsPrincipal.IsInRole(). + /// + /// Validates the file contents for supported SessionState options. + /// InitialSessionState object. + public static InitialSessionState CreateFromSessionConfigurationFile( + string path, + Func roleVerifier, + bool validateFile) { - Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier); + if (path is null) + { + throw new PSArgumentNullException(nameof(path)); + } + + if (!File.Exists(path)) + { + throw new PSInvalidOperationException( + StringUtil.Format(ConsoleInfoErrorStrings.ConfigurationFileDoesNotExist, path)); + } + + if (!path.EndsWith(".pssc", StringComparison.OrdinalIgnoreCase)) + { + throw new PSInvalidOperationException( + StringUtil.Format(ConsoleInfoErrorStrings.NotConfigurationFile, path)); + } + + Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier, validateFile); return discConfiguration.GetInitialSessionState(null); } /// /// Creates an instance that exposes only the minimal - /// set of commands needed by give set of + /// set of commands needed by give set of . /// All commands that are not needed are made private in order to minimize the attack surface. /// /// @@ -1380,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(); } @@ -1407,10 +1405,7 @@ private static InitialSessionState CreateRestrictedForRemoteServer() iss.ImportPSSnapIn(si, out warning); } - // // restrict what gets exposed - // - List allowedCommands = new List(); // required by implicit remoting and interactive remoting @@ -1452,9 +1447,7 @@ private static InitialSessionState CreateRestrictedForRemoteServer() // Add built-in variables. iss.Variables.Add(BuiltInVariables); - // // wrap some commands in a proxy function to restrict their parameters - // foreach (KeyValuePair proxyFunction in CommandMetadata.GetRestrictedCommands(SessionCapabilities.RemoteServer)) { string commandName = proxyFunction.Key; @@ -1466,16 +1459,13 @@ private static InitialSessionState CreateRestrictedForRemoteServer() originalCmdlet[0].Visibility = SessionStateEntryVisibility.Private; // and add a public proxy function - string proxyBody = ProxyCommand.Create(proxyFunction.Value, "", false); + string proxyBody = ProxyCommand.Create(proxyFunction.Value, string.Empty, false); iss.Commands.Add(new SessionStateFunctionEntry(commandName, proxyBody)); } 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; @@ -1485,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))); } @@ -1494,7 +1484,7 @@ private static void IncludePowerShellCoreFormats(InitialSessionState iss) #endregion /// - /// ctor for Custom-Shell - Do we need this? + /// Ctor for Custom-Shell - Do we need this? /// protected InitialSessionState() { @@ -1509,14 +1499,14 @@ public static InitialSessionState Create() { InitialSessionState iss = new InitialSessionState(); - //TODO: the following code is probably needed for the hosted constrained runspace + // TODO: the following code is probably needed for the hosted constrained runspace // There are too many things that depend on the built-in variables. At the same time, // these variables can't be public or they become a security issue. // This change still needs to be spec-reviewed before turning it on. It also seems to // 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; @@ -1553,12 +1543,12 @@ public static InitialSessionState CreateDefault() { ss.ImportPSSnapIn(si, out warning); } - catch (PSSnapInException pse) + catch (PSSnapInException) { - throw pse; + throw; } #if DEBUG - //NOTE: + // NOTE: // This code is for testing a module-based shell. It is only available when the shell is complied // in debug mode and is not intended to be a feature. // July 31 2008 - brucepay @@ -1579,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); - } } } @@ -1596,10 +1582,9 @@ public static InitialSessionState CreateDefault() return ss.Clone(); } - /// /// Creates the default PowerShell one with default cmdlets, provider etc. - /// The default cmdlets, provider, etc are loaded via Modules + /// The default cmdlets, provider, etc are loaded via Modules. /// For loading Microsoft.PowerShell.Core module only. /// /// @@ -1692,9 +1677,7 @@ public InitialSessionState Clone() ss.UseFullLanguageModeInDebugger = this.UseFullLanguageModeInDebugger; ss.ThreadOptions = this.ThreadOptions; ss.ThrowOnRunspaceOpenError = this.ThrowOnRunspaceOpenError; -#if !CORECLR // No ApartmentState In CoreCLR ss.ApartmentState = this.ApartmentState; -#endif foreach (ModuleSpecification modSpec in this.ModuleSpecificationsToImport) { @@ -1705,12 +1688,8 @@ public InitialSessionState Clone() { ss.CoreModulesToImport.Add(mod); } - ss.DisableFormatUpdates = this.DisableFormatUpdates; - foreach (var s in this.defaultSnapins) - { - ss.defaultSnapins.Add(s); - } + ss.DisableFormatUpdates = this.DisableFormatUpdates; foreach (var s in ImportedSnapins) { @@ -1720,10 +1699,9 @@ public InitialSessionState Clone() return ss; } - /// /// Want to get away from SnapIn and console file. Have modules and assemblies instead. - /// Specify the registered SnapIn name or name collection + /// Specify the registered SnapIn name or name collection. /// /// /// @@ -1733,7 +1711,6 @@ public static InitialSessionState Create(string snapInName) } /// - /// /// /// /// @@ -1744,12 +1721,7 @@ public static InitialSessionState Create(string[] snapInNameCollection, out PSCo return new InitialSessionState(); } - //This one is for module. Can take only one module, and not collection - //public static InitialSessionState Create(Module); - - //Specify the unregistered module/snapIn path or path collection /// - /// /// /// /// @@ -1761,7 +1733,6 @@ public static InitialSessionState CreateFrom(string snapInPath, out PSConsoleLoa } /// - /// /// /// /// @@ -1773,12 +1744,12 @@ public static InitialSessionState CreateFrom(string[] snapInPathCollection, out } /// - /// Specifies the language mode to be used for this session state instance + /// Specifies the language mode to be used for this session state instance. /// public PSLanguageMode LanguageMode { get; set; } = PSLanguageMode.NoLanguage; /// - /// Specifies the directory to be used for collection session transcripts + /// Specifies the directory to be used for collection session transcripts. /// public string TranscriptDirectory { get; set; } = null; @@ -1792,8 +1763,7 @@ internal bool UserDriveEnabled } /// - /// User name for the user drive. This will be part of the root path - /// for the User PSDrive. + /// User name for the user drive. This will be part of the root path for the User PSDrive. /// internal string UserDriveUserName { @@ -1820,35 +1790,37 @@ internal bool EnforceInputParameterValidation } /// - /// Specifies the execution policy to be used for this session state instance + /// Specifies the execution policy to be used for this session state instance. /// public Microsoft.PowerShell.ExecutionPolicy ExecutionPolicy { - get { return _executionPolicy; } + get + { + return _executionPolicy; + } + set { _executionPolicy = value; _wasExecutionPolicySet = true; } } + private Microsoft.PowerShell.ExecutionPolicy _executionPolicy = Microsoft.PowerShell.ExecutionPolicy.Default; private bool _wasExecutionPolicySet = false; /// - /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode + /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode. /// public bool UseFullLanguageModeInDebugger { get; set; } = false; -#if !CORECLR // No ApartmentState In CoreCLR /// - /// ApartmentState of the thread used to execute commands + /// ApartmentState of the thread used to execute commands. /// public ApartmentState ApartmentState { get; set; } = Runspace.DefaultApartmentState; -#endif - /// - /// This property determines whether a new thread is create for each invocation of a command + /// This property determines whether a new thread is created for each invocation of a command. /// public PSThreadOptions ThreadOptions { get; set; } = PSThreadOptions.Default; @@ -1878,12 +1850,12 @@ public Microsoft.PowerShell.ExecutionPolicy ExecutionPolicy /// /// Add a list of modules to import when the runspace is created. /// - /// The modules to add + /// The modules to add. /// - public void ImportPSModule(string[] name) + public void ImportPSModule(params string[] name) { - if (name == null) - throw new ArgumentNullException("name"); + ArgumentNullException.ThrowIfNull(name); + foreach (string n in name) { ModuleSpecificationsToImport.Add(new ModuleSpecification(n)); @@ -1907,10 +1879,7 @@ internal void ClearPSModules() /// public void ImportPSModule(IEnumerable modules) { - if (modules == null) - { - throw new ArgumentNullException("modules"); - } + ArgumentNullException.ThrowIfNull(modules); foreach (var moduleSpecification in modules) { @@ -1919,11 +1888,11 @@ public void ImportPSModule(IEnumerable modules) } /// - /// Imports all the modules from the specified module - /// path by default + /// Imports all the modules from the specified module path by default. /// - /// path from which all modules need - /// to be imported + /// + /// Path from which all modules need to be imported. + /// public void ImportPSModulesFromPath(string path) { string expandedpath = Environment.ExpandEnvironmentVariables(path); @@ -1934,12 +1903,12 @@ public void ImportPSModulesFromPath(string path) /// /// Add a list of core modules to import when the runspace is created. /// - /// The modules to add + /// The modules to add. /// internal void ImportPSCoreModule(string[] name) { - if (name == null) - throw new ArgumentNullException("name"); + ArgumentNullException.ThrowIfNull(name); + foreach (string n in name) { CoreModulesToImport.Add(n); @@ -1971,10 +1940,14 @@ public virtual InitialSessionStateEntryCollection Ass get { if (_assemblies == null) + { Interlocked.CompareExchange(ref _assemblies, new InitialSessionStateEntryCollection(), null); + } + return _assemblies; } } + private InitialSessionStateEntryCollection _assemblies; /// @@ -1985,24 +1958,31 @@ public virtual InitialSessionStateEntryCollection Types get { if (_types == null) + { Interlocked.CompareExchange(ref _types, new InitialSessionStateEntryCollection(), null); + } + return _types; } } + private InitialSessionStateEntryCollection _types; /// - /// /// public virtual InitialSessionStateEntryCollection Formats { get { if (_formats == null) + { Interlocked.CompareExchange(ref _formats, new InitialSessionStateEntryCollection(), null); + } + return _formats; } } + private InitialSessionStateEntryCollection _formats; /// @@ -2014,17 +1994,20 @@ public virtual InitialSessionStateEntryCollection Forma public bool DisableFormatUpdates { get; set; } /// - /// /// public virtual InitialSessionStateEntryCollection Providers { get { if (_providers == null) + { Interlocked.CompareExchange(ref _providers, new InitialSessionStateEntryCollection(), null); + } + return _providers; } } + private InitialSessionStateEntryCollection _providers; /// @@ -2035,10 +2018,14 @@ public virtual InitialSessionStateEntryCollection Comm get { if (_commands == null) + { Interlocked.CompareExchange(ref _commands, new InitialSessionStateEntryCollection(), null); + } + return _commands; } } + private InitialSessionStateEntryCollection _commands; internal SessionStateEntryVisibility DefaultCommandVisibility { get; set; } @@ -2048,10 +2035,14 @@ internal HashSet UnresolvedCommandsToExpose get { if (_unresolvedCommandsToExpose == null) + { Interlocked.CompareExchange(ref _unresolvedCommandsToExpose, new HashSet(StringComparer.OrdinalIgnoreCase), null); + } + return _unresolvedCommandsToExpose; } } + private HashSet _unresolvedCommandsToExpose; internal Dictionary CommandModifications @@ -2059,10 +2050,14 @@ internal Dictionary CommandModifications get { if (_commandModifications == null) + { Interlocked.CompareExchange(ref _commandModifications, new Dictionary(StringComparer.OrdinalIgnoreCase), null); + } + return _commandModifications; } } + private Dictionary _commandModifications; internal List DynamicVariablesToDefine @@ -2070,62 +2065,70 @@ internal List DynamicVariablesToDefine get { if (_dynamicVariablesToDefine == null) + { Interlocked.CompareExchange(ref _dynamicVariablesToDefine, new List(), null); + } + return _dynamicVariablesToDefine; } } + private List _dynamicVariablesToDefine; /// - /// /// public virtual InitialSessionStateEntryCollection Variables { get { if (_variables == null) + { Interlocked.CompareExchange(ref _variables, new InitialSessionStateEntryCollection(), null); + } + return _variables; } } + private InitialSessionStateEntryCollection _variables; /// - /// /// public virtual InitialSessionStateEntryCollection EnvironmentVariables { get { if (_environmentVariables == null) + { Interlocked.CompareExchange(ref _environmentVariables, new InitialSessionStateEntryCollection(), null); + } + return _environmentVariables; } } + private InitialSessionStateEntryCollection _environmentVariables; /// - /// /// - public virtual HashSet StartupScripts + public virtual HashSet StartupScripts { get { if (_startupScripts == null) - Interlocked.CompareExchange(ref _startupScripts, new HashSet(), null); + { + Interlocked.CompareExchange(ref _startupScripts, new HashSet(), null); + } + return _startupScripts; } } - private HashSet _startupScripts = new HashSet(); - private Object _syncObject = new Object(); + private HashSet _startupScripts; - internal void Bind(ExecutionContext context, bool updateOnly) - { - Bind(context, updateOnly, null, /*noClobber*/false, /*local*/ false); - } + private readonly object _syncObject = new object(); - internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo module, bool noClobber, bool local) + internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo module, bool noClobber, bool local, bool setLocation) { Host = context.EngineHostInterface; lock (_syncObject) @@ -2228,13 +2231,16 @@ internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo modul } } - SetSessionStateDrive(context, setLocation: false); + SetSessionStateDrive(context, setLocation: setLocation); } private void Bind_SetVariables(SessionStateInternal ss) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.LoadVariablesStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadVariablesStart(); + } // Add all of the variables to session state... foreach (SessionStateVariableEntry var in Variables) @@ -2242,64 +2248,105 @@ private void Bind_SetVariables(SessionStateInternal ss) ss.AddSessionStateEntry(var); } - if (etwEnabled) RunspaceEventSource.Log.LoadVariablesStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadVariablesStop(); + } } private void Bind_SetEnvironment() { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.LoadEnvironmentVariablesStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadEnvironmentVariablesStart(); + } foreach (SessionStateVariableEntry var in EnvironmentVariables) { Environment.SetEnvironmentVariable(var.Name, var.Value.ToString()); } - if (etwEnabled) RunspaceEventSource.Log.LoadEnvironmentVariablesStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadEnvironmentVariablesStop(); + } } private void Bind_UpdateTypes(ExecutionContext context, bool updateOnly) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.UpdateTypeTableStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.UpdateTypeTableStart(); + } + this.UpdateTypes(context, updateOnly); - if (etwEnabled) RunspaceEventSource.Log.UpdateTypeTableStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.UpdateTypeTableStop(); + } } private void Bind_UpdateFormats(ExecutionContext context, bool updateOnly) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.UpdateFormatTableStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.UpdateFormatTableStart(); + } this.UpdateFormats(context, updateOnly); - if (etwEnabled) RunspaceEventSource.Log.UpdateFormatTableStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.UpdateFormatTableStop(); + } } private void Bind_LoadProviders(SessionStateInternal ss) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.LoadProvidersStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadProvidersStart(); + } // Add all of the providers to session state... foreach (SessionStateProviderEntry provider in Providers) { - if (etwEnabled) RunspaceEventSource.Log.LoadProviderStart(provider.Name); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadProviderStart(provider.Name); + } + ss.AddSessionStateEntry(provider); - if (etwEnabled) RunspaceEventSource.Log.LoadProviderStop(provider.Name); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadProviderStop(provider.Name); + } } - if (etwEnabled) RunspaceEventSource.Log.LoadProvidersStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadProvidersStop(); + } } private void Bind_BindCommands(PSModuleInfo module, bool noClobber, bool local, SessionStateInternal ss) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.LoadCommandsStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadCommandsStart(); + } foreach (SessionStateCommandEntry cmd in Commands) { - if (etwEnabled) RunspaceEventSource.Log.LoadCommandStart(cmd.Name); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadCommandStart(cmd.Name); + } SessionStateCmdletEntry ssce = cmd as SessionStateCmdletEntry; if (ssce != null) @@ -2324,12 +2371,14 @@ private void Bind_BindCommands(PSModuleInfo module, bool noClobber, bool local, ss.AddSessionStateEntry(ssfe); continue; } + SessionStateAliasEntry ssae = cmd as SessionStateAliasEntry; if (ssae != null) { ss.AddSessionStateEntry(ssae, StringLiterals.Local); continue; } + SessionStateApplicationEntry ssappe = cmd as SessionStateApplicationEntry; if (ssappe != null) { @@ -2337,8 +2386,10 @@ private void Bind_BindCommands(PSModuleInfo module, bool noClobber, bool local, { ss.AddSessionStateEntry(ssappe); } + continue; } + SessionStateScriptEntry ssse = cmd as SessionStateScriptEntry; if (ssse != null) { @@ -2346,26 +2397,41 @@ private void Bind_BindCommands(PSModuleInfo module, bool noClobber, bool local, { ss.AddSessionStateEntry(ssse); } + continue; } - if (etwEnabled) RunspaceEventSource.Log.LoadCommandStop(cmd.Name); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadCommandStop(cmd.Name); + } } - if (etwEnabled) RunspaceEventSource.Log.LoadCommandsStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadCommandsStop(); + } } private void Bind_LoadAssemblies(ExecutionContext context) { bool etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.LoadAssembliesStart(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadAssembliesStart(); + } // 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) { @@ -2381,7 +2447,8 @@ private void Bind_LoadAssemblies(ExecutionContext context) // throw the exception instead of writing it out... if ((!string.IsNullOrEmpty(context.ModuleBeingProcessed) && Path.GetExtension(context.ModuleBeingProcessed) - .Equals(StringLiterals.PowerShellDataFileExtension, + .Equals( + StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) || ThrowOnRunspaceOpenError) { @@ -2392,81 +2459,131 @@ private void Bind_LoadAssemblies(ExecutionContext context) context.ReportEngineStartupError(error.Message); } } - if (etwEnabled) RunspaceEventSource.Log.LoadAssemblyStop(ssae.Name, ssae.FileName); + + if (etwEnabled) + { + RunspaceEventSource.Log.LoadAssemblyStop(ssae.Name, ssae.FileName); + } } - if (etwEnabled) RunspaceEventSource.Log.LoadAssembliesStop(); + if (etwEnabled) + { + RunspaceEventSource.Log.LoadAssembliesStop(); + } } internal Exception BindRunspace(Runspace initializedRunspace, PSTraceSource runspaceInitTracer) { - // Get initial list of public commands in session. - HashSet publicCommands = new HashSet(); - foreach (CommandInfo sessionCommand in initializedRunspace.ExecutionContext.SessionState.InvokeCommand.GetCommands( - "*", CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, true)) + // Get the initial list of public commands from session in a lazy way, so that we can defer + // the work until it's actually needed. + // + // We could use Lazy<> with an initializer for the same purpose, but we can save allocations + // by using the local function. It avoids allocating the delegate, and it's more efficient on + // capturing variables from the enclosing scope by using a struct. + HashSet publicCommands = null; + HashSet GetPublicCommands() { - if (sessionCommand.Visibility == SessionStateEntryVisibility.Public) + if (publicCommands != null) + { + return publicCommands; + } + + publicCommands = new HashSet(); + foreach (CommandInfo sessionCommand in initializedRunspace.ExecutionContext.SessionState.InvokeCommand.GetCommands( + name: "*", + CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, + nameIsPattern: true)) { - publicCommands.Add(sessionCommand); + if (sessionCommand.Visibility == SessionStateEntryVisibility.Public) + { + publicCommands.Add(sessionCommand); + } } + + return publicCommands; } - // If a user has any module with the same name as that of the core module( or nested module inside the core module) - // in his module path, then that will get loaded instead of the actual nested module (from the GAC - in our case) - // Hence, searching only from the system module path while loading the core modules - ProcessImportModule(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetPSHomeModulePath(), publicCommands); + var unresolvedCmdsToExpose = new HashSet(this.UnresolvedCommandsToExpose, StringComparer.OrdinalIgnoreCase); + if (CoreModulesToImport.Count > 0 || unresolvedCmdsToExpose.Count > 0) + { + // If a user has any module with the same name as that of the core module( or nested module inside the core module) + // in his module path, then that will get loaded instead of the actual nested module (from the GAC - in our case) + // Hence, searching only from the system module path while loading the core modules + ProcessModulesToImport(initializedRunspace, CoreModulesToImport, ModuleIntrinsics.GetPSHomeModulePath(), GetPublicCommands(), unresolvedCmdsToExpose); + } // Win8:328748 - functions defined in global scope end up in a module // Since we import the core modules, EngineSessionState's module is set to the last imported module. So, if a function is defined in global scope, it ends up in that module. // Setting the module to null fixes that. initializedRunspace.ExecutionContext.EngineSessionState.Module = null; - // Set the SessionStateDrive here since we have all the provider information at this point - SetSessionStateDrive(initializedRunspace.ExecutionContext, true); - - Exception moduleImportException = ProcessImportModule(initializedRunspace, ModuleSpecificationsToImport, "", publicCommands); - if (moduleImportException != null) + if (ModuleSpecificationsToImport.Count > 0 || unresolvedCmdsToExpose.Count > 0) { - runspaceInitTracer.WriteLine( - "Runspace open failed while loading module: First error {1}", moduleImportException); - return moduleImportException; + Exception moduleImportException = ProcessModulesToImport(initializedRunspace, ModuleSpecificationsToImport, string.Empty, GetPublicCommands(), unresolvedCmdsToExpose); + if (moduleImportException != null) + { + runspaceInitTracer.WriteLine( + "Runspace open failed while loading module: First error {1}", + moduleImportException); + return moduleImportException; + } } // If we still have unresolved commands after importing specified modules, then try finding associated module for // each unresolved command and import that module. - string[] foundModuleList = GetModulesForUnResolvedCommands(UnresolvedCommandsToExpose, initializedRunspace.ExecutionContext); - if (foundModuleList.Length > 0) + if (unresolvedCmdsToExpose.Count > 0) { - ProcessImportModule(initializedRunspace, foundModuleList, "", publicCommands); + string[] foundModuleList = GetModulesForUnResolvedCommands(unresolvedCmdsToExpose, initializedRunspace.ExecutionContext); + if (foundModuleList.Length > 0) + { + ProcessModulesToImport(initializedRunspace, foundModuleList, string.Empty, GetPublicCommands(), unresolvedCmdsToExpose); + } } - ProcessDynamicVariables(initializedRunspace); - ProcessCommandModifications(initializedRunspace); - - // Process User: drive - Exception userDriveException = ProcessUserDrive(initializedRunspace); - if (userDriveException != null) + // Process dynamic variables if any are defined. + if (DynamicVariablesToDefine.Count > 0) { - runspaceInitTracer.WriteLine( - "Runspace open failed while processing user drive with error {1}", userDriveException); - - Exception result = PSTraceSource.NewInvalidOperationException(userDriveException, RemotingErrorIdStrings.UserDriveProcessingThrewTerminatingError, userDriveException.Message); - return result; + ProcessDynamicVariables(initializedRunspace); } - // Process startup scripts - Exception startupScriptException = ProcessStartupScripts(initializedRunspace); - if (startupScriptException != null) + // Process command modifications if any are defined. + if (CommandModifications.Count > 0) { - runspaceInitTracer.WriteLine( - "Runspace open failed while running startup script: First error {1}", startupScriptException); + ProcessCommandModifications(initializedRunspace); + } - Exception result = PSTraceSource.NewInvalidOperationException(startupScriptException, RemotingErrorIdStrings.StartupScriptThrewTerminatingError, startupScriptException.Message); - return result; + // Process the 'User:' drive if 'UserDriveEnabled' is set. + if (UserDriveEnabled) + { + Exception userDriveException = ProcessUserDrive(initializedRunspace); + if (userDriveException != null) + { + runspaceInitTracer.WriteLine( + "Runspace open failed while processing user drive with error {1}", + userDriveException); + + Exception result = PSTraceSource.NewInvalidOperationException(userDriveException, RemotingErrorIdStrings.UserDriveProcessingThrewTerminatingError, userDriveException.Message); + return result; + } + } + + // Process startup scripts + if (StartupScripts.Count > 0) + { + Exception startupScriptException = ProcessStartupScripts(initializedRunspace); + if (startupScriptException != null) + { + runspaceInitTracer.WriteLine( + "Runspace open failed while running startup script: First error {1}", + startupScriptException); + + Exception result = PSTraceSource.NewInvalidOperationException(startupScriptException, RemotingErrorIdStrings.StartupScriptThrewTerminatingError, startupScriptException.Message); + return result; + } } // Start transcribing - if (!String.IsNullOrEmpty(TranscriptDirectory)) + if (!string.IsNullOrEmpty(TranscriptDirectory)) { using (PowerShell psToInvoke = PowerShell.Create()) { @@ -2483,7 +2600,7 @@ internal Exception BindRunspace(Runspace initializedRunspace, PSTraceSource runs 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); @@ -2520,7 +2637,7 @@ private string[] GetModulesForUnResolvedCommands(IEnumerable unresolvedC foreach (var unresolvedCommand in commandsToResolve) { // Use the analysis cache to find the first module containing the unresolved command. - foreach (string modulePath in ModuleUtils.GetDefaultAvailableModuleFiles(true, true, context)) + foreach (string modulePath in ModuleUtils.GetDefaultAvailableModuleFiles(isForAutoDiscovery: true, context)) { string expandedModulePath = IO.Path.GetFullPath(modulePath); var exportedCommands = AnalysisCache.GetExportedCommands(expandedModulePath, false, context); @@ -2601,7 +2718,7 @@ private void ProcessCommandModifications(Runspace initializedRunspace) ProcessCommandModification(commandModification, metadata, unprocessedCommandModification); } - string proxyBody = ProxyCommand.Create(metadata, "", false); + string proxyBody = ProxyCommand.Create(metadata, string.Empty, false); ScriptBlock proxyScriptBlock = ScriptBlock.Create(proxyBody); proxyScriptBlock.LanguageMode = PSLanguageMode.FullLanguage; @@ -2611,11 +2728,11 @@ private void ProcessCommandModifications(Runspace initializedRunspace) } /// - /// Process a command modification for a specific parameter + /// Process a command modification for a specific parameter. /// - /// The hashtable of command modifications for this command - /// The metadata for the command being processed - /// The parameter being modified + /// The hashtable of command modifications for this command. + /// The metadata for the command being processed. + /// The parameter being modified. private static void ProcessCommandModification(Hashtable commandModification, CommandMetadata metadata, string parameterName) { // If the metadata doesn't actually contain the parameter, then we need to create one. @@ -2639,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; @@ -2656,7 +2773,7 @@ private Exception ProcessDynamicVariables(Runspace initializedRunspace) string name = variable["Name"].ToString(); ScriptBlock sb = variable["Value"] as ScriptBlock; - if (!String.IsNullOrEmpty(name) && (sb != null)) + if (!string.IsNullOrEmpty(name) && (sb != null)) { sb.SessionStateInternal = initializedRunspace.ExecutionContext.EngineSessionState; @@ -2681,8 +2798,6 @@ private Exception ProcessDynamicVariables(Runspace initializedRunspace) private Exception ProcessUserDrive(Runspace initializedRunspace) { - if (!UserDriveEnabled) { return null; } - Exception ex = null; try { @@ -2693,18 +2808,11 @@ private Exception ProcessUserDrive(Runspace initializedRunspace) } // Create the User drive path directory in current user local appdata location: - // SystemDrive\Users\[user]\AppData\Local\Microsoft\Windows\PowerShell\DriveRoots\[UserName] + // SystemDrive\Users\[user]\AppData\Local\Microsoft\PowerShell\DriveRoots\[UserName] // Or for virtual accounts - // WinDir\System32\Microsoft\Windows\PowerShell\DriveRoots\[UserName] + // WinDir\System32\Microsoft\PowerShell\DriveRoots\[UserName] string directoryName = MakeUserNamePath(); -#if UNIX - string userDrivePath = Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "DriveRoots", directoryName); -#else - string userDrivePath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - @"Microsoft\Windows\PowerShell\DriveRoots", - directoryName); -#endif + string userDrivePath = Path.Combine(Platform.CacheDirectory, "DriveRoots", directoryName); // Create directory if it doesn't exist. if (!System.IO.Directory.Exists(userDrivePath)) @@ -2740,25 +2848,24 @@ private string MakeUserNamePath() { // Use the user name passed to initial session state if available, or // otherwise use the current user name. - var userName = (!string.IsNullOrEmpty(this.UserDriveUserName)) ? - this.UserDriveUserName : + var userName = !string.IsNullOrEmpty(this.UserDriveUserName) + ? this.UserDriveUserName // domain\user on Windows, just user on Unix #if UNIX - Platform.Unix.UserName + : Environment.UserName; #else - System.Security.Principal.WindowsIdentity.GetCurrent().Name + : Environment.UserDomainName + "_" + Environment.UserName; #endif - ; // 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); } - return userName.Replace("\\", "_"); + return userName; } private Exception ProcessStartupScripts(Runspace initializedRunspace) @@ -2810,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; @@ -2847,27 +2954,45 @@ private Exception ProcessPowerShellCommand(PowerShell psToInvoke, Runspace initi return null; } - private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initializedRunspace, IEnumerable moduleList, string path, HashSet publicCommands) + private RunspaceOpenModuleLoadException ProcessModulesToImport( + Runspace initializedRunspace, + IEnumerable moduleList, + string path, + HashSet publicCommands, + HashSet unresolvedCmdsToExpose) { RunspaceOpenModuleLoadException exceptionToReturn = null; + List processedModules = new List(); foreach (object module in moduleList) { string moduleName = module as string; - if (null != moduleName) + if (moduleName != null) { - exceptionToReturn = ProcessImportModule(initializedRunspace, moduleName, null, path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleName, + moduleInfoToLoad: null, + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { ModuleSpecification moduleSpecification = module as ModuleSpecification; - if (null != moduleSpecification) + if (moduleSpecification != null) { if ((moduleSpecification.RequiredVersion == null) && (moduleSpecification.Version == null) && (moduleSpecification.MaximumVersion == null) && (moduleSpecification.Guid == null)) { // 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 = ProcessImportModule(initializedRunspace, moduleSpecification.Name, null, path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleSpecification.Name, + moduleInfoToLoad: null, + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { @@ -2875,7 +3000,13 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized if (moduleInfos != null && moduleInfos.Count > 0) { - exceptionToReturn = ProcessImportModule(initializedRunspace, moduleSpecification.Name, moduleInfos[0], path, publicCommands); + exceptionToReturn = ProcessOneModule( + initializedRunspace: initializedRunspace, + name: moduleSpecification.Name, + moduleInfoToLoad: moduleInfos[0], + path: path, + publicCommands: publicCommands, + processedModules: processedModules); } else { @@ -2898,7 +3029,8 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized version = moduleSpecification.MaximumVersion; } - string message = StringUtil.Format(global::Modules.RequiredModuleNotFoundWrongGuidVersion, + string message = StringUtil.Format( + global::Modules.RequiredModuleNotFoundWrongGuidVersion, moduleSpecification.Name, moduleSpecification.Guid, version); @@ -2917,22 +3049,29 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized if (exceptionToReturn == null) { // Now go through the list of commands not yet resolved to ensure they are public if requested - foreach (string unresolvedCommand in UnresolvedCommandsToExpose.ToArray()) + foreach (string unresolvedCommand in unresolvedCmdsToExpose.ToArray()) { string moduleName; 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) { found = true; } + if (!found) + { + found = true; + } + try { // Special case for wild card lookups. // "Import-Module" or "ipmo" cannot be visible when exposing commands via VisibleCmdlets, etc. if ((cmd.Name.Equals("Import-Module", StringComparison.OrdinalIgnoreCase) && - (!string.IsNullOrEmpty(cmd.ModuleName) && cmd.ModuleName.Equals("Microsoft.PowerShell.Core", StringComparison.OrdinalIgnoreCase)) - ) || + (!string.IsNullOrEmpty(cmd.ModuleName) && cmd.ModuleName.Equals("Microsoft.PowerShell.Core", StringComparison.OrdinalIgnoreCase))) || cmd.Name.Equals("ipmo", StringComparison.OrdinalIgnoreCase) ) { @@ -2944,13 +3083,15 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized publicCommands.Add(cmd); } } - // Some CommandInfo derivations throw on the Visibility setter. - catch (PSNotImplementedException) { } + catch (PSNotImplementedException) + { + // Some CommandInfo derivations throw on the Visibility setter. + } } if (found && !WildcardPattern.ContainsWildcardCharacters(commandToMakeVisible)) { - UnresolvedCommandsToExpose.Remove(unresolvedCommand); + unresolvedCmdsToExpose.Remove(unresolvedCommand); } } } @@ -2969,14 +3110,16 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized /// /// /// + /// /// - 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) ? + var searchOptions = isWildCardPattern ? SearchResolutionOptions.CommandNameIsPattern | SearchResolutionOptions.ResolveFunctionPatterns | SearchResolutionOptions.SearchAllScopes : SearchResolutionOptions.ResolveFunctionPatterns | SearchResolutionOptions.SearchAllScopes; @@ -2987,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)) @@ -2995,11 +3142,18 @@ private IEnumerable LookupCommands( continue; } - if (!found) { found = true; } + if (!found) + { + found = true; + } + yield return commandInfo; // Return first match unless a wild card pattern is submitted. - if (!isWildCardPattern) { break; } + if (!isWildCardPattern) + { + break; + } } if (found || (cmdOrigin == CommandOrigin.Internal)) @@ -3010,19 +3164,49 @@ 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, + /// If is null, import module using . Otherwise, /// import module using /// - private RunspaceOpenModuleLoadException ProcessImportModule(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()) { CommandInfo c = new CmdletInfo("Import-Module", typeof(ImportModuleCommand), null, null, initializedRunspace.ExecutionContext); Command cmd = new Command(c); - if (null != moduleInfoToLoad) + if (moduleInfoToLoad != null) { cmd.Parameters.Add("ModuleInfo", moduleInfoToLoad); name = moduleInfoToLoad.Name; @@ -3037,6 +3221,7 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized { name = Path.Combine(path, name); } + cmd.Parameters.Add("Name", name); } @@ -3052,6 +3237,11 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized 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 @@ -3060,7 +3250,10 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized 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 { @@ -3071,7 +3264,9 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized if (this.DefaultCommandVisibility != SessionStateEntryVisibility.Public) { foreach (CommandInfo importedCommand in initializedRunspace.ExecutionContext.SessionState.InvokeCommand.GetCommands( - "*", CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, true)) + name: "*", + CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, + true)) { try { @@ -3082,8 +3277,10 @@ private RunspaceOpenModuleLoadException ProcessImportModule(Runspace initialized importedCommand.Visibility = this.DefaultCommandVisibility; } } - // Some CommandInfo derivations throw on the Visibility setter. - catch (PSNotImplementedException) { } + catch (PSNotImplementedException) + { + // Some CommandInfo derivations throw on the Visibility setter. + } } } @@ -3120,7 +3317,7 @@ private RunspaceOpenModuleLoadException ValidateAndReturnRunspaceOpenModuleLoadE rome = new RunspaceOpenModuleLoadException(moduleName, mergedErrors); } - if (null != rome) + if (rome != null) { return rome; } @@ -3146,8 +3343,11 @@ internal void ResetRunspaceState(ExecutionContext context) // Add the built-in variables foreach (SessionStateVariableEntry e in InitialSessionState.BuiltInVariables) { - PSVariable v = new PSVariable(e.Name, e.Value, - e.Options, e.Attributes, e.Description) + PSVariable v = new PSVariable( + e.Name, + e.Value, + e.Options, e.Attributes, + e.Description) { Visibility = e.Visibility }; ss.GlobalScope.SetVariable(e.Name, v, false, true, ss, fastPath: true); } @@ -3157,8 +3357,12 @@ internal void ResetRunspaceState(ExecutionContext context) // Then re-initialize it with variables to session state... foreach (SessionStateVariableEntry e in Variables) { - PSVariable v = new PSVariable(e.Name, e.Value, - e.Options, e.Attributes, e.Description) + PSVariable v = new PSVariable( + e.Name, + e.Value, + e.Options, + e.Attributes, + e.Description) { Visibility = e.Visibility }; ss.GlobalScope.SetVariable(e.Name, v, false, true, ss, fastPath: true); } @@ -3237,14 +3441,14 @@ internal static void SetSessionStateDrive(ExecutionContext context, bool setLoca try { + providerContext.SuppressWildcardExpansion = true; context.EngineSessionState.SetLocation(Directory.GetCurrentDirectory(), providerContext); } catch (ItemNotFoundException) { // 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); } } @@ -3261,103 +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 @@ -3374,6 +3481,7 @@ internal static void RemoveTypesAndFormats(ExecutionContext context, IList - /// Update the type metadata loaded into this runspace + /// Update the type metadata loaded into this runspace. /// - /// The execution context for the runspace to update - /// if true, re-initialize the metadata collection... + /// The execution context for the runspace to update. + /// If true, re-initialize the metadata collection... internal void UpdateTypes(ExecutionContext context, bool updateOnly) { if (Types.Count == 1) @@ -3442,12 +3550,7 @@ internal void UpdateTypes(ExecutionContext context, bool updateOnly) context.TypeTable = typeTable; Types.Clear(); - - // A TypeTable contains types info along with type file references used to create the types info, - // which is redundant information. When resused in a runspace the ISS unpacks the file types again - // resulting in duplicate types and duplication errors when processed. - // So use this special Add method to filter all types files found in the TypeTable. - Types.AddTypeTableTypesInfo(typeTable.typesInfo); + Types.Add(typeTable.typesInfo); return; } @@ -3461,24 +3564,26 @@ internal void UpdateTypes(ExecutionContext context, bool updateOnly) ConcurrentBag errors = new ConcurrentBag(); // Use at most 3 locks (we don't expect contention on that many cores anyways, // and typically we'll be processing just 2 or 3 files anyway, hence capacity=3. - ConcurrentDictionary filesProcessed - = new ConcurrentDictionary(/*concurrencyLevel*/3, /*capacity*/3, StringComparer.OrdinalIgnoreCase); - Parallel.ForEach(Types, sste => - // foreach (var sste in Types) - { + ConcurrentDictionary filesProcessed = new ConcurrentDictionary( + concurrencyLevel: 3, + capacity: 3, + StringComparer.OrdinalIgnoreCase); + Parallel.ForEach( + Types, + sste => + { + // foreach (var sste in Types) if (sste.FileName != null) { if (filesProcessed.TryAdd(sste.FileName, null)) { - string moduleName = ""; - if (sste.PSSnapIn != null && !String.IsNullOrEmpty(sste.PSSnapIn.Name)) + string moduleName = string.Empty; + if (sste.PSSnapIn != null && !string.IsNullOrEmpty(sste.PSSnapIn.Name)) { 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) @@ -3492,7 +3597,6 @@ ConcurrentDictionary filesProcessed context.TypeTable.Update(sste.TypeData, errors, sste.IsRemove); } }); - // } context.TypeTable.ClearConsolidatedMembers(); @@ -3505,7 +3609,7 @@ ConcurrentDictionary filesProcessed } } - if (errors.Count > 0) + if (!errors.IsEmpty) { var allErrors = new StringBuilder(); allErrors.Append('\n'); @@ -3540,10 +3644,10 @@ ConcurrentDictionary filesProcessed } /// - /// Update the formatting information for a runspace + /// Update the formatting information for a runspace. /// - /// The execution context for the runspace to be updated - /// True if we only want to add stuff, false if we want to reinitialize + /// The execution context for the runspace to be updated. + /// True if we only want to add stuff, false if we want to reinitialize. internal void UpdateFormats(ExecutionContext context, bool update) { if (DisableFormatUpdates || this.Formats.Count == 0) @@ -3575,6 +3679,7 @@ internal void UpdateFormats(ExecutionContext context, bool update) { name = snapin.Name; } + if (ssfe.Formattable != null) { if (formatsToLoad.Count == 1) @@ -3613,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) { @@ -3651,16 +3756,17 @@ private static void ThrowTypeOrFormatErrors(string resourceString, string errorM } /// - /// Need to have SnapIn support till we move to modules + /// Need to have SnapIn support till we move to modules. /// /// /// /// + [Obsolete("Custom PSSnapIn is deprecated. Please use a module instead.", true)] public PSSnapInInfo ImportPSSnapIn(string name, out PSSnapInException warning) { if (string.IsNullOrEmpty(name)) { - PSTraceSource.NewArgumentNullException("name"); + PSTraceSource.NewArgumentException(nameof(name)); } // Check whether the mshsnapin is present in the registry. @@ -3668,45 +3774,35 @@ 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); - throw PSTraceSource.NewArgumentException("mshSnapInID", - ConsoleInfoErrorStrings.AddPSSnapInBadMonadVersion, - newPSSnapIn.PSVersion.ToString(), - "2.0"); + throw PSTraceSource.NewArgumentException( + "mshSnapInID", + ConsoleInfoErrorStrings.AddPSSnapInBadMonadVersion, + newPSSnapIn.PSVersion.ToString(), + "2.0"); } // Now actually load the snapin... PSSnapInInfo snapin = ImportPSSnapIn(newPSSnapIn, out warning); - if (null != snapin) - { - 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 pse) - { - throw pse; - } + 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; @@ -3739,20 +3835,6 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce Dictionary> aliases = null; Dictionary providers = null; - if (psSnapInInfo == null) - { - ArgumentNullException e = new ArgumentNullException("psSnapInInfo"); - throw e; - } - -#if !CORECLR // CustomPSSnapIn Not Supported On CSS. - if (!String.IsNullOrEmpty(psSnapInInfo.CustomPSSnapInType)) - { - LoadCustomPSSnapIn(psSnapInInfo); - warning = null; - return psSnapInInfo; - } -#endif Assembly assembly = null; string helpFile = null; @@ -3760,18 +3842,18 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce { s_PSSnapInTracer.WriteLine("Loading assembly for psSnapIn {0}", psSnapInInfo.Name); - assembly = PSSnapInHelpers.LoadPSSnapInAssembly(psSnapInInfo, out cmdlets, out providers); + assembly = PSSnapInHelpers.LoadPSSnapInAssembly(psSnapInInfo); if (assembly == null) { s_PSSnapInTracer.TraceError("Loading assembly for psSnapIn {0} failed", psSnapInInfo.Name); warning = null; - return null; //BUGBUG - should add something to the warnings list here instead of quitting... + return null; // BUGBUG - should add something to the warnings list here instead of quitting... } s_PSSnapInTracer.WriteLine("Loading assembly for psSnapIn {0} succeeded", psSnapInInfo.Name); - PSSnapInHelpers.AnalyzePSSnapInAssembly(assembly, psSnapInInfo.Name, psSnapInInfo, null, true, out cmdlets, out aliases, out providers, out helpFile); + PSSnapInHelpers.AnalyzePSSnapInAssembly(assembly, psSnapInInfo.Name, psSnapInInfo, moduleInfo: null, out cmdlets, out aliases, out providers, out helpFile); } // We skip checking if the file exists when it's in $PSHOME because of magic @@ -3809,19 +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) { @@ -3853,6 +3925,7 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce this.Providers.Add(provider); } } + warning = null; // Add help file information for built-in functions @@ -3870,166 +3943,20 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce } } } - return psSnapInInfo; - } - - internal List 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 (loadedSnapins == null) - { - loadedSnapins = new List(); - } - loadedSnapins.Add(importedSnapin); - } - return loadedSnapins; - } - -#if !CORECLR // CustomPSSnapIn Not Supported On CSS. - /// - /// This is a "proxy" snapin that loads a subset of cmdlets from another snapin... - /// - /// - /// CustomPSSnapIn derives from System.Configuration.Install, which is not in CoreCLR. - /// So CustomPSSnapIn is not supported on CSS. - /// - /// The snapin to examine. - private void LoadCustomPSSnapIn(PSSnapInInfo psSnapInInfo) - { - if (psSnapInInfo == null) - return; - - if (String.IsNullOrEmpty(psSnapInInfo.CustomPSSnapInType)) - { - return; - } - - Dictionary cmdlets = null; - Dictionary providers = null; - Assembly assembly = null; - - s_PSSnapInTracer.WriteLine("Loading assembly for mshsnapin {0}", psSnapInInfo.Name); - - assembly = PSSnapInHelpers.LoadPSSnapInAssembly(psSnapInInfo, out cmdlets, out providers); - - if (assembly == null) - { - s_PSSnapInTracer.TraceError("Loading assembly for mshsnapin {0} failed", psSnapInInfo.Name); - return; - } - - CustomPSSnapIn customPSSnapIn = null; - try - { - Type type = assembly.GetType(psSnapInInfo.CustomPSSnapInType, true); - - if (type != null) - { - customPSSnapIn = (CustomPSSnapIn)assembly.CreateInstance(psSnapInInfo.CustomPSSnapInType); - } - - s_PSSnapInTracer.WriteLine("Loading assembly for mshsnapin {0} succeeded", psSnapInInfo.Name); - } - catch (TypeLoadException tle) - { - throw new PSSnapInException(psSnapInInfo.Name, tle.Message); - } - catch (ArgumentException ae) - { - throw new PSSnapInException(psSnapInInfo.Name, ae.Message); - } - catch (MissingMethodException mme) - { - throw new PSSnapInException(psSnapInInfo.Name, mme.Message); - } - catch (InvalidCastException ice) - { - throw new PSSnapInException(psSnapInInfo.Name, ice.Message); - } - catch (TargetInvocationException tie) - { - if (tie.InnerException != null) - { - throw new PSSnapInException(psSnapInInfo.Name, tie.InnerException.Message); - } - - throw new PSSnapInException(psSnapInInfo.Name, tie.Message); - } - MergeCustomPSSnapIn(psSnapInInfo, customPSSnapIn); + ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo); + return psSnapInInfo; } - private void MergeCustomPSSnapIn(PSSnapInInfo psSnapInInfo, CustomPSSnapIn customPSSnapIn) + internal PSSnapInInfo GetPSSnapIn(string psSnapinName) { - if (psSnapInInfo == null || customPSSnapIn == null) - return; - - s_PSSnapInTracer.WriteLine("Merging configuration from custom mshsnapin {0}", psSnapInInfo.Name); - - if (customPSSnapIn.Cmdlets != null) - { - foreach (CmdletConfigurationEntry entry in customPSSnapIn.Cmdlets) - { - SessionStateCmdletEntry cmdlet = new SessionStateCmdletEntry(entry.Name, entry.ImplementingType, entry.HelpFileName); - cmdlet.SetPSSnapIn(psSnapInInfo); - this.Commands.Add(cmdlet); - } - } - - if (customPSSnapIn.Providers != null) - { - foreach (ProviderConfigurationEntry entry in customPSSnapIn.Providers) - { - SessionStateProviderEntry provider = new SessionStateProviderEntry(entry.Name, entry.ImplementingType, entry.HelpFileName); - provider.SetPSSnapIn(psSnapInInfo); - this.Providers.Add(provider); - } - } - - if (customPSSnapIn.Types != null) - { - foreach (TypeConfigurationEntry entry in customPSSnapIn.Types) - { - string path = Path.Combine(psSnapInInfo.ApplicationBase, entry.FileName); - - SessionStateTypeEntry typeEntry = new SessionStateTypeEntry(path); - typeEntry.SetPSSnapIn(psSnapInInfo); - this.Types.Add(typeEntry); - } - } - - if (customPSSnapIn.Formats != null) + if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin)) { - foreach (FormatConfigurationEntry entry in customPSSnapIn.Formats) - { - string path = Path.Combine(psSnapInInfo.ApplicationBase, entry.FileName); - - SessionStateFormatEntry formatEntry = new SessionStateFormatEntry(path); - formatEntry.SetPSSnapIn(psSnapInInfo); - this.Formats.Add(formatEntry); - } + return importedSnapin; } - SessionStateAssemblyEntry assemblyEntry = new SessionStateAssemblyEntry(psSnapInInfo.AssemblyName, psSnapInInfo.AbsoluteModulePath); - - this.Assemblies.Add(assemblyEntry); + return null; } -#endif [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] internal static Assembly LoadAssemblyFromFile(string fileName) @@ -4050,27 +3977,18 @@ internal static Assembly LoadAssemblyFromFile(string fileName) internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) { - if (assembly == null) - { - ArgumentNullException e = new ArgumentNullException("assembly"); - throw e; - } - Dictionary cmdlets = null; - Dictionary> aliases = null; - Dictionary providers = null; + ArgumentNullException.ThrowIfNull(assembly); string assemblyPath = assembly.Location; - string throwAwayHelpFile = null; - PSSnapInHelpers.AnalyzePSSnapInAssembly(assembly, assemblyPath, null, module, true, out cmdlets, out aliases, out providers, out throwAwayHelpFile); - - // 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,16 +4018,14 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) } } - // // Now define a bunch of functions that describe the rest of the default session state... - // internal const string FormatEnumerationLimit = "FormatEnumerationLimit"; internal const int DefaultFormatEnumerationLimit = 4; /// /// 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. @@ -4121,12 +4037,14 @@ 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', Mandatory = $true, Position = 1)] - [int] $cursorColumn, + [Parameter(ParameterSetName = 'ScriptInputSet', Position = 1)] + [int] $cursorColumn = $inputScript.Length, [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)] [System.Management.Automation.Language.Ast] $ast, @@ -4160,16 +4078,16 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) <#options#> $options) } } - "; +"; /// - /// This is the default function to use for clear-host. On Windows it rewrites the - /// host, and on Linux, it delegates to the native binary, 'clear'. + /// This is the default function to use for clear-host. /// internal static string GetClearHostFunctionText() { if (Platform.IsWindows) { + // use $RawUI so this works over remoting where there isn't a physical console return @" $RawUI = $Host.UI.RawUI $RawUI.CursorPosition = @{X=0;Y=0} @@ -4177,7 +4095,7 @@ internal static string GetClearHostFunctionText() @{Top = -1; Bottom = -1; Right = -1; Left = -1}, @{Character = ' '; ForegroundColor = $rawui.ForegroundColor; BackgroundColor = $rawui.BackgroundColor}) # .Link -# https://go.microsoft.com/fwlink/?LinkID=225747 +# https://go.microsoft.com/fwlink/?LinkID=2096480 # .ExternalHelp System.Management.Automation.dll-help.xml "; } @@ -4185,24 +4103,26 @@ internal static string GetClearHostFunctionText() { // Porting note: non-Windows platforms use `clear` return @" -& (Get-Command -CommandType Application clear | Select-Object -First 1).Definition +[Console]::Write(( + & (Get-Command -CommandType Application clear | Select-Object -First 1).Definition +)) # .Link -# https://go.microsoft.com/fwlink/?LinkID=225747 +# https://go.microsoft.com/fwlink/?LinkID=2096480 # .ExternalHelp System.Management.Automation.dll-help.xml "; } } - /// - /// 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 @@ -4216,19 +4136,10 @@ .FORWARDHELPCATEGORY Cmdlet [string] ${Path}, - [ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','Workflow','DscResource','Class','Configuration')] + [ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','DscResource','Class','Configuration')] [string[]] ${Category}, - [string[]] - ${Component}, - - [string[]] - ${Functionality}, - - [string[]] - ${Role}, - [Parameter(ParameterSetName='DetailedView', Mandatory=$true)] [switch] ${Detailed}, @@ -4242,9 +4153,18 @@ .FORWARDHELPCATEGORY Cmdlet ${Examples}, [Parameter(ParameterSetName='Parameters', Mandatory=$true)] - [string] + [string[]] ${Parameter}, + [string[]] + ${Component}, + + [string[]] + ${Functionality}, + + [string[]] + ${Role}, + [Parameter(ParameterSetName='Online', Mandatory=$true)] [switch] ${Online}, @@ -4258,23 +4178,225 @@ .FORWARDHELPCATEGORY Cmdlet $PSBoundParameters['Full'] = $true } - # Set the outputencoding to Console::OutputEncoding. More.com doesn't work well with Unicode. - $outputEncoding=[System.Console]::OutputEncoding + # Nano needs to use Unicode, but Windows and Linux need the default + $OutputEncoding = if ([System.Management.Automation.Platform]::IsNanoServer -or [System.Management.Automation.Platform]::IsIoT) { + [System.Text.Encoding]::Unicode + } else { + [System.Console]::OutputEncoding + } $help = Get-Help @PSBoundParameters - # If a list of help is returned, don't pipe to more - if (($help | Select-Object -First 1).PSTypeNames -Contains 'HelpInfoShort') + # 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 } - else + elseif ($help -ne $null) { - $help | more - } -"; + # By default use more on Windows and less on Linux. + $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 { + # 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. + 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 { + # The pager command is a PowerShell function, script or alias, so pipe directly into it. + $help | & $pagerCommand $pagerArgs + } + } +"; + + /// + /// This is the default function to use for man/help. It uses + /// splatting to pass in the parameters. + /// +#if !UNIX + internal static string GetHelpPagingFunctionText() + { + // We used to generate the text for this function so you could add a parameter + // to Get-Help and not worry about adding it here. That was a little slow at + // startup, so it's hard coded, with a test to make sure the parameters match. + return WindowsHelpFunctionText; + } + +#else + internal static string GetHelpPagingFunctionText() + { + // We used to generate the text for this function so you could add a parameter + // to Get-Help and not worry about adding it here. That was a little slow at + // startup, so it's hard coded, with a test to make sure the parameters match. + // This version removes the -ShowWindow parameter since it is not supported on Linux. + return UnixHelpFunctionText; + } +#endif + internal static string GetMkdirFunctionText() { return @" @@ -4313,36 +4435,19 @@ .FORWARDHELPCATEGORY Cmdlet ) begin { + $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-Item', [System.Management.Automation.CommandTypes]::Cmdlet) + $scriptCmd = {& $wrappedCmd -Type Directory @PSBoundParameters } - try { - $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-Item', [System.Management.Automation.CommandTypes]::Cmdlet) - $scriptCmd = {& $wrappedCmd -Type Directory @PSBoundParameters } - $steppablePipeline = $scriptCmd.GetSteppablePipeline() - $steppablePipeline.Begin($PSCmdlet) - } catch { - throw - } - + $steppablePipeline = $scriptCmd.GetSteppablePipeline() + $steppablePipeline.Begin($PSCmdlet) } process { - - try { - $steppablePipeline.Process($_) - } catch { - throw - } - + $steppablePipeline.Process($_) } end { - - try { - $steppablePipeline.End() - } catch { - throw - } - + $steppablePipeline.End() } "; @@ -4361,35 +4466,21 @@ internal static string GetOSTFunctionText() [psobject] ${InputObject}) -begin -{ - try { - $PSBoundParameters['Stream'] = $true - $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String',[System.Management.Automation.CommandTypes]::Cmdlet) - $scriptCmd = {& $wrappedCmd @PSBoundParameters } - $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) - $steppablePipeline.Begin($PSCmdlet) - } catch { - throw - } +begin { + $PSBoundParameters['Stream'] = $true + $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String',[System.Management.Automation.CommandTypes]::Cmdlet) + $scriptCmd = {& $wrappedCmd @PSBoundParameters } + + $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) + $steppablePipeline.Begin($PSCmdlet) } -process -{ - try { - $steppablePipeline.Process($_) - } catch { - throw - } +process { + $steppablePipeline.Process($_) } -end -{ - try { - $steppablePipeline.End() - } catch { - throw - } +end { + $steppablePipeline.End() } <# .ForwardHelpTargetName Out-String @@ -4398,168 +4489,210 @@ .ForwardHelpCategory Cmdlet "; } - internal const ActionPreference defaultDebugPreference = ActionPreference.SilentlyContinue; - internal const ActionPreference defaultErrorActionPreference = ActionPreference.Continue; - internal const ActionPreference defaultProgressPreference = ActionPreference.Continue; - internal const ActionPreference defaultVerbosePreference = ActionPreference.SilentlyContinue; - internal const ActionPreference defaultWarningPreference = ActionPreference.Continue; - internal const ActionPreference defaultInformationPreference = ActionPreference.SilentlyContinue; - internal const bool defaultWhatIfPreference = false; - internal const ConfirmImpact defaultConfirmPreference = ConfirmImpact.High; - - internal static 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, - "NormalView", - RunspaceInit.ErrorViewDescription - ), - 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 - }; + internal const ActionPreference DefaultDebugPreference = ActionPreference.SilentlyContinue; + internal const ActionPreference DefaultErrorActionPreference = ActionPreference.Continue; + internal const ActionPreference DefaultProgressPreference = ActionPreference.Continue; + internal const ActionPreference DefaultVerbosePreference = ActionPreference.SilentlyContinue; + internal const ActionPreference DefaultWarningPreference = ActionPreference.Continue; + internal const ActionPreference DefaultInformationPreference = ActionPreference.SilentlyContinue; + + internal const ErrorView DefaultErrorView = ErrorView.ConciseView; + internal const bool DefaultWhatIfPreference = false; + internal const ConfirmImpact DefaultConfirmPreference = ConfirmImpact.High; + + 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. /// @@ -4577,96 +4710,98 @@ internal static SessionStateAliasEntry[] BuiltInAliases const ScopedItemOptions ReadOnly_AllScope = ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope; const ScopedItemOptions ReadOnly = ScopedItemOptions.ReadOnly; - return new SessionStateAliasEntry[] { - new SessionStateAliasEntry("foreach", "ForEach-Object", "", ReadOnly_AllScope), - new SessionStateAliasEntry("%", "ForEach-Object", "", ReadOnly_AllScope), - new SessionStateAliasEntry("where", "Where-Object", "", ReadOnly_AllScope), - new SessionStateAliasEntry("?", "Where-Object", "", ReadOnly_AllScope), - new SessionStateAliasEntry("clc", "Clear-Content", "", ReadOnly), - new SessionStateAliasEntry("cli", "Clear-Item", "", ReadOnly), - new SessionStateAliasEntry("clp", "Clear-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("clv", "Clear-Variable", "", ReadOnly), - new SessionStateAliasEntry("cpi", "Copy-Item", "", ReadOnly), - new SessionStateAliasEntry("cvpa", "Convert-Path", "", ReadOnly), - new SessionStateAliasEntry("dbp", "Disable-PSBreakpoint", "", ReadOnly), - new SessionStateAliasEntry("ebp", "Enable-PSBreakpoint", "", ReadOnly), - new SessionStateAliasEntry("epal", "Export-Alias", "", ReadOnly), - new SessionStateAliasEntry("epcsv", "Export-Csv", "", ReadOnly), - new SessionStateAliasEntry("fl", "Format-List", "", ReadOnly), - new SessionStateAliasEntry("ft", "Format-Table", "", ReadOnly), - new SessionStateAliasEntry("fw", "Format-Wide", "", ReadOnly), - new SessionStateAliasEntry("gal", "Get-Alias", "", ReadOnly), - new SessionStateAliasEntry("gbp", "Get-PSBreakpoint", "", ReadOnly), - new SessionStateAliasEntry("gc", "Get-Content", "", ReadOnly), - new SessionStateAliasEntry("gci", "Get-ChildItem", "", ReadOnly), - new SessionStateAliasEntry("gcm", "Get-Command", "", ReadOnly), - new SessionStateAliasEntry("gdr", "Get-PSDrive", "", ReadOnly), - new SessionStateAliasEntry("gcs", "Get-PSCallStack", "", ReadOnly), - new SessionStateAliasEntry("ghy", "Get-History", "", ReadOnly), - new SessionStateAliasEntry("gi", "Get-Item", "", ReadOnly), - new SessionStateAliasEntry("gl", "Get-Location", "", ReadOnly), - new SessionStateAliasEntry("gm", "Get-Member", "", ReadOnly), - new SessionStateAliasEntry("gmo", "Get-Module", "", ReadOnly), - new SessionStateAliasEntry("gp", "Get-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("gpv", "Get-ItemPropertyValue", "",ReadOnly), - new SessionStateAliasEntry("gps", "Get-Process", "", ReadOnly), - new SessionStateAliasEntry("group", "Group-Object", "", ReadOnly), - new SessionStateAliasEntry("gu", "Get-Unique", "", ReadOnly), - new SessionStateAliasEntry("gv", "Get-Variable", "", ReadOnly), - new SessionStateAliasEntry("iex", "Invoke-Expression", "", ReadOnly), - new SessionStateAliasEntry("ihy", "Invoke-History", "", ReadOnly), - new SessionStateAliasEntry("ii", "Invoke-Item", "", ReadOnly), - new SessionStateAliasEntry("ipmo", "Import-Module", "", ReadOnly), - new SessionStateAliasEntry("ipal", "Import-Alias", "", ReadOnly), - new SessionStateAliasEntry("ipcsv", "Import-Csv", "", ReadOnly), - new SessionStateAliasEntry("measure", "Measure-Object", "", ReadOnly), - new SessionStateAliasEntry("mi", "Move-Item", "", ReadOnly), - new SessionStateAliasEntry("mp", "Move-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("nal", "New-Alias", "", ReadOnly), - new SessionStateAliasEntry("ndr", "New-PSDrive", "", ReadOnly), - new SessionStateAliasEntry("ni", "New-Item", "", ReadOnly), - new SessionStateAliasEntry("nv", "New-Variable", "", ReadOnly), - new SessionStateAliasEntry("nmo", "New-Module", "", ReadOnly), - new SessionStateAliasEntry("oh", "Out-Host", "", ReadOnly), - new SessionStateAliasEntry("rbp", "Remove-PSBreakpoint", "", ReadOnly), - new SessionStateAliasEntry("rdr", "Remove-PSDrive", "", ReadOnly), - new SessionStateAliasEntry("ri", "Remove-Item", "", ReadOnly), - new SessionStateAliasEntry("rni", "Rename-Item", "", ReadOnly), - new SessionStateAliasEntry("rnp", "Rename-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("rp", "Remove-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("rmo", "Remove-Module", "", ReadOnly), - new SessionStateAliasEntry("rv", "Remove-Variable", "", ReadOnly), - new SessionStateAliasEntry("rvpa", "Resolve-Path", "", ReadOnly), - new SessionStateAliasEntry("sal", "Set-Alias", "", ReadOnly), - new SessionStateAliasEntry("sbp", "Set-PSBreakpoint", "", ReadOnly), - new SessionStateAliasEntry("select", "Select-Object", "", ReadOnly_AllScope), - new SessionStateAliasEntry("si", "Set-Item", "", ReadOnly), - new SessionStateAliasEntry("sl", "Set-Location", "", ReadOnly), - new SessionStateAliasEntry("sp", "Set-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("saps", "Start-Process", "", ReadOnly), - new SessionStateAliasEntry("spps", "Stop-Process", "", ReadOnly), - new SessionStateAliasEntry("sv", "Set-Variable", "", ReadOnly), + 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), + new SessionStateAliasEntry("?", "Where-Object", string.Empty, ReadOnly_AllScope), + new SessionStateAliasEntry("clc", "Clear-Content", string.Empty, ReadOnly), + new SessionStateAliasEntry("cli", "Clear-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("clp", "Clear-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("clv", "Clear-Variable", string.Empty, ReadOnly), + new SessionStateAliasEntry("cpi", "Copy-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("cvpa", "Convert-Path", string.Empty, ReadOnly), + new SessionStateAliasEntry("dbp", "Disable-PSBreakpoint", string.Empty, ReadOnly), + new SessionStateAliasEntry("ebp", "Enable-PSBreakpoint", string.Empty, ReadOnly), + new SessionStateAliasEntry("epal", "Export-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("epcsv", "Export-Csv", string.Empty, ReadOnly), + new SessionStateAliasEntry("fl", "Format-List", string.Empty, ReadOnly), + new SessionStateAliasEntry("ft", "Format-Table", string.Empty, ReadOnly), + new SessionStateAliasEntry("fw", "Format-Wide", string.Empty, ReadOnly), + new SessionStateAliasEntry("gal", "Get-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("gbp", "Get-PSBreakpoint", string.Empty, ReadOnly), + new SessionStateAliasEntry("gc", "Get-Content", string.Empty, ReadOnly), + new SessionStateAliasEntry("gci", "Get-ChildItem", string.Empty, ReadOnly), + new SessionStateAliasEntry("gcm", "Get-Command", string.Empty, ReadOnly), + new SessionStateAliasEntry("gdr", "Get-PSDrive", string.Empty, ReadOnly), + new SessionStateAliasEntry("gcs", "Get-PSCallStack", string.Empty, ReadOnly), + new SessionStateAliasEntry("ghy", "Get-History", string.Empty, ReadOnly), + new SessionStateAliasEntry("gi", "Get-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("gl", "Get-Location", string.Empty, ReadOnly), + 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("gps", "Get-Process", string.Empty, ReadOnly), + new SessionStateAliasEntry("group", "Group-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("gu", "Get-Unique", string.Empty, ReadOnly), + new SessionStateAliasEntry("gv", "Get-Variable", string.Empty, ReadOnly), + new SessionStateAliasEntry("iex", "Invoke-Expression", string.Empty, ReadOnly), + new SessionStateAliasEntry("ihy", "Invoke-History", string.Empty, ReadOnly), + new SessionStateAliasEntry("ii", "Invoke-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("ipmo", "Import-Module", string.Empty, ReadOnly), + new SessionStateAliasEntry("ipal", "Import-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("ipcsv", "Import-Csv", string.Empty, ReadOnly), + new SessionStateAliasEntry("measure", "Measure-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("mi", "Move-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("mp", "Move-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("nal", "New-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("ndr", "New-PSDrive", string.Empty, ReadOnly), + new SessionStateAliasEntry("ni", "New-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("nv", "New-Variable", string.Empty, ReadOnly), + new SessionStateAliasEntry("nmo", "New-Module", string.Empty, ReadOnly), + new SessionStateAliasEntry("oh", "Out-Host", string.Empty, ReadOnly), + new SessionStateAliasEntry("rbp", "Remove-PSBreakpoint", string.Empty, ReadOnly), + new SessionStateAliasEntry("rdr", "Remove-PSDrive", string.Empty, ReadOnly), + new SessionStateAliasEntry("ri", "Remove-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("rni", "Rename-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("rnp", "Rename-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("rp", "Remove-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("rmo", "Remove-Module", string.Empty, ReadOnly), + new SessionStateAliasEntry("rv", "Remove-Variable", string.Empty, ReadOnly), + new SessionStateAliasEntry("gerr", "Get-Error", string.Empty, ReadOnly), + new SessionStateAliasEntry("rvpa", "Resolve-Path", string.Empty, ReadOnly), + new SessionStateAliasEntry("sal", "Set-Alias", string.Empty, ReadOnly), + new SessionStateAliasEntry("sbp", "Set-PSBreakpoint", string.Empty, ReadOnly), + new SessionStateAliasEntry("select", "Select-Object", string.Empty, ReadOnly_AllScope), + new SessionStateAliasEntry("si", "Set-Item", string.Empty, ReadOnly), + new SessionStateAliasEntry("sl", "Set-Location", string.Empty, ReadOnly), + new SessionStateAliasEntry("sp", "Set-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("saps", "Start-Process", string.Empty, ReadOnly), + new SessionStateAliasEntry("spps", "Stop-Process", string.Empty, ReadOnly), + new SessionStateAliasEntry("sv", "Set-Variable", string.Empty, ReadOnly), // Web cmdlets aliases - new SessionStateAliasEntry("irm", "Invoke-RestMethod", "", ReadOnly), - new SessionStateAliasEntry("iwr", "Invoke-WebRequest", "", ReadOnly), + new SessionStateAliasEntry("irm", "Invoke-RestMethod", string.Empty, ReadOnly), + new SessionStateAliasEntry("iwr", "Invoke-WebRequest", string.Empty, ReadOnly), // Porting note: #if !UNIX is used to disable aliases for cmdlets which conflict with Linux / macOS #if !UNIX // ac is a native command on macOS - new SessionStateAliasEntry("ac", "Add-Content", "", ReadOnly), - new SessionStateAliasEntry("compare", "Compare-Object", "", ReadOnly), - new SessionStateAliasEntry("cpp", "Copy-ItemProperty", "", ReadOnly), - new SessionStateAliasEntry("diff", "Compare-Object", "", ReadOnly), - new SessionStateAliasEntry("gsv", "Get-Service", "", ReadOnly), - new SessionStateAliasEntry("sleep", "Start-Sleep", "", ReadOnly), - new SessionStateAliasEntry("sort", "Sort-Object", "", ReadOnly), - new SessionStateAliasEntry("start", "Start-Process", "", ReadOnly), - new SessionStateAliasEntry("sasv", "Start-Service", "", ReadOnly), - new SessionStateAliasEntry("spsv", "Stop-Service", "", ReadOnly), - new SessionStateAliasEntry("tee", "Tee-Object", "", ReadOnly), - new SessionStateAliasEntry("write", "Write-Output", "", ReadOnly), + new SessionStateAliasEntry("ac", "Add-Content", string.Empty, ReadOnly), + new SessionStateAliasEntry("clear", "Clear-Host"), + new SessionStateAliasEntry("compare", "Compare-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("cpp", "Copy-ItemProperty", string.Empty, ReadOnly), + new SessionStateAliasEntry("diff", "Compare-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("gsv", "Get-Service", string.Empty, ReadOnly), + new SessionStateAliasEntry("sleep", "Start-Sleep", string.Empty, ReadOnly), + new SessionStateAliasEntry("sort", "Sort-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("start", "Start-Process", string.Empty, ReadOnly), + new SessionStateAliasEntry("sasv", "Start-Service", string.Empty, ReadOnly), + new SessionStateAliasEntry("spsv", "Stop-Service", string.Empty, ReadOnly), + new SessionStateAliasEntry("tee", "Tee-Object", string.Empty, ReadOnly), + new SessionStateAliasEntry("write", "Write-Output", string.Empty, ReadOnly), // These were transferred from the "transferred from the profile" section new SessionStateAliasEntry("cat", "Get-Content"), - new SessionStateAliasEntry("cp", "Copy-Item", "", AllScope), + new SessionStateAliasEntry("cp", "Copy-Item", string.Empty, AllScope), new SessionStateAliasEntry("ls", "Get-ChildItem"), new SessionStateAliasEntry("man", "help"), new SessionStateAliasEntry("mount", "New-PSDrive"), @@ -4674,50 +4809,49 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("ps", "Get-Process"), new SessionStateAliasEntry("rm", "Remove-Item"), new SessionStateAliasEntry("rmdir", "Remove-Item"), - new SessionStateAliasEntry("cnsn", "Connect-PSSession", "", ReadOnly), - new SessionStateAliasEntry("dnsn", "Disconnect-PSSession", "", ReadOnly), + new SessionStateAliasEntry("cnsn", "Connect-PSSession", string.Empty, ReadOnly), + new SessionStateAliasEntry("dnsn", "Disconnect-PSSession", string.Empty, ReadOnly), + new SessionStateAliasEntry("ogv", "Out-GridView", string.Empty, ReadOnly), + new SessionStateAliasEntry("shcm", "Show-Command", string.Empty, ReadOnly), #endif // Bash built-ins we purposefully keep even if they override native commands - new SessionStateAliasEntry("cd", "Set-Location", "", AllScope), - new SessionStateAliasEntry("dir", "Get-ChildItem", "", AllScope), - new SessionStateAliasEntry("echo", "Write-Output", "", AllScope), - new SessionStateAliasEntry("fc", "Format-Custom", "", ReadOnly), + new SessionStateAliasEntry("cd", "Set-Location", string.Empty, AllScope), + new SessionStateAliasEntry("dir", "Get-ChildItem", string.Empty, AllScope), + new SessionStateAliasEntry("echo", "Write-Output", string.Empty, AllScope), + new SessionStateAliasEntry("fc", "Format-Custom", string.Empty, ReadOnly), +#if !UNIX new SessionStateAliasEntry("kill", "Stop-Process"), +#endif new SessionStateAliasEntry("pwd", "Get-Location"), new SessionStateAliasEntry("type", "Get-Content"), - // Native commands we keep because the functions act correctly on Linux - new SessionStateAliasEntry("clear", "Clear-Host"), -//#if !CORECLR is used to disable aliases for cmdlets which are not available on OneCore or not appropriate for PSCore6 due to conflicts +// #if !CORECLR is used to disable aliases for cmdlets which are not available on OneCore or not appropriate for PSCore6 due to conflicts #if !CORECLR - new SessionStateAliasEntry("gwmi", "Get-WmiObject", "", ReadOnly), - new SessionStateAliasEntry("iwmi", "Invoke-WMIMethod", "", ReadOnly), - new SessionStateAliasEntry("ogv", "Out-GridView", "", ReadOnly), - new SessionStateAliasEntry("ise", "powershell_ise.exe", "", ReadOnly), - new SessionStateAliasEntry("rwmi", "Remove-WMIObject", "", ReadOnly), - new SessionStateAliasEntry("sc", "Set-Content", "", ReadOnly), - new SessionStateAliasEntry("swmi", "Set-WMIInstance", "", ReadOnly), - new SessionStateAliasEntry("shcm", "Show-Command", "", ReadOnly), - new SessionStateAliasEntry("trcm", "Trace-Command", "", ReadOnly), - new SessionStateAliasEntry("lp", "Out-Printer"), + new SessionStateAliasEntry("gwmi", "Get-WmiObject", string.Empty, ReadOnly), + new SessionStateAliasEntry("iwmi", "Invoke-WMIMethod", string.Empty, ReadOnly), + new SessionStateAliasEntry("ise", "powershell_ise.exe", string.Empty, ReadOnly), + new SessionStateAliasEntry("rwmi", "Remove-WMIObject", string.Empty, ReadOnly), + new SessionStateAliasEntry("sc", "Set-Content", string.Empty, ReadOnly), + new SessionStateAliasEntry("swmi", "Set-WMIInstance", string.Empty, ReadOnly), + new SessionStateAliasEntry("trcm", "Trace-Command", string.Empty, ReadOnly), #endif // Aliases transferred from the profile new SessionStateAliasEntry("h", "Get-History"), new SessionStateAliasEntry("history", "Get-History"), - new SessionStateAliasEntry("md", "mkdir", "", AllScope), - new SessionStateAliasEntry("popd", "Pop-Location", "", AllScope), - new SessionStateAliasEntry("pushd", "Push-Location", "", AllScope), + new SessionStateAliasEntry("md", "mkdir", string.Empty, AllScope), + new SessionStateAliasEntry("popd", "Pop-Location", string.Empty, AllScope), + new SessionStateAliasEntry("pushd", "Push-Location", string.Empty, AllScope), new SessionStateAliasEntry("r", "Invoke-History"), new SessionStateAliasEntry("cls", "Clear-Host"), new SessionStateAliasEntry("chdir", "Set-Location"), - new SessionStateAliasEntry("copy", "Copy-Item", "", AllScope), - new SessionStateAliasEntry("del", "Remove-Item", "", AllScope), + new SessionStateAliasEntry("copy", "Copy-Item", string.Empty, AllScope), + new SessionStateAliasEntry("del", "Remove-Item", string.Empty, AllScope), new SessionStateAliasEntry("erase", "Remove-Item"), - new SessionStateAliasEntry("move", "Move-Item", "", AllScope), + new SessionStateAliasEntry("move", "Move-Item", string.Empty, AllScope), new SessionStateAliasEntry("rd", "Remove-Item"), new SessionStateAliasEntry("ren", "Rename-Item"), new SessionStateAliasEntry("set", "Set-Variable"), new SessionStateAliasEntry("icm", "Invoke-Command"), - new SessionStateAliasEntry("clhy", "Clear-History", "", ReadOnly), + new SessionStateAliasEntry("clhy", "Clear-History", string.Empty, ReadOnly), // Job Specific aliases new SessionStateAliasEntry("gjb", "Get-Job"), new SessionStateAliasEntry("rcjb", "Receive-Job"), @@ -4729,7 +4863,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("sujb", "Suspend-Job"), new SessionStateAliasEntry("rujb", "Resume-Job"), // Remoting Cmdlets Specific aliases - new SessionStateAliasEntry("npssc", "New-PSSessionConfigurationFile", "", ReadOnly), + new SessionStateAliasEntry("npssc", "New-PSSessionConfigurationFile", string.Empty, ReadOnly), new SessionStateAliasEntry("ipsn", "Import-PSSession"), new SessionStateAliasEntry("epsn", "Export-PSSession"), #endif @@ -4737,12 +4871,14 @@ internal static SessionStateAliasEntry[] BuiltInAliases new SessionStateAliasEntry("gsn", "Get-PSSession"), new SessionStateAliasEntry("rsn", "Remove-PSSession"), new SessionStateAliasEntry("etsn", "Enter-PSSession"), - new SessionStateAliasEntry("rcsn", "Receive-PSSession", "", ReadOnly), + new SessionStateAliasEntry("rcsn", "Receive-PSSession", string.Empty, ReadOnly), new SessionStateAliasEntry("exsn", "Exit-PSSession"), // Win8: 121662/169179 Add "sls" alias for Select-String cmdlet // - do not use AllScope - this causes errors in profiles that set this somewhat commonly used alias. new SessionStateAliasEntry("sls", "Select-String"), }; + + return builtInAliases.ToArray(); } } @@ -4753,51 +4889,38 @@ internal static SessionStateAliasEntry[] BuiltInAliases # .ExternalHelp System.Management.Automation.dll-help.xml "; - internal const string DefaultMoreFunctionText = @" -param([string[]]$paths) -# Nano needs to use Unicode, but Windows and Linux need the default -$OutputEncoding = if ([System.Management.Automation.Platform]::IsNanoServer -or [System.Management.Automation.Platform]::IsIoT) { - [System.Text.Encoding]::Unicode -} else { - [System.Console]::OutputEncoding -} + internal const string DefaultSetDriveFunctionText = "Set-Location $MyInvocation.MyCommand.Name"; -# Respect PAGER, use more on Windows, and use less on Linux -if (Test-Path env:PAGER) { - $moreCommand = (Get-Command -CommandType Application $env:PAGER | Select-Object -First 1).Definition -} elseif ($IsWindows) { - $moreCommand = (Get-Command -CommandType Application more | Select-Object -First 1).Definition -} else { - $moreCommand = (Get-Command -CommandType Application less | Select-Object -First 1).Definition -} + internal static readonly ScriptBlock SetDriveScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(DefaultSetDriveFunctionText, isProductCode: true); -if($paths) { - foreach ($file in $paths) { - Get-Content $file | & $moreCommand - } -} else { $input | & $moreCommand } -"; + private static readonly PSLanguageMode systemLanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage; - internal const string DefaultSetDriveFunctionText = "Set-Location $MyInvocation.MyCommand.Name"; - internal static ScriptBlock SetDriveScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(DefaultSetDriveFunctionText, isProductCode: true); - - internal static SessionStateFunctionEntry[] BuiltInFunctions = new SessionStateFunctionEntry[] - { - // Functions. Only the name and definitions are used - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("prompt", DefaultPromptFunctionText, isProductCode: true), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("TabExpansion2", s_tabExpansionFunctionText, isProductCode: true), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Clear-Host", GetClearHostFunctionText(), isProductCode: true), - // Porting note: we keep more because the function acts correctly on Linux - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("more", DefaultMoreFunctionText, isProductCode: true), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("help", GetHelpPagingFunctionText(), isProductCode: true), - // Porting note: we remove mkdir on Linux because it is a conflict -#if !UNIX - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("mkdir", GetMkdirFunctionText(), isProductCode: true), + 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), + 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), + 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 - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("oss", GetOSTFunctionText(), isProductCode: true), - // Porting note: we remove the drive functions from Linux because they make no sense + // 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), + SessionStateFunctionEntry.GetDelayParsedFunctionEntry("oss", GetOSTFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage), +#if !UNIX + // Porting note: we remove mkdir on Linux because of a conflict + SessionStateFunctionEntry.GetDelayParsedFunctionEntry("mkdir", GetMkdirFunctionText(), isProductCode: true, languageMode: PSLanguageMode.FullLanguage), +#endif #if !UNIX + // Porting note: we remove the drive functions from Linux because they make no sense in that environment // Default drives SessionStateFunctionEntry.GetDelayParsedFunctionEntry("A:", DefaultSetDriveFunctionText, SetDriveScriptBlock), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("B:", DefaultSetDriveFunctionText, SetDriveScriptBlock), @@ -4824,13 +4947,8 @@ internal static SessionStateAliasEntry[] BuiltInAliases SessionStateFunctionEntry.GetDelayParsedFunctionEntry("W:", DefaultSetDriveFunctionText, SetDriveScriptBlock), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("X:", DefaultSetDriveFunctionText, SetDriveScriptBlock), SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Y:", DefaultSetDriveFunctionText, SetDriveScriptBlock), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Z:", DefaultSetDriveFunctionText, SetDriveScriptBlock), + SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Z:", DefaultSetDriveFunctionText, SetDriveScriptBlock) #endif - - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd..", "Set-Location ..", isProductCode: true), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("cd\\", "Set-Location \\", isProductCode: true), - SessionStateFunctionEntry.GetDelayParsedFunctionEntry("Pause", - string.Concat("$null = Read-Host '", CodeGeneration.EscapeSingleQuotedStringContent(RunspaceInit.PauseDefinitionString),"'"), isProductCode: true) }; internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInternal ssi) @@ -4847,61 +4965,59 @@ 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 string CoreSnapin = "Microsoft.PowerShell.Core"; - internal static string CoreModule = "Microsoft.PowerShell.Core"; - internal Collection defaultSnapins = new Collection(); + internal static readonly string CoreSnapin = "Microsoft.PowerShell.Core"; + internal static readonly string CoreModule = "Microsoft.PowerShell.Core"; // The list of engine modules to create warnings when you try to remove them - internal static HashSet EngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Microsoft.PowerShell.Utility", - "Microsoft.PowerShell.Management", - "Microsoft.PowerShell.Diagnostics", - "Microsoft.PowerShell.Host", - "Microsoft.PowerShell.Security", - "Microsoft.WSMan.Management" - }; - - internal static HashSet NestedEngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "Microsoft.PowerShell.Commands.Utility", - "Microsoft.PowerShell.Commands.Management", - "Microsoft.PowerShell.Commands.Diagnostics", - "Microsoft.PowerShell.ConsoleHost" - }; - - internal static Dictionary EngineModuleNestedModuleMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Commands.Utility"}, - { "Microsoft.PowerShell.Management", "Microsoft.PowerShell.Commands.Management"}, - { "Microsoft.PowerShell.Diagnostics", "Microsoft.PowerShell.Commands.Diagnostics"}, - { "Microsoft.PowerShell.Host", "Microsoft.PowerShell.ConsoleHost"}, - }; - internal static Dictionary NestedModuleEngineModuleMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "Microsoft.PowerShell.Commands.Utility", "Microsoft.PowerShell.Utility"}, - { "Microsoft.PowerShell.Commands.Management", "Microsoft.PowerShell.Management"}, - { "Microsoft.PowerShell.Commands.Diagnostics", "Microsoft.PowerShell.Diagnostics"}, - { "Microsoft.PowerShell.ConsoleHost", "Microsoft.PowerShell.Host"}, - { "Microsoft.PowerShell.Security", "Microsoft.PowerShell.Security"}, - { "Microsoft.WSMan.Management", "Microsoft.WSMan.Management"}, - }; + internal static readonly HashSet EngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Microsoft.PowerShell.Utility", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.Security", + "Microsoft.WSMan.Management" + }; + + internal static readonly HashSet NestedEngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Microsoft.PowerShell.Commands.Utility", + "Microsoft.PowerShell.Commands.Management", + "Microsoft.PowerShell.Commands.Diagnostics", + "Microsoft.PowerShell.ConsoleHost" + }; + internal static readonly Dictionary EngineModuleNestedModuleMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Commands.Utility"}, + { "Microsoft.PowerShell.Management", "Microsoft.PowerShell.Commands.Management"}, + { "Microsoft.PowerShell.Diagnostics", "Microsoft.PowerShell.Commands.Diagnostics"}, + { "Microsoft.PowerShell.Host", "Microsoft.PowerShell.ConsoleHost"}, + }; + + internal static readonly Dictionary NestedModuleEngineModuleMapping = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Microsoft.PowerShell.Commands.Utility", "Microsoft.PowerShell.Utility"}, + { "Microsoft.PowerShell.Commands.Management", "Microsoft.PowerShell.Management"}, + { "Microsoft.PowerShell.Commands.Diagnostics", "Microsoft.PowerShell.Diagnostics"}, + { "Microsoft.PowerShell.ConsoleHost", "Microsoft.PowerShell.Host"}, + { "Microsoft.PowerShell.Security", "Microsoft.PowerShell.Security"}, + { "Microsoft.WSMan.Management", "Microsoft.WSMan.Management"}, + }; // The list of engine modules that we will not allow users to remove - internal static HashSet ConstantEngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) - { - CoreModule, - }; + internal static readonly HashSet ConstantEngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { + CoreModule, + }; // The list of nested engine modules that we will not allow users to remove - internal static HashSet ConstantEngineNestedModules = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "System.Management.Automation", - }; - + internal static readonly HashSet ConstantEngineNestedModules = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "System.Management.Automation", + }; internal static string GetNestedModuleDllName(string moduleName) { @@ -4922,13 +5038,9 @@ internal static string GetNestedModuleDllName(string moduleName) internal static class PSSnapInHelpers { [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] - internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo, - out Dictionary cmdlets, out Dictionary providers) + internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo) { Assembly assembly = null; - cmdlets = null; - providers = null; - s_PSSnapInTracer.WriteLine("Loading assembly from GAC. Assembly Name: {0}", psSnapInInfo.AssemblyName); try @@ -4949,7 +5061,9 @@ internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo, } if (assembly != null) + { return assembly; + } s_PSSnapInTracer.WriteLine("Loading assembly from path: {0}", psSnapInInfo.AssemblyName); @@ -4985,24 +5099,26 @@ internal static Assembly LoadPSSnapInAssembly(PSSnapInInfo psSnapInInfo, return assembly; } - private static T GetCustomAttribute(TypeInfo decoratedType) where T : Attribute + private static bool TryGetCustomAttribute(Type decoratedType, out T attribute) where T : Attribute { - var attributes = CustomAttributeExtensions.GetCustomAttributes(decoratedType, false); - var customAttrs = attributes.ToArray(); - - Debug.Assert(customAttrs.Length <= 1, "CmdletAttribute and/or CmdletProviderAttribute cannot normally appear more than once"); - return customAttrs.Length == 0 ? null : customAttrs[0]; + var attributes = decoratedType.GetCustomAttributes(inherit: false); + attribute = attributes.FirstOrDefault(); + return attribute != null; } - internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSSnapInInfo psSnapInInfo, PSModuleInfo moduleInfo, bool isModuleLoad, - out Dictionary cmdlets, out Dictionary> aliases, - out Dictionary providers, out string helpFile) + internal static void AnalyzePSSnapInAssembly( + Assembly assembly, + string name, + PSSnapInInfo psSnapInInfo, + PSModuleInfo moduleInfo, + out Dictionary cmdlets, + out Dictionary> aliases, + out Dictionary providers, + out string helpFile) { helpFile = null; - if (assembly == null) - { - throw new ArgumentNullException("assembly"); - } + + ArgumentNullException.ThrowIfNull(assembly); cmdlets = null; aliases = null; @@ -5013,8 +5129,8 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS Dictionary>> cachedCmdlets; if (s_cmdletCache.Value.TryGetValue(assembly, out cachedCmdlets)) { - cmdlets = new Dictionary(s_cmdletCache.Value.Count, StringComparer.OrdinalIgnoreCase); - aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); + cmdlets = new Dictionary(cachedCmdlets.Count, StringComparer.OrdinalIgnoreCase); + aliases = new Dictionary>(cachedCmdlets.Count, StringComparer.OrdinalIgnoreCase); foreach (var pair in cachedCmdlets) { @@ -5024,11 +5140,13 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS { entry.Item1.SetPSSnapIn(psSnapInInfo); } + var newEntry = (SessionStateCmdletEntry)entry.Item1.Clone(); if (newEntry.PSSnapIn != null && psSnapInInfo == null) { newEntry.SetPSSnapIn(null); } + cmdlets[key] = newEntry; if (entry.Item2 != null) @@ -5046,6 +5164,7 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS { newAliasEntry.SetPSSnapIn(null); } + aliasList.Add(newAliasEntry); } @@ -5066,17 +5185,18 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS { entry.SetPSSnapIn(psSnapInInfo); } + var newEntry = (SessionStateProviderEntry)entry.Clone(); if (newEntry.PSSnapIn != null && psSnapInInfo == null) { newEntry.SetPSSnapIn(null); } + providers[key] = newEntry; } } string assemblyPath = assembly.Location; - Type[] assemblyTypes; if (cmdlets != null || providers != null) { if (!s_assembliesWithModuleInitializerCache.Value.ContainsKey(assembly)) @@ -5087,19 +5207,15 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS else { s_PSSnapInTracer.WriteLine("Executing IModuleAssemblyInitializer.Import for {0}", assemblyPath); - assemblyTypes = GetAssemblyTypes(assembly, name); - ExecuteModuleInitializer(assembly, assemblyTypes, isModuleLoad); + var assemblyTypes = GetAssemblyTypes(assembly, name); + ExecuteModuleInitializer(assembly, assemblyTypes); return; } } s_PSSnapInTracer.WriteLine("Analyzing assembly {0} for cmdlet and providers", assemblyPath); - helpFile = GetHelpFile(assemblyPath); - Type randomCmdletToCheckLinkDemand = null; - Type randomProviderToCheckLinkDemand = null; - if (psSnapInInfo != null && psSnapInInfo.Name.Equals(InitialSessionState.CoreSnapin, StringComparison.OrdinalIgnoreCase)) { InitializeCoreCmdletsAndProviders(psSnapInInfo, out cmdlets, out providers, helpFile); @@ -5111,13 +5227,10 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS Dictionary cmdletsCheck = null; Dictionary providersCheck = null; Dictionary> aliasesCheck = null; - Type unused1 = null; - Type unused2 = null; - AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, - ref cmdletsCheck, ref aliasesCheck, ref providersCheck, helpFile, ref unused1, ref unused2); + AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, helpFile, ref cmdletsCheck, ref aliasesCheck, ref providersCheck); Diagnostics.Assert(aliasesCheck == null, "InitializeCoreCmdletsAndProviders assumes no aliases are defined in System.Management.Automation.dll"); - Diagnostics.Assert(providersCheck.Keys.Count == providers.Keys.Count, "new Provider added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); + Diagnostics.Assert(providersCheck.Count == providers.Count, "new Provider added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); foreach (var pair in providersCheck) { SessionStateProviderEntry other; @@ -5135,7 +5248,8 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS Diagnostics.Assert(false, "Missing provider: " + pair.Key); } } - Diagnostics.Assert(cmdletsCheck.Keys.Count == cmdlets.Keys.Count, "new Cmdlet added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); + + Diagnostics.Assert(cmdletsCheck.Count == cmdlets.Count, "new Cmdlet added to System.Management.Automation.dll - update InitializeCoreCmdletsAndProviders"); foreach (var pair in cmdletsCheck) { @@ -5158,39 +5272,7 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS } else { - AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, isModuleLoad, - ref cmdlets, ref aliases, ref providers, helpFile, ref randomCmdletToCheckLinkDemand, ref randomProviderToCheckLinkDemand); - } - - // force a LinkDemand check to get an explicit exception if - // Cmdlet[Provider]Attributes are silently swallowed by Type.GetCustomAttributes - // bug Win7:705573 - if ((providers == null || providers.Count == 0) && (cmdlets == null || cmdlets.Count == 0)) - { - try - { - if (randomCmdletToCheckLinkDemand != null) - { - ConstructorInfo constructor = randomCmdletToCheckLinkDemand.GetConstructor(PSTypeExtensions.EmptyTypes); - if (constructor != null) - { - constructor.Invoke(null); // this is how we artificially force a LinkDemand check - } - } - - if (randomProviderToCheckLinkDemand != null) - { - ConstructorInfo constructor = randomProviderToCheckLinkDemand.GetConstructor(PSTypeExtensions.EmptyTypes); - if (constructor != null) - { - constructor.Invoke(null); // this is how we artificially force a LinkDemand check - } - } - } - catch (TargetInvocationException e) - { - throw e.InnerException; - } + AnalyzeModuleAssemblyWithReflection(assembly, name, psSnapInInfo, moduleInfo, helpFile, ref cmdlets, ref aliases, ref providers); } // Cache the cmdlet and provider info for this assembly... @@ -5218,6 +5300,7 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS clone[entry.Key] = new Tuple>((SessionStateCmdletEntry)entry.Value.Clone(), aliasesCloneList); } + s_cmdletCache.Value[assembly] = clone; } @@ -5228,128 +5311,123 @@ internal static void AnalyzePSSnapInAssembly(Assembly assembly, string name, PSS { clone[entry.Key] = (SessionStateProviderEntry)entry.Value.Clone(); } - s_providerCache.Value[assembly] = providers; + + s_providerCache.Value[assembly] = clone; } } - private static void AnalyzeModuleAssemblyWithReflection(Assembly assembly, string name, PSSnapInInfo psSnapInInfo, - PSModuleInfo moduleInfo, bool isModuleLoad, + private static void AnalyzeModuleAssemblyWithReflection( + Assembly assembly, + string name, + PSSnapInInfo psSnapInInfo, + PSModuleInfo moduleInfo, + string helpFile, ref Dictionary cmdlets, ref Dictionary> aliases, - ref Dictionary providers, - string helpFile, - ref Type randomCmdletToCheckLinkDemand, - ref Type randomProviderToCheckLinkDemand) + ref Dictionary providers) { var assemblyTypes = GetAssemblyTypes(assembly, name); - - ExecuteModuleInitializer(assembly, assemblyTypes, isModuleLoad); + ExecuteModuleInitializer(assembly, assemblyTypes); foreach (Type type in assemblyTypes) { - var typeInfo = type.GetTypeInfo(); - if (!(typeInfo.IsPublic || typeInfo.IsNestedPublic) || typeInfo.IsAbstract) + if (!HasDefaultConstructor(type)) + { continue; + } // Check for cmdlets - if (IsCmdletClass(type) && HasDefaultConstructor(type)) + if (IsCmdletClass(type) && TryGetCustomAttribute(type, out CmdletAttribute cmdletAttribute)) { - randomCmdletToCheckLinkDemand = type; - - CmdletAttribute cmdletAttribute = GetCustomAttribute(typeInfo); - if (cmdletAttribute == null) - { - continue; - } - string cmdletName = GetCmdletName(cmdletAttribute); - if (string.IsNullOrEmpty(cmdletName)) + if (TryGetCustomAttribute(type, out ExperimentalAttribute expAttribute) && expAttribute.ToHide) { + // If 'ExperimentalAttribute' is specified on the cmdlet type and the + // effective action at run time is 'Hide', then we ignore the type. continue; } + string cmdletName = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName; if (cmdlets != null && cmdlets.ContainsKey(cmdletName)) { string message = StringUtil.Format(ConsoleInfoErrorStrings.PSSnapInDuplicateCmdlets, cmdletName, name); - s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(name, message); } SessionStateCmdletEntry cmdlet = new SessionStateCmdletEntry(cmdletName, type, helpFile); - cmdlet.SetPSSnapIn(psSnapInInfo); - if (cmdlets == null) + if (psSnapInInfo != null) + { + cmdlet.SetPSSnapIn(psSnapInInfo); + } + + if (moduleInfo != null) { - cmdlets = new Dictionary(StringComparer.OrdinalIgnoreCase); + cmdlet.SetModule(moduleInfo); } + + cmdlets ??= new Dictionary(StringComparer.OrdinalIgnoreCase); cmdlets.Add(cmdletName, cmdlet); - var aliasAttribute = GetCustomAttribute(typeInfo); - if (aliasAttribute != null) + if (TryGetCustomAttribute(type, out AliasAttribute aliasAttribute)) { - if (aliases == null) - { - aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); - } + aliases ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); var aliasList = new List(); foreach (var alias in aliasAttribute.AliasNames) { - // Alias declared by AliasAttribute is set with the option 'ScopedItemOptions.None', - // because we believe a user of the cmdlet, instead of the author of it, - // should be the one to decide the option - // ('ScopedItemOptions.ReadOnly' and/or 'ScopedItemOptions.AllScopes') of the alias usage." - var aliasEntry = new SessionStateAliasEntry(alias, cmdletName, "", ScopedItemOptions.None); + // Alias declared by 'AliasAttribute' is set with the option 'ScopedItemOptions.None', because we believe + // 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 (moduleInfo != null) + { + aliasEntry.SetModule(moduleInfo); + } + aliasList.Add(aliasEntry); } + aliases.Add(cmdletName, aliasList); } s_PSSnapInTracer.WriteLine("{0} from type {1} is added as a cmdlet. ", cmdletName, type.FullName); - continue; } - // Check for providers - if (IsProviderClass(type) && HasDefaultConstructor(type)) + else if (IsProviderClass(type) && TryGetCustomAttribute(type, out CmdletProviderAttribute providerAttribute)) { - randomProviderToCheckLinkDemand = type; - - CmdletProviderAttribute providerAttribute = GetCustomAttribute(typeInfo); - if (providerAttribute == null) - { - continue; - } - string providerName = GetProviderName(providerAttribute); - if (string.IsNullOrEmpty(providerName)) + if (TryGetCustomAttribute(type, out ExperimentalAttribute expAttribute) && expAttribute.ToHide) { + // If 'ExperimentalAttribute' is specified on the provider type and + // the effective action at run time is 'Hide', then we ignore the type. continue; } + string providerName = providerAttribute.ProviderName; if (providers != null && providers.ContainsKey(providerName)) { string message = StringUtil.Format(ConsoleInfoErrorStrings.PSSnapInDuplicateProviders, providerName, psSnapInInfo.Name); - s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(psSnapInInfo.Name, message); } SessionStateProviderEntry provider = new SessionStateProviderEntry(providerName, type, helpFile); - provider.SetPSSnapIn(psSnapInInfo); + if (psSnapInInfo != null) + { + provider.SetPSSnapIn(psSnapInInfo); + } - // After converting core snapins to load as modules, the providers will have Module property populated if (moduleInfo != null) { provider.SetModule(moduleInfo); } - if (providers == null) - { - 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); @@ -5357,6 +5435,7 @@ private static void AnalyzeModuleAssemblyWithReflection(Assembly assembly, strin } } + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed.")] private static void InitializeCoreCmdletsAndProviders( PSSnapInInfo psSnapInInfo, out Dictionary cmdlets, @@ -5365,76 +5444,85 @@ private static void InitializeCoreCmdletsAndProviders( { cmdlets = new Dictionary(StringComparer.OrdinalIgnoreCase) { - {"Add-History", new SessionStateCmdletEntry("Add-History", typeof(AddHistoryCommand), helpFile) }, - {"Clear-History", new SessionStateCmdletEntry("Clear-History", typeof(ClearHistoryCommand), helpFile) }, - {"Debug-Job", new SessionStateCmdletEntry("Debug-Job", typeof(DebugJobCommand), helpFile) }, + { "Add-History", new SessionStateCmdletEntry("Add-History", typeof(AddHistoryCommand), helpFile) }, + { "Clear-History", new SessionStateCmdletEntry("Clear-History", typeof(ClearHistoryCommand), helpFile) }, + { "Debug-Job", new SessionStateCmdletEntry("Debug-Job", typeof(DebugJobCommand), helpFile) }, #if !UNIX - {"Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, - {"Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, - {"Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, - {"Enter-PSHostProcess", new SessionStateCmdletEntry("Enter-PSHostProcess", typeof(EnterPSHostProcessCommand), helpFile) }, - {"Exit-PSHostProcess", new SessionStateCmdletEntry("Exit-PSHostProcess", typeof(ExitPSHostProcessCommand), helpFile) }, - {"Disable-PSSessionConfiguration", new SessionStateCmdletEntry("Disable-PSSessionConfiguration", typeof(DisablePSSessionConfigurationCommand), helpFile) }, - {"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) }, - {"New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), 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) }, - {"Set-PSSessionConfiguration", new SessionStateCmdletEntry("Set-PSSessionConfiguration", typeof(SetPSSessionConfigurationCommand), helpFile) }, - {"Test-PSSessionConfigurationFile", new SessionStateCmdletEntry("Test-PSSessionConfigurationFile", typeof(TestPSSessionConfigurationFileCommand), helpFile) }, - {"Connect-PSSession", new SessionStateCmdletEntry("Connect-PSSession", typeof(ConnectPSSessionCommand), helpFile) }, - {"Disconnect-PSSession", new SessionStateCmdletEntry("Disconnect-PSSession", typeof(DisconnectPSSessionCommand), helpFile) }, + { "Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, + { "Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, + { "Disable-PSSessionConfiguration", new SessionStateCmdletEntry("Disable-PSSessionConfiguration", typeof(DisablePSSessionConfigurationCommand), helpFile) }, + { "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) }, + { "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) }, + { "Set-PSSessionConfiguration", new SessionStateCmdletEntry("Set-PSSessionConfiguration", typeof(SetPSSessionConfigurationCommand), helpFile) }, + { "Test-PSSessionConfigurationFile", new SessionStateCmdletEntry("Test-PSSessionConfigurationFile", typeof(TestPSSessionConfigurationFileCommand), helpFile) }, + { "Connect-PSSession", new SessionStateCmdletEntry("Connect-PSSession", typeof(ConnectPSSessionCommand), helpFile) }, + { "Disconnect-PSSession", new SessionStateCmdletEntry("Disconnect-PSSession", typeof(DisconnectPSSessionCommand), helpFile) }, #endif - {"Enter-PSSession", new SessionStateCmdletEntry("Enter-PSSession", typeof(EnterPSSessionCommand), helpFile) }, - {"Exit-PSSession", new SessionStateCmdletEntry("Exit-PSSession", typeof(ExitPSSessionCommand), helpFile) }, - {"Export-ModuleMember", new SessionStateCmdletEntry("Export-ModuleMember", typeof(ExportModuleMemberCommand), helpFile) }, - {"ForEach-Object", new SessionStateCmdletEntry("ForEach-Object", typeof(ForEachObjectCommand), helpFile) }, - {"Get-Command", new SessionStateCmdletEntry("Get-Command", typeof(GetCommandCommand), helpFile) }, - {"Get-Help", new SessionStateCmdletEntry("Get-Help", typeof(GetHelpCommand), helpFile) }, - {"Get-History", new SessionStateCmdletEntry("Get-History", typeof(GetHistoryCommand), helpFile) }, - {"Get-Job", new SessionStateCmdletEntry("Get-Job", typeof(GetJobCommand), helpFile) }, - {"Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, - {"Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), 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) }, - {"New-Module", new SessionStateCmdletEntry("New-Module", typeof(NewModuleCommand), helpFile) }, - {"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-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) }, - {"Out-Default", new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), helpFile) }, - {"Out-Host", new SessionStateCmdletEntry("Out-Host", typeof(OutHostCommand), helpFile) }, - {"Out-Null", new SessionStateCmdletEntry("Out-Null", typeof(OutNullCommand), helpFile) }, - {"Receive-Job", new SessionStateCmdletEntry("Receive-Job", typeof(ReceiveJobCommand), helpFile) }, - {"Register-ArgumentCompleter", new SessionStateCmdletEntry("Register-ArgumentCompleter", typeof(RegisterArgumentCompleterCommand), helpFile) }, - {"Remove-Job", new SessionStateCmdletEntry("Remove-Job", typeof(RemoveJobCommand), helpFile) }, - {"Remove-Module", new SessionStateCmdletEntry("Remove-Module", typeof(RemoveModuleCommand), helpFile) }, - {"Remove-PSSession", new SessionStateCmdletEntry("Remove-PSSession", typeof(RemovePSSessionCommand), helpFile) }, - {"Save-Help", new SessionStateCmdletEntry("Save-Help", typeof(SaveHelpCommand), helpFile) }, - {"Set-PSDebug", new SessionStateCmdletEntry("Set-PSDebug", typeof(SetPSDebugCommand), helpFile) }, - {"Set-StrictMode", new SessionStateCmdletEntry("Set-StrictMode", typeof(SetStrictModeCommand), helpFile) }, - {"Start-Job", new SessionStateCmdletEntry("Start-Job", typeof(StartJobCommand), helpFile) }, - {"Stop-Job", new SessionStateCmdletEntry("Stop-Job", typeof(StopJobCommand), helpFile) }, - {"Test-ModuleManifest", new SessionStateCmdletEntry("Test-ModuleManifest", typeof(TestModuleManifestCommand), helpFile) }, - {"Update-Help", new SessionStateCmdletEntry("Update-Help", typeof(UpdateHelpCommand), helpFile) }, - {"Wait-Job", new SessionStateCmdletEntry("Wait-Job", typeof(WaitJobCommand), helpFile) }, - {"Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) }, + { "Disable-ExperimentalFeature", new SessionStateCmdletEntry("Disable-ExperimentalFeature", typeof(DisableExperimentalFeatureCommand), helpFile) }, + { "Enable-ExperimentalFeature", new SessionStateCmdletEntry("Enable-ExperimentalFeature", typeof(EnableExperimentalFeatureCommand), helpFile) }, + { "Enter-PSHostProcess", new SessionStateCmdletEntry("Enter-PSHostProcess", typeof(EnterPSHostProcessCommand), helpFile) }, + { "Enter-PSSession", new SessionStateCmdletEntry("Enter-PSSession", typeof(EnterPSSessionCommand), helpFile) }, + { "Exit-PSHostProcess", new SessionStateCmdletEntry("Exit-PSHostProcess", typeof(ExitPSHostProcessCommand), helpFile) }, + { "Exit-PSSession", new SessionStateCmdletEntry("Exit-PSSession", typeof(ExitPSSessionCommand), helpFile) }, + { "Export-ModuleMember", new SessionStateCmdletEntry("Export-ModuleMember", typeof(ExportModuleMemberCommand), helpFile) }, + { "ForEach-Object", new SessionStateCmdletEntry("ForEach-Object", typeof(ForEachObjectCommand), helpFile) }, + { "Get-Command", new SessionStateCmdletEntry("Get-Command", typeof(GetCommandCommand), helpFile) }, + { "Get-ExperimentalFeature", new SessionStateCmdletEntry("Get-ExperimentalFeature", typeof(GetExperimentalFeatureCommand), helpFile) }, + { "Get-Help", new SessionStateCmdletEntry("Get-Help", typeof(GetHelpCommand), helpFile) }, + { "Get-History", new SessionStateCmdletEntry("Get-History", typeof(GetHistoryCommand), helpFile) }, + { "Get-Job", new SessionStateCmdletEntry("Get-Job", typeof(GetJobCommand), helpFile) }, + { "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) }, + { "New-Module", new SessionStateCmdletEntry("New-Module", typeof(NewModuleCommand), helpFile) }, + { "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) }, + { "Out-Host", new SessionStateCmdletEntry("Out-Host", typeof(OutHostCommand), helpFile) }, + { "Out-Null", new SessionStateCmdletEntry("Out-Null", typeof(OutNullCommand), helpFile) }, + { "Receive-Job", new SessionStateCmdletEntry("Receive-Job", typeof(ReceiveJobCommand), helpFile) }, + { "Register-ArgumentCompleter", new SessionStateCmdletEntry("Register-ArgumentCompleter", typeof(RegisterArgumentCompleterCommand), helpFile) }, + { "Remove-Job", new SessionStateCmdletEntry("Remove-Job", typeof(RemoveJobCommand), helpFile) }, + { "Remove-Module", new SessionStateCmdletEntry("Remove-Module", typeof(RemoveModuleCommand), helpFile) }, + { "Remove-PSSession", new SessionStateCmdletEntry("Remove-PSSession", typeof(RemovePSSessionCommand), helpFile) }, + { "Save-Help", new SessionStateCmdletEntry("Save-Help", typeof(SaveHelpCommand), helpFile) }, + { "Set-PSDebug", new SessionStateCmdletEntry("Set-PSDebug", typeof(SetPSDebugCommand), helpFile) }, + { "Set-StrictMode", new SessionStateCmdletEntry("Set-StrictMode", typeof(SetStrictModeCommand), helpFile) }, + { "Start-Job", new SessionStateCmdletEntry("Start-Job", typeof(StartJobCommand), helpFile) }, + { "Stop-Job", new SessionStateCmdletEntry("Stop-Job", typeof(StopJobCommand), helpFile) }, + { "Test-ModuleManifest", new SessionStateCmdletEntry("Test-ModuleManifest", typeof(TestModuleManifestCommand), helpFile) }, + { "Update-Help", new SessionStateCmdletEntry("Update-Help", typeof(UpdateHelpCommand), helpFile) }, + { "Wait-Job", new SessionStateCmdletEntry("Wait-Job", typeof(WaitJobCommand), helpFile) }, + { "Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) }, #if !CORECLR - {"Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) }, - {"Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, - {"Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, - {"Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, - {"Resume-Job", new SessionStateCmdletEntry("Resume-Job", typeof(ResumeJobCommand), helpFile) }, - {"Suspend-Job", new SessionStateCmdletEntry("Suspend-Job", typeof(SuspendJobCommand), helpFile) }, + { "Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) }, + { "Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, + { "Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, + { "Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, + { "Resume-Job", new SessionStateCmdletEntry("Resume-Job", typeof(ResumeJobCommand), helpFile) }, + { "Suspend-Job", new SessionStateCmdletEntry("Suspend-Job", typeof(SuspendJobCommand), helpFile) }, #endif // Not exported, but are added via reflection so added here as well, though maybe they shouldn't be - {"Out-LineOutput", new SessionStateCmdletEntry("Out-LineOutput", typeof(OutLineOutputCommand), helpFile) }, - {"Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, + { "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); @@ -5451,46 +5539,36 @@ 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); } } - private static void ExecuteModuleInitializer(Assembly assembly, Type[] assemblyTypes, bool isModuleLoad) + private static void ExecuteModuleInitializer(Assembly assembly, IEnumerable assemblyTypes) { - for (int i = 0; i < assemblyTypes.Length; i++) + foreach (Type type in assemblyTypes) { - Type type = assemblyTypes[i]; - TypeInfo typeInfo = type.GetTypeInfo(); - if (!(typeInfo.IsPublic || typeInfo.IsNestedPublic) || typeInfo.IsAbstract) { continue; } - - if (isModuleLoad && typeof(IModuleAssemblyInitializer).IsAssignableFrom(type) && type != typeof(IModuleAssemblyInitializer)) + if (typeof(IModuleAssemblyInitializer).IsAssignableFrom(type)) { s_assembliesWithModuleInitializerCache.Value[assembly] = true; - IModuleAssemblyInitializer moduleInitializer = (IModuleAssemblyInitializer)Activator.CreateInstance(type, true); + var moduleInitializer = (IModuleAssemblyInitializer)Activator.CreateInstance(type, true); moduleInitializer.OnImport(); } } } - internal static Type[] GetAssemblyTypes(Assembly assembly, string name) + internal static IEnumerable GetAssemblyTypes(Assembly assembly, string name) { - Type[] assemblyTypes = null; - try { - var exportedTypes = assembly.ExportedTypes; - assemblyTypes = exportedTypes as Type[] ?? exportedTypes.ToArray(); + // Return types that are public, non-abstract, non-interface and non-valueType. + return assembly.ExportedTypes.Where(static t => !t.IsAbstract && !t.IsInterface && !t.IsValueType); } catch (ReflectionTypeLoadException e) { - string message; - - message = e.Message; - - message += "\nLoader Exceptions: \n"; - + string message = e.Message + "\nLoader Exceptions: \n"; if (e.LoaderExceptions != null) { foreach (Exception exception in e.LoaderExceptions) @@ -5500,106 +5578,105 @@ internal static Type[] GetAssemblyTypes(Assembly assembly, string name) } s_PSSnapInTracer.TraceError(message); - throw new PSSnapInException(name, message); } - return assemblyTypes; } // cmdletCache holds the list of cmdlets along with its aliases per each assembly. - private static Lazy>>>> s_cmdletCache = + private static readonly Lazy>>>> s_cmdletCache = new Lazy>>>>(); - private static Lazy>> s_providerCache = - new Lazy>>(); - // Using a ConcurrentDictionary for this so that we can avoid having a private lock variable. We use only the keys for checking. - private static Lazy> s_assembliesWithModuleInitializerCache = new Lazy>(); - - private static string GetCmdletName(CmdletAttribute cmdletAttribute) - { - string verb = cmdletAttribute.VerbName; - - string noun = cmdletAttribute.NounName; - return verb + "-" + noun; - } + private static readonly Lazy>> s_providerCache = + new Lazy>>(); - private static string GetProviderName(CmdletProviderAttribute providerAttribute) - { - return providerAttribute.ProviderName; - } + // Using a ConcurrentDictionary for this so that we can avoid having a private lock variable. We use only the keys for checking. + private static readonly Lazy> s_assembliesWithModuleInitializerCache = new Lazy>(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsCmdletClass(Type type) { - if (type == null) - return false; - return type.IsSubclassOf(typeof(System.Management.Automation.Cmdlet)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsProviderClass(Type type) { - if (type == null) - return false; - return type.IsSubclassOf(typeof(System.Management.Automation.Provider.CmdletProvider)); } - internal static bool IsModuleAssemblyInitializerClass(Type type) - { - if (type == null) - { - return false; - } - - return type.IsSubclassOf(typeof(System.Management.Automation.IModuleAssemblyInitializer)); - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasDefaultConstructor(Type type) { - return !(type.GetConstructor(PSTypeExtensions.EmptyTypes) == null); + return type.GetConstructor(Type.EmptyTypes) is not null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetHelpFile(string assemblyPath) { // Help files exist only for original module assemblies, not for generated Ngen binaries 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} + [EventSource(Name = "Microsoft-PowerShell-Runspaces")] internal class RunspaceEventSource : EventSource { - internal static RunspaceEventSource Log = new RunspaceEventSource(); + internal static readonly RunspaceEventSource Log = new RunspaceEventSource(); public void OpenRunspaceStart() { WriteEvent(1); } + public void OpenRunspaceStop() { WriteEvent(2); } + public void LoadAssembliesStart() { WriteEvent(3); } + public void LoadAssembliesStop() { WriteEvent(4); } + public void UpdateFormatTableStart() { WriteEvent(5); } + public void UpdateFormatTableStop() { WriteEvent(6); } + public void UpdateTypeTableStart() { WriteEvent(7); } + public void UpdateTypeTableStop() { WriteEvent(8); } + public void LoadProvidersStart() { WriteEvent(9); } + public void LoadProvidersStop() { WriteEvent(10); } + public void LoadCommandsStart() { WriteEvent(11); } + public void LoadCommandsStop() { WriteEvent(12); } + public void LoadVariablesStart() { WriteEvent(13); } + public void LoadVariablesStop() { WriteEvent(14); } + public void LoadEnvironmentVariablesStart() { WriteEvent(15); } + public void LoadEnvironmentVariablesStop() { WriteEvent(16); } public void LoadAssemblyStart(string Name, string FileName) { WriteEvent(17, Name, FileName); } + public void LoadAssemblyStop(string Name, string FileName) { WriteEvent(18, Name, FileName); } + public void ProcessFormatFileStart(string FileName) { WriteEvent(19, FileName); } + public void ProcessFormatFileStop(string FileName) { WriteEvent(20, FileName); } + public void ProcessTypeFileStart(string FileName) { WriteEvent(21, FileName); } + public void ProcessTypeFileStop(string FileName) { WriteEvent(22, FileName); } + public void LoadProviderStart(string Name) { WriteEvent(23, Name); } + public void LoadProviderStop(string Name) { WriteEvent(24, Name); } + public void LoadCommandStart(string Name) { WriteEvent(25, Name); } + public void LoadCommandStop(string Name) { WriteEvent(26, Name); } } } diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 40a1804f4a6..4e50e2d130f 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -1,231 +1,629 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Text; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using System.Globalization; +using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Language; -using System.Diagnostics.CodeAnalysis; +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 { + /// + /// A thin wrapper over a property-getting Callsite, to allow reuse when possible. + /// + internal struct DynamicPropertyGetter + { + private CallSite> _getValueDynamicSite; + + // For the wildcard case, lets us know if we can reuse the callsite: + private string _lastUsedPropertyName; + + public object GetValue(PSObject inputObject, string propertyName) + { + Dbg.Assert(!WildcardPattern.ContainsWildcardCharacters(propertyName), "propertyName should be pre-resolved by caller"); + + // If wildcards are involved, the resolved property name could potentially + // be different on every object... but probably not, so we'll attempt to + // reuse the callsite if possible. + if (!propertyName.Equals(_lastUsedPropertyName, StringComparison.OrdinalIgnoreCase)) + { + _lastUsedPropertyName = propertyName; + _getValueDynamicSite = CallSite>.Create( + PSGetMemberBinder.Get( + propertyName, + classScope: (Type)null, + @static: false)); + } + + return _getValueDynamicSite.Target.Invoke(_getValueDynamicSite, inputObject); + } + } + #region Built-in cmdlets that are used by or require direct access to the engine. /// /// Implements a cmdlet that applies a script block /// to each element of the pipeline. /// - [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] - [Cmdlet("ForEach", "Object", SupportsShouldProcess = true, DefaultParameterSetName = "ScriptBlockSet", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113300", RemotingCapability = RemotingCapability.None)] - public sealed class ForEachObjectCommand : PSCmdlet + [Cmdlet("ForEach", "Object", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + DefaultParameterSetName = ForEachObjectCommand.ScriptBlockSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096867", + RemotingCapability = RemotingCapability.None)] + public sealed class ForEachObjectCommand : PSCmdlet, IDisposable { + #region Private Members + + private const string ParallelParameterSet = "ParallelParameterSet"; + private const string ScriptBlockSet = "ScriptBlockSet"; + private const string PropertyAndMethodSet = "PropertyAndMethodSet"; + + #endregion + + #region Common Parameters + /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// - [Parameter(ValueFromPipeline = true, ParameterSetName = "ScriptBlockSet")] - [Parameter(ValueFromPipeline = true, ParameterSetName = "PropertyAndMethodSet")] + [Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.ScriptBlockSet)] + [Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet)] + [Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] public PSObject InputObject { - set { _inputObject = value; } get { return _inputObject; } + + set { _inputObject = value; } } + private PSObject _inputObject = AutomationNull.Value; + #endregion + #region ScriptBlockSet - private List _scripts = new List(); + private readonly List _scripts = new List(); /// - /// The script block to apply in begin processing + /// Gets or sets the script block to apply in begin processing. /// - [Parameter(ParameterSetName = "ScriptBlockSet")] + [Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet)] public ScriptBlock Begin { - set - { - _scripts.Insert(0, value); - } get { return null; } + + set + { + _scripts.Insert(0, value); + } } /// - /// The script block to apply + /// Gets or sets the script block to apply. /// - [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ScriptBlockSet")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ForEachObjectCommand.ScriptBlockSet)] [AllowNull] [AllowEmptyCollection] public ScriptBlock[] Process { + get + { + return null; + } + set { if (value == null) + { _scripts.Add(null); + } else + { _scripts.AddRange(value); - } - get - { - return null; + } } } private ScriptBlock _endScript; private bool _setEndScript; + /// - /// The script block to apply in complete processing + /// Gets or sets the script block to apply in complete processing. /// - [Parameter(ParameterSetName = "ScriptBlockSet")] + [Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet)] public ScriptBlock End { + get + { + return _endScript; + } + set { _endScript = value; _setEndScript = true; } - get - { - return _endScript; - } } /// - /// The remaining script blocks to apply + /// Gets or sets the remaining script blocks to apply. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - [Parameter(ParameterSetName = "ScriptBlockSet", ValueFromRemainingArguments = true)] + [Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet, ValueFromRemainingArguments = true)] [AllowNull] [AllowEmptyCollection] public ScriptBlock[] RemainingScripts { + get + { + return null; + } + set { if (value == null) + { _scripts.Add(null); + } else + { _scripts.AddRange(value); + } } - get { return null; } } private int _start, _end; #endregion ScriptBlockSet - #region PropertyAndMethodSet /// - /// The property or method name + /// Gets or sets the property or method name. /// - [Parameter(Mandatory = true, Position = 0, ParameterSetName = "PropertyAndMethodSet")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet)] + [ValidateTrustedData] [ValidateNotNullOrEmpty] public string MemberName { - set { _propertyOrMethodName = value; } - get { return _propertyOrMethodName; } + get + { + return _propertyOrMethodName; + } + + set + { + _propertyOrMethodName = value; + } } + private string _propertyOrMethodName; private string _targetString; + private DynamicPropertyGetter _propGetter; /// - /// The arguments passed to a method invocation + /// The arguments passed to a method invocation. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - [Parameter(ParameterSetName = "PropertyAndMethodSet", ValueFromRemainingArguments = true)] + [Parameter(ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet, ValueFromRemainingArguments = true)] + [ValidateTrustedData] [Alias("Args")] public object[] ArgumentList { - set { _arguments = value; } get { return _arguments; } + + set { _arguments = value; } } + private object[] _arguments; #endregion PropertyAndMethodSet + #region ParallelParameterSet + + /// + /// Gets or sets a script block to run in parallel for each pipeline object. + /// + [Parameter(Mandatory = true, ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] + public ScriptBlock Parallel { get; set; } + + /// + /// Gets or sets the maximum number of concurrently running scriptblocks on separate threads. + /// The default number is 5. + /// + [Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] + [ValidateRange(1, Int32.MaxValue)] + public int ThrottleLimit { get; set; } = 5; + + /// + /// Gets or sets a timeout time in seconds, after which the parallel running scripts will be stopped + /// The default value is 0, indicating no timeout. + /// + [Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] + [ValidateRange(0, (Int32.MaxValue / 1000))] + public int TimeoutSeconds { get; set; } + + /// + /// Gets or sets a flag that returns a job object immediately for the parallel operation, instead of returning after + /// all foreach processing is completed. + /// + [Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] + public SwitchParameter AsJob { get; set; } + + /// + /// Gets or sets a flag so that a new runspace object is created for each loop iteration, instead of reusing objects + /// from the runspace pool. + /// By default, runspaces are reused from a runspace pool. + /// + [Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] + public SwitchParameter UseNewRunspace { get; set; } + + #endregion + + #region Overrides /// - /// Execute the begin scriptblock at the start of processing + /// Execute the begin scriptblock at the start of processing. /// - /// could not parse script - /// see Pipeline.Invoke - /// see Pipeline.Invoke + /// Could not parse script. + /// See Pipeline.Invoke. + /// See Pipeline.Invoke. protected override void BeginProcessing() { - Dbg.Assert(ParameterSetName == "ScriptBlockSet" || ParameterSetName == "PropertyAndMethodSet", "ParameterSetName is neither 'ScriptBlockSet' nor 'PropertyAndMethodSet'"); + switch (ParameterSetName) + { + case ForEachObjectCommand.ScriptBlockSet: + InitScriptBlockParameterSet(); + break; - if (ParameterSetName != "ScriptBlockSet") return; + case ForEachObjectCommand.ParallelParameterSet: + InitParallelParameterSet(); + break; + } + } - // Win8: 176403: ScriptCmdlets sets the global WhatIf and Confirm preferences - // This effects the new W8 foreach-object cmdlet with -whatif and -confirm - // implemented. -whatif and -confirm needed only for PropertyAndMethodSet - // parameter set. So erring out in cases where these are used with ScriptBlockSet. - // Not using MshCommandRuntime, as those variables will be affected by ScriptCmdlet - // infrastructure (wherein ScriptCmdlet modifies the global preferences). - Dictionary psBoundParameters = this.MyInvocation.BoundParameters; - if (psBoundParameters != null) + /// + /// Execute the processing script blocks on the current pipeline object + /// which is passed as it's only parameter. + /// + /// Could not parse script. + /// See Pipeline.Invoke. + /// See Pipeline.Invoke. + protected override void ProcessRecord() + { + switch (ParameterSetName) { - SwitchParameter whatIf = false; - SwitchParameter confirm = false; + case ForEachObjectCommand.ScriptBlockSet: + ProcessScriptBlockParameterSet(); + break; - object argument; - if (psBoundParameters.TryGetValue("whatif", out argument)) + case ForEachObjectCommand.PropertyAndMethodSet: + ProcessPropertyAndMethodParameterSet(); + break; + + case ForEachObjectCommand.ParallelParameterSet: + ProcessParallelParameterSet(); + break; + } + } + + /// + /// Execute the end scriptblock when the pipeline is complete. + /// + /// Could not parse script. + /// See Pipeline.Invoke. + /// See Pipeline.Invoke. + protected override void EndProcessing() + { + switch (ParameterSetName) + { + case ForEachObjectCommand.ScriptBlockSet: + EndBlockParameterSet(); + break; + + case ForEachObjectCommand.ParallelParameterSet: + EndParallelParameterSet(); + break; + } + } + + /// + /// Handle pipeline stop signal. + /// + protected override void StopProcessing() + { + switch (ParameterSetName) + { + case ForEachObjectCommand.ParallelParameterSet: + StopParallelProcessing(); + break; + } + } + + #endregion + + #region IDisposable + + /// + /// Dispose cmdlet instance. + /// + public void Dispose() + { + // Ensure all parallel task objects are disposed + _taskTimer?.Dispose(); + _taskDataStreamWriter?.Dispose(); + _taskPool?.Dispose(); + _taskCollection?.Dispose(); + } + + #endregion + + #region Private Methods + + #region PSTasks + + private PSTaskPool _taskPool; + private PSTaskDataStreamWriter _taskDataStreamWriter; + private Dictionary _usingValuesMap; + private Timer _taskTimer; + private PSTaskJob _taskJob; + private PSDataCollection _taskCollection; + private Exception _taskCollectionException; + private string _currentLocationPath; + + private void InitParallelParameterSet() + { + // The following common parameters are not (yet) supported in this parameter set. + // 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( + new ErrorRecord( + new PSNotSupportedException(InternalCommandStrings.ParallelCommonParametersNotSupported), + "ParallelCommonParametersNotSupported", + ErrorCategory.NotImplemented, + this)); + } + + // Get the current working directory location, if available. + try + { + _currentLocationPath = SessionState.Internal.CurrentLocation.Path; + } + catch (PSInvalidOperationException) + { + } + + 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 or PSObject { BaseObject: ScriptBlock }) { - whatIf = (SwitchParameter)argument; + ThrowTerminatingError( + new ErrorRecord( + new PSArgumentException(InternalCommandStrings.ParallelUsingVariableCannotBeScriptBlock), + "ParallelUsingVariableCannotBeScriptBlock", + ErrorCategory.InvalidType, + this)); } + } - if (psBoundParameters.TryGetValue("confirm", out argument)) + if (AsJob) + { + // Set up for returning a job object. + if (MyInvocation.BoundParameters.ContainsKey(nameof(TimeoutSeconds))) { - confirm = (SwitchParameter)argument; + ThrowTerminatingError( + new ErrorRecord( + new PSArgumentException(InternalCommandStrings.ParallelCannotUseTimeoutWithJob), + "ParallelCannotUseTimeoutWithJob", + ErrorCategory.InvalidOperation, + this)); } - if (whatIf || confirm) + _taskJob = new PSTaskJob( + Parallel.ToString(), + ThrottleLimit, + UseNewRunspace); + + return; + } + + // Set up for synchronous processing and data streaming. + _taskCollection = new PSDataCollection(); + _taskDataStreamWriter = new PSTaskDataStreamWriter(this); + _taskPool = new PSTaskPool(ThrottleLimit, UseNewRunspace); + _taskPool.PoolComplete += (sender, args) => _taskDataStreamWriter.Close(); + + // Create timeout timer if requested. + if (TimeoutSeconds != 0) + { + _taskTimer = new Timer( + callback: (_) => { _taskCollection.Complete(); _taskPool.StopAll(); }, + state: null, + dueTime: TimeoutSeconds * 1000, + period: Timeout.Infinite); + } + + // Task collection handler. + System.Threading.ThreadPool.QueueUserWorkItem( + (_) => { - string message = InternalCommandStrings.NoShouldProcessForScriptBlockSet; + // As piped input are converted to PSTasks and added to the _taskCollection, + // transfer the task to the _taskPool on this dedicated thread. + // The _taskPool will block this thread when it is full, and allow more tasks to + // be added only when a currently running task completes and makes space in the pool. + // Continue adding any tasks appearing in _taskCollection until the collection is closed. + while (true) + { + // This handle will unblock the thread when a new task is available or the _taskCollection + // is closed. + _taskCollection.WaitHandle.WaitOne(); - ErrorRecord errorRecord = new ErrorRecord( - new InvalidOperationException(message), - "NoShouldProcessForScriptBlockSet", - ErrorCategory.InvalidOperation, - null); - ThrowTerminatingError(errorRecord); - } + // Task collection open state is volatile. + // Record current task collection open state here, to be checked after processing. + bool isOpen = _taskCollection.IsOpen; + + try + { + // Read all tasks in the collection. + foreach (var task in _taskCollection.ReadAll()) + { + // This _taskPool method will block if the pool is full and will unblock + // only after a task completes making more space. + _taskPool.Add(task); + } + } + catch (Exception ex) + { + _taskCollection.Complete(); + _taskCollectionException = ex; + _taskDataStreamWriter.Close(); + + break; + } + + // Loop is exited only when task collection is closed and all task + // collection tasks are processed. + if (!isOpen) + { + break; + } + } + + // We are done adding tasks and can close the task pool. + _taskPool.Close(); + }); + } + + private void ProcessParallelParameterSet() + { + // Validate piped InputObject + if (_inputObject != null && + _inputObject.BaseObject is ScriptBlock) + { + WriteError( + new ErrorRecord( + new PSArgumentException(InternalCommandStrings.ParallelPipedInputObjectCannotBeScriptBlock), + "ParallelPipedInputObjectCannotBeScriptBlock", + ErrorCategory.InvalidType, + this)); + + return; } - // Calculate the start and end indexes for the processRecord script blocks - _end = _scripts.Count; - _start = _scripts.Count > 1 ? 1 : 0; + if (AsJob) + { + // Add child task job. + var taskChildJob = new PSTaskChildJob( + Parallel, + _usingValuesMap, + InputObject, + _currentLocationPath); - // and set the end script if it wasn't explicitly set with a named parameter. - if (!_setEndScript) + _taskJob.AddJob(taskChildJob); + + return; + } + + // Write any streaming data + _taskDataStreamWriter.WriteImmediate(); + + // Add to task collection for processing. + if (_taskCollection.IsOpen) { - if (_scripts.Count > 2) + try { - _end = _scripts.Count - 1; - _endScript = _scripts[_end]; + // Create a PSTask based on this piped input and add it to the task collection. + // A dedicated thread will add it to the PSTask pool in a performant manner. + _taskCollection.Add( + new System.Management.Automation.PSTasks.PSTask( + Parallel, + _usingValuesMap, + InputObject, + _currentLocationPath, + _taskDataStreamWriter)); + } + catch (InvalidOperationException) + { + // This exception is thrown if the task collection is closed, which should not happen. + Dbg.Assert(false, "Should not add to a closed PSTask collection"); } } + } + + private void EndParallelParameterSet() + { + if (AsJob) + { + // Start and return parent job object. + _taskJob.Start(); + JobRepository.Add(_taskJob); + WriteObject(_taskJob); - // only process the start script if there is more than one script... - if (_end < 2) return; + } - if (_scripts[0] == null) + // Close task collection and wait for processing to complete while streaming data. + _taskDataStreamWriter.WriteImmediate(); + _taskCollection.Complete(); + _taskDataStreamWriter.WaitAndWrite(); + + // Check for an unexpected error from the _taskCollection handler thread and report here. + var ex = _taskCollectionException; + if (ex != null) + { + var msg = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ParallelPipedInputProcessingError, ex); + WriteError( + new ErrorRecord( + exception: new InvalidOperationException(msg), + errorId: "ParallelPipedInputProcessingError", + errorCategory: ErrorCategory.InvalidOperation, + targetObject: this)); + } + } + + private void StopParallelProcessing() + { + _taskCollection?.Complete(); + _taskPool?.StopAll(); + } + + #endregion + + private void EndBlockParameterSet() + { + if (_endScript == null) + { return; + } - var emptyArray = Utils.EmptyArray(); - _scripts[0].InvokeUsingCmdlet( + var emptyArray = Array.Empty(); + _endScript.InvokeUsingCmdlet( contextCmdlet: this, useLocalScope: false, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, @@ -235,264 +633,388 @@ protected override void BeginProcessing() args: emptyArray); } - /// - /// Execute the processing script blocks on the current pipeline object - /// which is passed as it's only parameter. - /// - /// could not parse script - /// see Pipeline.Invoke - /// see Pipeline.Invoke - protected override void ProcessRecord() + private void ProcessPropertyAndMethodParameterSet() { - Dbg.Assert(ParameterSetName == "ScriptBlockSet" || ParameterSetName == "PropertyAndMethodSet", "ParameterSetName is neither 'ScriptBlockSet' nor 'PropertyAndMethodSet'"); + _targetString = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ForEachObjectTarget, GetStringRepresentation(InputObject)); - switch (ParameterSetName) + if (LanguagePrimitives.IsNull(InputObject)) { - case "ScriptBlockSet": - for (int i = _start; i < _end; i++) + if (_arguments != null && _arguments.Length > 0) + { + WriteError(GenerateNameParameterError("InputObject", ParserStrings.InvokeMethodOnNull, + "InvokeMethodOnNull", _inputObject)); + } + else + { + // should process + string propertyAction = string.Format(CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName); + + if (ShouldProcess(_targetString, propertyAction)) { - // Only execute scripts that aren't null. This isn't treated as an error - // 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) + if (Context.IsStrictVersion(2)) + { + WriteError(GenerateNameParameterError("InputObject", InternalCommandStrings.InputObjectIsNull, + "InputObjectIsNull", _inputObject)); + } + else { - _scripts[i].InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: false, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: InputObject, - input: new object[] { InputObject }, - scriptThis: AutomationNull.Value, - args: Utils.EmptyArray()); + // we write null out because: + // PS C:\> $null | ForEach-object {$_.aa} | ForEach-Object {$_ + 3} + // 3 + // so we also want + // PS C:\> $null | ForEach-object aa | ForEach-Object {$_ + 3} + // 3 + // But if we don't write anything to the pipeline when _inputObject is null, + // the result 3 will not be generated. + WriteObject(null); } } - break; - case "PropertyAndMethodSet": + } - _targetString = String.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ForEachObjectTarget, GetStringRepresentation(InputObject)); + return; + } - if (LanguagePrimitives.IsNull(InputObject)) + ErrorRecord errorRecord = null; + + // if args exist, this is explicitly a method invocation + if (_arguments != null && _arguments.Length > 0) + { + MethodCallWithArguments(); + } + // no arg provided + else + { + // if inputObject is of IDictionary, get the value + if (GetValueFromIDictionaryInput()) + { + return; + } + + PSMemberInfo member = null; + if (WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName)) + { + // get the matched member(s) + ReadOnlyPSMemberInfoCollection members = + _inputObject.Members.Match(_propertyOrMethodName, PSMemberTypes.All); + Dbg.Assert(members != null, "The return value of Members.Match should never be null"); + + if (members.Count > 1) { - if (_arguments != null && _arguments.Length > 0) + // write error record: property method ambiguous + StringBuilder possibleMatches = new StringBuilder(); + foreach (PSMemberInfo item in members) { - WriteError(GenerateNameParameterError("InputObject", ParserStrings.InvokeMethodOnNull, - "InvokeMethodOnNull", _inputObject)); + possibleMatches.Append(CultureInfo.InvariantCulture, $" {item.Name}"); } - else - { - // should process - string propertyAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName); - if (ShouldProcess(_targetString, propertyAction)) - { - if (Context.IsStrictVersion(2)) - { - WriteError(GenerateNameParameterError("InputObject", InternalCommandStrings.InputObjectIsNull, - "InputObjectIsNull", _inputObject)); - } - else - { - // we write null out because: - // PS C:\> $null | ForEach-object {$_.aa} | ForEach-Object {$_ + 3} - // 3 - // so we also want - // PS C:\> $null | ForEach-object aa | ForEach-Object {$_ + 3} - // 3 - // But if we don't write anything to the pipeline when _inputObject is null, - // the result 3 will not be generated. - WriteObject(null); - } - } - } + WriteError(GenerateNameParameterError("Name", InternalCommandStrings.AmbiguousPropertyOrMethodName, + "AmbiguousPropertyOrMethodName", _inputObject, + _propertyOrMethodName, possibleMatches)); return; } - ErrorRecord errorRecord = null; - - // if args exist, this is explicitly a method invocation - if (_arguments != null && _arguments.Length > 0) + if (members.Count == 1) { - MethodCallWithArguments(); + member = members[0]; } - // no arg provided - else + } + else + { + member = _inputObject.Members[_propertyOrMethodName]; + } + + // member is a method + if (member is PSMethodInfo) + { + // first we check if the member is a ParameterizedProperty + PSParameterizedProperty targetParameterizedProperty = member as PSParameterizedProperty; + if (targetParameterizedProperty != null) { - // if inputObject is of IDictionary, get the value - if (GetValueFromIDictionaryInput()) { return; } + // should process + string propertyAction = string.Format(CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectPropertyAction, targetParameterizedProperty.Name); - PSMemberInfo member = null; - if (WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName)) + // ParameterizedProperty always take parameters, so we output the member.Value directly + if (ShouldProcess(_targetString, propertyAction)) { - // get the matched member(s) - ReadOnlyPSMemberInfoCollection members = - _inputObject.Members.Match(_propertyOrMethodName, PSMemberTypes.All); - Dbg.Assert(members != null, "The return value of Members.Match should never be null"); + WriteObject(member.Value); + } - if (members.Count > 1) - { - // write error record: property method ambiguous - StringBuilder possibleMatches = new StringBuilder(); - foreach (PSMemberInfo item in members) - { - possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name); - } + return; + } - WriteError(GenerateNameParameterError("Name", InternalCommandStrings.AmbiguousPropertyOrMethodName, - "AmbiguousPropertyOrMethodName", _inputObject, - _propertyOrMethodName, possibleMatches)); - return; - } - if (members.Count == 1) + PSMethodInfo targetMethod = member as PSMethodInfo; + Dbg.Assert(targetMethod != null, "targetMethod should not be null here."); + try + { + // should process + string methodAction = string.Format(CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectMethodActionWithoutArguments, targetMethod.Name); + + if (ShouldProcess(_targetString, methodAction)) + { + if (!BlockMethodInLanguageMode(InputObject)) { - member = members[0]; + object result = targetMethod.Invoke(Array.Empty()); + WriteToPipelineWithUnrolling(result); } } + } + catch (PipelineStoppedException) + { + // PipelineStoppedException can be caused by select-object + throw; + } + catch (Exception ex) + { + MethodException mex = ex as MethodException; + if (mex != null && mex.ErrorRecord != null && mex.ErrorRecord.FullyQualifiedErrorId == "MethodCountCouldNotFindBest") + { + WriteObject(targetMethod.Value); + } else { - member = _inputObject.Members[_propertyOrMethodName]; + WriteError(new ErrorRecord(ex, "MethodInvocationError", ErrorCategory.InvalidOperation, _inputObject)); } - - if (member == null) + } + } + else + { + string resolvedPropertyName = null; + bool isBlindDynamicAccess = false; + if (member == null) + { + if ((_inputObject.BaseObject is IDynamicMetaObjectProvider) && + !WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName)) + { + // Let's just try a dynamic property access. Note that if it + // comes to depending on dynamic access, we are assuming it is a + // property; we don't have ETS info to tell us up front if it + // even exists or not, let alone if it is a method or something + // else. + // + // Note that this is "truly blind"--the name did not show up in + // GetDynamicMemberNames(), else it would show up as a dynamic + // member. + + resolvedPropertyName = _propertyOrMethodName; + isBlindDynamicAccess = true; + } + else { errorRecord = GenerateNameParameterError("Name", InternalCommandStrings.PropertyOrMethodNotFound, "PropertyOrMethodNotFound", _inputObject, _propertyOrMethodName); } - else + } + else + { + // member is [presumably] a property (note that it could be a + // dynamic property, if it shows up in GetDynamicMemberNames()) + resolvedPropertyName = member.Name; + } + + if (!string.IsNullOrEmpty(resolvedPropertyName)) + { + // should process + string propertyAction = string.Format(CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectPropertyAction, resolvedPropertyName); + + if (ShouldProcess(_targetString, propertyAction)) { - // member is a method - if (member is PSMethodInfo) + try { - // first we check if the member is a ParameterizedProperty - PSParameterizedProperty targetParameterizedProperty = member as PSParameterizedProperty; - if (targetParameterizedProperty != null) - { - // should process - string propertyAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectPropertyAction, targetParameterizedProperty.Name); - - // ParameterizedProperty always take parameters, so we output the member.Value directly - if (ShouldProcess(_targetString, propertyAction)) - { - WriteObject(member.Value); - } - return; - } - - PSMethodInfo targetMethod = member as PSMethodInfo; - Dbg.Assert(targetMethod != null, "targetMethod should not be null here."); - try - { - // should process - string methodAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectMethodActionWithoutArguments, targetMethod.Name); - - if (ShouldProcess(_targetString, methodAction)) - { - if (!BlockMethodInLanguageMode(InputObject)) - { - object result = targetMethod.Invoke(Utils.EmptyArray()); - WriteToPipelineWithUnrolling(result); - } - } - } - catch (PipelineStoppedException) + WriteToPipelineWithUnrolling(_propGetter.GetValue(InputObject, resolvedPropertyName)); + } + catch (TerminateException) // The debugger is terminating the execution + { + throw; + } + catch (MethodException) + { + throw; + } + catch (PipelineStoppedException) + { + // PipelineStoppedException can be caused by select-object + throw; + } + catch (Exception ex) + { + // For normal property accesses, we do not generate an error + // here. The problem for truly blind dynamic accesses (the + // member did not show up in GetDynamicMemberNames) is that + // we can't tell the difference between "it failed because + // the property does not exist" (let's call this case 1) and + // "it failed because accessing it actually threw some + // exception" (let's call that case 2). + // + // PowerShell behavior for normal (non-dynamic) properties + // is different for these two cases: case 1 gets an error + // (which is possible because the ETS tells us up front if + // the property exists or not), and case 2 does not. (For + // normal properties, this catch block /is/ case 2.) + // + // For IDMOPs, we have the chance to attempt a "blind" + // access, but the cost is that we must have the same + // response to both cases (because we cannot distinguish + // between the two). So we have to make a choice: we can + // either swallow ALL errors (including "The property + // 'Blarg' does not exist"), or expose them all. + // + // Here, for truly blind dynamic access, we choose to + // preserve the behavior of showing "The property 'Blarg' + // does not exist" (case 1) errors than to suppress + // "FooException thrown when accessing Bloop property" (case + // 2) errors. + + if (isBlindDynamicAccess) { - // PipelineStoppedException can be caused by select-object - throw; + errorRecord = new ErrorRecord(ex, + "DynamicPropertyAccessFailed_" + _propertyOrMethodName, + ErrorCategory.InvalidOperation, + InputObject); } - catch (Exception ex) + else { - MethodException mex = ex as MethodException; - if (mex != null && mex.ErrorRecord != null && mex.ErrorRecord.FullyQualifiedErrorId == "MethodCountCouldNotFindBest") - { - WriteObject(targetMethod.Value); - } - else - { - WriteError(new ErrorRecord(ex, "MethodInvocationError", ErrorCategory.InvalidOperation, _inputObject)); - } + // When the property is not gettable or it throws an exception. + // e.g. when trying to access an assembly's location property, since dynamic assemblies are not backed up by a file, + // an exception will be thrown when accessing its location property. In this case, return null. + WriteObject(null); } } - // member is a property - else - { - // should process - string propertyAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectPropertyAction, member.Name); + } + } + } + } + + if (errorRecord != null) + { + string propertyAction = string.Format(CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName); + + if (ShouldProcess(_targetString, propertyAction)) + { + if (Context.IsStrictVersion(2)) + { + WriteError(errorRecord); + } + else + { + // we write null out because: + // PS C:\> "string" | ForEach-Object {$_.aa} | ForEach-Object {$_ + 3} + // 3 + // so we also want + // PS C:\> "string" | ForEach-Object aa | ForEach-Object {$_ + 3} + // 3 + // But if we don't write anything to the pipeline when no member is found, + // the result 3 will not be generated. + WriteObject(null); + } + } + } + } + + private void ProcessScriptBlockParameterSet() + { + for (int i = _start; i < _end; i++) + { + // Only execute scripts that aren't null. This isn't treated as an error + // 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... + _scripts[i]?.InvokeUsingCmdlet( + contextCmdlet: this, + useLocalScope: false, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: InputObject, + input: new object[] { InputObject }, + scriptThis: AutomationNull.Value, + args: Array.Empty()); + } + } + + private void InitScriptBlockParameterSet() + { + // Win8: 176403: ScriptCmdlets sets the global WhatIf and Confirm preferences + // This effects the new W8 foreach-object cmdlet with -whatif and -confirm + // implemented. -whatif and -confirm needed only for PropertyAndMethodSet + // parameter set. So erring out in cases where these are used with ScriptBlockSet. + // Not using MshCommandRuntime, as those variables will be affected by ScriptCmdlet + // infrastructure (wherein ScriptCmdlet modifies the global preferences). + Dictionary psBoundParameters = this.MyInvocation.BoundParameters; + if (psBoundParameters != null) + { + SwitchParameter whatIf = false; + SwitchParameter confirm = false; + + object argument; + if (psBoundParameters.TryGetValue("whatif", out argument)) + { + whatIf = (SwitchParameter)argument; + } - if (ShouldProcess(_targetString, propertyAction)) - { - try - { - WriteToPipelineWithUnrolling(member.Value); - } - catch (TerminateException) // The debugger is terminating the execution - { - throw; - } - catch (MethodException) - { - throw; - } - catch (PipelineStoppedException) - { - // PipelineStoppedException can be caused by select-object - throw; - } - catch (Exception) - { - // When the property is not gettable or it throws an exception. - // e.g. when trying to access an assembly's location property, since dynamic assemblies are not backed up by a file, - // an exception will be thrown when accessing its location property. In this case, return null. - WriteObject(null); - } - } - } // end of member is a property - } // member is not null - } // no args provided + if (psBoundParameters.TryGetValue("confirm", out argument)) + { + confirm = (SwitchParameter)argument; + } - if (errorRecord != null) - { - string propertyAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName); + if (whatIf || confirm) + { + string message = InternalCommandStrings.NoShouldProcessForScriptBlockSet; - if (ShouldProcess(_targetString, propertyAction)) - { - if (Context.IsStrictVersion(2)) - { - WriteError(errorRecord); - } - else - { - // we write null out because: - // PS C:\> "string" | ForEach-Object {$_.aa} | ForEach-Object {$_ + 3} - // 3 - // so we also want - // PS C:\> "string" | ForEach-Object aa | ForEach-Object {$_ + 3} - // 3 - // But if we don't write anything to the pipeline when no member is found, - // the result 3 will not be generated. - WriteObject(null); - } - } - } + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(message), + "NoShouldProcessForScriptBlockSet", + ErrorCategory.InvalidOperation, + null); + ThrowTerminatingError(errorRecord); + } + } - break; + // Calculate the start and end indexes for the processRecord script blocks + _end = _scripts.Count; + _start = _scripts.Count > 1 ? 1 : 0; + + // and set the end script if it wasn't explicitly set with a named parameter. + if (!_setEndScript) + { + if (_scripts.Count > 2) + { + _end = _scripts.Count - 1; + _endScript = _scripts[_end]; + } } + + // only process the start script if there is more than one script... + if (_end < 2) + return; + + if (_scripts[0] == null) + return; + + var emptyArray = Array.Empty(); + _scripts[0].InvokeUsingCmdlet( + contextCmdlet: this, + useLocalScope: false, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: AutomationNull.Value, + input: emptyArray, + scriptThis: AutomationNull.Value, + args: emptyArray); } /// - /// Do method invocation with arguments + /// Do method invocation with arguments. /// private void MethodCallWithArguments() { // resolve the name ReadOnlyPSMemberInfoCollection methods = - _inputObject.Members.Match(_propertyOrMethodName, - PSMemberTypes.Methods | PSMemberTypes.ParameterizedProperty); + _inputObject.Members.Match( + _propertyOrMethodName, + PSMemberTypes.Methods | PSMemberTypes.ParameterizedProperty); Dbg.Assert(methods != null, "The return value of Members.Match should never be null."); if (methods.Count > 1) @@ -501,17 +1023,26 @@ 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("Name", InternalCommandStrings.AmbiguousMethodName, - "AmbiguousMethodName", _inputObject, - _propertyOrMethodName, possibleMatches)); + + WriteError(GenerateNameParameterError( + "Name", + InternalCommandStrings.AmbiguousMethodName, + "AmbiguousMethodName", + _inputObject, + _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("Name", InternalCommandStrings.MethodNotFound, - "MethodNotFound", _inputObject, _propertyOrMethodName)); + WriteError(GenerateNameParameterError( + "Name", + InternalCommandStrings.MethodNotFound, + "MethodNotFound", + _inputObject, + _propertyOrMethodName)); } else { @@ -522,9 +1053,10 @@ 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, + + string methodAction = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ForEachObjectMethodActionWithArguments, targetMethod.Name, arglist); @@ -552,10 +1084,10 @@ private void MethodCallWithArguments() } /// - /// Get the string representation of the passed-in object + /// Get the string representation of the passed-in object. /// - /// - /// + /// Source object. + /// String representation of the source object. private static string GetStringRepresentation(object obj) { string objInString; @@ -569,7 +1101,7 @@ private static string GetStringRepresentation(object obj) objInString = null; } - if (String.IsNullOrEmpty(objInString)) + if (string.IsNullOrEmpty(objInString)) { var psobj = obj as PSObject; objInString = psobj != null ? psobj.BaseObject.GetType().FullName : obj.GetType().FullName; @@ -582,7 +1114,7 @@ private static string GetStringRepresentation(object obj) /// Get the value by taking _propertyOrMethodName as the key, if the /// input object is a IDictionary. /// - /// + /// True if success. private bool GetValueFromIDictionaryInput() { object target = PSObject.Base(_inputObject); @@ -592,13 +1124,16 @@ private bool GetValueFromIDictionaryInput() { if (hash != null && hash.Contains(_propertyOrMethodName)) { - string keyAction = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.ForEachObjectKeyAction, _propertyOrMethodName); + string keyAction = string.Format( + CultureInfo.InvariantCulture, + InternalCommandStrings.ForEachObjectKeyAction, + _propertyOrMethodName); if (ShouldProcess(_targetString, keyAction)) { object result = hash[_propertyOrMethodName]; WriteToPipelineWithUnrolling(result); } + return true; } } @@ -607,6 +1142,7 @@ private bool GetValueFromIDictionaryInput() // Ignore invalid operation exception, it can happen if the dictionary // has keys that can't be compared to property. } + return false; } @@ -614,7 +1150,7 @@ private bool GetValueFromIDictionaryInput() /// Unroll the object to be output. If it's of type IEnumerator, unroll and output it /// by calling WriteOutIEnumerator. If it's not, unroll and output it by calling WriteObject(obj, true) /// - /// + /// Source object. private void WriteToPipelineWithUnrolling(object obj) { IEnumerator objAsEnumerator = LanguagePrimitives.GetEnumerator(obj); @@ -631,7 +1167,7 @@ private void WriteToPipelineWithUnrolling(object obj) /// /// Unroll an IEnumerator and output all entries. /// - /// + /// Source list. private void WriteOutIEnumerator(IEnumerator list) { if (list != null) @@ -652,8 +1188,9 @@ private void WriteOutIEnumerator(IEnumerator list) /// Check if the language mode is the restrictedLanguageMode before invoking a method. /// Write out error message and return true if we are in restrictedLanguageMode. /// - /// - private bool BlockMethodInLanguageMode(Object inputObject) + /// Source object. + /// True if we are in restrictedLanguageMode. + private bool BlockMethodInLanguageMode(object inputObject) { // Cannot invoke a method in RestrictedLanguage mode if (Context.LanguageMode == PSLanguageMode.RestrictedLanguage) @@ -668,23 +1205,36 @@ private bool BlockMethodInLanguageMode(Object inputObject) // Cannot invoke certain methods in ConstrainedLanguage mode if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - Object baseObject = PSObject.Base(inputObject); + 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); } } return false; } + #endregion + /// - /// Generate the appropriate error record + /// Generate the appropriate error record. /// /// /// @@ -695,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 (null == args || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -705,7 +1255,7 @@ internal static ErrorRecord GenerateNameParameterError(string paraName, string r message = StringUtil.Format(resourceString, args); } - if (String.IsNullOrEmpty(message)) + if (string.IsNullOrEmpty(message)) { Dbg.Assert(false, "Could not load text for error record '" + errorId + "'"); } @@ -718,31 +1268,6 @@ internal static ErrorRecord GenerateNameParameterError(string paraName, string r return errorRecord; } - - - /// - /// Execute the end scriptblock when the pipeline is complete - /// - /// could not parse script - /// see Pipeline.Invoke - /// see Pipeline.Invoke - protected override void EndProcessing() - { - if (ParameterSetName != "ScriptBlockSet") return; - - if (_endScript == null) - return; - - var emptyArray = Utils.EmptyArray(); - _endScript.InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: false, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: emptyArray, - scriptThis: AutomationNull.Value, - args: emptyArray); - } } /// @@ -752,41 +1277,50 @@ protected override void EndProcessing() /// is passed on, otherwise it is dropped. /// [Cmdlet("Where", "Object", DefaultParameterSetName = "EqualSet", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113423", RemotingCapability = RemotingCapability.None)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096806", RemotingCapability = RemotingCapability.None)] public sealed class WhereObjectCommand : PSCmdlet { /// - /// This parameter specifies the current pipeline object + /// Gets or sets the current pipeline object. /// [Parameter(ValueFromPipeline = true)] public PSObject InputObject { - set { _inputObject = value; } - get { return _inputObject; } + get + { + return _inputObject; + } + + set + { + _inputObject = value; + } } + private PSObject _inputObject = AutomationNull.Value; private ScriptBlock _script; /// - /// The script block to apply + /// Gets or sets the script block to apply. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "ScriptBlockSet")] public ScriptBlock FilterScript { - set - { - _script = value; - } get { return _script; } - } + set + { + _script = value; + } + } private string _property; + /// - /// The property to retrieve value + /// Gets or sets the property to retrieve value. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "EqualSet")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveEqualSet")] @@ -818,19 +1352,27 @@ public ScriptBlock FilterScript [Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotInSet")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsSet")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsNotSet")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Not")] [ValidateNotNullOrEmpty] public string Property { - set { _property = value; } - get { return _property; } - } + get + { + return _property; + } + set + { + _property = value; + } + } private object _convertedValue; private object _value = true; private bool _valueNotSpecified = true; + /// - /// The value to compare against + /// The value to compare against. /// [Parameter(Position = 1, ParameterSetName = "EqualSet")] [Parameter(Position = 1, ParameterSetName = "CaseSensitiveEqualSet")] @@ -864,12 +1406,16 @@ public string Property [Parameter(Position = 1, ParameterSetName = "IsNotSet")] public object Value { + get + { + return _value; + } + set { _value = value; _valueNotSpecified = false; } - get { return _value; } } #region binary operator parameters @@ -881,334 +1427,646 @@ public object Value private bool _forceBooleanEvaluation = true; /// - /// Binary operator -Equal + /// Gets or sets binary operator -Equal /// It's the default parameter set, so -EQ is not mandatory. /// [Parameter(ParameterSetName = "EqualSet")] [Alias("IEQ")] public SwitchParameter EQ { + get + { + return _binaryOperator == TokenKind.Ieq; + } + set { - _binaryOperator = TokenKind.Ieq; - _forceBooleanEvaluation = false; + if (value) + { + _binaryOperator = TokenKind.Ieq; + _forceBooleanEvaluation = false; + } } - get { return _binaryOperator == TokenKind.Ieq; } } /// - /// Case sensitive binary operator -ceq + /// Gets or sets case sensitive binary operator -ceq. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveEqualSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CEQ")] public SwitchParameter CEQ { - set { _binaryOperator = TokenKind.Ceq; } - get { return _binaryOperator == TokenKind.Ceq; } + get + { + return _binaryOperator == TokenKind.Ceq; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ceq; + } + } } /// - /// Binary operator -NotEqual + /// Gets or sets binary operator -NotEqual. /// [Parameter(Mandatory = true, ParameterSetName = "NotEqualSet")] [Alias("INE")] public SwitchParameter NE { - set { _binaryOperator = TokenKind.Ine; } - get { return _binaryOperator == TokenKind.Ine; } + get + { + return _binaryOperator == TokenKind.Ine; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ine; + } + } } /// - /// Case sensitive binary operator -cne + /// Gets or sets case sensitive binary operator -cne. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotEqualSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CNE")] public SwitchParameter CNE { - set { _binaryOperator = TokenKind.Cne; } - get { return _binaryOperator == TokenKind.Cne; } + get + { + return _binaryOperator == TokenKind.Cne; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cne; + } + } } /// - /// Binary operator -GreaterThan + /// Gets or sets binary operator -GreaterThan. /// [Parameter(Mandatory = true, ParameterSetName = "GreaterThanSet")] [Alias("IGT")] public SwitchParameter GT { - set { _binaryOperator = TokenKind.Igt; } - get { return _binaryOperator == TokenKind.Igt; } + get + { + return _binaryOperator == TokenKind.Igt; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Igt; + } + } } /// - /// Case sensitive binary operator -cgt + /// Gets or sets case sensitive binary operator -cgt. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveGreaterThanSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CGT")] public SwitchParameter CGT { - set { _binaryOperator = TokenKind.Cgt; } - get { return _binaryOperator == TokenKind.Cgt; } + get + { + return _binaryOperator == TokenKind.Cgt; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cgt; + } + } } /// - /// Binary operator -LessThan + /// Gets or sets binary operator -LessThan. /// [Parameter(Mandatory = true, ParameterSetName = "LessThanSet")] [Alias("ILT")] public SwitchParameter LT { - set { _binaryOperator = _binaryOperator = TokenKind.Ilt; } - get { return _binaryOperator == TokenKind.Ilt; } + get + { + return _binaryOperator == TokenKind.Ilt; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ilt; + } + } } /// - /// Case sensitive binary operator -clt + /// Gets or sets case sensitive binary operator -clt. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessThanSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CLT")] public SwitchParameter CLT { - set { _binaryOperator = TokenKind.Clt; } - get { return _binaryOperator == TokenKind.Clt; } + get + { + return _binaryOperator == TokenKind.Clt; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Clt; + } + } } /// - /// Binary operator -GreaterOrEqual + /// Gets or sets binary operator -GreaterOrEqual. /// [Parameter(Mandatory = true, ParameterSetName = "GreaterOrEqualSet")] [Alias("IGE")] public SwitchParameter GE { - set { _binaryOperator = TokenKind.Ige; } - get { return _binaryOperator == TokenKind.Ige; } + get + { + return _binaryOperator == TokenKind.Ige; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ige; + } + } } /// - /// Case sensitive binary operator -cge + /// Gets or sets case sensitive binary operator -cge. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveGreaterOrEqualSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CGE")] public SwitchParameter CGE { - set { _binaryOperator = TokenKind.Cge; } - get { return _binaryOperator == TokenKind.Cge; } + get + { + return _binaryOperator == TokenKind.Cge; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cge; + } + } } /// - /// Binary operator -LessOrEqual + /// Gets or sets binary operator -LessOrEqual. /// [Parameter(Mandatory = true, ParameterSetName = "LessOrEqualSet")] [Alias("ILE")] public SwitchParameter LE { - set { _binaryOperator = TokenKind.Ile; } - get { return _binaryOperator == TokenKind.Ile; } + get + { + return _binaryOperator == TokenKind.Ile; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ile; + } + } } /// - /// Case sensitive binary operator -cle + /// Gets or sets case sensitive binary operator -cle. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessOrEqualSet")] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CLE")] public SwitchParameter CLE { - set { _binaryOperator = TokenKind.Cle; } - get { return _binaryOperator == TokenKind.Cle; } + get + { + return _binaryOperator == TokenKind.Cle; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cle; + } + } } /// - /// Binary operator -Like + ///Gets or sets binary operator -Like. /// [Parameter(Mandatory = true, ParameterSetName = "LikeSet")] [Alias("ILike")] public SwitchParameter Like { - set { _binaryOperator = TokenKind.Ilike; } - get { return _binaryOperator == TokenKind.Ilike; } + get + { + return _binaryOperator == TokenKind.Ilike; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ilike; + } + } } /// - /// Case sensitive binary operator -clike + /// Gets or sets case sensitive binary operator -clike. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLikeSet")] public SwitchParameter CLike { - set { _binaryOperator = TokenKind.Clike; } - get { return _binaryOperator == TokenKind.Clike; } + get + { + return _binaryOperator == TokenKind.Clike; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Clike; + } + } } /// - /// Binary operator -NotLike + /// Gets or sets binary operator -NotLike. /// [Parameter(Mandatory = true, ParameterSetName = "NotLikeSet")] [Alias("INotLike")] public SwitchParameter NotLike { - set { _binaryOperator = TokenKind.Inotlike; } - get { return false; } + get + { + return false; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Inotlike; + } + } } /// - /// Case sensitive binary operator -cnotlike + /// Gets or sets case sensitive binary operator -cnotlike. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotLikeSet")] public SwitchParameter CNotLike { - set { _binaryOperator = TokenKind.Cnotlike; } - get { return _binaryOperator == TokenKind.Cnotlike; } + get + { + return _binaryOperator == TokenKind.Cnotlike; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cnotlike; + } + } } /// - /// Binary operator -Match + /// Get or sets binary operator -Match. /// [Parameter(Mandatory = true, ParameterSetName = "MatchSet")] [Alias("IMatch")] public SwitchParameter Match { - set { _binaryOperator = TokenKind.Imatch; } - get { return _binaryOperator == TokenKind.Imatch; } + get + { + return _binaryOperator == TokenKind.Imatch; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Imatch; + } + } } /// - /// Case sensitive binary operator -cmatch + /// Gets or sets case sensitive binary operator -cmatch. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveMatchSet")] public SwitchParameter CMatch { - set { _binaryOperator = TokenKind.Cmatch; } - get { return _binaryOperator == TokenKind.Cmatch; } + get + { + return _binaryOperator == TokenKind.Cmatch; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cmatch; + } + } } /// - /// Binary operator -NotMatch + /// Gets or sets binary operator -NotMatch. /// [Parameter(Mandatory = true, ParameterSetName = "NotMatchSet")] [Alias("INotMatch")] public SwitchParameter NotMatch { - set { _binaryOperator = TokenKind.Inotmatch; } - get { return _binaryOperator == TokenKind.Inotmatch; } + get + { + return _binaryOperator == TokenKind.Inotmatch; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Inotmatch; + } + } } /// - /// Case sensitive binary operator -cnotmatch + /// Gets or sets case sensitive binary operator -cnotmatch. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotMatchSet")] public SwitchParameter CNotMatch { - set { _binaryOperator = TokenKind.Cnotmatch; } - get { return _binaryOperator == TokenKind.Cnotmatch; } + get + { + return _binaryOperator == TokenKind.Cnotmatch; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cnotmatch; + } + } } /// - /// Binary operator -Contains + /// Gets or sets binary operator -Contains. /// [Parameter(Mandatory = true, ParameterSetName = "ContainsSet")] [Alias("IContains")] public SwitchParameter Contains { - set { _binaryOperator = TokenKind.Icontains; } - get { return _binaryOperator == TokenKind.Icontains; } + get + { + return _binaryOperator == TokenKind.Icontains; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Icontains; + } + } } /// - /// Case sensitive binary operator -ccontains + /// Gets or sets case sensitive binary operator -ccontains. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveContainsSet")] public SwitchParameter CContains { - set { _binaryOperator = TokenKind.Ccontains; } - get { return _binaryOperator == TokenKind.Ccontains; } + get + { + return _binaryOperator == TokenKind.Ccontains; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Ccontains; + } + } } /// - /// Binary operator -NotContains + /// Gets or sets binary operator -NotContains. /// [Parameter(Mandatory = true, ParameterSetName = "NotContainsSet")] [Alias("INotContains")] public SwitchParameter NotContains { - set { _binaryOperator = TokenKind.Inotcontains; } - get { return _binaryOperator == TokenKind.Inotcontains; } + get + { + return _binaryOperator == TokenKind.Inotcontains; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Inotcontains; + } + } } /// - /// Case sensitive binary operator -cnotcontains + /// Gets or sets case sensitive binary operator -cnotcontains. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotContainsSet")] public SwitchParameter CNotContains { - set { _binaryOperator = TokenKind.Cnotcontains; } - get { return _binaryOperator == TokenKind.Cnotcontains; } + get + { + return _binaryOperator == TokenKind.Cnotcontains; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cnotcontains; + } + } } /// - /// Binary operator -In + /// Gets or sets binary operator -In. /// [Parameter(Mandatory = true, ParameterSetName = "InSet")] [Alias("IIn")] public SwitchParameter In { - set { _binaryOperator = TokenKind.In; } - get { return _binaryOperator == TokenKind.In; } + get + { + return _binaryOperator == TokenKind.In; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.In; + } + } } /// - /// Case sensitive binary operator -cin + /// Gets or sets case sensitive binary operator -cin. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveInSet")] public SwitchParameter CIn { - set { _binaryOperator = TokenKind.Cin; } - get { return _binaryOperator == TokenKind.Cin; } + get + { + return _binaryOperator == TokenKind.Cin; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cin; + } + } } /// - /// Binary operator -NotIn + /// Gets or sets binary operator -NotIn. /// [Parameter(Mandatory = true, ParameterSetName = "NotInSet")] [Alias("INotIn")] public SwitchParameter NotIn { - set { _binaryOperator = TokenKind.Inotin; } - get { return _binaryOperator == TokenKind.Inotin; } + get + { + return _binaryOperator == TokenKind.Inotin; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Inotin; + } + } } /// - /// Case sensitive binary operator -cnotin + /// Gets or sets case sensitive binary operator -cnotin. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotInSet")] public SwitchParameter CNotIn { - set { _binaryOperator = TokenKind.Cnotin; } - get { return _binaryOperator == TokenKind.Cnotin; } + get + { + return _binaryOperator == TokenKind.Cnotin; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Cnotin; + } + } } /// - /// Binary operator -Is + /// Gets or sets binary operator -Is. /// [Parameter(Mandatory = true, ParameterSetName = "IsSet")] public SwitchParameter Is { - set { _binaryOperator = TokenKind.Is; } - get { return _binaryOperator == TokenKind.Is; } + get + { + return _binaryOperator == TokenKind.Is; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Is; + } + } } /// - /// Binary operator -IsNot + /// Gets or sets binary operator -IsNot. /// [Parameter(Mandatory = true, ParameterSetName = "IsNotSet")] public SwitchParameter IsNot { - set { _binaryOperator = TokenKind.IsNot; } - get { return _binaryOperator == TokenKind.IsNot; } + get + { + return _binaryOperator == TokenKind.IsNot; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.IsNot; + } + } + } + + /// + /// Gets or sets binary operator -Not. + /// + [Parameter(Mandatory = true, ParameterSetName = "Not")] + public SwitchParameter Not + { + get + { + return _binaryOperator == TokenKind.Not; + } + + set + { + if (value) + { + _binaryOperator = TokenKind.Not; + } + } } #endregion binary operator parameters private readonly CallSite> _toBoolSite = CallSite>.Create(PSConvertBinder.Get(typeof(bool))); + private Func _operationDelegate; private static Func GetCallSiteDelegate(ExpressionType expressionType, bool ignoreCase) @@ -1217,23 +2075,34 @@ private static Func GetCallSiteDelegate(ExpressionType e return (x, y) => site.Target.Invoke(site, x, y); } + private static Func GetCallSiteDelegateBoolean(ExpressionType expressionType, bool ignoreCase) + { + // flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it + // equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to + // be compared under the bool context. So that '"string" | Where-Object Length' would behave + // just like '"string" | Where-Object {$_.Length}'. + var site = CallSite>.Create(binder: PSBinaryOperationBinder.Get(expressionType, ignoreCase)); + return (x, y) => site.Target.Invoke(site, y, x); + } + private static Tuple>, CallSite>> GetContainsCallSites(bool ignoreCase) { var enumerableSite = CallSite>.Create(PSEnumerableBinder.Get()); - var eqSite = + var equalSite = CallSite>.Create(PSBinaryOperationBinder.Get( ExpressionType.Equal, ignoreCase, scalarCompare: true)); - return Tuple.Create(enumerableSite, eqSite); + return Tuple.Create(enumerableSite, equalSite); } private void CheckLanguageMode() { if (Context.LanguageMode.Equals(PSLanguageMode.RestrictedLanguage)) { - string message = String.Format(CultureInfo.InvariantCulture, - InternalCommandStrings.OperationNotAllowedInRestrictedLanguageMode, - _binaryOperator); + string message = string.Format( + CultureInfo.InvariantCulture, + InternalCommandStrings.OperationNotAllowedInRestrictedLanguageMode, + _binaryOperator); PSInvalidOperationException exception = new PSInvalidOperationException(message); ThrowTerminatingError(new ErrorRecord(exception, "OperationNotAllowedInRestrictedLanguageMode", ErrorCategory.InvalidOperation, null)); @@ -1242,9 +2111,10 @@ private void CheckLanguageMode() private object GetLikeRHSOperand(object operand) { - var val = operand as string; - if (val == null) + if (operand is not string val) + { return operand; + } var wildcardOptions = _binaryOperator == TokenKind.Ilike || _binaryOperator == TokenKind.Inotlike ? WildcardOptions.IgnoreCase @@ -1256,7 +2126,9 @@ private object GetLikeRHSOperand(object operand) protected override void BeginProcessing() { if (_script != null) + { return; + } switch (_binaryOperator) { @@ -1267,13 +2139,9 @@ protected override void BeginProcessing() } else { - // flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it - // equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to - // be compared under the bool context. So that '"string" | Where-Object Length' would behave - // just like '"string" | Where-Object {$_.Length}'. - var site = CallSite>.Create(PSBinaryOperationBinder.Get(ExpressionType.Equal, true)); - _operationDelegate = (x, y) => site.Target.Invoke(site, y, x); + _operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.Equal, ignoreCase: true); } + break; case TokenKind.Ceq: _operationDelegate = GetCallSiteDelegate(ExpressionType.Equal, ignoreCase: false); @@ -1344,6 +2212,10 @@ protected override void BeginProcessing() _operationDelegate = (lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: true, ignoreCase: false); break; + case TokenKind.Not: + _operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.NotEqual, ignoreCase: true); + break; + // the second to last parameter in ContainsOperator has flipped semantics compared to others. // "true" means "contains" while "false" means "notcontains" case TokenKind.Icontains: @@ -1371,6 +2243,7 @@ protected override void BeginProcessing() (lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval); break; } + break; } case TokenKind.Ccontains: @@ -1398,6 +2271,7 @@ protected override void BeginProcessing() (lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval); break; } + break; } case TokenKind.Is: @@ -1434,22 +2308,27 @@ protected override void BeginProcessing() _convertedValue = LanguagePrimitives.ConvertTo(_convertedValue); } + break; } } } + private DynamicPropertyGetter _propGetter; + /// /// Execute the script block passing in the current pipeline object as /// it's only parameter. /// - /// could not parse script - /// see Pipeline.Invoke - /// see Pipeline.Invoke + /// Could not parse script. + /// See Pipeline.Invoke. + /// See Pipeline.Invoke. protected override void ProcessRecord() { if (_inputObject == AutomationNull.Value) + { return; + } if (_script != null) { @@ -1459,7 +2338,7 @@ protected override void ProcessRecord() dollarUnder: InputObject, input: new object[] { _inputObject }, scriptThis: AutomationNull.Value, - args: Utils.EmptyArray()); + args: Array.Empty()); if (_toBoolSite.Target.Invoke(_toBoolSite, result)) { @@ -1469,29 +2348,36 @@ protected override void ProcessRecord() else { // Both -Property and -Value need to be specified if the user specifies the binary operation - if (_valueNotSpecified && (_binaryOperator != TokenKind.Ieq || !_forceBooleanEvaluation)) + if (_valueNotSpecified && ((_binaryOperator != TokenKind.Ieq && _binaryOperator != TokenKind.Not) || !_forceBooleanEvaluation)) { // The binary operation is specified explicitly by the user and the -Value parameter is // not specified - ThrowTerminatingError(ForEachObjectCommand. - GenerateNameParameterError("Value", - InternalCommandStrings.ValueNotSpecifiedForWhereObject, - "ValueNotSpecifiedForWhereObject", null)); + ThrowTerminatingError( + ForEachObjectCommand.GenerateNameParameterError( + "Value", + InternalCommandStrings.ValueNotSpecifiedForWhereObject, + "ValueNotSpecifiedForWhereObject", + target: null)); } // The binary operation needs to be specified if the user specifies both the -Property and -Value if (!_valueNotSpecified && (_binaryOperator == TokenKind.Ieq && _forceBooleanEvaluation)) { // The -Property and -Value are specified explicitly by the user but the binary operation is not - ThrowTerminatingError(ForEachObjectCommand. - GenerateNameParameterError("Operator", - InternalCommandStrings.OperatorNotSpecified, - "OperatorNotSpecified", null)); + ThrowTerminatingError( + ForEachObjectCommand.GenerateNameParameterError( + "Operator", + InternalCommandStrings.OperatorNotSpecified, + "OperatorNotSpecified", + target: null)); } bool strictModeWithError = false; object lvalue = GetValue(ref strictModeWithError); - if (strictModeWithError) return; + if (strictModeWithError) + { + return; + } try { @@ -1528,9 +2414,9 @@ protected override void ProcessRecord() } /// - /// Get the value based on the given property name + /// Get the value based on the given property name. /// - /// the value of the property + /// The value of the property. private object GetValue(ref bool error) { if (LanguagePrimitives.IsNull(InputObject)) @@ -1538,13 +2424,15 @@ private object GetValue(ref bool error) if (Context.IsStrictVersion(2)) { WriteError( - ForEachObjectCommand. - GenerateNameParameterError("InputObject", - InternalCommandStrings.InputObjectIsNull, - "InputObjectIsNull", _inputObject, - _property)); + ForEachObjectCommand.GenerateNameParameterError( + "InputObject", + InternalCommandStrings.InputObjectIsNull, + "InputObjectIsNull", + _inputObject, + _property)); error = true; } + return null; } @@ -1566,38 +2454,65 @@ private object GetValue(ref bool error) // has keys that can't be compared to property. } + string resolvedPropertyName = null; + bool isBlindDynamicAccess = false; + ReadOnlyPSMemberInfoCollection members = GetMatchMembers(); if (members.Count > 1) { StringBuilder possibleMatches = new StringBuilder(); foreach (PSMemberInfo item in members) { - possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name); + possibleMatches.Append(CultureInfo.InvariantCulture, $" {item.Name}"); } WriteError( - ForEachObjectCommand. - GenerateNameParameterError("Property", - InternalCommandStrings.AmbiguousPropertyOrMethodName, - "AmbiguousPropertyName", _inputObject, - _property, possibleMatches)); + ForEachObjectCommand.GenerateNameParameterError( + "Property", + InternalCommandStrings.AmbiguousPropertyOrMethodName, + "AmbiguousPropertyName", + _inputObject, + _property, + possibleMatches)); error = true; } else if (members.Count == 0) { - if (Context.IsStrictVersion(2)) + if ((InputObject.BaseObject is IDynamicMetaObjectProvider) && + !WildcardPattern.ContainsWildcardCharacters(_property)) + { + // Let's just try a dynamic property access. Note that if it comes to + // depending on dynamic access, we are assuming it is a property; we + // don't have ETS info to tell us up front if it even exists or not, + // let alone if it is a method or something else. + // + // Note that this is "truly blind"--the name did not show up in + // GetDynamicMemberNames(), else it would show up as a dynamic member. + + resolvedPropertyName = _property; + isBlindDynamicAccess = true; + } + else if (Context.IsStrictVersion(2)) { - WriteError(ForEachObjectCommand.GenerateNameParameterError("Property", - InternalCommandStrings.PropertyNotFound, - "PropertyNotFound", _inputObject, _property)); + WriteError(ForEachObjectCommand.GenerateNameParameterError( + "Property", + InternalCommandStrings.PropertyNotFound, + "PropertyNotFound", + _inputObject, + _property)); error = true; } } else + { + resolvedPropertyName = members[0].Name; + } + + if (!string.IsNullOrEmpty(resolvedPropertyName)) { try { - return members[0].Value; + return _propGetter.GetValue(_inputObject, resolvedPropertyName); } catch (TerminateException) { @@ -1607,10 +2522,44 @@ private object GetValue(ref bool error) { throw; } - catch (Exception) + catch (Exception ex) { - // When the property is not gettable or it throws an exception - return null; + // For normal property accesses, we do not generate an error here. The problem + // for truly blind dynamic accesses (the member did not show up in + // GetDynamicMemberNames) is that we can't tell the difference between "it + // failed because the property does not exist" (let's call this case + // 1) and "it failed because accessing it actually threw some exception" (let's + // call that case 2). + // + // PowerShell behavior for normal (non-dynamic) properties is different for + // these two cases: case 1 gets an error (if strict mode is on) (which is + // possible because the ETS tells us up front if the property exists or not), + // and case 2 does not. (For normal properties, this catch block /is/ case 2.) + // + // For IDMOPs, we have the chance to attempt a "blind" access, but the cost is + // that we must have the same response to both cases (because we cannot + // distinguish between the two). So we have to make a choice: we can either + // swallow ALL errors (including "The property 'Blarg' does not exist"), or + // expose them all. + // + // Here, for truly blind dynamic access, we choose to preserve the behavior of + // showing "The property 'Blarg' does not exist" (case 1) errors than to + // suppress "FooException thrown when accessing Bloop property" (case + // 2) errors. + if (isBlindDynamicAccess && Context.IsStrictVersion(2)) + { + WriteError(new ErrorRecord(ex, + "DynamicPropertyAccessFailed_" + _property, + ErrorCategory.InvalidOperation, + _inputObject)); + + error = true; + } + else + { + // When the property is not gettable or it throws an exception + return null; + } } } @@ -1618,9 +2567,9 @@ private object GetValue(ref bool error) } /// - /// Get the matched PSMembers + /// Get the matched PSMembers. /// - /// + /// Matched PSMembers. private ReadOnlyPSMemberInfoCollection GetMatchMembers() { if (!WildcardPattern.ContainsWildcardCharacters(_property)) @@ -1631,6 +2580,7 @@ private ReadOnlyPSMemberInfoCollection GetMatchMembers() { results.Add(member); } + return new ReadOnlyPSMemberInfoCollection(results); } @@ -1640,61 +2590,91 @@ private ReadOnlyPSMemberInfoCollection GetMatchMembers() } } - - /// /// Implements a cmdlet that sets the script debugging options. /// - [Cmdlet(VerbsCommon.Set, "PSDebug", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113398")] + [Cmdlet(VerbsCommon.Set, "PSDebug", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096959")] public sealed class SetPSDebugCommand : PSCmdlet { /// - /// Sets the script tracing level + /// Gets or sets the script tracing level. /// [Parameter(ParameterSetName = "on")] [ValidateRange(0, 2)] public int Trace { - set { _trace = value; } - get { return _trace; } + get + { + return _trace; + } + + set + { + _trace = value; + } } + private int _trace = -1; /// - /// Turns stepping on and off + /// Gets or sets stepping on and off. /// [Parameter(ParameterSetName = "on")] public SwitchParameter Step { - set { _step = value; } - get { return (SwitchParameter)_step; } + get + { + return (SwitchParameter)_step; + } + + set + { + _step = value; + } } + private bool? _step; /// - /// Turns strict mode on and off. + /// Gets or sets strict mode on and off. /// [Parameter(ParameterSetName = "on")] public SwitchParameter Strict { - set { _strict = value; } - get { return (SwitchParameter)_strict; } + get + { + return (SwitchParameter)_strict; + } + + set + { + _strict = value; + } } + private bool? _strict; /// - /// Turns all script debugging features off. + /// Gets or sets all script debugging features off. /// [Parameter(ParameterSetName = "off")] public SwitchParameter Off { - get { return _off; } - set { _off = value; } + get + { + return _off; + } + + set + { + _off = value; + } } + private bool _off; /// - /// Execute the begin scriptblock at the start of processing + /// Execute the begin scriptblock at the start of processing. /// protected override void BeginProcessing() { @@ -1710,9 +2690,12 @@ protected override void BeginProcessing() { Context.Debugger.EnableTracing(_trace, _step); } + // Version 0 is the same as off if (_strict != null) + { Context.EngineSessionState.GlobalScope.StrictModeVersion = new Version((bool)_strict ? 1 : 0, 0); + } } } } @@ -1736,60 +2719,42 @@ protected override void BeginProcessing() /// Unlike Set-PSDebug -strict, Set-StrictMode is not engine-wide, and only /// affects the scope it was defined in. /// - [Cmdlet(VerbsCommon.Set, "StrictMode", DefaultParameterSetName = "Version", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113450")] + [Cmdlet(VerbsCommon.Set, "StrictMode", DefaultParameterSetName = "Version", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096804")] public class SetStrictModeCommand : PSCmdlet { /// - /// The following is the definition of the input parameter "Off". - /// Turns strict mode off + /// Gets or sets strict mode off. /// [Parameter(ParameterSetName = "Off", Mandatory = true)] public SwitchParameter Off { - get { return _off; } - set { _off = value; } + get + { + return _off; + } + + set + { + _off = value; + } } + 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) - { - // 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)) + if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase)) { - return new Version(majorVersion, 0); + version = PSVersionInfo.PSVersion; + return true; } - return inputData; + return base.TryConvertFromString(versionString, out version); } } @@ -1798,28 +2763,39 @@ 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("InvalidPSVersion", - null, Metadata.ValidateVersionFailure, arguments); + throw new ValidationMetadataException( + "InvalidPSVersion", + null, + Metadata.ValidateVersionFailure, + arguments); } } } /// - /// The following is the definition of the input parameter "Version". - /// Turns strict mode in the current scope. + /// Gets or sets strict mode in the current scope. /// [Parameter(ParameterSetName = "Version", Mandatory = true)] - [ArgumentToVersionTransformation()] - [ValidateVersion()] + [ArgumentCompleter(typeof(StrictModeVersionArgumentCompleter))] + [ArgumentToPSVersionTransformation] + [ValidateVersion] [Alias("v")] public Version Version { - get { return _version; } - set { _version = value; } + get + { + return _version; + } + + set + { + _version = value; + } } + private Version _version; /// @@ -1831,11 +2807,39 @@ protected override void EndProcessing() { _version = new Version(0, 0); } + Context.EngineSessionState.CurrentScope.StrictModeVersion = _version; } } - #endregion Set-StrictMode + /// + /// 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. -} \ No newline at end of file +} 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 2852eb5886e..402d7c8d4a6 100644 --- a/src/System.Management.Automation/engine/InvocationInfo.cs +++ b/src/System.Management.Automation/engine/InvocationInfo.cs @@ -1,19 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; -using System.Management.Automation.Internal; using System.Collections; -using System.Diagnostics; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Management.Automation.Internal; using System.Management.Automation.Language; namespace System.Management.Automation { /// - /// Describes how and where this command was invoked + /// Describes how and where this command was invoked. /// [DebuggerDisplay("Command = {MyCommand}")] public class InvocationInfo @@ -32,17 +31,14 @@ internal InvocationInfo(InternalCommand command) } /// - /// Constructor for InvocationInfo object + /// Constructor for InvocationInfo object. /// - /// /// /// The command information the invocation info represents. /// - /// /// /// The position representing the invocation, or the position representing the error. /// - /// internal InvocationInfo(CommandInfo commandInfo, IScriptExtent scriptPosition) : this(commandInfo, scriptPosition, null) { @@ -50,21 +46,17 @@ internal InvocationInfo(CommandInfo commandInfo, IScriptExtent scriptPosition) } /// - /// Constructor for InvocationInfo object + /// Constructor for InvocationInfo object. /// - /// /// /// The command information the invocation info represents. /// - /// /// /// The position representing the invocation, or the position representing the error. /// - /// /// /// The context in which the InvocationInfo is being created. /// - /// internal InvocationInfo(CommandInfo commandInfo, IScriptExtent scriptPosition, ExecutionContext context) { MyCommand = commandInfo; @@ -120,6 +112,7 @@ internal InvocationInfo(PSObject psObject) { scriptEndPosition = scriptPosition; } + _scriptPosition = new ScriptExtent(scriptPosition, scriptEndPosition); MyCommand = RemoteCommandInfo.FromPSObjectForRemoting(psObject); @@ -131,11 +124,11 @@ internal InvocationInfo(PSObject psObject) var list = (ArrayList)SerializationUtilities.GetPsObjectPropertyBaseObject(psObject, "InvocationInfo_PipelineIterationInfo"); if (list != null) { - PipelineIterationInfo = (int[])list.ToArray(typeof(Int32)); + PipelineIterationInfo = (int[])list.ToArray(typeof(int)); } else { - PipelineIterationInfo = Utils.EmptyArray(); + PipelineIterationInfo = Array.Empty(); } // @@ -195,7 +188,7 @@ internal InvocationInfo(PSObject psObject) #region Public Members /// - /// Provide basic information about the command + /// Provide basic information about the command. /// /// may be null public CommandInfo MyCommand { get; } @@ -206,24 +199,29 @@ internal InvocationInfo(PSObject psObject) /// public Dictionary BoundParameters { - get { - return _boundParameters ?? - (_boundParameters = new Dictionary(StringComparer.OrdinalIgnoreCase)); + get + { + return _boundParameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + internal set + { + _boundParameters = value; } - internal set { _boundParameters = value; } } /// - /// This member provides a list of the arguments that were not bound to any parameter + /// This member provides a list of the arguments that were not bound to any parameter. /// public List UnboundArguments { - get { return _unboundArguments ?? (_unboundArguments = new List()); } + get { return _unboundArguments ??= new List(); } + internal set { _unboundArguments = value; } } /// - /// The line number in the executing script that contained this cmdlet. + /// The line number in the executing script that contained this cmdlet. /// /// The script line number or -1 if not executing in a script. public int ScriptLineNumber @@ -253,7 +251,7 @@ public int OffsetInLine /// The script name or "" if there was no script. public string ScriptName { - get { return ScriptPosition.File ?? ""; } + get { return ScriptPosition.File ?? string.Empty; } } /// @@ -273,9 +271,21 @@ 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 + /// in the line. /// /// Formatted string indicating the command's position in the line public string PositionMessage @@ -284,25 +294,25 @@ public string PositionMessage } /// - /// This property tells you the directory from where you were being invoked + /// This property tells you the directory from where you were being invoked. /// public string PSScriptRoot { get { - if (!String.IsNullOrEmpty(ScriptPosition.File)) + if (!string.IsNullOrEmpty(ScriptPosition.File)) { return Path.GetDirectoryName(ScriptPosition.File); } else { - return String.Empty; + return string.Empty; } } } /// - /// This property tells you the full path to the command from where you were being invoked + /// This property tells you the full path to the command from where you were being invoked. /// public string PSCommandPath { @@ -316,18 +326,19 @@ public string PSCommandPath /// The name string. public string InvocationName { - get { return _invocationName ?? ""; } + get { return _invocationName ?? string.Empty; } + internal set { _invocationName = value; } } /// - /// How many elements are in the containing pipeline + /// How many elements are in the containing pipeline. /// /// number of elements in the containing pipeline public int PipelineLength { get; internal set; } /// - /// which element this command was in the containing pipeline + /// Which element this command was in the containing pipeline. /// /// which element this command was in the containing pipeline public int PipelinePosition { get; internal set; } @@ -349,7 +360,7 @@ public string InvocationName public IScriptExtent DisplayScriptPosition { get; set; } /// - /// Create + /// Create. /// /// /// @@ -384,7 +395,11 @@ internal IScriptExtent ScriptPosition return _scriptPosition; } } - set { _scriptPosition = value; } + + set + { + _scriptPosition = value; + } } /// @@ -402,7 +417,7 @@ internal string GetFullScript() /// /// All the commands in a given pipeline share the same PipelinePositionInfo. /// - internal int[] PipelineIterationInfo { get; set; } = Utils.EmptyArray(); + internal int[] PipelineIterationInfo { get; set; } = Array.Empty(); /// /// Adds the information about this informational record to a PSObject as note properties. @@ -437,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); @@ -456,9 +471,7 @@ internal void ToPSObjectForRemoting(PSObject psObject) public class RemoteCommandInfo : CommandInfo { /// - /// /// - /// private RemoteCommandInfo(string name, CommandTypes type) : base(name, type) { @@ -488,6 +501,7 @@ internal static RemoteCommandInfo FromPSObjectForRemoting(PSObject psObject) commandInfo._definition = RemotingDecoder.GetPropertyValue(psObject, "CommandInfo_Definition"); commandInfo.Visibility = RemotingDecoder.GetPropertyValue(psObject, "CommandInfo_Visibility"); } + return commandInfo; } @@ -511,7 +525,7 @@ internal static void ToPSObjectForRemoting(CommandInfo commandInfo, PSObject psO } /// - /// NYI + /// NYI. /// public override ReadOnlyCollection OutputType { @@ -521,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 3c14e8ee58b..1809ebd1c18 100644 --- a/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -16,60 +16,53 @@ public sealed class ItemCmdletProviderIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private ItemCmdletProviderIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // CmdletProviderIntrinsics private - + } /// - /// Constructs a facade over the "real" session state API + /// Constructs a facade over the "real" session state API. /// - /// /// /// An instance of the cmdlet. /// - /// /// /// If is null. /// - /// internal ItemCmdletProviderIntrinsics(Cmdlet cmdlet) { if (cmdlet == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); + throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); } _cmdlet = cmdlet; _sessionState = cmdlet.Context.EngineSessionState; - } // CmdletProviderIntrinsics internal + } /// - /// Constructs a facade over the "real" session state API + /// Constructs a facade over the "real" session state API. /// - /// /// /// An instance of the "real" session state class. /// - /// /// /// If is null. /// - /// internal ItemCmdletProviderIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // CmdletProviderIntrinsics internal + } #endregion Constructors @@ -80,38 +73,30 @@ internal ItemCmdletProviderIntrinsics(SessionStateInternal sessionState) /// /// Gets the item at the specified path. /// - /// /// /// The path to the item to retrieve. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The object(s) at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -124,51 +109,41 @@ public Collection Get(string path) // Parameter validation is done in the session state object return _sessionState.GetItem(new string[] { path }, false, false); - } // GetItem + } /// /// Gets the item at the specified path. /// - /// /// /// The path(s) to the item(s) to retrieve. They may be a drive or provider-qualified path(s) and may include /// glob characters. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The object(s) at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -181,47 +156,38 @@ public Collection Get(string[] path, bool force, bool literalPath) // Parameter validation is done in the session state object return _sessionState.GetItem(path, force, literalPath); - } // GetItem + } /// /// Gets the item at the specified path. /// - /// /// /// The path to the item to retrieve. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The object(s) at the specified path are written to the context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -234,43 +200,35 @@ internal void Get(string path, CmdletProviderContext context) // Parameter validation is done in the session state object _sessionState.GetItem(new string[] { path }, context); - } // GetItem + } /// /// Gets the dynamic parameters for the get-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -283,7 +241,7 @@ internal object GetItemDynamicParameters(string path, CmdletProviderContext cont // Parameter validation is done in the session state object return _sessionState.GetItemDynamicParameters(path, context); - } // GetItemDynamicParameters + } #endregion GetItem @@ -292,42 +250,33 @@ internal object GetItemDynamicParameters(string path, CmdletProviderContext cont /// /// Sets the item at the specified path. /// - /// /// /// The path to the item to set. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The new value to set the item to. /// - /// /// /// The object(s) set at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -340,55 +289,44 @@ public Collection Set(string path, object value) // Parameter validation is done in the session state object return _sessionState.SetItem(new string[] { path }, value, false, false); - } // SetItem + } /// /// Sets the item at the specified path. /// - /// /// /// The path(s) to the item(s) to set. They may be drive or provider-qualified paths and may include /// glob characters. /// - /// /// /// The new value to set the item to. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The object(s) set at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -401,51 +339,41 @@ public Collection Set(string[] path, object value, bool force, bool li // Parameter validation is done in the session state object return _sessionState.SetItem(path, value, force, literalPath); - } // SetItem + } /// /// Sets the item at the specified path. /// - /// /// /// The path to the item to set. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The new value to set the item to. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The object(s) set at the specified path are written to the context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -458,47 +386,38 @@ internal void Set(string path, object value, CmdletProviderContext context) // Parameter validation is done in the session state object _sessionState.SetItem(new string[] { path }, value, context); - } // SetItem + } /// /// Gets the dynamic parameters for the set-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The new value of the item at the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -514,7 +433,7 @@ internal object SetItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.SetItemDynamicParameters(path, value, context); - } // SetItemDynamicParameters + } #endregion SetItem @@ -523,38 +442,30 @@ internal object SetItemDynamicParameters( /// /// Clears the item at the specified path. /// - /// /// /// The path to the item to clear. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The object(s) cleared at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -567,51 +478,41 @@ public Collection Clear(string path) // Parameter validation is done in the session state object return _sessionState.ClearItem(new string[] { path }, false, false); - } // ClearItem + } /// /// Clears the item at the specified path. /// - /// /// /// The path(s) to the item to clear. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The object(s) cleared at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -624,47 +525,38 @@ public Collection Clear(string[] path, bool force, bool literalPath) // Parameter validation is done in the session state object return _sessionState.ClearItem(path, force, literalPath); - } // ClearItem + } /// /// Clears the item at the specified path. /// - /// /// /// The path to the item to be cleared. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The object(s) cleared at the specified path are written to the context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -677,43 +569,35 @@ internal void Clear(string path, CmdletProviderContext context) // Parameter validation is done in the session state object _sessionState.ClearItem(new string[] { path }, context); - } // ClearItem + } /// /// Gets the dynamic parameters for the clear-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -726,7 +610,7 @@ internal object ClearItemDynamicParameters(string path, CmdletProviderContext co // Parameter validation is done in the session state object return _sessionState.ClearItemDynamicParameters(path, context); - } // ClearItemDynamicParameters + } #endregion ClearItem @@ -735,34 +619,27 @@ internal object ClearItemDynamicParameters(string path, CmdletProviderContext co /// /// Invokes the default action of the item at the specified path. /// - /// /// /// The path to the item to invoke. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -775,44 +652,35 @@ public void Invoke(string path) // Parameter validation is done in the session state object _sessionState.InvokeDefaultAction(new string[] { path }, false); - } // InvokeDefaultAction - + } /// /// Invokes the default action of the item(s) at the specified path(s). /// - /// /// /// The path(s) to the item(s) to invoke. They may be drive or provider-qualified paths and may include /// glob characters. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -825,44 +693,35 @@ public void Invoke(string[] path, bool literalPath) // Parameter validation is done in the session state object _sessionState.InvokeDefaultAction(path, literalPath); - } // InvokeDefaultAction - + } /// /// Invokes the default action for the item at the specified path. /// - /// /// /// The path to the item to be invoked. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The context under which the command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -875,43 +734,35 @@ internal void Invoke(string path, CmdletProviderContext context) // Parameter validation is done in the session state object _sessionState.InvokeDefaultAction(new string[] { path }, context); - } // InvokeDefaultAction + } /// /// Gets the dynamic parameters for the invoke-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -924,7 +775,7 @@ internal object InvokeItemDynamicParameters(string path, CmdletProviderContext c // Parameter validation is done in the session state object return _sessionState.InvokeDefaultActionDynamicParameters(path, context); - } // InvokeItemDynamicParameters + } #endregion InvokeDefaultAction @@ -933,42 +784,33 @@ internal object InvokeItemDynamicParameters(string path, CmdletProviderContext c /// /// Renames the item at the given path. /// - /// /// /// The path to the item to rename. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The new name of the item. /// - /// /// /// The item(s) that were renamed. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -981,51 +823,41 @@ public Collection Rename(string path, string newName) // Parameter validation is done in the session state object return _sessionState.RenameItem(path, newName, false); - } // RenameItem + } /// /// Renames the item at the given path. /// - /// /// /// The path to the item to rename. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The new name of the item. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// The item(s) that were renamed. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1038,51 +870,41 @@ public Collection Rename(string path, string newName, bool force) // Parameter validation is done in the session state object return _sessionState.RenameItem(path, newName, force); - } // RenameItem + } /// /// Renames the item at the given path. /// - /// /// /// The path to the item to rename. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The new name of the item. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The item(s) that get renamed are written to the context. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1098,47 +920,38 @@ internal void Rename( // Parameter validation is done in the session state object _sessionState.RenameItem(path, newName, context); - } // RenameItem + } /// /// Gets the dynamic parameters for the rename-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The new name of the item. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1154,7 +967,7 @@ internal object RenameItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.RenameItemDynamicParameters(path, newName, context); - } // RenameItemDynamicParameters + } #endregion RenameItem @@ -1163,50 +976,39 @@ internal object RenameItemDynamicParameters( /// /// Creates a new item at the given path. /// - /// /// /// The path to the container to create item in. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The name of the new item to create. /// - /// /// /// The type of the new item to create. /// - /// /// /// The content of the new item to create. /// - /// /// /// The item that was created. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1223,60 +1025,47 @@ public Collection New( // Parameter validation is done in the session state object return _sessionState.NewItem(new string[] { path }, name, itemTypeName, content, false); - } // NewItem - + } /// /// Creates a new item at the given path. /// - /// /// /// The path(s) to the container to create item in. They may be drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The name of the new item to create. /// - /// /// /// The type of the new item to create. /// - /// /// /// The content of the new item to create. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// The item(s) that was created. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1294,60 +1083,47 @@ public Collection New( // Parameter validation is done in the session state object return _sessionState.NewItem(path, name, itemTypeName, content, force); - } // NewItem - + } /// /// Creates a new item at the given path. /// - /// /// /// The path to the container to create item in. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The name of the new item to create. /// - /// /// /// The type of the new item to create. /// - /// /// /// The content of the new item to create. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The new item is written to the context. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1365,51 +1141,41 @@ internal void New( // Parameter validation is done in the session state object _sessionState.NewItem(new string[] { path }, name, type, content, context); - } // NewItem + } /// /// Gets the dynamic parameters for the new-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The type of the new item to create. /// - /// /// /// The content of the new item to create. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1426,7 +1192,7 @@ internal object NewItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.NewItemDynamicParameters(path, type, content, context); - } // NewItemDynamicParameters + } #endregion NewItem @@ -1435,40 +1201,32 @@ internal object NewItemDynamicParameters( /// /// Removes the items at the given path. /// - /// /// /// The path to the item to remove. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// If true, removes all the children in all the sub-containers of the specified /// container. If false, only removes the immediate children of the specified /// container. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1481,53 +1239,43 @@ public void Remove(string path, bool recurse) // Parameter validation is done in the session state object _sessionState.RemoveItem(new string[] { path }, recurse, false, false); - } // RemoveItem + } /// /// Removes the items at the given path. /// - /// /// /// The path(s) to the item(s) to remove. They may be drive or provider-qualified paths and may include /// glob characters. /// - /// /// /// If true, removes all the children in all the sub-containers of the specified /// container. If false, only removes the immediate children of the specified /// container. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1540,45 +1288,37 @@ public void Remove(string[] path, bool recurse, bool force, bool literalPath) // Parameter validation is done in the session state object _sessionState.RemoveItem(path, recurse, force, literalPath); - } // RemoveItem + } /// /// Removes the items at the given path. /// - /// /// /// The path to the item to remove. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// If true, removes all the children in all the sub-containers of the specified /// container. If false, only removes the immediate children of the specified /// container. /// - /// /// /// The context under which the command is running. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1594,49 +1334,40 @@ internal void Remove( // Parameter validation is done in the session state object _sessionState.RemoveItem(new string[] { path }, recurse, context); - } // RemoveItem + } /// /// Gets the dynamic parameters for the remove-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// If true, removes all the children in all the sub-containers of the specified /// container. If false, only removes the immediate children of the specified /// container. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1652,60 +1383,49 @@ internal object RemoveItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.RemoveItemDynamicParameters(path, recurse, context); - } // RemoveItemDynamicParameters + } #endregion RemoveItem #region CopyItem /// - /// Copy item at the specified path + /// Copy item at the specified path. /// - /// /// /// The path to the item to copy. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The path to copy the item to. /// - /// /// /// If true, copies all the children in all the sub-containers of the specified /// container. If false, only copies the specified item. /// - /// /// /// Determines how the source container is used in the copy operation. /// - /// /// /// The item(s) that were copied. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1722,64 +1442,51 @@ public Collection Copy( // Parameter validation is done in the session state object return _sessionState.CopyItem(new string[] { path }, destinationPath, recurse, copyContainers, false, false); - } // CopyItem + } /// - /// Copy item at the specified path + /// Copy item at the specified path. /// - /// /// /// The path(s) to the item(s) to copy. They may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The path to copy the item to. /// - /// /// /// If true, copies all the children in all the sub-containers of the specified /// container. If false, only copies the specified item. /// - /// /// /// Determines how the source container is used in the copy operation. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The item(s) that were copied. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1798,60 +1505,48 @@ public Collection Copy( // Parameter validation is done in the session state object return _sessionState.CopyItem(path, destinationPath, recurse, copyContainers, force, literalPath); - } // CopyItem + } /// - /// Copy item at the specified path + /// Copy item at the specified path. /// - /// /// /// The path to the item to copy. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The path to copy the item to. /// - /// /// /// If true, copies all the children in all the sub-containers of the specified /// container. If false, only copies the specified item. /// - /// /// /// Determines how the source container is used in the copy operation. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The item(s) that were copied are written to the context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1869,52 +1564,42 @@ internal void Copy( // Parameter validation is done in the session state object _sessionState.CopyItem(new string[] { path }, destinationPath, recurse, copyContainers, context); - } // CopyItem + } /// /// Gets the dynamic parameters for the copy-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to copy the item to. /// - /// /// /// If true, copies all the children in all the sub-containers of the specified /// container. If false, only copies the specified item. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1931,7 +1616,7 @@ internal object CopyItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.CopyItemDynamicParameters(path, destination, recurse, context); - } // CopyItemDynamicParameters + } #endregion CopyItem @@ -1940,23 +1625,18 @@ internal object CopyItemDynamicParameters( /// /// Moves the item at the specified path to the specified destination. /// - /// /// /// The path to the item to move. /// - /// /// /// The path to the location that the item will be moved. /// - /// /// /// The item(s) that were moved. /// - /// /// /// If is null. /// - /// /// /// If resolves to multiple paths. /// or @@ -1966,25 +1646,20 @@ internal object CopyItemDynamicParameters( /// If resolves to multiple paths and /// is not a container. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1997,37 +1672,29 @@ public Collection Move(string path, string destination) // Parameter validation is done in the session state object return _sessionState.MoveItem(new string[] { path }, destination, false, false); - } // MoveItem - + } /// /// Moves the item at the specified path to the specified destination. /// - /// /// /// The path(s) to the item to move. /// - /// /// /// The path to the location that the item will be moved. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The item(s) that were moved. /// - /// /// /// If is null. /// - /// /// /// If resolves to multiple paths. /// or @@ -2037,25 +1704,20 @@ public Collection Move(string path, string destination) /// If resolves to multiple paths and /// is not a container. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2068,51 +1730,40 @@ public Collection Move(string[] path, string destination, bool force, // Parameter validation is done in the session state object return _sessionState.MoveItem(path, destination, force, literalPath); - } // MoveItem - + } /// /// Moves the item at the specified path to the specified destination. /// - /// /// /// The path to the item to move. /// - /// /// /// The path to the location that the item will be moved. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. The object that is moved is written to the context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2128,47 +1779,38 @@ internal void Move( // Parameter validation is done in the session state object _sessionState.MoveItem(new string[] { path }, destination, context); - } // MoveItem + } /// /// Gets the dynamic parameters for the move-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to move the item to. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2184,7 +1826,7 @@ internal object MoveItemDynamicParameters( // Parameter validation is done in the session state object return _sessionState.MoveItemDynamicParameters(path, destination, context); - } // MoveItemDynamicParameters + } #endregion MoveItem @@ -2193,33 +1835,26 @@ internal object MoveItemDynamicParameters( /// /// Determines if an item at the given path exits. /// - /// /// /// The path to the item to determine if it exists. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// True if the item at the specified path exists. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2232,46 +1867,37 @@ public bool Exists(string path) // Parameter validation is done in the session state object return _sessionState.ItemExists(path, false, false); - } // ItemExists + } /// /// Determines if an item at the given path exits. /// - /// /// /// The path to the item to determine if it exists. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// True if the item at the specified path exists. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2284,42 +1910,34 @@ public bool Exists(string path, bool force, bool literalPath) // Parameter validation is done in the session state object return _sessionState.ItemExists(path, force, literalPath); - } // ItemExists + } /// /// Determines if an item at the given path exits. /// - /// /// /// The path to the item to determine if it exists. It may be a drive or provider-qualified path and may include /// glob characters. /// - /// /// /// The context under which the command is running. /// - /// /// /// True if the item at the specified path exists. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2334,42 +1952,34 @@ internal bool Exists( // Parameter validation is done in the session state object return _sessionState.ItemExists(path, context); - } // ItemExists + } /// /// Gets the dynamic parameters for the test-path cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2384,7 +1994,7 @@ internal object ItemExistsDynamicParameters( // Parameter validation is done in the session state object return _sessionState.ItemExistsDynamicParameters(path, context); - } // ItemExistsDynamicParameters + } #endregion ItemExists #region IsContainer @@ -2392,32 +2002,25 @@ internal object ItemExistsDynamicParameters( /// /// Determines if the specified path is to an item that is a container. /// - /// /// /// The path to the item to determine if it is a container. /// - /// /// /// True if the path is to an item that is a container. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2430,41 +2033,33 @@ public bool IsContainer(string path) // Parameter validation is done in the session state object return _sessionState.IsItemContainer(path); - } // IsItemContainer + } /// /// Determines if the specified path is to an item that is a container. /// - /// /// /// The path to the item to determine if it is a container. /// - /// /// /// The context under which the command is running. /// - /// /// /// True if the path is to an item that is a container. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2479,7 +2074,7 @@ internal bool IsContainer( // Parameter validation is done in the session state object return _sessionState.IsItemContainer(path, context); - } // IsItemContainer + } #endregion IsItemContainer @@ -2487,11 +2082,11 @@ internal bool IsContainer( #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data - } // ItemCmdletProviderIntrinsics + } /// /// Determines how the source container of a copy operation @@ -2510,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 2be6567140a..13d8f66e5e9 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -1,30 +1,31 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Data; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; +using System.IO; 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; using System.Runtime.CompilerServices; +using System.Text; using System.Text.RegularExpressions; using System.Xml; -using System.IO; -using System.Text; -using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; // for fxcop -using Dbg = System.Management.Automation.Diagnostics; -using System.Reflection.Emit; +using System.Security; -#if !CORECLR -// System.DirectoryServices are not in CoreCLR +using Dbg = System.Management.Automation.Diagnostics; +using MethodCacheEntry = System.Management.Automation.DotNetAdapter.MethodCacheEntry; +#if !UNIX using System.DirectoryServices; #endif @@ -73,16 +74,16 @@ private static object GetSourceValueAsObject(PSObject sourceValue) /// /// Determines if the converter can convert the parameter to the parameter. /// - /// value supposedly *not* of the types supported by this converted to be converted to the parameter - /// one of the types supported by this converter to which the parameter should be converted + /// Value supposedly *not* of the types supported by this converted to be converted to the parameter. + /// One of the types supported by this converter to which the parameter should be converted. /// True if the converter can convert the parameter to the parameter, otherwise false. public abstract bool CanConvertFrom(object sourceValue, Type destinationType); /// /// Determines if the converter can convert the parameter to the parameter. /// - /// value supposedly *not* of the types supported by this converted to be converted to the parameter - /// one of the types supported by this converter to which the parameter should be converted + /// Value supposedly *not* of the types supported by this converted to be converted to the parameter. + /// One of the types supported by this converter to which the parameter should be converted. /// True if the converter can convert the parameter to the parameter, otherwise false. public virtual bool CanConvertFrom(PSObject sourceValue, Type destinationType) { @@ -90,43 +91,43 @@ public virtual bool CanConvertFrom(PSObject sourceValue, Type destinationType) } /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// Converts the parameter to the parameter using formatProvider and ignoreCase. /// - /// value supposedly *not* of the types supported by this converted to be converted to the parameter - /// one of the types supported by this converter to which the parameter should be converted 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 + /// Value supposedly *not* of the types supported by this converted to be converted to the parameter. + /// One of the types supported by this converter to which the parameter should be converted 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 abstract object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase); /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// Converts the parameter to the parameter using formatProvider and ignoreCase. /// - /// value supposedly *not* of the types supported by this converted to be converted to the parameter - /// one of the types supported by this converter to which the parameter should be converted 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 + /// Value supposedly *not* of the types supported by this converted to be converted to the parameter. + /// One of the types supported by this converter to which the parameter should be converted 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 virtual object ConvertFrom(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { return this.ConvertFrom(GetSourceValueAsObject(sourceValue), destinationType, formatProvider, ignoreCase); } /// - /// Returns true if the converter can convert the parameter to the parameter + /// Returns true if the converter can convert the parameter to the parameter. /// - /// value supposedly from one of the types supported by this converter to be converted to the parameter - /// type to convert the parameter, supposedly not one of the types supported by the converter + /// Value supposedly from one of the types supported by this converter to be converted to the parameter. + /// Type to convert the parameter, supposedly not one of the types supported by the converter. /// True if the converter can convert the parameter to the parameter, otherwise false. public abstract bool CanConvertTo(object sourceValue, Type destinationType); /// - /// Returns true if the converter can convert the parameter to the parameter + /// Returns true if the converter can convert the parameter to the parameter. /// - /// value supposedly from one of the types supported by this converter to be converted to the parameter - /// type to convert the parameter, supposedly not one of the types supported by the converter + /// Value supposedly from one of the types supported by this converter to be converted to the parameter. + /// Type to convert the parameter, supposedly not one of the types supported by the converter. /// True if the converter can convert the parameter to the parameter, otherwise false. public virtual bool CanConvertTo(PSObject sourceValue, Type destinationType) { @@ -134,25 +135,25 @@ public virtual bool CanConvertTo(PSObject sourceValue, Type destinationType) } /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// Converts the parameter to the parameter using formatProvider and ignoreCase. /// - /// value supposedly from one of the types supported by this converter to be converted to the parameter - /// type to convert the parameter, supposedly not one of the types supported by the converter - /// 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 + /// Value supposedly from one of the types supported by this converter to be converted to the parameter. + /// Type to convert the parameter, supposedly not one of the types supported by the converter. + /// 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 abstract object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase); /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// Converts the parameter to the parameter using formatProvider and ignoreCase. /// - /// value supposedly from one of the types supported by this converter to be converted to the parameter - /// type to convert the parameter, supposedly not one of the types supported by the converter - /// 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 + /// Value supposedly from one of the types supported by this converter to be converted to the parameter. + /// Type to convert the parameter, supposedly not one of the types supported by the converter. + /// 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 virtual object ConvertTo(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { return this.ConvertTo(GetSourceValueAsObject(sourceValue), destinationType, formatProvider, ignoreCase); @@ -161,7 +162,7 @@ public virtual object ConvertTo(PSObject sourceValue, Type destinationType, IFor /// /// Enables a type that only has conversion from string to be converted from all other - /// types through string + /// types through string. /// /// /// It is permitted to subclass @@ -170,11 +171,11 @@ public virtual object ConvertTo(PSObject sourceValue, Type destinationType, IFor public class ConvertThroughString : PSTypeConverter { /// - /// This will return false only if sourceValue is string + /// This will return false only if sourceValue is string. /// - /// value to convert from - /// ignored - /// false only if sourceValue is string + /// Value to convert from. + /// Ignored. + /// False only if sourceValue is string. public override bool CanConvertFrom(object sourceValue, Type destinationType) { // This if avoids infinite recursion. @@ -182,6 +183,7 @@ public override bool CanConvertFrom(object sourceValue, Type destinationType) { return false; } + return true; } @@ -189,25 +191,24 @@ public override bool CanConvertFrom(object sourceValue, Type destinationType) /// Converts to destinationType by first converting sourceValue to string /// and then converting the result to destinationType. /// - /// The value to convert from - /// The type this converter is associated with - /// The IFormatProvider to use - /// true if case should be ignored - /// sourceValue converted to destinationType - /// When no conversion was possible + /// The value to convert from. + /// The type this converter is associated with. + /// The IFormatProvider to use. + /// True if case should be ignored. + /// SourceValue converted to destinationType. + /// When no conversion was possible. public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { 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. /// - /// The value to convert from - /// The value to convert from - /// false + /// The value to convert from. + /// The value to convert from. + /// False. public override bool CanConvertTo(object sourceValue, Type destinationType) { return false; @@ -217,12 +218,12 @@ public override bool CanConvertTo(object sourceValue, Type destinationType) /// Throws NotSupportedException, since this converter is not designed to be used to /// convert from the type associated with the converted to other types. /// - /// The value to convert from - /// The value to convert from - /// The IFormatProvider to use - /// true if case should be ignored + /// The value to convert from. + /// The value to convert from. + /// The IFormatProvider to use. + /// True if case should be ignored. /// This method does not return a value. - /// NotSupportedException is always thrown + /// NotSupportedException is always thrown. public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { throw PSTraceSource.NewNotSupportedException(); @@ -291,25 +292,29 @@ internal enum ConversionRank } /// - /// Defines language support methods + /// Defines language support methods. /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Refactoring LanguagePrimitives takes lot of dev/test effort. Since V1 code is already shipped, we tend to exclude this message.")] 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); internal delegate void MemberSetValueError(SetValueException e); - internal const String OrderedAttribute = "ordered"; + internal const string OrderedAttribute = "ordered"; + internal const string DoublePrecision = "G15"; + internal const string SinglePrecision = "G7"; 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); @@ -335,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. @@ -358,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) @@ -374,13 +380,13 @@ internal EnumerableTWrapper(object enumerable, Type enumerableType) private void CreateGetEnumerator() { _getEnumerator = new DynamicMethod("GetEnumerator", typeof(object), - new Type[] { typeof(object) }, typeof(LanguagePrimitives).GetTypeInfo().Module, true); + new Type[] { typeof(object) }, typeof(LanguagePrimitives).Module, true); ILGenerator emitter = _getEnumerator.GetILGenerator(); 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); } @@ -399,17 +405,18 @@ private static IEnumerable GetEnumerableFromIEnumerableT(object obj) { foreach (Type i in obj.GetType().GetInterfaces()) { - TypeInfo typeinfo = i.GetTypeInfo(); - if (typeinfo.IsGenericType && typeinfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return new EnumerableTWrapper(obj, i); } } + return null; } 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) { @@ -422,6 +429,7 @@ private static GetEnumerableDelegate GetOrCalculateEnumerable(Type type) s_getEnumerableCache.Add(type, getEnumerable); } } + return getEnumerable; } @@ -441,12 +449,13 @@ private static void InitializeGetEnumerableCache() internal static bool IsTypeEnumerable(Type type) { if (type == null) { return false; } + GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(type); return (getEnumerable != LanguagePrimitives.ReturnNullEnumerable); } /// - /// Returns True if the language considers obj to be IEnumerable + /// Returns True if the language considers obj to be IEnumerable. /// /// /// IEnumerable or IEnumerable-like object @@ -457,9 +466,8 @@ public static bool IsObjectEnumerable(object obj) return IsTypeEnumerable(PSObject.Base(obj)?.GetType()); } - /// - /// Retrieves the IEnumerable of obj or null if the language does not consider obj to be IEnumerable + /// Retrieves the IEnumerable of obj or null if the language does not consider obj to be IEnumerable. /// /// /// IEnumerable or IEnumerable-like object @@ -469,6 +477,7 @@ public static IEnumerable GetEnumerable(object obj) { obj = PSObject.Base(obj); if (obj == null) { return null; } + GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(obj.GetType()); return getEnumerable(obj); } @@ -478,12 +487,10 @@ private static IEnumerable ReturnNullEnumerable(object obj) return null; } -#if !CORECLR private static IEnumerable DataTableEnumerable(object obj) { return (((DataTable)obj).Rows); } -#endif private static IEnumerable TypicalEnumerable(object obj) { @@ -519,12 +526,10 @@ private static IEnumerable TypicalEnumerable(object obj) private static GetEnumerableDelegate CalculateGetEnumerable(Type objectType) { -#if !CORECLR if (typeof(DataTable).IsAssignableFrom(objectType)) { return LanguagePrimitives.DataTableEnumerable; } -#endif // Don't treat IDictionary or XmlNode as enumerable... if (typeof(IEnumerable).IsAssignableFrom(objectType) @@ -537,17 +542,16 @@ private static GetEnumerableDelegate CalculateGetEnumerable(Type objectType) return LanguagePrimitives.ReturnNullEnumerable; } - private static readonly CallSite> s_getEnumeratorSite = CallSite>.Create(PSEnumerableBinder.Get()); /// - /// Retrieves the IEnumerator of obj or null if the language does not consider obj as capable of returning an IEnumerator + /// Retrieves the IEnumerator of obj or null if the language does not consider obj as capable of returning an IEnumerator. /// /// /// IEnumerable or IEnumerable-like object /// - /// When the act of getting the enumerator throws an exception + /// When the act of getting the enumerator throws an exception. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message for backward compatibility reasons.")] public static IEnumerator GetEnumerator(object obj) { @@ -581,6 +585,7 @@ public static PSDataCollection GetPSDataCollection(object inputValue) result.Add(PSObject.AsPSObject(inputValue)); } } + result.Complete(); return result; } @@ -588,10 +593,10 @@ public static PSDataCollection GetPSDataCollection(object inputValue) /// /// Used to compare two objects for equality converting the second to the type of the first, if required. /// - /// first object - /// object to compare first to - /// true if first is equal to the second - public new static bool Equals(object first, object second) + /// First object. + /// Object to compare first to. + /// True if first is equal to the second. + public static new bool Equals(object first, object second) { return Equals(first, second, false, CultureInfo.InvariantCulture); } @@ -599,11 +604,11 @@ public static PSDataCollection GetPSDataCollection(object inputValue) /// /// Used to compare two objects for equality converting the second to the type of the first, if required. /// - /// first object - /// object to compare first to + /// First object. + /// Object to compare first to. /// used only if first and second are strings /// to specify the type of string comparison - /// true if first is equal to the second + /// True if first is equal to the second. public static bool Equals(object first, object second, bool ignoreCase) { return Equals(first, second, ignoreCase, CultureInfo.InvariantCulture); @@ -612,32 +617,28 @@ public static bool Equals(object first, object second, bool ignoreCase) /// /// Used to compare two objects for equality converting the second to the type of the first, if required. /// - /// first object - /// object to compare first to + /// First object. + /// Object to compare first to. /// used only if first and second are strings /// to specify the type of string comparison /// the format/culture to be used. If this parameter is null, /// CultureInfo.InvariantCulture will be used. /// - /// true if first is equal to the second + /// True if first is equal to the second. public static bool Equals(object first, object second, bool ignoreCase, IFormatProvider formatProvider) { // If both first and second are null it returns true. // If one is null and the other is not it returns false. // if (first.Equals(second)) it returns true otherwise it goes ahead with type conversion operations. - // If both first and second are strings it returns (String.Compare(firstString, secondString, ignoreCase) == 0). + // If both first and second are strings it returns (string.Compare(firstString, secondString, ignoreCase) == 0). // 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("formatProvider"); + throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } first = PSObject.Base(first); @@ -645,8 +646,7 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP if (first == null) { - if (second == null) return true; - return false; + return second == null; } if (second == null) @@ -703,14 +703,38 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP return false; } + /// + /// Helper method for [Try]Compare to determine object ordering with null. + /// + /// The numeric value to compare to null. + /// True if the number to compare is on the right hand side if the comparison. + private static int CompareObjectToNull(object value, bool numberIsRightHandSide) + { + var i = numberIsRightHandSide ? -1 : 1; + + // If it's a positive number, including 0, it's greater than null + // for everything else it's less than zero... + switch (value) + { + case Int16 i16: return Math.Sign(i16) < 0 ? -i : i; + case Int32 i32: return Math.Sign(i32) < 0 ? -i : i; + case Int64 i64: return Math.Sign(i64) < 0 ? -i : i; + case sbyte sby: return Math.Sign(sby) < 0 ? -i : i; + case float f: return Math.Sign(f) < 0 ? -i : i; + case double d: return Math.Sign(d) < 0 ? -i : i; + case decimal de: return Math.Sign(de) < 0 ? -i : i; + default: return i; + } + } + /// /// Compare first and second, converting second to the /// type of the first, if necessary. /// - /// first comparison value - /// second comparison value - /// less than zero if first is smaller than second, more than - /// zero if it is greater or zero if they are the same + /// First comparison value. + /// Second comparison value. + /// Less than zero if first is smaller than second, more than + /// zero if it is greater or zero if they are the same. /// /// does not implement IComparable or cannot be converted /// to the type of . @@ -724,11 +748,11 @@ public static int Compare(object first, object second) /// Compare first and second, converting second to the /// type of the first, if necessary. /// - /// first comparison value - /// second comparison value - /// Used if both values are strings - /// less than zero if first is smaller than second, more than - /// zero if it is greater or zero if they are the same + /// First comparison value. + /// Second comparison value. + /// Used if both values are strings. + /// Less than zero if first is smaller than second, more than + /// zero if it is greater or zero if they are the same. /// /// does not implement IComparable or cannot be converted /// to the type of . @@ -742,27 +766,23 @@ public static int Compare(object first, object second, bool ignoreCase) /// Compare first and second, converting second to the /// type of the first, if necessary. /// - /// first comparison value - /// second comparison value - /// Used if both values are strings - /// Used in type conversions and if both values are strings - /// less than zero if first is smaller than second, more than - /// zero if it is greater or zero if they are the same + /// First comparison value. + /// Second comparison value. + /// Used if both values are strings. + /// Used in type conversions and if both values are strings. + /// Less than zero if first is smaller than second, more than + /// zero if it is greater or zero if they are the same. /// /// does not implement IComparable or cannot be converted /// to the type of . /// 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("formatProvider"); + throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } first = PSObject.Base(first); @@ -770,49 +790,15 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP if (first == null) { - if (second == null) - { - return 0; - } - else - { - // If it's a positive number, including 0, it's greater than null - // for everything else it's less than zero... - switch (LanguagePrimitives.GetTypeCode(second.GetType())) - { - case TypeCode.Int16: return System.Math.Sign((Int16)second) < 0 ? 1 : -1; - case TypeCode.Int32: return System.Math.Sign((Int32)second) < 0 ? 1 : -1; - case TypeCode.Int64: return System.Math.Sign((Int64)second) < 0 ? 1 : -1; - case TypeCode.SByte: return System.Math.Sign((sbyte)second) < 0 ? 1 : -1; - case TypeCode.Single: return System.Math.Sign((System.Single)second) < 0 ? 1 : -1; - case TypeCode.Double: return System.Math.Sign((System.Double)second) < 0 ? 1 : -1; - case TypeCode.Decimal: return System.Math.Sign((System.Decimal)second) < 0 ? 1 : -1; - default: return -1; - } - } + return second == null ? 0 : CompareObjectToNull(second, true); } if (second == null) { - // If it's a positive number, including 0, it's greater than null - // for everything else it's less than zero... - switch (LanguagePrimitives.GetTypeCode(first.GetType())) - { - case TypeCode.Int16: return System.Math.Sign((Int16)first) < 0 ? -1 : 1; - case TypeCode.Int32: return System.Math.Sign((Int32)first) < 0 ? -1 : 1; - case TypeCode.Int64: return System.Math.Sign((Int64)first) < 0 ? -1 : 1; - case TypeCode.SByte: return System.Math.Sign((sbyte)first) < 0 ? -1 : 1; - case TypeCode.Single: return System.Math.Sign((System.Single)first) < 0 ? -1 : 1; - case TypeCode.Double: return System.Math.Sign((System.Double)first) < 0 ? -1 : 1; - case TypeCode.Decimal: return System.Math.Sign((System.Decimal)first) < 0 ? -1 : 1; - default: return 1; - } + return CompareObjectToNull(first, false); } - - string firstString = first as string; - - if (firstString != null) + if (first is string firstString) { string secondString = second as string; if (secondString == null) @@ -823,10 +809,11 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP } catch (PSInvalidCastException e) { - throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, + throw PSTraceSource.NewArgumentException(nameof(second), ExtendedTypeSystem.ComparisonFailure, first.ToString(), second.ToString(), e.Message); } } + return culture.CompareInfo.Compare(firstString, secondString, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); } @@ -839,6 +826,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP { return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex); } + object secondConverted; try { @@ -846,13 +834,11 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP } catch (PSInvalidCastException e) { - throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, + throw PSTraceSource.NewArgumentException(nameof(second), ExtendedTypeSystem.ComparisonFailure, first.ToString(), second.ToString(), e.Message); } - IComparable firstComparable = first as IComparable; - - if (firstComparable != null) + if (first is IComparable firstComparable) { return firstComparable.CompareTo(secondConverted); } @@ -864,14 +850,132 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP // At this point, we know that they aren't equal but we have no way of // knowing which should compare greater than the other so we throw an exception. - throw PSTraceSource.NewArgumentException("first", ExtendedTypeSystem.NotIcomparable, first.ToString()); + throw PSTraceSource.NewArgumentException(nameof(first), ExtendedTypeSystem.NotIcomparable, first.ToString()); + } + + /// + /// Tries to compare first and second, converting second to the type of the first, if necessary. + /// If a conversion is needed but fails, false is return. + /// + /// First comparison value. + /// Second comparison value. + /// Less than zero if first is smaller than second, more than + /// zero if it is greater or zero if they are the same. + /// True if the comparison was successful, false otherwise. + public static bool TryCompare(object first, object second, out int result) + { + return TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result); + } + + /// + /// Tries to compare first and second, converting second to the type of the first, if necessary. + /// If a conversion is needed but fails, false is return. + /// + /// First comparison value. + /// Second comparison value. + /// Used if both values are strings. + /// Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same. + /// True if the comparison was successful, false otherwise. + public static bool TryCompare(object first, object second, bool ignoreCase, out int result) + { + return TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result); } /// - /// Returns true if the language considers obj to be true + /// Tries to compare first and second, converting second to the type of the first, if necessary. + /// If a conversion is needed but fails, false is return. /// - /// obj to verify if it is true - /// true if obj is true + /// First comparison value. + /// Second comparison value. + /// Used if both values are strings. + /// Used in type conversions and if both values are strings. + /// Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same. + /// True if the comparison was successful, false otherwise. + /// The parameter is not a . + public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result) + { + result = 0; + formatProvider ??= CultureInfo.InvariantCulture; + + if (formatProvider is not CultureInfo culture) + { + throw PSTraceSource.NewArgumentException(nameof(formatProvider)); + } + + first = PSObject.Base(first); + second = PSObject.Base(second); + + if (first == null && second == null) + { + result = 0; + return true; + } + + if (first == null) + { + result = CompareObjectToNull(second, true); + return true; + } + + if (second == null) + { + // If it's a positive number, including 0, it's greater than null + // for everything else it's less than zero... + result = CompareObjectToNull(first, false); + return true; + } + + if (first is string firstString) + { + if (second is not string secondString) + { + if (!TryConvertTo(second, culture, out secondString)) + { + return false; + } + } + + result = culture.CompareInfo.Compare(firstString, secondString, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); + return true; + } + + Type firstType = first.GetType(); + Type secondType = second.GetType(); + int firstIndex = TypeTableIndex(firstType); + int secondIndex = TypeTableIndex(secondType); + if (firstIndex != -1 && secondIndex != -1) + { + result = NumericCompare(first, second, firstIndex, secondIndex); + return true; + } + + if (!TryConvertTo(second, firstType, culture, out object secondConverted)) + { + return false; + } + + if (first is IComparable firstComparable) + { + result = firstComparable.CompareTo(secondConverted); + return true; + } + + if (first.Equals(second)) + { + result = 0; + return true; + } + + // At this point, we know that they aren't equal but we have no way of + // knowing which should compare greater than the other so we return false. + return false; + } + + /// + /// Returns true if the language considers obj to be true. + /// + /// Obj to verify if it is true. + /// True if obj is true. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message for backward compatibility reasons")] public static bool IsTrue(object obj) { @@ -895,8 +999,8 @@ public static bool IsTrue(object obj) if (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(objType))) { - ConversionData data = GetConversionData(objType, typeof(bool)) ?? - CacheConversion(objType, typeof(bool), CreateNumericToBoolConverter(objType), ConversionRank.Language); + IConversionData data = GetConversionData(objType, typeof(bool)) ?? + CacheConversion(objType, typeof(bool), CreateNumericToBoolConverter(objType), ConversionRank.Language); return (bool)data.Invoke(obj, typeof(bool), false, null, null, null); } @@ -932,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]); } @@ -952,15 +1054,15 @@ internal static bool IsTrue(IList objectArray) /// /// Internal routine that determines if an object meets any of our criteria for null. /// - /// The object to test - /// true if the object is null + /// The object to test. + /// True if the object is null. internal static bool IsNull(object obj) { return (obj == null || obj == AutomationNull.Value); } /// - /// Auxiliary for the cases where we want a new PSObject or null + /// Auxiliary for the cases where we want a new PSObject or null. /// internal static PSObject AsPSObjectOrNull(object obj) { @@ -968,8 +1070,10 @@ internal static PSObject AsPSObjectOrNull(object obj) { return null; } + return PSObject.AsPSObject(obj); } + internal static int TypeTableIndex(Type type) { switch (LanguagePrimitives.GetTypeCode(type)) @@ -997,7 +1101,7 @@ internal static int TypeTableIndex(Type type) /// an exception when converted to decimal. /// The order of lines and columns cannot be changed since NumericCompare depends on it. /// - internal static Type[][] LargestTypeTable = new Type[][] + internal static readonly Type[][] LargestTypeTable = new Type[][] { // System.Int16 System.Int32 System.Int64 System.UInt16 System.UInt32 System.UInt64 System.SByte System.Byte System.Single System.Double System.Decimal /* System.Int16 */new Type[] { typeof(System.Int16), typeof(System.Int32), typeof(System.Int64), typeof(System.Int32), typeof(System.Int64), typeof(System.Double), typeof(System.Int16), typeof(System.Int16), typeof(System.Single), typeof(System.Double), typeof(System.Decimal) }, @@ -1063,7 +1167,7 @@ private static int NumericCompare(object number1, object number2, int index1, in } /// - /// Necessary not to return an integer type code for enums + /// Necessary not to return an integer type code for enums. /// /// /// @@ -1077,9 +1181,9 @@ internal static TypeCode GetTypeCode(Type type) /// the PSObject if required. /// /// The type for which to convert - /// The object from which to convert + /// The object from which to convert. /// An object of the specified type, if the conversion was successful. Returns null otherwise. - internal static T FromObjectAs(Object castObject) + internal static T FromObjectAs(object castObject) { T returnType = default(T); @@ -1150,60 +1254,60 @@ private enum TypeCodeTraits }; /// - /// Verifies if type is a signed integer + /// Verifies if type is a signed integer. /// - /// type code to check - /// true if type is a signed integer, false otherwise + /// Type code to check. + /// True if type is a signed integer, false otherwise. internal static bool IsSignedInteger(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.SignedInteger) != 0; } /// - /// Verifies if type is an unsigned integer + /// Verifies if type is an unsigned integer. /// - /// type code to check - /// true if type is an unsigned integer, false otherwise + /// Type code to check. + /// True if type is an unsigned integer, false otherwise. internal static bool IsUnsignedInteger(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.UnsignedInteger) != 0; } /// - /// Verifies if type is integer + /// Verifies if type is integer. /// - /// type code to check - /// true if type is integer, false otherwise + /// Type code to check. + /// True if type is integer, false otherwise. internal static bool IsInteger(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Integer) != 0; } /// - /// Verifies if type is a floating point number + /// Verifies if type is a floating point number. /// - /// type code to check - /// true if type is floating point, false otherwise + /// Type code to check. + /// True if type is floating point, false otherwise. internal static bool IsFloating(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Floating) != 0; } /// - /// Verifies if type is an integer or floating point number + /// Verifies if type is an integer or floating point number. /// - /// type code to check - /// true if type is integer or floating point, false otherwise + /// Type code to check. + /// True if type is integer or floating point, false otherwise. internal static bool IsNumeric(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Numeric) != 0; } /// - /// Verifies if type is a CIM intrinsic type + /// Verifies if type is a CIM intrinsic type. /// - /// type code to check - /// true if type is CIM intrinsic type, false otherwise + /// Type code to check. + /// True if type is CIM intrinsic type, false otherwise. internal static bool IsCimIntrinsicScalarType(TypeCode typeCode) { return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.CimIntrinsicType) != 0; @@ -1218,7 +1322,7 @@ internal static bool IsCimIntrinsicScalarType(Type type) // - TimeSpan part of "datetime" // - ref TypeCode typeCode = LanguagePrimitives.GetTypeCode(type); - if (LanguagePrimitives.IsCimIntrinsicScalarType(typeCode) && !type.GetTypeInfo().IsEnum) + if (LanguagePrimitives.IsCimIntrinsicScalarType(typeCode) && !type.IsEnum) { return true; } @@ -1231,40 +1335,39 @@ internal static bool IsCimIntrinsicScalarType(Type type) return false; } - /// - /// Verifies if type is one of the boolean types + /// Verifies if type is one of the boolean types. /// - /// type to check - /// true if type is one of boolean types, false otherwise + /// Type to check. + /// True if type is one of boolean types, false otherwise. internal static bool IsBooleanType(Type type) { if (type == typeof(bool) || - type == typeof(Nullable)) + type == typeof(bool?)) return true; else return false; } /// - /// Verifies if type is one of switch parameter types + /// Verifies if type is one of switch parameter types. /// - /// type to check - /// true if type is one of switch parameter types, false otherwise + /// Type to check. + /// True if type is one of switch parameter types, false otherwise. internal static bool IsSwitchParameterType(Type type) { - if (type == typeof(SwitchParameter) || type == typeof(Nullable)) + if (type == typeof(SwitchParameter) || type == typeof(SwitchParameter?)) return true; else return false; } /// - /// Verifies if type is one of boolean or switch parameter types + /// Verifies if type is one of boolean or switch parameter types. /// - /// type to check - /// true if type if one of boolean or switch parameter types, - /// false otherwise + /// Type to check. + /// True if type if one of boolean or switch parameter types, + /// false otherwise. internal static bool IsBoolOrSwitchParameterType(Type type) { if (IsBooleanType(type) || IsSwitchParameterType(type)) @@ -1283,14 +1386,13 @@ internal static bool IsBoolOrSwitchParameterType(Type type) /// need of conversion. /// /// The dictionary that potentially implement - /// The object representing the key - /// The value to assign + /// The object representing the key. + /// The value to assign. internal static void DoConversionsForSetInGenericDictionary(IDictionary dictionary, ref object key, ref object value) { foreach (Type i in dictionary.GetType().GetInterfaces()) { - TypeInfo typeInfo = i.GetTypeInfo(); - if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { // If we get here, we know the target implements IDictionary. We will assume // that the non-generic implementation of the indexer property just forwards @@ -1310,7 +1412,8 @@ internal static void DoConversionsForSetInGenericDictionary(IDictionary dictiona #region type converter - internal static PSTraceSource typeConversion = PSTraceSource.GetTracer("TypeConversion", "Traces the type conversion algorithm", false); + internal static readonly PSTraceSource typeConversion = PSTraceSource.GetTracer("TypeConversion", "Traces the type conversion algorithm", false); + internal static readonly ConversionData NoConversion = new ConversionData(ConvertNoConversion, ConversionRank.None); private static TypeConverter GetIntegerSystemConverter(Type type) { @@ -1338,14 +1441,15 @@ private static TypeConverter GetIntegerSystemConverter(Type type) { return new UInt64Converter(); } - else if (type == typeof(Byte)) + else if (type == typeof(byte)) { return new ByteConverter(); } - else if (type == typeof(SByte)) + else if (type == typeof(sbyte)) { return new SByteConverter(); } + return null; } @@ -1362,6 +1466,7 @@ internal static object GetConverter(Type type, TypeTable backupTypeTable) s_tracer.WriteLine("ecFromTLS != null"); typesXmlConverter = ecFromTLS.TypeTable.GetTypeConverter(type.FullName); } + if ((typesXmlConverter == null) && (backupTypeTable != null)) { s_tracer.WriteLine("Using provided TypeTable to get the type converter"); @@ -1374,7 +1479,7 @@ internal static object GetConverter(Type type, TypeTable backupTypeTable) return typesXmlConverter; } - var typeConverters = type.GetTypeInfo().GetCustomAttributes(typeof(TypeConverterAttribute), false); + var typeConverters = type.GetCustomAttributes(typeof(TypeConverterAttribute), false); foreach (var typeConverter in typeConverters) { var attr = (TypeConverterAttribute)typeConverter; @@ -1390,44 +1495,38 @@ internal static object GetConverter(Type type, TypeTable backupTypeTable) private static object NewConverterInstance(string assemblyQualifiedTypeName) { - int typeSeparator = assemblyQualifiedTypeName.IndexOf(",", StringComparison.Ordinal); - if (typeSeparator == -1) + if (!assemblyQualifiedTypeName.Contains(',')) { typeConversion.WriteLine("Type name \"{0}\" should be assembly qualified.", assemblyQualifiedTypeName); return null; } - string assemblyName = assemblyQualifiedTypeName.Substring(typeSeparator + 2); - string typeName = assemblyQualifiedTypeName.Substring(0, typeSeparator); - foreach (Assembly assembly in ClrFacade.GetAssemblies(typeName)) + Type converterType; + try { - if (assembly.FullName == assemblyName) - { - Type converterType = null; - try - { - converterType = assembly.GetType(typeName, false, false); - } - catch (ArgumentException e) - { - typeConversion.WriteLine("Assembly \"{0}\" threw an exception when retrieving the type \"{1}\": \"{2}\".", assemblyName, typeName, e.Message); - return null; - } - try - { - return Activator.CreateInstance(converterType); - } - catch (Exception e) - { - TargetInvocationException inner = e as TargetInvocationException; - string message = (inner == null) || (inner.InnerException == null) ? e.Message : inner.InnerException.Message; - typeConversion.WriteLine("Creating an instance of type \"{0}\" caused an exception to be thrown: \"{1}\"", assemblyQualifiedTypeName, message); - return null; - } - } + // Type.GetType() can load an assembly. + // PowerShell is allowed to load only TPA. + // Since a type is already loaded we trust to an attribute assigned to the type + // and can use Type.GetType() without additional checks. + converterType = Type.GetType(assemblyQualifiedTypeName, throwOnError: true, ignoreCase: false); + } + catch (Exception e) + { + typeConversion.WriteLine("Threw an exception when retrieving the type \"{1}\": \"{2}\".", assemblyQualifiedTypeName, e.Message); + return null; + } + + try + { + return Activator.CreateInstance(converterType); + } + catch (Exception e) + { + TargetInvocationException inner = e as TargetInvocationException; + string message = (inner == null) || (inner.InnerException == null) ? e.Message : inner.InnerException.Message; + typeConversion.WriteLine("Creating an instance of type \"{0}\" caused an exception to be thrown: \"{1}\"", assemblyQualifiedTypeName, message); + return null; } - typeConversion.WriteLine("Could not create an instance of type \"{0}\".", assemblyQualifiedTypeName); - return null; } /// @@ -1469,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" }, @@ -1489,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[]" }, @@ -1508,7 +1607,7 @@ public static string ConvertTypeNameToPSTypeName(string typeName) #region public type conversion /// - /// Converts valueToConvert to resultType + /// Converts valueToConvert to resultType. /// /// /// A null valueToConvert can be converted to : @@ -1563,18 +1662,18 @@ public static string ConvertTypeNameToPSTypeName(string typeName) /// If any operation above throws an exception, this exception will be wrapped into a /// PSInvalidCastException and thrown resulting in no further conversion attempt. /// - /// value to be converted and returned - /// type to convert valueToConvert - /// converted value - /// if resultType is null - /// if the conversion failed + /// Value to be converted and returned. + /// Type to convert valueToConvert. + /// Converted value. + /// If resultType is null. + /// If the conversion failed. public static object ConvertTo(object valueToConvert, Type resultType) { return ConvertTo(valueToConvert, resultType, true, CultureInfo.InvariantCulture, null); } /// - /// Converts valueToConvert to resultType possibly considering formatProvider + /// Converts valueToConvert to resultType possibly considering formatProvider. /// /// /// A null valueToConvert can be converted to : @@ -1618,12 +1717,12 @@ public static object ConvertTo(object valueToConvert, Type resultType) /// If any operation above throws an exception, this exception will be wrapped into a /// PSInvalidCastException and thrown resulting in no further conversion attempt. /// - /// value to be converted and returned - /// type to convert valueToConvert - /// To be used in custom type conversions, to call parse and to call Convert.ChangeType - /// converted value - /// if resultType is null - /// if the conversion failed + /// Value to be converted and returned. + /// Type to convert valueToConvert. + /// To be used in custom type conversions, to call parse and to call Convert.ChangeType. + /// Converted value. + /// If resultType is null. + /// If the conversion failed. public static object ConvertTo(object valueToConvert, Type resultType, IFormatProvider formatProvider) { return ConvertTo(valueToConvert, resultType, true, formatProvider, null); @@ -1632,20 +1731,21 @@ public static object ConvertTo(object valueToConvert, Type resultType, IFormatPr /// /// Converts PSObject to resultType. /// - /// value to be converted and returned. - /// type to convert psobject. + /// Value to be converted and returned. + /// Type to convert psobject. /// Indicates if inner properties have to be recursively converted. - /// To be used in custom type conversions, to call parse and to call Convert.ChangeType + /// To be used in custom type conversions, to call parse and to call Convert.ChangeType. /// Indicates if Unknown members in the psobject have to be ignored if the corresponding members in resultType do not exist. - /// converted value. + /// Converted value. public static object ConvertPSObjectToType(PSObject valueToConvert, Type resultType, bool recursion, IFormatProvider formatProvider, bool ignoreUnknownMembers) { if (valueToConvert != null) { - ConstructorInfo toConstructor = resultType.GetConstructor(PSTypeExtensions.EmptyTypes); + ConstructorInfo toConstructor = resultType.GetConstructor(Type.EmptyTypes); ConvertViaNoArgumentConstructor noArgumentConstructorConverter = new ConvertViaNoArgumentConstructor(toConstructor, resultType); return noArgumentConstructorConverter.Convert(PSObject.Base(valueToConvert), resultType, recursion, (PSObject)valueToConvert, formatProvider, null, ignoreUnknownMembers); } + return null; } @@ -1657,10 +1757,11 @@ public static object ConvertPSObjectToType(PSObject valueToConvert, Type resultT /// public static T ConvertTo(object valueToConvert) { - if (valueToConvert is T) + if (valueToConvert is T value) { - return (T)valueToConvert; + return value; } + return (T)ConvertTo(valueToConvert, typeof(T), true, CultureInfo.InvariantCulture, null); } @@ -1670,16 +1771,17 @@ public static T ConvertTo(object valueToConvert) /// /// This method is a variant of ConvertTo that does not throw exceptions if the conversion fails. /// - /// value to be converted and returned - /// result of the conversion. This is valid only if the return is true. - /// false for conversion failure, true for success + /// Value to be converted and returned. + /// Result of the conversion. This is valid only if the return is true. + /// False for conversion failure, true for success. public static bool TryConvertTo(object valueToConvert, out T result) { - if (valueToConvert is T) + if (valueToConvert is T value) { - result = (T)valueToConvert; + result = value; return true; } + return TryConvertTo(valueToConvert, CultureInfo.InvariantCulture, out result); } /// @@ -1689,26 +1791,21 @@ public static bool TryConvertTo(object valueToConvert, out T result) /// /// This method is a variant of ConvertTo that does not throw exceptions if the conversion fails. /// - /// value to be converted and returned - /// governing conversion of types - /// result of the conversion. This is valid only if the return is true. - /// false for conversion failure, true for success + /// Value to be converted and returned. + /// Governing conversion of types. + /// Result of the conversion. This is valid only if the return is true. + /// False for conversion failure, true for success. public static bool TryConvertTo(object valueToConvert, IFormatProvider formatProvider, out T result) { result = default(T); - try - { - result = (T)ConvertTo(valueToConvert, typeof(T), formatProvider); - } - catch (InvalidCastException) - { - return false; - } - catch (ArgumentException) + + if (TryConvertTo(valueToConvert, typeof(T), formatProvider, out object res)) { - return false; + result = (T)res; + return true; } - return true; + + return false; } /// @@ -1717,10 +1814,10 @@ public static bool TryConvertTo(object valueToConvert, IFormatProvider format /// /// This method is a variant of ConvertTo that does not throw exceptions if the conversion fails. /// - /// value to be converted and returned - /// type to convert valueToConvert - /// result of the conversion. This is valid only if the return is true. - /// false for conversion failure, true for success + /// Value to be converted and returned. + /// Type to convert valueToConvert. + /// Result of the conversion. This is valid only if the return is true. + /// False for conversion failure, true for success. public static bool TryConvertTo(object valueToConvert, Type resultType, out object result) { return TryConvertTo(valueToConvert, resultType, CultureInfo.InvariantCulture, out result); @@ -1733,27 +1830,44 @@ public static bool TryConvertTo(object valueToConvert, Type resultType, out obje /// /// This method is a variant of ConvertTo that does not throw exceptions if the conversion fails. /// - /// value to be converted and returned - /// type to convert valueToConvert - /// governing conversion of types - /// result of the conversion. This is valid only if the return is true. - /// false for conversion failure, true for success + /// Value to be converted and returned. + /// Type to convert valueToConvert. + /// Governing conversion of types. + /// Result of the conversion. This is valid only if the return is true. + /// False for conversion failure, true for success. public static bool TryConvertTo(object valueToConvert, Type resultType, IFormatProvider formatProvider, out object result) { result = null; try { - result = ConvertTo(valueToConvert, resultType, formatProvider); + using (typeConversion.TraceScope("Converting \"{0}\" to \"{1}\".", valueToConvert, resultType)) + { + if (resultType == null) + { + return false; + } + + var conversion = FigureConversion(valueToConvert, resultType, out bool debase); + if (conversion.Rank == ConversionRank.None) + { + return false; + } + + result = conversion.Invoke( + debase ? PSObject.Base(valueToConvert) : valueToConvert, + resultType, + recurse: true, + debase ? (PSObject)valueToConvert : null, + formatProvider, + backupTable: null); + + return true; + } } catch (InvalidCastException) { return false; } - catch (ArgumentException) - { - return false; - } - return true; } #endregion public type conversion @@ -1768,9 +1882,9 @@ 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) + internal EnumHashEntry(string[] names, Array values, UInt64 allValues, bool hasNegativeValue, bool hasFlagsAttribute) { this.names = names; this.values = values; @@ -1779,7 +1893,7 @@ internal EnumHashEntry(String[] names, Array values, UInt64 allValues, bool hasN this.hasFlagsAttribute = hasFlagsAttribute; } - internal String[] names; + internal string[] names; internal Array values; internal UInt64 allValues; internal bool hasNegativeValue; @@ -1789,6 +1903,7 @@ internal EnumHashEntry(String[] names, Array values, UInt64 allValues, bool hasN // This static is thread safe based on the lock in GetEnumHashEntry // It can be shared by Runspaces in different MiniShells private static readonly Dictionary s_enumTable = new Dictionary(); + private const int maxEnumTableSize = 100; private static EnumHashEntry GetEnumHashEntry(Type enumType) @@ -1805,6 +1920,7 @@ private static EnumHashEntry GetEnumHashEntry(Type enumType) { s_enumTable.Clear(); } + UInt64 allValues = 0; bool hasNegativeValue = false; Array values = Enum.GetValues(enumType); @@ -1837,7 +1953,7 @@ private static EnumHashEntry GetEnumHashEntry(Type enumType) // See if the [Flag] attribute is set on this type... // MemberInfo.GetCustomAttributes returns IEnumerable in CoreCLR. - bool hasFlagsAttribute = enumType.GetTypeInfo().GetCustomAttributes(typeof(FlagsAttribute), false).Any(); + bool hasFlagsAttribute = enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0; returnValue = new EnumHashEntry(Enum.GetNames(enumType), values, allValues, hasNegativeValue, hasFlagsAttribute); s_enumTable.Add(enumType, returnValue); @@ -1847,14 +1963,14 @@ private static EnumHashEntry GetEnumHashEntry(Type enumType) public override bool CanConvertFrom(object sourceValue, Type destinationType) { - return sourceValue is string && destinationType.GetTypeInfo().IsEnum; + return sourceValue is string && destinationType.IsEnum; } /// /// Checks if the enumValue is defined or not in enumType. /// - /// some enumeration - /// supposed to be an integer + /// Some enumeration. + /// Supposed to be an integer. /// /// private static bool IsDefinedEnum(object enumValue, Type enumType) @@ -1921,9 +2037,9 @@ private static bool IsDefinedEnum(object enumValue, Type enumType) /// Throws if the enumType enumeration has no negative values, but the enumValue is not /// defined in enumType. /// - /// some enumeration - /// supposed to be an integer - /// the error id to be used when throwing an exception + /// Some enumeration. + /// Supposed to be an integer. + /// The error id to be used when throwing an exception. internal static void ThrowForUndefinedEnum(string errorId, object enumValue, Type enumType) { ThrowForUndefinedEnum(errorId, enumValue, enumValue, enumType); @@ -1933,10 +2049,10 @@ internal static void ThrowForUndefinedEnum(string errorId, object enumValue, Typ /// Throws if the enumType enumeration has no negative values, but the enumValue is not /// defined in enumType. /// - /// The error id to be used when throwing an exception - /// value to validate - /// value to use while throwing an exception - /// the enum type to validate the enumValue with. + /// The error id to be used when throwing an exception. + /// Value to validate. + /// Value to use while throwing an exception. + /// The enum type to validate the enumValue with. /// /// is used by those callers who want the exception /// to contain a different value than the one that is validated. @@ -1960,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); @@ -1968,20 +2100,21 @@ 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, sourceValue, ObjectToTypeNameString(sourceValue), destinationType); } - Diagnostics.Assert(destinationType.GetTypeInfo().IsEnum, "EnumSingleTypeConverter is only applied to enumerations"); + + Diagnostics.Assert(destinationType.IsEnum, "EnumSingleTypeConverter is only applied to enumerations"); if (sourceValueString.Length == 0) { throw new PSInvalidCastException("InvalidCastEnumFromEmptyString", null, ExtendedTypeSystem.InvalidCastException, sourceValue, ObjectToTypeNameString(sourceValue), destinationType); } + sourceValueString = sourceValueString.Trim(); if (sourceValueString.Length == 0) { @@ -1990,7 +2123,7 @@ protected static object BaseConvertFrom(object sourceValue, Type destinationType sourceValue, ObjectToTypeNameString(sourceValue), destinationType); } - if (Char.IsDigit(sourceValueString[0]) || sourceValueString[0] == '+' || sourceValueString[0] == '-') + if (char.IsDigit(sourceValueString[0]) || sourceValueString[0] == '+' || sourceValueString[0] == '-') { Type underlyingType = Enum.GetUnderlyingType(destinationType); try @@ -2009,12 +2142,13 @@ 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, sourceValue, destinationType); } + sourceValueEntries = new string[] { sourceValueString }; fromValuePatterns = new WildcardPattern[1]; if (WildcardPattern.ContainsWildcardCharacters(sourceValueString)) @@ -2028,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++) { @@ -2073,9 +2207,10 @@ protected static object BaseConvertFrom(object sourceValue, Type destinationType } else { - if (String.Compare(sourceValueEntry, name, ignoreCaseOpt) != 0) + if (!string.Equals(sourceValueEntry, name, ignoreCaseOpt)) continue; } + if (!multipleValues && foundOne) { object firstValue = Enum.ToObject(destinationType, returnUInt64); @@ -2084,9 +2219,11 @@ protected static object BaseConvertFrom(object sourceValue, Type destinationType ExtendedTypeSystem.InvalidCastExceptionEnumerationMoreThanOneValue, sourceValue, destinationType, firstValue, secondValue); } + foundOne = true; returnUInt64 |= Convert.ToUInt64(values.GetValue(j), CultureInfo.CurrentCulture); } + if (!foundOne) { throw new PSInvalidCastException("InvalidCastEnumStringNotFound", null, @@ -2112,13 +2249,13 @@ public override object ConvertTo(object sourceValue, Type destinationType, IForm /// /// There might be many cast operators in a Type A that take Type A. Each operator will have a /// different return type. Because of that we cannot call GetMethod since it would cause a - /// AmbiguousMatchException. This auxiliary method calls GetMember to find the right method + /// AmbiguousMatchException. This auxiliary method calls GetMember to find the right method. /// - /// Either op_Explicit or op_Implicit, at the moment - /// the type to look for an operator - /// Type of the only parameter the operator method should have - /// Return type of the operator method - /// A cast operator method, or null if not found + /// Either op_Explicit or op_Implicit, at the moment. + /// The type to look for an operator. + /// Type of the only parameter the operator method should have. + /// Return type of the operator method. + /// A cast operator method, or null if not found. private static MethodInfo FindCastOperator(string methodName, Type targetType, Type originalType, Type resultType) { using (typeConversion.TraceScope("Looking for \"{0}\" cast operator.", methodName)) @@ -2132,14 +2269,17 @@ private static MethodInfo FindCastOperator(string methodName, Type targetType, T { continue; } + System.Reflection.ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 1 || !parameters[0].ParameterType.IsAssignableFrom(originalType)) { continue; } + typeConversion.WriteLine("Found \"{0}\" cast operator in type {1}.", methodName, targetType.FullName); return method; } + typeConversion.TraceScope("Cast operator for \"{0}\" not found.", methodName); return null; } @@ -2157,15 +2297,7 @@ private static object ConvertNumericThroughDouble(object valueToConvert, Type re } } -#if !CORECLR - // No Following Types In CoreCLR - // ManagementObject - // ManagementObjectSearcher - // ManagementClass - // CommaDelimitedStringCollection - // DirectoryEntry - #region Converters_Not_Available_In_CorePS - +#if !UNIX private static ManagementObject ConvertToWMI(object valueToConvert, Type resultType, bool recursion, @@ -2290,20 +2422,6 @@ private static ManagementClass ConvertToWMIClass(object valueToConvert, } } - // System.Configuration.CommaDelimitedStringCollection is derived from the StringCollection class - private static System.Configuration.CommaDelimitedStringCollection ConvertToCommaDelimitedStringCollection(object valueToConvert, - Type resultType, - bool recursion, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) - { - typeConversion.WriteLine("Standard type conversion to a CommaDelimitedStringCollection."); - var commaDelimitedStringCollection = new System.Configuration.CommaDelimitedStringCollection(); - AddItemsToCollection(valueToConvert, resultType, formatProvider, backupTable, commaDelimitedStringCollection); - return commaDelimitedStringCollection; - } - private static DirectoryEntry ConvertToADSI(object valueToConvert, Type resultType, bool recursion, @@ -2361,8 +2479,6 @@ private static DirectorySearcher ConvertToADSISearcher(object valueToConvert, valueToConvert.ToString(), resultType.ToString(), e.Message); } } - - #endregion Converters_Not_Available_In_CorePS #endif private static StringCollection ConvertToStringCollection(object valueToConvert, @@ -2500,6 +2616,7 @@ private static bool IsCustomTypeConversion(object valueToConvert, typeConversion.WriteLine("TypeConverter cannot convert to resultType."); } } + PSTypeConverter valuePSTypeConverter = valueConverter as PSTypeConverter; if (valuePSTypeConverter != null) { @@ -2527,6 +2644,7 @@ private static bool IsCustomTypeConversion(object valueToConvert, } } } + s_tracer.WriteLine("No converter found in original type."); // now ConvertFrom for the destination type @@ -2558,6 +2676,7 @@ private static bool IsCustomTypeConversion(object valueToConvert, typeConversion.WriteLine("Destination type's converter cannot convert from originalType."); } } + PSTypeConverter valuePSTypeConverter = valueConverter as PSTypeConverter; if (valuePSTypeConverter != null) { @@ -2585,6 +2704,7 @@ private static bool IsCustomTypeConversion(object valueToConvert, } } } + result = null; return false; } @@ -2715,6 +2835,7 @@ private static Type ConvertStringToType(object valueToConvert, ExtendedTypeSystem.InvalidCastException, valueToConvert.ToString(), ObjectToTypeNameString(valueToConvert), resultType.ToString()); } + return namedType; } @@ -2748,38 +2869,97 @@ private static Uri ConvertStringToUri(object valueToConvert, } } - private static object ConvertStringToInteger(object valueToConvert, - Type resultType, - bool recursion, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) + /// + /// Attempts to use Parser.ScanNumber to get the value of a numeric string. + /// + /// The string to convert to a number. + /// The resulting value type to convert to. + /// The resulting numeric value. + /// + /// True if the parse succeeds, false if a parse exception arises. + /// In all other cases, an exception will be thrown. + /// + private static bool TryScanNumber(string strToConvert, Type resultType, out object result) + { + 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( + parsedNumber, + resultType, + System.Globalization.CultureInfo.InvariantCulture.NumberFormat); + return true; + } + catch (Exception) + { + // Parse or convert failed + result = null; + return false; + } + } + + 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 { - return integerConverter.ConvertFrom(strToConvert); + if (TryScanNumber(strToConvert, resultType, out object result)) + { + return result; + } + + 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; } + typeConversion.WriteLine("Exception converting to integer: \"{0}\".", e.Message); if (e is FormatException) { @@ -2793,6 +2973,7 @@ private static object ConvertStringToInteger(object valueToConvert, typeConversion.WriteLine("Exception converting to integer through double: \"{0}\".", ex.Message); } } + throw new PSInvalidCastException("InvalidCastFromStringToInteger", e, ExtendedTypeSystem.InvalidCastExceptionWithInnerException, strToConvert, resultType.ToString(), e.Message); @@ -2807,8 +2988,9 @@ private static object ConvertStringToDecimal(object valueToConvert, TypeTable backupTable) { Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string"); + var strToConvert = valueToConvert as string; - if (((string)valueToConvert).Length == 0) + if (strToConvert.Length == 0) { typeConversion.WriteLine("Returning numeric zero."); // This is not wrapped in a try/catch because it can't fail. @@ -2818,8 +3000,15 @@ private static object ConvertStringToDecimal(object valueToConvert, typeConversion.WriteLine("Converting to decimal."); try { - return Convert.ChangeType(valueToConvert, resultType, - System.Globalization.CultureInfo.InvariantCulture.NumberFormat); + typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes"); + if (TryScanNumber(strToConvert, resultType, out object result)) + { + return result; + } + else + { + return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat); + } } catch (Exception e) { @@ -2828,16 +3017,17 @@ private static object ConvertStringToDecimal(object valueToConvert, { try { - return ConvertNumericThroughDouble(valueToConvert, resultType); + return ConvertNumericThroughDouble(strToConvert, resultType); } catch (Exception ex) { typeConversion.WriteLine("Exception converting to integer through double: \"{0}\".", ex.Message); } } + throw new PSInvalidCastException("InvalidCastFromStringToDecimal", e, ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), e.Message); + strToConvert, resultType.ToString(), e.Message); } } @@ -2849,8 +3039,9 @@ private static object ConvertStringToReal(object valueToConvert, TypeTable backupTable) { Diagnostics.Assert(valueToConvert is string, "Value to convert must be a string"); + var strToConvert = valueToConvert as string; - if (((string)valueToConvert).Length == 0) + if (strToConvert.Length == 0) { typeConversion.WriteLine("Returning numeric zero."); // This is not wrapped in a try/catch because it can't fail. @@ -2860,15 +3051,23 @@ private static object ConvertStringToReal(object valueToConvert, typeConversion.WriteLine("Converting to double or single."); try { - return Convert.ChangeType(valueToConvert, resultType, - System.Globalization.CultureInfo.InvariantCulture.NumberFormat); + typeConversion.WriteLine("Parsing string value to account for multipliers and type suffixes"); + + if (TryScanNumber(strToConvert, resultType, out object result)) + { + return result; + } + else + { + return Convert.ChangeType(strToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat); + } } catch (Exception e) { typeConversion.WriteLine("Exception converting to double or single: \"{0}\".", e.Message); throw new PSInvalidCastException("InvalidCastFromStringToDoubleOrSingle", e, ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), e.Message); + strToConvert, resultType.ToString(), e.Message); } } @@ -3005,7 +3204,7 @@ private static bool ConvertSByteToBool(object valueToConvert, IFormatProvider formatProvider, TypeTable backupTable) { - return ((SByte)valueToConvert) != default(SByte); + return ((sbyte)valueToConvert) != default(sbyte); } private static bool ConvertByteToBool(object valueToConvert, @@ -3015,7 +3214,7 @@ private static bool ConvertByteToBool(object valueToConvert, IFormatProvider formatProvider, TypeTable backupTable) { - return ((Byte)valueToConvert) != default(Byte); + return ((byte)valueToConvert) != default(byte); } private static bool ConvertSingleToBool(object valueToConvert, @@ -3035,7 +3234,7 @@ private static bool ConvertDoubleToBool(object valueToConvert, IFormatProvider formatProvider, TypeTable backupTable) { - return ((Double)valueToConvert) != default(Double); + return ((double)valueToConvert) != default(double); } private static bool ConvertDecimalToBool(object valueToConvert, @@ -3048,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"); @@ -3118,6 +3335,22 @@ private static string ConvertNumericToString(object valueToConvert, try { // Ignore formatProvider here, the conversion should be culture invariant. + var numberFormat = CultureInfo.InvariantCulture.NumberFormat; + if (valueToConvert is double dbl) + { + return dbl.ToString(DoublePrecision, numberFormat); + } + + if (valueToConvert is float sgl) + { + return sgl.ToString(SinglePrecision, numberFormat); + } + + if (valueToConvert is BigInteger b) + { + return b.ToString(numberFormat); + } + return (string)Convert.ChangeType(valueToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat); } catch (Exception e) @@ -3140,7 +3373,7 @@ private static string ConvertNonNumericToString(object valueToConvert, try { typeConversion.WriteLine("Converting object to string."); - return PSObject.ToStringParser(ecFromTLS, valueToConvert); + return PSObject.ToStringParser(ecFromTLS, valueToConvert, formatProvider); } catch (ExtendedTypeSystemException e) { @@ -3212,57 +3445,6 @@ private static Delegate ConvertScriptBlockToDelegate(object valueToConvert, valueToConvert.ToString(), resultType.ToString(), exception.Message); } - - private static Delegate ConvertPSMethodInfoToDelegate(object valueToConvert, - Type resultType, - bool recurse, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) - { - PSMethod psMethod; - try - { - psMethod = (PSMethod) valueToConvert; - - var maybeType = psMethod.instance as Type; - var methodInfoCandiates = maybeType != null - ? maybeType.GetMethods(BindingFlags.Static | BindingFlags.Public) - : psMethod.instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public); - - var targetMethodInfo = resultType.GetMethod("Invoke"); - - var comparator = new DelegateArgsComparator(targetMethodInfo); - - foreach (var candidate in methodInfoCandiates) - { - if (candidate.Name != psMethod.Name) - { - continue; - } - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) - { - return maybeType != null - ? candidate.CreateDelegate(resultType) - : candidate.CreateDelegate(resultType, psMethod.instance); - } - } - } - catch (Exception e) - { - typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), e.Message); - } - - var msg = String.Format(ExtendedTypeSystem.PSMethodToDelegateNoMatchingOverLoad, psMethod, resultType); - typeConversion.WriteLine($"PSMethod to Delegate exception: \"{msg}\"."); - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", null, - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), msg); - } - private static object ConvertToNullable(object valueToConvert, Type resultType, bool recursion, @@ -3325,6 +3507,7 @@ private static object ConvertEnumerableToArray(object valueToConvert, // false means no further recursions and therefore no cycles result.Add(ConvertTo(obj, resultElementType, false, formatProvider, backupTable)); } + return result.ToArray(resultElementType); } catch (Exception e) @@ -3498,13 +3681,74 @@ private static object ConvertEnumerableToEnum(object valueToConvert, e.Current, resultType, EnumSingleTypeConverter.EnumValues(resultType)); } } + sbResult.Append(current); } return ConvertStringToEnum(sbResult.ToString(), resultType, recursion, originalValueToConvert, formatProvider, backupTable); } - private class ConvertViaParseMethod + private sealed class PSMethodToDelegateConverter + { + // Index of the matching overload method. + private readonly int _matchIndex; + // Size of the cache. It's rare to have more than 10 overloads for a method. + private const int CacheSize = 10; + + private static readonly PSMethodToDelegateConverter[] s_converterCache = new PSMethodToDelegateConverter[CacheSize]; + + private PSMethodToDelegateConverter(int matchIndex) + { + _matchIndex = matchIndex; + } + + internal static PSMethodToDelegateConverter GetConverter(int matchIndex) + { + if (matchIndex >= CacheSize) { return new PSMethodToDelegateConverter(matchIndex); } + + var result = s_converterCache[matchIndex]; + if (result == null) + { + // If the cache entry is null, generate a new instance for the cache slot. + var converter = new PSMethodToDelegateConverter(matchIndex); + Threading.Interlocked.CompareExchange(ref s_converterCache[matchIndex], converter, null); + result = s_converterCache[matchIndex]; + } + + return result; + } + + internal Delegate Convert(object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) + { + // We can only possibly convert PSMethod instance of the type PSMethod. + // Such a PSMethod essentially represents a set of .NET method overloads. + var psMethod = (PSMethod)valueToConvert; + + try + { + var methods = (MethodCacheEntry)psMethod.adapterData; + var isStatic = psMethod.instance is Type; + + var candidate = (MethodInfo)methods.methodInformationStructures[_matchIndex].method; + return isStatic ? candidate.CreateDelegate(resultType) + : candidate.CreateDelegate(resultType, psMethod.instance); + } + catch (Exception e) + { + typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); + throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, + ExtendedTypeSystem.InvalidCastExceptionWithInnerException, + valueToConvert.ToString(), resultType.ToString(), e.Message); + } + } + } + + private sealed class ConvertViaParseMethod { // TODO - use an ETS wrapper that generates a dynamic method internal MethodInfo parse; @@ -3570,7 +3814,7 @@ internal object ConvertWithoutCulture(object valueToConvert, } } - private class ConvertViaConstructor + private sealed class ConvertViaConstructor { internal Func TargetCtorLambda; @@ -3609,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; @@ -3707,7 +3951,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaNoArgumentConstructor + private sealed class ConvertViaNoArgumentConstructor { private readonly Func _constructor; @@ -3739,31 +3983,51 @@ internal object Convert(object valueToConvert, ExecutionContext ecFromTLS = LocalPipeline.GetExecutionContextFromTLS(); object result = null; - if (ecFromTLS == null || ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage) + // Setting arbitrary properties is dangerous, so we allow this only if + // - It's running on a thread without Runspace; Or + // - 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. + bool canProceedWithConversion = ecFromTLS == null || (ecFromTLS.LanguageMode == PSLanguageMode.FullLanguage && !ecFromTLS.LanguageModeTransitionInParameterBinding); + if (!canProceedWithConversion) { - 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 + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) { - // 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) @@ -3804,7 +4068,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaCast + private sealed class ConvertViaCast { internal MethodInfo cast; @@ -3884,8 +4148,7 @@ private static object ConvertNumericIConvertible(object valueToConvert, } } - - private class ConvertCheckingForCustomConverter + private sealed class ConvertCheckingForCustomConverter { internal PSConverter tryfirstConverter; internal PSConverter fallbackConverter; @@ -3928,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); } @@ -3960,7 +4231,7 @@ private static string ConvertNullToString(object valueToConvert, { typeConversion.WriteLine("Converting null to \"\"."); // if the destination type is string, return an empty string... - return String.Empty; + return string.Empty; } private static PSReference ConvertNullToPSReference(object valueToConvert, @@ -4079,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; @@ -4096,21 +4367,24 @@ internal delegate T PSConverter(object valueToConvert, internal delegate object PSNullConverter(object nullOrAutomationNull); - internal interface ConversionData +#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 : ConversionData + internal class ConversionData : IConversionData { private readonly PSConverter _converter; @@ -4133,13 +4407,12 @@ 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 ConversionData CacheConversion(Type fromType, Type toType, PSConverter converter, ConversionRank rank) + private static IConversionData CacheConversion(Type fromType, Type toType, PSConverter converter, ConversionRank rank) { ConversionTypePair pair = new ConversionTypePair(fromType, toType); - ConversionData data = null; + IConversionData data = null; lock (s_converterCache) { if (!s_converterCache.TryGetValue(pair, out data)) @@ -4153,14 +4426,15 @@ private static ConversionData CacheConversion(Type fromType, Type toType, PSC "Existing conversion isn't the same as new conversion"); } } + return data; } - private static ConversionData GetConversionData(Type fromType, Type toType) + private static IConversionData GetConversionData(Type fromType, Type toType) { lock (s_converterCache) { - ConversionData result = null; + IConversionData result = null; s_converterCache.TryGetValue(new ConversionTypePair(fromType, toType), out result); return result; } @@ -4171,25 +4445,25 @@ 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), - typeof(Single), typeof(Double), typeof(Decimal) + typeof(sbyte), typeof(byte), + typeof(Single), typeof(double), typeof(decimal), + 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) + 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 Type[] s_realTypes = new Type[] { typeof(Single), typeof(Double), typeof(Decimal) }; + 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 readonly Type[] s_realTypes = new Type[] { typeof(Single), typeof(double), typeof(decimal) }; internal static void RebuildConversionCache() { @@ -4201,7 +4475,7 @@ internal static void RebuildConversionCache() Type typeofNull = typeof(Null); Type typeofFloat = typeof(float); Type typeofDouble = typeof(double); - Type typeofDecimal = typeof(Decimal); + Type typeofDecimal = typeof(decimal); Type typeofBool = typeof(bool); Type typeofChar = typeof(char); foreach (Type type in LanguagePrimitives.s_numericTypes) @@ -4211,17 +4485,20 @@ 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); CacheConversion(typeof(UInt16), typeofBool, ConvertUInt16ToBool, ConversionRank.Language); CacheConversion(typeof(UInt32), typeofBool, ConvertUInt32ToBool, ConversionRank.Language); CacheConversion(typeof(UInt64), typeofBool, ConvertUInt64ToBool, ConversionRank.Language); - CacheConversion(typeof(SByte), typeofBool, ConvertSByteToBool, ConversionRank.Language); - CacheConversion(typeof(Byte), typeofBool, ConvertByteToBool, ConversionRank.Language); + CacheConversion(typeof(sbyte), typeofBool, ConvertSByteToBool, ConversionRank.Language); + CacheConversion(typeof(byte), typeofBool, ConvertByteToBool, ConversionRank.Language); CacheConversion(typeof(Single), typeofBool, ConvertSingleToBool, ConversionRank.Language); - CacheConversion(typeof(Double), typeofBool, ConvertDoubleToBool, ConversionRank.Language); - CacheConversion(typeof(Decimal), typeofBool, ConvertDecimalToBool, 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++) { @@ -4264,6 +4541,7 @@ internal static void RebuildConversionCache() LanguagePrimitives.ConvertNumeric, ConversionRank.NumericExplicit); } } + foreach (Type integerType in s_integerTypes) { CacheConversion(typeofString, integerType, LanguagePrimitives.ConvertStringToInteger, ConversionRank.NumericString); @@ -4275,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); @@ -4306,7 +4586,8 @@ internal static void RebuildConversionCache() CacheConversion(typeofString, typeofBool, LanguagePrimitives.ConvertStringToBool, ConversionRank.Language); CacheConversion(typeof(SwitchParameter), typeofBool, LanguagePrimitives.ConvertSwitchParameterToBool, ConversionRank.Language); -#if !CORECLR // No DirectoryService && WMIv1 In CoreCLR +#if !UNIX + // Conversions to WMI and ADSI CacheConversion(typeofString, typeof(ManagementObjectSearcher), LanguagePrimitives.ConvertToWMISearcher, ConversionRank.Language); CacheConversion(typeofString, typeof(ManagementClass), LanguagePrimitives.ConvertToWMIClass, ConversionRank.Language); CacheConversion(typeofString, typeof(ManagementObject), LanguagePrimitives.ConvertToWMI, ConversionRank.Language); @@ -4410,7 +4691,8 @@ 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 { PSObject propertyValue = prop.Value as PSObject; @@ -4433,6 +4715,13 @@ 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 @@ -4466,24 +4755,31 @@ 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 + ">]"); - if (first == true) + if (first) { first = false; } @@ -4493,7 +4789,7 @@ private static String GetAvailableProperties(PSObject pso) return availableProperties.ToString(); } - internal static ConversionData FigureConversion(object valueToConvert, Type resultType, out bool debase) + internal static IConversionData FigureConversion(object valueToConvert, Type resultType, out bool debase) { PSObject valueAsPsObj; Type originalType; @@ -4510,7 +4806,7 @@ internal static ConversionData FigureConversion(object valueToConvert, Type resu debase = false; - ConversionData data = FigureConversion(originalType, resultType); + IConversionData data = FigureConversion(originalType, resultType); if (data.Rank != ConversionRank.None) { return data; @@ -4548,20 +4844,19 @@ internal static ConversionData FigureConversion(object valueToConvert, Type resu } /// - /// /// - /// the same as in the public version - /// the same as in the public version - /// true if we should perform any recursive calls to ConvertTo - /// governing conversion of types + /// The same as in the public version. + /// The same as in the public version. + /// True if we should perform any recursive calls to ConvertTo. + /// Governing conversion of types. /// /// Used by Remoting Rehydration Logic. While Deserializing a remote object, /// LocalPipeline.ExecutionContextFromTLS() might return null..In which case this /// TypeTable will be used to do the conversion. /// - /// the value converted - /// if resultType is null - /// if the conversion failed + /// The value converted. + /// If resultType is null. + /// If the conversion failed. internal static object ConvertTo(object valueToConvert, Type resultType, bool recursion, @@ -4572,20 +4867,24 @@ internal static object ConvertTo(object valueToConvert, { if (resultType == null) { - throw PSTraceSource.NewArgumentNullException("resultType"); + throw PSTraceSource.NewArgumentNullException(nameof(resultType)); } bool debase; var conversion = FigureConversion(valueToConvert, resultType, out debase); - return conversion.Invoke(debase ? PSObject.Base(valueToConvert) : valueToConvert, - resultType, recursion, debase ? (PSObject)valueToConvert : null, - formatProvider, backupTypeTable); + return conversion.Invoke( + debase ? PSObject.Base(valueToConvert) : valueToConvert, + resultType, + recursion, + debase ? (PSObject)valueToConvert : null, + formatProvider, + backupTypeTable); } } /// - /// Get the errorId and errorMessage for an InvalidCastException + /// Get the errorId and errorMessage for an InvalidCastException. /// /// /// @@ -4595,9 +4894,17 @@ internal static object ConvertTo(object valueToConvert, internal static Tuple GetInvalidCastMessages(object valueToConvert, Type resultType) { string errorId, errorMsg; + if (resultType.IsByRefLike) + { + typeConversion.WriteLine("Cannot convert to ByRef-Like types as they should be used on stack only."); + errorId = nameof(ExtendedTypeSystem.InvalidCastToByRefLikeType); + errorMsg = StringUtil.Format(ExtendedTypeSystem.InvalidCastToByRefLikeType, resultType); + return Tuple.Create(errorId, errorMsg); + } + if (PSObject.Base(valueToConvert) == null) { - if (resultType.GetTypeInfo().IsEnum) + if (resultType.IsEnum) { typeConversion.WriteLine("Issuing an error message about not being able to convert null to an Enum type."); // a nice error message specifically for null being converted to enum @@ -4616,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); } @@ -4638,7 +4963,7 @@ internal static object ThrowInvalidConversionException(object valueToConvert, Ty throw new PSInvalidCastException("ConversionSupportedOnlyToCoreTypes", null, ExtendedTypeSystem.InvalidCastExceptionNonCoreType, resultType.ToString()); } - private static ConversionData FigureLanguageConversion(Type fromType, Type toType, + private static IConversionData FigureLanguageConversion(Type fromType, Type toType, out PSConverter valueDependentConversion, out ConversionRank valueDependentRank) { @@ -4648,7 +4973,7 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp Type underlyingType = Nullable.GetUnderlyingType(toType); if (underlyingType != null) { - ConversionData nullableConversion = FigureConversion(fromType, underlyingType); + IConversionData nullableConversion = FigureConversion(fromType, underlyingType); if (nullableConversion.Rank != ConversionRank.None) { return CacheConversion(fromType, toType, LanguagePrimitives.ConvertToNullable, nullableConversion.Rank); @@ -4667,11 +4992,11 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp { converter = LanguagePrimitives.ConvertIListToBool; } - else if (fromType.GetTypeInfo().IsEnum) + else if (fromType.IsEnum) { converter = LanguagePrimitives.CreateNumericToBoolConverter(fromType); } - else if (fromType.GetTypeInfo().IsValueType) + else if (fromType.IsValueType) { converter = LanguagePrimitives.ConvertValueToBool; } @@ -4679,12 +5004,13 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp { converter = LanguagePrimitives.ConvertClassToBool; } + return CacheConversion(fromType, toType, converter, ConversionRank.Language); } if (toType == typeof(string)) { - Dbg.Assert(!LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(fromType)) || fromType.GetTypeInfo().IsEnum, + Dbg.Assert(!LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(fromType)) || fromType.IsEnum, "Number to string should be cached on initialization of cache table"); return CacheConversion(fromType, toType, LanguagePrimitives.ConvertNonNumericToString, ConversionRank.ToString); } @@ -4708,7 +5034,7 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp return CacheConversion(fromType, toType, LanguagePrimitives.ConvertEnumerableToArray, ConversionRank.Language); } - ConversionData data = FigureConversion(fromType, toElementType); + IConversionData data = FigureConversion(fromType, toElementType); if (data.Rank != ConversionRank.None) { valueDependentRank = data.Rank & ConversionRank.ValueDependent; @@ -4762,14 +5088,6 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp return CacheConversion(fromType, toType, LanguagePrimitives.ConvertToStringCollection, rank); } -#if !CORECLR // No CommaDelimitedStringCollection In CoreCLR - if (toType == typeof(System.Configuration.CommaDelimitedStringCollection)) - { - ConversionRank rank = (fromType.IsArray || IsTypeEnumerable(fromType)) ? ConversionRank.Language : ConversionRank.LanguageS2A; - return CacheConversion(fromType, toType, LanguagePrimitives.ConvertToCommaDelimitedStringCollection, rank); - } -#endif - if (toType.IsSubclassOf(typeof(System.Delegate)) && (fromType == typeof(ScriptBlock) || fromType.IsSubclassOf(typeof(ScriptBlock)))) { @@ -4780,79 +5098,162 @@ private static ConversionData FigureLanguageConversion(Type fromType, Type toTyp { Type actualResultType = typeof(PSObject); - ConstructorInfo resultConstructor = actualResultType.GetConstructor(PSTypeExtensions.EmptyTypes); + ConstructorInfo resultConstructor = actualResultType.GetConstructor(Type.EmptyTypes); var converterObj = new ConvertViaNoArgumentConstructor(resultConstructor, actualResultType); return CacheConversion(fromType, toType, converterObj.Convert, ConversionRank.Language); } TypeCode fromTypeCode = LanguagePrimitives.GetTypeCode(fromType); - if (LanguagePrimitives.IsInteger(fromTypeCode) && toType.GetTypeInfo().IsEnum) + if (LanguagePrimitives.IsInteger(fromTypeCode) && toType.IsEnum) { return CacheConversion(fromType, toType, LanguagePrimitives.ConvertIntegerToEnum, ConversionRank.Language); } - if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate))) + if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate)) && !toType.IsAbstract) { - var mi = toType.GetMethod("Invoke"); - - var comparator = new DelegateArgsComparator(mi); + var targetMethod = toType.GetMethod("Invoke"); + var comparator = new SignatureComparator(targetMethod); var signatureEnumerator = new PSMethodSignatureEnumerator(fromType); + int index = -1, matchedIndex = -1; + while (signatureEnumerator.MoveNext()) { - var candidate = signatureEnumerator.Current.GetMethod("Invoke"); + index++; + var signatureType = signatureEnumerator.Current; + // Skip the non-bindable signatures + if (signatureType == typeof(Func)) { continue; } - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) + Type[] argumentTypes = signatureType.GenericTypeArguments; + if (comparator.ProjectedSignatureMatchesTarget(argumentTypes, out bool signaturesMatchExactly)) { - return CacheConversion(fromType, toType, LanguagePrimitives.ConvertPSMethodInfoToDelegate, ConversionRank.Language); + if (signaturesMatchExactly) + { + // We prefer the signature that exactly matches the target delegate. + matchedIndex = index; + break; + } + + // If there is no exact match, then we use the first compatible signature we found. + if (matchedIndex == -1) { matchedIndex = index; } } } + + if (matchedIndex > -1) + { + // We got the index of the matching method signature based on the PSMethod<..> type. + // Signatures in PSMethod<..> type were constructed based on the array of method overloads, + // in the exact order. So we can use this index directly to locate the matching overload in + // the converter, without having to compare the signature again. + var converter = PSMethodToDelegateConverter.GetConverter(matchedIndex); + return CacheConversion(fromType, toType, converter.Convert, ConversionRank.Language); + } } return null; } - struct DelegateArgsComparator + private readonly struct SignatureComparator { - private readonly ParameterInfo[] _targetParametersInfos; - private readonly Type _returnType; - - public DelegateArgsComparator(MethodInfo targetMethodInfo) + private enum TypeMatchingContext { - _returnType = targetMethodInfo.ReturnType; - _targetParametersInfos = targetMethodInfo.GetParameters(); + ReturnType, + ParameterType, + OutParameterType } - public bool SignatureMatches(Type returnType, ParameterInfo[] arguments) + private readonly ParameterInfo[] targetParameters; + private readonly Type targetReturnType; + + internal SignatureComparator(MethodInfo targetMethodInfo) { - return ReturnTypeMatches(returnType) && ParameterTypesMatches(arguments); + targetReturnType = targetMethodInfo.ReturnType; + targetParameters = targetMethodInfo.GetParameters(); } - private bool ReturnTypeMatches(Type returnType) - { - return PSMethod.MatchesPSMethodProjectedType(_returnType, returnType, testAssignment: true); + /// + /// Check if a projected signature matches the target method. + /// + /// + /// The type arguments from the metadata type 'Func[..]' that represents the projected signature. + /// It contains the return type as the last item in the array. + /// + /// + /// Set by this method to indicate if it's an exact match. + /// + internal bool ProjectedSignatureMatchesTarget(Type[] argumentTypes, out bool signaturesMatchExactly) + { + signaturesMatchExactly = false; + int length = argumentTypes.Length; + if (length != targetParameters.Length + 1) { return false; } + + bool typesMatchExactly, allTypesMatchExactly; + Type sourceReturnType = argumentTypes[length - 1]; + + if (ProjectedTypeMatchesTargetType(sourceReturnType, targetReturnType, TypeMatchingContext.ReturnType, out typesMatchExactly)) + { + allTypesMatchExactly = typesMatchExactly; + for (int i = 0; i < targetParameters.Length; i++) + { + var targetParam = targetParameters[i]; + var sourceType = argumentTypes[i]; + var matchContext = targetParam.IsOut ? TypeMatchingContext.OutParameterType : TypeMatchingContext.ParameterType; + + if (!ProjectedTypeMatchesTargetType(sourceType, targetParam.ParameterType, matchContext, out typesMatchExactly)) + { + return false; + } + + allTypesMatchExactly &= typesMatchExactly; + } + + signaturesMatchExactly = allTypesMatchExactly; + return true; + } + + return false; } - private bool ParameterTypesMatches(ParameterInfo[] arguments) + private static bool ProjectedTypeMatchesTargetType(Type sourceType, Type targetType, TypeMatchingContext matchContext, out bool matchExactly) { - var argsCount = _targetParametersInfos.Length; - // void is encoded as typeof(Unit) in the PSMethod> as the last parameter - if (arguments.Length != argsCount) + matchExactly = false; + if (targetType.IsByRef || targetType.IsPointer) { + if (!sourceType.IsGenericType) { return false; } + + var sourceTypeDef = sourceType.GetGenericTypeDefinition(); + bool isOutParameter = matchContext == TypeMatchingContext.OutParameterType; + + if (targetType.IsByRef && sourceTypeDef == (isOutParameter ? typeof(PSOutParameter<>) : typeof(PSReference<>)) || + targetType.IsPointer && sourceTypeDef == typeof(PSPointer<>)) + { + // For ref/out parameter types and pointer types, the element types need to match exactly. + if (targetType.GetElementType() == sourceType.GenericTypeArguments[0]) + { + matchExactly = true; + return true; + } + } + return false; } - for (int i = 0; i < arguments.Length; i++) + + if (targetType == sourceType || + targetType == typeof(void) && sourceType == typeof(VOID) || + targetType == typeof(TypedReference) && sourceType == typeof(PSTypedReference)) { - var arg = arguments[i]; - var argType = arg.ParameterType; - var targetParamType = _targetParametersInfos[i].ParameterType; - var isOut = (arg.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out; - if (!PSMethod.MatchesPSMethodProjectedType(targetParamType, argType, isOut: isOut)) - { - return false; - } + matchExactly = true; + return true; } - return true; + + if (targetType == typeof(void) || targetType == typeof(TypedReference)) + { + return false; + } + + return matchContext == TypeMatchingContext.ReturnType + ? targetType.IsAssignableFrom(sourceType) + : sourceType.IsAssignableFrom(targetType); } } @@ -4871,7 +5272,7 @@ private static PSConverter FigureStaticCreateMethodConversion(Type fromT private static PSConverter FigureParseConversion(Type fromType, Type toType) { - if (toType.GetTypeInfo().IsEnum) + if (toType.IsEnum) { if (fromType == typeof(string)) { @@ -4900,6 +5301,7 @@ private static PSConverter FigureParseConversion(Type fromType, Type toT { typeConversion.WriteLine("Exception finding Parse method with CultureInfo: \"{0}\".", e.Message); } + if (parse != null) { ConvertViaParseMethod converter = new ConvertViaParseMethod(); @@ -4934,7 +5336,7 @@ private static PSConverter FigureParseConversion(Type fromType, Type toT /// /// Figure conversion when following conditions are satisfied: /// 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 + /// 2. fromType is System.Array, System.Object[] or it's the same as the element type of toType. /// /// /// @@ -4942,8 +5344,7 @@ private static PSConverter FigureParseConversion(Type fromType, Type toT internal static Tuple, ConversionRank> FigureIEnumerableConstructorConversion(Type fromType, Type toType) { // Win8: 653180. If toType is an Abstract type then we cannot construct it anyway. So, bailing out fast. - TypeInfo toTypeInfo = toType.GetTypeInfo(); - if (toTypeInfo.IsAbstract == true) + if (toType.IsAbstract) { return null; } @@ -4955,7 +5356,7 @@ internal static Tuple, ConversionRank> FigureIEnumerableCons Type elementType = null; ConstructorInfo resultConstructor = null; - if (toTypeInfo.IsGenericType && !toTypeInfo.ContainsGenericParameters && + if (toType.IsGenericType && !toType.ContainsGenericParameters && (typeof(IList).IsAssignableFrom(toType) || typeof(ICollection).IsAssignableFrom(toType) || typeof(IEnumerable).IsAssignableFrom(toType))) @@ -4968,6 +5369,7 @@ internal static Tuple, ConversionRank> FigureIEnumerableCons "toType has more than one generic arguments. Here we only care about the toType which contains only one generic argument and whose constructor takes IEnumerable, ICollection or IList."); return null; } + elementType = argTypes[0]; if (typeof(Array) == fromType || typeof(object[]) == fromType || @@ -5096,7 +5498,7 @@ internal static PSConverter FigureConstructorConversion(Type fromType, T { ParameterInfo[] targetParams = resultConstructor.GetParameters(); Type targetParamType = targetParams[0].ParameterType; - bool useExplicitConversion = targetParamType.GetTypeInfo().IsValueType && fromType != targetParamType && Nullable.GetUnderlyingType(targetParamType) == null; + bool useExplicitConversion = targetParamType.IsValueType && fromType != targetParamType && Nullable.GetUnderlyingType(targetParamType) == null; converter.TargetCtorLambda = CreateCtorLambdaClosure(resultConstructor, targetParamType, useExplicitConversion); } catch (Exception e) @@ -5124,8 +5526,7 @@ private static bool IsIntegralType(Type type) internal static PSConverter FigurePropertyConversion(Type fromType, Type toType, ref ConversionRank rank) { - TypeInfo toTypeInfo = toType.GetTypeInfo(); - if ((!typeof(PSObject).IsAssignableFrom(fromType)) || (toTypeInfo.IsAbstract)) + if ((!typeof(PSObject).IsAssignableFrom(fromType)) || (toType.IsAbstract)) { return null; } @@ -5133,7 +5534,7 @@ internal static PSConverter FigurePropertyConversion(Type fromType, Type ConstructorInfo toConstructor = null; try { - toConstructor = toType.GetConstructor(PSTypeExtensions.EmptyTypes); + toConstructor = toType.GetConstructor(Type.EmptyTypes); } catch (AmbiguousMatchException e) { @@ -5144,7 +5545,7 @@ internal static PSConverter FigurePropertyConversion(Type fromType, Type typeConversion.WriteLine("Exception finding Constructor: \"{0}\".", e.Message); } - if (toConstructor == null && !toTypeInfo.IsValueType) + if (toConstructor == null && !toType.IsValueType) { return null; } @@ -5214,8 +5615,8 @@ private static bool TypeConverterPossiblyExists(Type type) } // GetCustomAttributes returns IEnumerable in CoreCLR - var typeConverters = type.GetTypeInfo().GetCustomAttributes(typeof(TypeConverterAttribute), false); - if (typeConverters.Any()) + var typeConverters = type.GetCustomAttributes(typeof(TypeConverterAttribute), false); + if (typeConverters.Length > 0) { return true; } @@ -5223,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} @@ -5236,9 +5637,10 @@ internal class InternalPSCustomObject } internal class InternalPSObject : PSObject { } - internal static ConversionData FigureConversion(Type fromType, Type toType) + + internal static IConversionData FigureConversion(Type fromType, Type toType) { - ConversionData data = GetConversionData(fromType, toType); + IConversionData data = GetConversionData(fromType, toType); if (data != null) { return data; @@ -5255,6 +5657,12 @@ internal static ConversionData FigureConversion(Type fromType, Type toType) toType == fromType ? ConversionRank.Identity : ConversionRank.Assignable); } + if (fromType.IsByRefLike || toType.IsByRefLike) + { + // ByRef-like types are not boxable and should be used on stack only. + return CacheConversion(fromType, toType, ConvertNoConversion, ConversionRank.None); + } + if (typeof(PSObject).IsAssignableFrom(fromType) && typeof(InternalPSObject) != fromType) { // We don't attempt converting PSObject (or derived) to anything else, @@ -5272,37 +5680,36 @@ internal static ConversionData 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); } } } -#if CORECLR - // Assemblies in CoreCLR might not allow reflection execution on their internal types. - TypeInfo typeInfo = toType.GetTypeInfo(); - if (!TypeResolver.IsPublic(typeInfo) && DotNetAdapter.DisallowPrivateReflection(typeInfo)) - { - // 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); - } -#endif + PSConverter valueDependentConversion = null; ConversionRank valueDependentRank = ConversionRank.None; - ConversionData conversionData = FigureLanguageConversion(fromType, toType, out valueDependentConversion, out valueDependentRank); + IConversionData conversionData = FigureLanguageConversion(fromType, toType, out valueDependentConversion, out valueDependentRank); if (conversionData != null) { return conversionData; @@ -5324,7 +5731,7 @@ internal static ConversionData FigureConversion(Type fromType, Type toType) { if (typeof(IConvertible).IsAssignableFrom(fromType)) { - if (LanguagePrimitives.IsNumeric(GetTypeCode(fromType)) && !fromType.GetTypeInfo().IsEnum) + if (LanguagePrimitives.IsNumeric(GetTypeCode(fromType)) && !fromType.IsEnum) { if (!toType.IsArray) { @@ -5349,10 +5756,9 @@ internal static ConversionData FigureConversion(Type fromType, Type toType) // If the ToType has a constructor that takes a hashtable or OrderedDictionary, // then it would have been returned as the constructor during FigureConstructorConversion // So, we need to check only for the first condition - ConstructorInfo resultConstructor = toType.GetConstructor(PSTypeExtensions.EmptyTypes); + ConstructorInfo resultConstructor = toType.GetConstructor(Type.EmptyTypes); - TypeInfo toTypeInfo = toType.GetTypeInfo(); - if (resultConstructor != null || (toTypeInfo.IsValueType && !toTypeInfo.IsPrimitive)) + if (resultConstructor != null || (toType.IsValueType && !toType.IsPrimitive)) { ConvertViaNoArgumentConstructor noArgumentConstructorConverter = new ConvertViaNoArgumentConstructor(resultConstructor, toType); converter = noArgumentConstructorConverter.Convert; @@ -5386,10 +5792,7 @@ internal static ConversionData 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)) @@ -5422,10 +5825,11 @@ internal static ConversionData FigureConversion(Type fromType, Type toType) return CacheConversion(fromType, toType, converter, rank); } - internal class Null { }; - private static ConversionData FigureConversionFromNull(Type toType) + internal class Null { } + + private static IConversionData FigureConversionFromNull(Type toType) { - ConversionData data = GetConversionData(typeof(Null), toType); + IConversionData data = GetConversionData(typeof(Null), toType); if (data != null) { return data; @@ -5435,10 +5839,11 @@ private static ConversionData FigureConversionFromNull(Type toType) { return CacheConversion(typeof(Null), toType, LanguagePrimitives.ConvertNullToNullable, ConversionRank.NullToValue); } - else if (!toType.GetTypeInfo().IsValueType) + else if (!toType.IsValueType) { return CacheConversion(typeof(Null), toType, LanguagePrimitives.ConvertNullToRef, ConversionRank.NullToRef); } + return CacheConversion(typeof(Null), toType, ConvertNoConversion, ConversionRank.None); } @@ -5455,6 +5860,7 @@ internal static string ObjectToTypeNameString(object o) { return typeNames[0]; } + return Microsoft.PowerShell.ToStringCodeMethods.Type(o.GetType()); } @@ -5470,6 +5876,7 @@ private static Assembly AssemblyResolveHelper(object sender, ResolveEventArgs ar return assem; } } + return null; } #endif diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index c7fa0334a7b..9b69ee168d7 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -1,16 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; using System.Collections.Specialized; -using System.Globalization; using System.ComponentModel; +using System.Globalization; using System.Management.Automation.Internal; +using System.Text; + using Microsoft.PowerShell; + using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -31,7 +32,7 @@ internal abstract class BaseWMIAdapter : Adapter /// by Get-Member cmdlet, original MethodData and computed method information such /// as whether a method is static etc. /// - internal class WMIMethodCacheEntry + internal class WMIMethodCacheEntry : CacheEntry { public string Name { get; } @@ -76,18 +77,19 @@ 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); yield return type.ToString(); @@ -98,24 +100,25 @@ private IEnumerable GetTypeNameHierarchyFromDerivation(ManagementBaseObj // the immediate parent class, the next is its parent, and so on; the last element // is the base class. PropertyData derivationData = managementObj.SystemProperties["__Derivation"]; - if (null != derivationData) + if (derivationData != null) { Dbg.Assert(derivationData.IsArray, "__Derivation must be a string array as per MSDN documentation"); // give the typenames based on NameSpace + __Derivation string[] typeHierarchy = PropertySetAndMethodArgumentConvertTo(derivationData.Value, typeof(string[]), CultureInfo.InvariantCulture) as string[]; - if (null != typeHierarchy) + if (typeHierarchy != null) { foreach (string t in typeHierarchy) { type.Clear(); type.Append(dotnetBaseType); - type.Append("#"); + type.Append('#'); if (shouldIncludeNamespace) { type.Append(managementObj.SystemProperties["__NAMESPACE"].Value); - type.Append("\\"); + type.Append('\\'); } + type.Append(t); yield return type.ToString(); } @@ -124,9 +127,9 @@ private IEnumerable GetTypeNameHierarchyFromDerivation(ManagementBaseObj } /// - /// Returns the TypeNameHierarchy out of an ManagementBaseObject + /// Returns the TypeNameHierarchy out of an ManagementBaseObject. /// - /// object to get the TypeNameHierarchy from + /// Object to get the TypeNameHierarchy from. /// /// TypeName is of the format ObjectType#__Namespace\\__Class /// @@ -153,24 +156,23 @@ protected override IEnumerable GetTypeNameHierarchy(object obj) yield return typeFromDerivation; } } + yield return baseType; } } /// /// Returns null if memberName is not a member in the adapter or - /// the corresponding PSMemberInfo + /// the corresponding PSMemberInfo. /// - /// object to retrieve the PSMemberInfo from - /// name of the member to be retrieved - /// The PSMemberInfo corresponding to memberName from obj + /// Object to retrieve the PSMemberInfo from. + /// Name of the member to be retrieved. + /// The PSMemberInfo corresponding to memberName from obj. 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; } @@ -196,6 +198,17 @@ protected override T GetMember(object obj, string memberName) return null; } + protected override T GetFirstMemberOrDefault(object obj, MemberNamePredicate predicate) + { + if (obj is ManagementBaseObject wmiObject) + { + return GetFirstOrDefaultProperty(wmiObject, predicate) + ?? GetFirstOrDefaultMethod(wmiObject, predicate); + } + + return null; + } + /// /// Retrieves all the members available in the object. /// The adapter implementation is encouraged to cache all properties/methods available @@ -206,8 +219,8 @@ protected override T GetMember(object obj, string memberName) /// In the case of the DirectoryEntry adapter, this could be a cache of the objectClass /// to the properties available in it. /// - /// object to get all the member information from - /// all members in obj + /// Object to get all the member information from. + /// All members in obj. protected override PSMemberInfoInternalCollection GetMembers(object obj) { // obj should never be null @@ -220,14 +233,13 @@ protected override PSMemberInfoInternalCollection GetMembers(object obj) return returnValue; } - /// /// Called after a non null return from GetMember to try to call - /// the method with the arguments + /// the method with the arguments. /// - /// the non empty return from GetMethods - /// the arguments to use - /// the return value for the method + /// The non empty return from GetMethods. + /// The arguments to use. + /// The return value for the method. protected override object MethodInvoke(PSMethod method, object[] arguments) { ManagementObject mgmtObject = method.baseObject as ManagementObject; @@ -240,11 +252,11 @@ protected override object MethodInvoke(PSMethod method, object[] arguments) } /// - /// Called after a non null return from GetMember to return the overloads + /// Called after a non null return from GetMember to return the overloads. /// - /// the return of GetMember + /// The return of GetMember. /// - protected override Collection MethodDefinitions(PSMethod method) + protected override Collection MethodDefinitions(PSMethod method) { WMIMethodCacheEntry methodEntry = (WMIMethodCacheEntry)method.adapterData; Collection returnValue = new Collection(); @@ -254,10 +266,10 @@ protected override Collection MethodDefinitions(PSMethod method) } /// - /// Returns true if the property is settable + /// Returns true if the property is settable. /// - /// property to check - /// true if the property is settable + /// Property to check. + /// True if the property is settable. protected override bool PropertyIsSettable(PSProperty property) { ManagementBaseObject mObj = property.baseObject as ManagementBaseObject; @@ -290,21 +302,21 @@ protected override bool PropertyIsSettable(PSProperty property) } /// - /// Returns true if the property is gettable + /// Returns true if the property is gettable. /// - /// property to check - /// true if the property is gettable + /// Property to check. + /// True if the property is gettable. protected override bool PropertyIsGettable(PSProperty property) { return true; } /// - /// Returns the name of the type corresponding to the property + /// Returns the name of the type corresponding to the property. /// - /// PSProperty obtained in a previous DoGetProperty - /// True if the result is for display purposes only - /// the name of the type corresponding to the property + /// PSProperty obtained in a previous DoGetProperty. + /// True if the result is for display purposes only. + /// The name of the type corresponding to the property. protected override string PropertyType(PSProperty property, bool forDisplay) { PropertyData pd = property.adapterData as PropertyData; @@ -333,10 +345,10 @@ protected override string PropertyType(PSProperty property, bool forDisplay) } /// - /// Returns the value from a property coming from a previous call to DoGetProperty + /// Returns the value from a property coming from a previous call to DoGetProperty. /// - /// PSProperty coming from a previous call to DoGetProperty - /// The value of the property + /// PSProperty coming from a previous call to DoGetProperty. + /// The value of the property. protected override object PropertyGet(PSProperty property) { PropertyData pd = property.adapterData as PropertyData; @@ -347,13 +359,12 @@ protected override object PropertyGet(PSProperty property) /// This method will only set the property on a particular instance. If you want /// to update the WMI store, call Put(). /// - /// PSProperty coming from a previous call to DoGetProperty - /// value to set the property with - /// instructs the adapter to convert before setting, if the adapter supports conversion + /// PSProperty coming from a previous call to DoGetProperty. + /// Value to set the property with. + /// 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, @@ -361,6 +372,7 @@ protected override void PropertySet(PSProperty property, object setValue, bool c property.Name, property.baseObject.GetType().FullName, typeof(ManagementBaseObject).FullName); } + if (!PropertyIsSettable(property)) { throw new SetValueException("ReadOnlyWMIProperty", @@ -368,6 +380,7 @@ protected override void PropertySet(PSProperty property, object setValue, bool c ExtendedTypeSystem.ReadOnlyProperty, property.Name); } + PropertyData pd = property.adapterData as PropertyData; if ((convertIfPossible) && (setValue != null)) @@ -383,38 +396,41 @@ protected override void PropertySet(PSProperty property, object setValue, bool c } /// - /// Returns the string representation of the property in the object + /// Returns the string representation of the property in the object. /// - /// property obtained in a previous GetMember - /// the string representation of the property in the object + /// Property obtained in a previous GetMember. + /// The string representation of the property in the object. protected override string PropertyToString(PSProperty property) { StringBuilder returnValue = new StringBuilder(); - //if (PropertyIsStatic(property)) + // if (PropertyIsStatic(property)) // { // returnValue.Append("static "); // } + returnValue.Append(PropertyType(property, forDisplay: true)); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(property.Name); returnValue.Append(" {"); if (PropertyIsGettable(property)) { returnValue.Append("get;"); } + if (PropertyIsSettable(property)) { returnValue.Append("set;"); } - returnValue.Append("}"); + + returnValue.Append('}'); return returnValue.ToString(); } /// - /// Returns an array with the property attributes + /// Returns an array with the property attributes. /// - /// property we want the attributes from - /// an array with the property attributes + /// Property we want the attributes from. + /// An array with the property attributes. protected override AttributeCollection PropertyAttributes(PSProperty property) { return null; @@ -425,10 +441,10 @@ protected override AttributeCollection PropertyAttributes(PSProperty property) #region Private/Internal Methods /// - /// Retrieves the table for instance methods + /// Retrieves the table for instance methods. /// - /// object containing methods to load in typeTable. - /// controls what methods are adapted. + /// Object containing methods to load in typeTable. + /// Controls what methods are adapted. protected static CacheTable GetInstanceMethodTable(ManagementBaseObject wmiObject, bool staticBinding) { @@ -438,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) @@ -477,18 +493,18 @@ protected static CacheTable GetInstanceMethodTable(ManagementBaseObject wmiObjec } /// - /// Populates methods of a ManagementClass in a CacheTable + /// Populates methods of a ManagementClass in a CacheTable. /// /// Class to get the method info from. /// Cachetable to update. - /// controls what methods are adapted. + /// Controls what methods are adapted. /// /// private static void PopulateMethodTable(ManagementClass mgmtClass, CacheTable methodTable, bool staticBinding) { - Dbg.Assert(null != mgmtClass, "ManagementClass cannot be null in this method"); + Dbg.Assert(mgmtClass != null, "ManagementClass cannot be null in this method"); MethodDataCollection mgmtMethods = mgmtClass.Methods; - if (null != mgmtMethods) + if (mgmtMethods != null) { ManagementPath classPath = mgmtClass.ClassPath; // new operation will never fail @@ -511,7 +527,7 @@ private static void PopulateMethodTable(ManagementClass mgmtClass, CacheTable me /// /// Constructs a ManagementClass object from the supplied mgmtBaseObject. /// ManagementObject has scope, options, path which need to be carried over to the ManagementClass for - /// retrieving method/property/parameter metadata + /// retrieving method/property/parameter metadata. /// /// /// @@ -521,13 +537,13 @@ private static ManagementClass CreateClassFrmObject(ManagementBaseObject mgmtBas ManagementClass mgmtClass = mgmtBaseObject as ManagementClass; // try to use the actual object sent to this method..otherwise construct one - if (null == mgmtClass) + if (mgmtClass == null) { mgmtClass = new ManagementClass(mgmtBaseObject.ClassPath); // inherit ManagementObject properties ManagementObject mgmtObject = mgmtBaseObject as ManagementObject; - if (null != mgmtObject) + if (mgmtObject != null) { mgmtClass.Scope = mgmtObject.Scope; mgmtClass.Options = mgmtObject.Options; @@ -538,9 +554,9 @@ private static ManagementClass CreateClassFrmObject(ManagementBaseObject mgmtBas } /// - /// Gets the object type associated with a CimType:object + /// Gets the object type associated with a CimType:object. /// - /// PropertyData representing a parameter + /// PropertyData representing a parameter. /// /// typeof(object)#EmbeddedObjectTypeName if one found /// typeof(object) otherwise @@ -561,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:", "")); + result = string.Format( + CultureInfo.InvariantCulture, + "{0}#{1}", + typeof(ManagementObject).FullName, + cimType.Replace("object:", string.Empty)); } catch (ManagementException) { @@ -575,7 +594,7 @@ protected static string GetEmbeddedObjectTypeName(PropertyData pData) } /// - /// Gets the dotnet type of a given PropertyData + /// Gets the dotnet type of a given PropertyData. /// /// PropertyData input. /// A string representing dotnet type. @@ -590,10 +609,10 @@ protected static Type GetDotNetType(PropertyData pData) switch (pData.Type) { case CimType.SInt8: - retValue = typeof(System.SByte).FullName; + retValue = typeof(sbyte).FullName; break; case CimType.UInt8: - retValue = typeof(System.Byte).FullName; + retValue = typeof(byte).FullName; break; case CimType.SInt16: retValue = typeof(System.Int16).FullName; @@ -614,7 +633,7 @@ protected static Type GetDotNetType(PropertyData pData) retValue = typeof(System.UInt64).FullName; break; case CimType.Real32: - retValue = typeof(System.Single).FullName; + retValue = typeof(Single).FullName; break; case CimType.Real64: retValue = typeof(double).FullName; @@ -683,7 +702,7 @@ protected static bool IsStaticMethod(MethodData mdata) return false; } - private Object AuxillaryInvokeMethod(ManagementObject obj, WMIMethodCacheEntry mdata, object[] arguments) + private object AuxillaryInvokeMethod(ManagementObject obj, WMIMethodCacheEntry mdata, object[] arguments) { // Evaluate method and arguments object[] verifiedArguments; @@ -704,7 +723,6 @@ private Object AuxillaryInvokeMethod(ManagementObject obj, WMIMethodCacheEntry m Diagnostics.Assert(parameterList.Length == verifiedArguments.Length, "The number of parameters and arguments should match"); - // we should not cache inParameters as we are updating // inParameters object with argument values..Caching will // have side effects in this scenario like we have to clear @@ -745,7 +763,7 @@ private Object AuxillaryInvokeMethod(ManagementObject obj, WMIMethodCacheEntry m /// Should not throw exceptions /// internal static void UpdateParameters(ManagementBaseObject parameters, - SortedList parametersList) + SortedList parametersList) { // ManagementObject class do not populate parameters when there are none. if (parameters == null) @@ -794,7 +812,7 @@ internal static MethodInformation GetMethodInformation(MethodData mData) Diagnostics.Assert(mData != null, "MethodData should not be null"); // Get Method parameters - SortedList parameters = new SortedList(); + var parameters = new SortedList(); UpdateParameters(mData.InParameters, parameters); // parameters is never null @@ -814,15 +832,16 @@ internal static string GetMethodDefinition(MethodData mData) // gather parameter information for this method. // input and output parameters reside in 2 different groups.. // we dont know the order they appear on the arguments line.. - SortedList parameters = new SortedList(); + var parameters = new SortedList(); UpdateParameters(mData.InParameters, parameters); StringBuilder inParameterString = new StringBuilder(); if (parameters.Count > 0) { - foreach (WMIParameterInformation parameter in parameters.Values) + for (int i = 0; i < parameters.Values.Count; i++) { + WMIParameterInformation parameter = parameters.Values[i]; string typeName = parameter.parameterType.ToString(); PropertyData pData = mData.InParameters.Properties[parameter.Name]; @@ -837,7 +856,7 @@ internal static string GetMethodDefinition(MethodData mData) } inParameterString.Append(typeName); - inParameterString.Append(" "); + inParameterString.Append(' '); inParameterString.Append(parameter.Name); inParameterString.Append(", "); } @@ -853,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); @@ -870,8 +889,8 @@ internal static string GetMethodDefinition(MethodData mData) /// /// Retrieves all the properties available in the object. /// - /// object to get all the property information from - /// collection where the members will be added + /// Object to get all the property information from. + /// Collection where the members will be added. protected abstract void AddAllProperties(ManagementBaseObject wmiObject, PSMemberInfoInternalCollection members) where T : PSMemberInfo; @@ -889,13 +908,12 @@ protected abstract void AddAllMethods(ManagementBaseObject wmiObject, protected abstract object InvokeManagementMethod(ManagementObject wmiObject, string methodName, ManagementBaseObject inParams); - /// - /// Get a method object given method name + /// Get a method object given method name. /// /// PSMemberInfo /// Object for which the method is required. - /// Name of the method + /// Name of the method. /// /// PsMemberInfo if method exists. /// Null otherwise. @@ -903,24 +921,32 @@ protected abstract object InvokeManagementMethod(ManagementObject wmiObject, protected abstract T GetManagementObjectMethod(ManagementBaseObject wmiObject, string methodName) where T : PSMemberInfo; - /// /// Returns null if propertyName is not a property in the adapter or /// the corresponding PSProperty with its adapterData set to information /// to be used when retrieving the property. /// - /// object to retrieve the PSProperty from - /// name of the property to be retrieved - /// The PSProperty corresponding to propertyName from obj + /// Object to retrieve the PSProperty from. + /// Name of the property to be retrieved. + /// The PSProperty corresponding to propertyName from obj. protected abstract PSProperty DoGetProperty(ManagementBaseObject wmiObject, string propertyName); + /// + /// Returns the first property whose name matches the specified + /// + protected abstract T GetFirstOrDefaultProperty(ManagementBaseObject wmiObject, MemberNamePredicate predicate) where T : PSMemberInfo; + + /// + /// Returns the first method whose name matches the specified + /// + protected abstract T GetFirstOrDefaultMethod(ManagementBaseObject wmiObject, MemberNamePredicate predicate) where T : PSMemberInfo; #endregion #region Private Data - private static HybridDictionary s_instanceMethodCacheTable = new HybridDictionary(); + private static readonly HybridDictionary s_instanceMethodCacheTable = new HybridDictionary(); #endregion } @@ -990,7 +1016,7 @@ protected override object InvokeManagementMethod(ManagementObject wmiObject, /// /// Adds method information of the ManagementClass. Only static methods are added for - /// an object of type ManagementClass + /// an object of type ManagementClass. /// /// PSMemberInfo /// Object for which the members need to be retrieved. @@ -1035,11 +1061,8 @@ protected override T GetManagementObjectMethod(ManagementBaseObject wmiObject return null; } - CacheTable typeTable; - WMIMethodCacheEntry method; - - typeTable = GetInstanceMethodTable(wmiObject, true); - method = (WMIMethodCacheEntry)typeTable[methodName]; + CacheTable typeTable = GetInstanceMethodTable(wmiObject, true); + WMIMethodCacheEntry method = (WMIMethodCacheEntry)typeTable[methodName]; if (method == null) { @@ -1048,11 +1071,51 @@ protected override T GetManagementObjectMethod(ManagementBaseObject wmiObject return new PSMethod(method.Name, this, wmiObject, method) as T; } + + protected override T GetFirstOrDefaultProperty(ManagementBaseObject wmiObject, MemberNamePredicate predicate) + { + if (!typeof(T).IsAssignableFrom(typeof(PSProperty))) + { + return null; + } + + if (wmiObject.SystemProperties != null) + { + foreach (PropertyData property in wmiObject.SystemProperties) + { + if (predicate(property.Name)) + { + return new PSProperty(property.Name, this, wmiObject, property) as T; + } + } + } + + return null; + } + + protected override T GetFirstOrDefaultMethod(ManagementBaseObject wmiObject, MemberNamePredicate predicate) + { + if (!typeof(T).IsAssignableFrom(typeof(PSMethod))) + { + return null; + } + + CacheTable table = GetInstanceMethodTable(wmiObject, true); + foreach (WMIMethodCacheEntry methodEntry in table.memberCollection) + { + if (predicate(methodEntry.Name)) + { + return new PSMethod(methodEntry.Name, this, wmiObject, methodEntry) as T; + } + } + + return null; + } } /// /// Deals with ManagementObject objects. - /// This class do not adapt static methods + /// This class do not adapt static methods. /// internal class ManagementObjectAdapter : ManagementClassApdapter { @@ -1102,15 +1165,12 @@ 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. } - return null; } diff --git a/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs b/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs index abd35ea65e6..d2b972a118d 100644 --- a/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs +++ b/src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.Management.Automation.Language; using System.Text; namespace System.Management.Automation @@ -19,11 +19,9 @@ internal class MergedCommandParameterMetadata /// will turn 'bindableParameters', 'aliasedParameters' and 'parameterSetMap' into /// ReadOnlyDictionary and ReadOnlyCollection. /// - /// /// /// The metadata to replace in this object. /// - /// /// /// A list of the merged parameter metadata that was added. /// @@ -57,38 +55,33 @@ internal List ReplaceMetadata(MergedCommandParam "After replacement with the metadata of the new parameters, ParameterSetCount should be equal to nextAvailableParameterSetIndex"); return result; - } // ReplaceMetadata + } /// /// Merges the specified metadata with the other metadata already defined /// in this object. /// - /// /// /// The compiled metadata for the type to be merged. /// - /// /// /// The type of binder that the CommandProcessor will use to bind /// the parameters for /// - /// /// /// A collection of the merged parameter metadata that was added. /// - /// /// /// If a parameter name or alias described in the already /// exists. /// - /// internal Collection AddMetadataForBinder( InternalParameterMetadata parameterMetadata, ParameterBinderAssociation binderAssociation) { if (parameterMetadata == null) { - throw PSTraceSource.NewArgumentNullException("parameterMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterMetadata)); } Collection result = @@ -159,10 +152,10 @@ internal Collection AddMetadataForBinder( _aliasedParameters.Add(aliasName, mergedParameter); } } + return result; } - /// /// The next available parameter set bit. This number increments but the parameter /// set bit is really 1 shifted left this number of times. This number also acts @@ -170,20 +163,26 @@ 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. /// - /// internal int ParameterSetCount { get { return _parameterSetMap.Count; } - } // ParameterSetCount + } /// - /// Gets a bit-field representing all valid parameter sets + /// Gets a bit-field representing all valid parameter sets. /// internal uint AllParameterSetFlags { @@ -199,11 +198,10 @@ internal uint AllParameterSetFlags /// The value is the parameter set name. /// New parameter sets are added at the nextAvailableParameterSetIndex. /// - /// private IList _parameterSetMap = new List(); /// - /// The name of the default parameter set + /// The name of the default parameter set. /// private string _defaultParameterSetName; @@ -212,36 +210,32 @@ internal uint AllParameterSetFlags /// index. If the parameter set name was already in the map, the index to /// the existing parameter set name is returned. /// - /// /// /// The name of the parameter set to add. /// - /// /// /// The index of the parameter set name. If the name didn't already exist the /// name gets added and the new index is returned. If the name already exists /// the index of the existing name is returned. /// - /// /// /// The nextAvailableParameterSetIndex is incremented if the parameter set name /// is added. /// - /// /// /// If more than uint.MaxValue parameter-sets are defined for the command. /// private int AddParameterSetToMap(string parameterSetName) { int index = -1; - if (!String.IsNullOrEmpty(parameterSetName)) + if (!string.IsNullOrEmpty(parameterSetName)) { index = _parameterSetMap.IndexOf(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 = @@ -263,27 +257,24 @@ private int AddParameterSetToMap(string parameterSetName) _nextAvailableParameterSetIndex++; } } + return index; - } // AddParameterSetToMap + } /// /// Loops through all the parameters and retrieves the parameter set names. In the process /// it generates a mapping of parameter set names to the bits in the bit-field and sets /// the parameter set flags for the parameter. /// - /// /// /// The default parameter set name. /// - /// /// /// The bit flag for the default parameter set. /// - /// /// /// If more than uint.MaxValue parameter-sets are defined for the command. /// - /// internal uint GenerateParameterSetMappingFromMetadata(string defaultParameterSetName) { // First clear the parameter set map @@ -292,7 +283,7 @@ internal uint GenerateParameterSetMappingFromMetadata(string defaultParameterSet uint defaultParameterSetFlag = 0; - if (!String.IsNullOrEmpty(defaultParameterSetName)) + if (!string.IsNullOrEmpty(defaultParameterSetName)) { _defaultParameterSetName = defaultParameterSetName; @@ -314,7 +305,7 @@ internal uint GenerateParameterSetMappingFromMetadata(string defaultParameterSet { var parameterSetName = keyValuePair.Key; var parameterSetData = keyValuePair.Value; - if (String.Equals(parameterSetName, ParameterAttribute.AllParameterSets, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(parameterSetName, ParameterAttribute.AllParameterSets, StringComparison.OrdinalIgnoreCase)) { // Don't add the parameter set name but assign the bit field zero and then mark the bool parameterSetData.ParameterSetFlag = 0; @@ -345,26 +336,24 @@ internal uint GenerateParameterSetMappingFromMetadata(string defaultParameterSet // Set the bit field in the parameter parameter.Parameter.ParameterSetFlags = parameterSetBitField; } + return defaultParameterSetFlag; - } // GenerateParameterSetMappingFromMetadata + } /// /// Gets the parameter set name for the specified parameter set. /// - /// /// /// The parameter set to get the name for. /// - /// /// /// The name of the specified parameter set. /// - /// internal string GetParameterSetName(uint parameterSet) { string result = _defaultParameterSetName; - if (String.IsNullOrEmpty(result)) + if (string.IsNullOrEmpty(result)) { result = ParameterAttribute.AllParameterSets; } @@ -381,7 +370,7 @@ internal string GetParameterSetName(uint parameterSet) } // Now check to see if there are any remaining sets passed this bit. - // If so return String.Empty + // If so return string.Empty if (((parameterSet >> (index + 1)) & 0x1) == 0) { @@ -392,17 +381,17 @@ internal string GetParameterSetName(uint parameterSet) } else { - result = String.Empty; + result = string.Empty; } } else { - result = String.Empty; + result = string.Empty; } } - return result; - } // GetParameterSetName + return result; + } /// /// Helper function to retrieve the name of the parameter @@ -416,49 +405,43 @@ private static string RetrieveParameterNameForAlias( IDictionary dict) { MergedCompiledCommandParameter mergedParam = dict[key]; - if (null != mergedParam) + if (mergedParam != null) { CompiledCommandParameter compiledParam = mergedParam.Parameter; - if (null != compiledParam) + if (compiledParam != null) { - if (!String.IsNullOrEmpty(compiledParam.Name)) + if (!string.IsNullOrEmpty(compiledParam.Name)) return compiledParam.Name; } } - return String.Empty; + + return string.Empty; } /// /// Gets the parameters by matching its name. /// - /// /// /// The name of the parameter. /// - /// /// /// If true and a matching parameter is not found, an exception will be /// throw. If false and a matching parameter is not found, null is returned. /// - /// /// /// If true we do exact matching, otherwise we do not. /// - /// /// /// The invocation information about the code being run. /// - /// /// /// The a collection of the metadata associated with the parameters that /// match the specified name. If no matches were found, an empty collection /// is returned. /// - /// /// /// If is null or empty. /// - /// internal MergedCompiledCommandParameter GetMatchingParameter( string name, bool throwOnParameterNotFound, @@ -467,19 +450,18 @@ internal MergedCompiledCommandParameter GetMatchingParameter( { if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } Collection matchingParameters = new Collection(); // Skip the leading '-' if present - if (name.Length > 0 && SpecialCharacters.IsDash(name[0])) + if (name.Length > 0 && CharExtensions.IsDash(name[0])) { name = name.Substring(1); } - // First try to match the bindable parameters foreach (string parameterName in _bindableParameters.Keys) @@ -489,7 +471,7 @@ internal MergedCompiledCommandParameter GetMatchingParameter( // If it is an exact match then only return the exact match // as the result - if (tryExactMatching && String.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) + if (tryExactMatching && string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) { return _bindableParameters[parameterName]; } @@ -509,7 +491,7 @@ internal MergedCompiledCommandParameter GetMatchingParameter( // If it is an exact match then only return the exact match // as the result - if (tryExactMatching && String.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) + if (tryExactMatching && string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) { return _aliasedParameters[parameterName]; } @@ -538,7 +520,7 @@ internal MergedCompiledCommandParameter GetMatchingParameter( } } - if (filteredParameters.Count == 1) + if (tryExactMatching && filteredParameters.Count == 1) { matchingParameters = filteredParameters; } @@ -591,21 +573,19 @@ internal MergedCompiledCommandParameter GetMatchingParameter( { result = matchingParameters[0]; } + return result; - } // GetMatchingParameter + } /// - /// Gets a collection of all the parameters that are allowed in the parameter set + /// Gets a collection of all the parameters that are allowed in the parameter set. /// - /// /// /// The bit representing the parameter set from which the parameters should be retrieved. /// - /// /// /// A collection of all the parameters in the specified parameter set. /// - /// internal Collection GetParametersInParameterSet(uint parameterSetFlag) { Collection result = @@ -619,8 +599,9 @@ internal Collection GetParametersInParameterSet( result.Add(parameter); } } + return result; - } // GetParametersInParameterSet + } /// /// Gets a dictionary of the compiled parameter metadata for this Type. @@ -628,6 +609,7 @@ internal Collection GetParametersInParameterSet( /// the values are the compiled parameter metadata. /// internal IDictionary BindableParameters { get { return _bindableParameters; } } + private IDictionary _bindableParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -636,6 +618,7 @@ internal Collection GetParametersInParameterSet( /// the alias name and the value is the MergedCompiledCommandParameter metadata. /// internal IDictionary AliasedParameters { get { return _aliasedParameters; } } + private IDictionary _aliasedParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -651,24 +634,21 @@ internal void ResetReadOnly() _bindableParameters = new Dictionary(_bindableParameters, StringComparer.OrdinalIgnoreCase); _aliasedParameters = new Dictionary(_aliasedParameters, StringComparer.OrdinalIgnoreCase); } - } // MergedCommandParameterMetadata + } /// /// Makes an association between a CompiledCommandParameter and the type /// of the parameter binder used to bind the parameter. /// - /// internal class MergedCompiledCommandParameter { /// /// Constructs an association between the CompiledCommandParameter and the /// binder that should be used to bind it. /// - /// /// /// The metadata for a parameter. /// - /// /// /// The type of binder that should be used to bind the parameter. /// @@ -682,14 +662,14 @@ internal MergedCompiledCommandParameter( } /// - /// Gets the compiled command parameter for the association + /// 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() { @@ -697,13 +677,11 @@ public override string ToString() } } - /// /// This enum is used in the MergedCompiledCommandParameter class /// to associate a particular CompiledCommandParameter with the /// appropriate ParameterBinder. /// - /// internal enum ParameterBinderAssociation { /// @@ -737,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 318596290cd..e19defd57a7 100644 --- a/src/System.Management.Automation/engine/MinishellParameterBinderController.cs +++ b/src/System.Management.Automation/engine/MinishellParameterBinderController.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.ObjectModel; +using System.Globalization; using System.IO; using System.Management.Automation.Language; using System.Xml; -using System.Globalization; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -16,20 +16,17 @@ namespace System.Management.Automation /// This is the interface between the NativeCommandProcessor and the /// parameter binders required to bind parameters to a minishell. /// - /// internal class MinishellParameterBinderController : NativeCommandParameterBinderController { #region ctor /// /// Initializes the parameter binder controller for - /// the specified native command and engine context + /// the specified native command and engine context. /// - /// /// /// The command that the parameters will be bound to. /// - /// internal MinishellParameterBinderController( NativeCommand command) : base(command) @@ -40,26 +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. /// @@ -71,31 +48,26 @@ internal override internal NativeCommandIOFormat OutputFormat { get; private set; } /// - /// IF true, child minishell is invoked with no-window + /// IF true, child minishell is invoked with no-window. /// internal bool NonInteractive { get; private set; } /// - /// Binds the specified parameters to the native command + /// Binds the specified parameters to the native command. /// - /// /// /// The parameters to bind. /// - /// /// /// true if minishell output is redirected. /// - /// /// /// name of the calling host. /// - /// /// /// For any parameters that do not have a name, they are added to the command /// line arguments for the command /// - /// internal Collection BindParameters(Collection parameters, bool outputRedirected, string hostName) { MinishellParameters seen = 0; @@ -127,7 +99,7 @@ internal Collection BindParameters(Collection BindParameters(Collection s_emptyReturnCollection = new Collection(); @@ -282,7 +254,7 @@ private enum MinishellParameters Arguments = 0x02, InputFormat = 0x04, OutputFormat = 0x08 - }; + } /// /// Handles error handling if some parameter is specified more than once. @@ -299,16 +271,16 @@ private void HandleSeenParameter(ref MinishellParameters seen, MinishellParamete } else { - seen = seen | parameter; + seen |= parameter; } } /// /// This function processes the value for -inputFormat and -outputFormat parameter of minishell. /// - /// Name of the parameter for error messages. Value should be -inputFormat or -outputFormat - /// value to process - /// Processed value + /// Name of the parameter for error messages. Value should be -inputFormat or -outputFormat. + /// Value to process. + /// Processed value. private string ProcessFormatParameterValue(string parameterName, object value) { @@ -329,6 +301,7 @@ private string { return XmlFormatValue; } + if (TextFormatValue.StartsWith(fpValue, StringComparison.OrdinalIgnoreCase)) { return TextFormatValue; @@ -342,15 +315,15 @@ private string } /// - /// Converts value of args parameter in to an encoded string + /// Converts value of args parameter in to an encoded string. /// private static string ConvertArgsValueToEncodedString(object value) { ArrayList list = ConvertArgsValueToArrayList(value); - //Serialize the list + // Serialize the list StringWriter stringWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture); - //When (if) switching to XmlTextWriter.Create remember the OmitXmlDeclaration difference + // When (if) switching to XmlTextWriter.Create remember the OmitXmlDeclaration difference XmlWriter xmlWriter = XmlWriter.Create(stringWriter); Serializer serializer = new Serializer(xmlWriter); serializer.Serialize(list); @@ -358,13 +331,13 @@ private static string ConvertArgsValueToEncodedString(object value) xmlWriter.Flush(); string result = stringWriter.ToString(); - //convert result to encoded string + // convert result to encoded string return StringToBase64Converter.StringToBase64String(result); } /// /// Converts the value of -args parameter received from - /// parser in to an arraylist + /// parser in to an arraylist. /// private static ArrayList ConvertArgsValueToArrayList(object value) { @@ -381,6 +354,7 @@ private static ArrayList ConvertArgsValueToArrayList(object value) results.Add(list.Current); } } + return results; } diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 06ae7ca0a95..8a5f29e2b16 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -1,11 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Language; @@ -13,35 +14,32 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using Microsoft.PowerShell.Commands; namespace System.Management.Automation { /// /// Class to manage the caching of analysis data. - /// /// For performance, module command caching is flattened after discovery. Many modules have nested /// modules that can only be resolved at runtime - for example, /// script modules that declare: $env:PATH += "; $psScriptRoot". When /// doing initial analysis, we include these in 'ExportedCommands'. - /// /// 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 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) { @@ -73,11 +71,11 @@ internal static ConcurrentDictionary GetExportedCommands(s { result = AnalyzeCdxmlModule(modulePath, context, lastWriteTime); } - else if (extension.Equals(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)) { - result = AnalyzeXamlModule(modulePath, context, lastWriteTime); + result = AnalyzeDllModule(modulePath, context, lastWriteTime); } - else if (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { result = AnalyzeDllModule(modulePath, context, lastWriteTime); } @@ -105,6 +103,12 @@ private static ConcurrentDictionary AnalyzeManifestModule( var moduleManifestProperties = PsUtils.GetModuleManifestProperties(modulePath, PsUtils.FastModuleManifestAnalysisPropertyNames); if (moduleManifestProperties != null) { + if (!Configuration.PowerShellConfig.Instance.IsImplicitWinCompatEnabled() && ModuleIsEditionIncompatible(modulePath, moduleManifestProperties)) + { + ModuleIntrinsics.Tracer.WriteLine($"Module lies on the Windows System32 legacy module path and is incompatible with current PowerShell edition, skipping module: {modulePath}"); + return null; + } + Version version; if (ModuleUtils.IsModuleInVersionSubdirectory(modulePath, out version)) { @@ -163,10 +167,34 @@ private static ConcurrentDictionary AnalyzeManifestModule( return result ?? AnalyzeTheOldWay(modulePath, context, lastWriteTime); } + /// + /// Check if a module is compatible with the current PSEdition given its path and its manifest properties. + /// + /// The path to the module. + /// The properties of the module's manifest. + /// + internal static bool ModuleIsEditionIncompatible(string modulePath, Hashtable moduleManifestProperties) + { +#if UNIX + return false; +#else + if (!ModuleUtils.IsOnSystem32ModulePath(modulePath)) + { + return false; + } + + if (!moduleManifestProperties.ContainsKey("CompatiblePSEditions")) + { + return true; + } + + return !Utils.IsPSEditionSupported(LanguagePrimitives.ConvertTo(moduleManifestProperties["CompatiblePSEditions"])); +#endif + } + 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)) @@ -183,7 +211,15 @@ internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bo return !hadFunctions || !hadAliases; } - if (modulePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + if (modulePath.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + { + // A dll just exports cmdlets, so if the manifest doesn't explicitly export any cmdlets, + // more analysis is required. If the module exports aliases, we can't discover that analyzing + // the binary, so aliases are always required to be explicit (no wildcards) in the manifest. + return !hadCmdlets; + } + + if (modulePath.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { // A dll just exports cmdlets, so if the manifest doesn't explicitly export any cmdlets, // more analysis is required. If the module exports aliases, we can't discover that analyzing @@ -220,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) @@ -254,8 +289,10 @@ private static bool AddPsd1EntryToResult(ConcurrentDictionary AnalyzeScriptModule(st { if (SessionStateUtilities.MatchesAnyWildcardPattern(command, scriptAnalysisPatterns, true)) { - if (command.IndexOfAny(InvalidCommandNameCharacters) < 0) + if (!ContainsInvalidCommandNameCharacters(command)) { result[command] = CommandTypes.Function; } @@ -320,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); } } @@ -334,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) @@ -347,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; @@ -367,11 +406,6 @@ private static ConcurrentDictionary AnalyzeScriptModule(st return result; } - private static ConcurrentDictionary AnalyzeXamlModule(string modulePath, ExecutionContext context, DateTime lastWriteTime) - { - return AnalyzeTheOldWay(modulePath, context, lastWriteTime); - } - private static ConcurrentDictionary AnalyzeCdxmlModule(string modulePath, ExecutionContext context, DateTime lastWriteTime) { return AnalyzeTheOldWay(modulePath, context, lastWriteTime); @@ -424,7 +458,7 @@ private static ConcurrentDictionary AnalyzeTheOldWay(strin /// Also re-cache the module if the cached item is stale. /// /// Path to the module to get exported types from. - /// Current Context + /// Current Context. /// internal static ConcurrentDictionary GetExportedClasses(string modulePath, ExecutionContext context) { @@ -457,6 +491,14 @@ internal static void CacheModuleExports(PSModuleInfo module, ExecutionContext co { ModuleIntrinsics.Tracer.WriteLine("Requested caching for {0}", module.Name); + // Don't cache incompatible modules on the system32 module path even if loaded with + // -SkipEditionCheck, since it will break subsequent sessions + if (!Configuration.PowerShellConfig.Instance.IsImplicitWinCompatEnabled() && !module.IsConsideredEditionCompatible) + { + ModuleIntrinsics.Tracer.WriteLine($"Module '{module.Name}' not edition compatible and not cached."); + return; + } + DateTime lastWriteTime; ModuleCacheEntry moduleCacheEntry; GetModuleEntryFromCache(module.Path, out lastWriteTime, out moduleCacheEntry); @@ -600,7 +642,7 @@ private static bool GetModuleEntryFromCache(string modulePath, out DateTime last } } - internal class AnalysisCacheData + internal sealed class AnalysisCacheData { private static byte[] GetHeader() { @@ -622,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 @@ -660,10 +707,9 @@ private void Cleanup() var keys = Entries.Keys; foreach (var key in keys) { - if (!Utils.NativeFileExists(key)) + if (!File.Exists(key)) { - ModuleCacheEntry unused; - removedSomething |= Entries.TryRemove(key, out unused); + removedSomething |= Entries.TryRemove(key, out ModuleCacheEntry _); } } @@ -679,6 +725,7 @@ private static unsafe void Write(int val, byte[] bytes, FileStream stream) fixed (byte* b = bytes) *((int*)b) = val; stream.Write(bytes, 0, 4); } + private static unsafe void Write(long val, byte[] bytes, FileStream stream) { Diagnostics.Assert(bytes.Length >= 8, "Must pass a large enough byte array"); @@ -696,11 +743,11 @@ private static void Write(string val, byte[] bytes, FileStream stream) private void Serialize(string filename) { AnalysisCacheData fromOtherProcess = null; - Diagnostics.Assert(_saveCacheToDisk != false, "Serialize should never be called without going through QueueSerialization which has a check"); + Diagnostics.Assert(_saveCacheToDisk, "Serialize should never be called without going through QueueSerialization which has a check"); try { - if (Utils.NativeFileExists(filename)) + if (File.Exists(filename)) { var fileLastWriteTime = new FileInfo(filename).LastWriteTime; if (fileLastWriteTime > this.LastReadTime) @@ -985,6 +1032,7 @@ public static AnalysisCacheData Deserialize(string filename) { Task.Delay(10000).ContinueWith(_ => result.Cleanup()); } + return result; } } @@ -997,7 +1045,7 @@ internal static AnalysisCacheData Get() { try { - if (Utils.NativeFileExists(s_cacheStoreLocation)) + if (File.Exists(s_cacheStoreLocation)) { return Deserialize(s_cacheStoreLocation); } @@ -1014,6 +1062,7 @@ internal static AnalysisCacheData Get() break; } } + retryCount -= 1; Thread.Sleep(25); // Sleep a bit to give time for another process to finish writing the cache } while (retryCount > 0); @@ -1035,13 +1084,49 @@ private AnalysisCacheData() static AnalysisCacheData() { - s_cacheStoreLocation = - Environment.GetEnvironmentVariable("PSModuleAnalysisCachePath") ?? -#if UNIX - Path.Combine(Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE), "ModuleAnalysisCache"); -#else - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\PowerShell\ModuleAnalysisCache"); -#endif + // If user defines a custom cache path, then use that. + string userDefinedCachePath = Environment.GetEnvironmentVariable("PSModuleAnalysisCachePath"); + if (!string.IsNullOrEmpty(userDefinedCachePath)) + { + s_cacheStoreLocation = userDefinedCachePath; + return; + } + + string cacheFileName = "ModuleAnalysisCache"; + + // 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.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); + + if (ExperimentalFeature.EnabledExperimentalFeatureNames.Count > 0) + { + // If any experimental features are enabled, we cannot use the default cache file because those + // features may expose commands that are not available in a regular powershell session, and we + // should not cache those commands in the default cache file because that will result in wrong + // auto-completion suggestions when the default cache file is used in another powershell session. + // + // Here we will generate a cache file name that represent the combination of enabled feature names. + // We first convert enabled feature names to lower case, then we sort the feature names, and then + // compute an CRC32 hash from the sorted feature names. We will use the CRC32 hash to generate the + // cache file name. + int index = 0; + string[] featureNames = new string[ExperimentalFeature.EnabledExperimentalFeatureNames.Count]; + foreach (string featureName in ExperimentalFeature.EnabledExperimentalFeatureNames) + { + featureNames[index++] = featureName.ToLowerInvariant(); + } + + Array.Sort(featureNames); + string allNames = string.Join(Environment.NewLine, featureNames); + + // 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.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); + } + + Platform.TryDeriveFromCache(cacheFileName, out s_cacheStoreLocation); } } @@ -1054,4 +1139,4 @@ internal class ModuleCacheEntry public ConcurrentDictionary Commands; public ConcurrentDictionary Types; } -} // System.Management.Automation +} diff --git a/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs b/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs index ab54f34d08e..3b47bc37f10 100644 --- a/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Security; // // Now define the set of commands for manipulating modules. @@ -16,9 +16,9 @@ namespace Microsoft.PowerShell.Commands { #region Export-ModuleMember /// - /// Implements a cmdlet that loads a module + /// Implements a cmdlet that loads a module. /// - [Cmdlet(VerbsData.Export, "ModuleMember", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141551")] + [Cmdlet(VerbsData.Export, "ModuleMember", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096578")] public sealed class ExportModuleMemberCommand : PSCmdlet { /// @@ -29,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; @@ -43,8 +48,8 @@ public string[] Function } } } - get { return _functionList; } } + private string[] _functionList; private List _functionPatterns; @@ -56,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; @@ -70,8 +80,8 @@ public string[] Cmdlet } } } - get { return _cmdletList; } } + private string[] _cmdletList; private List _cmdletPatterns; @@ -83,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; @@ -97,8 +112,8 @@ public string[] Variable } } } - get { return _variableExportList; } } + private string[] _variableExportList; private List _variablePatterns; @@ -110,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; @@ -124,8 +144,8 @@ public string[] Alias } } } - get { return _aliasExportList; } } + private string[] _aliasExportList; private List _aliasPatterns; @@ -143,10 +163,30 @@ protected override void ProcessRecord() ThrowTerminatingError(er); } + // Prevent script injection attack by disallowing ExportModuleMemberCommand to export module members across + // language boundaries. This will prevent injected untrusted script from exporting private trusted module functions. + if (Context.EngineSessionState.Module?.LanguageMode != null && + Context.LanguageMode != Context.EngineSessionState.Module.LanguageMode) + { + 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, this.Context.EngineSessionState, _functionPatterns, _cmdletPatterns, _aliasPatterns, _variablePatterns, null); } } #endregion Export-ModuleMember -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs index 5b3a7f8219a..caf7527d73e 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -1,20 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; + using Microsoft.Management.Infrastructure; + using Dbg = System.Management.Automation.Diagnostics; // @@ -27,7 +28,7 @@ namespace Microsoft.PowerShell.Commands /// Implements a cmdlet that gets the list of loaded modules... /// [Cmdlet(VerbsCommon.Get, "Module", DefaultParameterSetName = ParameterSet_Loaded, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141552")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096696")] [OutputType(typeof(PSModuleInfo))] public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable { @@ -39,7 +40,7 @@ public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable private const string ParameterSet_AvailableInCimSession = "CimSession"; /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(ParameterSetName = ParameterSet_Loaded, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_AvailableLocally, ValueFromPipeline = true, Position = 0)] @@ -51,7 +52,7 @@ public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable public string[] Name { get; set; } /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(ParameterSetName = ParameterSet_Loaded, ValueFromPipelineByPropertyName = true)] [Parameter(ParameterSetName = ParameterSet_AvailableLocally, ValueFromPipelineByPropertyName = true)] @@ -86,7 +87,20 @@ public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable public string PSEdition { get; set; } /// - /// If specified, then Get-Module refreshes the internal cmdlet analysis cache + /// When set, CompatiblePSEditions checking is disabled for modules in the System32 (Windows PowerShell) module directory. + /// + [Parameter(ParameterSetName = ParameterSet_AvailableLocally)] + [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)] + [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession)] + public SwitchParameter SkipEditionCheck + { + get { return (SwitchParameter)BaseSkipEditionCheck; } + + set { BaseSkipEditionCheck = value; } + } + + /// + /// If specified, then Get-Module refreshes the internal cmdlet analysis cache. /// [Parameter(ParameterSetName = ParameterSet_AvailableLocally)] [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)] @@ -94,28 +108,28 @@ public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable public SwitchParameter Refresh { get; set; } /// - /// If specified, then Get-Module will attempt to discover PowerShell modules on a remote computer using the specified session + /// If specified, then Get-Module will attempt to discover PowerShell modules on a remote computer using the specified session. /// [Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession, Mandatory = true)] [ValidateNotNull] public PSSession PSSession { get; set; } /// - /// If specified, then Get-Module will attempt to discover PS-CIM modules on a remote computer using the specified session + /// If specified, then Get-Module will attempt to discover PS-CIM modules on a remote computer using the specified session. /// [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = true)] [ValidateNotNull] public CimSession CimSession { get; set; } /// - /// For interoperability with 3rd party CIM servers, user can specify custom resource URI + /// For interoperability with 3rd party CIM servers, user can specify custom resource URI. /// [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = false)] [ValidateNotNull] public Uri CimResourceUri { get; set; } /// - /// For interoperability with 3rd party CIM servers, user can specify custom namespace + /// For interoperability with 3rd party CIM servers, user can specify custom namespace. /// [Parameter(ParameterSetName = ParameterSet_AvailableInCimSession, Mandatory = false)] [ValidateNotNullOrEmpty] @@ -125,7 +139,6 @@ public sealed class GetModuleCommand : ModuleCmdletBase, IDisposable #region Remote discovery - private IEnumerable GetAvailableViaPsrpSessionCore(string[] moduleNames, Runspace remoteRunspace) { Dbg.Assert(remoteRunspace != null, "Caller should verify remoteRunspace != null"); @@ -152,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; @@ -161,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); } @@ -210,7 +223,7 @@ private PSModuleInfo ConvertCimModuleInfoToPSModuleInfo(RemoteDiscoveryHelper.Ci ImportModuleOptions throwAwayOptions = new ImportModuleOptions(); moduleInfo = LoadModuleManifest( temporaryModuleManifestPath, - null, //scriptInfo + null, // scriptInfo mainData, localizedData, 0 /* - don't write errors, don't load elements, don't return null on first error */, @@ -252,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; } @@ -284,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; } @@ -343,6 +344,18 @@ protected override void ProcessRecord() ThrowTerminatingError(error); } + // -SkipEditionCheck only makes sense for -ListAvailable (otherwise the module is already loaded) + if (SkipEditionCheck && !ListAvailable) + { + ErrorRecord error = new ErrorRecord( + new InvalidOperationException(Modules.SkipEditionCheckNotSupportedWithoutListAvailable), + nameof(Modules.SkipEditionCheckNotSupportedWithoutListAvailable), + ErrorCategory.InvalidOperation, + targetObject: null); + + ThrowTerminatingError(error); + } + var strNames = new List(); if (Name != null) { @@ -352,8 +365,13 @@ protected override void ProcessRecord() var moduleSpecTable = new Dictionary(StringComparer.OrdinalIgnoreCase); if (FullyQualifiedName != null) { - moduleSpecTable = FullyQualifiedName.ToDictionary(moduleSpecification => moduleSpecification.Name, StringComparer.OrdinalIgnoreCase); - strNames.AddRange(FullyQualifiedName.Select(spec => spec.Name)); + for (int modSpecIndex = 0; modSpecIndex < FullyQualifiedName.Length; modSpecIndex++) + { + FullyQualifiedName[modSpecIndex] = FullyQualifiedName[modSpecIndex].WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path); + } + + 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; @@ -410,7 +428,7 @@ private void AssertNameDoesNotResolveToAPath(string[] names, string stringFormat { foreach (var n in names) { - if (n.IndexOf(StringLiterals.DefaultPathSeparator) != -1 || n.IndexOf(StringLiterals.AlternatePathSeparator) != -1) + if (n.Contains(StringLiterals.DefaultPathSeparator) || n.Contains(StringLiterals.AlternatePathSeparator)) { string errorMessage = StringUtil.Format(stringFormat, n); var argumentException = new ArgumentException(errorMessage); @@ -425,27 +443,12 @@ private void AssertNameDoesNotResolveToAPath(string[] names, string stringFormat } } - /// - /// Determine whether a module info matches a given module specification table and specified PSEdition value. - /// - /// - /// - /// - /// - private static bool ModuleMatch(PSModuleInfo moduleInfo, IDictionary moduleSpecTable, string edition) - { - ModuleSpecification moduleSpecification; - return (String.IsNullOrEmpty(edition) || moduleInfo.CompatiblePSEditions.Contains(edition, StringComparer.OrdinalIgnoreCase)) && - (!moduleSpecTable.TryGetValue(moduleInfo.Name, out moduleSpecification) || ModuleIntrinsics.IsModuleMatchingModuleSpec(moduleInfo, moduleSpecification)); - } - private void GetAvailableViaCimSession(IEnumerable names, IDictionary moduleSpecTable, CimSession cimSession, Uri resourceUri, string cimNamespace) { - var remoteModules = GetAvailableViaCimSessionCore(names, cimSession, resourceUri, cimNamespace); + IEnumerable remoteModules = GetAvailableViaCimSessionCore(names, cimSession, resourceUri, cimNamespace); - foreach (var remoteModule in remoteModules.Where(remoteModule => ModuleMatch(remoteModule, moduleSpecTable, PSEdition)) - ) + foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable)) { RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, cimSession, resourceUri, cimNamespace); @@ -455,10 +458,9 @@ private void GetAvailableViaCimSession(IEnumerable names, IDictionary moduleSpecTable, PSSession session) { - var remoteModules = GetAvailableViaPsrpSessionCore(names, session.Runspace); + IEnumerable remoteModules = GetAvailableViaPsrpSessionCore(names, session.Runspace); - foreach (var remoteModule in remoteModules.Where(remoteModule => ModuleMatch(remoteModule, moduleSpecTable, PSEdition)) - ) + foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable)) { RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, session); this.WriteObject(remoteModule); @@ -467,14 +469,10 @@ private void GetAvailableViaPsrpSession(string[] names, IDictionary moduleSpecTable, bool all) { - var refresh = Refresh.IsPresent; - var modules = GetModule(names, all, refresh); - - foreach ( - var psModule in - modules.Where(module => ModuleMatch(module, moduleSpecTable, PSEdition)).Select(module => new PSObject(module)) - ) + IEnumerable modules = GetModule(names, all, Refresh); + foreach (PSModuleInfo module in FilterModulesForEditionAndSpecification(modules, moduleSpecTable)) { + var psModule = new PSObject(module); psModule.TypeNames.Insert(0, "ModuleInfoGrouping"); WriteObject(psModule); } @@ -484,36 +482,117 @@ private void GetLoadedModules(string[] names, IDictionary ModuleMatch(moduleInfo, moduleSpecTable, PSEdition)) - ) + foreach (PSModuleInfo moduleInfo in FilterModulesForEditionAndSpecification(modulesToWrite, moduleSpecTable)) { WriteObject(moduleInfo); } } - } - /// - /// PSEditionArgumentCompleter for PowerShell Edition names. - /// - public class PSEditionArgumentCompleter : IArgumentCompleter - { /// - /// CompleteArgument + /// Filter an enumeration of PowerShell modules based on the required PowerShell edition + /// and the module specification constraints set for each module (if any). + /// + /// The modules to filter through. + /// Module constraints, keyed by module name, to filter modules of that name by. + /// All modules from the original input that meet both any module edition and module specification constraints provided. + private IEnumerable FilterModulesForEditionAndSpecification( + IEnumerable modules, + IDictionary moduleSpecificationTable) + { +#if !UNIX + // Edition check only applies to Windows System32 module path + if (!SkipEditionCheck && ListAvailable && !All) + { + modules = modules.Where(static module => module.IsConsideredEditionCompatible); + } +#endif + + if (!string.IsNullOrEmpty(PSEdition)) + { + modules = modules.Where(module => module.CompatiblePSEditions.Contains(PSEdition, StringComparer.OrdinalIgnoreCase)); + } + + if (moduleSpecificationTable != null && moduleSpecificationTable.Count > 0) + { + modules = FilterModulesForSpecificationMatch(modules, moduleSpecificationTable); + } + + return modules; + } + + /// + /// Take an enumeration of modules and only return those that match a specification + /// in the given specification table, or have no corresponding entry in the specification table. /// - public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + /// The modules to filter by specification match. + /// The specification lookup table to filter the modules on. + /// The modules that match their corresponding table entry, or which have no table entry. + private static IEnumerable FilterModulesForSpecificationMatch( + IEnumerable modules, + IDictionary moduleSpecificationTable) { - var wordToCompletePattern = WildcardPattern.Get(string.IsNullOrWhiteSpace(wordToComplete) ? "*" : wordToComplete + "*", WildcardOptions.IgnoreCase); + Dbg.Assert(moduleSpecificationTable != null, $"Caller to verify that {nameof(moduleSpecificationTable)} is not null"); + Dbg.Assert(moduleSpecificationTable.Count != 0, $"Caller to verify that {nameof(moduleSpecificationTable)} is not empty"); - foreach (var edition in Utils.AllowedEditionValues) + foreach (PSModuleInfo module in modules) { - if (wordToCompletePattern.IsMatch(edition)) + IEnumerable candidateModuleSpecs = GetCandidateModuleSpecs(moduleSpecificationTable, module); + + // Modules with table entries only get returned if they match them + // We skip the name check since modules have already been prefiltered base on the moduleSpec path/name + foreach (ModuleSpecification moduleSpec in candidateModuleSpecs) { - yield return new CompletionResult(edition, edition, CompletionResultType.Text, edition); + if (ModuleIntrinsics.IsModuleMatchingModuleSpec(module, moduleSpec, skipNameCheck: true)) + { + yield return module; + } + } + } + } + + /// + /// Take a dictionary of module specifications and return those that potentially match the module + /// passed in as a parameter (checks on names and paths). + /// + /// The module specifications to filter candidates from. + /// The module to find candidates for from the module specification table. + /// The module specifications matching the module based on name, path and subpath. + private static IEnumerable GetCandidateModuleSpecs( + IDictionary moduleSpecTable, + PSModuleInfo module) + { + const WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; + foreach (ModuleSpecification moduleSpec in moduleSpecTable.Values) + { + WildcardPattern namePattern = WildcardPattern.Get(moduleSpec.Name, options); + if (namePattern.IsMatch(module.Name) || moduleSpec.Name == module.Path || module.Path.Contains(moduleSpec.Name)) + { + yield return moduleSpec; } } } } -} // Microsoft.PowerShell.Commands + /// + /// Provides argument completion for PSEdition parameter. + /// + public class PSEditionArgumentCompleter : IArgumentCompleter + { + /// + /// Returns completion results for PSEdition 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: Utils.AllowedEditionValues); + } +} diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 90d8308e2c7..532079d1a77 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -1,28 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; -using System.Management.Automation.Runspaces; -using System.Reflection; using System.IO; +using System.Linq; using System.Management.Automation; +using System.Management.Automation.Configuration; using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Reflection; using System.Security; using System.Threading; + using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Cmdletization; -using Dbg = System.Management.Automation.Diagnostics; +using Microsoft.PowerShell.Telemetry; -using System.Management.Automation.Language; +using Dbg = System.Management.Automation.Diagnostics; using Parser = System.Management.Automation.Language.Parser; using ScriptBlock = System.Management.Automation.ScriptBlock; using Token = System.Management.Automation.Language.Token; + #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -33,9 +37,9 @@ namespace Microsoft.PowerShell.Commands { /// - /// Implements a cmdlet that loads a module + /// Implements a cmdlet that loads a module. /// - [Cmdlet(VerbsData.Import, "Module", DefaultParameterSetName = ParameterSet_Name, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141553")] + [Cmdlet(VerbsData.Import, "Module", DefaultParameterSetName = ParameterSet_Name, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096585")] [OutputType(typeof(PSModuleInfo))] public sealed class ImportModuleCommand : ModuleCmdletBase, IDisposable { @@ -49,27 +53,31 @@ public sealed class ImportModuleCommand : ModuleCmdletBase, IDisposable private const string ParameterSet_ViaPsrpSession = "PSSession"; private const string ParameterSet_ViaCimSession = "CimSession"; private const string ParameterSet_FQName_ViaPsrpSession = "FullyQualifiedNameAndPSSession"; + private const string ParameterSet_ViaWinCompat = "WinCompat"; + private const string ParameterSet_FQName_ViaWinCompat = "FullyQualifiedNameAndWinCompat"; /// /// This parameter specifies whether to import to the current session state - /// or to the global / top-level session state + /// or to the global / top-level session state. /// [Parameter] public SwitchParameter Global { - set { base.BaseGlobal = value; } get { return base.BaseGlobal; } + + set { base.BaseGlobal = value; } } /// - /// This parameter specified a prefix used to modify names of imported commands + /// This parameter specified a prefix used to modify names of imported commands. /// [Parameter] [ValidateNotNull] public string Prefix { - set { BasePrefix = value; } get { return BasePrefix; } + + set { BasePrefix = value; } } /// @@ -78,14 +86,18 @@ public string Prefix [Parameter(ParameterSetName = ParameterSet_Name, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [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; } = Utils.EmptyArray(); + public string[] Name { get; set; } = Array.Empty(); /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(ParameterSetName = ParameterSet_FQName, Mandatory = true, ValueFromPipeline = true, Position = 0)] [Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [Parameter(ParameterSetName = ParameterSet_FQName_ViaWinCompat, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public ModuleSpecification[] FullyQualifiedName { get; set; } @@ -94,6 +106,7 @@ public string Prefix /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] [Parameter(ParameterSetName = ParameterSet_Assembly, Mandatory = true, ValueFromPipeline = true, Position = 0)] + [ValidateTrustedData] public Assembly[] Assembly { get; set; } /// @@ -104,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) @@ -117,9 +135,9 @@ public string[] Function BaseFunctionPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _functionImportList; } } - private string[] _functionImportList = Utils.EmptyArray(); + + private string[] _functionImportList = Array.Empty(); /// /// This patterns matching the names of cmdlets to import from the module... @@ -129,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) @@ -143,9 +166,9 @@ public string[] Cmdlet BaseCmdletPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _cmdletImportList; } } - private string[] _cmdletImportList = Utils.EmptyArray(); + + private string[] _cmdletImportList = Array.Empty(); /// /// This parameter specifies the variables to import from the module... @@ -155,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) @@ -168,8 +196,8 @@ public string[] Variable BaseVariablePatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _variableExportList; } } + private string[] _variableExportList; /// @@ -180,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) @@ -194,8 +227,8 @@ public string[] Alias BaseAliasPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _aliasExportList; } } + private string[] _aliasExportList; /// @@ -205,9 +238,28 @@ public string[] Alias public SwitchParameter Force { get { return (SwitchParameter)BaseForce; } + set { BaseForce = value; } } + /// + /// Skips the check on CompatiblePSEditions for modules loaded from the System32 module path. + /// This is mutually exclusive with UseWindowsPowerShell parameter. + /// + [Parameter(ParameterSetName = ParameterSet_Name)] + [Parameter(ParameterSetName = ParameterSet_FQName)] + [Parameter(ParameterSetName = ParameterSet_ModuleInfo)] + [Parameter(ParameterSetName = ParameterSet_Assembly)] + [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] + [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession)] + public SwitchParameter SkipEditionCheck + { + get { return (SwitchParameter)BaseSkipEditionCheck; } + + set { BaseSkipEditionCheck = value; } + } + /// /// This parameter causes the session state instance to be written... /// @@ -215,6 +267,7 @@ public SwitchParameter Force public SwitchParameter PassThru { get { return (SwitchParameter)BasePassThru; } + set { BasePassThru = value; } } @@ -225,6 +278,7 @@ public SwitchParameter PassThru public SwitchParameter AsCustomObject { get { return (SwitchParameter)BaseAsCustomObject; } + set { BaseAsCustomObject = value; } } @@ -234,10 +288,12 @@ public SwitchParameter AsCustomObject [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter(ParameterSetName = ParameterSet_ViaWinCompat)] [Alias("Version")] public Version MinimumVersion { get { return BaseMinimumVersion; } + set { BaseMinimumVersion = value; } } @@ -247,6 +303,7 @@ public Version MinimumVersion [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter(ParameterSetName = ParameterSet_ViaWinCompat)] public string MaximumVersion { get @@ -256,6 +313,7 @@ public string MaximumVersion else return BaseMaximumVersion.ToString(); } + set { if (string.IsNullOrWhiteSpace(value)) @@ -275,18 +333,21 @@ public string MaximumVersion [Parameter(ParameterSetName = ParameterSet_Name)] [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession)] [Parameter(ParameterSetName = ParameterSet_ViaCimSession)] + [Parameter(ParameterSetName = ParameterSet_ViaWinCompat)] public Version RequiredVersion { get { return BaseRequiredVersion; } + set { BaseRequiredVersion = value; } } /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [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; } = Utils.EmptyArray(); + public PSModuleInfo[] ModuleInfo { get; set; } = Array.Empty(); /// /// The arguments to pass to the module script. @@ -297,6 +358,7 @@ public Version RequiredVersion public object[] ArgumentList { get { return BaseArgumentList; } + set { BaseArgumentList = value; } } @@ -308,6 +370,7 @@ public object[] ArgumentList public SwitchParameter DisableNameChecking { get { return BaseDisableNameChecking; } + set { BaseDisableNameChecking = value; } } @@ -318,24 +381,29 @@ public SwitchParameter DisableNameChecking public SwitchParameter NoClobber { get; set; } /// - /// Imports a command to the scope specified + /// Imports a command to the scope specified. /// [Parameter] [ValidateSet("Local", "Global")] - public String Scope + public string Scope { - get { return _scope; } + get + { + return _scope; + } + set { _scope = value; _isScopeSpecified = true; } } + private string _scope = string.Empty; private bool _isScopeSpecified = false; /// - /// If specified, then Import-Module will attempt to import PowerShell modules from a remote computer using the specified session + /// If specified, then Import-Module will attempt to import PowerShell modules from a remote computer using the specified session. /// [Parameter(ParameterSetName = ParameterSet_ViaPsrpSession, Mandatory = true)] [Parameter(ParameterSetName = ParameterSet_FQName_ViaPsrpSession, Mandatory = true)] @@ -349,26 +417,35 @@ public ImportModuleCommand() } /// - /// If specified, then Import-Module will attempt to import PS-CIM modules from a remote computer using the specified session + /// If specified, then Import-Module will attempt to import PS-CIM modules from a remote computer using the specified session. /// [Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = true)] [ValidateNotNull] public CimSession CimSession { get; set; } /// - /// For interoperability with 3rd party CIM servers, user can specify custom resource URI + /// For interoperability with 3rd party CIM servers, user can specify custom resource URI. /// [Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = false)] [ValidateNotNull] public Uri CimResourceUri { get; set; } /// - /// For interoperability with 3rd party CIM servers, user can specify custom namespace + /// For interoperability with 3rd party CIM servers, user can specify custom namespace. /// [Parameter(ParameterSetName = ParameterSet_ViaCimSession, Mandatory = false)] [ValidateNotNullOrEmpty] public string CimNamespace { get; set; } + /// + /// This parameter causes a module to be loaded into Windows PowerShell. + /// This is mutually exclusive with SkipEditionCheck parameter. + /// + [Parameter(ParameterSetName = ParameterSet_ViaWinCompat, Mandatory = true)] + [Parameter(ParameterSetName = ParameterSet_FQName_ViaWinCompat, Mandatory = true)] + [Alias("UseWinPS")] + public SwitchParameter UseWindowsPowerShell { get; set; } + #endregion Cmdlet parameters #region Local import @@ -378,8 +455,8 @@ private void ImportModule_ViaLocalModuleInfo(ImportModuleOptions importModuleOpt try { PSModuleInfo alreadyLoadedModule = null; - Context.Modules.ModuleTable.TryGetValue(module.Path, out alreadyLoadedModule); - if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule)) + TryGetFromModuleTable(module.Path, out alreadyLoadedModule); + if (!BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(alreadyLoadedModule)) { AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, alreadyLoadedModule); @@ -409,7 +486,7 @@ private void ImportModule_ViaLocalModuleInfo(ImportModuleOptions importModuleOpt else { PSModuleInfo moduleToRemove; - if (Context.Modules.ModuleTable.TryGetValue(module.Path, out moduleToRemove)) + if (TryGetFromModuleTable(module.Path, out moduleToRemove, toRemove: true)) { Dbg.Assert(BaseForce, "We should only remove and reload if -Force was specified"); RemoveModule(moduleToRemove); @@ -458,7 +535,6 @@ private void ImportModule_ViaLocalModuleInfo(ImportModuleOptions importModuleOpt } catch (IOException) { - ; } } } @@ -472,54 +548,58 @@ 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 == "") - { - 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) { WriteObject(pair.Value); } + 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); @@ -528,15 +608,33 @@ private void ImportModule_ViaAssembly(ImportModuleOptions importModuleOptions, A } } - private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name) + private PSModuleInfo ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptions importModuleOptions, string name) { - try + PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, name); + if (foundModule != null) { - if (name.Equals("PSWorkflow", StringComparison.OrdinalIgnoreCase) && Utils.IsRunningFromSysWOW64()) + SetModuleBaseForEngineModules(foundModule.Name, this.Context); + + // report loading of the module in telemetry + // avoid double reporting for WinCompat modules that go through CommandDiscovery\AutoloadSpecifiedModule + if (!foundModule.IsWindowsPowerShellCompatModule) { - throw new NotSupportedException(AutomationExceptions.WorkflowDoesNotSupportWOW64); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); +#if LEGACYTELEMETRY + TelemetryAPI.ReportModuleLoad(foundModule); +#endif } + } + + return foundModule; + } + private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name) + { + bool shallWriteError = !importModuleOptions.SkipSystem32ModulesAndSuppressError; + + try + { bool found = false; PSModuleInfo foundModule = null; @@ -563,32 +661,23 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } } - if (rootedPath == null) - { - // Check for full-qualified paths - either absolute or relative - rootedPath = ResolveRootedFilePath(name, this.Context); - } + // If null check for full-qualified paths - either absolute or relative + rootedPath ??= ResolveRootedFilePath(name, this.Context); bool alreadyLoaded = false; - if (!String.IsNullOrEmpty(rootedPath)) + var manifestProcessingFlags = ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.NullOnFirstError; + if (shallWriteError) { - // 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; - // Context.Modules.ModuleTable.TryGetValue(rootedPath, out alreadyLoadedModule); - // if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule)) + manifestProcessingFlags |= ManifestProcessingFlags.WriteErrors; + } + if (!string.IsNullOrEmpty(rootedPath)) + { // If the module has already been loaded, just emit it and continue... - PSModuleInfo module; - if (!BaseForce && Context.Modules.ModuleTable.TryGetValue(rootedPath, out module)) + if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module)) { - if (RequiredVersion == null - || module.Version.Equals(RequiredVersion) - || (BaseMinimumVersion == null && BaseMaximumVersion == null) - || module.ModuleType != ModuleType.Manifest - || (BaseMinimumVersion == null && BaseMaximumVersion != null && module.Version <= BaseMaximumVersion) - || (BaseMinimumVersion != null && BaseMaximumVersion == null && module.Version >= BaseMinimumVersion) - || (BaseMinimumVersion != null && BaseMaximumVersion != null && module.Version >= BaseMinimumVersion && module.Version <= BaseMaximumVersion)) + if (module.ModuleType != ModuleType.Manifest + || ModuleIntrinsics.IsVersionMatchingConstraints(module.Version, RequiredVersion, BaseMinimumVersion, BaseMaximumVersion)) { alreadyLoaded = true; AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); @@ -613,6 +702,7 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul { WriteObject(module); } + found = true; foundModule = module; } @@ -624,33 +714,47 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul if (File.Exists(rootedPath)) { PSModuleInfo moduleToRemove; - if (Context.Modules.ModuleTable.TryGetValue(rootedPath, out moduleToRemove)) + if (TryGetFromModuleTable(rootedPath, out moduleToRemove, toRemove: true)) { 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)) { + // If the path ends with a directory separator, remove it + if (rootedPath.EndsWith(Path.DirectorySeparatorChar)) + { + rootedPath = Path.GetDirectoryName(rootedPath); + } + // 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); } } } @@ -660,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) @@ -684,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 @@ -705,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; @@ -732,6 +851,7 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul { message = StringUtil.Format(Modules.MaximumVersionNotFound, name, BaseMaximumVersion); } + if (BaseRequiredVersion != null || BaseMinimumVersion != null || BaseMaximumVersion != null) { FileNotFoundException fnf = new FileNotFoundException(message); @@ -745,6 +865,7 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul er = new ErrorRecord(fnf, "Modules_ModuleNotFound", ErrorCategory.ResourceUnavailable, name); } + WriteError(er); } @@ -752,13 +873,33 @@ 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; } + private PSModuleInfo ImportModule_LocallyViaFQName(ImportModuleOptions importModuleOptions, ModuleSpecification modulespec) + { + RequiredVersion = modulespec.RequiredVersion; + MinimumVersion = modulespec.Version; + MaximumVersion = modulespec.MaximumVersion; + BaseGuid = modulespec.Guid; + + PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, modulespec.Name); + + if (foundModule != null) + { + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); + SetModuleBaseForEngineModules(foundModule.Name, this.Context); + } + + return foundModule; + } + #endregion Local import #region Remote import @@ -769,7 +910,8 @@ private IList ImportModule_RemotelyViaPsrpSession( ImportModuleOptions importModuleOptions, IEnumerable moduleNames, IEnumerable fullyQualifiedNames, - PSSession psSession) + PSSession psSession, + bool usingWinCompat = false) { var remotelyImportedModules = new List(); if (moduleNames != null) @@ -790,6 +932,12 @@ private IList ImportModule_RemotelyViaPsrpSession( } } + // Send telemetry on the imported modules + foreach (PSModuleInfo moduleInfo in remotelyImportedModules) + { + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo); + } + return remotelyImportedModules; } @@ -822,19 +970,23 @@ private IList ImportModule_RemotelyViaPsrpSession( { powerShell.AddParameter("Version", this.MinimumVersion); } + if (this.RequiredVersion != null) { powerShell.AddParameter("RequiredVersion", this.RequiredVersion); } + if (this.MaximumVersion != null) { powerShell.AddParameter("MaximumVersion", this.MaximumVersion); } } + if (this.ArgumentList != null) { powerShell.AddParameter("ArgumentList", this.ArgumentList); } + if (this.BaseForce) { powerShell.AddParameter("Force", true); @@ -843,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(); @@ -952,7 +1104,10 @@ private PSModuleInfo ImportModule_RemotelyViaPsrpSession_SinglePreimportedModule { powerShell.AddCommand("Export-PSSession"); powerShell.AddParameter("OutputModule", wildcardEscapedPath); - powerShell.AddParameter("AllowClobber", true); + if (!importModuleOptions.NoClobberExportPSSession) + { + powerShell.AddParameter("AllowClobber", true); + } powerShell.AddParameter("Module", remoteModuleName); // remoteModulePath is currently unsupported by Get-Command and implicit remoting powerShell.AddParameter("Force", true); powerShell.AddParameter("FormatTypeName", "*"); @@ -962,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; } @@ -977,6 +1131,7 @@ private PSModuleInfo ImportModule_RemotelyViaPsrpSession_SinglePreimportedModule { File.Delete(localPsd1File); } + File.Move( sourceFileName: Path.Combine(temporaryModulePath, Path.GetFileName(temporaryModulePath) + ".psd1"), destFileName: localPsd1File); @@ -1013,14 +1168,14 @@ private PSModuleInfo ImportModule_RemotelyViaPsrpSession_SinglePreimportedModule // // make sure the temporary folder gets removed when the module is removed // - PSModuleInfo moduleInfo; string psm1Path = Path.Combine(temporaryModulePath, Path.GetFileName(temporaryModulePath) + ".psm1"); - if (!this.Context.Modules.ModuleTable.TryGetValue(psm1Path, out moduleInfo)) + if (!TryGetFromModuleTable(psm1Path, out PSModuleInfo moduleInfo, toRemove: true)) { if (Directory.Exists(temporaryModulePath)) { Directory.Delete(temporaryModulePath, recursive: true); } + return null; } @@ -1050,6 +1205,7 @@ private PSModuleInfo ImportModule_RemotelyViaPsrpSession_SinglePreimportedModule { Directory.Delete(temporaryModulePath, recursive: true); } + throw; } } @@ -1086,6 +1242,7 @@ private bool IsMixedModePsCimModule(RemoteDiscoveryHelper.CimModule cimModule) { return true; } + Hashtable manifestData = RemoteDiscoveryHelper.ConvertCimModuleFileToManifestHashtable( mainManifestFile, temporaryModuleManifestPath, @@ -1169,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( @@ -1188,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); @@ -1209,19 +1366,32 @@ private void ImportModule_RemotelyViaCimSession( foreach (RemoteDiscoveryHelper.CimModule remoteCimModule in remotePsCimModules) { ImportModule_RemotelyViaCimModuleData(importModuleOptions, remoteCimModule, cimSession); + // 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("", s, ".ps1xml").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; @@ -1239,20 +1409,16 @@ 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)) { badEntries = new List(); } - if (badEntries == null) - { - badEntries = new List(); - } + + badEntries ??= new List(); bool presentInGoodEntries = IsPs1xmlFileHelper_IsPresentInEntries(cimModuleFile, goodEntries); bool presentInBadEntries = IsPs1xmlFileHelper_IsPresentInEntries(cimModuleFile, badEntries); @@ -1274,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, @@ -1387,6 +1553,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( { return null; } + localizedData = data; } @@ -1400,6 +1567,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( { moduleVersion = null; } + temporaryModuleDirectory = RemoteDiscoveryHelper.GetModulePath( remoteCimModule.ModuleName, moduleVersion, @@ -1415,6 +1583,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( { return alreadyImportedModule; } + try { Directory.CreateDirectory(temporaryModuleDirectory); @@ -1448,7 +1617,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( // moduleInfo = LoadModuleManifest( temporaryModuleManifestPath, - null, //scriptInfo + null, // scriptInfo data, localizedData, ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, @@ -1462,6 +1631,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( { return null; } + foreach (PSModuleInfo nestedModule in moduleInfo.NestedModules) { Type cmdletAdapter; @@ -1485,6 +1655,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( this.ThrowTerminatingError(errorRecord); } } + if (IsMixedModePsCimModule(remoteCimModule)) { // warn that some commands have not been imported @@ -1555,6 +1726,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( { Directory.Delete(temporaryModuleDirectory, recursive: true); } + throw; } finally @@ -1583,6 +1755,7 @@ private PSModuleInfo ImportModule_RemotelyViaCimModuleData( #region Cancellation support private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private CancellationToken CancellationToken { get @@ -1607,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; } @@ -1638,7 +1799,7 @@ private void Dispose(bool disposing) #endregion /// - /// BeginProcessing override + /// BeginProcessing override. /// protected override void BeginProcessing() { @@ -1650,6 +1811,7 @@ protected override void BeginProcessing() ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } + if (!string.IsNullOrEmpty(Scope) && Scope.Equals(StringLiterals.Global, StringComparison.OrdinalIgnoreCase)) { base.BaseGlobal = true; @@ -1666,10 +1828,10 @@ protected override void BeginProcessing() /// c:\temp\mdir\mdir # resolve by using extensions. mdir is a directory, mdir.xxx is a file. /// c:\temp\mdir # load default module if mdir is directory /// module # $PSScriptRoot/module/module.psd1 (ps1,psm1,dll) - /// module/foobar.psm1 # $PSScriptRoot/module/module.psm1 - /// module/foobar # $PSScriptRoot/module/foobar.XXX if foobar is not a directory... - /// module/foobar # $PSScriptRoot/module/foobar is a directory and $PSScriptRoot/module/foobar/foobar.XXX exists - /// module/foobar/foobar.XXX + /// module/examplemodule.psm1 # $PSScriptRoot/module/module.psm1 + /// module/examplemodule # $PSScriptRoot/module/examplemodule.XXX if examplemodule is not a directory... + /// module/examplemodule # $PSScriptRoot/module/examplemodule is a directory and $PSScriptRoot/module/examplemodule/examplemodule.XXX exists + /// module/examplemodule/examplemodule.XXX /// protected override void ProcessRecord() { @@ -1678,6 +1840,7 @@ protected override void ProcessRecord() string message = StringUtil.Format(Modules.MinimumVersionAndMaximumVersionInvalidRange, BaseMinimumVersion, BaseMaximumVersion); throw new PSArgumentOutOfRangeException(message); } + ImportModuleOptions importModuleOptions = new ImportModuleOptions(); importModuleOptions.NoClobber = NoClobber; if (!string.IsNullOrEmpty(Scope) && Scope.Equals(StringLiterals.Local, StringComparison.OrdinalIgnoreCase)) @@ -1691,13 +1854,14 @@ protected override void ProcessRecord() // of doing Get-Module -list foreach (PSModuleInfo module in ModuleInfo) { + 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, @@ -1716,27 +1880,18 @@ 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) - { - 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)) { foreach (string name in Name) { - PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, name); - if (null != foundModule) - { - SetModuleBaseForEngineModules(foundModule.Name, this.Context); - -#if LEGACYTELEMETRY - TelemetryAPI.ReportModuleLoad(foundModule); -#endif - } + ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, name); } } else if (this.ParameterSetName.Equals(ParameterSet_ViaPsrpSession, StringComparison.OrdinalIgnoreCase)) @@ -1751,21 +1906,24 @@ protected override void ProcessRecord() { foreach (var modulespec in FullyQualifiedName) { - RequiredVersion = modulespec.RequiredVersion; - MinimumVersion = modulespec.Version; - MaximumVersion = modulespec.MaximumVersion; - BaseGuid = modulespec.Guid; - - PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, modulespec.Name); - if (null != foundModule) - { - SetModuleBaseForEngineModules(foundModule.Name, this.Context); - } + ImportModule_LocallyViaFQName(importModuleOptions, modulespec); } } else if (this.ParameterSetName.Equals(ParameterSet_FQName_ViaPsrpSession, StringComparison.OrdinalIgnoreCase)) { ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession); + foreach (ModuleSpecification modulespec in FullyQualifiedName) + { + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name); + } + } + else if (this.ParameterSetName.Equals(ParameterSet_ViaWinCompat, StringComparison.OrdinalIgnoreCase) + || this.ParameterSetName.Equals(ParameterSet_FQName_ViaWinCompat, StringComparison.OrdinalIgnoreCase)) + { + if (this.UseWindowsPowerShell) + { + ImportModulesUsingWinCompat(this.Name, this.FullyQualifiedName, importModuleOptions); + } } else { @@ -1773,7 +1931,211 @@ protected override void ProcessRecord() } } - private void SetModuleBaseForEngineModules(string moduleName, System.Management.Automation.ExecutionContext context) + private bool IsModuleInDenyList(string[] moduleDenyList, string moduleName, ModuleSpecification 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 exactModuleName = ModuleIntrinsics.GetModuleName(moduleSpec == null ? moduleName : moduleSpec.Name); + bool match = false; + + foreach (var deniedModuleName in moduleDenyList) + { + // use case-insensitive module name comparison + match = exactModuleName.Equals(deniedModuleName, StringComparison.InvariantCultureIgnoreCase); + if (match) + { + string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatModuleInDenyList, exactModuleName); + InvalidOperationException exception = new InvalidOperationException(errorMessage); + ErrorRecord er = new ErrorRecord(exception, "Modules_ModuleInWinCompatDenyList", ErrorCategory.ResourceUnavailable, exactModuleName); + WriteError(er); + break; + } + } + + return match; + } + + private IEnumerable FilterModuleCollection(IEnumerable moduleCollection) + { + if (moduleCollection is null) + { + 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.Add(module); + } + } + + return filteredModuleCollection; + } + + private void PrepareNoClobberWinCompatModuleImport(string moduleName, ModuleSpecification moduleSpec, ref ImportModuleOptions importModuleOptions) + { + 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 moduleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec is null ? moduleName : moduleSpec.Name); + + var isBuiltInModule = BuiltInModules.TryGetValue(moduleToLoad, out string normalizedName); + if (isBuiltInModule) + { + moduleToLoad = normalizedName; + } + + string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList(); + if (isBuiltInModule || noClobberModuleList?.Contains(moduleToLoad, StringComparer.OrdinalIgnoreCase) == true) + { + bool shouldLoadModuleLocally = true; + if (isBuiltInModule) + { + PSSnapInInfo loadedSnapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(moduleToLoad); + shouldLoadModuleLocally = loadedSnapin is null; + + if (shouldLoadModuleLocally) + { + // 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 (shouldLoadModuleLocally) + { + // 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) + { + throw new InvalidOperationException( + StringUtil.Format( + Modules.CannotFindCoreCompatibleBuiltInModule, + moduleToLoad)); + } + + importModuleOptions.SkipSystem32ModulesAndSuppressError = savedValue; + } + + importModuleOptions.NoClobberExportPSSession = true; + } + } + + internal override IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) + { + IList 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) + 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) + { + return moduleProxyList; + } + + // perform necessary preparations if module has to be imported with NoClobber mode + if (filteredModuleNames != null) + { + foreach (string moduleName in filteredModuleNames) + { + PrepareNoClobberWinCompatModuleImport(moduleName, null, ref importModuleOptions); + } + } + + if (filteredModuleFullyQualifiedNames != null) + { + foreach (var moduleSpec in filteredModuleFullyQualifiedNames) + { + PrepareNoClobberWinCompatModuleImport(null, moduleSpec, ref importModuleOptions); + } + } + + 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 the module import / proxy generation + moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, filteredModuleNames, filteredModuleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true); + + foreach (PSModuleInfo moduleProxy in moduleProxyList) + { + moduleProxy.IsWindowsPowerShellCompatModule = true; + Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); + + string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name); + WriteWarning(message); + } + + // register LocationChanged handler so that $PWD in Windows PS process mirrors local $PWD changes + if (moduleProxyList.Count > 0) + { + // make sure that we add registration only once to a multicast delegate + SyncCurrentLocationDelegate ??= SyncCurrentLocationHandler; + var alreadyregistered = this.SessionState.InvokeCommand.LocationChangedAction?.GetInvocationList().Contains(SyncCurrentLocationDelegate); + + if (!alreadyregistered ?? true) + { + this.SessionState.InvokeCommand.LocationChangedAction += SyncCurrentLocationDelegate; + + // first sync has to be triggered manually + SyncCurrentLocationHandler(sender: this, args: new LocationChangedEventArgs(sessionState: null, oldPath: null, newPath: this.SessionState.Path.CurrentLocation)); + } + } +#endif + return moduleProxyList; + } + + private static void SetModuleBaseForEngineModules(string moduleName, System.Management.Automation.ExecutionContext context) { // Set modulebase of engine modules to point to $pshome // This is so that Get-Help can load the correct help. @@ -1807,4 +2169,4 @@ private void SetModuleBaseForEngineModules(string moduleName, System.Management. } } } -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index 6d703e93624..0a1d0bfc04f 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -13,17 +12,23 @@ using System.Management.Automation; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; +using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Management.Automation.Security; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Xml; +using System.Diagnostics; + using Microsoft.PowerShell.Cmdletization; + using Dbg = System.Management.Automation.Diagnostics; // // Now define the set of commands for manipulating modules. // + namespace Microsoft.PowerShell.Commands { #region ModuleCmdletBase class @@ -35,7 +40,7 @@ namespace Microsoft.PowerShell.Commands public class ModuleCmdletBase : PSCmdlet { /// - /// Flags defining how a module manifest should be processed + /// Flags defining how a module manifest should be processed. /// [Flags] internal enum ManifestProcessingFlags @@ -56,7 +61,7 @@ internal enum ManifestProcessingFlags LoadElements = 0x4, /// - /// Write warnings + /// Write warnings. /// WriteWarnings = 0x8, @@ -74,7 +79,7 @@ internal enum ManifestProcessingFlags } /// - /// Options set during module import + /// Options set during module import. /// [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] protected internal struct ImportModuleOptions @@ -91,20 +96,31 @@ protected internal struct ImportModuleOptions internal bool Local; /// - /// Win8:90779 - /// ServiceCore assembly is automatically imported as a nested module to process the NestedModules/RootModules/RequiredAssemblies fields in module manifests. - /// This property is set to ensure that Import-Module of a manifest module does not expose "Import-psworkflow" cmdlet. - /// - internal bool ServiceCoreAutoAdded; + /// Lets nested module import to export all of its functions, regardless of language boundaries. + /// This will be allowed when the manifest explicitly exports functions which will limit all visible module functions. + /// + internal bool AllowNestedModuleFunctionsToExport; + + /// + /// Flag that controls Export-PSSession -AllowClobber parameter in generating proxy modules from remote sessions. + /// Historically -AllowClobber in these scenarios was set as True. + /// + internal bool NoClobberExportPSSession; + + /// + /// Flag that controls skipping the System32 module path when searching a module in module paths. It also suppresses + /// writing out errors when specified. + /// + internal bool SkipSystem32ModulesAndSuppressError; } /// - /// This parameter specified a prefix used to modify names of imported commands + /// 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 + /// Flags -force operations. /// internal bool BaseForce { get; set; } @@ -113,6 +129,11 @@ protected internal struct ImportModuleOptions /// internal bool BaseGlobal { get; set; } + /// + /// If set, CompatiblePSEditions checking will be disabled for modules on the System32 path. + /// + internal bool BaseSkipEditionCheck { get; set; } + internal SessionState TargetSessionState { get @@ -129,35 +150,35 @@ internal SessionState TargetSessionState } /// - /// Flags -passthru operations + /// Flags -passthru operations. /// internal bool BasePassThru { get; set; } /// - /// Flags -passthru operations + /// Flags -passthru operations. /// internal bool BaseAsCustomObject { get; set; } /// - /// Wildcard patterns for the function to import + /// Wildcard patterns for the function to import. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Cmdlet parameters.")] internal List BaseFunctionPatterns { get; set; } /// - /// Wildcard patterns for the cmdlets to import + /// Wildcard patterns for the cmdlets to import. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Cmdlet parameters.")] internal List BaseCmdletPatterns { get; set; } /// - /// Wildcard patterns for the variables to import + /// Wildcard patterns for the variables to import. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Cmdlet parameters.")] internal List BaseVariablePatterns { get; set; } /// - /// Wildcard patterns for the aliases to import + /// Wildcard patterns for the aliases to import. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Cmdlet parameters.")] internal List BaseAliasPatterns { get; set; } @@ -187,7 +208,7 @@ internal SessionState TargetSessionState internal Guid? BaseGuid { get; set; } /// - /// The arguments to pass to the scriptblock used to create the module + /// The arguments to pass to the scriptblock used to create the module. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] protected object[] BaseArgumentList { get; set; } @@ -199,7 +220,7 @@ internal SessionState TargetSessionState protected bool BaseDisableNameChecking { get; set; } = true; /// - /// Add module path to app domain level module path cache if name is not rooted + /// Add module path to app domain level module path cache if name is not rooted. /// protected bool AddToAppDomainLevelCache { get; set; } = false; @@ -215,16 +236,18 @@ internal List MatchAll _matchAll = new List(); _matchAll.Add(WildcardPattern.Get("*", WildcardOptions.IgnoreCase)); } + return _matchAll; } } + private List _matchAll; // The list of commands permitted in a module manifest - internal static string[] PermittedCmdlets = new string[] { + internal static readonly string[] PermittedCmdlets = new string[] { "Import-LocalizedData", "ConvertFrom-StringData", "Write-Host", "Out-Host", "Join-Path" }; - internal static string[] ModuleManifestMembers = new string[] { + internal static readonly string[] ModuleManifestMembers = new string[] { "ModuleToProcess", "NestedModules", "GUID", @@ -258,73 +281,128 @@ internal List MatchAll "DefaultCommandPrefix" }; - private static string[] s_moduleVersionMembers = new string[] { + private static readonly string[] s_moduleVersionMembers = new string[] { "ModuleName", "GUID", "ModuleVersion" }; - private static List s_serviceCoreAssemblyCmdlets = new List(new string[] { - "Microsoft.PowerShell.Workflow.ServiceCore\\Import-PSWorkflow", - "Microsoft.PowerShell.Workflow.ServiceCore\\New-PSWorkflowExecutionOption", - }); + /// + /// 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. + /// The PSModuleInfo will still reflect the lack of value. + /// + internal static IReadOnlyList DefaultCompatiblePSEditions { get; } = new string[] + { + "Desktop" + }; + + /// + /// A counter for modules that are loaded using WindowsPS compat session. + /// + internal static int s_WindowsPowerShellCompatUsageCounter = 0; + + /// + /// Session name for WindowsPS compat remoting session. + /// + internal const string WindowsPowerShellCompatRemotingSessionName = "WinPSCompatSession"; + + /// + /// Synchronization object for creation/cleanup of WindowsPS compat remoting session. + /// + 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; module = null; - if (String.IsNullOrEmpty(extension) || !ModuleIntrinsics.IsPowerShellModuleExtension(extension)) + if (string.IsNullOrEmpty(extension) || !ModuleIntrinsics.IsPowerShellModuleExtension(extension)) { fileBaseName = name; 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 - string qualifiedPath = Path.Combine(path, fileBaseName); - module = LoadUsingMultiVersionModuleBase(qualifiedPath, manifestProcessingFlags, options, out found); - if (!found) - { - if (name.IndexOfAny(Utils.Separators.Directory) == -1) - { - qualifiedPath = Path.Combine(qualifiedPath, fileBaseName); - } - else if (Utils.NativeDirectoryExists(qualifiedPath)) - { - // if it points to a directory, add the basename back onto the path... - qualifiedPath = Path.Combine(qualifiedPath, Path.GetFileName(fileBaseName)); - } + string qualifiedPath = Path.Combine(path, fileBaseName); + module = LoadUsingMultiVersionModuleBase(qualifiedPath, manifestProcessingFlags, options, out found); + if (!found) + { + if (name.AsSpan().IndexOfAny('\\', '/') == -1) + { + qualifiedPath = Path.Combine(qualifiedPath, fileBaseName); + } + else if (Directory.Exists(qualifiedPath)) + { + // if it points to a directory, add the basename back onto the path... + qualifiedPath = Path.Combine(qualifiedPath, Path.GetFileName(fileBaseName)); + } - module = LoadUsingExtensions(parentModule, name, qualifiedPath, extension, null, this.BasePrefix, ss, options, manifestProcessingFlags, out found); - } - if (found) - { - break; - } + module = LoadUsingExtensions(parentModule, name, qualifiedPath, extension, null, this.BasePrefix, ss, options, manifestProcessingFlags, out found); + } + + if (found) + { + break; + } #if UNIX } } + if (found) { break; @@ -334,28 +412,23 @@ internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumer if (found) { - // Cache the module's exported commands after importing it, or if the -Refresh flag is used on - // "Get-Module -List" + // Cache the module's exported commands after importing it, or if the -Refresh flag is used on "Get-Module -List" if ((module != null) && !module.HadErrorsLoading) { - if (module.ExportedWorkflows != null && module.ExportedWorkflows.Count > 0 && Utils.IsRunningFromSysWOW64()) - { - throw new NotSupportedException(AutomationExceptions.WorkflowDoesNotSupportWOW64); - } - AnalysisCache.CacheModuleExports(module, Context); } } + return found; } /// - /// Loads the latest valid version if moduleBase is a multi-versioned module directory + /// Loads the latest valid version if moduleBase is a multi-versioned module directory. /// - /// module directory path - /// The flag that indicate manifest processing option - /// The set of options that are used while importing a module - /// True if a module was found + /// Module directory path. + /// The flag that indicate manifest processing option. + /// The set of options that are used while importing a module. + /// True if a module was found. /// internal PSModuleInfo LoadUsingMultiVersionModuleBase(string moduleBase, ManifestProcessingFlags manifestProcessingFlags, ImportModuleOptions importModuleOptions, out bool found) { @@ -365,9 +438,7 @@ internal PSModuleInfo LoadUsingMultiVersionModuleBase(string moduleBase, Manifes foreach (var version in ModuleUtils.GetModuleVersionSubfolders(moduleBase)) { // Skip the version folder if it is not equal to the required version or does not satisfy the minimum/maximum version criteria - if ((BaseRequiredVersion != null && !BaseRequiredVersion.Equals(version)) - || (BaseMinimumVersion != null && BaseRequiredVersion == null && version < BaseMinimumVersion) - || (BaseMaximumVersion != null && BaseRequiredVersion == null && version > BaseMaximumVersion)) + if (!ModuleIntrinsics.IsVersionMatchingConstraints(version, BaseRequiredVersion, BaseMinimumVersion, BaseMaximumVersion)) { continue; } @@ -398,7 +469,7 @@ internal PSModuleInfo LoadUsingMultiVersionModuleBase(string moduleBase, Manifes if (!isValidModuleVersion) { - WriteVerbose(String.Format(CultureInfo.InvariantCulture, Modules.SkippingInvalidModuleVersionFolder, + WriteVerbose(string.Format(CultureInfo.InvariantCulture, Modules.SkippingInvalidModuleVersionFolder, version.ToString(), moduleBase)); } } @@ -421,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); @@ -429,15 +500,16 @@ private Hashtable LoadModuleManifestData( ErrorCategory.ResourceUnavailable, scriptInfo.Path); WriteError(er); } + containedErrors = true; return null; } } /// - /// Extra variables that are allowed to be referenced in module manifest file + /// Extra variables that are allowed to be referenced in module manifest file. /// - private static readonly string[] s_extraAllowedVariables = new string[] { "PSScriptRoot", "PSEdition" }; + private static readonly string[] s_extraAllowedVariables = new string[] { SpecialVariables.PSScriptRoot, SpecialVariables.PSEdition, SpecialVariables.EnabledExperimentalFeatures }; /// /// Load and execute the manifest psd1 file or a localized manifest psd1 file. @@ -451,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 @@ -469,6 +541,7 @@ internal Hashtable LoadModuleManifestData( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + containedErrors = true; return null; } @@ -512,6 +585,7 @@ internal Hashtable LoadModuleManifestData( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + containedErrors = true; return null; } @@ -522,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; } @@ -557,22 +631,22 @@ private bool ValidateManifestHash( { if (badKeys.Length > 0) badKeys.Append(", "); - badKeys.Append("'"); + badKeys.Append('\''); badKeys.Append(s); - badKeys.Append("'"); + badKeys.Append('\''); } } + if (badKeys.Length > 0) { 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 // Else, the error is InvalidManifestMember - Version powerShellVersion; Version currentPowerShellVersion = PSVersionInfo.PSVersion; if (GetScalarFromData(data, moduleManifestPath, "PowerShellVersion", manifestProcessingFlags, out powerShellVersion) && @@ -595,7 +669,8 @@ private bool ValidateManifestHash( validMembersString.Append("', '"); 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", @@ -604,12 +679,23 @@ 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) + 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; @@ -621,13 +707,18 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module Version savedBaseRequiredVersion = BaseRequiredVersion; Guid? savedBaseGuid = BaseGuid; - var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); + 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)) + if (string.IsNullOrEmpty(rootedPath)) { - rootedPath = FixupFileName(moduleBase, moduleSpecification.Name, extension); + // 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 { @@ -652,12 +743,13 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module Modules.ManifestMemberNotValid, moduleSpecification.Name, "NestedModules", - parentModule.Path, + parentModule?.Path, StringUtil.Format(Modules.InvalidModuleExtension, extension, moduleSpecification.Name), - ModuleIntrinsics.GetModuleName(parentModule.Path)); + ModuleIntrinsics.GetModuleName(parentModule?.Path)); invalidOperation.SetErrorId("Modules_InvalidModuleExtension"); throw invalidOperation; } + extension = null; } @@ -671,8 +763,8 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module module = LoadUsingExtensions( parentModule, moduleSpecification.Name, - rootedPath, // fileBaseName - /*extension*/null, + fileBaseName: rootedPath, + extension: null, moduleBase, // not using base from tempModuleInfoFromVerification as we are looking under moduleBase directory prefix, ss, @@ -686,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); @@ -695,8 +787,8 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module module = LoadUsingExtensions( parentModule, moduleSpecification.Name, - newRootedPath, // fileBaseName - /*extension*/ null, + fileBaseName: newRootedPath, + extension: null, newModuleBase, // not using base from tempModuleInfoFromVerification as we are looking under moduleBase directory prefix, ss, @@ -727,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); @@ -749,10 +841,10 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module } // The rooted files wasn't found, so don't search anymore... - if (found == false && wasRooted == true) + if (!found && wasRooted) return null; - if (searchModulePath && found == false && moduleFileFound == false) + if (searchModulePath && !found && !moduleFileFound) { if (VerifyIfNestedModuleIsAvailable(moduleSpecification, null, null, out tempModuleInfoFromVerification)) { @@ -786,15 +878,40 @@ 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); } } + if (manifestLanguageMode.HasValue && found && (module != null) && module.LanguageMode.HasValue) + { + // Check for script module language mode consistency. All loaded script modules must have the same language mode as the manifest. + // If not then this indicates a malformed module and a possible exploit to make trusted private functions visible in a + // Constrained Language session. + if (module.LanguageMode != manifestLanguageMode) + { + 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. @@ -826,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) { @@ -872,14 +987,14 @@ internal List GetModule(string[] names, bool all, bool refresh) // Two lists - one to hold Module Paths and one to hold Module Names // For Module Paths, we don't do any path resolution - List modulePaths = new List(); - List moduleNames = new List(); + List modulePaths = new List(); + List moduleNames = new List(); if (names != null) { foreach (var n in names) { - if (n.IndexOf(StringLiterals.DefaultPathSeparator) != -1 || n.IndexOf(StringLiterals.AlternatePathSeparator) != -1) + if (n.Contains(StringLiterals.DefaultPathSeparator) || n.Contains(StringLiterals.AlternatePathSeparator)) { modulePaths.Add(n); } @@ -888,37 +1003,20 @@ internal List GetModule(string[] names, bool all, bool refresh) moduleNames.Add(n); } } - modulesToReturn.AddRange(GetModuleForRootedPaths(modulePaths.ToArray(), all, refresh)); + + modulesToReturn.AddRange(GetModuleForRootedPaths(modulePaths, all, refresh)); } // If no names were passed to this function, then this API will return list of all available modules if (names == null || moduleNames.Count > 0) { - modulesToReturn.AddRange(GetModuleForNonRootedPaths(moduleNames.ToArray(), all, refresh)); + modulesToReturn.AddRange(GetModuleForNames(moduleNames, all, refresh)); } return modulesToReturn; } - private IEnumerable GetModuleForNonRootedPaths(string[] names, bool all, bool refresh) - { - const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(names, wildcardOptions); - - Dictionary> availableModules = GetAvailableLocallyModulesCore(names, all, refresh); - foreach (var entry in availableModules) - { - foreach (PSModuleInfo module in entry.Value) - { - if (SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, patternList, true)) - { - yield return module; - } - } - } - } - - private IEnumerable GetModuleForRootedPaths(string[] modulePaths, bool all, bool refresh) + private IEnumerable GetModuleForRootedPaths(List modulePaths, bool all, bool refresh) { // This is to filter out duplicate modules var modules = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -927,7 +1025,7 @@ private IEnumerable GetModuleForRootedPaths(string[] modulePaths, { 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)) @@ -935,26 +1033,23 @@ private IEnumerable GetModuleForRootedPaths(string[] modulePaths, containsWildCards = true; } - //Now we resolve the possible paths in case it is relative path/path contains wildcards + // Now we resolve the possible paths in case it is relative path/path contains wildcards var modulePathCollection = GetResolvedPathCollection(modulePath, this.Context); - if (modulePathCollection != null) { foreach (string resolvedModulePath in modulePathCollection) { string moduleName = Path.GetFileName(resolvedModulePath); - bool isDirectory = Utils.NativeDirectoryExists(resolvedModulePath); // If the given path is a valid module file, we will load the specific file - if (!isDirectory && ModuleIntrinsics.IsPowerShellModuleExtension(Path.GetExtension(moduleName))) + if (!Directory.Exists(resolvedModulePath) && ModuleIntrinsics.IsPowerShellModuleExtension(Path.GetExtension(moduleName))) { PSModuleInfo module = CreateModuleInfoForGetModule(resolvedModulePath, refresh); if (module != null) { - if (!modules.Contains(resolvedModulePath)) + if (modules.Add(resolvedModulePath)) { - modules.Add(resolvedModulePath); yield return module; } } @@ -970,7 +1065,7 @@ private IEnumerable GetModuleForRootedPaths(string[] modulePaths, var availableModuleFiles = all ? ModuleUtils.GetAllAvailableModuleFiles(resolvedModulePath) - : ModuleUtils.GetModuleVersionsFromAbsolutePath(resolvedModulePath); + : ModuleUtils.GetModuleFilesFromAbsolutePath(resolvedModulePath); bool foundModule = false; foreach (string file in availableModuleFiles) @@ -978,14 +1073,13 @@ private IEnumerable GetModuleForRootedPaths(string[] modulePaths, PSModuleInfo module = CreateModuleInfoForGetModule(file, refresh); if (module != null) { - if (String.Equals(moduleName, module.Name, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(moduleName, module.Name, StringComparison.OrdinalIgnoreCase)) { 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; } } @@ -1010,7 +1104,7 @@ private IEnumerable GetModuleForRootedPaths(string[] modulePaths, } } - private ErrorRecord CreateModuleNotFoundError(string modulePath) + private static ErrorRecord CreateModuleNotFoundError(string modulePath) { string errorMessage = StringUtil.Format(Modules.ModuleNotFoundForGetModule, modulePath); FileNotFoundException fnf = new FileNotFoundException(errorMessage); @@ -1018,108 +1112,71 @@ private ErrorRecord CreateModuleNotFoundError(string modulePath) return er; } - private Dictionary> GetAvailableLocallyModulesCore(string[] names, bool all, bool refresh) + private IEnumerable GetModuleForNames(List names, bool all, bool refresh) { - var modules = new Dictionary>(StringComparer.OrdinalIgnoreCase); - var modulePaths = ModuleIntrinsics.GetModulePath(false, Context); + 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); + + // Ignore repeated module path. + if (!modulePathSet.Add(uniquePath)) { - try - { - var availableModules = all - ? GetAllAvailableModules(path, refresh) - : GetDefaultAvailableModules(names, path, refresh); + continue; + } - // Add the path in $env:PSModulePath as the keys of the dictionary - // If the paths are repeated, ignore the repetitions - string uniquePath = path.TrimEnd(Utils.Separators.Directory); - if (!modules.ContainsKey(uniquePath)) - { - modules.Add(uniquePath, availableModules.OrderBy(m => m.Name).ToList()); - } - } - catch (IOException) - { - continue; // ignore directories that can't be accessed - } - catch (UnauthorizedAccessException) - { - continue; // ignore directories that can't be accessed - } + 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; } } - return modules; + // Make sure we always return a non-null collection. + return allModules ?? Array.Empty(); } - /// - /// Get a list of all modules - /// which can be imported just by specifying a non rooted file name of the module - /// (Import-Module foo\bar.psm1; but not Import-Module .\foo\bar.psm1) + /// Get modules based on the given names and module files. /// - private IEnumerable GetAllAvailableModules(string directory, bool refresh) + private IEnumerable GetModulesFromOneModulePath(List names, string modulePath, bool all, bool refresh) { - var availableModuleFiles = ModuleUtils.GetAllAvailableModuleFiles(directory); - - foreach (string file in availableModuleFiles) + const WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; + IEnumerable namePatterns = null; + if (names != null && names.Count > 0) { - PSModuleInfo module = CreateModuleInfoForGetModule(file, refresh); - if (module != null) - { - yield return module; - } + namePatterns = SessionStateUtilities.CreateWildcardsFromStrings(names, options); } - } - - /// - /// Get a list of the available modules - /// which can be imported just by specifying a non rooted directory name of the module - /// (Import-Module foo\bar; but not Import-Module .\foo\bar or Import-Module .\foo\bar.psm1) - /// - private List GetDefaultAvailableModules(string[] name, string directory, bool refresh) - { - List availableModules = new List(); - - const WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(name, wildcardOptions); - var availableModuleFiles = ModuleUtils.GetDefaultAvailableModuleFiles(directory); + IEnumerable moduleFiles = all + ? ModuleUtils.GetAllAvailableModuleFiles(modulePath) + : ModuleUtils.GetDefaultAvailableModuleFiles(modulePath); - foreach (string file in availableModuleFiles) + foreach (string file in moduleFiles) { - string actualModuleName = System.IO.Path.GetFileNameWithoutExtension(file); - if (SessionStateUtilities.MatchesAnyWildcardPattern(actualModuleName, patternList, true)) + if (namePatterns == null || + SessionStateUtilities.MatchesAnyWildcardPattern( + Path.GetFileNameWithoutExtension(file), namePatterns, defaultValue: true)) { PSModuleInfo module = CreateModuleInfoForGetModule(file, refresh); + if (module == null) { continue; } - if (module != null) + if (all || !ModuleUtils.IsModuleInVersionSubdirectory(file, out Version directoryVersion) || directoryVersion == module.Version) { - Version directoryVersion; - if (!ModuleUtils.IsModuleInVersionSubdirectory(file, out directoryVersion) - || directoryVersion == module.Version) - { - availableModules.Add(module); - } + yield return module; } } } ClearAnalysisCaches(); - - return availableModules; } /// @@ -1136,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; @@ -1161,10 +1218,10 @@ internal static Version GetMaximumVersion(string stringVersion) } /// - /// Helper function for building a module info for Get-Module -List + /// Helper function for building a module info for Get-Module -List. /// - /// The module file - /// True if we should update any cached module info for this module + /// The module file. + /// True if we should update any cached module info for this module. /// private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) { @@ -1212,10 +1269,10 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) moduleInfo = LoadModuleManifest( scriptInfo, flags /* - don't write errors, don't load elements */, - null, - null, - null, - null); + minimumVersion: null, + maximumVersion: null, + requiredVersion: null, + requiredModuleGuid: null); } else { @@ -1223,13 +1280,13 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) ImportModuleOptions options = new ImportModuleOptions(); bool found = false; - moduleInfo = LoadModule(file, null, String.Empty, null, ref options, flags, out found); + moduleInfo = LoadModule(file, moduleBase: null, prefix: string.Empty, ss: null, ref options, flags, out found); } // return fake PSModuleInfo if can't read the file for any reason if (moduleInfo == null) { - moduleInfo = new PSModuleInfo(file, null, null); + moduleInfo = new PSModuleInfo(file, context: null, sessionState: null); moduleInfo.HadErrorsLoading = true; // Prevent analysis cache from caching a bad module. } @@ -1237,7 +1294,8 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) { if (moduleInfo.RootModuleForManifest != null) { - if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { moduleInfo.SetModuleType(ModuleType.Binary); } @@ -1245,10 +1303,6 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) { moduleInfo.SetModuleType(ModuleType.Script); } - else if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - moduleInfo.SetModuleType(ModuleType.Workflow); - } else if (moduleInfo.RootModuleForManifest.EndsWith(StringLiterals.PowerShellCmdletizationFileExtension, StringComparison.OrdinalIgnoreCase)) { moduleInfo.SetModuleType(ModuleType.Cim); @@ -1257,6 +1311,7 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) { moduleInfo.SetModuleType(ModuleType.Manifest); } + moduleInfo.RootModule = moduleInfo.RootModuleForManifest; } else @@ -1265,16 +1320,13 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) moduleInfo.RootModule = moduleInfo.Path; } } - else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || extension.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + extension.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { moduleInfo.SetModuleType(ModuleType.Binary); moduleInfo.RootModule = moduleInfo.Path; } - else if (extension.Equals(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - moduleInfo.SetModuleType(ModuleType.Workflow); - moduleInfo.RootModule = moduleInfo.Path; - } else if (extension.Equals(StringLiterals.PowerShellCmdletizationFileExtension)) { moduleInfo.SetModuleType(ModuleType.Cim); @@ -1326,14 +1378,13 @@ private PSModuleInfo CreateModuleInfoForGetModule(string file, bool refresh) /// /// Routine to process the module manifest data language script. /// - /// The script info for the manifest script - /// processing flags (whether to write errors / load elements) - /// The minimum version to check the manifest against - /// The maximum version to check the manifest against - /// The version to check the manifest against - /// The module guid to check the manifest against + /// The script info for the manifest script. + /// Processing flags (whether to write errors / load elements). + /// The minimum version to check the manifest against. + /// The maximum version to check the manifest against. + /// The version to check the manifest against. + /// The module guid to check the manifest against. /// - /// internal PSModuleInfo LoadModuleManifest( ExternalScriptInfo scriptInfo, ManifestProcessingFlags manifestProcessingFlags, @@ -1349,15 +1400,14 @@ internal PSModuleInfo LoadModuleManifest( /// /// Routine to process the module manifest data language script. /// - /// The script info for the manifest script - /// processing flags (whether to write errors / load elements) - /// The minimum version to check the manifest against - /// The maximum version to check the manifest against - /// The version to check the manifest against - /// The module guid to check the manifest against - /// The set of options that are used while importing a module + /// The script info for the manifest script. + /// Processing flags (whether to write errors / load elements). + /// The minimum version to check the manifest against. + /// The maximum version to check the manifest against. + /// The version to check the manifest against. + /// The module guid to check the manifest against. + /// The set of options that are used while importing a module. /// - /// internal PSModuleInfo LoadModuleManifest( ExternalScriptInfo scriptInfo, ManifestProcessingFlags manifestProcessingFlags, @@ -1389,6 +1439,7 @@ internal bool LoadModuleManifestData(ExternalScriptInfo scriptInfo, ManifestProc { return false; } + ExternalScriptInfo localizedScriptInfo = FindLocalizedModuleManifest(scriptInfo.Path); localizedData = null; if (localizedScriptInfo != null) @@ -1399,10 +1450,31 @@ internal bool LoadModuleManifestData(ExternalScriptInfo scriptInfo, ManifestProc return false; } } + return true; } - private ErrorRecord GetErrorRecordIfUnsupportedRootCdxmlAndNestedModuleScenario( + /// + /// Helper function to generate fake PSModuleInfo objects from ModuleSpecification objects. + /// + /// Collection of ModuleSpecification objects. + /// Collection of fake PSModuleInfo objects. + private IEnumerable CreateFakeModuleObject(IEnumerable moduleSpecs) + { + foreach (ModuleSpecification moduleSpec in moduleSpecs) + { + var fakeModuleInfo = new PSModuleInfo(moduleSpec.Name, Context, null); + if (moduleSpec.Guid.HasValue) + { + fakeModuleInfo.SetGuid(moduleSpec.Guid.Value); + } + + fakeModuleInfo.SetVersion(moduleSpec.RequiredVersion ?? moduleSpec.Version); + yield return fakeModuleInfo; + } + } + + private static ErrorRecord GetErrorRecordIfUnsupportedRootCdxmlAndNestedModuleScenario( Hashtable data, string moduleManifestPath, string rootModulePath) @@ -1411,10 +1483,12 @@ private ErrorRecord GetErrorRecordIfUnsupportedRootCdxmlAndNestedModuleScenario( { return null; } + if (!rootModulePath.EndsWith(StringLiterals.PowerShellCmdletizationFileExtension, StringComparison.OrdinalIgnoreCase)) { return null; } + if (!data.ContainsKey("NestedModules")) { return null; @@ -1439,22 +1513,21 @@ private ErrorRecord GetErrorRecordIfUnsupportedRootCdxmlAndNestedModuleScenario( /// /// Routine to process the module manifest data language script. /// - /// The path to the manifest file - /// The script info for the manifest script - /// Contents of the module manifest - /// Contents of the localized module manifest - /// processing flags (whether to write errors / load elements) - /// The minimum version to check the manifest against - /// The maximum version to check the manifest against - /// The version to check the manifest against - /// The module guid to check the manifest against - /// The set of options that are used while importing a module - /// Tracks if there were errors in the file + /// The path to the manifest file. + /// The script info for the manifest script. + /// Contents of the module manifest. + /// Contents of the localized module manifest. + /// Processing flags (whether to write errors / load elements). + /// The minimum version to check the manifest against. + /// The maximum version to check the manifest against. + /// The version to check the manifest against. + /// The module guid to check the manifest against. + /// The set of options that are used while importing a module. + /// Tracks if there were errors in the file. /// - /// internal PSModuleInfo LoadModuleManifest( string moduleManifestPath, - ExternalScriptInfo scriptInfo, + ExternalScriptInfo manifestScriptInfo, Hashtable data, Hashtable localizedData, ManifestProcessingFlags manifestProcessingFlags, @@ -1467,12 +1540,13 @@ internal PSModuleInfo LoadModuleManifest( { string message; - var bailOnFirstError = 0 != (manifestProcessingFlags & ManifestProcessingFlags.NullOnFirstError); - var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); - var writingErrors = 0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors); + var bailOnFirstError = manifestProcessingFlags.HasFlag(ManifestProcessingFlags.NullOnFirstError); + var importingModule = manifestProcessingFlags.HasFlag(ManifestProcessingFlags.LoadElements); + var writingErrors = manifestProcessingFlags.HasFlag(ManifestProcessingFlags.WriteErrors); Dbg.Assert(moduleManifestPath != null, "moduleManifestPath for module (.psd1) can't be null"); string moduleBase = Path.GetDirectoryName(moduleManifestPath); + string moduleName = ModuleIntrinsics.GetModuleName(moduleManifestPath); if ((manifestProcessingFlags & (ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | @@ -1483,26 +1557,16 @@ internal PSModuleInfo LoadModuleManifest( // START: Check if the ModuleToProcess is already loaded..if it is, ignore the this load manifest // call and return - - // Workflows specified in NestedModules from the manifest - List workflowsToProcess = new List(); - - // Workflows specified in RequiredAssemblies - List dependentWorkflows = new List(); - string moduleToProcess = null; - if ( - !GetScalarFromData(data, moduleManifestPath, "ModuleToProcess", manifestProcessingFlags, - out moduleToProcess)) + if (!GetScalarFromData(data, moduleManifestPath, "ModuleToProcess", manifestProcessingFlags, out moduleToProcess)) { containedErrors = true; if (bailOnFirstError) return null; } + string rootModule = null; - if ( - !GetScalarFromData(data, moduleManifestPath, "RootModule", manifestProcessingFlags, - out rootModule)) + if (!GetScalarFromData(data, moduleManifestPath, "RootModule", manifestProcessingFlags, out rootModule)) { containedErrors = true; if (bailOnFirstError) return null; @@ -1517,7 +1581,7 @@ internal PSModuleInfo LoadModuleManifest( !Context.ModuleBeingProcessed.Equals(Context.PreviousModuleProcessed, StringComparison.OrdinalIgnoreCase))) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteWarnings)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteWarnings) != 0) { WriteWarning(Modules.ModuleToProcessFieldDeprecated); } @@ -1545,33 +1609,12 @@ internal PSModuleInfo LoadModuleManifest( WriteError(er); } } + if (bailOnFirstError) return null; } string actualRootModule = moduleToProcess ?? rootModule; - bool actualRootModuleIsXaml = false; - // For workflow modules, the actualRootModule is added to workflowsToProcess and is nulled out. - // We need to save this name so that we can assign this to the RootModule property of ModuleInfo - string savedActualRootModule = actualRootModule; - - if (string.Equals(System.IO.Path.GetExtension(actualRootModule), - StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - if (WildcardPattern.ContainsWildcardCharacters(actualRootModule)) - { - PSInvalidOperationException invalidOperation = PSTraceSource.NewInvalidOperationException( - Modules.WildCardNotAllowedInModuleToProcessAndInNestedModules, - moduleManifestPath); - invalidOperation.SetErrorId("Modules_WildCardNotAllowedInModuleToProcessAndInNestedModules"); - throw invalidOperation; - } - - workflowsToProcess.Add(actualRootModule); - actualRootModule = null; - actualRootModuleIsXaml = true; - } - // extract defaultCommandPrefix from the manifest string defaultCommandPrefix = null; if ( @@ -1604,43 +1647,36 @@ internal PSModuleInfo LoadModuleManifest( invalidOperation.SetErrorId("Modules_WildCardNotAllowedInModuleToProcessAndInNestedModules"); throw invalidOperation; } + // See if this module is already loaded. Since the manifest entry may not // have an extension and the module table is indexed by full names, we // may have search through all the extensions. PSModuleInfo loadedModule = null; - string rootedPath = this.FixupFileName(moduleBase, actualRootModule, null); - string mtpExtension = Path.GetExtension(rootedPath); - if (!string.IsNullOrEmpty(mtpExtension) && ModuleIntrinsics.IsPowerShellModuleExtension(mtpExtension)) + string rootedPath = null; + + // For a root module, we use its own module name instead of the manifest module name when calling 'FixFileName'. + // This is because when actually loading the root module later, it won't have access to the parent manifest module, + // and we will use its own name to query for already loaded assemblies from 'Context.AssemblyCache'. + string rootModuleName = ModuleIntrinsics.GetModuleName(actualRootModule); + string extension = Path.GetExtension(actualRootModule); + if (!string.IsNullOrEmpty(extension) && ModuleIntrinsics.IsPowerShellModuleExtension(extension)) { - Context.Modules.ModuleTable.TryGetValue(rootedPath, out loadedModule); + rootedPath = FixFileName(rootModuleName, moduleBase, actualRootModule, extension: null, canLoadAssembly: importingModule); + TryGetFromModuleTable(rootedPath, out loadedModule); } else { foreach (string extensionToTry in ModuleIntrinsics.PSModuleExtensions) { - rootedPath = this.FixupFileName(moduleBase, actualRootModule, extensionToTry); - Context.Modules.ModuleTable.TryGetValue(rootedPath, out loadedModule); - if (loadedModule != null) + rootedPath = FixFileName(rootModuleName, moduleBase, actualRootModule, extensionToTry, canLoadAssembly: importingModule); + if (TryGetFromModuleTable(rootedPath, out loadedModule)) + { break; + } } } - // TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior - // if (IsModuleAlreadyLoaded(loadedModule) && - // ((manifestProcessingFlags & ManifestProcessingFlags.LoadElements) == ManifestProcessingFlags.LoadElements)) - if (loadedModule != null && - (BaseRequiredVersion == null || loadedModule.Version.Equals(BaseRequiredVersion)) && - ((BaseMinimumVersion == null && BaseMaximumVersion == null) - || - (BaseMaximumVersion != null && BaseMinimumVersion == null && - loadedModule.Version <= BaseMaximumVersion) - || - (BaseMaximumVersion == null && BaseMinimumVersion != null && - loadedModule.Version >= BaseMinimumVersion) - || - (BaseMaximumVersion != null && BaseMinimumVersion != null && - loadedModule.Version >= BaseMinimumVersion && loadedModule.Version <= BaseMaximumVersion)) && - (BaseGuid == null || loadedModule.Guid.Equals(BaseGuid)) && importingModule) + if (importingModule && DoesAlreadyLoadedModuleSatisfyConstraints(loadedModule)) { if (!BaseForce) { @@ -1651,7 +1687,7 @@ internal PSModuleInfo LoadModuleManifest( return loadedModule; } // remove the module if force is specified (and if module is already loaded) - else if (Utils.NativeFileExists(rootedPath)) + else if (File.Exists(rootedPath)) { RemoveModule(loadedModule); } @@ -1659,7 +1695,6 @@ internal PSModuleInfo LoadModuleManifest( } // END: Check if the ModuleToProcess is already loaded.. - string author = string.Empty; if (!GetScalarFromData(data, moduleManifestPath, "Author", manifestProcessingFlags, out author)) { @@ -1710,25 +1745,23 @@ internal PSModuleInfo LoadModuleManifest( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + if (bailOnFirstError) return null; } - else if (requiredVersion != null && !moduleVersion.Equals(requiredVersion)) - { - if (bailOnFirstError) return null; - } - else + else if (!ModuleIntrinsics.AreModuleFieldsMatchingConstraints( + moduleGuid: manifestGuid, + moduleVersion: moduleVersion, + requiredGuid: requiredModuleGuid, + requiredVersion: requiredVersion, + minimumRequiredVersion: minimumVersion, + maximumRequiredVersion: maximumVersion)) { - if (moduleVersion < minimumVersion || (maximumVersion != null && moduleVersion > maximumVersion)) + if (bailOnFirstError) { - if (bailOnFirstError) return null; + return null; } } - if (requiredModuleGuid != null && !requiredModuleGuid.Equals(manifestGuid)) - { - if (bailOnFirstError) return null; - } - // Verify that the module version from the module manifest is equal to module version folder. DirectoryInfo parent = null; try @@ -1794,6 +1827,7 @@ internal PSModuleInfo LoadModuleManifest( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + if (bailOnFirstError) return null; } } @@ -1807,7 +1841,7 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; // Ignore errors related to HostVersion as per the ManifestProcessingFlags // doing this at this place because we have to set "containedErrors" - if ((0 == (manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion)) && + if (((manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion) == 0) && bailOnFirstError) return null; } @@ -1819,7 +1853,7 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; // Ignore errors related to HostVersion as per the ManifestProcessingFlags // doing this at this place because we have to set "containedErrors" - if (0 == (manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion)) + if ((manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion) == 0) { if (writingErrors) { @@ -1830,6 +1864,7 @@ internal PSModuleInfo LoadModuleManifest( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + if (bailOnFirstError) return null; } } @@ -1844,7 +1879,7 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; // Ignore errors related to HostVersion as per the ManifestProcessingFlags // doing this at this place because we have to set "containedErrors" - if ((0 == (manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion)) && + if (((manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion) == 0) && bailOnFirstError) return null; } @@ -1856,7 +1891,7 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; // Ignore errors related to HostVersion as per the ManifestProcessingFlags // doing this at this place because we have to set "containedErrors" - if (0 == (manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion)) + if ((manifestProcessingFlags & ManifestProcessingFlags.IgnoreHostNameAndHostVersion) == 0) { if (writingErrors) { @@ -1868,6 +1903,7 @@ internal PSModuleInfo LoadModuleManifest( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + if (bailOnFirstError) return null; } } @@ -1885,28 +1921,24 @@ internal PSModuleInfo LoadModuleManifest( else if ((requiredProcessorArchitecture != ProcessorArchitecture.None) && (requiredProcessorArchitecture != ProcessorArchitecture.MSIL)) { - bool isRunningOnArm = false; - ProcessorArchitecture currentArchitecture = PsUtils.GetProcessorArchitecture(out isRunningOnArm); + Architecture currentArchitecture = RuntimeInformation.ProcessArchitecture; - // For ARM Architectures, we need to do additional string-level comparison - if ((currentArchitecture != requiredProcessorArchitecture && !isRunningOnArm) || - (isRunningOnArm && - !requiredProcessorArchitecture.ToString() - .Equals(PsUtils.ArmArchitecture, StringComparison.OrdinalIgnoreCase))) + if ((requiredProcessorArchitecture == ProcessorArchitecture.X86 && currentArchitecture != Architecture.X86) || + (requiredProcessorArchitecture == ProcessorArchitecture.Amd64 && currentArchitecture != Architecture.X64) || + (requiredProcessorArchitecture == ProcessorArchitecture.Arm && (currentArchitecture != Architecture.Arm && currentArchitecture != Architecture.Arm64)) || + requiredProcessorArchitecture == ProcessorArchitecture.IA64) { containedErrors = true; if (writingErrors) { - string actualCurrentArchitecture = isRunningOnArm - ? PsUtils.ArmArchitecture - : currentArchitecture.ToString(); message = StringUtil.Format(Modules.InvalidProcessorArchitecture, - actualCurrentArchitecture, moduleManifestPath, requiredProcessorArchitecture); + currentArchitecture, moduleManifestPath, requiredProcessorArchitecture); InvalidOperationException ioe = new InvalidOperationException(message); ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidProcessorArchitecture", ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + if (bailOnFirstError) return null; } } @@ -1920,26 +1952,6 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; if (bailOnFirstError) return null; } -#if !CORECLR // CLR version is not applicable to CoreCLR - else if (requestedClrVersion != null) - { - Version currentClrVersion = Environment.Version; - if (currentClrVersion < requestedClrVersion) - { - containedErrors = true; - if (writingErrors) - { - message = StringUtil.Format(Modules.ModuleManifestInsufficientCLRVersion, currentClrVersion, - moduleManifestPath, requestedClrVersion); - InvalidOperationException ioe = new InvalidOperationException(message); - ErrorRecord er = new ErrorRecord(ioe, "Modules_InsufficientCLRVersion", - ErrorCategory.ResourceUnavailable, moduleManifestPath); - WriteError(er); - } - if (bailOnFirstError) return null; - } - } -#endif // Test the required .NET Framework version Version requestedDotNetFrameworkVersion; @@ -1950,34 +1962,6 @@ internal PSModuleInfo LoadModuleManifest( containedErrors = true; if (bailOnFirstError) return null; } -#if !CORECLR // .NET Framework Version is not applicable to CoreCLR - else if (requestedDotNetFrameworkVersion != null) - { - bool higherThanKnownHighestVersion = false; - if ( - !Utils.IsNetFrameworkVersionSupported(requestedDotNetFrameworkVersion, - out higherThanKnownHighestVersion)) - { - containedErrors = true; - if (writingErrors) - { - message = StringUtil.Format(Modules.InvalidDotNetFrameworkVersion, - moduleManifestPath, requestedDotNetFrameworkVersion); - InvalidOperationException ioe = new InvalidOperationException(message); - ErrorRecord er = new ErrorRecord(ioe, "Modules_InsufficientDotNetFrameworkVersion", - ErrorCategory.ResourceUnavailable, moduleManifestPath); - WriteError(er); - } - if (bailOnFirstError) return null; - } - else if (higherThanKnownHighestVersion) - { - string cannotDetectNetFrameworkVersionMessage = - StringUtil.Format(Modules.CannotDetectNetFrameworkVersion, requestedDotNetFrameworkVersion); - WriteVerbose(cannotDetectNetFrameworkVersionMessage); - } - } -#endif // HelpInfo URI string helpInfoUri = null; @@ -2011,6 +1995,7 @@ internal PSModuleInfo LoadModuleManifest( { fakeManifestInfo.SetGuid(manifestGuid.Value); } + if (moduleVersion != null) { fakeManifestInfo.SetVersion(moduleVersion); @@ -2018,8 +2003,12 @@ internal PSModuleInfo LoadModuleManifest( foreach (ModuleSpecification requiredModule in requiredModules) { + // The required module name is essentially raw user input. + // We must process it so paths work. + ModuleSpecification normalizedRequiredModuleSpec = requiredModule?.WithNormalizedName(Context, moduleBase); + ErrorRecord error = null; - PSModuleInfo module = LoadRequiredModule(fakeManifestInfo, requiredModule, moduleManifestPath, + PSModuleInfo module = LoadRequiredModule(fakeManifestInfo, normalizedRequiredModuleSpec, moduleManifestPath, manifestProcessingFlags, containedErrors, out error); if (module == null && error != null) { @@ -2036,16 +2025,8 @@ internal PSModuleInfo LoadModuleManifest( } else { - PSModuleInfo fakeRequiredModuleInfo = null; - foreach (ModuleSpecification requiredModule in requiredModules) + foreach (PSModuleInfo fakeRequiredModuleInfo in CreateFakeModuleObject(requiredModules)) { - fakeRequiredModuleInfo = new PSModuleInfo(requiredModule.Name, Context, null); - if (requiredModule.Guid.HasValue) - { - fakeRequiredModuleInfo.SetGuid(requiredModule.Guid.Value); - } - fakeRequiredModuleInfo.SetVersion(requiredModule.RequiredVersion ?? requiredModule.Version); - requiredModulesSpecifiedInModuleManifest.Add(fakeRequiredModuleInfo); } } @@ -2077,35 +2058,87 @@ internal PSModuleInfo LoadModuleManifest( throw invalidOperation; } - if (string.Equals(System.IO.Path.GetExtension(s.Name), - StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - workflowsToProcess.Add(s.Name); - } - else - { - nestedModules.Add(s); - } + nestedModules.Add(s); } + Array.Clear(tmpNestedModules, 0, tmpNestedModules.Length); } + // Set the private data member for the module if the manifest contains this member + object privateData = data["PrivateData"]; - // Set the private data member for the module if the manifest contains - // this member - object privateData = null; - if (data.Contains("PrivateData")) + // Validate the 'ExperimentalFeatures' member of the manifest + List expFeatureList = null; + if (privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData) { - privateData = data["PrivateData"]; - } + if (!GetScalarFromData(psData, moduleManifestPath, "ExperimentalFeatures", manifestProcessingFlags, out Hashtable[] features)) + { + containedErrors = true; + if (bailOnFirstError) return null; + } - // Process all of the exports... - List exportedFunctions; - if ( - !GetListOfWildcardsFromData(data, moduleManifestPath, "FunctionsToExport", manifestProcessingFlags, - out exportedFunctions)) - { - containedErrors = true; + if (features != null && features.Length > 0) + { + bool nameMissingOrEmpty = false; + var invalidNames = new List(); + expFeatureList = new List(features.Length); + + foreach (Hashtable feature in features) + { + string featureName = feature["Name"] as string; + if (string.IsNullOrEmpty(featureName)) + { + nameMissingOrEmpty = true; + } + else if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName)) + { + string featureDescription = feature["Description"] as string; + expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, moduleManifestPath, + ExperimentalFeature.IsEnabled(featureName))); + } + else + { + invalidNames.Add(featureName); + } + } + + if (nameMissingOrEmpty) + { + if (writingErrors) + { + WriteError(new ErrorRecord(new ArgumentException(Modules.ExperimentalFeatureNameMissingOrEmpty), + "Modules_ExperimentalFeatureNameMissingOrEmpty", + ErrorCategory.InvalidData, null)); + } + + containedErrors = true; + if (bailOnFirstError) { return null; } + } + + if (invalidNames.Count > 0) + { + if (writingErrors) + { + string invalidNameStr = string.Join(", ", invalidNames); + string errorMsg = StringUtil.Format(Modules.InvalidExperimentalFeatureName, invalidNameStr); + WriteError(new ErrorRecord(new ArgumentException(errorMsg), + "Modules_InvalidExperimentalFeatureName", + ErrorCategory.InvalidData, null)); + } + + containedErrors = true; + if (bailOnFirstError) { return null; } + } + } + } + + // Process all of the exports... + List exportedFunctions; + if ( + !GetListOfWildcardsFromData(data, moduleManifestPath, "FunctionsToExport", manifestProcessingFlags, + out exportedFunctions)) + { + containedErrors = true; if (bailOnFirstError) return null; } @@ -2160,73 +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)) { - if (string.Equals(System.IO.Path.GetExtension(assembly), - StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - dependentWorkflows.Add(assembly); - } - else - { - 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); - string loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); - WriteVerbose(loadMessage); - iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); - fixedUpAssemblyPathList.Add(fileName); - - fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellILAssemblyExtension); + bool isPathResolved = false; + foreach (string extToTry in ModuleIntrinsics.ProcessableAssemblyExtensions) + { + fileName = FixFileNameWithoutLoadingAssembly(moduleBase, assembly, extToTry, out isPathResolved); + if (isPathResolved) + { + break; + } + } - loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); - WriteVerbose(loadMessage); - iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); - fixedUpAssemblyPathList.Add(fileName); - doBind = true; + 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); + } } + + WriteVerbose(StringUtil.Format(Modules.LoadingFile, "Assembly", fileName)); + + // Set a fake PSModuleInfo object to indicate the module it comes from. + var assemblyEntry = new SessionStateAssemblyEntry(assembly, fileName); + assemblyEntry.SetModule(new PSModuleInfo(moduleName, path: null, context: null, sessionState: null)); + + 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; @@ -2249,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; @@ -2268,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; @@ -2292,6 +2342,7 @@ internal PSModuleInfo LoadModuleManifest( { continue; } + if (entry.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase)) { isAlreadyLoaded = true; @@ -2309,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; @@ -2339,7 +2395,7 @@ internal PSModuleInfo LoadModuleManifest( } // Now add the metadata to the module info object for the manifest... - string description = String.Empty; + string description = string.Empty; if (data.Contains("Description")) { if (localizedData != null && localizedData.Contains("Description")) @@ -2347,6 +2403,7 @@ internal PSModuleInfo LoadModuleManifest( description = (string)LanguagePrimitives.ConvertTo(localizedData["Description"], typeof(string), CultureInfo.InvariantCulture); } + if (string.IsNullOrEmpty(description)) { description = (string)LanguagePrimitives.ConvertTo(data["Description"], @@ -2355,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; @@ -2387,6 +2447,64 @@ internal PSModuleInfo LoadModuleManifest( if (bailOnFirstError) return null; } + // On Windows, we want to include any modules under %WINDIR%\System32\WindowsPowerShell\v1.0\Modules + // that have declared compatibility with PS Core (or if the check is skipped) + IEnumerable inferredCompatiblePSEditions = compatiblePSEditions ?? DefaultCompatiblePSEditions; + bool isConsideredCompatible = ModuleUtils.IsPSEditionCompatible(moduleManifestPath, inferredCompatiblePSEditions); + if (!BaseSkipEditionCheck && !isConsideredCompatible) + { + if (PowerShellConfig.Instance.IsImplicitWinCompatEnabled()) + { + if (importingModule) + { + IList moduleProxies = ImportModulesUsingWinCompat( + moduleNames: new string[] { moduleManifestPath }, + moduleFullyQualifiedNames: null, + importModuleOptions: options); + + // We are loading by a single ManifestPath so expect max of 1 + return moduleProxies.Count > 0 ? moduleProxies[0] : null; + } + } + else + { + containedErrors = true; + if (writingErrors) + { + message = StringUtil.Format( + Modules.ImplicitWinCompatDisabled, + moduleManifestPath, + string.Join(',', inferredCompatiblePSEditions)); + + ErrorRecord er = new ErrorRecord( + new InvalidOperationException(message), + nameof(Modules) + "_" + nameof(Modules.ImplicitWinCompatDisabled), + ErrorCategory.ResourceUnavailable, + moduleManifestPath); + + WriteError(er); + } + + if (bailOnFirstError) + { + // If we're trying to load the module, return null so that caches + // are not polluted + if (importingModule) + { + return null; + } + + // If we return null with Get-Module, a fake module info will be created. Since + // we want to suppress output of the module, we need to do that here. + return new PSModuleInfo(moduleManifestPath, context: null, sessionState: null) + { + HadErrorsLoading = true, + IsConsideredEditionCompatible = false, + }; + } + } + } + // Process format.ps1xml / types.ps1.xml / RequiredAssemblies // as late as possible, but before ModuleToProcess, ScriptToProcess, NestedModules if (importingModule) @@ -2396,7 +2514,7 @@ internal PSModuleInfo LoadModuleManifest( { try { - iss.Bind(Context, /*updateOnly*/ true); + iss.Bind(Context, updateOnly: true, module: null, noClobber: false, local: false, setLocation: false); } catch (Exception e) { @@ -2440,34 +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; @@ -2485,6 +2596,16 @@ internal PSModuleInfo LoadModuleManifest( manifestInfo.PowerShellVersion = powerShellVersion; manifestInfo.ProcessorArchitecture = requiredProcessorArchitecture; manifestInfo.Prefix = resolvedCommandPrefix; + + // A module is considered compatible if it's not on the System32 module path, or + // if it is and declared "Core" as a compatible PSEdition. + manifestInfo.IsConsideredEditionCompatible = isConsideredCompatible; + + if (expFeatureList != null) + { + manifestInfo.ExperimentalFeatures = new ReadOnlyCollection(expFeatureList); + } + if (assemblyList != null) { foreach (var a in assemblyList) @@ -2492,6 +2613,7 @@ internal PSModuleInfo LoadModuleManifest( manifestInfo.AddRequiredAssembly(a); } } + if (fileList != null) { foreach (var f in fileList) @@ -2500,6 +2622,7 @@ internal PSModuleInfo LoadModuleManifest( manifestInfo.AddToFileList(absoluteFilePath); } } + if (moduleList != null) { foreach (var m in moduleList) @@ -2508,13 +2631,12 @@ internal PSModuleInfo LoadModuleManifest( manifestInfo.AddToModuleList(m); } } + if (compatiblePSEditions != null) { - foreach (var psEdition in compatiblePSEditions) - { - manifestInfo.AddToCompatiblePSEditions(psEdition); - } + manifestInfo.AddToCompatiblePSEditions(compatiblePSEditions); } + if (scriptsToProcess != null) { foreach (var s in scriptsToProcess) @@ -2522,20 +2644,24 @@ internal PSModuleInfo LoadModuleManifest( manifestInfo.AddScript(s); } } - manifestInfo.RootModule = savedActualRootModule; - manifestInfo.RootModuleForManifest = savedActualRootModule; + + manifestInfo.RootModule = actualRootModule; + manifestInfo.RootModuleForManifest = actualRootModule; if (manifestGuid != null) { manifestInfo.SetGuid((Guid)manifestGuid); } + if (helpInfoUri != null) { manifestInfo.SetHelpInfoUri(helpInfoUri); } + foreach (PSModuleInfo module in requiredModulesLoaded) { manifestInfo.AddRequiredModule(module); } + if (requiredModules != null) { foreach (ModuleSpecification moduleSpecification in requiredModules) @@ -2559,7 +2685,7 @@ internal PSModuleInfo LoadModuleManifest( var repositorySourceLocation = xml.Properties["RepositorySourceLocation"].Value.ToString(); Uri repositorySourceLocationUri; - if (!String.IsNullOrWhiteSpace(repositorySourceLocation) && + if (!string.IsNullOrWhiteSpace(repositorySourceLocation) && Uri.TryCreate(repositorySourceLocation, UriKind.RelativeOrAbsolute, out repositorySourceLocationUri)) { @@ -2703,9 +2829,13 @@ internal PSModuleInfo LoadModuleManifest( } } - var needToAnalyzeScriptModules = usedWildcard; + // We have to further analyze the module if any wildcard characters are used. + bool needToAnalyzeScriptModules = usedWildcard; - if (!needToAnalyzeScriptModules) + // We can skip further analysis if 'FunctionsToExport', 'CmdletsToExport' and 'AliasesToExport' + // are all declared and no wildcard character is used for them. But if any of 'FunctionsToExport', + // 'CmdletsToExport' or 'AliasesToExport' were not given, we must check to see if more analysis is needed. + if (!needToAnalyzeScriptModules && (!sawExportedCmdlets || !sawExportedFunctions || !sawExportedAliases)) { foreach (var nestedModule in nestedModules) { @@ -2725,10 +2855,19 @@ internal PSModuleInfo LoadModuleManifest( } bool etwEnabled = CommandDiscoveryEventSource.Log.IsEnabled(); - if (etwEnabled) CommandDiscoveryEventSource.Log.ModuleManifestAnalysisResult(manifestInfo.Path, !needToAnalyzeScriptModules); + if (etwEnabled) + { + CommandDiscoveryEventSource.Log.ModuleManifestAnalysisResult(manifestInfo.Path, !needToAnalyzeScriptModules); + } if (!needToAnalyzeScriptModules) { + // Add nested modules to the manifestInfo when no more analysis needs to be done + foreach (PSModuleInfo fakeNestedModuleInfo in CreateFakeModuleObject(nestedModules)) + { + manifestInfo.AddNestedModule(fakeNestedModuleInfo); + } + return manifestInfo; } } @@ -2824,6 +2963,7 @@ internal PSModuleInfo LoadModuleManifest( BaseDisableNameChecking = true; SessionStateInternal oldSessionState = Context.EngineSessionState; + var exportedFunctionsContainsWildcards = ModuleIntrinsics.PatternContainsWildcard(exportedFunctions); try { if (importingModule) @@ -2834,6 +2974,7 @@ internal PSModuleInfo LoadModuleManifest( if (ss != null) { + ss.Internal.ManifestWithExplicitFunctionExport = !exportedFunctionsContainsWildcards; Context.EngineSessionState = ss.Internal; } @@ -2842,54 +2983,33 @@ internal PSModuleInfo LoadModuleManifest( // For nested modules, we need to set importmoduleoptions to false as they should not use the options set for parent module ImportModuleOptions nestedModuleOptions = new ImportModuleOptions(); + // If the nested manifest explicitly (no wildcards) specifies functions to be exported then allow all functions to be exported + // into the session state function table (regardless of language boundaries), because the manifest will filter them later to the + // specified function list. + nestedModuleOptions.AllowNestedModuleFunctionsToExport = ((exportedFunctions != null) && !exportedFunctionsContainsWildcards); + foreach (ModuleSpecification nestedModuleSpecification in nestedModules) { bool found = false; // Never load nested modules to the global scope. - bool oldGLobal = this.BaseGlobal; + bool oldGlobal = this.BaseGlobal; this.BaseGlobal = false; - string shortModuleName = null; - if (nestedModuleSpecification.Name == _serviceCoreAssemblyFullName) - { - shortModuleName = _serviceCoreAssemblyShortName; - } - - PSModuleInfo nestedModule; - if ( - string.Equals(nestedModuleSpecification.Name, _serviceCoreAssemblyFullName, - StringComparison.OrdinalIgnoreCase) || - string.Equals(nestedModuleSpecification.Name, _serviceCoreAssemblyShortName, - StringComparison.OrdinalIgnoreCase)) - { - nestedModule = LoadServiceCoreModule( - manifestInfo, - moduleBase, - null, //SessionState - nestedModuleOptions, - manifestProcessingFlags, - false, // addToParentModuleIfFound - out found); - } - else - { - nestedModule = LoadModuleNamedInManifest( - manifestInfo, - nestedModuleSpecification, // moduleName - moduleBase, - true, // searchModulePath - string.Empty, // prefix: no -Prefix added for nested modules - null, - nestedModuleOptions, - manifestProcessingFlags, - true, - true, - privateData, - out found, - shortModuleName); - } - - this.BaseGlobal = oldGLobal; + PSModuleInfo nestedModule = LoadModuleNamedInManifest( + parentModule: manifestInfo, + moduleSpecification: nestedModuleSpecification, + moduleBase: moduleBase, + searchModulePath: true, + prefix: string.Empty, + ss: null, + options: nestedModuleOptions, + manifestProcessingFlags: manifestProcessingFlags, + privateData: privateData, + found: out found, + shortModuleName: null, + manifestLanguageMode: ((manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode.GetValueOrDefault() : (PSLanguageMode?)null)); + + this.BaseGlobal = oldGlobal; // If found, add it to the parent's list of NestedModules if (found) @@ -2898,27 +3018,20 @@ internal PSModuleInfo LoadModuleManifest( // exports if ((ss == null) && (nestedModule != null)) { - // If this was ServiceCore, don't process its exports - as these - // should not show up when a module defines WorkflowsToProcess - if ( - !String.Equals(nestedModule.Name, _serviceCoreAssemblyShortName, - StringComparison.OrdinalIgnoreCase)) + foreach (string detectedCmdlet in nestedModule.ExportedCmdlets.Keys) { - foreach (string detectedCmdlet in nestedModule.ExportedCmdlets.Keys) - { - manifestInfo.AddDetectedCmdletExport(detectedCmdlet); - } + manifestInfo.AddDetectedCmdletExport(detectedCmdlet); + } - foreach (var detectedFunction in nestedModule.ExportedFunctions.Keys) - { - manifestInfo.AddDetectedFunctionExport(detectedFunction); - } + foreach (var detectedFunction in nestedModule.ExportedFunctions.Keys) + { + manifestInfo.AddDetectedFunctionExport(detectedFunction); + } - foreach (string detectedAlias in nestedModule.ExportedAliases.Keys) - { - manifestInfo.AddDetectedAliasExport(detectedAlias, - nestedModule.ExportedAliases[detectedAlias].Definition); - } + foreach (string detectedAlias in nestedModule.ExportedAliases.Keys) + { + manifestInfo.AddDetectedAliasExport(detectedAlias, + nestedModule.ExportedAliases[detectedAlias].Definition); } } // If the NestedModules was a .ps1 script no module object would have been generated @@ -2941,75 +3054,6 @@ internal PSModuleInfo LoadModuleManifest( throw invalidOperation; } } - - - if (actualRootModuleIsXaml) - { - manifestInfo.SetModuleType(ModuleType.Workflow); - } - - if (workflowsToProcess != null && workflowsToProcess.Count > 0) - { -#if CORECLR // Workflow Not Supported On CSS - PSNotSupportedException workflowModuleNotSupported = - PSTraceSource.NewNotSupportedException( - Modules.WorkflowModuleNotSupportedInPowerShellCore, - ModuleIntrinsics.GetModuleName(moduleManifestPath)); - throw workflowModuleNotSupported; -#else - // Depending on current execution policy, check the signature of Module manifest file if required. - // Reusing already created ScriptInfo object to avoid race condition when modulemanifest was not signed during first check and signed before this step. - // - scriptInfo.ValidateScriptInfo(Host); - - nestedModuleOptions.ServiceCoreAutoAdded = true; - - if (!importingModule) - { - ProcessWorkflowsToProcess(moduleBase, workflowsToProcess, new List(), - new List(), null, manifestInfo, nestedModuleOptions); - } - else - { - // Never load nested modules to the global scope. - bool oldGLobal = this.BaseGlobal; - this.BaseGlobal = false; - bool found = false; - - foreach (string workflowFileName in workflowsToProcess) - { - List wfToProcess = new List(); - wfToProcess.Add(workflowFileName); - SessionState wfSS = new SessionState(Context, true, true); - - PSModuleInfo module = new PSModuleInfo( - ModuleIntrinsics.GetModuleName(workflowFileName), workflowFileName, Context, wfSS); - wfSS.Internal.Module = module; - module.PrivateData = privateData; - module.SetModuleType(ModuleType.Workflow); - module.SetModuleBase(moduleBase); - - LoadServiceCoreModule(module, String.Empty, wfSS, nestedModuleOptions, - manifestProcessingFlags, true, out found); - - ProcessWorkflowsToProcess(moduleBase, wfToProcess, dependentWorkflows, - fixedUpAssemblyPathList, wfSS, module, nestedModuleOptions); - - // And import the members from this module into the callers context... - if (importingModule) - { - ImportModuleMembers(module, this.BasePrefix, options); - } - - // Add it to all the module tables - AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); - manifestInfo.AddNestedModule(module); - } - - this.BaseGlobal = oldGLobal; - } -#endif - } } catch (Exception) { @@ -3052,16 +3096,19 @@ internal PSModuleInfo LoadModuleManifest( try { bool found; - newManifestInfo = LoadModuleNamedInManifest(null, new ModuleSpecification(actualRootModule), - moduleBase, /* searchModulePath */ false, - resolvedCommandPrefix, ss, options, manifestProcessingFlags, - // If types files already loaded, don't load snapin files - (exportedTypeFiles == null || 0 == exportedTypeFiles.Count), - // if format files already loaded, don't load snapin files - (exportedFormatFiles == null || 0 == exportedFormatFiles.Count), - privateData, - out found, - null); + newManifestInfo = LoadModuleNamedInManifest( + parentModule: null, + moduleSpecification: new ModuleSpecification(actualRootModule), + moduleBase: moduleBase, + searchModulePath: false, + prefix: resolvedCommandPrefix, + ss: ss, + options: options, + manifestProcessingFlags: manifestProcessingFlags, + privateData: privateData, + found: out found, + shortModuleName: null, + manifestLanguageMode: ((manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode.GetValueOrDefault() : (PSLanguageMode?)null)); if (!found || (newManifestInfo == null)) { @@ -3103,32 +3150,18 @@ internal PSModuleInfo LoadModuleManifest( BaseCmdletPatterns = oldCmdletPatterns; } - // If there is an existing session state and the new module info - // session state is empty, then use the existing session state. - // This will be the case when ModuleToProcess is a binary module - // and there were nested modules. - if (newManifestInfo.SessionState == null && ss != null) - { - newManifestInfo.SessionState = ss; - ss.Internal.Module = newManifestInfo; - } - // If the new module info session state is not empty but there is no - // existing session state, then use the new module info's sessions state; - // This will be the case if the module to process is a script module - // but there where no nested modules. - else if (newManifestInfo.SessionState != null && ss == null) - { - ss = newManifestInfo.SessionState; - } - - // We don't need to care if they are both null which will be the - // case when there is only a binary module in ModuleToProcess and - // no nested modules. At this point the moduleInfo and the value in - // ss should be identical. - Dbg.Assert(newManifestInfo.SessionState == ss, - "ss and newManifestInfo.SessionState should be the same after handling ModuleToProcess"); + // For most cases, 'newManifestInfo.SessionState' should be identical to 'ss': + // 1. when 'importingModule == true', 'newManifestInfo' uses the same session state as 'ss' because we passed in 'ss' when loading the RootModule via 'LoadModuleNamedInManifest'. + // 2. when 'importingModule == false', both session states will be null since we are in module analysis mode (Get-Module -ListAvailable). + // + // However, there is one exception when the RootModule is also put in NestedModules in the module manifest (ill-organized module structure). + // For example, module folder 'test' contains two files: 'test.psd1' and 'test.psm1', and 'test.psd1' has the following content: + // "@{ ModuleVersion = '0.0.1'; RootModule = 'test'; NestedModules = @('test') }" + // + // In that case, the nested module will first be loaded with a different session state, and then when trying to load the RootModule via 'LoadModuleNamedInManifest', + // the same loaded nested module will be reused for the RootModule by 'LoadModuleNamedInManifest'. - // Change the module name to match the manifest name, not the original name + // Change the module name to match the manifest name, not the original name. newManifestInfo.SetName(manifestInfo.Name); // Copy in any nested modules... @@ -3152,18 +3185,22 @@ internal PSModuleInfo LoadModuleManifest( { newManifestInfo.Description = description; } + if (newManifestInfo.Version.Equals(new Version(0, 0))) { newManifestInfo.SetVersion(moduleVersion); } + if (newManifestInfo.Guid.Equals(Guid.Empty) && (manifestGuid != null)) { newManifestInfo.SetGuid((Guid)manifestGuid); } + if (newManifestInfo.HelpInfoUri == null && (helpInfoUri != null)) { newManifestInfo.SetHelpInfoUri(helpInfoUri); } + if (requiredModules != null) { foreach (ModuleSpecification moduleSpecification in requiredModules) @@ -3171,27 +3208,26 @@ internal PSModuleInfo LoadModuleManifest( newManifestInfo.AddRequiredModuleSpecification(moduleSpecification); } } - 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) { newManifestInfo.AddToTags(tag); } + newManifestInfo.ReleaseNotes = manifestInfo.ReleaseNotes; newManifestInfo.ProjectUri = manifestInfo.ProjectUri; newManifestInfo.LicenseUri = manifestInfo.LicenseUri; newManifestInfo.IconUri = manifestInfo.IconUri; newManifestInfo.RepositorySourceLocation = manifestInfo.RepositorySourceLocation; + newManifestInfo.IsConsideredEditionCompatible = manifestInfo.IsConsideredEditionCompatible; + + newManifestInfo.ExperimentalFeatures = manifestInfo.ExperimentalFeatures; // If we are in module discovery, then fix the path. if (ss == null) @@ -3244,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) { @@ -3255,7 +3291,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.ModuleList == null || newManifestInfo.ModuleList.LongCount() == 0) + if (newManifestInfo.ModuleList == null || !newManifestInfo.ModuleList.Any()) { if (moduleList != null) { @@ -3266,14 +3302,11 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.CompatiblePSEditions == null || newManifestInfo.CompatiblePSEditions.LongCount() == 0) + if (newManifestInfo.CompatiblePSEditions == null || !newManifestInfo.CompatiblePSEditions.Any()) { if (compatiblePSEditions != null) { - foreach (var psEdition in compatiblePSEditions) - { - newManifestInfo.AddToCompatiblePSEditions(psEdition); - } + newManifestInfo.AddToCompatiblePSEditions(compatiblePSEditions); } } @@ -3282,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) { @@ -3293,7 +3326,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.Scripts == null || newManifestInfo.Scripts.LongCount() == 0) + if (newManifestInfo.Scripts == null || !newManifestInfo.Scripts.Any()) { if (scriptsToProcess != null) { @@ -3304,19 +3337,16 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.RootModuleForManifest == null) - { - newManifestInfo.RootModuleForManifest = manifestInfo.RootModuleForManifest; - } + newManifestInfo.RootModuleForManifest ??= manifestInfo.RootModuleForManifest; if (newManifestInfo.DeclaredCmdletExports == null || newManifestInfo.DeclaredCmdletExports.Count == 0) { newManifestInfo.DeclaredCmdletExports = manifestInfo.DeclaredCmdletExports; } - if (manifestInfo._detectedCmdletExports != null) + if (manifestInfo.DetectedCmdletExports != null) { - foreach (string detectedExport in manifestInfo._detectedCmdletExports) + foreach (string detectedExport in manifestInfo.DetectedCmdletExports) { newManifestInfo.AddDetectedCmdletExport(detectedExport); } @@ -3328,9 +3358,9 @@ internal PSModuleInfo LoadModuleManifest( newManifestInfo.DeclaredFunctionExports = manifestInfo.DeclaredFunctionExports; } - if (manifestInfo._detectedFunctionExports != null) + if (manifestInfo.DetectedFunctionExports != null) { - foreach (string detectedExport in manifestInfo._detectedFunctionExports) + foreach (string detectedExport in manifestInfo.DetectedFunctionExports) { newManifestInfo.AddDetectedFunctionExport(detectedExport); } @@ -3341,35 +3371,20 @@ internal PSModuleInfo LoadModuleManifest( newManifestInfo.DeclaredAliasExports = manifestInfo.DeclaredAliasExports; } - if (manifestInfo._detectedAliasExports != null) + if (manifestInfo.DetectedAliasExports != null) { - foreach (var pair in manifestInfo._detectedAliasExports) + foreach (var pair in manifestInfo.DetectedAliasExports) { newManifestInfo.AddDetectedAliasExport(pair.Key, pair.Value); } } - if (newManifestInfo.DeclaredVariableExports == null || newManifestInfo.DeclaredVariableExports.Count == 0) { newManifestInfo.DeclaredVariableExports = manifestInfo.DeclaredVariableExports; } - if (manifestInfo._detectedWorkflowExports != null) - { - foreach (string detectedExport in manifestInfo._detectedWorkflowExports) - { - newManifestInfo.AddDetectedWorkflowExport(detectedExport); - } - } - - if (newManifestInfo.DeclaredWorkflowExports == null || - newManifestInfo.DeclaredWorkflowExports.Count == 0) - { - newManifestInfo.DeclaredWorkflowExports = manifestInfo.DeclaredWorkflowExports; - } - // If there are types/formats entries in the ModuleToProcess use them // only if there are no entries from the manifest. The manifest entries // completely override the module's entries. @@ -3377,6 +3392,7 @@ internal PSModuleInfo LoadModuleManifest( { newManifestInfo.SetExportedTypeFiles(manifestInfo.ExportedTypeFiles); } + if (manifestInfo.ExportedFormatFiles.Count > 0) { newManifestInfo.SetExportedFormatFiles(manifestInfo.ExportedFormatFiles); @@ -3390,16 +3406,19 @@ internal PSModuleInfo LoadModuleManifest( { if ((exportedCmdlets != null) && (ss != null)) { - Dbg.Assert(ss.Internal.ExportedCmdlets != null, "ss.Internal.ExportedCmdlets should not be null"); manifestInfo.ExportedCmdlets.Clear(); // Mark stuff for export if (ss != null) { - ModuleIntrinsics.ExportModuleMembers(this, + ModuleIntrinsics.ExportModuleMembers( + this, ss.Internal, - exportedFunctions, exportedCmdlets, - exportedAliases, exportedVariables, null); + exportedFunctions, + exportedCmdlets, + exportedAliases, + exportedVariables, + doNotExportCmdlets: null); } } } @@ -3409,8 +3428,33 @@ internal PSModuleInfo LoadModuleManifest( // implicitly export functions and cmdlets. if ((ss != null) && (!ss.Internal.UseExportList)) { - ModuleIntrinsics.ExportModuleMembers(this, ss.Internal, MatchAll, - MatchAll, null, null, options.ServiceCoreAutoAdded ? s_serviceCoreAssemblyCmdlets : null); + // For cross language boundaries, implicitly import all functions only if + // 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, + cmdletPatterns: MatchAll, + aliasPatterns: null, + variablePatterns: null, + doNotExportCmdlets: null); } // Export* fields in .psd1 subset Export-ModuleMember calls from ModuleToProcess=psm1 @@ -3418,6 +3462,22 @@ internal PSModuleInfo LoadModuleManifest( { if (ss != null) { + // If module (psm1) functions were not exported because of cross language boundary restrictions, + // then implicitly export them here so that they can be filtered by the exportedFunctions list. + // Unless exportedFunctions contains the wildcard character that isn't allowed across language + // boundaries. + if (!ss.Internal.FunctionsExported && !exportedFunctionsContainsWildcards) + { + ModuleIntrinsics.ExportModuleMembers( + cmdlet: this, + sessionState: ss.Internal, + functionPatterns: MatchAll, + cmdletPatterns: null, + aliasPatterns: null, + variablePatterns: null, + doNotExportCmdlets: null); + } + Dbg.Assert(ss.Internal.ExportedFunctions != null, "ss.Internal.ExportedFunctions should not be null"); @@ -3426,15 +3486,7 @@ internal PSModuleInfo LoadModuleManifest( } else { - Collection v = new Collection(); - if (manifestInfo.DeclaredFunctionExports != null) - { - foreach (var f in manifestInfo.DeclaredFunctionExports) - { - v.Add(f); - } - } - UpdateCommandCollection(v, exportedFunctions); + UpdateCommandCollection(manifestInfo.DeclaredFunctionExports, exportedFunctions); } } @@ -3475,18 +3527,21 @@ internal PSModuleInfo LoadModuleManifest( if (ss != null) { Dbg.Assert(ss.Internal.ExportedVariables != null, - "ss.Internal.ExportedVariables should not be null"); + "ss.Internal.ExportedVariables should not be null"); // Update the exports to only contain things that are also in the manifest export list List updated = new List(); foreach (PSVariable element in ss.Internal.ExportedVariables) { - if (SessionStateUtilities.MatchesAnyWildcardPattern(element.Name, exportedVariables, - false)) + if (SessionStateUtilities.MatchesAnyWildcardPattern( + element.Name, + exportedVariables, + defaultValue: false)) { updated.Add(element); } } + ss.Internal.ExportedVariables.Clear(); ss.Internal.ExportedVariables.AddRange(updated); } @@ -3499,11 +3554,14 @@ internal PSModuleInfo LoadModuleManifest( { // In the case where there are only nested modules, // the members of the manifest are canonical... - ModuleIntrinsics.ExportModuleMembers(this, - ss.Internal, - exportedFunctions, exportedCmdlets, - exportedAliases, exportedVariables, - options.ServiceCoreAutoAdded ? s_serviceCoreAssemblyCmdlets : null); + ModuleIntrinsics.ExportModuleMembers( + this, + sessionState: ss.Internal, + functionPatterns: exportedFunctions, + cmdletPatterns: exportedCmdlets, + aliasPatterns: exportedAliases, + variablePatterns: exportedVariables, + doNotExportCmdlets: null); } } @@ -3516,6 +3574,8 @@ internal PSModuleInfo LoadModuleManifest( ImportModuleMembers(manifestInfo, resolvedCommandPrefix, options); } + manifestInfo.LanguageMode = (manifestScriptInfo != null) ? manifestScriptInfo.DefiningLanguageMode : (PSLanguageMode?)null; + return manifestInfo; } @@ -3547,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) { @@ -3591,6 +3651,7 @@ private static void SetDeclaredDscResources(List exportedDscRes return true; } } + return false; }); foreach (var exportedResource in exportedClassDscResources) @@ -3600,243 +3661,7 @@ private static void SetDeclaredDscResources(List exportedDscRes } } - private readonly string _serviceCoreAssemblyFullName = "Microsoft.Powershell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"; - private readonly string _serviceCoreAssemblyShortName = "Microsoft.Powershell.Workflow.ServiceCore"; - - private PSModuleInfo LoadServiceCoreModule(PSModuleInfo parentModule, string moduleBase, SessionState ss, ImportModuleOptions nestedModuleOptions, ManifestProcessingFlags manifestProcessingFlags, bool addToParentModuleIfFound, out bool found) - { - SessionStateInternal oldSessionState = Context.EngineSessionState; - - if (ss != null) - Context.EngineSessionState = ss.Internal; - - try - { - found = false; - // Never load nested modules to the global scope. - bool oldGLobal = this.BaseGlobal; - this.BaseGlobal = false; - - PSModuleInfo nestedModule = LoadBinaryModule( - parentModule, // parentModule - false, // trySnapInName - _serviceCoreAssemblyFullName, // moduleName - null, // fileName - null, // assemblyToLoad - moduleBase, // moduleBase - ss, // SessionState - nestedModuleOptions, // ImportModuleOptions - manifestProcessingFlags, // ManifestProcessingFlags - string.Empty, // prefix: no -Prefix added for nested modules - true, // loadTypes - true, // loadFormats - out found, // found - _serviceCoreAssemblyShortName,// shortModuleName - true // disableFormatUpdates - ); - this.BaseGlobal = oldGLobal; - - // If found, add it to the parent's list of NestedModules - if (found) - { - if (addToParentModuleIfFound) - { - parentModule.AddNestedModule(nestedModule); - } - - return nestedModule; - } - else - { - string errorMessage = StringUtil.Format(Modules.ManifestMemberNotFound, _serviceCoreAssemblyFullName, "NestedModules", parentModule.Name); - FileNotFoundException fnf = new FileNotFoundException(errorMessage); - PSInvalidOperationException invalidOperation = new PSInvalidOperationException(errorMessage, fnf, "Modules_ModuleFileNotFound", ErrorCategory.ResourceUnavailable, parentModule.Name); - throw invalidOperation; - } - } - finally - { - Context.EngineSessionState = oldSessionState; - } - } - -#if !CORECLR // Workflow Not Supported On CSS - private void ProcessWorkflowsToProcess(string moduleBase, List workflowsToProcess, List dependentWorkflows, List assemblyList, SessionState ss, PSModuleInfo manifestInfo, ImportModuleOptions options) - { - // If we are actually processing - if (ss != null) - { - if (workflowsToProcess != null && workflowsToProcess.Count > 0) - { - // In ConstrainedLanguage, XAML workflows are not supported (even from a trusted FullLanguage state), - // unless they are signed in-box OS binaries. - if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) || - (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage)) - { - // However, this static internal property can be changed by tests that can already run a script - // in full-language mode. - if (!SystemPolicy.XamlWorkflowSupported) - { - foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess)) - { - if (!SecuritySupport.IsProductBinary(workflowPath)) - { - throw new NotSupportedException(Modules.XamlWorkflowsNotSupported); - } - } - } - } - - SessionStateInternal oldSessionStateWF = Context.EngineSessionState; - PSLanguageMode? savedLanguageMode = null; - try - { - Context.EngineSessionState = ss.Internal; - - // Always run workflow import script as trusted since only signed in-box files can be imported - // on locked down machines. - if (Context.LanguageMode != PSLanguageMode.FullLanguage) - { - savedLanguageMode = Context.LanguageMode; - Context.LanguageMode = PSLanguageMode.FullLanguage; - } - - if (dependentWorkflows != null && dependentWorkflows.Count > 0) - { - ScriptBlock importWorkflow = ScriptBlock.Create(Context, - "param($files, $dependentFiles, $assemblyList) Microsoft.PowerShell.Workflow.ServiceCore\\Import-PSWorkflow -Path \"$files\" -DependentWorkflow $dependentFiles -DependentAssemblies $assemblyList -Force:$" + BaseForce - ); - - List dependentFiles = new List(ResolveDependentWorkflowFiles(moduleBase, dependentWorkflows)); - - foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess)) - { - WriteVerbose(StringUtil.Format(Modules.LoadingWorkflow, workflowPath)); - importWorkflow.Invoke(workflowPath, dependentFiles.ToArray(), assemblyList.ToArray()); - } - } - else - { - ScriptBlock importWorkflow = ScriptBlock.Create(Context, - "param($files, $dependentFiles) Microsoft.PowerShell.Workflow.ServiceCore\\Import-PSWorkflow -Path \"$files\" -Force:$" + BaseForce - ); - - foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess)) - { - WriteVerbose(StringUtil.Format(Modules.LoadingWorkflow, workflowPath)); - importWorkflow.Invoke(workflowPath); - } - } - - // In the case where there are only nested modules, - // the members of the manifest are canonical... - ModuleIntrinsics.ExportModuleMembers(this, ss.Internal, - MatchAll, MatchAll, MatchAll, MatchAll, - options.ServiceCoreAutoAdded ? s_serviceCoreAssemblyCmdlets : null); - } - finally - { - Context.EngineSessionState = oldSessionStateWF; - - if (savedLanguageMode != null) - { - Context.LanguageMode = savedLanguageMode.Value; - } - } - } - } - else - { - if (workflowsToProcess != null) - { - // We are in analysis - foreach (string workflowToProcess in workflowsToProcess) - { - manifestInfo.AddDetectedWorkflowExport(System.IO.Path.GetFileNameWithoutExtension(workflowToProcess)); - } - } - } - } - - private ICollection ResolveWorkflowFiles(string moduleBase, List workflowsToProcess) - { - Dictionary resolvedWorkflowsToProcess = new Dictionary(); - foreach (string workflowToProcess in workflowsToProcess) - { - if (string.Equals(System.IO.Path.GetExtension(workflowToProcess), - StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - string wf = workflowToProcess; - if (!Path.IsPathRooted(wf)) - { - wf = Path.Combine(moduleBase, wf); - } - - foreach (string resolvedWorkflow in Context.SessionState.Path.GetResolvedProviderPathFromProviderPath(wf, Context.ProviderNames.FileSystem)) - { - resolvedWorkflowsToProcess[resolvedWorkflow] = resolvedWorkflow; - } - } - else - { - PSInvalidOperationException invalidOperation = PSTraceSource.NewInvalidOperationException( - Modules.InvalidWorkflowExtensionDuringManifestProcessing, - workflowToProcess); - invalidOperation.SetErrorId("Modules_InvalidWorkflowExtensionDuringManifestProcessing"); - throw invalidOperation; - } - } - return resolvedWorkflowsToProcess.Values; - } - - private ICollection ResolveDependentWorkflowFiles(string moduleBase, List dependentWorkflowsToProcess) - { - Dictionary resolvedWorkflowsToProcess = new Dictionary(); - - if (dependentWorkflowsToProcess.Count == 1 && string.Equals(System.IO.Path.GetExtension(dependentWorkflowsToProcess[0]), StringLiterals.DependentWorkflowAssemblyExtension, StringComparison.OrdinalIgnoreCase)) - { - string wf = dependentWorkflowsToProcess[0]; - if (!Path.IsPathRooted(wf)) - { - wf = Path.Combine(moduleBase, wf); - } - - resolvedWorkflowsToProcess[wf] = wf; - } - else - { - foreach (string workflowToProcess in dependentWorkflowsToProcess) - { - if (string.Equals(System.IO.Path.GetExtension(workflowToProcess), - StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { - string wf = workflowToProcess; - if (!Path.IsPathRooted(wf)) - { - wf = Path.Combine(moduleBase, wf); - } - - foreach (string resolvedWorkflow in Context.SessionState.Path.GetResolvedProviderPathFromProviderPath(wf, Context.ProviderNames.FileSystem)) - { - resolvedWorkflowsToProcess[resolvedWorkflow] = resolvedWorkflow; - } - } - else - { - PSInvalidOperationException invalidOperation = PSTraceSource.NewInvalidOperationException( - Modules.InvalidWorkflowExtensionDuringManifestProcessing, - workflowToProcess); - invalidOperation.SetErrorId("Modules_InvalidWorkflowExtensionDuringManifestProcessing"); - throw invalidOperation; - } - } - } - - return resolvedWorkflowsToProcess.Values; - } -#endif - - private static void UpdateCommandCollection(List list, List patterns) where T : CommandInfo + private static void UpdateCommandCollection(List list, List patterns) where T : CommandInfo { List updated = new List(); foreach (T element in list) @@ -3846,6 +3671,7 @@ private static void UpdateCommandCollection(List list, List list, List /// Execution Context. /// Either a string or a hash of ModuleName, optional Guid, and ModuleVersion. - /// Sets if the module cannot be found due to incorrect version. - /// Sets if the module cannot be found due to incorrect guid. + /// The reason the module failed to load, or null on success. /// Sets if the module/snapin is already present. - /// null if the module is not loaded or loadElements is false, the loaded module otherwise. - internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecification requiredModule, out bool wrongVersion, out bool wrongGuid, out bool loaded) + /// Null if the module is not loaded or loadElements is false, the loaded module otherwise. + internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecification requiredModule, out ModuleMatchFailure matchFailureReason, out bool loaded) { loaded = false; Dbg.Assert(requiredModule != null, "Caller should verify requiredModuleSpecification != null"); // Assume the module is not loaded. object result = null; - wrongVersion = false; - wrongGuid = false; - - string moduleName = requiredModule.Name; - Guid? moduleGuid = requiredModule.Guid; - Dbg.Assert(moduleName != null, "GetModuleSpecification should guarantee that moduleName != null"); + Dbg.Assert(requiredModule.Name != null, "GetModuleSpecification should guarantee that moduleName != null"); + ModuleMatchFailure matchFailure = ModuleMatchFailure.None; foreach (PSModuleInfo module in context.Modules.GetModules(new string[] { "*" }, false)) { - if (moduleName.Equals(module.Name, StringComparison.OrdinalIgnoreCase)) + // Check that the module meets the module constraints give + if (ModuleIntrinsics.IsModuleMatchingModuleSpec(out matchFailure, module, requiredModule)) { - if (!moduleGuid.HasValue || moduleGuid.Value.Equals(module.Guid)) - { - if (requiredModule.RequiredVersion != null) - { - if (requiredModule.RequiredVersion.Equals(module.Version)) - { - result = module; - loaded = true; - break; - } - - wrongVersion = true; - } - else if (requiredModule.Version != null) - { - if (requiredModule.MaximumVersion != null) - { - if (GetMaximumVersion(requiredModule.MaximumVersion) >= module.Version && requiredModule.Version <= module.Version) - { - result = module; - loaded = true; - break; - } - else - { - wrongVersion = true; - } - } - else if (requiredModule.Version <= module.Version) - { - result = module; - loaded = true; - break; - } - else - { - wrongVersion = true; - } - } - else if (requiredModule.MaximumVersion != null) - { - if (GetMaximumVersion(requiredModule.MaximumVersion) >= module.Version) - { - result = module; - loaded = true; - break; - } - else - { - wrongVersion = true; - } - } - else - { - result = module; - loaded = true; - break; - } - } - else - { - wrongGuid = true; - } + result = module; + loaded = true; + break; } } // 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; } } + matchFailureReason = matchFailure; return result; } @@ -4027,10 +3791,10 @@ internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecificat /// The current module being loaded. /// Either a string or a hash of ModuleName, optional Guid, and ModuleVersion. /// Used for error messages. - /// Specifies how to treat errors and whether to load elements + /// Specifies how to treat errors and whether to load elements. /// Set if any errors are found. /// Contains error record information. - /// null if the module is not loaded or loadElements is false, the loaded module otherwise. + /// Null if the module is not loaded or loadElements is false, the loaded module otherwise. internal PSModuleInfo LoadRequiredModule(PSModuleInfo currentModule, ModuleSpecification requiredModule, string moduleManifestPath, ManifestProcessingFlags manifestProcessingFlags, bool containedErrors, out ErrorRecord error) { Dbg.Assert(moduleManifestPath != null, "Caller should verify moduleManifestPath != null"); @@ -4039,6 +3803,7 @@ internal PSModuleInfo LoadRequiredModule(PSModuleInfo currentModule, ModuleSpeci { return LoadRequiredModule(Context, currentModule, requiredModule, moduleManifestPath, manifestProcessingFlags, out error); } + return null; } @@ -4051,9 +3816,9 @@ internal PSModuleInfo LoadRequiredModule(PSModuleInfo currentModule, ModuleSpeci /// The current module being loaded. /// Either a string or a hash of ModuleName, optional Guid, and ModuleVersion. /// Used for error messages. - /// Specifies how to treat errors and whether to load elements + /// Specifies how to treat errors and whether to load elements. /// Contains error record information. - /// null if the module is not loaded or loadElements is false, the loaded module otherwise. + /// Null if the module is not loaded or loadElements is false, the loaded module otherwise. internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, PSModuleInfo currentModule, ModuleSpecification requiredModuleSpecification, @@ -4061,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; @@ -4069,10 +3834,9 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, Guid? moduleGuid = requiredModuleSpecification.Guid; PSModuleInfo result = null; - bool wrongVersion = false; - bool wrongGuid = false; + ModuleMatchFailure loadFailureReason = ModuleMatchFailure.None; bool loaded = false; - object loadedModule = IsModuleLoaded(context, requiredModuleSpecification, out wrongVersion, out wrongGuid, out loaded); + object loadedModule = IsModuleLoaded(context, requiredModuleSpecification, out loadFailureReason, out loaded); if (loadedModule == null) { @@ -4091,6 +3855,7 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, { requiredModules.Add(new ModuleSpecification(currentModule), new List { requiredModuleSpecification }); } + if (requiredModuleSpecification != null) { requiredModules.Add(requiredModuleSpecification, new List(requiredModuleInfo.RequiredModulesSpecification)); @@ -4117,6 +3882,7 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, { mm = new MissingMemberException(error.Exception.Message); } + error = new ErrorRecord(mm, "Modules_InvalidManifest", ErrorCategory.ResourceUnavailable, moduleManifestPath); } @@ -4127,35 +3893,77 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, string message; if (moduleManifestPath != null) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { - if (wrongVersion) - { - if (requiredModuleSpecification.RequiredVersion != null) - { - message = StringUtil.Format(Modules.RequiredModuleNotLoadedWrongVersion, moduleManifestPath, moduleName, requiredModuleSpecification.RequiredVersion); - } - else if (requiredModuleSpecification.Version != null && requiredModuleSpecification.MaximumVersion == null) - { - message = StringUtil.Format(Modules.RequiredModuleNotLoadedWrongVersion, moduleManifestPath, moduleName, requiredModuleSpecification.Version); - } - else if (requiredModuleSpecification.Version == null && requiredModuleSpecification.MaximumVersion != null) - { - message = StringUtil.Format(Modules.RequiredModuleNotLoadedWrongMaximumVersion, moduleManifestPath, moduleName, requiredModuleSpecification.MaximumVersion); - } - else - { - message = StringUtil.Format(Modules.RequiredModuleNotLoadedWrongMinimumVersionAndMaximumVersion, moduleManifestPath, moduleName, requiredModuleSpecification.Version, requiredModuleSpecification.MaximumVersion); - } - } - else if (wrongGuid) - { - message = StringUtil.Format(Modules.RequiredModuleNotLoadedWrongGuid, moduleManifestPath, moduleName, moduleGuid.Value); - } - else + switch (loadFailureReason) { - message = StringUtil.Format(Modules.RequiredModuleNotLoaded, moduleManifestPath, moduleName); + case ModuleMatchFailure.RequiredVersion: + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongVersion, + moduleManifestPath, + moduleName, + requiredModuleSpecification.RequiredVersion); + break; + + case ModuleMatchFailure.MinimumVersion: + // If both max and min versions were specified, use a different error message + if (requiredModuleSpecification.MaximumVersion == null) + { + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongVersion, + moduleManifestPath, moduleName, + requiredModuleSpecification.Version); + } + else + { + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongMinimumVersionAndMaximumVersion, + moduleManifestPath, + moduleName, + requiredModuleSpecification.Version, + requiredModuleSpecification.MaximumVersion); + } + + break; + + case ModuleMatchFailure.MaximumVersion: + // If both max and min versions were specified, use a different error message + if (requiredModuleSpecification.Version == null) + { + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongMaximumVersion, + moduleManifestPath, + moduleName, + requiredModuleSpecification.MaximumVersion); + } + else + { + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongMinimumVersionAndMaximumVersion, + moduleManifestPath, + moduleName, + requiredModuleSpecification.Version, + requiredModuleSpecification.MaximumVersion); + } + + break; + + case ModuleMatchFailure.Guid: + message = StringUtil.Format( + Modules.RequiredModuleNotLoadedWrongGuid, + moduleManifestPath, + moduleName, + moduleGuid.Value); + break; + + default: + message = StringUtil.Format( + Modules.RequiredModuleNotLoaded, + moduleManifestPath, + moduleName); + break; } + MissingMemberException mm = new MissingMemberException(message); error = new ErrorRecord(mm, "Modules_InvalidManifest", ErrorCategory.ResourceUnavailable, moduleManifestPath); @@ -4210,7 +4018,6 @@ private static PSModuleInfo ImportRequiredModule(ExecutionContext context, Modul powerShell.AddParameter("Version", requiredModule.Version); } - powerShell.Invoke(); if (powerShell.Streams.Error != null && powerShell.Streams.Error.Count > 0) { @@ -4218,8 +4025,6 @@ private static PSModuleInfo ImportRequiredModule(ExecutionContext context, Modul } else { - bool wrongVersion = false; - bool wrongGuid = false; // Check if the correct module is loaded using Version , Guid Information. string moduleNameToCheckAgainst = requiredModule.Name; string manifestPath = string.Empty; @@ -4228,26 +4033,34 @@ private static PSModuleInfo ImportRequiredModule(ExecutionContext context, Modul manifestPath = moduleNameToCheckAgainst; moduleNameToCheckAgainst = Path.GetFileNameWithoutExtension(moduleNameToCheckAgainst); } + ModuleSpecification ms = new ModuleSpecification(moduleNameToCheckAgainst); if (requiredModule.Guid != null) { ms.Guid = requiredModule.Guid.Value; } + if (requiredModule.RequiredVersion != null) { ms.RequiredVersion = requiredModule.RequiredVersion; } + if (requiredModule.Version != null) { ms.Version = requiredModule.Version; } + if (requiredModule.MaximumVersion != null) { ms.MaximumVersion = requiredModule.MaximumVersion; } + + ModuleMatchFailure loadFailureReason; bool loaded = false; - object r = IsModuleLoaded(context, ms, out wrongVersion, out wrongGuid, out loaded); + object r = IsModuleLoaded(context, ms, out loadFailureReason, out loaded); + Dbg.Assert(r is PSModuleInfo, "The returned value should be PSModuleInfo"); + result = r as PSModuleInfo; if (result == null) { @@ -4266,6 +4079,7 @@ private static PSModuleInfo ImportRequiredModule(ExecutionContext context, Modul } } } + return result; } @@ -4286,7 +4100,7 @@ internal bool VerifyIfNestedModuleIsAvailable(ModuleSpecification nestedModuleSp string extension, out PSModuleInfo nestedModuleInfoIfAvailable) { - Dbg.Assert(null != nestedModuleSpec, "nestedModuleSpec cannot be null."); + Dbg.Assert(nestedModuleSpec != null, "nestedModuleSpec cannot be null."); nestedModuleInfoIfAvailable = null; if ((nestedModuleSpec.Guid != null) || (nestedModuleSpec.Version != null) || (nestedModuleSpec.RequiredVersion != null) || (nestedModuleSpec.MaximumVersion != null)) { @@ -4302,6 +4116,7 @@ internal bool VerifyIfNestedModuleIsAvailable(ModuleSpecification nestedModuleSp { tempModuleName = rootedModulePath + StringLiterals.PowerShellDataFileExtension; } + ModuleSpecification tempSpec = new ModuleSpecification( string.IsNullOrEmpty(rootedModulePath) ? nestedModuleSpec.Name : tempModuleName); if (nestedModuleSpec.Guid.HasValue) @@ -4374,45 +4189,23 @@ internal static Collection GetModuleIfAvailable(ModuleSpecificatio tempResult = powerShell.Invoke(); } - // Check if the available module is of the correct version and GUID - foreach (var m in tempResult) + + // Check if the available module is of the correct version and GUID. The name is already checked. + // GH #8204: The required name here may be the full path, while the module name may be just the module, + // so comparing them may fail incorrectly. + foreach (var module in tempResult) { - if (!requiredModule.Guid.HasValue || requiredModule.Guid.Value.Equals(m.Guid)) + if (ModuleIntrinsics.IsModuleMatchingConstraints( + module, + guid: requiredModule.Guid, + requiredVersion: requiredModule.RequiredVersion, + minimumVersion: requiredModule.Version, + maximumVersion: requiredModule.MaximumVersion == null ? null : GetMaximumVersion(requiredModule.MaximumVersion))) { - if (requiredModule.RequiredVersion != null) - { - if (requiredModule.RequiredVersion.Equals(m.Version)) - { - result.Add(m); - } - } - else if (requiredModule.Version != null) - { - if (requiredModule.MaximumVersion != null) - { - if (GetMaximumVersion(requiredModule.MaximumVersion) >= m.Version && requiredModule.Version <= m.Version) - { - result.Add(m); - } - } - else if (requiredModule.Version <= m.Version) - { - result.Add(m); - } - } - else if (requiredModule.MaximumVersion != null) - { - if (GetMaximumVersion(requiredModule.MaximumVersion) >= m.Version) - { - result.Add(m); - } - } - else - { - result.Add(m); - } + result.Add(module); } } + return result; } @@ -4449,6 +4242,7 @@ private static bool HasRequiredModulesCyclicReference(ModuleSpecification curren break; } } + Dbg.Assert(mo != null, "The moduleInfo should be present"); string message = StringUtil.Format(Modules.RequiredModulesCyclicDependency, currentModuleSpecification.ToString(), requiredModuleSpecification.ToString(), mo.Path); MissingMemberException mm = new MissingMemberException(message); @@ -4485,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); @@ -4493,17 +4287,17 @@ private ExternalScriptInfo FindLocalizedModuleManifest(string path) CultureInfo culture = System.Globalization.CultureInfo.CurrentUICulture; CultureInfo currentCulture = culture; - while (currentCulture != null && !String.IsNullOrEmpty(currentCulture.Name)) + 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(); + string filePath = stringBuilder.ToString(); - if (Utils.NativeFileExists(filePath)) + if (File.Exists(filePath)) { localizedFile = filePath; break; @@ -4524,13 +4318,13 @@ 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 - /// the table key to use - /// Specifies how to treat errors and whether to load elements - /// Returns the extracted version + /// 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. + /// Returns the extracted version. /// internal bool GetListOfStringsFromData( Hashtable data, @@ -4556,19 +4350,20 @@ internal bool GetListOfStringsFromData( } } } + return true; } /// /// 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 - /// the table key to use - /// Specifies how to treat errors and whether to load elements - /// Returns the extracted version + /// 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. + /// Returns the extracted version. /// private bool GetListOfWildcardsFromData( Hashtable data, @@ -4615,16 +4410,16 @@ 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 - /// the table key to use - /// 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 - /// Returns the extracted version + /// 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. + /// Base directory of a module. + /// Expected file extension (added to strings that didn't have an extension). + /// If then we want to error out if the specified files don't exist. + /// Returns the extracted version. /// private bool GetListOfFilesFromData( Hashtable data, @@ -4637,9 +4432,7 @@ private bool GetListOfFilesFromData( out List list) { list = null; - - List listOfStrings; - if (!GetListOfStringsFromData(data, moduleManifestPath, key, manifestProcessingFlags, out listOfStrings)) + if (!GetListOfStringsFromData(data, moduleManifestPath, key, manifestProcessingFlags, out List listOfStrings)) { return false; } @@ -4660,7 +4453,7 @@ private bool GetListOfFilesFromData( { try { - string fixedFileName = FixupFileName(moduleBase, s, extension); + string fixedFileName = FixFileNameWithoutLoadingAssembly(moduleBase, s, extension); var dir = Path.GetDirectoryName(fixedFileName); if (string.Equals(psHome, dir, StringComparison.OrdinalIgnoreCase) || @@ -4670,19 +4463,21 @@ private bool GetListOfFilesFromData( // which we can't really do b/c the file doesn't exist. fixedFileName = psHome + "\\" + Path.GetFileName(s); } - else if (verifyFilesExist && !Utils.NativeFileExists(fixedFileName)) + else if (verifyFilesExist && !File.Exists(fixedFileName)) { string message = StringUtil.Format(SessionStateStrings.PathNotFound, fixedFileName); throw new FileNotFoundException(message, fixedFileName); } + list.Add(fixedFileName); } catch (Exception e) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if (manifestProcessingFlags.HasFlag(ManifestProcessingFlags.WriteErrors)) { this.ThrowTerminatingError(GenerateInvalidModuleMemberErrorRecord(key, moduleManifestPath, e)); } + list = null; WriteInvalidManifestMemberError(this, key, moduleManifestPath, e, manifestProcessingFlags); return false; @@ -4702,7 +4497,7 @@ internal enum ModuleLoggingGroupPolicyStatus } /// - /// Enable Module logging based on group policy + /// Enable Module logging based on group policy. /// internal void SetModuleLoggingInformation(PSModuleInfo m) { @@ -4714,15 +4509,13 @@ 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 - //if ((status & ModuleLoggingGroupPolicyStatus.Enabled) != 0) - //{ - - //} - + // if ((status & ModuleLoggingGroupPolicyStatus.Enabled) != 0) + // { + // } if (((status & ModuleLoggingGroupPolicyStatus.Enabled) != 0) && moduleNames != null) { foreach (string currentGPModuleName in moduleNames) @@ -4770,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 + /// 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. + /// if success; if there were errors. internal bool GetScalarFromData( Hashtable data, string moduleManifestPath, @@ -4802,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); @@ -4810,57 +4603,112 @@ internal bool GetScalarFromData( ErrorCategory.ResourceUnavailable, moduleManifestPath); WriteError(er); } + return false; } } + 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 + /// A utility routine to fix up a file name so it's rooted and has an extension. /// - /// The base path to use if the file is not rooted - /// The file name to resolve. - /// The extension to use. - /// - /// - internal string FixupFileName(string moduleBase, string name, string extension) + private string FixFileName(string moduleName, string moduleBase, string fileName, string extension, bool canLoadAssembly) + { + 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 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 for the look up. + /// Indicate if we can load assembly for the resolution. + /// Indicate if the returned path is fully resolved. + /// + /// The resolved file path. Or, the combined path of and when the file path cannot be resolved. + /// + private string FixFileName(string moduleName, string moduleBase, string fileName, string extension, bool canLoadAssembly, out bool pathIsResolved) { - // First check for full-qualified paths - either absolute or relative - string resolvedName; - if (!IsRooted(name)) + pathIsResolved = false; + + string originalName = fileName; + string originalExt = Path.GetExtension(fileName); + + if (string.IsNullOrEmpty(extension)) { - // The manifest file should only be related to moduleBase path. - resolvedName = ResolveRootedFilePath(Path.Combine(moduleBase, name), this.Context); + // When 'extension' is not explicitly specified, we honor the original extension. + extension = originalExt; } - else + else if (!extension.Equals(originalExt, StringComparison.OrdinalIgnoreCase)) { - resolvedName = ResolveRootedFilePath(name, this.Context); + // 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; } - if (string.IsNullOrEmpty(resolvedName)) - { - resolvedName = Path.Combine(moduleBase, name); - } - // Again resolve the file path - // This is so that any relative path references are expanded to give the absolute path - // C:\Windows\System32\WindowsPowerShell\V1.0\Modules\Microsoft.PowerShell.WSMAN\..\..\WSMan.format.ps1xml is expanded to - // C:\Windows\System32\WindowsPowerShell\V1.0\WSMan.format.ps1xml - string resolvedName2 = ResolveRootedFilePath(resolvedName, this.Context); - string ext = Path.GetExtension(name); - string result = !string.IsNullOrEmpty(resolvedName2) ? resolvedName2 : resolvedName; - if (string.IsNullOrEmpty(ext)) + // Try to get the resolved fully qualified path to the file. + // Note that, the 'IsRooted' method also returns true for relative paths, in which case we need to check for 'combinedPath' as well. + // * For example, the 'Microsoft.WSMan.Management.psd1' in Windows PowerShell defines 'FormatsToProcess="..\..\WSMan.format.ps1xml"'. + // * For such a module, we will have the following input when reaching this method: + // - moduleBase = 'C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Microsoft.WSMan.Management' + // - name = '..\..\WSMan.format.ps1xml' + // 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, fileName); + string resolvedPath = IsRooted(fileName) + ? ResolveRootedFilePath(fileName, Context) ?? ResolveRootedFilePath(combinedPath, Context) + : ResolveRootedFilePath(combinedPath, Context); + + // Return the path if successfully resolved. + if (resolvedPath is not null) { - result += extension; + 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' 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; + return resolvedPath; } - //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(ext) && ext.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + // Path resolution failed, use the combined path as default. + string result = combinedPath; + + // 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 (canLoadAssembly && !string.IsNullOrEmpty(extension) && + (extension.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + extension.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase))) { - Exception ignored = null; - Assembly assembly = ExecutionContext.LoadAssembly(name, null, out ignored); - 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; } } @@ -4871,9 +4719,8 @@ internal string FixupFileName(string moduleBase, string name, string extension) /// /// A utility routine to fix up a file name, if it is relative path convert it to absolute path combining moduleBase, if it is not a relative path, leave as it is. /// - /// The base path to use if the file is not rooted + /// The base path to use if the file is not rooted. /// The file name to resolve. - /// /// internal string GetAbsolutePath(string moduleBase, string path) { @@ -4887,6 +4734,11 @@ internal string GetAbsolutePath(string moduleBase, string path) } } + /// + /// Check if a path is rooted or "relative rooted". + /// + /// The file path to check. + /// True if the path is rooted, false otherwise. internal static bool IsRooted(string filePath) { return (Path.IsPathRooted(filePath) || @@ -4896,17 +4748,17 @@ internal static bool IsRooted(string filePath) filePath.StartsWith(@"../", StringComparison.Ordinal) || filePath.StartsWith(@"~/", StringComparison.Ordinal) || filePath.StartsWith(@"~\", StringComparison.Ordinal) || - filePath.IndexOf(":", StringComparison.Ordinal) >= 0); + filePath.Contains(':')); } /// /// This utility resolves a rooted file name using the provider /// routines. It will only work if the path exists. /// - /// The filename to resolve - /// Execution context - /// The resolved filename - internal static String ResolveRootedFilePath(string filePath, ExecutionContext context) + /// The filename to resolve. + /// Execution context. + /// The resolved filename. + internal static string ResolveRootedFilePath(string filePath, ExecutionContext context) { // If the path is not fully qualified or relative rooted, then // we need to do path-based resolution... @@ -4935,8 +4787,13 @@ internal static String ResolveRootedFilePath(string filePath, ExecutionContext c if (!provider.NameEquals(context.ProviderNames.FileSystem)) { // "The current provider ({0}) cannot open a file" - throw InterpreterError.NewInterpreterException(filePath, typeof(RuntimeException), - null, "FileOpenError", ParserStrings.FileOpenError, provider.FullName); + throw InterpreterError.NewInterpreterException( + filePath, + typeof(RuntimeException), + errorPosition: null, + "FileOpenError", + ParserStrings.FileOpenError, + provider.FullName); } } @@ -4949,9 +4806,12 @@ internal static String ResolveRootedFilePath(string filePath, ExecutionContext c if (filePaths.Count > 1) { // "The path resolved to more than one file; can only process one file at a time." - throw InterpreterError. - NewInterpreterException(filePaths, typeof(RuntimeException), - null, "AmbiguousPath", ParserStrings.AmbiguousPath); + throw InterpreterError.NewInterpreterException( + filePaths, + typeof(RuntimeException), + errorPosition: null, + "AmbiguousPath", + ParserStrings.AmbiguousPath); } return filePaths[0]; @@ -4963,7 +4823,6 @@ internal static string GetResolvedPath(string filePath, ExecutionContext context Collection filePaths; - if (context != null && context.EngineSessionState != null && context.EngineSessionState.IsProviderLoaded(context.ProviderNames.FileSystem)) { try @@ -5001,7 +4860,6 @@ internal static Collection GetResolvedPathCollection(string filePath, Ex Collection filePaths; - if (context != null && context.EngineSessionState != null && context.EngineSessionState.IsProviderLoaded(context.ProviderNames.FileSystem)) { try @@ -5033,6 +4891,85 @@ internal static Collection GetResolvedPathCollection(string filePath, Ex return filePaths; } + internal static PSSession GetWindowsPowerShellCompatRemotingSession() + { + PSSession result = null; + var commandInfo = new CmdletInfo("Get-PSSession", typeof(GetPSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("Name", WindowsPowerShellCompatRemotingSessionName); + ps.AddParameter("ErrorAction", ActionPreference.Ignore); + var results = ps.Invoke(); + if (results.Count > 0) + { + result = results[0]; + } + return result; + } + + internal static PSSession CreateWindowsPowerShellCompatResources() + { + PSSession compatSession = null; + lock (s_WindowsPowerShellCompatSyncObject) + { + compatSession = GetWindowsPowerShellCompatRemotingSession(); + if (compatSession == null) + { + var commandInfo = new CmdletInfo("New-PSSession", typeof(NewPSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("UseWindowsPowerShell", true); + ps.AddParameter("Name", WindowsPowerShellCompatRemotingSessionName); + var results = ps.Invoke(); + if (results.Count > 0) + { + compatSession = results[0]; + System.Threading.Interlocked.Exchange(ref s_WindowsPowerShellCompatUsageCounter, 0); + } + } + } + + return compatSession; + } + + internal static void CleanupWindowsPowerShellCompatResources(SessionState sessionState) + { + lock (s_WindowsPowerShellCompatSyncObject) + { + var compatSession = GetWindowsPowerShellCompatRemotingSession(); + if (compatSession != null) + { + if (sessionState?.InvokeCommand.LocationChangedAction != null) + { + sessionState.InvokeCommand.LocationChangedAction -= SyncCurrentLocationDelegate; + } + + var commandInfo = new CmdletInfo("Remove-PSSession", typeof(RemovePSSessionCommand)); + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(commandInfo); + ps.AddParameter("Session", compatSession); + ps.Invoke(); + } + } + } + + internal static void SyncCurrentLocationHandler(object sender, LocationChangedEventArgs args) + { + PSSession compatSession = GetWindowsPowerShellCompatRemotingSession(); + if (compatSession?.Runspace.RunspaceStateInfo.State == RunspaceState.Opened) + { + 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.Create(CultureInfo.InvariantCulture, $"Set-Location -Path '{args.NewPath.Path}'"))); + ps.Invoke(); + } + } + + internal static EventHandler SyncCurrentLocationDelegate; + + internal virtual IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { throw new System.NotImplementedException(); } + private void RemoveTypesAndFormatting( IList formatFilesToRemove, IList typeFilesToRemove) @@ -5068,30 +5005,29 @@ private void RemoveTypesAndFormatting( } /// - /// Removes a module from the session state + /// Removes a module from the session state. /// - /// module to remove + /// Module to remove. internal void RemoveModule(PSModuleInfo module) { RemoveModule(module, null); } /// - /// Removes a module from the session state + /// Removes a module from the session state. /// - /// module to remove - /// module name specified in the cmdlet + /// Module to remove. + /// 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 == "") + if (module.Path == string.Empty) { module.Path = module.Name; } - bool shouldModuleBeRemoved = ShouldModuleBeRemoved(module, moduleNameInRemoveModuleCmdlet, out isTopLevelModule); + + bool shouldModuleBeRemoved = ShouldModuleBeRemoved(module, moduleNameInRemoveModuleCmdlet, out bool isTopLevelModule); if (shouldModuleBeRemoved) { @@ -5099,24 +5035,21 @@ 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) { - if (typeof(IModuleAssemblyCleanup).IsAssignableFrom(type) && type != typeof(IModuleAssemblyCleanup)) + if (typeof(IModuleAssemblyCleanup).IsAssignableFrom(type)) { var moduleCleanup = (IModuleAssemblyCleanup)Activator.CreateInstance(type, true); moduleCleanup.OnRemove(module); @@ -5124,10 +5057,14 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC } } + if (module.IsWindowsPowerShellCompatModule && (System.Threading.Interlocked.Decrement(ref s_WindowsPowerShellCompatUsageCounter) == 0)) + { + CleanupWindowsPowerShellCompatResources(this.SessionState); + } + // First remove cmdlets from the session state // (can't just go through module.ExportedCmdlets // because the names of the cmdlets might have been changed by the -Prefix parameter of Import-Module) - List keysToRemoveFromCmdletCache = new List(); foreach (KeyValuePair> cmdlet in Context.EngineSessionState.GetCmdletTable()) { @@ -5139,6 +5076,7 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC { continue; } + if (matches[i].Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) { string name = matches[i].Name; @@ -5152,12 +5090,12 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC keysToRemoveFromCmdletCache.Add(cmdlet.Key); } } + foreach (string keyToRemove in keysToRemoveFromCmdletCache) { Context.EngineSessionState.RemoveCmdletEntry(keyToRemove, true); } - // Remove any providers imported by this module. Providers are always imported into // the top level session state. Only binary modules can import providers. if (module.ModuleType == ModuleType.Binary) @@ -5176,7 +5114,7 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC ProviderInfo pi = pl.Value[i]; // If it was implemented by this module, remove it - string implAssemblyLocation = pi.ImplementingType.GetTypeInfo().Assembly.Location; + string implAssemblyLocation = pi.ImplementingType.Assembly.Location; if (implAssemblyLocation.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) { // Remove all drives from the top level session state @@ -5224,28 +5162,29 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC // Remove the imported functions from SessionState... // (can't just go through module.SessionState.Internal.ExportedFunctions, // because the names of the functions might have been changed by the -Prefix parameter of Import-Module) - foreach (DictionaryEntry entry in ss.GetFunctionTable()) + foreach ((var _, FunctionInfo functionInfo) in ss.GetFunctionTable()) { - FunctionInfo func = (FunctionInfo)entry.Value; - if (func.Module == null) + if (functionInfo.Module == null) { continue; } - if (func.Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) + + if (functionInfo.Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) { + string functionName = functionInfo.Name; try { - ss.RemoveFunction(func.Name, true); + ss.RemoveFunction(functionName, true); - string memberMessage = StringUtil.Format(Modules.RemovingImportedFunction, func.Name); + string memberMessage = StringUtil.Format(Modules.RemovingImportedFunction, functionName); WriteVerbose(memberMessage); } catch (SessionStateUnauthorizedAccessException e) { - string message = StringUtil.Format(Modules.UnableToRemoveModuleMember, func.Name, module.Name, e.Message); + string message = StringUtil.Format(Modules.UnableToRemoveModuleMember, functionName, module.Name, e.Message); InvalidOperationException memberNotRemoved = new InvalidOperationException(message, e); ErrorRecord er = new ErrorRecord(memberNotRemoved, "Modules_MemberNotRemoved", - ErrorCategory.PermissionDenied, func.Name); + ErrorCategory.PermissionDenied, functionName); WriteError(er); } } @@ -5273,6 +5212,7 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC { continue; } + if (ai.Module.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) { // Remove the alias with force... @@ -5303,6 +5243,7 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC } } } + if (isTopLevelModule) { // Remove it from the top level session state @@ -5314,6 +5255,15 @@ internal void RemoveModule(PSModuleInfo module, string moduleNameInRemoveModuleC // And the appdomain level module path cache. PSModuleInfo.RemoveFromAppDomainLevelCache(module.Name); + + // 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)) + { + // 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); + } } } } @@ -5336,55 +5286,39 @@ private bool ShouldModuleBeRemoved(PSModuleInfo module, string moduleNameInRemov { return true; } + return false; } - internal bool IsModuleAlreadyLoaded(PSModuleInfo alreadyLoadedModule) + /// + /// Checks if an already loaded module meets the constraints passed to the module cmdlet by the user. + /// + /// The already loaded module that matched the name of the module to load. + /// True if the pre-loaded module matches all GUID and version constraints provided, false otherwise. + internal bool DoesAlreadyLoadedModuleSatisfyConstraints(PSModuleInfo alreadyLoadedModule) { - if (alreadyLoadedModule == null) - { - return false; - } - if (this.BaseRequiredVersion != null && !alreadyLoadedModule.Version.Equals(this.BaseRequiredVersion)) - { - // "alreadyLoadedModule" is a different module (than the one we want to load) - return false; - } - if (this.BaseMinimumVersion != null && alreadyLoadedModule.Version < this.BaseMinimumVersion) - { - // "alreadyLoadedModule" is a different module (than the one we want to load) - return false; - } - if (this.BaseMaximumVersion != null && alreadyLoadedModule.Version > this.BaseMaximumVersion) - { - // "alreadyLoadedModule" is a different module (than the one we want to load) - return false; - } - if (BaseGuid != null && !alreadyLoadedModule.Guid.Equals(this.BaseGuid)) - { - // "alreadyLoadedModule" is a different module (than the one we want to load) - return false; - } - - return true; + return ModuleIntrinsics.IsModuleMatchingConstraints( + alreadyLoadedModule, + guid: BaseGuid, + requiredVersion: BaseRequiredVersion, + minimumVersion: BaseMinimumVersion, + maximumVersion: BaseMaximumVersion); } /// - /// /// /// /// /// /// - /// 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 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 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) { - PSModuleInfo alreadyLoadedModule; - if (this.Context.Modules.ModuleTable.TryGetValue(modulePath, out alreadyLoadedModule)) + if (TryGetFromModuleTable(modulePath, out PSModuleInfo alreadyLoadedModule)) { - if (this.IsModuleAlreadyLoaded(alreadyLoadedModule)) + if (this.DoesAlreadyLoadedModuleSatisfyConstraints(alreadyLoadedModule)) { if (this.BaseForce) // remove the previously imported module + return null (null = please proceed with regular import) { @@ -5397,7 +5331,7 @@ internal PSModuleInfo IsModuleImportUnnecessaryBecauseModuleIsAlreadyLoaded(stri else // reimport the module + return alreadyLoadedModule (alreadyLoadedModule = no need to proceed with regular import) { // If the module has already been loaded, then while loading it the second time, we should load it with the DefaultCommandPrefix specified in the module manifest. (If there is no Prefix from command line) - if (string.IsNullOrEmpty(prefix) && Utils.NativeFileExists(alreadyLoadedModule.Path)) + if (string.IsNullOrEmpty(prefix) && File.Exists(alreadyLoadedModule.Path)) { string defaultPrefix = GetDefaultPrefix(alreadyLoadedModule); if (!string.IsNullOrEmpty(defaultPrefix)) @@ -5442,55 +5376,53 @@ internal PSModuleInfo IsModuleImportUnnecessaryBecauseModuleIsAlreadyLoaded(stri } /// - /// Loads a module file after searching for it using the known extension list + /// Loads a module file after searching for it using the known extension list. /// - /// The parent module for which this module is a nested module - /// The name to use for the module - /// The file basename for this module - /// The module's extension - /// The module base which comes from the module manifest - /// Command name prefix + /// The parent module for which this module is a nested module. + /// The name to use for the module. + /// The file basename for this module. + /// The module's extension. + /// The module base which comes from the module manifest. + /// Command name prefix. /// /// The session state instance to use for this module - may be null /// in which case a session state will be allocated if necessary /// - /// The set of options that are used while importing a module - /// The processing flags to use when processing the module - /// True if a module was found + /// The set of options that are used while importing a module. + /// The processing flags to use when processing the module. + /// True if a module was found. /// 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 _); } /// - /// Loads a module file after searching for it using the known extension list + /// Loads a module file after searching for it using the known extension list. /// - /// The parent module for which this module is a nested module - /// The name to use for the module - /// The file basename for this module - /// The module's extension - /// The module base which comes from the module manifest - /// Command name prefix + /// The parent module for which this module is a nested module. + /// The name to use for the module. + /// The file basename for this module. + /// The module's extension. + /// The module base which comes from the module manifest. + /// Command name prefix. /// /// The session state instance to use for this module - may be null /// in which case a session state will be allocated if necessary /// - /// The set of options that are used while importing a module - /// The processing flags to use when processing the module - /// True if a module was found - /// True if a module file was found + /// The set of options that are used while importing a module. + /// The processing flags to use when processing the module. + /// True if a module was found. + /// True if a module file was found. /// internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, string moduleName, string fileBaseName, string extension, string moduleBase, string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found, out bool moduleFileFound) { string[] extensions; - PSModuleInfo module; moduleFileFound = false; if (!string.IsNullOrEmpty(extension)) @@ -5498,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++) @@ -5521,21 +5453,11 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, } // If the module has already been loaded, just emit it and continue... - Context.Modules.ModuleTable.TryGetValue(fileName, out module); - // TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior - //if (!BaseForce && - // IsModuleAlreadyLoaded(module) && - // ((manifestProcessingFlags & ManifestProcessingFlags.LoadElements) == ManifestProcessingFlags.LoadElements)) - if (!BaseForce && module != null && - (BaseRequiredVersion == null || module.Version.Equals(BaseRequiredVersion)) && - ((BaseMinimumVersion == null && BaseMaximumVersion == null) - || (BaseMaximumVersion != null && BaseMinimumVersion == null && module.Version <= BaseMaximumVersion) - || (BaseMaximumVersion == null && BaseMinimumVersion != null && module.Version >= BaseMinimumVersion) - || (BaseMaximumVersion != null && BaseMinimumVersion != null && module.Version >= BaseMinimumVersion && module.Version <= BaseMaximumVersion)) && - (BaseGuid == null || module.Guid.Equals(BaseGuid)) && importingModule) + TryGetFromModuleTable(fileName, out PSModuleInfo module); + if (!BaseForce && importingModule && DoesAlreadyLoadedModuleSatisfyConstraints(module)) { moduleFileFound = true; - module = Context.Modules.ModuleTable[fileName]; + // If the module has already been loaded, then while loading it the second time, we should load it with the DefaultCommandPrefix specified in the module manifest. (If there is no Prefix from command line) if (string.IsNullOrEmpty(prefix)) { @@ -5570,22 +5492,18 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, { WriteObject(module); } + found = true; return module; } - else if (Utils.NativeFileExists(fileName)) + 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 && module != null && - (this.BaseRequiredVersion == null || module.Version.Equals(this.BaseRequiredVersion)) && - (BaseGuid == null || module.Guid.Equals(BaseGuid))) - // TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior - // TODO/FIXME: (for example the checks above are not lookint at this.BaseMinimumVersion) - //if (BaseForce && IsModuleAlreadyLoaded(module)) + if (BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(module)) { RemoveModule(module); } + module = LoadModule(parentModule, fileName, moduleBase, prefix, ss, null, ref options, manifestProcessingFlags, out found, out moduleFileFound); if (found) { @@ -5621,6 +5539,7 @@ internal string GetDefaultPrefix(PSModuleInfo module) prefix = (string)LanguagePrimitives.ConvertTo(localizedData["DefaultCommandPrefix"], typeof(string), CultureInfo.InvariantCulture); } + if (string.IsNullOrEmpty(prefix)) { prefix = (string)LanguagePrimitives.ConvertTo(data["DefaultCommandPrefix"], @@ -5629,15 +5548,16 @@ internal string GetDefaultPrefix(PSModuleInfo module) } } } + return prefix; } /// /// Create an ExternalScriptInfo object from a file path. /// - /// The path to the file - /// The base name of the script - /// check the current execution policy + /// The path to the file. + /// The base name of the script. + /// Check the current execution policy. /// The ExternalScriptInfo object. internal ExternalScriptInfo GetScriptInfoForFile(string fileName, out string scriptName, bool checkExecutionPolicy) { @@ -5646,7 +5566,6 @@ internal ExternalScriptInfo GetScriptInfoForFile(string fileName, out string scr // Skip ShouldRun check for .psd1 files. // Use ValidateScriptInfo() for explicitly validating the checkpolicy for psd1 file. - // if (!scriptName.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { if (checkExecutionPolicy) @@ -5663,19 +5582,6 @@ internal ExternalScriptInfo GetScriptInfoForFile(string fileName, out string scr if (!scriptName.EndsWith(".cdxml", StringComparison.OrdinalIgnoreCase)) { CommandDiscovery.VerifyScriptRequirements(scriptInfo, Context); - - // Verify that the NetFrameWorkVersion is correct... - - #region comment out RequiresNetFrameworkVersion feature 8/10/2010 - - /* - * The "#requires -NetFrameworkVersion" feature is CUT OFF. - * The call of "VerifyNetFrameworkVersion" will be CUT OFF too. - /* - CommandDiscovery.VerifyNetFrameworkVersion(scriptInfo); - */ - - #endregion } // If we got this far, the check succeeded and we don't need to check again. @@ -5688,14 +5594,14 @@ internal ExternalScriptInfo GetScriptInfoForFile(string fileName, out string scr /// /// Load a module from a file... /// - /// The resolved path to load the module from - /// The module base path to use for this module - /// Command name prefix - /// The session state instance to use for this module - may be null in which case a session state will be allocated if necessary - /// The set of options that are used while importing a module - /// The manifest processing flags to use when processing the module - /// True if a module was found - /// True if the module was successfully loaded + /// The resolved path to load the module from. + /// The module base path to use for this module. + /// Command name prefix. + /// The session state instance to use for this module - may be null in which case a session state will be allocated if necessary. + /// The set of options that are used while importing a module. + /// The manifest processing flags to use when processing the module. + /// True if a module was found. + /// True if the module was successfully loaded. internal PSModuleInfo LoadModule(string fileName, string moduleBase, string prefix, SessionState ss, ref ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found) { @@ -5706,32 +5612,32 @@ internal PSModuleInfo LoadModule(string fileName, string moduleBase, string pref /// /// Load a module from a file... /// - /// The parent module, if any - /// The resolved path to load the module from - /// The module base path to use for this module - /// Command name prefix - /// The session state instance to use for this module - may be null in which case a session state will be allocated if necessary - /// Private Data for the module - /// The set of options that are used while importing a module - /// The manifest processing flags to use when processing the module - /// True if a module was found - /// True if a module file was found - /// True if the module was successfully loaded + /// The parent module, if any. + /// The resolved path to load the module from. + /// The module base path to use for this module. + /// Command name prefix. + /// The session state instance to use for this module - may be null in which case a session state will be allocated if necessary. + /// Private Data for the module. + /// The set of options that are used while importing a module. + /// The manifest processing flags to use when processing the module. + /// True if a module was found. + /// True if a module file was found. + /// True if the module was successfully loaded. internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, string moduleBase, string prefix, SessionState ss, object privateData, ref ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found, out bool moduleFileFound) { Dbg.Assert(fileName != null, "Filename argument to LoadModule() shouldn't be null"); - if (!Utils.NativeFileExists(fileName)) + if (!File.Exists(fileName)) { found = false; moduleFileFound = false; 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; @@ -5743,8 +5649,29 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str { ext = Path.GetExtension(fileName); } + PSModuleInfo module = null; + // Block ps1 files from being imported in constrained language. + if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + ext.Equals(StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase)) + { + 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... if (BaseMinimumVersion != null || BaseMaximumVersion != null || BaseRequiredVersion != null || BaseGuid != null) { @@ -5758,23 +5685,31 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str // If the module is in memory and the versions don't match don't return it. // This will allow the search to continue and load a different version of the module. - if (Context.Modules.ModuleTable.TryGetValue(fileName, out module)) + if (TryGetFromModuleTable(fileName, out module)) { - if (BaseMinimumVersion != null && module.Version >= BaseMinimumVersion) + if (!ModuleIntrinsics.IsVersionMatchingConstraints(module.Version, minimumVersion: BaseMinimumVersion, maximumVersion: BaseMaximumVersion)) { - if (BaseMaximumVersion == null || module.Version <= BaseMaximumVersion) - { - found = false; - moduleFileFound = false; - return null; - } + found = false; + moduleFileFound = false; + return null; } } } found = false; string scriptName; - ExternalScriptInfo scriptInfo = null; + + // + // !!NOTE!! + // If a new module type to load is ever added and if that new module type is based on a script file, + // such as the existing .psd1 and .psm1 files, + // then be sure to include the script file LanguageMode in the moduleInfo type created for the loaded module. + // The PSModuleInfo.LanguageMode property is used to check consistency between the manifest (.psd1) file + // and all other script (.psm1) file based modules being loaded by that manifest. + // Use the PSModuleInfo class constructor that takes the PSLanguageMode parameter argument. + // Look at the LoadModuleNamedInManifest() method to see how the language mode check works. + // !!NOTE!! + // string _origModuleBeingProcessed = Context.ModuleBeingProcessed; try @@ -5803,34 +5738,73 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str } else { - scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); + var psm1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); try { - Context.Modules.IncrementModuleNestingDepth(this, scriptInfo.Path); + Context.Modules.IncrementModuleNestingDepth(this, psm1ScriptInfo.Path); // Create the module object... try { - module = Context.Modules.CreateModule(fileName, scriptInfo, MyInvocation.ScriptPosition, ss, privateData, BaseArgumentList); + module = Context.Modules.CreateModule(fileName, psm1ScriptInfo, MyInvocation.ScriptPosition, ss, privateData, BaseArgumentList); module.SetModuleBase(moduleBase); SetModuleLoggingInformation(module); // If the script didn't call Export-ModuleMember explicitly, then // implicitly export functions and cmdlets. + var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); if (!module.SessionState.Internal.UseExportList) { - ModuleIntrinsics.ExportModuleMembers(this, module.SessionState.Internal, MatchAll, - MatchAll, MatchAll, null, options.ServiceCoreAutoAdded ? ModuleCmdletBase.s_serviceCoreAssemblyCmdlets : null); - } + // For cross language boundaries don't implicitly export all functions, unless they are allowed nested modules. + // 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 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; + } - // Add it to the all module tables - if (importingModule) + ModuleIntrinsics.ExportModuleMembers( + cmdlet: this, + sessionState: module.SessionState.Internal, + functionPatterns: fnMatchPattern, + cmdletPatterns: MatchAll, + aliasPatterns: MatchAll, + variablePatterns: null, + doNotExportCmdlets: null); + } + else if ((systemLockdownPolicy == SystemEnforcementMode.Enforce || systemLockdownPolicy == SystemEnforcementMode.Audit) && + module.LanguageMode == PSLanguageMode.FullLanguage && + module.SessionState.Internal.FunctionsExportedWithWildcard && + !module.SessionState.Internal.ManifestWithExplicitFunctionExport) { - ImportModuleMembers(module, prefix, options); - AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); + // When in a constrained environment and functions are being exported from this module using wildcards, make sure + // 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. + RemoveNestedModuleFunctions(Context, module, systemLockdownPolicy); } + CheckForDisallowedDotSourcing(module, psm1ScriptInfo, options); + + // Add it to the all module tables + ImportModuleMembers(module, prefix, options); + AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); + found = true; if (BaseAsCustomObject) { @@ -5886,16 +5860,15 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str // Removing the module will not remove the commands dot-sourced from the .ps1 file. // This module info is created so that we can keep the behavior consistent between scripts imported as modules and other kind of modules(all of them should have a PSModuleInfo). // Auto-loading expects we always have a PSModuleInfo object for any module. This is how this issue was found. - module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, Context, ss); - - scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); + var ps1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); + Dbg.Assert(ps1ScriptInfo != null, "Scriptinfo for dotted file can't be null"); + module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, Context, ss, ps1ScriptInfo.DefiningLanguageMode); message = StringUtil.Format(Modules.DottingScriptFile, fileName); WriteVerbose(message); try { - Dbg.Assert(scriptInfo != null, "Scriptinfo for dotted file can't be null"); found = true; InvocationInfo oldInvocationInfo = (InvocationInfo)Context.GetVariableValue(SpecialVariables.MyInvocationVarPath); @@ -5904,9 +5877,8 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str try { - InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptInfo.ScriptBlock.Ast.Extent, - Context); - scriptInfo.ScriptBlock.InvokeWithPipe( + InvocationInfo invocationInfo = new InvocationInfo(ps1ScriptInfo, ps1ScriptInfo.ScriptBlock.Ast.Extent, Context); + ps1ScriptInfo.ScriptBlock.InvokeWithPipe( useLocalScope: false, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, @@ -5914,7 +5886,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str scriptThis: AutomationNull.Value, outputPipe: ((MshCommandRuntime)this.CommandRuntime).OutputPipe, invocationInfo: invocationInfo, - args: this.BaseArgumentList ?? Utils.EmptyArray()); + args: this.BaseArgumentList ?? Array.Empty()); } finally { @@ -5948,6 +5920,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str { e.ErrorRecord.PreserveInvocationInfoOnce = true; } + if (e.WasThrownFromThrowStatement) { ThrowTerminatingError(e.ErrorRecord); @@ -5966,11 +5939,11 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str } else if (ext.Equals(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) { - scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); + var psd1ScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); found = true; - Dbg.Assert(scriptInfo != null, "Scriptinfo for module manifest (.psd1) can't be null"); + Dbg.Assert(psd1ScriptInfo != null, "Scriptinfo for module manifest (.psd1) can't be null"); module = LoadModuleManifest( - scriptInfo, + psd1ScriptInfo, manifestProcessingFlags, BaseMinimumVersion, BaseMaximumVersion, @@ -5980,6 +5953,8 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str if (module != null) { + CheckForDisallowedDotSourcing(module, psd1ScriptInfo, options); + if (importingModule) { // Add it to all the module tables @@ -5996,12 +5971,27 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str found = false; } } - else if (ext.Equals(".dll", StringComparison.OrdinalIgnoreCase) || ext.Equals(StringLiterals.PowerShellNgenAssemblyExtension)) + else if (ext.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) || + 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 + module.LanguageMode = (PSLanguageMode?)null; + if (importingModule) { // Add it to all the module tables @@ -6022,73 +6012,6 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str } } } - else if (ext.Equals(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase)) - { -#if CORECLR // Workflow Not Supported On CSS - PSNotSupportedException workflowModuleNotSupported = - PSTraceSource.NewNotSupportedException( - Modules.WorkflowModuleNotSupportedInPowerShellCore, - ModuleIntrinsics.GetModuleName(fileName)); - throw workflowModuleNotSupported; -#else - if (Utils.IsRunningFromSysWOW64()) - { - throw new NotSupportedException(AutomationExceptions.WorkflowDoesNotSupportWOW64); - } - - scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); - - ImportModuleOptions nestedModuleOptions = new ImportModuleOptions(); - List wfToProcess = new List(); - wfToProcess.Add(fileName); - - found = true; - - if (!importingModule) - { - module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, null, null); - - ProcessWorkflowsToProcess(moduleBase, wfToProcess, new List(), new List(), null, module, nestedModuleOptions); - } - else - { - if (ss == null) - { - ss = new SessionState(Context, true, true); - } - - module = new PSModuleInfo(ModuleIntrinsics.GetModuleName(fileName), fileName, Context, ss); - ss.Internal.Module = module; - module.PrivateData = privateData; - module.SetModuleType(ModuleType.Workflow); - module.SetModuleBase(moduleBase); - - nestedModuleOptions.ServiceCoreAutoAdded = true; - - LoadServiceCoreModule(module, String.Empty, ss, nestedModuleOptions, manifestProcessingFlags, true, out found); - - ProcessWorkflowsToProcess(moduleBase, wfToProcess, new List(), new List(), ss, module, nestedModuleOptions); - - // And import the members from this module into the callers context... - if (importingModule) - { - ImportModuleMembers(module, prefix, options); - } - - // Add it to all the module tables - AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); - } - - if (BaseAsCustomObject) - { - WriteObject(module.AsCustomObject()); - } - else if (BasePassThru) - { - WriteObject(module); - } -#endif - } else if (ext.Equals(StringLiterals.PowerShellCmdletizationFileExtension, StringComparison.OrdinalIgnoreCase)) { found = true; @@ -6097,12 +6020,12 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str try { string moduleName = ModuleIntrinsics.GetModuleName(fileName); - scriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); + var cdxmlScriptInfo = GetScriptInfoForFile(fileName, out scriptName, true); try { // generate cmdletization proxies - var cmdletizationXmlReader = new StringReader(scriptInfo.ScriptContents); + var cmdletizationXmlReader = new StringReader(cdxmlScriptInfo.ScriptContents); var cmdletizationProxyModuleWriter = new StringWriter(CultureInfo.InvariantCulture); var scriptWriter = new ScriptWriter( cmdletizationXmlReader, @@ -6113,7 +6036,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str if (!importingModule) { - module = new PSModuleInfo(fileName, null, null); + module = new PSModuleInfo(null, fileName, null, null, cdxmlScriptInfo.DefiningLanguageMode); scriptWriter.PopulatePSModuleInfo(module); scriptWriter.ReportExportedCommands(module, prefix); } @@ -6173,25 +6096,102 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str message = StringUtil.Format(Modules.InvalidModuleExtension, ext, fileName); InvalidOperationException invalidOp = new InvalidOperationException(message); ErrorRecord er = new ErrorRecord(invalidOp, "Modules_InvalidModuleExtension", - ErrorCategory.PermissionDenied, null); + ErrorCategory.InvalidOperation, null); WriteError(er); } } - finally + finally + { + // Restore the name of the module being processed... + Context.ModuleBeingProcessed = _origModuleBeingProcessed; + } + + // If using AppDomain-level module path caching, add this module to the cache. This is only done for + // the modules loaded with without version info or other qualifiers. + if (PSModuleInfo.UseAppDomainLevelModuleCache && module != null && moduleBase == null && this.AddToAppDomainLevelCache) + { + // Cache using the actual name specified by the user rather than the module basename + PSModuleInfo.AddToAppDomainLevelModuleCache(module.Name, fileName, this.BaseForce); + } + + return module; + } + + private void CheckForDisallowedDotSourcing( + PSModuleInfo moduleInfo, + ExternalScriptInfo scriptInfo, + ImportModuleOptions options) + { + 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 = 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. + 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 => + { + var cmdAst = ast as CommandAst; + return (cmdAst?.InvocationOperator == TokenKind.Dot); + }, + searchNestedScriptBlocks: true).FirstOrDefault(); + + if (dotSourceOperator != null) + { + 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) { - // Restore the name of the module being processed... - Context.ModuleBeingProcessed = _origModuleBeingProcessed; + return; } - // If using AppDomain-level module path caching, add this module to the cache. This is only done for - // the modules loaded with without version info or other qualifiers. - if (PSModuleInfo.UseAppDomainLevelModuleCache && module != null && moduleBase == null && this.AddToAppDomainLevelCache) + if (systemLockdownPolicy != SystemEnforcementMode.Audit) { - // Cache using the actual name specified by the user rather than the module basename - PSModuleInfo.AddToAppDomainLevelModuleCache(module.Name, fileName, this.BaseForce); + input.RemoveAll(fnInfo => !module.Name.Equals(fnInfo.ModuleName, StringComparison.OrdinalIgnoreCase)); + return; } - return module; + 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) @@ -6200,7 +6200,6 @@ private static bool ShouldProcessScriptModule(PSModuleInfo parentModule, ref boo // If we are in module analysis and the parent module declares non-wildcarded exports, then we don't need to // actually process the script module. - if (parentModule != null) { if (shouldProcessModule && (parentModule.DeclaredFunctionExports != null) && (parentModule.DeclaredFunctionExports.Count > 0)) @@ -6223,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) { @@ -6235,12 +6234,11 @@ 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 + /// Analyze the module assembly to find out all cmdlets and aliases defined in that assembly. /// /// /// In CoreCLR, there is only one AppDomain, so we cannot spin up a new AppDomain to load the assembly and do analysis there. @@ -6278,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) { @@ -6464,18 +6313,17 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon { module.AddDetectedAliasExport(commandName, null); } - if ((commandType & CommandTypes.Workflow) == CommandTypes.Workflow) - { - module.AddDetectedWorkflowExport(commandName); - } + if ((commandType & CommandTypes.Function) == CommandTypes.Function) { module.AddDetectedFunctionExport(commandName); } + if ((commandType & CommandTypes.Cmdlet) == CommandTypes.Cmdlet) { module.AddDetectedCmdletExport(commandName); } + if ((commandType & CommandTypes.Configuration) == CommandTypes.Configuration) { module.AddDetectedFunctionExport(commandName); @@ -6510,7 +6358,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon { if (SessionStateUtilities.MatchesAnyWildcardPattern(command, scriptAnalysisPatterns, true)) { - if (!HasInvalidCharacters(command.Replace("-", ""))) + if (!HasInvalidCharacters(command.Replace("-", string.Empty))) { module.AddDetectedFunctionExport(command); } @@ -6522,7 +6370,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon { var commandName = pair.Key; // These are already filtered - if (!HasInvalidCharacters(commandName.Replace("-", ""))) + if (!HasInvalidCharacters(commandName.Replace("-", string.Empty))) { module.AddDetectedAliasExport(commandName, pair.Value); } @@ -6538,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)); } @@ -6557,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))) { @@ -6594,7 +6442,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon if (SessionStateUtilities.MatchesAnyWildcardPattern(commandName, patterns, true) && SessionStateUtilities.MatchesAnyWildcardPattern(commandName, scriptAnalysisPatterns, true)) { - if (!HasInvalidCharacters(commandName.Replace("-", ""))) + if (!HasInvalidCharacters(commandName.Replace("-", string.Empty))) { module.AddDetectedFunctionExport(commandName); } @@ -6606,7 +6454,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon if (SessionStateUtilities.MatchesAnyWildcardPattern(commandName, patterns, true) && SessionStateUtilities.MatchesAnyWildcardPattern(commandName, scriptAnalysisPatterns, true)) { - if (!HasInvalidCharacters(commandName.Replace("-", ""))) + if (!HasInvalidCharacters(commandName.Replace("-", string.Empty))) { module.AddDetectedCmdletExport(commandName); } @@ -6646,9 +6494,9 @@ 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 path to the assembly to load. /// The assembly to load so no lookup need be done. /// The module base to use for this module. /// @@ -6656,29 +6504,44 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon /// instance, however when loaded through a module manifest with nested modules, it will have a session /// state instance to store the imported functions, aliases and variables. /// - /// 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 + /// The set of options that are used while importing a module. + /// The manifest processing flags to use when processing the module. + /// 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 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 path to the assembly to load. /// The assembly to load so no lookup need be done. /// The module base to use for this module. /// @@ -6686,50 +6549,53 @@ internal PSModuleInfo LoadBinaryModule(bool trySnapInName, string moduleName, st /// instance, however when loaded through a module manifest with nested modules, it will have a session /// state instance to store the imported functions, aliases and variables. /// - /// 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 + /// The set of options that are used while importing a module. + /// The manifest processing flags to use when processing the module. + /// Command name prefix. /// 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) + 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) @@ -6737,193 +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; - - // 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 + assemblyVersion = GetAssemblyVersionNumber(assembly); + modulePath = string.IsNullOrEmpty(fileName) ? assembly.Location : fileName; - 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... @@ -6942,6 +6688,7 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn { aliasEntry = commandEntry as SessionStateAliasEntry; } + Dbg.Assert((cmdletEntry != null || aliasEntry != null), "When importing a binary module, the commands entry should only have cmdlets/aliases in it"); if (ss != null) { @@ -7028,22 +6775,15 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn { Context.EngineSessionState = ss.Internal; } + if (disableFormatUpdates) iss.DisableFormatUpdates = true; // Load the cmdlets and providers, bound to the new module... - iss.Bind(Context, /*updateOnly*/ true, module, options.NoClobber, options.Local); + 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... @@ -7088,20 +6828,13 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn WriteError(cnfe.ErrorRecord); } - // WIN8: 561104 Importing PSWorkflow module tells wrongly that their is a cmdlet Import-PSWorkflow, when there is none - // Skipping the verbose message when cmdlet name is import-psworkflow - // - if (!String.Equals(ssce.Name, "import-psworkflow", StringComparison.OrdinalIgnoreCase)) - { - string message = StringUtil.Format(ssce.CommandType == CommandTypes.Alias ? Modules.ImportingAlias : Modules.ImportingCmdlet, ssce.Name); - - WriteVerbose(message); - } + string message = StringUtil.Format(ssce.CommandType == CommandTypes.Alias ? Modules.ImportingAlias : Modules.ImportingCmdlet, ssce.Name); + WriteVerbose(message); } else { // The verbose output for Import-Module -NoClobber should state which members were skipped due to conflicts with existing names in the caller's environment. - String message = StringUtil.Format(Modules.ImportModuleNoClobberForCmdlet, ssce.Name); + string message = StringUtil.Format(Modules.ImportModuleNoClobberForCmdlet, ssce.Name); WriteVerbose(message); } } @@ -7112,6 +6845,7 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn { AddModuleToModuleTables(this.Context, this.TargetSessionState.Internal, module); } + return module; } @@ -7129,6 +6863,7 @@ private static Version GetAssemblyVersionNumber(Assembly assemblyToLoad) { assemblyVersion = new Version(0, 0); } + return assemblyVersion; } @@ -7149,11 +6884,12 @@ internal static string AddPrefixToCommandName(string commandName, string prefix) { commandName = prefix + commandName; } + return commandName; } /// - /// Removes prefix from a command name and returns the command name + /// Removes prefix from a command name and returns the command name. /// /// The command name from which the prefix needs to be removed. /// The string containing the prefix. @@ -7192,7 +6928,7 @@ internal static string RemovePrefixFromCommandName(string commandName, string pr internal static bool IsPrefixedCommand(CommandInfo commandInfo) { Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo is not null"); - Dbg.Assert(!String.IsNullOrEmpty(commandInfo.Prefix), "Caller should verify that the commandInfo has prefix"); + Dbg.Assert(!string.IsNullOrEmpty(commandInfo.Prefix), "Caller should verify that the commandInfo has prefix"); string verb, noun; bool isPrefixed = CmdletInfo.SplitCmdletName(commandInfo.Name, out verb, out noun) @@ -7210,7 +6946,7 @@ internal static void AddModuleToModuleTables(ExecutionContext context, SessionSt // if the module path is empty (assembly module in memory), we add the modulename as key string moduleTableKey; - if (module.Path != "") + if (module.Path != string.Empty) { moduleTableKey = module.Path; } @@ -7218,14 +6954,17 @@ internal static void AddModuleToModuleTables(ExecutionContext context, SessionSt { moduleTableKey = module.Name; } + if (!context.Modules.ModuleTable.ContainsKey(moduleTableKey)) { context.Modules.ModuleTable.Add(moduleTableKey, module); } + if (context.previousModuleImported.ContainsKey(module.Name)) { context.previousModuleImported.Remove(module.Name); } + context.previousModuleImported.Add(module.Name, module.Path); if (!targetSessionState.ModuleTable.ContainsKey(moduleTableKey)) @@ -7234,18 +6973,15 @@ internal static void AddModuleToModuleTables(ExecutionContext context, SessionSt targetSessionState.ModuleTableKeys.Add(moduleTableKey); } - if (targetSessionState.Module != null) - { - targetSessionState.Module.AddNestedModule(module); - } + targetSessionState.Module?.AddNestedModule(module); } /// /// Import the script-level functions from one session state to another, calling /// WriteVerbose for each imported member... /// - /// The session state instance to use as the source of the functions - /// Command name prefix + /// The session state instance to use as the source of the functions. + /// Command name prefix. protected internal void ImportModuleMembers(PSModuleInfo sourceModule, string prefix) { ImportModuleOptions importModuleOptions = new ImportModuleOptions(); @@ -7265,9 +7001,9 @@ protected internal void ImportModuleMembers(PSModuleInfo sourceModule, string pr /// Import the script-level functions from one session state to another, calling /// WriteVerbose for each imported member... /// - /// The session state instance to use as the source of the functions - /// Command name prefix - /// The set of options that are used while importing a module + /// The session state instance to use as the source of the functions. + /// Command name prefix. + /// The set of options that are used while importing a module. protected internal void ImportModuleMembers(PSModuleInfo sourceModule, string prefix, ImportModuleOptions options) { ImportModuleMembers( @@ -7294,7 +7030,7 @@ internal static void ImportModuleMembers( ImportModuleOptions options) { if (sourceModule == null) - throw PSTraceSource.NewArgumentNullException("sourceModule"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceModule)); bool isImportModulePrivate = cmdlet.CommandInfo.Visibility == SessionStateEntryVisibility.Private || targetSessionState.DefaultCommandVisibility == SessionStateEntryVisibility.Private; @@ -7316,6 +7052,7 @@ internal static void ImportModuleMembers( if (m.Path.Equals(sourceModule.Path, StringComparison.OrdinalIgnoreCase)) present = true; } + if (!present) { targetSessionState.Module.AddNestedModule(sourceModule); @@ -7350,6 +7087,7 @@ internal static void ImportModuleMembers( cmdlet.WriteVerbose(message); continue; } + CmdletInfo prefixedCmdlet = new CmdletInfo( AddPrefixToCommandName(cmdletToImport.Name, prefix), cmdletToImport.ImplementingType, @@ -7430,12 +7168,7 @@ internal static void ImportModuleMembers( { foreach (FunctionInfo func in sourceModule.ExportedFunctions.Values) { - ImportFunctionsOrWorkflows(func, targetSessionState, sourceModule, functionPatterns, noPatternsSpecified, prefix, options, usePrefix, ref checkVerb, ref checkNoun, original2prefixedName, cmdlet, isImportModulePrivate, isFunction: true); - } - - foreach (FunctionInfo func in sourceModule.ExportedWorkflows.Values) - { - ImportFunctionsOrWorkflows(func, targetSessionState, sourceModule, functionPatterns, noPatternsSpecified, prefix, options, usePrefix, ref checkVerb, ref checkNoun, original2prefixedName, cmdlet, isImportModulePrivate, isFunction: false); + ImportFunctions(func, targetSessionState, sourceModule, functionPatterns, noPatternsSpecified, prefix, options, usePrefix, ref checkVerb, ref checkNoun, original2prefixedName, cmdlet, isImportModulePrivate, isFunction: true); } // Import any exported variables... @@ -7516,7 +7249,7 @@ internal static void ImportModuleMembers( } } - private static void ImportFunctionsOrWorkflows(FunctionInfo func, SessionStateInternal targetSessionState, PSModuleInfo sourceModule, List functionPatterns, bool noPatternsSpecified, + private static void ImportFunctions(FunctionInfo func, SessionStateInternal targetSessionState, PSModuleInfo sourceModule, List functionPatterns, bool noPatternsSpecified, string prefix, ImportModuleOptions options, bool usePrefix, ref bool checkVerb, ref bool checkNoun, Dictionary original2prefixedName, ModuleCmdletBase cmdlet, bool isImportModulePrivate, bool isFunction) { string message = null; @@ -7542,6 +7275,9 @@ private static void ImportFunctionsOrWorkflows(FunctionInfo func, SessionStateIn 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; @@ -7554,20 +7290,11 @@ private static void ImportFunctionsOrWorkflows(FunctionInfo func, SessionStateIn } ValidateCommandName(cmdlet, functionInfo.Name, sourceModule.Name, ref checkNoun, ref checkVerb); - - if (func.CommandType == CommandTypes.Workflow) - { - message = StringUtil.Format(Modules.ImportingWorkflow, prefixedName); - } - else - { - message = StringUtil.Format(Modules.ImportingFunction, prefixedName); - } + message = StringUtil.Format(Modules.ImportingFunction, prefixedName); cmdlet.WriteVerbose(message); } } - private static void SetCommandVisibility(bool isImportModulePrivate, CommandInfo command) { if (isImportModulePrivate) @@ -7593,6 +7320,7 @@ internal static bool CommandFound(string commandName, SessionStateInternal sessi { return false; } + return true; } finally @@ -7668,6 +7396,7 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet, message = StringUtil.Format(Modules.ImportingNonStandardVerb, moduleName); cmdlet.WriteWarning(message); } + string[] alternates = Verbs.SuggestedAlternates(verb); if (alternates == null) { @@ -7691,6 +7420,7 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet, cmdlet.WriteWarning(message); checkNoun = false; } + message = StringUtil.Format(Modules.ImportingNonStandardNounVerbose, commandName, moduleName); cmdlet.WriteVerbose(message); return; @@ -7698,42 +7428,67 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet, } /// - /// Search a PSSnapin with the specified name + /// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while + /// exporting all functions by default. + /// + /// This protects cached trusted modules that exported all functions in a trusted context, from being re-used + /// in an untrusted context and thus exposing functions that were meant to be private in that context. + /// + /// Returning false forces module import to re-import the module from file with the current context and prevent + /// all module functions from being exported by default. + /// + /// 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 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 + /// $mParent = import-module ParentModule # This internally imports SubModule + /// $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 # Explicitly exports functions, useful + /// $mParent = import-module ParentModule # This internally imports SubModule + /// $mParent.DoSomething # This works because SubModule functions are exported and accessible. /// - internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string name) + /// Key. + /// PSModuleInfo. + /// True if module item is to be removed. + /// True if module found in table and is safe to use. + internal bool TryGetFromModuleTable(string key, out PSModuleInfo moduleInfo, bool toRemove = false) { - 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; + var foundModule = Context.Modules.ModuleTable.TryGetValue(key, out moduleInfo); + + // Check for unsafe language modes between module load context and current context. + // But only for script modules that exported all functions in a trusted (FL) context. + if (foundModule && + !toRemove && + moduleInfo.ModuleType == ModuleType.Script && + Context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + moduleInfo.LanguageMode == PSLanguageMode.FullLanguage && + moduleInfo.ModuleAutoExportsAllFunctions) + { + moduleInfo = null; + return false; } - return null; + return foundModule; } - } // end ModuleCmdletBase + } /// - /// Holds the result of a binary module analysis + /// Holds the result of a binary module analysis. /// internal class BinaryAnalysisResult { /// - /// The list of cmdlets detected from the binary + /// The list of cmdlets detected from the binary. /// internal List DetectedCmdlets { get; set; } - // The list of aliases detected from the binary + // The list of aliases detected from the binary. internal List> DetectedAliases { get; set; } } #endregion ModuleCmdletBase -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 1a71b3b619c..538c4775f0a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -1,16 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; using System.Management.Automation.Language; -using Microsoft.PowerShell.Commands; -using System.Linq; using System.Text; using System.Threading; +using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -26,22 +27,32 @@ internal static class Constants public class ModuleIntrinsics { /// - /// Tracer for module analysis + /// Tracer for module analysis. /// [TraceSource("Modules", "Module loading and analysis")] - internal static PSTraceSource Tracer = PSTraceSource.GetTracer("Modules", "Module loading and analysis"); + internal static readonly PSTraceSource Tracer = PSTraceSource.GetTracer("Modules", "Module loading and analysis"); + + // The %WINDIR%\System32\WindowsPowerShell\v1.0\Modules module path, + // to load forward compatible Windows PowerShell modules from + 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; @@ -57,6 +68,7 @@ internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path) cmdlet.ThrowTerminatingError(er); } } + internal void DecrementModuleNestingCount() { --ModuleNestingDepth; @@ -65,34 +77,34 @@ internal void DecrementModuleNestingCount() internal int ModuleNestingDepth { get; private set; } /// - /// Create a new module object from a scriptblock specifying the path to set for the module + /// Create a new module object from a scriptblock specifying the path to set for the module. /// - /// The name of the module - /// The path where the module is rooted + /// The name of the module. + /// The path where the module is rooted. /// /// ScriptBlock that is executed to initialize the module... /// /// /// The arguments to pass to the scriptblock used to initialize the module /// - /// The session state instance to use for this module - may be null - /// The results produced from evaluating the scriptblock - /// The newly created module info object + /// The session state instance to use for this module - may be null. + /// The results produced from evaluating the scriptblock. + /// The newly created module info object. internal PSModuleInfo CreateModule(string name, string path, ScriptBlock scriptBlock, SessionState ss, out List results, params object[] arguments) { return CreateModuleImplementation(name, path, scriptBlock, null, ss, null, out results, arguments); } /// - /// Create a new module object from a ScriptInfo object + /// Create a new module object from a ScriptInfo object. /// - /// The path where the module is rooted - /// The script info to use to create the module - /// The position for the command that loaded this module - /// Optional arguments to pass to the script while executing - /// The session state instance to use for this module - may be null - /// The private data to use for this module - may be null - /// The constructed module object + /// The path where the module is rooted. + /// The script info to use to create the module. + /// The position for the command that loaded this module. + /// Optional arguments to pass to the script while executing. + /// The session state instance to use for this module - may be null. + /// The private data to use for this module - may be null. + /// The constructed module object. internal PSModuleInfo CreateModule(string path, ExternalScriptInfo scriptInfo, IScriptExtent scriptPosition, SessionState ss, object privateData, params object[] arguments) { List result; @@ -100,10 +112,10 @@ internal PSModuleInfo CreateModule(string path, ExternalScriptInfo scriptInfo, I } /// - /// Create a new module object from code specifying the path to set for the module + /// Create a new module object from code specifying the path to set for the module. /// - /// The name of the module - /// The path to use for the module root + /// The name of the module. + /// The path to use for the module root. /// /// The code to use to create the module. This can be one of ScriptBlock, string /// or ExternalScriptInfo @@ -118,9 +130,9 @@ internal PSModuleInfo CreateModule(string path, ExternalScriptInfo scriptInfo, I /// The position of the caller of this function so you can tell where the call /// to Import-Module (or whatever) occurred. This can be null. /// - /// The session state instance to use for this module - may be null - /// The private data to use for this module - may be null - /// The created module + /// The session state instance to use for this module - may be null. + /// The private data to use for this module - may be null. + /// The created module. private PSModuleInfo CreateModuleImplementation(string name, string path, object moduleCode, IScriptExtent scriptPosition, SessionState ss, object privateData, out List result, params object[] arguments) { ScriptBlock sb; @@ -131,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; @@ -169,17 +178,17 @@ 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); } } + if (sb == null) throw PSTraceSource.NewInvalidOperationException(); sb.SessionStateInternal = ss.Internal; + module.LanguageMode = sb.LanguageMode; InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptPosition); @@ -210,13 +219,14 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object scriptThis: AutomationNull.Value, outputPipe: outputPipe, invocationInfo: invocationInfo, - args: arguments ?? Utils.EmptyArray()); + args: arguments ?? Array.Empty()); } catch (ExitException ee) { exitCode = (int)ee.Argument; setExitCode = true; } + result = resultList; } finally @@ -241,9 +251,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object /// bound to the module instance. /// /// Context to use to create bounded script. - /// The scriptblock to bind - /// Whether it should be linked to the global session state or not - /// A new scriptblock + /// The scriptblock to bind. + /// Whether it should be linked to the global session state or not. + /// A new scriptblock. internal ScriptBlock CreateBoundScriptBlock(ExecutionContext context, ScriptBlock sb, bool linkToGlobal) { PSModuleInfo module = new PSModuleInfo(context, linkToGlobal); @@ -257,7 +267,8 @@ 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); } @@ -273,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) { @@ -315,6 +323,7 @@ private List GetModuleCore(string[] patterns, bool all, bool exact found[path] = true; } } + if (_context.EngineSessionState != _context.TopLevelSessionState) { foreach (var pair in _context.TopLevelSessionState.ModuleTable) @@ -334,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) @@ -393,125 +402,539 @@ internal List GetModules(ModuleSpecification[] fullyQualifiedName, } } - return modulesMatched.OrderBy(m => m.Name).ToList(); + return modulesMatched.OrderBy(static m => m.Name).ToList(); + } + + /// + /// Check if a given module info object matches a given module specification. + /// + /// The module info object to check. + /// The module specification to match the module info object against. + /// True if we should skip the name check on the module specification. + /// True if the module info object meets all the constraints on the module specification, false otherwise. + internal static bool IsModuleMatchingModuleSpec( + PSModuleInfo moduleInfo, + ModuleSpecification moduleSpec, + bool skipNameCheck = false) + { + return IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, moduleInfo, moduleSpec, skipNameCheck); + } + + /// + /// Check if a given module info object matches a given module specification. + /// + /// The constraint that caused the match failure, if any. + /// The module info object to check. + /// The module specification to match the module info object against. + /// True if we should skip the name check on the module specification. + /// True if the module info object meets all the constraints on the module specification, false otherwise. + internal static bool IsModuleMatchingModuleSpec( + out ModuleMatchFailure matchFailureReason, + PSModuleInfo moduleInfo, + ModuleSpecification moduleSpec, + bool skipNameCheck = false) + { + if (moduleSpec == null) + { + matchFailureReason = ModuleMatchFailure.NullModuleSpecification; + return false; + } + + return IsModuleMatchingConstraints( + out matchFailureReason, + moduleInfo, + skipNameCheck ? null : moduleSpec.Name, + moduleSpec.Guid, + moduleSpec.RequiredVersion, + moduleSpec.Version, + moduleSpec.MaximumVersion == null ? null : ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion)); + } + + /// + /// Check if a given module info object matches the given constraints. + /// Constraints given as null are ignored. + /// + /// The module info object to check. + /// The name or normalized absolute path of the expected module. + /// The guid of the expected module. + /// The required version of the expected module. + /// The minimum required version of the expected module. + /// The maximum required version of the expected module. + /// True if the module info object matches all given constraints, false otherwise. + internal static bool IsModuleMatchingConstraints( + PSModuleInfo moduleInfo, + string name = null, + Guid? guid = null, + Version requiredVersion = null, + Version minimumVersion = null, + Version maximumVersion = null) + { + return IsModuleMatchingConstraints( + out ModuleMatchFailure matchFailureReason, + moduleInfo, + name, + guid, + requiredVersion, + minimumVersion, + maximumVersion); + } + + /// + /// Check if a given module info object matches the given constraints. + /// Constraints given as null are ignored. + /// + /// The reason for the module constraint match failing. + /// The module info object to check. + /// The name or normalized absolute path of the expected module. + /// The guid of the expected module. + /// The required version of the expected module. + /// The minimum required version of the expected module. + /// The maximum required version of the expected module. + /// True if the module info object matches all given constraints, false otherwise. + internal static bool IsModuleMatchingConstraints( + out ModuleMatchFailure matchFailureReason, + PSModuleInfo moduleInfo, + string name, + Guid? guid, + Version requiredVersion, + Version minimumVersion, + Version maximumVersion) + { + // Define that a null module does not meet any constraints + if (moduleInfo == null) + { + matchFailureReason = ModuleMatchFailure.NullModule; + return false; + } + + return AreModuleFieldsMatchingConstraints( + out matchFailureReason, + moduleInfo.Name, + moduleInfo.Path, + moduleInfo.Guid, + moduleInfo.Version, + name, + guid, + requiredVersion, + minimumVersion, + maximumVersion + ); + } + + /// + /// Check that given module fields meet any given constraints. + /// + /// The name of the module to check. + /// The path of the module to check. + /// The GUID of the module to check. + /// The version of the module to check. + /// The name or normalized absolute path the module must have, if any. + /// The GUID the module must have, if any. + /// The exact version the module must have, if any. + /// The minimum version the module may have, if any. + /// The maximum version the module may have, if any. + /// True if the module parameters match all given constraints, false otherwise. + internal static bool AreModuleFieldsMatchingConstraints( + string moduleName = null, + string modulePath = null, + Guid? moduleGuid = null, + Version moduleVersion = null, + string requiredName = null, + Guid? requiredGuid = null, + Version requiredVersion = null, + Version minimumRequiredVersion = null, + Version maximumRequiredVersion = null) + { + return AreModuleFieldsMatchingConstraints( + out ModuleMatchFailure matchFailureReason, + moduleName, + modulePath, + moduleGuid, + moduleVersion, + requiredName, + requiredGuid, + requiredVersion, + minimumRequiredVersion, + maximumRequiredVersion); + } + + /// + /// Check that given module fields meet any given constraints. + /// + /// The reason the match failed, if any. + /// The name of the module to check. + /// The path of the module to check. + /// The GUID of the module to check. + /// The version of the module to check. + /// The name or normalized absolute path the module must have, if any. + /// The GUID the module must have, if any. + /// The exact version the module must have, if any. + /// The minimum version the module may have, if any. + /// The maximum version the module may have, if any. + /// True if the module parameters match all given constraints, false otherwise. + internal static bool AreModuleFieldsMatchingConstraints( + out ModuleMatchFailure matchFailureReason, + string moduleName, + string modulePath, + Guid? moduleGuid, + Version moduleVersion, + string requiredName, + Guid? requiredGuid, + Version requiredVersion, + Version minimumRequiredVersion, + Version maximumRequiredVersion) + { + // If a name is required, check that it matches. + // A required module name may also be an absolute path, so check it against the given module's path as well. + if (requiredName != null + && !requiredName.Equals(moduleName, StringComparison.OrdinalIgnoreCase) + && !MatchesModulePath(modulePath, requiredName)) + { + matchFailureReason = ModuleMatchFailure.Name; + return false; + } + + // If a GUID is required, check it matches + if (requiredGuid != null && !requiredGuid.Equals(moduleGuid)) + { + matchFailureReason = ModuleMatchFailure.Guid; + return false; + } + + // Check the versions + return IsVersionMatchingConstraints(out matchFailureReason, moduleVersion, requiredVersion, minimumRequiredVersion, maximumRequiredVersion); + } + + /// + /// Check that a given module version matches the required or minimum/maximum version constraints. + /// Null constraints are not checked. + /// + /// The module version to check. Must not be null. + /// The version that the given version must be, if not null. + /// The minimum version that the given version must be greater than or equal to, if not null. + /// The maximum version that the given version must be less then or equal to, if not null. + /// + /// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise. + /// + internal static bool IsVersionMatchingConstraints( + Version version, + Version requiredVersion = null, + Version minimumVersion = null, + Version maximumVersion = null) + { + return IsVersionMatchingConstraints(out ModuleMatchFailure matchFailureReason, version, requiredVersion, minimumVersion, maximumVersion); + } + + /// + /// Check that a given module version matches the required or minimum/maximum version constraints. + /// Null constraints are not checked. + /// + /// The reason why the match failed. + /// The module version to check. Must not be null. + /// The version that the given version must be, if not null. + /// The minimum version that the given version must be greater than or equal to, if not null. + /// The maximum version that the given version must be less then or equal to, if not null. + /// + /// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise. + /// + internal static bool IsVersionMatchingConstraints( + out ModuleMatchFailure matchFailureReason, + Version version, + Version requiredVersion = null, + Version minimumVersion = null, + Version maximumVersion = null) + { + Dbg.Assert(version != null, $"Caller to verify that {nameof(version)} is not null"); + + // If a RequiredVersion is given it overrides other version settings + if (requiredVersion != null) + { + matchFailureReason = ModuleMatchFailure.RequiredVersion; + return requiredVersion.Equals(version); + } + + // Check the version is at least the minimum version + if (minimumVersion != null && version < minimumVersion) + { + matchFailureReason = ModuleMatchFailure.MinimumVersion; + return false; + } + + // Check the version is at most the maximum version + if (maximumVersion != null && version > maximumVersion) + { + matchFailureReason = ModuleMatchFailure.MaximumVersion; + return false; + } + + matchFailureReason = ModuleMatchFailure.None; + return true; } - internal static bool IsModuleMatchingModuleSpec(PSModuleInfo moduleInfo, ModuleSpecification moduleSpec) + /// + /// Checks whether a given module path is the same as + /// a required path. + /// + /// The path of the module whose path to check. This must be the path to the module file (.psd1, .psm1, .dll, etc). + /// The path of the required module. This may be the module directory path or the file path. Only normalized absolute paths will work for this. + /// True if the module path matches the required path, false otherwise. + internal static bool MatchesModulePath(string modulePath, string requiredPath) { - if (moduleInfo != null && moduleSpec != null && - moduleInfo.Name.Equals(moduleSpec.Name, StringComparison.OrdinalIgnoreCase) && - (!moduleSpec.Guid.HasValue || moduleSpec.Guid.Equals(moduleInfo.Guid)) && - ((moduleSpec.Version == null && moduleSpec.RequiredVersion == null && moduleSpec.MaximumVersion == null) - || (moduleSpec.RequiredVersion != null && moduleSpec.RequiredVersion.Equals(moduleInfo.Version)) - || (moduleSpec.MaximumVersion == null && moduleSpec.Version != null && moduleSpec.RequiredVersion == null && moduleSpec.Version <= moduleInfo.Version) - || (moduleSpec.MaximumVersion != null && moduleSpec.Version == null && moduleSpec.RequiredVersion == null && ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion) >= moduleInfo.Version) - || (moduleSpec.MaximumVersion != null && moduleSpec.Version != null && moduleSpec.RequiredVersion == null && ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion) >= moduleInfo.Version && moduleSpec.Version <= moduleInfo.Version))) + Dbg.Assert(requiredPath != null, $"Caller to verify that {nameof(requiredPath)} is not null"); + + if (modulePath == null) + { + return false; + } + +#if UNIX + const StringComparison strcmp = StringComparison.Ordinal; +#else + const StringComparison strcmp = StringComparison.OrdinalIgnoreCase; +#endif + + // We must check modulePath (e.g. /path/to/module/module.psd1) against several possibilities: + // 1. "/path/to/module" - Module dir path + // 2. "/path/to/module/module.psd1" - Module root file path + // 3. "/path/to/module/2.1/module.psd1" - Versioned module path + + // If the required module just matches the module path (case 1), we are done + if (modulePath.Equals(requiredPath, strcmp)) { return true; } - return false; + // At this point we are looking for the module directory (case 2 or 3). + // We can some allocations here if module path doesn't sit under the required path + // (the required path may still refer to some nested module though) + if (!modulePath.StartsWith(requiredPath, strcmp)) + { + return false; + } + + string moduleDirPath = Path.GetDirectoryName(modulePath); + + // The module itself may be in a versioned directory (case 3) + if (Version.TryParse(Path.GetFileName(moduleDirPath), out _)) + { + moduleDirPath = Path.GetDirectoryName(moduleDirPath); + } + + return moduleDirPath.Equals(requiredPath, strcmp); + } + + /// + /// Takes the name of a module as used in a module specification + /// and either returns it as a simple name (if it was a simple name) + /// or a fully qualified, PowerShell-resolved path. + /// + /// The name or path of the module from the specification. + /// The path to base relative paths off. + /// The current execution context. + /// + /// The simple module name if the given one was simple, + /// otherwise a fully resolved, absolute path to the module. + /// + /// + /// 2018-11-09 rjmholt: + /// There are several, possibly inconsistent, path handling mechanisms + /// in the module cmdlets. After looking through all of them and seeing + /// they all make some assumptions about their caller I wrote this method. + /// Hopefully we can find a standard path resolution API to settle on. + /// + internal static string NormalizeModuleName( + string moduleName, + string basePath, + ExecutionContext executionContext) + { + if (moduleName == null) + { + return null; + } + + // Check whether the module is a path -- if not, it is a simple name and we just return it. + if (!IsModuleNamePath(moduleName)) + { + return moduleName; + } + + // Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows + moduleName = moduleName.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + + // Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true + if (!Path.IsPathRooted(moduleName)) + { + moduleName = Path.Join(basePath, moduleName); + } + + // Use the PowerShell filesystem provider to fully resolve the path + // If there is a problem, null could be returned -- so default back to the pre-normalized path + string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleName, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator); + + // ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed. + // If it does, we return the fully qualified path generated before. + return normalizedPath ?? Path.GetFullPath(moduleName); + } + + /// + /// Check if a given module name is a path to a module rather than a simple name. + /// + /// The module name to check. + /// True if the module name is a path, false otherwise. + internal static bool IsModuleNamePath(string moduleName) + { + return moduleName.Contains(StringLiterals.DefaultPathSeparator) + || moduleName.Contains(StringLiterals.AlternatePathSeparator) + || moduleName.Equals("..") + || moduleName.Equals("."); } internal static Version GetManifestModuleVersion(string manifestPath) { - if (manifestPath != null && - manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + try { - try - { - var dataFileSetting = - PsUtils.GetModuleManifestProperties( - manifestPath, - PsUtils.ManifestModuleVersionPropertyName); + Hashtable dataFileSetting = + PsUtils.GetModuleManifestProperties( + manifestPath, + PsUtils.ManifestModuleVersionPropertyName); - var versionValue = dataFileSetting["ModuleVersion"]; - if (versionValue != null) + object versionValue = dataFileSetting["ModuleVersion"]; + if (versionValue != null) + { + Version moduleVersion; + if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion)) { - Version moduleVersion; - if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion)) - { - return moduleVersion; - } + return moduleVersion; } } - catch (PSInvalidOperationException) - { - } } + catch (PSInvalidOperationException) { } return new Version(0, 0); } internal static Guid GetManifestGuid(string manifestPath) { - if (manifestPath != null && - manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + try { - try + Hashtable dataFileSetting = + PsUtils.GetModuleManifestProperties( + manifestPath, + PsUtils.ManifestGuidPropertyName); + + object guidValue = dataFileSetting["GUID"]; + if (guidValue != null) { - var dataFileSetting = - PsUtils.GetModuleManifestProperties( - manifestPath, - PsUtils.ManifestGuidPropertyName); + Guid guidID; + if (LanguagePrimitives.TryConvertTo(guidValue, out guidID)) + { + return guidID; + } + } + } + catch (PSInvalidOperationException) { } + + return new Guid(); + } - var guidValue = dataFileSetting["GUID"]; - if (guidValue != null) + internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath) + { + try + { + Hashtable dataFileSetting = + PsUtils.GetModuleManifestProperties( + manifestPath, + PsUtils.ManifestPrivateDataPropertyName); + + object privateData = dataFileSetting["PrivateData"]; + if (privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData) + { + object expFeatureValue = psData["ExperimentalFeatures"]; + if (expFeatureValue != null && + LanguagePrimitives.TryConvertTo(expFeatureValue, out Hashtable[] features) && + features.Length > 0) { - Guid guidID; - if (LanguagePrimitives.TryConvertTo(guidValue, out guidID)) + string moduleName = ModuleIntrinsics.GetModuleName(manifestPath); + var expFeatureList = new List(); + foreach (Hashtable feature in features) { - return guidID; + string featureName = feature["Name"] as string; + if (string.IsNullOrEmpty(featureName)) + { + continue; + } + + if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName)) + { + string featureDescription = feature["Description"] as string; + expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, manifestPath, + ExperimentalFeature.IsEnabled(featureName))); + } } + + return expFeatureList.ToArray(); } } - catch (PSInvalidOperationException) - { - } } + catch (PSInvalidOperationException) { } - return new Guid(); + return Array.Empty(); } // 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 string[] PSModuleProcessableExtensions = new string[] { - StringLiterals.PowerShellDataFileExtension, - StringLiterals.PowerShellScriptFileExtension, - StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellCmdletizationFileExtension, - StringLiterals.WorkflowFileExtension, - StringLiterals.PowerShellNgenAssemblyExtension, - StringLiterals.PowerShellILAssemblyExtension}; + 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 string[] PSModuleExtensions = new string[] { - StringLiterals.PowerShellDataFileExtension, - StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellCmdletizationFileExtension, - StringLiterals.WorkflowFileExtension, - StringLiterals.PowerShellNgenAssemblyExtension, - StringLiterals.PowerShellILAssemblyExtension}; + 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... /// - /// The extension to check + /// The extension to check. /// True if it was a module extension... internal static bool IsPowerShellModuleExtension(string extension) { foreach (string ext in PSModuleProcessableExtensions) { if (extension.Equals(ext, StringComparison.OrdinalIgnoreCase)) + { return true; + } } + return false; } /// /// Gets the module name from module path. /// - /// The path to the module - /// The module name + /// The path to the module. + /// The module name. internal static string GetModuleName(string path) { string fileName = path == null ? string.Empty : Path.GetFileName(path); @@ -524,6 +947,7 @@ internal static string GetModuleName(string path) { ext = Path.GetExtension(fileName); } + if (!string.IsNullOrEmpty(ext) && IsPowerShellModuleExtension(ext)) { return fileName.Substring(0, fileName.Length - ext.Length); @@ -535,46 +959,50 @@ internal static string GetModuleName(string path) } /// - /// Gets the personal module path + /// Gets the personal module path. /// - /// personal module path + /// Personal module path. 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 } /// /// Gets the PSHome module path, as known as the "system wide module path" in windows powershell. /// - /// The PSHome module path + /// The PSHome module path. internal static string GetPSHomeModulePath() { if (s_psHomeModulePath != null) + { return s_psHomeModulePath; + } 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) + { } - catch (System.Security.SecurityException) { } return s_psHomeModulePath; } @@ -586,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); @@ -597,10 +1025,28 @@ private static string GetSharedModulePath() { sharedModulePath = Path.Combine(sharedModulePath, Utils.ModuleDirectory); } + return sharedModulePath; #endif } +#if !UNIX + /// + /// Get the path to the Windows PowerShell module directory under the + /// System32 directory on Windows (the Windows PowerShell $PSHOME). + /// + /// The path of the Windows PowerShell system module directory. + internal static string GetWindowsPowerShellPSHomeModulePath() + { + if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellPSHomeLocation)) + { + return InternalTestHooks.TestWindowsPowerShellPSHomeLocation; + } + + return s_windowsPowerShellPSHomeModulePath; + } +#endif + /// /// Combine the PS system-wide module path and the DSC module path /// to get the system module paths. @@ -634,210 +1080,136 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa { result = Environment.ExpandEnvironmentVariables(result); } - return result; - } - /// - /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%) - /// - /// '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) - { - // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function"); - Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); - - int pos = 0; // position of the current substring in pathToScan - string[] substrings = pathToScan.Split(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) - { - string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - - // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') - // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } - if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase)) - { - return pos; // match found - return index of it in the 'pathToScan' string - } - else - { - pos += substring.Length + 1; // '1' is for trailing semicolon - } - } - // if we are here, that means a match was not found - return -1; + return result; } /// /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. /// /// Path string (like %Path% or %PSModulePath%). - /// Collection of individual paths to add. + /// 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 AddToPath(string basePath, string pathToAdd, int insertPosition) + private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition) { // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); - Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function"); + Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + + // The 'pathToAdd' could be a 'combined path' (path-separator-separated). + string[] newPaths = pathToAdd.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (newPaths.Length is 0) + { + // The 'pathToAdd' doesn't really contain any paths to add. + return basePath; + } - StringBuilder result = new StringBuilder(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); - if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths + foreach (string p in initialPaths) { - foreach (string subPathToAdd in pathToAdd.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + addedPaths.Add(Path.TrimEndingDirectorySeparator(p)); + } + + 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)) { - int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly - if (-1 == position) // subPathToAdd not found - add it - { - if (-1 == insertPosition) // append subPathToAdd to the end - { - bool endsWithPathSeparator = false; - if (result.Length > 0) endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); + // The normalized sub path was already added - skip it. + continue; + } - if (endsWithPathSeparator) - result.Append(subPathToAdd); - else - result.Append(Path.PathSeparator + subPathToAdd); - } - else // 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); - } + // The normalized sub path was not found - add it. + if (insertPosition is -1 || insertPosition >= result.Length) + { + // Append the normalized sub path to the end. + if (result.Length > 0 && result[^1] != Path.PathSeparator) + { + result.Append(Path.PathSeparator); } + + result.Append(normalizedPath); + // Next insertion should happen at the end. + insertPosition = result.Length; + } + else + { + // Insert at the requested location. + // This is used by the user-specific module path, the shared module path ( location), and the PSHome module path. + string strToInsert = normalizedPath + Path.PathSeparator; + result.Insert(insertPosition, strToInsert); + + // Next insertion should happen after the just inserted string. + insertPosition += strToInsert.Length; } + + // Add it to the set. + addedPaths.Add(normalizedPath); } return result.ToString(); } /// - /// Check if the current powershell is likely running in following scenarios: - /// - sxs ps started on windows [machine-wide env:PSModulePath will influence] - /// - sxs ps started from full ps - /// - sxs ps started from inbox nano/iot ps - /// - full ps started from sxs ps - /// - inbox nano/iot ps started from sxs ps - /// If it's likely one of them, then we need to clear the current process module path. + /// The available module path scopes. /// - private static bool NeedToClearProcessModulePath(string currentProcessModulePath, string personalModulePath, string sharedModulePath, bool runningSxS) + public enum PSModulePathScope { -#if UNIX - return false; -#else - Dbg.Assert(!string.IsNullOrEmpty(personalModulePath), "caller makes sure personalModulePath not null or empty"); - Dbg.Assert(sharedModulePath != null, "caller makes sure sharedModulePath is not null"); - - const string winSxSModuleDirectory = @"PowerShell\Modules"; - const string winLegacyModuleDirectory = @"WindowsPowerShell\Modules"; + /// The users module path. + User, - if (runningSxS) - { - // The machine-wide and user-wide environment variables are only meaningful for full ps, - // so if the current process module path contains any of them, it's likely that the sxs - // ps was started directly on windows, or from full ps. The same goes for the legacy personal - // and shared module paths. - string hklmModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); - string hkcuModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User); - string legacyPersonalModulePath = personalModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory); - string legacyProgramFilesModulePath = sharedModulePath.Replace(winSxSModuleDirectory, winLegacyModuleDirectory); + /// The Builtin module path. This is where PowerShell is installed (PSHOME). + Builtin, - return (!string.IsNullOrEmpty(hklmModulePath) && currentProcessModulePath.IndexOf(hklmModulePath, StringComparison.OrdinalIgnoreCase) != -1) || - (!string.IsNullOrEmpty(hkcuModulePath) && currentProcessModulePath.IndexOf(hkcuModulePath, StringComparison.OrdinalIgnoreCase) != -1) || - currentProcessModulePath.IndexOf(legacyPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || - currentProcessModulePath.IndexOf(legacyProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; - } - - // The sxs personal and shared module paths are only meaningful for sxs ps, so if they appear - // in the current process module path, it's likely the running ps was started from a sxs ps. - string sxsPersonalModulePath = personalModulePath.Replace(winLegacyModuleDirectory, winSxSModuleDirectory); - string sxsProgramFilesModulePath = sharedModulePath.Replace(winLegacyModuleDirectory, winSxSModuleDirectory); - - return currentProcessModulePath.IndexOf(sxsPersonalModulePath, StringComparison.OrdinalIgnoreCase) != -1 || - currentProcessModulePath.IndexOf(sxsProgramFilesModulePath, StringComparison.OrdinalIgnoreCase) != -1; -#endif + /// The machine module path. This is the shared location for all users of the system. + Machine } /// - /// When sxs ps instance B got started from sxs ps instance A, A's pshome module path might - /// show up in current process module path. It doesn't make sense for B to load modules from - /// A's pshome module path, so remove it in such case. + /// Retrieve the current PSModulePath for the specified scope. /// - private static string RemoveSxSPsHomeModulePath(string currentProcessModulePath, string personalModulePath, string sharedModulePath, string psHomeModulePath) + /// 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 UNIX - const string powershellExeName = "pwsh"; -#else - const string powershellExeName = "pwsh.exe"; -#endif - const string powershellDepsName = "pwsh.deps.json"; - - StringBuilder modulePathString = new StringBuilder(currentProcessModulePath.Length); - char[] invalidPathChars = Path.GetInvalidPathChars(); - - foreach (var path in currentProcessModulePath.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) + if (scope == PSModulePathScope.User) { - string trimedPath = path.Trim().TrimEnd(Path.DirectorySeparatorChar); - if (trimedPath.IndexOfAny(invalidPathChars) != -1 || !Path.IsPathRooted(trimedPath)) - { - // Path contains invalid characters or it's not an absolute path. Ignore it. - continue; - } - - if (!trimedPath.Equals(personalModulePath, StringComparison.OrdinalIgnoreCase) && - !trimedPath.Equals(sharedModulePath, StringComparison.OrdinalIgnoreCase) && - !trimedPath.Equals(psHomeModulePath, StringComparison.OrdinalIgnoreCase) && - trimedPath.EndsWith("Modules", StringComparison.OrdinalIgnoreCase)) - { - string parentDir = Path.GetDirectoryName(trimedPath); - string psExePath = Path.Combine(parentDir, powershellExeName); - string psDepsPath = Path.Combine(parentDir, powershellDepsName); - if ((File.Exists(psExePath) && File.Exists(psDepsPath))) - { - // Path is a PSHome module path from a different powershell core instance. Ignore it. - continue; - } - } - - if (modulePathString.Length > 0) - { - modulePathString.Append(Path.PathSeparator); - } - modulePathString.Append(trimedPath); + return GetPersonalModulePath(); + } + else if (scope == PSModulePathScope.Builtin) + { + return GetPSHomeModulePath(); + } + else + { + return GetSharedModulePath(); } - - return modulePathString.ToString(); } /// - /// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate. Note - because these - /// strings go through the provider, we need to escape any wildcards before passing them - /// along. + /// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate. /// public static string GetModulePath(string currentProcessModulePath, string hklmMachineModulePath, string hkcuUserModulePath) { string personalModulePath = GetPersonalModulePath(); string sharedModulePath = GetSharedModulePath(); // aka location string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location - bool runningSxS = Platform.IsInbox ? false : true; - - if (!string.IsNullOrEmpty(currentProcessModulePath) && - NeedToClearProcessModulePath(currentProcessModulePath, personalModulePath, sharedModulePath, runningSxS)) - { - // Clear the current process module path in the following cases - // - start sxs ps on windows [machine-wide env:PSModulePath will influence] - // - start sxs ps from full ps - // - start sxs ps from inbox nano/iot ps - // - start full ps from sxs ps - // - start inbox nano/iot ps from sxs ps - currentProcessModulePath = null; - } // 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 @@ -849,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) @@ -861,102 +1241,23 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM } // EVT.Process exists // Now handle the case where the environment variable is already set. - else if (runningSxS) // The running powershell is an SxS PS instance + else { - // When SxS PS instance A starts SxS PS instance B, A's PSHome module path might be inherited by B. We need to remove that path from B - currentProcessModulePath = RemoveSxSPsHomeModulePath(currentProcessModulePath, personalModulePath, sharedModulePath, psHomeModulePath); - string personalModulePathToUse = string.IsNullOrEmpty(hkcuUserModulePath) ? personalModulePath : hkcuUserModulePath; string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath; - currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0); - currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, -1); - } - else // The running powershell is Full PS or inbox Core PS - { - // If there is no personal path key, then if the env variable doesn't match the system variable, - // the user modified it somewhere, else prepend the default personal module path - if (hklmMachineModulePath != null) // EVT.Machine exists - { - if (hkcuUserModulePath == null) // EVT.User does Not exist - { - if (!(hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase)) - { - // before returning, use heuristic to conditionally add location - int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath - if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules - { - return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition); - } // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules' - - return null; - } - currentProcessModulePath = personalModulePath + Path.PathSeparator + hklmMachineModulePath; // + EVT.Machine + inserted later in this function - } - else // EVT.User exists - { - // PSModulePath is designed to have behaviour like 'Path' var in a sense that EVT.User + EVT.Machine are merged to get final value of PSModulePath - string combined = string.Concat(hkcuUserModulePath, Path.PathSeparator, hklmMachineModulePath); // EVT.User + EVT.Machine - if (!((combined).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) || - (hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) || - (hkcuUserModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))) - { - // before returning, use heuristic to conditionally add location - int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath - if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules - { - return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition); - } // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules' - - return null; - } - currentProcessModulePath = combined; // = EVT.User + EVT.Machine + inserted later in this function - } - } - else // EVT.Machine does Not exist - { - // If there is no system path key, then if the env variable doesn't match the user variable, - // the user modified it somewhere, otherwise append the default system path - if (hkcuUserModulePath != null) // EVT.User exists - { - if (hkcuUserModulePath.Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase)) - { - currentProcessModulePath = hkcuUserModulePath + Path.PathSeparator + CombineSystemModulePaths(); // = EVT.User + (SharedModulePath + $PSHome\Modules) - } - else - { - // before returning, use heuristic to conditionally add location - int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath - if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules - { - return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition); - } // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules' + // Maintain order of the paths, but ahead of any existing paths: + // personalModulePath + // sharedModulePath + // systemModulePath - return null; - } - } - else // EVT.User does Not exist - { - // before returning, use heuristic to conditionally add location - int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath - if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert location before $PSHome\Modules - { - return AddToPath(currentProcessModulePath, sharedModulePath, psHomePosition); - } // if $PSHome\Modules NOT found = = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules' + int insertIndex = 0; - // Neither key is set so go with what the environment variable is already set to - return null; - } - } + currentProcessModulePath = UpdatePath(currentProcessModulePath, personalModulePathToUse, ref insertIndex); + currentProcessModulePath = UpdatePath(currentProcessModulePath, sharedModulePath, ref insertIndex); + currentProcessModulePath = UpdatePath(currentProcessModulePath, systemModulePathToUse, ref insertIndex); } - // if we reached this point - always add location to EVT.Process - // everything below is the same behaviour as WMF 4 code - int indexOfPSHomeModulePath = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath - // if $PSHome\Modules not found (psHomePosition == -1) - append location to the end; - // if $PSHome\Modules IS found (psHomePosition >= 0) - insert location before $PSHome\Modules - currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, indexOfPSHomeModulePath); - return currentProcessModulePath; } @@ -970,22 +1271,87 @@ internal static string GetModulePath() string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); return currentModulePath; } + +#if !UNIX + /// + /// Returns a PSModulePath suitable for Windows PowerShell by removing PowerShell's specific + /// paths from current PSModulePath. + /// + /// + /// Returns appropriate PSModulePath for Windows PowerShell. + /// + internal static string GetWindowsPowerShellModulePath() + { + string currentModulePath = GetModulePath(); + + if (currentModulePath == null) + { + return null; + } + + // PowerShell specific paths including if set in powershell.config.json file we want to exclude + var excludeModulePaths = new HashSet(StringComparer.OrdinalIgnoreCase) { + GetPersonalModulePath(), + GetSharedModulePath(), + GetPSHomeModulePath(), + PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers), + PowerShellConfig.Instance.GetModulePath(ConfigScope.CurrentUser) + }; + + var modulePathList = new List(); + foreach (var path in currentModulePath.Split(';', StringSplitOptions.TrimEntries)) + { + if (!excludeModulePaths.Contains(path)) + { + // make sure this module path is Not part of other PS Core installation + var possiblePwshDir = Path.GetDirectoryName(path); + + if (string.IsNullOrEmpty(possiblePwshDir)) + { + // i.e. module dir is in the drive root + modulePathList.Add(path); + } + else + { + if (!File.Exists(Path.Combine(possiblePwshDir, "pwsh.dll"))) + { + modulePathList.Add(path); + } + } + } + } + + return string.Join(Path.PathSeparator, modulePathList); + } +#endif + /// /// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these /// strings go through the provider, we need to escape any wildcards before passing them /// along. /// - internal static string SetModulePath() + private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); - string systemWideModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.SystemWide); +#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 (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0) + { + 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); string personalModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.CurrentUser); - - string newModulePathString = GetModulePath(currentModulePath, systemWideModulePath, personalModulePath); + string newModulePathString = GetModulePath(currentModulePath, allUsersModulePath, personalModulePath); if (!string.IsNullOrEmpty(newModulePathString)) { - // Set the environment variable... Environment.SetEnvironmentVariable(Constants.PSModulePathEnvVar, newModulePathString); } @@ -1006,7 +1372,7 @@ internal static string SetModulePath() /// modules long term - e.g. when open sourcing a module and installing from the gallery. /// /// - /// The module path as an array of strings + /// The module path as an array of strings. internal static IEnumerable GetModulePath(bool includeSystemModulePath, ExecutionContext context) { string modulePathString = Environment.GetEnvironmentVariable(Constants.PSModulePathEnvVar) ?? SetModulePath(); @@ -1015,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) @@ -1101,12 +1467,13 @@ private static string ProcessOneModulePath(ExecutionContext context, string envP return null; } +#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); @@ -1114,39 +1481,38 @@ 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 + /// Mark stuff to be exported from the current environment using the various patterns. /// - /// The cmdlet calling this method - /// The session state instance to do the exports on - /// Patterns describing the functions to export - /// Patterns describing the cmdlets to export - /// Patterns describing the aliases to export - /// Patterns describing the variables to export - /// List of Cmdlets that will not be exported, - /// even if they match in cmdletPatterns. - internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal sessionState, - List functionPatterns, List cmdletPatterns, - List aliasPatterns, List variablePatterns, List doNotExportCmdlets) + /// The cmdlet calling this method. + /// The session state instance to do the exports on. + /// Patterns describing the functions to export. + /// Patterns describing the cmdlets to export. + /// Patterns describing the aliases to export. + /// Patterns describing the variables to export. + /// List of Cmdlets that will not be exported, even if they match in cmdletPatterns. + internal static void ExportModuleMembers( + PSCmdlet cmdlet, + SessionStateInternal sessionState, + List functionPatterns, + List cmdletPatterns, + List aliasPatterns, + List variablePatterns, + List doNotExportCmdlets) { // If this cmdlet is called, then mark that the export list should be used for exporting // module members... @@ -1155,6 +1521,12 @@ internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal s if (functionPatterns != null) { + sessionState.FunctionsExported = true; + if (PatternContainsWildcard(functionPatterns)) + { + sessionState.FunctionsExportedWithWildcard = true; + } + IDictionary ft = sessionState.ModuleScope.FunctionTable; foreach (KeyValuePair entry in ft) @@ -1167,24 +1539,13 @@ internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal s if (SessionStateUtilities.MatchesAnyWildcardPattern(entry.Key, functionPatterns, false)) { - string message; - - if (entry.Value.CommandType == CommandTypes.Workflow) - { - message = StringUtil.Format(Modules.ExportingWorkflow, entry.Key); - sessionState.ExportedWorkflows.Add((WorkflowInfo)entry.Value); - } - else - { - message = StringUtil.Format(Modules.ExportingFunction, entry.Key); - sessionState.ExportedFunctions.Add(entry.Value); - } - + sessionState.ExportedFunctions.Add(entry.Value); + string message = StringUtil.Format(Modules.ExportingFunction, entry.Key); cmdlet.WriteVerbose(message); } } - SortAndRemoveDuplicates(sessionState.ExportedFunctions, delegate (FunctionInfo ci) { return ci.Name; }); - SortAndRemoveDuplicates(sessionState.ExportedWorkflows, delegate (WorkflowInfo ci) { return ci.Name; }); + + SortAndRemoveDuplicates(sessionState.ExportedFunctions, static (FunctionInfo ci) => ci.Name); } if (cmdletPatterns != null) @@ -1239,7 +1600,7 @@ internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal s } } - SortAndRemoveDuplicates(sessionState.Module.CompiledExports, delegate (CmdletInfo ci) { return ci.Name; }); + SortAndRemoveDuplicates(sessionState.Module.CompiledExports, static (CmdletInfo ci) => ci.Name); } if (variablePatterns != null) @@ -1261,7 +1622,8 @@ internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal s sessionState.ExportedVariables.Add(entry.Value); } } - SortAndRemoveDuplicates(sessionState.ExportedVariables, delegate (PSVariable v) { return v.Name; }); + + SortAndRemoveDuplicates(sessionState.ExportedVariables, static (PSVariable v) => v.Name); } if (aliasPatterns != null) @@ -1301,8 +1663,29 @@ internal static void ExportModuleMembers(PSCmdlet cmdlet, SessionStateInternal s } } - SortAndRemoveDuplicates(sessionState.ExportedAliases, delegate (AliasInfo ci) { return ci.Name; }); + SortAndRemoveDuplicates(sessionState.ExportedAliases, static (AliasInfo ci) => ci.Name); + } + } + + /// + /// Checks pattern list for wildcard characters. + /// + /// Pattern list. + /// True if pattern contains '*'. + internal static bool PatternContainsWildcard(List list) + { + if (list != null) + { + foreach (var item in list) + { + if (WildcardPattern.ContainsWildcardCharacters(item.Pattern)) + { + return true; + } + } } + + return false; } private static AliasInfo NewAliasInfo(AliasInfo alias, SessionStateInternal sessionState) @@ -1318,12 +1701,44 @@ private static AliasInfo NewAliasInfo(AliasInfo alias, SessionStateInternal sess }; return aliasCopy; } - } // ModuleIntrinsics + } + + /// + /// Enumeration of reasons for a failure to match a module by constraints. + /// + internal enum ModuleMatchFailure + { + /// Match did not fail. + None, + + /// Match failed because the module was null. + NullModule, + + /// Module name did not match. + Name, + + /// Module GUID did not match. + Guid, + + /// Module version did not match the required version. + RequiredVersion, + + /// Module version was lower than the minimum version. + MinimumVersion, + + /// Module version was greater than the maximum version. + MaximumVersion, + + /// 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 { /// @@ -1343,4 +1758,4 @@ public interface IModuleAssemblyCleanup /// void OnRemove(PSModuleInfo psModuleInfo); } -} // System.Management.Automation +} diff --git a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs index 0b7fb6f99ff..3c06ee856f3 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation.Language; -using System.Text; using System.Collections; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Text; + using Dbg = System.Management.Automation.Diagnostics; // @@ -32,7 +32,7 @@ namespace Microsoft.PowerShell.Commands public class ModuleSpecification { /// - /// Default constructor + /// Default constructor. /// public ModuleSpecification() { @@ -44,11 +44,10 @@ public ModuleSpecification() /// The module name. public ModuleSpecification(string moduleName) { - if (string.IsNullOrEmpty(moduleName)) - { - throw new ArgumentNullException("moduleName"); - } + ArgumentException.ThrowIfNullOrEmpty(moduleName); + this.Name = moduleName; + // Alias name of miniumVersion this.Version = null; this.RequiredVersion = null; @@ -66,10 +65,7 @@ public ModuleSpecification(string moduleName) /// The module specification as a hashtable. public ModuleSpecification(Hashtable moduleSpecification) { - if (moduleSpecification == null) - { - throw new ArgumentNullException("moduleSpecification"); - } + ArgumentNullException.ThrowIfNull(moduleSpecification); var exception = ModuleSpecificationInitHelper(this, moduleSpecification); if (exception != null) @@ -82,8 +78,8 @@ public ModuleSpecification(Hashtable moduleSpecification) /// Initialize moduleSpecification from hashtable. Return exception object, if hashtable cannot be converted. /// Return null, in the success case. /// - /// object to initialize - /// contains info about object to initialize. + /// Object to initialize. + /// Contains info about object to initialize. /// internal static Exception ModuleSpecificationInitHelper(ModuleSpecification moduleSpecification, Hashtable hashtable) { @@ -92,39 +88,44 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu { foreach (DictionaryEntry entry in hashtable) { - if (entry.Key.ToString().Equals("ModuleName", StringComparison.OrdinalIgnoreCase)) + string field = entry.Key.ToString(); + + if (field.Equals("ModuleName", StringComparison.OrdinalIgnoreCase)) { moduleSpecification.Name = LanguagePrimitives.ConvertTo(entry.Value); } - else if (entry.Key.ToString().Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase)) + else if (field.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase)) { moduleSpecification.Version = LanguagePrimitives.ConvertTo(entry.Value); } - else if (entry.Key.ToString().Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase)) + else if (field.Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase)) { moduleSpecification.RequiredVersion = LanguagePrimitives.ConvertTo(entry.Value); } - else if (entry.Key.ToString().Equals("MaximumVersion", StringComparison.OrdinalIgnoreCase)) + else if (field.Equals("MaximumVersion", StringComparison.OrdinalIgnoreCase)) { - moduleSpecification.MaximumVersion = LanguagePrimitives.ConvertTo(entry.Value); + moduleSpecification.MaximumVersion = LanguagePrimitives.ConvertTo(entry.Value); ModuleCmdletBase.GetMaximumVersion(moduleSpecification.MaximumVersion); } - else if (entry.Key.ToString().Equals("GUID", StringComparison.OrdinalIgnoreCase)) + else if (field.Equals("GUID", StringComparison.OrdinalIgnoreCase)) { moduleSpecification.Guid = LanguagePrimitives.ConvertTo(entry.Value); } else { if (badKeys.Length > 0) + { 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; @@ -160,16 +161,57 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu message = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "MaximumVersion", "RequiredVersion"); return new ArgumentException(message); } + return null; } - internal ModuleSpecification(PSModuleInfo moduleInfo) + internal string GetRequiredModuleNotFoundVersionMessage() { - if (moduleInfo == null) + if (RequiredVersion is not null) + { + 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) { - throw new ArgumentNullException("moduleInfo"); + 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; @@ -183,45 +225,47 @@ internal ModuleSpecification(PSModuleInfo moduleInfo) /// public override string ToString() { - string moduleSpecString = string.Empty; if (Guid == null && Version == null && RequiredVersion == null && MaximumVersion == null) { - moduleSpecString = Name; + return Name; + } + + var moduleSpecBuilder = new StringBuilder(); + + moduleSpecBuilder.Append("@{ ModuleName = '").Append(Name).Append('\''); + + if (Guid != null) + { + moduleSpecBuilder.Append("; Guid = '{").Append(Guid).Append("}' "); + } + + if (RequiredVersion != null) + { + moduleSpecBuilder.Append("; RequiredVersion = '").Append(RequiredVersion).Append('\''); } else { - moduleSpecString = "@{ ModuleName = '" + Name + "'"; - if (Guid != null) + if (Version != null) { - moduleSpecString += "; Guid = '{" + Guid + "}' "; + moduleSpecBuilder.Append("; ModuleVersion = '").Append(Version).Append('\''); } - if (RequiredVersion != null) - { - moduleSpecString += "; RequiredVersion = '" + RequiredVersion + "'"; - } - else + + if (MaximumVersion != null) { - if (Version != null) - { - moduleSpecString += "; ModuleVersion = '" + Version + "'"; - } - if (MaximumVersion != null) - { - moduleSpecString += "; MaximumVersion = '" + MaximumVersion + "'"; - } + moduleSpecBuilder.Append("; MaximumVersion = '").Append(MaximumVersion).Append('\''); } - moduleSpecString += " }"; } - return moduleSpecString; - } + moduleSpecBuilder.Append(" }"); + return moduleSpecBuilder.ToString(); + } /// - /// Parse the specified string into a ModuleSpecification object + /// Parse the specified string into a ModuleSpecification object. /// - /// The module specification string - /// the ModuleSpecification object + /// The module specification string. + /// The ModuleSpecification object. /// public static bool TryParse(string input, out ModuleSpecification result) { @@ -243,6 +287,31 @@ public static bool TryParse(string input, out ModuleSpecification result) return false; } + /// + /// Copy the module specification while normalizing the name + /// so that paths become absolute and use the right directory separators. + /// + /// The current execution context. Used for path normalization. + /// The base path where a relative path should be interpreted with respect to. + /// A fresh module specification object with the name normalized for use internally. + internal ModuleSpecification WithNormalizedName(ExecutionContext context, string basePath) + { + // Save allocating a new module spec if we don't need to change anything + if (!ModuleIntrinsics.IsModuleNamePath(Name)) + { + return this; + } + + return new ModuleSpecification() + { + Guid = Guid, + MaximumVersion = MaximumVersion, + Version = Version, + RequiredVersion = RequiredVersion, + Name = ModuleIntrinsics.NormalizeModuleName(Name, basePath, context) + }; + } + /// /// The module name. /// @@ -261,7 +330,7 @@ public static bool TryParse(string input, out ModuleSpecification result) /// /// The module maxVersion number if specified, otherwise null. /// - public String MaximumVersion { get; internal set; } + public string MaximumVersion { get; internal set; } /// /// The exact version of the module if specified, otherwise null. @@ -269,98 +338,47 @@ public static bool TryParse(string input, out ModuleSpecification result) public Version RequiredVersion { get; internal set; } } + /// + /// Compares two ModuleSpecification objects for equality. + /// internal class ModuleSpecificationComparer : IEqualityComparer { + /// + /// Check if two module specifications are property-wise equal. + /// + /// + /// + /// True if the specifications are equal, false otherwise. public bool Equals(ModuleSpecification x, ModuleSpecification y) { - bool result = false; - - if (x == null && y == null) - { - result = true; - } - else if (x != null && y != null) + if (x == y) { - if (x.Name != null && y.Name != null) - { - result = x.Name.Equals(y.Name, StringComparison.OrdinalIgnoreCase); - } - else - { - result = true; - } - if (result) - { - if (x.Guid.HasValue && y.Guid.HasValue) - { - result = x.Guid.Equals(y.Guid); - } - } - if (result) - { - if (x.Version != null && y.Version != null) - { - result = x.Version.Equals(y.Version); - } - else if (x.Version != null || y.Version != null) - { - result = false; - } - - if (x.MaximumVersion != null && y.MaximumVersion != null) - { - result = x.MaximumVersion.Equals(y.MaximumVersion); - } - else if (x.MaximumVersion != null || y.MaximumVersion != null) - { - result = false; - } - - if (result && x.RequiredVersion != null && y.RequiredVersion != null) - { - result = x.RequiredVersion.Equals(y.RequiredVersion); - } - else if (result && (x.RequiredVersion != null || y.RequiredVersion != null)) - { - result = false; - } - } + return true; } - return result; + return x != null && y != null + && string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) + && Guid.Equals(x.Guid, y.Guid) + && Version.Equals(x.RequiredVersion, y.RequiredVersion) + && Version.Equals(x.Version, y.Version) + && string.Equals(x.MaximumVersion, y.MaximumVersion); } + /// + /// Get a property-based hashcode for a ModuleSpecification object. + /// + /// The module specification for the object. + /// A hashcode that is always the same for any module specification with the same properties. public int GetHashCode(ModuleSpecification obj) { - int result = 0; - - if (obj != null) + if (obj == null) { - if (obj.Name != null) - { - result = result ^ obj.Name.GetHashCode(); - } - if (obj.Guid.HasValue) - { - result = result ^ obj.Guid.GetHashCode(); - } - if (obj.Version != null) - { - result = result ^ obj.Version.GetHashCode(); - } - if (obj.MaximumVersion != null) - { - result = result ^ obj.MaximumVersion.GetHashCode(); - } - if (obj.RequiredVersion != null) - { - result = result ^ obj.RequiredVersion.GetHashCode(); - } + return 0; } - return result; + return HashCode.Combine(obj.Name, obj.Guid, obj.RequiredVersion, obj.Version, obj.MaximumVersion); } } #endregion -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index c295f3af366..41bf4ac3521 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -1,35 +1,67 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Globalization; using System.IO; using System.Management.Automation.Runspaces; +using System.Text; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Internal { internal static class ModuleUtils { - internal static bool IsPossibleModuleDirectory(string dir) + // 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 = 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 = FileAttributesToSkip, BufferSize = 16384 }; + + private static readonly string EnCulturePath = Path.DirectorySeparatorChar + "en"; + private static readonly string EnUsCulturePath = Path.DirectorySeparatorChar + "en-us"; + + static ModuleUtils() { - // We shouldn't be searching in hidden directories. - var attributes = File.GetAttributes(dir); - if (0 != (attributes & FileAttributes.Hidden)) - { - return false; - } + 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. + /// + /// Directory to check if it is a possible resource folder. + /// True if the directory name matches a culture. + internal static bool IsPossibleResourceDirectory(string dir) + { // Assume locale directories do not contain modules. - if (dir.EndsWith(@"\en", StringComparison.OrdinalIgnoreCase) || - dir.EndsWith(@"\en-us", StringComparison.OrdinalIgnoreCase)) + if (dir.EndsWith(EnCulturePath, StringComparison.OrdinalIgnoreCase) || + dir.EndsWith(EnUsCulturePath, StringComparison.OrdinalIgnoreCase)) { - return false; + return true; } -#if !CORECLR dir = Path.GetFileName(dir); + // Use some simple pattern matching to avoid the call into GetCultureInfo when we know it will fail (and throw). if ((dir.Length == 2 && char.IsLetter(dir[0]) && char.IsLetter(dir[1])) || @@ -40,38 +72,35 @@ internal static bool IsPossibleModuleDirectory(string dir) // This might not throw on invalid culture still // 4096 is considered the unknown locale - so assume that could be a module var cultureInfo = new CultureInfo(dir); - return cultureInfo.LCID == 4096; + return cultureInfo.LCID != 4096; } catch { } } -#endif - return true; + return false; } /// - /// Get a list of all module files - /// which can be imported just by specifying a non rooted file name of the module - /// (Import-Module foo\bar.psm1; but not Import-Module .\foo\bar.psm1) + /// Get all module files by searching the given directory recursively. + /// All sub-directories that could be a module folder will be searched. /// - /// When obtaining all module files we return all possible - /// combinations for a given file. For example, for foo we return both - /// foo.psd1 and foo.psm1 if found. Get-Module will create the module - /// info only for the first one internal static IEnumerable GetAllAvailableModuleFiles(string topDirectoryToCheck) { + if (!Directory.Exists(topDirectoryToCheck)) { yield break; } + + var options = Utils.PathIsUnc(topDirectoryToCheck) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions; Queue directoriesToCheck = new Queue(); directoriesToCheck.Enqueue(topDirectoryToCheck); + bool firstSubDirs = true; while (directoriesToCheck.Count > 0) { - var directoryToCheck = directoriesToCheck.Dequeue(); + string directoryToCheck = directoriesToCheck.Dequeue(); try { - var subDirectories = Directory.GetDirectories(directoryToCheck, "*", SearchOption.TopDirectoryOnly); - foreach (var toAdd in subDirectories) + foreach (string toAdd in Directory.EnumerateDirectories(directoryToCheck, "*", options)) { - if (IsPossibleModuleDirectory(toAdd)) + if (firstSubDirs || !IsPossibleResourceDirectory(toAdd)) { directoriesToCheck.Enqueue(toAdd); } @@ -80,8 +109,8 @@ internal static IEnumerable GetAllAvailableModuleFiles(string topDirecto catch (IOException) { } catch (UnauthorizedAccessException) { } - var files = Directory.GetFiles(directoryToCheck, "*", SearchOption.TopDirectoryOnly); - foreach (string moduleFile in files) + firstSubDirs = false; + foreach (string moduleFile in Directory.EnumerateFiles(directoryToCheck, "*", options)) { foreach (string ext in ModuleIntrinsics.PSModuleExtensions) { @@ -95,7 +124,30 @@ internal static IEnumerable GetAllAvailableModuleFiles(string topDirecto } } - internal static IEnumerable GetDefaultAvailableModuleFiles(bool force, bool isForAutoDiscovery, ExecutionContext context) + /// + /// Check if the CompatiblePSEditions field of a given module + /// declares compatibility with the running PowerShell edition. + /// + /// The path to the module manifest being checked. + /// The value of the CompatiblePSEditions field of the module manifest. + /// True if the module is compatible with the running PowerShell edition, false otherwise. + internal static bool IsPSEditionCompatible( + string moduleManifestPath, + IEnumerable compatiblePSEditions) + { +#if UNIX + return true; +#else + if (!IsOnSystem32ModulePath(moduleManifestPath)) + { + return true; + } + + return Utils.IsPSEditionSupported(compatiblePSEditions); +#endif + } + + internal static IEnumerable GetDefaultAvailableModuleFiles(bool isForAutoDiscovery, ExecutionContext context) { HashSet uniqueModuleFiles = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -111,7 +163,7 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(bool force, b { analysisProgress = new ProgressRecord(0, Modules.DeterminingAvailableModules, - String.Format(CultureInfo.InvariantCulture, Modules.SearchingUncShare, directory)) + string.Format(CultureInfo.InvariantCulture, Modules.SearchingUncShare, directory)) { RecordType = ProgressRecordType.Processing }; @@ -147,59 +199,74 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(bool force, b } } - internal static List GetModuleVersionsFromAbsolutePath(string directory) + /// + /// Get a list of module files from the given directory without recursively searching all sub-directories. + /// This method assumes the given directory is a module folder or a version sub-directory of a module folder. + /// + internal static List GetModuleFilesFromAbsolutePath(string directory) { List result = new List(); string fileName = Path.GetFileName(directory); - Version moduleVersion; - // if the user give the module path including version, we should be able to find the module as well - if (Version.TryParse(fileName, out moduleVersion) && Directory.Exists(Directory.GetParent(directory).ToString())) - { - fileName = Directory.GetParent(directory).Name; - } - foreach (var version in GetModuleVersionSubfolders(directory)) + + // If the given directory doesn't exist or it's the root folder, then return an empty list. + if (!Directory.Exists(directory) || string.IsNullOrEmpty(fileName)) { return result; } + + // If the user give the module path including version, the module name could be the parent folder name. + if (Version.TryParse(fileName, out Version ver)) { - var qualifiedPathWithVersion = Path.Combine(directory, Path.Combine(version.ToString(), fileName)); - string manifestPath = qualifiedPathWithVersion + StringLiterals.PowerShellDataFileExtension; - if (File.Exists(manifestPath)) - { - bool isValidModuleVersion = version.Equals(ModuleIntrinsics.GetManifestModuleVersion(manifestPath)); + string parentDirPath = Path.GetDirectoryName(directory); + string parentDirName = Path.GetFileName(parentDirPath); - if (isValidModuleVersion) + // If the parent directory is NOT a root folder, then it could be the module folder. + if (!string.IsNullOrEmpty(parentDirName)) + { + string manifestPath = Path.Combine(directory, parentDirName); + manifestPath += StringLiterals.PowerShellDataFileExtension; + if (File.Exists(manifestPath) && ver.Equals(ModuleIntrinsics.GetManifestModuleVersion(manifestPath))) { result.Add(manifestPath); + return result; } } } - foreach (string ext in ModuleIntrinsics.PSModuleExtensions) + // If we reach here, then use the given directory as the module folder. + foreach (Version version in GetModuleVersionSubfolders(directory)) { - string moduleFile = Path.Combine(directory, fileName) + ext; - - if (!Utils.NativeFileExists(moduleFile)) + string manifestPath = Path.Combine(directory, version.ToString(), fileName); + manifestPath += StringLiterals.PowerShellDataFileExtension; + if (File.Exists(manifestPath) && version.Equals(ModuleIntrinsics.GetManifestModuleVersion(manifestPath))) { - continue; + result.Add(manifestPath); } + } - result.Add(moduleFile); + foreach (string ext in ModuleIntrinsics.PSModuleExtensions) + { + string moduleFile = Path.Combine(directory, fileName) + ext; + if (File.Exists(moduleFile)) + { + result.Add(moduleFile); - // when finding the default modules we stop when the first - // match is hit - searching in order .psd1, .psm1, .dll - // if a file is found but is not readable then it is an - // error - break; + // when finding the default modules we stop when the first + // match is hit - searching in order .psd1, .psm1, .dll, + // if a file is found but is not readable then it is an error. + break; + } } return result; } /// - /// Get a list of the available module files - /// which can be imported just by specifying a non rooted directory name of the module - /// (Import-Module foo\bar; but not Import-Module .\foo\bar or Import-Module .\foo\bar.psm1) + /// Get a list of the available module files from the given directory. + /// Search all module folders under the specified directory, but do not search sub-directories under a module folder. /// internal static IEnumerable GetDefaultAvailableModuleFiles(string topDirectoryToCheck) { + if (!Directory.Exists(topDirectoryToCheck)) { yield break; } + + var options = Utils.PathIsUnc(topDirectoryToCheck) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions; List versionDirectories = new List(); LinkedList directoriesToCheck = new LinkedList(); directoriesToCheck.AddLast(topDirectoryToCheck); @@ -208,62 +275,70 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(string topDir { versionDirectories.Clear(); string[] subdirectories; - var directoryToCheck = directoriesToCheck.First.Value; + string directoryToCheck = directoriesToCheck.First.Value; directoriesToCheck.RemoveFirst(); try { - subdirectories = Directory.GetDirectories(directoryToCheck, "*", SearchOption.TopDirectoryOnly); + subdirectories = Directory.GetDirectories(directoryToCheck, "*", options); ProcessPossibleVersionSubdirectories(subdirectories, versionDirectories); } - catch (IOException) { subdirectories = Utils.EmptyArray(); } - catch (UnauthorizedAccessException) { subdirectories = Utils.EmptyArray(); } + catch (IOException) { subdirectories = Array.Empty(); } + catch (UnauthorizedAccessException) { subdirectories = Array.Empty(); } bool isModuleDirectory = false; string proposedModuleName = Path.GetFileName(directoryToCheck); - foreach (var version in versionDirectories) + foreach (Version version in versionDirectories) { - var qualifiedPathWithVersion = Path.Combine(directoryToCheck, Path.Combine(version.ToString(), proposedModuleName)); - string manifestPath = qualifiedPathWithVersion + StringLiterals.PowerShellDataFileExtension; + string manifestPath = Path.Combine(directoryToCheck, version.ToString(), proposedModuleName); + manifestPath += StringLiterals.PowerShellDataFileExtension; if (File.Exists(manifestPath)) { + if (HasSkippedFileAttribute(manifestPath)) + { + continue; + } + isModuleDirectory = true; yield return manifestPath; } } - foreach (string ext in ModuleIntrinsics.PSModuleExtensions) + if (!isModuleDirectory) { - string moduleFile = Path.Combine(directoryToCheck, proposedModuleName) + ext; - if (!Utils.NativeFileExists(moduleFile)) + foreach (string ext in ModuleIntrinsics.PSModuleExtensions) { - continue; - } + string moduleFile = Path.Combine(directoryToCheck, proposedModuleName) + ext; + if (File.Exists(moduleFile)) + { + if (HasSkippedFileAttribute(moduleFile)) + { + continue; + } - isModuleDirectory = true; - yield return moduleFile; + isModuleDirectory = true; + yield return moduleFile; - // when finding the default modules we stop when the first - // match is hit - searching in order .psd1, .psm1, .dll - // if a file is found but is not readable then it is an - // error - break; + // when finding the default modules we stop when the first + // match is hit - searching in order .psd1, .psm1, .dll, .exe + // if a file is found but is not readable then it is an + // error + break; + } + } } if (!isModuleDirectory) { foreach (var subdirectory in subdirectories) { - if (IsPossibleModuleDirectory(subdirectory)) + if (subdirectory.EndsWith("Microsoft.PowerShell.Management", StringComparison.OrdinalIgnoreCase) || + subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase)) { - if (subdirectory.EndsWith("Microsoft.PowerShell.Management", StringComparison.OrdinalIgnoreCase) || - subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase)) - { - directoriesToCheck.AddFirst(subdirectory); - } - else - { - directoriesToCheck.AddLast(subdirectory); - } + directoriesToCheck.AddFirst(subdirectory); + } + else + { + directoriesToCheck.AddLast(subdirectory); } } } @@ -271,62 +346,118 @@ internal static IEnumerable GetDefaultAvailableModuleFiles(string topDir } /// - /// Gets the list of versions under the specified module base path in descending sorted order + /// Gets the list of versions under the specified module base path in descending sorted order. /// - /// module base path - /// sorted list of versions + /// Module base path. + /// Sorted list of versions. internal static List GetModuleVersionSubfolders(string moduleBase) { var versionFolders = new List(); if (!string.IsNullOrWhiteSpace(moduleBase) && Directory.Exists(moduleBase)) { - var subdirectories = Directory.GetDirectories(moduleBase); + var options = Utils.PathIsUnc(moduleBase) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions; + 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) { - foreach (var subdir in subdirectories) + try { - var subdirName = Path.GetFileName(subdir); - Version version; - if (Version.TryParse(subdirName, out version)) + 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) + { + string subdirName = Path.GetFileName(subdir); + if (Version.TryParse(subdirName, out Version version)) { versionFolders.Add(version); } } + if (versionFolders.Count > 1) { - versionFolders.Sort((x, y) => y.CompareTo(x)); + versionFolders.Sort(static (x, y) => y.CompareTo(x)); } } internal static bool IsModuleInVersionSubdirectory(string modulePath, out Version version) { version = null; - var folderName = Path.GetDirectoryName(modulePath); + string folderName = Path.GetDirectoryName(modulePath); if (folderName != null) { folderName = Path.GetFileName(folderName); return Version.TryParse(folderName, out version); } + + return false; + } + + internal static bool IsOnSystem32ModulePath(string path) + { +#if UNIX return false; +#else + Dbg.Assert(!string.IsNullOrEmpty(path), $"Caller to verify that {nameof(path)} is not null or empty"); + + string windowsPowerShellPSHomePath = ModuleIntrinsics.GetWindowsPowerShellPSHomeModulePath(); + return path.StartsWith(windowsPowerShellPSHomePath, StringComparison.OrdinalIgnoreCase); +#endif + } + + /// + /// Gets a list of fuzzy matching commands and their scores. + /// + /// 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, FuzzyMatcher fuzzyMatcher, bool rediscoverImportedModules = false, bool moduleVersionRequired = false) + { + foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, fuzzyMatcher: fuzzyMatcher)) + { + if (fuzzyMatcher.IsFuzzyMatch(command.Name, pattern, out int score)) + { + yield return new CommandScore(command, score); + } + } } /// - /// Gets a list of matching commands + /// Gets a list of matching commands. /// - /// command pattern - /// - /// - /// - /// - /// - internal static IEnumerable GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false) + /// Command pattern. + /// Execution context. + /// Command origin. + /// If true, rediscovers imported modules. + /// Specific module version to be required. + /// 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, FuzzyMatcher fuzzyMatcher = null, bool useAbbreviationExpansion = false) { // Otherwise, if it had wildcards, just return the "AvailableCommand" // type of command info. @@ -336,15 +467,13 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe PSModuleAutoLoadingPreference moduleAutoLoadingPreference = CommandDiscovery.GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference"); if ((moduleAutoLoadingPreference != PSModuleAutoLoadingPreference.None) && - ((commandOrigin == CommandOrigin.Internal) || ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public)) - ) - ) + ((commandOrigin == CommandOrigin.Internal) || ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public)))) { - foreach (string modulePath in GetDefaultAvailableModuleFiles(true, false, context)) + foreach (string modulePath in GetDefaultAvailableModuleFiles(isForAutoDiscovery: false, context)) { // Skip modules that have already been loaded so that we don't expose private commands. string moduleName = Path.GetFileNameWithoutExtension(modulePath); - var modules = context.Modules.GetExactMatchModules(moduleName, all: false, exactMatch: true); + List modules = context.Modules.GetExactMatchModules(moduleName, all: false, exactMatch: true); PSModuleInfo tempModuleInfo = null; if (modules.Count != 0) @@ -352,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; } @@ -360,21 +489,20 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe if (modules.Count == 1) { PSModuleInfo psModule = modules[0]; - tempModuleInfo = new PSModuleInfo(psModule.Name, psModule.Path, null, null); + tempModuleInfo = new PSModuleInfo(psModule.Name, psModule.Path, context: null, sessionState: null); tempModuleInfo.SetModuleBase(psModule.ModuleBase); - foreach (var entry in psModule.ExportedCommands) + foreach (KeyValuePair entry in psModule.ExportedCommands) { - if (commandPattern.IsMatch(entry.Value.Name)) + if (commandPattern.IsMatch(entry.Value.Name) || + (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) || + (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(entry.Value.Name), StringComparison.OrdinalIgnoreCase))) { CommandInfo current = null; switch (entry.Value.CommandType) { case CommandTypes.Alias: - current = new AliasInfo(entry.Value.Name, null, context); - break; - case CommandTypes.Workflow: - current = new WorkflowInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context); + current = new AliasInfo(entry.Value.Name, definition: null, context); break; case CommandTypes.Function: current = new FunctionInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context); @@ -386,7 +514,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe current = new ConfigurationInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context); break; case CommandTypes.Cmdlet: - current = new CmdletInfo(entry.Value.Name, null, null, null, context); + current = new CmdletInfo(entry.Value.Name, implementingType: null, helpFile: null, PSSnapin: null, context); break; default: Dbg.Assert(false, "cannot be hit"); @@ -402,30 +530,33 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe } } - string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(modulePath); - var exportedCommands = AnalysisCache.GetExportedCommands(modulePath, false, context); + string moduleShortName = Path.GetFileNameWithoutExtension(modulePath); + + IDictionary exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context); if (exportedCommands == null) { continue; } - tempModuleInfo = new PSModuleInfo(moduleShortName, modulePath, null, null); + tempModuleInfo = new PSModuleInfo(moduleShortName, modulePath, sessionState: null, context: null); if (InitialSessionState.IsEngineModule(moduleShortName)) { tempModuleInfo.SetModuleBase(Utils.DefaultPowerShellAppBase); } - //moduleVersionRequired is bypassed by FullyQualifiedModule from calling method. This is the only place where guid will be involved. + // moduleVersionRequired is bypassed by FullyQualifiedModule from calling method. This is the only place where guid will be involved. if (moduleVersionRequired && modulePath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) { tempModuleInfo.SetVersion(ModuleIntrinsics.GetManifestModuleVersion(modulePath)); tempModuleInfo.SetGuid(ModuleIntrinsics.GetManifestGuid(modulePath)); } - foreach (var pair in exportedCommands) + foreach (KeyValuePair pair in exportedCommands) { - var commandName = pair.Key; - var commandTypes = pair.Value; + string commandName = pair.Key; + CommandTypes commandTypes = pair.Value; - if (commandPattern.IsMatch(commandName)) + if (commandPattern.IsMatch(commandName) || + (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(commandName, pattern)) || + (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(commandName), StringComparison.OrdinalIgnoreCase))) { bool shouldExportCommand = true; @@ -445,7 +576,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe moduleCompareName = commandEntry.PSSnapIn.Name; } - if (String.Equals(moduleShortName, moduleCompareName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(moduleShortName, moduleCompareName, StringComparison.OrdinalIgnoreCase)) { if (commandEntry.Visibility == SessionStateEntryVisibility.Private) { @@ -464,6 +595,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe Module = tempModuleInfo }; } + if ((commandTypes & CommandTypes.Cmdlet) == CommandTypes.Cmdlet) { yield return new CmdletInfo(commandName, implementingType: null, helpFile: null, PSSnapin: null, context: context) @@ -471,6 +603,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe Module = tempModuleInfo }; } + if ((commandTypes & CommandTypes.Function) == CommandTypes.Function) { yield return new FunctionInfo(commandName, ScriptBlock.EmptyScriptBlock, context) @@ -478,6 +611,7 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe Module = tempModuleInfo }; } + if ((commandTypes & CommandTypes.Configuration) == CommandTypes.Configuration) { yield return new ConfigurationInfo(commandName, ScriptBlock.EmptyScriptBlock, context) @@ -485,19 +619,43 @@ internal static IEnumerable GetMatchingCommands(string pattern, Exe Module = tempModuleInfo }; } - if ((commandTypes & CommandTypes.Workflow) == CommandTypes.Workflow) - { - yield return new WorkflowInfo(commandName, ScriptBlock.EmptyScriptBlock, context) - { - Module = tempModuleInfo - }; - } } } } } } } + + /// + /// Returns abbreviated version of a command name. + /// + /// Name of the command to transform. + /// Abbreviated version of the command name. + internal static string AbbreviateName(string commandName) + { + // Use default size of 6 which represents expected average abbreviation length + StringBuilder abbreviation = new StringBuilder(6); + foreach (char c in commandName) + { + if (char.IsUpper(c) || c == '-') + { + abbreviation.Append(c); + } + } + + return abbreviation.ToString(); + } } -} + internal struct CommandScore + { + public CommandScore(CommandInfo command, int score) + { + Command = command; + Score = score; + } + + public CommandInfo Command; + public int Score; + } +} diff --git a/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs index 68aff2813ba..ceb5dd3c2e3 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Management.Automation; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Management.Automation.Security; // // Now define the set of commands for manipulating modules. @@ -18,7 +18,7 @@ namespace Microsoft.PowerShell.Commands /// /// Implements a cmdlet that creates a dynamic module from a scriptblock.. /// - [Cmdlet(VerbsCommon.New, "Module", DefaultParameterSetName = "ScriptBlock", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141554")] + [Cmdlet(VerbsCommon.New, "Module", DefaultParameterSetName = "ScriptBlock", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096698")] [OutputType(typeof(PSModuleInfo))] public sealed class NewModuleCommand : ModuleCmdletBase { @@ -28,9 +28,11 @@ 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; /// @@ -41,12 +43,17 @@ public string Name [ValidateNotNull] public ScriptBlock ScriptBlock { - get { return _scriptBlock; } + get + { + return _scriptBlock; + } + set { _scriptBlock = value; } } + private ScriptBlock _scriptBlock; /// @@ -57,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) @@ -71,9 +83,9 @@ public string[] Function BaseFunctionPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _functionImportList; } } - private string[] _functionImportList = Utils.EmptyArray(); + + private string[] _functionImportList = Array.Empty(); /// /// This parameter specifies the patterns matching the cmdlets to import from the module... @@ -83,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) @@ -97,9 +114,9 @@ public string[] Cmdlet BaseCmdletPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - get { return _cmdletImportList; } } - private string[] _cmdletImportList = Utils.EmptyArray(); + + private string[] _cmdletImportList = Array.Empty(); /// /// This parameter causes the session state instance to be written... @@ -108,8 +125,10 @@ public string[] Cmdlet public SwitchParameter ReturnResult { get { return (SwitchParameter)_returnResult; } + set { _returnResult = value; } } + private bool _returnResult; /// @@ -119,12 +138,14 @@ public SwitchParameter ReturnResult public SwitchParameter AsCustomObject { get { return (SwitchParameter)_asCustomObject; } + set { _asCustomObject = value; } } + private bool _asCustomObject; /// - /// The arguments to pass to the scriptblock used to create the module + /// The arguments to pass to the scriptblock used to create the module. /// [Parameter(ValueFromRemainingArguments = true)] [Alias("Args")] @@ -132,8 +153,10 @@ public SwitchParameter AsCustomObject public object[] ArgumentList { get { return _arguments; } + set { _arguments = value; } } + private object[] _arguments; /// @@ -144,8 +167,30 @@ protected override void EndProcessing() // Create a module from a scriptblock... if (_scriptBlock != null) { + // 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 (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(); - if (String.IsNullOrEmpty(_name)) + if (string.IsNullOrEmpty(_name)) { _name = PSModuleInfo.DynamicModulePrefixString + gs; } @@ -204,10 +249,11 @@ protected override void EndProcessing() { Context.Modules.DecrementModuleNestingCount(); } + return; } } } #endregion -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs index 981c9b19af4..9346e7caa97 100644 --- a/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/NewModuleManifestCommand.cs @@ -1,107 +1,118 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Linq; -using System.Text; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; -using System.Reflection; using System.IO; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Text; using Dbg = System.Management.Automation.Diagnostics; // // Now define the set of commands for manipulating modules. // + namespace Microsoft.PowerShell.Commands { #region New-ModuleManifest /// /// Cmdlet to create a new module manifest file. /// - [Cmdlet(VerbsCommon.New, "ModuleManifest", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141555")] + [Cmdlet(VerbsCommon.New, "ModuleManifest", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096487")] [OutputType(typeof(string))] public sealed class NewModuleManifestCommand : PSCmdlet { /// - /// The output path for the generated file... + /// Gets or sets the output path for the generated file. /// [Parameter(Mandatory = true, Position = 0)] public string Path { get { return _path; } + set { _path = value; } } + private string _path; /// - /// Sets the list of files to load by default... + /// Gets or sets the list of files to load by default. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] NestedModules { get { return _nestedModules; } + set { _nestedModules = value; } } + private object[] _nestedModules; /// - /// Set the GUID in the manifest file + /// Gets or sets the GUID in the manifest file. /// [Parameter] public Guid Guid { get { return _guid; } + set { _guid = value; } } + private Guid _guid = Guid.NewGuid(); /// - /// Set the author string in the manifest + /// Gets or sets the author string in the manifest. /// [Parameter] [AllowEmptyString] public string Author { get { return _author; } + set { _author = value; } } + private string _author; /// - /// Set the company name in the manifest + /// Gets or sets the company name in the manifest. /// [Parameter] [AllowEmptyString] public string CompanyName { get { return _companyName; } + set { _companyName = value; } } - private string _companyName = ""; + + private string _companyName = string.Empty; /// - /// Set the copyright string in the module manifest + /// Gets or sets the copyright string in the module manifest. /// [Parameter] [AllowEmptyString] public string Copyright { get { return _copyright; } + set { _copyright = value; } } + private string _copyright; /// - /// Set the module version... + /// Gets or sets the root module. /// [Parameter] [AllowEmptyString] @@ -109,390 +120,433 @@ public string Copyright public string RootModule { get { return _rootModule; } + set { _rootModule = value; } } + private string _rootModule = null; /// - /// Set the module version... + /// Gets or sets the module version. /// [Parameter] [ValidateNotNull] public Version ModuleVersion { get { return _moduleVersion; } + set { _moduleVersion = value; } } + private Version _moduleVersion = new Version(0, 0, 1); /// - /// Set the module description + /// Gets or sets the module description. /// [Parameter] [AllowEmptyString] public string Description { get { return _description; } + set { _description = value; } } + private string _description; /// - /// Set the ProcessorArchitecture required by this module + /// Gets or sets the ProcessorArchitecture required by this module. /// [Parameter] public ProcessorArchitecture ProcessorArchitecture { - get { return _processorArchitecture.HasValue ? _processorArchitecture.Value : ProcessorArchitecture.None; } + get { return _processorArchitecture ?? ProcessorArchitecture.None; } + set { _processorArchitecture = value; } } + private ProcessorArchitecture? _processorArchitecture = null; /// - /// Set the PowerShell version required by this module + /// Gets or sets the PowerShell version required by this module. /// [Parameter] public Version PowerShellVersion { get { return _powerShellVersion; } + set { _powerShellVersion = value; } } + private Version _powerShellVersion = null; /// - /// Set the CLR version required by the module. + /// Gets or sets the CLR version required by the module. /// [Parameter] public Version ClrVersion { get { return _ClrVersion; } + set { _ClrVersion = value; } } + private Version _ClrVersion = null; /// - /// Set the version of .NET Framework required by the module. + /// Gets or sets the version of .NET Framework required by the module. /// [Parameter] public Version DotNetFrameworkVersion { get { return _DotNetFrameworkVersion; } + set { _DotNetFrameworkVersion = value; } } + private Version _DotNetFrameworkVersion = null; /// - /// Set the name of PowerShell host required by the module. + /// Gets or sets the name of PowerShell host required by the module. /// [Parameter] public string PowerShellHostName { get { return _PowerShellHostName; } + set { _PowerShellHostName = value; } } + private string _PowerShellHostName = null; /// - /// Set the version of PowerShell host required by the module. + /// Gets or sets the version of PowerShell host required by the module. /// [Parameter] public Version PowerShellHostVersion { get { return _PowerShellHostVersion; } + set { _PowerShellHostVersion = value; } } + private Version _PowerShellHostVersion = null; /// - /// Sets the list of Dependencies for the module + /// Gets or sets the list of Dependencies for the module. /// [Parameter] [ArgumentTypeConverter(typeof(ModuleSpecification[]))] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] RequiredModules { get { return _requiredModules; } + set { _requiredModules = value; } } + private object[] _requiredModules; /// - /// Sets the list of types files for the module + /// Gets or sets the list of types files for the module. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] TypesToProcess { get { return _types; } + set { _types = value; } } + private string[] _types; /// - /// Sets the list of formats files for the module + /// Gets or sets the list of formats files for the module. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] FormatsToProcess { get { return _formats; } + set { _formats = value; } } + private string[] _formats; /// - /// Sets the list of ps1 scripts to run in the session state of the import-module invocation. + /// Gets or sets the list of ps1 scripts to run in the session state of the import-module invocation. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] ScriptsToProcess { get { return _scripts; } + set { _scripts = value; } } + private string[] _scripts; /// - /// Set the list of assemblies to load for this module. + /// Gets or sets the list of assemblies to load for this module. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] RequiredAssemblies { get { return _requiredAssemblies; } + set { _requiredAssemblies = value; } } + private string[] _requiredAssemblies; /// - /// Specify any additional files used by this module. + /// Gets or sets the additional files used by this module. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] FileList { get { return _miscFiles; } + set { _miscFiles = value; } } + private string[] _miscFiles; /// - /// List of other modules included with this module. + /// Gets or sets the list of other modules included with this module. /// Like the RequiredModules key, this list can be a simple list of module names or a complex list of module hashtables. /// [Parameter] [AllowEmptyCollection] [ArgumentTypeConverter(typeof(ModuleSpecification[]))] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public object[] ModuleList { get { return _moduleList; } + set { _moduleList = value; } } + private object[] _moduleList; /// - /// Specify any functions to export from this manifest. + /// Gets or sets the functions to export from this manifest. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] FunctionsToExport { get { return _exportedFunctions; } + set { _exportedFunctions = value; } } + private string[] _exportedFunctions; /// - /// Specify any aliases to export from this manifest. + /// Gets or sets the aliases to export from this manifest. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] AliasesToExport { get { return _exportedAliases; } + set { _exportedAliases = value; } } + private string[] _exportedAliases; /// - /// Specify any variables to export from this manifest. + /// Gets or sets the variables to export from this manifest. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] VariablesToExport { get { return _exportedVariables; } + set { _exportedVariables = value; } } + private string[] _exportedVariables = new string[] { "*" }; /// - /// Specify any cmdlets to export from this manifest. + /// Gets or sets the cmdlets to export from this manifest. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] CmdletsToExport { get { return _exportedCmdlets; } + set { _exportedCmdlets = value; } } + private string[] _exportedCmdlets; /// - /// Specify any dsc resources to export from this manifest. + /// Gets or sets the dsc resources to export from this manifest. /// [Parameter] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] DscResourcesToExport { get { return _dscResourcesToExport; } + set { _dscResourcesToExport = value; } } + private string[] _dscResourcesToExport; /// - /// Specify compatible PSEditions of this module. + /// Gets or sets the compatible PSEditions of this module. /// [Parameter] [AllowEmptyCollection] [ValidateSet("Desktop", "Core")] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] CompatiblePSEditions { get { return _compatiblePSEditions; } + set { _compatiblePSEditions = value; } } + private string[] _compatiblePSEditions; /// - /// Specify any module-specific private data here. + /// Gets or sets the module-specific private data here. /// [Parameter(Mandatory = false)] [AllowNull] public object PrivateData { get { return _privateData; } + set { _privateData = value; } } + private object _privateData; /// - /// Specify any Tags. + /// Gets or sets the Tags. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", - "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Tags { get; set; } /// - /// Specify the ProjectUri. + /// Gets or sets the ProjectUri. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] public Uri ProjectUri { get; set; } /// - /// Specify the LicenseUri. + /// Gets or sets the LicenseUri. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] public Uri LicenseUri { get; set; } /// - /// Specify the IconUri. + /// Gets or sets the IconUri. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] public Uri IconUri { get; set; } /// - /// Specify the ReleaseNotes. + /// Gets or sets the ReleaseNotes. /// [Parameter(Mandatory = false)] [ValidateNotNullOrEmpty] public string ReleaseNotes { get; set; } /// - /// Specify the HelpInfo URI + /// Gets or sets whether or not the module is a prerelease. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public string Prerelease { get; set; } + + /// + /// Gets or sets whether or not the module requires explicit user acceptance for install/update/save. + /// + [Parameter] + public SwitchParameter RequireLicenseAcceptance { get; set; } + + /// + /// Gets or sets the external module dependencies. + /// + [Parameter] + [ValidateNotNullOrEmpty] + public string[] ExternalModuleDependencies { get; set; } + + /// + /// Gets or sets the HelpInfo URI. /// [Parameter] [AllowNull] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public string HelpInfoUri { get { return _helpInfoUri; } + set { _helpInfoUri = value; } } + private string _helpInfoUri; /// - /// This parameter causes the module manifest string to be to the output stream... + /// Gets or sets whether the module manifest string should go to the output stream. /// [Parameter] public SwitchParameter PassThru { get { return (SwitchParameter)_passThru; } + set { _passThru = value; } } + private bool _passThru; /// - /// Specify the Default Command Prefix + /// Gets or sets the Default Command Prefix. /// [Parameter] [AllowNull] public string DefaultCommandPrefix { get { return _defaultCommandPrefix; } + set { _defaultCommandPrefix = value; } } + private string _defaultCommandPrefix; - private string _indent = ""; + private string _indent = string.Empty; /// /// Return a single-quoted string. Any embedded single quotes will be doubled. /// - /// The string to quote - /// The quoted string - private string QuoteName(string name) + /// The string to quote. + /// The quoted string. + private static string QuoteName(string name) { if (name == null) return "''"; - return ("'" + name.ToString().Replace("'", "''") + "'"); + return ("'" + name.Replace("'", "''") + "'"); } /// - /// Return a single-quoted string using the AbsoluteUri member to ensure it is escaped correctly + /// Return a single-quoted string using the AbsoluteUri member to ensure it is escaped correctly. /// - /// The Uri to quote - /// The quoted AbsoluteUri - private string QuoteName(Uri name) + /// The Uri to quote. + /// The quoted AbsoluteUri. + private static string QuoteName(Uri name) { if (name == null) return "''"; @@ -500,11 +554,11 @@ private string QuoteName(Uri name) } /// - /// Return a single-quoted string from a Version object + /// Return a single-quoted string from a Version object. /// - /// The Version object to quote - /// The quoted Version string - private string QuoteName(Version name) + /// The Version object to quote. + /// The quoted Version string. + private static string QuoteName(Version name) { if (name == null) return "''"; @@ -515,10 +569,10 @@ private string QuoteName(Version name) /// Takes a collection of strings and returns the collection /// quoted. /// - /// The list to quote - /// Streamwriter to get end of line character from - /// The quoted list - private string QuoteNames(IEnumerable names, StreamWriter streamWriter) + /// The list to quote. + /// Streamwriter to get end of line character from. + /// The quoted list. + private static string QuoteNames(IEnumerable names, StreamWriter streamWriter) { if (names == null) return "@()"; @@ -548,9 +602,11 @@ private string QuoteNames(IEnumerable names, StreamWriter streamWriter) result.Append(" "); offset = 15 + quotedString.Length; } + result.Append(quotedString); } } + if (result.Length == 0) return "@()"; @@ -566,13 +622,13 @@ private string QuoteNames(IEnumerable names, StreamWriter streamWriter) /// /// /// - private IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) + private static IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) { - if (null != moduleSpecs) + if (moduleSpecs != null) { foreach (object spec in moduleSpecs) { - if (!(spec is Hashtable)) + if (spec is not Hashtable) { yield return spec.ToString(); } @@ -586,12 +642,12 @@ private IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) /// /// Takes a collection of "module specifications" (string or hashtable) - /// and returns the collection as a string that can be inserted into a module manifest + /// and returns the collection as a string that can be inserted into a module manifest. /// - /// The list to quote - /// Streamwriter to get end of line character from - /// The quoted list - private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) + /// The list to quote. + /// Streamwriter to get end of line character from. + /// The quoted list. + private static string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) { StringBuilder result = new StringBuilder(); result.Append("@("); @@ -606,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); @@ -618,6 +673,7 @@ private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) result.Append(streamWriter.NewLine); result.Append(" "); } + firstModule = false; if ((moduleSpecification.Guid == null) && (moduleSpecification.Version == null) && (moduleSpecification.MaximumVersion == null) && (moduleSpecification.RequiredVersion == null)) @@ -660,12 +716,12 @@ private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) result.Append("; "); } - result.Append("}"); + result.Append('}'); } } } - result.Append(")"); + result.Append(')'); return result.ToString(); } @@ -673,9 +729,9 @@ private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) /// Takes a collection of file names and returns the collection /// quoted. /// - /// The list to quote - /// Streamwriter to get end of line character from - /// The quoted list + /// The list to quote. + /// Streamwriter to get end of line character from. + /// The quoted list. private string QuoteFiles(IEnumerable names, StreamWriter streamWriter) { List resolvedPaths = new List(); @@ -708,9 +764,9 @@ private string QuoteFiles(IEnumerable names, StreamWriter streamWriter) ///// This is the allowed file extension, any other extension will give an error. ///// Streamwriter to get end of line character from ///// The item of the manifest file for which names are being resolved. - ///// The quoted list - //private string QuoteFilesWithWildcard(string basePath, IEnumerable names, string allowedExtension, StreamWriter streamWriter, string item) - //{ + ///// The quoted list. + // private string QuoteFilesWithWildcard(string basePath, IEnumerable names, string allowedExtension, StreamWriter streamWriter, string item) + // { // if (names != null) // { // foreach (string name in names) @@ -771,7 +827,7 @@ private string QuoteFiles(IEnumerable names, StreamWriter streamWriter) // } // return QuoteNames(names, streamWriter); - //} + // } /// /// Glob a set of files then resolve them to relative paths. @@ -806,6 +862,7 @@ private List TryResolveFilePath(string filePath) { adjustedPath = adjustedPath.Substring(2); } + result.Add(adjustedPath); } } @@ -822,31 +879,30 @@ private List TryResolveFilePath(string filePath) /// for a particular key. It returns a formatted string that includes /// a comment describing the key as well as the key and its value. /// - /// The manifest key to use - /// resourceString that holds the message - /// The formatted manifest fragment - /// Streamwriter to get end of line character from + /// The manifest key to use. + /// ResourceString that holds the message. + /// The formatted manifest fragment. + /// Streamwriter to get end of line character from. /// 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)) { insert = " " + insert; } - return String.Format(CultureInfo.InvariantCulture, "#{0}{1}", insert, streamWriter.NewLine); + + return string.Format(CultureInfo.InvariantCulture, "#{0}{1}", insert, streamWriter.NewLine); } /// @@ -890,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"); @@ -906,9 +959,9 @@ protected override void EndProcessing() ValidateUriParameterValue(new Uri(_helpInfoUri), "HelpInfoUri"); } - if (CompatiblePSEditions != null && (CompatiblePSEditions.Distinct(StringComparer.OrdinalIgnoreCase).Count() != CompatiblePSEditions.Count())) + 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); @@ -939,17 +992,17 @@ protected override void EndProcessing() // Now open the output file... PathUtils.MasterStreamOpen( - cmdlet : this, - filePath : filePath, - resolvedEncoding : new UTF8Encoding(encoderShouldEmitUTF8Identifier : false), - defaultEncoding : false, - Append : false, - Force : false, - NoClobber : false, - fileStream : out fileStream, - streamWriter : out streamWriter, - readOnlyFileInfo : out readOnlyFileInfo, - isLiteralPath : false + cmdlet: this, + filePath: filePath, + resolvedEncoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), + defaultEncoding: false, + Append: false, + Force: false, + NoClobber: false, + fileStream: out fileStream, + streamWriter: out streamWriter, + readOnlyFileInfo: out readOnlyFileInfo, + isLiteralPath: false ); try @@ -957,85 +1010,84 @@ protected override void EndProcessing() StringBuilder result = new StringBuilder(); // Insert the formatted manifest header... - result.Append(ManifestComment("", streamWriter)); + result.Append(ManifestComment(string.Empty, streamWriter)); result.Append(ManifestComment(StringUtil.Format(Modules.ManifestHeaderLine1, System.IO.Path.GetFileNameWithoutExtension(filePath)), streamWriter)); - result.Append(ManifestComment("", streamWriter)); + result.Append(ManifestComment(string.Empty, streamWriter)); result.Append(ManifestComment(StringUtil.Format(Modules.ManifestHeaderLine2, _author), streamWriter)); - result.Append(ManifestComment("", streamWriter)); + result.Append(ManifestComment(string.Empty, streamWriter)); result.Append(ManifestComment(StringUtil.Format(Modules.ManifestHeaderLine3, DateTime.Now.ToString("d", CultureInfo.CurrentCulture)), streamWriter)); - result.Append(ManifestComment("", streamWriter)); + result.Append(ManifestComment(string.Empty, streamWriter)); result.Append(streamWriter.NewLine); result.Append("@{"); result.Append(streamWriter.NewLine); result.Append(streamWriter.NewLine); - if (_rootModule == null) - _rootModule = String.Empty; + _rootModule ??= string.Empty; - BuildModuleManifest(result, "RootModule", Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); + BuildModuleManifest(result, nameof(RootModule), Modules.RootModule, !string.IsNullOrEmpty(_rootModule), () => QuoteName(_rootModule), streamWriter); - BuildModuleManifest(result, "ModuleVersion", Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion), streamWriter); + BuildModuleManifest(result, nameof(ModuleVersion), Modules.ModuleVersion, _moduleVersion != null && !string.IsNullOrEmpty(_moduleVersion.ToString()), () => QuoteName(_moduleVersion), streamWriter); - BuildModuleManifest(result, "CompatiblePSEditions", Modules.CompatiblePSEditions, _compatiblePSEditions != null && _compatiblePSEditions.Length > 0, () => QuoteNames(_compatiblePSEditions, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(CompatiblePSEditions), Modules.CompatiblePSEditions, _compatiblePSEditions != null && _compatiblePSEditions.Length > 0, () => QuoteNames(_compatiblePSEditions, streamWriter), streamWriter); - BuildModuleManifest(result, "GUID", Modules.GUID, !string.IsNullOrEmpty(_guid.ToString()), () => QuoteName(_guid.ToString()), streamWriter); + BuildModuleManifest(result, nameof(Modules.GUID), Modules.GUID, !string.IsNullOrEmpty(_guid.ToString()), () => QuoteName(_guid.ToString()), streamWriter); - BuildModuleManifest(result, "Author", Modules.Author, !string.IsNullOrEmpty(_author), () => QuoteName(Author), streamWriter); + BuildModuleManifest(result, nameof(Author), Modules.Author, !string.IsNullOrEmpty(_author), () => QuoteName(Author), streamWriter); - BuildModuleManifest(result, "CompanyName", Modules.CompanyName, !string.IsNullOrEmpty(_companyName), () => QuoteName(_companyName), streamWriter); + BuildModuleManifest(result, nameof(CompanyName), Modules.CompanyName, !string.IsNullOrEmpty(_companyName), () => QuoteName(_companyName), streamWriter); - BuildModuleManifest(result, "Copyright", Modules.Copyright, !string.IsNullOrEmpty(_copyright), () => QuoteName(_copyright), streamWriter); + BuildModuleManifest(result, nameof(Copyright), Modules.Copyright, !string.IsNullOrEmpty(_copyright), () => QuoteName(_copyright), streamWriter); - BuildModuleManifest(result, "Description", Modules.Description, !string.IsNullOrEmpty(_description), () => QuoteName(_description), streamWriter); + BuildModuleManifest(result, nameof(Description), Modules.Description, !string.IsNullOrEmpty(_description), () => QuoteName(_description), streamWriter); - BuildModuleManifest(result, "PowerShellVersion", Modules.PowerShellVersion, _powerShellVersion != null && !string.IsNullOrEmpty(_powerShellVersion.ToString()), () => QuoteName(_powerShellVersion), streamWriter); + BuildModuleManifest(result, nameof(PowerShellVersion), Modules.PowerShellVersion, _powerShellVersion != null && !string.IsNullOrEmpty(_powerShellVersion.ToString()), () => QuoteName(_powerShellVersion), streamWriter); - BuildModuleManifest(result, "PowerShellHostName", Modules.PowerShellHostName, !string.IsNullOrEmpty(_PowerShellHostName), () => QuoteName(_PowerShellHostName), streamWriter); + BuildModuleManifest(result, nameof(PowerShellHostName), Modules.PowerShellHostName, !string.IsNullOrEmpty(_PowerShellHostName), () => QuoteName(_PowerShellHostName), streamWriter); - BuildModuleManifest(result, "PowerShellHostVersion", Modules.PowerShellHostVersion, _PowerShellHostVersion != null && !string.IsNullOrEmpty(_PowerShellHostVersion.ToString()), () => QuoteName(_PowerShellHostVersion), streamWriter); + BuildModuleManifest(result, nameof(PowerShellHostVersion), Modules.PowerShellHostVersion, _PowerShellHostVersion != null && !string.IsNullOrEmpty(_PowerShellHostVersion.ToString()), () => QuoteName(_PowerShellHostVersion), streamWriter); - BuildModuleManifest(result, "DotNetFrameworkVersion", StringUtil.Format(Modules.DotNetFrameworkVersion, Modules.PrerequisiteForDesktopEditionOnly), _DotNetFrameworkVersion != null && !string.IsNullOrEmpty(_DotNetFrameworkVersion.ToString()), () => QuoteName(_DotNetFrameworkVersion), streamWriter); + BuildModuleManifest(result, nameof(DotNetFrameworkVersion), StringUtil.Format(Modules.DotNetFrameworkVersion, Modules.PrerequisiteForDesktopEditionOnly), _DotNetFrameworkVersion != null && !string.IsNullOrEmpty(_DotNetFrameworkVersion.ToString()), () => QuoteName(_DotNetFrameworkVersion), streamWriter); - BuildModuleManifest(result, "CLRVersion", StringUtil.Format(Modules.CLRVersion, Modules.PrerequisiteForDesktopEditionOnly), _ClrVersion != null && !string.IsNullOrEmpty(_ClrVersion.ToString()), () => QuoteName(_ClrVersion), streamWriter); + BuildModuleManifest(result, nameof(ClrVersion), StringUtil.Format(Modules.CLRVersion, Modules.PrerequisiteForDesktopEditionOnly), _ClrVersion != null && !string.IsNullOrEmpty(_ClrVersion.ToString()), () => QuoteName(_ClrVersion), streamWriter); - BuildModuleManifest(result, "ProcessorArchitecture", Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture.ToString()), streamWriter); + BuildModuleManifest(result, nameof(ProcessorArchitecture), Modules.ProcessorArchitecture, _processorArchitecture.HasValue, () => QuoteName(_processorArchitecture.ToString()), streamWriter); - BuildModuleManifest(result, "RequiredModules", Modules.RequiredModules, _requiredModules != null && _requiredModules.Length > 0, () => QuoteModules(_requiredModules, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(RequiredModules), Modules.RequiredModules, _requiredModules != null && _requiredModules.Length > 0, () => QuoteModules(_requiredModules, streamWriter), streamWriter); - BuildModuleManifest(result, "RequiredAssemblies", Modules.RequiredAssemblies, _requiredAssemblies != null, () => QuoteFiles(_requiredAssemblies, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(RequiredAssemblies), Modules.RequiredAssemblies, _requiredAssemblies != null, () => QuoteFiles(_requiredAssemblies, streamWriter), streamWriter); - BuildModuleManifest(result, "ScriptsToProcess", Modules.ScriptsToProcess, _scripts != null, () => QuoteFiles(_scripts, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(ScriptsToProcess), Modules.ScriptsToProcess, _scripts != null, () => QuoteFiles(_scripts, streamWriter), streamWriter); - BuildModuleManifest(result, "TypesToProcess", Modules.TypesToProcess, _types != null, () => QuoteFiles(_types, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(TypesToProcess), Modules.TypesToProcess, _types != null, () => QuoteFiles(_types, streamWriter), streamWriter); - BuildModuleManifest(result, "FormatsToProcess", Modules.FormatsToProcess, _formats != null, () => QuoteFiles(_formats, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FormatsToProcess), Modules.FormatsToProcess, _formats != null, () => QuoteFiles(_formats, streamWriter), streamWriter); - BuildModuleManifest(result, "NestedModules", Modules.NestedModules, _nestedModules != null, () => QuoteModules(PreProcessModuleSpec(_nestedModules), streamWriter), streamWriter); + BuildModuleManifest(result, nameof(NestedModules), Modules.NestedModules, _nestedModules != null, () => QuoteModules(PreProcessModuleSpec(_nestedModules), streamWriter), streamWriter); - BuildModuleManifest(result, "FunctionsToExport", Modules.FunctionsToExport, true, () => QuoteNames(_exportedFunctions, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FunctionsToExport), Modules.FunctionsToExport, true, () => QuoteNames(_exportedFunctions, streamWriter), streamWriter); - BuildModuleManifest(result, "CmdletsToExport", Modules.CmdletsToExport, true, () => QuoteNames(_exportedCmdlets, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(CmdletsToExport), Modules.CmdletsToExport, true, () => QuoteNames(_exportedCmdlets, streamWriter), streamWriter); - BuildModuleManifest(result, "VariablesToExport", Modules.VariablesToExport, _exportedVariables != null && _exportedVariables.Length > 0, () => QuoteNames(_exportedVariables, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(VariablesToExport), Modules.VariablesToExport, _exportedVariables != null && _exportedVariables.Length > 0, () => QuoteNames(_exportedVariables, streamWriter), streamWriter); - BuildModuleManifest(result, "AliasesToExport", Modules.AliasesToExport, true, () => QuoteNames(_exportedAliases, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(AliasesToExport), Modules.AliasesToExport, true, () => QuoteNames(_exportedAliases, streamWriter), streamWriter); - BuildModuleManifest(result, "DscResourcesToExport", Modules.DscResourcesToExport, _dscResourcesToExport != null && _dscResourcesToExport.Length > 0, () => QuoteNames(_dscResourcesToExport, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(DscResourcesToExport), Modules.DscResourcesToExport, _dscResourcesToExport != null && _dscResourcesToExport.Length > 0, () => QuoteNames(_dscResourcesToExport, streamWriter), streamWriter); - BuildModuleManifest(result, "ModuleList", Modules.ModuleList, _moduleList != null, () => QuoteModules(_moduleList, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(ModuleList), Modules.ModuleList, _moduleList != null, () => QuoteModules(_moduleList, streamWriter), streamWriter); - BuildModuleManifest(result, "FileList", Modules.FileList, _miscFiles != null, () => QuoteFiles(_miscFiles, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(FileList), Modules.FileList, _miscFiles != null, () => QuoteFiles(_miscFiles, streamWriter), streamWriter); BuildPrivateDataInModuleManifest(result, streamWriter); - BuildModuleManifest(result, "HelpInfoURI", Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName((_helpInfoUri != null) ? new Uri(_helpInfoUri) : null), streamWriter); + BuildModuleManifest(result, nameof(Modules.HelpInfoURI), Modules.HelpInfoURI, !string.IsNullOrEmpty(_helpInfoUri), () => QuoteName((_helpInfoUri != null) ? new Uri(_helpInfoUri) : null), streamWriter); - BuildModuleManifest(result, "DefaultCommandPrefix", Modules.DefaultCommandPrefix, !string.IsNullOrEmpty(_defaultCommandPrefix), () => QuoteName(_defaultCommandPrefix), streamWriter); + 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(); @@ -1044,6 +1096,7 @@ protected override void EndProcessing() { WriteObject(strResult); } + streamWriter.Write(strResult); } finally @@ -1104,7 +1157,7 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter { WriteWarning(Modules.PrivateDataValueTypeShouldBeHashTableWarning); - BuildModuleManifest(result, "PrivateData", Modules.PrivateData, _privateData != null, + BuildModuleManifest(result, nameof(PrivateData), Modules.PrivateData, _privateData != null, () => QuoteName((string)LanguagePrimitives.ConvertTo(_privateData, typeof(string), CultureInfo.InvariantCulture)), streamWriter); } @@ -1122,11 +1175,14 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter _indent = " "; - BuildModuleManifest(result, "Tags", Modules.Tags, Tags != null && Tags.Length > 0, () => QuoteNames(Tags, streamWriter), streamWriter); - BuildModuleManifest(result, "LicenseUri", Modules.LicenseUri, LicenseUri != null, () => QuoteName(LicenseUri), streamWriter); - BuildModuleManifest(result, "ProjectUri", Modules.ProjectUri, ProjectUri != null, () => QuoteName(ProjectUri), streamWriter); - BuildModuleManifest(result, "IconUri", Modules.IconUri, IconUri != null, () => QuoteName(IconUri), streamWriter); - BuildModuleManifest(result, "ReleaseNotes", Modules.ReleaseNotes, !string.IsNullOrEmpty(ReleaseNotes), () => QuoteName(ReleaseNotes), streamWriter); + BuildModuleManifest(result, nameof(Tags), Modules.Tags, Tags != null && Tags.Length > 0, () => QuoteNames(Tags, streamWriter), streamWriter); + BuildModuleManifest(result, nameof(LicenseUri), Modules.LicenseUri, LicenseUri != null, () => QuoteName(LicenseUri), streamWriter); + BuildModuleManifest(result, nameof(ProjectUri), Modules.ProjectUri, ProjectUri != null, () => QuoteName(ProjectUri), 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, () => RequireLicenseAcceptance.IsPresent ? "$true" : "$false", streamWriter); + BuildModuleManifest(result, nameof(ExternalModuleDependencies), Modules.ExternalModuleDependencies, ExternalModuleDependencies != null && ExternalModuleDependencies.Length > 0, () => QuoteNames(ExternalModuleDependencies, streamWriter), streamWriter); result.Append(" } "); result.Append(ManifestComment(StringUtil.Format(Modules.EndOfManifestHashTable, "PSData"), streamWriter)); @@ -1146,7 +1202,7 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter result.Append("} "); result.Append(ManifestComment(StringUtil.Format(Modules.EndOfManifestHashTable, "PrivateData"), streamWriter)); - _indent = ""; + _indent = string.Empty; result.Append(streamWriter.NewLine); } @@ -1154,7 +1210,7 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter private void ValidateUriParameterValue(Uri uri, string parameterName) { - Dbg.Assert(!String.IsNullOrWhiteSpace(parameterName), "parameterName should not be null or whitespace"); + Dbg.Assert(!string.IsNullOrWhiteSpace(parameterName), "parameterName should not be null or whitespace"); if (uri != null && !Uri.IsWellFormedUriString(uri.AbsoluteUri, UriKind.Absolute)) { @@ -1167,5 +1223,5 @@ private void ValidateUriParameterValue(Uri uri, string parameterName) } } -#endregion -} // Microsoft.PowerShell.Commands + #endregion +} diff --git a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs index 77780ca7ef4..4315f8bcb2b 100644 --- a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs +++ b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -11,7 +10,9 @@ using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Reflection; + using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -27,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) { @@ -44,8 +45,8 @@ internal static void SetDefaultDynamicNameAndPath(PSModuleInfo module) /// /// This object describes a PowerShell module... /// - /// The absolute path to the module - /// The execution context for this engine instance + /// The absolute path to the module. + /// The execution context for this engine instance. /// The module's sessionstate object - this may be null if the module is a dll. internal PSModuleInfo(string path, ExecutionContext context, SessionState sessionState) : this(null, path, context, sessionState) @@ -55,9 +56,23 @@ internal PSModuleInfo(string path, ExecutionContext context, SessionState sessio /// /// This object describes a PowerShell module... /// - /// The name to use for the module. If null, get it from the path name - /// The absolute path to the module - /// The execution context for this engine instance + /// The name to use for the module. If null, get it from the path name. + /// The absolute path to the module. + /// The execution context for this engine instance. + /// The module's sessionstate object - this may be null if the module is a dll. + /// Language mode for script based modules. + internal PSModuleInfo(string name, string path, ExecutionContext context, SessionState sessionState, PSLanguageMode? languageMode) + : this(name, path, context, sessionState) + { + LanguageMode = languageMode; + } + + /// + /// This object describes a PowerShell module... + /// + /// The name to use for the module. If null, get it from the path name. + /// The absolute path to the module. + /// The execution context for this engine instance. /// The module's sessionstate object - this may be null if the module is a dll. internal PSModuleInfo(string name, string path, ExecutionContext context, SessionState sessionState) { @@ -115,7 +130,7 @@ public PSModuleInfo(ScriptBlock scriptBlock) { if (scriptBlock == null) { - throw PSTraceSource.NewArgumentException("scriptBlock"); + throw PSTraceSource.NewArgumentException(nameof(scriptBlock)); } // Get the ExecutionContext from the thread. @@ -135,6 +150,8 @@ public PSModuleInfo(ScriptBlock scriptBlock) SessionState = new SessionState(context, true, true); SessionState.Internal.Module = this; + LanguageMode = scriptBlock.LanguageMode; + // Now set up the module's session state to be the current session state SessionStateInternal oldSessionState = context.EngineSessionState; try @@ -165,17 +182,31 @@ public PSModuleInfo(ScriptBlock scriptBlock) } } + /// + /// Specifies the language mode for script based modules. + /// + internal PSLanguageMode? LanguageMode + { + get; + set; + } = PSLanguageMode.FullLanguage; + + /// + /// Set to true when script module automatically exports all functions by default. + /// + internal bool ModuleAutoExportsAllFunctions { get; set; } + internal bool ModuleHasPrivateMembers { get; set; } /// - /// True if the module had errors during loading + /// True if the module had errors during loading. /// internal bool HadErrorsLoading { get; set; } /// /// ToString() implementation which returns the name of the module. /// - /// The name of the module + /// The name of the module. public override string ToString() { return this.Name; @@ -189,12 +220,12 @@ public override string ToString() /// /// The name of this module. /// - public string Name { get; private set; } = String.Empty; + public string Name { get; private set; } = string.Empty; /// - /// Sets the name property of the PSModuleInfo object + /// Sets the name property of the PSModuleInfo object. /// - /// The name to set it to + /// The name to set it to. internal void SetName(string name) { Name = name; @@ -203,7 +234,7 @@ internal void SetName(string name) /// /// The path to the file that defined this module... /// - public string Path { get; internal set; } = String.Empty; + public string Path { get; internal set; } = string.Empty; /// /// If the module is a binary module or a script module that defines @@ -218,8 +249,9 @@ internal void SetName(string name) /// public string Definition { - get { return _definitionExtent == null ? String.Empty : _definitionExtent.Text; } + get { return _definitionExtent == null ? string.Empty : _definitionExtent.Text; } } + internal IScriptExtent _definitionExtent; /// @@ -228,9 +260,11 @@ public string Definition public string Description { get { return _description; } - set { _description = value ?? String.Empty; } + + set { _description = value ?? string.Empty; } } - private string _description = String.Empty; + + private string _description = string.Empty; /// /// The guid for this module if one was defined in the module manifest. @@ -248,6 +282,8 @@ internal void SetGuid(Guid guid) [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] public string HelpInfoUri { get; private set; } + internal bool IsWindowsPowerShellCompatModule { get; set; } + internal void SetHelpInfoUri(string uri) { HelpInfoUri = uri; @@ -263,14 +299,15 @@ 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; } } + internal void SetModuleBase(string moduleBase) { _moduleBase = moduleBase; } + private string _moduleBase; /// @@ -291,6 +328,7 @@ public object PrivateData SetPSDataPropertiesFromPrivateData(); } } + private object _privateData = null; private void SetPSDataPropertiesFromPrivateData() @@ -302,49 +340,37 @@ private void SetPSDataPropertiesFromPrivateData() ProjectUri = null; IconUri = null; - var privateDataHashTable = _privateData as Hashtable; - if (privateDataHashTable != null) + if (_privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData) { - var psData = privateDataHashTable["PSData"] as Hashtable; - if (psData != null) + var tagsValue = psData["Tags"]; + if (tagsValue is object[] tags && tags.Length > 0) { - object tagsValue = psData["Tags"]; - if (tagsValue != null) - { - var tags = tagsValue as object[]; - if (tags != null && tags.Any()) - { - foreach (var tagString in tags.OfType()) - { - AddToTags(tagString); - } - } - else - { - AddToTags(tagsValue.ToString()); - } - } - - var licenseUri = psData["LicenseUri"] as string; - if (licenseUri != null) + foreach (var tagString in tags.OfType()) { - LicenseUri = GetUriFromString(licenseUri); + AddToTags(tagString); } + } + else if (tagsValue is string tag) + { + AddToTags(tag); + } - var projectUri = psData["ProjectUri"] as string; - if (projectUri != null) - { - ProjectUri = GetUriFromString(projectUri); - } + if (psData["LicenseUri"] is string licenseUri) + { + LicenseUri = GetUriFromString(licenseUri); + } - var iconUri = psData["IconUri"] as string; - if (iconUri != null) - { - IconUri = GetUriFromString(iconUri); - } + if (psData["ProjectUri"] is string projectUri) + { + ProjectUri = GetUriFromString(projectUri); + } - ReleaseNotes = psData["ReleaseNotes"] as string; + if (psData["IconUri"] is string iconUri) + { + IconUri = GetUriFromString(iconUri); } + + ReleaseNotes = psData["ReleaseNotes"] as string; } } @@ -361,10 +387,15 @@ private static Uri GetUriFromString(string uriString) return uri; } + /// + /// Get the experimental features declared in this module. + /// + public IEnumerable ExperimentalFeatures { get; internal set; } = Utils.EmptyReadOnlyCollection(); + /// /// Tags of this module. /// - public IEnumerable Tags + public IEnumerable Tags { get { return _tags; } } @@ -402,14 +433,14 @@ internal void AddToTags(string tag) public Uri RepositorySourceLocation { get; internal set; } /// - /// The version of this module + /// The version of this module. /// public Version Version { get; private set; } = new Version(0, 0); /// - /// Sets the module version + /// Sets the module version. /// - /// the version to set... + /// The version to set... internal void SetVersion(Version version) { Version = version; @@ -422,12 +453,12 @@ 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; } /// - /// Module Author + /// Module Author. /// public string Author { @@ -439,20 +470,26 @@ public string Author /// public ModuleAccessMode AccessMode { - get { return _accessMode; } + get + { + return _accessMode; + } + set { if (_accessMode == ModuleAccessMode.Constant) { throw PSTraceSource.NewInvalidOperationException(); } + _accessMode = value; } } + private ModuleAccessMode _accessMode = ModuleAccessMode.ReadWrite; /// - /// CLR Version + /// CLR Version. /// public Version ClrVersion { @@ -461,25 +498,25 @@ public Version ClrVersion } /// - /// Company Name + /// Company Name. /// - public String CompanyName + public string CompanyName { get; internal set; } /// - /// Copyright + /// Copyright. /// - public String Copyright + public string Copyright { get; internal set; } /// - /// .NET Framework Version + /// .NET Framework Version. /// public Version DotNetFrameworkVersion { @@ -487,6 +524,15 @@ public Version DotNetFrameworkVersion internal set; } + internal Collection DeclaredFunctionExports = null; + internal Collection DeclaredCmdletExports = null; + internal Collection DeclaredAliasExports = null; + internal Collection DeclaredVariableExports = null; + + internal List DetectedFunctionExports = new List(); + internal List DetectedCmdletExports = new List(); + internal Dictionary DetectedAliasExports = new Dictionary(); + /// /// Lists the functions exported by this module... /// @@ -497,18 +543,19 @@ public Dictionary ExportedFunctions Dictionary exports = new Dictionary(StringComparer.OrdinalIgnoreCase); // If the module is not binary, it may also have functions... - if ((DeclaredFunctionExports != null) && (DeclaredFunctionExports.Count > 0)) + if (DeclaredFunctionExports != null) { + if (DeclaredFunctionExports.Count == 0) + { + return exports; + } + foreach (string fn in DeclaredFunctionExports) { FunctionInfo tempFunction = new FunctionInfo(fn, ScriptBlock.EmptyScriptBlock, null) { Module = this }; exports[fn] = tempFunction; } } - else if ((DeclaredFunctionExports != null) && (DeclaredFunctionExports.Count == 0)) - { - return exports; - } else if (SessionState != null) { // If there is no session state object associated with this list, @@ -526,7 +573,7 @@ public Dictionary ExportedFunctions } else { - foreach (var detectedExport in _detectedFunctionExports) + foreach (var detectedExport in DetectedFunctionExports) { if (!exports.ContainsKey(detectedExport)) { @@ -535,12 +582,12 @@ public Dictionary ExportedFunctions } } } + return exports; } } - - 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); @@ -577,31 +624,30 @@ public ReadOnlyDictionary GetExportedTypeDefinitions( } var res = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (this.NestedModules != null) + foreach (var nestedModule in this.NestedModules) { - foreach (var nestedModule in this.NestedModules) + if (nestedModule == this) { - if (nestedModule == this) - { - // this is totally bizzare, but it happens for some reasons for - // Microsoft.Powershell.Workflow.ServiceCore.dll, when there is a workflow defined in a nested module. - // TODO(sevoroby): we should handle possible circular dependencies - continue; - } - - foreach (var typePairs in nestedModule.GetExportedTypeDefinitions()) - { - // The last one name wins! It's the same for command names in nested modules. - // For rootModule C with Two nested modules (A, B) the order is: A, B, C - res[typePairs.Key] = typePairs.Value; - } + // Circular nested modules could happen with ill-organized module structure. + // For example, module folder 'test' has two files: 'test.psd1' and 'test.psm1', and 'test.psd1' has the following content: + // "@{ ModuleVersion = '0.0.1'; RootModule = 'test'; NestedModules = @('test') }" + // Then, 'Import-Module test.psd1 -PassThru' will return a ModuleInfo object with circular nested modules. + continue; } - foreach (var typePairs in _exportedTypeDefinitionsNoNested) + + foreach (var typePairs in nestedModule.GetExportedTypeDefinitions()) { + // The last one name wins! It's the same for command names in nested modules. + // For rootModule C with Two nested modules (A, B) the order is: A, B, C res[typePairs.Key] = typePairs.Value; } } + foreach (var typePairs in _exportedTypeDefinitionsNoNested) + { + res[typePairs.Key] = typePairs.Value; + } + return new ReadOnlyDictionary(res); } @@ -618,57 +664,38 @@ 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)); } /// - /// Prefix + /// Prefix. /// - public String Prefix + public string Prefix { get; internal set; } - internal Collection DeclaredFunctionExports = null; - internal List _detectedFunctionExports = new List(); - - internal List _detectedWorkflowExports = new List(); - /// - /// Add function to the fixed exports list + /// Add function to the fixed exports list. /// - /// the function to add + /// The function to add. internal void AddDetectedFunctionExport(string name) { Dbg.Assert(name != null, "AddDetectedFunctionExport should not be called with a null value"); - if (!_detectedFunctionExports.Contains(name)) + if (!DetectedFunctionExports.Contains(name)) { - _detectedFunctionExports.Add(name); - } - } - - /// - /// Add workflow to the fixed exports list - /// - /// the function to add - internal void AddDetectedWorkflowExport(string name) - { - Dbg.Assert(name != null, "AddDetectedWorkflowExport should not be called with a null value"); - - if (!_detectedWorkflowExports.Contains(name)) - { - _detectedWorkflowExports.Add(name); + DetectedFunctionExports.Add(name); } } @@ -681,18 +708,19 @@ public Dictionary ExportedCmdlets { Dictionary exports = new Dictionary(StringComparer.OrdinalIgnoreCase); - if ((DeclaredCmdletExports != null) && (DeclaredCmdletExports.Count > 0)) + if (DeclaredCmdletExports != null) { + if (DeclaredCmdletExports.Count == 0) + { + return exports; + } + foreach (string fn in DeclaredCmdletExports) { CmdletInfo tempCmdlet = new CmdletInfo(fn, null, null, null, null) { Module = this }; exports[fn] = tempCmdlet; } } - else if ((DeclaredCmdletExports != null) && (DeclaredCmdletExports.Count == 0)) - { - return exports; - } else if ((CompiledExports != null) && (CompiledExports.Count > 0)) { foreach (CmdletInfo cmdlet in CompiledExports) @@ -702,7 +730,7 @@ public Dictionary ExportedCmdlets } else { - foreach (string detectedExport in _detectedCmdletExports) + foreach (string detectedExport in DetectedCmdletExports) { if (!exports.ContainsKey(detectedExport)) { @@ -715,20 +743,18 @@ public Dictionary ExportedCmdlets return exports; } } - internal Collection DeclaredCmdletExports = null; - internal List _detectedCmdletExports = new List(); /// /// Add CmdletInfo to the fixed exports list... /// - /// the cmdlet to add... + /// The cmdlet to add... internal void AddDetectedCmdletExport(string cmdlet) { Dbg.Assert(cmdlet != null, "AddDetectedCmdletExport should not be called with a null value"); - if (!_detectedCmdletExports.Contains(cmdlet)) + if (!DetectedCmdletExports.Contains(cmdlet)) { - _detectedCmdletExports.Add(cmdlet); + DetectedCmdletExports.Add(cmdlet); } } @@ -761,15 +787,6 @@ public Dictionary ExportedCommands } } - Dictionary workflows = this.ExportedWorkflows; - if (workflows != null) - { - foreach (var workflow in workflows) - { - exports[workflow.Key] = workflow.Value; - } - } - Dictionary aliases = this.ExportedAliases; if (aliases != null) { @@ -786,7 +803,7 @@ public Dictionary ExportedCommands /// /// Add CmdletInfo to the fixed exports list... /// - /// the cmdlet to add... + /// The cmdlet to add... internal void AddExportedCmdlet(CmdletInfo cmdlet) { Dbg.Assert(cmdlet != null, "AddExportedCmdlet should not be called with a null value"); @@ -813,19 +830,20 @@ internal List CompiledExports { _compiledExports.Add(ci); } + SessionState.Internal.ExportedCmdlets.Clear(); } + return _compiledExports; } } private readonly List _compiledExports = new List(); - /// /// Add AliasInfo to the fixed exports list... /// - /// the cmdlet to add... + /// The cmdlet to add... internal void AddExportedAlias(AliasInfo aliasInfo) { Dbg.Assert(aliasInfo != null, "AddExportedAlias should not be called with a null value"); @@ -840,11 +858,10 @@ internal void AddExportedAlias(AliasInfo aliasInfo) /// internal List CompiledAliasExports { get; } = new List(); - /// - /// FileList + /// FileList. /// - public IEnumerable FileList + public IEnumerable FileList { get { return _fileList; } } @@ -857,22 +874,41 @@ internal void AddToFileList(string file) } /// - /// CompatiblePSEditions + /// Lists the PowerShell editions this module is compatible with. This should + /// reflect the module manifest the module was loaded with, or if no manifest was given + /// or the key was not in the manifest, this should be an empty collection. This + /// property is never null. /// - public IEnumerable CompatiblePSEditions + public IEnumerable CompatiblePSEditions { get { return _compatiblePSEditions; } } - private List _compatiblePSEditions = new List(); + private readonly List _compatiblePSEditions = new List(); internal void AddToCompatiblePSEditions(string psEdition) { _compatiblePSEditions.Add(psEdition); } + internal void AddToCompatiblePSEditions(IEnumerable psEditions) + { + _compatiblePSEditions.AddRange(psEditions); + } + /// - /// ModuleList + /// Describes whether the module was considered compatible at load time. + /// Any module not on the System32 module path should have this as true. + /// Modules loaded from the System32 module path will have this as true if they + /// have declared edition compatibility with PowerShell 6+. Currently, this field + /// is true for all non-psd1 module files, when it should not be. Being able to + /// load psm1/dll modules from the System32 module path without needing to skip + /// the edition check is considered a bug and should be fixed. + /// + internal bool IsConsideredEditionCompatible { get; set; } = true; + + /// + /// ModuleList. /// public IEnumerable ModuleList { @@ -894,33 +930,34 @@ public ReadOnlyCollection NestedModules { get { - return _readonlyNestedModules ?? - (_readonlyNestedModules = new ReadOnlyCollection(_nestedModules)); + return _readonlyNestedModules ??= new ReadOnlyCollection(_nestedModules); } } + private ReadOnlyCollection _readonlyNestedModules; /// /// Add a module to the list of child modules. /// - /// The module to add + /// The module to add. internal void AddNestedModule(PSModuleInfo nestedModule) { AddModuleToList(nestedModule, _nestedModules); } + private readonly List _nestedModules = new List(); /// - /// PowerShell Host Name + /// PowerShell Host Name. /// - public String PowerShellHostName + public string PowerShellHostName { get; internal set; } /// - /// PowerShell Host Version + /// PowerShell Host Version. /// public Version PowerShellHostVersion { @@ -929,7 +966,7 @@ public Version PowerShellHostVersion } /// - /// PowerShell Version + /// PowerShell Version. /// public Version PowerShellVersion { @@ -938,7 +975,7 @@ public Version PowerShellVersion } /// - /// Processor Architecture + /// Processor Architecture. /// public ProcessorArchitecture ProcessorArchitecture { @@ -947,14 +984,14 @@ public ProcessorArchitecture ProcessorArchitecture } /// - /// Scripts to Process + /// Scripts to Process. /// - public IEnumerable Scripts + public IEnumerable Scripts { get { return _scripts; } } - private List _scripts = new List(); + private List _scripts = new List(); internal void AddScript(string s) { @@ -962,13 +999,14 @@ internal void AddScript(string s) } /// - /// Required Assemblies + /// Required Assemblies. /// - public IEnumerable RequiredAssemblies + public IEnumerable RequiredAssemblies { get { return _requiredAssemblies; } } - private Collection _requiredAssemblies = new Collection(); + + private Collection _requiredAssemblies = new Collection(); internal void AddRequiredAssembly(string assembly) { @@ -983,20 +1021,21 @@ public ReadOnlyCollection RequiredModules { get { - return _readonlyRequiredModules ?? - (_readonlyRequiredModules = new ReadOnlyCollection(_requiredModules)); + return _readonlyRequiredModules ??= new ReadOnlyCollection(_requiredModules); } } + private ReadOnlyCollection _readonlyRequiredModules; /// /// Add a module to the list of required modules. /// - /// The module to add + /// The module to add. internal void AddRequiredModule(PSModuleInfo requiredModule) { AddModuleToList(requiredModule, _requiredModules); } + private List _requiredModules = new List(); /// @@ -1007,26 +1046,27 @@ internal ReadOnlyCollection RequiredModulesSpecification { get { - return _readonlyRequiredModulesSpecification ?? - (_readonlyRequiredModulesSpecification = new ReadOnlyCollection(_requiredModulesSpecification)); + return _readonlyRequiredModulesSpecification ??= new ReadOnlyCollection(_requiredModulesSpecification); } } + private ReadOnlyCollection _readonlyRequiredModulesSpecification; /// - /// Add a module to the list of required modules specification + /// Add a module to the list of required modules specification. /// - /// The module to add + /// The module to add. internal void AddRequiredModuleSpecification(ModuleSpecification requiredModuleSpecification) { _requiredModulesSpecification.Add(requiredModuleSpecification); } + private List _requiredModulesSpecification = new List(); /// - /// Root Module + /// Root Module. /// - public String RootModule + public string RootModule { get; internal set; @@ -1034,9 +1074,9 @@ public String RootModule /// /// This member is used to copy over the RootModule in case the module is a manifest module - /// This is so that only ModuleInfo for modules with type=Manifest have RootModule populated + /// This is so that only ModuleInfo for modules with type=Manifest have RootModule populated. /// - internal String RootModuleForManifest + internal string RootModuleForManifest { get; set; @@ -1054,10 +1094,11 @@ private static void AddModuleToList(PSModuleInfo module, List modu if (m.Path.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) return; } + moduleList.Add(module); } - internal static string[] _builtinVariables = new string[] { "_", "this", "input", "args", "true", "false", "null", + internal static readonly string[] _builtinVariables = new string[] { "_", "this", "input", "args", "true", "false", "null", "PSDefaultParameterValues", "Error", "PSScriptRoot", "PSCommandPath", "MyInvocation", "ExecutionContext", "StackTrace" }; /// @@ -1095,7 +1136,6 @@ public Dictionary ExportedVariables return exportedVariables; } } - internal Collection DeclaredVariableExports = null; /// /// Lists the aliases exported by this module. @@ -1127,9 +1167,9 @@ public Dictionary ExportedAliases if (SessionState == null) { // Check if we detected any - if (_detectedAliasExports.Count > 0) + if (DetectedAliasExports.Count > 0) { - foreach (var pair in _detectedAliasExports) + foreach (var pair in DetectedAliasExports) { string detectedExport = pair.Key; if (!exportedAliases.ContainsKey(detectedExport)) @@ -1159,73 +1199,20 @@ public Dictionary ExportedAliases return exportedAliases; } } - internal Collection DeclaredAliasExports = null; - internal Dictionary _detectedAliasExports = new Dictionary(); /// - /// Add alias to the detected alias list + /// Add alias to the detected alias list. /// - /// the alias to add - /// the command it resolves to + /// The alias to add. + /// The command it resolves to. internal void AddDetectedAliasExport(string name, string value) { Dbg.Assert(name != null, "AddDetectedAliasExport should not be called with a null value"); - _detectedAliasExports[name] = value; - } - - /// - /// Lists the workflows exported by this module. - /// - public Dictionary ExportedWorkflows - { - get - { - Dictionary exportedWorkflows = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if ((DeclaredWorkflowExports != null) && (DeclaredWorkflowExports.Count > 0)) - { - foreach (string fn in DeclaredWorkflowExports) - { - WorkflowInfo tempWf = new WorkflowInfo(fn, ScriptBlock.EmptyScriptBlock, context: null) { Module = this }; - exportedWorkflows[fn] = tempWf; - } - } - if ((DeclaredWorkflowExports != null) && (DeclaredWorkflowExports.Count == 0)) - { - return exportedWorkflows; - } - else - { - // If there is no session state object associated with this list, - // just return a null list of exports. This will be true if the - // module is a compiled module. - if (SessionState == null) - { - foreach (string detectedExport in _detectedWorkflowExports) - { - if (!exportedWorkflows.ContainsKey(detectedExport)) - { - WorkflowInfo tempWf = new WorkflowInfo(detectedExport, ScriptBlock.EmptyScriptBlock, context: null) { Module = this }; - exportedWorkflows[detectedExport] = tempWf; - } - } - return exportedWorkflows; - } - - foreach (WorkflowInfo wi in SessionState.Internal.ExportedWorkflows) - { - exportedWorkflows[wi.Name] = wi; - } - } - - return exportedWorkflows; - } + DetectedAliasExports[name] = value; } - internal Collection DeclaredWorkflowExports = null; /// - /// /// public ReadOnlyCollection ExportedDscResources { @@ -1247,8 +1234,8 @@ public ReadOnlyCollection ExportedDscResources /// /// Returns a new scriptblock bound to this module instance. /// - /// The original scriptblock - /// The new bound scriptblock + /// The original scriptblock. + /// The new bound scriptblock. public ScriptBlock NewBoundScriptBlock(ScriptBlock scriptBlockToBind) { var context = LocalPipeline.GetExecutionContextFromTLS(); @@ -1287,9 +1274,9 @@ internal ScriptBlock NewBoundScriptBlock(ScriptBlock scriptBlockToBind, Executio /// /// Invoke a scriptblock in the context of this module... /// - /// The scriptblock to invoke - /// Arguments to the scriptblock - /// The result of the invocation + /// The scriptblock to invoke. + /// Arguments to the scriptblock. + /// The result of the invocation. public object Invoke(ScriptBlock sb, params object[] args) { if (sb == null) @@ -1309,6 +1296,7 @@ public object Invoke(ScriptBlock sb, params object[] args) // and restore the scriptblocks session state... sb.SessionStateInternal = oldSessionState; } + return result; } @@ -1320,10 +1308,7 @@ public object Invoke(ScriptBlock sb, params object[] args) /// public PSVariable GetVariableFromCallersModule(string variableName) { - if (string.IsNullOrEmpty(variableName)) - { - throw new ArgumentNullException("variableName"); - } + ArgumentException.ThrowIfNullOrEmpty(variableName); var context = LocalPipeline.GetExecutionContextFromTLS(); SessionState callersSessionState = null; @@ -1341,6 +1326,7 @@ public PSVariable GetVariableFromCallersModule(string variableName) break; } } + if (callersSessionState != null) { return callersSessionState.Internal.GetVariable(variableName); @@ -1376,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 @@ -1394,7 +1380,7 @@ internal void CaptureLocals() /// /// Build a custom object out of this module... /// - /// A custom object + /// A custom object. public PSObject AsCustomObject() { if (SessionState == null) @@ -1428,7 +1414,7 @@ public PSObject AsCustomObject() } /// - /// Optional script that is going to be called just before Remove-Module cmdlet removes the module + /// Optional script that is going to be called just before Remove-Module cmdlet removes the module. /// public ScriptBlock OnRemove { get; set; } @@ -1454,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(); @@ -1479,6 +1465,7 @@ public PSModuleInfo Clone() { clone.AddRequiredModule(r); } + foreach (var r in _requiredModulesSpecification) { clone.AddRequiredModuleSpecification(r); @@ -1492,7 +1479,7 @@ public PSModuleInfo Clone() } /// - /// Enables or disables the appdomain module path cache + /// Enables or disables the appdomain module path cache. /// public static bool UseAppDomainLevelModuleCache { get; set; } @@ -1504,7 +1491,6 @@ public static void ClearAppDomainLevelModulePathCache() s_appdomainModulePathCache.Clear(); } - #if DEBUG /// /// A method available in debug mode providing access to the module path cache. @@ -1519,7 +1505,7 @@ public static object GetAppDomainLevelModuleCache() /// Look up a module in the appdomain wide module path cache. /// /// Module name to look up. - /// The path to the matched module + /// The path to the matched module. internal static string ResolveUsingAppDomainLevelModuleCache(string moduleName) { string path; @@ -1555,7 +1541,7 @@ internal static void AddToAppDomainLevelModuleCache(string moduleName, string pa /// /// If there is an entry for the named module in the appdomain level module path cache, remove it. /// - /// The name of the module to remove from the cache + /// The name of the module to remove from the cache. /// True if the module was remove. internal static bool RemoveFromAppDomainLevelCache(string moduleName) { @@ -1565,7 +1551,7 @@ internal static bool RemoveFromAppDomainLevelCache(string moduleName) private static readonly System.Collections.Concurrent.ConcurrentDictionary s_appdomainModulePathCache = new System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - } // PSModuleInfo + } /// /// Indicates the type of a module. @@ -1589,10 +1575,6 @@ public enum ModuleType /// Indicates that this is cmdlets-over-objects module (a powershell file with a .CDXML extension) /// Cim, - /// - /// Indicates that this is workflow module (a powershell file with a .XAML extension) - /// - Workflow, } /// @@ -1601,11 +1583,11 @@ public enum ModuleType public enum ModuleAccessMode { /// - /// The default access mode for the module + /// The default access mode for the module. /// ReadWrite = 0, /// - /// The module is readonly and can only be removed with -force + /// The module is readonly and can only be removed with -force. /// ReadOnly = 1, /// @@ -1614,7 +1596,6 @@ public enum ModuleAccessMode Constant = 2 } - /// /// An EqualityComparer to compare 2 PSModuleInfo instances. 2 PSModuleInfos are /// considered equal if their Name,Guid and Version are equal. @@ -1623,11 +1604,11 @@ internal sealed class PSModuleInfoComparer : IEqualityComparer { public bool Equals(PSModuleInfo x, PSModuleInfo y) { - //Check whether the compared objects reference the same data. - if (Object.ReferenceEquals(x, y)) return true; + // Check whether the compared objects reference the same data. + if (object.ReferenceEquals(x, y)) return true; - //Check whether any of the compared objects is null. - if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) + // Check whether any of the compared objects is null. + if (x is null || y is null) return false; bool result = string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && @@ -1666,4 +1647,4 @@ public int GetHashCode(PSModuleInfo obj) } } } -} // System.Management.Automation \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs b/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs index 30862775561..18e12541528 100644 --- a/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs +++ b/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; @@ -16,21 +15,23 @@ using System.Text.RegularExpressions; using System.Threading; using System.Xml; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; using Microsoft.PowerShell; using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; 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) @@ -42,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); } @@ -52,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); @@ -81,7 +82,7 @@ internal static PSModuleInfo RehydratePSModuleInfo(PSObject deserializedModuleIn moduleInfo.DeclaredVariableExports = RehydrateHashtableKeys(deserializedModuleInfo, "ExportedVariables"); var compatiblePSEditions = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "CompatiblePSEditions", rehydrationFlags); - if (compatiblePSEditions != null && compatiblePSEditions.Any()) + if (compatiblePSEditions != null && compatiblePSEditions.Length > 0) { foreach (var edition in compatiblePSEditions) { @@ -91,13 +92,14 @@ internal static PSModuleInfo RehydratePSModuleInfo(PSObject deserializedModuleIn // PowerShellGet related properties var tags = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "Tags", rehydrationFlags); - if (tags != null && tags.Any()) + if (tags != null && tags.Length > 0) { foreach (var tag in tags) { moduleInfo.AddToTags(tag); } } + moduleInfo.ReleaseNotes = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "ReleaseNotes", rehydrationFlags); moduleInfo.ProjectUri = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "ProjectUri", rehydrationFlags); moduleInfo.LicenseUri = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "LicenseUri", rehydrationFlags); @@ -110,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) @@ -135,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)) { @@ -149,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); @@ -159,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(); @@ -168,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(); @@ -177,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(); @@ -186,7 +188,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler informationHandler = GetStreamForwarder( informationRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { c.WriteInformation(informationRecord); return Enumerable.Empty(); @@ -254,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); @@ -300,6 +302,7 @@ private static void CopyParameterFromCmdletToPowerShell(Cmdlet cmdlet, PowerShel { continue; } + command.Parameters.Add(commandParameter); } } @@ -357,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); @@ -404,6 +407,7 @@ private static IEnumerable EnumerateWithCatch(IEnumerable enumerable, A { exceptionHandler(e); } + if (enumerator != null) using (enumerator) { @@ -463,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"); @@ -477,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); @@ -533,6 +537,7 @@ private static T GetPropertyValue(CimInstance cimInstance, string propertyNam { Array.Reverse(lengthBytes); } + return (T)(object)(lengthBytes.Concat(contentBytes).ToArray()); } } @@ -564,23 +569,28 @@ public CimFileCode FileCode { return CimFileCode.PsdV1; } + if (this.FileName.EndsWith(".cdxml", StringComparison.OrdinalIgnoreCase)) { return CimFileCode.CmdletizationV1; } + if (this.FileName.EndsWith(".types.ps1xml", StringComparison.OrdinalIgnoreCase)) { return CimFileCode.TypesV1; } + if (this.FileName.EndsWith(".format.ps1xml", StringComparison.OrdinalIgnoreCase)) { return CimFileCode.FormatV1; } + return CimFileCode.Unknown; } } public abstract string FileName { get; } + internal abstract byte[] RawFileDataCore { get; } public byte[] RawFileData @@ -600,9 +610,11 @@ public string FileData _fileData = sr.ReadToEnd(); } } + return _fileData; } } + private string _fileData; } @@ -650,7 +662,7 @@ public CimModuleFile MainManifest { get { - byte[] rawFileData = GetPropertyValue(_baseObject, "moduleManifestFileData", Utils.EmptyArray()); + byte[] rawFileData = GetPropertyValue(_baseObject, "moduleManifestFileData", Array.Empty()); return new CimModuleManifestFile(this.ModuleName + ".psd1", rawFileData); } } @@ -671,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) { @@ -693,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; @@ -718,7 +730,7 @@ public override string FileName internal override byte[] RawFileDataCore { - get { return GetPropertyValue(_baseObject, "FileData", Utils.EmptyArray()); } + get { return GetPropertyValue(_baseObject, "FileData", Array.Empty()); } } } } @@ -732,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 @@ -783,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; @@ -798,18 +810,20 @@ 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); } } + cmdlet.WriteError(errorRecord); }); } @@ -828,21 +842,23 @@ internal static Hashtable RewriteManifest(Hashtable originalManifest) "Description", "HelpInfoURI", }; + private static readonly string[] s_manifestEntriesToKeepAsStringArray = new[] { "FunctionsToExport", "VariablesToExport", "AliasesToExport", "CmdletsToExport", }; + internal static Hashtable RewriteManifest( Hashtable originalManifest, IEnumerable nestedModules, IEnumerable typesToProcess, IEnumerable formatsToProcess) { - nestedModules = nestedModules ?? Utils.EmptyArray(); - typesToProcess = typesToProcess ?? Utils.EmptyArray(); - formatsToProcess = formatsToProcess ?? Utils.EmptyArray(); + nestedModules ??= Array.Empty(); + typesToProcess ??= Array.Empty(); + formatsToProcess ??= Array.Empty(); var newManifest = new Hashtable(StringComparer.OrdinalIgnoreCase); newManifest["NestedModules"] = nestedModules; @@ -895,6 +911,7 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti return GetCimCredentials(PasswordAuthenticationMechanism.Default, credential); } } + if (authentication.Equals("Basic", StringComparison.OrdinalIgnoreCase)) { if (credential == null) @@ -906,6 +923,7 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti return GetCimCredentials(PasswordAuthenticationMechanism.Basic, credential); } } + if (authentication.Equals("Negotiate", StringComparison.OrdinalIgnoreCase)) { if (credential == null) @@ -917,6 +935,7 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti return GetCimCredentials(PasswordAuthenticationMechanism.Negotiate, credential); } } + if (authentication.Equals("CredSSP", StringComparison.OrdinalIgnoreCase)) { if (credential == null) @@ -928,6 +947,7 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti return GetCimCredentials(PasswordAuthenticationMechanism.CredSsp, credential); } } + if (authentication.Equals("Digest", StringComparison.OrdinalIgnoreCase)) { if (credential == null) @@ -939,6 +959,7 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti return GetCimCredentials(PasswordAuthenticationMechanism.Digest, credential); } } + if (authentication.Equals("Kerberos", StringComparison.OrdinalIgnoreCase)) { if (credential == null) @@ -952,16 +973,22 @@ private static CimCredential GetCimCredentials(string authentication, PSCredenti } Dbg.Assert(false, "Unrecognized authentication mechanism [ValidateSet should prevent that from happening]"); - throw new ArgumentOutOfRangeException("authentication"); + throw new ArgumentOutOfRangeException(nameof(authentication)); } internal static CimSession CreateCimSession( string computerName, PSCredential credential, string authentication, - CancellationToken cancellationToken, - PSCmdlet cmdlet) + bool isLocalHost, + PSCmdlet cmdlet, + CancellationToken cancellationToken) { + if (isLocalHost) + { + return CimSession.Create(null); + } + var sessionOptions = new CimSessionOptions(); CimCredential cimCredentials = GetCimCredentials(authentication, credential); @@ -1011,10 +1038,10 @@ 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 sanitizedComputerName = Regex.Replace(computerName, "[^a-zA-Z0-9]", ""); + string sanitizedRemoteModuleName = Regex.Replace(remoteModuleName, "[^a-zA-Z0-9]", string.Empty); + string sanitizedComputerName = Regex.Replace(computerName, "[^a-zA-Z0-9]", string.Empty); string moduleName = string.Format( CultureInfo.InvariantCulture, "remoteIpMoProxy_{0}_{1}_{2}_{3}", diff --git a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs index 7f735e44ad9..52fa4879f41 100644 --- a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -10,6 +9,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Reflection; + using Dbg = System.Management.Automation.Diagnostics; // @@ -22,39 +22,43 @@ namespace Microsoft.PowerShell.Commands /// /// Implements a cmdlet that gets the list of loaded modules... /// - [Cmdlet(VerbsCommon.Remove, "Module", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141556")] + [Cmdlet(VerbsCommon.Remove, "Module", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096802")] public sealed class RemoveModuleCommand : ModuleCmdletBase { /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(Mandatory = true, ParameterSetName = "name", ValueFromPipeline = true, Position = 0)] [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 = Utils.EmptyArray(); + + private string[] _name = Array.Empty(); /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(Mandatory = true, ParameterSetName = "FullyQualifiedName", ValueFromPipeline = true, Position = 0)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public ModuleSpecification[] FullyQualifiedName { get; set; } /// - /// This parameter specifies the current pipeline object + /// This parameter specifies the current pipeline object. /// [Parameter(Mandatory = true, ParameterSetName = "ModuleInfo", ValueFromPipeline = true, Position = 0)] [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 = Utils.EmptyArray(); + + private PSModuleInfo[] _moduleInfo = Array.Empty(); /// /// If provided, this parameter will allow readonly modules to be removed. @@ -63,6 +67,7 @@ public PSModuleInfo[] ModuleInfo public SwitchParameter Force { get { return BaseForce; } + set { BaseForce = value; } } @@ -85,6 +90,11 @@ protected override void ProcessRecord() if (FullyQualifiedName != null) { + // TODO: + // Paths in the module name may fail here because + // they the wrong directory separator or are relative. + // Fix with the code below: + // FullyQualifiedName = FullyQualifiedName.Select(ms => ms.WithNormalizedName(Context, SessionState.Path.CurrentLocation.Path)).ToArray(); foreach (var m in Context.Modules.GetModules(FullyQualifiedName, false)) { modulesToRemove.Add(m, new List { m }); @@ -171,6 +181,7 @@ protected override void ProcessRecord() ErrorCategory.PermissionDenied, module); WriteError(er); } + continue; } @@ -190,6 +201,7 @@ protected override void ProcessRecord() string message = StringUtil.Format(Modules.CoreModuleCannotBeRemoved, module.Name); this.WriteWarning(message); } + continue; } // Specify the overall module name if there is only one. @@ -207,6 +219,7 @@ protected override void ProcessRecord() // Add module to remove list. moduleList.Add(module); } + actualModulesToRemove[entry.Key] = moduleList; } @@ -260,7 +273,7 @@ private bool ModuleProvidesCurrentSessionDrive(PSModuleInfo module) Dbg.Assert(pList.Value != null, "There should never be a null list of entries in the provider table"); foreach (ProviderInfo pInfo in pList.Value) { - string implTypeAssemblyLocation = pInfo.ImplementingType.GetTypeInfo().Assembly.Location; + string implTypeAssemblyLocation = pInfo.ImplementingType.Assembly.Location; if (implTypeAssemblyLocation.Equals(module.Path, StringComparison.OrdinalIgnoreCase)) { foreach (PSDriveInfo dInfo in Context.TopLevelSessionState.GetDrivesForProvider(pInfo.FullName)) @@ -278,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) @@ -300,7 +313,7 @@ private void GetAllNestedModules(PSModuleInfo module, ref List nes } /// - /// Returns a map from a module to the list of modules that require it + /// Returns a map from a module to the list of modules that require it. /// private Dictionary> GetRequiredDependencies() { @@ -344,11 +357,12 @@ protected override void EndProcessing() { isEngineModule = false; } + if (!WildcardPattern.ContainsWildcardCharacters(n)) 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; } @@ -365,4 +379,4 @@ protected override void EndProcessing() } } #endregion Remove-Module -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index 00eaba100d4..892d7375387 100644 --- a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs +++ b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; -using System.Globalization; using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Text; using System.Management.Automation.Language; +using System.Text; using System.Text.RegularExpressions; namespace System.Management.Automation @@ -15,7 +14,6 @@ namespace System.Management.Automation /// /// Class describing a PowerShell module... /// - [Serializable] internal class ScriptAnalysis { internal static ScriptAnalysis Analyze(string path, ExecutionContext context) @@ -28,7 +26,7 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) { ProgressRecord analysisProgress = new ProgressRecord(0, Modules.ScriptAnalysisPreparing, - String.Format(CultureInfo.InvariantCulture, Modules.ScriptAnalysisModule, path)); + string.Format(CultureInfo.InvariantCulture, Modules.ScriptAnalysisModule, path)); analysisProgress.RecordType = ProgressRecordType.Processing; // Write the progress using a static source ID so that all @@ -42,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); @@ -91,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; } } @@ -159,12 +148,19 @@ 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) @@ -180,12 +176,14 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun // recorded for command/parameter completion. // function Foo-Bar { ... } + var functionName = functionDefinitionAst.Name; DiscoveredFunctions[functionName] = functionDefinitionAst; ModuleIntrinsics.Tracer.WriteLine("Discovered function definition: {0}", functionName); // Check if they've defined any aliases // function Foo-Bar { [Alias("Alias1", "...")] param() ... } + var functionBody = functionDefinitionAst.Body; if ((functionBody.ParamBlock != null) && (functionBody.ParamBlock.Attributes != null)) { @@ -214,6 +212,7 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { DiscoveredExports.Add(functionName); } + return AstVisitAction.Continue; } @@ -226,27 +225,38 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { // $env:PATH += "";$psScriptRoot"" - if (String.Equals("$env:PATH", assignmentStatementAst.Left.ToString(), StringComparison.OrdinalIgnoreCase) && + if (string.Equals("$env:PATH", assignmentStatementAst.Left.ToString(), StringComparison.OrdinalIgnoreCase) && Regex.IsMatch(assignmentStatementAst.Right.ToString(), "\\$psScriptRoot", RegexOptions.IgnoreCase)) { ModuleIntrinsics.Tracer.WriteLine("Module adds itself to the path."); AddsSelfToPath = true; } + return AstVisitAction.SkipChildren; } // We skip a bunch of random statements because we can't really be accurate detecting functions/classes etc. that // are conditionally defined. public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return AstVisitAction.SkipChildren; } + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return AstVisitAction.SkipChildren; } + // Visit one the other variations: // - Dotting scripts // - Setting aliases @@ -254,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; @@ -275,12 +297,12 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) } // They are setting an alias. - if (String.Equals(commandName, "New-Alias", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "Microsoft.PowerShell.Utility\\New-Alias", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "Set-Alias", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "Microsoft.PowerShell.Utility\\Set-Alias", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "nal", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "sal", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(commandName, "New-Alias", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "Microsoft.PowerShell.Utility\\New-Alias", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "Set-Alias", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "Microsoft.PowerShell.Utility\\Set-Alias", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "nal", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "sal", StringComparison.OrdinalIgnoreCase)) { // Set-Alias Foo-Bar5 Foo-Bar // Set-Alias -Name Foo-Bar6 -Value Foo-Bar @@ -306,8 +328,8 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) } // They are importing a module - if (String.Equals(commandName, "Import-Module", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "ipmo", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(commandName, "Import-Module", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "ipmo", StringComparison.OrdinalIgnoreCase)) { // Import-Module Module1 // Import-Module Module2 -Function Foo-Module2*, Foo-Module2Second* -Cmdlet Foo-Module2Cmdlet,Foo-Module2Cmdlet* @@ -321,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 @@ -350,9 +369,9 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) } // They are exporting a module member - if (String.Equals(commandName, "Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "Microsoft.PowerShell.Core\\Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || - String.Equals(commandName, "$script:ExportModuleMember", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(commandName, "Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "Microsoft.PowerShell.Core\\Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || + string.Equals(commandName, "$script:ExportModuleMember", StringComparison.OrdinalIgnoreCase)) { // Export-ModuleMember * // Export-ModuleMember Exported-UnNamedModuleMember @@ -398,7 +417,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) // They are exporting a module member using our advanced 'public' function // that we've presented in many demos - if ((String.Equals(commandName, "public", StringComparison.OrdinalIgnoreCase)) && + if ((string.Equals(commandName, "public", StringComparison.OrdinalIgnoreCase)) && (commandAst.CommandElements.Count > 2)) { // public function Publicly-ExportedFunction @@ -411,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) @@ -440,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); @@ -481,6 +503,7 @@ private Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string command result[parameterInfo.name] = GetSafeValueVisitor.GetSafeValue(argumentAst, null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis); } + break; } } @@ -532,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; } @@ -548,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; } } -} // System.Management.Automation \ No newline at end of file +} 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 a884a447ead..650588c32e4 100644 --- a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Collections; // // Now define the set of commands for manipulating modules. @@ -20,10 +20,23 @@ namespace Microsoft.PowerShell.Commands /// /// This cmdlet takes a module manifest and validates the contents... /// - [Cmdlet(VerbsDiagnostic.Test, "ModuleManifest", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=141557")] + [Cmdlet(VerbsDiagnostic.Test, "ModuleManifest", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096900")] [OutputType(typeof(PSModuleInfo))] public sealed class TestModuleManifestCommand : ModuleCmdletBase { + /// + /// Creates an instance of the Test-ModuleManifest command. + /// + public TestModuleManifestCommand() + { + // Test-ModuleManifest reads a manifest with ModuleCmdletBase.LoadModuleManifest(). + // This will error on an edition-incompatible manifest loaded from the System32 path, + // unless BaseSkipEditionCheck is true. Since Test-ModuleManifest shouldn't care about + // module edition (it just tests manifest validity), we always want to set this rather + // than provide it as a switch on the cmdlet. + BaseSkipEditionCheck = true; + } + /// /// The output path for the generated file... /// @@ -31,12 +44,14 @@ public sealed class TestModuleManifestCommand : ModuleCmdletBase public string Path { get { return _path; } + set { _path = value; } } + private string _path; /// - /// Implements the record processing for this cmdlet + /// Implements the record processing for this cmdlet. /// protected override void ProcessRecord() { @@ -116,7 +131,7 @@ protected override void ProcessRecord() if (module != null) { - //Validate file existence + // Validate file existence if (module.RequiredAssemblies != null) { foreach (string requiredAssembliespath in module.RequiredAssemblies) @@ -131,22 +146,12 @@ protected override void ProcessRecord() } } - //RootModule can be null, empty string or point to a valid .psm1, , .cdxml, .xaml or .dll. Anything else is invalid. - if (module.RootModule != null && module.RootModule != "") + if (!HasValidRootModule(module)) { - string rootModuleExt = System.IO.Path.GetExtension(module.RootModule); - if ((!IsValidFilePath(module.RootModule, module, true) && !IsValidGacAssembly(module.RootModule)) || - (!rootModuleExt.Equals(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(".dll", StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(".cdxml", StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(".xaml", StringComparison.OrdinalIgnoreCase)) - ) - { - string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath); - var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidRootModuleInModuleManifest", - ErrorCategory.InvalidArgument, _path); - WriteError(errorRecord); - } + string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath); + var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidRootModuleInModuleManifest", + ErrorCategory.InvalidArgument, _path); + WriteError(errorRecord); } Hashtable data = null; @@ -162,11 +167,12 @@ protected override void ProcessRecord() if (!IsValidFilePath(nestedModule.Name, module, true) && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILAssemblyExtension, module, true) && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellNgenAssemblyExtension, module, true) + && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellILExecutableExtension, module, true) && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellModuleFileExtension, module, true) && !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", @@ -246,6 +252,7 @@ protected override void ProcessRecord() { Context.ModuleBeingProcessed = _origModuleBeingProcessed; } + DirectoryInfo parent = null; try { @@ -285,6 +292,60 @@ protected override void ProcessRecord() } } + // All module extensions except ".psd1" are valid RootModule extensions + private static readonly IReadOnlyList s_validRootModuleExtensions = ModuleIntrinsics.PSModuleExtensions + .Where(static ext => !string.Equals(ext, StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + /// + /// Checks whether the RootModule field of a module is valid or not. + /// Valid root modules are: + /// - null + /// - Empty string + /// - A valid non-psd1 module file (psm1, cdxml, xaml, dll), as name with extension, name without extension, or path. + /// + /// The module for which we want to check the validity of the root module. + /// True if the root module is valid, false otherwise. + private bool HasValidRootModule(PSModuleInfo module) + { + // Empty/null root modules are allowed + if (string.IsNullOrEmpty(module.RootModule)) + { + return true; + } + + // GAC assemblies are allowed + if (IsValidGacAssembly(module.RootModule)) + { + return true; + } + + // Check for extensions + string rootModuleExt = System.IO.Path.GetExtension(module.RootModule); + if (!string.IsNullOrEmpty(rootModuleExt)) + { + // Check that the root module's extension is an allowed one + if (!s_validRootModuleExtensions.Contains(rootModuleExt, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + // Check the file path of the full root module + return IsValidFilePath(module.RootModule, module, verifyPathScope: true); + } + + // We have no extension, so we need to check all of them + foreach (string extension in s_validRootModuleExtensions) + { + if (IsValidFilePath(module.RootModule + extension, module, verifyPathScope: true)) + { + return true; + } + } + + return false; + } + /// /// Check if the given path is valid. /// @@ -312,7 +373,10 @@ private bool IsValidFilePath(string path, PSModuleInfo module, bool verifyPathSc ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidModuleManifestPath", ErrorCategory.InvalidArgument, path); 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)) @@ -320,7 +384,7 @@ private bool IsValidFilePath(string path, PSModuleInfo module, bool verifyPathSc return false; } - //Then, we validate if the path is under module scope + // Then, we validate if the path is under module scope if (verifyPathScope && !System.IO.Path.GetFullPath(path).StartsWith(System.IO.Path.GetFullPath(module.ModuleBase), StringComparison.OrdinalIgnoreCase)) { return false; @@ -342,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; @@ -355,28 +419,19 @@ private bool IsValidGacAssembly(string assemblyName) assemblyFile = assemblyName + StringLiterals.PowerShellILAssemblyExtension; ngenAssemblyFile = assemblyName + StringLiterals.PowerShellNgenAssemblyExtension; } + 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 } } #endregion -} // Microsoft.PowerShell.Commands +} diff --git a/src/System.Management.Automation/engine/MshCmdlet.cs b/src/System.Management.Automation/engine/MshCmdlet.cs index 6a9085eb75d..40095ad1472 100644 --- a/src/System.Management.Automation/engine/MshCmdlet.cs +++ b/src/System.Management.Automation/engine/MshCmdlet.cs @@ -1,19 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Reflection; + using PipelineResultTypes = System.Management.Automation.Runspaces.PipelineResultTypes; 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. /// @@ -82,9 +86,9 @@ public bool IsPresent /// /// Implicit cast operator for casting SwitchParameter to bool. /// - /// The SwitchParameter object to convert to bool + /// The SwitchParameter object to convert to bool. /// The corresponding boolean value. - public static implicit operator bool (SwitchParameter switchParameter) + public static implicit operator bool(SwitchParameter switchParameter) { return switchParameter.IsPresent; } @@ -92,7 +96,7 @@ public static implicit operator bool (SwitchParameter switchParameter) /// /// Implicit cast operator for casting bool to SwitchParameter. /// - /// The bool to convert to SwitchParameter + /// The bool to convert to SwitchParameter. /// The corresponding boolean value. public static implicit operator SwitchParameter(bool value) { @@ -102,7 +106,7 @@ public static implicit operator SwitchParameter(bool value) /// /// Explicit method to convert a SwitchParameter to a boolean value. /// - /// The boolean equivalent of the SwitchParameter + /// The boolean equivalent of the SwitchParameter. public bool ToBool() { return _isPresent; @@ -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) { @@ -131,7 +135,7 @@ public static SwitchParameter Present /// /// Compare this switch parameter to another object. /// - /// An object to compare against + /// An object to compare against. /// True if the objects are the same value. public override bool Equals(object obj) { @@ -160,19 +164,19 @@ public override int GetHashCode() /// /// Implement the == operator for switch parameters objects. /// - /// first object to compare - /// second object to compare - /// True if they are the same + /// First object to compare. + /// Second object to compare. + /// True if they are the same. public static bool operator ==(SwitchParameter first, SwitchParameter second) { return first.Equals(second); } /// - /// Implement the != operator for switch parameters + /// Implement the != operator for switch parameters. /// - /// first object to compare - /// second object to compare - /// True if they are different + /// First object to compare. + /// Second object to compare. + /// True if they are different. public static bool operator !=(SwitchParameter first, SwitchParameter second) { return !first.Equals(second); @@ -180,9 +184,9 @@ public override int GetHashCode() /// /// Implement the == operator for switch parameters and booleans. /// - /// first object to compare - /// second object to compare - /// True if they are the same + /// First object to compare. + /// Second object to compare. + /// True if they are the same. public static bool operator ==(SwitchParameter first, bool second) { return first.Equals(second); @@ -190,36 +194,36 @@ public override int GetHashCode() /// /// Implement the != operator for switch parameters and booleans. /// - /// first object to compare - /// second object to compare - /// True if they are different + /// First object to compare. + /// Second object to compare. + /// True if they are different. public static bool operator !=(SwitchParameter first, bool second) { return !first.Equals(second); } /// - /// Implement the == operator for bool and switch parameters + /// Implement the == operator for bool and switch parameters. /// - /// first object to compare - /// second object to compare - /// True if they are the same + /// First object to compare. + /// Second object to compare. + /// True if they are the same. public static bool operator ==(bool first, SwitchParameter second) { return first.Equals(second); } /// - /// Implement the != operator for bool and switch parameters + /// Implement the != operator for bool and switch parameters. /// - /// first object to compare - /// second object to compare - /// True if they are different + /// First object to compare. + /// Second object to compare. + /// True if they are different. public static bool operator !=(bool first, SwitchParameter second) { return !first.Equals(second); } /// - /// Returns the string representation for this object + /// Returns the string representation for this object. /// /// The string for this object. public override string ToString() @@ -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) { @@ -261,6 +265,7 @@ public bool HasErrors { return _commandRuntime.PipelineProcessor.ExecutionFailed; } + set { _commandRuntime.PipelineProcessor.ExecutionFailed = value; @@ -278,13 +283,11 @@ public bool HasErrors /// public string ExpandString(string source) { - if (null != _cmdlet) - _cmdlet.ThrowIfStopping(); + _cmdlet?.ThrowIfStopping(); return _context.Engine.Expand(source); } /// - /// /// /// /// @@ -298,10 +301,10 @@ public CommandInfo GetCommand(string commandName, CommandTypes type) /// Returns a command info for a given command name and type, using the specified arguments /// to resolve dynamic parameters. /// - /// The command name to search for - /// The command type to search for - /// The command arguments used to resolve dynamic parameters - /// A CommandInfo result that represents the resolved command + /// The command name to search for. + /// The command type to search for. + /// The command arguments used to resolve dynamic parameters. + /// A CommandInfo result that represents the resolved command. public CommandInfo GetCommand(string commandName, CommandTypes type, object[] arguments) { CommandInfo result = null; @@ -359,21 +362,26 @@ public CommandInfo GetCommand(string commandName, CommandTypes type, object[] ar public System.EventHandler PostCommandLookupAction { get; set; } /// - /// Returns the CmdletInfo object that corresponds to the name argument + /// Gets or sets the action that is invoked every time the runspace location (cwd) is changed. /// - /// The name of the cmdlet to look for - /// The cmdletInfo object if found, null otherwise + public System.EventHandler LocationChangedAction { get; set; } + + /// + /// Returns the CmdletInfo object that corresponds to the name argument. + /// + /// The name of the cmdlet to look for. + /// The cmdletInfo object if found, null otherwise. public CmdletInfo GetCmdlet(string commandName) { return GetCmdlet(commandName, _context); } /// - /// Returns the CmdletInfo object that corresponds to the name argument + /// Returns the CmdletInfo object that corresponds to the name argument. /// - /// The name of the cmdlet to look for - /// The execution context instance to use for lookup - /// The cmdletInfo object if found, null otherwise + /// The name of the cmdlet to look for. + /// The execution context instance to use for lookup. + /// The cmdletInfo object if found, null otherwise. internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext context) { CmdletInfo current = null; @@ -383,7 +391,7 @@ internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext contex SearchResolutionOptions.None, CommandTypes.Cmdlet, context); - do + while (true) { try { @@ -414,7 +422,7 @@ internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext contex } current = ((IEnumerator)searcher).Current as CmdletInfo; - } while (true); + } return current; } @@ -424,13 +432,13 @@ internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext contex /// session state and retrieves the command directly. Note that the help file and snapin/module /// info will both be null on returned object. /// - /// the type name of the class implementing this cmdlet - /// CmdletInfo for the cmdlet if found, null otherwise + /// The type name of the class implementing this cmdlet. + /// CmdletInfo for the cmdlet if found, null otherwise. public CmdletInfo GetCmdletByTypeName(string cmdletTypeName) { if (string.IsNullOrEmpty(cmdletTypeName)) { - throw PSTraceSource.NewArgumentNullException("cmdletTypeName"); + throw PSTraceSource.NewArgumentNullException(nameof(cmdletTypeName)); } Exception e = null; @@ -439,18 +447,20 @@ public CmdletInfo GetCmdletByTypeName(string cmdletTypeName) { throw e; } + if (cmdletType == null) { return null; } CmdletAttribute ca = null; - foreach (var attr in cmdletType.GetTypeInfo().GetCustomAttributes(true)) + foreach (var attr in cmdletType.GetCustomAttributes(true)) { ca = attr as CmdletAttribute; if (ca != null) break; } + if (ca == null) { throw PSTraceSource.NewNotSupportedException(); @@ -479,7 +489,7 @@ public List GetCmdlets() public List GetCmdlets(string pattern) { if (pattern == null) - throw PSTraceSource.NewArgumentNullException("pattern"); + throw PSTraceSource.NewArgumentNullException(nameof(pattern)); List cmdlets = new List(); @@ -490,7 +500,7 @@ public List GetCmdlets(string pattern) SearchResolutionOptions.CommandNameIsPattern, CommandTypes.Cmdlet, _context); - do + while (true) { try { @@ -523,7 +533,7 @@ public List GetCmdlets(string pattern) current = ((IEnumerator)searcher).Current as CmdletInfo; if (current != null) cmdlets.Add(current); - } while (true); + } return cmdlets; } @@ -533,15 +543,15 @@ public List GetCmdlets(string pattern) /// and optionally return the full path to applications and scripts rather than /// the simple command name. /// - /// The name of the command to use - /// If true treat the name as a pattern to search for - /// If true, return the full path to scripts and applications + /// The name of the command to use. + /// If true treat the name as a pattern to search for. + /// If true, return the full path to scripts and applications. /// A list of command names... public List GetCommandName(string name, bool nameIsPattern, bool returnFullName) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } List commands = new List(); @@ -551,7 +561,7 @@ public List GetCommandName(string name, bool nameIsPattern, bool returnF if (current.CommandType == CommandTypes.Application) { string cmdExtension = System.IO.Path.GetExtension(current.Name); - if (!String.IsNullOrEmpty(cmdExtension)) + if (!string.IsNullOrEmpty(cmdExtension)) { // Only add the application in PATHEXT... foreach (string extension in CommandDiscovery.PathExtensions) @@ -591,17 +601,17 @@ public List GetCommandName(string name, bool nameIsPattern, bool returnF } /// - /// Searches for PowerShell commands, optionally using wildcard patterns + /// Searches for PowerShell commands, optionally using wildcard patterns. /// - /// The name of the command to use - /// Type of commands to support - /// If true treat the name as a pattern to search for + /// The name of the command to use. + /// Type of commands to support. + /// If true treat the name as a pattern to search for. /// Collection of command names... public IEnumerable GetCommands(string name, CommandTypes commandTypes, bool nameIsPattern) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } SearchResolutionOptions options = nameIsPattern ? @@ -624,7 +634,7 @@ internal IEnumerable GetCommands(string name, CommandTypes commandT searcher.CommandOrigin = commandOrigin.Value; } - do + while (true) { try { @@ -659,53 +669,59 @@ 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. + /// The script text to evaluate. + /// 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 + /// 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 script text to evaluate. + /// 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 + /// 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) { - throw PSTraceSource.NewArgumentNullException("scriptBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } + if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } SessionStateInternal _oldSessionState = _context.EngineSessionState; @@ -713,11 +729,11 @@ public Collection InvokeScript( { _context.EngineSessionState = sessionState.Internal; return InvokeScript( - sb:scriptBlock, - useNewScope:false, - writeToPipeline:PipelineResultTypes.None, - input:null, - args:args); + sb: scriptBlock, + useNewScope: false, + writeToPipeline: PipelineResultTypes.None, + input: null, + args: args); } finally { @@ -728,17 +744,22 @@ 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 - /// The scriptblock to execute - /// Optionall input to the command - /// Arguments to pass to the scriptblock - /// The result of the evaluation + /// If true, executes the scriptblock in a new child scope, otherwise the scriptblock is dot-sourced into the calling scope. + /// The scriptblock to execute. + /// Optional input to the command. + /// Arguments to pass to the scriptblock. + /// + /// 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) { - throw PSTraceSource.NewArgumentNullException("scriptBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } // Force the current runspace onto the callers thread - this is needed @@ -760,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 + /// 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("script"); + ArgumentNullException.ThrowIfNull(script); // Compile the script text into an executable script block. ScriptBlock sb = ScriptBlock.Create(_context, script); @@ -785,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 (null != _cmdlet) - _cmdlet.ThrowIfStopping(); + _cmdlet?.ThrowIfStopping(); Cmdlet cmdletToUse = null; ScriptBlock.ErrorHandlingBehavior errorHandlingBehavior = ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe; @@ -875,18 +902,17 @@ private Collection InvokeScript(ScriptBlock sb, bool useNewScope, /// /// Compile a string into a script block. /// - /// The source text to compile - /// The compiled script block + /// The source text to compile. + /// The compiled script block. /// public ScriptBlock NewScriptBlock(string scriptText) { - if (null != _commandRuntime) - _commandRuntime.ThrowIfStopping(); + _commandRuntime?.ThrowIfStopping(); ScriptBlock result = ScriptBlock.Create(_context, scriptText); return result; } - } //CommandInvocationIntrinsics + } #endregion Auxiliary /// @@ -949,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 { @@ -970,10 +996,12 @@ public PagingParameters PagingParameters _pagingParameters = mshCommandRuntime.PagingParameters ?? new PagingParameters(mshCommandRuntime); } } + return _pagingParameters; } } } + private PagingParameters _pagingParameters; #region InvokeCommand @@ -990,14 +1018,13 @@ public CommandInvocationIntrinsics InvokeCommand { using (PSTransactionManager.GetEngineProtectionScope()) { - return _invokeCommand ?? (_invokeCommand = new CommandInvocationIntrinsics(Context, this)); + return _invokeCommand ??= new CommandInvocationIntrinsics(Context, this); } } - } //InvokeCommand + } #endregion InvokeCommand #endregion public members } } - diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index 43672e3116e..e674cb1c5eb 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -1,19 +1,17 @@ -#pragma warning disable 1634, 1691 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#pragma warning disable 1634, 1691 using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading; -using System.Security; using System.Management.Automation.Host; -using System.Management.Automation.Internal.Host; using System.Management.Automation.Internal; +using System.Management.Automation.Internal.Host; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -41,7 +39,7 @@ internal class MshCommandRuntime : ICommandRuntime2 internal InternalHost CBhost; /// - /// The host object for this object + /// The host object for this object. /// public PSHost Host { get; } @@ -70,6 +68,7 @@ internal bool IsPipelineInputExpected { return false; } + return true; } } @@ -77,9 +76,8 @@ internal bool IsPipelineInputExpected /// /// This allows all success output to be set to a variable. Similar to the way -errorvariable sets /// all errors to a variable name. Semantically this is equivalent to : cmd |set-var varname -passthru - /// but it should be MUCH faster as there is no binding that takes place + /// but it should be MUCH faster as there is no binding that takes place. /// - /// /// /// may not be set to null /// @@ -89,12 +87,13 @@ internal bool IsPipelineInputExpected internal string OutVariable { get; set; } internal IList OutVarList { get { return _outVarList; } set { _outVarList = value; } } + private IList _outVarList = null; internal PipelineProcessor PipelineProcessor { get; set; } - private CommandInfo _commandInfo; - private InternalCommand _thisCommand; + private readonly CommandInfo _commandInfo; + private readonly InternalCommand _thisCommand; #endregion private_members @@ -109,12 +108,12 @@ internal MshCommandRuntime(ExecutionContext context, CommandInfo commandInfo, In } /// - /// for diagnostic purposes + /// For diagnostic purposes. /// /// public override string ToString() { - if (null != _commandInfo) + if (_commandInfo != null) return _commandInfo.ToString(); return ""; // does not require localization } @@ -126,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; } } /// @@ -134,7 +133,7 @@ internal InvocationInfo MyInvocation /// internal bool IsStopping { - get { return (null != this.PipelineProcessor && this.PipelineProcessor.Stopping); } + get { return (this.PipelineProcessor != null && this.PipelineProcessor.Stopping); } } #region Write @@ -176,7 +175,7 @@ public void WriteObject(object sendToPipeline) #else if (UseSecurityContextRun) { - if (null == PipelineProcessor || null == PipelineProcessor.SecurityContext) + if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null) throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted); ContextCallback delegateCallback = new ContextCallback(DoWriteObject); @@ -250,11 +249,11 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) #if CORECLR // SecurityContext is not supported in CoreCLR - DoWriteObjects(sendToPipeline); + DoWriteEnumeratedObject(sendToPipeline); #else if (UseSecurityContextRun) { - if (null == PipelineProcessor || null == PipelineProcessor.SecurityContext) + if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null) throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted); ContextCallback delegateCallback = new ContextCallback(DoWriteObjects); @@ -270,6 +269,12 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) #endif } + /// + /// Writes an object enumerated from a collection to the output pipe. + /// + /// + /// The enumerated object that needs to be written to the pipeline. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -277,13 +282,13 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) /// to percolate up to the caller of ProcessRecord etc. /// /// - /// Not permitted at this time or from this thread + /// Not permitted at this time or from this thread. /// - private void DoWriteObjects(object sendToPipeline) + private void DoWriteEnumeratedObject(object sendToPipeline) { // NOTICE-2004/06/08-JonN 959638 ThrowIfWriteNotPermitted(true); - _WriteObjectsSkipAllowCheck(sendToPipeline); + _EnumerateAndWriteObjectSkipAllowCheck(sendToPipeline); } // Trust: public void WriteObject(object sendToPipeline, DataTrustCategory trustCategory); // enumerateCollection defaults to false // Trust: public void WriteObject(object sendToPipeline, bool enumerateCollection, DataTrustCategory trustCategory); @@ -294,9 +299,9 @@ private void DoWriteObjects(object sendToPipeline) private Int64 _sourceId /* = 0 */; /// - /// Display progress information + /// Display progress information. /// - /// progress information + /// Progress information. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -348,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); } @@ -357,7 +362,7 @@ internal void WriteProgress(ProgressRecord progressRecord, bool overrideInquire) } /// - /// Displays progress output if enabled + /// Displays progress output if enabled. /// /// /// Identifies which command is reporting progress @@ -383,7 +388,10 @@ public void WriteProgress( ProgressRecord progressRecord) { WriteProgress(sourceId, progressRecord, false); - } //WriteProgress + } + + internal bool IsWriteProgressEnabled() + => WriteHelper_ShouldWrite(ProgressPreference, lastProgressContinueStatus); internal void WriteProgress( Int64 sourceId, @@ -392,10 +400,10 @@ internal void WriteProgress( { if (progressRecord == null) { - throw PSTraceSource.NewArgumentNullException("progressRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(progressRecord)); } - if (null == Host || null == Host.UI) + if (Host == null || Host.UI == null) { Diagnostics.Assert(false, "No host in CommandBase.WriteProgress()"); throw PSTraceSource.NewInvalidOperationException(); @@ -412,6 +420,12 @@ internal void WriteProgress( if (WriteHelper_ShouldWrite( preference, lastProgressContinueStatus)) { + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(progressRecord); + } + ui.WriteProgress(sourceId, progressRecord); } @@ -422,12 +436,12 @@ internal void WriteProgress( lastProgressContinueStatus, "ProgressPreference", progressRecord.Activity); - } //WriteProgress + } /// - /// Display debug information + /// Display debug information. /// - /// debug output + /// Debug output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -461,8 +475,11 @@ public void WriteDebug(string text) WriteDebug(new DebugRecord(text)); } + internal bool IsWriteDebugEnabled() + => WriteHelper_ShouldWrite(DebugPreference, lastDebugContinueStatus); + /// - /// Display debug information + /// Display debug information. /// internal void WriteDebug(DebugRecord record, bool overrideInquire = false) { @@ -477,6 +494,12 @@ internal void WriteDebug(DebugRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (DebugOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -487,12 +510,9 @@ internal void WriteDebug(DebugRecord record, bool overrideInquire = false) CBhost.InternalUI.WriteDebugInfoBuffers(record); } - // Add note property so that the debug output is formatted correctly. + // Set WriteStream so that the debug output is formatted correctly. PSObject debugWrap = PSObject.AsPSObject(record); - if (debugWrap.Members["WriteDebugStream"] == null) - { - debugWrap.Properties.Add(new PSNoteProperty("WriteDebugStream", true)); - } + debugWrap.WriteStream = WriteStreamType.Debug; DebugOutputPipe.Add(debugWrap); } @@ -501,7 +521,7 @@ internal void WriteDebug(DebugRecord record, bool overrideInquire = false) // // If no pipe, write directly to host. // - if (null == Host || null == Host.UI) + if (Host == null || Host.UI == null) { Diagnostics.Assert(false, "No host in CommandBase.WriteDebug()"); throw PSTraceSource.NewInvalidOperationException(); @@ -522,9 +542,9 @@ internal void WriteDebug(DebugRecord record, bool overrideInquire = false) } /// - /// Display verbose information + /// Display verbose information. /// - /// verbose output + /// Verbose output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -552,8 +572,11 @@ public void WriteVerbose(string text) WriteVerbose(new VerboseRecord(text)); } + internal bool IsWriteVerboseEnabled() + => WriteHelper_ShouldWrite(VerbosePreference, lastVerboseContinueStatus); + /// - /// Display verbose information + /// Display verbose information. /// internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) { @@ -568,6 +591,12 @@ internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (VerboseOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -578,12 +607,9 @@ internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) CBhost.InternalUI.WriteVerboseInfoBuffers(record); } - // Add note property so that the verbose output is formatted correctly. + // Add WriteStream so that the verbose output is formatted correctly. PSObject verboseWrap = PSObject.AsPSObject(record); - if (verboseWrap.Members["WriteVerboseStream"] == null) - { - verboseWrap.Properties.Add(new PSNoteProperty("WriteVerboseStream", true)); - } + verboseWrap.WriteStream = WriteStreamType.Verbose; VerboseOutputPipe.Add(verboseWrap); } @@ -592,7 +618,7 @@ internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) // // If no pipe, write directly to host. // - if (null == Host || null == Host.UI) + if (Host == null || Host.UI == null) { Diagnostics.Assert(false, "No host in CommandBase.WriteVerbose()"); throw PSTraceSource.NewInvalidOperationException(); @@ -613,9 +639,9 @@ internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false) } /// - /// Display warning information + /// Display warning information. /// - /// warning output + /// Warning output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -643,8 +669,11 @@ public void WriteWarning(string text) WriteWarning(new WarningRecord(text)); } + internal bool IsWriteWarningEnabled() + => WriteHelper_ShouldWrite(WarningPreference, lastWarningContinueStatus); + /// - /// Display warning information + /// Display warning information. /// internal void WriteWarning(WarningRecord record, bool overrideInquire = false) { @@ -659,6 +688,12 @@ internal void WriteWarning(WarningRecord record, bool overrideInquire = false) record.SetInvocationInfo(MyInvocation); } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (WarningOutputPipe != null) { if (CBhost != null && CBhost.InternalUI != null && @@ -669,12 +704,9 @@ internal void WriteWarning(WarningRecord record, bool overrideInquire = false) CBhost.InternalUI.WriteWarningInfoBuffers(record); } - // Add note property so that the warning output is formatted correctly. + // Add WriteStream so that the warning output is formatted correctly. PSObject warningWrap = PSObject.AsPSObject(record); - if (warningWrap.Members["WriteWarningStream"] == null) - { - warningWrap.Properties.Add(new PSNoteProperty("WriteWarningStream", true)); - } + warningWrap.WriteStream = WriteStreamType.Warning; WarningOutputPipe.AddWithoutAppendingOutVarList(warningWrap); } @@ -683,7 +715,7 @@ internal void WriteWarning(WarningRecord record, bool overrideInquire = false) // // If no pipe, write directly to host. // - if (null == Host || null == Host.UI) + if (Host == null || Host.UI == null) { Diagnostics.Assert(false, "No host in CommandBase.WriteWarning()"); throw PSTraceSource.NewInvalidOperationException(); @@ -706,15 +738,18 @@ internal void WriteWarning(WarningRecord record, bool overrideInquire = false) } /// - /// Display tagged object information + /// Display tagged object information. /// public void WriteInformation(InformationRecord informationRecord) { WriteInformation(informationRecord, false); } + internal bool IsWriteInformationEnabled() + => WriteHelper_ShouldWrite(InformationPreference, lastInformationContinueStatus); + /// - /// Display tagged object information + /// Display tagged object information. /// internal void WriteInformation(InformationRecord record, bool overrideInquire = false) { @@ -722,6 +757,12 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = if (overrideInquire && preference == ActionPreference.Inquire) preference = ActionPreference.Continue; + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(record); + } + if (preference != ActionPreference.Ignore) { if (InformationOutputPipe != null) @@ -734,12 +775,9 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = CBhost.InternalUI.WriteInformationInfoBuffers(record); } - // Add note property so that the information output is formatted correctly. + // Add WriteStream so that the information output is formatted correctly. PSObject informationWrap = PSObject.AsPSObject(record); - if (informationWrap.Members["WriteInformationStream"] == null) - { - informationWrap.Properties.Add(new PSNoteProperty("WriteInformationStream", true)); - } + informationWrap.WriteStream = WriteStreamType.Information; InformationOutputPipe.Add(informationWrap); } @@ -748,10 +786,9 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = // // If no pipe, write directly to host. // - if (null == Host || null == Host.UI) + if (Host == null || Host.UI == null) { - Diagnostics.Assert(false, "No host in CommandBase.WriteVerbose()"); - throw PSTraceSource.NewInvalidOperationException(); + throw PSTraceSource.NewInvalidOperationException("No host in CommandBase.WriteInformation()"); } CBhost.InternalUI.WriteInformationRecord(record); @@ -823,11 +860,14 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = CBhost.InternalUI.WriteLine(record.ToString()); } } - else - { - // Only transcribe informational messages here. Transcription of PSHost-targeted messages is done in the InternalUI.Write* methods. - CBhost.InternalUI.TranscribeResult(StringUtil.Format(InternalHostUserInterfaceStrings.InformationFormatString, record.ToString())); - } + } + + // Both informational and PSHost-targeted messages are transcribed here. + // The only difference between these two is that PSHost-targeted messages are transcribed + // even if InformationAction is SilentlyContinue. + if (record.Tags.Contains("PSHOST") || (preference != ActionPreference.SilentlyContinue)) + { + CBhost.InternalUI.TranscribeResult(record.ToString()); } } @@ -845,13 +885,13 @@ internal void WriteInformation(InformationRecord record, bool overrideInquire = /// /// Write text into pipeline execution log. /// - /// text to be written to log + /// Text to be written to log. /// /// Use WriteCommandDetail to write important information about cmdlet execution to /// 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" /// /// /// @@ -869,7 +909,7 @@ private bool InitShouldLogPipelineExecutionDetail() if (cmdletInfo != null) { - if (String.Equals("Add-Type", cmdletInfo.Name, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("Add-Type", cmdletInfo.Name, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -902,11 +942,13 @@ private bool InitShouldLogPipelineExecutionDetail() /// the cmdlet. Semantically this is equivalent to : cmd | % { $pipelineVariable = $_; (...) } /// internal string PipelineVariable { get; set; } - private PSVariable _pipelineVarReference = null; + + private PSVariable _pipelineVarReference; + private bool _shouldRemovePipelineVariable; internal void SetupOutVariable() { - if (String.IsNullOrEmpty(this.OutVariable)) + if (string.IsNullOrEmpty(this.OutVariable)) { return; } @@ -915,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("+", StringComparison.Ordinal))) && - 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); } @@ -946,32 +985,52 @@ 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 + /// Configures the number of objects to buffer before calling the downstream Cmdlet. /// /// /// This is a common parameter via class CommonParameters. @@ -979,6 +1038,7 @@ internal void SetupPipelineVariable() internal int OutBuffer { get { return OutputPipe.OutBufferCount; } + set { OutputPipe.OutBufferCount = value; } } @@ -1036,8 +1096,8 @@ internal int OutBuffer /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype1")] /// public class RemoveMyObjectType1 : PSCmdlet @@ -1059,7 +1119,7 @@ internal int OutBuffer /// } /// } /// } - /// + /// /// /// /// @@ -1130,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 @@ -1153,7 +1213,7 @@ public bool ShouldProcess(string target) /// } /// } /// } - /// + /// /// /// /// @@ -1233,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 @@ -1250,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 @@ -1259,7 +1319,7 @@ public bool ShouldProcess(string target, string action) /// } /// } /// } - /// + /// /// /// /// @@ -1348,8 +1408,8 @@ public bool ShouldProcess( /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : PSCmdlet @@ -1366,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)) /// { @@ -1376,7 +1436,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1400,11 +1460,12 @@ private bool CanShouldProcessAutoConfirm() { // retrieve ConfirmImpact from commandInfo CommandMetadata commandMetadata = _commandInfo.CommandMetadata; - if (null == commandMetadata) + if (commandMetadata == null) { Dbg.Assert(false, "Expected CommandMetadata"); return true; } + ConfirmImpact cmdletConfirmImpact = commandMetadata.ConfirmImpact; // compare to ConfirmPreference @@ -1418,7 +1479,7 @@ private bool CanShouldProcessAutoConfirm() } /// - /// Helper function for ShouldProcess APIs + /// Helper function for ShouldProcess APIs. /// /// /// Description of operation, to be printed for Continue or WhatIf @@ -1437,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. @@ -1521,7 +1582,7 @@ private bool DoShouldProcess( return true; } - if (String.IsNullOrEmpty(verboseWarning)) + if (string.IsNullOrEmpty(verboseWarning)) verboseWarning = StringUtil.Format(CommandBaseStrings.ShouldProcessWarningFallback, verboseDescription); @@ -1653,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 @@ -1678,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")) /// ) /// { @@ -1697,7 +1758,7 @@ internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimiz /// } /// } /// } - /// + /// /// /// /// @@ -1707,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); } /// @@ -1732,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. /// /// @@ -1779,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. /// /// @@ -1830,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 @@ -1858,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 @@ -1879,7 +1945,7 @@ public bool ShouldContinue( /// } /// } /// } - /// + /// /// /// /// @@ -1891,7 +1957,6 @@ public bool ShouldContinue( return DoShouldContinue(query, caption, false, true, ref yesToAll, ref noToAll); } - private bool DoShouldContinue( string query, string caption, @@ -1959,7 +2024,7 @@ public bool TransactionAvailable() /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// public PSTransactionContext CurrentPSTransaction { @@ -2013,7 +2078,6 @@ public PSTransactionContext CurrentPSTransaction /// so that the additional information in /// /// is available. - /// /// /// always throws /// , @@ -2024,17 +2088,19 @@ public PSTransactionContext CurrentPSTransaction /// . /// etc. /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { ThrowIfStopping(); - if (null == errorRecord) + if (errorRecord == null) { - throw PSTraceSource.NewArgumentNullException("errorRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(errorRecord)); } + errorRecord.SetInvocationInfo(MyInvocation); - if (null != errorRecord.ErrorDetails - && null != errorRecord.ErrorDetails.TextLookupError) + if (errorRecord.ErrorDetails != null + && errorRecord.ErrorDetails.TextLookupError != null) { Exception textLookupError = errorRecord.ErrorDetails.TextLookupError; errorRecord.ErrorDetails.TextLookupError = null; @@ -2045,8 +2111,8 @@ public void ThrowTerminatingError(ErrorRecord errorRecord) } // This code forces the stack trace and source fields to be populated - if (null != errorRecord.Exception - && String.IsNullOrEmpty(errorRecord.Exception.StackTrace)) + if (errorRecord.Exception != null + && string.IsNullOrEmpty(errorRecord.Exception.StackTrace)) { try { @@ -2061,6 +2127,14 @@ public void ThrowTerminatingError(ErrorRecord errorRecord) CmdletInvocationException e = new CmdletInvocationException(errorRecord); + + // If the error action preference is set to break, break immediately + // into the debugger + if (ErrorAction == ActionPreference.Break) + { + Context.Debugger?.Break(e.InnerException ?? e); + } + // Code sees only that execution stopped throw ManageException(e); } @@ -2158,7 +2232,7 @@ internal void SetMergeFromRuntime(MshCommandRuntime fromRuntime) // /// - /// Claims the unclaimed error output of all previous commands + /// Claims the unclaimed error output of all previous commands. /// internal bool MergeUnclaimedPreviousErrorResults { get; set; } = false; @@ -2171,7 +2245,8 @@ internal void SetMergeFromRuntime(MshCommandRuntime fromRuntime) /// internal Pipe InputPipe { - get { return _inputPipe ?? (_inputPipe = new Pipe()); } + get { return _inputPipe ??= new Pipe(); } + set { _inputPipe = value; } } @@ -2180,7 +2255,8 @@ internal Pipe InputPipe /// internal Pipe OutputPipe { - get { return _outputPipe ?? (_outputPipe = new Pipe()); } + get { return _outputPipe ??= new Pipe(); } + set { _outputPipe = value; } } @@ -2195,14 +2271,15 @@ internal object[] GetResultsAsArray() /// An empty array that is declared statically so we don't keep /// allocating them over and over... /// - internal static object[] StaticEmptyArray = Utils.EmptyArray(); + internal static readonly object[] StaticEmptyArray = Array.Empty(); /// /// Gets or sets the error pipe. /// internal Pipe ErrorOutputPipe { - get { return _errorOutputPipe ?? (_errorOutputPipe = new Pipe()); } + get { return _errorOutputPipe ??= new Pipe(); } + set { _errorOutputPipe = value; } } @@ -2230,7 +2307,7 @@ internal Pipe ErrorOutputPipe #region Internal helpers /// - /// throws if the pipeline is stopping + /// Throws if the pipeline is stopping. /// /// internal void ThrowIfStopping() @@ -2240,14 +2317,14 @@ internal void ThrowIfStopping() } /// - /// throws if the caller is trying to call WriteObject/WriteError + /// Throws if the caller is trying to call WriteObject/WriteError /// from the wrong thread, or not during a call to - /// BeginProcessing/ProcessRecord/EndProcessing + /// BeginProcessing/ProcessRecord/EndProcessing. /// /// internal void ThrowIfWriteNotPermitted(bool needsToWriteToPipeline) { - if (null == this.PipelineProcessor + if (this.PipelineProcessor == null || _thisCommand != this.PipelineProcessor._permittedToWrite || needsToWriteToPipeline && !this.PipelineProcessor._permittedToWriteToPipeline || Thread.CurrentThread != this.PipelineProcessor._permittedToWriteThread @@ -2255,7 +2332,7 @@ internal void ThrowIfWriteNotPermitted(bool needsToWriteToPipeline) { // Only generate these exceptions if a pipeline has already been declared as the 'writing' pipeline. // Otherwise, these are probably infrastructure messages and can be ignored. - if (this.PipelineProcessor._permittedToWrite != null) + if (this.PipelineProcessor?._permittedToWrite != null) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.WriteNotPermitted); @@ -2268,23 +2345,22 @@ internal void ThrowIfWriteNotPermitted(bool needsToWriteToPipeline) /// Be sure to use this object only in "using" so that it is reliably /// disposed and follows stack semantics. /// - /// IDisposable + /// IDisposable. 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. /// internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPipeline) { - if (null == permittedToWrite) - throw PSTraceSource.NewArgumentNullException("permittedToWrite"); - MshCommandRuntime mcr = permittedToWrite.commandRuntime as MshCommandRuntime; - if (mcr == null) + if (permittedToWrite == null) + throw PSTraceSource.NewArgumentNullException(nameof(permittedToWrite)); + if (permittedToWrite.commandRuntime is not MshCommandRuntime mcr) throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime"); _pp = mcr.PipelineProcessor; if (_pp == null) @@ -2296,30 +2372,28 @@ 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; - } // AllowWrite - + private readonly PipelineProcessor _pp = null; + private readonly InternalCommand _wasPermittedToWrite = null; + private readonly bool _wasPermittedToWriteToPipeline = false; + private readonly Thread _wasPermittedToWriteThread = null; + } /// /// Stores the exception to be returned from @@ -2328,17 +2402,14 @@ public void Dispose() /// The general pattern is to call /// throw ManageException(e); /// - /// the exception - /// PipelineStoppedException + /// The exception. + /// PipelineStoppedException. public Exception ManageException(Exception e) { - if (null == e) - throw PSTraceSource.NewArgumentNullException("e"); + if (e == null) + throw PSTraceSource.NewArgumentNullException(nameof(e)); - if (null != PipelineProcessor) - { - PipelineProcessor.RecordFailure(e, _thisCommand); - } + PipelineProcessor?.RecordFailure(e, _thisCommand); // 1021203-2005/05/09-JonN // HaltCommandException will cause the command @@ -2346,7 +2417,11 @@ public Exception ManageException(Exception e) // 913088-2005/06/06 // PipelineStoppedException should not get added to $Error // 2008/06/25 - narrieta: ExistNestedPromptException should not be added to $error either - if (!(e is HaltCommandException) && !(e is PipelineStoppedException) && !(e is ExitNestedPromptException)) + // 2019/10/18 - StopUpstreamCommandsException should not be added either + if (e is not HaltCommandException + && e is not PipelineStoppedException + && e is not ExitNestedPromptException + && e is not StopUpstreamCommandsException) { try { @@ -2358,7 +2433,6 @@ public Exception ManageException(Exception e) } // Log a command health event - MshLog.LogCommandHealthEvent( Context, e, @@ -2386,7 +2460,7 @@ public Exception ManageException(Exception e) internal void SetupErrorVariable() { SetupVariable(VariableStreamKind.Error, this.ErrorVariable, ref _errorVarList); - } // SetupErrorVariable + } private void EnsureVariableParameterAllowed() { @@ -2401,9 +2475,9 @@ private void EnsureVariableParameterAllowed() } /// - /// Append an error to the ErrorVariable if specified, and also to $ERROR + /// Append an error to the ErrorVariable if specified, and also to $ERROR. /// - /// Exception or ErrorRecord + /// Exception or ErrorRecord. /// /// (An error occurred working with the error variable or $ERROR. /// @@ -2415,7 +2489,7 @@ internal void AppendErrorToVariables(object obj) AppendDollarError(obj); this.OutputPipe.AppendVariableList(VariableStreamKind.Error, obj); - } // AppendError + } /// /// Appends the object to $global:error. Non-terminating errors @@ -2434,12 +2508,12 @@ private void AppendDollarError(object obj) { if (obj is Exception) { - if (null == this.PipelineProcessor || !this.PipelineProcessor.TopLevel) + if (this.PipelineProcessor == null || !this.PipelineProcessor.TopLevel) return; // not outermost scope } Context.AppendDollarError(obj); - } // AppendDollarError + } #endregion Error PSVariable @@ -2458,16 +2532,16 @@ private void AppendDollarError(object obj) internal void SetupWarningVariable() { SetupVariable(VariableStreamKind.Warning, this.WarningVariable, ref _warningVarList); - } // SetupWarningVariable + } /// /// Append a warning to WarningVariable if specified. /// - /// The warning message + /// The warning message. internal void AppendWarningVarList(object obj) { this.OutputPipe.AppendVariableList(VariableStreamKind.Warning, obj); - } // AppendWarning + } #endregion Warning PSVariable @@ -2486,34 +2560,32 @@ internal void AppendWarningVarList(object obj) internal void SetupInformationVariable() { SetupVariable(VariableStreamKind.Information, this.InformationVariable, ref _informationVarList); - } // SetupWarningVariable - + } internal void SetupVariable(VariableStreamKind streamKind, string variableName, ref IList varList) { - if (String.IsNullOrEmpty(variableName)) + if (string.IsNullOrEmpty(variableName)) { return; } EnsureVariableParameterAllowed(); - if (_state == null) - _state = new SessionState(Context.EngineSessionState); + _state ??= new SessionState(Context.EngineSessionState); - if (variableName.StartsWith("+", StringComparison.Ordinal)) + if (variableName.StartsWith('+')) { variableName = variableName.Substring(1); object oldValue = PSObject.Base(_state.PSVariable.GetValue(variableName)); varList = oldValue as IList; - if (null == varList) + if (varList == null) { varList = new ArrayList(); - if (null != oldValue && AutomationNull.Value != oldValue) + if (oldValue != null && AutomationNull.Value != oldValue) { IEnumerable enumerable = LanguagePrimitives.GetEnumerable(oldValue); - if (null != enumerable) + if (enumerable != null) { foreach (object o in enumerable) { @@ -2538,29 +2610,34 @@ internal void SetupVariable(VariableStreamKind streamKind, string variableName, varList = new ArrayList(); } - if (!(_thisCommand is PSScriptCmdlet)) + if (_thisCommand is not PSScriptCmdlet) { this.OutputPipe.AddVariableList(streamKind, varList); } + _state.PSVariable.Set(variableName, varList); - } // SetupVariable + } /// /// Append a Information to InformationVariable if specified. /// - /// The Information message + /// The Information message. internal void AppendInformationVarList(object obj) { this.OutputPipe.AppendVariableList(VariableStreamKind.Information, obj); - } // AppendInformation + } #endregion Information PSVariable #region Write internal bool UseSecurityContextRun = true; - // NOTICE-2004/06/08-JonN 959638 - // Use this variant to skip the ThrowIfWriteNotPermitted check + /// + /// Writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check. + /// + /// + /// The object to write to the output pipe. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -2579,19 +2656,22 @@ internal void _WriteObjectSkipAllowCheck(object sendToPipeline) this.OutputPipe.Add(sendToPipeline); } - - // NOTICE-2004/06/08-JonN 959638 - // Use this variant to skip the ThrowIfWriteNotPermitted check + /// + /// Enumerates and writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check. + /// + /// + /// The object to enumerate and write to the output pipe. + /// /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. /// The Cmdlet should generally just allow PipelineStoppedException /// to percolate up to the caller of ProcessRecord etc. /// - internal void _WriteObjectsSkipAllowCheck(object sendToPipeline) + internal void _EnumerateAndWriteObjectSkipAllowCheck(object sendToPipeline) { IEnumerable enumerable = LanguagePrimitives.GetEnumerable(sendToPipeline); - if (null == enumerable) + if (enumerable == null) { _WriteObjectSkipAllowCheck(sendToPipeline); return; @@ -2603,7 +2683,9 @@ internal void _WriteObjectsSkipAllowCheck(object sendToPipeline) foreach (object toConvert in enumerable) { if (AutomationNull.Value == toConvert) + { continue; + } object converted = LanguagePrimitives.AsPSObjectOrNull(toConvert); convertedList.Add(converted); @@ -2612,7 +2694,7 @@ internal void _WriteObjectsSkipAllowCheck(object sendToPipeline) // Writing normal output with "2>&1" // bypasses ErrorActionPreference, as intended. this.OutputPipe.AddItems(convertedList); - } //_WriteObjectsSkipAllowCheck + } #endregion Write @@ -2628,7 +2710,7 @@ internal void _WriteObjectsSkipAllowCheck(object sendToPipeline) /// a /// rather than the real exception. /// - /// error + /// Error. /// /// Not permitted at this time or from this thread /// @@ -2669,13 +2751,19 @@ internal void WriteError(ErrorRecord errorRecord, bool overrideInquire) preference = ActionPreference.Continue; } + // Break into the debugger if requested + if (preference == ActionPreference.Break) + { + CBhost?.Runspace?.Debugger?.Break(errorRecord); + } + #if CORECLR // SecurityContext is not supported in CoreCLR DoWriteError(new KeyValuePair(errorRecord, preference)); #else if (UseSecurityContextRun) { - if (null == PipelineProcessor || null == PipelineProcessor.SecurityContext) + if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null) throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted); ContextCallback delegateCallback = new ContextCallback(DoWriteError); @@ -2690,7 +2778,7 @@ internal void WriteError(ErrorRecord errorRecord, bool overrideInquire) DoWriteError(new KeyValuePair(errorRecord, preference)); } #endif - } // WriteError + } /// /// Not permitted at this time or from this thread @@ -2713,7 +2801,7 @@ private void DoWriteError(object obj) KeyValuePair pair = (KeyValuePair)obj; ErrorRecord errorRecord = pair.Key; ActionPreference preference = pair.Value; - if (null == errorRecord) + if (errorRecord == null) { throw PSTraceSource.NewArgumentNullException("errorRecord"); } @@ -2741,10 +2829,18 @@ private void DoWriteError(object obj) ThrowIfWriteNotPermitted(true); _WriteErrorSkipAllowCheck(errorRecord, preference); - } // DoWriteError + } - // 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. @@ -2758,12 +2854,12 @@ 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(); - if (null != errorRecord.ErrorDetails - && null != errorRecord.ErrorDetails.TextLookupError) + if (errorRecord.ErrorDetails != null + && errorRecord.ErrorDetails.TextLookupError != null) { Exception textLookupError = errorRecord.ErrorDetails.TextLookupError; errorRecord.ErrorDetails.TextLookupError = null; @@ -2773,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) + 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. @@ -2842,10 +2940,9 @@ 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 && errorWrap.Members["writeErrorStream"] == null) + if (!isFromNativeStdError) { - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - errorWrap.Properties.Add(note); + errorWrap.WriteStream = WriteStreamType.Error; } // 2003/11/19-JonN Previously, PSObject instances in ErrorOutputPipe @@ -2872,13 +2969,12 @@ internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreferenc #region Preference - // These are a set of preference variables which affect the inner // workings of the command and when what information will get output. // See "User Feedback Mechanisms - Note.doc" for details. private bool _isConfirmPreferenceCached = false; - private ConfirmImpact _confirmPreference = InitialSessionState.defaultConfirmPreference; + private ConfirmImpact _confirmPreference = InitialSessionState.DefaultConfirmPreference; /// /// Preference setting controlling behavior of ShouldProcess() /// @@ -2895,33 +2991,31 @@ 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; } + return _confirmPreference; } } - - private bool _isDebugPreferenceSet = false; - private ActionPreference _debugPreference = InitialSessionState.defaultDebugPreference; + private ActionPreference _debugPreference = InitialSessionState.DefaultDebugPreference; private bool _isDebugPreferenceCached = false; /// - /// Preference setting + /// Preference setting. /// /// /// (get-only) An error occurred accessing $DebugPreference. @@ -2931,32 +3025,20 @@ internal ActionPreference DebugPreference get { if (_isDebugPreferenceSet) + { return _debugPreference; + } + if (IsDebugFlagSet) { - if (Debug) - { - // If the host couldn't prompt for the debug action anyways, use 'Continue'. - // This lets hosts still see debug output without having to implement the prompting logic. - if (CBhost.ExternalHost.UI == null) - { - return ActionPreference.Continue; - } - else - { - return ActionPreference.Inquire; - } - } - else - return ActionPreference.SilentlyContinue; + return Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue; } - if (!_isDebugPreferenceCached) { bool defaultUsed = false; - _debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed); + _debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed); // If the host couldn't prompt for the debug action anyways, change it to 'Continue'. // This lets hosts still see debug output without having to implement the prompting logic. @@ -2967,19 +3049,26 @@ internal ActionPreference DebugPreference _isDebugPreferenceCached = true; } + return _debugPreference; } + set { + if (value == ActionPreference.Suspend) + { + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); + } + _debugPreference = value; _isDebugPreferenceSet = true; - } // set + } } - private bool _isVerbosePreferenceCached = false; - private ActionPreference _verbosePreference = InitialSessionState.defaultVerbosePreference; + private readonly bool _isVerbosePreferenceCached = false; + private ActionPreference _verbosePreference = InitialSessionState.DefaultVerbosePreference; /// - /// Preference setting + /// Preference setting. /// /// /// An error occurred accessing $VerbosePreference. @@ -3013,7 +3102,7 @@ internal ActionPreference VerbosePreference if (!_isVerbosePreferenceCached) { bool defaultUsed = false; - _verbosePreference = Context.GetEnumPreference( + _verbosePreference = Context.GetEnumPreference( SpecialVariables.VerbosePreferenceVarPath, _verbosePreference, out defaultUsed); @@ -3025,10 +3114,10 @@ internal ActionPreference VerbosePreference internal bool IsWarningActionSet { get; private set; } = false; - private bool _isWarningPreferenceCached = false; - private ActionPreference _warningPreference = InitialSessionState.defaultWarningPreference; + private readonly bool _isWarningPreferenceCached = false; + private ActionPreference _warningPreference = InitialSessionState.DefaultWarningPreference; /// - /// Preference setting + /// Preference setting. /// /// /// An error occurred accessing $WarningPreference. @@ -3047,25 +3136,25 @@ internal ActionPreference WarningPreference return ActionPreference.Continue; // Debug:$false and Verbose:$false ignored - if (!_isWarningPreferenceCached) { bool defaultUsed = false; - _warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed); + _warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed); } return _warningPreference; } + set { if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceErrorActionOnly); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _warningPreference = value; IsWarningActionSet = true; - } // set + } } // This is used so that people can tell whether the verbose switch @@ -3081,12 +3170,16 @@ internal ActionPreference WarningPreference /// internal bool Verbose { - get { return _verboseFlag; } + get + { + return _verboseFlag; + } + set { _verboseFlag = value; IsVerboseFlagSet = true; - } // Set + } } internal bool IsVerboseFlagSet { get; private set; } = false; @@ -3105,11 +3198,12 @@ internal SwitchParameter Confirm { return _confirmFlag; } + set { _confirmFlag = value; IsConfirmFlagSet = true; - } // set + } } internal bool IsConfirmFlagSet { get; private set; } = false; @@ -3128,41 +3222,45 @@ internal SwitchParameter UseTransaction { return _useTransactionFlag; } + set { _useTransactionFlag = value; UseTransactionFlagSet = true; - } // set + } } internal bool UseTransactionFlagSet { get; private set; } = false; - - //This is used so that people can tell whether the debug switch was specified. This + // This is used so that people can tell whether the debug switch was specified. This // Is useful in the Cmdlet-calling-Cmdlet case where you'd like the underlying Cmdlet to // have the same switches. private bool _debugFlag = false; /// - /// Debug tell the command system to provide Programmer/Support type messages to understand what is really occuring - /// and give the user the opportunity to stop or debug the situation + /// 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. /// /// /// This is a common parameter via class CommonParameters. /// internal bool Debug { - get { return _debugFlag; } + get + { + return _debugFlag; + } + set { _debugFlag = value; IsDebugFlagSet = true; - } // set + } } internal bool IsDebugFlagSet { get; private set; } = false; - private bool _whatIfFlag = InitialSessionState.defaultWhatIfPreference; + private bool _whatIfFlag = InitialSessionState.DefaultWhatIfPreference; private bool _isWhatIfPreferenceCached /* = false */; /// /// WhatIf indicates that the command should not @@ -3177,26 +3275,26 @@ 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; } return _whatIfFlag; } + set { _whatIfFlag = value; IsWhatIfFlagSet = true; - } // set + } } internal bool IsWhatIfFlagSet { get; private set; } - private ActionPreference _errorAction = InitialSessionState.defaultErrorActionPreference; + private ActionPreference _errorAction = InitialSessionState.DefaultErrorActionPreference; private bool _isErrorActionPreferenceCached = false; /// - /// ErrorAction tells the command what to do when an error occurs + /// ErrorAction tells the command what to do when an error occurs. /// /// /// (get-only) An error occurred accessing $ErrorAction. @@ -3215,60 +3313,68 @@ internal ActionPreference ErrorAction if (!_isErrorActionPreferenceCached) { bool defaultUsed = false; - _errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed); + _errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed); _isErrorActionPreferenceCached = true; } + return _errorAction; } + set { - if ((!(_commandInfo is WorkflowInfo)) && (value == ActionPreference.Suspend)) + if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceSupportedOnlyOnWorkflow); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _errorAction = value; IsErrorActionSet = true; - } // set + } } internal bool IsErrorActionSet { get; private set; } = false; /// - /// /// Preference setting for displaying ProgressRecords when WriteProgress is called. - /// /// /// internal ActionPreference ProgressPreference { get { - if (_isProgressPreferenceSet) + if (IsProgressActionSet) return _progressPreference; if (!_isProgressPreferenceCached) { bool defaultUsed = false; - _progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed); + _progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed); _isProgressPreferenceCached = true; } + return _progressPreference; } + set { + if (value == ActionPreference.Suspend) + { + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); + } + _progressPreference = value; - _isProgressPreferenceSet = true; - } // set + IsProgressActionSet = true; + } } - private ActionPreference _progressPreference = InitialSessionState.defaultProgressPreference; - private bool _isProgressPreferenceSet = false; + + private ActionPreference _progressPreference = InitialSessionState.DefaultProgressPreference; + + internal bool IsProgressActionSet { get; private set; } = false; + private bool _isProgressPreferenceCached = false; /// - /// /// Preference setting for displaying InformationRecords when WriteInformation is called. - /// /// /// internal ActionPreference InformationPreference @@ -3281,29 +3387,31 @@ internal ActionPreference InformationPreference if (!_isInformationPreferenceCached) { bool defaultUsed = false; - _informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed); + _informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed); _isInformationPreferenceCached = true; } + return _informationPreference; } + set { if (value == ActionPreference.Suspend) { - throw PSTraceSource.NewNotSupportedException(ErrorPackage.SuspendActionPreferenceErrorActionOnly); + throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value); } _informationPreference = value; IsInformationActionSet = true; - } // set + } } - private ActionPreference _informationPreference = InitialSessionState.defaultInformationPreference; + + private ActionPreference _informationPreference = InitialSessionState.DefaultInformationPreference; internal bool IsInformationActionSet { get; private set; } = false; private bool _isInformationPreferenceCached = false; - internal PagingParameters PagingParameters { get; set; } #endregion Preference @@ -3322,7 +3430,7 @@ internal enum ContinueStatus No, YesToAll, NoToAll - }; + } internal ContinueStatus lastShouldProcessContinueStatus = ContinueStatus.Yes; internal ContinueStatus lastErrorContinueStatus = ContinueStatus.Yes; @@ -3378,16 +3486,17 @@ internal bool WriteHelper_ShouldWrite( case ActionPreference.Continue: case ActionPreference.Stop: case ActionPreference.Inquire: + case ActionPreference.Break: return true; default: Dbg.Assert(false, "Bad preference value" + preference); return true; } - } // WriteHelper_ShouldWrite + } /// - /// Complete implementation of WriteDebug/WriteVerbose/WriteProgress + /// Complete implementation of WriteDebug/WriteVerbose/WriteProgress. /// /// /// @@ -3395,7 +3504,7 @@ internal bool WriteHelper_ShouldWrite( /// /// /// - /// Did Inquire return YesToAll? + /// Did Inquire return YesToAll?. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -3430,6 +3539,7 @@ internal ContinueStatus WriteHelper( case ActionPreference.Ignore: // YesToAll case ActionPreference.SilentlyContinue: case ActionPreference.Continue: + case ActionPreference.Break: return ContinueStatus.Yes; case ActionPreference.Stop: @@ -3459,18 +3569,18 @@ internal ContinueStatus WriteHelper( true, // replaceNoWithHalt false // hasSecurityImpact ); - } // WriteHelper + } /// - /// Helper for continue prompt, handles Inquire + /// Helper for continue prompt, handles Inquire. /// - /// may be null - /// may be null + /// May be null. + /// May be null. /// /// /// /// - /// user's selection + /// User's selection. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -3551,31 +3661,33 @@ bool hasSecurityImpact pauseOption = currentOption++; } - if (String.IsNullOrEmpty(inquireMessage)) + if (string.IsNullOrEmpty(inquireMessage)) { inquireMessage = CommandBaseStrings.ShouldContinuePromptCaption; } - if (String.IsNullOrEmpty(inquireCaption)) + if (string.IsNullOrEmpty(inquireCaption)) { inquireCaption = CommandBaseStrings.InquireCaptionDefault; } - do + while (true) { // Transcribe the confirmation message CBhost.InternalUI.TranscribeResult(inquireCaption); CBhost.InternalUI.TranscribeResult(inquireMessage); - System.Text.StringBuilder textChoices = new System.Text.StringBuilder(); + Text.StringBuilder textChoices = new Text.StringBuilder(); foreach (ChoiceDescription choice in choices) { if (textChoices.Length > 0) { textChoices.Append(" "); } + textChoices.Append(choice.Label); } + CBhost.InternalUI.TranscribeResult(textChoices.ToString()); int defaultOption = 0; @@ -3618,7 +3730,7 @@ bool hasSecurityImpact CBhost.EnterNestedPrompt(_thisCommand); // continue loop } - else if (-1 == response) + else if (response == -1) { ActionPreferenceStopException e = new ActionPreferenceStopException( @@ -3633,8 +3745,8 @@ bool hasSecurityImpact PSTraceSource.NewInvalidOperationException(); throw ManageException(e); } - } while (true); - } // InquireHelper + } + } /// /// Determines if this is being run in the context of a remote host or not. @@ -3658,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); } @@ -3686,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); } @@ -3711,8 +3823,7 @@ internal void RemoveVariableListsInPipe() if (this.PipelineVariable != null) { this.OutputPipe.RemovePipelineVariable(); - _state.PSVariable.Remove(this.PipelineVariable); } } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index 126de7325a3..4bde286d165 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -1,19 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Linq; -using System.Management.Automation.Language; -using System.Reflection; -using System.Globalization; -using System.Collections.Specialized; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections; +using System.Collections.Specialized; using System.ComponentModel; -using System.Text; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Interpreter; +using System.Management.Automation.Language; +using System.Reflection; +using System.Text; + using Microsoft.PowerShell; using TypeTable = System.Management.Automation.Runspaces.TypeTable; @@ -25,74 +26,93 @@ namespace System.Management.Automation #region PSMemberInfo /// - /// Enumerates all possible types of members + /// Enumerates all possible types of members. /// - [TypeConverterAttribute(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] - [FlagsAttribute()] + [TypeConverter(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] + [Flags] public enum PSMemberTypes { /// - /// An alias to another member + /// An alias to another member. /// AliasProperty = 1, + /// - /// A property defined as a reference to a method + /// A property defined as a reference to a method. /// CodeProperty = 2, + /// - /// A property from the BaseObject + /// A property from the BaseObject. /// Property = 4, + /// - /// A property defined by a Name-Value pair + /// A property defined by a Name-Value pair. /// NoteProperty = 8, + /// - /// A property defined by script language + /// A property defined by script language. /// ScriptProperty = 16, + /// - /// A set of properties + /// A set of properties. /// PropertySet = 32, + /// - /// A method from the BaseObject + /// A method from the BaseObject. /// Method = 64, + /// - /// A method defined as a reference to another method + /// A method defined as a reference to another method. /// CodeMethod = 128, + /// - /// A method defined as a script + /// A method defined as a script. /// ScriptMethod = 256, + /// /// A member that acts like a Property that takes parameters. This is not consider to be a property or a method. /// ParameterizedProperty = 512, + /// - /// A set of members + /// A set of members. /// MemberSet = 1024, + /// - /// All events + /// All events. /// Event = 2048, + /// /// All dynamic members (where PowerShell cannot know the type of the member) /// Dynamic = 4096, + /// - /// All property member types + /// Members that are inferred by type inference for PSObject and hashtable. /// - Properties = AliasProperty | CodeProperty | Property | NoteProperty | ScriptProperty, + InferredProperty = 8192, /// - /// All method member types + /// All property member types. + /// + Properties = AliasProperty | CodeProperty | Property | NoteProperty | ScriptProperty | InferredProperty, + + /// + /// All method member types. /// Methods = CodeMethod | Method | ScriptMethod, + /// - /// All member types + /// All member types. /// All = Properties | Methods | Event | PropertySet | MemberSet | ParameterizedProperty | Dynamic } @@ -100,55 +120,61 @@ 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 { /// - /// Extended methods / properties + /// Extended methods / properties. /// Extended = 1, + /// - /// Adapted methods / properties + /// Adapted methods / properties. /// Adapted = 2, + /// - /// Base methods / properties + /// Base methods / properties. /// Base = 4, + /// - /// All methods / properties + /// All methods / properties. /// All = Extended | Adapted | Base } /// - /// Match options + /// Match options. /// - [FlagsAttribute] + [Flags] internal enum MshMemberMatchOptions { /// - /// No options + /// No options. /// None = 0, + /// - /// Hidden members should be displayed + /// Hidden members should be displayed. /// IncludeHidden = 1, + /// - /// Only include members with property set to true + /// Only include members with property set to /// OnlySerializable = 2 } /// - /// Serves as the base class for all members of an PSObject + /// Serves as the base class for all members of an PSObject. /// public abstract class PSMemberInfo { internal object instance; internal string name; + internal bool ShouldSerialize { get; set; } internal virtual void ReplicateInstance(object particularInstance) @@ -158,18 +184,17 @@ internal virtual void ReplicateInstance(object particularInstance) internal void SetValueNoConversion(object setValue) { - PSProperty thisAsProperty = this as PSProperty; - if (thisAsProperty == null) + if (this is not PSProperty thisAsProperty) { this.Value = setValue; return; } + thisAsProperty.SetAdaptedValue(setValue, false); } - /// - /// Initializes a new instance of an PSMemberInfo derived class + /// Initializes a new instance of an PSMemberInfo derived class. /// protected PSMemberInfo() { @@ -188,20 +213,14 @@ internal void CloneBaseProperties(PSMemberInfo destiny) } /// - /// Gets the member type + /// Gets the member type. /// public abstract PSMemberTypes MemberType { get; } /// - /// Gets the member name + /// Gets the member name. /// - public string Name - { - get - { - return this.name; - } - } + public string Name => this.name; /// /// Allows a derived class to set the member name... @@ -211,13 +230,14 @@ protected void SetMemberName(string name) { if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; } /// - /// True if this is one of the reserved members + /// True if this is one of the reserved members. /// internal bool IsReservedMember { get; set; } @@ -234,12 +254,12 @@ protected void SetMemberName(string name) /// /// True if this member has been added to the instance as opposed to - /// coming from the adapter or from type data + /// coming from the adapter or from type data. /// public bool IsInstance { get; internal set; } /// - /// Gets and Sets the value of this member + /// Gets and Sets the value of this member. /// /// When getting the value of a property throws an exception. /// This exception is also thrown if the property is an and there @@ -247,29 +267,29 @@ protected void SetMemberName(string name) /// When setting the value of a property throws an exception. /// This exception is also thrown if the property is an and there /// is no Runspace to run the script. - /// When some problem other then getting/setting the value happened + /// When some problem other then getting/setting the value happened. public abstract object Value { get; set; } /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// - /// When there was a problem getting the property + /// When there was a problem getting the property. public abstract string TypeNameOfValue { get; } /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public abstract PSMemberInfo Copy(); 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; } @@ -284,16 +304,19 @@ internal bool MatchesOptions(MshMemberMatchOptions options) public abstract class PSPropertyInfo : PSMemberInfo { /// - /// Initializes a new instance of an PSPropertyInfo derived class + /// Initializes a new instance of an PSPropertyInfo derived class. /// - protected PSPropertyInfo() { } + protected PSPropertyInfo() + { + } + /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// public abstract bool IsSettable { get; } /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// public abstract bool IsGettable { get; } @@ -315,7 +338,7 @@ internal Exception NewGetValueException(Exception e, string errorId) } /// - /// Serves as an alias to another member + /// Serves as an alias to another member. /// /// /// It is permitted to subclass @@ -324,9 +347,9 @@ internal Exception NewGetValueException(Exception e, string errorId) public class PSAliasProperty : PSPropertyInfo { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. public override string ToString() { StringBuilder returnValue = new StringBuilder(); @@ -334,10 +357,11 @@ public override string ToString() returnValue.Append(" = "); if (ConversionType != null) { - returnValue.Append("("); + returnValue.Append('('); returnValue.Append(ConversionType); - returnValue.Append(")"); + returnValue.Append(')'); } + returnValue.Append(ReferencedMemberName); return returnValue.ToString(); } @@ -346,20 +370,22 @@ public override string ToString() /// Initializes a new instance of PSAliasProperty setting the name of the alias /// and the name of the member this alias refers to. /// - /// name of the alias - /// name of the member this alias refers to - /// for invalid arguments + /// Name of the alias. + /// Name of the member this alias refers to. + /// For invalid arguments. public PSAliasProperty(string name, string referencedMemberName) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; - if (String.IsNullOrEmpty(referencedMemberName)) + if (string.IsNullOrEmpty(referencedMemberName)) { - throw PSTraceSource.NewArgumentException("referencedMemberName"); + throw PSTraceSource.NewArgumentException(nameof(referencedMemberName)); } + ReferencedMemberName = referencedMemberName; } @@ -368,44 +394,40 @@ public PSAliasProperty(string name, string referencedMemberName) /// the name of the member this alias refers to and the type to convert the referenced /// member's value. /// - /// name of the alias - /// name of the member this alias refers to - /// the type to convert the referenced member's value - /// for invalid arguments + /// Name of the alias. + /// Name of the member this alias refers to. + /// The type to convert the referenced member's value. + /// For invalid arguments. public PSAliasProperty(string name, string referencedMemberName, Type conversionType) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; - if (String.IsNullOrEmpty(referencedMemberName)) + if (string.IsNullOrEmpty(referencedMemberName)) { - throw PSTraceSource.NewArgumentException("referencedMemberName"); + throw PSTraceSource.NewArgumentException(nameof(referencedMemberName)); } + ReferencedMemberName = referencedMemberName; // conversionType is optional and can be null ConversionType = conversionType; } /// - /// Gets the name of the member this alias refers to + /// Gets the name of the member this alias refers to. /// public string ReferencedMemberName { get; } /// - /// Gets the member this alias refers to + /// Gets the member this alias refers to. /// - internal PSMemberInfo ReferencedMember - { - get - { - return this.LookupMember(ReferencedMemberName); - } - } + 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; } @@ -413,30 +435,23 @@ internal PSMemberInfo ReferencedMember #region virtual implementation /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { - PSAliasProperty alias = new PSAliasProperty(name, ReferencedMemberName); - alias.ConversionType = ConversionType; + PSAliasProperty alias = new PSAliasProperty(name, ReferencedMemberName) { ConversionType = ConversionType }; CloneBaseProperties(alias); return alias; } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.AliasProperty; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.AliasProperty; /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// /// /// When @@ -452,12 +467,13 @@ public override string TypeNameOfValue { return ConversionType.FullName; } + return this.ReferencedMember.TypeNameOfValue; } } /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// /// /// When @@ -469,17 +485,17 @@ public override bool IsSettable { get { - PSPropertyInfo memberProperty = this.ReferencedMember as PSPropertyInfo; - if (memberProperty != null) + if (this.ReferencedMember is PSPropertyInfo memberProperty) { return memberProperty.IsSettable; } + return false; } } /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// /// /// When @@ -491,29 +507,28 @@ public override bool IsGettable { get { - PSPropertyInfo memberProperty = this.ReferencedMember as PSPropertyInfo; - if (memberProperty != null) + if (this.ReferencedMember is PSPropertyInfo memberProperty) { return memberProperty.IsGettable; } + return false; } } private PSMemberInfo LookupMember(string name) { - bool hasCycle; - PSMemberInfo returnValue; - LookupMember(name, new HashSet(StringComparer.OrdinalIgnoreCase), out returnValue, out hasCycle); + LookupMember(name, new HashSet(StringComparer.OrdinalIgnoreCase), out PSMemberInfo returnValue, out bool hasCycle); if (hasCycle) { throw new ExtendedTypeSystemException( - "CycleInAliasLookup", - null, - ExtendedTypeSystem.CycleInAlias, - this.Name); + "CycleInAliasLookup", + null, + ExtendedTypeSystem.CycleInAlias, + this.Name); } + return returnValue; } @@ -538,24 +553,25 @@ private void LookupMember(string name, HashSet visitedAliases, out PSMem name); } - PSAliasProperty aliasMember = member as PSAliasProperty; - if (aliasMember == null) + if (member is not PSAliasProperty aliasMember) { hasCycle = false; returnedMember = member; return; } + if (visitedAliases.Contains(name)) { hasCycle = true; return; } + visitedAliases.Add(name); LookupMember(aliasMember.ReferencedMemberName, visitedAliases, out returnedMember, out hasCycle); } /// - /// Gets and Sets the value of this member + /// Gets and Sets the value of this member. /// /// /// When @@ -563,8 +579,8 @@ private void LookupMember(string name, HashSet visitedAliases, out PSMem /// the alias has a cycle or /// an aliased member is not present /// - /// When getting the value of a property throws an exception - /// When setting the value of a property throws an exception + /// When getting the value of a property throws an exception. + /// When setting the value of a property throws an exception. public override object Value { get @@ -574,13 +590,13 @@ public override object Value { returnValue = LanguagePrimitives.ConvertTo(returnValue, ConversionType, CultureInfo.InvariantCulture); } + return returnValue; } - set - { - this.ReferencedMember.Value = value; - } + + set => this.ReferencedMember.Value = value; } + #endregion virtual implementation } @@ -594,35 +610,36 @@ public override object Value public class PSCodeProperty : PSPropertyInfo { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. 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(); } - /// - /// Called from TypeTableUpdate before SetSetterFromTypeTable is called + /// Called from TypeTableUpdate before SetSetterFromTypeTable is called. /// internal void SetGetterFromTypeTable(Type type, string methodName) { @@ -645,11 +662,12 @@ internal void SetGetterFromTypeTable(Type type, string methodName) null, ExtendedTypeSystem.CodePropertyGetterFormat); } + SetGetter(methodAsMember); } /// - /// Called from TypeTableUpdate after SetGetterFromTypeTable is called + /// Called from TypeTableUpdate after SetGetterFromTypeTable is called. /// internal void SetSetterFromTypeTable(Type type, string methodName) { @@ -672,11 +690,12 @@ internal void SetSetterFromTypeTable(Type type, string methodName) null, ExtendedTypeSystem.CodePropertySetterFormat); } + SetSetter(methodAsMember, GetterCodeReference); } /// - /// Used from TypeTable with the internal constructor + /// Used from TypeTable with the internal constructor. /// internal void SetGetter(MethodInfo methodForGet) { @@ -693,6 +712,7 @@ internal void SetGetter(MethodInfo methodForGet) null, ExtendedTypeSystem.CodePropertyGetterFormat); } + GetterCodeReference = methodForGet; } @@ -700,14 +720,14 @@ internal static bool CheckGetterMethodInfo(MethodInfo methodForGet) { ParameterInfo[] parameters = methodForGet.GetParameters(); return methodForGet.IsPublic - && methodForGet.IsStatic - && methodForGet.ReturnType != typeof(void) - && parameters.Length == 1 - && parameters[0].ParameterType == typeof(PSObject); + && methodForGet.IsStatic + && methodForGet.ReturnType != typeof(void) + && parameters.Length == 1 + && parameters[0].ParameterType == typeof(PSObject); } /// - /// Used from TypeTable with the internal constructor + /// Used from TypeTable with the internal constructor. /// private void SetSetter(MethodInfo methodForSet, MethodInfo methodForGet) { @@ -720,6 +740,7 @@ private void SetSetter(MethodInfo methodForSet, MethodInfo methodForGet) null, ExtendedTypeSystem.CodePropertyGetterAndSetterNull); } + SetterCodeReference = null; return; } @@ -731,6 +752,7 @@ private void SetSetter(MethodInfo methodForSet, MethodInfo methodForGet) null, ExtendedTypeSystem.CodePropertySetterFormat); } + SetterCodeReference = methodForSet; } @@ -738,53 +760,56 @@ internal static bool CheckSetterMethodInfo(MethodInfo methodForSet, MethodInfo m { ParameterInfo[] parameters = methodForSet.GetParameters(); return methodForSet.IsPublic - && methodForSet.IsStatic - && methodForSet.ReturnType == typeof(void) - && parameters.Length == 2 - && parameters[0].ParameterType == typeof(PSObject) - && (methodForGet == null || methodForGet.ReturnType == parameters[1].ParameterType); + && methodForSet.IsStatic + && methodForSet.ReturnType == typeof(void) + && parameters.Length == 2 + && parameters[0].ParameterType == typeof(PSObject) + && (methodForGet == null || methodForGet.ReturnType == parameters[1].ParameterType); } /// - /// Used from TypeTable to delay setting getter and setter + /// Used from TypeTable to delay setting getter and setter. /// internal PSCodeProperty(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; } /// /// Initializes a new instance of the PSCodeProperty class as a read only property. /// - /// name of the property + /// Name of the property. /// This should be a public static non void method taking one PSObject parameter. - /// if name is null or empty or getterCodeReference is null - /// if getterCodeReference doesn't have the right format. + /// If name is null or empty or getterCodeReference is null. + /// If getterCodeReference doesn't have the right format. public PSCodeProperty(string name, MethodInfo getterCodeReference) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (getterCodeReference == null) { - throw PSTraceSource.NewArgumentNullException("getterCodeReference"); + throw PSTraceSource.NewArgumentNullException(nameof(getterCodeReference)); } + SetGetter(getterCodeReference); } /// /// Initializes a new instance of the PSCodeProperty class. Setter or getter can be null, but both cannot be null. /// - /// name of the property + /// Name of the property. /// This should be a public static non void method taking one PSObject parameter. /// This should be a public static void method taking 2 parameters, where the first is an PSObject. - /// when methodForGet and methodForSet are null + /// When methodForGet and methodForSet are null. /// /// if: /// - getterCodeReference doesn't have the right format, @@ -793,15 +818,17 @@ public PSCodeProperty(string name, MethodInfo getterCodeReference) /// public PSCodeProperty(string name, MethodInfo getterCodeReference, MethodInfo setterCodeReference) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (getterCodeReference == null && setterCodeReference == null) { throw PSTraceSource.NewArgumentNullException("getterCodeReference setterCodeReference"); } + SetGetter(getterCodeReference); SetSetter(setterCodeReference, getterCodeReference); } @@ -817,10 +844,11 @@ public PSCodeProperty(string name, MethodInfo getterCodeReference, MethodInfo se public MethodInfo SetterCodeReference { get; private set; } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSCodeProperty property = new PSCodeProperty(name, GetterCodeReference, SetterCodeReference); @@ -829,66 +857,52 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.CodeProperty; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.CodeProperty; /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// - public override bool IsSettable - { - get - { - return this.SetterCodeReference != null; - } - } + public override bool IsSettable => this.SetterCodeReference != null; /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// - public override bool IsGettable - { - get - { - return GetterCodeReference != null; - } - } - + public override bool IsGettable => GetterCodeReference != null; /// - /// Gets and Sets the value of this member + /// Gets and Sets the value of this member. /// - /// 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 + /// 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 { if (GetterCodeReference == null) { - throw new GetValueException("GetWithoutGetterFromCodePropertyValue", + throw new GetValueException( + "GetWithoutGetterFromCodePropertyValue", null, ExtendedTypeSystem.GetWithoutGetterException, this.Name); } + try { - return GetterCodeReference.Invoke(null, new object[1] { this.instance }); + return GetterCodeReference.Invoke(null, new object[] { this.instance }); } catch (TargetInvocationException ex) { Exception inner = ex.InnerException ?? ex; - throw new GetValueInvocationException("CatchFromCodePropertyGetTI", + throw new GetValueInvocationException( + "CatchFromCodePropertyGetTI", inner, ExtendedTypeSystem.ExceptionWhenGetting, - this.name, inner.Message); + this.name, + inner.Message); } catch (Exception e) { @@ -896,32 +910,40 @@ public override object Value { throw; } - throw new GetValueInvocationException("CatchFromCodePropertyGet", + + throw new GetValueInvocationException( + "CatchFromCodePropertyGet", e, ExtendedTypeSystem.ExceptionWhenGetting, - this.name, e.Message); + this.name, + e.Message); } } + set { if (SetterCodeReference == null) { - throw new SetValueException("SetWithoutSetterFromCodeProperty", + throw new SetValueException( + "SetWithoutSetterFromCodeProperty", null, ExtendedTypeSystem.SetWithoutSetterException, this.Name); } + try { - SetterCodeReference.Invoke(null, new object[2] { this.instance, value }); + SetterCodeReference.Invoke(null, new object[] { this.instance, value }); } catch (TargetInvocationException ex) { Exception inner = ex.InnerException ?? ex; - throw new SetValueInvocationException("CatchFromCodePropertySetTI", + throw new SetValueInvocationException( + "CatchFromCodePropertySetTI", inner, ExtendedTypeSystem.ExceptionWhenSetting, - this.name, inner.Message); + this.name, + inner.Message); } catch (Exception e) { @@ -929,44 +951,79 @@ public override object Value { throw; } - throw new SetValueInvocationException("CatchFromCodePropertySet", + + throw new SetValueInvocationException( + "CatchFromCodePropertySet", e, ExtendedTypeSystem.ExceptionWhenSetting, - this.name, e.Message); + this.name, + e.Message); } } } + /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// - /// If there is no property getter + /// If there is no property getter. + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "")] public override string TypeNameOfValue { get { if (GetterCodeReference == null) { - throw new GetValueException("GetWithoutGetterFromCodePropertyTypeOfValue", + throw new GetValueException( + "GetWithoutGetterFromCodePropertyTypeOfValue", null, ExtendedTypeSystem.GetWithoutGetterException, this.Name); } + return GetterCodeReference.ReturnType.FullName; } } + #endregion virtual implementation + } + /// + /// Type used to capture the properties inferred from Hashtable and PSObject. + /// + internal class PSInferredProperty : PSPropertyInfo + { + public PSInferredProperty(string name, PSTypeName typeName) + { + this.name = name; + TypeName = typeName; + } + + internal PSTypeName TypeName { get; } + + public override PSMemberTypes MemberType => PSMemberTypes.InferredProperty; + + public override object Value { get; set; } + + public override string TypeNameOfValue => TypeName.Name; + + public override PSMemberInfo Copy() => new PSInferredProperty(Name, TypeName); + + public override bool IsSettable => false; + + public override bool IsGettable => false; + + public override string ToString() => $"{ToStringCodeMethods.Type(TypeName.Type)} {Name}"; } /// - /// Used to access the adapted or base properties from the BaseObject + /// Used to access the adapted or base properties from the BaseObject. /// public class PSProperty : PSPropertyInfo { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. public override string ToString() { if (this.isDeserialized) @@ -976,31 +1033,33 @@ public override string ToString() returnValue.Append(" {get;set;}"); return returnValue.ToString(); } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); return adapter.BasePropertyToString(this); } /// - /// used by the adapters to keep intermediate data used between DoGetProperty and - /// DoGetValue or DoSetValue + /// Used by the adapters to keep intermediate data used between DoGetProperty and + /// DoGetValue or DoSetValue. /// - internal string typeOfValue; + internal object serializedValue; internal bool isDeserialized; /// - /// This will be either instance.adapter or instance.clrAdapter + /// This will be either instance.adapter or instance.clrAdapter. /// internal Adapter adapter; + internal object adapterData; internal object baseObject; /// - /// Constructs a property from a serialized value + /// Constructs a property from a serialized value. /// - /// name of the property - /// value of the property + /// Name of the property. + /// Value of the property. internal PSProperty(string name, object serializedValue) { this.isDeserialized = true; @@ -1009,19 +1068,20 @@ internal PSProperty(string name, object serializedValue) } /// - /// Constructs this property + /// Constructs this property. /// - /// name of the property - /// adapter used in DoGetProperty - /// object passed to DoGetProperty - /// adapter specific data - /// for invalid arguments + /// Name of the property. + /// Adapter used in DoGetProperty. + /// Object passed to DoGetProperty. + /// Adapter specific data. + /// For invalid arguments. internal PSProperty(string name, Adapter adapter, object baseObject, object adapterData) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; this.adapter = adapter; this.adapterData = adapterData; @@ -1029,10 +1089,11 @@ internal PSProperty(string name, Adapter adapter, object baseObject, object adap } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSProperty property = new PSProperty(this.name, this.adapter, this.baseObject, this.adapterData); @@ -1044,15 +1105,9 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.Property; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.Property; private object GetAdaptedValue() { @@ -1060,6 +1115,7 @@ private object GetAdaptedValue() { return serializedValue; } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); object o = adapter.BasePropertyGet(this); @@ -1073,29 +1129,24 @@ internal void SetAdaptedValue(object setValue, bool shouldConvert) serializedValue = setValue; return; } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); adapter.BasePropertySet(this, setValue, shouldConvert); } /// - /// Gets or sets the value of this property + /// Gets or sets the value of this property. /// - /// When getting the value of a property throws an exception - /// When setting the value of a property throws an exception + /// When getting the value of a property throws an exception. + /// When setting the value of a property throws an exception. public override object Value { - get - { - return GetAdaptedValue(); - } - set - { - SetAdaptedValue(value, true); - } + get => GetAdaptedValue(); + set => SetAdaptedValue(value, true); } /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// public override bool IsSettable { @@ -1105,13 +1156,14 @@ public override bool IsSettable { return true; } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); return adapter.BasePropertyIsSettable(this); } } /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// public override bool IsGettable { @@ -1121,12 +1173,14 @@ public override bool IsGettable { return true; } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); return adapter.BasePropertyIsGettable(this); } } + /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// public override string TypeNameOfValue { @@ -1136,11 +1190,10 @@ public override string TypeNameOfValue { if (serializedValue == null) { - return String.Empty; + return string.Empty; } - PSObject serializedValueAsPSObject = serializedValue as PSObject; - if (serializedValueAsPSObject != null) + if (serializedValue is PSObject serializedValueAsPSObject) { var typeNames = serializedValueAsPSObject.InternalTypeNames; if ((typeNames != null) && (typeNames.Count >= 1)) @@ -1153,24 +1206,26 @@ public override string TypeNameOfValue return serializedValue.GetType().FullName; } + Diagnostics.Assert((this.baseObject != null) && (this.adapter != null), "if it is deserialized, it should have all these properties set"); return adapter.BasePropertyType(this); } } + #endregion virtual implementation } /// - /// A property created by a user-defined PSPropertyAdapter + /// A property created by a user-defined PSPropertyAdapter. /// public class PSAdaptedProperty : PSProperty { /// - /// Creates a property for the given base object + /// Creates a property for the given base object. /// - /// name of the property - /// an adapter can use this object to keep any arbitrary data it needs - /// for invalid arguments + /// Name of the property. + /// An adapter can use this object to keep any arbitrary data it needs. + /// For invalid arguments. public PSAdaptedProperty(string name, object tag) : base(name, null, null, tag) { @@ -1198,26 +1253,14 @@ public override PSMemberInfo Copy() } /// - /// Gets the object the property belongs to + /// Gets the object the property belongs to. /// - public object BaseObject - { - get - { - return this.baseObject; - } - } + public object BaseObject => this.baseObject; /// - /// Gets the data attached to this property + /// Gets the data attached to this property. /// - public object Tag - { - get - { - return this.adapterData; - } - } + public object Tag => this.adapterData; } /// @@ -1226,46 +1269,47 @@ public object Tag public class PSNoteProperty : PSPropertyInfo { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. 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(); } - internal object noteValue; /// /// Initializes a new instance of the PSNoteProperty class. /// - /// name of the property - /// value of the property - /// for an empty or null name + /// Name of the property. + /// Value of the property. + /// For an empty or null name. public PSNoteProperty(string name, object value) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; // value can be null this.noteValue = value; } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSNoteProperty property = new PSNoteProperty(this.name, this.noteValue); @@ -1274,47 +1318,26 @@ public override PSMemberInfo Copy() } /// - /// Gets PSMemberTypes.NoteProperty + /// Gets PSMemberTypes.NoteProperty. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.NoteProperty; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.NoteProperty; /// - /// Gets true since the value of an PSNoteProperty can always be set + /// Gets true since the value of an PSNoteProperty can always be set. /// - public override bool IsSettable - { - get - { - return this.IsInstance; - } - } + public override bool IsSettable => this.IsInstance; /// - /// Gets true since the value of an PSNoteProperty can always be obtained + /// Gets true since the value of an PSNoteProperty can always be obtained. /// - public override bool IsGettable - { - get - { - return true; - } - } + public override bool IsGettable => true; /// - /// Gets or sets the value of this property + /// Gets or sets the value of this property. /// public override object Value { - get - { - return this.noteValue; - } + get => this.noteValue; set { if (!this.IsInstance) @@ -1324,12 +1347,13 @@ public override object Value ExtendedTypeSystem.ChangeStaticMember, this.Name); } + this.noteValue = value; } } /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// public override string TypeNameOfValue { @@ -1342,8 +1366,7 @@ public override string TypeNameOfValue return typeof(object).FullName; } - PSObject valAsPSObject = val as PSObject; - if (valAsPSObject != null) + if (val is PSObject valAsPSObject) { var typeNames = valAsPSObject.InternalTypeNames; if ((typeNames != null) && (typeNames.Count >= 1)) @@ -1364,8 +1387,7 @@ internal static string GetDisplayTypeNameOfValue(object val) { string displayTypeName = null; - PSObject valAsPSObject = val as PSObject; - if (valAsPSObject != null) + if (val is PSObject valAsPSObject) { var typeNames = valAsPSObject.InternalTypeNames; if ((typeNames != null) && (typeNames.Count >= 1)) @@ -1373,6 +1395,7 @@ internal static string GetDisplayTypeNameOfValue(object val) displayTypeName = typeNames[0]; } } + if (string.IsNullOrEmpty(displayTypeName)) { displayTypeName = val == null @@ -1394,46 +1417,42 @@ internal static string GetDisplayTypeNameOfValue(object val) public class PSVariableProperty : PSNoteProperty { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. 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(); } - internal PSVariable _variable; /// /// Initializes a new instance of the PSVariableProperty class. This is /// a subclass of the NoteProperty that wraps a variable instead of a simple value. /// - /// The variable to wrap - /// for an empty or null name + /// The variable to wrap. + /// For an empty or null name. public PSVariableProperty(PSVariable variable) - : base(variable != null ? variable.Name : null, null) + : base(variable?.Name, null) { - if (variable == null) - { - throw PSTraceSource.NewArgumentException("variable"); - } - _variable = variable; + _variable = variable ?? throw PSTraceSource.NewArgumentException(nameof(variable)); } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo, + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo, /// Note that it returns another reference to the variable, not a reference /// to a new variable... /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSNoteProperty property = new PSVariableProperty(_variable); @@ -1442,47 +1461,26 @@ public override PSMemberInfo Copy() } /// - /// Gets PSMemberTypes.NoteProperty + /// Gets PSMemberTypes.NoteProperty. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.NoteProperty; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.NoteProperty; /// /// True if the underlying variable is settable... /// - public override bool IsSettable - { - get - { - return (_variable.Options & (ScopedItemOptions.Constant | ScopedItemOptions.ReadOnly)) == ScopedItemOptions.None; - } - } + public override bool IsSettable => (_variable.Options & (ScopedItemOptions.Constant | ScopedItemOptions.ReadOnly)) == ScopedItemOptions.None; /// - /// Gets true since the value of an PSNoteProperty can always be obtained + /// Gets true since the value of an PSNoteProperty can always be obtained. /// - public override bool IsGettable - { - get - { - return true; - } - } + public override bool IsGettable => true; /// - /// Gets or sets the value of this property + /// Gets or sets the value of this property. /// public override object Value { - get - { - return _variable.Value; - } + get => _variable.Value; set { if (!this.IsInstance) @@ -1492,12 +1490,13 @@ public override object Value ExtendedTypeSystem.ChangeStaticMember, this.Name); } + _variable.Value = value; } } /// - /// Gets the type of the value for this member + /// Gets the type of the value for this member. /// public override string TypeNameOfValue { @@ -1510,8 +1509,7 @@ public override string TypeNameOfValue return typeof(object).FullName; } - PSObject valAsPSObject = val as PSObject; - if (valAsPSObject != null) + if (val is PSObject valAsPSObject) { var typeNames = valAsPSObject.InternalTypeNames; if ((typeNames != null) && (typeNames.Count >= 1)) @@ -1539,37 +1537,39 @@ public override string TypeNameOfValue public class PSScriptProperty : PSPropertyInfo { /// - /// Returns the string representation of this property + /// Returns the string representation of this property. /// - /// This property as a string + /// This property as a string. 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(); } - private Nullable _languageMode; - private string _getterScriptText; + private readonly PSLanguageMode? _languageMode; + private readonly string _getterScriptText; private ScriptBlock _getterScript; - private string _setterScriptText; + private readonly string _setterScriptText; private ScriptBlock _setterScript; private bool _shouldCloneOnAccess; @@ -1662,37 +1662,36 @@ public ScriptBlock SetterScript /// /// Initializes an instance of the PSScriptProperty class as a read only property. /// - /// name of the property - /// script to be used for the property getter. $this will be this PSObject. - /// for invalid arguments + /// Name of the property. + /// Script to be used for the property getter. $this will be this PSObject. + /// For invalid arguments. public PSScriptProperty(string name, ScriptBlock getterScript) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; - if (getterScript == null) - { - throw PSTraceSource.NewArgumentNullException("getterScript"); - } - _getterScript = getterScript; + + _getterScript = getterScript ?? throw PSTraceSource.NewArgumentNullException(nameof(getterScript)); } /// /// Initializes an instance of the PSScriptProperty class as a read only /// property. getterScript or setterScript can be null, but not both. /// - /// Name of this property - /// script to be used for the property getter. $this will be this PSObject. - /// script to be used for the property setter. $this will be this PSObject and $args(1) will be the value to set. - /// for invalid arguments + /// Name of this property. + /// Script to be used for the property getter. $this will be this PSObject. + /// Script to be used for the property setter. $this will be this PSObject and $args(1) will be the value to set. + /// For invalid arguments. public PSScriptProperty(string name, ScriptBlock getterScript, ScriptBlock setterScript) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (getterScript == null && setterScript == null) { @@ -1704,6 +1703,7 @@ public PSScriptProperty(string name, ScriptBlock getterScript, ScriptBlock sette { getterScript.DebuggerStepThrough = true; } + if (setterScript != null) { setterScript.DebuggerStepThrough = true; @@ -1717,17 +1717,18 @@ public PSScriptProperty(string name, ScriptBlock getterScript, ScriptBlock sette /// Initializes an instance of the PSScriptProperty class as a read only /// property, using the text of the properties to support lazy initialization. /// - /// Name of this property - /// script to be used for the property getter. $this will be this PSObject. - /// script to be used for the property setter. $this will be this PSObject and $args(1) will be the value to set. + /// Name of this property. + /// Script to be used for the property getter. $this will be this PSObject. + /// Script to be used for the property setter. $this will be this PSObject and $args(1) will be the value to set. /// Language mode to be used during script block evaluation. - /// for invalid arguments - internal PSScriptProperty(string name, string getterScript, string setterScript, Nullable languageMode) + /// For invalid arguments. + internal PSScriptProperty(string name, string getterScript, string setterScript, PSLanguageMode? languageMode) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (getterScript == null && setterScript == null) { @@ -1746,66 +1747,49 @@ internal PSScriptProperty(string name, ScriptBlock getterScript, ScriptBlock set _shouldCloneOnAccess = shouldCloneOnAccess; } - internal PSScriptProperty(string name, string getterScript, string setterScript, Nullable languageMode, bool shouldCloneOnAccess) + internal PSScriptProperty(string name, string getterScript, string setterScript, PSLanguageMode? languageMode, bool shouldCloneOnAccess) : this(name, getterScript, setterScript, languageMode) { _shouldCloneOnAccess = shouldCloneOnAccess; } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { - PSScriptProperty property; - property = new PSScriptProperty(name, this.GetterScript, this.SetterScript); - property._shouldCloneOnAccess = _shouldCloneOnAccess; + var property = new PSScriptProperty(name, this.GetterScript, this.SetterScript) { _shouldCloneOnAccess = _shouldCloneOnAccess }; CloneBaseProperties(property); return property; } + /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.ScriptProperty; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.ScriptProperty; /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// - public override bool IsSettable - { - get - { - return this._setterScript != null || this._setterScriptText != null; - } - } + public override bool IsSettable => this._setterScript != null || this._setterScriptText != null; /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// - public override bool IsGettable - { - get - { - return this._getterScript != null || this._getterScriptText != null; - } - } + public override bool IsGettable => this._getterScript != null || this._getterScriptText != null; /// - /// Gets and Sets the value of this property + /// Gets and Sets the value of this property. /// /// When getting and there is no getter, /// when the getter throws an exception or when there is no Runspace to run the script. /// /// 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 @@ -1817,8 +1801,10 @@ public override object Value ExtendedTypeSystem.GetWithoutGetterException, this.Name); } + return InvokeGetter(this.instance); } + set { if (this.SetterScript == null) @@ -1828,6 +1814,7 @@ public override object Value ExtendedTypeSystem.SetWithoutSetterException, this.Name); } + InvokeSetter(this.instance, value); } } @@ -1842,7 +1829,7 @@ internal object InvokeSetter(object scriptThis, object value) dollarUnder: AutomationNull.Value, input: AutomationNull.Value, scriptThis: scriptThis, - args: new object[] { value }); + args: new[] { value }); return value; } catch (RuntimeException e) @@ -1874,7 +1861,7 @@ internal object InvokeGetter(object scriptThis) dollarUnder: AutomationNull.Value, input: AutomationNull.Value, scriptThis: scriptThis, - args: Utils.EmptyArray()); + args: Array.Empty()); } catch (RuntimeException e) { @@ -1922,28 +1909,34 @@ internal class PSMethodInvocationConstraints internal PSMethodInvocationConstraints( Type methodTargetType, Type[] parameterTypes) + : this(methodTargetType, parameterTypes, genericTypeParameters: null) + { + } + + internal PSMethodInvocationConstraints( + Type methodTargetType, + Type[] parameterTypes, + object[] genericTypeParameters) { - this.MethodTargetType = methodTargetType; - _parameterTypes = parameterTypes; + MethodTargetType = methodTargetType; + ParameterTypes = parameterTypes; + GenericTypeParameters = genericTypeParameters; } /// - /// If null then there are no constraints + /// If then there are no constraints /// - public Type MethodTargetType { get; private set; } + public Type MethodTargetType { get; } /// - /// If null then there are no constraints + /// If then there are no constraints /// - public IEnumerable ParameterTypes - { - get - { - return _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) { @@ -1951,88 +1944,118 @@ internal static bool EqualsForCollection(ICollection xs, ICollection ys { return ys == null; } + if (ys == null) { return false; } + if (xs.Count != ys.Count) { return false; } + return xs.SequenceEqual(ys); } - // TODO: IEnumerable genericTypeParameters { get; private set; } - public bool Equals(PSMethodInvocationConstraints other) { - if (ReferenceEquals(null, other)) + if (other is null) { return false; } + if (ReferenceEquals(this, other)) { return true; } + if (other.MethodTargetType != this.MethodTargetType) { return false; } - if (!EqualsForCollection(_parameterTypes, other._parameterTypes)) + + if (!EqualsForCollection(ParameterTypes, other.ParameterTypes)) + { + return false; + } + + if (!EqualsForCollection(GenericTypeParameters, other.GenericTypeParameters)) { return false; } + return true; } public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) + if (obj is null) { return false; } + if (ReferenceEquals(this, obj)) { return true; } + if (obj.GetType() != typeof(PSMethodInvocationConstraints)) { return false; } + return Equals((PSMethodInvocationConstraints)obj); } public override int GetHashCode() - { - // algorithm based on http://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 = ""; - if (MethodTargetType != null) + string separator = string.Empty; + 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 = ""; - foreach (var p in _parameterTypes) + separator = string.Empty; + foreach (var p in ParameterTypes) { sb.Append(separator); sb.Append(ToStringCodeMethods.Type(p, dropNamespaces: true)); @@ -2044,6 +2067,7 @@ public override string ToString() { sb.Append(""); } + return sb.ToString(); } } @@ -2056,21 +2080,23 @@ public abstract class PSMethodInfo : PSMemberInfo /// /// Initializes a new instance of a class derived from PSMethodInfo. /// - protected PSMethodInfo() { } + protected PSMethodInfo() + { + } /// /// Invokes the appropriate method overload for the given arguments and returns its result. /// - /// arguments to the method - /// return value from the method - /// if arguments is null - /// For problems finding an appropriate method for the arguments + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. + /// For problems finding an appropriate method for the arguments. /// For exceptions invoking the method. /// This exception is also thrown for an when there is no Runspace to run the script. public abstract object Invoke(params object[] arguments); /// - /// Gets a list of all the overloads for this method + /// Gets a list of all the overloads for this method. /// public abstract Collection OverloadDefinitions { get; } @@ -2079,24 +2105,18 @@ protected PSMethodInfo() { } /// /// Gets the value of this member. The getter returns the PSMethodInfo itself. /// - /// When setting the member + /// When setting the member. /// /// This is not the returned value of the method even for Methods with no arguments. /// The getter returns this (the PSMethodInfo itself). The setter is not supported. /// public sealed override object Value { - get - { - return this; - } - set - { - throw new ExtendedTypeSystemException("CannotChangePSMethodInfoValue", - null, - ExtendedTypeSystem.CannotSetValueForMemberType, - this.GetType().FullName); - } + get => this; + set => throw new ExtendedTypeSystemException("CannotChangePSMethodInfoValue", + null, + ExtendedTypeSystem.CannotSetValueForMemberType, + this.GetType().FullName); } #endregion virtual implementation @@ -2112,9 +2132,9 @@ public sealed override object Value public class PSCodeMethod : PSMethodInfo { /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. public override string ToString() { StringBuilder returnValue = new StringBuilder(); @@ -2123,6 +2143,7 @@ public override string ToString() returnValue.Append(overload); returnValue.Append(", "); } + returnValue.Remove(returnValue.Length - 2, 2); return returnValue.ToString(); } @@ -2133,9 +2154,9 @@ internal static bool CheckMethodInfo(MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); return method.IsStatic - && method.IsPublic - && parameters.Length != 0 - && parameters[0].ParameterType == typeof(PSObject); + && method.IsPublic + && parameters.Length != 0 + && parameters[0].ParameterType == typeof(PSObject); } internal void SetCodeReference(Type type, string methodName) @@ -2155,8 +2176,9 @@ internal void SetCodeReference(Type type, string methodName) if (methodAsMember == null) { throw new ExtendedTypeSystemException("WrongMethodFormatFromTypeTable", null, - ExtendedTypeSystem.CodeMethodMethodFormat); + ExtendedTypeSystem.CodeMethodMethodFormat); } + CodeReference = methodAsMember; if (!CheckMethodInfo(CodeReference)) { @@ -2164,36 +2186,38 @@ internal void SetCodeReference(Type type, string methodName) } } - /// - /// Used from TypeTable + /// Used from TypeTable. /// internal PSCodeMethod(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; } /// /// Initializes a new instance of the PSCodeMethod class. /// - /// name of the property - /// this should be a public static method where the first parameter is an PSObject. - /// for invalid arguments - /// if the codeReference does not have the right format + /// Name of the property. + /// This should be a public static method where the first parameter is an PSObject. + /// For invalid arguments. + /// If the codeReference does not have the right format. public PSCodeMethod(string name, MethodInfo codeReference) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + if (codeReference == null) { - throw PSTraceSource.NewArgumentNullException("codeReference"); + throw PSTraceSource.NewArgumentNullException(nameof(codeReference)); } + if (!CheckMethodInfo(codeReference)) { throw new ExtendedTypeSystemException("WrongMethodFormat", null, ExtendedTypeSystem.CodeMethodMethodFormat); @@ -2204,15 +2228,16 @@ public PSCodeMethod(string name, MethodInfo codeReference) } /// - /// Gets the method referenced by this PSCodeMethod + /// Gets the method referenced by this PSCodeMethod. /// public MethodInfo CodeReference { get; private set; } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSCodeMethod member = new PSCodeMethod(name, CodeReference); @@ -2221,35 +2246,29 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.CodeMethod; - } - } - + public override PSMemberTypes MemberType => PSMemberTypes.CodeMethod; /// /// Invokes CodeReference method and returns its results. /// - /// arguments to the method - /// return value from the method - /// if arguments is null + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. /// /// When /// could CodeReference cannot match the given argument count or /// could not convert an argument to the type required /// - /// For exceptions invoking the CodeReference + /// For exceptions invoking the CodeReference. public override object Invoke(params object[] arguments) { if (arguments == null) { - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); } + object[] newArguments = new object[arguments.Length + 1]; newArguments[0] = this.instance; for (int i = 0; i < arguments.Length; i++) @@ -2257,46 +2276,31 @@ public override object Invoke(params object[] arguments) newArguments[i + 1] = arguments[i]; } - if (_codeReferenceMethodInformation == null) - { - _codeReferenceMethodInformation = DotNetAdapter.GetMethodInformationArray(new[] { CodeReference }); - } - object[] convertedArguments; - Adapter.GetBestMethodAndArguments(CodeReference.Name, _codeReferenceMethodInformation, newArguments, out convertedArguments); + _codeReferenceMethodInformation ??= DotNetAdapter.GetMethodInformationArray(new[] { CodeReference }); + + Adapter.GetBestMethodAndArguments(CodeReference.Name, _codeReferenceMethodInformation, newArguments, out object[] convertedArguments); return DotNetAdapter.AuxiliaryMethodInvoke(null, convertedArguments, _codeReferenceMethodInformation[0], newArguments); } /// - /// Gets the definition for CodeReference + /// Gets the definition for CodeReference. /// - public override Collection OverloadDefinitions + public override Collection OverloadDefinitions => new Collection { - get - { - return new Collection - { - DotNetAdapter.GetMethodInfoOverloadDefinition(null, CodeReference, 0) - }; - } - } + DotNetAdapter.GetMethodInfoOverloadDefinition(null, CodeReference, 0) + }; /// /// Gets the type of the value for this member. Currently this always returns typeof(PSCodeMethod).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(PSCodeMethod).FullName; - } - } + public override string TypeNameOfValue => typeof(PSCodeMethod).FullName; #endregion virtual implementation } /// - /// Serves as a method implemented with a script + /// Serves as a method implemented with a script. /// /// /// It is permitted to subclass @@ -2305,24 +2309,24 @@ public override string TypeNameOfValue public class PSScriptMethod : PSMethodInfo { /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. 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(); } - private ScriptBlock _script; + private readonly ScriptBlock _script; private bool _shouldCloneOnAccess; /// - /// Gets the script implementing this PSScriptMethod + /// Gets the script implementing this PSScriptMethod. /// public ScriptBlock Script { @@ -2346,29 +2350,25 @@ public ScriptBlock Script } } - /// - /// Initializes a new instance of PSScriptMethod + /// Initializes a new instance of PSScriptMethod. /// - /// name of the method - /// script to be used when calling the method. - /// for invalid arguments + /// Name of the method. + /// Script to be used when calling the method. + /// For invalid arguments. public PSScriptMethod(string name, ScriptBlock script) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; - if (script == null) - { - throw PSTraceSource.NewArgumentNullException("script"); - } - _script = script; + + _script = script ?? throw PSTraceSource.NewArgumentNullException(nameof(script)); } /// - /// /// /// /// @@ -2390,16 +2390,17 @@ internal PSScriptMethod(string name, ScriptBlock script, bool shouldCloneOnAcces /// /// Invokes Script method and returns its results. /// - /// arguments to the method - /// return value from the method - /// if arguments is null + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. /// For exceptions invoking the Script or if there is no Runspace to run the script. public override object Invoke(params object[] arguments) { if (arguments == null) { - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); } + return InvokeScript(Name, _script, this.instance, arguments); } @@ -2447,57 +2448,43 @@ internal static object InvokeScript(string methodName, ScriptBlock script, objec } /// - /// Gets a list of all the overloads for this method + /// Gets a list of all the overloads for this method. /// public override Collection OverloadDefinitions { get { - Collection retValue = new Collection(); - retValue.Add(this.ToString()); + Collection retValue = new Collection { this.ToString() }; return retValue; } } /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { - PSScriptMethod method; - method = new PSScriptMethod(this.name, _script); - method._shouldCloneOnAccess = _shouldCloneOnAccess; + var method = new PSScriptMethod(this.name, _script) { _shouldCloneOnAccess = _shouldCloneOnAccess }; CloneBaseProperties(method); return method; } + /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.ScriptMethod; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.ScriptMethod; /// /// Gets the type of the value for this member. Currently this always returns typeof(object).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(object).FullName; - } - } + public override string TypeNameOfValue => typeof(object).FullName; #endregion virtual implementation } /// - /// Used to access the adapted or base methods from the BaseObject + /// Used to access the adapted or base methods from the BaseObject. /// /// /// It is permitted to subclass @@ -2512,9 +2499,9 @@ internal override void ReplicateInstance(object particularInstance) } /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. public override string ToString() { return _adapter.BaseMethodToString(this); @@ -2525,35 +2512,36 @@ public override string ToString() internal object baseObject; /// - /// Constructs this method + /// Constructs this method. /// - /// name - /// adapter to be used invoking - /// baseObject for the methods - /// adapterData from adapter.GetMethodData - /// for invalid arguments + /// Name. + /// Adapter to be used invoking. + /// BaseObject for the methods. + /// AdapterData from adapter.GetMethodData. + /// For invalid arguments. internal PSMethod(string name, Adapter adapter, object baseObject, object adapterData) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; this.adapterData = adapterData; - _adapter = adapter; + this._adapter = adapter; this.baseObject = baseObject; } /// - /// Constructs a PSMethod + /// Constructs a PSMethod. /// - /// name - /// adapter to be used invoking - /// baseObject for the methods - /// adapterData from adapter.GetMethodData - /// true if this member is a special member, false otherwise. - /// true if this member is hidden, false otherwise. - /// for invalid arguments + /// Name. + /// Adapter to be used invoking. + /// BaseObject for the methods. + /// AdapterData from adapter.GetMethodData. + /// True if this member is a special member, false otherwise. + /// True if this member is hidden, false otherwise. + /// For invalid arguments. internal PSMethod(string name, Adapter adapter, object baseObject, object adapterData, bool isSpecial, bool isHidden) : this(name, adapter, baseObject, adapterData) { @@ -2562,10 +2550,11 @@ internal PSMethod(string name, Adapter adapter, object baseObject, object adapte } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSMethod member = new PSMethod(this.name, _adapter, this.baseObject, this.adapterData, this.IsSpecial, this.IsHidden); @@ -2574,24 +2563,18 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.Method; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.Method; /// /// Invokes the appropriate method overload for the given arguments and returns its result. /// - /// arguments to the method - /// return value from the method - /// if arguments is null - /// For problems finding an appropriate method for the arguments - /// For exceptions invoking the method + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. + /// For problems finding an appropriate method for the arguments. + /// For exceptions invoking the method. public override object Invoke(params object[] arguments) { return this.Invoke(null, arguments); @@ -2600,49 +2583,38 @@ public override object Invoke(params object[] arguments) /// /// Invokes the appropriate method overload for the given arguments and returns its result. /// - /// constraints - /// arguments to the method - /// return value from the method - /// if arguments is null - /// For problems finding an appropriate method for the arguments - /// For exceptions invoking the method + /// Constraints. + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. + /// For problems finding an appropriate method for the arguments. + /// For exceptions invoking the method. internal object Invoke(PSMethodInvocationConstraints invocationConstraints, params object[] arguments) { if (arguments == null) { - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); } + return _adapter.BaseMethodInvoke(this, invocationConstraints, arguments); } /// - /// Gets a list of all the overloads for this method + /// Gets a list of all the overloads for this method. /// - public override Collection OverloadDefinitions - { - get - { - return _adapter.BaseMethodDefinitions(this); - } - } + public override Collection OverloadDefinitions => _adapter.BaseMethodDefinitions(this); /// /// Gets the type of the value for this member. This always returns typeof(PSMethod).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(PSMethod).FullName; - } - } + public override string TypeNameOfValue => typeof(PSMethod).FullName; #endregion virtual implementation /// /// True if the method is a special method like GET/SET property accessor methods. /// - internal bool IsSpecial { get; private set; } + internal bool IsSpecial { get; } internal static PSMethod Create(string name, DotNetAdapter dotNetInstanceAdapter, object baseObject, DotNetAdapter.MethodCacheEntry method) { @@ -2651,165 +2623,115 @@ internal static PSMethod Create(string name, DotNetAdapter dotNetInstanceAdapter internal static PSMethod Create(string name, DotNetAdapter dotNetInstanceAdapter, object baseObject, DotNetAdapter.MethodCacheEntry method, bool isSpecial, bool isHidden) { - if (method.psmethodCtor == null) + if (method[0].method is ConstructorInfo) { - method.psmethodCtor = CreatePSMethodConstructor(method.methodInformationStructures); + // Constructor cannot be converted to a delegate, so just return a simple PSMethod instance + return new PSMethod(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); } - return method.psmethodCtor.Invoke(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); + + method.PSMethodCtor ??= CreatePSMethodConstructor(method.methodInformationStructures); + + return method.PSMethodCtor.Invoke(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); } - static Type GetMethodGroupType(MethodInfo methodInfo) + private static Type GetMethodGroupType(MethodInfo methodInfo) { if (methodInfo.DeclaringType.IsGenericTypeDefinition) { + // If the method is from a generic type definition, consider it not convertible. return typeof(Func); } if (methodInfo.IsGenericMethodDefinition) { - methodInfo = ReplaceGenericTypeArgumentsWithMarkerTypes(methodInfo); - if (methodInfo == null) - { - // this happens when there are constraints on the generic type parameters - return typeof(Func); - } + // For a generic method, it's possible to infer the generic parameters based on the target delegate. + // However, we don't yet handle generic methods in PSMethod-to-Delegate conversion, so for now, we + // don't produce the metadata type that represents the signature of a generic method. + // + // Say one day we want to support generic method in PSMethod-to-Delegate conversion and need to produce + // the metadata type, we should use the generic parameter types from the MethodInfo directly to construct + // the Func<> metadata type. See the concept shown in the following scripts: + // $class = "public class Zoo { public static T GetName(int index, T input) { return default(T); } }" + // Add-Type -TypeDefinition $class + // $method = [Zoo].GetMethod("GetName") + // $allTypes = $method.GetParameters().ParameterType + $method.ReturnType + // $metadataType = [Func`3].MakeGenericType($allTypes) + // In this way, '$metadataType.ContainsGenericParameters' returns 'True', indicating it represents a generic method. + // And also, given a generic argument type from `$metadataType.GetGenericArguments()`, it's easy to tell if it's a + // generic parameter (for example, 'T') based on the property 'IsGenericParameter'. + // Moreover, it's also easy to get constraints of the generic parameter, via 'GetGenericParameterConstraints()' + // and 'GenericParameterAttributes'. + return typeof(Func); } var parameterInfos = methodInfo.GetParameters(); if (parameterInfos.Length > 16) { + // Too many parameters, an unlikely scenario. return typeof(Func); } - var res = new Type[parameterInfos.Length + 1]; - for (int i = 0; i < res.Length - 1; i++) - { - var parameterInfo = parameterInfos[i]; - var parameterType = parameterInfo.ParameterType; - res[i] = GetPSMethodTypeProjection(parameterType, - (parameterInfo.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out); - } - var returnType = GetPSMethodTypeProjection(methodInfo.ReturnType); - res[parameterInfos.Length] = returnType; - try { - return DelegateHelpers.MakeDelegate(res); + var methodTypes = new Type[parameterInfos.Length + 1]; + for (int i = 0; i < parameterInfos.Length; i++) + { + var parameterInfo = parameterInfos[i]; + Type parameterType = parameterInfo.ParameterType; + methodTypes[i] = GetPSMethodProjectedType(parameterType, parameterInfo.IsOut); + } + + methodTypes[parameterInfos.Length] = GetPSMethodProjectedType(methodInfo.ReturnType); + + return DelegateHelpers.MakeDelegate(methodTypes); } - catch (TypeLoadException) + catch (Exception) { return typeof(Func); } } - private static Type GetPSMethodTypeProjection(Type type, bool isOut = false) + private static Type GetPSMethodProjectedType(Type type, bool isOut = false) { if (type == typeof(void)) { - return typeof(Unit); + return typeof(VOID); } + if (type == typeof(TypedReference)) { return typeof(PSTypedReference); } - var resType = type.IsEnum ? typeof(PSEnum<>).MakeGenericType(type) : type; - if (resType.HasElementType) { - var psMethodTypeProjection = GetPSMethodTypeProjection(resType.GetElementType()); - if (type.IsPointer) - { - resType = typeof(PSPointer<>).MakeGenericType(psMethodTypeProjection); - } - if (type.IsByRef) - { - resType = isOut ? typeof(PSOutParameter<>).MakeGenericType(psMethodTypeProjection) : typeof(PSReference<>).MakeGenericType(psMethodTypeProjection); - } - } - - return resType; - } - - internal static bool MatchesPSMethodProjectedType(Type targetType, Type projectedSourceType, bool testAssignment = false, bool isOut = false) - { - var sourceType = projectedSourceType; - if (targetType.IsByRef || targetType.IsPointer) - { - if (!projectedSourceType.IsGenericType) return false; - var defType = projectedSourceType.GetGenericTypeDefinition(); - if (targetType.IsByRef && defType == (isOut ? typeof(PSOutParameter<>) : typeof(PSReference<>)) - || targetType.IsPointer && defType == typeof(PSPointer<>)) - { - return MatchesPSMethodProjectedType(targetType.GetElementType(), - projectedSourceType.GenericTypeArguments[0], testAssignment, isOut); - } - } - if (targetType.IsEnum) - { - if (sourceType.IsGenericType && sourceType.GetGenericTypeDefinition() != typeof(PSEnum<>)) - { - return false; - } - sourceType = sourceType.GenericTypeArguments[0]; - } - if (targetType == typeof(void) && sourceType == typeof(Unit)) - { - return true; - } - if (targetType == typeof(TypedReference) && sourceType == typeof(PSTypedReference)) + if (type.IsByRef) { - return true; + var elementType = GetPSMethodProjectedType(type.GetElementType()); + type = isOut ? typeof(PSOutParameter<>).MakeGenericType(elementType) + : typeof(PSReference<>).MakeGenericType(elementType); } - if (testAssignment) - { - return targetType.IsAssignableFrom(sourceType); - } - return targetType == sourceType; - } - - private static MethodInfo ReplaceGenericTypeArgumentsWithMarkerTypes(MethodInfo methodInfo) - { - if (!methodInfo.ContainsGenericParameters) + else if (type.IsPointer) { - return methodInfo; + var elementType = GetPSMethodProjectedType(type.GetElementType()); + type = typeof(PSPointer<>).MakeGenericType(elementType); } - var genArgs = methodInfo.GetGenericArguments(); - var concrete = new Type[genArgs.Length]; - for (int i = 0; i < genArgs.Length; i++) - { - var genArg = genArgs[i]; - if (genArg.GetGenericParameterConstraints().Length != 0) - { - return null; - } - var gpa = genArg.GenericParameterAttributes; - concrete[i] = (gpa & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint - ? PSGenericValueType.GetGenericType(i) - : PSGenericType.GetGenericType(i); - } - return methodInfo.MakeGenericMethod(concrete); + return type; } private static Func CreatePSMethodConstructor(MethodInformation[] methods) { + // Produce the PSMethod creator for MethodInfo objects var types = new Type[methods.Length]; for (int i = 0; i < methods.Length; i++) { - var mb = methods[i].method; - - if (mb is MethodInfo mi) - { - types[i] = GetMethodGroupType(mi); - } - else - { - types[i] = typeof(Unit); - } + types[i] = GetMethodGroupType((MethodInfo)methods[i].method); } - var methodGroupType = CreateMethodGroup(types, 0, types.Length); + + var methodGroupType = CreateMethodGroup(types, 0, types.Length); Type psMethodType = typeof(PSMethod<>).MakeGenericType(methodGroupType); var delegateType = typeof(Func); - return (Func)Delegate.CreateDelegate(delegateType, psMethodType.GetMethod("Create", BindingFlags.NonPublic|BindingFlags.Static)); + return (Func)Delegate.CreateDelegate(delegateType, + psMethodType.GetMethod("Create", BindingFlags.NonPublic | BindingFlags.Static)); } private static Type CreateMethodGroup(Type[] sourceTypes, int start, int count) @@ -2829,135 +2751,68 @@ private static Type CreateMethodGroup(Type[] sourceTypes, int start, int count) case 4: return typeof(MethodGroup<,,,>).MakeGenericType(types); case int i when i < 8: return typeof(MethodGroup<,,,>).MakeGenericType(types[0], types[1], types[2], CreateMethodGroup(types, 3, i - 3)); case 8: return typeof(MethodGroup<,,,,,,,>).MakeGenericType(types); - case int i when i < 16: return typeof(MethodGroup<,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], CreateMethodGroup(types, 7, i - 7)); + case int i when i < 16: + return typeof(MethodGroup<,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], CreateMethodGroup(types, 7, i - 7)); case 16: return typeof(MethodGroup<,,,,,,,,,,,,,,,>).MakeGenericType(types); - case int i when i < 32: return typeof(MethodGroup<,,,,,,,,,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], types[7], types[8], types[9], types[10], types[11], types[12], types[13], types[14], CreateMethodGroup(types, 15, i - 15)); + case int i when i < 32: + return typeof(MethodGroup<,,,,,,,,,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], types[7], types[8], types[9], types[10], + types[11], types[12], types[13], types[14], CreateMethodGroup(types, 15, i - 15)); case 32: return typeof(MethodGroup<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types); default: - return typeof(MethodGroup<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], types[7], types[8], types[9], types[10], types[11], types[12], types[13], types[14], types[15], types[16], types[17], types[18], types[19], types[20], types[21], types[22], types[23], types[24], types[25], types[26], types[27], types[28], types[29], types[30], CreateMethodGroup(sourceTypes, start + 31, count - 31)); + return typeof(MethodGroup<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], types[7], types[8], + types[9], types[10], types[11], types[12], types[13], types[14], types[15], types[16], types[17], types[18], types[19], types[20], types[21], types[22], types[23], + types[24], types[25], types[26], types[27], types[28], types[29], types[30], CreateMethodGroup(sourceTypes, start + 31, count - 31)); } } } - class PSOutParameter { private PSOutParameter() { }} + internal abstract class PSNonBindableType + { + } - abstract class PSNonBindableType { } + internal class VOID + { + } - abstract class PSGenericType + internal class PSOutParameter { - public static Type GetGenericType(int i) - { - switch (i) - { - case 0: return typeof(PSGenericType0); - case 1: return typeof(PSGenericType1); - case 2: return typeof(PSGenericType2); - case 3: return typeof(PSGenericType3); - case 4: return typeof(PSGenericType4); - case 5: return typeof(PSGenericType5); - case 6: return typeof(PSGenericType6); - case 7: return typeof(PSGenericType7); - case 8: return typeof(PSGenericType8); - case 9: return typeof(PSGenericType9); - case 10: return typeof(PSGenericType10); - case 11: return typeof(PSGenericType11); - case 12: return typeof(PSGenericType12); - case 13: return typeof(PSGenericType13); - case 14: return typeof(PSGenericType14); - case 15: return typeof(PSGenericType15); - case 16: return typeof(PSGenericType16); - default: - return typeof(PSGenericType<>).MakeGenericType(GetGenericType(i - 1)); - } - } } - class PSGenericType0 : PSGenericType { internal PSGenericType0() { } } - class PSGenericType1 : PSGenericType { internal PSGenericType1() { } } - class PSGenericType2 : PSGenericType { internal PSGenericType2() { } } - class PSGenericType3 : PSGenericType { internal PSGenericType3() { } } - class PSGenericType4 : PSGenericType { internal PSGenericType4() { } } - class PSGenericType5 : PSGenericType { internal PSGenericType5() { } } - class PSGenericType6 : PSGenericType { internal PSGenericType6() { } } - class PSGenericType7 : PSGenericType { internal PSGenericType7() { } } - class PSGenericType8 : PSGenericType { internal PSGenericType8() { } } - class PSGenericType9 : PSGenericType { internal PSGenericType9() { } } - class PSGenericType10 : PSGenericType { internal PSGenericType10() { } } - class PSGenericType11 : PSGenericType { internal PSGenericType11() { } } - class PSGenericType12 : PSGenericType { internal PSGenericType12() { } } - class PSGenericType13 : PSGenericType { internal PSGenericType13() { } } - class PSGenericType14 : PSGenericType { internal PSGenericType14() { } } - class PSGenericType15 : PSGenericType { internal PSGenericType15() { } } - class PSGenericType16 : PSGenericType { internal PSGenericType16() { } } - - class PSGenericType : PSGenericType { internal PSGenericType() { } } - - struct PSGenericValueType + internal struct PSPointer + { + } + + internal struct PSTypedReference + { + } + + internal abstract class MethodGroup + { + } + + internal class MethodGroup : MethodGroup + { + } + + internal class MethodGroup : MethodGroup + { + } + + internal class MethodGroup : MethodGroup + { + } + + internal class MethodGroup : MethodGroup + { + } + + internal class MethodGroup : MethodGroup { - internal static Type GetGenericType(int i) - { - switch (i) - { - case 0: return typeof(PSGenericValueType0); - case 1: return typeof(PSGenericValueType1); - case 2: return typeof(PSGenericValueType2); - case 3: return typeof(PSGenericValueType3); - case 4: return typeof(PSGenericValueType4); - case 5: return typeof(PSGenericValueType5); - case 6: return typeof(PSGenericValueType6); - case 7: return typeof(PSGenericValueType7); - case 8: return typeof(PSGenericValueType8); - case 9: return typeof(PSGenericValueType9); - case 10: return typeof(PSGenericValueType10); - case 11: return typeof(PSGenericValueType11); - case 12: return typeof(PSGenericValueType12); - case 13: return typeof(PSGenericValueType13); - case 14: return typeof(PSGenericValueType14); - case 15: return typeof(PSGenericValueType15); - case 16: return typeof(PSGenericValueType16); - default: - return typeof(PSGenericValueType<>).MakeGenericType(GetGenericType(i - 1)); - } - } } - struct PSGenericValueType0 { internal int value; } - struct PSGenericValueType1 { internal int value; } - struct PSGenericValueType2 { internal int value; } - struct PSGenericValueType3 { internal int value; } - struct PSGenericValueType4 { internal int value; } - struct PSGenericValueType5 { internal int value; } - struct PSGenericValueType6 { internal int value; } - struct PSGenericValueType7 { internal int value; } - struct PSGenericValueType8 { internal int value; } - struct PSGenericValueType9 { internal int value; } - struct PSGenericValueType10 { internal int value; } - struct PSGenericValueType11 { internal int value; } - struct PSGenericValueType12 { internal int value; } - struct PSGenericValueType13 { internal int value; } - struct PSGenericValueType14 { internal int value; } - struct PSGenericValueType15 { internal int value; } - struct PSGenericValueType16 { internal int value; } - - struct PSGenericValueType { internal int value; } - - struct PSEnum { } - - struct PSPointer { } - - struct PSTypedReference { } - - internal abstract class MethodGroup { } - internal class MethodGroup : MethodGroup { } - internal class MethodGroup : MethodGroup { } - internal class MethodGroup : MethodGroup { } - internal class MethodGroup : MethodGroup { } - internal class MethodGroup : MethodGroup { } - internal class MethodGroup : MethodGroup { } - - class Unit + internal class MethodGroup : MethodGroup { - private Unit() { } } internal struct PSMethodSignatureEnumerator : IEnumerator @@ -2979,7 +2834,7 @@ public bool MoveNext() return MoveNext(_t, _currentIndex); } - bool MoveNext(Type type, int index) + private bool MoveNext(Type type, int index) { var genericTypeArguments = type.GenericTypeArguments; var length = genericTypeArguments.Length; @@ -2995,11 +2850,13 @@ bool MoveNext(Type type, int index) var remaining = index - (length - 1); return MoveNext(t, remaining); } - if (index >= genericTypeArguments.Length) + + if (index >= length) { Current = null; return false; } + Current = t; return true; } @@ -3029,9 +2886,14 @@ public override PSMemberInfo Copy() } internal PSMethod(string name, Adapter adapter, object baseObject, object adapterData) - : base(name, adapter, baseObject, adapterData) { } + : base(name, adapter, baseObject, adapterData) + { + } + internal PSMethod(string name, Adapter adapter, object baseObject, object adapterData, bool isSpecial, bool isHidden) - : base(name, adapter, baseObject, adapterData, isSpecial, isHidden) { } + : base(name, adapter, baseObject, adapterData, isSpecial, isHidden) + { + } /// /// Helper factory function since we cannot bind a delegate to a ConstructorInfo. @@ -3042,9 +2904,8 @@ internal static PSMethod Create(string name, Adapter adapter, object baseObje } } - /// - /// Used to access parameterized properties from the BaseObject + /// Used to access parameterized properties from the BaseObject. /// /// /// It is permitted to subclass @@ -3053,130 +2914,110 @@ internal static PSMethod Create(string name, Adapter adapter, object baseObje public class PSParameterizedProperty : PSMethodInfo { /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. public override string ToString() { Diagnostics.Assert((this.baseObject != null) && (this.adapter != null) && (this.adapterData != null), "it should have all these properties set"); return this.adapter.BaseParameterizedPropertyToString(this); } - internal Adapter adapter; internal object adapterData; internal object baseObject; /// - /// Constructs this parameterized property + /// Constructs this parameterized property. /// - /// name of the property - /// adapter used in DoGetMethod - /// object passed to DoGetMethod - /// adapter specific data - /// for invalid arguments + /// Name of the property. + /// Adapter used in DoGetMethod. + /// Object passed to DoGetMethod. + /// Adapter specific data. + /// For invalid arguments. internal PSParameterizedProperty(string name, Adapter adapter, object baseObject, object adapterData) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; this.adapter = adapter; this.adapterData = adapterData; this.baseObject = baseObject; } + internal PSParameterizedProperty(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; } /// - /// Gets true if this property can be set + /// Gets true if this property can be set. /// - public bool IsSettable - { - get - { - return adapter.BaseParameterizedPropertyIsSettable(this); - } - } + public bool IsSettable => adapter.BaseParameterizedPropertyIsSettable(this); /// - /// Gets true if this property can be read + /// Gets true if this property can be read. /// - public bool IsGettable - { - get - { - return adapter.BaseParameterizedPropertyIsGettable(this); - } - } + public bool IsGettable => adapter.BaseParameterizedPropertyIsGettable(this); #region virtual implementation + /// - /// Invokes the getter method and returns its result + /// Invokes the getter method and returns its result. /// - /// arguments to the method - /// return value from the method - /// if arguments is null - /// When getting the value of a property throws an exception + /// Arguments to the method. + /// Return value from the method. + /// If arguments is null. + /// When getting the value of a property throws an exception. public override object Invoke(params object[] arguments) { if (arguments == null) { - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); } + return this.adapter.BaseParameterizedPropertyGet(this, arguments); } /// - /// Invokes the setter method + /// Invokes the setter method. /// - /// value to set this property with - /// arguments to the method - /// if arguments is null - /// When setting the value of a property throws an exception + /// Value to set this property with. + /// Arguments to the method. + /// If arguments is null. + /// When setting the value of a property throws an exception. public void InvokeSet(object valueToSet, params object[] arguments) { if (arguments == null) { - throw PSTraceSource.NewArgumentNullException("arguments"); + throw PSTraceSource.NewArgumentNullException(nameof(arguments)); } + this.adapter.BaseParameterizedPropertySet(this, valueToSet, arguments); } - /// - /// Returns a collection of the definitions for this property + /// Returns a collection of the definitions for this property. /// - public override Collection OverloadDefinitions - { - get - { - return adapter.BaseParameterizedPropertyDefinitions(this); - } - } + public override Collection OverloadDefinitions => adapter.BaseParameterizedPropertyDefinitions(this); /// /// Gets the type of the value for this member. /// - public override string TypeNameOfValue - { - get - { - return adapter.BaseParameterizedPropertyType(this); - } - } + public override string TypeNameOfValue => adapter.BaseParameterizedPropertyType(this); /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSParameterizedProperty property = new PSParameterizedProperty(this.name, this.adapter, this.baseObject, this.adapterData); @@ -3185,21 +3026,15 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.ParameterizedProperty; - } - } - #endregion virtual implementation + public override PSMemberTypes MemberType => PSMemberTypes.ParameterizedProperty; + #endregion virtual implementation } /// - /// Serves as a set of members + /// Serves as a set of members. /// public class PSMemberSet : PSMemberInfo { @@ -3213,9 +3048,9 @@ internal override void ReplicateInstance(object particularInstance) } /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. public override string ToString() { StringBuilder returnValue = new StringBuilder(); @@ -3226,36 +3061,39 @@ public override string ToString() returnValue.Append(member.Name); returnValue.Append(", "); } + if (returnValue.Length > 2) { returnValue.Remove(returnValue.Length - 2, 2); } + returnValue.Insert(0, this.Name); - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } - private PSMemberInfoIntegratingCollection _members; - private PSMemberInfoIntegratingCollection _properties; - private PSMemberInfoIntegratingCollection _methods; + private readonly PSMemberInfoIntegratingCollection _members; + private readonly PSMemberInfoIntegratingCollection _properties; + private readonly PSMemberInfoIntegratingCollection _methods; internal PSMemberInfoInternalCollection internalMembers; - private PSObject _constructorPSObject; + private readonly PSObject _constructorPSObject; - private static Collection> s_emptyMemberCollection = new Collection>(); - private static Collection> s_emptyMethodCollection = new Collection>(); - private static Collection> s_emptyPropertyCollection = new Collection>(); + private static readonly Collection> s_emptyMemberCollection = new Collection>(); + private static readonly Collection> s_emptyMethodCollection = new Collection>(); + private static readonly Collection> s_emptyPropertyCollection = new Collection>(); /// - /// Initializes a new instance of PSMemberSet with no initial members + /// Initializes a new instance of PSMemberSet with no initial members. /// - /// name for the member set - /// for invalid arguments + /// Name for the member set. + /// For invalid arguments. public PSMemberSet(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; this.internalMembers = new PSMemberInfoInternalCollection(); _members = new PSMemberInfoIntegratingCollection(this, s_emptyMemberCollection); @@ -3266,37 +3104,61 @@ public PSMemberSet(string name) /// /// Initializes a new instance of PSMemberSet with all the initial members in /// - /// name for the member set - /// members in the member set - /// for invalid arguments + /// Name for the member set. + /// Members in the member set. + /// For invalid arguments. public PSMemberSet(string name, IEnumerable members) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (members == null) { - throw PSTraceSource.NewArgumentNullException("members"); + throw PSTraceSource.NewArgumentNullException(nameof(members)); } + this.internalMembers = new PSMemberInfoInternalCollection(); foreach (PSMemberInfo member in members) { if (member == null) { - throw PSTraceSource.NewArgumentNullException("members"); + throw PSTraceSource.NewArgumentNullException(nameof(members)); } + this.internalMembers.Add(member.Copy()); } + + _members = new PSMemberInfoIntegratingCollection(this, s_emptyMemberCollection); + _properties = new PSMemberInfoIntegratingCollection(this, s_emptyPropertyCollection); + _methods = new PSMemberInfoIntegratingCollection(this, s_emptyMethodCollection); + } + + /// + /// Initializes a new instance of PSMemberSet with all the initial members in . + /// This constructor is supposed to be used in TypeTable to reuse the passed-in member collection. + /// Null-argument check is skipped here, so callers need to check arguments before passing in. + /// + /// Name for the member set. + /// Members in the member set. + internal PSMemberSet(string name, PSMemberInfoInternalCollection members) + { + Diagnostics.Assert(!string.IsNullOrEmpty(name), "Caller needs to guarantee not null or empty."); + Diagnostics.Assert(members != null, "Caller needs to guarantee not null."); + + this.name = name; + this.internalMembers = members; + _members = new PSMemberInfoIntegratingCollection(this, s_emptyMemberCollection); _properties = new PSMemberInfoIntegratingCollection(this, s_emptyPropertyCollection); _methods = new PSMemberInfoIntegratingCollection(this, s_emptyMethodCollection); } - private static Collection> s_typeMemberCollection = GetTypeMemberCollection(); - private static Collection> s_typeMethodCollection = GetTypeMethodCollection(); - private static Collection> s_typePropertyCollection = GetTypePropertyCollection(); + private static readonly Collection> s_typeMemberCollection = GetTypeMemberCollection(); + private static readonly Collection> s_typeMethodCollection = GetTypeMethodCollection(); + private static readonly Collection> s_typePropertyCollection = GetTypePropertyCollection(); private static Collection> GetTypeMemberCollection() { @@ -3304,6 +3166,7 @@ private static Collection> GetTypeMemberCollection returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } @@ -3314,6 +3177,7 @@ private static Collection> GetTypeMethodCollection returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } @@ -3324,27 +3188,30 @@ private static Collection> GetTypePropertyCollec returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); return returnValue; } /// - /// Used to create the Extended MemberSet + /// Used to create the Extended MemberSet. /// - /// name of the memberSet - /// object associated with this memberset - /// for invalid arguments + /// Name of the memberSet. + /// Object associated with this memberset. + /// For invalid arguments. internal PSMemberSet(string name, PSObject mshObject) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (mshObject == null) { - throw PSTraceSource.NewArgumentNullException("mshObject"); + throw PSTraceSource.NewArgumentNullException(nameof(mshObject)); } + _constructorPSObject = mshObject; this.internalMembers = mshObject.InstanceMembers; _members = new PSMemberInfoIntegratingCollection(this, s_typeMemberCollection); @@ -3358,61 +3225,34 @@ internal PSMemberSet(string name, PSObject mshObject) /// Gets a flag indicating whether the memberset will inherit members of the memberset /// of the same name in the "parent" class. /// - public bool InheritMembers - { - get - { - return this.inheritMembers; - } - } - + public bool InheritMembers => this.inheritMembers; /// - /// Gets the internal member collection + /// Gets the internal member collection. /// - internal virtual PSMemberInfoInternalCollection InternalMembers - { - get { return this.internalMembers; } - } + internal virtual PSMemberInfoInternalCollection InternalMembers => this.internalMembers; /// - /// Gets the member collection + /// Gets the member collection. /// - public PSMemberInfoCollection Members - { - get - { - return _members; - } - } + public PSMemberInfoCollection Members => _members; /// /// Gets the Property collection, or the members that are actually properties. /// - public PSMemberInfoCollection Properties - { - get - { - return _properties; - } - } + public PSMemberInfoCollection Properties => _properties; /// /// Gets the Method collection, or the members that are actually methods. /// - public PSMemberInfoCollection Methods - { - get - { - return _methods; - } - } + public PSMemberInfoCollection Methods => _methods; #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { if (_constructorPSObject == null) @@ -3422,6 +3262,7 @@ public override PSMemberInfo Copy() { memberSet.Members.Add(member); } + CloneBaseProperties(memberSet); return memberSet; } @@ -3434,43 +3275,25 @@ public override PSMemberInfo Copy() /// /// Gets the member type. For PSMemberSet the member type is PSMemberTypes.MemberSet. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.MemberSet; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.MemberSet; /// /// Gets the value of this member. The getter returns the PSMemberSet itself. /// - /// When trying to set the property + /// When trying to set the property. public override object Value { - get - { - return this; - } - set - { - throw new ExtendedTypeSystemException("CannotChangePSMemberSetValue", null, - ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); - } + get => this; + set => throw new ExtendedTypeSystemException("CannotChangePSMemberSetValue", null, + ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); } /// /// Gets the type of the value for this member. This returns typeof(PSMemberSet).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(PSMemberSet).FullName; - } - } - #endregion virtual implementation + public override string TypeNameOfValue => typeof(PSMemberSet).FullName; + #endregion virtual implementation } /// @@ -3485,8 +3308,8 @@ public override string TypeNameOfValue /// internal class PSInternalMemberSet : PSMemberSet { - private object _syncObject = new Object(); - private PSObject _psObject; + private readonly object _syncObject = new object(); + private readonly PSObject _psObject; #region Constructor @@ -3524,11 +3347,11 @@ internal override PSMemberInfoInternalCollection InternalMembers } // cache "psbase" and "psobject" - if (null == internalMembers) + if (internalMembers == null) { lock (_syncObject) { - if (null == internalMembers) + if (internalMembers == null) { internalMembers = new PSMemberInfoInternalCollection(); @@ -3542,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; } } @@ -3560,11 +3382,11 @@ internal override PSMemberInfoInternalCollection InternalMembers private void GenerateInternalMembersFromBase() { - if (_psObject.isDeserialized) + if (_psObject.IsDeserialized) { - if (_psObject.clrMembers != null) + if (_psObject.ClrMembers != null) { - foreach (PSMemberInfo member in _psObject.clrMembers) + foreach (PSMemberInfo member in _psObject.ClrMembers) { internalMembers.Add(member.Copy()); } @@ -3573,7 +3395,7 @@ private void GenerateInternalMembersFromBase() else { foreach (PSMemberInfo member in - PSObject.dotNetInstanceAdapter.BaseGetMembers(_psObject.ImmediateBaseObject)) + PSObject.DotNetInstanceAdapter.BaseGetMembers(_psObject.ImmediateBaseObject)) { internalMembers.Add(member.Copy()); } @@ -3584,11 +3406,11 @@ private PSMemberInfoInternalCollection GetInternalMembersFromAdapt { PSMemberInfoInternalCollection retVal = new PSMemberInfoInternalCollection(); - if (_psObject.isDeserialized) + if (_psObject.IsDeserialized) { - if (_psObject.adaptedMembers != null) + if (_psObject.AdaptedMembers != null) { - foreach (PSMemberInfo member in _psObject.adaptedMembers) + foreach (PSMemberInfo member in _psObject.AdaptedMembers) { retVal.Add(member.Copy()); } @@ -3608,8 +3430,8 @@ private PSMemberInfoInternalCollection GetInternalMembersFromAdapt private void GenerateInternalMembersFromPSObject() { - PSMemberInfoCollection members = PSObject.dotNetInstanceAdapter.BaseGetMembers( - _psObject); + PSMemberInfoCollection members = PSObject.DotNetInstanceAdapter.BaseGetMembers( + _psObject); foreach (PSMemberInfo member in members) { internalMembers.Add(member.Copy()); @@ -3620,7 +3442,7 @@ private void GenerateInternalMembersFromPSObject() } /// - /// Serves as a list of property names + /// Serves as a list of property names. /// /// /// It is permitted to subclass @@ -3629,9 +3451,9 @@ private void GenerateInternalMembersFromPSObject() public class PSPropertySet : PSMemberInfo { /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. public override string ToString() { StringBuilder returnValue = new StringBuilder(); @@ -3644,99 +3466,107 @@ public override string ToString() returnValue.Append(property); returnValue.Append(", "); } + returnValue.Remove(returnValue.Length - 2, 2); } - returnValue.Append("}"); + + returnValue.Append('}'); return returnValue.ToString(); } /// - /// Initializes a new instance of PSPropertySet with a name and list of property names + /// Initializes a new instance of PSPropertySet with a name and list of property names. /// - /// name of the set - /// name of the properties in the set - /// for invalid arguments + /// Name of the set. + /// Name of the properties in the set. + /// For invalid arguments. public PSPropertySet(string name, IEnumerable referencedPropertyNames) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + this.name = name; if (referencedPropertyNames == null) { - throw PSTraceSource.NewArgumentNullException("referencedPropertyNames"); + throw PSTraceSource.NewArgumentNullException(nameof(referencedPropertyNames)); } + ReferencedPropertyNames = new Collection(); foreach (string referencedPropertyName in referencedPropertyNames) { - if (String.IsNullOrEmpty(referencedPropertyName)) + if (string.IsNullOrEmpty(referencedPropertyName)) { - throw PSTraceSource.NewArgumentException("referencedPropertyNames"); + throw PSTraceSource.NewArgumentException(nameof(referencedPropertyNames)); } + ReferencedPropertyNames.Add(referencedPropertyName); } } /// - /// Gets the property names in this property set + /// Initializes a new instance of PSPropertySet with a name and list of property names. + /// This constructor is supposed to be used in TypeTable to reuse the passed-in property name list. + /// Null-argument check is skipped here, so callers need to check arguments before passing in. + /// + /// Name of the set. + /// Name of the properties in the set. + internal PSPropertySet(string name, List referencedPropertyNameList) + { + Diagnostics.Assert(!string.IsNullOrEmpty(name), "Caller needs to guarantee not null or empty."); + Diagnostics.Assert(referencedPropertyNameList != null, "Caller needs to guarantee not null."); + + // We use the constructor 'public Collection(IList list)' to create the collection, + // so that the passed-in list is directly used as the backing store of the collection. + this.name = name; + ReferencedPropertyNames = new Collection(referencedPropertyNameList); + } + + /// + /// Gets the property names in this property set. /// public Collection ReferencedPropertyNames { get; } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSPropertySet member = new PSPropertySet(name, ReferencedPropertyNames); CloneBaseProperties(member); return member; } + /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.PropertySet; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.PropertySet; /// /// Gets the PSPropertySet itself. /// - /// When setting the member + /// When setting the member. public override object Value { - get - { - return this; - } - set - { - throw new ExtendedTypeSystemException("CannotChangePSPropertySetValue", null, - ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); - } + get => this; + set => throw new ExtendedTypeSystemException("CannotChangePSPropertySetValue", null, + ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); } /// /// Gets the type of the value for this member. This returns typeof(PSPropertySet).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(PSPropertySet).FullName; - } - } + public override string TypeNameOfValue => typeof(PSPropertySet).FullName; + #endregion virtual implementation } /// - /// Used to access the adapted or base events from the BaseObject + /// Used to access the adapted or base events from the BaseObject. /// /// /// It is permitted to subclass @@ -3745,15 +3575,15 @@ public override string TypeNameOfValue public class PSEvent : PSMemberInfo { /// - /// Returns the string representation of this member + /// Returns the string representation of this member. /// - /// This property as a string + /// This property as a string. 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()) @@ -3766,17 +3596,18 @@ public override string ToString() loopCounter++; } - eventDefinition.Append(")"); + eventDefinition.Append(')'); return eventDefinition.ToString(); } + internal EventInfo baseEvent; /// - /// Constructs this event + /// Constructs this event. /// - /// The actual event - /// for invalid arguments + /// The actual event. + /// For invalid arguments. internal PSEvent(EventInfo baseEvent) { this.baseEvent = baseEvent; @@ -3784,10 +3615,11 @@ internal PSEvent(EventInfo baseEvent) } #region virtual implementation + /// - /// returns a new PSMemberInfo that is a copy of this PSMemberInfo + /// Returns a new PSMemberInfo that is a copy of this PSMemberInfo. /// - /// a new PSMemberInfo that is a copy of this PSMemberInfo + /// A new PSMemberInfo that is a copy of this PSMemberInfo. public override PSMemberInfo Copy() { PSEvent member = new PSEvent(this.baseEvent); @@ -3796,50 +3628,32 @@ public override PSMemberInfo Copy() } /// - /// Gets the member type + /// Gets the member type. /// - public override PSMemberTypes MemberType - { - get - { - return PSMemberTypes.Event; - } - } + public override PSMemberTypes MemberType => PSMemberTypes.Event; /// /// Gets the value of this member. The getter returns the /// actual .NET event that this type wraps. /// - /// When setting the member + /// When setting the member. public sealed override object Value { - get - { - return baseEvent; - } - set - { - throw new ExtendedTypeSystemException("CannotChangePSEventInfoValue", null, - ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); - } + get => baseEvent; + set => throw new ExtendedTypeSystemException("CannotChangePSEventInfoValue", null, + ExtendedTypeSystem.CannotSetValueForMemberType, this.GetType().FullName); } /// /// Gets the type of the value for this member. This always returns typeof(PSMethod).FullName. /// - public override string TypeNameOfValue - { - get - { - return typeof(PSEvent).FullName; - } - } + public override string TypeNameOfValue => typeof(PSEvent).FullName; #endregion virtual implementation } /// - /// A dynamic member + /// A dynamic member. /// public class PSDynamicMember : PSMemberInfo { @@ -3855,23 +3669,17 @@ public override string ToString() } /// - public override PSMemberTypes MemberType - { - get { return PSMemberTypes.Dynamic; } - } + public override PSMemberTypes MemberType => PSMemberTypes.Dynamic; /// public override object Value { - get { throw PSTraceSource.NewInvalidOperationException(); } - set { throw PSTraceSource.NewInvalidOperationException(); } + get => throw PSTraceSource.NewInvalidOperationException(); + set => throw PSTraceSource.NewInvalidOperationException(); } /// - public override string TypeNameOfValue - { - get { return "dynamic"; } - } + public override string TypeNameOfValue => "dynamic"; /// public override PSMemberInfo Copy() @@ -3885,9 +3693,9 @@ public override PSMemberInfo Copy() #region Member collection classes and its auxiliary classes /// - /// /// This class is used in PSMemberInfoInternalCollection and ReadOnlyPSMemberInfoCollection + /// /// This class is used in PSMemberInfoInternalCollection and ReadOnlyPSMemberInfoCollection. /// - internal class MemberMatch + internal static class MemberMatch { internal static WildcardPattern GetNamePattern(string name) { @@ -3895,31 +3703,33 @@ internal static WildcardPattern GetNamePattern(string name) { return WildcardPattern.Get(name, WildcardOptions.IgnoreCase); } + return null; } - /// - /// Returns all members in memberList matching name and memberTypes + /// Returns all members in memberList matching name and memberTypes. /// /// Members to look for member with the correct types and name. - /// Name of the members to look for. The name might contain globbing characters - /// WildcardPattern out of name - /// type of members we want to retrieve + /// Name of the members to look for. The name might contain globbing characters. + /// WildcardPattern out of name. + /// Type of members we want to retrieve. /// A collection of members of the right types and name extracted from memberList. - /// for invalid arguments - internal static PSMemberInfoInternalCollection Match(PSMemberInfoInternalCollection memberList, string name, WildcardPattern nameMatch, PSMemberTypes memberTypes) where T : PSMemberInfo + /// For invalid arguments. + internal static PSMemberInfoInternalCollection Match(PSMemberInfoInternalCollection memberList, string name, WildcardPattern nameMatch, PSMemberTypes memberTypes) + where T : PSMemberInfo { PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); if (memberList == null) { - throw PSTraceSource.NewArgumentNullException("memberList"); + throw PSTraceSource.NewArgumentNullException(nameof(memberList)); } - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + if (nameMatch == null) { T member = memberList[name]; @@ -3927,6 +3737,7 @@ internal static PSMemberInfoInternalCollection Match(PSMemberInfoInternalC { returnValue.Add(member); } + return returnValue; } @@ -3937,29 +3748,40 @@ internal static PSMemberInfoInternalCollection Match(PSMemberInfoInternalC returnValue.Add(member); } } + return returnValue; } } /// - /// Serves as the collection of members in an PSObject or MemberSet + /// A Predicate that determine if a member name matches a criterion. + /// + /// + /// if the matches the predicate, otherwise . + public delegate bool MemberNamePredicate(string memberName); + + /// + /// Serves as the collection of members in an PSObject or MemberSet. /// public abstract class PSMemberInfoCollection : IEnumerable where T : PSMemberInfo { #region ctor + /// - /// Initializes a new instance of an PSMemberInfoCollection derived class + /// Initializes a new instance of an PSMemberInfoCollection derived class. /// protected PSMemberInfoCollection() { } + #endregion ctor #region abstract + /// - /// Adds a member to this collection + /// Adds a member to this collection. /// - /// member to be added + /// Member to be added. /// /// When: /// adding a member to an PSMemberSet from the type configuration file or @@ -3967,13 +3789,13 @@ protected PSMemberInfoCollection() /// trying to add a member with a type not compatible with this collection or /// a member by this name is already present /// - /// for invalid arguments + /// For invalid arguments. public abstract void Add(T member); /// - /// Adds a member to this collection + /// Adds a member to this collection. /// - /// member to be added + /// Member to be added. /// flag to indicate that validation has already been done /// on this new member. Use only when you can guarantee that the input will not /// cause any of the errors normally caught by this method. @@ -3984,81 +3806,78 @@ protected PSMemberInfoCollection() /// trying to add a member with a type not compatible with this collection or /// a member by this name is already present /// - /// for invalid arguments + /// For invalid arguments. public abstract void Add(T member, bool preValidated); /// - /// Removes a member from this collection + /// Removes a member from this collection. /// - /// name of the member to be removed + /// Name of the member to be removed. /// /// When: /// removing a member from an PSMemberSet from the type configuration file /// removing a member with a reserved member name or /// trying to remove a member with a type not compatible with this collection /// - /// for invalid arguments + /// For invalid arguments. public abstract void Remove(string name); /// /// Gets the member in this collection matching name. If the member does not exist, null is returned. /// - /// name of the member to look for - /// the member matching name - /// for invalid arguments - public abstract T this[string name] - { - get; - } + /// Name of the member to look for. + /// The member matching name. + /// For invalid arguments. + public abstract T this[string name] { get; } + #endregion abstract #region Match + /// - /// Returns all members in the collection matching name + /// Returns all members in the collection matching name. /// - /// name of the members to be return. May contain wildcard characters. - /// all members in the collection matching name - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// All members in the collection matching name. + /// For invalid arguments. public abstract ReadOnlyPSMemberInfoCollection Match(string name); /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// All members in the collection matching name and types. + /// For invalid arguments. public abstract ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes); /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// match options - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// Match options. + /// All members in the collection matching name and types. + /// For invalid arguments. internal abstract ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes, MshMemberMatchOptions matchOptions); - #endregion Match internal static bool IsReservedName(string name) { - return (String.Equals(name, PSObject.BaseObjectMemberSetName, StringComparison.OrdinalIgnoreCase) || - String.Equals(name, PSObject.AdaptedMemberSetName, StringComparison.OrdinalIgnoreCase) || - String.Equals(name, PSObject.ExtendedMemberSetName, StringComparison.OrdinalIgnoreCase) || - String.Equals(name, PSObject.PSObjectMemberSetName, StringComparison.OrdinalIgnoreCase) || - String.Equals(name, PSObject.PSTypeNames, StringComparison.OrdinalIgnoreCase)); + return (string.Equals(name, PSObject.BaseObjectMemberSetName, StringComparison.OrdinalIgnoreCase) || + string.Equals(name, PSObject.AdaptedMemberSetName, StringComparison.OrdinalIgnoreCase) || + string.Equals(name, PSObject.ExtendedMemberSetName, StringComparison.OrdinalIgnoreCase) || + string.Equals(name, PSObject.PSObjectMemberSetName, StringComparison.OrdinalIgnoreCase) || + string.Equals(name, PSObject.PSTypeNames, StringComparison.OrdinalIgnoreCase)); } #region IEnumerable - /// - /// Gets the general enumerator for this collection + /// Gets the general enumerator for this collection. /// - /// the enumerator for this collection + /// The enumerator for this collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -4067,13 +3886,16 @@ IEnumerator IEnumerable.GetEnumerator() /// /// Gets the specific enumerator for this collection. /// - /// the enumerator for this collection + /// The enumerator for this collection. public abstract IEnumerator GetEnumerator(); + #endregion IEnumerable + + internal abstract T FirstOrDefault(MemberNamePredicate predicate); } /// - /// Serves as a read only collection of members + /// Serves as a read only collection of members. /// /// /// It is permitted to subclass @@ -4081,75 +3903,79 @@ IEnumerator IEnumerable.GetEnumerator() /// public class ReadOnlyPSMemberInfoCollection : IEnumerable where T : PSMemberInfo { - private PSMemberInfoInternalCollection _members; + private readonly PSMemberInfoInternalCollection _members; /// - /// Initializes a new instance of ReadOnlyPSMemberInfoCollection with the given members + /// Initializes a new instance of ReadOnlyPSMemberInfoCollection with the given members. /// /// - /// for invalid arguments + /// For invalid arguments. internal ReadOnlyPSMemberInfoCollection(PSMemberInfoInternalCollection members) { if (members == null) { - throw PSTraceSource.NewArgumentNullException("members"); + throw PSTraceSource.NewArgumentNullException(nameof(members)); } + _members = members; } /// /// Return the member in this collection matching name. If the member does not exist, null is returned. /// - /// name of the member to look for - /// the member matching name - /// for invalid arguments + /// Name of the member to look for. + /// The member matching name. + /// For invalid arguments. public T this[string name] { get { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return _members[name]; } } /// - /// Returns all members in the collection matching name + /// Returns all members in the collection matching name. /// - /// name of the members to be return. May contain wildcard characters. - /// all members in the collection matching name - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// All members in the collection matching name. + /// For invalid arguments. public ReadOnlyPSMemberInfoCollection Match(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return _members.Match(name); } /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// All members in the collection matching name and types. + /// For invalid arguments. public ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return _members.Match(name, memberTypes); } /// - /// Gets the general enumerator for this collection + /// Gets the general enumerator for this collection. /// - /// the enumerator for this collection + /// The enumerator for this collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -4158,48 +3984,73 @@ IEnumerator IEnumerable.GetEnumerator() /// /// Gets the specific enumerator for this collection. /// - /// the enumerator for this collection + /// The enumerator for this collection. public virtual IEnumerator GetEnumerator() { return _members.GetEnumerator(); } /// - /// Gets the number of elements in this collection + /// Gets the number of elements in this collection. /// - public int Count { get { return _members.Count; } } + public int Count => _members.Count; /// - /// Returns the 0 based member identified by index + /// Returns the 0 based member identified by index. /// - /// index of the member to retrieve - /// for invalid arguments - public T this[int index] { get { return _members[index]; } } + /// Index of the member to retrieve. + /// For invalid arguments. + public T this[int index] => _members[index]; } /// - /// Collection of members + /// Collection of members. /// internal class PSMemberInfoInternalCollection : PSMemberInfoCollection, IEnumerable where T : PSMemberInfo { - private readonly OrderedDictionary _members; + private OrderedDictionary _members; private int _countHidden; /// - /// Constructs this collection + /// Gets the OrderedDictionary for holding all members. + /// We use this property to delay initializing _members until we absolutely need to. + /// + private OrderedDictionary Members + { + get + { + if (_members == null) + { + System.Threading.Interlocked.CompareExchange(ref _members, new OrderedDictionary(StringComparer.OrdinalIgnoreCase), null); + } + + return _members; + } + } + + /// + /// Constructs this collection. /// internal PSMemberInfoInternalCollection() { - _members = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Constructs this collection with an initial capacity. + /// + internal PSMemberInfoInternalCollection(int capacity) + { + _members = new OrderedDictionary(capacity, StringComparer.OrdinalIgnoreCase); } private void Replace(T oldMember, T newMember) { - _members[newMember.Name] = newMember; + Members[newMember.Name] = newMember; if (oldMember.IsHidden) { _countHidden--; } + if (newMember.IsHidden) { _countHidden++; @@ -4207,58 +4058,61 @@ private void Replace(T oldMember, T newMember) } /// - /// Adds a member to the collection by replacing the one with the same name + /// Adds a member to the collection by replacing the one with the same name. /// /// internal void Replace(T newMember) { Diagnostics.Assert(newMember != null, "called from internal code that checks for new member not null"); - lock (_members) + // Save to a local variable to reduce property access. + var members = Members; + lock (members) { - var oldMember = _members[newMember.Name] as T; + var oldMember = members[newMember.Name] as T; Diagnostics.Assert(oldMember != null, "internal code checks member already exists"); Replace(oldMember, newMember); } } /// - /// Adds a member to this collection + /// Adds a member to this collection. /// - /// member to be added - /// when a member by this name is already present - /// for invalid arguments + /// Member to be added. + /// When a member by this name is already present. + /// For invalid arguments. public override void Add(T member) { Add(member, false); } /// - /// Adds a member to this collection + /// Adds a member to this collection. /// - /// member to be added + /// Member to be added. /// flag to indicate that validation has already been done /// on this new member. Use only when you can guarantee that the input will not /// cause any of the errors normally caught by this method. - /// when a member by this name is already present - /// for invalid arguments + /// When a member by this name is already present. + /// For invalid arguments. public override void Add(T member, bool preValidated) { if (member == null) { - throw PSTraceSource.NewArgumentNullException("member"); + throw PSTraceSource.NewArgumentNullException(nameof(member)); } - lock (_members) + // Save to a local variable to reduce property access. + var members = Members; + lock (members) { - var existingMember = _members[member.Name] as T; - if (existingMember != null) + if (members[member.Name] is T existingMember) { Replace(existingMember, member); } else { - _members[member.Name] = member; + members[member.Name] = member; if (member.IsHidden) { _countHidden++; @@ -4268,16 +4122,16 @@ public override void Add(T member, bool preValidated) } /// - /// Removes a member from this collection + /// Removes a member from this collection. /// - /// name of the member to be removed - /// When removing a member with a reserved member name - /// for invalid arguments + /// Name of the member to be removed. + /// When removing a member with a reserved member name. + /// For invalid arguments. public override void Remove(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (IsReservedName(name)) @@ -4288,33 +4142,43 @@ public override void Remove(string name) name); } + if (_members == null) + { + return; + } + lock (_members) { - var member = _members[name] as PSMemberInfo; - if (member != null) + if (_members[name] is PSMemberInfo member) { if (member.IsHidden) { _countHidden--; } + _members.Remove(name); } } } /// - /// Returns the member in this collection matching name + /// Returns the member in this collection matching name. /// - /// name of the member to look for - /// the member matching name - /// for invalid arguments + /// Name of the member to look for. + /// The member matching name. + /// For invalid arguments. public override T this[string name] { get { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) + { + throw PSTraceSource.NewArgumentException(nameof(name)); + } + + if (_members == null) { - throw PSTraceSource.NewArgumentException("name"); + return null; } lock (_members) @@ -4325,49 +4189,51 @@ public override T this[string name] } /// - /// Returns all members in the collection matching name + /// Returns all members in the collection matching name. /// - /// name of the members to be return. May contain wildcard characters. - /// all members in the collection matching name - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// All members in the collection matching name. + /// For invalid arguments. public override ReadOnlyPSMemberInfoCollection Match(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return Match(name, PSMemberTypes.All, MshMemberMatchOptions.None); } /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// All members in the collection matching name and types. + /// For invalid arguments. public override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return Match(name, memberTypes, MshMemberMatchOptions.None); } /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// match options - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// Match options. + /// All members in the collection matching name and types. + /// For invalid arguments. internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes, MshMemberMatchOptions matchOptions) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } PSMemberInfoInternalCollection internalMembers = GetInternalMembers(matchOptions); @@ -4377,6 +4243,12 @@ internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberT private PSMemberInfoInternalCollection GetInternalMembers(MshMemberMatchOptions matchOptions) { PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); + + if (_members == null) + { + return returnValue; + } + lock (_members) { foreach (T member in _members.Values.OfType()) @@ -4392,12 +4264,17 @@ private PSMemberInfoInternalCollection GetInternalMembers(MshMemberMatchOptio } /// - /// The number of elements in this collection + /// The number of elements in this collection. /// internal int Count { get { + if (_members == null) + { + return 0; + } + lock (_members) { return _members.Count; @@ -4406,12 +4283,17 @@ internal int Count } /// - /// The number of elements in this collection not marked as Hidden + /// The number of elements in this collection not marked as Hidden. /// internal int VisibleCount { get { + if (_members == null) + { + return 0; + } + lock (_members) { return _members.Count - _countHidden; @@ -4420,14 +4302,19 @@ internal int VisibleCount } /// - /// Returns the 0 based member identified by index + /// Returns the 0 based member identified by index. /// - /// index of the member to retrieve - /// for invalid arguments + /// Index of the member to retrieve. + /// For invalid arguments. internal T this[int index] { get { + if (_members == null) + { + return null; + } + lock (_members) { return _members[index] as T; @@ -4440,32 +4327,64 @@ internal T this[int index] /// This virtual works around the difficulty of implementing /// interfaces virtually. /// - /// the enumerator for this collection + /// The enumerator for this collection. public override IEnumerator GetEnumerator() { + if (_members == null) + { + return Enumerable.Empty().GetEnumerator(); + } + lock (_members) { // Copy the members to a list so that iteration can be performed without holding a lock. return _members.Values.OfType().ToList().GetEnumerator(); } } + + /// + /// Returns the first member that matches the specified . + /// + internal override T FirstOrDefault(MemberNamePredicate predicate) + { + lock (_members) + { + foreach (DictionaryEntry entry in _members) + { + if (predicate((string)entry.Key)) + { + return entry.Value as T; + } + } + } + + return null; + } } #region CollectionEntry - internal class CollectionEntry where T : PSMemberInfo { internal delegate PSMemberInfoInternalCollection GetMembersDelegate(PSObject obj); + internal delegate T GetMemberDelegate(PSObject obj, string name); - internal CollectionEntry(GetMembersDelegate getMembers, GetMemberDelegate getMember, - bool shouldReplicateWhenReturning, bool shouldCloneWhenReturning, string collectionNameForTracing) + internal delegate T GetFirstOrDefaultDelegate(PSObject obj, MemberNamePredicate predicate); + + internal CollectionEntry( + GetMembersDelegate getMembers, + GetMemberDelegate getMember, + GetFirstOrDefaultDelegate getFirstOrDefault, + bool shouldReplicateWhenReturning, + bool shouldCloneWhenReturning, + string collectionNameForTracing) { GetMembers = getMembers; GetMember = getMember; - ShouldReplicateWhenReturning = shouldReplicateWhenReturning; - ShouldCloneWhenReturning = shouldCloneWhenReturning; + GetFirstOrDefault = getFirstOrDefault; + _shouldReplicateWhenReturning = shouldReplicateWhenReturning; + _shouldCloneWhenReturning = shouldCloneWhenReturning; CollectionNameForTracing = collectionNameForTracing; } @@ -4473,12 +4392,30 @@ internal CollectionEntry(GetMembersDelegate getMembers, GetMemberDelegate getMem internal GetMemberDelegate GetMember { get; } - internal bool ShouldReplicateWhenReturning { get; } - - internal bool ShouldCloneWhenReturning { get; } + internal GetFirstOrDefaultDelegate GetFirstOrDefault { get; } internal string CollectionNameForTracing { get; } + + private readonly bool _shouldReplicateWhenReturning; + + private readonly bool _shouldCloneWhenReturning; + + internal T CloneOrReplicateObject(object owner, T member) + { + if (_shouldCloneWhenReturning) + { + member = (T)member.Copy(); + } + + if (_shouldReplicateWhenReturning) + { + member.ReplicateInstance(owner); + } + + return member; + } } + #endregion CollectionEntry internal static class ReservedNameMembers @@ -4498,6 +4435,7 @@ private static object GenerateMemberSet(string name, object obj) mshOwner.InstanceMembers.Add(memberSet); memberSet.instance = mshOwner; } + return memberSet; } @@ -4545,7 +4483,7 @@ public static Collection PSTypeNames(PSObject o) internal static void GeneratePSTypeNames(object obj) { PSObject mshOwner = PSObject.AsPSObject(obj); - if (null != mshOwner.InstanceMembers[PSObject.PSTypeNames]) + if (mshOwner.InstanceMembers[PSObject.PSTypeNames] != null) { // PSTypeNames member set is already generated..just return. return; @@ -4568,9 +4506,9 @@ internal class PSMemberInfoIntegratingCollection : PSMemberInfoCollection, private void GenerateAllReservedMembers() { - if (!_mshOwner.hasGeneratedReservedMembers) + if (!_mshOwner.HasGeneratedReservedMembers) { - _mshOwner.hasGeneratedReservedMembers = true; + _mshOwner.HasGeneratedReservedMembers = true; ReservedNameMembers.GeneratePSExtendedMemberSet(_mshOwner); ReservedNameMembers.GeneratePSBaseMemberSet(_mshOwner); ReservedNameMembers.GeneratePSObjectMemberSet(_mshOwner); @@ -4585,27 +4523,26 @@ private void GenerateAllReservedMembers() internal Collection> Collections { get; } - - private PSObject _mshOwner; - private PSMemberSet _memberSetOwner; + private readonly PSObject _mshOwner; + private readonly PSMemberSet _memberSetOwner; internal PSMemberInfoIntegratingCollection(object owner, Collection> collections) { if (owner == null) { - throw PSTraceSource.NewArgumentNullException("owner"); + throw PSTraceSource.NewArgumentNullException(nameof(owner)); } _mshOwner = owner as PSObject; _memberSetOwner = owner as PSMemberSet; if (_mshOwner == null && _memberSetOwner == null) { - throw PSTraceSource.NewArgumentException("owner"); + throw PSTraceSource.NewArgumentException(nameof(owner)); } if (collections == null) { - throw PSTraceSource.NewArgumentNullException("collections"); + throw PSTraceSource.NewArgumentNullException(nameof(collections)); } Collections = collections; @@ -4616,9 +4553,9 @@ internal PSMemberInfoIntegratingCollection(object owner, Collection - /// Adds member to the collection + /// Adds member to the collection. /// - /// member to be added + /// Member to be added. /// /// When /// member is an PSProperty or PSMethod @@ -4628,16 +4565,16 @@ internal PSMemberInfoIntegratingCollection(object owner, Collection - /// for invalid arguments + /// For invalid arguments. public override void Add(T member) { Add(member, false); } /// - /// Adds member to the collection + /// Adds member to the collection. /// - /// member to be added + /// Member to be added. /// flag to indicate that validation has already been done /// on this new member. Use only when you can guarantee that the input will not /// cause any of the errors normally caught by this method. @@ -4650,12 +4587,12 @@ public override void Add(T member) /// a member with this name already exists /// trying to add a member to a static memberset /// - /// for invalid arguments + /// For invalid arguments. public override void Add(T member, bool preValidated) { if (member == null) { - throw PSTraceSource.NewArgumentNullException("member"); + throw PSTraceSource.NewArgumentNullException(nameof(member)); } if (!preValidated) @@ -4668,7 +4605,6 @@ public override void Add(T member, bool preValidated) ExtendedTypeSystem.CannotAddPropertyOrMethod); } - if (_memberSetOwner != null && _memberSetOwner.IsReservedMember) { throw new ExtendedTypeSystemException("CannotAddToReservedNameMemberset", @@ -4682,7 +4618,7 @@ public override void Add(T member, bool preValidated) } /// - /// Auxiliary to add members from types.xml + /// Auxiliary to add members from types.xml. /// /// /// @@ -4703,9 +4639,9 @@ internal void AddToReservedMemberSet(T member, bool preValidated) } /// - /// Adds member to the collection + /// Adds member to the collection. /// - /// member to be added + /// Member to be added. /// flag to indicate that validation has already been done /// on this new member. Use only when you can guarantee that the input will not /// cause any of the errors normally caught by this method. @@ -4716,12 +4652,12 @@ internal void AddToReservedMemberSet(T member, bool preValidated) /// a member with this name already exists /// trying to add a member to a static memberset /// - /// for invalid arguments + /// For invalid arguments. internal void AddToTypesXmlCache(T member, bool preValidated) { if (member == null) { - throw PSTraceSource.NewArgumentNullException("member"); + throw PSTraceSource.NewArgumentNullException(nameof(member)); } if (!preValidated) @@ -4744,8 +4680,9 @@ internal void AddToTypesXmlCache(T member, bool preValidated) TypeTable typeTable = _mshOwner.GetTypeTable(); if (typeTable != null) { - PSMemberInfoInternalCollection typesXmlMembers = typeTable.GetMembers(_mshOwner.InternalTypeNames); - if (typesXmlMembers[member.Name] != null) + var typesXmlMembers = typeTable.GetMembers(_mshOwner.InternalTypeNames); + var typesXmlMember = typesXmlMembers[member.Name]; + if (typesXmlMember is T) { throw new ExtendedTypeSystemException( "AlreadyPresentInTypesXml", @@ -4755,6 +4692,7 @@ internal void AddToTypesXmlCache(T member, bool preValidated) } } } + memberToBeAdded.ReplicateInstance(_mshOwner); _mshOwner.InstanceMembers.Add(memberToBeAdded, preValidated); @@ -4771,20 +4709,20 @@ internal void AddToTypesXmlCache(T member, bool preValidated) } /// - /// Removes the member named name from the collection + /// Removes the member named name from the collection. /// - /// Name of the member to be removed + /// Name of the member to be removed. /// /// When trying to remove a member with a type not compatible with this collection /// When trying to remove a member from a static memberset /// When trying to remove a member from a MemberSet with a reserved name /// - /// for invalid arguments + /// For invalid arguments. public override void Remove(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (_mshOwner != null) @@ -4812,7 +4750,6 @@ public override void Remove(string name) _memberSetOwner.InternalMembers.Remove(name); } - /// /// Method which checks if the is reserved and if so /// it will ensure that the particular reserved member is loaded into the @@ -4825,7 +4762,7 @@ public override void Remove(string name) /// private void EnsureReservedMemberIsLoaded(string name) { - Diagnostics.Assert(!String.IsNullOrEmpty(name), + Diagnostics.Assert(!string.IsNullOrEmpty(name), "Name cannot be null or empty"); // Length >= psbase (shortest special member) @@ -4854,21 +4791,20 @@ private void EnsureReservedMemberIsLoaded(string name) } } - /// - /// Returns the name corresponding to name or null if it is not present + /// Returns the name corresponding to name or null if it is not present. /// - /// name of the member to return - /// for invalid arguments + /// Name of the member to return. + /// For invalid arguments. public override T this[string name] { get { - using (PSObject.memberResolution.TraceScope("Lookup")) + using (PSObject.MemberResolution.TraceScope("Lookup")) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } PSMemberInfo member; @@ -4883,10 +4819,9 @@ public override T this[string name] if (PSObject.HasInstanceMembers(_mshOwner, out instanceMembers)) { member = instanceMembers[name]; - T memberAsT = member as T; - if (memberAsT != null) + if (member is T memberAsT) { - PSObject.memberResolution.WriteLine("Found PSObject instance member: {0}.", name); + PSObject.MemberResolution.WriteLine("Found PSObject instance member: {0}.", name); return memberAsT; } } @@ -4895,13 +4830,12 @@ public override T this[string name] { member = _memberSetOwner.InternalMembers[name]; delegateOwner = _memberSetOwner.instance; - T memberAsT = member as T; - if (memberAsT != null) + if (member is T memberAsT) { // In membersets we cannot replicate the instance when adding // since the memberset might not yet have an associated PSObject. // We replicate the instance when returning the members of the memberset. - PSObject.memberResolution.WriteLine("Found PSMemberSet member: {0}.", name); + PSObject.MemberResolution.WriteLine("Found PSMemberSet member: {0}.", name); member.ReplicateInstance(delegateOwner); return memberAsT; } @@ -4917,15 +4851,7 @@ public override T this[string name] T memberAsT = collection.GetMember((PSObject)delegateOwner, name); if (memberAsT != null) { - if (collection.ShouldCloneWhenReturning) - { - memberAsT = (T)memberAsT.Copy(); - } - if (collection.ShouldReplicateWhenReturning) - { - memberAsT.ReplicateInstance(delegateOwner); - } - return memberAsT; + return collection.CloneOrReplicateObject(delegateOwner, memberAsT); } } @@ -4934,10 +4860,9 @@ public override T this[string name] } } - private PSMemberInfoInternalCollection GetIntegratedMembers(MshMemberMatchOptions matchOptions) { - using (PSObject.memberResolution.TraceScope("Generating the total list of members")) + using (PSObject.MemberResolution.TraceScope("Generating the total list of members")) { PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); object delegateOwner; @@ -4948,8 +4873,7 @@ private PSMemberInfoInternalCollection GetIntegratedMembers(MshMemberMatchOpt { if (member.MatchesOptions(matchOptions)) { - T memberAsT = member as T; - if (memberAsT != null) + if (member is T memberAsT) { returnValue.Add(memberAsT); } @@ -4963,8 +4887,7 @@ private PSMemberInfoInternalCollection GetIntegratedMembers(MshMemberMatchOpt { if (member.MatchesOptions(matchOptions)) { - T memberAsT = member as T; - if (memberAsT != null) + if (member is T memberAsT) { member.ReplicateInstance(delegateOwner); returnValue.Add(memberAsT); @@ -4985,82 +4908,74 @@ private PSMemberInfoInternalCollection GetIntegratedMembers(MshMemberMatchOpt PSMemberInfo previousMember = returnValue[member.Name]; if (previousMember != null) { - PSObject.memberResolution.WriteLine("Member \"{0}\" of type \"{1}\" has been ignored because a member with the same name and type \"{2}\" is already present.", + PSObject.MemberResolution.WriteLine("Member \"{0}\" of type \"{1}\" has been ignored because a member with the same name and type \"{2}\" is already present.", member.Name, member.MemberType, previousMember.MemberType); continue; } + if (!member.MatchesOptions(matchOptions)) { - PSObject.memberResolution.WriteLine("Skipping hidden member \"{0}\".", member.Name); + PSObject.MemberResolution.WriteLine("Skipping hidden member \"{0}\".", member.Name); continue; } - T memberToAdd; - if (collection.ShouldCloneWhenReturning) - { - memberToAdd = (T)member.Copy(); - } - else - { - memberToAdd = member; - } - if (collection.ShouldReplicateWhenReturning) - { - memberToAdd.ReplicateInstance(delegateOwner); - } + + T memberToAdd = collection.CloneOrReplicateObject(delegateOwner, member); returnValue.Add(memberToAdd); } } + return returnValue; } } - /// - /// Returns all members in the collection matching name + /// Returns all members in the collection matching name. /// - /// name of the members to be return. May contain wildcard characters. - /// all members in the collection matching name - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// All members in the collection matching name. + /// For invalid arguments. public override ReadOnlyPSMemberInfoCollection Match(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return Match(name, PSMemberTypes.All, MshMemberMatchOptions.None); } /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// All members in the collection matching name and types. + /// For invalid arguments. public override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } + return Match(name, memberTypes, MshMemberMatchOptions.None); } /// - /// Returns all members in the collection matching name and types + /// Returns all members in the collection matching name and types. /// - /// name of the members to be return. May contain wildcard characters. - /// type of the members to be searched. - /// search options - /// all members in the collection matching name and types - /// for invalid arguments + /// Name of the members to be return. May contain wildcard characters. + /// Type of the members to be searched. + /// Search options. + /// All members in the collection matching name and types. + /// For invalid arguments. internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberTypes memberTypes, MshMemberMatchOptions matchOptions) { - using (PSObject.memberResolution.TraceScope("Matching \"{0}\"", name)) + using (PSObject.MemberResolution.TraceScope("Matching \"{0}\"", name)) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (_mshOwner != null) @@ -5071,7 +4986,7 @@ internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberT WildcardPattern nameMatch = MemberMatch.GetNamePattern(name); PSMemberInfoInternalCollection allMembers = GetIntegratedMembers(matchOptions); ReadOnlyPSMemberInfoCollection returnValue = new ReadOnlyPSMemberInfoCollection(MemberMatch.Match(allMembers, name, nameMatch, memberTypes)); - PSObject.memberResolution.WriteLine("{0} total matches.", returnValue.Count); + PSObject.MemberResolution.WriteLine("{0} total matches.", returnValue.Count); return returnValue; } } @@ -5081,30 +4996,76 @@ internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberT /// This virtual works around the difficulty of implementing /// interfaces virtually. /// - /// the enumerator for this collection + /// The enumerator for this collection. public override IEnumerator GetEnumerator() { - return new Enumerator(this); + return new Enumerator(this); + } + + internal override T FirstOrDefault(MemberNamePredicate predicate) + { + object delegateOwner; + if (_mshOwner != null) + { + delegateOwner = _mshOwner; + foreach (PSMemberInfo member in _mshOwner.InstanceMembers) + { + if (member is T memberAsT && predicate(memberAsT.Name)) + { + return memberAsT; + } + } + } + else + { + delegateOwner = _memberSetOwner.instance; + foreach (PSMemberInfo member in _memberSetOwner.InternalMembers) + { + if (member is T memberAsT && predicate(memberAsT.Name)) + { + memberAsT.ReplicateInstance(delegateOwner); + return memberAsT; + } + } + } + + if (delegateOwner == null) + { + return null; + } + + var ownerAsPSObj = PSObject.AsPSObject(delegateOwner); + for (int i = 0; i < Collections.Count; i++) + { + var collectionEntry = Collections[i]; + var member = collectionEntry.GetFirstOrDefault(ownerAsPSObj, predicate); + if (member != null) + { + return collectionEntry.CloneOrReplicateObject(ownerAsPSObj, member); + } + } + + return null; } #endregion overrides /// - /// Enumerable for this class + /// 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 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) + /// Members we are enumerating. + internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) { - using (PSObject.memberResolution.TraceScope("Enumeration Start")) + using (PSObject.MemberResolution.TraceScope("Enumeration Start")) { _currentIndex = -1; _current = null; @@ -5112,29 +5073,29 @@ internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) if (integratingCollection._mshOwner != null) { integratingCollection.GenerateAllReservedMembers(); - PSObject.memberResolution.WriteLine("Enumerating PSObject with type \"{0}\".", integratingCollection._mshOwner.ImmediateBaseObject.GetType().FullName); - PSObject.memberResolution.WriteLine("PSObject instance members: {0}", _allMembers.VisibleCount); + PSObject.MemberResolution.WriteLine("Enumerating PSObject with type \"{0}\".", integratingCollection._mshOwner.ImmediateBaseObject.GetType().FullName); + PSObject.MemberResolution.WriteLine("PSObject instance members: {0}", _allMembers.VisibleCount); } else { - PSObject.memberResolution.WriteLine("Enumerating PSMemberSet \"{0}\".", integratingCollection._memberSetOwner.Name); - PSObject.memberResolution.WriteLine("MemberSet instance members: {0}", _allMembers.VisibleCount); + PSObject.MemberResolution.WriteLine("Enumerating PSMemberSet \"{0}\".", integratingCollection._memberSetOwner.Name); + PSObject.MemberResolution.WriteLine("MemberSet instance members: {0}", _allMembers.VisibleCount); } } } /// - /// Moves to the next element in the enumeration + /// 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]; @@ -5142,6 +5103,7 @@ public bool MoveNext() { break; } + _currentIndex++; } @@ -5156,10 +5118,10 @@ public bool MoveNext() } /// - /// Current PSMemberInfo in the enumeration + /// Gets the current PSMemberInfo in the enumeration. /// - /// for invalid arguments - S IEnumerator.Current + /// For invalid arguments. + T IEnumerator.Current { get { @@ -5167,17 +5129,12 @@ S IEnumerator.Current { throw PSTraceSource.NewInvalidOperationException(); } + return _current; } } - object IEnumerator.Current - { - get - { - return ((IEnumerator)this).Current; - } - } + object IEnumerator.Current => ((IEnumerator)this).Current; void IEnumerator.Reset() { @@ -5185,11 +5142,12 @@ void IEnumerator.Reset() _current = null; } - /// - /// Not supported + /// Not supported. /// - public void Dispose() { } + public void Dispose() + { + } } } @@ -5197,4 +5155,3 @@ public void Dispose() { } } #pragma warning restore 56503 - diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index c4651c8a1ef..c5e8bbac443 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -1,32 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Data; +using System.Diagnostics.CodeAnalysis; 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.Runspaces; using System.Reflection; -using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Text; using System.Xml; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using System.Runtime.Serialization; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Management.Infrastructure; -#if !CORECLR +using Microsoft.Management.Infrastructure; +#if !UNIX using System.DirectoryServices; +using System.Management; #endif #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -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 @@ -53,19 +52,13 @@ public class PSObject : IFormattable, IComparable, ISerializable, IDynamicMetaOb internal TypeTable GetTypeTable() { - TypeTable typeTable; - if (_typeTable != null && _typeTable.TryGetTarget(out typeTable)) + if (_typeTable != null && _typeTable.TryGetTarget(out TypeTable typeTable)) { return typeTable; } ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); - if (context != null) - { - return context.TypeTable; - } - - return null; + return context?.TypeTable; } internal static T TypeTableGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo @@ -85,16 +78,17 @@ private static T TypeTableGetMemberDelegate(PSObject msjObj, TypeTable typeTa PSMemberInfo member = allMembers[name]; if (member == null) { - PSObject.memberResolution.WriteLine("\"{0}\" NOT present in type table.", name); + PSObject.MemberResolution.WriteLine("\"{0}\" NOT present in type table.", name); return null; } - T memberAsT = member as T; - if (memberAsT != null) + + if (member is T memberAsT) { - PSObject.memberResolution.WriteLine("\"{0}\" present in type table.", name); + PSObject.MemberResolution.WriteLine("\"{0}\" present in type table.", name); return memberAsT; } - PSObject.memberResolution.WriteLine("\"{0}\" from types table ignored because it has type {1} instead of {2}.", + + PSObject.MemberResolution.WriteLine("\"{0}\" from types table ignored because it has type {1} instead of {2}.", name, member.GetType(), typeof(T)); return null; } @@ -111,71 +105,110 @@ internal static PSMemberInfoInternalCollection TypeTableGetMembersDelegate { return new PSMemberInfoInternalCollection(); } + PSMemberInfoInternalCollection members = typeTableToUse.GetMembers(msjObj.InternalTypeNames); - PSObject.memberResolution.WriteLine("Type table members: {0}.", members.Count); + PSObject.MemberResolution.WriteLine("Type table members: {0}.", members.Count); return members; } + internal static T TypeTableGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo + { + TypeTable table = msjObj.GetTypeTable(); + return TypeTableGetFirstOrDefaultMemberDelegate(msjObj, table, predicate); + } + + internal static T TypeTableGetFirstOrDefaultMemberDelegate(PSObject msjObj, TypeTable typeTableToUse, MemberNamePredicate predicate) where T : PSMemberInfo + { + return typeTableToUse?.GetFirstMemberOrDefault(msjObj.InternalTypeNames, predicate); + } + private static T AdapterGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo { - if (msjObj.isDeserialized) + if (msjObj.IsDeserialized) { - if (msjObj.adaptedMembers == null) + if (msjObj.AdaptedMembers == null) { return null; } - T adaptedMember = msjObj.adaptedMembers[name] as T; - PSObject.memberResolution.WriteLine("Serialized adapted member: {0}.", adaptedMember == null ? "not found" : adaptedMember.Name); + + T adaptedMember = msjObj.AdaptedMembers[name] as T; + PSObject.MemberResolution.WriteLine("Serialized adapted member: {0}.", adaptedMember == null ? "not found" : adaptedMember.Name); return adaptedMember; } - T retValue = msjObj.InternalAdapter.BaseGetMember(msjObj._immediateBaseObject, name); - PSObject.memberResolution.WriteLine("Adapted member: {0}.", retValue == null ? "not found" : retValue.Name); + + T retValue = msjObj.InternalAdapter.BaseGetMember(msjObj.ImmediateBaseObject, name); + PSObject.MemberResolution.WriteLine("Adapted member: {0}.", retValue == null ? "not found" : retValue.Name); return retValue; } - internal static PSMemberInfoInternalCollection TransformMemberInfoCollection(PSMemberInfoCollection source) where T : PSMemberInfo where U : PSMemberInfo + private static T AdapterGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo { - if (typeof(T) == typeof(U)) + if (msjObj.IsDeserialized && typeof(T).IsAssignableFrom(typeof(PSPropertyInfo))) + { + if (msjObj.AdaptedMembers == null) + { + return null; + } + + foreach (var adaptedMember in msjObj.AdaptedMembers) + { + if (predicate(adaptedMember.Name)) + { + return adaptedMember as T; + } + } + } + + T retValue = msjObj.InternalAdapter.BaseGetFirstMemberOrDefault(msjObj._immediateBaseObject, predicate); + return retValue; + } + + internal static PSMemberInfoInternalCollection TransformMemberInfoCollection(PSMemberInfoCollection source) + where TSource : PSMemberInfo where TResult : PSMemberInfo + { + 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) { - U tAsU = member as U; - if (tAsU != null) + if (member is TResult result) { - returnValue.Add(tAsU); + returnValue.Add(result); } } + return returnValue; } private static PSMemberInfoInternalCollection AdapterGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo { - if (msjObj.isDeserialized) + if (msjObj.IsDeserialized) { - if (msjObj.adaptedMembers == null) + if (msjObj.AdaptedMembers == null) { return new PSMemberInfoInternalCollection(); } - PSObject.memberResolution.WriteLine("Serialized adapted members: {0}.", msjObj.adaptedMembers.Count); - return TransformMemberInfoCollection(msjObj.adaptedMembers); + + PSObject.MemberResolution.WriteLine("Serialized adapted members: {0}.", msjObj.AdaptedMembers.Count); + return TransformMemberInfoCollection(msjObj.AdaptedMembers); } + PSMemberInfoInternalCollection retValue = msjObj.InternalAdapter.BaseGetMembers(msjObj._immediateBaseObject); - PSObject.memberResolution.WriteLine("Adapted members: {0}.", retValue.VisibleCount); + PSObject.MemberResolution.WriteLine("Adapted members: {0}.", retValue.VisibleCount); return retValue; } private static PSMemberInfoInternalCollection DotNetGetMembersDelegate(PSObject msjObj) where T : PSMemberInfo { // Don't lookup dotnet members if the object doesn't insist. - if (null != msjObj.InternalAdapterSet.DotNetAdapter) + if (msjObj.InternalBaseDotNetAdapter != null) { - PSMemberInfoInternalCollection retValue = msjObj.InternalAdapterSet.DotNetAdapter.BaseGetMembers(msjObj._immediateBaseObject); - PSObject.memberResolution.WriteLine("DotNet members: {0}.", retValue.VisibleCount); + PSMemberInfoInternalCollection retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMembers(msjObj._immediateBaseObject); + PSObject.MemberResolution.WriteLine("DotNet members: {0}.", retValue.VisibleCount); return retValue; } @@ -185,19 +218,21 @@ private static PSMemberInfoInternalCollection DotNetGetMembersDelegate(PSO private static T DotNetGetMemberDelegate(PSObject msjObj, string name) where T : PSMemberInfo { // Don't lookup dotnet member if the object doesn't insist. - if (null != msjObj.InternalAdapterSet.DotNetAdapter) + if (msjObj.InternalBaseDotNetAdapter != null) { - T retValue = msjObj.InternalAdapterSet.DotNetAdapter.BaseGetMember(msjObj._immediateBaseObject, name); - PSObject.memberResolution.WriteLine("DotNet member: {0}.", retValue == null ? "not found" : retValue.Name); + T retValue = msjObj.InternalBaseDotNetAdapter.BaseGetMember(msjObj._immediateBaseObject, name); + PSObject.MemberResolution.WriteLine("DotNet member: {0}.", retValue == null ? "not found" : retValue.Name); return retValue; } return null; } - private static Collection> s_memberCollection = GetMemberCollection(PSMemberViewTypes.All); - private static Collection> s_methodCollection = GetMethodCollection(); - private static Collection> s_propertyCollection = GetPropertyCollection(PSMemberViewTypes.All); + private static T DotNetGetFirstMemberOrDefaultDelegate(PSObject msjObj, MemberNamePredicate predicate) where T : PSMemberInfo + { + // Don't lookup dotnet member if the object doesn't insist. + return msjObj.InternalBaseDotNetAdapter?.BaseGetFirstMemberOrDefault(msjObj._immediateBaseObject, predicate); + } /// /// A collection of delegates to get Extended/Adapted/Dotnet members based on the @@ -235,54 +270,70 @@ internal static Collection> GetMemberCollection( returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } else { returnValue.Add(new CollectionEntry( - delegate (PSObject msjObj) - { - return TypeTableGetMembersDelegate(msjObj, backupTypeTable); - }, - delegate (PSObject msjObj, string name) - { - return TypeTableGetMemberDelegate(msjObj, backupTypeTable, name); - }, + msjObj => TypeTableGetMembersDelegate(msjObj, backupTypeTable), + (msjObj, name) => TypeTableGetMemberDelegate(msjObj, backupTypeTable, name), + (msjObj, predicate) => TypeTableGetFirstOrDefaultMemberDelegate(msjObj, backupTypeTable, predicate), true, true, "type table members")); } } + if ((viewType & PSMemberViewTypes.Adapted) == PSMemberViewTypes.Adapted) { returnValue.Add(new CollectionEntry( PSObject.AdapterGetMembersDelegate, PSObject.AdapterGetMemberDelegate, - false, false, "adapted members")); + PSObject.AdapterGetFirstMemberOrDefaultDelegate, + shouldReplicateWhenReturning: false, + shouldCloneWhenReturning: false, + collectionNameForTracing: "adapted members")); } + if ((viewType & PSMemberViewTypes.Base) == PSMemberViewTypes.Base) { returnValue.Add(new CollectionEntry( PSObject.DotNetGetMembersDelegate, PSObject.DotNetGetMemberDelegate, - false, false, "clr members")); + PSObject.DotNetGetFirstMemberOrDefaultDelegate, + shouldReplicateWhenReturning: false, + shouldCloneWhenReturning: false, + collectionNameForTracing: "clr members")); } + return returnValue; } private static Collection> GetMethodCollection() { - Collection> returnValue = new Collection>(); - returnValue.Add(new CollectionEntry( - PSObject.TypeTableGetMembersDelegate, - PSObject.TypeTableGetMemberDelegate, - true, true, "type table members")); - returnValue.Add(new CollectionEntry( - PSObject.AdapterGetMembersDelegate, - PSObject.AdapterGetMemberDelegate, - false, false, "adapted members")); - returnValue.Add(new CollectionEntry( - PSObject.DotNetGetMembersDelegate, - PSObject.DotNetGetMemberDelegate, - false, false, "clr members")); + Collection> returnValue = new Collection> + { + new CollectionEntry( + PSObject.TypeTableGetMembersDelegate, + PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, + shouldReplicateWhenReturning: true, + shouldCloneWhenReturning: true, + collectionNameForTracing: "type table members"), + new CollectionEntry( + PSObject.AdapterGetMembersDelegate, + PSObject.AdapterGetMemberDelegate, + PSObject.AdapterGetFirstMemberOrDefaultDelegate, + shouldReplicateWhenReturning: false, + shouldCloneWhenReturning: false, + collectionNameForTracing: "adapted members"), + new CollectionEntry( + PSObject.DotNetGetMembersDelegate, + PSObject.DotNetGetMemberDelegate, + PSObject.DotNetGetFirstMemberOrDefaultDelegate, + shouldReplicateWhenReturning: false, + shouldCloneWhenReturning: false, + collectionNameForTracing: "clr members") + }; return returnValue; } @@ -323,36 +374,37 @@ internal static Collection> GetPropertyCollectio returnValue.Add(new CollectionEntry( PSObject.TypeTableGetMembersDelegate, PSObject.TypeTableGetMemberDelegate, + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } else { returnValue.Add(new CollectionEntry( - delegate (PSObject msjObj) - { - return TypeTableGetMembersDelegate(msjObj, backupTypeTable); - }, - delegate (PSObject msjObj, string name) - { - return TypeTableGetMemberDelegate(msjObj, backupTypeTable, name); - }, + msjObj => TypeTableGetMembersDelegate(msjObj, backupTypeTable), + (msjObj, name) => TypeTableGetMemberDelegate(msjObj, backupTypeTable, name), + PSObject.TypeTableGetFirstMemberOrDefaultDelegate, true, true, "type table members")); } } + if ((viewType & PSMemberViewTypes.Adapted) == PSMemberViewTypes.Adapted) { returnValue.Add(new CollectionEntry( PSObject.AdapterGetMembersDelegate, PSObject.AdapterGetMemberDelegate, + PSObject.AdapterGetFirstMemberOrDefaultDelegate, false, false, "adapted members")); } + if ((viewType & PSMemberViewTypes.Base) == PSMemberViewTypes.Base) { returnValue.Add(new CollectionEntry( PSObject.DotNetGetMembersDelegate, PSObject.DotNetGetMemberDelegate, + PSObject.DotNetGetFirstMemberOrDefaultDelegate, false, false, "clr members")); } + return returnValue; } @@ -361,90 +413,64 @@ private void CommonInitialization(object obj) Diagnostics.Assert(obj != null, "checked by callers"); if (obj is PSCustomObject) { - this.immediateBaseObjectIsEmpty = true; + this.ImmediateBaseObjectIsEmpty = true; } _immediateBaseObject = obj; var context = LocalPipeline.GetExecutionContextFromTLS(); - _typeTable = context != null ? context.TypeTableWeakReference : null; + _typeTable = context?.TypeTableWeakReference; } - internal static readonly DotNetAdapter dotNetInstanceAdapter = new DotNetAdapter(); - private static readonly DotNetAdapter s_baseAdapterForAdaptedObjects = new BaseDotNetAdapterForAdaptedObjects(); - internal static readonly DotNetAdapter dotNetStaticAdapter = new DotNetAdapter(true); - - private static readonly AdapterSet s_dotNetInstanceAdapterSet = new AdapterSet(dotNetInstanceAdapter, null); - private static readonly AdapterSet s_mshMemberSetAdapter = new AdapterSet(new PSMemberSetAdapter(), null); - private static readonly AdapterSet s_mshObjectAdapter = new AdapterSet(new PSObjectAdapter(), null); - private static PSObject.AdapterSet s_cimInstanceAdapter = - new PSObject.AdapterSet(new ThirdPartyAdapter(typeof(Microsoft.Management.Infrastructure.CimInstance), - new Microsoft.PowerShell.Cim.CimInstanceAdapter()), - PSObject.dotNetInstanceAdapter); - -#if !CORECLR // WMIv1/ADSI Adapters Not Supported in PowerShell Core - private static readonly AdapterSet managementObjectAdapter = new AdapterSet(new ManagementObjectAdapter(), dotNetInstanceAdapter); - private static readonly AdapterSet managementClassAdapter = new AdapterSet(new ManagementClassApdapter(), dotNetInstanceAdapter); - private static readonly AdapterSet directoryEntryAdapter = new AdapterSet(new DirectoryEntryAdapter(), dotNetInstanceAdapter); -#endif - private static readonly AdapterSet dataRowViewAdapter = new AdapterSet(new DataRowViewAdapter(), s_baseAdapterForAdaptedObjects); - private static readonly AdapterSet dataRowAdapter = new AdapterSet(new DataRowAdapter(), s_baseAdapterForAdaptedObjects); - private static readonly AdapterSet s_xmlNodeAdapter = new AdapterSet(new XmlNodeAdapter(), s_baseAdapterForAdaptedObjects); - #region Adapter Mappings private static readonly ConcurrentDictionary s_adapterMapping = new ConcurrentDictionary(); + private static readonly List> s_adapterSetMappers = new List> { MappedInternalAdapterSet }; - internal static void RegisterAdapterMapping(Func mapper) - { - lock (s_adapterSetMappers) - { - s_adapterSetMappers.Add(mapper); - } - } - private static AdapterSet MappedInternalAdapterSet(object obj) { if (obj is PSMemberSet) { return PSObject.s_mshMemberSetAdapter; } + if (obj is PSObject) { return PSObject.s_mshObjectAdapter; } + if (obj is CimInstance) { return PSObject.s_cimInstanceAdapter; } +#if !UNIX + if (obj is ManagementClass) { return PSObject.s_managementClassAdapter; } + + if (obj is ManagementBaseObject) { return PSObject.s_managementObjectAdapter; } -#if !CORECLR // WMIv1/ADSI Adapters Not Supported in PowerShell Core - if (obj is ManagementClass) { return PSObject.managementClassAdapter; } - if (obj is ManagementBaseObject) { return PSObject.managementObjectAdapter; } - if (obj is DirectoryEntry) { return PSObject.directoryEntryAdapter; } + if (obj is DirectoryEntry) { return PSObject.s_directoryEntryAdapter; } #endif - if (obj is DataRowView) { return PSObject.dataRowViewAdapter; } - if (obj is DataRow) { return PSObject.dataRowAdapter; } + if (obj is DataRowView) { return PSObject.s_dataRowViewAdapter; } + + if (obj is DataRow) { return PSObject.s_dataRowAdapter; } + if (obj is XmlNode) { return PSObject.s_xmlNodeAdapter; } + return null; } /// /// Returns the adapter corresponding to obj.GetType() /// - /// the adapter set corresponding to obj.GetType() + /// The adapter set corresponding to obj.GetType(). internal static AdapterSet GetMappedAdapter(object obj, TypeTable typeTable) { Type objectType = obj.GetType(); - if (typeTable != null) - { - PSObject.AdapterSet adapter = typeTable.GetTypeAdapter(objectType); + PSObject.AdapterSet adapter = typeTable?.GetTypeAdapter(objectType); - if (adapter != null) - { - // We don't cache results found via the type table because type tables may differ b/w runspaces, - // our cache is app domain wide, and the key is simply the type. - return adapter; - } + if (adapter != null) + { + // We don't cache results found via the type table because type tables may differ b/w runspaces, + // our cache is app domain wide, and the key is simply the type. + return adapter; } - AdapterSet result; - if (s_adapterMapping.TryGetValue(objectType, out result)) + if (s_adapterMapping.TryGetValue(objectType, out AdapterSet result)) { return result; } @@ -463,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. @@ -484,7 +511,7 @@ internal static AdapterSet GetMappedAdapter(object obj, TypeTable typeTable) ComTypeInfo info = ComTypeInfo.GetDispatchTypeInfo(obj); return info != null - ? new AdapterSet(new ComAdapter(info), dotNetInstanceAdapter) + ? new AdapterSet(new ComAdapter(info), DotNetInstanceAdapter) : PSObject.s_dotNetInstanceAdapterSet; } else @@ -499,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); @@ -517,45 +547,54 @@ internal static AdapterSet CreateThirdPartyAdapterSet(Type adaptedType, PSProper #endregion private to the constructors /// - /// Initializes a new instance of PSObject with an PSCustomObject BaseObject + /// Initializes a new instance of PSObject with an PSCustomObject BaseObject. /// public PSObject() { CommonInitialization(PSCustomObject.SelfInstance); } + /// + /// Initializes a new instance of PSObject with an PSCustomObject BaseObject + /// with an initial capacity for members. + /// + /// The initial capacity for the instance member collection. + public PSObject(int instanceMemberCapacity) : this() + { + _instanceMembers = new PSMemberInfoInternalCollection(instanceMemberCapacity); + } + /// /// Initializes a new instance of PSObject wrapping obj (accessible through BaseObject). /// - /// object we are wrapping - /// if is null + /// Object we are wrapping. + /// If is null. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "This is shipped as part of V1. Retaining this for backward compatibility.")] public PSObject(object obj) { if (obj == null) { - throw PSTraceSource.NewArgumentNullException("obj"); + throw PSTraceSource.NewArgumentNullException(nameof(obj)); } + CommonInitialization(obj); } /// - /// Creates a PSObject from an ISerializable context + /// Creates a PSObject from an ISerializable context. /// - /// Serialization information for this instance - /// The streaming context for this instance + /// Serialization information for this instance. + /// The streaming context for this instance. protected PSObject(SerializationInfo info, StreamingContext context) { if (info == null) { - throw PSTraceSource.NewArgumentNullException("info"); + 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("info"); + throw PSTraceSource.NewArgumentNullException(nameof(info)); } PSObject result = PSObject.AsPSObject(PSSerializer.Deserialize(serializedData)); @@ -574,37 +613,88 @@ internal static PSObject ConstructPSObjectFromSerializationInfo(SerializationInf #region fields #region instance fields - private object _lockObject = new Object(); - /// - /// If this is non-null return this string as the ToString() for this wrapped object. - /// - internal string TokenText; + private readonly object _lockObject = new object(); + + private ConsolidatedString _typeNames; /// /// This is the main field in the class representing - /// the System.Object we are encapsulating + /// the System.Object we are encapsulating. /// private object _immediateBaseObject; - private WeakReference _typeTable; + private AdapterSet _adapterSet; + private PSMemberInfoInternalCollection _instanceMembers; + private PSMemberInfoIntegratingCollection _members; + private PSMemberInfoIntegratingCollection _properties; + private PSMemberInfoIntegratingCollection _methods; - /// - /// This is the adapter that will depend on the type of baseObject. - /// - internal Adapter InternalAdapter + private PSObjectFlags _flags; + + #endregion instance fields + + private static readonly PSTraceSource s_memberResolution = PSTraceSource.GetTracer("MemberResolution", "Traces the resolution from member name to the member. A member can be a property, method, etc.", false); + + private static readonly ConditionalWeakTable s_typeNamesResurrectionTable = new ConditionalWeakTable(); + + private static readonly Collection> s_memberCollection = GetMemberCollection(PSMemberViewTypes.All); + private static readonly Collection> s_methodCollection = GetMethodCollection(); + private static readonly Collection> s_propertyCollection = GetPropertyCollection(PSMemberViewTypes.All); + + private static readonly DotNetAdapter s_dotNetInstanceAdapter = new DotNetAdapter(); + private static readonly DotNetAdapter s_baseAdapterForAdaptedObjects = new BaseDotNetAdapterForAdaptedObjects(); + private static readonly DotNetAdapter s_dotNetStaticAdapter = new DotNetAdapter(true); + + private static readonly AdapterSet s_dotNetInstanceAdapterSet = new AdapterSet(DotNetInstanceAdapter, null); + private static readonly AdapterSet s_mshMemberSetAdapter = new AdapterSet(new PSMemberSetAdapter(), null); + private static readonly AdapterSet s_mshObjectAdapter = new AdapterSet(new PSObjectAdapter(), null); + + private static readonly PSObject.AdapterSet s_cimInstanceAdapter = + 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); + private static readonly AdapterSet s_directoryEntryAdapter = new AdapterSet(new DirectoryEntryAdapter(), DotNetInstanceAdapter); +#endif + private static readonly AdapterSet s_dataRowViewAdapter = new AdapterSet(new DataRowViewAdapter(), s_baseAdapterForAdaptedObjects); + private static readonly AdapterSet s_dataRowAdapter = new AdapterSet(new DataRowAdapter(), s_baseAdapterForAdaptedObjects); + private static readonly AdapterSet s_xmlNodeAdapter = new AdapterSet(new XmlNodeAdapter(), s_baseAdapterForAdaptedObjects); + + #endregion fields + + #region properties + + internal PSMemberInfoInternalCollection InstanceMembers { get { - return InternalAdapterSet.OriginalAdapter; - } - set - { - InternalAdapterSet.OriginalAdapter = value; + if (_instanceMembers == null) + { + lock (_lockObject) + { + _instanceMembers ??= + s_instanceMembersResurrectionTable.GetValue( + GetKeyForResurrectionTables(this), + _ => new PSMemberInfoInternalCollection()); + } + } + + return _instanceMembers; } + + set => _instanceMembers = value; } + /// + /// This is the adapter that will depend on the type of baseObject. + /// + internal Adapter InternalAdapter => InternalAdapterSet.OriginalAdapter; + /// /// This is the adapter that is used to resolve the base dotnet members for an /// adapted object. If an object is not adapted, this will be null. @@ -616,13 +706,7 @@ internal Adapter InternalAdapter /// If an object is adapted, this adapter will be used to resolve the dotnet /// members. /// - internal Adapter InternalBaseDotNetAdapter - { - get - { - return InternalAdapterSet.DotNetAdapter; - } - } + internal Adapter InternalBaseDotNetAdapter => InternalAdapterSet.DotNetAdapter; /// /// This is the adapter set that will contain the adapter of the baseObject @@ -633,115 +717,20 @@ private AdapterSet InternalAdapterSet { get { - if (null == _adapterSet) + if (_adapterSet == null) { lock (_lockObject) { - if (null == _adapterSet) - { - _adapterSet = GetMappedAdapter(_immediateBaseObject, GetTypeTable()); - } + _adapterSet ??= GetMappedAdapter(_immediateBaseObject, GetTypeTable()); } } return _adapterSet; } } - private AdapterSet _adapterSet; - - internal bool hasGeneratedReservedMembers; - - internal PSMemberInfoInternalCollection InstanceMembers - { - get - { - if (_instanceMembers == null) - { - lock (_lockObject) - { - if (_instanceMembers == null) - { - _instanceMembers = - s_instanceMembersResurrectionTable.GetValue( - GetKeyForResurrectionTables(this), - _ => new PSMemberInfoInternalCollection()); - } - } - } - - return _instanceMembers; - } - set - { - _instanceMembers = value; - } - } - private PSMemberInfoInternalCollection _instanceMembers; - - internal static bool HasInstanceMembers(object obj, out PSMemberInfoInternalCollection instanceMembers) - { - var psobj = obj as PSObject; - if (psobj != null) - { - lock (psobj) - { - if (psobj._instanceMembers == null) - { - s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(psobj), - out psobj._instanceMembers); - } - } - instanceMembers = psobj._instanceMembers; - } - else if (obj != null) - { - s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out instanceMembers); - } - else - { - instanceMembers = null; - } - - return instanceMembers != null && instanceMembers.Count > 0; - } - - private static readonly ConditionalWeakTable> s_instanceMembersResurrectionTable = - new ConditionalWeakTable>(); - - /// - /// Indicate whether we store the instance members and type names locally - /// for this PSObject instance. - /// - private bool _storeTypeNameAndInstanceMembersLocally = false; - - /// - /// Members from the adapter of the object before it was serialized - /// Null for live objects but not null for deserialized objects - /// - internal PSMemberInfoInternalCollection adaptedMembers; - - /// - /// Members from the adapter of the object before it was serialized - /// Null for live objects but not null for deserialized objects - /// - internal PSMemberInfoInternalCollection clrMembers; - - /// - /// Set to true when the BaseObject is PSCustomObject - /// - internal bool immediateBaseObjectIsEmpty; - - internal static PSTraceSource memberResolution = PSTraceSource.GetTracer("MemberResolution", "Traces the resolution from member name to the member. A member can be a property, method, etc.", false); - - #endregion instance fields - - - #endregion fields - - #region properties /// - /// Gets the member collection + /// Gets the member collection. /// public PSMemberInfoCollection Members { @@ -751,17 +740,13 @@ public PSMemberInfoCollection Members { lock (_lockObject) { - if (_members == null) - { - _members = new PSMemberInfoIntegratingCollection(this, s_memberCollection); - } + _members ??= new PSMemberInfoIntegratingCollection(this, s_memberCollection); } } return _members; } } - private PSMemberInfoIntegratingCollection _members; /// /// Gets the Property collection, or the members that are actually properties. @@ -774,17 +759,13 @@ public PSMemberInfoCollection Properties { lock (_lockObject) { - if (_properties == null) - { - _properties = new PSMemberInfoIntegratingCollection(this, s_propertyCollection); - } + _properties ??= new PSMemberInfoIntegratingCollection(this, s_propertyCollection); } } return _properties; } } - private PSMemberInfoIntegratingCollection _properties; /// /// Gets the Method collection, or the members that are actually methods. @@ -797,31 +778,20 @@ public PSMemberInfoCollection Methods { lock (_lockObject) { - if (_methods == null) - { - _methods = new PSMemberInfoIntegratingCollection(this, s_methodCollection); - } + _methods ??= new PSMemberInfoIntegratingCollection(this, s_methodCollection); } } return _methods; } } - private PSMemberInfoIntegratingCollection _methods; - /// /// Gets the object we are directly wrapping. /// /// If the ImmediateBaseObject is another PSObject, /// that PSObject will be returned. - public object ImmediateBaseObject - { - get - { - return _immediateBaseObject; - } - } + public object ImmediateBaseObject => _immediateBaseObject; /// /// Gets the object we are wrapping. @@ -832,7 +802,7 @@ public object BaseObject { get { - object returnValue = null; + object returnValue; PSObject mshObj = this; do { @@ -866,6 +836,7 @@ public Collection TypeNames object baseObj = BaseObject; // In most cases, the TypeNames will be modified after it's returned if (baseObj != null) { PSVariableAssignmentBinder.NoteTypeHasInstanceMemberOrTypeName(baseObj.GetType()); } + return _typeNames; } } @@ -901,25 +872,22 @@ internal ConsolidatedString InternalTypeNames return _typeNames; } - set - { - _typeNames = value; - } + + set => _typeNames = value; } internal static ConsolidatedString GetTypeNames(object obj) { - var psobj = obj as PSObject; - if (psobj != null) + if (obj is PSObject psobj) { return psobj.InternalTypeNames; } - ConsolidatedString result; - if (HasInstanceTypeName(obj, out result)) + if (HasInstanceTypeName(obj, out ConsolidatedString result)) { return result; } + return PSObject.GetMappedAdapter(obj, null).OriginalAdapter.BaseGetTypeNameHierarchy(obj); } @@ -928,15 +896,39 @@ internal static bool HasInstanceTypeName(object obj, out ConsolidatedString resu return s_typeNamesResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out result); } - private ConsolidatedString _typeNames; - private static readonly ConditionalWeakTable s_typeNamesResurrectionTable = new ConditionalWeakTable(); - #endregion properties #region static methods + internal static bool HasInstanceMembers(object obj, out PSMemberInfoInternalCollection instanceMembers) + { + if (obj is PSObject psobj) + { + lock (psobj) + { + if (psobj._instanceMembers == null) + { + s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(psobj), out psobj._instanceMembers); + } + } + + instanceMembers = psobj._instanceMembers; + } + else if (obj != null) + { + s_instanceMembersResurrectionTable.TryGetValue(GetKeyForResurrectionTables(obj), out instanceMembers); + } + else + { + instanceMembers = null; + } + + return instanceMembers != null && instanceMembers.Count > 0; + } + + private static readonly ConditionalWeakTable> s_instanceMembersResurrectionTable = + new ConditionalWeakTable>(); /// - /// /// /// /// @@ -944,8 +936,8 @@ public static implicit operator PSObject(int valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// - /// /// /// /// @@ -953,8 +945,8 @@ public static implicit operator PSObject(string valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// - /// /// /// /// @@ -962,8 +954,8 @@ public static implicit operator PSObject(Hashtable valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// - /// /// /// /// @@ -971,8 +963,8 @@ public static implicit operator PSObject(double valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// - /// /// /// /// @@ -988,23 +980,24 @@ 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; } + if (mshObj == AutomationNull.Value) return null; - if (mshObj.immediateBaseObjectIsEmpty) + if (mshObj.ImmediateBaseObjectIsEmpty) { return obj; } - object returnValue = null; + + object returnValue; do { returnValue = mshObj._immediateBaseObject; mshObj = returnValue as PSObject; - } while ((mshObj != null) && (!mshObj.immediateBaseObjectIsEmpty)); + } while ((mshObj != null) && (!mshObj.ImmediateBaseObjectIsEmpty)); return returnValue; } @@ -1016,19 +1009,20 @@ internal static PSMemberInfo GetStaticCLRMember(object obj, string methodName) { return null; } + var objType = obj as Type ?? obj.GetType(); - return dotNetStaticAdapter.BaseGetMember(objType, methodName); + return DotNetStaticAdapter.BaseGetMember(objType, methodName); } /// /// If obj is an PSObject it will be returned as is, otherwise /// a new PSObject will be created based on obj. /// - /// object to be wrapped + /// Object to be wrapped. /// /// obj or a new PSObject whose BaseObject is obj /// - /// if is null + /// If is null. [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "This is shipped as part of V1. Retaining this for backward compatibility.")] public static PSObject AsPSObject(object obj) { @@ -1049,17 +1043,15 @@ internal static PSObject AsPSObject(object obj, bool storeTypeNameAndInstanceMem { if (obj == null) { - throw PSTraceSource.NewArgumentNullException("obj"); + throw PSTraceSource.NewArgumentNullException(nameof(obj)); } - PSObject so = obj as PSObject; - - if (so != null) + if (obj is PSObject so) { return so; } - return new PSObject(obj) { _storeTypeNameAndInstanceMembersLocally = storeTypeNameAndInstanceMembersLocally }; + return new PSObject(obj) { StoreTypeNameAndInstanceMembersLocally = storeTypeNameAndInstanceMembersLocally }; } /// @@ -1072,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; } @@ -1086,7 +1077,7 @@ internal static object GetKeyForResurrectionTables(object obj) if (psObjectAboveBase.ImmediateBaseObject is PSCustomObject || psObjectAboveBase.ImmediateBaseObject is string - || pso._storeTypeNameAndInstanceMembersLocally) + || pso.StoreTypeNameAndInstanceMembersLocally) { return psObjectAboveBase; } @@ -1105,14 +1096,13 @@ private static string GetSeparator(ExecutionContext context, string separator) { return separator; } - if (context != null) + + object obj = context?.GetVariableValue(SpecialVariables.OFSVarPath); + if (obj != null) { - object obj = context.GetVariableValue(SpecialVariables.OFSVarPath); - if (obj != null) - { - return obj.ToString(); - } + return obj.ToString(); } + return " "; } @@ -1126,10 +1116,12 @@ internal static string ToStringEnumerator(ExecutionContext context, IEnumerator returnValue.Append(PSObject.ToString(context, obj, separator, format, formatProvider, false, false)); returnValue.Append(separatorToUse); } + if (returnValue.Length == 0) { - return String.Empty; + return string.Empty; } + int separatorLength = separatorToUse.Length; returnValue.Remove(returnValue.Length - separatorLength, separatorLength); return returnValue.ToString(); @@ -1146,12 +1138,15 @@ internal static string ToStringEnumerable(ExecutionContext context, IEnumerable PSObject mshObj = PSObject.AsPSObject(obj); returnValue.Append(PSObject.ToString(context, mshObj, separator, format, formatProvider, false, false)); } + returnValue.Append(separatorToUse); } + if (returnValue.Length == 0) { - return String.Empty; + return string.Empty; } + int separatorLength = separatorToUse.Length; returnValue.Remove(returnValue.Length - separatorLength, separatorLength); return returnValue.ToString(); @@ -1167,35 +1162,30 @@ private static string ToStringEmptyBaseObject(ExecutionContext context, PSObject { returnValue.Append("; "); } + isFirst = false; returnValue.Append(property.Name); - returnValue.Append("="); + returnValue.Append('='); // Don't evaluate script properties during a ToString() operation. - Object propertyValue = null; - if (property is PSScriptProperty) - { - propertyValue = property.GetType().FullName; - } - else - { - propertyValue = property.Value; - } + var propertyValue = property is PSScriptProperty ? property.GetType().FullName : property.Value; returnValue.Append(PSObject.ToString(context, propertyValue, separator, format, formatProvider, false, false)); } + if (isFirst) { return string.Empty; } - returnValue.Append("}"); + + returnValue.Append('}'); return returnValue.ToString(); } /// /// Returns the string representation of obj. /// - /// ExecutionContext used to fetch the separator. + /// ExecutionContext used to fetch the separator. /// /// object we are trying to call ToString on. If this is not an PSObject we try /// enumerating and if that fails we call obj.ToString. @@ -1203,15 +1193,36 @@ private static string ToStringEmptyBaseObject(ExecutionContext context, PSObject /// If it is not present, and the BaseObject is null we try listing the properties. /// If the BaseObject is not null we try enumerating. If that fails we try the BaseObject's ToString. /// - /// A string representation of the object + /// A string representation of the object. /// /// When there is a brokered ToString but it failed, or when the ToString on obj throws an exception. /// internal static string ToStringParser(ExecutionContext context, object obj) + { + return ToStringParser(context, obj, CultureInfo.InvariantCulture); + } + + /// + /// Returns the string representation of obj. + /// + /// ExecutionContext used to fetch the separator. + /// + /// object we are trying to call ToString on. If this is not an PSObject we try + /// enumerating and if that fails we call obj.ToString. + /// If this is an PSObject, we look for a brokered ToString. + /// If it is not present, and the BaseObject is null we try listing the properties. + /// If the BaseObject is not null we try enumerating. If that fails we try the BaseObject's ToString. + /// + /// The formatProvider to be passed to ToString. + /// A string representation of the object. + /// + /// When there is a brokered ToString but it failed, or when the ToString on obj throws an exception. + /// + internal static string ToStringParser(ExecutionContext context, object obj, IFormatProvider formatProvider) { try { - return ToString(context, obj, null, null, CultureInfo.InvariantCulture, true, true); + return ToString(context, obj, null, null, formatProvider, true, true); } catch (ExtendedTypeSystemException etse) { @@ -1221,7 +1232,7 @@ internal static string ToStringParser(ExecutionContext context, object obj) } /// - /// Called from an PSObject instance ToString to provide a string representation for an object + /// Called from an PSObject instance ToString to provide a string representation for an object. /// /// /// ExecutionContext used to fetch the separator. @@ -1235,37 +1246,27 @@ internal static string ToStringParser(ExecutionContext context, object obj) /// If it is not present, and the BaseObject is null we try listing the properties. /// If the BaseObject is not null we try enumerating. If that fails we try the BaseObject's ToString. /// - /// The separator between elements, if this is an enumeration - /// the format to be passed to ToString - /// the formatProvider to be passed to ToString + /// The separator between elements, if this is an enumeration. + /// The format to be passed to ToString. + /// The formatProvider to be passed to ToString. /// true if we should enumerate values or properties which would cause recursive /// calls to this method. Such recursive calls will have recurse set to false, limiting the depth. /// If recurse is false, this parameter is not considered. If it is true /// this parameter will determine how enumerators are going to be treated. /// - /// A string representation of the object + /// A string representation of the object. /// /// When there is a brokered ToString but it failed, or when the ToString on obj throws an exception. /// internal static string ToString(ExecutionContext context, object obj, string separator, string format, IFormatProvider formatProvider, bool recurse, bool unravelEnumeratorOnRecurse) { - PSObject mshObj = obj as PSObject; - - #region plain object - if (mshObj == null) + bool TryFastTrackPrimitiveTypes(object value, out string str) { - if (obj == null) - { - return String.Empty; - } - - // Fast-track the primitive types... - Type objType = obj.GetType(); - TypeCode code = objType.GetTypeCode(); - switch (code) + switch (Convert.GetTypeCode(value)) { case TypeCode.String: - return (string)obj; + str = (string)value; + break; case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Int16: @@ -1274,20 +1275,40 @@ internal static string ToString(ExecutionContext context, object obj, string sep case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: - return obj.ToString(); case TypeCode.DateTime: - DateTime dt = (DateTime)obj; - return dt.ToString(formatProvider); case TypeCode.Decimal: - Decimal dec = (Decimal)obj; - return dec.ToString(formatProvider); + var formattable = (IFormattable)value; + str = formattable.ToString(format, formatProvider); + break; case TypeCode.Double: - double dbl = (double)obj; - return dbl.ToString(formatProvider); - + var dbl = (double)value; + str = dbl.ToString(format ?? LanguagePrimitives.DoublePrecision, formatProvider); + break; case TypeCode.Single: - float sgl = (float)obj; - return sgl.ToString(formatProvider); + var sgl = (float)value; + str = sgl.ToString(format ?? LanguagePrimitives.SinglePrecision, formatProvider); + break; + default: + str = null; + return false; + } + + return true; + } + + PSObject mshObj = obj as PSObject; + + #region plain object + if (mshObj == null) + { + if (obj == null) + { + return string.Empty; + } + + if (TryFastTrackPrimitiveTypes(obj, out string objString)) + { + return objString; } #region recurse @@ -1305,6 +1326,7 @@ internal static string ToString(ExecutionContext context, object obj, string sep // We do want to ignore exceptions here to try the regular ToString below. } } + if (unravelEnumeratorOnRecurse) { IEnumerator enumerator = LanguagePrimitives.GetEnumerator(obj); @@ -1335,8 +1357,10 @@ internal static string ToString(ExecutionContext context, object obj, string sep { return Microsoft.PowerShell.ToStringCodeMethods.Type(type); } + return obj.ToString(); } + return objFormattable.ToString(format, formatProvider); } catch (Exception e) @@ -1354,8 +1378,7 @@ internal static string ToString(ExecutionContext context, object obj, string sep // A brokered ToString has precedence over any other attempts. // If it fails we let the exception go because the caller must be notified. PSMethodInfo method = null; - PSMemberInfoInternalCollection instanceMembers; - if (PSObject.HasInstanceMembers(mshObj, out instanceMembers)) + if (PSObject.HasInstanceMembers(mshObj, out PSMemberInfoInternalCollection instanceMembers)) { method = instanceMembers["ToString"] as PSMethodInfo; } @@ -1379,11 +1402,11 @@ internal static string ToString(ExecutionContext context, object obj, string sep if (method != null) { - object retObj; try { // Even if a format specifier has been provided, if there is only one overload // then it can't take a format specified... + object retObj; if (formatProvider != null && method.OverloadDefinitions.Count > 1) { retObj = method.Invoke(format, formatProvider); @@ -1405,7 +1428,7 @@ internal static string ToString(ExecutionContext context, object obj, string sep // Since we don't have a brokered ToString, we check for the need to enumerate the object or its properties if (recurse) { - if (mshObj.immediateBaseObjectIsEmpty) + if (mshObj.ImmediateBaseObjectIsEmpty) { try { @@ -1429,6 +1452,7 @@ internal static string ToString(ExecutionContext context, object obj, string sep // We do want to ignore exceptions here to try the regular ToString below. } } + if (unravelEnumeratorOnRecurse) { IEnumerator enumerator = LanguagePrimitives.GetEnumerator(mshObj); @@ -1459,21 +1483,18 @@ internal static string ToString(ExecutionContext context, object obj, string sep // Since we don't have a brokered ToString and the enumerations were not necessary or failed // we try the BaseObject's ToString object baseObject = mshObj._immediateBaseObject; + + if (TryFastTrackPrimitiveTypes(baseObject, out string baseObjString)) + { + return baseObjString; + } + IFormattable msjObjFormattable = baseObject as IFormattable; try { - string result; + var result = msjObjFormattable == null ? baseObject.ToString() : msjObjFormattable.ToString(format, formatProvider); - if (msjObjFormattable == null) - { - result = baseObject.ToString(); - } - else - { - result = msjObjFormattable.ToString(format, formatProvider); - } - - return result ?? String.Empty; + return result ?? string.Empty; } catch (Exception e) { @@ -1490,16 +1511,17 @@ internal static string ToString(ExecutionContext context, object obj, string sep /// CodeMethod or ScriptMethod will be used, if available. Enumerations items are /// concatenated using $ofs. /// - /// the string representation for baseObject - /// if an exception was thrown by the BaseObject's ToString + /// The string representation for baseObject. + /// If an exception was thrown by the BaseObject's ToString. public override string ToString() { - //If ToString value from deserialization is available, - //simply return it. - if (_toStringFromDeserialization != null) + // If ToString value from deserialization is available, + // simply return it. + if (ToStringFromDeserialization != null) { - return _toStringFromDeserialization; + return ToStringFromDeserialization; } + return PSObject.ToString(null, this, null, null, null, true, false); } @@ -1508,18 +1530,19 @@ public override string ToString() /// CodeMethod or ScriptMethod will be used, if available. Enumerations items are /// concatenated using $ofs. /// - /// repassed to baseObject's IFormattable if present - /// repassed to baseObject's IFormattable if present - /// the string representation for baseObject - /// if an exception was thrown by the BaseObject's ToString + /// Repassed to baseObject's IFormattable if present. + /// Repassed to baseObject's IFormattable if present. + /// The string representation for baseObject. + /// If an exception was thrown by the BaseObject's ToString. public string ToString(string format, IFormatProvider formatProvider) { - //If ToString value from deserialization is available, - //simply return it. - if (_toStringFromDeserialization != null) + // If ToString value from deserialization is available, + // simply return it. + if (ToStringFromDeserialization != null) { - return _toStringFromDeserialization; + return ToStringFromDeserialization; } + return PSObject.ToString(null, this, null, format, formatProvider, true, false); } @@ -1534,6 +1557,7 @@ private string PrivateToString() { result = this.BaseObject.GetType().FullName; } + return result; } @@ -1545,7 +1569,7 @@ private string PrivateToString() /// it is a value type, and use BaseObject.Clone() for the new PSObject, /// if the BaseObject is ICloneable. /// - /// a copy of this object + /// A copy of this object. public virtual PSObject Copy() { PSObject returnValue = (PSObject)this.MemberwiseClone(); @@ -1553,12 +1577,12 @@ public virtual PSObject Copy() if (this.BaseObject is PSCustomObject) { returnValue._immediateBaseObject = PSCustomObject.SelfInstance; - returnValue.immediateBaseObjectIsEmpty = true; + returnValue.ImmediateBaseObjectIsEmpty = true; } else { returnValue._immediateBaseObject = _immediateBaseObject; - returnValue.immediateBaseObjectIsEmpty = false; + returnValue.ImmediateBaseObjectIsEmpty = false; } // Instance members will be recovered as necessary through the resurrection table. @@ -1573,8 +1597,7 @@ public virtual PSObject Copy() returnValue._adapterSet = GetMappedAdapter(returnValue._immediateBaseObject, returnValue.GetTypeTable()); - ICloneable cloneableBase = returnValue._immediateBaseObject as ICloneable; - if (cloneableBase != null) + if (returnValue._immediateBaseObject is ICloneable cloneableBase) { returnValue._immediateBaseObject = cloneableBase.Clone(); } @@ -1584,12 +1607,11 @@ public virtual PSObject Copy() returnValue._immediateBaseObject = CopyValueType(returnValue._immediateBaseObject); } - // needToReAddInstanceMembersAndTypeNames = returnValue will have a different key (different from "this") returned from GetKeyForResurrectionTables 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) @@ -1607,7 +1629,8 @@ public virtual PSObject Copy() } } - returnValue.hasGeneratedReservedMembers = false; + returnValue.WriteStream = WriteStream; + returnValue.HasGeneratedReservedMembers = false; return returnValue; } @@ -1642,10 +1665,11 @@ public int CompareTo(object obj) // This ReferenceEquals is not just an optimization. // It is necessary so that mshObject.Equals(mshObject) returns 0. // Please see the comments inside the Equals implementation. - if (Object.ReferenceEquals(this, obj)) + if (object.ReferenceEquals(this, obj)) { return 0; } + try { // PSObject.Base instead of BaseObject could cause an infinite @@ -1664,7 +1688,7 @@ public int CompareTo(object obj) /// Determines whether the specified Object is equal to the current Object. /// /// The Object to compare with the current Object. - /// true if the specified Object is equal to the current Object; otherwise, false. + /// True if the specified Object is equal to the current Object; otherwise, false. public override bool Equals(object obj) { // There is a slight difference between BaseObject and PSObject.Base. @@ -1672,7 +1696,7 @@ public override bool Equals(object obj) // BaseObject returns the MshCustomBaseObject. // Because we have to call BaseObject here, and LP.Compare uses PSObject.Base // we need the reference equals below so that mshObject.Equals(mshObject) returns true. - if (Object.ReferenceEquals(this, obj)) + if (object.ReferenceEquals(this, obj)) { return true; } @@ -1680,7 +1704,7 @@ public override bool Equals(object obj) // The above check validates if we are comparing with the same object references // This check "shortcuts" the comparison if the first object is a CustomObject // since 2 custom objects are not equal. - if (Object.ReferenceEquals(this.BaseObject, PSCustomObject.SelfInstance)) + if (object.ReferenceEquals(this.BaseObject, PSCustomObject.SelfInstance)) { return false; } @@ -1693,7 +1717,7 @@ public override bool Equals(object obj) /// /// Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table. /// - /// A hash code for the current Object + /// A hash code for the current Object. public override int GetHashCode() { return this.BaseObject.GetHashCode(); @@ -1703,8 +1727,7 @@ public override int GetHashCode() internal void AddOrSetProperty(string memberName, object value) { - PSMemberInfo memberInfo; - if (PSGetMemberBinder.TryGetInstanceMember(this, memberName, out memberInfo) && memberInfo is PSPropertyInfo) + if (PSGetMemberBinder.TryGetInstanceMember(this, memberName, out PSMemberInfo memberInfo) && memberInfo is PSPropertyInfo) { memberInfo.Value = value; } @@ -1716,8 +1739,7 @@ internal void AddOrSetProperty(string memberName, object value) internal void AddOrSetProperty(PSNoteProperty property) { - PSMemberInfo memberInfo; - if (PSGetMemberBinder.TryGetInstanceMember(this, property.Name, out memberInfo) && memberInfo is PSPropertyInfo) + if (PSGetMemberBinder.TryGetInstanceMember(this, property.Name, out PSMemberInfo memberInfo) && memberInfo is PSPropertyInfo) { memberInfo.Value = property.Value; } @@ -1732,7 +1754,7 @@ internal void AddOrSetProperty(PSNoteProperty property) #region public const strings /// - /// The name of the member set for adapted members + /// The name of the member set for adapted members. /// /// /// This needs to be Lower cased as it saves some comparison time elsewhere. @@ -1740,7 +1762,7 @@ internal void AddOrSetProperty(PSNoteProperty property) public const string AdaptedMemberSetName = "psadapted"; /// - /// The name of the member set for extended members + /// The name of the member set for extended members. /// /// /// This needs to be Lower cased as it saves some comparison time elsewhere. @@ -1748,16 +1770,15 @@ internal void AddOrSetProperty(PSNoteProperty property) public const string ExtendedMemberSetName = "psextended"; /// - /// The name of the member set for the BaseObject's members + /// The name of the member set for the BaseObject's members. /// /// /// This needs to be Lower cased as it saves some comparison time elsewhere. /// public const string BaseObjectMemberSetName = "psbase"; - /// - /// The PSObject's properties + /// The PSObject's properties. /// /// /// This needs to be Lower cased as it saves some comparison time elsewhere. @@ -1765,7 +1786,7 @@ internal void AddOrSetProperty(PSNoteProperty property) internal const string PSObjectMemberSetName = "psobject"; /// - /// a shortcut to .PSObject.TypeNames + /// A shortcut to .PSObject.TypeNames. /// /// /// This needs to be Lower cased as it saves some comparison time elsewhere. @@ -1777,20 +1798,20 @@ internal void AddOrSetProperty(PSNoteProperty property) #region serialization /// - /// Implements the ISerializable contract for serializing a PSObject + /// Implements the ISerializable contract for serializing a PSObject. /// - /// Serialization information for this instance - /// The streaming context for this instance + /// Serialization information for this instance. + /// The streaming context for this instance. public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) { - throw PSTraceSource.NewArgumentNullException("info"); + throw PSTraceSource.NewArgumentNullException(nameof(info)); } // We create a wrapper PSObject, so that we can successfully deserialize it - string serializedContent = null; - if (this.immediateBaseObjectIsEmpty) + string serializedContent; + if (this.ImmediateBaseObjectIsEmpty) { PSObject serializeTarget = new PSObject(this); serializedContent = PSSerializer.Serialize(serializeTarget); @@ -1804,16 +1825,6 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte } /// - /// Used in the serialization duplicate entry hashtable to detect when an PSObject has been serialized - /// - /// The System.Object implementation of GetHashCode - internal int GetReferenceHashCode() - { - return base.GetHashCode(); - } - - /// - /// /// /// /// @@ -1839,27 +1850,27 @@ 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; } + object noteValue = note.Value; if (noteValue == null || noteValue.GetType() != expectedType) { return defaultValue; } + return note.Value; } - - internal int GetSerializationDepth(TypeTable backupTypeTable) { int result = 0; TypeTable typeTable = backupTypeTable ?? this.GetTypeTable(); - if (null != typeTable) + if (typeTable != null) { PSMemberSet standardMemberSet = TypeTableGetMemberDelegate(this, typeTable, TypeTable.PSStandardMembers); @@ -1893,15 +1904,15 @@ internal PSPropertyInfo GetStringSerializationSource(TypeTable backupTypeTable) /// internal SerializationMethod GetSerializationMethod(TypeTable backupTypeTable) { - SerializationMethod result = TypeTable.defaultSerializationMethod; + SerializationMethod result = TypeTable.DefaultSerializationMethod; TypeTable typeTable = backupTypeTable ?? this.GetTypeTable(); - if (null != typeTable) + if (typeTable != null) { PSMemberSet standardMemberSet = TypeTableGetMemberDelegate(this, typeTable, TypeTable.PSStandardMembers); result = (SerializationMethod)GetNoteSettingValue(standardMemberSet, - TypeTable.SerializationMethodNode, TypeTable.defaultSerializationMethod, typeof(SerializationMethod), true, this); + TypeTable.SerializationMethodNode, TypeTable.DefaultSerializationMethod, typeof(SerializationMethod), true, this); } return result; @@ -1911,9 +1922,7 @@ internal PSMemberSet PSStandardMembers { get { - PSMemberSet retVal = null; - - retVal = TypeTableGetMemberDelegate(this, TypeTable.PSStandardMembers); + var retVal = TypeTableGetMemberDelegate(this, TypeTable.PSStandardMembers); if (retVal != null) { retVal = (PSMemberSet)retVal.Copy(); @@ -1935,7 +1944,7 @@ internal PSMemberInfo GetPSStandardMember(TypeTable backupTypeTable, string memb { PSMemberSet standardMemberSet = TypeTableGetMemberDelegate( this, typeTable, TypeTable.PSStandardMembers); - if (null != standardMemberSet) + if (standardMemberSet != null) { standardMemberSet.ReplicateInstance(this); PSMemberInfoIntegratingCollection members = @@ -1961,29 +1970,22 @@ internal PSMemberInfo GetPSStandardMember(TypeTable backupTypeTable, string memb internal Type GetTargetTypeForDeserialization(TypeTable backupTypeTable) { PSMemberInfo targetType = this.GetPSStandardMember(backupTypeTable, TypeTable.TargetTypeForDeserialization); - if (null != targetType) - { - return (targetType.Value as Type); - } - else - { - return null; - } + return targetType?.Value as Type; } /// - /// This is only going to be called if SerializationMethod is SpecificProperties + /// This is only going to be called if SerializationMethod is SpecificProperties. /// /// /// TypeTable to use if this PSObject.GetTypeTable() returns null. This will happen /// in the remoting scenario on the client side (where a LocalRunspace may not be /// present). /// - /// A collection with only the specific properties to serialize + /// A collection with only the specific properties to serialize. internal Collection GetSpecificPropertiesToSerialize(TypeTable backupTypeTable) { TypeTable typeTable = backupTypeTable ?? this.GetTypeTable(); - if (null != typeTable) + if (typeTable != null) { Collection tmp = typeTable.GetSpecificProperties(this.InternalTypeNames); return tmp; @@ -1994,41 +1996,26 @@ internal Collection GetSpecificPropertiesToSerialize(TypeTable backupTyp internal bool ShouldSerializeAdapter() { - if (this.isDeserialized) - { - return this.adaptedMembers != null; - } - return !this.immediateBaseObjectIsEmpty; - } - internal bool ShouldSerializeBase() - { - if (this.isDeserialized) - { - return this.adaptedMembers != this.clrMembers; - } - if (this.immediateBaseObjectIsEmpty) + if (this.IsDeserialized) { - return false; + return this.AdaptedMembers != null; } - return (this.InternalAdapter.GetType() != typeof(DotNetAdapter)); - } - internal PSMemberInfoInternalCollection GetAdaptedProperties() - { - return GetProperties(this.adaptedMembers, this.InternalAdapter); + return !this.ImmediateBaseObjectIsEmpty; } - internal PSMemberInfoInternalCollection GetBaseProperties() + internal PSMemberInfoInternalCollection GetAdaptedProperties() { - return GetProperties(this.clrMembers, dotNetInstanceAdapter); + return GetProperties(this.AdaptedMembers, this.InternalAdapter); } private PSMemberInfoInternalCollection GetProperties(PSMemberInfoInternalCollection serializedMembers, Adapter particularAdapter) { - if (this.isDeserialized) + if (this.IsDeserialized) { return serializedMembers; } + PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); foreach (PSPropertyInfo member in particularAdapter.BaseGetMembers(_immediateBaseObject)) @@ -2039,38 +2026,33 @@ private PSMemberInfoInternalCollection GetProperties(PSMemberInf return returnValue; } - /// - /// This flag is set to true in deserialized shellobject - /// - internal bool isDeserialized = false; - internal static void CopyDeserializerFields(PSObject source, PSObject target) { - if (!target.isDeserialized) + if (!target.IsDeserialized) { - target.isDeserialized = source.isDeserialized; - target.adaptedMembers = source.adaptedMembers; - target.clrMembers = source.clrMembers; + target.IsDeserialized = source.IsDeserialized; + target.AdaptedMembers = source.AdaptedMembers; + target.ClrMembers = source.ClrMembers; } - if (target._toStringFromDeserialization == null) + if (target.ToStringFromDeserialization == null) { - target._toStringFromDeserialization = source._toStringFromDeserialization; + target.ToStringFromDeserialization = source.ToStringFromDeserialization; target.TokenText = source.TokenText; } } /// - /// Set base object + /// Set base object. /// - /// object which is set as core - /// If true, overwrite the type information - ///This method is to be used only by Serialization code + /// Object which is set as core. + /// If true, overwrite the type information. + /// 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"); + Diagnostics.Assert(this.ImmediateBaseObjectIsEmpty, "BaseObject should be PSCustomObject for deserialized objects"); Diagnostics.Assert(value != null, "known objects are never null"); - this.immediateBaseObjectIsEmpty = false; + this.ImmediateBaseObjectIsEmpty = false; _immediateBaseObject = value; _adapterSet = GetMappedAdapter(_immediateBaseObject, GetTypeTable()); if (overrideTypeInfo) @@ -2079,47 +2061,6 @@ internal void SetCoreOnDeserialization(object value, bool overrideTypeInfo) } } - //This is toString value set on deserialization - private string _toStringFromDeserialization = null; - - internal bool preserveToString = false; - internal bool preserveToStringSet = false; - - internal bool PreserveToString - { - get - { - if (preserveToStringSet) - { - return preserveToString; - } - preserveToStringSet = true; - if (InternalTypeNames.Count == 0) - { - return false; - } - - preserveToString = false; - - return preserveToString; - } - } - - /// - /// Sets the to string value on deserialization - /// - internal string ToStringFromDeserialization - { - get - { - return _toStringFromDeserialization; - } - set - { - _toStringFromDeserialization = value; - } - } - #endregion serialization /// @@ -2183,7 +2124,7 @@ internal PSDynamicMetaObject(Expression expression, PSObject value) { } - private new PSObject Value { get { return (PSObject)base.Value; } } + private new PSObject Value => (PSObject)base.Value; private DynamicMetaObject GetUnwrappedObject() { @@ -2198,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) @@ -2224,6 +2165,7 @@ public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder bind { return DeferForIDMOP(binder, arg); } + return binder.FallbackBinaryOperation(GetUnwrappedObject(), arg); } @@ -2341,16 +2283,164 @@ DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) #endregion - #region Help formatting + internal bool IsDeserialized + { + get => _flags.HasFlag(PSObjectFlags.IsDeserialized); + set + { + if (value) + { + _flags |= PSObjectFlags.IsDeserialized; + } + else + { + _flags &= ~PSObjectFlags.IsDeserialized; + } + } + } + + private bool StoreTypeNameAndInstanceMembersLocally + { + get => _flags.HasFlag(PSObjectFlags.StoreTypeNameAndInstanceMembersLocally); + set + { + if (value) + { + _flags |= PSObjectFlags.StoreTypeNameAndInstanceMembersLocally; + } + else + { + _flags &= ~PSObjectFlags.StoreTypeNameAndInstanceMembersLocally; + } + } + } internal bool IsHelpObject { - get { return _isHelpObject; } - set { _isHelpObject = value; } + get => _flags.HasFlag(PSObjectFlags.IsHelpObject); + set + { + if (value) + { + _flags |= PSObjectFlags.IsHelpObject; + } + else + { + _flags &= ~PSObjectFlags.IsHelpObject; + } + } + } + + internal bool HasGeneratedReservedMembers + { + get => _flags.HasFlag(PSObjectFlags.HasGeneratedReservedMembers); + set + { + if (value) + { + _flags |= PSObjectFlags.HasGeneratedReservedMembers; + } + else + { + _flags &= ~PSObjectFlags.HasGeneratedReservedMembers; + } + } + } + + internal bool ImmediateBaseObjectIsEmpty + { + get => _flags.HasFlag(PSObjectFlags.ImmediateBaseObjectIsEmpty); + set + { + if (value) + { + _flags |= PSObjectFlags.ImmediateBaseObjectIsEmpty; + } + else + { + _flags &= ~PSObjectFlags.ImmediateBaseObjectIsEmpty; + } + } } - private bool _isHelpObject = false; - #endregion + /// + /// If 'this' is non-null, return this string as the ToString() for this wrapped object. + /// + internal string TokenText { get; set; } + + /// + /// Sets the 'ToString' value on deserialization. + /// + internal string ToStringFromDeserialization { get; set; } + + /// + /// This property contains a stream type used by the formatting system. + /// + internal WriteStreamType WriteStream { get; set; } + + /// + /// Members from the adapter of the object before it was serialized + /// Null for live objects but not null for deserialized objects. + /// + internal PSMemberInfoInternalCollection AdaptedMembers { get; set; } + + internal static DotNetAdapter DotNetStaticAdapter => s_dotNetStaticAdapter; + + internal static PSTraceSource MemberResolution => s_memberResolution; + + /// + /// Members from the adapter of the object before it was serialized + /// Null for live objects but not null for deserialized objects. + /// + internal PSMemberInfoInternalCollection ClrMembers { get; set; } + + internal static DotNetAdapter DotNetInstanceAdapter => s_dotNetInstanceAdapter; + + /// + /// Gets an instance member if it's name matches the predicate. Otherwise null. + /// + internal PSPropertyInfo GetFirstPropertyOrDefault(MemberNamePredicate predicate) + { + return Properties.FirstOrDefault(predicate); + } + + [Flags] + private enum PSObjectFlags : byte + { + None = 0, + + /// + /// This flag is set in deserialized shellobject. + /// + IsDeserialized = 0b00000001, + + /// + /// Set to true when the BaseObject is PSCustomObject. + /// + HasGeneratedReservedMembers = 0b00000010, + ImmediateBaseObjectIsEmpty = 0b00000100, + IsHelpObject = 0b00001000, + + /// + /// Indicate whether we store the instance members and type names locally + /// for this PSObject instance. + /// + StoreTypeNameAndInstanceMembersLocally = 0b00010000, + } + } + + /// + /// Specifies special stream write processing. + /// + internal enum WriteStreamType : byte + { + None, + Output, + Error, + Warning, + Verbose, + Debug, + Information } /// @@ -2360,22 +2450,22 @@ internal bool IsHelpObject public class PSCustomObject { /// - /// To prevent other instances than SelfInstance + /// To prevent other instances than SelfInstance. /// private PSCustomObject() { } - internal static PSCustomObject SelfInstance = new PSCustomObject(); + internal static readonly PSCustomObject SelfInstance = new PSCustomObject(); + /// - /// Returns an empty string + /// Returns an empty string. /// public override string ToString() { - return ""; + return string.Empty; } } /// - /// /// /// /// Please keep in sync with SerializationMethod from @@ -2386,7 +2476,7 @@ internal enum SerializationMethod AllPublicProperties = 0, String = 1, SpecificProperties = 2 - }; + } } #pragma warning restore 56500 @@ -2394,7 +2484,7 @@ internal enum SerializationMethod namespace Microsoft.PowerShell { /// - /// Contains auxiliary ToString CodeMethod implementations for some types + /// Contains auxiliary ToString CodeMethod implementations for some types. /// public static partial class ToStringCodeMethods { @@ -2404,19 +2494,22 @@ private static void AddGenericArguments(StringBuilder sb, Type[] genericArgument for (int i = 0; i < genericArguments.Length; i++) { if (i > 0) { sb.Append(','); } + sb.Append(Type(genericArguments[i], dropNamespaces)); } + sb.Append(']'); } internal static string Type(Type type, bool dropNamespaces = false, string key = null) { if (type == null) - return String.Empty; + { + return string.Empty; + } string result; - TypeInfo typeinfo = type.GetTypeInfo(); - if (typeinfo.IsGenericType && !typeinfo.IsGenericTypeDefinition) + if (type.IsGenericType && !type.IsGenericTypeDefinition) { string genericDefinition = Type(type.GetGenericTypeDefinition(), dropNamespaces); // For regular generic types, we find the backtick character, for example: @@ -2425,21 +2518,22 @@ internal static string Type(Type type, bool dropNamespaces = false, string key = // For nested generic types, we find the left bracket character, for example: // System.Collections.Generic.Dictionary`2+Enumerator[TKey, TValue] -> // System.Collections.Generic.Dictionary`2+Enumerator[string,string] - int backtickOrLeftBracketIndex = genericDefinition.LastIndexOf(typeinfo.IsNested ? '[' : '`'); + int backtickOrLeftBracketIndex = genericDefinition.LastIndexOf(type.IsNested ? '[' : '`'); var sb = new StringBuilder(genericDefinition, 0, backtickOrLeftBracketIndex, 512); AddGenericArguments(sb, type.GetGenericArguments(), dropNamespaces); result = sb.ToString(); } - else if (typeinfo.IsArray) + else if (type.IsArray) { 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 @@ -2451,9 +2545,10 @@ internal static string Type(Type type, bool dropNamespaces = false, string key = { return type.Name; } + if (dropNamespaces) { - if (typeinfo.IsNested) + if (type.IsNested) { // For nested types, we should return OuterType+InnerType. For example, // System.Environment+SpecialFolder -> Environment+SpecialFolder @@ -2476,67 +2571,73 @@ internal static string Type(Type type, bool dropNamespaces = false, string key = // We can't round trip anything with a generic parameter. // We also can't round trip if we're dropping the namespace. - if (!typeinfo.IsGenericParameter - && !typeinfo.ContainsGenericParameters + if (!type.IsGenericParameter + && !type.ContainsGenericParameters && !dropNamespaces - && !typeinfo.Assembly.GetCustomAttributes(typeof(DynamicClassImplementationAssemblyAttribute)).Any()) + && !type.Assembly.GetCustomAttributes(typeof(DynamicClassImplementationAssemblyAttribute)).Any()) { - Type roundTripType; - TypeResolver.TryResolveType(result, out roundTripType); + TypeResolver.TryResolveType(result, out Type roundTripType); if (roundTripType != type) { result = type.AssemblyQualifiedName; } } + return result; } /// - /// ToString implementation for Type + /// ToString implementation for Type. /// - /// instance of PSObject wrapping a Type + /// Instance of PSObject wrapping a Type. public static string Type(PSObject instance) { if (instance == null) - return String.Empty; + { + return string.Empty; + } + return Type((Type)instance.BaseObject); } /// - /// ToString implementation for XmlNode + /// ToString implementation for XmlNode. /// - /// instance of PSObject wrapping an XmlNode + /// Instance of PSObject wrapping an XmlNode. public static string XmlNode(PSObject instance) { - if (instance == null) - return String.Empty; - XmlNode node = (XmlNode)instance.BaseObject; + XmlNode node = (XmlNode)instance?.BaseObject; if (node == null) - return String.Empty; + { + return string.Empty; + } + return node.LocalName; } /// - /// ToString implementation for XmlNodeList + /// ToString implementation for XmlNodeList. /// - /// instance of PSObject wrapping an XmlNodeList + /// Instance of PSObject wrapping an XmlNodeList. public static string XmlNodeList(PSObject instance) { - if (instance == null) - return String.Empty; - XmlNodeList nodes = (XmlNodeList)instance.BaseObject; + XmlNodeList nodes = (XmlNodeList)instance?.BaseObject; if (nodes == null) - return String.Empty; + { + return string.Empty; + } + if (nodes.Count == 1) { if (nodes[0] == null) { - return String.Empty; + return string.Empty; } + return PSObject.AsPSObject(nodes[0]).ToString(); } - return PSObject.ToStringEnumerable(null, (IEnumerable)nodes, null, null, null); + return PSObject.ToStringEnumerable(context: null, enumerable: nodes, separator: null, format: null, formatProvider: null); } } } diff --git a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs index 6a7af45f14f..113161748c9 100644 --- a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs +++ b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs @@ -1,11 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.ComponentModel; using System.Management.Automation.Runspaces; - namespace System.Management.Automation { /// @@ -33,9 +31,9 @@ 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 + /// Exception that triggered the associated event. internal SettingValueExceptionEventArgs(Exception exception) { Exception = exception; @@ -66,9 +64,9 @@ 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 + /// Exception that triggered the associated event. internal GettingValueExceptionEventArgs(Exception exception) { Exception = exception; @@ -94,11 +92,11 @@ internal GettingValueExceptionEventArgs(Exception exception) public class PSObjectPropertyDescriptor : PropertyDescriptor { internal event EventHandler SettingValueException; - internal event EventHandler GettingValueException; + internal event EventHandler GettingValueException; internal PSObjectPropertyDescriptor(string propertyName, Type propertyType, bool isReadOnly, AttributeCollection propertyAttributes) - : base(propertyName, Utils.EmptyArray()) + : base(propertyName, Array.Empty()) { IsReadOnly = isReadOnly; Attributes = propertyAttributes; @@ -125,15 +123,15 @@ public override void ResetValue(object component) { } /// /// Returns false to indicate that ResetValue has no effect. /// - /// The component to test for reset capability. - /// false + /// The component to test for reset capability. + /// False. public override bool CanResetValue(object component) { return false; } /// /// Returns true to indicate that the value of this property needs to be persisted. /// /// The component with the property to be examined for persistence. - /// true + /// True. public override bool ShouldSerializeValue(object component) { return true; @@ -156,7 +154,7 @@ public override Type ComponentType /// /// Gets the current value of the property on a component. /// - /// The component with the property for which to retrieve the value. + /// The component with the property for which to retrieve the value. /// The value of a property for a given component. /// /// If the property has not been found in the component or an exception has @@ -168,13 +166,13 @@ public override Type ComponentType /// value of true to false. /// /// If is null. - /// if is not + /// If is not /// an or an . public override object GetValue(object component) { if (component == null) { - throw PSTraceSource.NewArgumentNullException("component"); + throw PSTraceSource.NewArgumentNullException(nameof(component)); } PSObject mshObj = GetComponentPSObject(component); @@ -194,8 +192,10 @@ public override object GetValue(object component) { throw e; } + return returnValue; } + return property.Value; } catch (ExtendedTypeSystemException e) @@ -207,6 +207,7 @@ public override object GetValue(object component) { throw; } + return returnValue; } } @@ -218,16 +219,17 @@ 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("component", ExtendedTypeSystem.InvalidComponent, + throw PSTraceSource.NewArgumentException(nameof(component), ExtendedTypeSystem.InvalidComponent, "component", - typeof(PSObject).Name, - typeof(PSObjectTypeDescriptor).Name); + nameof(PSObject), + nameof(PSObjectTypeDescriptor)); } + mshObj = descriptor.Instance; } + return mshObj; } @@ -241,6 +243,7 @@ private object DealWithGetValueException(ExtendedTypeSystemException e, out bool "GettingValueException event has been triggered resulting in ValueReplacement:\"{0}\".", eventArgs.ValueReplacement); } + shouldThrow = eventArgs.ShouldThrow; return eventArgs.ValueReplacement; } @@ -248,7 +251,7 @@ private object DealWithGetValueException(ExtendedTypeSystemException e, out bool /// /// Sets the value of the component to a different value. /// - /// The component with the property value that is to be set. + /// The component with the property value that is to be set. /// The new value. /// /// If the property has not been found in the component or an exception has @@ -260,14 +263,14 @@ private object DealWithGetValueException(ExtendedTypeSystemException e, out bool /// from its default value of true to false. /// /// If is null. - /// if is not an + /// If is not an /// or an . /// public override void SetValue(object component, object value) { if (component == null) { - throw PSTraceSource.NewArgumentNullException("component"); + throw PSTraceSource.NewArgumentNullException(nameof(component)); } PSObject mshObj = GetComponentPSObject(component); @@ -286,8 +289,10 @@ public override void SetValue(object component, object value) { throw e; } + return; } + property.Value = value; } catch (ExtendedTypeSystemException e) @@ -300,6 +305,7 @@ public override void SetValue(object component, object value) throw; } } + OnValueChanged(component, EventArgs.Empty); } @@ -313,6 +319,7 @@ private void DealWithSetValueException(ExtendedTypeSystemException e, out bool s "SettingValueException event has been triggered resulting in ShouldThrow:\"{0}\".", eventArgs.ShouldThrow); } + shouldThrow = eventArgs.ShouldThrow; return; } @@ -323,7 +330,7 @@ private void DealWithSetValueException(ExtendedTypeSystemException e, out bool s /// public class PSObjectTypeDescriptor : CustomTypeDescriptor { - internal static PSTraceSource typeDescriptor = PSTraceSource.GetTracer("TypeDescriptor", "Traces the behavior of PSObjectTypeDescriptor, PSObjectTypeDescriptionProvider and PSObjectPropertyDescriptor.", false); + internal static readonly PSTraceSource typeDescriptor = PSTraceSource.GetTracer("TypeDescriptor", "Traces the behavior of PSObjectTypeDescriptor, PSObjectTypeDescriptionProvider and PSObjectPropertyDescriptor.", false); /// /// Occurs when there was an exception setting the value of a property. @@ -368,6 +375,7 @@ private void CheckAndAddProperty(PSPropertyInfo propertyInfo, Attribute[] attrib typeDescriptor.WriteLine("Property \"{0}\" is write-only so it has been skipped.", propertyInfo.Name); return; } + AttributeCollection propertyAttributes = null; Type propertyType = typeof(object); if (attributes != null && attributes.Length != 0) @@ -402,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); @@ -447,6 +452,7 @@ public override PropertyDescriptorCollection GetProperties(Attribute[] attribute { CheckAndAddProperty(property, attributes, ref returnValue); } + return returnValue; } } @@ -455,44 +461,47 @@ public override PropertyDescriptorCollection GetProperties(Attribute[] attribute /// Determines whether the Instance property of is equal to the current Instance. /// /// The Object to compare with the current Object. - /// true if the Instance property of is equal to the current Instance; otherwise, false. + /// 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; } + if (this.Instance == null || other.Instance == null) { return ReferenceEquals(this, other); } + return other.Instance.Equals(this.Instance); } /// /// Provides a value for hashing algorithms. /// - /// A hash code for the current object + /// A hash code for the current object. public override int GetHashCode() { if (this.Instance == null) { return base.GetHashCode(); } + return this.Instance.GetHashCode(); } /// /// Returns the default property for this object. /// - /// An that represents the default property for this object, or a null reference (Nothing in Visual Basic) if this object does not have properties + /// An that represents the default property for this object, or a null reference (Nothing in Visual Basic) if this object does not have properties. public override PropertyDescriptor GetDefaultProperty() { if (this.Instance == null) { return null; } + string defaultProperty = null; PSMemberSet standardMembers = this.Instance.PSStandardMembers; if (standardMembers != null) @@ -516,6 +525,7 @@ public override PropertyDescriptor GetDefaultProperty() } } } + PropertyDescriptorCollection properties = this.GetProperties(); if (defaultProperty != null) @@ -524,7 +534,7 @@ public override PropertyDescriptor GetDefaultProperty() // returning in GetProperties foreach (PropertyDescriptor descriptor in properties) { - if (String.Equals(descriptor.Name, defaultProperty, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(descriptor.Name, defaultProperty, StringComparison.OrdinalIgnoreCase)) { return descriptor; } @@ -546,6 +556,7 @@ public override TypeConverter GetConverter() // GetConverter returned an illegal value return new TypeConverter(); } + object baseObject = this.Instance.BaseObject; TypeConverter retValue = LanguagePrimitives.GetConverter(baseObject.GetType(), null) as TypeConverter ?? TypeDescriptor.GetConverter(baseObject); @@ -587,6 +598,7 @@ public override EventDescriptor GetDefaultEvent() { return null; } + return TypeDescriptor.GetDefaultEvent(this.Instance.BaseObject); } @@ -600,13 +612,14 @@ public override EventDescriptorCollection GetEvents() { return new EventDescriptorCollection(null); } + return TypeDescriptor.GetEvents(this.Instance.BaseObject); } /// /// Returns the events for this instance of a component using the attribute array as a filter. /// - /// An array of type that is used as a filter. + /// An array of type that is used as a filter. /// An that represents the events for this component instance that match the given set of attributes. public override EventDescriptorCollection GetEvents(Attribute[] attributes) { @@ -614,6 +627,7 @@ public override EventDescriptorCollection GetEvents(Attribute[] attributes) { return null; } + return TypeDescriptor.GetEvents(this.Instance.BaseObject, attributes); } @@ -627,6 +641,7 @@ public override AttributeCollection GetAttributes() { return new AttributeCollection(); } + return TypeDescriptor.GetAttributes(this.Instance.BaseObject); } @@ -640,6 +655,7 @@ public override string GetClassName() { return null; } + return TypeDescriptor.GetClassName(this.Instance.BaseObject); } @@ -653,6 +669,7 @@ public override string GetComponentName() { return null; } + return TypeDescriptor.GetComponentName(this.Instance.BaseObject); } @@ -667,6 +684,7 @@ public override object GetEditor(Type editorBaseType) { return null; } + return TypeDescriptor.GetEditor(this.Instance.BaseObject, editorBaseType); } #endregion Forwarded To BaseObject @@ -702,7 +720,6 @@ public PSObjectTypeDescriptionProvider() { } - /// /// Retrieves a to provide information about the properties for an object of the type . /// @@ -750,5 +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 c22377905a2..fa8b6f13583 100644 --- a/src/System.Management.Automation/engine/MshReference.cs +++ b/src/System.Management.Automation/engine/MshReference.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Dynamic; using System.Management.Automation.Language; @@ -9,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: @@ -23,7 +22,6 @@ namespace System.Management.Automation /// [ref] $a = $b /// b. variable reference /// $a = [ref] $b - /// /// public class PSReference { @@ -58,6 +56,7 @@ public object Value return _value; } + set { PSVariable variable = _value as PSVariable; @@ -89,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 6b48bfdc322..07c70ec7317 100644 --- a/src/System.Management.Automation/engine/MshSecurityException.cs +++ b/src/System.Management.Automation/engine/MshSecurityException.cs @@ -1,22 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; namespace System.Management.Automation { /// - /// This is a wrapper for exception class SecurityException + /// This is a wrapper for exception class SecurityException. /// - [Serializable] public class PSSecurityException : RuntimeException { #region ctor /// - /// Recommended constructor for class PSSecurityException + /// Recommended constructor for class PSSecurityException. /// - /// constructed object + /// Constructed object. public PSSecurityException() : base() { @@ -30,31 +28,23 @@ public PSSecurityException() } /// - /// Serialization constructor for class PSSecurityException + /// Serialization constructor for class PSSecurityException. /// - /// serialization information - /// streaming context - /// constructed object + /// 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(); } /// - /// Constructor for class PSSecurityException + /// Constructor for class PSSecurityException. /// - /// - /// constructed object + /// + /// Constructed object. public PSSecurityException(string message) : base(message) { @@ -68,11 +58,11 @@ public PSSecurityException(string message) } /// - /// Constructor for class PSSecurityException + /// Constructor for class PSSecurityException. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSSecurityException(string message, Exception innerException) : base(message, innerException) @@ -94,17 +84,16 @@ public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "UnauthorizedAccess", - ErrorCategory.SecurityError, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "UnauthorizedAccess", + ErrorCategory.SecurityError, + null); + return _errorRecord; } } + private ErrorRecord _errorRecord; /// @@ -116,7 +105,7 @@ public override string Message { get { return _message; } } - private string _message; - } // PSSecurityException -} // System.Management.Automation + private readonly string _message; + } +} diff --git a/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs b/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs index 336c2f59326..b4cf7c16eef 100644 --- a/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs +++ b/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs @@ -1,15 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// A class representing a name that is qualified by the PSSnapin name + /// A class representing a name that is qualified by the PSSnapin name. /// - internal class PSSnapinQualifiedName + internal sealed class PSSnapinQualifiedName { private PSSnapinQualifiedName(string[] splitName) { @@ -22,7 +23,7 @@ private PSSnapinQualifiedName(string[] splitName) } else if (splitName.Length == 2) { - if (!String.IsNullOrEmpty(splitName[0])) + if (!string.IsNullOrEmpty(splitName[0])) { _psSnapinName = splitName[0]; } @@ -39,10 +40,10 @@ private PSSnapinQualifiedName(string[] splitName) // Now set the full name - if (!String.IsNullOrEmpty(_psSnapinName)) + if (!string.IsNullOrEmpty(_psSnapinName)) { _fullName = - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, "{0}\\{1}", _psSnapinName, @@ -57,26 +58,22 @@ private PSSnapinQualifiedName(string[] splitName) /// /// Gets an instance of the Name class. /// - /// /// /// The name of the command. /// - /// /// /// An instance of the Name class. /// - /// - internal static PSSnapinQualifiedName GetInstance(string name) + internal static PSSnapinQualifiedName? GetInstance(string? name) { if (name == null) return null; - PSSnapinQualifiedName result = null; - string[] splitName = name.Split(Utils.Separators.Backslash); - if (splitName.Length < 0 || splitName.Length > 2) + string[] splitName = name.Split('\\'); + if (splitName.Length == 0 || splitName.Length > 2) return null; - result = new PSSnapinQualifiedName(splitName); + var result = new PSSnapinQualifiedName(splitName); // If the shortname is empty, then return null... - if (String.IsNullOrEmpty(result.ShortName)) + if (string.IsNullOrEmpty(result.ShortName)) { return null; } @@ -87,7 +84,6 @@ internal static PSSnapinQualifiedName GetInstance(string name) /// /// Gets the command's full name. /// - /// internal string FullName { get @@ -95,25 +91,25 @@ internal string FullName return _fullName; } } - private string _fullName; + + private readonly string _fullName; /// /// Gets the command's PSSnapin name. /// - /// - internal string PSSnapInName + internal string? PSSnapInName { get { return _psSnapinName; } } - private string _psSnapinName; + + private readonly string? _psSnapinName; /// /// Gets the command's short name. /// - /// internal string ShortName { get @@ -121,21 +117,18 @@ internal string ShortName return _shortName; } } - private string _shortName; + + private readonly string _shortName; /// - /// The full name + /// The full name. /// - /// /// /// A string representing the full name. /// - /// public override string ToString() { return _fullName; } } } - - diff --git a/src/System.Management.Automation/engine/NativeCommand.cs b/src/System.Management.Automation/engine/NativeCommand.cs index 63bea73314f..80748605a98 100644 --- a/src/System.Management.Automation/engine/NativeCommand.cs +++ b/src/System.Management.Automation/engine/NativeCommand.cs @@ -1,20 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; namespace System.Management.Automation { /// - /// Derives InternalCommand for Native Commands + /// Derives InternalCommand for Native Commands. /// internal sealed class NativeCommand : InternalCommand { private NativeCommandProcessor _myCommandProcessor; + internal NativeCommandProcessor MyCommandProcessor { get { return _myCommandProcessor; } + set { _myCommandProcessor = value; } } @@ -25,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 a3df920e77e..31ac506c86a 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -1,15 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using Microsoft.PowerShell.Commands; using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Management.Automation.Internal; using System.Text; +using Microsoft.PowerShell.Commands; + namespace System.Management.Automation { using Language; @@ -17,19 +18,16 @@ namespace System.Management.Automation /// /// The parameter binder for native commands. /// - /// internal class NativeCommandParameterBinder : ParameterBinderBase { #region ctor /// - /// Constructs a NativeCommandParameterBinder + /// Constructs a NativeCommandParameterBinder. /// - /// /// /// The NativeCommand to bind to. /// - /// /// /// .Context is null /// @@ -62,7 +60,7 @@ internal override void BindParameter(string name, object value, CompiledCommandP Diagnostics.Assert(false, "Unreachable code"); throw new NotSupportedException(); - } // BindParameter + } internal override object GetDefaultParameterValue(string name) { @@ -79,12 +77,13 @@ internal void BindParameters(Collection parameters) { _arguments.Append(' '); } + first = false; if (parameter.ParameterNameSpecified) { - Diagnostics.Assert(parameter.ParameterText.IndexOf(' ') == -1, "Parameters cannot have whitespace"); - PossiblyGlobArg(parameter.ParameterText, usedQuotes: false); + Diagnostics.Assert(!parameter.ParameterText.Contains(' '), "Parameters cannot have whitespace"); + PossiblyGlobArg(parameter.ParameterText, parameter, usedQuotes: false); if (parameter.SpaceAfterParameter) { @@ -113,19 +112,18 @@ internal void BindParameters(Collection parameters) ArrayLiteralAst arrayLiteralAst = null; switch (parameter?.ArgumentAst) { - case StringConstantExpressionAst sce: - usedQuotes = sce.StringConstantType != StringConstantType.BareWord; - break; - case ExpandableStringExpressionAst ese: - usedQuotes = ese.StringConstantType != StringConstantType.BareWord; - break; - case ArrayLiteralAst ala: - arrayLiteralAst = ala; - break; + case StringConstantExpressionAst sce: + usedQuotes = sce.StringConstantType != StringConstantType.BareWord; + break; + case ExpandableStringExpressionAst ese: + usedQuotes = ese.StringConstantType != StringConstantType.BareWord; + break; + case ArrayLiteralAst ala: + arrayLiteralAst = ala; + break; } - appendOneNativeArgument(Context, argValue, - arrayLiteralAst, sawVerbatimArgumentMarker, usedQuotes); + AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, usedQuotes); } } } @@ -134,18 +132,81 @@ internal void BindParameters(Collection parameters) #endregion Parameter binding /// - /// Gets the command arguments in string form + /// Gets the command arguments in string form. /// - /// - internal String Arguments + internal string Arguments { get { return _arguments.ToString(); } - } // 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 @@ -155,27 +216,28 @@ internal String Arguments /// and trailing spaces as appropriate. An array gets added as multiple arguments /// each of which will be stringized. /// - /// Execution context instance - /// The object to append - /// If the argument was an array literal, the Ast, otherwise null - /// true if the argument occurs after --% - /// True if the argument was a quoted string (single or double) - private void appendOneNativeArgument(ExecutionContext context, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, bool usedQuotes) + /// 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 --%. + /// 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 separator = string.Empty; do { string arg; + object currentObj; if (list == null) { arg = PSObject.ToStringParser(context, obj); + currentObj = obj; } else { @@ -183,7 +245,9 @@ 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) @@ -192,14 +256,18 @@ private void appendOneNativeArgument(ExecutionContext context, object obj, Array } } - if (!String.IsNullOrEmpty(arg)) + 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 { @@ -219,31 +287,54 @@ private void appendOneNativeArgument(ExecutionContext context, object obj, Array if (NeedQuotes(arg)) { _arguments.Append('"'); + 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--) + for (int i = arg.Length - 1; i >= 0 && arg[i] == '\\'; i--) { _arguments.Append('\\'); } + _arguments.Append('"'); } else { - PossiblyGlobArg(arg, usedQuotes); + 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); + } } } } - } while (list != null); + 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 - /// True if the argument was a quoted string (single or double) - private void PossiblyGlobArg(string arg, bool usedQuotes) + /// The argument that possibly needs expansion. + /// 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; @@ -259,8 +350,8 @@ private void PossiblyGlobArg(string arg, bool usedQuotes) // If it's a filesystem location then expand the wildcards if (cwdinfo.Provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) { - // On UNIX, paths starting with ~ are not normalized - bool normalizePath = arg.Length == 0 || arg[0] != '~'; + // 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; @@ -276,7 +367,7 @@ private void PossiblyGlobArg(string arg, bool usedQuotes) // Expand paths, but only from the file system. if (paths?.Count > 0 && paths.All(p => p.BaseObject is FileSystemInfo)) { - var sep = ""; + var sep = string.Empty; foreach (var path in paths) { _arguments.Append(sep); @@ -290,14 +381,16 @@ private void PossiblyGlobArg(string arg, bool usedQuotes) // If the path contains spaces, then add quotes around it. if (NeedQuotes(expandedPath)) { - _arguments.Append("\""); + _arguments.Append('"'); _arguments.Append(expandedPath); - _arguments.Append("\""); + _arguments.Append('"'); } else { _arguments.Append(expandedPath); } + + AddToArgumentList(parameter, expandedPath); argExpanded = true; } } @@ -307,32 +400,56 @@ private void PossiblyGlobArg(string arg, bool usedQuotes) { // 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)) + if (ExpandTilde(arg, parameter)) { - var replacementString = home + arg.Substring(1); - _arguments.Append(replacementString); argExpanded = true; } } -#endif // UNIX +#else + if (!usedQuotes && ExpandTilde(arg, parameter)) + { + argExpanded = true; + } +#endif if (!argExpanded) { _arguments.Append(arg); + AddToArgumentList(parameter, arg); + } + } + + /// + /// Replace tilde for unquoted arguments in the form ~ and ~/. For windows, ~\ is also expanded. + /// + /// The argument that possibly needs expansion. + /// The parameter associated with the operation. + /// True if tilde expansion occurred. + private bool ExpandTilde(string arg, CommandParameterInternal parameter) + { + var fileSystemProvider = Context.EngineSessionState.GetSingleProvider(FileSystemProvider.ProviderName); + var home = fileSystemProvider.Home; + if (string.Equals(arg, "~")) + { + _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 false; } /// /// Check to see if the string contains spaces and therefore must be quoted. /// - /// The string to check for spaces + /// The string to check for spaces. internal static bool NeedQuotes(string stringToCheck) { bool needQuotes = false, followingBackslash = false; @@ -347,14 +464,19 @@ internal static bool NeedQuotes(string stringToCheck) { needQuotes = true; } + followingBackslash = stringToCheck[i] == '\\'; } + return needQuotes; } - static private string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, int index) + 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. @@ -366,21 +488,33 @@ static private 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 + /// The native command to bind to. /// - private NativeCommand _nativeCommand; -#endregion private members + private readonly NativeCommand _nativeCommand; + + #endregion private members } -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs index f8beed2b91d..9a1dab71beb 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; @@ -10,20 +9,17 @@ namespace System.Management.Automation /// This is the interface between the NativeCommandProcessor and the /// parameter binders required to bind parameters to a native command. /// - /// internal class NativeCommandParameterBinderController : ParameterBinderController { #region ctor /// /// Initializes the cmdlet parameter binder controller for - /// the specified native command and engine context + /// the specified native command and engine context. /// - /// /// /// The command that the parameters will be bound to. /// - /// internal NativeCommandParameterBinderController(NativeCommand command) : base(command.MyInvocation, command.Context, new NativeCommandParameterBinder(command)) { @@ -32,35 +28,51 @@ internal NativeCommandParameterBinderController(NativeCommand command) #endregion ctor /// - /// Gets the command arguments in string form + /// Gets the command arguments in string form. /// - /// - internal String Arguments + internal string Arguments { get { return ((NativeCommandParameterBinder)DefaultParameterBinder).Arguments; } - } // 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. /// - /// /// /// The name and value of the variable to bind. /// - /// /// /// 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, ParameterBindingFlags flags) @@ -71,18 +83,15 @@ internal override bool BindParameter( } /// - /// Binds the specified parameters to the native command + /// Binds the specified parameters to the native command. /// - /// /// /// 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) { ((NativeCommandParameterBinder)DefaultParameterBinder).BindParameters(parameters); @@ -90,10 +99,8 @@ internal override Collection BindParameters(Collection Diagnostics.Assert(s_emptyReturnCollection.Count == 0, "This list shouldn't be used for anything as it's shared."); return s_emptyReturnCollection; - } // BindParameters + } 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 c51e730495f..145fe968fda 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -1,24 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #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,10 +37,10 @@ internal enum NativeCommandIOFormat { Text, Xml - }; + } /// - /// Different streams produced by minishell output + /// Different streams produced by minishell output. /// internal enum MinishellStream { @@ -52,7 +56,7 @@ internal enum MinishellStream /// /// Helper class which holds stream names and also provide conversion - /// method + /// method. /// internal static class StringToMinishellStreamConverter { @@ -102,29 +106,28 @@ internal static MinishellStream ToMinishellStream(string stream) } } - /// /// An output object from the child process. - /// If it's from the error stream isError will be true + /// If it's from the error stream isError will be true. /// internal class ProcessOutputObject { /// - /// Get the data from this object + /// Get the data from this object. /// /// The data internal object Data { get; } /// - /// Stream to which data belongs + /// Stream to which data belongs. /// internal MinishellStream Stream { get; } /// - /// Build an output object + /// Build an output object. /// - /// The data to output - /// stream to which data belongs + /// The data to output. + /// Stream to which data belongs. internal ProcessOutputObject(object data, MinishellStream stream) { Data = data; @@ -132,31 +135,290 @@ 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 + /// NativeCommandProcessor. /// - private ApplicationInfo _applicationInfo; + private readonly ApplicationInfo _applicationInfo; /// /// Initializes the new instance of NativeCommandProcessor class. /// - /// /// /// The information about the application to run. /// - /// /// /// The execution context for this command. /// - /// /// /// or is null /// @@ -165,7 +427,7 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex { if (applicationInfo == null) { - throw PSTraceSource.NewArgumentNullException("applicationInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(applicationInfo)); } _applicationInfo = applicationInfo; @@ -178,15 +440,19 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex this.CommandScope = context.EngineSessionState.CurrentScope; - //provide native command a backpointer to this object. - //When kill is called on the command object, - //it calls this NCP back to kill the process... + // provide native command a backpointer to this object. + // When kill is called on the command object, + // it calls this NCP back to kill the process... ((NativeCommand)Command).MyCommandProcessor = this; - //Create input writer for providing input to the process. + // 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 } /// @@ -226,37 +492,32 @@ 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 + /// Parameter binder used by this command processor. /// private NativeCommandParameterBinderController _nativeParameterBinderController; /// - /// Gets a new instance of a ParameterBinderController using a NativeCommandParameterBinder + /// Gets a new instance of a ParameterBinderController using a NativeCommandParameterBinder. /// - /// /// /// The native command to be run. /// - /// /// /// A new parameter binder controller for the specified command. /// - /// internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - Dbg.Assert(_isPreparedCalled, "parameter binder should not be created before prepared is called"); - if (_isMiniShell) { _nativeParameterBinderController = @@ -281,6 +542,7 @@ internal NativeCommandParameterBinderController NativeParameterBinderController { NewParameterBinderController(this.Command); } + return _nativeParameterBinderController; } } @@ -294,13 +556,11 @@ internal NativeCommandParameterBinderController NativeParameterBinderController /// internal override void Prepare(IDictionary psDefaultParameterValues) { - _isPreparedCalled = true; - - //Check if the application is minishell + // Check if the application is minishell _isMiniShell = IsMiniShell(); - //For minishell parameter binding is done in Complete method because we need - //to know if output is redirected before we can bind parameters. + // For minishell parameter binding is done in Complete method because we need + // to know if output is redirected before we can bind parameters. if (!_isMiniShell) { this.NativeParameterBinderController.BindParameters(arguments); @@ -313,7 +573,7 @@ internal override void Prepare(IDictionary psDefaultParameterValues) catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } @@ -325,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); @@ -335,20 +600,20 @@ internal override void ProcessRecord() catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } /// - /// Process object for the invoked application + /// Process object for the invoked application. /// - private System.Diagnostics.Process _nativeProcess; + private Process _nativeProcess; /// - /// This is used for writing input to the process + /// 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 @@ -364,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; @@ -376,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 + /// 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. @@ -407,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; @@ -415,6 +734,30 @@ 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")) + { + // if starting Windows PowerShell, need to remove PowerShell specific segments of PSModulePath + string psmodulepath = ModuleIntrinsics.GetWindowsPowerShellModulePath(); + startInfo.Environment["PSModulePath"] = psmodulepath; + + // 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) { throw new PipelineStoppedException(); @@ -423,29 +766,28 @@ 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; } } - //Start the process. If stop has been called, throw exception. - //Note: if StopProcessing is called which this method has the lock, - //Stop thread will wait for nativeProcess to start. - //If StopProcessing gets the lock first, then it will set the stopped - //flag and this method will throw PipelineStoppedException when it gets - //the lock. + // Start the process. If stop has been called, throw exception. + // Note: if StopProcessing is called which this method has the lock, + // Stop thread will wait for nativeProcess to start. + // If StopProcessing gets the lock first, then it will set the stopped + // flag and this method will throw PipelineStoppedException when it gets + // the lock. lock (_sync) { if (_stopped) @@ -453,16 +795,6 @@ private void InitNativeProcess() throw new PipelineStoppedException(); } - if (!Platform.IsWindows && startInfo.UseShellExecute) - { - // UseShellExecute is not properly supported on Unix. It runs the file with '/bin/sh'. - // Before the behavior is improved (tracked by dotnet/corefx#19956), we use xdg-open/open as the default programs - string executable = Platform.IsLinux ? "xdg-open" : /* macOS */ "open"; - startInfo.Arguments = "\"" + startInfo.FileName + "\" " + startInfo.Arguments; - startInfo.FileName = executable; - startInfo.UseShellExecute = false; - } - try { _nativeProcess = new Process() { StartInfo = startInfo }; @@ -470,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; - if (!String.IsNullOrEmpty(executable)) + // 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(); @@ -487,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 { @@ -497,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; } } @@ -506,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; @@ -519,6 +878,13 @@ private void InitNativeProcess() throw; } } +#endif + } + + if (UpstreamIsNativeCommand) + { + _processInitialized ??= new SemaphoreSlim(0, 1); + _processInitialized.Release(); } } @@ -533,15 +899,15 @@ private void InitNativeProcess() else { _isRunningInBackground = true; - if (startInfo.UseShellExecute == false) + if (!startInfo.UseShellExecute) { - _isRunningInBackground = IsWindowsApplication(_nativeProcess.StartInfo.FileName); + _isRunningInBackground = isWindowsApplication; } } try { - //If input is redirected, start input to process. + // If input is redirected, start input to process. if (startInfo.RedirectStandardInput) { NativeCommandIOFormat inputFormat = NativeCommandIOFormat.Text; @@ -549,9 +915,10 @@ private void InitNativeProcess() { inputFormat = ((MinishellParameterBinderController)NativeParameterBinderController).InputFormat; } + lock (_sync) { - if (!_stopped) + if (!_stopped && !UpstreamIsNativeCommand) { _inputWriter.Start(_nativeProcess, inputFormat); } @@ -564,7 +931,7 @@ private void InitNativeProcess() throw; } - if (_isRunningInBackground == false) + if (!_isRunningInBackground) { InitOutputQueue(); } @@ -572,8 +939,7 @@ private void InitNativeProcess() catch (Win32Exception e) { exceptionToRethrow = e; - - } // try + } catch (PipelineStoppedException) { // If we're stopping the process, just rethrow this exception... @@ -601,18 +967,43 @@ private void InitNativeProcess() } } + private AsyncByteStreamTransfer _stdOutByteTransfer; + private void InitOutputQueue() { - //if output is redirected, start reading output of process in queue. + // if output is redirected, start reading output of process in queue. if (_nativeProcess.StartInfo.RedirectStandardOutput || _nativeProcess.StartInfo.RedirectStandardError) { lock (_sync) { 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); } } } @@ -643,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; } /// @@ -656,21 +1044,38 @@ private ProcessOutputObject DequeueProcessOutput(bool blocking) /// private void ConsumeAvailableNativeProcessOutput(bool blocking) { - if (_isRunningInBackground == false) + if (_isRunningInBackground) + { + return; + } + + bool stdOutRedirected = _nativeProcess.StartInfo.RedirectStandardOutput; + bool stdErrRedirected = _nativeProcess.StartInfo.RedirectStandardError; + if (stdOutRedirected && _stdOutByteTransfer is not null) { - if (_nativeProcess.StartInfo.RedirectStandardOutput || _nativeProcess.StartInfo.RedirectStandardError) + if (blocking) { - ProcessOutputObject record; - while ((record = DequeueProcessOutput(blocking)) != null) - { - if (this.Command.Context.CurrentPipelineStopping) - { - this.StopProcessing(); - return; - } + _stdOutByteTransfer.EOF.GetAwaiter().GetResult(); + } - ProcessOutputRecord(record); + if (!stdErrRedirected) + { + return; + } + } + + if (stdOutRedirected || stdErrRedirected) + { + ProcessOutputObject record; + while ((record = DequeueProcessOutput(blocking)) != null) + { + if (this.Command.Context.CurrentPipelineStopping) + { + this.StopProcessing(); + return; } + + ProcessOutputRecord(record); } } } @@ -680,17 +1085,27 @@ internal override void Complete() Exception exceptionToRethrow = null; try { - if (_isRunningInBackground == false) + if (!_isRunningInBackground) { - //Wait for input writer to finish. - _inputWriter.Done(); + // Wait for input writer to finish. + 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; @@ -727,14 +1142,69 @@ 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) { exceptionToRethrow = e; - } // try + } catch (PipelineStoppedException) { // If we're stopping the process, just rethrow this exception... @@ -747,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 @@ -767,7 +1237,6 @@ internal override void Complete() } } - #region Process cleanup with Child Process cleanup /// @@ -776,7 +1245,7 @@ internal override void Complete() /// if the process handle is invalid (as seems to be the case with an ntvdm) /// then we try to get a fresh handle based on the original process id. /// - /// The process to kill + /// The process to kill. private static void KillProcess(Process processToKill) { if (NativeCommandProcessor.IsServerSide) @@ -821,15 +1290,17 @@ internal struct ProcessWithParentId { public Process OriginalProcessInstance; private int _parentId; + public int ParentId { get { // Construct parent id only once. - if (int.MinValue == _parentId) + if (_parentId == int.MinValue) { ConstructParentId(); } + return _parentId; } } @@ -847,6 +1318,7 @@ public static ProcessWithParentId[] Construct(Process[] originalProcCollection) { result[index] = new ProcessWithParentId(originalProcCollection[index]); } + return result; } @@ -931,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 @@ -969,12 +1435,13 @@ private static bool IsWindowsApplication(string fileName) // anything else - is a windows program... return true; } +#endif } #endregion checkForConsoleApplication /// - /// This is set to true when StopProcessing is called + /// This is set to true when StopProcessing is called. /// private bool _stopped = false; /// @@ -984,7 +1451,11 @@ internal void StopProcessing() { lock (_sync) { - if (_stopped) return; + if (_stopped) + { + return; + } + _stopped = true; } @@ -992,9 +1463,13 @@ internal void StopProcessing() { if (!_runStandAlone) { - //Stop input writer - _inputWriter.Stop(); + // Stop input writer + if (!UpstreamIsNativeCommand) + { + _inputWriter.Stop(); + } + _stdOutByteTransfer?.Dispose(); KillProcess(_nativeProcess); } } @@ -1005,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) { @@ -1035,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) { @@ -1070,12 +1558,14 @@ private void ProcessOutputRecord(ProcessOutputObject outputValue) { sourceId = (long)info.Value; } + info = temp.Properties["Record"]; ProgressRecord rec = null; if (info != null) { rec = info.Value as ProgressRecord; } + if (rec != null) { this.Command.PSHostInternal.UI.WriteProgress(sourceId, rec); @@ -1091,78 +1581,171 @@ 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) - { - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.FileName = this.Path; + /// + private bool UseSpecialArgumentPassing(string filePath) => + NativeParameterBinderController.ArgumentPassingStyle switch + { + NativeArgumentPassingStyle.Legacy => true, + NativeArgumentPassingStyle.Windows => ShouldUseLegacyPassingStyle(filePath), + _ => false + }; - // On Windows, check the extension list and see if we should try to execute this directly. - // Otherwise, use the platform library to check executability - if ((Platform.IsWindows && ValidateExtension(this.Path)) - || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(this.Path))) + /// + /// 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) + { + var startInfo = new ProcessStartInfo { - startInfo.UseShellExecute = false; - if (redirectInput) - { - startInfo.RedirectStandardInput = true; - } - if (redirectOutput) - { - startInfo.RedirectStandardOutput = true; - } - if (redirectError) - { - startInfo.RedirectStandardError = true; - } - } - else + // 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 (Platform.IsNanoServer || Platform.IsIoT) { // 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); + throw InterpreterError.NewInterpreterException( + this.Path, + typeof(RuntimeException), + this.Command.InvocationExtent, + "CantActivateDocumentInPowerShellCore", + ParserStrings.CantActivateDocumentInPowerShellCore, + this.Path); } // We only want to ShellExecute something that is standalone... if (!soloCommand) { - throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException), - this.Command.InvocationExtent, "CantActivateDocumentInPipeline", ParserStrings.CantActivateDocumentInPipeline, this.Path); + throw InterpreterError.NewInterpreterException( + this.Path, + typeof(RuntimeException), + this.Command.InvocationExtent, + "CantActivateDocumentInPipeline", + ParserStrings.CantActivateDocumentInPipeline, + this.Path); } startInfo.UseShellExecute = true; } + else + { + startInfo.UseShellExecute = false; + startInfo.RedirectStandardInput = redirectInput; + + Encoding outputEncoding = GetOutputEncoding(); + if (redirectOutput) + { + startInfo.RedirectStandardOutput = true; + startInfo.StandardOutputEncoding = outputEncoding; + } + + if (redirectError) + { + startInfo.RedirectStandardError = true; + startInfo.StandardErrorEncoding = outputEncoding; + } + } - //For minishell value of -outoutFormat parameter depends on value of redirectOutput. - //So we delay the parameter binding. Do parameter binding for minishell now. + // For minishell value of -outoutFormat parameter depends on value of redirectOutput. + // So we delay the parameter binding. Do parameter binding for minishell now. 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."); @@ -1173,7 +1756,7 @@ private bool IsDownstreamOutDefault(Pipe downstreamPipe) // We have the test 'utscript\Engine\TestOutDefaultRedirection.ps1' to check that a user defined // Out-Default function should not cause a native command to be redirected. So here we should only // compare the command name to avoid breaking change. - if (String.Equals(outputProcessor.CommandInfo.Name, "Out-Default", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(outputProcessor.CommandInfo.Name, "Out-Default", StringComparison.OrdinalIgnoreCase)) { // Verify that this isn't an Out-Default added for transcribing if (!outputProcessor.Command.MyInvocation.BoundParameters.ContainsKey("Transcript")) @@ -1187,12 +1770,13 @@ private bool IsDownstreamOutDefault(Pipe downstreamPipe) } /// - /// This method calculates if input and output of the process are redirected + /// 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; @@ -1216,16 +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, @@ -1235,16 +1822,18 @@ 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; } } - //In minishell scenario, if output is redirected - //then error should also be redirected. - if (redirectError == false && redirectOutput == true && _isMiniShell) + // In minishell scenario, if output is redirected + // then error should also be redirected. + if (!redirectError && redirectOutput && _isMiniShell) { redirectError = true; } @@ -1264,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... @@ -1281,8 +1870,11 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr if (_runStandAlone) { - if (null == s_supportScreenScrape) + if (s_supportScreenScrape == null) { +#if UNIX + s_supportScreenScrape = false; +#else try { _startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition; @@ -1294,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; @@ -1307,116 +1900,47 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr } } - private bool ValidateExtension(string path) + // 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) { - // Now check the extension and see if it's one of the ones in pathext +#if UNIX + return Platform.NonWindowsIsExecutable(this.Path); +#else + string myExtension = System.IO.Path.GetExtension(path); - string pathext = (string)LanguagePrimitives.ConvertTo( - this.Command.Context.GetVariableValue(SpecialVariables.PathExtVarPath), - typeof(string), CultureInfo.InvariantCulture); + var pathext = Environment.GetEnvironmentVariable("PATHEXT"); string[] extensionList; - if (String.IsNullOrEmpty(pathext)) + if (string.IsNullOrEmpty(pathext)) { extensionList = new string[] { ".exe", ".com", ".bat", ".cmd" }; } else { - extensionList = pathext.Split(Utils.Separators.Semicolon); + extensionList = pathext.Split(';'); } + foreach (string extension in extensionList) { - if (String.Equals(extension, myExtension, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(extension, myExtension, StringComparison.OrdinalIgnoreCase)) { return true; } } - return false; - } - - #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; + return false; +#endif } - #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. /// @@ -1438,6 +1962,7 @@ private bool IsMiniShell() } } } + return false; } @@ -1451,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; @@ -1467,25 +2004,43 @@ public ProcessOutputHandler(Process process, BlockingCollection DeserializeCliXmlObject(string xml, bool isOut string streamName; object obj = des.Deserialize(out streamName); - //Decide the stream to which data belongs + // Decide the stream to which data belongs MinishellStream stream = MinishellStream.Unknown; if (streamName != null) { stream = StringToMinishellStreamConverter.ToMinishellStream(streamName); } + if (stream == MinishellStream.Unknown) { stream = isOutput ? MinishellStream.Output : MinishellStream.Error; } - //Null is allowed only in output stream + // Null is allowed only in output stream if (stream != MinishellStream.Output && obj == null) { continue; @@ -1618,6 +2174,7 @@ private List DeserializeCliXmlObject(string xml, bool isOut { continue; } + obj = new ErrorRecord(new RemoteException(errorMessage), "NativeCommandError", ErrorCategory.NotSpecified, errorMessage); } @@ -1647,7 +2204,7 @@ private List DeserializeCliXmlObject(string xml, bool isOut stream == MinishellStream.Verbose || stream == MinishellStream.Warning) { - //Convert to string + // Convert to string try { obj = LanguagePrimitives.ConvertTo(obj, typeof(string), CultureInfo.InvariantCulture); @@ -1657,6 +2214,7 @@ private List DeserializeCliXmlObject(string xml, bool isOut continue; } } + result.Add(new ProcessOutputObject(obj, stream)); } } @@ -1693,9 +2251,9 @@ internal class ProcessInputWriter { #region constructor - private InternalCommand _command; + private readonly InternalCommand _command; /// - /// Creates an instance of ProcessInputWriter + /// Creates an instance of ProcessInputWriter. /// internal ProcessInputWriter(InternalCommand command) { @@ -1709,7 +2267,7 @@ internal ProcessInputWriter(InternalCommand command) private Serializer _xmlSerializer; /// - /// Add an object to write to process + /// Add an object to write to process. /// /// internal void Add(object input) @@ -1721,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) @@ -1773,7 +2344,7 @@ private void AddXmlInput(object input) } /// - /// Stream to which input is written + /// Stream to which input is written. /// private StreamWriter _streamWriter; @@ -1783,7 +2354,7 @@ private void AddXmlInput(object input) private NativeCommandIOFormat _inputFormat; /// - /// Start writing input to process + /// Start writing input to process. /// /// /// process to which input is written @@ -1794,14 +2365,15 @@ internal void Start(Process process, NativeCommandIOFormat inputFormat) { Dbg.Assert(process != null, "caller should validate the paramter"); - //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; + // 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 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; @@ -1817,10 +2389,10 @@ internal void Start(Process process, NativeCommandIOFormat inputFormat) } } - bool _stopping = false; + private bool _stopping = false; /// - /// Stop writing input to process + /// Stop writing input to process. /// internal void Stop() { @@ -1859,6 +2431,7 @@ internal void Dispose() // lead to "Broken pipe" exception. // we are ignoring it here } + _streamWriter = null; } } @@ -1867,10 +2440,7 @@ internal void Done() { if (_inputFormat == NativeCommandIOFormat.Xml) { - if (_xmlSerializer != null) - { - _xmlSerializer.Done(); - } + _xmlSerializer?.Done(); } else // Text { @@ -1898,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, Int32 nCmdShow); - - /// - /// Code to allocate a console... - /// - /// true if a console was created... - [DllImport("kernel32.dll", SetLastError = true)] - 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 @@ -1958,94 +2476,59 @@ 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 { /// - /// Initializes a new instance of RemoteException + /// Initializes a new instance of RemoteException. /// public RemoteException() : base() @@ -2082,9 +2565,9 @@ public RemoteException(string message, Exception innerException) /// /// Initializes a new instance of the RemoteException /// with a specified error message, serialized Exception and - /// serialized InvocationInfo + /// serialized InvocationInfo. /// - /// The message that describes the error. + /// The message that describes the error. /// /// serialized exception from remote msh /// @@ -2103,7 +2586,6 @@ PSObject serializedRemoteInvocationInfo _serializedRemoteInvocationInfo = serializedRemoteInvocationInfo; } - #region ISerializable Members /// @@ -2118,20 +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. /// @@ -2147,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 { @@ -2159,7 +2643,7 @@ public PSObject SerializedRemoteInvocationInfo private ErrorRecord _remoteErrorRecord; /// - /// Sets the remote error record associated with this exception + /// Sets the remote error record associated with this exception. /// /// internal void SetRemoteErrorRecord(ErrorRecord remoteError) @@ -2168,7 +2652,7 @@ internal void SetRemoteErrorRecord(ErrorRecord remoteError) } /// - /// ErrorRecord associated with the exception + /// ErrorRecord associated with the exception. /// public override ErrorRecord ErrorRecord { diff --git a/src/System.Management.Automation/engine/NullString.cs b/src/System.Management.Automation/engine/NullString.cs index ef7ccc84b5b..9ac2ebe7c7e 100644 --- a/src/System.Management.Automation/engine/NullString.cs +++ b/src/System.Management.Automation/engine/NullString.cs @@ -1,11 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation.Language { /// - /// This type is introduced to provide a way to pass null into a .NET method that has a string parameter + /// This type is introduced to provide a way to pass null into a .NET method that has a string parameter. /// public class NullString { @@ -26,7 +25,7 @@ public override string ToString() } /// - /// This returns the singleton instance of NullString + /// This returns the singleton instance of NullString. /// public static NullString Value { get; } = new NullString(); @@ -43,4 +42,4 @@ private NullString() #endregion private Constructor } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/ObjectEventRegistrationBase.cs b/src/System.Management.Automation/engine/ObjectEventRegistrationBase.cs index dd6450f3969..3c385a6f7bf 100644 --- a/src/System.Management.Automation/engine/ObjectEventRegistrationBase.cs +++ b/src/System.Management.Automation/engine/ObjectEventRegistrationBase.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; @@ -15,7 +14,7 @@ public abstract class ObjectEventRegistrationBase : PSCmdlet #region parameters /// - /// Parameter for an identifier for this event subscription + /// Parameter for an identifier for this event subscription. /// [Parameter(Position = 100)] public string SourceIdentifier @@ -24,16 +23,17 @@ public string SourceIdentifier { return _sourceIdentifier; } + set { _sourceIdentifier = value; } } - private string _sourceIdentifier = Guid.NewGuid().ToString(); + private string _sourceIdentifier = Guid.NewGuid().ToString(); /// - /// Parameter for any action to be invoked when the event arrives + /// Parameter for any action to be invoked when the event arrives. /// [Parameter(Position = 101)] public ScriptBlock Action @@ -42,15 +42,17 @@ public ScriptBlock Action { return _action; } + set { _action = value; } } + private ScriptBlock _action = null; /// - /// Parameter for additional data to be associated with this event subscription + /// Parameter for additional data to be associated with this event subscription. /// [Parameter] public PSObject MessageData @@ -59,16 +61,18 @@ public PSObject MessageData { return _messageData; } + set { _messageData = value; } } + private PSObject _messageData = null; /// /// Parameter for the flag that determines if this subscription is used to support - /// other subscriptions + /// other subscriptions. /// [Parameter] public SwitchParameter SupportEvent @@ -77,16 +81,18 @@ public SwitchParameter SupportEvent { return _supportEvent; } + set { _supportEvent = value; } } + private SwitchParameter _supportEvent = new SwitchParameter(); /// /// Parameter for the flag that determines whether this - /// subscription will forward its events to the PowerShell client during remote executions + /// subscription will forward its events to the PowerShell client during remote executions. /// [Parameter] public SwitchParameter Forward @@ -95,16 +101,18 @@ public SwitchParameter Forward { return _forward; } + set { _forward = value; } } + private SwitchParameter _forward = new SwitchParameter(); /// /// Parameter to indicate that the subscriber should be auto-unregistered after being triggered for specified times. - /// 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 + /// 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. /// [Parameter] public int MaxTriggerCount @@ -113,36 +121,39 @@ public int MaxTriggerCount { return _maxTriggerCount; } + set { _maxTriggerCount = value <= 0 ? 0 : value; } } + private int _maxTriggerCount = 0; #endregion parameters /// - /// Returns the object that generates events to be monitored + /// Returns the object that generates events to be monitored. /// - protected abstract Object GetSourceObject(); + protected abstract object GetSourceObject(); /// - /// Returns the event name to be monitored on the input object + /// Returns the event name to be monitored on the input object. /// - protected abstract String GetSourceObjectEventName(); + protected abstract string GetSourceObjectEventName(); /// - /// Gets the subscriber generated by this command + /// Gets the subscriber generated by this command. /// protected PSEventSubscriber NewSubscriber { get { return _newSubscriber; } } + private PSEventSubscriber _newSubscriber; /// - /// Check arguments + /// Check arguments. /// protected override void BeginProcessing() { @@ -158,11 +169,11 @@ protected override void BeginProcessing() } /// - /// Subscribe to the event on the object + /// Subscribe to the event on the object. /// protected override void EndProcessing() { - Object inputObject = PSObject.Base(GetSourceObject()); + object inputObject = PSObject.Base(GetSourceObject()); string eventName = GetSourceObjectEventName(); try @@ -175,7 +186,7 @@ protected override void EndProcessing() // Detect if the event identifier already exists ErrorRecord errorRecord = new ErrorRecord( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, EventingResources.SubscriberExists, _sourceIdentifier)), "SUBSCRIBER_EXISTS", @@ -218,4 +229,4 @@ protected override void EndProcessing() } } } -} \ No newline at end of file +} 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 fbfd436bcec..10b2cf1101d 100644 --- a/src/System.Management.Automation/engine/PSClassInfo.cs +++ b/src/System.Management.Automation/engine/PSClassInfo.cs @@ -1,8 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -10,12 +7,12 @@ namespace System.Management.Automation { /// - /// Contains a PS Class information + /// Contains a PS Class information. /// public sealed class PSClassInfo { /// - /// Initializes a new instance of the PSClassInfo class + /// Initializes a new instance of the PSClassInfo class. /// /// Name of the PS Class. internal PSClassInfo(string name) @@ -26,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. @@ -36,7 +33,7 @@ internal PSClassInfo(string name) /// /// Updates members of the class. /// - /// Updated members + /// Updated members. public void UpdateMembers(IList members) { if (members != null) @@ -51,22 +48,20 @@ public void UpdateMembers(IList members) /// /// Gets the help file path for the cmdlet. /// - public string HelpFile { get; internal set; } = String.Empty; + public string HelpFile { get; internal set; } = string.Empty; } - /// - /// Contains a class field information + /// Contains a class field information. /// public sealed class PSClassMemberInfo { /// - /// Initializes a new instance of the PSClassMemberInfo class + /// Initializes a new instance of the PSClassMemberInfo class. /// internal PSClassMemberInfo(string name, string memberType, string defaultValue) { - if (String.IsNullOrEmpty(name)) - throw new ArgumentNullException("name"); + ArgumentException.ThrowIfNullOrEmpty(name); this.Name = name; this.TypeName = memberType; @@ -74,18 +69,18 @@ internal PSClassMemberInfo(string name, string memberType, string defaultValue) } /// - /// Gets or sets name of the member + /// Gets or sets name of the member. /// - public string Name { get; private set; } + public string Name { get; } /// - /// Gets or sets type of the member + /// 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; } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/PSClassSearcher.cs b/src/System.Management.Automation/engine/PSClassSearcher.cs index 90f28ee7223..ce47ae93e16 100644 --- a/src/System.Management.Automation/engine/PSClassSearcher.cs +++ b/src/System.Management.Automation/engine/PSClassSearcher.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Management.Automation.Language; using System.IO; -using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation.Internal; +using System.Management.Automation.Language; + +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { @@ -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 @@ -74,7 +74,7 @@ IEnumerator IEnumerable.GetEnumerator() } /// - /// Get the Enumerator + /// Get the Enumerator. /// /// IEnumerator IEnumerable.GetEnumerator() @@ -97,7 +97,7 @@ public bool MoveNext() } /// - /// Return the current PSClassInfo + /// Return the current PSClassInfo. /// PSClassInfo IEnumerator.Current { @@ -108,7 +108,7 @@ PSClassInfo IEnumerator.Current } /// - /// Return the current PSClassInfo as object + /// Return the current PSClassInfo as object. /// object IEnumerator.Current { @@ -158,7 +158,7 @@ private bool FindTypeByModulePath(WildcardPattern classNameMatcher) { bool matchFound = false; - var moduleList = ModuleUtils.GetDefaultAvailableModuleFiles(false, false, _context); + var moduleList = ModuleUtils.GetDefaultAvailableModuleFiles(isForAutoDiscovery: false, _context); foreach (var modulePath in moduleList) { @@ -167,7 +167,7 @@ private bool FindTypeByModulePath(WildcardPattern classNameMatcher) if (cachedClasses != null) { - //Exact match + // Exact match if (!_useWildCards) { if (cachedClasses.ContainsKey(_className)) @@ -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."); @@ -297,8 +297,7 @@ private PSClassInfo ConvertToClassInfo(PSModuleInfo module, ScriptBlockAst ast, foreach (var member in statement.Members) { - PropertyMemberAst propAst = member as PropertyMemberAst; - if (propAst != null) + if (member is PropertyMemberAst propAst && !propAst.PropertyAttributes.HasFlag(PropertyAttributes.Hidden)) { Dbg.Assert(propAst.Name != null, "PropName cannot be null"); Dbg.Assert(propAst.PropertyType != null, "PropertyType cannot be null"); @@ -319,7 +318,7 @@ private PSClassInfo ConvertToClassInfo(PSModuleInfo module, ScriptBlockAst ast, if (ast.GetHelpContent() != null) mamlHelpFile = ast.GetHelpContent().MamlHelpFile; - if (!String.IsNullOrEmpty(mamlHelpFile)) + if (!string.IsNullOrEmpty(mamlHelpFile)) classInfo.HelpFile = mamlHelpFile; return classInfo; diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index 8861e676c44..419a4cae95f 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -1,19 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; +using System.Collections.Generic; using System.IO; +using System.Management.Automation.Internal; using System.Text; using System.Threading; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System.Management.Automation.Internal; namespace System.Management.Automation.Configuration { - internal enum ConfigScope + /// + /// The scope of the configuration file. + /// + public enum ConfigScope { - // SystemWide configuration applies to all users. - SystemWide = 0, + /// + /// AllUsers configuration applies to all users. + /// + AllUsers = 0, - // CurrentUser configuration applies to the current user. + /// + /// CurrentUser configuration applies to the current user. + /// CurrentUser = 1 } @@ -21,43 +33,72 @@ internal enum ConfigScope /// Reads from and writes to the JSON configuration files. /// The config values were originally stored in the Windows registry. /// + /// + /// The config file access APIs are designed to avoid hitting the disk as much as possible. + /// - For the first read request targeting a config file, the config data is read from the file and then cached as a 'JObject' instance; + /// * the first read request happens very early during the startup of 'pwsh'. + /// - For the subsequent read requests targeting the same config file, they will then work with that 'JObject' instance; + /// - For the write request targeting a config file, the cached config data corresponding to that config file will be refreshed after the write operation is successfully done. + /// + /// To summarize the expected behavior: + /// Once a 'pwsh' process starts up - + /// 1. any changes made to the config file from outside this 'pwsh' process is not guaranteed to be seen by it (most likely won't be seen). + /// 2. any changes to the config file by this 'pwsh' process via the config file access APIs will be seen by it, if it chooses to read those changes afterwards. + /// internal sealed class PowerShellConfig { + private const string ConfigFileName = "powershell.config.json"; + private const string ExecutionPolicyDefaultShellKey = "Microsoft.PowerShell:ExecutionPolicy"; + private const string DisableImplicitWinCompatKey = "DisableImplicitWinCompat"; + private const string WindowsPowerShellCompatibilityModuleDenyListKey = "WindowsPowerShellCompatibilityModuleDenyList"; + private const string WindowsPowerShellCompatibilityNoClobberModuleListKey = "WindowsPowerShellCompatibilityNoClobberModuleList"; + // Provide a singleton - private static readonly PowerShellConfig s_instance = new PowerShellConfig(); - internal static PowerShellConfig Instance => s_instance; + internal static readonly PowerShellConfig Instance = new PowerShellConfig(); // The json file containing system-wide configuration settings. - // When passed as a pwsh command-line option, - // overrides the system wide configuration file. + // When passed as a pwsh command-line option, overrides the system wide configuration file. private string systemWideConfigFile; private string systemWideConfigDirectory; // The json file containing the per-user configuration settings. - private string perUserConfigFile; - private string perUserConfigDirectory; + private readonly string perUserConfigFile; + private readonly string perUserConfigDirectory; - private const string configFileName = "powershell.config.json"; + // Note: JObject and JsonSerializer are thread safe. + // Root Json objects corresponding to the configuration file for 'AllUsers' and 'CurrentUser' respectively. + // They are used as a cache to avoid hitting the disk for every read operation. + private readonly JObject[] configRoots; + private readonly JObject emptyConfig; + private readonly JsonSerializer serializer; /// /// Lock used to enable multiple concurrent readers and singular write locks within a single process. /// TODO: This solution only works for IO from a single process. /// A more robust solution is needed to enable ReaderWriterLockSlim behavior between processes. /// - private ReaderWriterLockSlim fileLock = new ReaderWriterLockSlim(); + private readonly ReaderWriterLockSlim fileLock; private PowerShellConfig() { // Sets the system-wide configuration file. systemWideConfigDirectory = Utils.DefaultPowerShellAppBase; - systemWideConfigFile = Path.Combine(systemWideConfigDirectory, configFileName); + systemWideConfigFile = Path.Combine(systemWideConfigDirectory, ConfigFileName); // Sets the per-user configuration directory - // 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 = Utils.GetUserConfigurationDirectory(); - perUserConfigFile = Path.Combine(perUserConfigDirectory, configFileName); + // 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; + if (!string.IsNullOrEmpty(perUserConfigDirectory)) + { + perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + } + + emptyConfig = new JObject(); + configRoots = new JObject[2]; + serializer = JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, MaxDepth = 10 }); + + fileLock = new ReaderWriterLockSlim(); } private string GetConfigFilePath(ConfigScope scope) @@ -79,6 +120,7 @@ internal void SetSystemConfigFilePath(string value) { throw new FileNotFoundException(value); } + FileInfo info = new FileInfo(value); systemWideConfigFile = info.FullName; systemWideConfigDirectory = info.Directory.FullName; @@ -98,6 +140,7 @@ internal string GetModulePath(ConfigScope scope) { modulePath = Environment.ExpandEnvironmentVariables(modulePath); } + return modulePath; } @@ -113,85 +156,94 @@ internal string GetModulePath(ConfigScope scope) /// TODO: In a single config file, it might be better to nest this. It is unnecessary complexity until a need arises for more nested values. /// /// Whether this is a system-wide or per-user setting. - /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" + /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell". /// The execution policy if found. Null otherwise. internal string GetExecutionPolicy(ConfigScope scope, string shellId) { - string execPolicy = null; - - string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); - string rawExecPolicy = ReadValueFromFile(scope, valueName); - - if (!String.IsNullOrEmpty(rawExecPolicy)) - { - execPolicy = rawExecPolicy; - } - return execPolicy; + string key = GetExecutionPolicySettingKey(shellId); + string execPolicy = ReadValueFromFile(scope, key); + return string.IsNullOrEmpty(execPolicy) ? null : execPolicy; } internal void RemoveExecutionPolicy(ConfigScope scope, string shellId) { - string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); - RemoveValueFromFile(scope, valueName); + string key = GetExecutionPolicySettingKey(shellId); + RemoveValueFromFile(scope, key); } internal void SetExecutionPolicy(ConfigScope scope, string shellId, string executionPolicy) { - // Defaults to system wide. - if (ConfigScope.CurrentUser == scope) + string key = GetExecutionPolicySettingKey(shellId); + WriteValueToFile(scope, key, executionPolicy); + } + + 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() + { + string[] features = ReadValueFromFile(ConfigScope.CurrentUser, "ExperimentalFeatures", Array.Empty()); + + if (features.Length == 0) { - // Exceptions are not caught so that they will propagate to the - // host for display to the user. - // CreateDirectory will succeed if the directory already exists - // so there is no reason to check Directory.Exists(). - Directory.CreateDirectory(perUserConfigDirectory); + features = ReadValueFromFile(ConfigScope.AllUsers, "ExperimentalFeatures", Array.Empty()); } - string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); - WriteValueToFile(scope, valueName, executionPolicy); + + return features; } /// - /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds - /// Proposed value = existing default. Probably "1" - /// - /// Schema: - /// { - /// "ConsolePrompting" : bool - /// } + /// Set the enabled list of experimental features in the config file. /// - /// Whether console prompting should happen. If the value cannot be read it defaults to false. - internal bool GetConsolePrompting() + /// The ConfigScope of the configuration file to update. + /// The name of the experimental feature to change in the configuration. + /// If true, add to configuration; otherwise, remove from configuration. + internal void SetExperimentalFeatures(ConfigScope scope, string featureName, bool setEnabled) { - return ReadValueFromFile(ConfigScope.SystemWide, "ConsolePrompting"); + var features = new List(GetExperimentalFeatures()); + bool containsFeature = features.Contains(featureName); + if (setEnabled && !containsFeature) + { + features.Add(featureName); + WriteValueToFile(scope, "ExperimentalFeatures", features.ToArray()); + } + else if (!setEnabled && containsFeature) + { + features.Remove(featureName); + WriteValueToFile(scope, "ExperimentalFeatures", features.ToArray()); + } } - internal void SetConsolePrompting(bool shouldPrompt) + internal bool IsImplicitWinCompatEnabled() { - WriteValueToFile(ConfigScope.SystemWide, "ConsolePrompting", shouldPrompt); + bool settingValue = ReadValueFromFile(ConfigScope.CurrentUser, DisableImplicitWinCompatKey) + ?? ReadValueFromFile(ConfigScope.AllUsers, DisableImplicitWinCompatKey) + ?? false; + + return !settingValue; } - /// - /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell - /// Proposed value = Existing default. Probably "0" - /// - /// Schema: - /// { - /// "DisablePromptToUpdateHelp" : bool - /// } - /// - /// Boolean indicating whether Update-Help should prompt. If the value cannot be read, it defaults to false. - internal bool GetDisablePromptToUpdateHelp() + internal string[] GetWindowsPowerShellCompatibilityModuleDenyList() { - return ReadValueFromFile(ConfigScope.SystemWide, "DisablePromptToUpdateHelp"); + return ReadValueFromFile(ConfigScope.CurrentUser, WindowsPowerShellCompatibilityModuleDenyListKey) + ?? ReadValueFromFile(ConfigScope.AllUsers, WindowsPowerShellCompatibilityModuleDenyListKey); } - internal void SetDisablePromptToUpdateHelp(bool prompt) + internal string[] GetWindowsPowerShellCompatibilityNoClobberModuleList() { - WriteValueToFile(ConfigScope.SystemWide, "DisablePromptToUpdateHelp", prompt); + return ReadValueFromFile(ConfigScope.CurrentUser, WindowsPowerShellCompatibilityNoClobberModuleListKey) + ?? ReadValueFromFile(ConfigScope.AllUsers, WindowsPowerShellCompatibilityNoClobberModuleListKey); } /// - /// Corresponding settings of the original Group Policies + /// Corresponding settings of the original Group Policies. /// internal PowerShellPolicies GetPowerShellPolicies(ConfigScope scope) { @@ -207,13 +259,14 @@ internal PowerShellPolicies GetPowerShellPolicies(ConfigScope scope) /// internal string GetSysLogIdentity() { - string identity = ReadValueFromFile(ConfigScope.SystemWide, "LogIdentity"); + string identity = ReadValueFromFile(ConfigScope.AllUsers, "LogIdentity"); if (string.IsNullOrEmpty(identity) || identity.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase)) { identity = "powershell"; } + return identity; } @@ -225,7 +278,7 @@ internal string GetSysLogIdentity() /// internal PSLevel GetLogLevel() { - string levelName = ReadValueFromFile(ConfigScope.SystemWide, "LogLevel"); + string levelName = ReadValueFromFile(ConfigScope.AllUsers, "LogLevel"); PSLevel level; if (string.IsNullOrEmpty(levelName) || @@ -234,18 +287,19 @@ internal PSLevel GetLogLevel() { level = PSLevel.Informational; } + return level; } /// /// 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. @@ -255,7 +309,7 @@ internal PSLevel GetLogLevel() /// internal PSChannel GetLogChannels() { - string values = ReadValueFromFile(ConfigScope.SystemWide, "LogChannels"); + string values = ReadValueFromFile(ConfigScope.AllUsers, "LogChannels"); PSChannel result = 0; if (!string.IsNullOrEmpty(values)) @@ -294,7 +348,7 @@ internal PSChannel GetLogChannels() /// internal PSKeyword GetLogKeywords() { - string values = ReadValueFromFile(ConfigScope.SystemWide, "LogKeywords"); + string values = ReadValueFromFile(ConfigScope.AllUsers, "LogKeywords"); PSKeyword result = 0; if (!string.IsNullOrEmpty(values)) @@ -333,38 +387,83 @@ internal PSKeyword GetLogKeywords() /// The ConfigScope of the configuration file to update. /// The string key of the value. /// The default value to return if the key is not present. - /// - private T ReadValueFromFile(ConfigScope scope, string key, T defaultValue = default(T), - Func readImpl = null) + private T ReadValueFromFile(ConfigScope scope, string key, T defaultValue = default) { string fileName = GetConfigFilePath(scope); - if (!File.Exists(fileName)) { return defaultValue; } + if (string.IsNullOrEmpty(fileName)) + { + return defaultValue; + } - // Open file for reading, but allow multiple readers - fileLock.EnterReadLock(); - try + JObject configData = configRoots[(int)scope]; + + if (configData == null) { - using (var streamReader = new StreamReader(fileName)) - using (var jsonReader = new JsonTextReader(streamReader)) + if (File.Exists(fileName)) { - var settings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.None, MaxDepth = 10 }; - var serializer = JsonSerializer.Create(settings); + try + { + // Open file for reading, but allow multiple readers + fileLock.EnterReadLock(); - var configData = serializer.Deserialize(jsonReader); - if (configData != null && configData.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out JToken jToken)) + using var stream = OpenFileStreamWithRetry(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var jsonReader = new JsonTextReader(new StreamReader(stream)); + + configData = serializer.Deserialize(jsonReader) ?? emptyConfig; + } + catch (Exception exc) { - return readImpl != null ? readImpl(jToken, serializer, defaultValue) : jToken.ToObject(serializer); + throw PSTraceSource.NewInvalidOperationException(exc, PSConfigurationStrings.CanNotConfigurationFile, args: fileName); } + finally + { + fileLock.ExitReadLock(); + } + } + else + { + configData = emptyConfig; + } + + // Set the configuration cache. + JObject originalValue = Interlocked.CompareExchange(ref configRoots[(int)scope], configData, null); + if (originalValue != null) + { + configData = originalValue; } } - finally + + if (configData != emptyConfig && configData.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out JToken jToken)) { - fileLock.ExitReadLock(); + return jToken.ToObject(serializer) ?? defaultValue; } return defaultValue; } + private static FileStream OpenFileStreamWithRetry(string fullPath, FileMode mode, FileAccess access, FileShare share) + { + const int MaxTries = 5; + for (int numTries = 0; numTries < MaxTries; numTries++) + { + try + { + return new FileStream(fullPath, mode, access, share); + } + catch (IOException) + { + if (numTries == (MaxTries - 1)) + { + throw; + } + + Thread.Sleep(50); + } + } + + throw new IOException(nameof(OpenFileStreamWithRetry)); + } + /// /// Update a value in the configuration file. /// @@ -372,97 +471,94 @@ internal PSKeyword GetLogKeywords() /// The ConfigScope of the configuration file to update. /// The string key of the value. /// The value to set. - /// Whether the key-value pair should be added to or removed from the file + /// Whether the key-value pair should be added to or removed from the file. private void UpdateValueInFile(ConfigScope scope, string key, T value, bool addValue) { - string fileName = GetConfigFilePath(scope); - fileLock.EnterWriteLock(); try { - // Since multiple properties can be in a single file, replacement - // is required instead of overwrite if a file already exists. - // Handling the read and write operations within a single FileStream - // prevents other processes from reading or writing the file while - // the update is in progress. It also locks out readers during write - // operations. - using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + string fileName = GetConfigFilePath(scope); + fileLock.EnterWriteLock(); + + // Since multiple properties can be in a single file, replacement is required instead of overwrite if a file already exists. + // Handling the read and write operations within a single FileStream prevents other processes from reading or writing the file while + // the update is in progress. It also locks out readers during write operations. + + JObject jsonObject = null; + using FileStream fs = OpenFileStreamWithRetry(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + + // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor. + // The most important parameter here is the last one, which keeps underlying stream open after StreamReader is disposed + // so that it can be reused for the subsequent write operation. + using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) { - JObject jsonObject = null; - - // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor. - // The most important parameter here is the last one, which keeps the StreamReader - // (and FileStream) open during Dispose so that it can be reused for the write - // operation. - using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, true, 1024, true)) - using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + // Safely determines whether there is content to read from the file + bool isReadSuccess = jsonReader.Read(); + if (isReadSuccess) { - // Safely determines whether there is content to read from the file - bool isReadSuccess = jsonReader.Read(); - if (isReadSuccess) - { - // Read the stream into a root JObject for manipulation - jsonObject = (JObject) JToken.ReadFrom(jsonReader); - JProperty propertyToModify = jsonObject.Property(key); + // Read the stream into a root JObject for manipulation + jsonObject = serializer.Deserialize(jsonReader); + JProperty propertyToModify = jsonObject.Property(key); - if (null == propertyToModify) - { - // The property doesn't exist, so add it - if (addValue) - { - jsonObject.Add(new JProperty(key, value)); - } - // else the property doesn't exist so there is nothing to remove - } - // The property exists - else + if (propertyToModify == null) + { + // The property doesn't exist, so add it + if (addValue) { - if (addValue) - { - propertyToModify.Replace(new JProperty(key, value)); - } - else - { - propertyToModify.Remove(); - } + jsonObject.Add(new JProperty(key, value)); } + // else the property doesn't exist so there is nothing to remove } else { - // The file doesn't already exist and we want to write to it - // or it exists with no content. - // A new file will be created that contains only this value. - // If the file doesn't exist and a we don't want to write to it, no - // action is necessary. + // The property exists if (addValue) { - jsonObject = new JObject(new JProperty(key, value)); + propertyToModify.Replace(new JProperty(key, value)); } else { - return; + propertyToModify.Remove(); } } } - - // Reset the stream position to the beginning so that the - // changes to the file can be written to disk - fs.Seek(0, SeekOrigin.Begin); - - // Update the file with new content - using (StreamWriter streamWriter = new StreamWriter(fs)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + else { - // The entire document exists within the root JObject. - // I just need to write that object to produce the document. - jsonObject.WriteTo(jsonWriter); - - // This trims the file if the file shrank. If the file grew, - // it is a no-op. The purpose is to trim extraneous characters - // from the file stream when the resultant JObject is smaller - // than the input JObject. - fs.SetLength(fs.Position); + // The file doesn't already exist and we want to write to it or it exists with no content. + // A new file will be created that contains only this value. + // If the file doesn't exist and a we don't want to write to it, no action is needed. + if (addValue) + { + jsonObject = new JObject(new JProperty(key, value)); + } + else + { + return; + } } } + + // Reset the stream position to the beginning so that the + // changes to the file can be written to disk + fs.Seek(0, SeekOrigin.Begin); + + // Update the file with new content + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + { + // The entire document exists within the root JObject. + // I just need to write that object to produce the document. + jsonObject.WriteTo(jsonWriter); + + // This trims the file if the file shrank. If the file grew, + // it is a no-op. The purpose is to trim extraneous characters + // from the file stream when the resultant JObject is smaller + // than the input JObject. + fs.SetLength(fs.Position); + } + + // Refresh the configuration cache. + Interlocked.Exchange(ref configRoots[(int)scope], jsonObject); } finally { @@ -479,6 +575,11 @@ 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 (scope == ConfigScope.CurrentUser && !Directory.Exists(perUserConfigDirectory)) + { + Directory.CreateDirectory(perUserConfigDirectory); + } + UpdateValueInFile(scope, key, value, true); } @@ -548,77 +649,91 @@ 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; } } internal abstract class PolicyBase { } /// - /// Setting about ScriptExecution + /// Setting about ScriptExecution. /// internal sealed class ScriptExecution : PolicyBase { public string ExecutionPolicy { get; set; } + public bool? EnableScripts { get; set; } } /// - /// Setting about ScriptBlockLogging + /// Setting about ScriptBlockLogging. /// internal sealed class ScriptBlockLogging : PolicyBase { public bool? EnableScriptBlockInvocationLogging { get; set; } + public bool? EnableScriptBlockLogging { get; set; } } /// - /// Setting about ModuleLogging + /// Setting about ModuleLogging. /// internal sealed class ModuleLogging : PolicyBase { public bool? EnableModuleLogging { get; set; } + public string[] ModuleNames { get; set; } } /// - /// Setting about Transcription + /// Setting about Transcription. /// internal sealed class Transcription : PolicyBase { public bool? EnableTranscripting { get; set; } + public bool? EnableInvocationHeader { get; set; } + public string OutputDirectory { get; set; } } /// - /// Setting about UpdatableHelp + /// Setting about UpdatableHelp. /// internal sealed class UpdatableHelp : PolicyBase { public bool? EnableUpdateHelpDefaultSourcePath { get; set; } + public string DefaultSourcePath { get; set; } } /// - /// Setting about ConsoleSessionConfiguration + /// Setting about ConsoleSessionConfiguration. /// internal sealed class ConsoleSessionConfiguration : PolicyBase { public bool? EnableConsoleSessionConfiguration { get; set; } + public string ConsoleSessionConfigurationName { get; set; } } /// - /// Setting about ProtectedEventLogging + /// Setting about ProtectedEventLogging. /// 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 55e30b350e4..eef9819a568 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -1,22 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Diagnostics; -using System.Reflection; using System.Collections; using System.Globalization; -using System.Management.Automation.Internal; using System.Text; using System.Text.RegularExpressions; + using Microsoft.Win32; namespace System.Management.Automation { /// + /// /// Encapsulates $PSVersionTable. + /// + /// + /// Provides a simple interface to retrieve details from the PowerShell version table: + /// + /// PSVersionInfo.PSVersion; + /// + /// The above statement retrieves the PowerShell version. + /// + /// PSVersionInfo.PSEdition; + /// + /// The above statement retrieves the PowerShell edition. + /// /// - internal class PSVersionInfo + public static partial class PSVersionInfo { internal const string PSVersionTableName = "PSVersionTable"; internal const string PSRemotingProtocolVersionName = "PSRemotingProtocolVersion"; @@ -28,7 +38,20 @@ internal class PSVersionInfo internal const string PSOSName = "OS"; internal const string SerializationVersionName = "SerializationVersion"; internal const string WSManStackVersionName = "WSManStackVersion"; - private static PSVersionHashTable s_psVersionTable = null; + + 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. @@ -41,16 +64,19 @@ internal class PSVersionInfo /// For each later release of PowerShell, this constant needs to /// be updated to reflect the right version. /// - private static Version s_psV1Version = new Version(1, 0); - private static Version s_psV2Version = new Version(2, 0); - private static Version s_psV3Version = new Version(3, 0); - private static Version s_psV4Version = new Version(4, 0); - private static Version s_psV5Version = new Version(5, 0); - private static Version s_psV51Version = new Version(5, 1, NTVerpVars.PRODUCTBUILD, NTVerpVars.PRODUCTBUILD_QFE); - private static SemanticVersion s_psV6Version; + 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 + /// A constant to track current PowerShell Edition. /// internal const string PSEditionValue = "Core"; @@ -59,44 +85,20 @@ 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 = "v" + productVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g"); - } - else - { - rawGitCommitId = "v" + mainVersion; - } - - s_psV6Version = new SemanticVersion(mainVersion); - - s_psVersionTable[PSVersionInfo.PSVersionName] = s_psV6Version; - 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_psVersionTable[PSVersionInfo.SerializationVersionName] = new Version(InternalSerializer.DefaultVersion); - s_psVersionTable[PSVersionInfo.PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion; - s_psVersionTable[PSVersionInfo.WSManStackVersionName] = GetWSManStackVersion(); + 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[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.ToString(); + s_psVersionTable[PSOSName] = Runtime.InteropServices.RuntimeInformation.OSDescription; } internal static PSVersionHashTable GetPSVersionTable() @@ -108,7 +110,7 @@ internal static Hashtable GetPSVersionTableForDownLevel() { var result = (Hashtable)s_psVersionTable.Clone(); // Downlevel systems don't support SemanticVersion, but Version is most likely good enough anyway. - result[PSVersionInfo.PSVersionName] = (Version)(SemanticVersion)s_psVersionTable[PSVersionInfo.PSVersionName]; + result[PSVersionInfo.PSVersionName] = s_psVersion; return result; } @@ -152,35 +154,25 @@ private static Version GetWSManStackVersion() #region Programmer APIs - internal static Version PSVersion - { - get - { - return (SemanticVersion)GetPSVersionTable()[PSVersionInfo.PSVersionName]; - } - } - - internal static string GitCommitId - { - get - { - return (string)GetPSVersionTable()[PSGitCommitIdName]; - } - } - - internal static Version[] PSCompatibleVersions + /// + /// Gets the version of PowerShell. + /// + public static Version PSVersion { get { - return (Version[])GetPSVersionTable()[PSCompatibleVersionsName]; + return s_psVersion; } } - internal static string PSEdition + /// + /// Gets the edition of PowerShell. + /// + public static string PSEdition { get { - return (string)GetPSVersionTable()[PSVersionInfo.PSEditionName]; + return PSEditionValue; } } @@ -188,28 +180,11 @@ internal static Version SerializationVersion { get { - return (Version)GetPSVersionTable()[SerializationVersionName]; + return (Version)s_psVersionTable[SerializationVersionName]; } } /// - /// - /// - /// - /// 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"; - } - } - - /// - /// /// /// /// For 3.0 PowerShell, we use "3" as the registry version key only for Engine @@ -226,7 +201,6 @@ internal static string RegistryVersionKey } } - internal static string GetRegistryVersionKeyForSnapinDiscovery(string majorVersion) { int tempMajorVersion = 0; @@ -245,62 +219,35 @@ 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_psV6Version.Major) - { - return version.Minor == s_psV6Version.Minor; - } - if (version.Major == s_psV5Version.Major) - { - return (version.Minor == s_psV5Version.Minor || version.Minor == s_psV51Version.Minor); - } - if (version.Major == s_psV4Version.Major) + if (version is null) { - 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; + return false; } - 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 + internal static SemanticVersion PSCurrentVersion { - 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; } + get { return s_psSemVersion; } } #endregion @@ -313,6 +260,7 @@ internal static SemanticVersion PSV6Version public sealed class PSVersionHashTable : Hashtable, IEnumerable { private static readonly PSVersionTableComparer s_keysComparer = new PSVersionTableComparer(); + internal PSVersionHashTable(IEqualityComparer equalityComparer) : base(equalityComparer) { } @@ -322,7 +270,7 @@ internal PSVersionHashTable(IEqualityComparer equalityComparer) : base(equalityC /// We want see special order: /// 1. PSVersionName /// 2. PSEditionName - /// 3. Remaining properties in alphabetical order + /// 3. Remaining properties in alphabetical order. /// public override ICollection Keys { @@ -334,7 +282,7 @@ public override ICollection Keys } } - private class PSVersionTableComparer : IComparer + private sealed class PSVersionTableComparer : IComparer { public int Compare(object x, object y) { @@ -358,7 +306,7 @@ public int Compare(object x, object y) } else { - return String.Compare(xString, yString, StringComparison.OrdinalIgnoreCase); + return string.Compare(xString, yString, StringComparison.OrdinalIgnoreCase); } } } @@ -377,7 +325,7 @@ IEnumerator IEnumerable.GetEnumerator() } /// - /// An implementation of semantic versioning (http://semver.org) + /// An implementation of semantic versioning (https://semver.org) /// that can be converted to/from . /// /// When converting to , a PSNoteProperty is @@ -387,17 +335,19 @@ 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"; + private string versionString; /// /// Construct a SemanticVersion from a string. /// - /// The version to parse + /// The version to parse. /// /// public SemanticVersion(string version) @@ -414,28 +364,34 @@ public SemanticVersion(string version) /// /// Construct a SemanticVersion. /// - /// The major version - /// The minor version - /// The patch version - /// The pre-release label for the version - /// The build metadata for the version + /// The major version. + /// The minor version. + /// The patch version. + /// The pre-release label for the 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; } @@ -444,10 +400,10 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, /// /// Construct a SemanticVersion. /// - /// The major version - /// The minor version - /// The minor version - /// The label for the version + /// The major version. + /// The minor version. + /// The minor version. + /// The label for the version. /// /// /// If don't match 'LabelRegEx'. @@ -455,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; @@ -471,17 +430,28 @@ public SemanticVersion(int major, int minor, int patch, string label) /// /// Construct a SemanticVersion. /// - /// The major version - /// The minor version - /// The minor version + /// The major version. + /// The minor version. + /// The minor version. /// /// If , , or is less than 0. /// 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; @@ -494,21 +464,21 @@ public SemanticVersion(int major, int minor, int patch) /// /// Construct a SemanticVersion. /// - /// The major version - /// The minor version + /// The major version. + /// The minor version. /// /// If or is less than 0. /// - public SemanticVersion(int major, int minor) : this(major, minor, 0) {} + public SemanticVersion(int major, int minor) : this(major, minor, 0) { } /// /// Construct a SemanticVersion. /// - /// The major version + /// The major version. /// /// If is less than 0. /// - public SemanticVersion(int major) : this(major, 0, 0) {} + public SemanticVersion(int major) : this(major, 0, 0) { } /// /// Construct a from a , @@ -523,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; @@ -535,6 +512,7 @@ public SemanticVersion(Version version) { PreReleaseLabel = preLabelNote.Value as string; } + var buildLabelNote = psobj.Properties[BuildLabelPropertyName]; if (buildLabelNote != null) { @@ -591,27 +569,34 @@ 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; } /// /// Parse and return the result if it is a valid , otherwise throws an exception. /// - /// The string to parse + /// The string to parse. /// /// /// /// 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); @@ -624,7 +609,7 @@ public static SemanticVersion Parse(string version) /// Parse and return true if it is a valid , otherwise return false. /// No exceptions are raised. /// - /// The string to parse + /// The string to parse. /// The return value when the string is a valid public static bool TryParse(string version, out SemanticVersion result) { @@ -653,13 +638,13 @@ private static bool TryParseVersion(string version, ref VersionResult result) } string versionSansLabel = null; - var major=0; - var minor=0; - var patch=0; + var major = 0; + var minor = 0; + var patch = 0; 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('+'); @@ -670,21 +655,21 @@ private static bool TryParseVersion(string version, ref VersionResult result) { // No buildLabel: buildLabel == null // Format is 'major.minor.patch-PreReleaseLabel' - preLabel = version.Substring(dashIndex+1); + preLabel = version.Substring(dashIndex + 1); versionSansLabel = version.Substring(0, dashIndex); } else { // No PreReleaseLabel: preLabel == null // Format is 'major.minor.patch+BuildLabel' - buildLabel = version.Substring(plusIndex+1); + buildLabel = version.Substring(plusIndex + 1); versionSansLabel = version.Substring(0, plusIndex); dashIndex = -1; } } else { - if (dashIndex == -1) + if (plusIndex == -1) { // Here dashIndex == plusIndex == -1 // No preLabel - preLabel == null; @@ -692,18 +677,25 @@ 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' - preLabel = version.Substring(dashIndex+1, plusIndex-dashIndex-1); - buildLabel = version.Substring(plusIndex+1); + preLabel = version.Substring(dashIndex + 1, plusIndex - dashIndex - 1); + buildLabel = version.Substring(plusIndex + 1); versionSansLabel = version.Substring(0, dashIndex); } } - if ((dashIndex != - 1 && String.IsNullOrEmpty(preLabel)) || - (plusIndex != - 1 && String.IsNullOrEmpty(buildLabel)) || - String.IsNullOrEmpty(versionSansLabel)) + if ((dashIndex != -1 && string.IsNullOrEmpty(preLabel)) || + (plusIndex != -1 && string.IsNullOrEmpty(buildLabel)) || + string.IsNullOrEmpty(versionSansLabel)) { // We have dash and no preReleaseLabel or // we have plus and no buildLabel or @@ -738,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; @@ -757,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(); @@ -803,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)); } @@ -814,11 +805,11 @@ public int CompareTo(object version) /// /// Implement . - /// Meets SymVer 2.0 p.11 http://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) @@ -830,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); } @@ -847,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); @@ -862,20 +853,20 @@ public override int GetHashCode() } /// - /// Overloaded == operator + /// Overloaded == operator. /// public static bool operator ==(SemanticVersion v1, SemanticVersion v2) { - if (object.ReferenceEquals(v1, null)) + if (v1 is null) { - return object.ReferenceEquals(v2, null); + return v2 is null; } return v1.Equals(v2); } /// - /// Overloaded != operator + /// Overloaded != operator. /// public static bool operator !=(SemanticVersion v1, SemanticVersion v2) { @@ -883,7 +874,7 @@ public override int GetHashCode() } /// - /// Overloaded < operator + /// Overloaded < operator. /// public static bool operator <(SemanticVersion v1, SemanticVersion v2) { @@ -891,7 +882,7 @@ public override int GetHashCode() } /// - /// Overloaded <= operator + /// Overloaded <= operator. /// public static bool operator <=(SemanticVersion v1, SemanticVersion v2) { @@ -899,7 +890,7 @@ public override int GetHashCode() } /// - /// Overloaded > operator + /// Overloaded > operator. /// public static bool operator >(SemanticVersion v1, SemanticVersion v2) { @@ -907,7 +898,7 @@ public override int GetHashCode() } /// - /// Overloaded >= operator + /// Overloaded >= operator. /// public static bool operator >=(SemanticVersion v1, SemanticVersion v2) { @@ -916,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: @@ -925,8 +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(preLabel2)) { return -1; } + if (string.IsNullOrEmpty(preLabel1)) + { + return string.IsNullOrEmpty(preLabel2) ? 0 : 1; + } + + if (string.IsNullOrEmpty(preLabel2)) + { + return -1; + } var units1 = preLabel1.Split('.'); var units2 = preLabel2.Split('.'); @@ -943,15 +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 (isNumber2) { return 1; } + if (isNumber1) + { + return -1; + } - int result = String.CompareOrdinal(ac, bc); - if (result != 0) { return result; } + if (isNumber2) + { + return 1; + } + + int result = string.CompareOrdinal(ac, bc); + if (result != 0) + { + return result; + } } } @@ -979,7 +990,7 @@ internal void Init(bool canThrow) internal void SetFailure(ParseFailureKind failure) { - SetFailure(failure, String.Empty); + SetFailure(failure, string.Empty); } internal void SetFailure(ParseFailureKind failure, string argument) @@ -1016,8 +1027,10 @@ internal Exception GetVersionParseException() { return e; } + break; } + return PSTraceSource.NewArgumentException("version"); } } diff --git a/src/System.Management.Automation/engine/ParameterBinderBase.cs b/src/System.Management.Automation/engine/ParameterBinderBase.cs index 94b15552f6c..21868b1a28d 100644 --- a/src/System.Management.Automation/engine/ParameterBinderBase.cs +++ b/src/System.Management.Automation/engine/ParameterBinderBase.cs @@ -1,44 +1,44 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; -using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Reflection; -using System.Globalization; -using System.Diagnostics.CodeAnalysis; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// Flags + /// Flags. /// [Flags] internal enum ParameterBindingFlags { /// - /// No flags specified + /// No flags specified. /// None = 0, /// - /// Set when the argument should be converted to the parameter type + /// Set when the argument should be converted to the parameter type. /// ShouldCoerceType = 0x01, /// - /// Set when the argument should not be validated or recorded in BoundParameters + /// Set when the argument should not be validated or recorded in BoundParameters. /// IsDefaultValue = 0x02, /// - /// Set when script blocks can be bound as a script block parameter instead of a normal argument + /// Set when script blocks can be bound as a script block parameter instead of a normal argument. /// DelayBindScriptBlock = 0x04, @@ -58,10 +58,10 @@ 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 PSTraceSource bindingTracer = + internal static readonly PSTraceSource bindingTracer = PSTraceSource.GetTracer( "ParameterBinding", "Traces the process of binding the arguments to the parameters of cmdlets, scripts, and applications.", @@ -75,23 +75,18 @@ internal abstract class ParameterBinderBase /// Constructs the parameter binder with the specified type metadata. The binder is only valid /// for a single instance of a bindable object and only for the duration of a command. /// - /// /// /// The target object that the parameter values will be bound to. /// - /// /// /// The invocation information for the code that is being bound. /// - /// /// /// The context of the currently running engine. /// - /// /// /// The command that the parameter binder is binding to. The command can be null. /// - /// internal ParameterBinderBase( object target, InvocationInfo invocationInfo, @@ -112,24 +107,19 @@ internal ParameterBinderBase( _isTranscribing = context.EngineHostInterface.UI.IsTranscribing; } - /// /// Constructs the parameter binder with the specified type metadata. The binder is only valid /// for a single instance of a bindable object and only for the duration of a command. /// - /// /// /// The invocation information for the code that is being bound. /// - /// /// /// The context of the currently running engine. /// - /// /// /// The command that the parameter binder is binding to. The command can be null. /// - /// internal ParameterBinderBase( InvocationInfo invocationInfo, ExecutionContext context, @@ -182,10 +172,12 @@ 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; /// @@ -194,7 +186,7 @@ internal CommandLineParameters CommandLineParameters internal bool RecordBoundParameters = true; /// - /// Full Qualified ID for the obsolete parameter warning + /// Full Qualified ID for the obsolete parameter warning. /// internal const string FQIDParameterObsolete = "ParameterObsolete"; @@ -202,17 +194,14 @@ internal CommandLineParameters CommandLineParameters /// /// Derived classes must override this method to get the default parameter - /// value so that it can be restored between pipeline input + /// value so that it can be restored between pipeline input. /// - /// /// /// The name of the parameter to get the default value of. /// - /// /// /// The value of the parameter specified by name. /// - /// internal abstract object GetDefaultParameterValue(string name); #endregion Parameter default values @@ -254,10 +243,10 @@ private void ValidatePSTypeName( if (!psTypeNamesOfArgumentValue.Contains(psTypeNameRequestedByParameter, StringComparer.OrdinalIgnoreCase)) { // win8: 228176..The callers know when to ignore and when not to ignore invalid cast exceptions. - PSInvalidCastException e = new PSInvalidCastException(ErrorCategory.InvalidArgument.ToString(), + PSInvalidCastException e = new PSInvalidCastException(nameof(ErrorCategory.InvalidArgument), null, ParameterBinderStrings.MismatchedPSTypeName, - (null != _invocationInfo) && (null != _invocationInfo.MyCommand) ? _invocationInfo.MyCommand.Name : string.Empty, + (_invocationInfo != null) && (_invocationInfo.MyCommand != null) ? _invocationInfo.MyCommand.Name : string.Empty, parameterMetadata.Name, parameterMetadata.Type, parameterValue.GetType(), @@ -304,24 +293,19 @@ private void ValidatePSTypeName( /// parameter, then calls the protected BindParameter method to have /// the derived class do the actual binding. /// - /// /// /// The parameter to be bound. /// - /// /// /// The metadata for the parameter to use in guiding the binding. /// - /// /// /// Flags for type coercion and validation. /// - /// /// /// True if the parameter was successfully bound. False if /// is false and the type does not match the parameter type. /// - /// /// /// The binding algorithm goes as follows: /// 1. The data generation attributes are run @@ -330,11 +314,9 @@ private void ValidatePSTypeName( /// 4. The data is encoded into the bindable object using the /// protected BindParameter method. /// - /// /// /// If or is null. /// - /// /// /// If argument transformation fails. /// or @@ -344,7 +326,6 @@ private void ValidatePSTypeName( /// or /// If the binding to the parameter fails. /// - /// internal virtual bool BindParameter( CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, @@ -356,12 +337,12 @@ internal virtual bool BindParameter( if (parameter == null) { - throw PSTraceSource.NewArgumentNullException("parameter"); + throw PSTraceSource.NewArgumentNullException(nameof(parameter)); } if (parameterMetadata == null) { - throw PSTraceSource.NewArgumentNullException("parameterMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterMetadata)); } using (bindingTracer.TraceScope( @@ -436,7 +417,7 @@ internal virtual bool BindParameter( parameterMetadata.CannotBeNull || dma.TransformNullOptionalParameters))) { - parameterValue = dma.Transform(_engine, parameterValue); + parameterValue = dma.TransformInternal(_engine, parameterValue); } } @@ -458,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); @@ -541,12 +522,13 @@ internal virtual bool BindParameter( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), ParameterBinderStrings.ParameterArgumentValidationError, "ParameterArgumentValidationError", e.Message); throw bindingException; } + s_tracer.WriteLine("Validation attribute on {0} returned {1}.", parameterMetadata.Name, result); } } @@ -570,22 +552,20 @@ internal virtual bool BindParameter( (!isDefaultValue) && spb != null && !usesCmdletBinding) { - string obsoleteWarning = String.Format( + string obsoleteWarning = string.Format( CultureInfo.InvariantCulture, ParameterBinderStrings.UseOfDeprecatedParameterWarning, parameterMetadata.Name, 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 @@ -604,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, @@ -650,7 +630,7 @@ internal virtual bool BindParameter( if (values != null) { var sb = new Text.StringBuilder(256); - var sep = ""; + var sep = string.Empty; foreach (var value in values) { sb.Append(sep); @@ -663,6 +643,7 @@ internal virtual bool BindParameter( break; } } + stringToPrint = sb.ToString(); } else if (parameterValue != null) @@ -673,6 +654,7 @@ internal virtual bool BindParameter( catch (Exception) // Catch-all OK, 3rd party callout { } + if (stringToPrint != null) { cmdRuntime.PipelineProcessor.LogExecutionParameterBinding(this.InvocationInfo, parameter.ParameterName, stringToPrint); @@ -682,33 +664,27 @@ internal virtual bool BindParameter( return result; } - } // BindParameter + } /// /// This method ensures that if the parameter is mandatory, and AllowNull, AllowEmptyString, /// and/or AllowEmptyCollection is not specified, then argument is not null or empty. /// - /// /// /// The argument token. /// - /// /// /// The metadata for the parameter. /// - /// /// /// The type of the argument to validate against. /// - /// /// /// The value that will be bound to the parameter. /// - /// /// /// If true, then elements of collections will be validated against the metadata. /// - /// private void ValidateNullOrEmptyArgument( CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, @@ -734,6 +710,7 @@ private void ValidateNullOrEmptyArgument( "ParameterArgumentValidationErrorNullNotAllowed"); throw bindingException; } + return; } @@ -757,11 +734,12 @@ private void ValidateNullOrEmptyArgument( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), ParameterBinderStrings.ParameterArgumentValidationErrorEmptyStringNotAllowed, "ParameterArgumentValidationErrorEmptyStringNotAllowed"); throw bindingException; } + return; } @@ -792,13 +770,17 @@ 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. if (!isEmpty && !isElementValueType) { - do { + do + { object element = ParserOps.Current(null, ienum); ValidateNullOrEmptyArgument( parameter, @@ -832,7 +814,7 @@ private void ValidateNullOrEmptyArgument( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), resourceString, errorId); throw bindingException; @@ -852,7 +834,7 @@ private bool ShouldContinueUncoercedBind( { return parameterType == null || isDefaultValue || - (!parameterType.GetTypeInfo().IsValueType && + (!parameterType.IsValueType && parameterType != typeof(string)); } @@ -871,7 +853,7 @@ private bool ShouldContinueUncoercedBind( } var psobj = parameterValue as PSObject; - if (psobj != null && !psobj.immediateBaseObjectIsEmpty) + if (psobj != null && !psobj.ImmediateBaseObjectIsEmpty) { // See if the base object is of the same type or // as subclass of the parameter @@ -909,6 +891,7 @@ private bool ShouldContinueUncoercedBind( // type for the parameter. return false; } + parameterValue = encodedValue; return true; } @@ -919,9 +902,10 @@ private bool ShouldContinueUncoercedBind( #endregion Parameter binding /// - /// The invocation information for the code that is being bound + /// The invocation information for the code that is being bound. /// - private InvocationInfo _invocationInfo; + private readonly InvocationInfo _invocationInfo; + internal InvocationInfo InvocationInfo { get @@ -931,9 +915,10 @@ internal InvocationInfo InvocationInfo } /// - /// The context of the currently running engine + /// The context of the currently running engine. /// - private ExecutionContext _context; + private readonly ExecutionContext _context; + internal ExecutionContext Context { get @@ -945,7 +930,8 @@ internal ExecutionContext Context /// /// An instance of InternalCommand that the binder is binding to. /// - private InternalCommand _command; + private readonly InternalCommand _command; + internal InternalCommand Command { get @@ -957,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 @@ -968,42 +954,33 @@ internal InternalCommand Command /// /// Coerces the argument type to the parameter value type as needed. /// - /// /// /// The argument as was specified by the command line. /// - /// /// /// The name of the parameter that the coercion is taking place to bind to. It is /// used only for error reporting. /// - /// /// /// The type to coerce the value to. /// - /// /// /// The information about the collection type, like element type, etc. /// - /// /// /// The current value of the argument. /// - /// /// /// The value of the argument in the type of the parameter. /// - /// /// /// If or is null. /// - /// /// /// If the argument value is missing and the parameter is not a bool or SwitchParameter. /// or /// If the argument value could not be converted to the parameter type. /// - /// private object CoerceTypeAsNeeded( CommandParameterInternal argument, string parameterName, @@ -1013,21 +990,19 @@ private object CoerceTypeAsNeeded( { if (argument == null) { - throw PSTraceSource.NewArgumentNullException("argument"); + throw PSTraceSource.NewArgumentNullException(nameof(argument)); } if (toType == null) { - throw PSTraceSource.NewArgumentNullException("toType"); + throw PSTraceSource.NewArgumentNullException(nameof(toType)); } // 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; using (bindingTracer.TraceScope( @@ -1085,7 +1060,7 @@ private object CoerceTypeAsNeeded( // If we have an PSObject with null base and we are trying to // convert to a string, then we need to use null instead of // calling LanguagePrimitives.ConvertTo as that will return - // String.Empty. + // string.Empty. if (toType == typeof(string) && argumentType == typeof(PSObject)) @@ -1108,7 +1083,7 @@ private object CoerceTypeAsNeeded( // Anything else passed should be reported as an error if (toType == typeof(bool) || toType == typeof(SwitchParameter) || - toType == typeof(Nullable)) + toType == typeof(bool?)) { Type boType = null; if (argumentType == typeof(PSObject)) @@ -1129,7 +1104,6 @@ private object CoerceTypeAsNeeded( boType = argumentType; } - if (boType == typeof(bool)) { if (LanguagePrimitives.IsBooleanType(toType)) @@ -1137,10 +1111,10 @@ private object CoerceTypeAsNeeded( else result = new SwitchParameter((bool)currentValue); } - else if (boType == typeof(Int32)) + else if (boType == typeof(int)) { - if ((Int32)LanguagePrimitives.ConvertTo(currentValue, - typeof(Int32), CultureInfo.InvariantCulture) != 0) + if ((int)LanguagePrimitives.ConvertTo(currentValue, + typeof(int), CultureInfo.InvariantCulture) != 0) { if (LanguagePrimitives.IsBooleanType(toType)) result = ParserOps.BoolToObject(true); @@ -1157,8 +1131,8 @@ private object CoerceTypeAsNeeded( } else if (LanguagePrimitives.IsNumeric(boType.GetTypeCode())) { - Double currentValueAsDouble = (Double)LanguagePrimitives.ConvertTo( - currentValue, typeof(Double), CultureInfo.InvariantCulture); + double currentValueAsDouble = (double)LanguagePrimitives.ConvertTo( + currentValue, typeof(double), CultureInfo.InvariantCulture); if (currentValueAsDouble != 0) { @@ -1180,7 +1154,7 @@ private object CoerceTypeAsNeeded( // Invalid types which cannot be associated with a bool // Since there is a catch block which appropriately // handles this situation we just throw an exception here - //throw new PSInvalidCastException(); + // throw new PSInvalidCastException(); ParameterBindingException pbe = new ParameterBindingException( ErrorCategory.InvalidArgument, @@ -1192,14 +1166,14 @@ private object CoerceTypeAsNeeded( ParameterBinderStrings.CannotConvertArgument, "CannotConvertArgument", boType, - ""); + string.Empty); throw pbe; } + break; } - // NTRAID#Windows OS Bugs-1009284-2004/05/05-JeffJon // Need to handle other collection types here as well @@ -1249,14 +1223,13 @@ private object CoerceTypeAsNeeded( // we don't want to attempt to bind a collection to a scalar unless // the parameter type is Object or PSObject or enum. - TypeInfo toTypeInfo = toType.GetTypeInfo(); if (GetIList(currentValue) != null && - toType != typeof(Object) && + toType != typeof(object) && toType != typeof(PSObject) && toType != typeof(PSListModifier) && - (!toTypeInfo.IsGenericType || toTypeInfo.GetGenericTypeDefinition() != typeof(PSListModifier<>)) && - (!toTypeInfo.IsGenericType || toTypeInfo.GetGenericTypeDefinition() != typeof(FlagsExpression<>)) && - !toTypeInfo.IsEnum) + (!toType.IsGenericType || toType.GetGenericTypeDefinition() != typeof(PSListModifier<>)) && + (!toType.IsGenericType || toType.GetGenericTypeDefinition() != typeof(FlagsExpression<>)) && + !toType.IsEnum) { throw new NotSupportedException(); } @@ -1265,29 +1238,24 @@ private object CoerceTypeAsNeeded( bindingTracer.WriteLine( "CONVERT arg type to param type using LanguagePrimitives.ConvertTo"); - // If we are in constrained language mode and the target command is trusted, - // allow type conversion to the target command's parameter type. - // Don't allow Hashtable-to-Object conversion (PSObject and IDictionary), though, - // as those can lead to property setters that probably aren't expected. - bool changeLanguageModeForTrustedCommand = false; - if (_context.LanguageMode == PSLanguageMode.ConstrainedLanguage) - { - var basedObject = PSObject.Base(currentValue); - var supportsPropertyConversion = basedObject is PSObject; - var supportsIDictionaryConversion = (basedObject != null) && - (typeof(IDictionary).IsAssignableFrom(basedObject.GetType())); - - changeLanguageModeForTrustedCommand = - (this.Command.CommandInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage) && - (!supportsPropertyConversion) && - (!supportsIDictionaryConversion); - } + // If we are in constrained language mode and the target command is trusted, which is often + // the case for C# cmdlets, then we allow type conversion to the target parameter type. + // + // 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 = + currentLanguageMode == PSLanguageMode.ConstrainedLanguage && + this.Command.CommandInfo.DefiningLanguageMode == PSLanguageMode.FullLanguage; + bool oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding; try { if (changeLanguageModeForTrustedCommand) { - _context.LanguageMode = PSLanguageMode.FullLanguage; + Context.LanguageMode = PSLanguageMode.FullLanguage; + Context.LanguageModeTransitionInParameterBinding = true; } result = LanguagePrimitives.ConvertTo(currentValue, toType, CultureInfo.CurrentCulture); @@ -1296,7 +1264,8 @@ private object CoerceTypeAsNeeded( { if (changeLanguageModeForTrustedCommand) { - _context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + Context.LanguageMode = currentLanguageMode; + Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus; } } @@ -1350,9 +1319,16 @@ private object CoerceTypeAsNeeded( throw pbe; } - } // TraceScope + } + + if (result != null) + { + // Set the converted result object untrusted if necessary + ExecutionContext.PropagateInputSource(originalValue, result, Context.LanguageMode); + } + return result; - } // CoerceTypeAsNeeded + } private static bool IsNullParameterValue(object currentValue) { @@ -1364,6 +1340,7 @@ private static bool IsNullParameterValue(object currentValue) { result = true; } + return result; } @@ -1393,7 +1370,7 @@ private object HandleNullParameterForSpecialTypes( null, ParameterBinderStrings.ParameterArgumentValidationErrorNullNotAllowed, "ParameterArgumentValidationErrorNullNotAllowed", - ""); + string.Empty); throw exception; } @@ -1436,45 +1413,36 @@ private object HandleNullParameterForSpecialTypes( /// Takes the current value specified and converts or adds it to /// a collection of the appropriate type. /// - /// /// /// The argument the current value comes from. Used for error reporting. /// - /// /// /// The name of the parameter. /// - /// /// /// The collection type information to which the current value will be /// encoded. /// - /// /// /// The type the current value will be converted to. /// - /// /// /// The value to be encoded. /// - /// /// /// If true, the element will be coerced into the appropriate type /// for the collection. If false, and the element isn't of the appropriate /// type then the out parameter will /// be true. /// - /// /// /// This out parameter will be true if /// is true and the value could not be encoded into the collection because it /// requires coercion to the element type. /// - /// /// /// A collection of the appropriate type containing the specified value. /// - /// /// /// If is a collection and one of its values /// cannot be coerced into the appropriate type. @@ -1482,9 +1450,8 @@ private object HandleNullParameterForSpecialTypes( /// A collection of the appropriate /// 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, @@ -1494,6 +1461,7 @@ private object EncodeCollection( bool coerceElementTypeIfNeeded, out bool coercionRequired) { + object originalValue = currentValue; object result = null; coercionRequired = false; @@ -1502,7 +1470,7 @@ private object EncodeCollection( bindingTracer.WriteLine( "Binding collection parameter {0}: argument type [{1}], parameter type [{2}], collection type {3}, element type [{4}], {5}", parameterName, - (null == currentValue) ? "null" : currentValue.GetType().Name, + (currentValue == null) ? "null" : currentValue.GetType().Name, toType, collectionTypeInformation.ParameterCollectionType, collectionTypeInformation.ElementType, @@ -1547,7 +1515,7 @@ private object EncodeCollection( // If System.Array is the type we are encoding to, then // the element type should be System.Object. - collectionElementType = typeof(System.Object); + collectionElementType = typeof(object); } bindingTracer.WriteLine( @@ -1590,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; @@ -1603,7 +1571,7 @@ private object EncodeCollection( // extract the ICollection::Add(T) method const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; Type elementType = collectionTypeInformation.ElementType; - Diagnostics.Assert(null != elementType, "null ElementType"); + Diagnostics.Assert(elementType != null, "null ElementType"); Exception getMethodError = null; try { @@ -1620,7 +1588,8 @@ private object EncodeCollection( "ArgumentException matching Add(T) for type {0}: {1}", toType.FullName, e.Message); getMethodError = e; } - if (null == addMethod) + + if (addMethod == null) { ParameterBindingException bindingException = new ParameterBindingException( @@ -1633,7 +1602,7 @@ private object EncodeCollection( currentValue.GetType(), ParameterBinderStrings.CannotExtractAddMethod, "CannotExtractAddMethod", - (getMethodError == null) ? "" : getMethodError.Message); + (getMethodError == null) ? string.Empty : getMethodError.Message); throw bindingException; } } @@ -1732,7 +1701,7 @@ private object EncodeCollection( { bindingTracer.WriteLine( "COERCE collection element from type {0} to type {1}", - (null == valueElement) ? "null" : valueElement.GetType().Name, + (valueElement == null) ? "null" : valueElement.GetType().Name, collectionElementType); // Coerce the element to the appropriate type. @@ -1747,7 +1716,7 @@ private object EncodeCollection( null, valueElement); } - else if (null != collectionElementType && null != currentValueElement) + else if (collectionElementType != null && currentValueElement != null) { Type currentValueElementType = currentValueElement.GetType(); Type desiredElementType = collectionElementType; @@ -1757,7 +1726,7 @@ private object EncodeCollection( { bindingTracer.WriteLine( "COERCION REQUIRED: Did not attempt to coerce collection element from type {0} to type {1}", - (null == valueElement) ? "null" : valueElement.GetType().Name, + (valueElement == null) ? "null" : valueElement.GetType().Name, collectionElementType); coercionRequired = true; @@ -1774,7 +1743,7 @@ private object EncodeCollection( { bindingTracer.WriteLine( "Adding element of type {0} to array position {1}", - (null == currentValueElement) ? "null" : currentValueElement.GetType().Name, + (currentValueElement == null) ? "null" : currentValueElement.GetType().Name, arrayIndex); resultAsIList[arrayIndex++] = currentValueElement; } @@ -1782,14 +1751,14 @@ private object EncodeCollection( { bindingTracer.WriteLine( "Adding element of type {0} via IList.Add", - (null == currentValueElement) ? "null" : currentValueElement.GetType().Name); + (currentValueElement == null) ? "null" : currentValueElement.GetType().Name); resultAsIList.Add(currentValueElement); } else { bindingTracer.WriteLine( "Adding element of type {0} via ICollection::Add()", - (null == currentValueElement) ? "null" : currentValueElement.GetType().Name); + (currentValueElement == null) ? "null" : currentValueElement.GetType().Name); addMethod.Invoke(resultCollection, new object[1] { currentValueElement }); } } @@ -1798,7 +1767,7 @@ private object EncodeCollection( // The inner exception to TargetInvocationException // (if present) has a better Message if (error is TargetInvocationException && - null != error.InnerException) + error.InnerException != null) { error = error.InnerException; } @@ -1811,7 +1780,7 @@ private object EncodeCollection( GetErrorExtent(argument), parameterName, toType, - (currentValueElement == null) ? null : currentValueElement.GetType(), + currentValueElement?.GetType(), ParameterBinderStrings.CannotConvertArgument, "CannotConvertArgument", currentValueElement ?? "null", @@ -1831,7 +1800,7 @@ private object EncodeCollection( if (coerceElementTypeIfNeeded) { bindingTracer.WriteLine( - "Coercing scalar arg value to type {1}", + "Coercing scalar arg value to type {0}", collectionElementType); // Coerce the scalar type into the collection @@ -1871,7 +1840,7 @@ private object EncodeCollection( { bindingTracer.WriteLine( "Adding scalar element of type {0} to array position {1}", - (null == currentValue) ? "null" : currentValue.GetType().Name, + (currentValue == null) ? "null" : currentValue.GetType().Name, 0); resultAsIList[0] = currentValue; } @@ -1879,14 +1848,14 @@ private object EncodeCollection( { bindingTracer.WriteLine( "Adding scalar element of type {0} via IList.Add", - (null == currentValue) ? "null" : currentValue.GetType().Name); + (currentValue == null) ? "null" : currentValue.GetType().Name); resultAsIList.Add(currentValue); } else { bindingTracer.WriteLine( "Adding scalar element of type {0} via ICollection::Add()", - (null == currentValue) ? "null" : currentValue.GetType().Name); + (currentValue == null) ? "null" : currentValue.GetType().Name); addMethod.Invoke(resultCollection, new object[1] { currentValue }); } } @@ -1895,7 +1864,7 @@ private object EncodeCollection( // The inner exception to TargetInvocationException // (if present) has a better Message if (error is TargetInvocationException && - null != error.InnerException) + error.InnerException != null) { error = error.InnerException; } @@ -1908,23 +1877,26 @@ private object EncodeCollection( GetErrorExtent(argument), parameterName, toType, - (currentValue == null) ? null : currentValue.GetType(), + currentValue?.GetType(), ParameterBinderStrings.CannotConvertArgument, "CannotConvertArgument", currentValue ?? "null", error.Message); throw bindingException; } - } // (currentValueAsIList == null) + } if (!coercionRequired) { result = resultCollection; + + // Set the converted result object untrusted if necessary + ExecutionContext.PropagateInputSource(originalValue, result, Context.LanguageMode); } } while (false); return result; - } // EncodeCollection + } internal static IList GetIList(object value) { @@ -1939,7 +1911,7 @@ internal static IList GetIList(object value) } return result; - } // GetIList + } protected IScriptExtent GetErrorExtent(CommandParameterInternal cpi) { @@ -1947,7 +1919,7 @@ protected IScriptExtent GetErrorExtent(CommandParameterInternal cpi) if (result == PositionUtilities.EmptyExtent) result = InvocationInfo.ScriptPosition; // Can't use this assertion - we don't have useful positions when invoked via PowerShell API - //Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); + // Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); return result; } @@ -1957,12 +1929,12 @@ protected IScriptExtent GetParameterErrorExtent(CommandParameterInternal cpi) if (result == PositionUtilities.EmptyExtent) result = InvocationInfo.ScriptPosition; // Can't use this assertion - we don't have useful positions when invoked via PowerShell API - //Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); + // Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); return result; } #endregion private helpers - } // ParameterBinderBase + } /// /// Represents an unbound parameter object in the engine. It's similar to @@ -2012,7 +1984,8 @@ 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; } } @@ -2091,5 +2064,4 @@ internal HashSet CopyBoundPositionalParameters() return result; } } -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/engine/ParameterBinderController.cs b/src/System.Management.Automation/engine/ParameterBinderController.cs index d4f78e0eb78..f9781a99435 100644 --- a/src/System.Management.Automation/engine/ParameterBinderController.cs +++ b/src/System.Management.Automation/engine/ParameterBinderController.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -25,7 +24,6 @@ internal abstract class ParameterBinderController /// Constructs a parameter binder controller for the specified command /// in the specified engine context. /// - /// /// /// The invocation information about the code being run. /// @@ -53,14 +51,12 @@ internal ParameterBinderController(InvocationInfo invocationInfo, ExecutionConte /// /// The engine context the command is running in. /// - /// internal ExecutionContext Context { get; } /// /// 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. @@ -71,24 +67,22 @@ internal ParameterBinderController(InvocationInfo invocationInfo, ExecutionConte /// All the metadata associated with any of the parameters that /// are available from the command. /// - /// internal MergedCommandParameterMetadata BindableParameters { get { return _bindableParameters; } } + protected MergedCommandParameterMetadata _bindableParameters = new MergedCommandParameterMetadata(); /// /// A list of the unbound parameters for the command. /// - /// protected List UnboundParameters { get; set; } /// /// A collection of the bound parameters for the command. The collection is /// indexed based on the name of the parameter. /// - /// protected Dictionary BoundParameters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); internal CommandLineParameters CommandLineParameters @@ -97,14 +91,14 @@ internal CommandLineParameters CommandLineParameters } /// - /// Set true if the default parameter binding is in use + /// Set true if the default parameter binding is in use. /// protected bool DefaultParameterBindingInUse { get; set; } = false; // Set true if the default parameter values are applied /// - /// A collection of bound default parameters + /// A collection of bound default parameters. /// protected Collection BoundDefaultParameters { get; } = new Collection(); @@ -122,24 +116,21 @@ internal void ClearUnboundArguments() } /// - /// A collection of the arguments that have been bound + /// A collection of the arguments that have been bound. /// - /// protected Dictionary BoundArguments { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Reparses the unbound arguments using the parameter metadata of the /// specified parameter binder as the parsing guide. /// - /// /// /// If a parameter token is not matched with an argument and its not a bool or /// SwitchParameter. /// Or /// The name of the argument matches more than one parameter. /// - /// - internal void ReparseUnboundArguments() + protected void ReparseUnboundArguments() { Collection result = new Collection(); @@ -230,6 +221,7 @@ internal void ReparseUnboundArguments() throw exception; } + ++index; argument.ParameterName = matchingParameter.Parameter.Name; argument.SetArgumentValue(nextArgument.ArgumentAst, nextArgument.ParameterText); @@ -266,10 +258,38 @@ internal void ReparseUnboundArguments() } UnboundArguments = result; - } // ReparseUnboundArgumentsForBinder + } + + 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, + string argumentName, CommandParameterInternal argument, CompiledCommandParameter matchingParameter) { @@ -283,27 +303,24 @@ private static bool IsSwitchAndSetValue( } return result; - } // EnsureBoolOrSwitchAndSetValue + } /// /// The argument looks like a parameter if it is a string /// and starts with a dash. /// - /// /// /// The argument to check. /// - /// /// /// True if the argument is a string and starts with a dash, /// or false otherwise. /// - /// internal static bool ArgumentLooksLikeParameter(string arg) { bool result = false; - if (!String.IsNullOrEmpty(arg)) + if (!string.IsNullOrEmpty(arg)) { result = arg[0].IsDash(); } @@ -316,15 +333,12 @@ internal static bool ArgumentLooksLikeParameter(string arg) /// based on whether the arguments look like parameters. The CommandParameterInternal instances then /// get added to the specified command processor. /// - /// /// /// The command processor instance to add the reparsed parameters to. /// - /// /// /// The arguments that require reparsing. /// - /// internal static void AddArgumentsToCommandProcessor(CommandProcessorBase commandProcessor, object[] arguments) { if ((arguments != null) && (arguments.Length > 0)) @@ -359,7 +373,7 @@ internal static void AddArgumentsToCommandProcessor(CommandProcessorBase command { param = CommandParameterInternal.CreateParameterWithArgument( /*parameterAst*/null, paramText.Substring(1, colonIndex - 1), paramText, - /*argumentAst*/null, paramText.Substring(colonIndex + 1).Trim(), + /*argumentAst*/null, paramText.AsSpan(colonIndex + 1).Trim().ToString(), false); } else if (argIndex == arguments.Length - 1 || paramText[paramText.Length - 1] != ':') @@ -380,6 +394,7 @@ internal static void AddArgumentsToCommandProcessor(CommandProcessorBase command { param = CommandParameterInternal.CreateArgument(arguments[argIndex]); } + commandProcessor.AddParameter(param); } } @@ -387,22 +402,18 @@ internal static void AddArgumentsToCommandProcessor(CommandProcessorBase command } /// - /// Bind the argument to the specified parameter + /// Bind the argument to the specified parameter. /// - /// /// /// The argument to be bound. /// - /// /// /// The flags for type coercion, validation, and script block binding. /// - /// /// /// True if the parameter was successfully bound. False if does not have the /// flag ParameterBindingFlags.ShouldCoerceType and the type does not match the parameter type. /// - /// /// /// If argument transformation fails. /// or @@ -414,7 +425,6 @@ internal static void AddArgumentsToCommandProcessor(CommandProcessorBase command /// or /// The parameter has already been bound. /// - /// internal virtual bool BindParameter( CommandParameterInternal argument, ParameterBindingFlags flags) @@ -444,13 +454,12 @@ internal virtual bool BindParameter( null, null, ParameterBinderStrings.ParameterAlreadyBound, - "ParameterAlreadyBound"); - + nameof(ParameterBinderStrings.ParameterAlreadyBound)); throw bindingException; } - flags = flags & ~ParameterBindingFlags.DelayBindScriptBlock; + flags &= ~ParameterBindingFlags.DelayBindScriptBlock; result = BindParameter(_currentParameterSetFlag, argument, matchingParameter, flags); } @@ -460,46 +469,39 @@ internal virtual bool BindParameter( /// /// Derived classes need to define the binding of multiple arguments. /// - /// /// /// The arguments to be bound. /// - /// /// /// 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 + /// Bind the argument to the specified parameter. /// - /// /// /// The parameter set used to bind the arguments. /// - /// /// /// The argument to be bound. /// - /// /// /// The metadata for the parameter to bind the argument to. /// - /// /// /// Flags for type coercion and validation of the arguments. /// - /// /// /// True if the parameter was successfully bound. False if /// specifies no type coercion and the type does not match the parameter type. /// - /// /// /// If or is null. /// - /// /// /// If argument transformation fails. /// or @@ -509,7 +511,6 @@ internal virtual bool BindParameter( /// or /// If the binding to the parameter fails. /// - /// internal virtual bool BindParameter( uint parameterSets, CommandParameterInternal argument, @@ -540,40 +541,149 @@ internal virtual bool BindParameter( UnboundParameters.Remove(parameter); BoundParameters.Add(parameter.Parameter.Name, parameter); } + return result; } /// - /// Binds the unbound arguments to positional parameters + /// 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. /// - /// /// /// The unbound arguments to attempt to bind as positional arguments. /// - /// /// /// The current parameter set flags that are valid. /// - /// /// /// The parameter set to use to disambiguate parameters that have the same position /// - /// /// /// Returns the underlying parameter binding exception if any was generated. /// - /// /// /// The remaining arguments that have not been bound. /// - /// /// /// It is assumed that the unboundArguments parameter has already been processed /// for this parameter binder. All named parameters have been paired with their /// values. Any arguments that don't have a name are considered positional and /// will be processed in this method. /// - /// /// /// If multiple parameters were found for the same position in the specified /// parameter set. @@ -635,7 +745,6 @@ out ParameterBindingException outgoingBindingException throw bindingException; } - if (positionalParameterDictionary.Count > 0) { int unboundArgumentsIndex = 0; @@ -757,22 +866,20 @@ out ParameterBindingException outgoingBindingException result = unboundArguments; } } + return result; - } // BindPositionalParameters + } /// /// This method only updates the collections contained in the dictionary, not the dictionary /// itself to contain only the parameters that are in the specified parameter set. /// - /// /// /// The sorted dictionary of positional parameters. /// - /// /// /// Valid parameter sets /// - /// internal static void UpdatePositionalDictionary( SortedDictionary> positionalParameterDictionary, uint validParameterSets) @@ -899,19 +1006,19 @@ out ParameterBindingException bindingException } } } + return result; } - /// - /// Generate elaborated binding exception so that the user will know the default binding might cause the failure + /// Generate elaborated binding exception so that the user will know the default binding might cause the failure. /// /// protected void ThrowElaboratedBindingException(ParameterBindingException pbex) { if (pbex == null) { - throw PSTraceSource.NewArgumentNullException("pbex"); + throw PSTraceSource.NewArgumentNullException(nameof(pbex)); } Diagnostics.Assert(pbex.ErrorRecord != null, "ErrorRecord should not be null in a ParameterBindingException"); @@ -922,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; @@ -941,7 +1048,6 @@ protected void ThrowElaboratedBindingException(ParameterBindingException pbex) throw newBindingException; } - private static CommandParameterInternal GetNextPositionalArgument( List unboundArgumentsCollection, Collection nonPositionalArguments, @@ -962,6 +1068,7 @@ private static CommandParameterInternal GetNextPositionalArgument( result = argument; break; } + nonPositionalArguments.Add(argument); // Now check to see if the next argument needs to be consumed as well. @@ -988,12 +1095,10 @@ private static CommandParameterInternal GetNextPositionalArgument( /// Gets the unbound positional parameters in a sorted dictionary in the order of their /// positions. /// - /// /// /// The sorted dictionary of MergedCompiledCommandParameter metadata with the position /// as the key. /// - /// internal static SortedDictionary> EvaluateUnboundPositionalParameters( ICollection unboundParameters, uint validParameterSetFlag) { @@ -1035,8 +1140,9 @@ internal static SortedDictionary> result, @@ -1067,6 +1173,7 @@ private static void AddNewPosition( positionalCommandParameter = new PositionalCommandParameter(parameter); positionalCommandParameters.Add(parameter, positionalCommandParameter); } + positionalCommandParameter.ParameterSetData.Add(parameterSetData); } else @@ -1112,6 +1219,7 @@ private static bool ContainsPositionalParameterInSet( break; } } + return result; } @@ -1119,7 +1227,6 @@ private static bool ContainsPositionalParameterInSet( /// Keeps track of the parameters that get bound through pipeline input, so that their /// previous values can be restored before the next pipeline input comes. /// - /// internal Collection ParametersBoundThroughPipelineInput { get; } = new Collection(); /// @@ -1181,6 +1288,7 @@ internal void BindUnboundScriptParameterWithDefaultValue(MergedCompiledCommandPa { flags |= ParameterBindingFlags.ShouldCoerceType; } + BindParameter(uint.MaxValue, argument, parameter, flags); } finally @@ -1199,7 +1307,7 @@ protected IScriptExtent GetErrorExtent(CommandParameterInternal cpi) if (result == PositionUtilities.EmptyExtent) result = InvocationInfo.ScriptPosition; // Can't use this assertion - we don't have useful positions when invoked via PowerShell API - //Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); + // Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); return result; } @@ -1209,11 +1317,10 @@ protected IScriptExtent GetParameterErrorExtent(CommandParameterInternal cpi) if (result == PositionUtilities.EmptyExtent) result = InvocationInfo.ScriptPosition; // Can't use this assertion - we don't have useful positions when invoked via PowerShell API - //Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); + // Diagnostics.Assert(result != PositionUtilities.EmptyExtent, "We are missing a valid position somewhere"); return result; } #endregion internal_members } } - diff --git a/src/System.Management.Automation/engine/ParameterInfo.cs b/src/System.Management.Automation/engine/ParameterInfo.cs index e6505370027..d1def1c468f 100644 --- a/src/System.Management.Automation/engine/ParameterInfo.cs +++ b/src/System.Management.Automation/engine/ParameterInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,28 +15,24 @@ public class CommandParameterInfo /// /// Constructs the parameter info using the specified aliases, attributes, and - /// parameter set metadata + /// parameter set metadata. /// - /// /// /// The parameter metadata to retrieve the parameter information from. /// - /// /// /// The parameter set flag to get the parameter information from. /// - /// /// /// If is null. /// - /// internal CommandParameterInfo( CompiledCommandParameter parameter, uint parameterSetFlag) { if (parameter == null) { - throw PSTraceSource.NewArgumentNullException("parameter"); + throw PSTraceSource.NewArgumentNullException(nameof(parameter)); } Name = parameter.Name; @@ -56,7 +51,7 @@ internal CommandParameterInfo( /// /// Gets the name of the parameter. /// - public string Name { get; } = String.Empty; + public string Name { get; } = string.Empty; /// /// Gets the type of the parameter. @@ -66,7 +61,6 @@ internal CommandParameterInfo( /// /// Gets whether or not the parameter is a dynamic parameter. /// - /// /// /// True if the parameter is dynamic, or false otherwise. /// @@ -75,7 +69,6 @@ internal CommandParameterInfo( /// /// Gets whether or not the parameter is mandatory. /// - /// /// /// True if the parameter is mandatory, or false otherwise. /// @@ -106,7 +99,7 @@ internal CommandParameterInfo( /// /// Gets the help message for this parameter. /// - public string HelpMessage { get; private set; } = String.Empty; + public string HelpMessage { get; private set; } = string.Empty; /// /// Gets the aliases by which this parameter can be referenced. @@ -138,7 +131,6 @@ private void SetAttributes(IList attributeMetadata) Attributes = new ReadOnlyCollection(processedAttributes); } - private void SetParameterSetData(ParameterSetSpecificMetadata parameterMetadata) { IsMandatory = parameterMetadata.IsMandatory; @@ -150,6 +142,5 @@ private void SetParameterSetData(ParameterSetSpecificMetadata parameterMetadata) } #endregion private members - } // class CommandParameterInfo -} // namespace System.Management.Automation - + } +} diff --git a/src/System.Management.Automation/engine/ParameterSetInfo.cs b/src/System.Management.Automation/engine/ParameterSetInfo.cs index f34c269f90f..5d81b8553cf 100644 --- a/src/System.Management.Automation/engine/ParameterSetInfo.cs +++ b/src/System.Management.Automation/engine/ParameterSetInfo.cs @@ -1,21 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; + using Microsoft.PowerShell; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// The information about a parameter set and its parameters for a cmdlet + /// The information about a parameter set and its parameters for a cmdlet. /// - /// public class CommandParameterSetInfo { #region ctor @@ -24,31 +24,24 @@ public class CommandParameterSetInfo /// Constructs the parameter set information using the specified parameter name, /// and type metadata. /// - /// /// /// The formal name of the parameter. /// - /// /// /// True if the parameter set is the default parameter set, or false otherwise. /// - /// /// /// The bit that specifies the parameter set in the type metadata. /// - /// /// /// The type metadata about the cmdlet. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// internal CommandParameterSetInfo( string name, bool isDefaultParameterSet, @@ -56,15 +49,15 @@ internal CommandParameterSetInfo( MergedCommandParameterMetadata parameterMetadata) { IsDefault = true; - Name = String.Empty; - if (String.IsNullOrEmpty(name)) + Name = string.Empty; + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (parameterMetadata == null) { - throw PSTraceSource.NewArgumentNullException("parameterMetadata"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterMetadata)); } this.Name = name; @@ -77,14 +70,14 @@ internal CommandParameterSetInfo( #region public members /// - /// Gets the name of the parameter set + /// 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,37 +85,25 @@ internal CommandParameterSetInfo( public ReadOnlyCollection Parameters { get; private set; } /// - /// Gets the synopsis for the cmdlet as a string + /// Gets the synopsis for the cmdlet as a string. /// public override string ToString() - { - return ToString(false); - } - - /// - /// - /// - /// - /// This boolean is used to suppress common workflow parameters (or) display - /// them separately towards the end - /// - /// - internal string ToString(bool isCapabilityWorkflow) { Text.StringBuilder result = new Text.StringBuilder(); - GenerateParametersInDisplayOrder(isCapabilityWorkflow, - parameter => AppendFormatCommandParameterInfo(parameter, ref result), - delegate (string str) - { - if (result.Length > 0) - { - result.Append(" "); - } - result.Append("["); - result.Append(str); - result.Append("]"); - }); + GenerateParametersInDisplayOrder( + parameter => AppendFormatCommandParameterInfo(parameter, result), + (string str) => + { + if (result.Length > 0) + { + result.Append(' '); + } + + result.Append('['); + result.Append(str); + result.Append(']'); + }); return result.ToString(); } @@ -137,14 +118,10 @@ internal string ToString(bool isCapabilityWorkflow) /// to handle /// syntax generation etc. /// - /// - /// This boolean is used to suppress common workflow parameters (or) display - /// them separately towards the end - /// /// /// /// - internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, + internal void GenerateParametersInDisplayOrder( Action parameterAction, Action commonParameterAction) { @@ -184,13 +161,11 @@ internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, sortedPositionalParameters.Add(null); } } + sortedPositionalParameters[parameter.Position] = parameter; } } - // Now convert the sorted positional parameters into a string - List commonWorkflowParameter = new List(); - foreach (CommandParameterInfo parameter in sortedPositionalParameters) { if (parameter == null) @@ -198,14 +173,7 @@ internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, continue; } - if (!Internal.CommonParameters.CommonWorkflowParameters.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase) || !isCapabilityWorkflow) - { - parameterAction(parameter); - } - else - { - commonWorkflowParameter.Add(parameter); - } + parameterAction(parameter); } // Now convert the named mandatory parameters into a string @@ -233,14 +201,7 @@ internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, bool isCommon = Cmdlet.CommonParameters.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase); if (!isCommon) { - if (!Internal.CommonParameters.CommonWorkflowParameters.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase) || !isCapabilityWorkflow) - { - parameterAction(parameter); - } - else - { - commonWorkflowParameter.Add(parameter); - } + parameterAction(parameter); } else { @@ -248,18 +209,6 @@ internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, } } - if (commonWorkflowParameter.Count == Internal.CommonParameters.CommonWorkflowParameters.Length) - { - commonParameterAction(HelpDisplayStrings.CommonWorkflowParameters); - } - else - { - foreach (CommandParameterInfo parameter in commonWorkflowParameter) - { - parameterAction(parameter); - } - } - // If all common parameters are present, group them together if (commonParameters.Count == Cmdlet.CommonParameters.Count) { @@ -279,12 +228,12 @@ internal void GenerateParametersInDisplayOrder(bool isCapabilityWorkflow, #region private members - private static void AppendFormatCommandParameterInfo(CommandParameterInfo parameter, ref Text.StringBuilder result) + private static void AppendFormatCommandParameterInfo(CommandParameterInfo parameter, Text.StringBuilder result) { if (result.Length > 0) { // Add a space between parameters - result.Append(" "); + result.Append(' '); } if (parameter.ParameterType == typeof(SwitchParameter)) @@ -297,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); } } } @@ -335,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); @@ -343,7 +296,7 @@ internal static string GetParameterTypeString(Type type, IEnumerable } // If the type is really an array, but the typename didn't include [], then add it. - if (type.IsArray && (parameterTypeString.IndexOf("[]", StringComparison.OrdinalIgnoreCase) == -1)) + if (type.IsArray && !parameterTypeString.Contains("[]", StringComparison.Ordinal)) { var t = type; while (t.IsArray) @@ -358,6 +311,7 @@ internal static string GetParameterTypeString(Type type, IEnumerable Type parameterType = Nullable.GetUnderlyingType(type) ?? type; parameterTypeString = ToStringCodeMethods.Type(parameterType, true); } + return parameterTypeString; } @@ -387,6 +341,5 @@ private void Initialize(MergedCommandParameterMetadata parameterMetadata, uint p } #endregion private members - } // class CommandParameterSetInfo -} // namespace System.Management.Automation - + } +} diff --git a/src/System.Management.Automation/engine/ParameterSetPromptingData.cs b/src/System.Management.Automation/engine/ParameterSetPromptingData.cs index 1df11b5e591..36e735e675a 100644 --- a/src/System.Management.Automation/engine/ParameterSetPromptingData.cs +++ b/src/System.Management.Automation/engine/ParameterSetPromptingData.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -8,9 +7,8 @@ namespace System.Management.Automation { /// /// This class holds the data for missing mandatory parameters for each parameter set as we - /// are trying to process which parameter set to use based on the missing mandatory parameters + /// are trying to process which parameter set to use based on the missing mandatory parameters. /// - /// internal class ParameterSetPromptingData { internal ParameterSetPromptingData(uint parameterSet, bool isDefaultSet) @@ -20,19 +18,17 @@ internal ParameterSetPromptingData(uint parameterSet, bool isDefaultSet) } /// - /// True if this parameter set represents the default parameter set + /// True if this parameter set represents the default parameter set. /// - /// internal bool IsDefaultSet { get; } /// - /// The parameter set this data represents + /// The parameter set this data represents. /// - /// internal uint ParameterSet { get; } = 0; /// - /// True if the parameter set represents parameters in all the parameter sets + /// True if the parameter set represents parameters in all the parameter sets. /// internal bool IsAllSet { @@ -40,29 +36,27 @@ internal bool IsAllSet } /// - /// Gets the parameters that take pipeline input and are mandatory in this parameter set + /// Gets the parameters that take pipeline input and are mandatory in this parameter set. /// internal Dictionary PipelineableMandatoryParameters { get; } = new Dictionary(); /// - /// Gets the parameters that take pipeline input by value, and are mandatory in this parameter set + /// Gets the parameters that take pipeline input by value, and are mandatory in this parameter set. /// internal Dictionary PipelineableMandatoryByValueParameters { get; } = new Dictionary(); /// - /// Gets the parameters that take pipeline input by property name, and are mandatory in this parameter set + /// Gets the parameters that take pipeline input by property name, and are mandatory in this parameter set. /// internal Dictionary PipelineableMandatoryByPropertyNameParameters { get; } = new Dictionary(); - /// - /// Gets the parameters that do not take pipeline input and are mandatory in this parameter set + /// Gets the parameters that do not take pipeline input and are mandatory in this parameter set. /// internal Dictionary NonpipelineableMandatoryParameters { get; } = new Dictionary(); } } - diff --git a/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs b/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs index 2dde7499cd0..a20b2af2061 100644 --- a/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs +++ b/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -12,20 +11,17 @@ internal class ParameterSetSpecificMetadata /// Constructs an instance of the ParameterSetSpecificMetadata using the instance of the attribute /// that is specified. /// - /// /// /// The attribute to be compiled. /// - /// /// /// If is null. /// - /// internal ParameterSetSpecificMetadata(ParameterAttribute attribute) { if (attribute == null) { - throw PSTraceSource.NewArgumentNullException("attribute"); + throw PSTraceSource.NewArgumentNullException(nameof(attribute)); } _attribute = attribute; @@ -77,7 +73,6 @@ internal ParameterSetSpecificMetadata( /// /// Returns true if the parameter is positional for this parameter set, or false otherwise. /// - /// internal bool IsPositional { get @@ -133,19 +128,16 @@ internal bool ValueFromPipelineByPropertyName /// internal string HelpMessageResourceId { get; } = null; - /// /// Gets or sets the value that tells whether this parameter set - /// data is for the "all" parameter set + /// data is for the "all" parameter set. /// - /// internal bool IsInAllSets { get; set; } /// /// Gets the parameter set flag that represents the parameter set /// that this data is valid for. /// - /// internal uint ParameterSetFlag { get; set; } /// @@ -154,24 +146,20 @@ internal bool ValueFromPipelineByPropertyName /// If that fails and HelpMessage is set, the help info is set to HelpMessage; otherwise, /// the exception that is thrown when loading the resource is thrown. /// If both HelpMessageBaseName and HelpMessageResourceId are not set, the help info is - /// set to HelpMessage + /// set to HelpMessage. /// - /// /// /// Help info about the parameter /// - /// /// /// If the value of the specified resource is not a string and /// HelpMessage is not set. /// - /// /// /// If only one of HelpMessageBaseName and HelpMessageResourceId is set /// OR if no usable resources have been found, and /// there are no neutral culture resources and HelpMessage is not set. /// - /// internal string GetHelpMessage(Cmdlet cmdlet) { string helpInfo = null; @@ -217,12 +205,10 @@ internal string GetHelpMessage(Cmdlet cmdlet) { helpInfo = HelpMessage; } + return helpInfo; } - - private ParameterAttribute _attribute; - } // ParameterSetSpecificMetadata + private readonly ParameterAttribute _attribute; + } } - - diff --git a/src/System.Management.Automation/engine/PathInterfaces.cs b/src/System.Management.Automation/engine/PathInterfaces.cs index c116ac3ea59..1cc9c6c173c 100644 --- a/src/System.Management.Automation/engine/PathInterfaces.cs +++ b/src/System.Management.Automation/engine/PathInterfaces.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -15,40 +15,36 @@ public sealed class PathIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private PathIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // PathInterfaces private + } /// /// Internal constructor for the PathIntrinsics facade. /// - /// /// /// The session for which this is a facade. /// - /// /// /// This is only public for testing purposes. /// - /// /// /// If is null. /// - /// internal PathIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // PathInterfaces internal + } #endregion Constructors @@ -57,7 +53,6 @@ internal PathIntrinsics(SessionStateInternal sessionState) /// /// Gets the current location. /// - /// /// /// If a location has not been set yet. /// @@ -70,25 +65,21 @@ public PathInfo CurrentLocation "The only constructor for this class should always set the sessionState field"); return _sessionState.CurrentLocation; - } // get - } // CurrentLocation + } + } /// - /// Gets the current location for a specific provider + /// Gets the current location for a specific provider. /// - /// /// /// The name of the provider to get the current location for. /// - /// /// /// If is null. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If a current drive cannot be found for the provider /// @@ -101,12 +92,11 @@ public PathInfo CurrentProviderLocation(string providerName) // Parameter validation is done in the session state object return _sessionState.GetNamespaceCurrentLocation(providerName); - } // CurrentProviderLocation + } /// - /// Gets the current location for the file system provider + /// Gets the current location for the file system provider. /// - /// /// /// If a current drive cannot be found for the FileSystem provider /// @@ -119,39 +109,32 @@ public PathInfo CurrentFileSystemLocation "The only constructor for this class should always set the sessionState field"); return CurrentProviderLocation(_sessionState.ExecutionContext.ProviderNames.FileSystem); - } // get - } // CurrentFileSystemLocation + } + } /// /// Changes the current location to the specified path. /// - /// /// /// The path to change the location to. This can be either a drive-relative or provider-relative /// path. It cannot be a provider-internal path. /// - /// /// /// The path of the new current location. /// - /// /// /// If is null. /// - /// /// /// If does not exist, is not a container, or /// resolved to multiple containers. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If refers to a drive that does not exist. /// - /// /// /// If the provider associated with threw an /// exception. @@ -165,42 +148,34 @@ public PathInfo SetLocation(string path) // Parameter validation is done in the session state object return _sessionState.SetLocation(path); - } // SetLocation + } /// /// Changes the current location to the specified path. /// - /// /// /// The path to change the location to. This can be either a drive-relative or provider-relative /// path. It cannot be a provider-internal path. /// - /// /// /// The context under which the command is running. /// - /// /// /// The path of the new current location. /// - /// /// /// If is null. /// - /// /// /// If does not exist, is not a container, or /// resolved to multiple containers. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If refers to a drive that does not exist. /// - /// /// /// If the provider associated with threw an /// exception. @@ -214,51 +189,85 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) // Parameter validation is done in the session state object return _sessionState.SetLocation(path, context); - } // SetLocation + } + + /// + /// Changes the current location to the specified path. + /// + /// + /// The path to change the location to. This can be either a drive-relative or provider-relative + /// path. It cannot be a provider-internal path. + /// + /// + /// The context under which the command is running. + /// + /// + /// Indicates if the path is a literal path. + /// + /// + /// The path of the new current location. + /// + /// + /// If is null. + /// + /// + /// If does not exist, is not a container, or + /// resolved to multiple containers. + /// + /// + /// If refers to a provider that does not exist. + /// + /// + /// If refers to a drive that does not exist. + /// + /// + /// If the provider associated with threw an + /// exception. + /// + internal PathInfo SetLocation(string path, CmdletProviderContext context, bool literalPath) + { + Dbg.Diagnostics.Assert( + _sessionState != null, + "The only constructor for this class should always set the sessionState field"); + + // Parameter validation is done in the session state object + + return _sessionState.SetLocation(path, context, literalPath); + } /// /// Determines if the specified path is the current location or a parent of the current location. /// - /// /// /// A drive or provider-qualified path to be compared against the current location. /// - /// /// /// The context under which the command is running. /// - /// /// /// True if the path is the current location or a parent of the current location. False otherwise. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while @@ -273,12 +282,11 @@ internal bool IsCurrentLocationOrAncestor(string path, CmdletProviderContext con // Parameter validation is done in the session state object return _sessionState.IsCurrentLocationOrAncestor(path, context); - } // IsCurrentLocationOrAncestor + } /// /// Pushes the current location onto the location stack so that it can be retrieved later. /// - /// /// /// The ID of the stack to push the location onto. /// @@ -289,21 +297,18 @@ public void PushCurrentLocation(string stackName) "The only constructor for this class should always set the sessionState field"); _sessionState.PushCurrentLocation(stackName); - } // PushCurrentLocation + } /// /// Gets the location off the top of the location stack. /// - /// /// /// The ID of the stack to pop the location from. If stackName is null or empty /// the default stack is used. /// - /// /// /// The path information for the location that was on the top of the location stack. /// - /// /// /// If the path on the stack does not exist, is not a container, or /// resolved to multiple containers. @@ -313,15 +318,12 @@ public void PushCurrentLocation(string stackName) /// or /// A stack was not found with the specified name. /// - /// /// /// If the path on the stack refers to a provider that does not exist. /// - /// /// /// If the path on the stack refers to a drive that does not exist. /// - /// /// /// If the provider associated with the path on the stack threw an /// exception. @@ -333,12 +335,11 @@ public PathInfo PopLocation(string stackName) "The only constructor for this class should always set the sessionState field"); return _sessionState.PopLocation(stackName); - } // PopLocation + } /// /// Gets the location stack and all the locations on it. /// - /// /// /// The stack ID of the stack to get the stack info for. /// @@ -349,16 +350,14 @@ public PathInfoStack LocationStack(string stackName) "The only constructor for this class should always set the sessionState field"); return _sessionState.LocationStack(stackName); - } // LocationStack + } /// /// Sets the default location stack to that specified by the stack ID. /// - /// /// /// The stack ID of the stack to use as the default location stack. /// - /// /// /// If does not exist as a location stack. /// @@ -375,44 +374,35 @@ public PathInfoStack SetDefaultLocationStack(string stackName) /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more absolute drive or provider qualified paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// 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. /// - /// /// /// If is a provider-qualified path /// and the specified provider does not exist. /// - /// /// /// If is a drive-qualified path and /// the specified drive does not exist. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// /// /// If the provider does not support multiple items. /// - /// /// /// If the home location for the provider is not set and /// starts with a "~". /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -422,49 +412,40 @@ public Collection GetResolvedPSPathFromPSPath(string path) // The parameters will be verified by the path resolver Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedMonadPathsFromMonadPath(path, false, out providerInstance); - } // GetResolvedPSPathFromPSPath + } /// /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more absolute drive or provider qualified paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// characters which will get resolved. /// - /// /// /// The context under which the command is running. /// - /// /// /// An array of Msh paths that resolved from the given path. /// - /// /// /// If or is null. /// - /// /// /// If is a provider-qualified path /// and the specified provider does not exist. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// /// /// If the provider does not support multiple items. /// - /// /// /// If the home location for the provider is not set and /// starts with a "~". /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -476,58 +457,47 @@ internal Collection GetResolvedPSPathFromPSPath( // The parameters will be verified by the path resolver Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedMonadPathsFromMonadPath(path, false, context, out providerInstance); - } // GetResolvedPSPathFromPSPath + } /// /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more provider-internal paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// characters which will get resolved. /// - /// /// /// The provider for which the returned paths should be used. /// - /// /// /// An array of provider-internal paths that resolved from the given path. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when building its path. /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -539,7 +509,7 @@ public Collection GetResolvedProviderPathFromPSPath( // The parameters will be verified by the path resolver Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedProviderPathsFromMonadPath(path, false, out provider, out providerInstance); - } // GetResolvedProviderPathFromPSPath + } internal Collection GetResolvedProviderPathFromPSPath( string path, @@ -549,63 +519,51 @@ internal Collection GetResolvedProviderPathFromPSPath( // The parameters will be verified by the path resolver Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedProviderPathsFromMonadPath(path, allowNonexistingPaths, out provider, out providerInstance); - } // GetResolvedProviderPathFromPSPath + } /// /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more provider-internal paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// characters which will get resolved. /// - /// /// /// The context under which the command is running. /// - /// /// /// The provider for which the returned paths should be used. /// - /// /// /// An array of provider-internal paths that resolved from the given path. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -619,48 +577,39 @@ internal Collection GetResolvedProviderPathFromPSPath( Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedProviderPathsFromMonadPath(path, false, context, out provider, out providerInstance); - } // GetResolvedProviderPathFromPSPath + } /// /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more provider-internal paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// characters which will get resolved. /// - /// /// /// The provider for which the returned paths should be used. /// - /// /// /// An array of provider-internal paths that resolved from the given path. /// - /// /// /// If is null. /// - /// /// /// If references a provider that does not exist. /// - /// /// /// If the references a provider that is not /// a ContainerCmdletProvider. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -672,53 +621,43 @@ public Collection GetResolvedProviderPathFromProviderPath( // The parameters will be verified by the path resolver Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedProviderPathsFromProviderPath(path, false, providerId, out providerInstance); - } // GetResolvedProviderPathFromProviderPath + } /// /// Resolves a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one or more provider-internal paths. /// - /// /// /// The drive or provider qualified path to be resolved. This path may contain wildcard /// characters which will get resolved. /// - /// /// /// The context under which the command is running. /// - /// /// /// The provider for which the returned paths should be used. /// - /// /// /// An array of provider-internal paths that resolved from the given path. /// - /// /// /// If , , or /// is null. /// - /// /// /// If references a provider that does not exist. /// - /// /// /// If the references a provider that is not /// a ContainerCmdletProvider. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If does not contain wildcard characters and /// could not be found. @@ -732,49 +671,40 @@ internal Collection GetResolvedProviderPathFromProviderPath( Provider.CmdletProvider providerInstance = null; return PathResolver.GetGlobbedProviderPathsFromProviderPath(path, false, providerId, context, out providerInstance); - } // GetResolvedProviderPathFromProviderPath + } /// /// Converts a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one a provider-internal path still containing the wildcard characters. /// - /// /// /// The drive or provider qualified path to be converted. This path may contain wildcard /// characters which will not get resolved. /// - /// /// /// A provider-internal path that does not have the wildcard characters resolved. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception. @@ -784,58 +714,47 @@ public string GetUnresolvedProviderPathFromPSPath(string path) // The parameters will be verified by the path resolver return PathResolver.GetProviderPath(path); - } // GetUnresolvedProviderPathFromPSPath + } /// /// Converts a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one a provider-internal path still containing the wildcard characters. /// - /// /// /// The drive or provider qualified path to be converted. This path may contain wildcard /// characters which will not get resolved. /// - /// /// /// 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. /// - /// /// /// A provider-internal path that does not have the wildcard characters resolved. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while @@ -855,61 +774,49 @@ public string GetUnresolvedProviderPathFromPSPath( context.ThrowFirstErrorOrDoNothing(); return result; - } // GetUnresolvedProviderPathFromPSPath + } /// /// Converts a drive or provider qualified absolute or relative path that may contain /// wildcard characters into one a provider-internal path still containing the wildcard characters. /// - /// /// /// The drive or provider qualified path to be converted. This path may contain wildcard /// characters which will not get resolved. /// - /// /// /// The context under which this command is running. /// - /// /// /// 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. /// - /// /// /// A provider-internal path that does not have the wildcard characters resolved. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while @@ -924,25 +831,21 @@ internal string GetUnresolvedProviderPathFromPSPath( // The parameters will be verified by the path resolver return PathResolver.GetProviderPath(path, context, out provider, out drive); - } // GetUnresolvedProviderPathFromPSPath #endregion Public methods + } /// - /// 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. /// - /// /// /// True if the specified path is provider-qualified, false otherwise. /// - /// /// /// A provider-qualified path is a path in the following form: /// providerId::provider-internal-path /// - /// /// /// If is null. /// @@ -951,32 +854,27 @@ public bool IsProviderQualified(string path) // The parameters will be verified by the path resolver return LocationGlobber.IsProviderQualifiedPath(path); - } // IsProviderQualifiedPath + } /// /// Determines if the given path is a drive-qualified absolute 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. /// - /// /// /// A path is an absolute drive-qualified path if it has the following /// form: /// drive-name:drive-relative-path /// - /// /// /// If is null. /// @@ -985,43 +883,35 @@ public bool IsPSAbsolute(string path, out string driveName) // The parameters will be verified by the path resolver return PathResolver.IsAbsolutePath(path, out driveName); - } // IsPSAbsolutePath + } #region Combine /// /// Combines two strings with a provider specific path separator. /// - /// /// /// The parent path to be joined with the child. /// - /// /// /// The child path to be joined with the parent. /// - /// /// /// The combined path of the parent and child with the provider /// specific path separator between them. /// - /// /// /// If is null. /// - /// /// /// If both and is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1034,45 +924,36 @@ public string Combine(string parent, string child) // Parameter validation is done in the session state object return _sessionState.MakePath(parent, child); - } // Combine + } /// /// Combines two strings with a provider specific path separator. /// - /// /// /// The parent path to be joined with the child. /// - /// /// /// The child path to be joined with the parent. /// - /// /// /// The context under which this command is running. /// - /// /// /// The combined path of the parent and child with the provider /// specific path separator between them. /// - /// /// /// If is null. /// - /// /// /// If both and is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1085,7 +966,7 @@ internal string Combine(string parent, string child, CmdletProviderContext conte // Parameter validation is done in the session state object return _sessionState.MakePath(parent, child, context); - } // Combine + } #endregion Combine @@ -1094,31 +975,24 @@ internal string Combine(string parent, string child, CmdletProviderContext conte /// /// Gets the parent path of the specified path. /// - /// /// /// The path to get the parent path from. /// - /// /// /// If the root is specified the path returned will not be any higher than the root. /// - /// /// /// The parent path of the specified path. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1131,40 +1005,32 @@ public string ParseParent(string path, string root) // Parameter validation is done in the session state object return _sessionState.GetParentPath(path, root); - } // GetParentPath + } /// /// Gets the parent path of the specified path. /// - /// /// /// The path to get the parent path from. /// - /// /// /// If the root is specified the path returned will not be any higher than the root. /// - /// /// /// The context under which the command is running. /// - /// /// /// The parent path of the specified path. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1180,47 +1046,37 @@ internal string ParseParent( // Parameter validation is done in the session state object return _sessionState.GetParentPath(path, root, context, false); - } // GetParentPath - + } /// /// Gets the parent path of the specified path. /// Allow to use FileSystem as the default provider when the /// given path is drive-qualified and the drive cannot be found. /// - /// /// /// The path to get the parent path from. /// - /// /// /// If the root is specified the path returned will not be any higher than the root. /// - /// /// /// The context under which the command is running. /// - /// /// /// to use default provider when needed. /// - /// /// /// The parent path of the specified path. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1246,32 +1102,25 @@ internal string ParseParent( /// /// Gets the child name of the specified path. /// - /// /// /// The path to get the child name from. /// - /// /// /// The last element of the path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1284,41 +1133,33 @@ public string ParseChildName(string path) // Parameter validation is done in the session state object return _sessionState.GetChildName(path); - } // ParseChildName + } /// /// Gets the child name of the specified path. /// - /// /// /// The path to get the child name from. /// - /// /// /// The context under which the command is running. /// - /// /// /// The last element of the path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1333,48 +1174,38 @@ internal string ParseChildName( // Parameter validation is done in the session state object return _sessionState.GetChildName(path, context, false); - } // ParseChildName - + } /// /// Gets the child name of the specified path. /// Allow to use FileSystem as the default provider when the /// given path is drive-qualified and the drive cannot be found. /// - /// /// /// The path to get the child name from. /// - /// /// /// The context under which the command is running. /// - /// /// /// to use default provider when needed. /// - /// /// /// The last element of the path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1390,7 +1221,7 @@ internal string ParseChildName( // Parameter validation is done in the session state object return _sessionState.GetChildName(path, context, useDefaultProvider); - } // ParseChildName + } #endregion ParseChildName @@ -1400,32 +1231,25 @@ internal string ParseChildName( /// Normalizes the path that was passed in and returns the normalized path /// 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. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// A normalized path that is relative to the basePath that was passed. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1438,42 +1262,34 @@ public string NormalizeRelativePath(string path, string basePath) // Parameter validation is done in the session state object return _sessionState.NormalizeRelativePath(path, basePath); - } // NormalizeRelativePath + } /// /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// An MSH path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// The context under which the command is running. /// - /// /// /// A normalized path that is relative to the basePath that was passed. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// @@ -1489,41 +1305,34 @@ internal string NormalizeRelativePath( // Parameter validation is done in the session state object return _sessionState.NormalizeRelativePath(path, basePath, context); - } // NormalizeRelativePath + } #endregion 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. /// - /// /// /// true if the object specified by path is syntactically and semantically valid, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1536,41 +1345,33 @@ public bool IsValid(string path) // Parameter validation is done in the session state object return _sessionState.IsValidPath(path); - } // IsValid + } /// /// Determines if the MSH path is a syntactically and semantically valid path for the provider. /// - /// /// /// The path to validate. /// - /// /// /// The context under which the call is being made. /// - /// /// /// true if the object specified by path is syntactically and semantically valid, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1585,7 +1386,7 @@ internal bool IsValid( // Parameter validation is done in the session state object return _sessionState.IsValidPath(path, context); - } // IsValid + } #endregion IsValid @@ -1601,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); - } // get - } // PathResolver + return _pathResolver ??= _sessionState.ExecutionContext.LocationGlobber; + } + } private LocationGlobber _pathResolver; - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data - } // PathIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/Pipe.cs b/src/System.Management.Automation/engine/Pipe.cs index 340adb41bad..d43a0f96a5d 100644 --- a/src/System.Management.Automation/engine/Pipe.cs +++ b/src/System.Management.Automation/engine/Pipe.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -17,7 +16,7 @@ internal enum VariableStreamKind Error, Warning, Information - }; + } /// /// Pipe provides a way to stitch two commands. @@ -29,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... @@ -41,13 +40,18 @@ internal class Pipe /// internal CommandProcessorBase DownstreamCmdlet { - get { return _downstreamCmdlet; } + get + { + return _downstreamCmdlet; + } + set { Diagnostics.Assert(_resultList == null, "Tried to set downstream cmdlet when _resultList not null"); _downstreamCmdlet = value; } } + private CommandProcessorBase _downstreamCmdlet; /// @@ -75,17 +79,22 @@ internal CommandProcessorBase DownstreamCmdlet /// internal PipelineWriter ExternalWriter { - get { return _externalWriter; } + get + { + return _externalWriter; + } + set { Diagnostics.Assert(_resultList == null, "Tried to set Pipe ExternalWriter when resultList not null"); _externalWriter = value; } } + private PipelineWriter _externalWriter; /// - /// for diagnostic purposes + /// For diagnostic purposes. /// /// public override string ToString() @@ -96,22 +105,34 @@ public override string ToString() } /// - /// OutBufferCount configures the number of objects to buffer before calling the downstream Cmdlet + /// OutBufferCount configures the number of objects to buffer before calling the downstream Cmdlet. /// 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 { _isRedirected = true; _nullPipe = value; } } + private bool _nullPipe; /// @@ -148,6 +169,7 @@ internal bool IsRedirected { get { return _downstreamCmdlet != null || _isRedirected; } } + private bool _isRedirected; /// @@ -211,31 +233,23 @@ 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; } @@ -289,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) { @@ -303,7 +317,7 @@ private void CopyVariableToTempPipe(VariableStreamKind streamKind, List v #region ctor /// - /// Default constructor - Creates the object queue + /// Default constructor - Creates the object queue. /// /// /// The initial Queue capacity is 1, but it will grow automatically. @@ -314,7 +328,7 @@ internal Pipe() } /// - /// This overload causes output to be written into a List + /// This overload causes output to be written into a List. /// /// internal Pipe(List resultList) @@ -323,27 +337,29 @@ internal Pipe(List resultList) _isRedirected = true; _resultList = resultList; } + private readonly List _resultList; /// /// This overload causes output to be /// written onto an Collection[PSObject] which is more useful - /// in many circumstances than arraylist + /// in many circumstances than arraylist. /// - /// The collection to write into + /// The collection to write into. internal Pipe(System.Collections.ObjectModel.Collection resultCollection) { Diagnostics.Assert(resultCollection != null, "resultCollection cannot be null"); _isRedirected = true; _resultCollection = resultCollection; } - private System.Collections.ObjectModel.Collection _resultCollection; + + private readonly System.Collections.ObjectModel.Collection _resultCollection; /// /// This pipe writes into another pipeline processor allowing /// pipelines to be chained together... /// - /// The execution context object for this engine instance + /// The execution context object for this engine instance. /// The pipeline to write into... internal Pipe(ExecutionContext context, PipelineProcessor outputPipeline) { @@ -367,7 +383,8 @@ internal Pipe(IEnumerator enumeratorToProcess) // assume that there is some stuff to read _enumeratorToProcessIsEmpty = false; } - private IEnumerator _enumeratorToProcess; + + private readonly IEnumerator _enumeratorToProcess; private bool _enumeratorToProcessIsEmpty; #endregion ctor @@ -376,7 +393,7 @@ internal Pipe(IEnumerator enumeratorToProcess) /// Writes an object to the pipe. This could recursively call to the /// downstream cmdlet, or write the object to the external output. /// - /// The object to add to the pipe + /// The object to add to the pipe. /// /// AutomationNull.Value is ignored /// @@ -432,7 +449,7 @@ private void AddToPipe(object obj) { _resultList.Add(obj); } - else if (null != _externalWriter) + else if (_externalWriter != null) { _externalWriter.Write(obj); } @@ -441,14 +458,13 @@ private void AddToPipe(object obj) ObjectQueue.Enqueue(obj); // This is the "streamlet" recursive call - if (null != _downstreamCmdlet && ObjectQueue.Count > OutBufferCount) + if (_downstreamCmdlet != null && ObjectQueue.Count > OutBufferCount) { _downstreamCmdlet.DoExecute(); } } } - /// /// Writes a set of objects to the pipe. This could recursively /// call to the downstream cmdlet, or write the objects to the @@ -498,13 +514,13 @@ 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(); } } - if (null != _externalWriter) + if (_externalWriter != null) return; // If there are objects waiting for the downstream command @@ -513,7 +529,7 @@ internal void AddItems(object objects) { _downstreamCmdlet.DoExecute(); } - } // internal void AddItems(object objects) + } /// /// Returns an object from the pipe. If pipe is empty returns null. @@ -531,17 +547,30 @@ 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 (null != ExternalReader) + else if (ExternalReader != null) { try { @@ -555,6 +584,7 @@ internal object Retrieve() // again if it already reported completion. ExternalReader = null; } + return o; } catch (PipelineClosedException) @@ -573,18 +603,14 @@ 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 /// not block on ExternalInput, and it does not modify the contents of /// the pipe. /// - /// possibly empty array of objects, but not null + /// Possibly empty array of objects, but not null. internal object[] ToArray() { if (ObjectQueue == null || ObjectQueue.Count == 0) @@ -593,4 +619,4 @@ internal object[] ToArray() return ObjectQueue.ToArray(); } } -} // namespace System.Management.Automation.Internal +} diff --git a/src/System.Management.Automation/engine/PositionalCommandParameter.cs b/src/System.Management.Automation/engine/PositionalCommandParameter.cs index 0d8261e24dc..3a2b4688ab4 100644 --- a/src/System.Management.Automation/engine/PositionalCommandParameter.cs +++ b/src/System.Management.Automation/engine/PositionalCommandParameter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; @@ -12,9 +11,8 @@ internal class PositionalCommandParameter /// /// Constructs a container for the merged parameter metadata and - /// parameter set specific metadata for a positional parameter + /// parameter set specific metadata for a positional parameter. /// - /// internal PositionalCommandParameter(MergedCompiledCommandParameter parameter) { Parameter = parameter; @@ -27,4 +25,3 @@ internal PositionalCommandParameter(MergedCompiledCommandParameter parameter) internal Collection ParameterSetData { get; } = new Collection(); } } - diff --git a/src/System.Management.Automation/engine/PowerShellStreamType.cs b/src/System.Management.Automation/engine/PowerShellStreamType.cs new file mode 100644 index 00000000000..6afbb79be51 --- /dev/null +++ b/src/System.Management.Automation/engine/PowerShellStreamType.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace System.Management.Automation +{ + /// + /// Enumeration of the possible PowerShell stream types. + /// This enumeration is obsolete. + /// + /// + /// This enumeration is a public type formerly used in PowerShell Workflow, + /// but kept due to its generic name and public accessibility. + /// It is not used by any other PowerShell API, and is now obsolete + /// and should not be used if possible. + /// + [Obsolete("This enum type was used only in PowerShell Workflow and is now obsolete.", error: true)] + public enum PowerShellStreamType + { + /// + /// PSObject. + /// + Input = 0, + + /// + /// PSObject. + /// + Output = 1, + + /// + /// ErrorRecord. + /// + Error = 2, + + /// + /// WarningRecord. + /// + Warning = 3, + + /// + /// VerboseRecord. + /// + Verbose = 4, + + /// + /// DebugRecord. + /// + Debug = 5, + + /// + /// ProgressRecord. + /// + Progress = 6, + + /// + /// InformationRecord. + /// + Information = 7 + } +} diff --git a/src/System.Management.Automation/engine/ProcessCodeMethods.cs b/src/System.Management.Automation/engine/ProcessCodeMethods.cs index 3349a1fcdfc..68d47fbec71 100644 --- a/src/System.Management.Automation/engine/ProcessCodeMethods.cs +++ b/src/System.Management.Automation/engine/ProcessCodeMethods.cs @@ -1,15 +1,19 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; using System.Diagnostics; using System.Management.Automation; using System.Runtime.InteropServices; -namespace Microsoft.PowerShell { +namespace Microsoft.PowerShell +{ /// - /// Helper functions for process info + /// Helper functions for process info. /// public static class ProcessCodeMethods { - const int InvalidProcessId = -1; + private const int InvalidProcessId = -1; internal static Process GetParent(this Process process) { @@ -20,6 +24,7 @@ internal static Process GetParent(this Process process) { return null; } + var candidate = Process.GetProcessById(pid); // if the candidate was started later than process, the pid has been recycled @@ -32,10 +37,10 @@ internal static Process GetParent(this Process process) } /// - /// CodeMethod for getting the parent process of a process + /// CodeMethod for getting the parent process of a process. /// /// - /// the parent process, or null if the parent is no longer running + /// The parent process, or null if the parent is no longer running. public static object GetParentProcess(PSObject obj) { var process = PSObject.Base(obj) as Process; @@ -43,10 +48,10 @@ public static object GetParentProcess(PSObject obj) } /// - /// Returns the parent id of a process or -1 if it fails + /// Returns the parent id of a process or -1 if it fails. /// /// - /// the pid of the parent process + /// The pid of the parent process. #if UNIX internal static int GetParentPid(Process process) { @@ -56,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)] - 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)] - 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 630407434bb..b3139dc8e6e 100644 --- a/src/System.Management.Automation/engine/ProgressRecord.cs +++ b/src/System.Management.Automation/engine/ProgressRecord.cs @@ -1,53 +1,39 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// /// Defines a data structure used to represent the status of an ongoing operation at a point in time. - /// /// /// - /// /// ProgressRecords are passed to , /// which, according to user preference, forwards that information on to the host for rendering to the user. - /// /// /// - - [DataContract()] + [DataContract] public class ProgressRecord { #region Public API /// - /// /// Initializes a new instance of the ProgressRecord class and defines the activity Id, /// activity description, and status description. - /// /// /// - /// /// A unique numeric key that identifies the activity to which this record applies. - /// /// /// - /// /// A description of the activity for which progress is being reported. - /// /// /// - /// /// A description of the status of the activity. - /// /// - public ProgressRecord(int activityId, string activity, string statusDescription) { @@ -55,15 +41,17 @@ class ProgressRecord { // negative Ids are reserved to indicate "no id" for parent Ids. - throw PSTraceSource.NewArgumentOutOfRangeException("activityId", activityId, ProgressRecordStrings.ArgMayNotBeNegative, "activityId"); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(activityId), activityId, ProgressRecordStrings.ArgMayNotBeNegative, "activityId"); } - if (String.IsNullOrEmpty(activity)) + + if (string.IsNullOrEmpty(activity)) { - throw PSTraceSource.NewArgumentException("activity", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "activity"); + throw PSTraceSource.NewArgumentException(nameof(activity), ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "activity"); } - if (String.IsNullOrEmpty(statusDescription)) + + if (string.IsNullOrEmpty(statusDescription)) { - throw PSTraceSource.NewArgumentException("activity", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "statusDescription"); + throw PSTraceSource.NewArgumentException(nameof(activity), ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "statusDescription"); } this.id = activityId; @@ -71,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) /// @@ -88,12 +95,9 @@ 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 @@ -104,15 +108,10 @@ internal ProgressRecord(ProgressRecord other) } } - - /// - /// /// Gets and sets the Id of the activity for which this record is a subordinate. - /// /// /// - /// /// Used to allow chaining of progress records (such as when one installation invokes a child installation). UI: /// normally not directly visible except as already displayed as its own activity. Usually a sub-activity will be /// positioned below and to the right of its parent. @@ -120,13 +119,10 @@ internal ProgressRecord(ProgressRecord other) /// A negative value (the default) indicates that the activity is not a subordinate. /// /// May not be the same as ActivityId. - /// /// - /// /// - public int ParentActivityId @@ -135,30 +131,25 @@ internal ProgressRecord(ProgressRecord other) { return parentId; } + set { if (value == ActivityId) { throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ParentActivityIdCantBeActivityId); } + parentId = value; } } - - /// - /// /// Gets and sets the description of the activity for which progress is being reported. - /// /// /// - /// /// 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 @@ -167,24 +158,21 @@ internal ProgressRecord(ProgressRecord other) { return activity; } + set { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "value"); } + activity = value; } } - - /// - /// /// 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 @@ -193,26 +181,23 @@ internal ProgressRecord(ProgressRecord other) { return status; } + set { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "value"); } + status = value; } } - - /// - /// /// Gets and sets the current operation of the many required to accomplish the activity (such as "copying foo.txt"). Normally displayed /// 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 @@ -221,6 +206,7 @@ internal ProgressRecord(ProgressRecord other) { return currentOperation; } + set { // null or empty string is allowed @@ -229,15 +215,10 @@ 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 @@ -246,6 +227,7 @@ internal ProgressRecord(ProgressRecord other) { return percent; } + set { // negative values are allowed @@ -261,22 +243,15 @@ internal ProgressRecord(ProgressRecord other) } } - - /// - /// /// Gets and sets the estimate of time remaining until this activity is completed. This can be based upon a measurement of time since /// started and the percent complete or another approach deemed appropriate by the caller. /// /// 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 @@ -285,6 +260,7 @@ internal ProgressRecord(ProgressRecord other) { return secondsRemaining; } + set { // negative values are allowed @@ -293,14 +269,9 @@ internal ProgressRecord(ProgressRecord other) } } - - /// - /// /// Gets and sets the type of record represented by this instance. - /// /// - public ProgressRecordType RecordType @@ -309,6 +280,7 @@ internal ProgressRecord(ProgressRecord other) { return type; } + set { if (value != ProgressRecordType.Completed && value != ProgressRecordType.Processing) @@ -320,27 +292,20 @@ 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() { return - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, "parent = {0} id = {1} act = {2} stat = {3} cur = {4} pct = {5} sec = {6} type = {7}", parentId, @@ -387,6 +352,7 @@ public override { return null; } + TimeSpan remainingTime = totalTime - elapsedTime; return (int)(remainingTime.TotalSeconds); @@ -397,9 +363,9 @@ public override /// The percentage complete will slowly converge toward 100%. /// At the the percentage complete will be 90%. /// - /// When did the operation start - /// How long does the operation usually take - /// Estimated percentage complete of the operation (always between 0 and 99% - never returns 100%) + /// When did the operation start. + /// How long does the operation usually take. + /// Estimated percentage complete of the operation (always between 0 and 99% - never returns 100%). /// /// Thrown when /// 1) is in the future @@ -413,14 +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("startTime"); - } - if (expectedDuration <= TimeSpan.Zero) - { - throw new ArgumentOutOfRangeException("expectedDuration"); - } + ArgumentOutOfRangeException.ThrowIfGreaterThan(startTime, now); + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(expectedDuration, TimeSpan.Zero); /* * According to the spec of Checkpoint-Computer @@ -469,40 +429,39 @@ 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 #region Serialization / deserialization for remoting - /// /// Creates a ProgressRecord object from a PSObject property bag. /// PSObject has to be in the format returned by ToPSObjectForRemoting method. /// - /// PSObject to rehydrate + /// PSObject to rehydrate. /// /// ProgressRecord rehydrated from a PSObject property bag /// @@ -516,7 +475,7 @@ internal static ProgressRecord FromPSObjectForRemoting(PSObject progressAsPSObje { if (progressAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("progressAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(progressAsPSObject)); } string activity = RemotingDecoder.GetPropertyValue(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_Activity); @@ -538,12 +497,16 @@ internal static ProgressRecord FromPSObjectForRemoting(PSObject progressAsPSObje /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// - /// This object as a PSObject property bag + /// 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)); @@ -557,23 +520,19 @@ internal PSObject ToPSObjectForRemoting() } #endregion - } //ProgressRecord - - + } /// - /// /// 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 - /// - /// + /// + /// + /// 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. @@ -584,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 - /// - /// + /// + /// 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 5efb3046105..4b6e8d818f1 100644 --- a/src/System.Management.Automation/engine/PropertyCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/PropertyCmdletProviderInterfaces.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -16,60 +16,53 @@ public sealed class PropertyCmdletProviderIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private PropertyCmdletProviderIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // CmdletProviderIntrinsics private - + } /// - /// Constructs a facade over the "real" session state API + /// Constructs a facade over the "real" session state API. /// - /// /// /// An instance of the cmdlet. /// - /// /// /// If is null. /// - /// internal PropertyCmdletProviderIntrinsics(Cmdlet cmdlet) { if (cmdlet == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); + throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); } _cmdlet = cmdlet; _sessionState = cmdlet.Context.EngineSessionState; - } // PropertyCmdletProviderIntrinsics internal + } /// - /// Constructs a facade over the "real" session state API + /// Constructs a facade over the "real" session state API. /// - /// /// /// An instance of the "real" session state. /// - /// /// /// If is null. /// - /// internal PropertyCmdletProviderIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // PropertyCmdletProviderIntrinsics internal + } #endregion Constructors @@ -80,43 +73,34 @@ internal PropertyCmdletProviderIntrinsics(SessionStateInternal sessionState) /// /// Gets the specified properties from the specified item(s) /// - /// /// /// The path to the item to get the properties from. /// - /// /// /// The properties to get from the item(s). If this is empty, null, or "*" all /// properties should be returned. /// - /// /// /// A PSObject for each item that the path represents. Each PSObject should /// contain a property for those in the providerSpecificPickList. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -131,52 +115,42 @@ public Collection Get( // Parameter validation is done in the session state object return _sessionState.GetProperty(new string[] { path }, providerSpecificPickList, false); - } // GetProperty + } /// /// Gets the specified properties from the specified item(s) /// - /// /// /// The path(s) to the item(s) to get the properties from. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The properties to get from the item(s). If this is empty, null, or "*" all /// properties should be returned. /// - /// /// /// A PSObject for each item that the path represents. Each PSObject should /// contain a property for those in the providerSpecificPickList. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -192,53 +166,43 @@ public Collection Get( // Parameter validation is done in the session state object return _sessionState.GetProperty(path, providerSpecificPickList, literalPath); - } // GetProperty + } /// /// Gets the specified properties from the specified item(s) /// - /// /// /// The path to the item to get the properties from. /// - /// /// /// The properties to get from the item(s). If this is empty, null, or "*" all /// properties should be returned. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for each item that the path represents is written /// to the context. Each PSObject should /// contain a property for those in the providerSpecificPickList. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -254,48 +218,39 @@ internal void Get( // Parameter validation is done in the session state object _sessionState.GetProperty(new string[] { path }, providerSpecificPickList, context); - } // GetProperty + } /// /// Gets the dynamic parameters for the get-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The properties to get from the item(s). If this is empty, null, or "*" all /// properties should be returned. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -311,7 +266,7 @@ internal object GetPropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.GetPropertyDynamicParameters(path, providerSpecificPickList, context); - } // GetPropertyDynamicParameters + } #endregion GetProperty @@ -320,41 +275,32 @@ internal object GetPropertyDynamicParameters( /// /// Sets the specified properties on the specified item(s) /// - /// /// /// The path to the item to set the properties on. /// - /// /// /// The properties that are to be set on the item /// - /// /// /// A PSObject for each item that had the property set on it. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -369,55 +315,43 @@ public Collection Set( // Parameter validation is done in the session state object return _sessionState.SetProperty(new string[] { path }, propertyValue, false, false); - } // SetProperty - + } /// /// Sets the specified properties on the specified item(s) /// - /// /// /// The path(s) to the item(s) to set the properties on. /// - /// /// /// The properties that are to be set on the item /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A PSObject for each item that had the property set on it. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -434,51 +368,40 @@ public Collection Set( // Parameter validation is done in the session state object return _sessionState.SetProperty(path, propertyValue, force, literalPath); - } // SetProperty - + } /// /// Sets the specified properties on the specified item(s) /// - /// /// /// The path to the item to set the properties on. /// - /// /// /// The properties that are to be set on the item /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for the property that was set is written to the context. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -494,47 +417,38 @@ internal void Set( // Parameter validation is done in the session state object _sessionState.SetProperty(new string[] { path }, propertyValue, context); - } // SetProperty + } /// /// Gets the dynamic parameters for the set-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The properties that are to be set on the item /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -550,7 +464,7 @@ internal object SetPropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.SetPropertyDynamicParameters(path, propertyValue, context); - } // SetPropertyDynamicParameters + } #endregion SetProperty @@ -559,37 +473,29 @@ internal object SetPropertyDynamicParameters( /// /// Clear the specified properties from the specified item(s) /// - /// /// /// The path to the item to clear the properties from. /// - /// /// /// The properties to clear from the item(s). /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -604,50 +510,40 @@ public void Clear( // Parameter validation is done in the session state object _sessionState.ClearProperty(new string[] { path }, propertyToClear, false, false); - } // ClearProperty + } /// /// Clear the specified properties from the specified item(s) /// - /// /// /// The path(s) to the item(s) to clear the properties from. /// - /// /// /// The properties to clear from the item(s). /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -664,46 +560,37 @@ public void Clear( // Parameter validation is done in the session state object _sessionState.ClearProperty(path, propertyToClear, force, literalPath); - } // ClearProperty + } /// /// Clears the specified properties from the specified item(s) /// - /// /// /// The path to the item to clear the properties from. /// - /// /// /// The properties to clear from the item(s). /// - /// /// /// The context under which the command is running. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -719,47 +606,38 @@ internal void Clear( // Parameter validation is done in the session state object _sessionState.ClearProperty(new string[] { path }, propertyToClear, context); - } // ClearProperty + } /// /// Gets the dynamic parameters for the clear-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The properties to clear from the item(s). /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -775,59 +653,48 @@ internal object ClearPropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.ClearPropertyDynamicParameters(path, propertyToClear, context); - } // ClearPropertyDynamicParameters + } #endregion ClearProperty #region NewProperty /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path to the item on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// A PSObject for each item that the property was created on. The PSObject /// contains the properties that were created. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -844,63 +711,50 @@ public Collection New( // Parameter validation is done in the session state object return _sessionState.NewProperty(new string[] { path }, propertyName, propertyTypeName, value, false, false); - } // NewProperty + } /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path(s) to the item(s0 on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A PSObject for each item that the property was created on. The PSObject /// contains the properties that were created. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -919,60 +773,48 @@ public Collection New( // Parameter validation is done in the session state object return _sessionState.NewProperty(path, propertyName, propertyTypeName, value, force, literalPath); - } // NewProperty + } /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path to the item on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for each item that the property was created on /// is written to the context. Each PSObject /// contains the properties that were created. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -990,55 +832,44 @@ internal void New( // Parameter validation is done in the session state object _sessionState.NewProperty(new string[] { path }, propertyName, type, value, context); - } // NewProperty + } /// /// Gets the dynamic parameters for the new-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1056,7 +887,7 @@ internal object NewPropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.NewPropertyDynamicParameters(path, propertyName, type, value, context); - } // NewPropertyDynamicParameters + } #endregion NewProperty @@ -1065,37 +896,29 @@ internal object NewPropertyDynamicParameters( /// /// Removes a property from the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be removed. /// - /// /// /// The property name that should be removed. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1108,50 +931,40 @@ public void Remove(string path, string propertyName) // Parameter validation is done in the session state object _sessionState.RemoveProperty(new string[] { path }, propertyName, false, false); - } // RemoveProperty + } /// /// Removes a property from the specified item(s) /// - /// /// /// The path(s) to the item(s) on which the property should be removed. /// - /// /// /// The property name that should be removed. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1164,46 +977,37 @@ public void Remove(string[] path, string propertyName, bool force, bool literalP // Parameter validation is done in the session state object _sessionState.RemoveProperty(path, propertyName, force, literalPath); - } // RemoveProperty + } /// /// Removes a property from the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be removed. /// - /// /// /// The property name that should be removed. /// - /// /// /// The context under which the command is running. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1219,47 +1023,38 @@ internal void Remove( // Parameter validation is done in the session state object _sessionState.RemoveProperty(new string[] { path }, propertyName, context); - } // RemoveProperty + } /// /// Gets the dynamic parameters for the remove-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property that should be removed. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1275,7 +1070,7 @@ internal object RemovePropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.RemovePropertyDynamicParameters(path, propertyName, context); - } // RemovePropertyDynamicParameters + } #endregion RemoveProperty @@ -1284,46 +1079,36 @@ internal object RemovePropertyDynamicParameters( /// /// Renames a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be renamed. /// - /// /// /// The source name of the property to be renamed. /// - /// /// /// The new name of the property. /// - /// /// /// A PSObject for each item that is the new property after the rename. /// - /// /// /// If , , /// or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1339,59 +1124,47 @@ public Collection Rename( // Parameter validation is done in the session state object return _sessionState.RenameProperty(new string[] { path }, sourceProperty, destinationProperty, false, false); - } // RenameProperty + } /// /// Renames a property on the specified item(s) /// - /// /// /// The path(s) to the item(s) on which the property should be renamed. /// - /// /// /// The source name of the property to be renamed. /// - /// /// /// The new name of the property. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A PSObject for each item that is the new property after the rename. /// - /// /// /// If , , /// or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1409,56 +1182,45 @@ public Collection Rename( // Parameter validation is done in the session state object return _sessionState.RenameProperty(path, sourceProperty, destinationProperty, force, literalPath); - } // RenameProperty + } /// /// Renames a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be renamed. /// - /// /// /// The source name of the property to be renamed. /// - /// /// /// The new name of the property. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for each item that the property is renamed on is /// written to the context. The Shellobject contains the new property after the rename. /// - /// /// /// If , , /// or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1475,51 +1237,41 @@ internal void Rename( // Parameter validation is done in the session state object _sessionState.RenameProperty(new string[] { path }, sourceProperty, destinationProperty, context); - } // RenameProperty + } /// /// Gets the dynamic parameters for the rename-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The source name of the property to be renamed. /// - /// /// /// The new name of the property. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1536,7 +1288,7 @@ internal object RenamePropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.RenamePropertyDynamicParameters(path, sourceProperty, destinationProperty, context); - } // RenamePropertyDynamicParameters + } #endregion RenameProperty @@ -1545,52 +1297,41 @@ internal object RenamePropertyDynamicParameters( /// /// Copies a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be copied. /// - /// /// /// The source name of the property to be copied. /// - /// /// /// The path to the item(s) to copy the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// A PSObject for each item that is the new property after the copy. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1613,66 +1354,52 @@ public Collection Copy( destinationPath, destinationProperty, false, false); - } // CopyProperty - + } /// /// Copies a property on the specified item(s) /// - /// /// /// The path(s) to the item(s) on which the property should be copied. /// - /// /// /// The source name of the property to be copied. /// - /// /// /// The path to the item(s) to copy the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A PSObject for each item that is the new property after the copy. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1698,63 +1425,50 @@ public Collection Copy( destinationProperty, force, literalPath); - } // CopyProperty - + } /// /// Copies a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be copied. /// - /// /// /// The source name of the property to be copied. /// - /// /// /// The path to the item(s) to copy the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for each item that the new property was copied to is /// written to the context. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1777,56 +1491,45 @@ internal void Copy( destinationPath, destinationProperty, context); - } // CopyProperty + } /// /// Gets the dynamic parameters for the copy-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The source name of the property to be copied. /// - /// /// /// The path to the item(s) to copy the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1844,7 +1547,7 @@ internal object CopyPropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.CopyPropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty, context); - } // CopyPropertyDynamicParameters + } #endregion CopyProperty @@ -1853,56 +1556,44 @@ internal object CopyPropertyDynamicParameters( /// /// Moves a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be moved. /// - /// /// /// The source name of the property to be moved. /// - /// /// /// The path to the item(s) to move the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// A PSObject for each item that is the new property after the move. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If resolves to more than one item. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -1926,70 +1617,55 @@ public Collection Move( destinationProperty, false, false); - } // MoveProperty - + } /// /// Moves a property on the specified item(s) /// - /// /// /// The path(s) to the item(s) on which the property should be moved. /// - /// /// /// The source name of the property to be moved. /// - /// /// /// The path to the item(s) to move the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A PSObject for each item that is the new property after the move. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If resolves to more than one item. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2015,67 +1691,53 @@ public Collection Move( destinationProperty, force, literalPath); - } // MoveProperty - + } /// /// Moves a property on the specified item(s) /// - /// /// /// The path to the item(s) on which the property should be moved. /// - /// /// /// The source name of the property to be moved. /// - /// /// /// The path to the item(s) to move the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// The context under which the command is running. /// - /// /// /// Nothing. A PSObject for each item that the property was moved to is written /// to the context. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If resolves to more than one item. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2098,56 +1760,45 @@ internal void Move( destinationPath, destinationProperty, context); - } // MoveProperty + } /// /// Gets the dynamic parameters for the copy-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The source name of the property to be moved. /// - /// /// /// The path to the item(s) to move the property to. It can be the same /// as the sourcePath as long as the destinationProperty is different. /// - /// /// /// The new name of the property. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -2165,7 +1816,7 @@ internal object MovePropertyDynamicParameters( // Parameter validation is done in the session state object return _sessionState.MovePropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty, context); - } // MovePropertyDynamicParameters + } #endregion MoveProperty @@ -2173,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 - } // PropertyCmdletProviderIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/ProviderInterfaces.cs b/src/System.Management.Automation/engine/ProviderInterfaces.cs index 46e288cbb38..e9edcb48415 100644 --- a/src/System.Management.Automation/engine/ProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ProviderInterfaces.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -18,58 +18,50 @@ public sealed class CmdletProviderManagementIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private CmdletProviderManagementIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // CmdletProviderManagementIntrinsics private + } /// - /// The facade for managing providers + /// The facade for managing providers. /// - /// /// /// The session to which this is a facade. /// - /// /// /// If is null. /// - /// internal CmdletProviderManagementIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // CmdletProviderManagementIntrinsics internal + } #endregion Constructors #region Public methods - /// /// Gets the specified provider(s). /// - /// /// /// Either the fully-qualified or friendly name for the provider. /// - /// /// /// The provider information for the specified provider. /// - /// /// /// If is null or empty. /// - /// /// /// If the provider specified by is not currently /// loaded. @@ -83,29 +75,24 @@ public Collection Get(string name) // Parameter validation is done in the session state object return _sessionState.GetProvider(name); - } // Get + } /// /// Gets the specified provider(s). /// - /// /// /// Either the fully-qualified or friendly name for the provider. /// - /// /// /// The provider information for the specified provider. /// - /// /// /// If is null or empty. /// - /// /// /// If is not PSSnapin-qualified and more than one provider /// exists with the specified name. /// - /// /// /// If the provider specified by is not currently /// loaded. @@ -119,7 +106,7 @@ public ProviderInfo GetOne(string name) // Parameter validation is done in the session state object return _sessionState.GetSingleProvider(name); - } // Get + } /// /// Gets all the Cmdlet Providers that are loaded. @@ -131,28 +118,24 @@ public IEnumerable GetAll() "The only constructor for this class should always set the sessionState field"); return _sessionState.ProviderList; - } // GetAll + } #endregion Public methods #region Internal methods /// - /// Determines if the specified provider has the specified capability + /// Determines if the specified provider has the specified capability. /// - /// /// /// The capability to check the provider for. /// - /// /// /// The provider information to use for the check. /// - /// /// /// True, if the provider has the capability, false otherwise. /// - /// internal static bool CheckProviderCapabilities( ProviderCapabilities capability, ProviderInfo provider) @@ -160,27 +143,25 @@ internal static bool CheckProviderCapabilities( // Check the capability return (provider.Capabilities & capability) != 0; - } // CheckProviderCapabilities + } /// - /// Gets the count of the number of providers that are loaded + /// Gets the count of the number of providers that are loaded. /// - /// internal int Count { get { return _sessionState.ProviderCount; } - } // Count + } #endregion Internal methods #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data - } // ProviderIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/ProviderNames.cs b/src/System.Management.Automation/engine/ProviderNames.cs index 59199cd9a63..6d9e3d2d598 100644 --- a/src/System.Management.Automation/engine/ProviderNames.cs +++ b/src/System.Management.Automation/engine/ProviderNames.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -10,52 +9,51 @@ namespace System.Management.Automation /// shell the provider name includes the PSSnapin name. In custom /// shells it does not. /// - /// internal abstract class ProviderNames { /// - /// Gets the name of the EnvironmentProvider + /// Gets the name of the EnvironmentProvider. /// internal abstract string Environment { get; } /// - /// Gets the name of the Certificate + /// Gets the name of the Certificate. /// internal abstract string Certificate { get; } /// - /// Gets the name of the VariableProvider + /// Gets the name of the VariableProvider. /// internal abstract string Variable { get; } /// - /// Gets the name of the AliasProvider + /// Gets the name of the AliasProvider. /// internal abstract string Alias { get; } /// - /// Gets the name of the FunctionProvider + /// Gets the name of the FunctionProvider. /// internal abstract string Function { get; } /// - /// Gets the name of the FileSystemProvider + /// Gets the name of the FileSystemProvider. /// internal abstract string FileSystem { get; } /// - /// Gets the name of the RegistryProvider + /// Gets the name of the RegistryProvider. /// internal abstract string Registry { get; } } /// - /// The provider names for the single shell + /// The provider names for the single shell. /// internal class SingleShellProviderNames : ProviderNames { /// - /// Gets the name of the EnvironmentProvider + /// Gets the name of the EnvironmentProvider. /// internal override string Environment { @@ -66,7 +64,7 @@ internal override string Environment } /// - /// Gets the name of the Certificate + /// Gets the name of the Certificate. /// internal override string Certificate { @@ -77,7 +75,7 @@ internal override string Certificate } /// - /// Gets the name of the VariableProvider + /// Gets the name of the VariableProvider. /// internal override string Variable { @@ -88,7 +86,7 @@ internal override string Variable } /// - /// Gets the name of the AliasProvider + /// Gets the name of the AliasProvider. /// internal override string Alias { @@ -99,7 +97,7 @@ internal override string Alias } /// - /// Gets the name of the FunctionProvider + /// Gets the name of the FunctionProvider. /// internal override string Function { @@ -110,7 +108,7 @@ internal override string Function } /// - /// Gets the name of the FileSystemProvider + /// Gets the name of the FileSystemProvider. /// internal override string FileSystem { @@ -121,7 +119,7 @@ internal override string FileSystem } /// - /// Gets the name of the RegistryProvider + /// Gets the name of the RegistryProvider. /// internal override string Registry { @@ -132,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 362dc23b00f..80d70377e87 100644 --- a/src/System.Management.Automation/engine/ProxyCommand.cs +++ b/src/System.Management.Automation/engine/ProxyCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; using System.Text; @@ -8,14 +7,14 @@ namespace System.Management.Automation { /// - /// A ProxyCommand class used to represent a Command constructed Dynamically + /// A ProxyCommand class used to represent a Command constructed Dynamically. /// public sealed class ProxyCommand { #region Private Constructor /// - /// Private Constructor to restrict inheritance + /// Private Constructor to restrict inheritance. /// private ProxyCommand() { @@ -40,12 +39,12 @@ private ProxyCommand() /// public static string Create(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } - return commandMetadata.GetProxyCommand("", true); + return commandMetadata.GetProxyCommand(string.Empty, true); } /// @@ -66,7 +65,7 @@ public static string Create(CommandMetadata commandMetadata) /// public static string Create(CommandMetadata commandMetadata, string helpComment) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } @@ -96,7 +95,7 @@ public static string Create(CommandMetadata commandMetadata, string helpComment) /// public static string Create(CommandMetadata commandMetadata, string helpComment, bool generateDynamicParameters) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } @@ -119,7 +118,7 @@ public static string Create(CommandMetadata commandMetadata, string helpComment, /// public static string GetCmdletBindingAttribute(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } @@ -144,7 +143,7 @@ public static string GetCmdletBindingAttribute(CommandMetadata commandMetadata) [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] public static string GetParamBlock(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } @@ -168,10 +167,11 @@ public static string GetParamBlock(CommandMetadata commandMetadata) /// public static string GetBegin(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } + return commandMetadata.GetBeginBlock(); } @@ -191,10 +191,11 @@ public static string GetBegin(CommandMetadata commandMetadata) /// public static string GetProcess(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } + return commandMetadata.GetProcessBlock(); } @@ -214,10 +215,11 @@ public static string GetProcess(CommandMetadata commandMetadata) /// public static string GetDynamicParam(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("commandMetaData"); } + return commandMetadata.GetDynamicParamBlock(); } @@ -237,13 +239,38 @@ public static string GetDynamicParam(CommandMetadata commandMetadata) /// public static string GetEnd(CommandMetadata commandMetadata) { - if (null == commandMetadata) + if (commandMetadata == null) { throw PSTraceSource.NewArgumentNullException("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; @@ -251,6 +278,7 @@ private static T GetProperty(PSObject obj, string property) where T : class { result = obj.Properties[property].Value as T; } + return result; } @@ -263,6 +291,7 @@ private static string GetObjText(object obj) { text = GetProperty(psobj, "Text"); } + return text ?? obj.ToString(); } @@ -273,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'); } } } @@ -299,13 +328,15 @@ private static void AppendContent(StringBuilder sb, string section, PSObject[] a sb.Append(section); sb.Append("\n\n"); } + sb.Append(text); - sb.Append("\n"); + sb.Append('\n'); } } + if (!first) { - sb.Append("\n"); + sb.Append('\n'); } } } @@ -320,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 { @@ -331,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'); } } } @@ -345,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("help"); - } + ArgumentNullException.ThrowIfNull(help); bool isHelpObject = false; foreach (string typeName in help.InternalTypeNames) @@ -388,7 +416,7 @@ public static string GetHelpComments(PSObject help) if (!string.IsNullOrEmpty(text)) { sb.Append(text); - sb.Append("\n"); + sb.Append('\n'); } } } @@ -413,26 +441,28 @@ public static string GetHelpComments(PSObject help) } } } + PSObject code = GetProperty(ex, "code"); if (code != null) { exsb.Append(code.ToString()); } + PSObject[] remarks = GetProperty(ex, "remarks"); if (remarks != null) { - exsb.Append("\n"); + exsb.Append('\n'); foreach (PSObject remark in remarks) { string remarkText = GetProperty(remark, "text"); - exsb.Append(remarkText.ToString()); + exsb.Append(remarkText); } } 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 344818f0ffc..9bebe01b2e7 100644 --- a/src/System.Management.Automation/engine/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/PseudoParameterBinder.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; @@ -9,7 +8,6 @@ namespace System.Management.Automation /// /// The parameter binder for runtime-defined parameters which are declared through the RuntimeDefinedParameterDictionary. /// - /// internal class RuntimeDefinedParameterBinder : ParameterBinderBase { #region ctor @@ -18,19 +16,15 @@ internal class RuntimeDefinedParameterBinder : ParameterBinderBase /// Constructs the parameter binder with the specified type metadata. The binder is only valid /// for a single instance of a bindable runtime-defined parameter collection and only for the duration of a command. /// - /// /// /// The target runtime-defined parameter collection that the parameter values will be bound to. /// - /// /// /// An instance of the command so that attributes can access the context. /// - /// /// /// The Command line parameter collection to update... /// - /// internal RuntimeDefinedParameterBinder( RuntimeDefinedParameterDictionary target, InternalCommand command, @@ -42,8 +36,8 @@ internal RuntimeDefinedParameterBinder( { string key = pair.Key; RuntimeDefinedParameter pp = pair.Value; - string ppName = (null == pp) ? null : pp.Name; - if (null == pp || key != ppName) + string ppName = pp?.Name; + if (pp == null || key != ppName) { ParameterBindingException bindingException = new ParameterBindingException( @@ -60,6 +54,7 @@ internal RuntimeDefinedParameterBinder( throw bindingException; } } + this.CommandLineParameters = commandLineParameters; } @@ -69,9 +64,8 @@ internal RuntimeDefinedParameterBinder( /// /// Hides the base class Target property to ensure the target - /// is always a RuntimeDefinedParameterDictionary + /// is always a RuntimeDefinedParameterDictionary. /// - /// internal new RuntimeDefinedParameterDictionary Target { get @@ -83,22 +77,19 @@ internal RuntimeDefinedParameterBinder( { base.Target = value; } - } // Target + } #region Parameter default values /// - /// Gets the default value for the specified parameter + /// Gets the default value for the specified parameter. /// - /// /// /// The name of the parameter to get the value for. /// - /// /// /// The value of the specified parameter /// - /// internal override object GetDefaultParameterValue(string name) { object result = null; @@ -107,8 +98,9 @@ internal override object GetDefaultParameterValue(string name) { result = parameter.Value; } + return result; - } // GetDefaultParameterValue + } #endregion Parameter default values @@ -130,15 +122,15 @@ internal override object GetDefaultParameterValue(string name) /// internal override void BindParameter(string name, object value, CompiledCommandParameter parameterMetadata) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } Target[name].Value = value; this.CommandLineParameters.Add(name, value); - } // BindParameter + } #endregion Parameter binding - } // RuntimeDefinedParameterBinder -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/PseudoParameters.cs b/src/System.Management.Automation/engine/PseudoParameters.cs index b7a40740e4b..a3703a63d90 100644 --- a/src/System.Management.Automation/engine/PseudoParameters.cs +++ b/src/System.Management.Automation/engine/PseudoParameters.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -28,43 +27,38 @@ public class RuntimeDefinedParameter /// public RuntimeDefinedParameter() { - } // RuntimeDefinedParameter + } /// /// Constructs a new instance of a runtime-defined parameter using the specified parameters. /// - /// /// /// The name of the parameter. This cannot be null or empty. /// - /// /// /// The type of the parameter value. Arguments will be coerced to this type before binding. /// This parameter cannot be null. /// - /// /// /// Any parameter attributes that should be on the parameter. This can be any of the /// parameter attributes including but not limited to Validate*Attribute, ExpandWildcardAttribute, etc. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// public RuntimeDefinedParameter(string name, Type parameterType, Collection attributes) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (parameterType == null) { - throw PSTraceSource.NewArgumentNullException("parameterType"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterType)); } _name = name; @@ -74,12 +68,11 @@ public RuntimeDefinedParameter(string name, Type parameterType, Collection - /// Gets or sets the name of the parameter + /// Gets or sets the name of the parameter. /// - /// /// /// If is null or empty on set. /// @@ -89,25 +82,26 @@ public string Name { return _name; } + set { - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { throw PSTraceSource.NewArgumentException("name"); } + _name = value; } - } // Name - private string _name = String.Empty; + } + + private string _name = string.Empty; /// /// Gets or sets the type of the parameter. /// - /// /// /// Arguments will be coerced to this type before being bound. /// - /// /// /// If is null. /// @@ -124,15 +118,16 @@ public Type ParameterType { throw PSTraceSource.NewArgumentNullException("value"); } + _parameterType = value; } } + private Type _parameterType; /// /// Gets or sets the value of the parameter. /// - /// /// /// If the value is set prior to parameter binding, the value will be /// reset before each pipeline object is processed. @@ -150,6 +145,7 @@ public object Value _value = value; } } + private object _value; /// @@ -160,12 +156,48 @@ public object Value /// /// Gets or sets the attribute collection that describes the parameter. /// - /// /// /// This can be any attribute that can be applied to a normal parameter. /// public Collection Attributes { get; } = new Collection(); - } // class RuntimeDefinedParameter + + /// + /// Check if the parameter is disabled due to the associated experimental feature. + /// + internal bool IsDisabled() + { + bool hasParameterAttribute = false; + bool hasEnabledParamAttribute = false; + bool hasSeenExpAttribute = false; + + foreach (Attribute attr in Attributes) + { + if (!hasSeenExpAttribute && attr is ExperimentalAttribute expAttribute) + { + if (expAttribute.ToHide) + { + return true; + } + + hasSeenExpAttribute = true; + } + else if (attr is ParameterAttribute paramAttribute) + { + hasParameterAttribute = true; + if (paramAttribute.ToHide) + { + continue; + } + + hasEnabledParamAttribute = true; + } + } + + // If one or more parameter attributes are declared but none is enabled, + // then we consider the parameter is disabled. + return hasParameterAttribute && !hasEnabledParamAttribute; + } + } /// /// Represents a collection of runtime-defined parameters that are keyed based on the name @@ -182,7 +214,6 @@ public object Value /// /// /// - [Serializable] public class RuntimeDefinedParameterDictionary : Dictionary { /// @@ -191,23 +222,25 @@ public class RuntimeDefinedParameterDictionary : Dictionary - /// Gets or sets the help file that documents these parameters + /// Gets or sets the help file that documents these parameters. /// public string HelpFile { get { return _helpFile; } - set { _helpFile = String.IsNullOrEmpty(value) ? String.Empty : value; } + + set { _helpFile = string.IsNullOrEmpty(value) ? string.Empty : value; } } - private string _helpFile = String.Empty; + + private string _helpFile = string.Empty; /// /// Gets or sets private data associated with the runtime-defined parameters. /// public object Data { get; set; } - internal static RuntimeDefinedParameter[] EmptyParameterArray = new RuntimeDefinedParameter[0]; - } // class RuntimeDefinedParameterDictionary -} // namespace System.Management.Automation + 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 ab9f25b0f66..b25cb5107ba 100644 --- a/src/System.Management.Automation/engine/QuestionMarkVariable.cs +++ b/src/System.Management.Automation/engine/QuestionMarkVariable.cs @@ -1,7 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -13,7 +11,6 @@ internal class QuestionMarkVariable : PSVariable /// /// Constructs an instance of the variable with execution context. /// - /// /// /// Execution context /// @@ -28,7 +25,6 @@ internal QuestionMarkVariable(ExecutionContext context) /// /// Gets or sets the value of the variable. /// - /// public override object Value { get @@ -45,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 9b0991deaa2..5ee84f4c42f 100644 --- a/src/System.Management.Automation/engine/ReflectionParameterBinder.cs +++ b/src/System.Management.Automation/engine/ReflectionParameterBinder.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; using System.Linq.Expressions; using System.Management.Automation.Internal; using System.Reflection; + using Microsoft.PowerShell.Commands; namespace System.Management.Automation @@ -13,7 +13,6 @@ namespace System.Management.Automation /// /// The parameter binder for real CLR objects that have properties and fields decorated with the parameter attributes. /// - /// internal class ReflectionParameterBinder : ParameterBinderBase { #region ctor @@ -22,15 +21,12 @@ internal class ReflectionParameterBinder : ParameterBinderBase /// Constructs the parameter binder with the specified type metadata. The binder is only valid /// for a single instance of a bindable object and only for the duration of a command. /// - /// /// /// The target object that the parameter values will be bound to. /// - /// /// /// An instance of the command so that attributes can access the context. /// - /// internal ReflectionParameterBinder( object target, Cmdlet command) @@ -42,19 +38,15 @@ internal ReflectionParameterBinder( /// Constructs the parameter binder with the specified type metadata. The binder is only valid /// for a single instance of a bindable object and only for the duration of a command. /// - /// /// /// The target object that the parameter values will be bound to. /// - /// /// /// An instance of the command so that attributes can access the context. /// - /// /// /// The dictionary to use to record the parameters set by this object... /// - /// internal ReflectionParameterBinder( object target, Cmdlet command, @@ -71,21 +63,17 @@ internal ReflectionParameterBinder( #region Parameter default values /// - /// Gets the default value for the specified parameter + /// Gets the default value for the specified parameter. /// - /// /// /// The name of the parameter to get the default value of. /// - /// /// /// The default value of the specified parameter. /// - /// /// /// If the ETS call to get the property value throws an exception. /// - /// internal override object GetDefaultParameterValue(string name) { try @@ -136,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); } @@ -156,7 +144,7 @@ internal override void BindParameter(string name, object value, CompiledCommandP ExtendedTypeSystem.ExceptionWhenSetting, name, e.Message); } - } // BindParameter + } #endregion Parameter binding @@ -167,55 +155,77 @@ 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(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(FormatDefaultCommand), "InputObject"), o => ((FormatDefaultCommand)o).InputObject); - s_setterMethods.TryAdd(Tuple.Create(typeof(FormatDefaultCommand), "InputObject"), (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_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(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_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(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(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(CommonParameters), "ErrorAction"), (o, v) => ((CommonParameters)o).ErrorAction = (ActionPreference)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "WarningAction"), (o, v) => ((CommonParameters)o).WarningAction = (ActionPreference)v); - s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "InformationAction"), (o, v) => ((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_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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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) => + { + v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); + ((CommonParameters)o).ErrorAction = (ActionPreference)v; + }); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "WarningAction"), + (o, v) => + { + v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); + ((CommonParameters)o).WarningAction = (ActionPreference)v; + }); + s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "InformationAction"), + (o, v) => + { + v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); + ((CommonParameters)o).InformationAction = (ActionPreference)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 = new ConcurrentDictionary, Func>(); + private static readonly ConcurrentDictionary, Action> s_setterMethods = new ConcurrentDictionary, Action>(); @@ -243,7 +253,7 @@ private static Action GetSetter(Type type, string property) var propertyExpr = GetPropertyOrFieldExpr(type, property, Expression.Convert(target, type)); Expression expr = Expression.Assign(propertyExpr, Expression.Convert(value, propertyExpr.Type)); - if (propertyExpr.Type.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(propertyExpr.Type) == null) + if (propertyExpr.Type.IsValueType && Nullable.GetUnderlyingType(propertyExpr.Type) == null) { var throwInvalidCastExceptionExpr = Expression.Call(Language.CachedReflectionInfo.LanguagePrimitives_ThrowInvalidCastException, @@ -308,5 +318,5 @@ private static Expression GetPropertyOrFieldExpr(Type type, string name, Express } #endregion Private members - } // ReflectionParameterBinder -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/RunspaceConfigurationEntry.cs b/src/System.Management.Automation/engine/RunspaceConfigurationEntry.cs deleted file mode 100644 index c90388e0e2b..00000000000 --- a/src/System.Management.Automation/engine/RunspaceConfigurationEntry.cs +++ /dev/null @@ -1,532 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -#pragma warning disable 1634, 1691 -#pragma warning disable 56506 - -namespace System.Management.Automation.Runspaces -{ - /// - /// Enum for describing different kind information that can be configured in runspace configuration. - /// - internal enum RunspaceConfigurationCategory - { - /// - /// Cmdlets - /// - Cmdlets, - - /// - /// Providers - /// - Providers, - - /// - /// Assemblies - /// - Assemblies, - - /// - /// Scripts - /// - Scripts, - - /// - /// Initialization scripts - /// - InitializationScripts, - - /// - /// Types - /// - Types, - - /// - /// Formats - /// - Formats, - } - - /// - /// Define class for runspace configuration entry. - /// - /// - /// This abstract class is to be derived internally by Monad for different - /// runspace configuration entries only. Developers should not derive from - /// this class. - /// - internal abstract class RunspaceConfigurationEntry - { - /// - /// Initiate an instance of runspace configuration entry. - /// - /// Name for the runspace configuration entry - /// - protected RunspaceConfigurationEntry(string name) - { - if (String.IsNullOrEmpty(name) || String.IsNullOrEmpty(name.Trim())) - { - throw PSTraceSource.NewArgumentNullException("name"); - } - - Name = name.Trim(); - } - - /// - /// Initiate an instance of runspace configuration entry. - /// - /// Name for the runspace configuration entry - /// The name of the PSSnapin the entry comes from. - /// - internal RunspaceConfigurationEntry(string name, PSSnapInInfo psSnapin) - { - if (String.IsNullOrEmpty(name) || String.IsNullOrEmpty(name.Trim())) - { - throw PSTraceSource.NewArgumentNullException("name"); - } - - Name = name.Trim(); - - if (psSnapin == null) - { - throw PSTraceSource.NewArgumentException("psSnapin"); - } - - PSSnapIn = psSnapin; - } - - - /// - /// Gets name of configuration entry - /// - public string Name { get; } - - /// - /// Gets name of PSSnapin that this configuration entry belongs to. - /// - public PSSnapInInfo PSSnapIn { get; } = null; - - internal bool _builtIn = false; - /// - /// Get whether this entry is a built-in entry. - /// - public bool BuiltIn - { - get - { - return _builtIn; - } - } - - internal UpdateAction _action = UpdateAction.None; - internal UpdateAction Action - { - get - { - return _action; - } - } - } - - /// - /// Defines class for type configuration entry. - /// - internal sealed class TypeConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for type configuration entry. - /// - /// Name of the type configuration entry - /// File name that contains the types configuration information. - /// when is null or empty - public TypeConfigurationEntry(string name, string fileName) - : base(name) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for type configuration entry. - /// - /// TypeData instance - /// Specify the operation with the typedata - public TypeConfigurationEntry(TypeData typeData, bool isRemove) - : base("*") - { - if (typeData == null) - { - throw PSTraceSource.NewArgumentException("typeData"); - } - - TypeData = typeData; - IsRemove = isRemove; - } - - /// - /// Initiate an instance for type configuration entry. - /// - /// Name of the type configuration entry - /// File name that contains the types configuration information. - /// PSSnapin from which type info comes. - /// when is null, empty or does not end in .ps1xml - internal TypeConfigurationEntry(string name, string fileName, PSSnapInInfo psSnapinInfo) - : base(name, psSnapinInfo) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for type configuration entry. - /// - /// File name that contains the types configuration information. - /// when is null, empty or does not end in .ps1xml - public TypeConfigurationEntry(string fileName) - : base(fileName) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } -#pragma warning suppress 56506 - FileName = fileName.Trim(); - } - - /// - /// Gets file name that contains the types configuration information. - /// - /// - public string FileName { get; } - - /// - /// Get the strong type data contains the type configuration information - /// - public TypeData TypeData { get; } - - /// - /// Set to true if the strong type data is to be removed - /// - public bool IsRemove { get; } - } - - /// - /// Defines class for type configuration entry. - /// - internal sealed class FormatConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for type configuration entry. - /// - /// Name of the format configuration entry - /// File name that contains the format configuration information. - /// when is null or empty - public FormatConfigurationEntry(string name, string fileName) - : base(name) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for Format configuration entry. - /// - /// Name of the Format configuration entry - /// File name that contains the Formats configuration information. - /// PSSnapin from which the format comes. - /// when is null, empty or does not end in .ps1xml - internal FormatConfigurationEntry(string name, string fileName, PSSnapInInfo psSnapinInfo) - : base(name, psSnapinInfo) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for type configuration entry. - /// - /// File name that contains the format configuration information. - /// when is null or empty - public FormatConfigurationEntry(string fileName) - : base(fileName) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentException("fileName"); - } -#pragma warning suppress 56506 - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for type configuration entry. - /// - /// - public FormatConfigurationEntry(ExtendedTypeDefinition typeDefinition) - : base("*") - { - if (typeDefinition == null) - { - throw PSTraceSource.NewArgumentNullException("typeDefinition"); - } - FormatData = typeDefinition; - } - - /// - /// Gets file name that contains the format configuration information. - /// - /// File name that contains the format configuration information. - public string FileName { get; } - - /// - /// Get the typeDefinition that contains the format configuration information - /// - public ExtendedTypeDefinition FormatData { get; } - } - - /// - /// Class to define configuration data for cmdlets - /// - internal sealed class CmdletConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for cmdlet configuration entry. - /// - /// Name of the cmdlet configuration entry - /// Class that include implementation of the cmdlet - /// Name of the help file that include help information for the cmdlet - public CmdletConfigurationEntry(string name, Type implementingType, string helpFileName) - : base(name) - { - if (implementingType == null) - { - throw PSTraceSource.NewArgumentNullException("implementingType"); - } - - ImplementingType = implementingType; - - if (!String.IsNullOrEmpty(helpFileName)) - { - HelpFileName = helpFileName.Trim(); - } - else - { - HelpFileName = helpFileName; - } - } - - /// - /// Initiate an instance for cmdlet configuration entry. - /// - /// Name of the cmdlet configuration entry - /// Class that include implementation of the cmdlet - /// PSSnapin from which the cmdlet comes. - /// Name of the help file that include help information for the cmdlet - internal CmdletConfigurationEntry(string name, Type implementingType, string helpFileName, PSSnapInInfo psSnapinInfo) - : base(name, psSnapinInfo) - { - if (implementingType == null) - { - throw PSTraceSource.NewArgumentNullException("implementingType"); - } - - ImplementingType = implementingType; - - if (!String.IsNullOrEmpty(helpFileName)) - { - HelpFileName = helpFileName.Trim(); - } - else - { - HelpFileName = helpFileName; - } - } - - /// - /// Get class that include implementation of the cmdlet - /// - public Type ImplementingType { get; } - - /// - /// Get name of the help file that include help information for the cmdlet - /// - /// - public string HelpFileName { get; } - } - - /// - /// Define class for provider configuration entry - /// - internal sealed class ProviderConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for provider configuration entry. - /// - /// Name of the provider configuration entry - /// Class that include implementation of the provider - /// Name of the help file that include help information for the provider - public ProviderConfigurationEntry(string name, Type implementingType, string helpFileName) - : base(name) - { - if (implementingType == null) - { - throw PSTraceSource.NewArgumentNullException("implementingType"); - } - - ImplementingType = implementingType; - - if (!String.IsNullOrEmpty(helpFileName)) - { - HelpFileName = helpFileName.Trim(); - } - else - { - HelpFileName = helpFileName; - } - } - - /// - /// Initiate an instance for provider configuration entry. - /// - /// Name of the provider configuration entry - /// Class that include implementation of the provider - /// Name of the help file that include help information for the provider - /// PSSnapin from which provider comes from. - internal ProviderConfigurationEntry(string name, Type implementingType, string helpFileName, PSSnapInInfo psSnapinInfo) - : base(name, psSnapinInfo) - { - if (implementingType == null) - { - throw PSTraceSource.NewArgumentNullException("implementingType"); - } - - ImplementingType = implementingType; - - if (!String.IsNullOrEmpty(helpFileName)) - { - HelpFileName = helpFileName.Trim(); - } - else - { - HelpFileName = helpFileName; - } - } - - /// - /// Get class that include implementation of the provider. - /// - /// - public Type ImplementingType { get; } - - /// - /// Get name of the help file that include help information for the provider - /// - public string HelpFileName { get; } - } - - /// - /// Define class for script configuration entry - /// - internal sealed class ScriptConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for script configuration entry. - /// - /// Name of the script configuration entry - /// Content of the script - public ScriptConfigurationEntry(string name, string definition) - : base(name) - { - if (String.IsNullOrEmpty(definition) || String.IsNullOrEmpty(definition.Trim())) - { - throw PSTraceSource.NewArgumentNullException("definition"); - } - - Definition = definition.Trim(); - } - - /// - /// Get content for the script. - /// - public string Definition { get; } - } - - /// - /// Configuration data for assemblies. - /// - internal sealed class AssemblyConfigurationEntry : RunspaceConfigurationEntry - { - /// - /// Initiate an instance for assembly configuration entry. - /// - /// Strong name of the assembly - /// Name of the assembly file - public AssemblyConfigurationEntry(string name, string fileName) - : base(name) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentNullException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Initiate an instance for assembly configuration entry. - /// - /// Strong name of the assembly - /// Name of the assembly file - /// PSSnapin information. - /// when is null, empty or does not end in .ps1xml - internal AssemblyConfigurationEntry(string name, string fileName, PSSnapInInfo psSnapinInfo) - : base(name, psSnapinInfo) - { - if (String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(fileName.Trim())) - { - throw PSTraceSource.NewArgumentNullException("fileName"); - } - - FileName = fileName.Trim(); - } - - /// - /// Get name of the assembly file - /// - /// Name of the assembly file - public string FileName { get; } - } - - internal enum UpdateAction - { - Add, - Remove, - None - } -} - - -#pragma warning restore 56506 \ No newline at end of file diff --git a/src/System.Management.Automation/engine/ScopedItemSearcher.cs b/src/System.Management.Automation/engine/ScopedItemSearcher.cs index b7e951450ce..fc67ee26374 100644 --- a/src/System.Management.Automation/engine/ScopedItemSearcher.cs +++ b/src/System.Management.Automation/engine/ScopedItemSearcher.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; @@ -10,11 +9,9 @@ namespace System.Management.Automation /// Enumerates the items matching a particular name in the scopes specified using /// the appropriate scoping lookup rules. /// - /// /// /// The type of items that the derived class returns. /// - /// internal abstract class ScopedItemSearcher : IEnumerator, IEnumerable { #region ctor @@ -22,32 +19,28 @@ internal abstract class ScopedItemSearcher : IEnumerator, IEnumerable /// /// Constructs a scoped item searcher. /// - /// /// /// The state of the engine instance to enumerate through the scopes. /// - /// /// /// The parsed name of the item to lookup. /// - /// /// /// If or /// is null. /// - /// internal ScopedItemSearcher( SessionStateInternal sessionState, VariablePath lookupPath) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } if (lookupPath == null) { - throw PSTraceSource.NewArgumentNullException("lookupPath"); + throw PSTraceSource.NewArgumentNullException(nameof(lookupPath)); } this.sessionState = sessionState; @@ -60,13 +53,11 @@ internal ScopedItemSearcher( #region IEnumerable/IEnumerator members /// - /// Gets the current object as an IEnumerator + /// Gets the current object as an IEnumerator. /// - /// /// /// The current object as an IEnumerator. /// - /// System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { return this; @@ -77,15 +68,12 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() return this; } - /// /// Moves the enumerator to the next matching scoped item. /// - /// /// /// True if another matching scoped item was found, or false otherwise. /// - /// public bool MoveNext() { bool result = true; @@ -108,6 +96,7 @@ public bool MoveNext() result = true; break; } + result = false; if (_isSingleScopeLookup) @@ -120,10 +109,8 @@ public bool MoveNext() } /// - /// Gets the current scoped item + /// Gets the current scoped item. /// - /// - T IEnumerator.Current { get @@ -132,7 +119,6 @@ T IEnumerator.Current } } - public object Current { get @@ -141,14 +127,11 @@ public object Current } } - - public void Reset() { InitializeScopeEnumerator(); } - public void Dispose() { _current = default(T); @@ -162,23 +145,18 @@ public void Dispose() /// Derived classes override this method to return their /// particular type of scoped item. /// - /// /// /// The scope to look the item up in. /// - /// /// /// The name of the item to retrieve. /// - /// /// /// The scope item that the derived class should return. /// - /// /// /// True if the scope item was found or false otherwise. /// - /// protected abstract bool GetScopeItem( SessionStateScope scope, VariablePath name, @@ -189,7 +167,6 @@ protected abstract bool GetScopeItem( /// /// Gets the lookup scope that the Current item was found in. /// - /// internal SessionStateScope CurrentLookupScope { get { return _currentScope; } @@ -200,11 +177,11 @@ internal SessionStateScope CurrentLookupScope /// /// Gets the scope in which the search begins. /// - /// internal SessionStateScope InitialScope { get { return _initialScope; } } + private SessionStateScope _initialScope; #region private members @@ -253,17 +230,16 @@ 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; #endregion private members - } // class ScopedItemSearcher - + } /// - /// The scope searcher for variables + /// The scope searcher for variables. /// internal class VariableScopeItemSearcher : ScopedItemSearcher { @@ -281,29 +257,24 @@ public VariableScopeItemSearcher( /// Derived classes override this method to return their /// particular type of scoped item. /// - /// /// /// The scope to look the item up in. /// - /// /// /// The name of the item to retrieve. /// - /// /// /// The scope item that the derived class should return. /// - /// /// /// True if the scope item was found or false otherwise. /// - /// protected override bool GetScopeItem( SessionStateScope scope, 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; @@ -323,10 +294,10 @@ protected override bool GetScopeItem( return result; } - } // VariableScopeItemSearcher + } /// - /// The scope searcher for aliases + /// The scope searcher for aliases. /// internal class AliasScopeItemSearcher : ScopedItemSearcher { @@ -340,29 +311,24 @@ public AliasScopeItemSearcher( /// Derived classes override this method to return their /// particular type of scoped item. /// - /// /// /// The scope to look the item up in. /// - /// /// /// The name of the item to retrieve. /// - /// /// /// The scope item that the derived class should return. /// - /// /// /// True if the scope item was found or false otherwise. /// - /// protected override bool GetScopeItem( SessionStateScope scope, 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; @@ -381,10 +347,10 @@ protected override bool GetScopeItem( return result; } - } // AliasScopeItemSearcher + } /// - /// The scope searcher for functions + /// The scope searcher for functions. /// internal class FunctionScopeItemSearcher : ScopedItemSearcher { @@ -398,28 +364,22 @@ public FunctionScopeItemSearcher( private readonly CommandOrigin _origin; - /// /// Derived classes override this method to return their /// particular type of scoped item. /// - /// /// /// The scope to look the item up in. /// - /// /// /// The name of the item to retrieve. /// - /// /// /// The scope item that the derived class should return. /// - /// /// /// True if the scope item was found or false otherwise. /// - /// protected override bool GetScopeItem( SessionStateScope scope, VariablePath path, @@ -434,7 +394,6 @@ protected override bool GetScopeItem( script = scope.GetFunction(_name); - if (script != null) { bool isPrivate; @@ -467,6 +426,7 @@ protected override bool GetScopeItem( { result = false; } + return result; } @@ -474,11 +434,12 @@ internal string Name { get { return _name; } } - private string _name = String.Empty; - } // FunctionScopeItemSearcher + + private string _name = string.Empty; + } /// - /// The scope searcher for drives + /// The scope searcher for drives. /// internal class DriveScopeItemSearcher : ScopedItemSearcher { @@ -492,29 +453,24 @@ public DriveScopeItemSearcher( /// Derived classes override this method to return their /// particular type of scoped item. /// - /// /// /// The scope to look the item up in. /// - /// /// /// The name of the item to retrieve. /// - /// /// /// The scope item that the derived class should return. /// - /// /// /// True if the scope item was found or false otherwise. /// - /// protected override bool GetScopeItem( SessionStateScope scope, 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; @@ -524,7 +480,8 @@ protected override bool GetScopeItem( { result = false; } + return result; } - } // DriveScopeItemSearcher -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/ScriptCommand.cs b/src/System.Management.Automation/engine/ScriptCommand.cs index 6c4e3f4d1a9..d64d6112171 100644 --- a/src/System.Management.Automation/engine/ScriptCommand.cs +++ b/src/System.Management.Automation/engine/ScriptCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; @@ -14,4 +13,4 @@ internal sealed class ScriptCommand : InternalCommand // This class just needs to exist so we have something to instantiate // to hold the pipeline connectors... } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs index 9f1151f4197..32bec9fd5a9 100644 --- a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs +++ b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs @@ -1,13 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Management.Automation.Internal; -using System.Reflection; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Reflection; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -18,10 +19,9 @@ namespace System.Management.Automation internal abstract class ScriptCommandProcessorBase : CommandProcessorBase { protected ScriptCommandProcessorBase(ScriptBlock scriptBlock, ExecutionContext context, bool useLocalScope, CommandOrigin origin, SessionStateInternal sessionState) + : base(new ScriptInfo(string.Empty, scriptBlock, context)) { this._dontUseScopeCommandOrigin = false; - this.CommandInfo = new ScriptInfo(String.Empty, scriptBlock, context); - this._fromScriptFile = false; CommonInitialization(scriptBlock, context, useLocalScope, origin, sessionState); @@ -48,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; @@ -67,6 +67,7 @@ protected ScriptCommandProcessorBase(IScriptCommandInfo commandInfo, ExecutionCo protected ScriptBlock _scriptBlock; private ScriptParameterBinderController _scriptParameterBinderController; + internal ScriptParameterBinderController ScriptParameterBinderController { get @@ -80,6 +81,7 @@ internal ScriptParameterBinderController ScriptParameterBinderController _scriptParameterBinderController.CommandLineParameters.UpdateInvocationInfo(this.Command.MyInvocation); this.Command.MyInvocation.UnboundArguments = _scriptParameterBinderController.DollarArgs; } + return _scriptParameterBinderController; } } @@ -120,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); } } @@ -128,9 +130,9 @@ protected void CommonInitialization(ScriptBlock scriptBlock, ExecutionContext co /// Checks if user has requested help (for example passing "-?" parameter for a cmdlet) /// and if yes, then returns the help target to display. /// - /// help target to request - /// help category to request - /// true if user requested help; false otherwise + /// Help target to request. + /// Help category to request. + /// 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) @@ -141,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; @@ -164,7 +165,6 @@ internal override bool IsHelpRequested(out string helpTarget, out HelpCategory h /// This class implements a command processor for script related commands. /// /// - /// /// 1. Usage scenarios /// /// ScriptCommandProcessor is used for four kinds of commands. @@ -228,17 +228,25 @@ internal override bool IsHelpRequested(out string helpTarget, out HelpCategory h /// If the command processor is created based on a script file, its exit exception /// handling is different in the sense that it indicates an exitcode instead of killing /// current powershell session. - /// /// internal sealed class DlrScriptCommandProcessor : ScriptCommandProcessorBase { - private new ScriptBlock _scriptBlock; private readonly ArrayList _input = new ArrayList(); + private readonly object _dollarUnderbar = AutomationNull.Value; + private new ScriptBlock _scriptBlock; 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) + : base(scriptBlock, context, useNewScope, origin, sessionState) + { + Init(); + _dollarUnderbar = dollarUnderbar; + } + internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState) : base(scriptBlock, context, useNewScope, origin, sessionState) { @@ -267,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) @@ -278,12 +286,13 @@ private void Init() } /// - /// Get the ObsoleteAttribute of the current command + /// Get the ObsoleteAttribute of the current command. /// internal override ObsoleteAttribute ObsoleteAttribute { get { return _obsoleteAttribute; } } + private ObsoleteAttribute _obsoleteAttribute; internal override void Prepare(IDictionary psDefaultParameterValues) @@ -319,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 @@ -402,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()) @@ -425,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); + } } } @@ -451,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, @@ -480,7 +516,26 @@ private void RunClause(Action clause, object dollarUnderbar, ob Context.LanguageMode = newLanguageMode.Value; } - EnterScope(); + bool? oldLangModeTransitionStatus = null; + try + { + // If it's from ConstrainedLanguage to FullLanguage, indicate the transition before parameter binding takes place. + if (oldLanguageMode == PSLanguageMode.ConstrainedLanguage && newLanguageMode == PSLanguageMode.FullLanguage) + { + oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding; + Context.LanguageModeTransitionInParameterBinding = true; + } + + EnterScope(); + } + finally + { + if (oldLangModeTransitionStatus.HasValue) + { + // Revert the transition state to old value after doing the parameter binding + Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus.Value; + } + } if (commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output) { @@ -495,6 +550,10 @@ private void RunClause(Action clause, object dollarUnderbar, ob { _localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnderbar, _context); } + else if (_dollarUnderbar != AutomationNull.Value) + { + _localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, _dollarUnderbar, _context); + } if (inputToProcess != AutomationNull.Value) { @@ -522,7 +581,7 @@ private void RunClause(Action clause, object dollarUnderbar, ob } finally { - this.Context.RestoreErrorPipe(oldErrorOutputPipe); + Context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe; if (oldLanguageMode.HasValue) { @@ -553,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 95e756826ff..1abdc1424bf 100644 --- a/src/System.Management.Automation/engine/ScriptInfo.cs +++ b/src/System.Management.Automation/engine/ScriptInfo.cs @@ -1,14 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Runspaces; using System.Collections.ObjectModel; +using System.Management.Automation.Runspaces; 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 { @@ -17,33 +16,28 @@ public class ScriptInfo : CommandInfo, IScriptCommandInfo /// /// Creates an instance of the ScriptInfo class with the specified name, and script. /// - /// /// /// The name of the script. /// - /// /// /// The script definition /// - /// /// /// The execution context for the script. /// - /// /// /// If is null. /// - /// internal ScriptInfo(string name, ScriptBlock script, ExecutionContext context) : base(name, CommandTypes.Script, context) { if (script == null) { - throw PSTraceSource.NewArgumentException("script"); + throw PSTraceSource.NewArgumentException(nameof(script)); } this.ScriptBlock = script; - } // ScriptInfo ctor + } /// /// This is a copy constructor, used primarily for get-command. @@ -74,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 @@ -91,7 +85,7 @@ public override string Definition } /// - /// The output type(s) is specified in the script block + /// The output type(s) is specified in the script block. /// public override ReadOnlyCollection OutputType { @@ -99,7 +93,7 @@ public override ReadOnlyCollection OutputType } /// - /// for diagnostic purposes + /// For diagnostic purposes. /// /// public override string ToString() @@ -122,11 +116,11 @@ 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()); } } + private CommandMetadata _commandMetadata; - } // ScriptInfo -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs index 6a8fb2fe828..0e034289254 100644 --- a/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Security.AccessControl; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -11,7 +11,6 @@ namespace System.Management.Automation /// /// Provides the *-SecurityDescriptor noun for the cmdlet providers. /// - /// public sealed class SecurityDescriptorCmdletProviderIntrinsics { #region Constructors @@ -24,14 +23,12 @@ private SecurityDescriptorCmdletProviderIntrinsics() Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // CmdletProviderIntrinsics private - + } /// /// Initializes a new instance of the SecurityDescriptorCmdletProviderIntrinsics /// class, using the Cmdlet parameter to obtain access to the SessionState APIs. /// - /// /// /// An instance of the cmdlet. /// @@ -39,18 +36,17 @@ internal SecurityDescriptorCmdletProviderIntrinsics(Cmdlet cmdlet) { if (cmdlet == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); + throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); } _cmdlet = cmdlet; _sessionState = cmdlet.Context.EngineSessionState; - } // CmdletProviderIntrinsics internal + } /// /// Initializes a new instance of the SecurityDescriptorCmdletProviderIntrinsics /// class, using the sessionState parameter to obtain access to the SessionState APIs. /// - /// /// /// An instance of the real session state class. /// @@ -58,11 +54,11 @@ internal SecurityDescriptorCmdletProviderIntrinsics(SessionStateInternal session { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // CmdletProviderIntrinsics internal + } #endregion Constructors @@ -74,7 +70,6 @@ internal SecurityDescriptorCmdletProviderIntrinsics(SessionStateInternal session /// Gets the SecurityDescriptor at the specified path, including only the specified /// AccessControlSections. /// - /// /// /// The path of the item to retrieve. It may be a drive or provider-qualified path and may include. /// glob characters. @@ -82,7 +77,6 @@ internal SecurityDescriptorCmdletProviderIntrinsics(SessionStateInternal session /// /// The sections of the security descriptor to include. /// - /// /// /// The SecurityDescriptor(s) at the specified path. /// @@ -94,13 +88,12 @@ public Collection Get(string path, AccessControlSections includeSectio // Parameter validation is done in the session state object return _sessionState.GetSecurityDescriptor(path, includeSections); - } // GetSecurityDescriptor + } /// /// Gets the SecurityDescriptor at the specified path, including only the specified /// AccessControlSections, using the provided Context. /// - /// /// /// The path of the item to retrieve. It may be a drive or provider-qualified path and may include /// glob characters. @@ -111,7 +104,6 @@ public Collection Get(string path, AccessControlSections includeSectio /// /// The context under which the command is running. /// - /// /// /// Nothing. The object(s) at the specified path are written to the context. /// @@ -125,7 +117,7 @@ internal void Get(string path, // Parameter validation is done in the session state object _sessionState.GetSecurityDescriptor(path, includeSections, context); - } // GetSecurityDescriptor + } #endregion GetSecurityDescriptor @@ -134,7 +126,6 @@ internal void Get(string path, /// /// Sets the provided SecurityDescriptor at the specified path. /// - /// /// /// The path of the item to set. It may be a drive or provider-qualified path and may include /// glob characters. @@ -142,7 +133,6 @@ internal void Get(string path, /// /// The new security descriptor to set. /// - /// /// /// The SecurityDescriptor(s) set at the specified path. /// @@ -156,12 +146,11 @@ public Collection Set(string path, ObjectSecurity sd) Collection result = _sessionState.SetSecurityDescriptor(path, sd); return result; - } // SetSecurityDescriptor + } /// /// Sets the SecurityDescriptor at the specified path, using the provided Context. /// - /// /// /// The path of the item to set. It may be a drive or provider-qualified path and may include /// glob characters. @@ -172,7 +161,6 @@ public Collection Set(string path, ObjectSecurity sd) /// /// The context under which the command is running. /// - /// /// /// Nothing. The object(s) set at the specified path are written to the context. /// @@ -185,7 +173,7 @@ internal void Set(string path, ObjectSecurity sd, CmdletProviderContext context) // Parameter validation is done in the session state object _sessionState.SetSecurityDescriptor(path, sd, context); - } // SetSecurityDescriptor + } #endregion SetSecurityDescriptor @@ -195,7 +183,6 @@ internal void Set(string path, ObjectSecurity sd, CmdletProviderContext context) /// Creates a new SecurityDescriptor from the item at the specified path, including only the specified /// AccessControlSections. /// - /// /// /// The path of the item to retrieve. It may be a drive or provider-qualified path and may include /// glob characters. @@ -203,7 +190,6 @@ internal void Set(string path, ObjectSecurity sd, CmdletProviderContext context) /// /// The sections of the security descriptor to include. /// - /// /// /// The SecurityDescriptor(s) at the specified path. /// @@ -215,14 +201,12 @@ public ObjectSecurity NewFromPath(string path, AccessControlSections includeSect // Parameter validation is done in the session state object return _sessionState.NewSecurityDescriptorFromPath(path, includeSections); - } // NewSecurityDescriptor - + } /// /// Creates a new SecurityDescriptor from the specified provider and of the given type, /// including only the specified AccessControlSections. /// - /// /// /// The name of the provider. /// @@ -233,11 +217,9 @@ public ObjectSecurity NewFromPath(string path, AccessControlSections includeSect /// /// The sections of the security descriptor to include. /// - /// /// /// A new SecurityDescriptor of the specified type. /// - /// public ObjectSecurity NewOfType(string providerId, string type, AccessControlSections includeSections) { Dbg.Diagnostics.Assert( @@ -249,7 +231,7 @@ public ObjectSecurity NewOfType(string providerId, string type, AccessControlSec return _sessionState.NewSecurityDescriptorOfType(providerId, type, includeSections); - } // NewSecurityDescriptor + } #endregion NewSecurityDescriptor @@ -257,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 - } // SecurityDescriptorCmdletProviderIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/SecurityManagerBase.cs b/src/System.Management.Automation/engine/SecurityManagerBase.cs index 4cea5e16922..0e4f3359704 100644 --- a/src/System.Management.Automation/engine/SecurityManagerBase.cs +++ b/src/System.Management.Automation/engine/SecurityManagerBase.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using Dbg = System.Management.Automation; using System.Management.Automation.Host; +using Dbg = System.Management.Automation; + namespace System.Management.Automation { /// @@ -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 @@ -44,7 +44,7 @@ public class AuthorizationManager #region constructor /// - /// Creates an instance of authorization manager using specified shellID + /// Creates an instance of authorization manager using specified shellID. /// /// /// @@ -55,29 +55,23 @@ public AuthorizationManager(string shellId) #endregion constructor - private object _policyCheckLock = new object(); + private readonly object _policyCheckLock = new object(); #region methods to use internally /// - /// determine if we should run the specified file + /// Determine if we should run the specified file. /// - /// - /// info on entity to be run - /// - /// the dispatch origin of a command - /// - /// allows access to the host. - /// + /// Info on entity to be run. + /// The dispatch origin of a command. + /// Allows access to the host. /// /// This method throws SecurityException in case running is not allowed. /// - /// /// /// If the derived security manager threw an exception or returned /// false with a reason. /// - /// internal void ShouldRunInternal(CommandInfo commandInfo, CommandOrigin origin, PSHost host) @@ -91,12 +85,11 @@ internal void ShouldRunInternal(CommandInfo commandInfo, return; #else - #if DEBUG // If we are debugging, let the unit tests swap the file from beneath us - if(commandInfo.CommandType == CommandTypes.ExternalScript) + if (commandInfo.CommandType == CommandTypes.ExternalScript) { - while(Environment.GetEnvironmentVariable("PSCommandDiscoveryPreDelay") != null) { System.Threading.Thread.Sleep(100); } + while (Environment.GetEnvironmentVariable("PSCommandDiscoveryPreDelay") != null) { System.Threading.Thread.Sleep(100); } } #endif @@ -113,9 +106,9 @@ internal void ShouldRunInternal(CommandInfo commandInfo, #if DEBUG // If we are debugging, let the unit tests swap the file from beneath us - if(commandInfo.CommandType == CommandTypes.ExternalScript) + if (commandInfo.CommandType == CommandTypes.ExternalScript) { - while(Environment.GetEnvironmentVariable("PSCommandDiscoveryPostDelay") != null) { System.Threading.Thread.Sleep(100); } + while (Environment.GetEnvironmentVariable("PSCommandDiscoveryPostDelay") != null) { System.Threading.Thread.Sleep(100); } } #endif } @@ -168,17 +161,11 @@ internal void ShouldRunInternal(CommandInfo commandInfo, /// Determines if the host should run the command a specified by the CommandInfo parameter. /// The default implementation gives permission to run every command. /// - /// - /// Information about the command to be run - /// - /// The origin of the command - /// - /// The host running the command - /// - /// The reason for preventing execution, if applicable - /// - /// True if the host should run the command. False otherwise - /// + /// Information about the command to be run. + /// The origin of the command. + /// The host running the command. + /// The reason for preventing execution, if applicable. + /// True if the host should run the command. False otherwise. protected internal virtual bool ShouldRun(CommandInfo commandInfo, CommandOrigin origin, PSHost host, @@ -194,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 9cc1ecd970d..81b0f9cb741 100644 --- a/src/System.Management.Automation/engine/SerializationStrings.cs +++ b/src/System.Management.Automation/engine/SerializationStrings.cs @@ -1,59 +1,57 @@ - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { /// - /// This class contains strings required for serialization + /// This class contains strings required for serialization. /// internal static class SerializationStrings { #region element tags /// - /// Element tag for root node + /// Element tag for root node. /// internal const string RootElementTag = "Objs"; #region PSObject /// - /// Element tag for PSObject + /// Element tag for PSObject. /// internal const string PSObjectTag = "Obj"; /// - /// Element tag for properties + /// Element tag for properties. /// internal const string AdapterProperties = "Props"; /// - /// TypeNames tag + /// TypeNames tag. /// internal const string TypeNamesTag = "TN"; /// - /// Tag for type item in typenames + /// Tag for type item in typenames. /// internal const string TypeNamesItemTag = "T"; /// - /// TypeName reference + /// TypeName reference. /// internal const string TypeNamesReferenceTag = "TNRef"; /// - /// Memberset + /// Memberset. /// internal const string MemberSet = "MS"; /// - /// Individual notes + /// Individual notes. /// internal const string NoteProperty = "N"; /// - /// Tag for ToString value + /// Tag for ToString value. /// internal const string ToStringElementTag = "ToString"; @@ -67,37 +65,37 @@ internal static class SerializationStrings internal const string CollectionTag = "IE"; /// - /// Element tag used for Dictionary + /// Element tag used for Dictionary. /// internal const string DictionaryTag = "DCT"; /// - /// Element tag used for Dictionary entry + /// Element tag used for Dictionary entry. /// internal const string DictionaryEntryTag = "En"; /// - /// Value of name attribute for dictionary key part in dictionary entry + /// Value of name attribute for dictionary key part in dictionary entry. /// internal const string DictionaryKey = "Key"; /// - /// Value of name attribute for dictionary value part in dictionary entry + /// Value of name attribute for dictionary value part in dictionary entry. /// internal const string DictionaryValue = "Value"; /// - /// Element tag used for Stack + /// Element tag used for Stack. /// internal const string StackTag = "STK"; /// - /// Element tag used for Queue + /// Element tag used for Queue. /// internal const string QueueTag = "QUE"; /// - /// Element tag used for List + /// Element tag used for List. /// internal const string ListTag = "LST"; @@ -201,7 +199,7 @@ internal static class SerializationStrings internal const string StringTag = "S"; /// - /// Element tag for secure string property + /// Element tag for secure string property. /// /// This property is used for System.Security.SecureString type internal const string SecureStringTag = "SS"; @@ -231,22 +229,22 @@ internal static class SerializationStrings internal const string AnyUriTag = "URI"; /// - /// Element tag for Version property + /// Element tag for Version property. /// internal const string VersionTag = "Version"; /// - /// Element tag for SemanticVersion property + /// Element tag for SemanticVersion property. /// internal const string SemanticVersionTag = "SemanticVersion"; /// - /// Element tag for XmlDocument + /// Element tag for XmlDocument. /// internal const string XmlDocumentTag = "XD"; /// - /// Element tag for property whose value is null + /// Element tag for property whose value is null. /// internal const string NilTag = "Nil"; @@ -276,22 +274,22 @@ internal static class SerializationStrings #region attribute tags /// - /// String for reference id attribute + /// String for reference id attribute. /// internal const string ReferenceIdAttribute = "RefId"; /// - /// String for name attribute + /// String for name attribute. /// internal const string NameAttribute = "N"; /// - /// String for version attribute + /// String for version attribute. /// internal const string VersionAttribute = "Version"; /// - /// String for stream attribute + /// String for stream attribute. /// internal const string StreamNameAttribute = "S"; @@ -300,16 +298,15 @@ internal static class SerializationStrings #region namespace values /// - /// Monad namespace + /// Monad namespace. /// internal const string MonadNamespace = "http://schemas.microsoft.com/powershell/2004/04"; /// - /// Prefix string for monad namespace + /// Prefix string for monad namespace. /// 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 51a631afe38..a53c211beb2 100644 --- a/src/System.Management.Automation/engine/SessionState.cs +++ b/src/System.Management.Automation/engine/SessionState.cs @@ -1,23 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Language; -using System.Security; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Security; + using Dbg = System.Management.Automation; -using System.Diagnostics.CodeAnalysis; namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// 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 { @@ -27,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"); @@ -39,17 +39,14 @@ internal sealed partial class SessionStateInternal #region Constructor /// - /// Constructor for session state object + /// Constructor for session state object. /// - /// /// /// The context for the runspace to which this session state object belongs. /// - /// /// /// if is null. /// - /// internal SessionStateInternal(ExecutionContext context) : this(null, false, context) { } @@ -58,14 +55,19 @@ internal SessionStateInternal(SessionStateInternal parent, bool linkToGlobal, Ex { if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } + ExecutionContext = context; // Create the working directory stack. This // is used for the pushd and popd commands - _workingLocationStack = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _workingLocationStack = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + // Conservative choice to limit the Set-Location history in order to limit memory impact in case of a regression. + const int locationHistoryLimit = 20; + _setLocationHistory = new HistoryStack(locationHistoryLimit); GlobalScope = new SessionStateScope(null); ModuleScope = GlobalScope; @@ -78,7 +80,6 @@ internal SessionStateInternal(SessionStateInternal parent, bool linkToGlobal, Ex // that uses variables qualified by script: it works. GlobalScope.ScriptScope = GlobalScope; - if (parent != null) { GlobalScope.Parent = parent.GlobalScope; @@ -123,7 +124,6 @@ internal void InitializeSessionStateInternalSpecialVariables(bool clearVariables PSVariable errorvariable = new PSVariable("Error", new ArrayList(), ScopedItemOptions.Constant); GlobalScope.SetVariable(errorvariable.Name, errorvariable, false, false, this, fastPath: true); - // Set variable $PSDefaultParameterValues Collection attributes = new Collection(); attributes.Add(new ArgumentTypeConverterAttribute(typeof(System.Management.Automation.DefaultParameterDictionary))); @@ -134,7 +134,6 @@ internal void InitializeSessionStateInternalSpecialVariables(bool clearVariables GlobalScope.SetVariable(psDefaultParameterValuesVariable.Name, psDefaultParameterValuesVariable, false, false, this, fastPath: true); } - #endregion Constructor #region Private data @@ -142,11 +141,11 @@ internal void InitializeSessionStateInternalSpecialVariables(bool clearVariables /// /// Provides all the path manipulation and globbing for Monad paths. /// - /// internal LocationGlobber Globber { - get { return _globberPrivate ?? (_globberPrivate = ExecutionContext.LocationGlobber); } + get { return _globberPrivate ??= ExecutionContext.LocationGlobber; } } + private LocationGlobber _globberPrivate; /// @@ -159,22 +158,25 @@ internal LocationGlobber Globber /// internal SessionState PublicSessionState { - get { return _publicSessionState ?? (_publicSessionState = new SessionState(this)); } + get { return _publicSessionState ??= new SessionState(this); } + set { _publicSessionState = value; } } + private SessionState _publicSessionState; /// - /// Gets the engine APIs to access providers + /// Gets the engine APIs to access providers. /// internal ProviderIntrinsics InvokeProvider { - get { return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); } // get + get { return _invokeProvider ??= new ProviderIntrinsics(this); } } + private ProviderIntrinsics _invokeProvider; /// - /// The module info object associated with this session state + /// The module info object associated with this session state. /// internal PSModuleInfo Module { get; set; } = null; @@ -188,7 +190,7 @@ internal ProviderIntrinsics InvokeProvider internal Dictionary ModuleTable { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// - /// Get/set constraints for this execution environment + /// Get/set constraints for this execution environment. /// internal PSLanguageMode LanguageMode { @@ -196,6 +198,7 @@ internal PSLanguageMode LanguageMode { return ExecutionContext.LanguageMode; } + set { ExecutionContext.LanguageMode = value; @@ -203,7 +206,7 @@ internal PSLanguageMode LanguageMode } /// - /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode + /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode. /// internal bool UseFullLanguageModeInDebugger { @@ -222,8 +225,8 @@ internal bool UseFullLanguageModeInDebugger /// /// See if a script is allowed to be run. /// - /// Path to check - /// true if script is allowed + /// Path to check. + /// True if script is allowed. internal SessionStateEntryVisibility CheckScriptVisibility(string scriptPath) { return checkPathVisibility(Scripts, scriptPath); @@ -235,7 +238,6 @@ internal SessionStateEntryVisibility CheckScriptVisibility(string scriptPath) /// public List Applications { get; } = new List(new string[] { "*" }); - /// /// List of functions/filters to export from this session state object... /// @@ -250,7 +252,7 @@ internal SessionStateEntryVisibility CheckScriptVisibility(string scriptPath) /// /// Add an new SessionState cmdlet entry to this session state object... /// - /// The entry to add + /// The entry to add. internal void AddSessionStateEntry(SessionStateCmdletEntry entry) { AddSessionStateEntry(entry, /*local*/false); @@ -259,8 +261,8 @@ internal void AddSessionStateEntry(SessionStateCmdletEntry entry) /// /// Add an new SessionState cmdlet entry to this session state object... /// - /// The entry to add - /// If local, add cmdlet to current scope. Else, add to module scope + /// The entry to add. + /// If local, add cmdlet to current scope. Else, add to module scope. internal void AddSessionStateEntry(SessionStateCmdletEntry entry, bool local) { ExecutionContext.CommandDiscovery.AddSessionStateCmdletEntryToCache(entry, local); @@ -269,7 +271,7 @@ internal void AddSessionStateEntry(SessionStateCmdletEntry entry, bool local) /// /// Add an new SessionState cmdlet entry to this session state object... /// - /// The entry to add + /// The entry to add. internal void AddSessionStateEntry(SessionStateApplicationEntry entry) { this.Applications.Add(entry.Path); @@ -278,7 +280,7 @@ internal void AddSessionStateEntry(SessionStateApplicationEntry entry) /// /// Add an new SessionState cmdlet entry to this session state object... /// - /// The entry to add + /// The entry to add. internal void AddSessionStateEntry(SessionStateScriptEntry entry) { this.Scripts.Add(entry.Path); @@ -301,7 +303,7 @@ internal void InitializeFixedVariables() ExecutionContext.EngineHostInterface, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSHostDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $HOME - indicate where a user's home directory is located in the file system. // -- %USERPROFILE% on windows @@ -311,45 +313,44 @@ internal void InitializeFixedVariables() home, ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope, RunspaceInit.HOMEDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $ExecutionContext v = new PSVariable(SpecialVariables.ExecutionContext, ExecutionContext.EngineIntrinsics, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.ExecutionContextDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $PSVersionTable v = new PSVariable(SpecialVariables.PSVersionTable, PSVersionInfo.GetPSVersionTable(), ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSVersionTableDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $PSEdition v = new PSVariable(SpecialVariables.PSEdition, PSVersionInfo.PSEditionValue, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSEditionDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + 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, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $PSCulture v = new PSCultureVariable(); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariableForce(v, this); // $PSUICulture v = new PSUICultureVariable(); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariableForce(v, this); // $? v = new QuestionMarkVariable(this.ExecutionContext); @@ -357,99 +358,59 @@ internal void InitializeFixedVariables() // $ShellId - if there is no runspace config, use the default string string shellId = ExecutionContext.ShellID; - v = new PSVariable(SpecialVariables.ShellId, shellId, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.MshShellIdDescription); - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $PSHOME - // This depends on the shellId. If we cannot read the application base - // registry key, set the variable to empty string - string applicationBase = ""; - try - { - applicationBase = Utils.GetApplicationBase(shellId); - } - catch (SecurityException) - { - } + string applicationBase = Utils.DefaultPowerShellAppBase; v = new PSVariable(SpecialVariables.PSHome, applicationBase, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSHOMEDescription); - - this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); + + // $EnabledExperimentalFeatures + v = new PSVariable(SpecialVariables.EnabledExperimentalFeatures, + ExperimentalFeature.EnabledExperimentalFeatureNames, + ScopedItemOptions.Constant | ScopedItemOptions.AllScope, + RunspaceInit.EnabledExperimentalFeatures); + this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); } /// - /// Add all of the default built-in functions to this session state instance... + /// Check to see if an application is allowed to be run. /// - internal void AddBuiltInEntries(bool addSetStrictMode) + /// The path to the application to check. + /// True if application is permitted. + internal SessionStateEntryVisibility CheckApplicationVisibility(string applicationPath) { - // Other built-in variables - AddBuiltInVariables(); - AddBuiltInFunctions(); - AddBuiltInAliases(); - if (addSetStrictMode) - { - SessionStateFunctionEntry f = new SessionStateFunctionEntry("Set-StrictMode", ""); - this.AddSessionStateEntry(f); - } + return checkPathVisibility(Applications, applicationPath); } - /// - /// Add the built-in variables to this instance of session state... - /// - internal void AddBuiltInVariables() + private static SessionStateEntryVisibility checkPathVisibility(List list, string path) { - foreach (SessionStateVariableEntry e in InitialSessionState.BuiltInVariables) + if (list == null || list.Count == 0) { - this.AddSessionStateEntry(e); + return SessionStateEntryVisibility.Private; } - } - /// - /// Add the built-in functions to this instance of session state... - /// - internal void AddBuiltInFunctions() - { - foreach (SessionStateFunctionEntry f in InitialSessionState.BuiltInFunctions) + if (string.IsNullOrEmpty(path)) { - this.AddSessionStateEntry(f); + return SessionStateEntryVisibility.Private; } - } - /// - /// Add the built-in aliases to this instance of session state... - /// - internal void AddBuiltInAliases() - { - foreach (SessionStateAliasEntry ae in InitialSessionState.BuiltInAliases) + if (list.Contains("*")) { - this.AddSessionStateEntry(ae, StringLiterals.Global); + return SessionStateEntryVisibility.Public; } - } - /// - /// Check to see if an application is allowed to be run. - /// - /// The path to the application to check - /// True if application is permitted. - internal SessionStateEntryVisibility CheckApplicationVisibility(string applicationPath) - { - return checkPathVisibility(Applications, applicationPath); - } - - private SessionStateEntryVisibility checkPathVisibility(List list, string path) - { - if (list == null || list.Count == 0) return SessionStateEntryVisibility.Private; - if (String.IsNullOrEmpty(path)) return SessionStateEntryVisibility.Private; - - if (list.Contains("*")) return SessionStateEntryVisibility.Public; foreach (string p in list) { - if (String.Equals(p, path, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(p, path, StringComparison.OrdinalIgnoreCase)) + { return SessionStateEntryVisibility.Public; + } if (WildcardPattern.ContainsWildcardCharacters(p)) { @@ -460,6 +421,7 @@ private SessionStateEntryVisibility checkPathVisibility(List list, strin } } } + return SessionStateEntryVisibility.Private; } @@ -469,7 +431,6 @@ private SessionStateEntryVisibility checkPathVisibility(List list, strin /// Notification for SessionState to do cleanup /// before runspace is closed. /// - /// internal void RunspaceClosingNotification() { if (this != ExecutionContext.TopLevelSessionState && Providers.Count > 0) @@ -491,40 +452,32 @@ internal void RunspaceClosingNotification() /// /// Constructs a new instance of a ProviderInvocationException - /// using the specified data + /// using the specified data. /// - /// /// /// The resource ID to use as the format message for the error. /// - /// /// /// This is the message template string. /// - /// /// /// The provider information used when formatting the error message. /// - /// /// /// The path used when formatting the error message. /// - /// /// /// The exception that was thrown by the provider. This will be set as /// the ProviderInvocationException's InnerException and the message will /// be used when formatting the error message. /// - /// /// /// A new instance of a ProviderInvocationException. /// - /// /// /// Wraps in a ProviderInvocationException /// and then throws it. /// - /// internal ProviderInvocationException NewProviderInvocationException( string resourceId, string resourceStr, @@ -535,48 +488,38 @@ internal ProviderInvocationException NewProviderInvocationException( return NewProviderInvocationException(resourceId, resourceStr, provider, path, e, true); } - /// /// Constructs a new instance of a ProviderInvocationException - /// using the specified data + /// using the specified data. /// - /// /// /// The resource ID to use as the format message for the error. /// - /// /// /// This is the message template string. /// - /// /// /// The provider information used when formatting the error message. /// - /// /// /// The path used when formatting the error message. /// - /// /// /// The exception that was thrown by the provider. This will be set as /// the ProviderInvocationException's InnerException and the message will /// be used when formatting the error message. /// - /// /// /// If true, the error record from the inner exception will be used if it contains one. /// If false, the error message specified by the resourceId will be used. /// - /// /// /// A new instance of a ProviderInvocationException. /// - /// /// /// Wraps in a ProviderInvocationException /// and then throws it. /// - /// internal ProviderInvocationException NewProviderInvocationException( string resourceId, string resourceStr, @@ -590,7 +533,7 @@ internal ProviderInvocationException NewProviderInvocationException( // ProviderInvocationException, and we don't want to // re-wrap it. ProviderInvocationException pie = e as ProviderInvocationException; - if (null != pie) + if (pie != null) { pie._providerInfo = provider; return pie; @@ -609,5 +552,5 @@ internal ProviderInvocationException NewProviderInvocationException( return pie; } #endregion Errors - } // SessionStateInternal class + } } diff --git a/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs b/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs index 57e79c33bfa..fa2cf65e170 100644 --- a/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation; +using Dbg = System.Management.Automation; namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -19,7 +18,7 @@ internal sealed partial class SessionStateInternal /// /// Add a new alias entry to this session state object... /// - /// The entry to add + /// The entry to add. /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope @@ -39,13 +38,14 @@ internal void AddSessionStateEntry(SessionStateAliasEntry entry, string scopeID) } /// - /// Gets an IEnumerable for the alias table + /// Gets an IEnumerable for the alias table. /// - /// internal IDictionary GetAliasTable() { + // On 7.0 version we have 132 aliases so we set a larger number to reduce re-allocations. + const int InitialAliasCount = 150; Dictionary result = - new Dictionary(StringComparer.OrdinalIgnoreCase); + new Dictionary(InitialAliasCount, StringComparer.OrdinalIgnoreCase); SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(_currentScope); @@ -69,28 +69,24 @@ internal IDictionary GetAliasTable() } return result; - } // GetAliasTable + } /// - /// Gets an IEnumerable for the alias table for a given scope + /// Gets an IEnumerable for the alias table for a given scope. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal IDictionary GetAliasTableAtScope(string scopeID) { Dictionary result = @@ -111,38 +107,33 @@ internal IDictionary GetAliasTableAtScope(string scopeID) } return result; - } // GetAliasTableAtScope + } /// /// List of aliases to export from this session state object... /// internal List ExportedAliases { get; } = new List(); - /// /// Gets the value of the specified alias from the alias table. /// - /// /// /// The name of the alias value to retrieve. /// - /// /// /// The origin of the command calling this API. /// /// /// The AliasInfo representing the alias. /// - /// internal AliasInfo GetAlias(string aliasName, CommandOrigin origin) { AliasInfo result = null; - if (String.IsNullOrEmpty(aliasName)) + if (string.IsNullOrEmpty(aliasName)) { return null; } - // Use the scope enumerator to find the alias using the // appropriate scoping rules @@ -174,20 +165,17 @@ internal AliasInfo GetAlias(string aliasName, CommandOrigin origin) } return result; - } // GetAlias + } /// /// Gets the value of the specified alias from the alias table. /// - /// /// /// The name of the alias value to retrieve. /// - /// /// /// The AliasInfo representing the alias. /// - /// internal AliasInfo GetAlias(string aliasName) { return GetAlias(aliasName, CommandOrigin.Internal); @@ -196,35 +184,29 @@ internal AliasInfo GetAlias(string aliasName) /// /// Gets the value of the specified alias from the alias table. /// - /// /// /// The name of the alias value to retrieve. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// The AliasInfo representing the alias. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal AliasInfo GetAliasAtScope(string aliasName, string scopeID) { AliasInfo result = null; - if (String.IsNullOrEmpty(aliasName)) + if (string.IsNullOrEmpty(aliasName)) { return null; } @@ -243,87 +225,72 @@ internal AliasInfo GetAliasAtScope(string aliasName, string scopeID) } return result; - } // GetAliasAtScope + } /// /// Sets the alias with specified name to the specified value in the current scope. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value to set the alias to. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// THe origin of the caller of this API /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If or is null or empty. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue(string aliasName, string value, bool force, CommandOrigin origin) { - if (String.IsNullOrEmpty(aliasName)) + if (string.IsNullOrEmpty(aliasName)) { - throw PSTraceSource.NewArgumentException("aliasName"); + throw PSTraceSource.NewArgumentException(nameof(aliasName)); } - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { - throw PSTraceSource.NewArgumentException("value"); + throw PSTraceSource.NewArgumentException(nameof(value)); } AliasInfo info = _currentScope.SetAliasValue(aliasName, value, this.ExecutionContext, force, origin); return info; - } // SetAliasValue + } /// /// Sets the alias with specified name to the specified value in the current scope. /// BUGBUG: this overload only exists for the test suites. They should be cleaned up /// and this overload removed. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value to set the alias to. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If or is null or empty. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue(string aliasName, string value, bool force) { return SetAliasValue(aliasName, value, force, CommandOrigin.Internal); @@ -332,39 +299,30 @@ internal AliasInfo SetAliasValue(string aliasName, string value, bool force) /// /// Sets the alias with specified name to the specified value in the current scope. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value to set the alias to. /// - /// /// /// The options to set on the alias. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If or is null or empty. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue( string aliasName, string value, @@ -372,54 +330,46 @@ internal AliasInfo SetAliasValue( bool force, CommandOrigin origin) { - if (String.IsNullOrEmpty(aliasName)) + if (string.IsNullOrEmpty(aliasName)) { - throw PSTraceSource.NewArgumentException("aliasName"); + throw PSTraceSource.NewArgumentException(nameof(aliasName)); } - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { - throw PSTraceSource.NewArgumentException("value"); + throw PSTraceSource.NewArgumentException(nameof(value)); } AliasInfo info = _currentScope.SetAliasValue(aliasName, value, options, this.ExecutionContext, force, origin); return info; - } // SetAliasValue + } /// /// Sets the alias with specified name to the specified value in the current scope. /// BUGBUG: this api only exists for the test suites. They should be fixed and it should be removed. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value to set the alias to. /// - /// /// /// The options to set on the alias. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If or is null or empty. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue( string aliasName, string value, @@ -432,100 +382,83 @@ internal AliasInfo SetAliasValue( /// /// Sets the alias with specified name to the specified value in the current scope. /// - /// /// /// The AliasInfo representing the alias. /// - /// /// /// If true, the alias will be set even if there is an existing ReadOnly /// alias. /// - /// /// /// Specifies the origin of the command setting the alias. /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If is null. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasItem(AliasInfo alias, bool force, CommandOrigin origin) { if (alias == null) { - throw PSTraceSource.NewArgumentNullException("alias"); + throw PSTraceSource.NewArgumentNullException(nameof(alias)); } AliasInfo info = _currentScope.SetAliasItem(alias, force, origin); return info; - } // SetAliasItem + } /// /// Sets the alias with specified name to the specified value in the current scope. /// - /// /// /// The AliasInfo representing the alias. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// If true, the alias will be set even if there is an existing ReadOnly /// alias. /// - /// /// /// Specifies the command origin of the calling command. /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If is null. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasItemAtScope(AliasInfo alias, string scopeID, bool force, CommandOrigin origin) { if (alias == null) { - throw PSTraceSource.NewArgumentNullException("alias"); + throw PSTraceSource.NewArgumentNullException(nameof(alias)); } // If the "private" scope was specified, make sure the options contain // the Private flag - if (String.Equals(scopeID, StringLiterals.Private, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(scopeID, StringLiterals.Private, StringComparison.OrdinalIgnoreCase)) { alias.Options |= ScopedItemOptions.Private; } @@ -535,49 +468,40 @@ internal AliasInfo SetAliasItemAtScope(AliasInfo alias, string scopeID, bool for AliasInfo info = scope.SetAliasItem(alias, force, origin); return info; - } // SetAliasItemAtScope + } /// /// Sets the alias with specified name to the specified value in the current scope. /// - /// /// /// The AliasInfo representing the alias. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// If true, the alias will be set even if there is an existing ReadOnly /// alias. /// - /// /// /// The resulting AliasInfo for the alias that was set. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If is null. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasItemAtScope(AliasInfo alias, string scopeID, bool force) { return SetAliasItemAtScope(alias, scopeID, force, CommandOrigin.Internal); @@ -586,28 +510,23 @@ internal AliasInfo SetAliasItemAtScope(AliasInfo alias, string scopeID, bool for /// /// Removes the specified alias. /// - /// /// /// The name of the alias to remove. /// - /// /// /// If true the alias will be removed even if its ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If the alias is constant. /// - /// internal void RemoveAlias(string aliasName, bool force) { - if (String.IsNullOrEmpty(aliasName)) + if (string.IsNullOrEmpty(aliasName)) { - throw PSTraceSource.NewArgumentException("aliasName"); + throw PSTraceSource.NewArgumentException(nameof(aliasName)); } // Use the scope enumerator to find an existing function @@ -620,7 +539,6 @@ internal void RemoveAlias(string aliasName, bool force) AliasInfo alias = scope.GetAlias(aliasName); - if (alias != null) { // Make sure the alias isn't private or if it is that the current @@ -639,7 +557,7 @@ internal void RemoveAlias(string aliasName, bool force) } } } - } // RemoveAlias + } /// /// Gets the aliases by command name (used by metadata-driven help) @@ -663,7 +581,5 @@ internal IEnumerable GetAliasesByCommandName(string command) } #endregion aliases - } // SessionStateInternal class + } } - - diff --git a/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs b/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs index 320a23a20e8..5afa8f0169f 100644 --- a/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs @@ -1,15 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using Dbg = System.Management.Automation; +using Dbg = System.Management.Automation; namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -18,15 +17,12 @@ internal sealed partial class SessionStateInternal /// /// Gets the value of the specified cmdlet from the cmdlet table. /// - /// /// /// The name of the cmdlet value to retrieve. /// - /// /// /// The CmdletInfo representing the cmdlet. /// - /// internal CmdletInfo GetCmdlet(string cmdletName) { return GetCmdlet(cmdletName, CommandOrigin.Internal); @@ -35,23 +31,19 @@ internal CmdletInfo GetCmdlet(string cmdletName) /// /// Gets the value of the specified cmdlet from the cmdlet table. /// - /// /// /// 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. /// - /// internal CmdletInfo GetCmdlet(string cmdletName, CommandOrigin origin) { CmdletInfo result = null; - if (String.IsNullOrEmpty(cmdletName)) + if (string.IsNullOrEmpty(cmdletName)) { return null; } @@ -87,40 +79,34 @@ internal CmdletInfo GetCmdlet(string cmdletName, CommandOrigin origin) } return result; - } // GetCmdlet + } /// /// Gets the value of the specified cmdlet from the cmdlet table. /// - /// /// /// The name of the cmdlet value to retrieve. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// The CmdletInfo representing the cmdlet. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal CmdletInfo GetCmdletAtScope(string cmdletName, string scopeID) { CmdletInfo result = null; - if (String.IsNullOrEmpty(cmdletName)) + if (string.IsNullOrEmpty(cmdletName)) { return null; } @@ -139,12 +125,11 @@ internal CmdletInfo GetCmdletAtScope(string cmdletName, string scopeID) } return result; - } // GetCmdletAtScope + } /// - /// Gets an IEnumerable for the cmdlet table + /// Gets an IEnumerable for the cmdlet table. /// - /// internal IDictionary> GetCmdletTable() { Dictionary> result = @@ -171,34 +156,31 @@ internal IDictionary> GetCmdletTable() toBeAdded.Add(cmdletInfo); } } + result.Add(entry.Key, toBeAdded); } } } return result; - } // GetCmdletTable + } /// - /// Gets an IEnumerable for the cmdlet table for a given scope + /// Gets an IEnumerable for the cmdlet table for a given scope. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal IDictionary> GetCmdletTableAtScope(string scopeID) { Dictionary> result = @@ -219,11 +201,12 @@ internal IDictionary> GetCmdletTableAtScope(string scop toBeAdded.Add(cmdletInfo); } } + result.Add(entry.Key, toBeAdded); } return result; - } // GetCmdletTableAtScope + } internal void RemoveCmdlet(string name, int index, bool force) { @@ -233,36 +216,29 @@ internal void RemoveCmdlet(string name, int index, bool force) /// /// Removes a cmdlet from the function table. /// - /// /// /// The name of the cmdlet to remove. /// - /// /// /// The name of the cmdlet to remove. /// - /// /// /// THe origin of the caller of this API /// - /// /// /// If true, the cmdlet is removed even if it is ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If the function is constant. /// - /// internal void RemoveCmdlet(string name, int index, bool force, CommandOrigin origin) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } // Use the scope enumerator to find an existing function @@ -275,7 +251,6 @@ internal void RemoveCmdlet(string name, int index, bool force, CommandOrigin ori CmdletInfo cmdletInfo = scope.GetCmdlet(name); - if (cmdletInfo != null) { // Make sure the cmdlet isn't private or if it is that the current @@ -293,33 +268,28 @@ internal void RemoveCmdlet(string name, int index, bool force, CommandOrigin ori } } } - } // RemoveCmdlet + } /// /// Removes a cmdlet entry from the cmdlet table. /// - /// /// /// The name of the cmdlet entry to remove. /// - /// /// /// If true, the cmdlet is removed even if it is ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If the function is constant. /// - /// internal void RemoveCmdletEntry(string name, bool force) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } // Use the scope enumerator to find an existing function @@ -349,10 +319,8 @@ internal void RemoveCmdletEntry(string name, bool force) } } } - } // RemoveCmdlet + } #endregion cmdlets - } // SessionStateInternal class + } } - - diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs index 3541a1d9bf3..fa8884b92e8 100644 --- a/src/System.Management.Automation/engine/SessionStateContainer.cs +++ b/src/System.Management.Automation/engine/SessionStateContainer.cs @@ -1,15 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.ObjectModel; +using System.IO; +using System.Management.Automation.Internal; using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; using System.Reflection; + using Dbg = System.Management.Automation; -using System.IO; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings #pragma warning disable 56500 @@ -17,7 +17,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -28,49 +28,39 @@ internal sealed partial class SessionStateInternal /// /// Determines if the monad virtual namespace path exists. /// - /// /// /// The path to the object to determine if it exists. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// true if the object specified by path exists, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool ItemExists(string path, bool force, bool literalPath) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -82,52 +72,43 @@ internal bool ItemExists(string path, bool force, bool literalPath) context.ThrowFirstErrorOrDoNothing(); return result; - } // ItemExists + } /// /// Determines if the monad virtual namespace path exists. /// - /// /// /// The path to the object to determine if it exists. /// - /// /// /// The context which the core command is running. /// - /// /// /// true if the object specified by path exists, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool ItemExists( string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -144,11 +125,10 @@ internal bool ItemExists( out provider, out providerInstance); - foreach (string providerPath in providerPaths) { result = ItemExists(providerInstance, providerPath, context); - if (result == true) + if (result) { break; } @@ -158,37 +138,31 @@ internal bool ItemExists( { result = false; } + return result; - } // Exists + } /// - /// Determines if the item at the specified path exists + /// Determines if the item at the specified path exists. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal bool ItemExists( CmdletProvider providerInstance, string path, @@ -207,7 +181,6 @@ internal bool ItemExists( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -238,57 +211,48 @@ internal bool ItemExists( path, e); } + return result; - } // Exists + } /// /// Gets the dynamic parameters for the test-path cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object ItemExistsDynamicParameters(string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -315,42 +279,35 @@ internal object ItemExistsDynamicParameters(string path, CmdletProviderContext c return ItemExistsDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // ItemExistsDynamicParameters + } /// /// Gets the dynamic parameters for the test-path cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object ItemExistsDynamicParameters( CmdletProvider providerInstance, string path, @@ -369,7 +326,6 @@ private object ItemExistsDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -399,8 +355,9 @@ private object ItemExistsDynamicParameters( path, e); } + return result; - } // ITemExistsDynamicParameters + } #endregion Exists @@ -409,41 +366,33 @@ private object ItemExistsDynamicParameters( /// /// Determines if the MSH path is a syntactically and semantically valid path for the provider. /// - /// /// /// The path to validate. /// - /// /// /// true if the object specified by path is syntactically and semantically valid, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool IsValidPath(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -453,52 +402,43 @@ internal bool IsValidPath(string path) context.ThrowFirstErrorOrDoNothing(); return result; - } // IsValidPath + } /// /// Determines if the MSH path is a syntactically and semantically valid path for the provider. /// - /// /// /// The path to validate. /// - /// /// /// The context which the core command is running. /// - /// /// /// true if the object specified by path is syntactically and semantically valid, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool IsValidPath( string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -514,36 +454,29 @@ internal bool IsValidPath( ItemCmdletProvider providerInstance = GetItemProviderInstance(provider); return IsValidPath(providerInstance, providerPath, context); - } // IsValidPath + } /// /// Determines if the specified path is valid. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private bool IsValidPath( CmdletProvider providerInstance, string path, @@ -562,7 +495,6 @@ private bool IsValidPath( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -593,8 +525,9 @@ private bool IsValidPath( path, e); } + return result; - } // IsValidPath + } #endregion IsValidPath @@ -603,41 +536,33 @@ private bool IsValidPath( /// /// Determines if the monad virtual namespace path is a container. /// - /// /// /// The path to the object to determine if it is a container. /// - /// /// /// true if the object specified by path is a container, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool IsItemContainer(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -647,52 +572,43 @@ internal bool IsItemContainer(string path) context.ThrowFirstErrorOrDoNothing(); return result; - } // IsItemContainer + } /// /// Determines if the monad virtual namespace path is a container. /// - /// /// /// The path to the object to determine if it is a container. /// - /// /// /// The context which the core command is running. /// - /// /// /// true if the object specified by path is a container, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool IsItemContainer( string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -709,11 +625,10 @@ internal bool IsItemContainer( out provider, out providerInstance); - foreach (string providerPath in providerPaths) { result = IsItemContainer(providerInstance, providerPath, context); - if (result == false) + if (!result) { break; } @@ -725,36 +640,29 @@ internal bool IsItemContainer( } return result; - } // IsItemContainer + } /// /// Determines if the item at the specified path is a container. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private bool IsItemContainer( CmdletProvider providerInstance, string path, @@ -840,58 +748,48 @@ private bool IsItemContainer( } return result; - } // IsItemContainer + } #endregion IsItemContainer #region RemoveItem /// - /// Deletes the specified object + /// Deletes the specified object. /// - /// /// /// A relative or absolute path to the object to be deleted. /// - /// /// /// The delete should occur in all sub-containers of the specified path. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveItem(string[] paths, bool recurse, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -901,50 +799,40 @@ internal void RemoveItem(string[] paths, bool recurse, bool force, bool literalP RemoveItem(paths, recurse, context); context.ThrowFirstErrorOrDoNothing(); - } // RemoveItem + } /// - /// Deletes the specified object + /// Deletes the specified object. /// - /// /// /// A relative or absolute path to the object to be deleted. /// - /// /// /// The delete should occur in all sub-containers of the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void RemoveItem( string[] paths, bool recurse, @@ -952,14 +840,14 @@ internal void RemoveItem( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -978,45 +866,36 @@ internal void RemoveItem( RemoveItem(providerInstance, providerPath, recurse, context); } } - } // RemoveItem + } /// /// Internal remove item method that just calls the provider directly without globbing. /// - /// /// /// The name of the provider to use. /// - /// /// /// The path of the item to remove. /// - /// /// /// True if all items should be removed recursively. /// - /// /// /// The context under which the command is running. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveItem( string providerId, string path, @@ -1043,40 +922,31 @@ internal void RemoveItem( /// /// Internal remove item method that just calls the provider directly without globbing. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The path of the item to remove. /// - /// /// /// True if all items should be removed recursively. /// - /// /// /// The context under which the command is running. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveItem( CmdletProvider providerInstance, string path, @@ -1096,7 +966,6 @@ internal void RemoveItem( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -1142,7 +1011,7 @@ internal void RemoveItem( } } } - } // IsItemContainer + } } else { @@ -1170,51 +1039,41 @@ internal void RemoveItem( path, e); } - } // RemoveItem + } /// /// Gets the dynamic parameters for the remove-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The delete should occur in all sub-containers of the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object RemoveItemDynamicParameters( string path, bool recurse, @@ -1249,46 +1108,38 @@ internal object RemoveItemDynamicParameters( return RemoveItemDynamicParameters(providerInstance, providerPaths[0], recurse, newContext); } + return null; - } // RemoveItemDynamicParameters + } /// /// Gets the dynamic parameters for the remove-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// If true, all items in the subtree should be removed. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object RemoveItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -1308,7 +1159,6 @@ private object RemoveItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -1338,8 +1188,9 @@ private object RemoveItemDynamicParameters( path, e); } + return result; - } // RemoveItemDynamicParameters + } #endregion RemoveItem @@ -1348,60 +1199,48 @@ private object RemoveItemDynamicParameters( /// /// Gets the children of the specified item. /// - /// /// /// An array of relative or absolute paths to the object to get the children of. /// - /// /// /// If true, gets all the children in all the sub-containers of the specified /// container. If false, only gets the immediate children of the specified /// container. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// This method does not provider streaming of the results. If you want streaming /// then you must call the overload that takes a CmdletProviderContext. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetChildItems(string[] paths, bool recurse, uint depth, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1412,7 +1251,7 @@ internal Collection GetChildItems(string[] paths, bool recurse, uint d { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } GetChildItems(path, recurse, depth, context); @@ -1421,56 +1260,45 @@ internal Collection GetChildItems(string[] paths, bool recurse, uint d context.ThrowFirstErrorOrDoNothing(); return context.GetAccumulatedObjects(); - } // GetChildItems + } /// /// Gets the children of the specified item. /// - /// /// /// A relative or absolute path to the object to get the children of. /// - /// /// /// If true, gets all the children in all the sub-containers of the specified /// container. If false, only gets the immediate children of the specified /// container. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// The context which the core command is running. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void GetChildItems( string path, bool recurse, @@ -1479,12 +1307,12 @@ internal void GetChildItems( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } ProviderInfo provider = null; @@ -1496,9 +1324,25 @@ 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); + + // If -File or -Directory is specified and path is ended with '*', we should include the parent path as search path + + bool isFileOrDirectoryPresent = false; + + if (context.DynamicParameters is Microsoft.PowerShell.Commands.GetChildDynamicParameters dynParam) + { + isFileOrDirectoryPresent = dynParam.File.IsPresent || dynParam.Directory.IsPresent; + } + + if (string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase) && isFileOrDirectoryPresent) + { + string parentName = path.Substring(0, path.Length - childName.Length); + path = parentName; + } // dir c:\tem* -include *.ps1 -rec => No change if ((context.Include == null) || (context.Include.Count == 0)) { @@ -1509,10 +1353,9 @@ internal void GetChildItems( // Should glob paths and files that match tem*, but then // recurse into all subdirectories and do the same for // those directories. - if ((!String.IsNullOrEmpty(path)) && (!IsItemContainer(path))) + if (!string.IsNullOrEmpty(path) && !IsItemContainer(path)) { - string childName = GetChildName(path, context); - if (!String.Equals(childName, "*", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(childName, "*", StringComparison.OrdinalIgnoreCase)) { if (context.Include != null) { @@ -1570,7 +1413,6 @@ internal void GetChildItems( ContainerCmdletProvider unused = GetContainerProviderInstance(provider); } - bool getChildrenBecauseNoGlob = !LocationGlobber.StringContainsGlobCharacters(path); // If we are doing recursion and we have include or exclude // filters the recursion must be done manually. @@ -1592,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 @@ -1636,7 +1477,7 @@ internal void GetChildItems( string originalPath = path; path = Globber.GetProviderPath( - path, + context.SuppressWildcardExpansion ? path : WildcardPattern.Unescape(path), context, out provider, out drive); @@ -1648,7 +1489,24 @@ internal void GetChildItems( ContainerCmdletProvider providerInstance = GetContainerProviderInstance(provider); - if (path != null && this.ItemExists(providerInstance, path, context)) + if ( + (context.Include != null && context.Include.Count > 0) || + (context.Exclude != null && context.Exclude.Count > 0)) + { + // Do the recursion manually so that we can apply the + // include and exclude filters + try + { + // Temporary set literal path as false to apply filter + context.SuppressWildcardExpansion = false; + ProcessPathItems(providerInstance, path, recurse, depth, context, out _, ProcessMode.Enumerate); + } + finally + { + context.SuppressWildcardExpansion = true; + } + } + else if (path != null && this.ItemExists(providerInstance, path, context)) { if (IsItemContainer(providerInstance, path, context)) { @@ -1669,44 +1527,35 @@ internal void GetChildItems( throw pathNotFound; } } - } // GetChildItems + } /// /// Gets the child items of the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// If true, all the child items in the subtree are returned. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void GetChildItems( CmdletProvider providerInstance, string path, @@ -1727,7 +1576,6 @@ private void GetChildItems( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -1756,40 +1604,36 @@ private void GetChildItems( path, e); } - } // GetChildItems + } /// /// Determines if the item at the specified path is a container. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// private bool IsPathContainer( CmdletProvider providerInstance, string path, CmdletProviderContext context) { - bool itemContainer = false; - try - { - itemContainer = IsItemContainer(providerInstance, path, context); - } - catch (UnauthorizedAccessException accessException) - { - context.WriteError(new ErrorRecord(accessException, "GetItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); - } - catch (ProviderInvocationException accessException) - { + bool itemContainer = false; + try + { + itemContainer = IsItemContainer(providerInstance, path, context); + } + catch (UnauthorizedAccessException accessException) + { + context.WriteError(new ErrorRecord(accessException, "GetItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); + } + catch (ProviderInvocationException accessException) + { // if providerinvocationexception is wrapping access denied error, it is ok to not terminate the pipeline if (accessException.InnerException != null && accessException.InnerException.GetType().Equals(typeof(System.UnauthorizedAccessException))) @@ -1800,10 +1644,10 @@ private bool IsPathContainer( { throw; } - } - return itemContainer; + } - } // IsPathContainer + return itemContainer; + } /// /// Since we can't do include and exclude filtering on items we have to @@ -1811,48 +1655,36 @@ private bool IsPathContainer( /// the include and exclude filters. If the child is a container we recurse /// into that container. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The path to the item to get the children from. /// - /// /// /// Recurse into sub-containers when getting children. /// - /// /// /// The context under which the command is running. /// - /// /// /// The count of items that do not match any include/exclude criteria. /// - /// - /// Indicates if this is a Enumerate/Remove operation - /// - /// a hint used to skip IsItemContainer checks - /// + /// Indicates if this is a Enumerate/Remove operation. + /// A hint used to skip IsItemContainer checks. /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// private void ProcessPathItems( CmdletProvider providerInstance, string path, @@ -1864,7 +1696,7 @@ private void ProcessPathItems( { // Call ProcessPathItems with 'depth' set to maximum value for infinite recursion when needed. ProcessPathItems(providerInstance, path, recurse, uint.MaxValue, context, out childrenNotMatchingFilterCriteria, processMode, skipIsItemContainerCheck); - } // ProcessPathItems + } /// /// Since we can't do include and exclude filtering on items we have to @@ -1872,52 +1704,39 @@ private void ProcessPathItems( /// the include and exclude filters. If the child is a container we recurse /// into that container. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The path to the item to get the children from. /// - /// /// /// Recurse into sub-containers when getting children. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// The context under which the command is running. /// - /// /// /// The count of items that do not match any include/exclude criteria. /// - /// - /// Indicates if this is a Enumerate/Remove operation - /// - /// a hint used to skip IsItemContainer checks - /// + /// Indicates if this is a Enumerate/Remove operation. + /// A hint used to skip IsItemContainer checks. /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// private void ProcessPathItems( CmdletProvider providerInstance, string path, @@ -1950,7 +1769,6 @@ private void ProcessPathItems( context.Include, WildcardOptions.IgnoreCase); - // Construct the exclude filter Collection excludeMatcher = @@ -1979,7 +1797,6 @@ private void ProcessPathItems( newContext.WriteErrorsToContext(context); childNameObjects = newContext.GetAccumulatedObjects(); - // The code above initially retrieves all of the containers so that it doesn't limit the recursion, // but then emits the non-matching container further down. The public API doesn't support a way to // differentiate the two, so we need to do a diff. @@ -2022,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; } @@ -2092,7 +1907,7 @@ private void ProcessPathItems( // The item is a container so recurse into it. ProcessPathItems(providerInstance, qualifiedPath, recurse, depth - 1, context, out childrenNotMatchingFilterCriteria, processMode, skipIsItemContainerCheck: true); } - } // for each childName + } } else { @@ -2130,51 +1945,41 @@ private void ProcessPathItems( } } } - } // ProcessPathItems + } /// /// Gets the dynamic parameters for the get-childitem cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The delete should occur in all sub-containers of the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetChildItemsDynamicParameters( string path, bool recurse, @@ -2251,10 +2056,10 @@ internal object GetChildItemsDynamicParameters( } return null; - } // GetChildItemsDynamicParameters + } // Detect if the GetChildItemDynamicParameters has been overridden. - private bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) + private static bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) { Type providerType = providerInfo.ImplementingType; @@ -2264,7 +2069,7 @@ private bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) { mi = providerType.GetMethod("GetChildItemsDynamicParameters", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); - providerType = providerType.GetTypeInfo().BaseType; + providerType = providerType.BaseType; } while ( (mi == null) && (providerType != null) && @@ -2277,40 +2082,31 @@ private bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) /// /// Gets the dynamic parameters for the get-childitem cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// If true, all child items in the subtree should be returned. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetChildItemsDynamicParameters( CmdletProvider providerInstance, string path, @@ -2330,7 +2126,6 @@ private object GetChildItemsDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -2361,8 +2156,9 @@ private object GetChildItemsDynamicParameters( path, e); } + return result; - } // GetChildItemsDynamicParameters + } #endregion GetChildItems @@ -2371,68 +2167,54 @@ private object GetChildItemsDynamicParameters( /// /// Gets names of the children of the specified path. /// - /// /// /// The paths to the items from which to retrieve the child names. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// If true, gets all the relative paths of all the children /// in all the sub-containers of the specified /// container. If false, only gets the immediate child names of the specified /// container. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// An array of strings that contains the names of the children of the specified /// container. /// - /// /// /// The child names are the leaf portion of the path. Example, for the file system /// the name for the path c:\windows\system32\foo.dll would be foo.dll or for /// the directory c:\windows\system32 would be system32. For Active Directory the /// child names would be RDN values of the child objects of the container. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetChildNames( string[] paths, ReturnContainers returnContainers, @@ -2443,7 +2225,7 @@ internal Collection GetChildNames( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -2454,7 +2236,7 @@ internal Collection GetChildNames( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } GetChildNames(path, returnContainers, recurse, depth, context); @@ -2472,73 +2254,59 @@ internal Collection GetChildNames( } return results; - } // GetChildNames + } /// /// Gets names of the children of the specified path. /// - /// /// /// The path to the item from which to retrieve the child names. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// If true, gets all the relative paths of all the children /// in all the sub-containers of the specified /// container. If false, only gets the immediate child names of the specified /// container. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing is returned, but all names should be written to the context object. /// - /// /// /// The child names are the leaf portion of the path. Example, for the file system /// the name for the path c:\windows\system32\foo.dll would be foo.dll or for /// the directory c:\windows\system32 would be system32. For Active Directory the /// child names would be RDN values of the child objects of the container. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void GetChildNames( string path, ReturnContainers returnContainers, @@ -2548,7 +2316,7 @@ internal void GetChildNames( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } // Construct the include filter @@ -2558,7 +2326,6 @@ internal void GetChildNames( context.Include, WildcardOptions.IgnoreCase); - // Construct the exclude filter Collection excludeMatcher = @@ -2606,7 +2373,6 @@ internal void GetChildNames( return; } - if ((!pathContainsGlobCharacters || recurse) && IsItemContainer(providerInstance, providerPath, context)) { // Since the path contained glob characters or we are recursing and the @@ -2615,7 +2381,7 @@ internal void GetChildNames( DoGetChildNamesManually( providerInstance, providerPath, - String.Empty, + string.Empty, returnContainers, includeMatcher, excludeMatcher, @@ -2671,7 +2437,7 @@ internal void GetChildNames( string providerPath = Globber.GetProviderPath( - path, + context.SuppressWildcardExpansion ? path : WildcardPattern.Unescape(path), context, out provider, out drive); @@ -2701,7 +2467,7 @@ internal void GetChildNames( DoGetChildNamesManually( providerInstance, providerPath, - String.Empty, + string.Empty, returnContainers, includeMatcher, excludeMatcher, @@ -2721,63 +2487,50 @@ internal void GetChildNames( context); } } - } // GetChildNames + } /// /// Gets the child names of the item at the specified path by /// manually recursing through all the containers instead of /// allowing the provider to do the recursion. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path the name is relative to. /// - /// /// /// If true all names in the subtree should be returned. /// - /// /// /// Current depth of recursion; special case uint.MaxValue performs full recursion. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// A set of filters that the names must match to be returned. /// - /// /// /// A set of filters that the names cannot match to be returned. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void DoGetChildNamesManually( CmdletProvider providerInstance, string providerPath, @@ -2831,9 +2584,7 @@ private void DoGetChildNamesManually( return; } - string name = result.BaseObject as string; - - if (name == null) + if (result.BaseObject is not string name) { continue; } @@ -2881,9 +2632,7 @@ private void DoGetChildNamesManually( return; } - string name = result.BaseObject as string; - - if (name == null) + if (result.BaseObject is not string name) { continue; } @@ -2923,49 +2672,41 @@ private void DoGetChildNamesManually( true, depth - 1); } - } // foreach - } // if - } // recurse + } + } + } } finally { childNamesContext.RemoveStopReferral(); } - } // DoGetChildNamesRecurseManually + } /// /// Gets the names of the children of the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void GetChildNames( CmdletProvider providerInstance, string path, @@ -2985,7 +2726,6 @@ private void GetChildNames( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -3014,47 +2754,38 @@ private void GetChildNames( path, e); } - } // GetChildNames + } /// /// Gets the dynamic parameters for the get-childitem -name cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetChildNamesDynamicParameters( string path, CmdletProviderContext context) @@ -3119,42 +2850,35 @@ internal object GetChildNamesDynamicParameters( } } } + return result; - } // GetChildNamesDynamicParameters + } /// /// Gets the dynamic parameters for the get-childitem -names cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetChildNamesDynamicParameters( CmdletProvider providerInstance, string path, @@ -3173,7 +2897,6 @@ private object GetChildNamesDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -3204,8 +2927,9 @@ private object GetChildNamesDynamicParameters( path, e); } + return result; - } // GetChildNamesDynamicParameters + } #endregion GetChildNames @@ -3214,50 +2938,40 @@ private object GetChildNamesDynamicParameters( /// /// Renames the item at the specified path to the new name provided. /// - /// /// /// The path to the item to rename. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// The item that was renamed at the specified path. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection RenameItem(string path, string newName, bool force) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -3270,55 +2984,44 @@ internal Collection RenameItem(string path, string newName, bool force // Since there was no errors return the accumulated objects return context.GetAccumulatedObjects(); - } // RenameItem + } /// /// Renames the item at the specified path to the new name provided. /// - /// /// /// The path to the item to rename. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. All items that are renamed are written into the context object. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void RenameItem( string path, string newName, @@ -3326,7 +3029,7 @@ internal void RenameItem( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -3351,7 +3054,7 @@ internal void RenameItem( { ArgumentException argException = PSTraceSource.NewArgumentException( - "path", + nameof(path), SessionStateStrings.RenameMultipleItemError); context.WriteError( @@ -3361,40 +3064,32 @@ internal void RenameItem( ErrorCategory.InvalidArgument, providerPaths)); } - } // RenameItem + } /// /// Renames the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The new name of the item. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void RenameItem( CmdletProvider providerInstance, string path, @@ -3414,7 +3109,6 @@ private void RenameItem( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -3443,52 +3137,42 @@ private void RenameItem( path, e); } - } // RenameItem + } /// /// Gets the dynamic parameters for the rename-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object RenameItemDynamicParameters( string path, string newName, @@ -3523,46 +3207,38 @@ internal object RenameItemDynamicParameters( return RenameItemDynamicParameters(providerInstance, providerPaths[0], newName, newContext); } + return null; - } // RenameItemDynamicParameters + } /// /// Gets the dynamic parameters for the rename-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The new name of the item. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object RenameItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -3582,7 +3258,6 @@ private object RenameItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -3613,8 +3288,9 @@ private object RenameItemDynamicParameters( path, e); } + return result; - } // RenameItemDynamicParameters + } #endregion RenameItem @@ -3623,57 +3299,45 @@ private object RenameItemDynamicParameters( /// /// Creates a new item at the specified path. /// - /// /// /// The path(s) to the container(s) to create the item in. /// - /// /// /// The name of the item to create. /// - /// /// /// The provider specific type of the object to be created. /// - /// /// /// The content of the new item to create. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// The item(s) that was created. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection NewItem(string[] paths, string name, string type, object content, bool force) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -3685,62 +3349,49 @@ internal Collection NewItem(string[] paths, string name, string type, // Since there was no errors return the accumulated objects return context.GetAccumulatedObjects(); - } // NewItem + } /// /// Creates a new item at the specified path. /// - /// /// /// The path(s) to the item(s) to create. /// - /// /// /// The name of the item to create. /// - /// /// /// The provider specific type of the item to be created. /// - /// /// /// The content to create the new item with. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. The item created is written to the context object. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void NewItem( string[] paths, string name, @@ -3750,7 +3401,7 @@ internal void NewItem( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } foreach (string path in paths) @@ -3758,12 +3409,18 @@ internal void NewItem( string resolvePath = null; if (path == null) { - PSTraceSource.NewArgumentNullException("paths"); + PSTraceSource.NewArgumentNullException(nameof(paths)); + } + else if (path.EndsWith((":" + Path.DirectorySeparatorChar), StringComparison.Ordinal) || + path.EndsWith((":" + Path.AltDirectorySeparatorChar), StringComparison.Ordinal)) + { + // path is Windows root + resolvePath = path; } else { // To be compatible with Linux OS. Which will be either '/' or '\' depends on the OS type. - char[] charsToTrim = {' ', Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar}; + char[] charsToTrim = { ' ', Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar }; resolvePath = path.TrimEnd(charsToTrim); } @@ -3775,7 +3432,7 @@ internal void NewItem( // Only glob the path if the name is specified - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { string providerPath = Globber.GetProviderPath(resolvePath, context, out provider, out driveInfo); @@ -3800,7 +3457,7 @@ internal void NewItem( // to pass on to the provider. string composedPath = providerPath; - if (!String.IsNullOrEmpty(name)) + if (!string.IsNullOrEmpty(name)) { composedPath = MakePath(providerInstance, providerPath, name, context); } @@ -3810,7 +3467,7 @@ internal void NewItem( // function can be abused if (context.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode && (providerInstance is Microsoft.PowerShell.Commands.FunctionProvider) && - (String.Equals(type, "Directory", StringComparison.OrdinalIgnoreCase))) + (string.Equals(type, "Directory", StringComparison.OrdinalIgnoreCase))) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -3833,88 +3490,48 @@ internal void NewItem( if (isSymbolicJunctionOrHardLink) { - if (content == null) - { - throw PSTraceSource.NewArgumentNullException(SessionStateStrings.NewItemValueNotSpecified, path); - } - - string targetPath = content.ToString(); - - if (String.IsNullOrEmpty(targetPath)) - { - throw PSTraceSource.NewArgumentNullException(SessionStateStrings.PathNotFound, targetPath); - } - - ProviderInfo targetProvider = null; - CmdletProvider targetProviderInstance = null; - - var globbedTarget = Globber.GetGlobbedProviderPathsFromMonadPath( - targetPath, - allowNonexistingPath, - context, - out targetProvider, - out targetProviderInstance); - - if (String.Compare(targetProvider.Name, "filesystem", StringComparison.OrdinalIgnoreCase) != 0) - { - throw PSTraceSource.NewNotSupportedException(SessionStateStrings.MustBeFileSystemPath); - } + string targetPath; - if (globbedTarget.Count > 1) + if (content is null || string.IsNullOrEmpty(targetPath = content.ToString())) { - throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathResolvedToMultiple, targetPath); + throw PSTraceSource.NewArgumentNullException(nameof(content), SessionStateStrings.NewLinkTargetNotSpecified, path); } - if (globbedTarget.Count == 0) - { - throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathNotFound, targetPath); - } - - content = globbedTarget[0]; + content = targetPath; } NewItemPrivate(providerInstance, composedPath, type, content, context); } } - } // NewItem - + } /// /// Creates a new item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The type of the item to create. /// - /// /// /// The content of the item to create. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void NewItemPrivate( CmdletProvider providerInstance, string path, @@ -3931,7 +3548,6 @@ private void NewItemPrivate( path != null, "Caller should validate path before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -3960,56 +3576,44 @@ private void NewItemPrivate( path, e); } - } // NewItem - + } /// /// Gets the dynamic parameters for the new-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The provider specific type of the item to be created. /// - /// /// /// The content to create the new item with. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object NewItemDynamicParameters( string path, string type, @@ -4045,50 +3649,41 @@ internal object NewItemDynamicParameters( return NewItemDynamicParameters(providerInstance, providerPaths[0], type, newItemValue, newContext); } + return null; - } // NewItemDynamicParameters + } /// /// Gets the dynamic parameters for the new-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The type of the new item. /// - /// /// /// The value of the new item /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object NewItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -4109,7 +3704,6 @@ private object NewItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -4139,8 +3733,9 @@ private object NewItemDynamicParameters( path, e); } + return result; - } // NewItemDynamicParameters + } #endregion NewItem @@ -4149,49 +3744,39 @@ private object NewItemDynamicParameters( /// /// Determines if the item at the specified path has children. /// - /// /// /// The path to the item to see if it has children. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// True if the item has children, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal bool HasChildItems(string path, bool force, bool literalPath) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -4203,57 +3788,47 @@ internal bool HasChildItems(string path, bool force, bool literalPath) context.ThrowFirstErrorOrDoNothing(); return result; - } // HasChildItems + } /// /// Determines if the item at the specified path has children. /// - /// /// /// The path to the item to see if it has children. /// - /// /// /// The context which the core command is running. /// - /// /// /// True if the item has children, false otherwise. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal bool HasChildItems( string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -4271,53 +3846,47 @@ internal bool HasChildItems( foreach (string providerPath in providerPaths) { result = HasChildItems(providerInstance, providerPath, context); - if (result == true) + if (result) { break; } } return result; - } // HasChildItems + } /// /// Determines if the item at the specified path has children. /// - /// /// /// The provider to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal bool HasChildItems( string providerId, string path) { bool result = false; - if (String.IsNullOrEmpty(providerId)) + if (string.IsNullOrEmpty(providerId)) { - throw PSTraceSource.NewArgumentException("providerId"); + throw PSTraceSource.NewArgumentException(nameof(providerId)); } if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -4331,31 +3900,24 @@ internal bool HasChildItems( /// /// Determines if the item at the specified path has children. /// - /// /// /// The provider to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context under which the command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal bool HasChildItems( string providerId, string path, @@ -4369,31 +3931,24 @@ internal bool HasChildItems( /// /// Determines if the item at the specified path has children. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private bool HasChildItems( CmdletProvider providerInstance, string path, @@ -4412,7 +3967,6 @@ private bool HasChildItems( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -4445,65 +3999,52 @@ private bool HasChildItems( } return result; - } // HasChildItems + } #endregion 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. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// Determines how the source container is used in the copy operation. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The objects that were copied. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection CopyItem(string[] paths, string copyPath, bool recurse, @@ -4513,13 +4054,10 @@ internal Collection CopyItem(string[] paths, { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } - if (copyPath == null) - { - copyPath = String.Empty; - } + copyPath ??= string.Empty; CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); context.Force = force; @@ -4530,58 +4068,46 @@ internal Collection CopyItem(string[] paths, context.ThrowFirstErrorOrDoNothing(); return context.GetAccumulatedObjects(); - } // 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. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// Determines how the source container is used in the copy operation. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void CopyItem( string[] paths, string copyPath, @@ -4591,17 +4117,13 @@ internal void CopyItem( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + 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; @@ -4616,6 +4138,7 @@ internal void CopyItem( sourceIsRemote = true; session = dynamicParams.FromSession; } + if (dynamicParams.ToSession != null) { destinationIsRemote = true; @@ -4627,7 +4150,7 @@ internal void CopyItem( { context.WriteError(new ErrorRecord( new ArgumentException( - String.Format(System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemFromSessionToSession, "FromSession", "ToSession")), + string.Format(System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemFromSessionToSession, "FromSession", "ToSession")), "InvalidInput", ErrorCategory.InvalidArgument, dynamicParams)); @@ -4651,17 +4174,17 @@ internal void CopyItem( copyPath, context, out destinationProvider, - out unusedDrive); + out _); } else { // Validate remote destination path providerDestinationPath = copyPath; - if (String.IsNullOrEmpty(providerDestinationPath)) + if (string.IsNullOrEmpty(providerDestinationPath)) { context.WriteError(new ErrorRecord( new ArgumentNullException( - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemRemotelyPathIsNullOrEmpty, "Destination")), @@ -4687,7 +4210,7 @@ internal void CopyItem( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths; @@ -4864,44 +4387,35 @@ internal void CopyItem( } } } - } // CopyItem + } /// - /// Copies the specified item(s) to the specified destination + /// Copies the specified item(s) to the specified destination. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to copy the item(s) to. /// - /// /// /// If true all sub-containers and their children should be copied. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void CopyItem( CmdletProvider providerInstance, string path, @@ -4922,7 +4436,6 @@ private void CopyItem( context != null, "Caller should validate context before calling this method"); - ContainerCmdletProvider containerCmdletProvider = GetContainerProviderInstance(providerInstance); @@ -4951,40 +4464,32 @@ private void CopyItem( path, e); } - } // CopyItem + } /// - /// Recursively copies many items to a single container + /// Recursively copies many items to a single container. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to copy the item(s) to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void CopyRecurseToSingleContainer( CmdletProvider providerInstance, string sourcePath, @@ -4996,11 +4501,11 @@ private void CopyRecurseToSingleContainer( "The providerInstance should have been verified by the caller"); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(sourcePath), + !string.IsNullOrEmpty(sourcePath), "The sourcePath should have been verified by the caller"); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(destinationPath), + !string.IsNullOrEmpty(destinationPath), "The destinationPath should have been verified by the caller"); Dbg.Diagnostics.Assert( @@ -5035,55 +4540,44 @@ private void CopyRecurseToSingleContainer( CopyItem(containerProviderInstance, childPath, destinationPath, false, context); } - } // CopyRecurseToSingleContainer + } /// /// Gets the dynamic parameters for the copy-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object CopyItemDynamicParameters( string path, string destination, @@ -5120,7 +4614,6 @@ internal object CopyItemDynamicParameters( if (providerPaths.Count > 0) providerPath = providerPaths[0]; } - catch (DriveNotFoundException) { // This exception is expected for remote sessions where drives exist in a remote session but not @@ -5149,56 +4642,46 @@ 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); } return null; - } // CopyItemDynamicParameters + } /// /// Gets the dynamic parameters for the copy-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to copy the item to. /// - /// /// /// If true, subcontainers and their children should be copied. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object CopyItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -5211,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"); @@ -5248,12 +4727,13 @@ private object CopyItemDynamicParameters( path, e); } + return result; - } // 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; @@ -5267,16 +4747,15 @@ 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) { context.WriteError(new ErrorRecord( new InvalidOperationException( - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemSessionProperties, "LanguageMode", @@ -5293,19 +4772,18 @@ 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); if (sourceIsRemote) { - ps.AddParameter("sourceIsRemote", true); + ps.AddParameter(nameof(sourceIsRemote), true); } op = Microsoft.PowerShell.Commands.SafeInvokeCommand.Invoke(ps, null, context); @@ -5315,7 +4793,7 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses { context.WriteError(new ErrorRecord( new InvalidOperationException( - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemValidateRemotePath, path)), "FailedToValidateRemotePath", ErrorCategory.InvalidOperation, @@ -5331,7 +4809,7 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses { context.WriteError(new ErrorRecord( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemRemotelyPathIsNotAbsolute, path)), "RemotePathIsNotAbsolute", ErrorCategory.InvalidArgument, @@ -5360,7 +4838,7 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses { context.WriteError(new ErrorRecord( new ArgumentException( - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.PathNotFound, path)), "RemotePathNotFound", ErrorCategory.InvalidArgument, @@ -5371,14 +4849,14 @@ 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) { context.WriteError(new ErrorRecord( new InvalidOperationException( - String.Format(System.Globalization.CultureInfo.InvariantCulture, + string.Format(System.Globalization.CultureInfo.InvariantCulture, SessionStateStrings.CopyItemSessionProperties, "Availability", session.Availability)), "SessionIsNotAvailable", @@ -5397,25 +4875,23 @@ private bool isValidSession(PSSession session, CmdletProviderContext context, ou #endregion CopyItem #endregion ContainerCmdletProvider accessors - } // SessionStateInternal class - + } /// - /// Defines the action to be taken for Navigation cmdlets + /// Defines the action to be taken for Navigation cmdlets. /// internal enum ProcessMode { /// - /// Write out the details + /// Write out the details. /// Enumerate = 1, /// - /// Delete the item + /// Delete the item. /// Delete = 2 } } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateContent.cs b/src/System.Management.Automation/engine/SessionStateContent.cs index fc62fce44fd..0f1cc74bc49 100644 --- a/src/System.Management.Automation/engine/SessionStateContent.cs +++ b/src/System.Management.Automation/engine/SessionStateContent.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -12,7 +12,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -23,49 +23,39 @@ internal sealed partial class SessionStateInternal /// /// Gets the content reader for the specified item. /// - /// /// /// The path(s) to the item(s) to get the content reader for. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The content readers for all items that the path resolves to. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetContentReader(string[] paths, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -77,57 +67,47 @@ internal Collection GetContentReader(string[] paths, bool force, context.ThrowFirstErrorOrDoNothing(); return results; - } // GetContentReader + } /// /// Gets the content reader for the specified item. /// - /// /// /// The path(s) to the item(s) to get the content reader from. /// - /// /// /// The context which the core command is running. /// - /// /// /// The content readers for all items that the path resolves to. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal Collection GetContentReader( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -139,7 +119,7 @@ internal Collection GetContentReader( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -164,36 +144,29 @@ internal Collection GetContentReader( } return results; - } // GetContentReader + } /// /// Gets the content reader for the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private IContentReader GetContentReaderPrivate( CmdletProvider providerInstance, string path, @@ -243,48 +216,40 @@ private IContentReader GetContentReaderPrivate( path, e); } + return result; - } // GetContentReaderPrivate + } /// /// Gets the dynamic parameters for the get-content cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetContentReaderDynamicParameters( string path, CmdletProviderContext context) @@ -312,48 +277,34 @@ internal object GetContentReaderDynamicParameters( out provider, out providerInstance); - if (providerPaths.Count > 0) - { - // Get the dynamic parameters for the first resolved path - - return GetContentReaderDynamicParameters(providerInstance, providerPaths[0], newContext); - } - return null; - } // GetContentReaderDynamicParameters + return GetContentReaderDynamicParameters(providerInstance, path, newContext); + } /// /// Gets the dynamic parameters for the get-content cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetContentReaderDynamicParameters( CmdletProvider providerInstance, string path, @@ -402,8 +353,9 @@ private object GetContentReaderDynamicParameters( path, e); } + return result; - } // GetContentReaderDynamicParameters + } #endregion GetContentReader @@ -412,49 +364,39 @@ private object GetContentReaderDynamicParameters( /// /// Gets the content writer for the specified item. /// - /// /// /// The path(s) to the item(s) to get the content writer for. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The content writers for all items that the path resolves to. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetContentWriter(string[] paths, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -466,57 +408,47 @@ internal Collection GetContentWriter(string[] paths, bool force, context.ThrowFirstErrorOrDoNothing(); return results; - } // GetContentWriter + } /// /// Gets the content writer for the specified item. /// - /// /// /// The path(s) to the item(s) to get the content writer from. /// - /// /// /// The context which the core command is running. /// - /// /// /// The content writers for all items that the path resolves to. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal Collection GetContentWriter( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -527,7 +459,7 @@ internal Collection GetContentWriter( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -551,36 +483,29 @@ internal Collection GetContentWriter( } return results; - } // GetContentWriter + } /// /// Gets the content writer for the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private IContentWriter GetContentWriterPrivate( CmdletProvider providerInstance, string path, @@ -629,48 +554,40 @@ private IContentWriter GetContentWriterPrivate( path, e); } + return result; - } // GetContentWriterPrivate + } /// /// Gets the dynamic parameters for the set-content and add-content cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetContentWriterDynamicParameters( string path, CmdletProviderContext context) @@ -704,42 +621,35 @@ internal object GetContentWriterDynamicParameters( return GetContentWriterDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // GetContentWriterDynamicParameters + } /// /// Gets the dynamic parameters for the set-content and add-content cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetContentWriterDynamicParameters( CmdletProvider providerInstance, string path, @@ -788,8 +698,9 @@ private object GetContentWriterDynamicParameters( path, e); } + return result; - } // GetContentWriterDynamicParameters + } #endregion GetContentWriter @@ -798,45 +709,36 @@ private object GetContentWriterDynamicParameters( /// /// Clears all the content from the specified item. /// - /// /// /// The path(s) to the item(s) to clear the content from. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void ClearContent(string[] paths, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -846,53 +748,44 @@ internal void ClearContent(string[] paths, bool force, bool literalPath) ClearContent(paths, context); context.ThrowFirstErrorOrDoNothing(); - } // ClearContent + } /// /// Clears all of the content from the specified item. /// - /// /// /// The path to the item to clear the content from. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void ClearContent( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -902,7 +795,7 @@ internal void ClearContent( { if (path == null) { - PSTraceSource.NewArgumentNullException("paths"); + PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -918,36 +811,29 @@ internal void ClearContent( ClearContentPrivate(providerInstance, providerPath, context); } } - } // ClearContent + } /// /// Clears the content from the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void ClearContentPrivate( CmdletProvider providerInstance, string path, @@ -995,47 +881,38 @@ private void ClearContentPrivate( path, e); } - } // ClearContentPrivate + } /// /// Gets the dynamic parameters for the clear-content cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object ClearContentDynamicParameters( string path, CmdletProviderContext context) @@ -1069,41 +946,34 @@ internal object ClearContentDynamicParameters( return ClearContentDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // ClearContentDynamicParameters + } /// - /// Calls the provider to get the clear-content dynamic parameters + /// Calls the provider to get the clear-content dynamic parameters. /// - /// /// /// The instance of the provider to call /// - /// /// /// The path to pass to the provider. /// - /// /// /// The context the command is executing under. /// - /// /// /// The dynamic parameter object returned by the provider. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object ClearContentDynamicParameters( CmdletProvider providerInstance, string path, @@ -1152,15 +1022,14 @@ private object ClearContentDynamicParameters( path, e); } + return result; - } // ClearContentDynamicParameters + } #endregion ClearContent #endregion IContentCmdletProvider accessors - } // SessionStateInternal class + } } #pragma warning restore 56500 - - diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index 1350d57700a..d01d4c63f5d 100644 --- a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Globalization; using System.IO; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; -using System.Globalization; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings #pragma warning disable 56500 @@ -16,7 +16,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -25,17 +25,14 @@ internal sealed partial class SessionStateInternal /// private PSDriveInfo _currentDrive; - #region NewDrive /// /// Adds the specified drive to the current scope. /// - /// /// /// The drive to be added to the current scope. /// - /// /// /// The ID for the scope to add the drive to. The scope ID can be any of the /// "special" scope identifiers like "global", "local", or "private" or it @@ -44,38 +41,31 @@ internal sealed partial class SessionStateInternal /// If this parameter is null or empty the drive will be placed in the /// current scope. /// - /// /// /// The drive that was added, if any. /// - /// /// /// If is null. /// - /// /// /// If the drive already exists, /// or /// If .Name contains one or more invalid characters; ~ / \\ . : /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// The provider for the could not be found. /// - /// /// /// If the provider threw an exception or returned null. /// - /// internal PSDriveInfo NewDrive(PSDriveInfo drive, string scopeID) { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } PSDriveInfo result = null; @@ -99,23 +89,21 @@ internal PSDriveInfo NewDrive(PSDriveInfo drive, string scopeID) // set the return value to the first drive (should only be one). - if (!successObjects[0].immediateBaseObjectIsEmpty) + if (!successObjects[0].ImmediateBaseObjectIsEmpty) { result = (PSDriveInfo)successObjects[0].BaseObject; } } return result; - } // NewDrive + } /// /// Adds a drive to the PowerShell namespace. /// - /// /// /// The new drive to be added. /// - /// /// /// The ID for the scope to add the drive to. The scope ID can be any of the /// "special" scope identifiers like "global", "local", or "private" or it @@ -124,48 +112,40 @@ internal PSDriveInfo NewDrive(PSDriveInfo drive, string scopeID) /// If this parameter is null or empty the drive will be placed in the /// current scope. /// - /// /// /// The context which the core command is running. /// - /// /// /// If or is null. /// - /// /// /// If the drive already exists /// or /// If .Name contains one or more invalid characters; ~ / \\ . : /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// The provider for the could not be found. /// - /// /// /// If the provider threw an exception or returned null. /// - /// internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext context) { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } if (!IsValidDriveName(drive.Name)) @@ -190,7 +170,7 @@ internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext return; } - if (String.Compare(result.Name, drive.Name, StringComparison.CurrentCultureIgnoreCase) == 0) + if (string.Equals(result.Name, drive.Name, StringComparison.OrdinalIgnoreCase)) { // Set the drive in the current scope. @@ -198,7 +178,7 @@ internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext { SessionStateScope scope = _currentScope; - if (!String.IsNullOrEmpty(scopeID)) + if (!string.IsNullOrEmpty(scopeID)) { scope = GetScopeByID(scopeID); } @@ -223,7 +203,6 @@ internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext throw; } - if (ProvidersCurrentWorkingDrive[drive.Provider] == null) { // Set the new drive as the current @@ -248,49 +227,30 @@ internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext throw e; } - } // NewDrive + } private static bool IsValidDriveName(string name) { - bool result = true; - - do - { - if (String.IsNullOrEmpty(name)) - { - result = false; - break; - } - - if (name.IndexOfAny(s_charactersInvalidInDriveName) >= 0) - { - result = false; - break; - } - } while (false); + const string CharactersInvalidInDriveName = ":/\\.~"; - return result; - } // IsValidDriveName - private static char[] s_charactersInvalidInDriveName = new char[] { ':', '/', '\\', '.', '~' }; + return !string.IsNullOrEmpty(name) + && name.AsSpan().IndexOfAny(CharactersInvalidInDriveName) < 0; + } /// /// 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 /// does not resolve to a single MSH path the root is returned as it was passed. /// - /// /// /// The root path of the drive to be resolved. /// - /// /// /// The provider that should be used when resolving the path. /// - /// /// /// The new root path of the drive. /// - /// private string GetProviderRootFromSpecifiedRoot(string root, ProviderInfo provider) { Dbg.Diagnostics.Assert( @@ -376,34 +336,28 @@ private string GetProviderRootFromSpecifiedRoot(string root, ProviderInfo provid } return result; - } // GetProviderRootFromSpecifiedRoot + } /// /// Gets an object that defines the additional parameters for the NewDrive implementation /// for a provider. /// - /// /// /// The provider ID for the drive that is being created. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the is not a DriveCmdletProvider. /// - /// /// /// If does not exist. /// - /// internal object NewDriveDynamicParameters(string providerId, CmdletProviderContext context) { if (providerId == null) @@ -413,7 +367,6 @@ internal object NewDriveDynamicParameters(string providerId, CmdletProviderConte return null; } - DriveCmdletProvider provider = GetDriveProviderInstance(providerId); object result = null; @@ -431,33 +384,29 @@ internal object NewDriveDynamicParameters(string providerId, CmdletProviderConte null, e); } + return result; - } // NewDriveDynamicParameters + } #endregion NewDrive #region GetDrive /// - /// Searches through the session state scopes to find a drive + /// Searches through the session state scopes to find a drive. /// - /// /// /// The name of a drive to find. /// - /// /// /// The drive information if the drive is found. /// - /// /// /// If is null. /// - /// /// /// If there is no drive with . /// - /// internal PSDriveInfo GetDrive(string name) { return GetDrive(name, true); @@ -467,7 +416,7 @@ private PSDriveInfo GetDrive(string name, bool automount) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } PSDriveInfo result = null; @@ -504,17 +453,13 @@ private PSDriveInfo GetDrive(string name, bool automount) // Increment the scope ID ++scopeID; - } // foreach scope + } if (result == null && automount) { - // first try to automount as a file system drive - result = AutomountFileSystemDrive(name); - // if it didn't work, then try automounting as a BuiltIn drive (e.g. "Cert"/"Certificate"/"WSMan") - if (result == null) - { - result = AutomountBuiltInDrive(name); // internally this calls GetDrive(name, false) - } + // Attempt to automount as a file system drive + // or as a BuiltIn drive (e.g. "Cert"/"Certificate"/"WSMan") + result = AutomountFileSystemDrive(name) ?? AutomountBuiltInDrive(name); } if (result == null) @@ -529,48 +474,41 @@ private PSDriveInfo GetDrive(string name, bool automount) } return result; - } // GetDrive + } /// /// Searches through the session state scopes looking /// for a drive of the specified name. /// - /// /// /// The name of the drive to return. /// - /// /// /// The scope ID of the scope to look in for the drive. /// If this parameter is null or empty the drive will be /// found by searching the scopes using the dynamic scoping /// rules. /// - /// /// /// The drive for the given name in the given scope or null if /// the drive was not found. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal PSDriveInfo GetDrive(string name, string scopeID) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } PSDriveInfo result = null; @@ -579,7 +517,7 @@ internal PSDriveInfo GetDrive(string name, string scopeID) // so do a search through the scopes looking for the // drive. - if (String.IsNullOrEmpty(scopeID)) + if (string.IsNullOrEmpty(scopeID)) { SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(CurrentScope); @@ -639,7 +577,7 @@ internal PSDriveInfo GetDrive(string name, string scopeID) } return result; - } // GetDrive + } private PSDriveInfo AutomountFileSystemDrive(string name) { @@ -676,8 +614,9 @@ private PSDriveInfo AutomountFileSystemDrive(string name) // DriveNotFoundException which will be thrown by the caller } } + return result; - } // AutomountFileSystemDrive + } private PSDriveInfo AutomountFileSystemDrive(System.IO.DriveInfo systemDriveInfo) { @@ -702,7 +641,7 @@ private PSDriveInfo AutomountFileSystemDrive(System.IO.DriveInfo systemDriveInfo { // Create a new drive string systemDriveName = systemDriveInfo.Name.Substring(0, 1); - string volumeLabel = String.Empty; + string volumeLabel = string.Empty; string displayRoot = null; try @@ -772,20 +711,23 @@ private PSDriveInfo AutomountFileSystemDrive(System.IO.DriveInfo systemDriveInfo // want to let errors find their way out. If there are any // failures we just don't mount the drive. - MshLog.LogProviderHealthEvent( this.ExecutionContext, this.ExecutionContext.ProviderNames.FileSystem, e, Severity.Warning); } + return result; - } // AutomountFileSystemDrive + } /// /// Auto-mounts a built-in drive. /// - /// The name of the drive to load + /// + /// Calls GetDrive(name, false) internally. + /// + /// The name of the drive to load. /// internal PSDriveInfo AutomountBuiltInDrive(string name) { @@ -793,10 +735,10 @@ internal PSDriveInfo AutomountBuiltInDrive(string name) PSDriveInfo result = GetDrive(name, false); return result; - } // AutomountFileSystemDrive + } /// - /// Automatically mount the specified drive + /// Automatically mount the specified drive. /// /// /// Neither 'WSMan' nor 'Certificate' provider works in UNIX PS today. @@ -818,18 +760,18 @@ internal static void MountDefaultDrive(string name, ExecutionContext context) // mount the default drive, since the provider names can be used for provider-qualified paths. // The WSMAN drive is the same as the provider name. if ( - String.Equals("Cert", name, StringComparison.OrdinalIgnoreCase) || - String.Equals("Certificate", name, StringComparison.OrdinalIgnoreCase) + string.Equals("Cert", name, StringComparison.OrdinalIgnoreCase) || + string.Equals("Certificate", name, StringComparison.OrdinalIgnoreCase) ) { moduleName = "Microsoft.PowerShell.Security"; } - else if (String.Equals("WSMan", name, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals("WSMan", name, StringComparison.OrdinalIgnoreCase)) { moduleName = "Microsoft.WSMan.Management"; } - if (!String.IsNullOrEmpty(moduleName)) + if (!string.IsNullOrEmpty(moduleName)) { s_tracer.WriteLine("Auto-mounting built-in drive: {0}", name); CommandInfo commandInfo = new CmdletInfo("Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand), null, null, context); @@ -848,20 +790,16 @@ internal static void MountDefaultDrive(string name, ExecutionContext context) /// Determines if the specified automounted drive still exists. If not, /// the drive is removed. /// - /// /// /// The drive to validate or remove. /// - /// /// /// The scope the drive is in. This will be used to remove the drive /// if necessary. /// - /// /// /// True if the drive is still valid, false if the drive was removed. /// - /// private bool ValidateOrRemoveAutoMountedDrive(PSDriveInfo drive, SessionStateScope scope) { bool result = true; @@ -923,13 +861,12 @@ private bool ValidateOrRemoveAutoMountedDrive(PSDriveInfo drive, SessionStateSco { } - scope.RemoveDrive(drive); } } return result; - } // ValidateOrRemoveAutoMountedDrive + } /// /// If a VHD is mounted to a drive prior to the PowerShell session being launched, @@ -963,32 +900,15 @@ private bool IsAStaleVhdMountedDrive(PSDriveInfo drive) { char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture); - - if (Char.ToUpperInvariant(driveChar) >= 'A' && Char.ToUpperInvariant(driveChar) <= 'Z') + if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z') { DriveInfo systemDriveInfo = new DriveInfo(drive.Name); if (systemDriveInfo.DriveType == DriveType.NoRootDirectory) { - try - { - // Checking for the presence of mounted drive locally using Utils.NativeDirectoryExists API as - // the calls to this API is faster than normal Directory.Exist API. - bool validDrive = Utils.NativeDirectoryExists(drive.Root); - if (!validDrive) - { - result = true; - } - } - // We don't want to have automounting cause an exception. We - // rather it just fail silently as it wasn't a result of an - // explicit request by the user anyway. - // Following the same pattern as the Calling API. - catch (IOException) - { - } - catch (UnauthorizedAccessException) + if (!Directory.Exists(drive.Root)) { + result = true; } } } @@ -1003,20 +923,17 @@ private bool IsAStaleVhdMountedDrive(PSDriveInfo drive) } /// - /// Gets all the drives for a specific provider + /// Gets all the drives for a specific provider. /// - /// /// /// The identifier for the provider to retrieve the drives for. /// - /// /// /// An IEnumerable that contains the drives for the specified provider. /// - /// internal Collection GetDrivesForProvider(string providerId) { - if (String.IsNullOrEmpty(providerId)) + if (string.IsNullOrEmpty(providerId)) { return Drives(null); } @@ -1036,7 +953,7 @@ internal Collection GetDrivesForProvider(string providerId) } return drives; - } // GetDrivesForProvider + } #endregion GetDrive @@ -1044,27 +961,23 @@ internal Collection GetDrivesForProvider(string providerId) /// /// Removes the drive with the specified name. /// - /// /// /// The name of the drive to remove. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope from which to remove the drive. /// If the scope ID is null or empty, the scope hierarchy will be searched /// starting at the current scope through all the parent scopes to the /// global scope until a drive of the given name is found to remove. /// - /// internal void RemoveDrive(string driveName, bool force, string scopeID) { if (driveName == null) { - throw PSTraceSource.NewArgumentNullException("driveName"); + throw PSTraceSource.NewArgumentNullException(nameof(driveName)); } PSDriveInfo drive = GetDrive(driveName, scopeID); @@ -1079,31 +992,26 @@ internal void RemoveDrive(string driveName, bool force, string scopeID) } RemoveDrive(drive, force, scopeID); - } // RemoveDrive + } /// /// Removes the drive with the specified name. /// - /// /// /// The name of the drive to remove. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope from which to remove the drive. /// If the scope ID is null or empty, the scope hierarchy will be searched /// starting at the current scope through all the parent scopes to the /// global scope until a drive of the given name is found to remove. /// - /// /// /// The context of the command. /// - /// internal void RemoveDrive( string driveName, bool force, @@ -1112,7 +1020,7 @@ internal void RemoveDrive( { if (driveName == null) { - throw PSTraceSource.NewArgumentNullException("driveName"); + throw PSTraceSource.NewArgumentNullException(nameof(driveName)); } Dbg.Diagnostics.Assert( @@ -1133,32 +1041,28 @@ internal void RemoveDrive( { RemoveDrive(drive, force, scopeID, context); } - } // RemoveDrive + } /// /// Removes the specified drive. /// - /// /// /// The drive to be removed. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope from which to remove the drive. /// If the scope ID is null or empty, the scope hierarchy will be searched /// starting at the current scope through all the parent scopes to the /// global scope until a drive of the given name is found to remove. /// - /// internal void RemoveDrive(PSDriveInfo drive, bool force, string scopeID) { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1169,36 +1073,30 @@ internal void RemoveDrive(PSDriveInfo drive, bool force, string scopeID) { context.ThrowFirstErrorOrDoNothing(); } - } // RemoveDrive + } /// /// Removes the specified drive. /// - /// /// /// The drive to be removed. /// - /// /// /// Determines whether drive should be forcefully removed even if there was errors. /// - /// /// /// The ID of the scope from which to remove the drive. /// If the scope ID is null or empty, the scope hierarchy will be searched /// starting at the current scope through all the parent scopes to the /// global scope until a drive of the given name is found to remove. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal void RemoveDrive( PSDriveInfo drive, bool force, @@ -1243,7 +1141,7 @@ internal void RemoveDrive( // so do a search through the scopes looking for the // drive. - if (String.IsNullOrEmpty(scopeID)) + if (string.IsNullOrEmpty(scopeID)) { SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(CurrentScope); @@ -1264,6 +1162,7 @@ internal void RemoveDrive( { ProvidersCurrentWorkingDrive[drive.Provider] = null; } + break; } } @@ -1277,7 +1176,6 @@ internal void RemoveDrive( SessionStateScope scope = GetScopeByID(scopeID); scope.RemoveDrive(drive); - // If the drive is the current drive for the provider, remove // it from the current drive list. @@ -1290,8 +1188,7 @@ internal void RemoveDrive( else { PSInvalidOperationException e = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( SessionStateStrings.DriveRemovalPreventedByProvider, drive.Name, drive.Provider); @@ -1301,43 +1198,37 @@ internal void RemoveDrive( e.ErrorRecord, e)); } - } // RemoveDrive + } /// /// Determines if the drive can be removed by calling the provider /// for the drive. /// - /// /// /// The drive to test for removal. /// - /// /// /// The context under which the command is running. /// - /// /// /// True if the drive can be removed, false otherwise. /// - /// /// /// If or is null. /// - /// /// /// If the provider threw an exception when RemoveDrive was called. /// - /// private bool CanRemoveDrive(PSDriveInfo drive, CmdletProviderContext context) { if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } s_tracer.WriteLine("Drive name = {0}", drive.Name); @@ -1381,20 +1272,19 @@ private bool CanRemoveDrive(PSDriveInfo drive, CmdletProviderContext context) e); } - if (result != null) { // Make sure the provider didn't try to pull a fast one on us // and substitute a different drive. - if (String.Compare(result.Name, drive.Name, StringComparison.CurrentCultureIgnoreCase) == 0) + if (string.Equals(result.Name, drive.Name, StringComparison.OrdinalIgnoreCase)) { driveRemovable = true; } } return driveRemovable; - } // CanRemoveDrive + } #endregion RemoveDrive @@ -1404,29 +1294,25 @@ private bool CanRemoveDrive(PSDriveInfo drive, CmdletProviderContext context) /// Gets an enumerable list of the drives that are mounted in /// the specified scope. /// - /// /// /// The scope to retrieve the drives from. If null or empty, /// all drives from all scopes will be retrieved. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal Collection Drives(string scope) { - Dictionary driveTable = new Dictionary(); + Dictionary driveTable = new Dictionary(); SessionStateScope startingScope = _currentScope; - if (!String.IsNullOrEmpty(scope)) + if (!string.IsNullOrEmpty(scope)) { startingScope = GetScopeByID(scope); } @@ -1434,13 +1320,12 @@ internal Collection Drives(string scope) SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(startingScope); DriveInfo[] alldrives = DriveInfo.GetDrives(); - Collection driveNames = new Collection(); + Collection driveNames = new Collection(); foreach (DriveInfo drive in alldrives) { driveNames.Add(drive.Name.Substring(0, 1)); } - foreach (SessionStateScope lookupScope in scopeEnumerator) { foreach (PSDriveInfo drive in lookupScope.Drives) @@ -1466,8 +1351,6 @@ internal Collection Drives(string scope) driveTable.Remove(drive.Name); } - - if (driveIsValid && !driveTable.ContainsKey(drive.Name)) { driveTable[drive.Name] = drive; @@ -1482,7 +1365,7 @@ internal Collection Drives(string scope) { break; } - } // foreach scope + } // Now lookup all the file system drives and automount any that are not // present @@ -1520,13 +1403,14 @@ internal Collection Drives(string scope) { results.Add(drive); } + return results; - } // Drives + } #endregion Drives /// - /// Gets or sets the current working drive + /// Gets or sets the current working drive. /// internal PSDriveInfo CurrentDrive { @@ -1536,7 +1420,7 @@ internal PSDriveInfo CurrentDrive return ExecutionContext.TopLevelSessionState.CurrentDrive; else return _currentDrive; - } // get + } set { @@ -1544,10 +1428,9 @@ internal PSDriveInfo CurrentDrive ExecutionContext.TopLevelSessionState.CurrentDrive = value; else _currentDrive = value; - } // set - } // CurrentDrive - } // SessionStateInternal class + } + } + } } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateDynamicProperty.cs b/src/System.Management.Automation/engine/SessionStateDynamicProperty.cs index 4c15dfc2fed..575c3387d5e 100644 --- a/src/System.Management.Automation/engine/SessionStateDynamicProperty.cs +++ b/src/System.Management.Automation/engine/SessionStateDynamicProperty.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -12,7 +12,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -21,58 +21,45 @@ internal sealed partial class SessionStateInternal #region NewProperty /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path(s) to the item(s) on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A property table containing the properties and their values. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection NewProperty( string[] paths, string property, @@ -83,12 +70,12 @@ internal Collection NewProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) { - throw PSTraceSource.NewArgumentNullException("property"); + throw PSTraceSource.NewArgumentNullException(nameof(property)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -102,62 +89,49 @@ internal Collection NewProperty( Collection results = context.GetAccumulatedObjects(); return results; - } // NewProperty + } /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path(s) to the item(s) on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. The property should be passed to the context as a PSObject. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void NewProperty( string[] paths, string property, @@ -167,12 +141,12 @@ internal void NewProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) { - throw PSTraceSource.NewArgumentNullException("property"); + throw PSTraceSource.NewArgumentNullException(nameof(property)); } ProviderInfo provider = null; @@ -182,7 +156,7 @@ internal void NewProperty( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -198,48 +172,38 @@ internal void NewProperty( NewProperty(providerInstance, providerPath, property, type, value, context); } } - } // NewProperty + } /// /// Creates a new property on the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to create. /// - /// /// /// The type of the property to create. /// - /// /// /// The value of the property to create. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void NewProperty( CmdletProvider providerInstance, string path, @@ -294,59 +258,47 @@ private void NewProperty( path, e); } - } // NewProperty + } /// /// Gets the dynamic parameters for the new-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object NewPropertyDynamicParameters( string path, string propertyName, @@ -383,55 +335,44 @@ internal object NewPropertyDynamicParameters( return NewPropertyDynamicParameters(providerInstance, providerPaths[0], propertyName, type, value, newContext); } - return null; - } // NewPropertyDynamicParameters + return null; + } /// /// Gets the dynamic parameters for the new-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to create. /// - /// /// /// The type of the property to create. /// - /// /// /// The value of the property. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object NewPropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -483,8 +424,9 @@ private object NewPropertyDynamicParameters( path, e); } + return result; - } // NewPropertyDynamicParameters + } #endregion NewProperty @@ -493,54 +435,44 @@ private object NewPropertyDynamicParameters( /// /// Removes the specified property from the specified item. /// - /// /// /// The path(s) to the item(s) to remove the property from. /// - /// /// /// The name of the property to remove /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveProperty(string[] paths, string property, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) { - throw PSTraceSource.NewArgumentNullException("property"); + throw PSTraceSource.NewArgumentNullException(nameof(property)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -550,50 +482,40 @@ internal void RemoveProperty(string[] paths, string property, bool force, bool l RemoveProperty(paths, property, context); context.ThrowFirstErrorOrDoNothing(); - } // RemoveProperty + } /// /// Removes the specified properties from the specified item. /// - /// /// /// The path(s) to the item(s) to remove the properties from. /// - /// /// /// The name of the property to remove /// - /// /// /// The context which the core command is running. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void RemoveProperty( string[] paths, string property, @@ -601,19 +523,19 @@ internal void RemoveProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) { - throw PSTraceSource.NewArgumentNullException("property"); + throw PSTraceSource.NewArgumentNullException(nameof(property)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -632,40 +554,32 @@ internal void RemoveProperty( RemoveProperty(providerInstance, providerPath, property, context); } } - } // RemoveProperty + } /// /// Removes the property from the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to remove. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void RemoveProperty( CmdletProvider providerInstance, string path, @@ -718,51 +632,41 @@ private void RemoveProperty( path, e); } - } // RemoveProperty + } /// /// Gets the dynamic parameters for the remove-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object RemovePropertyDynamicParameters( string path, string propertyName, @@ -797,47 +701,38 @@ internal object RemovePropertyDynamicParameters( return RemovePropertyDynamicParameters(providerInstance, providerPaths[0], propertyName, newContext); } - return null; - } // RemovePropertyDynamicParameters + return null; + } /// /// Gets the dynamic parameters for the remove-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to remove. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object RemovePropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -857,7 +752,6 @@ private object RemovePropertyDynamicParameters( context != null, "Caller should validate context before calling this method"); - object result = null; try { @@ -888,8 +782,9 @@ private object RemovePropertyDynamicParameters( path, e); } + return result; - } // RemovePropertyDynamicParameters + } #endregion RemoveProperty @@ -899,54 +794,42 @@ private object RemovePropertyDynamicParameters( /// Copies the specified property on the specified item to the specified property /// on the destination item. The source and destination items can be the same item. /// - /// /// /// The path(s) to the item(s) to copy the property from. /// - /// /// /// The name of the property to be copied. /// - /// /// /// The path to the item to copy the property to. /// - /// /// /// The name of the property to copy the property to. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection CopyProperty( string[] sourcePaths, string sourceProperty, @@ -957,22 +840,22 @@ internal Collection CopyProperty( { if (sourcePaths == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationPath == null) { - throw PSTraceSource.NewArgumentNullException("destinationPath"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationPath)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -986,61 +869,49 @@ internal Collection CopyProperty( Collection results = context.GetAccumulatedObjects(); return results; - } // CopyProperty + } /// /// Copies the specified property on the specified item to the specified property /// on the destination item. The source and destination items can be the same item. /// - /// /// /// The path(s) to the item(s) to copy the property from. /// - /// /// /// The name of the property to be copied. /// - /// /// /// The path to the item to copy the property to. /// - /// /// /// The name of the property to copy the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void CopyProperty( string[] sourcePaths, string sourceProperty, @@ -1050,29 +921,29 @@ internal void CopyProperty( { if (sourcePaths == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationPath == null) { - throw PSTraceSource.NewArgumentNullException("destinationPath"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationPath)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } foreach (string sourcePath in sourcePaths) { if (sourcePath == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } ProviderInfo provider = null; @@ -1124,48 +995,38 @@ internal void CopyProperty( } } } - } // CopyProperty + } /// - /// Copies the property + /// Copies the property. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to be copied. /// - /// /// /// The path to the item to copy the property to. /// - /// /// /// The name of the property to copy the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void CopyProperty( CmdletProvider providerInstance, string sourcePath, @@ -1228,59 +1089,47 @@ private void CopyProperty( sourcePath, e); } - } // CopyProperty + } /// /// Gets the dynamic parameters for the copy-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to be copied. /// - /// /// /// The path to the item to copy the property to. /// - /// /// /// The name of the property to copy the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object CopyPropertyDynamicParameters( string path, string sourceProperty, @@ -1323,54 +1172,44 @@ internal object CopyPropertyDynamicParameters( destinationProperty, newContext); } + return null; - } // CopyPropertyDynamicParameters + } /// /// Gets the dynamic parameters for the copy-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The name of the property to copy. /// - /// /// /// The path to the item to copy the property to. /// - /// /// /// The name of the property to copy the property to on the destination item. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object CopyPropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -1427,8 +1266,9 @@ private object CopyPropertyDynamicParameters( path, e); } + return result; - } // CopyPropertyDynamicParameters + } #endregion CopyProperty @@ -1438,58 +1278,45 @@ private object CopyPropertyDynamicParameters( /// Moves the specified property on the specified item to the specified property /// on the destination item. The source and destination items can be the same item. /// - /// /// /// The path(s) to the item(s) to move the property from. /// - /// /// /// The name of the property to be moved. /// - /// /// /// The path to the item to move the property to. /// - /// /// /// The name of the property to move the property to. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If resolves to more than one item. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection MoveProperty( string[] sourcePaths, string sourceProperty, @@ -1500,22 +1327,22 @@ internal Collection MoveProperty( { if (sourcePaths == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationPath == null) { - throw PSTraceSource.NewArgumentNullException("destinationPath"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationPath)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1529,65 +1356,52 @@ internal Collection MoveProperty( Collection results = context.GetAccumulatedObjects(); return results; - } // MoveProperty + } /// /// Moves the specified property on the specified item to the specified property /// on the destination item. The source and destination items can be the same item. /// - /// /// /// The path(s) to the item(s) to move the property from. /// - /// /// /// The name of the property to be moved. /// - /// /// /// The path to the item to move the property to. /// - /// /// /// The name of the property to move the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If , , /// , or /// is null. /// - /// /// /// If resolves to more than one item. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void MoveProperty( string[] sourcePaths, string sourceProperty, @@ -1597,22 +1411,22 @@ internal void MoveProperty( { if (sourcePaths == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationPath == null) { - throw PSTraceSource.NewArgumentNullException("destinationPath"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationPath)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } ProviderInfo provider = null; @@ -1639,7 +1453,7 @@ internal void MoveProperty( { ArgumentException argException = PSTraceSource.NewArgumentException( - "destinationPath", + nameof(destinationPath), SessionStateStrings.MovePropertyDestinationResolveToSingle); context.WriteError(new ErrorRecord(argException, argException.GetType().FullName, ErrorCategory.InvalidArgument, destinationProviderPaths)); @@ -1650,7 +1464,7 @@ internal void MoveProperty( { if (sourcePath == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } Collection providerPaths = @@ -1667,49 +1481,38 @@ internal void MoveProperty( } } } - } // MoveProperty - + } /// - /// Moves the property from one item to another + /// Moves the property from one item to another. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The property to be moved. /// - /// /// /// The path of the item to move the property to. /// - /// /// /// The name of the property to move the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void MoveProperty( CmdletProvider providerInstance, string sourcePath, @@ -1772,59 +1575,47 @@ private void MoveProperty( sourcePath, e); } - } // MoveProperty + } /// /// Gets the dynamic parameters for the move-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to be moved. /// - /// /// /// The path to the item to move the property to. /// - /// /// /// The name of the property to move the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object MovePropertyDynamicParameters( string path, string sourceProperty, @@ -1867,54 +1658,44 @@ internal object MovePropertyDynamicParameters( destinationProperty, newContext); } + return null; - } // MovePropertyDynamicParameters + } /// /// Gets the dynamic parameters for the move-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to move. /// - /// /// /// The path to the item to move the property to. /// - /// /// /// The name of the property on the destination item to move the property to. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object MovePropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -1936,7 +1717,6 @@ private object MovePropertyDynamicParameters( context != null, "Caller should validate context before calling this method"); - object result = null; try @@ -1973,8 +1753,9 @@ private object MovePropertyDynamicParameters( path, e); } + return result; - } // MovePropertyDynamicParameters + } #endregion MoveProperty @@ -1983,49 +1764,38 @@ private object MovePropertyDynamicParameters( /// /// Renames the specified property on the specified item to the specified property. /// - /// /// /// The path(s) to the item(s) to rename the property on. /// - /// /// /// The name of the property to be renamed. /// - /// /// /// The name of the property to rename the property to. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If , , /// or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection RenameProperty( string[] sourcePaths, string sourceProperty, @@ -2035,17 +1805,17 @@ internal Collection RenameProperty( { if (sourcePaths == null) { - throw PSTraceSource.NewArgumentNullException("sourcePaths"); + throw PSTraceSource.NewArgumentNullException(nameof(sourcePaths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -2058,55 +1828,44 @@ internal Collection RenameProperty( Collection results = context.GetAccumulatedObjects(); return results; - } // RenameProperty + } /// /// Renames the specified property on the specified item to the specified property. /// - /// /// /// The path(s) to the item(s) to rename the property on. /// - /// /// /// The name of the property to be renamed. /// - /// /// /// The name of the property to rename the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// If , , /// or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void RenameProperty( string[] paths, string sourceProperty, @@ -2115,24 +1874,24 @@ internal void RenameProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (sourceProperty == null) { - throw PSTraceSource.NewArgumentNullException("sourceProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(sourceProperty)); } if (destinationProperty == null) { - throw PSTraceSource.NewArgumentNullException("destinationProperty"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationProperty)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -2151,45 +1910,35 @@ internal void RenameProperty( RenameProperty(providerInstance, providerPath, sourceProperty, destinationProperty, context); } } - } // RenameProperty - + } /// /// Renames the property of the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to rename. /// - /// /// /// The new name of the property. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void RenameProperty( CmdletProvider providerInstance, string sourcePath, @@ -2247,55 +1996,44 @@ private void RenameProperty( sourcePath, e); } - } // RenameProperty + } /// /// Gets the dynamic parameters for the rename-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to be renamed. /// - /// /// /// The name of the property to rename the property to. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object RenamePropertyDynamicParameters( string path, string sourceProperty, @@ -2336,50 +2074,41 @@ internal object RenamePropertyDynamicParameters( destinationProperty, newContext); } + return null; - } // RenamePropertyDynamicParameters + } /// /// Gets the dynamic parameters for the rename-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The name of the property to rename. /// - /// /// /// The new name for the property. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object RenamePropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -2434,13 +2163,14 @@ private object RenamePropertyDynamicParameters( path, e); } + return result; - } // RenamePropertyDynamicParameters + } #endregion RenameProperty #endregion IDynamicPropertyCmdletProvider accessors - } // SessionStateInternal class + } } #pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs b/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs index 1387543d285..5ef285ddcec 100644 --- a/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs @@ -1,18 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; +using System.Management.Automation.Security; +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -21,7 +21,7 @@ internal sealed partial class SessionStateInternal /// /// Add an new SessionState function entry to this session state object... /// - /// The entry to add + /// The entry to add. internal void AddSessionStateEntry(SessionStateFunctionEntry entry) { ScriptBlock sb = entry.ScriptBlock.Clone(); @@ -29,7 +29,7 @@ internal void AddSessionStateEntry(SessionStateFunctionEntry entry) FunctionInfo fn = this.SetFunction(entry.Name, sb, null, entry.Options, false, CommandOrigin.Internal, this.ExecutionContext, entry.HelpFile, true); fn.Visibility = entry.Visibility; fn.Module = entry.Module; - fn.ScriptBlock.LanguageMode = PSLanguageMode.FullLanguage; + fn.ScriptBlock.LanguageMode = entry.ScriptBlock.LanguageMode ?? PSLanguageMode.FullLanguage; } /// @@ -37,12 +37,10 @@ internal void AddSessionStateEntry(SessionStateFunctionEntry entry) /// the current scope as a reference and filtering the functions in /// the other scopes based on the scoping rules. /// - /// /// /// An IDictionary representing the visible functions. /// - /// - internal IDictionary GetFunctionTable() + internal IDictionary GetFunctionTable() { SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(_currentScope); @@ -62,28 +60,24 @@ internal IDictionary GetFunctionTable() } return result; - } // GetFunctionTable + } /// - /// Gets an IEnumerable for the function table for a given scope + /// Gets an IEnumerable for the function table for a given scope. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "script", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal IDictionary GetFunctionTableAtScope(string scopeID) { Dictionary result = @@ -104,45 +98,67 @@ internal IDictionary GetFunctionTableAtScope(string scopeI } return result; - } // GetFunctionTableAtScope + } /// /// List of functions/filters to export from this session state object... /// internal List ExportedFunctions { get; } = new List(); + internal bool UseExportList { get; set; } = false; + /// - /// List of workflows to export from this session state object... + /// Set to true when module functions are being explicitly exported using Export-ModuleMember. /// - internal List ExportedWorkflows { get; } = new List(); + internal bool FunctionsExported { get; set; } - internal bool UseExportList { get; set; } = false; + /// + /// Set to true when any processed module functions are being explicitly exported using '*' wildcard. + /// + internal bool FunctionsExportedWithWildcard + { + get + { + return _functionsExportedWithWildcard; + } + + set + { + Dbg.Assert((value), "This property should never be set/reset to false"); + if (value) + { + _functionsExportedWithWildcard = value; + } + } + } + + private bool _functionsExportedWithWildcard; + + /// + /// Set to true if module loading is performed under a manifest that explicitly exports functions (no wildcards) + /// + internal bool ManifestWithExplicitFunctionExport { get; set; } /// /// Get a functions out of session state. /// - /// /// /// name of function to look up /// - /// /// /// Origin of the command that called this API... /// - /// /// /// The value of the specified function. /// - /// /// /// If is null or empty. /// - /// internal FunctionInfo GetFunction(string name, CommandOrigin origin) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } FunctionInfo result = null; @@ -156,31 +172,59 @@ internal FunctionInfo GetFunction(string name, CommandOrigin origin) { result = ((IEnumerator)searcher).Current; } - return result; - } // GetFunction + + return (IsFunctionVisibleInDebugger(result, origin)) ? result : null; + } + + private bool IsFunctionVisibleInDebugger(FunctionInfo fnInfo, CommandOrigin origin) + { + // Ensure the returned function item is not exposed across language boundaries when in + // a debugger breakpoint or nested prompt. + // A debugger breakpoint/nested prompt has access to all current scoped functions. + // This includes both running commands from the prompt or via a debugger Action scriptblock. + + // Early out. + // Always allow built-in functions needed for command line debugging. + if (this.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage || + (fnInfo == null) || + (fnInfo.Name.Equals("prompt", StringComparison.OrdinalIgnoreCase)) || + (fnInfo.Name.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase)) || + (fnInfo.Name.Equals("Clear-Host", StringComparison.Ordinal))) + { + return true; + } + + // Check both InNestedPrompt and Debugger.InBreakpoint to ensure we don't miss a case. + // Function is not visible if function and context language modes are different. + var runspace = this.ExecutionContext.CurrentRunspace; + if ((runspace != null) && + (runspace.InNestedPrompt || (runspace.Debugger?.InBreakpoint == true)) && + (fnInfo.DefiningLanguageMode.HasValue && (fnInfo.DefiningLanguageMode != this.ExecutionContext.LanguageMode))) + { + return false; + } + + return true; + } /// /// Get a functions out of session state. /// - /// /// /// name of function to look up /// - /// /// /// The value of the specified function. /// - /// /// /// If is null or empty. /// - /// internal FunctionInfo GetFunction(string name) { return GetFunction(name, CommandOrigin.Internal); - } // GetFunction + } - private IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) + private static IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) { if (ipmp == null || ipmp.Body.ParamBlock == null) yield break; @@ -194,7 +238,7 @@ private IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) var cvv = new ConstantValueVisitor { AttributeArgument = true }; for (int i = 0; i < attributeAst.PositionalArguments.Count; i++) { - yield return Compiler._attrArgToStringConverter.Target(Compiler._attrArgToStringConverter, + yield return Compiler.s_attrArgToStringConverter.Target(Compiler.s_attrArgToStringConverter, attributeAst.PositionalArguments[i].Accept(cvv)); } } @@ -204,44 +248,37 @@ private IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// Origin of the caller of this API /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunctionRaw( string name, ScriptBlock function, CommandOrigin origin) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (function == null) { - throw PSTraceSource.NewArgumentNullException("function"); + throw PSTraceSource.NewArgumentNullException(nameof(function)); } string originalName = name; @@ -249,7 +286,7 @@ internal FunctionInfo SetFunctionRaw( FunctionLookupPath path = new FunctionLookupPath(name); name = path.UnqualifiedPath; - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { SessionStateException exception = new SessionStateException( @@ -282,47 +319,38 @@ internal FunctionInfo SetFunctionRaw( } return functionInfo; - } // SetFunctionRaw + } /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// The original function (if any) from which the ScriptBlock is derived. /// - /// /// /// The options to set on the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// Origin of the caller of this API /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -332,52 +360,41 @@ internal FunctionInfo SetFunction( CommandOrigin origin) { return SetFunction(name, function, originalFunction, options, force, origin, ExecutionContext, null); - } // SetFunction + } /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// The original function (if any) from which the ScriptBlock is derived. /// - /// /// /// The options to set on the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// Origin of the caller of this API /// - /// /// /// The name of the help file associated with the function. /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -388,55 +405,44 @@ internal FunctionInfo SetFunction( string helpFile) { return SetFunction(name, function, originalFunction, options, force, origin, ExecutionContext, helpFile, false); - } // SetFunction + } /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// The original function (if any) from which the ScriptBlock is derived. /// - /// /// /// The options to set on the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// Origin of the caller of this API /// - /// /// /// The execution context for the function. /// - /// /// /// The name of the help file associated with the function. /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -448,60 +454,47 @@ internal FunctionInfo SetFunction( string helpFile) { return SetFunction(name, function, originalFunction, options, force, origin, context, helpFile, false); - } // SetFunction + } /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// The original function (if any) from which the ScriptBlock is derived. /// - /// /// /// The options to set on the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// Origin of the caller of this API /// - /// /// /// The execution context for the function. /// - /// /// /// The name of the help file associated with the function. /// - /// /// /// Set to true if it is a regular function (meaning, we do not need to check if the script contains JobDefinition Attribute and then process it) /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -513,14 +506,14 @@ internal FunctionInfo SetFunction( string helpFile, bool isPreValidated) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (function == null) { - throw PSTraceSource.NewArgumentNullException("function"); + throw PSTraceSource.NewArgumentNullException(nameof(function)); } string originalName = name; @@ -528,7 +521,7 @@ internal FunctionInfo SetFunction( FunctionLookupPath path = new FunctionLookupPath(name); name = path.UnqualifiedPath; - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { SessionStateException exception = new SessionStateException( @@ -546,7 +539,6 @@ internal FunctionInfo SetFunction( options |= ScopedItemOptions.Private; } - FunctionScopeItemSearcher searcher = new FunctionScopeItemSearcher( this, @@ -554,47 +546,38 @@ internal FunctionInfo SetFunction( origin); return searcher.InitialScope.SetFunction(name, function, originalFunction, options, force, origin, context, helpFile); - } // SetFunction + } /// /// Set a function in the current scope of session state. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// The original function (if any) from which the ScriptBlock is derived. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// The origin of the caller /// - /// /// /// If is null or empty. /// or /// If is not a FilterInfo /// or FunctionInfo /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -602,14 +585,14 @@ internal FunctionInfo SetFunction( bool force, CommandOrigin origin) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } if (function == null) { - throw PSTraceSource.NewArgumentNullException("function"); + throw PSTraceSource.NewArgumentNullException(nameof(function)); } string originalName = name; @@ -617,7 +600,7 @@ internal FunctionInfo SetFunction( FunctionLookupPath path = new FunctionLookupPath(name); name = path.UnqualifiedPath; - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { SessionStateException exception = new SessionStateException( @@ -636,7 +619,6 @@ internal FunctionInfo SetFunction( options |= ScopedItemOptions.Private; } - FunctionScopeItemSearcher searcher = new FunctionScopeItemSearcher( this, @@ -675,6 +657,7 @@ internal FunctionInfo SetFunction( result = scope.SetFunction(name, function, force, origin, ExecutionContext); } } + return result; } @@ -684,34 +667,27 @@ internal FunctionInfo SetFunction( /// BUGBUG: this overload is preserved because a lot of tests use reflection to /// call it. The tests should be fixed and this API eventually removed. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value of the function being set. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// If is null or empty. /// or /// If is not a FilterInfo /// or FunctionInfo /// - /// /// /// If is null. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction(string name, ScriptBlock function, bool force) { return SetFunction(name, function, null, force, CommandOrigin.Internal); @@ -720,32 +696,26 @@ internal FunctionInfo SetFunction(string name, ScriptBlock function, bool force) /// /// Removes a function from the function table. /// - /// /// /// The name of the function to remove. /// - /// /// /// THe origin of the caller of this API /// - /// /// /// If true, the function is removed even if it is ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If the function is constant. /// - /// internal void RemoveFunction(string name, bool force, CommandOrigin origin) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } // Use the scope enumerator to find an existing function @@ -764,29 +734,25 @@ internal void RemoveFunction(string name, bool force, CommandOrigin origin) { scope = searcher.CurrentLookupScope; } + scope.RemoveFunction(name, force); - } // RemoveFunction + } /// /// Removes a function from the function table. /// - /// /// /// The name of the function to remove. /// - /// /// /// If true, the function is removed even if it is ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If the function is constant. /// - /// internal void RemoveFunction(string name, bool force) { RemoveFunction(name, force, CommandOrigin.Internal); @@ -798,19 +764,15 @@ internal void RemoveFunction(string name, bool force) /// /// BUGBUG: This is only used by the implicit remoting functions... /// - /// /// /// The name of the function to remove. /// - /// /// /// Module the function might be imported from. /// - /// /// /// If the function is constant. /// - /// internal void RemoveFunction(string name, PSModuleInfo module) { Dbg.Assert(module != null, "Caller should verify that module parameter is not null"); @@ -825,5 +787,5 @@ internal void RemoveFunction(string name, PSModuleInfo module) } #endregion Functions - } // SessionStateInternal class -} \ No newline at end of file + } +} diff --git a/src/System.Management.Automation/engine/SessionStateItem.cs b/src/System.Management.Automation/engine/SessionStateItem.cs index 2869d702c44..173043515ba 100644 --- a/src/System.Management.Automation/engine/SessionStateItem.cs +++ b/src/System.Management.Automation/engine/SessionStateItem.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -12,7 +12,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -21,52 +21,42 @@ internal sealed partial class SessionStateInternal #region GetItem /// - /// Gets the specified object + /// Gets the specified object. /// - /// /// /// The path(s) to the object(s). They can be either a relative (most common) /// or absolute path. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The item at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetItem(string[] paths, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -82,59 +72,49 @@ internal Collection GetItem(string[] paths, bool force, bool literalPa Collection results = context.GetAccumulatedObjects(); return results; - } // GetItem + } /// - /// Gets the specified object + /// Gets the specified object. /// - /// /// /// The path(s) to the object(s). They can be either a relative (most common) /// or absolute path. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteObject /// method of the parameter. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void GetItem( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -144,7 +124,7 @@ internal void GetItem( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -160,36 +140,29 @@ internal void GetItem( GetItemPrivate(providerInstance, providerPath, context); } } - } // GetItem + } /// /// Gets the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void GetItemPrivate( CmdletProvider providerInstance, string path, @@ -208,7 +181,6 @@ private void GetItemPrivate( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -237,47 +209,38 @@ private void GetItemPrivate( path, e); } - } // GetItem + } /// /// Gets the dynamic parameters for the get-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetItemDynamicParameters(string path, CmdletProviderContext context) { if (path == null) @@ -309,42 +272,35 @@ internal object GetItemDynamicParameters(string path, CmdletProviderContext cont return GetItemDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // GetItemDynamicParameters + } /// /// Gets the dynamic parameters for the get-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -363,7 +319,6 @@ private object GetItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -393,64 +348,54 @@ private object GetItemDynamicParameters( path, e); } + return result; - } // GetItemDynamicParameters + } #endregion GetItem #region SetItem /// - /// Gets the specified object + /// Gets the specified object. /// - /// /// /// The path(s) to the object. It can be either a relative (most common) /// or absolute path. /// - /// /// /// The new value for the item at the specified path. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The item that was modified at the specified path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection SetItem(string[] paths, object value, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -464,51 +409,41 @@ internal Collection SetItem(string[] paths, object value, bool force, // Since there was no errors return the accumulated objects return context.GetAccumulatedObjects(); - } // SetItem + } /// - /// Sets the specified object to the specified value + /// Sets the specified object to the specified value. /// - /// /// /// The path(s) to the object. It can be either a relative (most common) /// or absolute path. /// - /// /// /// The new value of the item at the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void SetItem( string[] paths, object value, @@ -516,14 +451,14 @@ internal void SetItem( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -545,40 +480,32 @@ internal void SetItem( } } } - } // SetItem + } /// /// Sets item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The value of the item. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void SetItem( CmdletProvider providerInstance, string path, @@ -598,7 +525,6 @@ private void SetItem( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -627,51 +553,41 @@ private void SetItem( path, e); } - } // SetItem + } /// /// Gets the dynamic parameters for the set-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The new value of the item at the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object SetItemDynamicParameters(string path, object value, CmdletProviderContext context) { if (path == null) @@ -703,46 +619,38 @@ internal object SetItemDynamicParameters(string path, object value, CmdletProvid return SetItemDynamicParameters(providerInstance, providerPaths[0], value, newContext); } + return null; - } // SetItemDynamicParameters + } /// /// Gets the dynamic parameters for the set-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The value to be set. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object SetItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -762,7 +670,6 @@ private object SetItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -792,8 +699,9 @@ private object SetItemDynamicParameters( path, e); } + return result; - } // SetItemDynamicParameters + } #endregion SetItem @@ -804,54 +712,43 @@ private object SetItemDynamicParameters( /// maps to, this could mean the properties and/or content and/or value is /// cleared. /// - /// /// /// The path(s) to the object. It can be either a relative (most common) /// or absolute path. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The items that were cleared. /// - /// /// /// If an error occurs that error will be thrown. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection ClearItem(string[] paths, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -863,56 +760,47 @@ internal Collection ClearItem(string[] paths, bool force, bool literal context.ThrowFirstErrorOrDoNothing(); return context.GetAccumulatedObjects(); - } // ClearItem + } /// /// Clears the specified item. Depending on the provider that the path /// maps to, this could mean the properties and/or content and/or value is /// cleared. /// - /// /// /// The path(s) to the object. It can be either a relative (most common) /// or absolute path. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void ClearItem( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -922,7 +810,7 @@ internal void ClearItem( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -941,36 +829,29 @@ internal void ClearItem( } } } - } // ClearItem + } /// /// Clears the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void ClearItemPrivate( CmdletProvider providerInstance, string path, @@ -989,7 +870,6 @@ private void ClearItemPrivate( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -1018,47 +898,38 @@ private void ClearItemPrivate( path, e); } - } // ClearItem + } /// /// Gets the dynamic parameters for the clear-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object ClearItemDynamicParameters(string path, CmdletProviderContext context) { if (path == null) @@ -1090,42 +961,35 @@ internal object ClearItemDynamicParameters(string path, CmdletProviderContext co return ClearItemDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // ClearItemDynamicParameters + } /// /// Gets the dynamic parameters for the clear-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object ClearItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -1144,7 +1008,6 @@ private object ClearItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -1174,8 +1037,9 @@ private object ClearItemDynamicParameters( path, e); } + return result; - } // ClearItemDynamicParameters + } #endregion ClearItem @@ -1185,46 +1049,37 @@ private object ClearItemDynamicParameters( /// Performs the default action on the specified item. The default action is /// determined by the provider. /// - /// /// /// The path(s) to the object(s). They can be either a relative (most common) /// or absolute path(s). /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If an error occurs that error will be thrown. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void InvokeDefaultAction(string[] paths, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1233,55 +1088,46 @@ internal void InvokeDefaultAction(string[] paths, bool literalPath) InvokeDefaultAction(paths, context); context.ThrowFirstErrorOrDoNothing(); - } // InvokeDefaultAction + } /// /// Performs the default action on the specified item. The default action /// is determined by the provider. /// - /// /// /// The path(s) to the object(s). They can be either a relative (most common) /// or absolute paths. /// - /// /// /// The context which the core command is running. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void InvokeDefaultAction( string[] paths, CmdletProviderContext context) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -1291,7 +1137,7 @@ internal void InvokeDefaultAction( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -1310,36 +1156,29 @@ internal void InvokeDefaultAction( } } } - } // InvokeDefaultAction + } /// /// Invokes the default action on the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void InvokeDefaultActionPrivate( CmdletProvider providerInstance, string path, @@ -1358,7 +1197,6 @@ private void InvokeDefaultActionPrivate( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -1387,47 +1225,38 @@ private void InvokeDefaultActionPrivate( path, e); } - } // InvokeDefaultAction + } /// /// Gets the dynamic parameters for the invoke-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object InvokeDefaultActionDynamicParameters(string path, CmdletProviderContext context) { if (path == null) @@ -1459,42 +1288,35 @@ internal object InvokeDefaultActionDynamicParameters(string path, CmdletProvider return InvokeDefaultActionDynamicParameters(providerInstance, providerPaths[0], newContext); } + return null; - } // InvokeDefaultActionDynamicParameters + } /// /// Gets the dynamic parameters for the invoke-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object InvokeDefaultActionDynamicParameters( CmdletProvider providerInstance, string path, @@ -1513,7 +1335,6 @@ private object InvokeDefaultActionDynamicParameters( context != null, "Caller should validate context before calling this method"); - ItemCmdletProvider itemCmdletProvider = GetItemProviderInstance(providerInstance); @@ -1543,14 +1364,14 @@ private object InvokeDefaultActionDynamicParameters( path, e); } + return result; - } // InvokeDefaultActionDynamicParameters + } #endregion InvokeDefaultAction #endregion ItemCmdletProvider accessors - } // SessionStateInternal class + } } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 9c512b40b92..56de07b8871 100644 --- a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs @@ -1,19 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; +using System.Management.Automation.Internal; using System.Management.Automation.Provider; using Dbg = System.Management.Automation; - namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -24,11 +24,9 @@ internal sealed partial class SessionStateInternal /// you want to change the current working directory use the SetLocation /// method. /// - /// /// /// If a location has not been set yet. /// - /// internal PathInfo CurrentLocation { get @@ -49,45 +47,38 @@ internal PathInfo CurrentLocation new SessionState(this)); return result; - } // get - } // CurrentLocation + } + } /// /// Gets the namespace specific path of the current working directory /// for the specified namespace. /// - /// /// /// An identifier that uniquely identifies the namespace to get the /// current working directory for. /// - /// /// /// The namespace specific path of the current working directory for /// the specified namespace. /// - /// /// /// If is null. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If a current drive cannot be found for the provider /// - /// internal PathInfo GetNamespaceCurrentLocation(string namespaceID) { if (namespaceID == null) { - throw PSTraceSource.NewArgumentNullException("namespaceID"); + throw PSTraceSource.NewArgumentNullException(nameof(namespaceID)); } // If namespace ID is empty, we will use the current working drive - PSDriveInfo drive = null; if (namespaceID.Length == 0) @@ -97,7 +88,6 @@ internal PathInfo GetNamespaceCurrentLocation(string namespaceID) else { // First check to see if the provider exists - ProvidersCurrentWorkingDrive.TryGetValue(GetSingleProvider(namespaceID), out drive); } @@ -115,7 +105,6 @@ internal PathInfo GetNamespaceCurrentLocation(string namespaceID) context.Drive = drive; // Now make the namespace specific path - string path = null; if (drive.Hidden) @@ -133,108 +122,156 @@ internal PathInfo GetNamespaceCurrentLocation(string namespaceID) { path = LocationGlobber.GetDriveQualifiedPath(drive.CurrentLocation, drive); } + return new PathInfo(drive, drive.Provider, path, new SessionState(this)); - } // GetNamespaceCurrentLocation + } /// - /// Changes the current working directory to the path specified + /// Changes the current working directory to the path specified. /// - /// /// - /// The path of the new current working directory + /// The path of the new current working directory. /// - /// /// /// The PathInfo object representing the path of the location /// that was set. /// - /// /// /// If is null. /// - /// /// /// If does not exist, is not a container, or /// resolved to multiple containers. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If refers to a drive that does not exist. /// - /// /// /// If the provider associated with threw an /// exception. /// - /// internal PathInfo SetLocation(string path) { return SetLocation(path, null); - } // SetLocation + } /// - /// Changes the current working directory to the path specified + /// Changes the current working directory to the path specified. /// - /// /// /// The path of the new current working directory /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// The PathInfo object representing the path of the location /// that was set. /// - /// /// /// If is null. /// - /// /// /// If does not exist, is not a container, or /// resolved to multiple containers. /// - /// /// /// If refers to a provider that does not exist. /// - /// /// /// If refers to a drive that does not exist. /// - /// /// /// If the provider associated with threw an /// exception. /// - /// /// /// If the could not be resolved. /// - /// internal PathInfo SetLocation(string path, CmdletProviderContext context) + { + return SetLocation(path, context, literalPath: false); + } + + /// + /// Changes the current working directory to the path specified. + /// + /// + /// The path of the new current working directory. + /// + /// + /// The context the provider uses when performing the operation. + /// + /// + /// Indicate if the path is a literal path. + /// + /// + /// The PathInfo object representing the path of the location + /// that was set. + /// + /// + /// If is null. + /// + /// + /// If does not exist, is not a container, or + /// resolved to multiple containers. + /// + /// + /// If refers to a provider that does not exist. + /// + /// + /// If refers to a drive that does not exist. + /// + /// + /// If the provider associated with threw an + /// exception. + /// + /// + /// If the could not be resolved. + /// + internal PathInfo SetLocation(string path, CmdletProviderContext context, bool literalPath) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } + PathInfo current = CurrentLocation; string originalPath = path; string driveName = null; ProviderInfo provider = null; string providerId = null; + switch (originalPath) + { + case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("-", StringComparison.Ordinal): + if (_setLocationHistory.UndoCount <= 0) + { + throw new InvalidOperationException(SessionStateStrings.LocationUndoStackIsEmpty); + } + + path = _setLocationHistory.Undo(this.CurrentLocation).Path; + break; + case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("+", StringComparison.Ordinal): + if (_setLocationHistory.RedoCount <= 0) + { + throw new InvalidOperationException(SessionStateStrings.LocationRedoStackIsEmpty); + } + + path = _setLocationHistory.Redo(this.CurrentLocation).Path; + break; + default: + var pushPathInfo = GetNewPushPathInfo(); + _setLocationHistory.Push(pushPathInfo); + break; + } + PSDriveInfo previousWorkingDrive = CurrentDrive; // First check to see if the path is a home path - if (LocationGlobber.IsHomePath(path)) { path = Globber.GetHomeRelativePath(path); @@ -245,7 +282,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) // The path is a provider-direct path so use the current // provider and its hidden drive but don't modify the path // at all. - provider = CurrentLocation.Provider; CurrentDrive = provider.HiddenDrive; } @@ -258,25 +294,29 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // See if the path is a relative or absolute // path. - if (Globber.IsAbsolutePath(path, out driveName)) { // Since the path is an absolute path // we need to change the current working // drive - PSDriveInfo newWorkingDrive = GetDrive(driveName); CurrentDrive = newWorkingDrive; + // If the path is simply a colon-terminated drive, + // not a slash-terminated path to the root of a drive, + // set the path to the current working directory of that drive. + string colonTerminatedVolume = CurrentDrive.Name + ':'; + if (CurrentDrive.VolumeSeparatedByColon && (path.Length == colonTerminatedVolume.Length)) + { + path = Path.Combine(colonTerminatedVolume + Path.DirectorySeparatorChar, CurrentDrive.CurrentLocation); + } + // Now that the current working drive is set, // process the rest of the path as a relative path. } } - if (context == null) - { - context = new CmdletProviderContext(this.ExecutionContext); - } + context ??= new CmdletProviderContext(this.ExecutionContext); if (CurrentDrive != null) { @@ -308,11 +348,10 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } @@ -321,7 +360,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw @@ -332,7 +370,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) } // We allow globbing the location as long as it only resolves a single container. - bool foundContainer = false; bool pathIsContainer = false; bool pathIsProviderQualifiedPath = false; @@ -353,12 +390,11 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // The path should be the provider-qualified path without the provider ID // or :: - string providerInternalPath = LocationGlobber.RemoveProviderQualifier(resolvedPath.Path); try { - currentPath = NormalizeRelativePath(GetSingleProvider(providerName), providerInternalPath, String.Empty, normalizePathContext); + currentPath = NormalizeRelativePath(GetSingleProvider(providerName), providerInternalPath, string.Empty, normalizePathContext); } catch (NotSupportedException) { @@ -377,11 +413,10 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } @@ -409,23 +444,20 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } } // Now see if there was errors while normalizing the path - if (normalizePathContext.HasErrors()) { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; normalizePathContext.ThrowFirstErrorOrDoNothing(); @@ -436,8 +468,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) normalizePathContext.RemoveStopReferral(); } - // Check to see if the path is a container - bool isContainer = false; CmdletProviderContext itemContainerContext = @@ -455,7 +485,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; itemContainerContext.ThrowFirstErrorOrDoNothing(); @@ -468,7 +497,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) // Treat this as a container because providers that only // support the ContainerCmdletProvider interface are really // containers at their root. - isContainer = true; } } @@ -482,15 +510,13 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) if (foundContainer) { // The path resolved to more than one container - // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw PSTraceSource.NewArgumentException( - "path", + nameof(path), SessionStateStrings.PathResolvedToMultiple, originalPath); } @@ -516,9 +542,8 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // Remove the root slash since it is implied that the // current working directory is relative to the root. - if (!LocationGlobber.IsProviderDirectPath(path) && - path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.CurrentCulture) && + path.StartsWith(StringLiterals.DefaultPathSeparator) && !pathIsProviderQualifiedPath) { path = path.Substring(1); @@ -534,7 +559,6 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw @@ -546,74 +570,70 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) // Now make sure the current drive is set in the provider's // current working drive hashtable - ProvidersCurrentWorkingDrive[CurrentDrive.Provider] = CurrentDrive; // Set the $PWD variable to the new location - this.SetVariable(SpecialVariables.PWDVarPath, this.CurrentLocation, false, true, CommandOrigin.Internal); + + // If an action has been defined for location changes, invoke it now. + if (PublicSessionState.InvokeCommand.LocationChangedAction != null) + { + var eventArgs = new LocationChangedEventArgs(PublicSessionState, current, CurrentLocation); + PublicSessionState.InvokeCommand.LocationChangedAction.Invoke(ExecutionContext.CurrentRunspace, eventArgs); + s_tracer.WriteLine("Invoked LocationChangedAction"); + } + return this.CurrentLocation; - } // SetLocation + } /// /// Determines if the specified path is the current working directory /// or a parent of the current working directory. /// - /// /// /// A monad namespace absolute or relative path. /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// true, if the path is the current working directory or a parent of the current /// working directory. false, otherwise. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// internal bool IsCurrentLocationOrAncestor(string path, CmdletProviderContext context) { bool result = false; if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } PSDriveInfo drive = null; @@ -644,12 +664,10 @@ internal bool IsCurrentLocationOrAncestor(string path, CmdletProviderContext con // Check to see if the path that was specified is within the current // working drive - if (drive == CurrentDrive) { // The path needs to be normalized to get rid of relative path tokens // so they don't interfere with our path comparisons below - CmdletProviderContext normalizePathContext = new CmdletProviderContext(context); @@ -687,7 +705,6 @@ CmdletProviderContext normalizePathContext s_tracer.WriteLine("Provider path = {0}", providerSpecificPath); // Get the current working directory provider specific path - PSDriveInfo currentWorkingDrive = null; ProviderInfo currentDriveProvider = null; @@ -708,17 +725,15 @@ CmdletProviderContext normalizePathContext // See if the path is the current working directory or a parent // of the current working directory - s_tracer.WriteLine( "Comparing {0} to {1}", providerSpecificPath, currentWorkingPath); - if (String.Compare(providerSpecificPath, currentWorkingPath, StringComparison.CurrentCultureIgnoreCase) == 0) + if (string.Equals(providerSpecificPath, currentWorkingPath, StringComparison.OrdinalIgnoreCase)) { // The path is the current working directory so // return true - s_tracer.WriteLine("The path is the current working directory"); result = true; @@ -727,7 +742,6 @@ CmdletProviderContext normalizePathContext { // Check to see if the specified path is a parent // of the current working directory - string lockedDirectory = currentWorkingPath; while (lockedDirectory.Length > 0) @@ -736,12 +750,11 @@ CmdletProviderContext normalizePathContext // as it can even if that means it has to traverse higher // than the mount point for this drive. That is // why we are passing the empty string as the root here. - lockedDirectory = GetParentPath( drive.Provider, lockedDirectory, - String.Empty, + string.Empty, context); s_tracer.WriteLine( @@ -749,11 +762,10 @@ CmdletProviderContext normalizePathContext lockedDirectory, providerSpecificPath); - if (String.Compare(lockedDirectory, providerSpecificPath, StringComparison.CurrentCultureIgnoreCase) == 0) + if (string.Equals(lockedDirectory, providerSpecificPath, StringComparison.OrdinalIgnoreCase)) { // The path is a parent of the current working // directory - s_tracer.WriteLine( "The path is a parent of the current working directory: {0}", lockedDirectory); @@ -770,20 +782,25 @@ CmdletProviderContext normalizePathContext } return result; - } // IsCurrentLocationOrAncestor + } #endregion Current working directory/drive #region push-Pop current working directory /// - /// A stack of the most recently pushed locations + /// Location history for Set-Location that supports Undo/Redo using bounded stacks. /// - private Dictionary> _workingLocationStack; + private readonly HistoryStack _setLocationHistory; + + /// + /// A stack of the most recently pushed locations. + /// + private readonly Dictionary> _workingLocationStack; private const string startingDefaultStackName = "default"; /// - /// The name of the default location stack + /// The name of the default location stack. /// private string _defaultStackName = startingDefaultStackName; @@ -791,21 +808,34 @@ CmdletProviderContext normalizePathContext /// Pushes the current location onto the working /// location stack so that it can be retrieved later. /// - /// /// /// The ID of the stack to push the location on. If /// it is null or empty the default stack is used. /// - /// internal void PushCurrentLocation(string stackName) { - if (String.IsNullOrEmpty(stackName)) + if (string.IsNullOrEmpty(stackName)) { stackName = _defaultStackName; } - // Create a new instance of the directory/drive pair + // Get the location stack from the hashtable + Stack locationStack = null; + if (!_workingLocationStack.TryGetValue(stackName, out locationStack)) + { + locationStack = new Stack(); + _workingLocationStack[stackName] = locationStack; + } + + // Push the directory/drive pair onto the stack + var pushPathInfo = GetNewPushPathInfo(); + locationStack.Push(pushPathInfo); + } + + private PathInfo GetNewPushPathInfo() + { + // Create a new instance of the directory/drive pair ProviderInfo provider = CurrentDrive.Provider; string mshQualifiedPath = LocationGlobber.GetMshQualifiedPath(CurrentDrive.CurrentLocation, CurrentDrive); @@ -822,19 +852,7 @@ internal void PushCurrentLocation(string stackName) CurrentDrive.Name, mshQualifiedPath); - // Get the location stack from the hashtable - - Stack locationStack = null; - - if (!_workingLocationStack.TryGetValue(stackName, out locationStack)) - { - locationStack = new Stack(); - _workingLocationStack[stackName] = locationStack; - } - - // Push the directory/drive pair onto the stack - - locationStack.Push(newPushLocation); + return newPushLocation; } /// @@ -842,17 +860,14 @@ internal void PushCurrentLocation(string stackName) /// entry on the working directory stack and removes that entry /// from the stack. /// - /// /// /// The ID of the stack to pop the location from. If it is null or /// empty the default stack is used. /// - /// /// /// A PathInfo object representing the location that was popped /// from the location stack and set as the new location. /// - /// /// /// If the path on the stack does not exist, is not a container, or /// resolved to multiple containers. @@ -862,23 +877,19 @@ internal void PushCurrentLocation(string stackName) /// or /// A stack was not found with the specified name. /// - /// /// /// If the path on the stack refers to a provider that does not exist. /// - /// /// /// If the path on the stack refers to a drive that does not exist. /// - /// /// /// If the provider associated with the path on the stack threw an /// exception. /// - /// internal PathInfo PopLocation(string stackName) { - if (String.IsNullOrEmpty(stackName)) + if (string.IsNullOrEmpty(stackName)) { stackName = _defaultStackName; } @@ -886,7 +897,6 @@ internal PathInfo PopLocation(string stackName) if (WildcardPattern.ContainsWildcardCharacters(stackName)) { // Need to glob the stack name, but it can only glob to a single. - bool haveMatch = false; WildcardPattern stackNamePattern = @@ -900,10 +910,11 @@ internal PathInfo PopLocation(string stackName) { throw PSTraceSource.NewArgumentException( - "stackName", + nameof(stackName), SessionStateStrings.StackNameResolvedToMultiple, stackName); } + haveMatch = true; stackName = key; } @@ -921,10 +932,11 @@ internal PathInfo PopLocation(string stackName) { throw PSTraceSource.NewArgumentException( - "stackName", + nameof(stackName), SessionStateStrings.StackNotFound, stackName); } + return null; } @@ -943,11 +955,10 @@ internal PathInfo PopLocation(string stackName) result = SetLocation(newPath); if (locationStack.Count == 0 && - !String.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase)) + !string.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase)) { // Remove the stack from the stack list if it // no longer contains any paths. - _workingLocationStack.Remove(stackName); } } @@ -964,25 +975,21 @@ internal PathInfo PopLocation(string stackName) /// Gets the monad namespace paths for all the directories that are /// pushed on the working directory stack. /// - /// /// /// The stack of the ID of the location stack to retrieve. If it is /// null or empty the default stack is used. /// - /// /// /// The PathInfoStack representing the location stack for the specified /// stack ID. /// - /// /// /// If no location stack exists except if /// the default stack is requested. /// - /// internal PathInfoStack LocationStack(string stackName) { - if (String.IsNullOrEmpty(stackName)) + if (string.IsNullOrEmpty(stackName)) { stackName = _defaultStackName; } @@ -993,8 +1000,7 @@ internal PathInfoStack LocationStack(string stackName) { // If the request was for the default stack, but it doesn't // yet exist, create a dummy stack and return it. - - if (String.Equals( + if (string.Equals( stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase)) @@ -1003,42 +1009,38 @@ internal PathInfoStack LocationStack(string stackName) } else { - throw PSTraceSource.NewArgumentException("stackName"); + throw PSTraceSource.NewArgumentException(nameof(stackName)); } } PathInfoStack result = new PathInfoStack(stackName, locationStack); return result; - } // LocationStack + } /// - /// Sets the default stack ID to the specified stack ID + /// Sets the default stack ID to the specified stack ID. /// - /// /// /// The stack ID to be used as the default. /// - /// /// /// The PathInfoStack for the new default stack or null if the /// stack does not exist yet. /// - /// /// /// If does not exist as a location stack. /// - /// internal PathInfoStack SetDefaultLocationStack(string stackName) { - if (String.IsNullOrEmpty(stackName)) + if (string.IsNullOrEmpty(stackName)) { stackName = startingDefaultStackName; } if (!_workingLocationStack.ContainsKey(stackName)) { - if (String.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase)) { // Since the "default" stack must always exist, create it here return new PathInfoStack(startingDefaultStackName, new Stack()); @@ -1061,12 +1063,52 @@ internal PathInfoStack SetDefaultLocationStack(string stackName) { return new PathInfoStack(_defaultStackName, locationStack); } + return null; - } // SetDefaultLocationStack + } #endregion push-Pop current working directory - } // SessionStateInternal class -} + } + /// + /// Event argument for the LocationChangedAction containing + /// information about the old location we were in and the new + /// location we changed to. + /// + public class LocationChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the LocationChangedEventArgs class. + /// + /// + /// The public session state instance associated with this runspace. + /// + /// + /// The path we changed locations from. + /// + /// + /// The path we change locations to. + /// + internal LocationChangedEventArgs(SessionState sessionState, PathInfo oldPath, PathInfo newPath) + { + SessionState = sessionState; + OldPath = oldPath; + NewPath = newPath; + } + /// + /// Gets the path we changed location from. + /// + public PathInfo OldPath { get; internal set; } + /// + /// Gets the path we changed location to. + /// + public PathInfo NewPath { get; internal set; } + + /// + /// Gets the session state instance for the current runspace. + /// + 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 42a934ffd55..d2d27ec2d83 100644 --- a/src/System.Management.Automation/engine/SessionStateNavigation.cs +++ b/src/System.Management.Automation/engine/SessionStateNavigation.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -12,7 +12,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -21,42 +21,34 @@ internal sealed partial class SessionStateInternal #region GetParentPath /// - /// Gets the path to the parent object for the given object + /// Gets the path to the parent object for the given object. /// - /// /// /// The path to the object to get the parent path from /// - /// /// /// The root of the drive. /// - /// /// /// The path to the parent object /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetParentPath(string path, string root) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -66,45 +58,36 @@ internal string GetParentPath(string path, string root) context.ThrowFirstErrorOrDoNothing(); return result; - } //GetParentPath + } /// - /// Gets the path to the parent object for the given object + /// Gets the path to the parent object for the given object. /// - /// /// /// The path to the object to get the parent path from /// - /// /// /// The root of the drive. Namespace providers should /// return the root if GetParentPath is called for the root. /// - /// /// /// The context which the core command is running. /// - /// /// /// The path to the parent object /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetParentPath( string path, string root, @@ -113,30 +96,24 @@ internal string GetParentPath( return GetParentPath(path, root, context, false); } - /// /// Gets the path to the parent object for the given object. /// Allow to use FileSystem as the default provider when the /// given path is drive-qualified and the drive cannot be found. /// - /// /// /// The path to the object to get the parent path from /// - /// /// /// The root of the drive. Namespace providers should /// return the root if GetParentPath is called for the root. /// - /// /// /// The context which the core command is running. /// - /// /// /// Specify whether to use default provider when needed. /// - /// /// /// The path to the parent object /// @@ -148,7 +125,7 @@ internal string GetParentPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext getProviderPathContext = @@ -200,7 +177,7 @@ internal string GetParentPath( string result = GetParentPath(provider, pathNoQualifier, root, context); - if (!String.IsNullOrEmpty(qualifier) && !String.IsNullOrEmpty(result)) + if (!string.IsNullOrEmpty(qualifier) && !string.IsNullOrEmpty(result)) { result = AddQualifier(result, provider, qualifier, isProviderQualified, isDriveQualified); } @@ -211,9 +188,9 @@ internal string GetParentPath( { getProviderPathContext.RemoveStopReferral(); } - } // 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; @@ -248,31 +225,24 @@ private string AddQualifier(string path, ProviderInfo provider, string qualifier /// /// Removes either the drive or provider qualifier or both from the path. /// - /// /// /// The path to strip the provider qualifier from. /// - /// /// /// The provider that should handle the RemoveQualifier call. /// - /// /// /// Returns the qualifier of the path. /// - /// /// /// Returns true if the path is a provider-qualified path. /// - /// /// /// Returns true if the path is a drive-qualified path. /// - /// /// /// The path without the qualifier. /// - /// private string RemoveQualifier(string path, ProviderInfo provider, out string qualifier, out bool isProviderQualified, out bool isDriveQualified) { Dbg.Diagnostics.Assert( @@ -317,49 +287,39 @@ private string RemoveQualifier(string path, ProviderInfo provider, out string qu } return result; - } // RemoveQualifier + } /// - /// Gets the path to the parent object for the given object + /// Gets the path to the parent object for the given object. /// - /// /// /// The provider that should handle the GetParentPath call. /// - /// /// /// The path to the object to get the parent path from /// - /// /// /// The root of the drive. Namespace providers should /// return the root if GetParentPath is called for the root. /// - /// /// /// The context which the core command is running. /// - /// /// /// The path to the parent object /// - /// /// /// This is internal so that it can be called from the LocationGlobber. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetParentPath( ProviderInfo provider, string path, @@ -388,46 +348,36 @@ internal string GetParentPath( } /// - /// Gets the path to the parent object for the given object + /// Gets the path to the parent object for the given object. /// - /// /// /// The instance of the provider that should handle the GetParentPath call. /// - /// /// /// The path to the object to get the parent path from /// - /// /// /// The root of the drive. Namespace providers should /// return the root if GetParentPath is called for the root. /// - /// /// /// The context which the core command is running. /// - /// /// /// The path to the parent object /// - /// /// /// This is internal so that it can be called from the LocationGlobber. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetParentPath( CmdletProvider providerInstance, string path, @@ -451,7 +401,6 @@ internal string GetParentPath( context != null, "Caller should validate context before calling this method"); - NavigationCmdletProvider navigationCmdletProvider = GetNavigationProviderInstance(providerInstance, false); @@ -482,8 +431,9 @@ internal string GetParentPath( path, e); } + return result; - } // GetParentPath + } #endregion GetParentPath @@ -493,41 +443,33 @@ internal string GetParentPath( /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// An MSH path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// A normalized path that is relative to the basePath that was passed. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string NormalizeRelativePath(string path, string basePath) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -537,46 +479,37 @@ internal string NormalizeRelativePath(string path, string basePath) context.ThrowFirstErrorOrDoNothing(); return result; - } //NormalizeRelativePath + } /// /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// An MSH path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// The context under which the command is running. /// - /// /// /// A normalized path that is relative to the basePath that was passed. /// - /// /// /// If is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string NormalizeRelativePath( string path, string basePath, @@ -584,7 +517,7 @@ internal string NormalizeRelativePath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext getProviderPathContext = @@ -613,7 +546,7 @@ internal string NormalizeRelativePath( // Since the provider didn't write an error, and we didn't get any // results ourselves, we need to write out our own error. - Exception e = PSTraceSource.NewArgumentException("path"); + Exception e = PSTraceSource.NewArgumentException(nameof(path)); context.WriteError(new ErrorRecord(e, "NormalizePathNullResult", ErrorCategory.InvalidArgument, path)); return null; } @@ -657,7 +590,7 @@ internal string NormalizeRelativePath( // \\HKEY_LOCAL_MACHINE if ( (GetProviderInstance(provider) is NavigationCmdletProvider) && - (!String.IsNullOrEmpty(drive.Root)) && + (!string.IsNullOrEmpty(drive.Root)) && (path.StartsWith(drive.Root, StringComparison.OrdinalIgnoreCase))) { // @@ -678,17 +611,14 @@ internal string NormalizeRelativePath( // 1. Test for the drive root ending with a path separator. bool driveRootEndsWithPathSeparator = IsPathSeparator(drive.Root[drive.Root.Length - 1]); - // 2. Test for the path starting with the drive root followed by a path separator int indexAfterDriveRoot = drive.Root.Length; bool pathStartsWithDriveRootAndPathSeparator = indexAfterDriveRoot < path.Length && IsPathSeparator(path[indexAfterDriveRoot]); - // 3. Test for the drive root exactly matching the path. // Since we know the path starts with the drive root then they are equal if the lengths are equal. bool pathEqualsDriveRoot = drive.Root.Length == path.Length; - if (driveRootEndsWithPathSeparator || pathStartsWithDriveRootAndPathSeparator || pathEqualsDriveRoot) { workingPath = path; @@ -702,16 +632,15 @@ internal string NormalizeRelativePath( { getProviderPathContext.RemoveStopReferral(); } - } // NormalizeRelativePath - + } /// /// Tests the specified character for equality with one of the powershell path separators and /// returns true if it matches. /// - /// The character to test + /// 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; } @@ -720,39 +649,30 @@ private bool IsPathSeparator(char c) /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// The provider to use to normalize the path. /// - /// /// /// An provider internal path to normalize. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// The context under which the command is running. /// - /// /// /// A normalized path that is relative to the basePath that was passed. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string NormalizeRelativePath( ProviderInfo provider, string path, @@ -774,7 +694,6 @@ internal string NormalizeRelativePath( // Get an instance of the provider - Provider.CmdletProvider providerInstance = GetProviderInstance(provider); NavigationCmdletProvider navigationCmdletProvider = providerInstance as NavigationCmdletProvider; @@ -816,7 +735,7 @@ internal string NormalizeRelativePath( } return path; - } // NormalizeRelativePath + } #endregion NormalizeRelativePath @@ -825,39 +744,30 @@ internal string NormalizeRelativePath( /// /// Generates a path from the given parts. /// - /// /// /// The parent segment of the path to be joined with the child. /// - /// /// /// The child segment of the ath to be joined with the parent. /// - /// /// /// The generated path. /// - /// /// /// If is null. /// - /// /// /// If both and is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string MakePath( string parent, string child) @@ -865,48 +775,38 @@ internal string MakePath( CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); return MakePath(parent, child, context); - } // MakePath + } /// /// Generates a path from the given parts. /// - /// /// /// The parent segment of the path to be joined with the child. /// - /// /// /// The child segment of the ath to be joined with the parent. /// - /// /// /// The context which the core command is running. /// - /// /// /// The generated path. /// - /// /// /// If is null. /// - /// /// /// If both and is null. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string MakePath( string parent, string child, @@ -916,13 +816,13 @@ internal string MakePath( if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } if (parent == null && child == null) { - throw PSTraceSource.NewArgumentException("parent"); + throw PSTraceSource.NewArgumentException(nameof(parent)); } // Set the drive data for the context @@ -932,6 +832,7 @@ internal string MakePath( { provider = CurrentDrive.Provider; } + if (context.Drive == null) { bool isProviderQualified = LocationGlobber.IsProviderQualifiedPath(parent); @@ -947,12 +848,14 @@ internal string MakePath( { drive = provider.HiddenDrive; } + context.Drive = drive; } else { context.Drive = CurrentDrive; } + result = MakePath(provider, parent, child, context); if (isAbsolute) @@ -969,45 +872,37 @@ internal string MakePath( provider = context.Drive.Provider; result = MakePath(provider, parent, child, context); } + return result; - } // MakePath + } /// - /// Uses the specified provider to put the two parts of a path together + /// Uses the specified provider to put the two parts of a path together. /// - /// /// /// The provider to use. /// - /// /// /// The parent part of the path to join with the child. /// - /// /// /// The child part of the path to join with the parent. /// - /// /// /// The context under which the command is running. /// - /// /// /// The combined path. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string MakePath( ProviderInfo provider, string parent, @@ -1025,48 +920,38 @@ internal string MakePath( // Get an instance of the provider - Provider.CmdletProvider providerInstance = provider.CreateInstance(); return MakePath(providerInstance, parent, child, context); } /// - /// Uses the specified provider to put the two parts of a path together + /// Uses the specified provider to put the two parts of a path together. /// - /// /// /// The provider instance to use. /// - /// /// /// The parent part of the path to join with the child. /// - /// /// /// The child part of the path to join with the parent. /// - /// /// /// The context under which the command is running. /// - /// /// /// The combined path. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// internal string MakePath( CmdletProvider providerInstance, string parent, @@ -1126,7 +1011,7 @@ internal string MakePath( } return result; - } // MakePath + } #endregion MakePath @@ -1135,41 +1020,33 @@ internal string MakePath( /// /// Gets the name of the leaf element in the specified path. /// - /// /// /// The fully qualified path to the item /// - /// /// /// The leaf element in the path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetChildName(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1179,45 +1056,36 @@ internal string GetChildName(string path) context.ThrowFirstErrorOrDoNothing(); return result; - } // GetChildName + } /// /// Gets the name of the leaf element in the specified path. /// - /// /// /// The fully qualified path to the item /// - /// /// /// The context which the core command is running. /// - /// /// /// The leaf element in the path. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal string GetChildName( string path, CmdletProviderContext context) @@ -1230,19 +1098,15 @@ internal string GetChildName( /// Allow to use FileSystem as the default provider when the /// given path is drive-qualified and the drive cannot be found. /// - /// /// /// The fully qualified path to the item /// - /// /// /// The context which the core command is running. /// - /// /// /// to use default provider when needed. /// - /// /// /// The leaf element in the path. /// @@ -1253,7 +1117,7 @@ internal string GetChildName( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } PSDriveInfo drive = null; @@ -1297,36 +1161,29 @@ internal string GetChildName( } return GetChildName(provider, workingPath, context); - } // GetChildName + } /// /// Gets the leaf element of the specified path. /// - /// /// /// The provider to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private string GetChildName( ProviderInfo provider, string path, @@ -1345,7 +1202,6 @@ private string GetChildName( context != null, "Caller should validate context before calling this method"); - CmdletProvider providerInstance = provider.CreateInstance(); return GetChildName(providerInstance, path, context, true); @@ -1354,36 +1210,28 @@ private string GetChildName( /// /// Gets the leaf element of the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The context which the core command is running. /// - /// /// /// Specify True if the method should just return the Path if the /// provider doesn't support container overloads. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private string GetChildName( CmdletProvider providerInstance, string path, @@ -1437,8 +1285,9 @@ bool acceptNonContainerProviders path, e); } + return result; - } // GetChildName + } #endregion GetChildName @@ -1447,31 +1296,24 @@ bool acceptNonContainerProviders /// /// Moves the item specified by path to the specified destination. /// - /// /// /// The path(s) to the item(s) to be moved. /// - /// /// /// The path of the destination container. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// The item(s) that were moved. /// - /// /// /// If is null. /// - /// /// /// If resolves to multiple paths. /// or @@ -1481,29 +1323,24 @@ bool acceptNonContainerProviders /// If resolves to multiple paths and /// is not a container. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection MoveItem(string[] paths, string destination, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -1517,54 +1354,43 @@ internal Collection MoveItem(string[] paths, string destination, bool // Since there was no errors return the accumulated objects return context.GetAccumulatedObjects(); - } // MoveItem + } /// /// Moves the item specified by path to the specified destination. /// - /// /// /// The path(s) to the item(s) to be moved. /// - /// /// /// The path of the destination container. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. All items that are moved are written into the context object. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void MoveItem( string[] paths, string destination, @@ -1572,12 +1398,12 @@ internal void MoveItem( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (destination == null) { - throw PSTraceSource.NewArgumentNullException("destination"); + throw PSTraceSource.NewArgumentNullException(nameof(destination)); } ProviderInfo provider = null; @@ -1594,7 +1420,7 @@ internal void MoveItem( { ArgumentException argException = PSTraceSource.NewArgumentException( - "destination", + nameof(destination), SessionStateStrings.MoveItemOneDestination); context.WriteError(new ErrorRecord(argException, argException.GetType().FullName, ErrorCategory.InvalidArgument, destination)); @@ -1605,7 +1431,7 @@ internal void MoveItem( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } Collection providerPaths = @@ -1632,7 +1458,6 @@ internal void MoveItem( } else { - PSDriveInfo unusedPSDriveInfo = null; ProviderInfo destinationProvider = null; CmdletProviderContext destinationContext = new CmdletProviderContext(this.ExecutionContext); @@ -1646,7 +1471,7 @@ internal void MoveItem( providerDestinationPaths[0].Path, destinationContext, out destinationProvider, - out unusedPSDriveInfo); + out _); } else { @@ -1658,19 +1483,19 @@ internal void MoveItem( destination, destinationContext, out destinationProvider, - out unusedPSDriveInfo); + out _); } // Now verify the providers are the same. - if (!String.Equals( + if (!string.Equals( provider.FullName, destinationProvider.FullName, StringComparison.OrdinalIgnoreCase)) { ArgumentException argException = PSTraceSource.NewArgumentException( - "destination", + nameof(destination), SessionStateStrings.MoveItemSourceAndDestinationNotSameProvider); context.WriteError(new ErrorRecord(argException, argException.GetType().FullName, ErrorCategory.InvalidArgument, providerPaths)); @@ -1685,40 +1510,32 @@ internal void MoveItem( } } } - } // MoveItem + } /// - /// Moves the item at the specified path to the destination path + /// Moves the item at the specified path to the destination path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to where the item should be moved. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void MoveItemPrivate( CmdletProvider providerInstance, string path, @@ -1738,7 +1555,6 @@ private void MoveItemPrivate( context != null, "Caller should validate context before calling this method"); - NavigationCmdletProvider navigationCmdletProvider = GetNavigationProviderInstance(providerInstance, false); @@ -1767,51 +1583,41 @@ private void MoveItemPrivate( path, e); } - } // MoveItem + } /// /// Gets the dynamic parameters for the move-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to move the item to. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object MoveItemDynamicParameters( string path, string destination, @@ -1846,46 +1652,38 @@ internal object MoveItemDynamicParameters( return MoveItemDynamicParameters(providerInstance, providerPaths[0], destination, newContext); } + return null; - } // MoveItemDynamicParameters + } /// /// Gets the dynamic parameters for the move-item cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The path to where the item should be moved. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object MoveItemDynamicParameters( CmdletProvider providerInstance, string path, @@ -1905,7 +1703,6 @@ private object MoveItemDynamicParameters( context != null, "Caller should validate context before calling this method"); - NavigationCmdletProvider navigationCmdletProvider = GetNavigationProviderInstance(providerInstance, false); @@ -1940,14 +1737,14 @@ private object MoveItemDynamicParameters( path, e); } + return result; - } // MoveItemDynamicParameters + } #endregion MoveItem #endregion NavigationCmdletProvider accessors - } // SessionStateInternal class + } } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProperty.cs b/src/System.Management.Automation/engine/SessionStateProperty.cs index 385385018bb..9f621fa2f7a 100644 --- a/src/System.Management.Automation/engine/SessionStateProperty.cs +++ b/src/System.Management.Automation/engine/SessionStateProperty.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -12,7 +12,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -23,44 +23,34 @@ internal sealed partial class SessionStateInternal /// /// Gets the specified properties from the specified item. /// - /// /// /// The path(s) to the item(s) to get the properties from. /// - /// /// /// A list of the properties that the provider should return. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// A property table container the properties and their values. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection GetProperty( string[] paths, Collection providerSpecificPickList, @@ -81,55 +71,44 @@ internal Collection GetProperty( Collection results = context.GetAccumulatedObjects(); return results; - } // GetProperties + } /// /// Gets the specified properties from the specified item. /// - /// /// /// The path(s) to the item(s) to get the properties from. /// - /// /// /// A list of the properties that the provider should return. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. A PSObject representing the properties should be written to the /// context. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void GetProperty( string[] paths, Collection providerSpecificPickList, @@ -137,14 +116,14 @@ internal void GetProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -167,40 +146,32 @@ internal void GetProperty( context); } } - } // GetProperty + } /// /// Gets the property from the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The names of the properties to get. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void GetPropertyPrivate( CmdletProvider providerInstance, string path, @@ -249,51 +220,41 @@ private void GetPropertyPrivate( path, e); } - } // GetPropertyPrivate + } /// /// Gets the dynamic parameters for the get-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// A list of the properties that the provider should return. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object GetPropertyDynamicParameters( string path, Collection providerSpecificPickList, @@ -328,46 +289,38 @@ internal object GetPropertyDynamicParameters( return GetPropertyDynamicParameters(providerInstance, providerPaths[0], providerSpecificPickList, newContext); } + return null; - } // GetPropertyDynamicParameters + } /// /// Gets the dynamic parameters for the get-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The names of the properties to get. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object GetPropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -417,8 +370,9 @@ private object GetPropertyDynamicParameters( path, e); } + return result; - } // GetPropertyDynamicParameters + } #endregion GetProperty @@ -427,53 +381,42 @@ private object GetPropertyDynamicParameters( /// /// Sets the specified properties on the specified item. /// - /// /// /// The path(s) to the item(s) to set the properties on. /// - /// /// /// A PSObject containing the properties to be changed. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// An array of PSObjects representing the properties that were set on each item. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal Collection SetProperty(string[] paths, PSObject property, bool force, bool literalPath) { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) @@ -492,55 +435,44 @@ internal Collection SetProperty(string[] paths, PSObject property, boo Collection results = context.GetAccumulatedObjects(); return results; - } // SetProperty + } /// /// Sets the specified properties on specified item. /// - /// /// /// The path(s) to the item(s) to set the properties on. /// - /// /// /// A property table containing the properties and values to be set on the object. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. A PSObject is passed to the context for the properties on each item /// that were modified. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void SetProperty( string[] paths, PSObject property, @@ -548,19 +480,19 @@ internal void SetProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (property == null) { - throw PSTraceSource.NewArgumentNullException("property"); + throw PSTraceSource.NewArgumentNullException(nameof(property)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -582,40 +514,32 @@ internal void SetProperty( } } } - } // SetProperty + } /// /// Sets the property of the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to set. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void SetPropertyPrivate( CmdletProvider providerInstance, string path, @@ -668,51 +592,41 @@ private void SetPropertyPrivate( path, e); } - } // SetPropertyPrivate + } /// /// Gets the dynamic parameters for the clear-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// A property table containing the properties and values to be set on the object. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object SetPropertyDynamicParameters( string path, PSObject propertyValue, @@ -747,46 +661,38 @@ internal object SetPropertyDynamicParameters( return SetPropertyDynamicParameters(providerInstance, providerPaths[0], propertyValue, newContext); } + return null; - } // SetPropertyDynamicParameters + } /// /// Gets the dynamic parameters for the set-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The value of the property to set. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object SetPropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -806,7 +712,6 @@ private object SetPropertyDynamicParameters( context != null, "Caller should validate context before calling this method"); - object result = null; try { @@ -837,8 +742,9 @@ private object SetPropertyDynamicParameters( path, e); } + return result; - } // SetPropertyDynamicParameters + } #endregion SetProperty @@ -847,44 +753,34 @@ private object SetPropertyDynamicParameters( /// /// Clears the specified property on the specified item. /// - /// /// /// The path(s) to the item(s) to clear the property on. /// - /// /// /// The name of the property to clear. /// - /// /// /// Passed on to providers to force operations. /// - /// /// /// If true, globbing is not done on paths. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void ClearProperty( string[] paths, Collection propertyToClear, @@ -893,12 +789,12 @@ internal void ClearProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (propertyToClear == null) { - throw PSTraceSource.NewArgumentNullException("propertyToClear"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyToClear)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -908,50 +804,40 @@ internal void ClearProperty( ClearProperty(paths, propertyToClear, context); context.ThrowFirstErrorOrDoNothing(); - } // ClearProperty + } /// /// Clears the specified property in the specified item. /// - /// /// /// The path(s) to the item(s) to clear the property on. /// - /// /// /// A property table containing the property to clear. /// - /// /// /// The context which the core command is running. /// - /// /// /// If or is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void ClearProperty( string[] paths, Collection propertyToClear, @@ -959,19 +845,19 @@ internal void ClearProperty( { if (paths == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } if (propertyToClear == null) { - throw PSTraceSource.NewArgumentNullException("propertyToClear"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyToClear)); } foreach (string path in paths) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("paths"); + throw PSTraceSource.NewArgumentNullException(nameof(paths)); } ProviderInfo provider = null; @@ -990,40 +876,32 @@ internal void ClearProperty( ClearPropertyPrivate(providerInstance, providerPath, propertyToClear, context); } } - } // ClearProperty + } /// /// Clears the value of the property from the item at the specified path. /// - /// /// /// The provider instance to use. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to clear. /// - /// /// /// The context which the core command is running. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private void ClearPropertyPrivate( CmdletProvider providerInstance, string path, @@ -1076,51 +954,41 @@ private void ClearPropertyPrivate( path, e); } - } // ClearPropertyPrivate + } /// /// Gets the dynamic parameters for the clear-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// A property table containing the property to clear. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal object ClearPropertyDynamicParameters( string path, Collection propertyToClear, @@ -1155,46 +1023,38 @@ internal object ClearPropertyDynamicParameters( return ClearPropertyDynamicParameters(providerInstance, providerPaths[0], propertyToClear, newContext); } + return null; - } // ClearPropertyDynamicParameters + } /// /// Gets the dynamic parameters for the clear-itemproperty cmdlet. /// - /// /// /// The path to the item if it was specified on the command line. /// - /// /// /// The name of the property to clear. /// - /// /// /// The instance of the provider to use. /// - /// /// /// The context which the core command is running. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// /// /// If the does not support this operation. /// - /// /// /// If the pipeline is being stopped while executing the command. /// - /// /// /// If the provider threw an exception. /// - /// private object ClearPropertyDynamicParameters( CmdletProvider providerInstance, string path, @@ -1245,14 +1105,14 @@ private object ClearPropertyDynamicParameters( path, e); } + return result; - } // ClearPropertyDynamicParameters + } #endregion ClearProperty #endregion IPropertyCmdletProvider accessors - } // SessionStateInternal class + } } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs index 62012a4db0e..3ad2ed99e31 100644 --- a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; using System.Text; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -15,7 +15,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a PowerShell session + /// Holds the state of a PowerShell session. /// internal sealed partial class SessionStateInternal { @@ -32,6 +32,7 @@ internal Dictionary> Providers return ExecutionContext.TopLevelSessionState.Providers; } } + private Dictionary> _providers = new Dictionary>( SessionStateConstants.DefaultDictionaryCapacity, StringComparer.OrdinalIgnoreCase); @@ -50,7 +51,8 @@ internal Dictionary ProvidersCurrentWorkingDrive return ExecutionContext.TopLevelSessionState.ProvidersCurrentWorkingDrive; } } - private Dictionary _providersCurrentWorkingDrive = new Dictionary(); + + private readonly Dictionary _providersCurrentWorkingDrive = new Dictionary(); /// /// Entrypoint used by to add a provider to the current session state @@ -59,12 +61,11 @@ internal Dictionary ProvidersCurrentWorkingDrive /// internal void AddSessionStateEntry(SessionStateProviderEntry providerEntry) { - ProviderInfo provider = AddProvider(providerEntry.ImplementingType, - providerEntry.Name, - providerEntry.HelpFileName, - providerEntry.PSSnapIn, - providerEntry.Module - ); + AddProvider(providerEntry.ImplementingType, + providerEntry.Name, + providerEntry.HelpFileName, + providerEntry.PSSnapIn, + providerEntry.Module); } private ProviderInfo AddProvider(Type implementingType, string name, string helpFileName, PSSnapInInfo psSnapIn, PSModuleInfo module) @@ -120,44 +121,36 @@ private ProviderInfo AddProvider(Type implementingType, string name, string help // NTRAID#Windows OS Bugs-1009281-2004/02/11-JeffJon this.ExecutionContext.ReportEngineStartupError(e); } + return provider; } - /// /// Determines the appropriate provider for the drive and then calls the NewDrive /// method of that provider. /// - /// /// /// The drive to have the provider verify. /// - /// /// /// The command context under which the drive is being added. /// - /// /// /// If true, the drive root will be resolved as an MSH path before verifying with /// the provider. If false, the path is assumed to be a provider-internal path. /// - /// /// /// The instance of the drive to be added as approved by the provider. /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// The provider for the could not be found. /// - /// /// /// If the provider throws an exception while validating the drive. /// - /// private PSDriveInfo ValidateDriveWithProvider(PSDriveInfo drive, CmdletProviderContext context, bool resolvePathIfPossible) { Dbg.Diagnostics.Assert( @@ -237,84 +230,72 @@ private PSDriveInfo ValidateDriveWithProvider( { drive.DriveBeingCreated = false; } + return result; - } // ValidateDriveWithProvider + } /// /// Gets an instance of a provider given the provider ID. /// - /// /// /// The identifier for the provider to return an instance of. /// - /// /// /// An instance of the specified provider. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that doesn't exist or /// the name passed matched multiple providers. /// - /// internal Provider.CmdletProvider GetProviderInstance(string providerId) { if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("providerId"); + throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } ProviderInfo provider = GetSingleProvider(providerId); return GetProviderInstance(provider); - } // GetProviderInstance + } /// /// Gets an instance of a provider given the provider information. /// - /// /// /// The provider to return an instance of. /// - /// /// /// An instance of the specified provider. /// - /// /// /// If is null. /// - /// internal Provider.CmdletProvider GetProviderInstance(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } return provider.CreateInstance(); - } // GetProviderInstance + } /// /// Creates an exception for the case where the provider name matched multiple providers. /// - /// /// /// The name of the provider. /// - /// /// /// The ProviderInfo of the possible matches. /// - /// /// /// An exception representing the error with a message stating which providers are possible matches. /// - /// internal static ProviderNameAmbiguousException NewAmbiguousProviderName(string name, Collection matchingProviders) { string possibleMatches = GetPossibleMatches(matchingProviders); @@ -336,7 +317,7 @@ private static string GetPossibleMatches(Collection matchingProvid foreach (ProviderInfo matchingProvider in matchingProviders) { - possibleMatches.Append(" "); + possibleMatches.Append(' '); possibleMatches.Append(matchingProvider.FullName); } @@ -346,446 +327,357 @@ private static string GetPossibleMatches(Collection matchingProvid /// /// Gets an instance of an DriveCmdletProvider given the provider ID. /// - /// /// /// The provider ID of the provider to get an instance of. /// - /// /// /// An instance of a DriveCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// /// /// If the refers to a provider that doesn't exist. /// - /// internal DriveCmdletProvider GetDriveProviderInstance(string providerId) { if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("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); } return driveCmdletProvider; - } // GetDriveProviderInstance + } /// /// Gets an instance of an DriveCmdletProvider given the provider information. /// - /// /// /// The provider to get an instance of. /// - /// /// /// An instance of a DriveCmdletProvider for the specified provider. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// internal DriveCmdletProvider GetDriveProviderInstance(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("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); } return driveCmdletProvider; - } // GetDriveProviderInstance + } /// /// Gets an instance of an DriveCmdletProvider given the provider ID. /// - /// /// /// The instance of the provider to use. /// - /// /// /// An instance of a DriveCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from DriveCmdletProvider. /// - /// private static DriveCmdletProvider GetDriveProviderInstance(CmdletProvider providerInstance) { if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + 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); } return driveCmdletProvider; - } // GetDriveProviderInstance + } /// /// Gets an instance of an ItemCmdletProvider given the provider ID. /// - /// /// /// The provider ID of the provider to get an instance of. /// - /// /// /// An instance of a ItemCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// /// /// If the refers to a provider that doesn't exist. /// - /// internal ItemCmdletProvider GetItemProviderInstance(string providerId) { if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("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); } return itemCmdletProvider; - } // GetItemProviderInstance + } /// /// Gets an instance of an ItemCmdletProvider given the provider. /// - /// /// /// The provider to get an instance of. /// - /// /// /// An instance of a ItemCmdletProvider for the specified provider. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// internal ItemCmdletProvider GetItemProviderInstance(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("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); } return itemCmdletProvider; - } // GetItemProviderInstance + } /// /// Gets an instance of an ItemCmdletProvider given the provider ID. /// - /// /// /// The instance of the provider to use. /// - /// /// /// An instance of a ItemCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from ItemCmdletProvider. /// - /// private static ItemCmdletProvider GetItemProviderInstance(CmdletProvider providerInstance) { if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + 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); } return itemCmdletProvider; - } // GetItemProviderInstance + } /// /// Gets an instance of an ContainerCmdletProvider given the provider ID. /// - /// /// /// The provider ID of the provider to get an instance of. /// - /// /// /// An instance of a ContainerCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// /// /// If the refers to a provider that doesn't exist. /// - /// internal ContainerCmdletProvider GetContainerProviderInstance(string providerId) { if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("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); } return containerCmdletProvider; - } // GetContainerProviderInstance + } /// /// Gets an instance of an ContainerCmdletProvider given the provider. /// - /// /// /// The provider to get an instance of. /// - /// /// /// An instance of a ContainerCmdletProvider for the specified provider. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// internal ContainerCmdletProvider GetContainerProviderInstance(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + 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); } return containerCmdletProvider; - } // GetContainerProviderInstance + } /// /// Gets an instance of an ContainerCmdletProvider given the provider ID. /// - /// /// /// The instance of the provider to use. /// - /// /// /// An instance of a ContainerCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from ContainerCmdletProvider. /// - /// private static ContainerCmdletProvider GetContainerProviderInstance(CmdletProvider providerInstance) { if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + 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); } return containerCmdletProvider; - } // GetContainerProviderInstance + } /// /// Gets an instance of an NavigationCmdletProvider given the provider. /// - /// /// /// The provider to get an instance of. /// - /// /// /// An instance of a NavigationCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// internal NavigationCmdletProvider GetNavigationProviderInstance(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + 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); } return navigationCmdletProvider; - } // GetNavigationProviderInstance + } /// /// Gets an instance of an NavigationCmdletProvider given the provider ID. /// - /// /// /// The instance of the provider to use. /// - /// /// /// Specify True if the method should just return the Path if the /// provider doesn't support container overloads. /// - /// /// /// An instance of a NavigationCmdletProvider for the specified provider ID. /// - /// /// /// if is null. /// - /// /// /// if the is not for a provider /// that is derived from NavigationCmdletProvider. /// - /// private static NavigationCmdletProvider GetNavigationProviderInstance(CmdletProvider providerInstance, bool acceptNonContainerProviders) { if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } NavigationCmdletProvider navigationCmdletProvider = @@ -798,33 +690,29 @@ private static NavigationCmdletProvider GetNavigationProviderInstance(CmdletProv } return navigationCmdletProvider; - } // GetNavigationProviderInstance + } #region GetProvider /// /// Determines if the specified CmdletProvider is loaded. /// - /// /// /// The name of the CmdletProvider. /// - /// /// /// true if the CmdletProvider is loaded, or false otherwise. /// - /// /// /// If is null or empty. /// - /// internal bool IsProviderLoaded(string name) { bool result = false; - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } // Get the provider from the providers container @@ -840,34 +728,29 @@ internal bool IsProviderLoaded(string name) } return result; - } // IsProviderLoaded + } /// - /// Gets the provider of the specified name + /// Gets the provider of the specified name. /// - /// /// /// The name of the provider to retrieve /// - /// /// /// The provider of the given name /// - /// /// /// If is null or empty. /// - /// /// /// The provider with the specified /// could not be found. /// - /// internal Collection GetProvider(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } PSSnapinQualifiedName providerName = PSSnapinQualifiedName.GetInstance(name); @@ -883,32 +766,28 @@ internal Collection GetProvider(string name) throw e; } + return GetProvider(providerName); } /// - /// Gets the provider of the specified name + /// Gets the provider of the specified name. /// - /// /// /// The name of the provider to retrieve /// - /// /// /// The provider of the given name /// - /// /// /// If is null or empty. /// - /// /// /// The provider with the specified /// could not be found or the name was ambiguous. /// If the name is ambiguous then the PSSnapin qualified name must /// be specified. /// - /// internal ProviderInfo GetSingleProvider(string name) { Collection matchingProviders = GetProvider(name); @@ -931,6 +810,7 @@ internal ProviderInfo GetSingleProvider(string name) throw NewAmbiguousProviderName(name, matchingProviders); } } + return matchingProviders[0]; } @@ -972,17 +852,17 @@ internal Collection GetProvider(PSSnapinQualifiedName providerName } } - if (!String.IsNullOrEmpty(providerName.PSSnapInName)) + if (!string.IsNullOrEmpty(providerName.PSSnapInName)) { // Be sure the PSSnapin/Module name matches foreach (ProviderInfo provider in matchingProviders) { - if (String.Equals( + if (string.Equals( provider.PSSnapInName, providerName.PSSnapInName, StringComparison.OrdinalIgnoreCase) || - String.Equals( + string.Equals( provider.ModuleName, providerName.PSSnapInName, StringComparison.OrdinalIgnoreCase)) @@ -998,13 +878,13 @@ internal Collection GetProvider(PSSnapinQualifiedName providerName result.Add(provider); } } + return result; - } // GetProvider + } /// - /// Gets all the CoreCommandProviders + /// Gets all the CoreCommandProviders. /// - /// internal IEnumerable ProviderList { get @@ -1018,14 +898,15 @@ internal IEnumerable ProviderList result.Add(provider); } } + return result; - } // get - } // Providers + } + } /// /// Copy the Providers from another session state instance... /// - /// the session state instance to copy from... + /// The session state instance to copy from... internal void CopyProviders(SessionStateInternal ss) { if (ss == null || ss.Providers == null) @@ -1049,33 +930,26 @@ internal void CopyProviders(SessionStateInternal ss) /// provider, calling its start method followed by the InitializeDefaultDrives method. The /// Drives that are returned from the InitializeDefaultDrives method are then mounted. /// - /// /// /// An instance of the provider to use for the initialization. /// - /// /// /// The provider to be initialized. /// - /// /// /// The context under which the initialization is occurring. If this parameter is not /// null, errors will be written to the WriteError method of the context. /// - /// /// /// If or is null. /// - /// /// /// If the provider is not a DriveCmdletProvider. /// - /// /// /// If a drive already exists for the name of one of the drives the /// provider tries to add. /// - /// internal void InitializeProvider( Provider.CmdletProvider providerInstance, ProviderInfo provider, @@ -1083,13 +957,10 @@ internal void InitializeProvider( { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + 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. @@ -1128,7 +999,7 @@ internal void InitializeProvider( "InitializeDefaultDrivesException", SessionStateStrings.InitializeDefaultDrivesException, provider, - String.Empty, + string.Empty, e); context.WriteError( @@ -1174,40 +1045,34 @@ internal void InitializeProvider( { context.WriteError(exception.ErrorRecord); } - } // foreach (drive in newDrives) + } } - } // InitializeProvider + } /// - /// Creates and adds a provider to the provider container + /// Creates and adds a provider to the provider container. /// - /// /// /// The provider to add. /// - /// /// /// The provider that was added or null if the provider failed to be added. /// - /// /// /// If is null. /// - /// /// /// If the provider already exists. /// - /// /// /// If there was a failure to load the provider or the provider /// threw an exception. /// - /// internal ProviderInfo NewProvider(ProviderInfo provider) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } // Check to see if the provider already exists. @@ -1232,7 +1097,6 @@ internal ProviderInfo NewProvider(ProviderInfo provider) throw sessionStateException; } - // Make sure we are able to create an instance of the provider. // Note, this will also set the friendly name if the user didn't // specify one. @@ -1372,7 +1236,7 @@ internal ProviderInfo NewProvider(ProviderInfo provider) // An exception during initialization should remove the provider from // session state. - Providers.Remove(provider.Name.ToString()); + Providers.Remove(provider.Name); ProvidersCurrentWorkingDrive.Remove(provider); provider = null; } @@ -1381,7 +1245,7 @@ internal ProviderInfo NewProvider(ProviderInfo provider) // Now write out the result return provider; - } // NewProvider + } private ProviderInfo ProviderExists(ProviderInfo provider) { @@ -1397,21 +1261,19 @@ private ProviderInfo ProviderExists(ProviderInfo provider) } } } + return null; } /// /// Creates an entry in the providers hashtable for the new provider. /// - /// /// /// The provider being added. /// - /// /// /// If a provider with the same name and PSSnapIn name already exists. /// - /// private void NewProviderEntry(ProviderInfo provider) { bool isDuplicateProvider = false; @@ -1429,14 +1291,14 @@ private void NewProviderEntry(ProviderInfo provider) foreach (ProviderInfo existingProvider in existingProviders) { - //making sure that we are not trying to add the same provider by checking the provider name & type of the new and existing providers. + // making sure that we are not trying to add the same provider by checking the provider name & type of the new and existing providers. if (string.IsNullOrEmpty(provider.PSSnapInName) && (string.Equals(existingProvider.Name, provider.Name, StringComparison.OrdinalIgnoreCase) && (existingProvider.GetType().Equals(provider.GetType())))) { isDuplicateProvider = true; } - //making sure that we are not trying to add the same provider by checking the PSSnapinName of the new and existing providers. + // making sure that we are not trying to add the same provider by checking the PSSnapinName of the new and existing providers. else if (string.Equals(existingProvider.PSSnapInName, provider.PSSnapInName, StringComparison.OrdinalIgnoreCase)) { isDuplicateProvider = true; @@ -1457,52 +1319,41 @@ private void NewProviderEntry(ProviderInfo provider) /// /// Removes the provider of the given name. /// - /// /// /// The name of the provider to remove. /// - /// /// /// Determines if the provider should be removed forcefully even if there were /// drives present or errors. /// - /// /// /// The context under which the command is being run. /// - /// /// /// If is null. /// - /// /// /// There are still drives associated with this provider, /// and the "force" option was not specified. /// - /// /// /// A provider with name could not be found. /// - /// /// /// If a provider throws an exception it gets written to the . /// - /// /// /// If is null or empty. /// - /// /// /// If is null. /// - /// /// /// All drives associated with the provider must be removed before the provider /// can be removed. Call SessionState.GetDrivesForProvider() to determine if there /// are any drives associated with the provider. A SessionStateException /// will be written to the context if any such drives do exist. /// - /// internal void RemoveProvider( string providerName, bool force, @@ -1510,12 +1361,12 @@ internal void RemoveProvider( { if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } - if (String.IsNullOrEmpty(providerName)) + if (string.IsNullOrEmpty(providerName)) { - throw PSTraceSource.NewArgumentException("providerName"); + throw PSTraceSource.NewArgumentException(nameof(providerName)); } bool errors = false; @@ -1652,22 +1503,19 @@ internal void RemoveProvider( ProvidersCurrentWorkingDrive.Remove(provider); } } - } // RemoveProvider + } /// /// Removes the provider from the providers dictionary. /// - /// /// /// The provider to be removed. /// - /// /// /// If there are multiple providers with the same name, then only the provider /// from the matching PSSnapin is removed. /// If the last provider of that name is removed the entry is removed from the dictionary. /// - /// private void RemoveProviderFromCollection(ProviderInfo provider) { List matchingProviders; @@ -1687,9 +1535,8 @@ private void RemoveProviderFromCollection(ProviderInfo provider) #endregion RemoveProvider /// - /// Gets the count of the number of providers that are loaded + /// Gets the count of the number of providers that are loaded. /// - /// internal int ProviderCount { get @@ -1699,10 +1546,11 @@ internal int ProviderCount { count += matchingProviders.Count; } + return count; } - } // ProviderCount - } // SessionStateInternal class + } + } } #pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/SessionStatePublic.cs b/src/System.Management.Automation/engine/SessionStatePublic.cs index ad8d3d83d9e..ce14d4948dc 100644 --- a/src/System.Management.Automation/engine/SessionStatePublic.cs +++ b/src/System.Management.Automation/engine/SessionStatePublic.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -18,29 +18,25 @@ public sealed class SessionState /// /// The internal constructor for this object. It should be the only one that gets called. /// - /// /// /// An instance of SessionState that the APIs should work against. /// - /// /// /// If is null. /// - /// internal SessionState(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // SessionState + } /// /// The internal constructor for this object. It should be the only one that gets called. /// - /// /// /// An instance of ExecutionContext whose EngineSessionState represents the parent session state. /// @@ -50,7 +46,6 @@ internal SessionState(SessionStateInternal sessionState) /// /// True if the session state should be linked to the global scope. /// - /// /// /// If is null. /// @@ -69,8 +64,7 @@ internal SessionState(ExecutionContext context, bool createAsChild, bool linkToG } _sessionState.PublicSessionState = this; - } // SessionState - + } /// /// Construct a new session state object... @@ -90,27 +84,27 @@ public SessionState() #region Public methods /// - /// Gets the APIs to access drives + /// Gets the APIs to access drives. /// public DriveManagementIntrinsics Drive { - get { return _drive ?? (_drive = new DriveManagementIntrinsics(_sessionState)); } + get { return _drive ??= new DriveManagementIntrinsics(_sessionState); } } /// - /// Gets the APIs to access providers + /// Gets the APIs to access providers. /// public CmdletProviderManagementIntrinsics Provider { - get { return _provider ?? (_provider = new CmdletProviderManagementIntrinsics(_sessionState)); } // get + get { return _provider ??= new CmdletProviderManagementIntrinsics(_sessionState); } } /// - /// Gets the APIs to access paths and location + /// Gets the APIs to access paths and location. /// public PathIntrinsics Path { - get { return _path ?? (_path = new PathIntrinsics(_sessionState)); } + get { return _path ??= new PathIntrinsics(_sessionState); } } /// @@ -118,20 +112,21 @@ public PathIntrinsics Path /// public PSVariableIntrinsics PSVariable { - get { return _variable ?? (_variable = new PSVariableIntrinsics(_sessionState)); } // get + get { return _variable ??= new PSVariableIntrinsics(_sessionState); } } /// - /// Get/set constraints for this execution environment + /// Get/set constraints for this execution environment. /// public PSLanguageMode LanguageMode { get { return _sessionState.LanguageMode; } + set { _sessionState.LanguageMode = value; } } /// - /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode + /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode. /// public bool UseFullLanguageModeInDebugger { @@ -186,7 +181,7 @@ public CommandInvocationIntrinsics InvokeCommand /// then the check will be made. If the check fails, then an exception will be thrown... /// /// The command origin value to check against... - /// The object to check + /// The object to check. public static void ThrowIfNotVisible(CommandOrigin origin, object valueToCheck) { SessionStateException exception; @@ -205,6 +200,7 @@ public static void ThrowIfNotVisible(CommandOrigin origin, object valueToCheck) throw exception; } + CommandInfo cinfo = valueToCheck as CommandInfo; if (cinfo != null) { @@ -224,7 +220,7 @@ public static void ThrowIfNotVisible(CommandOrigin origin, object valueToCheck) { exception = new SessionStateException( - "", + string.Empty, SessionStateCategory.Command, "CommandIsPrivate", SessionStateStrings.CommandIsPrivate, @@ -250,9 +246,9 @@ public static void ThrowIfNotVisible(CommandOrigin origin, object valueToCheck) /// /// Checks the visibility of an object based on the command origin argument. /// - /// The origin to check against - /// The object to check - /// Returns true if the object is visible, false otherwise + /// The origin to check against. + /// The object to check. + /// Returns true if the object is visible, false otherwise. public static bool IsVisible(CommandOrigin origin, object valueToCheck) { if (origin == CommandOrigin.Internal) @@ -262,38 +258,41 @@ public static bool IsVisible(CommandOrigin origin, object valueToCheck) { return (obj.Visibility == SessionStateEntryVisibility.Public); } + return true; } /// /// Checks the visibility of an object based on the command origin argument. /// - /// The origin to check against - /// The variable to check - /// Returns true if the object is visible, false otherwise + /// The origin to check against. + /// The variable to check. + /// Returns true if the object is visible, false otherwise. public static bool IsVisible(CommandOrigin origin, PSVariable variable) { if (origin == CommandOrigin.Internal) return true; if (variable == null) { - throw PSTraceSource.NewArgumentNullException("variable"); + throw PSTraceSource.NewArgumentNullException(nameof(variable)); } + return (variable.Visibility == SessionStateEntryVisibility.Public); } /// /// Checks the visibility of an object based on the command origin argument. /// - /// The origin to check against - /// The command to check - /// Returns true if the object is visible, false otherwise + /// The origin to check against. + /// The command to check. + /// Returns true if the object is visible, false otherwise. public static bool IsVisible(CommandOrigin origin, CommandInfo commandInfo) { if (origin == CommandOrigin.Internal) return true; if (commandInfo == null) { - throw PSTraceSource.NewArgumentNullException("commandInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(commandInfo)); } + return (commandInfo.Visibility == SessionStateEntryVisibility.Public); } @@ -302,25 +301,24 @@ public static bool IsVisible(CommandOrigin origin, CommandInfo commandInfo) #region Internal methods /// - /// Gets a reference to the "real" session state object instead of the facade + /// Gets a reference to the "real" session state object instead of the facade. /// - /// internal SessionStateInternal Internal { get { return _sessionState; } - } // Internal + } #endregion Internal methods #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; private DriveManagementIntrinsics _drive; private CmdletProviderManagementIntrinsics _provider; private PathIntrinsics _path; private PSVariableIntrinsics _variable; #endregion private data - } // SessionStatePublic + } /// /// This enum defines the visibility of execution environment elements... @@ -328,16 +326,17 @@ internal SessionStateInternal Internal public enum SessionStateEntryVisibility { /// - /// Entries are visible to requests from outside the runspace + /// Entries are visible to requests from outside the runspace. /// Public = 0, /// - /// Entries are not visible to requests from outside the runspace + /// Entries are not visible to requests from outside the runspace. /// Private = 1 } +#nullable enable internal interface IHasSessionStateEntryVisibility { SessionStateEntryVisibility Visibility { get; set; } @@ -350,12 +349,12 @@ internal interface IHasSessionStateEntryVisibility public enum PSLanguageMode { /// - /// All PowerShell language elements are available + /// All PowerShell language elements are available. /// FullLanguage = 0, /// - /// A subset of language elements are available to external requests + /// A subset of language elements are available to external requests. /// RestrictedLanguage = 1, @@ -373,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 7e24039a168..03ab9bdfb4b 100644 --- a/src/System.Management.Automation/engine/SessionStateScope.cs +++ b/src/System.Management.Automation/engine/SessionStateScope.cs @@ -1,11 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; namespace System.Management.Automation { @@ -20,11 +21,9 @@ internal sealed class SessionStateScope /// /// Constructor for a session state scope. /// - /// /// /// The parent of this scope. It can be null for the global scope. /// - /// internal SessionStateScope(SessionStateScope parentScope) { ScopeOrigin = CommandOrigin.Internal; @@ -39,7 +38,7 @@ internal SessionStateScope(SessionStateScope parentScope) { _scriptScope = this; } - } // SessionStateScope constructor + } #endregion constructor @@ -61,20 +60,23 @@ internal SessionStateScope(SessionStateScope parentScope) /// The script scope for this scope. It may reference itself but may not /// be a null reference. /// - /// /// /// If is null when setting the property. /// - /// internal SessionStateScope ScriptScope { - get { return _scriptScope; } + get + { + return _scriptScope; + } + set { Diagnostics.Assert(value != null, "Caller to verify scope is not null"); _scriptScope = value; } } + private SessionStateScope _scriptScope; /// @@ -98,35 +100,31 @@ internal SessionStateScope ScriptScope /// other variables use the variable apis to find the variable and get/set it. /// internal Stack DottedScopes { get { return _dottedScopes; } } + private readonly Stack _dottedScopes = new Stack(); #region Drives /// /// Adds a new drive to the scope's drive collection. /// - /// /// /// The new drive to be added. /// - /// /// /// This method assumes the drive has already been verified and /// the provider has already been notified. /// - /// /// - /// If is null. + /// If is null. /// - /// /// /// If a drive of the same name already exists in this scope. /// - /// internal void NewDrive(PSDriveInfo newDrive) { if (newDrive == null) { - throw PSTraceSource.NewArgumentNullException("newDrive"); + throw PSTraceSource.NewArgumentNullException(nameof(newDrive)); } // Ensure that multiple threads do not try to modify the @@ -158,30 +156,26 @@ internal void NewDrive(PSDriveInfo newDrive) automountedDrives.Add(newDrive.Name, newDrive); } } - } // New Drive + } /// /// Removes the specified drive from this scope. /// - /// /// /// The drive to be removed. /// - /// /// /// This method assumes that the drive has already been validated for removal /// by the provider. /// - /// /// - /// If is null. + /// If is null. /// - /// internal void RemoveDrive(PSDriveInfo drive) { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } if (_drives == null) @@ -204,7 +198,7 @@ internal void RemoveDrive(PSDriveInfo drive) } } } - } // RemoveDrive + } /// /// Removes all the drives from the scope. @@ -213,30 +207,26 @@ internal void RemoveAllDrives() { GetDrives().Clear(); GetAutomountedDrives().Clear(); - } // RemoveAllDrives + } /// /// Retrieves the drive of the specified name. /// - /// /// /// The name of the drive to retrieve. /// - /// /// /// An instance of a PSDriveInfo object with the specified name if one /// exists in this scope or null if one does not exist. /// - /// /// - /// If is null. + /// If is null. /// - /// internal PSDriveInfo GetDrive(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } PSDriveInfo result = null; @@ -248,8 +238,9 @@ internal PSDriveInfo GetDrive(string name) // manually removed drives. GetAutomountedDrives().TryGetValue(name, out result); } + return result; - } // GetDrive + } /// /// Gets an IEnumerable for the drives in this scope. @@ -274,9 +265,10 @@ internal IEnumerable Drives result.Add(drive); } } + return result; } - } // Drives + } #endregion Drives #region Variables @@ -284,48 +276,40 @@ internal IEnumerable Drives /// /// Gets an IDictionary for the variables in this scope. /// - /// internal IDictionary Variables { get { return GetPrivateVariables(); } } /// /// Gets the specified variable from the variable table. /// - /// /// /// The name of the variable to retrieve. /// - /// /// /// The origin of the command trying to retrieve this variable... /// - /// /// /// The PSVariable representing the variable specified. /// - /// internal PSVariable GetVariable(string name, CommandOrigin origin) { PSVariable result; TryGetVariable(name, origin, false, out result); return result; - } // GetVariable + } /// /// Gets the specified variable from the variable table. /// - /// /// /// The name of the variable to retrieve. /// - /// /// /// The PSVariable representing the variable specified. /// - /// internal PSVariable GetVariable(string name) { return GetVariable(name, ScopeOrigin); - } // GetVariable + } /// /// Looks up a variable, returns true and the variable if found and is visible, throws if the found variable is not visible, @@ -333,8 +317,8 @@ internal PSVariable GetVariable(string name) /// /// The name of the variable. /// The command origin (where the scope was created), used to decide if the variable is visible. - /// true if looking up the variable as part of a new or set variable operation - /// The variable, if one is found in scope + /// True if looking up the variable as part of a new or set variable operation. + /// The variable, if one is found in scope. /// Thrown if the variable is not visible based on CommandOrigin. /// True if there is a variable in scope, false otherwise. internal bool TryGetVariable(string name, CommandOrigin origin, bool fromNewOrSet, out PSVariable variable) @@ -352,11 +336,11 @@ internal bool TryGetVariable(string name, CommandOrigin origin, bool fromNewOrSe SessionState.ThrowIfNotVisible(origin, variable); return true; } + return false; } /// - /// /// /// /// @@ -384,32 +368,25 @@ internal object GetAutomaticVariableValue(AutomaticVariable variable) /// /// Sets a variable to the given value. /// - /// /// /// The name of the variable to set. /// - /// /// /// The value for the variable /// - /// /// /// If true, sets the variable value to newValue. If false, newValue must /// be a PSVariable object and the item will be set rather than the value. /// - /// /// /// If true, the variable will be set even if it is readonly. /// - /// /// /// Which SessionState this variable belongs to. /// - /// /// /// The origin of the caller /// - /// /// /// If true and the variable is being set in the global scope, /// then all of the normal variable lookup stuff is bypassed and @@ -418,11 +395,9 @@ internal object GetAutomaticVariableValue(AutomaticVariable variable) /// /// The PSVariable representing the variable that was set. /// - /// /// /// If the variable is read-only or constant. /// - /// internal PSVariable SetVariable(string name, object value, bool asValue, bool force, SessionStateInternal sessionState, CommandOrigin origin = CommandOrigin.Internal, bool fastPath = false) { Diagnostics.Assert(name != null, "The caller should verify the name"); @@ -438,6 +413,7 @@ internal PSVariable SetVariable(string name, object value, bool asValue, bool fo { throw new NotImplementedException("fastPath"); } + variable = new PSVariable(name, variableToSet.Value, variableToSet.Options, variableToSet.Attributes) { Description = variableToSet.Description }; GetPrivateVariables()[name] = variable; return variable; @@ -446,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) { @@ -468,7 +447,7 @@ internal PSVariable SetVariable(string name, object value, bool asValue, bool fo } if (variable is LocalVariable - && (variableToSet.Attributes.Any() || variableToSet.Options != variable.Options)) + && (variableToSet.Attributes.Count > 0 || variableToSet.Options != variable.Options)) { SessionStateUnauthorizedAccessException e = new SessionStateUnauthorizedAccessException( @@ -517,36 +496,25 @@ 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); } - // Don't let people set AllScope variables in ConstrainedLanguage, - // as they can be used to interfere with the session state of - // trusted commands. if (ExecutionContext.HasEverUsedConstrainedLanguage) { - var context = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS(); - - if ((context != null) && - (context.LanguageMode == PSLanguageMode.ConstrainedLanguage) && - ((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope)) - { - throw new PSNotSupportedException(); - } + CheckVariableChangeInConstrainedLanguage(variable); } - _variables[name] = variable; variable.SessionState = sessionState; return variable; - } // SetVariable + } /// /// Sets a variable to scope without any checks. /// This is intended to be used only for global scope. /// - /// PSVariable to set - /// SessionState for variable + /// PSVariable to set. + /// SessionState for variable. /// internal void SetVariableForce(PSVariable variableToSet, SessionStateInternal sessionState) { @@ -562,27 +530,21 @@ internal void SetVariableForce(PSVariable variableToSet, SessionStateInternal se /// /// Sets a variable to the given value. /// - /// /// /// The new variable to create. /// - /// /// /// If true, the variable will be set even if it is readonly. /// - /// /// /// Which SessionState this variable belongs to. /// - /// /// /// The PSVariable representing the variable that was set. /// - /// /// /// If the variable is read-only or constant. /// - /// internal PSVariable NewVariable(PSVariable newVariable, bool force, SessionStateInternal sessionState) { PSVariable variable; @@ -635,42 +597,28 @@ internal PSVariable NewVariable(PSVariable newVariable, bool force, SessionState variable = newVariable; } - // Don't let people set AllScope variables in ConstrainedLanguage, - // as they can be used to interfere with the session state of - // trusted commands. if (ExecutionContext.HasEverUsedConstrainedLanguage) { - var context = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS(); - - if ((context != null) && - (context.LanguageMode == PSLanguageMode.ConstrainedLanguage) && - ((variable.Options & ScopedItemOptions.AllScope) == ScopedItemOptions.AllScope)) - { - throw new PSNotSupportedException(); - } + CheckVariableChangeInConstrainedLanguage(variable); } _variables[variable.Name] = variable; variable.SessionState = sessionState; return variable; - } // NewVariable + } /// /// Removes a variable from the variable table. /// - /// /// /// The name of the variable to remove. /// - /// /// /// If true, the variable will be removed even if its ReadOnly. /// - /// /// /// if the variable is constant. /// - /// internal void RemoveVariable(string name, bool force) { Diagnostics.Assert( @@ -708,7 +656,7 @@ internal void RemoveVariable(string name, bool force) // Finally mark the variable itself has having been removed so // anyone holding a reference to it can be aware of this. variable.WasRemoved = true; - } // RemoveVariable + } internal bool TrySetLocalParameterValue(string name, object value) { @@ -750,27 +698,23 @@ internal bool TryGetLocalVariableFromTuple(string name, bool fromNewOrSet, out P /// /// Gets an IEnumerable for the aliases in this scope. /// - /// internal IEnumerable AliasTable { get { return GetAliases().Values; } - } // AliasTable + } /// /// Gets the specified alias from the alias table. /// - /// /// /// The name of the alias to retrieve. /// - /// /// /// The string representing the value of the alias specified. /// - /// internal AliasInfo GetAlias(string name) { Diagnostics.Assert( @@ -781,40 +725,32 @@ internal AliasInfo GetAlias(string name) GetAliases().TryGetValue(name, out result); return result; - } // GetAlias + } /// /// Sets an alias to the given value. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value for the alias /// - /// /// /// The execution context for this engine instance. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// Origin of the caller of this API /// - /// /// /// The string representing the value that was set. /// - /// /// /// if the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue(string name, string value, ExecutionContext context, bool force, CommandOrigin origin) { Diagnostics.Assert( @@ -861,44 +797,35 @@ internal AliasInfo SetAliasValue(string name, string value, ExecutionContext con AddAliasToCache(name, value); return aliasInfos[name]; - } // SetAliasValue + } /// /// Sets an alias to the given value. /// - /// /// /// The name of the alias to set. /// - /// /// /// The value for the alias /// - /// /// /// The execution context for this engine instance. /// - /// /// /// The options to set on the alias. /// - /// /// /// If true, the value will be set even if the alias is ReadOnly. /// - /// /// /// Origin of the caller of this API /// - /// /// /// The string representing the value that was set. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasValue( string name, string value, @@ -935,7 +862,6 @@ internal AliasInfo SetAliasValue( throw e; } - // Ensure we are not trying to set the alias to constant as this can only be // done at creation time. @@ -987,41 +913,33 @@ internal AliasInfo SetAliasValue( AddAliasToCache(name, value); return result; - } // SetAliasValue - + } /// /// Sets an alias to the given value. /// - /// /// /// The information about the alias to be set /// - /// /// /// If true, the alias will be set even if there is an existing ReadOnly /// alias. /// - /// /// /// Specifies the command origin of the calling command. /// - /// /// /// The string representing the value that was set. /// - /// /// /// If the alias is read-only or constant. /// - /// internal AliasInfo SetAliasItem(AliasInfo aliasToSet, bool force, CommandOrigin origin = CommandOrigin.Internal) { Diagnostics.Assert( aliasToSet != null, "The caller should verify the aliasToSet"); - var aliasInfos = GetAliases(); AliasInfo aliasInfo; if (aliasInfos.TryGetValue(aliasToSet.Name, out aliasInfo)) @@ -1062,29 +980,26 @@ internal AliasInfo SetAliasItem(AliasInfo aliasToSet, bool force, CommandOrigin RemoveAliasFromCache(aliasInfo.Name, aliasInfo.Definition); } + aliasInfos[aliasToSet.Name] = aliasToSet; AddAliasToCache(aliasToSet.Name, aliasToSet.Definition); return aliasToSet; - } // SetAliasItem + } /// /// Removes a alias from the alias table. /// - /// /// /// The name of the alias to remove. /// - /// /// /// If true, the alias will be removed even if it is ReadOnly. /// - /// /// /// If the alias is constant. /// - /// internal void RemoveAlias(string name, bool force) { Diagnostics.Assert( @@ -1114,7 +1029,7 @@ internal void RemoveAlias(string name, bool force) } aliasInfos.Remove(name); - } // RemoveAlias + } #endregion aliases @@ -1123,28 +1038,24 @@ internal void RemoveAlias(string name, bool force) /// /// Gets an IEnumerable for the functions in this scope. /// - /// internal Dictionary FunctionTable { get { return GetFunctions(); - } // get - } // FunctionTable + } + } /// /// Gets the specified function from the function table. /// - /// /// /// The name of the function to retrieve. /// - /// /// /// A FunctionInfo that is either a FilterInfo or FunctionInfo representing the /// function or filter. /// - /// internal FunctionInfo GetFunction(string name) { Diagnostics.Assert( @@ -1155,41 +1066,33 @@ internal FunctionInfo GetFunction(string name) GetFunctions().TryGetValue(name, out result); return result; - } // GetFunction + } /// /// Sets an function to the given function declaration. /// - /// /// /// The name of the function to set. /// - /// /// /// The script block that represents the code for the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The execution context for the function/filter. /// - /// /// /// A FunctionInfo that is either a FilterInfo or FunctionInfo representing the /// function or filter. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -1198,44 +1101,35 @@ internal FunctionInfo SetFunction( ExecutionContext context) { return SetFunction(name, function, null, ScopedItemOptions.Unspecified, force, origin, context); - } // SetFunction + } /// /// Sets an function to the given function declaration. /// - /// /// /// The name of the function to set. /// - /// /// /// The script block that represents the code for the function. /// - /// /// /// The original function (if any) from which the scriptblock was derived. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The execution context for the function/filter. /// - /// /// /// A FunctionInfo that is either a FilterInfo or FunctionInfo representing the /// function or filter. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -1245,49 +1139,39 @@ internal FunctionInfo SetFunction( ExecutionContext context) { return SetFunction(name, function, originalFunction, ScopedItemOptions.Unspecified, force, origin, context); - } // SetFunction + } /// /// Sets an function to the given function declaration. /// - /// /// /// The name of the function to set. /// - /// /// /// The script block that the function should represent. /// - /// /// /// The original function (if any) from which the scriptblock was derived. /// - /// /// /// The options that should be applied to the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The execution context for the function/filter. /// - /// /// /// A FunctionInfo that is either a FilterInfo or FunctionInfo representing the /// function or filter. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -1298,7 +1182,7 @@ internal FunctionInfo SetFunction( ExecutionContext context) { return SetFunction(name, function, originalFunction, options, force, origin, context, null); - } // SetFunction + } internal FunctionInfo SetFunction( string name, @@ -1316,52 +1200,40 @@ internal FunctionInfo SetFunction( /// /// Sets an function to the given function declaration. /// - /// /// /// The name of the function to set. /// - /// /// /// The script block that the function should represent. /// - /// /// /// The original function (if any) from which the scriptblock was derived. /// - /// /// /// The options that should be applied to the function. /// - /// /// /// If true, the function will be set even if its ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The execution context for the function/filter. /// - /// /// /// The name of the help file associated with the function. /// - /// /// /// Function to create the FunctionInfo. /// - /// /// /// A FunctionInfo that is either a FilterInfo or FunctionInfo representing the /// function or filter. /// - /// /// /// If the function is read-only or constant. /// - /// internal FunctionInfo SetFunction( string name, ScriptBlock function, @@ -1377,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; @@ -1389,102 +1268,95 @@ internal FunctionInfo SetFunction( { GetAllScopeFunctions()[name] = result; } - } - else - { - // Make sure the function isn't constant or readonly - SessionState.ThrowIfNotVisible(origin, existingValue); + return result; + } - if (IsFunctionOptionSet(existingValue, ScopedItemOptions.Constant) || - (!force && IsFunctionOptionSet(existingValue, ScopedItemOptions.ReadOnly))) - { - SessionStateUnauthorizedAccessException e = - new SessionStateUnauthorizedAccessException( - name, - SessionStateCategory.Function, - "FunctionNotWritable", - SessionStateStrings.FunctionNotWritable); + // Update the existing function. - throw e; - } + // Make sure the function isn't constant or readonly. + SessionState.ThrowIfNotVisible(origin, existingValue); - // 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 (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; - } // SetFunction + } /// /// Removes a function from the function table. /// - /// /// /// The name of the function to remove. /// - /// /// /// If true, the function is removed even if it is ReadOnly. /// - /// /// /// If the function is constant. /// - /// internal void RemoveFunction(string name, bool force) { Diagnostics.Assert( @@ -1513,8 +1385,9 @@ internal void RemoveFunction(string name, bool force) GetAllScopeFunctions().Remove(name); } } + functionInfos.Remove(name); - } // RemoveFunction + } #endregion functions @@ -1523,27 +1396,23 @@ internal void RemoveFunction(string name, bool force) /// /// Gets an IEnumerable for the cmdlets in this scope. /// - /// internal Dictionary> CmdletTable { get { return _cmdlets; - } // get - } // CmdletTable + } + } /// /// Gets the specified cmdlet from the cmdlet table. /// - /// /// /// The name of the cmdlet to retrieve. /// - /// /// /// A CmdletInfo representing this cmdlet /// - /// internal CmdletInfo GetCmdlet(string name) { Diagnostics.Assert( @@ -1563,37 +1432,30 @@ internal CmdletInfo GetCmdlet(string name) } return result; - } // GetCmdlet + } /// /// Adds a cmdlet to the cmdlet cache. /// - /// /// /// The name of the cmdlet to add. /// - /// /// /// The cmdlet that should be added. /// - /// /// /// The origin of the caller of this API /// - /// /// /// The execution context for the cmdlet. /// - /// /// /// A CmdletInfo representing the cmdlet /// - /// /// /// If the cmdlet is read-only or constant. /// /// - /// internal CmdletInfo AddCmdletToCache( string name, CmdletInfo cmdlet, @@ -1663,7 +1525,6 @@ internal CmdletInfo AddCmdletToCache( } } } - catch (ArgumentException) { throwNotSupported = true; @@ -1680,29 +1541,24 @@ internal CmdletInfo AddCmdletToCache( } return _cmdlets[name][0]; - } // AddCmdlet + } /// /// Removes a cmdlet from the cmdlet table. /// - /// /// /// The name of the cmdlet to remove. /// - /// /// /// The index at which to remove the cmdlet /// If index is -1, remove all cmdlets with that name /// - /// /// /// If true, the cmdlet is removed even if it is ReadOnly. /// - /// /// /// If the cmdlet is constant. /// - /// internal void RemoveCmdlet(string name, int index, bool force) { Diagnostics.Assert( @@ -1724,29 +1580,25 @@ internal void RemoveCmdlet(string name, int index, bool force) // Remove the entry is the list is now empty if (cmdlets.Count == 0) { - //Remove the key + // Remove the key _cmdlets.Remove(name); return; } } - }// RemoveCmdlet + } /// /// Removes a cmdlet entry from the cmdlet table. /// - /// /// /// The key for the cmdlet entry to remove. /// - /// /// /// If true, the cmdlet entry is removed even if it is ReadOnly. /// - /// /// /// If the cmdlet is constant. /// - /// internal void RemoveCmdletEntry(string name, bool force) { Diagnostics.Assert( @@ -1754,7 +1606,7 @@ internal void RemoveCmdletEntry(string name, bool force) "The caller should verify the name"); _cmdlets.Remove(name); - }// RemoveCmdletEntry + } #endregion Cmdlets @@ -1766,42 +1618,39 @@ internal Language.TypeResolutionState TypeResolutionState { get { - // this is kind of our own lazy initialization logic here. - if (_typeResolutionState == null) + if (_typeResolutionState != null) { - if (this.Parent != null) - { - _typeResolutionState = this.Parent.TypeResolutionState; - } - else - { - _typeResolutionState = Language.TypeResolutionState.UsingSystem; - } + return _typeResolutionState; } - return _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; } @@ -1831,10 +1680,6 @@ private static FunctionInfo CreateFunction(string name, ScriptBlock function, Fu { newValue = new FilterInfo(name, (FilterInfo)originalFunction); } - else if (originalFunction is WorkflowInfo) - { - newValue = new WorkflowInfo(name, (WorkflowInfo)originalFunction); - } else if (originalFunction is ConfigurationInfo) { newValue = new ConfigurationInfo(name, (ConfigurationInfo)originalFunction); @@ -1846,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; } @@ -1863,10 +1714,10 @@ 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; + private Dictionary _drives; /// /// Contains the drives that have been automounted by the system. @@ -1875,13 +1726,13 @@ 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; + private Dictionary _automountedDrives; private Dictionary _variables; + private Dictionary GetPrivateVariables() { if (_variables == null) @@ -2022,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); /// @@ -2030,7 +1880,6 @@ private Dictionary GetAllScopeFunctions() /// We don't need a new reference in each scope since it /// is ScopedItemOptions.Constant. /// - /// private static readonly PSVariable s_trueVar = new PSVariable( StringLiterals.True, @@ -2043,7 +1892,6 @@ private Dictionary GetAllScopeFunctions() /// We don't need a new reference in each scope since it /// is ScopedItemOptions.Constant. /// - /// private static readonly PSVariable s_falseVar = new PSVariable( StringLiterals.False, @@ -2056,7 +1904,6 @@ private Dictionary GetAllScopeFunctions() /// We don't need a new reference in each scope since it /// is ScopedItemOptions.Constant. /// - /// private static readonly NullVariable s_nullVar = new NullVariable(); @@ -2064,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) @@ -2086,7 +1933,6 @@ internal IEnumerable GetAliasesByCommandName(string command) } /// - /// /// /// /// @@ -2109,7 +1955,6 @@ private void AddAliasToCache(string alias, string value) } /// - /// /// /// /// @@ -2127,7 +1972,7 @@ private void RemoveAliasFromCache(string alias, string value) } else { - string itemToRemove = list.FirstOrDefault(item => item.Equals(alias, StringComparison.OrdinalIgnoreCase)); + string itemToRemove = list.Find(item => item.Equals(alias, StringComparison.OrdinalIgnoreCase)); if (itemToRemove != null) { list.Remove(itemToRemove); @@ -2135,7 +1980,34 @@ private void RemoveAliasFromCache(string alias, string value) } } - #endregion - } // class SessionStateScope -} // namespace System.Management.Automation + private void CheckVariableChangeInConstrainedLanguage(PSVariable variable) + { + var context = LocalPipeline.GetExecutionContextFromTLS(); + if (context?.LanguageMode == PSLanguageMode.ConstrainedLanguage) + { + if (variable.Options.HasFlag(ScopedItemOptions.AllScope)) + { + 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 + // a module scope, if it's necessary. + ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(variable, this, context.EngineSessionState); + } + } + + #endregion + } +} diff --git a/src/System.Management.Automation/engine/SessionStateScopeAPIs.cs b/src/System.Management.Automation/engine/SessionStateScopeAPIs.cs index b6be9c55175..9dbcfb9c519 100644 --- a/src/System.Management.Automation/engine/SessionStateScopeAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateScopeAPIs.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation; @@ -10,7 +9,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -22,59 +21,59 @@ internal sealed partial class SessionStateInternal /// private SessionStateScope _currentScope; + /// + /// Cmdlet parameter name to return in the error message instead of "scopeID". + /// + internal const string ScopeParameterName = "Scope"; + /// /// Given a scope identifier, returns the proper session state scope. /// - /// /// /// A scope identifier that is either one of the "special" scopes like /// "global", "local", or "private, or a numeric ID of a relative scope /// to the current scope. /// - /// /// /// The scope identified by the scope ID or the current scope if the /// scope ID is not defined as a special or numeric scope identifier. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal SessionStateScope GetScopeByID(string scopeID) { SessionStateScope result = _currentScope; if (!string.IsNullOrEmpty(scopeID)) { - if (String.Equals( + if (string.Equals( scopeID, StringLiterals.Global, StringComparison.OrdinalIgnoreCase)) { result = GlobalScope; } - else if (String.Equals( + else if (string.Equals( scopeID, StringLiterals.Local, StringComparison.OrdinalIgnoreCase)) { result = _currentScope; } - else if (String.Equals( + else if (string.Equals( scopeID, StringLiterals.Private, StringComparison.OrdinalIgnoreCase)) { result = _currentScope; } - else if (String.Equals( + else if (string.Equals( scopeID, StringLiterals.Script, StringComparison.OrdinalIgnoreCase)) @@ -93,43 +92,39 @@ internal SessionStateScope GetScopeByID(string scopeID) if (scopeNumericID < 0) { - throw PSTraceSource.NewArgumentOutOfRangeException("scopeID", scopeID); + throw PSTraceSource.NewArgumentOutOfRangeException(ScopeParameterName, scopeID); } result = GetScopeByID(scopeNumericID) ?? _currentScope; } catch (FormatException) { - throw PSTraceSource.NewArgumentException("scopeID", AutomationExceptions.InvalidScopeIdArgument, "scopeID"); + throw PSTraceSource.NewArgumentException(ScopeParameterName, AutomationExceptions.InvalidScopeIdArgument, ScopeParameterName); } catch (OverflowException) { - throw PSTraceSource.NewArgumentOutOfRangeException("scopeID", scopeID); + throw PSTraceSource.NewArgumentOutOfRangeException(ScopeParameterName, scopeID); } } } return result; - } // GetScopeByID + } /// /// Given a scope ID, walks the scope list to the appropriate scope and returns it. /// - /// /// /// The numeric indexer to the scope relative to the current scope. /// - /// /// /// The scope at the index specified. The index is relative to the current /// scope. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal SessionStateScope GetScopeByID(int scopeID) { SessionStateScope processingScope = _currentScope; @@ -145,7 +140,7 @@ internal SessionStateScope GetScopeByID(int scopeID) { ArgumentOutOfRangeException outOfRange = PSTraceSource.NewArgumentOutOfRangeException( - "scopeID", + ScopeParameterName, originalID, SessionStateStrings.ScopeIDExceedsAvailableScopes, originalID); @@ -153,7 +148,7 @@ internal SessionStateScope GetScopeByID(int scopeID) } return processingScope; - } // GetScopeByID + } /// /// The global scope of session state. Can be accessed @@ -200,6 +195,7 @@ internal SessionStateScope CurrentScope inGlobalScopeLineage = true; break; } + scope = scope.Parent; } @@ -210,7 +206,7 @@ internal SessionStateScope CurrentScope _currentScope = value; } - } // CurrentScope + } /// /// Gets the session state current script scope. @@ -221,16 +217,13 @@ internal SessionStateScope CurrentScope /// Creates a new scope in the scope tree and assigns the parent /// and child scopes appropriately. /// - /// /// /// If true, the new scope is pushed on to the script scope stack and /// can be referenced using $script: /// - /// /// /// A new SessionStateScope which is a child of the current scope. /// - /// internal SessionStateScope NewScope(bool isScriptScope) { Diagnostics.Assert( @@ -245,22 +238,20 @@ internal SessionStateScope NewScope(bool isScriptScope) { newScope.ScriptScope = newScope; } + return newScope; - } // NewScope + } /// /// Removes the current scope from the scope tree and /// changes the current scope to the parent scope. /// - /// /// /// The scope to cleanup and remove. /// - /// /// /// The global scope cannot be removed. /// - /// internal void RemoveScope(SessionStateScope scope) { Diagnostics.Assert( @@ -315,7 +306,7 @@ internal void RemoveScope(SessionStateScope scope) // Ignore all exceptions from the provider as we are // going to force the removal anyway } - } // foreach drive + } scope.RemoveAllDrives(); @@ -326,9 +317,10 @@ internal void RemoveScope(SessionStateScope scope) { _currentScope = _currentScope.Parent; } + scope.Parent = null; - } // RemoveScope - } // SessionStateInternal class + } + } } #pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs b/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs index b81e26ae486..e64137cf396 100644 --- a/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs +++ b/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -13,7 +12,6 @@ internal sealed class SessionStateScopeEnumerator : IEnumerator - /// /// /// The starting scope to start the enumeration from. /// @@ -21,16 +19,14 @@ internal SessionStateScopeEnumerator(SessionStateScope scope) { Diagnostics.Assert(scope != null, "Caller to verify scope argument"); _initialScope = scope; - } // ctor + } /// /// Uses the proper scoping rules to get the next scope to do the lookup in. /// - /// /// /// True if the enumerator was advanced to the next scope, or false otherwise. /// - /// public bool MoveNext() { // On the first call to MoveNext the enumerator should be before @@ -42,25 +38,23 @@ public bool MoveNext() // If the current scope is the global scope there is nowhere else // to do the lookup, so return false. return (_currentEnumeratedScope != null); - } // MoveNext + } /// - /// Sets the enumerator to before the first scope + /// Sets the enumerator to before the first scope. /// public void Reset() { _currentEnumeratedScope = null; - } // Reset + } /// - /// Gets the current lookup scope + /// Gets the current lookup scope. /// - /// /// /// The enumerator is positioned before the first element of the /// collection or after the last element. /// - /// SessionStateScope IEnumerator.Current { get @@ -71,8 +65,8 @@ SessionStateScope IEnumerator.Current } return _currentEnumeratedScope; - } // get - } // Current + } + } object IEnumerator.Current { @@ -83,9 +77,8 @@ object IEnumerator.Current } /// - /// Gets the IEnumerator for this class + /// Gets the IEnumerator for this class. /// - /// /// /// The IEnumerator interface for this class. /// @@ -106,6 +99,5 @@ public void Dispose() private readonly SessionStateScope _initialScope; private SessionStateScope _currentEnumeratedScope; - } // class SessionStateScopeEnumerator -} // namespace System.Management.Automation - + } +} diff --git a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs index 2df21f9b154..08635c7efe1 100644 --- a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs +++ b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; using System.Management.Automation.Provider; @@ -9,7 +8,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -18,32 +17,25 @@ internal sealed partial class SessionStateInternal /// /// Gets an instance of an ISecurityDescriptorCmdletProvider given the provider ID. /// - /// /// /// An instance of a CmdletProvider. /// - /// /// /// An instance of a ISecurityDescriptorCmdletProvider for the specified provider ID. /// - /// /// /// ArgumentNullException if providerId is null. /// NotSupportedException if the providerId is not for a provider /// that is derived from ISecurityDescriptorCmdletProvider. /// - /// internal static ISecurityDescriptorCmdletProvider GetPermissionProviderInstance(CmdletProvider providerInstance) { if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - ISecurityDescriptorCmdletProvider permissionCmdletProvider = - providerInstance as ISecurityDescriptorCmdletProvider; - - if (permissionCmdletProvider == null) + if (providerInstance is not ISecurityDescriptorCmdletProvider permissionCmdletProvider) { throw PSTraceSource.NewNotSupportedException( @@ -51,35 +43,30 @@ internal static ISecurityDescriptorCmdletProvider GetPermissionProviderInstance( } return permissionCmdletProvider; - } // GetPermissionProviderInstance + } #endregion private methods #region GetSecurityDescriptor - /// /// Gets the security descriptor from the specified item. /// - /// /// /// The path to the item to retrieve the security descriptor from. /// - /// /// /// Specifies the parts of a security descriptor to retrieve. /// - /// /// /// The security descriptor for the item at the specified path. /// - /// internal Collection GetSecurityDescriptor(string path, AccessControlSections sections) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -91,34 +78,28 @@ internal Collection GetSecurityDescriptor(string path, Collection contextResults = context.GetAccumulatedObjects() ?? new Collection(); return contextResults; - } // GetSecurityDescriptor + } /// /// Gets the security descriptor from the specified item. /// - /// /// /// The path to the item to retrieve the security descriptor from. /// - /// /// /// Specifies the parts of a security descriptor to retrieve. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. The security descriptor for the item at the specified path is /// written to the context. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void GetSecurityDescriptor( string path, AccessControlSections sections, @@ -126,7 +107,7 @@ internal void GetSecurityDescriptor( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -144,7 +125,7 @@ internal void GetSecurityDescriptor( { GetSecurityDescriptor(providerInstance, providerPath, sections, context); } - } // GetSecurityDescriptor + } private void GetSecurityDescriptor( CmdletProvider providerInstance, @@ -194,7 +175,7 @@ private void GetSecurityDescriptor( path, e); } - } // GetSecurityDescriptor + } #endregion GetSecurityDescriptor @@ -203,29 +184,25 @@ private void GetSecurityDescriptor( /// /// Sets the security descriptor on the specified item. /// - /// /// /// The path to the item to set the security descriptor on. /// - /// /// /// The security descriptor to set on the item at the specified path. /// - /// /// /// The security descriptor that was set on the item at the specified path. /// - /// internal Collection SetSecurityDescriptor(string path, ObjectSecurity securityDescriptor) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (securityDescriptor == null) { - throw PSTraceSource.NewArgumentNullException("securityDescriptor"); + throw PSTraceSource.NewArgumentNullException(nameof(securityDescriptor)); } CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); @@ -238,34 +215,28 @@ internal Collection SetSecurityDescriptor(string path, ObjectSecurity Collection contextResults = context.GetAccumulatedObjects() ?? new Collection(); return contextResults; - } // SetSecurityDescriptor + } /// /// Sets the security descriptor on the specified item. /// - /// /// /// The path to the item to set the security descriptor on. /// - /// /// /// The security descriptor to set on the item at the specified path. /// - /// /// /// The context which the core command is running. /// - /// /// /// Nothing. The security descriptor that was set on the item at the specified path /// is written to the context. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal void SetSecurityDescriptor( string path, ObjectSecurity securityDescriptor, @@ -273,12 +244,12 @@ internal void SetSecurityDescriptor( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (securityDescriptor == null) { - throw PSTraceSource.NewArgumentNullException("securityDescriptor"); + throw PSTraceSource.NewArgumentNullException(nameof(securityDescriptor)); } ProviderInfo provider = null; @@ -300,7 +271,7 @@ internal void SetSecurityDescriptor( securityDescriptor, context); } - } // SetSecurityDescriptor + } private void SetSecurityDescriptor( CmdletProvider providerInstance, @@ -389,7 +360,7 @@ private void SetSecurityDescriptor( path, e); } - } // SetSecurityDescriptor + } #endregion SetSecurityDescriptor @@ -398,25 +369,20 @@ private void SetSecurityDescriptor( /// /// Gets the security descriptor from the specified item. /// - /// /// /// The path to the item to retrieve the security descriptor from. /// - /// /// /// Specifies the parts of a security descriptor to retrieve. /// - /// /// /// Nothing. The security descriptor for the item at the specified path is /// written to the context. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal ObjectSecurity NewSecurityDescriptorFromPath( string path, AccessControlSections sections) @@ -425,7 +391,7 @@ internal ObjectSecurity NewSecurityDescriptorFromPath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } ProviderInfo provider = null; @@ -450,11 +416,11 @@ internal ObjectSecurity NewSecurityDescriptorFromPath( } else { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } return sd; - } // NewSecurityDescriptor + } private ObjectSecurity NewSecurityDescriptorFromPath( CmdletProvider providerInstance, @@ -509,30 +475,25 @@ private ObjectSecurity NewSecurityDescriptorFromPath( } return sd; - } // NewSecurityDescriptor + } /// /// Gets the security descriptor from the specified item. /// - /// /// /// The type of the item which corresponds to the security /// descriptor that we want to create. /// - /// /// /// The name of the provider. /// - /// /// /// Specifies the parts of a security descriptor to retrieve. /// - /// /// /// Nothing. The security descriptor for the item at the specified type is /// written to the context. /// - /// internal ObjectSecurity NewSecurityDescriptorOfType( string providerId, string type, @@ -545,26 +506,21 @@ internal ObjectSecurity NewSecurityDescriptorOfType( /// /// Gets the security descriptor from the specified item. /// - /// /// /// The type of the item which corresponds to the security /// descriptor that we want to create. /// - /// /// /// The type of the item which corresponds to the security /// descriptor that we want to create. /// - /// /// /// Specifies the parts of a security descriptor to retrieve. /// - /// /// /// Nothing. The security descriptor for the item at the specified type is /// written to the context. /// - /// internal ObjectSecurity NewSecurityDescriptorOfType( CmdletProvider providerInstance, string type, @@ -574,12 +530,12 @@ internal ObjectSecurity NewSecurityDescriptorOfType( if (type == null) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } if (providerInstance == null) { - throw PSTraceSource.NewArgumentNullException("providerInstance"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } // This just verifies that the provider supports the interface. @@ -615,9 +571,8 @@ internal ObjectSecurity NewSecurityDescriptorOfType( } return sd; - } // NewSecurityDescriptorOfType + } #endregion NewSecurityDescriptor } } - diff --git a/src/System.Management.Automation/engine/SessionStateStrings.cs b/src/System.Management.Automation/engine/SessionStateStrings.cs index 73108816663..ac5ced27170 100644 --- a/src/System.Management.Automation/engine/SessionStateStrings.cs +++ b/src/System.Management.Automation/engine/SessionStateStrings.cs @@ -1,13 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { /// /// Holds the #defines for any special strings used in session state. /// - /// internal static class StringLiterals { // constants @@ -24,7 +22,6 @@ internal static class StringLiterals /// Porting note: IO.Path.DirectorySeparatorChar is correct for all platforms. On Windows, /// it is '\', and on Linux, it is '/', as expected. /// - /// internal static readonly char DefaultPathSeparator = System.IO.Path.DirectorySeparatorChar; internal static readonly string DefaultPathSeparatorString = DefaultPathSeparator.ToString(); @@ -36,7 +33,6 @@ internal static class StringLiterals /// be "slash agnostic", we need to use the assumption that a '\' is the alternate path /// separator on Linux. /// - /// internal static readonly char AlternatePathSeparator = Platform.IsWindows ? '/' : '\\'; internal static readonly string AlternatePathSeparatorString = AlternatePathSeparator.ToString(); @@ -44,14 +40,12 @@ internal static class StringLiterals /// The default path prefix for remote paths. This is to mimic /// UNC paths in the file system. /// - /// internal const string DefaultRemotePathPrefix = "\\\\"; /// /// The alternate path prefix for remote paths. This is to mimic /// UNC paths in the file system. /// - /// internal const string AlternateRemotePathPrefix = "//"; /// @@ -60,7 +54,7 @@ internal static class StringLiterals internal const string HomePath = "~"; /// - /// name of the global variable table in Variable scopes of session state. + /// Name of the global variable table in Variable scopes of session state. /// internal const string Global = "GLOBAL"; @@ -82,69 +76,69 @@ internal static class StringLiterals internal const string Script = "SCRIPT"; /// - /// session state string used as resource name in exceptions + /// Session state string used as resource name in exceptions. /// internal const string SessionState = "SessionState"; /// - /// The file extension (including the dot) of an PowerShell script file + /// The file extension (including the dot) of an PowerShell script file. /// internal const string PowerShellScriptFileExtension = ".ps1"; /// - /// The file extension (including the dot) of an PowerShell module file + /// The file extension (including the dot) of an PowerShell module file. /// internal const string PowerShellModuleFileExtension = ".psm1"; /// - /// The file extension (including the dot) of an Mof file + /// The file extension (including the dot) of an Mof file. /// internal const string PowerShellMofFileExtension = ".mof"; /// - /// The file extension (including the dot) of a PowerShell cmdletization file + /// The file extension (including the dot) of a PowerShell cmdletization file. /// internal const string PowerShellCmdletizationFileExtension = ".cdxml"; /// - /// The file extension (including the dot) of a PowerShell declarative session configuration file + /// The file extension (including the dot) of a PowerShell declarative session configuration file. /// internal const string PowerShellDISCFileExtension = ".pssc"; /// - /// The file extension (including the dot) of a PowerShell role capability file + /// The file extension (including the dot) of a PowerShell role capability file. /// internal const string PowerShellRoleCapabilityFileExtension = ".psrc"; /// - /// The file extension (including the dot) of an PowerShell data file + /// The file extension (including the dot) of an PowerShell data file. /// internal const string PowerShellDataFileExtension = ".psd1"; /// - /// The file extension (including the dot) of an workflow file + /// The file extension (including the dot) of an workflow dependent assembly. /// - internal const string WorkflowFileExtension = ".xaml"; + internal const string PowerShellILAssemblyExtension = ".dll"; /// - /// The file extension (including the dot) of an workflow dependent assembly + /// The file extension (including the dot) of an workflow dependent Ngen assembly. /// - internal const string PowerShellILAssemblyExtension = ".dll"; + internal const string PowerShellNgenAssemblyExtension = ".ni.dll"; /// - /// The file extension (including the dot) of an workflow dependent Ngen assembly + /// The file extension (including the dot) of an executable file. /// - internal const string PowerShellNgenAssemblyExtension = ".ni.dll"; + internal const string PowerShellILExecutableExtension = ".exe"; internal const string PowerShellConsoleFileExtension = ".psc1"; /// - /// The default verb/noun separator for a command. verb-noun or verb/noun + /// The default verb/noun separator for a command. verb-noun or verb/noun. /// internal const char CommandVerbNounSeparator = '-'; /// - /// The default verb to try if the command was not resolved + /// The default verb to try if the command was not resolved. /// internal const string DefaultCommandVerb = "get"; @@ -176,10 +170,10 @@ internal static class StringLiterals /// /// The escape character used in the language. /// - internal const string EscapeCharacter = "`"; + internal const char EscapeCharacter = '`'; /// - /// The default cmdlet adapter for cmdletization / cdxml modules + /// The default cmdlet adapter for cmdletization / cdxml modules. /// internal const string DefaultCmdletAdapter = "Microsoft.PowerShell.Cmdletization.Cim.CimCmdletAdapter, Microsoft.PowerShell.Commands.Management, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; } diff --git a/src/System.Management.Automation/engine/SessionStateUtils.cs b/src/System.Management.Automation/engine/SessionStateUtils.cs index 5f555c5ebe0..6bdc29da198 100644 --- a/src/System.Management.Automation/engine/SessionStateUtils.cs +++ b/src/System.Management.Automation/engine/SessionStateUtils.cs @@ -1,125 +1,120 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; + using Microsoft.PowerShell.Commands; namespace System.Management.Automation { /// - /// This class holds the integer constants used in Session State + /// This class holds the integer constants used in Session State. /// internal static class SessionStateConstants { /// - /// The default maximum for the number of variables + /// The default maximum for the number of variables. /// internal const int DefaultVariableCapacity = 4096; /// - /// Max # of variables allowed in a scope in Session State + /// Max # of variables allowed in a scope in Session State. /// internal const int MaxVariablesCapacity = 32768; /// - /// Min # of variables allows in a scope in Session State + /// Min # of variables allows in a scope in Session State. /// internal const int MinVariablesCapacity = 1024; /// - /// The default maximum for the number of aliases + /// The default maximum for the number of aliases. /// internal const int DefaultAliasCapacity = 4096; /// - /// Max # of aliases allowed in a scope in Session State + /// Max # of aliases allowed in a scope in Session State. /// internal const int MaxAliasCapacity = 32768; /// - /// Min # of aliases allowed in a scope in Session State + /// Min # of aliases allowed in a scope in Session State. /// internal const int MinAliasCapacity = 1024; /// - /// The default maximum for the number of functions + /// The default maximum for the number of functions. /// internal const int DefaultFunctionCapacity = 4096; /// - /// Max # of functions allowed in a scope in Session State + /// Max # of functions allowed in a scope in Session State. /// internal const int MaxFunctionCapacity = 32768; /// - /// Min # of functions allowed in a scope in Session State + /// Min # of functions allowed in a scope in Session State. /// internal const int MinFunctionCapacity = 1024; /// - /// The default maximum for the number of drives + /// The default maximum for the number of drives. /// internal const int DefaultDriveCapacity = 4096; /// - /// Max # of drives allowed in a scope in Session State + /// Max # of drives allowed in a scope in Session State. /// - /// internal const int MaxDriveCapacity = 32768; /// - /// Min # of drives allowed in a scope in Session State + /// Min # of drives allowed in a scope in Session State. /// internal const int MinDriveCapacity = 1024; /// - /// The default maximum for the number of errors + /// The default maximum for the number of errors. /// internal const int DefaultErrorCapacity = 256; /// - /// Max # of errors allowed in a scope in Session State + /// Max # of errors allowed in a scope in Session State. /// - /// internal const int MaxErrorCapacity = 32768; /// - /// Min # of errors allowed in a scope in Session State + /// Min # of errors allowed in a scope in Session State. /// internal const int MinErrorCapacity = 256; /// - /// The default capacity for a Dictionary store + /// The default capacity for a Dictionary store. /// internal const int DefaultDictionaryCapacity = 100; /// - /// default load factor on a hash table + /// Default load factor on a hash table. /// internal const float DefaultHashTableLoadFactor = 0.25F; } /// - /// This class has static methods that are used in Session State + /// This class has static methods that are used in Session State. /// internal static class SessionStateUtilities { /// /// Converts the specified array into a collection of the specified type. /// - /// /// /// The array to be converted. /// - /// /// /// A collection of the elements that were in the array. /// - /// internal static Collection ConvertArrayToCollection(T[] array) { Collection result = new Collection(); @@ -130,6 +125,7 @@ internal static Collection ConvertArrayToCollection(T[] array) result.Add(element); } } + return result; } @@ -138,33 +134,24 @@ internal static Collection ConvertArrayToCollection(T[] array) /// the string comparer is specified it is used for the comparison, else the /// .Equals method is used. /// - /// /// /// The collection to check for the value. /// - /// /// /// The value to check for. /// - /// /// /// If specified the comparer will be used instead of .Equals. /// - /// /// /// true if the value is contained in the collection or false otherwise. /// - /// /// /// If is null. /// - /// internal static bool CollectionContainsValue(IEnumerable collection, object value, IComparer comparer) { - if (collection == null) - { - throw new ArgumentNullException("collection"); - } + ArgumentNullException.ThrowIfNull(collection); bool result = false; @@ -187,6 +174,7 @@ internal static bool CollectionContainsValue(IEnumerable collection, object valu } } } + return result; } @@ -194,20 +182,16 @@ internal static bool CollectionContainsValue(IEnumerable collection, object valu /// Constructs a collection of WildcardPatterns for the specified /// string collection. /// - /// /// /// The string patterns to construct the WildcardPatterns for. /// - /// /// /// The options to create the WildcardPatterns with. /// - /// /// /// A collection of WildcardPatterns that represent the string patterns /// that were passed. /// - /// internal static Collection CreateWildcardsFromStrings( IEnumerable globPatterns, WildcardOptions options) @@ -218,9 +202,9 @@ internal static Collection CreateWildcardsFromStrings( { // Loop through the patterns and construct a wildcard pattern for each one - foreach (String pattern in globPatterns) + foreach (string pattern in globPatterns) { - if (!String.IsNullOrEmpty(pattern)) + if (!string.IsNullOrEmpty(pattern)) { result.Add( WildcardPattern.Get( @@ -231,30 +215,25 @@ internal static Collection CreateWildcardsFromStrings( } return result; - } // CreateWildcardsFromStrings + } /// - /// Determines if the specified text matches any of the patterns + /// Determines if the specified text matches any of the patterns. /// - /// /// /// The text to check against the wildcard pattern. /// - /// /// /// An array of wildcard patterns. If the array is empty or null the text is deemed /// to be a match. /// - /// /// /// The default value that should be returned if /// is empty or null. /// - /// /// /// True if the text matches any of the patterns OR if patterns is null or empty and defaultValue is True. /// - /// internal static bool MatchesAnyWildcardPattern( string text, IEnumerable patterns, @@ -284,20 +263,17 @@ internal static bool MatchesAnyWildcardPattern( } return result; - } // MatchesAnyWildcardPattern + } /// - /// Converts an OpenMode enum value to a FileMode + /// Converts an OpenMode enum value to a FileMode. /// - /// /// /// The OpenMode value to be converted. /// - /// /// /// The FileMode representation of the OpenMode. /// - /// internal static FileMode GetFileModeFromOpenMode(OpenMode openMode) { FileMode result = FileMode.Create; @@ -328,7 +304,6 @@ namespace Microsoft.PowerShell.Commands /// The enum used by commands to allow the user to specify how /// a file (or other item) should be opened. /// - /// public enum OpenMode { /// @@ -347,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 333e2dd132e..336c2b77952 100644 --- a/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Management.Automation.Provider; using System.Management.Automation.Internal; +using System.Management.Automation.Provider; using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -16,7 +16,7 @@ namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -25,7 +25,7 @@ internal sealed partial class SessionStateInternal /// /// Add an new SessionStateVariable entry to this session state object... /// - /// The entry to add + /// The entry to add. internal void AddSessionStateEntry(SessionStateVariableEntry entry) { PSVariable v = new PSVariable(entry.Name, entry.Value, @@ -36,30 +36,25 @@ internal void AddSessionStateEntry(SessionStateVariableEntry entry) /// /// Get a variable out of session state. This interface supports - /// the scope specifiers like "global:foobar" + /// the scope specifiers like "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// Origin of the command making this request. /// - /// /// /// The specified variable. /// - /// /// /// If is null. /// - /// internal PSVariable GetVariable(string name, CommandOrigin origin) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name, VariablePathFlags.Variable | VariablePathFlags.Unqualified); @@ -68,70 +63,58 @@ internal PSVariable GetVariable(string name, CommandOrigin origin) PSVariable resultItem = GetVariableItem(variablePath, out scope, origin); return resultItem; - } // GetVariable + } /// /// Get a variable out of session state. This interface supports - /// the scope specifiers like "global:foobar" + /// the scope specifiers like "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// The specified variable. /// - /// /// /// If is null. /// - /// internal PSVariable GetVariable(string name) { return GetVariable(name, CommandOrigin.Internal); - } // GetVariable + } /// /// Get a variable out of session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object GetVariableValue(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -141,48 +124,38 @@ internal object GetVariableValue(string name) object resultItem = GetVariableValue(variablePath, out context, out scope); return resultItem; - } // GetVariableValue - + } /// /// Get a variable out of session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// value to return if you can't find Name or it returns null. /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object GetVariableValue(string name, object defaultValue) { object returnObject = GetVariableValue(name) ?? defaultValue; @@ -193,27 +166,22 @@ internal object GetVariableValue(string name, object defaultValue) /// Looks up the specified variable and returns the context under which /// the variable was found as well as the variable itself. /// - /// /// /// The VariablePath helper for the variable. /// - /// /// /// The scope the variable was found in. Null if the variable wasn't found. /// - /// /// /// Returns the context under which the variable was found. The context will /// have the drive data already set. This will be null if the variable was /// not found. /// - /// /// /// 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. @@ -221,28 +189,22 @@ internal object GetVariableValue(string name, object defaultValue) /// - each consecutive parent scope until the variable is found. /// - global scope /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object GetVariableValue( VariablePath variablePath, out CmdletProviderContext context, @@ -272,31 +234,25 @@ internal object GetVariableValue( /// Looks up the specified variable and returns the context under which /// the variable was found as well as the variable itself. /// - /// /// /// The VariablePath helper for the variable. /// - /// /// /// The scope the variable was found in. Null if the variable wasn't found. /// - /// /// /// Returns the context under which the variable was found. The context will /// have the drive data already set. This will be null if the variable was /// not found. /// - /// /// /// The origin of the caller of this API /// - /// /// /// 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. @@ -304,28 +260,22 @@ internal object GetVariableValue( /// - each consecutive parent scope until the variable is found. /// - global scope /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// #pragma warning disable 0162 internal object GetVariableValueFromProvider( VariablePath variablePath, @@ -337,7 +287,7 @@ internal object GetVariableValueFromProvider( if (variablePath == null) { - throw PSTraceSource.NewArgumentNullException("variablePath"); + throw PSTraceSource.NewArgumentNullException(nameof(variablePath)); } Dbg.Diagnostics.Assert( @@ -402,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", @@ -418,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", @@ -442,7 +390,7 @@ internal object GetVariableValueFromProvider( { // Since more than one path was resolved, this is an error. - //Before throwing exception. Close the readers to avoid sharing violation. + // Before throwing exception. Close the readers to avoid sharing violation. foreach (IContentReader r in readers) { r.Close(); @@ -457,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", @@ -495,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( @@ -553,43 +499,36 @@ internal object GetVariableValueFromProvider( } while (false); return result; - } // GetVariableFromProvider + } #pragma warning restore 0162 /// /// Looks up the specified variable and returns the context under which /// the variable was found as well as the variable itself. /// - /// /// /// The VariablePath helper for the variable. /// - /// /// /// The scope the variable was found in. Null if the variable wasn't found. /// - /// /// /// Origin of the command requesting this variable /// - /// /// /// 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 /// - each consecutive parent scope until the variable is found. /// - global scope /// - /// /// /// If is null. /// - /// internal PSVariable GetVariableItem( VariablePath variablePath, out SessionStateScope scope, @@ -599,7 +538,7 @@ internal PSVariable GetVariableItem( if (variablePath == null) { - throw PSTraceSource.NewArgumentNullException("variablePath"); + throw PSTraceSource.NewArgumentNullException(nameof(variablePath)); } Dbg.Diagnostics.Assert(variablePath.IsVariable, "Can't get variable w/ non-variable path"); @@ -614,83 +553,71 @@ internal PSVariable GetVariableItem( result = ((IEnumerator)searcher).Current; scope = searcher.CurrentLookupScope; } + return result; - } // GetVariableItem + } /// /// Looks up the specified variable and returns the context under which /// the variable was found as well as the variable itself. /// - /// /// /// The VariablePath helper for the variable. /// - /// /// /// The scope the variable was found in. Null if the variable wasn't found. /// - /// /// /// 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 /// - each consecutive parent scope until the variable is found. /// - global scope /// - /// /// /// If is null. /// - /// internal PSVariable GetVariableItem( VariablePath variablePath, out SessionStateScope scope) { return GetVariableItem(variablePath, out scope, CommandOrigin.Internal); - } // GetVariableItem + } /// /// Get a variable out of session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal PSVariable GetVariableAtScope(string name, string scopeID) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -710,62 +637,51 @@ internal PSVariable GetVariableAtScope(string name, string scopeID) } return resultItem; - } // GetVariable + } /// /// Get a variable out of session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to look up /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object GetVariableValueAtScope(string name, string scopeID) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -821,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", @@ -837,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", @@ -849,7 +763,6 @@ internal object GetVariableValueAtScope(string name, string scopeID) false); } - if (readers == null || readers.Count == 0) { // The drive was found but the path was wrong or something so return null. @@ -877,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", @@ -916,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( @@ -979,10 +890,10 @@ internal object GetVariableValueAtScope(string name, string scopeID) { } } - } // if resultItem != null + } return resultItem; - } // GetVariableValueAtScope + } internal object GetAutomaticVariableValue(AutomaticVariable variable) { @@ -1003,51 +914,41 @@ internal object GetAutomaticVariableValue(AutomaticVariable variable) /// /// Set a variable in session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "$env:PATH = 'c:\windows'" or "$global:foobar = 13" + /// "$env:PATH = 'c:\windows'" or "$global:example = 13" /// - /// /// /// The name of the item to set. /// - /// /// /// The new value of the item being set. /// - /// /// /// The origin of the caller of this API... /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void SetVariableValue(string name, object newValue, CommandOrigin origin) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -1058,46 +959,37 @@ internal void SetVariableValue(string name, object newValue, CommandOrigin origi /// /// Set a variable in session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "$env:PATH = 'c:\windows'" or "$global:foobar = 13" + /// "$env:PATH = 'c:\windows'" or "$global:example = 13" /// /// BUGBUG: this overload exists because a lot of tests in the /// testsuite use it. Those tests should eventually be fixed and this overload /// should be removed. /// - /// /// /// The name of the item to set. /// - /// /// /// The new value of the item being set. /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void SetVariableValue(string name, object newValue) { SetVariableValue(name, newValue, CommandOrigin.Internal); @@ -1105,39 +997,32 @@ internal void SetVariableValue(string name, object newValue) /// /// Set a variable in session state. This interface supports - /// the scope specifiers like "$global:foobar = 13" + /// the scope specifiers like "$global:example = 13" /// - /// /// /// The variable to be set. /// - /// /// /// If true, the variable is set even if it is ReadOnly. /// - /// /// /// The origin of the caller of this API /// - /// /// /// A PSVariable object if refers to a variable. /// An PSObject if refers to a provider path. /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// internal object SetVariable(PSVariable variable, bool force, CommandOrigin origin) { - if (variable == null || String.IsNullOrEmpty(variable.Name)) + if (variable == null || string.IsNullOrEmpty(variable.Name)) { - throw PSTraceSource.NewArgumentException("variable"); + throw PSTraceSource.NewArgumentException(nameof(variable)); } VariablePath variablePath = new VariablePath(variable.Name, VariablePathFlags.Variable | VariablePathFlags.Unqualified); @@ -1148,54 +1033,42 @@ internal object SetVariable(PSVariable variable, bool force, CommandOrigin origi /// /// Set a variable using a pre-parsed variablePath object instead of a string. /// - /// /// /// A pre-parsed variable path object for the variable in question. /// - /// /// /// The value to set. /// - /// /// /// If true, sets the variable value to newValue. If false, newValue must /// be a PSVariable object and the item will be set rather than the value. /// - /// /// /// The origin of the caller /// - /// /// /// A PSVariable object if refers to a variable. /// An PSObject if refers to a provider path. /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object SetVariable( VariablePath variablePath, object newValue, @@ -1208,58 +1081,45 @@ internal object SetVariable( /// /// Set a variable using a pre-parsed variablePath object instead of a string. /// - /// /// /// A pre-parsed variable path object for the variable in question. /// - /// /// /// The value to set. /// - /// /// /// If true, sets the variable value to newValue. If false, newValue must /// be a PSVariable object and the item will be set rather than the value. /// - /// /// /// If true, the variable is set even if it is ReadOnly. /// - /// /// /// The origin of the caller /// - /// /// /// A PSVariable object if refers to a variable. /// An PSObject if refers to a provider path. /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal object SetVariable( VariablePath variablePath, object newValue, @@ -1270,8 +1130,9 @@ internal object SetVariable( object result = null; if (variablePath == null) { - throw PSTraceSource.NewArgumentNullException("variablePath"); + throw PSTraceSource.NewArgumentNullException(nameof(variablePath)); } + CmdletProviderContext context = null; SessionStateScope scope = null; @@ -1310,8 +1171,9 @@ internal object SetVariable( if (variablePath.IsPrivate && varResult != null) { - varResult.Options = varResult.Options | ScopedItemOptions.Private; + varResult.Options |= ScopedItemOptions.Private; } + result = varResult; } else @@ -1375,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", @@ -1391,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", @@ -1403,7 +1263,6 @@ internal object SetVariable( false); } - if (writers == null || writers.Count == 0) { ItemNotFoundException itemNotFound = @@ -1432,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", @@ -1456,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( @@ -1494,57 +1351,49 @@ internal object SetVariable( } #endif } + return result; - } // SetVariable + } /// /// Set a variable in session state. /// - /// /// /// The variable to set /// - /// /// /// The ID of the scope to do the lookup in. The ID is either a zero based index /// of the scope tree with the current scope being zero, its parent scope /// being 1 and so on, or "global", "local", "private", or "script" /// - /// /// /// If true, the variable is set even if it is ReadOnly. /// - /// /// /// The origin of the caller /// - /// /// /// If is null or its name is null or empty. /// or /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// A PSVariable object if refers to a variable. /// An PSObject if refers to a provider path. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If the variable is read-only or constant. /// - /// internal object SetVariableAtScope(PSVariable variable, string scopeID, bool force, CommandOrigin origin) { - if (variable == null || String.IsNullOrEmpty(variable.Name)) + if (variable == null || string.IsNullOrEmpty(variable.Name)) { - throw PSTraceSource.NewArgumentException("variable"); + throw PSTraceSource.NewArgumentException(nameof(variable)); } SessionStateScope lookupScope = GetScopeByID(scopeID); @@ -1557,35 +1406,30 @@ internal object SetVariableAtScope(PSVariable variable, string scopeID, bool for force, this, origin); - } // SetVariableAtScope + } #region NewVariable /// /// Creates a new variable. /// - /// /// /// The variable to create /// - /// /// /// If true, the variable is created even if it is ReadOnly. /// - /// /// /// A PSVariable representing the variable that was created. /// - /// /// /// If the variable is read-only or constant. /// - /// internal object NewVariable(PSVariable variable, bool force) { - if (variable == null || String.IsNullOrEmpty(variable.Name)) + if (variable == null || string.IsNullOrEmpty(variable.Name)) { - throw PSTraceSource.NewArgumentException("variable"); + throw PSTraceSource.NewArgumentException(nameof(variable)); } return @@ -1593,51 +1437,43 @@ internal object NewVariable(PSVariable variable, bool force) variable, force, this); - } // NewVariable + } /// - /// Creates a new variable in the specified scope + /// Creates a new variable in the specified scope. /// - /// /// /// The variable to create /// - /// /// /// The ID of the scope to do the lookup in. The ID is either a zero based index /// of the scope tree with the current scope being zero, its parent scope /// being 1 and so on, or "global", "local", "private", or "script" /// - /// /// /// If true, the variable is set even if it is ReadOnly. /// - /// /// /// If is null or its name is null or empty. /// or /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// A PSVariable representing the variable that was created. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If the variable is read-only or constant. /// - /// internal object NewVariableAtScope(PSVariable variable, string scopeID, bool force) { - if (variable == null || String.IsNullOrEmpty(variable.Name)) + if (variable == null || string.IsNullOrEmpty(variable.Name)) { - throw PSTraceSource.NewArgumentException("variable"); + throw PSTraceSource.NewArgumentException(nameof(variable)); } // The lookup scope from above is ignored and the scope is retrieved by @@ -1650,43 +1486,35 @@ internal object NewVariableAtScope(PSVariable variable, string scopeID, bool for variable, force, this); - } // NewVariableAtScope + } #endregion NewVariable /// /// Removes a variable from the variable table. /// - /// /// /// The name of the variable to remove. /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveVariable(string name) { RemoveVariable(name, false); @@ -1695,45 +1523,36 @@ internal void RemoveVariable(string name) /// /// Removes a variable from the variable table. /// - /// /// /// The name of the variable to remove. /// - /// /// /// If true, the variable will be removed even if its ReadOnly. /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// - /// internal void RemoveVariable(string name, bool force) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -1754,24 +1573,20 @@ internal void RemoveVariable(string name, bool force) RemoveItem(new string[] { variablePath.QualifiedName }, false, context); context.ThrowFirstErrorOrDoNothing(); } - } // RemoveVariable + } /// /// Removes a variable from the variable table. /// - /// /// /// The variable to remove. /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// - /// internal void RemoveVariable(PSVariable variable) { RemoveVariable(variable, false); @@ -1780,28 +1595,23 @@ internal void RemoveVariable(PSVariable variable) /// /// Removes a variable from the variable table. /// - /// /// /// The variable to remove. /// - /// /// /// If true, the variable will be removed even if its ReadOnly. /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// - /// internal void RemoveVariable(PSVariable variable, bool force) { if (variable == null) { - throw PSTraceSource.NewArgumentNullException("variable"); + throw PSTraceSource.NewArgumentNullException(nameof(variable)); } VariablePath variablePath = new VariablePath(variable.Name); @@ -1812,41 +1622,33 @@ internal void RemoveVariable(PSVariable variable, bool force) { scope.RemoveVariable(variablePath.QualifiedName, force); } - } // RemoveVariable + } /// /// Remove a variable from session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to remove /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// If is null. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// - /// /// /// if the variable is constant. /// - /// /// /// If refers to an MSH path (not a variable) /// and the provider throws an exception. /// - /// internal void RemoveVariableAtScope(string name, string scopeID) { RemoveVariableAtScope(name, scopeID, false); @@ -1855,44 +1657,36 @@ internal void RemoveVariableAtScope(string name, string scopeID) /// /// Remove a variable from session state. This interface supports /// the "namespace:name" syntax so you can do things like - /// "env:PATH" or "global:foobar" + /// "env:PATH" or "global:example" /// - /// /// /// name of variable to remove /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// If true, the variable will be removed even if its ReadOnly. /// - /// /// /// If is null or empty. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// if the variable is constant. /// - /// /// /// If refers to an MSH path (not a variable) /// and the provider throws an exception. /// - /// internal void RemoveVariableAtScope(string name, string scopeID, bool force) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } VariablePath variablePath = new VariablePath(name); @@ -1922,33 +1716,27 @@ internal void RemoveVariableAtScope(string name, string scopeID, bool force) context.ThrowFirstErrorOrDoNothing(); } } - } // RemoveVariableAtScope + } /// /// Remove a variable from session state. /// - /// /// /// The variable to remove /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// If is null. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// if the variable is constant. /// - /// internal void RemoveVariableAtScope(PSVariable variable, string scopeID) { RemoveVariableAtScope(variable, scopeID, false); @@ -1957,37 +1745,30 @@ internal void RemoveVariableAtScope(PSVariable variable, string scopeID) /// /// Remove a variable from session state. /// - /// /// /// The variable to remove /// - /// /// /// The ID of the scope to lookup the variable in. /// - /// /// /// If true, the variable will be removed even if its ReadOnly. /// - /// /// /// If is null. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// if the variable is constant. /// - /// internal void RemoveVariableAtScope(PSVariable variable, string scopeID, bool force) { if (variable == null) { - throw PSTraceSource.NewArgumentNullException("variable"); + throw PSTraceSource.NewArgumentNullException(nameof(variable)); } VariablePath variablePath = new VariablePath(variable.Name); @@ -1997,18 +1778,16 @@ internal void RemoveVariableAtScope(PSVariable variable, string scopeID, bool fo SessionStateScope lookupScope = GetScopeByID(scopeID); lookupScope.RemoveVariable(variablePath.QualifiedName, force); - } // RemoveVariableAtScope + } /// /// Gets a flattened view of the variables that are visible using /// the current scope as a reference and filtering the variables in /// the other scopes based on the scoping rules. /// - /// /// /// An IDictionary representing the visible variables. /// - /// internal IDictionary GetVariableTable() { SessionStateScopeEnumerator scopeEnumerator = @@ -2025,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) { @@ -2048,10 +1827,7 @@ private void GetScopeVariableTable(SessionStateScope scope, Dictionary @@ -2059,21 +1835,17 @@ private void GetScopeVariableTable(SessionStateScope scope, Dictionary - /// /// /// An IDictionary representing the visible variables. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// internal IDictionary GetVariableTableAtScope(string scopeID) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -2087,7 +1859,7 @@ internal IDictionary GetVariableTableAtScope(string scopeID) internal List ExportedVariables { get; } = new List(); #endregion variables - } // SessionStateInternal class + } } -#pragma warning restore 56500 \ No newline at end of file +#pragma warning restore 56500 diff --git a/src/System.Management.Automation/engine/ShellVariable.cs b/src/System.Management.Automation/engine/ShellVariable.cs index 974bdb49d35..8440845a32d 100644 --- a/src/System.Management.Automation/engine/ShellVariable.cs +++ b/src/System.Management.Automation/engine/ShellVariable.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Runtime.CompilerServices; -using System.Collections.Generic; -using System.Collections.ObjectModel; namespace System.Management.Automation { @@ -20,11 +19,9 @@ public class PSVariable : IHasSessionStateEntryVisibility /// /// Constructs a variable with the given name. /// - /// /// /// The name of the variable. /// - /// /// /// If is null or empty. /// @@ -36,15 +33,12 @@ public PSVariable(string name) /// /// Constructs a variable with the given name, and value. /// - /// /// /// The name of the variable. /// - /// /// /// The value of the variable. /// - /// /// /// If is null or empty. /// @@ -56,20 +50,16 @@ public PSVariable(string name, object value) /// /// Constructs a variable with the given name, value, and options. /// - /// /// /// The name of the variable. /// - /// /// /// The value of the variable. /// - /// /// /// The constraints of the variable. Note, variables can only be made constant /// in the constructor. /// - /// /// /// If is null or empty. /// @@ -81,24 +71,19 @@ public PSVariable(string name, object value, ScopedItemOptions options) /// /// Constructs a variable with the given name, value, options, and description. /// - /// /// /// The name of the variable. /// - /// /// /// The value of the variable. /// - /// /// /// The constraints of the variable. Note, variables can only be made constant /// in the constructor. /// - /// /// /// The description for the variable. /// - /// /// /// If is null or empty. /// @@ -111,29 +96,23 @@ internal PSVariable(string name, object value, ScopedItemOptions options, string /// /// Constructs a variable with the given name, value, options, and description. /// - /// /// /// The name of the variable. /// - /// /// /// The value of the variable. /// - /// /// /// The constraints of the variable. Note, variables can only be made constant /// in the constructor. /// - /// /// /// The attributes for the variable. ValidateArgumentsAttribute and derived types /// will be used to validate a value before setting it. /// - /// /// /// The description for the variable. /// - /// /// /// If is null or empty. /// @@ -148,33 +127,26 @@ internal PSVariable( _description = description; } - /// - /// Constructs a variable with the given name, value, options, and attributes + /// Constructs a variable with the given name, value, options, and attributes. /// - /// /// /// The name of the variable. /// - /// /// /// The value of the variable. /// - /// /// /// The constraints of the variable. Note, variables can only be made constant /// in the constructor. /// - /// /// /// The attributes for the variable. ValidateArgumentsAttribute and derived types /// will be used to validate a value before setting it. /// - /// /// /// If is null or empty. /// - /// /// /// If the validation metadata identified in /// throws an exception. @@ -185,9 +157,9 @@ public PSVariable( ScopedItemOptions options, Collection attributes) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } Name = name; @@ -230,7 +202,7 @@ internal PSVariable(string name, bool dummy) /// /// Gets the name of the variable. /// - public string Name { get; } = String.Empty; + public string Name { get; } = string.Empty; /// /// Gets or sets the description of the variable. @@ -241,20 +213,21 @@ public virtual string Description { return _description; } + set { _description = value; } } - private string _description = String.Empty; + private string _description = string.Empty; internal void DebuggerCheckVariableRead() { var context = SessionState != null ? SessionState.ExecutionContext : LocalPipeline.GetExecutionContextFromTLS(); - if (null != context && context._debuggingMode > 0) + if (context != null && context._debuggingMode > 0) { context.Debugger.CheckVariableRead(Name); } @@ -265,20 +238,26 @@ internal void DebuggerCheckVariableWrite() var context = SessionState != null ? SessionState.ExecutionContext : LocalPipeline.GetExecutionContextFromTLS(); - if (null != context && context._debuggingMode > 0) + if (context != null && context._debuggingMode > 0) { context.Debugger.CheckVariableWrite(Name); } } /// - /// Gets or sets the value of the variable + /// Gets the value without triggering debugger check. + /// + internal virtual object GetValueRaw() + { + return _value; + } + + /// + /// Gets or sets the value of the variable. /// - /// /// /// If the variable is read-only or constant upon call to set. /// - /// /// /// is not valid according to one or more /// of the attributes of this shell variable. @@ -296,6 +275,7 @@ public virtual object Value SetValue(value); } } + private object _value; /// @@ -329,7 +309,6 @@ public string ModuleName /// /// Gets or sets the scope options on the variable. /// - /// /// /// Upon set, if the variable is constant or if /// contains the constant flag. @@ -404,36 +383,33 @@ internal void SetOptions(ScopedItemOptions newOptions, bool force) _options = newOptions; } + private ScopedItemOptions _options = ScopedItemOptions.None; /// /// Gets the collection that contains the attributes for the variable. /// - /// /// /// To add or remove attributes, get the collection and then add or remove /// attributes to that collection. /// public Collection Attributes { - get { return _attributes ?? (_attributes = new PSVariableAttributeCollection(this)); } + get { return _attributes ??= new PSVariableAttributeCollection(this); } } - private PSVariableAttributeCollection _attributes; + private PSVariableAttributeCollection _attributes; /// /// Checks if the given value meets the validation attribute constraints on the PSVariable. /// - /// /// /// value which needs to be checked /// - /// /// /// If is null or if no attributes are set, then /// the value is deemed valid. /// - /// /// /// If the validation metadata throws an exception. /// @@ -454,21 +430,19 @@ internal static bool IsValidValue(IEnumerable attributes, object valu } } } + return true; } /// - /// Determines if the value is valid for the specified attribute + /// Determines if the value is valid for the specified attribute. /// - /// /// /// The variable value to validate. /// - /// /// /// The attribute to use to validate that value. /// - /// /// /// True if the value is valid with respect to the attribute, or false otherwise. /// @@ -498,25 +472,23 @@ internal static bool IsValidValue(object value, Attribute attribute) result = false; } } + return result; - } // IsValidValue + } /// /// Runs all ArgumentTransformationAttributes that are specified in the Attributes /// collection on the given value in the order that they are in the collection. /// - /// /// /// The attributes to use to transform the value. /// /// /// The value to be transformed. /// - /// /// /// The transformed value. /// - /// /// /// If the argument transformation fails. /// @@ -542,9 +514,10 @@ internal static object TransformValue(IEnumerable attributes, object attribute as ArgumentTransformationAttribute; if (transformationAttribute != null) { - result = transformationAttribute.Transform(engine, result); + result = transformationAttribute.TransformInternal(engine, result); } } + return result; } @@ -553,7 +526,7 @@ internal static object TransformValue(IEnumerable attributes, object /// attributes, so repeating that process is slow and wrong. This function /// applies the attributes without repeating the checks. /// - /// The list of attributes to add + /// The list of attributes to add. internal void AddParameterAttributesNoChecks(Collection attributes) { foreach (Attribute attribute in attributes) @@ -568,53 +541,49 @@ internal void AddParameterAttributesNoChecks(Collection attributes) /// Returns true if the PSVariable is constant (only visible in the /// current scope), false otherwise. /// - /// internal bool IsConstant { get { return (_options & ScopedItemOptions.Constant) != 0; } - } // IsConstant + } /// /// Returns true if the PSVariable is readonly (only visible in the /// current scope), false otherwise. /// - /// internal bool IsReadOnly { get { return (_options & ScopedItemOptions.ReadOnly) != 0; } - } // IsReadOnly + } /// /// Returns true if the PSVariable is private (only visible in the /// current scope), false otherwise. /// - /// internal bool IsPrivate { get { return (_options & ScopedItemOptions.Private) != 0; } - } // IsPrivate + } /// /// Returns true if the PSVariable is propagated to all scopes /// when the scope is created. /// - /// internal bool IsAllScope { get { return (_options & ScopedItemOptions.AllScope) != 0; } - } // IsAllScope + } /// /// Indicates that the variable has been removed from session state @@ -628,6 +597,7 @@ internal bool WasRemoved { return _wasRemoved; } + set { _wasRemoved = value; @@ -641,6 +611,7 @@ internal bool WasRemoved } } } + private bool _wasRemoved; internal SessionStateInternal SessionState { get; set; } @@ -648,22 +619,18 @@ internal bool WasRemoved #endregion internal members /// - /// Verifies the constraints and attributes before setting the value + /// Verifies the constraints and attributes before setting the value. /// - /// /// /// The value to be set. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the validation metadata throws an exception or the value doesn't /// pass the validation metadata. /// - /// private void SetValue(object value) { // Check to see if the variable is writable @@ -718,6 +685,7 @@ private void SetValueRawImpl(object newValue, bool preserveValueTypeSemantics) { newValue = CopyMutableValues(newValue); } + _value = newValue; } @@ -728,13 +696,13 @@ internal virtual void SetValueRaw(object newValue, bool preserveValueTypeSemanti private readonly CallSite> _copyMutableValueSite = CallSite>.Create(PSVariableAssignmentBinder.Get()); + internal object CopyMutableValues(object o) { // The variable assignment binder copies mutable values and returns other values as is. return _copyMutableValueSite.Target.Invoke(_copyMutableValueSite, o); } - internal void WrapValue() { if (!this.IsConstant) @@ -783,7 +751,7 @@ private static object PreserveValueType(object value) return value; } #endif - } // class PSVariable + } internal class LocalVariable : PSVariable { @@ -799,7 +767,11 @@ public LocalVariable(string name, MutableTuple tuple, int tupleSlot) public override ScopedItemOptions Options { - get { return base.Options; } + get + { + return base.Options; + } + set { // Throw, but only if someone is actually changing the options. @@ -824,6 +796,7 @@ public override object Value DebuggerCheckVariableRead(); return _tuple.GetValue(_tupleSlot); } + set { _tuple.SetValue(_tupleSlot, value); @@ -831,12 +804,18 @@ public override object Value } } + internal override object GetValueRaw() + { + return _tuple.GetValue(_tupleSlot); + } + internal override void SetValueRaw(object newValue, bool preserveValueTypeSemantics) { if (preserveValueTypeSemantics) { newValue = CopyMutableValues(newValue); } + this.Value = newValue; } } @@ -845,14 +824,12 @@ internal override void SetValueRaw(object newValue, bool preserveValueTypeSemant /// This class is used for $null. It always returns null as a value and accepts /// any value when it is set and throws it away. /// - /// internal class NullVariable : PSVariable { /// /// Constructor that calls the base class constructor with name "null" and /// value null. /// - /// internal NullVariable() : base(StringLiterals.Null, null, ScopedItemOptions.Constant | ScopedItemOptions.AllScope) { } @@ -879,9 +856,11 @@ public override object Value /// public override string Description { - get { return _description ?? (_description = SessionStateStrings.DollarNullDescription); } + get { return _description ??= SessionStateStrings.DollarNullDescription; } + set { /* Do nothing */ } } + private string _description; /// @@ -890,6 +869,7 @@ public override string Description public override ScopedItemOptions Options { get { return ScopedItemOptions.None; } + set { /* Do nothing */ } } } @@ -929,9 +909,8 @@ public enum ScopedItemOptions AllScope = 0x8, /// - /// The option is not specified by the user + /// The option is not specified by the user. /// Unspecified = 0x10 } } - diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index fb9fbd9a526..51419a0d5c2 100644 --- a/src/System.Management.Automation/engine/SpecialVariables.cs +++ b/src/System.Management.Automation/engine/SpecialVariables.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Management.Automation.Internal; namespace System.Management.Automation { @@ -22,198 +22,276 @@ namespace System.Management.Automation internal static class SpecialVariables { internal const string HistorySize = "MaximumHistoryCount"; + internal static readonly VariablePath HistorySizeVarPath = new VariablePath(HistorySize); internal const string MyInvocation = "MyInvocation"; + internal static readonly VariablePath MyInvocationVarPath = new VariablePath(MyInvocation); internal const string OFS = "OFS"; + 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); #region Logging Variables internal const string LogEngineHealthEvent = "LogEngineHealthEvent"; + internal static readonly VariablePath LogEngineHealthEventVarPath = new VariablePath(LogEngineHealthEvent); internal const string LogEngineLifecycleEvent = "LogEngineLifecycleEvent"; + internal static readonly VariablePath LogEngineLifecycleEventVarPath = new VariablePath(LogEngineLifecycleEvent); internal const string LogCommandHealthEvent = "LogCommandHealthEvent"; + internal static readonly VariablePath LogCommandHealthEventVarPath = new VariablePath(LogCommandHealthEvent); internal const string LogCommandLifecycleEvent = "LogCommandLifecycleEvent"; + internal static readonly VariablePath LogCommandLifecycleEventVarPath = new VariablePath(LogCommandLifecycleEvent); internal const string LogProviderHealthEvent = "LogProviderHealthEvent"; + internal static readonly VariablePath LogProviderHealthEventVarPath = new VariablePath(LogProviderHealthEvent); internal const string LogProviderLifecycleEvent = "LogProviderLifecycleEvent"; + internal static readonly VariablePath LogProviderLifecycleEventVarPath = new VariablePath(LogProviderLifecycleEvent); internal const string LogSettingsEvent = "LogSettingsEvent"; + internal static readonly VariablePath LogSettingsEventVarPath = new VariablePath(LogSettingsEvent); internal const string PSLogUserData = "PSLogUserData"; + internal static readonly VariablePath PSLogUserDataPath = new VariablePath(PSLogUserData); #endregion Logging Variables internal const string NestedPromptLevel = "NestedPromptLevel"; + internal static readonly VariablePath NestedPromptCounterVarPath = new VariablePath("global:" + NestedPromptLevel); internal const string CurrentlyExecutingCommand = "CurrentlyExecutingCommand"; + internal static readonly VariablePath CurrentlyExecutingCommandVarPath = new VariablePath(CurrentlyExecutingCommand); internal const string PSBoundParameters = "PSBoundParameters"; + internal static readonly VariablePath PSBoundParametersVarPath = new VariablePath(PSBoundParameters); internal const string Matches = "Matches"; + internal static readonly VariablePath MatchesVarPath = new VariablePath(Matches); internal const string LastExitCode = "LASTEXITCODE"; + internal static readonly VariablePath LastExitCodeVarPath = new VariablePath("global:" + LastExitCode); internal const string PSDebugContext = "PSDebugContext"; + internal static readonly VariablePath PSDebugContextVarPath = new VariablePath(PSDebugContext); internal const string StackTrace = "StackTrace"; + internal static readonly VariablePath StackTraceVarPath = new VariablePath("global:" + StackTrace); internal const string FirstToken = "^"; + internal static readonly VariablePath FirstTokenVarPath = new VariablePath("global:" + FirstToken); internal const string LastToken = "$"; + internal static readonly VariablePath LastTokenVarPath = new VariablePath("global:" + LastToken); internal static bool IsUnderbar(string name) { return name.Length == 1 && name[0] == '_'; } + internal const string PSItem = "PSItem"; // simple alias for $_ internal const string Underbar = "_"; + internal static readonly VariablePath UnderbarVarPath = new VariablePath(Underbar); internal const string Question = "?"; + internal static readonly VariablePath QuestionVarPath = new VariablePath(Question); internal const string Args = "args"; + internal static readonly VariablePath ArgsVarPath = new VariablePath("local:" + Args); internal const string This = "this"; + internal static readonly VariablePath ThisVarPath = new VariablePath("this"); internal const string Input = "input"; + internal static readonly VariablePath InputVarPath = new VariablePath("local:" + Input); internal const string PSCmdlet = "PSCmdlet"; + internal static readonly VariablePath PSCmdletVarPath = new VariablePath("PSCmdlet"); internal const string Error = "error"; + internal static readonly VariablePath ErrorVarPath = new VariablePath("global:" + Error); internal const string EventError = "error"; - internal static readonly VariablePath EventErrorVarPath = new VariablePath("script:" + EventError); + internal static readonly VariablePath EventErrorVarPath = new VariablePath("script:" + EventError); +#if !UNIX internal const string PathExt = "env:PATHEXT"; - internal static readonly VariablePath PathExtVarPath = new VariablePath(PathExt); + internal static readonly VariablePath PathExtVarPath = new VariablePath(PathExt); +#endif internal const string PSEmailServer = "PSEmailServer"; + internal static readonly VariablePath PSEmailServerVarPath = new VariablePath(PSEmailServer); internal const string PSDefaultParameterValues = "PSDefaultParameterValues"; + internal static readonly VariablePath PSDefaultParameterValuesVarPath = new VariablePath(PSDefaultParameterValues); internal const string PSScriptRoot = "PSScriptRoot"; + internal static readonly VariablePath PSScriptRootVarPath = new VariablePath(PSScriptRoot); internal const string PSCommandPath = "PSCommandPath"; + internal static readonly VariablePath PSCommandPathVarPath = new VariablePath(PSCommandPath); internal const string PSSenderInfo = "PSSenderInfo"; + internal static readonly VariablePath PSSenderInfoVarPath = new VariablePath(PSSenderInfo); internal const string @foreach = "foreach"; + internal static readonly VariablePath foreachVarPath = new VariablePath("local:" + @foreach); internal const string @switch = "switch"; + internal static readonly VariablePath switchVarPath = new VariablePath("local:" + @switch); internal const string pwd = "PWD"; - internal static VariablePath PWDVarPath = new VariablePath("global:" + pwd); + + internal static readonly VariablePath PWDVarPath = new VariablePath("global:" + pwd); internal const string Null = "null"; - internal static VariablePath NullVarPath = new VariablePath("null"); + + internal static readonly VariablePath NullVarPath = new VariablePath("null"); internal const string True = "true"; - internal static VariablePath TrueVarPath = new VariablePath("true"); + + internal static readonly VariablePath TrueVarPath = new VariablePath("true"); internal const string False = "false"; - internal static VariablePath FalseVarPath = new VariablePath("false"); + + internal static readonly VariablePath FalseVarPath = new VariablePath("false"); internal const string PSModuleAutoLoading = "PSModuleAutoLoadingPreference"; - internal static VariablePath PSModuleAutoLoadingPreferenceVarPath = new VariablePath("global:" + PSModuleAutoLoading); + + internal static readonly VariablePath PSModuleAutoLoadingPreferenceVarPath = new VariablePath("global:" + PSModuleAutoLoading); #region Platform Variables + internal const string IsLinux = "IsLinux"; - internal static VariablePath IsLinuxPath = new VariablePath("IsLinux"); + + internal static readonly VariablePath IsLinuxPath = new VariablePath("IsLinux"); internal const string IsMacOS = "IsMacOS"; - internal static VariablePath IsMacOSPath = new VariablePath("IsMacOS"); + + internal static readonly VariablePath IsMacOSPath = new VariablePath("IsMacOS"); internal const string IsWindows = "IsWindows"; - internal static VariablePath IsWindowsPath = new VariablePath("IsWindows"); + + internal static readonly VariablePath IsWindowsPath = new VariablePath("IsWindows"); internal const string IsCoreCLR = "IsCoreCLR"; - internal static VariablePath IsCoreCLRPath = new VariablePath("IsCoreCLR"); + + internal static readonly VariablePath IsCoreCLRPath = new VariablePath("IsCoreCLR"); #endregion + #region Preference Variables internal const string DebugPreference = "DebugPreference"; + internal static readonly VariablePath DebugPreferenceVarPath = new VariablePath(DebugPreference); internal const string ErrorActionPreference = "ErrorActionPreference"; + internal static readonly VariablePath ErrorActionPreferenceVarPath = new VariablePath(ErrorActionPreference); internal const string ProgressPreference = "ProgressPreference"; + internal static readonly VariablePath ProgressPreferenceVarPath = new VariablePath(ProgressPreference); internal const string VerbosePreference = "VerbosePreference"; + internal static readonly VariablePath VerbosePreferenceVarPath = new VariablePath(VerbosePreference); internal const string WarningPreference = "WarningPreference"; + internal static readonly VariablePath WarningPreferenceVarPath = new VariablePath(WarningPreference); internal const string WhatIfPreference = "WhatIfPreference"; + internal static readonly VariablePath WhatIfPreferenceVarPath = new VariablePath(WhatIfPreference); internal const string ConfirmPreference = "ConfirmPreference"; + internal static readonly VariablePath ConfirmPreferenceVarPath = new VariablePath(ConfirmPreference); internal const string InformationPreference = "InformationPreference"; + internal static readonly VariablePath InformationPreferenceVarPath = new VariablePath(InformationPreference); #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); /// - /// shell environment variable + /// Shell environment variable. /// internal const string PSSessionConfigurationName = "PSSessionConfigurationName"; + internal static readonly VariablePath PSSessionConfigurationNameVarPath = new VariablePath("global:" + PSSessionConfigurationName); /// - /// environment variable that will define the default - /// application name for the connection uri + /// Environment variable that will define the default + /// application name for the connection uri. /// internal const string PSSessionApplicationName = "PSSessionApplicationName"; - internal static readonly VariablePath PSSessionApplicationNameVarPath = new VariablePath("global:" + PSSessionApplicationName); + internal static readonly VariablePath PSSessionApplicationNameVarPath = new VariablePath("global:" + PSSessionApplicationName); #region AllScope variables created in every session @@ -227,21 +305,7 @@ internal static class SpecialVariables internal const string PSVersionTable = "PSVersionTable"; internal const string PSEdition = "PSEdition"; internal const string ShellId = "ShellId"; - - - internal static List AllScopeSessionVariables = new List - { - ExecutionContext, - Home, - Host, - PID, - PSCulture, - PSHome, - PSUICulture, - PSVersionTable, - PSEdition, - ShellId - }; + internal const string EnabledExperimentalFeatures = "EnabledExperimentalFeatures"; #endregion AllScope variables created in every session @@ -269,45 +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, - }; + 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 ( @@ -320,6 +392,7 @@ internal static class SpecialVariables SpecialVariables.NestedPromptLevel, SpecialVariables.pwd, SpecialVariables.Matches, + SpecialVariables.PSApplicationOutputEncoding, }, StringComparer.OrdinalIgnoreCase ); @@ -353,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 73326087939..d77e700e90e 100644 --- a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs +++ b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -20,12 +19,12 @@ internal ThirdPartyAdapter(Type adaptedType, PSPropertyAdapter externalAdapter) } /// - /// The type this instance is adapting + /// The type this instance is adapting. /// internal Type AdaptedType { get; } /// - /// The type of the external adapter + /// The type of the external adapter. /// internal Type ExternalAdapterType { @@ -36,7 +35,7 @@ internal Type ExternalAdapterType } /// - /// Returns the TypeNameHierarchy out of an object + /// Returns the TypeNameHierarchy out of an object. /// protected override IEnumerable GetTypeNameHierarchy(object obj) { @@ -129,8 +128,32 @@ protected override PSProperty DoGetProperty(object obj, string propertyName) return property; } + protected override PSProperty DoGetFirstPropertyOrDefault(object obj, MemberNamePredicate predicate) + { + PSAdaptedProperty property = null; + + try + { + property = _externalAdapter.GetFirstPropertyOrDefault(obj, predicate); + } + catch (Exception exception) + { + throw new ExtendedTypeSystemException( + "PSPropertyAdapter.GetProperty", + exception, + ExtendedTypeSystem.GetProperty, nameof(predicate), obj.ToString()); + } + + if (property != null) + { + InitializeProperty(property, obj); + } + + return property; + } + /// - /// Ensures that the adapter and base object are set in the given PSAdaptedProperty + /// Ensures that the adapter and base object are set in the given PSAdaptedProperty. /// private void InitializeProperty(PSAdaptedProperty property, object baseObject) { @@ -142,7 +165,7 @@ private void InitializeProperty(PSAdaptedProperty property, object baseObject) } /// - /// Returns true if the property is settable + /// Returns true if the property is settable. /// protected override bool PropertyIsSettable(PSProperty property) { @@ -164,7 +187,7 @@ protected override bool PropertyIsSettable(PSProperty property) } /// - /// Returns true if the property is gettable + /// Returns true if the property is gettable. /// protected override bool PropertyIsGettable(PSProperty property) { @@ -186,7 +209,7 @@ protected override bool PropertyIsGettable(PSProperty property) } /// - /// Returns the value from a property coming from a previous call to DoGetProperty + /// Returns the value from a property coming from a previous call to DoGetProperty. /// protected override object PropertyGet(PSProperty property) { @@ -208,7 +231,7 @@ protected override object PropertyGet(PSProperty property) } /// - /// Sets the value of a property coming from a previous call to DoGetProperty + /// Sets the value of a property coming from a previous call to DoGetProperty. /// protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible) { @@ -231,7 +254,7 @@ protected override void PropertySet(PSProperty property, object setValue, bool c } /// - /// Returns the name of the type corresponding to the property + /// Returns the name of the type corresponding to the property. /// protected override string PropertyType(PSProperty property, bool forDisplay) { @@ -256,11 +279,11 @@ protected override string PropertyType(PSProperty property, bool forDisplay) return propertyTypeName ?? "System.Object"; } - private PSPropertyAdapter _externalAdapter; + private readonly PSPropertyAdapter _externalAdapter; } /// - /// User-defined property adapter + /// User-defined property adapter. /// /// /// This class is used to expose a simplified version of the type adapter API @@ -268,19 +291,16 @@ protected override string PropertyType(PSProperty property, bool forDisplay) public abstract class PSPropertyAdapter { /// - /// Returns the type hierarchy for the given object + /// Returns the type hierarchy for the given object. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "object")] public virtual Collection GetTypeNameHierarchy(object baseObject) { - if (baseObject == null) - { - throw new ArgumentNullException("baseObject"); - } + ArgumentNullException.ThrowIfNull(baseObject); - Collection types = new Collection(); + Collection types = new Collection(); - for (Type type = baseObject.GetType(); type != null; type = type.GetTypeInfo().BaseType) + for (Type type = baseObject.GetType(); type != null; type = type.BaseType) { types.Add(type.FullName); } @@ -289,40 +309,57 @@ public virtual Collection GetTypeNameHierarchy(object baseObject) } /// - /// Returns a list of the adapted properties + /// Returns a list of the adapted properties. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "object")] public abstract Collection GetProperties(object baseObject); /// - /// Returns a specific property, or null if the base object does not contain the given property + /// Returns a specific property, or null if the base object does not contain the given property. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "object")] public abstract PSAdaptedProperty GetProperty(object baseObject, string propertyName); /// - /// Returns true if the given property is settable + /// Returns true if the given property is settable. /// public abstract bool IsSettable(PSAdaptedProperty adaptedProperty); /// - /// Returns true if the given property is gettable + /// Returns true if the given property is gettable. /// public abstract bool IsGettable(PSAdaptedProperty adaptedProperty); /// - /// Returns the value of a given property + /// Returns the value of a given property. /// public abstract object GetPropertyValue(PSAdaptedProperty adaptedProperty); /// - /// Sets the value of a given property + /// Sets the value of a given property. /// public abstract void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value); /// - /// Returns the type for a given property + /// Returns the type for a given property. /// public abstract string GetPropertyTypeName(PSAdaptedProperty adaptedProperty); + + /// + /// Returns a property if it's name matches the specified , otherwise null. + /// + /// An adapted property if the predicate matches, or . + public virtual PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) + { + foreach (var property in GetProperties(baseObject)) + { + if (predicate(property.Name)) + { + return property; + } + } + + return null; + } } } diff --git a/src/System.Management.Automation/engine/TransactedString.cs b/src/System.Management.Automation/engine/TransactedString.cs index 73f67018219..05eab93d198 100644 --- a/src/System.Management.Automation/engine/TransactedString.cs +++ b/src/System.Management.Automation/engine/TransactedString.cs @@ -1,15 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Transactions; using System.Text; +using System.Transactions; 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 { @@ -20,17 +19,16 @@ public class TransactedString : IEnlistmentNotification /// /// Constructor for the TransactedString class. /// - public TransactedString() : this("") + 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); @@ -65,6 +63,7 @@ void IEnlistmentNotification.InDoubt(Enlistment enlistment) { enlistment.Done(); } + void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) { preparingEnlistment.Prepared(); @@ -72,11 +71,10 @@ void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) /// /// Append text to the transacted string. - /// + /// /// /// The text to append. /// - /// public void Append(string text) { ValidateTransactionOrEnlist(); @@ -93,14 +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(); @@ -197,5 +194,4 @@ private void ValidateTransactionOrEnlist() } } } -} // namespace Microsoft.Test.Management.Automation - +} diff --git a/src/System.Management.Automation/engine/TransactionManager.cs b/src/System.Management.Automation/engine/TransactionManager.cs index 3abeb718c57..e77ffebedb4 100644 --- a/src/System.Management.Automation/engine/TransactionManager.cs +++ b/src/System.Management.Automation/engine/TransactionManager.cs @@ -1,8 +1,7 @@ -#pragma warning disable 1634, 1691 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#pragma warning disable 1634, 1691 using System.Collections.Generic; using System.Transactions; @@ -16,31 +15,29 @@ namespace System.Management.Automation public enum PSTransactionStatus { /// - /// The transaction has been rolled back + /// The transaction has been rolled back. /// RolledBack = 0, /// - /// The transaction has been committed + /// The transaction has been committed. /// Committed = 1, /// - /// The transaction is currently active + /// The transaction is currently active. /// Active = 2 } /// - /// Represents an active transaction + /// Represents an active transaction. /// - /// public sealed class PSTransaction : IDisposable { /// - /// Initializes a new instance of the PSTransaction class + /// Initializes a new instance of the PSTransaction class. /// - /// internal PSTransaction(RollbackSeverity rollbackPreference, TimeSpan timeout) { _transaction = new CommittableTransaction(timeout); @@ -49,9 +46,8 @@ internal PSTransaction(RollbackSeverity rollbackPreference, TimeSpan timeout) } /// - /// Initializes a new instance of the PSTransaction class using a CommittableTransaction + /// Initializes a new instance of the PSTransaction class using a CommittableTransaction. /// - /// internal PSTransaction(CommittableTransaction transaction, RollbackSeverity severity) { _transaction = transaction; @@ -62,15 +58,13 @@ internal PSTransaction(CommittableTransaction transaction, RollbackSeverity seve private CommittableTransaction _transaction; /// - /// Gets the rollback preference for this transaction + /// Gets the rollback preference for this transaction. /// - /// public RollbackSeverity RollbackPreference { get; } /// - /// Gets the number of subscribers to this transaction + /// Gets the number of subscribers to this transaction. /// - /// public int SubscriberCount { get @@ -83,14 +77,15 @@ public int SubscriberCount return _subscriberCount; } + set { _subscriberCount = value; } } + private int _subscriberCount; /// /// Returns the status of this transaction. /// - /// public PSTransactionStatus Status { get @@ -111,18 +106,16 @@ public PSTransactionStatus Status } /// - /// Activates the transaction held by this PSTransaction + /// Activates the transaction held by this PSTransaction. /// - /// internal void Activate() { Transaction.Current = _transaction; } /// - /// Commits the transaction held by this PSTransaction + /// Commits the transaction held by this PSTransaction. /// - /// internal void Commit() { _transaction.Commit(); @@ -130,9 +123,8 @@ internal void Commit() } /// - /// Rolls back the transaction held by this PSTransaction + /// Rolls back the transaction held by this PSTransaction. /// - /// internal void Rollback() { _transaction.Rollback(); @@ -143,7 +135,6 @@ internal void Rollback() /// Determines whether this PSTransaction has been /// rolled back or not. /// - /// internal bool IsRolledBack { get @@ -159,24 +150,24 @@ internal bool IsRolledBack return _isRolledBack; } + set { _isRolledBack = value; } } + private bool _isRolledBack = false; /// /// Determines whether this PSTransaction /// has been committed or not. /// - /// internal bool IsCommitted { get; set; } = false; /// - /// Destructor for the PSTransaction class + /// Destructor for the PSTransaction class. /// - /// ~PSTransaction() { Dispose(false); @@ -185,7 +176,6 @@ internal bool IsRolledBack /// /// Disposes the PSTransaction object. /// - /// public void Dispose() { Dispose(true); @@ -195,12 +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) @@ -214,26 +202,24 @@ public void Dispose(bool disposing) } /// - /// Supports the transaction management infrastructure for the PowerShell engine + /// Supports the transaction management infrastructure for the PowerShell engine. /// - /// public sealed class PSTransactionContext : IDisposable { /// - /// Initializes a new instance of the PSTransactionManager class + /// Initializes a new instance of the PSTransactionManager class. /// - /// internal PSTransactionContext(PSTransactionManager transactionManager) { _transactionManager = transactionManager; transactionManager.SetActive(); } + private PSTransactionManager _transactionManager; /// - /// Destructor for the PSTransactionManager class + /// Destructor for the PSTransactionManager class. /// - /// ~PSTransactionContext() { Dispose(false); @@ -242,7 +228,6 @@ internal PSTransactionContext(PSTransactionManager transactionManager) /// /// Disposes the PSTransactionContext object. /// - /// public void Dispose() { Dispose(true); @@ -252,12 +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) @@ -274,17 +257,17 @@ private void Dispose(bool disposing) public enum RollbackSeverity { /// - /// Non-terminating errors or worse + /// Non-terminating errors or worse. /// Error, /// - /// Terminating errors or worse + /// Terminating errors or worse. /// TerminatingError, /// - /// Do not rollback the transaction on error + /// Do not rollback the transaction on error. /// Never } @@ -293,15 +276,13 @@ public enum RollbackSeverity namespace System.Management.Automation.Internal { /// - /// Supports the transaction management infrastructure for the PowerShell engine + /// Supports the transaction management infrastructure for the PowerShell engine. /// - /// internal sealed class PSTransactionManager : IDisposable { /// - /// Initializes a new instance of the PSTransactionManager class + /// Initializes a new instance of the PSTransactionManager class. /// - /// internal PSTransactionManager() { _transactionStack = new Stack(); @@ -312,7 +293,6 @@ internal PSTransactionManager() /// Called by engine APIs to ensure they are protected from /// ambient transactions. /// - /// internal static IDisposable GetEngineProtectionScope() { if (s_engineProtectionEnabled && (Transaction.Current != null)) @@ -331,17 +311,16 @@ internal static IDisposable GetEngineProtectionScope() /// protection the first time a transaction is activated. /// Engine protection APIs remain protected from this point on. /// - /// internal static void EnableEngineProtection() { s_engineProtectionEnabled = true; } + private static bool s_engineProtectionEnabled = false; /// - /// Gets the rollback preference for the active transaction + /// Gets the rollback preference for the active transaction. /// - /// internal RollbackSeverity RollbackPreference { get @@ -366,7 +345,6 @@ internal RollbackSeverity RollbackPreference /// Creates a new Transaction if none are active. Otherwise, increments /// the subscriber count for the active transaction. /// - /// internal void CreateOrJoin() { CreateOrJoin(RollbackSeverity.Error, TimeSpan.FromMinutes(1)); @@ -376,7 +354,6 @@ internal void CreateOrJoin() /// Creates a new Transaction if none are active. Otherwise, increments /// the subscriber count for the active transaction. /// - /// internal void CreateOrJoin(RollbackSeverity rollbackPreference, TimeSpan timeout) { PSTransaction currentTransaction = _transactionStack.Peek(); @@ -411,7 +388,6 @@ internal void CreateOrJoin(RollbackSeverity rollbackPreference, TimeSpan timeout /// Creates a new Transaction that should be managed independently of /// any parent transactions. /// - /// internal void CreateNew() { CreateNew(RollbackSeverity.Error, TimeSpan.FromMinutes(1)); @@ -421,7 +397,6 @@ internal void CreateNew() /// Creates a new Transaction that should be managed independently of /// any parent transactions. /// - /// internal void CreateNew(RollbackSeverity rollbackPreference, TimeSpan timeout) { _transactionStack.Push(new PSTransaction(rollbackPreference, timeout)); @@ -431,7 +406,6 @@ internal void CreateNew(RollbackSeverity rollbackPreference, TimeSpan timeout) /// Completes the current transaction. If only one subscriber is active, this /// commits the transaction. Otherwise, it reduces the subscriber count by one. /// - /// internal void Commit() { PSTransaction currentTransaction = _transactionStack.Peek(); @@ -478,7 +452,6 @@ internal void Commit() /// /// Aborts the current transaction, no matter how many subscribers are part of it. /// - /// internal void Rollback() { Rollback(false); @@ -487,7 +460,6 @@ internal void Rollback() /// /// Aborts the current transaction, no matter how many subscribers are part of it. /// - /// internal void Rollback(bool suppressErrors) { PSTransaction currentTransaction = _transactionStack.Peek(); @@ -533,9 +505,8 @@ internal void Rollback(bool suppressErrors) } /// - /// Sets the base transaction; any transactions created thereafter will be nested to this instance + /// Sets the base transaction; any transactions created thereafter will be nested to this instance. /// - /// internal void SetBaseTransaction(CommittableTransaction transaction, RollbackSeverity severity) { if (this.HasTransaction) @@ -557,9 +528,8 @@ internal void SetBaseTransaction(CommittableTransaction transaction, RollbackSev } /// - /// Removes the transaction added by SetBaseTransaction + /// Removes the transaction added by SetBaseTransaction. /// - /// internal void ClearBaseTransaction() { if (_baseTransaction == null) @@ -580,9 +550,8 @@ internal void ClearBaseTransaction() private PSTransaction _baseTransaction; /// - /// Returns the current engine transaction + /// Returns the current engine transaction. /// - /// internal PSTransaction GetCurrent() { return _transactionStack.Peek(); @@ -591,7 +560,6 @@ internal PSTransaction GetCurrent() /// /// Activates the current transaction, both in the engine, and in the Ambient. /// - /// internal void SetActive() { PSTransactionManager.EnableEngineProtection(); @@ -616,13 +584,13 @@ internal void SetActive() _previousActiveTransaction = Transaction.Current; currentTransaction.Activate(); } + private Transaction _previousActiveTransaction; /// /// Deactivates the current transaction in the engine, and restores the /// ambient transaction. /// - /// internal void ResetActive() { // Even if you are in a transaction that has been aborted, you @@ -635,7 +603,6 @@ internal void ResetActive() /// /// Determines if you have a transaction that you can set active and work on. /// - /// internal bool HasTransaction { get @@ -696,9 +663,8 @@ internal bool IsLastTransactionRolledBack } /// - /// Destructor for the PSTransactionManager class + /// Destructor for the PSTransactionManager class. /// - /// ~PSTransactionManager() { Dispose(false); @@ -707,7 +673,6 @@ internal bool IsLastTransactionRolledBack /// /// Disposes the PSTransactionManager object. /// - /// public void Dispose() { Dispose(true); @@ -717,12 +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) { @@ -743,5 +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 ad6df66cadc..926b07fb15a 100644 --- a/src/System.Management.Automation/engine/TypeMetadata.cs +++ b/src/System.Management.Automation/engine/TypeMetadata.cs @@ -1,15 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using System.Management.Automation.Language; using System.Reflection; + using Microsoft.PowerShell; + using Dbg = System.Diagnostics.Debug; -using System.Management.Automation.Language; namespace System.Management.Automation { @@ -34,24 +35,23 @@ public sealed class ParameterSetMetadata #region Constructor /// - /// /// /// internal ParameterSetMetadata(ParameterSetSpecificMetadata psMD) { - Dbg.Assert(null != psMD, "ParameterSetSpecificMetadata cannot be null"); + Dbg.Assert(psMD != null, "ParameterSetSpecificMetadata cannot be null"); Initialize(psMD); } /// /// A copy constructor that creates a deep copy of the ParameterSetMetadata object. /// - /// object to copy + /// Object to copy. internal ParameterSetMetadata(ParameterSetMetadata other) { if (other == null) { - throw PSTraceSource.NewArgumentNullException("other"); + throw PSTraceSource.NewArgumentNullException(nameof(other)); } _helpMessage = other._helpMessage; @@ -96,6 +96,7 @@ public int Position { return _position; } + set { _position = value; @@ -111,6 +112,7 @@ public bool ValueFromPipeline { return _valueFromPipeline; } + set { _valueFromPipeline = value; @@ -127,6 +129,7 @@ public bool ValueFromPipelineByPropertyName { return _valueFromPipelineByPropertyName; } + set { _valueFromPipelineByPropertyName = value; @@ -135,7 +138,7 @@ public bool ValueFromPipelineByPropertyName /// /// Specifies if this parameter takes all the remaining unbound - /// arguments that were specified + /// arguments that were specified. /// /// public bool ValueFromRemainingArguments @@ -144,6 +147,7 @@ public bool ValueFromRemainingArguments { return _valueFromRemainingArguments; } + set { _valueFromRemainingArguments = value; @@ -159,6 +163,7 @@ public string HelpMessage { return _helpMessage; } + set { _helpMessage = value; @@ -174,6 +179,7 @@ public string HelpMessageBaseName { return _helpMessageBaseName; } + set { _helpMessageBaseName = value; @@ -189,6 +195,7 @@ public string HelpMessageResourceId { return _helpMessageResourceId; } + set { _helpMessageResourceId = value; @@ -200,7 +207,6 @@ public string HelpMessageResourceId #region Private / Internal Methods & Properties /// - /// /// /// internal void Initialize(ParameterSetSpecificMetadata psMD) @@ -259,23 +265,28 @@ internal ParameterFlags Flags get { ParameterFlags flags = 0; - if (IsMandatory) { flags = flags | ParameterFlags.Mandatory; } - if (ValueFromPipeline) { flags = flags | ParameterFlags.ValueFromPipeline; } - if (ValueFromPipelineByPropertyName) { flags = flags | ParameterFlags.ValueFromPipelineByPropertyName; } - if (ValueFromRemainingArguments) { flags = flags | ParameterFlags.ValueFromRemainingArguments; } + if (IsMandatory) { flags |= ParameterFlags.Mandatory; } + + if (ValueFromPipeline) { flags |= ParameterFlags.ValueFromPipeline; } + + if (ValueFromPipelineByPropertyName) { flags |= ParameterFlags.ValueFromPipelineByPropertyName; } + + 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); } } /// - /// Constructor used by rehydration + /// Constructor used by rehydration. /// internal ParameterSetMetadata( int position, @@ -299,13 +310,12 @@ internal ParameterSetMetadata( private const string HelpMessageFormat = @"{0}HelpMessage='{1}'"; /// - /// /// /// internal string GetProxyParameterData() { Text.StringBuilder result = new System.Text.StringBuilder(); - string prefix = ""; + string prefix = string.Empty; if (_isMandatory) { @@ -401,7 +411,7 @@ public ParameterMetadata(string name, Type parameterType) { if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } _name = name; @@ -416,12 +426,12 @@ public ParameterMetadata(string name, Type parameterType) /// A copy constructor that creates a deep copy of the ParameterMetadata object. /// Instances of Attribute and Type classes are copied by reference. /// - /// object to copy + /// Object to copy. public ParameterMetadata(ParameterMetadata other) { if (other == null) { - throw PSTraceSource.NewArgumentNullException("other"); + throw PSTraceSource.NewArgumentNullException(nameof(other)); } _isDynamic = other._isDynamic; @@ -468,21 +478,21 @@ public ParameterMetadata(ParameterMetadata other) /// /// An internal constructor which constructs a ParameterMetadata object /// from compiled command parameter metadata. ParameterMetadata - /// is a proxy written on top of CompiledCommandParameter + /// is a proxy written on top of CompiledCommandParameter. /// /// /// Internal CompiledCommandParameter metadata /// internal ParameterMetadata(CompiledCommandParameter cmdParameterMD) { - Dbg.Assert(null != cmdParameterMD, + Dbg.Assert(cmdParameterMD != null, "CompiledCommandParameter cannot be null"); Initialize(cmdParameterMD); } /// - /// Constructor used by implicit remoting + /// Constructor used by implicit remoting. /// internal ParameterMetadata( Collection aliases, @@ -504,10 +514,9 @@ internal ParameterMetadata( #region Public Methods/Properties /// - /// Gets the name of the parameter + /// Gets the name of the parameter. /// - /// - public String Name + public string Name { get { @@ -553,15 +562,16 @@ public Dictionary ParameterSets } /// - /// Specifies if the parameter is Dynamic + /// Specifies if the parameter is Dynamic. /// public bool IsDynamic { get { return _isDynamic; } + set { _isDynamic = value; } } /// - /// Specifies the alias names for this parameter + /// Specifies the alias names for this parameter. /// public Collection Aliases { @@ -583,7 +593,7 @@ public Collection Attributes } /// - /// Specifies if the parameter is a SwitchParameter + /// Specifies if the parameter is a SwitchParameter. /// public bool SwitchParameter { @@ -613,9 +623,9 @@ public bool SwitchParameter /// public static Dictionary GetParameterMetadata(Type type) { - if (null == type) + if (type == null) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } CommandMetadata cmdMetaData = new CommandMetadata(type); @@ -630,7 +640,6 @@ public static Dictionary GetParameterMetadata(Type ty #region Internal Methods/Properties /// - /// /// /// internal void Initialize(CompiledCommandParameter compiledParameterMD) @@ -663,14 +672,13 @@ internal void Initialize(CompiledCommandParameter compiledParameterMD) } /// - /// /// /// /// internal static Dictionary GetParameterMetadata(MergedCommandParameterMetadata cmdParameterMetadata) { - Dbg.Assert(null != cmdParameterMetadata, "cmdParameterMetadata cannot be null"); + Dbg.Assert(cmdParameterMetadata != null, "cmdParameterMetadata cannot be null"); Dictionary result = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -698,6 +706,7 @@ internal bool IsMatchingType(PSTypeName psTypeName) { return parameterAcceptsObjects; } + if (parameterAcceptsObjects) { return (psTypeName.Type != null) && (psTypeName.Type.Equals(typeof(object))); @@ -717,16 +726,18 @@ internal bool IsMatchingType(PSTypeName psTypeName) } var wildcardPattern = WildcardPattern.Get( - "*" + (psTypeName.Name ?? ""), + "*" + (psTypeName.Name ?? string.Empty), WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); if (wildcardPattern.IsMatch(this.ParameterType.FullName)) { return true; } + if (this.ParameterType.IsArray && wildcardPattern.IsMatch((this.ParameterType.GetElementType().FullName))) { return true; } + if (this.Attributes != null) { PSTypeNameAttribute typeNameAttribute = this.Attributes.OfType().FirstOrDefault(); @@ -735,6 +746,7 @@ internal bool IsMatchingType(PSTypeName psTypeName) return true; } } + return false; } @@ -748,6 +760,8 @@ internal bool IsMatchingType(PSTypeName psTypeName) private const string ParameterSetNameFormat = "ParameterSetName='{0}'"; 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}')]"; @@ -756,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()]"; @@ -764,7 +779,6 @@ internal bool IsMatchingType(PSTypeName psTypeName) private const string CredentialAttributeFormat = @"{0}[System.Management.Automation.CredentialAttribute()]"; /// - /// /// /// /// prefix that is added to every new-line. Used for tabbing content. @@ -789,7 +803,7 @@ internal string GetProxyParameterData(string prefix, string paramNameOverride, b string paramSetData = parameterSet.GetProxyParameterData(); if (!string.IsNullOrEmpty(paramSetData) || !parameterSetName.Equals(ParameterAttribute.AllParameterSets)) { - string separator = ""; + string separator = string.Empty; result.Append(prefix); result.Append("[Parameter("); if (!parameterSetName.Equals(ParameterAttribute.AllParameterSets)) @@ -800,11 +814,13 @@ internal string GetProxyParameterData(string prefix, string paramNameOverride, b CodeGeneration.EscapeSingleQuotedStringContent(parameterSetName)); separator = ", "; } + if (!string.IsNullOrEmpty(paramSetData)) { result.Append(separator); result.Append(paramSetData); } + result.Append(")]"); } } @@ -813,7 +829,7 @@ internal string GetProxyParameterData(string prefix, string paramNameOverride, b if ((_aliases != null) && (_aliases.Count > 0)) { Text.StringBuilder aliasesData = new System.Text.StringBuilder(); - string comma = ""; // comma is not need for the first element + string comma = string.Empty; // comma is not need for the first element foreach (string alias in _aliases) { @@ -881,37 +897,60 @@ 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; ValidateLengthAttribute validLengthAttrib = attrib as ValidateLengthAttribute; if (validLengthAttrib != null) { - result = string.Format(CultureInfo.InvariantCulture, + result = string.Format( + CultureInfo.InvariantCulture, ValidateLengthFormat, prefix, - validLengthAttrib.MinLength, validLengthAttrib.MaxLength); + validLengthAttrib.MinLength, + validLengthAttrib.MaxLength); return result; } ValidateRangeAttribute validRangeAttrib = attrib as ValidateRangeAttribute; if (validRangeAttrib != null) { - Type rangeType = validRangeAttrib.MinRange.GetType(); - string format; - - if (rangeType == typeof(float) || rangeType == typeof(double)) + if (validRangeAttrib.RangeKind.HasValue) { - format = ValidateRangeFloatFormat; + result = string.Format( + CultureInfo.InvariantCulture, + ValidateRangeRangeKindFormat, + prefix, + validRangeAttrib.RangeKind.ToString()); + return result; } else { - format = ValidateRangeFormat; + Type rangeType = validRangeAttrib.MinRange.GetType(); + string format; + + if (rangeType == typeof(float) || rangeType == typeof(double)) + { + format = ValidateRangeFloatFormat; + } + else if (rangeType.IsEnum) + { + format = ValidateRangeEnumFormat; + } + else + { + format = ValidateRangeFormat; + } + + result = string.Format( + CultureInfo.InvariantCulture, + format, + prefix, + validRangeAttrib.MinRange, + validRangeAttrib.MaxRange, + rangeType.FullName); + return result; } - result = string.Format(CultureInfo.InvariantCulture, - format, prefix, - validRangeAttrib.MinRange, validRangeAttrib.MaxRange); - return result; } AllowNullAttribute allowNullAttrib = attrib as AllowNullAttribute; @@ -943,10 +982,10 @@ private string GetProxyAttributeData(Attribute attrib, string prefix) { /* TODO: Validate Pattern dont support Options in ScriptCmdletText. StringBuilder regexOps = new System.Text.StringBuilder(); - string or = ""; - string[] regexOptionEnumValues = Enum.GetNames(typeof(System.Text.RegularExpressions.RegexOptions)); + string or = string.Empty; + 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), @@ -993,11 +1032,19 @@ 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) { Text.StringBuilder values = new System.Text.StringBuilder(); - string comma = ""; + string comma = string.Empty; foreach (string validValue in setAttrib.ValidValues) { values.AppendFormat( @@ -1065,9 +1112,8 @@ private string GetProxyAttributeData(Attribute attrib, string prefix) } /// - /// The metadata associated with a bindable type + /// The metadata associated with a bindable type. /// - /// internal class InternalParameterMetadata { #region ctor @@ -1075,33 +1121,26 @@ internal class InternalParameterMetadata /// /// Gets or constructs an instance of the InternalParameterMetadata for the specified runtime-defined parameters. /// - /// /// /// The runtime-defined parameter collection that describes the parameters and their metadata. /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// Check for reserved parameter names. /// - /// /// /// An instance of the TypeMetadata for the specified runtime-defined parameters. The metadata /// is always constructed on demand and never cached. /// - /// /// /// If is null. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal static InternalParameterMetadata Get(RuntimeDefinedParameterDictionary runtimeDefinedParameters, bool processingDynamicParameters, bool checkNames) @@ -1117,38 +1156,31 @@ internal static InternalParameterMetadata Get(RuntimeDefinedParameterDictionary /// /// Gets or constructs an instance of the InternalParameterMetadata for the specified type. /// - /// /// /// The type to get the metadata for. /// - /// /// /// The current engine context. /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// An instance of the TypeMetadata for the specified type. The metadata may get /// constructed on-demand or may be retrieved from the cache. /// - /// /// /// If is null. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal static InternalParameterMetadata Get(Type type, ExecutionContext context, bool processingDynamicParameters) { if (type == null) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } InternalParameterMetadata result; @@ -1161,43 +1193,39 @@ internal static InternalParameterMetadata Get(Type type, ExecutionContext contex s_parameterMetadataCache.TryAdd(type.AssemblyQualifiedName, result); } } + return result; - } // GetMetadata + } // /// /// Constructs an instance of the InternalParameterMetadata using the metadata in the /// runtime-defined parameter collection. /// - /// /// /// The collection of runtime-defined parameters that declare the parameters and their /// metadata. /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// Check if the parameter name has been reserved. /// - /// /// /// If is null. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal InternalParameterMetadata(RuntimeDefinedParameterDictionary runtimeDefinedParameters, bool processingDynamicParameters, bool checkNames) { if (runtimeDefinedParameters == null) { - throw PSTraceSource.NewArgumentNullException("runtimeDefinedParameters"); + throw PSTraceSource.NewArgumentNullException(nameof(runtimeDefinedParameters)); } + ConstructCompiledParametersUsingRuntimeDefinedParameters(runtimeDefinedParameters, processingDynamicParameters, checkNames); } @@ -1206,29 +1234,24 @@ internal InternalParameterMetadata(RuntimeDefinedParameterDictionary runtimeDefi /// Constructs an instance of the InternalParameterMetadata using the reflection information retrieved /// from the enclosing bindable object type. /// - /// /// /// The type information for the bindable object /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// If is null. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// internal InternalParameterMetadata(Type type, bool processingDynamicParameters) { if (type == null) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } _type = type; @@ -1240,26 +1263,25 @@ internal InternalParameterMetadata(Type type, bool processingDynamicParameters) #endregion ctor /// - /// Gets the type name of the bindable type + /// Gets the type name of the bindable type. /// - /// - internal string TypeName { get; } = String.Empty; + internal string TypeName { get; } = string.Empty; /// /// Gets a dictionary of the compiled parameter metadata for this Type. /// The dictionary keys are the names of the parameters (or aliases) and /// the values are the compiled parameter metadata. /// - /// internal Dictionary BindableParameters { get; } + = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Gets a dictionary of the parameters that have been aliased to other names. The key is /// the alias name and the value is the CompiledCommandParameter metadata. /// - /// internal Dictionary AliasedParameters { get; } + = new Dictionary(StringComparer.OrdinalIgnoreCase); /// @@ -1267,36 +1289,31 @@ 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 + /// The flags used when reflecting against the object to create the metadata. /// internal static readonly BindingFlags metaDataBindingFlags = (BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); #region helper methods /// - /// Fills in the data for an instance of this class using the specified runtime-defined parameters + /// Fills in the data for an instance of this class using the specified runtime-defined parameters. /// - /// /// /// A description of the parameters and their metadata. /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// Check if the parameter name has been reserved. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// private void ConstructCompiledParametersUsingRuntimeDefinedParameters( RuntimeDefinedParameterDictionary runtimeDefinedParameters, bool processingDynamicParameters, @@ -1309,30 +1326,28 @@ private void ConstructCompiledParametersUsingRuntimeDefinedParameters( foreach (RuntimeDefinedParameter parameterDefinition in runtimeDefinedParameters.Values) { // Create the compiled parameter and add it to the bindable parameters collection - - // NTRAID#Windows Out Of Band Releases-926374-2005/12/22-JonN - if (null == parameterDefinition) - continue; + if (processingDynamicParameters) + { + // When processing dynamic parameters, parameter definitions come from the user, + // Invalid data could be passed in, or the parameter could be actually disabled. + if (parameterDefinition == null || parameterDefinition.IsDisabled()) { continue; } + } CompiledCommandParameter parameter = new CompiledCommandParameter(parameterDefinition, processingDynamicParameters); AddParameter(parameter, checkNames); } - } // ConstructCompiledParametersUsingRuntimeDefinedParameters - + } /// /// Compiles the parameter using reflection against the CLR type. /// - /// /// /// True if dynamic parameters are being processed, or false otherwise. /// - /// /// /// If a parameter defines the same parameter-set name multiple times. /// If the attributes could not be read from a property or field. /// - /// private void ConstructCompiledParametersUsingReflection(bool processingDynamicParameters) { Diagnostics.Assert( @@ -1365,9 +1380,9 @@ private void ConstructCompiledParametersUsingReflection(bool processingDynamicPa AddParameter(field, processingDynamicParameters); } - } // ConstructCompiledParametersUsingReflection + } - private void CheckForReservedParameter(string name) + private static void CheckForReservedParameter(string name) { if (name.Equals("SelectProperty", StringComparison.OrdinalIgnoreCase) || @@ -1381,7 +1396,6 @@ private void CheckForReservedParameter(string name) } } - // NTRAID#Windows Out Of Band Releases-906345-2005/06/30-JeffJon // This call verifies that the parameter is unique or // can be deemed unique. If not, an exception is thrown. // If it is unique (or deemed unique), then it is added @@ -1460,7 +1474,6 @@ private void AddParameter(CompiledCommandParameter parameter, bool checkNames) foreach (string alias in parameter.Aliases) { - // NTRAID#Windows Out Of Band Releases-917356-JonN if (AliasedParameters.ContainsKey(alias)) { throw new MetadataException( @@ -1469,6 +1482,7 @@ private void AddParameter(CompiledCommandParameter parameter, bool checkNames) DiscoveryExceptions.AliasDeclaredMultipleTimes, alias); } + AliasedParameters.Add(alias, parameter); } } @@ -1486,33 +1500,36 @@ private void RemoveParameter(CompiledCommandParameter parameter) } /// - /// Determines if the specified member represents a parameter based on its attributes + /// Determines if the specified member represents a parameter based on its attributes. /// - /// /// /// The member to check to see if it is a parameter. /// - /// /// /// True if at least one ParameterAttribute is declared on the member, or false otherwise. /// - /// /// /// If GetCustomAttributes fails on . /// - /// private static bool IsMemberAParameter(MemberInfo member) { - bool result = false; - try { - // MemberInfo.GetCustomAttributes returns IEnumerable in CoreCLR - var attributes = member.GetCustomAttributes(typeof(ParameterAttribute), false); - if (attributes.Any()) + var expAttribute = member.GetCustomAttributes(inherit: false).FirstOrDefault(); + if (expAttribute != null && expAttribute.ToHide) { return false; } + + var hasAnyVisibleParamAttributes = false; + var paramAttributes = member.GetCustomAttributes(inherit: false); + foreach (var paramAttribute in paramAttributes) { - result = true; + if (!paramAttribute.ToHide) + { + hasAnyVisibleParamAttributes = true; + break; + } } + + return hasAnyVisibleParamAttributes; } catch (MetadataException metadataException) { @@ -1532,9 +1549,7 @@ private static bool IsMemberAParameter(MemberInfo member) member.Name, argumentException.Message); } - - return result; - } // IsMemberAParameter + } #endregion helper methods @@ -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 - } // CompiledCommandParameter + } } - diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index 3b9f958bf92..a0eead95e23 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; using System.Collections.Generic; @@ -16,9 +15,10 @@ using System.Reflection; using System.Runtime.Serialization; using System.Security; +using System.Security.Permissions; using System.Xml; + using Dbg = System.Diagnostics.Debug; -using System.Security.Permissions; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -51,6 +51,7 @@ private string ReadElementString(string nodeName) _context.AddError(_readerLineInfo.LineNumber, TypesXmlStrings.NodeShouldHaveInnerText, nodeName); return null; } + if (!_reader.IsEmptyElement) { _reader.Read(); @@ -62,11 +63,13 @@ private string ReadElementString(string nodeName) break; } } + if (_reader.NodeType != XmlNodeType.EndElement) { _context.AddError(_readerLineInfo.LineNumber, TypesXmlStrings.NodeShouldHaveInnerText, nodeName); return null; } + result = result.Trim(); _reader.Read(); } @@ -74,11 +77,13 @@ private string ReadElementString(string nodeName) { _reader.Read(); } + if (string.IsNullOrWhiteSpace(result)) { _context.AddError(_readerLineInfo.LineNumber, TypesXmlStrings.NodeShouldHaveInnerText, nodeName); return null; } + return result; } @@ -92,6 +97,7 @@ private string ReadElementString(string nodeName) { isHidden = ToBoolean(_reader.Value); } + // Unknown attributes are ignored. } @@ -109,11 +115,18 @@ private void ReadIsHiddenAttributeNotSupported(string node) private void ReadEndElement() { while (_reader.NodeType == XmlNodeType.Whitespace) + { _reader.Skip(); + } + if (_reader.NodeType == XmlNodeType.None) + { _reader.Skip(); + } else + { _reader.ReadEndElement(); + } } private void UnknownNode(string node, string expectedNodes) @@ -126,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)) + if (_reader.IsStartElement() && _reader.LocalName.Equals(nodeName)) { - SkipUntillNodeEnd(nodeName); + SkipUntilNodeEnd(nodeName); } else if ((_reader.NodeType == XmlNodeType.EndElement) && _reader.LocalName.Equals(nodeName)) { @@ -158,7 +171,9 @@ private void NodeNotFound(int lineNumber, string node, string parent) private ScriptBlock GetScriptBlock(string text, int initialLine) { if (text == null) + { return null; + } ScriptBlock scriptBlock; try @@ -196,6 +211,7 @@ private Type ResolveType(string typeName, int line) { _context.AddError(line, ParserStrings.TypeNotFound, typeName); } + return type; } @@ -203,9 +219,15 @@ private bool ToBoolean(string value) { value = value.Trim(); if (string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) + { return true; + } + if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase)) + { return false; + } + _context.AddError(_readerLineInfo.LineNumber, TypesXmlStrings.ValueShouldBeTrueOrFalse, value); return false; } @@ -224,9 +246,10 @@ private T Converter(object value, string name) private bool BoolConverter(object value, string name) { - var s = value as string; - if (s != null) + if (value is string s) + { return ToBoolean(s); + } return Converter(value, name); } @@ -245,6 +268,7 @@ private void CheckStandardNote(TypeMemberData member, TypeData typeData, Acti { value = (T)note.Value; } + setter(typeData, value); } else @@ -253,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) @@ -319,8 +343,10 @@ private IEnumerable Read_Types() { UnknownNode(_idTypes, "Type"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -351,22 +377,38 @@ private TypeData Read_Type() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idType); + if (name != null) + { + NotMoreThanOnce(_idName, _idType); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idMembers) { - if (members != null) NotMoreThanOnce(_idMembers, _idType); + if (members != null) + { + NotMoreThanOnce(_idMembers, _idType); + } + members = Read_Members(out standardMembers); } else if ((object)_reader.LocalName == (object)_idTypeConverter) { - if (typeConverter != null) NotMoreThanOnce(_idTypeConverter, _idType); + if (typeConverter != null) + { + NotMoreThanOnce(_idTypeConverter, _idType); + } + typeConverter = Read_TypeX(_idTypeConverter); } else if ((object)_reader.LocalName == (object)_idTypeAdapter) { - if (typeAdapter != null) NotMoreThanOnce(_idTypeAdapter, _idType); + if (typeAdapter != null) + { + NotMoreThanOnce(_idTypeAdapter, _idType); + } + typeAdapter = Read_TypeX(_idTypeAdapter); } else @@ -378,8 +420,10 @@ private TypeData Read_Type() { UnknownNode(_idType, "Name,Members,TypeConverter,TypeAdapter"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -405,6 +449,7 @@ private TypeData Read_Type() typeData.Members.Add(m.Name, m); } } + typeData.TypeAdapter = typeAdapter; typeData.TypeConverter = typeConverter; @@ -414,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)) { @@ -432,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)) { @@ -459,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 { @@ -471,6 +516,7 @@ private TypeData Read_Type() } } } + return typeData; } @@ -498,7 +544,11 @@ private Type Read_TypeX(string elementName) { if ((object)_reader.LocalName == (object)_idTypeName) { - if (typeName != null) NotMoreThanOnce(_idTypeName, elementName); + if (typeName != null) + { + NotMoreThanOnce(_idTypeName, elementName); + } + typeLineNumber = _readerLineInfo.LineNumber; typeName = ReadElementString(_idTypeName); } @@ -511,8 +561,10 @@ private Type Read_TypeX(string elementName) { UnknownNode(elementName, "TypeName"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -553,37 +605,58 @@ private Collection Read_Members(out MemberSetData standardMember if ((object)_reader.LocalName == (object)_idNoteProperty) { var p = Read_NoteProperty(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idAliasProperty) { var p = Read_AliasProperty(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idScriptProperty) { var p = Read_ScriptProperty(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idCodeProperty) { var p = Read_CodeProperty(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idScriptMethod) { var p = Read_ScriptMethod(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idCodeMethod) { var p = Read_CodeMethod(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idPropertySet) { var p = Read_PropertySet(); - if (p != null) members.Add(p); + if (p != null) + { + members.Add(p); + } } else if ((object)_reader.LocalName == (object)_idMemberSet) { @@ -609,8 +682,10 @@ private Collection Read_Members(out MemberSetData standardMember { UnknownNode(_idMembers, "NoteProperty,AliasProperty,ScriptProperty,CodeProperty,ScriptMethod,CodeMethod,PropertySet,MemberSet"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -642,17 +717,29 @@ private MemberSetData Read_MemberSet() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idMemberSet); + if (name != null) + { + NotMoreThanOnce(_idName, _idMemberSet); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idInheritMembers) { - if (inheritMembers.HasValue) NotMoreThanOnce(_idInheritMembers, _idMemberSet); + if (inheritMembers.HasValue) + { + NotMoreThanOnce(_idInheritMembers, _idMemberSet); + } + inheritMembers = ToBoolean(ReadElementString(_idMemberSet)); } else if ((object)_reader.LocalName == (object)_idMembers) { - if (members != null) NotMoreThanOnce(_idMembers, _idMemberSet); + if (members != null) + { + NotMoreThanOnce(_idMembers, _idMemberSet); + } + MemberSetData standardMembers; members = Read_Members(out standardMembers); if (standardMembers != null) @@ -671,28 +758,36 @@ private MemberSetData Read_MemberSet() { UnknownNode(_idMemberSet, "Name,InheritMembers,Members"); } + _reader.MoveToContent(); } + ReadEndElement(); } if (string.IsNullOrWhiteSpace(name)) + { NodeNotFound(lineNumber, _idName, _idMemberSet); + } + // Somewhat pointlessly (backcompat), we allow a missing Member node - if (members == null) - members = new Collection(); + members ??= new Collection(); if (_context.errors.Count != errorCount) + { return null; + } var result = new MemberSetData(name, members) { IsHidden = isHidden.GetValueOrDefault() }; + if (inheritMembers.HasValue) { result.InheritMembers = inheritMembers.Value; } + return result; } @@ -720,12 +815,16 @@ private PropertySetData Read_PropertySet() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idPropertySet); + if (name != null) + { + NotMoreThanOnce(_idName, _idPropertySet); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idReferencedProperties) { - if ((_reader.IsEmptyElement)) + if (_reader.IsEmptyElement) { _reader.Skip(); } @@ -739,7 +838,8 @@ 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)); } else @@ -751,8 +851,10 @@ private PropertySetData Read_PropertySet() { UnknownNode(_idPropertySet, "Name"); } + _reader.MoveToContent(); } + ReadEndElement(); } } @@ -765,8 +867,10 @@ private PropertySetData Read_PropertySet() { UnknownNode(_idPropertySet, "Name,ReferencedProperties"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -776,7 +880,9 @@ private PropertySetData Read_PropertySet() NodeNotFound(lineNumber, _idReferencedProperties, _idPropertySet); if (_context.errors.Count != errorCount) + { return null; + } return new PropertySetData(referencedProperties) { @@ -810,12 +916,20 @@ private CodeMethodData Read_CodeMethod() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idCodeMethod); + if (name != null) + { + NotMoreThanOnce(_idName, _idCodeMethod); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idCodeReference) { - if (codeReference != null) NotMoreThanOnce(_idCodeReference, _idCodeMethod); + if (codeReference != null) + { + NotMoreThanOnce(_idCodeReference, _idCodeMethod); + } + methodLineNumber = _readerLineInfo.LineNumber; codeReference = Read_CodeReference(); if (codeReference == null) @@ -830,8 +944,10 @@ private CodeMethodData Read_CodeMethod() { UnknownNode(_idCodeMethod, "Name,CodeReference"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -843,7 +959,9 @@ private CodeMethodData Read_CodeMethod() _context.AddError(methodLineNumber, ExtendedTypeSystem.CodeMethodMethodFormat); if (_context.errors.Count != errorCount) + { return null; + } return new CodeMethodData(name, codeReference); } @@ -874,13 +992,21 @@ private MethodInfo Read_CodeReference() { if ((object)_reader.LocalName == (object)_idTypeName) { - if (typeName != null) NotMoreThanOnce(_idTypeName, _idCodeReference); + if (typeName != null) + { + NotMoreThanOnce(_idTypeName, _idCodeReference); + } + typeLineNumber = _readerLineInfo.LineNumber; typeName = ReadElementString(_idTypeName); } else if ((object)_reader.LocalName == (object)_idMethodName) { - if (methodName != null) NotMoreThanOnce(_idMethodName, _idCodeReference); + if (methodName != null) + { + NotMoreThanOnce(_idMethodName, _idCodeReference); + } + methodLineNumber = _readerLineInfo.LineNumber; methodName = ReadElementString(_idMethodName); } @@ -893,8 +1019,10 @@ private MethodInfo Read_CodeReference() { UnknownNode(_idCodeReference, "TypeName,MethodName"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -904,7 +1032,9 @@ private MethodInfo Read_CodeReference() NodeNotFound(lineNumber, _idMethodName, _idCodeReference); if (_context.errors.Count != errorCount) + { return null; + } MethodInfo member = null; var type = ResolveType(typeName, typeLineNumber); @@ -948,12 +1078,20 @@ private ScriptMethodData Read_ScriptMethod() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idScriptMethod); + if (name != null) + { + NotMoreThanOnce(_idName, _idScriptMethod); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idScript) { - if (script != null) NotMoreThanOnce(_idScript, _idScriptMethod); + if (script != null) + { + NotMoreThanOnce(_idScript, _idScriptMethod); + } + scriptLineNumber = _readerLineInfo.LineNumber; script = ReadElementString(_idScript); } @@ -966,8 +1104,10 @@ private ScriptMethodData Read_ScriptMethod() { UnknownNode(_idScriptMethod, "Name,Script"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -975,6 +1115,7 @@ private ScriptMethodData Read_ScriptMethod() { NodeNotFound(lineNumber, _idName, _idScriptMethod); } + if (string.IsNullOrWhiteSpace(script)) { NodeNotFound(lineNumber, _idScript, _idScriptMethod); @@ -983,7 +1124,9 @@ private ScriptMethodData Read_ScriptMethod() ScriptBlock scriptBlock = GetScriptBlock(script, scriptLineNumber); if (_context.errors.Count != errorCount) + { return null; + } return new ScriptMethodData(name, scriptBlock); } @@ -1015,24 +1158,40 @@ private CodePropertyData Read_CodeProperty() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idCodeProperty); + if (name != null) + { + NotMoreThanOnce(_idName, _idCodeProperty); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idGetCodeReference) { - if (getter != null) NotMoreThanOnce(_idGetCodeReference, _idCodeProperty); + if (getter != null) + { + NotMoreThanOnce(_idGetCodeReference, _idCodeProperty); + } + getterLineNumber = _readerLineInfo.LineNumber; getter = Read_CodeReference(); if (getter == null) + { _context.AddError(getterLineNumber, ExtendedTypeSystem.CodePropertyGetterAndSetterNull); + } } else if ((object)_reader.LocalName == (object)_idSetCodeReference) { - if (setter != null) NotMoreThanOnce(_idSetCodeReference, _idCodeProperty); + if (setter != null) + { + NotMoreThanOnce(_idSetCodeReference, _idCodeProperty); + } + setterLineNumber = _readerLineInfo.LineNumber; setter = Read_CodeReference(); if (setter == null) + { _context.AddError(setterLineNumber, ExtendedTypeSystem.CodePropertyGetterAndSetterNull); + } } else { @@ -1043,22 +1202,37 @@ private CodePropertyData Read_CodeProperty() { UnknownNode(_idCodeProperty, "Name,GetCodeReference,SetCodeReference"); } + _reader.MoveToContent(); } + ReadEndElement(); } if (string.IsNullOrEmpty(name)) + { NodeNotFound(lineNumber, _idName, _idCodeProperty); + } + if (getter == null && setter == null && getterLineNumber == 0 && setterLineNumber == 0) + { _context.AddError(lineNumber, TypesXmlStrings.CodePropertyShouldHaveGetterOrSetter); + } + if (getter != null && !PSCodeProperty.CheckGetterMethodInfo(getter)) + { _context.AddError(getterLineNumber, ExtendedTypeSystem.CodePropertyGetterFormat); + } + if (setter != null && !PSCodeProperty.CheckSetterMethodInfo(setter, getter)) + { _context.AddError(setterLineNumber, ExtendedTypeSystem.CodePropertySetterFormat); + } if (_context.errors.Count != errorCount) + { return null; + } return new CodePropertyData(name, getter, setter) { @@ -1093,18 +1267,30 @@ private ScriptPropertyData Read_ScriptProperty() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idScriptProperty); + if (name != null) + { + NotMoreThanOnce(_idName, _idScriptProperty); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idGetScriptBlock) { - if (getScriptBlock != null) NotMoreThanOnce(_idGetScriptBlock, _idScriptProperty); + if (getScriptBlock != null) + { + NotMoreThanOnce(_idGetScriptBlock, _idScriptProperty); + } + getterInitialLine = _readerLineInfo.LineNumber; getScriptBlock = ReadElementString(_idGetScriptBlock); } else if ((object)_reader.LocalName == (object)_idSetScriptBlock) { - if (setScriptBlock != null) NotMoreThanOnce(_idSetScriptBlock, _idScriptProperty); + if (setScriptBlock != null) + { + NotMoreThanOnce(_idSetScriptBlock, _idScriptProperty); + } + setterInitialLine = _readerLineInfo.LineNumber; setScriptBlock = ReadElementString(_idSetScriptBlock); } @@ -1117,8 +1303,10 @@ private ScriptPropertyData Read_ScriptProperty() { UnknownNode(_idScriptProperty, "Name,GetScriptBlock,SetScriptBlock"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -1126,6 +1314,7 @@ private ScriptPropertyData Read_ScriptProperty() { NodeNotFound(lineNumber, _idName, _idScriptProperty); } + if (string.IsNullOrWhiteSpace(getScriptBlock) && string.IsNullOrWhiteSpace(setScriptBlock)) { _context.AddError(lineNumber, TypesXmlStrings.ScriptPropertyShouldHaveGetterOrSetter); @@ -1135,7 +1324,9 @@ private ScriptPropertyData Read_ScriptProperty() ScriptBlock setter = GetScriptBlock(setScriptBlock, setterInitialLine); if (_context.errors.Count != errorCount) + { return null; + } return new ScriptPropertyData(name, getter, setter) { @@ -1169,17 +1360,29 @@ private AliasPropertyData Read_AliasProperty() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idAliasProperty); + if (name != null) + { + NotMoreThanOnce(_idName, _idAliasProperty); + } + name = ReadElementString(_idAliasProperty); } else if ((object)_reader.LocalName == (object)_idReferencedMemberName) { - if (referencedMemberName != null) NotMoreThanOnce(_idReferencedMemberName, _idAliasProperty); + if (referencedMemberName != null) + { + NotMoreThanOnce(_idReferencedMemberName, _idAliasProperty); + } + referencedMemberName = ReadElementString(_idReferencedMemberName); } else if ((object)_reader.LocalName == (object)_idTypeName) { - if (typeName != null) NotMoreThanOnce(_idTypeName, _idAliasProperty); + if (typeName != null) + { + NotMoreThanOnce(_idTypeName, _idAliasProperty); + } + typeLineNumber = _readerLineInfo.LineNumber; typeName = ReadElementString(_idTypeName); } @@ -1192,6 +1395,7 @@ private AliasPropertyData Read_AliasProperty() { UnknownNode(_idAliasProperty, "Name,ReferencedMemberName,TypeName"); } + _reader.MoveToContent(); } @@ -1202,13 +1406,18 @@ private AliasPropertyData Read_AliasProperty() { NodeNotFound(lineNumber, _idName, _idAliasProperty); } + if (string.IsNullOrWhiteSpace(referencedMemberName)) { NodeNotFound(lineNumber, _idReferencedMemberName, _idAliasProperty); } + Type convertToType = (typeName != null) ? ResolveType(typeName, typeLineNumber) : null; - if (_context.errors.Count != errorCount) return null; + if (_context.errors.Count != errorCount) + { + return null; + } return new AliasPropertyData(name, referencedMemberName, convertToType) { @@ -1242,17 +1451,29 @@ private NotePropertyData Read_NoteProperty() { if ((object)_reader.LocalName == (object)_idName) { - if (name != null) NotMoreThanOnce(_idName, _idNoteProperty); + if (name != null) + { + NotMoreThanOnce(_idName, _idNoteProperty); + } + name = ReadElementString(_idName); } else if ((object)_reader.LocalName == (object)_idValue) { - if (valueAsString != null) NotMoreThanOnce(_idValue, _idNoteProperty); + if (valueAsString != null) + { + NotMoreThanOnce(_idValue, _idNoteProperty); + } + valueAsString = ReadElementString(_idValue); } else if ((object)_reader.LocalName == (object)_idTypeName) { - if (typeName != null) NotMoreThanOnce(_idTypeName, _idNoteProperty); + if (typeName != null) + { + NotMoreThanOnce(_idTypeName, _idNoteProperty); + } + typeLineNumber = _readerLineInfo.LineNumber; typeName = ReadElementString(_idTypeName); } @@ -1265,8 +1486,10 @@ private NotePropertyData Read_NoteProperty() { UnknownNode(_idNoteProperty, "Name,Value,TypeName"); } + _reader.MoveToContent(); } + ReadEndElement(); } @@ -1274,6 +1497,7 @@ private NotePropertyData Read_NoteProperty() { NodeNotFound(lineNumber, _idName, _idNoteProperty); } + if (string.IsNullOrWhiteSpace(valueAsString)) { NodeNotFound(lineNumber, _idValue, _idNoteProperty); @@ -1294,7 +1518,9 @@ private NotePropertyData Read_NoteProperty() } if (_context.errors.Count != errorCount) + { return null; + } return new NotePropertyData(name, value) { @@ -1366,16 +1592,17 @@ protected void InitIDs() /// /// Internal class to provide a Hashtable key out of a Collection of strings - /// preserving the evaluation of the key + /// preserving the evaluation of the key. /// internal class ConsolidatedString : Collection { protected override void SetItem(int index, string item) { - if (String.IsNullOrEmpty(item)) + if (string.IsNullOrEmpty(item)) { - throw PSTraceSource.NewArgumentException("item"); + throw PSTraceSource.NewArgumentException(nameof(item)); } + base.SetItem(index, item); UpdateKey(); } @@ -1388,10 +1615,11 @@ protected override void ClearItems() protected override void InsertItem(int index, string item) { - if (String.IsNullOrEmpty(item)) + if (string.IsNullOrEmpty(item)) { - throw PSTraceSource.NewArgumentException("item"); + throw PSTraceSource.NewArgumentException(nameof(item)); } + base.InsertItem(index, item); UpdateKey(); } @@ -1403,6 +1631,7 @@ protected override void RemoveItem(int index) } internal string Key { get; private set; } + internal bool IsReadOnly { get { return ((ICollection)this).IsReadOnly; } } private void UpdateKey() @@ -1435,19 +1664,20 @@ public ConsolidatedString(IEnumerable strings) for (int i = 0; i < this.Count; i++) { string str = this[i]; - if (String.IsNullOrEmpty(str)) + if (string.IsNullOrEmpty(str)) { - throw PSTraceSource.NewArgumentException("strings"); + throw PSTraceSource.NewArgumentException(nameof(strings)); } } + UpdateKey(); } - internal static readonly ConsolidatedString Empty = new ConsolidatedString(Utils.EmptyArray()); + internal static readonly ConsolidatedString Empty = new ConsolidatedString(Array.Empty()); - internal static IEqualityComparer EqualityComparer = new ConsolidatedStringEqualityComparer(); + internal static readonly IEqualityComparer EqualityComparer = new ConsolidatedStringEqualityComparer(); - private class ConsolidatedStringEqualityComparer : IEqualityComparer + private sealed class ConsolidatedStringEqualityComparer : IEqualityComparer { bool IEqualityComparer.Equals(ConsolidatedString x, ConsolidatedString y) { @@ -1481,6 +1711,7 @@ internal LoadContext(string PSSnapinName, string fileName, ConcurrentBag internal bool IsFullyTrusted { get { return isFullyTrusted; } + set { isFullyTrusted = value; } } @@ -1496,28 +1727,25 @@ internal void AddError(string resourceString, params object[] formatArguments) internal void AddError(int errorLineNumber, string resourceString, params object[] formatArguments) { string errorMsg = StringUtil.Format(resourceString, formatArguments); - string errorLine = StringUtil.Format(TypesXmlStrings.FileLineError, - this.PSSnapinName, this.fileName, errorLineNumber, errorMsg); + string errorLine = StringUtil.Format(TypesXmlStrings.FileLineError, this.PSSnapinName, this.fileName, errorLineNumber, errorMsg); this.errors.Add(errorLine); } internal void AddError(string typeName, int errorLineNumber, string resourceString, params object[] formatArguments) { string errorMsg = StringUtil.Format(resourceString, formatArguments); - string errorLine = StringUtil.Format(TypesXmlStrings.FileLineTypeError, - this.PSSnapinName, this.fileName, errorLineNumber, typeName, errorMsg); + string errorLine = StringUtil.Format(TypesXmlStrings.FileLineTypeError, this.PSSnapinName, this.fileName, errorLineNumber, typeName, errorMsg); this.errors.Add(errorLine); } - }; + } /// /// 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 @@ -1541,8 +1769,6 @@ public TypeTableLoadException(string message) SetDefaultErrorRecord(); } - - /// /// This constructor takes a localized message and an inner exception. /// @@ -1563,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) @@ -1578,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("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("info"); - } - - base.GetObjectData(info, context); - - // If there are simple fields, serialize them with info.AddValue - if (null != _errors) - { - 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. /// @@ -1653,7 +1836,7 @@ public Collection Errors #region TypeData /// - /// TypeData represent a Type Definition + /// TypeData represent a Type Definition. /// public sealed class TypeData { @@ -1673,13 +1856,13 @@ private TypeData() } /// - /// Initialize a TypeData instance by providing the typeName + /// Initialize a TypeData instance by providing the typeName. /// /// public TypeData(string typeName) : this() { - if (String.IsNullOrWhiteSpace(typeName)) - throw PSTraceSource.NewArgumentNullException("typeName"); + if (string.IsNullOrWhiteSpace(typeName)) + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); this.TypeName = typeName; } @@ -1690,54 +1873,61 @@ internal TypeData(string typeName, bool typesXml) : this() } /// - /// Initialize a TypeData instance by providing a Type + /// Initialize a TypeData instance by providing a Type. /// /// public TypeData(Type type) : this() { if (type == null) - throw PSTraceSource.NewArgumentNullException("type"); + { + throw PSTraceSource.NewArgumentNullException(nameof(type)); + } + this.TypeName = type.FullName; } - internal bool fromTypesXmlFile { get; private set; } + internal bool fromTypesXmlFile { get; } /// - /// Get the TypeName + /// 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 + /// The type converter. /// public Type TypeConverter { get; set; } /// - /// The type adapter + /// The type adapter. /// public Type TypeAdapter { get; set; } /// - /// Set to true if override the existing definition + /// Set to true if override the existing definition. /// public bool IsOverride { get; set; } #region StandardMember - internal Dictionary StandardMembers { get; private set; } + internal Dictionary StandardMembers { get; } /// - /// The serializationMethod + /// The serializationMethod. /// public string SerializationMethod { - get { return _serializationMethod; } + get + { + return _serializationMethod; + } + set { _serializationMethod = value; @@ -1761,11 +1951,15 @@ public string SerializationMethod } /// - /// The targetTypeForDeserialization + /// The targetTypeForDeserialization. /// public Type TargetTypeForDeserialization { - get { return _targetTypeForDeserialization; } + get + { + return _targetTypeForDeserialization; + } + set { _targetTypeForDeserialization = value; @@ -1789,11 +1983,15 @@ public Type TargetTypeForDeserialization } /// - /// The serializationDepth + /// The serializationDepth. /// public uint SerializationDepth { - get { return _serializationDepth; } + get + { + return _serializationDepth; + } + set { _serializationDepth = value; @@ -1812,11 +2010,15 @@ public uint SerializationDepth } /// - /// The defaultDisplayProperty + /// The defaultDisplayProperty. /// public string DefaultDisplayProperty { - get { return _defaultDisplayProperty; } + get + { + return _defaultDisplayProperty; + } + set { _defaultDisplayProperty = value; @@ -1840,12 +2042,16 @@ public string DefaultDisplayProperty } /// - /// The InheritPropertySerializationSet + /// The InheritPropertySerializationSet. /// public bool InheritPropertySerializationSet { - get { return _inheritPropertySerializationSet; } - set + get + { + return _inheritPropertySerializationSet; + } + + set { _inheritPropertySerializationSet = value; TypeMemberData typeMemberData; @@ -1862,11 +2068,15 @@ public bool InheritPropertySerializationSet } /// - /// The stringSerializationSource + /// The stringSerializationSource. /// public string StringSerializationSource { - get { return _stringSerializationSource; } + get + { + return _stringSerializationSource; + } + set { if (value == null) @@ -1878,6 +2088,7 @@ public string StringSerializationSource { StandardMembers.Remove(TypeTable.StringSerializationSource); } + _stringSerializationSource = null; return; } @@ -1909,7 +2120,11 @@ public string StringSerializationSource /// public TypeMemberData StringSerializationSourceProperty { - get { return _stringSerializationSourceProperty; } + get + { + return _stringSerializationSourceProperty; + } + set { if (value == null) @@ -1921,11 +2136,12 @@ public TypeMemberData StringSerializationSourceProperty { StandardMembers.Remove(TypeTable.StringSerializationSource); } + _stringSerializationSourceProperty = null; 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"); } @@ -1941,11 +2157,15 @@ public TypeMemberData StringSerializationSourceProperty } /// - /// The defaultDisplayPropertySet + /// The defaultDisplayPropertySet. /// public PropertySetData DefaultDisplayPropertySet { - get { return _defaultDisplayPropertySet; } + get + { + return _defaultDisplayPropertySet; + } + set { _defaultDisplayPropertySet = value; @@ -1957,11 +2177,15 @@ public PropertySetData DefaultDisplayPropertySet } /// - /// The defaultKeyPropertySet + /// The defaultKeyPropertySet. /// public PropertySetData DefaultKeyPropertySet { - get { return _defaultKeyPropertySet; } + get + { + return _defaultKeyPropertySet; + } + set { _defaultKeyPropertySet = value; @@ -1973,11 +2197,15 @@ public PropertySetData DefaultKeyPropertySet } /// - /// The PropertySerializationSet + /// The PropertySerializationSet. /// public PropertySetData PropertySerializationSet { - get { return _propertySerializationSet; } + get + { + return _propertySerializationSet; + } + set { _propertySerializationSet = value; @@ -1988,17 +2216,18 @@ public PropertySetData PropertySerializationSet } } - // They are of NoteProperty private string _serializationMethod; private Type _targetTypeForDeserialization; private uint _serializationDepth; private string _defaultDisplayProperty; + // InheritPropertySerializationSet should be true or false private bool _inheritPropertySerializationSet; // It is of AliasProperty private string _stringSerializationSource; + // Except when it's not private TypeMemberData _stringSerializationSourceProperty; @@ -2010,7 +2239,7 @@ public PropertySetData PropertySerializationSet #endregion StandardMember /// - /// Return a TypeData that is a copy of this one + /// Return a TypeData that is a copy of this one. /// /// public TypeData Copy() @@ -2049,10 +2278,11 @@ public TypeData Copy() newTypeData.StringSerializationSource = this.StringSerializationSource; break; default: - Dbg.Assert(false, "Standard members should at most contain six kinds of elements"); + Dbg.Fail("Standard members should at most contain six kinds of elements"); break; } } + newTypeData.DefaultDisplayPropertySet = this.DefaultDisplayPropertySet == null ? null : (PropertySetData)this.DefaultDisplayPropertySet.Copy(); @@ -2075,18 +2305,20 @@ public TypeData Copy() /// ScriptPropertyData, /// CodePropertyData, /// ScriptMethodData, - /// CodeMethodData + /// CodeMethodData. /// public abstract class TypeMemberData { /// - /// TypeMemberData constructor + /// TypeMemberData constructor. /// /// internal TypeMemberData(string name) { - if (String.IsNullOrWhiteSpace(name)) - throw PSTraceSource.NewArgumentException("name"); + if (string.IsNullOrWhiteSpace(name)) + { + throw PSTraceSource.NewArgumentException(nameof(name)); + } Name = name; } @@ -2096,7 +2328,7 @@ internal TypeMemberData() } /// - /// The name of the member + /// The name of the member. /// public string Name { get; protected set; } @@ -2110,13 +2342,13 @@ internal TypeMemberData() } /// - /// NotePropertyData represents a NoteProperty definition + /// NotePropertyData represents a NoteProperty definition. /// [DebuggerDisplay("NoteProperty: {Name,nq} = {Value,nq}")] public sealed class NotePropertyData : TypeMemberData { /// - /// NotePropertyData constructor + /// NotePropertyData constructor. /// /// /// @@ -2127,17 +2359,17 @@ public NotePropertyData(string name, object value) } /// - /// The value of this NoteProperty + /// The value of this NoteProperty. /// public object Value { get; set; } /// - /// Set true if the member is supposed to be hidden + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } /// - /// Return a new NotePropertyData that is a copy of this one + /// Return a new NotePropertyData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2153,13 +2385,13 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// AliasPropertyData represents a AliasProperty definition + /// AliasPropertyData represents a AliasProperty definition. /// [DebuggerDisplay("AliasProperty: {Name,nq} = {ReferencedMemberName,nq}")] public sealed class AliasPropertyData : TypeMemberData { /// - /// AliasPropertyData constructor + /// AliasPropertyData constructor. /// /// /// @@ -2170,7 +2402,7 @@ public AliasPropertyData(string name, string referencedMemberName) } /// - /// AliasPropertyData constructor + /// AliasPropertyData constructor. /// /// /// @@ -2183,23 +2415,23 @@ public AliasPropertyData(string name, string referencedMemberName, Type type) } /// - /// The name of the referenced member + /// The name of the referenced member. /// public string ReferencedMemberName { get; set; } /// /// Specify the Type to which the referenced member value will be - /// converted to + /// converted to. /// public Type MemberType { get; set; } /// - /// Set true if the member is supposed to be hidden + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } /// - /// Return a new AliasPropertyData that is a copy of this one + /// Return a new AliasPropertyData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2218,13 +2450,13 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// ScriptPropertyData represents a ScriptProperty definition + /// ScriptPropertyData represents a ScriptProperty definition. /// [DebuggerDisplay("ScriptProperty: {Name,nq}")] public sealed class ScriptPropertyData : TypeMemberData { /// - /// Initialize the ScriptPropertyData as a read only property + /// Initialize the ScriptPropertyData as a read only property. /// /// /// @@ -2235,7 +2467,7 @@ public ScriptPropertyData(string name, ScriptBlock getScriptBlock) } /// - /// ScriptPropertyData constructor + /// ScriptPropertyData constructor. /// /// /// @@ -2248,22 +2480,22 @@ public ScriptPropertyData(string name, ScriptBlock getScriptBlock, ScriptBlock s } /// - /// The getter ScriptBlock + /// The getter ScriptBlock. /// public ScriptBlock GetScriptBlock { get; set; } /// - /// The setter ScriptBlock + /// The setter ScriptBlock. /// public ScriptBlock SetScriptBlock { get; set; } /// - /// Set true if the member is supposed to be hidden + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } /// - /// Return a new ScriptPropertyData that is a copy of this one + /// Return a new ScriptPropertyData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2282,12 +2514,12 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// CodePropertyData represents a CodeProperty definition + /// CodePropertyData represents a CodeProperty definition. /// public sealed class CodePropertyData : TypeMemberData { /// - /// Initialize the CodePropertyData as a read only property + /// Initialize the CodePropertyData as a read only property. /// /// /// @@ -2298,7 +2530,7 @@ public CodePropertyData(string name, MethodInfo getMethod) } /// - /// CodePropertyData constructor + /// CodePropertyData constructor. /// /// /// @@ -2311,22 +2543,22 @@ public CodePropertyData(string name, MethodInfo getMethod, MethodInfo setMethod) } /// - /// The getter code reference + /// The getter code reference. /// public MethodInfo GetCodeReference { get; set; } /// - /// The setter code reference + /// The setter code reference. /// public MethodInfo SetCodeReference { get; set; } /// - /// Set true if the member is supposed to be hidden + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } /// - /// Return a CodePropertyData that is a copy of this one + /// Return a CodePropertyData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2345,14 +2577,13 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// ScriptMethodData represents a ScriptMethod definition + /// ScriptMethodData represents a ScriptMethod definition. /// [DebuggerDisplay(@"ScriptMethod: {Name,nq}")] - public sealed class ScriptMethodData : TypeMemberData { /// - /// ScriptMethodData constructor + /// ScriptMethodData constructor. /// /// /// @@ -2363,12 +2594,12 @@ public ScriptMethodData(string name, ScriptBlock scriptToInvoke) } /// - /// The script method + /// The script method. /// public ScriptBlock Script { get; set; } /// - /// Return a ScriptMethodData that is a copy of this one + /// Return a ScriptMethodData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2384,13 +2615,13 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// CodeMethodData represents a CodeMethodData definition + /// CodeMethodData represents a CodeMethodData definition. /// [DebuggerDisplay("CodeMethod: {Name,nq}")] public sealed class CodeMethodData : TypeMemberData { /// - /// CodeMethodData constructor + /// CodeMethodData constructor. /// /// /// @@ -2401,12 +2632,12 @@ public CodeMethodData(string name, MethodInfo methodToCall) } /// - /// The code reference + /// The code reference. /// public MethodInfo CodeReference { get; set; } /// - /// Return a CodeMethodData that is a copy of this one + /// Return a CodeMethodData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2422,46 +2653,42 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// PropertySetData represent a PropertySet definition + /// PropertySetData represent a PropertySet definition. /// [DebuggerDisplay("PropertySet: {Name,nq}")] public sealed class PropertySetData : TypeMemberData { /// - /// PropertySetData constructor + /// PropertySetData constructor. /// /// public PropertySetData(IEnumerable referencedProperties) { - if (null == referencedProperties) + if (referencedProperties == null) { - throw PSTraceSource.NewArgumentNullException("referencedProperties"); + throw PSTraceSource.NewArgumentNullException(nameof(referencedProperties)); } - ReferencedProperties = new Collection(); - foreach (string property in referencedProperties) - { - ReferencedProperties.Add(property); - } + ReferencedProperties = new Collection(new List(referencedProperties)); } /// - /// The referenced properties + /// The referenced properties. /// - public Collection ReferencedProperties { get; private set; } + public Collection ReferencedProperties { get; } /// - /// The PropertySet name + /// The PropertySet name. /// internal new string Name { get { return base.Name; } set { base.Name = value; } } /// - /// Set true if the member is supposed to be hidden + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } /// - /// Return a new PropertySetData that is a copy of this one + /// Return a new PropertySetData that is a copy of this one. /// /// internal override TypeMemberData Copy() @@ -2481,15 +2708,15 @@ internal override void Process(ConcurrentBag errors, string typeName, PS } /// - /// MemberSetData represents a MemberSet definition + /// MemberSetData represents a MemberSet definition. /// public class MemberSetData : TypeMemberData { /// - /// MemberSetData constructor + /// MemberSetData constructor. /// - /// The name of the MemberSet - /// The members of the MemberSet + /// The name of the MemberSet. + /// The members of the MemberSet. public MemberSetData(string name, Collection members) : base(name) { @@ -2498,12 +2725,12 @@ public MemberSetData(string name, Collection members) } /// - /// The members of the MemberSet + /// 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 + /// Set true if the member is supposed to be hidden. /// public bool IsHidden { get; set; } @@ -2531,11 +2758,10 @@ internal override void Process(ConcurrentBag errors, string typeName, PS #endregion TypeData - /// - /// A class that keeps the information from types.ps1xml files in a cache table + /// A class that keeps the information from types.ps1xml files in a cache table. /// - public sealed class TypeTable + public sealed partial class TypeTable { #region private @@ -2553,6 +2779,7 @@ public sealed class TypeTable internal const string DefaultDisplayPropertySet = "DefaultDisplayPropertySet"; internal const string DefaultKeyPropertySet = "DefaultKeyPropertySet"; internal const string DefaultDisplayProperty = "DefaultDisplayProperty"; + // this is used for extended properties like Note,Alias,Script,Code internal const string IsHiddenAttribute = "IsHidden"; @@ -2561,43 +2788,46 @@ public sealed class TypeTable #region fields /// - /// Table from type name list into PSMemberInfoInternalCollection + /// Table from type name list into PSMemberInfoInternalCollection. /// private readonly ConcurrentDictionary> _consolidatedMembers = new ConcurrentDictionary>( - /*concurrency*/1, /*capacity*/256, StringComparer.OrdinalIgnoreCase); + concurrencyLevel: 1, capacity: 256, StringComparer.OrdinalIgnoreCase); /// - /// Table from type name list into Collection of strings + /// Table from type name list into Collection of strings. /// private readonly ConcurrentDictionary> _consolidatedSpecificProperties = new ConcurrentDictionary>( - /*concurrency*/1, /*capacity*/10, StringComparer.OrdinalIgnoreCase); + concurrencyLevel: 1, capacity: 10, StringComparer.OrdinalIgnoreCase); /// - /// Table from type name into PSMemberInfoInternalCollection + /// Table from type name into PSMemberInfoInternalCollection. /// private readonly ConcurrentDictionary> _extendedMembers = new ConcurrentDictionary>( - /*concurrency*/3, /*capacity*/300, StringComparer.OrdinalIgnoreCase); + concurrencyLevel: 3, capacity: 300, StringComparer.OrdinalIgnoreCase); /// - /// points to a Hashtable from type name to type converter + /// Points to a Hashtable from type name to type converter. /// private readonly ConcurrentDictionary _typeConverters = new ConcurrentDictionary( - /*concurrency*/1, /*capacity*/5, StringComparer.OrdinalIgnoreCase); + concurrencyLevel: 1, capacity: 5, StringComparer.OrdinalIgnoreCase); /// - /// points to a Hashtable from type name to type adapter + /// Points to a Hashtable from type name to type adapter. /// private readonly ConcurrentDictionary _typeAdapters = new ConcurrentDictionary( - /*concurrency*/1, /*capacity*/5, StringComparer.OrdinalIgnoreCase); + concurrencyLevel: 1, capacity: 5, StringComparer.OrdinalIgnoreCase); // this is used to throw errors when updating a shared TypeTable. internal readonly bool isShared; - private List _typeFileList; + private readonly List _typeFileList; + + // The member factory is cached to avoid allocating Func<> delegates on each call + private readonly Func> _memberFactoryFunc; // This holds all the type information that is in the typetable // Holds file name if types file was used to update the types @@ -2606,11 +2836,12 @@ private readonly ConcurrentDictionary _typeConverters internal InitialSessionStateEntryCollection typesInfo = new InitialSessionStateEntryCollection(); - internal const SerializationMethod defaultSerializationMethod = SerializationMethod.AllPublicProperties; + internal const SerializationMethod DefaultSerializationMethod = SerializationMethod.AllPublicProperties; - internal const bool defaultInheritPropertySerializationSet = true; + internal const bool DefaultInheritPropertySerializationSet = true; - private static readonly string[] s_standardMembers = new string[] { + private static readonly string[] s_standardMembers = new string[] + { DefaultDisplayProperty, DefaultDisplayPropertySet, DefaultKeyPropertySet, @@ -2622,6 +2853,11 @@ private readonly ConcurrentDictionary _typeConverters TargetTypeForDeserialization }; + // Built-in type file paths. + internal static readonly string TypesFilePath; + internal static readonly string TypesV3FilePath; + internal static readonly string GetEventTypesFilePath; + #endregion #region Update @@ -2635,11 +2871,13 @@ private static void AddMember(ConcurrentBag errors, string typeName, PSM AddError(errors, typeName, TypesXmlStrings.ReservedNameMember, member.name); return; } + if (membersCollection[member.name] != null && !isOverride) { AddError(errors, typeName, TypesXmlStrings.DuplicateMember, member.name); return; } + member.IsInstance = false; if (membersCollection[member.name] == null) @@ -2660,7 +2898,7 @@ private static bool GetCheckNote(ConcurrentBag errors, string typeName, for (int i = 0; i < members.Count; i++) { PSMemberInfo member = members[i]; - if (String.Compare(member.Name, noteName, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(member.Name, noteName, StringComparison.OrdinalIgnoreCase)) { noteAsMemberInfo = member; } @@ -2690,11 +2928,13 @@ private static bool GetCheckNote(ConcurrentBag errors, string typeName, } else { - note.noteValue = String.Compare(sourceValueAsString, "false", StringComparison.OrdinalIgnoreCase) != 0; + note.noteValue = !string.Equals(sourceValueAsString, "false", StringComparison.OrdinalIgnoreCase); } + return true; } } + try { note.noteValue = LanguagePrimitives.ConvertTo(sourceValue, noteType, CultureInfo.InvariantCulture); @@ -2704,6 +2944,7 @@ private static bool GetCheckNote(ConcurrentBag errors, string typeName, AddError(errors, typeName, TypesXmlStrings.ErrorConvertingNote, note.Name, e.Message); return false; } + return true; } @@ -2712,12 +2953,13 @@ private static bool EnsureNotPresent(ConcurrentBag errors, string typeNa for (int i = 0; i < members.Count; i++) { PSMemberInfo member = members[i]; - if (String.Compare(member.Name, memberName, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(member.Name, memberName, StringComparison.OrdinalIgnoreCase)) { AddError(errors, typeName, TypesXmlStrings.MemberShouldNotBePresent, member.Name); return false; } } + return true; } @@ -2727,7 +2969,7 @@ private static bool GetCheckMemberType(ConcurrentBag errors, string type for (int i = 0; i < members.Count; i++) { PSMemberInfo m = members[i]; - if (String.Compare(m.Name, noteName, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(m.Name, noteName, StringComparison.OrdinalIgnoreCase)) { member = m; } @@ -2748,11 +2990,10 @@ private static bool GetCheckMemberType(ConcurrentBag errors, string type return false; } - /// /// 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 @@ -2762,7 +3003,7 @@ private static bool GetCheckMemberType(ConcurrentBag errors, string type /// --------------------- ------------------------------- ------------------------ ------------------- --------------------------- /// String must NOT be present must NOT be present must NOT be present optional /// SpecificProperties optional must be present optional optional - /// AllPublicProperties must NOT be present must NOT be present optional optional + /// AllPublicProperties must NOT be present must NOT be present optional optional. /// private static bool CheckStandardMembers(ConcurrentBag errors, string typeName, PSMemberInfoInternalCollection members) { @@ -2774,12 +3015,13 @@ private static bool CheckStandardMembers(ConcurrentBag errors, string ty string memberName = members[i].Name; for (int j = 0; j < s_standardMembers.Length; j++) { - if (String.Equals(memberName, s_standardMembers[j], StringComparison.OrdinalIgnoreCase)) + if (string.Equals(memberName, s_standardMembers[j], StringComparison.OrdinalIgnoreCase)) { found = true; break; } } + if (!found) { membersToBeIgnored.Add(memberName); @@ -2798,62 +3040,109 @@ private static bool CheckStandardMembers(ConcurrentBag errors, string ty { PSNoteProperty serializationMethodNote; serializationSettingsOk = GetCheckNote(errors, typeName, members, SerializationMethodNode, typeof(SerializationMethod), out serializationMethodNote); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + SerializationMethod serializationMethod = SerializationMethod.AllPublicProperties; if (serializationMethodNote != null) { serializationMethod = (SerializationMethod)serializationMethodNote.Value; } + if (serializationMethod == SerializationMethod.String) { serializationSettingsOk = EnsureNotPresent(errors, typeName, members, InheritPropertySerializationSet); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + serializationSettingsOk = EnsureNotPresent(errors, typeName, members, PropertySerializationSet); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + serializationSettingsOk = EnsureNotPresent(errors, typeName, members, SerializationDepth); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } } else if (serializationMethod == SerializationMethod.SpecificProperties) { PSNoteProperty inheritPropertiesNote; serializationSettingsOk = GetCheckNote(errors, typeName, members, InheritPropertySerializationSet, typeof(bool), out inheritPropertiesNote); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } PSMemberInfo propertySerializationSet; serializationSettingsOk = GetCheckMemberType(errors, typeName, members, PropertySerializationSet, typeof(PSPropertySet), out propertySerializationSet); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + if (inheritPropertiesNote != null && inheritPropertiesNote.Value.Equals(false) && propertySerializationSet == null) { - AddError(errors, typeName, TypesXmlStrings.MemberMustBePresent, - PropertySerializationSet, SerializationMethodNode, - SerializationMethod.SpecificProperties.ToString(), - InheritPropertySerializationSet, "false"); + AddError( + errors, + typeName, + TypesXmlStrings.MemberMustBePresent, + PropertySerializationSet, + SerializationMethodNode, + nameof(SerializationMethod.SpecificProperties), + InheritPropertySerializationSet, + "false"); serializationSettingsOk = false; break; } + PSNoteProperty noteProperty; serializationSettingsOk = GetCheckNote(errors, typeName, members, SerializationDepth, typeof(int), out noteProperty); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } } else if (serializationMethod == SerializationMethod.AllPublicProperties) { serializationSettingsOk = EnsureNotPresent(errors, typeName, members, InheritPropertySerializationSet); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + serializationSettingsOk = EnsureNotPresent(errors, typeName, members, PropertySerializationSet); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } + PSNoteProperty noteProperty; serializationSettingsOk = GetCheckNote(errors, typeName, members, SerializationDepth, typeof(int), out noteProperty); - if (!serializationSettingsOk) break; + if (!serializationSettingsOk) + { + break; + } } PSMemberInfo serializationSource; serializationSettingsOk = GetCheckMemberType(errors, typeName, members, StringSerializationSource, typeof(PSPropertyInfo), out serializationSource); - if (!serializationSettingsOk) break; - } while (false); + if (!serializationSettingsOk) + { + break; + } + } + while (false); - if (serializationSettingsOk == false) + if (!serializationSettingsOk) { AddError(errors, typeName, TypesXmlStrings.SerializationSettingsIgnored); members.Remove(InheritPropertySerializationSet); @@ -2864,31 +3153,30 @@ private static bool CheckStandardMembers(ConcurrentBag errors, string ty } PSMemberInfo otherMember; - if (!GetCheckMemberType(errors, typeName, members, - DefaultDisplayPropertySet, typeof(PSPropertySet), out otherMember)) + if (!GetCheckMemberType(errors, typeName, members, DefaultDisplayPropertySet, typeof(PSPropertySet), out otherMember)) { members.Remove(DefaultDisplayPropertySet); } - if (!GetCheckMemberType(errors, typeName, members, - DefaultKeyPropertySet, typeof(PSPropertySet), out otherMember)) + + if (!GetCheckMemberType(errors, typeName, members, DefaultKeyPropertySet, typeof(PSPropertySet), out otherMember)) { members.Remove(DefaultKeyPropertySet); } + PSNoteProperty defaultDisplayProperty; - if (!GetCheckNote(errors, typeName, members, - DefaultDisplayProperty, typeof(string), out defaultDisplayProperty)) + if (!GetCheckNote(errors, typeName, members, DefaultDisplayProperty, typeof(string), out defaultDisplayProperty)) { members.Remove(DefaultDisplayProperty); } + PSNoteProperty targetTypeForDeserialization; - if (!GetCheckNote(errors, typeName, members, - TargetTypeForDeserialization, typeof(Type), out targetTypeForDeserialization)) + if (!GetCheckNote(errors, typeName, members, TargetTypeForDeserialization, typeof(Type), out targetTypeForDeserialization)) { members.Remove(TargetTypeForDeserialization); } else { - if (null != targetTypeForDeserialization) + if (targetTypeForDeserialization != null) { // GetCheckNote converts the value from string to System.Type.. We should store value as Type // as this will save time spent converting string to Type. @@ -2902,7 +3190,7 @@ private static bool CheckStandardMembers(ConcurrentBag errors, string ty #endregion CheckStandardMembers /// - /// Helper for ProcessTypeConverter/ProcessTypeAdapter from TypeData + /// Helper for ProcessTypeConverter/ProcessTypeAdapter from TypeData. /// /// /// @@ -2921,7 +3209,10 @@ private static bool CreateInstance(ConcurrentBag errors, string typeName { instance = Activator.CreateInstance(type); } - catch (TargetInvocationException e) { instanceException = e.InnerException ?? e; } + catch (TargetInvocationException e) + { + instanceException = e.InnerException ?? e; + } catch (Exception e) { instanceException = e; @@ -2950,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); } @@ -2968,7 +3264,7 @@ internal static void ProcessNoteData(ConcurrentBag errors, string typeNa internal static void ProcessAliasData(ConcurrentBag errors, string typeName, AliasPropertyData aliasData, PSMemberInfoInternalCollection membersCollection, bool isOverride) { // ReferencedMemberName should not be an empty string - if (String.IsNullOrEmpty(aliasData.ReferencedMemberName)) + if (string.IsNullOrEmpty(aliasData.ReferencedMemberName)) { AddError(errors, typeName, TypesXmlStrings.TypeDataShouldHaveValue, "AliasPropertyData", "ReferencedMemberName"); return; @@ -3052,6 +3348,7 @@ internal static void ProcessCodeMethodData(ConcurrentBag errors, string AddError(errors, typeName, TypesXmlStrings.Exception, exception.Message); return; } + AddMember(errors, typeName, codeMethod, membersCollection, isOverride); } @@ -3064,14 +3361,15 @@ internal static void ProcessPropertySetData(ConcurrentBag errors, string } // the node cardinality is OneToMany - Collection referencedProperties = new Collection(); + var referencedProperties = new List(propertySetData.ReferencedProperties.Count); foreach (string name in propertySetData.ReferencedProperties) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { AddError(errors, typeName, TypesXmlStrings.TypeDataShouldNotBeNullOrEmpty, "PropertySetData", "ReferencedProperties"); continue; } + referencedProperties.Add(name); } @@ -3089,11 +3387,12 @@ internal static void ProcessPropertySetData(ConcurrentBag errors, string internal static void ProcessMemberSetData(ConcurrentBag errors, string typeName, MemberSetData memberSetData, PSMemberInfoInternalCollection membersCollection, bool isOverride) { - var memberSetMembers = new PSMemberInfoInternalCollection(); + var memberSetMembers = new PSMemberInfoInternalCollection(memberSetData.Members.Count); foreach (var m in memberSetData.Members) { m.Process(errors, typeName, memberSetMembers, isOverride); } + var memberSet = new PSMemberSet(memberSetData.Name, memberSetMembers) { IsHidden = memberSetData.IsHidden, @@ -3102,12 +3401,20 @@ internal static void ProcessMemberSetData(ConcurrentBag errors, string t AddMember(errors, typeName, memberSet, membersCollection, isOverride); } - private static void ProcessStandardMembers(ConcurrentBag errors, string typeName, IEnumerable standardMembers, IEnumerable propertySets, PSMemberInfoInternalCollection membersCollection, bool isOverride) + private static void ProcessStandardMembers( + ConcurrentBag errors, + string typeName, + Dictionary standardMembers, + List propertySets, + PSMemberInfoInternalCollection membersCollection, + bool isOverride) { + int newMemberCount = standardMembers.Count + propertySets.Count; + // If StandardMembers do not exists, we follow the original logic to create the StandardMembers if (membersCollection[PSStandardMembers] == null) { - var memberSetMembers = new PSMemberInfoInternalCollection(); + var memberSetMembers = new PSMemberInfoInternalCollection(newMemberCount); ProcessMembersData(errors, typeName, standardMembers, memberSetMembers, false); foreach (PropertySetData propertySet in propertySets) @@ -3130,8 +3437,9 @@ private static void ProcessStandardMembers(ConcurrentBag errors, string var psStandardMemberSet = (PSMemberSet)membersCollection[PSStandardMembers]; // Copy existing internal PSStandard members - var existingMembers = new PSMemberInfoInternalCollection(); - var oldMembersCopy = new PSMemberInfoInternalCollection(); + int totalMemberCount = psStandardMemberSet.InternalMembers.Count + newMemberCount; + var existingMembers = new PSMemberInfoInternalCollection(totalMemberCount); + var oldMembersCopy = new PSMemberInfoInternalCollection(totalMemberCount); foreach (var existingMember in psStandardMemberSet.InternalMembers) { existingMembers.Add(existingMember.Copy()); @@ -3167,6 +3475,7 @@ private static void ProcessStandardMembers(ConcurrentBag errors, string oldMembersCopy.Add(member); } } + PSMemberSet standardMemberSet = new PSMemberSet(PSStandardMembers, oldMembersCopy) { inheritMembers = true, @@ -3177,21 +3486,165 @@ private static void ProcessStandardMembers(ConcurrentBag errors, string } } + private static void ProcessStandardMembers( + ConcurrentBag errors, + string typeName, + PSMemberInfoInternalCollection memberSetMembers, + PSMemberInfoInternalCollection typeMemberCollection, + bool isOverride) + { + // If StandardMembers do not exists, we follow the original logic to create the StandardMembers + if (typeMemberCollection[PSStandardMembers] == null) + { + CheckStandardMembers(errors, typeName, memberSetMembers); + PSMemberSet standardMemberSet = new PSMemberSet(PSStandardMembers, memberSetMembers) + { + inheritMembers = true, + IsHidden = true, + ShouldSerialize = false + }; + AddMember(errors, typeName, standardMemberSet, typeMemberCollection, false); + return; + } + + // StandardMembers exist + var psStandardMemberSet = (PSMemberSet)typeMemberCollection[PSStandardMembers]; + + // Copy existing internal PSStandard members + int totalMemberCount = psStandardMemberSet.InternalMembers.Count + memberSetMembers.Count; + var existingMembers = new PSMemberInfoInternalCollection(totalMemberCount); + var oldMembersCopy = new PSMemberInfoInternalCollection(totalMemberCount); + foreach (var existingMember in psStandardMemberSet.InternalMembers) + { + existingMembers.Add(existingMember.Copy()); + oldMembersCopy.Add(existingMember.Copy()); + } + + // Process the Members directly into the 'existingMembers' collection + foreach (PSMemberInfo member in memberSetMembers) + { + AddMember(errors, typeName, member, existingMembers, isOverride); + } + + if (CheckStandardMembers(errors, typeName, existingMembers)) + { + // No conflict in serialization settings, replace the old StandardMembers with the new one + PSMemberSet standardMemberSet = new PSMemberSet(PSStandardMembers, existingMembers) + { + inheritMembers = true, + IsHidden = true, + ShouldSerialize = false + }; + AddMember(errors, typeName, standardMemberSet, typeMemberCollection, isOverride: true); + } + else + { + // There are conflicts in serialization settings, add non-serializationSetting configurations + // into the original member collection. Replace the old StandardMembers with the new one + foreach (PSMemberInfo member in existingMembers) + { + if (oldMembersCopy[member.name] == null) + { + oldMembersCopy.Add(member); + } + } + + PSMemberSet standardMemberSet = new PSMemberSet(PSStandardMembers, oldMembersCopy) + { + inheritMembers = true, + IsHidden = true, + ShouldSerialize = false + }; + AddMember(errors, typeName, standardMemberSet, typeMemberCollection, isOverride: true); + } + } + + private static void ProcessTypeConverter( + ConcurrentBag errors, + string typeName, + Type converterType, + ConcurrentDictionary typeConverters, + bool isOverride) + { + if (CreateInstance(errors, typeName, converterType, TypesXmlStrings.UnableToInstantiateTypeConverter, out object instance)) + { + if ((instance is TypeConverter) || (instance is PSTypeConverter)) + { + LanguagePrimitives.UpdateTypeConvertFromTypeTable(typeName); + } + else + { + AddError(errors, typeName, TypesXmlStrings.TypeIsNotTypeConverter, converterType.FullName); + } + } + + if (instance != null && !typeConverters.TryAdd(typeName, instance)) + { + if (!isOverride) + { + AddError(errors, typeName, TypesXmlStrings.TypeConverterAlreadyPresent); + } + + // If IsOverride == true, eat the TypeConverterAlreadyPresent failure. + } + } + + private static void ProcessTypeAdapter( + ConcurrentBag errors, + string typeName, + Type adapterType, + ConcurrentDictionary typeAdapters, + bool isOverride) + { + PSObject.AdapterSet adapterSet = null; + if (CreateInstance(errors, typeName, adapterType, TypesXmlStrings.UnableToInstantiateTypeAdapter, out object instance)) + { + PSPropertyAdapter psPropertyAdapter = instance as PSPropertyAdapter; + + if (psPropertyAdapter == null) + { + AddError(errors, typeName, TypesXmlStrings.TypeIsNotTypeAdapter, adapterType.FullName); + } + else + { + if (LanguagePrimitives.TryConvertTo(typeName, out Type adaptedType)) + { + adapterSet = PSObject.CreateThirdPartyAdapterSet(adaptedType, psPropertyAdapter); + } + else + { + AddError(errors, typeName, TypesXmlStrings.InvalidAdaptedType, typeName); + } + } + } + + if (adapterSet != null && !typeAdapters.TryAdd(typeName, adapterSet)) + { + if (!isOverride) + { + AddError(errors, typeName, TypesXmlStrings.TypeAdapterAlreadyPresent); + } + + // If IsOverride == true, eat the TypeConverterAlreadyPresent failure. + } + } private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeData) { string typeName = typeData.TypeName; - Dbg.Assert(!String.IsNullOrEmpty(typeName), "TypeData class guarantees the typeName is not null and not empty"); + 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); } + if (typeData.DefaultKeyPropertySet != null) { propertySets.Add(typeData.DefaultKeyPropertySet); } + if (typeData.PropertySerializationSet != null) { propertySets.Add(typeData.PropertySerializationSet); @@ -3205,12 +3658,14 @@ private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeDat return; } + PSMemberInfoInternalCollection typeMembers = null; + bool hasStandardMembers = typeData.StandardMembers.Count > 0 || propertySets.Count > 0; + int collectionSize = typeData.Members.Count + (hasStandardMembers ? 1 : 0); + if (typeData.Members.Count > 0) { - PSMemberInfoInternalCollection typeMembers - = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); - - ProcessMembersData(errors, typeName, typeData.Members.Values, typeMembers, typeData.IsOverride); + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(collectionSize)); + ProcessMembersData(errors, typeName, typeData.Members, typeMembers, typeData.IsOverride); foreach (var memberName in typeData.Members.Keys) { @@ -3218,77 +3673,26 @@ PSMemberInfoInternalCollection typeMembers } } - if (typeData.StandardMembers.Count > 0 || propertySets.Count > 0) + if (hasStandardMembers) { - PSMemberInfoInternalCollection typeMembers - = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); + 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) { - object instance = null; - if (CreateInstance(errors, typeName, typeData.TypeConverter, TypesXmlStrings.UnableToInstantiateTypeConverter, out instance)) - { - if ((instance is TypeConverter) || (instance is PSTypeConverter)) - { - LanguagePrimitives.UpdateTypeConvertFromTypeTable(typeName); - } - else - { - AddError(errors, typeName, TypesXmlStrings.TypeIsNotTypeConverter, typeData.TypeConverter.FullName); - } - } - - if (instance != null && !_typeConverters.TryAdd(typeName, instance)) - { - if (!typeData.IsOverride) - { - AddError(errors, typeName, TypesXmlStrings.TypeConverterAlreadyPresent); - } - // If IsOverride == true, eat the TypeConverterAlreadyPresent failure. - } + ProcessTypeConverter(errors, typeName, typeData.TypeConverter, _typeConverters, typeData.IsOverride); } if (typeData.TypeAdapter != null) { - object instance; - PSObject.AdapterSet adapterSet = null; - if (CreateInstance(errors, typeName, typeData.TypeAdapter, TypesXmlStrings.UnableToInstantiateTypeAdapter, out instance)) - { - PSPropertyAdapter psPropertyAdapter = instance as PSPropertyAdapter; - - if (psPropertyAdapter == null) - { - AddError(errors, typeName, TypesXmlStrings.TypeIsNotTypeAdapter, typeData.TypeAdapter.FullName); - } - else - { - Type adaptedType = null; - if (LanguagePrimitives.TryConvertTo(typeName, out adaptedType)) - { - adapterSet = PSObject.CreateThirdPartyAdapterSet(adaptedType, psPropertyAdapter); - } - else - { - AddError(errors, typeName, TypesXmlStrings.InvalidAdaptedType, typeName); - } - } - } - - if (adapterSet != null && !_typeAdapters.TryAdd(typeName, adapterSet)) - { - if (!typeData.IsOverride) - { - AddError(errors, typeName, TypesXmlStrings.TypeAdapterAlreadyPresent); - } - // If IsOverride == true, eat the TypeConverterAlreadyPresent failure. - } + ProcessTypeAdapter(errors, typeName, typeData.TypeAdapter, _typeAdapters, typeData.IsOverride); } + // Record the information that this typedata was removed from the typetable // The next time the typetable is updated, we will need to exclude this typedata from the typetable - typesInfo.Add(new SessionStateTypeEntry(typeData, false)); + typesInfo.Add(new SessionStateTypeEntry(typeData, isRemove: false)); } #endregion add members from TypeData @@ -3298,7 +3702,7 @@ PSMemberInfoInternalCollection typeMembers private void ProcessTypeDataToRemove(ConcurrentBag errors, TypeData typeData) { string typeName = typeData.TypeName; - Dbg.Assert(!String.IsNullOrEmpty(typeName), "TypeData class guarantees the typeName is not null and not empty"); + Dbg.Assert(!string.IsNullOrEmpty(typeName), "TypeData class guarantees the typeName is not null and not empty"); // We always remove the whole type bool typeExist = false; @@ -3313,15 +3717,13 @@ private void ProcessTypeDataToRemove(ConcurrentBag errors, TypeData type } } - object unused1; - if (_typeConverters.TryRemove(typeName, out unused1)) + if (_typeConverters.TryRemove(typeName, out _)) { typeExist = true; LanguagePrimitives.UpdateTypeConvertFromTypeTable(typeName); } - PSObject.AdapterSet unused2; - if (_typeAdapters.TryRemove(typeName, out unused2)) + if (_typeAdapters.TryRemove(typeName, out _)) { typeExist = true; } @@ -3380,17 +3782,25 @@ private void Update(ConcurrentBag errors, TypeData typeData, bool isRemo static TypeTable() { + s_valueFactoryCache = new Func>[ValueFactoryCacheCount]; + // Rather than set these members every time we process the standard members, do it // just once at startup. foreach (var sm in s_standardMembers) { PSGetMemberBinder.TypeTableMemberAdded(sm); } + PSGetMemberBinder.TypeTableMemberAdded(PSStandardMembers); + + // Set the built-in type file paths. + var psHome = Utils.DefaultPowerShellAppBase; + TypesFilePath = Path.Combine(psHome, "types.ps1xml"); + TypesV3FilePath = Path.Combine(psHome, "typesv3.ps1xml"); + GetEventTypesFilePath = Path.Combine(psHome, "GetEvent.types.ps1xml"); } /// - /// /// internal TypeTable() : this(isShared: false) { @@ -3400,6 +3810,7 @@ internal TypeTable(bool isShared) { this.isShared = isShared; _typeFileList = new List(); + _memberFactoryFunc = MemberFactory; } /// @@ -3420,34 +3831,24 @@ public TypeTable(IEnumerable typeFiles) : this(typeFiles, null, null) } /// - /// Load types.ps1xml, typesv3.ps1xml into the typetable + /// Load types.ps1xml, typesv3.ps1xml into the typetable. /// /// - /// if caller doesn't have permission to read the PowerShell registry key + /// If caller doesn't have permission to read the PowerShell registry key. /// - /// TypeTable + /// TypeTable. public static TypeTable LoadDefaultTypeFiles() { return new TypeTable(GetDefaultTypeFiles()); } /// - /// Gets the default types files available in PowerShell + /// Gets the default types files available in PowerShell. /// - /// list of type files - public static List GetDefaultTypeFiles() + /// List of type files. + public static List GetDefaultTypeFiles() { - string typesFilePath = string.Empty; - string typesV3FilePath = string.Empty; - - var psHome = Utils.DefaultPowerShellAppBase; - if (!string.IsNullOrEmpty(psHome)) - { - typesFilePath = Path.Combine(psHome, "types.ps1xml"); - typesV3FilePath = Path.Combine(psHome, "typesv3.ps1xml"); - } - - return new List() { typesFilePath, typesV3FilePath }; + return new List() { TypesFilePath, TypesV3FilePath }; } /// @@ -3469,16 +3870,13 @@ public static List GetDefaultTypeFiles() /// 1. There were errors loading TypeTable. Look in the Errors property to get /// detailed error messages. /// - internal TypeTable(IEnumerable typeFiles, AuthorizationManager authorizationManager, PSHost host) + internal TypeTable(IEnumerable typeFiles, AuthorizationManager authorizationManager, PSHost host) : this(isShared: true) { - if (null == typeFiles) + if (typeFiles == null) { - throw PSTraceSource.NewArgumentNullException("typeFiles"); + throw PSTraceSource.NewArgumentNullException(nameof(typeFiles)); } - isShared = true; - - _typeFileList = new List(); ConcurrentBag errors = new ConcurrentBag(); foreach (string typefile in typeFiles) { @@ -3487,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); } @@ -3503,46 +3900,57 @@ internal TypeTable(IEnumerable typeFiles, AuthorizationManager authoriza #region internal methods /// - /// The first type in the type hierarchy is guaranteed to have SpecificProperties + /// The first type in the type hierarchy is guaranteed to have SpecificProperties. /// /// - /// null if this should not be serialized with SpecificProperties - internal Collection GetSpecificProperties(ConsolidatedString types) + /// Null if this should not be serialized with SpecificProperties. + internal Collection GetSpecificProperties(ConsolidatedString types) { if (types == null || string.IsNullOrEmpty(types.Key)) { - return new Collection(); + return new Collection(); } - Collection result = _consolidatedSpecificProperties.GetOrAdd(types.Key, key => + + Collection result = _consolidatedSpecificProperties.GetOrAdd(types.Key, key => { var retValueTable = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string type in types) { - PSMemberInfoInternalCollection typeMembers; - if (!_extendedMembers.TryGetValue(type, out typeMembers)) + if (!_extendedMembers.TryGetValue(type, out var typeMembers)) + { continue; + } + PSMemberSet settings = typeMembers[PSStandardMembers] as PSMemberSet; - if (settings == null) - continue; - PSPropertySet typeProperties = settings.Members[PropertySerializationSet] as PSPropertySet; - if (typeProperties == null) + if (settings?.Members[PropertySerializationSet] is not PSPropertySet typeProperties) + { continue; + } + foreach (string reference in typeProperties.ReferencedPropertyNames) { retValueTable.Add(reference); } - bool inherit = (bool)PSObject.GetNoteSettingValue(settings, InheritPropertySerializationSet, - defaultInheritPropertySerializationSet, typeof(bool), false, null); + + bool inherit = (bool)PSObject.GetNoteSettingValue( + settings, + InheritPropertySerializationSet, + DefaultInheritPropertySerializationSet, + expectedType: typeof(bool), + shouldReplicateInstance: false, + ownerObject: null); if (!inherit) { break; } } - var retValue = new Collection(); + + var retValue = new Collection(); foreach (var value in retValueTable) { retValue.Add(value); } + return retValue; }); return result; @@ -3552,79 +3960,91 @@ internal Collection GetSpecificProperties(ConsolidatedString types) /// Gets the MemberInfoCollection for types. This method will cache its /// return value for future reference to the same types. /// - /// list of types to get the member from + /// List of types to get the member from. /// internal PSMemberInfoInternalCollection GetMembers(ConsolidatedString types) where T : PSMemberInfo { return PSObject.TransformMemberInfoCollection(GetMembers(types)); } + internal T GetFirstMemberOrDefault(ConsolidatedString types, MemberNamePredicate predicate) where T : PSMemberInfo + { + return GetMembers(types).FirstOrDefault(member => member is T && predicate(member.Name)) as T; + } - private PSMemberInfoInternalCollection GetMembers(ConsolidatedString types) + internal PSMemberInfoInternalCollection GetMembers(ConsolidatedString types) { if ((types == null) || string.IsNullOrEmpty(types.Key)) { return new PSMemberInfoInternalCollection(); } - PSMemberInfoInternalCollection result = _consolidatedMembers.GetOrAdd(types.Key, k => + + return _consolidatedMembers.GetOrAdd(types.Key, _memberFactoryFunc, types); + } + + private PSMemberInfoInternalCollection MemberFactory(string k, ConsolidatedString types) + { + var retValue = new PSMemberInfoInternalCollection(); + for (int i = types.Count - 1; i >= 0; i--) { - var retValue = new PSMemberInfoInternalCollection(); - for (int i = types.Count - 1; i >= 0; i--) + if (!_extendedMembers.TryGetValue(types[i], out var typeMembers)) { - PSMemberInfoInternalCollection typeMembers; - if (!_extendedMembers.TryGetValue(types[i], out typeMembers)) + continue; + } + + foreach (PSMemberInfo typeMember in typeMembers) + { + PSMemberInfo currentMember = retValue[typeMember.Name]; + + // If the member was not present, we add it + if (currentMember == null) { + retValue.Add(typeMember.Copy()); continue; } - foreach (PSMemberInfo typeMember in typeMembers) + + // There was a currentMember with the same name as typeMember + PSMemberSet currentMemberAsMemberSet = currentMember as PSMemberSet; + PSMemberSet typeMemberAsMemberSet = typeMember as PSMemberSet; + + // if we are not in a memberset inherit members situation we just replace + // the current member with the new more specific member + if (currentMemberAsMemberSet == null || typeMemberAsMemberSet == null || + !typeMemberAsMemberSet.InheritMembers) { - PSMemberInfo currentMember = retValue[typeMember.Name]; - // If the member was not present, we add it - if (currentMember == null) - { - retValue.Add(typeMember.Copy()); - continue; - } - // There was a currentMember with the same name as typeMember - PSMemberSet currentMemberAsMemberSet = currentMember as PSMemberSet; - PSMemberSet typeMemberAsMemberSet = typeMember as PSMemberSet; - // if we are not in a memberset inherit members situation we just replace - // the current member with the new more specific member - if (currentMemberAsMemberSet == null || typeMemberAsMemberSet == null || - !typeMemberAsMemberSet.InheritMembers) + retValue.Remove(typeMember.Name); + retValue.Add(typeMember.Copy()); + continue; + } + + // We are in a MemberSet InheritMembers situation, so we add the members in + // typeMembers to the existing memberset. + foreach (PSMemberInfo typeMemberAsMemberSetMember in typeMemberAsMemberSet.Members) + { + if (currentMemberAsMemberSet.Members[typeMemberAsMemberSetMember.Name] == null) { - retValue.Remove(typeMember.Name); - retValue.Add(typeMember.Copy()); + ((PSMemberInfoIntegratingCollection)currentMemberAsMemberSet.Members) + .AddToTypesXmlCache(typeMemberAsMemberSetMember, false); continue; } - // We are in a MemberSet InheritMembers situation, so we add the members in - // typeMembers to the existing memberset. - foreach (PSMemberInfo typeMemberAsMemberSetMember in typeMemberAsMemberSet.Members) - { - if (currentMemberAsMemberSet.Members[typeMemberAsMemberSetMember.Name] == null) - { - ((PSMemberInfoIntegratingCollection)currentMemberAsMemberSet.Members) - .AddToTypesXmlCache(typeMemberAsMemberSetMember, false); - continue; - } - // there is a name conflict, the new member wins. - Diagnostics.Assert(!typeMemberAsMemberSetMember.IsHidden, - "new member in types.xml cannot be hidden"); - currentMemberAsMemberSet.InternalMembers.Replace(typeMemberAsMemberSetMember); - } + + // there is a name conflict, the new member wins. + Diagnostics.Assert( + !typeMemberAsMemberSetMember.IsHidden, + "new member in types.xml cannot be hidden"); + currentMemberAsMemberSet.InternalMembers.Replace(typeMemberAsMemberSetMember); } } + } - return retValue; - }); - return result; + return retValue; } /// - /// Gets the type converter for the typeName + /// Gets the type converter for the typeName. /// - /// type name with the converter - /// the type converter for the typeName or null, if there is no type converter + /// Type name with the converter. + /// The type converter for the typeName or null, if there is no type converter. internal object GetTypeConverter(string typeName) { if (string.IsNullOrEmpty(typeName)) @@ -3638,9 +4058,9 @@ internal object GetTypeConverter(string typeName) } /// - /// Gets the type adapter for the given type + /// Gets the type adapter for the given type. /// - /// the type adapter or null, if there is no adapter + /// The type adapter or null, if there is no adapter. internal PSObject.AdapterSet GetTypeAdapter(Type type) { if (type == null) @@ -3678,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) @@ -3733,6 +4153,7 @@ private TypeMemberData GetTypeMemberDataFromPSMemberInfo(PSMemberInfo member) { membersData.Add(GetTypeMemberDataFromPSMemberInfo(m)); } + return new MemberSetData(memberSet.Name, membersData); } @@ -3740,11 +4161,11 @@ private TypeMemberData GetTypeMemberDataFromPSMemberInfo(PSMemberInfo member) } /// - /// Load a PSMemberInfo instance to the passed-in TypeData + /// Load a PSMemberInfo instance to the passed-in TypeData. /// /// /// - 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"); @@ -3767,7 +4188,7 @@ private void LoadMembersToTypeData(PSMemberInfo member, TypeData typeData) } /// - /// Helper function to convert an object to a specific type + /// Helper function to convert an object to a specific type. /// /// /// @@ -3778,9 +4199,9 @@ private static T GetParameterType(object sourceValue) } /// - /// Load the standard members into the passed-in TypeData + /// 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) { @@ -3841,7 +4262,7 @@ private void LoadStandardMembersToTypeData(PSMemberSet memberSet, TypeData typeD } /// - /// Get all Type configurations, return a Dictionary with typeName as the key, TypeData as the value + /// Get all Type configurations, return a Dictionary with typeName as the key, TypeData as the value. /// /// internal Dictionary GetAllTypeData() @@ -3918,6 +4339,7 @@ private bool RetrieveMembersToTypeData(TypeData typeData) PSMemberInfo newMember = member.Copy(); LoadMembersToTypeData(newMember, typeData); } + return true; } @@ -4012,6 +4434,7 @@ internal void Clear() { LanguagePrimitives.UpdateTypeConvertFromTypeTable(conv); } + _typeConverters.Clear(); foreach (var ml in _extendedMembers.Values) @@ -4021,6 +4444,7 @@ internal void Clear() PSGetMemberBinder.TypeTableMemberPossiblyUpdated(m.Name); } } + _extendedMembers.Clear(); StandardMembersUpdated(); @@ -4043,6 +4467,7 @@ private static void StandardMembersUpdated() { PSGetMemberBinder.TypeTableMemberPossiblyUpdated(sm); } + PSGetMemberBinder.TypeTableMemberPossiblyUpdated(PSStandardMembers); } @@ -4060,7 +4485,7 @@ private static void StandardMembersUpdated() /// /// Host passed to . Can be null if no interactive questions should be asked. /// - /// Indicate if the file failed to be loaded + /// Indicate if the file failed to be loaded. internal void Initialize( string snapinName, string fileToLoad, @@ -4070,7 +4495,9 @@ internal void Initialize( out bool failToLoadFile) { if (ProcessIsBuiltIn(fileToLoad, errors, out failToLoadFile)) + { return; + } bool isFullyTrusted; bool isProductCode; @@ -4087,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, @@ -4153,12 +4580,12 @@ private string GetModuleContents( /// /// Helper method to update with module file contents. /// - /// Module contents - /// Module name - /// Module file path - /// Whether the module contents are fully trusted - /// Whether the module contents are considered part of Windows (e.g. catalog signed) - /// Errors + /// Module contents. + /// Module name. + /// Module file path. + /// Whether the module contents are fully trusted. + /// Whether the module contents are considered part of Windows (e.g. catalog signed). + /// Errors. private void UpdateWithModuleContents( string fileContents, string moduleName, @@ -4167,7 +4594,6 @@ private void UpdateWithModuleContents( bool isProductCode, ConcurrentBag errors) { - typesInfo.Add(new SessionStateTypeEntry(fileToLoad)); LoadContext loadContext = new LoadContext(moduleName, fileToLoad, errors) { IsFullyTrusted = isFullyTrusted, @@ -4183,30 +4609,16 @@ private void UpdateWithModuleContents( } } - /// - /// Removes the from the current TypeTable's type file list. - /// The TypeTable will not reflect the change until Update is called. - /// - /// - internal void Remove(string typeFile) - { - lock (_typeFileList) - { - _typeFileList.Remove(typeFile); - typesInfo.Remove(typeFile, null); - } - } - /// /// Update the TypeTable by adding a TypeData instance. /// - /// throw when the argument is null - /// throw when there were failures during the update - /// a TypeData instance to update the TypeTable + /// Throw when the argument is null. + /// Throw when there were failures during the update. + /// A TypeData instance to update the TypeTable. public void AddType(TypeData typeData) { if (typeData == null) - throw PSTraceSource.NewArgumentNullException("typeData"); + throw PSTraceSource.NewArgumentNullException(nameof(typeData)); Dbg.Assert(isShared, "This method should only be called by the developer user. It should not be used internally."); @@ -4218,20 +4630,23 @@ public void AddType(TypeData typeData) Update(errors, typeData, false); StandardMembersUpdated(); + // Throw exception if there are any errors - FormatAndTypeDataHelper.ThrowExceptionOnError("ErrorsUpdatingTypes", errors, RunspaceConfigurationCategory.Types); + FormatAndTypeDataHelper.ThrowExceptionOnError("ErrorsUpdatingTypes", errors, FormatAndTypeDataHelper.Category.Types); } /// /// Remove all type information related to the type name. /// - /// throw when the argument is null or empty - /// throw if there were failures when remove the type - /// the name of the type to remove from TypeTable + /// Throw when the argument is null or empty. + /// Throw if there were failures when remove the type. + /// The name of the type to remove from TypeTable. public void RemoveType(string typeName) { - if (String.IsNullOrEmpty(typeName)) - throw PSTraceSource.NewArgumentNullException("typeName"); + if (string.IsNullOrEmpty(typeName)) + { + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); + } Dbg.Assert(isShared, "This method should only be called by the developer user. It should not be used internally."); @@ -4244,88 +4659,16 @@ public void RemoveType(string typeName) Update(errors, typeData, true); StandardMembersUpdated(); - // Throw exception if there are any errors - FormatAndTypeDataHelper.ThrowExceptionOnError("ErrorsUpdatingTypes", errors, RunspaceConfigurationCategory.Types); - } - - /// - /// Update typetable from psSnapinTypes, this method will always rebuild the typetable. - /// The psSnapinTypes contain files and strong type data - /// - /// - /// - /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) - /// - /// - /// Host passed to . Can be null if no interactive questions should be asked. - /// - /// - /// 1. The TypeTable cannot be updated because the TypeTable might have - /// been created outside of the Runspace. - /// - internal void Update( - Collection psSnapinTypes, - AuthorizationManager authorizationManager, - PSHost host - ) - { - if (isShared) - { - throw PSTraceSource.NewInvalidOperationException(TypesXmlStrings.SharedTypeTableCannotBeUpdated); - } - // Always rebuild the whole TypeTable - Clear(); - - foreach (PSSnapInTypeAndFormatErrors snapin in psSnapinTypes) - { - // FullPath is not null, then it is a type xml file - if (snapin.FullPath != null) - { - Initialize(snapin.PSSnapinName, snapin.FullPath, snapin.Errors, authorizationManager, host, - out snapin.FailToLoadFile); - } - // FullPath is null, then it is a TypeData - else - { - Update(snapin.Errors, snapin.TypeData, snapin.IsRemove); - } - } - } - - /// - /// Entry created to make reflection-based test suites happy. DO NOT USE THIS ENTRY - /// - /// The path to the file to load - /// A place to put the errors... - /// If true, reset the table to empty... - /// - /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) - /// - /// - /// Host passed to . Can be null if no interactive questions should be asked. - /// - /// Indicate if the file cannot be loaded due to the security reason - /// - /// 1. The TypeTable cannot be updated because the TypeTable might have - /// been created outside of the Runspace. - /// - internal void Update( - string filePath, - ConcurrentBag errors, - bool clearTable, - AuthorizationManager authorizationManager, - PSHost host, - out bool failToLoadFile) - { - Update(filePath, filePath, errors, authorizationManager, host, out failToLoadFile); + // Throw exception if there are any errors + FormatAndTypeDataHelper.ThrowExceptionOnError("ErrorsUpdatingTypes", errors, FormatAndTypeDataHelper.Category.Types); } /// /// Update type data from a specific file... /// /// The name of the module or snapin that this file is associated with. - /// The path to the file to load + /// The path to the file to load. /// A place to put the errors... /// /// Authorization manager to perform signature checks before reading ps1xml files (or null of no checks are needed) @@ -4333,7 +4676,7 @@ internal void Update( /// /// Host passed to . Can be null if no interactive questions should be asked. /// - /// Indicate if the file cannot be loaded due to security reason + /// Indicate if the file cannot be loaded due to security reason. /// /// 1. The TypeTable cannot be updated because the TypeTable might have /// been created outside of the Runspace. @@ -4346,11 +4689,9 @@ internal void Update( PSHost host, out bool failToLoadFile) { - if (filePath == null) - throw new ArgumentNullException("filePath"); + ArgumentNullException.ThrowIfNull(filePath); - if (errors == null) - throw new ArgumentNullException("errors"); + ArgumentNullException.ThrowIfNull(errors); if (isShared) { @@ -4358,7 +4699,10 @@ internal void Update( } var etwEnabled = RunspaceEventSource.Log.IsEnabled(); - if (etwEnabled) RunspaceEventSource.Log.ProcessTypeFileStart(filePath); + if (etwEnabled) + { + RunspaceEventSource.Log.ProcessTypeFileStart(filePath); + } if (!ProcessIsBuiltIn(filePath, errors, out failToLoadFile)) { @@ -4374,20 +4718,11 @@ internal void Update( UpdateWithModuleContents(fileContents, moduleName, filePath, isFullyTrusted, isProductCode, errors); } } - if (etwEnabled) RunspaceEventSource.Log.ProcessTypeFileStop(filePath); - } - private void ProcessTypeData(string filePath, ConcurrentBag errors, IEnumerable types) - { - typesInfo.Add(new SessionStateTypeEntry(filePath)); - - // TODO - use parallel foreach without causing any contention - //Parallel.ForEach(types, typeData => - foreach (var typeData in types) + if (etwEnabled) { - ProcessTypeDataToAdd(errors, typeData); + RunspaceEventSource.Log.ProcessTypeFileStop(filePath); } - //}); } private bool ProcessIsBuiltIn(string filePath, ConcurrentBag errors, out bool failToLoadFile) @@ -4395,20 +4730,19 @@ private bool ProcessIsBuiltIn(string filePath, ConcurrentBag errors, out var result = false; var errorCount = errors.Count; - var psHome = Utils.DefaultPowerShellAppBase; - if (string.Equals(Path.Combine(psHome, "types.ps1xml"), filePath, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(TypesFilePath, filePath, StringComparison.OrdinalIgnoreCase)) { - ProcessTypeData(filePath, errors, Types_Ps1Xml.Get()); + Process_Types_Ps1Xml(filePath, errors); result = true; } - else if (string.Equals(Path.Combine(psHome, "typesv3.ps1xml"), filePath, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(TypesV3FilePath, filePath, StringComparison.OrdinalIgnoreCase)) { - ProcessTypeData(filePath, errors, TypesV3_Ps1Xml.Get()); + Process_TypesV3_Ps1Xml(filePath, errors); result = true; } - else if (string.Equals(Path.Combine(psHome, "GetEvent.types.ps1xml"), filePath, StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(GetEventTypesFilePath, filePath, StringComparison.OrdinalIgnoreCase)) { - ProcessTypeData(filePath, errors, GetEvent_Types_Ps1Xml.Get()); + Process_GetEvent_Types_Ps1Xml(filePath, errors); result = true; } @@ -4417,7 +4751,7 @@ private bool ProcessIsBuiltIn(string filePath, ConcurrentBag errors, out } /// - /// Update typetable from a specific strong type data + /// Update typetable from a specific strong type data. /// /// /// @@ -4427,10 +4761,9 @@ internal void Update( ConcurrentBag errors, bool isRemove) { - if (type == null) - throw new ArgumentNullException("type"); - if (errors == null) - throw new ArgumentNullException("errors"); + ArgumentNullException.ThrowIfNull(type); + + ArgumentNullException.ThrowIfNull(errors); if (isShared) { diff --git a/src/System.Management.Automation/engine/TypeTable_GetEvent_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_GetEvent_Types_Ps1Xml.cs new file mode 100644 index 00000000000..a306c9cd9c8 --- /dev/null +++ b/src/System.Management.Automation/engine/TypeTable_GetEvent_Types_Ps1Xml.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Reflection; + +namespace System.Management.Automation.Runspaces +{ + public sealed partial class TypeTable + { + private void Process_GetEvent_Types_Ps1Xml(string filePath, ConcurrentBag errors) + { + typesInfo.Add(new SessionStateTypeEntry(filePath)); + + string typeName = null; + PSMemberInfoInternalCollection typeMembers = null; + PSMemberInfoInternalCollection memberSetMembers = null; + HashSet newMembers = new HashSet(StringComparer.OrdinalIgnoreCase); + + #region System.Diagnostics.Eventing.Reader.EventLogConfiguration + + typeName = @"System.Diagnostics.Eventing.Reader.EventLogConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "LogName", "MaximumSizeInBytes", "RecordCount", "LogMode" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.Eventing.Reader.EventLogConfiguration + + #region System.Diagnostics.Eventing.Reader.EventLogRecord + + typeName = @"System.Diagnostics.Eventing.Reader.EventLogRecord"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "TimeCreated", "ProviderName", "Id", "Message" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.Eventing.Reader.EventLogRecord + + #region System.Diagnostics.Eventing.Reader.ProviderMetadata + + typeName = @"System.Diagnostics.Eventing.Reader.ProviderMetadata"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"ProviderName"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"ProviderName", @"Name", conversionType: null), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "LogLinks" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.Eventing.Reader.ProviderMetadata + +#if !CORECLR + #region Microsoft.PowerShell.Commands.GetCounter.CounterSet + + typeName = @"Microsoft.PowerShell.Commands.GetCounter.CounterSet"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Counter"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Counter", @"Paths", conversionType: null), + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.GetCounter.CounterSet + + #region Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample + + typeName = @"Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Path", "InstanceName", "CookedValue" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample + + #region Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet + + typeName = @"Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"Readings"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Readings", + GetScriptBlock(@"$strPaths = """" + foreach ($ctr in $this.CounterSamples) + { + $strPaths += ($ctr.Path + "" :"" + ""`n"") + $strPaths += ($ctr.CookedValue.ToString() + ""`n`n"") + } + + return $strPaths"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Timestamp", "Readings" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet +#endif + + // Update binder version for newly added members. + foreach (string memberName in newMembers) + { + PSGetMemberBinder.TypeTableMemberAdded(memberName); + } + } + } +} diff --git a/src/System.Management.Automation/engine/TypeTable_TypesV3_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_TypesV3_Ps1Xml.cs new file mode 100644 index 00000000000..63c2ea1918d --- /dev/null +++ b/src/System.Management.Automation/engine/TypeTable_TypesV3_Ps1Xml.cs @@ -0,0 +1,437 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Reflection; + +namespace System.Management.Automation.Runspaces +{ + public sealed partial class TypeTable + { + private void Process_TypesV3_Ps1Xml(string filePath, ConcurrentBag errors) + { + typesInfo.Add(new SessionStateTypeEntry(filePath)); + + string typeName = null; + PSMemberInfoInternalCollection typeMembers = null; + PSMemberInfoInternalCollection memberSetMembers = null; + HashSet newMembers = new HashSet(StringComparer.OrdinalIgnoreCase); + + #region System.Security.Cryptography.X509Certificates.X509Certificate2 + + typeName = @"System.Security.Cryptography.X509Certificates.X509Certificate2"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"EnhancedKeyUsageList"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"EnhancedKeyUsageList", + GetScriptBlock(@",(new-object Microsoft.Powershell.Commands.EnhancedKeyUsageProperty -argumentlist $this).EnhancedKeyUsageList;"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"DnsNameList"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"DnsNameList", + GetScriptBlock(@",(new-object Microsoft.Powershell.Commands.DnsNameProperty -argumentlist $this).DnsNameList;"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"SendAsTrustedIssuer"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"SendAsTrustedIssuer", + GetScriptBlock(@"[Microsoft.Powershell.Commands.SendAsTrustedIssuerProperty]::ReadSendAsTrustedIssuerProperty($this)"), + GetScriptBlock(@"$sendAsTrustedIssuer = $args[0] + [Microsoft.Powershell.Commands.SendAsTrustedIssuerProperty]::WriteSendAsTrustedIssuerProperty($this,$this.PsPath,$sendAsTrustedIssuer)"), + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Security.Cryptography.X509Certificates.X509Certificate2 + + #region System.Management.Automation.Remoting.PSSenderInfo + + typeName = @"System.Management.Automation.Remoting.PSSenderInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"ConnectedUser"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ConnectedUser", + GetScriptBlock(@"$this.UserInfo.Identity.Name"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"RunAsUser"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"RunAsUser", + GetScriptBlock(@"if($null -ne $this.UserInfo.WindowsIdentity) + { + $this.UserInfo.WindowsIdentity.Name + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Remoting.PSSenderInfo + + #region System.Management.Automation.CompletionResult + + typeName = @"System.Management.Automation.CompletionResult"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CompletionResult + + #region Deserialized.System.Management.Automation.CompletionResult + + typeName = @"Deserialized.System.Management.Automation.CompletionResult"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.CompletionResult + + #region System.Management.Automation.CommandCompletion + + typeName = @"System.Management.Automation.CommandCompletion"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CommandCompletion + + #region Deserialized.System.Management.Automation.CommandCompletion + + typeName = @"Deserialized.System.Management.Automation.CommandCompletion"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.CommandCompletion + + #region Microsoft.PowerShell.Commands.ModuleSpecification + + typeName = @"Microsoft.PowerShell.Commands.ModuleSpecification"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.ModuleSpecification + + #region Deserialized.Microsoft.PowerShell.Commands.ModuleSpecification + + typeName = @"Deserialized.Microsoft.PowerShell.Commands.ModuleSpecification"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.Microsoft.PowerShell.Commands.ModuleSpecification + + #region System.Management.Automation.JobStateEventArgs + + typeName = @"System.Management.Automation.JobStateEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.JobStateEventArgs + + #region Deserialized.System.Management.Automation.JobStateEventArgs + + typeName = @"Deserialized.System.Management.Automation.JobStateEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.JobStateEventArgs + + #region System.Exception + + typeName = @"System.Exception"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Exception + + #region System.Management.Automation.Remoting.PSSessionOption + + typeName = @"System.Management.Automation.Remoting.PSSessionOption"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Remoting.PSSessionOption + + #region Deserialized.System.Management.Automation.Remoting.PSSessionOption + + typeName = @"Deserialized.System.Management.Automation.Remoting.PSSessionOption"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.Remoting.PSSessionOption + + #region System.Management.Automation.DebuggerStopEventArgs + + typeName = @"System.Management.Automation.DebuggerStopEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"SerializedInvocationInfo"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"SerializedInvocationInfo", + GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), @"GetInvocationInfo"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 3); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "Breakpoints", "ResumeAction", "SerializedInvocationInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.DebuggerStopEventArgs + + #region Deserialized.System.Management.Automation.DebuggerStopEventArgs + + typeName = @"Deserialized.System.Management.Automation.DebuggerStopEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.DebuggerStopEventArgs + + // Update binder version for newly added members. + foreach (string memberName in newMembers) + { + PSGetMemberBinder.TypeTableMemberAdded(memberName); + } + } + } +} diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs new file mode 100644 index 00000000000..26e3621c240 --- /dev/null +++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs @@ -0,0 +1,9139 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Reflection; +using System.Threading; + +namespace System.Management.Automation.Runspaces +{ + public sealed partial class TypeTable + { + private const int ValueFactoryCacheCount = 6; + + private static readonly Func>[] s_valueFactoryCache; + + private static Func> GetValueFactoryBasedOnInitCapacity(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); + + if (capacity > ValueFactoryCacheCount) + { + return CreateValueFactory(capacity); + } + + int cacheIndex = capacity - 1; + if (s_valueFactoryCache[cacheIndex] == null) + { + Interlocked.CompareExchange( + ref s_valueFactoryCache[cacheIndex], + CreateValueFactory(capacity), + comparand: null); + } + + return s_valueFactoryCache[cacheIndex]; + + // Local helper function to avoid creating an instance of the generated delegate helper class + // every time 'GetValueFactoryBasedOnInitCapacity' is invoked. + static Func> CreateValueFactory(int capacity) + { + return key => new PSMemberInfoInternalCollection(capacity); + } + } + + private static MethodInfo GetMethodInfo(Type type, string method) + { + return type.GetMethod(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase); + } + + private static ScriptBlock GetScriptBlock(string s) + { + var sb = ScriptBlock.CreateDelayParsedScriptBlock(s, isProductCode: true); + sb.LanguageMode = PSLanguageMode.FullLanguage; + return sb; + } + + private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) + { + typesInfo.Add(new SessionStateTypeEntry(filePath)); + + string typeName = null; + PSMemberInfoInternalCollection typeMembers = null; + PSMemberInfoInternalCollection memberSetMembers = null; + HashSet newMembers = new HashSet(StringComparer.OrdinalIgnoreCase); + + #region System.Xml.XmlNode + + typeName = @"System.Xml.XmlNode"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"ToString"); + AddMember( + errors, + typeName, + new PSCodeMethod( + @"ToString", + GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"XmlNode")), + typeMembers, + isOverride: false); + + #endregion System.Xml.XmlNode + + #region System.Xml.XmlNodeList + + typeName = @"System.Xml.XmlNodeList"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"ToString"); + AddMember( + errors, + typeName, + new PSCodeMethod( + @"ToString", + GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"XmlNodeList")), + typeMembers, + isOverride: false); + + #endregion System.Xml.XmlNodeList + + #region System.Management.Automation.PSDriveInfo + + typeName = @"System.Management.Automation.PSDriveInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"Used"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Used", + GetScriptBlock(@"## Ensure that this is a FileSystem drive + if($this.Provider.ImplementingType -eq + [Microsoft.PowerShell.Commands.FileSystemProvider]) + { + $driveInfo = [System.IO.DriveInfo]::New($this.Root) + if ( $driveInfo.IsReady ) { $driveInfo.TotalSize - $driveInfo.AvailableFreeSpace } + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Free"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Free", + GetScriptBlock(@"## Ensure that this is a FileSystem drive + if($this.Provider.ImplementingType -eq + [Microsoft.PowerShell.Commands.FileSystemProvider]) + { + [System.IO.DriveInfo]::New($this.Root).AvailableFreeSpace + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSDriveInfo + + #region System.DirectoryServices.PropertyValueCollection +#if !UNIX + typeName = @"System.DirectoryServices.PropertyValueCollection"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"ToString"); + AddMember( + errors, + typeName, + new PSCodeMethod( + @"ToString", + GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"PropertyValueCollection")), + typeMembers, + isOverride: false); +#endif + #endregion System.DirectoryServices.PropertyValueCollection + + #region System.Drawing.Printing.PrintDocument + + typeName = @"System.Drawing.Printing.PrintDocument"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"Name"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Name", + GetScriptBlock(@"$this.PrinterSettings.PrinterName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Color"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Color", + GetScriptBlock(@"$this.PrinterSettings.SupportsColor"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Duplex"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Duplex", + GetScriptBlock(@"$this.PrinterSettings.Duplex"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Drawing.Printing.PrintDocument + + #region System.Management.Automation.ApplicationInfo + + typeName = @"System.Management.Automation.ApplicationInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"FileVersionInfo"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"FileVersionInfo", + GetScriptBlock(@"[System.Diagnostics.FileVersionInfo]::getversioninfo( $this.Path )"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ApplicationInfo + + #region System.DateTime + + typeName = @"System.DateTime"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"DateTime"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"DateTime", + GetScriptBlock(@"if ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq ""Date"") + { + ""{0}"" -f $this.ToLongDateString() + } + elseif ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq ""Time"") + { + ""{0}"" -f $this.ToLongTimeString() + } + else + { + ""{0} {1}"" -f $this.ToLongDateString(), $this.ToLongTimeString() + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.DateTime + + #region System.Net.IPAddress + + typeName = @"System.Net.IPAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"IPAddressToString"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IPAddressToString", + GetScriptBlock(@"$this.Tostring()"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 2); + AddMember( + errors, + typeName, + new PSNoteProperty(@"DefaultDisplayProperty", @"IPAddressToString"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Net.IPAddress + + #region Deserialized.System.Net.IPAddress + + typeName = @"Deserialized.System.Net.IPAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Net.IPAddress + + #region System.Diagnostics.ProcessModule + + typeName = @"System.Diagnostics.ProcessModule"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 6)); + + // Process regular members. + newMembers.Add(@"Size"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Size", + GetScriptBlock(@"$this.ModuleMemorySize / 1024"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Company"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Company", + GetScriptBlock(@"$this.FileVersionInfo.CompanyName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"FileVersion"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"FileVersion", + GetScriptBlock(@"$this.FileVersionInfo.FileVersion"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ProductVersion"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ProductVersion", + GetScriptBlock(@"$this.FileVersionInfo.ProductVersion"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Description"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Description", + GetScriptBlock(@"$this.FileVersionInfo.FileDescription"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Product"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Product", + GetScriptBlock(@"$this.FileVersionInfo.ProductName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.ProcessModule + + #region System.Collections.DictionaryEntry + + typeName = @"System.Collections.DictionaryEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Name"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Name", @"Key", conversionType: null), + typeMembers, + isOverride: false); + + #endregion System.Collections.DictionaryEntry + + #region System.Management.Automation.PSModuleInfo + + typeName = @"System.Management.Automation.PSModuleInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "Path", "Description", "Guid", "Version", "ModuleBase", "ModuleType", "PrivateData", "AccessMode", "ExportedAliases", "ExportedCmdlets", "ExportedFunctions", "ExportedVariables", "NestedModules" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSModuleInfo + + #region System.ServiceProcess.ServiceController + + typeName = @"System.ServiceProcess.ServiceController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 4)); + + // Process regular members. + newMembers.Add(@"Name"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Name", @"ServiceName", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"RequiredServices"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"RequiredServices", @"ServicesDependedOn", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"ToString"); + AddMember( + errors, + typeName, + new PSScriptMethod( + @"ToString", + GetScriptBlock(@"$this.ServiceName"), + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Status", "Name", "DisplayName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.ServiceProcess.ServiceController + + #region Deserialized.System.ServiceProcess.ServiceController + + typeName = @"Deserialized.System.ServiceProcess.ServiceController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Status", "Name", "DisplayName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.ServiceProcess.ServiceController + + #region System.Management.Automation.CmdletInfo + + typeName = @"System.Management.Automation.CmdletInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"DLL"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"DLL", + GetScriptBlock(@"$this.ImplementingType.Assembly.Location"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CmdletInfo + + #region System.Management.Automation.AliasInfo + + typeName = @"System.Management.Automation.AliasInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"ResolvedCommandName"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ResolvedCommandName", + GetScriptBlock(@"$this.ResolvedCommand.Name"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"DisplayName"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"DisplayName", + GetScriptBlock(@"if ($null -ne $this.ResolvedCommand) + { + $this.Name + "" -> "" + $this.ResolvedCommand.Name + } + else + { + $this.Name + "" -> "" + $this.Definition + } + "), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.AliasInfo + + #region System.DirectoryServices.DirectoryEntry +#if !UNIX + typeName = @"System.DirectoryServices.DirectoryEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"ConvertLargeIntegerToInt64"); + AddMember( + errors, + typeName, + new PSCodeMethod( + @"ConvertLargeIntegerToInt64", + GetMethodInfo(typeof(Microsoft.PowerShell.AdapterCodeMethods), @"ConvertLargeIntegerToInt64")), + typeMembers, + isOverride: false); + + newMembers.Add(@"ConvertDNWithBinaryToString"); + AddMember( + errors, + typeName, + new PSCodeMethod( + @"ConvertDNWithBinaryToString", + GetMethodInfo(typeof(Microsoft.PowerShell.AdapterCodeMethods), @"ConvertDNWithBinaryToString")), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "distinguishedName", "Path" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); +#endif + #endregion System.DirectoryServices.DirectoryEntry + + #region System.IO.DirectoryInfo + + typeName = @"System.IO.DirectoryInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 9)); + + // Process regular members. + newMembers.Add(@"Mode"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"Mode", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"Mode"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"ModeWithoutHardLink"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"ModeWithoutHardLink", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"ModeWithoutHardLink"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"BaseName"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"BaseName", + GetScriptBlock(@"$this.Name"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ResolvedTarget"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"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, + typeName, + new PSCodeProperty( + @"LinkType", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetLinkType"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"NameString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"NameString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"NameString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + newMembers.Add(@"LengthString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"LengthString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"LengthString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + newMembers.Add(@"LastWriteTimeString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"LastWriteTimeString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"LastWriteTimeString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"DefaultDisplayProperty", @"Name"), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.IO.DirectoryInfo + + #region System.IO.FileInfo + + typeName = @"System.IO.FileInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 10)); + + // Process regular members. + newMembers.Add(@"Mode"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"Mode", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"Mode"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"ModeWithoutHardLink"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"ModeWithoutHardLink", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"ModeWithoutHardLink"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"VersionInfo"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"VersionInfo", + GetScriptBlock(@"[System.Diagnostics.FileVersionInfo]::GetVersionInfo($this.FullName)"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"BaseName"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"BaseName", + GetScriptBlock(@"if ($this.Extension.Length -gt 0){$this.Name.Remove($this.Name.Length - $this.Extension.Length)}else{$this.Name}"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ResolvedTarget"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"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, + typeName, + new PSCodeProperty( + @"LinkType", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), @"GetLinkType"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"NameString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"NameString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"NameString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + newMembers.Add(@"LengthString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"LengthString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"LengthString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + newMembers.Add(@"LastWriteTimeString"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"LastWriteTimeString", + GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), @"LastWriteTimeString"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "LastWriteTime", "Length", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.IO.FileInfo + + #region System.Diagnostics.FileVersionInfo + + typeName = @"System.Diagnostics.FileVersionInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"FileVersionRaw"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"FileVersionRaw", + GetScriptBlock(@"New-Object System.Version -ArgumentList @( + $this.FileMajorPart + $this.FileMinorPart + $this.FileBuildPart + $this.FilePrivatePart)"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ProductVersionRaw"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ProductVersionRaw", + GetScriptBlock(@"New-Object System.Version -ArgumentList @( + $this.ProductMajorPart + $this.ProductMinorPart + $this.ProductBuildPart + $this.ProductPrivatePart)"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.FileVersionInfo + + #region System.Diagnostics.EventLogEntry + + typeName = @"System.Diagnostics.EventLogEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"EventID"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"EventID", + GetScriptBlock(@"$this.get_EventID() -band 0xFFFF"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.EventLogEntry + + #region System.Management.ManagementBaseObject + + typeName = @"System.Management.ManagementBaseObject"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"PSComputerName"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"PSComputerName", @"__SERVER", conversionType: null), + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementBaseObject + + #region System.Management.ManagementObject#root\cimv2\Win32_PingStatus + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PingStatus"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"IPV4Address"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IPV4Address", + GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) + $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | Select-Object -first 1"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"IPV6Address"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IPV6Address", + GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) + $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | Select-Object -first 1"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PingStatus + + #region System.Management.ManagementObject#root\cimv2\Win32_Process + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Process"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 5)); + + // Process regular members. + newMembers.Add(@"ProcessName"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"ProcessName", @"Name", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Handles"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Handles", @"Handlecount", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"VM"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"VM", @"VirtualSize", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"WS"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"WS", @"WorkingSetSize", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Path"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Path", + GetScriptBlock(@"$this.ExecutablePath"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Process + + #region System.Diagnostics.Process + + typeName = @"System.Diagnostics.Process"; + typeMembers = _extendedMembers.GetOrAdd(typeName, static key => new PSMemberInfoInternalCollection(capacity: 19)); + + // Process regular members. + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "Name", "Id", "PriorityClass", "FileVersion" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSResources"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSResources", + new List { "Name", "Id", "Handlecount", "WorkingSet", "NonPagedMemorySize", "PagedMemorySize", "PrivateMemorySize", "VirtualMemorySize", "Threads.Count", "TotalProcessorTime" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"Name"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Name", @"ProcessName", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"SI"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"SI", @"SessionId", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Handles"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Handles", @"Handlecount", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"VM"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"VM", @"VirtualMemorySize64", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"WS"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"WS", @"WorkingSet64", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"PM"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"PM", @"PagedMemorySize64", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"NPM"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"NPM", @"NonpagedSystemMemorySize64", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Path"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Path", + GetScriptBlock(@"$this.Mainmodule.FileName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"CommandLine"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"CommandLine", + GetScriptBlock(@" + if ($IsWindows) { + (Get-CimInstance Win32_Process -Filter ""ProcessId = $($this.Id)"").CommandLine + } elseif ($IsLinux) { + $rawCmd = Get-Content -LiteralPath ""/proc/$($this.Id)/cmdline"" + $rawCmd.Substring(0, $rawCmd.Length - 1) -replace ""`0"", "" "" + } + "), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Parent"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"Parent", + GetMethodInfo(typeof(Microsoft.PowerShell.ProcessCodeMethods), @"GetParentProcess"), + setterCodeReference: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Company"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Company", + GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.CompanyName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"CPU"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"CPU", + GetScriptBlock(@"$this.TotalProcessorTime.TotalSeconds"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"FileVersion"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"FileVersion", + GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.FileVersion"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ProductVersion"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ProductVersion", + GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.ProductVersion"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Description"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Description", + GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.FileDescription"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Product"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Product", + GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.ProductName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"__NounName"); + AddMember( + errors, + typeName, + new PSNoteProperty(@"__NounName", @"Process"), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Id", "Handles", "CPU", "SI", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Diagnostics.Process + + #region Deserialized.System.Diagnostics.Process + + typeName = @"Deserialized.System.Diagnostics.Process"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "Name", "Id", "PriorityClass", "FileVersion" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSResources"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSResources", + new List { "Name", "Id", "Handlecount", "WorkingSet", "NonPagedMemorySize", "PagedMemorySize", "PrivateMemorySize", "VirtualMemorySize", "Threads.Count", "TotalProcessorTime" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Id", "Handles", "CPU", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Diagnostics.Process + + #region System.Management.ManagementObject#root\cli\Msft_CliAlias + + typeName = @"System.Management.ManagementObject#root\cli\Msft_CliAlias"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "FriendlyName", "PWhere", "Target" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cli\Msft_CliAlias + + #region System.Management.ManagementObject#root\cimv2\Win32_BaseBoard + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_BaseBoard"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "PoweredOn" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Model", "Name", "SerialNumber", "SKU", "Product" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_BaseBoard + + #region System.Management.ManagementObject#root\cimv2\Win32_BIOS + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_BIOS"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "Caption", "SMBIOSPresent" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SMBIOSBIOSVersion", "Manufacturer", "Name", "SerialNumber", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_BIOS + + #region System.Management.ManagementObject#root\cimv2\Win32_BootConfiguration + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_BootConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "SettingID", "ConfigurationPath" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BootDirectory", "Name", "SettingID", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_BootConfiguration + + #region System.Management.ManagementObject#root\cimv2\Win32_CDROMDrive + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_CDROMDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "Drive", "ErrorCleared", "MediaLoaded", "NeedsCleaning", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Drive", "Manufacturer", "VolumeName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_CDROMDrive + + #region System.Management.ManagementObject#root\cimv2\Win32_ComputerSystem + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_ComputerSystem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "AdminPasswordStatus", "BootupState", "ChassisBootupState", "KeyboardPasswordStatus", "PowerOnPasswordStatus", "PowerSupplyState", "PowerState", "FrontPanelResetStatus", "ThermalState", "Status", "Name" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"POWER"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"POWER", + new List { "Name", "PowerManagementCapabilities", "PowerManagementSupported", "PowerOnPasswordStatus", "PowerState", "PowerSupplyState" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Domain", "Manufacturer", "Model", "Name", "PrimaryOwnerName", "TotalPhysicalMemory" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_ComputerSystem + + #region System.Management.ManagementObject#root\cimv2\WIN32_PROCESSOR + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_PROCESSOR"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "CpuStatus", "CurrentVoltage", "DeviceID", "ErrorCleared", "ErrorDescription", "LastErrorCode", "LoadPercentage", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "AddressWidth", "DataWidth", "DeviceID", "ExtClock", "L2CacheSize", "L2CacheSpeed", "MaxClockSpeed", "PowerManagementSupported", "ProcessorType", "Revision", "SocketDesignation", "Version", "VoltageCaps" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "DeviceID", "Manufacturer", "MaxClockSpeed", "Name", "SocketDesignation" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_PROCESSOR + + #region System.Management.ManagementObject#root\cimv2\Win32_ComputerSystemProduct + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_ComputerSystemProduct"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Version" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_ComputerSystemProduct + + #region System.Management.ManagementObject#root\cimv2\CIM_DataFile + + typeName = @"System.Management.ManagementObject#root\cimv2\CIM_DataFile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Compressed", "Encrypted", "Size", "Hidden", "Name", "Readable", "System", "Version", "Writeable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\CIM_DataFile + + #region System.Management.ManagementObject#root\cimv2\WIN32_DCOMApplication + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_DCOMApplication"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AppID", "InstallDate", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_DCOMApplication + + #region System.Management.ManagementObject#root\cimv2\WIN32_DESKTOP + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_DESKTOP"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "ScreenSaverActive" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "ScreenSaverActive", "ScreenSaverSecure", "ScreenSaverTimeout", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_DESKTOP + + #region System.Management.ManagementObject#root\cimv2\WIN32_DESKTOPMONITOR + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_DESKTOPMONITOR"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "DeviceID", "Name", "PixelsPerXLogicalInch", "PixelsPerYLogicalInch", "ScreenHeight", "ScreenWidth" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DeviceID", "IsLocked", "LastErrorCode", "Name", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "DisplayType", "MonitorManufacturer", "Name", "ScreenHeight", "ScreenWidth" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_DESKTOPMONITOR + + #region System.Management.ManagementObject#root\cimv2\Win32_DeviceMemoryAddress + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_DeviceMemoryAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "MemoryType" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "MemoryType", "Name", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_DeviceMemoryAddress + + #region System.Management.ManagementObject#root\cimv2\Win32_DiskDrive + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_DiskDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "ConfigManagerErrorCode", "LastErrorCode", "NeedsCleaning", "Status", "DeviceID", "StatusInfo", "Partitions" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "BytesPerSector", "ConfigManagerUserConfig", "DefaultBlockSize", "DeviceID", "Index", "InstallDate", "InterfaceType", "MaxBlockSize", "MaxMediaSize", "MinBlockSize", "NumberOfMediaSupported", "Partitions", "SectorsPerTrack", "Size", "TotalCylinders", "TotalHeads", "TotalSectors", "TotalTracks", "TracksPerCylinder" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Partitions", "DeviceID", "Model", "Size", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_DiskDrive + + #region System.Management.ManagementObject#root\cimv2\Win32_DiskQuota + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_DiskQuota"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "__PATH", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DiskSpaceUsed", "Limit", "QuotaVolume", "User" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_DiskQuota + + #region System.Management.ManagementObject#root\cimv2\Win32_DMAChannel + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_DMAChannel"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AddressSize", "DMAChannel", "MaxTransferSize", "Name", "Port" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_DMAChannel + + #region System.Management.ManagementObject#root\cimv2\Win32_Environment + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Environment"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "SystemVariable" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "VariableValue", "Name", "UserName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Environment + + #region System.Management.ManagementObject#root\cimv2\Win32_Directory + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Directory"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Compressed", "Encrypted", "Name", "Readable", "Writeable" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Hidden", "Archive", "EightDotThreeFileName", "FileSize", "Name", "Compressed", "Encrypted", "Readable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Directory + + #region System.Management.ManagementObject#root\cimv2\Win32_Group + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Group"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Domain", "Name", "SID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Group + + #region System.Management.ManagementObject#root\cimv2\Win32_IDEController + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_IDEController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_IDEController + + #region System.Management.ManagementObject#root\cimv2\Win32_IRQResource + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_IRQResource"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Caption", "Availability" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Hardware", "IRQNumber", "Name", "Shareable", "TriggerLevel", "TriggerType" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_IRQResource + + #region System.Management.ManagementObject#root\cimv2\Win32_ScheduledJob + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_ScheduledJob"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "JobId", "JobStatus", "ElapsedTime", "StartTime", "Owner" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "JobId", "Name", "Owner", "Priority", "Command" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_ScheduledJob + + #region System.Management.ManagementObject#root\cimv2\Win32_LoadOrderGroup + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_LoadOrderGroup"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "GroupOrder", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_LoadOrderGroup + + #region System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Availability", "DeviceID", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "DriveType", "ProviderName", "FreeSpace", "Size", "VolumeName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk + + #region System.Management.ManagementObject#root\cimv2\Win32_LogonSession + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_LogonSession"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "__PATH", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AuthenticationPackage", "LogonId", "LogonType", "Name", "StartTime", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_LogonSession + + #region System.Management.ManagementObject#root\cimv2\WIN32_CACHEMEMORY + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_CACHEMEMORY"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 4)); + + // Process regular members. + newMembers.Add(@"ERROR"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"ERROR", + new List { "DeviceID", "ErrorCorrectType" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "DeviceID", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status", "WritePolicy" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_CACHEMEMORY + + #region System.Management.ManagementObject#root\cimv2\Win32_LogicalMemoryConfiguration + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_LogicalMemoryConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "AvailableVirtualMemory", "Name", "TotalVirtualMemory" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "TotalVirtualMemory", "TotalPhysicalMemory", "TotalPageFileSpace" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_LogicalMemoryConfiguration + + #region System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemoryArray + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemoryArray"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "Replaceable", "Location" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Model", "Name", "MaxCapacity", "MemoryDevices" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemoryArray + + #region System.Management.ManagementObject#root\cimv2\WIN32_NetworkClient + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_NetworkClient"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "InstallDate", "Manufacturer", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_NetworkClient + + #region System.Management.ManagementObject#root\cimv2\Win32_NetworkLoginProfile + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NetworkLoginProfile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Privileges", "Profile", "UserId", "UserType", "Workstations" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NetworkLoginProfile + + #region System.Management.ManagementObject#root\cimv2\Win32_NetworkProtocol + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NetworkProtocol"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"FULLXXX"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"FULLXXX", + new List { "ConnectionlessService", "Description", "GuaranteesDelivery", "GuaranteesSequencing", "InstallDate", "MaximumAddressSize", "MaximumMessageSize", "MessageOriented", "MinimumAddressSize", "Name", "PseudoStreamOriented", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "GuaranteesDelivery", "GuaranteesSequencing", "ConnectionlessService", "Status", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NetworkProtocol + + #region System.Management.ManagementObject#root\cimv2\Win32_NetworkConnection + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NetworkConnection"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "ConnectionState", "Persistent", "LocalName", "RemoteName" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "LocalName", "RemoteName", "ConnectionState", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NetworkConnection + + #region System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapter + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapter"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "Name", "Status", "StatusInfo", "DeviceID" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ServiceName", "MACAddress", "AdapterType", "DeviceID", "Name", "NetworkAddresses", "Speed" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapter + + #region System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapterConfiguration + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapterConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 6)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DHCPLeaseExpires", "Index", "Description" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"DHCP"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DHCP", + new List { "Description", "DHCPEnabled", "DHCPLeaseExpires", "DHCPLeaseObtained", "DHCPServer", "Index" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"DNS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DNS", + new List { "Description", "DNSDomain", "DNSDomainSuffixSearchOrder", "DNSEnabledForWINSResolution", "DNSHostName", "DNSServerSearchOrder", "DomainDNSRegistrationEnabled", "FullDNSRegistrationEnabled", "Index" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"IP"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"IP", + new List { "Description", "Index", "IPAddress", "IPConnectionMetric", "IPEnabled", "IPFilterSecurityEnabled" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"WINS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"WINS", + new List { "Description", "Index", "WINSEnableLMHostsLookup", "WINSHostLookupFile", "WINSPrimaryServer", "WINSScopeID", "WINSSecondaryServer" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DHCPEnabled", "IPAddress", "DefaultIPGateway", "DNSDomain", "ServiceName", "Description", "Index" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapterConfiguration + + #region System.Management.ManagementObject#root\cimv2\Win32_NTDomain + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NTDomain"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DomainName" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"GUID"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"GUID", + new List { "DomainName", "DomainGuid" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ClientSiteName", "DcSiteName", "Description", "DnsForestName", "DomainControllerAddress", "DomainControllerName", "DomainName", "Roles", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NTDomain + + #region System.Management.ManagementObject#root\cimv2\Win32_NTLogEvent + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NTLogEvent"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Category", "CategoryString", "EventCode", "EventIdentifier", "TypeEvent", "InsertionStrings", "LogFile", "Message", "RecordNumber", "SourceName", "TimeGenerated", "TimeWritten", "Type", "UserName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NTLogEvent + + #region System.Management.ManagementObject#root\cimv2\Win32_NTEventlogFile + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_NTEventlogFile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "LogfileName", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "FileSize", "LogfileName", "Name", "NumberOfRecords" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_NTEventlogFile + + #region System.Management.ManagementObject#root\cimv2\Win32_OnBoardDevice + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_OnBoardDevice"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Description" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceType", "SerialNumber", "Enabled", "Description" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_OnBoardDevice + + #region System.Management.ManagementObject#root\cimv2\Win32_OperatingSystem + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_OperatingSystem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"FREE"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"FREE", + new List { "FreePhysicalMemory", "FreeSpaceInPagingFiles", "FreeVirtualMemory", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SystemDirectory", "Organization", "BuildNumber", "RegisteredUser", "SerialNumber", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_OperatingSystem + + #region System.Management.ManagementObject#root\cimv2\Win32_PageFileUsage + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PageFileUsage"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "CurrentUsage" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "PeakUsage" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PageFileUsage + + #region System.Management.ManagementObject#root\cimv2\Win32_PageFileSetting + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PageFileSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "MaximumSize", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PageFileSetting + + #region System.Management.ManagementObject#root\cimv2\Win32_DiskPartition + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_DiskPartition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Index", "Status", "StatusInfo", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "NumberOfBlocks", "BootPartition", "Name", "PrimaryPartition", "Size", "Index" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_DiskPartition + + #region System.Management.ManagementObject#root\cimv2\Win32_PortResource + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PortResource"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "NetConnectionStatus", "Status", "Name", "StartingAddress", "EndingAddress" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "Alias" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PortResource + + #region System.Management.ManagementObject#root\cimv2\Win32_PortConnector + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PortConnector"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "ExternalReferenceDesignator" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Tag", "ConnectorType", "SerialNumber", "ExternalReferenceDesignator", "PortType" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PortConnector + + #region System.Management.ManagementObject#root\cimv2\Win32_Printer + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Printer"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Location", "Name", "PrinterState", "PrinterStatus", "ShareName", "SystemName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Printer + + #region System.Management.ManagementObject#root\cimv2\Win32_PrinterConfiguration + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PrinterConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DriverVersion", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "PrintQuality", "DriverVersion", "Name", "PaperSize", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PrinterConfiguration + + #region System.Management.ManagementObject#root\cimv2\Win32_PrintJob + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PrintJob"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Document", "JobId", "JobStatus", "Name", "PagesPrinted", "Status", "JobIdCopy", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Document", "JobId", "JobStatus", "Owner", "Priority", "Size", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PrintJob + + #region System.Management.ManagementObject#root\cimv2\Win32_ProcessXXX + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_ProcessXXX"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 5)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "ProcessId" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"MEMORY"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"MEMORY", + new List { "Handle", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "Name", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "VirtualSize", "WorkingSetSize" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"IO"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"IO", + new List { "Name", "ProcessId", "ReadOperationCount", "ReadTransferCount", "WriteOperationCount", "WriteTransferCount" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"STATISTICS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"STATISTICS", + new List { "HandleCount", "Name", "KernelModeTime", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "OtherOperationCount", "OtherTransferCount", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "ProcessId", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "ReadOperationCount", "ReadTransferCount", "ThreadCount", "UserModeTime", "VirtualSize", "WorkingSetSize", "WriteOperationCount", "WriteTransferCount" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ThreadCount", "HandleCount", "Name", "Priority", "ProcessId", "WorkingSetSize" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_ProcessXXX + + #region System.Management.ManagementObject#root\cimv2\Win32_Product + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Product"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Version", "InstallState" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Product + + #region System.Management.ManagementObject#root\cimv2\Win32_QuickFixEngineering + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_QuickFixEngineering"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"InstalledOn"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"InstalledOn", + GetScriptBlock(@"if ([environment]::osversion.version.build -ge 7000) + { + # WMI team fixed the formatting issue related to InstalledOn + # property in Windows7 (to return string)..so returning the WMI's + # version directly + [DateTime]::Parse($this.psBase.properties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) + } + else + { + $orig = $this.psBase.properties[""InstalledOn""].Value + $date = [datetime]::FromFileTimeUTC($(""0x"" + $orig)) + if ($date -lt ""1/1/1980"") + { + if ($orig -match ""([0-9]{4})([01][0-9])([012][0-9])"") + { + new-object datetime @([int]$matches[1], [int]$matches[2], [int]$matches[3]) + } + } + else + { + $date + } + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "__PATH", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Description", "FixComments", "HotFixID", "InstallDate", "InstalledBy", "InstalledOn", "Name", "ServicePackInEffect", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_QuickFixEngineering + + #region System.Management.ManagementObject#root\cimv2\Win32_QuotaSetting + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_QuotaSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "State", "VolumePath", "Caption" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "DefaultLimit", "SettingID", "State", "VolumePath", "DefaultWarningLimit" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_QuotaSetting + + #region System.Management.ManagementObject#root\cimv2\Win32_OSRecoveryConfiguration + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_OSRecoveryConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DebugFilePath", "Name", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_OSRecoveryConfiguration + + #region System.Management.ManagementObject#root\cimv2\Win32_Registry + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Registry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "CurrentSize", "MaximumSize", "ProposedSize" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "CurrentSize", "MaximumSize", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Registry + + #region System.Management.ManagementObject#root\cimv2\Win32_SCSIController + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SCSIController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DriverName", "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SCSIController + + #region System.Management.ManagementObject#root\cimv2\Win32_PerfRawData_PerfNet_Server + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_PerfRawData_PerfNet_Server"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "LogonPerSec", "LogonTotal", "Name", "ServerSessions", "WorkItemShortages" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_PerfRawData_PerfNet_Server + + #region System.Management.ManagementObject#root\cimv2\Win32_Service + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Service"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status", "ExitCode" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "DesktopInteract", "ErrorControl", "Name", "PathName", "ServiceType", "StartMode" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ExitCode", "Name", "ProcessId", "StartMode", "State", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Service + + #region System.Management.ManagementObject#root\cimv2\Win32_Share + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_Share"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Type", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "Path", "Description" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_Share + + #region System.Management.ManagementObject#root\cimv2\Win32_SoftwareElement + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SoftwareElement"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SoftwareElementState", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "Path", "SerialNumber", "SoftwareElementID", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SoftwareElement + + #region System.Management.ManagementObject#root\cimv2\Win32_SoftwareFeature + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SoftwareFeature"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "InstallState", "LastUse" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "IdentifyingNumber", "ProductName", "Vendor", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SoftwareFeature + + #region System.Management.ManagementObject#root\cimv2\WIN32_SoundDevice + + typeName = @"System.Management.ManagementObject#root\cimv2\WIN32_SoundDevice"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "ConfigManagerUserConfig", "Name", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Name", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\WIN32_SoundDevice + + #region System.Management.ManagementObject#root\cimv2\Win32_StartupCommand + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_StartupCommand"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Command", "User", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_StartupCommand + + #region System.Management.ManagementObject#root\cimv2\Win32_SystemAccount + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SystemAccount"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SIDType", "Name", "Domain" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Domain", "Name", "SID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SystemAccount + + #region System.Management.ManagementObject#root\cimv2\Win32_SystemDriver + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SystemDriver"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "State", "ExitCode", "Started", "ServiceSpecificExitCode" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DisplayName", "Name", "State", "Status", "Started" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SystemDriver + + #region System.Management.ManagementObject#root\cimv2\Win32_SystemEnclosure + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SystemEnclosure"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Tag", "Status", "Name", "SecurityStatus" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Model", "LockPresent", "SerialNumber", "SMBIOSAssetTag", "SecurityStatus" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SystemEnclosure + + #region System.Management.ManagementObject#root\cimv2\Win32_SystemSlot + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_SystemSlot"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SlotDesignation" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SlotDesignation", "Tag", "SupportsHotPlug", "Status", "Shared", "PMESignal", "MaxDataWidth" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_SystemSlot + + #region System.Management.ManagementObject#root\cimv2\Win32_TapeDrive + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_TapeDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Availability", "DeviceID", "NeedsCleaning", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "Id", "Manufacturer", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_TapeDrive + + #region System.Management.ManagementObject#root\cimv2\Win32_TemperatureProbe + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_TemperatureProbe"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "CurrentReading", "DeviceID", "Name", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "CurrentReading", "Name", "Description", "MinReadable", "MaxReadable", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_TemperatureProbe + + #region System.Management.ManagementObject#root\cimv2\Win32_TimeZone + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_TimeZone"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Bias", "SettingID", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_TimeZone + + #region System.Management.ManagementObject#root\cimv2\Win32_UninterruptiblePowerSupply + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_UninterruptiblePowerSupply"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DeviceID", "EstimatedChargeRemaining", "EstimatedRunTime", "Name", "StatusInfo", "TimeOnBackup" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "EstimatedRunTime", "Name", "TimeOnBackup", "UPSPort", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_UninterruptiblePowerSupply + + #region System.Management.ManagementObject#root\cimv2\Win32_UserAccount + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_UserAccount"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Caption", "PasswordExpires" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AccountType", "Caption", "Domain", "SID", "FullName", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_UserAccount + + #region System.Management.ManagementObject#root\cimv2\Win32_VoltageProbe + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_VoltageProbe"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DeviceID", "Name", "NominalReading", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Status", "Description", "CurrentReading", "MaxReadable", "MinReadable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_VoltageProbe + + #region System.Management.ManagementObject#root\cimv2\Win32_VolumeQuotaSetting + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_VolumeQuotaSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Element", "Setting" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_VolumeQuotaSetting + + #region System.Management.ManagementObject#root\cimv2\Win32_WMISetting + + typeName = @"System.Management.ManagementObject#root\cimv2\Win32_WMISetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BuildVersion", "Caption", "DatabaseDirectory", "EnableEvents", "LoggingLevel", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject#root\cimv2\Win32_WMISetting + + #region System.Management.ManagementObject + + typeName = @"System.Management.ManagementObject"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"ConvertToDateTime"); + AddMember( + errors, + typeName, + new PSScriptMethod( + @"ConvertToDateTime", + GetScriptBlock(@"[System.Management.ManagementDateTimeConverter]::ToDateTime($args[0])"), + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ConvertFromDateTime"); + AddMember( + errors, + typeName, + new PSScriptMethod( + @"ConvertFromDateTime", + GetScriptBlock(@"[System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($args[0])"), + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementObject + + #region Microsoft.PowerShell.Commands.HistoryInfo + + typeName = @"Microsoft.PowerShell.Commands.HistoryInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultKeyPropertySet", + new List { "Id" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.HistoryInfo + + #region System.Management.ManagementClass + + typeName = @"System.Management.ManagementClass"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Name"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Name", @"__Class", conversionType: null), + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementClass + + #region System.Management.Automation.Runspaces.PSSession + + typeName = @"System.Management.Automation.Runspaces.PSSession"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 5)); + + // Process regular members. + newMembers.Add(@"State"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"State", + GetScriptBlock(@"$this.Runspace.RunspaceStateInfo.State"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"IdleTimeout"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IdleTimeout", + GetScriptBlock(@"$this.Runspace.ConnectionInfo.IdleTimeout"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"OutputBufferingMode"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"OutputBufferingMode", + GetScriptBlock(@"$this.Runspace.ConnectionInfo.OutputBufferingMode"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"DisconnectedOn"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"DisconnectedOn", + GetScriptBlock(@"$this.Runspace.DisconnectedOn"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"ExpiresOn"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"ExpiresOn", + GetScriptBlock(@"$this.Runspace.ExpiresOn"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Runspaces.PSSession + + #region System.Guid + + typeName = @"System.Guid"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Guid"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Guid", + GetScriptBlock(@"$this.ToString()"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Guid + + #region System.Management.Automation.Signature + + typeName = @"System.Management.Automation.Signature"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Signature + + #region System.Management.Automation.Job + + typeName = @"System.Management.Automation.Job"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"State"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"State", + GetScriptBlock(@"$this.JobStateInfo.State.ToString()"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 3); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "HasMoreData", "StatusMessage", "Location", "Command", "JobStateInfo", "InstanceId", "Id", "Name", "State", "ChildJobs", "PSJobTypeName", "PSBeginTime", "PSEndTime" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Job + + #region System.Management.Automation.JobStateInfo + + typeName = @"System.Management.Automation.JobStateInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.JobStateInfo + + #region Deserialized.System.Management.Automation.JobStateInfo + + typeName = @"Deserialized.System.Management.Automation.JobStateInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.JobStateInfo + + #region Microsoft.PowerShell.DeserializingTypeConverter + + typeName = @"Microsoft.PowerShell.DeserializingTypeConverter"; + + // Process type converter. + ProcessTypeConverter( + errors, + typeName, + typeof(Microsoft.PowerShell.DeserializingTypeConverter), + _typeConverters, + isOverride: false); + + #endregion Microsoft.PowerShell.DeserializingTypeConverter + + #region System.Net.Mail.MailAddress + + typeName = @"System.Net.Mail.MailAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Net.Mail.MailAddress + + #region Deserialized.System.Net.Mail.MailAddress + + typeName = @"Deserialized.System.Net.Mail.MailAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Net.Mail.MailAddress + + #region System.Globalization.CultureInfo + + typeName = @"System.Globalization.CultureInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 3); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "LCID", "Name", "DisplayName", "IetfLanguageTag", "ThreeLetterISOLanguageName", "ThreeLetterWindowsLanguageName", "TwoLetterISOLanguageName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Globalization.CultureInfo + + #region Deserialized.System.Globalization.CultureInfo + + typeName = @"Deserialized.System.Globalization.CultureInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Globalization.CultureInfo + + #region System.Management.Automation.PSCredential + + typeName = @"System.Management.Automation.PSCredential"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSCredential + + #region Deserialized.System.Management.Automation.PSCredential + + typeName = @"Deserialized.System.Management.Automation.PSCredential"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.PSCredential + + #region System.Management.Automation.PSPrimitiveDictionary + + typeName = @"System.Management.Automation.PSPrimitiveDictionary"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSPrimitiveDictionary + + #region Deserialized.System.Management.Automation.PSPrimitiveDictionary + + typeName = @"Deserialized.System.Management.Automation.PSPrimitiveDictionary"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.PSPrimitiveDictionary + + #region System.Management.Automation.SwitchParameter + + typeName = @"System.Management.Automation.SwitchParameter"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.SwitchParameter + + #region Deserialized.System.Management.Automation.SwitchParameter + + typeName = @"Deserialized.System.Management.Automation.SwitchParameter"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.SwitchParameter + + #region System.Management.Automation.PSListModifier + + typeName = @"System.Management.Automation.PSListModifier"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSListModifier + + #region Deserialized.System.Management.Automation.PSListModifier + + typeName = @"Deserialized.System.Management.Automation.PSListModifier"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.PSListModifier + + #region System.Security.Cryptography.X509Certificates.X509Certificate2 + + typeName = @"System.Security.Cryptography.X509Certificates.X509Certificate2"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 3); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "RawData" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Security.Cryptography.X509Certificates.X509Certificate2 + + #region Deserialized.System.Security.Cryptography.X509Certificates.X509Certificate2 + + typeName = @"Deserialized.System.Security.Cryptography.X509Certificates.X509Certificate2"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Security.Cryptography.X509Certificates.X509Certificate2 + + #region System.Security.Cryptography.X509Certificates.X500DistinguishedName + + typeName = @"System.Security.Cryptography.X509Certificates.X500DistinguishedName"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 3); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "RawData" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Security.Cryptography.X509Certificates.X500DistinguishedName + + #region Deserialized.System.Security.Cryptography.X509Certificates.X500DistinguishedName + + typeName = @"Deserialized.System.Security.Cryptography.X509Certificates.X500DistinguishedName"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Security.Cryptography.X509Certificates.X500DistinguishedName + + #region System.Security.AccessControl.RegistrySecurity + + typeName = @"System.Security.AccessControl.RegistrySecurity"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Security.AccessControl.RegistrySecurity + + #region Deserialized.System.Security.AccessControl.RegistrySecurity + + typeName = @"Deserialized.System.Security.AccessControl.RegistrySecurity"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Security.AccessControl.RegistrySecurity + + #region System.Security.AccessControl.FileSystemSecurity + + typeName = @"System.Security.AccessControl.FileSystemSecurity"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Security.AccessControl.FileSystemSecurity + + #region Deserialized.System.Security.AccessControl.FileSystemSecurity + + typeName = @"Deserialized.System.Security.AccessControl.FileSystemSecurity"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Security.AccessControl.FileSystemSecurity + + #region HelpInfo + + typeName = @"HelpInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion HelpInfo + + #region System.Management.Automation.PSTypeName + + typeName = @"System.Management.Automation.PSTypeName"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 2); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"String"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSAliasProperty(@"StringSerializationSource", @"Name", conversionType: null), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSTypeName + + #region System.Management.Automation.ParameterMetadata + + typeName = @"System.Management.Automation.ParameterMetadata"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 2); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "Name", "ParameterType", "Aliases", "IsDynamic", "SwitchParameter" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ParameterMetadata + + #region System.Management.Automation.CommandInfo + + typeName = @"System.Management.Automation.CommandInfo"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"Namespace"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Namespace", @"ModuleName", conversionType: null) { IsHidden = true }, + typeMembers, + isOverride: false); + + newMembers.Add(@"HelpUri"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"HelpUri", + GetScriptBlock(@"$oldProgressPreference = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + try + { + [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($this) + } + catch {} + finally + { + $ProgressPreference = $oldProgressPreference + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CommandInfo + + #region System.Management.Automation.ParameterSetMetadata + + typeName = @"System.Management.Automation.ParameterSetMetadata"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"Flags"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"Flags", + GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), @"GetParameterSetMetadataFlags"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 2); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "Position", "Flags", "HelpMessage" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ParameterSetMetadata + + #region Deserialized.System.Management.Automation.ParameterSetMetadata + + typeName = @"Deserialized.System.Management.Automation.ParameterSetMetadata"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.ParameterSetMetadata + + #region Deserialized.System.Management.Automation.ExtendedTypeDefinition + + typeName = @"Deserialized.System.Management.Automation.ExtendedTypeDefinition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.ExtendedTypeDefinition + + #region System.Management.Automation.ExtendedTypeDefinition + + typeName = @"System.Management.Automation.ExtendedTypeDefinition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 4); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "TypeNames", "FormatViewDefinition" }), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "TypeName", "TypeNames", "FormatViewDefinition" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ExtendedTypeDefinition + + #region Deserialized.System.Management.Automation.FormatViewDefinition + + typeName = @"Deserialized.System.Management.Automation.FormatViewDefinition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.FormatViewDefinition + + #region System.Management.Automation.FormatViewDefinition + + typeName = @"System.Management.Automation.FormatViewDefinition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"InstanceId"); + AddMember( + errors, + typeName, + new PSCodeProperty( + @"InstanceId", + GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), @"GetFormatViewDefinitionInstanceId"), + setterCodeReference: null) + { IsHidden = true }, + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.FormatViewDefinition + + #region Deserialized.System.Management.Automation.PSControl + + typeName = @"Deserialized.System.Management.Automation.PSControl"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.PSControl + + #region System.Management.Automation.PSControl + + typeName = @"System.Management.Automation.PSControl"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSControl + + #region Deserialized.System.Management.Automation.PSControlGroupBy + + typeName = @"Deserialized.System.Management.Automation.PSControlGroupBy"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.PSControlGroupBy + + #region System.Management.Automation.PSControlGroupBy + + typeName = @"System.Management.Automation.PSControlGroupBy"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.PSControlGroupBy + + #region Deserialized.System.Management.Automation.EntrySelectedBy + + typeName = @"Deserialized.System.Management.Automation.EntrySelectedBy"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.EntrySelectedBy + + #region System.Management.Automation.EntrySelectedBy + + typeName = @"System.Management.Automation.EntrySelectedBy"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.EntrySelectedBy + + #region Deserialized.System.Management.Automation.DisplayEntry + + typeName = @"Deserialized.System.Management.Automation.DisplayEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.DisplayEntry + + #region System.Management.Automation.DisplayEntry + + typeName = @"System.Management.Automation.DisplayEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.DisplayEntry + + #region Deserialized.System.Management.Automation.TableControlColumnHeader + + typeName = @"Deserialized.System.Management.Automation.TableControlColumnHeader"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.TableControlColumnHeader + + #region System.Management.Automation.TableControlColumnHeader + + typeName = @"System.Management.Automation.TableControlColumnHeader"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.TableControlColumnHeader + + #region Deserialized.System.Management.Automation.TableControlRow + + typeName = @"Deserialized.System.Management.Automation.TableControlRow"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.TableControlRow + + #region System.Management.Automation.TableControlRow + + typeName = @"System.Management.Automation.TableControlRow"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.TableControlRow + + #region Deserialized.System.Management.Automation.TableControlColumn + + typeName = @"Deserialized.System.Management.Automation.TableControlColumn"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.TableControlColumn + + #region System.Management.Automation.TableControlColumn + + typeName = @"System.Management.Automation.TableControlColumn"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.TableControlColumn + + #region Deserialized.System.Management.Automation.ListControlEntry + + typeName = @"Deserialized.System.Management.Automation.ListControlEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.ListControlEntry + + #region System.Management.Automation.ListControlEntry + + typeName = @"System.Management.Automation.ListControlEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 4); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationMethod", @"SpecificProperties"), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Items", "EntrySelectedBy" }), + memberSetMembers, + isOverride: false); + + AddMember( + errors, + typeName, + new PSPropertySet( + @"PropertySerializationSet", + new List { "Items", "SelectedBy", "EntrySelectedBy" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ListControlEntry + + #region Deserialized.System.Management.Automation.ListControlEntryItem + + typeName = @"Deserialized.System.Management.Automation.ListControlEntryItem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.ListControlEntryItem + + #region System.Management.Automation.ListControlEntryItem + + typeName = @"System.Management.Automation.ListControlEntryItem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ListControlEntryItem + + #region Deserialized.System.Management.Automation.WideControlEntryItem + + typeName = @"Deserialized.System.Management.Automation.WideControlEntryItem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.WideControlEntryItem + + #region System.Management.Automation.WideControlEntryItem + + typeName = @"System.Management.Automation.WideControlEntryItem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.WideControlEntryItem + + #region Deserialized.System.Management.Automation.CustomControlEntry + + typeName = @"Deserialized.System.Management.Automation.CustomControlEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.CustomControlEntry + + #region System.Management.Automation.CustomControlEntry + + typeName = @"System.Management.Automation.CustomControlEntry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CustomControlEntry + + #region Deserialized.System.Management.Automation.CustomItemBase + + typeName = @"Deserialized.System.Management.Automation.CustomItemBase"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.CustomItemBase + + #region System.Management.Automation.CustomItemBase + + typeName = @"System.Management.Automation.CustomItemBase"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CustomItemBase + + #region System.Web.Services.Protocols.SoapException + + typeName = @"System.Web.Services.Protocols.SoapException"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"PSMessageDetails"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"PSMessageDetails", + GetScriptBlock(@"$this.Detail.""#text"""), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Web.Services.Protocols.SoapException + + #region System.Management.Automation.ErrorRecord + + typeName = @"System.Management.Automation.ErrorRecord"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"PSMessageDetails"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"PSMessageDetails", + GetScriptBlock(@"& { Set-StrictMode -Version 1; $this.Exception.InnerException.PSMessageDetails }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.ErrorRecord + + #region Deserialized.System.Enum + + typeName = @"Deserialized.System.Enum"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Value"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Value", + GetScriptBlock(@"$this.ToString()"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Enum + + #region Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData + + typeName = @"Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData + + #region Deserialized.Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData + + typeName = @"Deserialized.Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData + + #region System.Management.ManagementEventArgs + + typeName = @"System.Management.ManagementEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.ManagementEventArgs + + #region Deserialized.System.Management.ManagementEventArgs + + typeName = @"Deserialized.System.Management.ManagementEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.ManagementEventArgs + + #region System.Management.Automation.CallStackFrame + + typeName = @"System.Management.Automation.CallStackFrame"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"Command"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Command", + GetScriptBlock(@"if ($null -eq $this.InvocationInfo) { return $this.FunctionName } + + $commandInfo = $this.InvocationInfo.MyCommand + if ($null -eq $commandInfo) { return $this.InvocationInfo.InvocationName } + + if ($commandInfo.Name -ne """") { return $commandInfo.Name } + + return $this.FunctionName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Location"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Location", + GetScriptBlock(@"$this.GetScriptLocation()"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"Arguments"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Arguments", + GetScriptBlock(@"$argumentsBuilder = new-object System.Text.StringBuilder + + $null = $( + $argumentsBuilder.Append(""{"") + foreach ($entry in $this.InvocationInfo.BoundParameters.GetEnumerator()) + { + if ($argumentsBuilder.Length -gt 1) + { + $argumentsBuilder.Append("", ""); + } + + $argumentsBuilder.Append($entry.Key).Append(""="") + + if ($entry.Value) + { + $argumentsBuilder.Append([string]$entry.Value) + } + } + + foreach ($arg in $this.InvocationInfo.UnboundArguments.GetEnumerator()) + { + if ($argumentsBuilder.Length -gt 1) + { + $argumentsBuilder.Append("", "") + } + + if ($arg) + { + $argumentsBuilder.Append([string]$arg) + } + else + { + $argumentsBuilder.Append('$null') + } + } + + $argumentsBuilder.Append('}'); + ) + + return $argumentsBuilder.ToString();"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.CallStackFrame + + #region Microsoft.PowerShell.Commands.PSSessionConfigurationCommands#PSSessionConfiguration + + typeName = @"Microsoft.PowerShell.Commands.PSSessionConfigurationCommands#PSSessionConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"Permission"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Permission", + GetScriptBlock(@"trap { continue; } + + $private:sd = $null + $private:sd = new-object System.Security.AccessControl.CommonSecurityDescriptor $false,$false,$this.SecurityDescriptorSddl + if ($private:sd) + { + # reset trap + trap { } + + $private:dacls = """"; + $private:first = $true + $private:sd.DiscretionaryAcl | ForEach-Object { + trap { } + + if ($private:first) + { + $private:first = $false; + } + else + { + $private:dacls += "", "" + } + + $private:dacls += $_.SecurityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() + "" "" + $_.AceType + } # end of foreach + + return $private:dacls + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion Microsoft.PowerShell.Commands.PSSessionConfigurationCommands#PSSessionConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"IPV4Address"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IPV4Address", + GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) + $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | Select-Object -first 1"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + newMembers.Add(@"IPV6Address"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"IPV6Address", + GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) + $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | Select-Object -first 1"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Process + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Process"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 6)); + + // Process regular members. + newMembers.Add(@"ProcessName"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"ProcessName", @"Name", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Handles"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"Handles", @"Handlecount", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"VM"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"VM", @"VirtualSize", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"WS"); + AddMember( + errors, + typeName, + new PSAliasProperty(@"WS", @"WorkingSetSize", conversionType: null), + typeMembers, + isOverride: false); + + newMembers.Add(@"Path"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"Path", + GetScriptBlock(@"$this.ExecutablePath"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ProcessId", "Name", "HandleCount", "WorkingSetSize", "VirtualSize" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Process + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Msft_CliAlias + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Msft_CliAlias"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "FriendlyName", "PWhere", "Target" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Msft_CliAlias + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BaseBoard + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BaseBoard"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "PoweredOn" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Model", "Name", "SerialNumber", "SKU", "Product" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BaseBoard + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BIOS + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BIOS"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "Caption", "SMBIOSPresent" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SMBIOSBIOSVersion", "Manufacturer", "Name", "SerialNumber", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BIOS + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BootConfiguration + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BootConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "SettingID", "ConfigurationPath" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BootDirectory", "Name", "SettingID", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BootConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_CDROMDrive + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_CDROMDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "Drive", "ErrorCleared", "MediaLoaded", "NeedsCleaning", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Drive", "Manufacturer", "VolumeName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_CDROMDrive + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystem + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "AdminPasswordStatus", "BootupState", "ChassisBootupState", "KeyboardPasswordStatus", "PowerOnPasswordStatus", "PowerSupplyState", "PowerState", "FrontPanelResetStatus", "ThermalState", "Status", "Name" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"POWER"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"POWER", + new List { "Name", "PowerManagementCapabilities", "PowerManagementSupported", "PowerOnPasswordStatus", "PowerState", "PowerSupplyState" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Domain", "Manufacturer", "Model", "Name", "PrimaryOwnerName", "TotalPhysicalMemory" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystem + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_PROCESSOR + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_PROCESSOR"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "CpuStatus", "CurrentVoltage", "DeviceID", "ErrorCleared", "ErrorDescription", "LastErrorCode", "LoadPercentage", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "AddressWidth", "DataWidth", "DeviceID", "ExtClock", "L2CacheSize", "L2CacheSpeed", "MaxClockSpeed", "PowerManagementSupported", "ProcessorType", "Revision", "SocketDesignation", "Version", "VoltageCaps" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "DeviceID", "Manufacturer", "MaxClockSpeed", "Name", "SocketDesignation" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_PROCESSOR + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystemProduct + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystemProduct"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Version" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystemProduct + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/CIM_DataFile + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/CIM_DataFile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Compressed", "Encrypted", "Size", "Hidden", "Name", "Readable", "System", "Version", "Writeable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/CIM_DataFile + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DCOMApplication + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DCOMApplication"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AppID", "InstallDate", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DCOMApplication + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOP + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOP"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "ScreenSaverActive" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "ScreenSaverActive", "ScreenSaverSecure", "ScreenSaverTimeout", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOP + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOPMONITOR + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOPMONITOR"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "DeviceID", "Name", "PixelsPerXLogicalInch", "PixelsPerYLogicalInch", "ScreenHeight", "ScreenWidth" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DeviceID", "IsLocked", "LastErrorCode", "Name", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "DisplayType", "MonitorManufacturer", "Name", "ScreenHeight", "ScreenWidth" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOPMONITOR + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DeviceMemoryAddress + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DeviceMemoryAddress"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "MemoryType" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "MemoryType", "Name", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DeviceMemoryAddress + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskDrive + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "ConfigManagerErrorCode", "LastErrorCode", "NeedsCleaning", "Status", "DeviceID", "StatusInfo", "Partitions" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "BytesPerSector", "ConfigManagerUserConfig", "DefaultBlockSize", "DeviceID", "Index", "InstallDate", "InterfaceType", "MaxBlockSize", "MaxMediaSize", "MinBlockSize", "NumberOfMediaSupported", "Partitions", "SectorsPerTrack", "Size", "TotalCylinders", "TotalHeads", "TotalSectors", "TotalTracks", "TracksPerCylinder" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Partitions", "DeviceID", "Model", "Size", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskDrive + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskQuota + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskQuota"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DiskSpaceUsed", "Limit", "QuotaVolume", "User" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskQuota + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DMAChannel + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DMAChannel"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AddressSize", "DMAChannel", "MaxTransferSize", "Name", "Port" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DMAChannel + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Environment + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Environment"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "SystemVariable" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "VariableValue", "Name", "UserName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Environment + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Directory + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Directory"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Compressed", "Encrypted", "Name", "Readable", "Writeable" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Hidden", "Archive", "EightDotThreeFileName", "FileSize", "Name", "Compressed", "Encrypted", "Readable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Directory + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Group + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Group"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Domain", "Name", "SID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Group + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IDEController + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IDEController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IDEController + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IRQResource + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IRQResource"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Caption", "Availability" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Hardware", "IRQNumber", "Name", "Shareable", "TriggerLevel", "TriggerType" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IRQResource + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ScheduledJob + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ScheduledJob"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "JobId", "JobStatus", "ElapsedTime", "StartTime", "Owner" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "JobId", "Name", "Owner", "Priority", "Command" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ScheduledJob + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LoadOrderGroup + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LoadOrderGroup"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "GroupOrder", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LoadOrderGroup + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalDisk + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalDisk"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Availability", "DeviceID", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "DriveType", "ProviderName", "FreeSpace", "Size", "VolumeName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalDisk + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogonSession + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogonSession"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AuthenticationPackage", "LogonId", "LogonType", "Name", "StartTime", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogonSession + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_CACHEMEMORY + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_CACHEMEMORY"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 4)); + + // Process regular members. + newMembers.Add(@"ERROR"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"ERROR", + new List { "DeviceID", "ErrorCorrectType" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "DeviceID", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status", "WritePolicy" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_CACHEMEMORY + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalMemoryConfiguration + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalMemoryConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "AvailableVirtualMemory", "Name", "TotalVirtualMemory" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "TotalVirtualMemory", "TotalPhysicalMemory", "TotalPageFileSpace" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalMemoryConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PhysicalMemoryArray + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PhysicalMemoryArray"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "Replaceable", "Location" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Model", "Name", "MaxCapacity", "MemoryDevices" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PhysicalMemoryArray + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_NetworkClient + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_NetworkClient"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "InstallDate", "Manufacturer", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_NetworkClient + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkLoginProfile + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkLoginProfile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Privileges", "Profile", "UserId", "UserType", "Workstations" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkLoginProfile + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkProtocol + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkProtocol"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"FULLXXX"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"FULLXXX", + new List { "ConnectionlessService", "Description", "GuaranteesDelivery", "GuaranteesSequencing", "InstallDate", "MaximumAddressSize", "MaximumMessageSize", "MessageOriented", "MinimumAddressSize", "Name", "PseudoStreamOriented", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "GuaranteesDelivery", "GuaranteesSequencing", "ConnectionlessService", "Status", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkProtocol + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkConnection + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkConnection"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "ConnectionState", "Persistent", "LocalName", "RemoteName" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "LocalName", "RemoteName", "ConnectionState", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkConnection + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapter + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapter"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Availability", "Name", "Status", "StatusInfo", "DeviceID" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ServiceName", "MACAddress", "AdapterType", "DeviceID", "Name", "NetworkAddresses", "Speed" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapter + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapterConfiguration + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapterConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 6)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DHCPLeaseExpires", "Index", "Description" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"DHCP"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DHCP", + new List { "Description", "DHCPEnabled", "DHCPLeaseExpires", "DHCPLeaseObtained", "DHCPServer", "Index" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"DNS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DNS", + new List { "Description", "DNSDomain", "DNSDomainSuffixSearchOrder", "DNSEnabledForWINSResolution", "DNSHostName", "DNSServerSearchOrder", "DomainDNSRegistrationEnabled", "FullDNSRegistrationEnabled", "Index" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"IP"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"IP", + new List { "Description", "Index", "IPAddress", "IPConnectionMetric", "IPEnabled", "IPFilterSecurityEnabled" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"WINS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"WINS", + new List { "Description", "Index", "WINSEnableLMHostsLookup", "WINSHostLookupFile", "WINSPrimaryServer", "WINSScopeID", "WINSSecondaryServer" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DHCPEnabled", "IPAddress", "DefaultIPGateway", "DNSDomain", "ServiceName", "Description", "Index" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapterConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTDomain + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTDomain"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DomainName" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"GUID"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"GUID", + new List { "DomainName", "DomainGuid" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ClientSiteName", "DcSiteName", "Description", "DnsForestName", "DomainControllerAddress", "DomainControllerName", "DomainName", "Roles", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTDomain + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTLogEvent + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTLogEvent"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Category", "CategoryString", "EventCode", "EventIdentifier", "TypeEvent", "InsertionStrings", "LogFile", "Message", "RecordNumber", "SourceName", "TimeGenerated", "TimeWritten", "Type", "UserName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTLogEvent + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTEventlogFile + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTEventlogFile"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "LogfileName", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "FileSize", "LogfileName", "Name", "NumberOfRecords" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTEventlogFile + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OnBoardDevice + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OnBoardDevice"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Description" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceType", "SerialNumber", "Enabled", "Description" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OnBoardDevice + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OperatingSystem + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OperatingSystem"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"FREE"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"FREE", + new List { "FreePhysicalMemory", "FreeSpaceInPagingFiles", "FreeVirtualMemory", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SystemDirectory", "Organization", "BuildNumber", "RegisteredUser", "SerialNumber", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OperatingSystem + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileUsage + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileUsage"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "CurrentUsage" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "PeakUsage" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileUsage + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileSetting + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "MaximumSize", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileSetting + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskPartition + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskPartition"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Index", "Status", "StatusInfo", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "NumberOfBlocks", "BootPartition", "Name", "PrimaryPartition", "Size", "Index" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskPartition + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortResource + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortResource"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "NetConnectionStatus", "Status", "Name", "StartingAddress", "EndingAddress" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "Alias" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortResource + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortConnector + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortConnector"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "ExternalReferenceDesignator" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Tag", "ConnectorType", "SerialNumber", "ExternalReferenceDesignator", "PortType" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortConnector + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Printer + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Printer"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Location", "Name", "PrinterState", "PrinterStatus", "ShareName", "SystemName" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Printer + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrinterConfiguration + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrinterConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "DriverVersion", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "PrintQuality", "DriverVersion", "Name", "PaperSize", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrinterConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrintJob + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrintJob"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Document", "JobId", "JobStatus", "Name", "PagesPrinted", "Status", "JobIdCopy", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Document", "JobId", "JobStatus", "Owner", "Priority", "Size", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrintJob + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ProcessXXX + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ProcessXXX"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 5)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "ProcessId" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"MEMORY"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"MEMORY", + new List { "Handle", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "Name", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "VirtualSize", "WorkingSetSize" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"IO"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"IO", + new List { "Name", "ProcessId", "ReadOperationCount", "ReadTransferCount", "WriteOperationCount", "WriteTransferCount" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"STATISTICS"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"STATISTICS", + new List { "HandleCount", "Name", "KernelModeTime", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "OtherOperationCount", "OtherTransferCount", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "ProcessId", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "ReadOperationCount", "ReadTransferCount", "ThreadCount", "UserModeTime", "VirtualSize", "WorkingSetSize", "WriteOperationCount", "WriteTransferCount" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ThreadCount", "HandleCount", "Name", "Priority", "ProcessId", "WorkingSetSize" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ProcessXXX + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Product + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Product"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Version", "InstallState" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Product + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuickFixEngineering + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuickFixEngineering"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"InstalledOn"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"InstalledOn", + GetScriptBlock(@"if ([environment]::osversion.version.build -ge 7000) + { + # WMI team fixed the formatting issue related to InstalledOn + # property in Windows7 (to return string)..so returning the WMI's + # version directly + [DateTime]::Parse($this.psBase.CimInstanceProperties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) + } + else + { + $orig = $this.psBase.CimInstanceProperties[""InstalledOn""].Value + $date = [datetime]::FromFileTimeUTC($(""0x"" + $orig)) + if ($date -lt ""1/1/1980"") + { + if ($orig -match ""([0-9]{4})([01][0-9])([012][0-9])"") + { + new-object datetime @([int]$matches[1], [int]$matches[2], [int]$matches[3]) + } + } + else + { + $date + } + }"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Description", "FixComments", "HotFixID", "InstallDate", "InstalledBy", "InstalledOn", "Name", "ServicePackInEffect", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuickFixEngineering + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuotaSetting + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuotaSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "State", "VolumePath", "Caption" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "DefaultLimit", "SettingID", "State", "VolumePath", "DefaultWarningLimit" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuotaSetting + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OSRecoveryConfiguration + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OSRecoveryConfiguration"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DebugFilePath", "Name", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OSRecoveryConfiguration + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Registry + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Registry"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "CurrentSize", "MaximumSize", "ProposedSize" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "CurrentSize", "MaximumSize", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Registry + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SCSIController + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SCSIController"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DriverName", "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SCSIController + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PerfRawData_PerfNet_Server + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PerfRawData_PerfNet_Server"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "LogonPerSec", "LogonTotal", "Name", "ServerSessions", "WorkItemShortages" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PerfRawData_PerfNet_Server + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Service + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Service"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 3)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Name", "Status", "ExitCode" }), + typeMembers, + isOverride: false); + + newMembers.Add(@"PSConfiguration"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSConfiguration", + new List { "DesktopInteract", "ErrorControl", "Name", "PathName", "ServiceType", "StartMode" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "ExitCode", "Name", "ProcessId", "StartMode", "State", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Service + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Share + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Share"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Type", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Name", "Path", "Description" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Share + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareElement + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareElement"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SoftwareElementState", "Name" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Name", "Path", "SerialNumber", "SoftwareElementID", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareElement + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareFeature + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareFeature"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "InstallState", "LastUse" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "IdentifyingNumber", "ProductName", "Vendor", "Version" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareFeature + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_SoundDevice + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_SoundDevice"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "ConfigManagerUserConfig", "Name", "Status", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Name", "Status", "StatusInfo" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_SoundDevice + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_StartupCommand + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_StartupCommand"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Command", "User", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_StartupCommand + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemAccount + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemAccount"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SIDType", "Name", "Domain" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Caption", "Domain", "Name", "SID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemAccount + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemDriver + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemDriver"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Name", "State", "ExitCode", "Started", "ServiceSpecificExitCode" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DisplayName", "Name", "State", "Status", "Started" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemDriver + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemEnclosure + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemEnclosure"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Tag", "Status", "Name", "SecurityStatus" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Manufacturer", "Model", "LockPresent", "SerialNumber", "SMBIOSAssetTag", "SecurityStatus" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemEnclosure + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemSlot + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemSlot"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "SlotDesignation" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "SlotDesignation", "Tag", "SupportsHotPlug", "Status", "Shared", "PMESignal", "MaxDataWidth" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemSlot + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TapeDrive + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TapeDrive"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Availability", "DeviceID", "NeedsCleaning", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "Id", "Manufacturer", "Name", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TapeDrive + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TemperatureProbe + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TemperatureProbe"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "CurrentReading", "DeviceID", "Name", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "CurrentReading", "Name", "Description", "MinReadable", "MaxReadable", "Status" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TemperatureProbe + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TimeZone + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TimeZone"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Bias", "SettingID", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TimeZone + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UninterruptiblePowerSupply + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UninterruptiblePowerSupply"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DeviceID", "EstimatedChargeRemaining", "EstimatedRunTime", "Name", "StatusInfo", "TimeOnBackup" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "DeviceID", "EstimatedRunTime", "Name", "TimeOnBackup", "UPSPort", "Caption" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UninterruptiblePowerSupply + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UserAccount + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UserAccount"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "Caption", "PasswordExpires" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "AccountType", "Caption", "Domain", "SID", "FullName", "Name" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UserAccount + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VoltageProbe + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VoltageProbe"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 2)); + + // Process regular members. + newMembers.Add(@"PSStatus"); + AddMember( + errors, + typeName, + new PSPropertySet( + @"PSStatus", + new List { "Status", "DeviceID", "Name", "NominalReading", "StatusInfo" }), + typeMembers, + isOverride: false); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Status", "Description", "CurrentReading", "MaxReadable", "MinReadable" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VoltageProbe + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VolumeQuotaSetting + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VolumeQuotaSetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "Element", "Setting" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VolumeQuotaSetting + + #region Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_WMISetting + + typeName = @"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_WMISetting"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSPropertySet( + @"DefaultDisplayPropertySet", + new List { "BuildVersion", "Caption", "DatabaseDirectory", "EnableEvents", "LoggingLevel", "SettingID" }), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_WMISetting + + #region Microsoft.Management.Infrastructure.CimClass + + typeName = @"Microsoft.Management.Infrastructure.CimClass"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"CimClassName"); + AddMember( + errors, + typeName, + new PSScriptProperty( + @"CimClassName", + GetScriptBlock(@"[OutputType([string])] + param() + $this.PSBase.CimSystemProperties.ClassName"), + setterScript: null, + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimClass + + #region Microsoft.Management.Infrastructure.CimCmdlets.CimIndicationEventInstanceEventArgs + + typeName = @"Microsoft.Management.Infrastructure.CimCmdlets.CimIndicationEventInstanceEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Microsoft.Management.Infrastructure.CimCmdlets.CimIndicationEventInstanceEventArgs + + #region System.Management.Automation.Breakpoint + + typeName = @"System.Management.Automation.Breakpoint"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.Breakpoint + + #region Deserialized.System.Management.Automation.Breakpoint + + typeName = @"Deserialized.System.Management.Automation.Breakpoint"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.Breakpoint + + #region System.Management.Automation.BreakpointUpdatedEventArgs + + typeName = @"System.Management.Automation.BreakpointUpdatedEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 2), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.BreakpointUpdatedEventArgs + + #region Deserialized.System.Management.Automation.BreakpointUpdatedEventArgs + + typeName = @"Deserialized.System.Management.Automation.BreakpointUpdatedEventArgs"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.BreakpointUpdatedEventArgs + + #region System.Management.Automation.DebuggerCommand + + typeName = @"System.Management.Automation.DebuggerCommand"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.DebuggerCommand + + #region Deserialized.System.Management.Automation.DebuggerCommand + + typeName = @"Deserialized.System.Management.Automation.DebuggerCommand"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.DebuggerCommand + + #region System.Management.Automation.DebuggerCommandResults + + typeName = @"System.Management.Automation.DebuggerCommandResults"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"SerializationDepth", 1), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion System.Management.Automation.DebuggerCommandResults + + #region Deserialized.System.Management.Automation.DebuggerCommandResults + + typeName = @"Deserialized.System.Management.Automation.DebuggerCommandResults"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process standard members. + memberSetMembers = new PSMemberInfoInternalCollection(capacity: 1); + AddMember( + errors, + typeName, + new PSNoteProperty(@"TargetTypeForDeserialization", typeof(Microsoft.PowerShell.DeserializingTypeConverter)), + memberSetMembers, + isOverride: false); + + ProcessStandardMembers( + errors, + typeName, + memberSetMembers, + typeMembers, + isOverride: false); + + #endregion Deserialized.System.Management.Automation.DebuggerCommandResults + + #region System.Version#IncludeLabel + + typeName = @"System.Version#IncludeLabel"; + typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1)); + + // Process regular members. + newMembers.Add(@"ToString"); + AddMember( + errors, + typeName, + new PSScriptMethod( + @"ToString", + GetScriptBlock(@" + $suffix = """" + if (![String]::IsNullOrEmpty($this.PSSemVerPreReleaseLabel)) + { + $suffix = ""-""+$this.PSSemVerPreReleaseLabel + } + + if (![String]::IsNullOrEmpty($this.PSSemVerBuildLabel)) + { + $suffix += ""+""+$this.PSSemVerBuildLabel + } + ""$($this.Major).$($this.Minor).$($this.Build)""+$suffix + "), + shouldCloneOnAccess: true), + typeMembers, + isOverride: false); + + #endregion System.Version#IncludeLabel + +#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); + + 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 + + // Update binder version for newly added members. + foreach (string memberName in newMembers) + { + PSGetMemberBinder.TypeTableMemberAdded(memberName); + } + } + } +} diff --git a/src/System.Management.Automation/engine/TypesV3_Ps1Xml.cs b/src/System.Management.Automation/engine/TypesV3_Ps1Xml.cs deleted file mode 100644 index 36aecab308f..00000000000 --- a/src/System.Management.Automation/engine/TypesV3_Ps1Xml.cs +++ /dev/null @@ -1,117 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Management.Automation; -using System.Reflection; - -namespace System.Management.Automation.Runspaces -{ - internal sealed class TypesV3_Ps1Xml - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - static MethodInfo GetMethodInfo(string typeName, string method) - { - var type = LanguagePrimitives.ConvertTo(typeName); - return GetMethodInfo(type, method); - } - - static MethodInfo GetMethodInfo(Type type, string method) - { - return type.GetMethod(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase); - } - - static ScriptBlock GetScriptBlock(string s) - { - var sb = ScriptBlock.CreateDelayParsedScriptBlock(s, isProductCode: true); - sb.LanguageMode = PSLanguageMode.FullLanguage; - return sb; - } - - public static IEnumerable Get() - { - - var td1 = new TypeData(@"System.Security.Cryptography.X509Certificates.X509Certificate2", true); - td1.Members.Add("EnhancedKeyUsageList", - new ScriptPropertyData(@"EnhancedKeyUsageList", GetScriptBlock(@",(new-object Microsoft.Powershell.Commands.EnhancedKeyUsageProperty -argumentlist $this).EnhancedKeyUsageList;"), null)); - td1.Members.Add("DnsNameList", - new ScriptPropertyData(@"DnsNameList", GetScriptBlock(@",(new-object Microsoft.Powershell.Commands.DnsNameProperty -argumentlist $this).DnsNameList;"), null)); - td1.Members.Add("SendAsTrustedIssuer", - new ScriptPropertyData(@"SendAsTrustedIssuer", GetScriptBlock(@"[Microsoft.Powershell.Commands.SendAsTrustedIssuerProperty]::ReadSendAsTrustedIssuerProperty($this)"), GetScriptBlock(@"$sendAsTrustedIssuer = $args[0] - [Microsoft.Powershell.Commands.SendAsTrustedIssuerProperty]::WriteSendAsTrustedIssuerProperty($this,$this.PsPath,$sendAsTrustedIssuer)"))); - yield return td1; - - var td2 = new TypeData(@"System.Management.Automation.Remoting.PSSenderInfo", true); - td2.Members.Add("ConnectedUser", - new ScriptPropertyData(@"ConnectedUser", GetScriptBlock(@"$this.UserInfo.Identity.Name"), null)); - td2.Members.Add("RunAsUser", - new ScriptPropertyData(@"RunAsUser", GetScriptBlock(@"if($null -ne $this.UserInfo.WindowsIdentity) - { - $this.UserInfo.WindowsIdentity.Name - }"), null)); - yield return td2; - - var td3 = new TypeData(@"System.Management.Automation.CompletionResult", true); - td3.SerializationDepth = 1; - yield return td3; - - var td4 = new TypeData(@"Deserialized.System.Management.Automation.CompletionResult", true); - td4.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td4; - - var td5 = new TypeData(@"System.Management.Automation.CommandCompletion", true); - td5.SerializationDepth = 1; - yield return td5; - - var td6 = new TypeData(@"Deserialized.System.Management.Automation.CommandCompletion", true); - td6.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td6; - - var td7 = new TypeData(@"Microsoft.PowerShell.Commands.ModuleSpecification", true); - td7.SerializationDepth = 1; - yield return td7; - - var td8 = new TypeData(@"Deserialized.Microsoft.PowerShell.Commands.ModuleSpecification", true); - td8.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td8; - - var td9 = new TypeData(@"System.Management.Automation.JobStateEventArgs", true); - td9.SerializationDepth = 2; - yield return td9; - - var td10 = new TypeData(@"Deserialized.System.Management.Automation.JobStateEventArgs", true); - td10.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td10; - - var td11 = new TypeData(@"System.Exception", true); - td11.SerializationDepth = 1; - yield return td11; - - var td12 = new TypeData(@"System.Management.Automation.Remoting.PSSessionOption", true); - td12.SerializationDepth = 1; - yield return td12; - - var td13 = new TypeData(@"Deserialized.System.Management.Automation.Remoting.PSSessionOption", true); - td13.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td13; - - var td14 = new TypeData(@"System.Management.Automation.DebuggerStopEventArgs", true); - td14.Members.Add("SerializedInvocationInfo", - new CodePropertyData("SerializedInvocationInfo", GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), "GetInvocationInfo"), null) { IsHidden = true }); - td14.SerializationMethod = "SpecificProperties"; - td14.SerializationDepth = 2; - td14.PropertySerializationSet = - new PropertySetData(new [] { "Breakpoints", "ResumeAction", "SerializedInvocationInfo" }) { Name = "PropertySerializationSet" }; - yield return td14; - - var td15 = new TypeData(@"Deserialized.System.Management.Automation.DebuggerStopEventArgs", true); - td15.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td15; - } - } -} diff --git a/src/System.Management.Automation/engine/Types_Ps1Xml.cs b/src/System.Management.Automation/engine/Types_Ps1Xml.cs deleted file mode 100644 index 0103c2df6ef..00000000000 --- a/src/System.Management.Automation/engine/Types_Ps1Xml.cs +++ /dev/null @@ -1,1980 +0,0 @@ -using System.Collections.Generic; -using System.Management.Automation; -using System.Reflection; - -namespace System.Management.Automation.Runspaces -{ - internal sealed class Types_Ps1Xml - { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - static MethodInfo GetMethodInfo(Type type, string method) - { - return type.GetMethod(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase); - } - - static ScriptBlock GetScriptBlock(string s) - { - var sb = ScriptBlock.CreateDelayParsedScriptBlock(s, isProductCode: true); - sb.LanguageMode = PSLanguageMode.FullLanguage; - return sb; - } - - public static IEnumerable Get() - { - TypeData td; - - var td2 = new TypeData(@"System.Xml.XmlNode", true); - td2.Members.Add("ToString", - new CodeMethodData("ToString", GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"XmlNode"))); - yield return td2; - - var td3 = new TypeData(@"System.Xml.XmlNodeList", true); - td3.Members.Add("ToString", - new CodeMethodData("ToString", GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"XmlNodeList"))); - yield return td3; - - var td4 = new TypeData(@"System.Management.Automation.PSDriveInfo", true); - td4.Members.Add("Used", - new ScriptPropertyData(@"Used", GetScriptBlock(@"## Ensure that this is a FileSystem drive - if($this.Provider.ImplementingType -eq - [Microsoft.PowerShell.Commands.FileSystemProvider]) - { - $driveInfo = [System.IO.DriveInfo]::New($this.Root) - if ( $driveInfo.IsReady ) { $driveInfo.TotalSize - $driveInfo.AvailableFreeSpace } - }"), null)); - td4.Members.Add("Free", - new ScriptPropertyData(@"Free", GetScriptBlock(@"## Ensure that this is a FileSystem drive - if($this.Provider.ImplementingType -eq - [Microsoft.PowerShell.Commands.FileSystemProvider]) - { - [System.IO.DriveInfo]::New($this.Root).AvailableFreeSpace - }"), null)); - yield return td4; - -#if !CORECLR - var td5 = new TypeData(@"System.DirectoryServices.PropertyValueCollection", true); - td5.Members.Add("ToString", - new CodeMethodData("ToString", GetMethodInfo(typeof(Microsoft.PowerShell.ToStringCodeMethods), @"PropertyValueCollection"))); - yield return td5; -#endif // !CORECLR - - var td6 = new TypeData(@"System.Drawing.Printing.PrintDocument", true); - td6.Members.Add("Name", - new ScriptPropertyData(@"Name", GetScriptBlock(@"$this.PrinterSettings.PrinterName"), null)); - td6.Members.Add("Color", - new ScriptPropertyData(@"Color", GetScriptBlock(@"$this.PrinterSettings.SupportsColor"), null)); - td6.Members.Add("Duplex", - new ScriptPropertyData(@"Duplex", GetScriptBlock(@"$this.PrinterSettings.Duplex"), null)); - yield return td6; - - var td7 = new TypeData(@"System.Management.Automation.ApplicationInfo", true); - td7.Members.Add("FileVersionInfo", - new ScriptPropertyData(@"FileVersionInfo", GetScriptBlock(@"[System.Diagnostics.FileVersionInfo]::getversioninfo( $this.Path )"), null)); - yield return td7; - - var td8 = new TypeData(@"System.DateTime", true); - td8.Members.Add("DateTime", - new ScriptPropertyData(@"DateTime", GetScriptBlock(@"if ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq ""Date"") - { - ""{0}"" -f $this.ToLongDateString() - } - elseif ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq ""Time"") - { - ""{0}"" -f $this.ToLongTimeString() - } - else - { - ""{0} {1}"" -f $this.ToLongDateString(), $this.ToLongTimeString() - }"), null)); - yield return td8; - - var td9 = new TypeData(@"System.Net.IPAddress", true); - td9.Members.Add("IPAddressToString", - new ScriptPropertyData(@"IPAddressToString", GetScriptBlock(@"$this.Tostring()"), null)); - td9.DefaultDisplayProperty = @"IPAddressToString"; - td9.SerializationDepth = 1; - yield return td9; - - var td10 = new TypeData(@"Deserialized.System.Net.IPAddress", true); - td10.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td10; - - var td11 = new TypeData(@"System.Diagnostics.ProcessModule", true); - td11.Members.Add("Size", - new ScriptPropertyData(@"Size", GetScriptBlock(@"$this.ModuleMemorySize / 1024"), null)); - td11.Members.Add("Company", - new ScriptPropertyData(@"Company", GetScriptBlock(@"$this.FileVersionInfo.CompanyName"), null)); - td11.Members.Add("FileVersion", - new ScriptPropertyData(@"FileVersion", GetScriptBlock(@"$this.FileVersionInfo.FileVersion"), null)); - td11.Members.Add("ProductVersion", - new ScriptPropertyData(@"ProductVersion", GetScriptBlock(@"$this.FileVersionInfo.ProductVersion"), null)); - td11.Members.Add("Description", - new ScriptPropertyData(@"Description", GetScriptBlock(@"$this.FileVersionInfo.FileDescription"), null)); - td11.Members.Add("Product", - new ScriptPropertyData(@"Product", GetScriptBlock(@"$this.FileVersionInfo.ProductName"), null)); - yield return td11; - - var td12 = new TypeData(@"System.Collections.DictionaryEntry", true); - td12.Members.Add("Name", - new AliasPropertyData("Name", "Key")); - yield return td12; - - var td13 = new TypeData(@"System.Management.Automation.PSModuleInfo", true); - td13.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "Path", "Description", "Guid", "Version", "ModuleBase", "ModuleType", "PrivateData", "AccessMode", "ExportedAliases", "ExportedCmdlets", "ExportedFunctions", "ExportedVariables", "NestedModules" }) { Name = "DefaultDisplayPropertySet" }; - yield return td13; - - var td14 = new TypeData(@"System.ServiceProcess.ServiceController", true); - td14.Members.Add("Name", - new AliasPropertyData("Name", "ServiceName")); - td14.Members.Add("RequiredServices", - new AliasPropertyData("RequiredServices", "ServicesDependedOn")); - td14.Members.Add("ToString", - new ScriptMethodData(@"ToString", GetScriptBlock(@"$this.ServiceName"))); - td14.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Status", "Name", "DisplayName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td14; - - var td15 = new TypeData(@"Deserialized.System.ServiceProcess.ServiceController", true); - td15.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Status", "Name", "DisplayName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td15; - - var td16 = new TypeData(@"System.Management.Automation.CmdletInfo", true); - td16.Members.Add("DLL", - new ScriptPropertyData(@"DLL", GetScriptBlock(@"$this.ImplementingType.Assembly.Location"), null)); - yield return td16; - - var td17 = new TypeData(@"System.Management.Automation.AliasInfo", true); - td17.Members.Add("ResolvedCommandName", - new ScriptPropertyData(@"ResolvedCommandName", GetScriptBlock(@"$this.ResolvedCommand.Name"), null)); - td17.Members.Add("DisplayName", - new ScriptPropertyData(@"DisplayName", GetScriptBlock(@"if ($this.Name.IndexOf('-') -lt 0) - { - if ($null -ne $this.ResolvedCommand) - { - $this.Name + "" -> "" + $this.ResolvedCommand.Name - } - else - { - $this.Name + "" -> "" + $this.Definition - } - } - else - { - $this.Name - }"), null)); - yield return td17; - -#if !CORECLR - var td18 = new TypeData(@"System.DirectoryServices.DirectoryEntry", true); - td18.Members.Add("ConvertLargeIntegerToInt64", - new CodeMethodData("ConvertLargeIntegerToInt64", GetMethodInfo(typeof(Microsoft.PowerShell.AdapterCodeMethods), @"ConvertLargeIntegerToInt64"))); - td18.Members.Add("ConvertDNWithBinaryToString", - new CodeMethodData("ConvertDNWithBinaryToString", GetMethodInfo(typeof(Microsoft.PowerShell.AdapterCodeMethods), @"ConvertDNWithBinaryToString"))); - td18.DefaultDisplayPropertySet = - new PropertySetData(new [] { "distinguishedName", "Path" }) { Name = "DefaultDisplayPropertySet" }; - yield return td18; -#endif // !CORECLR - - var td19 = new TypeData(@"System.IO.DirectoryInfo", true); - td19.Members.Add("Mode", - new CodePropertyData("Mode", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), "Mode"), null)); - td19.Members.Add("BaseName", - new ScriptPropertyData(@"BaseName", GetScriptBlock(@"$this.Name"), null)); - td19.Members.Add("Target", - new CodePropertyData("Target", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), "GetTarget"), null)); - td19.Members.Add("LinkType", - new CodePropertyData("LinkType", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), "GetLinkType"), null)); - td19.DefaultDisplayProperty = @"Name"; - yield return td19; - - var td20 = new TypeData(@"System.IO.FileInfo", true); - td20.Members.Add("Mode", - new CodePropertyData("Mode", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.FileSystemProvider), "Mode"), null)); - td20.Members.Add("VersionInfo", - new ScriptPropertyData(@"VersionInfo", GetScriptBlock(@"[System.Diagnostics.FileVersionInfo]::GetVersionInfo($this.FullName)"), null)); - td20.Members.Add("BaseName", - new ScriptPropertyData(@"BaseName", GetScriptBlock(@"if ($this.Extension.Length -gt 0){$this.Name.Remove($this.Name.Length - $this.Extension.Length)}else{$this.Name}"), null)); - td20.Members.Add("Target", - new CodePropertyData("Target", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), "GetTarget"), null)); - td20.Members.Add("LinkType", - new CodePropertyData("LinkType", GetMethodInfo(typeof(Microsoft.PowerShell.Commands.InternalSymbolicLinkLinkCodeMethods), "GetLinkType"), null)); - td20.DefaultDisplayPropertySet = - new PropertySetData(new [] { "LastWriteTime", "Length", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td20; - - var td21 = new TypeData(@"System.Diagnostics.FileVersionInfo", true); - td21.Members.Add("FileVersionRaw", - new ScriptPropertyData(@"FileVersionRaw", GetScriptBlock(@"New-Object System.Version -ArgumentList @( - $this.FileMajorPart - $this.FileMinorPart - $this.FileBuildPart - $this.FilePrivatePart)"), null)); - td21.Members.Add("ProductVersionRaw", - new ScriptPropertyData(@"ProductVersionRaw", GetScriptBlock(@"New-Object System.Version -ArgumentList @( - $this.ProductMajorPart - $this.ProductMinorPart - $this.ProductBuildPart - $this.ProductPrivatePart)"), null)); - yield return td21; - - var td22 = new TypeData(@"System.Diagnostics.EventLogEntry", true); - td22.Members.Add("EventID", - new ScriptPropertyData(@"EventID", GetScriptBlock(@"$this.get_EventID() -band 0xFFFF"), null)); - yield return td22; - - var td23 = new TypeData(@"System.Management.ManagementBaseObject", true); - td23.Members.Add("PSComputerName", - new AliasPropertyData("PSComputerName", "__SERVER")); - yield return td23; - - var td24 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PingStatus", true); - td24.Members.Add("IPV4Address", - new ScriptPropertyData(@"IPV4Address", GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) - $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | Select-Object -first 1"), null)); - td24.Members.Add("IPV6Address", - new ScriptPropertyData(@"IPV6Address", GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) - $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | Select-Object -first 1"), null)); - yield return td24; - - var td25 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Process", true); - td25.Members.Add("ProcessName", - new AliasPropertyData("ProcessName", "Name")); - td25.Members.Add("Handles", - new AliasPropertyData("Handles", "Handlecount")); - td25.Members.Add("VM", - new AliasPropertyData("VM", "VirtualSize")); - td25.Members.Add("WS", - new AliasPropertyData("WS", "WorkingSetSize")); - td25.Members.Add("Path", - new ScriptPropertyData(@"Path", GetScriptBlock(@"$this.ExecutablePath"), null)); - yield return td25; - - var td26 = new TypeData(@"System.Diagnostics.Process", true); - td26.Members.Add("PSConfiguration", - new PropertySetData(new [] { "Name", "Id", "PriorityClass", "FileVersion" }) { Name = "PSConfiguration" }); - td26.Members.Add("PSResources", - new PropertySetData(new [] { "Name", "Id", "Handlecount", "WorkingSet", "NonPagedMemorySize", "PagedMemorySize", "PrivateMemorySize", "VirtualMemorySize", "Threads.Count", "TotalProcessorTime" }) { Name = "PSResources" }); - td26.Members.Add("Name", - new AliasPropertyData("Name", "ProcessName")); - td26.Members.Add("SI", - new AliasPropertyData("SI", "SessionId")); - td26.Members.Add("Handles", - new AliasPropertyData("Handles", "Handlecount")); - td26.Members.Add("VM", - new AliasPropertyData("VM", "VirtualMemorySize64")); - td26.Members.Add("WS", - new AliasPropertyData("WS", "WorkingSet64")); - td26.Members.Add("PM", - new AliasPropertyData("PM", "PagedMemorySize64")); - td26.Members.Add("NPM", - new AliasPropertyData("NPM", "NonpagedSystemMemorySize64")); - td26.Members.Add("Path", - new ScriptPropertyData(@"Path", GetScriptBlock(@"$this.Mainmodule.FileName"), null)); - td26.Members.Add("Parent", - new CodePropertyData("Parent", GetMethodInfo(typeof(Microsoft.PowerShell.ProcessCodeMethods), @"GetParentProcess"))); - td26.Members.Add("Company", - new ScriptPropertyData(@"Company", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.CompanyName"), null)); - td26.Members.Add("CPU", - new ScriptPropertyData(@"CPU", GetScriptBlock(@"$this.TotalProcessorTime.TotalSeconds"), null)); - td26.Members.Add("FileVersion", - new ScriptPropertyData(@"FileVersion", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.FileVersion"), null)); - td26.Members.Add("ProductVersion", - new ScriptPropertyData(@"ProductVersion", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.ProductVersion"), null)); - td26.Members.Add("Description", - new ScriptPropertyData(@"Description", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.FileDescription"), null)); - td26.Members.Add("Product", - new ScriptPropertyData(@"Product", GetScriptBlock(@"$this.Mainmodule.FileVersionInfo.ProductName"), null)); - td26.Members.Add("__NounName", - new NotePropertyData(@"__NounName", @"Process")); - td26.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Id", "Handles", "CPU", "SI", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td26; - - var td27 = new TypeData(@"Deserialized.System.Diagnostics.Process", true); - td27.Members.Add("PSConfiguration", - new PropertySetData(new [] { "Name", "Id", "PriorityClass", "FileVersion" }) { Name = "PSConfiguration" }); - td27.Members.Add("PSResources", - new PropertySetData(new [] { "Name", "Id", "Handlecount", "WorkingSet", "NonPagedMemorySize", "PagedMemorySize", "PrivateMemorySize", "VirtualMemorySize", "Threads.Count", "TotalProcessorTime" }) { Name = "PSResources" }); - td27.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Id", "Handles", "CPU", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td27; - - var td28 = new TypeData(@"System.Management.ManagementObject#root\cli\Msft_CliAlias", true); - td28.DefaultDisplayPropertySet = - new PropertySetData(new [] { "FriendlyName", "PWhere", "Target" }) { Name = "DefaultDisplayPropertySet" }; - yield return td28; - - var td29 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_BaseBoard", true); - td29.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "PoweredOn" }) { Name = "PSStatus" }); - td29.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Model", "Name", "SerialNumber", "SKU", "Product" }) { Name = "DefaultDisplayPropertySet" }; - yield return td29; - - var td30 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_BIOS", true); - td30.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "Caption", "SMBIOSPresent" }) { Name = "PSStatus" }); - td30.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SMBIOSBIOSVersion", "Manufacturer", "Name", "SerialNumber", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td30; - - var td31 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_BootConfiguration", true); - td31.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "SettingID", "ConfigurationPath" }) { Name = "PSStatus" }); - td31.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BootDirectory", "Name", "SettingID", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td31; - - var td32 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_CDROMDrive", true); - td32.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "Drive", "ErrorCleared", "MediaLoaded", "NeedsCleaning", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td32.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Drive", "Manufacturer", "VolumeName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td32; - - var td33 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_ComputerSystem", true); - td33.Members.Add("PSStatus", - new PropertySetData(new [] { "AdminPasswordStatus", "BootupState", "ChassisBootupState", "KeyboardPasswordStatus", "PowerOnPasswordStatus", "PowerSupplyState", "PowerState", "FrontPanelResetStatus", "ThermalState", "Status", "Name" }) { Name = "PSStatus" }); - td33.Members.Add("POWER", - new PropertySetData(new [] { "Name", "PowerManagementCapabilities", "PowerManagementSupported", "PowerOnPasswordStatus", "PowerState", "PowerSupplyState" }) { Name = "POWER" }); - td33.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Domain", "Manufacturer", "Model", "Name", "PrimaryOwnerName", "TotalPhysicalMemory" }) { Name = "DefaultDisplayPropertySet" }; - yield return td33; - - var td34 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_PROCESSOR", true); - td34.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "CpuStatus", "CurrentVoltage", "DeviceID", "ErrorCleared", "ErrorDescription", "LastErrorCode", "LoadPercentage", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td34.Members.Add("PSConfiguration", - new PropertySetData(new [] { "AddressWidth", "DataWidth", "DeviceID", "ExtClock", "L2CacheSize", "L2CacheSpeed", "MaxClockSpeed", "PowerManagementSupported", "ProcessorType", "Revision", "SocketDesignation", "Version", "VoltageCaps" }) { Name = "PSConfiguration" }); - td34.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "DeviceID", "Manufacturer", "MaxClockSpeed", "Name", "SocketDesignation" }) { Name = "DefaultDisplayPropertySet" }; - yield return td34; - - var td35 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_ComputerSystemProduct", true); - td35.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Version" }) { Name = "PSStatus" }); - td35.DefaultDisplayPropertySet = - new PropertySetData(new [] { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td35; - - var td36 = new TypeData(@"System.Management.ManagementObject#root\cimv2\CIM_DataFile", true); - td36.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td36.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Compressed", "Encrypted", "Size", "Hidden", "Name", "Readable", "System", "Version", "Writeable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td36; - - var td37 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_DCOMApplication", true); - td37.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status" }) { Name = "PSStatus" }); - td37.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AppID", "InstallDate", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td37; - - var td38 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_DESKTOP", true); - td38.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "ScreenSaverActive" }) { Name = "PSStatus" }); - td38.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "ScreenSaverActive", "ScreenSaverSecure", "ScreenSaverTimeout", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td38; - - var td39 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_DESKTOPMONITOR", true); - td39.Members.Add("PSConfiguration", - new PropertySetData(new [] { "DeviceID", "Name", "PixelsPerXLogicalInch", "PixelsPerYLogicalInch", "ScreenHeight", "ScreenWidth" }) { Name = "PSConfiguration" }); - td39.Members.Add("PSStatus", - new PropertySetData(new [] { "DeviceID", "IsLocked", "LastErrorCode", "Name", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td39.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "DisplayType", "MonitorManufacturer", "Name", "ScreenHeight", "ScreenWidth" }) { Name = "DefaultDisplayPropertySet" }; - yield return td39; - - var td40 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_DeviceMemoryAddress", true); - td40.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "MemoryType" }) { Name = "PSStatus" }); - td40.DefaultDisplayPropertySet = - new PropertySetData(new [] { "MemoryType", "Name", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td40; - - var td41 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_DiskDrive", true); - td41.Members.Add("PSStatus", - new PropertySetData(new [] { "ConfigManagerErrorCode", "LastErrorCode", "NeedsCleaning", "Status", "DeviceID", "StatusInfo", "Partitions" }) { Name = "PSStatus" }); - td41.Members.Add("PSConfiguration", - new PropertySetData(new [] { "BytesPerSector", "ConfigManagerUserConfig", "DefaultBlockSize", "DeviceID", "Index", "InstallDate", "InterfaceType", "MaxBlockSize", "MaxMediaSize", "MinBlockSize", "NumberOfMediaSupported", "Partitions", "SectorsPerTrack", "Size", "TotalCylinders", "TotalHeads", "TotalSectors", "TotalTracks", "TracksPerCylinder" }) { Name = "PSConfiguration" }); - td41.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Partitions", "DeviceID", "Model", "Size", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td41; - - var td42 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_DiskQuota", true); - td42.Members.Add("PSStatus", - new PropertySetData(new [] { "__PATH", "Status" }) { Name = "PSStatus" }); - td42.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DiskSpaceUsed", "Limit", "QuotaVolume", "User" }) { Name = "DefaultDisplayPropertySet" }; - yield return td42; - - var td43 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_DMAChannel", true); - td43.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td43.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AddressSize", "DMAChannel", "MaxTransferSize", "Name", "Port" }) { Name = "DefaultDisplayPropertySet" }; - yield return td43; - - var td44 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Environment", true); - td44.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "SystemVariable" }) { Name = "PSStatus" }); - td44.DefaultDisplayPropertySet = - new PropertySetData(new [] { "VariableValue", "Name", "UserName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td44; - - var td45 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Directory", true); - td45.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Compressed", "Encrypted", "Name", "Readable", "Writeable" }) { Name = "PSStatus" }); - td45.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Hidden", "Archive", "EightDotThreeFileName", "FileSize", "Name", "Compressed", "Encrypted", "Readable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td45; - - var td46 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Group", true); - td46.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td46.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Domain", "Name", "SID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td46; - - var td47 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_IDEController", true); - td47.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td47.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td47; - - var td48 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_IRQResource", true); - td48.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Caption", "Availability" }) { Name = "PSStatus" }); - td48.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Hardware", "IRQNumber", "Name", "Shareable", "TriggerLevel", "TriggerType" }) { Name = "DefaultDisplayPropertySet" }; - yield return td48; - - var td49 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_ScheduledJob", true); - td49.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "JobId", "JobStatus", "ElapsedTime", "StartTime", "Owner" }) { Name = "PSStatus" }); - td49.DefaultDisplayPropertySet = - new PropertySetData(new [] { "JobId", "Name", "Owner", "Priority", "Command" }) { Name = "DefaultDisplayPropertySet" }; - yield return td49; - - var td50 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_LoadOrderGroup", true); - td50.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td50.DefaultDisplayPropertySet = - new PropertySetData(new [] { "GroupOrder", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td50; - - var td51 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_LogicalDisk", true); - td51.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Availability", "DeviceID", "StatusInfo" }) { Name = "PSStatus" }); - td51.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "DriveType", "ProviderName", "FreeSpace", "Size", "VolumeName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td51; - - var td52 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_LogonSession", true); - td52.Members.Add("PSStatus", - new PropertySetData(new [] { "__PATH", "Status" }) { Name = "PSStatus" }); - td52.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AuthenticationPackage", "LogonId", "LogonType", "Name", "StartTime", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td52; - - var td53 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_CACHEMEMORY", true); - td53.Members.Add("ERROR", - new PropertySetData(new [] { "DeviceID", "ErrorCorrectType" }) { Name = "ERROR" }); - td53.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "DeviceID", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td53.Members.Add("PSConfiguration", - new PropertySetData(new [] { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status", "WritePolicy" }) { Name = "PSConfiguration" }); - td53.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td53; - - var td54 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_LogicalMemoryConfiguration", true); - td54.Members.Add("PSStatus", - new PropertySetData(new [] { "AvailableVirtualMemory", "Name", "TotalVirtualMemory" }) { Name = "PSStatus" }); - td54.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "TotalVirtualMemory", "TotalPhysicalMemory", "TotalPageFileSpace" }) { Name = "DefaultDisplayPropertySet" }; - yield return td54; - - var td55 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PhysicalMemoryArray", true); - td55.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "Replaceable", "Location" }) { Name = "PSStatus" }); - td55.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Model", "Name", "MaxCapacity", "MemoryDevices" }) { Name = "DefaultDisplayPropertySet" }; - yield return td55; - - var td56 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_NetworkClient", true); - td56.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status" }) { Name = "PSStatus" }); - td56.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "InstallDate", "Manufacturer", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td56; - - var td57 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NetworkLoginProfile", true); - td57.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Privileges", "Profile", "UserId", "UserType", "Workstations" }) { Name = "DefaultDisplayPropertySet" }; - yield return td57; - - var td58 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NetworkProtocol", true); - td58.Members.Add("FULLXXX", - new PropertySetData(new [] { "ConnectionlessService", "Description", "GuaranteesDelivery", "GuaranteesSequencing", "InstallDate", "MaximumAddressSize", "MaximumMessageSize", "MessageOriented", "MinimumAddressSize", "Name", "PseudoStreamOriented", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }) { Name = "FULLXXX" }); - td58.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }) { Name = "PSStatus" }); - td58.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "GuaranteesDelivery", "GuaranteesSequencing", "ConnectionlessService", "Status", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td58; - - var td59 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NetworkConnection", true); - td59.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "ConnectionState", "Persistent", "LocalName", "RemoteName" }) { Name = "PSStatus" }); - td59.DefaultDisplayPropertySet = - new PropertySetData(new [] { "LocalName", "RemoteName", "ConnectionState", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td59; - - var td60 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapter", true); - td60.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "Name", "Status", "StatusInfo", "DeviceID" }) { Name = "PSStatus" }); - td60.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ServiceName", "MACAddress", "AdapterType", "DeviceID", "Name", "NetworkAddresses", "Speed" }) { Name = "DefaultDisplayPropertySet" }; - yield return td60; - - var td61 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NetworkAdapterConfiguration", true); - td61.Members.Add("PSStatus", - new PropertySetData(new [] { "DHCPLeaseExpires", "Index", "Description" }) { Name = "PSStatus" }); - td61.Members.Add("DHCP", - new PropertySetData(new [] { "Description", "DHCPEnabled", "DHCPLeaseExpires", "DHCPLeaseObtained", "DHCPServer", "Index" }) { Name = "DHCP" }); - td61.Members.Add("DNS", - new PropertySetData(new [] { "Description", "DNSDomain", "DNSDomainSuffixSearchOrder", "DNSEnabledForWINSResolution", "DNSHostName", "DNSServerSearchOrder", "DomainDNSRegistrationEnabled", "FullDNSRegistrationEnabled", "Index" }) { Name = "DNS" }); - td61.Members.Add("IP", - new PropertySetData(new [] { "Description", "Index", "IPAddress", "IPConnectionMetric", "IPEnabled", "IPFilterSecurityEnabled" }) { Name = "IP" }); - td61.Members.Add("WINS", - new PropertySetData(new [] { "Description", "Index", "WINSEnableLMHostsLookup", "WINSHostLookupFile", "WINSPrimaryServer", "WINSScopeID", "WINSSecondaryServer" }) { Name = "WINS" }); - td61.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DHCPEnabled", "IPAddress", "DefaultIPGateway", "DNSDomain", "ServiceName", "Description", "Index" }) { Name = "DefaultDisplayPropertySet" }; - yield return td61; - - var td62 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NTDomain", true); - td62.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DomainName" }) { Name = "PSStatus" }); - td62.Members.Add("GUID", - new PropertySetData(new [] { "DomainName", "DomainGuid" }) { Name = "GUID" }); - td62.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ClientSiteName", "DcSiteName", "Description", "DnsForestName", "DomainControllerAddress", "DomainControllerName", "DomainName", "Roles", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td62; - - var td63 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NTLogEvent", true); - td63.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Category", "CategoryString", "EventCode", "EventIdentifier", "TypeEvent", "InsertionStrings", "LogFile", "Message", "RecordNumber", "SourceName", "TimeGenerated", "TimeWritten", "Type", "UserName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td63; - - var td64 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_NTEventlogFile", true); - td64.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "LogfileName", "Name" }) { Name = "PSStatus" }); - td64.DefaultDisplayPropertySet = - new PropertySetData(new [] { "FileSize", "LogfileName", "Name", "NumberOfRecords" }) { Name = "DefaultDisplayPropertySet" }; - yield return td64; - - var td65 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_OnBoardDevice", true); - td65.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Description" }) { Name = "PSStatus" }); - td65.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceType", "SerialNumber", "Enabled", "Description" }) { Name = "DefaultDisplayPropertySet" }; - yield return td65; - - var td66 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_OperatingSystem", true); - td66.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td66.Members.Add("FREE", - new PropertySetData(new [] { "FreePhysicalMemory", "FreeSpaceInPagingFiles", "FreeVirtualMemory", "Name" }) { Name = "FREE" }); - td66.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SystemDirectory", "Organization", "BuildNumber", "RegisteredUser", "SerialNumber", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td66; - - var td67 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PageFileUsage", true); - td67.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "CurrentUsage" }) { Name = "PSStatus" }); - td67.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "PeakUsage" }) { Name = "DefaultDisplayPropertySet" }; - yield return td67; - - var td68 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PageFileSetting", true); - td68.DefaultDisplayPropertySet = - new PropertySetData(new [] { "MaximumSize", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td68; - - var td69 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_DiskPartition", true); - td69.Members.Add("PSStatus", - new PropertySetData(new [] { "Index", "Status", "StatusInfo", "Name" }) { Name = "PSStatus" }); - td69.DefaultDisplayPropertySet = - new PropertySetData(new [] { "NumberOfBlocks", "BootPartition", "Name", "PrimaryPartition", "Size", "Index" }) { Name = "DefaultDisplayPropertySet" }; - yield return td69; - - var td70 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PortResource", true); - td70.Members.Add("PSStatus", - new PropertySetData(new [] { "NetConnectionStatus", "Status", "Name", "StartingAddress", "EndingAddress" }) { Name = "PSStatus" }); - td70.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "Alias" }) { Name = "DefaultDisplayPropertySet" }; - yield return td70; - - var td71 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PortConnector", true); - td71.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "ExternalReferenceDesignator" }) { Name = "PSStatus" }); - td71.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Tag", "ConnectorType", "SerialNumber", "ExternalReferenceDesignator", "PortType" }) { Name = "DefaultDisplayPropertySet" }; - yield return td71; - - var td72 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Printer", true); - td72.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td72.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Location", "Name", "PrinterState", "PrinterStatus", "ShareName", "SystemName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td72; - - var td73 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PrinterConfiguration", true); - td73.Members.Add("PSStatus", - new PropertySetData(new [] { "DriverVersion", "Name" }) { Name = "PSStatus" }); - td73.DefaultDisplayPropertySet = - new PropertySetData(new [] { "PrintQuality", "DriverVersion", "Name", "PaperSize", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td73; - - var td74 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PrintJob", true); - td74.Members.Add("PSStatus", - new PropertySetData(new [] { "Document", "JobId", "JobStatus", "Name", "PagesPrinted", "Status", "JobIdCopy", "Name" }) { Name = "PSStatus" }); - td74.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Document", "JobId", "JobStatus", "Owner", "Priority", "Size", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td74; - - var td75 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_ProcessXXX", true); - td75.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "ProcessId" }) { Name = "PSStatus" }); - td75.Members.Add("MEMORY", - new PropertySetData(new [] { "Handle", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "Name", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "VirtualSize", "WorkingSetSize" }) { Name = "MEMORY" }); - td75.Members.Add("IO", - new PropertySetData(new [] { "Name", "ProcessId", "ReadOperationCount", "ReadTransferCount", "WriteOperationCount", "WriteTransferCount" }) { Name = "IO" }); - td75.Members.Add("STATISTICS", - new PropertySetData(new [] { "HandleCount", "Name", "KernelModeTime", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "OtherOperationCount", "OtherTransferCount", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "ProcessId", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "ReadOperationCount", "ReadTransferCount", "ThreadCount", "UserModeTime", "VirtualSize", "WorkingSetSize", "WriteOperationCount", "WriteTransferCount" }) { Name = "STATISTICS" }); - td75.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ThreadCount", "HandleCount", "Name", "Priority", "ProcessId", "WorkingSetSize" }) { Name = "DefaultDisplayPropertySet" }; - yield return td75; - - var td76 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Product", true); - td76.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Version", "InstallState" }) { Name = "PSStatus" }); - td76.DefaultDisplayPropertySet = - new PropertySetData(new [] { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td76; - - var td77 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_QuickFixEngineering", true); - td77.Members.Add("InstalledOn", - new ScriptPropertyData(@"InstalledOn", GetScriptBlock(@"if ([environment]::osversion.version.build -ge 7000) - { - # WMI team fixed the formatting issue related to InstalledOn - # property in Windows7 (to return string)..so returning the WMI's - # version directly - [DateTime]::Parse($this.psBase.properties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) - } - else - { - $orig = $this.psBase.properties[""InstalledOn""].Value - $date = [datetime]::FromFileTimeUTC($(""0x"" + $orig)) - if ($date -lt ""1/1/1980"") - { - if ($orig -match ""([0-9]{4})([01][0-9])([012][0-9])"") - { - new-object datetime @([int]$matches[1], [int]$matches[2], [int]$matches[3]) - } - } - else - { - $date - } - }"), null)); - td77.Members.Add("PSStatus", - new PropertySetData(new [] { "__PATH", "Status" }) { Name = "PSStatus" }); - td77.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Description", "FixComments", "HotFixID", "InstallDate", "InstalledBy", "InstalledOn", "Name", "ServicePackInEffect", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td77; - - var td78 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_QuotaSetting", true); - td78.Members.Add("PSStatus", - new PropertySetData(new [] { "State", "VolumePath", "Caption" }) { Name = "PSStatus" }); - td78.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "DefaultLimit", "SettingID", "State", "VolumePath", "DefaultWarningLimit" }) { Name = "DefaultDisplayPropertySet" }; - yield return td78; - - var td79 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_OSRecoveryConfiguration", true); - td79.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DebugFilePath", "Name", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td79; - - var td80 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Registry", true); - td80.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "CurrentSize", "MaximumSize", "ProposedSize" }) { Name = "PSStatus" }); - td80.DefaultDisplayPropertySet = - new PropertySetData(new [] { "CurrentSize", "MaximumSize", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td80; - - var td81 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SCSIController", true); - td81.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "StatusInfo" }) { Name = "PSStatus" }); - td81.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DriverName", "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td81; - - var td82 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_PerfRawData_PerfNet_Server", true); - td82.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "LogonPerSec", "LogonTotal", "Name", "ServerSessions", "WorkItemShortages" }) { Name = "DefaultDisplayPropertySet" }; - yield return td82; - - var td83 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Service", true); - td83.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status", "ExitCode" }) { Name = "PSStatus" }); - td83.Members.Add("PSConfiguration", - new PropertySetData(new [] { "DesktopInteract", "ErrorControl", "Name", "PathName", "ServiceType", "StartMode" }) { Name = "PSConfiguration" }); - td83.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ExitCode", "Name", "ProcessId", "StartMode", "State", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td83; - - var td84 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_Share", true); - td84.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Type", "Name" }) { Name = "PSStatus" }); - td84.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "Path", "Description" }) { Name = "DefaultDisplayPropertySet" }; - yield return td84; - - var td85 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SoftwareElement", true); - td85.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SoftwareElementState", "Name" }) { Name = "PSStatus" }); - td85.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "Path", "SerialNumber", "SoftwareElementID", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td85; - - var td86 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SoftwareFeature", true); - td86.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "InstallState", "LastUse" }) { Name = "PSStatus" }); - td86.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "IdentifyingNumber", "ProductName", "Vendor", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td86; - - var td87 = new TypeData(@"System.Management.ManagementObject#root\cimv2\WIN32_SoundDevice", true); - td87.Members.Add("PSStatus", - new PropertySetData(new [] { "ConfigManagerUserConfig", "Name", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td87.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Name", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td87; - - var td88 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_StartupCommand", true); - td88.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Command", "User", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td88; - - var td89 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SystemAccount", true); - td89.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SIDType", "Name", "Domain" }) { Name = "PSStatus" }); - td89.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Domain", "Name", "SID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td89; - - var td90 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SystemDriver", true); - td90.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "State", "ExitCode", "Started", "ServiceSpecificExitCode" }) { Name = "PSStatus" }); - td90.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DisplayName", "Name", "State", "Status", "Started" }) { Name = "DefaultDisplayPropertySet" }; - yield return td90; - - var td91 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SystemEnclosure", true); - td91.Members.Add("PSStatus", - new PropertySetData(new [] { "Tag", "Status", "Name", "SecurityStatus" }) { Name = "PSStatus" }); - td91.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Model", "LockPresent", "SerialNumber", "SMBIOSAssetTag", "SecurityStatus" }) { Name = "DefaultDisplayPropertySet" }; - yield return td91; - - var td92 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_SystemSlot", true); - td92.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SlotDesignation" }) { Name = "PSStatus" }); - td92.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SlotDesignation", "Tag", "SupportsHotPlug", "Status", "Shared", "PMESignal", "MaxDataWidth" }) { Name = "DefaultDisplayPropertySet" }; - yield return td92; - - var td93 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_TapeDrive", true); - td93.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Availability", "DeviceID", "NeedsCleaning", "StatusInfo" }) { Name = "PSStatus" }); - td93.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "Id", "Manufacturer", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td93; - - var td94 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_TemperatureProbe", true); - td94.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "CurrentReading", "DeviceID", "Name", "StatusInfo" }) { Name = "PSStatus" }); - td94.DefaultDisplayPropertySet = - new PropertySetData(new [] { "CurrentReading", "Name", "Description", "MinReadable", "MaxReadable", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td94; - - var td95 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_TimeZone", true); - td95.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Bias", "SettingID", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td95; - - var td96 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_UninterruptiblePowerSupply", true); - td96.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DeviceID", "EstimatedChargeRemaining", "EstimatedRunTime", "Name", "StatusInfo", "TimeOnBackup" }) { Name = "PSStatus" }); - td96.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "EstimatedRunTime", "Name", "TimeOnBackup", "UPSPort", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td96; - - var td97 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_UserAccount", true); - td97.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Caption", "PasswordExpires" }) { Name = "PSStatus" }); - td97.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AccountType", "Caption", "Domain", "SID", "FullName", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td97; - - var td98 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_VoltageProbe", true); - td98.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DeviceID", "Name", "NominalReading", "StatusInfo" }) { Name = "PSStatus" }); - td98.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Status", "Description", "CurrentReading", "MaxReadable", "MinReadable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td98; - - var td99 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_VolumeQuotaSetting", true); - td99.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Element", "Setting" }) { Name = "DefaultDisplayPropertySet" }; - yield return td99; - - var td100 = new TypeData(@"System.Management.ManagementObject#root\cimv2\Win32_WMISetting", true); - td100.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BuildVersion", "Caption", "DatabaseDirectory", "EnableEvents", "LoggingLevel", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td100; - - var td101 = new TypeData(@"System.Management.ManagementObject", true); - td101.Members.Add("ConvertToDateTime", - new ScriptMethodData(@"ConvertToDateTime", GetScriptBlock(@"[System.Management.ManagementDateTimeConverter]::ToDateTime($args[0])"))); - td101.Members.Add("ConvertFromDateTime", - new ScriptMethodData(@"ConvertFromDateTime", GetScriptBlock(@"[System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($args[0])"))); - yield return td101; - - Exception exception; - var securityDescriptorCommandsBaseType = Language.TypeResolver.ResolveType("Microsoft.PowerShell.Commands.SecurityDescriptorCommandsBase", out exception); - - var td102 = new TypeData(@"System.Security.AccessControl.ObjectSecurity", true); - td102.Members.Add("Path", new CodePropertyData("Path", GetMethodInfo(securityDescriptorCommandsBaseType, "GetPath"), null)); - td102.Members.Add("Owner", new CodePropertyData("Owner", GetMethodInfo(securityDescriptorCommandsBaseType, "GetOwner"), null)); - td102.Members.Add("Group", new CodePropertyData("Group", GetMethodInfo(securityDescriptorCommandsBaseType, "GetGroup"), null)); - td102.Members.Add("Access", new CodePropertyData("Access", GetMethodInfo(securityDescriptorCommandsBaseType, "GetAccess"), null)); - td102.Members.Add("Sddl", new CodePropertyData("Sddl", GetMethodInfo(securityDescriptorCommandsBaseType, "GetSddl"), null)); - td102.Members.Add("AccessToString", - new ScriptPropertyData(@"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;"), null)); - td102.Members.Add("AuditToString", - new ScriptPropertyData(@"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;"), null)); - yield return td102; - - var td103 = new TypeData(@"Microsoft.PowerShell.Commands.HistoryInfo", true); - td103.DefaultKeyPropertySet = - new PropertySetData(new [] { "Id" }) { Name = "DefaultKeyPropertySet" }; - yield return td103; - - var td104 = new TypeData(@"System.Management.ManagementClass", true); - td104.Members.Add("Name", - new AliasPropertyData("Name", "__Class")); - yield return td104; - - var td105 = new TypeData(@"System.Management.Automation.Runspaces.PSSession", true); - td105.Members.Add("State", - new ScriptPropertyData(@"State", GetScriptBlock(@"$this.Runspace.RunspaceStateInfo.State"), null)); - td105.Members.Add("IdleTimeout", - new ScriptPropertyData(@"IdleTimeout", GetScriptBlock(@"$this.Runspace.ConnectionInfo.IdleTimeout"), null)); - td105.Members.Add("OutputBufferingMode", - new ScriptPropertyData(@"OutputBufferingMode", GetScriptBlock(@"$this.Runspace.ConnectionInfo.OutputBufferingMode"), null)); - td105.Members.Add("DisconnectedOn", - new ScriptPropertyData(@"DisconnectedOn", GetScriptBlock(@"$this.Runspace.DisconnectedOn"), null)); - td105.Members.Add("ExpiresOn", - new ScriptPropertyData(@"ExpiresOn", GetScriptBlock(@"$this.Runspace.ExpiresOn"), null)); - yield return td105; - - var td106 = new TypeData(@"System.Guid", true); - td106.Members.Add("Guid", - new ScriptPropertyData(@"Guid", GetScriptBlock(@"$this.ToString()"), null)); - yield return td106; - - var td107 = new TypeData(@"System.Management.Automation.Signature", true); - td107.SerializationDepth = 2; - yield return td107; - - var td108 = new TypeData(@"System.Management.Automation.Job", true); - td108.Members.Add("State", - new ScriptPropertyData(@"State", GetScriptBlock(@"$this.JobStateInfo.State.ToString()"), null)); - td108.SerializationMethod = "SpecificProperties"; - td108.SerializationDepth = 2; - td108.PropertySerializationSet = - new PropertySetData(new [] { "HasMoreData", "StatusMessage", "Location", "Command", "JobStateInfo", "InstanceId", "Id", "Name", "State", "ChildJobs", "PSJobTypeName", "PSBeginTime", "PSEndTime" }) { Name = "PropertySerializationSet" }; - yield return td108; - - var td109 = new TypeData(@"System.Management.Automation.JobStateInfo", true); - td109.SerializationDepth = 1; - yield return td109; - - var td110 = new TypeData(@"Deserialized.System.Management.Automation.JobStateInfo", true); - td110.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td110; - - var td111 = new TypeData(@"Microsoft.PowerShell.DeserializingTypeConverter", true); - td111.TypeConverter = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td111; - - var td112 = new TypeData(@"System.Net.Mail.MailAddress", true); - td112.SerializationDepth = 1; - yield return td112; - - var td113 = new TypeData(@"Deserialized.System.Net.Mail.MailAddress", true); - td113.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td113; - - var td114 = new TypeData(@"System.Globalization.CultureInfo", true); - td114.SerializationMethod = "SpecificProperties"; - td114.SerializationDepth = 1; - td114.PropertySerializationSet = - new PropertySetData(new [] { "LCID", "Name", "DisplayName", "IetfLanguageTag", "ThreeLetterISOLanguageName", "ThreeLetterWindowsLanguageName", "TwoLetterISOLanguageName" }) { Name = "PropertySerializationSet" }; - yield return td114; - - var td115 = new TypeData(@"Deserialized.System.Globalization.CultureInfo", true); - td115.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td115; - - var td116 = new TypeData(@"System.Management.Automation.PSCredential", true); - td116.SerializationDepth = 1; - yield return td116; - - var td117 = new TypeData(@"Deserialized.System.Management.Automation.PSCredential", true); - td117.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td117; - - var td118 = new TypeData(@"System.Management.Automation.PSPrimitiveDictionary", true); - td118.SerializationDepth = 1; - yield return td118; - - var td119 = new TypeData(@"Deserialized.System.Management.Automation.PSPrimitiveDictionary", true); - td119.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td119; - - var td120 = new TypeData(@"System.Management.Automation.SwitchParameter", true); - td120.SerializationDepth = 1; - yield return td120; - - var td121 = new TypeData(@"Deserialized.System.Management.Automation.SwitchParameter", true); - td121.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td121; - - var td122 = new TypeData(@"System.Management.Automation.PSListModifier", true); - td122.SerializationDepth = 2; - yield return td122; - - var td123 = new TypeData(@"Deserialized.System.Management.Automation.PSListModifier", true); - td123.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td123; - - var td124 = new TypeData(@"System.Security.Cryptography.X509Certificates.X509Certificate2", true); - td124.SerializationMethod = "SpecificProperties"; - td124.SerializationDepth = 1; - td124.PropertySerializationSet = - new PropertySetData(new [] { "RawData" }) { Name = "PropertySerializationSet" }; - yield return td124; - - var td125 = new TypeData(@"Deserialized.System.Security.Cryptography.X509Certificates.X509Certificate2", true); - td125.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td125; - - var td126 = new TypeData(@"System.Security.Cryptography.X509Certificates.X500DistinguishedName", true); - td126.SerializationMethod = "SpecificProperties"; - td126.SerializationDepth = 1; - td126.PropertySerializationSet = - new PropertySetData(new [] { "RawData" }) { Name = "PropertySerializationSet" }; - yield return td126; - - var td127 = new TypeData(@"Deserialized.System.Security.Cryptography.X509Certificates.X500DistinguishedName", true); - td127.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td127; - - var td128 = new TypeData(@"System.Security.AccessControl.RegistrySecurity", true); - td128.SerializationDepth = 1; - yield return td128; - - var td129 = new TypeData(@"Deserialized.System.Security.AccessControl.RegistrySecurity", true); - td129.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td129; - - var td130 = new TypeData(@"System.Security.AccessControl.FileSystemSecurity", true); - td130.SerializationDepth = 1; - yield return td130; - - var td131 = new TypeData(@"Deserialized.System.Security.AccessControl.FileSystemSecurity", true); - td131.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td131; - - var td132 = new TypeData(@"HelpInfo", true); - td132.SerializationDepth = 1; - yield return td132; - - var td133 = new TypeData(@"System.Management.Automation.PSTypeName", true); - td133.SerializationMethod = "String"; - td133.StringSerializationSource = "Name"; - yield return td133; - - var td134 = new TypeData(@"System.Management.Automation.ParameterMetadata", true); - td134.SerializationMethod = "SpecificProperties"; - td134.PropertySerializationSet = - new PropertySetData(new [] { "Name", "ParameterType", "Aliases", "IsDynamic", "SwitchParameter" }) { Name = "PropertySerializationSet" }; - yield return td134; - - var td135 = new TypeData(@"System.Management.Automation.CommandInfo", true); - td135.Members.Add("Namespace", - new AliasPropertyData("Namespace", "ModuleName") { IsHidden = true }); - td135.Members.Add("HelpUri", - new ScriptPropertyData(@"HelpUri", GetScriptBlock(@"$oldProgressPreference = $ProgressPreference - $ProgressPreference = 'SilentlyContinue' - try - { - [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($this) - } - catch {} - finally - { - $ProgressPreference = $oldProgressPreference - }"), null)); - yield return td135; - - var td136 = new TypeData(@"System.Management.Automation.ParameterSetMetadata", true); - td136.Members.Add("Flags", - new CodePropertyData("Flags", GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), "GetParameterSetMetadataFlags"), null) { IsHidden = true }); - td136.SerializationMethod = "SpecificProperties"; - td136.PropertySerializationSet = - new PropertySetData(new [] { "Position", "Flags", "HelpMessage" }) { Name = "PropertySerializationSet" }; - yield return td136; - - var td137 = new TypeData(@"Deserialized.System.Management.Automation.ParameterSetMetadata", true); - td137.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td137; - - var td138 = new TypeData(@"Deserialized.System.Management.Automation.ExtendedTypeDefinition", true); - td138.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td138; - - var td139 = new TypeData(@"System.Management.Automation.ExtendedTypeDefinition", true); - td139.SerializationMethod = "SpecificProperties"; - td139.SerializationDepth = 1; - td139.DefaultDisplayPropertySet = - new PropertySetData(new [] { "TypeNames", "FormatViewDefinition" }) { Name = "DefaultDisplayPropertySet" }; - // Serialize TypeName for remote machines running earlier versions of PowerShell that do not - // expect TypeNames, it won't do the right thing when there are multiple type names, but - // it's better than having no type names. - td139.PropertySerializationSet = - new PropertySetData(new [] { "TypeName", "TypeNames", "FormatViewDefinition" }) { Name = "PropertySerializationSet" }; - yield return td139; - - var td140 = new TypeData(@"Deserialized.System.Management.Automation.FormatViewDefinition", true); - td140.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td140; - - var td141 = new TypeData(@"System.Management.Automation.FormatViewDefinition", true); - td141.Members.Add("InstanceId", - new CodePropertyData("InstanceId", GetMethodInfo(typeof(Microsoft.PowerShell.DeserializingTypeConverter), "GetFormatViewDefinitionInstanceId"), null) { IsHidden = true }); - td141.SerializationDepth = 1; - yield return td141; - - var td142 = new TypeData(@"Deserialized.System.Management.Automation.PSControl", true); - td142.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td142; - - var td143 = new TypeData(@"System.Management.Automation.PSControl", true); - td143.SerializationDepth = 1; - yield return td143; - - td = new TypeData(@"Deserialized.System.Management.Automation.PSControlGroupBy", true); - td.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td; - - td = new TypeData(@"System.Management.Automation.PSControlGroupBy", true); - td.SerializationDepth = 2; - yield return td; - - td = new TypeData(@"Deserialized.System.Management.Automation.EntrySelectedBy", true); - td.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td; - - td = new TypeData(@"System.Management.Automation.EntrySelectedBy", true); - td.SerializationDepth = 1; - yield return td; - - var td144 = new TypeData(@"Deserialized.System.Management.Automation.DisplayEntry", true); - td144.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td144; - - var td145 = new TypeData(@"System.Management.Automation.DisplayEntry", true); - td145.SerializationDepth = 1; - yield return td145; - - var td146 = new TypeData(@"Deserialized.System.Management.Automation.TableControlColumnHeader", true); - td146.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td146; - - var td147 = new TypeData(@"System.Management.Automation.TableControlColumnHeader", true); - td147.SerializationDepth = 1; - yield return td147; - - var td148 = new TypeData(@"Deserialized.System.Management.Automation.TableControlRow", true); - td148.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td148; - - var td149 = new TypeData(@"System.Management.Automation.TableControlRow", true); - td149.SerializationDepth = 1; - yield return td149; - - var td150 = new TypeData(@"Deserialized.System.Management.Automation.TableControlColumn", true); - td150.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td150; - - var td151 = new TypeData(@"System.Management.Automation.TableControlColumn", true); - td151.SerializationDepth = 1; - yield return td151; - - var td152 = new TypeData(@"Deserialized.System.Management.Automation.ListControlEntry", true); - td152.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td152; - - var td153 = new TypeData(@"System.Management.Automation.ListControlEntry", true); - td153.SerializationMethod = "SpecificProperties"; - td153.SerializationDepth = 1; - td153.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Items", "EntrySelectedBy" }) { Name = "DefaultDisplayPropertySet" }; - // Serialize SelectedBy for remote machines running earlier versions of PowerShell that do not - // expect EntrySelectedBy, it won't do the right thing when there are conditions in the EntrySelectedBy, - // but it's better than nothing. - td153.PropertySerializationSet = - new PropertySetData(new [] { "Items", "SelectedBy", "EntrySelectedBy" }) { Name = "PropertySerializationSet" }; - yield return td153; - - var td154 = new TypeData(@"Deserialized.System.Management.Automation.ListControlEntryItem", true); - td154.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td154; - - var td155 = new TypeData(@"System.Management.Automation.ListControlEntryItem", true); - td155.SerializationDepth = 1; - yield return td155; - - var td156 = new TypeData(@"Deserialized.System.Management.Automation.WideControlEntryItem", true); - td156.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td156; - - var td157 = new TypeData(@"System.Management.Automation.WideControlEntryItem", true); - td157.SerializationDepth = 1; - yield return td157; - - td = new TypeData(@"Deserialized.System.Management.Automation.CustomControlEntry", true); - td.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td; - - td = new TypeData(@"System.Management.Automation.CustomControlEntry", true); - td.SerializationDepth = 1; - yield return td; - - td = new TypeData(@"Deserialized.System.Management.Automation.CustomItemBase", true); - td.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td; - - td = new TypeData(@"System.Management.Automation.CustomItemBase", true); - td.SerializationDepth = 1; - yield return td; - - var td158 = new TypeData(@"System.Web.Services.Protocols.SoapException", true); - td158.Members.Add("PSMessageDetails", - new ScriptPropertyData(@"PSMessageDetails", GetScriptBlock(@"$this.Detail.""#text"""), null)); - yield return td158; - - var td159 = new TypeData(@"System.Management.Automation.ErrorRecord", true); - td159.Members.Add("PSMessageDetails", - new ScriptPropertyData(@"PSMessageDetails", GetScriptBlock(@"& { Set-StrictMode -Version 1; $this.Exception.InnerException.PSMessageDetails }"), null)); - yield return td159; - - var td160 = new TypeData(@"Deserialized.System.Enum", true); - td160.Members.Add("Value", - new ScriptPropertyData(@"Value", GetScriptBlock(@"$this.ToString()"), null)); - yield return td160; - - var td161 = new TypeData(@"Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData", true); - td161.SerializationDepth = 1; - yield return td161; - - var td162 = new TypeData(@"Deserialized.Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData", true); - td162.SerializationDepth = 1; - yield return td162; - - var td163 = new TypeData(@"System.Management.ManagementEventArgs", true); - td163.SerializationDepth = 2; - yield return td163; - - var td164 = new TypeData(@"Deserialized.System.Management.ManagementEventArgs", true); - td164.SerializationDepth = 2; - yield return td164; - - var td165 = new TypeData(@"System.Management.Automation.CallStackFrame", true); - td165.Members.Add("Command", - new ScriptPropertyData(@"Command", GetScriptBlock(@"if ($null -eq $this.InvocationInfo) { return $this.FunctionName } - $commandInfo = $this.InvocationInfo.MyCommand - if ($null -eq $commandInfo) { return $this.InvocationInfo.InvocationName } - if ($commandInfo.Name -ne """") { return $commandInfo.Name } - return $this.FunctionName"), null)); - td165.Members.Add("Location", - new ScriptPropertyData(@"Location", GetScriptBlock(@"$this.GetScriptLocation()"), null)); - td165.Members.Add("Arguments", - new ScriptPropertyData(@"Arguments", GetScriptBlock(@"$argumentsBuilder = new-object System.Text.StringBuilder - - $null = $( - $argumentsBuilder.Append(""{"") - foreach ($entry in $this.InvocationInfo.BoundParameters.GetEnumerator()) - { - if ($argumentsBuilder.Length -gt 1) - { - $argumentsBuilder.Append("", ""); - } - - $argumentsBuilder.Append($entry.Key).Append(""="") - - if ($entry.Value) - { - $argumentsBuilder.Append([string]$entry.Value) - } - } - - foreach ($arg in $this.InvocationInfo.UnboundArguments.GetEnumerator()) - { - if ($argumentsBuilder.Length -gt 1) - { - $argumentsBuilder.Append("", "") - } - if ($arg) - { - $argumentsBuilder.Append([string]$arg) - } - else - { - $argumentsBuilder.Append('$null') - } - } - - $argumentsBuilder.Append('}'); - ) - - return $argumentsBuilder.ToString();"), null)); - yield return td165; - - var td166 = new TypeData(@"Microsoft.PowerShell.Commands.PSSessionConfigurationCommands#PSSessionConfiguration", true); - td166.Members.Add("Permission", - new ScriptPropertyData(@"Permission", GetScriptBlock(@"trap { continue; } - $private:sd = $null - $private:sd = new-object System.Security.AccessControl.CommonSecurityDescriptor $false,$false,$this.SecurityDescriptorSddl - if ($private:sd) - { - # reset trap - trap { } - $private:dacls = """"; - $private:first = $true - $private:sd.DiscretionaryAcl | ForEach-Object { - trap { } - if ($private:first) - { - $private:first = $false; - } - else - { - $private:dacls += "", "" - } - $private:dacls += $_.SecurityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() + "" "" + $_.AceType - } # end of foreach - - return $private:dacls - }"), null)); - yield return td166; - - var td167 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PingStatus", true); - td167.Members.Add("IPV4Address", - new ScriptPropertyData(@"IPV4Address", GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) - $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } | Select-Object -first 1"), null)); - td167.Members.Add("IPV6Address", - new ScriptPropertyData(@"IPV6Address", GetScriptBlock(@"$iphost = [System.Net.Dns]::GetHostEntry($this.address) - $iphost.AddressList | Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetworkV6 } | Select-Object -first 1"), null)); - yield return td167; - - var td168 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Process", true); - td168.Members.Add("ProcessName", - new AliasPropertyData("ProcessName", "Name")); - td168.Members.Add("Handles", - new AliasPropertyData("Handles", "Handlecount")); - td168.Members.Add("VM", - new AliasPropertyData("VM", "VirtualSize")); - td168.Members.Add("WS", - new AliasPropertyData("WS", "WorkingSetSize")); - td168.Members.Add("Path", - new ScriptPropertyData(@"Path", GetScriptBlock(@"$this.ExecutablePath"), null)); - td168.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ProcessId", "Name", "HandleCount", "WorkingSetSize", "VirtualSize" }) { Name = "DefaultDisplayPropertySet" }; - yield return td168; - - var td169 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Msft_CliAlias", true); - td169.DefaultDisplayPropertySet = - new PropertySetData(new [] { "FriendlyName", "PWhere", "Target" }) { Name = "DefaultDisplayPropertySet" }; - yield return td169; - - var td170 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BaseBoard", true); - td170.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "PoweredOn" }) { Name = "PSStatus" }); - td170.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Model", "Name", "SerialNumber", "SKU", "Product" }) { Name = "DefaultDisplayPropertySet" }; - yield return td170; - - var td171 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BIOS", true); - td171.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "Caption", "SMBIOSPresent" }) { Name = "PSStatus" }); - td171.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SMBIOSBIOSVersion", "Manufacturer", "Name", "SerialNumber", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td171; - - var td172 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_BootConfiguration", true); - td172.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "SettingID", "ConfigurationPath" }) { Name = "PSStatus" }); - td172.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BootDirectory", "Name", "SettingID", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td172; - - var td173 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_CDROMDrive", true); - td173.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "Drive", "ErrorCleared", "MediaLoaded", "NeedsCleaning", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td173.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Drive", "Manufacturer", "VolumeName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td173; - - var td174 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystem", true); - td174.Members.Add("PSStatus", - new PropertySetData(new [] { "AdminPasswordStatus", "BootupState", "ChassisBootupState", "KeyboardPasswordStatus", "PowerOnPasswordStatus", "PowerSupplyState", "PowerState", "FrontPanelResetStatus", "ThermalState", "Status", "Name" }) { Name = "PSStatus" }); - td174.Members.Add("POWER", - new PropertySetData(new [] { "Name", "PowerManagementCapabilities", "PowerManagementSupported", "PowerOnPasswordStatus", "PowerState", "PowerSupplyState" }) { Name = "POWER" }); - td174.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Domain", "Manufacturer", "Model", "Name", "PrimaryOwnerName", "TotalPhysicalMemory" }) { Name = "DefaultDisplayPropertySet" }; - yield return td174; - - var td175 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_PROCESSOR", true); - td175.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "CpuStatus", "CurrentVoltage", "DeviceID", "ErrorCleared", "ErrorDescription", "LastErrorCode", "LoadPercentage", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td175.Members.Add("PSConfiguration", - new PropertySetData(new [] { "AddressWidth", "DataWidth", "DeviceID", "ExtClock", "L2CacheSize", "L2CacheSpeed", "MaxClockSpeed", "PowerManagementSupported", "ProcessorType", "Revision", "SocketDesignation", "Version", "VoltageCaps" }) { Name = "PSConfiguration" }); - td175.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "DeviceID", "Manufacturer", "MaxClockSpeed", "Name", "SocketDesignation" }) { Name = "DefaultDisplayPropertySet" }; - yield return td175; - - var td176 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ComputerSystemProduct", true); - td176.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Version" }) { Name = "PSStatus" }); - td176.DefaultDisplayPropertySet = - new PropertySetData(new [] { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td176; - - var td177 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/CIM_DataFile", true); - td177.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td177.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Compressed", "Encrypted", "Size", "Hidden", "Name", "Readable", "System", "Version", "Writeable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td177; - - var td178 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DCOMApplication", true); - td178.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status" }) { Name = "PSStatus" }); - td178.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AppID", "InstallDate", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td178; - - var td179 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOP", true); - td179.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "ScreenSaverActive" }) { Name = "PSStatus" }); - td179.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "ScreenSaverActive", "ScreenSaverSecure", "ScreenSaverTimeout", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td179; - - var td180 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_DESKTOPMONITOR", true); - td180.Members.Add("PSConfiguration", - new PropertySetData(new [] { "DeviceID", "Name", "PixelsPerXLogicalInch", "PixelsPerYLogicalInch", "ScreenHeight", "ScreenWidth" }) { Name = "PSConfiguration" }); - td180.Members.Add("PSStatus", - new PropertySetData(new [] { "DeviceID", "IsLocked", "LastErrorCode", "Name", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td180.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "DisplayType", "MonitorManufacturer", "Name", "ScreenHeight", "ScreenWidth" }) { Name = "DefaultDisplayPropertySet" }; - yield return td180; - - var td181 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DeviceMemoryAddress", true); - td181.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "MemoryType" }) { Name = "PSStatus" }); - td181.DefaultDisplayPropertySet = - new PropertySetData(new [] { "MemoryType", "Name", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td181; - - var td182 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskDrive", true); - td182.Members.Add("PSStatus", - new PropertySetData(new [] { "ConfigManagerErrorCode", "LastErrorCode", "NeedsCleaning", "Status", "DeviceID", "StatusInfo", "Partitions" }) { Name = "PSStatus" }); - td182.Members.Add("PSConfiguration", - new PropertySetData(new [] { "BytesPerSector", "ConfigManagerUserConfig", "DefaultBlockSize", "DeviceID", "Index", "InstallDate", "InterfaceType", "MaxBlockSize", "MaxMediaSize", "MinBlockSize", "NumberOfMediaSupported", "Partitions", "SectorsPerTrack", "Size", "TotalCylinders", "TotalHeads", "TotalSectors", "TotalTracks", "TracksPerCylinder" }) { Name = "PSConfiguration" }); - td182.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Partitions", "DeviceID", "Model", "Size", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td182; - - var td183 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskQuota", true); - td183.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DiskSpaceUsed", "Limit", "QuotaVolume", "User" }) { Name = "DefaultDisplayPropertySet" }; - yield return td183; - - var td184 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DMAChannel", true); - td184.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td184.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AddressSize", "DMAChannel", "MaxTransferSize", "Name", "Port" }) { Name = "DefaultDisplayPropertySet" }; - yield return td184; - - var td185 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Environment", true); - td185.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "SystemVariable" }) { Name = "PSStatus" }); - td185.DefaultDisplayPropertySet = - new PropertySetData(new [] { "VariableValue", "Name", "UserName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td185; - - var td186 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Directory", true); - td186.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Compressed", "Encrypted", "Name", "Readable", "Writeable" }) { Name = "PSStatus" }); - td186.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Hidden", "Archive", "EightDotThreeFileName", "FileSize", "Name", "Compressed", "Encrypted", "Readable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td186; - - var td187 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Group", true); - td187.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td187.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Domain", "Name", "SID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td187; - - var td188 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IDEController", true); - td188.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td188.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td188; - - var td189 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_IRQResource", true); - td189.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Caption", "Availability" }) { Name = "PSStatus" }); - td189.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Hardware", "IRQNumber", "Name", "Shareable", "TriggerLevel", "TriggerType" }) { Name = "DefaultDisplayPropertySet" }; - yield return td189; - - var td190 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ScheduledJob", true); - td190.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "JobId", "JobStatus", "ElapsedTime", "StartTime", "Owner" }) { Name = "PSStatus" }); - td190.DefaultDisplayPropertySet = - new PropertySetData(new [] { "JobId", "Name", "Owner", "Priority", "Command" }) { Name = "DefaultDisplayPropertySet" }; - yield return td190; - - var td191 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LoadOrderGroup", true); - td191.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td191.DefaultDisplayPropertySet = - new PropertySetData(new [] { "GroupOrder", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td191; - - var td192 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalDisk", true); - td192.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Availability", "DeviceID", "StatusInfo" }) { Name = "PSStatus" }); - td192.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "DriveType", "ProviderName", "FreeSpace", "Size", "VolumeName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td192; - - var td193 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogonSession", true); - td193.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AuthenticationPackage", "LogonId", "LogonType", "Name", "StartTime", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td193; - - var td194 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_CACHEMEMORY", true); - td194.Members.Add("ERROR", - new PropertySetData(new [] { "DeviceID", "ErrorCorrectType" }) { Name = "ERROR" }); - td194.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "DeviceID", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td194.Members.Add("PSConfiguration", - new PropertySetData(new [] { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status", "WritePolicy" }) { Name = "PSConfiguration" }); - td194.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BlockSize", "CacheSpeed", "CacheType", "DeviceID", "InstalledSize", "Level", "MaxCacheSize", "NumberOfBlocks", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td194; - - var td195 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogicalMemoryConfiguration", true); - td195.Members.Add("PSStatus", - new PropertySetData(new [] { "AvailableVirtualMemory", "Name", "TotalVirtualMemory" }) { Name = "PSStatus" }); - td195.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "TotalVirtualMemory", "TotalPhysicalMemory", "TotalPageFileSpace" }) { Name = "DefaultDisplayPropertySet" }; - yield return td195; - - var td196 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PhysicalMemoryArray", true); - td196.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "Replaceable", "Location" }) { Name = "PSStatus" }); - td196.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Model", "Name", "MaxCapacity", "MemoryDevices" }) { Name = "DefaultDisplayPropertySet" }; - yield return td196; - - var td197 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_NetworkClient", true); - td197.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status" }) { Name = "PSStatus" }); - td197.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "InstallDate", "Manufacturer", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td197; - - var td198 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkLoginProfile", true); - td198.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Privileges", "Profile", "UserId", "UserType", "Workstations" }) { Name = "DefaultDisplayPropertySet" }; - yield return td198; - - var td199 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkProtocol", true); - td199.Members.Add("FULLXXX", - new PropertySetData(new [] { "ConnectionlessService", "Description", "GuaranteesDelivery", "GuaranteesSequencing", "InstallDate", "MaximumAddressSize", "MaximumMessageSize", "MessageOriented", "MinimumAddressSize", "Name", "PseudoStreamOriented", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }) { Name = "FULLXXX" }); - td199.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status", "SupportsBroadcasting", "SupportsConnectData", "SupportsDisconnectData", "SupportsEncryption", "SupportsExpeditedData", "SupportsFragmentation", "SupportsGracefulClosing", "SupportsGuaranteedBandwidth", "SupportsMulticasting", "SupportsQualityofService" }) { Name = "PSStatus" }); - td199.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "GuaranteesDelivery", "GuaranteesSequencing", "ConnectionlessService", "Status", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td199; - - var td200 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkConnection", true); - td200.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "ConnectionState", "Persistent", "LocalName", "RemoteName" }) { Name = "PSStatus" }); - td200.DefaultDisplayPropertySet = - new PropertySetData(new [] { "LocalName", "RemoteName", "ConnectionState", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td200; - - var td201 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapter", true); - td201.Members.Add("PSStatus", - new PropertySetData(new [] { "Availability", "Name", "Status", "StatusInfo", "DeviceID" }) { Name = "PSStatus" }); - td201.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ServiceName", "MACAddress", "AdapterType", "DeviceID", "Name", "NetworkAddresses", "Speed" }) { Name = "DefaultDisplayPropertySet" }; - yield return td201; - - var td202 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NetworkAdapterConfiguration", true); - td202.Members.Add("PSStatus", - new PropertySetData(new [] { "DHCPLeaseExpires", "Index", "Description" }) { Name = "PSStatus" }); - td202.Members.Add("DHCP", - new PropertySetData(new [] { "Description", "DHCPEnabled", "DHCPLeaseExpires", "DHCPLeaseObtained", "DHCPServer", "Index" }) { Name = "DHCP" }); - td202.Members.Add("DNS", - new PropertySetData(new [] { "Description", "DNSDomain", "DNSDomainSuffixSearchOrder", "DNSEnabledForWINSResolution", "DNSHostName", "DNSServerSearchOrder", "DomainDNSRegistrationEnabled", "FullDNSRegistrationEnabled", "Index" }) { Name = "DNS" }); - td202.Members.Add("IP", - new PropertySetData(new [] { "Description", "Index", "IPAddress", "IPConnectionMetric", "IPEnabled", "IPFilterSecurityEnabled" }) { Name = "IP" }); - td202.Members.Add("WINS", - new PropertySetData(new [] { "Description", "Index", "WINSEnableLMHostsLookup", "WINSHostLookupFile", "WINSPrimaryServer", "WINSScopeID", "WINSSecondaryServer" }) { Name = "WINS" }); - td202.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DHCPEnabled", "IPAddress", "DefaultIPGateway", "DNSDomain", "ServiceName", "Description", "Index" }) { Name = "DefaultDisplayPropertySet" }; - yield return td202; - - var td203 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTDomain", true); - td203.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DomainName" }) { Name = "PSStatus" }); - td203.Members.Add("GUID", - new PropertySetData(new [] { "DomainName", "DomainGuid" }) { Name = "GUID" }); - td203.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ClientSiteName", "DcSiteName", "Description", "DnsForestName", "DomainControllerAddress", "DomainControllerName", "DomainName", "Roles", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td203; - - var td204 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTLogEvent", true); - td204.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Category", "CategoryString", "EventCode", "EventIdentifier", "TypeEvent", "InsertionStrings", "LogFile", "Message", "RecordNumber", "SourceName", "TimeGenerated", "TimeWritten", "Type", "UserName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td204; - - var td205 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_NTEventlogFile", true); - td205.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "LogfileName", "Name" }) { Name = "PSStatus" }); - td205.DefaultDisplayPropertySet = - new PropertySetData(new [] { "FileSize", "LogfileName", "Name", "NumberOfRecords" }) { Name = "DefaultDisplayPropertySet" }; - yield return td205; - - var td206 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OnBoardDevice", true); - td206.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Description" }) { Name = "PSStatus" }); - td206.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceType", "SerialNumber", "Enabled", "Description" }) { Name = "DefaultDisplayPropertySet" }; - yield return td206; - - var td207 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OperatingSystem", true); - td207.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td207.Members.Add("FREE", - new PropertySetData(new [] { "FreePhysicalMemory", "FreeSpaceInPagingFiles", "FreeVirtualMemory", "Name" }) { Name = "FREE" }); - td207.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SystemDirectory", "Organization", "BuildNumber", "RegisteredUser", "SerialNumber", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td207; - - var td208 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileUsage", true); - td208.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "CurrentUsage" }) { Name = "PSStatus" }); - td208.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "PeakUsage" }) { Name = "DefaultDisplayPropertySet" }; - yield return td208; - - var td209 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PageFileSetting", true); - td209.DefaultDisplayPropertySet = - new PropertySetData(new [] { "MaximumSize", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td209; - - var td210 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_DiskPartition", true); - td210.Members.Add("PSStatus", - new PropertySetData(new [] { "Index", "Status", "StatusInfo", "Name" }) { Name = "PSStatus" }); - td210.DefaultDisplayPropertySet = - new PropertySetData(new [] { "NumberOfBlocks", "BootPartition", "Name", "PrimaryPartition", "Size", "Index" }) { Name = "DefaultDisplayPropertySet" }; - yield return td210; - - var td211 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortResource", true); - td211.Members.Add("PSStatus", - new PropertySetData(new [] { "NetConnectionStatus", "Status", "Name", "StartingAddress", "EndingAddress" }) { Name = "PSStatus" }); - td211.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "Alias" }) { Name = "DefaultDisplayPropertySet" }; - yield return td211; - - var td212 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PortConnector", true); - td212.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "ExternalReferenceDesignator" }) { Name = "PSStatus" }); - td212.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Tag", "ConnectorType", "SerialNumber", "ExternalReferenceDesignator", "PortType" }) { Name = "DefaultDisplayPropertySet" }; - yield return td212; - - var td213 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Printer", true); - td213.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name" }) { Name = "PSStatus" }); - td213.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Location", "Name", "PrinterState", "PrinterStatus", "ShareName", "SystemName" }) { Name = "DefaultDisplayPropertySet" }; - yield return td213; - - var td214 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrinterConfiguration", true); - td214.Members.Add("PSStatus", - new PropertySetData(new [] { "DriverVersion", "Name" }) { Name = "PSStatus" }); - td214.DefaultDisplayPropertySet = - new PropertySetData(new [] { "PrintQuality", "DriverVersion", "Name", "PaperSize", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td214; - - var td215 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PrintJob", true); - td215.Members.Add("PSStatus", - new PropertySetData(new [] { "Document", "JobId", "JobStatus", "Name", "PagesPrinted", "Status", "JobIdCopy", "Name" }) { Name = "PSStatus" }); - td215.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Document", "JobId", "JobStatus", "Owner", "Priority", "Size", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td215; - - var td216 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_ProcessXXX", true); - td216.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "ProcessId" }) { Name = "PSStatus" }); - td216.Members.Add("MEMORY", - new PropertySetData(new [] { "Handle", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "Name", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "VirtualSize", "WorkingSetSize" }) { Name = "MEMORY" }); - td216.Members.Add("IO", - new PropertySetData(new [] { "Name", "ProcessId", "ReadOperationCount", "ReadTransferCount", "WriteOperationCount", "WriteTransferCount" }) { Name = "IO" }); - td216.Members.Add("STATISTICS", - new PropertySetData(new [] { "HandleCount", "Name", "KernelModeTime", "MaximumWorkingSetSize", "MinimumWorkingSetSize", "OtherOperationCount", "OtherTransferCount", "PageFaults", "PageFileUsage", "PeakPageFileUsage", "PeakVirtualSize", "PeakWorkingSetSize", "PrivatePageCount", "ProcessId", "QuotaNonPagedPoolUsage", "QuotaPagedPoolUsage", "QuotaPeakNonPagedPoolUsage", "QuotaPeakPagedPoolUsage", "ReadOperationCount", "ReadTransferCount", "ThreadCount", "UserModeTime", "VirtualSize", "WorkingSetSize", "WriteOperationCount", "WriteTransferCount" }) { Name = "STATISTICS" }); - td216.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ThreadCount", "HandleCount", "Name", "Priority", "ProcessId", "WorkingSetSize" }) { Name = "DefaultDisplayPropertySet" }; - yield return td216; - - var td217 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Product", true); - td217.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Version", "InstallState" }) { Name = "PSStatus" }); - td217.DefaultDisplayPropertySet = - new PropertySetData(new [] { "IdentifyingNumber", "Name", "Vendor", "Version", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td217; - - var td218 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuickFixEngineering", true); - td218.Members.Add("InstalledOn", - new ScriptPropertyData(@"InstalledOn", GetScriptBlock(@"if ([environment]::osversion.version.build -ge 7000) - { - # WMI team fixed the formatting issue related to InstalledOn - # property in Windows7 (to return string)..so returning the WMI's - # version directly - [DateTime]::Parse($this.psBase.CimInstanceProperties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) - } - else - { - $orig = $this.psBase.CimInstanceProperties[""InstalledOn""].Value - $date = [datetime]::FromFileTimeUTC($(""0x"" + $orig)) - if ($date -lt ""1/1/1980"") - { - if ($orig -match ""([0-9]{4})([01][0-9])([012][0-9])"") - { - new-object datetime @([int]$matches[1], [int]$matches[2], [int]$matches[3]) - } - } - else - { - $date - } - }"), null)); - td218.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Description", "FixComments", "HotFixID", "InstallDate", "InstalledBy", "InstalledOn", "Name", "ServicePackInEffect", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td218; - - var td219 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_QuotaSetting", true); - td219.Members.Add("PSStatus", - new PropertySetData(new [] { "State", "VolumePath", "Caption" }) { Name = "PSStatus" }); - td219.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "DefaultLimit", "SettingID", "State", "VolumePath", "DefaultWarningLimit" }) { Name = "DefaultDisplayPropertySet" }; - yield return td219; - - var td220 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_OSRecoveryConfiguration", true); - td220.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DebugFilePath", "Name", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td220; - - var td221 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Registry", true); - td221.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "CurrentSize", "MaximumSize", "ProposedSize" }) { Name = "PSStatus" }); - td221.DefaultDisplayPropertySet = - new PropertySetData(new [] { "CurrentSize", "MaximumSize", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td221; - - var td222 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SCSIController", true); - td222.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "StatusInfo" }) { Name = "PSStatus" }); - td222.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DriverName", "Manufacturer", "Name", "ProtocolSupported", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td222; - - var td223 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_PerfRawData_PerfNet_Server", true); - td223.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "LogonPerSec", "LogonTotal", "Name", "ServerSessions", "WorkItemShortages" }) { Name = "DefaultDisplayPropertySet" }; - yield return td223; - - var td224 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Service", true); - td224.Members.Add("PSStatus", - new PropertySetData(new [] { "Name", "Status", "ExitCode" }) { Name = "PSStatus" }); - td224.Members.Add("PSConfiguration", - new PropertySetData(new [] { "DesktopInteract", "ErrorControl", "Name", "PathName", "ServiceType", "StartMode" }) { Name = "PSConfiguration" }); - td224.DefaultDisplayPropertySet = - new PropertySetData(new [] { "ExitCode", "Name", "ProcessId", "StartMode", "State", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td224; - - var td225 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Share", true); - td225.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Type", "Name" }) { Name = "PSStatus" }); - td225.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Name", "Path", "Description" }) { Name = "DefaultDisplayPropertySet" }; - yield return td225; - - var td226 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareElement", true); - td226.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SoftwareElementState", "Name" }) { Name = "PSStatus" }); - td226.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Name", "Path", "SerialNumber", "SoftwareElementID", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td226; - - var td227 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SoftwareFeature", true); - td227.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "InstallState", "LastUse" }) { Name = "PSStatus" }); - td227.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "IdentifyingNumber", "ProductName", "Vendor", "Version" }) { Name = "DefaultDisplayPropertySet" }; - yield return td227; - - var td228 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/WIN32_SoundDevice", true); - td228.Members.Add("PSStatus", - new PropertySetData(new [] { "ConfigManagerUserConfig", "Name", "Status", "StatusInfo" }) { Name = "PSStatus" }); - td228.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Name", "Status", "StatusInfo" }) { Name = "DefaultDisplayPropertySet" }; - yield return td228; - - var td229 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_StartupCommand", true); - td229.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Command", "User", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td229; - - var td230 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemAccount", true); - td230.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SIDType", "Name", "Domain" }) { Name = "PSStatus" }); - td230.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Caption", "Domain", "Name", "SID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td230; - - var td231 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemDriver", true); - td231.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Name", "State", "ExitCode", "Started", "ServiceSpecificExitCode" }) { Name = "PSStatus" }); - td231.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DisplayName", "Name", "State", "Status", "Started" }) { Name = "DefaultDisplayPropertySet" }; - yield return td231; - - var td232 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemEnclosure", true); - td232.Members.Add("PSStatus", - new PropertySetData(new [] { "Tag", "Status", "Name", "SecurityStatus" }) { Name = "PSStatus" }); - td232.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Manufacturer", "Model", "LockPresent", "SerialNumber", "SMBIOSAssetTag", "SecurityStatus" }) { Name = "DefaultDisplayPropertySet" }; - yield return td232; - - var td233 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_SystemSlot", true); - td233.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "SlotDesignation" }) { Name = "PSStatus" }); - td233.DefaultDisplayPropertySet = - new PropertySetData(new [] { "SlotDesignation", "Tag", "SupportsHotPlug", "Status", "Shared", "PMESignal", "MaxDataWidth" }) { Name = "DefaultDisplayPropertySet" }; - yield return td233; - - var td234 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TapeDrive", true); - td234.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Availability", "DeviceID", "NeedsCleaning", "StatusInfo" }) { Name = "PSStatus" }); - td234.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "Id", "Manufacturer", "Name", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td234; - - var td235 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TemperatureProbe", true); - td235.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "CurrentReading", "DeviceID", "Name", "StatusInfo" }) { Name = "PSStatus" }); - td235.DefaultDisplayPropertySet = - new PropertySetData(new [] { "CurrentReading", "Name", "Description", "MinReadable", "MaxReadable", "Status" }) { Name = "DefaultDisplayPropertySet" }; - yield return td235; - - var td236 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_TimeZone", true); - td236.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Bias", "SettingID", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td236; - - var td237 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UninterruptiblePowerSupply", true); - td237.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DeviceID", "EstimatedChargeRemaining", "EstimatedRunTime", "Name", "StatusInfo", "TimeOnBackup" }) { Name = "PSStatus" }); - td237.DefaultDisplayPropertySet = - new PropertySetData(new [] { "DeviceID", "EstimatedRunTime", "Name", "TimeOnBackup", "UPSPort", "Caption" }) { Name = "DefaultDisplayPropertySet" }; - yield return td237; - - var td238 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UserAccount", true); - td238.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "Caption", "PasswordExpires" }) { Name = "PSStatus" }); - td238.DefaultDisplayPropertySet = - new PropertySetData(new [] { "AccountType", "Caption", "Domain", "SID", "FullName", "Name" }) { Name = "DefaultDisplayPropertySet" }; - yield return td238; - - var td239 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VoltageProbe", true); - td239.Members.Add("PSStatus", - new PropertySetData(new [] { "Status", "DeviceID", "Name", "NominalReading", "StatusInfo" }) { Name = "PSStatus" }); - td239.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Status", "Description", "CurrentReading", "MaxReadable", "MinReadable" }) { Name = "DefaultDisplayPropertySet" }; - yield return td239; - - var td240 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_VolumeQuotaSetting", true); - td240.DefaultDisplayPropertySet = - new PropertySetData(new [] { "Element", "Setting" }) { Name = "DefaultDisplayPropertySet" }; - yield return td240; - - var td241 = new TypeData(@"Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_WMISetting", true); - td241.DefaultDisplayPropertySet = - new PropertySetData(new [] { "BuildVersion", "Caption", "DatabaseDirectory", "EnableEvents", "LoggingLevel", "SettingID" }) { Name = "DefaultDisplayPropertySet" }; - yield return td241; - - var td242 = new TypeData(@"Microsoft.Management.Infrastructure.CimClass", true); - td242.Members.Add("CimClassName", - new ScriptPropertyData(@"CimClassName", GetScriptBlock(@"[OutputType([string])] - param() - $this.PSBase.CimSystemProperties.ClassName"), null)); - yield return td242; - - var td243 = new TypeData(@"Microsoft.Management.Infrastructure.CimCmdlets.CimIndicationEventInstanceEventArgs", true); - td243.SerializationDepth = 1; - yield return td243; - - var td244 = new TypeData(@"System.Management.Automation.Breakpoint", true); - td244.SerializationDepth = 1; - yield return td244; - - var td245 = new TypeData(@"Deserialized.System.Management.Automation.Breakpoint", true); - td245.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td245; - - var td246 = new TypeData(@"System.Management.Automation.BreakpointUpdatedEventArgs", true); - td246.SerializationDepth = 2; - yield return td246; - - var td247 = new TypeData(@"Deserialized.System.Management.Automation.BreakpointUpdatedEventArgs", true); - td247.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td247; - - var td248 = new TypeData(@"System.Management.Automation.DebuggerCommand", true); - td248.SerializationDepth = 1; - yield return td248; - - var td249 = new TypeData(@"Deserialized.System.Management.Automation.DebuggerCommand", true); - td249.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td249; - - var td250 = new TypeData(@"System.Management.Automation.DebuggerCommandResults", true); - td250.SerializationDepth = 1; - yield return td250; - - var td251 = new TypeData(@"Deserialized.System.Management.Automation.DebuggerCommandResults", true); - td251.TargetTypeForDeserialization = typeof(Microsoft.PowerShell.DeserializingTypeConverter); - yield return td251; - - var td252 = new TypeData(@"System.Version#IncludeLabel", true); - td252.Members.Add("ToString", - new ScriptMethodData(@"ToString", GetScriptBlock(@" - $suffix = """" - if (![String]::IsNullOrEmpty($this.PSSemVerPreReleaseLabel)) - { - $suffix = ""-""+$this.PSSemVerPreReleaseLabel - } - if (![String]::IsNullOrEmpty($this.PSSemVerBuildLabel)) - { - $suffix += ""+""+$this.PSSemVerBuildLabel - } - ""$($this.Major).$($this.Minor).$($this.Build)""+$suffix - "))); - yield return td252; - } - } -} diff --git a/src/System.Management.Automation/engine/UserFeedbackParameters.cs b/src/System.Management.Automation/engine/UserFeedbackParameters.cs index 657d0b4fede..2fd26e5f32c 100644 --- a/src/System.Management.Automation/engine/UserFeedbackParameters.cs +++ b/src/System.Management.Automation/engine/UserFeedbackParameters.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; using System.Management.Automation.Language; @@ -19,8 +18,9 @@ internal PagingParameters(MshCommandRuntime commandRuntime) { if (commandRuntime == null) { - throw PSTraceSource.NewArgumentNullException("commandRuntime"); + throw PSTraceSource.NewArgumentNullException(nameof(commandRuntime)); } + commandRuntime.PagingParameters = this; } @@ -57,14 +57,14 @@ internal PagingParameters(MshCommandRuntime commandRuntime) /// of objects that the cmdlet would return without paging /// (this can be more than the size of the page specified in the cmdlet parameter). /// - /// a total count of objects that the cmdlet would return without paging + /// A total count of objects that the cmdlet would return without paging. /// /// accuracy of the parameter. /// 1.0 means 100% accurate; /// 0.0 means that total count is unknown; /// anything in-between means that total count is estimated /// - /// An object that represents a total count of objects that the cmdlet would return without paging + /// An object that represents a total count of objects that the cmdlet would return without paging. public PSObject NewTotalCount(UInt64 totalCount, double accuracy) { PSObject result = new PSObject(totalCount); @@ -106,22 +106,21 @@ public sealed class ShouldProcessParameters #region ctor /// - /// Constructs an instance with the specified command instance + /// Constructs an instance with the specified command instance. /// - /// /// /// The instance of the command that the parameters should set the /// user feedback properties on when the parameters get bound. /// - /// internal ShouldProcessParameters(MshCommandRuntime commandRuntime) { if (commandRuntime == null) { - throw PSTraceSource.NewArgumentNullException("commandRuntime"); + throw PSTraceSource.NewArgumentNullException(nameof(commandRuntime)); } + _commandRuntime = commandRuntime; - } // ctor + } #endregion ctor #region parameters @@ -137,6 +136,7 @@ public SwitchParameter WhatIf { return _commandRuntime.WhatIf; } + set { _commandRuntime.WhatIf = value; @@ -154,6 +154,7 @@ public SwitchParameter Confirm { return _commandRuntime.Confirm; } + set { _commandRuntime.Confirm = value; @@ -161,30 +162,28 @@ public SwitchParameter Confirm } #endregion parameters - private MshCommandRuntime _commandRuntime; + private readonly MshCommandRuntime _commandRuntime; } /// /// The declaration of parameters for the Transactions mechanisms. -UseTransaction, and -BypassTransaction. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "These are only exposed by way of the PowerShell core cmdlets that surface them.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "These are only exposed by way of the PowerShell cmdlets that surface them.")] public sealed class TransactionParameters { #region ctor /// - /// Constructs an instance with the specified command instance + /// Constructs an instance with the specified command instance. /// - /// /// /// The instance of the command that the parameters should set the /// user feedback properties on when the parameters get bound. /// - /// internal TransactionParameters(MshCommandRuntime commandRuntime) { _commandRuntime = commandRuntime; - } // ctor + } #endregion ctor #region parameters @@ -192,7 +191,6 @@ internal TransactionParameters(MshCommandRuntime commandRuntime) /// /// Gets or sets the value of the -UseTransaction parameter for all cmdlets. /// - /// [Parameter] [Alias("usetx")] public SwitchParameter UseTransaction @@ -201,6 +199,7 @@ public SwitchParameter UseTransaction { return _commandRuntime.UseTransaction; } + set { _commandRuntime.UseTransaction = value; @@ -209,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 3659d0c4cdd..0de9fe0d5cc 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -1,39 +1,262 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Security; -using System.Runtime.InteropServices; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; 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.Remoting; using System.Management.Automation.Security; +using System.Numerics; using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; +#if !UNIX +using System.Security.Principal; +#endif +using System.Text; +using System.Threading; using Microsoft.PowerShell.Commands; using Microsoft.Win32; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Text; using TypeTable = System.Management.Automation.Runspaces.TypeTable; -using System.Diagnostics; -using Microsoft.Win32.SafeHandles; - namespace System.Management.Automation { /// - /// helper fns + /// Helper fns. /// internal static class Utils { + /// + /// Converts a given double value to BigInteger via Math.Round(). + /// + /// The value to convert. + /// Returns a BigInteger value equivalent to the input value rounded to nearest integer. + internal static BigInteger AsBigInt(this double d) => new BigInteger(Math.Round(d)); + + internal static bool TryCast(BigInteger value, out byte b) + { + if (value < byte.MinValue || value > byte.MaxValue) + { + b = 0; + return false; + } + + b = (byte)value; + return true; + } + + internal static bool TryCast(BigInteger value, out sbyte sb) + { + if (value < sbyte.MinValue || value > sbyte.MaxValue) + { + sb = 0; + return false; + } + + sb = (sbyte)value; + return true; + } + + internal static bool TryCast(BigInteger value, out short s) + { + if (value < short.MinValue || value > short.MaxValue) + { + s = 0; + return false; + } + + s = (short)value; + return true; + } + + internal static bool TryCast(BigInteger value, out ushort us) + { + if (value < ushort.MinValue || value > ushort.MaxValue) + { + us = 0; + return false; + } + + us = (ushort)value; + return true; + } + + internal static bool TryCast(BigInteger value, out int i) + { + if (value < int.MinValue || value > int.MaxValue) + { + i = 0; + return false; + } + + i = (int)value; + return true; + } + + internal static bool TryCast(BigInteger value, out uint u) + { + if (value < uint.MinValue || value > uint.MaxValue) + { + u = 0; + return false; + } + + u = (uint)value; + return true; + } + + internal static bool TryCast(BigInteger value, out long l) + { + if (value < long.MinValue || value > long.MaxValue) + { + l = 0; + return false; + } + + l = (long)value; + return true; + } + + internal static bool TryCast(BigInteger value, out ulong ul) + { + if (value < ulong.MinValue || value > ulong.MaxValue) + { + ul = 0; + return false; + } + + ul = (ulong)value; + return true; + } + + internal static bool TryCast(BigInteger value, out decimal dm) + { + if (value < (BigInteger)decimal.MinValue || (BigInteger)decimal.MaxValue < value) + { + dm = 0; + return false; + } + + dm = (decimal)value; + return true; + } + + internal static bool TryCast(BigInteger value, out double db) + { + if (value < (BigInteger)double.MinValue || (BigInteger)double.MaxValue < value) + { + db = 0; + return false; + } + + db = (double)value; + return true; + } + + /// + /// Parses a given string or ReadOnlySpan<char> to calculate its value as a binary number. + /// Assumes input has already been sanitized and only contains zeroes (0) or ones (1). + /// + /// Span or string of binary digits. Assumes all digits are either 1 or 0. + /// + /// Whether to treat the number as unsigned. When false, respects established conventions + /// with sign bits for certain input string lengths. + /// + /// Returns the value of the binary string as a BigInteger. + internal static BigInteger ParseBinary(ReadOnlySpan digits, bool unsigned) + { + if (!unsigned) + { + if (digits[0] == '0') + { + unsigned = true; + } + else + { + switch (digits.Length) + { + // Only accept sign bits at these lengths: + case 8: // byte + case 16: // short + case 32: // int + case 64: // long + case 96: // decimal + case int n when n >= 128: // BigInteger + break; + default: + // If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length + unsigned = true; + break; + } + } + } + + // Only use heap allocation for very large numbers + const int MaxStackAllocation = 512; + + // Calculate number of 8-bit bytes needed to hold the input, rounded up to next whole number. + int outputByteCount = (digits.Length + 7) / 8; + Span outputBytes = outputByteCount <= MaxStackAllocation ? stackalloc byte[outputByteCount] : new byte[outputByteCount]; + int outputByteIndex = outputBytes.Length - 1; + + // We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases + // where we only have less than 8 bits to work with from the beginning. + // + // Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes). + int byteWalker; + for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8) + { + // Use bit shifts and binary-or to sum the values in each byte. These calculations will + // create values higher than a single byte, but the higher bits will be stripped out when cast + // to byte. + // + // The low bits are added in separately to allow us to strip the higher 'noise' bits before we + // sum the values using binary-or. + // + // Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 ) + // + // N.B.: This code has been tested against a straight for loop iterating through the byte, and in no + // circumstance was it faster or more effective than this unrolled version. + outputBytes[outputByteIndex--] = + (byte)( + ((digits[byteWalker - 7] << 7) + | (digits[byteWalker - 6] << 6) + | (digits[byteWalker - 5] << 5) + | (digits[byteWalker - 4] << 4) + ) + | ( + ((digits[byteWalker - 3] << 3) + | (digits[byteWalker - 2] << 2) + | (digits[byteWalker - 1] << 1) + | (digits[byteWalker]) + ) & 0b1111 + ) + ); + } + + // With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1 + if (byteWalker >= 0) + { + int currentByteValue = 0; + for (int i = 0; i <= byteWalker; i++) + { + currentByteValue = (currentByteValue << 1) | (digits[i] - '0'); + } + + outputBytes[outputByteIndex] = (byte)currentByteValue; + } + + return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true); + } + // From System.Web.Util.HashCodeCombiner internal static int CombineHashCodes(int h1, int h2) { @@ -71,24 +294,16 @@ internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int } /// - /// The existence of the following registry confirms that the host machine is a WinPE - /// HKLM\System\CurrentControlSet\Control\MiniNT - /// - internal static string WinPEIdentificationRegKey = @"System\CurrentControlSet\Control\MiniNT"; - - /// - /// Allowed PowerShell Editions + /// Allowed PowerShell Editions. /// - internal static string[] AllowedEditionValues = { "Desktop", "Core" }; + internal static readonly string[] AllowedEditionValues = { "Desktop", "Core" }; /// - /// helper fn to check byte[] arg for null. + /// 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) @@ -110,14 +325,12 @@ internal static void CheckKeyArg(byte[] arg, string argName) } /// - /// helper fn to check arg for empty or null. + /// 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) @@ -131,14 +344,12 @@ internal static void CheckArgForNullOrEmpty(string arg, string argName) } /// - /// helper fn to check arg for null. + /// 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) @@ -148,13 +359,11 @@ internal static void CheckArgForNull(object arg, string argName) } /// - /// helper fn to check arg for null. + /// 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) @@ -163,7 +372,6 @@ internal static void CheckSecureStringArg(SecureString arg, string argName) } } - [ArchitectureSensitive] internal static string GetStringFromSecureString(SecureString ss) { IntPtr p = IntPtr.Zero; @@ -206,7 +414,7 @@ internal static TypeTable GetTypeTableFromExecutionContextTLS() private static string s_pshome = null; /// - /// Get the application base path of the shell from registry + /// Get the application base path of the shell from registry. /// internal static string GetApplicationBaseFromRegistry(string shellId) { @@ -232,32 +440,61 @@ internal static string GetApplicationBaseFromRegistry(string shellId) return null; } + + private static string s_windowsPowerShellVersion = null; + + /// + /// Get the Windows PowerShell version from registry. + /// + /// + /// String of Windows PowerShell version from registry. + /// + internal static string GetWindowsPowerShellVersionFromRegistry() + { + if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellVersionString)) + { + return InternalTestHooks.TestWindowsPowerShellVersionString; + } + + if (s_windowsPowerShellVersion != null) + { + return s_windowsPowerShellVersion; + } + + string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" + + PSVersionInfo.RegistryVersionKey + "\\" + RegistryStrings.MonadEngineKey; + + using (RegistryKey engineKey = Registry.LocalMachine.OpenSubKey(engineKeyPath)) + { + if (engineKey != null) + { + s_windowsPowerShellVersion = engineKey.GetValue(RegistryStrings.MonadEngine_MonadVersion) as string; + return s_windowsPowerShellVersion; + } + } + + return string.Empty; + } #endif internal static string DefaultPowerShellAppBase => GetApplicationBase(DefaultPowerShellShellID); + 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; - /// - /// Specifies the per-user configuration settings directory in a platform agnostic manner. - /// - /// The current user's configuration settings directory - internal static string GetUserConfigurationDirectory() - { -#if UNIX - return Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); -#else - string basePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); - return IO.Path.Combine(basePath, Utils.ProductNameForDirectory); -#endif - } - private static string[] GetProductFolderDirectories() { if (s_productFolderDirectories == null) @@ -271,7 +508,6 @@ private static string[] GetProductFolderDirectories() baseDirectories.Add(appBase); } #if !UNIX - // Win8: 454976 // Now add the two variations of System32 baseDirectories.Add(Environment.GetFolderPath(Environment.SpecialFolder.System)); string systemX86 = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86); @@ -280,20 +516,6 @@ private static string[] GetProductFolderDirectories() baseDirectories.Add(systemX86); } #endif - // And built-in modules - string progFileDir; - // TODO: #1184 will resolve this work-around - // Side-by-side versions of PowerShell use modules from their application base, not - // the system installation path. - progFileDir = Path.Combine(appBase, "Modules"); - - if (!string.IsNullOrEmpty(progFileDir)) - { - baseDirectories.Add(Path.Combine(progFileDir, "PackageManagement")); - baseDirectories.Add(Path.Combine(progFileDir, "PowerShellGet")); - baseDirectories.Add(Path.Combine(progFileDir, "Pester")); - baseDirectories.Add(Path.Combine(progFileDir, "PSReadLine")); - } Interlocked.CompareExchange(ref s_productFolderDirectories, baseDirectories.ToArray(), null); } @@ -326,7 +548,7 @@ internal static bool IsUnderProductFolder(string filePath) } /// - /// Checks if the current process is using WOW + /// Checks if the current process is using WOW. /// internal static bool IsRunningFromSysWOW64() { @@ -334,7 +556,7 @@ internal static bool IsRunningFromSysWOW64() } /// - /// Checks if host machine is WinPE + /// Checks if host machine is WinPE. /// internal static bool IsWinPEHost() { @@ -345,7 +567,7 @@ internal static bool IsWinPEHost() { // The existence of the following registry confirms that the host machine is a WinPE // HKLM\System\CurrentControlSet\Control\MiniNT - winPEKey = Registry.LocalMachine.OpenSubKey(WinPEIdentificationRegKey); + winPEKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\MiniNT"); return winPEKey != null; } @@ -354,23 +576,19 @@ internal static bool IsWinPEHost() catch (ObjectDisposedException) { } finally { - if (winPEKey != null) - { - winPEKey.Dispose(); - } + winPEKey?.Dispose(); } #endif return false; } - #region Versioning related methods /// - /// returns current major version of monad ( that is running ) in a string + /// Returns current major version of monad ( that is running ) in a string /// format. /// - /// string + /// String. /// /// Cannot return a Version object as minor number is a requirement for /// version object. @@ -387,7 +605,7 @@ internal static string GetCurrentMajorVersion() /// Version.TryParse will be used to convert the string to a Version /// object. /// - /// string representing version + /// String representing version. /// A Version Object. internal static Version StringToVersion(string versionString) { @@ -420,60 +638,45 @@ internal static Version StringToVersion(string versionString) { return result; } + return null; } /// - /// Checks whether current monad session supports version specified - /// by ver. + /// Checks whether current PowerShell session supports edition specified + /// by checkEdition. /// - /// Version to check - /// true if supported, false otherwise - internal static bool IsPSVersionSupported(string ver) + /// Edition to check. + /// True if supported, false otherwise. + internal static bool IsPSEditionSupported(string checkEdition) { - // Convert version to supported format ie., x.x - Version inputVersion = StringToVersion(ver); - return IsPSVersionSupported(inputVersion); + return PSVersionInfo.PSEditionValue.Equals(checkEdition, StringComparison.OrdinalIgnoreCase); } /// - /// Checks whether current monad session supports version specified - /// by checkVersion. + /// Check whether the current PowerShell session supports any of the specified editions. /// - /// Version to check - /// true if supported, false otherwise - internal static bool IsPSVersionSupported(Version checkVersion) + /// The PowerShell editions to check compatibility with. + /// True if the edition is supported by this runtime, false otherwise. + internal static bool IsPSEditionSupported(IEnumerable editions) { - if (checkVersion == null) - { - return false; - } - - foreach (Version compatibleVersion in PSVersionInfo.PSCompatibleVersions) + string currentPSEdition = PSVersionInfo.PSEditionValue; + foreach (string edition in editions) { - if (checkVersion.Major == compatibleVersion.Major && checkVersion.Minor <= compatibleVersion.Minor) + if (currentPSEdition.Equals(edition, StringComparison.OrdinalIgnoreCase)) + { return true; + } } return false; } /// - /// Checks whether current monad session supports edition specified - /// by checkEdition. - /// - /// Edition to check - /// true if supported, false otherwise - internal static bool IsPSEditionSupported(string checkEdition) - { - return PSVersionInfo.PSEdition.Equals(checkEdition, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Checks whether the specified edition values is allowed. + /// Checks whether the specified edition value is allowed. /// - /// Edition value to check - /// true if allowed, false otherwise + /// Edition value to check. + /// True if allowed, false otherwise. internal static bool IsValidPSEditionValue(string editionValue) { return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase); @@ -489,25 +692,30 @@ internal static bool IsValidPSEditionValue(string editionValue) /// /// This is used to construct the profile path. /// - internal static string ProductNameForDirectory = Platform.IsInbox ? "WindowsPowerShell" : "PowerShell"; + internal const string ProductNameForDirectory = "PowerShell"; + + /// + /// WSL introduces a new filesystem path to access the Linux filesystem from Windows, like '\\wsl$\ubuntu'. + /// + internal const string WslRootPath = @"\\wsl$"; /// /// The subdirectory of module paths - /// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules + /// e.g. ~\Documents\WindowsPowerShell\Modules and %ProgramFiles%\WindowsPowerShell\Modules. /// - internal static string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules"); + internal static readonly string ModuleDirectory = Path.Combine(ProductNameForDirectory, "Modules"); - internal readonly static ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.SystemWide }; - internal readonly static ConfigScope[] CurrentUserOnlyConfig = new[] { ConfigScope.CurrentUser }; - internal readonly static ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.SystemWide, ConfigScope.CurrentUser }; - internal readonly static ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.SystemWide }; + internal static readonly ConfigScope[] SystemWideOnlyConfig = new[] { ConfigScope.AllUsers }; + internal static readonly ConfigScope[] CurrentUserOnlyConfig = new[] { ConfigScope.CurrentUser }; + internal static readonly ConfigScope[] SystemWideThenCurrentUserConfig = new[] { ConfigScope.AllUsers, ConfigScope.CurrentUser }; + internal static readonly ConfigScope[] CurrentUserThenSystemWideConfig = new[] { ConfigScope.CurrentUser, ConfigScope.AllUsers }; internal static T GetPolicySetting(ConfigScope[] preferenceOrder) where T : PolicyBase, new() { T policy = null; #if !UNIX // On Windows, group policy settings from registry take precedence. - // If the requested policy is not defined in registry, we query the configuration file. + // If the requested policy is not defined in registry, we query the configuration file. policy = GetPolicySettingFromGPO(preferenceOrder); if (policy != null) { return policy; } #endif @@ -515,7 +723,7 @@ internal static bool IsValidPSEditionValue(string editionValue) return policy; } - private readonly static ConcurrentDictionary s_cachedPoliciesFromConfigFile = + private static readonly ConcurrentDictionary s_cachedPoliciesFromConfigFile = new ConcurrentDictionary(); /// @@ -544,16 +752,33 @@ internal static bool IsValidPSEditionValue(string editionValue) PolicyBase result = null; switch (typeof(T).Name) { - case nameof(ScriptExecution): result = policies.ScriptExecution; break; - case nameof(ScriptBlockLogging): result = policies.ScriptBlockLogging; break; - case nameof(ModuleLogging): result = policies.ModuleLogging; break; - case nameof(ProtectedEventLogging): result = policies.ProtectedEventLogging; break; - case nameof(Transcription): result = policies.Transcription; break; - case nameof(UpdatableHelp): result = policies.UpdatableHelp; break; - case nameof(ConsoleSessionConfiguration): result = policies.ConsoleSessionConfiguration; break; - default: Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when new PowerShell policy types are added."); break; + case nameof(ScriptExecution): + result = policies.ScriptExecution; + break; + case nameof(ScriptBlockLogging): + result = policies.ScriptBlockLogging; + break; + case nameof(ModuleLogging): + result = policies.ModuleLogging; + break; + case nameof(ProtectedEventLogging): + result = policies.ProtectedEventLogging; + break; + case nameof(Transcription): + result = policies.Transcription; + break; + case nameof(UpdatableHelp): + result = policies.UpdatableHelp; + break; + case nameof(ConsoleSessionConfiguration): + result = policies.ConsoleSessionConfiguration; + break; + default: + Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when new PowerShell policy types are added."); + break; } - if (result != null) { return (T) result; } + + if (result != null) { return (T)result; } } } @@ -571,37 +796,44 @@ internal static bool IsValidPSEditionValue(string editionValue) {nameof(UpdatableHelp), @"Software\Policies\Microsoft\PowerShellCore\UpdatableHelp"}, {nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"} }; - private readonly static ConcurrentDictionary, PolicyBase> s_cachedPoliciesFromRegistry = - new ConcurrentDictionary, PolicyBase>(); - /// - /// The implementation of fetching a specific kind of policy setting from the given configuration scope. - /// - private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new() + private static readonly Dictionary WindowsPowershellGroupPolicyKeys = new Dictionary { - Type tType = typeof(T); - // SystemWide scope means 'LocalMachine' root key when query from registry - RegistryKey rootKey = (scope == ConfigScope.SystemWide) ? Registry.LocalMachine : Registry.CurrentUser; + { nameof(ScriptExecution), @"Software\Policies\Microsoft\Windows\PowerShell" }, + { nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" }, + { nameof(ModuleLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging" }, + { nameof(Transcription), @"Software\Policies\Microsoft\Windows\PowerShell\Transcription" }, + { nameof(UpdatableHelp), @"Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp" }, + }; - GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath); - Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name)); + private const string PolicySettingFallbackKey = "UseWindowsPowerShellPolicySetting"; - using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath)) - { - // If the corresponding GPO key doesn't exist, return null - if (gpoKey == null) { return null; } + private static readonly ConcurrentDictionary> s_cachedPoliciesFromRegistry = + new ConcurrentDictionary>(); - // The corresponding GPO key exists, then create an instance of T - // and populate its properties with the settings - object tInstance = Activator.CreateInstance(tType, nonPublic: true); - var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); - bool isAnyPropertySet = false; + private static readonly Func> s_subCacheCreationDelegate = + key => new ConcurrentDictionary(StringComparer.Ordinal); + + /// + /// Read policy settings from a registry key into a policy object. + /// + /// Policy object that will be filled with values from registry. + /// Type of policy object used. + /// Registry key that has policy settings. + /// True if any property was successfully set on the policy object. + private static bool TrySetPolicySettingsFromRegistryKey(object instance, Type instanceType, RegistryKey gpoKey) + { + var properties = instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public); + bool isAnyPropertySet = false; - string[] valueNames = gpoKey.GetValueNames(); - string[] subKeyNames = gpoKey.GetSubKeyNames(); - var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null; - var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null; + string[] valueNames = gpoKey.GetValueNames(); + string[] subKeyNames = gpoKey.GetSubKeyNames(); + var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null; + var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null; + // If there are any values or subkeys in the registry key - read them into the policy instance object + if ((valueNameSet != null) || (subKeyNameSet != null)) + { foreach (var property in properties) { string settingName = property.Name; @@ -616,7 +848,10 @@ internal static bool IsValidPSEditionValue(string editionValue) { using (RegistryKey subKey = gpoKey.OpenSubKey(settingName)) { - if (subKey != null) { rawRegistryValue = subKey.GetValueNames(); } + if (subKey != null) + { + rawRegistryValue = subKey.GetValueNames(); + } } } @@ -632,15 +867,23 @@ internal static bool IsValidPSEditionValue(string editionValue) case var _ when propertyType == typeof(bool?): if (rawRegistryValue is int rawIntValue) { - if (rawIntValue == 1) { propertyValue = true; } - else if (rawIntValue == 0) { propertyValue = false; } + if (rawIntValue == 1) + { + propertyValue = true; + } + else if (rawIntValue == 0) + { + propertyValue = false; + } } + break; case var _ when propertyType == typeof(string): if (rawRegistryValue is string rawStringValue) { propertyValue = rawStringValue; } + break; case var _ when propertyType == typeof(string[]): if (rawRegistryValue is string[] rawStringArrayValue) @@ -651,23 +894,67 @@ internal static bool IsValidPSEditionValue(string editionValue) { propertyValue = new string[] { stringValue }; } + break; default: - Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when properties of new types are added to PowerShell policy types."); - break; + throw System.Management.Automation.Interpreter.Assert.Unreachable; } // Set the property if the value is not null if (propertyValue != null) { - property.SetValue(tInstance, propertyValue); + property.SetValue(instance, propertyValue); isAnyPropertySet = true; } } } + } + + return isAnyPropertySet; + } + + /// + /// The implementation of fetching a specific kind of policy setting from the given configuration scope. + /// + private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new() + { + Type tType = typeof(T); + // SystemWide scope means 'LocalMachine' root key when query from registry + RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser; + + GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath); + Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name)); + + using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath)) + { + // If the corresponding GPO key doesn't exist, return null + if (gpoKey == null) { return null; } + + // The corresponding GPO key exists, then create an instance of T + // and populate its properties with the settings + object tInstance = Activator.CreateInstance(tType, nonPublic: true); + bool isAnyPropertySet = false; + + // if PolicySettingFallbackKey is Not set - use PowerShell Core policy reg key + if ((int)gpoKey.GetValue(PolicySettingFallbackKey, 0) == 0) + { + isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, gpoKey); + } + else + { + // when PolicySettingFallbackKey flag is set (REG_DWORD "1") use Windows PS policy reg key + WindowsPowershellGroupPolicyKeys.TryGetValue(tType.Name, out string winPowershellGpoKeyPath); + Diagnostics.Assert(winPowershellGpoKeyPath != null, StringUtil.Format("The Windows PS GPO registry key path should be pre-defined for {0}", tType.Name)); + using (RegistryKey winPowershellGpoKey = rootKey.OpenSubKey(winPowershellGpoKeyPath)) + { + // If the corresponding Windows PS GPO key doesn't exist, return null + if (winPowershellGpoKey == null) { return null; } + isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, winPowershellGpoKey); + } + } // If no property is set, then we consider this policy as undefined - return isAnyPropertySet ? (T) tInstance : null; + return isAnyPropertySet ? (T)tInstance : null; } } @@ -677,6 +964,8 @@ internal static bool IsValidPSEditionValue(string editionValue) private static T GetPolicySettingFromGPO(ConfigScope[] preferenceOrder) where T : PolicyBase, new() { PolicyBase policy = null; + string policyName = typeof(T).Name; + foreach (ConfigScope scope in preferenceOrder) { if (InternalTestHooks.BypassGroupPolicyCaching) @@ -685,17 +974,14 @@ internal static bool IsValidPSEditionValue(string editionValue) } else { - var key = Tuple.Create(scope, typeof(T).Name); - if (!s_cachedPoliciesFromRegistry.TryGetValue(key, out policy)) + var subordinateCache = s_cachedPoliciesFromRegistry.GetOrAdd(scope, s_subCacheCreationDelegate); + if (!subordinateCache.TryGetValue(policyName, out policy)) { - lock (s_cachedPoliciesFromRegistry) - { - policy = s_cachedPoliciesFromRegistry.GetOrAdd(key, tuple => GetPolicySettingFromGPOImpl(tuple.Item1)); - } + policy = subordinateCache.GetOrAdd(policyName, key => GetPolicySettingFromGPOImpl(scope)); } } - if (policy != null) { return (T) policy; } + if (policy != null) { return (T)policy; } } return null; @@ -745,10 +1031,7 @@ internal static void EnsureModuleLoaded(string module, ExecutionContext context) finally { context.AutoLoadingModuleInProgress.Remove(module); - if (null != ps) - { - ps.Dispose(); - } + ps?.Dispose(); } } } @@ -795,11 +1078,7 @@ internal static List GetModules(string module, ExecutionContext co } else { - // append to result - foreach (PSModuleInfo temp in gmoOutPut) - { - result.Add(temp); - } + result.AddRange(gmoOutPut); } } } @@ -809,10 +1088,7 @@ internal static List GetModules(string module, ExecutionContext co } finally { - if (null != ps) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; @@ -870,191 +1146,79 @@ internal static List GetModules(ModuleSpecification fullyQualified } finally { - if (null != ps) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; } - internal static bool IsAdministrator() - { - // Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to - // advanced privilege models, the correct approach on Unix is to assume the user has - // permissions, attempt the task, and error gracefully if the task fails due to - // permissions. To fit into PowerShell's existing model of preemptively checking - // permissions (which cannot be assumed on Unix), we "assume" the user is an - // administrator by returning true, thus nullifying this check on Unix. -#if UNIX - return true; -#else - System.Security.Principal.WindowsIdentity currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent(); - System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(currentIdentity); - - return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); -#endif - } - - internal static bool NativeItemExists(string path) - { - bool unusedIsDirectory; - Exception unusedException; - - return NativeItemExists(path, out unusedIsDirectory, out unusedException); - } - - // This is done through P/Invoke since File.Exists and Directory.Exists pay 13% performance degradation - // through the CAS checks, and are terribly slow for network paths. - internal static bool NativeItemExists(string path, out bool isDirectory, out Exception exception) +#if !UNIX + private static bool TryGetWindowsCurrentIdentity(out WindowsIdentity currentIdentity) { - exception = null; - - if (String.IsNullOrEmpty(path)) - { - isDirectory = false; - return false; - } -#if UNIX - isDirectory = Platform.NonWindowsIsDirectory(path); - return Platform.NonWindowsIsFile(path); -#else - - if (IsReservedDeviceName(path)) + try { - isDirectory = false; - return false; + currentIdentity = WindowsIdentity.GetCurrent(); } - - int result = NativeMethods.GetFileAttributes(path); - if (result == -1) + catch (SecurityException) { - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == 5) - { - // Handle "Access denied" specifically. - Win32Exception win32Exception = new Win32Exception(errorCode); - exception = new UnauthorizedAccessException(win32Exception.Message, win32Exception); - } - else if (errorCode == 32) - { - // Errorcode 32 is 'ERROR_SHARING_VIOLATION' i.e. - // The process cannot access the file because it is being used by another process. - // GetFileAttributes may return INVALID_FILE_ATTRIBUTES for a system file or directory because of this error. - // GetFileAttributes function tries to open the file with FILE_READ_ATTRIBUTES access right but it fails if the - // sharing flag for the file is set to 0x00000000.This flag prevents it from opening a file for delete, read, or - // write access. For example: C:\pagefile.sys is always opened by OS with sharing flag 0x00000000. - // But FindFirstFile is still able to get attributes as this api retrieves the required information using a find - // handle generated with FILE_LIST_DIRECTORY access. - // Fall back to FindFirstFile to check if the file actually exists. - IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); - NativeMethods.WIN32_FIND_DATA findData; - IntPtr findHandle = NativeMethods.FindFirstFile(path, out findData); - if (findHandle != INVALID_HANDLE_VALUE) - { - isDirectory = (findData.dwFileAttributes & NativeMethods.FileAttributes.Directory) != 0; - NativeMethods.FindClose(findHandle); - return true; - } - } - else if (errorCode == 53) - { - // ERROR_BAD_NETPATH - The network path was not found. - Win32Exception win32Exception = new Win32Exception(errorCode); - exception = new IOException(win32Exception.Message, win32Exception); - } - - isDirectory = false; - return false; + currentIdentity = null; } - isDirectory = (result & ((int)NativeMethods.FileAttributes.Directory)) == - ((int)NativeMethods.FileAttributes.Directory); - - return true; -#endif + return (currentIdentity != null); } - // This is done through P/Invoke since we pay 13% performance degradation - // through the CAS checks required by File.Exists and Directory.Exists - internal static bool NativeFileExists(string path) + /// + /// Gets the current impersonating Windows identity, if any. + /// + /// Current impersonated Windows identity or null. + /// True if current identity is impersonated. + internal static bool TryGetWindowsImpersonatedIdentity(out WindowsIdentity impersonatedIdentity) { - bool isDirectory; - Exception ioException; - - bool itemExists = NativeItemExists(path, out isDirectory, out ioException); - if (ioException != null) + WindowsIdentity currentIdentity; + if (TryGetWindowsCurrentIdentity(out currentIdentity) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) { - throw ioException; + impersonatedIdentity = currentIdentity; + return true; } - return (itemExists && (!isDirectory)); + impersonatedIdentity = null; + return false; } +#endif - // This is done through P/Invoke since we pay 13% performance degradation - // through the CAS checks required by File.Exists and Directory.Exists - internal static bool NativeDirectoryExists(string path) + internal static bool IsAdministrator() { - bool isDirectory; - Exception ioException; - - bool itemExists = NativeItemExists(path, out isDirectory, out ioException); - if (ioException != null) + // Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to + // advanced privilege models, the correct approach on Unix is to assume the user has + // permissions, attempt the task, and error gracefully if the task fails due to + // permissions. To fit into PowerShell's existing model of preemptively checking + // permissions (which cannot be assumed on Unix), we "assume" the user is an + // administrator by returning true, thus nullifying this check on Unix. +#if UNIX + return true; +#else + WindowsIdentity currentIdentity; + if (TryGetWindowsCurrentIdentity(out currentIdentity)) { - throw ioException; + var principal = new WindowsPrincipal(currentIdentity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); } - return (itemExists && isDirectory); - } - - internal static void NativeEnumerateDirectory(string directory, out List directories, out List files) - { - IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); - NativeMethods.WIN32_FIND_DATA findData; - - files = new List(); - directories = new List(); - - IntPtr findHandle; - - findHandle = NativeMethods.FindFirstFile(directory + "\\*", out findData); - if (findHandle != INVALID_HANDLE_VALUE) - { - do - { - if ((findData.dwFileAttributes & NativeMethods.FileAttributes.Directory) != 0) - { - if ((!String.Equals(".", findData.cFileName, StringComparison.OrdinalIgnoreCase)) && - (!String.Equals("..", findData.cFileName, StringComparison.OrdinalIgnoreCase))) - { - directories.Add(directory + "\\" + findData.cFileName); - } - } - else - { - files.Add(directory + "\\" + findData.cFileName); - } - } - while (NativeMethods.FindNextFile(findHandle, out findData)); - NativeMethods.FindClose(findHandle); - } + return false; +#endif } - 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); - // See if it's the correct length. If it's shorter than CON, AUX, etc, it can't be a device name. - // Likewise, if it's longer than 'CLOCK$', it can't be a device name. - 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; } @@ -1062,8 +1226,8 @@ internal static bool IsReservedDeviceName(string destinationPath) foreach (string deviceName in reservedDeviceNames) { if ( - String.Equals(deviceName, compareName, StringComparison.OrdinalIgnoreCase) || - String.Equals(deviceName, noExtensionCompareName, StringComparison.OrdinalIgnoreCase)) + string.Equals(deviceName, compareName, StringComparison.OrdinalIgnoreCase) || + string.Equals(deviceName, noExtensionCompareName, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -1072,77 +1236,35 @@ 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; #else - Uri uri; - return !string.IsNullOrEmpty(path) && Uri.TryCreate(path, UriKind.Absolute, out uri) && uri.IsUnc; -#endif - } - - internal class NativeMethods - { - private static string EnsureLongPathPrefixIfNeeded(string path) - { - if (path.Length >= MAX_PATH && !path.StartsWith(@"\\?\", StringComparison.Ordinal)) - return @"\\?\" + path; - - return path; - } - - [DllImport(PinvokeDllNames.GetFileAttributesDllName, EntryPoint = "GetFileAttributesW", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern int GetFileAttributesPrivate(string lpFileName); - - internal static int GetFileAttributes(string fileName) + if (string.IsNullOrEmpty(path) || !path.StartsWith('\\')) { - fileName = EnsureLongPathPrefixIfNeeded(fileName); - return GetFileAttributesPrivate(fileName); - } - - [Flags] - internal enum FileAttributes - { - Hidden = 0x0002, - Directory = 0x0010 + return false; } - public const int MAX_PATH = 260; - public const int MAX_ALTERNATE = 14; - - [StructLayout(LayoutKind.Sequential)] - public struct FILETIME - { - public uint dwLowDateTime; - public uint dwHighDateTime; - }; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct WIN32_FIND_DATA + // 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))) { - public FileAttributes dwFileAttributes; - public FILETIME ftCreationTime; - public FILETIME ftLastAccessTime; - public FILETIME ftLastWriteTime; - public uint nFileSizeHigh; //changed all to uint, otherwise you run into unexpected overflow - public uint nFileSizeLow; //| - public uint dwReserved0; //| - public uint dwReserved1; //v - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] - public string cFileName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)] - public string cAlternate; + return true; } - [DllImport(PinvokeDllNames.FindFirstFileDllName, CharSet = CharSet.Unicode)] - public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); - - [DllImport(PinvokeDllNames.FindNextFileDllName, CharSet = CharSet.Unicode)] - public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData); + Uri uri; + return Uri.TryCreate(path, UriKind.Absolute, out uri) && uri.IsUnc; +#endif + } - [DllImport(PinvokeDllNames.FindCloseDllName, CharSet = CharSet.Unicode)] - public static extern bool FindClose(IntPtr hFindFile); + 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 = @@ -1164,10 +1286,10 @@ public struct WIN32_FIND_DATA internal static bool IsPowerShellAssembly(string assemblyName) { - if (!String.IsNullOrWhiteSpace(assemblyName)) + if (!string.IsNullOrWhiteSpace(assemblyName)) { // Remove the '.dll' if it's there... - var fixedName = assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + var fixedName = assemblyName.EndsWith(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) ? Path.GetFileNameWithoutExtension(assemblyName) : assemblyName; @@ -1182,7 +1304,7 @@ internal static bool IsPowerShellAssembly(string assemblyName) internal static string GetPowerShellAssemblyStrongName(string assemblyName) { - if (!String.IsNullOrWhiteSpace(assemblyName)) + if (!string.IsNullOrWhiteSpace(assemblyName)) { // Remove the '.dll' if it's there... string fixedName = assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) @@ -1198,13 +1320,12 @@ internal static string GetPowerShellAssemblyStrongName(string assemblyName) return assemblyName; } - /// - /// If a mutex is abandoned, in our case, it is ok to proceed + /// If a mutex is abandoned, in our case, it is ok to proceed. /// - /// The mutex to wait on. If it is null, a new one will be created + /// The mutex to wait on. If it is null, a new one will be created. /// The initializer to use to recreate the mutex. - /// A working mutex. If the mutex was abandoned, a new one is created to replace it + /// A working mutex. If the mutex was abandoned, a new one is created to replace it. internal static Mutex SafeWaitMutex(Mutex mutex, MutexInitializer initializer) { try @@ -1225,6 +1346,7 @@ internal static Mutex SafeWaitMutex(Mutex mutex, MutexInitializer initializer) return mutex; } + internal delegate Mutex MutexInitializer(); internal static bool Succeeded(int hresult) @@ -1232,102 +1354,13 @@ 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 Encoding BigEndianUTF32Encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); + 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 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 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 !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImpersonated to replace it. +#if !UNIX /// /// Queues a CLR worker thread with impersonation of provided Windows identity. /// @@ -1353,41 +1386,28 @@ private static void WorkItemCallback(object callBackArgs) WaitCallback callback = args[1] as WaitCallback; object state = args[2]; - WindowsImpersonationContext impersonationContext = null; - if ((identityToImpersonate != null) && - (identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) + if (identityToImpersonate != null) { - impersonationContext = identityToImpersonate.Impersonate(); - } - try - { - callback(state); - } - finally - { - if (impersonationContext != null) - { - try - { - impersonationContext.Undo(); - impersonationContext.Dispose(); - } - catch (System.Security.SecurityException) { } - } + WindowsIdentity.RunImpersonated( + identityToImpersonate.AccessToken, + () => callback(state)); + return; } + + callback(state); } #endif /// /// If the command name is fully qualified then it is split into its component parts - /// E.g., moduleName\commandName + /// E.g., moduleName\commandName. /// /// /// - /// Command name and as appropriate Module name in out parameter + /// 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]; @@ -1398,26 +1418,15 @@ internal static string ParseCommandName(string commandName, out string moduleNam return commandName; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static T[] EmptyArray() - { - return EmptyArrayHolder._instance; - } - internal static ReadOnlyCollection EmptyReadOnlyCollection() { return EmptyReadOnlyCollectionHolder._instance; } - private static class EmptyArrayHolder - { - internal static readonly T[] _instance = new T[0]; - } - private static class EmptyReadOnlyCollectionHolder { internal static readonly ReadOnlyCollection _instance = - new ReadOnlyCollection(EmptyArray()); + new ReadOnlyCollection(Array.Empty()); } internal static class Separators @@ -1425,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. @@ -1448,44 +1443,175 @@ internal static class Separators internal static readonly char[] PathSearchTrimEnd = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 }; } -#if !UNIX - // This is to reduce the runtime overhead of the feature query - private static readonly Type ComObjectType = typeof(object).Assembly.GetType("System.__ComObject"); -#endif - - internal static bool IsComObject(PSObject psObject) + /// + /// A COM object could be directly of the type 'System.__ComObject', or it could be a strongly typed RWC, + /// whose specific type derives from 'System.__ComObject'. + /// A strongly typed RWC can be created via the 'new' operation with a Primary Interop Assembly (PIA). + /// For example, with the PIA 'Microsoft.Office.Interop.Excel', you can write the following code: + /// var excelApp = new Microsoft.Office.Interop.Excel.Application(); + /// Type type = excelApp.GetType(); + /// Type comObjectType = typeof(object).Assembly.GetType("System.__ComObject"); + /// Console.WriteLine("excelApp type: {0}", type.FullName); + /// Console.WriteLine("Is __ComObject assignable from? {0}", comObjectType.IsAssignableFrom(type)); + /// and the results are: + /// excelApp type: Microsoft.Office.Interop.Excel.ApplicationClass + /// Is __ComObject assignable from? True. + /// + internal static bool IsComObject(object obj) { #if UNIX return false; #else - if (psObject == null) { return false; } - - object obj = PSObject.Base(psObject); - return IsComObject(obj); + return obj != null && Marshal.IsComObject(obj); #endif } - internal static bool IsComObject(object obj) + /// + /// EnforceSystemLockDownLanguageMode + /// FullLangauge -> ConstrainedLanguage + /// RestrictedLanguage -> NoLanguage + /// ConstrainedLanguage -> ConstrainedLanguage + /// NoLanguage -> NoLanguage. + /// + /// ExecutionContext. + /// The current ExecutionContext language mode. + internal static PSLanguageMode EnforceSystemLockDownLanguageMode(ExecutionContext context) { -#if UNIX - return false; -#else - // We can't use System.Runtime.InteropServices.Marshal.IsComObject(obj) since it doesn't work in partial trust. - // - // There could be strongly typed RWCs whose type is not 'System.__ComObject', but the more specific type should - // derive from 'System.__ComObject'. The strongly typed RWCs can be created with 'new' operation via the Primay - // Interop Assembly (PIA). - // For example, with the PIA 'Microsoft.Office.Interop.Excel', you can write the following code: - // var excelApp = new Microsoft.Office.Interop.Excel.Application(); - // Type type = excelApp.GetType(); - // Type comObjectType = typeof(object).Assembly.GetType("System.__ComObject"); - // Console.WriteLine("excelApp type: {0}", type.FullName); - // Console.WriteLine("Is __ComObject assignable from? {0}", comObjectType.IsAssignableFrom(type)); - // and the results are: - // excelApp type: Microsoft.Office.Interop.Excel.ApplicationClass - // Is __ComObject assignable from? True - return obj != null && ComObjectType.IsAssignableFrom(obj.GetType()); -#endif + switch (SystemPolicy.GetSystemLockdownPolicy()) + { + case SystemEnforcementMode.Enforce: + switch (context.LanguageMode) + { + case PSLanguageMode.FullLanguage: + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + break; + + case PSLanguageMode.RestrictedLanguage: + context.LanguageMode = PSLanguageMode.NoLanguage; + break; + + case PSLanguageMode.ConstrainedLanguage: + case PSLanguageMode.NoLanguage: + break; + + default: + Diagnostics.Assert(false, "Unexpected PSLanguageMode"); + context.LanguageMode = PSLanguageMode.NoLanguage; + break; + } + break; + + case SystemEnforcementMode.Audit: + switch (context.LanguageMode) + { + 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; + } + break; + } + + return context.LanguageMode; + } + + internal static string DisplayHumanReadableFileSize(long bytes) + { + 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", + }; + } + + /// + /// 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) + { + CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module"); + // if import-module is visible, then the session is not restricted, + // because the user can load arbitrary code. + if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public) + { + return false; + } + return true; + } + + /// + /// 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) + { + var str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + var boolStr = str.AsSpan(); + + if (boolStr.Length == 1) + { + if (boolStr[0] == '1') + { + return true; + } + + if (boolStr[0] == '0') + { + return false; + } + } + + if (boolStr.Length == 3 && + (boolStr[0] == 'y' || boolStr[0] == 'Y') && + (boolStr[1] == 'e' || boolStr[1] == 'E') && + (boolStr[2] == 's' || boolStr[2] == 'S')) + { + return true; + } + + if (boolStr.Length == 2 && + (boolStr[0] == 'n' || boolStr[0] == 'N') && + (boolStr[1] == 'o' || boolStr[1] == 'O')) + { + return false; + } + + if (boolStr.Length == 4 && + (boolStr[0] == 't' || boolStr[0] == 'T') && + (boolStr[1] == 'r' || boolStr[1] == 'R') && + (boolStr[2] == 'u' || boolStr[2] == 'U') && + (boolStr[3] == 'e' || boolStr[3] == 'E')) + { + return true; + } + + if (boolStr.Length == 5 && + (boolStr[0] == 'f' || boolStr[0] == 'F') && + (boolStr[1] == 'a' || boolStr[1] == 'A') && + (boolStr[2] == 'l' || boolStr[2] == 'L') && + (boolStr[3] == 's' || boolStr[3] == 'S') && + (boolStr[4] == 'e' || boolStr[4] == 'E')) + { + return false; + } + + return defaultValue; } } } @@ -1501,27 +1627,239 @@ public static class InternalTestHooks internal static bool UseDebugAmsiImplementation; 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; internal static bool TestWaitStopComputer; internal static bool TestRenameComputer; - internal static int TestStopComputerResults; - internal static int TestRenameComputerResults; + internal static int TestStopComputerResults; + internal static int TestRenameComputerResults; // It's useful to test that we don't depend on the ScriptBlock and AST objects and can use a re-parsed version. internal static bool IgnoreScriptBlockCache; // Simulate 'System.Diagnostics.Stopwatch.IsHighResolution is false' to test Get-Uptime throw internal static bool StopwatchIsNotHighResolution; internal static bool DisableGACLoading; + 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; + + // A version of Windows PS that is installed on the system; normally this is retrieved from a reg key that is write-protected. + internal static string TestWindowsPowerShellVersionString; + + internal static bool ShowMarkdownOutputBypass; + + 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); + } + + /// + /// Constructs a custom PSSenderInfo instance that can be assigned to $PSSenderInfo + /// in order to simulate a remoting session with respect to the $PSSenderInfo.ConnectionString (connection URL) + /// and $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion (the remoting client's PowerShell version). + /// See Get-FormatDataTest.ps1. + /// + /// The connection URL to reflect in the returned instance's ConnectionString property. + /// The version number to report as the remoting client's PowerShell version. + /// The newly constructed custom PSSenderInfo instance. + public static PSSenderInfo GetCustomPSSenderInfo(string url, Version clientVersion) + { + var dummyPrincipal = new PSPrincipal(new PSIdentity("none", true, "someuser", null), null); + var pssi = new PSSenderInfo(dummyPrincipal, url); + pssi.ApplicationArguments = new PSPrimitiveDictionary(); + pssi.ApplicationArguments.Add("PSVersionTable", new PSObject(new PSPrimitiveDictionary())); + ((PSPrimitiveDictionary)PSObject.Base(pssi.ApplicationArguments["PSVersionTable"])).Add("PSVersion", new PSObject(clientVersion)); + return pssi; + } + } + + /// + /// Provides undo/redo functionality by using 2 instances of . + /// + internal class HistoryStack + { + private readonly BoundedStack _boundedUndoStack; + private readonly BoundedStack _boundedRedoStack; + + internal HistoryStack(int capacity) + { + _boundedUndoStack = new BoundedStack(capacity); + _boundedRedoStack = new BoundedStack(capacity); + } + + internal void Push(T item) + { + _boundedUndoStack.Push(item); + if (RedoCount >= 0) + { + _boundedRedoStack.Clear(); + } + } + + /// + /// Handles bounded history stacks by pushing the current item to the redoStack and returning the item from the popped undoStack. + /// + internal T Undo(T currentItem) + { + T previousItem = _boundedUndoStack.Pop(); + _boundedRedoStack.Push(currentItem); + return previousItem; + } + + /// + /// Handles bounded history stacks by pushing the current item to the undoStack and returning the item from the popped redoStack. + /// + internal T Redo(T currentItem) + { + var nextItem = _boundedRedoStack.Pop(); + _boundedUndoStack.Push(currentItem); + return nextItem; + } + + internal int UndoCount => _boundedUndoStack.Count; + + internal int RedoCount => _boundedRedoStack.Count; + } + + /// + /// A bounded stack based on a linked list. + /// + internal class BoundedStack : LinkedList + { + private readonly int _capacity; + + /// + /// Lazy initialisation, i.e. it sets only its limit but does not allocate the memory for the given capacity. + /// + /// + internal BoundedStack(int capacity) + { + _capacity = capacity; + } + + /// + /// Push item. + /// + /// + internal void Push(T item) + { + this.AddFirst(item); + + if (this.Count > _capacity) + { + this.RemoveLast(); + } + } + + /// + /// Pop item. + /// + /// + internal T Pop() + { + if (this.First == null) + { + throw new InvalidOperationException(SessionStateStrings.BoundedStackIsEmpty); + } + + var item = this.First.Value; + try + { + this.RemoveFirst(); + } + catch (InvalidOperationException) + { + throw new InvalidOperationException(SessionStateStrings.BoundedStackIsEmpty); + } + + return item; + } + } + + /// + /// A readonly Hashset. + /// + internal sealed class ReadOnlyBag : IEnumerable + { + private readonly HashSet _hashset; + + /// + /// Constructor for the readonly Hashset. + /// + internal ReadOnlyBag(HashSet hashset) + { + ArgumentNullException.ThrowIfNull(hashset); + + _hashset = hashset; + } + + /// + /// Get the count of the Hashset. + /// + public int Count => _hashset.Count; + + /// + /// Indicate if it's a readonly Hashset. + /// + public bool IsReadOnly => true; + + /// + /// Check if the set contains an item. + /// + public bool Contains(T item) => _hashset.Contains(item); + + /// + /// GetEnumerator method. + /// + public IEnumerator GetEnumerator() => _hashset.GetEnumerator(); + + /// + /// Get an empty singleton. + /// + 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) { - fieldInfo.SetValue(null, value); + throw new ArgumentNullException(paramName); } } } diff --git a/src/System.Management.Automation/engine/VariableAttributeCollection.cs b/src/System.Management.Automation/engine/VariableAttributeCollection.cs index 86dfa5396df..a1a81dbb01f 100644 --- a/src/System.Management.Automation/engine/VariableAttributeCollection.cs +++ b/src/System.Management.Automation/engine/VariableAttributeCollection.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using Dbg = System.Management.Automation; using System.Collections.ObjectModel; +using Dbg = System.Management.Automation; + namespace System.Management.Automation { /// @@ -19,21 +19,18 @@ internal class PSVariableAttributeCollection : Collection /// the specified variable. Whenever the attributes change /// the variable value is verified against the attribute. /// - /// /// /// The variable that needs to be verified anytime an attribute /// changes. /// - /// /// /// If is null. /// - /// internal PSVariableAttributeCollection(PSVariable variable) { if (variable == null) { - throw PSTraceSource.NewArgumentNullException("variable"); + throw PSTraceSource.NewArgumentNullException(nameof(variable)); } _variable = variable; @@ -46,24 +43,19 @@ internal PSVariableAttributeCollection(PSVariable variable) /// Ensures that the variable that the attribute is being added to is still /// valid after the attribute is added. /// - /// /// /// The zero-based index at which should be inserted. /// - /// /// /// The attribute being added to the collection. /// - /// /// /// If the new attribute causes the variable to be in an invalid state. /// - /// /// /// If the new attribute is an ArgumentTransformationAttribute and the transformation /// fails. /// - /// protected override void InsertItem(int index, Attribute item) { object variableValue = VerifyNewAttribute(item); @@ -77,19 +69,15 @@ protected override void InsertItem(int index, Attribute item) /// Ensures that the variable that the attribute is being set to is still /// valid after the attribute is set. /// - /// /// /// The zero-based index at which should be set. /// - /// /// /// The attribute being set in the collection. /// - /// /// /// If the new attribute causes the variable to be in an invalid state. /// - /// protected override void SetItem(int index, Attribute item) { object variableValue = VerifyNewAttribute(item); @@ -109,7 +97,7 @@ protected override void SetItem(int index, Attribute item) /// has already been done, this function will add the attribute without checking /// and possibly updating the value. /// - /// The attribute to add + /// The attribute to add. internal void AddAttributeNoCheck(Attribute item) { base.InsertItem(this.Count, item); @@ -119,16 +107,13 @@ internal void AddAttributeNoCheck(Attribute item) /// Validates and performs any transformations that the new attribute /// implements. /// - /// /// /// The new attribute to be added to the collection. /// - /// /// /// The new variable value. This may change from the original value if the /// new attribute is an ArgumentTransformationAttribute. /// - /// private object VerifyNewAttribute(Attribute item) { object variableValue = _variable.Value; @@ -147,7 +132,7 @@ private object VerifyNewAttribute(Attribute item) engine = context.EngineIntrinsics; } - variableValue = argumentTransformation.Transform(engine, variableValue); + variableValue = argumentTransformation.TransformInternal(engine, variableValue); } if (!PSVariable.IsValidValue(variableValue, item)) @@ -157,7 +142,7 @@ private object VerifyNewAttribute(Attribute item) null, Metadata.InvalidMetadataForCurrentValue, _variable.Name, - ((_variable.Value != null) ? _variable.Value.ToString() : "")); + ((_variable.Value != null) ? _variable.Value.ToString() : string.Empty)); throw e; } @@ -169,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 a3fbe14cd32..4ced936df57 100644 --- a/src/System.Management.Automation/engine/VariableInterfaces.cs +++ b/src/System.Management.Automation/engine/VariableInterfaces.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation; @@ -14,36 +13,33 @@ public sealed class PSVariableIntrinsics #region Constructors /// - /// Hide the default constructor since we always require an instance of SessionState + /// Hide the default constructor since we always require an instance of SessionState. /// private PSVariableIntrinsics() { Dbg.Diagnostics.Assert( false, "This constructor should never be called. Only the constructor that takes an instance of SessionState should be called."); - } // PSVariableInterfaces private + } /// /// Constructs a facade for the specified session. /// - /// /// /// The session for which the facade wraps. /// - /// /// /// If is null. /// - /// internal PSVariableIntrinsics(SessionStateInternal sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentException("sessionState"); + throw PSTraceSource.NewArgumentException(nameof(sessionState)); } _sessionState = sessionState; - } // PSVariableInterfaces internal + } #endregion Constructors @@ -52,16 +48,13 @@ internal PSVariableIntrinsics(SessionStateInternal sessionState) /// /// Gets the specified variable from session state. /// - /// /// /// The name of the variable to get. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The specified variable. /// - /// /// /// If is null. /// @@ -73,7 +66,7 @@ public PSVariable Get(string name) // Parameter validation is done in the session state object - // Null is returned whenever the requested variable is String.Empty. + // Null is returned whenever the requested variable is string.Empty. // As per Powershell V1 implementation: // 1. If the requested variable exists in the session scope, the variable value is returned. // 2. If the requested variable is not null and does not exist in the session scope, then a null value is returned to the pipeline. @@ -85,36 +78,30 @@ public PSVariable Get(string name) } return _sessionState.GetVariable(name); - } // Get + } /// /// Gets the specified variable from session state in the specified scope. /// If the variable doesn't exist in the specified scope no additional lookup /// will be done. /// - /// /// /// The name of the variable to get. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The ID of the scope to do the lookup in. /// - /// /// /// The specified variable. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. @@ -128,38 +115,31 @@ internal PSVariable GetAtScope(string name, string scope) // Parameter validation is done in the session state object return _sessionState.GetVariableAtScope(name, scope); - } // GetAtScope + } /// /// Gets the specified variable value from session state. /// - /// /// /// The name of the variable to get. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -172,44 +152,36 @@ public object GetValue(string name) // Parameter validation is done in the session state object return _sessionState.GetVariableValue(name); - } // GetValue + } /// /// Gets the specified variable from session state. If the variable /// is not found the default value is returned. /// - /// /// /// The name of the variable to get. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The default value returned if the variable could not be found. /// - /// /// /// The value of the specified variable or the default value if the variable /// is not found. /// - /// /// /// If is null. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -222,54 +194,44 @@ public object GetValue(string name, object defaultValue) // Parameter validation is done in the session state object return _sessionState.GetVariableValue(name) ?? defaultValue; - } // GetValue + } /// /// Gets the specified variable from session state in the specified scope. /// If the variable doesn't exist in the specified scope no additional lookup /// will be done. /// - /// /// /// The name of the variable to get. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The ID of the scope to do the lookup in. /// - /// /// /// The value of the specified variable. /// - /// /// /// If is null. /// - /// /// /// If is less than zero, or not /// a number and not "script", "global", "local", or "private" /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -282,42 +244,34 @@ internal object GetValueAtScope(string name, string scope) // Parameter validation is done in the session state object return _sessionState.GetVariableValueAtScope(name, scope); - } // GetValueAtScope + } /// /// Sets the variable to the specified value. /// - /// /// /// The name of the variable to be set. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// The value to set the variable to. /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -330,25 +284,20 @@ public void Set(string name, object value) // Parameter validation is done in the session state object _sessionState.SetVariableValue(name, value, CommandOrigin.Internal); - } // SetVariable + } /// /// Sets the variable. /// - /// /// - /// /// The variable to set /// - /// /// /// If is null. /// - /// /// /// If the variable is read-only or constant. /// - /// public void Set(PSVariable variable) { Dbg.Diagnostics.Assert( @@ -358,38 +307,31 @@ public void Set(PSVariable variable) // Parameter validation is done in the session state object _sessionState.SetVariable(variable, false, CommandOrigin.Internal); - } // SetVariable + } /// /// Removes the specified variable from session state. /// - /// /// /// The name of the variable to be removed. The name can contain drive and/or /// scope specifiers like "ENV:path" or "global:myvar". /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// - /// /// /// If the refers to a provider that could not be found. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider that the refers to does /// not support this operation. /// - /// /// /// If the provider threw an exception. /// @@ -402,20 +344,17 @@ public void Remove(string name) // Parameter validation is done in the session state object _sessionState.RemoveVariable(name); - } // RemoveVariable + } /// /// Removes the specified variable from session state. /// - /// /// /// The variable to be removed. It is removed based on the name of the variable. /// - /// /// /// If is null. /// - /// /// /// if the variable is constant. /// @@ -428,36 +367,29 @@ public void Remove(PSVariable variable) // Parameter validation is done in the session state object _sessionState.RemoveVariable(variable); - } // RemoveVariable + } /// - /// Removes the specified variable from the specified scope + /// Removes the specified variable from the specified scope. /// - /// /// /// The name of the variable to remove. /// - /// /// /// The ID of the scope to do the lookup in. The ID is a zero based index /// of the scope tree with the current scope being zero, its parent scope /// being 1 and so on. /// - /// /// /// If is null. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// - /// /// /// if the variable is constant. /// - /// /// /// If refers to an MSH path (not a variable) /// and the provider throws an exception. @@ -474,29 +406,23 @@ internal void RemoveAtScope(string name, string scope) } /// - /// Removes the specified variable from the specified scope + /// Removes the specified variable from the specified scope. /// - /// /// /// The variable to be removed. It is removed based on the name of the variable. /// - /// /// /// The ID of the scope to do the lookup in. The ID is a zero based index /// of the scope tree with the current scope being zero, its parent scope /// being 1 and so on. /// - /// /// /// If is null. /// - /// /// /// If is less than zero or greater than the number of currently /// active scopes. /// - /// - /// /// /// if the variable is constant. /// @@ -515,9 +441,8 @@ internal void RemoveAtScope(PSVariable variable, string scope) #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data - } // PSVariableIntrinsics + } } - diff --git a/src/System.Management.Automation/engine/VariablePath.cs b/src/System.Management.Automation/engine/VariablePath.cs index d5675a8de36..12bfc2d03a0 100644 --- a/src/System.Management.Automation/engine/VariablePath.cs +++ b/src/System.Management.Automation/engine/VariablePath.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -72,13 +71,11 @@ public VariablePath(string path) /// /// Constructs a scoped item lookup path. /// - /// /// The path to parse. /// /// These flags for anything known about the path (such as, is it a function) before /// being scanned. /// - /// /// /// If is null. /// @@ -86,7 +83,7 @@ internal VariablePath(string path, VariablePathFlags knownFlags) { if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } _userPath = path; @@ -136,6 +133,7 @@ internal VariablePath(string path, VariablePathFlags knownFlags) candidateScopeUpper = "ARIABLE"; candidateFlags = VariablePathFlags.Variable; } + break; } @@ -159,6 +157,7 @@ internal VariablePath(string path, VariablePathFlags knownFlags) { _flags = VariablePathFlags.Variable; } + _flags |= candidateFlags; lastScannedColon = currentCharIndex; currentCharIndex += 1; @@ -223,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. @@ -287,7 +286,7 @@ public string DriveName } /// - /// Gets the namespace specific string + /// Gets the namespace specific string. /// internal string UnqualifiedPath { @@ -320,4 +319,4 @@ internal FunctionLookupPath(string path) { } } -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/engine/WinRT/IInspectable.cs b/src/System.Management.Automation/engine/WinRT/IInspectable.cs index c218472b6a3..28544d42af4 100644 --- a/src/System.Management.Automation/engine/WinRT/IInspectable.cs +++ b/src/System.Management.Automation/engine/WinRT/IInspectable.cs @@ -1,10 +1,10 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.InteropServices; using System.Reflection; +using System.Runtime.InteropServices; +#nullable enable namespace System.Management.Automation { /// @@ -27,7 +27,7 @@ internal interface IInspectable { } /// /// Helper class for WinRT types. /// - internal class WinRTHelper + internal static class WinRTHelper { internal static bool IsWinRTType(Type type) { @@ -35,7 +35,7 @@ internal static bool IsWinRTType(Type type) // TypeAttributes.WindowsRuntime is part of CLR 4.5. Inorder to build PowerShell for // CLR 4.0, a string comparison for the for the existence of TypeAttributes.WindowsRuntime // in the Attributes flag is performed rather than the actual bitwise comparison. - return type.GetTypeInfo().Attributes.ToString().Contains("WindowsRuntime"); + return type.Attributes.ToString().Contains("WindowsRuntime"); } } } diff --git a/src/System.Management.Automation/engine/WorkflowInfo.cs b/src/System.Management.Automation/engine/WorkflowInfo.cs deleted file mode 100644 index 3966c2c06f9..00000000000 --- a/src/System.Management.Automation/engine/WorkflowInfo.cs +++ /dev/null @@ -1,312 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections.ObjectModel; - -namespace System.Management.Automation -{ - /// - /// Provides information about a workflow that is stored in session state. - /// - public class WorkflowInfo : FunctionInfo - { - #region ctor - - /// - /// Creates an instance of the workflowInfo class with the specified name and ScriptBlock - /// - /// - /// - /// The name of the workflow. - /// - /// - /// - /// The script body defining the workflow. - /// - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// - /// The XAML used to define the workflow - /// - /// - /// - /// The workflows referenced within . - /// - /// - /// - /// If is null. - /// - public WorkflowInfo(string name, string definition, ScriptBlock workflow, string xamlDefinition, WorkflowInfo[] workflowsCalled) - : this(name, workflow, (ExecutionContext)null) - { - if (string.IsNullOrEmpty(xamlDefinition)) - { - throw PSTraceSource.NewArgumentNullException("xamlDefinition"); - } - - _definition = definition; - this.XamlDefinition = xamlDefinition; - if (workflowsCalled != null) - { - _workflowsCalled = new ReadOnlyCollection(workflowsCalled); - } - } - - /// - /// Creates an instance of the workflowInfo class with the specified name and ScriptBlock - /// - /// - /// The name of the workflow. - /// - /// - /// The script body defining the workflow. - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// The XAML used to define the workflow - /// - /// - /// The workflows referenced within . - /// - /// module - /// - /// If is null. - /// - public WorkflowInfo(string name, string definition, ScriptBlock workflow, string xamlDefinition, WorkflowInfo[] workflowsCalled, PSModuleInfo module) - : this(name, definition, workflow, xamlDefinition, workflowsCalled) - { - this.Module = module; - } - - /// - /// Creates an instance of the workflowInfo class with the specified name and ScriptBlock - /// - /// - /// - /// The name of the workflow. - /// - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// - /// The ExecutionContext for the workflow. - /// - /// - /// - /// If is null. - /// - /// - internal WorkflowInfo(string name, ScriptBlock workflow, ExecutionContext context) : this(name, workflow, context, null) - { - } - - /// - /// Creates an instance of the workflowInfo class with the specified name and ScriptBlock - /// - /// - /// - /// The name of the workflow. - /// - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// - /// The ExecutionContext for the workflow. - /// - /// - /// - /// The helpfile for the workflow. - /// - /// - /// - /// If is null. - /// - /// - internal WorkflowInfo(string name, ScriptBlock workflow, ExecutionContext context, string helpFile) - : base(name, workflow, context, helpFile) - { - SetCommandType(CommandTypes.Workflow); - } - - /// - /// Creates an instance of the WorkflowInfo class with the specified name and ScriptBlock - /// - /// - /// - /// The name of the workflow. - /// - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// - /// The options to set on the function. Note, Constant can only be set at creation time. - /// - /// - /// - /// The execution context for the workflow. - /// - /// - /// - /// If is null. - /// - /// - internal WorkflowInfo(string name, ScriptBlock workflow, ScopedItemOptions options, ExecutionContext context) : this(name, workflow, options, context, null) - { - } // workflowInfo ctor - - /// - /// Creates an instance of the WorkflowInfo class with the specified name and ScriptBlock - /// - /// - /// - /// The name of the workflow. - /// - /// - /// - /// The ScriptBlock for the workflow - /// - /// - /// - /// The options to set on the function. Note, Constant can only be set at creation time. - /// - /// - /// - /// The execution context for the workflow. - /// - /// - /// - /// The helpfile for the workflow. - /// - /// - /// - /// If is null. - /// - /// - internal WorkflowInfo(string name, ScriptBlock workflow, ScopedItemOptions options, ExecutionContext context, string helpFile) - : base(name, workflow, options, context, helpFile) - { - SetCommandType(CommandTypes.Workflow); - } // workflowInfo ctor - - /// - /// This is a copy constructor. - /// - internal WorkflowInfo(WorkflowInfo other) - : base(other) - { - SetCommandType(CommandTypes.Workflow); - - CopyFields(other); - } - - /// - /// This is a copy constructor. - /// - internal WorkflowInfo(string name, WorkflowInfo other) - : base(name, other) - { - SetCommandType(CommandTypes.Workflow); - - CopyFields(other); - } - - private void CopyFields(WorkflowInfo other) - { - this.XamlDefinition = other.XamlDefinition; - this.NestedXamlDefinition = other.NestedXamlDefinition; - _workflowsCalled = other.WorkflowsCalled; - _definition = other.Definition; - } - - /// - /// Update a workflow - /// - /// - /// The script block that the function should represent. - /// - /// - /// - /// If true, the script block will be applied even if the filter is ReadOnly. - /// - /// - /// - /// Any options to set on the new function, null if none. - /// - /// - /// - /// Helpfile for this function - /// - protected internal override void Update(FunctionInfo function, bool force, ScopedItemOptions options, string helpFile) - { - var other = function as WorkflowInfo; - if (other == null) - { - throw PSTraceSource.NewArgumentException("function"); - } - - base.Update(function, force, options, helpFile); - - CopyFields(other); - } - - /// - /// Create a copy of commandInfo for GetCommandCommand so that we can generate parameter - /// sets based on an argument list (so we can get the dynamic parameters.) - /// - internal override CommandInfo CreateGetCommandCopy(object[] arguments) - { - WorkflowInfo copy = new WorkflowInfo(this); - copy.IsGetCommandCopy = true; - copy.Arguments = arguments; - return copy; - } - - #endregion ctor - - /// - /// Returns the definition of the workflow. - /// - public override string Definition - { - get { return _definition; } - } - private string _definition = ""; - - /// - /// Gets the XAML that represents the definition of the workflow. - /// - public string XamlDefinition { get; internal set; } - - /// - /// Gets or sets the XAML that represents the definition of the workflow - /// when called from another workflow. - /// - public string NestedXamlDefinition { get; set; } - - /// - /// Gets the XAML for workflows called by this workflow. - /// - public ReadOnlyCollection WorkflowsCalled - { - get { return _workflowsCalled ?? Utils.EmptyReadOnlyCollection(); } - } - private ReadOnlyCollection _workflowsCalled; - - internal override HelpCategory HelpCategory - { - get { return HelpCategory.Workflow; } - } - } // WorkflowInfo -} // namespace System.Management.Automation diff --git a/src/System.Management.Automation/engine/cmdlet.cs b/src/System.Management.Automation/engine/cmdlet.cs index ba66798d5e7..db2233d44a9 100644 --- a/src/System.Management.Automation/engine/cmdlet.cs +++ b/src/System.Management.Automation/engine/cmdlet.cs @@ -1,8 +1,7 @@ -#pragma warning disable 1634, 1691 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#pragma warning disable 1634, 1691 using System.Collections; using System.Diagnostics.CodeAnalysis; @@ -11,9 +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 { @@ -26,14 +23,14 @@ 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 /// task, extending the Cmdlet or PSCmdlet classes only as a thin management layer. /// /// - public abstract partial class Cmdlet : InternalCommand + public abstract class Cmdlet : InternalCommand { #region public_properties @@ -48,17 +45,17 @@ public static HashSet CommonParameters return s_commonParameters.Value; } } - 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" }; } ); - /// /// Lists the common parameters that are added by the PowerShell engine when a cmdlet defines /// additional capabilities (SupportsShouldProcess, SupportsTransactions) @@ -70,7 +67,8 @@ public static HashSet OptionalCommonParameters return s_optionalCommonParameters.Value; } } - private static Lazy> s_optionalCommonParameters = new Lazy>( + + private static readonly Lazy> s_optionalCommonParameters = new Lazy>( () => { return new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -78,7 +76,6 @@ public static HashSet OptionalCommonParameters } ); - /// /// Is this command stopping? /// @@ -103,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. /// @@ -113,18 +115,17 @@ internal string _ParameterSetName } /// - /// Sets the parameter set + /// Sets the parameter set. /// - /// /// /// The name of the valid parameter set. /// - /// internal void SetParameterSetName(string parameterSetName) { _parameterSetName = parameterSetName; } - private string _parameterSetName = ""; + + private string _parameterSetName = string.Empty; #region Override Internal @@ -234,9 +235,9 @@ protected Cmdlet() /// baseName and resourceId from the current assembly. /// You should override this if you require a different behavior. /// - /// the base resource name - /// the resource id - /// the resource string corresponding to baseName and resourceId + /// The base resource name. + /// The resource id. + /// The resource string corresponding to baseName and resourceId. /// /// Invalid or , or /// string not found in resources @@ -255,13 +256,13 @@ public virtual string GetResourceString(string baseName, string resourceId) { using (PSTransactionManager.GetEngineProtectionScope()) { - if (String.IsNullOrEmpty(baseName)) - throw PSTraceSource.NewArgumentNullException("baseName"); + if (string.IsNullOrEmpty(baseName)) + throw PSTraceSource.NewArgumentNullException(nameof(baseName)); - if (String.IsNullOrEmpty(resourceId)) - throw PSTraceSource.NewArgumentNullException("resourceId"); + if (string.IsNullOrEmpty(resourceId)) + throw PSTraceSource.NewArgumentNullException(nameof(resourceId)); - ResourceManager manager = ResourceManagerCache.GetResourceManager(this.GetType().GetTypeInfo().Assembly, baseName); + ResourceManager manager = ResourceManagerCache.GetResourceManager(this.GetType().Assembly, baseName); string retValue = null; try @@ -270,11 +271,12 @@ public virtual string GetResourceString(string baseName, string resourceId) } catch (MissingManifestResourceException) { - throw PSTraceSource.NewArgumentException("baseName", GetErrorText.ResourceBaseNameFailure, baseName); + throw PSTraceSource.NewArgumentException(nameof(baseName), GetErrorText.ResourceBaseNameFailure, baseName); } + if (retValue == null) { - throw PSTraceSource.NewArgumentException("resourceId", GetErrorText.ResourceIdFailure, resourceId); + throw PSTraceSource.NewArgumentException(nameof(resourceId), GetErrorText.ResourceIdFailure, resourceId); } return retValue; @@ -298,6 +300,7 @@ public ICommandRuntime CommandRuntime return commandRuntime; } } + set { using (PSTransactionManager.GetEngineProtectionScope()) @@ -318,7 +321,7 @@ public ICommandRuntime CommandRuntime /// a /// rather than the real exception. /// - /// error + /// Error. /// /// Not permitted at this time or from this thread /// @@ -419,9 +422,9 @@ public void WriteObject(object sendToPipeline, bool enumerateCollection) } /// - /// Display verbose information + /// Display verbose information. /// - /// verbose output + /// Verbose output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -453,12 +456,15 @@ public void WriteVerbose(string text) else throw new System.NotImplementedException("WriteVerbose"); } - }//WriteVerbose + } + + internal bool IsWriteVerboseEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteVerboseEnabled(); /// - /// Display warning information + /// Display warning information. /// - /// warning output + /// Warning output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -490,12 +496,15 @@ public void WriteWarning(string text) else throw new System.NotImplementedException("WriteWarning"); } - }//WriteVerbose + } + + internal bool IsWriteWarningEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteWarningEnabled(); /// /// Write text into pipeline execution log. /// - /// text to be written to log + /// Text to be written to log. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -513,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" /// /// /// @@ -530,9 +539,9 @@ public void WriteCommandDetail(string text) } /// - /// Display progress information + /// Display progress information. /// - /// progress information + /// Progress information. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -569,7 +578,7 @@ public void WriteProgress(ProgressRecord progressRecord) } /// - /// Displays progress output if enabled + /// Displays progress output if enabled. /// /// /// Identifies which command is reporting progress @@ -598,12 +607,15 @@ internal void WriteProgress( commandRuntime.WriteProgress(sourceId, progressRecord); else throw new System.NotImplementedException("WriteProgress"); - }//WriteProgress + } + + internal bool IsWriteProgressEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteProgressEnabled(); /// - /// Display debug information + /// Display debug information. /// - /// debug output + /// Debug output. /// /// The pipeline has already been terminated, or was terminated /// during the execution of this method. @@ -641,7 +653,10 @@ public void WriteDebug(string text) else throw new System.NotImplementedException("WriteDebug"); } - }//WriteDebug + } + + internal bool IsWriteDebugEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteDebugEnabled(); /// /// Route information to the user or host. @@ -676,7 +691,7 @@ public void WriteDebug(string text) /// but the command failure will ultimately be /// , /// - public void WriteInformation(Object messageData, string[] tags) + public void WriteInformation(object messageData, string[] tags) { using (PSTransactionManager.GetEngineProtectionScope()) { @@ -684,7 +699,7 @@ public void WriteInformation(Object messageData, string[] tags) if (commandRuntime2 != null) { string source = this.MyInvocation.PSCommandPath; - if (String.IsNullOrEmpty(source)) + if (string.IsNullOrEmpty(source)) { source = this.MyInvocation.MyCommand.Name; } @@ -703,7 +718,7 @@ public void WriteInformation(Object messageData, string[] tags) throw new System.NotImplementedException("WriteInformation"); } } - }//WriteInformation + } /// /// Route information to the user or host. @@ -748,7 +763,10 @@ public void WriteInformation(InformationRecord informationRecord) throw new System.NotImplementedException("WriteInformation"); } } - }//WriteInformation + } + + internal bool IsWriteInformationEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteInformationEnabled(); #endregion Write @@ -803,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 @@ -826,7 +844,7 @@ public void WriteInformation(InformationRecord informationRecord) /// } /// } /// } - /// + /// /// /// /// @@ -899,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 @@ -922,7 +940,7 @@ public bool ShouldProcess(string target) /// } /// } /// } - /// + /// /// /// /// @@ -1003,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 @@ -1020,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 @@ -1029,7 +1047,7 @@ public bool ShouldProcess(string target, string action) /// } /// } /// } - /// + /// /// /// /// @@ -1119,8 +1137,8 @@ public bool ShouldProcess( /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : Cmdlet @@ -1137,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)) /// { @@ -1147,7 +1165,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1172,7 +1190,6 @@ public bool ShouldProcess( } } - #endregion ShouldProcess #region ShouldContinue @@ -1236,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 @@ -1261,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")) /// ) /// { @@ -1280,7 +1297,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1314,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. /// /// @@ -1365,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 @@ -1393,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 @@ -1414,7 +1431,7 @@ public bool ShouldContinue(string query, string caption) /// } /// } /// } - /// + /// /// /// /// @@ -1454,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. /// /// @@ -1505,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 @@ -1533,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 @@ -1554,7 +1571,7 @@ public bool ShouldContinue( /// } /// } /// } - /// + /// /// /// /// @@ -1614,7 +1631,7 @@ internal List GetResults() /// /// Invoke this cmdlet object returning a collection of results. /// - /// The results that were produced by this class + /// The results that were produced by this class. public IEnumerable Invoke() { using (PSTransactionManager.GetEngineProtectionScope()) @@ -1629,8 +1646,8 @@ public IEnumerable Invoke() /// Returns a strongly-typed enumerator for the results of this cmdlet. /// /// The type returned by the enumerator - /// An instance of the appropriate enumerator - /// Thrown when the object returned by the cmdlet cannot be converted to the target type + /// An instance of the appropriate enumerator. + /// Thrown when the object returned by the cmdlet cannot be converted to the target type. public IEnumerable Invoke() { using (PSTransactionManager.GetEngineProtectionScope()) @@ -1662,7 +1679,7 @@ public bool TransactionAvailable() /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] public PSTransactionContext CurrentPSTransaction @@ -1681,7 +1698,7 @@ public PSTransactionContext CurrentPSTransaction #region ThrowTerminatingError /// - /// Terminate the command and report an error + /// Terminate the command and report an error. /// /// /// The error which caused the command to be terminated @@ -1707,7 +1724,6 @@ public PSTransactionContext CurrentPSTransaction /// so that the additional information in /// /// is available. - /// /// /// always throws /// , @@ -1718,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("errorRecord"); + ArgumentNullException.ThrowIfNull(errorRecord); if (commandRuntime != null) { @@ -1810,7 +1826,7 @@ protected virtual void StopProcessing() #endregion Exposed API Override #endregion public_methods - } // Cmdlet + } /// /// This describes the reason why ShouldProcess returned what it returned. @@ -1824,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 5c3da3d7d61..c4b699bd152 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -1,29 +1,29 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.IO; using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Threading; namespace System.Management.Automation { /// - /// Holds the information for a given breakpoint + /// Holds the information for a given breakpoint. /// public abstract class Breakpoint { #region properties /// - /// The action to take when the breakpoint is hit + /// 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 + /// Gets whether this breakpoint is enabled. /// public bool Enabled { get; private set; } @@ -33,17 +33,17 @@ internal void SetEnabled(bool value) } /// - /// Records how many times this breakpoint has been triggered + /// Records how many times this breakpoint has been triggered. /// public int HitCount { get; private set; } /// - /// This breakpoint's Id + /// 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 + /// True if breakpoint is set on a script, false if the breakpoint is not scoped. /// internal bool IsScriptBreakpoint { @@ -51,27 +51,47 @@ internal bool IsScriptBreakpoint } /// - /// The script this breakpoint is on, or null if the breakpoint is not scoped + /// 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 #region constructors - internal Breakpoint(string script, ScriptBlock action) + /// + /// Creates a new instance of a + /// + protected Breakpoint(string script) + : this(script, null) + { } + + /// + /// Creates a new instance of a + /// + protected Breakpoint(string script, ScriptBlock action) { Enabled = true; - Script = script; - Id = s_lastID++; + Script = string.IsNullOrEmpty(script) ? null : script; + Id = Interlocked.Increment(ref s_lastID); Action = action; HitCount = 0; } - internal Breakpoint(string script, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + protected Breakpoint(string script, int id) + : this(script, null, id) + { } + + /// + /// Creates a new instance of a + /// + protected Breakpoint(string script, ScriptBlock action, int id) { Enabled = true; - Script = script; + Script = string.IsNullOrEmpty(script) ? null : script; Id = id; Action = action; HitCount = 0; @@ -84,7 +104,7 @@ internal Breakpoint(string script, ScriptBlock action, int id) internal BreakpointAction Trigger() { ++HitCount; - if (null == Action) + if (Action == null) { return BreakpointAction.Break; } @@ -95,7 +115,7 @@ internal BreakpointAction Trigger() // implement a "trigger once" breakpoint that disables itself after first hit. // One could also share an action across many breakpoints - and hence needs // to know something about the breakpoint that is hit, e.g. in a poor mans code coverage tool. - Action.DoInvoke(dollarUnder: this, input: null, args: Utils.EmptyArray()); + Action.DoInvoke(dollarUnder: this, input: null, args: Array.Empty()); } catch (BreakException) { @@ -108,9 +128,7 @@ internal BreakpointAction Trigger() return BreakpointAction.Continue; } - internal virtual void RemoveSelf(ScriptDebugger debugger) - { - } + internal virtual bool RemoveSelf(ScriptDebugger debugger) => false; #endregion methods @@ -132,18 +150,38 @@ internal enum BreakpointAction } /// - /// A breakpoint on a command + /// A breakpoint on a command. /// public class CommandBreakpoint : Breakpoint { - internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString) + : this(script, command, commandString, null) + { } + + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action) : base(script, action) { CommandPattern = command; Command = commandString; } - internal CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, int id) + : this(script, command, commandString, null, id) + { } + + /// + /// Creates a new instance of a + /// + public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id) : base(script, action, id) { CommandPattern = command; @@ -151,16 +189,16 @@ internal CommandBreakpoint(string script, WildcardPattern command, string comman } /// - /// Which command this breakpoint is on + /// 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. /// - /// A string representation of this breakpoint + /// A string representation of this breakpoint. public override string ToString() { return IsScriptBreakpoint @@ -168,10 +206,8 @@ public override string ToString() : StringUtil.Format(DebuggerStrings.CommandBreakpointString, Command); } - internal override void RemoveSelf(ScriptDebugger debugger) - { + internal override bool RemoveSelf(ScriptDebugger debugger) => debugger.RemoveCommandBreakpoint(this); - } private bool CommandInfoMatches(CommandInfo commandInfo) { @@ -184,7 +220,7 @@ private bool CommandInfoMatches(CommandInfo commandInfo) // If the breakpoint looks like it might have specified a module name and the command // we're checking is in a module, try matching the module\command against the pattern // in the breakpoint. - if (!string.IsNullOrEmpty(commandInfo.ModuleName) && Command.IndexOf('\\') != -1) + if (!string.IsNullOrEmpty(commandInfo.ModuleName) && Command.Contains('\\')) { if (CommandPattern.IsMatch(commandInfo.ModuleName + "\\" + commandInfo.Name)) return true; @@ -210,42 +246,63 @@ internal bool Trigger(InvocationInfo invocationInfo) { return (Script == null || Script.Equals(invocationInfo.ScriptName, StringComparison.OrdinalIgnoreCase)); } + return false; } } /// - /// The access type for variable breakpoints to break on + /// The access type for variable breakpoints to break on. /// public enum VariableAccessMode { /// - /// Break on read access only + /// Break on read access only. /// Read, /// - /// Break on write access only (default) + /// Break on write access only (default). /// Write, /// - /// Breakon read or write access + /// Breakon read or write access. /// ReadWrite } /// - /// A breakpoint on a variable + /// A breakpoint on a variable. /// public class VariableBreakpoint : Breakpoint { - internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action) + /// + /// Creates a new instance of a . + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode) + : this(script, variable, accessMode, null) + { } + + /// + /// Creates a new instance of a . + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action) : base(script, action) { Variable = variable; AccessMode = accessMode; } - internal VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id) + /// + /// Creates a new instance of a . + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, int id) + : this(script, variable, accessMode, null, id) + { } + + /// + /// Creates a new instance of a . + /// + public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id) : base(script, action, id) { Variable = variable; @@ -253,19 +310,19 @@ internal VariableBreakpoint(string script, string variable, VariableAccessMode a } /// - /// The access mode to trigger this variable breakpoint on + /// 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 + /// Which variable this breakpoint is on. /// - public string Variable { get; private set; } + public string Variable { get; } /// - /// Gets the string representation of this breakpoint + /// Gets the string representation of this breakpoint. /// - /// The string representation of this breakpoint + /// The string representation of this breakpoint. public override string ToString() { return IsScriptBreakpoint @@ -289,18 +346,26 @@ internal bool Trigger(string currentScriptFile, bool read) return false; } - internal override void RemoveSelf(ScriptDebugger debugger) - { + internal override bool RemoveSelf(ScriptDebugger debugger) => debugger.RemoveVariableBreakpoint(this); - } } /// - /// A breakpoint on a line or statement + /// A breakpoint on a line or statement. /// public class LineBreakpoint : Breakpoint { - internal LineBreakpoint(string script, int line, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line) + : this(script, line, null) + { } + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, ScriptBlock action) : base(script, action) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); @@ -309,7 +374,17 @@ internal LineBreakpoint(string script, int line, ScriptBlock action) SequencePointIndex = -1; } - internal LineBreakpoint(string script, int line, int column, ScriptBlock action) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column) + : this(script, line, column, null) + { } + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, ScriptBlock action) : base(script, action) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); @@ -318,7 +393,17 @@ internal LineBreakpoint(string script, int line, int column, ScriptBlock action) SequencePointIndex = -1; } - internal LineBreakpoint(string script, int line, int column, ScriptBlock action, int id) + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, int id) + : this(script, line, column, null, id) + { } + + /// + /// Creates a new instance of a + /// + public LineBreakpoint(string script, int line, int column, ScriptBlock action, int id) : base(script, action, id) { Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty."); @@ -328,19 +413,19 @@ internal LineBreakpoint(string script, int line, int column, ScriptBlock action, } /// - /// Which column this breakpoint is on + /// 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. /// - /// A string representation of this breakpoint + /// A string representation of this breakpoint. public override string ToString() { return Column == 0 @@ -349,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) { @@ -395,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; @@ -422,8 +506,6 @@ internal bool TrySetBreakpoint(string scriptFile, FunctionContext functionContex couldBeInNestedScriptBlock = false; } - - int sequencePointIndex; var sequencePoint = FindSequencePoint(functionContext, Line, Column, out sequencePointIndex); if (sequencePoint != null) @@ -446,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; @@ -500,7 +583,7 @@ private void SetBreakpoint(FunctionContext functionContext, int sequencePointInd this.BreakpointBitArray.Set(SequencePointIndex, true); } - internal override void RemoveSelf(ScriptDebugger debugger) + internal override bool RemoveSelf(ScriptDebugger debugger) { if (this.SequencePoints != null) { @@ -509,11 +592,11 @@ internal override void 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. @@ -522,7 +605,8 @@ internal override void RemoveSelf(ScriptDebugger debugger) } } } - debugger.RemoveLineBreakpoint(this); + + return debugger.RemoveLineBreakpoint(this); } } } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 9d787e214ea..985d90ab3ec 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -1,55 +1,56 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Collections.Concurrent; using System.Linq; -using System.Threading; using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Management.Automation.Internal.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; -using System.Diagnostics.CodeAnalysis; +using System.Threading; + +using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { #region Event Args /// - /// Possible actions for the debugger after hitting a breakpoint/step + /// Possible actions for the debugger after hitting a breakpoint/step. /// public enum DebuggerResumeAction { /// - /// Continue running until the next breakpoint, or the end of the script + /// Continue running until the next breakpoint, or the end of the script. /// Continue = 0, /// - /// Step to next statement, going into functions, scripts, etc + /// Step to next statement, going into functions, scripts, etc. /// StepInto = 1, /// - /// Step to next statement, going over functions, scripts, etc + /// Step to next statement, going over functions, scripts, etc. /// StepOut = 2, /// - /// Step to next statement after the current function, script, etc + /// Step to next statement after the current function, script, etc. /// StepOver = 3, /// - /// Stop executing the script + /// Stop executing the script. /// Stop = 4, - }; + } /// /// Arguments for the DebuggerStop event. @@ -57,7 +58,7 @@ public enum DebuggerResumeAction public class DebuggerStopEventArgs : EventArgs { /// - /// Initializes the DebuggerStopEventArgs + /// Initializes the DebuggerStopEventArgs. /// internal DebuggerStopEventArgs(InvocationInfo invocationInfo, List breakpoints) { @@ -83,21 +84,21 @@ public DebuggerStopEventArgs( } /// - /// Invocation info of the code being executed + /// Invocation info of the code being executed. /// public InvocationInfo InvocationInfo { get; internal set; } /// - /// The breakpoint(s) hit + /// The breakpoint(s) hit. /// /// /// 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 + /// This property must be set in the event handler to indicate the debugger what it should do next. /// /// /// The default action is DebuggerAction.Continue. @@ -111,30 +112,30 @@ public DebuggerStopEventArgs( /// leave pending runspace debug sessions suspended until a debugger is attached. /// internal bool SuspendRemote { get; set; } - }; + } /// - /// Kinds of breakpoint updates + /// Kinds of breakpoint updates. /// public enum BreakpointUpdateType { /// - /// A breakpoint was set + /// A breakpoint was set. /// Set = 0, /// - /// A breakpoint was removed + /// A breakpoint was removed. /// Removed = 1, /// - /// A breakpoint was enabled + /// A breakpoint was enabled. /// Enabled = 2, /// - /// A breakpoint was disabled + /// A breakpoint was disabled. /// Disabled = 3 - }; + } /// /// Arguments for the BreakpointUpdated event. @@ -142,7 +143,7 @@ public enum BreakpointUpdateType public class BreakpointUpdatedEventArgs : EventArgs { /// - /// Initializes the BreakpointUpdatedEventArgs + /// Initializes the BreakpointUpdatedEventArgs. /// internal BreakpointUpdatedEventArgs(Breakpoint breakpoint, BreakpointUpdateType updateType, int breakpointCount) { @@ -152,20 +153,20 @@ internal BreakpointUpdatedEventArgs(Breakpoint breakpoint, BreakpointUpdateType } /// - /// Gets the breakpoint that was updated + /// Gets the breakpoint that was updated. /// - public Breakpoint Breakpoint { get; private set; } + public Breakpoint Breakpoint { get; } /// - /// Gets the type of update + /// Gets the type of update. /// - public BreakpointUpdateType UpdateType { get; private set; } + public BreakpointUpdateType UpdateType { get; } /// - /// Gets the current breakpoint count + /// Gets the current breakpoint count. /// - public int BreakpointCount { get; private set; } - }; + public int BreakpointCount { get; } + } #region PSJobStartEventArgs @@ -175,38 +176,26 @@ internal BreakpointUpdatedEventArgs(Breakpoint breakpoint, BreakpointUpdateType public sealed class PSJobStartEventArgs : EventArgs { /// - /// Job to be started + /// Job to be started. /// - public Job Job - { - get; - private set; - } + public Job Job { get; } /// - /// Job debugger + /// Job debugger. /// - public Debugger Debugger - { - get; - private set; - } + public Debugger Debugger { get; } /// - /// Job is run asynchronously + /// Job is run asynchronously. /// - public bool IsAsync - { - get; - private set; - } + public bool IsAsync { get; } /// - /// Constructor + /// Constructor. /// - /// Started job - /// Debugger - /// Job started asynchronously + /// Started job. + /// Debugger. + /// Job started asynchronously. public PSJobStartEventArgs(Job job, Debugger debugger, bool isAsync) { this.Job = job; @@ -217,62 +206,15 @@ public PSJobStartEventArgs(Job job, Debugger debugger, bool isAsync) #endregion - #region DebugSource - - /// - /// Contains debugger script and script file information. - /// - public sealed class DebugSource - { - /// - /// Full script. - /// - public string Script { get; private set; } - - /// - /// Script file for script or null. - /// - public string ScriptFile { get; private set; } - - /// - /// Xaml definition for Workflow script or null. - /// - public string XamlDefinition { get; private set; } - - /// - /// Constructor. - /// - /// Script text - /// Script file - /// Xaml definition - public DebugSource( - string script, - string scriptFile, - string xamlDefinition) - { - Script = script; - ScriptFile = scriptFile; - XamlDefinition = xamlDefinition; - } - - private DebugSource() { } - } - - #endregion - #region Runspace Debug Processing /// - /// StartRunspaceDebugProcessing event arguments + /// StartRunspaceDebugProcessing event arguments. /// 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 @@ -286,37 +228,33 @@ public bool UseDefaultProcessing } /// - /// Constructor + /// Constructor. /// public StartRunspaceDebugProcessingEventArgs(Runspace runspace) { - if (runspace == null) { throw new PSArgumentNullException("runspace"); } + if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); } Runspace = runspace; } } /// - /// ProcessRunspaceDebugEnd event arguments + /// ProcessRunspaceDebugEnd event arguments. /// public sealed class ProcessRunspaceDebugEndEventArgs : EventArgs { /// - /// The runspace where internal debug processing has ended + /// The runspace where internal debug processing has ended. /// - public Runspace Runspace - { - get; - private set; - } + public Runspace Runspace { get; } /// - /// Constructor + /// Constructor. /// /// public ProcessRunspaceDebugEndEventArgs(Runspace runspace) { - if (runspace == null) { throw new PSArgumentNullException("runspace"); } + if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); } Runspace = runspace; } @@ -346,18 +284,18 @@ public enum DebugModes Default = 0x1, /// - /// PowerShell script debugging including workflow script. + /// PowerShell script debugging. /// LocalScript = 0x2, /// - /// PowerShell remote script and workflow debugging. + /// PowerShell remote script debugging. /// RemoteScript = 0x4 - }; + } /// - /// Defines unhandled breakpoint processing behavior + /// Defines unhandled breakpoint processing behavior. /// internal enum UnhandledBreakpointProcessingMode { @@ -384,12 +322,12 @@ public abstract class Debugger #region Events /// - /// Event raised when the debugger hits a breakpoint or a step + /// Event raised when the debugger hits a breakpoint or a step. /// public event EventHandler DebuggerStop; /// - /// Event raised when a breakpoint is updated + /// Event raised when a breakpoint is updated. /// public event EventHandler BreakpointUpdated; @@ -432,7 +370,7 @@ protected bool DebuggerStopped } /// - /// IsPushed + /// IsPushed. /// internal virtual bool IsPushed { @@ -440,7 +378,7 @@ internal virtual bool IsPushed } /// - /// IsRemote + /// IsRemote. /// internal virtual bool IsRemote { @@ -473,16 +411,17 @@ internal bool IsDebugHandlerSubscribed } /// - /// UnhandledBreakpointMode + /// UnhandledBreakpointMode. /// internal virtual UnhandledBreakpointProcessingMode UnhandledBreakpointMode { get { throw new PSNotImplementedException(); } + set { throw new PSNotImplementedException(); } } /// - /// DebuggerMode + /// DebuggerMode. /// public DebugModes DebugMode { get; protected set; } = DebugModes.Default; @@ -496,7 +435,7 @@ public virtual bool IsActive } /// - /// InstanceId + /// InstanceId. /// public virtual Guid InstanceId { @@ -516,9 +455,9 @@ public virtual bool InBreakpoint #region Protected Methods /// - /// RaiseDebuggerStopEvent + /// RaiseDebuggerStopEvent. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] protected void RaiseDebuggerStopEvent(DebuggerStopEventArgs args) { @@ -534,18 +473,18 @@ protected void RaiseDebuggerStopEvent(DebuggerStopEventArgs args) } /// - /// IsDebuggerStopEventSubscribed + /// IsDebuggerStopEventSubscribed. /// - /// True if event subscription exists + /// True if event subscription exists. protected bool IsDebuggerStopEventSubscribed() { return (DebuggerStop != null); } /// - /// RaiseBreakpointUpdatedEvent + /// RaiseBreakpointUpdatedEvent. /// - /// BreakpointUpdatedEventArgs + /// BreakpointUpdatedEventArgs. [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] protected void RaiseBreakpointUpdatedEvent(BreakpointUpdatedEventArgs args) { @@ -553,9 +492,9 @@ protected void RaiseBreakpointUpdatedEvent(BreakpointUpdatedEventArgs args) } /// - /// IsDebuggerBreakpointUpdatedEventSubscribed + /// IsDebuggerBreakpointUpdatedEventSubscribed. /// - /// True if event subscription exists + /// True if event subscription exists. protected bool IsDebuggerBreakpointUpdatedEventSubscribed() { return (BreakpointUpdated != null); @@ -566,14 +505,16 @@ protected bool IsDebuggerBreakpointUpdatedEventSubscribed() /// protected void RaiseStartRunspaceDebugProcessingEvent(StartRunspaceDebugProcessingEventArgs args) { - if (args == null) { throw new PSArgumentNullException("args"); } + if (args == null) { throw new PSArgumentNullException(nameof(args)); } + StartRunspaceDebugProcessing.SafeInvoke(this, args); } /// protected void RaiseRunspaceProcessingCompletedEvent(ProcessRunspaceDebugEndEventArgs args) { - if (args == null) { throw new PSArgumentNullException("args"); } + if (args == null) { throw new PSArgumentNullException(nameof(args)); } + RunspaceDebugProcessingCompleted.SafeInvoke(this, args); } @@ -599,15 +540,15 @@ protected void RaiseCancelRunspaceDebugProcessingEvent() /// Evaluates provided command either as a debugger specific command /// or a PowerShell command. /// - /// PowerShell command - /// Output - /// DebuggerCommandResults + /// PowerShell command. + /// Output. + /// DebuggerCommandResults. public abstract DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output); /// /// Sets the debugger resume action. /// - /// DebuggerResumeAction + /// DebuggerResumeAction. public abstract void SetDebuggerAction(DebuggerResumeAction resumeAction); /// @@ -619,17 +560,17 @@ protected void RaiseCancelRunspaceDebugProcessingEvent() /// Returns current debugger stop event arguments if debugger is in /// debug stop state. Otherwise returns null. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public abstract DebuggerStopEventArgs GetDebuggerStopArgs(); /// - /// Sets the parent debugger and breakpoints. + /// Sets the parent debugger, breakpoints and other debugging context information. /// - /// Parent debugger - /// List of breakpoints - /// Debugger mode - /// host - /// Current path + /// Parent debugger. + /// List of breakpoints. + /// Debugger mode. + /// Host. + /// Current path. public virtual void SetParent( Debugger parent, IEnumerable breakPoints, @@ -640,27 +581,6 @@ public virtual void SetParent( throw new PSNotImplementedException(); } - /// - /// Sets the parent debugger, breakpoints, function source and other - /// debugging context information. - /// - /// Parent debugger - /// List of breakpoints - /// Debugger mode - /// PowerShell host - /// Current path - /// Function to source map - public virtual void SetParent( - Debugger parent, - IEnumerable breakPoints, - DebuggerResumeAction? startAction, - PSHost host, - PathInfo path, - Dictionary functionSourceMap) - { - throw new PSNotImplementedException(); - } - /// /// Sets the debugger mode. /// @@ -678,14 +598,166 @@ public virtual IEnumerable GetCallStack() return new Collection(); } + /// + /// Get a breakpoint by id in the current runspace, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// Id of the breakpoint you want. + public Breakpoint GetBreakpoint(int id) => + GetBreakpoint(id, runspaceId: null); + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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. + public virtual Breakpoint GetBreakpoint(int id, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Adds the provided set of breakpoints to the debugger, in the current runspace. + /// + /// Breakpoints. + public void SetBreakpoints(IEnumerable breakpoints) => + SetBreakpoints(breakpoints, runspaceId: null); + /// /// Adds the provided set of breakpoints to the debugger. /// /// Breakpoints. - public virtual void SetBreakpoints(IEnumerable breakpoints) - { + /// The runspace id of the runspace you want to interact with, null being the current runspace. + public virtual void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Returns breakpoints in the current runspace, primarily for the Get-PSBreakpoint cmdlet. + /// + public List GetBreakpoints() => + GetBreakpoints(runspaceId: null); + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public virtual List GetBreakpoints(int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Sets a command breakpoint in the current runspace in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The command breakpoint that was set. + public CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path) => + SetCommandBreakpoint(command, action, path, runspaceId: null); + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A value of null will use the current runspace. + /// The command breakpoint that was set. + public virtual CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Sets a line breakpoint in the current runspace in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The line breakpoint that was set. + public LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action) => + SetLineBreakpoint(path, line, column, action, runspaceId: null); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public virtual LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Sets a variable breakpoint in the current runspace in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The variable breakpoint that was set. + public VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path) => + SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId: null); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public virtual VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Removes a breakpoint from the debugger in the current runspace. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// True if the breakpoint was removed from the debugger; false otherwise. + public bool RemoveBreakpoint(Breakpoint breakpoint) => + RemoveBreakpoint(breakpoint, runspaceId: null); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public virtual bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Enables a breakpoint in the debugger in the current runspace. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public Breakpoint EnableBreakpoint(Breakpoint breakpoint) => + EnableBreakpoint(breakpoint, runspaceId: null); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public virtual Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + throw new PSNotImplementedException(); + + /// + /// Disables a breakpoint in the debugger in the current runspace. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public Breakpoint DisableBreakpoint(Breakpoint breakpoint) => + DisableBreakpoint(breakpoint, runspaceId: null); + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public virtual Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) => throw new PSNotImplementedException(); - } /// /// Resets the command processor source information so that it is @@ -699,7 +771,7 @@ public virtual void ResetCommandProcessorSource() /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. public virtual void SetDebuggerStepMode(bool enabled) { throw new PSNotImplementedException(); @@ -709,12 +781,31 @@ public virtual void SetDebuggerStepMode(bool enabled) #region Internal Methods + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + 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. /// - /// Command string - /// Output collection + /// Command string. + /// Output collection. /// DebuggerCommand containing information on whether and how the command was processed. internal virtual DebuggerCommand InternalProcessCommand(string command, IList output) { @@ -726,9 +817,9 @@ internal virtual DebuggerCommand InternalProcessCommand(string command, IList - /// Current source line - /// Output collection - /// True if source listed successfully + /// Current source line. + /// Output collection. + /// True if source listed successfully. internal virtual bool InternalProcessListCommand(int lineNum, IList output) { throw new PSNotImplementedException(); @@ -738,19 +829,21 @@ internal virtual bool InternalProcessListCommand(int lineNum, IList ou /// Sets up debugger to debug provided job or its child jobs. /// /// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal virtual void DebugJob(Job job) - { + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal virtual void DebugJob(Job job, bool breakAll) => throw new PSNotImplementedException(); - } /// /// Removes job from debugger job list and pops the its /// debugger from the active debugger stack. /// - /// Job + /// Job. internal virtual void StopDebugJob(Job job) { throw new PSNotImplementedException(); @@ -759,7 +852,7 @@ internal virtual void StopDebugJob(Job job) /// /// GetActiveDebuggerCallStack. /// - /// Array of stack frame objects of active debugger + /// Array of stack frame objects of active debugger. internal virtual CallStackFrame[] GetActiveDebuggerCallStack() { throw new PSNotImplementedException(); @@ -770,7 +863,7 @@ internal virtual CallStackFrame[] GetActiveDebuggerCallStack() /// for monitoring of debugger events. This is used to implement nested /// debugging of runspaces. /// - /// PSEntityCreatedRunspaceEventArgs + /// PSEntityCreatedRunspaceEventArgs. internal virtual void StartMonitoringRunspace(PSMonitorRunspaceInfo args) { throw new PSNotImplementedException(); @@ -779,7 +872,7 @@ internal virtual void StartMonitoringRunspace(PSMonitorRunspaceInfo args) /// /// Method to end the monitoring of a runspace for debugging events. /// - /// PSEntityCreatedRunspaceEventArgs + /// PSEntityCreatedRunspaceEventArgs. internal virtual void EndMonitoringRunspace(PSMonitorRunspaceInfo args) { throw new PSNotImplementedException(); @@ -797,16 +890,20 @@ internal virtual void ReleaseSavedDebugStop() /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug - internal virtual void DebugRunspace(Runspace runspace) - { + /// + /// The runspace to debug. + /// + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the runspace. + /// + internal virtual void DebugRunspace(Runspace runspace, bool breakAll) => throw new PSNotImplementedException(); - } /// /// Removes the provided Runspace from the nested "active" debugger state. /// - /// Runspace + /// Runspace. internal virtual void StopDebugRunspace(Runspace runspace) { throw new PSNotImplementedException(); @@ -840,7 +937,7 @@ internal void RaiseNestedDebuggingCancelEvent() /// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow /// a host script debugger implementation to provide an active debugging session. /// - /// Runspace to debug + /// Runspace to debug. internal virtual void QueueRunspaceForDebug(Runspace runspace) { throw new PSNotImplementedException(); @@ -873,7 +970,7 @@ public virtual void CancelDebuggerProcessing() #region ScriptDebugger class /// - /// Holds the debugging information for a Monad Shell session + /// Holds the debugging information for a Monad Shell session. /// internal sealed class ScriptDebugger : Debugger, IDisposable { @@ -883,11 +980,12 @@ internal ScriptDebugger(ExecutionContext context) { _context = context; _inBreakpoint = false; - _idToBreakpoint = new Dictionary(); - _pendingBreakpoints = new List(); - _boundBreakpoints = new Dictionary>>(StringComparer.OrdinalIgnoreCase); - _commandBreakpoints = new List(); - _variableBreakpoints = new Dictionary>(StringComparer.OrdinalIgnoreCase); + _idToBreakpoint = 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); _steppingMode = SteppingMode.None; _callStack = new CallStackList { _callStackList = new List() }; @@ -901,7 +999,7 @@ internal ScriptDebugger(ExecutionContext context) } /// - /// Static constructor + /// Static constructor. /// static ScriptDebugger() { @@ -936,7 +1034,7 @@ public override bool InBreakpoint internal override bool IsPushed { - get { return (_activeDebuggers.Count > 0); } + get { return (!_activeDebuggers.IsEmpty); } } /// @@ -965,20 +1063,24 @@ internal override bool IsDebuggerSteppingEnabled } private bool? _isLocalSession; + 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; } } + /// + /// Gets or sets the object that triggered the current breakpoint. + /// + private object TriggerObject { get; set; } + #endregion properties #region internal methods @@ -1012,7 +1114,6 @@ internal void ResetDebugger() _isLocalSession = null; _nestedDebuggerStop = false; - _writeWFErrorOnce = false; _debuggerStopEventArgs.Clear(); _lastActiveDebuggerAction = DebuggerResumeAction.Continue; _currentDebuggerAction = DebuggerResumeAction.Continue; @@ -1084,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; @@ -1121,10 +1222,10 @@ internal void RegisterScriptFile(ExternalScriptInfo scriptCommandInfo) internal void RegisterScriptFile(string path, string scriptContents) { - Tuple> boundBreakpoints; + Tuple> boundBreakpoints; if (!_boundBreakpoints.TryGetValue(path, out boundBreakpoints)) { - _boundBreakpoints.Add(path, Tuple.Create(new WeakReference(scriptContents), new List())); + _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary()); } else { @@ -1133,15 +1234,15 @@ internal void RegisterScriptFile(string path, string scriptContents) boundBreakpoints.Item1.TryGetTarget(out oldScriptContents); if (oldScriptContents == null || !oldScriptContents.Equals(scriptContents, StringComparison.Ordinal)) { - UnbindBoundBreakpoints(boundBreakpoints.Item2); - _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new List()); + UnbindBoundBreakpoints(boundBreakpoints.Item2.Values.ToList()); + _boundBreakpoints[path] = Tuple.Create(new WeakReference(scriptContents), new ConcurrentDictionary()); } } } #endregion Call stack management - #region adding breakpoints + #region setting breakpoints internal void AddBreakpointCommon(Breakpoint breakpoint) { @@ -1154,30 +1255,27 @@ internal void AddBreakpointCommon(Breakpoint breakpoint) OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Set, _idToBreakpoint.Count)); } - private Breakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) + private CommandBreakpoint AddCommandBreakpoint(CommandBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - _commandBreakpoints.Add(breakpoint); + _commandBreakpoints[breakpoint.Id] = breakpoint; return breakpoint; } - internal Breakpoint NewCommandBreakpoint(string path, string command, ScriptBlock action) + private LineBreakpoint AddLineBreakpoint(LineBreakpoint breakpoint) { - WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action)); - } + AddBreakpointCommon(breakpoint); + AddPendingBreakpoint(breakpoint); - internal Breakpoint NewCommandBreakpoint(string command, ScriptBlock action) - { - WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); - return AddCommandBreakpoint(new CommandBreakpoint(null, pattern, command, action)); + return breakpoint; } - private Breakpoint AddLineBreakpoint(LineBreakpoint breakpoint) + private void AddPendingBreakpoint(LineBreakpoint breakpoint) { - AddBreakpointCommon(breakpoint); - _pendingBreakpoints.Add(breakpoint); - return breakpoint; + _pendingBreakpoints.AddOrUpdate( + breakpoint.Script, + new ConcurrentDictionary { [breakpoint.Id] = breakpoint }, + (_, dictionary) => { dictionary.TryAdd(breakpoint.Id, breakpoint); return dictionary; }); } private void AddNewBreakpoint(Breakpoint breakpoint) @@ -1203,46 +1301,44 @@ private void AddNewBreakpoint(Breakpoint breakpoint) } } - internal Breakpoint NewLineBreakpoint(string path, int line, ScriptBlock action) - { - Diagnostics.Assert(path != null, "caller to verify path is not null"); - - return AddLineBreakpoint(new LineBreakpoint(path, line, action)); - } - - internal Breakpoint NewStatementBreakpoint(string path, int line, int column, ScriptBlock action) - { - Diagnostics.Assert(path != null, "caller to verify path is not null"); - - return AddLineBreakpoint(new LineBreakpoint(path, line, column, action)); - } - internal VariableBreakpoint AddVariableBreakpoint(VariableBreakpoint breakpoint) { AddBreakpointCommon(breakpoint); - List breakpoints; - if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out breakpoints)) + if (!_variableBreakpoints.TryGetValue(breakpoint.Variable, out ConcurrentDictionary breakpoints)) { - breakpoints = new List(); - _variableBreakpoints.Add(breakpoint.Variable, breakpoints); + breakpoints = new ConcurrentDictionary(); + _variableBreakpoints[breakpoint.Variable] = breakpoints; } - breakpoints.Add(breakpoint); + + breakpoints[breakpoint.Id] = breakpoint; return breakpoint; } - internal Breakpoint NewVariableBreakpoint(string path, string variableName, VariableAccessMode accessMode, ScriptBlock action) + private void UpdateBreakpoints(FunctionContext functionContext) { - return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action)); - } + if (functionContext._breakPoints == null) + { + // This should be rare - setting a breakpoint inside a script, but debugger hadn't started. + SetupBreakpoints(functionContext); + } + else + { + // Check pending breakpoints to see if any apply to this script. + if (string.IsNullOrEmpty(functionContext._file)) + { + return; + } - internal Breakpoint NewVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action) - { - return AddVariableBreakpoint(new VariableBreakpoint(null, variableName, accessMode, action)); + if (_pendingBreakpoints.TryGetValue(functionContext._file, out var dictionary) && !dictionary.IsEmpty) + { + SetPendingBreakpoints(functionContext); + } + } } /// - /// Raises the BreakpointUpdated event + /// Raises the BreakpointUpdated event. /// /// private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e) @@ -1250,45 +1346,31 @@ private void OnBreakpointUpdated(BreakpointUpdatedEventArgs e) RaiseBreakpointUpdatedEvent(e); } - #endregion adding breakpoints + #endregion setting breakpoints #region removing breakpoints - // This is the implementation of the Remove-PSBreakpoint cmdlet. - internal void RemoveBreakpoint(Breakpoint breakpoint) - { - _idToBreakpoint.Remove(breakpoint.Id); + internal bool RemoveVariableBreakpoint(VariableBreakpoint breakpoint) => + _variableBreakpoints[breakpoint.Variable].Remove(breakpoint.Id, out _); - breakpoint.RemoveSelf(this); + internal bool RemoveCommandBreakpoint(CommandBreakpoint breakpoint) => + _commandBreakpoints.Remove(breakpoint.Id, out _); - if (_idToBreakpoint.Count == 0) + internal bool RemoveLineBreakpoint(LineBreakpoint breakpoint) + { + bool removed = false; + if (_pendingBreakpoints.TryGetValue(breakpoint.Script, out var dictionary)) { - // The last breakpoint was removed, turn off debugging. - SetInternalDebugMode(InternalDebugMode.Disabled); + removed = dictionary.Remove(breakpoint.Id, out _); } - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count)); - } - - internal void RemoveVariableBreakpoint(VariableBreakpoint breakpoint) - { - _variableBreakpoints[breakpoint.Variable].Remove(breakpoint); - } - - internal void RemoveCommandBreakpoint(CommandBreakpoint breakpoint) - { - _commandBreakpoints.Remove(breakpoint); - } - - internal void RemoveLineBreakpoint(LineBreakpoint breakpoint) - { - _pendingBreakpoints.Remove(breakpoint); - - Tuple> value; + Tuple> value; if (_boundBreakpoints.TryGetValue(breakpoint.Script, out value)) { - value.Item2.Remove(breakpoint); + removed = value.Item2.Remove(breakpoint.Id, out _); } + + return removed; } #endregion removing breakpoints @@ -1298,11 +1380,11 @@ internal void 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 + /// Checks for command breakpoints. /// internal bool CheckCommand(InvocationInfo invocationInfo) { @@ -1314,13 +1396,13 @@ internal bool CheckCommand(InvocationInfo invocationInfo) } List breakpoints = - _commandBreakpoints.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList(); + _commandBreakpoints.Values.Where(bp => bp.Enabled && bp.Trigger(invocationInfo)).ToList(); bool checkLineBp = true; - if (breakpoints.Any()) + if (breakpoints.Count > 0) { breakpoints = TriggerBreakpoints(breakpoints); - if (breakpoints.Any()) + if (breakpoints.Count > 0) { var breakInvocationInfo = functionContext != null @@ -1337,7 +1419,7 @@ internal bool CheckCommand(InvocationInfo invocationInfo) internal void CheckVariableRead(string variableName) { var breakpointsToTrigger = GetVariableBreakpointsToTrigger(variableName, read: true); - if (breakpointsToTrigger != null && breakpointsToTrigger.Any()) + if (breakpointsToTrigger != null && breakpointsToTrigger.Count > 0) { TriggerVariableBreakpoints(breakpointsToTrigger); } @@ -1346,7 +1428,7 @@ internal void CheckVariableRead(string variableName) internal void CheckVariableWrite(string variableName) { var breakpointsToTrigger = GetVariableBreakpointsToTrigger(variableName, read: false); - if (breakpointsToTrigger != null && breakpointsToTrigger.Any()) + if (breakpointsToTrigger != null && breakpointsToTrigger.Count > 0) { TriggerVariableBreakpoints(breakpointsToTrigger); } @@ -1367,7 +1449,7 @@ private List GetVariableBreakpointsToTrigger(string variable { SetInternalDebugMode(InternalDebugMode.Disabled); - List breakpoints; + ConcurrentDictionary breakpoints; if (!_variableBreakpoints.TryGetValue(variableName, out breakpoints)) { // $PSItem is an alias for $_. We don't use PSItem internally, but a user might @@ -1382,8 +1464,8 @@ private List GetVariableBreakpointsToTrigger(string variable return null; var callStackInfo = _callStack.Last(); - var currentScriptFile = (callStackInfo != null) ? callStackInfo.File : null; - return breakpoints.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList(); + var currentScriptFile = callStackInfo?.File; + return breakpoints.Values.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList(); } finally { @@ -1398,33 +1480,16 @@ internal void TriggerVariableBreakpoints(List breakpoints) OnDebuggerStop(invocationInfo, breakpoints.ToList()); } - /// - /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. - /// - internal Breakpoint GetBreakpoint(int id) - { - Breakpoint breakpoint; - _idToBreakpoint.TryGetValue(id, out breakpoint); - return breakpoint; - } - - /// - /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. - /// - internal List GetBreakpoints() - { - return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList(); - } - // 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; } + return null; } @@ -1469,33 +1534,18 @@ private List TriggerBreakpoints(List breakpoints) return breaks; } - #endregion triggering breakpoints - - #region enabling/disabling breakpoints - - /// - /// Implementation of Enable-PSBreakpoint cmdlet. - /// - internal void EnableBreakpoint(Breakpoint bp) - { - bp.SetEnabled(true); - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Enabled, _idToBreakpoint.Count)); - } - - /// - /// Implementation of Disable-PSBreakpoint cmdlet. - /// - internal void DisableBreakpoint(Breakpoint bp) - { - bp.SetEnabled(false); - OnBreakpointUpdated(new BreakpointUpdatedEventArgs(bp, BreakpointUpdateType.Disabled, _idToBreakpoint.Count)); - } - - #endregion enabling/disabling 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); } @@ -1508,13 +1558,7 @@ internal void OnSequencePointHit(FunctionContext functionContext) _currentDebuggerAction = DebuggerResumeAction.Continue; ResumeExecution(DebuggerResumeAction.Stop); } -#if !CORECLR // Workflow Not Supported on OneCore PS - // Lazily subscribe to workflow start engine event for workflow debugging. - if (!_wfStartEventSubscribed && IsJobDebuggingMode()) - { - SubscribeToEngineWFJobStartEvent(); - } -#endif + UpdateBreakpoints(functionContext); if (_steppingMode == SteppingMode.StepIn && @@ -1535,58 +1579,45 @@ 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.Any()) + 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); + } + } - private void UpdateBreakpoints(FunctionContext functionContext) - { - if (functionContext._breakPoints == null) - { - // This should be rare - setting a breakpoint inside a script, but debugger hadn't started. - SetupBreakpoints(functionContext); - } - else - { - // Check pending breakpoints to see if any apply to this script. - if (string.IsNullOrEmpty(functionContext._file)) { return; } - bool havePendingBreakpoint = false; - foreach (var item in _pendingBreakpoints) - { - if (item.IsScriptBreakpoint && item.Script.Equals(functionContext._file, StringComparison.OrdinalIgnoreCase)) - { - havePendingBreakpoint = true; - break; + if (enabledBreakpoints.Count > 0) + { + enabledBreakpoints = TriggerBreakpoints(enabledBreakpoints); + if (enabledBreakpoints.Count > 0) + { + StopOnSequencePoint(functionContext, enabledBreakpoints); + } + } } } - if (havePendingBreakpoint) - { - SetPendingBreakpoints(functionContext); - } } } + #endregion triggering breakpoints + #endregion internal methods #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; } /// @@ -1596,7 +1627,7 @@ private class CallStackInfo internal bool IsFrameHidden { get; set; } internal bool TopFrameAtBreakpoint { get; set; } - }; + } private struct CallStackList { @@ -1640,7 +1671,7 @@ internal CallStackInfo Last() internal FunctionContext LastFunctionContext() { var last = Last(); - return last != null ? last.FunctionContext : null; + return last?.FunctionContext; } internal bool Any() @@ -1680,11 +1711,11 @@ internal void Clear() } private readonly ExecutionContext _context; - private List _pendingBreakpoints; - private readonly Dictionary>> _boundBreakpoints; - private readonly List _commandBreakpoints; - private readonly Dictionary> _variableBreakpoints; - private readonly Dictionary _idToBreakpoint; + private readonly ConcurrentDictionary> _pendingBreakpoints; + private readonly ConcurrentDictionary>> _boundBreakpoints; + private readonly ConcurrentDictionary _commandBreakpoints; + private readonly ConcurrentDictionary> _variableBreakpoints; + private readonly ConcurrentDictionary _idToBreakpoint; private SteppingMode _steppingMode; private CallStackInfo _overOrOutFrame; private CallStackList _callStack; @@ -1696,35 +1727,36 @@ internal void Clear() private PowerShell _psDebuggerCommand; // Job debugger integration. -#if !CORECLR // Workflow Not Supported on OneCore PS - private bool _wfStartEventSubscribed; -#endif private bool _nestedDebuggerStop; - private bool _writeWFErrorOnce; - 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; + private bool _preserveUnhandledDebugStopEvent; private ManualResetEventSlim _preserveDebugStopEvent; // Process runspace debugger - private Lazy> _runspaceDebugQueue = new Lazy>(); - private volatile Int32 _processingRunspaceDebugQueue; + private readonly Lazy> _runspaceDebugQueue = new Lazy>(); + private volatile int _processingRunspaceDebugQueue; private ManualResetEventSlim _runspaceDebugCompleteEvent; + // System is locked down when true. Used to disable debugger on lock down. + private bool? _isSystemLockedDown; + private static readonly string s_processDebugPromptMatch; #endregion private members @@ -1732,7 +1764,7 @@ internal void Clear() #region private methods /// - /// Raises the DebuggerStop event + /// Raises the DebuggerStop event. /// private void OnDebuggerStop(InvocationInfo invocationInfo, List breakpoints) { @@ -1746,7 +1778,7 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea { _context.EngineHostInterface.UI.WriteWarningLine( breakpoints.Count > 0 - ? String.Format(CultureInfo.CurrentCulture, DebuggerStrings.WarningBreakpointWillNotBeHit, + ? string.Format(CultureInfo.CurrentCulture, DebuggerStrings.WarningBreakpointWillNotBeHit, breakpoints[0]) : new InvalidOperationException().Message); return; @@ -1764,7 +1796,9 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea return; } - _context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints)); + bool oldQuestionMarkVariableValue = _context.QuestionMarkVariableValue; + + _context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints, TriggerObject)); FunctionInfo defaultPromptInfo = null; string originalPromptString = null; @@ -1790,15 +1824,26 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea // Ignore, it means they don't have the default prompt } + // Change the context language mode before updating the prompt script. + // This way the new prompt scriptblock will pick up the current context language mode. + PSLanguageMode? originalLanguageMode = null; + if (_context.UseFullLanguageModeInDebugger && + (_context.LanguageMode != PSLanguageMode.FullLanguage)) + { + originalLanguageMode = _context.LanguageMode; + _context.LanguageMode = PSLanguageMode.FullLanguage; + } + // Update the prompt to the debug prompt if (hadDefaultPrompt) { - int index = originalPromptString.IndexOf("\"", StringComparison.OrdinalIgnoreCase); + int index = originalPromptString.IndexOf('"', StringComparison.OrdinalIgnoreCase); if (index > -1) { // 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); } @@ -1808,21 +1853,6 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea } } - PSLanguageMode? originalLanguageMode = null; - if (_context.UseFullLanguageModeInDebugger && - (_context.LanguageMode != PSLanguageMode.FullLanguage)) - { - originalLanguageMode = _context.LanguageMode; - _context.LanguageMode = PSLanguageMode.FullLanguage; - } - else if (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() == - System.Management.Automation.Security.SystemEnforcementMode.Enforce) - { - // If there is a system lockdown in place, enforce it - originalLanguageMode = _context.LanguageMode; - _context.LanguageMode = PSLanguageMode.ConstrainedLanguage; - } - RunspaceAvailability previousAvailability = _context.CurrentRunspace.RunspaceAvailability; _context.CurrentRunspace.UpdateRunspaceAvailability( @@ -1883,12 +1913,14 @@ private void OnDebuggerStop(InvocationInfo invocationInfo, List brea DebuggerStopEventArgs oldArgs; _debuggerStopEventArgs.TryPop(out oldArgs); + _context.QuestionMarkVariableValue = oldQuestionMarkVariableValue; + _inBreakpoint = false; } } /// - /// Resumes execution after a breakpoint/step event has been handled + /// Resumes execution after a breakpoint/step event has been handled. /// private void ResumeExecution(DebuggerResumeAction action) { @@ -1916,6 +1948,7 @@ private void ResumeExecution(DebuggerResumeAction action) // breakpoints in the current frame, but otherwise just go.) goto case DebuggerResumeAction.Continue; } + break; case DebuggerResumeAction.StepOver: @@ -1935,7 +1968,7 @@ private void ResumeExecution(DebuggerResumeAction action) throw new TerminateException(); default: - Debug.Assert(false, "Received an unknown action: " + action); + Debug.Fail("Received an unknown action: " + action); break; } } @@ -1951,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) @@ -1962,6 +1992,7 @@ private bool WaitForDebugStopSubscriber() Diagnostics.Assert(false, "The _preserveDebugStop event handle should always be in the signaled state at this point."); return false; } + _preserveDebugStopEvent.Reset(); // Wait indefinitely for a signal event. @@ -1988,46 +2019,53 @@ 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.Add(breakpoint); + + AddPendingBreakpoint(breakpoint); } + boundBreakpoints.Clear(); } private void SetPendingBreakpoints(FunctionContext functionContext) { - if (!_pendingBreakpoints.Any()) - return; - - var newPendingBreakpoints = new List(); 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."); } + Diagnostics.Assert(tuple.Item1 == functionContext._boundBreakpoints, "What's up?"); - foreach (var breakpoint in _pendingBreakpoints) + foreach ((int breakpointId, LineBreakpoint breakpoint) in breakpoints) { bool bound = false; if (breakpoint.TrySetBreakpoint(currentScriptFile, functionContext)) @@ -2038,22 +2076,32 @@ private void SetPendingBreakpoints(FunctionContext functionContext) } bound = true; - tuple.Item1.Add(breakpoint); + + if (tuple.Item1.TryGetValue(breakpoint.SequencePointIndex, out var list)) + { + list.Add(breakpoint); + } + else + { + tuple.Item1.Add(breakpoint.SequencePointIndex, new List { breakpoint }); + } // We need to keep track of any breakpoints that are bound in each script because they may // need to be rebound if the script changes. var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2; - Diagnostics.Assert(boundBreakpoints.IndexOf(breakpoint) < 0, "Don't add more than once."); - boundBreakpoints.Add(breakpoint); + boundBreakpoints[breakpoint.Id] = breakpoint; } - if (!bound) + if (bound) { - newPendingBreakpoints.Add(breakpoint); + breakpoints.TryRemove(breakpointId, out _); } } - _pendingBreakpoints = 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) @@ -2089,11 +2137,22 @@ private enum InternalDebugMode /// Sets the internal Execution context debug mode given the /// current DebugMode setting. /// - /// Internal debug mode + /// Internal debug mode. private void SetInternalDebugMode(InternalDebugMode mode) { lock (_syncObject) { + // Disable script debugger when in system lock down mode + if (IsSystemLockedDown) + { + if (_context._debuggingMode != (int)InternalDebugMode.Disabled) + { + _context._debuggingMode = (int)InternalDebugMode.Disabled; + } + + return; + } + switch (mode) { case InternalDebugMode.InPushedStop: @@ -2120,6 +2179,45 @@ private bool CanEnableDebugger } } + private bool CanDisableDebugger + { + get + { + // The debugger can be disabled if there are no breakpoints + // left and if we are not currently stepping in the debugger. + return _idToBreakpoint.IsEmpty && + _currentDebuggerAction != DebuggerResumeAction.StepInto && + _currentDebuggerAction != DebuggerResumeAction.StepOver && + _currentDebuggerAction != DebuggerResumeAction.StepOut; + } + } + + private bool IsSystemLockedDown + { + get + { + if (_isSystemLockedDown == null) + { + lock (_syncObject) + { + _isSystemLockedDown ??= (System.Management.Automation.Security.SystemPolicy.GetSystemLockdownPolicy() == + System.Management.Automation.Security.SystemEnforcementMode.Enforce); + } + } + + return _isSystemLockedDown.Value; + } + } + + private void CheckForBreakpointSupport() + { + if (IsSystemLockedDown) + { + // Local script debugging is not supported in locked down mode + throw new PSNotSupportedException(); + } + } + #region Enable debug stepping [Flags] @@ -2162,14 +2260,7 @@ private void EnableDebuggerStepping(EnableNestedType nestedType) ResumeExecution(DebuggerResumeAction.StepInto); } } -#if !CORECLR - // Workflow Not Supported on OneCore PS - // Look for any running workflow jobs and set to step mode. - if ((nestedType & EnableNestedType.NestedJob) == EnableNestedType.NestedJob) - { - EnableRunningWorkflowJobsForStepping(); - } -#endif + // Look for any runspaces with debuggers and set to setp mode. if ((nestedType & EnableNestedType.NestedRunspace) == EnableNestedType.NestedRunspace) { @@ -2202,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); } @@ -2215,7 +2306,7 @@ private void RestoreInternalDebugMode() /// /// Set ScriptDebugger action. /// - /// DebuggerResumeAction + /// DebuggerResumeAction. public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { throw new PSNotSupportedException( @@ -2223,9 +2314,9 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) } /// - /// GetDebuggerStopped + /// GetDebuggerStopped. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public override DebuggerStopEventArgs GetDebuggerStopArgs() { DebuggerStopEventArgs rtnArgs; @@ -2238,21 +2329,21 @@ public override DebuggerStopEventArgs GetDebuggerStopArgs() } /// - /// ProcessCommand + /// ProcessCommand. /// - /// PowerShell command - /// Output - /// DebuggerCommandResults + /// PowerShell command. + /// Output. + /// DebuggerCommandResults. public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { if (command == null) { - throw new PSArgumentNullException("command"); + throw new PSArgumentNullException(nameof(command)); } if (output == null) { - throw new PSArgumentNullException("output"); + throw new PSArgumentNullException(nameof(output)); } if (!DebuggerStopped) @@ -2277,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, @@ -2296,6 +2386,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC { _psDebuggerCommand.SetIsNested(true); } + _psDebuggerCommand.Runspace = localRunspace; _psDebuggerCommand.Commands = command; foreach (var cmd in _psDebuggerCommand.Commands.Commands) @@ -2336,7 +2427,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC } /// - /// StopProcessCommand + /// StopProcessCommand. /// public override void StopProcessCommand() { @@ -2349,27 +2440,33 @@ public override void StopProcessCommand() } PowerShell ps = _psDebuggerCommand; - if (ps != null) - { - ps.BeginStop(null, null); - } + ps?.BeginStop(null, null); } /// - /// Set debug mode + /// Set debug mode. /// /// public override void SetDebugMode(DebugModes mode) { lock (_syncObject) { + // Restrict local script debugger mode when in system lock down. + // DebugModes enum flags provide a combination of values. To disable local script debugging + // we have to disallow 'LocalScript' and 'Default' flags and only allow 'None' or 'RemoteScript' + // flags exclusively. This allows only no debugging 'None' or remote debugging 'RemoteScript'. + if (IsSystemLockedDown && (mode != DebugModes.None) && (mode != DebugModes.RemoteScript)) + { + mode = DebugModes.RemoteScript; + } + base.SetDebugMode(mode); if (!CanEnableDebugger) { 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); @@ -2380,7 +2477,7 @@ public override void SetDebugMode(DebugModes mode) /// /// Returns current call stack. /// - /// IEnumerable of CallStackFrame objects + /// IEnumerable of CallStackFrame objects. public override IEnumerable GetCallStack() { CallStackInfo[] callStack = _callStack.ToArray(); @@ -2406,43 +2503,6 @@ public override IEnumerable GetCallStack() } } - /// - /// SetBreakpoints - /// - /// - public override void SetBreakpoints(IEnumerable breakpoints) - { - if (breakpoints == null) - { - throw new PSArgumentNullException("breakpoints"); - } - - foreach (var breakpoint in breakpoints) - { - if (_idToBreakpoint.ContainsKey(breakpoint.Id)) { continue; } - - LineBreakpoint lineBp = breakpoint as LineBreakpoint; - if (lineBp != null) - { - AddLineBreakpoint(lineBp); - continue; - } - - CommandBreakpoint cmdBp = breakpoint as CommandBreakpoint; - if (cmdBp != null) - { - AddCommandBreakpoint(cmdBp); - continue; - } - - VariableBreakpoint variableBp = breakpoint as VariableBreakpoint; - if (variableBp != null) - { - AddVariableBreakpoint(variableBp); - } - } - } - /// /// True when debugger is active with breakpoints. /// @@ -2467,7 +2527,7 @@ public override void ResetCommandProcessorSource() /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. public override void SetDebuggerStepMode(bool enabled) { if (enabled) @@ -2480,12 +2540,68 @@ public override void SetDebuggerStepMode(bool enabled) } } + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + internal override void Break(object triggerObject = null) + { + if (!IsDebugHandlerSubscribed && + (UnhandledBreakpointMode == UnhandledBreakpointProcessingMode.Ignore)) + { + // No debugger attached and runspace debugging is not enabled. Enable runspace debugging here + // so that this command is effective. + UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait; + } + + // Store the triggerObject so that we can add it to PSDebugContext + TriggerObject = triggerObject; + + // Set debugger to step mode so that a break can occur. + SetDebuggerStepMode(true); + + // If the debugger is enabled and we are not in a breakpoint, trigger an immediate break in the current location + if (_context._debuggingMode > 0) + { + using (IEnumerator enumerator = GetCallStack().GetEnumerator()) + { + if (enumerator.MoveNext()) + { + OnSequencePointHit(enumerator.Current.FunctionContext); + } + } + } + } + + /// + /// 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. /// - /// Command string - /// output + /// Command string. + /// Output. /// DebuggerCommand containing information on whether and how the command was processed. internal override DebuggerCommand InternalProcessCommand(string command, IList output) { @@ -2508,9 +2624,9 @@ internal override DebuggerCommand InternalProcessCommand(string command, IList

- /// Current source line - /// Output collection - /// True if source listed successfully + /// Current source line. + /// Output collection. + /// True if source listed successfully. internal override bool InternalProcessListCommand(int lineNum, IList output) { if (!DebuggerStopped || (_currentInvocationInfo == null)) { return false; } @@ -2543,7 +2659,7 @@ internal override bool InternalProcessListCommand(int lineNum, IList o } ///

- /// IsRemote + /// IsRemote. /// internal override bool IsRemote { @@ -2563,7 +2679,7 @@ internal override bool IsRemote /// Array of stack frame objects of active debugger if any, /// otherwise null. ///
- /// CallStackFrame[] + /// CallStackFrame[]. internal override CallStackFrame[] GetActiveDebuggerCallStack() { Debugger activeDebugger; @@ -2588,6 +2704,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode { return (_preserveUnhandledDebugStopEvent) ? UnhandledBreakpointProcessingMode.Wait : UnhandledBreakpointProcessingMode.Ignore; } + set { switch (value) @@ -2596,26 +2713,260 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode _preserveUnhandledDebugStopEvent = true; break; - case UnhandledBreakpointProcessingMode.Ignore: - _preserveUnhandledDebugStopEvent = false; - ReleaseSavedDebugStop(); - break; - } + case UnhandledBreakpointProcessingMode.Ignore: + _preserveUnhandledDebugStopEvent = false; + ReleaseSavedDebugStop(); + break; + } + } + } + + #region Breakpoints + + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// The breakpoints to set. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) + { + if (runspaceId.HasValue) + { + GetRunspaceDebugger(runspaceId.Value).SetBreakpoints(breakpoints); + return; + } + + foreach (Breakpoint bp in breakpoints) + { + switch (bp) + { + case CommandBreakpoint commandBreakpoint: + AddCommandBreakpoint(commandBreakpoint); + continue; + + case LineBreakpoint lineBreakpoint: + AddLineBreakpoint(lineBreakpoint); + continue; + + case VariableBreakpoint variableBreakpoint: + AddVariableBreakpoint(variableBreakpoint); + continue; + } + } + } + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).GetBreakpoint(id); + } + + _idToBreakpoint.TryGetValue(id, out Breakpoint breakpoint); + return breakpoint; + } + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override List GetBreakpoints(int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).GetBreakpoints(); + } + + return (from bp in _idToBreakpoint.Values orderby bp.Id select bp).ToList(); + } + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).SetCommandBreakpoint(command, action, path); + } + + Diagnostics.Assert(!string.IsNullOrEmpty(command), "Caller to verify command is not null or empty."); + + WildcardPattern pattern = WildcardPattern.Get(command, WildcardOptions.Compiled | WildcardOptions.IgnoreCase); + + CheckForBreakpointSupport(); + return AddCommandBreakpoint(new CommandBreakpoint(path, pattern, command, action)); + } + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A LineBreakpoint + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).SetLineBreakpoint(path, line, column, action); + } + + Diagnostics.Assert(path != null, "Caller to verify path is not null."); + Diagnostics.Assert(line > 0, "Caller to verify line is greater than 0."); + + CheckForBreakpointSupport(); + return AddLineBreakpoint(new LineBreakpoint(path, line, column, action)); + } + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A VariableBreakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).SetVariableBreakpoint(variableName, accessMode, action, path); + } + + Diagnostics.Assert(!string.IsNullOrEmpty(variableName), "Caller to verify variableName is not null or empty."); + + CheckForBreakpointSupport(); + return AddVariableBreakpoint(new VariableBreakpoint(path, variableName, accessMode, action)); + } + + /// + /// This is the implementation of the Remove-PSBreakpoint cmdlet. + /// + /// 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. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).RemoveBreakpoint(breakpoint); + } + + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.Remove(breakpoint.Id, out _)) + { + breakpoint.RemoveSelf(this); + + if (CanDisableDebugger) + { + SetInternalDebugMode(InternalDebugMode.Disabled); + } + + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Removed, _idToBreakpoint.Count)); + + return true; + } + + return false; + } + + /// + /// This is the implementation of the Enable-PSBreakpoint cmdlet. + /// + /// 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. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).EnableBreakpoint(breakpoint); + } + + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _)) + { + breakpoint.SetEnabled(true); + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Enabled, _idToBreakpoint.Count)); + + return breakpoint; + } + + return null; + } + + /// + /// This is the implementation of the Disable-PSBreakpoint cmdlet. + /// + /// 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. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + if (runspaceId.HasValue) + { + return GetRunspaceDebugger(runspaceId.Value).DisableBreakpoint(breakpoint); + } + + Diagnostics.Assert(breakpoint != null, "Caller to verify the breakpoint is not null."); + + if (_idToBreakpoint.TryGetValue(breakpoint.Id, out _)) + { + breakpoint.SetEnabled(false); + OnBreakpointUpdated(new BreakpointUpdatedEventArgs(breakpoint, BreakpointUpdateType.Disabled, _idToBreakpoint.Count)); + + return breakpoint; + } + + return null; + } + + private static Debugger GetRunspaceDebugger(int runspaceId) + { + if (!Runspace.RunspaceDictionary.TryGetValue(runspaceId, out WeakReference wr)) + { + throw new PSArgumentException(string.Format(DebuggerStrings.InvalidRunspaceId, runspaceId)); + } + + if (!wr.TryGetTarget(out Runspace rs)) + { + throw new PSArgumentException(DebuggerStrings.UnableToGetRunspace); } + + return rs.Debugger; } + #endregion Breakpoints + #region Job Debugging /// /// Sets up debugger to debug provided job or its child jobs. /// /// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal override void DebugJob(Job job) + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal override void DebugJob(Job job, bool breakAll) { - if (job == null) { throw new PSArgumentNullException("job"); } + if (job == null) { throw new PSArgumentNullException(nameof(job)); } lock (_syncObject) { @@ -2627,13 +2978,13 @@ internal override void DebugJob(Job job) // If a debuggable job was passed in then add it to the // job running list. - bool jobsAdded = TryAddDebugJob(job); + bool jobsAdded = TryAddDebugJob(job, breakAll); if (!jobsAdded) { // Otherwise treat as parent Job and iterate over child jobs. foreach (Job childJob in job.ChildJobs) { - if (TryAddDebugJob(childJob) && !jobsAdded) + if (TryAddDebugJob(childJob, breakAll) && !jobsAdded) { jobsAdded = true; } @@ -2646,7 +2997,7 @@ internal override void DebugJob(Job job) } } - private bool TryAddDebugJob(Job job) + private bool TryAddDebugJob(Job job, bool breakAll) { IJobDebugger debuggableJob = job as IJobDebugger; if ((debuggableJob != null) && (debuggableJob.Debugger != null) && @@ -2659,7 +3010,7 @@ private bool TryAddDebugJob(Job job) SetDebugJobAsync(debuggableJob, false); AddToJobRunningList( new PSJobStartEventArgs(job, debuggableJob.Debugger, false), - DebuggerResumeAction.StepInto); + breakAll ? DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue); // Raise debug stop event if job is already in stopped state. if (jobDebugAlreadyStopped) @@ -2685,11 +3036,11 @@ private bool TryAddDebugJob(Job job) /// Removes job from debugger job list and pops its /// debugger from the active debugger stack. ///
- /// Job + /// Job. internal override void StopDebugJob(Job job) { // Parameter validation. - if (job == null) { throw new PSArgumentNullException("job"); } + if (job == null) { throw new PSArgumentNullException(nameof(job)); } SetInternalDebugMode(InternalDebugMode.Disabled); @@ -2708,8 +3059,8 @@ internal override void StopDebugJob(Job job) /// /// Helper method to set a IJobDebugger job CanDebug property. /// - /// IJobDebugger - /// Boolean + /// IJobDebugger. + /// Boolean. internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync) { if (debuggableJob != null) @@ -2725,12 +3076,18 @@ internal static void SetDebugJobAsync(IJobDebugger debuggableJob, bool isAsync) /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug - internal override void DebugRunspace(Runspace runspace) + /// + /// Runspace to debug. + /// + /// + /// When true, this command will invoke a BreakAll when the debugger is + /// first attached. + /// + internal override void DebugRunspace(Runspace runspace, bool breakAll) { if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } if (runspace.RunspaceStateInfo.State != RunspaceState.Opened) @@ -2761,7 +3118,7 @@ internal override void DebugRunspace(Runspace runspace) AddToRunningRunspaceList(new PSStandaloneMonitorRunspaceInfo(runspace)); - if (!runspace.Debugger.InBreakpoint) + if (!runspace.Debugger.InBreakpoint && breakAll) { EnableDebuggerStepping(EnableNestedType.NestedRunspace); } @@ -2770,10 +3127,10 @@ internal override void DebugRunspace(Runspace runspace) /// /// Removes the provided Runspace from the nested "active" debugger state. /// - /// Runspace + /// Runspace. internal override void StopDebugRunspace(Runspace runspace) { - if (runspace == null) { throw new PSArgumentNullException("runspace"); } + if (runspace == null) { throw new PSArgumentNullException(nameof(runspace)); } SetInternalDebugMode(InternalDebugMode.Disabled); @@ -2791,7 +3148,7 @@ internal override void StopDebugRunspace(Runspace runspace) /// The queue will then raise the StartRunspaceDebugProcessing events for each runspace to allow /// a host script debugger implementation to provide an active debugging session. /// - /// Runspace to debug + /// Runspace to debug. internal override void QueueRunspaceForDebug(Runspace runspace) { runspace.StateChanged += RunspaceStateChangedHandler; @@ -2877,62 +3234,6 @@ private void RunspaceAvailabilityChangedHandler(object sender, RunspaceAvailabil #region Job debugger integration -#if !CORECLR // Workflow Not Supported on OneCore PS - private void SubscribeToEngineWFJobStartEvent() - { - Diagnostics.Assert(_context.Events != null, "Event manager cannot be null."); - - _context.Events.SubscribeEvent( - source: null, - eventName: null, - sourceIdentifier: PSEngineEvent.WorkflowJobStartEvent, - data: null, - handlerDelegate: HandleJobStartEvent, - supportEvent: true, - forwardEvent: false); - - _wfStartEventSubscribed = true; - } - - private void UnsubscribeFromEngineWFJobStartEvent() - { - PSEventManager eventManager = _context.Events; - Diagnostics.Assert(eventManager != null, "Event manager cannot be null."); - - foreach (var subscriber in eventManager.GetEventSubscribers(PSEngineEvent.WorkflowJobStartEvent)) - { - eventManager.UnsubscribeEvent(subscriber); - } - - _wfStartEventSubscribed = false; - } - - private void HandleJobStartEvent(object sender, PSEventArgs args) - { - Diagnostics.Assert(args.SourceArgs.Length == 1, "WF Job Started engine event SourceArgs should have a single element."); - PSJobStartEventArgs jobStartedArgs = args.SourceArgs[0] as PSJobStartEventArgs; - Diagnostics.Assert(jobStartedArgs != null, "WF Job Started engine event args cannot be null."); - Diagnostics.Assert(jobStartedArgs.Job != null, "WF Job to start cannot be null."); - Diagnostics.Assert(jobStartedArgs.Debugger != null, "WF Job Started Debugger object cannot be null."); - - if (!(jobStartedArgs.Debugger.GetType().FullName.Equals("Microsoft.PowerShell.Workflow.PSWorkflowDebugger", - StringComparison.OrdinalIgnoreCase))) - { - // Check to ensure only PS workflow debuggers can be passed in. - throw new PSInvalidOperationException(); - } - - // At this point the script debugger stack frame must be the Workflow execution function which is DebuggerHidden. - // The internal debugger resume action is set to StepOut, *if* the user selected StepIn, so as to skip this frame - // since debugging the workflow execution function is turned off. We look at the previous resume action to see - // what the user intended. If it is StepIn then we start the workflow job debugger in step mode. - Diagnostics.Assert(_callStack.LastFunctionContext()._debuggerHidden, "Current stack frame must be WF function DebuggerHidden"); - DebuggerResumeAction startAction = (_previousDebuggerAction == DebuggerResumeAction.StepInto) ? - DebuggerResumeAction.StepInto : DebuggerResumeAction.Continue; - AddToJobRunningList(jobStartedArgs, startAction); - } -#endif - private void AddToJobRunningList(PSJobStartEventArgs jobArgs, DebuggerResumeAction startAction) { bool newJob = false; @@ -2958,7 +3259,6 @@ private void AddToJobRunningList(PSJobStartEventArgs jobArgs, DebuggerResumeActi _runningJobs.Add(jobArgs.Job.InstanceId, jobArgs); jobArgs.Debugger.DebuggerStop += HandleMonitorRunningJobsDebuggerStop; - jobArgs.Debugger.BreakpointUpdated += HandleBreakpointUpdated; newJob = true; } @@ -2971,8 +3271,7 @@ private void AddToJobRunningList(PSJobStartEventArgs jobArgs, DebuggerResumeActi _idToBreakpoint.Values.ToArray(), startAction, _context.EngineHostInterface.ExternalHost, - _context.SessionState.Path.CurrentLocation, - GetFunctionToSourceMap()); + _context.SessionState.Path.CurrentLocation); } else { @@ -2991,6 +3290,7 @@ private void SetRunningJobListToStep(bool enableStepping) { runningJobs = _runningJobs.Values.ToArray(); } + foreach (var item in runningJobs) { try @@ -3008,72 +3308,18 @@ private void SetRunspaceListToStep(bool enableStepping) { runspaceList = _runningRunspaces.Values.ToArray(); } + foreach (var item in runspaceList) { try { Debugger nestedDebugger = item.NestedDebugger; - - if (nestedDebugger != null) - { - nestedDebugger.SetDebuggerStepMode(enableStepping); - } + nestedDebugger?.SetDebuggerStepMode(enableStepping); } catch (PSNotImplementedException) { } } } - private Dictionary GetFunctionToSourceMap() - { - Dictionary fnToSource = new Dictionary(); - - // Get workflow function source information for workflow debugger. - Collection items = _context.SessionState.InvokeProvider.Item.Get("function:\\*"); - foreach (var item in items) - { - var funcItem = item.BaseObject as WorkflowInfo; - if ((funcItem != null) && - (!string.IsNullOrEmpty(funcItem.Name))) - { - if ((funcItem.Module != null) && (funcItem.Module.Path != null)) - { - string scriptFile = funcItem.Module.Path; - string scriptSource = GetFunctionSource(scriptFile); - if (scriptSource != null) - { - fnToSource.Add( - funcItem.Name, - new DebugSource( - scriptSource, - scriptFile, - funcItem.XamlDefinition)); - } - } - } - } - - return fnToSource; - } - - private string GetFunctionSource( - string scriptFile) - { - if (System.IO.File.Exists(scriptFile)) - { - try - { - return System.IO.File.ReadAllText(scriptFile); - } - catch (ArgumentException) { } - catch (System.IO.IOException) { } - catch (UnauthorizedAccessException) { } - catch (NotSupportedException) { } - catch (System.Security.SecurityException) { } - } - - return null; - } - private void RemoveFromRunningJobList(Job job) { job.StateChanged -= HandleJobStateChanged; @@ -3085,7 +3331,6 @@ private void RemoveFromRunningJobList(Job job) if (_runningJobs.TryGetValue(job.InstanceId, out jobArgs)) { jobArgs.Debugger.DebuggerStop -= HandleMonitorRunningJobsDebuggerStop; - jobArgs.Debugger.BreakpointUpdated -= HandleBreakpointUpdated; _runningJobs.Remove(job.InstanceId); } } @@ -3109,9 +3354,6 @@ private void RemoveFromRunningJobList(Job job) private void ClearRunningJobList() { -#if !CORECLR // Workflow Not Supported on OneCore PS - UnsubscribeFromEngineWFJobStartEvent(); -#endif PSJobStartEventArgs[] runningJobs = null; lock (_syncObject) { @@ -3264,12 +3506,6 @@ private void HandleMonitorRunningJobsDebuggerStop(object sender, DebuggerStopEve { if (!IsJobDebuggingMode()) { -#if !CORECLR // Workflow Not Supported on OneCore PS - // Remove workflow job callback. - UnsubscribeFromEngineWFJobStartEvent(); - // Write warning to user. - WriteWorkflowDebugNotSupportedError(); -#endif // Ignore job debugger stop. args.ResumeAction = DebuggerResumeAction.Continue; return; @@ -3312,28 +3548,6 @@ private bool IsJobDebuggingMode() (((DebugMode & DebugModes.RemoteScript) == DebugModes.RemoteScript) && !IsLocalSession)); } - private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) - { - switch (e.UpdateType) - { - case BreakpointUpdateType.Set: - AddNewBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Removed: - RemoveBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Enabled: - EnableBreakpoint(e.Breakpoint); - break; - - case BreakpointUpdateType.Disabled: - DisableBreakpoint(e.Breakpoint); - break; - } - } - private bool IsRunningWFJobsDebugger(Debugger debugger) { lock (_syncObject) @@ -3402,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(); } @@ -3421,18 +3635,6 @@ private DebuggerCommandResults ProcessCommandForActiveDebugger(PSCommand command _lastActiveDebuggerAction = dbgCommand.ResumeAction.Value; return new DebuggerCommandResults(dbgCommand.ResumeAction, true); } - - // If active debugger is Workflow debugger then process command here (for "list" and "help"). - if (activeDebugger.GetType().FullName.Equals("Microsoft.PowerShell.Workflow.PSWorkflowDebugger", StringComparison.OrdinalIgnoreCase)) - { - DebuggerCommand results = _commandProcessor.ProcessCommand(null, commandText, stopArgs.InvocationInfo, output); - - if ((results != null) && - results.ExecutedByDebugger) - { - return new DebuggerCommandResults(results.ResumeAction, true); - } - } } return activeDebugger.ProcessCommand(command, output); @@ -3459,71 +3661,6 @@ private bool StopCommandForActiveDebugger() return false; } - private void WriteWorkflowDebugNotSupportedError() - { - if (!_writeWFErrorOnce) - { - var host = _context.EngineHostInterface.ExternalHost; - if (host != null && host.UI != null) - { - host.UI.WriteErrorLine(DebuggerStrings.WorkflowDebuggingNotSupported); - } - - _writeWFErrorOnce = true; - } - } - -#if !CORECLR // Workflow Not Supported on OneCore PS - private void EnableRunningWorkflowJobsForStepping() - { - // Make sure workflow job start callback is set to pick - // up any newly starting jobs. - if (!_wfStartEventSubscribed) - { - lock (_syncObject) - { - if (!_wfStartEventSubscribed) - { - SubscribeToEngineWFJobStartEvent(); - } - } - } - - // Get list of workflow jobs - Collection jobs; - using (PowerShell ps = PowerShell.Create()) - { - ps.Commands.Clear(); - ps.AddScript(@"Get-Job | Where-Object {$_.PSJobTypeName -eq 'PSWorkflowJob'}"); - jobs = ps.Invoke(); - } - - // Add debuggable workflow jobs to running Job list - // and set debugger to step mode. - foreach (var parentJob in jobs) - { - if (parentJob != null) - { - foreach (var childJob in parentJob.ChildJobs) - { - IJobDebugger debuggableJob = childJob as IJobDebugger; - if (debuggableJob != null) - { - AddToJobRunningList( - new PSJobStartEventArgs( - childJob, - debuggableJob.Debugger, - debuggableJob.IsAsync), - DebuggerResumeAction.StepInto); - } - } - } - } - - // Ensure existing running jobs in list are also set to step mode. - SetRunningJobListToStep(true); - } -#endif #endregion #region Runspace debugger integration @@ -3535,7 +3672,7 @@ internal override void StartMonitoringRunspace(PSMonitorRunspaceInfo runspaceInf if ((runspaceInfo.Runspace.Debugger != null) && runspaceInfo.Runspace.Debugger.Equals(this)) { - Debug.Assert(false, "Nested debugger cannot be the root debugger."); + Debug.Fail("Nested debugger cannot be the root debugger."); return; } @@ -3605,11 +3742,10 @@ 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; - nestedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; nestedDebugger.Dispose(); // If current active debugger, then pop. @@ -3693,42 +3829,9 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent } // Get nested debugger runspace info. - NestedRunspaceDebugger nestedDebugger = senderDebugger as NestedRunspaceDebugger; - if (nestedDebugger == null) { return; } - PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType; - - // If this is a workflow debugger then ensure that there is a current active - // debugger that is the associated job debugger for this inline script WF runspace. - if (runspaceType == PSMonitorRunspaceType.WorkflowInlineScript) - { - bool needToPushAssociatedWFDebugger = true; - if (_activeDebuggers.TryPeek(out activeDebugger)) - { - needToPushAssociatedWFDebugger = (activeDebugger.InstanceId != nestedDebugger.ParentDebuggerId); - if (needToPushAssociatedWFDebugger) - { - // Pop incorrect active debugger. - PopActiveDebugger(); - } - } - - if (needToPushAssociatedWFDebugger) - { - PSJobStartEventArgs wfJobArgs = null; - lock (_syncObject) - { - _runningJobs.TryGetValue(nestedDebugger.ParentDebuggerId, out wfJobArgs); - } - - if (wfJobArgs == null) - { - Diagnostics.Assert(false, "We should never get a WF job InlineScript debugger without an associated WF parent job."); - return; - } + if (senderDebugger is not NestedRunspaceDebugger nestedDebugger) { return; } - PushActiveDebugger(wfJobArgs.Debugger, _jobCallStackOffset); - } - } + PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType; // Fix up invocation info script extents for embedded nested debuggers where the script source is // from the parent. @@ -3811,7 +3914,6 @@ private bool SetUpDebuggerOnRunspace(Runspace runspace) runspaceInfo.NestedDebugger = nestedDebugger; nestedDebugger.DebuggerStop += HandleMonitorRunningRSDebuggerStop; - nestedDebugger.BreakpointUpdated += HandleBreakpointUpdated; if (((_lastActiveDebuggerAction == DebuggerResumeAction.StepInto) || (_currentDebuggerAction == DebuggerResumeAction.StepInto)) && !nestedDebugger.IsActive) @@ -3839,7 +3941,7 @@ private bool SetUpDebuggerOnRunspace(Runspace runspace) private void StartRunspaceForDebugQueueProcessing() { - Int32 startThread = Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 1, 0); + int startThread = Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 1, 0); if (startThread == 0) { @@ -3881,7 +3983,7 @@ private void DebuggerQueueThreadProc() Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 0, 1); - if (_runspaceDebugQueue.Value.Count > 0) + if (!_runspaceDebugQueue.Value.IsEmpty) { StartRunspaceForDebugQueueProcessing(); } @@ -3891,7 +3993,7 @@ private void ProcessRunspaceDebugInternally(Runspace runspace) { WaitForReadyDebug(); - DebugRunspace(runspace); + DebugRunspace(runspace, breakAll: true); // Block this event thread until debugging has ended. WaitForDebugComplete(); @@ -3949,6 +4051,7 @@ private void WaitForDebugComplete() { _runspaceDebugCompleteEvent.Reset(); } + _runspaceDebugCompleteEvent.Wait(); } @@ -3957,19 +4060,17 @@ private void WaitForDebugComplete() #region IDisposable /// - /// Dispose + /// Dispose. /// public void Dispose() { -#if !CORECLR // Workflow Not Supported on OneCore PS - UnsubscribeFromEngineWFJobStartEvent(); -#endif // Ensure all job event handlers are removed. PSJobStartEventArgs[] runningJobs; lock (_syncObject) { runningJobs = _runningJobs.Values.ToArray(); } + foreach (var item in runningJobs) { Job job = item.Job; @@ -3988,6 +4089,12 @@ public void Dispose() _preserveDebugStopEvent.Dispose(); _preserveDebugStopEvent = null; } + + if (_runspaceDebugCompleteEvent != null) + { + _runspaceDebugCompleteEvent.Dispose(); + _runspaceDebugCompleteEvent = null; + } } #endregion @@ -4020,12 +4127,12 @@ internal void DisableTracing() _context.IgnoreScriptDebug = _savedIgnoreScriptDebug; _context.PSDebugTraceLevel = 0; _context.PSDebugTraceStep = false; - if (!_idToBreakpoint.Any()) + if (CanDisableDebugger) { - // Only disable debug mode if there are no breakpoints. SetInternalDebugMode(InternalDebugMode.Disabled); } } + private bool _savedIgnoreScriptDebug = false; internal void Trace(string messageId, string resourceString, params object[] args) @@ -4033,7 +4140,7 @@ internal void Trace(string messageId, string resourceString, params object[] arg ActionPreference pref = ActionPreference.Continue; string message; - if (null == args || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -4042,7 +4149,8 @@ internal void Trace(string messageId, string resourceString, params object[] arg { message = StringUtil.Format(resourceString, args); } - if (String.IsNullOrEmpty(message)) + + if (string.IsNullOrEmpty(message)) { message = "Could not load text for msh script tracing message id '" + messageId + "'"; Diagnostics.Assert(false, message); @@ -4068,7 +4176,7 @@ internal void TraceLine(IScriptExtent extent) internal void TraceScriptFunctionEntry(FunctionContext functionContext) { var methodName = functionContext._functionName; - if (String.IsNullOrEmpty(functionContext._file)) + if (string.IsNullOrEmpty(functionContext._file)) { Trace("TraceEnteringFunction", ParserStrings.TraceEnteringFunction, methodName); } @@ -4095,13 +4203,14 @@ 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) { - valAsString = valAsString.Substring(0, msgLength) + "..."; + valAsString = valAsString.Substring(0, msgLength) + PSObjectHelper.Ellipsis; } + Trace("TraceVariableAssignment", ParserStrings.TraceVariableAssignment, varName, valAsString); } @@ -4130,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. @@ -4152,17 +4257,17 @@ public Guid ParentDebuggerId /// /// Creates an instance of NestedRunspaceDebugger. /// - /// Runspace - /// Runspace type - /// Debugger Id of parent - public NestedRunspaceDebugger( + /// Runspace. + /// Runspace type. + /// Debugger Id of parent. + protected NestedRunspaceDebugger( Runspace runspace, PSMonitorRunspaceType runspaceType, Guid parentDebuggerId) { if (runspace == null || runspace.Debugger == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } _runspace = runspace; @@ -4180,18 +4285,26 @@ public NestedRunspaceDebugger( #region Overrides + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) => + _wrappedDebugger.SetBreakpoints(breakpoints, runspaceId); + /// /// Process debugger or PowerShell command/script. /// - /// PowerShell command - /// Output collection - /// DebuggerCommandResults + /// PowerShell command. + /// Output collection. + /// DebuggerCommandResults. public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { if (_isDisposed) { return new DebuggerCommandResults(null, false); } // Preprocess debugger commands. - String cmd = command.Commands[0].CommandText.Trim(); + string cmd = command.Commands[0].CommandText.Trim(); if (cmd.Equals("prompt", StringComparison.OrdinalIgnoreCase)) { @@ -4217,9 +4330,87 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC } /// - /// SetDebuggerAction + /// Get a breakpoint by id. + /// + /// 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. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) => + _wrappedDebugger.GetBreakpoint(id, runspaceId); + + /// + /// Returns breakpoints on a runspace. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A list of breakpoints in a runspace. + public override List GetBreakpoints(int? runspaceId) => + _wrappedDebugger.GetBreakpoints(runspaceId); + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The command breakpoint that was set. + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path, runspaceId); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action, runspaceId); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.RemoveBreakpoint(breakpoint, runspaceId); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.EnableBreakpoint(breakpoint, runspaceId); + + /// + /// Disables a breakpoint in the debugger. /// - /// Debugger resume action + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.DisableBreakpoint(breakpoint, runspaceId); + + /// + /// SetDebuggerAction. + /// + /// Debugger resume action. public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { _wrappedDebugger.SetDebuggerAction(resumeAction); @@ -4237,7 +4428,7 @@ public override void StopProcessCommand() /// Returns current debugger stop event arguments if debugger is in /// debug stop state. Otherwise returns null. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public override DebuggerStopEventArgs GetDebuggerStopArgs() { return _wrappedDebugger.GetDebuggerStopArgs(); @@ -4246,7 +4437,7 @@ public override DebuggerStopEventArgs GetDebuggerStopArgs() /// /// Sets the debugger mode. /// - /// Debug mode + /// Debug mode. public override void SetDebugMode(DebugModes mode) { _wrappedDebugger.SetDebugMode(mode); @@ -4255,7 +4446,7 @@ public override void SetDebugMode(DebugModes mode) /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. public override void SetDebuggerStepMode(bool enabled) { _wrappedDebugger.SetDebuggerStepMode(enabled); @@ -4269,12 +4460,21 @@ public override bool IsActive get { return _wrappedDebugger.IsActive; } } + /// + /// Breaks into the debugger. + /// + /// The object that triggered the breakpoint, if there is one. + internal override void Break(object triggerObject = null) + { + _wrappedDebugger.Break(triggerObject); + } + #endregion #region IDisposable /// - /// Dispose + /// Dispose. /// public virtual void Dispose() { @@ -4312,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. @@ -4367,11 +4567,11 @@ 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, - /// such as with Workflow InlineScripts and script Invoke-Command cases. + /// cases where the debugged runspace is called inside a parent script, + /// such as with script Invoke-Command cases. /// /// - /// InvocationInfo + /// InvocationInfo. internal virtual InvocationInfo FixupInvocationInfo(InvocationInfo debugStopInvocationInfo) { // Default is no fix up. @@ -4406,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(); } } @@ -4441,9 +4638,9 @@ internal sealed class StandaloneRunspaceDebugger : NestedRunspaceDebugger #region Constructor /// - /// Constructor + /// Constructor. /// - /// Runspace + /// Runspace. public StandaloneRunspaceDebugger( Runspace runspace) : base(runspace, PSMonitorRunspaceType.Standalone, Guid.Empty) @@ -4489,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) @@ -4513,7 +4709,7 @@ private object DrainAndBlockRemoteOutput() return null; } - private void RestoreRemoteOutput(object runningCmd) + private static void RestoreRemoteOutput(object runningCmd) { if (runningCmd == null) { return; } @@ -4534,8 +4730,7 @@ private void RestoreRemoteOutput(object runningCmd) /// /// Wrapper class for runspace debugger where the runspace is being used in an - /// embedded scenario such as Workflow InlineScript or Invoke-Command command - /// inside script. + /// embedded scenario such as Invoke-Command command inside script. /// internal sealed class EmbeddedRunspaceDebugger : NestedRunspaceDebugger { @@ -4553,11 +4748,11 @@ internal sealed class EmbeddedRunspaceDebugger : NestedRunspaceDebugger /// /// Constructor for runspaces executing from script. /// - /// Runspace to debug - /// PowerShell command - /// Root debugger - /// Runspace to monitor type - /// Parent debugger Id + /// Runspace to debug. + /// PowerShell command. + /// Root debugger. + /// Runspace to monitor type. + /// Parent debugger Id. public EmbeddedRunspaceDebugger( Runspace runspace, PowerShell command, @@ -4568,7 +4763,7 @@ public EmbeddedRunspaceDebugger( { if (rootDebugger == null) { - throw new PSArgumentNullException("rootDebugger"); + throw new PSArgumentNullException(nameof(rootDebugger)); } _command = command; @@ -4586,7 +4781,7 @@ protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs new Collection(e.Breakpoints), e.ResumeAction); - Object remoteRunningCmd = null; + object remoteRunningCmd = null; try { // For remote debugging drain/block output channel. @@ -4643,11 +4838,11 @@ 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, - /// such as with Workflow InlineScripts and script Invoke-Command cases. + /// cases where the debugged runspace is called inside a parent script, + /// such as with script Invoke-Command cases. /// - /// Invocation information from debugger stop - /// InvocationInfo + /// Invocation information from debugger stop. + /// InvocationInfo. internal override InvocationInfo FixupInvocationInfo(InvocationInfo debugStopInvocationInfo) { if (debugStopInvocationInfo == null) { return null; } @@ -4685,7 +4880,7 @@ internal override InvocationInfo FixupInvocationInfo(InvocationInfo debugStopInv #region IDisposable /// - /// Dispose + /// Dispose. /// public override void Dispose() { @@ -4728,18 +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; } } @@ -4772,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); @@ -4781,10 +4976,10 @@ private string FixUpStatementExtent(int startColNum, string stateExtentText) return sb.ToString(); } - private Object DrainAndBlockRemoteOutput() + private object DrainAndBlockRemoteOutput() { // We only do this for remote runspaces. - if (!(_runspace is RemoteRunspace)) { return null; } + if (_runspace is not RemoteRunspace) { return null; } try { @@ -4811,7 +5006,7 @@ private Object DrainAndBlockRemoteOutput() return null; } - private void RestoreRemoteOutput(Object runningCmd) + private static void RestoreRemoteOutput(object runningCmd) { if (runningCmd == null) { return; } @@ -4823,10 +5018,7 @@ private void RestoreRemoteOutput(Object runningCmd) else { Pipeline pipelineCommand = runningCmd as Pipeline; - if (pipelineCommand != null) - { - pipelineCommand.ResumeIncomingData(); - } + pipelineCommand?.ResumeIncomingData(); } } @@ -4845,7 +5037,7 @@ public sealed class DebuggerCommandResults #region Properties /// - /// Resume action + /// Resume action. /// public DebuggerResumeAction? ResumeAction { @@ -4857,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 @@ -4871,10 +5059,10 @@ private DebuggerCommandResults() { } /// - /// Constructor + /// Constructor. /// - /// Resume action - /// True if evaluated by debugger + /// Resume action. + /// True if evaluated by debugger. public DebuggerCommandResults( DebuggerResumeAction? resumeAction, bool evaluatedByDebugger) @@ -4919,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; @@ -4939,24 +5127,24 @@ internal class DebuggerCommandProcessor private const string Crlf = "\x000D\x000A"; /// - /// Creates the table of debugger commands + /// Creates the table of debugger commands. /// public DebuggerCommandProcessor() { _commandTable = new Dictionary(StringComparer.OrdinalIgnoreCase); - _commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, true, false); - _commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, false, false); - _commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, true, false); - _commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, false, false); - _commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, false, false); - _commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, false, false); - _commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, false, true); - _commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, true, true); - _commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, false, true); + _commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, repeatOnEnter: true, executedByDebugger: false); + _commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, repeatOnEnter: false, executedByDebugger: false); + _commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, repeatOnEnter: true, executedByDebugger: false); + _commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, repeatOnEnter: false, executedByDebugger: false); + _commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, repeatOnEnter: false, executedByDebugger: false); + _commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, repeatOnEnter: false, executedByDebugger: false); + _commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, repeatOnEnter: false, executedByDebugger: true); + _commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, repeatOnEnter: true, executedByDebugger: true); + _commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, repeatOnEnter: false, executedByDebugger: true); } /// - /// Resets any state in the command processor + /// Resets any state in the command processor. /// public void Reset() { @@ -4973,7 +5161,7 @@ public DebuggerCommand ProcessCommand(PSHost host, string command, InvocationInf } /// - /// ProcessCommand + /// ProcessCommand. /// /// /// @@ -4984,14 +5172,15 @@ public DebuggerCommand ProcessCommand(PSHost host, string command, InvocationInf { DebuggerCommand dbgCommand = DoProcessCommand(host, command, invocationInfo, output); if (dbgCommand.ExecutedByDebugger || (dbgCommand.ResumeAction != null)) { _lastCommand = dbgCommand; } + return dbgCommand; } /// /// Process list command with provided line number. /// - /// Current InvocationInfo - /// Output + /// Current InvocationInfo. + /// Output. public void ProcessListCommand(InvocationInfo invocationInfo, IList output) { DoProcessCommand(null, "list", invocationInfo, output); @@ -5001,7 +5190,7 @@ public void ProcessListCommand(InvocationInfo invocationInfo, IList ou /// Looks up string command and if it is a debugger command returns the /// corresponding DebuggerCommand object. /// - /// String command + /// String command. /// DebuggerCommand or null. public DebuggerCommand ProcessBasicCommand(string command) { @@ -5014,6 +5203,7 @@ public DebuggerCommand ProcessBasicCommand(string command) if (_commandTable.TryGetValue(command, out debuggerCommand)) { if (debuggerCommand.ExecutedByDebugger || (debuggerCommand.ResumeAction != null)) { _lastCommand = debuggerCommand; } + return debuggerCommand; } @@ -5021,7 +5211,7 @@ public DebuggerCommand ProcessBasicCommand(string command) } /// - /// Helper for ProcessCommand + /// Helper for ProcessCommand. /// private DebuggerCommand DoProcessCommand(PSHost host, string command, InvocationInfo invocationInfo, IList output) { @@ -5034,8 +5224,10 @@ private DebuggerCommand DoProcessCommand(PSHost host, string command, Invocation { DisplayScript(host, output, invocationInfo, _lastLineDisplayed + 1, DefaultListLineCount); } + return _listCommand; } + command = _lastCommand.Command; } @@ -5069,36 +5261,36 @@ private DebuggerCommand DoProcessCommand(PSHost host, string command, Invocation } /// - /// Displays the help text for the debugger commands + /// Displays the help text for the debugger commands. /// - private void DisplayHelp(PSHost host, IList output) + private static void DisplayHelp(PSHost host, IList output) { - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.StepHelp, StepShortcut, StepCommand), host, output); WriteLine(StringUtil.Format(DebuggerStrings.StepOverHelp, StepOverShortcut, StepOverCommand), host, output); WriteLine(StringUtil.Format(DebuggerStrings.StepOutHelp, StepOutShortcut, StepOutCommand), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.ContinueHelp, ContinueShortcut, ContinueCommand), host, output); WriteLine(StringUtil.Format(DebuggerStrings.StopHelp, StopShortcut, StopCommand), host, output); WriteLine(StringUtil.Format(DebuggerStrings.DetachHelp, DetachShortcut, DetachCommand), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.GetStackTraceHelp, GetStackTraceShortcut), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.ListHelp, ListShortcut, ListCommand), host, output); WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp1), host, output); WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp2), host, output); WriteLine(StringUtil.Format(DebuggerStrings.AdditionalListHelp3), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.EnterHelp, StepCommand, StepOverCommand, ListCommand), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.HelpCommandHelp, HelpShortcut, HelpCommand), host, output); WriteLine("\n", host, output); WriteLine(StringUtil.Format(DebuggerStrings.PromptHelp), host, output); - WriteLine("", host, output); + WriteLine(string.Empty, host, output); } /// - /// Executes the list command + /// Executes the list command. /// private void DisplayScript(PSHost host, IList output, InvocationInfo invocationInfo, Match match) { @@ -5135,6 +5327,7 @@ private void DisplayScript(PSHost host, IList output, InvocationInfo i WriteErrorLine(StringUtil.Format(DebuggerStrings.BadStartFormat, _lines.Length), host, output); return; } + if (start <= 0 || start > _lines.Length) { WriteErrorLine(StringUtil.Format(DebuggerStrings.BadStartFormat, _lines.Length), host, output); @@ -5176,7 +5369,7 @@ private void DisplayScript(PSHost host, IList output, InvocationInfo i } /// - /// Executes the list command + /// Executes the list command. /// private void DisplayScript(PSHost host, IList output, InvocationInfo invocationInfo, int start, int count) { @@ -5185,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); @@ -5198,54 +5390,36 @@ 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))); } } /// - /// Class used to hold the output of the DebuggerCommandProcessor + /// Class used to hold the output of the DebuggerCommandProcessor. /// internal class DebuggerCommand { @@ -5267,7 +5441,7 @@ public DebuggerCommand(string command, DebuggerResumeAction? action, bool repeat /// /// When ResumeAction is null, this property indicates the command that the - /// host should pass to the PowerShell engine + /// host should pass to the PowerShell engine. /// public string Command { get; } @@ -5277,7 +5451,7 @@ public DebuggerCommand(string command, DebuggerResumeAction? action, bool repeat public bool RepeatOnEnter { get; } /// - /// If true, the command was executed by the debugger and the host should ignore the command + /// If true, the command was executed by the debugger and the host should ignore the command. /// public bool ExecutedByDebugger { get; } } @@ -5287,36 +5461,53 @@ public DebuggerCommand(string command, DebuggerResumeAction? action, bool repeat #region PSDebugContext class /// - /// This class exposes the information about the debugger that is available via $PSDebugContext + /// This class exposes the information about the debugger that is available via $PSDebugContext. /// public class PSDebugContext { /// - /// Constructor. + /// Initializes a new instance of the class. /// - /// InvocationInfo - /// Breakpoints + /// The invocation information for the current command. + /// The breakpoint(s) that caused the script to break in the debugger. public PSDebugContext(InvocationInfo invocationInfo, List breakpoints) + : this(invocationInfo, breakpoints, triggerObject: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The invocation information for the current command. + /// The breakpoint(s) that caused the script to break in the debugger. + /// The object that caused the script to break in the debugger. + public PSDebugContext(InvocationInfo invocationInfo, List breakpoints, object triggerObject) { if (breakpoints == null) { - throw new PSArgumentNullException("breakpoints"); + throw new PSArgumentNullException(nameof(breakpoints)); } this.InvocationInfo = invocationInfo; this.Breakpoints = breakpoints.ToArray(); + this.Trigger = triggerObject; } /// - /// InvocationInfo of the command currently being executed + /// 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; } } #endregion @@ -5329,24 +5520,24 @@ public PSDebugContext(InvocationInfo invocationInfo, List breakpoint public sealed class CallStackFrame { /// - /// Constructor + /// Constructor. /// - /// Invocation Info + /// Invocation Info. public CallStackFrame(InvocationInfo invocationInfo) : this(null, invocationInfo) { } /// - /// Constructor + /// Constructor. /// - /// Function context - /// Invocation Info + /// Function context. + /// Invocation Info. internal CallStackFrame(FunctionContext functionContext, InvocationInfo invocationInfo) { if (invocationInfo == null) { - throw new PSArgumentNullException("invocationInfo"); + throw new PSArgumentNullException(nameof(invocationInfo)); } if (functionContext != null) @@ -5366,7 +5557,7 @@ internal CallStackFrame(FunctionContext functionContext, InvocationInfo invocati } /// - /// File name of the current location, or null if the frame is not associated to a script + /// File name of the current location, or null if the frame is not associated to a script. /// public string ScriptName { @@ -5374,7 +5565,7 @@ public string ScriptName } /// - /// Line number of the current location, or 0 if the frame is not associated to a script + /// Line number of the current location, or 0 if the frame is not associated to a script. /// public int ScriptLineNumber { @@ -5382,15 +5573,15 @@ public int ScriptLineNumber } /// - /// The InvocationInfo of the command + /// 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. @@ -5400,7 +5591,7 @@ public int ScriptLineNumber internal FunctionContext FunctionContext { get; } /// - /// Returns a formatted string containing the ScriptName and ScriptLineNumber + /// Returns a formatted string containing the ScriptName and ScriptLineNumber. /// public string GetScriptLocation() { @@ -5432,7 +5623,7 @@ public Dictionary GetFrameVariables() break; } - if (scope.DottedScopes != null && scope.DottedScopes.Where(s => s == FunctionContext._localsTuple).Any()) + if (scope.DottedScopes != null && scope.DottedScopes.Any(s => s == FunctionContext._localsTuple)) { var dottedScopes = scope.DottedScopes.ToArray(); @@ -5478,77 +5669,12 @@ namespace System.Management.Automation.Internal #region DebuggerUtils /// - /// Debugger Utilities class + /// Debugger Utilities class. /// [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] public static class DebuggerUtils { - /// - /// Set-DebuggerVariable function. - /// - public const string SetVariableFunction = @"function Set-DebuggerVariable - { - [CmdletBinding()] - param( - [Parameter(Position=0)] - [HashTable] - $Variables - ) - - foreach($key in $Variables.Keys) - { - microsoft.powershell.utility\set-variable -Name $key -Value $Variables[$key] -Scope global - } - - Set-StrictMode -Off - }"; - - /// - /// Remove-DebuggerVariable function. - /// - public const string RemoveVariableFunction = @"function Remove-DebuggerVariable - { - [CmdletBinding()] - param( - [Parameter(Position=0)] - [string[]] - $Name - ) - - foreach ($item in $Name) - { - microsoft.powershell.utility\remove-variable -name $item -scope global - } - - Set-StrictMode -Off - }"; - - /// - /// Get-PSCallStack override function. - /// - public const string GetPSCallStackOverrideFunction = @"function Get-PSCallStack - { - [CmdletBinding()] - param() - - if ($null -ne $PSWorkflowDebugger) - { - foreach ($frame in $PSWorkflowDebugger.GetCallStack()) - { - Write-Output $frame - } - } - - Set-StrictMode -Off - }"; - - internal const string SetDebugModeFunctionName = "__Set-PSDebugMode"; - internal const string SetDebuggerActionFunctionName = "__Set-PSDebuggerAction"; - internal const string GetDebuggerStopArgsFunctionName = "__Get-PSDebuggerStopArgs"; - internal const string SetDebuggerStepMode = "__Set-PSDebuggerStepMode"; - internal const string SetPSUnhandledBreakpointMode = "__Set-PSUnhandledBreakpointMode"; - - private static SortedSet s_noHistoryCommandNames = new SortedSet(StringComparer.OrdinalIgnoreCase) + private static readonly SortedSet s_noHistoryCommandNames = new SortedSet(StringComparer.OrdinalIgnoreCase) { "prompt", "Set-PSDebuggerAction", @@ -5561,13 +5687,13 @@ public static class DebuggerUtils /// Helper method to determine if command should be added to debugger /// history. /// - /// Command string - /// True if command can be added to history + /// Command string. + /// True if command can be added to history. public static bool ShouldAddCommandToHistory(string command) { if (command == null) { - throw new PSArgumentNullException("command"); + throw new PSArgumentNullException(nameof(command)); } lock (s_noHistoryCommandNames) @@ -5576,56 +5702,41 @@ public static bool ShouldAddCommandToHistory(string command) } } - /// - /// Helper method to return an enumeration of workflow debugger - /// functions. - /// - /// - public static IEnumerable GetWorkflowDebuggerFunctions() - { - return new Collection() - { - SetVariableFunction, - RemoveVariableFunction, - GetPSCallStackOverrideFunction - }; - } - /// /// Start monitoring a runspace on the target debugger. /// - /// Target debugger - /// PSMonitorRunspaceInfo + /// Target debugger. + /// PSMonitorRunspaceInfo. public static void StartMonitoringRunspace(Debugger debugger, PSMonitorRunspaceInfo runspaceInfo) { if (debugger == null) { - throw new PSArgumentNullException("debugger"); + throw new PSArgumentNullException(nameof(debugger)); } if (runspaceInfo == null) { - throw new PSArgumentNullException("runspaceInfo"); + throw new PSArgumentNullException(nameof(runspaceInfo)); } debugger.StartMonitoringRunspace(runspaceInfo); } /// - /// End monitoring a runspace on the target degbugger. + /// End monitoring a runspace on the target debugger. /// - /// Target debugger - /// PSMonitorRunspaceInfo + /// Target debugger. + /// PSMonitorRunspaceInfo. public static void EndMonitoringRunspace(Debugger debugger, PSMonitorRunspaceInfo runspaceInfo) { if (debugger == null) { - throw new PSArgumentNullException("debugger"); + throw new PSArgumentNullException(nameof(debugger)); } if (runspaceInfo == null) { - throw new PSArgumentNullException("runspaceInfo"); + throw new PSArgumentNullException(nameof(runspaceInfo)); } debugger.EndMonitoringRunspace(runspaceInfo); @@ -5635,7 +5746,7 @@ public static void EndMonitoringRunspace(Debugger debugger, PSMonitorRunspaceInf #region PSMonitorRunspaceEvent /// - /// PSMonitorRunspaceEvent + /// PSMonitorRunspaceEvent. /// [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] public enum PSMonitorRunspaceType @@ -5649,11 +5760,6 @@ public enum PSMonitorRunspaceType /// Runspace from remote Invoke-Command script. /// InvokeCommand, - - /// - /// Runspace from Workflow activity inline script. - /// - WorkflowInlineScript } /// @@ -5665,14 +5771,14 @@ public abstract class PSMonitorRunspaceInfo #region Properties /// - /// Created Runspace + /// 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. @@ -5686,17 +5792,17 @@ public abstract class PSMonitorRunspaceInfo private PSMonitorRunspaceInfo() { } /// - /// Constructor + /// Constructor. /// - /// Runspace - /// Runspace type + /// Runspace. + /// Runspace type. protected PSMonitorRunspaceInfo( Runspace runspace, PSMonitorRunspaceType runspaceType) { if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } Runspace = runspace; @@ -5717,7 +5823,7 @@ protected PSMonitorRunspaceInfo( /// Creates an instance of a NestedRunspaceDebugger. /// /// Root debugger or null. - /// NestedRunspaceDebugger + /// NestedRunspaceDebugger. internal abstract NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger); #endregion @@ -5732,9 +5838,9 @@ public sealed class PSStandaloneMonitorRunspaceInfo : PSMonitorRunspaceInfo #region Constructor /// - /// Creates instance of PSStandaloneMonitorRunspaceInfo + /// Creates instance of PSStandaloneMonitorRunspaceInfo. /// - /// Runspace to monitor + /// Runspace to monitor. public PSStandaloneMonitorRunspaceInfo( Runspace runspace) : base(runspace, PSMonitorRunspaceType.Standalone) @@ -5756,8 +5862,8 @@ internal override PSMonitorRunspaceInfo Copy() /// /// Creates an instance of a NestedRunspaceDebugger. /// - /// Root debugger or null - /// NestedRunspaceDebugger wrapper + /// Root debugger or null. + /// NestedRunspaceDebugger wrapper. internal override NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger) { return new StandaloneRunspaceDebugger(Runspace); @@ -5778,10 +5884,10 @@ 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 + /// Unique parent debugger identifier. /// public Guid ParentDebuggerId { get; private set; } @@ -5790,12 +5896,12 @@ public sealed class PSEmbeddedMonitorRunspaceInfo : PSMonitorRunspaceInfo #region Constructor /// - /// Creates instance of PSEmbeddedMonitorRunspaceInfo + /// Creates instance of PSEmbeddedMonitorRunspaceInfo. /// - /// Runspace to monitor - /// Type of runspace - /// Running command - /// Unique parent debugger id or null + /// Runspace to monitor. + /// Type of runspace. + /// Running command. + /// Unique parent debugger id or null. public PSEmbeddedMonitorRunspaceInfo( Runspace runspace, PSMonitorRunspaceType runspaceType, @@ -5827,8 +5933,8 @@ internal override PSMonitorRunspaceInfo Copy() /// /// Creates an instance of a NestedRunspaceDebugger. /// - /// Root debugger or null - /// NestedRunspaceDebugger wrapper + /// Root debugger or null. + /// NestedRunspaceDebugger wrapper. internal override NestedRunspaceDebugger CreateDebugger(Debugger rootDebugger) { return new EmbeddedRunspaceDebugger( diff --git a/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs b/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs index 3b33c4458d2..74bcefae2a8 100644 --- a/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs +++ b/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs @@ -1,22 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces { /// /// Base class for AsyncResult objects that are returned by various - /// Async operations supported by RunspacePool , PowerShell types + /// Async operations supported by RunspacePool , PowerShell types. /// 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). @@ -29,7 +29,7 @@ internal class AsyncResult : IAsyncResult #region Constructor /// - /// Constructor + /// Constructor. /// /// /// Instance Id of the object creating this instance @@ -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; @@ -53,7 +53,7 @@ internal AsyncResult(Guid ownerId, AsyncCallback callback, object state) #region IAsync Overrides /// - /// This always returns false + /// This always returns false. /// public bool CompletedSynchronously { @@ -63,7 +63,6 @@ public bool CompletedSynchronously } } - /// /// Gets an indication whether the asynchronous operation has completed. /// @@ -82,14 +81,11 @@ public WaitHandle AsyncWaitHandle { get { - if (null == _completedWaitHandle) + if (_completedWaitHandle == null) { lock (SyncObject) { - if (null == _completedWaitHandle) - { - _completedWaitHandle = new ManualResetEvent(IsCompleted); - } + _completedWaitHandle ??= new ManualResetEvent(IsCompleted); } } @@ -118,7 +114,7 @@ public WaitHandle AsyncWaitHandle internal AsyncCallback Callback { get; } /// - /// SyncObject + /// SyncObject. /// internal object SyncObject { get; } = new object(); @@ -126,11 +122,11 @@ 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) { - //Dbg.Assert(!isCompleted, "AsynResult already completed"); + // Dbg.Assert(!isCompleted, "AsynResult already completed"); if (IsCompleted) { return; @@ -153,10 +149,7 @@ internal void SetAsCompleted(Exception exception) } // call the user supplied callback - if (null != Callback) - { - Callback(this); - } + Callback?.Invoke(this); } /// @@ -182,10 +175,7 @@ internal void SignalWaitHandle() { lock (SyncObject) { - if (null != _completedWaitHandle) - { - _completedWaitHandle.Set(); - } + _completedWaitHandle?.Set(); } } @@ -226,8 +216,8 @@ internal void EndInvoke() _invokeOnThreadEvent.Dispose(); _invokeOnThreadEvent = null; // Allow early GC - // Operation is done: if an exception occured, throw it - if (null != Exception) + // Operation is done: if an exception occurred, throw it + if (Exception != null) { throw Exception; } @@ -236,13 +226,13 @@ internal void EndInvoke() /// /// Use blocked thread to invoke callback delegate. /// - /// Callback delegate - /// Callback state + /// Callback delegate. + /// Callback state. internal bool InvokeCallbackOnThread(WaitCallback callback, object state) { if (callback == null) { - throw new PSArgumentNullException("callback"); + throw new PSArgumentNullException(nameof(callback)); } _invokeCallback = callback; diff --git a/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs b/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs index 72b9e860869..0cac1d02b6f 100644 --- a/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs @@ -1,103 +1,77 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; - - 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 { - #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell compatibility with Windows PowerShell private readonly string label = null; - private string helpMessage = ""; + private string helpMessage = string.Empty; #endregion /// - /// /// Initializes an new instance of ChoiceDescription and defines the Label value. - /// /// /// - /// /// The label to identify this field description - /// /// /// - /// /// is null or empty. - /// /// - public ChoiceDescription(string label) { // the only required parameter is label. - if (String.IsNullOrEmpty(label)) + if (string.IsNullOrEmpty(label)) { // "label" is not localizable - throw PSTraceSource.NewArgumentException("label", DescriptionsStrings.NullOrEmptyErrorTemplate, "label"); + throw PSTraceSource.NewArgumentException(nameof(label), DescriptionsStrings.NullOrEmptyErrorTemplate, "label"); } this.label = label; } /// - /// /// Initializes an new instance of ChoiceDescription and defines the Label and HelpMessage values. - /// /// /// - /// /// The label to identify this field description. - /// /// /// - /// /// The help message for this field. - /// /// /// - /// /// is null or empty. - /// /// /// - /// /// is null. - /// /// - public ChoiceDescription(string label, string helpMessage) { // the only required parameter is label. - if (String.IsNullOrEmpty(label)) + if (string.IsNullOrEmpty(label)) { // "label" is not localizable - throw PSTraceSource.NewArgumentException("label", DescriptionsStrings.NullOrEmptyErrorTemplate, "label"); + throw PSTraceSource.NewArgumentException(nameof(label), DescriptionsStrings.NullOrEmptyErrorTemplate, "label"); } if (helpMessage == null) { // "helpMessage" is not localizable - throw PSTraceSource.NewArgumentNullException("helpMessage"); + throw PSTraceSource.NewArgumentNullException(nameof(helpMessage)); } this.label = label; @@ -105,21 +79,16 @@ class ChoiceDescription } /// - /// /// Gets a short, human-presentable message to describe and identify the choice. Think Button label. - /// /// /// - /// /// 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 @@ -132,25 +101,16 @@ class ChoiceDescription } } - - /// - /// /// Gets and sets the help message for this field. - /// /// /// - /// /// Set to null. - /// /// /// - /// /// 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 @@ -161,6 +121,7 @@ class ChoiceDescription return this.helpMessage; } + set { if (value == null) @@ -173,6 +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 c08209fbb31..3de339ff112 100644 --- a/src/System.Management.Automation/engine/hostifaces/Command.cs +++ b/src/System.Management.Automation/engine/hostifaces/Command.cs @@ -1,13 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; -using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation.Internal; + using Microsoft.Management.Infrastructure; +using Dbg = System.Management.Automation.Diagnostics; + namespace System.Management.Automation.Runspaces { /// @@ -21,8 +22,8 @@ public sealed class Command /// /// Initializes a new instance of Command class using specified command parameter. /// - /// Name of the command or script contents - /// command is null + /// Name of the command or script contents. + /// Command is null. public Command(string command) : this(command, false, null) { @@ -31,27 +32,27 @@ public Command(string command) /// /// Initializes a new instance of Command class using specified command parameter. /// - /// The command name or script contents + /// The command name or script contents. /// True if this command represents a script, otherwise; false. - /// command is null + /// Command is null. public Command(string command, bool isScript) : this(command, isScript, null) { } /// - /// Constructor + /// Constructor. /// - /// The command name or script contents + /// The command name or script contents. /// True if this command represents a script, otherwise; false. - /// if true local scope is used to run the script command - /// command is null + /// If true local scope is used to run the script command. + /// Command is null. public Command(string command, bool isScript, bool useLocalScope) { IsEndOfStatement = false; if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } CommandText = command; @@ -64,7 +65,7 @@ internal Command(string command, bool isScript, bool? useLocalScope) IsEndOfStatement = false; if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } CommandText = command; @@ -95,7 +96,7 @@ internal Command(CommandInfo commandInfo, bool isScript) } /// - /// Copy constructor for clone operations + /// Copy constructor for clone operations. /// /// The source instance. internal Command(Command command) @@ -108,6 +109,7 @@ internal Command(Command command) MergeToResult = command.MergeToResult; _mergeUnclaimedPreviousCommandResults = command._mergeUnclaimedPreviousCommandResults; IsEndOfStatement = command.IsEndOfStatement; + CommandInfo = command.CommandInfo; foreach (CommandParameter param in command.Parameters) { @@ -172,6 +174,13 @@ internal bool? UseLocalScopeNullable get { return _useLocalScope; } } + /// + /// Gets or sets DollarUnderbar ($_) value to be used with script command. + /// This is used by foreach-object -parallel where each piped input ($_) is associated + /// with a parallel running script block. + /// + internal object DollarUnderbar { get; set; } = AutomationNull.Value; + /// /// Checks if the current command marks the end of a statement (see PowerShell.AddStatement()) /// @@ -191,7 +200,7 @@ internal Command Clone() } /// - /// for diagnostic purposes + /// For diagnostic purposes. /// /// public override string ToString() @@ -207,7 +216,7 @@ public override string ToString() PipelineResultTypes.None; /// /// Sets this command as the mergepoint for previous unclaimed - /// commands' results + /// commands' results. /// /// /// @@ -226,6 +235,7 @@ public PipelineResultTypes MergeUnclaimedPreviousCommandResults { return _mergeUnclaimedPreviousCommandResults; } + set { if (value == PipelineResultTypes.None) @@ -263,26 +273,24 @@ internal enum MergeType Debug = 3, Information = 4 } + internal const int MaxMergeType = (int)(MergeType.Information + 1); /// /// Internal accessor for _mergeInstructions. It is used by serialization - /// code + /// code. /// internal PipelineResultTypes[] MergeInstructions { get; set; } = new PipelineResultTypes[MaxMergeType]; /// - /// Merges this commands results + /// Merges this commands results. /// - /// /// /// Pipeline stream to be redirected. /// - /// /// /// Pipeline stream in to which myResult is merged /// - /// /// /// myResult parameter is not PipelineResultTypes.Error or /// toResult parameter is not PipelineResultTypes.Output @@ -303,21 +311,24 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR { MergeInstructions[i] = PipelineResultTypes.None; } + return; } // Validate parameters. if (myResult == PipelineResultTypes.None || myResult == PipelineResultTypes.Output) { - throw PSTraceSource.NewArgumentException("myResult", RunspaceStrings.InvalidMyResultError); + throw PSTraceSource.NewArgumentException(nameof(myResult), RunspaceStrings.InvalidMyResultError); } + if (myResult == PipelineResultTypes.Error && toResult != PipelineResultTypes.Output) { - throw PSTraceSource.NewArgumentException("toResult", RunspaceStrings.InvalidValueToResultError); + throw PSTraceSource.NewArgumentException(nameof(toResult), RunspaceStrings.InvalidValueToResultError); } + if (toResult != PipelineResultTypes.Output && toResult != PipelineResultTypes.Null) { - throw PSTraceSource.NewArgumentException("toResult", RunspaceStrings.InvalidValueToResult); + throw PSTraceSource.NewArgumentException(nameof(toResult), RunspaceStrings.InvalidValueToResult); } // For V2 backwards compatibility. @@ -332,18 +343,22 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR { MergeInstructions[(int)MergeType.Error] = toResult; } + if (myResult == PipelineResultTypes.Warning || myResult == PipelineResultTypes.All) { MergeInstructions[(int)MergeType.Warning] = toResult; } + if (myResult == PipelineResultTypes.Verbose || myResult == PipelineResultTypes.All) { MergeInstructions[(int)MergeType.Verbose] = toResult; } + if (myResult == PipelineResultTypes.Debug || myResult == PipelineResultTypes.All) { MergeInstructions[(int)MergeType.Debug] = toResult; } + if (myResult == PipelineResultTypes.Information || myResult == PipelineResultTypes.All) { MergeInstructions[(int)MergeType.Information] = toResult; @@ -351,7 +366,7 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR } /// - /// Set the merge settings on commandProcessor + /// Set the merge settings on commandProcessor. /// /// private @@ -364,7 +379,7 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR if (_mergeUnclaimedPreviousCommandResults != PipelineResultTypes.None) { - //Currently only merging previous unclaimed error and output is supported. + // Currently only merging previous unclaimed error and output is supported. if (mcr != null) { mcr.MergeUnclaimedPreviousErrorResults = true; @@ -374,7 +389,7 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR // Error merge. if (MergeInstructions[(int)MergeType.Error] == PipelineResultTypes.Output) { - //Currently only merging error with output is supported. + // Currently only merging error with output is supported. mcr.ErrorMergeTo = MshCommandRuntime.MergeDataStream.Output; } @@ -407,7 +422,7 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR } } - private Pipe GetRedirectionPipe( + private static Pipe GetRedirectionPipe( PipelineResultTypes toType, MshCommandRuntime mcr) { @@ -424,7 +439,7 @@ private Pipe GetRedirectionPipe( #endregion Merge /// - /// Create a CommandProcessorBase for this Command + /// Create a CommandProcessorBase for this Command. /// /// /// @@ -479,7 +494,7 @@ CommandOrigin origin if (scriptBlock.UsesCmdletBinding) { - FunctionInfo functionInfo = new FunctionInfo("", scriptBlock, executionContext); + FunctionInfo functionInfo = new FunctionInfo(string.Empty, scriptBlock, executionContext); commandProcessorBase = new CommandProcessor(functionInfo, executionContext, _useLocalScope ?? false, fromScriptFile: false, sessionState: executionContext.EngineSessionState); } @@ -488,7 +503,8 @@ CommandOrigin origin commandProcessorBase = new DlrScriptCommandProcessor(scriptBlock, executionContext, _useLocalScope ?? false, origin, - executionContext.EngineSessionState); + executionContext.EngineSessionState, + DollarUnderbar); } } else @@ -502,8 +518,8 @@ CommandOrigin origin case PSLanguageMode.NoLanguage: string message = StringUtil.Format(RunspaceStrings.UseLocalScopeNotAllowed, "UseLocalScope", - PSLanguageMode.RestrictedLanguage.ToString(), - PSLanguageMode.NoLanguage.ToString()); + nameof(PSLanguageMode.RestrictedLanguage), + nameof(PSLanguageMode.NoLanguage)); throw new RuntimeException(message); case PSLanguageMode.FullLanguage: // Interactive script commands are permitted in this mode... @@ -536,7 +552,7 @@ CommandOrigin origin helpCategory); } - //Set the merge settings + // Set the merge settings SetMergeSettingsOnCommandProcessor(commandProcessorBase); return commandProcessorBase; @@ -552,18 +568,17 @@ 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 #region Serialization / deserialization for remoting - /// /// Creates a Command object from a PSObject property bag. /// PSObject has to be in the format returned by ToPSObjectForRemoting method. /// - /// PSObject to rehydrate + /// PSObject to rehydrate. /// /// Command rehydrated from a PSObject property bag /// @@ -577,7 +592,7 @@ internal static Command FromPSObjectForRemoting(PSObject commandAsPSObject) { if (commandAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("commandAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(commandAsPSObject)); } string commandText = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.CommandText); @@ -597,18 +612,22 @@ internal static Command FromPSObjectForRemoting(PSObject commandAsPSObject) { command.MergeInstructions[(int)MergeType.Error] = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.MergeError); } + if (commandAsPSObject.Properties[RemoteDataNameStrings.MergeWarning] != null) { command.MergeInstructions[(int)MergeType.Warning] = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.MergeWarning); } + if (commandAsPSObject.Properties[RemoteDataNameStrings.MergeVerbose] != null) { command.MergeInstructions[(int)MergeType.Verbose] = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.MergeVerbose); } + if (commandAsPSObject.Properties[RemoteDataNameStrings.MergeDebug] != null) { command.MergeInstructions[(int)MergeType.Debug] = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.MergeDebug); } + if (commandAsPSObject.Properties[RemoteDataNameStrings.MergeInformation] != null) { command.MergeInstructions[(int)MergeType.Information] = RemotingDecoder.GetPropertyValue(commandAsPSObject, RemoteDataNameStrings.MergeInformation); @@ -626,8 +645,8 @@ internal static Command FromPSObjectForRemoting(PSObject commandAsPSObject) /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// - /// PowerShell remoting protocol version - /// This object as a PSObject property bag + /// PowerShell remoting protocol version. + /// This object as a PSObject property bag. internal PSObject ToPSObjectForRemoting(Version psRPVersion) { PSObject commandAsPSObject = RemotingEncoder.CreateEmptyPSObject(); @@ -642,9 +661,8 @@ 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])); @@ -654,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])); @@ -708,12 +726,12 @@ internal PSObject ToPSObjectForRemoting(Version psRPVersion) { parametersAsListOfPSObjects.Add(parameter.ToPSObjectForRemoting()); } + commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.Parameters, parametersAsListOfPSObjects)); return commandAsPSObject; } - #endregion #region Win Blue Extensions @@ -756,48 +774,48 @@ internal CimInstance ToCimInstance() } /// - /// Enum defining the types of streams coming out of a pipeline + /// Enum defining the types of streams coming out of a pipeline. /// [Flags] public enum PipelineResultTypes { /// - /// Default streaming behavior + /// Default streaming behavior. /// None, /// - /// Success output + /// Success output. /// Output, /// - /// Error output + /// Error output. /// Error, /// - /// Warning information stream + /// Warning information stream. /// Warning, /// - /// Verbose information stream + /// Verbose information stream. /// Verbose, /// - /// Debug information stream + /// Debug information stream. /// Debug, /// - /// Information information stream + /// Information information stream. /// Information, /// - /// All streams + /// All streams. /// All, @@ -814,21 +832,21 @@ public enum PipelineResultTypes public sealed class CommandCollection : Collection { /// - /// Make the default constructor internal + /// Make the default constructor internal. /// internal CommandCollection() { } /// - /// Adds a new command for given string + /// Adds a new command for given string. /// /// /// command is null. /// public void Add(string command) { - if (String.Equals(command, "out-default", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(command, "out-default", StringComparison.OrdinalIgnoreCase)) { this.Add(command, true); } @@ -844,9 +862,9 @@ internal void Add(string command, bool mergeUnclaimedPreviousCommandError) } /// - /// Adds a new script command + /// Adds a new script command. /// - /// script contents + /// Script contents. /// /// scriptContents is null. /// @@ -856,10 +874,10 @@ public void AddScript(string scriptContents) } /// - /// Adds a new scrip command for given script + /// Adds a new scrip command for given script. /// - /// script contents - /// if true local scope is used to run the script command + /// Script contents. + /// If true local scope is used to run the script command. /// /// scriptContents is null. /// @@ -882,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 c948a2f205c..ccb918c7dd9 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -1,14 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; -using System.Threading; using System.Management.Automation.Host; using System.Management.Automation.Internal; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Runtime.Serialization; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces @@ -19,11 +19,10 @@ namespace System.Management.Automation.Runspaces /// Exception thrown when state of the runspace is different from /// expected state of runspace. /// - [Serializable] public class InvalidRunspaceStateException : SystemException { /// - /// Initializes a new instance of InvalidRunspaceStateException + /// Initializes a new instance of InvalidRunspaceStateException. /// public InvalidRunspaceStateException() : base @@ -64,9 +63,9 @@ public InvalidRunspaceStateException(string message, Exception innerException) /// Initializes a new instance of the InvalidRunspaceStateException /// with a specified error message and current and expected state. /// - /// The message that describes the error. - /// Current state of runspace - /// Expected states of runspace + /// The message that describes the error. + /// Current state of runspace. + /// Expected states of runspace. internal InvalidRunspaceStateException ( string message, @@ -79,7 +78,6 @@ RunspaceState expectedState _currentState = currentState; } - #region ISerializable Members // 2005/04/20-JonN No need to implement GetObjectData @@ -97,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 @@ -161,7 +160,7 @@ internal set public enum RunspaceState { /// - /// Beginning state upon creation + /// Beginning state upon creation. /// BeforeOpen = 0, /// @@ -177,7 +176,7 @@ public enum RunspaceState /// Closed = 3, /// - /// The runspace is being closed + /// The runspace is being closed. /// Closing = 4, /// @@ -199,17 +198,17 @@ public enum RunspaceState } /// - /// These options control whether a new thread is created when a command is executed within a runspace + /// These options control whether a new thread is created when a command is executed within a runspace. /// public enum PSThreadOptions { /// - /// Use the default options: UseNewThread for local Runspace, ReuseThread for local RunspacePool, server settings for remote Runspace and RunspacePool + /// Use the default options: UseNewThread for local Runspace, ReuseThread for local RunspacePool, server settings for remote Runspace and RunspacePool. /// Default = 0, /// - /// Creates a new thread for each invocation + /// Creates a new thread for each invocation. /// UseNewThread = 1, @@ -220,18 +219,15 @@ 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 - /// Exception associated with RunspaceState + /// Exception associated with RunspaceState. /// public sealed class RunspaceStateInfo { @@ -247,7 +243,7 @@ internal RunspaceStateInfo(RunspaceState state) } /// - /// Constructor for state changes with an optional error + /// Constructor for state changes with an optional error. /// /// The state of runspace. /// A non-null exception if the state change was @@ -293,7 +289,7 @@ internal RunspaceStateInfo(RunspaceStateInfo runspaceStateInfo) #endregion public_properties /// - /// override for ToString() + /// Override for ToString() /// /// public override string ToString() @@ -302,9 +298,9 @@ public override string ToString() } /// - /// Clones current object + /// Clones current object. /// - /// Cloned object + /// Cloned object. internal RunspaceStateInfo Clone() { return new RunspaceStateInfo(this); @@ -324,18 +320,19 @@ public sealed class RunspaceStateEventArgs : EventArgs #region constructors /// - /// Constructs RunspaceStateEventArgs using RunspaceStateInfo + /// Constructs RunspaceStateEventArgs using RunspaceStateInfo. /// /// The information about /// current state of the runspace. - /// runspaceStateInfo is null + /// RunspaceStateInfo is null /// internal RunspaceStateEventArgs(RunspaceStateInfo runspaceStateInfo) { if (runspaceStateInfo == null) { - throw PSTraceSource.NewArgumentNullException("runspaceStateInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(runspaceStateInfo)); } + RunspaceStateInfo = runspaceStateInfo; } @@ -344,7 +341,7 @@ internal RunspaceStateEventArgs(RunspaceStateInfo runspaceStateInfo) #region public_properties /// - /// Information about state of the runspace + /// Information about state of the runspace. /// /// /// This value indicates the state of the runspace after the @@ -356,27 +353,27 @@ internal RunspaceStateEventArgs(RunspaceStateInfo runspaceStateInfo) } /// - /// Enum to indicate whether a Runspace is busy or available + /// Enum to indicate whether a Runspace is busy or available. /// public enum RunspaceAvailability { /// - /// The Runspace is not been in the Opened state + /// The Runspace is not been in the Opened state. /// None = 0, /// - /// The Runspace is available to execute commands + /// The Runspace is available to execute commands. /// Available, /// - /// The Runspace is available to execute nested commands + /// The Runspace is available to execute nested commands. /// AvailableForNestedCommand, /// - /// The Runspace is busy executing a command + /// The Runspace is busy executing a command. /// Busy, @@ -399,7 +396,7 @@ internal RunspaceAvailabilityEventArgs(RunspaceAvailability runspaceAvailability } /// - /// Whether the Runspace is available to execute commands + /// Whether the Runspace is available to execute commands. /// public RunspaceAvailability RunspaceAvailability { get; } } @@ -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,16 +452,16 @@ 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 #region constructor /// - /// Explicit default constructor + /// Explicit default constructor. /// internal Runspace() { @@ -477,7 +479,7 @@ internal Runspace() } /// - /// Static Constructor + /// Static Constructor. /// static Runspace() { @@ -498,7 +500,7 @@ static Runspace() [ThreadStatic] private static Runspace t_threadSpecificDefaultRunspace = null; /// - /// Gets and sets the default Runspace used to evaluate scripts + /// Gets and sets the default Runspace used to evaluate scripts. /// /// The Runspace used to set this property should not be shared between different threads. public static Runspace DefaultRunspace @@ -507,6 +509,7 @@ public static Runspace DefaultRunspace { return t_threadSpecificDefaultRunspace; } + set { if (value == null || !value.RunspaceIsRemote) @@ -541,6 +544,7 @@ internal static Runspace PrimaryRunspace } } } + private static Runspace s_primaryRunspace; /// @@ -572,7 +576,7 @@ public static bool CanUseDefaultRunspace { return (localPipeline.NestedPipelineExecutionThread.ManagedThreadId - == Threading.Thread.CurrentThread.ManagedThreadId); + == Environment.CurrentManagedThreadId); } } @@ -580,11 +584,10 @@ public static bool CanUseDefaultRunspace } } -#if !CORECLR // No ApartmentState In CoreCLR internal const ApartmentState DefaultApartmentState = ApartmentState.Unknown; /// - /// ApartmentState of the thread used to execute commands within this Runspace + /// ApartmentState of the thread used to execute commands within this Runspace. /// /// /// Any updates to the value of this property must be done before the Runspace is opened @@ -609,11 +612,11 @@ public ApartmentState ApartmentState this.apartmentState = value; } } + private ApartmentState apartmentState = Runspace.DefaultApartmentState; -#endif /// - /// This property determines whether a new thread is create for each invocation + /// This property determines whether a new thread is create for each invocation. /// /// /// Any updates to the value of this property must be done before the Runspace is opened @@ -631,7 +634,7 @@ public abstract PSThreadOptions ThreadOptions } /// - /// Return version of this runspace + /// Return version of this runspace. /// public abstract Version Version { @@ -643,18 +646,18 @@ public abstract Version Version /// We can determine this by whether the runspace is an implementation of LocalRunspace /// or infer it from whether the ConnectionInfo property is null /// If it happens to be an instance of a LocalRunspace, but has a non-null ConnectionInfo - /// we declare it to be remote + /// we declare it to be remote. /// public bool RunspaceIsRemote { get { - return !(this is LocalRunspace || ConnectionInfo == null); + return this is not LocalRunspace && ConnectionInfo != null; } } /// - /// Retrieve information about current state of the runspace + /// Retrieve information about current state of the runspace. /// public abstract RunspaceStateInfo RunspaceStateInfo { @@ -662,7 +665,7 @@ public abstract RunspaceStateInfo RunspaceStateInfo } /// - /// Gets the current availability of the Runspace + /// Gets the current availability of the Runspace. /// public abstract RunspaceAvailability RunspaceAvailability { @@ -680,21 +683,24 @@ public abstract InitialSessionState InitialSessionState /// /// Get unique id for this instance of runspace. It is primarily used - /// for logging purposes + /// for logging purposes. /// - public Guid InstanceId { get; + public Guid InstanceId + { + get; // This id is also used to identify proxy and remote runspace objects. // We need to set this when reconstructing a remote runspace to connect // to an existing remote runspace. - internal set; } = Guid.NewGuid(); + internal set; + } = Guid.NewGuid(); /// /// Gets execution context. /// /// Runspace is not opened. /// - internal System.Management.Automation.ExecutionContext ExecutionContext + internal ExecutionContext ExecutionContext { get { @@ -703,17 +709,17 @@ internal System.Management.Automation.ExecutionContext ExecutionContext } /// - /// Skip user profile on engine initialization + /// Skip user profile on engine initialization. /// internal bool SkipUserProfile { get; set; } = false; /// - /// Connection information for remote Runspaces, null for local Runspaces + /// Connection information for remote Runspaces, null for local Runspaces. /// public abstract RunspaceConnectionInfo ConnectionInfo { get; } /// - /// ConnectionInfo originally supplied by the user + /// ConnectionInfo originally supplied by the user. /// public abstract RunspaceConnectionInfo OriginalConnectionInfo { get; } @@ -754,14 +760,16 @@ 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 + /// Returns protocol version that the remote server uses for PS remoting. /// internal Version GetRemoteProtocolVersion() { @@ -800,8 +808,9 @@ 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. @@ -843,7 +852,7 @@ internal static IReadOnlyList RunspaceList public abstract event EventHandler AvailabilityChanged; /// - /// Returns true if there are any subscribers to the AvailabilityChanged event + /// Returns true if there are any subscribers to the AvailabilityChanged event. /// internal abstract bool HasAvailabilityChangedSubscribers { @@ -851,12 +860,12 @@ internal abstract bool HasAvailabilityChangedSubscribers } /// - /// Raises the AvailabilityChanged event + /// Raises the AvailabilityChanged event. /// protected abstract void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e); /// - /// Used to raise the AvailabilityChanged event when the state of the currently executing pipeline changes + /// Used to raise the AvailabilityChanged event when the state of the currently executing pipeline changes. /// /// /// The possible pipeline states are @@ -885,6 +894,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise // Otherwise no change. } + break; case RunspaceAvailability.Available: @@ -898,6 +908,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise this.RunspaceAvailability = Runspaces.RunspaceAvailability.None; break; } + break; case RunspaceAvailability.AvailableForNestedCommand: @@ -915,6 +926,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise default: break; // no change in the availability } + break; case RunspaceAvailability.Busy: @@ -930,6 +942,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise { this.RunspaceAvailability = RunspaceAvailability.None; } + break; case PipelineState.Stopping: @@ -938,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; } @@ -946,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)) @@ -1022,6 +1036,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise } } } + break; case PipelineState.Running: // this can happen if a nested pipeline is created without entering a nested prompt @@ -1030,6 +1045,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise default: break; // no change in the availability } + break; default: @@ -1044,7 +1060,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise } /// - /// Used to update the runspace availability when the state of the currently executing PowerShell instance changes + /// Used to update the runspace availability when the state of the currently executing PowerShell instance changes. /// /// /// The possible invocation states are @@ -1094,7 +1110,7 @@ internal void UpdateRunspaceAvailability(PSInvocationState invocationState, bool } /// - /// Used to update the runspace availability event when the state of the runspace changes + /// Used to update the runspace availability event when the state of the runspace changes. /// /// /// The possible runspace states are: @@ -1134,11 +1150,13 @@ protected void UpdateRunspaceAvailability(RunspaceState runspaceState, bool rais this.RunspaceAvailability = (remoteCommand == null && GetCurrentlyRunningPipeline() == null) ? RunspaceAvailability.Available : RunspaceAvailability.Busy; } + break; default: break; // no change in the availability } + break; case RunspaceAvailability.Available: @@ -1157,6 +1175,7 @@ protected void UpdateRunspaceAvailability(RunspaceState runspaceState, bool rais default: break; // no change in the availability } + break; default: @@ -1171,7 +1190,7 @@ protected void UpdateRunspaceAvailability(RunspaceState runspaceState, bool rais } /// - /// Used to update the runspace availability from Enter/ExitNestedPrompt and the debugger + /// Used to update the runspace availability from Enter/ExitNestedPrompt and the debugger. /// internal void UpdateRunspaceAvailability(RunspaceAvailability availability, bool raiseEvent) { @@ -1186,7 +1205,7 @@ internal void UpdateRunspaceAvailability(RunspaceAvailability availability, bool } /// - /// Raises the AvailabilityChanged event + /// Raises the AvailabilityChanged event. /// internal void RaiseAvailabilityChangedEvent(RunspaceAvailability availability) { @@ -1342,7 +1361,7 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s /// /// Returns Runspace capabilities. /// - /// RunspaceCapability + /// RunspaceCapability. public abstract RunspaceCapability GetCapabilities(); #endregion @@ -1389,15 +1408,15 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s public abstract void CloseAsync(); /// - /// Create an empty pipeline + /// Create an empty pipeline. /// - /// An empty pipeline + /// An empty pipeline. public abstract Pipeline CreatePipeline(); /// - /// Creates a pipeline for specified command string + /// Creates a pipeline for specified command string. /// - /// A valid command string + /// A valid command string. /// /// A pipeline pre-filled with a object for specified command parameter. /// @@ -1409,8 +1428,8 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s /// /// Create a pipeline from a command string. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with a object for specified command parameter. /// @@ -1432,8 +1451,8 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s /// /// Creates a nested pipeline. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with Command specified in commandString. /// @@ -1443,7 +1462,7 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s public abstract Pipeline CreateNestedPipeline(string command, bool addToHistory); /// - /// Returns the currently executing pipeline, or null if no pipeline is executing + /// Returns the currently executing pipeline, or null if no pipeline is executing. /// internal abstract Pipeline GetCurrentlyRunningPipeline(); @@ -1461,7 +1480,7 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s public abstract PSPrimitiveDictionary GetApplicationPrivateData(); /// - /// A method that runspace pools can use to propagate application private data into runspaces + /// A method that runspace pools can use to propagate application private data into runspaces. /// /// internal abstract void SetApplicationPrivateData(PSPrimitiveDictionary applicationPrivateData); @@ -1469,7 +1488,7 @@ public static Runspace GetRunspace(RunspaceConnectionInfo connectionInfo, Guid s /// /// Push a running PowerShell onto the stack. /// - /// PowerShell + /// PowerShell. internal void PushRunningPowerShell(PowerShell ps) { Dbg.Assert(ps != null, "Caller should not pass in null reference."); @@ -1488,7 +1507,7 @@ internal void PushRunningPowerShell(PowerShell ps) /// /// Pop the currently running PowerShell from stack. /// - /// PowerShell + /// PowerShell. internal PowerShell PopRunningPowerShell() { lock (_syncObject) @@ -1497,7 +1516,11 @@ internal PowerShell PopRunningPowerShell() if (count > 0) { - if (count == 1) { _baseRunningPowerShell = null; } + if (count == 1) + { + _baseRunningPowerShell = null; + } + return _runningPowerShells.Pop(); } } @@ -1515,7 +1538,7 @@ internal PowerShell GetCurrentBasePowerShell() #region SessionStateProxy /// - /// Gets session state proxy + /// Gets session state proxy. /// public SessionStateProxy SessionStateProxy { @@ -1555,15 +1578,15 @@ protected virtual void Dispose(bool disposing) #endregion IDisposable Members /// - /// Gets the execution context + /// Gets the execution context. /// - internal abstract System.Management.Automation.ExecutionContext GetExecutionContext + internal abstract ExecutionContext GetExecutionContext { get; } /// - /// Returns true if the internal host is in a nested prompt + /// Returns true if the internal host is in a nested prompt. /// internal abstract bool InNestedPrompt { @@ -1571,19 +1594,19 @@ internal abstract bool InNestedPrompt } /// - /// Gets the debugger + /// Gets the debugger. /// public virtual Debugger Debugger { get { var context = GetExecutionContext; - return (context != null) ? context.Debugger : null; + return context?.Debugger; } } /// - /// InternalDebugger + /// InternalDebugger. /// internal Debugger InternalDebugger { @@ -1592,7 +1615,7 @@ internal Debugger InternalDebugger } /// - /// Gets the event manager + /// Gets the event manager. /// public abstract PSEventManager Events { @@ -1601,21 +1624,20 @@ public abstract PSEventManager Events #if !CORECLR // Transaction Not Supported On CSS /// - /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance + /// 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); } /// - /// Sets the base transaction for the runspace; any transactions created on this runspace will be nested to this instance + /// 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); @@ -1624,7 +1646,6 @@ public void SetBaseTransaction(System.Transactions.CommittableTransaction transa /// /// Clears the transaction set by SetBaseTransaction() /// - /// public void ClearBaseTransaction() { this.ExecutionContext.TransactionManager.ClearBaseTransaction(); @@ -1641,7 +1662,8 @@ public virtual void ResetRunspaceState() // Used for pipeline id generation. private long _pipelineIdSeed; - //Generate pipeline id unique to this runspace + + // Generate pipeline id unique to this runspace internal long GeneratePipelineId() { return System.Threading.Interlocked.Increment(ref _pipelineIdSeed); @@ -1658,7 +1680,8 @@ internal SessionStateProxy() { } - private RunspaceBase _runspace; + private readonly RunspaceBase _runspace; + internal SessionStateProxy(RunspaceBase runspace) { Dbg.Assert(runspace != null, "Caller should validate the parameter"); @@ -1668,23 +1691,18 @@ internal SessionStateProxy(RunspaceBase runspace) /// /// Set a variable in session state. /// - /// /// /// The name of the item to set. /// - /// /// /// The new value of the item being set. /// - /// /// /// name is null /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1692,31 +1710,27 @@ public virtual void SetVariable(string name, object value) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } + _runspace.SetVariable(name, value); } /// /// Get a variable out of session state. /// - /// /// /// name of variable to look up /// - /// /// /// The value of the specified variable. /// - /// /// /// name is null /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1724,24 +1738,23 @@ public virtual object GetVariable(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (name.Equals(string.Empty)) { return null; } + return _runspace.GetVariable(name); } /// /// Get the list of applications out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1756,11 +1769,9 @@ public virtual List Applications /// /// Get the list of scripts out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1773,13 +1784,11 @@ public virtual List Scripts } /// - /// Get the APIs to access drives out of session state + /// Get the APIs to access drives out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1791,28 +1800,25 @@ public virtual DriveManagementIntrinsics Drive /// /// Get/Set the language mode out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// public virtual PSLanguageMode LanguageMode { get { return _runspace.LanguageMode; } + set { _runspace.LanguageMode = value; } } /// /// Get the module info out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1825,11 +1831,9 @@ public virtual PSModuleInfo Module /// /// Get the APIs to access paths and locations out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1841,11 +1845,9 @@ public virtual PathIntrinsics Path /// /// Get the APIs to access a provider out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1857,11 +1859,9 @@ public virtual CmdletProviderManagementIntrinsics Provider /// /// Get the APIs to access variables out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1873,11 +1873,9 @@ public virtual PSVariableIntrinsics PSVariable /// /// Get the APIs to build script blocks and execute script out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -1889,11 +1887,9 @@ public virtual CommandInvocationIntrinsics InvokeCommand /// /// Gets the instance of the provider interface APIs out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs index 094d0189d04..0969d7b08c6 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs @@ -1,12 +1,11 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Management.Automation.Host; using System.Management.Automation.Internal; +using System.Threading; #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -26,11 +25,33 @@ 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. /// - /// The explicit PSHost implementation + /// The explicit PSHost implementation. /// /// Host is null. /// @@ -41,8 +62,9 @@ protected RunspaceBase(PSHost host) { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } + InitialSessionState = InitialSessionState.CreateDefault(); Host = host; } @@ -51,7 +73,7 @@ protected RunspaceBase(PSHost host) /// Construct an instance of an Runspace using a custom /// implementation of PSHost. /// - /// The explicit PSHost implementation + /// The explicit PSHost implementation. /// /// Host is null. /// @@ -66,20 +88,18 @@ protected RunspaceBase(PSHost host, InitialSessionState initialSessionState) { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } + if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } Host = host; InitialSessionState = initialSessionState.Clone(); this.ThreadOptions = initialSessionState.ThreadOptions; - -#if !CORECLR // No ApartmentState In CoreCLR this.ApartmentState = initialSessionState.ApartmentState; -#endif } /// @@ -106,11 +126,12 @@ protected RunspaceBase(PSHost host, InitialSessionState initialSessionState, boo { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } + if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } Host = host; @@ -122,20 +143,18 @@ protected RunspaceBase(PSHost host, InitialSessionState initialSessionState, boo { InitialSessionState = initialSessionState.Clone(); } - this.ThreadOptions = initialSessionState.ThreadOptions; -#if !CORECLR // No ApartmentState In CoreCLR + this.ThreadOptions = initialSessionState.ThreadOptions; this.ApartmentState = initialSessionState.ApartmentState; -#endif } /// - /// The host implemented PSHost interface + /// The host implemented PSHost interface. /// protected PSHost Host { get; } /// - /// InitialSessionState information for this runspace + /// InitialSessionState information for this runspace. /// public override InitialSessionState InitialSessionState { get; } @@ -144,14 +163,14 @@ protected RunspaceBase(PSHost host, InitialSessionState initialSessionState, boo #region properties /// - /// Return version of this runspace + /// Return version of this runspace. /// public override Version Version { get; } = PSVersionInfo.PSVersion; private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo(RunspaceState.BeforeOpen); /// - /// Retrieve information about current state of the runspace + /// Retrieve information about current state of the runspace. /// public override RunspaceStateInfo RunspaceStateInfo { @@ -159,29 +178,31 @@ public override RunspaceStateInfo RunspaceStateInfo { lock (SyncRoot) { - //Do not return internal state. + // Do not return internal state. return _runspaceStateInfo.Clone(); } } } /// - /// Gets the current availability of the Runspace + /// Gets the current availability of the Runspace. /// public override RunspaceAvailability RunspaceAvailability { get { return _runspaceAvailability; } + protected set { _runspaceAvailability = value; } } + private RunspaceAvailability _runspaceAvailability = RunspaceAvailability.None; /// - /// Object used for synchronization + /// Object used for synchronization. /// protected internal object SyncRoot { get; } = new object(); /// - /// Information about the computer where this runspace is created + /// Information about the computer where this runspace is created. /// public override RunspaceConnectionInfo ConnectionInfo { @@ -193,7 +214,7 @@ public override RunspaceConnectionInfo ConnectionInfo } /// - /// Original Connection Info that the user passed + /// Original Connection Info that the user passed. /// public override RunspaceConnectionInfo OriginalConnectionInfo { @@ -238,10 +259,14 @@ 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. + // Call fails if RunspaceState is not BeforeOpen. if (RunspaceState != RunspaceState.BeforeOpen) { InvalidRunspaceStateException e = @@ -257,32 +282,33 @@ private void CoreOpen(bool syncCall) SetRunspaceState(RunspaceState.Opening); } - //Raise event outside the lock + // Raise event outside the lock 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); } #endif } - /// - /// Derived class's open implementation + /// Derived class's open implementation. /// protected abstract void OpenHelper(bool syncCall); - #endregion open #region close @@ -316,16 +342,14 @@ public override void CloseAsync() } /// - /// Close the runspace + /// Close the runspace. /// /// If true runspace is closed synchronously /// else runspaces is closed asynchronously /// - /// /// /// RunspaceState is BeforeOpen or Opening /// - /// /// /// If SessionStateProxy has some method call in progress /// @@ -394,32 +418,32 @@ private void CoreClose(bool syncCall) if (alreadyClosing) { - //Already closing is set to true if Runspace is already - //in closing. In this case wait for runspace to close. - //This can happen in two scenarios: - //1) User calls Runspace.Close from two threads. - //2) In remoting, some error from data structure handler layer can start - //runspace closure. At the same time, user can call - //remove runspace. + // Already closing is set to true if Runspace is already + // in closing. In this case wait for runspace to close. + // This can happen in two scenarios: + // 1) User calls Runspace.Close from two threads. + // 2) In remoting, some error from data structure handler layer can start + // runspace closure. At the same time, user can call + // remove runspace. // if (syncCall) { WaitForFinishofPipelines(); } + return; } - //Raise Event outside the lock + // Raise Event outside the lock RaiseRunspaceStateEvents(); - //Call the derived class implementation to do the actual work + // Call the derived class implementation to do the actual work CloseHelper(syncCall); } - /// - /// Derived class's close implementation + /// Derived class's close implementation. /// /// If true runspace is closed synchronously /// else runspaces is closed asynchronously @@ -481,7 +505,7 @@ public override void ConnectAsync() /// /// Creates a pipeline object in the Disconnected state. /// - /// Pipeline + /// Pipeline. public override Pipeline CreateDisconnectedPipeline() { // @@ -494,7 +518,7 @@ public override Pipeline CreateDisconnectedPipeline() /// /// Creates a powershell object in the Disconnected state. /// - /// PowerShell + /// PowerShell. public override PowerShell CreateDisconnectedPowerShell() { // @@ -507,7 +531,7 @@ public override PowerShell CreateDisconnectedPowerShell() /// /// Returns Runspace capabilities. /// - /// RunspaceCapability + /// RunspaceCapability. public override RunspaceCapability GetCapabilities() { return RunspaceCapability.Default; @@ -518,18 +542,18 @@ public override RunspaceCapability GetCapabilities() #region CreatePipeline /// - /// Create an empty pipeline + /// Create an empty pipeline. /// - /// An empty pipeline + /// An empty pipeline. public override Pipeline CreatePipeline() { return CoreCreatePipeline(null, false, false); } /// - /// Create a pipeline from a command string + /// Create a pipeline from a command string. /// - /// A valid command string + /// A valid command string. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -540,7 +564,7 @@ public override Pipeline CreatePipeline(string command) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, false, false); @@ -549,8 +573,8 @@ public override Pipeline CreatePipeline(string command) /// /// Create a pipeline from a command string. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -561,7 +585,7 @@ public override Pipeline CreatePipeline(string command, bool addToHistory) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, false); @@ -583,8 +607,8 @@ public override Pipeline CreateNestedPipeline() /// /// Creates a nested pipeline. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -595,19 +619,18 @@ public override Pipeline CreateNestedPipeline(string command, bool addToHistory) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, true); } /// - /// Create a pipeline from a command string + /// Create a pipeline from a command string. /// - /// - /// A valid command string or String.Empty. - /// if true command is added to history - /// True for nested pipeline + /// A valid command string or string.Empty. + /// If true command is added to history. + /// True for nested pipeline. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -628,7 +651,7 @@ public override Pipeline CreateNestedPipeline(string command, bool addToHistory) public override event EventHandler AvailabilityChanged; /// - /// Returns true if there are any subscribers to the AvailabilityChanged event + /// Returns true if there are any subscribers to the AvailabilityChanged event. /// internal override bool HasAvailabilityChangedSubscribers { @@ -636,7 +659,7 @@ internal override bool HasAvailabilityChangedSubscribers } /// - /// Raises the AvailabilityChanged event + /// Raises the AvailabilityChanged event. /// protected override void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e) { @@ -667,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. @@ -676,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) { @@ -696,7 +719,7 @@ public RunspaceEventQueueItem(RunspaceStateInfo runspaceStateInfo, RunspaceAvail /// /// Set the new runspace state. /// - /// the new state + /// The new state. /// An exception indicating the state change is the /// result of an error, otherwise; null. /// @@ -713,12 +736,12 @@ protected void SetRunspaceState(RunspaceState state, Exception reason) { _runspaceStateInfo = new RunspaceStateInfo(state, reason); - //Add _runspaceStateInfo to _runspaceEventQueue. - //RaiseRunspaceStateEvents will raise event for each item - //in this queue. - //Note:We are doing clone here instead of passing the member - //_runspaceStateInfo because we donot want outside - //to change our runspace state. + // Add _runspaceStateInfo to _runspaceEventQueue. + // RaiseRunspaceStateEvents will raise event for each item + // in this queue. + // Note:We are doing clone here instead of passing the member + // _runspaceStateInfo because we donot want outside + // to change our runspace state. RunspaceAvailability previousAvailability = _runspaceAvailability; this.UpdateRunspaceAvailability(_runspaceStateInfo.State, false); @@ -733,15 +756,14 @@ protected void SetRunspaceState(RunspaceState state, Exception reason) } /// - /// Set the current runspace state - no error + /// Set the current runspace state - no error. /// - /// the new state + /// The new state. protected void SetRunspaceState(RunspaceState state) { this.SetRunspaceState(state, null); } - /// /// Raises events for changes in runspace state. /// @@ -763,9 +785,9 @@ protected void RaiseRunspaceStateEvents() } else { - //Clear the events if there are no EventHandlers. This - //ensures that events do not get called for state - //changes prior to their registration. + // Clear the events if there are no EventHandlers. This + // ensures that events do not get called for state + // changes prior to their registration. _runspaceEventQueue.Clear(); } } @@ -782,8 +804,8 @@ protected void RaiseRunspaceStateEvents() } #pragma warning disable 56500 - //Exception raised by events are not error condition for runspace - //object. + // Exception raised by events are not error condition for runspace + // object. if (stateChanged != null) { try @@ -823,12 +845,10 @@ protected void RaiseRunspaceStateEvents() /// /// Pipeline to add to the /// list of pipelines in execution - /// /// /// Thrown if the runspace is not in the Opened state. /// . /// - /// /// Thrown if /// is null. /// @@ -838,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 @@ -850,9 +870,9 @@ internal void AddToRunningPipelineList(PipelineBase pipeline) throw e; } - //Add the pipeline to list of Executing pipeline. - //Note:_runningPipelines is always accessed with the lock so - //there is no need to create a synchronized version of list + // Add the pipeline to list of Executing pipeline. + // Note:_runningPipelines is always accessed with the lock so + // there is no need to create a synchronized version of list RunningPipelines.Add(pipeline); _currentlyRunningPipeline = pipeline; } @@ -863,7 +883,6 @@ internal void AddToRunningPipelineList(PipelineBase pipeline) /// /// Pipeline to remove from the /// list of pipelines in execution - /// /// /// Thrown if is null. /// @@ -876,9 +895,9 @@ internal void RemoveFromRunningPipelineList(PipelineBase pipeline) Dbg.Assert(RunspaceState != RunspaceState.BeforeOpen, "Runspace should not be before open when pipeline is running"); - //Remove the pipeline to list of Executing pipeline. - //Note:_runningPipelines is always accessed with the lock so - //there is no need to create a synchronized version of list + // Remove the pipeline to list of Executing pipeline. + // Note:_runningPipelines is always accessed with the lock so + // there is no need to create a synchronized version of list RunningPipelines.Remove(pipeline); // Update the running pipeline @@ -901,13 +920,13 @@ internal void RemoveFromRunningPipelineList(PipelineBase pipeline) /// internal bool WaitForFinishofPipelines() { - //Take a snapshot of list of active pipelines. - //Note:Before we enter to this CloseHelper routine - //CoreClose has already set the state of Runspace - //to closing. So no new pipelines can be executed on this - //runspace and so no new pipelines will be added to - //_runningPipelines. However we still need to lock because - //running pipelines can be removed from this. + // Take a snapshot of list of active pipelines. + // Note:Before we enter to this CloseHelper routine + // CoreClose has already set the state of Runspace + // to closing. So no new pipelines can be executed on this + // runspace and so no new pipelines will be added to + // _runningPipelines. However we still need to lock because + // running pipelines can be removed from this. PipelineBase[] runningPipelines; lock (_pipelineListLock) @@ -924,7 +943,6 @@ internal bool WaitForFinishofPipelines() waitHandles[i] = runningPipelines[i].PipelineFinishedEvent; } -#if !CORECLR // No ApartmentState.STA In CoreCLR // WaitAll for multiple handles on a STA (single-thread apartment) thread is not supported as WaitAll will prevent the message pump to run if (runningPipelines.Length > 1 && Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { @@ -935,16 +953,17 @@ 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(); } } -#endif + return WaitHandle.WaitAll(waitHandles); } else @@ -953,9 +972,8 @@ internal bool WaitForFinishofPipelines() } } - /// - /// Stops all the running pipelines + /// Stops all the running pipelines. /// protected void StopPipelines() { @@ -968,7 +986,7 @@ protected void StopPipelines() if (runningPipelines.Length > 0) { - //Start from the most recent pipeline. + // Start from the most recent pipeline. for (int i = runningPipelines.Length - 1; i >= 0; i--) { runningPipelines[i].Stop(); @@ -976,28 +994,16 @@ protected void StopPipelines() } } - internal bool RunActionIfNoRunningPipelinesWithThreadCheck(Action action) + internal bool CanRunActionInCurrentPipeline() { - bool ranit = false; - bool shouldRunAction = false; lock (_pipelineListLock) { // If we have no running pipeline, or if the currently running pipeline is // the same as the current thread, then execute the action. - var pipelineRunning = _currentlyRunningPipeline as PipelineBase; - if (pipelineRunning == null || - Thread.CurrentThread.Equals(pipelineRunning.NestedPipelineExecutionThread)) - { - shouldRunAction = true; - } + return pipelineRunning == null || + Thread.CurrentThread == pipelineRunning.NestedPipelineExecutionThread; } - if (shouldRunAction) - { - action(); - ranit = true; - } - return ranit; } /// @@ -1008,11 +1014,12 @@ internal override Pipeline GetCurrentlyRunningPipeline() { return _currentlyRunningPipeline; } + private Pipeline _currentlyRunningPipeline = null; /// /// This method stops all the pipelines which are nested - /// under specified pipeline + /// under specified pipeline. /// /// /// @@ -1022,22 +1029,22 @@ internal void StopNestedPipelines(Pipeline pipeline) lock (_pipelineListLock) { - //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) + // first check if this pipeline is in the list of running + // pipelines. It is possible that pipeline has already + // completed. + if (!RunningPipelines.Contains(pipeline)) { return; } - //If this pipeline is currently running pipeline, - //then it does not have nested pipelines + // If this pipeline is currently running pipeline, + // then it does not have nested pipelines if (GetCurrentlyRunningPipeline() == pipeline) { return; } - //Build list of nested pipelines + // Build list of nested pipelines nestedPipelines = new List(); for (int i = RunningPipelines.Count - 1; i >= 0; i--) { @@ -1063,18 +1070,18 @@ internal void StopNestedPipelines(Pipeline pipeline) void DoConcurrentCheckAndAddToRunningPipelines(PipelineBase pipeline, bool syncCall) { - //Concurrency check should be done under runspace lock + // Concurrency check should be done under runspace lock lock (SyncRoot) { - if (_bSessionStateProxyCallInProgress == true) + if (_bSessionStateProxyCallInProgress) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoPipelineWhenSessionStateProxyInProgress); } - //Delegate to pipeline to do check if it is fine to invoke if another - //pipeline is running. + // Delegate to pipeline to do check if it is fine to invoke if another + // pipeline is running. pipeline.DoConcurrentCheck(syncCall, SyncRoot, true); - //Finally add to the list of running pipelines. + // Finally add to the list of running pipelines. AddToRunningPipelineList(pipeline); } } @@ -1148,9 +1155,9 @@ internal void Pulse() #region session state proxy - //Note: When SessionStateProxy calls are in progress, - //pipeline cannot be invoked. Also when pipeline is in - //progress, SessionStateProxy calls cannot be made. + // Note: When SessionStateProxy calls are in progress, + // pipeline cannot be invoked. Also when pipeline is in + // progress, SessionStateProxy calls cannot be made. private bool _bSessionStateProxyCallInProgress; /// @@ -1174,7 +1181,7 @@ private void DoConcurrentCheckAndMarkSessionStateProxyCallInProgress() throw e; } - if (_bSessionStateProxyCallInProgress == true) + if (_bSessionStateProxyCallInProgress) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.AnotherSessionStateProxyInProgress); } @@ -1210,7 +1217,7 @@ private void DoConcurrentCheckAndMarkSessionStateProxyCallInProgress() } } - //Now we can invoke session state proxy + // Now we can invoke session state proxy _bSessionStateProxyCallInProgress = true; } } @@ -1353,8 +1360,10 @@ public PSLanguageMode LanguageMode ); throw e; } + return DoLanguageMode; } + set { if (RunspaceState != RunspaceState.Opened) @@ -1368,6 +1377,7 @@ public PSLanguageMode LanguageMode ); throw e; } + DoLanguageMode = value; } } @@ -1510,13 +1520,12 @@ internal ProviderIntrinsics InvokeProvider } } - /// /// Protected methods to be implemented by derived class. /// This does the actual work of setting variable. /// - /// Name of the variable to set - /// The value to set it to + /// Name of the variable to set. + /// The value to set it to. protected abstract void DoSetVariable(string name, object value); /// @@ -1594,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 6a95c8865a3..45591ee2906 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Globalization; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Tracing; + using Microsoft.PowerShell; using Microsoft.PowerShell.Commands; @@ -17,7 +17,7 @@ namespace System.Management.Automation.Runspaces public static class RunspaceFactory { /// - /// Static constructor + /// Static constructor. /// static RunspaceFactory() { @@ -62,7 +62,7 @@ public static Runspace CreateRunspace(PSHost host) { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } return new LocalRunspace(host, InitialSessionState.CreateDefault()); @@ -85,7 +85,7 @@ public static Runspace CreateRunspace(InitialSessionState initialSessionState) { if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } PSHost host = new DefaultHost(CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture); @@ -94,7 +94,7 @@ public static Runspace CreateRunspace(InitialSessionState initialSessionState) } /// - /// Creates a runspace using specified PSHost and InitialSessionState + /// Creates a runspace using specified PSHost and InitialSessionState. /// /// /// Host implementation for runspace. @@ -116,19 +116,19 @@ public static Runspace CreateRunspace(PSHost host, InitialSessionState initialSe { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } return new LocalRunspace(host, initialSessionState); } /// - /// Creates a runspace using specified PSHost and InitialSessionState + /// Creates a runspace using specified PSHost and InitialSessionState. /// /// /// Host implementation for runspace. @@ -150,12 +150,12 @@ internal static Runspace CreateRunspaceFromSessionStateNoClone(PSHost host, Init { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } return new LocalRunspace(host, initialSessionState, true); @@ -290,7 +290,6 @@ public static RunspacePool CreateRunspacePool(int minRunspaces, int maxRunspaces maxRunspaces, initialSessionState, host); } - #endregion #region RunspacePool - remote Factory @@ -455,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(); } @@ -477,61 +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 @@ -540,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(); @@ -562,21 +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); @@ -587,10 +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); @@ -599,11 +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 }; @@ -614,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 3c8634e1d00..56d4225be56 100644 --- a/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Globalization; using System.Management.Automation; using System.Management.Automation.Host; + using Dbg = System.Diagnostics; namespace Microsoft.PowerShell @@ -14,18 +14,16 @@ 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 /// - /// Creates an instance based on the current culture and current UI culture + /// Creates an instance based on the current culture and current UI culture. /// - /// Current culture for this host - /// Current UI culture for this host + /// Current culture for this host. + /// Current UI culture for this host. /// - internal DefaultHost(CultureInfo currentCulture, CultureInfo currentUICulture) { CurrentCulture = currentCulture; @@ -47,33 +45,29 @@ internal DefaultHost(CultureInfo currentCulture, CultureInfo currentUICulture) /// /// See base class - /// This property is not supported + /// This property is not supported. /// public override PSHostUserInterface UI { get { return null; } } /// - /// See base class + /// See base class. /// public override CultureInfo CurrentCulture { get; } = null; /// - /// See base class + /// See base class. /// public override CultureInfo CurrentUICulture { get; } = null; #endregion properties - #region methods /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override void SetShouldExit(int exitCode) @@ -82,17 +76,12 @@ public override } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// On calling this method - /// /// - public override void EnterNestedPrompt() @@ -101,15 +90,11 @@ public override } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// On calling this method - /// /// public override void @@ -119,13 +104,10 @@ public override } /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override void NotifyBeginApplication() @@ -134,13 +116,10 @@ public override } /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override void NotifyEndApplication() @@ -154,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 8d02deab24f..3f47fedb55e 100644 --- a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs @@ -1,9 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation.Diagnostics; //using System.Runtime.Serialization; @@ -13,48 +12,36 @@ //using System.Management.Automation; //using System.Reflection; - - 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 { /// - /// /// Initializes a new instance of FieldDescription and defines the Name value. - /// /// /// - /// /// The name to identify this field description - /// /// /// - /// /// is null or empty. - /// /// - public FieldDescription(string name) { // the only required parameter is the name. - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentException("name", DescriptionsStrings.NullOrEmptyErrorTemplate, "name"); + throw PSTraceSource.NewArgumentException(nameof(name), DescriptionsStrings.NullOrEmptyErrorTemplate, "name"); } this.name = name; @@ -71,30 +58,22 @@ public string Name } } - /// - /// /// Sets the ParameterTypeName, ParameterTypeFullName, and ParameterAssemblyFullName as a single operation. - /// /// /// - /// /// The Type that sets the properties. - /// /// /// - /// /// If is null. - /// /// - public void SetParameterType(System.Type parameterType) { if (parameterType == null) { - throw PSTraceSource.NewArgumentNullException("parameterType"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterType)); } SetParameterTypeName(parameterType.Name); @@ -102,34 +81,25 @@ public string Name SetParameterAssemblyFullName(parameterType.AssemblyQualifiedName); } - - /// - /// /// Gets the short name of the parameter's type. - /// /// /// - /// /// The type name of the parameter - /// /// /// - /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// - /// /// - public string ParameterTypeName { get { - if (String.IsNullOrEmpty(parameterTypeName)) + if (string.IsNullOrEmpty(parameterTypeName)) { // the default if the type name is not specified is 'string' @@ -140,29 +110,22 @@ public string Name } } - - /// - /// /// Gets the full string name of the parameter's type. - /// /// /// - /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// - /// /// - public string ParameterTypeFullName { get { - if (String.IsNullOrEmpty(parameterTypeFullName)) + if (string.IsNullOrEmpty(parameterTypeFullName)) { // the default if the type name is not specified is 'string' @@ -173,29 +136,23 @@ public string Name } } - /// - /// - /// Gets the full name of the assembly containing the type identified by ParameterTypeFullName or ParameterTypeName - /// + /// Gets the full name of the assembly containing the type identified by ParameterTypeFullName or ParameterTypeName. /// /// - /// /// If the assembly is not currently loaded in the hosting application's AppDomain, the hosting application needs /// 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 { get { - if (String.IsNullOrEmpty(parameterAssemblyFullName)) + if (string.IsNullOrEmpty(parameterAssemblyFullName)) { // the default if the type name is not specified is 'string' @@ -206,35 +163,26 @@ 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. - /// /// /// - /// /// set to null. - /// /// /// - /// /// 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 @@ -245,6 +193,7 @@ public string Name return label; } + set { if (value == null) @@ -256,25 +205,16 @@ public string Name } } - - /// - /// /// Gets and sets the help message for this field. - /// /// /// - /// /// Set to null. - /// /// /// - /// /// 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 @@ -285,6 +225,7 @@ public string Name return helpMessage; } + set { if (value == null) @@ -296,13 +237,9 @@ public string Name } } - /// - /// - /// Gets and sets whether a value must be supplied for this field - /// + /// Gets and sets whether a value must be supplied for this field. /// - public bool IsMandatory @@ -311,6 +248,7 @@ public string Name { return isMandatory; } + set { isMandatory = value; @@ -318,19 +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 @@ -349,91 +283,69 @@ 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(); } } /// - /// /// For use by remoting serialization. - /// /// /// /// - /// /// If is null. - /// /// - internal void SetParameterTypeName(string nameOfType) { - if (String.IsNullOrEmpty(nameOfType)) + if (string.IsNullOrEmpty(nameOfType)) { - throw PSTraceSource.NewArgumentException("nameOfType", DescriptionsStrings.NullOrEmptyErrorTemplate, "nameOfType"); + throw PSTraceSource.NewArgumentException(nameof(nameOfType), DescriptionsStrings.NullOrEmptyErrorTemplate, "nameOfType"); } parameterTypeName = nameOfType; } - - /// - /// /// For use by remoting serialization. - /// /// /// /// - /// /// If is null. - /// /// - internal void SetParameterTypeFullName(string fullNameOfType) { - if (String.IsNullOrEmpty(fullNameOfType)) + if (string.IsNullOrEmpty(fullNameOfType)) { - throw PSTraceSource.NewArgumentException("fullNameOfType", DescriptionsStrings.NullOrEmptyErrorTemplate, "fullNameOfType"); + throw PSTraceSource.NewArgumentException(nameof(fullNameOfType), DescriptionsStrings.NullOrEmptyErrorTemplate, "fullNameOfType"); } parameterTypeFullName = fullNameOfType; } - - /// - /// /// For use by remoting serialization. - /// /// /// /// - /// /// If is null. - /// /// - internal void SetParameterAssemblyFullName(string fullNameOfAssembly) { - if (String.IsNullOrEmpty(fullNameOfAssembly)) + if (string.IsNullOrEmpty(fullNameOfAssembly)) { - throw PSTraceSource.NewArgumentException("fullNameOfAssembly", DescriptionsStrings.NullOrEmptyErrorTemplate, "fullNameOfAssembly"); + throw PSTraceSource.NewArgumentException(nameof(fullNameOfAssembly), DescriptionsStrings.NullOrEmptyErrorTemplate, "fullNameOfAssembly"); } parameterAssemblyFullName = fullNameOfAssembly; @@ -441,7 +353,7 @@ public string Name /// /// Indicates if this field description was - /// modified by the remoting protocol layer + /// modified by the remoting protocol layer. /// /// Used by the console host to /// determine if this field description was @@ -453,6 +365,7 @@ internal bool ModifiedByRemotingProtocol { return modifiedByRemotingProtocol; } + set { modifiedByRemotingProtocol = value; @@ -461,7 +374,7 @@ internal bool ModifiedByRemotingProtocol /// /// Indicates if this field description - /// is coming from a remote host + /// is coming from a remote host. /// /// Used by the console host to /// not cast strings to an arbitrary type, @@ -473,6 +386,7 @@ internal bool IsFromRemoteHost { return isFromRemoteHost; } + set { isFromRemoteHost = value; @@ -485,11 +399,11 @@ internal bool IsFromRemoteHost #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell private readonly string name = null; - private string label = ""; + private string label = string.Empty; private string parameterTypeName = null; private string parameterTypeFullName = null; private string parameterAssemblyFullName = null; - private string helpMessage = ""; + private string helpMessage = string.Empty; private bool isMandatory = true; private PSObject defaultValue = null; @@ -500,6 +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 7fa640a1f0e..00a090d4b22 100644 --- a/src/System.Management.Automation/engine/hostifaces/History.cs +++ b/src/System.Management.Automation/engine/hostifaces/History.cs @@ -1,283 +1,176 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; +using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// - /// Contains information about a single history entry + /// Contains information about a single history entry. /// public class HistoryInfo { - #region constuctor - /// - /// Constructor + /// Constructor. /// /// Id of pipeline in which command associated /// with this history entry is executed - /// command string - /// status of pipeline execution - /// startTime of execution - /// endTime of execution + /// Command string. + /// Status of pipeline execution. + /// StartTime of execution. + /// EndTime of execution. internal HistoryInfo(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime) { Dbg.Assert(cmdline != null, "caller should validate the parameter"); _pipelineId = pipelineId; - _cmdline = cmdline; - _status = status; - _startTime = startTime; - _endTime = endTime; - _cleared = false; + CommandLine = cmdline; + ExecutionStatus = status; + StartExecutionTime = startTime; + EndExecutionTime = endTime; + Cleared = false; } /// - /// Copy constructor to support cloning + /// Copy constructor to support cloning. /// /// private HistoryInfo(HistoryInfo history) { - _id = history._id; + Id = history.Id; _pipelineId = history._pipelineId; - _cmdline = history._cmdline; - _status = history._status; - _startTime = history._startTime; - _endTime = history._endTime; - _cleared = history._cleared; + CommandLine = history.CommandLine; + ExecutionStatus = history.ExecutionStatus; + StartExecutionTime = history.StartExecutionTime; + EndExecutionTime = history.EndExecutionTime; + Cleared = history.Cleared; } - #endregion constructor - - #region public /// /// Id of this history entry. /// /// - public long Id - { - get - { - return _id; - } - } + public long Id { get; private set; } /// - /// CommandLine string + /// CommandLine string. /// /// - public string CommandLine - { - get - { - return _cmdline; - } - } + public string CommandLine { get; private set; } /// - /// Execution status of associated pipeline + /// Execution status of associated pipeline. /// /// - public PipelineState ExecutionStatus - { - get - { - return _status; - } - } + public PipelineState ExecutionStatus { get; private set; } /// - /// Start time of execution of associated pipeline + /// Start time of execution of associated pipeline. /// /// - public DateTime StartExecutionTime - { - get - { - return _startTime; - } - } + public DateTime StartExecutionTime { get; } /// - /// End time of execution of associated pipeline + /// End time of execution of associated pipeline. /// /// - public DateTime EndExecutionTime - { - get - { - return _endTime; - } - } + public DateTime EndExecutionTime { get; private set; } /// - /// Override for ToString() method + /// The time it took to execute the associeated pipeline. + /// + public TimeSpan Duration => EndExecutionTime - StartExecutionTime; + + /// + /// Override for ToString() method. /// /// public override string ToString() { - if (string.IsNullOrEmpty(_cmdline)) + if (string.IsNullOrEmpty(CommandLine)) { return base.ToString(); } else { - return _cmdline; + return CommandLine; } } - - #endregion public - - #region internal - - - /// - /// Cleared status of an entry + /// Cleared status of an entry. /// - - internal bool Cleared - { - get - { - return _cleared; - } - set - { - _cleared = value; - } - } - - + internal bool Cleared { get; set; } = false; /// - /// Sets Id + /// Sets Id. /// /// - internal void SetId(long id) - { - _id = id; - } + internal void SetId(long id) => Id = id; /// - /// Set status + /// Set status. /// /// - internal void SetStatus(PipelineState status) - { - _status = status; - } + internal void SetStatus(PipelineState status) => ExecutionStatus = status; /// - /// Set endtime + /// Set endtime. /// /// - internal void SetEndTime(DateTime endTime) - { - _endTime = endTime; - } + internal void SetEndTime(DateTime endTime) => EndExecutionTime = endTime; /// - /// Sets command + /// Sets command. /// /// - internal void SetCommand(string command) - { - _cmdline = command; - } - - #endregion internal - - #region private - - /// - /// Id of the pipeline corresponding to this history entry - /// - private long _pipelineId; - - /// - /// Id of the history entry - /// - private long _id; + internal void SetCommand(string command) => CommandLine = command; /// - /// CommandLine string + /// Id of the pipeline corresponding to this history entry. /// - private string _cmdline; - - /// - /// ExecutionStatus of execution - /// - private PipelineState _status; - - /// - /// Start time of execution - /// - private DateTime _startTime; - - /// - ///End time of execution - /// - private DateTime _endTime; - - - - /// - /// Flag indicating an entry is present/cleared - /// - - private bool _cleared = false; - - #endregion private - - #region ICloneable Members + private readonly long _pipelineId; /// - /// Returns a clone of this object + /// Returns a clone of this object. /// /// public HistoryInfo Clone() { return new HistoryInfo(this); } - - #endregion } - /// /// This class implements history and provides APIs for adding and fetching - /// entries from history + /// entries from history. /// internal class History { /// - /// Default history size + /// Default history size. /// internal const int DefaultHistorySize = 4096; #region constructors /// - /// Constructs history store + /// Constructs history store. /// - internal History(ExecutionContext context) { - //Create history size variable. Add ValidateRangeAttribute to - //validate the range. + // Create history size variable. Add ValidateRangeAttribute to + // validate the range. Collection attrs = new Collection(); attrs.Add(new ValidateRangeAttribute(1, (int)Int16.MaxValue)); PSVariable historySizeVar = new PSVariable(SpecialVariables.HistorySize, DefaultHistorySize, ScopedItemOptions.None, attrs); @@ -294,16 +187,16 @@ internal History(ExecutionContext context) #region internal /// - /// Create a new history entry + /// Create a new history entry. /// /// /// /// /// /// - /// If true, the entry will not be added when the history is locked - /// id for the new created entry. Use this id to fetch the - /// entry. Returns -1 if the entry is not added + /// If true, the entry will not be added when the history is locked. + /// Id for the new created entry. Use this id to fetch the + /// entry. Returns -1 if the entry is not added. /// This function is thread safe internal long AddEntry(long pipelineId, string cmdline, PipelineState status, DateTime startTime, DateTime endTime, bool skipIfLocked) { @@ -328,10 +221,10 @@ internal long AddEntry(long pipelineId, string cmdline, PipelineState status, Da /// /// Update the history entry corresponding to id. /// - /// id of history entry to be updated - /// status to be updated - /// endTime to be updated - /// If true, the entry will not be added when the history is locked + /// Id of history entry to be updated. + /// Status to be updated. + /// EndTime to be updated. + /// If true, the entry will not be added when the history is locked. /// internal void UpdateEntry(long id, PipelineState status, DateTime endTime, bool skipIfLocked) { @@ -359,8 +252,8 @@ internal void UpdateEntry(long id, PipelineState status, DateTime endTime, bool /// Gets entry from buffer for given id. This id should be the /// id returned by Add method. /// - /// Id of the entry to be fetched - /// entry corresponding to id if it is present else null + /// Id of the entry to be fetched. + /// Entry corresponding to id if it is present else null /// internal HistoryInfo GetEntry(long id) { @@ -370,65 +263,62 @@ internal HistoryInfo GetEntry(long id) HistoryInfo entry = CoreGetEntry(id); if (entry != null) - if (entry.Cleared == false) + if (!entry.Cleared) return entry.Clone(); return null; } } - - /// - /// Get count HistoryEntries + /// Get count HistoryEntries. /// /// /// /// - /// history entries + /// History entries. internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) { ReallocateBufferIfNeeded(); if (count < -1) { - throw PSTraceSource.NewArgumentOutOfRangeException("count", count); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(count), count); } if (newest.ToString() == null) { - throw PSTraceSource.NewArgumentNullException("newest"); + throw PSTraceSource.NewArgumentNullException(nameof(newest)); } - if (count == -1 || count > _countEntriesAdded || count > _countEntriesInBuffer) count = _countEntriesInBuffer; if (count == 0 || _countEntriesInBuffer == 0) { - return Utils.EmptyArray(); + return Array.Empty(); } lock (_syncRoot) { - //Using list instead of an array to store the entries.With array we are getting null values - //when the historybuffer size is changed + // Using list instead of an array to store the entries.With array we are getting null values + // when the historybuffer size is changed List entriesList = new List(); if (id > 0) { long firstId, baseId; baseId = id; - //get id,count,newest values + // get id,count,newest values if (!newest.IsPresent) { - //get older entries + // get older entries - //Calculate the first id (i.e lowest id to fetch) + // Calculate the first id (i.e lowest id to fetch) firstId = baseId - count + 1; - //If first id is less than the lowest id in history store, - //assign lowest id as first ID + // If first id is less than the lowest id in history store, + // assign lowest id as first ID if (firstId < 1) { firstId = 1; @@ -439,7 +329,7 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) if (firstId <= 1) break; // if entry is null , continue the loop with the next entry if (_buffer[GetIndexFromId(i)] == null) continue; - if (_buffer[GetIndexFromId(i)].Cleared == true) + if (_buffer[GetIndexFromId(i)].Cleared) { // we have to clear count entries before an id, so if an entry is null,decrement // first id as long as its is greater than the lowest entry in the buffer. @@ -450,15 +340,15 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) for (long i = firstId; i <= baseId; ++i) { - //if an entry is null after being cleared by clear-history cmdlet, - //continue with the next entry - if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared == true) + // if an entry is null after being cleared by clear-history cmdlet, + // continue with the next entry + if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared) continue; entriesList.Add(_buffer[GetIndexFromId(i)].Clone()); } } else - { //get latest entries + { // get latest entries // first id becomes the id +count no of entries from the end of the buffer firstId = baseId + count - 1; // if first id is more than the no of entries in the buffer, first id will be the last entry in the buffer @@ -472,7 +362,7 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) if (firstId >= _countEntriesAdded) break; // if entry is null , continue the loop with the next entry if (_buffer[GetIndexFromId(i)] == null) continue; - if (_buffer[GetIndexFromId(i)].Cleared == true) + if (_buffer[GetIndexFromId(i)].Cleared) { // we have to clear count entries before an id, so if an entry is null,increment first id firstId++; @@ -482,9 +372,9 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) for (long i = firstId; i >= baseId; --i) { - //if an entry is null after being cleared by clear-history cmdlet, - //continue with the next entry - if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared == true) + // if an entry is null after being cleared by clear-history cmdlet, + // continue with the next entry + if (_buffer[GetIndexFromId(i)] == null || _buffer[GetIndexFromId(i)].Cleared) continue; entriesList.Add(_buffer[GetIndexFromId(i)].Clone()); } @@ -492,28 +382,29 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) } else { - //get entries for count,newest + // get entries for count,newest long index, SmallestID = 0; - //if we change the defaulthistory size and when no of entries exceed the size, then - //we need to get the smallest entry in the buffer when we want to clear the oldest entry - //eg if size is 5 and then the entries can be 7,6,1,2,3 + // if we change the defaulthistory size and when no of entries exceed the size, then + // we need to get the smallest entry in the buffer when we want to clear the oldest entry + // eg if size is 5 and then the entries can be 7,6,1,2,3 if (_capacity != DefaultHistorySize) SmallestID = SmallestIDinBuffer(); if (!newest.IsPresent) { - //get oldest count entries + // get oldest count entries index = 1; if (_capacity != DefaultHistorySize) { if (_countEntriesAdded > _capacity) index = SmallestID; } + for (long i = count - 1; i >= 0;) { if (index > _countEntriesAdded) break; if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) || - (_buffer[GetIndexFromId(index)].Cleared == true)) + (_buffer[GetIndexFromId(index)].Cleared)) { index++; continue; } @@ -526,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;) { @@ -539,25 +430,26 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) break; } } + if (index < 1) break; if ((index <= 0 || GetIndexFromId(index) >= _buffer.Length) || - (_buffer[GetIndexFromId(index)].Cleared == true)) + (_buffer[GetIndexFromId(index)].Cleared)) { index--; continue; } else { - //clone the entry from the history buffer + // clone the entry from the history buffer entriesList.Add(_buffer[GetIndexFromId(index)].Clone()); i--; index--; } } } } + HistoryInfo[] entries = new HistoryInfo[entriesList.Count]; entriesList.CopyTo(entries); return entries; - }// end lock - }// end function - + } + } /// /// Get History Entries based on the WildCard Pattern value. @@ -573,19 +465,22 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S { if (count < -1) { - throw PSTraceSource.NewArgumentOutOfRangeException("count", count); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(count), count); } + if (newest.ToString() == null) { - throw PSTraceSource.NewArgumentNullException("newest"); + throw PSTraceSource.NewArgumentNullException(nameof(newest)); } + if (count > _countEntriesAdded || count == -1) { count = _countEntriesInBuffer; } + List cmdlist = new List(); long SmallestID = 1; - //if buffersize is changes,Get the smallest entry that's not cleared in the buffer + // if buffersize is changes,Get the smallest entry that's not cleared in the buffer if (_capacity != DefaultHistorySize) SmallestID = SmallestIDinBuffer(); if (count != 0) @@ -598,13 +493,15 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S if (_countEntriesAdded > _capacity) id = SmallestID; } + 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++; } + id++; } } @@ -613,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) @@ -622,11 +519,13 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S break; } } + 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++; } + id--; } } @@ -635,33 +534,31 @@ 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()); } } } + HistoryInfo[] entries = new HistoryInfo[cmdlist.Count]; cmdlist.CopyTo(entries); return entries; } } - - - /// /// Clears the history entry from buffer for a given id. /// - /// Id of the entry to be Cleared - /// nothing + /// Id of the entry to be Cleared. + /// Nothing. internal void ClearEntry(long id) { lock (_syncRoot) { if (id < 0) { - throw PSTraceSource.NewArgumentOutOfRangeException("id", id); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(id), id); } // no entries are present to clear if (_countEntriesInBuffer == 0) @@ -679,51 +576,48 @@ internal void ClearEntry(long id) entry.Cleared = true; _countEntriesInBuffer--; } + return; } } - - /// + /// /// gets the total number of entries added - /// - ///count of total entries added - + /// + /// count of total entries added. internal int Buffercapacity() { return _capacity; } - #endregion internal #region private /// /// Adds an entry to the buffer. If buffer is full, overwrites - /// oldest entry in the buffer + /// oldest entry in the buffer. /// /// /// Returns id for the entry. This id should be used to fetch - /// the entry from the buffer + /// 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) { - throw PSTraceSource.NewArgumentNullException("entry"); + throw PSTraceSource.NewArgumentNullException(nameof(entry)); } _buffer[GetIndexForNewEntry()] = entry; - //Increment count of entries added so far + // Increment count of entries added so far _countEntriesAdded++; - //Id of an entry in history is same as its number in history store. + // Id of an entry in history is same as its number in history store. entry.SetId(_countEntriesAdded); - //Increment count of entries in buffer by 1 + // Increment count of entries in buffer by 1 IncrementCountOfEntriesInBuffer(); return _countEntriesAdded; @@ -733,15 +627,16 @@ private long Add(HistoryInfo entry) /// Gets entry from buffer for given id. This id should be the /// id returned by Add method. /// - /// Id of the entry to be fetched - /// entry corresponding to id if it is present else null + /// Id of the entry to be fetched. + /// Entry corresponding to id if it is present else null /// private HistoryInfo CoreGetEntry(long id) { if (id <= 0) { - throw PSTraceSource.NewArgumentOutOfRangeException("id", id); + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(id), id); } + if (_countEntriesInBuffer == 0) return null; if (id > _countEntriesAdded) @@ -755,7 +650,7 @@ private HistoryInfo CoreGetEntry(long id) } /// - /// Gets the smallest id in the buffer + /// Gets the smallest id in the buffer. /// /// private long SmallestIDinBuffer() @@ -765,29 +660,30 @@ private long SmallestIDinBuffer() return minID; 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) + // assign the first entry in the buffer as min. + if (_buffer[i] != null && !_buffer[i].Cleared) { minID = _buffer[i].Id; break; } } - //check for the minimum id that is not cleared + // 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; } + return minID; } /// - /// Reallocates the buffer if history size changed + /// Reallocates the buffer if history size changed. /// private void ReallocateBufferIfNeeded() { - //Get current value of histoysize variable + // Get current value of histoysize variable int historySize = GetHistorySize(); if (historySize == _capacity) @@ -795,10 +691,10 @@ private void ReallocateBufferIfNeeded() HistoryInfo[] tempBuffer = new HistoryInfo[historySize]; - //Calculate number of entries to copy in new buffer. + // Calculate number of entries to copy in new buffer. int numberOfEntries = _countEntriesInBuffer; - //when buffer size is changed,we have to consider the totalnumber of entries added + // when buffer size is changed,we have to consider the totalnumber of entries added if (numberOfEntries < _countEntriesAdded) numberOfEntries = (int)_countEntriesAdded; @@ -818,16 +714,16 @@ private void ReallocateBufferIfNeeded() } /// - /// Get the index for new entry + /// Get the index for new entry. /// - /// Index for new entry + /// Index for new entry. private int GetIndexForNewEntry() { return (int)(_countEntriesAdded % _capacity); } /// - /// Gets index in buffer for an entry with given Id + /// Gets index in buffer for an entry with given Id. /// /// private int GetIndexFromId(long id) @@ -837,7 +733,7 @@ private int GetIndexFromId(long id) /// /// Gets index in buffer for an entry with given Id using passed in - /// capacity + /// capacity. /// /// /// @@ -848,7 +744,7 @@ private static int GetIndexFromId(long id, int capacity) } /// - /// Increment number of entries in buffer by 1 + /// Increment number of entries in buffer by 1. /// private void IncrementCountOfEntriesInBuffer() { @@ -856,16 +752,15 @@ private void IncrementCountOfEntriesInBuffer() _countEntriesInBuffer++; } - /// - /// Get the current history size + /// 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 @@ -885,69 +780,93 @@ private int GetHistorySize() } /// - /// buffer + /// Buffer. /// private HistoryInfo[] _buffer; /// - /// Capacity of circular buffer + /// Capacity of circular buffer. /// private int _capacity; /// - /// Number of entries in buffer currently + /// Number of entries in buffer currently. /// private int _countEntriesInBuffer; /// - /// total number of entries added till now including those which have + /// Total number of entries added till now including those which have /// been overwritten after buffer got full. This is also number of /// last entry added. /// private long _countEntriesAdded; - /// - /// Private object for synchronization + /// Private object for synchronization. /// - private object _syncRoot = new object(); + private readonly object _syncRoot = new object(); #endregion private /// - /// return the ID of the next history item to be added + /// Return the ID of the next history item to be added. /// 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 + } /// - /// This class Implements the get-history command + /// This class Implements the get-history command. /// - [Cmdlet(VerbsCommon.Get, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113317")] + [Cmdlet(VerbsCommon.Get, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096788")] [OutputType(typeof(HistoryInfo))] public class GetHistoryCommand : PSCmdlet { /// - /// Ids of entries to display + /// Ids of entries to display. /// private long[] _id; /// - /// Ids of entries to display + /// Ids of entries to display. /// /// [Parameter(Position = 0, ValueFromPipeline = true)] - [ValidateRangeAttribute((long)1, long.MaxValue)] + [ValidateRange((long)1, long.MaxValue)] public long[] Id { get { return _id; } + set { _id = value; @@ -955,7 +874,7 @@ public long[] Id } /// - /// Is Count parameter specified + /// Is Count parameter specified. /// private bool _countParameterSpecified; /// @@ -968,13 +887,14 @@ 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 { return _count; } + set { _countParameterSpecified = true; @@ -982,9 +902,8 @@ public int Count } } - /// - /// Implements the Processing() method for show/History command + /// Implements the Processing() method for show/History command. /// protected override void ProcessRecord() { @@ -994,8 +913,8 @@ protected override void ProcessRecord() { if (!_countParameterSpecified) { - //If Id parameter is specified and count is not specified, - //get history + // If Id parameter is specified and count is not specified, + // get history foreach (long id in _id) { Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); @@ -1061,6 +980,7 @@ protected override void ProcessRecord() { _count = history.Buffercapacity(); } + HistoryInfo[] entries = history.GetEntries(0, _count, true); for (long i = entries.Length - 1; i >= 0; i--) WriteObject(entries[i]); @@ -1069,9 +989,9 @@ protected override void ProcessRecord() } /// - /// This class implements the Invoke-History command + /// This class implements the Invoke-History command. /// - [Cmdlet(VerbsLifecycle.Invoke, "History", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113344")] + [Cmdlet(VerbsLifecycle.Invoke, "History", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096586")] public class InvokeHistoryCommand : PSCmdlet { #region Parameters @@ -1099,115 +1019,92 @@ public string Id { return _id; } + set { if (_id != null) { - //Id has been set already. + // Id has been set already. _multipleIdProvided = true; } + _id = value; } } #endregion - /// - /// Implements the BeginProcessing() method for eval/History command + /// Implements the BeginProcessing() method for eval/History command. /// protected override void EndProcessing() { - //Invoke-history can execute only one command. If multiple - //ids were provided, throw exception - if (_multipleIdProvided == true) + // Invoke-history can execute only one command. If multiple + // 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 + // 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 + // Echo command Host.UI.WriteLine(commandToInvoke); } catch (HostException) { - //when the host is not interactive, HostException is thrown - //do nothing + // when the host is not interactive, HostException is thrown + // do nothing } - // Items invoked as History should act as though they were submitted by the user - so should still come from // the runspace itself. For this reason, it is insufficient to just use the InvokeScript method on the Cmdlet class. using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) { 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; @@ -1216,17 +1113,34 @@ protected override void EndProcessing() ps.Streams.Verbose.DataAdded += verboseAdded; ps.Streams.Warning.DataAdded += warningAdded; + LocalRunspace localRunspace = ps.Runspace as LocalRunspace; + try { + // Indicate to the system that we are in nested prompt mode, since we are emulating running the command at the prompt. + // This ensures that the command being run as nested runs in the correct language mode, because CreatePipelineProcessor() + // always forces CommandOrigin to Internal for nested running commands, and Command.CreateCommandProcessor() forces Internal + // commands to always run in FullLanguage mode unless in a nested prompt. + if (localRunspace != null) + { + localRunspace.InInternalNestedPrompt = ps.IsNested; + } + Collection results = ps.Invoke(); if (results.Count > 0) { WriteObject(results, true); } - pipeline.RemoveFromInvokeHistoryEntryList(entry); } finally { + history.RemoveFromInvokeHistoryEntrySet(entry); + + if (localRunspace != null) + { + localRunspace.InInternalNestedPrompt = false; + } + ps.Streams.Debug.DataAdded -= debugAdded; ps.Streams.Error.DataAdded -= errorAdded; ps.Streams.Information.DataAdded -= informationAdded; @@ -1238,15 +1152,15 @@ protected override void EndProcessing() } /// - /// Helper function which gets history entry to invoke + /// Helper function which gets history entry to invoke. /// [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "It's ok to use ID in the ArgumentException")] private HistoryInfo GetHistoryEntryToInvoke(History history) { HistoryInfo entry = null; - //User didn't specify any input parameter. Invoke the last - //entry + // User didn't specify any input parameter. Invoke the last + // entry if (_id == null) { HistoryInfo[] entries = history.GetEntries(0, 1, true); @@ -1277,10 +1191,10 @@ private HistoryInfo GetHistoryEntryToInvoke(History history) } else { - //Parse input + // Parse input PopulateIdAndCommandLine(); - //User specified a commandline. Get list of all history entries - //and find latest match + // User specified a commandline. Get list of all history entries + // and find latest match if (_commandLine != null) { HistoryInfo[] entries = history.GetEntries(0, -1, false); @@ -1288,12 +1202,13 @@ private HistoryInfo GetHistoryEntryToInvoke(History history) // and search backwards through the entries for (int i = entries.Length - 1; i >= 0; i--) { - if (entries[i].CommandLine.StartsWith(_commandLine, StringComparison.CurrentCulture)) + if (entries[i].CommandLine.StartsWith(_commandLine, StringComparison.Ordinal)) { entry = entries[i]; break; } } + if (entry == null) { Exception ex = @@ -1338,7 +1253,7 @@ private HistoryInfo GetHistoryEntryToInvoke(History history) } else { - //Retrieve the command at the index we've specified + // Retrieve the command at the index we've specified entry = history.GetEntry(_historyId); if (entry == null || entry.Id != _historyId) { @@ -1362,10 +1277,10 @@ private HistoryInfo GetHistoryEntryToInvoke(History history) } } } + return entry; } - /// /// Id of history entry to execute. /// @@ -1377,7 +1292,7 @@ private HistoryInfo GetHistoryEntryToInvoke(History history) private string _commandLine; /// - /// Parse Id parameter to populate _historyId and _commandLine + /// Parse Id parameter to populate _historyId and _commandLine. /// private void PopulateIdAndCommandLine() { @@ -1401,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; @@ -1412,57 +1326,56 @@ private void ReplaceHistoryString(HistoryInfo entry) } } - /// - /// This class Implements the add-history command + /// This class Implements the add-history command. /// - [Cmdlet(VerbsCommon.Add, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113279")] + [Cmdlet(VerbsCommon.Add, "History", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096479")] [OutputType(typeof(HistoryInfo))] public class AddHistoryCommand : PSCmdlet { #region parameters /// - /// This parameter specifies the current pipeline object + /// 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; } + set { _passthru = value; } } #endregion parameters /// - /// override for BeginProcessing + /// Override for BeginProcessing. /// protected override void BeginProcessing() { - //Get currently running pipeline and add history entry for - //this pipeline. - //Note:Generally History entry for current pipeline is added - //on completion of pipeline (See LocalPipeline implementation). - //However Add-history adds additional entries in to history and - //additional entries must be added after history for current pipeline. - //This is done by adding the history entry for current pipeline below. + // Get currently running pipeline and add history entry for + // this pipeline. + // Note:Generally History entry for current pipeline is added + // on completion of pipeline (See LocalPipeline implementation). + // However Add-history adds additional entries in to history and + // additional entries must be added after history for current pipeline. + // This is done by adding the history entry for current pipeline below. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); lpl.AddHistoryEntryFromAddHistoryCmdlet(); } - /// - /// override for ProcessRecord + /// Override for ProcessRecord. /// protected override @@ -1475,8 +1388,8 @@ void ProcessRecord() { foreach (PSObject input in InputObject) { - //Wrap the inputobject in PSObject and convert it to - //HistoryInfo object. + // Wrap the inputobject in PSObject and convert it to + // HistoryInfo object. HistoryInfo infoToAdd = GetHistoryInfoObject(input); if (infoToAdd != null) { @@ -1501,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. /// /// @@ -1521,120 +1434,44 @@ void ProcessRecord() { break; } - //Read CommandLine property - string commandLine = GetPropertyValue(mshObject, "CommandLine") as string; - if (commandLine == null) - { - break; - } - //Read ExecutionStatus property - object pipelineState = GetPropertyValue(mshObject, "ExecutionStatus"); - if (pipelineState == null) + // Read CommandLine property + if (GetPropertyValue(mshObject, "CommandLine") is not string commandLine) { 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 + // Read ExecutionStatus property + object pipelineState = GetPropertyValue(mshObject, "ExecutionStatus"); + if (pipelineState == null || !LanguagePrimitives.TryConvertTo(pipelineState, out PipelineState executionStatus)) { break; } - //Read StartExecutionTime property - DateTime startExecutionTime; + // Read StartExecutionTime property 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; + // Read EndExecutionTime property 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 ( @@ -1667,12 +1504,10 @@ private static } } - - /// + /// /// This Class implements the Clear History cmdlet - /// - - [Cmdlet(VerbsCommon.Clear, "History", SupportsShouldProcess = true, DefaultParameterSetName = "IDParameter", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135199")] + /// + [Cmdlet(VerbsCommon.Clear, "History", SupportsShouldProcess = true, DefaultParameterSetName = "IDParameter", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096691")] public class ClearHistoryCommand : PSCmdlet { #region Command Line Parameters @@ -1683,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 { @@ -1691,6 +1526,7 @@ public int[] Id { return _id; } + set { _id = value; @@ -1698,17 +1534,15 @@ public int[] Id } /// - /// id of a history entry + /// Id of a history entry. /// - private int[] _id; /// - /// command line name of an entry in the session history + /// 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 { @@ -1716,6 +1550,7 @@ public string[] CommandLine { return _commandline; } + set { _commandline = value; @@ -1723,23 +1558,22 @@ public string[] CommandLine } /// - /// commandline parameter + /// 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 { return _count; } + set { _countParameterSpecified = true; @@ -1748,19 +1582,18 @@ public int Count } /// - /// count of the history entries + /// Count of the history entries. /// private int _count = 32; /// - /// a boolean variable to indicate if the count parameter specified + /// A boolean variable to indicate if the count parameter specified. /// private bool _countParameterSpecified = false; /// /// 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 { @@ -1768,6 +1601,7 @@ public SwitchParameter Newest { return _newest; } + set { _newest = value; @@ -1775,30 +1609,27 @@ public SwitchParameter Newest } /// - /// switch parameter on the history entries + /// Switch parameter on the history entries. /// - private SwitchParameter _newest; #endregion Command Line Parameters /// - /// Overriding Begin Processing + /// Overriding Begin Processing. /// - protected override void BeginProcessing() { _history = ((LocalRunspace)Context.CurrentRunspace).History; } /// - /// Overriding Process Record + /// Overriding Process Record. /// - protected override void ProcessRecord() { - //case statement to identify the parameter set - switch (ParameterSetName.ToString()) + // case statement to identify the parameter set + switch (ParameterSetName) { case "IDParameter": ClearHistoryByID(); @@ -1820,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) @@ -1897,8 +1728,8 @@ private void ClearHistoryByID() ); } else - {// if id,count adn newest parameters are present - //throw an exception for invalid count values + {// if id,count and newest parameters are present + // throw an exception for invalid count values long id = _id[0]; Dbg.Assert(id > 0, "ValidateRangeAttribute should not allow this"); @@ -1908,13 +1739,14 @@ 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; } + ClearHistoryEntries(0, -1, null, _newest); } else @@ -1924,12 +1756,11 @@ 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 @@ -1989,16 +1820,14 @@ 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. @@ -2026,6 +1855,7 @@ private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParam ) ); } + _entries = _history.GetEntries(id, count, newest); } else @@ -2035,9 +1865,9 @@ private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParam } else { - //creates a wild card pattern + // creates a wild card pattern WildcardPattern wildcardpattern = WildcardPattern.Get(cmdline, WildcardOptions.IgnoreCase); - //count set to zero if not specified. + // count set to zero if not specified. if (!_countParameterSpecified && WildcardPattern.ContainsWildcardCharacters(cmdline)) { count = 0; @@ -2045,31 +1875,28 @@ private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParam // Return the matching history entries for the command line parameter // if newest id false...gets the oldest entry _entries = _history.GetEntries(wildcardpattern, count, newest); - }// end case cmdline + } - //Clear the History value. + // Clear the History value. foreach (HistoryInfo entry in _entries) { - if (entry != null && entry.Cleared == false) + if (entry != null && !entry.Cleared) _history.ClearEntry(entry.Id); } return; - }//end function - + } /// - /// history obj + /// History obj. /// private History _history; /// - /// array of historyinfo objects + /// Array of historyinfo objects. /// private HistoryInfo[] _entries; #endregion Private } } - - diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index e5f8688bae4..8e5d30be71f 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -1,27 +1,19 @@ -using System.Collections; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; -using Microsoft.PowerShell.Commands; +using System.Globalization; using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; using System.Management.Automation.Internal; -using System.Management.Automation.Language; -using System.Text; -using System.Text.RegularExpressions; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Subsystem.Feedback; using System.Runtime.InteropServices; -using System.Security; -using System.Globalization; +using System.Text; +using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { - internal enum SuggestionMatchType - { - Command = 0, - Error = 1, - Dynamic = 2 - } - #region Public HostUtilities Class /// @@ -31,42 +23,26 @@ public static class HostUtilities { #region Internal Access - private static string s_checkForCommandInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param() + private static readonly char s_actionIndicator = HostSupportUnicode() ? '\u27a4' : '>'; - $foundSuggestion = $false - - if($lastError -and - ($lastError.Exception -is ""System.Management.Automation.CommandNotFoundException"")) + private static bool HostSupportUnicode() + { + // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - $escapedCommand = [System.Management.Automation.WildcardPattern]::Escape($lastError.TargetObject) - $foundSuggestion = @(Get-Command ($ExecutionContext.SessionState.Path.Combine(""."", $escapedCommand)) -ErrorAction Ignore).Count -gt 0 + 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"; } - $foundSuggestion - "; - - private static string s_createCommandExistsInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) - - $formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)"" - "; + string ctype = Environment.GetEnvironmentVariable("LC_ALL") ?? + Environment.GetEnvironmentVariable("LC_CTYPE") ?? + Environment.GetEnvironmentVariable("LANG") ?? + string.Empty; - private static ArrayList s_suggestions = new ArrayList( - new Hashtable[] { - NewSuggestion(1, "Transactions", SuggestionMatchType.Command, "^Start-Transaction", - SuggestionStrings.Suggestion_StartTransaction, true), - NewSuggestion(2, "Transactions", SuggestionMatchType.Command, "^Use-Transaction", - SuggestionStrings.Suggestion_UseTransaction, true), - NewSuggestion(3, "General", SuggestionMatchType.Dynamic, - ScriptBlock.CreateDelayParsedScriptBlock(s_checkForCommandInCurrentDirectoryScript, isProductCode: true), - ScriptBlock.CreateDelayParsedScriptBlock(s_createCommandExistsInCurrentDirectoryScript, isProductCode: true), - new object[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory) }, - true) - } - ); + return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); + } #region GetProfileCommands /// @@ -87,27 +63,16 @@ 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 + /// Gets the object that serves as a value to $profile and the paths on it. /// /// The id identifying the host or shell used in profile file names. - /// used from test not to overwrite the profile file names from development boxes - /// path for all users and all hosts - /// path for current user and all hosts - /// path for all users current host - /// path for current user and current host - /// the object that serves as a value to $profile + /// Used from test not to overwrite the profile file names from development boxes. + /// Path for all users and all hosts. + /// Path for current user and all hosts. + /// Path for all users current host. + /// Path for current user and current host. + /// The object that serves as a value to $profile. /// internal static void GetProfileObjectData(string shellId, bool useTestProfile, out string allUsersAllHosts, out string allUsersCurrentHost, out string currentUserAllHosts, out string currentUserCurrentHost, out PSObject dollarProfile) { @@ -122,7 +87,7 @@ internal static void GetProfileObjectData(string shellId, bool useTestProfile, o /// 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. - /// used from test not to overwrite the profile file names from development boxes + /// Used from test not to overwrite the profile file names from development boxes. /// internal static PSCommand[] GetProfileCommands(string shellId, bool useTestProfile) { @@ -145,6 +110,7 @@ internal static PSCommand[] GetProfileCommands(string shellId, bool useTestProfi { continue; } + command = new PSCommand(); command.AddCommand(profilePath, false); commands.Add(command); @@ -156,8 +122,8 @@ internal static PSCommand[] GetProfileCommands(string shellId, bool useTestProfi /// /// Used to get all profile file names for the current or all hosts and for the current or all users. /// - /// null for all hosts, not null for the specified host - /// false for all users, true for the current user. + /// Null for all hosts, not null for the specified host. + /// False for all users, true for the current user. /// The profile file name matching the parameters. internal static string GetFullProfileFileName(string shellId, bool forCurrentUser) { @@ -167,9 +133,9 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse /// /// Used to get all profile file names for the current or all hosts and for the current or all users. /// - /// null for all hosts, not null for the specified host - /// false for all users, true for the current user. - /// used from test not to overwrite the profile file names from development boxes + /// Null for all hosts, not null for the specified host. + /// False for all users, true for the current user. + /// Used from test not to overwrite the profile file names from development boxes. /// The profile file name matching the parameters. internal static string GetFullProfileFileName(string shellId, bool forCurrentUser, bool useTestProfile) { @@ -177,15 +143,16 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse if (forCurrentUser) { - basePath = Utils.GetUserConfigurationDirectory(); + basePath = Platform.ConfigDirectory; } else { basePath = GetAllUsersFolderPath(shellId); - if (string.IsNullOrEmpty(basePath)) - { - return ""; - } + } + + if (string.IsNullOrEmpty(basePath)) + { + return string.Empty; } string profileName = useTestProfile ? "profile_test.ps1" : "profile.ps1"; @@ -204,7 +171,7 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse /// Used internally in GetFullProfileFileName to get the base path for all users profiles. /// /// The shellId to use. - /// the base path for all users profiles. + /// The base path for all users profiles. private static string GetAllUsersFolderPath(string shellId) { string folderPath = string.Empty; @@ -223,14 +190,14 @@ private static string GetAllUsersFolderPath(string shellId) /// /// Gets the first lines of . /// - /// string we want to limit the number of lines - /// maximum number of lines to be returned + /// String we want to limit the number of lines. + /// Maximum number of lines to be returned. /// The first lines of . internal static string GetMaxLines(string source, int maxLines) { - if (String.IsNullOrEmpty(source)) + if (string.IsNullOrEmpty(source)) { - return String.Empty; + return string.Empty; } StringBuilder returnValue = new StringBuilder(); @@ -248,7 +215,7 @@ internal static string GetMaxLines(string source, int maxLines) if (lineCount == maxLines) { - returnValue.Append("..."); + returnValue.Append(PSObjectHelper.Ellipsis); break; } } @@ -256,510 +223,6 @@ internal static string GetMaxLines(string source, int maxLines) return returnValue.ToString(); } - internal static ArrayList GetSuggestion(Runspace runspace) - { - LocalRunspace localRunspace = runspace as LocalRunspace; - if (localRunspace == null) { return new ArrayList(); } - - // 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 ArrayList(); - - 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; - } - - ArrayList 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 ArrayList GetSuggestion(HistoryInfo lastHistory, Object lastError, ArrayList errorList) - { - ArrayList returnSuggestions = new ArrayList(); - - 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 - { - 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. - /// - 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 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); - } - } - - internal static PSCredential CredUIPromptForCredential( - string caption, - string message, - string userName, - string targetName, - PSCredentialTypes allowedCredentialTypes, - PSCredentialUIOptions options, - IntPtr parentHWND) - { - PSCredential cred = null; - - // From WinCred.h - const int CRED_MAX_USERNAME_LENGTH = (256 + 1 + 256); - const int CRED_MAX_CREDENTIAL_BLOB_SIZE = 512; - const int CRED_MAX_PASSWORD_LENGTH = CRED_MAX_CREDENTIAL_BLOB_SIZE / 2; - const int CREDUI_MAX_MESSAGE_LENGTH = 1024; - const int CREDUI_MAX_CAPTION_LENGTH = 128; - - // Populate the UI text with defaults, if required - if (string.IsNullOrEmpty(caption)) - { - caption = CredUI.PromptForCredential_DefaultCaption; - } - - if (string.IsNullOrEmpty(message)) - { - message = CredUI.PromptForCredential_DefaultMessage; - } - - if (caption.Length > CREDUI_MAX_CAPTION_LENGTH) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, CredUI.PromptForCredential_InvalidCaption, CREDUI_MAX_CAPTION_LENGTH)); - } - - if (message.Length > CREDUI_MAX_MESSAGE_LENGTH) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, CredUI.PromptForCredential_InvalidMessage, CREDUI_MAX_MESSAGE_LENGTH)); - } - - if (userName != null && userName.Length > CRED_MAX_USERNAME_LENGTH) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, CredUI.PromptForCredential_InvalidUserName, CRED_MAX_USERNAME_LENGTH)); - } - - CREDUI_INFO credUiInfo = new CREDUI_INFO(); - credUiInfo.pszCaptionText = caption; - credUiInfo.pszMessageText = message; - - StringBuilder usernameBuilder = new StringBuilder(userName, CRED_MAX_USERNAME_LENGTH); - StringBuilder passwordBuilder = new StringBuilder(CRED_MAX_PASSWORD_LENGTH); - - bool save = false; - int saveCredentials = Convert.ToInt32(save); - credUiInfo.cbSize = Marshal.SizeOf(credUiInfo); - credUiInfo.hwndParent = parentHWND; - - - CREDUI_FLAGS flags = CREDUI_FLAGS.DO_NOT_PERSIST; - - // Set some of the flags if they have not requested a domain credential - if ((allowedCredentialTypes & PSCredentialTypes.Domain) != PSCredentialTypes.Domain) - { - flags |= CREDUI_FLAGS.GENERIC_CREDENTIALS; - - // If they've asked to always prompt, do so. - if ((options & PSCredentialUIOptions.AlwaysPrompt) == PSCredentialUIOptions.AlwaysPrompt) - flags |= CREDUI_FLAGS.ALWAYS_SHOW_UI; - } - - // To prevent buffer overrun attack, only attempt call if buffer lengths are within bounds. - CredUIReturnCodes result = CredUIReturnCodes.ERROR_INVALID_PARAMETER; - if (usernameBuilder.Length <= CRED_MAX_USERNAME_LENGTH && passwordBuilder.Length <= CRED_MAX_PASSWORD_LENGTH) - { - result = CredUIPromptForCredentials( - ref credUiInfo, - targetName, - IntPtr.Zero, - 0, - usernameBuilder, - CRED_MAX_USERNAME_LENGTH, - passwordBuilder, - CRED_MAX_PASSWORD_LENGTH, - ref saveCredentials, - flags); - } - - if (result == CredUIReturnCodes.NO_ERROR) - { - // Extract the username - string credentialUsername = null; - if (usernameBuilder != null) - credentialUsername = usernameBuilder.ToString(); - - // Trim the leading '\' from the username, which CredUI automatically adds - // if you don't specify a domain. - // This is a really common bug in V1 and V2, causing everybody to have to do - // it themselves. - // This could be a breaking change for hosts that do hard-coded hacking: - // $cred.UserName.SubString(1, $cred.Username.Length - 1) - // But that's OK, because they would have an even worse bug when you've - // set the host (ConsolePrompting = true) configuration (which does not do this). - credentialUsername = credentialUsername.TrimStart('\\'); - - // Extract the password into a SecureString, zeroing out the memory - // as soon as possible. - SecureString password = new SecureString(); - for (int counter = 0; counter < passwordBuilder.Length; counter++) - { - password.AppendChar(passwordBuilder[counter]); - passwordBuilder[counter] = (char)0; - } - - if (!String.IsNullOrEmpty(credentialUsername)) - cred = new PSCredential(credentialUsername, password); - else - cred = null; - } - else // result is not CredUIReturnCodes.NO_ERROR - { - cred = null; - } - - return cred; - } - - [DllImport("credui", EntryPoint = "CredUIPromptForCredentialsW", CharSet = CharSet.Unicode)] - private static extern CredUIReturnCodes CredUIPromptForCredentials(ref CREDUI_INFO pUiInfo, - string pszTargetName, IntPtr Reserved, int dwAuthError, StringBuilder pszUserName, - int ulUserNameMaxChars, StringBuilder pszPassword, int ulPasswordMaxChars, ref int pfSave, CREDUI_FLAGS dwFlags); - - [Flags] - private enum CREDUI_FLAGS - { - INCORRECT_PASSWORD = 0x1, - DO_NOT_PERSIST = 0x2, - REQUEST_ADMINISTRATOR = 0x4, - EXCLUDE_CERTIFICATES = 0x8, - REQUIRE_CERTIFICATE = 0x10, - SHOW_SAVE_CHECK_BOX = 0x40, - ALWAYS_SHOW_UI = 0x80, - REQUIRE_SMARTCARD = 0x100, - PASSWORD_ONLY_OK = 0x200, - VALIDATE_USERNAME = 0x400, - COMPLETE_USERNAME = 0x800, - PERSIST = 0x1000, - SERVER_CREDENTIAL = 0x4000, - EXPECT_CONFIRMATION = 0x20000, - GENERIC_CREDENTIALS = 0x40000, - USERNAME_TARGET_CREDENTIALS = 0x80000, - KEEP_USERNAME = 0x100000, - } - - private struct CREDUI_INFO - { - public int cbSize; - public IntPtr hwndParent; - [MarshalAs(UnmanagedType.LPWStr)] - public string pszMessageText; - [MarshalAs(UnmanagedType.LPWStr)] - public string pszCaptionText; - public IntPtr hbmBanner; - } - - private enum CredUIReturnCodes - { - NO_ERROR = 0, - ERROR_CANCELLED = 1223, - ERROR_NO_SUCH_LOGON_SESSION = 1312, - ERROR_NOT_FOUND = 1168, - ERROR_INVALID_ACCOUNT_NAME = 1315, - ERROR_INSUFFICIENT_BUFFER = 122, - ERROR_INVALID_PARAMETER = 87, - ERROR_INVALID_FLAGS = 1004, - } - /// /// Returns the prompt used in remote sessions: "[machine]: basePrompt" /// @@ -772,47 +235,27 @@ runspace.ConnectionInfo is VMConnectionInfo || { return basePrompt; } - else - { - 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; + SSHConnectionInfo sshConnectionInfo = runspace.ConnectionInfo as SSHConnectionInfo; - // Making it 2 seconds because of things like delayed prompt - if (idleTime.TotalSeconds > 2) - return true; - } - catch (System.ComponentModel.Win32Exception) + // Usernames are case-sensitive on Unix systems + if (sshConnectionInfo != null && + !string.IsNullOrEmpty(sshConnectionInfo.UserName) && + !System.Environment.UserName.Equals(sshConnectionInfo.UserName, StringComparison.Ordinal)) { - // 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); } /// @@ -845,6 +288,7 @@ internal static RemoteRunspace CreateConfiguredRunspace( e); } + remoteRunspace.IsConfiguredLoopBack = true; return remoteRunspace; } @@ -867,19 +311,19 @@ internal static RemoteRunspace CreateConfiguredRunspace( /// provided runspace. It assumes the thread invoking this method is the same that runs all other /// commands on the provided runspace. /// - /// Runspace to invoke the command on - /// Command to invoke - /// Collection of command output result objects + /// Runspace to invoke the command on. + /// Command to invoke. + /// Collection of command output result objects. public static Collection InvokeOnRunspace(PSCommand command, Runspace runspace) { if (command == null) { - throw new PSArgumentNullException("command"); + throw new PSArgumentNullException(nameof(command)); } if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } if ((runspace.Debugger != null) && runspace.Debugger.InBreakpoint) @@ -902,6 +346,7 @@ public static Collection InvokeOnRunspace(PSCommand command, Runspace // Local runspace. Make a nested PowerShell object as needed. ps.SetIsNested(runspace.GetCurrentlyRunningPipeline() != null); } + using (ps) { ps.Commands = command; @@ -918,7 +363,7 @@ public static Collection InvokeOnRunspace(PSCommand command, Runspace /// public const string PSEditFunction = @" param ( - [Parameter(Mandatory=$true)] [String[]] $FileName + [Parameter(Mandatory=$true)] [string[]] $FileName ) foreach ($file in $FileName) @@ -943,7 +388,7 @@ public static Collection InvokeOnRunspace(PSCommand command, Runspace [string] $PSEditFunction ) - Register-EngineEvent -SourceIdentifier PSISERemoteSessionOpenFile -Forward + Register-EngineEvent -SourceIdentifier PSISERemoteSessionOpenFile -Forward -SupportEvent if ((Test-Path -Path 'function:\global:PSEdit') -eq $false) { @@ -960,7 +405,7 @@ public static Collection InvokeOnRunspace(PSCommand command, Runspace Remove-Item -Path 'function:\global:PSEdit' -Force } - Get-EventSubscriber -SourceIdentifier PSISERemoteSessionOpenFile -EA Ignore | Remove-Event + Unregister-Event -SourceIdentifier PSISERemoteSessionOpenFile -Force -ErrorAction Ignore "; /// @@ -970,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 9ed6e675e53..f8196167c32 100644 --- a/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs +++ b/src/System.Management.Automation/engine/hostifaces/InformationalRecord.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.ObjectModel; @@ -14,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 { /// @@ -51,7 +50,7 @@ internal InformationalRecord(PSObject serializedObject) } /// - /// The message written by the command that created this record + /// The message written by the command that created this record. /// public string Message { @@ -59,11 +58,15 @@ public string Message { return _message; } - set { _message = value; } + + set + { + _message = value; + } } /// - /// The InvocationInfo of the command that created this record + /// The InvocationInfo of the command that created this record. /// /// /// The InvocationInfo can be null if the record was not created by a command. @@ -91,7 +94,7 @@ public ReadOnlyCollection PipelineIterationInfo } /// - /// Sets the InvocationInfo (and PipelineIterationInfo) for this record + /// Sets the InvocationInfo (and PipelineIterationInfo) for this record. /// internal void SetInvocationInfo(InvocationInfo invocationInfo) { @@ -109,7 +112,7 @@ internal void SetInvocationInfo(InvocationInfo invocationInfo) } /// - /// Whether to serialize the InvocationInfo and PipelineIterationInfo during remote calls + /// Whether to serialize the InvocationInfo and PipelineIterationInfo during remote calls. /// internal bool SerializeExtendedInfo { @@ -117,6 +120,7 @@ internal bool SerializeExtendedInfo { return _serializeExtendedInfo; } + set { _serializeExtendedInfo = value; @@ -124,7 +128,7 @@ internal bool SerializeExtendedInfo } /// - /// Returns the record's message + /// Returns the record's message. /// public override string ToString() { @@ -155,8 +159,9 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) } } - [DataMember()] + [DataMember] private string _message; + private InvocationInfo _invocationInfo; private ReadOnlyCollection _pipelineIterationInfo; private bool _serializeExtendedInfo; @@ -165,11 +170,10 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) /// /// A warning record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class WarningRecord : InformationalRecord { /// - /// /// /// public WarningRecord(string message) @@ -177,7 +181,6 @@ public WarningRecord(string message) { } /// - /// /// /// public WarningRecord(PSObject record) @@ -187,8 +190,8 @@ public WarningRecord(PSObject record) /// /// Constructor for Fully qualified warning Id. /// - /// Fully qualified warning Id - /// Warning message + /// Fully qualified warning Id. + /// Warning message. public WarningRecord(string fullyQualifiedWarningId, string message) : base(message) { @@ -198,8 +201,8 @@ public WarningRecord(string fullyQualifiedWarningId, string message) /// /// Constructor for Fully qualified warning Id. /// - /// Fully qualified warning Id - /// Warning serialized object + /// Fully qualified warning Id. + /// Warning serialized object. public WarningRecord(string fullyQualifiedWarningId, PSObject record) : base(record) { @@ -216,17 +219,17 @@ public string FullyQualifiedWarningId return _fullyQualifiedWarningId ?? string.Empty; } } - private string _fullyQualifiedWarningId; + + private readonly string _fullyQualifiedWarningId; } /// /// A debug record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class DebugRecord : InformationalRecord { /// - /// /// /// public DebugRecord(string message) @@ -234,7 +237,6 @@ public DebugRecord(string message) { } /// - /// /// /// public DebugRecord(PSObject record) @@ -245,11 +247,10 @@ public DebugRecord(PSObject record) /// /// A verbose record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class VerboseRecord : InformationalRecord { /// - /// /// /// public VerboseRecord(string message) @@ -257,11 +258,10 @@ public VerboseRecord(string message) { } /// - /// /// /// public VerboseRecord(PSObject record) : base(record) { } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs index 26253e07d38..c60bff90303 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; using System.Globalization; +using System.Management.Automation.Host; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; -using System.Management.Automation.Host; -using System.Collections.Generic; using Dbg = System.Management.Automation.Diagnostics; @@ -15,7 +14,6 @@ namespace System.Management.Automation.Internal.Host { /// - /// /// Wraps PSHost instances to provide a shim layer /// between InternalCommand and the host-supplied PSHost instance. /// @@ -25,12 +23,10 @@ namespace System.Management.Automation.Internal.Host /// /// That leverage may be necessary to manage concurrent access between multiple pipelines sharing the same instance of /// PSHost. - /// /// internal class InternalHost : PSHost, IHostSupportsInteractiveSession { /// - /// /// There should only be one instance of InternalHost per runspace (i.e. per engine), and all engine use of the host /// should be through that single instance. If we ever accidentally create more than one instance of InternalHost per /// runspace, then some of the internal state checks that InternalHost makes, like checking the nestedPromptCounter, can @@ -39,12 +35,11 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession /// To ensure that this constraint is met, I wanted to make this class a singleton. However, Hitesh rightly pointed out /// that a singleton would be appdomain-global, which would prevent having multiple runspaces per appdomain. So we will /// just have to be careful not to create extra instances of InternalHost per runspace. - /// /// 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"); @@ -59,26 +54,22 @@ internal InternalHost(PSHost externalHost, ExecutionContext executionContext) } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// when the external host's Name is null or empty. - /// /// public override string Name { get { - if (String.IsNullOrEmpty(_nameResult)) + if (string.IsNullOrEmpty(_nameResult)) { _nameResult = _externalHostRef.Value.Name; #pragma warning disable 56503 - if (String.IsNullOrEmpty(_nameResult)) + if (string.IsNullOrEmpty(_nameResult)) { throw PSTraceSource.NewNotImplementedException(); } @@ -90,15 +81,11 @@ public override string Name } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// when the external host's Version is null. - /// /// public override System.Version Version { @@ -121,15 +108,11 @@ public override System.Version Version } /// - /// - /// See base class - /// + /// See base class. /// /// /// - /// /// when the external host's InstanceId is a zero Guid. - /// /// public override System.Guid InstanceId { @@ -146,14 +129,13 @@ public override System.Guid InstanceId } #pragma warning restore 56503 } + return _idResult; } } /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -180,16 +162,12 @@ internal InternalHostUserInterface InternalUI } /// - /// - /// See base class - /// + /// See base class. /// /// /// /// - /// /// when the external host's CurrentCulture is null. - /// /// public override System.Globalization.CultureInfo CurrentCulture { @@ -202,34 +180,24 @@ public override System.Globalization.CultureInfo CurrentCulture } /// - /// - /// See base class - /// + /// See base class. /// /// /// /// - /// /// If the external host's CurrentUICulture is null. - /// /// public override CultureInfo CurrentUICulture { get { -#if CORECLR // No CultureInfo.InstalledUICulture In CoreCLR. Locale cannot be changed On CSS. - CultureInfo ci = _externalHostRef.Value.CurrentUICulture ?? CultureInfo.CurrentUICulture; -#else CultureInfo ci = _externalHostRef.Value.CurrentUICulture ?? CultureInfo.InstalledUICulture; -#endif return ci; } } /// - /// - /// See base class - /// + /// See base class. /// /// public override void SetShouldExit(int exitCode) @@ -238,9 +206,7 @@ public override void SetShouldExit(int exitCode) } /// - /// /// See base class - /// /// /// public override void EnterNestedPrompt() @@ -258,10 +224,9 @@ private struct PromptContextData } /// - /// Internal proxy for EnterNestedPrompt + /// Internal proxy for EnterNestedPrompt. /// /// - /// internal void EnterNestedPrompt(InternalCommand callingCommand) { // Ensure we are in control of the pipeline @@ -335,7 +300,6 @@ internal void EnterNestedPrompt(InternalCommand callingCommand) commandInfoProperty.Value = callingCommand.CommandInfo; } -#if !CORECLR //TODO:CORECLR StackTrace not in CoreCLR stackTraceProperty = newValue.Properties["StackTrace"]; if (stackTraceProperty == null) { @@ -346,7 +310,6 @@ internal void EnterNestedPrompt(InternalCommand callingCommand) oldStackTrace = stackTraceProperty.Value; stackTraceProperty.Value = new System.Diagnostics.StackTrace(); } -#endif Context.SetVariable(SpecialVariables.CurrentlyExecutingCommandVarPath, newValue); } @@ -387,6 +350,7 @@ internal void EnterNestedPrompt(InternalCommand callingCommand) { commandInfoProperty.Value = oldCommandInfo; } + if (stackTraceProperty != null) { stackTraceProperty.Value = oldStackTrace; @@ -420,9 +384,7 @@ private void ExitNestedPromptHelper() } /// - /// /// See base class - /// /// /// public override void ExitNestedPrompt() @@ -440,14 +402,13 @@ public override void ExitNestedPrompt() { ExitNestedPromptHelper(); } + ExitNestedPromptException enpe = new ExitNestedPromptException(); throw enpe; } /// - /// - /// See base class - /// + /// See base class. /// public override PSObject PrivateData { @@ -459,9 +420,7 @@ public override PSObject PrivateData } /// - /// /// See base class - /// /// /// public override void NotifyBeginApplication() @@ -470,9 +429,7 @@ public override void NotifyBeginApplication() } /// - /// /// Called by the engine to notify the host that the execution of a legacy command has completed. - /// /// /// public override void NotifyEndApplication() @@ -491,11 +448,11 @@ 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(); } + return host; } @@ -544,10 +501,10 @@ public Runspace Runspace } /// - /// Checks if the host is in a nested prompt + /// Checks if the host is in a nested prompt. /// - /// true, if host in nested prompt - /// false, otherwise + /// True, if host in nested prompt + /// false, otherwise. internal bool HostInNestedPrompt() { if (NestedPromptCount > 0) @@ -580,7 +537,11 @@ 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(); } @@ -593,7 +554,6 @@ internal bool IsHostRefSet get { return _externalHostRef.IsOverridden; } } - internal ExecutionContext Context { get; } internal PSHost ExternalHost @@ -607,15 +567,15 @@ 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; } -} // namespace +} diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs index 652693cbb7a..69346ae303b 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs @@ -1,15 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; - - namespace System.Management.Automation.Internal.Host { internal @@ -23,8 +19,6 @@ class InternalHostRawUserInterface : PSHostRawUserInterface _parentHost = parentHost; } - - internal void ThrowNotInteractive() @@ -41,12 +35,8 @@ class InternalHostRawUserInterface : PSHostRawUserInterface throw e; } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -68,6 +58,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -79,19 +70,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -107,6 +93,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -118,19 +105,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -146,6 +128,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -157,19 +140,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -185,6 +163,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -196,19 +175,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -224,6 +198,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -235,19 +210,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -263,6 +233,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -274,19 +245,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -302,6 +268,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -313,19 +280,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -343,19 +305,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -373,12 +330,8 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -387,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) @@ -396,6 +348,7 @@ public override { ThrowNotInteractive(); } + KeyInfo result = new KeyInfo(); try { @@ -403,31 +356,27 @@ public override } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // PipelineStoppedException is thrown by host when it wants + // to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parentHost.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } + lpl.Stopper.Stop(); } return result; } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// 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() @@ -440,19 +389,14 @@ public override _externalRawUI.FlushInputBuffer(); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -470,19 +414,14 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// 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 @@ -498,6 +437,7 @@ public override return result; } + set { if (_externalRawUI == null) @@ -509,12 +449,8 @@ public override } } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -534,12 +470,8 @@ public override _externalRawUI.SetBufferContents(origin, contents); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -551,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) @@ -564,12 +495,8 @@ public override _externalRawUI.SetBufferContents(r, fill); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -577,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) @@ -590,12 +516,8 @@ public override return _externalRawUI.GetBufferContents(r); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -609,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 @@ -629,7 +550,6 @@ BufferCell fill } /// - /// /// /// /// @@ -643,11 +563,11 @@ public override int LengthInBufferCells(string str) { ThrowNotInteractive(); } + return _externalRawUI.LengthInBufferCells(str); } /// - /// /// /// /// @@ -665,12 +585,11 @@ public override int LengthInBufferCells(string str, int offset) { ThrowNotInteractive(); } + return _externalRawUI.LengthInBufferCells(str, offset); } - /// - /// /// /// /// @@ -686,10 +605,11 @@ public override { ThrowNotInteractive(); } + return _externalRawUI.LengthInBufferCells(character); } - private PSHostRawUserInterface _externalRawUI; - private InternalHost _parentHost; + private readonly PSHostRawUserInterface _externalRawUI; + private readonly InternalHost _parentHost; } -} // namespace +} diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs index 9be358ee6c3..d8f2ef0bf90 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs @@ -1,14 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. - - -using System.Management.Automation.Language; -using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Management.Automation.Host; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Security; using Dbg = System.Management.Automation.Diagnostics; @@ -30,8 +27,9 @@ class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleCh Dbg.Assert(parentHost != null, "parent may not be null"); if (parentHost == null) { - throw PSTraceSource.NewArgumentNullException("parentHost"); + throw PSTraceSource.NewArgumentNullException(nameof(parentHost)); } + _parent = parentHost; PSHostRawUserInterface rawui = null; @@ -51,8 +49,7 @@ class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleCh _internalRawUI.ThrowNotInteractive(); } - private - void + private static void ThrowPromptNotInteractive(string promptMessage) { string message = StringUtil.Format(HostInterfaceExceptionsStrings.HostFunctionPromptNotImplemented, promptMessage); @@ -65,13 +62,10 @@ class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleCh } /// - /// - /// See base class - /// + /// See base class. /// /// /// - public override System.Management.Automation.Host.PSHostRawUserInterface RawUI @@ -84,19 +78,15 @@ public override public override bool SupportsVirtualTerminal { - get { return _externalUI.SupportsVirtualTerminal; } + get { return _externalUI != null && _externalUI.SupportsVirtualTerminal; } } /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not - /// implemented by the external host - /// + /// implemented by the external host. /// public override string @@ -114,33 +104,27 @@ public override } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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 - /// + /// See base class. /// /// - /// /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not - /// implemented by the external host - /// + /// implemented by the external host. /// - public override SecureString ReadLineAsSecureString() @@ -158,35 +142,29 @@ public override } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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 - /// + /// See base class. /// /// /// /// - /// /// 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) @@ -204,12 +182,8 @@ public override _externalUI.Write(value); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -218,12 +192,9 @@ 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) @@ -238,25 +209,25 @@ public override return; } - _externalUI.Write(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.Write(value); + } + else + { + _externalUI.Write(foregroundColor, backgroundColor, value); + } } - - /// - /// /// See base class - /// /// /// /// /// - /// /// 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() @@ -269,22 +240,15 @@ public override _externalUI.WriteLine(); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// /// - /// /// 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) @@ -302,8 +266,6 @@ public override _externalUI.WriteLine(value); } - - public override void WriteErrorLine(string value) @@ -321,12 +283,8 @@ public override _externalUI.WriteErrorLine(value); } - - /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -335,12 +293,9 @@ 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) @@ -355,23 +310,23 @@ public override return; } - _externalUI.WriteLine(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.WriteLine(value); + } + else + { + _externalUI.WriteLine(foregroundColor, backgroundColor, value); + } } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// 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) @@ -380,7 +335,6 @@ public override } /// - /// /// internal void WriteDebugRecord(DebugRecord record) { @@ -397,37 +351,23 @@ internal void WriteDebugRecord(DebugRecord record) /// /// Writes the DebugRecord to informational buffers. /// - /// DebugRecord - internal void WriteDebugInfoBuffers(DebugRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddDebug(record); - } - } - + /// DebugRecord. + internal void WriteDebugInfoBuffers(DebugRecord record) => _informationalBuffers?.AddDebug(record); /// - /// Helper function for WriteDebugLine + /// Helper function for WriteDebugLine. /// /// /// /// - /// /// If the debug preference is set to ActionPreference.Stop - /// /// /// - /// /// If the debug preference is set to ActionPreference.Inquire and user requests to stop execution. - /// /// /// - /// /// If the debug preference is not a valid ActionPreference value. - /// /// - internal void WriteDebugLine(string message, ref ActionPreference preference) @@ -437,6 +377,7 @@ internal void WriteDebugInfoBuffers(DebugRecord record) switch (preference) { case ActionPreference.Continue: + case ActionPreference.Break: WriteDebugLineHelper(message); break; case ActionPreference.SilentlyContinue: @@ -458,6 +399,7 @@ internal void WriteDebugInfoBuffers(DebugRecord record) { WriteDebugLineHelper(message); } + break; case ActionPreference.Stop: WriteDebugLineHelper(message); @@ -470,7 +412,7 @@ internal void WriteDebugInfoBuffers(DebugRecord record) throw ense; default: Dbg.Assert(false, "all preferences should be checked"); - throw PSTraceSource.NewArgumentException("preference", + throw PSTraceSource.NewArgumentException(nameof(preference), InternalHostUserInterfaceStrings.UnsupportedPreferenceError, preference); // break; } @@ -494,9 +436,9 @@ internal void SetInformationalMessageBuffers(PSInformationalBuffers informationa } /// - /// Gets the informational message buffers of the host + /// Gets the informational message buffers of the host. /// - /// informational message buffers + /// Informational message buffers. internal PSInformationalBuffers GetInformationalMessageBuffers() { return _informationalBuffers; @@ -514,26 +456,17 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() WriteDebugRecord(new DebugRecord(message)); } - - /// - /// /// Ask the user whether to continue/stop or break to a nested prompt. - /// /// /// - /// /// Message to display to the user. This routine will append the text "Continue" to ensure that people know what question /// they are answering. - /// /// /// - /// /// 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) @@ -576,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. @@ -590,40 +523,30 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() _parent.EnterNestedPrompt(); endLoop = false; break; - }//switch - } while (endLoop != true); + } + } while (!endLoop); return shouldContinue; } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// 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) { if (record == null) { - throw PSTraceSource.NewArgumentNullException("record"); + throw PSTraceSource.NewArgumentNullException(nameof(record)); } // Write to Information Buffers - if (null != _informationalBuffers) - { - _informationalBuffers.AddProgress(record); - } + _informationalBuffers?.AddProgress(record); if (_externalUI == null) { @@ -633,20 +556,13 @@ public override _externalUI.WriteProgress(sourceId, record); } - - /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// 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) @@ -660,7 +576,6 @@ public override } /// - /// /// internal void WriteVerboseRecord(VerboseRecord record) { @@ -677,27 +592,16 @@ internal void WriteVerboseRecord(VerboseRecord record) /// /// Writes the VerboseRecord to informational buffers. /// - /// VerboseRecord - internal void WriteVerboseInfoBuffers(VerboseRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddVerbose(record); - } - } + /// VerboseRecord. + internal void WriteVerboseInfoBuffers(VerboseRecord record) => _informationalBuffers?.AddVerbose(record); /// - /// - /// See base class - /// + /// See base class. /// /// - /// /// 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) @@ -709,7 +613,6 @@ public override void WriteWarningLine(string message) } /// - /// /// internal void WriteWarningRecord(WarningRecord record) { @@ -726,17 +629,10 @@ internal void WriteWarningRecord(WarningRecord record) /// /// Writes the WarningRecord to informational buffers. /// - /// WarningRecord - internal void WriteWarningInfoBuffers(WarningRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddWarning(record); - } - } + /// WarningRecord. + internal void WriteWarningInfoBuffers(WarningRecord record) => _informationalBuffers?.AddWarning(record); /// - /// /// internal void WriteInformationRecord(InformationRecord record) { @@ -753,14 +649,8 @@ internal void WriteInformationRecord(InformationRecord record) /// /// Writes the InformationRecord to informational buffers. /// - /// WarningRecord - internal void WriteInformationInfoBuffers(InformationRecord record) - { - if (_informationalBuffers != null) - { - _informationalBuffers.AddInformation(record); - } - } + /// WarningRecord. + internal void WriteInformationInfoBuffers(InformationRecord record) => _informationalBuffers?.AddInformation(record); internal static Type GetFieldType(FieldDescription field) { @@ -776,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; } @@ -790,9 +680,7 @@ internal static bool IsSecuritySensitiveType(string typeName) } /// - /// - /// See base class - /// + /// See base class. /// /// /// @@ -801,34 +689,27 @@ internal static bool IsSecuritySensitiveType(string typeName) /// /// /// - /// /// If is null. - /// /// /// - /// /// If .Count is less than 1. - /// /// /// - /// /// if the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host - /// /// - public override - Dictionary + Dictionary Prompt(string caption, string message, Collection descriptions) { if (descriptions == null) { - throw PSTraceSource.NewArgumentNullException("descriptions"); + throw PSTraceSource.NewArgumentNullException(nameof(descriptions)); } if (descriptions.Count < 1) { - throw PSTraceSource.NewArgumentException("descriptions", InternalHostUserInterfaceStrings.PromptEmptyDescriptionsError, "descriptions"); + throw PSTraceSource.NewArgumentException(nameof(descriptions), InternalHostUserInterfaceStrings.PromptEmptyDescriptionsError, "descriptions"); } if (_externalUI == null) @@ -836,7 +717,7 @@ public override ThrowPromptNotInteractive(message); } - Dictionary result = null; + Dictionary result = null; try { @@ -844,26 +725,22 @@ public override } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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 - /// + /// See base class. /// /// /// @@ -871,12 +748,9 @@ 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) @@ -893,13 +767,14 @@ public override } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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(); } @@ -942,7 +817,7 @@ public Collection PromptForChoice(string caption, Collection result = null; try { - if (null == hostForMultipleChoices) + if (hostForMultipleChoices == null) { // host did not implement this new interface.. // so work with V1 host API to get the behavior.. @@ -957,13 +832,14 @@ public Collection PromptForChoice(string caption, } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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(); } @@ -989,21 +865,21 @@ private Collection EmulatePromptForMultipleChoice(string caption, Collection choices, IEnumerable defaultChoices) { - Dbg.Assert(null != _externalUI, "externalUI cannot be null."); + Dbg.Assert(_externalUI != null, "externalUI cannot be null."); if (choices == null) { - throw PSTraceSource.NewArgumentNullException("choices"); + throw PSTraceSource.NewArgumentNullException(nameof(choices)); } if (choices.Count == 0) { - throw PSTraceSource.NewArgumentException("choices", + throw PSTraceSource.NewArgumentException(nameof(choices), InternalHostUserInterfaceStrings.EmptyChoicesError, "choices"); } Dictionary defaultChoiceKeys = new Dictionary(); - if (null != defaultChoices) + if (defaultChoices != null) { foreach (int defaultChoice in defaultChoices) { @@ -1016,16 +892,13 @@ private Collection EmulatePromptForMultipleChoice(string caption, defaultChoice); } - if (!defaultChoiceKeys.ContainsKey(defaultChoice)) - { - defaultChoiceKeys.Add(defaultChoice, true); - } + defaultChoiceKeys.TryAdd(defaultChoice, true); } } // 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); @@ -1041,11 +914,11 @@ 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 = - String.Format( + string.Format( Globalization.CultureInfo.InvariantCulture, choiceTemplate, hotkeysAndPlainLabels[0, i], @@ -1055,10 +928,10 @@ private Collection EmulatePromptForMultipleChoice(string caption, } // default choices - string defaultPrompt = ""; + string defaultPrompt = string.Empty; if (defaultChoiceKeys.Count > 0) { - string prepend = ""; + string prepend = string.Empty; Text.StringBuilder defaultChoicesBuilder = new Text.StringBuilder(); foreach (int defaultChoice in defaultChoiceKeys.Keys) { @@ -1068,16 +941,15 @@ 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 = ","; } + string defaultChoicesStr = defaultChoicesBuilder.ToString(); if (defaultChoiceKeys.Count == 1) { - defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, - defaultChoicesStr); + defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, defaultChoicesStr); } else { @@ -1090,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; @@ -1106,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) @@ -1117,6 +989,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, // allow for no choice selection. break; } + int choicePicked = HostUIHelperMethods.DetermineChoicePicked(response.Trim(), choices, hotkeysAndPlainLabels); if (choicePicked >= 0) @@ -1125,16 +998,15 @@ private Collection EmulatePromptForMultipleChoice(string caption, choicesSelected++; } // reset messageToBeDisplayed - messageToBeDisplayed = ""; - } while (true); + messageToBeDisplayed = string.Empty; + } 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; } -} // namespace - +} diff --git a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs index 237fa4ec0c7..aab1cb2e777 100644 --- a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs +++ b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs @@ -1,14 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; namespace System.Management.Automation { - using System; - using System.Collections.ObjectModel; - using System.Collections.Generic; - using System.Collections; - /// /// PSListModifier is a simple helper class created by the update-list cmdlet. /// The update-list cmdlet will either return an instance of this class, or @@ -36,8 +35,8 @@ public PSListModifier() /// /// Create a new PSListModifier with the specified add and remove lists. /// - /// The items to remove - /// The items to add + /// The items to remove. + /// The items to add. public PSListModifier(Collection removeItems, Collection addItems) { _itemsToAdd = addItems ?? new Collection(); @@ -48,7 +47,7 @@ public PSListModifier(Collection removeItems, Collection addItem /// /// Create a new PSListModifier to replace a given list with replaceItems. /// - /// The item(s) to replace an existing list with + /// The item(s) to replace an existing list with. public PSListModifier(object replacementItems) { _itemsToAdd = new Collection(); @@ -89,7 +88,7 @@ public PSListModifier(Hashtable hash) { if (hash == null) { - throw PSTraceSource.NewArgumentNullException("hash"); + throw PSTraceSource.NewArgumentNullException(nameof(hash)); } _itemsToAdd = new Collection(); @@ -107,7 +106,7 @@ public PSListModifier(Hashtable hash) if (!isAdd && !isRemove && !isReplace) { - throw PSTraceSource.NewArgumentException("hash", PSListModifierStrings.ListModifierDisallowedKey, key); + throw PSTraceSource.NewArgumentException(nameof(hash), PSListModifierStrings.ListModifierDisallowedKey, key); } Collection collection; @@ -139,7 +138,7 @@ public PSListModifier(Hashtable hash) } else { - throw PSTraceSource.NewArgumentException("hash", PSListModifierStrings.ListModifierDisallowedKey, entry.Key); + throw PSTraceSource.NewArgumentException(nameof(hash), PSListModifierStrings.ListModifierDisallowedKey, entry.Key); } } } @@ -151,7 +150,8 @@ public Collection Add { get { return _itemsToAdd; } } - private Collection _itemsToAdd; + + private readonly Collection _itemsToAdd; /// /// The list of items to remove when AppyTo is called. @@ -160,7 +160,8 @@ public Collection Remove { get { return _itemsToRemove; } } - private Collection _itemsToRemove; + + private readonly Collection _itemsToRemove; /// /// The list of items to replace an existing list with. @@ -169,17 +170,18 @@ public Collection Replace { get { return _replacementItems; } } - private Collection _replacementItems; + + private readonly Collection _replacementItems; /// /// Update the given collection with the items in Add and Remove. /// - /// The collection to update + /// The collection to update. public void ApplyTo(IList collectionToUpdate) { if (collectionToUpdate == null) { - throw PSTraceSource.NewArgumentNullException("collectionToUpdate"); + throw PSTraceSource.NewArgumentNullException(nameof(collectionToUpdate)); } if (_replacementItems.Count > 0) @@ -196,6 +198,7 @@ public void ApplyTo(IList collectionToUpdate) { collectionToUpdate.Remove(PSObject.Base(obj)); } + foreach (object obj in _itemsToAdd) { collectionToUpdate.Add(PSObject.Base(obj)); @@ -206,21 +209,18 @@ public void ApplyTo(IList collectionToUpdate) /// /// Update the given collection with the items in Add and Remove. /// - /// The collection to update + /// The collection to update. public void ApplyTo(object collectionToUpdate) { - if (collectionToUpdate == null) - { - throw new ArgumentNullException("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); } + ApplyTo(list); } @@ -271,8 +271,8 @@ public PSListModifier() /// /// Create a new PSListModifier with the specified add and remove lists. /// - /// The items to remove - /// The items to add + /// The items to remove. + /// The items to add. public PSListModifier(Collection removeItems, Collection addItems) : base(removeItems, addItems) { @@ -281,7 +281,7 @@ public PSListModifier(Collection removeItems, Collection addItem /// /// Create a new PSListModifier to replace a given list with replaceItems. /// - /// The items to replace an existing list with + /// The items to replace an existing list with. public PSListModifier(object replacementItems) : base(replacementItems) { diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index e9c66ea9106..822dc552204 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -1,21 +1,22 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading; -using System.Runtime.Serialization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; // for fxcop +using System.IO; +using System.Linq; using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Management.Automation.Remoting; +using System.Runtime.Serialization; +using System.Threading; + using Microsoft.PowerShell.Commands; -using System.Management.Automation.Internal; -using System.Diagnostics.CodeAnalysis; // for fxcop + using Dbg = System.Management.Automation.Diagnostics; -using System.Diagnostics; -using System.Linq; #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -25,7 +26,7 @@ namespace System.Management.Automation.Runspaces { /// - /// Runspace class for local runspace + /// Runspace class for local runspace. /// internal sealed partial class LocalRunspace : RunspaceBase { @@ -83,10 +84,7 @@ public override PSPrimitiveDictionary GetApplicationPrivateData() { lock (this.SyncRoot) { - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); } } @@ -94,7 +92,7 @@ public override PSPrimitiveDictionary GetApplicationPrivateData() } /// - /// A method that runspace pools can use to propagate application private data into runspaces + /// A method that runspace pools can use to propagate application private data into runspaces. /// /// internal override void SetApplicationPrivateData(PSPrimitiveDictionary applicationPrivateData) @@ -105,7 +103,7 @@ internal override void SetApplicationPrivateData(PSPrimitiveDictionary applicati private PSPrimitiveDictionary _applicationPrivateData; /// - /// Gets the event manager + /// Gets the event manager. /// public override PSEventManager Events { @@ -123,7 +121,7 @@ public override PSEventManager Events } /// - /// This property determines whether a new thread is create for each invocation + /// This property determines whether a new thread is create for each invocation. /// /// /// Any updates to the value of this property must be done before the Runspace is opened @@ -145,31 +143,32 @@ public override PSThreadOptions ThreadOptions { lock (this.SyncRoot) { - if (value != _createThreadOptions) + if (value == _createThreadOptions) { - if (this.RunspaceStateInfo.State != RunspaceState.BeforeOpen) - { -#if CORECLR // No ApartmentState.STA Support In CoreCLR - bool allowed = value == PSThreadOptions.ReuseThread; -#else - // if the runspace is already opened we only allow changing the options if - // the apartment state is MTA and the new value is ReuseThread - bool allowed = (this.ApartmentState == ApartmentState.MTA || this.ApartmentState == ApartmentState.Unknown) // Unknown is the same as MTA - && - value == PSThreadOptions.ReuseThread; -#endif + return; + } - if (!allowed) - { - throw new InvalidOperationException(StringUtil.Format(RunspaceStrings.InvalidThreadOptionsChange)); - } + if (this.RunspaceStateInfo.State != RunspaceState.BeforeOpen) + { + if (!IsValidThreadOptionsConfiguration(value)) + { + throw new InvalidOperationException(StringUtil.Format(RunspaceStrings.InvalidThreadOptionsChange)); } - - _createThreadOptions = value; } + + _createThreadOptions = value; } } } + + private bool IsValidThreadOptionsConfiguration(PSThreadOptions options) + { + // If the runspace is already opened, we only allow changing options when: + // - The new value is ReuseThread, and + // - The apartment state is not STA + return options == PSThreadOptions.ReuseThread && this.ApartmentState != ApartmentState.STA; + } + private PSThreadOptions _createThreadOptions = PSThreadOptions.Default; /// @@ -212,11 +211,11 @@ public override void ResetRunspaceState() #region protected_methods /// - /// Create a pipeline from a command string + /// Create a pipeline from a command string. /// - /// A valid command string. Can be null - /// if true command is added to history - /// True for nested pipeline + /// A valid command string. Can be null. + /// If true command is added to history. + /// True for nested pipeline. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -236,9 +235,9 @@ protected override Pipeline CoreCreatePipeline(string command, bool addToHistory #region protected_properties /// - /// Gets the execution context + /// Gets the execution context. /// - internal override System.Management.Automation.ExecutionContext GetExecutionContext + internal override ExecutionContext GetExecutionContext { get { @@ -250,7 +249,7 @@ internal override System.Management.Automation.ExecutionContext GetExecutionCont } /// - /// Returns true if the internal host is in a nested prompt + /// Returns true if the internal host is in a nested prompt. /// internal override bool InNestedPrompt { @@ -263,16 +262,27 @@ internal override bool InNestedPrompt return false; } - return context.InternalHost.HostInNestedPrompt(); + return context.InternalHost.HostInNestedPrompt() || InInternalNestedPrompt; } } + /// + /// Allows internal nested commands to be run as "HostInNestedPrompt" so that CreatePipelineProcessor() does + /// not set CommandOrigin to Internal as it normally does by default. This then allows cmdlets like Invoke-History + /// to replay history command lines in the current runspace with the same language mode context as the host. + /// + internal bool InInternalNestedPrompt + { + get; + set; + } + #endregion protected_properties #region internal_properties /// - /// Gets history manager for this runspace + /// Gets history manager for this runspace. /// /// internal History History @@ -284,7 +294,7 @@ internal History History } /// - /// Gets transcription data for this runspace + /// Gets transcription data for this runspace. /// /// internal TranscriptionData TranscriptionData @@ -294,12 +304,12 @@ internal TranscriptionData TranscriptionData return _transcriptionData; } } - private TranscriptionData _transcriptionData = null; + private TranscriptionData _transcriptionData = null; private JobRepository _jobRepository; /// - /// List of jobs in this runspace + /// List of jobs in this runspace. /// internal JobRepository JobRepository { @@ -325,7 +335,7 @@ public override JobManager JobManager private RunspaceRepository _runspaceRepository; /// - /// List of remote runspaces in this runspace + /// List of remote runspaces in this runspace. /// internal RunspaceRepository RunspaceRepository { @@ -340,7 +350,7 @@ internal RunspaceRepository RunspaceRepository #region Debugger /// - /// Debugger + /// Debugger. /// public override Debugger Debugger { @@ -350,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 @@ -363,10 +373,10 @@ public class DebugPreference } /// - /// CreateDebugPerfStruct is a helper method to populate DebugPreference + /// CreateDebugPerfStruct is a helper method to populate DebugPreference. /// - /// App Domain Names - /// DebugPreference + /// App Domain Names. + /// DebugPreference. private static DebugPreference CreateDebugPreference(string[] AppDomainNames) { DebugPreference DebugPreference = new DebugPreference(); @@ -377,8 +387,8 @@ private static DebugPreference CreateDebugPreference(string[] AppDomainNames) /// /// SetDebugPreference is a helper method used to enable and disable debug preference. /// - /// Process Name - /// App Domain Name + /// Process Name. + /// App Domain Name. /// Indicates if the debug preference has to be enabled or disabled. internal static void SetDebugPreference(string processName, List appDomainName, bool enable) { @@ -525,7 +535,7 @@ internal static void SetDebugPreference(string processName, List appDoma /// GetDebugPreferenceCache is a helper method used to fetch /// the debug preference cache contents as a Hashtable. /// - /// Runspace + /// Runspace. /// If the Debug preference is persisted then a hashtable containing /// the debug preference is returned or else Null is returned. private static Hashtable GetDebugPreferenceCache(Runspace runspace) @@ -546,6 +556,7 @@ private static Hashtable GetDebugPreferenceCache(Runspace runspace) debugPreferenceCache = psObjects[0].BaseObject as Hashtable; } } + return debugPreferenceCache; } @@ -565,13 +576,14 @@ private static DebugPreference GetProcessSpecificDebugPreference(object debugPre processDebugPreference = LanguagePrimitives.ConvertTo(debugPreferencePsObject); } } + return processDebugPreference; } #endregion /// - /// Open the runspace + /// Open the runspace. /// /// /// parameter which control if Open is done synchronously or asynchronously @@ -580,12 +592,12 @@ protected override void OpenHelper(bool syncCall) { if (syncCall) { - //Open runspace synchronously + // Open runspace synchronously DoOpenHelper(); } else { - //Open runspace in another thread + // Open runspace in another thread Thread asyncThread = new Thread(new ThreadStart(this.OpenThreadProc)); asyncThread.Start(); @@ -593,7 +605,7 @@ protected override void OpenHelper(bool syncCall) } /// - /// Start method for asynchronous open + /// Start method for asynchronous open. /// private void OpenThreadProc() { @@ -604,14 +616,14 @@ private void OpenThreadProc() } catch (Exception) { - //This exception is reported by raising RunspaceState - //change event. + // This exception is reported by raising RunspaceState + // change event. } #pragma warning restore 56500 } /// - /// Helper function used for opening a runspace + /// Helper function used for opening a runspace. /// private void DoOpenHelper() { @@ -634,7 +646,7 @@ private void DoOpenHelper() _engine = new AutomationEngine(Host, InitialSessionState); _engine.Context.CurrentRunspace = this; - //Log engine for start of engine life + // Log engine for start of engine life MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Available); startLifeCycleEventWritten = true; @@ -650,60 +662,57 @@ private void DoOpenHelper() { s_runspaceInitTracer.WriteLine("Runspace open failed"); - //Log engine health event + // Log engine health event LogEngineHealthEvent(exception); - //Log engine for end of engine life + // Log engine for end of engine life if (startLifeCycleEventWritten) { Dbg.Assert(_engine.Context != null, "if startLifeCycleEventWritten is true, ExecutionContext must be present"); MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); } - //Open failed. Set the RunspaceState to Broken. + // Open failed. Set the RunspaceState to Broken. SetRunspaceState(RunspaceState.Broken, exception); - //Raise the event + // Raise the event RaiseRunspaceStateEvents(); - //Rethrow the exception. For asynchronous execution, - //OpenThreadProc will catch it. For synchronous execution - //caller of open will catch it. + // Rethrow the exception. For asynchronous execution, + // OpenThreadProc will catch it. For synchronous execution + // caller of open will catch it. throw; } SetRunspaceState(RunspaceState.Opened); RunspaceOpening.Set(); - //Raise the event + // Raise the event RaiseRunspaceStateEvents(); s_runspaceInitTracer.WriteLine("runspace opened successfully"); // Now do initial state configuration that requires an active runspace - if (InitialSessionState != null) + Exception initError = InitialSessionState.BindRunspace(this, s_runspaceInitTracer); + if (initError != null) { - Exception initError = InitialSessionState.BindRunspace(this, s_runspaceInitTracer); - if (initError != null) - { - // Log engine health event - LogEngineHealthEvent(initError); + // Log engine health event + LogEngineHealthEvent(initError); - // Log engine for end of engine life - Debug.Assert(_engine.Context != null, - "if startLifeCycleEventWritten is true, ExecutionContext must be present"); - MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); + // Log engine for end of engine life + Debug.Assert(_engine.Context != null, + "if startLifeCycleEventWritten is true, ExecutionContext must be present"); + MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); - // Open failed. Set the RunspaceState to Broken. - SetRunspaceState(RunspaceState.Broken, initError); + // Open failed. Set the RunspaceState to Broken. + SetRunspaceState(RunspaceState.Broken, initError); - // Raise the event - RaiseRunspaceStateEvents(); + // Raise the event + RaiseRunspaceStateEvents(); - // Throw the exception. For asynchronous execution, - // OpenThreadProc will catch it. For synchronous execution - // caller of open will catch it. - throw initError; - } + // Throw the exception. For asynchronous execution, + // OpenThreadProc will catch it. For synchronous execution + // caller of open will catch it. + throw initError; } #if LEGACYTELEMETRY @@ -712,7 +721,7 @@ private void DoOpenHelper() } /// - /// Logs engine health event + /// Logs engine health event. /// internal void LogEngineHealthEvent(Exception exception) { @@ -724,12 +733,12 @@ internal void LogEngineHealthEvent(Exception exception) } /// - /// Logs engine health event + /// Logs engine health event. /// internal void LogEngineHealthEvent(Exception exception, Severity severity, int id, - Dictionary additionalInfo) + Dictionary additionalInfo) { Dbg.Assert(exception != null, "Caller should validate the parameter"); @@ -749,21 +758,14 @@ internal void LogEngineHealthEvent(Exception exception, } /// - /// Returns the thread that must be used to execute pipelines when CreateThreadOptions is ReuseThread + /// Returns the thread that must be used to execute pipelines when CreateThreadOptions is ReuseThread. /// /// /// The pipeline calls this function after ensuring there is a single thread in the pipeline, so no locking is necessary /// internal PipelineThread GetPipelineThread() { - if (_pipelineThread == null) - { -#if CORECLR // No ApartmentState In CoreCLR - _pipelineThread = new PipelineThread(); -#else - _pipelineThread = new PipelineThread(this.ApartmentState); -#endif - } + _pipelineThread ??= new PipelineThread(this.ApartmentState); return _pipelineThread; } @@ -774,12 +776,12 @@ protected override void CloseHelper(bool syncCall) { if (syncCall) { - //Do close synchronously + // Do close synchronously DoCloseHelper(); } else { - //Do close asynchronously + // Do close asynchronously Thread asyncThread = new Thread(new ThreadStart(this.CloseThreadProc)); asyncThread.Start(); @@ -787,7 +789,7 @@ protected override void CloseHelper(bool syncCall) } /// - /// Start method for asynchronous close + /// Start method for asynchronous close. /// private void CloseThreadProc() { @@ -825,39 +827,35 @@ private void DoCloseHelper() // When closing the primary runspace, ensure all other local runspaces are closed. var closeAllOpenRunspaces = isPrimaryRunspace && haveOpenRunspaces; - // Stop all transcriptions and unitialize AMSI if we're the last runspace to exit or we are exiting the primary runspace. + // Stop all transcriptions and un-initialize AMSI if we're the last runspace to exit or we are exiting the primary runspace. if (!haveOpenRunspaces) { ExecutionContext executionContext = this.GetExecutionContext; 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); - - //Stop all running pipelines - //Note:Do not perform the Cancel in lock. Reason is - //Pipeline executes in separate thread, say threadP. - //When pipeline is canceled/failed/completed in - //Pipeline.ExecuteThreadProc it removes the pipeline - //from the list of running pipelines. threadP will need - //lock to remove the pipelines from the list of running pipelines - //And we will deadlock. - //Note:It is possible that one or more pipelines in the list - //of active pipelines have completed before we call cancel. - //That is fine since Pipeline.Cancel handles that( It ignores - //the cancel request if pipeline execution has already - //completed/failed/canceled. + Events?.GenerateEvent(PSEngineEvent.Exiting, null, Array.Empty(), null, true, false); + + // Stop all running pipelines + // Note:Do not perform the Cancel in lock. Reason is + // Pipeline executes in separate thread, say threadP. + // When pipeline is canceled/failed/completed in + // Pipeline.ExecuteThreadProc it removes the pipeline + // from the list of running pipelines. threadP will need + // lock to remove the pipelines from the list of running pipelines + // And we will deadlock. + // Note:It is possible that one or more pipelines in the list + // of active pipelines have completed before we call cancel. + // That is fine since Pipeline.Cancel handles that( It ignores + // the cancel request if pipeline execution has already + // completed/failed/canceled. StopPipelines(); // Disconnect all disconnectable jobs in the job repository. @@ -876,18 +874,18 @@ 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. + // Log engine lifecycle event. MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); - //All pipelines have been canceled. Close the runspace. + // All pipelines have been canceled. Close the runspace. _engine = null; SetRunspaceState(RunspaceState.Closed); - //Raise Event + // Raise Event RaiseRunspaceStateEvents(); if (closeAllOpenRunspaces) @@ -912,6 +910,7 @@ private void DoCloseHelper() allRunspacesClosed = false; break; } + var localRunspace = r as LocalRunspace; if (localRunspace != null && localRunspace.Host is IHostProvidesTelemetryData) { @@ -919,6 +918,7 @@ private void DoCloseHelper() break; } } + if (allRunspacesClosed && !hostProvidesExitTelemetry) { TelemetryAPI.ReportExitTelemetry(null); @@ -931,25 +931,26 @@ 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) { IThrottleOperation operation = new CloseOrDisconnectRunspaceOperationHelper(remoteRunspace); throttleManager.AddOperation(operation); } + throttleManager.EndSubmitOperations(); remoteRunspaceCloseCompleted.WaitOne(); @@ -961,21 +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; } @@ -996,18 +998,15 @@ private void StopOrDisconnectAllJobs() disconnectRunspaces.AddRange(jobRunspaces); } } - } // foreach job + } // Stop jobs. throttleManager.EndSubmitOperations(); jobsStopCompleted.WaitOne(); - } // using jobsStopCompleted + } // Disconnect all disconnectable job runspaces found. - CloseOrDisconnectAllRemoteRunspaces(() => - { - return disconnectRunspaces; - }); + CloseOrDisconnectAllRemoteRunspaces(() => disconnectRunspaces); } internal void ReleaseDebugger() @@ -1101,6 +1100,7 @@ protected override PSLanguageMode DoLanguageMode return _engine.Context.SessionState.LanguageMode; } + set { if (_disposed) @@ -1190,13 +1190,12 @@ protected override ProviderIntrinsics DoInvokeProvider } } - #endregion SessionStateProxy #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; @@ -1213,6 +1212,7 @@ protected override void Dispose(bool disposing) { return; } + lock (SyncRoot) { if (_disposed) @@ -1238,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) { @@ -1249,7 +1247,6 @@ protected override void Dispose(bool disposing) } catch (ObjectDisposedException) { - ; } } } @@ -1261,7 +1258,7 @@ protected override void Dispose(bool disposing) } /// - /// Close the runspace + /// Close the runspace. /// public override void Close() { @@ -1271,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 @@ -1282,7 +1276,7 @@ public override void Close() #region private fields /// - /// AutomationEngine instance for this runspace + /// AutomationEngine instance for this runspace. /// private AutomationEngine _engine; @@ -1295,19 +1289,18 @@ internal AutomationEngine Engine } /// - /// Manages history for this runspace + /// Manages history for this runspace. /// 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 } @@ -1319,22 +1312,22 @@ private static /// internal sealed class StopJobOperationHelper : IThrottleOperation { - private Job _job; + private readonly Job _job; /// - /// Internal constructor + /// Internal constructor. /// /// Job object to stop. internal StopJobOperationHelper(Job job) { _job = job; - _job.StateChanged += new EventHandler(HandleJobStateChanged); + _job.StateChanged += HandleJobStateChanged; } /// /// Handles the Job state change event. /// - /// Originator of event, unused + /// Originator of event, unused. /// Event arguments containing Job state. private void HandleJobStateChanged(object sender, JobStateEventArgs eventArgs) { @@ -1380,7 +1373,7 @@ internal override void StopOperation() /// private void RaiseOperationCompleteEvent() { - _job.StateChanged -= new EventHandler(HandleJobStateChanged); + _job.StateChanged -= HandleJobStateChanged; OperationStateEventArgs operationStateArgs = new OperationStateEventArgs(); operationStateArgs.OperationState = OperationState.StartComplete; @@ -1396,23 +1389,23 @@ private void RaiseOperationCompleteEvent() /// internal sealed class CloseOrDisconnectRunspaceOperationHelper : IThrottleOperation { - private RemoteRunspace _remoteRunspace; + private readonly RemoteRunspace _remoteRunspace; /// - /// Internal constructor + /// Internal constructor. /// /// internal CloseOrDisconnectRunspaceOperationHelper(RemoteRunspace remoteRunspace) { _remoteRunspace = remoteRunspace; - _remoteRunspace.StateChanged += new EventHandler(HandleRunspaceStateChanged); + _remoteRunspace.StateChanged += HandleRunspaceStateChanged; } /// - /// Handle the runspace state changed event + /// Handle the runspace state changed event. /// - /// sender of this information, unused - /// runspace event args + /// Sender of this information, unused. + /// Runspace event args. private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs eventArgs) { switch (eventArgs.RunspaceStateInfo.State) @@ -1425,13 +1418,13 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs ev return; } - //remoteRunspace.Dispose(); - //remoteRunspace = null; + // remoteRunspace.Dispose(); + // remoteRunspace = null; RaiseOperationCompleteEvent(); } /// - /// Start the operation of closing the runspace + /// Start the operation of closing the runspace. /// internal override void StartOperation() { @@ -1466,23 +1459,23 @@ internal override void StartOperation() /// /// There is no scenario where we are going to cancel this close - /// Hence this method is intentionally empty + /// Hence this method is intentionally empty. /// internal override void StopOperation() { } /// - /// Event raised when the required operation is complete + /// Event raised when the required operation is complete. /// internal override event EventHandler OperationComplete; /// - /// Raise the operation completed event + /// Raise the operation completed event. /// private void RaiseOperationCompleteEvent() { - _remoteRunspace.StateChanged -= new EventHandler(HandleRunspaceStateChanged); + _remoteRunspace.StateChanged -= HandleRunspaceStateChanged; OperationStateEventArgs operationStateEventArgs = new OperationStateEventArgs(); @@ -1496,16 +1489,15 @@ 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 + /// contains a list of all of the module errors that have occurred. /// - [Serializable] public class RunspaceOpenModuleLoadException : RuntimeException { #region ctor /// /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException - /// with the message set to typeof(ScriptBlockToPowerShellNotSupportedException).FullName + /// with the message set to typeof(ScriptBlockToPowerShellNotSupportedException).FullName. /// public RunspaceOpenModuleLoadException() : base(typeof(ScriptBlockToPowerShellNotSupportedException).FullName) @@ -1513,34 +1505,34 @@ public RunspaceOpenModuleLoadException() } /// - /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message + /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message. /// - /// the exception's message + /// The exception's message. public RunspaceOpenModuleLoadException(string message) : base(message) { } /// - /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException + /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException. /// - /// the exception's message - /// the exceptions's inner exception + /// The exception's message. + /// The exception's inner exception. public RunspaceOpenModuleLoadException(string message, Exception innerException) : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// The name of the module that cause the error - /// The collection of errors that occurred during module processing + /// The name of the module that cause the error. + /// The collection of errors that occurred during module processing. internal RunspaceOpenModuleLoadException( string moduleName, PSDataCollection errors) : base(StringUtil.Format(RunspaceStrings.ErrorLoadingModulesOnRunspaceOpen, moduleName, - (errors != null && errors.Count > 0 && errors[0] != null) ? errors[0].ToString() : String.Empty), null) + (errors != null && errors.Count > 0 && errors[0] != null) ? errors[0].ToString() : string.Empty), null) { _errors = errors; this.SetErrorId("ErrorLoadingModulesOnRunspaceOpen"); @@ -1556,42 +1548,22 @@ public PSDataCollection ErrorRecords { get { return _errors; } } - private PSDataCollection _errors; + + private readonly PSDataCollection _errors; #region Serialization /// - /// Initializes a new instance of RunspaceOpenModuleLoadException with serialization parameters + /// Initializes a new instance of RunspaceOpenModuleLoadException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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("info"); - } - - base.GetObjectData(info, context); + throw new NotSupportedException(); } - #endregion Serialization - } // RunspaceOpenException + } #endregion Helper Class } - - - - diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index 2cac96bb84f..a9a3a66b859 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -1,26 +1,33 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Diagnostics; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; -using System.Threading; -using Microsoft.Win32; using System.Management.Automation.Internal; using System.Management.Automation.Internal.Host; using System.Management.Automation.Tracing; +#if !UNIX +using System.Security.Principal; +#endif +using System.Threading; using Microsoft.PowerShell.Commands; -using Dbg = System.Management.Automation.Diagnostics; - namespace System.Management.Automation.Runspaces { /// - /// Pipeline class to be used for LocalRunspace + /// Pipeline class to be used for LocalRunspace. /// internal sealed class LocalPipeline : PipelineBase { + // Each OS platform uses different default stack size for threads: + // - Windows 2 MB + // - Linux 8 Mb + // - MacOs 512 KB + // We should use the same stack size for pipeline threads on all platforms to get predictable behavior. + // The stack size we use for pipeline threads is 10MB, which is inherited from Windows PowerShell. + internal const int DefaultPipelineStackSize = 10_000_000; + #region constructors /// @@ -30,8 +37,8 @@ internal sealed class LocalPipeline : PipelineBase /// pipeline. /// /// The command string to parse. - /// if true, add pipeline to history - /// True for nested pipeline + /// If true, add pipeline to history. + /// True for nested pipeline. internal LocalPipeline(LocalRunspace runspace, string command, bool addToHistory, bool isNested) : base((Runspace)runspace, command, addToHistory, isNested) { @@ -82,11 +89,10 @@ internal LocalPipeline(LocalRunspace runspace, InitStreams(); } - /// - /// Copy constructor to support cloning + /// Copy constructor to support cloning. /// - /// The source pipeline + /// The source pipeline. internal LocalPipeline(LocalPipeline pipeline) : base((PipelineBase)(pipeline)) { @@ -131,14 +137,14 @@ protected override void StartPipelineExecution() throw PSTraceSource.NewObjectDisposedException("pipeline"); } - //Note:This method is called from within a lock by parent class. There - //is no need to lock further. + // Note:This method is called from within a lock by parent class. There + // is no need to lock further. - //Use input stream in two cases: - //1)inputStream is open. In this case PipelineProcessor - //will call Invoke only if at least one object is added - //to inputStream. - //2)inputStream is closed but there are objects in the stream. + // Use input stream in two cases: + // 1)inputStream is open. In this case PipelineProcessor + // will call Invoke only if at least one object is added + // to inputStream. + // 2)inputStream is closed but there are objects in the stream. // NTRAID#Windows Out Of Band Releases-925566-2005/12/09-JonN // Remember this here, in the synchronous thread, // to avoid timing dependencies in the pipeline thread. @@ -146,20 +152,29 @@ protected override void StartPipelineExecution() PSThreadOptions memberOptions = this.IsNested ? PSThreadOptions.UseCurrentThread : this.LocalRunspace.ThreadOptions; +#if !UNIX + // Use thread proc that supports impersonation flow for new thread start. + ThreadStart invokeThreadProcDelegate = InvokeThreadProcImpersonate; + _identityToImpersonate = null; + + // If impersonation identity flow is requested, then get current thread impersonation, if any. + if ((InvocationSettings != null) && InvocationSettings.FlowImpersonationPolicy) + { + Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate); + } +#else + // UNIX does not support thread impersonation flow. + ThreadStart invokeThreadProcDelegate = InvokeThreadProc; +#endif + switch (memberOptions) { case PSThreadOptions.Default: case PSThreadOptions.UseNewThread: { -#if CORECLR - //Start execution of pipeline in another thread - // No ApartmentState/ThreadStackSize In CoreCLR - Thread invokeThread = new Thread(new ThreadStart(this.InvokeThreadProc)); - SetupInvokeThread(invokeThread, true); -#else - //Start execution of pipeline in another thread - // 2004/05/02-JonN Specify maxStack parameter - Thread invokeThread = new Thread(new ThreadStart(this.InvokeThreadProc), MaxStack); + // Start execution of pipeline in another thread, + // and support impersonation flow as needed (Windows only). + Thread invokeThread = new Thread(new ThreadStart(invokeThreadProcDelegate), DefaultPipelineStackSize); SetupInvokeThread(invokeThread, true); ApartmentState apartmentState; @@ -173,11 +188,13 @@ protected override void StartPipelineExecution() apartmentState = this.LocalRunspace.ApartmentState; // use the Runspace apartment state } - if (apartmentState != ApartmentState.Unknown) +#if !UNIX + if (apartmentState != ApartmentState.Unknown && Platform.IsStaSupported) { invokeThread.SetApartmentState(apartmentState); } #endif + invokeThread.Start(); break; @@ -187,17 +204,20 @@ protected override void StartPipelineExecution() { if (this.IsNested) { - // if this a nested pipeline we are already in the appropriate thread so we just execute the pipeline here + // If this a nested pipeline we are already in the appropriate thread so we just execute the pipeline here. + // Impersonation flow (Windows only) is not needed when using existing thread. SetupInvokeThread(Thread.CurrentThread, true); - this.InvokeThreadProc(); + InvokeThreadProc(); } else { - // otherwise we execute the pipeline in the Runspace's thread + // Otherwise we execute the pipeline in the Runspace's thread, + // and support information flow on new thread as needed (Windows only). PipelineThread invokeThread = this.LocalRunspace.GetPipelineThread(); SetupInvokeThread(invokeThread.Worker, true); - invokeThread.Start(this.InvokeThreadProc); + invokeThread.Start(invokeThreadProcDelegate); } + break; } @@ -210,9 +230,10 @@ protected override void StartPipelineExecution() try { - // prepare invoke thread + // Prepare invoke thread. + // Impersonation flow (Windows only) is not needed when using existing thread. SetupInvokeThread(Thread.CurrentThread, false); - this.InvokeThreadProc(); + InvokeThreadProc(); } finally { @@ -220,17 +241,18 @@ protected override void StartPipelineExecution() Thread.CurrentThread.CurrentCulture = oldCurrentCulture; Thread.CurrentThread.CurrentUICulture = oldCurrentUICulture; } + break; } default: - Debug.Assert(false); + Debug.Fail(string.Empty); break; } } /// - /// Prepares the invoke thread for execution + /// Prepares the invoke thread for execution. /// private void SetupInvokeThread(Thread invokeThread, bool changeName) { @@ -247,110 +269,60 @@ private void SetupInvokeThread(Thread invokeThread, bool changeName) } } -#if !CORECLR /// - /// Stack Reserve setting for pipeline threads - /// - internal static int MaxStack - { - get - { - int i = ReadRegistryInt("PipelineMaxStackSizeMB", 10); - if (i < 10) - i = 10; // minimum 10MB - else if (i > 100) - i = 100; // maximum 100MB - return i * 1000000; - } - } - - internal static int ReadRegistryInt(string policyValueName, int defaultValue) - { - RegistryKey key; - try - { - key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\PowerShell\\1\\ShellIds"); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (null == key) - return defaultValue; - - object temp; - try - { - temp = key.GetValue(policyValueName); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (!(temp is int)) - { - return defaultValue; - } - int i = (int)temp; - return i; - } -#endif - - /// /// 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 + // Raise the event for Pipeline.Running RaisePipelineStateEvents(); - //Add this pipeline to history + // 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)); @@ -361,7 +333,7 @@ private FlowControlException InvokeHelper() try { - //Create PipelineProcessor to invoke this pipeline + // Create PipelineProcessor to invoke this pipeline pipelineProcessor = CreatePipelineProcessor(); } catch (Exception ex) @@ -371,10 +343,11 @@ private FlowControlException InvokeHelper() SetHadErrors(true); Runspace.ExecutionContext.AppendDollarError(ex); } + throw; } - //Supply input stream to PipelineProcessor + // Supply input stream to PipelineProcessor // NTRAID#Windows Out Of Band Releases-925566-2005/12/09-JonN if (_useExternalInput) @@ -387,8 +360,9 @@ private FlowControlException InvokeHelper() // Set Informational Buffers on the host only if this is not a child. // Do not overwrite parent's informational buffers. if (!this.IsChild) - LocalRunspace.ExecutionContext.InternalHost. - InternalUI.SetInformationalMessageBuffers(InformationalBuffers); + { + LocalRunspace.ExecutionContext.InternalHost.InternalUI.SetInformationalMessageBuffers(InformationalBuffers); + } bool oldQuestionMarkValue = true; bool savedIgnoreScriptDebug = this.LocalRunspace.ExecutionContext.IgnoreScriptDebug; @@ -398,7 +372,7 @@ private FlowControlException InvokeHelper() try { - //Add this pipeline to stopper + // Add this pipeline to stopper _stopper.Push(pipelineProcessor); // Preserve the last value of $? across non-interactive commands. @@ -418,9 +392,9 @@ private FlowControlException InvokeHelper() this.LocalRunspace.ExecutionContext.ResetRedirection(); } - //Invoke the pipeline. - //Note:Since we are using pipes for output, return array is - //be empty. + // Invoke the pipeline. + // Note:Since we are using pipes for output, return array is + // be empty. try { pipelineProcessor.SynchronousExecuteEnumerate(AutomationNull.Value); @@ -451,7 +425,6 @@ private FlowControlException InvokeHelper() catch (ExitNestedPromptException) { // Already at the top level so we just want to ignore this exception... - ; } } } @@ -469,13 +442,6 @@ private FlowControlException InvokeHelper() finally { this.LocalRunspace.ExecutionContext.EngineHostInterface.SetShouldExit(exitCode); - - // close the remote runspaces available - /*foreach (RemoteRunspaceInfo remoteRunspaceInfo in - this.LocalRunspace.RunspaceRepository.Runspaces) - { - remoteRunspaceInfo.RemoteRunspace.CloseAsync(); - }*/ } } } @@ -492,7 +458,6 @@ private FlowControlException InvokeHelper() } // Otherwise discard this type of exception generated by the debugger or from an unhandled break, continue or return. - ; } catch (Exception) { @@ -522,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; @@ -534,7 +496,7 @@ private FlowControlException InvokeHelper() if (!IsChild) LocalRunspace.ExecutionContext.InternalHost.InternalUI.SetInformationalMessageBuffers(null); - //Pop the pipeline processor from stopper. + // Pop the pipeline processor from stopper. _stopper.Pop(false); if (!AddToHistory) @@ -549,12 +511,11 @@ private FlowControlException InvokeHelper() catch (FlowControlException) { // Discard this type of exception generated by the debugger or from an unhandled break, continue or return. - ; } finally { // 2004/02/26-JonN added IDisposable to PipelineProcessor - if (null != pipelineProcessor) + if (pipelineProcessor != null) { pipelineProcessor.Dispose(); pipelineProcessor = null; @@ -564,8 +525,26 @@ private FlowControlException InvokeHelper() return flowControlException; } - // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 - // Removed HandleUnexpectedExceptions infrastructure +#if !UNIX + /// + /// Invokes the InvokeThreadProc() method on new thread, and flows calling thread + /// impersonation as needed. + /// + private void InvokeThreadProcImpersonate() + { + if (_identityToImpersonate != null) + { + WindowsIdentity.RunImpersonated( + _identityToImpersonate.AccessToken, + () => InvokeThreadProc()); + + return; + } + + InvokeThreadProc(); + } +#endif + /// /// Start thread method for asynchronous pipeline execution. /// @@ -576,19 +555,6 @@ private void InvokeThreadProc() try { -#if !CORECLR // Impersonation is not supported in CoreCLR. - // Used to store old impersonation context if we impersonate. - System.Security.Principal.WindowsImpersonationContext oldImpersonationCtxt = null; - try - { - if ((null != InvocationSettings) && (InvocationSettings.FlowImpersonationPolicy)) - { - // we have a valid identity to impersonate. - System.Security.Principal.WindowsIdentity identityToImPersonate = - new System.Security.Principal.WindowsIdentity(InvocationSettings.WindowsIdentityToImpersonate.Token); - oldImpersonationCtxt = identityToImPersonate.Impersonate(); - } -#endif // Set up pipeline internal host if it is available. if (InvocationSettings != null && InvocationSettings.Host != null) { @@ -616,7 +582,7 @@ private void InvokeThreadProc() Microsoft.PowerShell.NativeCultureResolver.SetThreadUILanguage(0); } - //Put Execution Context In TLS + // Put Execution Context In TLS Runspace.DefaultRunspace = this.LocalRunspace; FlowControlException flowControlException = InvokeHelper(); @@ -631,29 +597,6 @@ private void InvokeThreadProc() // Invoke finished successfully. Set state to Completed. SetPipelineState(PipelineState.Completed); } -#if !CORECLR - } - finally - { - // Impersonation is not supported in CoreCLR. - // This finally block is needed to handle fxcop CA2124 - // If sensitive operations such as impersonation occur in the try block, and an - // exception is thrown, the filter can execute before the finally block. For the - // impersonation example, this means that the filter would execute as the impersonated user. - if (null != oldImpersonationCtxt) - { - try - { - oldImpersonationCtxt.Undo(); - oldImpersonationCtxt.Dispose(); - oldImpersonationCtxt = null; - } - catch (System.Security.SecurityException) - { - } - } - } -#endif } catch (PipelineStoppedException ex) { @@ -675,18 +618,11 @@ private void InvokeThreadProc() SetPipelineState(PipelineState.Failed, ex); SetHadErrors(true); } -#if !CORECLR // No ThreadAbortException In CoreCLR - catch (ThreadAbortException ex) - { - SetPipelineState(PipelineState.Failed, ex); - SetHadErrors(true); - } -#endif - // 1021203-2005/05/09-JonN - // HaltCommandException will cause the command - // to stop, but not be reported as an error. catch (HaltCommandException) { + // 1021203-2005/05/09-JonN + // HaltCommandException will cause the command + // to stop, but not be reported as an error. SetPipelineState(PipelineState.Completed); } finally @@ -700,12 +636,12 @@ private void InvokeThreadProc() LocalRunspace.ExecutionContext.InternalHost.RevertHostRef(); } - //Remove Execution Context From TLS + // Remove Execution Context From TLS Runspace.DefaultRunspace = previousDefaultRunspace; - //If incomplete parse exception is hit, we should not add to history. - //This is ensure that in case of multiline commands, command is in the - //history only once. + // If incomplete parse exception is hit, we should not add to history. + // This is ensure that in case of multiline commands, command is in the + // history only once. if (!incompleteParseException) { try @@ -732,7 +668,7 @@ private void InvokeThreadProc() // IsChild makes it possible for LocalPipeline to differentiate // between a true v1 nested pipeline and the "Cmdlets Calling Cmdlets" case. - //Close the output stream if it is not closed. + // Close the output stream if it is not closed. if (OutputStream.IsOpen && !IsChild) { try @@ -744,7 +680,7 @@ private void InvokeThreadProc() } } - //Close the error stream if it is not closed. + // Close the error stream if it is not closed. if (ErrorStream.IsOpen && !IsChild) { try @@ -756,7 +692,7 @@ private void InvokeThreadProc() } } - //Close the input stream if it is not closed. + // Close the input stream if it is not closed. if (InputStream.IsOpen && !IsChild) { try @@ -771,19 +707,19 @@ private void InvokeThreadProc() // Clear stream links from ExecutionContext ClearStreams(); - //Runspace object maintains a list of pipelines in execution. - //Remove this pipeline from the list. This method also calls the - //pipeline finished event. + // Runspace object maintains a list of pipelines in execution. + // Remove this pipeline from the list. This method also calls the + // pipeline finished event. LocalRunspace.RemoveFromRunningPipelineList(this); - //If async call raise the event here. For sync invoke call, - //thread on which invoke is called will raise the event. + // If async call raise the event here. For sync invoke call, + // thread on which invoke is called will raise the event. if (!SyncInvokeCall) { - //This should be called after signaling PipelineFinishedEvent and - //RemoveFromRunningPipelineList. If it is done before, and in the - //Event, Runspace.Close is called which waits for pipeline to close. - //We will have deadlock + // This should be called after signaling PipelineFinishedEvent and + // RemoveFromRunningPipelineList. If it is done before, and in the + // Event, Runspace.Close is called which waits for pipeline to close. + // We will have deadlock RaisePipelineStateEvents(); } } @@ -794,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) { @@ -810,18 +746,18 @@ protected override void ImplementStop(bool syncCall) } /// - /// Start method for asynchronous Stop + /// Start method for asynchronous Stop. /// private void StopThreadProc() { StopHelper(); } - private PipelineStopper _stopper; + private readonly PipelineStopper _stopper; /// /// Gets PipelineStopper object which maintains stack of PipelineProcessor - /// for this pipeline + /// for this pipeline. /// /// internal PipelineStopper Stopper @@ -832,19 +768,19 @@ internal PipelineStopper Stopper } } /// - /// Helper method for Stop functionality + /// Helper method for Stop functionality. /// private void StopHelper() { // Ensure that any saved debugger stop is released LocalRunspace.ReleaseDebugger(); - //first stop all child pipelines of this pipeline + // first stop all child pipelines of this pipeline LocalRunspace.StopNestedPipelines(this); - //close the input pipe if it hasn't been closed. - //This would release the pipeline thread if it is - //waiting for input. + // close the input pipe if it hasn't been closed. + // This would release the pipeline thread if it is + // waiting for input. if (InputStream.IsOpen) { try @@ -857,12 +793,12 @@ private void StopHelper() } _stopper.Stop(); - //Wait for pipeline to finish + // Wait for pipeline to finish PipelineFinishedEvent.WaitOne(); } /// - /// Returns true if pipeline is stopping + /// Returns true if pipeline is stopping. /// /// internal bool IsStopping @@ -877,7 +813,7 @@ internal bool IsStopping /// /// Creates a PipelineProcessor object from LocalPipeline object. /// - /// Created PipelineProcessor object + /// Created PipelineProcessor object. private PipelineProcessor CreatePipelineProcessor() { CommandCollection commands = Commands; @@ -903,7 +839,12 @@ private PipelineProcessor CreatePipelineProcessor() try { CommandOrigin commandOrigin = command.CommandOrigin; - if (IsNested) + + // Do not set command origin to internal if this is a script debugger originated command (which always + // runs nested commands). This prevents the script debugger command line from seeing private commands. + if (IsNested && + !LocalRunspace.InNestedPrompt && + !((LocalRunspace.Debugger != null) && (LocalRunspace.Debugger.InBreakpoint))) { commandOrigin = CommandOrigin.Internal; } @@ -952,6 +893,7 @@ private PipelineProcessor CreatePipelineProcessor() commandProcessorBase.RedirectShellErrorOutputPipe = this.RedirectShellErrorOutputPipe; pipelineProcessor.Add(commandProcessorBase); } + return pipelineProcessor; } catch (RuntimeException) @@ -977,9 +919,9 @@ private PipelineProcessor CreatePipelineProcessor() } /// - /// Resolves command.CommandInfo to an appropriate CommandProcessorBase implementation + /// Resolves command.CommandInfo to an appropriate CommandProcessorBase implementation. /// - /// command to resolve + /// Command to resolve. /// private CommandProcessorBase CreateCommandProcessBase(Command command) { @@ -1027,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() @@ -1039,11 +981,11 @@ private void ClearStreams() } } - //History object for this pipeline + // History object for this pipeline private DateTime _pipelineStartTime; /// - /// Adds an entry in history for this pipeline + /// Adds an entry in history for this pipeline. /// private void RecordPipelineStartTime() { @@ -1056,14 +998,13 @@ private void RecordPipelineStartTime() /// private void AddHistoryEntry(bool skipIfLocked) { - //History id is greater than zero if entry was added to history + // History id is greater than zero if entry was added to history if (AddToHistory) { LocalRunspace.History.AddEntry(InstanceId, HistoryString, PipelineState, _pipelineStartTime, DateTime.Now, skipIfLocked); } } - private long _historyIdForThisPipeline = -1; /// /// This method is called Add-History cmdlet to add history entry. @@ -1078,14 +1019,15 @@ private void AddHistoryEntry(bool skipIfLocked) internal void AddHistoryEntryFromAddHistoryCmdlet() { - //This method can be called by multiple times during a single - //pipeline execution. For ex: a script can execute add-history - //command multiple times. However we should add entry only - //once. + // This method can be called by multiple times during a single + // pipeline execution. For ex: a script can execute add-history + // command multiple times. However we should add entry only + // once. if (_historyIdForThisPipeline != -1) { return; } + if (AddToHistory) { _historyIdForThisPipeline = LocalRunspace.History.AddEntry(InstanceId, HistoryString, PipelineState, _pipelineStartTime, DateTime.Now, false); @@ -1107,9 +1049,9 @@ void UpdateHistoryEntryAddedByAddHistoryCmdlet(bool skipIfLocked) } /// - /// sets the history string to the specified one + /// Sets the history string to the specified one. /// - /// history string to set to + /// History string to set to. internal override void SetHistoryString(string historyString) { HistoryString = historyString; @@ -1117,24 +1059,22 @@ internal override void SetHistoryString(string historyString) #region TLS - - - /// /// Gets the execution context in the thread local storage of current - /// thread + /// thread. /// /// /// 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; } + return runspace.ExecutionContext; } @@ -1146,7 +1086,7 @@ internal static System.Management.Automation.ExecutionContext GetExecutionContex /// /// Holds reference to LocalRunspace to which this pipeline is - /// associated with + /// associated with. /// private LocalRunspace LocalRunspace { @@ -1156,43 +1096,21 @@ private LocalRunspace LocalRunspace } } - // NTRAID#Windows Out Of Band Releases-925566-2005/12/09-JonN private bool _useExternalInput; private PipelineWriter _oldExternalErrorOutput; private PipelineWriter _oldExternalSuccessOutput; - #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); - } +#if !UNIX + private WindowsIdentity _identityToImpersonate; +#endif - #endregion invoke_loop_detection + #endregion private_fields #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; @@ -1206,7 +1124,7 @@ protected override { try { - if (_disposed == false) + if (!_disposed) { _disposed = true; if (disposing) @@ -1225,38 +1143,30 @@ protected override } /// - /// Helper class that holds the thread used to execute pipelines when CreateThreadOptions.ReuseThread is used + /// Helper class that holds the thread used to execute pipelines when CreateThreadOptions.ReuseThread is used. /// internal class PipelineThread : IDisposable { /// - /// Creates the worker thread and waits for it to be ready + /// Creates the worker thread and waits for it to be ready. /// -#if CORECLR - internal PipelineThread() - { - _worker = new Thread(WorkerProc); - _workItem = null; - _workItemReady = new AutoResetEvent(false); - _closed = false; - } -#else internal PipelineThread(ApartmentState apartmentState) { - _worker = new Thread(WorkerProc, LocalPipeline.MaxStack); + _worker = new Thread(WorkerProc, LocalPipeline.DefaultPipelineStackSize); _workItem = null; _workItemReady = new AutoResetEvent(false); _closed = false; - if (apartmentState != ApartmentState.Unknown) +#if !UNIX + if (apartmentState != ApartmentState.Unknown && Platform.IsStaSupported) { _worker.SetApartmentState(apartmentState); } - } #endif + } /// - /// Returns the worker thread + /// Returns the worker thread. /// internal Thread Worker { @@ -1267,7 +1177,7 @@ internal Thread Worker } /// - /// Posts an item to the worker thread and wait for its completion + /// Posts an item to the worker thread and wait for its completion. /// internal void Start(ThreadStart workItem) { @@ -1286,7 +1196,7 @@ internal void Start(ThreadStart workItem) } /// - /// Shortcut for dispose + /// Shortcut for dispose. /// internal void Close() { @@ -1294,7 +1204,7 @@ internal void Close() } /// - /// Implementation of the worker thread + /// Implementation of the worker thread. /// private void WorkerProc() { @@ -1310,7 +1220,7 @@ private void WorkerProc() } /// - /// Releases the worker thread + /// Releases the worker thread. /// public void Dispose() { @@ -1334,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; } @@ -1356,18 +1266,18 @@ public void Dispose() internal class PipelineStopper { /// - /// stack of current executing pipeline processor + /// Stack of current executing pipeline processor. /// - private Stack _stack = new Stack(); + private readonly Stack _stack = new Stack(); /// - /// Object used for synchronization + /// Object used for synchronization. /// - private object _syncRoot = new object(); - private LocalPipeline _localPipeline; + private readonly object _syncRoot = new object(); + private readonly LocalPipeline _localPipeline; /// - /// Default constructor + /// Default constructor. /// internal PipelineStopper(LocalPipeline localPipeline) { @@ -1375,15 +1285,17 @@ internal PipelineStopper(LocalPipeline localPipeline) } /// - /// This is set true when stop is called + /// This is set true when stop is called. /// private bool _stopping; + internal bool IsStopping { get { return _stopping; } + set { _stopping = value; @@ -1391,15 +1303,16 @@ internal bool IsStopping } /// - /// Push item in to PipelineProcessor stack + /// Push item in to PipelineProcessor stack. /// /// internal void Push(PipelineProcessor item) { if (item == null) { - throw PSTraceSource.NewArgumentNullException("item"); + throw PSTraceSource.NewArgumentNullException(nameof(item)); } + lock (_syncRoot) { if (_stopping) @@ -1407,13 +1320,15 @@ internal void Push(PipelineProcessor item) PipelineStoppedException e = new PipelineStoppedException(); throw e; } + _stack.Push(item); } + item.LocalPipeline = _localPipeline; } /// - /// Pop top item from PipelineProcessor stack + /// Pop top item from PipelineProcessor stack. /// internal void Pop(bool fromSteppablePipeline) { @@ -1448,10 +1363,11 @@ internal void Stop() PipelineProcessor[] copyStack; lock (_syncRoot) { - if (_stopping == true) + if (_stopping) { return; } + _stopping = true; copyStack = _stack.ToArray(); @@ -1467,14 +1383,14 @@ internal void Stop() } } - //Note: after _stopping is set to true, nothing can be pushed/popped - //from stack and it is safe to call stop on PipelineProcessors in stack - //outside the lock - //Note: you want to do below loop outside the lock so that - //pipeline execution thread doesn't get blocked on Push and Pop. - //Note: A copy of the stack is made because we "unstop" a stopped - //pipeline to execute finally blocks. We don't want to stop pipelines - //in the finally block though. + // Note: after _stopping is set to true, nothing can be pushed/popped + // from stack and it is safe to call stop on PipelineProcessors in stack + // outside the lock + // Note: you want to do below loop outside the lock so that + // pipeline execution thread doesn't get blocked on Push and Pop. + // Note: A copy of the stack is made because we "unstop" a stopped + // pipeline to execute finally blocks. We don't want to stop pipelines + // in the finally block though. foreach (PipelineProcessor pp in copyStack) { pp.Stop(); @@ -1482,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 1ad99b0313d..bfa79e89f13 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHost.cs @@ -1,25 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Runspaces; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Runspaces; namespace System.Management.Automation.Host { /// - /// - /// Defines the properties and facilities providing by an application hosting an MSH . - /// /// /// - /// /// A hosting application derives from this class and /// 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,200 +30,142 @@ namespace System.Management.Automation.Host /// The instance of the host class that is passed to a Runspace is exposed by the Runspace to the cmdlets, scripts, and /// providers that are executed in that Runspace. Scripts access the host class via the $Host built-in variable. Cmdlets /// access the host via the Host property of the Cmdlet base class. - /// /// /// /// /// - public abstract class PSHost { /// /// The powershell spec states that 128 is the maximum nesting depth. /// internal const int MaximumNestedPromptLevel = 128; + internal static bool IsStdOutputRedirected; /// - /// - /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1 - /// + /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1. /// - protected PSHost() { // do nothing } /// - /// /// Gets the hosting application's identification in some user-friendly fashion. This name can be referenced by scripts and cmdlets /// to identify the host that is executing them. The format of the value is not defined, but a short, simple string is /// recommended. - /// /// /// /// In implementing this member, you should return some sort of informative string describing the nature /// your hosting application. For the default console host shipped by Microsoft this is ConsoleHost. /// /// - /// /// 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; } - - /// - /// /// Gets the version of the hosting application. This value should remain invariant for a particular build of the /// host. This value may be referenced by scripts and cmdlets. - /// /// /// /// 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; } - - /// - /// /// 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; } - - /// - /// /// Gets the hosting application's implementation of the /// abstract base class. A host /// that does not want to support user interaction should return null. - /// /// /// - /// /// A reference to an instance of the hosting application's implementation of a class derived from /// , or null to indicate that user /// interaction is not supported. - /// /// /// /// The implementation of this routine should return an instance of the appropriate /// 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; } - - /// - /// - /// Gets the host's culture: the culture that the runspace should use to set the CurrentCulture on new threads - /// + /// Gets the host's culture: the culture that the runspace should use to set the CurrentCulture on new threads. /// /// - /// /// A CultureInfo object representing the host's current culture. Returning null is not allowed. - /// /// /// - /// /// 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; } - - /// - /// /// Gets the host's UI culture: the culture that the runspace and cmdlets should use to do resource loading. /// /// The runspace will set the thread current ui culture to this value each time it starts a pipeline. - /// /// /// - /// /// A CultureInfo object representing the host's current UI culture. Returning null is not allowed. - /// /// - public abstract System.Globalization.CultureInfo CurrentUICulture { get; } - - /// - /// /// Request by the engine to end the current engine runspace (to shut down and terminate the host's root runspace). - /// /// /// - /// /// This method is called by the engine to request the host shutdown the engine. This is invoked by the exit keyword /// or by any other facility by which a runspace instance wishes to be shut down. /// /// To honor this request, the host should stop accepting and submitting commands to the engine and close the runspace. - /// /// /// - /// /// 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); - - /// - /// /// Instructs the host to interrupt the currently running pipeline and start a new, "nested" input loop, where an input /// loop is the cycle of prompt, input, execute. - /// /// /// - /// /// Typically called by the engine in response to some user action that suspends the currently executing pipeline, such /// as choosing the "suspend" option of a ConfirmProcessing call. Before calling this method, the engine should set /// various shell variables to the express the state of the interrupted input loop (current pipeline, current object in @@ -236,55 +174,36 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// A non-interactive host may throw a "not implemented" exception here. /// /// If the UI property returns null, the engine should not call this method. - /// - /// /// - /// /// /// - public abstract void EnterNestedPrompt(); - - /// - /// /// Causes the host to end the currently running input loop. If the input loop was created by a prior call to /// EnterNestedPrompt, the enclosing pipeline will be resumed. If the current input loop is the top-most loop, then the /// host will act as though SetShouldExit was called. - /// /// /// - /// /// Typically called by the engine in response to some user action that resumes a suspended pipeline, such as with the /// 'continue-command' intrinsic cmdlet. Before calling this method, the engine should clear out the loop-specific /// variables that were set when the loop was created. /// /// If the UI Property returns a null, the engine should not call this method. - /// /// /// - public abstract void ExitNestedPrompt(); - - /// - /// /// Used to allow the host to pass private data through a Runspace to cmdlets running inside that Runspace's /// runspace. The type and nature of that data is entirely defined by the host, but there are some caveats: - /// - /// /// /// - /// /// The default implementation returns null. - /// /// /// - /// /// If the host is using an out-of-process Runspace, then the value of this property is serialized when crossing /// that process boundary in the same fashion as any object in a pipeline is serialized when crossing process boundaries. /// In this case, the BaseObject property of the value will be null. @@ -299,9 +218,7 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// reflected across processes. Ex: if a cmdlet reads this property, then changes the state of the result, that /// 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 @@ -310,20 +227,15 @@ public virtual PSObject PrivateData } } - - /// - /// /// Called by the engine to notify the host that it is about to execute a "legacy" command line application. A legacy /// application is defined as a console-mode executable that may do one or more of the following: /// . reads from stdin /// . writes to stdout /// . writes to stderr - /// . uses any of the win32 console APIs - /// + /// . uses any of the win32 console APIs. /// /// - /// /// Notifying the host allows the host to do such things as save off any state that might need to be restored when the /// legacy application terminates, set or remove break handler hooks, redirect stream handles, and so forth. /// @@ -344,21 +256,14 @@ public virtual PSObject PrivateData /// words, NotifyBeginApplication may be called several times before NotifyEndApplication is called. The only thing /// that is guaranteed is that there will be an equal number of calls to NotifyEndApplication as to /// 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(); /// @@ -376,6 +281,7 @@ public virtual PSObject PrivateData public virtual bool DebuggerEnabled { get { return false; } + set { throw new PSNotImplementedException(); } } } @@ -384,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")] @@ -411,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; } } -} // namespace System.Management.Automation.Host +} diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs index 843cfb7ae5a..64b200b7d61 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs @@ -1,7 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; @@ -14,14 +12,9 @@ namespace System.Management.Automation.Host // I would have preferred to make these nested types within PSHostRawUserInterface, but that // is evidently discouraged by the .net design guidelines. - - /// - /// - /// Represents an (x,y) coordinate pair - /// + /// Represents an (x,y) coordinate pair. /// - public struct Coordinates { @@ -33,42 +26,33 @@ struct Coordinates #endregion /// - /// - /// Gets and sets the X coordinate - /// + /// Gets and sets the X coordinate. /// public int X { get { return x; } + set { x = value; } } /// - /// - /// Gets and sets the Y coordinate - /// + /// Gets and sets the Y coordinate. /// public int Y { get { return y; } + set { y = value; } } - /// - /// /// Initializes a new instance of the Coordinates class and defines the X and Y values. - /// /// /// - /// /// The X coordinate - /// /// /// - /// /// The Y coordinate - /// /// public Coordinates(int x, int y) @@ -77,45 +61,29 @@ public int Y this.y = 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. - /// /// /// - /// /// 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) @@ -130,19 +98,12 @@ public override return result; } - - /// - /// - /// Overrides - /// + /// Overrides /// /// - /// /// Hash code for this instance. - /// /// - public override int GetHashCode() @@ -196,29 +157,18 @@ public override return result; } - - /// - /// - /// Compares two instances for equality - /// + /// Compares two instances for equality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if the respective X and Y values are the same, false otherwise. - /// /// - public static bool operator ==(Coordinates first, Coordinates second) @@ -228,29 +178,18 @@ public static return result; } - - /// - /// - /// Compares two instances for inequality - /// + /// Compares two instances for inequality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// 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) @@ -259,14 +198,9 @@ public static } } - - /// - /// - /// Represents a width and height pair - /// + /// Represents a width and height pair. /// - public struct Size { @@ -278,42 +212,33 @@ struct Size #endregion /// - /// - /// Gets and sets the Width - /// + /// Gets and sets the Width. /// public int Width { get { return width; } + set { width = value; } } /// - /// - /// Gets and sets the Height - /// + /// Gets and sets the Height. /// public int Height { get { return height; } + set { height = value; } } - /// - /// /// Initialize a new instance of the Size class and defines the Width and Height values. - /// /// /// - /// /// The Width - /// /// /// - /// /// The Height - /// /// public Size(int width, int height) @@ -322,43 +247,29 @@ public int Height this.height = 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. - /// /// /// - /// /// 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) @@ -374,19 +285,14 @@ public override } /// - /// - /// Overrides - /// + /// Overrides /// /// - /// /// Hash code for this instance. - /// /// /// - public override int GetHashCode() @@ -441,26 +347,17 @@ public override } /// - /// - /// Compares two instances for equality - /// + /// Compares two instances for equality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if the respective Width and Height fields are the same, false otherwise. - /// /// - public static bool operator ==(Size first, Size second) @@ -471,26 +368,17 @@ public static } /// - /// - /// Compares two instances for inequality - /// + /// Compares two instances for inequality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if any of the respective Width and Height fields are not the same, false otherwise. - /// /// - public static bool operator !=(Size first, Size second) @@ -499,61 +387,39 @@ public static } } - - /// - /// /// Governs the behavior of /// and - /// /// - [Flags] public enum ReadKeyOptions { /// - /// /// 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 - /// + /// Defines the states of Control Key. /// - [Flags] public enum ControlKeyStates @@ -561,66 +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 - /// + /// Represents information of a keystroke. /// - public struct KeyInfo { @@ -634,74 +486,61 @@ struct KeyInfo #endregion /// - /// - /// Gets and set device-independent key - /// + /// Gets and set device-independent key. /// - public int VirtualKeyCode { get { return virtualKeyCode; } + set { virtualKeyCode = value; } } /// - /// Gets and set unicode Character of the key + /// Gets and set unicode Character of the key. /// - public char Character { get { return character; } + set { character = value; } } /// /// State of the control keys. /// - public ControlKeyStates ControlKeyState { get { return controlKeyState; } + set { controlKeyState = value; } } /// - /// Gets and set the status of whether this instance is generated by a key pressed or released + /// Gets and set the status of whether this instance is generated by a key pressed or released. /// - public bool KeyDown { get { return keyDown; } + set { keyDown = value; } } /// - /// /// Initialize a new instance of the KeyInfo class and defines the VirtualKeyCode, /// Character, ControlKeyState and KeyDown values. - /// /// /// - /// /// The virtual key code - /// /// /// - /// /// The character - /// /// /// - /// /// The control key state - /// /// /// - /// /// Whether the key is pressed or released - /// /// - public KeyInfo ( @@ -718,39 +557,27 @@ 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. - /// /// /// - /// /// 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) @@ -766,20 +593,15 @@ public override } /// - /// - /// Overrides - /// + /// Overrides /// /// - /// /// Hash code for this instance. - /// /// /// - public override int GetHashCode() @@ -801,28 +623,19 @@ public override } /// - /// - /// Compares two instances for equality - /// + /// Compares two instances for equality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if the respective Character, ControlKeyStates , KeyDown, and VirtualKeyCode fields /// are the same, false otherwise. - /// /// /// - public static bool operator ==(KeyInfo first, KeyInfo second) @@ -834,28 +647,19 @@ public static } /// - /// - /// Compares two instances for inequality - /// + /// Compares two instances for inequality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if any of the respective Character, ControlKeyStates , KeyDown, or VirtualKeyCode fields /// are the different, false otherwise. - /// /// /// - public static bool operator !=(KeyInfo first, KeyInfo second) @@ -864,18 +668,11 @@ public static } } - - - /// - /// /// Represents a rectangular region of the screen. - /// /// - /// /// - public struct Rectangle { @@ -889,97 +686,77 @@ struct Rectangle #endregion /// - /// - /// Gets and sets the left side of the rectangle - /// + /// Gets and sets the left side of the rectangle. /// - public int Left { get { return left; } + set { left = value; } } /// - /// - /// Gets and sets the top of the rectangle - /// + /// Gets and sets the top of the rectangle. /// - public int Top { get { return top; } + set { top = value; } } /// - /// - /// Gets and sets the right side of the rectangle - /// + /// Gets and sets the right side of the rectangle. /// - public int Right { get { return right; } + set { right = value; } } /// - /// - /// Gets and sets the bottom of the rectangle - /// + /// Gets and sets the bottom of the rectangle. /// - public int Bottom { get { return bottom; } + set { bottom = value; } } /// - /// /// Initialize a new instance of the Rectangle class and defines the Left, Top, Right, and Bottom values. - /// /// /// - /// /// The left side of the rectangle - /// /// /// - /// /// The top of the rectangle - /// /// /// - /// /// The right side of the rectangle - /// /// /// - /// /// The bottom of the rectangle - /// /// /// - /// /// is less than ; /// is less than - /// /// - public Rectangle(int left, int top, int right, int bottom) { if (right < left) { // "right" and "left" are not localizable - throw PSTraceSource.NewArgumentException("right", MshHostRawUserInterfaceStrings.LessThanErrorTemplate, "right", "left"); + throw PSTraceSource.NewArgumentException(nameof(right), MshHostRawUserInterfaceStrings.LessThanErrorTemplate, "right", "left"); } + if (bottom < top) { // "bottom" and "top" are not localizable - throw PSTraceSource.NewArgumentException("bottom", MshHostRawUserInterfaceStrings.LessThanErrorTemplate, "bottom", "top"); + throw PSTraceSource.NewArgumentException(nameof(bottom), MshHostRawUserInterfaceStrings.LessThanErrorTemplate, "bottom", "top"); } this.left = left; @@ -988,72 +765,50 @@ public int Bottom this.bottom = bottom; } - /// - /// /// Initializes a new instance of the Rectangle class and defines the Left, Top, Right, and Bottom values /// by , the upper left corner and , the lower /// right corner. - /// /// /// /// - /// /// The Coordinates of the upper left corner of the Rectangle - /// /// /// - /// /// 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) { } - - /// - /// - /// 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. - /// /// /// - /// /// 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) @@ -1069,19 +824,14 @@ public override } /// - /// - /// Overrides - /// + /// Overrides /// /// - /// /// Hash code for this instance. /// - /// /// /// - public override int GetHashCode() @@ -1139,26 +889,17 @@ public override } /// - /// - /// Compares two instances for equality - /// + /// Compares two instances for equality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if the respective Top, Left, Bottom, and Right fields are the same, false otherwise. - /// /// - public static bool operator ==(Rectangle first, Rectangle second) @@ -1170,27 +911,18 @@ public static } /// - /// - /// Compares two instances for inequality - /// + /// Compares two instances for inequality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// 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) @@ -1199,14 +931,9 @@ public static } } - - /// - /// - /// Represents a character, a foregroundColor color, and background color - /// + /// Represents a character, a foregroundColor color, and background color. /// - public struct BufferCell { @@ -1220,14 +947,12 @@ struct BufferCell #endregion /// - /// - /// Gets and sets the character value - /// + /// Gets and sets the character value. /// - public char Character { get { return character; } + set { character = value; } } @@ -1235,68 +960,51 @@ public char Character // essentially identical enum /// - /// - /// Gets and sets the foreground color - /// + /// Gets and sets the foreground color. /// - public ConsoleColor ForegroundColor { get { return foregroundColor; } + set { foregroundColor = value; } } /// - /// - /// Gets and sets the background color - /// + /// Gets and sets the background color. /// - public ConsoleColor BackgroundColor { get { return backgroundColor; } + set { backgroundColor = value; } } /// - /// - /// Gets and sets the type value - /// + /// Gets and sets the type value. /// - public BufferCellType BufferCellType { get { return bufferCellType; } + set { bufferCellType = value; } } /// - /// /// Initializes a new instance of the BufferCell class and defines the /// Character, ForegroundColor, BackgroundColor and Type values. - /// /// /// - /// /// The character in this BufferCell object - /// /// /// - /// /// The foreground color of this BufferCell object - /// /// /// - /// /// The foreground color of this BufferCell object - /// /// /// - /// /// The type of this BufferCell object - /// /// - public BufferCell(char character, ConsoleColor foreground, ConsoleColor background, BufferCellType bufferCellType) { @@ -1306,43 +1014,29 @@ public BufferCellType BufferCellType this.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. - /// /// /// - /// /// 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) @@ -1358,18 +1052,14 @@ public override } /// - /// - /// Overrides - /// + /// Overrides /// /// /// - /// /// Hash code for this instance. /// - /// - + /// public override int GetHashCode() @@ -1388,26 +1078,17 @@ public override } /// - /// - /// Compares two instances for equality - /// + /// Compares two instances for equality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// true if the respective Character, ForegroundColor, BackgroundColor, and BufferCellType values are the same, false otherwise. - /// /// - public static bool operator ==(BufferCell first, BufferCell second) @@ -1421,124 +1102,87 @@ public static } /// - /// - /// Compares two instances for inequality - /// + /// Compares two instances for inequality. /// /// - /// /// The left side operand. - /// /// /// - /// /// The right side operand. - /// /// /// - /// /// 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) { return !(first == second); } + private const string StringsBaseName = "MshHostRawUserInterfaceStrings"; } /// - /// /// 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 - /// + /// Character occupies one BufferCell. /// - Complete, /// - /// - /// Character occupies two BufferCells and this is the leading one - /// + /// Character occupies two BufferCells and this is the leading one. /// - Leading, /// - /// - /// Preceded by a Leading BufferCell - /// + /// 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. - /// /// /// - /// /// It models an 2-dimensional grid of cells called a Buffer. A buffer has a visible rectangular region, called a window. /// Each cell of the grid has a character, a foreground color, and a background color. When the buffer has input focus, it /// shows a cursor positioned in one cell. Keystrokes can be read from the buffer and optionally echoed at the current /// cursor position. - /// /// /// /// - public abstract class PSHostRawUserInterface { /// - /// - /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1 - /// + /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1. /// - protected PSHostRawUserInterface() { // do nothing } - - /// - /// /// Gets or sets the color used to render characters on the screen buffer. Each character cell in the screen buffer can /// have a separate foreground color. - /// /// - /// /// /// - public abstract ConsoleColor ForegroundColor @@ -1547,16 +1191,11 @@ public abstract set; } - - /// - /// /// Gets or sets the color used to render the background behind characters on the screen buffer. Each character cell in /// the screen buffer can have a separate background color. - /// /// /// - public abstract ConsoleColor BackgroundColor @@ -1565,20 +1204,14 @@ public abstract set; } - - /// - /// /// Gets or sets the cursor position in the screen buffer. The view window always adjusts it's location over the screen /// buffer such that the cursor is always visible. - /// /// /// - /// /// To write to the screen buffer without updating the cursor position, use /// or /// - /// /// /// /// @@ -1586,7 +1219,6 @@ public abstract /// /// /// - public abstract Coordinates CursorPosition @@ -1595,19 +1227,14 @@ public abstract set; } - - /// - /// /// Gets or sets position of the view window relative to the screen buffer, in characters. (0,0) is the upper left of the screen /// buffer. - /// /// /// /// /// /// - public abstract Coordinates WindowPosition @@ -1616,15 +1243,10 @@ public abstract set; } - - /// - /// /// Gets or sets the cursor size as a percentage 0..100. - /// /// /// - public abstract int CursorSize @@ -1633,19 +1255,14 @@ public abstract set; } - - /// - /// /// Gets or sets the current size of the screen buffer, measured in character cells. - /// /// /// /// /// /// /// - public abstract Size BufferSize @@ -1654,20 +1271,15 @@ public abstract set; } - - /// - /// /// Gets or sets the current view window size, measured in character cells. The window size cannot be larger than the /// dimensions returned by . - /// /// /// /// /// /// /// - public abstract Size WindowSize @@ -1676,31 +1288,22 @@ public abstract set; } - - /// - /// /// Gets the size of the largest window possible for the current buffer, current font, and current display hardware. /// The view window cannot be larger than the screen buffer or the current display (the display the window is rendered on). - /// /// /// - /// /// The largest dimensions the window can be resized to without resizing the screen buffer. - /// /// /// - /// /// Always returns a value less than or equal to /// . - /// /// /// /// /// /// /// - public abstract Size MaxWindowSize @@ -1708,27 +1311,20 @@ public abstract get; } - - /// - /// /// Gets the largest window possible for the current font and display hardware, ignoring the current buffer dimensions. In /// other words, the dimensions of the largest window that could be rendered in the current display, if the buffer was /// at least as large. - /// /// /// - /// /// To resize the window to this dimension, use /// to first check and, if necessary, adjust, the screen buffer size. - /// /// /// /// /// /// /// - public abstract Size MaxPhysicalWindowSize @@ -1736,29 +1332,22 @@ public abstract get; } - - /// - /// /// Reads a key stroke from the keyboard device, blocking until a keystroke is typed. /// Same as ReadKey(ReadKeyOptions.IncludeKeyDown) - /// /// /// - /// /// Key stroke when a key is pressed. - /// /// /// - /// + /// /// $Host.UI.RawUI.ReadKey() - /// + /// /// /// /// /// /// - public KeyInfo ReadKey() @@ -1766,77 +1355,54 @@ public abstract return ReadKey(ReadKeyOptions.IncludeKeyDown); } - - /// - /// /// Reads a key stroke from the keyboard device, blocking until a keystroke is typed. /// Either one of ReadKeyOptions.IncludeKeyDown and ReadKeyOptions.IncludeKeyUp or both must be specified. - /// /// /// - /// /// A bit mask of the options to be used to read the keyboard. Constants defined by /// - /// /// /// - /// /// Key stroke depending on the value of . - /// /// /// - /// /// 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); - - /// - /// /// Resets the keyboard input buffer. - /// /// /// /// /// - public abstract void FlushInputBuffer(); - - /// - /// /// A non-blocking call to examine if a keystroke is waiting in the input buffer. - /// /// /// - /// /// True if a keystroke is waiting in the input buffer, false if not. - /// /// /// /// /// - public abstract bool KeyAvailable @@ -1844,14 +1410,9 @@ public abstract get; } - - /// - /// /// Gets or sets the titlebar text of the current view window. - /// /// - public abstract string WindowTitle @@ -1860,24 +1421,16 @@ public abstract set; } - - /// - /// /// Copies the array into the screen buffer at the /// given origin, clipping such that cells in the array that would fall outside the screen buffer are ignored. - /// /// /// - /// /// The top left corner of the rectangular screen area to which is copied. - /// /// /// - /// /// A rectangle of objects to be copied to the /// screen buffer. - /// /// /// /// @@ -1887,40 +1440,29 @@ public abstract /// /// /// - public abstract void SetBufferContents(Coordinates origin, BufferCell[,] contents); - - /// - /// /// Copies a given character to all of the character cells in the screen buffer with the indicated colors. - /// /// /// - /// /// The rectangle on the screen buffer to which is copied. /// If all elements are -1, the entire screen buffer will be copied with . - /// /// /// - /// /// The character and attributes used to fill . - /// /// /// - /// /// 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 @@ -1932,7 +1474,7 @@ public abstract /// } /// } /// } - /// + /// /// /// /// @@ -1942,31 +1484,21 @@ public abstract /// /// /// - public abstract void SetBufferContents(Rectangle rectangle, BufferCell fill); - - /// - /// /// Extracts a rectangular region of the screen buffer. - /// /// /// - /// /// The rectangle on the screen buffer to extract. - /// /// /// - /// /// An array of objects extracted from /// the rectangular region of the screen buffer specified by - /// /// /// - /// /// If the rectangle is completely outside of the screen buffer, a BufferCell array of zero rows and column will be /// returned. /// @@ -1981,7 +1513,6 @@ public abstract /// The resulting array is organized in row-major order for performance reasons. The screen buffer, however, is /// organized in column-major order -- e.g. you specify the column index first, then the row index second, as in (x, y). /// This means that a cell at screen buffer position (x, y) is in the array element [y, x]. - /// /// /// /// @@ -1991,40 +1522,27 @@ public abstract /// /// /// - public abstract BufferCell[,] GetBufferContents(Rectangle rectangle); - - /// - /// /// Scroll a region of the screen buffer. - /// /// /// - /// /// Indicates the region of the screen to be scrolled. - /// /// /// - /// /// Indicates the upper left coordinates of the region of the screen to receive the source region contents. The target /// region is the same size as the source region. - /// /// /// - /// /// Indicates the region of the screen to include in the operation. If a cell would be changed by the operation but /// does not fall within the clip region, it will be unchanged. - /// /// /// - /// /// The character and attributes to be used to fill any cells within the intersection of the source rectangle and /// clipping rectangle that are left "empty" by the move. - /// /// /// /// @@ -2034,7 +1552,6 @@ public abstract /// /// /// - public abstract void ScrollBufferContents @@ -2046,26 +1563,18 @@ BufferCell fill ); /// - /// /// Determines the number of BufferCells a substring of a string occupies. - /// /// /// - /// /// The string whose substring length we want to know. - /// /// /// - /// /// Offset where the substring begins in - /// /// /// - /// /// The default implementation calls method /// with the substring extracted from the string /// starting at the offset - /// /// /// /// @@ -2076,7 +1585,6 @@ BufferCell fill /// /// /// - public virtual int LengthInBufferCells @@ -2087,7 +1595,7 @@ int offset { if (source == null) { - throw PSTraceSource.NewArgumentNullException("source"); + throw PSTraceSource.NewArgumentNullException(nameof(source)); } // this implementation is inefficient @@ -2099,19 +1607,13 @@ int offset } /// - /// /// Determines the number of BufferCells a string occupies. - /// /// /// - /// /// The string whose length we want to know. - /// /// /// - /// /// The default implementation returns the length of - /// /// /// /// @@ -2122,7 +1624,6 @@ int offset /// /// /// - public virtual int LengthInBufferCells @@ -2132,25 +1633,20 @@ string source { if (source == null) { - throw PSTraceSource.NewArgumentNullException("source"); + throw PSTraceSource.NewArgumentNullException(nameof(source)); } + return source.Length; } /// - /// /// Determines the number of BufferCells a character occupies. - /// /// /// - /// /// The character whose length we want to know. - /// /// /// - /// /// The default implementation returns 1. - /// /// /// /// @@ -2160,7 +1656,6 @@ string source /// /// /// - public virtual int LengthInBufferCells @@ -2172,40 +1667,27 @@ char source } /// - /// /// Creates a two dimensional array of BufferCells by examining each character in . - /// /// /// - /// /// String array based on which the two dimensional array of BufferCells will be created. - /// /// /// - /// /// Foreground color of the buffer cells in the resulting array. - /// /// /// - /// /// Background color of the buffer cells in the resulting array. - /// /// /// - /// /// A two dimensional array of BufferCells whose characters are the same as those in /// and whose foreground and background colors set to and /// - /// /// /// - /// /// 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 @@ -2219,8 +1701,7 @@ char source /// and , respectively. /// The resulting array is suitable for use with /// and . - /// - /// + /// /// /// /// @@ -2238,8 +1719,9 @@ char source if (contents == null) { - throw PSTraceSource.NewArgumentNullException("contents"); + throw PSTraceSource.NewArgumentNullException(nameof(contents)); } + byte[][] charLengths = new byte[contents.Length][]; int maxStringLengthInBufferCells = 0; for (int i = 0; i < contents.Length; i++) @@ -2248,6 +1730,7 @@ char source { continue; } + int lengthInBufferCells = 0; charLengths[i] = new byte[contents[i].Length]; for (int j = 0; j < contents[i].Length; j++) @@ -2255,15 +1738,18 @@ char source charLengths[i][j] = (byte)LengthInBufferCells(contents[i][j]); lengthInBufferCells += charLengths[i][j]; } + if (maxStringLengthInBufferCells < lengthInBufferCells) { maxStringLengthInBufferCells = lengthInBufferCells; } } + if (maxStringLengthInBufferCells <= 0) { - throw PSTraceSource.NewArgumentException("contents", MshHostRawUserInterfaceStrings.AllNullOrEmptyStringsErrorTemplate); + throw PSTraceSource.NewArgumentException(nameof(contents), MshHostRawUserInterfaceStrings.AllNullOrEmptyStringsErrorTemplate); } + BufferCell[,] results = new BufferCell[contents.Length, maxStringLengthInBufferCells]; for (int i = 0; i < contents.Length; i++) { @@ -2290,48 +1776,35 @@ char source resultJ++; } } + return results; #pragma warning restore 56506 } #pragma warning restore 56506 /// - /// /// Creates a 2D array of BufferCells by examining .Character. - /// - /// + /// /// /// - /// /// The number of columns of the resulting array - /// /// /// - /// /// The number of rows of the resulting array - /// /// /// - /// /// The cell to be copied to each of the elements of the resulting array. - /// /// /// - /// /// A by array of BufferCells where each cell's value is /// based on - /// /// - /// /// /// - /// /// is less than 1; /// is less than 1. - /// /// /// - /// /// If the character takes one BufferCell to display as determined by LengthInBufferCells, /// one BufferCell is allocated with its Character set to the character and BufferCellType to /// BufferCell.Complete. @@ -2342,7 +1815,6 @@ char source /// is odd, the last column will just contain the leading cell. /// .BufferCellType is not used in creating the array. /// The resulting array is suitable for use with the PSHostRawUserInterface.SetBufferContents method. - /// /// /// /// @@ -2352,7 +1824,6 @@ char source /// /// /// - public BufferCell[,] NewBufferCellArray(int width, int height, BufferCell contents) @@ -2360,14 +1831,14 @@ char source if (width <= 0) { // "width" is not localizable - throw PSTraceSource.NewArgumentOutOfRangeException("width", width, + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(width), width, MshHostRawUserInterfaceStrings.NonPositiveNumberErrorTemplate, "width"); } if (height <= 0) { // "height" is not localizable - throw PSTraceSource.NewArgumentOutOfRangeException("height", height, + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(height), height, MshHostRawUserInterfaceStrings.NonPositiveNumberErrorTemplate, "height"); } @@ -2398,6 +1869,7 @@ char source contents.ForegroundColor, contents.BackgroundColor, BufferCellType.Trailing); } + if (normalizedWidth < width) { buffer[i, normalizedWidth] = contents; @@ -2405,34 +1877,25 @@ char source } } } + return buffer; } /// - /// /// Same as - /// /// /// - /// /// The width and height of the resulting array. - /// /// /// - /// /// The cell to be copied to each of the elements of the resulting array. - /// /// /// - /// /// An array of BufferCells whose size is and where each cell's value is /// based on - /// /// /// - /// /// If .Width or .Height is less than 1. - /// /// /// /// @@ -2442,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 bac5cdd0124..5f51ba15751 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -1,32 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; -using System.Text; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Security; using System.Globalization; +using System.IO; using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using Microsoft.PowerShell.Commands; +using System.Security; +using System.Text; using System.Threading; using System.Threading.Tasks; namespace System.Management.Automation.Host { /// - /// /// Defines the properties and facilities providing by an hosting application deriving from /// that offers dialog-oriented and /// line-oriented interactive features. - /// /// /// /// - public abstract class PSHostUserInterface { /// @@ -62,6 +58,7 @@ public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI /// /// public abstract string ReadLine(); + /// /// 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). @@ -115,14 +112,14 @@ public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI /// /// The default implementation writes a carriage return to the screen buffer. - /// - /// - /// - /// + /// + /// + /// + /// /// public virtual void WriteLine() { - WriteLine(""); + WriteLine(string.Empty); } /// @@ -173,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. @@ -235,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 @@ -269,10 +407,11 @@ private TranscriptionData TranscriptionData return temporaryTranscriptionData; } } + private TranscriptionData _volatileTranscriptionData; /// - /// Transcribes a command being invoked + /// Transcribes a command being invoked. /// /// The text of the command being invoked. /// The invocation info of the command being transcribed. @@ -302,7 +441,7 @@ internal void TranscribeCommand(string commandText, InvocationInfo invocation) { transcript.OutputToLog.Add("**********************"); transcript.OutputToLog.Add( - String.Format( + string.Format( Globalization.CultureInfo.InvariantCulture, InternalHostUserInterfaceStrings.CommandStartTime, DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture))); transcript.OutputToLog.Add("**********************"); @@ -352,7 +491,7 @@ private bool ShouldIgnoreCommand(string logElement, InvocationInfo invocation) string[] helperCommands = { "TabExpansion2", "prompt", "TabExpansion", "PSConsoleHostReadline" }; foreach (string helperCommand in helperCommands) { - if (String.Equals(helperCommand, commandName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(helperCommand, commandName, StringComparison.OrdinalIgnoreCase)) { IgnoreCommand(logElement, invocation); @@ -393,17 +532,22 @@ internal void IgnoreCommand(string commandText, InvocationInfo invocation) /// make it to the actual host. /// internal bool TranscribeOnly => Interlocked.CompareExchange(ref _transcribeOnlyCount, 0, 0) != 0; + private int _transcribeOnlyCount = 0; + internal IDisposable SetTranscribeOnly() => new TranscribeOnlyCookie(this); + private sealed class TranscribeOnlyCookie : IDisposable { - private PSHostUserInterface _ui; + private readonly PSHostUserInterface _ui; private bool _disposed = false; + public TranscribeOnlyCookie(PSHostUserInterface ui) { - _ui=ui; + _ui = ui; Interlocked.Increment(ref _ui._transcribeOnlyCount); } + public void Dispose() { if (!_disposed) @@ -413,6 +557,7 @@ public void Dispose() GC.SuppressFinalize(this); } } + ~TranscribeOnlyCookie() => Dispose(); } @@ -434,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); @@ -443,66 +588,79 @@ private void CheckSystemTranscript() } } - internal void StartTranscribing(string path, System.Management.Automation.Remoting.PSSenderInfo senderInfo, bool includeInvocationHeader) + internal void StartTranscribing(string path, System.Management.Automation.Remoting.PSSenderInfo senderInfo, bool includeInvocationHeader, bool useMinimalHeader) { TranscriptionOption transcript = new TranscriptionOption(); transcript.Path = path; transcript.IncludeInvocationHeader = includeInvocationHeader; TranscriptionData.Transcripts.Add(transcript); - LogTranscriptHeader(senderInfo, transcript); + LogTranscriptHeader(senderInfo, transcript, useMinimalHeader); } - private void LogTranscriptHeader(System.Management.Automation.Remoting.PSSenderInfo senderInfo, TranscriptionOption transcript) + private void LogTranscriptHeader(System.Management.Automation.Remoting.PSSenderInfo senderInfo, TranscriptionOption transcript, bool useMinimalHeader = false) { - string username = Environment.UserDomainName + "\\" + Environment.UserName; - string runAsUser = username; - - if (senderInfo != null) + // Transcribe the transcript header + string line; + if (useMinimalHeader) { - username = senderInfo.UserInfo.Identity.Name; + line = + string.Format( + Globalization.CultureInfo.InvariantCulture, + InternalHostUserInterfaceStrings.MinimalTranscriptPrologue, + DateTime.Now); } - - // Add bits from PSVersionTable - StringBuilder psVersionInfo = new StringBuilder(); - Hashtable versionInfo = PSVersionInfo.GetPSVersionTable(); - foreach (string versionKey in versionInfo.Keys) + else { - Object value = versionInfo[versionKey]; + string username = Environment.UserDomainName + "\\" + Environment.UserName; + string runAsUser = username; - if (value != null) + if (senderInfo != null) { - var arrayValue = value as object[]; - string valueString = arrayValue != null ? string.Join(", ", arrayValue) : value.ToString(); - psVersionInfo.AppendLine(versionKey + ": " + valueString); + username = senderInfo.UserInfo.Identity.Name; } + + // Add bits from PSVersionTable + StringBuilder versionInfoFooter = new StringBuilder(); + Hashtable versionInfo = PSVersionInfo.GetPSVersionTable(); + foreach (string versionKey in versionInfo.Keys) + { + object value = versionInfo[versionKey]; + + if (value != null) + { + var arrayValue = value as object[]; + string valueString = arrayValue != null ? string.Join(", ", arrayValue) : value.ToString(); + versionInfoFooter.AppendLine(versionKey + ": " + valueString); + } + } + + string configurationName = string.Empty; + if (senderInfo != null && !string.IsNullOrEmpty(senderInfo.ConfigurationName)) + { + configurationName = senderInfo.ConfigurationName; + } + + line = + string.Format( + Globalization.CultureInfo.InvariantCulture, + InternalHostUserInterfaceStrings.TranscriptPrologue, + DateTime.Now, + username, + runAsUser, + configurationName, + Environment.MachineName, + Environment.OSVersion.VersionString, + string.Join(" ", Environment.GetCommandLineArgs()), + Environment.ProcessId, + versionInfoFooter.ToString().TrimEnd()); } - string psConfigurationName = string.Empty; - if (senderInfo != null && !string.IsNullOrEmpty(senderInfo.ConfigurationName)) - { - psConfigurationName = senderInfo.ConfigurationName; - } - // Transcribe the transcript header - string format = InternalHostUserInterfaceStrings.TranscriptPrologue; - string line = - String.Format( - Globalization.CultureInfo.InvariantCulture, - format, - DateTime.Now, - username, - runAsUser, - psConfigurationName, - Environment.MachineName, - Environment.OSVersion.VersionString, - String.Join(" ", Environment.GetCommandLineArgs()), - System.Diagnostics.Process.GetCurrentProcess().Id, - psVersionInfo.ToString().TrimEnd() - ); lock (transcript.OutputToLog) { transcript.OutputToLog.Add(line); } + TranscribeCommandComplete(null); } @@ -526,7 +684,7 @@ private void LogTranscriptFooter(TranscriptionOption stoppedTranscript) // Transcribe the transcript epilogue try { - string message = String.Format( + string message = string.Format( Globalization.CultureInfo.InvariantCulture, InternalHostUserInterfaceStrings.TranscriptEpilogue, DateTime.Now); @@ -534,6 +692,7 @@ private void LogTranscriptFooter(TranscriptionOption stoppedTranscript) { stoppedTranscript.OutputToLog.Add(message); } + TranscribeCommandComplete(null); } catch (Exception) @@ -592,7 +751,7 @@ internal void TranscribeResult(Runspace sourceRunspace, string resultText) if (TranscriptionData.CommandBeingIgnored != null) { // If we're ignoring a prompt, capture the value - if (String.Equals("prompt", TranscriptionData.CommandBeingIgnored, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("prompt", TranscriptionData.CommandBeingIgnored, StringComparison.OrdinalIgnoreCase)) { TranscriptionData.PromptText = resultText; } @@ -601,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) @@ -632,7 +798,7 @@ internal void TranscribeResult(string resultText) } /// - /// Transcribes / records the completion of a command + /// Transcribes / records the completion of a command. /// /// internal void TranscribeCommandComplete(InvocationInfo invocation) @@ -653,7 +819,7 @@ internal void TranscribeCommandComplete(InvocationInfo invocation) // If we're completing a command that we were ignoring, start transcribing results / etc. again. if ((TranscriptionData.CommandBeingIgnored != null) && (invocation != null) && (invocation.MyCommand != null) && - String.Equals(commandNameToCheck, invocation.MyCommand.Name, StringComparison.OrdinalIgnoreCase)) + string.Equals(commandNameToCheck, invocation.MyCommand.Name, StringComparison.OrdinalIgnoreCase)) { TranscriptionData.CommandBeingIgnored = null; TranscriptionData.IsHelperCommand = false; @@ -702,19 +868,21 @@ private void FlushPendingOutput() // Transcription should begin only if file generation is successful. // If there is an error in file generation, throw the exception. string baseDirectory = Path.GetDirectoryName(transcript.Path); - if (Directory.Exists(transcript.Path) || (String.Equals(baseDirectory, transcript.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.Ordinal))) + if (Directory.Exists(transcript.Path) || (string.Equals(baseDirectory, transcript.Path.TrimEnd(Path.DirectorySeparatorChar), StringComparison.Ordinal))) { - string errorMessage = String.Format( + string errorMessage = string.Format( System.Globalization.CultureInfo.CurrentCulture, InternalHostUserInterfaceStrings.InvalidTranscriptFilePath, transcript.Path); throw new ArgumentException(errorMessage); } - if(!Directory.Exists(baseDirectory)) + + if (!Directory.Exists(baseDirectory)) { Directory.CreateDirectory(baseDirectory); } - if(!File.Exists(transcript.Path)) + + if (!File.Exists(transcript.Path)) { File.Create(transcript.Path).Dispose(); } @@ -724,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) @@ -758,7 +926,7 @@ private void FlushPendingOutput() #endregion Line-oriented interaction - # region Dialog-oriented Interaction + #region Dialog-oriented Interaction /// /// Constructs a 'dialog' where the user is presented with a number of fields for which to supply values. @@ -783,7 +951,7 @@ private void FlushPendingOutput() /// /// /// - public abstract Dictionary Prompt(string caption, string message, Collection descriptions); + public abstract Dictionary Prompt(string caption, string message, Collection descriptions); /// /// Prompt for credentials. @@ -883,7 +1051,7 @@ PSCredentialUIOptions options #endregion Dialog-oriented interaction /// - /// Creates a new instance of the PSHostUserInterface class + /// Creates a new instance of the PSHostUserInterface class. /// protected PSHostUserInterface() { @@ -893,9 +1061,9 @@ protected PSHostUserInterface() /// /// Helper to transcribe an error through formatting and output. /// - /// The Execution Context - /// The invocation info associated with the record - /// The error record + /// The Execution Context. + /// The invocation info associated with the record. + /// The error record. internal void TranscribeError(ExecutionContext context, InvocationInfo invocation, PSObject errorWrap) { context.InternalHost.UI.TranscribeCommandComplete(invocation); @@ -910,7 +1078,10 @@ internal void TranscribeError(ExecutionContext context, InvocationInfo invocatio /// internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOption currentTranscript) { - var transcription = Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig); + var transcription = InternalTestHooks.BypassGroupPolicyCaching + ? Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig) + : s_transcriptionSettingCache.Value; + if (transcription != null) { // If we have an existing system transcript for this process, use that. @@ -919,17 +1090,19 @@ 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); } } return systemTranscript; } + internal static TranscriptionOption systemTranscript = null; - private static Object s_systemTranscriptLock = new Object(); + private static readonly object s_systemTranscriptLock = new object(); + + private static readonly Lazy s_transcriptionSettingCache = new Lazy( + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + isThreadSafe: true); private static TranscriptionOption GetTranscriptOptionFromSettings(Transcription transcriptConfig, TranscriptionOption currentTranscript) { @@ -969,7 +1142,7 @@ internal static string GetTranscriptPath() internal static string GetTranscriptPath(string baseDirectory, bool includeDate) { - if (String.IsNullOrEmpty(baseDirectory)) + if (string.IsNullOrEmpty(baseDirectory)) { baseDirectory = Platform.GetFolderPath(Environment.SpecialFolder.MyDocuments); } @@ -983,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)); @@ -994,9 +1172,9 @@ 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); - string filename = String.Format( + 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", Environment.MachineName, @@ -1020,19 +1198,17 @@ 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; } } - // Holds options for PowerShell transcription internal class TranscriptionOption : IDisposable { @@ -1045,31 +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 @@ -1077,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 @@ -1090,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) @@ -1098,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) @@ -1111,8 +1276,9 @@ 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; } @@ -1125,6 +1291,7 @@ internal void FlushContentToDisk() OutputBeingLogged.Clear(); } } + private StreamWriter _contentWriter = null; /// @@ -1132,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) @@ -1147,13 +1317,26 @@ 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; } _disposed = true; } + private bool _disposed = false; } @@ -1162,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 { /// @@ -1186,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 @@ -1220,9 +1405,10 @@ 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)); - hotkeysAndPlainLabels[0, i] = CultureInfo.CurrentCulture.TextInfo.ToUpper(choices[i].Label.Substring(andPos + 1, 1).Trim()); + splitLabel.Append(choices[i].Label.AsSpan(andPos + 1)); + hotkeysAndPlainLabels[0, i] = CultureInfo.CurrentCulture.TextInfo.ToUpper(choices[i].Label.AsSpan(andPos + 1, 1).Trim().ToString()); } + hotkeysAndPlainLabels[1, i] = splitLabel.ToString().Trim(); } else @@ -1232,10 +1418,10 @@ internal static void BuildHotkeysAndPlainLabels(Collection ch #endregion SplitLabel // ? is not localizable - if (string.Compare(hotkeysAndPlainLabels[0, i], "?", StringComparison.Ordinal) == 0) + 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; } @@ -1252,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"); @@ -1264,7 +1450,7 @@ internal static int DetermineChoicePicked(string response, Collection 0) { - if (string.Compare(response, hotkeysAndPlainLabels[0, i], StringComparison.CurrentCultureIgnoreCase) == 0) + if (string.Equals(response, hotkeysAndPlainLabels[0, i], StringComparison.CurrentCultureIgnoreCase)) { result = i; break; @@ -1291,6 +1477,4 @@ 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 ((null != base.Parent) && (!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 ((null != _fallbacks) && (_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 (null == _parentCI) - { - lock (_syncObject) - { - if (null == _parentCI) - { - string parentCulture = base.Parent.Name; - // remove the parentCulture from the m_fallbacks list. - // ie., remove duplicates from the parent hierarchy. - string[] fallbacksForTheParent = null; - if (null != _fallbacks) - { - 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 (null == s_uiCulture) - { - lock (s_syncObject) - { - if (null == s_uiCulture) - { - s_uiCulture = GetUICulture(); - } - } - } - - return (CultureInfo)s_uiCulture.Clone(); - } - } - - internal static CultureInfo Culture - { - get - { - if (null == s_culture) - { - lock (s_syncObject) - { - if (null == s_culture) - { - 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(new char[] { '\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 = ""; - - - 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 - } -} \ No newline at end of file diff --git a/src/System.Management.Automation/engine/hostifaces/PSCommand.cs b/src/System.Management.Automation/engine/hostifaces/PSCommand.cs index 3b0266586c9..6db40850381 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSCommand.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSCommand.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -32,7 +32,7 @@ public PSCommand() } /// - /// Internal copy constructor + /// Internal copy constructor. /// /// internal PSCommand(PSCommand commandToClone) @@ -48,9 +48,9 @@ internal PSCommand(PSCommand commandToClone) } /// - /// Creates a PSCommand from the specified command + /// Creates a PSCommand from the specified command. /// - /// Command object to use + /// Command object to use. internal PSCommand(Command command) { _currentCommand = command; @@ -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. @@ -87,15 +87,13 @@ internal PSCommand(Command command) /// public PSCommand AddCommand(string command) { - if (null == command) + if (command == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); - } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } + _owner?.AssertChangesAreAccepted(); + _currentCommand = new Command(command, false); _commands.Add(_currentCommand); @@ -130,15 +128,13 @@ public PSCommand AddCommand(string command) /// public PSCommand AddCommand(string cmdlet, bool useLocalScope) { - if (null == cmdlet) + if (cmdlet == null) { - throw PSTraceSource.NewArgumentNullException("cmdlet"); - } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewArgumentNullException(nameof(cmdlet)); } + _owner?.AssertChangesAreAccepted(); + _currentCommand = new Command(cmdlet, false, useLocalScope); _commands.Add(_currentCommand); @@ -149,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. @@ -171,15 +167,13 @@ public PSCommand AddCommand(string cmdlet, bool useLocalScope) /// public PSCommand AddScript(string script) { - if (null == script) - { - throw PSTraceSource.NewArgumentNullException("script"); - } - if (_owner != null) + if (script == null) { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewArgumentNullException(nameof(script)); } + _owner?.AssertChangesAreAccepted(); + _currentCommand = new Command(script, true); _commands.Add(_currentCommand); @@ -190,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); /// /// /// @@ -201,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. @@ -215,15 +209,13 @@ public PSCommand AddScript(string script) /// public PSCommand AddScript(string script, bool useLocalScope) { - if (null == script) + if (script == null) { - throw PSTraceSource.NewArgumentNullException("script"); - } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewArgumentNullException(nameof(script)); } + _owner?.AssertChangesAreAccepted(); + _currentCommand = new Command(script, true, useLocalScope); _commands.Add(_currentCommand); @@ -252,15 +244,13 @@ public PSCommand AddScript(string script, bool useLocalScope) /// public PSCommand AddCommand(Command command) { - if (null == command) + if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); - } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } + _owner?.AssertChangesAreAccepted(); + _currentCommand = command; _commands.Add(_currentCommand); @@ -271,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"); /// /// /// @@ -297,15 +288,14 @@ public PSCommand AddCommand(Command command) /// public PSCommand AddParameter(string parameterName, object value) { - if (null == _currentCommand) + if (_currentCommand == null) { throw PSTraceSource.NewInvalidOperationException(PSCommandStrings.ParameterRequiresCommand, new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + + _owner?.AssertChangesAreAccepted(); + _currentCommand.Parameters.Add(parameterName, value); return this; } @@ -314,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"); /// /// /// @@ -337,16 +328,32 @@ public PSCommand AddParameter(string parameterName, object value) /// public PSCommand AddParameter(string parameterName) { - if (null == _currentCommand) + if (_currentCommand == null) { throw PSTraceSource.NewInvalidOperationException(PSCommandStrings.ParameterRequiresCommand, 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; } @@ -354,10 +361,10 @@ 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 /// of the "select-object" cmdlet which is "Property". @@ -378,15 +385,14 @@ public PSCommand AddParameter(string parameterName) /// public PSCommand AddArgument(object value) { - if (null == _currentCommand) + if (_currentCommand == null) { throw PSTraceSource.NewInvalidOperationException(PSCommandStrings.ParameterRequiresCommand, new object[] { "PSCommand" }); } - if (_owner != null) - { - _owner.AssertChangesAreAccepted(); - } + + _owner?.AssertChangesAreAccepted(); + _currentCommand.Parameters.Add(null, value); return this; } @@ -437,7 +443,7 @@ public CommandCollection Commands } /// - /// The PowerShell instance this PSCommand is associated to, or null if it is an standalone command + /// The PowerShell instance this PSCommand is associated to, or null if it is an standalone command. /// internal PowerShell Owner { @@ -445,6 +451,7 @@ internal PowerShell Owner { return _owner; } + set { _owner = value; diff --git a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs index f2b4fe0ca48..a0945e21df1 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs @@ -1,14 +1,14 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; -using System.Collections.ObjectModel; using System.Collections.Generic; -using System.Threading; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; // for fxcop using System.Reflection; using System.Runtime.Serialization; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -27,7 +27,7 @@ public sealed class DataAddedEventArgs : EventArgs #region Constructor /// - /// Constructor + /// Constructor. /// /// /// PowerShell InstanceId which added this data. @@ -76,7 +76,7 @@ public sealed class DataAddingEventArgs : EventArgs #region Constructor /// - /// Constructor + /// Constructor. /// /// /// PowerShell InstanceId which added this data. @@ -99,7 +99,7 @@ internal DataAddingEventArgs(Guid psInstanceId, object itemAdded) /// /// The item about to be added. /// - public Object ItemAdded { get; } + public object ItemAdded { get; } /// /// PowerShell InstanceId which added this data. @@ -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; @@ -134,13 +133,13 @@ public class PSDataCollection : IList, ICollection, IEnumerable, ILi /// /// Whether the enumerator needs to be blocking - /// by default + /// by default. /// private bool _blockingEnumerator = false; /// - /// whether the ref count was incremented when - /// BlockingEnumerator was updated + /// Whether the ref count was incremented when + /// BlockingEnumerator was updated. /// private bool _refCountIncrementedForBlockingEnumerator = false; @@ -153,7 +152,7 @@ public class PSDataCollection : IList, ICollection, IEnumerable, ILi #region Public Constructors /// - /// Default Constructor + /// Default Constructor. /// public PSDataCollection() : this(new List()) { @@ -196,10 +195,10 @@ public PSDataCollection(int capacity) : this(new List(capacity)) #region type converters /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(bool valueToConvert) @@ -208,10 +207,10 @@ public static implicit operator PSDataCollection(bool valueToConvert) } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(string valueToConvert) @@ -220,10 +219,10 @@ public static implicit operator PSDataCollection(string valueToConvert) } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(int valueToConvert) @@ -232,10 +231,10 @@ public static implicit operator PSDataCollection(int valueToConvert) } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(byte valueToConvert) @@ -252,10 +251,10 @@ private static PSDataCollection CreateAndInitializeFromExplicitValue(object v } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(Hashtable valueToConvert) @@ -267,10 +266,10 @@ public static implicit operator PSDataCollection(Hashtable valueToConvert) } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(T valueToConvert) @@ -282,10 +281,10 @@ public static implicit operator PSDataCollection(T valueToConvert) } /// - /// Wrap the argument in a PSDataCollection + /// Wrap the argument in a PSDataCollection. /// - /// The value to convert - /// New collection of value, marked as Complete + /// The value to convert. + /// New collection of value, marked as Complete. [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "There are already alternates to the implicit casts, ToXXX and FromXXX methods are unnecessary and redundant")] public static implicit operator PSDataCollection(object[] arrayToConvert) @@ -298,6 +297,7 @@ public static implicit operator PSDataCollection(object[] arrayToConvert) psdc.Add(LanguagePrimitives.ConvertTo(ae)); } } + psdc.Complete(); return psdc; } @@ -324,22 +324,20 @@ internal PSDataCollection(IList listToUse) } /// - /// Creates a PSDataCollection from an ISerializable context + /// Creates a PSDataCollection from an ISerializable context. /// - /// Serialization information for this instance - /// The streaming context for this instance + /// Serialization information for this instance. + /// The streaming context for this instance. protected PSDataCollection(SerializationInfo info, StreamingContext context) { if (info == null) { - throw PSTraceSource.NewArgumentNullException("info"); + 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("info"); + throw PSTraceSource.NewArgumentNullException(nameof(info)); } _data = listToUse; @@ -360,7 +358,7 @@ protected PSDataCollection(SerializationInfo info, StreamingContext context) public event EventHandler DataAdding; /// - /// Event fired when objects are done being added to the underlying buffer + /// Event fired when objects are done being added to the underlying buffer. /// public event EventHandler DataAdded; @@ -391,7 +389,11 @@ public bool IsOpen /// public int DataAddedCount { - get { return _dataAddedFrequency; } + get + { + return _dataAddedFrequency; + } + set { bool raiseDataAdded = false; @@ -436,6 +438,7 @@ public bool SerializeInput _serializeInput = value; } } + private bool _serializeInput = false; /// @@ -460,6 +463,7 @@ internal Guid SourceId return _sourceGuid; } } + set { lock (SyncObject) @@ -471,7 +475,7 @@ internal Guid SourceId /// /// If this flag is set to true, the items in the collection will be set to null when it is - /// traversed using a PSDataCollectionEnumerator + /// traversed using a PSDataCollectionEnumerator. /// internal bool ReleaseOnEnumeration { @@ -482,6 +486,7 @@ internal bool ReleaseOnEnumeration return _releaseOnEnumeration; } } + set { lock (SyncObject) @@ -492,7 +497,7 @@ internal bool ReleaseOnEnumeration } /// - /// This flag is true when the collection has been enumerated at least once by a PSDataCollectionEnumerator + /// This flag is true when the collection has been enumerated at least once by a PSDataCollectionEnumerator. /// internal bool IsEnumerated { @@ -503,6 +508,7 @@ internal bool IsEnumerated return _isEnumerated; } } + set { lock (SyncObject) @@ -514,7 +520,7 @@ internal bool IsEnumerated /// /// Completes insertions to the buffer. - /// Subsequent Inserts to the buffer will result in an InvalidOperationException + /// Subsequent Inserts to the buffer will result in an InvalidOperationException. /// public void Complete() { @@ -546,20 +552,14 @@ public void Complete() // raise the events outside of the lock. if (raiseEvents) { - if (null != _readWaitHandle) - { - // 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 (null != tempCompleted) - { - tempCompleted(this, EventArgs.Empty); - } + Completed?.Invoke(this, EventArgs.Empty); } + if (raiseDataAdded) { RaiseDataAddedEvent(_lastPsInstanceId, _lastIndex); @@ -574,7 +574,7 @@ public void Complete() /// the data collection, a reference count is added /// which causes the enumerator to be blocking. This /// prevents the use of PSDataCollection without a - /// PowerShell object. This property fixes the same + /// PowerShell object. This property fixes the same. /// public bool BlockingEnumerator { @@ -585,6 +585,7 @@ public bool BlockingEnumerator return _blockingEnumerator; } } + set { lock (SyncObject) @@ -649,20 +650,22 @@ public T this[int index] return _data[index]; } } + set { lock (SyncObject) { if ((index < 0) || (index >= _data.Count)) { - throw PSTraceSource.NewArgumentOutOfRangeException("index", index, + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(index), index, PSDataBufferStrings.IndexOutOfRange, 0, _data.Count - 1); } if (_serializeInput) { - value = (T)(Object)GetSerializedObject(value); + value = (T)(object)GetSerializedObject(value); } + _data[index] = value; } } @@ -728,9 +731,10 @@ public void RemoveAt(int index) { if ((index < 0) || (index >= _data.Count)) { - throw PSTraceSource.NewArgumentOutOfRangeException("index", index, + throw PSTraceSource.NewArgumentOutOfRangeException(nameof(index), index, PSDataBufferStrings.IndexOutOfRange, 0, _data.Count - 1); } + RemoveItem(index); } } @@ -740,7 +744,7 @@ public void RemoveAt(int index) #region ICollection Generic Overrides /// - /// Gets the number of elements contained in the buffer + /// Gets the number of elements contained in the buffer. /// public int Count { @@ -768,7 +772,7 @@ public bool IsReadOnly } /// - /// Adds an item to the thread-safe buffer + /// Adds an item to the thread-safe buffer. /// /// /// item to add @@ -784,16 +788,13 @@ public void Add(T item) } /// - /// Removes all items from the buffer + /// Removes all items from the buffer. /// public void Clear() { lock (SyncObject) { - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } @@ -812,7 +813,7 @@ public bool Contains(T item) { if (_serializeInput) { - item = (T)(Object)GetSerializedObject(item); + item = (T)(object)GetSerializedObject(item); } return _data.Contains(item); @@ -1052,6 +1053,7 @@ object IList.this[int index] { return this[index]; } + set { PSDataCollection.VerifyValueType(value); @@ -1075,7 +1077,7 @@ bool ICollection.IsSynchronized } /// - /// Gets the object used to synchronize access to the thread-safe buffer + /// Gets the object used to synchronize access to the thread-safe buffer. /// object ICollection.SyncRoot { @@ -1162,10 +1164,10 @@ public Collection ReadAll() /// /// A new collection with a copy of all the elements in the current collection. /// - /// maximum number of elements to read + /// Maximum number of elements to read. internal Collection ReadAndRemove(int readCount) { - Dbg.Assert(null != _data, "Collection cannot be null"); + Dbg.Assert(_data != null, "Collection cannot be null"); Dbg.Assert(readCount >= 0, "ReadCount cannot be negative"); @@ -1190,7 +1192,7 @@ internal Collection ReadAndRemove(int readCount) } } - if (null != _readWaitHandle) + if (_readWaitHandle != null) { if (_data.Count > 0 || !_isOpen) { @@ -1253,14 +1255,14 @@ protected virtual void InsertItem(Guid psInstanceId, int index, T item) if (_serializeInput) { - item = (T)(Object)GetSerializedObject(item); + item = (T)(object)GetSerializedObject(item); } _data.Insert(index, item); } /// - /// Removes the item at a specified index + /// Removes the item at a specified index. /// /// /// The zero-based index of the buffer where the object is to be removed. @@ -1279,15 +1281,15 @@ protected virtual void RemoveItem(int index) #region Serializable /// - /// Implements the ISerializable contract for serializing a PSDataCollection + /// Implements the ISerializable contract for serializing a PSDataCollection. /// - /// Serialization information for this instance - /// The streaming context for this instance + /// Serialization information for this instance. + /// The streaming context for this instance. public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) { - throw PSTraceSource.NewArgumentNullException("info"); + throw PSTraceSource.NewArgumentNullException(nameof(info)); } info.AddValue("Data", _data); @@ -1303,24 +1305,22 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte /// /// Waitable handle for caller's to block until new data - /// is added to the underlying buffer + /// is added to the underlying buffer. /// internal WaitHandle WaitHandle { get { - if (null == _readWaitHandle) + if (_readWaitHandle == null) { lock (SyncObject) { - if (null == _readWaitHandle) - { - // 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); } } + return _readWaitHandle; } } @@ -1342,7 +1342,7 @@ private void RaiseEvents(Guid psInstanceId, int index) bool raiseDataAdded = false; lock (SyncObject) { - if (null != _readWaitHandle) + if (_readWaitHandle != null) { // TODO: Should ObjectDisposedException be caught. @@ -1388,26 +1388,18 @@ private void RaiseEvents(Guid psInstanceId, int index) private Guid _lastPsInstanceId; private int _lastIndex; - private void RaiseDataAddingEvent(Guid psInstanceId, Object itemAdded) + 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 (null != tempDataAdding) - { - 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 (null != tempDataAdded) - { - tempDataAdded(this, new DataAddedEventArgs(psInstanceId, index)); - } + DataAdded?.Invoke(this, new DataAddedEventArgs(psInstanceId, index)); } /// @@ -1446,7 +1438,7 @@ private void InternalInsertItem(Guid psInstanceId, int index, T item) } /// - /// Adds an item to the thread-safe buffer + /// Adds an item to the thread-safe buffer. /// /// /// InstanceId of PowerShell instance adding this data. @@ -1499,9 +1491,9 @@ internal void InternalAdd(Guid psInstanceId, T item) /// internal void InternalAddRange(Guid psInstanceId, ICollection collection) { - if (null == collection) + if (collection == null) { - throw PSTraceSource.NewArgumentNullException("collection"); + throw PSTraceSource.NewArgumentNullException(nameof(collection)); } int index = -1; @@ -1520,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; } @@ -1555,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 (null != _readWaitHandle) + 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. @@ -1586,7 +1578,7 @@ private int InternalIndexOf(T item) { if (_serializeInput) { - item = (T)(Object)GetSerializedObject(item); + item = (T)(object)GetSerializedObject(item); } int count = _data.Count; @@ -1597,6 +1589,7 @@ private int InternalIndexOf(T item) return index; } } + return -1; } @@ -1613,23 +1606,23 @@ private int InternalIndexOf(T item) /// private static void VerifyValueType(object value) { - if (null == value) + if (value == null) { - if (typeof(T).GetTypeInfo().IsValueType) + if (typeof(T).IsValueType) { - throw PSTraceSource.NewArgumentNullException("value", PSDataBufferStrings.ValueNullReference); + throw PSTraceSource.NewArgumentNullException(nameof(value), PSDataBufferStrings.ValueNullReference); } } - else if (!(value is T)) + else if (value is not T) { - throw PSTraceSource.NewArgumentException("value", PSDataBufferStrings.CannotConvertToGenericType, + throw PSTraceSource.NewArgumentException(nameof(value), PSDataBufferStrings.CannotConvertToGenericType, value.GetType().FullName, typeof(T).FullName); } } // 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. @@ -1642,7 +1635,7 @@ private PSObject GetSerializedObject(Object value) } else { - Object deserialized = PSSerializer.Deserialize(PSSerializer.Serialize(value)); + object deserialized = PSSerializer.Deserialize(PSSerializer.Serialize(value)); if (deserialized == null) { return null; @@ -1654,14 +1647,14 @@ private PSObject GetSerializedObject(Object value) } } - private bool SerializationWouldHaveNoEffect(PSObject result) + private static bool SerializationWouldHaveNoEffect(PSObject result) { if (result == null) { return true; } - Object baseObject = PSObject.Base(result); + object baseObject = PSObject.Base(result); if (baseObject == null) { return true; @@ -1689,19 +1682,20 @@ private bool SerializationWouldHaveNoEffect(PSObject result) } /// - /// Sync object for this collection + /// Sync object for this collection. /// - internal Object SyncObject { get; } = new object(); + internal object SyncObject { get; } = new object(); /// - /// Reference count variable + /// Reference count variable. /// - internal Int32 RefCount + internal int RefCount { get { return _refCount; } + set { lock (SyncObject) @@ -1716,7 +1710,7 @@ internal Int32 RefCount #region Idle event /// - /// Indicates whether or not the collection should pulse idle events + /// Indicates whether or not the collection should pulse idle events. /// internal bool PulseIdleEvent { @@ -1726,7 +1720,7 @@ internal bool PulseIdleEvent internal event EventHandler IdleEvent; /// - /// Fires an idle event + /// Fires an idle event. /// internal void FireIdleEvent() { @@ -1734,7 +1728,7 @@ internal void FireIdleEvent() } /// - /// Pulses the collection + /// Pulses the collection. /// internal void Pulse() { @@ -1749,7 +1743,7 @@ internal void Pulse() #region IDisposable Overrides /// - /// Public dispose method + /// Public dispose method. /// public void Dispose() { @@ -1759,9 +1753,9 @@ public void Dispose() } /// - /// Release all the resources + /// Release all the resources. /// - /// if true, release all managed resources + /// If true, release all managed resources. protected void Dispose(bool disposing) { if (disposing) @@ -1777,6 +1771,7 @@ protected void Dispose(bool disposing) { return; } + _isDisposed = true; } @@ -1790,10 +1785,7 @@ protected void Dispose(bool disposing) _readWaitHandle = null; } - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } } @@ -1807,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); } @@ -1820,22 +1812,22 @@ 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 #region Constructor /// - /// Constructor + /// Constructor. /// /// /// PSDataCollection to enumerate. @@ -1843,16 +1835,16 @@ 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(null != collection, + Dbg.Assert(collection != null, "Collection cannot be null"); Dbg.Assert(!collection.ReleaseOnEnumeration || !collection.IsEnumerated, "shouldn't enumerate more than once if ReleaseOnEnumeration is true"); _collToEnumerate = collection; _index = 0; - _currentElement = default(W); + _currentElement = default(T); _collToEnumerate.IsEnumerated = true; _neverBlock = neverBlock; } @@ -1870,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 { @@ -1909,7 +1901,7 @@ public object Current /// public bool MoveNext() { - return MoveNext(_neverBlock == false); + return MoveNext(!_neverBlock); } /// @@ -1919,27 +1911,28 @@ public bool MoveNext() /// true if the enumerator successfully advanced to the next element; /// otherwise, false. /// - /// true - to block when no elements are available + /// True - to block when no elements are available. 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++; return true; } // 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; } @@ -1962,7 +1955,7 @@ public bool MoveNext(bool block) { return false; } - } while (true); + } } } @@ -1972,12 +1965,11 @@ public bool MoveNext(bool block) /// public void Reset() { - _currentElement = default(W); + _currentElement = default(T); _index = 0; } /// - /// /// void IDisposable.Dispose() { @@ -1994,7 +1986,7 @@ void IDisposable.Dispose() /// internal sealed class PSInformationalBuffers { - private Guid _psInstanceId; + private readonly Guid _psInstanceId; /// /// Default constructor. @@ -2026,12 +2018,17 @@ internal PSInformationalBuffers(Guid psInstanceId) /// internal PSDataCollection Progress { - get { return progress; } + get + { + return progress; + } + set { progress = value; } } + internal PSDataCollection progress; /// @@ -2040,12 +2037,17 @@ internal PSDataCollection Progress /// internal PSDataCollection Verbose { - get { return verbose; } + get + { + return verbose; + } + set { verbose = value; } } + internal PSDataCollection verbose; /// @@ -2054,12 +2056,17 @@ internal PSDataCollection Verbose /// internal PSDataCollection Debug { - get { return debug; } + get + { + return debug; + } + set { debug = value; } } + internal PSDataCollection debug; /// @@ -2079,66 +2086,36 @@ internal PSDataCollection Debug /// The item is added to the buffer along with PowerShell InstanceId. /// /// - internal void AddProgress(ProgressRecord item) - { - if (null != progress) - { - 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 (null != verbose) - { - 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 (null != debug) - { - 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 (null != Warning) - { - 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 (null != Information) - { - Information.InternalAdd(_psInstanceId, item); - } - } + internal void AddInformation(InformationRecord item) => Information?.InternalAdd(_psInstanceId, item); #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs new file mode 100644 index 00000000000..56bdabbff14 --- /dev/null +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -0,0 +1,1593 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Management.Automation.Host; +using System.Management.Automation.Language; +using System.Management.Automation.Remoting.Internal; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; +using System.Threading; + +using Dbg = System.Management.Automation.Diagnostics; + +namespace System.Management.Automation.PSTasks +{ + #region PSTask + + /// + /// Class to encapsulate synchronous running scripts in parallel. + /// + internal sealed class PSTask : PSTaskBase + { + #region Members + + private readonly PSTaskDataStreamWriter _dataStreamWriter; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// Script block to run in task. + /// Using values passed into script block. + /// Dollar underbar variable value. + /// Current working directory. + /// Cmdlet data stream writer. + public PSTask( + ScriptBlock scriptBlock, + Dictionary usingValuesMap, + object dollarUnderbar, + string currentLocationPath, + PSTaskDataStreamWriter dataStreamWriter) + : base( + scriptBlock, + usingValuesMap, + dollarUnderbar, + currentLocationPath) + { + _dataStreamWriter = dataStreamWriter; + } + + #endregion + + #region Overrides + + /// + /// Initialize PowerShell object. + /// + protected override void InitializePowershell() + { + // Writer data stream handlers + _output.DataAdded += (sender, args) => HandleOutputData(); + _powershell.Streams.Error.DataAdded += (sender, args) => HandleErrorData(); + _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 + _powershell.InvocationStateChanged += (sender, args) => HandleStateChanged(args); + } + + #endregion + + #region Writer data stream handlers + + private void HandleOutputData() + { + foreach (var item in _output.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Output, item)); + } + } + + private void HandleErrorData() + { + foreach (var item in _powershell.Streams.Error.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Error, item)); + } + } + + private void HandleWarningData() + { + foreach (var item in _powershell.Streams.Warning.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Warning, item.Message)); + } + } + + private void HandleVerboseData() + { + foreach (var item in _powershell.Streams.Verbose.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Verbose, item.Message)); + } + } + + private void HandleDebugData() + { + foreach (var item in _powershell.Streams.Debug.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Debug, item.Message)); + } + } + + private void HandleInformationData() + { + foreach (var item in _powershell.Streams.Information.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Information, item)); + } + } + + private void HandleProgressData() + { + foreach (var item in _powershell.Streams.Progress.ReadAll()) + { + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Progress, item)); + } + } + + #endregion + + #region Event handlers + + private void HandleStateChanged(PSInvocationStateChangedEventArgs stateChangeInfo) + { + if (_dataStreamWriter != null) + { + // Treat any terminating exception as a non-terminating error record + var newStateInfo = stateChangeInfo.InvocationStateInfo; + if (newStateInfo.Reason != null) + { + var errorRecord = new ErrorRecord( + newStateInfo.Reason, + "PSTaskException", + ErrorCategory.InvalidOperation, + this); + + _dataStreamWriter.Add( + new PSStreamObject(PSStreamObjectType.Error, errorRecord)); + } + } + + RaiseStateChangedEvent(stateChangeInfo); + } + + #endregion + } + + /// + /// Class to encapsulate asynchronous running scripts in parallel as jobs. + /// + internal sealed class PSJobTask : PSTaskBase + { + #region Members + + private readonly Job _job; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// Script block to run. + /// Using variable values passed to script block. + /// Dollar underbar variable value for script block. + /// Current working directory. + /// Job object associated with task. + public PSJobTask( + ScriptBlock scriptBlock, + Dictionary usingValuesMap, + object dollarUnderbar, + string currentLocationPath, + Job job) : base( + scriptBlock, + usingValuesMap, + dollarUnderbar, + currentLocationPath) + { + _job = job; + } + + #endregion + + #region Overrides + + /// + /// Initialize PowerShell object. + /// + protected override void InitializePowershell() + { + // Job data stream handlers + _output.DataAdded += (sender, args) => HandleJobOutputData(); + _powershell.Streams.Error.DataAdded += (sender, args) => HandleJobErrorData(); + _powershell.Streams.Warning.DataAdded += (sender, args) => HandleJobWarningData(); + _powershell.Streams.Verbose.DataAdded += (sender, args) => HandleJobVerboseData(); + _powershell.Streams.Debug.DataAdded += (sender, args) => HandleJobDebugData(); + _powershell.Streams.Information.DataAdded += (sender, args) => HandleJobInformationData(); + + // State change handler + _powershell.InvocationStateChanged += (sender, args) => HandleStateChanged(args); + } + + #endregion + + #region Job data stream handlers + + private void HandleJobOutputData() + { + foreach (var item in _output.ReadAll()) + { + _job.Output.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Output, item)); + } + } + + private void HandleJobErrorData() + { + foreach (var item in _powershell.Streams.Error.ReadAll()) + { + _job.Error.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Error, item)); + } + } + + private void HandleJobWarningData() + { + foreach (var item in _powershell.Streams.Warning.ReadAll()) + { + _job.Warning.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Warning, item.Message)); + } + } + + private void HandleJobVerboseData() + { + foreach (var item in _powershell.Streams.Verbose.ReadAll()) + { + _job.Verbose.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Verbose, item.Message)); + } + } + + private void HandleJobDebugData() + { + foreach (var item in _powershell.Streams.Debug.ReadAll()) + { + _job.Debug.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Debug, item.Message)); + } + } + + private void HandleJobInformationData() + { + foreach (var item in _powershell.Streams.Information.ReadAll()) + { + _job.Information.Add(item); + _job.Results.Add( + new PSStreamObject(PSStreamObjectType.Information, item)); + } + } + + #endregion + + #region Event handlers + + private void HandleStateChanged(PSInvocationStateChangedEventArgs stateChangeInfo) + { + RaiseStateChangedEvent(stateChangeInfo); + } + + #endregion + + #region Properties + + /// + /// Gets Debugger. + /// + public Debugger Debugger + { + get => _powershell.Runspace.Debugger; + } + + #endregion + } + + /// + /// Base class to encapsulate running a PowerShell script concurrently in a cmdlet or job context. + /// + internal abstract class PSTaskBase : IDisposable + { + #region Members + + private readonly ScriptBlock _scriptBlockToRun; + private readonly Dictionary _usingValuesMap; + private readonly object _dollarUnderbar; + private readonly int _id; + private readonly string _currentLocationPath; + private Runspace _runspace; + protected PowerShell _powershell; + protected PSDataCollection _output; + + public const string RunspaceName = "PSTask"; + + private static int s_taskId; + + #endregion + + #region Events + + /// + /// Event that fires when the task running state changes. + /// + public event EventHandler StateChanged; + + internal void RaiseStateChangedEvent(PSInvocationStateChangedEventArgs args) + { + StateChanged.SafeInvoke(this, args); + } + + #endregion + + #region Properties + + /// + /// Gets current running state of the task. + /// + public PSInvocationState State + { + get + { + PowerShell ps = _powershell; + if (ps != null) + { + return ps.InvocationStateInfo.State; + } + + return PSInvocationState.NotStarted; + } + } + + /// + /// Gets Task Id. + /// + public int Id { get => _id; } + + /// + /// Gets Task Runspace. + /// + public Runspace Runspace { get => _runspace; } + + #endregion + + #region Constructor + + private PSTaskBase() + { + _id = Interlocked.Increment(ref s_taskId); + } + + /// + /// Initializes a new instance of the class. + /// + /// Script block to run. + /// Using variable values passed to script block. + /// Dollar underbar variable value. + /// Current working directory. + protected PSTaskBase( + ScriptBlock scriptBlock, + Dictionary usingValuesMap, + object dollarUnderbar, + string currentLocationPath) : this() + { + _scriptBlockToRun = scriptBlock; + _usingValuesMap = usingValuesMap; + _dollarUnderbar = dollarUnderbar; + _currentLocationPath = currentLocationPath; + } + + #endregion + + #region Abstract Methods + + /// + /// Initialize PowerShell object. + /// + protected abstract void InitializePowershell(); + + #endregion + + #region IDisposable + + /// + /// Dispose PSTaskBase instance. + /// + public void Dispose() + { + _powershell.Dispose(); + _output.Dispose(); + } + + #endregion + + #region Public Methods + + /// + /// Start task. + /// + /// Runspace used to run task. + public void Start(Runspace runspace) + { + if (_powershell != null) + { + Dbg.Assert(false, "A PSTask can be started only once."); + return; + } + + Dbg.Assert(runspace != null, "Task runspace cannot be null."); + _runspace = runspace; + + // If available, set current working directory on the runspace. + // Temporarily set the newly created runspace as the thread default runspace for any needed module loading. + if (_currentLocationPath != null) + { + var oldDefaultRunspace = Runspace.DefaultRunspace; + try + { + Runspace.DefaultRunspace = runspace; + var context = new CmdletProviderContext(runspace.ExecutionContext) + { + // _currentLocationPath denotes the current path as-is, and should not be attempted expanded. + SuppressWildcardExpansion = true + }; + runspace.ExecutionContext.SessionState.Internal.SetLocation(_currentLocationPath, context); + } + catch (DriveNotFoundException) + { + // Allow task to run if current drive is not available. + } + finally + { + Runspace.DefaultRunspace = oldDefaultRunspace; + } + } + + // Create the PowerShell command pipeline for the provided script block + // The script will run on the provided Runspace in a new thread by default + _powershell = PowerShell.Create(runspace); + + // Initialize PowerShell object data streams and event handlers + _output = new PSDataCollection(); + InitializePowershell(); + + // Start the script running in a new thread + _powershell.AddScript(_scriptBlockToRun.ToString()); + _powershell.Commands.Commands[0].DollarUnderbar = _dollarUnderbar; + if (_usingValuesMap != null && _usingValuesMap.Count > 0) + { + _powershell.AddParameter(Parser.VERBATIM_ARGUMENT, _usingValuesMap); + } + + _powershell.BeginInvoke(input: null, output: _output); + } + + /// + /// Signals the running task to stop. + /// + public void SignalStop() => _powershell?.BeginStop(null, null); + + #endregion + } + + #endregion + + #region PSTaskDataStreamWriter + + /// + /// Class that handles writing task data stream objects to a cmdlet. + /// + internal sealed class PSTaskDataStreamWriter : IDisposable + { + #region Members + + private readonly PSCmdlet _cmdlet; + private readonly PSDataCollection _dataStream; + private readonly int _cmdletThreadId; + + #endregion + + #region Properties + + /// + /// Gets wait-able handle that signals when new data has been added to + /// the data stream collection. + /// + /// Data added wait handle. + internal WaitHandle DataAddedWaitHandle + { + get => _dataStream.WaitHandle; + } + + #endregion + + #region Constructor + + private PSTaskDataStreamWriter() { } + + /// + /// Initializes a new instance of the class. + /// + /// Parent cmdlet. + public PSTaskDataStreamWriter(PSCmdlet psCmdlet) + { + _cmdlet = psCmdlet; + _cmdletThreadId = Environment.CurrentManagedThreadId; + _dataStream = new PSDataCollection(); + } + + #endregion + + #region Public Methods + + /// + /// Add data stream object to the writer. + /// + /// Data stream object to write. + public void Add(PSStreamObject streamObject) + { + _dataStream.Add(streamObject); + } + + /// + /// Write all objects in data stream collection to the cmdlet data stream. + /// + public void WriteImmediate() + { + CheckCmdletThread(); + + foreach (var item in _dataStream.ReadAll()) + { + item.WriteStreamObject(cmdlet: _cmdlet, overrideInquire: true); + } + } + + /// + /// Waits for data stream objects to be added to the collection, and writes them + /// to the cmdlet data stream. + /// This method returns only after the writer has been closed. + /// + public void WaitAndWrite() + { + CheckCmdletThread(); + + while (true) + { + _dataStream.WaitHandle.WaitOne(); + WriteImmediate(); + + if (!_dataStream.IsOpen) + { + WriteImmediate(); + break; + } + } + } + + /// + /// Closes the stream writer. + /// + public void Close() + { + _dataStream.Complete(); + } + + #endregion + + #region Private Methods + + private void CheckCmdletThread() + { + if (Environment.CurrentManagedThreadId != _cmdletThreadId) + { + throw new PSInvalidOperationException(InternalCommandStrings.PSTaskStreamWriterWrongThread); + } + } + + #endregion + + #region IDisposable + + /// + /// Dispose the stream writer. + /// + public void Dispose() + { + _dataStream.Dispose(); + } + + #endregion + } + + #endregion + + #region PSTaskPool + + /// + /// Pool for running PSTasks, with limit of total number of running tasks at a time. + /// + internal sealed class PSTaskPool : IDisposable + { + #region Members + + private readonly ManualResetEvent _addAvailable; + private readonly int _sizeLimit; + private readonly ManualResetEvent _stopAll; + private readonly object _syncObject; + private readonly Dictionary _taskPool; + private readonly ConcurrentQueue _runspacePool; + private readonly ConcurrentDictionary _activeRunspaces; + private readonly WaitHandle[] _waitHandles; + private readonly bool _useRunspacePool; + private bool _isOpen; + private bool _stopping; + private int _createdRunspaceCount; + + private const int AddAvailable = 0; + private const int Stop = 1; + + #endregion + + #region Constructor + + private PSTaskPool() { } + + /// + /// Initializes a new instance of the class. + /// + /// Total number of allowed running objects in pool at one time. + /// When true, a new runspace object is created for the task instead of reusing one from the pool. + public PSTaskPool( + int size, + bool useNewRunspace) + { + _sizeLimit = size; + _useRunspacePool = !useNewRunspace; + _isOpen = true; + _syncObject = new object(); + _addAvailable = new ManualResetEvent(true); + _stopAll = new ManualResetEvent(false); + _waitHandles = new WaitHandle[] + { + _addAvailable, // index 0 + _stopAll, // index 1 + }; + _taskPool = new Dictionary(size); + _activeRunspaces = new ConcurrentDictionary(); + if (_useRunspacePool) + { + _runspacePool = new ConcurrentQueue(); + } + } + + #endregion + + #region Events + + /// + /// Event that fires when pool is closed and drained of all tasks. + /// + public event EventHandler PoolComplete; + + #endregion + + #region Properties + + /// + /// Gets a value indicating whether a pool is currently open for accepting tasks. + /// + public bool IsOpen + { + get => _isOpen; + } + + /// + /// Gets a value of the count of total runspaces allocated. + /// + public int AllocatedRunspaceCount + { + get => _createdRunspaceCount; + } + + #endregion + + #region IDisposable + + /// + /// Dispose task pool. + /// + public void Dispose() + { + _addAvailable.Dispose(); + _stopAll.Dispose(); + + DisposeRunspaces(); + } + + /// + /// Dispose runspaces. + /// + internal void DisposeRunspaces() + { + foreach (var item in _activeRunspaces) + { + item.Value.Dispose(); + } + + _activeRunspaces.Clear(); + } + + #endregion + + #region Public Methods + + /// + /// Method to add a task to the pool. + /// If the pool is full, then this method blocks until space is available. + /// This method is not multi-thread safe and assumes only one thread waits and adds tasks. + /// + /// Task to be added to pool. + /// True when task is successfully added. + public bool Add(PSTaskBase task) + { + if (!_isOpen) + { + return false; + } + + // Block until either space is available, or a stop is commanded + var index = WaitHandle.WaitAny(_waitHandles); + + switch (index) + { + case AddAvailable: + var runspace = GetRunspace(task.Id); + task.StateChanged += HandleTaskStateChangedDelegate; + lock (_syncObject) + { + if (!_isOpen) + { + return false; + } + + _taskPool.Add(task.Id, task); + if (_taskPool.Count == _sizeLimit) + { + _addAvailable.Reset(); + } + + task.Start(runspace); + } + + return true; + + case Stop: + return false; + + default: + return false; + } + } + + /// + /// Add child job task to task pool. + /// + /// Child job to be added to pool. + /// True when child job is successfully added. + public bool Add(PSTaskChildJob childJob) + { + return Add(childJob.Task); + } + + /// + /// Signals all running tasks to stop and closes pool for any new tasks. + /// + public void StopAll() + { + _stopping = true; + + // Accept no more input + Close(); + _stopAll.Set(); + + // Stop all running tasks + PSTaskBase[] tasksToStop; + lock (_syncObject) + { + tasksToStop = new PSTaskBase[_taskPool.Values.Count]; + _taskPool.Values.CopyTo(tasksToStop, 0); + } + + foreach (var task in tasksToStop) + { + task.Dispose(); + } + + // Dispose all active runspaces + DisposeRunspaces(); + _stopping = false; + } + + /// + /// Closes the pool and prevents any new tasks from being added. + /// + public void Close() + { + _isOpen = false; + CheckForComplete(); + } + + #endregion + + #region Private Methods + + private void HandleTaskStateChangedDelegate(object sender, PSInvocationStateChangedEventArgs args) => HandleTaskStateChanged(sender, args); + + private void HandleTaskStateChanged(object sender, PSInvocationStateChangedEventArgs args) + { + var task = sender as PSTaskBase; + Dbg.Assert(task != null, "State changed sender must always be PSTaskBase"); + var stateInfo = args.InvocationStateInfo; + switch (stateInfo.State) + { + // Look for completed state and remove + case PSInvocationState.Completed: + case PSInvocationState.Stopped: + case PSInvocationState.Failed: + ReturnRunspace(task); + lock (_syncObject) + { + _taskPool.Remove(task.Id); + if (_taskPool.Count == (_sizeLimit - 1)) + { + _addAvailable.Set(); + } + } + + task.StateChanged -= HandleTaskStateChangedDelegate; + if (!_stopping || stateInfo.State != PSInvocationState.Stopped) + { + // StopAll disposes tasks. + task.Dispose(); + } + + CheckForComplete(); + break; + } + } + + private void CheckForComplete() + { + bool isTaskPoolComplete; + lock (_syncObject) + { + isTaskPoolComplete = !_isOpen && _taskPool.Count == 0; + } + + if (isTaskPoolComplete) + { + try + { + PoolComplete.SafeInvoke( + this, + new EventArgs()); + } + catch + { + Dbg.Assert(false, "Exceptions should not be thrown on event thread"); + } + } + } + + private Runspace GetRunspace(int taskId) + { + var runspaceName = string.Create(CultureInfo.InvariantCulture, $"{PSTask.RunspaceName}:{taskId}"); + + if (_useRunspacePool && _runspacePool.TryDequeue(out Runspace runspace)) + { + if (runspace.RunspaceStateInfo.State == RunspaceState.Opened && + runspace.RunspaceAvailability == RunspaceAvailability.Available) + { + try + { + runspace.ResetRunspaceState(); + runspace.Name = runspaceName; + return runspace; + } + catch + { + // If the runspace cannot be reset for any reason, remove it. + } + } + + RemoveActiveRunspace(runspace); + } + + // Create and initialize a new Runspace + var iss = InitialSessionState.CreateDefault2(); + 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); + runspace.Open(); + _createdRunspaceCount++; + + return runspace; + } + + private void ReturnRunspace(PSTaskBase task) + { + var runspace = task.Runspace; + Dbg.Assert(runspace != null, "Task runspace cannot be null."); + if (_useRunspacePool && + runspace.RunspaceStateInfo.State == RunspaceState.Opened && + runspace.RunspaceAvailability == RunspaceAvailability.Available) + { + _runspacePool.Enqueue(runspace); + return; + } + + RemoveActiveRunspace(runspace); + } + + private void RemoveActiveRunspace(Runspace runspace) + { + runspace.Dispose(); + _activeRunspaces.TryRemove(runspace.Id, out Runspace _); + } + + #endregion + } + + #endregion + + #region PSTaskJobs + + /// + /// Job for running ForEach-Object parallel task child jobs asynchronously. + /// + public sealed class PSTaskJob : Job + { + #region Members + + private readonly PSTaskPool _taskPool; + private bool _isOpen; + private bool _stopSignaled; + + #endregion + + #region Properties + + /// + /// Gets a value of the count of total runspaces allocated. + /// + public int AllocatedRunspaceCount + { + get => _taskPool.AllocatedRunspaceCount; + } + + #endregion + + #region Constructor + + private PSTaskJob() { } + + /// + /// Initializes a new instance of the class. + /// + /// Job command text. + /// Pool size limit for task job. + /// When true, a new runspace object is created for the task instead of reusing one from the pool. + internal PSTaskJob( + string command, + int throttleLimit, + bool useNewRunspace) : base(command, string.Empty) + { + _taskPool = new PSTaskPool(throttleLimit, useNewRunspace); + _isOpen = true; + PSJobTypeName = nameof(PSTaskJob); + + _taskPool.PoolComplete += (sender, args) => HandleTaskPoolComplete(sender, args); + } + + #endregion + + #region Overrides + + /// + /// Gets Location. + /// + public override string Location + { + get => "PowerShell"; + } + + /// + /// Gets HasMoreData. + /// + public override bool HasMoreData + { + get + { + foreach (var childJob in ChildJobs) + { + if (childJob.HasMoreData) + { + return true; + } + } + + return false; + } + } + + /// + /// Gets StatusMessage. + /// + public override string StatusMessage + { + get => string.Empty; + } + + /// + /// Stops running job. + /// + public override void StopJob() + { + _stopSignaled = true; + SetJobState(JobState.Stopping); + + _taskPool.StopAll(); + SetJobState(JobState.Stopped); + } + + /// + /// Disposes task job. + /// + /// Indicates disposing action. + protected override void Dispose(bool disposing) + { + if (disposing) + { + _taskPool.Dispose(); + } + + base.Dispose(disposing); + } + + #endregion + + #region Internal Methods + + /// + /// Add a child job to the collection. + /// + /// Child job to add. + /// True when child job is successfully added. + internal bool AddJob(PSTaskChildJob childJob) + { + if (!_isOpen) + { + return false; + } + + ChildJobs.Add(childJob); + return true; + } + + /// + /// Closes this parent job to adding more child jobs and starts + /// the child jobs running with the provided throttle limit. + /// + internal void Start() + { + _isOpen = false; + SetJobState(JobState.Running); + + // Submit jobs to the task pool, blocking when throttle limit is reached. + // This thread will end once all jobs reach a finished state by either running + // to completion, terminating with error, or stopped. + System.Threading.ThreadPool.QueueUserWorkItem( + (_) => + { + foreach (var childJob in ChildJobs) + { + _taskPool.Add((PSTaskChildJob)childJob); + } + + _taskPool.Close(); + }); + } + + #endregion + + #region Private Methods + + private void HandleTaskPoolComplete(object sender, EventArgs args) + { + try + { + if (_stopSignaled) + { + SetJobState(JobState.Stopped, new PipelineStoppedException()); + return; + } + + // Final state will be 'Complete', only if all child jobs completed successfully. + JobState finalState = JobState.Completed; + foreach (var childJob in ChildJobs) + { + if (childJob.JobStateInfo.State != JobState.Completed) + { + finalState = JobState.Failed; + break; + } + } + + SetJobState(finalState); + + // Release job task pool runspace resources. + (sender as PSTaskPool).DisposeRunspaces(); + } + catch (ObjectDisposedException) + { } + } + + #endregion + } + + /// + /// PSTaskChildJob debugger wrapper. + /// + internal sealed class PSTaskChildDebugger : Debugger + { + #region Members + + private readonly Debugger _wrappedDebugger; + private readonly string _jobName; + + #endregion + + #region Constructor + + private PSTaskChildDebugger() { } + + /// + /// Initializes a new instance of the class. + /// + /// Script debugger associated with task. + /// Job name for associated task. + public PSTaskChildDebugger( + Debugger debugger, + string jobName) + { + if (debugger == null) + { + throw new PSArgumentNullException(nameof(debugger)); + } + + _wrappedDebugger = debugger; + _jobName = jobName ?? string.Empty; + + // Create handlers for wrapped debugger events. + _wrappedDebugger.BreakpointUpdated += HandleBreakpointUpdated; + _wrappedDebugger.DebuggerStop += HandleDebuggerStop; + } + + #endregion + + #region Debugger overrides + + /// + /// Evaluates provided command either as a debugger specific command + /// or a PowerShell command. + /// + /// PowerShell command. + /// PowerShell output. + /// Debugger command results. + public override DebuggerCommandResults ProcessCommand( + PSCommand command, + PSDataCollection output) + { + // Special handling for the prompt command. + if (command.Commands[0].CommandText.Trim().Equals("prompt", StringComparison.OrdinalIgnoreCase)) + { + return HandlePromptCommand(output); + } + + return _wrappedDebugger.ProcessCommand(command, output); + } + + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// List of breakpoints. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) => + _wrappedDebugger.SetBreakpoints(breakpoints, runspaceId); + + /// + /// Sets the debugger resume action. + /// + /// Debugger resume action. + public override void SetDebuggerAction(DebuggerResumeAction resumeAction) + { + _wrappedDebugger.SetDebuggerAction(resumeAction); + } + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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. + /// The breakpoint with the specified id. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) => + _wrappedDebugger.GetBreakpoint(id, runspaceId); + + /// + /// Returns breakpoints on a runspace. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A list of breakpoints in a runspace. + public override List GetBreakpoints(int? runspaceId) => + _wrappedDebugger.GetBreakpoints(runspaceId); + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The command breakpoint that was set. + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path, runspaceId); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action, runspaceId); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.EnableBreakpoint(breakpoint, runspaceId); + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.DisableBreakpoint(breakpoint, runspaceId); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.RemoveBreakpoint(breakpoint, runspaceId); + + /// + /// Stops a running command. + /// + public override void StopProcessCommand() + { + _wrappedDebugger.StopProcessCommand(); + } + + /// + /// Returns current debugger stop event arguments if debugger is in + /// debug stop state. Otherwise returns null. + /// + /// Debugger stop eventArgs. + public override DebuggerStopEventArgs GetDebuggerStopArgs() + { + return _wrappedDebugger.GetDebuggerStopArgs(); + } + + /// + /// Sets the parent debugger, breakpoints, and other debugging context information. + /// + /// Parent debugger. + /// List of breakpoints. + /// Debugger mode. + /// PowerShell host. + /// Current path. + public override void SetParent( + Debugger parent, + IEnumerable breakPoints, + DebuggerResumeAction? startAction, + PSHost host, + PathInfo path) + { + // For now always enable step mode debugging. + SetDebuggerStepMode(true); + } + + /// + /// Sets the debugger mode. + /// + /// Debugger mode to set. + public override void SetDebugMode(DebugModes mode) + { + _wrappedDebugger.SetDebugMode(mode); + + base.SetDebugMode(mode); + } + + /// + /// Returns IEnumerable of CallStackFrame objects. + /// + /// Enumerable call stack. + public override IEnumerable GetCallStack() + { + return _wrappedDebugger.GetCallStack(); + } + + /// + /// Sets debugger stepping mode. + /// + /// True to enable debugger step mode. + public override void SetDebuggerStepMode(bool enabled) + { + _wrappedDebugger.SetDebuggerStepMode(enabled); + } + + /// + /// Gets boolean indicating when debugger is stopped at a breakpoint. + /// + public override bool InBreakpoint + { + get => _wrappedDebugger.InBreakpoint; + } + + #endregion + + #region Private methods + + private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) + { + this.RaiseDebuggerStopEvent(e); + } + + private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) + { + this.RaiseBreakpointUpdatedEvent(e); + } + + private DebuggerCommandResults HandlePromptCommand(PSDataCollection output) + { + // Nested debugged runspace prompt should look like: + // [DBG]: [JobName]: PS C:\>> + string promptScript = "'[DBG]: '" + " + " + "'[" + CodeGeneration.EscapeSingleQuotedStringContent(_jobName) + "]: '" + " + " + @"""PS $($executionContext.SessionState.Path.CurrentLocation)>> """; + PSCommand promptCommand = new PSCommand(); + promptCommand.AddScript(promptScript); + _wrappedDebugger.ProcessCommand(promptCommand, output); + + return new DebuggerCommandResults(null, true); + } + + #endregion + } + + /// + /// Task child job that wraps asynchronously running tasks. + /// + internal sealed class PSTaskChildJob : Job, IJobDebugger + { + #region Members + + private readonly PSJobTask _task; + private PSTaskChildDebugger _jobDebuggerWrapper; + + #endregion + + #region Constructor + + private PSTaskChildJob() { } + + /// + /// Initializes a new instance of the class. + /// + /// Script block to run. + /// Using variable values passed to script block. + /// Dollar underbar variable value. + /// Current working directory. + public PSTaskChildJob( + ScriptBlock scriptBlock, + Dictionary usingValuesMap, + object dollarUnderbar, + string currentLocationPath) + : base(scriptBlock.ToString(), string.Empty) + + { + PSJobTypeName = nameof(PSTaskChildJob); + _task = new PSJobTask(scriptBlock, usingValuesMap, dollarUnderbar, currentLocationPath, this); + _task.StateChanged += (sender, args) => HandleTaskStateChange(sender, args); + } + + #endregion + + #region Properties + + /// + /// Gets child job task. + /// + internal PSTaskBase Task + { + get => _task; + } + + #endregion + + #region Overrides + + /// + /// Gets Location. + /// + public override string Location + { + get => "PowerShell"; + } + + /// + /// Gets HasMoreData. + /// + public override bool HasMoreData + { + get => this.Output.Count > 0 || + this.Error.Count > 0 || + this.Progress.Count > 0 || + this.Verbose.Count > 0 || + this.Debug.Count > 0 || + this.Warning.Count > 0 || + this.Information.Count > 0; + } + + /// + /// Gets StatusMessage. + /// + public override string StatusMessage + { + get => string.Empty; + } + + /// + /// Stops running job. + /// + public override void StopJob() + { + _task.SignalStop(); + } + + #endregion + + #region IJobDebugger + + /// + /// Gets Job Debugger. + /// + public Debugger Debugger + { + get + { + _jobDebuggerWrapper ??= new PSTaskChildDebugger( + _task.Debugger, + this.Name); + + return _jobDebuggerWrapper; + } + } + + /// + /// Gets or sets a value indicating whether IAsync. + /// + public bool IsAsync { get; set; } + + #endregion + + #region Private Methods + + private void HandleTaskStateChange(object sender, PSInvocationStateChangedEventArgs args) + { + var stateInfo = args.InvocationStateInfo; + + switch (stateInfo.State) + { + case PSInvocationState.Running: + SetJobState(JobState.Running); + break; + + case PSInvocationState.Stopped: + SetJobState(JobState.Stopped, stateInfo.Reason); + break; + + case PSInvocationState.Failed: + SetJobState(JobState.Failed, stateInfo.Reason); + break; + + case PSInvocationState.Completed: + SetJobState(JobState.Completed, stateInfo.Reason); + break; + } + } + + #endregion + } + + #endregion +} diff --git a/src/System.Management.Automation/engine/hostifaces/Parameter.cs b/src/System.Management.Automation/engine/hostifaces/Parameter.cs index b87e14f8bc3..8511d13c050 100644 --- a/src/System.Management.Automation/engine/hostifaces/Parameter.cs +++ b/src/System.Management.Automation/engine/hostifaces/Parameter.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Language; + using Microsoft.Management.Infrastructure; namespace System.Management.Automation.Runspaces @@ -19,9 +19,9 @@ public sealed class CommandParameter #region Public constructors /// - /// Create a named parameter with a null value + /// Create a named parameter with a null value. /// - /// parameter name + /// Parameter name. /// /// name is null. /// @@ -33,15 +33,15 @@ public CommandParameter(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } } /// - /// Create a named parameter + /// Create a named parameter. /// - /// parameter name - /// parameter value + /// Parameter name. + /// Parameter value. /// /// Name is non null and name length is zero after trimming whitespace. /// @@ -51,7 +51,7 @@ public CommandParameter(string name, object value) { if (string.IsNullOrWhiteSpace(name)) { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } Name = name; @@ -60,29 +60,30 @@ public CommandParameter(string name, object value) { Name = null; } + Value = value; } - #endregion Public constructors #region Public properties /// - /// gets the parameter name + /// Gets the parameter name. /// public string Name { get; } /// - /// gets the value of the parameter + /// Gets the value of the parameter. /// public object Value { get; } #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 @@ -90,7 +91,7 @@ internal static CommandParameter FromCommandParameterInternal(CommandParameterIn { if (internalParameter == null) { - throw PSTraceSource.NewArgumentNullException("internalParameter"); + throw PSTraceSource.NewArgumentNullException(nameof(internalParameter)); } // we want the name to preserve 1) dashes, 2) colons, 3) followed-by-space information @@ -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,23 +109,21 @@ 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); - } - 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); + CommandParameter result = internalParameter.ParameterAndArgumentSpecified + ? new CommandParameter(name, internalParameter.ArgumentValue) + : name != null + ? new CommandParameter(name) + : new CommandParameter(name: null, internalParameter.ArgumentValue); + + result.FromHashtableSplatting = internalParameter.FromHashtableSplatting; + return result; } internal static CommandParameterInternal ToCommandParameterInternal(CommandParameter publicParameter, bool forNativeCommand) { if (publicParameter == null) { - throw PSTraceSource.NewArgumentNullException("publicParameter"); + throw PSTraceSource.NewArgumentNullException(nameof(publicParameter)); } string name = publicParameter.Name; @@ -142,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 @@ -158,6 +160,7 @@ internal static CommandParameterInternal ToCommandParameterInternal(CommandParam spaceAfterParameter = true; endPosition--; } + Debug.Assert(endPosition > 0, "parameter name should have some non-whitespace characters in it"); // now make sure that parameterText doesn't have whitespace at the end, @@ -182,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 @@ -195,7 +202,7 @@ internal static CommandParameterInternal ToCommandParameterInternal(CommandParam /// Creates a CommandParameter object from a PSObject property bag. /// PSObject has to be in the format returned by ToPSObjectForRemoting method. /// - /// PSObject to rehydrate + /// PSObject to rehydrate. /// /// CommandParameter rehydrated from a PSObject property bag /// @@ -209,7 +216,7 @@ internal static CommandParameter FromPSObjectForRemoting(PSObject parameterAsPSO { if (parameterAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("parameterAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(parameterAsPSObject)); } string name = RemotingDecoder.GetPropertyValue(parameterAsPSObject, RemoteDataNameStrings.ParameterName); @@ -221,7 +228,7 @@ internal static CommandParameter FromPSObjectForRemoting(PSObject parameterAsPSO /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// - /// This object as a PSObject property bag + /// This object as a PSObject property bag. internal PSObject ToPSObjectForRemoting() { PSObject parameterAsPSObject = RemotingEncoder.CreateEmptyPSObject(); @@ -231,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 } /// @@ -266,19 +245,19 @@ internal CimInstance ToCimInstance() /// public sealed class CommandParameterCollection : Collection { - //TODO: this class needs a mechanism to lock further changes + // TODO: this class needs a mechanism to lock further changes /// - /// Create a new empty instance of this collection type + /// Create a new empty instance of this collection type. /// public CommandParameterCollection() { } /// - /// Add a parameter with given name and default null value + /// Add a parameter with given name and default null value. /// - /// name of the parameter + /// Name of the parameter. /// /// name is null. /// @@ -291,10 +270,10 @@ public void Add(string name) } /// - /// Add a parameter with given name and value + /// Add a parameter with given name and value. /// - /// name of the parameter - /// value of the parameter + /// Name of the parameter. + /// Value of the parameter. /// /// Both name and value are null. One of these must be non-null. /// @@ -307,5 +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 fd7e2e4cd49..f7bca01fe7b 100644 --- a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.ObjectModel; -using System.Threading; +using System.Management.Automation.Internal; using System.Runtime.Serialization; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Internal; namespace System.Management.Automation.Runspaces { @@ -16,11 +16,10 @@ 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 { /// - /// Initializes a new instance of the InvalidPipelineStateException class + /// Initializes a new instance of the InvalidPipelineStateException class. /// public InvalidPipelineStateException() : base(StringUtil.Format(RunspaceStrings.InvalidPipelineStateStateGeneral)) @@ -29,7 +28,7 @@ public InvalidPipelineStateException() /// /// Initializes a new instance of the InvalidPipelineStateException class - /// with a specified error message + /// with a specified error message. /// /// /// The error message that explains the reason for the exception. @@ -56,12 +55,12 @@ public InvalidPipelineStateException(string message, Exception innerException) /// /// Initializes a new instance of the InvalidPipelineStateException and defines value of - /// CurrentState and ExpectedState + /// CurrentState and ExpectedState. /// /// The error message that explains the reason for the exception. /// - /// Current state of pipeline - /// Expected state of pipeline + /// Current state of pipeline. + /// Expected state of pipeline. internal InvalidPipelineStateException(string message, PipelineState currentState, PipelineState expectedState) : base(message) { @@ -86,15 +85,16 @@ 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 /// - /// Gets CurrentState of the pipeline + /// Gets CurrentState of the pipeline. /// public PipelineState CurrentState { @@ -102,7 +102,7 @@ public PipelineState CurrentState } /// - /// Gets ExpectedState of the pipeline + /// Gets ExpectedState of the pipeline. /// public PipelineState ExpectedState { @@ -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 @@ -127,16 +127,16 @@ public PipelineState ExpectedState #region PipelineState /// - /// Enumerated type defining the state of the Pipeline + /// Enumerated type defining the state of the Pipeline. /// public enum PipelineState { /// - /// The pipeline has not been started + /// The pipeline has not been started. /// NotStarted = 0, /// - /// The pipeline is executing + /// The pipeline is executing. /// Running = 1, /// @@ -163,7 +163,7 @@ public enum PipelineState /// /// Type which has information about PipelineState and Exception - /// associated with PipelineState + /// associated with PipelineState. /// public sealed class PipelineStateInfo { @@ -172,7 +172,7 @@ public sealed class PipelineStateInfo /// /// Constructor for state changes not resulting from an error. /// - /// Execution state + /// Execution state. internal PipelineStateInfo(PipelineState state) : this(state, null) { @@ -192,9 +192,9 @@ internal PipelineStateInfo(PipelineState state, Exception reason) } /// - /// Copy constructor to support cloning + /// Copy constructor to support cloning. /// - /// source information + /// Source information. /// /// ArgumentNullException when is null. /// @@ -206,7 +206,6 @@ internal PipelineStateInfo(PipelineStateInfo pipelineStateInfo) Reason = pipelineStateInfo.Reason; } - #endregion constructors #region public_properties @@ -232,9 +231,9 @@ internal PipelineStateInfo(PipelineStateInfo pipelineStateInfo) #endregion public_properties /// - /// Clones this object + /// Clones this object. /// - /// Cloned object + /// Cloned object. internal PipelineStateInfo Clone() { return new PipelineStateInfo(this); @@ -250,7 +249,7 @@ public sealed class PipelineStateEventArgs : EventArgs #region constructors /// - /// Constructor PipelineStateEventArgs from PipelineStateInfo + /// Constructor PipelineStateEventArgs from PipelineStateInfo. /// /// The current state of the /// pipeline. @@ -284,7 +283,7 @@ public abstract class Pipeline : IDisposable #region constructor /// - /// Explicit default constructor + /// Explicit default constructor. /// internal Pipeline(Runspace runspace) : this(runspace, new CommandCollection()) @@ -306,11 +305,11 @@ internal Pipeline(Runspace runspace, CommandCollection command) { if (runspace == null) { - PSTraceSource.NewArgumentNullException("runspace"); + PSTraceSource.NewArgumentNullException(nameof(runspace)); } // This constructor is used only internally. // Caller should make sure the input is valid - Dbg.Assert(null != command, "Command cannot be null"); + Dbg.Assert(command != null, "Command cannot be null"); InstanceId = runspace.GeneratePipelineId(); Commands = command; @@ -324,7 +323,7 @@ internal Pipeline(Runspace runspace, CommandCollection command) #region properties /// - /// gets the runspace this pipeline is created on. + /// Gets the runspace this pipeline is created on. /// public abstract Runspace Runspace { get; } @@ -343,11 +342,12 @@ internal Pipeline(Runspace runspace, CommandCollection command) internal virtual bool IsChild { get { return false; } + set { } } /// - /// gets input writer for this pipeline. + /// Gets input writer for this pipeline. /// /// /// When the caller calls Input.Write(), the caller writes to the @@ -369,7 +369,7 @@ internal virtual bool IsChild public abstract PipelineReader Output { get; } /// - /// gets the error output reader for this pipeline. + /// Gets the error output reader for this pipeline. /// /// /// When the caller calls Error.Read(), the caller reads from the @@ -400,6 +400,7 @@ public virtual bool HadErrors { get { return _hadErrors; } } + private bool _hadErrors; internal void SetHadErrors(bool status) @@ -408,13 +409,13 @@ internal void SetHadErrors(bool status) } /// - /// gets the unique identifier for this pipeline. This identifier is unique with in + /// Gets the unique identifier for this pipeline. This identifier is unique with in /// the scope of Runspace. /// public long InstanceId { get; } /// - /// gets the collection of commands for this pipeline. + /// Gets the collection of commands for this pipeline. /// public CommandCollection Commands { get; private set; } @@ -444,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; @@ -461,7 +462,6 @@ internal void SetHadErrors(bool status) #region methods - /// /// Invoke the pipeline, synchronously, returning the results as an array of /// objects. @@ -497,9 +497,6 @@ internal void SetHadErrors(bool status) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the pipeline was executing was aborted. - /// /// /// Pipeline.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -536,7 +533,7 @@ public Collection Invoke() /// /// an array of input objects to pass to the pipeline. /// Array may be empty but may not be null - /// An array of zero or more result objects + /// An array of zero or more result objects. /// If using synchronous exectute, do not close /// input objectWriter. Synchronous invoke will always close the input /// objectWriter. @@ -568,9 +565,6 @@ public Collection Invoke() /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the pipeline was executing was aborted. - /// /// /// Pipeline.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -600,7 +594,7 @@ public Collection Invoke() public abstract Collection Invoke(IEnumerable input); /// - /// Invoke the pipeline asynchronously + /// Invoke the pipeline asynchronously. /// /// /// 1) Results are returned through the reader. @@ -667,7 +661,7 @@ public Collection Invoke() /// /// Sets the command collection. /// - /// command collection to set + /// Command collection to set. /// called by ClientRemotePipeline internal void SetCommandCollection(CommandCollection commands) { @@ -675,10 +669,10 @@ internal void SetCommandCollection(CommandCollection commands) } /// - /// Sets the history string to the one that is specified + /// Sets the history string to the one that is specified. /// - /// history string to set - internal abstract void SetHistoryString(String historyString); + /// History string to set. + internal abstract void SetHistoryString(string historyString); /// /// Invokes a remote command and immediately disconnects if @@ -744,5 +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 f6c5bb988ee..3af978ea165 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -1,20 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Runtime.Serialization; -using System.Threading; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; -using System.Diagnostics.CodeAnalysis; // for fxcop. -using Dbg = System.Management.Automation.Diagnostics; -using System.Diagnostics; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; + using Microsoft.Management.Infrastructure; +using Microsoft.PowerShell.Telemetry; + +using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -26,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 { /// @@ -67,7 +71,7 @@ public InvalidPowerShellStateException(string message, Exception innerException) /// Initializes a new instance of the InvalidPowerShellStateException and defines value of /// CurrentState. /// - /// Current state of powershell + /// Current state of powershell. internal InvalidPowerShellStateException(PSInvocationState currentState) : base (StringUtil.Format(PowerShellStrings.InvalidPowerShellStateGeneral)) @@ -92,16 +96,17 @@ 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 /// - /// Gets CurrentState of the powershell + /// Gets CurrentState of the powershell. /// public PSInvocationState CurrentState { @@ -115,7 +120,7 @@ public PSInvocationState CurrentState /// State of powershell when exception was thrown. /// [NonSerialized] - private PSInvocationState _currState = 0; + private readonly PSInvocationState _currState = 0; } #endregion @@ -123,16 +128,16 @@ public PSInvocationState CurrentState #region PSInvocationState, PSInvocationStateInfo, PSInvocationStateChangedEventArgs /// - /// Enumerated type defining the state of the PowerShell + /// Enumerated type defining the state of the PowerShell. /// public enum PSInvocationState { /// - /// PowerShell has not been started + /// PowerShell has not been started. /// NotStarted = 0, /// - /// PowerShell is executing + /// PowerShell is executing. /// Running = 1, /// @@ -158,24 +163,24 @@ public enum PSInvocationState } /// - /// Enumerated type defining runspace modes for nested pipeline + /// Enumerated type defining runspace modes for nested pipeline. /// public enum RunspaceMode { /// - /// Use current runspace from the current thread of execution + /// Use current runspace from the current thread of execution. /// CurrentRunspace = 0, /// - /// Create new runspace + /// Create new runspace. /// NewRunspace = 1 } /// /// Type which has information about InvocationState and Exception - /// associated with InvocationState + /// associated with InvocationState. /// public sealed class PSInvocationStateInfo { @@ -195,7 +200,7 @@ internal PSInvocationStateInfo(PSInvocationState state, Exception reason) } /// - /// Construct from PipelineStateInfo + /// Construct from PipelineStateInfo. /// /// internal PSInvocationStateInfo(PipelineStateInfo pipelineStateInfo) @@ -256,14 +261,14 @@ internal PSInvocationStateInfo Clone() #region Private data /// - /// The current execution state + /// 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 } @@ -277,7 +282,7 @@ public sealed class PSInvocationStateChangedEventArgs : EventArgs #region Constructors /// - /// Constructs PSInvocationStateChangedEventArgs from PSInvocationStateInfo + /// Constructs PSInvocationStateChangedEventArgs from PSInvocationStateInfo. /// /// /// state to raise the event with. @@ -325,9 +330,7 @@ public sealed class PSInvocationSettings /// public PSInvocationSettings() { -#if !CORECLR // No ApartmentState In CoreCLR this.ApartmentState = ApartmentState.Unknown; -#endif _host = null; RemoteStreamOptions = 0; AddToHistory = false; @@ -336,13 +339,11 @@ public PSInvocationSettings() #endregion -#if !CORECLR // No ApartmentState In CoreCLR /// /// ApartmentState of the thread in which the command /// is executed. /// public ApartmentState ApartmentState { get; set; } -#endif /// /// Host to use with the Runspace when the command is @@ -354,18 +355,20 @@ public PSHost Host { return _host; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Host"); } + _host = value; } } /// - /// Options for the Error, Warning, Verbose and Debug streams during remote calls + /// Options for the Error, Warning, Verbose and Debug streams during remote calls. /// public RemoteStreamOptions RemoteStreamOptions { get; set; } @@ -376,7 +379,7 @@ public PSHost Host public bool AddToHistory { get; set; } /// - /// Determines how errors should be handled during batch command execution + /// Determines how errors should be handled during batch command execution. /// public ActionPreference? ErrorActionPreference { get; set; } @@ -412,14 +415,14 @@ public bool ExposeFlowControlExceptions } /// - /// Batch execution context + /// Batch execution context. /// internal class BatchInvocationContext { - private AutoResetEvent _completionEvent; + private readonly AutoResetEvent _completionEvent; /// - /// Class constructor + /// Class constructor. /// /// /// @@ -431,17 +434,17 @@ internal BatchInvocationContext(PSCommand command, PSDataCollection ou } /// - /// Invocation output + /// Invocation output. /// internal PSDataCollection Output { get; } /// - /// Command to invoke + /// Command to invoke. /// internal PSCommand Command { get; } /// - /// Waits for the completion event + /// Waits for the completion event. /// internal void Wait() { @@ -449,7 +452,7 @@ internal void Wait() } /// - /// Signals the completion event + /// Signals the completion event. /// internal void Signal() { @@ -459,33 +462,33 @@ internal void Signal() /// /// These flags control whether InvocationInfo is added to items in the Error, Warning, Verbose and Debug - /// streams during remote calls + /// streams during remote calls. /// [Flags] public enum RemoteStreamOptions { /// - /// If this flag is set, ErrorRecord will include an instance of InvocationInfo on remote calls + /// If this flag is set, ErrorRecord will include an instance of InvocationInfo on remote calls. /// AddInvocationInfoToErrorRecord = 0x01, /// - /// If this flag is set, WarningRecord will include an instance of InvocationInfo on remote calls + /// If this flag is set, WarningRecord will include an instance of InvocationInfo on remote calls. /// AddInvocationInfoToWarningRecord = 0x02, /// - /// If this flag is set, DebugRecord will include an instance of InvocationInfo on remote calls + /// If this flag is set, DebugRecord will include an instance of InvocationInfo on remote calls. /// AddInvocationInfoToDebugRecord = 0x04, /// - /// If this flag is set, VerboseRecord will include an instance of InvocationInfo on remote calls + /// If this flag is set, VerboseRecord will include an instance of InvocationInfo on remote calls. /// AddInvocationInfoToVerboseRecord = 0x08, /// - /// If this flag is set, ErrorRecord, WarningRecord, DebugRecord, and VerboseRecord will include an instance of InvocationInfo on remote calls + /// If this flag is set, ErrorRecord, WarningRecord, DebugRecord, and VerboseRecord will include an instance of InvocationInfo on remote calls. /// AddInvocationInfo = AddInvocationInfoToErrorRecord | AddInvocationInfoToWarningRecord @@ -506,13 +509,13 @@ internal sealed class PowerShellAsyncResult : AsyncResult // a BeginStop operation. /// - /// true if AsyncResult monitors Async BeginInvoke(). - /// false otherwise + /// True if AsyncResult monitors Async BeginInvoke(). + /// false otherwise. /// internal bool IsAssociatedWithAsyncInvoke { get; } /// - /// The output buffer for the asynchronous invoke + /// The output buffer for the asynchronous invoke. /// internal PSDataCollection Output { get; } @@ -521,7 +524,7 @@ internal sealed class PowerShellAsyncResult : AsyncResult #region Constructor /// - /// Constructor + /// Constructor. /// /// /// Instance Id of the Powershell object creating this instance @@ -560,7 +563,7 @@ internal PowerShellAsyncResult(Guid ownerId, AsyncCallback callback, object stat /// /// Provides a simple interface to execute a powershell command: /// - /// Powershell.Create("get-process").Invoke(); + /// Powershell.Create().AddScript("get-process").Invoke(); /// /// The above statement creates a local runspace using default /// configuration, executes the command and then closes the runspace. @@ -586,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 @@ -596,12 +599,16 @@ public sealed class PowerShell : IDisposable private bool _isBatching = false; private bool _stopBatchExecution = false; + // Delegates for asynchronous invocation/termination of PowerShell commands + private readonly Func> _endInvokeMethod; + private readonly Action _endStopMethod; + #endregion #region Internal Constructors /// - /// Constructs PowerShell + /// Constructs PowerShell. /// /// /// A PSCommand. @@ -631,6 +638,9 @@ private PowerShell(PSCommand command, Collection extraCommands, objec ErrorBufferOwner = true; InformationalBuffers = new PSInformationalBuffers(InstanceId); Streams = new PSDataStreams(this); + _endInvokeMethod = EndInvoke; + _endStopMethod = EndStop; + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.PowerShellCreate, "create"); } /// @@ -661,12 +671,12 @@ internal PowerShell(ConnectCommandInfo connectCmdInfo, object rsConnection) { _runspacePool = (RunspacePool)rsConnection; } + Dbg.Assert(_runspacePool != null, "Invalid rsConnection parameter>"); RemotePowerShell = new ClientRemotePowerShell(this, _runspacePool.RemoteRunspacePoolInternal); } /// - /// /// /// /// @@ -693,6 +703,9 @@ internal PowerShell(ObjectStreamBase inputstream, { RemotePowerShell = new ClientRemotePowerShell(this, runspacePool.RemoteRunspacePoolInternal); } + + _endInvokeMethod = EndInvoke; + _endStopMethod = EndStop; } /// @@ -726,7 +739,7 @@ internal PowerShell(ConnectCommandInfo connectCmdInfo, ObjectStreamBase inputstr } /// - /// Sets the command collection in this powershell + /// Sets the command collection in this powershell. /// /// This method will be called by RemotePipeline /// before it begins execution. This method is used to set @@ -749,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'. @@ -791,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) { @@ -807,7 +814,7 @@ internal void InitForRemotePipelineConnect(ObjectStreamBase inputstream, ObjectS #region Construction Factory /// - /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance + /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// /// /// An instance of PowerShell. @@ -818,9 +825,9 @@ public static PowerShell Create() } /// - /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance + /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// - /// runspace mode + /// Runspace mode. /// An instance of PowerShell. public static PowerShell Create(RunspaceMode runspace) { @@ -833,6 +840,7 @@ public static PowerShell Create(RunspaceMode runspace) { throw new InvalidOperationException(PowerShellStrings.NoDefaultRunspaceForPSCreate); } + result = new PowerShell(new PSCommand(), null, Runspace.DefaultRunspace); result.IsChild = true; result.IsNested = true; @@ -848,9 +856,9 @@ public static PowerShell Create(RunspaceMode runspace) } /// - /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance + /// Constructs an empty PowerShell instance; a script or command must be added before invoking this instance. /// - /// InitialSessionState with which to create the runspace + /// InitialSessionState with which to create the runspace. /// An instance of PowerShell. public static PowerShell Create(InitialSessionState initialSessionState) { @@ -862,6 +870,32 @@ public static PowerShell Create(InitialSessionState initialSessionState) return result; } + /// + /// Constructs an empty PowerShell instance and associates it with the provided + /// Runspace; a script or command must be added before invoking this instance. + /// + /// Runspace in which to invoke commands. + /// An instance of PowerShell. + /// + /// The required Runspace argument is accepted no matter what state it is in. + /// Leaving Runspace state management to the caller allows them to open their + /// runspace in whatever manner is most appropriate for their application + /// (in another thread while this instance of the PowerShell class is being + /// instantiated, for example). + /// + public static PowerShell Create(Runspace runspace) + { + if (runspace == null) + { + throw new PSArgumentNullException(nameof(runspace)); + } + + PowerShell result = Create(); + result.Runspace = runspace; + + return result; + } + /// /// Creates a nested powershell within the current instance. /// Nested PowerShell is used to do simple operations like checking state @@ -882,7 +916,7 @@ public static PowerShell Create(InitialSessionState initialSessionState) [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ps", Justification = "ps represents PowerShell and is used at many places.")] public PowerShell CreateNestedPowerShell() { - if ((null != _worker) && (null != _worker.CurrentlyRunningPipeline)) + if ((_worker != null) && (_worker.CurrentlyRunningPipeline != null)) { PowerShell result = new PowerShell(new PSCommand(), null, _worker.CurrentlyRunningPipeline.Runspace); @@ -894,11 +928,11 @@ public PowerShell CreateNestedPowerShell() } /// - /// Method needed when deserializing PowerShell object coming from a RemoteDataObject + /// Method needed when deserializing PowerShell object coming from a RemoteDataObject. /// - /// Indicates if PowerShell object is nested - /// Commands that the PowerShell pipeline is built of - /// Extra commands to run + /// Indicates if PowerShell object is nested. + /// Commands that the PowerShell pipeline is built of. + /// Extra commands to run. private static PowerShell Create(bool isNested, PSCommand psCommand, Collection extraCommands) { PowerShell powerShell = new PowerShell(psCommand, extraCommands, null); @@ -912,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"); /// /// /// @@ -950,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); /// /// /// @@ -991,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 }"); /// /// /// @@ -1030,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); /// /// /// @@ -1108,7 +1146,7 @@ internal PowerShell AddCommand(Command command) /// /// CommandInfo object for the command to add. /// - /// The CommandInfo object for the command to add + /// The CommandInfo object for the command to add. /// /// A PSCommand instance with the command added. /// @@ -1129,20 +1167,21 @@ public PowerShell AddCommand(CommandInfo commandInfo) { if (commandInfo == null) { - throw PSTraceSource.NewArgumentNullException("commandInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(commandInfo)); } + Command cmd = new Command(commandInfo); _psCommand.AddCommand(cmd); return this; } - /// /// 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"); /// /// /// @@ -1226,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. /// @@ -1252,7 +1309,7 @@ public PowerShell AddParameters(IList parameters) { if (parameters == null) { - throw PSTraceSource.NewArgumentNullException("parameters"); + throw PSTraceSource.NewArgumentNullException(nameof(parameters)); } if (_psCommand.Commands.Count == 0) @@ -1300,7 +1357,7 @@ public PowerShell AddParameters(IDictionary parameters) { if (parameters == null) { - throw PSTraceSource.NewArgumentNullException("parameters"); + throw PSTraceSource.NewArgumentNullException(nameof(parameters)); } if (_psCommand.Commands.Count == 0) @@ -1312,11 +1369,9 @@ 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("parameters", PowerShellStrings.KeyMustBeString); + throw PSTraceSource.NewArgumentException(nameof(parameters), PowerShellStrings.KeyMustBeString); } _psCommand.AddParameter(parameterName, entry.Value); @@ -1328,12 +1383,12 @@ 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 /// of the "select-object" cmdlet which is "Property". @@ -1426,10 +1481,11 @@ public PSCommand Commands set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Command"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1440,7 +1496,7 @@ public PSCommand Commands } /// - /// Streams generated by PowerShell invocations + /// Streams generated by PowerShell invocations. /// public PSDataStreams Streams { get; } @@ -1468,10 +1524,11 @@ internal PSDataCollection ErrorBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Error"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1502,10 +1559,11 @@ internal PSDataCollection ProgressBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Progress"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1535,10 +1593,11 @@ internal PSDataCollection VerboseBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Verbose"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1568,10 +1627,11 @@ internal PSDataCollection DebugBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Debug"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1604,10 +1664,11 @@ internal PSDataCollection WarningBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Warning"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1640,10 +1701,11 @@ internal PSDataCollection InformationBuffer set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Information"); } + lock (_syncObject) { AssertChangesAreAccepted(); @@ -1653,7 +1715,7 @@ internal PSDataCollection InformationBuffer } /// - /// Gets the informational buffers + /// Gets the informational buffers. /// internal PSInformationalBuffers InformationalBuffers { get; } @@ -1690,7 +1752,6 @@ internal PSDataCollection InformationBuffer /// IsChild flag makes it possible for the pipeline to differentiate between /// a true v1 nested pipeline and the cmdlets calling cmdlets case. See bug /// 211462. - /// /// internal bool IsChild { get; private set; } = false; @@ -1784,7 +1845,7 @@ public Runspace Runspace } /// - /// Internal method to set the Runspace property + /// Internal method to set the Runspace property. /// private void SetRunspace(Runspace runspace, bool owner) { @@ -1803,6 +1864,7 @@ private void SetRunspace(Runspace runspace, bool owner) RemotePowerShell.Clear(); RemotePowerShell.Dispose(); } + RemotePowerShell = new ClientRemotePowerShell(this, remoteRunspace.RunspacePool.RemoteRunspacePoolInternal); } @@ -1864,9 +1926,11 @@ public RunspacePool RunspacePool RemotePowerShell.Clear(); RemotePowerShell.Dispose(); } + RemotePowerShell = new ClientRemotePowerShell(this, _runspacePool.RemoteRunspacePoolInternal); } + _runspace = null; } } @@ -1942,7 +2006,7 @@ public IAsyncResult ConnectAsync() /// The output buffer to return from EndInvoke. /// An AsyncCallback to be called once the previous invocation has completed. /// A user supplied state to call the with. - /// IAsyncResult + /// IAsyncResult. public IAsyncResult ConnectAsync( PSDataCollection output, AsyncCallback invocationCallback, @@ -1984,6 +2048,7 @@ public IAsyncResult ConnectAsync( OutputBuffer = new PSDataCollection(); OutputBufferOwner = true; } + streamToUse = OutputBuffer; ObjectStreamBase outputStream = new PSDataCollectionStream(InstanceId, streamToUse); @@ -2057,6 +2122,7 @@ public IAsyncResult ConnectAsync( { throw poolException.ToInvalidRunspaceStateException(); } + throw; } @@ -2109,13 +2175,13 @@ private void CheckRunspacePoolAndConnect() /// If the debugger evaluated a command then DebuggerCommand.ResumeAction /// value will be set appropriately. /// - /// Input - /// Output collection - /// PS invocation settings + /// Input. + /// Output collection. + /// PS invocation settings. /// True if PowerShell Invoke must run regardless /// of whether debugger handles the command. /// - /// DebuggerCommandResults + /// DebuggerCommandResults. internal void InvokeWithDebugger( IEnumerable input, IList output, @@ -2163,19 +2229,18 @@ internal void InvokeWithDebugger( if (Commands.Commands.Count == 0 && invokeMustRun) { - Commands.Commands.AddScript(""); + Commands.Commands.AddScript(string.Empty); } if (Commands.Commands.Count > 0) { if (addToHistory) { - if (settings == null) - { - settings = new PSInvocationSettings(); - } + settings ??= new PSInvocationSettings(); + settings.AddToHistory = true; } + Invoke(input, output, settings); } } @@ -2209,9 +2274,6 @@ internal void InvokeWithDebugger( /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2271,9 +2333,6 @@ public Collection Invoke() /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2336,9 +2395,6 @@ public Collection Invoke(IEnumerable input) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2398,9 +2454,6 @@ public Collection Invoke(IEnumerable input, PSInvocationSettings setti /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2464,9 +2517,6 @@ public Collection Invoke() /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2531,9 +2581,6 @@ public Collection Invoke(IEnumerable input) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2601,9 +2648,6 @@ public Collection Invoke(IEnumerable input, PSInvocationSettings settings) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2635,7 +2679,6 @@ public void Invoke(IEnumerable input, IList output) Invoke(input, output, null); } - /// /// Invoke the synchronously and collect /// output data into the buffer @@ -2667,9 +2710,6 @@ public void Invoke(IEnumerable input, IList output) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2698,9 +2738,9 @@ public void Invoke(IEnumerable input, IList output) /// public void Invoke(IEnumerable input, IList output, PSInvocationSettings settings) { - if (null == output) + if (output == null) { - throw PSTraceSource.NewArgumentNullException("output"); + throw PSTraceSource.NewArgumentNullException(nameof(output)); } // use the above collection as the data store. PSDataCollection listToWriteTo = new PSDataCollection(output); @@ -2741,9 +2781,6 @@ public void Invoke(IEnumerable input, IList output, PSInvocationSettings s /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -2772,10 +2809,11 @@ public void Invoke(IEnumerable input, IList output, PSInvocationSettings s /// public void Invoke(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings) { - if (null == output) + if (output == null) { - throw PSTraceSource.NewArgumentNullException("output"); + throw PSTraceSource.NewArgumentNullException(nameof(output)); } + CoreInvoke(input, output, settings); } @@ -2864,6 +2902,9 @@ public IAsyncResult BeginInvoke(PSDataCollection input) /// /// /// An AsyncCallback to call once the BeginInvoke completes. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. /// /// /// A user supplied state to call the @@ -2985,6 +3026,9 @@ public IAsyncResult BeginInvoke(PSDataCollection input, /// /// /// An AsyncCallback to call once the BeginInvoke completes. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. /// /// /// A user supplied state to call the @@ -3001,9 +3045,9 @@ public IAsyncResult BeginInvoke(PSDataCollection input, /// public IAsyncResult BeginInvoke(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings, AsyncCallback callback, object state) { - if (null == output) + if (output == null) { - throw PSTraceSource.NewArgumentNullException("output"); + throw PSTraceSource.NewArgumentNullException(nameof(output)); } DetermineIsBatching(); @@ -3017,7 +3061,259 @@ public IAsyncResult BeginInvoke(PSDataCollection input, } /// - /// Begins a batch execution + /// Invoke a PowerShell command asynchronously. + /// Use await to wait for the command to complete and obtain the output of the command. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke. + /// + /// + /// Cannot perform the operation because the command is already started. + /// Stop the command and try the operation again. + /// (or) + /// No command is added. + /// + /// + /// Object is disposed. + /// + /// + /// The running PowerShell pipeline was stopped. + /// This occurs when or is called. + /// + public Task> InvokeAsync() + => Task>.Factory.FromAsync(BeginInvoke(), _endInvokeMethod); + + /// + /// Invoke a PowerShell command asynchronously. + /// Use await to wait for the command to complete and obtain the output of the command. + /// + /// + /// + /// When invoked using InvokeAsync, invocation doesn't + /// finish until Input is closed. Caller of InvokeAsync must + /// close the input buffer after all input has been written to + /// input buffer. Input buffer is closed by calling + /// Close() method. + /// + /// If you want this command to execute as a standalone cmdlet + /// (that is, using command-line parameters only), + /// be sure to call Close() before calling InvokeAsync(). Otherwise, + /// the command will be executed as though it had external input. + /// If you observe that the command isn't doing anything, + /// this may be the reason. + /// + /// + /// + /// Type of the input buffer. + /// + /// + /// Input to the command. See remarks for more details. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke. + /// + /// + /// Cannot perform the operation because the command is already started. + /// Stop the command and try the operation again. + /// (or) + /// No command is added. + /// + /// + /// 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); + + /// + /// Invoke a PowerShell command asynchronously. + /// Use await to wait for the command to complete and obtain the output of the command. + /// + /// + /// + /// When invoked using InvokeAsync, invocation doesn't + /// finish until Input is closed. Caller of InvokeAsync must + /// close the input buffer after all input has been written to + /// input buffer. Input buffer is closed by calling + /// Close() method. + /// + /// If you want this command to execute as a standalone cmdlet + /// (that is, using command-line parameters only), + /// be sure to call Close() before calling InvokeAsync(). Otherwise, + /// the command will be executed as though it had external input. + /// If you observe that the command isn't doing anything, + /// this may be the reason. + /// + /// + /// + /// Type of the input buffer. + /// + /// + /// Input to the command. See remarks for more details. + /// + /// + /// Invocation Settings. + /// + /// + /// An AsyncCallback to call once the command is invoked. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. + /// + /// + /// A user supplied state to call the + /// with. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke. + /// + /// + /// Cannot perform the operation because the command is already started. + /// Stop the command and try the operation again. + /// (or) + /// No command is added. + /// + /// + /// 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); + + /// + /// Invoke a PowerShell command asynchronously. + /// Use await to wait for the command to complete and obtain the output of the command. + /// + /// + /// + /// When invoked using InvokeAsync, invocation doesn't + /// finish until Input is closed. Caller of InvokeAsync must + /// close the input buffer after all input has been written to + /// input buffer. Input buffer is closed by calling + /// Close() method. + /// + /// If you want this command to execute as a standalone cmdlet + /// (that is, using command-line parameters only), + /// be sure to call Close() before calling InvokeAsync(). Otherwise, + /// the command will be executed as though it had external input. + /// If you observe that the command isn't doing anything, + /// this may be the reason. + /// + /// + /// + /// Type of input object(s) for the command invocation. + /// + /// + /// Type of output object(s) expected from the command invocation. + /// + /// + /// Input to the command. See remarks for more details. + /// + /// + /// A buffer supplied by the user where output is collected. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke, + /// or null if the caller provided their own buffer. + /// + /// + /// Cannot perform the operation because the command is already started. + /// Stop the command and try the operation again. + /// (or) + /// No command is added. + /// + /// + /// 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); + + /// + /// Invoke a PowerShell command asynchronously and collect + /// output data into the buffer . + /// Use await to wait for the command to complete and obtain the output of the command. + /// + /// + /// + /// When invoked using InvokeAsync, invocation doesn't + /// finish until Input is closed. Caller of InvokeAsync must + /// close the input buffer after all input has been written to + /// input buffer. Input buffer is closed by calling + /// Close() method. + /// + /// If you want this command to execute as a standalone cmdlet + /// (that is, using command-line parameters only), + /// be sure to call Close() before calling InvokeAsync(). Otherwise, + /// the command will be executed as though it had external input. + /// If you observe that the command isn't doing anything, + /// this may be the reason. + /// + /// + /// + /// Type of input object(s) for the command invocation. + /// + /// + /// Type of output object(s) expected from the command invocation. + /// + /// + /// Input to the command. See remarks for more details. + /// + /// + /// A buffer supplied by the user where output is collected. + /// + /// + /// Invocation Settings. + /// + /// + /// An AsyncCallback to call once the command is invoked. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. + /// + /// + /// A user supplied state to call the + /// with. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke, + /// or null if the caller provided their own buffer. + /// + /// + /// Cannot perform the operation because the command is already started. + /// Stop the command and try the operation again. + /// (or) + /// No command is added. + /// + /// + /// 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); + + /// + /// Begins a batch execution. /// /// /// Type of input object(s) for the command invocation. @@ -3052,9 +3348,7 @@ public IAsyncResult BeginInvoke(PSDataCollection input, /// 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(); } @@ -3065,7 +3359,7 @@ private IAsyncResult BeginBatchInvoke(PSDataCollection } RunspacePool pool = _rsConnection as RunspacePool; - if ((null != pool) && (pool.IsRemote)) + if ((pool != null) && (pool.IsRemote)) { // Server supports batch invocation, in this case, we just send everything to the server and return immediately if (ServerSupportsBatchInvocation()) @@ -3097,9 +3391,8 @@ private IAsyncResult BeginBatchInvoke(PSDataCollection return _batchAsyncResult; } - /// - /// Batch invocation callback + /// Batch invocation callback. /// /// private void BatchInvocationWorkItem(object state) @@ -3140,7 +3433,7 @@ private void BatchInvocationWorkItem(object state) SetHadErrors(true); // Stop if necessarily - if ((null != _batchInvocationSettings) && _batchInvocationSettings.ErrorActionPreference == ActionPreference.Stop) + if ((_batchInvocationSettings != null) && _batchInvocationSettings.ErrorActionPreference == ActionPreference.Stop) { _stopBatchExecution = true; AppendExceptionToErrorStream(e); @@ -3189,7 +3482,7 @@ private void BatchInvocationWorkItem(object state) } /// - /// Batch invocation callback + /// Batch invocation callback. /// /// private void BatchInvocationCallback(IAsyncResult result) @@ -3225,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 { @@ -3250,10 +3541,7 @@ private void BatchInvocationCallback(IAsyncResult result) break; } - if (objs == null) - { - objs = _batchAsyncResult.Output; - } + objs ??= _batchAsyncResult.Output; DoRemainingBatchCommands(objs); } @@ -3267,7 +3555,7 @@ private void BatchInvocationCallback(IAsyncResult result) } /// - /// Executes remaining batch commands + /// Executes remaining batch commands. /// private void DoRemainingBatchCommands(PSDataCollection objs) { @@ -3291,7 +3579,6 @@ private void DoRemainingBatchCommands(PSDataCollection objs) } /// - /// /// private void DetermineIsBatching() { @@ -3308,7 +3595,7 @@ private void DetermineIsBatching() } /// - /// Prepare for async batch execution + /// Prepare for async batch execution. /// private void SetupAsyncBatchExecution() { @@ -3344,7 +3631,7 @@ private void SetupAsyncBatchExecution() } /// - /// Ends an async batch execution + /// Ends an async batch execution. /// private void EndAsyncBatchExecution() { @@ -3354,7 +3641,7 @@ private void EndAsyncBatchExecution() } /// - /// Appends an exception to the error stream + /// Appends an exception to the error stream. /// /// private void AppendExceptionToErrorStream(Exception e) @@ -3388,24 +3675,32 @@ 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 { _commandInvokedSynchronously = true; - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; - if ((null == psAsyncResult) || + if ((psAsyncResult == null) || (psAsyncResult.OwnerId != InstanceId) || - (psAsyncResult.IsAssociatedWithAsyncInvoke != true)) + (!psAsyncResult.IsAssociatedWithAsyncInvoke)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), PowerShellStrings.AsyncResultNotOwned, "IAsyncResult", "BeginInvoke"); } @@ -3413,13 +3708,8 @@ public PSDataCollection EndInvoke(IAsyncResult asyncResult) psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; - if (OutputBufferOwner) - { - // PowerShell no longer owns the output buffer when it is passed back to the caller. - OutputBufferOwner = false; - OutputBuffer = null; - } - + // PowerShell no longer owns the output buffer when it is passed back to the caller. + ResetOutputBufferAsNeeded(); return psAsyncResult.Output; } catch (InvalidRunspacePoolStateException exception) @@ -3429,6 +3719,7 @@ public PSDataCollection EndInvoke(IAsyncResult asyncResult) { throw exception.ToInvalidRunspaceStateException(); } + throw; } } @@ -3443,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 @@ -3450,6 +3745,9 @@ public void Stop() IAsyncResult asyncResult = CoreStop(true, null, null); // This is a sync call..Wait for the stop operation to complete. asyncResult.AsyncWaitHandle.WaitOne(); + + // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. + ResetOutputBufferAsNeeded(); } catch (ObjectDisposedException) { @@ -3467,6 +3765,9 @@ public void Stop() /// /// /// A AsyncCallback to call once the BeginStop completes. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. /// /// /// A user supplied state to call the @@ -3493,32 +3794,73 @@ 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 (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } PowerShellAsyncResult psAsyncResult = asyncResult as PowerShellAsyncResult; - if ((null == psAsyncResult) || + if ((psAsyncResult == null) || (psAsyncResult.OwnerId != InstanceId) || - (psAsyncResult.IsAssociatedWithAsyncInvoke != false)) + (psAsyncResult.IsAssociatedWithAsyncInvoke)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), PowerShellStrings.AsyncResultNotOwned, "IAsyncResult", "BeginStop"); } psAsyncResult.EndInvoke(); + + // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. + ResetOutputBufferAsNeeded(); } + /// + /// Stop a PowerShell command asynchronously. + /// Use await to wait for the command to stop. + /// + /// + /// + /// If the command is not started, the state of the PowerShell instance + /// is changed to Stopped and corresponding events will be raised. + /// + /// + /// + /// An AsyncCallback to call once the command is invoked. + /// Note: when using this API in script, don't pass in a delegate that is cast from a script block. + /// The callback could be invoked from a thread without a default Runspace and a delegate cast from + /// a script block would fail in that case. + /// + /// + /// A user supplied state to call the + /// with. + /// + /// + /// The output buffer created to hold the results of the asynchronous invoke, + /// or null if the caller provided their own buffer. + /// + /// + /// 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); + #endregion #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. @@ -3538,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 @@ -3555,7 +3932,7 @@ public void Dispose() /// /// Indicates if this PowerShell object is the owner of the - /// runspace or RunspacePool assigned to this object + /// runspace or RunspacePool assigned to this object. /// public bool IsRunspaceOwner { get; internal set; } = false; @@ -3564,39 +3941,28 @@ public void Dispose() internal bool OutputBufferOwner { get; set; } = true; /// - /// OutputBuffer + /// OutputBuffer. /// internal PSDataCollection OutputBuffer { get; private set; } /// - /// This has been added as a work around for Windows8 bug 803461. - /// It should be used only for the PSJobProxy API. - /// - /// Resets the instance ID of the command to a new guid. - /// If this is not done, then there is a race condition on the server - /// in the following circumstances: - /// - /// ps.BeginInvoke(...); - /// ps.Stop() - /// ps.Commands.Clear(); - /// ps.AddCommand("Foo"); - /// ps.Invoke(); - /// - /// In these conditions, stop returns before the server is done cleaning up. - /// The subsequent invoke will cause an error because the guid already - /// identifies a command in progress. + /// Reset the output buffer to null if it's owned by the current powershell instance. /// - internal void GenerateNewInstanceId() + private void ResetOutputBufferAsNeeded() { - InstanceId = Guid.NewGuid(); + if (OutputBufferOwner) + { + OutputBufferOwner = false; + OutputBuffer = null; + } } /// /// Get a steppable pipeline object. /// - /// A steppable pipeline object + /// A steppable pipeline object. /// An attempt was made to use the scriptblock outside of the engine. - internal SteppablePipeline GetSteppablePipeline() + public SteppablePipeline GetSteppablePipeline() { ExecutionContext context = GetContextFromTLS(); SteppablePipeline spl = GetSteppablePipeline(context, CommandOrigin.Internal); @@ -3638,11 +4004,11 @@ internal ExecutionContext GetContextFromTLS() } /// - /// Gets the steppable pipeline from the powershell object + /// Gets the steppable pipeline from the powershell object. /// - /// engine execution context - /// command origin - /// steppable pipeline object + /// Engine execution context. + /// Command origin. + /// Steppable pipeline object. private SteppablePipeline GetSteppablePipeline(ExecutionContext context, CommandOrigin commandOrigin) { // Check for an empty pipeline @@ -3663,7 +4029,7 @@ private SteppablePipeline GetSteppablePipeline(ExecutionContext context, Command ( Runspace.DefaultRunspace.ExecutionContext, false, - IsNested == true ? CommandOrigin.Internal : CommandOrigin.Runspace + IsNested ? CommandOrigin.Internal : CommandOrigin.Runspace ); commandProcessorBase.RedirectShellErrorOutputPipe = RedirectShellErrorOutputPipe; @@ -3695,7 +4061,7 @@ private SteppablePipeline GetSteppablePipeline(ExecutionContext context, Command internal bool IsGetCommandMetadataSpecialPipeline { get; set; } /// - /// Checks if the command is running + /// Checks if the command is running. /// /// private bool IsCommandRunning() @@ -3791,72 +4157,13 @@ 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 + /// Clear the internal elements. /// private void InternalClearSuppressExceptions() { lock (_syncObject) { - if (null != _worker) - { - _worker.InternalClearSuppressExceptions(); - } + _worker?.InternalClearSuppressExceptions(); } } @@ -3894,7 +4201,7 @@ private void RaiseStateChangeEvent(PSInvocationStateInfo stateInfo) /// /// Sets the state of this powershell instance. /// - /// the state info to set + /// The state info to set. internal void SetStateChanged(PSInvocationStateInfo stateInfo) { PSInvocationStateInfo copyStateInfo = stateInfo; @@ -3930,6 +4237,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { return; } + break; case PSInvocationState.Stopping: // We are in stopping state and we should not honor Running state @@ -3944,6 +4252,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { copyStateInfo = new PSInvocationStateInfo(PSInvocationState.Stopped, stateInfo.Reason); } + break; default: break; @@ -3980,10 +4289,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { if (RunningExtraCommands) { - if (null != tempInvokeAsyncResult) - { - tempInvokeAsyncResult.SetAsCompleted(InvocationStateInfo.Reason); - } + tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); RaiseStateChangeEvent(InvocationStateInfo.Clone()); } @@ -3991,16 +4297,10 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { RaiseStateChangeEvent(InvocationStateInfo.Clone()); - if (null != tempInvokeAsyncResult) - { - tempInvokeAsyncResult.SetAsCompleted(InvocationStateInfo.Reason); - } + tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); } - if (null != tempStopAsyncResult) - { - tempStopAsyncResult.SetAsCompleted(null); - } + tempStopAsyncResult?.SetAsCompleted(null); } catch (Exception) { @@ -4012,12 +4312,13 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) } finally { - // takes care exception occured with invokeAsyncResult - if (isExceptionOccured && (null != tempStopAsyncResult)) + // takes care exception occurred with invokeAsyncResult + if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); } } + break; case PSInvocationState.Disconnected: try @@ -4038,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 @@ -4062,8 +4360,8 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) } finally { - // takes care exception occured with invokeAsyncResult - if (isExceptionOccured && (null != tempStopAsyncResult)) + // takes care exception occurred with invokeAsyncResult + if (isExceptionOccured && (tempStopAsyncResult != null)) { tempStopAsyncResult.Release(); } @@ -4101,21 +4399,18 @@ private void CloseInputBufferOnReconnection(PSInvocationState previousState) } /// - /// clear the internal reference to remote powershell + /// Clear the internal reference to remote powershell. /// internal void ClearRemotePowerShell() { lock (_syncObject) { - if (null != RemotePowerShell) - { - RemotePowerShell.Clear(); - } + RemotePowerShell?.Clear(); } } /// - /// Sets if the pipeline is nested, typically used by the remoting infrastructure + /// Sets if the pipeline is nested, typically used by the remoting infrastructure. /// /// internal void SetIsNested(bool isNested) @@ -4153,9 +4448,6 @@ internal void SetIsNested(bool isNested) /// because the current CLR permissions do not allow adequate /// reflection access to a cmdlet assembly. /// - /// - /// The thread in which the command was executing was aborted. - /// /// /// PowerShell.Invoke can throw a variety of exceptions derived /// from RuntimeException. The most likely of these exceptions @@ -4185,26 +4477,28 @@ internal void SetIsNested(bool isNested) private void CoreInvoke(IEnumerable input, PSDataCollection output, PSInvocationSettings settings) { PSDataCollection inputBuffer = null; - if (null != input) + if (input != null) { inputBuffer = new PSDataCollection(); foreach (object o in input) { inputBuffer.Add(o); } + inputBuffer.Complete(); } + CoreInvoke(inputBuffer, output, settings); } /// - /// Core invocation helper method + /// Core invocation helper method. /// /// input type /// output type - /// input objects - /// output object - /// invocation settings + /// Input objects. + /// Output object. + /// Invocation settings. private void CoreInvokeHelper(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings) { RunspacePool pool = _rsConnection as RunspacePool; @@ -4218,11 +4512,12 @@ private void CoreInvokeHelper(PSDataCollection input, P Runspace rsToUse = null; if (!IsNested) { - if (null != pool) + if (pool != null) { -#if !CORECLR // No ApartmentState In CoreCLR +#if !UNIX VerifyThreadSettings(settings, pool.ApartmentState, pool.ThreadOptions, false); #endif + // getting the runspace asynchronously so that Stop can be supported from a different // thread. _worker.GetRunspaceAsyncResult = pool.BeginGetRunspace(null, null); @@ -4232,11 +4527,12 @@ private void CoreInvokeHelper(PSDataCollection input, P else { rsToUse = _rsConnection as Runspace; - if (null != rsToUse) + if (rsToUse != null) { -#if !CORECLR // No ApartmentState In CoreCLR +#if !UNIX VerifyThreadSettings(settings, rsToUse.ApartmentState, rsToUse.ThreadOptions, false); #endif + if (rsToUse.RunspaceStateInfo.State != RunspaceState.Opened) { string message = StringUtil.Format(PowerShellStrings.InvalidRunspaceState, RunspaceState.Opened, rsToUse.RunspaceStateInfo.State); @@ -4257,10 +4553,9 @@ private void CoreInvokeHelper(PSDataCollection input, P else { rsToUse = _rsConnection as Runspace; - Dbg.Assert(null != rsToUse, + Dbg.Assert(rsToUse != null, "Nested PowerShell can only work on a Runspace"); - // Perform work on the current thread. Nested Pipeline // should be invoked from the same thread that the parent // pipeline is executing in. @@ -4278,18 +4573,19 @@ private void CoreInvokeHelper(PSDataCollection input, P { throw poolException.ToInvalidRunspaceStateException(); } + throw; } } /// - /// Core invocation helper method for remoting + /// Core invocation helper method for remoting. /// /// input type /// output type - /// input objects - /// output object - /// invocation settings + /// Input objects. + /// Output object. + /// Invocation settings. private void CoreInvokeRemoteHelper(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings) { RunspacePool pool = _rsConnection as RunspacePool; @@ -4307,8 +4603,8 @@ private void CoreInvokeRemoteHelper(PSDataCollection in psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; - if ((PSInvocationState.Failed == InvocationStateInfo.State) && - (null != InvocationStateInfo.Reason)) + if ((InvocationStateInfo.State == PSInvocationState.Failed) && + (InvocationStateInfo.Reason != null)) { throw InvocationStateInfo.Reason; } @@ -4317,13 +4613,13 @@ private void CoreInvokeRemoteHelper(PSDataCollection in } /// - /// Core invocation method + /// Core invocation method. /// /// input type /// output type - /// input objects - /// output object - /// invocation settings + /// Input objects. + /// Output object. + /// Invocation settings. private void CoreInvoke(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings) { bool isRemote = false; @@ -4337,7 +4633,7 @@ private void CoreInvoke(PSDataCollection input, PSDataC SetHadErrors(false); RunspacePool pool = _rsConnection as RunspacePool; - if ((null != pool) && (pool.IsRemote)) + if ((pool != null) && (pool.IsRemote)) { if (ServerSupportsBatchInvocation()) { @@ -4398,13 +4694,13 @@ private void CoreInvoke(PSDataCollection input, PSDataC SetHadErrors(true); // Stop if necessarily - if ((null != settings) && settings.ErrorActionPreference == ActionPreference.Stop) + if ((settings != null) && settings.ErrorActionPreference == ActionPreference.Stop) { throw; } // Ignore the exception if necessary. - if ((null != settings) && settings.ErrorActionPreference == ActionPreference.Ignore) + if ((settings != null) && settings.ErrorActionPreference == ActionPreference.Ignore) { continue; } @@ -4498,11 +4794,12 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i // IsNested is true for the icm | % { icm } scenario if (!IsNested || (pool != null && pool.IsRemote)) { - if (null != pool) + if (pool != null) { -#if !CORECLR // No ApartmentState In CoreCLR +#if !UNIX VerifyThreadSettings(settings, pool.ApartmentState, pool.ThreadOptions, pool.IsRemote); #endif + pool.AssertPoolIsOpen(); // for executing in a remote runspace pool case @@ -4542,6 +4839,7 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i inputStream = new ObjectStream(); inputStream.Close(); } + RemotePowerShell.Initialize( inputStream, new PSDataCollectionStream(InstanceId, output), new PSDataCollectionStream(InstanceId, _errorBuffer), @@ -4553,6 +4851,7 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i { RemotePowerShell.InputStream = inputStream; } + if (output != null) { RemotePowerShell.OutputStream = @@ -4561,7 +4860,7 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i } pool.RemoteRunspacePoolInternal.CreatePowerShellOnServerAndInvoke(RemotePowerShell); - } // lock + } RaiseStateChangeEvent(InvocationStateInfo.Clone()); } @@ -4574,11 +4873,12 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i else { LocalRunspace rs = _rsConnection as LocalRunspace; - if (null != rs) + if (rs != null) { -#if !CORECLR // No ApartmentState In CoreCLR +#if !UNIX VerifyThreadSettings(settings, rs.ApartmentState, rs.ThreadOptions, false); #endif + if (rs.RunspaceStateInfo.State != RunspaceState.Opened) { string message = StringUtil.Format(PowerShellStrings.InvalidRunspaceState, RunspaceState.Opened, rs.RunspaceStateInfo.State); @@ -4590,6 +4890,7 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i throw e; } + _worker.CreateRunspaceIfNeededAndDoWork(rs, false); } else @@ -4619,17 +4920,19 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i { throw poolException.ToInvalidRunspaceStateException(); } + throw; } return _invokeAsyncResult; } -#if !CORECLR // No ApartmentState In CoreCLR + // Apartment thread state does not apply to non-Windows platforms. +#if !UNIX /// - /// Verifies the settings for ThreadOptions and ApartmentState + /// 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; @@ -4663,7 +4966,6 @@ private void VerifyThreadSettings(PSInvocationSettings settings, ApartmentState #endif /// - /// /// /// Type for the input collection /// Type for the output collection @@ -4680,11 +4982,11 @@ private void VerifyThreadSettings(PSInvocationSettings settings, ApartmentState /// private void Prepare(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings, bool shouldCreateWorker) { - Dbg.Assert(null != output, "Output cannot be null"); + Dbg.Assert(output != null, "Output cannot be null"); lock (_syncObject) { - if ((null == _psCommand) || (null == _psCommand.Commands) || (0 == _psCommand.Commands.Count)) + if ((_psCommand == null) || (_psCommand.Commands == null) || (_psCommand.Commands.Count == 0)) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.NoCommandToInvoke); } @@ -4699,7 +5001,7 @@ private void Prepare(PSDataCollection input, PSDataColl InvocationStateInfo = new PSInvocationStateInfo(PSInvocationState.Running, null); // update settings for impersonation policy - if ((null != settings) && (settings.FlowImpersonationPolicy)) + if ((settings != null) && (settings.FlowImpersonationPolicy)) { // get the identity of the thread. // false behavior: If the thread is impersonating the WindowsIdentity for the @@ -4713,7 +5015,7 @@ private void Prepare(PSDataCollection input, PSDataColl // this way pipeline will not waste resources creating // the same. ObjectStreamBase inputStream; - if (null != input) + if (input != null) { inputStream = new PSDataCollectionStream(InstanceId, input); } @@ -4743,7 +5045,7 @@ private void Prepare(PSDataCollection input, PSDataColl /// /// Called by both Sync Stop and Async Stop. /// If isSyncCall is false, then an IAsyncResult object is returned which - /// can be passed back to the user + /// can be passed back to the user. /// /// /// true if pipeline to be stopped synchronously, @@ -4764,8 +5066,8 @@ private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object st // Acquire lock as we are going to change state here.. lock (_syncObject) { - //BUGBUG: remote powershell appears to handle state change's differently - //Need to speak with remoting dev and resolve this. + // BUGBUG: remote powershell appears to handle state change's differently + // Need to speak with remoting dev and resolve this. switch (InvocationStateInfo.State) { case PSInvocationState.NotStarted: @@ -4793,6 +5095,7 @@ private IAsyncResult CoreStop(bool isSyncCall, AsyncCallback callback, object st _stopAsyncResult = new PowerShellAsyncResult(InstanceId, callback, state, null, false); _stopAsyncResult.SetAsCompleted(null); } + return _stopAsyncResult; case PSInvocationState.Running: @@ -4816,11 +5119,9 @@ 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); // Raise event for failed state change. @@ -4880,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(); } /// @@ -4930,7 +5228,7 @@ private void StopThreadProc(object state) /// /// The client remote powershell associated with this - /// powershell object + /// powershell object. /// internal ClientRemotePowerShell RemotePowerShell { get; private set; } @@ -4938,15 +5236,15 @@ private void StopThreadProc(object state) /// The history string to be used for displaying /// the history. /// - public String HistoryString { get; set; } + public string HistoryString { get; set; } /// - /// Extra commands to run in a single invocation + /// Extra commands to run in a single invocation. /// internal Collection ExtraCommands { get; } /// - /// Currently running extra commands + /// Currently running extra commands. /// internal bool RunningExtraCommands { get; private set; } @@ -4955,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; @@ -4969,7 +5267,7 @@ private bool ServerSupportsBatchInvocation() } return remoteRunspacePoolInternal != null && - remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM; + remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersion_2_2; } /// @@ -4984,10 +5282,7 @@ private void AddToRemoteRunspaceRunningList() else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); - if (remoteRunspacePoolInternal != null) - { - remoteRunspacePoolInternal.PushRunningPowerShell(this); - } + remoteRunspacePoolInternal?.PushRunningPowerShell(this); } } @@ -5003,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 @@ -5026,16 +5318,15 @@ 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(); /// - /// /// /// /// @@ -5097,7 +5388,7 @@ internal void CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, bool isSync) { // Set the host for this local runspace if user specified one. LocalRunspace rs = rsToUse as LocalRunspace; - if (null == rs) + if (rs == null) { lock (_shell._syncObject) { @@ -5109,7 +5400,7 @@ internal void CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, bool isSync) { Runspace runspace = null; - if ((null != _settings) && (null != _settings.Host)) + if ((_settings != null) && (_settings.Host != null)) { runspace = RunspaceFactory.CreateRunspace(_settings.Host); } @@ -5194,6 +5485,7 @@ internal void RunspaceAvailableCallback(IAsyncResult asyncResult) return; _isNotActive = true; } + _shell.PipelineStateChanged(this, new PipelineStateEventArgs( new PipelineStateInfo(PipelineState.Failed, @@ -5238,12 +5530,12 @@ internal bool ConstructPipelineAndDoWork(Runspace rs, bool performSyncInvoke) return false; } - if (null != lrs) + if (lrs != null) { LocalPipeline localPipeline = new LocalPipeline( lrs, _shell.Commands.Commands, - ((null != _settings) && (_settings.AddToHistory)) ? true : false, + (_settings != null && _settings.AddToHistory), _shell.IsNested, _inputStream, _outputStream, @@ -5252,7 +5544,7 @@ internal bool ConstructPipelineAndDoWork(Runspace rs, bool performSyncInvoke) localPipeline.IsChild = _shell.IsChild; - if (!String.IsNullOrEmpty(_shell.HistoryString)) + if (!string.IsNullOrEmpty(_shell.HistoryString)) { localPipeline.SetHistoryString(_shell.HistoryString); } @@ -5302,7 +5594,7 @@ internal void Stop(bool isSyncCall) } _isNotActive = true; - if (null != CurrentlyRunningPipeline) + if (CurrentlyRunningPipeline != null) { if (isSyncCall) { @@ -5312,10 +5604,11 @@ internal void Stop(bool isSyncCall) { CurrentlyRunningPipeline.StopAsync(); } + return; } - if (null != GetRunspaceAsyncResult) + if (GetRunspaceAsyncResult != null) { RunspacePool pool = _shell._rsConnection as RunspacePool; Dbg.Assert(pool != null, "RunspaceConnection must be a runspace pool"); @@ -5346,7 +5639,7 @@ internal void InternalClearSuppressExceptions() { try { - if ((null != _settings) && (null != _settings.WindowsIdentityToImpersonate)) + if ((_settings != null) && (_settings.WindowsIdentityToImpersonate != null)) { _settings.WindowsIdentityToImpersonate.Dispose(); _settings.WindowsIdentityToImpersonate = null; @@ -5356,7 +5649,7 @@ internal void InternalClearSuppressExceptions() _outputStream.Close(); _errorStream.Close(); - if (null == CurrentlyRunningPipeline) + if (CurrentlyRunningPipeline == null) { return; } @@ -5365,7 +5658,7 @@ internal void InternalClearSuppressExceptions() // and pipeline.dispose will not change powershell instances state CurrentlyRunningPipeline.StateChanged -= _shell.PipelineStateChanged; - if ((null == GetRunspaceAsyncResult) && (null == _shell._rsConnection)) + if ((GetRunspaceAsyncResult == null) && (_shell._rsConnection == null)) { // user did not supply a runspace..Invoke* method created // a new runspace..so close it. @@ -5374,10 +5667,7 @@ internal void InternalClearSuppressExceptions() else { RunspacePool pool = _shell._rsConnection as RunspacePool; - if (null != pool) - { - pool.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); - } + pool?.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); } CurrentlyRunningPipeline.Dispose(); @@ -5416,7 +5706,7 @@ internal void GetSettings(out bool addToHistory, out bool noInput, out uint apar /// Creates a PowerShell object from a PSObject property bag. /// PSObject has to be in the format returned by ToPSObjectForRemoting method. /// - /// PSObject to rehydrate + /// PSObject to rehydrate. /// /// PowerShell rehydrated from a PSObject property bag /// @@ -5430,7 +5720,7 @@ internal static PowerShell FromPSObjectForRemoting(PSObject powerShellAsPSObject { if (powerShellAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("powerShellAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(powerShellAsPSObject)); } Collection extraCommands = null; @@ -5489,7 +5779,7 @@ internal static PowerShell FromPSObjectForRemoting(PSObject powerShellAsPSObject /// Returns this object as a PSObject property bag /// that can be used in a remoting protocol data object. /// - /// This object as a PSObject property bag + /// This object as a PSObject property bag. internal PSObject ToPSObjectForRemoting() { PSObject powerShellAsPSObject = RemotingEncoder.CreateEmptyPSObject(); @@ -5523,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) @@ -5548,10 +5838,7 @@ internal void SuspendIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.SuspendQueue(true); - } + RemotePowerShell.DataStructureHandler?.TransportManager.SuspendQueue(true); } /// @@ -5564,10 +5851,7 @@ internal void ResumeIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.ResumeQueue(); - } + RemotePowerShell.DataStructureHandler?.TransportManager.ResumeQueue(); } /// @@ -5596,55 +5880,6 @@ internal void WaitForServicingComplete() #endregion - #region V3 Extensions - - /// - /// Returns a job object which can be used to - /// control the invocation of the command with - /// AsJob Parameter - /// - /// Job object - public PSJobProxy AsJobProxy() - { - // if there are no commands added - // throw an invalid operation exception - if (this.Commands.Commands.Count == 0) - { - throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.GetJobForCommandRequiresACommand); - } - - // if there is more than one command in the - // command collection throw an error - if (this.Commands.Commands.Count > 1) - { - throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.GetJobForCommandNotSupported); - } - - // check if the AsJob parameter has already - // been added. If not, add the same - bool found = false; - foreach (CommandParameter parameter in this.Commands.Commands[0].Parameters) - { - if (string.Compare(parameter.Name, "AsJob", StringComparison.OrdinalIgnoreCase) == 0) - { - found = true; - } - } - - if (!found) - { - AddParameter("AsJob"); - } - - // initialize the job invoker and return the same - PSJobProxy job = new PSJobProxy(this.Commands.Commands[0].CommandText); - job.InitializeJobProxy(this.Commands, this.Runspace, this.RunspacePool); - - return job; - } - - #endregion V3 Extensions - #if !CORECLR // PSMI Not Supported On CSS #region Win Blue Extensions @@ -5666,6 +5901,7 @@ internal CimInstance AsPSPowerShellPipeline() { _worker.GetSettings(out addToHistoryValue, out noInputValue, out apartmentStateValue); } + CimProperty addToHistoryProperty = InternalMISerializer.CreateCimProperty("AddToHistory", addToHistoryValue, Microsoft.Management.Infrastructure.CimType.Boolean); @@ -5700,12 +5936,12 @@ internal CimInstance AsPSPowerShellPipeline() } /// - /// Streams generated by PowerShell invocations + /// Streams generated by PowerShell invocations. /// public sealed class PSDataStreams { /// - /// PSDataStreams is the public interface to access the *Buffer properties in the PowerShell class + /// PSDataStreams is the public interface to access the *Buffer properties in the PowerShell class. /// internal PSDataStreams(PowerShell powershell) { @@ -5872,7 +6108,7 @@ public PSDataCollection Information } /// - /// Removes all items from all the data streams + /// Removes all items from all the data streams. /// public void ClearStreams() { @@ -5884,7 +6120,7 @@ public void ClearStreams() this.Warning.Clear(); } - private PowerShell _powershell; + private readonly PowerShell _powershell; } /// @@ -5903,20 +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("context"); - } - if (powerShell == null) - { - throw new ArgumentNullException("powerShell"); - } + ArgumentNullException.ThrowIfNull(context); + + 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 a13c97a8a2e..f86d2c00a54 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -1,115 +1,94 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Management.Automation.Remoting; using System.Text; -using System.Diagnostics; - 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 { - #region Private Members + #region Fields private readonly ProcessStartInfo _startInfo; - private static readonly string s_PSExePath; private RunspacePool _runspacePool; private readonly object _syncObject = new object(); private bool _started; private bool _isDisposed; private bool _processExited; - #endregion Private Members + internal static readonly string PwshExePath; + internal static readonly string WinPwshExePath; + + #endregion Fields #region Constructors - /// - /// - /// static PowerShellProcessInstance() { #if UNIX - s_PSExePath = Path.Combine(Utils.DefaultPowerShellAppBase, - "pwsh"); + PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh"); #else - s_PSExePath = Path.Combine(Utils.DefaultPowerShellAppBase, - "pwsh.exe"); + PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh.exe"); + var winPowerShellDir = Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID); + WinPwshExePath = string.IsNullOrEmpty(winPowerShellDir) ? null : Path.Combine(winPowerShellDir, "powershell.exe"); #endif } /// - /// + /// Initializes a new instance of the class. Initializes the underlying dotnet process class. /// - /// - /// - /// - /// - public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) + /// 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. + /// Specifies the initial working directory for the new powershell process. + public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64, string workingDirectory) { - string psWow64Path = s_PSExePath; - - if (useWow64) + string exePath = PwshExePath; + bool startingWindowsPowerShell51 = false; +#if !UNIX + // if requested PS version was "5.1" then we start Windows PS instead of PS Core + startingWindowsPowerShell51 = (powerShellVersion != null) && (powerShellVersion.Major == 5) && (powerShellVersion.Minor == 1); + if (startingWindowsPowerShell51) { - string procArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); - - if ((!string.IsNullOrEmpty(procArch)) && (procArch.Equals("amd64", StringComparison.OrdinalIgnoreCase) || - procArch.Equals("ia64", StringComparison.OrdinalIgnoreCase))) + if (WinPwshExePath == null) { - psWow64Path = s_PSExePath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\"); - - if (!File.Exists(psWow64Path)) - { - string message = - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.IPCWowComponentNotPresent, - psWow64Path); - throw new PSInvalidOperationException(message); - } + throw new PSInvalidOperationException(RemotingErrorIdStrings.WindowsPowerShellNotPresent); } - } -#if CORECLR - string processArguments = " -s -NoLogo -NoProfile"; -#else - // Adding Version parameter to powershell - // Version parameter needs to go before all other parameters because the native layer looks for Version or - // PSConsoleFile parameters before parsing other parameters. - // The other parameters get parsed in the managed layer. - Version tempVersion = powerShellVersion ?? PSVersionInfo.PSVersion; - string processArguments = string.Format(CultureInfo.InvariantCulture, - "-Version {0}", new Version(tempVersion.Major, tempVersion.Minor)); + exePath = WinPwshExePath; - processArguments = string.Format(CultureInfo.InvariantCulture, - "{0} -s -NoLogo -NoProfile", processArguments); + if (useWow64) + { + string procArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); -#endif + if ((!string.IsNullOrEmpty(procArch)) && (procArch.Equals("amd64", StringComparison.OrdinalIgnoreCase) || + procArch.Equals("ia64", StringComparison.OrdinalIgnoreCase))) + { + exePath = WinPwshExePath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\"); - if (initializationScript != null) - { - string scripBlockAsString = initializationScript.ToString(); - if (!string.IsNullOrEmpty(scripBlockAsString)) - { - string encodedCommand = - Convert.ToBase64String(Encoding.Unicode.GetBytes(scripBlockAsString)); - processArguments = string.Format(CultureInfo.InvariantCulture, - "{0} -EncodedCommand {1}", processArguments, encodedCommand); + if (!File.Exists(exePath)) + { + string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WowComponentNotPresent, exePath); + throw new PSInvalidOperationException(message); + } + } } } - +#endif // 'WindowStyle' is used only if 'UseShellExecute' is 'true'. Since 'UseShellExecute' is set // to 'false' in our use, we can ignore the 'WindowStyle' setting in the initialization below. _startInfo = new ProcessStartInfo { - FileName = useWow64 ? psWow64Path : s_PSExePath, - Arguments = processArguments, + FileName = exePath, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, @@ -119,6 +98,36 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent LoadUserProfile = true, #endif }; +#if !UNIX + if (startingWindowsPowerShell51) + { + _startInfo.ArgumentList.Add("-Version"); + _startInfo.ArgumentList.Add("5.1"); + + // if starting Windows PowerShell, need to remove PowerShell specific segments of PSModulePath + _startInfo.Environment["PSModulePath"] = ModuleIntrinsics.GetWindowsPowerShellModulePath(); + } +#endif + _startInfo.ArgumentList.Add("-s"); + _startInfo.ArgumentList.Add("-NoLogo"); + _startInfo.ArgumentList.Add("-NoProfile"); + + if (!string.IsNullOrWhiteSpace(workingDirectory) && !startingWindowsPowerShell51) + { + _startInfo.ArgumentList.Add("-wd"); + _startInfo.ArgumentList.Add(workingDirectory); + } + + if (initializationScript != null) + { + var scriptBlockString = initializationScript.ToString(); + if (!string.IsNullOrEmpty(scriptBlockString)) + { + var encodedCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlockString)); + _startInfo.ArgumentList.Add("-EncodedCommand"); + _startInfo.ArgumentList.Add(encodedCommand); + } + } if (credential != null) { @@ -133,9 +142,20 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent } /// - /// + /// Initializes a new instance of the class. Initializes the underlying dotnet process class. /// - public PowerShellProcessInstance() : this(null, null, null, false) + /// 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) + { + } + + /// + /// Initializes a new instance of the class. Default initializes the underlying dotnet process class. + /// + public PowerShellProcessInstance() : this(powerShellVersion: null, credential: null, initializationScript: null, useWow64: false, workingDirectory: null) { } @@ -157,51 +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; } @@ -218,6 +236,7 @@ internal RunspacePool RunspacePool return _runspacePool; } } + set { lock (_syncObject) @@ -248,6 +267,7 @@ internal void Start() _started = true; Process.Exited += ProcessExited; } + Process.Start(); } diff --git a/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs b/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs index 17001670df0..1d352448f29 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs @@ -1,29 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; using DWORD = System.UInt32; - namespace System.Management.Automation.Runspaces { /// - /// Runspace class for local runspace + /// Runspace class for local runspace. /// - internal sealed partial class LocalRunspace : RunspaceBase { /// - /// initialize default values of preference vars + /// Initialize default values of preference vars. /// - /// - /// Does not return a value - /// - /// - /// - + /// Does not return a value. private void InitializeDefaults() { SessionStateInternal ss = _engine.Context.EngineSessionState; @@ -34,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 3ee11cfd8ad..dcce30264ec 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -10,14 +9,14 @@ namespace System.Management.Automation using System.Management.Automation.Runspaces; /// - /// Defines a class which allows simple execution of commands from CLR languages + /// Defines a class which allows simple execution of commands from CLR languages. /// public class RunspaceInvoke : IDisposable { #region constructors /// - /// Runspace on which commands are invoked + /// Runspace on which commands are invoked. /// private Runspace _runspace; @@ -47,6 +46,7 @@ public RunspaceInvoke(Runspace runspace) { throw PSTraceSource.NewArgumentNullException("runspace"); } + _runspace = runspace; if (Runspace.DefaultRunspace == null) { @@ -59,21 +59,21 @@ public RunspaceInvoke(Runspace runspace) #region invoke /// - /// Invoke the specified script + /// Invoke the specified script. /// - /// msh script to invoke - /// Output of invocation + /// PowerShell script to invoke. + /// Output of invocation. public Collection Invoke(string script) { return Invoke(script, null); } /// - /// Invoke the specified script and passes specified input to the script + /// Invoke the specified script and passes specified input to the script. /// - /// msh script to invoke - /// input to script - /// Output of invocation + /// PowerShell script to invoke. + /// Input to script. + /// Output of invocation. public Collection Invoke(string script, IEnumerable input) { if (_disposed == true) @@ -85,6 +85,7 @@ public Collection Invoke(string script, IEnumerable input) { throw PSTraceSource.NewArgumentNullException("script"); } + Pipeline p = _runspace.CreatePipeline(script); return p.Invoke(input); } @@ -92,10 +93,10 @@ public Collection Invoke(string script, IEnumerable input) /// /// Invoke the specified script and passes specified input to the script. /// - /// msh script to invoke - /// input to script - /// this gets errors from script - /// output of invocation + /// PowerShell script to invoke. + /// Input to script. + /// This gets errors from script. + /// Output of invocation. /// /// is the non-terminating error stream /// from the command. @@ -113,6 +114,7 @@ public Collection Invoke(string script, IEnumerable input, out IList e { throw PSTraceSource.NewArgumentNullException("script"); } + Pipeline p = _runspace.CreatePipeline(script); Collection output = p.Invoke(input); // 2004/06/30-JonN was ReadAll() which was non-blocking @@ -125,12 +127,12 @@ public Collection Invoke(string script, IEnumerable input, out IList e #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; /// - /// Dispose underlying Runspace + /// Dispose underlying Runspace. /// public void Dispose() { @@ -152,13 +154,10 @@ protected virtual void Dispose(bool disposing) _runspace = null; } } + _disposed = true; } #endregion IDisposable Members } } - - - - diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 33966d90579..e7cfb888a88 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs @@ -1,13 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; -using PSHost = System.Management.Automation.Host.PSHost; +using System.Collections.ObjectModel; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces.Internal; -using System.Collections.ObjectModel; using System.Runtime.Serialization; +using System.Threading; + +using PSHost = System.Management.Automation.Host.PSHost; namespace System.Management.Automation.Runspaces { @@ -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 { /// @@ -59,7 +58,7 @@ public InvalidRunspacePoolStateException(string message, Exception innerExceptio /// Initializes a new instance of the InvalidRunspacePoolStateException /// with a specified error message and current and expected state. /// - /// The message that describes the error. + /// The message that describes the error. /// Current state of runspace pool. /// Expected state of the runspace pool. internal InvalidRunspacePoolStateException @@ -74,7 +73,6 @@ RunspacePoolState expectedState _currentState = currentState; } - #region ISerializable Members // No need to implement GetObjectData @@ -92,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 @@ -127,7 +126,7 @@ public RunspacePoolState ExpectedState } /// - /// Converts the current to an InvalidRunspaceStateException + /// Converts the current to an InvalidRunspaceStateException. /// internal InvalidRunspaceStateException ToInvalidRunspaceStateException() { @@ -140,7 +139,7 @@ internal InvalidRunspaceStateException ToInvalidRunspaceStateException() } /// - /// Converts a RunspacePoolState to a RunspaceState + /// Converts a RunspacePoolState to a RunspaceState. /// private static RunspaceState RunspacePoolStateToRunspaceState(RunspacePoolState state) { @@ -183,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 @@ -200,7 +199,7 @@ private static RunspaceState RunspacePoolStateToRunspaceState(RunspacePoolState public enum RunspacePoolState { /// - /// Beginning state upon creation + /// Beginning state upon creation. /// BeforeOpen = 0, /// @@ -216,7 +215,7 @@ public enum RunspacePoolState /// Closed = 3, /// - /// The RunspacePool is being closed + /// The RunspacePool is being closed. /// Closing = 4, /// @@ -225,17 +224,17 @@ public enum RunspacePoolState Broken = 5, /// - /// The RunspacePool is being disconnected + /// The RunspacePool is being disconnected. /// Disconnecting = 6, /// - /// The RunspacePool has been disconnected + /// The RunspacePool has been disconnected. /// Disconnected = 7, /// - /// The RunspacePool is being connected + /// The RunspacePool is being connected. /// Connecting = 8, } @@ -249,7 +248,7 @@ public sealed class RunspacePoolStateChangedEventArgs : EventArgs #region Constructors /// - /// Constructor + /// Constructor. /// /// /// state to raise the event with. @@ -260,7 +259,6 @@ internal RunspacePoolStateChangedEventArgs(RunspacePoolState state) } /// - /// /// /// internal RunspacePoolStateChangedEventArgs(RunspacePoolStateInfo stateInfo) @@ -364,7 +362,7 @@ public enum RunspacePoolCapability #region AsyncResult /// - /// Encapsulated the AsyncResult for pool's Open/Close async operations + /// Encapsulated the AsyncResult for pool's Open/Close async operations. /// internal sealed class RunspacePoolAsyncResult : AsyncResult { @@ -375,7 +373,7 @@ internal sealed class RunspacePoolAsyncResult : AsyncResult #region Constructor /// - /// Constructor + /// Constructor. /// /// /// Instance Id of the pool creating this instance @@ -402,8 +400,8 @@ internal RunspacePoolAsyncResult(Guid ownerId, AsyncCallback callback, object st #region Internal Properties /// - /// true if AsyncResult monitors Async Open. - /// false otherwise + /// True if AsyncResult monitors Async Open. + /// false otherwise. /// internal bool IsAssociatedWithAsyncOpen { get; } @@ -411,7 +409,7 @@ internal RunspacePoolAsyncResult(Guid ownerId, AsyncCallback callback, object st } /// - /// Encapsulated the results of a RunspacePool.BeginGetRunspace method + /// Encapsulated the results of a RunspacePool.BeginGetRunspace method. /// internal sealed class GetRunspaceAsyncResult : AsyncResult { @@ -424,7 +422,7 @@ internal sealed class GetRunspaceAsyncResult : AsyncResult #region Constructor /// - /// Constructor + /// Constructor. /// /// /// Instance Id of the pool creating this instance @@ -466,6 +464,7 @@ internal bool IsActive return _isActive; } } + set { lock (SyncObject) @@ -505,10 +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(); } @@ -700,7 +702,7 @@ public InitialSessionState InitialSessionState } /// - /// Connection information for remote RunspacePools, null for local RunspacePools + /// Connection information for remote RunspacePools, null for local RunspacePools. /// public RunspaceConnectionInfo ConnectionInfo { @@ -716,6 +718,7 @@ public RunspaceConnectionInfo ConnectionInfo public TimeSpan CleanupInterval { get { return _internalPool.CleanupInterval; } + set { _internalPool.CleanupInterval = value; } } @@ -740,15 +743,14 @@ public event EventHandler StateChanged { lock (_syncObject) { - bool firstEntry = (null == InternalStateChanged); + bool firstEntry = (InternalStateChanged == null); InternalStateChanged += value; if (firstEntry) { // call any event handlers on this object, replacing the // internalPool sender with 'this' since receivers // are expecting a RunspacePool. - _internalPool.StateChanged += - new EventHandler(OnStateChanged); + _internalPool.StateChanged += OnStateChanged; } } } @@ -758,10 +760,9 @@ public event EventHandler StateChanged lock (_syncObject) { InternalStateChanged -= value; - if (null == InternalStateChanged) + if (InternalStateChanged == null) { - _internalPool.StateChanged -= - new EventHandler(OnStateChanged); + _internalPool.StateChanged -= OnStateChanged; } } } @@ -792,7 +793,7 @@ private void OnStateChanged(object source, RunspacePoolStateChangedEventArgs arg } /// - /// Event raised when one of the runspaces in the pool forwards an event to this instance + /// Event raised when one of the runspaces in the pool forwards an event to this instance. /// internal event EventHandler ForwardEvent { @@ -826,7 +827,7 @@ internal event EventHandler ForwardEvent } /// - /// Pass thru of the ForwardEvent event from the internal pool + /// Pass thru of the ForwardEvent event from the internal pool. /// private void OnInternalPoolForwardEvent(object sender, PSEventArgs e) { @@ -834,16 +835,11 @@ private void OnInternalPoolForwardEvent(object sender, PSEventArgs e) } /// - /// Raises the ForwardEvent event + /// Raises the ForwardEvent event. /// private void OnEventForwarded(PSEventArgs e) { - EventHandler eh = InternalForwardEvent; - - if (eh != null) - { - eh(this, e); - } + InternalForwardEvent?.Invoke(this, e); } /// @@ -855,7 +851,7 @@ internal event EventHandler RunspaceCreated { lock (_syncObject) { - bool firstEntry = (null == InternalRunspaceCreated); + bool firstEntry = (InternalRunspaceCreated == null); InternalRunspaceCreated += value; if (firstEntry) { @@ -872,7 +868,7 @@ internal event EventHandler RunspaceCreated lock (_syncObject) { InternalRunspaceCreated -= value; - if (null == InternalRunspaceCreated) + if (InternalRunspaceCreated == null) { _internalPool.RunspaceCreated -= OnRunspaceCreated; } @@ -1007,10 +1003,10 @@ public Collection CreateDisconnectedPowerShells() return _internalPool.CreateDisconnectedPowerShells(this); } - /// + /// /// Returns RunspacePool capabilities. /// - /// RunspacePoolCapability + /// RunspacePoolCapability. public RunspacePoolCapability GetCapabilities() { return _internalPool.GetCapabilities(); @@ -1203,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(); } /// @@ -1233,7 +1227,7 @@ public PSPrimitiveDictionary GetApplicationPrivateData() #region Internal API /// - /// This property determines whether a new thread is created for each invocation + /// This property determines whether a new thread is created for each invocation. /// /// /// Any updates to the value of this property must be done before the RunspacePool is opened @@ -1259,9 +1253,8 @@ public PSThreadOptions ThreadOptions } } -#if !CORECLR // No ApartmentState In CoreCLR /// - /// ApartmentState of the thread used to execute commands within this RunspacePool + /// ApartmentState of the thread used to execute commands within this RunspacePool. /// /// /// Any updates to the value of this property must be done before the RunspacePool is opened @@ -1286,7 +1279,6 @@ public ApartmentState ApartmentState _internalPool.ApartmentState = value; } } -#endif /// /// Gets Runspace asynchronously from the runspace pool. The caller @@ -1361,13 +1353,13 @@ internal void ReleaseRunspace(Runspace runspace) } /// - /// Indicates whether the RunspacePool is a remote one + /// Indicates whether the RunspacePool is a remote one. /// internal bool IsRemote { get; } = false; /// /// RemoteRunspacePoolInternal associated with this - /// runspace pool + /// runspace pool. /// internal RemoteRunspacePoolInternal RemoteRunspacePoolInternal { diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index 4b1096c40de..1b20fc4c85f 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -1,22 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation.Internal; +using System.Management.Automation.Security; using System.Management.Automation.Tracing; using System.Threading; -using PSHost = System.Management.Automation.Host.PSHost; -using System.Management.Automation.Security; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Internal; -using System.Collections.ObjectModel; +using PSHost = System.Management.Automation.Host.PSHost; 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 @@ -76,7 +76,7 @@ public RunspacePoolInternal(int minRunspaces, { if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } this.host = host; @@ -121,21 +121,18 @@ public RunspacePoolInternal(int minRunspaces, { if (initialSessionState == null) { - throw PSTraceSource.NewArgumentNullException("initialSessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(initialSessionState)); } if (host == null) { - throw PSTraceSource.NewArgumentNullException("host"); + throw PSTraceSource.NewArgumentNullException(nameof(host)); } _initialSessionState = initialSessionState.Clone(); this.host = host; ThreadOptions = initialSessionState.ThreadOptions; -#if !CORECLR - // No ApartmentState In CoreCLR this.ApartmentState = initialSessionState.ApartmentState; -#endif pool = new Stack(); runspaceRequestQueue = new Queue(); ultimateRequestQueue = new Queue(); @@ -143,7 +140,7 @@ public RunspacePoolInternal(int minRunspaces, /// /// Constructor for doing common initialization between - /// this class and its derivatives + /// this class and its derivatives. /// /// /// The maximum number of Runspaces that can exist in this pool. @@ -157,17 +154,17 @@ protected RunspacePoolInternal(int minRunspaces, int maxRunspaces) { if (maxRunspaces < 1) { - throw PSTraceSource.NewArgumentException("maxRunspaces", RunspacePoolStrings.MaxPoolLessThan1); + throw PSTraceSource.NewArgumentException(nameof(maxRunspaces), RunspacePoolStrings.MaxPoolLessThan1); } if (minRunspaces < 1) { - throw PSTraceSource.NewArgumentException("minRunspaces", RunspacePoolStrings.MinPoolLessThan1); + throw PSTraceSource.NewArgumentException(nameof(minRunspaces), RunspacePoolStrings.MinPoolLessThan1); } if (minRunspaces > maxRunspaces) { - throw PSTraceSource.NewArgumentException("minRunspaces", RunspacePoolStrings.MinPoolGreaterThanMaxPool); + throw PSTraceSource.NewArgumentException(nameof(minRunspaces), RunspacePoolStrings.MinPoolGreaterThanMaxPool); } maxPoolSz = maxRunspaces; @@ -181,7 +178,7 @@ protected RunspacePoolInternal(int minRunspaces, int maxRunspaces) } /// - /// default constructor + /// Default constructor. /// internal RunspacePoolInternal() { } @@ -234,10 +231,7 @@ internal virtual PSPrimitiveDictionary GetApplicationPrivateData() { lock (this.syncObject) { - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); } } @@ -264,7 +258,7 @@ public InitialSessionState InitialSessionState } /// - /// the connection associated with this runspace pool + /// The connection associated with this runspace pool. /// public virtual RunspaceConnectionInfo ConnectionInfo { @@ -279,7 +273,11 @@ public virtual RunspaceConnectionInfo ConnectionInfo /// public TimeSpan CleanupInterval { - get { return _cleanupInterval; } + get + { + return _cleanupInterval; + } + set { lock (this.syncObject) @@ -312,7 +310,7 @@ public virtual RunspacePoolAvailability RunspacePoolAvailability public event EventHandler StateChanged; /// - /// Event raised when one of the runspaces in the pool forwards an event to this instance + /// Event raised when one of the runspaces in the pool forwards an event to this instance. /// public event EventHandler ForwardEvent; @@ -394,7 +392,7 @@ public virtual Collection CreateDisconnectedPowerShells(RunspacePool /// /// Returns RunspacePool capabilities. /// - /// RunspacePoolCapability + /// RunspacePoolCapability. public virtual RunspacePoolCapability GetCapabilities() { return RunspacePoolCapability.Default; @@ -409,7 +407,7 @@ public virtual RunspacePoolCapability GetCapabilities() /// runspace. /// This is currently supported *only* for remote runspaces. /// - /// True if successful + /// True if successful. internal virtual bool ResetRunspaceState() { throw new PSNotSupportedException(); @@ -618,18 +616,18 @@ public IAsyncResult BeginOpen(AsyncCallback callback, object state) /// public void EndOpen(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } RunspacePoolAsyncResult rsAsyncResult = asyncResult as RunspacePoolAsyncResult; - if ((null == rsAsyncResult) || + if ((rsAsyncResult == null) || (rsAsyncResult.OwnerId != instanceId) || (!rsAsyncResult.IsAssociatedWithAsyncOpen)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginOpen"); @@ -685,18 +683,18 @@ public virtual IAsyncResult BeginClose(AsyncCallback callback, object state) /// public virtual void EndClose(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } RunspacePoolAsyncResult rsAsyncResult = asyncResult as RunspacePoolAsyncResult; - if ((null == rsAsyncResult) || + if ((rsAsyncResult == null) || (rsAsyncResult.OwnerId != instanceId) || (rsAsyncResult.IsAssociatedWithAsyncOpen)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginClose"); @@ -727,7 +725,7 @@ public Runspace GetRunspace() // throw the exception that occurred while // processing the async operation - if (null != asyncResult.Exception) + if (asyncResult.Exception != null) { throw asyncResult.Exception; } @@ -754,9 +752,9 @@ public Runspace GetRunspace() /// public void ReleaseRunspace(Runspace runspace) { - if (null == runspace) + if (runspace == null) { - throw PSTraceSource.NewArgumentNullException("runspace"); + throw PSTraceSource.NewArgumentNullException(nameof(runspace)); } AssertPoolIsOpen(); @@ -816,12 +814,22 @@ public void ReleaseRunspace(Runspace runspace) } /// - /// Dispose off the current runspace pool + /// 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) { @@ -832,6 +840,7 @@ public virtual void Dispose(bool disposing) _initialSessionState = null; host = null; } + _isDisposed = true; } } @@ -849,15 +858,13 @@ public virtual void Dispose(bool disposing) /// internal PSThreadOptions ThreadOptions { get; set; } = PSThreadOptions.Default; -#if !CORECLR // No ApartmentState In CoreCLR /// - /// The value of this property is propagated to all the Runspaces in this pool + /// The value of this property is propagated to all the Runspaces in this pool. /// /// /// Any updates to the value of this property must be done before the RunspacePool is opened /// internal ApartmentState ApartmentState { get; set; } = Runspace.DefaultApartmentState; -#endif /// /// Gets Runspace asynchronously from the runspace pool. The caller @@ -894,17 +901,17 @@ internal IAsyncResult BeginGetRunspace( /// internal void CancelGetRunspace(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } GetRunspaceAsyncResult grsAsyncResult = asyncResult as GetRunspaceAsyncResult; - if ((null == grsAsyncResult) || (grsAsyncResult.OwnerId != instanceId)) + if ((grsAsyncResult == null) || (grsAsyncResult.OwnerId != instanceId)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginGetRunspace"); @@ -933,17 +940,17 @@ internal void CancelGetRunspace(IAsyncResult asyncResult) /// internal Runspace EndGetRunspace(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } GetRunspaceAsyncResult grsAsyncResult = asyncResult as GetRunspaceAsyncResult; - if ((null == grsAsyncResult) || (grsAsyncResult.OwnerId != instanceId)) + if ((grsAsyncResult == null) || (grsAsyncResult.OwnerId != instanceId)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginGetRunspace"); @@ -996,7 +1003,7 @@ protected virtual IAsyncResult CoreOpen(bool isAsync, AsyncCallback callback, if (isAsync) { AsyncResult asyncResult = new RunspacePoolAsyncResult(instanceId, callback, asyncState, true); - //Open pool in another thread + // Open pool in another thread ThreadPool.QueueUserWorkItem(new WaitCallback(OpenThreadProc), asyncResult); return asyncResult; } @@ -1075,7 +1082,7 @@ private void SetStateToBroken(Exception reason) } /// - /// Starting point for asynchronous thread + /// Starting point for asynchronous thread. /// /// /// asyncResult object @@ -1154,7 +1161,7 @@ private IAsyncResult CoreClose(bool isAsync, AsyncCallback callback, object asyn if (isAsync) { RunspacePoolAsyncResult asyncResult = new RunspacePoolAsyncResult(instanceId, callback, asyncState, false); - //Open pool in another thread + // Open pool in another thread ThreadPool.QueueUserWorkItem(new WaitCallback(CloseThreadProc), asyncResult); return asyncResult; } @@ -1208,9 +1215,9 @@ private void CloseThreadProc(object o) /// /// Raise state changed event based on the StateInfo - /// object + /// object. /// - /// state information object + /// State information object. protected void RaiseStateChangeEvent(RunspacePoolStateInfo stateInfo) { StateChanged.SafeInvoke(this, @@ -1249,25 +1256,20 @@ internal void AssertPoolIsOpen() /// protected Runspace CreateRunspace() { - Dbg.Assert(null != _initialSessionState, "_initialSessionState should not be null"); + Dbg.Assert(_initialSessionState != null, "_initialSessionState should not be null"); // TODO: exceptions thrown here need to be documented // runspace.Open() did not document all the exceptions. Runspace result = RunspaceFactory.CreateRunspaceFromSessionStateNoClone(host, _initialSessionState); result.ThreadOptions = this.ThreadOptions == PSThreadOptions.Default ? PSThreadOptions.ReuseThread : this.ThreadOptions; -#if !CORECLR // No ApartmentState In CoreCLR result.ApartmentState = this.ApartmentState; -#endif this.PropagateApplicationPrivateData(result); result.Open(); // Enforce the system lockdown policy if one is defined. - if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) - { - result.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; - } + Utils.EnforceSystemLockDownLanguageMode(result.ExecutionContext); result.Events.ForwardEvent += OnRunspaceForwardEvent; // this must be done after open since open initializes the ExecutionContext @@ -1290,14 +1292,14 @@ protected Runspace CreateRunspace() } /// - /// Cleans/Closes the runspace + /// Cleans/Closes the runspace. /// /// /// Runspace to be closed/cleaned /// protected void DestroyRunspace(Runspace runspace) { - Dbg.Assert(null != runspace, "Runspace cannot be null"); + Dbg.Assert(runspace != null, "Runspace cannot be null"); runspace.Events.ForwardEvent -= OnRunspaceForwardEvent; // this must be done after open since open initializes the ExecutionContext runspace.Close(); runspace.Dispose(); @@ -1311,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. /// /// @@ -1340,7 +1342,7 @@ protected void CleanupCallback(object state) Runspace runspaceToDestroy = null; lock (pool) { - if (pool.Count <= 0) + if (pool.Count == 0) { break; // break from while } @@ -1366,7 +1368,7 @@ protected void CleanupCallback(object state) } /// - /// Close all the runspaces in the pool + /// Close all the runspaces in the pool. /// private void InternalClearAllResources() { @@ -1404,7 +1406,7 @@ private void InternalClearAllResources() runspaceList.Clear(); } - //Start from the most recent runspace. + // Start from the most recent runspace. for (int index = runspaceListCopy.Count - 1; index >= 0; index--) { // close runspaces suppress exceptions @@ -1518,7 +1520,7 @@ protected void ServicePendingRequests(object useCallingThreadState) try { - do + while (true) { lock (ultimateRequestQueue) { @@ -1583,7 +1585,7 @@ protected void ServicePendingRequests(object useCallingThreadState) ThreadPool.QueueUserWorkItem(new WaitCallback(runspaceRequester.DoComplete)); } } - } // end lock(ultimateRequestQueue) + } lock (runspaceRequestQueue) { @@ -1599,7 +1601,7 @@ protected void ServicePendingRequests(object useCallingThreadState) ultimateRequestQueue.Enqueue(runspaceRequestQueue.Dequeue()); } } - } while (true); + } endOuterWhile:; } finally @@ -1612,7 +1614,7 @@ protected void ServicePendingRequests(object useCallingThreadState) } } - if ((useCallingThread) && (null != runspaceRequester)) + if ((useCallingThread) && (runspaceRequester != null)) { // call DoComplete outside of the lock and finally..as the // DoComplete handler may handle the runspace in the same @@ -1623,13 +1625,13 @@ protected void ServicePendingRequests(object useCallingThreadState) /// /// Throws an exception if the runspace state is not - /// BeforeOpen + /// BeforeOpen. /// protected void AssertIfStateIsBeforeOpen() { if (stateInfo.State != RunspacePoolState.BeforeOpen) { - //Call fails if RunspacePoolState is not BeforeOpen. + // Call fails if RunspacePoolState is not BeforeOpen. InvalidRunspacePoolStateException e = new InvalidRunspacePoolStateException ( @@ -1641,23 +1643,18 @@ protected void AssertIfStateIsBeforeOpen() ); throw e; } - } // AssertIfStateIsBeforeOpen + } /// - /// Raises the ForwardEvent event + /// Raises the ForwardEvent event. /// protected virtual void OnForwardEvent(PSEventArgs e) { - EventHandler eh = this.ForwardEvent; - - if (eh != null) - { - eh(this, e); - } + this.ForwardEvent?.Invoke(this, e); } /// - /// Forward runspace events to the pool's event queue + /// Forward runspace events to the pool's event queue. /// private void OnRunspaceForwardEvent(object sender, PSEventArgs e) { diff --git a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs index 17d17cb90f4..a7acbd0760b 100644 --- a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs +++ b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs @@ -1,10 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Host; using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Internal.Host @@ -13,11 +12,8 @@ internal partial class InternalHostUserInterface : PSHostUserInterface { /// - /// - /// See base class - /// + /// See base class. /// - public override PSCredential PromptForCredential @@ -35,11 +31,8 @@ string targetName } /// - /// - /// See base class - /// + /// See base class. /// - public override PSCredential PromptForCredential @@ -64,13 +57,14 @@ PSCredentialUIOptions options } catch (PipelineStoppedException) { - //PipelineStoppedException is thrown by host when it wants - //to stop the pipeline. + // 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(); } @@ -78,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 90a6b90c17f..f144ff7f506 100644 --- a/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs +++ b/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation.Runspaces { @@ -23,12 +22,12 @@ internal abstract class PipelineBase : Pipeline #region constructors /// - /// Create a pipeline initialized with a command string + /// Create a pipeline initialized with a command string. /// - /// The associated Runspace/>. - /// command string - /// if true, add pipeline to history - /// True for nested pipeline + /// The associated Runspace/> + /// Command string. + /// If true, add pipeline to history. + /// True for nested pipeline. /// /// Command is null and add to history is true /// @@ -37,7 +36,7 @@ protected PipelineBase(Runspace runspace, string command, bool addToHistory, boo { Initialize(runspace, command, addToHistory, isNested); - //Initialize streams + // Initialize streams InputStream = new ObjectStream(); OutputStream = new ObjectStream(); ErrorStream = new ObjectStream(); @@ -88,17 +87,17 @@ protected PipelineBase(Runspace runspace, PSInformationalBuffers infoBuffers) : base(runspace, command) { - Dbg.Assert(null != inputStream, "Caller Should validate inputstream parameter"); - Dbg.Assert(null != outputStream, "Caller Should validate outputStream parameter"); - Dbg.Assert(null != errorStream, "Caller Should validate errorStream parameter"); - Dbg.Assert(null != infoBuffers, "Caller Should validate informationalBuffers parameter"); - Dbg.Assert(null != command, "Command cannot be null"); + Dbg.Assert(inputStream != null, "Caller Should validate inputstream parameter"); + Dbg.Assert(outputStream != null, "Caller Should validate outputStream parameter"); + Dbg.Assert(errorStream != null, "Caller Should validate errorStream parameter"); + Dbg.Assert(infoBuffers != null, "Caller Should validate informationalBuffers parameter"); + Dbg.Assert(command != null, "Command cannot be null"); // Since we are constructing this pipeline using a commandcollection we dont need // to add cmd to CommandCollection again (Initialize does this).. because of this // I am handling history here.. Initialize(runspace, null, false, isNested); - if (true == addToHistory) + if (addToHistory) { // get command text for history.. string cmdText = command.GetCommandStringForHistory(); @@ -106,7 +105,7 @@ protected PipelineBase(Runspace runspace, AddToHistory = addToHistory; } - //Initialize streams + // Initialize streams InputStream = inputStream; OutputStream = outputStream; ErrorStream = errorStream; @@ -114,9 +113,9 @@ protected PipelineBase(Runspace runspace, } /// - /// Copy constructor to support cloning + /// Copy constructor to support cloning. /// - /// The source pipeline + /// The source pipeline. /// /// The copy constructor's intent is to support the scenario /// where a host needs to run the same set of commands multiple @@ -127,10 +126,11 @@ protected PipelineBase(PipelineBase pipeline) : this(pipeline.Runspace, null, false, pipeline.IsNested) { // NTRAID#Windows Out Of Band Releases-915851-2005/09/13 - if (null == pipeline) + if (pipeline == null) { - throw PSTraceSource.NewArgumentNullException("pipeline"); + throw PSTraceSource.NewArgumentNullException(nameof(pipeline)); } + if (pipeline._disposed) { throw PSTraceSource.NewObjectDisposedException("pipeline"); @@ -176,7 +176,7 @@ internal Runspace GetRunspace() private bool _isNested; /// - /// Is this pipeline nested + /// Is this pipeline nested. /// public override bool IsNested { @@ -187,7 +187,7 @@ public override bool IsNested } /// - /// Is this a pulse pipeline (created by the EventManager) + /// Is this a pulse pipeline (created by the EventManager) /// internal bool IsPulsePipeline { get; set; } @@ -205,7 +205,7 @@ public override PipelineStateInfo PipelineStateInfo { lock (SyncRoot) { - //Note:We do not return internal state. + // Note:We do not return internal state. return _pipelineStateInfo.Clone(); } } @@ -282,11 +282,11 @@ 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) { - //Is pipeline already in stopping state. + // Is pipeline already in stopping state. bool alreadyStopping = false; lock (SyncRoot) { @@ -297,14 +297,14 @@ private void CoreStop(bool syncCall) SetPipelineState(PipelineState.Stopped); break; - //If pipeline execution has failed or completed or - //stoped, return silently. + // If pipeline execution has failed or completed or + // stopped, return silently. case PipelineState.Stopped: case PipelineState.Completed: case PipelineState.Failed: return; - //If pipeline is in Stopping state, ignore the second - //stop. + // If pipeline is in Stopping state, ignore the second + // stop. case PipelineState.Stopping: alreadyStopping = true; break; @@ -315,44 +315,45 @@ private void CoreStop(bool syncCall) } } - //If pipeline is already in stopping state. Wait for pipeline - //to finish. We do need to raise any events here as no - //change of state has occurred. + // If pipeline is already in stopping state. Wait for pipeline + // to finish. We do need to raise any events here as no + // change of state has occurred. if (alreadyStopping) { if (syncCall) { PipelineFinishedEvent.WaitOne(); } + return; } - //Raise the event outside the lock + // Raise the event outside the lock RaisePipelineStateEvents(); - //A pipeline can be stoped 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. + // 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) { if (PipelineState == PipelineState.Stopped) { - //Note:if we have reached here, Stopped state was set - //in PipelineState.NotStarted case above. Only other - //way Stopped can be set when this method calls - //StopHelper below + // Note:if we have reached here, Stopped state was set + // in PipelineState.NotStarted case above. Only other + // way Stopped can be set when this method calls + // StopHelper below return; } } - //Start stop operation in derived class + // Start stop operation in derived class ImplementStop(syncCall); } /// - /// Stop execution of pipeline + /// Stop execution of pipeline. /// - /// If false, call is asynchronous + /// If false, call is asynchronous. protected abstract void ImplementStop(bool syncCall); #endregion stop @@ -365,7 +366,7 @@ private void CoreStop(bool syncCall) /// /// an array of input objects to pass to the pipeline. /// Array may be empty but may not be null - /// An array of zero or more result objects + /// An array of zero or more result objects. /// Caller of synchronous exectute should not close /// input objectWriter. Synchronous invoke will always close the input /// objectWriter. @@ -383,14 +384,14 @@ public override Collection Invoke(IEnumerable input) CoreInvoke(input, true); - //Wait for pipeline to finish execution + // Wait for pipeline to finish execution PipelineFinishedEvent.WaitOne(); if (SyncInvokeCall) { - //Raise the pipeline completion events. These events are set in - //pipeline execution thread. However for Synchronous execution - //we raise the event in the main thread. + // Raise the pipeline completion events. These events are set in + // pipeline execution thread. However for Synchronous execution + // we raise the event in the main thread. RaisePipelineStateEvents(); } @@ -410,13 +411,13 @@ public override Collection Invoke(IEnumerable input) throw PipelineStateInfo.Reason; } - //Execution completed successfully + // Execution completed successfully // 2004/06/30-JonN was ReadAll() which was non-blocking return Output.NonBlockingRead(Int32.MaxValue); } /// - /// Invoke the pipeline asynchronously + /// Invoke the pipeline asynchronously. /// /// /// Results are returned through the reader. @@ -490,62 +491,65 @@ 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. + // Method is called from synchronous invoke. if (input != null) { - //TO-DO-Add a test make sure that ObjectDisposed - //exception is thrown - //Write input data in to inputStream and close the input - //pipe. If Input stream is already closed an - //ObjectDisposed exception will be thrown + // TO-DO-Add a test make sure that ObjectDisposed + // exception is thrown + // Write input data in to inputStream and close the input + // pipe. If Input stream is already closed an + // ObjectDisposed exception will be thrown foreach (object temp in input) { InputStream.Write(temp); } } + InputStream.Close(); } SyncInvokeCall = syncCall; - //Create event which will be signalled when pipeline execution - //is completed/failed/stoped. - //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 - //where Close is called after pipeline is added to list of - //running pipeline but before event is created. + // Create event which will be signalled when pipeline execution + // 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 + // where Close is called after pipeline is added to list of + // running pipeline but before event is created. PipelineFinishedEvent = new ManualResetEvent(false); - //1) Do the check to ensure that pipeline no other + // 1) Do the check to ensure that pipeline no other // pipeline is running. - //2) Runspace object maintains a list of pipelines in - //execution. Add this pipeline to the list. + // 2) Runspace object maintains a list of pipelines in + // execution. Add this pipeline to the list. RunspaceBase.DoConcurrentCheckAndAddToRunningPipelines(this, syncCall); - //Note: Set PipelineState to Running only after adding pipeline to list - //of pipelines in execution. AddForExecution checks that runspace is in - //state where pipeline can be run. - //StartPipelineExecution raises this event. See Windows Bug 1160481 for - //more details. + // Note: Set PipelineState to Running only after adding pipeline to list + // of pipelines in execution. AddForExecution checks that runspace is in + // state where pipeline can be run. + // StartPipelineExecution raises this event. See Windows Bug 1160481 for + // more details. SetPipelineState(PipelineState.Running); } try { - //Let the derived class start the pipeline execution. + // Let the derived class start the pipeline execution. StartPipelineExecution(); } catch (Exception exception) { - //If we fail in any of the above three steps, set the correct states. + // If we fail in any of the above three steps, set the correct states. RunspaceBase.RemoveFromRunningPipelineList(this); SetPipelineState(PipelineState.Failed, exception); - //Note: we are not raising the events in this case. However this is - //fine as user is getting the exception. + // Note: we are not raising the events in this case. However this is + // fine as user is getting the exception. throw; } } @@ -566,7 +570,6 @@ internal override void InvokeAsyncAndDisconnect() #region concurrent pipeline check - private bool _performNestedCheck = true; /// @@ -601,8 +604,8 @@ internal bool PerformNestedCheck /// /// True if method is called from Invoke, false /// if called from InvokeAsync - /// The sync object on which the lock is acquired - /// True if the method is invoked in a critical section + /// The sync object on which the lock is acquired. + /// True if the method is invoked in a critical section. /// /// 1) A pipeline is already executing. Pipeline cannot execute /// concurrently. @@ -615,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) { @@ -662,7 +665,7 @@ internal void DoConcurrentCheck(bool syncCall, object syncObject, bool isInLock) { if (_performNestedCheck) { - if (syncCall == false) + if (!syncCall) { throw PSTraceSource.NewInvalidOperationException( RunspaceStrings.NestedPipelineInvokeAsync); @@ -687,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); @@ -746,7 +749,7 @@ protected PipelineState PipelineState } /// - /// This returns true if pipeline state is Completed, Failed or Stopped + /// This returns true if pipeline state is Completed, Failed or Stopped. /// /// protected bool IsPipelineFinished() @@ -757,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. @@ -765,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) { @@ -782,7 +785,7 @@ public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvai /// /// Sets the new execution state. /// - /// the new state + /// The new state. /// /// An exception indicating that state change is the result of an error, /// otherwise; null. @@ -800,12 +803,12 @@ protected void SetPipelineState(PipelineState state, Exception reason) { _pipelineStateInfo = new PipelineStateInfo(state, reason); - //Add _pipelineStateInfo to _executionEventQueue. - //RaisePipelineStateEvents will raise event for each item - //in this queue. - //Note:We are doing clone here instead of passing the member - //_pipelineStateInfo because we donot want outside - //to change pipeline state. + // Add _pipelineStateInfo to _executionEventQueue. + // RaisePipelineStateEvents will raise event for each item + // in this queue. + // Note:We are doing clone here instead of passing the member + // _pipelineStateInfo because we donot want outside + // to change pipeline state. RunspaceAvailability previousAvailability = _runspace.RunspaceAvailability; _runspace.UpdateRunspaceAvailability(_pipelineStateInfo.State, false); @@ -820,9 +823,9 @@ protected void SetPipelineState(PipelineState state, Exception reason) } /// - /// Set the new execution state + /// Set the new execution state. /// - /// the new state + /// The new state. protected void SetPipelineState(PipelineState state) { SetPipelineState(state, null); @@ -849,9 +852,9 @@ protected void RaisePipelineStateEvents() } else { - //Clear the events if there are no EventHandlers. This - //ensures that events do not get called for state - //changes prior to their registration. + // Clear the events if there are no EventHandlers. This + // ensures that events do not get called for state + // changes prior to their registration. _executionEventQueue.Clear(); } } @@ -869,8 +872,8 @@ protected void RaisePipelineStateEvents() // this is shipped as part of V1. So disabling the warning here. #pragma warning disable 56500 - //Exception raised in the eventhandler are not error in pipeline. - //silently ignore them. + // Exception raised in the eventhandler are not error in pipeline. + // silently ignore them. if (stateChanged != null) { try @@ -888,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; } @@ -920,7 +923,7 @@ private set { Dbg.Assert(value != null, "ErrorStream cannot be null"); _errorStream = value; - _errorStream.DataReady += new EventHandler(OnErrorStreamDataReady); + _errorStream.DataReady += OnErrorStreamDataReady; } } @@ -932,7 +935,7 @@ private void OnErrorStreamDataReady(object sender, EventArgs e) // unsubscribe from further event notifications as // this notification is suffice to say there is an // error. - _errorStream.DataReady -= new EventHandler(OnErrorStreamDataReady); + _errorStream.DataReady -= OnErrorStreamDataReady; SetHadErrors(true); } } @@ -958,16 +961,16 @@ private void OnErrorStreamDataReady(object sender, EventArgs e) #region history - //History information is internal so that Pipeline serialization code - //can access it. + // History information is internal so that Pipeline serialization code + // can access it. /// - /// if true, this pipeline is added in history + /// If true, this pipeline is added in history. /// internal bool AddToHistory { get; set; } /// - /// String which is added in the history + /// String which is added in the history. /// /// This needs to be internal so that it can be replaced /// by invoke-cmd to place correct string in history. @@ -996,7 +999,7 @@ private void Initialize(Runspace runspace, string command, bool addToHistory, bo if (addToHistory && command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } if (command != null) @@ -1019,9 +1022,8 @@ private RunspaceBase RunspaceBase } } - /// - /// Object used for synchronization + /// Object used for synchronization. /// protected internal object SyncRoot { get; } = new object(); @@ -1030,7 +1032,7 @@ private RunspaceBase RunspaceBase #region IDisposable Members /// - /// Set to true when object is disposed + /// Set to true when object is disposed. /// private bool _disposed; @@ -1044,7 +1046,7 @@ protected override { try { - if (_disposed == false) + if (!_disposed) { _disposed = true; if (disposing) @@ -1052,7 +1054,7 @@ protected override InputStream.Close(); OutputStream.Close(); - _errorStream.DataReady -= new EventHandler(OnErrorStreamDataReady); + _errorStream.DataReady -= OnErrorStreamDataReady; _errorStream.Close(); _executionEventQueue.Clear(); diff --git a/src/System.Management.Automation/engine/interpreter/AddInstruction.cs b/src/System.Management.Automation/engine/interpreter/AddInstruction.cs index 0a5f4e46443..2f7ada9dcfd 100644 --- a/src/System.Management.Automation/engine/interpreter/AddInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/AddInstruction.cs @@ -14,15 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; namespace System.Management.Automation.Interpreter { internal abstract class AddInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private AddInstruction() @@ -119,7 +119,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l + (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l + (double)r; frame.StackIndex--; return +1; } @@ -127,17 +127,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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; @@ -152,9 +152,10 @@ public override string ToString() internal abstract class AddOvfInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private AddOvfInstruction() @@ -251,7 +252,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l + (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l + (double)r; frame.StackIndex--; return +1; } @@ -259,17 +260,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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/ArrayOperations.cs b/src/System.Management.Automation/engine/interpreter/ArrayOperations.cs index f85482522c5..8d8ffd8468f 100644 --- a/src/System.Management.Automation/engine/interpreter/ArrayOperations.cs +++ b/src/System.Management.Automation/engine/interpreter/ArrayOperations.cs @@ -25,6 +25,7 @@ internal NewArrayInitInstruction(int elementCount) } public override int ConsumedStack { get { return _elementCount; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -34,6 +35,7 @@ public override int Run(InterpretedFrame frame) { array[i] = (TElement)frame.Pop(); } + frame.Push(array); return +1; } @@ -44,6 +46,7 @@ internal sealed class NewArrayInstruction : Instruction internal NewArrayInstruction() { } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -66,6 +69,7 @@ internal NewArrayBoundsInstruction(Type elementType, int rank) } public override int ConsumedStack { get { return _rank; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -75,6 +79,7 @@ public override int Run(InterpretedFrame frame) { lengths[i] = (int)frame.Pop(); } + var array = Array.CreateInstance(_elementType, lengths); frame.Push(array); return +1; @@ -86,6 +91,7 @@ internal sealed class GetArrayItemInstruction : Instruction internal GetArrayItemInstruction() { } public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -107,6 +113,7 @@ internal sealed class SetArrayItemInstruction : Instruction internal SetArrayItemInstruction() { } public override int ConsumedStack { get { return 3; } } + public override int ProducedStack { get { return 0; } } public override int Run(InterpretedFrame frame) diff --git a/src/System.Management.Automation/engine/interpreter/BranchLabel.cs b/src/System.Management.Automation/engine/interpreter/BranchLabel.cs index e5fe6f9e09d..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; @@ -34,7 +34,7 @@ public RuntimeLabel(int index, int continuationStackDepth, int stackDepth) public override string ToString() { - return String.Format(CultureInfo.InvariantCulture, "->{0} C({1}) S({2})", Index, ContinuationStackDepth, StackDepth); + return string.Format(CultureInfo.InvariantCulture, "->{0} C({1}) S({2})", Index, ContinuationStackDepth, StackDepth); } } @@ -59,6 +59,7 @@ public BranchLabel() internal int LabelIndex { get { return _labelIndex; } + set { _labelIndex = value; } } @@ -85,7 +86,7 @@ internal RuntimeLabel ToRuntimeLabel() internal void Mark(InstructionList instructions) { - //ContractUtils.Requires(_targetIndex == UnknownIndex && _stackDepth == UnknownDepth && _continuationStackDepth == UnknownDepth); + // ContractUtils.Requires(_targetIndex == UnknownIndex && _stackDepth == UnknownDepth && _continuationStackDepth == UnknownDepth); _stackDepth = instructions.CurrentStackDepth; _continuationStackDepth = instructions.CurrentContinuationsDepth; @@ -97,6 +98,7 @@ internal void Mark(InstructionList instructions) { FixupBranch(instructions, branchIndex); } + _forwardBranchFixups = null; } } @@ -108,10 +110,8 @@ internal void AddBranch(InstructionList instructions, int branchIndex) if (_targetIndex == UnknownIndex) { - if (_forwardBranchFixups == null) - { - _forwardBranchFixups = new List(); - } + _forwardBranchFixups ??= new List(); + _forwardBranchFixups.Add(branchIndex); } else diff --git a/src/System.Management.Automation/engine/interpreter/CallInstruction.Generated.cs b/src/System.Management.Automation/engine/interpreter/CallInstruction.Generated.cs index 4f34eff30fd..b63bde344bc 100644 --- a/src/System.Management.Automation/engine/interpreter/CallInstruction.Generated.cs +++ b/src/System.Management.Automation/engine/interpreter/CallInstruction.Generated.cs @@ -68,14 +68,23 @@ public virtual object Invoke(params object[] args) { } public virtual object Invoke() { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3, object arg4) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3, object arg4, object arg5) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7) { throw new InvalidOperationException(); } + public virtual object Invoke(object arg0, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6, object arg7, object arg8) { throw new InvalidOperationException(); } /// @@ -98,31 +107,31 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterInfo[] pi) return new ActionCallInstruction(target); } - var typeInfo = t.GetTypeInfo(); - if (typeInfo.IsEnum) return SlowCreate(target, pi); + if (t.IsEnum) return SlowCreate(target, pi); switch (t.GetTypeCode()) { case TypeCode.Object: { - if (t != typeof(object) && (IndexIsNotReturnType(0, target, pi) || typeInfo.IsValueType)) { + if (t != typeof(object) && (IndexIsNotReturnType(0, target, pi) || t.IsValueType)) { // if we're on the return type relaxed delegates makes it ok to use object goto default; } - return FastCreate(target, pi); + + return FastCreate(target, pi); } case TypeCode.Int16: return FastCreate(target, pi); case TypeCode.Int32: return FastCreate(target, pi); case TypeCode.Int64: return FastCreate(target, pi); - case TypeCode.Boolean: return FastCreate(target, pi); - case TypeCode.Char: return FastCreate(target, pi); - case TypeCode.Byte: return FastCreate(target, pi); + case TypeCode.Boolean: return FastCreate(target, pi); + case TypeCode.Char: return FastCreate(target, pi); + case TypeCode.Byte: return FastCreate(target, pi); case TypeCode.Decimal: return FastCreate(target, pi); case TypeCode.DateTime: return FastCreate(target, pi); - case TypeCode.Double: return FastCreate(target, pi); + case TypeCode.Double: return FastCreate(target, pi); case TypeCode.Single: return FastCreate(target, pi); case TypeCode.UInt16: return FastCreate(target, pi); case TypeCode.UInt32: return FastCreate(target, pi); case TypeCode.UInt64: return FastCreate(target, pi); - case TypeCode.String: return FastCreate(target, pi); - case TypeCode.SByte: return FastCreate(target, pi); + case TypeCode.String: return FastCreate(target, pi); + case TypeCode.SByte: return FastCreate(target, pi); default: return SlowCreate(target, pi); } } @@ -133,25 +142,26 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterInfo[] if (target.ReturnType == typeof(void)) { return new ActionCallInstruction(target); } + return new FuncCallInstruction(target); } - var typeInfo = t.GetTypeInfo(); - if (typeInfo.IsEnum) return SlowCreate(target, pi); + if (t.IsEnum) return SlowCreate(target, pi); switch (t.GetTypeCode()) { case TypeCode.Object: { - if (t != typeof(object) && (IndexIsNotReturnType(1, target, pi) || typeInfo.IsValueType)) { + if (t != typeof(object) && (IndexIsNotReturnType(1, target, pi) || t.IsValueType)) { // if we're on the return type relaxed delegates makes it ok to use object goto default; } - return FastCreate(target, pi); + + return FastCreate(target, pi); } case TypeCode.Int16: return FastCreate(target, pi); case TypeCode.Int32: return FastCreate(target, pi); case TypeCode.Int64: return FastCreate(target, pi); - case TypeCode.Boolean: return FastCreate(target, pi); - case TypeCode.Char: return FastCreate(target, pi); - case TypeCode.Byte: return FastCreate(target, pi); + case TypeCode.Boolean: return FastCreate(target, pi); + case TypeCode.Char: return FastCreate(target, pi); + case TypeCode.Byte: return FastCreate(target, pi); case TypeCode.Decimal: return FastCreate(target, pi); case TypeCode.DateTime: return FastCreate(target, pi); case TypeCode.Double: return FastCreate(target, pi); @@ -159,8 +169,8 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterInfo[] case TypeCode.UInt16: return FastCreate(target, pi); case TypeCode.UInt32: return FastCreate(target, pi); case TypeCode.UInt64: return FastCreate(target, pi); - case TypeCode.String: return FastCreate(target, pi); - case TypeCode.SByte: return FastCreate(target, pi); + case TypeCode.String: return FastCreate(target, pi); + case TypeCode.SByte: return FastCreate(target, pi); default: return SlowCreate(target, pi); } } @@ -171,24 +181,24 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterIn if (target.ReturnType == typeof(void)) { return new ActionCallInstruction(target); } + return new FuncCallInstruction(target); } - var typeInfo = t.GetTypeInfo(); - if (typeInfo.IsEnum) return SlowCreate(target, pi); + if (t.IsEnum) return SlowCreate(target, pi); switch (t.GetTypeCode()) { case TypeCode.Object: { Debug.Assert(pi.Length == 2); - if (typeInfo.IsValueType) goto default; + if (t.IsValueType) goto default; - return new FuncCallInstruction(target); + return new FuncCallInstruction(target); } case TypeCode.Int16: return new FuncCallInstruction(target); case TypeCode.Int32: return new FuncCallInstruction(target); case TypeCode.Int64: return new FuncCallInstruction(target); - case TypeCode.Boolean: return new FuncCallInstruction(target); - case TypeCode.Char: return new FuncCallInstruction(target); - case TypeCode.Byte: return new FuncCallInstruction(target); + case TypeCode.Boolean: return new FuncCallInstruction(target); + case TypeCode.Char: return new FuncCallInstruction(target); + case TypeCode.Byte: return new FuncCallInstruction(target); case TypeCode.Decimal: return new FuncCallInstruction(target); case TypeCode.DateTime: return new FuncCallInstruction(target); case TypeCode.Double: return new FuncCallInstruction(target); @@ -196,8 +206,8 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterIn case TypeCode.UInt16: return new FuncCallInstruction(target); case TypeCode.UInt32: return new FuncCallInstruction(target); case TypeCode.UInt64: return new FuncCallInstruction(target); - case TypeCode.String: return new FuncCallInstruction(target); - case TypeCode.SByte: return new FuncCallInstruction(target); + case TypeCode.String: return new FuncCallInstruction(target); + case TypeCode.SByte: return new FuncCallInstruction(target); default: return SlowCreate(target, pi); } } @@ -233,13 +243,16 @@ private static Type GetHelperType(MethodInfo info, Type[] arrTypes) { default: throw new InvalidOperationException(); } } + return t; } + public static MethodInfo CacheFunc(Func method) { var info = method.GetMethodInfo(); lock (s_cache) { s_cache[info] = new FuncCallInstruction(method); } + return info; } @@ -248,6 +261,7 @@ public static MethodInfo CacheFunc(Func method) { lock (s_cache) { s_cache[info] = new FuncCallInstruction(method); } + return info; } @@ -256,6 +270,7 @@ public static MethodInfo CacheFunc(Func method) { lock (s_cache) { s_cache[info] = new FuncCallInstruction(method); } + return info; } @@ -264,6 +279,7 @@ public static MethodInfo CacheFunc(Func meth lock (s_cache) { s_cache[info] = new FuncCallInstruction(method); } + return info; } @@ -272,6 +288,7 @@ public static MethodInfo CacheFunc(Func(method); } + return info; } @@ -280,6 +297,7 @@ public static MethodInfo CacheFunc(Func(method); } + return info; } @@ -288,6 +306,7 @@ public static MethodInfo CacheFunc(Func(method); } + return info; } @@ -296,6 +315,7 @@ public static MethodInfo CacheFunc(Func(method); } + return info; } @@ -304,6 +324,7 @@ public static MethodInfo CacheFunc(Func(method); } + return info; } @@ -312,6 +333,7 @@ public static MethodInfo CacheFunc(Fun lock (s_cache) { s_cache[info] = new FuncCallInstruction(method); } + return info; } @@ -320,6 +342,7 @@ public static MethodInfo CacheAction(Action method) { lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -328,6 +351,7 @@ public static MethodInfo CacheAction(Action method) { lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -336,6 +360,7 @@ public static MethodInfo CacheAction(Action method) { lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -344,6 +369,7 @@ public static MethodInfo CacheAction(Action method) { lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -352,6 +378,7 @@ public static MethodInfo CacheAction(Action meth lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -360,6 +387,7 @@ public static MethodInfo CacheAction(Action(method); } + return info; } @@ -368,6 +396,7 @@ public static MethodInfo CacheAction(Action(method); } + return info; } @@ -376,6 +405,7 @@ public static MethodInfo CacheAction(Action(method); } + return info; } @@ -384,6 +414,7 @@ public static MethodInfo CacheAction(Action(method); } + return info; } @@ -392,6 +423,7 @@ public static MethodInfo CacheAction(Action< lock (s_cache) { s_cache[info] = new ActionCallInstruction(method); } + return info; } @@ -400,6 +432,7 @@ public static MethodInfo CacheAction(Action< internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 0; } } public ActionCallInstruction(Action target) { @@ -425,6 +458,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 1; } } public ActionCallInstruction(Action target) { @@ -450,6 +484,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 2; } } public ActionCallInstruction(Action target) { @@ -475,6 +510,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 3; } } public ActionCallInstruction(Action target) { @@ -500,6 +536,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 4; } } public ActionCallInstruction(Action target) { @@ -525,6 +562,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 5; } } public ActionCallInstruction(Action target) { @@ -550,6 +588,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 6; } } public ActionCallInstruction(Action target) { @@ -575,6 +614,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 7; } } public ActionCallInstruction(Action target) { @@ -600,6 +640,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 8; } } public ActionCallInstruction(Action target) { @@ -625,6 +666,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class ActionCallInstruction : CallInstruction { private readonly Action _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 9; } } public ActionCallInstruction(Action target) { @@ -650,6 +692,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 0; } } public FuncCallInstruction(Func target) { @@ -674,6 +717,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 1; } } public FuncCallInstruction(Func target) { @@ -698,6 +742,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 2; } } public FuncCallInstruction(Func target) { @@ -722,6 +767,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 3; } } public FuncCallInstruction(Func target) { @@ -746,6 +792,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 4; } } public FuncCallInstruction(Func target) { @@ -770,6 +817,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 5; } } public FuncCallInstruction(Func target) { @@ -794,6 +842,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 6; } } public FuncCallInstruction(Func target) { @@ -818,6 +867,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 7; } } public FuncCallInstruction(Func target) { @@ -842,6 +892,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 8; } } public FuncCallInstruction(Func target) { @@ -866,6 +917,7 @@ public override int Run(InterpretedFrame frame) { internal sealed class FuncCallInstruction : CallInstruction { private readonly Func _target; public override MethodInfo Info { get { return _target.GetMethodInfo(); } } + public override int ArgumentCount { get { return 9; } } public FuncCallInstruction(Func target) { @@ -891,9 +943,11 @@ internal sealed partial class MethodInfoCallInstruction : CallInstruction { public override object Invoke() { return InvokeWorker(); } + public override object Invoke(object arg0) { return InvokeWorker(arg0); } + public override object Invoke(object arg0, object arg1) { return InvokeWorker(arg0, arg1); } diff --git a/src/System.Management.Automation/engine/interpreter/CallInstruction.cs b/src/System.Management.Automation/engine/interpreter/CallInstruction.cs index bcaca8bda38..f7173c745ff 100644 --- a/src/System.Management.Automation/engine/interpreter/CallInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/CallInstruction.cs @@ -57,7 +57,7 @@ public static CallInstruction Create(MethodInfo info, ParameterInfo[] parameters return GetArrayAccessor(info, argumentCount); } - if (info is DynamicMethod || !info.IsStatic && info.DeclaringType.GetTypeInfo().IsValueType) + if (info is DynamicMethod || !info.IsStatic && info.DeclaringType.IsValueType) { return new MethodInfoCallInstruction(info, argumentCount); } @@ -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; } /// @@ -217,20 +217,26 @@ private static bool IndexIsNotReturnType(int index, MethodInfo target, Parameter } /// - /// Uses reflection to create new instance of the appropriate ReflectedCaller + /// Uses reflection to create new instance of the appropriate ReflectedCaller. /// 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); } + if (info.ReturnType != typeof(void)) { types.Add(info.ReturnType); } + Type[] arrTypes = types.ToArray(); return (CallInstruction)Activator.CreateInstance(GetHelperType(info, arrTypes), info); @@ -241,6 +247,7 @@ private static CallInstruction SlowCreate(MethodInfo info, ParameterInfo[] pis) #region Instruction public sealed override int ProducedStack { get { return Info.ReturnType == typeof(void) ? 0 : 1; } } + public sealed override int ConsumedStack { get { return ArgumentCount; } } public sealed override string InstructionName @@ -262,6 +269,7 @@ internal sealed partial class MethodInfoCallInstruction : CallInstruction private readonly int _argumentCount; public override MethodInfo Info { get { return _target; } } + public override int ArgumentCount { get { return _argumentCount; } } internal MethodInfoCallInstruction(MethodInfo target, int argumentCount) @@ -330,6 +338,7 @@ private static object[] GetNonStaticArgs(object[] args) { newArgs[i] = args[i + 1]; } + return newArgs; } @@ -352,6 +361,7 @@ public sealed override int Run(InterpretedFrame frame) { frame.StackIndex = first; } + return 1; } } diff --git a/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs b/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs index 9bf1630c92b..5d994d934c5 100644 --- a/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs +++ b/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs @@ -1,11 +1,11 @@ /* **************************************************************************** * - * Copyright (c) Microsoft Corporation. + * Copyright (c) Microsoft Corporation. * - * This source code is subject to terms and conditions of the Apache License, Version 2.0. A - * copy of the license can be found in the License.html file at the root of this distribution. If - * you cannot locate the Apache License, Version 2.0, please send an email to - * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound + * This source code is subject to terms and conditions of the Apache License, Version 2.0. A + * copy of the license can be found in the License.html file at the root of this distribution. If + * you cannot locate the Apache License, Version 2.0, please send an email to + * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. @@ -35,6 +35,7 @@ internal abstract class OffsetInstruction : Instruction protected int _offset = Unknown; public int Offset { get { return _offset; } } + public abstract Instruction[] Cache { get; } public Instruction Fixup(int offset) @@ -53,7 +54,7 @@ public Instruction Fixup(int offset) public override string ToDebugString(int instructionIndex, object cookie, Func labelIndexer, IList objects) { - return ToString() + (_offset != Unknown ? " -> " + (instructionIndex + _offset) : ""); + return ToString() + (_offset != Unknown ? " -> " + (instructionIndex + _offset) : string.Empty); } public override string ToString() @@ -68,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() @@ -96,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() @@ -124,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() @@ -132,6 +133,7 @@ internal CoalescingBranchInstruction() } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -155,10 +157,8 @@ 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]); } } @@ -201,7 +201,7 @@ internal abstract class IndexedBranchInstruction : Instruction internal readonly int _labelIndex; - public IndexedBranchInstruction(int labelIndex) + protected IndexedBranchInstruction(int labelIndex) { _labelIndex = labelIndex; } @@ -216,7 +216,7 @@ public override string ToDebugString(int instructionIndex, object cookie, Func " + targetIndex : ""); + return ToString() + (targetIndex != BranchLabel.UnknownIndex ? " -> " + targetIndex : string.Empty); } public override string ToString() @@ -227,32 +227,30 @@ public override string ToString() } /// - /// This instruction implements a goto expression that can jump out of any expression. - /// It pops values (arguments) from the evaluation stack that the expression tree nodes in between - /// the goto expression and the target label node pushed and not consumed yet. - /// A goto expression can jump into a node that evaluates arguments only if it carries - /// a value and jumps right after the first argument (the carried value will be used as the first argument). - /// Goto can jump into an arbitrary child of a BlockExpression since the block doesn't accumulate values + /// This instruction implements a goto expression that can jump out of any expression. + /// It pops values (arguments) from the evaluation stack that the expression tree nodes in between + /// the goto expression and the target label node pushed and not consumed yet. + /// A goto expression can jump into a node that evaluates arguments only if it carries + /// a value and jumps right after the first argument (the carried value will be used as the first argument). + /// Goto can jump into an arbitrary child of a BlockExpression since the block doesn't accumulate values /// on evaluation stack as its child expressions are being evaluated. - /// + /// /// Goto needs to execute any finally blocks on the way to the target label. /// - /// { + /// { /// f(1, 2, try { g(3, 4, try { goto L } finally { ... }, 6) } finally { ... }, 7, 8) - /// L: ... + /// L: ... /// } /// - /// The goto expression here jumps to label L while having 4 items on evaluation stack (1, 2, 3 and 4). - /// The jump needs to execute both finally blocks, the first one on stack level 4 the - /// second one on stack level 2. So, it needs to jump the first finally block, pop 2 items from the stack, + /// The goto expression here jumps to label L while having 4 items on evaluation stack (1, 2, 3 and 4). + /// The jump needs to execute both finally blocks, the first one on stack level 4 the + /// second one on stack level 2. So, it needs to jump the first finally block, pop 2 items from the stack, /// run second finally block and pop another 2 items from the stack and set instruction pointer to label L. - /// - /// Goto also needs to rethrow ThreadAbortException iff it jumps out of a catch handler and - /// the current thread is in "abort requested" state. /// internal sealed class GotoInstruction : IndexedBranchInstruction { private const int Variants = 4; + private static readonly GotoInstruction[] s_cache = new GotoInstruction[Variants * CacheSize]; private readonly bool _hasResult; @@ -260,12 +258,13 @@ internal sealed class GotoInstruction : IndexedBranchInstruction // TODO: We can remember hasValue in label and look it up when calculating stack balance. That would save some cache. private readonly bool _hasValue; - // The values should technically be Consumed = 1, Produced = 1 for gotos that target a label whose continuation depth + // The values should technically be Consumed = 1, Produced = 1 for gotos that target a label whose continuation depth // is different from the current continuation depth. This is because we will consume one continuation from the _continuations - // and at meantime produce a new _pendingContinuation. However, in case of forward gotos, we don't not know that is the + // and at meantime produce a new _pendingContinuation. However, in case of forward gotos, we don't not know that is the // case until the label is emitted. By then the consumed and produced stack information is useless. // The important thing here is that the stack balance is 0. public override int ConsumedContinuations { get { return 0; } } + public override int ProducedContinuations { get { return 0; } } public override int ConsumedStack @@ -292,14 +291,12 @@ internal static GotoInstruction Create(int labelIndex, bool hasResult, bool hasV var index = Variants * labelIndex | (hasResult ? 2 : 0) | (hasValue ? 1 : 0); return s_cache[index] ?? (s_cache[index] = new GotoInstruction(labelIndex, hasResult, hasValue)); } + return new GotoInstruction(labelIndex, hasResult, hasValue); } public override int Run(InterpretedFrame frame) { - // Are we jumping out of catch/finally while aborting the current thread? - Interpreter.AbortThreadIfRequested(frame, _labelIndex); - // goto the target label or the current finally continuation: return frame.Goto(_labelIndex, _hasValue ? frame.Pop() : Interpreter.NoValue, gotoExceptionHandler: false); } @@ -328,6 +325,7 @@ internal static EnterTryCatchFinallyInstruction CreateTryFinally(int labelIndex) { return new EnterTryCatchFinallyInstruction(labelIndex, true); } + internal static EnterTryCatchFinallyInstruction CreateTryCatch() { return new EnterTryCatchFinallyInstruction(UnknownInstrIndex, false); @@ -339,9 +337,10 @@ public override int Run(InterpretedFrame frame) if (_hasFinally) { - // Push finally. + // Push finally. frame.PushContinuation(_labelIndex); } + int prevInstrIndex = frame.InstructionIndex; frame.InstructionIndex++; @@ -380,15 +379,6 @@ public override int Run(InterpretedFrame frame) ExceptionHandler exHandler; frame.InstructionIndex += _tryHandler.GotoHandler(frame, exception, out exHandler); if (exHandler == null) { throw; } -#if !CORECLR // Thread.Abort and ThreadAbortException are not in CoreCLR. - // stay in the current catch so that ThreadAbortException is not rethrown by CLR: - var abort = exception as ThreadAbortException; - if (abort != null) - { - Interpreter.AnyAbortException = abort; - frame.CurrentAbortHandler = exHandler; - } -#endif bool rethrow = false; try { @@ -464,6 +454,7 @@ internal sealed class EnterFinallyInstruction : IndexedBranchInstruction private static readonly EnterFinallyInstruction[] s_cache = new EnterFinallyInstruction[CacheSize]; public override int ProducedStack { get { return 2; } } + public override int ConsumedContinuations { get { return 1; } } private EnterFinallyInstruction(int labelIndex) @@ -477,6 +468,7 @@ internal static EnterFinallyInstruction Create(int labelIndex) { return s_cache[labelIndex] ?? (s_cache[labelIndex] = new EnterFinallyInstruction(labelIndex)); } + return new EnterFinallyInstruction(labelIndex); } @@ -514,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(); @@ -535,10 +527,10 @@ private EnterExceptionHandlerInstruction(bool hasValue) _hasValue = hasValue; } - // If an exception is throws in try-body the expression result of try-body is not evaluated and loaded to the stack. + // If an exception is throws in try-body the expression result of try-body is not evaluated and loaded to the stack. // So the stack doesn't contain the try-body's value when we start executing the handler. - // However, while emitting instructions try block falls thru the catch block with a value on stack. - // We need to declare it consumed so that the stack state upon entry to the handler corresponds to the real + // However, while emitting instructions try block falls thru the catch block with a value on stack. + // We need to declare it consumed so that the stack state upon entry to the handler corresponds to the real // stack depth after throw jumped to this catch block. public override int ConsumedStack { get { return _hasValue ? 1 : 0; } } @@ -559,11 +551,11 @@ 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; - // The catch block yields a value if the body is non-void. This value is left on the stack. + // The catch block yields a value if the body is non-void. This value is left on the stack. public override int ConsumedStack { get { return _hasValue ? 1 : 0; } @@ -587,13 +579,12 @@ internal static LeaveExceptionHandlerInstruction Create(int labelIndex, bool has int index = (2 * labelIndex) | (hasValue ? 1 : 0); return s_cache[index] ?? (s_cache[index] = new LeaveExceptionHandlerInstruction(labelIndex, hasValue)); } + return new LeaveExceptionHandlerInstruction(labelIndex, hasValue); } public override int Run(InterpretedFrame frame) { - // CLR rethrows ThreadAbortException when leaving catch handler if abort is requested on the current thread. - Interpreter.AbortThreadIfRequested(frame, _labelIndex); return GetLabel(frame).Index - frame.InstructionIndex; } } @@ -617,7 +608,7 @@ public override int ConsumedStack get { return 1; } } - // While emitting instructions a non-void try-fault expression is expected to produce a value. + // While emitting instructions a non-void try-fault expression is expected to produce a value. public override int ProducedStack { get { return _hasValue ? 1 : 0; } @@ -630,16 +621,11 @@ private LeaveFaultInstruction(bool hasValue) public override int Run(InterpretedFrame frame) { - // TODO: ThreadAbortException ? - object exception = frame.Pop(); - // ExceptionHandler handler; - // return frame.Interpreter.GotoHandler(frame, exception, out handler); throw new RethrowException(); } } - internal sealed class ThrowInstruction : Instruction { internal static readonly ThrowInstruction Throw = new ThrowInstruction(true, false); @@ -677,6 +663,7 @@ public override int Run(InterpretedFrame frame) // return frame.Interpreter.GotoHandler(frame, ex, out handler); throw new RethrowException(); } + throw ex; } } @@ -692,6 +679,7 @@ internal SwitchInstruction(Dictionary cases) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 0; } } public override int Run(InterpretedFrame frame) @@ -735,7 +723,7 @@ public override int Run(InterpretedFrame frame) // // The first is okay, it just means we take longer to compile. // The second we explicitly guard against inside of Compile(). - // + // // We can't miss 0. The first thread that writes -1 must have read 0 and hence start compilation. if (unchecked(_compilationThreshold--) == 0) { @@ -749,6 +737,7 @@ public override int Run(InterpretedFrame frame) ThreadPool.QueueUserWorkItem(Compile, frame); } } + return 1; } @@ -771,7 +760,7 @@ private void Compile(object frameObj) return; } - //PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Interpreted loop compiled"); + // PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Interpreted loop compiled"); InterpretedFrame frame = (InterpretedFrame)frameObj; var compiler = new LoopCompiler(_loop, frame.Interpreter.LabelMapping, _variables, _closureVariables, _instructionIndex, _loopEnd); diff --git a/src/System.Management.Automation/engine/interpreter/DivInstruction.cs b/src/System.Management.Automation/engine/interpreter/DivInstruction.cs index 3a4039bcc9b..11edd140945 100644 --- a/src/System.Management.Automation/engine/interpreter/DivInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/DivInstruction.cs @@ -14,16 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; - namespace System.Management.Automation.Interpreter { internal abstract class DivInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private DivInstruction() @@ -120,7 +119,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l / (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l / (double)r; frame.StackIndex--; return 1; } @@ -128,17 +127,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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/DynamicInstructionN.cs b/src/System.Management.Automation/engine/interpreter/DynamicInstructionN.cs index 0459902698e..3d4686628cf 100644 --- a/src/System.Management.Automation/engine/interpreter/DynamicInstructionN.cs +++ b/src/System.Management.Automation/engine/interpreter/DynamicInstructionN.cs @@ -15,7 +15,6 @@ using System.Runtime.CompilerServices; - namespace System.Management.Automation.Interpreter { internal sealed partial class DynamicInstructionN : Instruction @@ -44,6 +43,7 @@ public DynamicInstructionN(Type delegateType, CallSite site, bool isVoid) } public override int ProducedStack { get { return _isVoid ? 0 : 1; } } + public override int ConsumedStack { get { return _argumentCount; } } public override int Run(InterpretedFrame frame) diff --git a/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs b/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs index 32c317038d7..abe64c471af 100644 --- a/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs +++ b/src/System.Management.Automation/engine/interpreter/DynamicInstructions.Generated.cs @@ -13,7 +13,6 @@ * * ***************************************************************************/ - using System; using System.Linq; using System.Reflection; @@ -23,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) { @@ -109,6 +112,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 0; } } public override int Run(InterpretedFrame frame) { @@ -134,6 +138,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 1; } } public override int Run(InterpretedFrame frame) { @@ -158,6 +163,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 2; } } public override int Run(InterpretedFrame frame) { @@ -183,6 +189,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 3; } } public override int Run(InterpretedFrame frame) { @@ -208,6 +215,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 4; } } public override int Run(InterpretedFrame frame) { @@ -233,6 +241,7 @@ private DynamicInstruction(CallSite> site) { } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 5; } } public override int Run(InterpretedFrame frame) { @@ -258,6 +267,7 @@ private DynamicInstruction(CallSite> site) } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 6; } } public override int Run(InterpretedFrame frame) { @@ -283,6 +293,7 @@ private DynamicInstruction(CallSite> si } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 7; } } public override int Run(InterpretedFrame frame) { @@ -308,6 +319,7 @@ private DynamicInstruction(CallSite> } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return 8; } } public override int Run(InterpretedFrame frame) { @@ -333,6 +345,7 @@ private DynamicInstruction(CallSite /// Implements dynamic call site with many arguments. Wraps the arguments into . /// - internal sealed partial class DynamicSplatInstruction : Instruction + internal sealed class DynamicSplatInstruction : Instruction { private readonly CallSite> _site; private readonly int _argumentCount; @@ -33,6 +32,7 @@ internal DynamicSplatInstruction(int argumentCount, CallSite but is 3/2 to 2 times slower. - private static Instruction s_reference,s_boolean,s_SByte,s_int16,s_char,s_int32,s_int64,s_byte,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_reference, s_boolean, s_SByte, s_int16, s_char, s_int32, s_int64, s_byte, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private EqualInstruction() @@ -33,7 +34,7 @@ internal sealed class EqualBoolean : EqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Boolean)frame.Pop()) == ((Boolean)frame.Pop())); + frame.Push(((bool)frame.Pop()) == ((bool)frame.Pop())); return +1; } } @@ -42,7 +43,7 @@ internal sealed class EqualSByte : EqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((SByte)frame.Pop()) == ((SByte)frame.Pop())); + frame.Push(((sbyte)frame.Pop()) == ((sbyte)frame.Pop())); return +1; } } @@ -60,7 +61,7 @@ internal sealed class EqualChar : EqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Char)frame.Pop()) == ((Char)frame.Pop())); + frame.Push(((char)frame.Pop()) == ((char)frame.Pop())); return +1; } } @@ -87,7 +88,7 @@ internal sealed class EqualByte : EqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Byte)frame.Pop()) == ((Byte)frame.Pop())); + frame.Push(((byte)frame.Pop()) == ((byte)frame.Pop())); return +1; } } @@ -132,7 +133,7 @@ internal sealed class EqualDouble : EqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Double)frame.Pop()) == ((Double)frame.Pop())); + frame.Push(((double)frame.Pop()) == ((double)frame.Pop())); return +1; } } @@ -149,30 +150,29 @@ public override int Run(InterpretedFrame frame) [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public static Instruction Create(Type type) { - var typeInfo = type.GetTypeInfo(); // Boxed enums can be unboxed as their underlying types: - var typeToUse = typeInfo.IsEnum ? Enum.GetUnderlyingType(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 (!typeInfo.IsValueType) + 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/FieldOperations.cs b/src/System.Management.Automation/engine/interpreter/FieldOperations.cs index 57d207c805d..1c9c3f5d3b6 100644 --- a/src/System.Management.Automation/engine/interpreter/FieldOperations.cs +++ b/src/System.Management.Automation/engine/interpreter/FieldOperations.cs @@ -48,6 +48,7 @@ public LoadFieldInstruction(FieldInfo field) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -68,6 +69,7 @@ public StoreFieldInstruction(FieldInfo field) } public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 0; } } public override int Run(InterpretedFrame frame) @@ -90,6 +92,7 @@ public StoreStaticFieldInstruction(FieldInfo field) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 0; } } public override int Run(InterpretedFrame frame) diff --git a/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs b/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs index 61a7e3aab10..ccd5c3780f7 100644 --- a/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs @@ -14,15 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; namespace System.Management.Automation.Interpreter { internal abstract class GreaterThanInstruction : Instruction { - private static Instruction s_SByte,s_int16,s_char,s_int32,s_int64,s_byte,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_SByte, s_int16, s_char, s_int32, s_int64, s_byte, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private GreaterThanInstruction() @@ -33,8 +33,8 @@ internal sealed class GreaterThanSByte : GreaterThanInstruction { public override int Run(InterpretedFrame frame) { - SByte right = (SByte)frame.Pop(); - frame.Push(((SByte)frame.Pop()) > right); + sbyte right = (sbyte)frame.Pop(); + frame.Push(((sbyte)frame.Pop()) > right); return +1; } } @@ -53,8 +53,8 @@ internal sealed class GreaterThanChar : GreaterThanInstruction { public override int Run(InterpretedFrame frame) { - Char right = (Char)frame.Pop(); - frame.Push(((Char)frame.Pop()) > right); + char right = (char)frame.Pop(); + frame.Push(((char)frame.Pop()) > right); return +1; } } @@ -83,8 +83,8 @@ internal sealed class GreaterThanByte : GreaterThanInstruction { public override int Run(InterpretedFrame frame) { - Byte right = (Byte)frame.Pop(); - frame.Push(((Byte)frame.Pop()) > right); + byte right = (byte)frame.Pop(); + frame.Push(((byte)frame.Pop()) > right); return +1; } } @@ -133,28 +133,28 @@ internal sealed class GreaterThanDouble : GreaterThanInstruction { public override int Run(InterpretedFrame frame) { - Double right = (Double)frame.Pop(); - frame.Push(((Double)frame.Pop()) > right); + double right = (double)frame.Pop(); + frame.Push(((double)frame.Pop()) > right); return +1; } } public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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 a49e9b5f662..f1d8edaeb87 100644 --- a/src/System.Management.Automation/engine/interpreter/Instruction.cs +++ b/src/System.Management.Automation/engine/interpreter/Instruction.cs @@ -17,18 +17,23 @@ namespace System.Management.Automation.Interpreter { +#nullable enable internal interface IInstructionProvider { void AddInstructions(LightCompiler compiler); } +#nullable restore - internal abstract partial class Instruction + internal abstract class Instruction { public const int UnknownInstrIndex = int.MaxValue; public virtual int ConsumedStack { get { return 0; } } + public virtual int ProducedStack { get { return 0; } } + public virtual int ConsumedContinuations { get { return 0; } } + public virtual int ProducedContinuations { get { return 0; } } public int StackBalance @@ -45,7 +50,7 @@ public int ContinuationsBalance public virtual string InstructionName { - get { return GetType().Name.Replace("Instruction", ""); } + get { return GetType().Name.Replace("Instruction", string.Empty); } } public override string ToString() @@ -69,8 +74,11 @@ internal sealed class NotInstruction : Instruction public static readonly Instruction Instance = new NotInstruction(); private NotInstruction() { } + public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } + public override int Run(InterpretedFrame frame) { frame.Push((bool)frame.Pop() ? ScriptingRuntimeHelpers.False : ScriptingRuntimeHelpers.True); diff --git a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs index 98e8545277a..839ccd6f905 100644 --- a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs +++ b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs @@ -13,13 +13,11 @@ * * ***************************************************************************/ - using System.Collections.Concurrent; using BigInt = System.Numerics.BigInteger; using System.Runtime.CompilerServices; using System.Threading; - namespace System.Management.Automation.Interpreter { internal abstract class InstructionFactory @@ -55,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); } @@ -83,45 +87,43 @@ 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)); } + return new NewArrayInitInstruction(elementCount); } } diff --git a/src/System.Management.Automation/engine/interpreter/InstructionList.cs b/src/System.Management.Automation/engine/interpreter/InstructionList.cs index cddf8dab6d8..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; @@ -63,7 +63,7 @@ public DebugView(InstructionArray array) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public InstructionList.DebugView.InstructionView[]/*!*/ A0 + public InstructionList.DebugView.InstructionView[] A0 { get { @@ -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 @@ -109,7 +111,7 @@ public DebugView(InstructionList list) } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public InstructionView[]/*!*/ A0 + public InstructionView[] A0 { get { @@ -130,7 +132,7 @@ internal static InstructionView[] GetInstructionViews(IList instruc int stackDepth = 0; int continuationsDepth = 0; - var cookieEnumerator = (debugCookies ?? Automation.Utils.EmptyArray>()).GetEnumerator(); + var cookieEnumerator = (debugCookies ?? Array.Empty>()).GetEnumerator(); var hasCookie = cookieEnumerator.MoveNext(); for (int i = 0; i < instructions.Count; i++) @@ -151,11 +153,12 @@ internal static InstructionView[] GetInstructionViews(IList instruc stackDepth += stackDiff; continuationsDepth += contDiff; } + return result.ToArray(); } [DebuggerDisplay("{GetValue(),nq}", Name = "{GetName(),nq}", Type = "{GetDisplayType(), nq}")] - internal struct InstructionView + internal readonly struct InstructionView { private readonly int _index; private readonly int _stackDepth; @@ -166,8 +169,8 @@ internal struct InstructionView internal string GetName() { return _index + - (_continuationsDepth == 0 ? "" : " C(" + _continuationsDepth + ")") + - (_stackDepth == 0 ? "" : " S(" + _stackDepth + ")"); + (_continuationsDepth == 0 ? string.Empty : " C(" + _continuationsDepth + ")") + + (_stackDepth == 0 ? string.Empty : " S(" + _stackDepth + ")"); } internal string GetValue() @@ -230,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)); @@ -272,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(); @@ -302,6 +302,7 @@ public InstructionArray ToArray() if (!_instances.TryGetValue(name, out dict)) { _instances[name] = dict = new Dictionary(); } + dict[instr] = true; }); } @@ -310,7 +311,7 @@ public InstructionArray ToArray() _maxStackDepth, _maxContinuationDepth, _instructions.ToArray(), - (_objects != null) ? _objects.ToArray() : null, + _objects?.ToArray(), BuildRuntimeLabels(), _debugCookies ); @@ -339,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)); } } @@ -351,11 +352,11 @@ 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; } - if (type == null || type.GetTypeInfo().IsValueType) + if (type == null || type.IsValueType) { if (value is bool) { @@ -368,10 +369,8 @@ 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))); return; @@ -382,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) @@ -446,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) { @@ -468,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) { @@ -485,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) { @@ -502,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) { @@ -519,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) { @@ -536,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) { @@ -558,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) { @@ -580,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) { @@ -597,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) { @@ -625,7 +594,7 @@ public void EmitInitializeLocal(int index, Type type) { Emit(new InitializeLocalInstruction.ImmutableValue(index, value)); } - else if (type.GetTypeInfo().IsValueType) + else if (type.IsValueType) { Emit(new InitializeLocalInstruction.MutableValue(index, type)); } @@ -642,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) { @@ -657,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) { @@ -672,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) { @@ -687,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) { @@ -712,8 +669,7 @@ public void EmitNewRuntimeVariables(int count) public void EmitGetArrayItem(Type arrayType) { var elementType = arrayType.GetElementType(); - var elementTypeInfo = elementType.GetTypeInfo(); - if (elementTypeInfo.IsClass || elementTypeInfo.IsInterface) + if (elementType.IsClass || elementType.IsInterface) { Emit(InstructionFactory.Factory.GetArrayItem()); } @@ -726,8 +682,7 @@ public void EmitGetArrayItem(Type arrayType) public void EmitSetArrayItem(Type arrayType) { var elementType = arrayType.GetElementType(); - var elementTypeInfo = elementType.GetTypeInfo(); - if (elementTypeInfo.IsClass || elementTypeInfo.IsInterface) + if (elementType.IsClass || elementType.IsInterface) { Emit(InstructionFactory.Factory.SetArrayItem()); } @@ -924,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) { @@ -939,8 +894,10 @@ private Instruction GetLoadField(FieldInfo field) { instruction = new LoadFieldInstruction(field); } + s_loadFields.Add(field, instruction); } + return instruction; } } @@ -1056,12 +1013,11 @@ public void EmitDynamic.Factory(binder)); } - // *** END GENERATED CODE *** #endregion - private static Dictionary> s_factories = + private static readonly Dictionary> s_factories = new Dictionary>(); internal static Instruction CreateDynamicInstruction(Type delegateType, CallSiteBinder binder) @@ -1084,13 +1040,14 @@ 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; } } + return factory(binder); } @@ -1122,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 7f89a41edcd..977fee85803 100644 --- a/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs +++ b/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs @@ -27,31 +27,24 @@ 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; public int InstructionIndex; - // When a ThreadAbortException is raised from interpreted code this is the first frame that caught it. - // No handlers within this handler re-abort the current thread when left. - public ExceptionHandler CurrentAbortHandler; - internal InterpretedFrame(Interpreter interpreter, StrongBox[] closure) { Interpreter = interpreter; @@ -140,7 +133,7 @@ public InterpretedFrame Parent public static bool IsInterpretedFrame(MethodBase method) { - //ContractUtils.RequiresNotNull(method, "method"); + // ContractUtils.RequiresNotNull(method, "method"); return method.DeclaringType == typeof(Interpreter) && method.Name == "Run"; } @@ -159,12 +152,14 @@ public static IEnumerable GroupStackFrames(IEnumerable s { continue; } + inInterpretedFrame = true; } else { inInterpretedFrame = false; } + yield return frame; } } @@ -249,7 +244,7 @@ public int YieldToCurrentContinuation() } /// - /// Get called from the LeaveFinallyInstruction + /// Get called from the LeaveFinallyInstruction. /// public int YieldToPendingContinuation() { @@ -296,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) @@ -323,6 +318,7 @@ public int Goto(int labelIndex, object value, bool gotoExceptionHandler) { Data[StackIndex - 1] = value; } + return target.Index - InstructionIndex; } diff --git a/src/System.Management.Automation/engine/interpreter/Interpreter.cs b/src/System.Management.Automation/engine/interpreter/Interpreter.cs index 02ca997b2eb..8afdfe69b3d 100644 --- a/src/System.Management.Automation/engine/interpreter/Interpreter.cs +++ b/src/System.Management.Automation/engine/interpreter/Interpreter.cs @@ -13,17 +13,9 @@ * * ***************************************************************************/ -#if !CLR2 +using System.Collections.Generic; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif - using System.Runtime.CompilerServices; -using System.Threading; - -using System.Diagnostics; -using System.Collections.Generic; namespace System.Management.Automation.Interpreter { @@ -39,6 +31,7 @@ namespace System.Management.Automation.Interpreter internal sealed class Interpreter { internal static readonly object NoValue = new object(); + internal const int RethrowOnReturn = Int32.MaxValue; // zero: sync compilation @@ -75,6 +68,7 @@ internal int ClosureSize { return 0; } + return ClosureVariables.Count; } } @@ -112,51 +106,5 @@ public void Run(InterpretedFrame frame) frame.InstructionIndex = index; } } - - /// - /// To get to the current AbortReason object on Thread.CurrentThread - /// we need to use ExceptionState property of any ThreadAbortException instance. - /// - [ThreadStatic] - internal static ThreadAbortException AnyAbortException = null; - - /// - /// If the target that 'Goto' jumps to is inside the current catch block or the subsequent finally block, - /// we delay the call to 'Abort' method, because we want to finish the catch/finally blocks - /// - internal static void AbortThreadIfRequested(InterpretedFrame frame, int targetLabelIndex) - { - var abortHandler = frame.CurrentAbortHandler; - var targetInstrIndex = frame.Interpreter._labels[targetLabelIndex].Index; - if (abortHandler != null && - !abortHandler.IsInsideCatchBlock(targetInstrIndex) && - !abortHandler.IsInsideFinallyBlock(targetInstrIndex)) - { - frame.CurrentAbortHandler = null; - - var currentThread = Thread.CurrentThread; - if ((currentThread.ThreadState & System.Threading.ThreadState.AbortRequested) != 0) - { - Debug.Assert(AnyAbortException != null); - - // The current abort reason needs to be preserved. -#if SILVERLIGHT - currentThread.Abort(); -#else - currentThread.Abort(AnyAbortException.ExceptionState); -#endif - } - } - } - - internal int ReturnAndRethrowLabelIndex - { - get - { - // the last label is "return and rethrow" label: - Debug.Assert(_labels[_labels.Length - 1].Index == RethrowOnReturn); - return _labels.Length - 1; - } - } } } diff --git a/src/System.Management.Automation/engine/interpreter/LabelInfo.cs b/src/System.Management.Automation/engine/interpreter/LabelInfo.cs index 970a757a20e..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}")); } } @@ -121,6 +121,7 @@ private void ValidateJump(LabelScopeInfo reference) // found it, jump is valid! return; } + if (j.Kind == LabelScopeKind.Filter) { break; @@ -131,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) @@ -175,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) @@ -193,6 +191,7 @@ private bool DefinedIn(LabelScopeInfo scope) { return definitions.Contains(scope); } + return false; } @@ -211,6 +210,7 @@ private LabelScopeInfo FirstDefinition() { return scope; } + return ((HashSet)_definitions).First(); } @@ -227,6 +227,7 @@ private void AddDefinition(LabelScopeInfo scope) { _definitions = set = new HashSet() { (LabelScopeInfo)_definitions }; } + set.Add(scope); } } @@ -246,11 +247,13 @@ internal static T CommonNode(T first, T second, Func parent) where T : { return first; } + var set = new HashSet(cmp); for (T t = first; t != null; t = parent(t)) { set.Add(t); } + for (T t = second; t != null; t = parent(t)) { if (set.Contains(t)) @@ -258,6 +261,7 @@ internal static T CommonNode(T first, T second, Func parent) where T : return t; } } + return null; } } @@ -308,7 +312,7 @@ internal LabelScopeInfo(LabelScopeInfo parent, LabelScopeKind kind) } /// - /// Returns true if we can jump into this node + /// Returns true if we can jump into this node. /// internal bool CanJumpInto { @@ -322,11 +326,11 @@ internal bool CanJumpInto case LabelScopeKind.Lambda: return true; } + return false; } } - internal bool ContainsTarget(LabelTarget target) { if (_labels == null) @@ -352,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 6b5f4fcae80..2d6754ff2fd 100644 --- a/src/System.Management.Automation/engine/interpreter/LessThanInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/LessThanInstruction.cs @@ -14,15 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; namespace System.Management.Automation.Interpreter { internal abstract class LessThanInstruction : Instruction { - private static Instruction s_SByte,s_int16,s_char,s_int32,s_int64,s_byte,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_SByte, s_int16, s_char, s_int32, s_int64, s_byte, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private LessThanInstruction() @@ -33,8 +33,8 @@ internal sealed class LessThanSByte : LessThanInstruction { public override int Run(InterpretedFrame frame) { - SByte right = (SByte)frame.Pop(); - frame.Push(((SByte)frame.Pop()) < right); + sbyte right = (sbyte)frame.Pop(); + frame.Push(((sbyte)frame.Pop()) < right); return +1; } } @@ -53,8 +53,8 @@ internal sealed class LessThanChar : LessThanInstruction { public override int Run(InterpretedFrame frame) { - Char right = (Char)frame.Pop(); - frame.Push(((Char)frame.Pop()) < right); + char right = (char)frame.Pop(); + frame.Push(((char)frame.Pop()) < right); return +1; } } @@ -83,8 +83,8 @@ internal sealed class LessThanByte : LessThanInstruction { public override int Run(InterpretedFrame frame) { - Byte right = (Byte)frame.Pop(); - frame.Push(((Byte)frame.Pop()) < right); + byte right = (byte)frame.Pop(); + frame.Push(((byte)frame.Pop()) < right); return +1; } } @@ -133,28 +133,28 @@ internal sealed class LessThanDouble : LessThanInstruction { public override int Run(InterpretedFrame frame) { - Double right = (Double)frame.Pop(); - frame.Push(((Double)frame.Pop()) < right); + double right = (double)frame.Pop(); + frame.Push(((double)frame.Pop()) < right); return +1; } } public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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 09236a3ec5d..c117fbff33a 100644 --- a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs @@ -62,12 +62,16 @@ public bool Matches(Type exceptionType) { return true; } + return false; } 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; @@ -91,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); } } @@ -120,7 +127,7 @@ internal bool IsCatchBlockExist } /// - /// No finally block + /// No finally block. /// internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndTargetIndex, ExceptionHandler[] handlers) : this(tryStart, tryEnd, gotoEndTargetIndex, Instruction.UnknownInstrIndex, Instruction.UnknownInstrIndex, handlers) @@ -129,7 +136,7 @@ internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndTargetIndex } /// - /// No catch blocks + /// No catch blocks. /// internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndTargetIndex, int finallyStart, int finallyEnd) : this(tryStart, tryEnd, gotoEndTargetIndex, finallyStart, finallyEnd, null) @@ -138,7 +145,7 @@ internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndTargetIndex } /// - /// Generic constructor + /// Generic constructor. /// internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndLabelIndex, int finallyStart, int finallyEnd, ExceptionHandler[] handlers) { @@ -161,25 +168,25 @@ internal TryCatchFinallyHandler(int tryStart, int tryEnd, int gotoEndLabelIndex, } /// - /// Goto the index of the first instruction of the suitable catch block + /// Goto the index of the first instruction of the suitable catch block. /// internal int GotoHandler(InterpretedFrame frame, object exception, out ExceptionHandler handler) { Debug.Assert(_handlers != null, "we should have at least one handler if the method gets called"); - handler = _handlers.FirstOrDefault(t => t.Matches(exception.GetType())); + handler = Array.Find(_handlers, t => t.Matches(exception.GetType())); if (handler == null) { return 0; } + return frame.Goto(handler.LabelIndex, exception, gotoExceptionHandler: true); } } /// - /// The re-throw instruction will throw this exception + /// The re-throw instruction will throw this exception. /// internal sealed class RethrowException : SystemException { } - [Serializable] internal class DebugInfo { // TODO: readonly @@ -190,9 +197,9 @@ 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 + // We allow comparison between int and DebugInfo here int IComparer.Compare(DebugInfo d1, DebugInfo d2) { if (d1.Index > d2.Index) return 1; @@ -203,23 +210,23 @@ int IComparer.Compare(DebugInfo d1, DebugInfo d2) public static DebugInfo GetMatchingDebugInfo(DebugInfo[] debugInfos, int index) { - //Create a faked DebugInfo to do the search + // Create a faked DebugInfo to do the search DebugInfo d = new DebugInfo { Index = index }; - //to find the closest debug info before the current index + // to find the closest debug info before the current index int i = Array.BinarySearch(debugInfos, d, s_debugComparer); if (i < 0) { - //~i is the index for the first bigger element - //if there is no bigger element, ~i is the length of the array + // ~i is the index for the first bigger element + // if there is no bigger element, ~i is the length of the array i = ~i; if (i == 0) { return null; } - //return the last one that is smaller - i = i - 1; + // return the last one that is smaller + i -= 1; } return debugInfos[i]; @@ -229,19 +236,18 @@ public override string ToString() { if (IsClear) { - return String.Format(CultureInfo.InvariantCulture, "{0}: clear", Index); + return string.Format(CultureInfo.InvariantCulture, "{0}: clear", Index); } else { - return String.Format(CultureInfo.InvariantCulture, "{0}: [{1}-{2}] '{3}'", Index, StartLine, EndLine, FileName); + return string.Format(CultureInfo.InvariantCulture, "{0}: [{1}-{2}] '{3}'", Index, StartLine, EndLine, FileName); } } } // TODO: [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] - [Serializable] - internal struct InterpretedFrameInfo + internal readonly struct InterpretedFrameInfo { public readonly string MethodName; @@ -286,7 +292,7 @@ internal sealed class LightCompiler private readonly LightCompiler _parent; - private static LocalDefinition[] s_emptyLocals = Automation.Utils.EmptyArray(); + private static readonly LocalDefinition[] s_emptyLocals = Array.Empty(); public LightCompiler(int compilationThreshold) { @@ -337,7 +343,7 @@ public LightDelegateCreator CompileTop(LambdaExpression node) return new LightDelegateCreator(MakeInterpreter(node.Name), node); } - //internal LightDelegateCreator CompileTop(LightLambdaExpression node) { + // internal LightDelegateCreator CompileTop(LightLambdaExpression node) { // foreach (var p in node.Parameters) { // var local = _locals.DefineLocal(p, 0); // _instructions.EmitInitializeParameter(local.Index); @@ -353,7 +359,7 @@ public LightDelegateCreator CompileTop(LambdaExpression node) // Debug.Assert(_instructions.CurrentStackDepth == (node.ReturnType != typeof(void) ? 1 : 0)); // // return new LightDelegateCreator(MakeInterpreter(node.Name), node); - //} + // } private Interpreter MakeInterpreter(string lambdaName) { @@ -366,7 +372,6 @@ private Interpreter MakeInterpreter(string lambdaName) return new Interpreter(lambdaName, _locals, GetBranchMapping(), _instructions.ToArray(), debugInfos, _compilationThreshold); } - private void CompileConstantExpression(Expression expr) { var node = (ConstantExpression)expr; @@ -382,7 +387,7 @@ private void CompileDefaultExpression(Type type) { if (type != typeof(void)) { - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { object value = ScriptingRuntimeHelpers.GetPrimitiveDefaultValue(type); if (value != null) @@ -410,6 +415,7 @@ private LocalVariable EnsureAvailableForClosure(ParameterExpression expr) { _locals.Box(expr, _instructions); } + return local; } else if (_parent != null) @@ -423,11 +429,11 @@ private LocalVariable EnsureAvailableForClosure(ParameterExpression expr) } } - //private void EnsureVariable(ParameterExpression variable) { + // private void EnsureVariable(ParameterExpression variable) { // if (!_locals.ContainsVariable(variable)) { // EnsureAvailableForClosure(variable); // } - //} + // } private LocalVariable ResolveLocal(ParameterExpression variable) { @@ -436,6 +442,7 @@ private LocalVariable ResolveLocal(ParameterExpression variable) { local = EnsureAvailableForClosure(variable); } + return local; } @@ -564,6 +571,7 @@ private LocalDefinition[] CompileBlockStart(BlockExpression node) { CompileAsVoid(node.Expressions[i]); } + return locals; } @@ -658,6 +666,7 @@ private void CompileMemberAssignment(BinaryExpression node, bool asVoid) { Compile(member.Expression); } + Compile(node.Right); int start = _instructions.Count; @@ -673,6 +682,7 @@ private void CompileMemberAssignment(BinaryExpression node, bool asVoid) { _instructions.EmitCall(method); } + return; } @@ -683,6 +693,7 @@ private void CompileMemberAssignment(BinaryExpression node, bool asVoid) { Compile(member.Expression); } + Compile(node.Right); int start = _instructions.Count; @@ -698,6 +709,7 @@ private void CompileMemberAssignment(BinaryExpression node, bool asVoid) { _instructions.EmitStoreField(fi); } + return; } @@ -791,7 +803,7 @@ private void CompileBinaryExpression(Expression expr) private void CompileEqual(Expression left, Expression right) { Debug.Assert(left.Type == right.Type || - !left.Type.GetTypeInfo().IsValueType && !right.Type.GetTypeInfo().IsValueType); + !left.Type.IsValueType && !right.Type.IsValueType); Compile(left); Compile(right); _instructions.EmitEqual(left.Type); @@ -800,7 +812,7 @@ private void CompileEqual(Expression left, Expression right) private void CompileNotEqual(Expression left, Expression right) { Debug.Assert(left.Type == right.Type || - !left.Type.GetTypeInfo().IsValueType && !right.Type.GetTypeInfo().IsValueType); + !left.Type.IsValueType && !right.Type.IsValueType); Compile(left); Compile(right); _instructions.EmitNotEqual(left.Type); @@ -889,6 +901,7 @@ private void CompileConvertToType(Type typeFrom, Type typeTo, bool isChecked) { _instructions.EmitNumericConvertUnchecked(from, to); } + return; } @@ -968,6 +981,7 @@ private void CompileLogicalBinaryExpression(Expression expr, bool andAlso) { _instructions.EmitBranchTrue(elseLabel); } + Compile(node.Right); _instructions.EmitBranch(endLabel, false, true); _instructions.MarkLabel(elseLabel); @@ -1015,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); @@ -1053,10 +1067,11 @@ 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(); } + LabelInfo end = DefineLabel(null); bool hasValue = node.Type != typeof(void); @@ -1073,6 +1088,7 @@ private void CompileSwitchExpression(Expression expr) { Debug.Assert(!hasValue); } + _instructions.EmitBranch(end.GetLabel(this), false, hasValue); for (int i = 0; i < node.Cases.Count; i++) @@ -1120,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) { @@ -1177,6 +1190,7 @@ private LabelInfo EnsureLabel(LabelTarget node) { _treeLabels[node] = result = new LabelInfo(node); } + return result; } @@ -1193,6 +1207,7 @@ internal LabelInfo DefineLabel(LabelTarget node) { return new LabelInfo(null); } + LabelInfo result = EnsureLabel(node); result.Define(_labelBlock); return result; @@ -1214,6 +1229,7 @@ private bool TryPushLabelBlock(Expression node) PushLabelBlock(LabelScopeKind.Expression); return true; } + return false; case ExpressionType.Label: // LabelExpression is a bit special, if it's directly in a @@ -1226,12 +1242,14 @@ private bool TryPushLabelBlock(Expression node) { return false; } + if (_labelBlock.Parent.Kind == LabelScopeKind.Switch && _labelBlock.Parent.ContainsTarget(label)) { return false; } } + PushLabelBlock(LabelScopeKind.Statement); return true; case ExpressionType.Block: @@ -1242,6 +1260,7 @@ private bool TryPushLabelBlock(Expression node) { DefineBlockLabels(node); } + return true; case ExpressionType.Switch: PushLabelBlock(LabelScopeKind.Switch); @@ -1254,6 +1273,7 @@ private bool TryPushLabelBlock(Expression node) SwitchCase c = @switch.Cases[index]; DefineBlockLabels(c.Body); } + DefineBlockLabels(@switch.DefaultBody); return true; @@ -1264,6 +1284,7 @@ private bool TryPushLabelBlock(Expression node) // treat it as an expression goto default; } + PushLabelBlock(LabelScopeKind.Statement); return true; @@ -1277,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; } @@ -1302,6 +1322,7 @@ private HybridReferenceDictionary GetBranchMapping() { newLabelMapping[kvp.Key] = kvp.Value.GetLabel(this); } + return newLabelMapping; } @@ -1336,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) { @@ -1349,10 +1370,10 @@ private bool EndsWithRethrow(Expression expr) { return EndsWithRethrow(block.Expressions[block.Expressions.Count - 1]); } + return false; } - // TODO: remove (replace by true fault support) private void CompileAsVoidRemoveRethrow(Expression expr) { @@ -1452,9 +1473,9 @@ private void CompileTryExpression(Expression expr) if (handler.Filter != null) { - //PushLabelBlock(LabelScopeKind.Filter); + // PushLabelBlock(LabelScopeKind.Filter); throw new NotImplementedException(); - //PopLabelBlock(LabelScopeKind.Filter); + // PopLabelBlock(LabelScopeKind.Filter); } var parameter = handler.Variable ?? Expression.Parameter(handler.Test); @@ -1511,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 @@ -1552,9 +1573,9 @@ private void CompileMethodCallExpression(Expression expr) // force compilation for now for ref types // also could be a mutable value type, Delegate.CreateDelegate and MethodInfo.Invoke both can't handle this, we // need to generate code. - var declaringTypeInfo = node.Method.DeclaringType.GetTypeInfo(); - if (!parameters.TrueForAll(p => !p.ParameterType.IsByRef) || - (!node.Method.IsStatic && declaringTypeInfo.IsValueType && !declaringTypeInfo.IsPrimitive)) + var declaringType = node.Method.DeclaringType; + if (!parameters.TrueForAll(static p => !p.ParameterType.IsByRef) || + (!node.Method.IsStatic && declaringType.IsValueType && !declaringType.IsPrimitive)) { _forceCompile = true; } @@ -1580,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; } @@ -1593,11 +1614,12 @@ private void CompileNewExpression(Expression expr) var arg = node.Arguments[index]; this.Compile(arg); } + _instructions.EmitNew(node.Constructor); } else { - Debug.Assert(expr.Type.GetTypeInfo().IsValueType); + Debug.Assert(expr.Type.IsValueType); _instructions.EmitDefaultValue(node.Type); } } @@ -1630,6 +1652,7 @@ private void CompileMemberExpression(Expression expr) Compile(node.Expression); _instructions.EmitLoadField(fi); } + return; } @@ -1641,11 +1664,11 @@ private void CompileMemberExpression(Expression expr) { Compile(node.Expression); } + _instructions.EmitCall(method); return; } - throw new System.NotImplementedException(); } @@ -1702,7 +1725,6 @@ private void CompileExtensionExpression(Expression expr) } } - private void CompileDebugInfoExpression(Expression expr) { var node = (DebugInfoExpression)expr; @@ -1732,7 +1754,6 @@ private void CompileRuntimeVariablesExpression(Expression expr) _instructions.EmitNewRuntimeVariables(node.Variables.Count); } - private void CompileLambdaExpression(Expression expr) { var node = (LambdaExpression)expr; @@ -1746,6 +1767,7 @@ private void CompileLambdaExpression(Expression expr) CompileGetBoxedVariable(variable); } } + _instructions.EmitCreateDelegate(creator); } @@ -1783,7 +1805,7 @@ private void CompileInvocationExpression(Expression expr) } // TODO: do not create a new Call Expression - //if (PlatformAdaptationLayer.IsCompactFramework) { + // if (PlatformAdaptationLayer.IsCompactFramework) { // // Workaround for a bug in Compact Framework // Compile( // AstUtils.Convert( @@ -1795,9 +1817,9 @@ private void CompileInvocationExpression(Expression expr) // node.Type // ) // ); - //} else { + // } else { CompileMethodCallExpression(Expression.Call(node.Expression, node.Expression.Type.GetMethod("Invoke"), node.Arguments)); - //} + // } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "expr")] @@ -1849,7 +1871,7 @@ private void CompileTypeIsExpression(Expression expr) Compile(node.Expression); // use TypeEqual for sealed types: - if (node.TypeOperand.GetTypeInfo().IsSealed) + if (node.TypeOperand.IsSealed) { _instructions.EmitLoad(node.TypeOperand); _instructions.EmitTypeEquals(); @@ -1908,8 +1930,10 @@ internal void CompileAsVoid(Expression expr) { _instructions.EmitPop(); } + break; } + Debug.Assert(_instructions.CurrentStackDepth == startingStackDepth); if (pushLabelBlock) { @@ -1981,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; @@ -2010,7 +2035,7 @@ private void CompileNoLabelPush(Expression expr) case ExpressionType.PostDecrementAssign: CompileReducibleExpression(expr); break; default: throw Assert.Unreachable; - }; + } Debug.Assert(_instructions.CurrentStackDepth == startingStackDepth + (expr.Type == typeof(void) ? 0 : 1)); } diff --git a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs index 847030a4cd4..6f9497db987 100644 --- a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs +++ b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs @@ -45,11 +45,11 @@ internal LightDelegateCreator(Interpreter interpreter, LambdaExpression lambda) _lambda = lambda; } - //internal LightDelegateCreator(Interpreter interpreter, LightLambdaExpression lambda) { + // internal LightDelegateCreator(Interpreter interpreter, LightLambdaExpression lambda) { // Assert.NotNull(lambda); // _interpreter = interpreter; // _lambda = lambda; - //} + // } internal Interpreter Interpreter { @@ -67,7 +67,7 @@ internal bool HasCompiled } /// - /// true if the compiled delegate has the same type as the lambda; + /// True if the compiled delegate has the same type as the lambda; /// false if the type was changed for interpretation. /// internal bool SameDelegateType @@ -120,8 +120,9 @@ private Type DelegateType { return le.Type; } + return null; - //return ((LightLambdaExpression)_lambda).Type; + // return ((LightLambdaExpression)_lambda).Type; } } @@ -138,6 +139,7 @@ internal Delegate CreateCompiledDelegate(StrongBox[] closure) var applyClosure = (Func[], Delegate>)_compiled; return applyClosure(closure); } + return _compiled; } @@ -161,13 +163,13 @@ internal void Compile(object state) return; } - //PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Interpreted lambda compiled"); + // PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Interpreted lambda compiled"); // Interpreter needs a standard delegate type. // 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); @@ -190,11 +192,11 @@ private static Type GetFuncOrAction(LambdaExpression lambda) Type delegateType; bool isVoid = lambda.ReturnType == typeof(void); - //if (isVoid && lambda.Parameters.Count == 2 && + // if (isVoid && lambda.Parameters.Count == 2 && // 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); + // } else { + Type[] types = lambda.Parameters.Map(static p => p.IsByRef ? p.Type.MakeByRefType() : p.Type); if (isVoid) { if (Expression.TryGetActionType(types, out delegateType)) @@ -210,8 +212,9 @@ private static Type GetFuncOrAction(LambdaExpression lambda) return delegateType; } } + return lambda.Type; - //} + // } } } } diff --git a/src/System.Management.Automation/engine/interpreter/LightLambda.Generated.cs b/src/System.Management.Automation/engine/interpreter/LightLambda.Generated.cs index d2169e3ff82..7ff5de773c5 100644 --- a/src/System.Management.Automation/engine/interpreter/LightLambda.Generated.cs +++ b/src/System.Management.Automation/engine/interpreter/LightLambda.Generated.cs @@ -13,7 +13,6 @@ * * ***************************************************************************/ - using System; using System.Runtime.CompilerServices; using System.Reflection; @@ -34,6 +33,7 @@ internal TRet Run0() { var frame = MakeFrame(); var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -51,9 +51,11 @@ internal void RunVoid0() { internal static Delegate MakeRun0(LightLambda lambda) { return new Func(lambda.Run0); } + internal static Delegate MakeRunVoid0(LightLambda lambda) { return new Action(lambda.RunVoid0); } + internal TRet Run1(T0 arg0) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0); @@ -63,6 +65,7 @@ internal TRet Run1(T0 arg0) { frame.Data[0] = arg0; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -81,9 +84,11 @@ internal void RunVoid1(T0 arg0) { internal static Delegate MakeRun1(LightLambda lambda) { return new Func(lambda.Run1); } + internal static Delegate MakeRunVoid1(LightLambda lambda) { return new Action(lambda.RunVoid1); } + internal TRet Run2(T0 arg0,T1 arg1) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1); @@ -94,6 +99,7 @@ internal TRet Run2(T0 arg0,T1 arg1) { frame.Data[1] = arg1; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -113,9 +119,11 @@ internal void RunVoid2(T0 arg0,T1 arg1) { internal static Delegate MakeRun2(LightLambda lambda) { return new Func(lambda.Run2); } + internal static Delegate MakeRunVoid2(LightLambda lambda) { return new Action(lambda.RunVoid2); } + internal TRet Run3(T0 arg0,T1 arg1,T2 arg2) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2); @@ -127,6 +135,7 @@ internal TRet Run3(T0 arg0,T1 arg1,T2 arg2) { frame.Data[2] = arg2; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -147,9 +156,11 @@ internal void RunVoid3(T0 arg0,T1 arg1,T2 arg2) { internal static Delegate MakeRun3(LightLambda lambda) { return new Func(lambda.Run3); } + internal static Delegate MakeRunVoid3(LightLambda lambda) { return new Action(lambda.RunVoid3); } + internal TRet Run4(T0 arg0,T1 arg1,T2 arg2,T3 arg3) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3); @@ -162,6 +173,7 @@ internal TRet Run4(T0 arg0,T1 arg1,T2 arg2,T3 arg3) { frame.Data[3] = arg3; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -183,9 +195,11 @@ internal void RunVoid4(T0 arg0,T1 arg1,T2 arg2,T3 arg3) { internal static Delegate MakeRun4(LightLambda lambda) { return new Func(lambda.Run4); } + internal static Delegate MakeRunVoid4(LightLambda lambda) { return new Action(lambda.RunVoid4); } + internal TRet Run5(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4); @@ -199,6 +213,7 @@ internal TRet Run5(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4) frame.Data[4] = arg4; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -221,9 +236,11 @@ internal void RunVoid5(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4) internal static Delegate MakeRun5(LightLambda lambda) { return new Func(lambda.Run5); } + internal static Delegate MakeRunVoid5(LightLambda lambda) { return new Action(lambda.RunVoid5); } + internal TRet Run6(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5); @@ -238,6 +255,7 @@ internal TRet Run6(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 ar frame.Data[5] = arg5; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -261,9 +279,11 @@ internal void RunVoid6(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg internal static Delegate MakeRun6(LightLambda lambda) { return new Func(lambda.Run6); } + internal static Delegate MakeRunVoid6(LightLambda lambda) { return new Action(lambda.RunVoid6); } + internal TRet Run7(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6); @@ -279,6 +299,7 @@ internal TRet Run7(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 frame.Data[6] = arg6; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -303,9 +324,11 @@ internal void RunVoid7(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 internal static Delegate MakeRun7(LightLambda lambda) { return new Func(lambda.Run7); } + internal static Delegate MakeRunVoid7(LightLambda lambda) { return new Action(lambda.RunVoid7); } + internal TRet Run8(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); @@ -322,6 +345,7 @@ internal TRet Run8(T0 arg0,T1 arg1,T2 arg2,T3 arg3 frame.Data[7] = arg7; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -347,9 +371,11 @@ internal void RunVoid8(T0 arg0,T1 arg1,T2 arg2,T3 arg3, internal static Delegate MakeRun8(LightLambda lambda) { return new Func(lambda.Run8); } + internal static Delegate MakeRunVoid8(LightLambda lambda) { return new Action(lambda.RunVoid8); } + internal TRet Run9(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); @@ -367,6 +393,7 @@ internal TRet Run9(T0 arg0,T1 arg1,T2 arg2,T3 a frame.Data[8] = arg8; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -393,9 +420,11 @@ internal void RunVoid9(T0 arg0,T1 arg1,T2 arg2,T3 ar internal static Delegate MakeRun9(LightLambda lambda) { return new Func(lambda.Run9); } + internal static Delegate MakeRunVoid9(LightLambda lambda) { return new Action(lambda.RunVoid9); } + internal TRet Run10(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); @@ -414,6 +443,7 @@ internal TRet Run10(T0 arg0,T1 arg1,T2 arg2, frame.Data[9] = arg9; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -441,9 +471,11 @@ internal void RunVoid10(T0 arg0,T1 arg1,T2 arg2,T internal static Delegate MakeRun10(LightLambda lambda) { return new Func(lambda.Run10); } + internal static Delegate MakeRunVoid10(LightLambda lambda) { return new Action(lambda.RunVoid10); } + internal TRet Run11(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9,T10 arg10) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); @@ -463,6 +495,7 @@ internal TRet Run11(T0 arg0,T1 arg1,T2 a frame.Data[10] = arg10; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -491,9 +524,11 @@ internal void RunVoid11(T0 arg0,T1 arg1,T2 ar internal static Delegate MakeRun11(LightLambda lambda) { return new Func(lambda.Run11); } + internal static Delegate MakeRunVoid11(LightLambda lambda) { return new Action(lambda.RunVoid11); } + internal TRet Run12(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9,T10 arg10,T11 arg11) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); @@ -514,6 +549,7 @@ internal TRet Run12(T0 arg0,T1 arg1, frame.Data[11] = arg11; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -543,9 +579,11 @@ internal void RunVoid12(T0 arg0,T1 arg1,T internal static Delegate MakeRun12(LightLambda lambda) { return new Func(lambda.Run12); } + internal static Delegate MakeRunVoid12(LightLambda lambda) { return new Action(lambda.RunVoid12); } + internal TRet Run13(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9,T10 arg10,T11 arg11,T12 arg12) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); @@ -567,6 +605,7 @@ internal TRet Run13(T0 arg0,T1 a frame.Data[12] = arg12; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -597,9 +636,11 @@ internal void RunVoid13(T0 arg0,T1 ar internal static Delegate MakeRun13(LightLambda lambda) { return new Func(lambda.Run13); } + internal static Delegate MakeRunVoid13(LightLambda lambda) { return new Action(lambda.RunVoid13); } + internal TRet Run14(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9,T10 arg10,T11 arg11,T12 arg12,T13 arg13) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); @@ -622,6 +663,7 @@ internal TRet Run14(T0 arg0, frame.Data[13] = arg13; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -653,9 +695,11 @@ internal void RunVoid14(T0 arg0,T internal static Delegate MakeRun14(LightLambda lambda) { return new Func(lambda.Run14); } + internal static Delegate MakeRunVoid14(LightLambda lambda) { return new Action(lambda.RunVoid14); } + internal TRet Run15(T0 arg0,T1 arg1,T2 arg2,T3 arg3,T4 arg4,T5 arg5,T6 arg6,T7 arg7,T8 arg8,T9 arg9,T10 arg10,T11 arg11,T12 arg12,T13 arg13,T14 arg14) { if (_compiled != null || TryGetCompiled()) { return ((Func)_compiled)(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); @@ -679,6 +723,7 @@ internal TRet Run15(T0 a frame.Data[14] = arg14; var current = frame.Enter(); try { _interpreter.Run(frame); } finally { frame.Leave(current); } + return (TRet)frame.Pop(); } @@ -711,6 +756,7 @@ internal void RunVoid15(T0 ar internal static Delegate MakeRun15(LightLambda lambda) { return new Func(lambda.Run15); } + internal static Delegate MakeRunVoid15(LightLambda lambda) { return new Action(lambda.RunVoid15); } diff --git a/src/System.Management.Automation/engine/interpreter/LightLambda.cs b/src/System.Management.Automation/engine/interpreter/LightLambda.cs index f883509412b..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) { @@ -73,6 +73,7 @@ private static Func GetRunDelegateCtor(Type delegateType) { return fastCtor; } + return MakeRunDelegateCtor(delegateType); } } @@ -156,10 +157,10 @@ private static Func MakeRunDelegateCtor(Type delegateType return s_runCache[delegateType] = lambda => targetMethod.CreateDelegate(delegateType, lambda); } - //TODO enable sharing of these custom delegates + // TODO enable sharing of these custom delegates private Delegate CreateCustomDelegate(Type delegateType) { - //PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Synchronously compiling a custom delegate"); + // PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Synchronously compiling a custom delegate"); var method = delegateType.GetMethod("Invoke"); var paramInfos = method.GetParameters(); @@ -251,10 +252,10 @@ private InterpretedFrame MakeFrame() internal void RunVoidRef2(ref T0 arg0, ref T1 arg1) { - //if (_compiled != null || TryGetCompiled()) { + // if (_compiled != null || TryGetCompiled()) { // ((ActionRef)_compiled)(ref arg0, ref arg1); // return; - //} + // } // copy in and copy out for today... var frame = MakeFrame(); @@ -273,7 +274,6 @@ internal void RunVoidRef2(ref T0 arg0, ref T1 arg1) } } - public object Run(params object[] arguments) { if (_compiled != null || TryGetCompiled()) @@ -286,6 +286,7 @@ public object Run(params object[] arguments) { frame.Data[i] = arguments[i]; } + var currentFrame = frame.Enter(); try { @@ -295,6 +296,7 @@ public object Run(params object[] arguments) { frame.Leave(currentFrame); } + return frame.Pop(); } } diff --git a/src/System.Management.Automation/engine/interpreter/LightLambdaClosureVisitor.cs b/src/System.Management.Automation/engine/interpreter/LightLambdaClosureVisitor.cs index 31ede7fbb02..00fe0b95c77 100644 --- a/src/System.Management.Automation/engine/interpreter/LightLambdaClosureVisitor.cs +++ b/src/System.Management.Automation/engine/interpreter/LightLambdaClosureVisitor.cs @@ -41,7 +41,7 @@ internal sealed class LightLambdaClosureVisitor : ExpressionVisitor /// /// The variable that holds onto the StrongBox{object}[] closure from - /// the interpreter + /// the interpreter. /// private readonly ParameterExpression _closureArray; @@ -93,6 +93,7 @@ protected override Expression VisitLambda(Expression node) { return node; } + return Expression.Lambda(b, node.Name, node.TailCall, node.Parameters); } @@ -102,15 +103,18 @@ protected override Expression VisitBlock(BlockExpression node) { _shadowedVars.Push(new HashSet(node.Variables)); } + var b = Visit(node.Expressions); if (node.Variables.Count > 0) { _shadowedVars.Pop(); } + if (b == node.Expressions) { return node; } + return Expression.Block(node.Variables, b); } @@ -120,16 +124,19 @@ protected override CatchBlock VisitCatchBlock(CatchBlock node) { _shadowedVars.Push(new HashSet(new[] { node.Variable })); } + Expression b = Visit(node.Body); Expression f = Visit(node.Filter); if (node.Variable != null) { _shadowedVars.Pop(); } + if (b == node.Body && f == node.Filter) { return node; } + return Expression.MakeCatchBlock(node.Test, node.Variable, b, f); } @@ -206,6 +213,7 @@ protected override Expression VisitBinary(BinaryExpression node) ); } } + return base.VisitBinary(node); } @@ -236,11 +244,10 @@ protected override Expression VisitExtension(Expression node) return Visit(node.ReduceExtensions()); } - #region MergedRuntimeVariables /// - /// Provides a list of variables, supporting read/write of the values + /// Provides a list of variables, supporting read/write of the values. /// private sealed class MergedRuntimeVariables : IRuntimeVariables { @@ -275,6 +282,7 @@ object IRuntimeVariables.this[int index] index = _indexes[index]; return (index >= 0) ? _first[index] : _second[-1 - index]; } + set { index = _indexes[index]; diff --git a/src/System.Management.Automation/engine/interpreter/LocalAccess.cs b/src/System.Management.Automation/engine/interpreter/LocalAccess.cs index 26258908696..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 { @@ -59,7 +62,7 @@ internal LoadLocalInstruction(int index) public override int Run(InterpretedFrame frame) { frame.Data[frame.StackIndex++] = frame.Data[_index]; - //frame.Push(frame.Data[_index]); + // frame.Push(frame.Data[_index]); return +1; } @@ -132,6 +135,7 @@ internal AssignLocalInstruction(int index) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -154,10 +158,11 @@ internal StoreLocalInstruction(int index) } public override int ConsumedStack { get { return 1; } } + public override int Run(InterpretedFrame frame) { frame.Data[_index] = frame.Data[--frame.StackIndex]; - //frame.Data[_index] = frame.Pop(); + // frame.Data[_index] = frame.Pop(); return +1; } @@ -175,6 +180,7 @@ internal AssignLocalBoxedInstruction(int index) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -193,6 +199,7 @@ internal StoreLocalBoxedInstruction(int index) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 0; } } public override int Run(InterpretedFrame frame) @@ -211,6 +218,7 @@ internal AssignLocalToClosureInstruction(int index) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -340,6 +348,7 @@ public Instruction BoxIfIndexMatches(int index) { return InstructionList.ParameterBox(index); } + return null; } @@ -422,6 +431,7 @@ public RuntimeVariablesInstruction(int count) } public override int ProducedStack { get { return 1; } } + public override int ConsumedStack { get { return _count; } } public override int Run(InterpretedFrame frame) @@ -431,6 +441,7 @@ public override int Run(InterpretedFrame frame) { ret[i] = (IStrongBox)frame.Pop(); } + frame.Push(RuntimeVariables.Create(ret)); return +1; } diff --git a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs index d9b8d33d360..0c1f6416741 100644 --- a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs +++ b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs @@ -35,7 +35,11 @@ internal sealed class LocalVariable public bool IsBoxed { - get { return (_flags & IsBoxedFlag) != 0; } + get + { + return (_flags & IsBoxedFlag) != 0; + } + set { if (value) @@ -56,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) @@ -73,11 +77,11 @@ internal Expression LoadFromArray(Expression frameData, Expression closure) public override string ToString() { - return String.Format(CultureInfo.InvariantCulture, "{0}: {1} {2}", Index, IsBoxed ? "boxed" : null, InClosure ? "in closure" : null); + return string.Format(CultureInfo.InvariantCulture, "{0}: {1} {2}", Index, IsBoxed ? "boxed" : null, InClosure ? "in closure" : null); } } - internal struct LocalDefinition + internal readonly struct LocalDefinition { private readonly int _index; private readonly ParameterExpression _parameter; @@ -121,6 +125,7 @@ public override int GetHashCode() { return 0; } + return _parameter.GetHashCode() ^ _index.GetHashCode(); } @@ -148,8 +153,8 @@ internal LocalVariables() public LocalDefinition DefineLocal(ParameterExpression variable, int start) { - //ContractUtils.RequiresNotNull(variable, "variable"); - //ContractUtils.Requires(start >= 0, "start", "start must be positive"); + // ContractUtils.RequiresNotNull(variable, "variable"); + // ContractUtils.Requires(start >= 0, "start", "start must be positive"); LocalVariable result = new LocalVariable(_localCount++, false, false); _maxLocalCount = System.Math.Max(_localCount, _maxLocalCount); @@ -158,10 +163,8 @@ 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); } else @@ -226,6 +229,7 @@ public int GetOrDefineLocal(ParameterExpression var) { return DefineLocal(var, 0).Index; } + return index; } @@ -243,6 +247,7 @@ public bool TryGetLocalOrClosure(ParameterExpression var, out LocalVariable loca local = scope.Variable; return true; } + if (_closureVariables != null && _closureVariables.TryGetValue(var, out local)) { return true; @@ -263,6 +268,7 @@ internal Dictionary CopyLocals() { res[keyValue.Key] = keyValue.Value.Variable; } + return res; } @@ -287,17 +293,15 @@ 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); return result; } /// - /// Tracks where a variable is defined and what range of instructions it's used in + /// Tracks where a variable is defined and what range of instructions it's used in. /// private sealed class VariableScope { diff --git a/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs b/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs index b6e018f50c4..78c5534a132 100644 --- a/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LoopCompiler.cs @@ -97,6 +97,7 @@ internal LoopFunc CreateDelegate() { local = _closureVariables[variable.Key]; } + Expression elemRef = local.LoadFromArray(_frameDataVar, _frameClosureVar); if (local.InClosureOrBoxed) @@ -256,6 +257,7 @@ protected override Expression VisitBinary(BinaryExpression node) { return Visit(node.Reduce()); } + Debug.Assert(!node.NodeType.IsReadWriteAssignment()); var param = node.Left as ParameterExpression; @@ -273,12 +275,14 @@ protected override Expression VisitBinary(BinaryExpression node) if (right.NodeType != ExpressionType.Parameter) { // { left.Value = (object)(rightVar = right), rightVar } + rightVar = AddTemp(Expression.Parameter(right.Type)); right = Expression.Assign(rightVar, right); } else { // { left.Value = (object)right, right } + rightVar = right; } @@ -305,6 +309,7 @@ protected override Expression VisitUnary(UnaryExpression node) { return Visit(node.Reduce()); } + Debug.Assert(!node.NodeType.IsReadWriteAssignment()); return base.VisitUnary(node); } @@ -369,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 5a05c71367c..3b150a7abbd 100644 --- a/src/System.Management.Automation/engine/interpreter/MulInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/MulInstruction.cs @@ -14,15 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; namespace System.Management.Automation.Interpreter { internal abstract class MulInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private MulInstruction() @@ -119,7 +119,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l * (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l * (double)r; frame.StackIndex--; return +1; } @@ -127,17 +127,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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; @@ -152,9 +152,10 @@ public override string ToString() internal abstract class MulOvfInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private MulOvfInstruction() @@ -251,7 +252,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l * (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l * (double)r; frame.StackIndex--; return +1; } @@ -259,17 +260,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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 4f207028d15..79df1b9a84b 100644 --- a/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs @@ -20,9 +20,10 @@ namespace System.Management.Automation.Interpreter internal abstract class NotEqualInstruction : Instruction { // Perf: EqualityComparer but is 3/2 to 2 times slower. - private static Instruction s_reference,s_boolean,s_SByte,s_int16,s_char,s_int32,s_int64,s_byte,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_reference, s_boolean, s_SByte, s_int16, s_char, s_int32, s_int64, s_byte, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private NotEqualInstruction() @@ -33,7 +34,7 @@ internal sealed class NotEqualBoolean : NotEqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Boolean)frame.Pop()) != ((Boolean)frame.Pop())); + frame.Push(((bool)frame.Pop()) != ((bool)frame.Pop())); return +1; } } @@ -42,7 +43,7 @@ internal sealed class NotEqualSByte : NotEqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((SByte)frame.Pop()) != ((SByte)frame.Pop())); + frame.Push(((sbyte)frame.Pop()) != ((sbyte)frame.Pop())); return +1; } } @@ -60,7 +61,7 @@ internal sealed class NotEqualChar : NotEqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Char)frame.Pop()) != ((Char)frame.Pop())); + frame.Push(((char)frame.Pop()) != ((char)frame.Pop())); return +1; } } @@ -87,7 +88,7 @@ internal sealed class NotEqualByte : NotEqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Byte)frame.Pop()) != ((Byte)frame.Pop())); + frame.Push(((byte)frame.Pop()) != ((byte)frame.Pop())); return +1; } } @@ -132,7 +133,7 @@ internal sealed class NotEqualDouble : NotEqualInstruction { public override int Run(InterpretedFrame frame) { - frame.Push(((Double)frame.Pop()) != ((Double)frame.Pop())); + frame.Push(((double)frame.Pop()) != ((double)frame.Pop())); return +1; } } @@ -149,30 +150,29 @@ public override int Run(InterpretedFrame frame) [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public static Instruction Create(Type type) { - var typeInfo = type.GetTypeInfo(); // Boxed enums can be unboxed as their underlying types: - var typeToUse = typeInfo.IsEnum ? Enum.GetUnderlyingType(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 (!typeInfo.IsValueType) + 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/NumericConvertInstruction.cs b/src/System.Management.Automation/engine/interpreter/NumericConvertInstruction.cs index f61c512af32..bec2003db11 100644 --- a/src/System.Management.Automation/engine/interpreter/NumericConvertInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/NumericConvertInstruction.cs @@ -26,6 +26,7 @@ protected NumericConvertInstruction(TypeCode from, TypeCode to) } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override string ToString() @@ -53,17 +54,17 @@ private object Convert(object obj) { switch (_from) { - case TypeCode.Byte: return ConvertInt32((Byte)obj); - case TypeCode.SByte: return ConvertInt32((SByte)obj); + case TypeCode.Byte: return ConvertInt32((byte)obj); + case TypeCode.SByte: return ConvertInt32((sbyte)obj); case TypeCode.Int16: return ConvertInt32((Int16)obj); - case TypeCode.Char: return ConvertInt32((Char)obj); + case TypeCode.Char: return ConvertInt32((char)obj); case TypeCode.Int32: return ConvertInt32((Int32)obj); case TypeCode.Int64: return ConvertInt64((Int64)obj); case TypeCode.UInt16: return ConvertInt32((UInt16)obj); case TypeCode.UInt32: return ConvertInt64((UInt32)obj); case TypeCode.UInt64: return ConvertUInt64((UInt64)obj); case TypeCode.Single: return ConvertDouble((Single)obj); - case TypeCode.Double: return ConvertDouble((Double)obj); + case TypeCode.Double: return ConvertDouble((double)obj); default: throw Assert.Unreachable; } } @@ -74,17 +75,17 @@ private object ConvertInt32(int obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } @@ -96,17 +97,17 @@ private object ConvertInt64(Int64 obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } @@ -118,39 +119,39 @@ private object ConvertUInt64(UInt64 obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } } - private object ConvertDouble(Double obj) + private object ConvertDouble(double obj) { unchecked { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } @@ -177,17 +178,17 @@ private object Convert(object obj) { switch (_from) { - case TypeCode.Byte: return ConvertInt32((Byte)obj); - case TypeCode.SByte: return ConvertInt32((SByte)obj); + case TypeCode.Byte: return ConvertInt32((byte)obj); + case TypeCode.SByte: return ConvertInt32((sbyte)obj); case TypeCode.Int16: return ConvertInt32((Int16)obj); - case TypeCode.Char: return ConvertInt32((Char)obj); + case TypeCode.Char: return ConvertInt32((char)obj); case TypeCode.Int32: return ConvertInt32((Int32)obj); case TypeCode.Int64: return ConvertInt64((Int64)obj); case TypeCode.UInt16: return ConvertInt32((UInt16)obj); case TypeCode.UInt32: return ConvertInt64((UInt32)obj); case TypeCode.UInt64: return ConvertUInt64((UInt64)obj); case TypeCode.Single: return ConvertDouble((Single)obj); - case TypeCode.Double: return ConvertDouble((Double)obj); + case TypeCode.Double: return ConvertDouble((double)obj); default: throw Assert.Unreachable; } } @@ -198,17 +199,17 @@ private object ConvertInt32(int obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } @@ -220,17 +221,17 @@ private object ConvertInt64(Int64 obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } @@ -242,39 +243,39 @@ private object ConvertUInt64(UInt64 obj) { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } } - private object ConvertDouble(Double obj) + private object ConvertDouble(double obj) { checked { switch (_to) { - case TypeCode.Byte: return (Byte)obj; - case TypeCode.SByte: return (SByte)obj; + case TypeCode.Byte: return (byte)obj; + case TypeCode.SByte: return (sbyte)obj; case TypeCode.Int16: return (Int16)obj; - case TypeCode.Char: return (Char)obj; + case TypeCode.Char: return (char)obj; case TypeCode.Int32: return (Int32)obj; case TypeCode.Int64: return (Int64)obj; case TypeCode.UInt16: return (UInt16)obj; case TypeCode.UInt32: return (UInt32)obj; case TypeCode.UInt64: return (UInt64)obj; case TypeCode.Single: return (Single)obj; - case TypeCode.Double: return (Double)obj; + case TypeCode.Double: return (double)obj; default: throw Assert.Unreachable; } } diff --git a/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs b/src/System.Management.Automation/engine/interpreter/PowerShellInstructions.cs index f579bcaf73f..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; @@ -39,6 +39,7 @@ public override int Run(InterpretedFrame frame) context.Debugger.OnSequencePointHit(functionContext); } } + return +1; } diff --git a/src/System.Management.Automation/engine/interpreter/RuntimeVariables.cs b/src/System.Management.Automation/engine/interpreter/RuntimeVariables.cs index 40fd29333dd..638ff84c2ca 100644 --- a/src/System.Management.Automation/engine/interpreter/RuntimeVariables.cs +++ b/src/System.Management.Automation/engine/interpreter/RuntimeVariables.cs @@ -40,6 +40,7 @@ object IRuntimeVariables.this[int index] { return _boxes[index].Value; } + set { _boxes[index].Value = value; diff --git a/src/System.Management.Automation/engine/interpreter/StackOperations.cs b/src/System.Management.Automation/engine/interpreter/StackOperations.cs index 065bbbbf74f..51a5f03348c 100644 --- a/src/System.Management.Automation/engine/interpreter/StackOperations.cs +++ b/src/System.Management.Automation/engine/interpreter/StackOperations.cs @@ -60,7 +60,7 @@ public override int Run(InterpretedFrame frame) public override string ToDebugString(int instructionIndex, object cookie, Func labelIndexer, IList objects) { - return String.Format(CultureInfo.InvariantCulture, "LoadCached({0}: {1})", _index, objects[(int)_index]); + return string.Format(CultureInfo.InvariantCulture, "LoadCached({0}: {1})", _index, objects[(int)_index]); } public override string ToString() @@ -96,6 +96,7 @@ internal sealed class DupInstruction : Instruction private DupInstruction() { } public override int ConsumedStack { get { return 0; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) diff --git a/src/System.Management.Automation/engine/interpreter/SubInstruction.cs b/src/System.Management.Automation/engine/interpreter/SubInstruction.cs index fd6985e602d..486cd7f8933 100644 --- a/src/System.Management.Automation/engine/interpreter/SubInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/SubInstruction.cs @@ -14,15 +14,15 @@ * ***************************************************************************/ using System.Diagnostics; -using System.Reflection; namespace System.Management.Automation.Interpreter { internal abstract class SubInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private SubInstruction() @@ -119,7 +119,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l - (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l - (double)r; frame.StackIndex--; return +1; } @@ -127,17 +127,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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; @@ -152,9 +152,10 @@ public override string ToString() internal abstract class SubOvfInstruction : Instruction { - private static Instruction s_int16,s_int32,s_int64,s_UInt16,s_UInt32,s_UInt64,s_single,s_double; + private static Instruction s_int16, s_int32, s_int64, s_UInt16, s_UInt32, s_UInt64, s_single, s_double; public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private SubOvfInstruction() @@ -251,7 +252,7 @@ public override int Run(InterpretedFrame frame) { object l = frame.Data[frame.StackIndex - 2]; object r = frame.Data[frame.StackIndex - 1]; - frame.Data[frame.StackIndex - 2] = (Double)l - (Double)r; + frame.Data[frame.StackIndex - 2] = (double)l - (double)r; frame.StackIndex--; return +1; } @@ -259,17 +260,17 @@ public override int Run(InterpretedFrame frame) public static Instruction Create(Type type) { - Debug.Assert(!type.GetTypeInfo().IsEnum); + 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/TypeOperations.cs b/src/System.Management.Automation/engine/interpreter/TypeOperations.cs index 5d50c5c957a..190e89d6e0b 100644 --- a/src/System.Management.Automation/engine/interpreter/TypeOperations.cs +++ b/src/System.Management.Automation/engine/interpreter/TypeOperations.cs @@ -28,6 +28,7 @@ internal CreateDelegateInstruction(LightDelegateCreator delegateCreator) } public override int ConsumedStack { get { return _creator.Interpreter.ClosureSize; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -63,7 +64,9 @@ public NewInstruction(ConstructorInfo constructor) _constructor = constructor; _argCount = constructor.GetParameters().Length; } + public override int ConsumedStack { get { return _argCount; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -84,6 +87,7 @@ public override int Run(InterpretedFrame frame) ExceptionHelpers.UpdateForRethrow(e.InnerException); throw e.InnerException; } + frame.Push(ret); return +1; } @@ -99,6 +103,7 @@ internal sealed class DefaultValueInstruction : Instruction internal DefaultValueInstruction() { } public override int ConsumedStack { get { return 0; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -118,6 +123,7 @@ internal sealed class TypeIsInstruction : Instruction internal TypeIsInstruction() { } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -138,6 +144,7 @@ internal sealed class TypeAsInstruction : Instruction internal TypeAsInstruction() { } public override int ConsumedStack { get { return 1; } } + public override int ProducedStack { get { return 1; } } public override int Run(InterpretedFrame frame) @@ -152,6 +159,7 @@ public override int Run(InterpretedFrame frame) { frame.Push(null); } + return +1; } @@ -166,6 +174,7 @@ internal sealed class TypeEqualsInstruction : Instruction public static readonly TypeEqualsInstruction Instance = new TypeEqualsInstruction(); public override int ConsumedStack { get { return 2; } } + public override int ProducedStack { get { return 1; } } private TypeEqualsInstruction() diff --git a/src/System.Management.Automation/engine/interpreter/Utilities.cs b/src/System.Management.Automation/engine/interpreter/Utilities.cs index 1100ce9e3c4..0eef4728e46 100644 --- a/src/System.Management.Automation/engine/interpreter/Utilities.cs +++ b/src/System.Management.Automation/engine/interpreter/Utilities.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Diagnostics; @@ -9,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading; + using AstUtils = System.Management.Automation.Interpreter.Utils; namespace System.Management.Automation.Interpreter @@ -21,23 +21,24 @@ internal static Type GetNonNullableType(this Type type) { return type.GetGenericArguments()[0]; } + return type; } internal static Type GetNullableType(Type type) { Debug.Assert(type != null, "type cannot be null"); - if (type.GetTypeInfo().IsValueType && !IsNullableType(type)) + if (type.IsValueType && !IsNullableType(type)) { return typeof(Nullable<>).MakeGenericType(type); } + return type; } internal static bool IsNullableType(Type type) { - var typeInfo = type.GetTypeInfo(); - return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>); + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } internal static bool IsBool(Type type) @@ -48,7 +49,7 @@ internal static bool IsBool(Type type) internal static bool IsNumeric(Type type) { type = GetNonNullableType(type); - if (!type.GetTypeInfo().IsEnum) + if (!type.IsEnum) { switch (type.GetTypeCode()) { @@ -66,6 +67,7 @@ internal static bool IsNumeric(Type type) return true; } } + return false; } @@ -86,13 +88,14 @@ internal static bool IsNumeric(TypeCode typeCode) case TypeCode.UInt64: return true; } + return false; } internal static bool IsArithmetic(Type type) { type = GetNonNullableType(type); - if (!type.GetTypeInfo().IsEnum) + if (!type.IsEnum) { switch (type.GetTypeCode()) { @@ -107,6 +110,7 @@ internal static bool IsArithmetic(Type type) return true; } } + return false; } } @@ -122,7 +126,7 @@ internal static T[] AddLast(this IList list, T item) } } - internal static partial class DelegateHelpers + internal static class DelegateHelpers { #region Generated Maximum Delegate Arity @@ -143,10 +147,10 @@ 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); + // return MakeCustomDelegate(types); } Type returnType = types[types.Length - 1]; @@ -215,11 +219,12 @@ internal static Type MakeDelegate(Type[] types) #endregion } } + throw Assert.Unreachable; } } - internal class ScriptingRuntimeHelpers + internal static class ScriptingRuntimeHelpers { internal static object Int32ToObject(int i) { @@ -234,17 +239,17 @@ internal static object BooleanToObject(bool b) internal static readonly MethodInfo BooleanToObjectMethod = typeof(ScriptingRuntimeHelpers).GetMethod("BooleanToObject"); internal static readonly MethodInfo Int32ToObjectMethod = typeof(ScriptingRuntimeHelpers).GetMethod("Int32ToObject"); - internal static object True = true; - internal static object False = false; + internal static readonly object True = true; + internal static readonly object False = false; internal static object GetPrimitiveDefaultValue(Type type) { switch (type.GetTypeCode()) { case TypeCode.Boolean: return ScriptingRuntimeHelpers.False; - case TypeCode.SByte: return default(SByte); - case TypeCode.Byte: return default(Byte); - case TypeCode.Char: return default(Char); + case TypeCode.SByte: return default(sbyte); + case TypeCode.Byte: return default(byte); + case TypeCode.Char: return default(char); case TypeCode.Int16: return default(Int16); case TypeCode.Int32: return ScriptingRuntimeHelpers.Int32ToObject(0); case TypeCode.Int64: return default(Int64); @@ -252,7 +257,7 @@ internal static object GetPrimitiveDefaultValue(Type type) case TypeCode.UInt32: return default(UInt32); case TypeCode.UInt64: return default(UInt64); case TypeCode.Single: return default(Single); - case TypeCode.Double: return default(Double); + case TypeCode.Double: return default(double); case TypeCode.DateTime: return default(DateTime); case TypeCode.Decimal: return default(Decimal); // TypeCode.Empty: null; @@ -288,7 +293,7 @@ internal ArgumentArray(object[] arguments, int first, int count) public object GetArgument(int index) { - //ContractUtils.RequiresArrayIndex(_arguments, index, "index"); + // ContractUtils.RequiresArrayIndex(_arguments, index, "index"); return _arguments[_first + index]; } @@ -304,7 +309,7 @@ public DynamicMetaObject GetMetaObject(Expression parameter, int index) ); } - //[CLSCompliant(false)] + // [CLSCompliant(false)] public static object GetArg(ArgumentArray array, int index) { return array._arguments[array._first + index]; @@ -342,7 +347,7 @@ public static Exception UpdateForRethrow(Exception rethrow) } /// - /// Returns all the stack traces associates with an exception + /// Returns all the stack traces associates with an exception. /// public static IList GetExceptionStackTraces(Exception rethrow) { @@ -370,6 +375,7 @@ internal class HybridReferenceDictionary where TKey : class private KeyValuePair[] _keysAndValues; private Dictionary _dict; private int _count; + private const int _arraySize = 10; public HybridReferenceDictionary() @@ -407,6 +413,7 @@ public bool TryGetValue(TKey key, out TValue value) } } } + value = default(TValue); return false; } @@ -465,6 +472,7 @@ public int Count { return _dict.Count; } + return _count; } } @@ -507,6 +515,7 @@ public TValue this[TKey key] throw new KeyNotFoundException(); } + set { Debug.Assert(key != null); @@ -552,6 +561,7 @@ public TValue this[TKey key] { _dict[_keysAndValues[i].Key] = _keysAndValues[i].Value; } + _keysAndValues = null; _dict[key] = value; @@ -648,15 +658,17 @@ public TValue this[TKey key] { return res; } + throw new KeyNotFoundException(); } + set { Add(key, value); } } - private struct KeyInfo + private readonly struct KeyInfo { internal readonly TValue Value; internal readonly LinkedListNode List; @@ -672,10 +684,9 @@ internal KeyInfo(TValue value, LinkedListNode list) internal class ThreadLocal { private StorageInfo[] _stores; // array of storage indexed by managed thread ID - private static readonly StorageInfo[] s_updating = Automation.Utils.EmptyArray(); // a marker used when updating the array + private static readonly StorageInfo[] s_updating = Array.Empty(); // a marker used when updating the array private readonly bool _refCounted; - public ThreadLocal() { } @@ -706,6 +717,7 @@ public T Value { return GetStorageInfo().Value; } + set { GetStorageInfo().Value = value; @@ -757,31 +769,6 @@ public T Update(T newValue) #region Storage implementation -#if SILVERLIGHT - private static int _cfThreadIdDispenser = 1; - - [ThreadStatic] - private static int _cfThreadId; - - private static int GetCurrentThreadId() { - if (PlatformAdaptationLayer.IsCompactFramework) { - // CF doesn't index threads by small integers, so we need to do the indexing ourselves: - int id = _cfThreadId; - if (id == 0) { - _cfThreadId = id = Interlocked.Increment(ref _cfThreadIdDispenser); - } - return id; - } else { - return Thread.CurrentThread.ManagedThreadId; - } - } -#else - private static int GetCurrentThreadId() - { - return Thread.CurrentThread.ManagedThreadId; - } -#endif - /// /// Gets the StorageInfo for the current thread. /// @@ -792,7 +779,7 @@ public StorageInfo GetStorageInfo() private StorageInfo GetStorageInfo(StorageInfo[] curStorage) { - int threadId = GetCurrentThreadId(); + int threadId = Environment.CurrentManagedThreadId; // fast path if we already have a value in the array if (curStorage != null && curStorage.Length > threadId) @@ -842,7 +829,7 @@ private StorageInfo CreateStorageInfo() StorageInfo[] curStorage = s_updating; try { - int threadId = GetCurrentThreadId(); + int threadId = Environment.CurrentManagedThreadId; StorageInfo newInfo = new StorageInfo(Thread.CurrentThread); // set to updating while potentially resizing/mutating, then we'll @@ -869,6 +856,7 @@ private StorageInfo CreateStorageInfo() newStorage[i] = curStorage[i]; } } + curStorage = newStorage; } @@ -917,7 +905,7 @@ internal static Exception Unreachable { get { - Debug.Assert(false, "Unreachable"); + Debug.Fail("Unreachable"); return new InvalidOperationException("Code supposed to be unreachable"); } } @@ -953,7 +941,7 @@ public static void NotNullItems(IEnumerable items) where T : class [Conditional("DEBUG")] public static void NotEmpty(string str) { - Debug.Assert(!String.IsNullOrEmpty(str)); + Debug.Assert(!string.IsNullOrEmpty(str)); } } @@ -982,11 +970,12 @@ public static DefaultExpression Empty() public static Expression Void(Expression expression) { - //ContractUtils.RequiresNotNull(expression, "expression"); + // ContractUtils.RequiresNotNull(expression, "expression"); if (expression.Type == typeof(void)) { return expression; } + return Expression.Block(expression, Utils.Empty()); } @@ -996,12 +985,13 @@ public static DefaultExpression Default(Type type) { return Empty(); } + return Expression.Default(type); } public static Expression Convert(Expression expression, Type type) { - //ContractUtils.RequiresNotNull(expression, "expression"); + // ContractUtils.RequiresNotNull(expression, "expression"); if (expression.Type == type) { @@ -1074,6 +1064,7 @@ public static bool IsReadWriteAssignment(this ExpressionType type) case ExpressionType.SubtractAssignChecked: return true; } + return false; } } @@ -1082,26 +1073,30 @@ internal static class CollectionExtension { internal static bool TrueForAll(this IEnumerable collection, Predicate predicate) { - //ContractUtils.RequiresNotNull(collection, "collection"); - //ContractUtils.RequiresNotNull(predicate, "predicate"); + // ContractUtils.RequiresNotNull(collection, "collection"); + // ContractUtils.RequiresNotNull(predicate, "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; } @@ -1114,6 +1109,7 @@ internal static int ListHashCode(this IEnumerable list) { h ^= (h << 5) ^ cmp.GetHashCode(t); } + return h; } @@ -1123,6 +1119,7 @@ internal static bool ListEquals(this ICollection first, ICollection sec { return false; } + var cmp = EqualityComparer.Default; var f = first.GetEnumerator(); var s = second.GetEnumerator(); @@ -1135,6 +1132,7 @@ internal static bool ListEquals(this ICollection first, ICollection sec return false; } } + return true; } } diff --git a/src/System.Management.Automation/engine/lang/codegen.cs b/src/System.Management.Automation/engine/lang/codegen.cs index 6e20600406e..16fdf3079ba 100644 --- a/src/System.Management.Automation/engine/lang/codegen.cs +++ b/src/System.Management.Automation/engine/lang/codegen.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Text; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Language @@ -24,16 +24,18 @@ public static string EscapeSingleQuotedStringContent(string value) { return string.Empty; } + StringBuilder sb = new StringBuilder(value.Length); foreach (char c in value) { sb.Append(c); - if (SpecialCharacters.IsSingleQuote(c)) + if (CharExtensions.IsSingleQuote(c)) { // double-up quotes to escape them sb.Append(c); } } + return sb.ToString(); } @@ -49,6 +51,7 @@ public static string EscapeBlockCommentContent(string value) { return string.Empty; } + return value .Replace("<#", "<`#") .Replace("#>", "#`>"); @@ -58,7 +61,7 @@ public static string EscapeBlockCommentContent(string value) /// Escapes content so that it is safe for inclusion in a string that will later be used as a /// format string. If this is to be embedded inside of a single-quoted string, be sure to also /// call EscapeSingleQuotedStringContent. - /// For example: "'" + EscapeSingleQuotedStringContent(EscapeFormatStringContent(userContent)) + "'" -f $args + /// For example: "'" + EscapeSingleQuotedStringContent(EscapeFormatStringContent(userContent)) + "'" -f $args. /// /// The content to be included in a format string. /// Content with all curly braces escaped. @@ -68,16 +71,18 @@ public static string EscapeFormatStringContent(string value) { return string.Empty; } + StringBuilder sb = new StringBuilder(value.Length); foreach (char c in value) { sb.Append(c); - if (SpecialCharacters.IsCurlyBracket(c)) + if (CharExtensions.IsCurlyBracket(c)) { // double-up curly brackets to escape them sb.Append(c); } } + return sb.ToString(); } @@ -95,10 +100,11 @@ public static string EscapeVariableName(string value) { return string.Empty; } + return value .Replace("`", "``") .Replace("}", "`}") .Replace("{", "`{"); } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/lang/interface/PSParseError.cs b/src/System.Management.Automation/engine/lang/interface/PSParseError.cs index c29e49856ef..00a273cc6c0 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSParseError.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSParseError.cs @@ -1,9 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + /********************************************************************++ - Copyright (c) Microsoft Corporation. All rights reserved. Project: PowerShell - Contents: PowerShell error interface for syntax editors Classes: System.Management.Automation.PSParseError diff --git a/src/System.Management.Automation/engine/lang/interface/PSParser.cs b/src/System.Management.Automation/engine/lang/interface/PSParser.cs index 1ea3b176ef9..ade092e18ad 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSParser.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSParser.cs @@ -1,9 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + /********************************************************************++ - Copyright (c) Microsoft Corporation. All rights reserved. Project: PowerShell - Contents: PowerShell parser interface for syntax editors Classes: System.Management.Automation.PSParser @@ -19,7 +20,7 @@ namespace System.Management.Automation { /// - /// PSParser class + /// PSParser class. /// /// /// This is a class providing the interface for parsing a script into a collection of @@ -33,7 +34,6 @@ namespace System.Management.Automation /// /// These three classes are provided for exposing interfaces only. They /// should not be used in PowerShell engine code. - /// /// // // 1. Design @@ -64,7 +64,7 @@ namespace System.Management.Automation public sealed class PSParser { /// - /// Constructor + /// Constructor. /// /// /// This constructor is made private intentionally. The only way to create an instance @@ -134,9 +134,9 @@ private Collection Errors /// /// Parse a script into a collection of tokens. /// - /// script to parse - /// errors happened during parsing - /// collection of tokens generated during parsing + /// Script to parse. + /// Errors happened during parsing. + /// Collection of tokens generated during parsing. /// /// Although this API returns most parse-time exceptions in the errors /// collection, there are some scenarios where resource limits will result @@ -148,7 +148,7 @@ private Collection Errors public static Collection Tokenize(string script, out Collection errors) { if (script == null) - throw PSTraceSource.NewArgumentNullException("script"); + throw PSTraceSource.NewArgumentNullException(nameof(script)); PSParser psParser = new PSParser(); @@ -161,9 +161,9 @@ public static Collection Tokenize(string script, out Collection /// Parse a script into a collection of tokens. /// - /// script to parse, as an array of lines - /// errors happened during parsing - /// collection of tokens generated during parsing + /// Script to parse, as an array of lines. + /// Errors happened during parsing. + /// Collection of tokens generated during parsing. /// /// Although this API returns most parse-time exceptions in the errors /// collection, there are some scenarios where resource limits will result @@ -175,7 +175,7 @@ public static Collection Tokenize(string script, out Collection Tokenize(object[] script, out Collection errors) { if (script == null) - throw PSTraceSource.NewArgumentNullException("script"); + throw PSTraceSource.NewArgumentNullException(nameof(script)); StringBuilder sb = new StringBuilder(); foreach (object obj in script) @@ -185,6 +185,7 @@ public static Collection Tokenize(object[] script, out Collection - /// Map a V3 token to a V2 PSTokenType + /// Map a V3 token to a V2 PSTokenType. /// - /// The V3 token - /// The V2 PSTokenType + /// The V3 token. + /// The V2 PSTokenType. public static PSTokenType GetPSTokenType(Token token) { if ((token.TokenFlags & TokenFlags.CommandName) != 0) { return PSTokenType.Command; } + if ((token.TokenFlags & TokenFlags.MemberName) != 0) { return PSTokenType.Member; } + if ((token.TokenFlags & TokenFlags.AttributeName) != 0) { return PSTokenType.Attribute; } + if ((token.TokenFlags & TokenFlags.TypeName) != 0) { return PSTokenType.Type; } + return s_tokenKindMapping[(int)token.Kind]; } @@ -295,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 @@ -361,243 +368,277 @@ public int Length public enum PSTokenType { /// - /// Unknown token + /// Unknown token. /// - /// - /// Unknown, /// - /// Command - /// - /// + /// + /// Command. + /// + /// /// For example, 'get-process' in /// - /// get-process -name foo - /// - /// + /// get-process -name foo + /// + /// Command, /// - /// Command Parameter - /// - /// + /// + /// Command Parameter. + /// + /// /// For example, '-name' in /// - /// get-process -name foo - /// - /// + /// get-process -name foo + /// + /// CommandParameter, /// - /// Command Argument - /// - /// + /// + /// Command Argument. + /// + /// /// For example, 'foo' in /// - /// get-process -name foo - /// - /// + /// get-process -name foo + /// + /// CommandArgument, /// - /// Number - /// - /// + /// + /// Number. + /// + /// /// For example, 12 in /// - /// $a=12 - /// - /// + /// $a=12 + /// + /// Number, /// - /// String - /// - /// + /// + /// String. + /// + /// /// For example, "12" in /// - /// $a="12" - /// - /// + /// $a="12" + /// + /// String, /// - /// Variable - /// + /// + /// Variable. + /// + /// /// /// For example, $a in /// - /// $a="12" - /// - /// + /// $a="12" + /// + /// Variable, /// - /// Property name or method name - /// - /// + /// + /// Property name or method name. + /// + /// /// For example, Name in /// - /// $a.Name - /// - /// + /// $a.Name + /// + /// Member, /// - /// Loop label - /// - /// + /// + /// Loop label. + /// + /// /// For example, :loop in /// + /// /// :loop /// foreach($a in $b) /// { /// $a /// } - /// - /// + /// + /// LoopLabel, /// - /// Attributes - /// - /// + /// + /// Attributes. + /// + /// /// For example, Mandatory in /// - /// param([Mandatory] $a) - /// - /// + /// param([Mandatory] $a) + /// + /// Attribute, /// - /// Types - /// - /// + /// + /// Types. + /// + /// /// For example, [string] in /// - /// $a = [string] 12 - /// - /// + /// $a = [string] 12 + /// + /// Type, /// - /// Operators - /// - /// + /// + /// Operators. + /// + /// /// For example, + in /// - /// $a = 1 + 2 - /// - /// + /// $a = 1 + 2 + /// + /// Operator, /// - /// Group Starter - /// - /// + /// + /// Group Starter. + /// + /// /// For example, { in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// - /// + /// + /// + /// GroupStart, /// - /// Group Ender - /// - /// + /// + /// Group Ender. + /// + /// /// For example, } in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// - /// + /// + /// + /// GroupEnd, /// - /// Keyword - /// - /// + /// + /// Keyword. + /// + /// /// For example, if in /// + /// /// if ($a -gt 4) /// { /// $a++; /// } - /// - /// + /// + /// + /// Keyword, /// - /// Comment - /// - /// + /// + /// 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 - /// - /// + /// + /// Line continuation. + /// + /// /// For example, ` in /// + /// /// get-command -name ` /// foo - /// - /// + /// + /// + /// LineContinuation, /// - /// Position token - /// - /// - /// Position token are bogus tokens generated for identifying a location + /// + /// Position token. + /// + /// + /// 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 1be5c02a347..184c82c6815 100644 --- a/src/System.Management.Automation/engine/lang/parserutils.cs +++ b/src/System.Management.Automation/engine/lang/parserutils.cs @@ -1,88 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Text; -using System.Text.RegularExpressions; using System.Globalization; using System.Management.Automation.Internal; using System.Management.Automation.Internal.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; + using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings namespace System.Management.Automation { - #region SpecialCharacters - /// - /// Define the various unicode special characters that - /// the parser has to deal with. - /// - internal static class SpecialCharacters - { - public const char enDash = (char)0x2013; - public const char emDash = (char)0x2014; - public const char horizontalBar = (char)0x2015; - - public const char quoteSingleLeft = (char)0x2018; // left single quotation mark - public const char quoteSingleRight = (char)0x2019; // right single quotation mark - public const char quoteSingleBase = (char)0x201a; // single low-9 quotation mark - public const char quoteReversed = (char)0x201b; // single high-reversed-9 quotation mark - public const char quoteDoubleLeft = (char)0x201c; // left double quotation mark - public const char quoteDoubleRight = (char)0x201d; // right double quotation mark - public const char quoteLowDoubleLeft = (char)0x201E;// low double left quote used in german. - - public static bool IsDash(char c) - { - return (c == enDash || c == emDash || c == horizontalBar || c == '-'); - } - public static bool IsSingleQuote(char c) - { - return (c == quoteSingleLeft || c == quoteSingleRight || c == quoteSingleBase || - c == quoteReversed || c == '\''); - } - public static bool IsDoubleQuote(char c) - { - return (c == '"' || c == quoteDoubleLeft || c == quoteDoubleRight || c == quoteLowDoubleLeft); - } - public static bool IsQuote(char c) - { - return (IsSingleQuote(c) || IsDoubleQuote(c)); - } - public static bool IsDelimiter(char c, char delimiter) - { - if (delimiter == '"') return IsDoubleQuote(c); - if (delimiter == '\'') return IsSingleQuote(c); - return (c == delimiter); - } - public static bool IsCurlyBracket(char c) - { - return (c == '{' || c == '}'); - } - /// - /// Canonicalize the quote character - map all of the aliases for " or ' - /// into their ascii equivalent. - /// - /// The character to map - /// The mapped character. - public static char AsQuote(char c) - { - if (IsSingleQuote(c)) return '\''; - if (IsDoubleQuote(c)) return '"'; - return (c); - } - }; - - #endregion SpecialChars - #region Flow Control Exceptions /// @@ -91,11 +28,6 @@ public static char AsQuote(char c) public abstract class FlowControlException : SystemException { internal FlowControlException() { } - - internal FlowControlException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } /// @@ -105,12 +37,7 @@ public abstract class LoopFlowException : FlowControlException { internal LoopFlowException(string label) { - this.Label = label ?? ""; - } - - internal LoopFlowException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + this.Label = label ?? string.Empty; } internal LoopFlowException() { } @@ -135,7 +62,7 @@ internal static bool MatchLoopLabel(string flowLabel, string loopLabel) // If the flow statement has no label, it always matches (because it just means, break or continue from // the most nested loop.) Otherwise, compare the labels. - return String.IsNullOrEmpty(flowLabel) || flowLabel.Equals(loopLabel, StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(flowLabel) || flowLabel.Equals(loopLabel, StringComparison.OrdinalIgnoreCase); } } @@ -154,19 +81,14 @@ internal BreakException(string label) internal BreakException() { } [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")] - internal BreakException(String label, Exception innerException) + internal BreakException(string label, Exception innerException) : base(label) { } - - private BreakException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } /// - /// Flow control ContinueException + /// Flow control ContinueException. /// public sealed class ContinueException : LoopFlowException { @@ -180,15 +102,10 @@ internal ContinueException(string label) internal ContinueException() { } [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")] - internal ContinueException(String label, Exception innerException) + internal ContinueException(string label, Exception innerException) : base(label) { } - - private ContinueException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } internal class ReturnException : FlowControlException @@ -213,18 +130,12 @@ internal ExitException(object argument) } /// - /// Argument + /// Argument. /// public object Argument { get; internal set; } [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) - { - } } /// @@ -253,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 @@ -314,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. /// @@ -341,6 +252,7 @@ static ParserOps() private const int _MinCache = -100; private const int _MaxCache = 1000; + private static readonly object[] s_integerCache = new object[_MaxCache - _MinCache]; private static readonly string[] s_chars = new string[255]; internal static readonly object _TrueObject = (object)true; @@ -360,14 +272,15 @@ internal static object BoolToObject(bool value) /// /// Convert an object into an int, avoiding boxing small integers... /// - /// The int to convert - /// The reference equivalent + /// The int to convert. + /// The reference equivalent. internal static object IntToObject(int value) { if (value < _MaxCache && value >= _MinCache) { return s_integerCache[value - _MinCache]; } + return (object)value; } @@ -385,7 +298,7 @@ internal static PSObject WrappedNumber(object data, string text) /// /// The position to use for error reporting. /// - /// The result could not be represented as an integer + /// The result could not be represented as an integer. internal static int FixNum(object obj, IScriptExtent errorPosition) { obj = PSObject.Base(obj); @@ -417,19 +330,20 @@ internal static T ConvertTo(object obj, IScriptExtent errorPosition) re.ErrorRecord.SetInvocationInfo(new InvocationInfo(null, errorPosition)); throw re; } + return result; } /// - /// private method used to call the op_* operations for the math operators + /// Private method used to call the op_* operations for the math operators. /// - /// left operand - /// right operand - /// name of the operation method to perform + /// Left operand. + /// Right operand. + /// Name of the operation method to perform. /// The position to use for error reporting. - /// the string to use in error messages representing the op - /// The result of the operation - /// An error occurred performing the operation, see inner exception + /// The string to use in error messages representing the op. + /// The result of the operation. + /// An error occurred performing the operation, see inner exception. internal static object ImplicitOp(object lval, object rval, string op, IScriptExtent errorPosition, string errorOp) { // Get the base object. At somepoint, we may allow users to dynamically extend @@ -438,10 +352,10 @@ 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.GetTypeInfo().IsPrimitive)) + if (lvalType == null || (lvalType.IsPrimitive)) { // Prefer the LHS type when looking for the operator, but attempt the right // the lhs can't have an operator. @@ -450,7 +364,7 @@ internal static object ImplicitOp(object lval, object rval, string op, IScriptEx // would look for overloads in both types, but this logic covers the most common // cases. - opType = (rvalType == null || rvalType.GetTypeInfo().IsPrimitive) ? null : rvalType; + opType = (rvalType == null || rvalType.IsPrimitive) ? null : rvalType; } else { @@ -512,7 +426,7 @@ private static object[] unfoldTuple(ExecutionContext context, IScriptExtent erro // uses "yield" from C# 2.0, which automatically creates // an enumerable out of the loop code. See - // http://msdn.microsoft.com/msdnmag/issues/04/05/C20/ for + // https://msdn.microsoft.com/msdnmag/issues/04/05/C20/ for // more details. private static IEnumerable enumerateContent(ExecutionContext context, IScriptExtent errorPosition, SplitImplOptions implOptions, object tuple) { @@ -530,22 +444,35 @@ private static IEnumerable enumerateContent(ExecutionContext context, IS private static RegexOptions parseRegexOptions(SplitOptions options) { - int[][] map = { - new int[] { (int)SplitOptions.CultureInvariant, (int)RegexOptions.CultureInvariant }, - new int[] { (int)SplitOptions.IgnorePatternWhitespace, (int)RegexOptions.IgnorePatternWhitespace }, - new int[] { (int)SplitOptions.Multiline, (int)RegexOptions.Multiline }, - new int[] { (int)SplitOptions.Singleline, (int)RegexOptions.Singleline }, - new int[] { (int)SplitOptions.IgnoreCase, (int)RegexOptions.IgnoreCase }, - new int[] { (int)SplitOptions.ExplicitCapture, (int)RegexOptions.ExplicitCapture }, - }; - RegexOptions result = RegexOptions.None; - foreach (int[] entry in map) + if ((options & SplitOptions.CultureInvariant) != 0) { - if (((int)options & entry[0]) != 0) - { - result |= (RegexOptions)entry[1]; - } + result |= RegexOptions.CultureInvariant; + } + + if ((options & SplitOptions.IgnorePatternWhitespace) != 0) + { + result |= RegexOptions.IgnorePatternWhitespace; + } + + if ((options & SplitOptions.Multiline) != 0) + { + result |= RegexOptions.Multiline; + } + + if ((options & SplitOptions.Singleline) != 0) + { + result |= RegexOptions.Singleline; + } + + if ((options & SplitOptions.IgnoreCase) != 0) + { + result |= RegexOptions.IgnoreCase; + } + + if ((options & SplitOptions.ExplicitCapture) != 0) + { + result |= RegexOptions.ExplicitCapture; } return result; @@ -564,15 +491,7 @@ internal static object SplitOperator(ExecutionContext context, IScriptExtent err return SplitOperatorImpl(context, errorPosition, lval, rval, SplitImplOptions.None, ignoreCase); } - private static void ExtendList(IList list, IList items) - { - foreach (T item in items) - { - list.Add(item); - } - } - - private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, SplitImplOptions implOptions, bool ignoreCase) + private static IReadOnlyList SplitOperatorImpl(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, SplitImplOptions implOptions, bool ignoreCase) { IEnumerable content = enumerateContent(context, errorPosition, implOptions, lval); @@ -596,6 +515,7 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException), errorPosition, "BadOperatorArgument", ParserStrings.BadOperatorArgument, "-split", rval); } + if (args.Length >= 2) limit = FixNum(args[1], errorPosition); if (args.Length >= 3 && args[2] != null) @@ -609,6 +529,7 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent throw InterpreterError.NewInterpreterException(null, typeof(ParseException), errorPosition, "InvalidSplitOptionWithPredicate", ParserStrings.InvalidSplitOptionWithPredicate); } + if (ignoreCase && (options & SplitOptions.IgnoreCase) == 0) { options |= SplitOptions.IgnoreCase; @@ -620,92 +541,176 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent options |= SplitOptions.IgnoreCase; } - if (predicate != null) + if (predicate == null) + { + return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options); + } + else if (limit >= 0) { return SplitWithPredicate(context, errorPosition, content, predicate, limit); } else { - return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options); + return NegativeSplitWithPredicate(context, errorPosition, content, predicate, limit); } } - private static object SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable content, ScriptBlock predicate, int limit) + private static IReadOnlyList NegativeSplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable content, ScriptBlock predicate, int limit) { - List results = new List(); + var results = new List(); + + if (limit == -1) + { + // If the user just wants 1 string + // then just return the content + return new List(content); + } + foreach (string item in content) { - List split = new List(); + var split = new List(); + + // Used to traverse through the item + int cursor = item.Length - 1; + + int subStringLength = 0; + + for (int charCount = 0; charCount < item.Length; charCount++) + { + // Evaluate the predicate using the character at cursor. + object predicateResult = predicate.DoInvokeReturnAsIs( + useLocalScope: true, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe, + dollarUnder: CharToString(item[cursor]), + input: AutomationNull.Value, + scriptThis: AutomationNull.Value, + args: new object[] { item, cursor }); + + if (!LanguagePrimitives.IsTrue(predicateResult)) + { + subStringLength++; + cursor -= 1; + continue; + } + + split.Add(item.Substring(cursor + 1, subStringLength)); + + subStringLength = 0; - if (limit == 1) + cursor -= 1; + + if (System.Math.Abs(limit) == (split.Count + 1)) + { + break; + } + } + + if (cursor == -1) + { + // Used when the limit is negative + // and the cursor was allowed to go + // all the way to the start of the + // string. + split.Add(item.Substring(0, subStringLength)); + } + else { - // Don't bother with looking for any delimiters, - // just return the original string. - results.Add(item); - continue; + // Used to get the rest of the string + // when using a negative limit and + // the cursor doesn't reach the end + // of the string. + split.Add(item.Substring(0, cursor + 1)); } - StringBuilder buf = new StringBuilder(); - for (int strIndex = 0; strIndex < item.Length; strIndex++) + split.Reverse(); + + results.AddRange(split); + } + + return results.ToArray(); + } + + private static IReadOnlyList SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable content, ScriptBlock predicate, int limit) + { + var results = new List(); + + if (limit == 1) + { + // If the user just wants 1 string + // then just return the content + return new List(content); + } + + foreach (string item in content) + { + var split = new List(); + + // Used to traverse through the item + int cursor = 0; + + // This is used to calculate how much to split from item. + int subStringLength = 0; + + for (int charCount = 0; charCount < item.Length; charCount++) { - object isDelimChar = predicate.DoInvokeReturnAsIs( + // Evaluate the predicate using the character at cursor. + object predicateResult = predicate.DoInvokeReturnAsIs( useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe, - dollarUnder: CharToString(item[strIndex]), + dollarUnder: CharToString(item[cursor]), input: AutomationNull.Value, scriptThis: AutomationNull.Value, - args: new object[] { item, strIndex }); - if (LanguagePrimitives.IsTrue(isDelimChar)) + args: new object[] { item, cursor }); + + // If the current character is not a delimiter + // then it must be included into a substring. + if (!LanguagePrimitives.IsTrue(predicateResult)) { - split.Add(buf.ToString()); - buf = new StringBuilder(); + subStringLength++; - if (limit > 0 && split.Count >= (limit - 1)) - { - // We're one item below the limit. If - // we have any string left, go ahead - // and add it as the last item, otherwise - // add an empty string if there was - // a delimiter at the end. - if ((strIndex + 1) < item.Length) - { - split.Add(item.Substring(strIndex + 1)); - } - else - { - split.Add(""); - } - break; - } + cursor += 1; - // If this delimiter is at the end of the string, - // add an empty string to denote the item "after" - // it. - if (strIndex == (item.Length - 1)) - { - split.Add(""); - } + continue; } - else + + // Else, if the character is a delimiter + // then add a substring to the split list. + split.Add(item.Substring(cursor - subStringLength, subStringLength)); + + subStringLength = 0; + + cursor += 1; + + if (limit == (split.Count + 1)) { - buf.Append(item[strIndex]); + break; } } - // Add any remainder, if we're under the limit. - if (buf.Length > 0 && - (limit <= 0 || split.Count < limit)) + if (cursor == item.Length) { - split.Add(buf.ToString()); + // Used to get the rest of the string + // when the limit is not negative and + // the cursor is allowed to make it to + // the end of the string. + split.Add(item.Substring(cursor - subStringLength, subStringLength)); + } + else + { + // Used to get the rest of the string + // when the limit is not negative and + // the cursor is not at the end of the + // string. + split.Add(item.Substring(cursor, item.Length - cursor)); } - ExtendList(results, split); + results.AddRange(split); } return results.ToArray(); } - private static object SplitWithPattern(ExecutionContext context, IScriptExtent errorPosition, IEnumerable content, string separatorPattern, int limit, SplitOptions options) + private static IReadOnlyList SplitWithPattern(ExecutionContext context, IScriptExtent errorPosition, IEnumerable content, string separatorPattern, int limit, SplitOptions options) { // Default to Regex matching if no match specified. if ((options & SplitOptions.SimpleMatch) == 0 && @@ -728,21 +733,25 @@ private static object SplitWithPattern(ExecutionContext context, IScriptExtent e separatorPattern = Regex.Escape(separatorPattern); } - if (limit < 0) + RegexOptions regexOptions = parseRegexOptions(options); + + int calculatedLimit = limit; + + // If the limit is negative then set Regex to read from right to left + if (calculatedLimit < 0) { - // Regex only allows 0 to signify "no limit", whereas - // we allow any integer <= 0. - limit = 0; + regexOptions |= RegexOptions.RightToLeft; + calculatedLimit *= -1; } - RegexOptions regexOptions = parseRegexOptions(options); Regex regex = NewRegex(separatorPattern, regexOptions); - List results = new List(); + var results = new List(); + foreach (string item in content) { - string[] split = regex.Split(item, limit, 0); - ExtendList(results, split); + string[] split = regex.Split(item, calculatedLimit); + results.AddRange(split); } return results.ToArray(); @@ -751,23 +760,23 @@ private static object SplitWithPattern(ExecutionContext context, IScriptExtent e /// /// Implementation of the PowerShell unary -join operator... /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// The result of the operator + /// Left operand. + /// The result of the operator. internal static object UnaryJoinOperator(ExecutionContext context, IScriptExtent errorPosition, object lval) { - return JoinOperator(context, errorPosition, lval, ""); + return JoinOperator(context, errorPosition, lval, string.Empty); } /// - /// Implementation of the PowerShell binary -join operator + /// Implementation of the PowerShell binary -join operator. /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// The result of the operator + /// Left operand. + /// Right operand. + /// The result of the operator. internal static object JoinOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval) { string separator = PSObject.ToStringParser(context, rval); @@ -785,19 +794,104 @@ internal static object JoinOperator(ExecutionContext context, IScriptExtent erro } } + /// + /// The implementation of the PowerShell range operator. + /// + /// The object on which to start. + /// The object on which to stop. + /// The array of objects. + internal static object RangeOperator(object lval, object rval) + { + var lbase = PSObject.Base(lval); + var rbase = PSObject.Base(rval); + + // If both arguments is [char] type or [string] type with length==1 + // return objects of [char] type. + // In special case "0".."9" return objects of [int] type. + if (AsChar(lbase) is char lc && AsChar(rbase) is char rc) + { + return CharOps.Range(lc, rc); + } + + // As a last resort, the range operator tries to return objects of [int] type. + // 1..10 + // "1".."10" + // [int]"1"..[int]"10" + var l = Convert.ToInt32(lbase); + var r = Convert.ToInt32(rbase); + + return IntOps.Range(l, r); + } + + /// + /// The implementation of an enumerator for the PowerShell range operator. + /// + /// The object on which to start. + /// The object on which to stop. + /// The enumerator. + internal static IEnumerator GetRangeEnumerator(object lval, object rval) + { + var lbase = PSObject.Base(lval); + var rbase = PSObject.Base(rval); + + // If both arguments is [char] type or [string] type with length==1 + // return objects of [char] type. + // In special case "0".."9" return objects of [int] type. + if (AsChar(lbase) is char lc && AsChar(rbase) is char rc) + { + return new CharRangeEnumerator(lc, rc); + } + + // As a last resort, the range operator tries to return objects of [int] type. + // 1..10 + // "1".."10" + // [int]"1"..[int]"10" + var l = Convert.ToInt32(lbase); + var r = Convert.ToInt32(rbase); + + return new RangeEnumerator(l, r); + } + + // Help function for Range operator. + // + // In common case: + // for [char] type + // for [string] type and length == 1 + // return objects of [char] type: + // [char]'A'..[char]'Z' + // [char]'A'..'Z' + // [char]'A'.."Z" + // 'A'..[char]'Z' + // "A"..[char]'Z' + // "A".."Z" + // [char]"A"..[string]"Z" + // "A"..[char]"Z" + // [string]"A".."Z" + // and so on. + // + // In special case: + // "0".."9" + // return objects of [int] type. + private static object AsChar(object obj) + { + if (obj is char) return obj; + if (obj is string str && str.Length == 1 && !char.IsDigit(str, 0)) return str[0]; + return null; + } + /// /// The implementation of the PowerShell -replace operator.... /// - /// The execution context in which to evaluate the expression + /// The execution context in which to evaluate the expression. /// The position to use for error reporting. - /// The object on which to replace the values + /// The object on which to replace the values. /// The replacement description. - /// True for -ireplace/-replace, false for -creplace - /// The result of the operator + /// True for -ireplace/-replace, false for -creplace. + /// The result of the operator. internal static object ReplaceOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool ignoreCase) { - string replacement = ""; - object pattern = ""; + object pattern = string.Empty; + object substitute = string.Empty; rval = PSObject.Base(rval); IList rList = rval as IList; @@ -807,7 +901,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e { // only allow 1 or 2 arguments to -replace throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException), errorPosition, - "BadReplaceArgument", ParserStrings.BadReplaceArgument, ignoreCase ? "-ireplace" : "-replace", rList.Count); + "BadReplaceArgument", ParserStrings.BadReplaceArgument, errorPosition.Text, rList.Count); } if (rList.Count > 0) @@ -815,7 +909,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e pattern = rList[0]; if (rList.Count > 1) { - replacement = PSObject.ToStringParser(context, rList[1]); + substitute = PSObject.Base(rList[1]); } } } @@ -845,13 +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 = lval?.ToString() ?? String.Empty; + string lvalString = PSObject.ToStringParser(context, lval) ?? string.Empty; - // Find a single match in the string. - return rr.Replace(lvalString, replacement); + return replacer.Replace(lvalString); } else { @@ -859,22 +953,95 @@ 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(rr.Replace(lvalString, replacement)); + resultList.Add(replacer.Replace(lvalString)); } return resultList.ToArray(); } } + private struct ReplaceOperatorImpl + { + 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) + { + _regex = regex; + _cachedReplacementString = null; + _cachedMatchEvaluator = null; + + switch (substitute) + { + case string replacement: + _cachedReplacementString = replacement; + break; + + case ScriptBlock sb: + _cachedMatchEvaluator = GetMatchEvaluator(context, sb); + break; + + case object val when LanguagePrimitives.TryConvertTo(val, out _cachedMatchEvaluator): + break; + + default: + _cachedReplacementString = PSObject.ToStringParser(context, substitute); + break; + } + } + + // 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()); + + 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); + } + } + /// /// Implementation of the PowerShell type operators... /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// The result of the operator + /// Left operand. + /// Right operand. + /// The result of the operator. internal static object IsOperator(ExecutionContext context, IScriptExtent errorPosition, object left, object right) { object lval = PSObject.Base(left); @@ -911,11 +1078,11 @@ internal static object IsOperator(ExecutionContext context, IScriptExtent errorP /// /// Implementation of the PowerShell type operators... /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// The result of the operator + /// Left operand. + /// Right operand. + /// The result of the operator. internal static object IsNotOperator(ExecutionContext context, IScriptExtent errorPosition, object left, object right) { object lval = PSObject.Base(left); @@ -949,16 +1116,15 @@ internal static object IsNotOperator(ExecutionContext context, IScriptExtent err return BoolToObject(!rType.IsInstanceOfType(lval)); } - /// - /// Implementation of the PowerShell -like operator + /// Implementation of the PowerShell -like operator. /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// the operator - /// The result of the operator + /// Left operand. + /// Right operand. + /// The operator. + /// The result of the operator. internal static object LikeOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, TokenKind @operator) { var wcp = rval as WildcardPattern; @@ -973,7 +1139,7 @@ internal static object LikeOperator(ExecutionContext context, IScriptExtent erro IEnumerator list = LanguagePrimitives.GetEnumerator(lval); if (list == null) { - string lvalString = lval == null ? String.Empty : PSObject.ToStringParser(context, lval); + string lvalString = lval == null ? string.Empty : PSObject.ToStringParser(context, lval); return BoolToObject(wcp.IsMatch(lvalString) ^ notLike); } @@ -984,7 +1150,7 @@ internal static object LikeOperator(ExecutionContext context, IScriptExtent erro { object val = ParserOps.Current(errorPosition, list); - string lvalString = val == null ? String.Empty : PSObject.ToStringParser(context, val); + string lvalString = val == null ? string.Empty : PSObject.ToStringParser(context, val); if (wcp.IsMatch(lvalString) ^ notLike) { @@ -996,33 +1162,30 @@ internal static object LikeOperator(ExecutionContext context, IScriptExtent erro } /// - /// Implementation of the PowerShell -match operator + /// Implementation of the PowerShell -match operator. /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// ignore case? - /// true for -notmatch, false for -match - /// The result of the operator + /// Left operand. + /// Right operand. + /// Ignore case? + /// True for -notmatch, false for -match. + /// The result of the operator. internal static object MatchOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool notMatch, bool ignoreCase) { RegexOptions reOptions = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None; // if passed an explicit regex, just use it // otherwise compile the expression. - Regex r = PSObject.Base(rval) as Regex; - if (r == null) - { - // In this situation, creation of Regex should not fail. We are not - // processing ArgumentException in this case. - r = NewRegex(PSObject.ToStringParser(context, rval), reOptions); - } + // In this situation, creation of Regex should not fail. We are not + // processing ArgumentException in this case. + Regex r = PSObject.Base(rval) as Regex + ?? NewRegex(PSObject.ToStringParser(context, rval), reOptions); IEnumerator list = LanguagePrimitives.GetEnumerator(lval); if (list == null) { - string lvalString = lval == null ? String.Empty : PSObject.ToStringParser(context, lval); + string lvalString = lval == null ? string.Empty : PSObject.ToStringParser(context, lval); // Find a match in the string. Match m = r.Match(lvalString); @@ -1065,7 +1228,7 @@ internal static object MatchOperator(ExecutionContext context, IScriptExtent err { object val = list.Current; - string lvalString = val == null ? String.Empty : PSObject.ToStringParser(context, val); + string lvalString = val == null ? string.Empty : PSObject.ToStringParser(context, val); // Find a single match in the string. Match m = r.Match(lvalString); @@ -1074,6 +1237,7 @@ internal static object MatchOperator(ExecutionContext context, IScriptExtent err { resultList.Add(val); } + if (check++ > 1000) { // Check to see if we're stopping every one in a while... @@ -1133,13 +1297,13 @@ internal static bool ContainsOperatorCompiled(ExecutionContext context, /// /// Implementation of the PowerShell -contains/-notcontains operators (and case sensitive variants) /// - /// The execution context to use + /// The execution context to use. /// The position to use for error reporting. - /// left operand - /// right operand - /// ignore case? - /// true for -contains, false for -notcontains - /// The result of the operator + /// Left operand. + /// Right operand. + /// Ignore case? + /// True for -contains, false for -notcontains. + /// The result of the operator. internal static object ContainsOperator(ExecutionContext context, IScriptExtent errorPosition, object left, object right, bool contains, bool ignoreCase) { IEnumerator list = LanguagePrimitives.GetEnumerator(left); @@ -1188,45 +1352,48 @@ internal static object CompareOperators(ExecutionContext context, IScriptExtent } /// - /// Cache regular expressions... + /// Cache regular expressions. /// - /// The string to find the pattern for - /// The options used to create the regex... - /// A case-insensitive Regex... + /// The string to find the pattern for. + /// The options used to create the regex. + /// New or cached Regex. internal static Regex NewRegex(string patternString, RegexOptions options) { - if (options != RegexOptions.IgnoreCase) - return new Regex(patternString, options); - - lock (s_regexCache) + var subordinateRegexCache = s_regexCache.GetOrAdd(options, s_subordinateRegexCacheCreationDelegate); + if (subordinateRegexCache.TryGetValue(patternString, out Regex result)) { - Regex result; - if (s_regexCache.TryGetValue(patternString, out result)) - { - return result; - } - else + return result; + } + else + { + if (subordinateRegexCache.Count > MaxRegexCache) { - if (s_regexCache.Count > MaxRegexCache) - s_regexCache.Clear(); - Regex re = new Regex(patternString, RegexOptions.IgnoreCase); - s_regexCache.Add(patternString, re); - return re; + // TODO: it would be useful to get a notice (in telemetry?) if the cache is full. + subordinateRegexCache.Clear(); } + + var regex = new Regex(patternString, options); + return subordinateRegexCache.GetOrAdd(patternString, regex); } } - private static Dictionary s_regexCache = new Dictionary(); + + private static readonly ConcurrentDictionary> s_regexCache = + new ConcurrentDictionary>(); + + private static readonly Func> s_subordinateRegexCacheCreationDelegate = + key => new ConcurrentDictionary(StringComparer.Ordinal); + private const int MaxRegexCache = 1000; /// /// A routine used to advance an enumerator and catch errors that might occur - /// performing the operation + /// performing the operation. /// - /// The execution context used to see if the pipeline is stopping + /// The execution context used to see if the pipeline is stopping. /// The position to use for error reporting. /// THe enumerator to advance. - /// An error occurred moving to the next element in the enumeration - /// True if the move succeeded + /// An error occurred moving to the next element in the enumeration. + /// True if the move succeeded. internal static bool MoveNext(ExecutionContext context, IScriptExtent errorPosition, IEnumerator enumerator) { try @@ -1260,7 +1427,7 @@ internal static bool MoveNext(ExecutionContext context, IScriptExtent errorPosit /// Wrapper caller for enumerator.MoveNext - handles and republishes errors... /// /// The position to use for error reporting. - /// The enumerator to read from + /// The enumerator to read from. /// internal static object Current(IScriptExtent errorPosition, IEnumerator enumerator) { @@ -1288,44 +1455,43 @@ internal static object Current(IScriptExtent errorPosition, IEnumerator enumerat } /// - /// Retrieves the obj's type full name + /// Retrieves the obj's type full name. /// - /// the object we want to retrieve the type's full name from - /// The obj's type full name + /// The object we want to retrieve the type's full name from. + /// The obj's type full name. internal static string GetTypeFullName(object obj) { if (obj == null) { - return String.Empty; + return string.Empty; } - PSObject mshObj = obj as PSObject; - if (mshObj == null) + + if (obj is not PSObject mshObj) { return obj.GetType().FullName; } + if (mshObj.InternalTypeNames.Count == 0) { return typeof(PSObject).FullName; } + return mshObj.InternalTypeNames[0]; } - - - /// /// Launch a method on an object. This will handle .NET native methods, COM /// 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 name of the method to call - /// Invocation constraints + /// 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. /// Set to true if you want to call a static method. - /// If not automation null, then this must be a settable property - /// Wraps the exception returned from the method call - /// Internal exception from a flow control statement + /// If not automation null, then this must be a settable property. + /// Wraps the exception returned from the method call. + /// Internal exception from a flow control statement. /// internal static object CallMethod( IScriptExtent errorPosition, @@ -1349,6 +1515,7 @@ internal static object CallMethod( // "you can't call a method on null" throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), errorPosition, "InvokeMethodOnNull", ParserStrings.InvokeMethodOnNull); } + targetBase = PSObject.Base(target); targetAsPSObject = PSObject.AsPSObject(target); @@ -1403,13 +1570,12 @@ 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); } + propertyToSet.InvokeSet(valueToSet, paramArray); return valueToSet; } @@ -1454,7 +1620,7 @@ internal static object CallMethod( throw InterpreterError.NewInterpreterExceptionByMessage(typeof(RuntimeException), errorPosition, e.Message, "MethodInvocationException", e); } - } // CallMethod + } } #endregion ParserOps @@ -1466,19 +1632,28 @@ 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 { get { return _upperBound; } } private int _current; - public object Current + + object IEnumerator.Current + { + get { return Current; } + } + + public virtual int Current { get { return _current; } } @@ -1488,7 +1663,7 @@ internal int CurrentValue get { return _current; } } - private int _increment = 1; + private readonly int _increment = 1; private bool _firstElement = true; @@ -1514,6 +1689,7 @@ public bool MoveNext() _firstElement = false; return true; } + if (_current == _upperBound) return false; @@ -1521,16 +1697,74 @@ public bool MoveNext() return true; } } + + /// + /// The simple enumerator class is used for the range operator '..' + /// in expressions like 'A'..'B' | ForEach-Object { $_ } + /// + internal class CharRangeEnumerator : IEnumerator + { + private readonly int _increment = 1; + + private bool _firstElement = true; + + public CharRangeEnumerator(char lowerBound, char upperBound) + { + LowerBound = lowerBound; + Current = lowerBound; + UpperBound = upperBound; + if (lowerBound > upperBound) + _increment = -1; + } + + object IEnumerator.Current + { + get { return Current; } + } + + internal char LowerBound { get; } + + internal char UpperBound { get; } + + public char Current + { + get; private set; + } + + public bool MoveNext() + { + if (_firstElement) + { + _firstElement = false; + return true; + } + + if (Current == UpperBound) + { + return false; + } + + Current = (char)(Current + _increment); + return true; + } + + public void Reset() + { + Current = LowerBound; + _firstElement = true; + } + } + #endregion RangeEnumerator #region InterpreterError internal static class InterpreterError { /// - /// Create a new instance of an interpreter exception + /// Create a new instance of an interpreter exception. /// /// The target object for this exception. - /// Type of exception to build + /// Type of exception to build. /// The position to use for error reporting. /// /// ResourceID to look up template message, and also ErrorID @@ -1538,8 +1772,8 @@ internal static class InterpreterError /// /// Resource string that holds the error message /// - /// Insertion parameters to message - /// A new instance of the specified exception type + /// Insertion parameters to message. + /// A new instance of the specified exception type. internal static RuntimeException NewInterpreterException(object targetObject, Type exceptionType, IScriptExtent errorPosition, string resourceIdAndErrorId, string resourceString, params object[] args) { @@ -1547,10 +1781,10 @@ internal static RuntimeException NewInterpreterException(object targetObject, } /// - /// Create a new instance of an interpreter exception + /// Create a new instance of an interpreter exception. /// - /// The object associated with the problem - /// Type of exception to build + /// The object associated with the problem. + /// Type of exception to build. /// The position to use for error reporting. /// /// ResourceID to look up template message, and also ErrorID @@ -1558,15 +1792,15 @@ internal static RuntimeException NewInterpreterException(object targetObject, /// /// Resource string which holds the error message /// - /// inner exception - /// Insertion parameters to message - /// New instance of an interpreter exception + /// Inner exception. + /// Insertion parameters to message. + /// New instance of an interpreter exception. internal static RuntimeException NewInterpreterExceptionWithInnerException(object targetObject, Type exceptionType, IScriptExtent errorPosition, string resourceIdAndErrorId, string resourceString, Exception innerException, params object[] args) { // errToken may be null - if (String.IsNullOrEmpty(resourceIdAndErrorId)) - throw PSTraceSource.NewArgumentException("resourceIdAndErrorId"); + if (string.IsNullOrEmpty(resourceIdAndErrorId)) + throw PSTraceSource.NewArgumentException(nameof(resourceIdAndErrorId)); // innerException may be null // args may be null or empty @@ -1575,7 +1809,7 @@ internal static RuntimeException NewInterpreterExceptionWithInnerException(objec try { string message; - if (null == args || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -1584,7 +1818,8 @@ internal static RuntimeException NewInterpreterExceptionWithInnerException(objec { message = StringUtil.Format(resourceString, args); } - if (String.IsNullOrEmpty(message)) + + if (string.IsNullOrEmpty(message)) { Dbg.Assert(false, "Could not load text for parser exception '" @@ -1620,27 +1855,28 @@ internal static RuntimeException NewInterpreterExceptionWithInnerException(objec + "' due to FormatException " + e.Message); rte = NewBackupInterpreterException(exceptionType, errorPosition, resourceIdAndErrorId, e); } + rte.SetTargetObject(targetObject); return rte; } /// - /// Create a new instance of an interpreter exception + /// Create a new instance of an interpreter exception. /// - /// Type of exception to build + /// Type of exception to build. /// The position to use for error reporting. - /// Message - /// ErrorID - /// inner exception - /// New instance of ParseException + /// Message. + /// ErrorID. + /// Inner exception. + /// New instance of ParseException. internal static RuntimeException NewInterpreterExceptionByMessage( Type exceptionType, IScriptExtent errorPosition, string message, string errorId, Exception innerException) { // errToken may be null // only assert -- be permissive at runtime - Dbg.Assert(!String.IsNullOrEmpty(message), "message was null or empty"); - Dbg.Assert(!String.IsNullOrEmpty(errorId), "errorId was null or empty"); + Dbg.Assert(!string.IsNullOrEmpty(message), "message was null or empty"); + Dbg.Assert(!string.IsNullOrEmpty(errorId), "errorId was null or empty"); // innerException may be null RuntimeException e; @@ -1674,7 +1910,7 @@ private static RuntimeException NewBackupInterpreterException( Exception innerException) { string message; - if (null == innerException) + if (innerException == null) { // there is no reason this string lookup should fail message = StringUtil.Format(ParserStrings.BackupParserMessage, errorId); @@ -1711,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 @@ -1734,7 +1986,7 @@ internal static void Trace(ExecutionContext context, int level, string messageId if (context.PSDebugTraceLevel > level) { string message; - if (null == args || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -1743,7 +1995,8 @@ internal static void Trace(ExecutionContext context, int level, string messageId { message = StringUtil.Format(resourceString, args); } - if (String.IsNullOrEmpty(message)) + + if (string.IsNullOrEmpty(message)) { message = "Could not load text for msh script tracing message id '" + messageId + "'"; Dbg.Assert(false, message); @@ -1755,4 +2008,3 @@ internal static void Trace(ExecutionContext context, int level, string messageId } #endregion ScriptTrace } - diff --git a/src/System.Management.Automation/engine/lang/scriptblock.cs b/src/System.Management.Automation/engine/lang/scriptblock.cs index 5c4a5fef5cf..06fa66d0f7c 100644 --- a/src/System.Management.Automation/engine/lang/scriptblock.cs +++ b/src/System.Management.Automation/engine/lang/scriptblock.cs @@ -1,23 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.Reflection; -using System.Runtime.Serialization; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; namespace System.Management.Automation { @@ -25,7 +24,6 @@ namespace System.Management.Automation /// An object representing a pre-compiled block of powershell script. /// /// - /// /// This class track a block of script in a compiled form. It is also /// used for direct invocation of the script block. /// @@ -81,14 +79,13 @@ namespace System.Management.Automation /// runspace API. /// /// This class will handle the logic for direct invocation of script blocks. - /// /// public partial class ScriptBlock { /// /// Create a script block object based on a script string to be parsed immediately. /// - /// Engine context for this script block + /// Engine context for this script block. /// The string to compile. internal static ScriptBlock Create(ExecutionContext context, string script) { @@ -97,6 +94,7 @@ internal static ScriptBlock Create(ExecutionContext context, string script) { sb.SessionStateInternal = context.EngineSessionState; } + return sb; } @@ -105,15 +103,13 @@ internal static ScriptBlock Create(ExecutionContext context, string script) /// context is provided. /// /// The string to compile. - public static ScriptBlock Create(string script) - { - return Create(new Language.Parser(), null, script); - } + public static ScriptBlock Create(string script) => Create( + parser: new Parser(), + fileName: null, + fileContents: script); internal static ScriptBlock CreateDelayParsedScriptBlock(string script, bool isProductCode) - { - return new ScriptBlock(new CompiledScriptBlockData(script, isProductCode)); - } + => new ScriptBlock(new CompiledScriptBlockData(script, isProductCode)) { DebuggerHidden = true }; /// /// Returns a new scriptblock bound to a module. Any local variables in the @@ -128,7 +124,7 @@ public ScriptBlock GetNewClosure() } /// - /// Returns PowerShell object representing the pipeline contained in this ScriptBlock + /// Returns PowerShell object representing the pipeline contained in this ScriptBlock. /// /// /// Some ScriptBlocks are too complicated to be converted into a PowerShell object. @@ -167,11 +163,13 @@ public ScriptBlock GetNewClosure() /// /// Thrown when there is no ExecutionContext associated with this ScriptBlock object. /// - public PowerShell GetPowerShell(params object[] args) - { - ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); - return GetPowerShellImpl(context, null, false, false, null, args); - } + public PowerShell GetPowerShell(params object[] args) => GetPowerShellImpl( + context: LocalPipeline.GetExecutionContextFromTLS(), + variables: null, + isTrustedInput: false, + filterNonUsingVariables: false, + createLocalScope: null, + args); /// /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, @@ -189,10 +187,13 @@ public PowerShell GetPowerShell(params object[] args) /// can be null /// public PowerShell GetPowerShell(bool isTrustedInput, params object[] args) - { - ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); - return GetPowerShellImpl(context, null, isTrustedInput, false, null, args); - } + => GetPowerShellImpl( + context: LocalPipeline.GetExecutionContextFromTLS(), + variables: null, + isTrustedInput, + filterNonUsingVariables: false, + createLocalScope: null, + args); /// /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables @@ -234,6 +235,7 @@ public PowerShell GetPowerShell(Dictionary variables, params obj suppliedVariables = new Dictionary(variables, StringComparer.OrdinalIgnoreCase); context = null; } + return GetPowerShellImpl(context, suppliedVariables, false, false, null, args); } @@ -270,10 +272,11 @@ public PowerShell GetPowerShell(Dictionary variables, params obj /// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no /// variables are supplied. /// - public PowerShell GetPowerShell(Dictionary variables, out Dictionary usingVariables, params object[] args) - { - return GetPowerShell(variables, out usingVariables, false, args); - } + public PowerShell GetPowerShell( + Dictionary variables, + out Dictionary usingVariables, + params object[] args) + => GetPowerShell(variables, out usingVariables, isTrustedInput: false, args); /// /// Returns PowerShell object representing the pipeline contained in this ScriptBlock, using variables @@ -312,7 +315,11 @@ public PowerShell GetPowerShell(Dictionary variables, out Dictio /// Thrown when there is no ExecutionContext associated with this ScriptBlock object and no /// variables are supplied. /// - public PowerShell GetPowerShell(Dictionary variables, out Dictionary usingVariables, bool isTrustedInput, params object[] args) + public PowerShell GetPowerShell( + Dictionary variables, + out Dictionary usingVariables, + bool isTrustedInput, + params object[] args) { ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); Dictionary suppliedVariables = null; @@ -329,55 +336,66 @@ public PowerShell GetPowerShell(Dictionary variables, out Dictio return powershell; } - internal PowerShell GetPowerShell(ExecutionContext context, bool isTrustedInput, bool? useLocalScope, object[] args) - { - return GetPowerShellImpl(context, null, isTrustedInput, false, useLocalScope, args); - } + internal PowerShell GetPowerShell( + ExecutionContext context, + bool isTrustedInput, + bool? useLocalScope, + object[] args) + => GetPowerShellImpl( + context, + variables: null, + isTrustedInput, + filterNonUsingVariables: false, + useLocalScope, + args); /// /// Get a steppable pipeline object. /// - /// A steppable pipeline object - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Steppable", Justification = "Review this during API naming")] + /// A steppable pipeline object. + [SuppressMessage( + "Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", + MessageId = "Steppable", + Justification = "Review this during API naming")] public SteppablePipeline GetSteppablePipeline() - { - return GetSteppablePipelineImpl(CommandOrigin.Internal, null); - } + => GetSteppablePipelineImpl(commandOrigin: CommandOrigin.Internal, args: null); /// /// Get a steppable pipeline object. /// - /// A steppable pipeline object - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Steppable", Justification = "Review this during API naming")] + /// A steppable pipeline object. + [SuppressMessage( + "Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", + MessageId = "Steppable", + Justification = "Review this during API naming")] public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin) - { - return GetSteppablePipelineImpl(commandOrigin, null); - } + => GetSteppablePipelineImpl(commandOrigin, args: null); /// /// Get a steppable pipeline object. /// - /// A steppable pipeline object - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Steppable", Justification = "Review this during API naming")] + /// A steppable pipeline object. + [SuppressMessage( + "Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", + MessageId = "Steppable", + Justification = "Review this during API naming")] public SteppablePipeline GetSteppablePipeline(CommandOrigin commandOrigin, object[] args) - { - return GetSteppablePipelineImpl(commandOrigin, args); - } + => GetSteppablePipelineImpl(commandOrigin, args); /// /// Execute this node with the specified arguments. The arguments show /// up in the script as $args with $_ being the first argument. /// - /// /// The arguments to this script. /// The object(s) generated during the execution of - /// the script block returned as a collection of PSObjects - /// Thrown if a script runtime exceptionexception occurred - /// An internal (non-public) exception from a flow control statement - public Collection Invoke(params object[] args) - { - return DoInvoke(AutomationNull.Value, AutomationNull.Value, args); - } + /// the script block returned as a collection of PSObjects. + /// Thrown if a script runtime exceptionexception occurred. + /// An internal (non-public) exception from a flow control statement. + public Collection Invoke(params object[] args) => + DoInvoke(dollarUnder: AutomationNull.Value, input: AutomationNull.Value, args); /// /// A method that allows a scriptblock to be invoked with additional context in the form of a @@ -387,9 +405,9 @@ public Collection Invoke(params object[] args) /// This overload of the function takes a hashtable and converts it to the /// required dictionary which makes the API easier to use from within a PowerShell script. /// - /// A dictionary of functions to define - /// A list of variables to define - /// The arguments to the actual scriptblock + /// A dictionary of functions to define. + /// A list of variables to define. + /// The arguments to the actual scriptblock. /// public Collection InvokeWithContext( IDictionary functionsToDefine, @@ -429,9 +447,9 @@ public Collection InvokeWithContext( /// set of local functions and variables to be defined in the scriptblock's scope. The list of /// variables may include the special variables $input, $_ and $this. /// - /// A dictionary of functions to define - /// A list of variables to define - /// The arguments to the actual scriptblock + /// A dictionary of functions to define. + /// A list of variables to define. + /// The arguments to the actual scriptblock. /// public Collection InvokeWithContext( Dictionary functionsToDefine, @@ -444,24 +462,25 @@ public Collection InvokeWithContext( if (variablesToDefine != null) { - // // Extract the special variables "this", "input" and "_" - // - PSVariable located = variablesToDefine.FirstOrDefault(v => string.Equals(v.Name, "this", StringComparison.OrdinalIgnoreCase)); + PSVariable located = variablesToDefine.Find( + v => string.Equals(v.Name, "this", StringComparison.OrdinalIgnoreCase)); if (located != null) { scriptThis = located.Value; variablesToDefine.Remove(located); } - located = variablesToDefine.FirstOrDefault(v => string.Equals(v.Name, "_", StringComparison.OrdinalIgnoreCase)); + located = variablesToDefine.Find( + v => string.Equals(v.Name, "_", StringComparison.Ordinal)); if (located != null) { dollarUnder = located.Value; variablesToDefine.Remove(located); } - located = variablesToDefine.FirstOrDefault(v => string.Equals(v.Name, "input", StringComparison.OrdinalIgnoreCase)); + located = variablesToDefine.Find( + v => string.Equals(v.Name, "input", StringComparison.OrdinalIgnoreCase)); if (located != null) { input = located.Value; @@ -472,16 +491,17 @@ public Collection InvokeWithContext( List result = new List(); Pipe outputPipe = new Pipe(result); - InvokeWithPipe(useLocalScope: true, - functionsToDefine: functionsToDefine, - variablesToDefine: variablesToDefine, - errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: dollarUnder, - input: input, - scriptThis: scriptThis, - outputPipe: outputPipe, - invocationInfo: null, - args: args); + InvokeWithPipe( + useLocalScope: true, + functionsToDefine: functionsToDefine, + variablesToDefine: variablesToDefine, + errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: dollarUnder, + input: input, + scriptThis: scriptThis, + outputPipe: outputPipe, + invocationInfo: null, + args: args); return GetWrappedResult(result); } @@ -493,34 +513,44 @@ public Collection InvokeWithContext( /// The arguments to pass to this scriptblock. /// The object(s) generated during the execution of the /// script block. They may or may not be wrapped in PSObject. It's up to the caller to check. - /// Thrown if a script runtime exceptionexception occurred - /// An internal (non-public) exception from a flow control statement + /// Thrown if a script runtime exceptionexception occurred. + /// An internal (non-public) exception from a flow control statement. public object InvokeReturnAsIs(params object[] args) - { - return DoInvokeReturnAsIs( - useLocalScope: true, - errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, - dollarUnder: AutomationNull.Value, - input: AutomationNull.Value, - scriptThis: AutomationNull.Value, - args: args); - } + => DoInvokeReturnAsIs( + useLocalScope: true, + errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, + dollarUnder: AutomationNull.Value, + input: AutomationNull.Value, + scriptThis: AutomationNull.Value, + args: args); internal T InvokeAsMemberFunctionT(object instance, object[] args) { List result = new List(); Pipe pipe = new Pipe(result); - InvokeWithPipe(useLocalScope: true, - errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, - dollarUnder: AutomationNull.Value, - input: AutomationNull.Value, - scriptThis: instance ?? AutomationNull.Value, - outputPipe: pipe, - invocationInfo: null, - propagateAllExceptionsToTop: true, - args: args); - Diagnostics.Assert(result.Count == 1, "Code generation ensures we return the correct type"); + InvokeWithPipe( + useLocalScope: true, + errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, + dollarUnder: AutomationNull.Value, + input: AutomationNull.Value, + scriptThis: instance ?? AutomationNull.Value, + outputPipe: pipe, + invocationInfo: null, + propagateAllExceptionsToTop: true, + args: args); + + // This is needed only for the case where the + // method returns [object]. If the argument to 'return' + // 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 receives nothing from it's argument. + if (result.Count == 0) + { + return default(T); + } + return (T)result[0]; } @@ -529,68 +559,49 @@ internal void InvokeAsMemberFunction(object instance, object[] args) List result = new List(); Pipe pipe = new Pipe(result); - InvokeWithPipe(useLocalScope: true, - errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: AutomationNull.Value, - scriptThis: instance ?? AutomationNull.Value, - outputPipe: pipe, - invocationInfo: null, - propagateAllExceptionsToTop: true, - args: args); + InvokeWithPipe( + useLocalScope: true, + errorHandlingBehavior: ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: AutomationNull.Value, + input: AutomationNull.Value, + scriptThis: instance ?? AutomationNull.Value, + outputPipe: pipe, + invocationInfo: null, + propagateAllExceptionsToTop: true, + args: args); Diagnostics.Assert(result.Count == 0, "Code generation ensures we return the correct type"); } /// /// Return all attributes on a script block. /// - public List Attributes - { - get { return GetAttributes(); } - } + public List Attributes { get => GetAttributes(); } /// - /// The script file that defined this script block... + /// The script file that defined this script block. /// - public string File - { - get { return GetFileName(); } - } + public string File { get => GetFileName(); } /// /// Get/set whether this scriptblock is a filter. /// - public bool IsFilter - { - get { return _scriptBlockData.IsFilter; } - set { throw new PSInvalidOperationException(); } - } + public bool IsFilter { get => _scriptBlockData.IsFilter; } /// /// Get/set whether this scriptblock is a Configuration. /// - public bool IsConfiguration - { - get { return _scriptBlockData.GetIsConfiguration(); } - set { throw new PSInvalidOperationException(); } - } + public bool IsConfiguration { get => _scriptBlockData.GetIsConfiguration(); } /// /// Get the PSModuleInfo object for the module that defined this /// scriptblock. /// - public PSModuleInfo Module - { - get { return SessionStateInternal != null ? SessionStateInternal.Module : null; } - } + public PSModuleInfo Module { get => SessionStateInternal?.Module; } /// /// Return the PSToken object for this function definition... /// - public PSToken StartPosition - { - get { return GetStartPosition(); } - } + public PSToken StartPosition { get => GetStartPosition(); } // LanguageMode is a nullable PSLanguageMode enumeration because script blocks // need to inherit the language mode from the context in which they are executing. @@ -624,6 +635,7 @@ internal ReadOnlyCollection OutputType result.AddRange(outputType.Type); } } + return new ReadOnlyCollection(result); } } @@ -634,24 +646,28 @@ internal ReadOnlyCollection OutputType /// /// This does normal array reduction in the case of a one-element array. /// - internal static object GetRawResult(List result) + internal static object GetRawResult(List result, bool wrapToPSObject) { - if (result.Count == 0) - return AutomationNull.Value; - - if (result.Count == 1) - return LanguagePrimitives.AsPSObjectOrNull(result[0]); - - return LanguagePrimitives.AsPSObjectOrNull(result.ToArray()); + switch (result.Count) + { + case 0: + return AutomationNull.Value; + case 1: + return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(result[0]) : result[0]; + default: + object resultArray = result.ToArray(); + return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(resultArray) : resultArray; + } } - internal void InvokeUsingCmdlet(Cmdlet contextCmdlet, - bool useLocalScope, - ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - object[] args) + internal void InvokeUsingCmdlet( + Cmdlet contextCmdlet, + bool useLocalScope, + ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + object[] args) { Diagnostics.Assert(contextCmdlet != null, "caller to verify contextCmdlet parameter"); @@ -659,11 +675,20 @@ internal void InvokeUsingCmdlet(Cmdlet contextCmdlet, ExecutionContext context = GetContextFromTLS(); var myInv = context.EngineSessionState.CurrentScope.GetAutomaticVariableValue(AutomaticVariable.MyInvocation); InvocationInfo inInfo = myInv == AutomationNull.Value ? null : (InvocationInfo)myInv; - InvokeWithPipe(useLocalScope, errorHandlingBehavior, dollarUnder, input, scriptThis, outputPipe, inInfo, propagateAllExceptionsToTop: false, args: args); + InvokeWithPipe( + useLocalScope, + errorHandlingBehavior, + dollarUnder, + input, + scriptThis, + outputPipe, + inInfo, + propagateAllExceptionsToTop: false, + args: args); } /// - /// The internal session state object associated with this scriptblock... + /// The internal session state object associated with this scriptblock. /// internal SessionStateInternal SessionStateInternal { get; set; } @@ -684,12 +709,16 @@ internal SessionState SessionState } } - return SessionStateInternal != null ? SessionStateInternal.PublicSessionState : null; + return SessionStateInternal?.PublicSessionState; } + set { if (value == null) - throw PSTraceSource.NewArgumentNullException("value"); + { + throw PSTraceSource.NewArgumentNullException(nameof(value)); + } + SessionStateInternal = value.Internal; } } @@ -700,10 +729,7 @@ internal SessionState SessionState new ConditionalWeakTable>(); internal Delegate GetDelegate(Type delegateType) - { - var instanceDelegateTable = s_delegateTable.GetOrCreateValue(this); - return instanceDelegateTable.GetOrAdd(delegateType, CreateDelegate); - } + => s_delegateTable.GetOrCreateValue(this).GetOrAdd(delegateType, CreateDelegate); /// /// Get the delegate method as a call back. @@ -752,11 +778,15 @@ 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(PSConvertBinder.Get(invokeMethod.ReturnType), invokeMethod.ReturnType, call); + call = DynamicExpression.Dynamic( + PSConvertBinder.Get(invokeMethod.ReturnType), + invokeMethod.ReturnType, + call); } + return Expression.Lambda(delegateType, call, parameterExprs).Compile(); } @@ -777,7 +807,7 @@ internal object InvokeAsDelegateHelper(object dollarUnder, object dollarThis, ob outputPipe: outputPipe, invocationInfo: null, args: args); - return GetRawResult(rawResult); + return GetRawResult(rawResult, wrapToPSObject: false); } #endregion @@ -785,7 +815,9 @@ internal object InvokeAsDelegateHelper(object dollarUnder, object dollarThis, ob /// /// Returns the current execution context from TLS, or raises an exception if it is null. /// - /// An attempt was made to use the scriptblock outside the engine + /// + /// An attempt was made to use the scriptblock outside the engine. + /// internal ExecutionContext GetContextFromTLS() { ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS(); @@ -812,34 +844,32 @@ internal ExecutionContext GetContextFromTLS() /// Execute this node with the specified arguments. The arguments show /// up in the script as $args with $_ being the first argument. /// - /// /// /// The value of the $_ variable for the script block. If AutomationNull.Value, /// the $_ variable is not created. /// - /// /// /// The value of the $input variable for the script block. If AutomationNull.Value, /// the $input variable is not created. /// - /// /// The arguments to this script. /// The object(s) generated during the execution of - /// the script block returned as a collection of PSObjects - /// A script exception occurred - /// Internal exception from a flow control statement + /// the script block returned as a collection of PSObjects. + /// A script exception occurred. + /// Internal exception from a flow control statement. internal Collection DoInvoke(object dollarUnder, object input, object[] args) { List result = new List(); Pipe outputPipe = new Pipe(result); - InvokeWithPipe(useLocalScope: true, - errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, - dollarUnder: dollarUnder, - input: input, - scriptThis: AutomationNull.Value, - outputPipe: outputPipe, - invocationInfo: null, - args: args); + InvokeWithPipe( + useLocalScope: true, + errorHandlingBehavior: ErrorHandlingBehavior.WriteToExternalErrorPipe, + dollarUnder: dollarUnder, + input: input, + scriptThis: AutomationNull.Value, + outputPipe: outputPipe, + invocationInfo: null, + args: args); return GetWrappedResult(result); } @@ -869,7 +899,6 @@ private static Collection GetWrappedResult(List result) /// Execute this node with the specified arguments. The arguments show /// up in the script as $args with $_ being the first argument. /// - /// /// /// /// @@ -882,43 +911,44 @@ private static Collection GetWrappedResult(List result) /// /// /// The arguments to this script. - /// /// The object(s) generated during the execution of - /// the script block returned as a collection of PSObjects - /// A script exception occurred - /// Internal exception from a flow control statement - internal object DoInvokeReturnAsIs(bool useLocalScope, - ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - object[] args) + /// the script block returned as a collection of PSObjects. + /// A script exception occurred. + /// Internal exception from a flow control statement. + internal object DoInvokeReturnAsIs( + bool useLocalScope, + ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + object[] args) { List result = new List(); Pipe outputPipe = new Pipe(result); - InvokeWithPipe(useLocalScope: useLocalScope, - errorHandlingBehavior: errorHandlingBehavior, - dollarUnder: dollarUnder, - input: input, - scriptThis: scriptThis, - outputPipe: outputPipe, - invocationInfo: null, - args: args); - return GetRawResult(result); + InvokeWithPipe( + useLocalScope: useLocalScope, + errorHandlingBehavior: errorHandlingBehavior, + dollarUnder: dollarUnder, + input: input, + scriptThis: scriptThis, + outputPipe: outputPipe, + invocationInfo: null, + args: args); + return GetRawResult(result, wrapToPSObject: true); } internal void InvokeWithPipe( - bool useLocalScope, - ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - Pipe outputPipe, - InvocationInfo invocationInfo, - bool propagateAllExceptionsToTop = false, - List variablesToDefine = null, - Dictionary functionsToDefine = null, - object[] args = null) + bool useLocalScope, + ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + Pipe outputPipe, + InvocationInfo invocationInfo, + bool propagateAllExceptionsToTop = false, + List variablesToDefine = null, + Dictionary functionsToDefine = null, + object[] args = null) { bool shouldGenerateEvent = false; bool oldPropagateExceptions = false; @@ -945,9 +975,24 @@ internal void InvokeWithPipe( try { var runspace = (RunspaceBase)context.CurrentRunspace; - shouldGenerateEvent = !runspace.RunActionIfNoRunningPipelinesWithThreadCheck(() => - InvokeWithPipeImpl(useLocalScope, functionsToDefine, variablesToDefine, errorHandlingBehavior, - dollarUnder, input, scriptThis, outputPipe, invocationInfo, args)); + if (runspace.CanRunActionInCurrentPipeline()) + { + InvokeWithPipeImpl( + useLocalScope, + functionsToDefine, + variablesToDefine, + errorHandlingBehavior, + dollarUnder, + input, + scriptThis, + outputPipe, + invocationInfo, + args); + } + else + { + shouldGenerateEvent = true; + } } finally { @@ -961,19 +1006,26 @@ internal void InvokeWithPipe( if (shouldGenerateEvent) { context.Events.SubscribeEvent( - source: null, - eventName: PSEngineEvent.OnScriptBlockInvoke, - sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke, - data: null, - handlerDelegate: new PSEventReceivedEventHandler(OnScriptBlockInvokeEventHandler), - supportEvent: true, - forwardEvent: false, - shouldQueueAndProcessInExecutionThread: true, - maxTriggerCount: 1); + source: null, + eventName: PSEngineEvent.OnScriptBlockInvoke, + sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke, + data: null, + handlerDelegate: new PSEventReceivedEventHandler(OnScriptBlockInvokeEventHandler), + supportEvent: true, + forwardEvent: false, + shouldQueueAndProcessInExecutionThread: true, + maxTriggerCount: 1); var scriptBlockInvocationEventArgs = new ScriptBlockInvocationEventArgs( - this, useLocalScope, errorHandlingBehavior, dollarUnder, input, scriptThis, outputPipe, - invocationInfo, args); + scriptBlock: this, + useLocalScope, + errorHandlingBehavior, + dollarUnder, + input, + scriptThis, + outputPipe, + invocationInfo, + args); context.Events.GenerateEvent( sourceIdentifier: PSEngineEvent.OnScriptBlockInvoke, @@ -983,25 +1035,33 @@ internal void InvokeWithPipe( processInCurrentThread: true, waitForCompletionInCurrentThread: true); - if (scriptBlockInvocationEventArgs.Exception != null) - { - scriptBlockInvocationEventArgs.Exception.Throw(); - } + scriptBlockInvocationEventArgs.Exception?.Throw(); } } /// - /// Handles OnScriptBlockInvoke event, this is called by the event manager + /// Handles OnScriptBlockInvoke event, this is called by the event manager. /// private static void OnScriptBlockInvokeEventHandler(object sender, PSEventArgs args) { var eventArgs = (object)args.SourceEventArgs as ScriptBlockInvocationEventArgs; - Diagnostics.Assert(eventArgs != null, "Event Arguments to OnScriptBlockInvokeEventHandler should not be null"); + Diagnostics.Assert(eventArgs != null, + "Event Arguments to OnScriptBlockInvokeEventHandler should not be null"); try { ScriptBlock sb = eventArgs.ScriptBlock; - sb.InvokeWithPipeImpl(eventArgs.UseLocalScope, null, null, eventArgs.ErrorHandlingBehavior, eventArgs.DollarUnder, eventArgs.Input, eventArgs.ScriptThis, eventArgs.OutputPipe, eventArgs.InvocationInfo, eventArgs.Args); + sb.InvokeWithPipeImpl( + eventArgs.UseLocalScope, + functionsToDefine: null, + variablesToDefine: null, + eventArgs.ErrorHandlingBehavior, + eventArgs.DollarUnder, + eventArgs.Input, + eventArgs.ScriptThis, + eventArgs.OutputPipe, + eventArgs.InvocationInfo, + eventArgs.Args); } catch (Exception e) { @@ -1018,6 +1078,7 @@ internal void SetPSScriptRootAndPSCommandPath(MutableTuple locals, ExecutionCont psScriptRoot = Path.GetDirectoryName(File); psCommandPath = File; } + locals.SetAutomaticVariable(AutomaticVariable.PSScriptRoot, psScriptRoot, context); locals.SetAutomaticVariable(AutomaticVariable.PSCommandPath, psCommandPath, context); } @@ -1026,46 +1087,46 @@ internal void SetPSScriptRootAndPSCommandPath(MutableTuple locals, ExecutionCont /// /// A steppable pipeline wrapper object... /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Steppable", Justification = "Consider Name change during API review")] + [SuppressMessage( + "Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", + MessageId = "Steppable", + Justification = "Consider Name change during API review")] public sealed class SteppablePipeline : IDisposable { internal SteppablePipeline(ExecutionContext context, PipelineProcessor pipeline) { - if (pipeline == null) throw new ArgumentNullException("pipeline"); - if (context == null) throw new ArgumentNullException("context"); + ArgumentNullException.ThrowIfNull(pipeline); + + 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 - public void Begin(bool expectInput) - { - Begin(expectInput, (ICommandRuntime)null); - } + /// 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 - /// context used to figure out how to route the output and errors. + /// 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("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); } @@ -1074,11 +1135,13 @@ public void Begin(bool expectInput, EngineIntrinsics contextToRedirectTo) /// out how to route the output and errors. This is the most effective /// way to start stepping. /// - /// The command you're calling this from (i.e. instance of PSCmdlet or value of $PSCmdlet variable) + /// 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) - throw new ArgumentNullException("command"); + if (command is null || command.MyInvocation is null) + { + throw new ArgumentNullException(nameof(command)); + } Begin(command.MyInvocation.ExpectingInput, command.commandRuntime); } @@ -1101,11 +1164,13 @@ private void Begin(bool expectInput, ICommandRuntime commandRuntime) { _pipeline.LinkPipelineSuccessOutput(crt.OutputPipe); } + if (crt.ErrorOutputPipe != null) { _pipeline.LinkPipelineErrorOutput(crt.ErrorOutputPipe); } } + _pipeline.StartStepping(_expectInput); } finally @@ -1118,8 +1183,8 @@ private void Begin(bool expectInput, ICommandRuntime commandRuntime) /// /// Process a single input object. /// - /// The object to process - /// a collection of 0 or more result objects + /// The object to process. + /// A collection of 0 or more result objects. public Array Process(object input) { try @@ -1145,7 +1210,7 @@ public Array Process(object input) /// that the PowerShell runtime will PSBase an object before passing it to /// a .NET API call with argument type object. /// - /// The input object to process + /// The input object to process. /// public Array Process(PSObject input) { @@ -1173,7 +1238,7 @@ public Array Process(PSObject input) /// Begin() was called with $false so we won't send any /// input to be processed. /// - /// The result of the execution + /// The result of the execution. public Array Process() { try @@ -1203,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(); } } @@ -1216,48 +1318,30 @@ 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(); + return; } + _pipeline.Dispose(); _disposed = true; } - /// - /// Finalizer for class SteppablePipeline - /// - ~SteppablePipeline() - { - Dispose(false); - } - #endregion IDispose } - /// /// 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 /// /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException - /// with the message set to typeof(ScriptBlockToPowerShellNotSupportedException).FullName + /// with the message set to typeof(ScriptBlockToPowerShellNotSupportedException).FullName. /// public ScriptBlockToPowerShellNotSupportedException() : base(typeof(ScriptBlockToPowerShellNotSupportedException).FullName) @@ -1265,65 +1349,63 @@ public ScriptBlockToPowerShellNotSupportedException() } /// - /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message + /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message. /// - /// the exception's message + /// The exception's message. public ScriptBlockToPowerShellNotSupportedException(string message) : base(message) { } /// - /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException + /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException setting the message and innerException. /// - /// the exception's message - /// the exceptions's inner exception + /// The exception's message. + /// The exception's inner exception. public ScriptBlockToPowerShellNotSupportedException(string message, Exception innerException) : base(message, innerException) { } /// - /// Recommended constructor for the class + /// Recommended constructor for the class. /// - /// String that uniquely identifies each thrown Exception - /// The inner exception - /// The error message - /// Arguments to the resource string + /// String that uniquely identifies each thrown Exception. + /// The inner exception. + /// The error message. + /// Arguments to the resource string. internal ScriptBlockToPowerShellNotSupportedException( string errorId, Exception innerException, string message, params object[] arguments) : base(string.Format(CultureInfo.CurrentCulture, message, arguments), innerException) - { - this.SetErrorId(errorId); - } - + => this.SetErrorId(errorId); #region Serialization /// - /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException with serialization parameters + /// Initializes a new instance of ScriptBlockToPowerShellNotSupportedException with serialization parameters. /// - /// serialization information - /// streaming context + /// 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 #endregion ctor - } // ScriptBlockToPowerShellNotSupportedException + } /// - /// Defines Event arguments passed to OnScriptBlockInvocationEventHandler + /// Defines Event arguments passed to OnScriptBlockInvocationEventHandler. /// internal sealed class ScriptBlockInvocationEventArgs : EventArgs { /// - /// Constructs ScriptBlockInvocationEventArgs + /// Constructs ScriptBlockInvocationEventArgs. /// /// The scriptblock to invoke /// @@ -1342,21 +1424,22 @@ internal sealed class ScriptBlockInvocationEventArgs : EventArgs /// /// The information about current state of the runspace. /// The arguments to this script. - /// scriptBlock is null + /// ScriptBlock is null /// - internal ScriptBlockInvocationEventArgs(ScriptBlock scriptBlock, - bool useLocalScope, - ScriptBlock.ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - Pipe outputPipe, - InvocationInfo invocationInfo, - object[] args) + internal ScriptBlockInvocationEventArgs( + ScriptBlock scriptBlock, + bool useLocalScope, + ScriptBlock.ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + Pipe outputPipe, + InvocationInfo invocationInfo, + object[] args) { if (scriptBlock == null) { - throw PSTraceSource.NewArgumentNullException("scriptBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } ScriptBlock = scriptBlock; @@ -1371,13 +1454,21 @@ internal ScriptBlockInvocationEventArgs(ScriptBlock scriptBlock, } 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 de4255de363..03b234cf41f 100644 --- a/src/System.Management.Automation/engine/parser/AstVisitor.cs +++ b/src/System.Management.Automation/engine/parser/AstVisitor.cs @@ -1,189 +1,249 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; using System.Reflection.Emit; 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 { /// - object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst); + object? VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => DefaultVisit(typeDefinitionAst); + + /// + object? VisitPropertyMember(PropertyMemberAst propertyMemberAst) => DefaultVisit(propertyMemberAst); + + /// + object? VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); /// - object VisitPropertyMember(PropertyMemberAst propertyMemberAst); + object? VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => DefaultVisit(baseCtorInvokeMemberExpressionAst); /// - object VisitFunctionMember(FunctionMemberAst functionMemberAst); + object? VisitUsingStatement(UsingStatementAst usingStatement) => DefaultVisit(usingStatement); /// - object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst); + object? VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => DefaultVisit(configurationDefinitionAst); /// - object VisitUsingStatement(UsingStatementAst usingStatement); + object? VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) => DefaultVisit(dynamicKeywordAst); /// - object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst); + object? VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); /// - object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst); + object? VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst); } +#nullable restore #if DEBUG - class CheckAllParentsSet : AstVisitor2 + internal class CheckAllParentsSet : AstVisitor2 { internal CheckAllParentsSet(Ast root) { this.Root = root; } - private Ast Root { get; set; } + private Ast Root { get; } internal AstVisitAction CheckParent(Ast ast) { @@ -191,89 +251,155 @@ internal AstVisitAction CheckParent(Ast ast) { Diagnostics.Assert(ast.Parent != null, "Parent not set"); } + return AstVisitAction.Continue; } + public override AstVisitAction VisitErrorStatement(ErrorStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitErrorExpression(ErrorExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitScriptBlock(ScriptBlockAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitParamBlock(ParamBlockAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitNamedBlock(NamedBlockAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitAttribute(AttributeAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitParameter(ParameterAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitTypeExpression(TypeExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitStatementBlock(StatementBlockAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitIfStatement(IfStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitTrap(TrapStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitSwitchStatement(SwitchStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitDataStatement(DataStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitForEachStatement(ForEachStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitForStatement(ForStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitWhileStatement(WhileStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitCatchClause(CatchClauseAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitTryStatement(TryStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitBreakStatement(BreakStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitContinueStatement(ContinueStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitReturnStatement(ReturnStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitExitStatement(ExitStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitThrowStatement(ThrowStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitPipeline(PipelineAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitCommand(CommandAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitCommandExpression(CommandExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitCommandParameter(CommandParameterAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitFileRedirection(FileRedirectionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitConvertExpression(ConvertExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitConstantExpression(ConstantExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitSubExpression(SubExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitUsingExpression(UsingExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitVariableExpression(VariableExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitMemberExpression(MemberExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitArrayExpression(ArrayExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitHashtable(HashtableAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitParenExpression(ParenExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitIndexExpression(IndexExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitBlockStatement(BlockStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitFunctionMember(FunctionMemberAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitPropertyMember(PropertyMemberAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitUsingStatement(UsingStatementAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return CheckParent(ast); } + public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return CheckParent(ast); } + + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) => CheckParent(ast); + + public override AstVisitAction VisitPipelineChain(PipelineChainAst ast) => CheckParent(ast); } /// - /// Check if contains type + /// Check if contains type. /// - class CheckTypeBuilder : AstVisitor2 + internal class CheckTypeBuilder : AstVisitor2 { 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; } } #endif /// - /// Searches an AST, using the evaluation function provided by either of the constructors + /// Searches an AST, using the evaluation function provided by either of the constructors. /// internal class AstSearcher : AstVisitor2 { @@ -303,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.FirstOrDefault() != null; + return searcher.Results.Count > 0; } internal static bool IsUsingDollarInput(Ast ast) @@ -319,6 +445,7 @@ internal static bool IsUsingDollarInput(Ast ast) varAst.VariablePath.UnqualifiedPath.Equals(SpecialVariables.Input, StringComparison.OrdinalIgnoreCase); } + return false; }, searchNestedScriptBlocks: false)); @@ -349,6 +476,7 @@ protected AstVisitAction Check(Ast ast) return AstVisitAction.StopVisit; } } + return AstVisitAction.Continue; } @@ -359,204 +487,336 @@ protected AstVisitAction CheckScriptBlock(Ast ast) { action = AstVisitAction.SkipChildren; } + return action; } public override AstVisitAction VisitErrorStatement(ErrorStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitErrorExpression(ErrorExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitScriptBlock(ScriptBlockAst ast) { return Check(ast); } + public override AstVisitAction VisitParamBlock(ParamBlockAst ast) { return Check(ast); } + public override AstVisitAction VisitNamedBlock(NamedBlockAst ast) { return Check(ast); } + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst ast) { return Check(ast); } + public override AstVisitAction VisitAttribute(AttributeAst ast) { return Check(ast); } + public override AstVisitAction VisitParameter(ParameterAst ast) { return Check(ast); } + public override AstVisitAction VisitTypeExpression(TypeExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst ast) { return CheckScriptBlock(ast); } + public override AstVisitAction VisitStatementBlock(StatementBlockAst ast) { return Check(ast); } + public override AstVisitAction VisitIfStatement(IfStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitTrap(TrapStatementAst ast) { return CheckScriptBlock(ast); } + public override AstVisitAction VisitSwitchStatement(SwitchStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitDataStatement(DataStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitForEachStatement(ForEachStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitForStatement(ForStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitWhileStatement(WhileStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitCatchClause(CatchClauseAst ast) { return Check(ast); } + public override AstVisitAction VisitTryStatement(TryStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitBreakStatement(BreakStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitContinueStatement(ContinueStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitReturnStatement(ReturnStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitExitStatement(ExitStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitThrowStatement(ThrowStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitPipeline(PipelineAst ast) { return Check(ast); } + public override AstVisitAction VisitCommand(CommandAst ast) { return Check(ast); } + public override AstVisitAction VisitCommandExpression(CommandExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitCommandParameter(CommandParameterAst ast) { return Check(ast); } + public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst ast) { return Check(ast); } + public override AstVisitAction VisitFileRedirection(FileRedirectionAst ast) { return Check(ast); } + public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitConvertExpression(ConvertExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitConstantExpression(ConstantExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitSubExpression(SubExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitUsingExpression(UsingExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitVariableExpression(VariableExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitMemberExpression(MemberExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitArrayExpression(ArrayExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst ast) { return Check(ast); } + public override AstVisitAction VisitHashtable(HashtableAst ast) { return Check(ast); } + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst ast) { return CheckScriptBlock(ast); } + public override AstVisitAction VisitParenExpression(ParenExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitIndexExpression(IndexExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst ast) { return Check(ast); } + public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst ast) { return Check(ast); } + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst ast) { return Check(ast); } + public override AstVisitAction VisitPropertyMember(PropertyMemberAst ast) { return Check(ast); } + public override AstVisitAction VisitFunctionMember(FunctionMemberAst ast) { return Check(ast); } + public override AstVisitAction VisitUsingStatement(UsingStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitBlockStatement(BlockStatementAst ast) { return Check(ast); } + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return Check(ast); } + public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return Check(ast); } + + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) { return Check(ast); } + + public override AstVisitAction VisitPipelineChain(PipelineChainAst ast) { return Check(ast); } } /// - /// Default implementation of interface + /// Default implementation of interface. /// public abstract class DefaultCustomAstVisitor : ICustomAstVisitor { /// - public virtual object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return null; } + public virtual object DefaultVisit(Ast ast) => null; + + /// + public virtual object VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + /// - public virtual object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return null; } + public virtual object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); + /// - public virtual object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return null; } + 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); } /// - /// Default implementation of interface + /// Default implementation of interface. /// 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) => DefaultVisit(dynamicKeywordAst); + /// - public virtual object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return null; } + public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => DefaultVisit(typeDefinitionAst); + /// - public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return null; } + public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); + + /// + public virtual object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); + /// - public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return null; } + public virtual object VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst); } } diff --git a/src/System.Management.Automation/engine/parser/CharTraits.cs b/src/System.Management.Automation/engine/parser/CharTraits.cs index be5b90d0778..fc45f017afd 100644 --- a/src/System.Management.Automation/engine/parser/CharTraits.cs +++ b/src/System.Management.Automation/engine/parser/CharTraits.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation.Language { @@ -28,39 +27,64 @@ internal static class SpecialChars [Flags] internal enum CharTraits { + /// + /// No specific character traits. + /// None = 0x0000, - // For identifiers, is the character a letter? + /// + /// For identifiers, the first character must be a letter or underscore. + /// IdentifierStart = 0x0002, - // The character is a valid first character of a multiplier + /// + /// The character is a valid first character of a multiplier. + /// MultiplierStart = 0x0004, - // The character is a valid type suffix for numeric literals + /// + /// The character is a valid type suffix for numeric literals. + /// TypeSuffix = 0x0008, - // The character is a whitespace character + /// + /// The character is a whitespace character. + /// Whitespace = 0x0010, - // The character terminates a line. + /// + /// The character terminates a line. + /// Newline = 0x0020, - // The character is a hexadecimal digit. + /// + /// The character is a hexadecimal digit. + /// HexDigit = 0x0040, - // The character is a decimal digit. + /// + /// The character is a decimal digit. + /// Digit = 0x0080, - // The character is allowed as the first character in an unbraced variable name. + /// + /// The character is allowed as the first character in an unbraced variable name. + /// VarNameFirst = 0x0100, - // The character is not part of the token being scanned. + /// + /// The character is not part of the token being scanned. + /// ForceStartNewToken = 0x0200, - // The character is not part of the token being scanned, when the token is known to be part of an assembly name. + /// + /// The character is not part of the token being scanned, when the token is known to be part of an assembly name. + /// ForceStartNewAssemblyNameSpecToken = 0x0400, - // The character is the first character of some operator (and hence is not part of a token that starts a number) + /// + /// The character is the first character of some operator (and hence is not part of a token that starts a number). + /// ForceStartNewTokenAfterNumber = 0x0800, } @@ -151,18 +175,18 @@ static CharExtensions() /* K */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, /* L */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* M */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, -/* N */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* N */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* O */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* P */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, /* Q */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* R */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, -/* S */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* S */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* T */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, -/* U */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* U */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* V */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* W */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* X */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, -/* Y */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* Y */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* Z */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* [ */ CharTraits.None, /* \ */ CharTraits.None, @@ -183,18 +207,18 @@ static CharExtensions() /* k */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, /* l */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* m */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, -/* n */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* n */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* o */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* p */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, /* q */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* r */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, -/* s */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* s */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* t */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.MultiplierStart, -/* u */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* u */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* v */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* w */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* x */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, -/* y */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, +/* y */ CharTraits.IdentifierStart | CharTraits.VarNameFirst | CharTraits.TypeSuffix, /* z */ CharTraits.IdentifierStart | CharTraits.VarNameFirst, /* { */ CharTraits.ForceStartNewToken, /* | */ CharTraits.ForceStartNewToken, @@ -203,6 +227,11 @@ static CharExtensions() /* 0x7F */ CharTraits.None, }; + public static bool IsCurlyBracket(char c) + { + return (c == '{' || c == '}'); + } + // Return true if the character is a whitespace character. // Newlines are not whitespace. internal static bool IsWhitespace(this char c) @@ -260,6 +289,7 @@ internal static bool IsVariableStart(this char c) { return (s_traits[c] & CharTraits.VarNameFirst) != 0; } + return char.IsLetterOrDigit(c); } @@ -271,6 +301,7 @@ internal static bool IsIdentifierStart(this char c) { return (s_traits[c] & CharTraits.IdentifierStart) != 0; } + return char.IsLetter(c); } @@ -282,6 +313,7 @@ internal static bool IsIdentifierFollow(this char c) { return (s_traits[c] & (CharTraits.IdentifierStart | CharTraits.Digit)) != 0; } + return char.IsLetterOrDigit(c); } @@ -292,26 +324,28 @@ internal static bool IsHexDigit(this char c) { return (s_traits[c] & CharTraits.HexDigit) != 0; } - return false; - } - // Return true if the character is a decimal digit. - internal static bool IsDecimalDigit(this char c) - { - if (c < 128) - { - return (s_traits[c] & CharTraits.Digit) != 0; - } return false; } - // Return true if the character is a type suffix character. + // Returns true if the character is a decimal digit. + internal static bool IsDecimalDigit(this char c) => (uint)(c - '0') <= 9; + + // These decimal/binary checking methods are more performant than the alternatives due to requiring + // less overall operations than a more readable check such as {(this char c) => c == 0 | c == 1}, + // especially in the case of IsDecimalDigit(). + + // Returns true if the character is a binary digit. + internal static bool IsBinaryDigit(this char c) => (uint)(c - '0') <= 1; + + // Returns true if the character is a type suffix character. internal static bool IsTypeSuffix(this char c) { if (c < 128) { return (s_traits[c] & CharTraits.TypeSuffix) != 0; } + return false; } @@ -322,6 +356,7 @@ internal static bool IsMultiplierStart(this char c) { return (s_traits[c] & CharTraits.MultiplierStart) != 0; } + return false; } @@ -339,13 +374,26 @@ internal static bool ForceStartNewToken(this char c) return c.IsWhitespace(); } - // Return true if the character ends the current number token. This allows the tokenizer - // to scan '7z' as a single token, but '7+' as 2 tokens. - internal static bool ForceStartNewTokenAfterNumber(this char c) + /// + /// Check if the current character forces to end scanning a number token. + /// This allows the tokenizer to scan '7z' as a single token, but '7+' as 2 tokens. + /// + /// The character to check. + /// + /// In some cases, we want '?' and ':' to end a number token too, so they can be + /// treated as the ternary operator tokens. + /// + /// Return true if the character ends the current number token. + internal static bool ForceStartNewTokenAfterNumber(this char c, bool forceEndNumberOnTernaryOperatorChars) { if (c < 128) { - return (s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0; + if ((s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0) + { + return true; + } + + return forceEndNumberOnTernaryOperatorChars && (c == '?' || c == ':'); } return c.IsDash(); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 9c3175fa8e1..5ce6a6c6dc8 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -18,6 +17,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; + using Microsoft.PowerShell.Commands; namespace System.Management.Automation.Language @@ -28,197 +28,241 @@ namespace System.Management.Automation.Language internal static class CachedReflectionInfo { // ReSharper disable InconsistentNaming - internal const BindingFlags instanceFlags = BindingFlags.Instance | BindingFlags.NonPublic; - internal const BindingFlags staticFlags = BindingFlags.Static | BindingFlags.NonPublic; - internal const BindingFlags staticPublicFlags = BindingFlags.Static | BindingFlags.Public; - internal const BindingFlags instancePublicFlags = BindingFlags.Instance | BindingFlags.Public; + internal const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic; + internal const BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.NonPublic; + internal const BindingFlags StaticPublicFlags = BindingFlags.Static | BindingFlags.Public; + internal const BindingFlags InstancePublicFlags = BindingFlags.Instance | BindingFlags.Public; internal static readonly ConstructorInfo ObjectList_ctor = - typeof(List).GetConstructor(PSTypeExtensions.EmptyTypes); + typeof(List).GetConstructor(Type.EmptyTypes); + internal static readonly MethodInfo ObjectList_ToArray = - typeof(List).GetMethod(nameof(List.ToArray), PSTypeExtensions.EmptyTypes); + 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); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValue), StaticFlags); + internal static readonly MethodInfo ArrayOps_GetMDArrayValueOrSlice = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValueOrSlice), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetMDArrayValueOrSlice), StaticFlags); + internal static readonly MethodInfo ArrayOps_GetNonIndexable = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetNonIndexable), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.GetNonIndexable), StaticFlags); + internal static readonly MethodInfo ArrayOps_IndexStringMessage = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.IndexStringMessage), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.IndexStringMessage), StaticFlags); + internal static readonly MethodInfo ArrayOps_Multiply = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.Multiply), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.Multiply), StaticFlags); + internal static readonly MethodInfo ArrayOps_SetMDArrayValue = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.SetMDArrayValue), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.SetMDArrayValue), StaticFlags); + internal static readonly MethodInfo ArrayOps_SlicingIndex = - typeof(ArrayOps).GetMethod(nameof(ArrayOps.SlicingIndex), staticFlags); + typeof(ArrayOps).GetMethod(nameof(ArrayOps.SlicingIndex), StaticFlags); internal static readonly ConstructorInfo BreakException_ctor = - typeof(BreakException).GetConstructor(instanceFlags, null, CallingConventions.Standard, - new Type[] { typeof(string) }, null); + typeof(BreakException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(string) }, null); internal static readonly MethodInfo CharOps_CompareIeq = - typeof(CharOps).GetMethod(nameof(CharOps.CompareIeq), staticFlags); + typeof(CharOps).GetMethod(nameof(CharOps.CompareIeq), StaticFlags); + internal static readonly MethodInfo CharOps_CompareIne = - typeof(CharOps).GetMethod(nameof(CharOps.CompareIne), staticFlags); + typeof(CharOps).GetMethod(nameof(CharOps.CompareIne), StaticFlags); + internal static readonly MethodInfo CharOps_CompareStringIeq = - typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIeq), staticFlags); + typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIeq), StaticFlags); + internal static readonly MethodInfo CharOps_CompareStringIne = - typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIne), staticFlags); - internal static readonly MethodInfo CharOps_Range = - typeof(CharOps).GetMethod(nameof(CharOps.Range), staticFlags); + typeof(CharOps).GetMethod(nameof(CharOps.CompareStringIne), StaticFlags); internal static readonly MethodInfo CommandParameterInternal_CreateArgument = - typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateArgument), staticFlags); + typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateArgument), StaticFlags); + internal static readonly MethodInfo CommandParameterInternal_CreateParameter = - typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameter), staticFlags); + typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameter), StaticFlags); + internal static readonly MethodInfo CommandParameterInternal_CreateParameterWithArgument = - typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameterWithArgument), staticFlags); + typeof(CommandParameterInternal).GetMethod(nameof(CommandParameterInternal.CreateParameterWithArgument), StaticFlags); internal static readonly MethodInfo CommandRedirection_UnbindForExpression = - typeof(CommandRedirection).GetMethod(nameof(CommandRedirection.UnbindForExpression), instanceFlags); + typeof(CommandRedirection).GetMethod(nameof(CommandRedirection.UnbindForExpression), InstanceFlags); internal static readonly ConstructorInfo ContinueException_ctor = - typeof(ContinueException).GetConstructor(instanceFlags, null, CallingConventions.Standard, - new Type[] { typeof(string) }, null); + typeof(ContinueException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(string) }, null); internal static readonly MethodInfo Convert_ChangeType = typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type) }); internal static readonly MethodInfo Debugger_EnterScriptFunction = - typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.EnterScriptFunction), instanceFlags); + typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.EnterScriptFunction), InstanceFlags); + internal static readonly MethodInfo Debugger_ExitScriptFunction = - typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.ExitScriptFunction), instanceFlags); + typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.ExitScriptFunction), InstanceFlags); + internal static readonly MethodInfo Debugger_OnSequencePointHit = - typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.OnSequencePointHit), instanceFlags); + typeof(ScriptDebugger).GetMethod(nameof(ScriptDebugger.OnSequencePointHit), InstanceFlags); internal static readonly MethodInfo EnumerableOps_AddEnumerable = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddEnumerable), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddEnumerable), StaticFlags); + internal static readonly MethodInfo EnumerableOps_AddObject = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddObject), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.AddObject), StaticFlags); + internal static readonly MethodInfo EnumerableOps_Compare = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Compare), staticFlags); - internal static readonly MethodInfo EnumerableOps_Current = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Current), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Compare), StaticFlags); + internal static readonly MethodInfo EnumerableOps_GetEnumerator = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetEnumerator), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetEnumerator), StaticFlags); + internal static readonly MethodInfo EnumerableOps_GetCOMEnumerator = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetCOMEnumerator), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetCOMEnumerator), StaticFlags); + internal static readonly MethodInfo EnumerableOps_GetGenericEnumerator = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetGenericEnumerator), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetGenericEnumerator), StaticFlags); + internal static readonly MethodInfo EnumerableOps_GetSlice = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetSlice), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.GetSlice), StaticFlags); + internal static readonly MethodInfo EnumerableOps_MethodInvoker = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.MethodInvoker), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.MethodInvoker), StaticFlags); + internal static readonly MethodInfo EnumerableOps_Where = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Where), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Where), StaticFlags); + internal static readonly MethodInfo EnumerableOps_ForEach = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ForEach), staticFlags); - internal static readonly MethodInfo EnumerableOps_MoveNext = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.MoveNext), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ForEach), StaticFlags); + internal static readonly MethodInfo EnumerableOps_Multiply = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Multiply), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.Multiply), StaticFlags); + internal static readonly MethodInfo EnumerableOps_PropertyGetter = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.PropertyGetter), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.PropertyGetter), StaticFlags); + internal static readonly MethodInfo EnumerableOps_SlicingIndex = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.SlicingIndex), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.SlicingIndex), StaticFlags); + internal static readonly MethodInfo EnumerableOps_ToArray = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ToArray), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.ToArray), StaticFlags); + internal static readonly MethodInfo EnumerableOps_WriteEnumerableToPipe = - typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.WriteEnumerableToPipe), staticFlags); + typeof(EnumerableOps).GetMethod(nameof(EnumerableOps.WriteEnumerableToPipe), StaticFlags); internal static readonly ConstructorInfo ErrorRecord__ctor = - typeof(ErrorRecord).GetConstructor(instanceFlags | BindingFlags.Public, null, CallingConventions.Standard, - new Type[] { typeof(ErrorRecord), typeof(RuntimeException) }, null); + typeof(ErrorRecord).GetConstructor( + InstanceFlags | BindingFlags.Public, + null, + CallingConventions.Standard, + new Type[] { typeof(ErrorRecord), typeof(RuntimeException) }, + null); internal static readonly PropertyInfo Exception_Message = typeof(Exception).GetProperty(nameof(Exception.Message)); internal static readonly MethodInfo ExceptionHandlingOps_CheckActionPreference = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.CheckActionPreference), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.CheckActionPreference), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_ConvertToArgumentConversionException = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToArgumentConversionException), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToArgumentConversionException), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_ConvertToException = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToException), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToException), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_ConvertToMethodInvocationException = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToMethodInvocationException), staticFlags); - internal static readonly MethodInfo ExceptionHandlingOps_ConvertToRuntimeException = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToRuntimeException), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.ConvertToMethodInvocationException), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_FindMatchingHandler = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.FindMatchingHandler), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.FindMatchingHandler), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_RestoreStoppingPipeline = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.RestoreStoppingPipeline), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.RestoreStoppingPipeline), StaticFlags); + internal static readonly MethodInfo ExceptionHandlingOps_SuspendStoppingPipeline = - typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.SuspendStoppingPipeline), staticFlags); + typeof(ExceptionHandlingOps).GetMethod(nameof(ExceptionHandlingOps.SuspendStoppingPipeline), StaticFlags); internal static readonly PropertyInfo ExecutionContext_CurrentExceptionBeingHandled = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.CurrentExceptionBeingHandled), instanceFlags); + typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.CurrentExceptionBeingHandled), InstanceFlags); + internal static readonly FieldInfo ExecutionContext_Debugger = - typeof(ExecutionContext).GetField(nameof(ExecutionContext._debugger), instanceFlags); + typeof(ExecutionContext).GetField(nameof(ExecutionContext._debugger), InstanceFlags); + internal static readonly FieldInfo ExecutionContext_DebuggingMode = - typeof(ExecutionContext).GetField(nameof(ExecutionContext._debuggingMode), instanceFlags); + typeof(ExecutionContext).GetField(nameof(ExecutionContext._debuggingMode), InstanceFlags); + internal static readonly PropertyInfo ExecutionContext_ExceptionHandlerInEnclosingStatementBlock = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.PropagateExceptionsToEnclosingStatementBlock), instanceFlags); + typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.PropagateExceptionsToEnclosingStatementBlock), InstanceFlags); + internal static readonly MethodInfo ExecutionContext_IsStrictVersion = - typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.IsStrictVersion), staticFlags); + typeof(ExecutionContext).GetMethod(nameof(ExecutionContext.IsStrictVersion), StaticFlags); + internal static readonly PropertyInfo ExecutionContext_QuestionMarkVariableValue = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.QuestionMarkVariableValue), instanceFlags); + typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.QuestionMarkVariableValue), InstanceFlags); + internal static readonly PropertyInfo ExecutionContext_LanguageMode = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.LanguageMode), instanceFlags); + typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.LanguageMode), InstanceFlags); + internal static readonly PropertyInfo ExecutionContext_EngineIntrinsics = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.EngineIntrinsics), instanceFlags); - internal static readonly PropertyInfo ExecutionContext_ShellFunctionErrorOutputPipe = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.ShellFunctionErrorOutputPipe), instanceFlags); - internal static readonly PropertyInfo ExecutionContext_TypeTable = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.TypeTable), instanceFlags); - - internal static readonly PropertyInfo ExecutionContext_ExpressionWarningOutputPipe = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.ExpressionWarningOutputPipe), instanceFlags); - internal static readonly PropertyInfo ExecutionContext_ExpressionVerboseOutputPipe = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.ExpressionVerboseOutputPipe), instanceFlags); - internal static readonly PropertyInfo ExecutionContext_ExpressionDebugOutputPipe = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.ExpressionDebugOutputPipe), instanceFlags); - internal static readonly PropertyInfo ExecutionContext_ExpressionInformationOutputPipe = - typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.ExpressionInformationOutputPipe), instanceFlags); + typeof(ExecutionContext).GetProperty(nameof(ExecutionContext.EngineIntrinsics), InstanceFlags); internal static readonly MethodInfo FileRedirection_BindForExpression = - typeof(FileRedirection).GetMethod(nameof(FileRedirection.BindForExpression), instanceFlags); + typeof(FileRedirection).GetMethod(nameof(FileRedirection.BindForExpression), InstanceFlags); + internal static readonly MethodInfo FileRedirection_CallDoCompleteForExpression = - typeof(FileRedirection).GetMethod(nameof(FileRedirection.CallDoCompleteForExpression), instanceFlags); + typeof(FileRedirection).GetMethod(nameof(FileRedirection.CallDoCompleteForExpression), InstanceFlags); + internal static readonly ConstructorInfo FileRedirection_ctor = - typeof(FileRedirection).GetConstructor(instanceFlags, null, CallingConventions.Standard, - new Type[] { typeof(RedirectionStream), typeof(bool), typeof(string) }, null); + typeof(FileRedirection).GetConstructor( + InstanceFlags, + null, + CallingConventions.Standard, + new Type[] { typeof(RedirectionStream), typeof(bool), typeof(string) }, + null); + internal static readonly MethodInfo FileRedirection_Dispose = typeof(FileRedirection).GetMethod(nameof(FileRedirection.Dispose)); internal static readonly FieldInfo FunctionContext__currentSequencePointIndex = - typeof(FunctionContext).GetField(nameof(FunctionContext._currentSequencePointIndex), instanceFlags); + typeof(FunctionContext).GetField(nameof(FunctionContext._currentSequencePointIndex), InstanceFlags); + internal static readonly FieldInfo FunctionContext__executionContext = - typeof(FunctionContext).GetField(nameof(FunctionContext._executionContext), instanceFlags); + typeof(FunctionContext).GetField(nameof(FunctionContext._executionContext), InstanceFlags); + internal static readonly FieldInfo FunctionContext__functionName = - typeof(FunctionContext).GetField(nameof(FunctionContext._functionName), instanceFlags); + typeof(FunctionContext).GetField(nameof(FunctionContext._functionName), InstanceFlags); + internal static readonly FieldInfo FunctionContext__localsTuple = - typeof(FunctionContext).GetField(nameof(FunctionContext._localsTuple), instanceFlags); + typeof(FunctionContext).GetField(nameof(FunctionContext._localsTuple), InstanceFlags); + internal static readonly FieldInfo FunctionContext__outputPipe = - typeof(FunctionContext).GetField(nameof(FunctionContext._outputPipe), instanceFlags); - internal static readonly FieldInfo FunctionContext__traps = - typeof(FunctionContext).GetField(nameof(FunctionContext._traps), instanceFlags); + typeof(FunctionContext).GetField(nameof(FunctionContext._outputPipe), InstanceFlags); + internal static readonly MethodInfo FunctionContext_PopTrapHandlers = - typeof(FunctionContext).GetMethod(nameof(FunctionContext.PopTrapHandlers), instanceFlags); + typeof(FunctionContext).GetMethod(nameof(FunctionContext.PopTrapHandlers), InstanceFlags); + internal static readonly MethodInfo FunctionContext_PushTrapHandlers = - typeof(FunctionContext).GetMethod(nameof(FunctionContext.PushTrapHandlers), instanceFlags); + typeof(FunctionContext).GetMethod(nameof(FunctionContext.PushTrapHandlers), InstanceFlags); internal static readonly MethodInfo FunctionOps_DefineFunction = - typeof(FunctionOps).GetMethod(nameof(FunctionOps.DefineFunction), staticFlags); + typeof(FunctionOps).GetMethod(nameof(FunctionOps.DefineFunction), StaticFlags); internal static readonly ConstructorInfo Hashtable_ctor = - typeof(Hashtable).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, - CallingConventions.Standard, new Type[] { typeof(int), typeof(IEqualityComparer) }, null); + typeof(Hashtable).GetConstructor( + BindingFlags.Instance | BindingFlags.Public, + null, + CallingConventions.Standard, + 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); + typeof(HashtableOps).GetMethod(nameof(HashtableOps.Add), StaticFlags); + internal static readonly MethodInfo HashtableOps_AddKeyValuePair = - typeof(HashtableOps).GetMethod(nameof(HashtableOps.AddKeyValuePair), staticFlags); + typeof(HashtableOps).GetMethod(nameof(HashtableOps.AddKeyValuePair), StaticFlags); internal static readonly PropertyInfo ICollection_Count = typeof(ICollection).GetProperty(nameof(ICollection.Count)); @@ -226,9 +270,6 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo IComparable_CompareTo = typeof(IComparable).GetMethod(nameof(IComparable.CompareTo)); - internal static readonly MethodInfo IDictionary_set_Item = - typeof(IDictionary).GetMethod("set_Item"); - internal static readonly MethodInfo IDisposable_Dispose = typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose)); @@ -237,6 +278,7 @@ internal static class CachedReflectionInfo internal static readonly PropertyInfo IEnumerator_Current = typeof(IEnumerator).GetProperty(nameof(IEnumerator.Current)); + internal static readonly MethodInfo IEnumerator_MoveNext = typeof(IEnumerator).GetMethod(nameof(IEnumerator.MoveNext)); @@ -244,119 +286,163 @@ internal static class CachedReflectionInfo typeof(IList).GetMethod("get_Item"); internal static readonly MethodInfo InterpreterError_NewInterpreterException = - typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterException), staticFlags); - internal static readonly MethodInfo InterpreterError_NewInterpreterExceptionWithInnerException = - typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterExceptionWithInnerException), staticFlags); + typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterException), StaticFlags); - internal static readonly MethodInfo IntOps_Range = - typeof(IntOps).GetMethod(nameof(IntOps.Range), staticFlags); + internal static readonly MethodInfo InterpreterError_NewInterpreterExceptionWithInnerException = + typeof(InterpreterError).GetMethod(nameof(InterpreterError.NewInterpreterExceptionWithInnerException), StaticFlags); internal static readonly MethodInfo LanguagePrimitives_GetInvalidCastMessages = - typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.GetInvalidCastMessages), staticFlags); + typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.GetInvalidCastMessages), StaticFlags); + + internal static readonly MethodInfo LanguagePrimitives_IsNull = + typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.IsNull), StaticFlags); + internal static readonly MethodInfo LanguagePrimitives_ThrowInvalidCastException = - typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.ThrowInvalidCastException), staticFlags); + typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.ThrowInvalidCastException), StaticFlags); internal static readonly MethodInfo LocalPipeline_GetExecutionContextFromTLS = - typeof(LocalPipeline).GetMethod(nameof(LocalPipeline.GetExecutionContextFromTLS), staticFlags); + typeof(LocalPipeline).GetMethod(nameof(LocalPipeline.GetExecutionContextFromTLS), StaticFlags); internal static readonly MethodInfo LoopFlowException_MatchLabel = - typeof(LoopFlowException).GetMethod(nameof(LoopFlowException.MatchLabel), instanceFlags); + typeof(LoopFlowException).GetMethod(nameof(LoopFlowException.MatchLabel), InstanceFlags); internal static readonly MethodInfo MergingRedirection_BindForExpression = - typeof(MergingRedirection).GetMethod(nameof(MergingRedirection.BindForExpression), instanceFlags); + typeof(MergingRedirection).GetMethod(nameof(MergingRedirection.BindForExpression), InstanceFlags); internal static readonly ConstructorInfo MethodException_ctor = - typeof(MethodException).GetConstructor(instanceFlags, null, - new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, null); + typeof(MethodException).GetConstructor( + InstanceFlags, + null, + new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, + null); internal static readonly MethodInfo MutableTuple_IsValueSet = - typeof(MutableTuple).GetMethod(nameof(MutableTuple.IsValueSet), instanceFlags); + typeof(MutableTuple).GetMethod(nameof(MutableTuple.IsValueSet), InstanceFlags); internal static readonly MethodInfo Object_Equals = typeof(object).GetMethod(nameof(object.Equals), new Type[] { typeof(object) }); internal static readonly ConstructorInfo OrderedDictionary_ctor = - typeof(OrderedDictionary).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, - CallingConventions.Standard, new Type[] { typeof(int), typeof(IEqualityComparer) }, null); + typeof(OrderedDictionary).GetConstructor( + BindingFlags.Instance | BindingFlags.Public, + null, + CallingConventions.Standard, + new Type[] { typeof(int), typeof(IEqualityComparer) }, + null); internal static readonly MethodInfo Parser_ScanNumber = - typeof(Parser).GetMethod(nameof(Parser.ScanNumber), staticFlags); + typeof(Parser).GetMethod(nameof(Parser.ScanNumber), StaticFlags); internal static readonly MethodInfo ParserOps_ContainsOperatorCompiled = - typeof(ParserOps).GetMethod(nameof(ParserOps.ContainsOperatorCompiled), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.ContainsOperatorCompiled), StaticFlags); + internal static readonly MethodInfo ParserOps_ImplicitOp = - typeof(ParserOps).GetMethod(nameof(ParserOps.ImplicitOp), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.ImplicitOp), StaticFlags); + internal static readonly MethodInfo ParserOps_JoinOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.JoinOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.JoinOperator), StaticFlags); + internal static readonly MethodInfo ParserOps_LikeOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.LikeOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.LikeOperator), StaticFlags); + internal static readonly MethodInfo ParserOps_MatchOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.MatchOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.MatchOperator), StaticFlags); + + internal static readonly MethodInfo ParserOps_RangeOperator = + typeof(ParserOps).GetMethod(nameof(ParserOps.RangeOperator), StaticFlags); + + internal static readonly MethodInfo ParserOps_GetRangeEnumerator = + typeof(ParserOps).GetMethod(nameof(ParserOps.GetRangeEnumerator), StaticFlags); + internal static readonly MethodInfo ParserOps_ReplaceOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.ReplaceOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.ReplaceOperator), StaticFlags); + internal static readonly MethodInfo ParserOps_SplitOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.SplitOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.SplitOperator), StaticFlags); + internal static readonly MethodInfo ParserOps_UnaryJoinOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.UnaryJoinOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.UnaryJoinOperator), StaticFlags); + internal static readonly MethodInfo ParserOps_UnarySplitOperator = - typeof(ParserOps).GetMethod(nameof(ParserOps.UnarySplitOperator), staticFlags); + typeof(ParserOps).GetMethod(nameof(ParserOps.UnarySplitOperator), StaticFlags); internal static readonly ConstructorInfo Pipe_ctor = - typeof(Pipe).GetConstructor(instanceFlags, null, CallingConventions.Standard, new Type[] { typeof(List) }, null); + typeof(Pipe).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(List) }, null); + internal static readonly MethodInfo Pipe_Add = - typeof(Pipe).GetMethod(nameof(Pipe.Add), instanceFlags); + typeof(Pipe).GetMethod(nameof(Pipe.Add), InstanceFlags); + internal static readonly MethodInfo Pipe_SetVariableListForTemporaryPipe = - typeof(Pipe).GetMethod(nameof(Pipe.SetVariableListForTemporaryPipe), instanceFlags); + typeof(Pipe).GetMethod(nameof(Pipe.SetVariableListForTemporaryPipe), InstanceFlags); internal static readonly MethodInfo PipelineOps_CheckAutomationNullInCommandArgument = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgument), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgument), StaticFlags); + internal static readonly MethodInfo PipelineOps_CheckAutomationNullInCommandArgumentArray = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgumentArray), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckAutomationNullInCommandArgumentArray), StaticFlags); + internal static readonly MethodInfo PipelineOps_CheckForInterrupts = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckForInterrupts), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.CheckForInterrupts), StaticFlags); + internal static readonly MethodInfo PipelineOps_GetExitException = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.GetExitException), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.GetExitException), StaticFlags); + internal static readonly MethodInfo PipelineOps_FlushPipe = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.FlushPipe), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.FlushPipe), StaticFlags); + internal static readonly MethodInfo PipelineOps_InvokePipeline = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipeline), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipeline), StaticFlags); + internal static readonly MethodInfo PipelineOps_InvokePipelineInBackground = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipelineInBackground), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.InvokePipelineInBackground), StaticFlags); + internal static readonly MethodInfo PipelineOps_Nop = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.Nop), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.Nop), StaticFlags); + internal static readonly MethodInfo PipelineOps_PipelineResult = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.PipelineResult), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.PipelineResult), StaticFlags); + internal static readonly MethodInfo PipelineOps_ClearPipe = - typeof(PipelineOps).GetMethod(nameof(PipelineOps.ClearPipe), staticFlags); + typeof(PipelineOps).GetMethod(nameof(PipelineOps.ClearPipe), StaticFlags); internal static readonly MethodInfo PSGetDynamicMemberBinder_GetIDictionaryMember = - typeof(PSGetDynamicMemberBinder).GetMethod(nameof(PSGetDynamicMemberBinder.GetIDictionaryMember), staticFlags); + typeof(PSGetDynamicMemberBinder).GetMethod(nameof(PSGetDynamicMemberBinder.GetIDictionaryMember), StaticFlags); internal static readonly MethodInfo PSGetMemberBinder_CloneMemberInfo = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.CloneMemberInfo), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.CloneMemberInfo), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_GetAdaptedValue = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetAdaptedValue), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetAdaptedValue), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_GetTypeTableFromTLS = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetTypeTableFromTLS), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.GetTypeTableFromTLS), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_IsTypeNameSame = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.IsTypeNameSame), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.IsTypeNameSame), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_TryGetGenericDictionaryValue = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetGenericDictionaryValue), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetGenericDictionaryValue), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_TryGetInstanceMember = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetInstanceMember), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetInstanceMember), StaticFlags); + internal static readonly MethodInfo PSGetMemberBinder_TryGetIDictionaryValue = - typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetIDictionaryValue), staticFlags); + typeof(PSGetMemberBinder).GetMethod(nameof(PSGetMemberBinder.TryGetIDictionaryValue), StaticFlags); internal static readonly MethodInfo PSInvokeMemberBinder_InvokeAdaptedMember = - typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedMember), staticFlags); + typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedMember), StaticFlags); + internal static readonly MethodInfo PSInvokeMemberBinder_InvokeAdaptedSetMember = - typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedSetMember), staticFlags); + typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.InvokeAdaptedSetMember), StaticFlags); + 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); + typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.IsHeterogeneousArray), 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); + typeof(PSInvokeMemberBinder).GetMethod(nameof(PSInvokeMemberBinder.TryGetInstanceMethod), StaticFlags); internal static readonly MethodInfo PSMethodInfo_Invoke = typeof(PSMethodInfo).GetMethod(nameof(PSMethodInfo.Invoke)); @@ -365,162 +451,206 @@ internal static class CachedReflectionInfo typeof(PSNoteProperty).GetProperty(nameof(PSNoteProperty.Value)); internal static readonly MethodInfo PSObject_Base = - typeof(PSObject).GetMethod(nameof(PSObject.Base), staticFlags); + typeof(PSObject).GetMethod(nameof(PSObject.Base), StaticFlags); + internal static readonly PropertyInfo PSObject_BaseObject = typeof(PSObject).GetProperty(nameof(PSObject.BaseObject)); - internal static readonly FieldInfo PSObject_isDeserialized = - typeof(PSObject).GetField(nameof(PSObject.isDeserialized), instanceFlags); + + internal static readonly PropertyInfo PSObject_IsDeserialized = + typeof(PSObject).GetProperty(nameof(PSObject.IsDeserialized), InstanceFlags); + internal static readonly MethodInfo PSObject_ToStringParser = - typeof(PSObject).GetMethod(nameof(PSObject.ToStringParser), staticFlags); + typeof(PSObject).GetMethod(nameof(PSObject.ToStringParser), StaticFlags, null, new[] { typeof(ExecutionContext), typeof(object) }, null); internal static readonly PropertyInfo PSReference_Value = typeof(PSReference).GetProperty(nameof(PSReference.Value)); internal static readonly MethodInfo PSScriptMethod_InvokeScript = - typeof(PSScriptMethod).GetMethod(nameof(PSScriptMethod.InvokeScript), staticFlags); + typeof(PSScriptMethod).GetMethod(nameof(PSScriptMethod.InvokeScript), StaticFlags); internal static readonly MethodInfo PSScriptProperty_InvokeGetter = - typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeGetter), instanceFlags); + typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeGetter), InstanceFlags); + internal static readonly MethodInfo PSScriptProperty_InvokeSetter = - typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeSetter), instanceFlags); + typeof(PSScriptProperty).GetMethod(nameof(PSScriptProperty.InvokeSetter), InstanceFlags); internal static readonly MethodInfo PSSetMemberBinder_SetAdaptedValue = - typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetAdaptedValue), staticFlags); + 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); + typeof(PSVariableAssignmentBinder).GetMethod(nameof(PSVariableAssignmentBinder.CopyInstanceMembersOfValueType), StaticFlags); + internal static readonly FieldInfo PSVariableAssignmentBinder__mutableValueWithInstanceMemberVersion = - typeof(PSVariableAssignmentBinder).GetField(nameof(PSVariableAssignmentBinder.s_mutableValueWithInstanceMemberVersion), staticFlags); + typeof(PSVariableAssignmentBinder).GetField(nameof(PSVariableAssignmentBinder.s_mutableValueWithInstanceMemberVersion), StaticFlags); internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeNonPublic = - typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeNonPublic), staticFlags); - internal static readonly MethodInfo PSCreateInstanceBinder_GetTargetTypeName = - typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.GetTargetTypeName), staticFlags); + typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeNonPublic), StaticFlags); - internal static readonly ConstructorInfo RangeEnumerator_ctor = - typeof(RangeEnumerator).GetConstructor(new Type[] { typeof(int), typeof(int) }); + internal static readonly MethodInfo PSCreateInstanceBinder_IsTargetTypeByRefLike = + typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.IsTargetTypeByRefLike), StaticFlags); + + internal static readonly MethodInfo PSCreateInstanceBinder_GetTargetTypeName = + typeof(PSCreateInstanceBinder).GetMethod(nameof(PSCreateInstanceBinder.GetTargetTypeName), StaticFlags); internal static readonly MethodInfo ReservedNameMembers_GeneratePSAdaptedMemberSet = - typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSAdaptedMemberSet), staticFlags); + typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSAdaptedMemberSet), StaticFlags); + internal static readonly MethodInfo ReservedNameMembers_GeneratePSBaseMemberSet = - typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSBaseMemberSet), staticFlags); + typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSBaseMemberSet), StaticFlags); + internal static readonly MethodInfo ReservedNameMembers_GeneratePSExtendedMemberSet = - typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSExtendedMemberSet), staticFlags); + typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSExtendedMemberSet), StaticFlags); + internal static readonly MethodInfo ReservedNameMembers_GeneratePSObjectMemberSet = - typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSObjectMemberSet), staticFlags); + typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.GeneratePSObjectMemberSet), StaticFlags); + internal static readonly MethodInfo ReservedNameMembers_PSTypeNames = typeof(ReservedNameMembers).GetMethod(nameof(ReservedNameMembers.PSTypeNames)); internal static readonly MethodInfo RestrictedLanguageChecker_CheckDataStatementLanguageModeAtRuntime = - typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementLanguageModeAtRuntime), staticFlags); + typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementLanguageModeAtRuntime), StaticFlags); + internal static readonly MethodInfo RestrictedLanguageChecker_CheckDataStatementAstAtRuntime = - typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementAstAtRuntime), staticFlags); + typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.CheckDataStatementAstAtRuntime), StaticFlags); internal static readonly MethodInfo RestrictedLanguageChecker_EnsureUtilityModuleLoaded = - typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.EnsureUtilityModuleLoaded), staticFlags); - - + typeof(RestrictedLanguageChecker).GetMethod(nameof(RestrictedLanguageChecker.EnsureUtilityModuleLoaded), StaticFlags); internal static readonly ConstructorInfo ReturnException_ctor = - typeof(ReturnException).GetConstructor(instanceFlags, null, CallingConventions.Standard, new Type[] { typeof(object) }, null); + typeof(ReturnException).GetConstructor(InstanceFlags, null, CallingConventions.Standard, new Type[] { typeof(object) }, null); internal static readonly PropertyInfo RuntimeException_ErrorRecord = typeof(RuntimeException).GetProperty(nameof(RuntimeException.ErrorRecord)); internal static readonly MethodInfo ScriptBlock_DoInvokeReturnAsIs = - typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.DoInvokeReturnAsIs), instanceFlags); + typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.DoInvokeReturnAsIs), InstanceFlags); + internal static readonly MethodInfo ScriptBlock_InvokeAsDelegateHelper = - typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.InvokeAsDelegateHelper), instanceFlags); - internal static readonly MethodInfo ScriptBlock_InvokeAsMemberFunction = - typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.InvokeAsMemberFunction), instanceFlags); + typeof(ScriptBlock).GetMethod(nameof(ScriptBlock.InvokeAsDelegateHelper), InstanceFlags); internal static readonly MethodInfo ScriptBlockExpressionWrapper_GetScriptBlock = - typeof(ScriptBlockExpressionWrapper).GetMethod(nameof(ScriptBlockExpressionWrapper.GetScriptBlock), instanceFlags); + typeof(ScriptBlockExpressionWrapper).GetMethod(nameof(ScriptBlockExpressionWrapper.GetScriptBlock), InstanceFlags); internal static readonly ConstructorInfo SetValueException_ctor = - typeof(SetValueException).GetConstructor(instanceFlags, null, - new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, null); + typeof(SetValueException).GetConstructor( + InstanceFlags, + null, + new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, + null); internal static readonly ConstructorInfo GetValueException_ctor = - typeof(GetValueException).GetConstructor(instanceFlags, null, - new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, null); + typeof(GetValueException).GetConstructor( + InstanceFlags, + null, + new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, + null); + internal static readonly ConstructorInfo StreamReader_ctor = typeof(StreamReader).GetConstructor(new Type[] { typeof(string) }); + internal static readonly MethodInfo StreamReader_ReadLine = - typeof(StreamReader).GetMethod(nameof(StreamReader.ReadLine)); + typeof(StreamReader).GetMethod(nameof(StreamReader.ReadLine), Type.EmptyTypes); internal static readonly ConstructorInfo String_ctor_char_int = - typeof(String).GetConstructor(new Type[] { typeof(char), typeof(int) }); + typeof(string).GetConstructor(new Type[] { typeof(char), typeof(int) }); + internal static readonly MethodInfo String_Concat_String = - typeof(String).GetMethod(nameof(String.Concat), staticPublicFlags, null, - CallingConventions.Standard, new Type[] { typeof(string), typeof(string) }, null); + typeof(string).GetMethod( + nameof(string.Concat), + StaticPublicFlags, null, + CallingConventions.Standard, + new Type[] { typeof(string), typeof(string) }, + null); + internal static readonly MethodInfo String_Equals = - typeof(String).GetMethod(nameof(String.Equals), staticPublicFlags, null, - CallingConventions.Standard, - new Type[] { typeof(string), typeof(string), typeof(StringComparison) }, null); - internal static readonly MethodInfo String_get_Chars = - typeof(string).GetMethod("get_Chars"); - internal static readonly PropertyInfo String_Length = - typeof(string).GetProperty(nameof(String.Length)); + typeof(string).GetMethod( + nameof(string.Equals), + StaticPublicFlags, + null, + CallingConventions.Standard, + new Type[] { typeof(string), typeof(string), typeof(StringComparison) }, + null); internal static readonly MethodInfo StringOps_Compare = - typeof(StringOps).GetMethod(nameof(StringOps.Compare), staticFlags); + typeof(StringOps).GetMethod(nameof(StringOps.Compare), StaticFlags); + internal static readonly MethodInfo StringOps_Equals = - typeof(StringOps).GetMethod(nameof(StringOps.Equals), staticFlags); + typeof(StringOps).GetMethod(nameof(StringOps.Equals), StaticFlags); internal static readonly MethodInfo StringOps_FormatOperator = - typeof(StringOps).GetMethod(nameof(StringOps.FormatOperator), staticFlags); + typeof(StringOps).GetMethod(nameof(StringOps.FormatOperator), StaticFlags); + internal static readonly MethodInfo StringOps_Multiply = - typeof(StringOps).GetMethod(nameof(StringOps.Multiply), staticFlags); + typeof(StringOps).GetMethod(nameof(StringOps.Multiply), StaticFlags); internal static readonly MethodInfo SwitchOps_ConditionSatisfiedRegex = - typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedRegex), staticFlags); + typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedRegex), StaticFlags); + internal static readonly MethodInfo SwitchOps_ConditionSatisfiedWildcard = - typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedWildcard), staticFlags); + typeof(SwitchOps).GetMethod(nameof(SwitchOps.ConditionSatisfiedWildcard), StaticFlags); + internal static readonly MethodInfo SwitchOps_ResolveFilePath = - typeof(SwitchOps).GetMethod(nameof(SwitchOps.ResolveFilePath), staticFlags); + typeof(SwitchOps).GetMethod(nameof(SwitchOps.ResolveFilePath), StaticFlags); internal static readonly MethodInfo TypeOps_AsOperator = - typeof(TypeOps).GetMethod(nameof(TypeOps.AsOperator), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.AsOperator), StaticFlags); + internal static readonly MethodInfo TypeOps_AddPowerShellTypesToTheScope = - typeof(TypeOps).GetMethod(nameof(TypeOps.AddPowerShellTypesToTheScope), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.AddPowerShellTypesToTheScope), StaticFlags); + internal static readonly MethodInfo TypeOps_InitPowerShellTypesAtRuntime = - typeof(TypeOps).GetMethod(nameof(TypeOps.InitPowerShellTypesAtRuntime), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.InitPowerShellTypesAtRuntime), StaticFlags); + internal static readonly MethodInfo TypeOps_SetCurrentTypeResolutionState = - typeof(TypeOps).GetMethod(nameof(TypeOps.SetCurrentTypeResolutionState), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.SetCurrentTypeResolutionState), StaticFlags); + internal static readonly MethodInfo TypeOps_SetAssemblyDefiningPSTypes = - typeof(TypeOps).GetMethod(nameof(TypeOps.SetAssemblyDefiningPSTypes), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.SetAssemblyDefiningPSTypes), StaticFlags); + internal static readonly MethodInfo TypeOps_IsInstance = - typeof(TypeOps).GetMethod(nameof(TypeOps.IsInstance), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.IsInstance), StaticFlags); + internal static readonly MethodInfo TypeOps_ResolveTypeName = - typeof(TypeOps).GetMethod(nameof(TypeOps.ResolveTypeName), staticFlags); + typeof(TypeOps).GetMethod(nameof(TypeOps.ResolveTypeName), StaticFlags); internal static readonly MethodInfo VariableOps_GetUsingValue = - typeof(VariableOps).GetMethod(nameof(VariableOps.GetUsingValue), staticFlags); + typeof(VariableOps).GetMethod(nameof(VariableOps.GetUsingValue), StaticFlags); + internal static readonly MethodInfo VariableOps_GetVariableAsRef = - typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableAsRef), staticFlags); + typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableAsRef), StaticFlags); + internal static readonly MethodInfo VariableOps_GetVariableValue = - typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableValue), staticFlags); + typeof(VariableOps).GetMethod(nameof(VariableOps.GetVariableValue), StaticFlags); + internal static readonly MethodInfo VariableOps_GetAutomaticVariableValue = - typeof(VariableOps).GetMethod(nameof(VariableOps.GetAutomaticVariableValue), staticFlags); + typeof(VariableOps).GetMethod(nameof(VariableOps.GetAutomaticVariableValue), StaticFlags); + internal static readonly MethodInfo VariableOps_SetVariableValue = - typeof(VariableOps).GetMethod(nameof(VariableOps.SetVariableValue), staticFlags); + typeof(VariableOps).GetMethod(nameof(VariableOps.SetVariableValue), StaticFlags); internal static readonly MethodInfo Utils_IsComObject = - typeof(Utils).GetMethod(nameof(Utils.IsComObject), staticFlags, binder: null, types: new Type[] {typeof(object)}, modifiers: null); + typeof(Utils).GetMethod(nameof(Utils.IsComObject), StaticFlags); internal static readonly MethodInfo ClassOps_ValidateSetProperty = - typeof(ClassOps).GetMethod(nameof(ClassOps.ValidateSetProperty), staticPublicFlags); + typeof(ClassOps).GetMethod(nameof(ClassOps.ValidateSetProperty), StaticPublicFlags); + internal static readonly MethodInfo ClassOps_CallBaseCtor = - typeof(ClassOps).GetMethod(nameof(ClassOps.CallBaseCtor), staticPublicFlags); + typeof(ClassOps).GetMethod(nameof(ClassOps.CallBaseCtor), StaticPublicFlags); + internal static readonly MethodInfo ClassOps_CallMethodNonVirtually = - typeof(ClassOps).GetMethod(nameof(ClassOps.CallMethodNonVirtually), staticPublicFlags); + typeof(ClassOps).GetMethod(nameof(ClassOps.CallMethodNonVirtually), StaticPublicFlags); + internal static readonly MethodInfo ClassOps_CallVoidMethodNonVirtually = - typeof(ClassOps).GetMethod(nameof(ClassOps.CallVoidMethodNonVirtually), staticPublicFlags); + typeof(ClassOps).GetMethod(nameof(ClassOps.CallVoidMethodNonVirtually), StaticPublicFlags); internal static readonly MethodInfo ArgumentTransformationAttribute_Transform = - typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), instancePublicFlags); - // ReSharper restore InconsistentNaming + typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), InstancePublicFlags); + + internal static readonly MethodInfo MemberInvocationLoggingOps_LogMemberInvocation = + typeof(MemberInvocationLoggingOps).GetMethod(nameof(MemberInvocationLoggingOps.LogMemberInvocation), StaticFlags); } internal static class ExpressionCache @@ -539,21 +669,21 @@ internal static class ExpressionCache internal static readonly Expression NullType = Expression.Constant(null, typeof(Type)); internal static readonly Expression NullDelegateArray = Expression.Constant(null, typeof(Action[])); internal static readonly Expression NullPipe = Expression.Constant(new Pipe { NullPipe = true }); - internal static readonly Expression ConstEmptyString = Expression.Constant(""); + internal static readonly Expression ConstEmptyString = Expression.Constant(string.Empty); internal static readonly Expression CompareOptionsIgnoreCase = Expression.Constant(CompareOptions.IgnoreCase); internal static readonly Expression CompareOptionsNone = Expression.Constant(CompareOptions.None); internal static readonly Expression Ordinal = Expression.Constant(StringComparison.Ordinal); internal static readonly Expression InvariantCulture = Expression.Constant(CultureInfo.InvariantCulture); - internal static readonly Expression CurrentCultureIgnoreCaseComparer = Expression.Constant(StringComparer.CurrentCultureIgnoreCase, typeof(StringComparer)); + internal static readonly Expression OrdinalIgnoreCaseComparer = Expression.Constant(StringComparer.OrdinalIgnoreCase, typeof(StringComparer)); internal static readonly Expression CatchAllType = Expression.Constant(typeof(ExceptionHandlingOps.CatchAll), typeof(Type)); + // Empty expression is used at the end of blocks to give them the void expression result internal static readonly Expression Empty = Expression.Empty(); - internal static Expression GetExecutionContextFromTLS = + + internal static readonly Expression GetExecutionContextFromTLS = Expression.Call(CachedReflectionInfo.LocalPipeline_GetExecutionContextFromTLS); - internal static readonly Expression BoxedTrue = Expression.Field(null, - typeof(Boxed).GetField("True", BindingFlags.Static | BindingFlags.NonPublic)); - internal static readonly Expression BoxedFalse = Expression.Field(null, - typeof(Boxed).GetField("False", BindingFlags.Static | BindingFlags.NonPublic)); + internal static readonly Expression BoxedTrue = Expression.Field(null, typeof(Boxed).GetField("True", BindingFlags.Static | BindingFlags.NonPublic)); + internal static readonly Expression BoxedFalse = Expression.Field(null, typeof(Boxed).GetField("False", BindingFlags.Static | BindingFlags.NonPublic)); private static readonly Expression[] s_intConstants = new Expression[102]; @@ -571,6 +701,7 @@ internal static Expression Constant(int i) result = Expression.Constant(i); s_intConstants[i + 1] = result; } + return result; } @@ -604,7 +735,7 @@ internal static Expression Convert(this Expression expr, Type type) return Expression.Convert(expr, type); } - if (type.GetTypeInfo().ContainsGenericParameters) + if (type.ContainsGenericParameters || type.IsByRefLike) { return Expression.Call( CachedReflectionInfo.LanguagePrimitives_ThrowInvalidCastException, @@ -622,15 +753,16 @@ internal static Expression Cast(this Expression expr, Type type) return expr; } - if ((expr.Type.IsFloating() || expr.Type == typeof(Decimal)) && type.GetTypeInfo().IsPrimitive) + if ((expr.Type.IsFloating() || expr.Type == typeof(decimal)) && type.IsPrimitive) { // Convert correctly handles most "primitive" conversions for PowerShell, // but it does not correctly handle floating point. - - expr = Expression.Call(CachedReflectionInfo.Convert_ChangeType, - Expression.Convert(expr, typeof(object)), - Expression.Constant(type, typeof(Type))); + expr = Expression.Call( + CachedReflectionInfo.Convert_ChangeType, + Expression.Convert(expr, typeof(object)), + Expression.Constant(type, typeof(Type))); } + return Expression.Convert(expr, type); } @@ -656,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[]>>(); @@ -680,39 +812,55 @@ internal void PopTrapHandlers() internal class Compiler : ICustomAstVisitor2 { - internal static readonly ParameterExpression _executionContextParameter; - internal static readonly ParameterExpression _functionContext; - internal static readonly ParameterExpression _returnPipe; + internal static readonly ParameterExpression s_executionContextParameter; + internal static readonly ParameterExpression s_functionContext; + private static readonly ParameterExpression s_returnPipe; + private static readonly Expression s_notDollarQuestion; + private static readonly Expression s_getDollarQuestion; private static readonly Expression s_setDollarQuestionToTrue; private static readonly Expression s_callCheckForInterrupts; private static readonly Expression s_getCurrentPipe; private static readonly Expression s_currentExceptionBeingHandled; private static readonly CatchBlock s_catchFlowControl; - internal static readonly CatchBlock[] _stmtCatchHandlers; + private static readonly CatchBlock[] s_stmtCatchHandlers; internal static readonly Type DottedLocalsTupleType = MutableTuple.MakeTupleType(SpecialVariables.AutomaticVariableTypes); - internal static Type DottedScriptCmdletLocalsTupleType = - MutableTuple.MakeTupleType(SpecialVariables.AutomaticVariableTypes.Concat(SpecialVariables.PreferenceVariableTypes).ToArray()); + internal static readonly Dictionary DottedLocalsNameIndexMap = new Dictionary(SpecialVariables.AutomaticVariableTypes.Length, StringComparer.OrdinalIgnoreCase); + internal static readonly Dictionary DottedScriptCmdletLocalsNameIndexMap = - new Dictionary(SpecialVariables.AutomaticVariableTypes.Length + SpecialVariables.PreferenceVariableTypes.Length, - StringComparer.OrdinalIgnoreCase); + new Dictionary( + SpecialVariables.AutomaticVariableTypes.Length + SpecialVariables.PreferenceVariableTypes.Length, + StringComparer.OrdinalIgnoreCase); static Compiler() { - _functionContext = Expression.Parameter(typeof(FunctionContext), "funcContext"); - _executionContextParameter = Expression.Variable(typeof(ExecutionContext), "context"); + 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"); + + s_getDollarQuestion = Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue); + + s_notDollarQuestion = Expression.Not(s_getDollarQuestion); s_setDollarQuestionToTrue = Expression.Assign( - Expression.Property(_executionContextParameter, CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue), + s_getDollarQuestion, ExpressionCache.TrueConstant); - s_callCheckForInterrupts = Expression.Call(CachedReflectionInfo.PipelineOps_CheckForInterrupts, - _executionContextParameter); + s_callCheckForInterrupts = Expression.Call( + CachedReflectionInfo.PipelineOps_CheckForInterrupts, + s_executionContextParameter); - s_getCurrentPipe = Expression.Field(_functionContext, CachedReflectionInfo.FunctionContext__outputPipe); - _returnPipe = Expression.Variable(s_getCurrentPipe.Type, "returnPipe"); + s_getCurrentPipe = Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__outputPipe); + s_returnPipe = Expression.Variable(s_getCurrentPipe.Type, "returnPipe"); var exception = Expression.Variable(typeof(Exception), "exception"); @@ -722,11 +870,11 @@ static Compiler() Expression.Block( Expression.Call( CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference, - Compiler._functionContext, exception))); - _stmtCatchHandlers = new CatchBlock[] { s_catchFlowControl, catchAll }; + Compiler.s_functionContext, exception))); + s_stmtCatchHandlers = new CatchBlock[] { s_catchFlowControl, catchAll }; s_currentExceptionBeingHandled = Expression.Property( - _executionContextParameter, CachedReflectionInfo.ExecutionContext_CurrentExceptionBeingHandled); + s_executionContextParameter, CachedReflectionInfo.ExecutionContext_CurrentExceptionBeingHandled); int i; for (i = 0; i < SpecialVariables.AutomaticVariables.Length; ++i) @@ -734,13 +882,16 @@ static Compiler() DottedLocalsNameIndexMap.Add(SpecialVariables.AutomaticVariables[i], i); DottedScriptCmdletLocalsNameIndexMap.Add(SpecialVariables.AutomaticVariables[i], i); } + for (i = 0; i < SpecialVariables.PreferenceVariables.Length; ++i) { - DottedScriptCmdletLocalsNameIndexMap.Add(SpecialVariables.PreferenceVariables[i], - i + (int)AutomaticVariable.NumberOfAutomaticVariables); + DottedScriptCmdletLocalsNameIndexMap.Add( + SpecialVariables.PreferenceVariables[i], + i + (int)AutomaticVariable.NumberOfAutomaticVariables); } s_builtinAttributeGenerator.Add(typeof(CmdletBindingAttribute), NewCmdletBindingAttribute); + s_builtinAttributeGenerator.Add(typeof(ExperimentalAttribute), NewExperimentalAttribute); s_builtinAttributeGenerator.Add(typeof(ParameterAttribute), NewParameterAttribute); s_builtinAttributeGenerator.Add(typeof(OutputTypeAttribute), NewOutputTypeAttribute); s_builtinAttributeGenerator.Add(typeof(AliasAttribute), NewAliasAttribute); @@ -750,20 +901,26 @@ static Compiler() s_builtinAttributeGenerator.Add(typeof(ValidateNotNullOrEmptyAttribute), NewValidateNotNullOrEmptyAttribute); } - private Compiler(List sequencePoints) + private Compiler(List sequencePoints, Dictionary sequencePointIndexMap) { _sequencePoints = sequencePoints; + _sequencePointIndexMap = sequencePointIndexMap; } internal Compiler() { _sequencePoints = new List(); + _sequencePointIndexMap = new Dictionary(); } internal bool CompilingConstantExpression { get; set; } + internal bool Optimize { get; private set; } + internal Type LocalVariablesTupleType { get; private set; } + internal ParameterExpression LocalVariablesParameter { get; private set; } + private SymbolDocumentInfo _debugSymbolDocument; internal TypeDefinitionAst _memberFunctionType; private bool _compilingTrap; @@ -773,10 +930,13 @@ internal Compiler() private int _switchTupleIndex = VariableAnalysis.Unanalyzed; private int _foreachTupleIndex = VariableAnalysis.Unanalyzed; private readonly List _sequencePoints; + private readonly Dictionary _sequencePointIndexMap; private int _stmtCount; internal bool CompilingMemberFunction { get; set; } + internal SpecialMemberFunctionType SpecialMemberFunctionType { get; set; } + internal Type MemberFunctionReturnType { get @@ -784,7 +944,11 @@ internal Type MemberFunctionReturnType Diagnostics.Assert(CompilingMemberFunction, "Return not only set for member functions"); return _memberFunctionReturnType; } - set { _memberFunctionReturnType = value; } + + set + { + _memberFunctionReturnType = value; + } } private Type _memberFunctionReturnType; @@ -803,18 +967,31 @@ internal Expression CompileExpressionOperand(ExpressionAst exprAst) { result = Expression.Block(result, ExpressionCache.NullConstant); } + return result; } - private IEnumerable CompileInvocationArguments(IEnumerable arguments) + private IEnumerable CompileInvocationArguments(IReadOnlyList arguments) { - return arguments == null ? Utils.EmptyArray() : 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) { IAssignableValue av = left.GetAssignableValue(); ExpressionType et = ExpressionType.Extension; + switch (tokenKind) { case TokenKind.Equals: return av.SetValue(this, right); @@ -823,15 +1000,53 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; + case TokenKind.QuestionQuestionEquals: et = ExpressionType.Coalesce; break; } var exprs = new List(); var temps = new List(); var getExpr = av.GetValue(this, exprs, temps); - exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right))); + + if (et == ExpressionType.Coalesce) + { + exprs.Add(av.SetValue(this, Coalesce(getExpr, right))); + } + else + { + exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right))); + } + return Expression.Block(temps, exprs); } + private static Expression Coalesce(Expression left, Expression right) + { + Type leftType = left.Type; + + if (leftType.IsValueType) + { + return left; + } + else + { + ParameterExpression lhsStoreVar = Expression.Variable(typeof(object)); + var blockParameters = new ParameterExpression[] { lhsStoreVar }; + var blockStatements = new Expression[] + { + Expression.Assign(lhsStoreVar, left.Cast(typeof(object))), + Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNull, lhsStoreVar), + right.Cast(typeof(object)), + lhsStoreVar), + }; + + return Expression.Block( + typeof(object), + blockParameters, + blockStatements); + } + } + internal Expression GetLocal(int tupleIndex) { Expression result = LocalVariablesParameter; @@ -839,26 +1054,31 @@ internal Expression GetLocal(int tupleIndex) { result = Expression.Property(result, property); } + return result; } internal static Expression CallGetVariable(Expression variablePath, VariableExpressionAst varAst) { - return Expression.Call(CachedReflectionInfo.VariableOps_GetVariableValue, variablePath, - _executionContextParameter, - Expression.Constant(varAst).Cast(typeof(VariableExpressionAst))); + return Expression.Call( + CachedReflectionInfo.VariableOps_GetVariableValue, + variablePath, + s_executionContextParameter, + Expression.Constant(varAst).Cast(typeof(VariableExpressionAst))); } internal static Expression CallSetVariable(Expression variablePath, Expression rhs, Expression attributes = null) { - return Expression.Call(CachedReflectionInfo.VariableOps_SetVariableValue, - variablePath, rhs.Cast(typeof(object)), _executionContextParameter, - attributes ?? ExpressionCache.NullConstant.Cast(typeof(AttributeAst[]))); + return Expression.Call( + CachedReflectionInfo.VariableOps_SetVariableValue, + variablePath, rhs.Cast(typeof(object)), + s_executionContextParameter, + attributes ?? ExpressionCache.NullConstant.Cast(typeof(AttributeAst[]))); } internal Expression GetAutomaticVariable(VariableExpressionAst varAst) { - // Generate, in psuedo code: + // Generate, in pseudo code: // // return (localsTuple.IsValueSet(tupleIndex) // ? localsTuple.ItemXXX @@ -868,65 +1088,82 @@ 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. - + // int tupleIndex = varAst.TupleIndex; var expr = GetLocal(tupleIndex); - var callGetAutomaticVariable = Expression.Call(CachedReflectionInfo.VariableOps_GetAutomaticVariableValue, - ExpressionCache.Constant(tupleIndex), - _executionContextParameter, - Expression.Constant(varAst)).Convert(expr.Type); + var callGetAutomaticVariable = Expression.Call( + CachedReflectionInfo.VariableOps_GetAutomaticVariableValue, + ExpressionCache.Constant(tupleIndex), + s_executionContextParameter, + Expression.Constant(varAst)).Convert(expr.Type); if (!Optimize) return callGetAutomaticVariable; return Expression.Condition( - Expression.Call(LocalVariablesParameter, - CachedReflectionInfo.MutableTuple_IsValueSet, - ExpressionCache.Constant(tupleIndex)), + Expression.Call( + LocalVariablesParameter, + CachedReflectionInfo.MutableTuple_IsValueSet, + ExpressionCache.Constant(tupleIndex)), expr, callGetAutomaticVariable); } internal static Expression CallStringEquals(Expression left, Expression right, bool ignoreCase) { - return Expression.Call(CachedReflectionInfo.StringOps_Equals, left, right, ExpressionCache.InvariantCulture, - ignoreCase - ? ExpressionCache.CompareOptionsIgnoreCase - : ExpressionCache.CompareOptionsNone); + return Expression.Call( + CachedReflectionInfo.StringOps_Equals, + left, + right, + ExpressionCache.InvariantCulture, + ignoreCase ? ExpressionCache.CompareOptionsIgnoreCase : ExpressionCache.CompareOptionsNone); } internal static Expression IsStrictMode(int version, Expression executionContext = null) { - if (executionContext == null) + executionContext ??= ExpressionCache.NullExecutionContext; + + return Expression.Call( + CachedReflectionInfo.ExecutionContext_IsStrictVersion, + executionContext, + ExpressionCache.Constant(version)); + } + + private int AddSequencePoint(IScriptExtent extent) + { + // Make sure we don't add the same extent to the sequence point list twice. + if (!_sequencePointIndexMap.TryGetValue(extent, out int index)) { - executionContext = ExpressionCache.NullExecutionContext; + _sequencePoints.Add(extent); + index = _sequencePoints.Count - 1; + _sequencePointIndexMap.Add(extent, index); } - return Expression.Call(CachedReflectionInfo.ExecutionContext_IsStrictVersion, - executionContext, - ExpressionCache.Constant(version)); + return index; } - internal Expression UpdatePosition(Ast ast) + private Expression UpdatePosition(Ast ast) { - _sequencePoints.Add(ast.Extent); + IScriptExtent extent = ast.Extent; + int index = AddSequencePoint(extent); // If we just added the first sequence point, then we don't want to check for breakpoints - we'll do that // in EnterScriptFunction. // Except for while/do loops, in this case we want to check breakpoints on the first sequence point since it // will be executed multiple times. - return ((_sequencePoints.Count == 1) && !_generatingWhileOrDoLoop) + return (index == 0 && !_generatingWhileOrDoLoop) ? ExpressionCache.Empty - : new UpdatePositionExpr(ast.Extent, _sequencePoints.Count - 1, _debugSymbolDocument, !_compilingSingleExpression); + : new UpdatePositionExpr(extent, index, _debugSymbolDocument, !_compilingSingleExpression); } private int _tempCounter; + 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) @@ -944,26 +1181,36 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr) firstConvert = (ConvertExpressionAst)expr; break; } + 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; + } 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) @@ -981,11 +1228,14 @@ internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expres } // typeName can't be resolved at compile time, so defer resolution until runtime. - return DynamicExpression.Dynamic(PSDynamicConvertBinder.Get(), typeof(object), - Expression.Call(CachedReflectionInfo.TypeOps_ResolveTypeName, - Expression.Constant(typeName), - Expression.Constant(typeName.Extent)), - expr); + return DynamicExpression.Dynamic( + PSDynamicConvertBinder.Get(), + typeof(object), + Expression.Call( + CachedReflectionInfo.TypeOps_ResolveTypeName, + Expression.Constant(typeName), + Expression.Constant(typeName.Extent)), + expr); } internal static Expression ConvertValue(Expression expr, List conversions) @@ -1008,34 +1258,36 @@ internal static Expression ConvertValue(Expression expr, List internal static RuntimeDefinedParameterDictionary GetParameterMetaData(ReadOnlyCollection parameters, bool automaticPositions, ref bool usesCmdletBinding) { - var md = new RuntimeDefinedParameterDictionary(); - var listMd = new List(); + var runtimeDefinedParamDict = new RuntimeDefinedParameterDictionary(); + var runtimeDefinedParamList = new List(parameters.Count); var customParameterSet = false; for (int index = 0; index < parameters.Count; index++) { var param = parameters[index]; var rdp = GetRuntimeDefinedParameter(param, ref customParameterSet, ref usesCmdletBinding); - - listMd.Add(rdp); - md.Add(param.Name.VariablePath.UserPath, rdp); + if (rdp != null) + { + runtimeDefinedParamList.Add(rdp); + runtimeDefinedParamDict.Add(param.Name.VariablePath.UserPath, rdp); + } } int pos = 0; if (automaticPositions && !customParameterSet) { - for (int index = 0; index < listMd.Count; index++) + for (int index = 0; index < runtimeDefinedParamList.Count; index++) { - var rdp = listMd[index]; - var paramAttribute = (ParameterAttribute)rdp.Attributes.First(attr => attr is ParameterAttribute); - if (!(rdp.ParameterType == typeof(SwitchParameter))) + var rdp = runtimeDefinedParamList[index]; + var paramAttribute = (ParameterAttribute)rdp.Attributes.First(static attr => attr is ParameterAttribute); + if (rdp.ParameterType != typeof(SwitchParameter)) { paramAttribute.Position = pos++; } } } - md.Data = listMd.ToArray(); - return md; + runtimeDefinedParamDict.Data = runtimeDefinedParamList.ToArray(); + return runtimeDefinedParamDict; } private static readonly Dictionary s_attributeGeneratorCache = new Dictionary(); @@ -1060,29 +1312,45 @@ private static Delegate GetAttributeGenerator(CallInfo callInfo) s_attributeGeneratorCache.Add(callInfo, result); } } + return result; } private static readonly CallSite> s_attrArgToIntConverter = CallSite>.Create(PSConvertBinder.Get(typeof(int))); - internal static readonly CallSite> _attrArgToStringConverter = + + internal static readonly CallSite> s_attrArgToStringConverter = CallSite>.Create(PSConvertBinder.Get(typeof(string))); + private static readonly CallSite> s_attrArgToStringArrayConverter = CallSite>.Create(PSConvertBinder.Get(typeof(string[]))); + private static readonly CallSite> s_attrArgToBoolConverter = CallSite>.Create(PSConvertBinder.Get(typeof(bool))); + private static readonly CallSite> s_attrArgToConfirmImpactConverter = CallSite>.Create(PSConvertBinder.Get(typeof(ConfirmImpact))); + private static readonly CallSite> s_attrArgToRemotingCapabilityConverter = CallSite>.Create(PSConvertBinder.Get(typeof(RemotingCapability))); + private static readonly CallSite> s_attrArgToExperimentActionConverter = + CallSite>.Create(PSConvertBinder.Get(typeof(ExperimentAction))); + + private static readonly ConstantValueVisitor s_cvv = new ConstantValueVisitor { AttributeArgument = true }; + private static void CheckNoPositionalArgs(AttributeAst ast) { var positionalArgCount = ast.PositionalArguments.Count; if (positionalArgCount > 0) { - throw InterpreterError.NewInterpreterException(null, typeof(MethodException), ast.Extent, - "MethodCountCouldNotFindBest", ExtendedTypeSystem.MethodArgumentCountException, ".ctor", + throw InterpreterError.NewInterpreterException( + null, + typeof(MethodException), + ast.Extent, + "MethodCountCouldNotFindBest", + ExtendedTypeSystem.MethodArgumentCountException, + ".ctor", positionalArgCount); } } @@ -1093,31 +1361,44 @@ private static void CheckNoNamedArgs(AttributeAst ast) { var namedArg = ast.NamedArguments[0]; var argumentName = namedArg.ArgumentName; - throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, - "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, argumentName, + throw InterpreterError.NewInterpreterException( + namedArg, + typeof(RuntimeException), + namedArg.Extent, + "PropertyNotFoundForType", + ParserStrings.PropertyNotFoundForType, + argumentName, typeof(CmdletBindingAttribute)); } } + private static (string, ExperimentAction) GetFeatureNameAndAction(AttributeAst ast) + { + var argValue0 = ast.PositionalArguments[0].Accept(s_cvv); + var argValue1 = ast.PositionalArguments[1].Accept(s_cvv); + + var featureName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue0); + var action = s_attrArgToExperimentActionConverter.Target(s_attrArgToExperimentActionConverter, argValue1); + return (featureName, action); + } + private static Attribute NewCmdletBindingAttribute(AttributeAst ast) { CheckNoPositionalArgs(ast); - var cvv = new ConstantValueVisitor { AttributeArgument = true }; - var result = new CmdletBindingAttribute(); foreach (var namedArg in ast.NamedArguments) { - var argValue = namedArg.Argument.Accept(cvv); + var argValue = namedArg.Argument.Accept(s_cvv); var argumentName = namedArg.ArgumentName; if (argumentName.Equals("DefaultParameterSetName", StringComparison.OrdinalIgnoreCase)) { - result.DefaultParameterSetName = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.DefaultParameterSetName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("HelpUri", StringComparison.OrdinalIgnoreCase)) { - result.HelpUri = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.HelpUri = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("SupportsShouldProcess", StringComparison.OrdinalIgnoreCase)) { @@ -1145,8 +1426,13 @@ private static Attribute NewCmdletBindingAttribute(AttributeAst ast) } else { - throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, - "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, argumentName, + throw InterpreterError.NewInterpreterException( + namedArg, + typeof(RuntimeException), + namedArg.Extent, + "PropertyNotFoundForType", + ParserStrings.PropertyNotFoundForType, + argumentName, typeof(CmdletBindingAttribute)); } } @@ -1154,17 +1440,52 @@ private static Attribute NewCmdletBindingAttribute(AttributeAst ast) return result; } - private static Attribute NewParameterAttribute(AttributeAst ast) + private static Attribute NewExperimentalAttribute(AttributeAst ast) { - CheckNoPositionalArgs(ast); + int positionalArgCount = ast.PositionalArguments.Count; + if (positionalArgCount != 2) + { + throw InterpreterError.NewInterpreterException( + targetObject: null, + typeof(MethodException), + ast.Extent, + "MethodCountCouldNotFindBest", + ExtendedTypeSystem.MethodArgumentCountException, + ".ctor", + positionalArgCount); + } - var cvv = new ConstantValueVisitor { AttributeArgument = true }; + (string name, ExperimentAction action) = GetFeatureNameAndAction(ast); + return new ExperimentalAttribute(name, action); + } - var result = new ParameterAttribute(); + private static Attribute NewParameterAttribute(AttributeAst ast) + { + ParameterAttribute result; + int positionalArgCount = ast.PositionalArguments.Count; + switch (positionalArgCount) + { + case 0: + result = new ParameterAttribute(); + break; + case 2: + (string name, ExperimentAction action) = GetFeatureNameAndAction(ast); + result = new ParameterAttribute(name, action); + break; + default: + throw InterpreterError.NewInterpreterException( + targetObject: null, + typeof(MethodException), + ast.Extent, + "MethodCountCouldNotFindBest", + ExtendedTypeSystem.MethodArgumentCountException, + ".ctor", + positionalArgCount); + } foreach (var namedArg in ast.NamedArguments) { - var argValue = namedArg.Argument.Accept(cvv); + var argValue = namedArg.Argument.Accept(s_cvv); var argumentName = namedArg.ArgumentName; if (argumentName.Equals("Position", StringComparison.OrdinalIgnoreCase)) @@ -1173,7 +1494,7 @@ private static Attribute NewParameterAttribute(AttributeAst ast) } else if (argumentName.Equals("ParameterSetName", StringComparison.OrdinalIgnoreCase)) { - result.ParameterSetName = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.ParameterSetName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("Mandatory", StringComparison.OrdinalIgnoreCase)) { @@ -1193,15 +1514,15 @@ private static Attribute NewParameterAttribute(AttributeAst ast) } else if (argumentName.Equals("HelpMessage", StringComparison.OrdinalIgnoreCase)) { - result.HelpMessage = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.HelpMessage = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("HelpMessageBaseName", StringComparison.OrdinalIgnoreCase)) { - result.HelpMessageBaseName = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.HelpMessageBaseName = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("HelpMessageResourceId", StringComparison.OrdinalIgnoreCase)) { - result.HelpMessageResourceId = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.HelpMessageResourceId = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else if (argumentName.Equals("DontShow", StringComparison.OrdinalIgnoreCase)) { @@ -1209,8 +1530,13 @@ private static Attribute NewParameterAttribute(AttributeAst ast) } else { - throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, - "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, argumentName, + throw InterpreterError.NewInterpreterException( + namedArg, + typeof(RuntimeException), + namedArg.Extent, + "PropertyNotFoundForType", + ParserStrings.PropertyNotFoundForType, + argumentName, typeof(CmdletBindingAttribute)); } } @@ -1220,12 +1546,10 @@ private static Attribute NewParameterAttribute(AttributeAst ast) private static Attribute NewOutputTypeAttribute(AttributeAst ast) { - var cvv = new ConstantValueVisitor { AttributeArgument = true }; - OutputTypeAttribute result; if (ast.PositionalArguments.Count == 0) { - result = new OutputTypeAttribute(Utils.EmptyArray()); + result = new OutputTypeAttribute(Array.Empty()); } else if (ast.PositionalArguments.Count == 1) { @@ -1237,8 +1561,8 @@ private static Attribute NewOutputTypeAttribute(AttributeAst ast) } else { - var argValue = ast.PositionalArguments[0].Accept(cvv); - result = new OutputTypeAttribute(_attrArgToStringConverter.Target(_attrArgToStringConverter, argValue)); + var argValue = ast.PositionalArguments[0].Accept(s_cvv); + result = new OutputTypeAttribute(s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue)); } } else @@ -1250,12 +1574,20 @@ private static Attribute NewOutputTypeAttribute(AttributeAst ast) var typeArg = positionalArgument as TypeExpressionAst; args[i] = typeArg != null ? TypeOps.ResolveTypeName(typeArg.TypeName, typeArg.Extent) - : positionalArgument.Accept(cvv); + : positionalArgument.Accept(s_cvv); } 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 { @@ -1265,7 +1597,7 @@ private static Attribute NewOutputTypeAttribute(AttributeAst ast) foreach (var namedArg in ast.NamedArguments) { - var argValue = namedArg.Argument.Accept(cvv); + var argValue = namedArg.Argument.Accept(s_cvv); var argumentName = namedArg.ArgumentName; if (argumentName.Equals("ParameterSetName", StringComparison.OrdinalIgnoreCase)) @@ -1274,12 +1606,17 @@ private static Attribute NewOutputTypeAttribute(AttributeAst ast) } else if (argumentName.Equals("ProviderCmdlet", StringComparison.OrdinalIgnoreCase)) { - result.ProviderCmdlet = _attrArgToStringConverter.Target(_attrArgToStringConverter, argValue); + result.ProviderCmdlet = s_attrArgToStringConverter.Target(s_attrArgToStringConverter, argValue); } else { - throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, - "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, argumentName, + throw InterpreterError.NewInterpreterException( + namedArg, + typeof(RuntimeException), + namedArg.Extent, + "PropertyNotFoundForType", + ParserStrings.PropertyNotFoundForType, + argumentName, typeof(CmdletBindingAttribute)); } } @@ -1291,20 +1628,20 @@ private static Attribute NewAliasAttribute(AttributeAst ast) { CheckNoNamedArgs(ast); - var cvv = new ConstantValueVisitor { AttributeArgument = true }; var args = new string[ast.PositionalArguments.Count]; for (int i = 0; i < ast.PositionalArguments.Count; i++) { - args[i] = _attrArgToStringConverter.Target(_attrArgToStringConverter, - ast.PositionalArguments[i].Accept(cvv)); + args[i] = s_attrArgToStringConverter.Target( + s_attrArgToStringConverter, + ast.PositionalArguments[i].Accept(s_cvv)); } + return new AliasAttribute(args); } private static Attribute NewValidateSetAttribute(AttributeAst ast) { ValidateSetAttribute result; - var cvv = new ConstantValueVisitor { AttributeArgument = true }; // 'ValidateSet([CustomGeneratorType], IgnoreCase=$false)' is supported in scripts. if (ast.PositionalArguments.Count == 1 && ast.PositionalArguments[0] is TypeExpressionAst generatorTypeAst) @@ -1317,8 +1654,14 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) else { throw InterpreterError.NewInterpreterExceptionWithInnerException( - ast, typeof(RuntimeException), ast.Extent, "TypeNotFound", ParserStrings.TypeNotFound, exception, - generatorTypeAst.TypeName.FullName, typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName); + ast, + typeof(RuntimeException), + ast.Extent, + "TypeNotFound", + ParserStrings.TypeNotFound, + exception, + generatorTypeAst.TypeName.FullName, + typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName); } } else @@ -1327,8 +1670,9 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) var args = new string[ast.PositionalArguments.Count]; for (int i = 0; i < ast.PositionalArguments.Count; i++) { - args[i] = _attrArgToStringConverter.Target(_attrArgToStringConverter, - ast.PositionalArguments[i].Accept(cvv)); + args[i] = s_attrArgToStringConverter.Target( + s_attrArgToStringConverter, + ast.PositionalArguments[i].Accept(s_cvv)); } result = new ValidateSetAttribute(args); @@ -1336,7 +1680,7 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) foreach (var namedArg in ast.NamedArguments) { - var argValue = namedArg.Argument.Accept(cvv); + var argValue = namedArg.Argument.Accept(s_cvv); var argumentName = namedArg.ArgumentName; if (argumentName.Equals("IgnoreCase", StringComparison.OrdinalIgnoreCase)) { @@ -1348,8 +1692,13 @@ private static Attribute NewValidateSetAttribute(AttributeAst ast) } else { - throw InterpreterError.NewInterpreterException(namedArg, typeof(RuntimeException), namedArg.Extent, - "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, argumentName, + throw InterpreterError.NewInterpreterException( + namedArg, + typeof(RuntimeException), + namedArg.Extent, + "PropertyNotFoundForType", + ParserStrings.PropertyNotFoundForType, + argumentName, typeof(CmdletBindingAttribute)); } } @@ -1383,8 +1732,13 @@ internal static Attribute GetAttribute(AttributeAst attributeAst) var attributeType = attributeAst.TypeName.GetReflectionAttributeType(); if (attributeType == null) { - throw InterpreterError.NewInterpreterException(attributeAst, typeof(RuntimeException), attributeAst.Extent, - "CustomAttributeTypeNotFound", ParserStrings.CustomAttributeTypeNotFound, attributeAst.TypeName.FullName); + throw InterpreterError.NewInterpreterException( + attributeAst, + typeof(RuntimeException), + attributeAst.Extent, + "CustomAttributeTypeNotFound", + ParserStrings.CustomAttributeTypeNotFound, + attributeAst.TypeName.FullName); } Func generator; @@ -1394,24 +1748,24 @@ 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); var delegateArgs = new object[totalArgCount + 1]; delegateArgs[0] = attributeType; - var cvv = new ConstantValueVisitor { AttributeArgument = true }; int i = 1; for (int index = 0; index < attributeAst.PositionalArguments.Count; index++) { var posArg = attributeAst.PositionalArguments[index]; - delegateArgs[i++] = posArg.Accept(cvv); + delegateArgs[i++] = posArg.Accept(s_cvv); } + for (int index = 0; index < attributeAst.NamedArguments.Count; index++) { var namedArg = attributeAst.NamedArguments[index]; - delegateArgs[i++] = namedArg.Argument.Accept(cvv); + delegateArgs[i++] = namedArg.Argument.Accept(s_cvv); } try @@ -1423,12 +1777,16 @@ 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; } @@ -1442,6 +1800,7 @@ internal static Attribute GetAttribute(TypeConstraintAst typeConstraintAst) { type = ihct.CachedType; } + if (type == null) { type = TypeOps.ResolveTypeName(typeConstraintAst.TypeName, typeConstraintAst.Extent); @@ -1450,31 +1809,61 @@ internal static Attribute GetAttribute(TypeConstraintAst typeConstraintAst) ihct.CachedType = type; } } + return new ArgumentTypeConverterAttribute(type); } private static RuntimeDefinedParameter GetRuntimeDefinedParameter(ParameterAst parameterAst, ref bool customParameterSet, ref bool usesCmdletBinding) { - List attributes = new List(); + var attributes = new List(parameterAst.Attributes.Count); bool hasParameterAttribute = false; + bool hasEnabledParamAttribute = false; + bool hasSeenExpAttribute = false; + for (int index = 0; index < parameterAst.Attributes.Count; index++) { var attributeAst = parameterAst.Attributes[index]; var attribute = attributeAst.GetAttribute(); - attributes.Add(attribute); - var parameterAttribute = attribute as ParameterAttribute; - if (parameterAttribute != null) + if (attribute is ExperimentalAttribute expAttribute) + { + // Only honor the first seen experimental attribute, ignore the others. + if (!hasSeenExpAttribute && expAttribute.ToHide) + { + return null; + } + + // Do not add experimental attributes to the attribute list. + hasSeenExpAttribute = true; + continue; + } + else if (attribute is ParameterAttribute paramAttribute) { hasParameterAttribute = true; + if (paramAttribute.ToHide) + { + continue; + } + + hasEnabledParamAttribute = true; usesCmdletBinding = true; - if (parameterAttribute.Position != int.MinValue || - !parameterAttribute.ParameterSetName.Equals(ParameterAttribute.AllParameterSets, - StringComparison.OrdinalIgnoreCase)) + if (paramAttribute.Position != int.MinValue || + !paramAttribute.ParameterSetName.Equals( + ParameterAttribute.AllParameterSets, + StringComparison.OrdinalIgnoreCase)) { customParameterSet = true; } } + + attributes.Add(attribute); + } + + // If all 'ParameterAttribute' declared for the parameter are hidden due to + // an experimental feature, then the parameter should be ignored. + if (hasParameterAttribute && !hasEnabledParamAttribute) + { + return null; } attributes.Reverse(); @@ -1484,7 +1873,7 @@ private static RuntimeDefinedParameter GetRuntimeDefinedParameter(ParameterAst p } var result = new RuntimeDefinedParameter(parameterAst.Name.VariablePath.UserPath, parameterAst.StaticType, - new Collection(attributes.ToArray())); + new Collection(attributes)); if (parameterAst.DefaultValue != null) { @@ -1510,6 +1899,7 @@ private static RuntimeDefinedParameter GetRuntimeDefinedParameter(ParameterAst p result.Value = defaultValue; } } + return result; } @@ -1521,7 +1911,7 @@ internal static bool TryGetDefaultParameterValue(Type type, out object value) return true; } - if (type.GetTypeInfo().IsClass) + if (type.IsClass) { value = null; return true; @@ -1539,7 +1929,7 @@ internal static bool TryGetDefaultParameterValue(Type type, out object value) return true; } - if (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)) && !type.GetTypeInfo().IsEnum) + if (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)) && !type.IsEnum) { value = 0; return true; @@ -1552,17 +1942,25 @@ internal static bool TryGetDefaultParameterValue(Type type, out object value) internal class DefaultValueExpressionWrapper { internal ExpressionAst Expression { get; set; } + private Func _delegate; private IScriptExtent[] _sequencePoints; - private Type _localsTupleType; + private Type LocalsTupleType; internal object GetValue(ExecutionContext context, SessionStateInternal sessionStateInternal, IDictionary usingValues = null) { lock (this) { // Code written as part of a default value in a parameter is considered trusted - return Compiler.GetExpressionValue(this.Expression, true, context, sessionStateInternal, usingValues, ref _delegate, - ref _sequencePoints, ref _localsTupleType); + return Compiler.GetExpressionValue( + this.Expression, + true, + context, + sessionStateInternal, + usingValues, + ref _delegate, + ref _sequencePoints, + ref LocalsTupleType); } } } @@ -1573,8 +1971,10 @@ internal object GetValue(ExecutionContext context, SessionStateInternal sessionS internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize) { var body = scriptBlock.Ast; - Diagnostics.Assert(body is ScriptBlockAst || body is FunctionDefinitionAst || body is FunctionMemberAst || body is CompilerGeneratedMemberFunctionAst, - "Caller to verify ast is correct type."); + Diagnostics.Assert( + body is ScriptBlockAst || body is FunctionDefinitionAst || body is FunctionMemberAst || body is CompilerGeneratedMemberFunctionAst, + "Caller to verify ast is correct type."); + var ast = (Ast)body; Optimize = optimize; _compilingScriptCmdlet = scriptBlock.UsesCmdletBinding; @@ -1593,6 +1993,7 @@ internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize) { _switchTupleIndex = VariableAnalysis.ForceDynamic; } + if (!nameToIndexMap.TryGetValue(SpecialVariables.@foreach, out _foreachTupleIndex)) { _foreachTupleIndex = VariableAnalysis.ForceDynamic; @@ -1640,7 +2041,7 @@ internal void Compile(CompiledScriptBlockData scriptBlock, bool optimize) // on this sequence point, but it makes it safe to access the CurrentPosition // property in FunctionContext (which can happen if there are exceptions // defining the functions.) - _sequencePoints.Add(ast.Extent); + AddSequencePoint(ast.Extent); } var compileInterpretChoice = (_stmtCount > 300) ? CompileInterpretChoice.NeverCompile : CompileInterpretChoice.CompileOnDemand; @@ -1651,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; @@ -1661,22 +2063,22 @@ 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) + { return null; + } if (compileInterpretChoice == CompileInterpretChoice.AlwaysCompile) { @@ -1704,14 +2106,15 @@ internal static object GetExpressionValue(ExpressionAst expressionAst, bool isTr return GetExpressionValue(expressionAst, isTrustedInput, context, sessionStateInternal, usingValues, ref lambda, ref sequencePoints, ref localsTupleType); } - private static object GetExpressionValue(ExpressionAst expressionAst, - bool isTrustedInput, - ExecutionContext context, - SessionStateInternal sessionStateInternal, - IDictionary usingValues, - ref Func lambda, - ref IScriptExtent[] sequencePoints, - ref Type localsTupleType) + private static object GetExpressionValue( + ExpressionAst expressionAst, + bool isTrustedInput, + ExecutionContext context, + SessionStateInternal sessionStateInternal, + IDictionary usingValues, + ref Func lambda, + ref IScriptExtent[] sequencePoints, + ref Type localsTupleType) { object constantValue; if (IsConstantValueVisitor.IsConstant(expressionAst, out constantValue)) @@ -1736,10 +2139,7 @@ private static object GetExpressionValue(ExpressionAst expressionAst, // 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 @@ -1768,11 +2168,13 @@ private static object GetExpressionValue(ExpressionAst expressionAst, var boundParameters = new PSBoundParametersDictionary { ImplicitUsingParameters = usingValues }; functionContext._localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters, boundParameters, context); } + var result = lambda(functionContext); if (result == AutomationNull.Value) { return resultList.Count == 0 ? null : PipelineOps.PipelineResult(resultList); } + return result; } catch (TargetInvocationException tie) @@ -1808,19 +2210,19 @@ private Func CompileSingleExpression(ExpressionAst expr _loopTargets.Clear(); var exprs = new List(); - var temps = new List { _executionContextParameter, LocalVariablesParameter }; + var temps = new List { s_executionContextParameter, LocalVariablesParameter }; GenerateFunctionProlog(exprs, temps, null); - _sequencePoints.Add(expressionAst.Extent); - exprs.Add(new UpdatePositionExpr(expressionAst.Extent, _sequencePoints.Count - 1, _debugSymbolDocument, checkBreakpoints: true)); + int index = AddSequencePoint(expressionAst.Extent); + exprs.Add(new UpdatePositionExpr(expressionAst.Extent, index, _debugSymbolDocument, checkBreakpoints: true)); var result = Compile(expressionAst).Cast(typeof(object)); exprs.Add(Expression.Label(_returnTarget, result)); - var body = Expression.Block(new[] { _executionContextParameter, LocalVariablesParameter }, exprs); - var parameters = new[] { _functionContext }; + var body = Expression.Block(new[] { s_executionContextParameter, LocalVariablesParameter }, exprs); + var parameters = new[] { s_functionContext }; sequencePoints = _sequencePoints.ToArray(); return Expression.Lambda>(body, parameters).Compile(); } - private class LoopGotoTargets + private sealed class LoopGotoTargets { internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget continueLabel) { @@ -1828,9 +2230,12 @@ internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget conti this.BreakLabel = breakLabel; this.ContinueLabel = continueLabel; } - internal string Label { get; private set; } - internal LabelTarget ContinueLabel { get; private set; } - internal LabelTarget BreakLabel { get; private set; } + + internal string Label { get; } + + internal LabelTarget ContinueLabel { get; } + + internal LabelTarget BreakLabel { get; } } private LabelTarget _returnTarget; @@ -1838,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; @@ -1871,7 +2277,7 @@ private Expression CaptureAstResults( // FlushPipe(oldPipe, resultList); // funcContext.OutputPipe = oldPipe; // } - + // var temps = new List(); var exprs = new List(); var catches = new List(); @@ -1885,11 +2291,10 @@ 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)); switch (context) @@ -1902,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; @@ -1923,7 +2328,7 @@ private Expression CaptureAstResults( result = resultList; break; default: - throw new ArgumentOutOfRangeException("context"); + throw new ArgumentOutOfRangeException(nameof(context)); } finallyExprs.Add(Expression.Assign(s_getCurrentPipe, oldPipe)); @@ -1933,15 +2338,17 @@ private Expression CaptureAstResults( { return Expression.Block( temps.ToArray(), - Expression.TryCatchFinally(Expression.Block(exprs), - Expression.Block(finallyExprs), - catches.ToArray())); + Expression.TryCatchFinally( + Expression.Block(exprs), + Expression.Block(finallyExprs), + catches.ToArray())); } return Expression.Block( temps.ToArray(), - Expression.TryFinally(Expression.Block(exprs), - Expression.Block(finallyExprs))); + Expression.TryFinally( + Expression.Block(exprs), + Expression.Block(finallyExprs))); } private Expression CaptureStatementResultsHelper( @@ -1956,6 +2363,7 @@ private Expression CaptureStatementResultsHelper( { return GetRedirectedExpression(commandExpressionAst, captureForInput: true); } + return Compile(commandExpressionAst.Expression); } @@ -1967,15 +2375,19 @@ private Expression CaptureStatementResultsHelper( { expr = Expression.Block(expr, ExpressionCache.Empty); } + return expr; } var pipelineAst = stmt as PipelineAst; // If it's a pipeline that isn't being backgrounded, try to optimize expression - if (pipelineAst != null && ! pipelineAst.Background) + if (pipelineAst != null && !pipelineAst.Background) { var expr = pipelineAst.GetPureExpression(); - if (expr != null) { return Compile(expr); } + if (expr != null) + { + return Compile(expr); + } } return CaptureAstResults(stmt, context, generateRedirectExprs); @@ -1996,14 +2408,17 @@ private Expression CaptureStatementResults( // does have a measurable impact, so only set $? = $true if the condition might change $? to $false. // 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(new[] { tmp }, + result = Expression.Block( + new[] { tmp }, Expression.Assign(tmp, result), s_setDollarQuestionToTrue, tmp); } + return result; } @@ -2014,7 +2429,7 @@ internal Expression CallAddPipe(Expression expr, Expression pipe) return Expression.Call(pipe, CachedReflectionInfo.Pipe_Add, expr.Cast(typeof(object))); } - return DynamicExpression.Dynamic(PSPipeWriterBinder.Get(), typeof(void), expr, pipe, _executionContextParameter); + return DynamicExpression.Dynamic(PSPipeWriterBinder.Get(), typeof(void), expr, pipe, s_executionContextParameter); } public object VisitErrorStatement(ErrorStatementAst errorStatementAst) @@ -2034,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; @@ -2045,12 +2460,12 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) } // Skip param block - nothing to generate, defaults get generated when generating parameter metadata. - if (scriptBlockAst.BeginBlock != null) { _beginBlockLambda = CompileNamedBlock(scriptBlockAst.BeginBlock, funcName + "", rootForDefiningTypesAndUsings); rootForDefiningTypesAndUsings = null; } + if (scriptBlockAst.ProcessBlock != null) { var processFuncName = funcName; @@ -2058,16 +2473,26 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { processFuncName = funcName + ""; } + _processBlockLambda = CompileNamedBlock(scriptBlockAst.ProcessBlock, processFuncName, rootForDefiningTypesAndUsings); rootForDefiningTypesAndUsings = null; } + if (scriptBlockAst.EndBlock != null) { 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; @@ -2100,7 +2525,7 @@ private Expression> CompileNamedBlock(NamedBlockAst name private Tuple, Type> CompileTrap(TrapStatementAst trap) { - var compiler = new Compiler(_sequencePoints) { _compilingTrap = true }; + var compiler = new Compiler(_sequencePoints, _sequencePointIndexMap) { _compilingTrap = true }; string funcName = _currentFunctionName + ""; if (trap.TrapType != null) { @@ -2121,12 +2546,13 @@ private Tuple, Type> CompileTrap(TrapStatementAst trap) return Tuple.Create(lambda.Compile(), compiler.LocalVariablesTupleType); } - private Expression> CompileSingleLambda(ReadOnlyCollection statements, - ReadOnlyCollection traps, - string funcName, - IScriptExtent entryExtent, - IScriptExtent exitExtent, - ScriptBlockAst rootForDefiningTypesAndUsings) + private Expression> CompileSingleLambda( + ReadOnlyCollection statements, + ReadOnlyCollection traps, + string funcName, + IScriptExtent entryExtent, + IScriptExtent exitExtent, + ScriptBlockAst rootForDefiningTypesAndUsings) { _currentFunctionName = funcName; @@ -2147,7 +2573,7 @@ private Expression> CompileSingleLambda(ReadOnlyCollecti if (CompilingMemberFunction) { - temps.Add(_returnPipe); + temps.Add(s_returnPipe); } CompileStatementListWithTraps(statements, traps, actualBodyExprs, temps); @@ -2170,14 +2596,14 @@ private Expression> CompileSingleLambda(ReadOnlyCollecti // 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[] { _executionContextParameter }, + new[] { s_executionContextParameter }, Expression.TryCatchFinally( body, Expression.Call( - Expression.Field(_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), + Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), CachedReflectionInfo.Debugger_ExitScriptFunction), Expression.Catch(typeof(ReturnException), ExpressionCache.Empty))); } @@ -2185,18 +2611,18 @@ private Expression> CompileSingleLambda(ReadOnlyCollecti { // Either no traps, or we're compiling a trap - either way don't catch the ReturnException. body = Expression.Block( - new[] { _executionContextParameter }, + new[] { s_executionContextParameter }, Expression.TryFinally( body, Expression.Call( - Expression.Field(_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), + Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), CachedReflectionInfo.Debugger_ExitScriptFunction))); } - return Expression.Lambda>(body, funcName, new[] { _functionContext }); + 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. @@ -2207,42 +2633,47 @@ private void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings // If Parent of rootForDefiningTypesAndUsings is not null, then we already defined all types, when Visit a parent ScriptBlock if (rootForDefiningTypesAndUsings.Parent == null) { - if (rootForDefiningTypesAndUsings.UsingStatements.Any()) + 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(); if (typeAsts.Length > 0) { var assembly = DefinePowerShellTypes(rootForDefiningTypesAndUsings, typeAsts); - exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_SetAssemblyDefiningPSTypes, - _functionContext, Expression.Constant(assembly))); + exprs.Add( + Expression.Call( + CachedReflectionInfo.TypeOps_SetAssemblyDefiningPSTypes, + s_functionContext, + Expression.Constant(assembly))); - exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_InitPowerShellTypesAtRuntime, - Expression.Constant(typeAsts))); + exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_InitPowerShellTypesAtRuntime, Expression.Constant(typeAsts))); } } 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(Expression.Call(CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope, - Expression.Constant(typesToAddToScope), _executionContextParameter)); + exprs.Add( + Expression.Call( + CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope, + Expression.Constant(typesToAddToScope), + s_executionContextParameter)); } } /// /// - /// + /// Using statement asts. /// This flag allow us some optimizations, if usings don't have assemblies and modules. /// internal static void GenerateLoadUsings(IEnumerable usingStatements, bool allUsingsAreNamespaces, List exprs) @@ -2261,12 +2692,15 @@ internal static void GenerateLoadUsings(IEnumerable usingStat TypeOps.GetNamespacesForTypeResolutionState(usingStatements), assemblies); } - exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_SetCurrentTypeResolutionState, - Expression.Constant(trs), _executionContextParameter)); + + exprs.Add(Expression.Call( + CachedReflectionInfo.TypeOps_SetCurrentTypeResolutionState, + Expression.Constant(trs), s_executionContextParameter)); if (typesToAdd != null && typesToAdd.Count > 0) { - exprs.Add(Expression.Call(CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope, - Expression.Constant(typesToAdd), _executionContextParameter)); + exprs.Add(Expression.Call( + CachedReflectionInfo.TypeOps_AddPowerShellTypesToTheScope, + Expression.Constant(typesToAdd), s_executionContextParameter)); } } @@ -2275,14 +2709,13 @@ internal static void GenerateLoadUsings(IEnumerable usingStat /// This method should be called only for rootAsts (i.e. script file root ScriptBlockAst). /// /// - /// non-empty array of TypeDefinitionAst - /// Assembly with defined types + /// Non-empty array of TypeDefinitionAst. + /// Assembly with defined types. internal static Assembly DefinePowerShellTypes(Ast rootForDefiningTypes, TypeDefinitionAst[] typeAsts) { // TODO(sevoroby): this Diagnostic is conceptually right. // BUT It triggers, when we define type in an InitialSessionState and use it later in two different PowerShell instances. // Diagnostics.Assert(typeAsts[0].Type == null, "We must not call DefinePowerShellTypes twice for the same TypeDefinitionAsts"); - if (typeAsts[0].Type != null) { // We treat Type as a mutable buffer field and wipe it here to start definitions from scratch. @@ -2343,6 +2776,7 @@ private static Dictionary LoadUsingsImpl(IEnumerable< types[SymbolResolver.GetModuleQualifiedName(module.Name, nameTypePair.Key)] = nameTypePair.Value; } } + break; case UsingStatementKind.Namespace: break; @@ -2368,9 +2802,13 @@ private static void PopulateRuntimeTypes(ReadOnlyDictionary /// Take module info of module that can be already loaded or not and loads it. /// - /// - /// module info of the same module, but loaded + /// Module to load. + /// Module info of the same module, but loaded. private static PSModuleInfo LoadModule(PSModuleInfo originalModuleInfo) { // originalModuleInfo is created during parse time and may not contain [System.Type] types exported from the module. @@ -2442,8 +2878,12 @@ private static PSModuleInfo LoadModule(PSModuleInfo originalModuleInfo) if (ps.HadErrors && ps.Streams.Error.Count > 0) { var errorRecord = ps.Streams.Error[0]; - throw InterpreterError.NewInterpreterException(modulePath, typeof(RuntimeException), null, - errorRecord.FullyQualifiedErrorId, errorRecord.ToString()); + throw InterpreterError.NewInterpreterException( + modulePath, + typeof(RuntimeException), + null, + errorRecord.FullyQualifiedErrorId, + errorRecord.ToString()); } if (moduleInfo.Count == 1) @@ -2459,30 +2899,32 @@ private static PSModuleInfo LoadModule(PSModuleInfo originalModuleInfo) private void GenerateFunctionProlog(List exprs, List temps, IScriptExtent entryExtent) { - exprs.Add(Expression.Assign(_executionContextParameter, - Expression.Field(_functionContext, CachedReflectionInfo.FunctionContext__executionContext))); - exprs.Add(Expression.Assign(LocalVariablesParameter, - Expression.Field(_functionContext, CachedReflectionInfo.FunctionContext__localsTuple).Cast(this.LocalVariablesTupleType))); + exprs.Add(Expression.Assign( + s_executionContextParameter, + Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__executionContext))); + exprs.Add(Expression.Assign( + LocalVariablesParameter, + Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__localsTuple).Cast(this.LocalVariablesTupleType))); // Compiling a single expression (a default argument, or an locally evaluated argument in a ScriptBlock=>PowerShell conversion) // does not support debugging, so skip calling EnterScriptFunction. if (!_compilingSingleExpression) { exprs.Add(Expression.Assign( - Expression.Field(_functionContext, CachedReflectionInfo.FunctionContext__functionName), + Expression.Field(s_functionContext, CachedReflectionInfo.FunctionContext__functionName), Expression.Constant(_currentFunctionName))); if (entryExtent != null) { - _sequencePoints.Add(entryExtent); - exprs.Add(new UpdatePositionExpr(entryExtent, _sequencePoints.Count - 1, _debugSymbolDocument, checkBreakpoints: false)); + int index = AddSequencePoint(entryExtent); + exprs.Add(new UpdatePositionExpr(entryExtent, index, _debugSymbolDocument, checkBreakpoints: false)); } exprs.Add( Expression.Call( - Expression.Field(_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), + Expression.Field(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), CachedReflectionInfo.Debugger_EnterScriptFunction, - _functionContext)); + s_functionContext)); } if (CompilingMemberFunction) @@ -2490,14 +2932,15 @@ private void GenerateFunctionProlog(List exprs, List exprs, IScriptExtent exitExtent) @@ -2591,14 +3034,17 @@ public object VisitStatementBlock(StatementBlockAst statementBlockAst) { exprs.Add(ExpressionCache.Empty); } + return Expression.Block(typeof(void), temps, exprs); } private int _trapNestingCount; - private void CompileStatementListWithTraps(ReadOnlyCollection statements, - ReadOnlyCollection traps, - List exprs, - List temps) + + private void CompileStatementListWithTraps( + ReadOnlyCollection statements, + ReadOnlyCollection traps, + List exprs, + List temps) { if (statements.Count == 0) { @@ -2634,11 +3080,11 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat // We use a runtime check on popping because in some rare cases, PushTrapHandlers might not // get called (e.g. if a trap handler specifies a type that doesn't exist, like trap [baddtype]{}). // We don't want to pop if we didn't push. - exprs = new List(); - handlerInScope = Expression.Property(_executionContextParameter, - CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock); + handlerInScope = Expression.Property( + s_executionContextParameter, + CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock); oldActiveHandler = NewTemp(typeof(bool), "oldActiveHandler"); exprs.Add(Expression.Assign(oldActiveHandler, handlerInScope)); @@ -2658,10 +3104,11 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat trapTupleType.Add(tuple.Item2); } - exprs.Add(Expression.Call(_functionContext, CachedReflectionInfo.FunctionContext_PushTrapHandlers, - Expression.NewArrayInit(typeof(Type), trapTypes), - Expression.Constant(trapDelegates.ToArray()), - Expression.Constant(trapTupleType.ToArray()))); + exprs.Add(Expression.Call( + s_functionContext, CachedReflectionInfo.FunctionContext_PushTrapHandlers, + Expression.NewArrayInit(typeof(Type), trapTypes), + Expression.Constant(trapDelegates.ToArray()), + Expression.Constant(trapTupleType.ToArray()))); trapHandlersPushed = NewTemp(typeof(bool), "trapHandlersPushed"); exprs.Add(Expression.Assign(trapHandlersPushed, ExpressionCache.Constant(true))); _trapNestingCount += 1; @@ -2687,11 +3134,14 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat // We push null onto the active trap handlers to let ExceptionHandlingOps.CheckActionPreference know it // shouldn't process traps (but should still query the user if appropriate), and just rethrow so we can // unwind to the block with the trap. - exprs = new List(); - exprs.Add(Expression.Call(_functionContext, CachedReflectionInfo.FunctionContext_PushTrapHandlers, - ExpressionCache.NullTypeArray, ExpressionCache.NullDelegateArray, ExpressionCache.NullTypeArray)); + exprs.Add(Expression.Call( + s_functionContext, + CachedReflectionInfo.FunctionContext_PushTrapHandlers, + ExpressionCache.NullTypeArray, + ExpressionCache.NullDelegateArray, + ExpressionCache.NullTypeArray)); trapHandlersPushed = NewTemp(typeof(bool), "trapHandlersPushed"); exprs.Add(Expression.Assign(trapHandlersPushed, ExpressionCache.Constant(true))); } @@ -2733,13 +3183,12 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat // // This approach makes JIT possible (but still slow) for large functions and it speeds up the light // compile (interpreter compile) by about 80%. - if (statements.Count == 1) { var exprList = new List(3); CompileTrappableExpression(exprList, statements[0]); exprList.Add(ExpressionCache.Empty); - var expr = Expression.TryCatch(Expression.Block(exprList), _stmtCatchHandlers); + var expr = Expression.TryCatch(Expression.Block(exprList), s_stmtCatchHandlers); exprs.Add(expr); } else @@ -2749,8 +3198,9 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat for (int i = 0; i <= statements.Count; i++) { dispatchTargets[i] = Expression.Label(); - switchCases[i] = Expression.SwitchCase(Expression.Goto(dispatchTargets[i]), - ExpressionCache.Constant(i)); + switchCases[i] = Expression.SwitchCase( + Expression.Goto(dispatchTargets[i]), + ExpressionCache.Constant(i)); } var dispatchIndex = Expression.Variable(typeof(int), "stmt"); @@ -2775,14 +3225,17 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat var exception = Expression.Variable(typeof(Exception), "exception"); var callCheckActionPreference = Expression.Call( CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference, - Compiler._functionContext, exception); + Compiler.s_functionContext, + exception); var catchAll = Expression.Catch( exception, - Expression.Block(callCheckActionPreference, - Expression.Goto(dispatchNextStatementTarget))); + Expression.Block( + callCheckActionPreference, + Expression.Goto(dispatchNextStatementTarget))); - var expr = Expression.TryCatch(Expression.Block(tryBodyExprs), - new CatchBlock[] { s_catchFlowControl, catchAll }); + var expr = Expression.TryCatch( + Expression.Block(tryBodyExprs), + new CatchBlock[] { s_catchFlowControl, catchAll }); exprs.Add(expr); exprs.Add(Expression.Label(dispatchTargets[statements.Count])); } @@ -2799,7 +3252,7 @@ private void CompileStatementListWithTraps(ReadOnlyCollection stat parameterExprs.Add(trapHandlersPushed); finallyBlockExprs.Add( - Expression.IfThen(trapHandlersPushed, Expression.Call(_functionContext, CachedReflectionInfo.FunctionContext_PopTrapHandlers))); + Expression.IfThen(trapHandlersPushed, Expression.Call(s_functionContext, CachedReflectionInfo.FunctionContext_PopTrapHandlers))); originalExprs.Add( Expression.Block(parameterExprs, Expression.TryFinally(Expression.Block(exprs), Expression.Block(finallyBlockExprs)))); @@ -2816,37 +3269,149 @@ private void CompileTrappableExpression(List exprList, StatementAst var expr = Compile(stmt); exprList.Add(expr); - var pipeAst = stmt as PipelineAst; - if (pipeAst != null) + if (ShouldSetExecutionStatusToSuccess(stmt)) { - if (pipeAst.PipelineElements.Count == 1 && pipeAst.PipelineElements[0] is CommandExpressionAst) - { - // A single expression - must set $? after the expression. - exprList.Add(s_setDollarQuestionToTrue); - } + exprList.Add(s_setDollarQuestionToTrue); } - else + } + + /// + /// Determines whether a statement must have an explicit setting + /// for $? = $true after it by the compiler. + /// + /// The statement to examine. + /// True is the compiler should add the success setting, false otherwise. + private bool ShouldSetExecutionStatusToSuccess(StatementAst statementAst) + { + // Simple overload fan out + switch (statementAst) { - var assignmentStatementAst = stmt as AssignmentStatementAst; - if (assignmentStatementAst != null) - { - Ast right = null; - var assignAst = assignmentStatementAst; - while (assignAst != null) + case PipelineAst pipelineAst: + return ShouldSetExecutionStatusToSuccess(pipelineAst); + case AssignmentStatementAst assignmentStatementAst: + return ShouldSetExecutionStatusToSuccess(assignmentStatementAst); + default: + return false; + } + } + + /// + /// Determines whether a pipeline must have an explicit setting + /// for $? = $true after it by the compiler. + /// + /// The pipeline to examine. + /// True is the compiler should add the success setting, false otherwise. + private bool ShouldSetExecutionStatusToSuccess(PipelineAst pipelineAst) + { + ExpressionAst expressionAst = GetSingleExpressionFromPipeline(pipelineAst); + + // If the pipeline is not a single expression, it will set $? + if (expressionAst == null) + { + return false; + } + + // Expressions may still set $? themselves, so dig deeper + 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. + /// + /// The assignment statement to examine. + /// True is the compiler should add the success setting, false otherwise. + private bool ShouldSetExecutionStatusToSuccess(AssignmentStatementAst assignmentStatementAst) + { + // Get right-most RHS in cases like $x = $y = + StatementAst innerRhsStatementAst = assignmentStatementAst.Right; + while (innerRhsStatementAst is AssignmentStatementAst rhsAssignmentAst) + { + innerRhsStatementAst = rhsAssignmentAst.Right; + } + + // Simple assignments to pure expressions may need $? set, so examine the RHS statement for pure expressions + switch (innerRhsStatementAst) + { + case CommandExpressionAst commandExpression: + return ShouldSetExecutionStatusToSuccess(commandExpression.Expression); + + case PipelineAst rhsPipelineAst: + return ShouldSetExecutionStatusToSuccess(rhsPipelineAst); + + default: + return false; + } + } + + /// + /// Determines whether an expression in a statement must have an explicit setting + /// for $? = $true after it by the compiler. + /// + /// The expression to examine. + /// True is the compiler should add the success setting, false otherwise. + private bool ShouldSetExecutionStatusToSuccess(ExpressionAst expressionAst) + { + switch (expressionAst) + { + case ParenExpressionAst parenExpression: + // Pipelines in paren expressions that are just pure expressions will need $? set + // e.g. ("Hi"), vs (Test-Path ./here.txt) + return ShouldSetExecutionStatusToSuccess(parenExpression.Pipeline); + + case SubExpressionAst subExpressionAst: + // Subexpressions generally set $? since they encapsulate a statement block + // But $() requires an explicit setting + return subExpressionAst.SubExpression.Statements.Count == 0; + + case ArrayExpressionAst arrayExpressionAst: + // ArrayExpressionAsts and SubExpressionAsts must be treated differently, + // since they are optimised for a single expression differently. + // A SubExpressionAst with a single expression in it has the $? = $true added, + // but the optimisation drills deeper for ArrayExpressionAsts, + // meaning we must inspect the expression itself in these cases + + switch (arrayExpressionAst.SubExpression.Statements.Count) { - right = assignAst.Right; - assignAst = right as AssignmentStatementAst; - } + case 0: + // @() needs $? set + return true; + + case 1: + // Single expressions with a trap are handled as statements + // For example: @(trap { continue } "Value") + if (arrayExpressionAst.SubExpression.Traps != null) + { + return false; + } + + // Pure, single statement expressions need $? set + // For example @("One") and @("One", "Two") + return ShouldSetExecutionStatusToSuccess(arrayExpressionAst.SubExpression.Statements[0]); - pipeAst = right as PipelineAst; - if (right is CommandExpressionAst || - (pipeAst != null && pipeAst.PipelineElements.Count == 1 && - pipeAst.PipelineElements[0] is CommandExpressionAst)) - { - // If the RHS of the assign was an expression, - exprList.Add(s_setDollarQuestionToTrue); + default: + // Arrays with multiple statements in them will have $? set + return false; } - } + + default: + return true; } } @@ -2899,22 +3464,23 @@ public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKey public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - return Expression.Call(CachedReflectionInfo.FunctionOps_DefineFunction, - _executionContextParameter, - Expression.Constant(functionDefinitionAst), - Expression.Constant(new ScriptBlockExpressionWrapper(functionDefinitionAst))); + return Expression.Call( + CachedReflectionInfo.FunctionOps_DefineFunction, + s_executionContextParameter, + Expression.Constant(functionDefinitionAst), + Expression.Constant(new ScriptBlockExpressionWrapper(functionDefinitionAst))); } public object VisitIfStatement(IfStatementAst ifStmtAst) { int clauseCount = ifStmtAst.Clauses.Count; - var exprs = new Tuple[clauseCount]; + var exprs = new Tuple[clauseCount]; for (int i = 0; i < clauseCount; ++i) { IfClause ifClause = ifStmtAst.Clauses[i]; - var cond = Expression.Block( - UpdatePosition(ifClause.Item1), + var cond = UpdatePositionForInitializerOrCondition( + ifClause.Item1, CaptureStatementResults(ifClause.Item1, CaptureAstContext.Condition).Convert(typeof(bool))); var body = Compile(ifClause.Item2); exprs[i] = Tuple.Create(cond, body); @@ -2970,8 +3536,10 @@ private Expression CompileAssignment( // to generate the assignments, no sense in converting to object[], or worse, returning a // single object. // We should not preserve the partial output if exception is thrown when evaluating right-hand-side expression. - Expression rightExpr = CaptureStatementResults(assignmentStatementAst.Right, - arrayLHS != null ? CaptureAstContext.Enumerable : CaptureAstContext.AssignmentWithoutResultPreservation, generateRedirectExprs); + Expression rightExpr = CaptureStatementResults( + assignmentStatementAst.Right, + arrayLHS != null ? CaptureAstContext.Enumerable : CaptureAstContext.AssignmentWithoutResultPreservation, + generateRedirectExprs); if (arrayLHS != null) { @@ -2979,23 +3547,195 @@ private Expression CompileAssignment( } var exprs = new List - { + { // Set current position in case of errors. UpdatePosition(assignmentStatementAst), - ReduceAssignment((ISupportsAssignment)assignmentStatementAst.Left, - assignmentStatementAst.Operator, - rightExpr) + ReduceAssignment( + (ISupportsAssignment)assignmentStatementAst.Left, + assignmentStatementAst.Operator, + rightExpr) }; return Expression.Block(exprs); } + public object VisitPipelineChain(PipelineChainAst pipelineChainAst) + { + // If the statement chain is backgrounded, + // we defer that to the background operation call + if (pipelineChainAst.Background) + { + return Expression.Call( + CachedReflectionInfo.PipelineOps_InvokePipelineInBackground, + Expression.Constant(pipelineChainAst), + s_functionContext); + } + + // We want to generate code like: + // + // dispatchIndex = 0; + // DispatchNextStatementTarget: + // try { + // switch (dispatchIndex) { + // case 0: goto L0; + // case 1: goto L1; + // case 2: goto L2; + // } + // L0: dispatchIndex = 1; + // pipeline1; + // L1: dispatchIndex = 2; + // if ($?) pipeline2; + // L2: dispatchIndex = 3; + // if ($?) pipeline3; + // ... + // } catch (FlowControlException) { + // throw; + // } catch (Exception e) { + // ExceptionHandlingOps.CheckActionPreference(functionContext, e); + // goto DispatchNextStatementTarget; + // } + // LN: + // + // Note that we deliberately do not push trap handlers + // so that those can be handled by the enclosing statement block instead. + + var exprs = new List(); + + // A pipeline chain is left-hand-side deep, + // so to compile from left to right, we need to start from the leaf + // and roll back up to the top, being the right-most element in the chain + PipelineChainAst currentChain = pipelineChainAst; + while (currentChain.LhsPipelineChain is PipelineChainAst lhsPipelineChain) + { + currentChain = lhsPipelineChain; + } + + // int chainIndex = 0; + ParameterExpression dispatchIndex = Expression.Variable(typeof(int), nameof(dispatchIndex)); + var temps = new ParameterExpression[] { dispatchIndex }; + exprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(0))); + + // DispatchNextTargetStatement: + LabelTarget dispatchNextStatementTargetLabel = Expression.Label(); + exprs.Add(Expression.Label(dispatchNextStatementTargetLabel)); + + // try statement body + var switchCases = new List(); + var dispatchTargets = new List(); + var tryBodyExprs = new List() + { + null, // Add a slot for the initial switch/case that we'll come back to + }; + + // L0: dispatchIndex = 1; pipeline1 + LabelTarget label0 = Expression.Label(); + dispatchTargets.Add(label0); + switchCases.Add( + Expression.SwitchCase( + Expression.Goto(label0), + ExpressionCache.Constant(0))); + tryBodyExprs.Add(Expression.Label(label0)); + tryBodyExprs.Add(Expression.Assign(dispatchIndex, ExpressionCache.Constant(1))); + tryBodyExprs.Add(CompilePipelineChainElement((PipelineAst)currentChain.LhsPipelineChain)); + + // Remainder of try statement body + // L1: dispatchIndex = 2; if ($?) pipeline2; + // ... + int chainIndex = 1; + do + { + // Record label and switch case for later use + LabelTarget currentLabel = Expression.Label(); + dispatchTargets.Add(currentLabel); + switchCases.Add( + Expression.SwitchCase( + Expression.Goto(currentLabel), + ExpressionCache.Constant(chainIndex))); + + // Add label and dispatchIndex for current pipeline + tryBodyExprs.Add(Expression.Label(currentLabel)); + tryBodyExprs.Add( + Expression.Assign( + dispatchIndex, + ExpressionCache.Constant(chainIndex + 1))); + + // Increment chain index for next iteration + chainIndex++; + + Diagnostics.Assert( + currentChain.Operator == TokenKind.AndAnd || currentChain.Operator == TokenKind.OrOr, + "Chain operators must be either && or ||"); + + Expression dollarQuestionCheck = currentChain.Operator == TokenKind.AndAnd + ? s_getDollarQuestion + : s_notDollarQuestion; + + tryBodyExprs.Add(Expression.IfThen(dollarQuestionCheck, CompilePipelineChainElement(currentChain.RhsPipeline))); + + currentChain = currentChain.Parent as PipelineChainAst; + } + while (currentChain != null); + + // Add empty expression to make the block value void + tryBodyExprs.Add(ExpressionCache.Empty); + + // Create the final label that follows the entire try/catch + LabelTarget afterLabel = Expression.Label(); + switchCases.Add( + Expression.SwitchCase( + Expression.Goto(afterLabel), + ExpressionCache.Constant(chainIndex))); + + // Now set the switch/case that belongs at the top + tryBodyExprs[0] = Expression.Switch(dispatchIndex, switchCases.ToArray()); + + // Create the catch block for flow control and action preference + ParameterExpression exception = Expression.Variable(typeof(Exception), nameof(exception)); + MethodCallExpression callCheckActionPreference = Expression.Call( + CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference, + Compiler.s_functionContext, + exception); + CatchBlock catchAll = Expression.Catch( + exception, + Expression.Block( + callCheckActionPreference, + Expression.Goto(dispatchNextStatementTargetLabel))); + + TryExpression expr = Expression.TryCatch( + Expression.Block(tryBodyExprs), + new CatchBlock[] { s_catchFlowControl, catchAll }); + + exprs.Add(expr); + exprs.Add(Expression.Label(afterLabel)); + + BlockExpression fullyExpandedBlock = Expression.Block(typeof(void), temps, exprs); + + return fullyExpandedBlock; + } + + /// + /// Compile a pipeline as an element in a pipeline chain. + /// Needed since pure expressions won't set $? after them. + /// which does something similar. + /// + /// The pipeline in the pipeline chain to compile to an expression. + /// The compiled expression to execute the pipeline. + private Expression CompilePipelineChainElement(PipelineAst pipelineAst) + { + if (ShouldSetExecutionStatusToSuccess(pipelineAst)) + { + return Expression.Block(Compile(pipelineAst), s_setDollarQuestionToTrue); + } + + return Compile(pipelineAst); + } + 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)); @@ -3006,13 +3746,13 @@ public object VisitPipeline(PipelineAst pipelineAst) Expression invokeBackgroundPipe = Expression.Call( CachedReflectionInfo.PipelineOps_InvokePipelineInBackground, Expression.Constant(pipelineAst), - _functionContext); + s_functionContext); exprs.Add(invokeBackgroundPipe); } else { var pipeElements = pipelineAst.PipelineElements; - var firstCommandExpr = (pipeElements[0] as CommandExpressionAst); + var firstCommandExpr = pipeElements[0] as CommandExpressionAst; if (firstCommandExpr != null && pipeElements.Count == 1) { @@ -3056,11 +3796,11 @@ public object VisitPipeline(PipelineAst pipelineAst) // here so that we can tell the difference b/w $null and no input when // starting the pipeline, in other words, PipelineOps.InvokePipe will // not pass this value to the pipe. - input = ExpressionCache.AutomationNullConstant; i = 0; commandsInPipe = pipeElements.Count; } + Expression[] pipelineExprs = new Expression[commandsInPipe]; CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe]; var commandRedirections = new object[commandsInPipe]; @@ -3078,19 +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[])))); + Expression.NewArrayInit( + 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 { @@ -3113,7 +3854,7 @@ public object VisitPipeline(PipelineAst pipelineAst) Expression.NewArrayInit(typeof(CommandParameterInternal[]), pipelineExprs), Expression.Constant(pipeElementAsts), redirectionExpr, - _functionContext); + s_functionContext); exprs.Add(invokePipe); } } @@ -3138,14 +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))); + return Expression.NewArrayInit( + typeof(CommandRedirection), + 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 @@ -3156,31 +3898,31 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo // Generate code like: // // try { - // oldPipe = funcContext._outputPipe; - // funcContext._outputPipe = outputFileRedirection.BindForExpression(executionContext); + // oldPipe = funcContext.OutputPipe; + // funcContext.OutputPipe = outputFileRedirection.BindForExpression(executionContext); // tmp = nonOutputFileRedirection.BindForExpression(executionContext); - // tmp1 = mergingRedirection.BindForExpression(executionContext, funcContext._outputPipe); - // funcContext._outputPipe.Add(expression...); + // tmp1 = mergingRedirection.BindForExpression(executionContext, funcContext.OutputPipe); + // funcContext.OutputPipe.Add(expression...); // } finally { // nonOutputFileRedirection.UnbindForExpression(tmp); // mergingRedirection.UnbindForExpression(tmp1); // nonOutputFileRedirection.Dispose(); // outputFileRedirection.Dispose(); - // funcContext._outputPipe = oldPipe; + // 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.) - + // funcContext.OutputPipe has the correct value when setting up merging.) + // var exprs = new List(); var temps = new List(); var finallyExprs = new List(); - // For the output stream, we change funcContext._outputPipe so all output goes to the file. + // 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; @@ -3199,18 +3941,16 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo } List extraFileRedirectExprs = null; + // We must generate the code for output redirection to a file before any merging redirections - // because merging redirections will use funcContext._outputPipe as the value to merge to, so defer merging + // because merging redirections will use funcContext.OutputPipe as the value to merge to, so defer merging // redirections until file redirections are done. foreach (var fileRedirectionAst in commandExpr.Redirections.OfType()) { // 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"); @@ -3222,7 +3962,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo temps.Add(savedPipes); exprs.Add(Expression.Assign( savedPipes, - Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_BindForExpression, _functionContext))); + Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_BindForExpression, s_functionContext))); // We need to call 'DoComplete' on the file redirection pipeline processor after writing the stream output to redirection pipe, // so that the 'EndProcessing' method of 'Out-File' would be called as expected. @@ -3249,13 +3989,14 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo // Otherwise, the original pipe might not be correctly restored in the end. For example, // '1 *> b.txt > a.txt; 123' would result in the following error when evaluating '123': // "Cannot perform operation because object "PipelineProcessor" has already been disposed" - finallyExprs.Insert(0, Expression.Call(redirectionExpr.Cast(typeof(CommandRedirection)), - CachedReflectionInfo.CommandRedirection_UnbindForExpression, - _functionContext, - savedPipes)); + finallyExprs.Insert(0, Expression.Call( + redirectionExpr.Cast(typeof(CommandRedirection)), + CachedReflectionInfo.CommandRedirection_UnbindForExpression, + s_functionContext, + savedPipes)); + // In either case, we must dispose of the redirection or file handles won't get released. - finallyExprs.Insert(1, Expression.Call(redirectionExpr, - CachedReflectionInfo.FileRedirection_Dispose)); + finallyExprs.Insert(1, Expression.Call(redirectionExpr, CachedReflectionInfo.FileRedirection_Dispose)); } Expression result = null; @@ -3315,6 +4056,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo else { exprs.Add(CallAddPipe(result, s_getCurrentPipe)); + // Make sure the result of the expression we return is AutomationNull.Value. exprs.Add(ExpressionCache.AutomationNullConstant); } @@ -3360,6 +4102,7 @@ private Expression GetRedirectedExpression(CommandExpressionAst commandExpr, boo { return Expression.Block(temps.ToArray(), Expression.TryFinally(Expression.Block(exprs), Expression.Block(finallyExprs))); } + return Expression.Block(temps.ToArray(), exprs); } @@ -3376,15 +4119,18 @@ private void AddMergeRedirectionExpressions( var redirectionExpr = Expression.Constant(VisitMergingRedirection(mergingRedirectionAst)); exprs.Add( Expression.Assign(savedPipes, - Expression.Call(redirectionExpr, - CachedReflectionInfo.MergingRedirection_BindForExpression, - _executionContextParameter, - _functionContext))); + Expression.Call( + redirectionExpr, + CachedReflectionInfo.MergingRedirection_BindForExpression, + s_executionContextParameter, + s_functionContext))); + // Undo merging redirections first (so file redirections get undone after). - finallyExprs.Insert(0, Expression.Call(redirectionExpr.Cast(typeof(CommandRedirection)), - CachedReflectionInfo.CommandRedirection_UnbindForExpression, - _functionContext, - savedPipes)); + finallyExprs.Insert(0, Expression.Call( + redirectionExpr.Cast(typeof(CommandRedirection)), + CachedReflectionInfo.CommandRedirection_UnbindForExpression, + s_functionContext, + savedPipes)); } } @@ -3394,7 +4140,6 @@ public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAs // may be able to pass a compile time array of redirections if all of the redirections are // constant. Merging redirections never vary at runtime, so there is no sense in deferring // the creation of the merging object until runtime. - return new MergingRedirection(mergingRedirectionAst.FromStream, mergingRedirectionAst.ToStream); } @@ -3412,14 +4157,18 @@ public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) else { // The filename is not constant, so we must generate code to evaluate the filename at runtime. - fileNameExpr = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), - CompileExpressionOperand(fileRedirectionAst.Location), _executionContextParameter); + fileNameExpr = DynamicExpression.Dynamic( + PSToStringBinder.Get(), + typeof(string), + CompileExpressionOperand(fileRedirectionAst.Location), + s_executionContextParameter); } - return Expression.New(CachedReflectionInfo.FileRedirection_ctor, - Expression.Constant(fileRedirectionAst.FromStream), - ExpressionCache.Constant(fileRedirectionAst.Append), - fileNameExpr); + return Expression.New( + CachedReflectionInfo.FileRedirection_ctor, + Expression.Constant(fileRedirectionAst.FromStream), + ExpressionCache.Constant(fileRedirectionAst.Append), + fileNameExpr); } public object VisitCommand(CommandAst commandAst) @@ -3451,11 +4200,11 @@ public object VisitCommand(CommandAst commandAst) splatted = variableExpression.Splatted; } - elementExprs[i] = - Expression.Call(CachedReflectionInfo.CommandParameterInternal_CreateArgument, - Expression.Convert(GetCommandArgumentExpression(element), typeof(object)), - Expression.Constant(element), - ExpressionCache.Constant(splatted)); + elementExprs[i] = Expression.Call( + CachedReflectionInfo.CommandParameterInternal_CreateArgument, + Expression.Convert(GetCommandArgumentExpression(element), typeof(object)), + Expression.Constant(element), + ExpressionCache.Constant(splatted)); } } @@ -3465,7 +4214,6 @@ public object VisitCommand(CommandAst commandAst) { // If they used method invocation syntax, we want a strict mode error. We can't // check that at compile time as it might change at runtime, so we'll add a runtime check. - var args = ((ParenExpressionAst)commandElements[1]).Pipeline.GetPureExpression(); if (args is ArrayLiteralAst) { @@ -3475,9 +4223,8 @@ public object VisitCommand(CommandAst commandAst) // no whitespace b/w command name and paren, add a strict mode check result = Expression.Block( Expression.IfThen( - Compiler.IsStrictMode(2, _executionContextParameter), - Compiler.ThrowRuntimeError("StrictModeFunctionCallWithParens", - ParserStrings.StrictModeFunctionCallWithParens)), + Compiler.IsStrictMode(2, s_executionContextParameter), + Compiler.ThrowRuntimeError("StrictModeFunctionCallWithParens", ParserStrings.StrictModeFunctionCallWithParens)), result); } } @@ -3489,7 +4236,9 @@ public object VisitCommand(CommandAst commandAst) private Expression GetCommandArgumentExpression(CommandElementAst element) { var constElement = element as ConstantExpressionAst; - if (constElement != null && LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constElement.StaticType))) + if (constElement != null + && (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constElement.StaticType)) + || constElement.StaticType == typeof(System.Numerics.BigInteger))) { var commandArgumentText = constElement.Extent.Text; if (!commandArgumentText.Equals(constElement.Value.ToString(), StringComparison.Ordinal)) @@ -3522,6 +4271,7 @@ public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return expr; } + return CallAddPipe(expr, s_getCurrentPipe); } @@ -3531,21 +4281,24 @@ public object VisitCommandParameter(CommandParameterAst commandParameterAst) var errorPos = commandParameterAst.ErrorPosition; if (arg != null) { - bool spaceAfterParameter = (errorPos.EndLineNumber != arg.Extent.StartLineNumber || - errorPos.EndColumnNumber != arg.Extent.StartColumnNumber); - return Expression.Call(CachedReflectionInfo.CommandParameterInternal_CreateParameterWithArgument, - Expression.Constant(commandParameterAst), - Expression.Constant(commandParameterAst.ParameterName), - Expression.Constant(errorPos.Text), - Expression.Constant(arg), - Expression.Convert(GetCommandArgumentExpression(arg), typeof(object)), - ExpressionCache.Constant(spaceAfterParameter)); + bool spaceAfterParameter = errorPos.EndLineNumber != arg.Extent.StartLineNumber || + errorPos.EndColumnNumber != arg.Extent.StartColumnNumber; + return Expression.Call( + CachedReflectionInfo.CommandParameterInternal_CreateParameterWithArgument, + Expression.Constant(commandParameterAst), + Expression.Constant(commandParameterAst.ParameterName), + Expression.Constant(errorPos.Text), + Expression.Constant(arg), + Expression.Convert(GetCommandArgumentExpression(arg), typeof(object)), + ExpressionCache.Constant(spaceAfterParameter), + ExpressionCache.Constant(false)); } - return Expression.Call(CachedReflectionInfo.CommandParameterInternal_CreateParameter, - Expression.Constant(commandParameterAst.ParameterName), - Expression.Constant(errorPos.Text), - Expression.Constant(commandParameterAst)); + return Expression.Call( + CachedReflectionInfo.CommandParameterInternal_CreateParameter, + Expression.Constant(commandParameterAst.ParameterName), + Expression.Constant(errorPos.Text), + Expression.Constant(commandParameterAst)); } internal static Expression ThrowRuntimeError(string errorID, string resourceString, params Expression[] exceptionArgs) @@ -3561,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[] { @@ -3578,19 +4331,21 @@ internal static Expression ThrowRuntimeError(Type exceptionType, string errorID, throwResultType); } - internal static Expression ThrowRuntimeErrorWithInnerException(string errorID, - string resourceString, - Expression innerException, - params Expression[] exceptionArgs) + internal static Expression ThrowRuntimeErrorWithInnerException( + string errorID, + string resourceString, + Expression innerException, + params Expression[] exceptionArgs) { return ThrowRuntimeErrorWithInnerException(errorID, Expression.Constant(resourceString), innerException, typeof(object), exceptionArgs); } - internal static Expression ThrowRuntimeErrorWithInnerException(string errorID, - Expression resourceString, - Expression innerException, - Type throwResultType, - params Expression[] exceptionArgs) + internal static Expression ThrowRuntimeErrorWithInnerException( + string errorID, + Expression resourceString, + Expression innerException, + Type throwResultType, + params Expression[] exceptionArgs) { var exceptionArgArray = exceptionArgs != null ? Expression.NewArrayInit(typeof(object), exceptionArgs) @@ -3623,19 +4378,19 @@ internal static Expression CreateThrow(Type resultType, Type exception, Type[] e argExprs[i] = e; } - const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - ConstructorInfo constructor = exception.GetConstructor(flags, null, CallingConventions.Any, exceptionArgTypes, null); + const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + ConstructorInfo constructor = exception.GetConstructor(Flags, null, CallingConventions.Any, exceptionArgTypes, null); if (constructor == null) { throw new PSArgumentException("Type doesn't have constructor with a given signature"); } - return (Expression.Throw(Expression.New(constructor, argExprs), resultType)); + return Expression.Throw(Expression.New(constructor, argExprs), resultType); } internal static Expression CreateThrow(Type resultType, Type exception, params object[] exceptionArgs) { - Type[] argTypes = PSTypeExtensions.EmptyTypes; + Type[] argTypes = Type.EmptyTypes; if (exceptionArgs != null) { argTypes = new Type[exceptionArgs.Length]; @@ -3690,7 +4445,6 @@ public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) // { // if (sr != null) sr.Dispose(); // } - var exprs = new List(); var path = NewTemp(typeof(string), "path"); @@ -3699,16 +4453,19 @@ public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) exprs.Add(UpdatePosition(switchStatementAst.Condition)); // We should not preserve the partial output if exception is thrown when evaluating the condition. - var cond = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), - CaptureStatementResults(switchStatementAst.Condition, CaptureAstContext.AssignmentWithoutResultPreservation), - _executionContextParameter); + var cond = DynamicExpression.Dynamic( + PSToStringBinder.Get(), + typeof(string), + CaptureStatementResults(switchStatementAst.Condition, CaptureAstContext.AssignmentWithoutResultPreservation), + s_executionContextParameter); exprs.Add( Expression.Assign( path, - Expression.Call(CachedReflectionInfo.SwitchOps_ResolveFilePath, - Expression.Constant(switchStatementAst.Condition.Extent), - cond, - _executionContextParameter))); + Expression.Call( + CachedReflectionInfo.SwitchOps_ResolveFilePath, + Expression.Constant(switchStatementAst.Condition.Extent), + cond, + s_executionContextParameter))); var tryBodyExprs = new List(); @@ -3718,18 +4475,18 @@ public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) temps.Add(line); tryBodyExprs.Add( - Expression.Assign(streamReader, - Expression.New(CachedReflectionInfo.StreamReader_ctor, path))); + Expression.Assign(streamReader, Expression.New(CachedReflectionInfo.StreamReader_ctor, path))); var loopTest = Expression.NotEqual( - Expression.Assign(line, - Expression.Call(streamReader, CachedReflectionInfo.StreamReader_ReadLine)).Cast(typeof(object)), + Expression.Assign(line, Expression.Call(streamReader, CachedReflectionInfo.StreamReader_ReadLine)).Cast(typeof(object)), ExpressionCache.NullConstant); tryBodyExprs.Add(avs.SaveAutomaticVar()); - tryBodyExprs.Add(GenerateWhileLoop(switchStatementAst.Label, - () => loopTest, - (loopBody, breakTarget, continueTarget) => switchBodyGenerator(loopBody, line))); + tryBodyExprs.Add( + GenerateWhileLoop( + switchStatementAst.Label, + () => loopTest, + (loopBody, breakTarget, continueTarget) => switchBodyGenerator(loopBody, line))); var tryBlock = Expression.Block(tryBodyExprs); var finallyBlock = Expression.Block( Expression.IfThen( @@ -3739,10 +4496,11 @@ public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) var exception = NewTemp(typeof(Exception), "exception"); var catchAllBlock = Expression.Block( tryBlock.Type, - ThrowRuntimeErrorWithInnerException("FileReadError", - ParserStrings.FileReadError, - exception, - Expression.Property(exception, CachedReflectionInfo.Exception_Message))); + ThrowRuntimeErrorWithInnerException( + "FileReadError", + ParserStrings.FileReadError, + exception, + Expression.Property(exception, CachedReflectionInfo.Exception_Message))); exprs.Add(Expression.TryCatchFinally( tryBlock, finallyBlock, @@ -3765,13 +4523,16 @@ public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) // } // REVIEW: should we consider this form of switch a loop for the purposes of deciding // to compile or not? I have a feeling the loop form is uncommon and compiling isn't worth it. - + // var tryStmt = Expression.TryFinally( Expression.Block( avs.SaveAutomaticVar(), - GenerateIteratorStatement(SpecialVariables.switchVarPath, () => UpdatePosition(switchStatementAst.Condition), _switchTupleIndex, - switchStatementAst, switchBodyGenerator) - ), + GenerateIteratorStatement( + SpecialVariables.switchVarPath, + () => UpdatePosition(switchStatementAst.Condition), + _switchTupleIndex, + switchStatementAst, + switchBodyGenerator)), avs.RestoreAutomaticVar()); return Expression.Block(temps.Concat(avs.GetTemps()), tryStmt); @@ -3816,14 +4577,16 @@ private Action, Expression> GetSwitchBodyGenerator(SwitchStatem SwitchFlags flags = switchStatementAst.Flags; Expression conditionExpr = constValue is Regex || constValue is WildcardPattern ? (Expression)Expression.Constant(constValue) - : DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), - (constValue is Type) - ? Expression.Constant(constValue, typeof(Type)) - : Expression.Constant(constValue), - _executionContextParameter); + : DynamicExpression.Dynamic( + PSToStringBinder.Get(), + typeof(string), + (constValue is Type) + ? Expression.Constant(constValue, typeof(Type)) + : Expression.Constant(constValue), + s_executionContextParameter); Expression currentAsString = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), GetLocal((int)AutomaticVariable.Underbar), - _executionContextParameter); + s_executionContextParameter); if ((flags & SwitchFlags.Regex) != 0 || constValue is Regex) { test = Expression.Call(CachedReflectionInfo.SwitchOps_ConditionSatisfiedRegex, @@ -3831,7 +4594,7 @@ private Action, Expression> GetSwitchBodyGenerator(SwitchStatem /*condition=*/ conditionExpr, /*errorPosition=*/ Expression.Constant(clause.Item1.Extent), /*str=*/ currentAsString, - /*context=*/ _executionContextParameter); + /*context=*/ s_executionContextParameter); } else if ((flags & SwitchFlags.Wildcard) != 0 || constValue is WildcardPattern) { @@ -3841,19 +4604,22 @@ private Action, Expression> GetSwitchBodyGenerator(SwitchStatem /*caseSensitive=*/ ExpressionCache.Constant((flags & SwitchFlags.CaseSensitive) != 0), /*condition=*/ conditionExpr, /*str=*/ currentAsString, - /*context=*/ _executionContextParameter); + /*context=*/ s_executionContextParameter); } else { - test = CallStringEquals(conditionExpr, currentAsString, - ((flags & SwitchFlags.CaseSensitive) == 0)); + test = CallStringEquals(conditionExpr, currentAsString, ((flags & SwitchFlags.CaseSensitive) == 0)); } } else { var cond = Compile(clause.Item1); - test = DynamicExpression.Dynamic(clauseEvalBinder, typeof(bool), - cond, GetLocal((int)AutomaticVariable.Underbar), _executionContextParameter); + test = DynamicExpression.Dynamic( + clauseEvalBinder, + typeof(bool), + cond, + GetLocal((int)AutomaticVariable.Underbar), + s_executionContextParameter); } exprs.Add(UpdatePosition(clause.Item1)); @@ -3896,9 +4662,8 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) // { // context.LanguageMode = oldLanguageMode; // } - var oldLanguageMode = NewTemp(typeof(PSLanguageMode), "oldLanguageMode"); - var languageModePropertyExpr = Expression.Property(_executionContextParameter, CachedReflectionInfo.ExecutionContext_LanguageMode); + var languageModePropertyExpr = Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_LanguageMode); var exprs = new List { @@ -3910,8 +4675,9 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) // Auto-import utility module if needed, so that ConvertFrom-StringData isn't set to RestrictedLanguage // by the data section. - Expression.Call(CachedReflectionInfo.RestrictedLanguageChecker_EnsureUtilityModuleLoaded, - _executionContextParameter) + Expression.Call( + CachedReflectionInfo.RestrictedLanguageChecker_EnsureUtilityModuleLoaded, + s_executionContextParameter) }; if (dataStatementAst.CommandsAllowed.Count > 0) @@ -3922,7 +4688,8 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) exprs.Add( Expression.Call( CachedReflectionInfo.RestrictedLanguageChecker_CheckDataStatementLanguageModeAtRuntime, - Expression.Constant(dataStatementAst), _executionContextParameter)); + Expression.Constant(dataStatementAst), + s_executionContextParameter)); } if (dataStatementAst.HasNonConstantAllowedCommand) @@ -3932,8 +4699,7 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) Expression.Call(CachedReflectionInfo.RestrictedLanguageChecker_CheckDataStatementAstAtRuntime, Expression.Constant(dataStatementAst), Expression.NewArrayInit(typeof(string), - dataStatementAst.CommandsAllowed.Select( - elem => Compile(elem).Convert(typeof(string)))))); + dataStatementAst.CommandsAllowed.Select(elem => Compile(elem).Convert(typeof(string)))))); } exprs.Add(Expression.Assign(languageModePropertyExpr, Expression.Constant(PSLanguageMode.RestrictedLanguage))); @@ -3947,9 +4713,7 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) var block = Expression.Block( new[] { oldLanguageMode }, - Expression.TryFinally( - Expression.Block(exprs), - Expression.Assign(languageModePropertyExpr, oldLanguageMode))); + Expression.TryFinally(Expression.Block(exprs), Expression.Assign(languageModePropertyExpr, oldLanguageMode))); if (dataStatementAst.Variable != null) { @@ -3968,20 +4732,23 @@ private static CatchBlock[] GenerateLoopBreakContinueCatchBlocks(string label, L return new[] { // catch (BreakException ev) { if (ev.MatchLabel(loopStatement.Label) { goto breakTarget; } throw; } - Expression.Catch(breakExceptionVar, + Expression.Catch( + breakExceptionVar, Expression.IfThenElse( Expression.Call(breakExceptionVar, CachedReflectionInfo.LoopFlowException_MatchLabel, - Expression.Constant(label ?? "", typeof(string))), + Expression.Constant(label ?? string.Empty, typeof(string))), Expression.Break(breakLabel), Expression.Rethrow())), // catch (ContinueException ev) { if (ev.MatchLabel(loopStatement.Label) { goto continueTarget; } throw; } - Expression.Catch(continueExceptionVar, + Expression.Catch( + continueExceptionVar, Expression.IfThenElse( - Expression.Call(continueExceptionVar, - CachedReflectionInfo.LoopFlowException_MatchLabel, - Expression.Constant(label ?? "", typeof(string))), + Expression.Call( + continueExceptionVar, + CachedReflectionInfo.LoopFlowException_MatchLabel, + Expression.Constant(label ?? string.Empty, typeof(string))), Expression.Continue(continueLabel), Expression.Rethrow())) }; @@ -4031,7 +4798,6 @@ private Expression GenerateWhileLoop(string loopLabel, // } // } // :BreakTarget - int preStmtCount = _stmtCount; var exprs = new List(); @@ -4049,7 +4815,7 @@ private Expression GenerateWhileLoop(string loopLabel, var loopBodyExprs = new List(); loopBodyExprs.Add(s_callCheckForInterrupts); - _loopTargets.Add(new LoopGotoTargets(loopLabel ?? "", breakLabel, continueLabel)); + _loopTargets.Add(new LoopGotoTargets(loopLabel ?? string.Empty, breakLabel, continueLabel)); _generatingWhileOrDoLoop = true; generateLoopBody(loopBodyExprs, breakLabel, continueLabel); _generatingWhileOrDoLoop = false; @@ -4057,11 +4823,11 @@ private Expression GenerateWhileLoop(string loopLabel, { loopBodyExprs.Add(Expression.Goto(loopTop)); } + _loopTargets.RemoveAt(_loopTargets.Count - 1); Expression loopBody = - Expression.TryCatch(Expression.Block(loopBodyExprs), - GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel)); + Expression.TryCatch(Expression.Block(loopBodyExprs), GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel)); if (continueAst != null) { var x = new List(); @@ -4073,6 +4839,7 @@ private Expression GenerateWhileLoop(string loopLabel, // but pre/post increments don't, so we add it here explicitly. x.Add(UpdatePosition(continueAst)); } + // We should not preserve the partial output if exception is thrown when evaluating the continueAst. x.Add(CaptureStatementResults(continueAst, CaptureAstContext.AssignmentWithoutResultPreservation)); x.Add(Expression.Goto(loopTop)); @@ -4087,9 +4854,10 @@ private Expression GenerateWhileLoop(string loopLabel, { exprs.Add(loopBody); } + exprs.Add(Expression.Label(breakLabel)); enterLoop.LoopStatementCount = _stmtCount - preStmtCount; - return (enterLoop.Loop = new PowerShellLoopExpression(exprs)); + return enterLoop.Loop = new PowerShellLoopExpression(exprs); } private Expression GenerateDoLoop(LoopStatementAst loopStatement) @@ -4115,7 +4883,7 @@ private Expression GenerateDoLoop(LoopStatementAst loopStatement) // goto RepeatTarget // } // :BreakTarget - + // int preStmtCount = _stmtCount; string loopLabel = loopStatement.Label; @@ -4128,10 +4896,10 @@ private Expression GenerateDoLoop(LoopStatementAst loopStatement) exprs.Add(Expression.Label(repeatLabel)); exprs.Add(enterLoopExpression); - _loopTargets.Add(new LoopGotoTargets(loopLabel ?? "", breakLabel, continueLabel)); + _loopTargets.Add(new LoopGotoTargets(loopLabel ?? string.Empty, breakLabel, continueLabel)); _generatingWhileOrDoLoop = true; var loopBodyExprs = new List - { + { s_callCheckForInterrupts, Compile(loopStatement.Body), ExpressionCache.Empty @@ -4139,14 +4907,17 @@ private Expression GenerateDoLoop(LoopStatementAst loopStatement) _generatingWhileOrDoLoop = false; _loopTargets.RemoveAt(_loopTargets.Count - 1); - exprs.Add(Expression.TryCatch(Expression.Block(loopBodyExprs), - GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel))); + exprs.Add(Expression.TryCatch( + Expression.Block(loopBodyExprs), + GenerateLoopBreakContinueCatchBlocks(loopLabel, breakLabel, continueLabel))); exprs.Add(Expression.Label(continueLabel)); var test = CaptureStatementResults(loopStatement.Condition, CaptureAstContext.Condition).Convert(typeof(bool)); if (loopStatement is DoUntilStatementAst) { test = Expression.Not(test); } + + test = UpdatePositionForInitializerOrCondition(loopStatement.Condition, test); exprs.Add(Expression.IfThen(test, Expression.Goto(repeatLabel))); exprs.Add(Expression.Label(breakLabel)); @@ -4154,11 +4925,12 @@ private Expression GenerateDoLoop(LoopStatementAst loopStatement) return enterLoopExpression.Loop = new PowerShellLoopExpression(exprs); } - private Expression GenerateIteratorStatement(VariablePath iteratorVariablePath, - Func generateMoveNextUpdatePosition, - int iteratorTupleIndex, - LabeledStatementAst stmt, - Action, Expression> generateBody) + private Expression GenerateIteratorStatement( + VariablePath iteratorVariablePath, + Func generateMoveNextUpdatePosition, + int iteratorTupleIndex, + LabeledStatementAst stmt, + Action, Expression> generateBody) { // We convert: // foreach ($x in $enumerable) {} @@ -4185,7 +4957,7 @@ private Expression GenerateIteratorStatement(VariablePath iteratorVariablePath, // $foreach = $oldforeach // } // The translation for switch is similar. - + // var temps = new List(); var exprs = new List(); var avs = new AutomaticVarSaver(this, iteratorVariablePath, iteratorTupleIndex); @@ -4197,19 +4969,37 @@ private Expression GenerateIteratorStatement(VariablePath iteratorVariablePath, // $foreach/$switch = GetEnumerator $enumerable var enumerable = NewTemp(typeof(object), "enumerable"); temps.Add(enumerable); + + // Update position to make it safe to access 'CurrentPosition' property in FunctionContext in case + // that the evaluation of 'stmt.Condition' throws exception. if (generatingForeach) { + // For foreach statement, we want the debugger to stop at 'stmt.Condition' before evaluating it. + // The debugger will stop at 'stmt.Condition' only once. The following enumeration will stop at + // the foreach variable. exprs.Add(UpdatePosition(stmt.Condition)); } + else + { + // For switch statement, we don't want the debugger to stop at 'stmt.Condition' before evaluating it. + // The following enumeration will stop at 'stmt.Condition' again, and we don't want the debugger to + // stop at 'stmt.Condition' twice before getting into one of its case clauses. + var extent = stmt.Condition.Extent; + int index = AddSequencePoint(extent); + exprs.Add(new UpdatePositionExpr(extent, index, _debugSymbolDocument, checkBreakpoints: false)); + } + exprs.Add( - Expression.Assign(enumerable, - GetRangeEnumerator(stmt.Condition.GetPureExpression()) - ?? CaptureStatementResults(stmt.Condition, CaptureAstContext.Enumerable).Convert(typeof(object)))); + Expression.Assign( + enumerable, + GetRangeEnumerator(stmt.Condition.GetPureExpression()) ?? CaptureStatementResults(stmt.Condition, CaptureAstContext.Enumerable).Convert(typeof(object)))); var iteratorTemp = NewTemp(typeof(IEnumerator), iteratorVariablePath.UnqualifiedPath); temps.Add(iteratorTemp); - exprs.Add(Expression.Assign(iteratorTemp, - DynamicExpression.Dynamic(PSEnumerableBinder.Get(), typeof(IEnumerator), enumerable))); + exprs.Add( + Expression.Assign( + iteratorTemp, + DynamicExpression.Dynamic(PSEnumerableBinder.Get(), typeof(IEnumerator), enumerable))); // In a foreach, generate: // if ($foreach == $null && $enumerable != $null) @@ -4221,7 +5011,6 @@ private Expression GenerateIteratorStatement(VariablePath iteratorVariablePath, // { // $switch = (new object[] { $enumerable }).GetEnumerator() // } - var testNeedScalarToEnumerable = generatingForeach ? Expression.AndAlso( @@ -4240,9 +5029,10 @@ private Expression GenerateIteratorStatement(VariablePath iteratorVariablePath, generateMoveNextUpdatePosition(), Expression.Call(iteratorTemp, CachedReflectionInfo.IEnumerator_MoveNext)); - var loop = GenerateWhileLoop(stmt.Label, - () => moveNext, - (loopBody, breakTarget, continueTarget) => generateBody(loopBody, Expression.Property(iteratorTemp, CachedReflectionInfo.IEnumerator_Current))); + var loop = GenerateWhileLoop( + stmt.Label, + () => moveNext, + (loopBody, breakTarget, continueTarget) => generateBody(loopBody, Expression.Property(iteratorTemp, CachedReflectionInfo.IEnumerator_Current))); // With a foreach, the enumerator may never get assigned, in which case we skip the loop entirely. // Generate that test. @@ -4279,7 +5069,6 @@ public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) // $x = $foreach.Current // } // } - Action, Expression> loopBodyGenerator = (exprs, newValue) => { @@ -4287,14 +5076,16 @@ public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) exprs.Add(Compile(forEachStatementAst.Body)); }; - return GenerateIteratorStatement(SpecialVariables.foreachVarPath, - () => UpdatePosition(forEachStatementAst.Variable), _foreachTupleIndex, - forEachStatementAst, loopBodyGenerator); + return GenerateIteratorStatement( + SpecialVariables.foreachVarPath, + () => UpdatePosition(forEachStatementAst.Variable), + _foreachTupleIndex, + forEachStatementAst, + loopBodyGenerator); } private Expression GetRangeEnumerator(ExpressionAst condExpr) { - Expression result = null; if (condExpr != null) { var binaryExpr = condExpr as BinaryExpressionAst; @@ -4303,12 +5094,27 @@ private Expression GetRangeEnumerator(ExpressionAst condExpr) Expression lhs = Compile(binaryExpr.Left); Expression rhs = Compile(binaryExpr.Right); - result = Expression.New(CachedReflectionInfo.RangeEnumerator_ctor, - lhs.Convert(typeof(int)), - rhs.Convert(typeof(int))); + return Expression.Call( + CachedReflectionInfo.ParserOps_GetRangeEnumerator, + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object))); } } - return result; + + return null; + } + + private Expression UpdatePositionForInitializerOrCondition(PipelineBaseAst pipelineBaseAst, Expression initializerOrCondition) + { + if (pipelineBaseAst is PipelineAst pipelineAst && !pipelineAst.Background && pipelineAst.GetPureExpression() != null) + { + // If the initializer or condition is a pure expression (CommandExpressionAst without redirection), + // then we need to add a sequence point. If it's an AssignmentStatementAst, we don't need to add + // sequence point here because one will be added when processing the AssignmentStatementAst. + initializerOrCondition = Expression.Block(UpdatePosition(pipelineBaseAst), initializerOrCondition); + } + + return initializerOrCondition; } public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) @@ -4324,32 +5130,40 @@ public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) public object VisitForStatement(ForStatementAst forStatementAst) { // We should not preserve the partial output if exception is thrown when evaluating the initializer. - var init = (forStatementAst.Initializer != null) - ? CaptureStatementResults(forStatementAst.Initializer, CaptureAstContext.AssignmentWithoutResultPreservation) - : null; + Expression init = null; + PipelineBaseAst initializer = forStatementAst.Initializer; + if (initializer != null) + { + init = CaptureStatementResults(initializer, CaptureAstContext.AssignmentWithoutResultPreservation); + init = UpdatePositionForInitializerOrCondition(initializer, init); + } - var generateCondition = forStatementAst.Condition != null - ? () => Expression.Block(UpdatePosition(forStatementAst.Condition), - CaptureStatementResults(forStatementAst.Condition, CaptureAstContext.Condition)) + PipelineBaseAst condition = forStatementAst.Condition; + var generateCondition = condition != null + ? () => UpdatePositionForInitializerOrCondition(condition, CaptureStatementResults(condition, CaptureAstContext.Condition)) : (Func)null; - var loop = GenerateWhileLoop(forStatementAst.Label, generateCondition, - (loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(forStatementAst.Body)), - forStatementAst.Iterator); + var loop = GenerateWhileLoop( + forStatementAst.Label, + generateCondition, + (loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(forStatementAst.Body)), + forStatementAst.Iterator); if (init != null) { return Expression.Block(init, loop); } + return loop; } public object VisitWhileStatement(WhileStatementAst whileStatementAst) { - return GenerateWhileLoop(whileStatementAst.Label, - () => Expression.Block(UpdatePosition(whileStatementAst.Condition), - CaptureStatementResults(whileStatementAst.Condition, CaptureAstContext.Condition)), - (loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(whileStatementAst.Body))); + PipelineBaseAst condition = whileStatementAst.Condition; + return GenerateWhileLoop( + whileStatementAst.Label, + () => UpdatePositionForInitializerOrCondition(condition, CaptureStatementResults(condition, CaptureAstContext.Condition)), + (loopBody, breakTarget, continueTarget) => loopBody.Add(Compile(whileStatementAst.Body))); } public object VisitCatchClause(CatchClauseAst catchClauseAst) @@ -4365,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; @@ -4377,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; @@ -4438,8 +5252,9 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) // know if we're dynamically executing code guarded by a try/catch. var oldActiveHandler = NewTemp(typeof(bool), "oldActiveHandler"); temps.Add(oldActiveHandler); - var handlerInScope = Expression.Property(_executionContextParameter, - CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock); + var handlerInScope = Expression.Property( + s_executionContextParameter, + CachedReflectionInfo.ExecutionContext_ExceptionHandlerInEnclosingStatementBlock); tryBlockExprs.Add(Expression.Assign(oldActiveHandler, handlerInScope)); tryBlockExprs.Add(Expression.Assign(handlerInScope, ExpressionCache.Constant(true))); finallyBlockExprs.Add(Expression.Assign(handlerInScope, oldActiveHandler)); @@ -4463,28 +5278,28 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) // $_ = oldDollarUnder // context.CurrentExceptionBeingHandled = oldrte // } - AutomaticVarSaver avs = new AutomaticVarSaver(this, SpecialVariables.UnderbarVarPath, - (int)AutomaticVariable.Underbar); + AutomaticVarSaver avs = new AutomaticVarSaver( + this, SpecialVariables.UnderbarVarPath, + (int)AutomaticVariable.Underbar); var rte = NewTemp(typeof(RuntimeException), "rte"); var oldrte = NewTemp(typeof(RuntimeException), "oldrte"); - var errorRecord = Expression.New(CachedReflectionInfo.ErrorRecord__ctor, - Expression.Property(rte, CachedReflectionInfo.RuntimeException_ErrorRecord), - rte); + var errorRecord = Expression.New( + CachedReflectionInfo.ErrorRecord__ctor, + Expression.Property(rte, CachedReflectionInfo.RuntimeException_ErrorRecord), + rte); var catchExprs = new List - { - Expression.Assign(oldrte, s_currentExceptionBeingHandled), - Expression.Assign(s_currentExceptionBeingHandled, rte), - avs.SaveAutomaticVar(), - avs.SetNewValue(errorRecord) - }; + { + Expression.Assign(oldrte, s_currentExceptionBeingHandled), + Expression.Assign(s_currentExceptionBeingHandled, rte), + avs.SaveAutomaticVar(), + avs.SetNewValue(errorRecord) + }; StatementBlockAst statementBlock = tryStatementAst.CatchClauses[0].Body; CompileStatementListWithTraps(statementBlock.Statements, statementBlock.Traps, catchExprs, temps); var tf = Expression.TryFinally( Expression.Block(typeof(void), catchExprs), - Expression.Block(typeof(void), - avs.RestoreAutomaticVar(), - Expression.Assign(s_currentExceptionBeingHandled, oldrte))); + Expression.Block(typeof(void), avs.RestoreAutomaticVar(), Expression.Assign(s_currentExceptionBeingHandled, oldrte))); catches.Add(Expression.Catch(typeof(PipelineStoppedException), Expression.Rethrow())); catches.Add(Expression.Catch(rte, Expression.Block(avs.GetTemps().Append(oldrte).ToArray(), tf))); @@ -4530,6 +5345,7 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) for (int index = 0; index < tryStatementAst.CatchClauses.Count; index++) { var c = tryStatementAst.CatchClauses[index]; + // If CatchTypes.Count is empty, we still want to count the catch all handler. countTypes += Math.Max(c.CatchTypes.Count, 1); } @@ -4563,17 +5379,18 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) // // We use a constant array, resolve just once (unless it fails) to prevent re-resolving // each time it executes. - var indexExpr = Expression.ArrayAccess(catchTypesExpr, ExpressionCache.Constant(i)); dynamicCatchTypes.Add( Expression.IfThen( Expression.Equal(indexExpr, ExpressionCache.NullType), Expression.Assign( indexExpr, - Expression.Call(CachedReflectionInfo.TypeOps_ResolveTypeName, - Expression.Constant(ct.TypeName), - Expression.Constant(ct.Extent))))); + Expression.Call( + CachedReflectionInfo.TypeOps_ResolveTypeName, + Expression.Constant(ct.TypeName), + Expression.Constant(ct.Extent))))); } + i += 1; } } @@ -4583,15 +5400,13 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) if (c.IsCatchAll) { - cases.Add(Expression.SwitchCase(catchBody, - ExpressionCache.Constant(handlerTypeIndex))); + cases.Add(Expression.SwitchCase(catchBody, ExpressionCache.Constant(handlerTypeIndex))); handlerTypeIndex += 1; } else { cases.Add(Expression.SwitchCase(catchBody, - Enumerable.Range(handlerTypeIndex, c.CatchTypes.Count).Select( - ExpressionCache.Constant))); + Enumerable.Range(handlerTypeIndex, c.CatchTypes.Count).Select(ExpressionCache.Constant))); handlerTypeIndex += c.CatchTypes.Count; } } @@ -4600,14 +5415,16 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) { // This might be worth a strict-mode check - if there was a typo, the typo isn't discovered until // the first time an exception is raised, which is rather unfortunate. - catchTypesExpr = Expression.Block(dynamicCatchTypes.Append(catchTypesExpr)); } - AutomaticVarSaver avs = new AutomaticVarSaver(this, SpecialVariables.UnderbarVarPath, - (int)AutomaticVariable.Underbar); - var swCond = Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_FindMatchingHandler, - LocalVariablesParameter, exception, catchTypesExpr, _executionContextParameter); + AutomaticVarSaver avs = new AutomaticVarSaver(this, SpecialVariables.UnderbarVarPath, (int)AutomaticVariable.Underbar); + var swCond = Expression.Call( + CachedReflectionInfo.ExceptionHandlingOps_FindMatchingHandler, + LocalVariablesParameter, + exception, + catchTypesExpr, + s_executionContextParameter); var oldexception = NewTemp(typeof(RuntimeException), "oldrte"); var tf = Expression.TryFinally( @@ -4621,7 +5438,7 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) swCond, Expression.Call( CachedReflectionInfo.ExceptionHandlingOps_CheckActionPreference, - _functionContext, exception), + s_functionContext, exception), cases.ToArray())), Expression.Block( avs.RestoreAutomaticVar(), @@ -4640,16 +5457,20 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) // } finally { // ExceptionHandlingOps.RestoreStoppingPipeline(executionContext, oldIsStopping); // } - var oldIsStopping = NewTemp(typeof(bool), "oldIsStopping"); temps.Add(oldIsStopping); finallyBlockExprs.Add( - Expression.Assign(oldIsStopping, - Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_SuspendStoppingPipeline, - _executionContextParameter))); + Expression.Assign( + oldIsStopping, + Expression.Call( + CachedReflectionInfo.ExceptionHandlingOps_SuspendStoppingPipeline, + s_executionContextParameter))); var nestedFinallyExprs = new List(); - CompileStatementListWithTraps(tryStatementAst.Finally.Statements, - tryStatementAst.Finally.Traps, nestedFinallyExprs, temps); + CompileStatementListWithTraps( + tryStatementAst.Finally.Statements, + tryStatementAst.Finally.Traps, + nestedFinallyExprs, + temps); if (nestedFinallyExprs.Count == 0) { nestedFinallyExprs.Add(ExpressionCache.Empty); @@ -4658,9 +5479,7 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) finallyBlockExprs.Add(Expression.Block( Expression.TryFinally( Expression.Block(nestedFinallyExprs), - Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_RestoreStoppingPipeline, - _executionContextParameter, - oldIsStopping)))); + Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_RestoreStoppingPipeline, s_executionContextParameter, oldIsStopping)))); } // Our result must have void type, so make sure it does. @@ -4686,11 +5505,12 @@ public object VisitTryStatement(TryStatementAst tryStatementAst) Expression.Block(finallyBlockExprs))); } - private Expression GenerateBreakOrContinue(Ast ast, - ExpressionAst label, - Func fieldSelector, - Func exprGenerator, - ConstructorInfo nonLocalExceptionCtor) + private Expression GenerateBreakOrContinue( + Ast ast, + ExpressionAst label, + Func fieldSelector, + Func exprGenerator, + ConstructorInfo nonLocalExceptionCtor) { LabelTarget labelTarget = null; Expression labelExpr = null; @@ -4720,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)))); } @@ -4729,14 +5549,22 @@ where t.Label.Equals(labelStrAst.Value, StringComparison.OrdinalIgnoreCase) public object VisitBreakStatement(BreakStatementAst breakStatementAst) { - return GenerateBreakOrContinue(breakStatementAst, breakStatementAst.Label, lgt => lgt.BreakLabel, Expression.Break, - CachedReflectionInfo.BreakException_ctor); + return GenerateBreakOrContinue( + breakStatementAst, + breakStatementAst.Label, + lgt => lgt.BreakLabel, + Expression.Break, + CachedReflectionInfo.BreakException_ctor); } public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { - return GenerateBreakOrContinue(continueStatementAst, continueStatementAst.Label, lgt => lgt.ContinueLabel, Expression.Continue, - CachedReflectionInfo.ContinueException_ctor); + return GenerateBreakOrContinue( + continueStatementAst, + continueStatementAst.Label, + lgt => lgt.ContinueLabel, + Expression.Continue, + CachedReflectionInfo.ContinueException_ctor); } public object VisitReturnStatement(ReturnStatementAst returnStatementAst) @@ -4746,8 +5574,7 @@ public object VisitReturnStatement(ReturnStatementAst returnStatementAst) Expression returnExpr; if (_compilingTrap) { - returnExpr = Expression.Throw(Expression.New(CachedReflectionInfo.ReturnException_ctor, - ExpressionCache.AutomationNullConstant)); + returnExpr = Expression.Throw(Expression.New(CachedReflectionInfo.ReturnException_ctor, ExpressionCache.AutomationNullConstant)); } else { @@ -4772,12 +5599,14 @@ public object VisitReturnStatement(ReturnStatementAst returnStatementAst) if (MemberFunctionReturnType != typeof(void)) { // Write directly to the pipe - don't use the dynamic site (CallAddPipe) as that could enumerate. - returnValue = Expression.Call(_returnPipe, CachedReflectionInfo.Pipe_Add, + returnValue = Expression.Call( + s_returnPipe, + CachedReflectionInfo.Pipe_Add, returnValue.Convert(MemberFunctionReturnType).Cast(typeof(object))); } return Expression.Block(UpdatePosition(returnStatementAst.Pipeline), - Expression.Assign(s_getCurrentPipe, _returnPipe), + Expression.Assign(s_getCurrentPipe, s_returnPipe), returnValue, returnExpr); } @@ -4789,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) @@ -4820,7 +5651,8 @@ public object VisitThrowStatement(ThrowStatementAst throwStatementAst) UpdatePosition(throwStatementAst), Expression.Throw(Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToException, throwExpr.Convert(typeof(object)), - Expression.Constant(throwStatementAst.Extent)))); + Expression.Constant(throwStatementAst.Extent), + Expression.Constant(throwStatementAst.IsRethrow)))); } #endregion Statements @@ -4831,7 +5663,7 @@ public Expression GenerateCallContains(Expression lhs, Expression rhs, bool igno { return Expression.Call( CachedReflectionInfo.ParserOps_ContainsOperatorCompiled, - _executionContextParameter, + s_executionContextParameter, Expression.Constant(CallSite>.Create(PSEnumerableBinder.Get())), Expression.Constant(CallSite>.Create( PSBinaryOperationBinder.Get(ExpressionType.Equal, ignoreCase, scalarCompare: true))), @@ -4839,6 +5671,16 @@ public Expression GenerateCallContains(Expression lhs, Expression rhs, bool igno rhs.Cast(typeof(object))); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + var expr = Expression.Condition( + Compile(ternaryExpressionAst.Condition).Convert(typeof(bool)), + Compile(ternaryExpressionAst.IfTrue).Convert(typeof(object)), + Compile(ternaryExpressionAst.IfFalse).Convert(typeof(object))); + + return expr; + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { object constantValue; @@ -4862,11 +5704,14 @@ 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.GetTypeInfo().IsValueType ? lhs : Expression.Call(CachedReflectionInfo.PSObject_Base, lhs); + lhs = lhs.Type.IsValueType ? lhs : Expression.Call(CachedReflectionInfo.PSObject_Base, lhs); if (binaryExpressionAst.Operator == TokenKind.Is) + { return Expression.TypeIs(lhs, isType); + } + return Expression.Not(Expression.TypeIs(lhs, isType)); } } @@ -4876,25 +5721,26 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { result = Expression.Not(result); } + return result; case TokenKind.As: return Expression.Call(CachedReflectionInfo.TypeOps_AsOperator, lhs.Cast(typeof(object)), rhs.Convert(typeof(Type))); case TokenKind.DotDot: - if(lhs.Type == typeof(string)){ - return Expression.Call(CachedReflectionInfo.CharOps_Range, - lhs.Convert(typeof(char)), - rhs.Convert(typeof(char))); - } - return Expression.Call(CachedReflectionInfo.IntOps_Range, - lhs.Convert(typeof(int)), - rhs.Convert(typeof(int))); + // We could generate faster code using Expression.Dynamic with a binder. + // Currently, type checks are done in ParserOps.RangeOperator at runtime every time + // a range operator is used. By replacing with Expression.Dynamic and a binder, the + // type check is done only once when you repeatedly execute the same line in script. + return Expression.Call( + CachedReflectionInfo.ParserOps_RangeOperator, lhs.Cast(typeof(object)), rhs.Cast(typeof(object))); + case TokenKind.Multiply: if (lhs.Type == typeof(double) && rhs.Type == typeof(double)) { return Expression.Multiply(lhs, rhs); } + binder = PSBinaryOperationBinder.Get(ExpressionType.Multiply); return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); case TokenKind.Divide: @@ -4902,6 +5748,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return Expression.Divide(lhs, rhs); } + binder = PSBinaryOperationBinder.Get(ExpressionType.Divide); return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); case TokenKind.Rem: @@ -4912,6 +5759,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return Expression.Add(lhs, rhs); } + binder = PSBinaryOperationBinder.Get(ExpressionType.Add); return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); case TokenKind.Minus: @@ -4919,13 +5767,15 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return Expression.Subtract(lhs, rhs); } + binder = PSBinaryOperationBinder.Get(ExpressionType.Subtract); return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); case TokenKind.Format: if (lhs.Type != typeof(string)) { - lhs = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), lhs, _executionContextParameter); + lhs = DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), lhs, s_executionContextParameter); } + return Expression.Call(CachedReflectionInfo.StringOps_FormatOperator, lhs, rhs.Cast(typeof(object))); case TokenKind.Xor: return Expression.NotEqual(lhs.Convert(typeof(bool)), rhs.Convert(typeof(bool))); @@ -4948,7 +5798,10 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_JoinOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object))); + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object))); case TokenKind.Ieq: binder = PSBinaryOperationBinder.Get(ExpressionType.Equal); return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); @@ -4971,7 +5824,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_LikeOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), GetLikeRHSOperand(WildcardOptions.IgnoreCase, rhs).Cast(typeof(object)), Expression.Constant(binaryExpressionAst.Operator)); @@ -4979,7 +5833,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_LikeOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), GetLikeRHSOperand(WildcardOptions.IgnoreCase, rhs).Cast(typeof(object)), Expression.Constant(binaryExpressionAst.Operator)); @@ -4987,19 +5842,30 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_MatchOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), - ExpressionCache.Constant(false), ExpressionCache.Constant(true)); + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), + ExpressionCache.Constant(false), + ExpressionCache.Constant(true)); case TokenKind.Inotmatch: // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_MatchOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), - ExpressionCache.Constant(true), ExpressionCache.Constant(true)); + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), + ExpressionCache.Constant(true), + ExpressionCache.Constant(true)); case TokenKind.Ireplace: // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_ReplaceOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), ExpressionCache.Constant(true)); case TokenKind.Icontains: return GenerateCallContains(lhs, rhs, true); @@ -5013,7 +5879,10 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_SplitOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), ExpressionCache.Constant(true)); case TokenKind.Ceq: binder = PSBinaryOperationBinder.Get(ExpressionType.Equal, false); @@ -5037,7 +5906,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_LikeOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), GetLikeRHSOperand(WildcardOptions.None, rhs).Cast(typeof(object)), Expression.Constant(binaryExpressionAst.Operator)); @@ -5045,7 +5915,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_LikeOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), GetLikeRHSOperand(WildcardOptions.None, rhs).Cast(typeof(object)), Expression.Constant(binaryExpressionAst.Operator)); @@ -5053,19 +5924,30 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_MatchOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), - ExpressionCache.Constant(false), ExpressionCache.Constant(false)); + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), + ExpressionCache.Constant(false), + ExpressionCache.Constant(false)); case TokenKind.Cnotmatch: // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_MatchOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), - ExpressionCache.Constant(true), ExpressionCache.Constant(false)); + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), + ExpressionCache.Constant(true), + ExpressionCache.Constant(false)); case TokenKind.Creplace: // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_ReplaceOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); case TokenKind.Ccontains: return GenerateCallContains(lhs, rhs, false); @@ -5079,8 +5961,13 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // TODO: replace this with faster code return Expression.Call( CachedReflectionInfo.ParserOps_SplitOperator, - _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), + s_executionContextParameter, + Expression.Constant(binaryExpressionAst.ErrorPosition), + lhs.Cast(typeof(object)), + rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); + case TokenKind.QuestionQuestion: + return Coalesce(lhs, rhs); } throw new InvalidOperationException("Unknown token in binary operator."); @@ -5088,13 +5975,15 @@ 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; + } return Expression.Constant(WildcardPattern.Get(val, options)); } @@ -5114,14 +6003,22 @@ public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) case TokenKind.Not: return DynamicExpression.Dynamic(PSUnaryOperationBinder.Get(ExpressionType.Not), typeof(object), CompileExpressionOperand(child)); case TokenKind.Minus: - return DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(ExpressionType.Subtract), - typeof(object), ExpressionCache.Constant(0), CompileExpressionOperand(child)); + return DynamicExpression.Dynamic( + PSBinaryOperationBinder.Get(ExpressionType.Subtract), + typeof(object), + ExpressionCache.Constant(0), + CompileExpressionOperand(child)); case TokenKind.Plus: - return DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(ExpressionType.Add), - typeof(object), ExpressionCache.Constant(0), CompileExpressionOperand(child)); + return DynamicExpression.Dynamic( + PSBinaryOperationBinder.Get(ExpressionType.Add), + typeof(object), + ExpressionCache.Constant(0), + CompileExpressionOperand(child)); case TokenKind.Bnot: - return DynamicExpression.Dynamic(PSUnaryOperationBinder.Get(ExpressionType.OnesComplement), - typeof(object), CompileExpressionOperand(child)); + return DynamicExpression.Dynamic( + PSUnaryOperationBinder.Get(ExpressionType.OnesComplement), + typeof(object), + CompileExpressionOperand(child)); case TokenKind.PlusPlus: return CompileIncrementOrDecrement(child, 1, true); case TokenKind.MinusMinus: @@ -5132,17 +6029,19 @@ public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) return CompileIncrementOrDecrement(child, -1, false); case TokenKind.Join: // TODO: replace this with faster code - return Expression.Call(CachedReflectionInfo.ParserOps_UnaryJoinOperator, - _executionContextParameter, - Expression.Constant(unaryExpressionAst.Extent), - (CompileExpressionOperand(child)).Cast(typeof(object))); + return Expression.Call( + CachedReflectionInfo.ParserOps_UnaryJoinOperator, + s_executionContextParameter, + Expression.Constant(unaryExpressionAst.Extent), + (CompileExpressionOperand(child)).Cast(typeof(object))); case TokenKind.Isplit: case TokenKind.Csplit: // TODO: replace this with faster code - return Expression.Call(CachedReflectionInfo.ParserOps_UnarySplitOperator, - _executionContextParameter, - Expression.Constant(unaryExpressionAst.Extent), - (CompileExpressionOperand(child)).Cast(typeof(object))); + return Expression.Call( + CachedReflectionInfo.ParserOps_UnarySplitOperator, + s_executionContextParameter, + Expression.Constant(unaryExpressionAst.Extent), + (CompileExpressionOperand(child)).Cast(typeof(object))); } throw new InvalidOperationException("Unknown token in unary operator."); @@ -5157,8 +6056,10 @@ private Expression CompileIncrementOrDecrement(ExpressionAst exprAst, int valueT Expression beforeVal = av.GetValue(this, exprs, temps); if (prefix) { - var newValue = DynamicExpression.Dynamic(PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement), - typeof(object), beforeVal); + var newValue = DynamicExpression.Dynamic( + PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement), + typeof(object), + beforeVal); tmp = Expression.Parameter(newValue.Type); exprs.Add(Expression.Assign(tmp, newValue)); exprs.Add(av.SetValue(this, tmp)); @@ -5168,10 +6069,12 @@ private Expression CompileIncrementOrDecrement(ExpressionAst exprAst, int valueT { tmp = Expression.Parameter(beforeVal.Type); exprs.Add(Expression.Assign(tmp, beforeVal)); - var newValue = DynamicExpression.Dynamic(PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement), - typeof(object), tmp); + var newValue = DynamicExpression.Dynamic( + PSUnaryOperationBinder.Get(valueToAdd == 1 ? ExpressionType.Increment : ExpressionType.Decrement), + typeof(object), + tmp); exprs.Add(av.SetValue(this, newValue)); - if (tmp.Type.GetTypeInfo().IsValueType) + if (tmp.Type.IsValueType) { // This is the result of the expression - it might be unused, but we don't bother knowing if it is used or not. exprs.Add(tmp); @@ -5182,10 +6085,13 @@ private Expression CompileIncrementOrDecrement(ExpressionAst exprAst, int valueT // the increment because this value isn't always used, so it might be removed as dead code, and we can // thus avoid adding an extra if test in some common cases (e.g. a for loop frequently uses ++ as the iteration // expression. - exprs.Add(Expression.Condition(Expression.Equal(tmp, ExpressionCache.NullConstant), - ExpressionCache.Constant(0).Cast(typeof(object)), tmp)); + exprs.Add(Expression.Condition( + Expression.Equal(tmp, ExpressionCache.NullConstant), + ExpressionCache.Constant(0).Cast(typeof(object)), + tmp)); } } + temps.Add(tmp); return Expression.Block(temps, exprs); } @@ -5206,15 +6112,18 @@ public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) var temp = NewTemp(typeof(OrderedDictionary), "orderedDictionary"); if (typeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase)) { - return Expression.Block(typeof(OrderedDictionary), + return Expression.Block( + typeof(OrderedDictionary), new[] { temp }, BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true)); } + if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase)) { // pure laziness here - we should construct the PSObject directly. Instead, we're relying on the conversion // to create the PSObject from an OrderedDictionary. - childExpr = Expression.Block(typeof(OrderedDictionary), + childExpr = Expression.Block( + typeof(OrderedDictionary), new[] { temp }, BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true)); } @@ -5227,21 +6136,18 @@ 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); - return Expression.Call(CachedReflectionInfo.VariableOps_GetVariableAsRef, - Expression.Constant(varExpr.VariablePath), _executionContextParameter, - varType != null && varType != typeof(object) - ? Expression.Constant(varType, typeof(Type)) - : ExpressionCache.NullType); + var varType = varExpr.GetVariableType(this, out _, out _); + return Expression.Call( + CachedReflectionInfo.VariableOps_GetVariableAsRef, + Expression.Constant(varExpr.VariablePath), + s_executionContextParameter, + varType != null && varType != typeof(object) + ? Expression.Constant(varType, typeof(Type)) + : ExpressionCache.NullType); } } - if (childExpr == null) - { - childExpr = Compile(convertExpressionAst.Child); - } + childExpr ??= Compile(convertExpressionAst.Child); if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase)) { @@ -5276,19 +6182,21 @@ public object VisitSubExpression(SubExpressionAst subExpressionAst) // is thrown. For example, the output of $(1; throw 2) should be 1 and the error record with message '2'; // but the output of $(1; throw 2).Length should just be the error record with message '2'. bool shouldPreserveResultInCaseofException = subExpressionAst.ShouldPreserveOutputInCaseOfException(); - return CaptureAstResults(subExpressionAst.SubExpression, - shouldPreserveResultInCaseofException - ? CaptureAstContext.AssignmentWithResultPreservation - : CaptureAstContext.AssignmentWithoutResultPreservation); + return CaptureAstResults( + subExpressionAst.SubExpression, + shouldPreserveResultInCaseofException + ? CaptureAstContext.AssignmentWithResultPreservation + : CaptureAstContext.AssignmentWithoutResultPreservation); } public object VisitUsingExpression(UsingExpressionAst usingExpression) { string usingExprKey = PsUtils.GetUsingExpressionKey(usingExpression); - return Expression.Call(CachedReflectionInfo.VariableOps_GetUsingValue, LocalVariablesParameter, - Expression.Constant(usingExprKey), - ExpressionCache.Constant(usingExpression.RuntimeUsingIndex), - _executionContextParameter); + return Expression.Call( + CachedReflectionInfo.VariableOps_GetUsingValue, LocalVariablesParameter, + Expression.Constant(usingExprKey), + ExpressionCache.Constant(usingExpression.RuntimeUsingIndex), + s_executionContextParameter); } public object VisitVariableExpression(VariableExpressionAst variableExpressionAst) @@ -5301,10 +6209,12 @@ public object VisitVariableExpression(VariableExpressionAst variableExpressionAs { return ExpressionCache.NullConstant; } + if (varPath.UnqualifiedPath.Equals(SpecialVariables.True, StringComparison.OrdinalIgnoreCase)) { return ExpressionCache.Constant(true); } + if (varPath.UnqualifiedPath.Equals(SpecialVariables.False, StringComparison.OrdinalIgnoreCase)) { return ExpressionCache.Constant(false); @@ -5318,8 +6228,7 @@ public object VisitVariableExpression(VariableExpressionAst variableExpressionAs { if (Optimize) { - return Expression.Property(_executionContextParameter, - CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue); + return Expression.Property(s_executionContextParameter, CachedReflectionInfo.ExecutionContext_QuestionMarkVariableValue); } // Unoptimized - need to check for breakpoints, so just get the variable, etc. @@ -5333,6 +6242,7 @@ public object VisitVariableExpression(VariableExpressionAst variableExpressionAs { return CallGetVariable(Expression.Constant(variableExpressionAst.VariablePath), variableExpressionAst); } + return GetLocal(tupleIndex); } @@ -5354,9 +6264,10 @@ internal Expression CompileTypeName(ITypeName typeName, IScriptExtent errorPos) return Expression.Constant(type, typeof(Type)); } - return Expression.Call(CachedReflectionInfo.TypeOps_ResolveTypeName, - Expression.Constant(typeName), - Expression.Constant(errorPos)); + return Expression.Call( + CachedReflectionInfo.TypeOps_ResolveTypeName, + Expression.Constant(typeName), + Expression.Constant(errorPos)); } public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) @@ -5372,20 +6283,37 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) if (memberExpressionAst.Static && (memberExpressionAst.Expression is TypeExpressionAst)) { var type = ((TypeExpressionAst)memberExpressionAst.Expression).TypeName.GetReflectionType(); - if (type != null && !type.GetTypeInfo().IsGenericTypeDefinition) + if (type != null && !type.IsGenericTypeDefinition) { var member = memberExpressionAst.Member as StringConstantExpressionAst; if (member != null) { // We skip Methods because the adapter wraps them in a PSMethod and it's not a common scenario. - var memberInfo = type.GetMember(member.Value, MemberTypes.Field | MemberTypes.Property, - BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static | - BindingFlags.FlattenHierarchy); + var memberInfo = type.GetMember( + member.Value, + MemberTypes.Field | MemberTypes.Property, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); if (memberInfo.Length == 1) { var propertyInfo = memberInfo[0] as PropertyInfo; if (propertyInfo != null) { + if (propertyInfo.PropertyType.IsByRefLike) + { + // ByRef-like types are not boxable and should be used only on stack. + return Expression.Throw( + Expression.New( + CachedReflectionInfo.GetValueException_ctor, + Expression.Constant(nameof(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField)), + Expression.Constant(null, typeof(Exception)), + Expression.Constant(ExtendedTypeSystem.CannotAccessByRefLikePropertyOrField), + Expression.NewArrayInit( + typeof(object), + Expression.Constant(propertyInfo.Name), + Expression.Constant(propertyInfo.PropertyType, typeof(Type)))), + typeof(object)); + } + if (propertyInfo.CanRead) { return Expression.Property(null, propertyInfo); @@ -5394,6 +6322,8 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) } else { + // Field cannot be of a ByRef-like type unless it's an instance member of a ref struct. + // So we don't need to check 'IsByRefLike' for static field access. return Expression.Field(null, (FieldInfo)memberInfo[0]); } } @@ -5402,74 +6332,113 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) } var target = CompileExpressionOperand(memberExpressionAst.Expression); + + // If the ?. operator is used for null conditional check, add the null conditional expression. var memberNameAst = memberExpressionAst.Member as StringConstantExpressionAst; - if (memberNameAst != null) - { - string name = memberNameAst.Value; - return DynamicExpression.Dynamic(PSGetMemberBinder.Get(name, _memberFunctionType, memberExpressionAst.Static), typeof(object), target); - } + Expression memberAccessExpr = memberNameAst != null + ? DynamicExpression.Dynamic(PSGetMemberBinder.Get(memberNameAst.Value, _memberFunctionType, memberExpressionAst.Static), typeof(object), target) + : DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(_memberFunctionType, memberExpressionAst.Static), typeof(object), target, Compile(memberExpressionAst.Member)); - var memberNameExpr = Compile(memberExpressionAst.Member); - return DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(_memberFunctionType, memberExpressionAst.Static), typeof(object), target, memberNameExpr); + return memberExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(target, memberAccessExpr) : memberAccessExpr; } 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) { - targetTypeConstraint = (typeDefinitionAst as TypeDefinitionAst).Type.GetTypeInfo().BaseType; + targetTypeConstraint = (typeDefinitionAst as TypeDefinitionAst).Type.BaseType; } else { 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(string name, - PSMethodInvocationConstraints constraints, - Expression target, - IEnumerable args, - bool @static, - bool propertySet) + internal Expression InvokeMember( + string name, + PSMethodInvocationConstraints constraints, + Expression target, + IEnumerable args, + bool @static, + bool propertySet, + 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); - return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target)); + + var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target)); + + 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); return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target)); } - internal Expression InvokeDynamicMember(Expression memberNameExpr, - PSMethodInvocationConstraints constraints, - Expression target, - IEnumerable args, - bool @static, - bool propertySet) + internal Expression InvokeDynamicMember( + Expression memberNameExpr, + PSMethodInvocationConstraints constraints, + Expression target, + IEnumerable args, + bool @static, + bool propertySet, + bool nullConditional = false) { var binder = PSInvokeDynamicMemberBinder.Get(new CallInfo(args.Count()), _memberFunctionType, @static, propertySet, constraints); - return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(memberNameExpr).Prepend(target)); + var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(memberNameExpr).Prepend(target)); + + return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder; } public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) @@ -5482,13 +6451,18 @@ public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMember var memberNameAst = invokeMemberExpressionAst.Member as StringConstantExpressionAst; if (memberNameAst != null) { - return InvokeMember(memberNameAst.Value, constraints, target, - args, invokeMemberExpressionAst.Static, false); + return InvokeMember( + memberNameAst.Value, + constraints, + target, + args, + invokeMemberExpressionAst.Static, + propertySet: false, + invokeMemberExpressionAst.NullConditional); } var memberNameExpr = Compile(invokeMemberExpressionAst.Member); - return InvokeDynamicMember(memberNameExpr, constraints, target, - args, invokeMemberExpressionAst.Static, false); + return InvokeDynamicMember(memberNameExpr, constraints, target, args, invokeMemberExpressionAst.Static, propertySet: false, invokeMemberExpressionAst.NullConditional); } public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) @@ -5517,19 +6491,22 @@ public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) return Expression.NewArrayInit(typeof(object)); } } - values = values ?? CaptureAstResults(subExpr, CaptureAstContext.Enumerable); + + values ??= CaptureAstResults(subExpr, CaptureAstContext.Enumerable); if (pureExprAst is ArrayLiteralAst) { // If the pure expression is ArrayLiteralAst, just return the result. return values; } + if (values.Type.IsPrimitive || values.Type == typeof(string)) { // Slight optimization - no need for a dynamic site. We could special case other // types as well, but it's probably not worth it. return Expression.NewArrayInit(typeof(object), values.Cast(typeof(object))); } + if (values.Type == typeof(void)) { // A dynamic site can't take void - but a void value is just an empty array. @@ -5544,9 +6521,10 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) List elementValues = new List(arrayLiteralAst.Elements.Count); foreach (var element in arrayLiteralAst.Elements) { - var eValue = Compile(element); - elementValues.Add(eValue.Type != typeof(void) ? eValue.Cast(typeof(object)) : Expression.Block(eValue, ExpressionCache.AutomationNullConstant)); + var elementValue = Compile(element); + elementValues.Add(elementValue.Type != typeof(void) ? elementValue.Cast(typeof(object)) : Expression.Block(elementValue, ExpressionCache.AutomationNullConstant)); } + return Expression.NewArrayInit(typeof(object), elementValues); } @@ -5555,28 +6533,27 @@ private IEnumerable BuildHashtable(ReadOnlyCollection yield return Expression.Assign(temp, Expression.New(ordered ? CachedReflectionInfo.OrderedDictionary_ctor : CachedReflectionInfo.Hashtable_ctor, ExpressionCache.Constant(keyValuePairs.Count), - ExpressionCache.CurrentCultureIgnoreCaseComparer.Cast(typeof(IEqualityComparer)))); + ExpressionCache.OrdinalIgnoreCaseComparer.Cast(typeof(IEqualityComparer)))); for (int index = 0; index < keyValuePairs.Count; index++) { var keyValuePair = keyValuePairs[index]; Expression key = Expression.Convert(Compile(keyValuePair.Item1), typeof(object)); + // We should not preserve the partial output if exception is thrown when evaluating the value. Expression value = - Expression.Convert( - CaptureStatementResults(keyValuePair.Item2, - CaptureAstContext.AssignmentWithoutResultPreservation), typeof(object)); + Expression.Convert(CaptureStatementResults(keyValuePair.Item2, CaptureAstContext.AssignmentWithoutResultPreservation), typeof(object)); Expression errorExtent = Expression.Constant(keyValuePair.Item1.Extent); - yield return - Expression.Call(CachedReflectionInfo.HashtableOps_AddKeyValuePair, - temp, key, value, errorExtent); + yield return Expression.Call(CachedReflectionInfo.HashtableOps_AddKeyValuePair, temp, key, value, errorExtent); } + yield return temp; } public object VisitHashtable(HashtableAst hashtableAst) { var temp = NewTemp(typeof(Hashtable), "hashtable"); - return Expression.Block(typeof(Hashtable), + return Expression.Block( + typeof(Hashtable), new[] { temp }, BuildHashtable(hashtableAst.KeyValuePairs, temp, ordered: false)); } @@ -5590,11 +6567,10 @@ public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExp // the expression is evaluated more than once (e.g. in a loop, or if the containing function/script // is called again.) return Expression.Call( - Expression.Constant(new ScriptBlockExpressionWrapper(scriptBlockExpressionAst.ScriptBlock)), - CachedReflectionInfo.ScriptBlockExpressionWrapper_GetScriptBlock, - _executionContextParameter, - ExpressionCache.Constant(false) - ); + Expression.Constant(new ScriptBlockExpressionWrapper(scriptBlockExpressionAst.ScriptBlock)), + CachedReflectionInfo.ScriptBlockExpressionWrapper_GetScriptBlock, + s_executionContextParameter, + ExpressionCache.Constant(false)); } public object VisitParenExpression(ParenExpressionAst parenExpressionAst) @@ -5611,10 +6587,11 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) // is thrown. For example, function bar { 1; throw 2 }, the output of (bar) should be 1 and the error record with message '2'; // but the output of (bar).Length should just be the error record with message '2'. bool shouldPreserveOutputInCaseOfException = parenExpressionAst.ShouldPreserveOutputInCaseOfException(); - return CaptureStatementResults(pipe, - shouldPreserveOutputInCaseOfException - ? CaptureAstContext.AssignmentWithResultPreservation - : CaptureAstContext.AssignmentWithoutResultPreservation); + return CaptureStatementResults( + pipe, + shouldPreserveOutputInCaseOfException + ? CaptureAstContext.AssignmentWithResultPreservation + : CaptureAstContext.AssignmentWithoutResultPreservation); } public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) @@ -5622,11 +6599,10 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa var left = Expression.Constant(expandableStringExpressionAst.FormatExpression); var nestedAsts = expandableStringExpressionAst.NestedExpressions; var toStringBinder = PSToStringBinder.Get(); - var right = Expression.NewArrayInit(typeof(string), - nestedAsts.Select( - e => DynamicExpression.Dynamic(toStringBinder, typeof(string), Compile(e), _executionContextParameter))); - return Expression.Call(CachedReflectionInfo.StringOps_FormatOperator, - left, right); + var right = Expression.NewArrayInit( + typeof(string), + nestedAsts.Select(e => DynamicExpression.Dynamic(toStringBinder, typeof(string), Compile(e), s_executionContextParameter))); + return Expression.Call(CachedReflectionInfo.StringOps_FormatOperator, left, right); } public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) @@ -5635,8 +6611,9 @@ public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) var index = indexExpressionAst.Index; var arrayLiteral = (index as ArrayLiteralAst); - var constraints = CombineTypeConstraintForMethodResolution(GetTypeConstraintForMethodResolution(indexExpressionAst.Target), - GetTypeConstraintForMethodResolution(index)); + var constraints = CombineTypeConstraintForMethodResolution( + GetTypeConstraintForMethodResolution(indexExpressionAst.Target), + GetTypeConstraintForMethodResolution(index)); // An array literal is either: // $x[1,2] @@ -5645,13 +6622,26 @@ public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) // In the former case, the user is requesting an array slice. In the latter case, they index expression is likely // an array (dynamically determined) and they don't want an array slice, they want to use the array as the index // expression. - if (arrayLiteral != null && arrayLiteral.Elements.Count > 1) - { - return DynamicExpression.Dynamic(PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints), typeof(object), - arrayLiteral.Elements.Select(CompileExpressionOperand).Prepend(targetExpr)); - } + Expression indexingExpr = arrayLiteral != null && arrayLiteral.Elements.Count > 1 + ? DynamicExpression.Dynamic( + PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints), + typeof(object), + arrayLiteral.Elements.Select(CompileExpressionOperand).Prepend(targetExpr)) + : DynamicExpression.Dynamic( + PSGetIndexBinder.Get(argCount: 1, constraints), + typeof(object), + targetExpr, + CompileExpressionOperand(index)); + + return indexExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(targetExpr, indexingExpr) : indexingExpr; + } - return DynamicExpression.Dynamic(PSGetIndexBinder.Get(1, constraints), typeof(object), targetExpr, CompileExpressionOperand(index)); + private static Expression GetNullConditionalWrappedExpression(Expression targetExpr, Expression memberAccessExpression) + { + return Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNull, targetExpr.Cast(typeof(object))), + ExpressionCache.NullConstant, + memberAccessExpression); } public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) @@ -5672,6 +6662,7 @@ internal class MemberAssignableValue : IAssignableValue internal MemberExpressionAst MemberExpression { get; set; } private Expression CachedTarget { get; set; } + private Expression CachedPropertyExpr { get; set; } private Expression GetTargetExpr(Compiler compiler) @@ -5712,13 +6703,17 @@ public Expression SetValue(Compiler compiler, Expression rhs) if (memberNameAst != null) { string name = memberNameAst.Value; - return DynamicExpression.Dynamic(PSSetMemberBinder.Get(name, compiler._memberFunctionType, MemberExpression.Static), - typeof(object), CachedTarget ?? GetTargetExpr(compiler), rhs); + return DynamicExpression.Dynamic( + PSSetMemberBinder.Get(name, compiler._memberFunctionType, MemberExpression.Static), + typeof(object), + CachedTarget ?? GetTargetExpr(compiler), rhs); } - return DynamicExpression.Dynamic(PSSetDynamicMemberBinder.Get(compiler._memberFunctionType, MemberExpression.Static), typeof(object), - CachedTarget ?? GetTargetExpr(compiler), - CachedPropertyExpr ?? GetPropertyExpr(compiler), rhs); + return DynamicExpression.Dynamic( + PSSetDynamicMemberBinder.Get(compiler._memberFunctionType, MemberExpression.Static), + typeof(object), + CachedTarget ?? GetTargetExpr(compiler), + CachedPropertyExpr ?? GetPropertyExpr(compiler), rhs); } } @@ -5747,9 +6742,20 @@ private IEnumerable GetArgumentExprs(Compiler compiler) return _argExprTemps; } - return InvokeMemberExpressionAst.Arguments == null - ? Utils.EmptyArray() - : (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) @@ -5762,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; @@ -5810,8 +6816,9 @@ internal class IndexAssignableValue : IAssignableValue private PSMethodInvocationConstraints GetInvocationConstraints() { - return Compiler.CombineTypeConstraintForMethodResolution(Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Target), - Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Index)); + return Compiler.CombineTypeConstraintForMethodResolution( + Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Target), + Compiler.GetTypeConstraintForMethodResolution(IndexExpressionAst.Index)); } private Expression GetTargetExpr(Compiler compiler) @@ -5832,7 +6839,7 @@ public Expression GetValue(Compiler compiler, List exprs, List exprs, List _exprs; @@ -5967,17 +6981,16 @@ 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); } } internal class EnterLoopExpression : Expression, Interpreter.IInstructionProvider { public override bool CanReduce { get { return true; } } + public override Type Type { get { return typeof(void); } } + public override ExpressionType NodeType { get { return ExpressionType.Extension; } } public override Expression Reduce() @@ -5986,7 +6999,9 @@ public override Expression Reduce() } internal new PowerShellLoopExpression Loop { get; set; } + internal EnterLoopInstruction EnterLoopInstruction { get; private set; } + internal int LoopStatementCount { get; set; } public void AddInstructions(LightCompiler compiler) @@ -6007,7 +7022,9 @@ public void AddInstructions(LightCompiler compiler) internal class UpdatePositionExpr : Expression, Interpreter.IInstructionProvider { public override bool CanReduce { get { return true; } } + public override Type Type { get { return typeof(void); } } + public override ExpressionType NodeType { get { return ExpressionType.Extension; } } private readonly IScriptExtent _extent; @@ -6033,7 +7050,7 @@ public override Expression Reduce() exprs.Add( Expression.Assign( - Expression.Field(Compiler._functionContext, CachedReflectionInfo.FunctionContext__currentSequencePointIndex), + Expression.Field(Compiler.s_functionContext, CachedReflectionInfo.FunctionContext__currentSequencePointIndex), ExpressionCache.Constant(_sequencePoint))); if (_checkBreakpoints) @@ -6041,13 +7058,14 @@ public override Expression Reduce() exprs.Add( Expression.IfThen( Expression.GreaterThan( - Expression.Field(Compiler._executionContextParameter, CachedReflectionInfo.ExecutionContext_DebuggingMode), + Expression.Field(Compiler.s_executionContextParameter, CachedReflectionInfo.ExecutionContext_DebuggingMode), ExpressionCache.Constant(0)), Expression.Call( - Expression.Field(Compiler._executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), + Expression.Field(Compiler.s_executionContextParameter, CachedReflectionInfo.ExecutionContext_Debugger), CachedReflectionInfo.Debugger_OnSequencePointHit, - Compiler._functionContext))); + Compiler.s_functionContext))); } + exprs.Add(ExpressionCache.Empty); return Expression.Block(exprs); diff --git a/src/System.Management.Automation/engine/parser/ConstantValues.cs b/src/System.Management.Automation/engine/parser/ConstantValues.cs index 8f6f0df41db..d580148ddd2 100644 --- a/src/System.Management.Automation/engine/parser/ConstantValues.cs +++ b/src/System.Management.Automation/engine/parser/ConstantValues.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Diagnostics; @@ -16,7 +15,7 @@ namespace System.Management.Automation.Language * There is a number of similarities between these two classes, and changes (fixes) in this code * may need to be reflected in that class and vice versa */ - internal class IsConstantValueVisitor : ICustomAstVisitor + internal class IsConstantValueVisitor : ICustomAstVisitor2 { public static bool IsConstant(Ast ast, out object constantValue, bool forAttribute = false, bool forRequires = false) { @@ -31,6 +30,7 @@ public static bool IsConstant(Ast ast, out object constantValue, bool forAttribu { break; } + parent = parent.Parent; } @@ -53,51 +53,111 @@ 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; } + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return false; } + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return false; } + public object VisitParamBlock(ParamBlockAst paramBlockAst) { return false; } + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { return false; } + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return false; } + public object VisitAttribute(AttributeAst attributeAst) { return false; } + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return false; } + public object VisitParameter(ParameterAst parameterAst) { return false; } + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return false; } + public object VisitIfStatement(IfStatementAst ifStmtAst) { return false; } + public object VisitTrap(TrapStatementAst trapStatementAst) { return false; } + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return false; } + public object VisitDataStatement(DataStatementAst dataStatementAst) { return false; } + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return false; } + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return false; } + public object VisitForStatement(ForStatementAst forStatementAst) { return false; } + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { return false; } + public object VisitCatchClause(CatchClauseAst catchClauseAst) { return false; } + public object VisitTryStatement(TryStatementAst tryStatementAst) { return false; } + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { return false; } + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return false; } + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return false; } + public object VisitExitStatement(ExitStatementAst exitStatementAst) { return false; } + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return false; } + public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return false; } + public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return false; } + public object VisitCommand(CommandAst commandAst) { return false; } + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return false; } + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { return false; } + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return false; } + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return false; } + public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return false; } + public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return false; } + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return false; } + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { return false; } + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return false; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return false; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return false; } + 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); } @@ -110,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; } @@ -136,6 +195,13 @@ private static bool IsNullDivisor(ExpressionAst operand) return false; } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return binaryExpressionAst.Operator.HasTrait(TokenFlags.CanConstantFold) && @@ -156,12 +222,14 @@ public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { return false; } + if (!type.IsSafePrimitive()) { // Only do conversions to built-in types - other conversions might not // be safe to optimize. return false; } + return (bool)convertExpressionAst.Child.Accept(this); } @@ -202,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; } @@ -213,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; } @@ -262,9 +329,10 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) } } - internal class ConstantValueVisitor : ICustomAstVisitor + internal class ConstantValueVisitor : ICustomAstVisitor2 { internal bool AttributeArgument { get; set; } + internal bool RequiresArgument { get; set; } [Conditional("DEBUG")] @@ -289,43 +357,93 @@ private static object CompileAndInvoke(Ast ast) } public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return AutomationNull.Value; } + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return AutomationNull.Value; } + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return AutomationNull.Value; } + public object VisitParamBlock(ParamBlockAst paramBlockAst) { return AutomationNull.Value; } + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { return AutomationNull.Value; } + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return AutomationNull.Value; } + public object VisitAttribute(AttributeAst attributeAst) { return AutomationNull.Value; } + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return AutomationNull.Value; } + public object VisitParameter(ParameterAst parameterAst) { return AutomationNull.Value; } + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return AutomationNull.Value; } + public object VisitIfStatement(IfStatementAst ifStmtAst) { return AutomationNull.Value; } + public object VisitTrap(TrapStatementAst trapStatementAst) { return AutomationNull.Value; } + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return AutomationNull.Value; } + public object VisitDataStatement(DataStatementAst dataStatementAst) { return AutomationNull.Value; } + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return AutomationNull.Value; } + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return AutomationNull.Value; } + public object VisitForStatement(ForStatementAst forStatementAst) { return AutomationNull.Value; } + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { return AutomationNull.Value; } + public object VisitCatchClause(CatchClauseAst catchClauseAst) { return AutomationNull.Value; } + public object VisitTryStatement(TryStatementAst tryStatementAst) { return AutomationNull.Value; } + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { return AutomationNull.Value; } + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return AutomationNull.Value; } + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return AutomationNull.Value; } + public object VisitExitStatement(ExitStatementAst exitStatementAst) { return AutomationNull.Value; } + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return AutomationNull.Value; } + public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return AutomationNull.Value; } + public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return AutomationNull.Value; } + public object VisitCommand(CommandAst commandAst) { return AutomationNull.Value; } + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return AutomationNull.Value; } + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { return AutomationNull.Value; } + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return AutomationNull.Value; } + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return AutomationNull.Value; } + public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return AutomationNull.Value; } + public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return AutomationNull.Value; } + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return AutomationNull.Value; } + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { return AutomationNull.Value; } + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return AutomationNull.Value; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return AutomationNull.Value; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return AutomationNull.Value; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return AutomationNull.Value; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return AutomationNull.Value; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return AutomationNull.Value; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return AutomationNull.Value; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return AutomationNull.Value; } + public object VisitStatementBlock(StatementBlockAst statementBlockAst) { CheckIsConstant(statementBlockAst, "Caller to verify ast is constant"); @@ -338,6 +456,16 @@ public object VisitPipeline(PipelineAst pipelineAst) return pipelineAst.GetPureExpression().Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + CheckIsConstant(ternaryExpressionAst, "Caller to verify ast is constant"); + + object condition = ternaryExpressionAst.Condition.Accept(this); + return LanguagePrimitives.IsTrue(condition) + ? ternaryExpressionAst.IfTrue.Accept(this) + : ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { CheckIsConstant(binaryExpressionAst, "Caller to verify ast is constant"); @@ -443,6 +571,7 @@ public object VisitHashtable(HashtableAst hashtableAst) { result.Add(pair.Item1.Accept(this), pair.Item2.Accept(this)); } + return result; } } diff --git a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs index 7b5868ca46c..cb280bdaf61 100644 --- a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs +++ b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs @@ -13,7 +13,6 @@ * * ***************************************************************************/ - #if ENABLE_BINDER_DEBUG_LOGGING using System.Linq.Expressions; @@ -87,6 +86,7 @@ private int Depth { private void Indent() { _delta += Tab; } + private void Dedent() { _delta -= Tab; } @@ -107,27 +107,28 @@ private static int GetId(T e, ref Dictionary ids) { id = ids.Count + 1; ids.Add(e, id); } + return id; } } private int GetLambdaId(LambdaExpression le) { - Debug.Assert(String.IsNullOrEmpty(le.Name)); + Debug.Assert(string.IsNullOrEmpty(le.Name)); return GetId(le, ref _lambdaIds); } private int GetParamId(ParameterExpression p) { - Debug.Assert(String.IsNullOrEmpty(p.Name)); + Debug.Assert(string.IsNullOrEmpty(p.Name)); return GetId(p, ref _paramIds); } private int GetLabelTargetId(LabelTarget target) { - Debug.Assert(String.IsNullOrEmpty(target.Name)); + Debug.Assert(string.IsNullOrEmpty(target.Name)); return GetId(target, ref _labelIds); } /// - /// Write out the given AST + /// Write out the given AST. /// internal static void WriteTo(Expression node, TextWriter writer) { Debug.Assert(node != null); @@ -179,9 +180,10 @@ private void Out(Flow before, string s, Flow after) { break; case Flow.NewLine: WriteLine(); - Write(new String(' ', Depth)); + Write(new string(' ', Depth)); break; } + Write(s); _flow = after; } @@ -190,6 +192,7 @@ private void WriteLine() { _out.WriteLine(); _column = 0; } + private void Write(string s) { _out.Write(s); _column += s.Length; @@ -213,6 +216,7 @@ private Flow CheckBreak(Flow flow) { flow &= ~Flow.Break; } } + return flow; } @@ -279,6 +283,7 @@ private void VisitDeclarations(IList expressions) { if (variable.IsByRef) { Out("&"); } + Out(" "); VisitParameter(variable); }); @@ -295,12 +300,15 @@ private void VisitExpressions(char open, char separator, IList expressions if (open == '{' || expressions.Count > 1) { NewLine(); } + isFirst = false; } else { Out(separator.ToString(), Flow.NewLine); } + visit(e); } + Dedent(); } @@ -319,6 +327,7 @@ private void VisitExpressions(char open, char separator, IList expressions if (open == '{') { NewLine(); } + Out(close.ToString(), Flow.Break); } @@ -398,29 +407,32 @@ protected override Expression VisitBinary(BinaryExpression node) { // prepend # to the operator to represent checked op if (isChecked) { - op = String.Format( + op = string.Format( CultureInfo.CurrentCulture, "#{0}", op ); } + Out(beforeOp, op, Flow.Space | Flow.Break); if (parenthesizeRight) { Out("(", Flow.None); } + Visit(node.Right); if (parenthesizeRight) { Out(Flow.None, ")", Flow.Break); } } + return node; } protected override Expression VisitParameter(ParameterExpression node) { // Have '$' for the DebugView of ParameterExpressions Out("$"); - if (String.IsNullOrEmpty(node.Name)) { + if (string.IsNullOrEmpty(node.Name)) { // If no name if provided, generate a name as $var1, $var2. // No guarantee for not having name conflicts with user provided variable names. // @@ -429,12 +441,13 @@ protected override Expression VisitParameter(ParameterExpression node) { } else { Out(GetDisplayName(node.Name)); } + return node; } protected override Expression VisitLambda(Expression node) { Out( - String.Format(CultureInfo.CurrentCulture, + string.Format(CultureInfo.CurrentCulture, "{0} {1}<{2}>", ".Lambda", GetLambdaName(node), @@ -457,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; @@ -475,6 +488,7 @@ protected override Expression VisitConditional(ConditionalExpression node) { Dedent(); Out(Flow.NewLine, ") {", Flow.NewLine); } + Indent(); Visit(node.IfTrue); Dedent(); @@ -492,12 +506,12 @@ protected override Expression VisitConstant(ConstantExpression node) { if (value == null) { Out("null"); } else if ((value is string) && node.Type == typeof(string)) { - Out(String.Format( + Out(string.Format( CultureInfo.CurrentCulture, "\"{0}\"", value)); } else if ((value is char) && node.Type == typeof(char)) { - Out(String.Format( + Out(string.Format( CultureInfo.CurrentCulture, "'{0}'", value)); @@ -510,13 +524,14 @@ protected override Expression VisitConstant(ConstantExpression node) { Out(value.ToString()); Out(suffix); } else { - Out(String.Format( + Out(string.Format( CultureInfo.CurrentCulture, ".Constant<{0}>({1})", node.Type.ToString(), value)); } } + return node; } @@ -524,21 +539,27 @@ private static string GetConstantValueSuffix(Type type) { if (type == typeof(UInt32)) { return "U"; } + if (type == typeof(Int64)) { return "L"; } + if (type == typeof(UInt64)) { return "UL"; } - if (type == typeof(Double)) { + + if (type == typeof(double)) { return "D"; } + if (type == typeof(Single)) { return "F"; } - if (type == typeof(Decimal)) { + + if (type == typeof(decimal)) { return "M"; } + return null; } @@ -629,6 +650,7 @@ private static bool NeedsParentheses(Expression parent, Expression child) { // Need to have parenthesis for the right operand. return child == binary.Right; } + return true; } @@ -783,6 +805,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) { } else { Out(""); } + Out("."); Out(node.Method.Name); VisitExpressions('(', node.Arguments); @@ -796,9 +819,11 @@ protected override Expression VisitNewArray(NewArrayExpression node) { VisitExpressions('[', node.Expressions); } else { // .NewArray MyType {expr1, expr2} + Out(".NewArray " + node.Type.ToString(), Flow.Space); VisitExpressions('{', node.Expressions); } + return node; } @@ -814,6 +839,7 @@ protected override ElementInit VisitElementInit(ElementInit node) { } else { VisitExpressions('{', node.Arguments); } + return node; } @@ -860,6 +886,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression node) { Out(Flow.Space, ".TypeEqual", Flow.Space); break; } + Out(node.TypeOperand.ToString()); return node; } @@ -903,6 +930,7 @@ protected override Expression VisitUnary(UnaryExpression node) { } else { Out(".Throw", Flow.Space); } + break; case ExpressionType.IsFalse: Out(".IsFalse"); @@ -947,6 +975,7 @@ protected override Expression VisitUnary(UnaryExpression node) { Out("++"); break; } + return node; } @@ -956,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); @@ -996,15 +1025,17 @@ protected override Expression VisitLoop(LoopExpression node) { if (node.ContinueLabel != null) { DumpLabel(node.ContinueLabel); } + Out(" {", Flow.NewLine); Indent(); Visit(node.Body); Dedent(); Out(Flow.NewLine, "}"); if (node.BreakLabel != null) { - Out("", Flow.NewLine); + Out(string.Empty, Flow.NewLine); DumpLabel(node.BreakLabel); } + return node; } @@ -1014,6 +1045,7 @@ protected override SwitchCase VisitSwitchCase(SwitchCase node) { Visit(test); Out("):", Flow.NewLine); } + Indent(); Indent(); Visit(node.Body); Dedent(); Dedent(); @@ -1034,6 +1066,7 @@ protected override Expression VisitSwitch(SwitchExpression node) { Dedent(); Dedent(); NewLine(); } + Out("}"); return node; } @@ -1041,13 +1074,15 @@ protected override Expression VisitSwitch(SwitchExpression node) { protected override CatchBlock VisitCatchBlock(CatchBlock node) { Out(Flow.NewLine, "} .Catch (" + node.Test.ToString()); if (node.Variable != null) { - Out(Flow.Space, ""); + Out(Flow.Space, string.Empty); VisitParameter(node.Variable); } + if (node.Filter != null) { Out(") .If (", Flow.Break); Visit(node.Filter); } + Out(") {", Flow.NewLine); Indent(); Visit(node.Body); @@ -1089,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); @@ -1103,7 +1138,7 @@ protected override Expression VisitExtension(Expression node) { } protected override Expression VisitDebugInfo(DebugInfoExpression node) { - Out(String.Format( + Out(string.Format( CultureInfo.CurrentCulture, ".DebugInfo({0}: {1}, {2} - {3}, {4})", node.Document.FileName, @@ -1115,15 +1150,14 @@ protected override Expression VisitDebugInfo(DebugInfoExpression node) { return 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); } @@ -1131,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); @@ -1149,9 +1179,10 @@ private void WriteLambda(LambdaExpression lambda) { } private string GetLambdaName(LambdaExpression lambda) { - if (String.IsNullOrEmpty(lambda.Name)) { + if (string.IsNullOrEmpty(lambda.Name)) { return "#Lambda" + GetLambdaId(lambda); } + return GetDisplayName(lambda.Name); } @@ -1161,15 +1192,16 @@ private string GetLambdaName(LambdaExpression lambda) { /// private static bool ContainsWhiteSpace(string name) { foreach (char c in name) { - if (Char.IsWhiteSpace(c)) { + if (char.IsWhiteSpace(c)) { return true; } } + return false; } 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 2179b73595a..00000000000 --- a/src/System.Management.Automation/engine/parser/FusionAssemblyIdentity.cs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. 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) - { - throw Marshal.GetExceptionForHR(hr); - } - - byte[] data = new byte[(int)characterCountIncludingTerminator * 2]; - fixed (byte* p = data) - { - hr = nameObject.GetDisplayName(p, ref characterCountIncludingTerminator, displayFlags); - if (hr != 0) - { - throw Marshal.GetExceptionForHR(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) - { - throw Marshal.GetExceptionForHR(hr); - } - - byte[] data = new byte[(int)size]; - fixed (byte* p = data) - { - hr = nameObject.GetProperty(propertyId, p, ref size); - if (hr != 0) - { - throw Marshal.GetExceptionForHR(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); - if (hr != 0) - { - throw Marshal.GetExceptionForHR(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 27345674487..00000000000 --- a/src/System.Management.Automation/engine/parser/GlobalAssemblyCache.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. 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 91ee67d9afa..06f978a51ce 100644 --- a/src/System.Management.Automation/engine/parser/PSType.cs +++ b/src/System.Management.Automation/engine/parser/PSType.cs @@ -1,29 +1,33 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Management.Automation.Internal; using System.Reflection; using System.Reflection.Emit; -using Microsoft.PowerShell; using System.Threading; -using System.Management.Automation.Internal; + +using Microsoft.PowerShell; 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,"; private static int s_globalCounter = 0; + private static readonly CustomAttributeBuilder s_hiddenCustomAttributeBuilder = - new CustomAttributeBuilder(typeof(HiddenAttribute).GetConstructor(Type.EmptyTypes), Utils.EmptyArray()); + new CustomAttributeBuilder(typeof(HiddenAttribute).GetConstructor(Type.EmptyTypes), Array.Empty()); private static readonly string s_sessionStateKeeperFieldName = "__sessionStateKeeper"; internal static readonly string SessionStateFieldName = "__sessionState"; + private static readonly MethodInfo s_sessionStateKeeper_GetSessionState = typeof(SessionStateKeeper).GetMethod("GetSessionState", BindingFlags.Instance | BindingFlags.Public); @@ -35,11 +39,16 @@ private static bool TryConvertArg(object arg, Type type, out object result, Pars result = arg; return true; } + if (!LanguagePrimitives.TryConvertTo(arg, type, out result)) { - parser.ReportError(errorExtent, () => ParserStrings.CannotConvertValue, ToStringCodeMethods.Type(type)); + parser.ReportError(errorExtent, + nameof(ParserStrings.CannotConvertValue), + ParserStrings.CannotConvertValue, + ToStringCodeMethods.Type(type)); return false; } + return true; } @@ -49,8 +58,8 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu Diagnostics.Assert(attributeType != null, "Semantic checks should have verified attribute type exists"); Diagnostics.Assert( - attributeType.GetTypeInfo().GetCustomAttribute(true) == null || - (attributeType.GetTypeInfo().GetCustomAttribute(true).ValidOn & attributeTargets) != 0, "Semantic checks should have verified attribute usage"); + attributeType.GetCustomAttribute(true) == null || + (attributeType.GetCustomAttribute(true).ValidOn & attributeTargets) != 0, "Semantic checks should have verified attribute usage"); var positionalArgs = new object[attributeAst.PositionalArguments.Count]; var cvv = new ConstantValueVisitor { AttributeArgument = false }; @@ -68,7 +77,17 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu bool expandParamsOnBest; bool callNonVirtually; var positionalArgCount = positionalArgs.Length; - var bestMethod = Adapter.FindBestMethod(newConstructors, null, positionalArgs, ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually); + + var bestMethod = Adapter.FindBestMethod( + newConstructors, + invocationConstraints: null, + allowCastingToByRefLikeType: false, + positionalArgs, + ref errorId, + ref errorMsg, + out expandParamsOnBest, + out callNonVirtually); + if (bestMethod == null) { parser.ReportError(new ParseError(attributeAst.Extent, errorId, @@ -92,7 +111,7 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu // This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling // against CoreCLR. So we need to add a null check until this is fixed in CLR. var paramArrayAttrs = parameterInfo[argIndex].GetCustomAttributes(typeof(ParamArrayAttribute), true); - if (paramArrayAttrs != null && paramArrayAttrs.Any() && expandParamsOnBest) + if (paramArrayAttrs != null && paramArrayAttrs.Length > 0 && expandParamsOnBest) { var elementType = parameterInfo[argIndex].ParameterType.GetElementType(); var paramsArray = Array.CreateInstance(elementType, positionalArgCount - argIndex); @@ -105,8 +124,10 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu { return null; } + paramsArray.SetValue(arg, i); } + break; } @@ -115,6 +136,7 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu { return null; } + ctorArgs[argIndex] = arg; } @@ -159,6 +181,7 @@ private static CustomAttributeBuilder GetAttributeBuilder(Parser parser, Attribu { return null; } + fieldInfoList.Add(fieldInfo); fieldArgs.Add(arg); } @@ -243,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; @@ -254,6 +277,7 @@ private class DefineTypeHelper internal readonly TypeBuilder _staticHelpersTypeBuilder; private readonly Dictionary _definedProperties; private readonly Dictionary>> _definedMethods; + private Dictionary, PropertyInfo> _abstractProperties; internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions; private bool _baseClassHasDefaultCtor; @@ -273,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; @@ -290,7 +314,7 @@ public DefineTypeHelper(Parser parser, ModuleBuilder module, TypeDefinitionAst t /// /// /// - /// return declared interfaces + /// Return declared interfaces. /// private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, out List interfaces) { @@ -300,7 +324,7 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou // Default base class is System.Object and it has a default ctor. _baseClassHasDefaultCtor = true; - if (typeDefinitionAst.BaseTypes.Any()) + if (typeDefinitionAst.BaseTypes.Count > 0) { // base class var baseTypeAsts = typeDefinitionAst.BaseTypes; @@ -308,7 +332,10 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou if (firstBaseTypeAst.TypeName.IsArray) { - parser.ReportError(firstBaseTypeAst.Extent, () => ParserStrings.SubtypeArray, firstBaseTypeAst.TypeName.FullName); + parser.ReportError(firstBaseTypeAst.Extent, + nameof(ParserStrings.SubtypeArray), + ParserStrings.SubtypeArray, + firstBaseTypeAst.TypeName.FullName); // fall to the default base type } else @@ -316,25 +343,33 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou baseClass = firstBaseTypeAst.TypeName.GetReflectionType(); if (baseClass == null) { - parser.ReportError(firstBaseTypeAst.Extent, () => ParserStrings.TypeNotFound, firstBaseTypeAst.TypeName.FullName); + parser.ReportError(firstBaseTypeAst.Extent, + nameof(ParserStrings.TypeNotFound), + ParserStrings.TypeNotFound, + firstBaseTypeAst.TypeName.FullName); // fall to the default base type } else - { - if (baseClass.GetTypeInfo().IsSealed) + if (baseClass.IsSealed) { - parser.ReportError(firstBaseTypeAst.Extent, () => ParserStrings.SealedBaseClass, baseClass.Name); + parser.ReportError(firstBaseTypeAst.Extent, + nameof(ParserStrings.SealedBaseClass), + ParserStrings.SealedBaseClass, + baseClass.Name); // ignore base type if it's sealed. baseClass = null; } - else if (baseClass.GetTypeInfo().IsGenericType && !baseClass.IsConstructedGenericType) + else if (baseClass.IsGenericType && !baseClass.IsConstructedGenericType) { - parser.ReportError(firstBaseTypeAst.Extent, () => ParserStrings.SubtypeUnclosedGeneric, baseClass.Name); + parser.ReportError(firstBaseTypeAst.Extent, + nameof(ParserStrings.SubtypeUnclosedGeneric), + ParserStrings.SubtypeUnclosedGeneric, + baseClass.Name); // ignore base type, we cannot inherit from unclosed generic. baseClass = null; } - else if (baseClass.GetTypeInfo().IsInterface) + else if (baseClass.IsInterface) { // First Ast can represent interface as well as BaseClass. interfaces.Add(baseClass); @@ -362,7 +397,10 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou { if (baseTypeAsts[i].TypeName.IsArray) { - parser.ReportError(baseTypeAsts[i].Extent, () => ParserStrings.SubtypeArray, baseTypeAsts[i].TypeName.FullName); + parser.ReportError(baseTypeAsts[i].Extent, + nameof(ParserStrings.SubtypeArray), + ParserStrings.SubtypeArray, + baseTypeAsts[i].TypeName.FullName); this.HasFatalErrors = true; } } @@ -371,24 +409,33 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou { if (baseTypeAsts[i].TypeName.IsArray) { - parser.ReportError(baseTypeAsts[i].Extent, () => ParserStrings.SubtypeArray, baseTypeAsts[i].TypeName.FullName); + parser.ReportError(baseTypeAsts[i].Extent, + nameof(ParserStrings.SubtypeArray), + ParserStrings.SubtypeArray, + baseTypeAsts[i].TypeName.FullName); } else { Type interfaceType = baseTypeAsts[i].TypeName.GetReflectionType(); if (interfaceType == null) { - parser.ReportError(baseTypeAsts[i].Extent, () => ParserStrings.TypeNotFound, baseTypeAsts[i].TypeName.FullName); + parser.ReportError(baseTypeAsts[i].Extent, + nameof(ParserStrings.TypeNotFound), + ParserStrings.TypeNotFound, + baseTypeAsts[i].TypeName.FullName); } else { - if (interfaceType.GetTypeInfo().IsInterface) + if (interfaceType.IsInterface) { interfaces.Add(interfaceType); } else { - parser.ReportError(baseTypeAsts[i].Extent, () => ParserStrings.InterfaceNameExpected, interfaceType.Name); + parser.ReportError(baseTypeAsts[i].Extent, + nameof(ParserStrings.InterfaceNameExpected), + ParserStrings.InterfaceNameExpected, + interfaceType.Name); } } } @@ -398,6 +445,48 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou return baseClass ?? typeof(object); } + private bool ShouldImplementProperty(string name, Type type, [NotNullWhen(true)] out PropertyInfo interfaceProperty) + { + if (_abstractProperties == null) + { + _abstractProperties = new Dictionary, PropertyInfo>(); + var allInterfaces = new HashSet(); + + // TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor. + // During compilation the interface hierarchy is flattened, so we only need to resolve one level of ancestral interfaces. + foreach (var interfaceType in _typeBuilder.GetInterfaces()) + { + foreach (var parentInterface in interfaceType.GetInterfaces()) + { + allInterfaces.Add(parentInterface); + } + + allInterfaces.Add(interfaceType); + } + + foreach (var interfaceType in allInterfaces) + { + foreach (var property in interfaceType.GetProperties()) + { + _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 _abstractProperties.TryGetValue(Tuple.Create(name, type), out interfaceProperty); + } + public void DefineMembers() { // If user didn't provide any instance ctors or static ctor we will generate default ctor or static ctor respectively. @@ -442,6 +531,7 @@ public void DefineMembers() instanceCtors.Add(method); } } + hasAnyMethods = true; DefineMethod(method); @@ -472,7 +562,7 @@ public void DefineMembers() if (needDefaultCtor) { - needDefaultCtor = !instanceCtors.Any(); + needDefaultCtor = instanceCtors.Count == 0; } //// Now we can decide to create explicit default ctors or report error. @@ -493,9 +583,12 @@ public void DefineMembers() } else { - if (!instanceCtors.Any()) + if (instanceCtors.Count == 0) { - _parser.ReportError(_typeDefinitionAst.Extent, () => ParserStrings.BaseClassNoDefaultCtor, _typeBuilder.BaseType.Name); + _parser.ReportError(_typeDefinitionAst.Extent, + nameof(ParserStrings.BaseClassNoDefaultCtor), + ParserStrings.BaseClassNoDefaultCtor, + _typeBuilder.BaseType.Name); this.HasFatalErrors = true; } } @@ -505,7 +598,10 @@ private void DefineProperty(PropertyMemberAst propertyMemberAst) { if (_definedProperties.ContainsKey(propertyMemberAst.Name)) { - _parser.ReportError(propertyMemberAst.Extent, () => ParserStrings.MemberAlreadyDefined, propertyMemberAst.Name); + _parser.ReportError(propertyMemberAst.Extent, + nameof(ParserStrings.MemberAlreadyDefined), + ParserStrings.MemberAlreadyDefined, + propertyMemberAst.Name); return; } @@ -534,13 +630,28 @@ 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; + MethodInfo implementingGetter = null; + MethodInfo implementingSetter = null; + if (ShouldImplementProperty(propertyMemberAst.Name, type, out PropertyInfo interfaceProperty)) + { + if (propertyMemberAst.IsStatic) + { + implementingGetter = interfaceProperty.GetGetMethod(); + implementingSetter = interfaceProperty.GetSetMethod(); + } + else + { + getSetAttributes |= Reflection.MethodAttributes.Virtual; + } + } + if (propertyMemberAst.IsStatic) { backingFieldAttributes |= FieldAttributes.Static; 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; @@ -561,7 +672,7 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type PropertyBuilder property = _typeBuilder.DefineProperty(propertyMemberAst.Name, Reflection.PropertyAttributes.None, type, null); // Define the "get" accessor method. - MethodBuilder getMethod = _typeBuilder.DefineMethod(String.Concat("get_", propertyMemberAst.Name), getSetAttributes, type, Type.EmptyTypes); + MethodBuilder getMethod = _typeBuilder.DefineMethod(string.Concat("get_", propertyMemberAst.Name), getSetAttributes, type, Type.EmptyTypes); ILGenerator getIlGen = getMethod.GetILGenerator(); if (propertyMemberAst.IsStatic) { @@ -577,8 +688,13 @@ 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 }); + MethodBuilder setMethod = _typeBuilder.DefineMethod(string.Concat("set_", propertyMemberAst.Name), getSetAttributes, null, new Type[] { type }); ILGenerator setIlGen = setMethod.GetILGenerator(); if (hasValidateAttributes) @@ -588,10 +704,11 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type setIlGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); // load current Type on stack setIlGen.Emit(OpCodes.Ldstr, propertyMemberAst.Name); // load name of Property setIlGen.Emit(propertyMemberAst.IsStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1); // load set value - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { setIlGen.Emit(OpCodes.Box, type); } + setIlGen.Emit(OpCodes.Call, CachedReflectionInfo.ClassOps_ValidateSetProperty); } @@ -606,8 +723,14 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type setIlGen.Emit(OpCodes.Ldarg_1); setIlGen.Emit(OpCodes.Stfld, backingField); } + 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); @@ -659,7 +782,9 @@ private bool CheckForDuplicateOverload(FunctionMemberAst functionMemberAst, Type !functionMemberAst.IsConstructor) { _parser.ReportError(functionMemberAst.NameExtent ?? functionMemberAst.Extent, - () => ParserStrings.MemberAlreadyDefined, functionMemberAst.Name); + nameof(ParserStrings.MemberAlreadyDefined), + ParserStrings.MemberAlreadyDefined, + functionMemberAst.Name); return true; } } @@ -675,7 +800,7 @@ private Type[] GetParameterTypes(FunctionMemberAst functionMemberAst) var parameters = ((IParameterMetadataProvider)functionMemberAst).Parameters; if (parameters == null) { - return PSTypeExtensions.EmptyTypes; + return Type.EmptyTypes; } bool anyErrors = false; @@ -688,17 +813,24 @@ private Type[] GetParameterTypes(FunctionMemberAst functionMemberAst) : typeof(object); if (paramType == null) { - _parser.ReportError(typeConstraint.Extent, () => ParserStrings.TypeNotFound, typeConstraint.TypeName.FullName); + _parser.ReportError(typeConstraint.Extent, + nameof(ParserStrings.TypeNotFound), + ParserStrings.TypeNotFound, + typeConstraint.TypeName.FullName); anyErrors = true; } - else if (paramType == typeof(void) || paramType.GetTypeInfo().IsGenericTypeDefinition) + else if (paramType == typeof(void) || paramType.IsGenericTypeDefinition) { - _parser.ReportError(typeConstraint.Extent, () => ParserStrings.TypeNotAllowedForParameter, + _parser.ReportError(typeConstraint.Extent, + nameof(ParserStrings.TypeNotAllowedForParameter), + ParserStrings.TypeNotAllowedForParameter, typeConstraint.TypeName.FullName); anyErrors = true; } + result[i] = paramType; } + return anyErrors ? null : result; } @@ -711,6 +843,7 @@ private bool MethodExistsOnBaseClassAndFinal(string methodName, Type[] parameter { return false; } + var mi = baseType.GetMethod(methodName, parameterTypes); return mi != null && mi.IsFinal; } @@ -723,6 +856,7 @@ private void DefineMethod(FunctionMemberAst functionMemberAst) // There must have been an error, just return return; } + if (CheckForDuplicateOverload(functionMemberAst, parameterTypes)) { return; @@ -736,11 +870,16 @@ private void DefineMethod(FunctionMemberAst functionMemberAst) var parameters = functionMemberAst.Parameters; if (parameters.Count > 0) { - _parser.ReportError(Parser.ExtentOf(parameters.First(), parameters.Last()), () => ParserStrings.StaticConstructorCantHaveParameters); + IScriptExtent errorExtent = Parser.ExtentOf(parameters[0], parameters.Last()); + _parser.ReportError(errorExtent, + nameof(ParserStrings.StaticConstructorCantHaveParameters), + ParserStrings.StaticConstructorCantHaveParameters); return; } + methodAttributes |= Reflection.MethodAttributes.Static; } + DefineConstructor(functionMemberAst, functionMemberAst.Attributes, functionMemberAst.IsHidden, methodAttributes, parameterTypes); return; } @@ -759,22 +898,29 @@ private void DefineMethod(FunctionMemberAst functionMemberAst) attributes |= Reflection.MethodAttributes.HideBySig; attributes |= Reflection.MethodAttributes.NewSlot; } + attributes |= Reflection.MethodAttributes.Virtual; } + var returnType = functionMemberAst.GetReturnType(); if (returnType == null) { - _parser.ReportError(functionMemberAst.ReturnType.Extent, () => ParserStrings.TypeNotFound, functionMemberAst.ReturnType.TypeName.FullName); + _parser.ReportError(functionMemberAst.ReturnType.Extent, + nameof(ParserStrings.TypeNotFound), + ParserStrings.TypeNotFound, + functionMemberAst.ReturnType.TypeName.FullName); return; } + var method = _typeBuilder.DefineMethod(functionMemberAst.Name, attributes, returnType, parameterTypes); DefineCustomAttributes(method, functionMemberAst.Attributes, _parser, AttributeTargets.Method); if (functionMemberAst.IsHidden) { method.SetCustomAttribute(s_hiddenCustomAttributeBuilder); } + var ilGenerator = method.GetILGenerator(); - DefineMethodBody(functionMemberAst, ilGenerator, GetMetaDataName(method.Name, parameterTypes.Count()), functionMemberAst.IsStatic, parameterTypes, returnType, + DefineMethodBody(functionMemberAst, ilGenerator, GetMetaDataName(method.Name, parameterTypes.Length), functionMemberAst.IsStatic, parameterTypes, returnType, (i, n) => method.DefineParameter(i, ParameterAttributes.None, n)); } @@ -789,6 +935,7 @@ private void DefineConstructor(IParameterMetadataProvider ipmp, ReadOnlyCollecti { ctor.SetCustomAttribute(s_hiddenCustomAttributeBuilder); } + var ilGenerator = ctor.GetILGenerator(); if (!isStatic) @@ -802,11 +949,11 @@ private void DefineConstructor(IParameterMetadataProvider ipmp, ReadOnlyCollecti ilGenerator.Emit(OpCodes.Stfld, _sessionStateField); } - DefineMethodBody(ipmp, ilGenerator, GetMetaDataName(ctor.Name, parameterTypes.Count()), isStatic, parameterTypes, typeof(void), + DefineMethodBody(ipmp, ilGenerator, GetMetaDataName(ctor.Name, parameterTypes.Length), isStatic, parameterTypes, typeof(void), (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; @@ -822,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); @@ -855,16 +1002,18 @@ private void DefineMethodBody( ilGenerator.Emit(OpCodes.Ldloc, local); // load array EmitLdc(ilGenerator, i); // index to save at EmitLdarg(ilGenerator, j); // load argument (skipping this) - if (parameterTypes[i].GetTypeInfo().IsValueType) // value types must be boxed + if (parameterTypes[i].IsValueType) // value types must be boxed { ilGenerator.Emit(OpCodes.Box, parameterTypes[i]); } + ilGenerator.Emit(OpCodes.Stelem_Ref); // save the argument in the array // Set the parameter name, mostly for Get-Member // Parameters are indexed beginning with the number 1 for the first parameter parameterNameSetter(i + 1, parameters[i].Name.VariablePath.UserPath); } + ilGenerator.Emit(OpCodes.Ldloc, local); // load array } else @@ -881,6 +1030,7 @@ private void DefineMethodBody( { invokeHelper = typeof(ScriptBlockMemberMethodWrapper).GetMethod("InvokeHelperT", BindingFlags.Instance | BindingFlags.Public).MakeGenericMethod(returnType); } + ilGenerator.Emit(OpCodes.Tailcall); ilGenerator.EmitCall(OpCodes.Call, invokeHelper, null); ilGenerator.Emit(OpCodes.Ret); @@ -889,7 +1039,7 @@ private void DefineMethodBody( } } - private class DefineEnumHelper + private sealed class DefineEnumHelper { private readonly Parser _parser; private readonly TypeDefinitionAst _enumDefinitionAst; @@ -964,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) @@ -1020,7 +1170,9 @@ internal static List Sort(List defineEnumHel { if (!result.Contains(helper)) { - parser.ReportError(helper._enumDefinitionAst.Extent, () => ParserStrings.CycleInEnumInitializers); + parser.ReportError(helper._enumDefinitionAst.Extent, + nameof(ParserStrings.CycleInEnumInitializers), + ParserStrings.CycleInEnumInitializers); } } } @@ -1034,11 +1186,52 @@ internal static List Sort(List defineEnumHel internal void DefineEnum() { + var typeConstraintAst = _enumDefinitionAst.BaseTypes.FirstOrDefault(); + var underlyingType = typeConstraintAst == null ? typeof(int) : typeConstraintAst.TypeName.GetReflectionType(); + var definedEnumerators = new HashSet(StringComparer.OrdinalIgnoreCase); - var enumBuilder = _moduleBuilder.DefineEnum(_typeName, Reflection.TypeAttributes.Public, typeof(int)); + var enumBuilder = _moduleBuilder.DefineEnum(_typeName, Reflection.TypeAttributes.Public, underlyingType); DefineCustomAttributes(enumBuilder, _enumDefinitionAst.Attributes, _parser, AttributeTargets.Enum); - int value = 0; + + dynamic value = 0; + dynamic maxValue = 0; + switch (Type.GetTypeCode(underlyingType)) + { + case TypeCode.Byte: + maxValue = byte.MaxValue; + break; + case TypeCode.Int16: + maxValue = short.MaxValue; + break; + case TypeCode.Int32: + maxValue = int.MaxValue; + break; + case TypeCode.Int64: + maxValue = long.MaxValue; + break; + case TypeCode.SByte: + maxValue = sbyte.MaxValue; + break; + case TypeCode.UInt16: + maxValue = ushort.MaxValue; + break; + case TypeCode.UInt32: + maxValue = uint.MaxValue; + break; + case TypeCode.UInt64: + maxValue = ulong.MaxValue; + break; + default: + _parser.ReportError( + typeConstraintAst.Extent, + nameof(ParserStrings.InvalidUnderlyingType), + ParserStrings.InvalidUnderlyingType, + underlyingType); + break; + } + bool valueTooBig = false; + foreach (var member in _enumDefinitionAst.Members) { var enumerator = (PropertyMemberAst)member; @@ -1047,56 +1240,73 @@ internal void DefineEnum() object constValue; if (IsConstantValueVisitor.IsConstant(enumerator.InitialValue, out constValue, false, false)) { - if (constValue is int) - { - value = (int)constValue; - } - else + if (!LanguagePrimitives.TryConvertTo(constValue, underlyingType, out value)) { - if (!LanguagePrimitives.TryConvertTo(constValue, out value)) + if (constValue != null && + LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constValue.GetType()))) { - if (constValue != null && - LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constValue.GetType()))) - { - _parser.ReportError(enumerator.InitialValue.Extent, () => ParserStrings.EnumeratorValueTooLarge); - } - else - { - _parser.ReportError(enumerator.InitialValue.Extent, () => ParserStrings.CannotConvertValue, - ToStringCodeMethods.Type(typeof(int))); - } + _parser.ReportError( + enumerator.InitialValue.Extent, + nameof(ParserStrings.EnumeratorValueOutOfBounds), + ParserStrings.EnumeratorValueOutOfBounds, + ToStringCodeMethods.Type(underlyingType)); + } + else + { + _parser.ReportError( + enumerator.InitialValue.Extent, + nameof(ParserStrings.CannotConvertValue), + ParserStrings.CannotConvertValue, + ToStringCodeMethods.Type(underlyingType)); } } } else { - _parser.ReportError(enumerator.InitialValue.Extent, () => ParserStrings.EnumeratorValueMustBeConstant); + _parser.ReportError( + enumerator.InitialValue.Extent, + nameof(ParserStrings.EnumeratorValueMustBeConstant), + ParserStrings.EnumeratorValueMustBeConstant); } + + valueTooBig = value > maxValue; } - else if (valueTooBig) + + if (valueTooBig) { - _parser.ReportError(enumerator.Extent, () => ParserStrings.EnumeratorValueTooLarge); + _parser.ReportError( + enumerator.Extent, + nameof(ParserStrings.EnumeratorValueOutOfBounds), + ParserStrings.EnumeratorValueOutOfBounds, + ToStringCodeMethods.Type(underlyingType)); } if (definedEnumerators.Contains(enumerator.Name)) { - _parser.ReportError(enumerator.Extent, () => ParserStrings.MemberAlreadyDefined, enumerator.Name); + _parser.ReportError( + enumerator.Extent, + nameof(ParserStrings.MemberAlreadyDefined), + ParserStrings.MemberAlreadyDefined, + enumerator.Name); } - else + else if (value != null) { + value = Convert.ChangeType(value, underlyingType); definedEnumerators.Add(enumerator.Name); enumBuilder.DefineLiteral(enumerator.Name, value); - if (value < int.MaxValue) - { - value += 1; - valueTooBig = false; - } - else - { - valueTooBig = true; - } + } + + if (value < maxValue) + { + value += 1; + valueTooBig = false; + } + else + { + valueTooBig = true; } } + _enumDefinitionAst.Type = enumBuilder.CreateTypeInfo().AsType(); } } @@ -1104,9 +1314,10 @@ internal void DefineEnum() private static IEnumerable GetAssemblyAttributeBuilders(string scriptFile) { var ctor = typeof(DynamicClassImplementationAssemblyAttribute).GetConstructor(Type.EmptyTypes); - var emptyArgs = Utils.EmptyArray(); + var emptyArgs = Array.Empty(); - if (string.IsNullOrEmpty(scriptFile)) { + if (string.IsNullOrEmpty(scriptFile)) + { yield return new CustomAttributeBuilder(ctor, emptyArgs); yield break; } @@ -1116,11 +1327,11 @@ private static IEnumerable GetAssemblyAttributeBuilders( var propertyArgs = new object[] { scriptFile }; yield return new CustomAttributeBuilder(ctor, emptyArgs, - propertyInfo, propertyArgs, Utils.EmptyArray(), emptyArgs); - + propertyInfo, propertyArgs, Array.Empty(), emptyArgs); } private static int counter = 0; + internal static Assembly DefineTypes(Parser parser, Ast rootAst, TypeDefinitionAst[] typeDefinitions) { Diagnostics.Assert(rootAst.Parent == null, "Caller should only define types from the root ast"); @@ -1142,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)); @@ -1205,8 +1415,11 @@ internal static Assembly DefineTypes(Parser parser, Ast rootAst, TypeDefinitionA // // Presumably this catch could go away when we will not create Type at parse time. // Error checking should be moved/added to semantic checks. - parser.ReportError(helper._typeDefinitionAst.Extent, () => ParserStrings.TypeCreationError, - helper._typeBuilder.Name, e.Message); + parser.ReportError(helper._typeDefinitionAst.Extent, + nameof(ParserStrings.TypeCreationError), + ParserStrings.TypeCreationError, + helper._typeBuilder.Name, + e.Message); } } @@ -1229,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) { @@ -1241,6 +1454,7 @@ private static string GetClassNameInAssembly(TypeDefinitionAst typeDefinitionAst nameParts.Add("<" + parent.Extent.Text.GetHashCode().ToString("x", CultureInfo.InvariantCulture) + ">"); } } + parent = parent.Parent; } @@ -1251,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 @@ -1272,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 }; @@ -1289,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 b347cbd3e03..1592d2e7e7d 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -9,14 +8,14 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Linq.Expressions; 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; -#if !CORECLR -using Microsoft.CodeAnalysis; -#endif +using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language { @@ -43,8 +42,8 @@ public sealed class Parser private bool _inConfiguration; private ParseMode _parseMode; - //private bool _v3FeatureUsed; internal string _fileName; + internal bool ProduceV2Tokens { get; set; } internal const string VERBATIM_ARGUMENT = "--%"; @@ -74,7 +73,7 @@ public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out var parser = new Parser(); if (!string.IsNullOrEmpty(fileName) && fileName.Length > scriptSchemaExtension.Length && fileName.EndsWith(scriptSchemaExtension, StringComparison.OrdinalIgnoreCase)) { - parser._keywordModuleName = Path.GetFileName(fileName.Substring(0, fileName.Length - scriptSchemaExtension.Length)); + parser._keywordModuleName = Path.GetFileName(fileName.AsSpan(0, fileName.Length - scriptSchemaExtension.Length)).ToString(); parseDscResource = true; } @@ -89,7 +88,7 @@ public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out var emptyExtent = new EmptyScriptExtent(); var errorMsg = string.Format(CultureInfo.CurrentCulture, ParserStrings.FileReadError, e.Message); errors = new[] { new ParseError(emptyExtent, "FileReadError", errorMsg) }; - tokens = Utils.EmptyArray(); + tokens = Array.Empty(); return new ScriptBlockAst(emptyExtent, null, new StatementBlockAst(emptyExtent, null, null), false); } @@ -101,6 +100,7 @@ public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out { DynamicKeyword.Push(); } + result = parser.Parse(fileName, scriptContents, tokenList, out errors, ParseMode.Default); } catch (Exception e) @@ -118,8 +118,8 @@ public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out tokens = tokenList.ToArray(); return result; } - private string _keywordModuleName; + private string _keywordModuleName; /// /// Parse input that does not come from a file. @@ -143,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; @@ -202,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 @@ -214,7 +216,9 @@ private ScriptBlockAst ParseTask(string fileName, string input, List toke } else { - ReportError(_tokenizer.CurrentExtent(), () => ParserStrings.ScriptTooComplicated); + ReportError(_tokenizer.CurrentExtent(), + nameof(ParserStrings.ScriptTooComplicated), + ParserStrings.ScriptTooComplicated); } } @@ -223,7 +227,7 @@ private ScriptBlockAst ParseTask(string fileName, string input, List toke } // This helper routine is used from the runtime to convert a string to a number. - internal static object ScanNumber(string str, Type toType) + internal static object ScanNumber(string str, Type toType, bool shouldTryCoercion = true) { str = str.Trim(); if (str.Length == 0) @@ -247,11 +251,17 @@ internal static object ScanNumber(string str, Type toType) if (token == null || !tokenizer.IsAtEndOfScript(token.Extent)) { - // We call ConvertTo, primarily because we expect it will throw an exception, - // but it's possible it could succeed, e.g. if the string had commas, our lexer - // will fail, but Convert.ChangeType could succeed. - - return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture); + if (shouldTryCoercion) + { + // We call ConvertTo, primarily because we expect it will throw an exception, + // but it's possible it could succeed, e.g. if the string had commas, our lexer + // will fail, but Convert.ChangeType could succeed. + return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture); + } + else + { + throw new ParseException(); + } } return token.Value; @@ -268,14 +278,14 @@ 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; } + return result; } @@ -290,6 +300,7 @@ internal static ExpressionAst ScanString(string str) { throw new ParseException(parser.ErrorList.ToArray()); } + return ast; } @@ -330,7 +341,6 @@ internal void SetPreviousFirstLastToken(ExecutionContext context) } } - //public bool V3FeatureUsed { get { return _v3FeatureUsed; } } internal List ErrorList { get; } #region Utilities @@ -340,18 +350,7 @@ private void SkipNewlines() if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine) { _ungotToken = null; - _tokenizer.SkipNewlines(false, false); - } - } - - // Same as SkipNewlines, but remembers is used when we skip lines differently in - // V3. - private void V3SkipNewlines() - { - if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine) - { - _ungotToken = null; - _tokenizer.SkipNewlines(false, true); + _tokenizer.SkipNewlines(skipSemis: false); } } @@ -360,7 +359,7 @@ private void SkipNewlinesAndSemicolons() if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine || _ungotToken.Kind == TokenKind.Semi) { _ungotToken = null; - _tokenizer.SkipNewlines(true, false); + _tokenizer.SkipNewlines(skipSemis: true); } } @@ -383,8 +382,10 @@ private void SyncOnError(bool consumeClosingToken, params TokenKind[] syncTokens { UngetToken(token); } + return; } + break; case TokenKind.LCurly: ++curlies; break; @@ -396,8 +397,10 @@ private void SyncOnError(bool consumeClosingToken, params TokenKind[] syncTokens { UngetToken(token); } + return; } + break; case TokenKind.LBracket: ++braces; break; @@ -409,12 +412,14 @@ private void SyncOnError(bool consumeClosingToken, params TokenKind[] syncTokens { UngetToken(token); } + return; } + break; case TokenKind.EndOfInput: - // Never consume , but return it so caller + // Never consume , but return it to caller UngetToken(token); return; } @@ -436,8 +441,7 @@ private Token NextToken() private Token PeekToken() { Token token = _ungotToken ?? _tokenizer.NextToken(); - if (_ungotToken == null) - _ungotToken = token; + _ungotToken ??= token; return token; } @@ -465,6 +469,7 @@ private Token NextLBracket() // If _ungotToken is not null, we're in some sort of error state, don't return the token. return null; } + return _tokenizer.GetLBracket(); } @@ -475,6 +480,7 @@ private StringToken GetVerbatimCommandArgumentToken() _ungotToken = null; return _tokenizer.GetVerbatimCommandArgument(); } + return null; } @@ -525,6 +531,7 @@ private void SetTokenizerMode(TokenizerMode mode) } #endif } + _tokenizer.Mode = mode; } @@ -547,12 +554,6 @@ private static bool IsSpecificParameter(Token token, string parameter) return parameter.StartsWith(paramToken.ParameterName, StringComparison.OrdinalIgnoreCase); } - internal void NoteV3FeatureUsed() - { - //_v3FeatureUsed = true; - } - - internal void RequireStatementTerminator() { var terminatorToken = PeekToken(); @@ -562,7 +563,9 @@ internal void RequireStatementTerminator() } else if (terminatorToken.Kind != TokenKind.EndOfInput) { - ReportIncompleteInput(terminatorToken.Extent, () => ParserStrings.MissingStatementTerminator); + ReportIncompleteInput(terminatorToken.Extent, + nameof(ParserStrings.MissingStatementTerminator), + ParserStrings.MissingStatementTerminator); } } @@ -605,6 +608,7 @@ internal static IScriptExtent LastCharacterOf(IScriptExtent extent) { offset = 0; } + return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset); } @@ -636,21 +640,32 @@ internal static IScriptExtent ExtentFromFirstOf(params object[] objs) return (IScriptExtent)obj; } } + Diagnostics.Assert(false, "One of the objects must not be null"); return PositionUtilities.EmptyExtent; } internal static IScriptExtent ExtentOf(Token first, Token last) { return ExtentOf(first.Extent, last.Extent); } + internal static IScriptExtent ExtentOf(Ast first, Ast last) { return ExtentOf(first.Extent, last.Extent); } + internal static IScriptExtent ExtentOf(Ast first, Token last) { return ExtentOf(first.Extent, last.Extent); } + internal static IScriptExtent ExtentOf(Token first, Ast last) { return ExtentOf(first.Extent, last.Extent); } + internal static IScriptExtent ExtentOf(IScriptExtent first, Ast last) { return ExtentOf(first, last.Extent); } + internal static IScriptExtent ExtentOf(IScriptExtent first, Token last) { return ExtentOf(first, last.Extent); } + internal static IScriptExtent ExtentOf(Ast first, IScriptExtent last) { return ExtentOf(first.Extent, last); } + internal static IScriptExtent ExtentOf(Token first, IScriptExtent last) { return ExtentOf(first.Extent, last); } - //private static IScriptExtent Before(Ast ast) { return Before(ast.Extent); } + // private static IScriptExtent Before(Ast ast) { return Before(ast.Extent); } + internal static IScriptExtent Before(Token token) { return Before(token.Extent); } + internal static IScriptExtent After(Ast ast) { return After(ast.Extent); } + internal static IScriptExtent After(Token token) { return After(token.Extent); } private static IEnumerable GetNestedErrorAsts(params object[] asts) @@ -687,16 +702,16 @@ private static IEnumerable GetNestedErrorAsts(params object[] asts) } /// - /// Parses the specified constant hashtable string into a Hashtable object + /// Parses the specified constant hashtable string into a Hashtable object. /// - /// The Hashtable string - /// the Hashtable object + /// The Hashtable string. + /// The Hashtable object. /// internal static bool TryParseAsConstantHashtable(string input, out Hashtable result) { result = null; - if (String.IsNullOrWhiteSpace(input)) + if (string.IsNullOrWhiteSpace(input)) { return false; } @@ -705,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; } @@ -721,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; } @@ -733,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; } @@ -762,12 +776,12 @@ private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter) private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter, StatementAst predefinedStatementAst) { - //G script-block: - //G using-statements:opt param-block:opt statement-terminators:opt script-block-body:opt - //G - //G using-statements: - //G using-statement - //G using-statements using-statement + // G script-block: + // G using-statements:opt param-block:opt statement-terminators:opt script-block-body:opt + // G + // G using-statements: + // G using-statement + // G using-statements using-statement // We could set the mode here, but we can avoid rescanning keywords if the caller // sets the mode before skipping newlines. @@ -788,6 +802,7 @@ private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter, StatementAst // mean something different, such as a type literal expression, or a cast.) Resync(restorePoint); } + SkipNewlinesAndSemicolons(); return ScriptBlockBodyRule(lCurly, usingStatements, paramBlock, isFilter, predefinedStatementAst); @@ -805,10 +820,8 @@ 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. // We ignore it here, because error already reported to the parser. @@ -816,6 +829,7 @@ private List UsingStatementsRule() { result.Add(usingStatement); } + continue; } @@ -830,9 +844,9 @@ private List UsingStatementsRule() private ParamBlockAst ParamBlockRule() { - //G param-block: - //G new-lines:opt attribute-list:opt new-lines:opt 'param' new-lines:opt - //G '(' parameter-list:opt new-lines:opt ')' + // G param-block: + // G new-lines:opt attribute-list:opt new-lines:opt 'param' new-lines:opt + // G '(' parameter-list:opt new-lines:opt ')' SkipNewlines(); List candidateAttributes = AttributeListRule(false); @@ -851,7 +865,7 @@ private ParamBlockAst ParamBlockRule() { UngetToken(lparen); - //This is not an error, we'll end up trying to invoke a command named 'param'. + // This is not an error, we'll end up trying to invoke a command named 'param'. return null; } @@ -866,8 +880,9 @@ private ParamBlockAst ParamBlockRule() UngetToken(rParen); endExtent = Before(rParen); - ReportIncompleteInput(After(parameters != null && parameters.Any() ? parameters.Last().Extent : lparen.Extent), - () => ParserStrings.MissingEndParenthesisInFunctionParameterList); + ReportIncompleteInput(After(parameters != null && parameters.Count > 0 ? parameters.Last().Extent : lparen.Extent), + nameof(ParserStrings.MissingEndParenthesisInFunctionParameterList), + ParserStrings.MissingEndParenthesisInFunctionParameterList); } List attributes = new List(); @@ -885,7 +900,10 @@ private ParamBlockAst ParamBlockRule() // ErrorRecovery: nothing to do, this is a semantic error that is caught in the parser // because the ast only allows attributes, no type constraints. - ReportError(attr.Extent, () => ParserStrings.TypeNotAllowedBeforeParam, attr.TypeName.FullName); + ReportError(attr.Extent, + nameof(ParserStrings.TypeNotAllowedBeforeParam), + ParserStrings.TypeNotAllowedBeforeParam, + attr.TypeName.FullName); } } } @@ -895,9 +913,9 @@ private ParamBlockAst ParamBlockRule() private List ParameterListRule() { - //G parameter-list: - //G script-parameter - //G parameter-list new-lines:opt ',' script-parameter + // G parameter-list: + // G script-parameter + // G parameter-list new-lines:opt ',' script-parameter List parameters = new List(); Token commaToken = null; @@ -910,11 +928,15 @@ private List ParameterListRule() { // ErrorRecovery: ?? - ReportIncompleteInput(After(commaToken), () => ParserStrings.MissingExpressionAfterToken, - commaToken.Kind.Text()); + ReportIncompleteInput(After(commaToken), + nameof(ParserStrings.MissingExpressionAfterToken), + ParserStrings.MissingExpressionAfterToken, + commaToken.Kind.Text()); } + break; } + parameters.Add(parameter); SkipNewlines(); commaToken = PeekToken(); @@ -922,6 +944,7 @@ private List ParameterListRule() { break; } + SkipToken(); } @@ -930,10 +953,10 @@ private List ParameterListRule() private ParameterAst ParameterRule() { - //G script-parameter: - //G new-lines:opt attribute-list:opt new-lines:opt variable script-parameter-default:opt - //G script-parameter-default: - //G new-lines:opt '=' new-lines:opt expression + // G script-parameter: + // G new-lines:opt attribute-list:opt new-lines:opt variable script-parameter-default:opt + // G script-parameter-default: + // G new-lines:opt '=' new-lines:opt expression List attributes; VariableToken variableToken; @@ -957,7 +980,9 @@ private ParameterAst ParameterRule() { // ErrorRecovery: skip to closing paren because returning null signals the last parameter. - ReportIncompleteInput(After(attributes.Last()), () => ParserStrings.InvalidFunctionParameter); + ReportIncompleteInput(After(attributes.Last()), + nameof(ParserStrings.InvalidFunctionParameter), + ParserStrings.InvalidFunctionParameter); SyncOnError(true, TokenKind.RParen); // Even though we don't have a complete parameter, we do have attributes. Intellisense @@ -965,6 +990,7 @@ private ParameterAst ParameterRule() var extent = ExtentOf(attributes[0].Extent, attributes[attributes.Count - 1].Extent); return new ParameterAst(extent, new VariableExpressionAst(extent, "__error__", false), attributes, null); } + return null; } @@ -979,7 +1005,10 @@ private ParameterAst ParameterRule() defaultValue = ExpressionRule(); if (defaultValue == null) { - ReportIncompleteInput(After(equalsToken), () => ParserStrings.MissingExpressionAfterToken, equalsToken.Kind.Text()); + ReportIncompleteInput(After(equalsToken), + nameof(ParserStrings.MissingExpressionAfterToken), + ParserStrings.MissingExpressionAfterToken, + equalsToken.Kind.Text()); } } } @@ -997,9 +1026,9 @@ private ParameterAst ParameterRule() private List AttributeListRule(bool inExpressionMode) { - //G attribute-list: - //G attribute - //G attribute-list attribute + // G attribute-list: + // G attribute + // G attribute-list attribute List attributes = new List(); AttributeBaseAst attribute = AttributeRule(); @@ -1010,6 +1039,7 @@ private List AttributeListRule(bool inExpressionMode) { SkipNewlines(); } + attribute = AttributeRule(); } @@ -1023,10 +1053,10 @@ private List AttributeListRule(bool inExpressionMode) private AttributeBaseAst AttributeRule() { - //G attribute: - //G '[' attribute-name '(' attribute-arguments ')' ']' - //G attribute-name: - //G type-spec + // G attribute: + // G '[' attribute-name '(' attribute-arguments ')' ']' + // G attribute-name: + // G type-spec var lBracket = NextLBracket(); if (lBracket == null) @@ -1034,7 +1064,7 @@ private AttributeBaseAst AttributeRule() return null; } - V3SkipNewlines(); + SkipNewlines(); Token firstTypeNameToken; ITypeName typeName = TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out firstTypeNameToken); @@ -1043,7 +1073,9 @@ private AttributeBaseAst AttributeRule() // ErrorRecovery: Return null so we stop looking for attributes. Resync(lBracket); // TypeNameRule might have consumed some tokens - ReportIncompleteInput(After(lBracket), () => ParserStrings.MissingTypename); + ReportIncompleteInput(After(lBracket), + nameof(ParserStrings.MissingTypename), + ParserStrings.MissingTypename); return null; } @@ -1075,8 +1107,11 @@ private AttributeBaseAst AttributeRule() UngetToken(rParen); rParen = null; - ReportIncompleteInput(After(lastItemExtent), () => ParserStrings.MissingEndParenthesisInExpression); + ReportIncompleteInput(After(lastItemExtent), + nameof(ParserStrings.MissingEndParenthesisInExpression), + ParserStrings.MissingEndParenthesisInExpression); } + SkipNewlines(); Token rBracket = NextToken(); if (rBracket.Kind != TokenKind.RBracket) @@ -1088,7 +1123,9 @@ private AttributeBaseAst AttributeRule() // Don't bother reporting a missing ']' if we reported a missing ')'. if (rParen != null) { - ReportIncompleteInput(After(rParen), () => ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); + ReportIncompleteInput(After(rParen), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), + ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); } } @@ -1106,7 +1143,9 @@ private AttributeBaseAst AttributeRule() if (lParenOrRBracket.Kind != TokenKind.RBracket) { UngetToken(lParenOrRBracket); - ReportError(Before(lParenOrRBracket), () => ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); + ReportError(Before(lParenOrRBracket), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), + ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); lParenOrRBracket = null; } @@ -1117,12 +1156,12 @@ private void AttributeArgumentsRule(ICollection positionalArgumen ICollection namedArguments, ref IScriptExtent lastItemExtent) { - //G attribute-arguments: - //G attribute-argument - //G attribute-argument new-lines:opt ',' attribute-arguments - //G attribute-argument: - //G new-lines:opt expression - //G new-lines:opt property-name '=' new-lines:opt expression + // G attribute-arguments: + // G attribute-argument + // G attribute-argument new-lines:opt ',' attribute-arguments + // G attribute-argument: + // G new-lines:opt expression + // G new-lines:opt property-name '=' new-lines:opt expression bool oldDisableCommaOperator = _disableCommaOperator; Token commaToken = null; @@ -1153,10 +1192,14 @@ private void AttributeArgumentsRule(ICollection positionalArgumen // ErrorRecovery: ? IScriptExtent errorPosition = After(token); - ReportIncompleteInput(errorPosition, () => ParserStrings.MissingExpressionInNamedArgument); + ReportIncompleteInput( + errorPosition, + nameof(ParserStrings.MissingExpressionInNamedArgument), + ParserStrings.MissingExpressionInNamedArgument); expr = new ErrorExpressionAst(errorPosition); SyncOnError(true, TokenKind.Comma, TokenKind.RParen, TokenKind.RBracket, TokenKind.NewLine); } + lastItemExtent = expr.Extent; } else @@ -1165,7 +1208,6 @@ private void AttributeArgumentsRule(ICollection positionalArgumen // and record that it was defaulted for better error messages. expr = new ConstantExpressionAst(name.Extent, true); expressionOmitted = true; - NoteV3FeatureUsed(); } } else @@ -1179,7 +1221,10 @@ private void AttributeArgumentsRule(ICollection positionalArgumen { // ErrorRecovery: this is a semantic error, so just keep parsing. - ReportError(name.Extent, () => ParserStrings.DuplicateNamedArgument, name.Value); + ReportError(name.Extent, + nameof(ParserStrings.DuplicateNamedArgument), + ParserStrings.DuplicateNamedArgument, + name.Value); } else { @@ -1197,7 +1242,10 @@ private void AttributeArgumentsRule(ICollection positionalArgumen // ErrorRecovery: Pretend we saw the argument and keep going. IScriptExtent errorExtent = After(commaToken); - ReportIncompleteInput(errorExtent, () => ParserStrings.MissingExpressionAfterToken, + ReportIncompleteInput( + errorExtent, + nameof(ParserStrings.MissingExpressionAfterToken), + ParserStrings.MissingExpressionAfterToken, commaToken.Kind.Text()); positionalArguments.Add(new ErrorExpressionAst(errorExtent)); lastItemExtent = errorExtent; @@ -1222,37 +1270,37 @@ private void AttributeArgumentsRule(ICollection positionalArgumen private ITypeName TypeNameRule(bool allowAssemblyQualifiedNames, out Token firstTypeNameToken) { - //G type-spec: - //G array-type-name dimension:opt ']' - //G generic-type-name generic-type-arguments ']' - //G type-name - //G array-type-name: - //G type-name '[' - //G generic-type-name: - //G type-name '[' - //G dimension: - //G ',' - //G dimension ',' - //G generic-type-arguments: - //G type-spec - //G generic-type-arguments ',' type-spec - //G type-name: - //G namespace-type-name - //G namespace-type-name ',' assembly-name-spec - //G namespace-type-name: - //G nested-type-name - //G namespace-spec '.' nested-type-name - //G nested-type-name: - //G type-name-identifier - //G nested-type-name '+' type-name-identifier - //G namespace-spec: - //G type-name-identifier - //G type-name-identifier '.' type-name-identifier - //G type-name-identifier: - //G type-name-identifier-char - //G type-name-identifier type-name-identifier-char - //G type-name-identifier-char: - //G any letter, digit, '.', '`', or '_'. + // G type-spec: + // G array-type-name dimension:opt ']' + // G generic-type-name generic-type-arguments ']' + // G type-name + // G array-type-name: + // G type-name '[' + // G generic-type-name: + // G type-name '[' + // G dimension: + // G ',' + // G dimension ',' + // G generic-type-arguments: + // G type-spec + // G generic-type-arguments ',' type-spec + // G type-name: + // G namespace-type-name + // G namespace-type-name ',' assembly-name-spec + // G namespace-type-name: + // G nested-type-name + // G namespace-spec '.' nested-type-name + // G nested-type-name: + // G type-name-identifier + // G nested-type-name '+' type-name-identifier + // G namespace-spec: + // G type-name-identifier + // G type-name-identifier '.' type-name-identifier + // G type-name-identifier: + // G type-name-identifier-char + // G type-name-identifier type-name-identifier-char + // G type-name-identifier-char: + // G any letter, digit, '.', '`', or '_'. // The above grammar is specified by the CLR. We don't attempt to implement the above grammar precisely, e.g. // we would permit 'a+b.c'. The above grammar would disallow this. @@ -1292,32 +1340,38 @@ private ITypeName FinishTypeNameRule(Token typeName, bool unBracketedGenericArg // Array or generic SkipToken(); - V3SkipNewlines(); + SkipNewlines(); token = NextToken(); switch (token.Kind) { 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. if (token.Kind != TokenKind.EndOfInput) { - ReportError(token.Extent, () => ParserStrings.UnexpectedToken, token.Text); + ReportError(token.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + token.Text); SyncOnError(true, TokenKind.RBracket); } else { UngetToken(token); - ReportIncompleteInput(After(lbracket), () => ParserStrings.MissingTypename); + ReportIncompleteInput(After(lbracket), + nameof(ParserStrings.MissingTypename), + ParserStrings.MissingTypename); } + return new TypeName(typeName.Extent, typeName.Text); } } @@ -1328,9 +1382,12 @@ private ITypeName FinishTypeNameRule(Token typeName, bool unBracketedGenericArg string assemblyNameSpec = _tokenizer.GetAssemblyNameSpec(); if (string.IsNullOrWhiteSpace(assemblyNameSpec)) { - ReportError(After(token), () => ParserStrings.MissingAssemblyNameSpecification); + ReportError(After(token), + nameof(ParserStrings.MissingAssemblyNameSpecification), + ParserStrings.MissingAssemblyNameSpecification); return new TypeName(typeName.Extent, typeName.Text); } + return new TypeName(ExtentOf(typeName.Extent, _tokenizer.CurrentExtent()), typeName.Text, assemblyNameSpec); } @@ -1350,7 +1407,9 @@ private ITypeName GetSingleGenericArgument(Token firstToken) if (token.Kind != TokenKind.Identifier) { UngetToken(token); - ReportIncompleteInput(After(token), () => ParserStrings.MissingTypename); + ReportIncompleteInput(After(token), + nameof(ParserStrings.MissingTypename), + ParserStrings.MissingTypename); return new TypeName(firstToken.Extent, ":ErrorTypeName:"); } @@ -1363,14 +1422,16 @@ private ITypeName GetSingleGenericArgument(Token firstToken) // ErrorRecovery: pretend we saw the closing bracket. UngetToken(rBracket); - ReportIncompleteInput(Before(rBracket), () => ParserStrings.EndSquareBracketExpectedAtEndOfType); + ReportIncompleteInput(Before(rBracket), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType), + ParserStrings.EndSquareBracketExpectedAtEndOfType); } } 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(); @@ -1379,19 +1440,18 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok ITypeName typeName = GetSingleGenericArgument(firstToken); genericArguments.Add(typeName); - Token commaOrRBracketToken; - Token token; while (true) { - V3SkipNewlines(); - commaOrRBracketToken = NextToken(); - if (commaOrRBracketToken.Kind != TokenKind.Comma) + SkipNewlines(); + lastToken = NextToken(); + if (lastToken.Kind != TokenKind.Comma) { break; } - V3SkipNewlines(); - token = PeekToken(); + SkipNewlines(); + + Token token = PeekToken(); if (token.Kind == TokenKind.Identifier || token.Kind == TokenKind.LBracket) { SkipToken(); @@ -1399,48 +1459,68 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok } else { - ReportIncompleteInput(After(commaOrRBracketToken), () => ParserStrings.MissingTypename); - typeName = new TypeName(commaOrRBracketToken.Extent, ":ErrorTypeName:"); + ReportIncompleteInput( + After(lastToken), + nameof(ParserStrings.MissingTypename), + ParserStrings.MissingTypename); + 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), - () => ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); - commaOrRBracketToken = null; + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), + ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); + 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), () => ParserStrings.MissingAssemblyNameSpecification); + ReportError( + After(token), + nameof(ParserStrings.MissingAssemblyNameSpecification), + ParserStrings.MissingAssemblyNameSpecification); } else { openGenericType.AssemblyName = assemblyNameSpec; } } + return result; } - private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForAssemblyQualification, Token firstTokenAfterLBracket) + private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForAssemblyQualification, Token firstTokenAfterLBracket, bool unBracketedGenericArg) { while (true) { @@ -1458,12 +1538,33 @@ 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 ']'. UngetToken(token); - ReportError(After(lastComma), () => ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); + ReportError(After(lastComma), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), + ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); } elementType = new ArrayTypeName(ExtentOf(elementType.Extent, token.Extent), elementType, dim); @@ -1475,30 +1576,40 @@ private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForA case TokenKind.EndOfInput: UngetToken(firstTokenAfterLBracket); - ReportError(Before(firstTokenAfterLBracket), () => ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); + ReportError(Before(firstTokenAfterLBracket), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), + ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); break; default: // ErrorRecovery: sync to ']', and return null to avoid cascading errors. - ReportError(firstTokenAfterLBracket.Extent, () => ParserStrings.UnexpectedToken, firstTokenAfterLBracket.Text); + ReportError(firstTokenAfterLBracket.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + firstTokenAfterLBracket.Text); SyncOnError(true, TokenKind.RBracket); break; } 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(); if (string.IsNullOrEmpty(assemblyName)) { - ReportError(After(token), () => ParserStrings.MissingAssemblyNameSpecification); + ReportError(After(token), + nameof(ParserStrings.MissingAssemblyNameSpecification), + ParserStrings.MissingAssemblyNameSpecification); } else { typeForAssemblyQualification.AssemblyName = assemblyName; } + break; } @@ -1529,7 +1640,10 @@ private bool CompleteScriptBlockBody(Token lCurly, ref IScriptExtent bodyExtent, UngetToken(rCurly); endScriptBlock = bodyExtent ?? lCurly.Extent; - ReportIncompleteInput(lCurly.Extent, rCurly.Extent, () => ParserStrings.MissingEndCurlyBrace); + ReportIncompleteInput(lCurly.Extent, + rCurly.Extent, + nameof(ParserStrings.MissingEndCurlyBrace), + ParserStrings.MissingEndCurlyBrace); } else { @@ -1541,6 +1655,7 @@ private bool CompleteScriptBlockBody(Token lCurly, ref IScriptExtent bodyExtent, bodyExtent = ExtentOf(After(lCurly), Before(rCurly)); } } + fullBodyExtent = ExtentOf(lCurly, endScriptBlock); } else @@ -1553,7 +1668,10 @@ private bool CompleteScriptBlockBody(Token lCurly, ref IScriptExtent bodyExtent, // ErrorRecovery: eat the unexpected token, and continue parsing to find more // of the script. - ReportError(token.Extent, () => ParserStrings.UnexpectedToken, token.Text); + ReportError(token.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + token.Text); return false; } } @@ -1563,9 +1681,9 @@ private bool CompleteScriptBlockBody(Token lCurly, ref IScriptExtent bodyExtent, private ScriptBlockAst ScriptBlockBodyRule(Token lCurly, List usingStatements, ParamBlockAst paramBlockAst, bool isFilter, StatementAst predefinedStatementAst) { - //G script-block-body: - //G named-block-list - //G statement-list + // G script-block-body: + // G named-block-list + // G statement-list Token token = PeekToken(); @@ -1580,7 +1698,8 @@ private ScriptBlockAst ScriptBlockBodyRule(Token lCurly, List { statements.Add(predefinedStatementAst); } - IScriptExtent statementListExtent = paramBlockAst != null ? paramBlockAst.Extent : null; + + IScriptExtent statementListExtent = paramBlockAst?.Extent; IScriptExtent scriptBlockExtent; while (true) @@ -1607,23 +1726,24 @@ private ScriptBlockAst ScriptBlockBodyRule(Token lCurly, List private ScriptBlockAst NamedBlockListRule(Token lCurly, List usingStatements, ParamBlockAst paramBlockAst) { - //G named-block-list: - //G named-block - //G named-block-list named-block - //G named-block: - //G statement-terminators:opt block-name statement-block - //G block-name: one of - //G 'dynamicparam' 'begin' 'process' 'end' + // G named-block-list: + // G named-block + // G named-block-list named-block + // G named-block: + // G statement-terminators:opt block-name statement-block + // G block-name: one of + // G 'dynamicparam' 'begin' 'process' 'end' NamedBlockAst dynamicParamBlock = null; 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; + IScriptExtent extent = null; + IScriptExtent scriptBlockExtent = null; while (true) { @@ -1631,20 +1751,43 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List switch (blockNameToken.Kind) { default: + // Next token is unexpected. + // ErrorRecovery: if 'lCurly' is present, pretend we saw a closing curly; otherwise, eat the unexpected token. + if (lCurly != null) + { + UngetToken(blockNameToken); + scriptBlockExtent = ExtentOf(startExtent, endExtent); + } + else + { + // If "lCurly == null", then it's a ps1/psm1 file, and thus the extent is the whole file. + scriptBlockExtent = _tokenizer.GetScriptExtent(); + } + + // Report error about the unexpected token. + ReportError(blockNameToken.Extent, + nameof(ParserStrings.MissingNamedBlocks), + ParserStrings.MissingNamedBlocks, + blockNameToken.Text); + goto return_script_block_ast; + + case TokenKind.RCurly: + case TokenKind.EndOfInput: + // If the next token is RCurly or , handle it in 'CompleteScriptBlockBody'. UngetToken(blockNameToken); + extent = ExtentOf(startExtent, endExtent); goto finished_named_block_list; case TokenKind.Dynamicparam: 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; StatementBlockAst statementBlock = StatementBlockRule(); @@ -1654,8 +1797,10 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List // ErrorRecovery: Eat the block name and keep going, there might be a valid block next. ReportIncompleteInput(After(blockNameToken.Extent), - () => ParserStrings.MissingNamedStatementBlock, blockNameToken.Kind.Text()); - statementBlock = new StatementBlockAst(blockNameToken.Extent, Utils.EmptyArray(), null); + nameof(ParserStrings.MissingNamedStatementBlock), + ParserStrings.MissingNamedStatementBlock, + blockNameToken.Kind.Text()); + statementBlock = new StatementBlockAst(blockNameToken.Extent, Array.Empty(), null); } else { @@ -1675,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); @@ -1684,25 +1833,33 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List // ErrorRecovery: this is a semantic error, we can keep parsing w/o trouble. ReportError(extent, - () => ParserStrings.DuplicateScriptCommandClause, blockNameToken.Kind.Text()); + nameof(ParserStrings.DuplicateScriptCommandClause), + ParserStrings.DuplicateScriptCommandClause, + blockNameToken.Kind.Text()); } SkipNewlinesAndSemicolons(); } - finished_named_block_list: - IScriptExtent scriptBlockExtent; - extent = ExtentOf(startExtent, endExtent); + finished_named_block_list: CompleteScriptBlockBody(lCurly, ref extent, out scriptBlockExtent); - return new ScriptBlockAst(scriptBlockExtent, usingStatements, paramBlockAst, beginBlock, processBlock, endBlock, + return_script_block_ast: + return new ScriptBlockAst( + scriptBlockExtent, + usingStatements, + paramBlockAst, + beginBlock, + processBlock, + endBlock, + cleanBlock, dynamicParamBlock); } private StatementBlockAst StatementBlockRule() { - //G statement-block: - //G new-lines:opt '{' statement-list:opt new-lines:opt '}' + // G statement-block: + // G new-lines:opt '{' statement-list:opt new-lines:opt '}' SkipNewlines(); Token lCurly = NextToken(); @@ -1724,7 +1881,10 @@ private StatementBlockAst StatementBlockRule() UngetToken(rCurly); endBlock = statementListExtent ?? lCurly.Extent; - ReportIncompleteInput(lCurly.Extent, rCurly.Extent, () => ParserStrings.MissingEndCurlyBrace); + ReportIncompleteInput(lCurly.Extent, + rCurly.Extent, + nameof(ParserStrings.MissingEndCurlyBrace), + ParserStrings.MissingEndCurlyBrace); } else { @@ -1736,9 +1896,9 @@ private StatementBlockAst StatementBlockRule() private IScriptExtent StatementListRule(List statements, List traps) { - //G statement-list: - //G statement - //G statement-list statement + // G statement-list: + // G statement + // G statement-list statement StatementAst firstStatement = null; StatementAst lastStatement = null; @@ -1767,10 +1927,8 @@ private IScriptExtent StatementListRule(List statements, List statements, ListA statement ast. Never returns null, always returns PipelineAst.EmptyPipeline if there was no statement. private StatementAst StatementRule() { - //G statement: - //G if-statement - //G label:opt labeled-statement - //G function-statement - //G flow-control-statement statement-terminator - //G trap-statement - //G try-statement - //G data-statement - //G pipeline statement-terminator - //G - //G labeled-statement: - //G switch-statement - //G foreach-statement - //G for-statement - //G while-statement - //G do-statement - //G - //G flow-control-statement: - //G 'break' label-expression:opt - //G 'continue' label-expression:opt - //G 'throw' pipeline:opt - //G 'return' pipeline:opt - //G 'exit' pipeline:opt - //G - //G statement-terminator: - //G ';' - //G new-line-character + // G statement: + // G if-statement + // G label:opt labeled-statement + // G function-statement + // G flow-control-statement statement-terminator + // G trap-statement + // G try-statement + // G data-statement + // G pipeline-chain statement-terminator + // G + // G labeled-statement: + // G switch-statement + // G foreach-statement + // G for-statement + // G while-statement + // G do-statement + // G + // G flow-control-statement: + // G 'break' label-expression:opt + // G 'continue' label-expression:opt + // G 'throw' pipeline:opt + // G 'return' pipeline:opt + // G 'exit' pipeline:opt + // G + // G statement-terminator: + // G ';' + // G new-line-character RuntimeHelpers.EnsureSufficientExecutionStack(); int restorePoint = 0; @@ -1844,15 +2002,19 @@ private StatementAst StatementRule() } else { - ReportError(attributes[0].Extent, () => ParserStrings.UnexpectedAttribute, + ReportError(attributes[0].Extent, + nameof(ParserStrings.UnexpectedAttribute), + ParserStrings.UnexpectedAttribute, attributes[0].TypeName.FullName); } } else if ((token.TokenFlags & TokenFlags.Keyword) != 0) { - foreach (var attr in attributes.Where(attr => !(attr is AttributeAst))) + foreach (var attr in attributes.Where(static attr => attr is not AttributeAst)) { - ReportError(attr.Extent, () => ParserStrings.TypeNotAllowedBeforeStatement, + ReportError(attr.Extent, + nameof(ParserStrings.TypeNotAllowedBeforeStatement), + ParserStrings.TypeNotAllowedBeforeStatement, attr.TypeName.FullName); break; } @@ -1865,7 +2027,6 @@ private StatementAst StatementRule() } } - switch (token.Kind) { case TokenKind.If: @@ -1920,12 +2081,15 @@ 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: case TokenKind.Var: - ReportError(token.Extent, () => ParserStrings.ReservedKeywordNotAllowed, token.Kind.Text()); + ReportError(token.Extent, + nameof(ParserStrings.ReservedKeywordNotAllowed), + ParserStrings.ReservedKeywordNotAllowed, + token.Kind.Text()); statement = new ErrorStatementAst(token.Extent); break; case TokenKind.Label: @@ -1936,13 +2100,14 @@ private StatementAst StatementRule() if (attributes != null) { Resync(restorePoint); - statement = PipelineRule(); + statement = PipelineChainRule(); } else { UngetToken(token); statement = null; } + break; case TokenKind.Else: case TokenKind.ElseIf: @@ -1956,6 +2121,7 @@ private StatementAst StatementRule() SkipNewlines(); return StatementRule(); } + goto default; case TokenKind.DynamicKeyword: DynamicKeyword keywordData = DynamicKeyword.GetKeyword(token.Text); @@ -1970,7 +2136,9 @@ private StatementAst StatementRule() case TokenKind.Using: statement = UsingStatementRule(token); // Report an error - usings must appear before anything else in the script, but parse it anyway - ReportError(statement.Extent, () => ParserStrings.UsingMustBeAtStartOfScript); + ReportError(statement.Extent, + nameof(ParserStrings.UsingMustBeAtStartOfScript), + ParserStrings.UsingMustBeAtStartOfScript); break; default: @@ -1982,7 +2150,8 @@ private StatementAst StatementRule() { UngetToken(token); } - statement = PipelineRule(); + + statement = PipelineChainRule(); break; } @@ -2022,12 +2191,12 @@ private StringConstantExpressionAst SimpleNameRule(out Token token) private ExpressionAst LabelOrKeyRule() { - //G label-expression: - //G simple-name - //G unary-expression - //G key-expression: - //G simple-name - //G unary-expression + // G label-expression: + // G simple-name + // G unary-expression + // G key-expression: + // G simple-name + // G unary-expression var simpleName = SimpleNameRule(); if (simpleName != null) @@ -2049,6 +2218,7 @@ private ExpressionAst LabelOrKeyRule() { _disableCommaOperator = disableCommaOperator; } + if (labelExpr != null) { return labelExpr; @@ -2065,8 +2235,8 @@ private ExpressionAst LabelOrKeyRule() private BreakStatementAst BreakStatementRule(Token breakToken) { - //G flow-control-statement: - //G 'break' label-expression:opt + // G flow-control-statement: + // G 'break' label-expression:opt ExpressionAst labelExpr = LabelOrKeyRule(); IScriptExtent extent = (labelExpr != null) @@ -2078,8 +2248,8 @@ private BreakStatementAst BreakStatementRule(Token breakToken) private ContinueStatementAst ContinueStatementRule(Token continueToken) { - //G flow-control-statement: - //G 'continue' label-expression:opt + // G flow-control-statement: + // G 'continue' label-expression:opt ExpressionAst labelExpr = LabelOrKeyRule(); IScriptExtent extent = (labelExpr != null) @@ -2091,10 +2261,10 @@ private ContinueStatementAst ContinueStatementRule(Token continueToken) private ReturnStatementAst ReturnStatementRule(Token token) { - //G flow-control-statement: - //G 'return' pipeline:opt + // G flow-control-statement: + // G 'return' pipeline-chain:opt - PipelineBaseAst pipeline = PipelineRule(); + PipelineBaseAst pipeline = PipelineChainRule(); IScriptExtent extent = (pipeline != null) ? ExtentOf(token, pipeline) : token.Extent; @@ -2103,10 +2273,10 @@ private ReturnStatementAst ReturnStatementRule(Token token) private ExitStatementAst ExitStatementRule(Token token) { - //G flow-control-statement: - //G 'exit' pipeline:opt + // G flow-control-statement: + // G 'exit' pipeline-chain:opt - PipelineBaseAst pipeline = PipelineRule(); + PipelineBaseAst pipeline = PipelineChainRule(); IScriptExtent extent = (pipeline != null) ? ExtentOf(token, pipeline) : token.Extent; @@ -2115,27 +2285,29 @@ private ExitStatementAst ExitStatementRule(Token token) private ThrowStatementAst ThrowStatementRule(Token token) { - //G flow-control-statement: - //G 'throw' pipeline:opt + // G flow-control-statement: + // G 'throw' pipeline:opt - PipelineBaseAst pipeline = PipelineRule(); + PipelineBaseAst pipeline = PipelineChainRule(); IScriptExtent extent = (pipeline != null) ? ExtentOf(token, pipeline) : token.Extent; + return new ThrowStatementAst(extent, pipeline); } private StatementAst LabeledStatementRule(LabelToken label) { - //G statement: - //G label:opt labeled-statement - //G - //G labeled-statement: - //G switch-statement - //G foreach-statement - //G for-statement - //G while-statement - //G do-statement + // G statement: + // G label:opt labeled-statement + // G + // G labeled-statement: + // G switch-statement + // G foreach-statement + // G for-statement + // G while-statement + // G do-statement + // G pipeline-chain StatementAst statement; Token token = NextToken(); @@ -2159,7 +2331,7 @@ private StatementAst LabeledStatementRule(LabelToken label) default: // We can only unget 1 token, but have 2 to unget, so resync on the label. Resync(label); - statement = PipelineRule(); + statement = PipelineChainRule(); break; } @@ -2168,15 +2340,18 @@ private StatementAst LabeledStatementRule(LabelToken label) private StatementAst BlockStatementRule(Token kindToken) { - //G block-statement - //G keyword statement-block + // G block-statement + // G keyword statement-block StatementBlockAst body = StatementBlockRule(); // ErrorRecovery: nothing more to look for, so just return the error statement. if (body == null) { - ReportIncompleteInput(After(kindToken.Extent), () => ParserStrings.MissingStatementAfterKeyword, kindToken.Text); + ReportIncompleteInput(After(kindToken.Extent), + nameof(ParserStrings.MissingStatementAfterKeyword), + ParserStrings.MissingStatementAfterKeyword, + kindToken.Text); return new ErrorStatementAst(ExtentOf(kindToken, kindToken)); } @@ -2184,7 +2359,7 @@ private StatementAst BlockStatementRule(Token kindToken) } /// - /// Handle the InlineScript syntax in the script workflow + /// Handle the InlineScript syntax in the script workflow. /// /// /// @@ -2194,8 +2369,8 @@ private StatementAst BlockStatementRule(Token kindToken) /// private bool InlineScriptRule(Token inlineScriptToken, List elements) { - //G Command - //G InlineScript scriptblock-expression + // G Command + // G InlineScript scriptblock-expression Diagnostics.Assert(elements != null && elements.Count == 0, "The CommandElement list should be empty"); var commandName = new StringConstantExpressionAst(inlineScriptToken.Extent, inlineScriptToken.Text, StringConstantType.BareWord); @@ -2210,7 +2385,10 @@ private bool InlineScriptRule(Token inlineScriptToken, List e // ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything. UngetToken(lCurly); - ReportIncompleteInput(After(inlineScriptToken), () => ParserStrings.MissingStatementAfterKeyword, inlineScriptToken.Text); + ReportIncompleteInput(After(inlineScriptToken), + nameof(ParserStrings.MissingStatementAfterKeyword), + ParserStrings.MissingStatementAfterKeyword, + inlineScriptToken.Text); return false; } @@ -2221,15 +2399,15 @@ private bool InlineScriptRule(Token inlineScriptToken, List e private StatementAst IfStatementRule(Token ifToken) { - //G if-statement: - //G 'if' new-lines:opt '(' pipeline-statement ')' statement-block elseif-clauses:opt else-clause:opt - //G elseif-clauses: - //G elseif-clause - //G elseif-clauses elseif-clause - //G elseif-clause: - //G 'elseif' new-lines:opt '(' pipeline-statement ')' statement-block - //G else-clause: - //G 'else' statement-block + // G if-statement: + // G 'if' new-lines:opt '(' pipeline-chain ')' statement-block elseif-clauses:opt else-clause:opt + // G elseif-clauses: + // G elseif-clause + // G elseif-clauses elseif-clause + // G elseif-clause: + // G 'elseif' new-lines:opt '(' pipeline-chain ')' statement-block + // G else-clause: + // G 'else' statement-block List clauses = new List(); List componentAsts = new List(); @@ -2247,20 +2425,25 @@ private StatementAst IfStatementRule(Token ifToken) UngetToken(lParen); ReportIncompleteInput(After(keyword), - () => ParserStrings.MissingOpenParenthesisInIfStatement, keyword.Text); + nameof(ParserStrings.MissingOpenParenthesisInIfStatement), + ParserStrings.MissingOpenParenthesisInIfStatement, + keyword.Text); return new ErrorStatementAst(ExtentOf(ifToken, keyword), componentAsts); } SkipNewlines(); - PipelineBaseAst condition = PipelineRule(); + PipelineBaseAst condition = PipelineChainRule(); if (condition == null) { // ErrorRecovery: assume pipeline just hasn't been entered yet, continue hoping // to find a close paren and statement block. IScriptExtent errorPosition = After(lParen); - ReportIncompleteInput(errorPosition, - () => ParserStrings.IfStatementMissingCondition, keyword.Text); + ReportIncompleteInput( + errorPosition, + nameof(ParserStrings.IfStatementMissingCondition), + ParserStrings.IfStatementMissingCondition, + keyword.Text); condition = new ErrorStatementAst(errorPosition); } else @@ -2277,11 +2460,14 @@ 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, - () => ParserStrings.MissingEndParenthesisAfterStatement, keyword.Text); + nameof(ParserStrings.MissingEndParenthesisAfterStatement), + ParserStrings.MissingEndParenthesisAfterStatement, + keyword.Text); } + return new ErrorStatementAst(ExtentOf(ifToken, Before(rParen)), componentAsts); } @@ -2292,7 +2478,10 @@ private StatementAst IfStatementRule(Token ifToken) // ErrorRecovery: assume the next token is a newline or part of something else, // so stop parsing the statement and try parsing something else. - ReportIncompleteInput(rParen.Extent, () => ParserStrings.MissingStatementBlock, keyword.Text); + ReportIncompleteInput(rParen.Extent, + nameof(ParserStrings.MissingStatementBlock), + ParserStrings.MissingStatementBlock, + keyword.Text); return new ErrorStatementAst(ExtentOf(ifToken, rParen), componentAsts); } @@ -2300,6 +2489,17 @@ private StatementAst IfStatementRule(Token ifToken) clauses.Add(new IfClause(condition, body)); + // Save a restore point here. In case there is no 'elseif' or 'else' following, + // we should resync back here to preserve the possible new lines. The new lines + // could be important for the following parsing. For example, in case we are in + // a HashExpression, a new line might be needed for parsing the key-value that + // is following the if statement: + // @{ + // a = if (1) {} + // b = 10 + // } + + int restorePoint = _ungotToken == null ? _tokenizer.GetRestorePoint() : _ungotToken.Extent.StartOffset; SkipNewlines(); keyword = PeekToken(); @@ -2308,8 +2508,7 @@ private StatementAst IfStatementRule(Token ifToken) SkipToken(); continue; } - - if (keyword.Kind == TokenKind.Else) + else if (keyword.Kind == TokenKind.Else) { SkipToken(); SkipNewlines(); @@ -2319,10 +2518,18 @@ private StatementAst IfStatementRule(Token ifToken) // ErrorRecovery: assume the next token is a newline or part of something else, // so stop parsing the statement and try parsing something else. - ReportIncompleteInput(After(keyword), () => ParserStrings.MissingStatementBlockAfterElse); + ReportIncompleteInput(After(keyword), + nameof(ParserStrings.MissingStatementBlockAfterElse), + ParserStrings.MissingStatementBlockAfterElse); return new ErrorStatementAst(ExtentOf(ifToken, keyword), componentAsts); } } + else + { + // There is no 'elseif' or 'else' following, so resync back to the possible new lines. + Resync(restorePoint); + } + break; } @@ -2335,33 +2542,33 @@ private StatementAst IfStatementRule(Token ifToken) private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToken) { - //G switch-statement: - //G 'switch' new-lines:opt switch-parameters:opt switch-condition switch-body - //G switch-parameters: - //G switch-parameter - //G switch-parameters switch-parameter - //G switch-parameter: - //G '-regex' - //G '-wildcard' - //G '-exact' - //G '-casesensitive' - //G '-parallel' - //G switch-condition: - //G '(' new-lines:opt pipeline new-lines:opt ')' - //G -file new-lines:opt switch-filename - //G switch-filename: - //G command-argument - //G primary-expression - //G switch-body: - //G new-lines:opt '{' new-lines:opt switch-clauses '}' - //G switch-clauses: - //G switch-clause - //G switch-clauses switch-clause - //G switch-clause: - //G switch-clause-condition statement-block statement-terminators:opt - //G switch-clause-condition: - //G command-argument - //G primary-expression + // G switch-statement: + // G 'switch' new-lines:opt switch-parameters:opt switch-condition switch-body + // G switch-parameters: + // G switch-parameter + // G switch-parameters switch-parameter + // G switch-parameter: + // G '-regex' + // G '-wildcard' + // G '-exact' + // G '-casesensitive' + // G '-parallel' + // G switch-condition: + // G '(' new-lines:opt pipeline new-lines:opt ')' + // G -file new-lines:opt switch-filename + // G switch-filename: + // G command-argument + // G primary-expression + // G switch-body: + // G new-lines:opt '{' new-lines:opt switch-clauses '}' + // G switch-clauses: + // G switch-clause + // G switch-clauses switch-clause + // G switch-clause: + // G switch-clause-condition statement-block statement-terminators:opt + // G switch-clause-condition: + // G command-argument + // G primary-expression IScriptExtent startExtent = (labelToken ?? switchToken).Extent; IScriptExtent endErrorStatement = startExtent; @@ -2380,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")) { @@ -2440,7 +2647,9 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke // ErrorRecovery: pretend we saw the filename and continue. isError = true; - isIncompleteError = ReportIncompleteInput(After(switchParameterToken), () => ParserStrings.MissingFilenameOption); + isIncompleteError = ReportIncompleteInput(After(switchParameterToken), + nameof(ParserStrings.MissingFilenameOption), + ParserStrings.MissingFilenameOption); if (!specifiedFlags.ContainsKey("file")) { @@ -2464,7 +2673,9 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke // ErrorRecovery: just ignore the token, continue parsing. isError = true; - ReportError(switchParameterToken.Extent, () => ParserStrings.InvalidSwitchFlag, + ReportError(switchParameterToken.Extent, + nameof(ParserStrings.InvalidSwitchFlag), + ParserStrings.InvalidSwitchFlag, ((ParameterToken)switchParameterToken).ParameterName); } @@ -2473,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)); } @@ -2488,18 +2699,22 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke // ErrorRecovery: nothing special this is a semantic error. isError = true; - ReportError(lParen.Extent, () => ParserStrings.PipelineValueRequired); + ReportError(lParen.Extent, + nameof(ParserStrings.PipelineValueRequired), + ParserStrings.PipelineValueRequired); } needErrorCondition = true; // need to add condition ast to the error statement if the parsing fails SkipNewlines(); - condition = PipelineRule(); + condition = PipelineChainRule(); if (condition == null) { // ErrorRecovery: pretend we saw the condition and keep parsing. isError = true; - isIncompleteError = ReportIncompleteInput(After(lParen), () => ParserStrings.PipelineValueRequired); + isIncompleteError = ReportIncompleteInput(After(lParen), + nameof(ParserStrings.PipelineValueRequired), + ParserStrings.PipelineValueRequired); } else { @@ -2518,7 +2733,8 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke isError = true; isIncompleteError = ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingEndParenthesisInSwitchStatement); + nameof(ParserStrings.MissingEndParenthesisInSwitchStatement), + ParserStrings.MissingEndParenthesisInSwitchStatement); } } else @@ -2532,7 +2748,8 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke { isError = true; isIncompleteError = ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.PipelineValueRequired); + nameof(ParserStrings.PipelineValueRequired), + ParserStrings.PipelineValueRequired); } else { @@ -2554,7 +2771,9 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke if (!isIncompleteError) { isError = true; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingCurlyBraceInSwitchStatement); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingCurlyBraceInSwitchStatement), + ParserStrings.MissingCurlyBraceInSwitchStatement); } } else @@ -2564,24 +2783,42 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke while (true) { - ExpressionAst clauseCondition = GetSingleCommandArgument(CommandArgumentContext.SwitchCondition); - if (clauseCondition == null) - { - // ErrorRecovery: if we don't have anything that looks like a condition, we won't - // find a body (because a body is just a script block, which works as a condition.) - // So don't look for a body, hope we find the '}' next. + Token token = PeekToken(); + bool isDefaultClause = token.Kind == TokenKind.Default; + ExpressionAst clauseCondition = null; - isError = true; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingSwitchConditionExpression); - // Consume a closing curly, if there is one, to avoid an extra error - if (PeekToken().Kind == TokenKind.RCurly) + if (isDefaultClause) + { + // Consume the 'default' token. + SkipToken(); + clauseCondition = new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord); + } + else + { + clauseCondition = GetSingleCommandArgument(CommandArgumentContext.SwitchCondition); + if (clauseCondition == null) { - SkipToken(); + // ErrorRecovery: if we don't have anything that looks like a condition, we won't + // find a body (because a body is just a script block, which works as a condition.) + // So don't look for a body, hope we find the '}' next. + isError = true; + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingSwitchConditionExpression), + ParserStrings.MissingSwitchConditionExpression); + + // Consume a closing curly, if there is one, to avoid an extra error + if (PeekToken().Kind == TokenKind.RCurly) + { + SkipToken(); + } + + break; } - break; } + errorAsts.Add(clauseCondition); endErrorStatement = clauseCondition.Extent; + StatementBlockAst clauseBody = StatementBlockRule(); if (clauseBody == null) { @@ -2589,26 +2826,26 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke isError = true; isIncompleteError = ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingSwitchStatementClause); + nameof(ParserStrings.MissingSwitchStatementClause), + ParserStrings.MissingSwitchStatementClause); } else { errorAsts.Add(clauseBody); endErrorStatement = clauseBody.Extent; - var clauseConditionString = clauseCondition as StringConstantExpressionAst; - - if (clauseConditionString != null && - clauseConditionString.StringConstantType == StringConstantType.BareWord && - clauseConditionString.Value.Equals("default", StringComparison.OrdinalIgnoreCase)) + if (isDefaultClause) { if (@default != null) { // ErrorRecovery: just report the error and continue, forget the previous default clause. isError = true; - ReportError(clauseCondition.Extent, () => ParserStrings.MultipleSwitchDefaultClauses); + ReportError(clauseCondition.Extent, + nameof(ParserStrings.MultipleSwitchDefaultClauses), + ParserStrings.MultipleSwitchDefaultClauses); } + @default = clauseBody; } else @@ -2616,22 +2853,28 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke clauses.Add(new SwitchClause(clauseCondition, clauseBody)); } } + SkipNewlinesAndSemicolons(); - Token token = PeekToken(); + token = PeekToken(); if (token.Kind == TokenKind.RCurly) { rCurly = token; SkipToken(); break; } + if (token.Kind == TokenKind.EndOfInput) { if (!isIncompleteError) { isError = true; - ReportIncompleteInput(lCurly.Extent, token.Extent, () => ParserStrings.MissingEndCurlyBrace); + ReportIncompleteInput(lCurly.Extent, + token.Extent, + nameof(ParserStrings.MissingEndCurlyBrace), + ParserStrings.MissingEndCurlyBrace); } + break; } } @@ -2644,16 +2887,16 @@ 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) { - //G configuration-statement: - //G 'configuration' new-lines:opt singleNameExpression new-lines:opt statement-block - //G singleNameExpression: - //G command-argument - //G primary-expression + // G configuration-statement: + // G 'configuration' new-lines:opt singleNameExpression new-lines:opt statement-block + // G singleNameExpression: + // G command-argument + // G primary-expression IScriptExtent startExtent = configurationToken.Extent; IScriptExtent endErrorStatement = startExtent; @@ -2670,19 +2913,22 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (configurationNameToken.Kind == TokenKind.LCurly) { - ReportError(After(startExtent), () => ParserStrings.MissingConfigurationName); + ReportError(After(startExtent), + nameof(ParserStrings.MissingConfigurationName), + ParserStrings.MissingConfigurationName); // Try reading the configuration body - this should keep the parse in sync - but we won't return it ScriptBlockExpressionRule(configurationNameToken); return null; } - if (configurationNameToken.Kind == TokenKind.EndOfInput) + if (configurationNameToken.Kind is TokenKind.EndOfInput or TokenKind.Comma) { UngetToken(configurationNameToken); ReportIncompleteInput(After(configurationNameToken.Extent), - () => ParserStrings.MissingConfigurationName); + nameof(ParserStrings.MissingConfigurationName), + ParserStrings.MissingConfigurationName); return null; } @@ -2694,7 +2940,9 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (configurationName == null) { isError = true; - ReportIncompleteInput(configurationNameToken.Extent, () => ParserStrings.MissingConfigurationName); + ReportIncompleteInput(configurationNameToken.Extent, + nameof(ParserStrings.MissingConfigurationName), + ParserStrings.MissingConfigurationName); } else { @@ -2708,7 +2956,10 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom // This is actually a semantics check, the syntax is fine at this point. // Continue parsing to get as much information as possible isError = true; - ReportError(configurationName.Extent, () => ParserStrings.InvalidConfigurationName, simpleConfigurationNameValue ?? string.Empty); + ReportError(configurationName.Extent, + nameof(ParserStrings.InvalidConfigurationName), + ParserStrings.InvalidConfigurationName, + simpleConfigurationNameValue ?? string.Empty); } } } @@ -2733,12 +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, - () => 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; } @@ -2746,14 +3019,14 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (Utils.IsWinPEHost()) { ReportError(configurationToken.Extent, - () => ParserStrings.ConfigurationNotAllowedOnWinPE, + nameof(ParserStrings.ConfigurationNotAllowedOnWinPE), + ParserStrings.ConfigurationNotAllowedOnWinPE, configurationToken.Kind.Text()); return null; } 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. @@ -2781,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) @@ -2808,16 +3092,16 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom { // This shouldn't happen - the system classes should always be good, but just in case, // we'll catch the exception and report it as an error. - ReportError(configurationKeywordToken.Extent, () => e.ToString()); + ReportError(configurationKeywordToken.Extent, + nameof(ParserStrings.ParserError), + ParserStrings.ParserError, + e.ToString()); return null; } } finally { - if (p != null) - { - p.Dispose(); - } + p?.Dispose(); // // Put the parser back... @@ -2825,11 +3109,12 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser = currentParser; } - Token lCurly = NextToken(); if (lCurly.Kind != TokenKind.LCurly) { - ReportIncompleteInput(After(lCurly.Extent), () => ParserStrings.MissingCurlyInConfigurationStatement); + ReportIncompleteInput(After(lCurly.Extent), + nameof(ParserStrings.MissingCurlyInConfigurationStatement), + ParserStrings.MissingCurlyInConfigurationStatement); isError = true; UngetToken(lCurly); } @@ -2845,9 +3130,12 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom { _inConfiguration = oldInConfiguration; } + if (configurationBodyScriptBlock == null) { - ReportError(After(lCurly.Extent), () => ParserStrings.ConfigurationBodyEmpty); + ReportError(After(lCurly.Extent), + nameof(ParserStrings.ConfigurationBodyEmpty), + ParserStrings.ConfigurationBodyEmpty); return null; } } @@ -2857,7 +3145,6 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom return new ErrorStatementAst(ExtentOf(startExtent, endErrorStatement), configurationToken); } - #region "Add Configuration Keywords" // If the configuration name is a constant string, then @@ -2913,6 +3200,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom keywordProp.TypeConstraint = typeConstraint.TypeName.Name; continue; } + var aAst = attr as AttributeAst; if (aAst != null) { @@ -2943,6 +3231,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom } } } + keywordToAddForThisConfigurationStatement.Properties.Add(keywordProp.Name, keywordProp); } } @@ -2950,10 +3239,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (topLevel) { - if (_configurationKeywordsDefinedInThisFile == null) - { - _configurationKeywordsDefinedInThisFile = new Dictionary(); - } + _configurationKeywordsDefinedInThisFile ??= new Dictionary(); _configurationKeywordsDefinedInThisFile[keywordToAddForThisConfigurationStatement.Keyword] = keywordToAddForThisConfigurationStatement; } @@ -2976,6 +3262,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom attribute => (attribute.TypeName.GetReflectionAttributeType() != null) && (attribute.TypeName.GetReflectionAttributeType() == typeof(DscLocalConfigurationManagerAttribute))); } + ScriptBlockExpressionAst bodyAst = configurationBodyScriptBlock as ScriptBlockExpressionAst; IScriptExtent configurationExtent = ExtentOf(startExtent, bodyAst); return new ConfigurationDefinitionAst(configurationExtent, @@ -2992,7 +3279,10 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom catch (Exception e) { // In theory this should never happen so if it does, we'll report the actual exception rather than introducing a new message - ReportError(configurationKeywordToken.Extent, () => "ConfigurationStatementToken: " + e); + ReportError(configurationKeywordToken.Extent, + nameof(ParserStrings.ParserError), + ParserStrings.ParserError, + "ConfigurationStatementToken: " + e); return null; } finally @@ -3011,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(); } @@ -3026,9 +3325,9 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom /// /// Reads an argument expression for a keyword or keyword parameter. - /// This can be either a bare word or an expression + /// This can be either a bare word or an expression. /// - /// the token of the associated keyword + /// The token of the associated keyword. private ExpressionAst GetWordOrExpression(Token keywordToken) { Token nameToken = NextToken(); @@ -3037,7 +3336,9 @@ private ExpressionAst GetWordOrExpression(Token keywordToken) { // ErrorRecovery: report incomplete statement and return UngetToken(nameToken); - ReportIncompleteInput(After(keywordToken), () => ParserStrings.RequiredNameOrExpressionMissing); + ReportIncompleteInput(After(keywordToken), + nameof(ParserStrings.RequiredNameOrExpressionMissing), + ParserStrings.RequiredNameOrExpressionMissing); return null; } @@ -3045,26 +3346,30 @@ private ExpressionAst GetWordOrExpression(Token keywordToken) if (argument == null) { var extent = keywordToken.Extent; - ReportError(After(extent), () => ParserStrings.ParameterRequiresArgument, keywordToken.Text); + ReportError(After(extent), + nameof(ParserStrings.ParameterRequiresArgument), + ParserStrings.ParameterRequiresArgument, + keywordToken.Text); } + return argument; } private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachToken) { - //G foreach-statement: - //G 'foreach' new-lines:opt foreach-parameters:opt new-lines:opt '(' - //G new-lines:opt variable new-lines:opt 'in' new-lines:opt pipeline - //G new-lines:opt ')' statement-block - //G foreach-parameters: - //G foreach-parameter - //G foreach-parameters foreach-parameter - //G foreach-parameter: - //G '-parallel' - //G '-throttlelimit' new-lines:opt foreach-throttlelimit - //G foreach-throttlelimit: - //G command-argument - //G primary-expression + // G foreach-statement: + // G 'foreach' new-lines:opt foreach-parameters:opt new-lines:opt '(' + // G new-lines:opt variable new-lines:opt 'in' new-lines:opt pipeline + // G new-lines:opt ')' statement-block + // G foreach-parameters: + // G foreach-parameter + // G foreach-parameters foreach-parameter + // G foreach-parameter: + // G '-parallel' + // G '-throttlelimit' new-lines:opt foreach-throttlelimit + // G foreach-throttlelimit: + // G command-argument + // G primary-expression IScriptExtent startOfStatement = labelToken != null ? labelToken.Extent : forEachToken.Extent; IScriptExtent endErrorStatement = null; @@ -3092,7 +3397,9 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo { // ErrorRecovery: pretend we saw the throttle limit and continue. - ReportIncompleteInput(After(foreachParameterToken), () => ParserStrings.MissingThrottleLimit); + ReportIncompleteInput(After(foreachParameterToken), + nameof(ParserStrings.MissingThrottleLimit), + ParserStrings.MissingThrottleLimit); } } else @@ -3100,7 +3407,9 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo // ErrorRecovery: just ignore the token, continue parsing. endErrorStatement = foreachParameterToken.Extent; - ReportError(foreachParameterToken.Extent, () => ParserStrings.InvalidForeachFlag, + ReportError(foreachParameterToken.Extent, + nameof(ParserStrings.InvalidForeachFlag), + ParserStrings.InvalidForeachFlag, ((ParameterToken)foreachParameterToken).ParameterName); } @@ -3116,9 +3425,12 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo UngetToken(lParen); endErrorStatement = forEachToken.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingOpenParenthesisAfterKeyword, forEachToken.Kind.Text()); + nameof(ParserStrings.MissingOpenParenthesisAfterKeyword), + ParserStrings.MissingOpenParenthesisAfterKeyword, + forEachToken.Kind.Text()); return new ErrorStatementAst(ExtentOf(startOfStatement, endErrorStatement)); } + SkipNewlines(); Token token = NextToken(); @@ -3128,7 +3440,9 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo UngetToken(token); endErrorStatement = lParen.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingVariableNameAfterForeach); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingVariableNameAfterForeach), + ParserStrings.MissingVariableNameAfterForeach); return new ErrorStatementAst(ExtentOf(startOfStatement, endErrorStatement)); } @@ -3144,18 +3458,22 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo UngetToken(inToken); endErrorStatement = variableAst.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingInInForeach); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingInInForeach), + ParserStrings.MissingInInForeach); } else { SkipNewlines(); - pipeline = PipelineRule(); + pipeline = PipelineChainRule(); if (pipeline == null) { // ErrorRecovery: assume the rest of the statement is missing. endErrorStatement = inToken.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingForeachExpression); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingForeachExpression), + ParserStrings.MissingForeachExpression); } else { @@ -3167,7 +3485,9 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo UngetToken(rParen); endErrorStatement = pipeline.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingEndParenthesisAfterForeach); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingEndParenthesisAfterForeach), + ParserStrings.MissingEndParenthesisAfterForeach); } else { @@ -3177,7 +3497,9 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo // ErrorRecovery: nothing more to look for, so just return the error statement. endErrorStatement = rParen.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingForeachStatement); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingForeachStatement), + ParserStrings.MissingForeachStatement); } } } @@ -3190,32 +3512,32 @@ 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); } private StatementAst ForStatementRule(LabelToken labelToken, Token forToken) { - //G for-statement: - //G 'for' new-lines:opt '(' - //G new-lines:opt for-initializer:opt statement-terminator - //G new-lines:opt for-condition:opt statement-terminator - //G new-lines:opt for-iterator:opt - //G new-lines:opt ')' statement-block - //G 'for' new-lines:opt '(' - //G new-lines:opt for-initializer:opt statement-terminator - //G new-lines:opt for-condition:opt - //G new-lines:opt ')' statement-block - //G 'for' new-lines:opt '(' - //G new-lines:opt for-initializer:opt - //G new-lines:opt ')' statement-block - //G for-initializer: - //G pipeline - //G for-condition: - //G pipeline - //G for-iterator: - //G pipeline + // G for-statement: + // G 'for' new-lines:opt '(' + // G new-lines:opt for-initializer:opt statement-terminator + // G new-lines:opt for-condition:opt statement-terminator + // G new-lines:opt for-iterator:opt + // G new-lines:opt ')' statement-block + // G 'for' new-lines:opt '(' + // G new-lines:opt for-initializer:opt statement-terminator + // G new-lines:opt for-condition:opt + // G new-lines:opt ')' statement-block + // G 'for' new-lines:opt '(' + // G new-lines:opt for-initializer:opt + // G new-lines:opt ')' statement-block + // G for-initializer: + // G pipeline-chain + // G for-condition: + // G pipeline-chain + // G for-iterator: + // G pipeline-chain IScriptExtent endErrorStatement = null; SkipNewlines(); @@ -3227,35 +3549,43 @@ private StatementAst ForStatementRule(LabelToken labelToken, Token forToken) UngetToken(lParen); endErrorStatement = forToken.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingOpenParenthesisAfterKeyword, forToken.Kind.Text()); + nameof(ParserStrings.MissingOpenParenthesisAfterKeyword), + ParserStrings.MissingOpenParenthesisAfterKeyword, + forToken.Kind.Text()); return new ErrorStatementAst(ExtentOf(labelToken ?? forToken, endErrorStatement)); } + SkipNewlines(); - PipelineBaseAst initializer = PipelineRule(); + PipelineBaseAst initializer = PipelineChainRule(); if (initializer != null) { endErrorStatement = initializer.Extent; } + if (PeekToken().Kind == TokenKind.Semi) { endErrorStatement = NextToken().Extent; } + SkipNewlines(); - PipelineBaseAst condition = PipelineRule(); + PipelineBaseAst condition = PipelineChainRule(); if (condition != null) { endErrorStatement = condition.Extent; } + if (PeekToken().Kind == TokenKind.Semi) { endErrorStatement = NextToken().Extent; } + SkipNewlines(); - PipelineBaseAst iterator = PipelineRule(); + PipelineBaseAst iterator = PipelineChainRule(); if (iterator != null) { endErrorStatement = iterator.Extent; } + SkipNewlines(); Token rParen = NextToken(); StatementBlockAst body = null; @@ -3264,12 +3594,12 @@ 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), - () => ParserStrings.MissingEndParenthesisAfterStatement, forToken.Kind.Text()); + nameof(ParserStrings.MissingEndParenthesisAfterStatement), + ParserStrings.MissingEndParenthesisAfterStatement, + forToken.Kind.Text()); } else { @@ -3279,7 +3609,9 @@ private StatementAst ForStatementRule(LabelToken labelToken, Token forToken) // ErrorRecovery: return an error statement. endErrorStatement = rParen.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingLoopStatement, forToken.Kind.Text()); + nameof(ParserStrings.MissingLoopStatement), + ParserStrings.MissingLoopStatement, + forToken.Kind.Text()); } } @@ -3290,13 +3622,16 @@ 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) { - //G while-statement: - //G 'while ' new-lines:opt '(' new-lines:opt while-condition new-lines:opt ')' statement-block + // G while-statement: + // G 'while ' new-lines:opt '(' new-lines:opt while-condition new-lines:opt ')' statement-block + // G + // G while-condition: + // G pipeline-chain SkipNewlines(); @@ -3308,12 +3643,14 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) UngetToken(lParen); ReportIncompleteInput(After(whileToken), - () => ParserStrings.MissingOpenParenthesisAfterKeyword, whileToken.Text); + nameof(ParserStrings.MissingOpenParenthesisAfterKeyword), + ParserStrings.MissingOpenParenthesisAfterKeyword, + whileToken.Text); return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, whileToken)); } SkipNewlines(); - PipelineBaseAst condition = PipelineRule(); + PipelineBaseAst condition = PipelineChainRule(); PipelineBaseAst errorCondition = null; if (condition == null) { @@ -3321,8 +3658,11 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) // to find a close paren and statement block. IScriptExtent errorPosition = After(lParen); - ReportIncompleteInput(errorPosition, - () => ParserStrings.MissingExpressionAfterKeyword, whileToken.Kind.Text()); + ReportIncompleteInput( + errorPosition, + nameof(ParserStrings.MissingExpressionAfterKeyword), + ParserStrings.MissingExpressionAfterKeyword, + whileToken.Kind.Text()); condition = new ErrorStatementAst(errorPosition); } else @@ -3339,11 +3679,14 @@ 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), - () => ParserStrings.MissingEndParenthesisAfterStatement, whileToken.Kind.Text()); + nameof(ParserStrings.MissingEndParenthesisAfterStatement), + ParserStrings.MissingEndParenthesisAfterStatement, + whileToken.Kind.Text()); } + return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, condition), GetNestedErrorAsts(errorCondition)); } @@ -3354,12 +3697,14 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) // ErrorRecovery: assume the next token is a newline or part of something else. ReportIncompleteInput(After(rParen), - () => ParserStrings.MissingLoopStatement, whileToken.Kind.Text()); + nameof(ParserStrings.MissingLoopStatement), + ParserStrings.MissingLoopStatement, + whileToken.Kind.Text()); return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, rParen), GetNestedErrorAsts(errorCondition)); } return new WhileStatementAst(ExtentOf(labelToken ?? whileToken, body), - labelToken != null ? labelToken.LabelText : null, condition, body); + labelToken?.LabelText, condition, body); } /// @@ -3371,10 +3716,10 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) /// This custom keyword does not introduce a new AST node type. Instead it generates a /// CommandAst that calls a PowerShell command to implement the keyword's logic. /// This command has one of two signatures: - /// keywordImplCommand + /// keywordImplCommand. /// - /// The name of the function to invoke - /// The data for this keyword definition + /// The name of the function to invoke. + /// The data for this keyword definition. /// private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyword keywordData) { @@ -3396,7 +3741,10 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw } catch (Exception e) { - ReportError(functionName.Extent, () => ParserStrings.DynamicKeywordPreParseException, keywordData.ResourceName, e.ToString()); + ReportError(functionName.Extent, + nameof(ParserStrings.DynamicKeywordPreParseException), + ParserStrings.DynamicKeywordPreParseException, + keywordData.ResourceName, e.ToString()); return null; } } @@ -3404,14 +3752,20 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw if (keywordData.IsReservedKeyword) { // ErrorRecovery: eat the token - ReportError(functionName.Extent, () => ParserStrings.UnsupportedReservedKeyword, keywordData.Keyword); + ReportError(functionName.Extent, + nameof(ParserStrings.UnsupportedReservedKeyword), + ParserStrings.UnsupportedReservedKeyword, + keywordData.Keyword); return null; } if (keywordData.HasReservedProperties) { // ErrorRecovery: eat the token - ReportError(functionName.Extent, () => ParserStrings.UnsupportedReservedProperty, "'Require', 'Trigger', 'Notify', 'Before', 'After' and 'Subscribe'"); + ReportError(functionName.Extent, + nameof(ParserStrings.UnsupportedReservedProperty), + ParserStrings.UnsupportedReservedProperty, + "'Require', 'Trigger', 'Notify', 'Before', 'After' and 'Subscribe'"); return null; } @@ -3439,13 +3793,18 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired) { - ReportIncompleteInput(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing); + ReportIncompleteInput(After(functionName), + nameof(ParserStrings.RequiredNameOrExpressionMissing), + ParserStrings.RequiredNameOrExpressionMissing); } else { // Name not required so report missing brace - ReportIncompleteInput(After(functionName.Extent), () => ParserStrings.MissingBraceInObjectDefinition); + ReportIncompleteInput(After(functionName.Extent), + nameof(ParserStrings.MissingBraceInObjectDefinition), + ParserStrings.MissingBraceInObjectDefinition); } + return null; } @@ -3456,7 +3815,9 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw lCurly = nameToken; if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired) { - ReportError(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing); + ReportError(After(functionName), + nameof(ParserStrings.RequiredNameOrExpressionMissing), + ParserStrings.RequiredNameOrExpressionMissing); UngetToken(nameToken); return null; } @@ -3465,7 +3826,11 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw { if (keywordData.NameMode == DynamicKeywordNameMode.NoName) { - ReportError(After(functionName), () => ParserStrings.UnexpectedNameForType, functionName.Text, nameToken.Text); + ReportError(After(functionName), + nameof(ParserStrings.UnexpectedNameForType), + ParserStrings.UnexpectedNameForType, + functionName.Text, + nameToken.Text); UngetToken(nameToken); return null; } @@ -3476,7 +3841,9 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw // If only a simple name is allowed, then the string must be non-null. if ((keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName) && string.IsNullOrEmpty(elementName)) { - ReportIncompleteInput(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing); + ReportIncompleteInput(After(functionName), + nameof(ParserStrings.RequiredNameOrExpressionMissing), + ParserStrings.RequiredNameOrExpressionMissing); UngetToken(nameToken); return null; } @@ -3491,20 +3858,30 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw { if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName) { - ReportError(After(functionName), () => ParserStrings.RequiredNameOrExpressionMissing); + ReportError(After(functionName), + nameof(ParserStrings.RequiredNameOrExpressionMissing), + ParserStrings.RequiredNameOrExpressionMissing); } else { // It wasn't an '{' and it wasn't a name expression so it's a unexpected token. - ReportError(After(functionName), () => ParserStrings.UnexpectedToken, nameToken.Text); + ReportError(After(functionName), + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + nameToken.Text); } + return null; } // Ok, we got a name expression, but we're expecting no name, so it's and error. if (keywordData.NameMode == DynamicKeywordNameMode.NoName) { - ReportError(After(functionName), () => ParserStrings.UnexpectedNameForType, functionName.Text, instanceName.ToString()); + ReportError(After(functionName), + nameof(ParserStrings.UnexpectedNameForType), + ParserStrings.UnexpectedNameForType, + functionName.Text, + instanceName.ToString()); return null; } @@ -3512,7 +3889,10 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName) { // If no match, then this is an incomplete token BUGBUG fix message - ReportError(nameToken.Extent, () => ParserStrings.UnexpectedToken, nameToken.Text); + ReportError(nameToken.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + nameToken.Text); return null; } } @@ -3523,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(); @@ -3540,7 +3917,9 @@ private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyw if (lCurly.Kind == TokenKind.EndOfInput) { UngetToken(lCurly); - ReportIncompleteInput(After(functionName.Extent), () => ParserStrings.MissingBraceInObjectDefinition); + ReportIncompleteInput(After(functionName.Extent), + nameof(ParserStrings.MissingBraceInObjectDefinition), + ParserStrings.MissingBraceInObjectDefinition); // Preserve the name expression for tab completion return originalInstanceName == null @@ -3567,11 +3946,17 @@ instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst && // the last condition checks that there is no space between "method" name and '{' instanceInvokeMemberExpressionAst.Member.Extent.EndOffset == instanceInvokeMemberExpressionAst.Arguments[0].Extent.StartOffset) { - ReportError(LastCharacterOf(instanceInvokeMemberExpressionAst.Member.Extent), () => ParserStrings.UnexpectedTokenInDynamicKeyword, functionName.Text); + ReportError(LastCharacterOf(instanceInvokeMemberExpressionAst.Member.Extent), + nameof(ParserStrings.UnexpectedTokenInDynamicKeyword), + ParserStrings.UnexpectedTokenInDynamicKeyword, + functionName.Text); } else { - ReportError(lCurly.Extent, () => ParserStrings.UnexpectedToken, lCurly.Text); + ReportError(lCurly.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + lCurly.Text); } if (lCurly.Kind == TokenKind.Dot && originalInstanceName != null && lCurly.Extent.StartOffset == originalInstanceName.Extent.EndOffset) @@ -3616,7 +4001,7 @@ instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst && else if (keywordData.BodyMode == DynamicKeywordBodyMode.Hashtable) { // Resource property value could be set to nested DSC resources except Script resource - bool isScriptResource = String.Compare(functionName.Text, @"Script", StringComparison.OrdinalIgnoreCase) == 0; + bool isScriptResource = string.Equals(functionName.Text, @"Script", StringComparison.OrdinalIgnoreCase); try { if (isScriptResource) @@ -3634,7 +4019,10 @@ instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst && if (body == null) { // Failed to read the statement body - ReportIncompleteInput(After(lCurly), () => ParserStrings.MissingStatementAfterKeyword, keywordData.Keyword); + ReportIncompleteInput(After(lCurly), + nameof(ParserStrings.MissingStatementAfterKeyword), + ParserStrings.MissingStatementAfterKeyword, + keywordData.Keyword); // Preserve the name expression for tab completion return originalInstanceName == null @@ -3689,7 +4077,11 @@ instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst && } catch (Exception e) { - ReportError(functionName.Extent, () => ParserStrings.DynamicKeywordPostParseException, keywordData.Keyword, e.ToString()); + ReportError(functionName.Extent, + nameof(ParserStrings.DynamicKeywordPostParseException), + ParserStrings.DynamicKeywordPostParseException, + keywordData.Keyword, + e.ToString()); return null; } } @@ -3707,12 +4099,12 @@ internal StatementAst CreateErrorStatementAst( private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) { - //G do-statement: - //G 'do' statement-block new-lines:opt 'while' new-lines:opt '(' while-condition new-lines:opt ')' - //G 'do' statement-block new-lines:opt 'until' new-lines:opt '(' while-condition new-lines:opt ')' - //G - //G while-condition: - //G new-lines:opt pipeline + // G do-statement: + // G 'do' statement-block new-lines:opt 'while' new-lines:opt '(' while-condition new-lines:opt ')' + // G 'do' statement-block new-lines:opt 'until' new-lines:opt '(' while-condition new-lines:opt ')' + // G + // G while-condition: + // G new-lines:opt pipeline IScriptExtent startExtent = (labelToken ?? doToken).Extent; IScriptExtent endErrorStatement = null; @@ -3727,7 +4119,10 @@ private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) // comes next. endErrorStatement = doToken.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingLoopStatement, TokenKind.Do.Text()); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingLoopStatement), + ParserStrings.MissingLoopStatement, + TokenKind.Do.Text()); } else { @@ -3739,7 +4134,9 @@ private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) UngetToken(whileOrUntilToken); endErrorStatement = body.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingWhileOrUntilInDoWhile); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingWhileOrUntilInDoWhile), + ParserStrings.MissingWhileOrUntilInDoWhile); } else { @@ -3752,20 +4149,25 @@ private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) UngetToken(lParen); endErrorStatement = whileOrUntilToken.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingOpenParenthesisAfterKeyword, whileOrUntilToken.Kind.Text()); + nameof(ParserStrings.MissingOpenParenthesisAfterKeyword), + ParserStrings.MissingOpenParenthesisAfterKeyword, + whileOrUntilToken.Kind.Text()); } else { SkipNewlines(); - condition = PipelineRule(); + condition = PipelineChainRule(); if (condition == null) { // ErrorRecovery: try to get the matching close paren, then return an error statement. endErrorStatement = lParen.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingExpressionAfterKeyword, whileOrUntilToken.Kind.Text()); + nameof(ParserStrings.MissingExpressionAfterKeyword), + ParserStrings.MissingExpressionAfterKeyword, + whileOrUntilToken.Kind.Text()); } + SkipNewlines(); rParen = NextToken(); if (rParen.Kind != TokenKind.RParen) @@ -3779,7 +4181,9 @@ private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) { endErrorStatement = condition.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingEndParenthesisAfterStatement, whileOrUntilToken.Kind.Text()); + nameof(ParserStrings.MissingEndParenthesisAfterStatement), + ParserStrings.MissingEndParenthesisAfterStatement, + whileOrUntilToken.Kind.Text()); } } } @@ -3792,37 +4196,62 @@ 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); } + return new DoWhileStatementAst(extent, label, condition, body); } private StatementAst ClassDefinitionRule(List customAttributes, Token classToken) { - //G class-statement: - //G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt '{' class-member-list '}' - //G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt ':' base-type-list '{' new-lines:opt class-member-list:opt '}' - //G - //G class-name: - //G simple-name - //G - //G base-type-list: - //G new-lines:opt type-name new-lines:opt - //G base-class-list ',' new-lines:opt type-name new-lines:opt - //G - //G class-member-list: - //G class-member new-lines:opt - //G class-member-list class-member + // G class-statement: + // G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt '{' class-member-list '}' + // G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt ':' base-type-list '{' new-lines:opt class-member-list:opt '}' + // G + // G class-name: + // G simple-name + // G + // G base-type-list: + // G new-lines:opt type-name new-lines:opt + // G base-class-list ',' new-lines:opt type-name new-lines:opt + // G + // G class-member-list: + // G class-member new-lines:opt + // G class-member-list class-member + + // PowerShell classes are not supported in ConstrainedLanguage + if (Runspace.DefaultRunspace?.ExecutionContext?.LanguageMode == PSLanguageMode.ConstrainedLanguage) + { + if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit) + { + ReportError(classToken.Extent, + nameof(ParserStrings.ClassesNotAllowedInConstrainedLanguage), + ParserStrings.ClassesNotAllowedInConstrainedLanguage, + classToken.Kind.Text()); + + return null; + } + + SystemPolicy.LogWDACAuditMessage( + context: Runspace.DefaultRunspace?.ExecutionContext, + title: ParserStrings.WDACParserClassKeywordLogTitle, + message: ParserStrings.WDACParserClassKeywordLogMessage, + fqid: "ClassLanguageKeywordNotAllowed", + dropIntoDebugger: true); + } SkipNewlines(); Token classNameToken; var name = SimpleNameRule(out classNameToken); if (name == null) { - ReportIncompleteInput(After(classToken), () => ParserStrings.MissingNameAfterKeyword, classToken.Text); + ReportIncompleteInput(After(classToken), + nameof(ParserStrings.MissingNameAfterKeyword), + ParserStrings.MissingNameAfterKeyword, + classToken.Text); return new ErrorStatementAst(classToken.Extent); } @@ -3844,14 +4273,15 @@ 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)), () => ParserStrings.TypeNameExpected); + ReportIncompleteInput(After(ExtentFromFirstOf(commaToken, colonToken)), + nameof(ParserStrings.TypeNameExpected), + ParserStrings.TypeNameExpected); break; } @@ -3877,7 +4307,10 @@ private StatementAst ClassDefinitionRule(List customAttributes UngetToken(lCurly); var lastElement = (superClassesList.Count > 0) ? (Ast)superClassesList[superClassesList.Count - 1] : name; - ReportIncompleteInput(After(lastElement), () => ParserStrings.MissingTypeBody, classToken.Kind.Text()); + ReportIncompleteInput(After(lastElement), + nameof(ParserStrings.MissingTypeBody), + ParserStrings.MissingTypeBody, + classToken.Kind.Text()); return new ErrorStatementAst(ExtentOf(classToken, lastElement), superClassesList); } @@ -3894,12 +4327,11 @@ private StatementAst ClassDefinitionRule(List customAttributes members.Add(member); lastExtent = member.Extent; } + if (astsOnError != null && astsOnError.Count > 0) { - if (nestedAsts == null) - { - nestedAsts = new List(); - } + nestedAsts ??= new List(); + nestedAsts.AddRange(astsOnError); lastExtent = astsOnError.Last().Extent; } @@ -3909,7 +4341,11 @@ private StatementAst ClassDefinitionRule(List customAttributes if (rCurly.Kind != TokenKind.RCurly) { UngetToken(rCurly); - ReportIncompleteInput(After(lCurly), rCurly.Extent, () => ParserStrings.MissingEndCurlyBrace); + ReportIncompleteInput( + After(lCurly), + rCurly.Extent, + nameof(ParserStrings.MissingEndCurlyBrace), + ParserStrings.MissingEndCurlyBrace); } else { @@ -3920,14 +4356,11 @@ 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(); - } - //no need to report error since the error is reported in method StatementRule + nestedAsts ??= new List(); + // no need to report error since the error is reported in method StatementRule nestedAsts.AddRange(customAttributes.OfType()); nestedAsts.Add(classDefn); } @@ -3947,25 +4380,25 @@ private StatementAst ClassDefinitionRule(List customAttributes private MemberAst ClassMemberRule(string className, out List astsOnError) { - //G class-member: - //G method-member - //G property-member - //G - //G method-member: - //G member-attribute-list:opt function-statement - //G - //G property-member: - //G member-attribute-list:opt variable - //G member-attribute-list:opt variable '=' expression - //G - //G member-attribute-list: - //G member-attribute - //G member-attribute-list member-attribute - //G - //G member-attribute: - //G attribute - //G 'static' - //G 'hidden' + // G class-member: + // G method-member + // G property-member + // G + // G method-member: + // G member-attribute-list:opt function-statement + // G + // G property-member: + // G member-attribute-list:opt variable + // G member-attribute-list:opt variable '=' expression + // G + // G member-attribute-list: + // G member-attribute + // G member-attribute-list member-attribute + // G + // G member-attribute: + // G attribute + // G 'static' + // G 'hidden' IScriptExtent startExtent = null; var attributeList = new List(); @@ -3990,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) @@ -4006,52 +4436,72 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) } else { - ReportError(attribute.Extent, () => ParserStrings.TooManyTypes); + ReportError(attribute.Extent, nameof(ParserStrings.TooManyTypes), ParserStrings.TooManyTypes); } + continue; } token = PeekToken(); - if (startExtent == null) - { - startExtent = token.Extent; - } + startExtent ??= token.Extent; switch (token.Kind) { #if SUPPORT_PUBLIC_PRIVATE - case TokenKind.Public: - if (publicToken != null) - { - ReportError(token.Extent, () => ParserStrings.DuplicateQualifier, token.Text); - } - if (privateToken != null) - { - ReportError(token.Extent, () => ParserStrings.ModifiersCannotBeCombined, privateToken.Text, token.Text); - } - publicToken = token; - SkipToken(); - break; + case TokenKind.Public: + if (publicToken != null) + { + ReportError(token.Extent, + nameof(ParserStrings.DuplicateQualifier), + ParserStrings.DuplicateQualifier, + token.Text); + } - case TokenKind.Private: - if (privateToken != null) - { - ReportError(token.Extent, () => ParserStrings.DuplicateQualifier, token.Text); - } - if (publicToken != null) - { - ReportError(token.Extent, () => ParserStrings.ModifiersCannotBeCombined, publicToken.Text, token.Text); - } - privateToken = token; - SkipToken(); - break; + if (privateToken != null) + { + ReportError(token.Extent, + nameof(ParserStrings.ModifiersCannotBeCombined), + ParserStrings.ModifiersCannotBeCombined, + privateToken.Text, + token.Text); + } + + publicToken = token; + SkipToken(); + break; + + case TokenKind.Private: + if (privateToken != null) + { + ReportError(token.Extent, + nameof(ParserStrings.DuplicateQualifier), + ParserStrings.DuplicateQualifier, + token.Text); + } + + if (publicToken != null) + { + ReportError(token.Extent, + nameof(ParserStrings.ModifiersCannotBeCombined), + ParserStrings.ModifiersCannotBeCombined, + publicToken.Text, + token.Text); + } + + privateToken = token; + SkipToken(); + break; #endif case TokenKind.Hidden: if (hiddenToken != null) { - ReportError(token.Extent, () => ParserStrings.DuplicateQualifier, token.Text); + ReportError(token.Extent, + nameof(ParserStrings.DuplicateQualifier), + ParserStrings.DuplicateQualifier, + token.Text); } + hiddenToken = token; lastAttribute = token; SkipToken(); @@ -4060,8 +4510,12 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) case TokenKind.Static: if (staticToken != null) { - ReportError(token.Extent, () => ParserStrings.DuplicateQualifier, token.Text); + ReportError(token.Extent, + nameof(ParserStrings.DuplicateQualifier), + ParserStrings.DuplicateQualifier, + token.Text); } + staticToken = token; lastAttribute = token; SkipToken(); @@ -4097,6 +4551,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) { attributes |= PropertyAttributes.Static; } + if (hiddenToken != null) { attributes |= PropertyAttributes.Hidden; @@ -4106,8 +4561,11 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) Token terminatorToken = PeekToken(); if (terminatorToken.Kind != TokenKind.NewLine && terminatorToken.Kind != TokenKind.Semi && terminatorToken.Kind != TokenKind.RCurly) { - ReportIncompleteInput(After(endExtent), () => ParserStrings.MissingPropertyTerminator); + ReportIncompleteInput(After(endExtent), + nameof(ParserStrings.MissingPropertyTerminator), + ParserStrings.MissingPropertyTerminator); } + SkipNewlinesAndSemicolons(); // Include the semicolon in the extent but not newline or rcurly as that will look weird, e.g. if an error is reported on the full extent @@ -4116,7 +4574,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) endExtent = terminatorToken.Extent; } - if (!String.IsNullOrEmpty(varToken.Name)) + if (!string.IsNullOrEmpty(varToken.Name)) { return new PropertyMemberAst(ExtentOf(startExtent, endExtent), varToken.Name, typeConstraint, attributeList, attributes, initialValueAst); @@ -4134,7 +4592,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) } } - if (token.Kind == TokenKind.Identifier) + if (TryUseTokenAsSimpleName(token)) { SkipToken(); var functionDefinition = MethodDeclarationRule(token, className, staticToken != null) as FunctionDefinitionAst; @@ -4149,7 +4607,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) return null; } -#if FALSE +#if SUPPORT_PUBLIC_PRIVATE MethodAttributes attributes = privateToken != null ? MethodAttributes.Private : MethodAttributes.Public; #else MethodAttributes attributes = MethodAttributes.Public; @@ -4158,17 +4616,21 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) { attributes |= MethodAttributes.Static; } + if (hiddenToken != null) { attributes |= MethodAttributes.Hidden; } + return new FunctionMemberAst(ExtentOf(startExtent, functionDefinition), functionDefinition, typeConstraint, attributeList, attributes); } if (lastAttribute != null) { // We have the start of a member, but didn't see a variable or 'def'. - ReportIncompleteInput(After(ExtentFromFirstOf(lastAttribute)), () => ParserStrings.IncompleteMemberDefinition); + ReportIncompleteInput(After(ExtentFromFirstOf(lastAttribute)), + nameof(ParserStrings.IncompleteMemberDefinition), + ParserStrings.IncompleteMemberDefinition); RecordErrorAsts(attributeList, ref astsOnError); RecordErrorAsts(typeConstraint, ref astsOnError); } @@ -4176,31 +4638,40 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) return null; } - private void RecordErrorAsts(Ast errAst, ref List astsOnError) + private static bool TryUseTokenAsSimpleName(Token token) { - if (errAst == null) + if (token.Kind == TokenKind.Identifier + || token.Kind == TokenKind.DynamicKeyword + || token.TokenFlags.HasFlag(TokenFlags.Keyword)) { - return; + token.TokenFlags = TokenFlags.None; + return true; } - if (astsOnError == null) + return false; + } + + private static void RecordErrorAsts(Ast errAst, ref List astsOnError) + { + if (errAst == null) { - astsOnError = new List(); + return; } + + 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); } @@ -4218,6 +4689,7 @@ private Token NextTypeIdentifierToken() UngetToken(typeName); return null; } + return typeName; } finally @@ -4228,75 +4700,133 @@ private Token NextTypeIdentifierToken() private StatementAst EnumDefinitionRule(List customAttributes, Token enumToken) { - //G enum-statement: - //G 'enum' new-lines:opt enum-name '{' enum-member-list '}' - //G - //G enum-name: - //G simple-name - //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; SkipNewlines(); var name = SimpleNameRule(); if (name == null) { - ReportIncompleteInput(After(enumToken), () => ParserStrings.MissingNameAfterKeyword, enumToken.Text); + ReportIncompleteInput( + After(enumToken), + nameof(ParserStrings.MissingNameAfterKeyword), + ParserStrings.MissingNameAfterKeyword, + enumToken.Text); return new ErrorStatementAst(enumToken.Extent); } - SkipNewlines(); - Token lCurly = NextToken(); - if (lCurly.Kind != TokenKind.LCurly) + TypeConstraintAst underlyingTypeConstraint = null; + var oldTokenizerMode = _tokenizer.Mode; + try { - // ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything. + SetTokenizerMode(TokenizerMode.Signature); + Token colonToken = PeekToken(); + if (colonToken.Kind == TokenKind.Colon) + { + this.SkipToken(); + SkipNewlines(); + ITypeName underlyingType; + underlyingType = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out _); + if (underlyingType == null) + { + ReportIncompleteInput( + After(colonToken), + nameof(ParserStrings.TypeNameExpected), + ParserStrings.TypeNameExpected); + } + else + { + var resolvedType = underlyingType.GetReflectionType(); + if (resolvedType == null || !ValidUnderlyingTypeCodes.HasFlag(resolvedType.GetTypeCode())) + { + ReportError( + underlyingType.Extent, + nameof(ParserStrings.InvalidUnderlyingType), + ParserStrings.InvalidUnderlyingType, + underlyingType.Name); + } + underlyingTypeConstraint = new TypeConstraintAst(underlyingType.Extent, underlyingType); + } + } - UngetToken(lCurly); - ReportIncompleteInput(After(name), () => ParserStrings.MissingTypeBody, enumToken.Kind.Text()); - return new ErrorStatementAst(ExtentOf(enumToken, name)); - } + SkipNewlines(); + Token lCurly = NextToken(); + if (lCurly.Kind != TokenKind.LCurly) + { + // ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything. - IScriptExtent lastExtent = lCurly.Extent; - MemberAst member; - List members = new List(); - while ((member = EnumMemberRule()) != null) - { - members.Add(member); - lastExtent = member.Extent; - } + UngetToken(lCurly); + ReportIncompleteInput( + After(name), + nameof(ParserStrings.MissingTypeBody), + ParserStrings.MissingTypeBody, + enumToken.Kind.Text()); + return new ErrorStatementAst(ExtentOf(enumToken, name)); + } - var rCurly = NextToken(); - if (rCurly.Kind != TokenKind.RCurly) - { - UngetToken(rCurly); - ReportIncompleteInput(After(lCurly), rCurly.Extent, () => ParserStrings.MissingEndCurlyBrace); - } + IScriptExtent lastExtent = lCurly.Extent; + MemberAst member; + List members = new List(); + while ((member = EnumMemberRule()) != null) + { + members.Add(member); + lastExtent = member.Extent; + } - var startExtent = customAttributes != null && customAttributes.Count > 0 - ? 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, null); - if (customAttributes != null && customAttributes.OfType().Any()) + var rCurly = NextToken(); + if (rCurly.Kind != TokenKind.RCurly) + { + UngetToken(rCurly); + ReportIncompleteInput( + After(lCurly), + rCurly.Extent, + nameof(ParserStrings.MissingEndCurlyBrace), + ParserStrings.MissingEndCurlyBrace); + } + + var startExtent = customAttributes != null && customAttributes.Count > 0 + ? customAttributes[0].Extent + : enumToken.Extent; + var extent = ExtentOf(startExtent, rCurly); + 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 + List nestedAsts = new List(); + nestedAsts.AddRange(customAttributes.OfType()); + nestedAsts.Add(enumDefn); + return new ErrorStatementAst(startExtent, nestedAsts); + } + + return enumDefn; + } + finally { - //no need to report error since there is error reported in method StatementRule - List nestedAsts = new List(); - nestedAsts.AddRange(customAttributes.OfType()); - nestedAsts.Add(enumDefn); - return new ErrorStatementAst(startExtent, nestedAsts); + SetTokenizerMode(oldTokenizerMode); } - return enumDefn; } private MemberAst EnumMemberRule() { - //G enum-member: - //G enum-member-name - //G enum-member-name '=' expression - //G - //G enum-member-name: - //G simple-name + // G enum-member: + // G enum-member-name + // G enum-member-name '=' expression + // G + // G enum-member-name: + // G simple-name const PropertyAttributes enumMemberAttributes = PropertyAttributes.Public | PropertyAttributes.Static | PropertyAttributes.Literal; @@ -4323,7 +4853,10 @@ private MemberAst EnumMemberRule() initialValueAst = ExpressionRule(); if (initialValueAst == null) { - ReportError(After(assignToken), () => ParserStrings.ExpectedValueExpression, assignToken.Kind.Text()); + ReportError(After(assignToken), + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + assignToken.Kind.Text()); endExtent = assignToken.Extent; missingInitializer = true; } @@ -4344,9 +4877,12 @@ private MemberAst EnumMemberRule() // If the initializer is missing, no sense in reporting another error about a missing terminator if (!missingInitializer) { - ReportIncompleteInput(After(endExtent), () => ParserStrings.MissingPropertyTerminator); + ReportIncompleteInput(After(endExtent), + nameof(ParserStrings.MissingPropertyTerminator), + ParserStrings.MissingPropertyTerminator); } } + SkipNewlinesAndSemicolons(); // Include the semicolon in the extent but not newline or rcurly as that will look weird, e.g. if an error is reported on the full extent @@ -4360,16 +4896,16 @@ private MemberAst EnumMemberRule() private StatementAst UsingStatementRule(Token usingToken) { - //G using-statement: - //G 'using' 'namespace' identifier - //G 'using' 'namespace' identifier '=' identifier - //G 'using' 'module' identifier - //G 'using' 'module' identifier '=' identifier - //G 'using' 'module' hashtable - //G 'using' 'module' identifier '=' hashtable - //G 'using' 'type' identifier '=' identifier - //G 'using' 'assembly' identifier - //G 'using' 'command' identifier '=' identifier + // G using-statement: + // G 'using' 'namespace' identifier + // G 'using' 'namespace' identifier '=' identifier + // G 'using' 'module' identifier + // G 'using' 'module' identifier '=' identifier + // G 'using' 'module' hashtable + // G 'using' 'module' identifier '=' hashtable + // G 'using' 'type' identifier '=' identifier + // G 'using' 'assembly' identifier + // G 'using' 'command' identifier '=' identifier // in case of aliasing (=), first identifier is alias name, second identifier is alias value (the real name). @@ -4406,7 +4942,9 @@ private StatementAst UsingStatementRule(Token usingToken) default: UngetToken(directiveToken); - ReportIncompleteInput(After(usingToken), () => ParserStrings.MissingUsingStatementDirective); + ReportIncompleteInput(After(usingToken), + nameof(ParserStrings.MissingUsingStatementDirective), + ParserStrings.MissingUsingStatementDirective); return new ErrorStatementAst(usingToken.Extent); } @@ -4415,13 +4953,15 @@ private StatementAst UsingStatementRule(Token usingToken) { case TokenKind.EndOfInput: case TokenKind.NewLine: - // Example: 'using module ,FooBar' + // Example: 'using module ,exampleModuleName' // GetCommandArgument will successfully return an argument for a unary array argument // but we don't want to allow that syntax with a using statement. case TokenKind.Comma: case TokenKind.Semi: { - ReportIncompleteInput(After(directiveToken), () => ParserStrings.MissingUsingItemName); + ReportIncompleteInput(After(directiveToken), + nameof(ParserStrings.MissingUsingItemName), + ParserStrings.MissingUsingItemName); return new ErrorStatementAst(ExtentOf(usingToken, directiveToken)); } } @@ -4429,15 +4969,22 @@ private StatementAst UsingStatementRule(Token usingToken) var itemAst = GetCommandArgument(CommandArgumentContext.CommandArgument, itemToken); if (itemAst == null) { - ReportError(itemToken.Extent, () => ParserStrings.InvalidValueForUsingItemName, itemToken.Text); + ReportError(itemToken.Extent, + nameof(ParserStrings.InvalidValueForUsingItemName), + ParserStrings.InvalidValueForUsingItemName, + itemToken.Text); // ErrorRecovery: If there is no identifier, skip whole 'using' line SyncOnError(true, TokenKind.Semi, TokenKind.NewLine); 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), () => ParserStrings.InvalidValueForUsingItemName, itemAst.Extent.Text); + ReportError(ExtentFromFirstOf(itemAst, itemToken), + nameof(ParserStrings.InvalidValueForUsingItemName), + ParserStrings.InvalidValueForUsingItemName, + itemAst.Extent.Text); return new ErrorStatementAst(ExtentOf(usingToken, ExtentFromFirstOf(itemAst, itemToken))); } @@ -4461,21 +5008,41 @@ 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), () => ParserStrings.MissingNamespaceAlias); + ReportIncompleteInput(After(equalsToken), + nameof(ParserStrings.MissingNamespaceAlias), + ParserStrings.MissingNamespaceAlias); 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(); @@ -4499,7 +5066,9 @@ private StatementAst UsingStatementRule(Token usingToken) if (aliasRequired) { - ReportIncompleteInput(After(itemToken), () => ParserStrings.MissingEqualsInUsingAlias); + ReportIncompleteInput(After(itemToken), + nameof(ParserStrings.MissingEqualsInUsingAlias), + ParserStrings.MissingEqualsInUsingAlias); return new ErrorStatementAst(ExtentOf(usingToken, itemAst), new Ast[] { itemAst }); } } @@ -4526,13 +5095,19 @@ private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressio { if (uri.IsUnc) { - ReportError(name.Extent, () => ParserStrings.CannotLoadAssemblyFromUncPath, assemblyName); + ReportError(name.Extent, + nameof(ParserStrings.CannotLoadAssemblyFromUncPath), + ParserStrings.CannotLoadAssemblyFromUncPath, + assemblyName); } // don't allow things like 'using assembly http://microsoft.com' if (uri.Scheme != "file") { - ReportError(name.Extent, () => ParserStrings.CannotLoadAssemblyWithUriSchema, uri.Scheme); + ReportError(name.Extent, + nameof(ParserStrings.CannotLoadAssemblyWithUriSchema), + ParserStrings.CannotLoadAssemblyWithUriSchema, + uri.Scheme); } } else @@ -4568,15 +5143,9 @@ private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressio { workingDirectory = Path.GetDirectoryName(scriptFileName); } - assemblyFileName = workingDirectory + @"\" + assemblyFileName; - } -#if !CORECLR - if (!File.Exists(assemblyFileName)) - { - GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName); + assemblyFileName = Path.Combine(workingDirectory, assemblyFileName); } -#endif } catch { @@ -4584,7 +5153,10 @@ private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressio if (assemblyFileName == null || !File.Exists(assemblyFileName)) { - ReportError(name.Extent, () => ParserStrings.ErrorLoadingAssembly, assemblyName); + ReportError(name.Extent, + nameof(ParserStrings.ErrorLoadingAssembly), + ParserStrings.ErrorLoadingAssembly, + assemblyName); } else { @@ -4597,14 +5169,14 @@ private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressio private StatementAst MethodDeclarationRule(Token functionNameToken, string className, bool isStaticMethod) { - //G method-statement: - //G new-lines:opt function-name function-parameter-declaration base-ctor-call:opt '{' script-block '}' - //G - //G base-ctor-call: // can be present only if function-name == className - //G ':' new-lines:opt 'base' new-lines:opt parenthesized-expression new-lines:opt - //G - //G function-name: - //G command-argument + // G method-statement: + // G new-lines:opt function-name function-parameter-declaration base-ctor-call:opt '{' script-block '}' + // G + // G base-ctor-call: // can be present only if function-name == className + // G ':' new-lines:opt 'base' new-lines:opt parenthesized-expression new-lines:opt + // G + // G function-name: + // G command-argument var functionName = functionNameToken.Text; List parameters; @@ -4619,7 +5191,9 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class } else { - this.ReportIncompleteInput(After(functionNameToken), () => ParserStrings.MissingMethodParameterList); + this.ReportIncompleteInput(After(functionNameToken), + nameof(ParserStrings.MissingMethodParameterList), + ParserStrings.MissingMethodParameterList); parameters = new List(); } @@ -4651,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(); @@ -4659,13 +5233,17 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class else { endErrorStatement = baseToken.Extent; - ReportIncompleteInput(After(baseToken), () => ParserStrings.MissingMethodParameterList); + ReportIncompleteInput(After(baseToken), + nameof(ParserStrings.MissingMethodParameterList), + ParserStrings.MissingMethodParameterList); } } else { endErrorStatement = colonToken.Extent; - ReportIncompleteInput(After(colonToken), () => ParserStrings.MissingBaseCtorCall); + ReportIncompleteInput(After(colonToken), + nameof(ParserStrings.MissingBaseCtorCall), + ParserStrings.MissingBaseCtorCall); } } } @@ -4674,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(); @@ -4690,7 +5266,9 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class if (endErrorStatement == null) { endErrorStatement = ExtentFromFirstOf(rParen, functionNameToken); - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingFunctionBody); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingFunctionBody), + ParserStrings.MissingFunctionBody); } } @@ -4714,6 +5292,7 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class baseCallExtent = PositionUtilities.EmptyExtent; baseKeywordExtent = PositionUtilities.EmptyExtent; } + var invokeMemberAst = new BaseCtorInvokeMemberExpressionAst(baseKeywordExtent, baseCallExtent, baseCtorCallParams); baseCtorCallStatement = new CommandExpressionAst(invokeMemberAst.Extent, invokeMemberAst, null); } @@ -4736,13 +5315,13 @@ private StatementAst MethodDeclarationRule(Token functionNameToken, string class private StatementAst FunctionDeclarationRule(Token functionToken) { - //G function-statement: - //G 'function' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' - //G 'filter' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' - //G 'workflow' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' - //G - //G function-name: - //G command-argument + // G function-statement: + // G 'function' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' + // G 'filter' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' + // G 'workflow' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}' + // G + // G function-name: + // G command-argument SkipNewlines(); @@ -4776,7 +5355,10 @@ private StatementAst FunctionDeclarationRule(Token functionToken) // ErrorRecovery: Don't continue, assume the function isn't there yet. UngetToken(functionNameToken); - ReportIncompleteInput(After(functionToken), () => ParserStrings.MissingNameAfterKeyword, functionToken.Text); + ReportIncompleteInput(After(functionToken), + nameof(ParserStrings.MissingNameAfterKeyword), + ParserStrings.MissingNameAfterKeyword, + functionToken.Text); return new ErrorStatementAst(functionToken.Extent); } @@ -4796,7 +5378,9 @@ private StatementAst FunctionDeclarationRule(Token functionToken) if (endErrorStatement == null) { endErrorStatement = ExtentFromFirstOf(rParen, functionNameToken); - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingFunctionBody); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingFunctionBody), + ParserStrings.MissingFunctionBody); } } @@ -4830,8 +5414,8 @@ private StatementAst FunctionDeclarationRule(Token functionToken) private List FunctionParameterDeclarationRule(out IScriptExtent endErrorStatement, out Token rParen) { - //G function-parameter-declaration: - //G new-lines:opt '(' parameter-list new-lines:opt ')' + // G function-parameter-declaration: + // G new-lines:opt '(' parameter-list new-lines:opt ')' List parameters = null; endErrorStatement = null; @@ -4850,18 +5434,22 @@ private List FunctionParameterDeclarationRule(out IScriptExtent en // ErrorRecovery: assume a body follows, so just keep parsing. UngetToken(rParen); - endErrorStatement = parameters.Any() ? parameters.Last().Extent : lParen.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingEndParenthesisInFunctionParameterList); + endErrorStatement = parameters.Count > 0 ? parameters.Last().Extent : lParen.Extent; + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingEndParenthesisInFunctionParameterList), + ParserStrings.MissingEndParenthesisInFunctionParameterList); } + SkipNewlines(); } + return parameters; } private StatementAst TrapStatementRule(Token trapToken) { - //G trap-statement: - //G 'trap' new-lines:opt type-literal:opt statement-block + // G trap-statement: + // G 'trap' new-lines:opt type-literal:opt statement-block var restorePoint = _tokenizer.GetRestorePoint(); SkipNewlines(); @@ -4883,7 +5471,9 @@ private StatementAst TrapStatementRule(Token trapToken) // ErrorRecovery: just return an error statement. IScriptExtent endErrorStatement = ExtentFromFirstOf(typeConstraintAst, trapToken); - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingTrapStatement); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingTrapStatement), + ParserStrings.MissingTrapStatement); return new ErrorStatementAst(ExtentOf(trapToken, endErrorStatement), GetNestedErrorAsts(typeConstraintAst)); } @@ -4903,11 +5493,11 @@ private StatementAst TrapStatementRule(Token trapToken) /// A catch clause, or null there is no catch or there was some error. private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref List errorAsts) { - //G catch-clause: - //G new-lines:opt 'catch' catch-type-list:opt statement-block - //G catch-type-list: - //G new-lines:opt type-literal - //G catch-type-list new-lines:opt ',' new-lines:opt type-literal + // G catch-clause: + // G new-lines:opt 'catch' catch-type-list:opt statement-block + // G catch-type-list: + // G new-lines:opt type-literal + // G catch-type-list new-lines:opt ',' new-lines:opt type-literal SkipNewlines(); Token catchToken = NextToken(); @@ -4919,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(); @@ -4932,8 +5522,11 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L // ErrorRecovery: Just consume the comma, pretend it wasn't there and look for the handler block. endErrorStatement = commaToken.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingTypeLiteralToken); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingTypeLiteralToken), + ParserStrings.MissingTypeLiteralToken); } + break; } @@ -4950,10 +5543,8 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L break; } - if (exceptionTypes == null) - { - exceptionTypes = new List(); - } + exceptionTypes ??= new List(); + exceptionTypes.Add(typeConstraintAst); SkipNewlines(); @@ -4962,8 +5553,9 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L { break; } + SkipToken(); - } while (true); + } StatementBlockAst handler = StatementBlockRule(); if (handler == null) @@ -4975,8 +5567,11 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L // ErrorRecovery: just use the "missing" block in the result ast. endErrorStatement = exceptionTypes != null ? exceptionTypes.Last().Extent : catchToken.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingCatchHandlerBlock); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingCatchHandlerBlock), + ParserStrings.MissingCatchHandlerBlock); } + if (exceptionTypes != null) { if (errorAsts == null) @@ -4988,6 +5583,7 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L errorAsts.AddRange(exceptionTypes); } } + return null; } @@ -4996,20 +5592,20 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L private StatementAst TryStatementRule(Token tryToken) { - //G try-statement: - //G 'try' statement-block catch-clauses - //G 'try' statement-block finally-clause - //G 'try' statement-block catch-clauses finally-clause - //G catch-clauses: - //G catch-clause - //G catch-clauses catch-clause - //G catch-clause: - //G new-lines:opt 'catch' catch-type-list:opt statement-block - //G catch-type-list: - //G new-lines:opt type-literal - //G catch-type-list new-lines:opt ',' new-lines:opt type-literal - //G finally-clause: - //G new-lines:opt 'finally' statement-block + // G try-statement: + // G 'try' statement-block catch-clauses + // G 'try' statement-block finally-clause + // G 'try' statement-block catch-clauses finally-clause + // G catch-clauses: + // G catch-clause + // G catch-clauses catch-clause + // G catch-clause: + // G new-lines:opt 'catch' catch-type-list:opt statement-block + // G catch-type-list: + // G new-lines:opt type-literal + // G catch-type-list new-lines:opt ',' new-lines:opt type-literal + // G finally-clause: + // G new-lines:opt 'finally' statement-block SkipNewlines(); @@ -5018,7 +5614,9 @@ private StatementAst TryStatementRule(Token tryToken) { // ErrorRecovery: don't parse more, return an error statement. - ReportIncompleteInput(After(tryToken), () => ParserStrings.MissingTryStatementBlock); + ReportIncompleteInput(After(tryToken), + nameof(ParserStrings.MissingTryStatementBlock), + ParserStrings.MissingTryStatementBlock); return new ErrorStatementAst(tryToken.Extent); } @@ -5046,7 +5644,9 @@ private StatementAst TryStatementRule(Token tryToken) endErrorStatement = finallyToken.Extent; ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingFinallyStatementBlock, finallyToken.Kind.Text()); + nameof(ParserStrings.MissingFinallyStatementBlock), + ParserStrings.MissingFinallyStatementBlock, + finallyToken.Kind.Text()); } } @@ -5055,7 +5655,9 @@ private StatementAst TryStatementRule(Token tryToken) // ErrorRecovery: don't parse more, return an error statement. endErrorStatement = body.Extent; - ReportIncompleteInput(After(endErrorStatement), () => ParserStrings.MissingCatchOrFinally); + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingCatchOrFinally), + ParserStrings.MissingCatchOrFinally); } if (endErrorStatement != null) @@ -5069,22 +5671,22 @@ private StatementAst TryStatementRule(Token tryToken) private StatementAst DataStatementRule(Token dataToken) { - //G data-statement: - //G 'data' new-lines:opt data-name data-commands-allowed:opt statement-block - //G data-name: - //G simple-name - //G data-commands-allowed: - //G new-lines:opt '-supportedcommand' data-commands-list - //G data-commands-list: - //G new-lines:opt data-command - //G data-commands-list ',' new-lines:opt data-command - //G data-command: - //G command-name-expr + // G data-statement: + // G 'data' new-lines:opt data-name data-commands-allowed:opt statement-block + // G data-name: + // G simple-name + // G data-commands-allowed: + // G new-lines:opt '-supportedcommand' data-commands-list + // G data-commands-list: + // G new-lines:opt data-command + // G data-commands-list ',' new-lines:opt data-command + // G data-command: + // G command-name-expr IScriptExtent endErrorStatement = null; SkipNewlines(); var dataVariableNameAst = SimpleNameRule(); - string dataVariableName = (dataVariableNameAst != null) ? dataVariableNameAst.Value : null; + string dataVariableName = dataVariableNameAst?.Value; SkipNewlines(); Token supportedCommandToken = PeekToken(); @@ -5098,7 +5700,9 @@ private StatementAst DataStatementRule(Token dataToken) // ErrorRecovery: Assume the parameter is just misspelled, look for command names next endErrorStatement = supportedCommandToken.Extent; - ReportError(endErrorStatement, () => ParserStrings.InvalidParameterForDataSectionStatement, + ReportError(endErrorStatement, + nameof(ParserStrings.InvalidParameterForDataSectionStatement), + ParserStrings.InvalidParameterForDataSectionStatement, ((ParameterToken)supportedCommandToken).ParameterName); } @@ -5116,8 +5720,10 @@ private StatementAst DataStatementRule(Token dataToken) if (endErrorStatement == null) { ReportIncompleteInput(After(commaToken ?? supportedCommandToken), - () => ParserStrings.MissingValueForSupportedCommandInDataSectionStatement); + nameof(ParserStrings.MissingValueForSupportedCommandInDataSectionStatement), + ParserStrings.MissingValueForSupportedCommandInDataSectionStatement); } + endErrorStatement = commaToken != null ? commaToken.Extent : supportedCommandToken.Extent; break; } @@ -5147,7 +5753,8 @@ private StatementAst DataStatementRule(Token dataToken) ? commands.Last().Extent : ExtentFromFirstOf(dataVariableNameAst, dataToken); ReportIncompleteInput(After(endErrorStatement), - () => ParserStrings.MissingStatementBlockForDataSection); + nameof(ParserStrings.MissingStatementBlockForDataSection), + ParserStrings.MissingStatementBlockForDataSection); } } @@ -5163,80 +5770,273 @@ private StatementAst DataStatementRule(Token dataToken) #region Pipelines - private PipelineBaseAst PipelineRule() + private PipelineBaseAst PipelineChainRule() { - //G pipeline: - //G assignment-expression - //G expression redirections:opt pipeline-tail:opt - //G command pipeline-tail:opt - //G - //G assignment-expression: - //G expression assignment-operator statement - //G - //G pipeline-tail: - //G '|' new-lines:opt command - //G '|' new-lines:opt command pipeline-tail + // G pipeline-chain: + // G pipeline + // G pipeline-chain chain-operator pipeline + // G + // G chain-operator: + // G '||' + // G '&&' - var pipelineElements = new List(); - IScriptExtent startExtent = null; + // If this feature is not enabled, + // just look for pipelines as before. + RuntimeHelpers.EnsureSufficientExecutionStack(); - Token pipeToken = null; - bool scanning = true; - bool background = false; - while (scanning) - { - CommandBaseAst commandAst; - Token assignToken = null; - ExpressionAst expr; + // 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; + ExpressionAst expr; - var oldTokenizerMode = _tokenizer.Mode; - try + // Look for an expression, + // which could either be a variable to assign to, + // or the first expression in a pipeline: "Hi" | % { $_ } + var oldTokenizerMode = _tokenizer.Mode; + try + { + SetTokenizerMode(TokenizerMode.Expression); + expr = ExpressionRule(); + if (expr != null) { - SetTokenizerMode(TokenizerMode.Expression); - expr = ExpressionRule(); - if (expr != null) + // We peek here because we are in expression mode, otherwise =0 will get scanned + // as a single token. + var token = PeekToken(); + if (token.Kind.HasTrait(TokenFlags.AssignmentOperator)) { - // We peek here because we are in expression mode, otherwise =0 will get scanned - // as a single token. - var token = PeekToken(); - if (token.Kind.HasTrait(TokenFlags.AssignmentOperator)) - { - SkipToken(); - assignToken = token; - } + SkipToken(); + assignToken = token; } } - finally + } + finally + { + SetTokenizerMode(oldTokenizerMode); + } + + // If we have an assign token, deal with assignment + if (expr != null && assignToken != null) + { + SkipNewlines(); + StatementAst statement = StatementRule(); + + if (statement == null) { - SetTokenizerMode(oldTokenizerMode); + // ErrorRecovery: we are very likely at EOF because pretty much anything should result in some + // pipeline, so just keep parsing. + IScriptExtent errorExtent = After(assignToken); + ReportIncompleteInput( + errorExtent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + assignToken.Kind.Text()); + statement = new ErrorStatementAst(errorExtent); } - if (expr != null) + return new AssignmentStatementAst( + ExtentOf(expr, statement), + expr, + assignToken.Kind, + statement, + assignToken.Extent); + } + + // Start scanning for a pipeline chain, + // possibly starting with the expression from earlier + ExpressionAst startExpression = expr; + ChainableAst currentPipelineChain = null; + Token currentChainOperatorToken = null; + Token nextToken = null; + bool background = false; + while (true) + { + // Look for the next pipeline in the chain, + // at this point we should already have parsed all assignments + // in enclosing calls to PipelineChainRule + PipelineAst nextPipeline; + Token firstPipelineToken = null; + if (startExpression != null) { - if (pipelineElements.Count > 0) - { - // ErrorRecovery: this is a semantic error, so just keep parsing. + nextPipeline = (PipelineAst)PipelineRule(startExpression); + startExpression = null; + } + else + { + // Remember the token for error reporting, + // since erroneous results in the rules still consume tokens + firstPipelineToken = PeekToken(); + nextPipeline = (PipelineAst)PipelineRule(); + } - ReportError(expr.Extent, () => ParserStrings.ExpressionsMustBeFirstInPipeline); + if (nextPipeline == null) + { + if (currentChainOperatorToken == null) + { + // We haven't seen a chain token, so the caller is responsible + // for expecting a pipeline and must manage this + return null; } - if (assignToken != null) + // See if we are responsible for reporting the issue + switch (firstPipelineToken.Kind) { + case TokenKind.EndOfInput: + // If we're at EOF, we should allow input to complete + ReportIncompleteInput( + After(currentChainOperatorToken), + nameof(ParserStrings.EmptyPipelineChainElement), + ParserStrings.EmptyPipelineChainElement, + currentChainOperatorToken.Text); + break; + + case TokenKind.Dot: + case TokenKind.Ampersand: + // If something like 'command && &' or 'command && .' was provided, + // CommandRule has already reported the error. + break; + + default: + ReportError( + ExtentOf(currentChainOperatorToken, firstPipelineToken), + nameof(ParserStrings.EmptyPipelineChainElement), + ParserStrings.EmptyPipelineChainElement, + currentChainOperatorToken.Text); + break; + } + + return new ErrorStatementAst( + ExtentOf(currentPipelineChain, currentChainOperatorToken), + currentChainOperatorToken, + new[] { currentPipelineChain }); + } + + // Look ahead for a chain operator + nextToken = PeekToken(); + switch (nextToken.Kind) + { + case TokenKind.AndAnd: + case TokenKind.OrOr: + SkipToken(); SkipNewlines(); - StatementAst statement = StatementRule(); + break; - if (statement == null) + // Background operators may also occur here + case TokenKind.Ampersand: + SkipToken(); + nextToken = PeekToken(); + + switch (nextToken.Kind) { - // ErrorRecovery: we are very likely at EOF because pretty much anything should result in some - // pipeline, so just keep parsing. + case TokenKind.AndAnd: + case TokenKind.OrOr: + SkipToken(); + ReportError(nextToken.Extent, nameof(ParserStrings.BackgroundOperatorInPipelineChain), ParserStrings.BackgroundOperatorInPipelineChain); + return new ErrorStatementAst(ExtentOf(currentPipelineChain ?? nextPipeline, nextToken.Extent)); + } + + background = true; + goto default; - IScriptExtent errorExtent = After(assignToken); - ReportIncompleteInput(errorExtent, () => ParserStrings.ExpectedValueExpression, assignToken.Kind.Text()); - statement = new ErrorStatementAst(errorExtent); + // No more chain operators -- return + default: + // If we haven't seen a chain yet, pass through the pipeline + // Simplifies the AST and prevents allocation + if (currentPipelineChain == null) + { + if (!background) + { + return nextPipeline; + } + + // Set background on the pipeline AST + nextPipeline.Background = true; + return nextPipeline; } - return new AssignmentStatementAst(ExtentOf(expr, statement), - expr, assignToken.Kind, statement, assignToken.Extent); + return new PipelineChainAst( + ExtentOf(currentPipelineChain.Extent, nextPipeline.Extent), + currentPipelineChain, + nextPipeline, + currentChainOperatorToken.Kind, + background); + } + + // Assemble the new chain statement AST + currentPipelineChain = currentPipelineChain == null + ? (ChainableAst)nextPipeline + : new PipelineChainAst( + ExtentOf(currentPipelineChain.Extent, nextPipeline.Extent), + currentPipelineChain, + nextPipeline, + currentChainOperatorToken.Kind); + + // Remember the last operator to chain the coming pipeline + currentChainOperatorToken = nextToken; + + // Look ahead to report incomplete input if needed + if (PeekToken().Kind == TokenKind.EndOfInput) + { + ReportIncompleteInput( + After(nextToken), + nameof(ParserStrings.EmptyPipelineChainElement), + ParserStrings.EmptyPipelineChainElement); + + return currentPipelineChain; + } + } + } + + private PipelineBaseAst PipelineRule( + ExpressionAst startExpression = null, + bool allowBackground = false) + { + // G pipeline: + // G assignment-expression + // G expression redirections:opt pipeline-tail:opt + // G command pipeline-tail:opt + // G + // G assignment-expression: + // G expression assignment-operator statement + // G + // G pipeline-tail: + // G new-lines:opt '|' new-lines:opt command pipeline-tail:opt + // + var pipelineElements = new List(); + IScriptExtent startExtent = null; + + Token nextToken = null; + bool scanning = true; + bool background = false; + ExpressionAst expr = startExpression; + while (scanning) + { + CommandBaseAst commandAst; + + if (expr == null) + { + // Look for an expression at the beginning of a pipeline + var oldTokenizerMode = _tokenizer.Mode; + try + { + SetTokenizerMode(TokenizerMode.Expression); + expr = ExpressionRule(); + } + finally + { + SetTokenizerMode(oldTokenizerMode); + } + } + + if (expr != null) + { + if (pipelineElements.Count > 0) + { + // ErrorRecovery: this is a semantic error, so just keep parsing. + ReportError( + expr.Extent, + nameof(ParserStrings.ExpressionsMustBeFirstInPipeline), + ParserStrings.ExpressionsMustBeFirstInPipeline); } RedirectionAst[] redirections = null; @@ -5246,10 +6046,8 @@ 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); @@ -5257,8 +6055,10 @@ private PipelineBaseAst PipelineRule() } var exprExtent = lastRedirection != null ? ExtentOf(expr, lastRedirection) : expr.Extent; - commandAst = new CommandExpressionAst(exprExtent, expr, - redirections != null ? redirections.Where(r => r != null) : null); + commandAst = new CommandExpressionAst( + exprExtent, + expr, + redirections?.Where(static r => r != null)); } else { @@ -5267,10 +6067,8 @@ private PipelineBaseAst PipelineRule() if (commandAst != null) { - if (startExtent == null) - { - startExtent = commandAst.Extent; - } + startExtent ??= commandAst.Extent; + pipelineElements.Add(commandAst); } else if (pipelineElements.Count > 0 || PeekToken().Kind == TokenKind.Pipe) @@ -5279,51 +6077,75 @@ private PipelineBaseAst PipelineRule() // If the first pipe element is null, the position points to the pipe (ideally it would // point before, but the pipe could be the first character), otherwise the empty element // is after the pipe character. - IScriptExtent errorPosition = pipeToken != null ? After(pipeToken) : PeekToken().Extent; - ReportIncompleteInput(errorPosition, () => ParserStrings.EmptyPipeElement); + IScriptExtent errorPosition = nextToken != null ? After(nextToken) : PeekToken().Extent; + ReportIncompleteInput( + errorPosition, + nameof(ParserStrings.EmptyPipeElement), + ParserStrings.EmptyPipeElement); } - pipeToken = PeekToken(); + // Reset the expression for the next loop + expr = null; + nextToken = PeekToken(); + + // Skip newlines before pipe tokens to support (pipe)line continuation when pipe + // tokens start the next line of script + if (nextToken.Kind == TokenKind.NewLine && _tokenizer.IsPipeContinuation(nextToken.Extent)) + { + SkipNewlines(); + nextToken = PeekToken(); + } - switch (pipeToken.Kind) + switch (nextToken.Kind) { case TokenKind.Semi: case TokenKind.NewLine: case TokenKind.RParen: case TokenKind.RCurly: case TokenKind.EndOfInput: + // Handled by invoking rule scanning = false; continue; + + case TokenKind.AndAnd: + case TokenKind.OrOr: + scanning = false; + continue; + case TokenKind.Ampersand: + if (!allowBackground) + { + // Handled by invoking rule + scanning = false; + continue; + } + SkipToken(); scanning = false; background = true; break; + case TokenKind.Pipe: SkipToken(); SkipNewlines(); if (PeekToken().Kind == TokenKind.EndOfInput) { scanning = false; - ReportIncompleteInput(After(pipeToken), () => ParserStrings.EmptyPipeElement); - } - break; - case TokenKind.AndAnd: - case TokenKind.OrOr: - // Parse in a manner similar to a pipe, but issue an error (for now, but should implement this for V3.) - SkipToken(); - SkipNewlines(); - ReportError(pipeToken.Extent, () => ParserStrings.InvalidEndOfLine, pipeToken.Text); - if (PeekToken().Kind == TokenKind.EndOfInput) - { - scanning = false; + ReportIncompleteInput( + After(nextToken), + nameof(ParserStrings.EmptyPipeElement), + ParserStrings.EmptyPipeElement); } + break; default: // ErrorRecovery: don't eat the token, assume it belongs to something else. - - ReportError(pipeToken.Extent, () => ParserStrings.UnexpectedToken, pipeToken.Text); + ReportError( + nextToken.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + nextToken.Text); scanning = false; break; } @@ -5339,15 +6161,15 @@ private PipelineBaseAst PipelineRule() private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent) { - //G redirections: - //G redirection - //G redirections redirection - //G redirection: - //G merging-redirection-operator - //G file-redirection-operator redirected-file-name - //G redirected-file-name: - //G command-argument - //G primary-expression + // G redirections: + // G redirection + // G redirections redirection + // G redirection: + // G merging-redirection-operator + // G file-redirection-operator redirected-file-name + // G redirected-file-name: + // G command-argument + // G primary-expression RedirectionAst result; @@ -5360,14 +6182,19 @@ private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, Redire { // ErrorRecovery: Just pretend we have a filename and continue parsing. - ReportError(After(redirectionToken), () => ParserStrings.MissingFileSpecification); + ReportError(After(redirectionToken), + nameof(ParserStrings.MissingFileSpecification), + ParserStrings.MissingFileSpecification); filename = new ErrorExpressionAst(redirectionToken.Extent); } if (fileRedirectionToken == null) { // Must be an input redirection - ReportError(redirectionToken.Extent, () => ParserStrings.RedirectionNotSupported, redirectionToken.Text); + ReportError(redirectionToken.Extent, + nameof(ParserStrings.RedirectionNotSupported), + ParserStrings.RedirectionNotSupported, + redirectionToken.Text); extent = ExtentOf(redirectionToken, filename); return null; } @@ -5386,7 +6213,10 @@ private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, Redire // Have we seen something like 1>&2 or 2>&3 // ErrorRecovery: This is just a semantic error, so no special recovery. - ReportError(redirectionToken.Extent, () => ParserStrings.RedirectionNotSupported, mergingRedirectionToken.Text); + ReportError(redirectionToken.Extent, + nameof(ParserStrings.RedirectionNotSupported), + ParserStrings.RedirectionNotSupported, + mergingRedirectionToken.Text); toStream = RedirectionStream.Output; } else if (fromStream == toStream) @@ -5394,7 +6224,10 @@ private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, Redire // Make sure 1>&1, 2>&2, etc. is an error. // ErrorRecovery: This is just a semantic error, so no special recovery. - ReportError(redirectionToken.Extent, () => ParserStrings.RedirectionNotSupported, mergingRedirectionToken.Text); + ReportError(redirectionToken.Extent, + nameof(ParserStrings.RedirectionNotSupported), + ParserStrings.RedirectionNotSupported, + mergingRedirectionToken.Text); } result = new MergingRedirectionAst(mergingRedirectionToken.Extent, mergingRedirectionToken.FromStream, toStream); @@ -5419,7 +6252,11 @@ private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, Redire default: throw PSTraceSource.NewArgumentOutOfRangeException("result.FromStream", result.FromStream); } - ReportError(result.Extent, () => ParserStrings.StreamAlreadyRedirected, errorStream); + + ReportError(result.Extent, + nameof(ParserStrings.StreamAlreadyRedirected), + ParserStrings.StreamAlreadyRedirected, + errorStream); } extent = result.Extent; @@ -5492,8 +6329,11 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t // ErrorRecovery: stop looking for additional arguments, exclude the trailing comma - ReportIncompleteInput(After(commaToken), () => ParserStrings.MissingExpression, ","); - return new ErrorExpressionAst(ExtentOf(commandArgs.First(), commaToken), commandArgs); + ReportIncompleteInput(After(commaToken), + nameof(ParserStrings.MissingExpression), + ParserStrings.MissingExpression, + ","); + return new ErrorExpressionAst(ExtentOf(commandArgs[0], commaToken), commandArgs); case TokenKind.SplattedVariable: case TokenKind.Variable: @@ -5533,11 +6373,12 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t exprAst = new StringConstantExpressionAst(genericToken.Extent, genericToken.Value, StringConstantType.BareWord); // If this is a verbatim argument, then don't continue peeking - if (String.Equals(genericToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(genericToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase)) { foundVerbatimArgument = true; } } + break; default: @@ -5558,6 +6399,7 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t token.SetIsCommandArgument(); break; } + break; } @@ -5576,11 +6418,10 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t { break; } + commaToken = token; - if (commandArgs == null) - { - commandArgs = new List(); - } + commandArgs ??= new List(); + commandArgs.Add(exprAst); SkipToken(); @@ -5602,30 +6443,30 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t internal Ast CommandRule(bool forDynamicKeyword) { - //G command: - //G command-name command-elements:opt - //G command-invocation-operator command-module:opt command-name-expr command-elements:opt - //G command-invocation-operator: one of - //G '&' '.' - //G command-module: - //G primary-expression - //G command-name: - //G generic-token - //G generic-token-with-subexpr - //G generic-token-with-subexpr: No whitespace is allowed between ) and command-name. - //G generic-token-with-subexpr-start statement-list:opt ) command-name - //G command-name-expr: - //G command-name - //G primary-expression - //G command-elements: - //G command-element - //G command-elements command-element - //G command-element: - //G command-parameter - //G command-argument - //G redirection - //G command-argument: - //G command-name-expr + // G command: + // G command-name command-elements:opt + // G command-invocation-operator command-module:opt command-name-expr command-elements:opt + // G command-invocation-operator: one of + // G '&' '.' + // G command-module: + // G primary-expression + // G command-name: + // G generic-token + // G generic-token-with-subexpr + // G generic-token-with-subexpr: No whitespace is allowed between ) and command-name. + // G generic-token-with-subexpr-start statement-list:opt ) command-name + // G command-name-expr: + // G command-name + // G primary-expression + // G command-elements: + // G command-element + // G command-elements command-element + // G command-element: + // G command-parameter + // G command-argument + // G redirection + // G command-argument: + // G command-name-expr Token firstToken; bool dotSource, ampersand; @@ -5695,7 +6536,9 @@ internal Ast CommandRule(bool forDynamicKeyword) case TokenKind.Comma: endExtent = token.Extent; - ReportError(token.Extent, () => ParserStrings.MissingArgument); + ReportError(token.Extent, + nameof(ParserStrings.MissingArgument), + ParserStrings.MissingArgument); SkipNewlines(); break; @@ -5708,6 +6551,7 @@ internal Ast CommandRule(bool forDynamicKeyword) elements.Add(commandName); break; } + var parameterToken = (ParameterToken)token; ExpressionAst parameterArgs; IScriptExtent extent; @@ -5719,7 +6563,10 @@ internal Ast CommandRule(bool forDynamicKeyword) if (parameterArgs == null) { extent = parameterToken.Extent; - ReportError(After(extent), () => ParserStrings.ParameterRequiresArgument, parameterToken.Text); + ReportError(After(extent), + nameof(ParserStrings.ParameterRequiresArgument), + ParserStrings.ParameterRequiresArgument, + parameterToken.Text); } else { @@ -5741,10 +6588,8 @@ 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); } else @@ -5756,6 +6601,7 @@ internal Ast CommandRule(bool forDynamicKeyword) endExtent = token.Extent; elements.Add(new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord)); } + break; default: @@ -5773,7 +6619,7 @@ internal Ast CommandRule(bool forDynamicKeyword) // If this is the special verbatim argument syntax, look for the next element StringToken argumentToken = token as StringToken; - if ((argumentToken != null) && String.Equals(argumentToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase)) + if ((argumentToken != null) && string.Equals(argumentToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase)) { elements.Add(ast); endExtent = ast.Extent; @@ -5794,6 +6640,7 @@ internal Ast CommandRule(bool forDynamicKeyword) endExtent = ast.Extent; elements.Add(ast); } + break; } @@ -5816,8 +6663,12 @@ internal Ast CommandRule(bool forDynamicKeyword) if (dotSource || ampersand) { IScriptExtent extent = firstToken.Extent; - ReportError(extent, () => ParserStrings.MissingExpression, firstToken.Text); + ReportError(extent, + nameof(ParserStrings.MissingExpression), + ParserStrings.MissingExpression, + firstToken.Text); } + return null; } @@ -5829,52 +6680,161 @@ 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 #region Expressions - private ExpressionAst ExpressionRule() - { - //G expression: - //G logical-expression - //G - //G logical-expression: - //G bitwise-expression - //G logical-expression '-and' new-lines:opt bitwise-expression - //G logical-expression '-or' new-lines:opt bitwise-expression - //G logical-expression '-xor' new-lines:opt bitwise-expression - //G - //G bitwise-expression: - //G comparison-expression - //G bitwise-expression '-band' new-lines:opt comparison-expression - //G bitwise-expression '-bor' new-lines:opt comparison-expression - //G bitwise-expression '-bxor' new-lines:opt comparison-expression - //G - //G comparison-expression: - //G additive-expression - //G comparison-expression comparison-operator new-lines:opt additive-expression - //G - //G additive-expression: - //G multiplicative-expression - //G additive-expression '+' new-lines:opt multiplicative-expression - //G additive-expression dash new-lines:opt multiplicative-expression - //G - //G multiplicative-expression: - //G format-expression - //G multiplicative-expression '*' new-lines:opt format-expression - //G multiplicative-expression '/' new-lines:opt format-expression - //G multiplicative-expression '%' new-lines:opt format-expression - //G - //G format-expression: - //G range-expression - //G format-expression format-operator new-lines:opt range-expression - //G - //G range-expression: - //G array-literal-expression - //G range-expression '..' new-lines:opt array-literal-expression + /// Parse an expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst ExpressionRule(bool endNumberOnTernaryOpChars = false) + { + // G expression: + // G logical-expression + // G + // G logical-expression: + // G binary-expression + // G ternary-expression + // G + // G ternary-expression: + // G binary-expression '?' new-lines:opt ternary-expression new-lines:opt ':' new-lines:opt ternary-expression + + RuntimeHelpers.EnsureSufficientExecutionStack(); + var oldTokenizerMode = _tokenizer.Mode; + try + { + SetTokenizerMode(TokenizerMode.Expression); + + ExpressionAst condition = BinaryExpressionRule(endNumberOnTernaryOpChars); + if (condition == null) + { + return null; + } + + Token token = PeekToken(); + + if (token.Kind != TokenKind.QuestionMark) + { + return condition; + } + + SkipToken(); + SkipNewlines(); + + // We have seen the ternary operator '?' and now expecting the 'IfTrue' expression. + ExpressionAst ifTrue = ExpressionRule(endNumberOnTernaryOpChars: true); + if (ifTrue == null) + { + // ErrorRecovery: create an error expression to fill out the ast and keep parsing. + IScriptExtent extent = After(token); + + ReportIncompleteInput( + extent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + token.Text); + ifTrue = new ErrorExpressionAst(extent); + } + + SkipNewlines(); + + token = NextToken(); + if (token.Kind != TokenKind.Colon) + { + var componentAsts = new List() { condition }; + + // ErrorRecovery: we have done the expression parsing and should try parsing something else. + UngetToken(token); + + // Don't bother reporting this error if we already reported an empty 'IfTrue' operand error. + if (ifTrue is not ErrorExpressionAst) + { + componentAsts.Add(ifTrue); + ReportIncompleteInput( + token.Extent, + nameof(ParserStrings.MissingColonInTernaryExpression), + ParserStrings.MissingColonInTernaryExpression); + } + + return new ErrorExpressionAst(ExtentOf(condition, Before(token)), componentAsts); + } + + SkipNewlines(); + + ExpressionAst ifFalse = ExpressionRule(endNumberOnTernaryOpChars: true); + if (ifFalse == null) + { + // ErrorRecovery: create an error expression to fill out the ast and keep parsing. + IScriptExtent extent = After(token); + + ReportIncompleteInput( + extent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + token.Text); + ifFalse = new ErrorExpressionAst(extent); + } + + return new TernaryExpressionAst(ExtentOf(condition, ifFalse), condition, ifTrue, ifFalse); + } + finally + { + SetTokenizerMode(oldTokenizerMode); + } + } + + /// Parse a binary expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst BinaryExpressionRule(bool endNumberOnTernaryOpChars = false) + { + // G binary-expression: + // G bitwise-expression + // G binary-expression '-and' new-lines:opt bitwise-expression + // G binary-expression '-or' new-lines:opt bitwise-expression + // G binary-expression '-xor' new-lines:opt bitwise-expression + // G + // G bitwise-expression: + // G comparison-expression + // G bitwise-expression '-band' new-lines:opt comparison-expression + // G bitwise-expression '-bor' new-lines:opt comparison-expression + // G bitwise-expression '-bxor' new-lines:opt comparison-expression + // G + // G comparison-expression: + // G nullcoalesce-expression + // G comparison-expression comparison-operator new-lines:opt nullcoalesce-expression + // G + // G nullcoalesce-expression: + // G additive-expression + // G nullcoalesce-expression '??' new-lines:opt additive-expression + // G + // G additive-expression: + // G multiplicative-expression + // G additive-expression '+' new-lines:opt multiplicative-expression + // G additive-expression dash new-lines:opt multiplicative-expression + // G + // G multiplicative-expression: + // G format-expression + // G multiplicative-expression '*' new-lines:opt format-expression + // G multiplicative-expression '/' new-lines:opt format-expression + // G multiplicative-expression '%' new-lines:opt format-expression + // G + // G format-expression: + // G range-expression + // G format-expression format-operator new-lines:opt range-expression + // G + // G range-expression: + // G array-literal-expression + // G range-expression '..' new-lines:opt array-literal-expression RuntimeHelpers.EnsureSufficientExecutionStack(); var oldTokenizerMode = _tokenizer.Mode; try @@ -5882,7 +6842,7 @@ private ExpressionAst ExpressionRule() SetTokenizerMode(TokenizerMode.Expression); ExpressionAst lhs, rhs; - ExpressionAst expr = ArrayLiteralRule(); + ExpressionAst expr = ArrayLiteralRule(endNumberOnTernaryOpChars); if (expr == null) { @@ -5898,6 +6858,11 @@ private ExpressionAst ExpressionRule() { return ErrorRecoveryParameterInExpression(paramToken, expr); } + + return expr; + } + else if (token.Kind == TokenKind.AndAnd || token.Kind == TokenKind.OrOr) + { return expr; } @@ -5914,17 +6879,24 @@ private ExpressionAst ExpressionRule() { SkipNewlines(); - expr = ArrayLiteralRule(); + // We have seen a binary operator token and now expecting the right-hand-side expression. + expr = ArrayLiteralRule(endNumberOnTernaryOpChars: true); if (expr == null) { // ErrorRecovery: create an error expression to fill out the ast and keep parsing. - IScriptExtent extent = After(token); + // Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used // when a case insensitive operator is used. - ReportIncompleteInput(extent, () => ParserStrings.ExpectedValueExpression, token.Text); + ReportIncompleteInput( + extent, + nameof(ParserStrings.ExpectedValueExpression), + ParserStrings.ExpectedValueExpression, + token.Text); + expr = new ErrorExpressionAst(extent); } + operandStack.Push(expr); token = NextToken(); @@ -5945,12 +6917,16 @@ private ExpressionAst ExpressionRule() Token op = operatorStack.Pop(); operandStack.Push(new BinaryExpressionAst(ExtentOf(lhs, rhs), lhs, op.Kind, rhs, op.Extent)); if (operatorStack.Count == 0) + { break; + } precedence = operatorStack.Peek().Kind.GetBinaryPrecedence(); } + operatorStack.Push(token); precedence = newPrecedence; } + rhs = operandStack.Pop(); Diagnostics.Assert(operandStack.Count == operatorStack.Count, "Stacks out of sync"); while (operandStack.Count > 0) @@ -5979,7 +6955,10 @@ private ExpressionAst ErrorRecoveryParameterInExpression(ParameterToken paramTok // that it's an incomplete operator. This simplifies analysis later, e.g. trying to autocomplete // operators. - ReportError(paramToken.Extent, () => ParserStrings.UnexpectedToken, paramToken.Text); + ReportError(paramToken.Extent, + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken, + paramToken.Text); SkipToken(); return new ErrorExpressionAst(ExtentOf(expr, paramToken), new Ast[] { @@ -5987,13 +6966,23 @@ private ExpressionAst ErrorRecoveryParameterInExpression(ParameterToken paramTok new CommandParameterAst(paramToken.Extent, paramToken.ParameterName, null, paramToken.Extent)}); } - private ExpressionAst ArrayLiteralRule() + /// Parse an array literal expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst ArrayLiteralRule(bool endNumberOnTernaryOpChars = false) { - //G array-literal-expression: - //G unary-expression - //G unary-expression ',' new-lines:opt array-literal-expression + // G array-literal-expression: + // G unary-expression + // G unary-expression ',' new-lines:opt array-literal-expression + ExpressionAst lastExpr = UnaryExpressionRule(endNumberOnTernaryOpChars); + if (lastExpr == null) + { + return null; + } - ExpressionAst lastExpr = UnaryExpressionRule(); ExpressionAst firstExpr = lastExpr; Token commaToken = PeekToken(); @@ -6009,16 +6998,20 @@ private ExpressionAst ArrayLiteralRule() SkipToken(); SkipNewlines(); - lastExpr = UnaryExpressionRule(); + // We have seen a comma token and now expecting an expression as an array element. + lastExpr = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (lastExpr == null) { // ErrorRecovery: create an error expression for the ast and break. - - ReportIncompleteInput(After(commaToken), () => ParserStrings.MissingExpressionAfterToken, commaToken.Text); + ReportIncompleteInput(After(commaToken), + nameof(ParserStrings.MissingExpressionAfterToken), + ParserStrings.MissingExpressionAfterToken, + commaToken.Text); lastExpr = new ErrorExpressionAst(commaToken.Extent); arrayValues.Add(lastExpr); break; } + arrayValues.Add(lastExpr); commaToken = PeekToken(); @@ -6027,43 +7020,64 @@ private ExpressionAst ArrayLiteralRule() return new ArrayLiteralAst(ExtentOf(firstExpr, lastExpr), arrayValues); } - private ExpressionAst UnaryExpressionRule() - { - //G unary-expression: - //G primary-expression - //G expression-with-unary-operator - //G - //G expression-with-unary-operator: - //G ',' new-lines:opt unary-expression - //G '-not' new-lines:opt unary-expression - //G '!' new-lines:opt unary-expression - //G '-bnot' new-lines:opt unary-expression - //G '+' new-lines:opt unary-expression - //G dash new-lines:opt unary-expression - //G pre-increment-expression - //G pre-decrement-expression - //G cast-expression - //G '-split' new-lines:opt unary-expression - //G '-join' new-lines:opt unary-expression - //G - //G pre-increment-expression: - //G '++' new-lines:opt unary-expression - //G - //G pre-decrement-expression: - //G dashdash new-lines:opt unary-expression - //G - //G cast-expression: - //G type-literal unary-expression + /// Parse an unary expression. + /// + /// When it's known for sure that we are expecting an expression, allowing a generic token like '12?' or '12:' is + /// not useful. In those cases, we force to start a new token upon seeing '?' and ':' when scanning for a number + /// by setting this parameter to true, hoping to find a ternary expression. + /// + private ExpressionAst UnaryExpressionRule(bool endNumberOnTernaryOpChars = false) + { + // G unary-expression: + // G primary-expression + // G expression-with-unary-operator + // G + // G expression-with-unary-operator: + // G ',' new-lines:opt unary-expression + // G '-not' new-lines:opt unary-expression + // G '!' new-lines:opt unary-expression + // G '-bnot' new-lines:opt unary-expression + // G '+' new-lines:opt unary-expression + // G dash new-lines:opt unary-expression + // G pre-increment-expression + // G pre-decrement-expression + // G cast-expression + // G '-split' new-lines:opt unary-expression + // G '-join' new-lines:opt unary-expression + // G + // G pre-increment-expression: + // G '++' new-lines:opt unary-expression + // G + // G pre-decrement-expression: + // G dashdash new-lines:opt unary-expression + // G + // G cast-expression: + // G type-literal unary-expression RuntimeHelpers.EnsureSufficientExecutionStack(); ExpressionAst expr = null; Token token; bool oldAllowSignedNumbers = _tokenizer.AllowSignedNumbers; + bool oldForceEndNumberOnTernaryOperators = _tokenizer.ForceEndNumberOnTernaryOpChars; try { _tokenizer.AllowSignedNumbers = true; - if (_ungotToken != null && _ungotToken.Kind == TokenKind.Minus) + _tokenizer.ForceEndNumberOnTernaryOpChars = endNumberOnTernaryOpChars; + + if (_ungotToken != null) { - Resync(_ungotToken); + // Possibly a signed number. Need to resync. + bool needResync = _ungotToken.Kind == TokenKind.Minus; + + if (!needResync) + { + // A generic token possibly composed of numbers and ternary operator chars. Need to resync. + needResync = endNumberOnTernaryOpChars && _ungotToken.Kind == TokenKind.Generic; + } + + if (needResync) + { + Resync(_ungotToken); + } } token = PeekToken(); @@ -6071,6 +7085,7 @@ private ExpressionAst UnaryExpressionRule() finally { _tokenizer.AllowSignedNumbers = oldAllowSignedNumbers; + _tokenizer.ForceEndNumberOnTernaryOpChars = oldForceEndNumberOnTernaryOperators; } ExpressionAst child; @@ -6083,7 +7098,9 @@ private ExpressionAst UnaryExpressionRule() SkipToken(); SkipNewlines(); - child = UnaryExpressionRule(); + + // We have seen a unary operator token and now expecting an expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child != null) { if (token.Kind == TokenKind.Comma) @@ -6099,10 +7116,15 @@ private ExpressionAst UnaryExpressionRule() { // ErrorRecovery: don't bother constructing a unary expression, but we know we must have // some sort of expression, so return an error expression. - + // // Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used // when a case insensitive operator is used. - ReportIncompleteInput(After(token), () => ParserStrings.MissingExpressionAfterOperator, token.Text); + ReportIncompleteInput( + After(token), + nameof(ParserStrings.MissingExpressionAfterOperator), + ParserStrings.MissingExpressionAfterOperator, + token.Text); + return new ErrorExpressionAst(token.Extent); } } @@ -6119,49 +7141,56 @@ private ExpressionAst UnaryExpressionRule() if (lastAttribute is AttributeAst) { SkipNewlines(); - child = UnaryExpressionRule(); + + // We are now expecting a child expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child == null) { // ErrorRecovery: We have a list of attributes, and we know it's not before a param statement, // so we know we must have some sort of expression. Return an error expression then. + ReportIncompleteInput( + lastAttribute.Extent, + nameof(ParserStrings.UnexpectedAttribute), + ParserStrings.UnexpectedAttribute, + lastAttribute.TypeName.FullName); - ReportIncompleteInput(lastAttribute.Extent, - () => 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); } else { - Diagnostics.Assert(_ungotToken == null || ErrorList.Count > 0, - "Unexpected lookahead from AttributeListRule."); + Diagnostics.Assert( + _ungotToken == null || ErrorList.Count > 0, + "Unexpected lookahead from AttributeListRule."); + // If we've looked ahead, don't go looking for a member access token, we've already issued an error, // just assume we're not trying to access a member. var memberAccessToken = _ungotToken != null ? null : NextMemberAccessToken(false); if (memberAccessToken != null) { - expr = CheckPostPrimaryExpressionOperators(memberAccessToken, - new TypeExpressionAst(lastAttribute.Extent, - lastAttribute.TypeName)); + expr = CheckPostPrimaryExpressionOperators( + memberAccessToken, + new TypeExpressionAst(lastAttribute.Extent, lastAttribute.TypeName)); } else { token = PeekToken(); if (token.Kind != TokenKind.NewLine && token.Kind != TokenKind.Comma) { - child = UnaryExpressionRule(); + // We are now expecting a child expression. + child = UnaryExpressionRule(endNumberOnTernaryOpChars: true); if (child != null) { - expr = new ConvertExpressionAst(ExtentOf(lastAttribute, child), - (TypeConstraintAst)lastAttribute, child); + expr = new ConvertExpressionAst( + ExtentOf(lastAttribute, child), + (TypeConstraintAst)lastAttribute, child); } } } - 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) @@ -6198,22 +7227,22 @@ private ExpressionAst UnaryExpressionRule() private ExpressionAst PrimaryExpressionRule(bool withMemberAccess) { - //G primary-expression: - //G value - //G member-access - //G element-access - //G invocation-expression - //G post-increment-expression - //G post-decrement-expression - //G value: - //G parenthesized-expression - //G sub-expression - //G array-expression - //G script-block-expression - //G hash-literal-expression - //G literal - //G type-literal - //G variable + // G primary-expression: + // G value + // G member-access + // G element-access + // G invocation-expression + // G post-increment-expression + // G post-decrement-expression + // G value: + // G parenthesized-expression + // G sub-expression + // G array-expression + // G script-block-expression + // G hash-literal-expression + // G literal + // G type-literal + // G variable ExpressionAst expr; Token token = NextToken(); @@ -6265,6 +7294,7 @@ private ExpressionAst PrimaryExpressionRule(bool withMemberAccess) { return expr; } + return CheckPostPrimaryExpressionOperators(NextMemberAccessToken(true), expr); } @@ -6282,6 +7312,7 @@ private ExpressionAst CheckUsingVariable(VariableToken variableToken, bool withM return new UsingExpressionAst(childExpr.Extent, childExpr); } + return new VariableExpressionAst(variableToken); } @@ -6290,16 +7321,17 @@ private ExpressionAst CheckPostPrimaryExpressionOperators(Token token, Expressio while (token != null) { // To support fluent style programming, allow newlines after the member access operator. - V3SkipNewlines(); + SkipNewlines(); - if (token.Kind == TokenKind.Dot || token.Kind == TokenKind.ColonColon) + if (token.Kind == TokenKind.Dot || token.Kind == TokenKind.ColonColon || token.Kind == TokenKind.QuestionDot) { expr = MemberAccessRule(expr, token); } - else if (token.Kind == TokenKind.LBracket) + else if (token.Kind == TokenKind.LBracket || token.Kind == TokenKind.QuestionLBracket) { expr = ElementAccessRule(expr, token); } + token = NextMemberAccessToken(true); } @@ -6308,14 +7340,14 @@ private ExpressionAst CheckPostPrimaryExpressionOperators(Token token, Expressio private ExpressionAst HashExpressionRule(Token atCurlyToken, bool parsingSchemaElement) { - //G hash-literal-expression: - //G '@{' new-lines:opt hash-literal-body:opt new-lines:opt '}' - //G hash-literal-body: - //G hash-entry - //G hash-literal-body statement-terminators hash-entry - //G statement-terminators: - //G statement-terminator - //G statement-terminators statement-terminator + // G hash-literal-expression: + // G '@{' new-lines:opt hash-literal-body:opt new-lines:opt '}' + // G hash-literal-body: + // G hash-entry + // G hash-literal-body statement-terminators hash-entry + // G statement-terminators: + // G statement-terminator + // G statement-terminators statement-terminator SkipNewlines(); @@ -6327,6 +7359,7 @@ private ExpressionAst HashExpressionRule(Token atCurlyToken, bool parsingSchemaE { break; } + keyValuePairs.Add(pair); Token token = PeekToken(); @@ -6343,11 +7376,20 @@ private ExpressionAst HashExpressionRule(Token atCurlyToken, bool parsingSchemaE if (rCurly.Kind != TokenKind.RCurly) { UngetToken(rCurly); - // Note - the error handling function inspects the error message body to extra the ParserStrings property name. It uses this value as the errorid. - var errorMessageExpression = parsingSchemaElement ? - (Expression>)(() => ParserStrings.IncompletePropertyAssignmentBlock) - : (Expression>)(() => ParserStrings.MissingEndCurlyBrace); - ReportIncompleteInput(After(atCurlyToken), rCurly.Extent, errorMessageExpression); + string errorId; + string errorMsg; + if (parsingSchemaElement) + { + errorId = nameof(ParserStrings.IncompletePropertyAssignmentBlock); + errorMsg = ParserStrings.IncompletePropertyAssignmentBlock; + } + else + { + errorId = nameof(ParserStrings.MissingEndCurlyBrace); + errorMsg = ParserStrings.MissingEndCurlyBrace; + } + + ReportIncompleteInput(After(atCurlyToken), rCurly.Extent, errorId, errorMsg); endExtent = Before(rCurly); } else @@ -6362,8 +7404,8 @@ private ExpressionAst HashExpressionRule(Token atCurlyToken, bool parsingSchemaE private KeyValuePair GetKeyValuePair(bool parsingSchemaElement) { - //G hash-entry: - //G key-expression '=' new-lines:opt statement + // G hash-entry: + // G key-expression '=' new-lines:opt statement Token equals; ExpressionAst key; @@ -6391,11 +7433,21 @@ private KeyValuePair GetKeyValuePair(bool parsingSchemaElement) UngetToken(equals); IScriptExtent errorExtent = After(key); - // Note - the error handling function inspects the error message body to extra the ParserStrings property name. It uses this value as the errorid. - var errorMessageExpression = parsingSchemaElement - ? (() => (ParserStrings.MissingEqualsInPropertyAssignmentBlock)) - : (Expression>)(() => ParserStrings.MissingEqualsInHashLiteral); - ReportError(errorExtent, errorMessageExpression); + + string errorId; + string errorMsg; + if (parsingSchemaElement) + { + errorId = nameof(ParserStrings.MissingEqualsInPropertyAssignmentBlock); + errorMsg = ParserStrings.MissingEqualsInPropertyAssignmentBlock; + } + else + { + errorId = nameof(ParserStrings.MissingEqualsInHashLiteral); + errorMsg = ParserStrings.MissingEqualsInHashLiteral; + } + + ReportError(errorExtent, errorId, errorMsg); SyncOnError(false, TokenKind.RCurly, TokenKind.Semi, TokenKind.NewLine); return new KeyValuePair(key, new ErrorStatementAst(errorExtent)); } @@ -6412,11 +7464,21 @@ private KeyValuePair GetKeyValuePair(bool parsingSchemaElement) // ErrorRecovery: pretend we saw a statement and keep parsing. IScriptExtent errorExtent = After(equals); - // Note - the error handling function inspects the error message body to extra the ParserStrings property name. It uses this value as the errorid. - var errorMessageExpression = parsingSchemaElement ? - (Expression>)(() => ParserStrings.MissingEqualsInPropertyAssignmentBlock) - : (Expression>)(() => ParserStrings.MissingStatementInHashLiteral); - ReportIncompleteInput(errorExtent, errorMessageExpression); + + string errorId; + string errorMsg; + if (parsingSchemaElement) + { + errorId = nameof(ParserStrings.MissingEqualsInPropertyAssignmentBlock); + errorMsg = ParserStrings.MissingEqualsInPropertyAssignmentBlock; + } + else + { + errorId = nameof(ParserStrings.MissingStatementInHashLiteral); + errorMsg = ParserStrings.MissingStatementInHashLiteral; + } + + ReportIncompleteInput(errorExtent, errorId, errorMsg); statement = new ErrorStatementAst(errorExtent); } } @@ -6430,8 +7492,8 @@ private KeyValuePair GetKeyValuePair(bool parsingSchemaElement) private ExpressionAst ScriptBlockExpressionRule(Token lCurly) { - //G script-block-expression: - //G '{' new-lines:opt script-block new-lines:opt '}' + // G script-block-expression: + // G '{' new-lines:opt script-block new-lines:opt '}' ScriptBlockAst scriptBlockAst; @@ -6456,10 +7518,10 @@ private ExpressionAst ScriptBlockExpressionRule(Token lCurly) private ExpressionAst SubExpressionRule(Token firstToken) { - //G array-expression: - //G '@(' new-lines:opt statement-list:opt new-lines:opt ')' - //G sub-expression: - //G '$(' new-lines:opt statement-list:opt new-lines:opt ')' + // G array-expression: + // G '@(' new-lines:opt statement-list:opt new-lines:opt ')' + // G sub-expression: + // G '$(' new-lines:opt statement-list:opt new-lines:opt ')' IScriptExtent statementListExtent; List traps = new List(); @@ -6482,7 +7544,9 @@ private ExpressionAst SubExpressionRule(Token firstToken) // ErrorRecovery: Assume only the closing paren is missing, continue as though it was present. UngetToken(rParen); - ReportIncompleteInput(rParen.Extent, () => ParserStrings.MissingEndParenthesisInSubexpression); + ReportIncompleteInput(rParen.Extent, + nameof(ParserStrings.MissingEndParenthesisInSubexpression), + ParserStrings.MissingEndParenthesisInSubexpression); } } finally @@ -6508,8 +7572,8 @@ private ExpressionAst SubExpressionRule(Token firstToken) private ExpressionAst ParenthesizedExpressionRule(Token lParen) { - //G parenthesized-expression: - //G '(' new-lines:opt pipeline new-lines:opt ')' + // G parenthesized-expression: + // G '(' new-lines:opt pipeline-chain new-lines:opt ')' Token rParen; PipelineBaseAst pipelineAst; @@ -6521,13 +7585,17 @@ private ExpressionAst ParenthesizedExpressionRule(Token lParen) _disableCommaOperator = false; SkipNewlines(); - pipelineAst = PipelineRule(); + pipelineAst = PipelineChainRule(); if (pipelineAst == null) { IScriptExtent errorPosition = After(lParen); - ReportIncompleteInput(errorPosition, () => ParserStrings.ExpectedExpression); + ReportIncompleteInput( + errorPosition, + nameof(ParserStrings.ExpectedExpression), + ParserStrings.ExpectedExpression); pipelineAst = new ErrorStatementAst(errorPosition); } + SkipNewlines(); rParen = NextToken(); if (rParen.Kind != TokenKind.RParen) @@ -6535,7 +7603,9 @@ private ExpressionAst ParenthesizedExpressionRule(Token lParen) // ErrorRecovery: Assume only the closing paren is missing, continue as though it was present. UngetToken(rParen); - ReportIncompleteInput(After(pipelineAst), () => ParserStrings.MissingEndParenthesisInExpression); + ReportIncompleteInput(After(pipelineAst), + nameof(ParserStrings.MissingEndParenthesisInExpression), + ParserStrings.MissingEndParenthesisInExpression); rParen = null; } } @@ -6554,7 +7624,7 @@ private List ParseNestedExpressions(StringExpandableToken expanda List newNestedTokens = _savingTokens ? new List() : null; foreach (var token in expandableStringToken.NestedTokens) { - Diagnostics.Assert(!token.HasError || ErrorList.Any(), "No nested tokens should have unreported errors."); + Diagnostics.Assert(!token.HasError || ErrorList.Count > 0, "No nested tokens should have unreported errors."); ExpressionAst exprAst; var varToken = token as VariableToken; @@ -6566,15 +7636,15 @@ private List ParseNestedExpressions(StringExpandableToken expanda } // Enable if we decide we still need to support // "${}" or "$var:" - //else if (token.Kind == TokenKind.Unknown) - //{ - // //Diagnostics.Assert(token.Text.Equals("${}", StringComparison.OrdinalIgnoreCase), + // else if (token.Kind == TokenKind.Unknown) + // { + // // Diagnostics.Assert(token.Text.Equals("${}", StringComparison.OrdinalIgnoreCase), // // "The unknown token is only used in an expandable string when it's an empty variable name."); // // TODO: Need strict-mode check at runtime. // // TODO: in V2, "${}" expanded to '$', but "$var:" expanded to the empty string // exprAst = new StringConstantExpressionAst(token.Extent, "$", StringConstantType.BareWord); // if (_savingTokens) { newNestedTokens.Add(token); } - //} + // } else { TokenizerState ts = null; @@ -6592,17 +7662,19 @@ private List ParseNestedExpressions(StringExpandableToken expanda _tokenizer.FinishNestedScan(ts); } } + nestedExpressions.Add(exprAst); } if (_savingTokens) { expandableStringToken.NestedTokens = new ReadOnlyCollection(newNestedTokens); } + return nestedExpressions; } private ExpressionAst ExpandableStringRule(StringExpandableToken strToken) { - //G value: - //G literal + // G value: + // G literal ExpressionAst expr; // We need to scan the nested tokens even if there was some error. This is used by the tab completion: "pshome is $psh @@ -6615,17 +7687,18 @@ private ExpressionAst ExpandableStringRule(StringExpandableToken strToken) { expr = new StringConstantExpressionAst(strToken); } + return expr; } private ExpressionAst MemberNameRule() { - //G member-name: - //G simple-name - //G string-literal - //G string-literal-with-subexpression - //G expression-with-unary-operator - //G value + // G member-name: + // G simple-name + // G string-literal + // G string-literal-with-subexpression + // G expression-with-unary-operator + // G value ExpressionAst simpleName = SimpleNameRule(); if (simpleName != null) @@ -6644,9 +7717,9 @@ private ExpressionAst MemberNameRule() private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorToken) { - //G member-access: No whitespace is allowed between terms in these productions. - //G primary-expression '.' member-name - //G primary-expression '::' member-name + // G member-access: No whitespace is allowed between terms in these productions. + // G primary-expression '.' member-name + // G primary-expression '::' member-name // On entry, we've verified that operatorToken is not preceded by whitespace. @@ -6657,34 +7730,134 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT // ErrorRecovery: pretend we saw a property name, don't bother looking for an invocation, // and keep parsing. - ReportIncompleteInput(After(operatorToken), () => ParserStrings.MissingPropertyName); + ReportIncompleteInput(After(operatorToken), + nameof(ParserStrings.MissingPropertyName), + ParserStrings.MissingPropertyName); 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, operatorToken.Kind == TokenKind.ColonColon); + return new MemberExpressionAst( + ExtentOf(targetExpr, member), + targetExpr, + member, + @static: operatorToken.Kind == TokenKind.ColonColon, + nullConditional: operatorToken.Kind == TokenKind.QuestionDot); } - private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member) + private List GenericMethodArgumentsRule(int resyncIndex, out Token rBracketToken) { - //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 + List genericTypes = null; + + Token lBracket = NextToken(); + rBracketToken = null; + + if (lBracket.Kind != TokenKind.LBracket) + { + // We cannot avoid this Resync(); if we use PeekToken() to try to avoid a Resync(), the method called + // after this [`NextInvokeMemberToken()` or `NextMemberAccessToken()`] will note that an _ungotToken + // is present and assume an error state. That will cause any property accesses or non-generic method + // calls to throw a parse error. + Resync(resyncIndex); + return null; + } + + // This is either a InvokeMember expression with generic type arguments, or some sort of collection index + // on a property. + TokenizerMode oldTokenizerMode = _tokenizer.Mode; + try + { + // Switch to typename mode to avoid aggressive argument tokenization. + SetTokenizerMode(TokenizerMode.TypeName); + + SkipNewlines(); + Token firstToken = NextToken(); + + // For method generic arguments, we only support the syntax `$var.Method[TypeName1 <, TypeName2 ...>]`, + // not the syntax `$var.Method[[TypeName1] <, [TypeName2] ...>]`. + // The latter syntax has been supported for type expression since the beginning, but it's ambiguous in + // this scenario because we could be looking at an indexing operation on a property like: + // `$var.Property[]` + // and the `` could start with a type expression like `[TypeName]::Method()`, or even just + // a single type expression acting as a key to a hashtable property. Such cases will cause ambiguities. + // + // It could be possible to write code that sorts out the ambiguity and continue to support the latter + // syntax for method generic arguments, and thus to allow assembly-qualified type names. But we choose + // not to do so because: + // 1. that will definitely increase the complexity of the parsing code and also make it fragile; + // 2. the latter syntax hurts readability a lot due to the number of opening/closing brackets. + // The downside is that the assembly-qualified type names won't be supported for method generic args, + // but that's likely not a problem in practice, and we can revisit if it turns out otherwise. + if (firstToken.Kind == TokenKind.Identifier) + { + resyncIndex = -1; + genericTypes = GenericTypeArgumentsRule(firstToken, out rBracketToken); + + if (rBracketToken.Kind != TokenKind.RBracket) + { + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType), + ParserStrings.EndSquareBracketExpectedAtEndOfType); + rBracketToken = null; + } + } + } + finally + { + SetTokenizerMode(oldTokenizerMode); + + if (resyncIndex > 0) + { + Resync(resyncIndex); + } + } + return genericTypes; + } + + private ExpressionAst MemberInvokeRule( + ExpressionAst targetExpr, + Token lBracket, + Token operatorToken, + CommandElementAst member, + IList genericTypes) + { + // G invocation-expression: target-expression passed as a parameter. lBracket can be '(' or '{'. + // G target-expression member-name invoke-param-list + // G invoke-param-list: + // G '(' invoke-param-paren-list + // G script-block IScriptExtent lastExtent = null; List arguments; @@ -6695,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); @@ -6702,20 +7876,27 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, lastExtent = argument.Extent; } - return new InvokeMemberExpressionAst(ExtentOf(targetExpr, lastExtent), targetExpr, member, arguments, operatorToken.Kind == TokenKind.ColonColon); + return new InvokeMemberExpressionAst( + ExtentOf(targetExpr, lastExtent), + targetExpr, + member, + arguments, + operatorToken.Kind == TokenKind.ColonColon, + operatorToken.Kind == TokenKind.QuestionDot, + genericTypes); } private List InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent) { - //G argument-list: '(' is passed in lParen - //G argument-expression-list:opt new-lines:opt ')' - //G argument-expression-list: - //G argument-expression - //G argument-expression new-lines:opt ',' argument-expression-list - //G argument-expression: - //G See grammar for expression - the only difference is that an - //G array-literal-expression is not allowed - the comma is used - //G to separate argument-expressions. + // G argument-list: '(' is passed in lParen + // G argument-expression-list:opt new-lines:opt ')' + // G argument-expression-list: + // G argument-expression + // G argument-expression new-lines:opt ',' argument-expression-list + // G argument-expression: + // G See grammar for expression - the only difference is that an + // G array-literal-expression is not allowed - the comma is used + // G to separate argument-expressions. List arguments = new List(); Token comma = null; @@ -6739,11 +7920,15 @@ private List InvokeParamParenListRule(Token lParen, out IScriptEx // ErrorRecovery: sync at closing paren or newline. ReportIncompleteInput(After(comma), - () => ParserStrings.MissingExpressionAfterToken, TokenKind.Comma.Text()); + nameof(ParserStrings.MissingExpressionAfterToken), + ParserStrings.MissingExpressionAfterToken, + TokenKind.Comma.Text()); reportedError = true; } + break; } + arguments.Add(argument); SkipNewlines(); @@ -6755,6 +7940,7 @@ private List InvokeParamParenListRule(Token lParen, out IScriptEx break; } } + SkipNewlines(); rParen = NextToken(); if (rParen.Kind != TokenKind.RParen) @@ -6764,9 +7950,11 @@ private List InvokeParamParenListRule(Token lParen, out IScriptEx UngetToken(rParen); if (!reportedError) { - ReportIncompleteInput(arguments.Any() ? After(arguments.Last()) : After(lParen), - () => ParserStrings.MissingEndParenthesisInMethodCall); + ReportIncompleteInput(arguments.Count > 0 ? After(arguments.Last()) : After(lParen), + nameof(ParserStrings.MissingEndParenthesisInMethodCall), + ParserStrings.MissingEndParenthesisInMethodCall); } + rParen = null; } } @@ -6774,24 +7962,39 @@ private List InvokeParamParenListRule(Token lParen, out IScriptEx { _disableCommaOperator = oldDisableCommaOperator; } + lastExtent = ExtentFromFirstOf(rParen, comma, arguments.LastOrDefault(), lParen); return arguments; } private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token lBracket) { - //G element-access: - //G primary-expression '[' new-lines:opt expression new-lines:opt ']' + // G element-access: + // 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 // the closing bracket, but build an expression that can't compile. var errorExtent = After(lBracket); - ReportIncompleteInput(errorExtent, () => ParserStrings.MissingArrayIndexExpression); + ReportIncompleteInput( + errorExtent, + nameof(ParserStrings.MissingArrayIndexExpression), + ParserStrings.MissingArrayIndexExpression); indexExpr = new ErrorExpressionAst(lBracket.Extent); } @@ -6803,14 +8006,17 @@ 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), () => ParserStrings.MissingEndSquareBracket); + ReportIncompleteInput(After(indexExpr), + nameof(ParserStrings.MissingEndSquareBracket), + ParserStrings.MissingEndSquareBracket); } + rBracket = null; } - return new IndexExpressionAst(ExtentOf(primaryExpression, ExtentFromFirstOf(rBracket, indexExpr)), primaryExpression, indexExpr); + return new IndexExpressionAst(ExtentOf(primaryExpression, ExtentFromFirstOf(rBracket, indexExpr)), primaryExpression, indexExpr, lBracket.Kind == TokenKind.QuestionLBracket); } #endregion Expressions @@ -6819,47 +8025,29 @@ private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token l private void SaveError(ParseError error) { - if (ErrorList.Any()) + if (ErrorList.Count > 0) { - // Avoiding adding duplicate errors - can happen when the tokenizer resyncs. - if (ErrorList.Any(err => err.ErrorId.Equals(error.ErrorId, StringComparison.Ordinal) - && err.Extent.EndColumnNumber == error.Extent.EndColumnNumber - && err.Extent.EndLineNumber == error.Extent.EndLineNumber - && err.Extent.StartColumnNumber == error.Extent.StartColumnNumber - && err.Extent.StartLineNumber == error.Extent.StartLineNumber)) + foreach (ParseError err in ErrorList) { - return; + if (err.ErrorId.Equals(error.ErrorId, StringComparison.Ordinal) + && err.Extent.EndColumnNumber == error.Extent.EndColumnNumber + && err.Extent.EndLineNumber == error.Extent.EndLineNumber + && err.Extent.StartColumnNumber == error.Extent.StartColumnNumber + && err.Extent.StartLineNumber == error.Extent.StartLineNumber) + { + return; + } } } ErrorList.Add(error); } - private void SaveError(IScriptExtent extent, Expression> errorExpr, bool incompleteInput, params object[] args) + private void SaveError(IScriptExtent extent, string errorId, string errorMsg, bool incompleteInput, params object[] args) { - string errorMsg = null; - string errorId = null; - var memberExpression = errorExpr.Body as MemberExpression; - if (memberExpression != null) - { - var propertyInfo = memberExpression.Member as PropertyInfo; - if (propertyInfo != null) - { - var getter = propertyInfo.GetMethod; - if (getter != null && getter.IsStatic && getter.ReturnType == typeof(string)) - { - errorMsg = (string)getter.Invoke(null, null); - errorId = propertyInfo.Name; - } - } - } - if (errorMsg == null) - { - errorMsg = errorExpr.Compile().Invoke(); - errorId = "ParserError"; - } + AssertErrorIdCorrespondsToMsgString(errorId, errorMsg); - if (args != null && args.Any()) + if (args != null && args.Length > 0) { errorMsg = string.Format(CultureInfo.CurrentCulture, errorMsg, args); } @@ -6868,72 +8056,111 @@ private void SaveError(IScriptExtent extent, Expression> errorExpr, SaveError(errorToSave); } + /// + /// Debug assertion to ensure that all errors saved by the parser come + /// from resource (.resx) files. + /// + /// The error ID string (.resx key). + /// The error message, which may be a template string (.resx value). + [System.Diagnostics.Conditional("DEBUG")] + [System.Diagnostics.Conditional("ASSERTIONS_TRACE")] + private static void AssertErrorIdCorrespondsToMsgString(string errorId, string errorMsg) + { + // These types are the ones known to contain + // strings used by the parser as errors + Type[] resxTypes = new[] + { + typeof(ParserStrings), + typeof(DiscoveryExceptions), + typeof(ExtendedTypeSystem), + typeof(MshSnapInCmdletResources), + typeof(ParameterBinderStrings) + }; + + // Go through each resource type and see if the errorId key is in it, and whether the value corresponds to the errorMsg + bool msgCorrespondsToString = false; + foreach (Type resxType in resxTypes) + { + string resxErrorBody = resxType.GetProperty(errorId, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as string; + if (string.Equals(errorMsg, resxErrorBody, StringComparison.Ordinal)) + { + msgCorrespondsToString = true; + break; + } + } + + 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] private static object[] t_arrayOfOneArg; private static object[] arrayOfTwoArgs { - get { return t_arrayOfTwoArgs ?? (t_arrayOfTwoArgs = new object[2]); } + get { return t_arrayOfTwoArgs ??= new object[2]; } } + [ThreadStatic] private static object[] t_arrayOfTwoArgs; - internal bool ReportIncompleteInput(IScriptExtent extent, Expression> errorExpr) + internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string errorMsg) { // If the error position isn't at the end of the input, then we don't want to mark the error // as incomplete input. bool incompleteInput = _tokenizer.IsAtEndOfScript(extent, checkCommentsAndWhitespace: true); - SaveError(extent, errorExpr, incompleteInput, null); + SaveError(extent, errorId, errorMsg, incompleteInput, null); return incompleteInput; } - internal bool ReportIncompleteInput(IScriptExtent extent, Expression> errorExpr, object arg) + internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string errorMsg, object arg) { // If the error position isn't at the end of the input, then we don't want to mark the error // as incomplete input. bool incompleteInput = _tokenizer.IsAtEndOfScript(extent, checkCommentsAndWhitespace: true); arrayOfOneArg[0] = arg; - SaveError(extent, errorExpr, incompleteInput, arrayOfOneArg); + SaveError(extent, errorId, errorMsg, incompleteInput, arrayOfOneArg); return incompleteInput; } internal bool ReportIncompleteInput(IScriptExtent errorPosition, IScriptExtent errorDetectedPosition, - Expression> errorExpr, + string errorId, + string errorMsg, params object[] args) { // If the error position isn't at the end of the input, then we don't want to mark the error // as incomplete input. bool incompleteInput = _tokenizer.IsAtEndOfScript(errorDetectedPosition, checkCommentsAndWhitespace: true); - SaveError(errorPosition, errorExpr, incompleteInput, args); + SaveError(errorPosition, errorId, errorMsg, incompleteInput, args); return incompleteInput; } - internal void ReportError(IScriptExtent extent, Expression> errorExpr) + internal void ReportError(IScriptExtent extent, string errorId, string errorMsg) { - SaveError(extent, errorExpr, false, null); + SaveError(extent, errorId, errorMsg, false, null); } - internal void ReportError(IScriptExtent extent, Expression> errorExpr, object arg) + internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg) { arrayOfOneArg[0] = arg; - SaveError(extent, errorExpr, false, arrayOfOneArg); + SaveError(extent, errorId, errorMsg, false, arrayOfOneArg); } - internal void ReportError(IScriptExtent extent, Expression> errorExpr, object arg1, object arg2) + internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg1, object arg2) { arrayOfTwoArgs[0] = arg1; arrayOfTwoArgs[1] = arg2; - SaveError(extent, errorExpr, false, arrayOfTwoArgs); + SaveError(extent, errorId, errorMsg, false, arrayOfTwoArgs); } - internal void ReportError(IScriptExtent extent, Expression> errorExpr, params object[] args) + internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, params object[] args) { - SaveError(extent, errorExpr, false, args); + SaveError(extent, errorId, errorMsg, false, args); } internal void ReportError(ParseError error) @@ -6941,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) @@ -6962,7 +8189,6 @@ private void ReportErrorsAsWarnings(Collection errors) #region Error related classes /// - /// /// public class ParseError { @@ -6990,31 +8216,26 @@ internal ParseError(IScriptExtent extent, string errorId, string message, bool i } /// - /// /// /// public override string ToString() { - return PositionUtilities.VerboseMessage(Extent) + "\n" + Message; + return PositionUtilities.VerboseMessage(Extent) + Environment.NewLine + Message; } /// - /// /// public IScriptExtent Extent { get; } /// - /// /// public string ErrorId { get; } /// - /// /// public string Message { get; } /// - /// /// public bool IncompleteInput { get; } } @@ -7022,26 +8243,37 @@ public override string ToString() #endregion Error related classes // Guid is {eba789d9-533b-58d4-cd1f-2e6520e3a9c2} + [EventSource(Name = "Microsoft-PowerShell-Parser")] internal class ParserEventSource : EventSource { - internal static ParserEventSource Log = new ParserEventSource(); + internal static readonly ParserEventSource Log = new ParserEventSource(); + internal const int MaxScriptLengthToLog = 50; public void ParseStart(string FileName, int Length) { WriteEvent(1, FileName, Length); } + public void ParseStop() { WriteEvent(2); } + public void ResolveSymbolsStart() { WriteEvent(3); } + public void ResolveSymbolsStop() { WriteEvent(4); } + public void SemanticChecksStart() { WriteEvent(5); } + public void SemanticChecksStop() { WriteEvent(6); } + public void CheckSecurityStart(string FileName) { WriteEvent(7, FileName); } + public void CheckSecurityStop(string FileName) { WriteEvent(8, FileName); } + public void CompileStart(string FileName, int Length, bool Optimized) { WriteEvent(9, FileName, Length, Optimized); } + public void CompileStop() { WriteEvent(10); } internal static string GetFileOrScript(string fileName, string input) { - return fileName ?? input.Substring(0, Math.Min(256, input.Length)).Trim(); + return fileName ?? input.AsSpan(0, Math.Min(256, input.Length)).Trim().ToString(); } } } diff --git a/src/System.Management.Automation/engine/parser/Position.cs b/src/System.Management.Automation/engine/parser/Position.cs index c5aa366b7e0..9e1363e4a53 100644 --- a/src/System.Management.Automation/engine/parser/Position.cs +++ b/src/System.Management.Automation/engine/parser/Position.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -16,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. @@ -46,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. @@ -95,15 +97,16 @@ public interface IScriptExtent string Text { get; } /// - /// The starting offset of the extent + /// The starting offset of the extent. /// int StartOffset { get; } /// - /// The ending offset of the extent + /// The ending offset of the extent. /// int EndOffset { get; } } +#nullable restore /// /// A few utility functions for script positions. @@ -131,27 +134,27 @@ internal static string VerboseMessage(IScriptExtent position) { if (PositionUtilities.EmptyExtent.Equals(position)) { - return ""; + return string.Empty; } string fileName = position.File; - if (String.IsNullOrEmpty(fileName)) + if (string.IsNullOrEmpty(fileName)) { fileName = ParserStrings.TextForWordLine; } string sourceLine = position.StartScriptPosition.Line.TrimEnd(); - string message = ""; - if (!String.IsNullOrEmpty(sourceLine)) + string message = string.Empty; + if (!string.IsNullOrEmpty(sourceLine)) { int spacesBeforeError = position.StartColumnNumber - 1; - int errorLength = (position.StartLineNumber == position.EndLineNumber) + int errorLength = (position.StartLineNumber == position.EndLineNumber && position.EndColumnNumber <= sourceLine.Length + 1) ? position.EndColumnNumber - position.StartColumnNumber - : sourceLine.TrimEnd().Length - position.StartColumnNumber + 1; + : sourceLine.Length - position.StartColumnNumber + 1; // Expand tabs before figuring out if we need to truncate the line - if (sourceLine.IndexOf('\t') != -1) + if (sourceLine.Contains('\t')) { var copyLine = new StringBuilder(sourceLine.Length * 2); @@ -200,6 +203,7 @@ internal static string VerboseMessage(IScriptExtent position) { errorLength = maxLineLength - prefix; } + needsSuffixDots = true; } else @@ -217,6 +221,7 @@ internal static string VerboseMessage(IScriptExtent position) { suffix += Math.Min(totalSuffix, maxLineLength - candidateLength); } + needsSuffixDots = (suffix < totalSuffix); } @@ -229,25 +234,37 @@ internal static string VerboseMessage(IScriptExtent position) } if (needsPrefixDots) - sb.Append("... "); + { + sb.Append("\u2026 "); // Unicode ellipsis character + } + sb.Append(sourceLine); + if (needsSuffixDots) - sb.Append(" ..."); + { + sb.Append(" \u2026"); // Unicode ellipsis character + } + sb.Append(Environment.NewLine); sb.Append("+ "); - sb.Append(' ', spacesBeforeError + (needsPrefixDots ? 4 : 0)); + sb.Append(' ', spacesBeforeError + (needsPrefixDots ? 2 : 0)); // errorLength of 0 happens at EOF - always write out 1. sb.Append('~', errorLength > 0 ? errorLength : 1); + message = sb.ToString(); } - return StringUtil.Format(ParserStrings.TextForPositionMessage, fileName, position.StartLineNumber, - position.StartColumnNumber, message); + return StringUtil.Format( + ParserStrings.TextForPositionMessage, + fileName, + position.StartLineNumber, + position.StartColumnNumber, + message); } /// /// Return a message that looks like: - /// 12+ $x + <<<< $b + /// 12+ $x + <<<< $b. /// internal static string BriefMessage(IScriptPosition position) { @@ -260,6 +277,7 @@ internal static string BriefMessage(IScriptPosition position) { message.Insert(position.ColumnNumber - 1, " >>>> "); } + return StringUtil.Format(ParserStrings.TraceScriptLineMessage, position.LineNumber, message.ToString()); } @@ -269,10 +287,12 @@ internal static IScriptExtent NewScriptExtent(IScriptExtent start, IScriptExtent { return start; } + if (start == EmptyExtent) { return end; } + if (end == EmptyExtent) { return start; @@ -318,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) @@ -336,12 +353,21 @@ 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); } + return false; } @@ -388,6 +414,7 @@ internal int LineFromOffset(int offset) { line = ~line - 1; } + return line + 1; } @@ -404,6 +431,7 @@ internal string Text(int line) int length = _lineStartMap[line] - start; return ScriptText.Substring(start, length); } + return ScriptText.Substring(start); } } @@ -419,9 +447,13 @@ internal InternalScriptPosition(PositionHelper _positionHelper, int offset) } public string File { get { return _positionHelper.File; } } + public int LineNumber { get { return _positionHelper.LineFromOffset(Offset); } } + public int ColumnNumber { get { return _positionHelper.ColumnFromOffset(Offset); } } + public string Line { get { return _positionHelper.Text(LineNumber); } } + public int Offset { get; } internal InternalScriptPosition CloneWithNewOffset(int offset) @@ -486,8 +518,9 @@ public string Text // StartOffset can be > the length for the EOF token. if (StartOffset > PositionHelper.ScriptText.Length) { - return ""; + return string.Empty; } + return PositionHelper.ScriptText.Substring(StartOffset, EndOffset - StartOffset); } } @@ -498,7 +531,9 @@ public override string ToString() } internal PositionHelper PositionHelper { get; } + public int StartOffset { get; } + public int EndOffset { get; } } @@ -509,40 +544,53 @@ public override string ToString() internal sealed class EmptyScriptPosition : IScriptPosition { public string File { get { return null; } } + public int LineNumber { get { return 0; } } + public int ColumnNumber { get { return 0; } } + public int Offset { get { return 0; } } - public string Line { get { return ""; } } + + public string Line { get { return string.Empty; } } + public string GetFullScript() { return null; } } internal sealed class EmptyScriptExtent : IScriptExtent { public string File { get { return null; } } + public IScriptPosition StartScriptPosition { get { return PositionUtilities.EmptyPosition; } } + public IScriptPosition EndScriptPosition { get { return PositionUtilities.EmptyPosition; } } + public int StartLineNumber { get { return 0; } } + public int StartColumnNumber { get { return 0; } } + public int EndLineNumber { get { return 0; } } + public int EndColumnNumber { get { return 0; } } + public int StartOffset { get { return 0; } } + public int EndOffset { get { return 0; } } - public string Text { get { return ""; } } + + public string Text { get { return string.Empty; } } public override bool Equals(object obj) { - IScriptExtent otherPosition = obj as IScriptExtent; - if (otherPosition == null) + if (!(obj is IScriptExtent otherPosition)) { return false; } - if ((String.IsNullOrEmpty(otherPosition.File)) && + if ((string.IsNullOrEmpty(otherPosition.File)) && (otherPosition.StartLineNumber == StartLineNumber) && (otherPosition.StartColumnNumber == StartColumnNumber) && (otherPosition.EndLineNumber == EndLineNumber) && (otherPosition.EndColumnNumber == EndColumnNumber) && - (String.IsNullOrEmpty(otherPosition.Text))) + (string.IsNullOrEmpty(otherPosition.Text))) { return true; } @@ -568,7 +616,7 @@ public sealed class ScriptPosition : IScriptPosition private readonly string _fullScript; /// - /// Creates a new script position, which represents a point in a script + /// Creates a new script position, which represents a point in a script. /// /// The name of the file, or if the script did not come from a file, then null. /// The line number of the position, with the value 1 being the first line. @@ -583,7 +631,7 @@ public ScriptPosition(string scriptName, int scriptLineNumber, int offsetInLine, if (string.IsNullOrEmpty(line)) { - Line = String.Empty; + Line = string.Empty; } else { @@ -592,7 +640,7 @@ public ScriptPosition(string scriptName, int scriptLineNumber, int offsetInLine, } /// - /// Creates a new script position, which represents a point in a script + /// Creates a new script position, which represents a point in a script. /// /// The name of the file, or if the script did not come from a file, then null. /// The line number of the position, with the value 1 being the first line. @@ -605,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; } @@ -643,7 +691,7 @@ public ScriptPosition( } /// - /// A script extent used to customize the display of error location information + /// A script extent used to customize the display of error location information. /// public sealed class ScriptExtent : IScriptExtent { @@ -722,13 +770,14 @@ public string Text return _startPosition.Line.Substring(_startPosition.ColumnNumber - 1, _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 { - return String.Empty; + return string.Empty; } } } diff --git a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs index 62defcc9a58..675e53394c6 100644 --- a/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs +++ b/src/System.Management.Automation/engine/parser/PreOrderVisitor.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; 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 @@ -38,160 +37,221 @@ 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 VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + /// - public virtual AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); + /// - public virtual AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return AstVisitAction.Continue; } + 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); } /// - /// AstVisitor for new Ast node types + /// AstVisitor for new Ast node types. /// 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) => DefaultVisit(propertyMemberAst); + + /// + public virtual AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); /// - public virtual AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => DefaultVisit(baseCtorInvokeMemberExpressionAst); /// - public virtual AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) => DefaultVisit(usingStatementAst); /// - public virtual AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => DefaultVisit(configurationDefinitionAst); /// - public virtual AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) => DefaultVisit(dynamicKeywordStatementAst); /// - public virtual AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); /// - public virtual AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) { 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 ed1924382de..38181168a66 100644 --- a/src/System.Management.Automation/engine/parser/SafeValues.cs +++ b/src/System.Management.Automation/engine/parser/SafeValues.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.IO; @@ -32,74 +31,136 @@ * o VisitArrayExpression may be safe if its components are safe * o VisitArrayLiteral may be safe if its components are safe * o VisitHashtable may be safe if its components are safe - * + * o VisitTernaryExpression may be safe if its components are safe */ namespace System.Management.Automation.Language { - internal class IsSafeValueVisitor : ICustomAstVisitor + internal class IsSafeValueVisitor : ICustomAstVisitor2 { public static bool IsAstSafe(Ast ast, GetSafeValueVisitor.SafeValueContext safeValueContext) { IsSafeValueVisitor visitor = new IsSafeValueVisitor(safeValueContext); - if ((bool)ast.Accept(visitor) && visitor._visitCount < MaxVisitCount) - { - return true; - } - return false; + return visitor.IsAstSafe(ast); } 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) + { + return true; + } + + return false; } + // A readonly singleton with the default SafeValueContext. + internal static readonly IsSafeValueVisitor Default = new IsSafeValueVisitor(GetSafeValueVisitor.SafeValueContext.Default); + // This is a check of the number of visits - private int _visitCount = 0; - private const int MaxVisitCount = 5000; - private const int MaxHashtableKeyCount = 500; + private uint _visitCount = 0; + + 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 // what we can verify. - private GetSafeValueVisitor.SafeValueContext _safeValueContext; + private readonly GetSafeValueVisitor.SafeValueContext _safeValueContext; public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return false; } + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return false; } + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return false; } + public object VisitParamBlock(ParamBlockAst paramBlockAst) { return false; } + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { return false; } + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return false; } + public object VisitAttribute(AttributeAst attributeAst) { return false; } + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return false; } + public object VisitParameter(ParameterAst parameterAst) { return false; } + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return false; } + public object VisitIfStatement(IfStatementAst ifStmtAst) { return false; } + public object VisitTrap(TrapStatementAst trapStatementAst) { return false; } + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return false; } + public object VisitDataStatement(DataStatementAst dataStatementAst) { return false; } + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return false; } + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return false; } + public object VisitForStatement(ForStatementAst forStatementAst) { return false; } + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { return false; } + public object VisitCatchClause(CatchClauseAst catchClauseAst) { return false; } + public object VisitTryStatement(TryStatementAst tryStatementAst) { return false; } + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { return false; } + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return false; } + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return false; } + public object VisitExitStatement(ExitStatementAst exitStatementAst) { return false; } + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return false; } + public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return false; } + public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return false; } + public object VisitCommand(CommandAst commandAst) { return false; } + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return false; } + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { return false; } + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return false; } + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return false; } + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return false; } + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { return false; } + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; } + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { return false; } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return false; } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return false; } + public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return (bool)indexExpressionAst.Index.Accept(this) && (bool)indexExpressionAst.Target.Accept(this); @@ -117,6 +178,7 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa break; } } + return isSafe; } @@ -131,12 +193,14 @@ public object VisitStatementBlock(StatementBlockAst statementBlockAst) isSafe = false; break; } + if (!(bool)statement.Accept(this)) { isSafe = false; break; } } + return isSafe; } @@ -146,6 +210,13 @@ public object VisitPipeline(PipelineAst pipelineAst) return expr != null && (bool)expr.Accept(this); } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return (bool)ternaryExpressionAst.Condition.Accept(this) && + (bool)ternaryExpressionAst.IfTrue.Accept(this) && + (bool)ternaryExpressionAst.IfFalse.Accept(this); + } + public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { // This can be used for a denial of service @@ -163,6 +234,7 @@ public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { _visitCount++; } + return unaryExpressionIsSafe; } @@ -173,12 +245,14 @@ public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { return false; } + if (!type.IsSafePrimitive()) { // Only do conversions to built-in types - other conversions might not // be safe to optimize. return false; } + _visitCount++; return (bool)convertExpressionAst.Child.Accept(this); } @@ -260,10 +334,11 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) public object VisitHashtable(HashtableAst hashtableAst) { - if (hashtableAst.KeyValuePairs.Count > MaxHashtableKeyCount) + if (hashtableAst.KeyValuePairs.Count > _maxHashtableKeyCount) { return false; } + return hashtableAst.KeyValuePairs.All(pair => (bool)pair.Item1.Accept(this) && (bool)pair.Item2.Accept(this)); } @@ -285,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 : ICustomAstVisitor + internal sealed class GetSafeValueVisitor : ICustomAstVisitor2 { internal enum SafeValueContext { Default, GetPowerShell, - ModuleAnalysis + ModuleAnalysis, + SkipHashtableSizeCheck, } // future proofing @@ -299,7 +375,8 @@ private GetSafeValueVisitor() { } public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueContext safeValueContext) { - s_context = context; + t_context = context; + if (IsSafeValueVisitor.IsAstSafe(ast, safeValueContext)) { return ast.Accept(new GetSafeValueVisitor()); @@ -309,46 +386,99 @@ public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueCo { return null; } - throw PSTraceSource.NewArgumentException("ast"); - } - - private static ExecutionContext s_context; - - public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitParamBlock(ParamBlockAst paramBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitNamedBlock(NamedBlockAst namedBlockAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitAttribute(AttributeAst attributeAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitParameter(ParameterAst parameterAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitIfStatement(IfStatementAst ifStmtAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitTrap(TrapStatementAst trapStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitDataStatement(DataStatementAst dataStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitForStatement(ForStatementAst forStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitWhileStatement(WhileStatementAst whileStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitCatchClause(CatchClauseAst catchClauseAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitTryStatement(TryStatementAst tryStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitBreakStatement(BreakStatementAst breakStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitExitStatement(ExitStatementAst exitStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitCommand(CommandAst commandAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitCommandParameter(CommandParameterAst commandParameterAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitBlockStatement(BlockStatementAst blockStatementAst) { throw PSTraceSource.NewArgumentException("ast"); } - public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException("ast"); } + + throw PSTraceSource.NewArgumentException(nameof(ast)); + } + + /// + /// This field needs to be thread-static to make 'GetSafeValue' thread safe. + /// + [ThreadStatic] + private static ExecutionContext t_context; + + public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { throw PSTraceSource.NewArgumentException(nameof(errorStatementAst)); } + + public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(errorExpressionAst)); } + + public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { throw PSTraceSource.NewArgumentException(nameof(scriptBlockAst)); } + + public object VisitParamBlock(ParamBlockAst paramBlockAst) { throw PSTraceSource.NewArgumentException(nameof(paramBlockAst)); } + + public object VisitNamedBlock(NamedBlockAst namedBlockAst) { throw PSTraceSource.NewArgumentException(nameof(namedBlockAst)); } + + public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { throw PSTraceSource.NewArgumentException(nameof(typeConstraintAst)); } + + public object VisitAttribute(AttributeAst attributeAst) { throw PSTraceSource.NewArgumentException(nameof(attributeAst)); } + + public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { throw PSTraceSource.NewArgumentException(nameof(namedAttributeArgumentAst)); } + + public object VisitParameter(ParameterAst parameterAst) { throw PSTraceSource.NewArgumentException(nameof(parameterAst)); } + + public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(functionDefinitionAst)); } + + public object VisitIfStatement(IfStatementAst ifStmtAst) { throw PSTraceSource.NewArgumentException(nameof(ifStmtAst)); } + + public object VisitTrap(TrapStatementAst trapStatementAst) { throw PSTraceSource.NewArgumentException(nameof(trapStatementAst)); } + + public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { throw PSTraceSource.NewArgumentException(nameof(switchStatementAst)); } + + public object VisitDataStatement(DataStatementAst dataStatementAst) { throw PSTraceSource.NewArgumentException(nameof(dataStatementAst)); } + + public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { throw PSTraceSource.NewArgumentException(nameof(forEachStatementAst)); } + + public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { throw PSTraceSource.NewArgumentException(nameof(doWhileStatementAst)); } + + public object VisitForStatement(ForStatementAst forStatementAst) { throw PSTraceSource.NewArgumentException(nameof(forStatementAst)); } + + public object VisitWhileStatement(WhileStatementAst whileStatementAst) { throw PSTraceSource.NewArgumentException(nameof(whileStatementAst)); } + + public object VisitCatchClause(CatchClauseAst catchClauseAst) { throw PSTraceSource.NewArgumentException(nameof(catchClauseAst)); } + + public object VisitTryStatement(TryStatementAst tryStatementAst) { throw PSTraceSource.NewArgumentException(nameof(tryStatementAst)); } + + public object VisitBreakStatement(BreakStatementAst breakStatementAst) { throw PSTraceSource.NewArgumentException(nameof(breakStatementAst)); } + + public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { throw PSTraceSource.NewArgumentException(nameof(continueStatementAst)); } + + public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { throw PSTraceSource.NewArgumentException(nameof(returnStatementAst)); } + + public object VisitExitStatement(ExitStatementAst exitStatementAst) { throw PSTraceSource.NewArgumentException(nameof(exitStatementAst)); } + + public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { throw PSTraceSource.NewArgumentException(nameof(throwStatementAst)); } + + public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { throw PSTraceSource.NewArgumentException(nameof(doUntilStatementAst)); } + + public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { throw PSTraceSource.NewArgumentException(nameof(assignmentStatementAst)); } + + public object VisitCommand(CommandAst commandAst) { throw PSTraceSource.NewArgumentException(nameof(commandAst)); } + + public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(commandExpressionAst)); } + + public object VisitCommandParameter(CommandParameterAst commandParameterAst) { throw PSTraceSource.NewArgumentException(nameof(commandParameterAst)); } + + public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { throw PSTraceSource.NewArgumentException(nameof(fileRedirectionAst)); } + + public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { throw PSTraceSource.NewArgumentException(nameof(mergingRedirectionAst)); } + + public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(attributedExpressionAst)); } + + public object VisitBlockStatement(BlockStatementAst blockStatementAst) { throw PSTraceSource.NewArgumentException(nameof(blockStatementAst)); } + + public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(invokeMemberExpressionAst)); } + + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(typeDefinitionAst)); } + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { throw PSTraceSource.NewArgumentException(nameof(propertyMemberAst)); } + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { throw PSTraceSource.NewArgumentException(nameof(functionMemberAst)); } + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { throw PSTraceSource.NewArgumentException(nameof(baseCtorInvokeMemberExpressionAst)); } + + public object VisitUsingStatement(UsingStatementAst usingStatement) { throw PSTraceSource.NewArgumentException(nameof(usingStatement)); } + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { throw PSTraceSource.NewArgumentException(nameof(configurationDefinitionAst)); } + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { throw PSTraceSource.NewArgumentException(nameof(dynamicKeywordAst)); } // // This is similar to logic used deep in the engine for slicing something that can be sliced @@ -356,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) @@ -366,8 +496,10 @@ private object GetSingleValueFromTarget(object target, object index) { return null; } + return offset >= 0 ? targetString[offset] : targetString[targetString.Length + offset]; } + var targetArray = target as object[]; if (targetArray != null) { @@ -377,8 +509,10 @@ private object GetSingleValueFromTarget(object target, object index) { return null; } + return offset >= 0 ? targetArray[offset] : targetArray[targetArray.Length + offset]; } + var targetHashtable = target as Hashtable; if (targetHashtable != null) { @@ -389,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); @@ -400,9 +534,10 @@ 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("indexExpressionAst"); + throw new ArgumentNullException(nameof(indexExpressionAst)); } return GetIndexedValueFromTarget(target, index); @@ -413,22 +548,21 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa object[] safeValues = new object[expandableStringExpressionAst.NestedExpressions.Count]; // retrieve OFS, and if it doesn't exist set it to space string ofs = null; - if (s_context != null) + if (t_context != null) { - ofs = s_context.SessionState.PSVariable.GetValue("OFS") as string; - } - if (ofs == null) - { - ofs = " "; + ofs = t_context.SessionState.PSVariable.GetValue("OFS") as string; } + + ofs ??= " "; + for (int offset = 0; offset < safeValues.Length; offset++) { var result = expandableStringExpressionAst.NestedExpressions[offset].Accept(this); // depending on the nested expression we may retrieve a variable, or even need to // execute a sub-expression. The result of which may be returned // as a scalar, array or nested array. If the unwrap of first array doesn't contain a nested - // array we can then pass it to String.Join. If it *does* contain an array, - // we need to unwrap the inner array and pass *that* to String.Join. + // array we can then pass it to string.Join. If it *does* contain an array, + // we need to unwrap the inner array and pass *that* to string.Join. // // This means we get the same answer with GetPowerShell() as in the command-line // { echo "abc $true $(1) $(2,3) def" }.Invoke() gives the same answer as @@ -459,13 +593,14 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa object[] subResult = resultArray[subExpressionOffset] as object[]; if (subResult != null) { - subExpressionResult[subExpressionOffset] = String.Join(ofs, subResult); + subExpressionResult[subExpressionOffset] = string.Join(ofs, subResult); } else // it is a scalar, so we can just add it to our collections { subExpressionResult[subExpressionOffset] = resultArray[subExpressionOffset]; } } + safeValues[offset] = string.Join(ofs, subExpressionResult); } else @@ -500,9 +635,10 @@ public object VisitStatementBlock(StatementBlockAst statementBlockAst) } else { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(statementBlockAst)); } } + return statementList.ToArray(); } @@ -513,7 +649,18 @@ public object VisitPipeline(PipelineAst pipelineAst) { return expr.Accept(this); } - throw PSTraceSource.NewArgumentException("ast"); + + throw PSTraceSource.NewArgumentException(nameof(pipelineAst)); + } + + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + if (t_context == null) + { + throw PSTraceSource.NewArgumentException(nameof(ternaryExpressionAst)); + } + + return Compiler.GetExpressionValue(ternaryExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) @@ -521,33 +668,29 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // This can be used for a denial of service // Write-Output (((((("AAAAAAAAAAAAAAAAAAAAAA"*2)*2)*2)*2)*2)*2) // Keep on going with that pattern, and we're generating gigabytes of strings. - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(binaryExpressionAst)); } public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { - if (s_context != null) + if (t_context == null) { - return Compiler.GetExpressionValue(unaryExpressionAst, true, s_context, null); - } - else - { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(unaryExpressionAst)); } + + return Compiler.GetExpressionValue(unaryExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { // at this point, we know we're safe because we checked both the type and the child, // so now we can just call the compiler and indicate that it's trusted (at this point) - if (s_context != null) - { - return Compiler.GetExpressionValue(convertExpressionAst, true, s_context, null); - } - else + if (t_context == null) { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(convertExpressionAst)); } + + return Compiler.GetExpressionValue(convertExpressionAst, isTrustedInput: true, t_context, usingValues: null); } public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) @@ -598,12 +741,12 @@ public object VisitVariableExpression(VariableExpressionAst variableExpressionAs return Path.GetDirectoryName(scriptFileName); } - if (s_context != null) + if (t_context != null) { - return VariableOps.GetVariableValue(variableExpressionAst.VariablePath, s_context, variableExpressionAst); + return VariableOps.GetVariableValue(variableExpressionAst.VariablePath, t_context, variableExpressionAst); } - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(variableExpressionAst)); } public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) @@ -611,12 +754,12 @@ public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) // Type expressions are not safe as they allow fingerprinting by providing // a set of types, you can inspect the types in the AppDomain implying which assemblies are in use // and their version - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(typeExpressionAst)); } public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) { - throw PSTraceSource.NewArgumentException("ast"); + throw PSTraceSource.NewArgumentException(nameof(memberExpressionAst)); } public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) @@ -634,6 +777,7 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) { arrayElements.Add(element.Accept(this)); } + return arrayElements.ToArray(); } @@ -646,6 +790,7 @@ public object VisitHashtable(HashtableAst hashtableAst) var value = pair.Item2.Accept(this); hashtable.Add(key, value); } + return hashtable; } diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index 86394e5ba06..4a2a3bb626c 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -1,33 +1,38 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; 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; + private static readonly IsConstantValueVisitor s_isConstantAttributeArgVisitor = new IsConstantValueVisitor { CheckingAttributeArgument = true, }; + private static readonly IsConstantValueVisitor s_isConstantAttributeArgForClassVisitor = new IsConstantValueVisitor { CheckingAttributeArgument = true, CheckingClassAttributeArguments = true }; + private readonly Stack _memberScopeStack; private readonly Stack _scopeStack; @@ -60,16 +65,21 @@ 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 Expression> GetNonConstantAttributeArgErrorExpr(IsConstantValueVisitor visitor) + private static (string id, string msg) GetNonConstantAttributeArgErrorExpr(IsConstantValueVisitor visitor) { - return visitor.CheckingClassAttributeArguments - ? (Expression>)(() => ParserStrings.ParameterAttributeArgumentNeedsToBeConstant) - : () => ParserStrings.ParameterAttributeArgumentNeedsToBeConstantOrScriptBlock; + if (visitor.CheckingClassAttributeArguments) + { + return (nameof(ParserStrings.ParameterAttributeArgumentNeedsToBeConstant), + ParserStrings.ParameterAttributeArgumentNeedsToBeConstant); + } + + return (nameof(ParserStrings.ParameterAttributeArgumentNeedsToBeConstantOrScriptBlock), + ParserStrings.ParameterAttributeArgumentNeedsToBeConstantOrScriptBlock); } private void CheckForDuplicateParameters(ReadOnlyCollection parameters) @@ -80,20 +90,22 @@ private void CheckForDuplicateParameters(ReadOnlyCollection parame foreach (var parameter in parameters) { string parameterName = parameter.Name.VariablePath.UserPath; - if (parametersSet.Contains(parameterName)) - { - _parser.ReportError(parameter.Name.Extent, () => ParserStrings.DuplicateFormalParameter, parameterName); - } - else + if (!parametersSet.Add(parameterName)) { - parametersSet.Add(parameterName); + _parser.ReportError(parameter.Name.Extent, + nameof(ParserStrings.DuplicateFormalParameter), + ParserStrings.DuplicateFormalParameter, + 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) { - _parser.ReportError(voidConstraint.Extent, () => ParserStrings.VoidTypeConstraintNotAllowed); + _parser.ReportError(voidConstraint.Extent, + nameof(ParserStrings.VoidTypeConstraintNotAllowed), + ParserStrings.VoidTypeConstraintNotAllowed); } } } @@ -168,10 +180,14 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) } else { - var usage = attributeType.GetTypeInfo().GetCustomAttribute(true); + var usage = attributeType.GetCustomAttribute(true); if (usage != null && (usage.ValidOn & attributeTargets) == 0) { - _parser.ReportError(attributeAst.Extent, () => ParserStrings.AttributeNotAllowedOnDeclaration, ToStringCodeMethods.Type(attributeType), usage.ValidOn); + _parser.ReportError(attributeAst.Extent, + nameof(ParserStrings.AttributeNotAllowedOnDeclaration), + ParserStrings.AttributeNotAllowedOnDeclaration, + ToStringCodeMethods.Type(attributeType), + usage.ValidOn); } foreach (var namedArg in attributeAst.NamedArguments) @@ -180,10 +196,12 @@ 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, - () => ParserStrings.PropertyNotFoundForAttribute, + nameof(ParserStrings.PropertyNotFoundForAttribute), + ParserStrings.PropertyNotFoundForAttribute, name, ToStringCodeMethods.Type(attributeType), GetValidNamedAttributeProperties(attributeType)); @@ -196,7 +214,10 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { if (propertyInfo.GetSetMethod() == null) { - _parser.ReportError(namedArg.Extent, () => ExtendedTypeSystem.ReadOnlyProperty, name); + _parser.ReportError(namedArg.Extent, + nameof(ExtendedTypeSystem.ReadOnlyProperty), + ExtendedTypeSystem.ReadOnlyProperty, + name); } continue; @@ -205,7 +226,10 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) var fieldInfo = (FieldInfo)members[0]; if (fieldInfo.IsInitOnly || fieldInfo.IsLiteral) { - _parser.ReportError(namedArg.Extent, () => ExtendedTypeSystem.ReadOnlyProperty, name); + _parser.ReportError(namedArg.Extent, + nameof(ExtendedTypeSystem.ReadOnlyProperty), + ExtendedTypeSystem.ReadOnlyProperty, + name); } } } @@ -214,17 +238,19 @@ 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, () => ParserStrings.DuplicateNamedArgument, name); + _parser.ReportError(namedArg.Extent, + nameof(ParserStrings.DuplicateNamedArgument), + ParserStrings.DuplicateNamedArgument, + name); } else { - names.Add(name); - if (!namedArg.ExpressionOmitted && !IsValidAttributeArgument(namedArg.Argument, constantValueVisitor)) { - _parser.ReportError(namedArg.Argument.Extent, GetNonConstantAttributeArgErrorExpr(constantValueVisitor)); + var error = GetNonConstantAttributeArgErrorExpr(constantValueVisitor); + _parser.ReportError(namedArg.Argument.Extent, error.id, error.msg); } } } @@ -233,7 +259,8 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { if (!IsValidAttributeArgument(posArg, constantValueVisitor)) { - _parser.ReportError(posArg.Extent, GetNonConstantAttributeArgErrorExpr(constantValueVisitor)); + var error = GetNonConstantAttributeArgErrorExpr(constantValueVisitor); + _parser.ReportError(posArg.Extent, error.id, error.msg); } } @@ -262,10 +289,10 @@ private static string GetValidNamedAttributeProperties(Type attributeType) propertyNames.Add(fieldInfo.Name); } } + return string.Join(", ", propertyNames); } - public override AstVisitAction VisitParameter(ParameterAst parameterAst) { bool isClassMethod = parameterAst.Parent.Parent is FunctionMemberAst; @@ -276,7 +303,10 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) { if (attribute.TypeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase)) { - _parser.ReportError(attribute.Extent, () => ParserStrings.OrderedAttributeOnlyOnHashLiteralNode, attribute.TypeName.FullName); + _parser.ReportError(attribute.Extent, + nameof(ParserStrings.OrderedAttributeOnlyOnHashLiteralNode), + ParserStrings.OrderedAttributeOnlyOnHashLiteralNode, + attribute.TypeName.FullName); } else { @@ -285,8 +315,11 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) // attribute represent parameter type. if (isParamTypeDefined) { - _parser.ReportError(attribute.Extent, () => ParserStrings.MultipleTypeConstraintsOnMethodParam); + _parser.ReportError(attribute.Extent, + nameof(ParserStrings.MultipleTypeConstraintsOnMethodParam), + ParserStrings.MultipleTypeConstraintsOnMethodParam); } + isParamTypeDefined = true; } } @@ -313,14 +346,17 @@ 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) { - parser.ReportError(extent, () => ParserStrings.ScriptTooComplicated); + parser.ReportError(extent, + nameof(ParserStrings.ScriptTooComplicated), + ParserStrings.ScriptTooComplicated); break; } + if (type is ArrayTypeName) { type = ((ArrayTypeName)type).ElementType; @@ -344,6 +380,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit break; } } + if (dscResourceAttributeAst != null) { DscResourceChecker.CheckType(_parser, typeDefinitionAst, dscResourceAttributeAst); @@ -359,28 +396,36 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem var body = functionMemberAst.Body; if (body.ParamBlock != null) { - _parser.ReportError(body.ParamBlock.Extent, () => ParserStrings.ParamBlockNotAllowedInMethod); + _parser.ReportError(body.ParamBlock.Extent, + nameof(ParserStrings.ParamBlockNotAllowedInMethod), + 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), - () => ParserStrings.NamedBlockNotAllowedInMethod); + nameof(ParserStrings.NamedBlockNotAllowedInMethod), + ParserStrings.NamedBlockNotAllowedInMethod); } if (functionMemberAst.IsConstructor && functionMemberAst.ReturnType != null) { - _parser.ReportError(functionMemberAst.ReturnType.Extent, () => ParserStrings.ConstructorCantHaveReturnType); + _parser.ReportError(functionMemberAst.ReturnType.Extent, + nameof(ParserStrings.ConstructorCantHaveReturnType), + ParserStrings.ConstructorCantHaveReturnType); } // Analysis determines if all paths return and do data flow for variables. var allCodePathsReturned = VariableAnalysis.AnalyzeMemberFunction(functionMemberAst); if (!allCodePathsReturned && !functionMemberAst.IsReturnTypeVoid()) { - _parser.ReportError(functionMemberAst.NameExtent ?? functionMemberAst.Extent, () => ParserStrings.MethodHasCodePathNotReturn); + _parser.ReportError(functionMemberAst.NameExtent ?? functionMemberAst.Extent, + nameof(ParserStrings.MethodHasCodePathNotReturn), + ParserStrings.MethodHasCodePathNotReturn); } return AstVisitAction.Continue; @@ -391,7 +436,9 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun if (functionDefinitionAst.Parameters != null && functionDefinitionAst.Body.ParamBlock != null) { - _parser.ReportError(functionDefinitionAst.Body.ParamBlock.Extent, () => ParserStrings.OnlyOneParameterListAllowed); + _parser.ReportError(functionDefinitionAst.Body.ParamBlock.Extent, + nameof(ParserStrings.OnlyOneParameterListAllowed), + ParserStrings.OnlyOneParameterListAllowed); } else if (functionDefinitionAst.Parameters != null) { @@ -400,7 +447,9 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun if (functionDefinitionAst.IsWorkflow) { - _parser.ReportError(functionDefinitionAst.Extent, () => ParserStrings.WorkflowNotSupportedInPowerShellCore); + _parser.ReportError(functionDefinitionAst.Extent, + nameof(ParserStrings.WorkflowNotSupportedInPowerShellCore), + ParserStrings.WorkflowNotSupportedInPowerShellCore); } return AstVisitAction.Continue; @@ -411,11 +460,12 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta // Parallel flag not allowed if ((switchStatementAst.Flags & SwitchFlags.Parallel) == SwitchFlags.Parallel) { - bool reportError = !switchStatementAst.IsInWorkflow(); - if (reportError) - { - _parser.ReportError(switchStatementAst.Extent, () => ParserStrings.ParallelNotSupported); - } + _parser.ReportError( + switchStatementAst.Extent, + nameof(ParserStrings.KeywordParameterReservedForFutureUse), + ParserStrings.KeywordParameterReservedForFutureUse, + "switch", + "parallel"); } return AstVisitAction.Continue; @@ -445,18 +495,32 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach // Parallel flag not allowed if ((forEachStatementAst.Flags & ForEachFlags.Parallel) == ForEachFlags.Parallel) { - bool reportError = !forEachStatementAst.IsInWorkflow(); - if (reportError) - { - _parser.ReportError(forEachStatementAst.Extent, () => ParserStrings.ParallelNotSupported); - } + _parser.ReportError( + forEachStatementAst.Extent, + nameof(ParserStrings.KeywordParameterReservedForFutureUse), + ParserStrings.KeywordParameterReservedForFutureUse, + "foreach", + "parallel"); + } + + if (forEachStatementAst.ThrottleLimit != null) + { + _parser.ReportError( + forEachStatementAst.Extent, + nameof(ParserStrings.KeywordParameterReservedForFutureUse), + ParserStrings.KeywordParameterReservedForFutureUse, + "foreach", + "throttlelimit"); } // Throttle limit must be combined with Parallel flag if ((forEachStatementAst.ThrottleLimit != null) && ((forEachStatementAst.Flags & ForEachFlags.Parallel) != ForEachFlags.Parallel)) { - _parser.ReportError(forEachStatementAst.Extent, () => ParserStrings.ThrottleLimitRequiresParallelFlag); + _parser.ReportError( + forEachStatementAst.Extent, + nameof(ParserStrings.ThrottleLimitRequiresParallelFlag), + ParserStrings.ThrottleLimitRequiresParallelFlag); } return AstVisitAction.Continue; @@ -464,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) { @@ -475,11 +542,16 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst if (block1.IsCatchAll) { - _parser.ReportError(Parser.Before(block2.Extent), () => ParserStrings.EmptyCatchNotLast); + _parser.ReportError(Parser.Before(block2.Extent), + nameof(ParserStrings.EmptyCatchNotLast), + ParserStrings.EmptyCatchNotLast); break; } - if (block2.IsCatchAll) continue; + if (block2.IsCatchAll) + { + continue; + } foreach (TypeConstraintAst typeLiteral1 in block1.CatchTypes) { @@ -497,7 +569,10 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst if (type1 == type2 || type2.IsSubclassOf(type1)) { - _parser.ReportError(typeLiteral2.Extent, () => ParserStrings.ExceptionTypeAlreadyCaught, type2.FullName); + _parser.ReportError(typeLiteral2.Extent, + nameof(ParserStrings.ExceptionTypeAlreadyCaught), + ParserStrings.ExceptionTypeAlreadyCaught, + type2.FullName); } } } @@ -511,11 +586,11 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst /// Check that label exists inside the method. /// Only call it, when label is present and can be calculated in compile time. /// - /// BreakStatementAst or ContinueStatementAst - /// label name. Can be null + /// BreakStatementAst or ContinueStatementAst. + /// Label name. Can be null. private void CheckLabelExists(StatementAst ast, string label) { - if (String.IsNullOrEmpty(label)) + if (string.IsNullOrEmpty(label)) { return; } @@ -527,15 +602,19 @@ private void CheckLabelExists(StatementAst ast, string label) { if (parent.Parent is FunctionMemberAst) { - _parser.ReportError(ast.Extent, () => ParserStrings.LabelNotFound, label); + _parser.ReportError(ast.Extent, + nameof(ParserStrings.LabelNotFound), + ParserStrings.LabelNotFound, + label); } + break; } var loop = parent as LoopStatementAst; if (loop != null) { - if (LoopFlowException.MatchLoopLabel(label, loop.Label ?? "")) + if (LoopFlowException.MatchLoopLabel(label, loop.Label ?? string.Empty)) break; } } @@ -564,9 +643,9 @@ private void CheckForFlowOutOfFinally(Ast ast, string label) // If label is not null, we have a break/continue where we know the loop label at compile // time. If we can match the label before finding the finally, then we're not flowing out // of the finally. - if (label != null && parent is LoopStatementAst) + if (label != null && parent is LabeledStatementAst) { - if (LoopFlowException.MatchLoopLabel(label, ((LoopStatementAst)parent).Label ?? "")) + if (LoopFlowException.MatchLoopLabel(label, ((LabeledStatementAst)parent).Label ?? string.Empty)) break; } @@ -576,7 +655,9 @@ private void CheckForFlowOutOfFinally(Ast ast, string label) var tryStatementAst = stmtBlock.Parent as TryStatementAst; if (tryStatementAst != null && tryStatementAst.Finally == stmtBlock) { - _parser.ReportError(ast.Extent, () => ParserStrings.ControlLeavingFinally); + _parser.ReportError(ast.Extent, + nameof(ParserStrings.ControlLeavingFinally), + ParserStrings.ControlLeavingFinally); break; } } @@ -589,11 +670,11 @@ private static string GetLabel(ExpressionAst expr) // we just use the empty string. if (expr == null) { - return ""; + return string.Empty; } var str = expr as StringConstantExpressionAst; - return str != null ? str.Value : null; + return str?.Value; } public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) @@ -616,23 +697,27 @@ 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; } + if (ast.Pipeline != null) { if (functionMemberAst.IsReturnTypeVoid()) { - _parser.ReportError(ast.Extent, () => ParserStrings.VoidMethodHasReturn); + _parser.ReportError(ast.Extent, + nameof(ParserStrings.VoidMethodHasReturn), + ParserStrings.VoidMethodHasReturn); } } else { if (!functionMemberAst.IsReturnTypeVoid()) { - _parser.ReportError(ast.Extent, () => ParserStrings.NonVoidMethodMissingReturnValue); + _parser.ReportError(ast.Extent, + nameof(ParserStrings.NonVoidMethodMissingReturnValue), + ParserStrings.NonVoidMethodMissingReturnValue); } } } @@ -648,7 +733,7 @@ public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnSta /// Check if the ast is a valid target for assignment. If not, the action reportError is called. /// /// The target of an assignment. - /// True if the operator '=' is used, false otherwise (e.g. false on '+=' or '++'.) + /// True if the operator '=' is used, false otherwise (e.g. false on '+=' or '++'.). /// The action called to report any errors. private void CheckAssignmentTarget(ExpressionAst ast, bool simpleAssignment, Action reportError) { @@ -680,7 +765,15 @@ 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; + } + else if (ast is MemberExpressionAst memberExprAst && memberExprAst.NullConditional) + { + errorAst = ast; + } + else if (ast is IndexExpressionAst indexExprAst && indexExprAst.NullConditional) { errorAst = ast; } @@ -704,15 +797,20 @@ private void CheckAssignmentTarget(ExpressionAst ast, bool simpleAssignment, Act } else if (typeof(void) == lastConvertType) { - _parser.ReportError(convertExpr.Type.Extent, () => ParserStrings.VoidTypeConstraintNotAllowed); + _parser.ReportError(convertExpr.Type.Extent, + nameof(ParserStrings.VoidTypeConstraintNotAllowed), + ParserStrings.VoidTypeConstraintNotAllowed); } } + expr = ((AttributedExpressionAst)expr).Child; } if ((errorPosition != null) && converts > 1) { - _parser.ReportError(errorPosition, () => ParserStrings.ReferenceNeedsToBeByItselfInTypeConstraint); + _parser.ReportError(errorPosition, + nameof(ParserStrings.ReferenceNeedsToBeByItselfInTypeConstraint), + ParserStrings.ReferenceNeedsToBeByItselfInTypeConstraint); } else { @@ -730,18 +828,26 @@ private void CheckAssignmentTarget(ExpressionAst ast, bool simpleAssignment, Act var expectedType = SpecialVariables.AutomaticVariableTypes[specialIndex]; if (expectedType != lastConvertType) { - _parser.ReportError(ast.Extent, () => ParserStrings.AssignmentStatementToAutomaticNotSupported, varPath.UnqualifiedPath, expectedType); + _parser.ReportError(ast.Extent, + nameof(ParserStrings.AssignmentStatementToAutomaticNotSupported), + ParserStrings.AssignmentStatementToAutomaticNotSupported, + varPath.UnqualifiedPath, + expectedType); } + break; } + specialIndex += 1; } } } + CheckAssignmentTarget(expr, simpleAssignment, reportError); } } } + if (errorAst != null) { reportError(errorAst); @@ -761,7 +867,9 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a { // Make sure LHS is something that can be assigned to. CheckAssignmentTarget(assignmentStatementAst.Left, assignmentStatementAst.Operator == TokenKind.Equals, - ast => _parser.ReportError(ast.Extent, () => ParserStrings.InvalidLeftHandSide)); + ast => _parser.ReportError(ast.Extent, + nameof(ParserStrings.InvalidLeftHandSide), + ParserStrings.InvalidLeftHandSide)); return AstVisitAction.Continue; } @@ -771,7 +879,9 @@ public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryE if (binaryExpressionAst.Operator == TokenKind.AndAnd || binaryExpressionAst.Operator == TokenKind.OrOr) { - _parser.ReportError(binaryExpressionAst.ErrorPosition, () => ParserStrings.InvalidEndOfLine, + _parser.ReportError(binaryExpressionAst.ErrorPosition, + nameof(ParserStrings.InvalidEndOfLine), + ParserStrings.InvalidEndOfLine, binaryExpressionAst.Operator.Text()); } @@ -787,7 +897,9 @@ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpr case TokenKind.MinusMinus: case TokenKind.PostfixMinusMinus: CheckAssignmentTarget(unaryExpressionAst.Child, false, - ast => _parser.ReportError(ast.Extent, () => ParserStrings.OperatorRequiresVariableOrProperty, + ast => _parser.ReportError(ast.Extent, + nameof(ParserStrings.OperatorRequiresVariableOrProperty), + ParserStrings.OperatorRequiresVariableOrProperty, unaryExpressionAst.TokenKind.Text())); break; } @@ -799,13 +911,23 @@ 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 // $a = [ordered]10 - _parser.ReportError(convertExpressionAst.Extent, () => ParserStrings.OrderedAttributeOnlyOnHashLiteralNode, convertExpressionAst.Type.TypeName.FullName); + _parser.ReportError(convertExpressionAst.Extent, + nameof(ParserStrings.OrderedAttributeOnlyOnHashLiteralNode), + 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()) @@ -823,8 +945,10 @@ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst conve { multipleRefs = true; _parser.ReportError(childConvert.Type.Extent, - () => ParserStrings.ReferenceNeedsToBeByItselfInTypeSequence); + nameof(ParserStrings.ReferenceNeedsToBeByItselfInTypeSequence), + ParserStrings.ReferenceNeedsToBeByItselfInTypeSequence); } + child = childAttrExpr.Child; continue; } @@ -856,18 +980,23 @@ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst conve skipError = statementAst.Left.Find(ast1 => ast1 == convertExpressionAst, searchNestedScriptBlocks: true) != null; break; } + if (ast is CommandExpressionAst) { break; } + ast = ast.Parent; } + if (!skipError) { _parser.ReportError(convertExpressionAst.Type.Extent, - () => ParserStrings.ReferenceNeedsToBeLastTypeInTypeConversion); + nameof(ParserStrings.ReferenceNeedsToBeLastTypeInTypeConversion), + ParserStrings.ReferenceNeedsToBeLastTypeInTypeConversion); } } + parent = parent.Child as AttributedExpressionAst; } } @@ -891,13 +1020,15 @@ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpr var badExpr = CheckUsingExpression(exprAst); if (badExpr != null) { - _parser.ReportError(badExpr.Extent, () => ParserStrings.InvalidUsingExpression); + _parser.ReportError(badExpr.Extent, + nameof(ParserStrings.InvalidUsingExpression), + ParserStrings.InvalidUsingExpression); } return AstVisitAction.Continue; } - private ExpressionAst CheckUsingExpression(ExpressionAst exprAst) + private static ExpressionAst CheckUsingExpression(ExpressionAst exprAst) { RuntimeHelpers.EnsureSufficientExecutionStack(); if (exprAst is VariableExpressionAst) @@ -906,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); } @@ -918,6 +1051,7 @@ private ExpressionAst CheckUsingExpression(ExpressionAst exprAst) { return indexExpr.Index; } + return CheckUsingExpression(indexExpr.Target); } @@ -926,15 +1060,23 @@ 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) { - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.SplattingNotPermittedInArgumentList, variableExpressionAst.VariablePath.UserPath); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.SplattingNotPermittedInArgumentList), + ParserStrings.SplattingNotPermittedInArgumentList, + variableExpressionAst.VariablePath.UserPath); } else { - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.SplattingNotPermitted, variableExpressionAst.VariablePath.UserPath); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.SplattingNotPermitted), + ParserStrings.SplattingNotPermitted, + variableExpressionAst.VariablePath.UserPath); } } @@ -947,7 +1089,9 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var && !variableExpressionAst.IsConstantVariable() && !SpecialVariables.IsImplicitVariableAccessibleInClassMethod(variableExpressionAst.VariablePath)) { - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.VariableNotLocal); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.VariableNotLocal), + ParserStrings.VariableNotLocal); } } @@ -955,7 +1099,10 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var { if (AnalyzingStaticMember()) { - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.NonStaticMemberAccessInStaticMember, variableExpressionAst.VariablePath.UserPath); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.NonStaticMemberAccessInStaticMember), + ParserStrings.NonStaticMemberAccessInStaticMember, + variableExpressionAst.VariablePath.UserPath); } } @@ -971,19 +1118,22 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) if (keyStrAst != null) { var keyStr = keyStrAst.Value.ToString(); - if (keys.Contains(keyStr)) + if (!keys.Add(keyStr)) { - // Note - the error handling function inspects the error message body to extra the ParserStrings property name. It uses this value as the errorid. - var errorMessageExpression = hashtableAst.IsSchemaElement - ? (() => ParserStrings.DuplicatePropertyInInstanceDefinition) - : (Expression>)(() => ParserStrings.DuplicateKeyInHashLiteral); + string errorId; + string errorMsg; + if (hashtableAst.IsSchemaElement) + { + errorId = nameof(ParserStrings.DuplicatePropertyInInstanceDefinition); + errorMsg = ParserStrings.DuplicatePropertyInInstanceDefinition; + } + else + { + errorId = nameof(ParserStrings.DuplicateKeyInHashLiteral); + errorMsg = ParserStrings.DuplicateKeyInHashLiteral; + } - _parser.ReportError(entry.Item1.Extent, errorMessageExpression, - keyStr); - } - else - { - keys.Add(keyStr); + _parser.ReportError(entry.Item1.Extent, errorId, errorMsg, keyStr); } } } @@ -1005,7 +1155,10 @@ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst = attributedExpressionAst.Child as AttributedExpressionAst; } - _parser.ReportError(errorAst.Extent, () => ParserStrings.UnexpectedAttribute, errorAst.TypeName.FullName); + _parser.ReportError(errorAst.Extent, + nameof(ParserStrings.UnexpectedAttribute), + ParserStrings.UnexpectedAttribute, + errorAst.TypeName.FullName); return AstVisitAction.Continue; } @@ -1017,7 +1170,10 @@ public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatem return AstVisitAction.Continue; } - _parser.ReportError(blockStatementAst.Kind.Extent, () => ParserStrings.UnexpectedKeyword, blockStatementAst.Kind.Text); + _parser.ReportError(blockStatementAst.Kind.Extent, + nameof(ParserStrings.UnexpectedKeyword), + ParserStrings.UnexpectedKeyword, + blockStatementAst.Kind.Text); return AstVisitAction.Continue; } @@ -1034,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); } @@ -1052,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; @@ -1069,10 +1225,13 @@ 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); } + return AstVisitAction.Continue; } @@ -1128,35 +1287,73 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA var commandNameAst = commandAst.CommandElements[0] as StringConstantExpressionAst; if (commandNameAst != null) { - _parser.ReportError(commandNameAst.Extent, () => ParserStrings.ResourceNotDefined, commandNameAst.Extent.Text); + _parser.ReportError(commandNameAst.Extent, + nameof(ParserStrings.ResourceNotDefined), + ParserStrings.ResourceNotDefined, + commandNameAst.Extent.Text); } } } } + break; } + statementAst = ast as PipelineAst; ancestorNodeLevel++; ast = ast.Parent; } } + return AstVisitAction.Continue; } 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, + nameof(ParserStrings.UsingStatementNotSupported), + ParserStrings.UsingStatementNotSupported); + } + + if (kind is UsingStatementKind.Namespace) { - _parser.ReportError(usingStatementAst.Extent, () => ParserStrings.UsingStatementNotSupported); + 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) { // @@ -1170,7 +1367,9 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit { if (namedBlock != null) { - _parser.ReportError(namedBlock.OpenCurlyExtent, () => ParserStrings.UnsupportedNamedBlockInConfiguration); + _parser.ReportError(namedBlock.OpenCurlyExtent, + nameof(ParserStrings.UnsupportedNamedBlockInConfiguration), + ParserStrings.UnsupportedNamedBlockInConfiguration); } } @@ -1195,9 +1394,14 @@ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatem } catch (Exception e) { - _parser.ReportError(dynamicKeywordStatementAst.Extent, () => ParserStrings.DynamicKeywordSemanticCheckException, dynamicKeywordStatementAst.Keyword.ResourceName, e.ToString()); + _parser.ReportError(dynamicKeywordStatementAst.Extent, + nameof(ParserStrings.DynamicKeywordSemanticCheckException), + ParserStrings.DynamicKeywordSemanticCheckException, + dynamicKeywordStatementAst.Keyword.ResourceName, + e.ToString()); } } + DynamicKeyword keyword = dynamicKeywordStatementAst.Keyword; HashtableAst hashtable = dynamicKeywordStatementAst.BodyExpression as HashtableAst; if (hashtable != null) @@ -1211,15 +1415,21 @@ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatem if (propName == null) { _parser.ReportError(keyValueTuple.Item1.Extent, - () => ParserStrings.ConfigurationInvalidPropertyName, - dynamicKeywordStatementAst.FunctionName.Extent, keyValueTuple.Item1.Extent); + nameof(ParserStrings.ConfigurationInvalidPropertyName), + ParserStrings.ConfigurationInvalidPropertyName, + dynamicKeywordStatementAst.FunctionName.Extent, + keyValueTuple.Item1.Extent); } else if (!keyword.Properties.ContainsKey(propName.Value)) { + IOrderedEnumerable tableKeys = keyword.Properties.Keys + .Order(StringComparer.OrdinalIgnoreCase); + _parser.ReportError(propName.Extent, - () => ParserStrings.InvalidInstanceProperty, + nameof(ParserStrings.InvalidInstanceProperty), + ParserStrings.InvalidInstanceProperty, propName.Value, - string.Join("', '", keyword.Properties.Keys.OrderBy(key => key, StringComparer.OrdinalIgnoreCase))); + string.Join("', '", tableKeys)); } } } @@ -1232,18 +1442,23 @@ 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()) { _parser.ReportError(nameAst.Extent, - () => ParserStrings.RegularResourceUsedInMetaConfig, + nameof(ParserStrings.RegularResourceUsedInMetaConfig), + ParserStrings.RegularResourceUsedInMetaConfig, nameAst.Extent.Text); } else if (configAst.ConfigurationType != ConfigurationType.Meta && dynamicKeywordStatementAst.Keyword.IsMetaDSCResource()) { _parser.ReportError(nameAst.Extent, - () => ParserStrings.MetaConfigurationUsedInRegularConfig, + nameof(ParserStrings.MetaConfigurationUsedInRegularConfig), + ParserStrings.MetaConfigurationUsedInRegularConfig, nameAst.Extent.Text); } } @@ -1252,7 +1467,6 @@ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatem return AstVisitAction.Continue; } - public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { if (propertyMemberAst.PropertyType != null) @@ -1260,12 +1474,15 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem // Type must be resolved, but if it's not, the error was reported by the symbol resolver. var type = propertyMemberAst.PropertyType.TypeName.GetReflectionType(); - if (type != null && (type == typeof(void) || type.GetTypeInfo().IsGenericTypeDefinition)) + if (type != null && (type == typeof(void) || type.IsGenericTypeDefinition)) { - _parser.ReportError(propertyMemberAst.PropertyType.Extent, () => ParserStrings.TypeNotAllowedForProperty, + _parser.ReportError(propertyMemberAst.PropertyType.Extent, + nameof(ParserStrings.TypeNotAllowedForProperty), + ParserStrings.TypeNotAllowedForProperty, propertyMemberAst.PropertyType.TypeName.FullName); } } + _memberScopeStack.Push(propertyMemberAst); return AstVisitAction.Continue; } @@ -1275,10 +1492,13 @@ 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(); } + _scopeStack.Pop(); scriptBlockAst.PostParseChecksPerformed = true; // at this moment, we could use different parser for the initial syntax check @@ -1296,7 +1516,7 @@ public void PostVisit(Ast ast) internal static class DscResourceChecker { /// - /// Check if it is a qualified DSC resource type + /// Check if it is a qualified DSC resource type. /// /// /// @@ -1350,38 +1570,53 @@ internal static void CheckType(Parser parser, TypeDefinitionAst typeDefinitionAs if (!hasSet) { - parser.ReportError(dscResourceAttributeAst.Extent, () => ParserStrings.DscResourceMissingSetMethod, name); + parser.ReportError(dscResourceAttributeAst.Extent, + nameof(ParserStrings.DscResourceMissingSetMethod), + ParserStrings.DscResourceMissingSetMethod, + name); } if (!hasGet) { - parser.ReportError(dscResourceAttributeAst.Extent, () => ParserStrings.DscResourceMissingGetMethod, name); + parser.ReportError(dscResourceAttributeAst.Extent, + nameof(ParserStrings.DscResourceMissingGetMethod), + ParserStrings.DscResourceMissingGetMethod, + name); } if (!hasTest) { - parser.ReportError(dscResourceAttributeAst.Extent, () => ParserStrings.DscResourceMissingTestMethod, name); + parser.ReportError(dscResourceAttributeAst.Extent, + nameof(ParserStrings.DscResourceMissingTestMethod), + ParserStrings.DscResourceMissingTestMethod, + name); } if (!hasDefaultCtor && hasNonDefaultCtor) { - parser.ReportError(dscResourceAttributeAst.Extent, () => ParserStrings.DscResourceMissingDefaultConstructor, name); + parser.ReportError(dscResourceAttributeAst.Extent, + nameof(ParserStrings.DscResourceMissingDefaultConstructor), + ParserStrings.DscResourceMissingDefaultConstructor, + name); } if (!hasKey) { - parser.ReportError(dscResourceAttributeAst.Extent, () => ParserStrings.DscResourceMissingKeyProperty, name); + parser.ReportError(dscResourceAttributeAst.Extent, + nameof(ParserStrings.DscResourceMissingKeyProperty), + ParserStrings.DscResourceMissingKeyProperty, + name); } } /// - /// Look up all the way up until find all the required members + /// Look up all the way up until find all the required members. /// /// - /// The type definition ast of the DSC resource type - /// flag to indicate if the class contains Set method. - /// flag to indicate if the class contains Get method. - /// flag to indicate if the class contains Test method. - /// flag to indicate if the class contains Key property. + /// The type definition ast of the DSC resource type. + /// Flag to indicate if the class contains Set method. + /// Flag to indicate if the class contains Get method. + /// Flag to indicate if the class contains Test method. + /// Flag to indicate if the class contains Key property. private static void LookupRequiredMembers(Parser parser, TypeDefinitionAst typeDefinitionAst, ref bool hasSet, ref bool hasGet, ref bool hasTest, ref bool hasKey) { if (typeDefinitionAst == null) @@ -1396,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; } @@ -1423,6 +1657,7 @@ private static void LookupRequiredMembers(Parser parser, TypeDefinitionAst typeD CheckKey(parser, propertyMemberAst, ref hasKey); } } + if (baseTypeDefinitionAst.BaseTypes != null && (!hasSet || !hasGet || !hasTest || !hasKey)) { LookupRequiredMembers(parser, baseTypeDefinitionAst, ref hasSet, ref hasGet, ref hasTest, ref hasKey); @@ -1430,64 +1665,79 @@ private static void LookupRequiredMembers(Parser parser, TypeDefinitionAst typeD } } /// - /// Check if it is a Get method with correct return type and signature + /// Check if it is a Get method with correct return type and signature. /// /// - /// The function member AST - /// True if it is a Get method with qualified return type and signature; otherwise, false. + /// The function member AST. + /// True if it is a Get method with qualified return type and signature; otherwise, false. private static void CheckGet(Parser parser, FunctionMemberAst functionMemberAst, ref bool hasGet) { if (hasGet) { return; } + if (functionMemberAst.Name.Equals("Get", StringComparison.OrdinalIgnoreCase) && functionMemberAst.Parameters.Count == 0) { if (functionMemberAst.ReturnType != null) { // Return type is of the class we're defined in - //it must return the class type, or array of the class type. + // it must return the class type, or array of the class type. var arrayTypeName = functionMemberAst.ReturnType.TypeName as ArrayTypeName; var typeName = (arrayTypeName != null ? arrayTypeName.ElementType : functionMemberAst.ReturnType.TypeName) as TypeName; if (typeName == null || typeName._typeDefinitionAst != functionMemberAst.Parent) { - parser.ReportError(functionMemberAst.Extent, () => ParserStrings.DscResourceInvalidGetMethod, ((TypeDefinitionAst)functionMemberAst.Parent).Name); + parser.ReportError(functionMemberAst.Extent, + nameof(ParserStrings.DscResourceInvalidGetMethod), + ParserStrings.DscResourceInvalidGetMethod, + ((TypeDefinitionAst)functionMemberAst.Parent).Name); } } else { - parser.ReportError(functionMemberAst.Extent, () => ParserStrings.DscResourceInvalidGetMethod, ((TypeDefinitionAst)functionMemberAst.Parent).Name); + parser.ReportError(functionMemberAst.Extent, + nameof(ParserStrings.DscResourceInvalidGetMethod), + ParserStrings.DscResourceInvalidGetMethod, + ((TypeDefinitionAst)functionMemberAst.Parent).Name); } - //Set hasGet to true to stop look up; it may have invalid get + // Set hasGet to true to stop look up; it may have invalid get hasGet = true; return; } } /// - /// Check if it is a Test method with correct return type and signature + /// Check if it is a Test method with correct return type and signature. /// - /// The function member AST + /// The function member AST. /// 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 && functionMemberAst.ReturnType.TypeName.GetReflectionType() == typeof(bool)); } /// - /// Check if it is a Set method with correct return type and signature + /// Check if it is a Set method with correct return type and signature. /// - /// The function member AST + /// The function member AST. /// 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()); @@ -1497,7 +1747,7 @@ private static void CheckSet(FunctionMemberAst functionMemberAst, ref bool hasSe /// True if it is a key property. /// /// - /// The property member AST + /// The property member AST. /// True if it is a key property; otherwise, false. private static void CheckKey(Parser parser, PropertyMemberAst propertyMemberAst, ref bool hasKey) { @@ -1537,10 +1787,14 @@ private static void CheckKey(Parser parser, PropertyMemberAst propertyMemberAst, } } } + if (!keyPropertyTypeAllowed) { - parser.ReportError(propertyMemberAst.Extent, () => ParserStrings.DscResourceInvalidKeyProperty); + parser.ReportError(propertyMemberAst.Extent, + nameof(ParserStrings.DscResourceInvalidKeyProperty), + ParserStrings.DscResourceInvalidKeyProperty); } + return; } } @@ -1599,9 +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, () => 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); } } @@ -1610,7 +1876,7 @@ internal static void CheckDataStatementAstAtRuntime(DataStatementAst dataStateme var parser = new Parser(); var rlc = new RestrictedLanguageChecker(parser, allowedCommands, null, false); dataStatementAst.Body.InternalVisit(rlc); - if (parser.ErrorList.Any()) + if (parser.ErrorList.Count > 0) { throw new ParseException(parser.ErrorList.ToArray()); } @@ -1621,28 +1887,32 @@ internal static void EnsureUtilityModuleLoaded(ExecutionContext context) Utils.EnsureModuleLoaded("Microsoft.PowerShell.Utility", context); } - private void ReportError(Ast ast, Expression> errorExpr, params object[] args) + private void ReportError(Ast ast, string errorId, string errorMsg, params object[] args) { - ReportError(ast.Extent, errorExpr, args); + ReportError(ast.Extent, errorId, errorMsg, args); FoundError = true; } - private void ReportError(IScriptExtent extent, Expression> errorExpr, params object[] args) + private void ReportError(IScriptExtent extent, string errorId, string errorMsg, params object[] args) { - _parser.ReportError(extent, errorExpr, args); + _parser.ReportError(extent, errorId, errorMsg, args); FoundError = true; } public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { - ReportError(scriptBlockAst, () => ParserStrings.ScriptBlockNotSupportedInDataSection); + ReportError(scriptBlockAst, + nameof(ParserStrings.ScriptBlockNotSupportedInDataSection), + ParserStrings.ScriptBlockNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) { - ReportError(paramBlockAst, () => ParserStrings.ParameterDeclarationNotSupportedInDataSection); + ReportError(paramBlockAst, + nameof(ParserStrings.ParameterDeclarationNotSupportedInDataSection), + ParserStrings.ParameterDeclarationNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1665,7 +1935,10 @@ private void CheckTypeName(Ast ast, ITypeName typename) // If we couldn't resolve the type, then it's definitely an error. if (type == null || ((type.IsArray ? type.GetElementType() : type).GetTypeCode() == TypeCode.Object)) { - ReportError(ast, () => ParserStrings.TypeNotAllowedInDataSection, typename.FullName); + ReportError(ast, + nameof(ParserStrings.TypeNotAllowedInDataSection), + ParserStrings.TypeNotAllowedInDataSection, + typename.FullName); } } @@ -1679,7 +1952,9 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { Diagnostics.Assert(FoundError, "an error should have been reported elsewhere, making this redunant"); - ReportError(attributeAst, () => ParserStrings.AttributeNotSupportedInDataSection); + ReportError(attributeAst, + nameof(ParserStrings.AttributeNotSupportedInDataSection), + ParserStrings.AttributeNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1700,7 +1975,9 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - ReportError(functionDefinitionAst, () => ParserStrings.FunctionDeclarationNotSupportedInDataSection); + ReportError(functionDefinitionAst, + nameof(ParserStrings.FunctionDeclarationNotSupportedInDataSection), + ParserStrings.FunctionDeclarationNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1721,49 +1998,63 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) { - ReportError(trapStatementAst, () => ParserStrings.TrapStatementNotSupportedInDataSection); + ReportError(trapStatementAst, + nameof(ParserStrings.TrapStatementNotSupportedInDataSection), + ParserStrings.TrapStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { - ReportError(switchStatementAst, () => ParserStrings.SwitchStatementNotSupportedInDataSection); + ReportError(switchStatementAst, + nameof(ParserStrings.SwitchStatementNotSupportedInDataSection), + ParserStrings.SwitchStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) { - ReportError(dataStatementAst, () => ParserStrings.DataSectionStatementNotSupportedInDataSection); + ReportError(dataStatementAst, + nameof(ParserStrings.DataSectionStatementNotSupportedInDataSection), + ParserStrings.DataSectionStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { - ReportError(forEachStatementAst, () => ParserStrings.ForeachStatementNotSupportedInDataSection); + ReportError(forEachStatementAst, + nameof(ParserStrings.ForeachStatementNotSupportedInDataSection), + ParserStrings.ForeachStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { - ReportError(doWhileStatementAst, () => ParserStrings.DoWhileStatementNotSupportedInDataSection); + ReportError(doWhileStatementAst, + nameof(ParserStrings.DoWhileStatementNotSupportedInDataSection), + ParserStrings.DoWhileStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst) { - ReportError(forStatementAst, () => ParserStrings.ForWhileStatementNotSupportedInDataSection); + ReportError(forStatementAst, + nameof(ParserStrings.ForWhileStatementNotSupportedInDataSection), + ParserStrings.ForWhileStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) { - ReportError(whileStatementAst, () => ParserStrings.ForWhileStatementNotSupportedInDataSection); + ReportError(whileStatementAst, + nameof(ParserStrings.ForWhileStatementNotSupportedInDataSection), + ParserStrings.ForWhileStatementNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1777,49 +2068,63 @@ public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) { - ReportError(tryStatementAst, () => ParserStrings.TryStatementNotSupportedInDataSection); + ReportError(tryStatementAst, + nameof(ParserStrings.TryStatementNotSupportedInDataSection), + ParserStrings.TryStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) { - ReportError(breakStatementAst, () => ParserStrings.FlowControlStatementNotSupportedInDataSection); + ReportError(breakStatementAst, + nameof(ParserStrings.FlowControlStatementNotSupportedInDataSection), + ParserStrings.FlowControlStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) { - ReportError(continueStatementAst, () => ParserStrings.FlowControlStatementNotSupportedInDataSection); + ReportError(continueStatementAst, + nameof(ParserStrings.FlowControlStatementNotSupportedInDataSection), + ParserStrings.FlowControlStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) { - ReportError(returnStatementAst, () => ParserStrings.FlowControlStatementNotSupportedInDataSection); + ReportError(returnStatementAst, + nameof(ParserStrings.FlowControlStatementNotSupportedInDataSection), + ParserStrings.FlowControlStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst) { - ReportError(exitStatementAst, () => ParserStrings.FlowControlStatementNotSupportedInDataSection); + ReportError(exitStatementAst, + nameof(ParserStrings.FlowControlStatementNotSupportedInDataSection), + ParserStrings.FlowControlStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) { - ReportError(throwStatementAst, () => ParserStrings.FlowControlStatementNotSupportedInDataSection); + ReportError(throwStatementAst, + nameof(ParserStrings.FlowControlStatementNotSupportedInDataSection), + ParserStrings.FlowControlStatementNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { - ReportError(doUntilStatementAst, () => ParserStrings.DoWhileStatementNotSupportedInDataSection); + ReportError(doUntilStatementAst, + nameof(ParserStrings.DoWhileStatementNotSupportedInDataSection), + ParserStrings.DoWhileStatementNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1827,7 +2132,9 @@ public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntil public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { // Assignments are never allowed. - ReportError(assignmentStatementAst, () => ParserStrings.AssignmentStatementNotSupportedInDataSection); + ReportError(assignmentStatementAst, + nameof(ParserStrings.AssignmentStatementNotSupportedInDataSection), + ParserStrings.AssignmentStatementNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1848,7 +2155,9 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) if (commandAst.InvocationOperator == TokenKind.Dot) { - ReportError(commandAst, () => ParserStrings.DotSourcingNotSupportedInDataSection); + ReportError(commandAst, + nameof(ParserStrings.DotSourcingNotSupportedInDataSection), + ParserStrings.DotSourcingNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1860,13 +2169,19 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) { if (commandAst.InvocationOperator == TokenKind.Ampersand) { - ReportError(commandAst, () => ParserStrings.OperatorNotSupportedInDataSection, - TokenKind.Ampersand.Text()); + ReportError(commandAst, + nameof(ParserStrings.OperatorNotSupportedInDataSection), + ParserStrings.OperatorNotSupportedInDataSection, + TokenKind.Ampersand.Text()); } else { - ReportError(commandAst, () => ParserStrings.CmdletNotInAllowedListForDataSection, commandAst.Extent.Text); + ReportError(commandAst, + nameof(ParserStrings.CmdletNotInAllowedListForDataSection), + ParserStrings.CmdletNotInAllowedListForDataSection, + commandAst.Extent.Text); } + return AstVisitAction.Continue; } @@ -1875,7 +2190,10 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) return AstVisitAction.Continue; } - ReportError(commandAst, () => ParserStrings.CmdletNotInAllowedListForDataSection, commandName); + ReportError(commandAst, + nameof(ParserStrings.CmdletNotInAllowedListForDataSection), + ParserStrings.CmdletNotInAllowedListForDataSection, + commandName); return AstVisitAction.Continue; } @@ -1896,14 +2214,18 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { - ReportError(mergingRedirectionAst, () => ParserStrings.RedirectionNotSupportedInDataSection); + ReportError(mergingRedirectionAst, + nameof(ParserStrings.RedirectionNotSupportedInDataSection), + ParserStrings.RedirectionNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { - ReportError(fileRedirectionAst, () => ParserStrings.RedirectionNotSupportedInDataSection); + ReportError(fileRedirectionAst, + nameof(ParserStrings.RedirectionNotSupportedInDataSection), + ParserStrings.RedirectionNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -1922,7 +2244,10 @@ public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryE if (binaryExpressionAst.Operator.HasTrait(TokenFlags.DisallowedInRestrictedMode)) { - ReportError(binaryExpressionAst.ErrorPosition, () => ParserStrings.OperatorNotSupportedInDataSection, binaryExpressionAst.Operator.Text()); + ReportError(binaryExpressionAst.ErrorPosition, + nameof(ParserStrings.OperatorNotSupportedInDataSection), + ParserStrings.OperatorNotSupportedInDataSection, + binaryExpressionAst.Operator.Text()); } return AstVisitAction.Continue; @@ -1932,7 +2257,10 @@ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpr { if (unaryExpressionAst.TokenKind.HasTrait(TokenFlags.DisallowedInRestrictedMode)) { - ReportError(unaryExpressionAst, () => ParserStrings.OperatorNotSupportedInDataSection, unaryExpressionAst.TokenKind.Text()); + ReportError(unaryExpressionAst, + nameof(ParserStrings.OperatorNotSupportedInDataSection), + ParserStrings.OperatorNotSupportedInDataSection, + unaryExpressionAst.TokenKind.Text()); } return AstVisitAction.Continue; @@ -2008,28 +2336,35 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var else argBuilder.Append(", "); - argBuilder.Append("$"); + argBuilder.Append('$'); argBuilder.Append(varName); } resourceArg = argBuilder.ToString(); } - ReportError(variableExpressionAst, () => ParserStrings.VariableReferenceNotSupportedInDataSection, resourceArg); + ReportError(variableExpressionAst, + nameof(ParserStrings.VariableReferenceNotSupportedInDataSection), + ParserStrings.VariableReferenceNotSupportedInDataSection, + resourceArg); return AstVisitAction.Continue; } public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) { - ReportError(memberExpressionAst, () => ParserStrings.PropertyReferenceNotSupportedInDataSection); + ReportError(memberExpressionAst, + nameof(ParserStrings.PropertyReferenceNotSupportedInDataSection), + ParserStrings.PropertyReferenceNotSupportedInDataSection); return AstVisitAction.Continue; } public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) { - ReportError(methodCallAst, () => ParserStrings.MethodCallNotSupportedInDataSection); + ReportError(methodCallAst, + nameof(ParserStrings.MethodCallNotSupportedInDataSection), + ParserStrings.MethodCallNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -2057,7 +2392,9 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) { - ReportError(scriptBlockExpressionAst, () => ParserStrings.ScriptBlockNotSupportedInDataSection); + ReportError(scriptBlockExpressionAst, + nameof(ParserStrings.ScriptBlockNotSupportedInDataSection), + ParserStrings.ScriptBlockNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -2073,7 +2410,7 @@ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringE { // REVIEW: it should be OK to allow these, since the ast now would visit the nested expressions and catch the errors. // Not allowed since most variables are not allowed - //ReportError(expandableStringExpressionAst, () => ParserStrings.ExpandableStringNotSupportedInDataSection); + // ReportError(expandableStringExpressionAst, () => ParserStrings.ExpandableStringNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -2081,7 +2418,9 @@ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringE public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst) { // Array references are never allowed. They could turn into function calls. - ReportError(indexExpressionAst, () => ParserStrings.ArrayReferenceNotSupportedInDataSection); + ReportError(indexExpressionAst, + nameof(ParserStrings.ArrayReferenceNotSupportedInDataSection), + ParserStrings.ArrayReferenceNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -2089,7 +2428,9 @@ public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpr public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { // Attributes are not allowed, they may code in attribute constructors. - ReportError(attributedExpressionAst, () => ParserStrings.AttributeNotSupportedInDataSection); + ReportError(attributedExpressionAst, + nameof(ParserStrings.AttributeNotSupportedInDataSection), + ParserStrings.AttributeNotSupportedInDataSection); return AstVisitAction.Continue; } @@ -2097,7 +2438,9 @@ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst) { // Keyword blocks are not allowed - ReportError(blockStatementAst, () => ParserStrings.ParallelAndSequenceBlockNotSupportedInDataSection); + ReportError(blockStatementAst, + nameof(ParserStrings.ParallelAndSequenceBlockNotSupportedInDataSection), + ParserStrings.ParallelAndSequenceBlockNotSupportedInDataSection); return AstVisitAction.Continue; } diff --git a/src/System.Management.Automation/engine/parser/SymbolResolver.cs b/src/System.Management.Automation/engine/parser/SymbolResolver.cs index cc8dc8f92ee..097bcabac3b 100644 --- a/src/System.Management.Automation/engine/parser/SymbolResolver.cs +++ b/src/System.Management.Automation/engine/parser/SymbolResolver.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Globalization; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; using System.Linq; using System.Linq.Expressions; using System.Management.Automation.Runspaces; using System.Runtime.CompilerServices; + using Microsoft.PowerShell.Commands; -using System.IO; namespace System.Management.Automation.Language { @@ -30,6 +30,7 @@ public TypeLookupResult(TypeDefinitionAst type = null) } public TypeDefinitionAst Type { get; set; } + public List ExternalNamespaces { get; set; } public bool IsAmbiguous() @@ -93,7 +94,10 @@ internal void AddType(Parser parser, TypeDefinitionAst typeDefinitionAst) } else { - parser.ReportError(typeDefinitionAst.Extent, () => ParserStrings.MemberAlreadyDefined, typeDefinitionAst.Name); + parser.ReportError(typeDefinitionAst.Extent, + nameof(ParserStrings.MemberAlreadyDefined), + ParserStrings.MemberAlreadyDefined, + typeDefinitionAst.Name); } } else @@ -107,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 { @@ -126,7 +127,10 @@ internal void AddTypeFromUsingModule(Parser parser, TypeDefinitionAst typeDefini string fullName = SymbolResolver.GetModuleQualifiedName(moduleInfo.Name, typeDefinitionAst.Name); if (_typeTable.TryGetValue(fullName, out result)) { - parser.ReportError(typeDefinitionAst.Extent, () => ParserStrings.MemberAlreadyDefined, fullName); + parser.ReportError(typeDefinitionAst.Extent, + nameof(ParserStrings.MemberAlreadyDefined), + ParserStrings.MemberAlreadyDefined, + fullName); } else { @@ -171,7 +175,8 @@ internal void AddTypesInScope(Ast ast) // This way, we can support types that refer to each other, e.g. // 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); @@ -226,6 +231,7 @@ public TypeLookupResult LookupType(TypeName typeName) if (result != null) break; } + return result; } @@ -238,13 +244,14 @@ public Ast LookupVariable(VariablePath variablePath) if (result != null) break; } + return result; } /// /// Return the most deep typeDefinitionAst in the current context. /// - /// typeDefinitionAst or null, if currently not in type definition + /// TypeDefinitionAst or null, if currently not in type definition. public TypeDefinitionAst GetCurrentTypeDefinitionAst() { for (int i = _scopes.Count - 1; i >= 0; i--) @@ -265,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; @@ -292,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); @@ -344,10 +351,11 @@ 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); } + return AstVisitAction.Continue; } @@ -384,6 +392,7 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a break; } } + if (variableExpressionAst != null && variableExpressionAst.VariablePath.IsVariable) { var ast = _symbolTable.LookupVariable(variableExpressionAst.VariablePath); @@ -394,18 +403,28 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a { var typeAst = _symbolTable.GetCurrentTypeDefinitionAst(); Diagnostics.Assert(typeAst != null, "Method scopes can exist only inside type definitions."); - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.MissingTypeInStaticPropertyAssignment, - String.Format(CultureInfo.InvariantCulture, "[{0}]::", typeAst.Name), propertyMember.Name); + + string typeString = string.Create(CultureInfo.InvariantCulture, $"[{typeAst.Name}]::"); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.MissingTypeInStaticPropertyAssignment), + ParserStrings.MissingTypeInStaticPropertyAssignment, + typeString, + propertyMember.Name); } else { - _parser.ReportError(variableExpressionAst.Extent, () => ParserStrings.MissingThis, "$this.", propertyMember.Name); + _parser.ReportError(variableExpressionAst.Extent, + nameof(ParserStrings.MissingThis), + ParserStrings.MissingThis, + "$this.", + propertyMember.Name); } } } } // TODO: static look for alias and function. } + return AstVisitAction.Continue; } @@ -426,7 +445,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra /// PSModuleInfo objects are returned in the right order: i.e. if multiply versions of the module /// is presented on the system and user didn't specify version, we will return all of them, but newer one would go first. /// - /// using statement + /// Using statement. /// If exception happens, return exception object. /// /// True if in the module name uses wildcardCharacter. @@ -477,8 +496,8 @@ private Collection GetModulesFromUsingModule(UsingStatementAst usi return null; } - // case 1: relative path. Relative for file in the same folder should include .\ - bool isPath = fullyQualifiedNameStr.Contains(@"\"); + // case 1: relative path. Relative for file in the same folder should include .\ or ./ + bool isPath = fullyQualifiedNameStr.Contains('\\') || fullyQualifiedNameStr.Contains('/'); if (isPath && !LocationGlobber.IsAbsolutePath(fullyQualifiedNameStr)) { string rootPath = Path.GetDirectoryName(_parser._fileName); @@ -522,16 +541,23 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem var moduleInfo = GetModulesFromUsingModule(usingStatementAst, out exception, out wildcardCharactersUsed, out isConstant); if (!isConstant) { - _parser.ReportError(usingStatementAst.Extent, () => ParserStrings.RequiresArgumentMustBeConstant); + _parser.ReportError(usingStatementAst.Extent, + nameof(ParserStrings.RequiresArgumentMustBeConstant), + ParserStrings.RequiresArgumentMustBeConstant); } else if (exception != null) { // we re-using RequiresModuleInvalid string, semantic is very similar so it's fine to do that. - _parser.ReportError(usingStatementAst.Extent, () => ParserStrings.RequiresModuleInvalid, exception.Message); + _parser.ReportError(usingStatementAst.Extent, + nameof(ParserStrings.RequiresModuleInvalid), + ParserStrings.RequiresModuleInvalid, + exception.Message); } else if (wildcardCharactersUsed) { - _parser.ReportError(usingStatementAst.Extent, () => ParserStrings.WildCardModuleNameError); + _parser.ReportError(usingStatementAst.Extent, + nameof(ParserStrings.WildCardModuleNameError), + ParserStrings.WildCardModuleNameError); } else if (moduleInfo != null && moduleInfo.Count > 0) { @@ -550,7 +576,10 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem { // if there is no exception, but we didn't find the module then it's not present string moduleText = usingStatementAst.Name != null ? usingStatementAst.Name.Value : usingStatementAst.ModuleSpecification.Extent.Text; - _parser.ReportError(usingStatementAst.Extent, () => ParserStrings.ModuleNotFoundDuringParse, moduleText); + _parser.ReportError(usingStatementAst.Extent, + nameof(ParserStrings.ModuleNotFoundDuringParse), + ParserStrings.ModuleNotFoundDuringParse, + moduleText); } } @@ -587,6 +616,7 @@ private bool DispatchTypeName(ITypeName type, int genericArgumentCount, bool isA } } } + return false; } @@ -608,7 +638,9 @@ private bool VisitTypeName(TypeName typeName, int genericArgumentCount, bool isA if (classDefn != null && classDefn.IsAmbiguous()) { - _parser.ReportError(typeName.Extent, () => ParserStrings.AmbiguousTypeReference, + _parser.ReportError(typeName.Extent, + nameof(ParserStrings.AmbiguousTypeReference), + ParserStrings.AmbiguousTypeReference, typeName.Name, GetModuleQualifiedName(classDefn.ExternalNamespaces[0], typeName.Name), GetModuleQualifiedName(classDefn.ExternalNamespaces[1], typeName.Name)); @@ -632,10 +664,20 @@ private bool VisitTypeName(TypeName typeName, int genericArgumentCount, bool isA // [ordered] is an attribute, but it's looks like a type constraint. if (!typeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase)) { - _parser.ReportError(typeName.Extent, - isAttribute - ? (Expression>)(() => ParserStrings.CustomAttributeTypeNotFound) - : () => ParserStrings.TypeNotFound, typeName.Name); + string errorId; + string errorMsg; + if (isAttribute) + { + errorId = nameof(ParserStrings.CustomAttributeTypeNotFound); + errorMsg = ParserStrings.CustomAttributeTypeNotFound; + } + else + { + errorId = nameof(ParserStrings.TypeNotFound); + errorMsg = ParserStrings.TypeNotFound; + } + + _parser.ReportError(typeName.Extent, errorId, errorMsg, typeName.Name); } } } @@ -679,7 +721,6 @@ public void PostVisit(Ast ast) ast.Accept(_symbolResolvePostActionVisitor); } - internal static string GetModuleQualifiedName(string namespaceName, string typeName) { const char NAMESPACE_SEPARATOR = '.'; @@ -693,10 +734,11 @@ internal class SymbolResolvePostActionVisitor : DefaultCustomAstVisitor2 public override object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - if (!(functionDefinitionAst.Parent is FunctionMemberAst)) + if (functionDefinitionAst.Parent is not FunctionMemberAst) { _symbolResolver._symbolTable.LeaveScope(); } + return null; } @@ -718,4 +760,4 @@ public override object VisitFunctionMember(FunctionMemberAst functionMemberAst) return null; } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 7369607174a..a7744ac6411 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -1,12 +1,16 @@ -using System.Collections; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Reflection; +using System.Text; using System.Text.RegularExpressions; + using Microsoft.PowerShell.Commands; using CimClass = Microsoft.Management.Infrastructure.CimClass; @@ -15,63 +19,65 @@ namespace System.Management.Automation { /// - /// Enum describing permissions to use runtime evaluation during type inference + /// Enum describing permissions to use runtime evaluation during type inference. /// - public enum TypeInferenceRuntimePermissions { + public enum TypeInferenceRuntimePermissions + { /// - /// No runtime use is allowed + /// No runtime use is allowed. /// None = 0, + /// - /// Use of SafeExprEvaluator visitor is allowed + /// Use of SafeExprEvaluator visitor is allowed. /// AllowSafeEval = 1, } /// - /// static class containing methods to work with type inference of abstract syntax trees + /// Static class containing methods to work with type inference of abstract syntax trees. /// internal static class AstTypeInference { /// - /// Infers the type that the result of executing a statement would have without using runtime safe eval + /// Infers the type that the result of executing a statement would have without using runtime safe eval. /// - /// the ast to infer the type from - /// + /// The ast to infer the type from. + /// List of inferred typenames. public static IList InferTypeOf(Ast ast) { return InferTypeOf(ast, TypeInferenceRuntimePermissions.None); } /// - /// Infers the type that the result of executing a statement would have + /// Infers the type that the result of executing a statement would have. /// - /// the ast to infer the type from - /// The runtime usage permissions allowed during type inference - /// + /// The ast to infer the type from. + /// The runtime usage permissions allowed during type inference. + /// List of inferred typenames. public static IList InferTypeOf(Ast ast, TypeInferenceRuntimePermissions evalPermissions) { return InferTypeOf(ast, PowerShell.Create(RunspaceMode.CurrentRunspace), evalPermissions); } /// - /// Infers the type that the result of executing a statement would have without using runtime safe eval + /// Infers the type that the result of executing a statement would have without using runtime safe eval. /// - /// the ast to infer the type from - /// the instance of powershell to user for expression evalutaion needed for type inference - /// + /// The ast to infer the type from. + /// The instance of powershell to use for expression evaluation needed for type inference. + /// List of inferred typenames. public static IList InferTypeOf(Ast ast, PowerShell powerShell) { return InferTypeOf(ast, powerShell, TypeInferenceRuntimePermissions.None); } /// - /// Infers the type that the result of executing a statement would have + /// Infers the type that the result of executing a statement would have. /// - /// the ast to infer the type from - /// the instance of powershell to user for expression evalutaion needed for type inference - /// The runtime usage permissions allowed during type inference - /// + /// The ast to infer the type from. + /// The instance of powershell to user for expression evaluation needed for type inference. + /// The runtime usage permissions allowed during type inference. + /// List of inferred typenames. public static IList InferTypeOf(Ast ast, PowerShell powerShell, TypeInferenceRuntimePermissions evalPersmissions) { var context = new TypeInferenceContext(powerShell); @@ -79,13 +85,16 @@ public static IList InferTypeOf(Ast ast, PowerShell powerShell, Type } /// - /// Infers the type that the result of executing a statement would have + /// Infers the type that the result of executing a statement would have. /// - /// the ast to infer the type from - /// The current type inference context - /// The runtime usage permissions allowed during type inference - /// - internal static IList InferTypeOf(Ast ast, TypeInferenceContext context, TypeInferenceRuntimePermissions evalPersmissions = TypeInferenceRuntimePermissions.None) + /// The ast to infer the type from. + /// The current type inference context. + /// The runtime usage permissions allowed during type inference. + /// List of inferred typenames. + internal static IList InferTypeOf( + Ast ast, + TypeInferenceContext context, + TypeInferenceRuntimePermissions evalPersmissions = TypeInferenceRuntimePermissions.None) { var originalRuntimePermissions = context.RuntimePermissions; try @@ -100,7 +109,7 @@ internal static IList InferTypeOf(Ast ast, TypeInferenceContext cont } } - class PSTypeNameComparer : IEqualityComparer + internal class PSTypeNameComparer : IEqualityComparer { public bool Equals(PSTypeName x, PSTypeName y) { @@ -115,7 +124,7 @@ public int GetHashCode(PSTypeName obj) internal class TypeInferenceContext { - public static readonly PSTypeName[] EmptyPSTypeNameArray = Utils.EmptyArray(); + public static readonly PSTypeName[] EmptyPSTypeNameArray = Array.Empty(); private readonly PowerShell _powerShell; public TypeInferenceContext() : this(PowerShell.Create(RunspaceMode.CurrentRunspace)) @@ -123,10 +132,10 @@ public TypeInferenceContext() : this(PowerShell.Create(RunspaceMode.CurrentRunsp } /// - /// Create a new Type Inference context. - /// The powerShell instance passed need to have a non null Runspace + /// 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. public TypeInferenceContext(PowerShell powerShell) { Diagnostics.Assert(powerShell.Runspace != null, "Callers are required to ensure we have a runspace"); @@ -141,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; } @@ -154,9 +165,9 @@ public bool TryGetRepresentativeTypeNameFromExpressionSafeEval(ExpressionAst exp { return false; } - object value; + return expression != null && - SafeExprEvaluator.TrySafeEval(expression, ExecutionContext, out value) && + SafeExprEvaluator.TrySafeEval(expression, ExecutionContext, out var value) && TryGetRepresentativeTypeNameFromValue(value, out typeName); } @@ -165,6 +176,14 @@ internal IList GetMembersByInferredType(PSTypeName typename, bool isStat List results = new List(); Func filterToCall = filter; + if (typename is PSSyntheticTypeName synthetic) + { + foreach (var mem in synthetic.Members) + { + results.Add(new PSInferredProperty(mem.Name, mem.PSTypeName)); + } + } + if (typename.Type != null) { AddMembersByInferredTypesClrType(typename, isStatic, filter, filterToCall, results); @@ -178,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)); } @@ -188,7 +225,6 @@ internal IList GetMembersByInferredType(PSTypeName typename, bool isStat return results; } - internal void AddMembersByInferredTypesClrType(PSTypeName typename, bool isStatic, Func filter, Func filterToCall, List results) { if (CurrentTypeDefinitionAst == null || CurrentTypeDefinitionAst.Type != typename.Type) @@ -202,6 +238,7 @@ internal void AddMembersByInferredTypesClrType(PSTypeName typename, bool isStati filterToCall = o => !IsMemberHidden(o) && filter(o); } } + IEnumerable elementTypes; if (typename.Type.IsArray) { @@ -209,9 +246,18 @@ internal void AddMembersByInferredTypesClrType(PSTypeName typename, bool isStati } else { - elementTypes = typename.Type.GetInterfaces().Where( - t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + var elementList = new List(); + foreach (var t in typename.Type.GetInterfaces()) + { + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + elementList.Add(t); + } + } + + elementTypes = elementList; } + foreach (var type in elementTypes.Prepend(typename.Type)) { // Look in the type table first. @@ -222,36 +268,57 @@ internal void AddMembersByInferredTypesClrType(PSTypeName typename, bool isStati } var members = isStatic - ? PSObject.dotNetStaticAdapter.BaseGetMembers(type) - : PSObject.dotNetInstanceAdapter.GetPropertiesAndMethods(type, false); - results.AddRange(filterToCall != null ? members.Where(filterToCall) : members); + ? PSObject.DotNetStaticAdapter.BaseGetMembers(type) + : PSObject.DotNetInstanceAdapter.GetPropertiesAndMethods(type, false); + + if (filterToCall != null) + { + foreach (var member in members) + { + if (filterToCall(member)) + { + results.Add(member); + } + } + } + else + { + results.AddRange(members); + } } } - internal void AddMembersByInferredTypeDefinitionAst(PSTypeName typename, bool isStatic, - Func filter, Func filterToCall, List results) + internal void AddMembersByInferredTypeDefinitionAst( + PSTypeName typename, + bool isStatic, + Func filter, + Func filterToCall, + List results) { if (CurrentTypeDefinitionAst != typename.TypeDefinitionAst) { if (filterToCall == null) + { filterToCall = o => !IsMemberHidden(o); + } else + { filterToCall = o => !IsMemberHidden(o) && filter(o); + } } bool foundConstructor = false; foreach (var member in typename.TypeDefinitionAst.Members) { bool add; - var propertyMember = member as PropertyMemberAst; - if (propertyMember != null) + if (member is PropertyMemberAst propertyMember) { add = propertyMember.IsStatic == isStatic; } else { - var functionMember = (FunctionMemberAst) member; - add = functionMember.IsStatic == isStatic; + var functionMember = (FunctionMemberAst)member; + add = (functionMember.IsConstructor && isStatic) || (!functionMember.IsConstructor && functionMember.IsStatic == isStatic); foundConstructor |= functionMember.IsConstructor; } @@ -266,13 +333,27 @@ internal void AddMembersByInferredTypeDefinitionAst(PSTypeName typename, bool is } } - //iterate through bases/interfaces + // iterate through bases/interfaces foreach (var baseType in typename.TypeDefinitionAst.BaseTypes) { - var baseTypeName = baseType.TypeName as TypeName; - if (baseTypeName == null) continue; + 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. @@ -283,7 +364,6 @@ internal void AddMembersByInferredTypeDefinitionAst(PSTypeName typename, bool is { filterToCall = o => !IsConstructor(o); } - else { filterToCall = o => !IsConstructor(o) && filter(o); @@ -292,7 +372,9 @@ internal void AddMembersByInferredTypeDefinitionAst(PSTypeName typename, bool is if (!foundConstructor) { results.Add( - new CompilerGeneratedMemberFunctionAst(PositionUtilities.EmptyExtent, typename.TypeDefinitionAst, + new CompilerGeneratedMemberFunctionAst( + PositionUtilities.EmptyExtent, + typename.TypeDefinitionAst, SpecialMemberFunctionType.DefaultConstructor)); } } @@ -301,23 +383,45 @@ internal void AddMembersByInferredTypeDefinitionAst(PSTypeName typename, bool is // Reset the filter because the recursive call will add IsHidden back if necessary. 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) { - string cimNamespace; - string className; - if (ParseCimCommandsTypeName(typename, out cimNamespace, out className)) + if (ParseCimCommandsTypeName(typename, out var cimNamespace, out var className)) { var powerShellExecutionHelper = Helper; - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("CimCmdlets\\Get-CimClass") - .AddParameter("Namespace", cimNamespace) - .AddParameter("Class", className); + powerShellExecutionHelper.AddCommandWithPreferenceSetting("CimCmdlets\\Get-CimClass") + .AddParameter("Namespace", cimNamespace) + .AddParameter("Class", className); + var classes = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _); - foreach (var cimClass in classes.Select(PSObject.Base).OfType()) + var cimClasses = new List(); + foreach (var c in classes) + { + if (PSObject.Base(c) is CimClass cc) + { + cimClasses.Add(cc); + } + } + + foreach (var cimClass in cimClasses) { if (filterToCall == null) { @@ -349,18 +453,42 @@ private static bool TryGetRepresentativeTypeNameFromValue(object value, out PSTy type = null; if (value != null) { - var list = value as IList; - if (list != null && list.Count > 0) + if (value is IList list + && list.Count > 0) { value = list[0]; } + 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; } } + return false; } @@ -372,6 +500,7 @@ internal static bool ParseCimCommandsTypeName(PSTypeName typename, out string ci { return false; } + if (typename.Type != null) { return false; @@ -400,7 +529,7 @@ private static bool IsMemberHidden(object member) case PSMemberInfo psMemberInfo: return psMemberInfo.IsHidden; case MemberInfo memberInfo: - return memberInfo.GetCustomAttributes(typeof(HiddenAttribute), false).Any(); + return memberInfo.GetCustomAttributes(typeof(HiddenAttribute), false).Length != 0; case PropertyMemberAst propertyMember: return propertyMember.IsHidden; case FunctionMemberAst functionMember: @@ -422,7 +551,7 @@ internal class TypeInferenceVisitor : ICustomAstVisitor2 { private readonly TypeInferenceContext _context; - private static readonly PSTypeName StringPSTypeName = new PSTypeName(typeof(string)); + private static readonly PSTypeName StringPSTypeName = new PSTypeName(typeof(string)); public TypeInferenceVisitor(TypeInferenceContext context) { @@ -451,16 +580,94 @@ object ICustomAstVisitor.VisitInvokeMemberExpression(InvokeMemberExpressionAst i object ICustomAstVisitor.VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) { - return new[] { new PSTypeName(typeof(object[])) }; + if (arrayExpressionAst.SubExpression.Statements.Count == 0) + { + return new[] { new PSTypeName(typeof(object[])) }; + } + + return new[] { GetArrayType(InferTypes(arrayExpressionAst.SubExpression)) }; } object ICustomAstVisitor.VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) { - return new[] { new PSTypeName(typeof(object[])) }; + var inferredElementTypes = new List(); + foreach (ExpressionAst expression in arrayLiteralAst.Elements) + { + inferredElementTypes.AddRange(InferTypes(expression)); + } + + return new[] { GetArrayType(inferredElementTypes) }; } 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; + if (kv.Item1 is StringConstantExpressionAst stringConstantExpressionAst) + { + name = stringConstantExpressionAst.Value; + } + else if (kv.Item1 is ConstantExpressionAst constantExpressionAst) + { + name = constantExpressionAst.Value.ToString(); + } + else if (SafeExprEvaluator.TrySafeEval(kv.Item1, _context.ExecutionContext, out object nameValue)) + { + name = nameValue.ToString(); + } + + if (name is not null) + { + if (kv.Item2 is PipelineAst pipelineAst && pipelineAst.GetPureExpression() is ExpressionAst expression) + { + object value; + if (expression is ConstantExpressionAst constant) + { + value = constant.Value; + } + else + { + _ = SafeExprEvaluator.TrySafeEval(expression, _context.ExecutionContext, out value); + } + + if (value is null) + { + AddInferredTypes(expression, name); + continue; + } + + PSTypeName valueType = new(value.GetType()); + properties.Add(new PSMemberNameAndType(name, valueType, value)); + } + else + { + AddInferredTypes(kv.Item2, name); + } + } + } + + return new[] { PSSyntheticTypeName.Create(typeof(Hashtable), properties) }; + } + return new[] { new PSTypeName(typeof(Hashtable)) }; } @@ -501,7 +708,9 @@ object ICustomAstVisitor.VisitUsingExpression(UsingExpressionAst usingExpression object ICustomAstVisitor.VisitVariableExpression(VariableExpressionAst ast) { - return InferTypeFrom(ast); + var inferredTypes = new List(); + InferTypeFrom(ast, inferredTypes); + return inferredTypes; } object ICustomAstVisitor.VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) @@ -511,20 +720,205 @@ 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) { var tokenKind = unaryExpressionAst.TokenKind; return (tokenKind == TokenKind.Not || tokenKind == TokenKind.Exclaim) - ? BinaryExpressionAst.BoolTypeNameArray - : unaryExpressionAst.Child.Accept(this); + ? BinaryExpressionAst.BoolTypeNameArray + : unaryExpressionAst.Child.Accept(this); } object ICustomAstVisitor.VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { + // The reflection type of PSCustomObject is PSObject, so this covers both the + // [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) + { + return new[] { PSSyntheticTypeName.Create(type, syntheticTypeName.Members) }; + } + } + var psTypeName = type != null ? new PSTypeName(type) : new PSTypeName(convertExpressionAst.Type.TypeName.FullName); return new[] { psTypeName }; } @@ -547,12 +941,43 @@ object ICustomAstVisitor.VisitSubExpression(SubExpressionAst subExpressionAst) object ICustomAstVisitor.VisitErrorStatement(ErrorStatementAst errorStatementAst) { - return errorStatementAst.Conditions.Concat(errorStatementAst.Bodies).Concat(errorStatementAst.NestedAst).SelectMany(InferTypes); + var inferredTypes = new List(); + if (errorStatementAst.Conditions is not null) + { + foreach (var ast in errorStatementAst.Conditions) + { + inferredTypes.AddRange(InferTypes(ast)); + } + } + + if (errorStatementAst.Bodies is not null) + { + foreach (var ast in errorStatementAst.Bodies) + { + inferredTypes.AddRange(InferTypes(ast)); + } + } + + if (errorStatementAst.NestedAst is not null) + { + foreach (var ast in errorStatementAst.NestedAst) + { + inferredTypes.AddRange(InferTypes(ast)); + } + } + + return inferredTypes; } object ICustomAstVisitor.VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { - return errorExpressionAst.NestedAst.SelectMany(InferTypes); + var inferredTypes = new List(); + foreach (var ast in errorExpressionAst.NestedAst) + { + inferredTypes.AddRange(InferTypes(ast)); + } + + return inferredTypes; } object ICustomAstVisitor.VisitScriptBlock(ScriptBlockAst scriptBlockAst) @@ -561,19 +986,23 @@ object ICustomAstVisitor.VisitScriptBlock(ScriptBlockAst scriptBlockAst) var beginBlock = scriptBlockAst.BeginBlock; var processBlock = scriptBlockAst.ProcessBlock; var endBlock = scriptBlockAst.EndBlock; + // The following is used when we don't find OutputType, which is checked elsewhere. if (beginBlock != null) { res.AddRange(InferTypes(beginBlock)); } + if (processBlock != null) { res.AddRange(InferTypes(processBlock)); } + if (endBlock != null) { res.AddRange(InferTypes(endBlock)); } + return res; } @@ -584,7 +1013,25 @@ object ICustomAstVisitor.VisitParamBlock(ParamBlockAst paramBlockAst) object ICustomAstVisitor.VisitNamedBlock(NamedBlockAst namedBlockAst) { - return namedBlockAst.Statements.SelectMany(InferTypes); + var inferredTypes = new List(); + for (int index = 0; index < namedBlockAst.Statements.Count; 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)); + } + + return inferredTypes; } object ICustomAstVisitor.VisitTypeConstraint(TypeConstraintAst typeConstraintAst) @@ -606,24 +1053,38 @@ object ICustomAstVisitor.VisitParameter(ParameterAst parameterAst) { var res = new List(); var attributes = parameterAst.Attributes; - var typeConstraint = attributes.OfType().FirstOrDefault(); - if (typeConstraint != null) - { - res.Add(new PSTypeName(typeConstraint.TypeName)); - } - foreach (var attributeAst in attributes.OfType()) + bool typeConstraintAdded = false; + foreach (var attrib in attributes) { - PSTypeNameAttribute attribute = null; - try - { - attribute = attributeAst.GetAttribute() as PSTypeNameAttribute; - } - catch (RuntimeException) { } - if (attribute != null) + switch (attrib) { - res.Add(new PSTypeName(attribute.PSTypeName)); + case TypeConstraintAst typeConstraint: + if (!typeConstraintAdded) + { + res.Add(new PSTypeName(typeConstraint.TypeName)); + typeConstraintAdded = true; + } + + break; + case AttributeAst attributeAst: + PSTypeNameAttribute attribute = null; + try + { + attribute = attributeAst.GetAttribute() as PSTypeNameAttribute; + } + catch (RuntimeException) + { + } + + if (attribute != null) + { + res.Add(new PSTypeName(attribute.PSTypeName)); + } + + break; } } + return res; } @@ -634,20 +1095,41 @@ object ICustomAstVisitor.VisitFunctionDefinition(FunctionDefinitionAst functionD object ICustomAstVisitor.VisitStatementBlock(StatementBlockAst statementBlockAst) { - return statementBlockAst.Statements.SelectMany(InferTypes); + var inferredTypes = new List(); + 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)); + } + + return inferredTypes; } object ICustomAstVisitor.VisitIfStatement(IfStatementAst ifStmtAst) { var res = new List(); - res.AddRange(ifStmtAst.Clauses.SelectMany(clause => InferTypes(clause.Item2))); + foreach (var clause in ifStmtAst.Clauses) + { + res.AddRange(InferTypes(clause.Item2)); + } var elseClause = ifStmtAst.ElseClause; if (elseClause != null) { res.AddRange(InferTypes(elseClause)); } + return res; } @@ -662,12 +1144,16 @@ object ICustomAstVisitor.VisitSwitchStatement(SwitchStatementAst switchStatement var clauses = switchStatementAst.Clauses; var defaultStatement = switchStatementAst.Default; - res.AddRange(clauses.SelectMany(clause => InferTypes(clause.Item2))); + foreach (var clause in clauses) + { + res.AddRange(InferTypes(clause.Item2)); + } if (defaultStatement != null) { res.AddRange(InferTypes(defaultStatement)); } + return res; } @@ -709,10 +1195,12 @@ object ICustomAstVisitor.VisitTryStatement(TryStatementAst tryStatementAst) { res.AddRange(InferTypes(catchClauseAst)); } + if (tryStatementAst.Finally != null) { res.AddRange(InferTypes(tryStatementAst.Finally)); } + return res; } @@ -728,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); } @@ -748,17 +1241,31 @@ 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) { - return pipelineAst.PipelineElements.Last().Accept(this); + var pipelineAstPipelineElements = pipelineAst.PipelineElements; + return pipelineAstPipelineElements[pipelineAstPipelineElements.Count - 1].Accept(this); } object ICustomAstVisitor.VisitCommand(CommandAst commandAst) { - return InferTypesFrom(commandAst); + var inferredTypes = new List(); + InferTypesFrom(commandAst, inferredTypes); + return inferredTypes; } object ICustomAstVisitor.VisitCommandExpression(CommandExpressionAst commandExpressionAst) @@ -776,225 +1283,627 @@ object ICustomAstVisitor.VisitFileRedirection(FileRedirectionAst fileRedirection return TypeInferenceContext.EmptyPSTypeNameArray; } - private IEnumerable InferTypesFrom(CommandAst commandAst) + private void InferTypesFrom(CommandAst commandAst, List inferredTypes, bool forRedirection = false) { - PseudoBindingInfo pseudoBinding = new PseudoParameterBinder() - .DoPseudoParameterBinding(commandAst, null, null, PseudoParameterBinder.BindingType.ParameterCompletion); - if (pseudoBinding?.CommandInfo == null) + if (commandAst.Redirections.Count > 0) { - yield break; - } - - AstParameterArgumentPair pathArgument; - string pathParameterName = "Path"; - if (!pseudoBinding.BoundArguments.TryGetValue(pathParameterName, out pathArgument)) - { - pathParameterName = "LiteralPath"; - pseudoBinding.BoundArguments.TryGetValue(pathParameterName, out pathArgument); - } + 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 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; + } + + string pathParameterName = "Path"; + if (!pseudoBinding.BoundArguments.TryGetValue(pathParameterName, out var pathArgument)) + { + pathParameterName = "LiteralPath"; + pseudoBinding.BoundArguments.TryGetValue(pathParameterName, out pathArgument); + } // The OutputType on cmdlets like Get-ChildItem may depend on the path. // The CmdletInfo returned based on just the command name will specify returning all possibilities, e.g.certificates, environment, registry, etc. // If you specified - Path, the list of OutputType can be refined, but we have to make a copy of the CmdletInfo object this way to get that refinement. var commandInfo = pseudoBinding.CommandInfo; var pathArgumentPair = pathArgument as AstPair; - if (pathArgumentPair?.Argument is StringConstantExpressionAst) + if (pathArgumentPair?.Argument is StringConstantExpressionAst ast) { - var pathValue = ((StringConstantExpressionAst)pathArgumentPair.Argument).Value; + var pathValue = ast.Value; try { commandInfo = commandInfo.CreateGetCommandCopy(new object[] { "-" + pathParameterName, pathValue }); } - catch (InvalidOperationException) { } + catch (InvalidOperationException) + { + } } - var cmdletInfo = commandInfo as CmdletInfo; - if (cmdletInfo != null) + if (commandInfo is CmdletInfo cmdletInfo) { // Special cases - var inferTypesFromObjectCmdlets = InferTypesFromObjectCmdlets(commandAst, cmdletInfo, pseudoBinding).ToArray(); - if (inferTypesFromObjectCmdlets.Length > 0) + var inferTypesFromObjectCmdlets = InferTypesFromObjectCmdlets(commandAst, cmdletInfo, pseudoBinding); + if (inferTypesFromObjectCmdlets.Count > 0) + { + inferredTypes.AddRange(inferTypesFromObjectCmdlets); + return; + } + + if (cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.GetRandomCommand") + && pseudoBinding.BoundArguments.TryGetValue("InputObject", out var value)) { - foreach (var objectCmdletTypes in inferTypesFromObjectCmdlets) + if (value.ParameterArgumentType == AstParameterArgumentType.PipeObject) { - yield return objectCmdletTypes; + InferTypesFromPreviousCommand(commandAst, inferredTypes); } - yield break; + 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. - foreach (var result in commandInfo.OutputType) - { - yield return result; - } + inferredTypes.AddRange(commandInfo.OutputType); } /// - /// Infer types from the well-known object cmdlets, like foreach-object, where-object, sort-object etc + /// Infer types from the well-known object cmdlets, like foreach-object, where-object, sort-object etc. /// - /// - /// - /// - /// - private IEnumerable InferTypesFromObjectCmdlets(CommandAst commandAst, CmdletInfo cmdletInfo, PseudoBindingInfo pseudoBinding) + /// The ast to infer types from. + /// The cmdletInfo. + /// Pseudo bindings of parameters. + /// List of inferred type names. + private List InferTypesFromObjectCmdlets(CommandAst commandAst, CmdletInfo cmdletInfo, PseudoBindingInfo pseudoBinding) { - // new-object - yields an instance of whatever -Type is bound to + var inferredTypes = new List(16); + if (cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.NewObjectCommand")) { + // new - object - yields an instance of whatever -Type is bound to var newObjectType = InferTypesFromNewObjectCommand(pseudoBinding); if (newObjectType != null) { - yield return newObjectType; + inferredTypes.Add(newObjectType); } - - yield break; // yield break; } - - // Get-CimInstance/New-CimInstance - yields a CimInstance with ETS type based on its arguments for -Namespace and -ClassName parameters - if ( + else if ( cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.Management.Infrastructure.CimCmdlets.GetCimInstanceCommand") || cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.Management.Infrastructure.CimCmdlets.NewCimInstanceCommand")) { - foreach (var cimType in InferTypesFromCimCommand(pseudoBinding)) - { - yield return cimType; - } - yield break; // yield break; + // Get-CimInstance/New-CimInstance - adds a CimInstance with ETS type based on its arguments for -Namespace and -ClassName parameters + InferTypesFromCimCommand(pseudoBinding, inferredTypes); } - - // where-object - yields whatever we saw before where-object in the pipeline. - // same for sort-object - if (cmdletInfo.ImplementingType == typeof(WhereObjectCommand) - || - cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.SortObjectCommand")) + else if (cmdletInfo.ImplementingType == typeof(WhereObjectCommand) || + cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.SortObjectCommand")) { - foreach (var whereOrSortType in InferTypesFromWhereAndSortCommand(commandAst)) - { - yield return whereOrSortType; - } - - // We could also check -InputObject, but that is rarely used. But don't bother continuing. - yield break; // yield break; + // where-object - adds whatever we saw before where-object in the pipeline. + // same for sort-object + InferTypesFromWhereAndSortCommand(commandAst, inferredTypes); } - - // foreach-object - yields the type of it's script block parameters - if (cmdletInfo.ImplementingType == typeof(ForEachObjectCommand)) + else if (cmdletInfo.ImplementingType == typeof(ForEachObjectCommand)) { - foreach (var foreachType in InferTypesFromForeachCommand(pseudoBinding, commandAst)) - { - yield return foreachType; - } + // foreach-object - adds the type of it's script block parameters + InferTypesFromForeachCommand(pseudoBinding, commandAst, inferredTypes); + } + else if (cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.SelectObjectCommand")) + { + // Select-object - adds whatever we saw before where-object in the pipeline. + // unless -property or -excludeproperty + InferTypesFromSelectCommand(pseudoBinding, commandAst, inferredTypes); } + else if (cmdletInfo.ImplementingType.FullName.EqualsOrdinalIgnoreCase("Microsoft.PowerShell.Commands.GroupObjectCommand")) + { + // Group-object - annotates the types of Group and Value based on whatever we saw before Group-Object in the pipeline. + InferTypesFromGroupCommand(pseudoBinding, commandAst, inferredTypes); + } + + return inferredTypes; } - private static IEnumerable InferTypesFromCimCommand(PseudoBindingInfo pseudoBinding) + private static void InferTypesFromCimCommand(PseudoBindingInfo pseudoBinding, List inferredTypes) { string pseudoboundNamespace = - CompletionCompleters.NativeCommandArgumentCompletion_ExtractSecondaryArgument(pseudoBinding.BoundArguments, - "Namespace").FirstOrDefault(); + CompletionCompleters.NativeCommandArgumentCompletion_ExtractSecondaryArgument( + pseudoBinding.BoundArguments, + "Namespace") + .FirstOrDefault(); + string pseudoboundClassName = - CompletionCompleters.NativeCommandArgumentCompletion_ExtractSecondaryArgument(pseudoBinding.BoundArguments, - "ClassName").FirstOrDefault(); + CompletionCompleters.NativeCommandArgumentCompletion_ExtractSecondaryArgument( + pseudoBinding.BoundArguments, + "ClassName") + .FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(pseudoboundClassName)) { - yield return new PSTypeName(string.Format( - CultureInfo.InvariantCulture, - "{0}#{1}/{2}", - typeof(CimInstance).FullName, - pseudoboundNamespace ?? "root/cimv2", - pseudoboundClassName)); + var typeName = new PSTypeName( + string.Format( + CultureInfo.InvariantCulture, + "{0}#{1}/{2}", + typeof(CimInstance).FullName, + pseudoboundNamespace ?? "root/cimv2", + pseudoboundClassName)); + + inferredTypes.Add(typeName); } - yield return new PSTypeName(typeof(CimInstance)); + + inferredTypes.Add(new PSTypeName(typeof(CimInstance))); } - private IEnumerable InferTypesFromForeachCommand(PseudoBindingInfo pseudoBinding, CommandAst commandAst) + private void InferTypesFromForeachCommand(PseudoBindingInfo pseudoBinding, CommandAst commandAst, List inferredTypes) { - AstParameterArgumentPair argument; - if (pseudoBinding.BoundArguments.TryGetValue("MemberName", out argument)) + if (pseudoBinding.BoundArguments.TryGetValue("MemberName", out AstParameterArgumentPair argument)) { var previousPipelineElement = GetPreviousPipelineCommand(commandAst); if (previousPipelineElement == null) { - yield break; + return; } + foreach (var t in InferTypes(previousPipelineElement)) { var memberName = (((AstPair)argument).Argument as StringConstantExpressionAst)?.Value; - + if (memberName != null) { var members = _context.GetMembersByInferredType(t, false, null); bool maybeWantDefaultCtor = false; - foreach (var type in GetTypesOfMembers(t, memberName, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst: false)) - { - yield return type; - } + GetTypesOfMembers(t, memberName, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst: false, inferredTypes); } } } if (pseudoBinding.BoundArguments.TryGetValue("Begin", out argument)) { - foreach (var type in GetInferredTypeFromScriptBlockParameter(argument)) + GetInferredTypeFromScriptBlockParameter(argument, inferredTypes); + } + + if (pseudoBinding.BoundArguments.TryGetValue("Process", out argument)) + { + GetInferredTypeFromScriptBlockParameter(argument, inferredTypes); + } + + if (pseudoBinding.BoundArguments.TryGetValue("End", out argument)) + { + GetInferredTypeFromScriptBlockParameter(argument, inferredTypes); + } + } + + private void InferTypesFromGroupCommand(PseudoBindingInfo pseudoBinding, CommandAst commandAst, List inferredTypes) + { + if (pseudoBinding.BoundArguments.TryGetValue("AsHashTable", out AstParameterArgumentPair _)) + { + inferredTypes.Add(new PSTypeName(typeof(Hashtable))); + return; + } + + var noElement = pseudoBinding.BoundArguments.TryGetValue("NoElement", out AstParameterArgumentPair _); + + string[] properties = null; + bool scriptBlockProperty = false; + if (pseudoBinding.BoundArguments.TryGetValue("Property", out AstParameterArgumentPair propertyArgumentPair)) + { + if (propertyArgumentPair is AstPair astPair) { - yield return type; + switch (astPair.Argument) + { + case StringConstantExpressionAst stringConstant: + properties = new[] { stringConstant.Value }; + break; + case ArrayLiteralAst arrayLiteral: + properties = arrayLiteral.Elements.OfType().Select(static c => c.Value).ToArray(); + scriptBlockProperty = arrayLiteral.Elements.OfType().Any(); + break; + case CommandElementAst _: + scriptBlockProperty = true; + break; + } } } - if (pseudoBinding.BoundArguments.TryGetValue("Process", out argument)) + bool IsInPropertyArgument(object o) { - foreach (var type in GetInferredTypeFromScriptBlockParameter(argument)) + if (properties == null) { - yield return type; + return true; } + + string name; + switch (o) + { + case string s: + name = s; + break; + default: + name = GetMemberName(o); + break; + } + + foreach (var propertyName in properties) + { + if (name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; } - if (pseudoBinding.BoundArguments.TryGetValue("End", out argument)) + var previousPipelineElement = GetPreviousPipelineCommand(commandAst); + const string typeName = "Microsoft.PowerShell.Commands.GroupInfo"; + var members = new List(); + foreach (var prevType in InferTypes(previousPipelineElement)) { - foreach (var type in GetInferredTypeFromScriptBlockParameter(argument)) + members.Clear(); + if (noElement) + { + members.Add(new PSMemberNameAndType("Values", new PSTypeName(prevType.Name))); + inferredTypes.Add(PSSyntheticTypeName.Create(typeName, members)); + continue; + } + + var memberNameAndTypes = GetMemberNameAndTypeFromProperties(prevType, IsInPropertyArgument); + if (memberNameAndTypes.Count == 0) + { + continue; + } + + if (properties != null) { - yield return type; + foreach (var memType in memberNameAndTypes) + { + members.Clear(); + members.Add(new PSMemberNameAndType("Group", new PSTypeName(prevType.Name))); + members.Add(new PSMemberNameAndType("Values", new PSTypeName(memType.PSTypeName.Name))); + inferredTypes.Add(PSSyntheticTypeName.Create(typeName, members)); + } } + else + { + // No Property parameter given + // group infers to IList + members.Add(new PSMemberNameAndType("Group", new PSTypeName(prevType.Name))); + // Value infers to IList + if (!scriptBlockProperty) + { + members.Add(new PSMemberNameAndType("Values", new PSTypeName(prevType.Name))); + } + } + + inferredTypes.Add(PSSyntheticTypeName.Create(typeName, members)); } } - private IEnumerable InferTypesFromWhereAndSortCommand(CommandAst commandAst) + private void InferTypesFromWhereAndSortCommand(CommandAst commandAst, List inferredTypes) { - var parentPipeline = commandAst.Parent as PipelineAst; - if (parentPipeline != null) + InferTypesFromPreviousCommand(commandAst, inferredTypes); + } + + private void InferTypesFromPreviousCommand(CommandAst commandAst, List inferredTypes) + { + if (commandAst.Parent is PipelineAst parentPipeline) { int i; for (i = 0; i < parentPipeline.PipelineElements.Count; i++) { if (parentPipeline.PipelineElements[i] == commandAst) + { break; + } } + if (i > 0) { - foreach (var typename in InferTypes(parentPipeline.PipelineElements[i - 1])) + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(parentPipeline.PipelineElements[i - 1]))); + } + } + } + + private void InferTypesFromSelectCommand(PseudoBindingInfo pseudoBinding, CommandAst commandAst, List inferredTypes) + { + void InferFromSelectProperties(AstParameterArgumentPair astParameterArgumentPair, CommandBaseAst previousPipelineElementAst, bool includeMatchedProperties = true) + { + if (astParameterArgumentPair is AstPair astPair) + { + static object ToWildCardOrString(string value) => WildcardPattern.ContainsWildcardCharacters(value) ? (object)new WildcardPattern(value) : value; + object[] properties = null; + switch (astPair.Argument) { - yield return typename; + case StringConstantExpressionAst stringConstant: + properties = new[] { ToWildCardOrString(stringConstant.Value) }; + break; + case ArrayLiteralAst arrayLiteral: + properties = arrayLiteral.Elements.OfType().Select(static c => ToWildCardOrString(c.Value)).ToArray(); + break; } + + if (properties == null) + { + return; + } + + bool IsInPropertyArgument(object o) + { + string name; + switch (o) + { + case string s: + name = s; + break; + default: + name = GetMemberName(o); + break; + } + + foreach (var propertyNameOrPattern in properties) + { + switch (propertyNameOrPattern) + { + case string propertyName: + if (string.Equals(name, propertyName, StringComparison.OrdinalIgnoreCase)) + { + return includeMatchedProperties; + } + + break; + case WildcardPattern pattern: + if (pattern.IsMatch(name)) + { + return includeMatchedProperties; + } + + break; + } + } + + return !includeMatchedProperties; + } + + foreach (var t in InferTypes(previousPipelineElementAst)) + { + var list = GetMemberNameAndTypeFromProperties(t, IsInPropertyArgument); + inferredTypes.Add(PSSyntheticTypeName.Create(typeof(PSObject), list)); + } + } + } + + var previousPipelineElement = GetPreviousPipelineCommand(commandAst); + if (previousPipelineElement == null) + { + return; + } + + if (pseudoBinding.BoundArguments.TryGetValue("Property", out var property)) + { + InferFromSelectProperties(property, previousPipelineElement); + + return; + } + + if (pseudoBinding.BoundArguments.TryGetValue("ExcludeProperty", out var excludeProperty)) + { + InferFromSelectProperties(excludeProperty, previousPipelineElement, includeMatchedProperties: false); + + return; + } + + if (pseudoBinding.BoundArguments.TryGetValue("ExpandProperty", out var expandedPropertyArgument)) + { + foreach (var t in InferTypes(previousPipelineElement)) + { + var memberName = (((AstPair)expandedPropertyArgument).Argument as StringConstantExpressionAst)?.Value; + + if (memberName != null) + { + var members = _context.GetMembersByInferredType(t, false, null); + bool maybeWantDefaultCtor = false; + GetTypesOfMembers(t, memberName, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst: false, inferredTypes); + } + } + + return; + } + + InferTypesFromPreviousCommand(commandAst, inferredTypes); + } + + private List GetMemberNameAndTypeFromProperties(PSTypeName t, Func isInPropertyList) + { + var list = new List(8); + var members = _context.GetMembersByInferredType(t, false, isInPropertyList); + var memberTypes = new List(); + foreach (var mem in members) + { + if (!IsProperty(mem)) + { + continue; } + + var memberName = GetMemberName(mem); + if (!isInPropertyList(memberName)) + { + continue; + } + + bool maybeWantDefaultCtor = false; + memberTypes.Clear(); + GetTypesOfMembers(t, memberName, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst: false, memberTypes); + if (memberTypes.Count > 0) + { + list.Add(new PSMemberNameAndType(memberName, memberTypes[0])); + } + } + + return list; + } + + private static bool IsProperty(object member) + { + switch (member) + { + case PropertyInfo _: + return true; + case PSMemberInfo memberInfo: + return (memberInfo.MemberType & PSMemberTypes.Properties) == memberInfo.MemberType; + default: + return false; } } + private static string GetMemberName(object member) + { + var name = string.Empty; + switch (member) + { + case PSMemberInfo psMemberInfo: + name = psMemberInfo.Name; + break; + case MemberInfo memberInfo: + name = memberInfo.Name; + break; + case PropertyMemberAst propertyMember: + name = propertyMember.Name; + break; + case FunctionMemberAst functionMember: + name = functionMember.Name; + break; + case DotNetAdapter.MethodCacheEntry methodCacheEntry: + name = methodCacheEntry[0].method.Name; + break; + } + + return name; + } + private static PSTypeName InferTypesFromNewObjectCommand(PseudoBindingInfo pseudoBinding) { - AstParameterArgumentPair typeArgument; - if (pseudoBinding.BoundArguments.TryGetValue("TypeName", out typeArgument)) + if (pseudoBinding.BoundArguments.TryGetValue("TypeName", out var typeArgument)) { var typeArgumentPair = typeArgument as AstPair; - var stringConstantExpr = typeArgumentPair?.Argument as StringConstantExpressionAst; - if (stringConstantExpr != null) + if (typeArgumentPair?.Argument is StringConstantExpressionAst stringConstantExpr) { - return new PSTypeName(stringConstantExpr.Value); + return new PSTypeName(stringConstantExpr.Value); } } + return null; } @@ -1005,34 +1914,44 @@ 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) - return Utils.EmptyArray(); + if (!(memberCommandElement is StringConstantExpressionAst memberAsStringConst)) + { + return Array.Empty(); + } - var exprType = GetExpressionType(expression, isStatic); + var exprType = GetExpressionType(expression, isStatic); if (exprType == null || exprType.Length == 0) { - return Utils.EmptyArray(); + 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"); + && isInvokeMemberExpressionAst + && memberAsStringConst.Value.EqualsOrdinalIgnoreCase("new"); // We use a list of member names because we might discover aliases properties // and if we do, we'll add to the list. 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) @@ -1040,26 +1959,67 @@ private IEnumerable InferTypesFrom(MemberExpressionAst memberExpress res.Add(type); } } + return res; } - private List GetTypesOfMembers(PSTypeName thisType, string memberName, IList members, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst) + private static IEnumerable InferTypeFromRef(InvokeMemberExpressionAst invokeMember, ExpressionAst refArgument) { - var memberNamesToCheck = new List { memberName }; - var res = new List(10); + 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; + } - AddTypesOfMembers(thisType, memberNamesToCheck, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, res); - return res; + 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 AddTypesOfMembers(PSTypeName currentType, List memberNamesToCheck, IList members, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst, List result) + private void GetTypesOfMembers( + PSTypeName thisType, + string memberName, + IList members, + ref bool maybeWantDefaultCtor, + bool isInvokeMemberExpressionAst, + List inferredTypes) + { + var memberNamesToCheck = new List { memberName }; + AddTypesOfMembers(thisType, memberNamesToCheck, members, ref maybeWantDefaultCtor, isInvokeMemberExpressionAst, genericTypeArguments: null, inferredTypes); + } + + private void AddTypesOfMembers( + PSTypeName currentType, + List memberNamesToCheck, + IList members, + ref bool maybeWantDefaultCtor, + bool isInvokeMemberExpressionAst, + IList genericTypeArguments, + List result) { for (int i = 0; i < memberNamesToCheck.Count; i++) { 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; } @@ -1067,53 +2027,44 @@ private void AddTypesOfMembers(PSTypeName currentType, List memberNamesT } } - private bool TryGetTypeFromMember(PSTypeName currentType, object member, string memberName, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst, List result, List memberNamesToCheck) + private bool TryGetTypeFromMember( + PSTypeName currentType, + object member, + string memberName, + ref bool maybeWantDefaultCtor, + bool isInvokeMemberExpressionAst, + IList genericTypeArguments, + List result, + List memberNamesToCheck) { switch (member) { case PropertyInfo propertyInfo: // .net property - { if (propertyInfo.Name.EqualsOrdinalIgnoreCase(memberName) && !isInvokeMemberExpressionAst) { result.Add(new PSTypeName(propertyInfo.PropertyType)); return true; } + return false; - } - case FieldInfo fieldInfo: // .net field - { + case FieldInfo fieldInfo: // .net field if (fieldInfo.Name.EqualsOrdinalIgnoreCase(memberName) && !isInvokeMemberExpressionAst) { result.Add(new PSTypeName(fieldInfo.FieldType)); return true; } + return false; - } case DotNetAdapter.MethodCacheEntry methodCacheEntry: // .net method - { if (methodCacheEntry[0].method.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) { maybeWantDefaultCtor = false; - if (isInvokeMemberExpressionAst) - { - var res = (from method in methodCacheEntry.methodInformationStructures - select method.method as MethodInfo into methodInfo - where methodInfo != null && !methodInfo.ReturnType.GetTypeInfo().ContainsGenericParameters - select new PSTypeName(methodInfo.ReturnType)); - result.AddRange(res); - return true; - } - else - { - // Accessing a method as a property, we'd return a wrapper over the method. - result.Add(new PSTypeName(typeof(PSMethod))); - return true; - } + AddTypesFromMethodCacheEntry(methodCacheEntry, genericTypeArguments, result, isInvokeMemberExpressionAst); + return true; } + return false; - } case MemberAst memberAst: // this is for members defined by PowerShell classes - { if (memberAst.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) { if (isInvokeMemberExpressionAst) @@ -1128,9 +2079,11 @@ select method.method as MethodInfo into methodInfo { if (memberAst is PropertyMemberAst propertyMemberAst) { - result.Add(propertyMemberAst.PropertyType != null + result.Add( + propertyMemberAst.PropertyType != null ? new PSTypeName(propertyMemberAst.PropertyType.TypeName) : new PSTypeName(typeof(object))); + return true; } else @@ -1141,51 +2094,52 @@ select method.method as MethodInfo into methodInfo } } } + return false; - } case PSMemberInfo memberInfo: - { if (!memberInfo.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) { return false; } + ScriptBlock scriptBlock = null; switch (memberInfo) { + case PSMethod m: + if (m.adapterData is DotNetAdapter.MethodCacheEntry methodCacheEntry) + { + AddTypesFromMethodCacheEntry(methodCacheEntry, genericTypeArguments, result, isInvokeMemberExpressionAst); + return true; + } + + return false; case PSProperty p: - { result.Add(new PSTypeName(p.Value.GetType())); return true; - } case PSNoteProperty noteProperty: - { result.Add(new PSTypeName(noteProperty.Value.GetType())); return true; - } case PSAliasProperty aliasProperty: - { memberNamesToCheck.Add(aliasProperty.ReferencedMemberName); return true; - } case PSCodeProperty codeProperty: - { if (codeProperty.GetterCodeReference != null) { result.Add(new PSTypeName(codeProperty.GetterCodeReference.ReturnType)); } + return true; - } case PSScriptProperty scriptProperty: - { scriptBlock = scriptProperty.GetterScript; break; - } case PSScriptMethod scriptMethod: - { scriptBlock = scriptMethod.Script; break; - } + case PSInferredProperty inferredProperty: + result.Add(inferredProperty.TypeName); + break; } + if (scriptBlock != null) { var thisToRestore = _context.CurrentThisType; @@ -1200,7 +2154,7 @@ select method.method as MethodInfo into methodInfo } else { - result.AddRange(InferTypes(scriptBlock.Ast).ToArray()); + result.AddRange(InferTypes(scriptBlock.Ast)); return true; } } @@ -1209,32 +2163,101 @@ select method.method as MethodInfo into methodInfo _context.CurrentThisType = thisToRestore; } } - } - return false; + + return false; } + return false; } + private static void AddTypesFromMethodCacheEntry( + DotNetAdapter.MethodCacheEntry methodCacheEntry, + IList genericTypeArguments, + List result, + bool isInvokeMemberExpressionAst) + { + if (isInvokeMemberExpressionAst) + { + Type[] resolvedTypeArguments = null; + if (genericTypeArguments is not null) + { + resolvedTypeArguments = new Type[genericTypeArguments.Count]; + for (int i = 0; i < genericTypeArguments.Count; i++) + { + 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; + } + } + + 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) { - var exprAsType = expression as TypeExpressionAst; - if (exprAsType == null) + if (!(expression is TypeExpressionAst exprAsType)) + { return null; + } + var type = exprAsType.TypeName.GetReflectionType(); if (type == null) { var typeName = exprAsType.TypeName as TypeName; if (typeName?._typeDefinitionAst == null) + { return null; + } - exprType = new[] {new PSTypeName(typeName._typeDefinitionAst)}; + exprType = new[] { new PSTypeName(typeName._typeDefinitionAst) }; } else { - exprType = new[] {new PSTypeName(type)}; + exprType = new[] { new PSTypeName(type) }; } } else @@ -1246,225 +2269,437 @@ private PSTypeName[] GetExpressionType(ExpressionAst expression, bool isStatic) { return exprType; } + return exprType; } } + return exprType; } - private IEnumerable InferTypeFrom(VariableExpressionAst variableExpressionAst) + private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List inferredTypes) { // We don't need to handle drive qualified variables, we can usually get those values // without needing to "guess" at the type. var astVariablePath = variableExpressionAst.VariablePath; - if (!astVariablePath.IsVariable) + if (!astVariablePath.IsVariable || astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.Null)) { // Not a variable - the caller should have already tried going to session state // to get the item and hence it's type, but that must have failed. Don't try again. - yield break; + return; } - Ast parent = variableExpressionAst.Parent; + Ast currentAst = variableExpressionAst.Parent; if (astVariablePath.IsUnqualified && (SpecialVariables.IsUnderbar(astVariablePath.UserPath) || astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.PSItem))) { - // $_ is special, see if we're used in a script block in some pipeline. - while (parent != null) + // The automatic variable $_ is assigned a value in scriptblocks, Switch loops and Catch/Trap statements + // This loop will find whichever Ast that determines the value of $_ + // The value in scriptblocks is determined by the parents of that scriptblock, the only interesting scenarios are: + // 1: MemberInvocation like: $Collection.Where({$_}) + // 2: Command pipelines like: dir | where {$_} + // The value in a Switch loop is whichever item is in the condition part of the statement. + // The value in Catch/Trap statements is always an error record. + bool hasSeenScriptBlock = false; + while (currentAst is not null) { - if (parent is ScriptBlockExpressionAst) + if (currentAst is CatchClauseAst or TrapStatementAst) + { break; - parent = parent.Parent; - } + } + else if (currentAst is SwitchStatementAst switchStatement + && switchStatement.Condition.Extent.EndOffset < variableExpressionAst.Extent.StartOffset) + { + currentAst = switchStatement.Condition; + break; + } + else if (currentAst is ErrorStatementAst switchErrorStatement && switchErrorStatement.Kind?.Kind == TokenKind.Switch) + { + if (switchErrorStatement.Conditions?.Count > 0) + { + if (switchErrorStatement.Conditions[0].Extent.EndOffset < variableExpressionAst.Extent.StartOffset) + { + 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; + } + } - if (parent != null) - { - if (parent.Parent is CommandExpressionAst && parent.Parent.Parent is PipelineAst) + break; + } + else if (currentAst is ScriptBlockExpressionAst) { - // Script block in a hash table, could be something like: - // dir | ft @{ Expression = { $_ } } - if (parent.Parent.Parent.Parent is HashtableAst) + hasSeenScriptBlock = true; + } + else if (hasSeenScriptBlock) + { + if (currentAst is InvokeMemberExpressionAst invokeMember) { - parent = parent.Parent.Parent.Parent; + currentAst = invokeMember.Expression; + break; } - else if (parent.Parent.Parent.Parent is ArrayLiteralAst && parent.Parent.Parent.Parent.Parent is HashtableAst) + else if (currentAst is CommandAst cmdAst && cmdAst.Parent is PipelineAst pipeline && pipeline.PipelineElements.Count > 1) { - parent = parent.Parent.Parent.Parent.Parent; + // 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) + { + currentAst = pipeline.PipelineElements[indexOfPreviousCommand]; + break; + } } } - if (parent.Parent is CommandParameterAst) - { - parent = parent.Parent; - } - var commandAst = parent.Parent as CommandAst; - if (commandAst != null) + currentAst = currentAst.Parent; + } + + if (currentAst is CatchClauseAst catchBlock) + { + if (catchBlock.CatchTypes.Count > 0) { - // 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) yield break; - foreach (var result in InferTypes(pipelineAst.PipelineElements[0])) + foreach (TypeConstraintAst catchType in catchBlock.CatchTypes) { - if (result.Type != null) + Type exceptionType = catchType.TypeName.GetReflectionType(); + if (typeof(Exception).IsAssignableFrom(exceptionType)) { - // 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) - { - yield return 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().Where( - t => - t.GetTypeInfo().IsGenericType && - t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - foreach (var i in enumerableInterfaces) - { - yield return new PSTypeName(i.GetGenericArguments()[0]); - } - continue; - } + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord<>).MakeGenericType(exceptionType))); } - yield return result; } - yield break; + } + + // 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(typeof(ErrorRecord<>).MakeGenericType(exceptionType))); + } + } + if (inferredTypes.Count == 0) + { + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); + } + } + else if (currentAst is not null) + { + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(currentAst))); + } + + return; } - // For certain variables, we always know their type, well at least we can assume we know. - if (astVariablePath.IsUnqualified) + // Process the well known variable $this + if (astVariablePath.IsUnqualified + && astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.This) + && (_context.CurrentTypeDefinitionAst is not null || _context.CurrentThisType is not null)) { - var isThis = astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.This); - if (!isThis || (_context.CurrentTypeDefinitionAst == null && _context.CurrentThisType == null)) + // $this is special in script properties and in PowerShell classes + PSTypeName typeName = _context.CurrentThisType ?? new PSTypeName(_context.CurrentTypeDefinitionAst); + inferredTypes.Add(typeName); + return; + } + + // Process other well known variables like $true and $pshome + if (SpecialVariables.AllScopeVariables.TryGetValue(astVariablePath.UnqualifiedPath, out Type knownType)) + { + if (knownType == typeof(object)) { - for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) + if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var psType)) { - if (!astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) - continue; - var type = SpecialVariables.AutomaticVariableTypes[i]; - if (type != typeof(object)) - yield return new PSTypeName(type); + inferredTypes.Add(psType); + } + } + else + { + inferredTypes.Add(new PSTypeName(knownType)); + } + + return; + } + + // Process automatic variables like $MyInvocation and $PSBoundParameters + for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) + { + if (!astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) + { + continue; + } + + Type type = SpecialVariables.AutomaticVariableTypes[i]; + if (type != typeof(object)) + { + inferredTypes.Add(new PSTypeName(type)); + } + + return; + } + + // 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 (currentAst is IParameterMetadataProvider) + { + 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; + } + + currentAst = currentAst.Parent; + } + + // 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.Add(new PSTypeName(assignmentVisitor.LastConstraint)); + } + else if (assignmentVisitor.LastAssignment is not null) + { + if (assignmentVisitor.EnumerateAssignment) + { + 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)); + } + } + } + else if (assignmentVisitor.LastAssignmentType is not null) + { + inferredTypes.Add(assignmentVisitor.LastAssignmentType); + } + + if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var evalTypeName)) + { + inferredTypes.Add(evalTypeName); + } + } + + /// + /// Gets the most specific array type possible from a group of inferred types. + /// + /// The inferred types all the items in the array. + /// The inferred strongly typed array type. + private static PSTypeName GetArrayType(IEnumerable inferredTypes) + { + PSTypeName foundType = null; + foreach (PSTypeName inferredType in inferredTypes) + { + if (inferredType.Type == null) + { + return new PSTypeName(typeof(object[])); } - else + + // IEnumerable<>.GetEnumerator and IDictionary.GetEnumerator will always be + // inferred as multiple types due to explicit implementations, so if we find + // one then assume the rest are also enumerators. + if (typeof(IEnumerator).IsAssignableFrom(inferredType.Type)) { - yield return _context.CurrentThisType != null - ? _context.CurrentThisType - : new PSTypeName(_context.CurrentTypeDefinitionAst); - yield break; + foundType = inferredType; + break; } - } - else + + if (foundType == null) { - yield return new PSTypeName(_context.CurrentTypeDefinitionAst); - yield break; + foundType = inferredType; + continue; } + // If there are mixed types then fall back to object[]. + if (foundType.Type != inferredType.Type) + { + return new PSTypeName(typeof(object[])); + } + } - // 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. + if (foundType == null) + { + return new PSTypeName(typeof(object[])); + } - while (parent.Parent != null) + if (foundType.Type.IsArray) { - parent = parent.Parent; + return foundType; } - if (parent.Parent is FunctionDefinitionAst) + Type enumeratedItemType = GetMostSpecificEnumeratedItemType(foundType.Type); + if (enumeratedItemType != null) { - parent = parent.Parent; + return new PSTypeName(enumeratedItemType.MakeArrayType()); } - int startOffset = variableExpressionAst.Extent.StartOffset; - var targetAsts = (List)AstSearcher.FindAll(parent, - ast => (ast is ParameterAst || ast is AssignmentStatementAst || ast is ForEachStatementAst || ast is CommandAst) - && variableExpressionAst.AstAssignsToSameVariable(ast) - && ast.Extent.EndOffset < startOffset, - searchNestedScriptBlocks: true); + return new PSTypeName(foundType.Type.MakeArrayType()); + } - var parameterAst = targetAsts.OfType().FirstOrDefault(); - if (parameterAst != null) + /// + /// Gets the most specific type item type from a type that is potentially enumerable. + /// + /// The type to infer enumerated item type from. + /// The inferred enumerated item type. + private static Type GetMostSpecificEnumeratedItemType(Type enumerableType) + { + if (enumerableType.IsArray) { - var parameterTypes = InferTypes(parameterAst).ToArray(); - if (parameterTypes.Length > 0) - { - foreach (var parameterType in parameterTypes) - { - yield return parameterType; - } - yield break; - } + return enumerableType.GetElementType(); } - var assignAsts = targetAsts.OfType().ToArray(); + // These types implement IEnumerable, but we intentionally do not enumerate them. + if (enumerableType == typeof(string) || + typeof(IDictionary).IsAssignableFrom(enumerableType) || + typeof(Xml.XmlNode).IsAssignableFrom(enumerableType)) + { + return enumerableType; + } - // 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) + if (enumerableType == typeof(Data.DataTable)) { - var lhsConvert = assignAst.Left as ConvertExpressionAst; - if (lhsConvert != null) - { - yield return new PSTypeName(lhsConvert.Type.TypeName); - yield break; - } + return typeof(Data.DataRow); } - var foreachAst = targetAsts.OfType().FirstOrDefault(); - if (foreachAst != null) + bool hasSeenNonGeneric = false; + bool hasSeenDictionaryEnumerator = false; + Type collectionInterface = GetGenericCollectionLikeInterface( + enumerableType, + ref hasSeenNonGeneric, + ref hasSeenDictionaryEnumerator); + + if (collectionInterface != null) { - foreach (var typeName in InferTypes(foreachAst.Condition)) - { - yield return typeName; - } - yield break; + return collectionInterface.GetGenericArguments()[0]; } - var commandCompletionAst = targetAsts.OfType().FirstOrDefault(); - if (commandCompletionAst != null) + foreach (Type interfaceType in enumerableType.GetInterfaces()) { - foreach (var typeName in InferTypes(commandCompletionAst)) + collectionInterface = GetGenericCollectionLikeInterface( + interfaceType, + ref hasSeenNonGeneric, + ref hasSeenDictionaryEnumerator); + + if (collectionInterface != null) { - yield return typeName; + return collectionInterface.GetGenericArguments()[0]; } - yield break; } - int smallestDiff = int.MaxValue; - AssignmentStatementAst closestAssignment = null; - foreach (var assignAst in assignAsts) + if (hasSeenDictionaryEnumerator) { - var endOffset = assignAst.Extent.EndOffset; - if ((startOffset - endOffset) < smallestDiff) - { - smallestDiff = startOffset - endOffset; - closestAssignment = assignAst; - } + return typeof(DictionaryEntry); } - if (closestAssignment != null) + if (hasSeenNonGeneric) { - foreach (var type in InferTypes(closestAssignment.Right)) + return typeof(object); + } + + return null; + } + + /// + /// Determines if the interface can be used to infer a specific enumerated type. + /// + /// 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 . + /// + /// + /// 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 . + /// + private static Type GetGenericCollectionLikeInterface( + Type interfaceType, + ref bool hasSeenNonGeneric, + ref bool hasSeenDictionaryEnumerator) + { + if (!interfaceType.IsInterface) + { + return null; + } + + if (interfaceType.IsConstructedGenericType) + { + Type openGeneric = interfaceType.GetGenericTypeDefinition(); + if (openGeneric == typeof(IEnumerator<>) || + openGeneric == typeof(IEnumerable<>)) { - yield return type; + return interfaceType; } } + if (interfaceType == typeof(IDictionaryEnumerator)) + { + hasSeenDictionaryEnumerator = true; + } - PSTypeName evalTypeName; - if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out evalTypeName)) + if (interfaceType == typeof(IEnumerator) || + interfaceType == typeof(IEnumerable)) { - yield return evalTypeName; + hasSeenNonGeneric = true; } + return null; } private IEnumerable InferTypeFrom(IndexExpressionAst indexExpressionAst) @@ -1473,22 +2708,44 @@ 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) { if (type.IsArray) { yield return new PSTypeName(type.GetElementType()); + + 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()) { - var isGenericType = iface.GetTypeInfo().IsGenericType; + var isGenericType = iface.IsGenericType; if (isGenericType && iface.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var valueType = iface.GetGenericArguments()[1]; - if (!valueType.GetTypeInfo().ContainsGenericParameters) + if (!valueType.ContainsGenericParameters) { foundAny = true; yield return new PSTypeName(valueType); @@ -1497,7 +2754,7 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression else if (isGenericType && iface.GetGenericTypeDefinition() == typeof(IList<>)) { var valueType = iface.GetGenericArguments()[0]; - if (!valueType.GetTypeInfo().ContainsGenericParameters) + if (!valueType.ContainsGenericParameters) { foundAny = true; yield return new PSTypeName(valueType); @@ -1528,15 +2785,41 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression } } - private IEnumerable GetInferredTypeFromScriptBlockParameter(AstParameterArgumentPair argument) + /// + /// 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. + internal static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) + { + foreach (PSTypeName maybeEnumerableType in enumerableTypes) + { + Type type = maybeEnumerableType.Type; + if (type == null) + { + yield return maybeEnumerableType; + continue; + } + + Type enumeratedItemType = GetMostSpecificEnumeratedItemType(type); + yield return enumeratedItemType == null + ? maybeEnumerableType + : new PSTypeName(enumeratedItemType); + } + } + + private void GetInferredTypeFromScriptBlockParameter(AstParameterArgumentPair argument, List inferredTypes) { var argumentPair = argument as AstPair; - var scriptBlockExpressionAst = argumentPair?.Argument as ScriptBlockExpressionAst; - if (scriptBlockExpressionAst == null) yield break; - foreach (var type in InferTypes(scriptBlockExpressionAst.ScriptBlock)) + if (!(argumentPair?.Argument is ScriptBlockExpressionAst scriptBlockExpressionAst)) { - yield return type; + return; } + + inferredTypes.AddRange(InferTypes(scriptBlockExpressionAst.ScriptBlock)); } object ICustomAstVisitor2.VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) @@ -1575,94 +2858,479 @@ object ICustomAstVisitor2.VisitDynamicKeywordStatement(DynamicKeywordStatementAs return dynamicKeywordAst.CommandElements[0].Accept(this); } + object ICustomAstVisitor2.VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + return InferTypes(ternaryExpressionAst.IfTrue).Concat(InferTypes(ternaryExpressionAst.IfFalse)); + } + + object ICustomAstVisitor2.VisitPipelineChain(PipelineChainAst pipelineChainAst) + { + var types = new List(); + types.AddRange(InferTypes(pipelineChainAst.LhsPipelineChain)); + types.AddRange(InferTypes(pipelineChainAst.RhsPipeline)); + return types.Distinct(); + } + private static CommandBaseAst GetPreviousPipelineCommand(CommandAst commandAst) { var pipe = (PipelineAst)commandAst.Parent; var i = pipe.PipelineElements.IndexOf(commandAst); return i != 0 ? pipe.PipelineElements[i - 1] : null; } - } - static class TypeInferenceExtension - { - public static bool EqualsOrdinalIgnoreCase(this string s, string t) + private sealed class VariableAssignmentVisitor : AstVisitor2 { - return string.Equals(s, t, StringComparison.OrdinalIgnoreCase); - } + /// + /// 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; - public static IEnumerable GetGetterProperty(this Type type, string propertyName) - { - return type.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where( - m => + /// + /// 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)) { - var name = m.Name; - // Equals without string allocation - return name.Length == propertyName.Length + 4 && - name.StartsWith("get_") && propertyName.IndexOf(name, 4, StringComparison.Ordinal) == 4; + // 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; } - ); - } + } - public static bool AstAssignsToSameVariable(this VariableExpressionAst variableAst, Ast ast) - { - var parameterAst = ast as ParameterAst; - var variableAstVariablePath = variableAst.VariablePath; - if (parameterAst != null) + 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; + } + + 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) + { + if (string.IsNullOrEmpty(userPath)) + { + 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); + } + + private bool AssignsToTargetScope(string scopeName) + => LocalScopeOnly + ? string.IsNullOrEmpty(scopeName) || scopeName.EqualsOrdinalIgnoreCase("Local") || scopeName.EqualsOrdinalIgnoreCase("Private") + : ScopeIsLocal || !(scopeName.EqualsOrdinalIgnoreCase("Local") || scopeName.EqualsOrdinalIgnoreCase("Private")); + + 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; } - var foreachAst = ast as ForEachStatementAst; - if (foreachAst != null) + 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; } - var commandAst = ast as CommandAst; - if (commandAst != null) + public override AstVisitAction VisitCommand(CommandAst commandAst) { - string[] variableParameters = new string[] { "PV", "PipelineVariable", "OV", "OutVariable" }; - StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false, variableParameters); + if (commandAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + string commandName = commandAst.GetCommandName(); + if (commandName is not null && CompletionCompleters.s_varModificationCommands.Contains(commandName)) + { + 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)) + { + SetLastAssignment(variableValue.Value); + return AstVisitAction.Continue; + } + } - if (bindingResult != null) + StaticBindingResult bindResult = StaticParameterBinder.BindCommand(commandAst, resolve: false); + if (bindResult is not null) { - foreach (string commandVariableParameter in variableParameters) + foreach (string parameterName in CompletionCompleters.s_outVarParameters) { - if (bindingResult.BoundParameters.TryGetValue(commandVariableParameter, out ParameterBindingResult parameterBindingResult)) + 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; + } + + public override AstVisitAction VisitParameter(ParameterAst parameterAst) + { + 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; } - var assignmentAst = (AssignmentStatementAst)ast; - var lhs = assignmentAst.Left; - var convertExpr = lhs as ConvertExpressionAst; - if (convertExpr != null) + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { - lhs = convertExpr.Child; + 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 varExpr = lhs as VariableExpressionAst; - if (varExpr == null) - return false; + public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst) + { + if (convertExpressionAst.IsRef() + && convertExpressionAst.Child is VariableExpressionAst varAst + && AssignsToTargetVar(varAst)) + { + SetLastAssignment(convertExpressionAst); + } + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) + { + // Attributes can't assign values to variables so they aren't interesting. + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + return scriptBlockExpressionAst.IsDotsourced() + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) + { + if (dataStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + if (AssignsToTargetVar(dataStatementAst.Variable) && dataStatementAst.Extent.EndOffset < VariableTarget.Extent.StartOffset) + { + SetLastAssignment(dataStatementAst.Body); + } + + return AstVisitAction.SkipChildren; + } + + 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; - var candidateVarPath = varExpr.VariablePath; - if (candidateVarPath.UserPath.Equals(variableAstVariablePath.UserPath, StringComparison.OrdinalIgnoreCase)) - return true; + // 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; + } - // 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)) - return true; + 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 1728c2f812e..73d44e94fbe 100644 --- a/src/System.Management.Automation/engine/parser/TypeResolver.cs +++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs @@ -1,12 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -// for fxcop +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Specialized; +#if !UNIX +using System.DirectoryServices; +#endif using System.Globalization; using System.Linq; using System.Management.Automation.Language; @@ -21,12 +22,9 @@ using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; using System.Xml; + using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Commands; -#if !CORECLR -// System.DirectoryServices are not in CoreCLR -using System.DirectoryServices; -#endif namespace System.Management.Automation.Language { @@ -56,8 +54,9 @@ private static Type LookForTypeInSingleAssembly(Assembly assembly, string typena /// internal class AmbiguousTypeException : InvalidCastException { - public string[] Candidates { private set; get; } - public TypeName TypeName { private set; get; } + public string[] Candidates { get; } + + public TypeName TypeName { get; } public AmbiguousTypeException(TypeName typeName, IEnumerable candidates) { @@ -81,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 { @@ -130,7 +132,7 @@ private static Type LookForTypeInAssemblies(TypeName typeName, if (foundType2 != null) { - exception = new AmbiguousTypeException(typeName, new String[] { foundType.AssemblyQualifiedName, foundType2.AssemblyQualifiedName }); + exception = new AmbiguousTypeException(typeName, new string[] { foundType.AssemblyQualifiedName, foundType2.AssemblyQualifiedName }); return null; } @@ -142,32 +144,24 @@ private static Type LookForTypeInAssemblies(TypeName typeName, /// internal static bool IsPublic(Type type) { - TypeInfo typeInfo = type.GetTypeInfo(); - return IsPublic(typeInfo); - } - - /// - /// A typeInfo is public if IsPublic or (IsNestedPublic and is nested in public type(s)) - /// - internal static bool IsPublic(TypeInfo typeInfo) - { - if (typeInfo.IsPublic) + if (type.IsPublic) { return true; } - if (!typeInfo.IsNestedPublic) + + if (!type.IsNestedPublic) { return false; } - Type type; - while ((type = typeInfo.DeclaringType) != null) + + while ((type = type.DeclaringType) != null) { - typeInfo = type.GetTypeInfo(); - if (!(typeInfo.IsPublic || typeInfo.IsNestedPublic)) + if (!(type.IsPublic || type.IsNestedPublic)) { return false; } } + return true; } @@ -192,6 +186,7 @@ private static Type ResolveTypeNameWorker(TypeName typeName, { return result; } + currentScope = currentScope.Parent; } @@ -230,7 +225,7 @@ private static Type ResolveTypeNameWorker(TypeName typeName, /// This set should be used directly only in the method CallResolveTypeNameWorkerHelper. /// [ThreadStatic] - private static HashSet s_searchedAssemblies = null; + private static HashSet t_searchedAssemblies = null; /// /// A helper method to call ResolveTypeNameWorker in steps. @@ -242,21 +237,21 @@ private static Type CallResolveTypeNameWorkerHelper(TypeName typeName, TypeResolutionState typeResolutionState, out Exception exception) { - if (s_searchedAssemblies == null) + if (t_searchedAssemblies == null) { - s_searchedAssemblies = new HashSet(); + t_searchedAssemblies = new HashSet(); } else { // Clear the set before starting a full search to make sure we have a clean start. - s_searchedAssemblies.Clear(); + t_searchedAssemblies.Clear(); } try { exception = null; - var currentScope = context != null ? context.EngineSessionState.CurrentScope : null; - Type result = ResolveTypeNameWorker(typeName, currentScope, typeResolutionState.assemblies, s_searchedAssemblies, typeResolutionState, + 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) { @@ -265,14 +260,14 @@ private static Type CallResolveTypeNameWorkerHelper(TypeName typeName, // If the assemblies to search from is not specified by the caller of 'ResolveTypeNameWithContext', // then we search our assembly cache first, so as to give preference to resolving the type against // assemblies explicitly loaded by powershell, for example, via importing module/snapin. - result = ResolveTypeNameWorker(typeName, currentScope, context.AssemblyCache.Values, s_searchedAssemblies, typeResolutionState, + result = ResolveTypeNameWorker(typeName, currentScope, context.AssemblyCache.Values, t_searchedAssemblies, typeResolutionState, /*onlySearchInGivenAssemblies*/ true, /* reportAmbiguousException */ false, out exception); } if (result == null) { // Search from the assembly list passed in. - result = ResolveTypeNameWorker(typeName, currentScope, assemblies, s_searchedAssemblies, typeResolutionState, + result = ResolveTypeNameWorker(typeName, currentScope, assemblies, t_searchedAssemblies, typeResolutionState, /*onlySearchInGivenAssemblies*/ true, /* reportAmbiguousException */ false, out exception); } } @@ -282,7 +277,7 @@ private static Type CallResolveTypeNameWorkerHelper(TypeName typeName, finally { // Clear the set after a full search, so dynamic assemblies can get reclaimed as needed. - s_searchedAssemblies.Clear(); + t_searchedAssemblies.Clear(); } } @@ -379,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. @@ -452,6 +444,7 @@ internal static Type ResolveTypeNameWithContext(TypeName typeName, out Exception { TypeCache.Add(typeName, typeResolutionState, result); } + return result; } @@ -491,13 +484,13 @@ internal static Type ResolveITypeName(ITypeName iTypeName, out Exception excepti /// /// This routine converts a string into a Type object using the msh rules. /// - /// A string representing the name of the type to convert - /// The exception, if one happened, trying to find the type + /// A string representing the name of the type to convert. + /// The exception, if one happened, trying to find the type. /// A type if the conversion was successful, null otherwise. internal static Type ResolveType(string strTypeName, out Exception exception) { exception = null; - if (String.IsNullOrWhiteSpace(strTypeName)) + if (string.IsNullOrWhiteSpace(strTypeName)) { return null; } @@ -527,7 +520,7 @@ internal static Type ResolveType(string strTypeName, out Exception exception) internal class TypeResolutionState { internal static readonly string[] systemNamespace = { "System" }; - internal static readonly Assembly[] emptyAssemblies = Utils.EmptyArray(); + internal static readonly Assembly[] emptyAssemblies = Array.Empty(); internal static readonly TypeResolutionState UsingSystem = new TypeResolutionState(); internal readonly string[] namespaces; @@ -590,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) { @@ -614,6 +604,7 @@ internal string GetAlternateTypeName(string typeName) { alternateName = typeName + "Attribute"; } + return alternateName; } @@ -622,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) @@ -665,21 +654,24 @@ public override int GetHashCode() { result = Utils.CombineHashCodes(result, stringComparer.GetHashCode(namespaces[i])); } + for (int i = 0; i < assemblies.Length; i++) { result = Utils.CombineHashCodes(result, this.assemblies[i].GetHashCode()); } + foreach (var t in _typesDefined) { result = Utils.CombineHashCodes(result, t.GetHashCode()); } + return result; } } - internal class TypeCache + internal static class TypeCache { - private class KeyComparer : IEqualityComparer> + private sealed class KeyComparer : IEqualityComparer> { public bool Equals(Tuple x, Tuple y) @@ -727,7 +719,7 @@ internal static class CoreTypes // expose the ability to corrupt or escape PowerShell's environment. The following operations must // be safe: type conversion, all constructors, all methods (instance and static), and // and properties (instance and static). - internal static Lazy> Items = new Lazy>( + internal static readonly Lazy> Items = new Lazy>( () => new Dictionary { @@ -745,12 +737,15 @@ internal static class CoreTypes { typeof(DateTime), new[] { "datetime" } }, { typeof(decimal), new[] { "decimal" } }, { typeof(double), new[] { "double" } }, - { typeof(DscResourceAttribute), new[] { "DscResource"} }, + { typeof(DscResourceAttribute), new[] { "DscResource" } }, + { typeof(ExperimentAction), new[] { "ExperimentAction" } }, + { typeof(ExperimentalAttribute), new[] { "Experimental" } }, + { typeof(ExperimentalFeature), new[] { "ExperimentalFeature" } }, { typeof(float), new[] { "float", "single" } }, { typeof(Guid), new[] { "guid" } }, { typeof(Hashtable), new[] { "hashtable" } }, { typeof(int), new[] { "int", "int32" } }, - { typeof(Int16), new[] { "int16" } }, + { typeof(short), new[] { "short", "int16" } }, { typeof(long), new[] { "long", "int64" } }, { typeof(CimInstance), new[] { "ciminstance" } }, { typeof(CimClass), new[] { "cimclass" } }, @@ -758,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(object[]), null }, { typeof(ObjectSecurity), new[] { "ObjectSecurity" } }, + { typeof(OrderedDictionary), new[] { "ordered" } }, { typeof(ParameterAttribute), new[] { "Parameter" } }, { typeof(PhysicalAddress), new[] { "PhysicalAddress" } }, { typeof(PSCredential), new[] { "pscredential" } }, @@ -773,7 +770,7 @@ internal static class CoreTypes { typeof(PSTypeNameAttribute), new[] { "PSTypeNameAttribute" } }, { typeof(Regex), new[] { "regex" } }, { typeof(DscPropertyAttribute), new[] { "DscProperty" } }, - { typeof(SByte), new[] { "sbyte" } }, + { typeof(sbyte), new[] { "sbyte" } }, { typeof(string), new[] { "string" } }, { typeof(SupportsWildcardsAttribute), new[] { "SupportsWildcards" } }, { typeof(SwitchParameter), new[] { "switch" } }, @@ -781,19 +778,21 @@ internal static class CoreTypes { typeof(BigInteger), new[] { "bigint" } }, { typeof(SecureString), new[] { "securestring" } }, { typeof(TimeSpan), new[] { "timespan" } }, - { typeof(UInt16), new[] { "uint16" } }, - { typeof(UInt32), new[] { "uint32" } }, - { typeof(UInt64), new[] { "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" } }, { typeof(ValidateSetAttribute), new[] { "ValidateSet" } }, + { typeof(ValidateTrustedDataAttribute), new[] { "ValidateTrustedData" } }, { typeof(ValidateUserDriveAttribute), new[] { "ValidateUserDrive"} }, { typeof(Version), new[] { "version" } }, { typeof(void), new[] { "void" } }, @@ -806,8 +805,7 @@ internal static class CoreTypes { typeof(CimSession), new[] { "CimSession" } }, { typeof(MailAddress), new[] { "mailaddress" } }, { typeof(SemanticVersion), new[] { "semver" } }, -#if !CORECLR - // Following types not in CoreCLR +#if !UNIX { typeof(DirectoryEntry), new[] { "adsi" } }, { typeof(DirectorySearcher), new[] { "adsisearcher" } }, { typeof(ManagementClass), new[] { "wmiclass" } }, @@ -823,16 +821,18 @@ internal static bool Contains(Type inputType) { return true; } - var inputTypeInfo = inputType.GetTypeInfo(); - if (inputTypeInfo.IsEnum) + + if (inputType.IsEnum) { return true; } - if (inputTypeInfo.IsGenericType) + + if (inputType.IsGenericType) { - var genericTypeDefinition = inputTypeInfo.GetGenericTypeDefinition(); + var genericTypeDefinition = inputType.GetGenericTypeDefinition(); return genericTypeDefinition == typeof(Nullable<>) || genericTypeDefinition == typeof(FlagsExpression<>); } + return (inputType.IsArray && Contains(inputType.GetElementType())); } } @@ -844,11 +844,11 @@ internal static bool Contains(Type inputType) internal static class TypeAccelerators { // builtins are not exposed publicly in a direct manner so they can't be changed at all - internal static Dictionary builtinTypeAccelerators = new Dictionary(64, StringComparer.OrdinalIgnoreCase); + internal static readonly Dictionary builtinTypeAccelerators = new Dictionary(64, StringComparer.OrdinalIgnoreCase); // users can add to user added accelerators (but not currently remove any.) Keeping a separate // list allows us to add removing in the future w/o worrying about breaking the builtins. - internal static Dictionary userTypeAccelerators = new Dictionary(64, StringComparer.OrdinalIgnoreCase); + internal static readonly Dictionary userTypeAccelerators = new Dictionary(64, StringComparer.OrdinalIgnoreCase); // We expose this one publicly for programmatic access to our type accelerator table, but it is // otherwise unused (so changes to this dictionary don't affect internals.) @@ -871,6 +871,7 @@ static TypeAccelerators() // Add additional utility types that are useful as type accelerators, but aren't // fundamentally "core language", or may be unsafe to expose to untrusted input. builtinTypeAccelerators.Add("scriptblock", typeof(ScriptBlock)); + builtinTypeAccelerators.Add("pspropertyexpression", typeof(PSPropertyExpression)); builtinTypeAccelerators.Add("psvariable", typeof(PSVariable)); builtinTypeAccelerators.Add("type", typeof(Type)); builtinTypeAccelerators.Add("psmoduleinfo", typeof(PSModuleInfo)); @@ -889,7 +890,7 @@ internal static string FindBuiltinAccelerator(Type type, string expectedKey = nu { // Taking attributes as special case. In this case, we only want to return the // accelerator. - if (null == expectedKey || typeof(Attribute).IsAssignableFrom(type)) + if (expectedKey == null || typeof(Attribute).IsAssignableFrom(type)) { foreach (KeyValuePair entry in builtinTypeAccelerators) { @@ -903,11 +904,12 @@ internal static string FindBuiltinAccelerator(Type type, string expectedKey = nu { Type resultType = null; builtinTypeAccelerators.TryGetValue(expectedKey, out resultType); - if (null != resultType && resultType == type) + if (resultType != null && resultType == type) { return expectedKey; } } + return null; } /// @@ -932,10 +934,8 @@ 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; } @@ -970,6 +970,7 @@ internal static void FillCache(Dictionary cache) { cache.Add(val.Key, val.Value); } + foreach (KeyValuePair val in userTypeAccelerators) { cache.Add(val.Key, val.Value); diff --git a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs index f89c3b83575..8bf14d79aac 100644 --- a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs +++ b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs @@ -1,13 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Concurrent; using System.Linq; -using System.Reflection; namespace System.Management.Automation.Language { @@ -27,18 +25,26 @@ 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); + private static readonly string[] s_pessimizingCmdlets = new string[] { "New-Variable", @@ -103,11 +109,13 @@ internal static Dictionary Visit(IParameterMeta { visitor.VisitParameters(ast.Parameters); } - localsAllocated = visitor._variables.Where(details => details.Value.LocalTupleIndex != VariableAnalysis.Unanalyzed).Count(); + + localsAllocated = visitor._variables.Count(static details => details.Value.LocalTupleIndex != VariableAnalysis.Unanalyzed); return visitor._variables; } private bool _disableOptimizations; + private readonly Dictionary _variables = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -176,8 +184,8 @@ private void VisitParameters(ReadOnlyCollection parameters) // comparisons with $null and don't try to convert the $null value to the // 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; } @@ -190,6 +198,25 @@ private void VisitParameters(ReadOnlyCollection parameters) } } + // Add a variable to the variable dictionary + private void NoteVariable(string variableName, int index, Type type, bool automatic = false, bool preferenceVariable = false) + { + if (!_variables.ContainsKey(variableName)) + { + var details = new VariableAnalysisDetails + { + BitIndex = _variables.Count, + LocalTupleIndex = index, + Name = variableName, + Type = type, + Automatic = automatic, + PreferenceVariable = preferenceVariable, + Assigned = false, + }; + _variables.Add(variableName, details); + } + } + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) { if (dataStatementAst.Variable != null) @@ -224,6 +251,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var // TODO: force just this variable to be dynamic, not all variables. _disableOptimizations = true; } + NoteVariable(VariableAnalysis.GetUnaliasedVariableName(varPath), VariableAnalysis.Unanalyzed, null); } @@ -231,6 +259,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var } private int _runtimeUsingIndex; + public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) { // On the local machine, we may have set the index because of a call to ScriptBlockToPowerShell or Invoke-Command. @@ -240,6 +269,7 @@ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpr { usingExpressionAst.RuntimeUsingIndex = _runtimeUsingIndex; } + Diagnostics.Assert(usingExpressionAst.RuntimeUsingIndex == _runtimeUsingIndex, "Logic error in visiting using expressions."); _runtimeUsingIndex += 1; @@ -290,28 +320,9 @@ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) // We don't want to discover any variables in traps - they get their own scope. return AstVisitAction.SkipChildren; } - - // Return true if the variable is newly allocated and should be allocated in the locals tuple. - private void NoteVariable(string variableName, int index, Type type, bool automatic = false, bool preferenceVariable = false) - { - if (!_variables.ContainsKey(variableName)) - { - var details = new VariableAnalysisDetails - { - BitIndex = _variables.Count, - LocalTupleIndex = index, - Name = variableName, - Type = type, - Automatic = automatic, - PreferenceVariable = preferenceVariable, - Assigned = false, - }; - _variables.Add(variableName, details); - } - } } - internal class VariableAnalysis : ICustomAstVisitor + internal class VariableAnalysis : ICustomAstVisitor2 { // Tuple slots start at index 0. >= 0 means a variable is allocated in the tuple. -1 means we haven't // analyzed a specific use of a variable and don't know what slot it might be assigned to yet. @@ -326,7 +337,7 @@ internal class VariableAnalysis : ICustomAstVisitor // 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) { @@ -334,12 +345,15 @@ internal LoopGotoTargets(string label, Block breakTarget, Block continueTarget) this.BreakTarget = breakTarget; this.ContinueTarget = continueTarget; } - internal string Label { get; private set; } - internal Block BreakTarget { get; private set; } - internal Block ContinueTarget { get; private set; } + + internal string Label { get; } + + internal Block BreakTarget { get; } + + internal Block ContinueTarget { get; } } - private class Block + private sealed class Block { internal readonly List _asts = new List(); private readonly List _successors = new List(); @@ -348,6 +362,7 @@ private class Block internal object _visitData; internal bool _throws; internal bool _returns; + internal bool _unreachable { get; private set; } // Only Entry block, that can be constructed via NewEntryBlock() is reachable initially. @@ -380,6 +395,7 @@ internal void FlowsTo(Block next) { next._unreachable = false; } + _successors.Add(next); next._predecessors.Add(this); } @@ -422,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; @@ -452,6 +468,7 @@ internal override object Accept(ICustomAstVisitor visitor) Diagnostics.Assert(false, "This code is unreachable."); return null; } + internal override AstVisitAction InternalVisit(AstVisitor visitor) { Diagnostics.Assert(false, "This code is unreachable."); @@ -484,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; @@ -505,7 +522,7 @@ private Tuple> AnalyzeImpl(ExpressionAst exprAst) { _variables = FindAllVariablesVisitor.Visit(exprAst); - // We disable optimizations for trap because it simplifies what we need to do when invoking + // We disable optimizations for expression because it simplifies what we need to do when invoking // the default argument, and it's assumed that the code inside a default argument rarely, if ever, actually creates // any local variables. _disableOptimizations = true; @@ -563,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) @@ -615,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. @@ -666,6 +683,7 @@ private Tuple> FinishAnalysis(bool scriptCmdlet = bitArray.And((BitArray)pred._visitData); } } + Diagnostics.Assert(predCount != 0, "If we didn't and anything, there is a flaw in the logic and incorrect code may be generated."); AnalyzeBlock(bitArray, block); @@ -732,7 +750,7 @@ private static bool MustBeBoxed(Type type) // $value.Property = 42 // We make sure we never allocate an instance of such mutable types in the MutableType. - return (type.GetTypeInfo().IsValueType && PSVariableAssignmentBinder.IsValueTypeMutable(type)) && typeof(SwitchParameter) != type; + return (type.IsValueType && PSVariableAssignmentBinder.IsValueTypeMutable(type)) && typeof(SwitchParameter) != type; } private static void FixTupleIndex(Ast ast, int newIndex) @@ -791,6 +809,7 @@ private void AnalyzeBlock(BitArray assignedBitArray, Block block) : VariableAnalysis.ForceDynamic; } } + continue; } @@ -805,6 +824,7 @@ private void AnalyzeBlock(BitArray assignedBitArray, Block block) { CheckLHSAssignVar(assignmentTarget._variableName, assignedBitArray, assignmentTarget._type); } + continue; } @@ -924,23 +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); @@ -1047,6 +1055,28 @@ public object VisitIfStatement(IfStatementAst ifStmtAst) return null; } + public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + var ifTrue = new Block(); + var ifFalse = new Block(); + var after = new Block(); + + ternaryExpressionAst.Condition.Accept(this); + _currentBlock.FlowsTo(ifTrue); + _currentBlock.FlowsTo(ifFalse); + _currentBlock = ifTrue; + + ternaryExpressionAst.IfTrue.Accept(this); + _currentBlock.FlowsTo(after); + _currentBlock = ifFalse; + + ternaryExpressionAst.IfFalse.Accept(this); + _currentBlock.FlowsTo(after); + _currentBlock = after; + + return null; + } + public object VisitTrap(TrapStatementAst trapStatementAst) { trapStatementAst.Body.Accept(this); @@ -1120,6 +1150,7 @@ public object VisitDataStatement(DataStatementAst dataStatementAst) { _currentBlock.AddAst(dataStatementAst); } + return null; } @@ -1167,13 +1198,14 @@ private void GenerateWhileLoop(string loopLabel, var breakBlock = new Block(); // Condition can be null from an uncommon for loop: for() {} + if (generateCondition != null) { generateCondition(); _currentBlock.FlowsTo(breakBlock); } - _loopTargets.Add(new LoopGotoTargets(loopLabel ?? "", breakBlock, continueBlock)); + _loopTargets.Add(new LoopGotoTargets(loopLabel ?? string.Empty, breakBlock, continueBlock)); _currentBlock.FlowsTo(bodyBlock); _currentBlock = bodyBlock; generateLoopBody(); @@ -1203,7 +1235,7 @@ private void GenerateDoLoop(LoopStatementAst loopStatement) var breakBlock = new Block(); var gotoRepeatTargetBlock = new Block(); - _loopTargets.Add(new LoopGotoTargets(loopStatement.Label ?? "", breakBlock, continueBlock)); + _loopTargets.Add(new LoopGotoTargets(loopStatement.Label ?? string.Empty, breakBlock, continueBlock)); _currentBlock.FlowsTo(bodyBlock); _currentBlock = bodyBlock; @@ -1270,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) @@ -1376,7 +1405,7 @@ private void BreakOrContinue(ExpressionAst label, Func f if (label != null) { label.Accept(this); - if (_loopTargets.Any()) + if (_loopTargets.Count > 0) { var labelStrAst = label as StringConstantExpressionAst; if (labelStrAst != null) @@ -1409,22 +1438,20 @@ 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; @@ -1498,6 +1525,7 @@ public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatemen { anyAttributes = true; } + leftAst = ((AttributedExpressionAst)leftAst).Child; } @@ -1532,6 +1560,7 @@ public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatemen assignTarget.Accept(this); } } + return null; } @@ -1545,6 +1574,7 @@ public object VisitPipeline(PipelineAst pipelineAst) { invokesCommand = true; } + foreach (var redir in command.Redirections) { redir.Accept(this); @@ -1556,7 +1586,7 @@ public object VisitPipeline(PipelineAst pipelineAst) // break or continue, so add the appropriate edges to our graph. These edges occur after visiting // the command elements because command arguments could create new blocks, and we won't have executed // the command yet. - if (invokesCommand && _loopTargets.Any()) + if (invokesCommand && _loopTargets.Count > 0) { foreach (var loopTarget in _loopTargets) { @@ -1579,6 +1609,7 @@ public object VisitCommand(CommandAst commandAst) { element.Accept(this); } + return null; } @@ -1590,10 +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; } @@ -1618,7 +1646,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) // The right operand is conditionally evaluated. We aren't generating any code here, just // modeling the flow graph, so we just visit the right operand in a new block, and have - // both the current and new blocks both flow to a post-expression block. + // both the current and new blocks flow to a post-expression block. var targetBlock = new Block(); var nextBlock = new Block(); _currentBlock.FlowsTo(targetBlock); @@ -1729,6 +1757,7 @@ public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMember arg.Accept(this); } } + return null; } @@ -1744,6 +1773,7 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) { element.Accept(this); } + return null; } @@ -1754,6 +1784,7 @@ public object VisitHashtable(HashtableAst hashtableAst) pair.Item1.Accept(this); pair.Item2.Accept(this); } + return null; } @@ -1777,6 +1808,7 @@ public object VisitExpandableStringExpression(ExpandableStringExpressionAst expa { expr.Accept(this); } + return null; } @@ -1798,5 +1830,19 @@ public object VisitBlockStatement(BlockStatementAst blockStatementAst) blockStatementAst.Body.Accept(this); return null; } + + public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => null; + + public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) => null; + + public object VisitFunctionMember(FunctionMemberAst functionMemberAst) => null; + + public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => null; + + public object VisitUsingStatement(UsingStatementAst usingStatement) => null; + + public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => null; + + public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) => null; } } diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 36f660c0fef..ba5c48a2752 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // // This file contains all of the publicly visible parts of the PowerShell abstract syntax tree. @@ -30,6 +29,8 @@ namespace System.Management.Automation.Language using System.Runtime.CompilerServices; using System.Reflection.Emit; +#nullable enable + internal interface ISupportsAssignment { IAssignableValue GetAssignableValue(); @@ -42,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 @@ -50,21 +51,29 @@ 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(); + ReadOnlyCollection Parameters { get; } + ScriptBlockAst Body { get; } #region Remoting/Invoke Command PowerShell GetPowerShell(ExecutionContext context, Dictionary variables, bool isTrustedInput, bool filterNonUsingVariables, bool? createLocalScope, params object[] args); + string GetWithInputHandlingForInvokeCommand(); /// @@ -92,7 +101,7 @@ protected Ast(IScriptExtent extent) { if (extent == null) { - throw PSTraceSource.NewArgumentNullException("extent"); + throw PSTraceSource.NewArgumentNullException(nameof(extent)); } this.Extent = extent; @@ -101,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. @@ -112,13 +121,13 @@ protected Ast(IScriptExtent extent) /// Visit the Ast using a visitor that can choose how the tree traversal is performed. This visit method is /// for advanced uses of the visitor pattern where an is insufficient. /// - /// The visitor + /// The visitor. /// Returns the value returned by the visitor. public object Visit(ICustomAstVisitor astVisitor) { if (astVisitor == null) { - throw PSTraceSource.NewArgumentNullException("astVisitor"); + throw PSTraceSource.NewArgumentNullException(nameof(astVisitor)); } return this.Accept(astVisitor); @@ -127,12 +136,12 @@ public object Visit(ICustomAstVisitor astVisitor) /// /// Visit each node in the Ast, calling the methods in for each node in the ast. /// - /// The visitor + /// The visitor. public void Visit(AstVisitor astVisitor) { if (astVisitor == null) { - throw PSTraceSource.NewArgumentNullException("astVisitor"); + throw PSTraceSource.NewArgumentNullException(nameof(astVisitor)); } this.InternalVisit(astVisitor); @@ -148,7 +157,7 @@ public IEnumerable FindAll(Func predicate, bool searchNestedScri { if (predicate == null) { - throw PSTraceSource.NewArgumentNullException("predicate"); + throw PSTraceSource.NewArgumentNullException(nameof(predicate)); } return AstSearcher.FindAll(this, predicate, searchNestedScriptBlocks); @@ -157,14 +166,14 @@ public IEnumerable FindAll(Func predicate, bool searchNestedScri /// /// Traverse the entire Ast, returning the first node in the tree for which returns true. /// - /// The predicate + /// The predicate. /// Search nested functions and script block expressions. /// The first matching node, or null if there is no match. public Ast Find(Func predicate, bool searchNestedScriptBlocks) { if (predicate == null) { - throw PSTraceSource.NewArgumentNullException("predicate"); + throw PSTraceSource.NewArgumentNullException(nameof(predicate)); } return AstSearcher.FindFirst(this, predicate, searchNestedScriptBlocks); @@ -187,11 +196,24 @@ public override string ToString() /// /// Constructs the resultant object from the AST and returns it if it is safe. /// - /// The object represented by the AST as a safe object + /// The object represented by the AST as a safe object. /// /// 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 { @@ -201,40 +223,43 @@ 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 { - throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AutomationExceptions.CantConvertWithDynamicExpression, this.Extent.Text)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, AutomationExceptions.CantConvertWithDynamicExpression, this.Extent.Text)); } } /// - /// Copy a collection of AST elements + /// Copy a collection of AST elements. /// /// The actual AST type - /// Collection of ASTs + /// Collection of ASTs. /// internal static T[] CopyElements(ReadOnlyCollection elements) where T : Ast { if (elements == null || elements.Count == 0) { return null; } + var result = new T[elements.Count]; - for (int i = 0; i < result.Count(); i++) + for (int i = 0; i < result.Length; i++) { result[i] = (T)elements[i].Copy(); } + return result; } /// - /// Copy a single AST element + /// Copy a single AST element. /// /// The actual AST type - /// An AST instance + /// An AST instance. /// internal static T CopyElement(T element) where T : Ast { if (element == null) { return null; } + return (T)element.Copy(); } @@ -269,6 +294,7 @@ internal void SetParent(Ast child) { throw new InvalidOperationException(ParserStrings.AstIsReused); } + Diagnostics.Assert(child.Parent == null, "Parent can only be set once"); child.Parent = this; } @@ -279,9 +305,10 @@ internal void ClearParent() } internal abstract object Accept(ICustomAstVisitor visitor); + internal abstract AstVisitAction InternalVisit(AstVisitor visitor); - internal static PSTypeName[] EmptyPSTypeNameArray = Utils.EmptyArray(); + internal static readonly PSTypeName[] EmptyPSTypeNameArray = Array.Empty(); internal bool IsInWorkflow() { @@ -293,21 +320,21 @@ 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 && - String.Equals(TokenKind.InlineScript.Text(), commandAst.GetCommandName(), StringComparison.OrdinalIgnoreCase) && + if (current is CommandAst commandAst && + string.Equals(TokenKind.InlineScript.Text(), commandAst.GetCommandName(), StringComparison.OrdinalIgnoreCase) && this != commandAst) { return false; @@ -345,6 +372,7 @@ internal static HashtableAst GetAncestorHashtableAst(Ast ast, out Ast lastChildO lastChildOfHashtable = ast; ast = ast.Parent; } + return hashtableAst; } @@ -360,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; } @@ -370,7 +397,7 @@ internal static TypeDefinitionAst GetAncestorTypeDefinitionAst(Ast ast) } /// - /// Get ancestor Ast of the given type of the given ast + /// Get ancestor Ast of the given type of the given ast. /// /// /// @@ -385,6 +412,7 @@ internal static T GetAncestorAst(Ast ast) where T : Ast break; parent = parent.Parent; } + return targetAst; } @@ -401,7 +429,7 @@ public SequencePointAst(IScriptExtent extent) } /// - /// Copy the SequencePointAst instance + /// Copy the SequencePointAst instance. /// public override Ast Copy() { @@ -442,7 +470,7 @@ internal ErrorStatementAst(IScriptExtent extent, Token kind, IEnumerable ne { if (kind == null) { - throw PSTraceSource.NewArgumentNullException("kind"); + throw PSTraceSource.NewArgumentNullException(nameof(kind)); } Kind = kind; @@ -458,7 +486,7 @@ internal ErrorStatementAst(IScriptExtent extent, Token kind, IEnumerable - /// Indicate the kind of the ErrorStatement. e.g. Kind == Switch means that this error statment is generated + /// Indicate the kind of the ErrorStatement. e.g. Kind == Switch means that this error statement is generated /// when parsing a switch statement. /// - public Token Kind { get; private set; } + public Token Kind { get; } /// /// The flags specified and their value. The value is null if it's not specified. @@ -505,27 +533,27 @@ 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 + /// Copy the ErrorStatementAst instance. /// public override Ast Copy() { @@ -612,6 +640,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -638,10 +667,10 @@ 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 + /// Copy the ErrorExpressionAst instance. /// public override Ast Copy() { @@ -680,16 +709,18 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) #region Script Blocks /// - /// /// public class ScriptRequirements { internal static readonly ReadOnlyCollection EmptySnapinCollection = Utils.EmptyReadOnlyCollection(); + internal static readonly ReadOnlyCollection EmptyAssemblyCollection = Utils.EmptyReadOnlyCollection(); + internal static readonly ReadOnlyCollection EmptyModuleCollection = Utils.EmptyReadOnlyCollection(); + internal static readonly ReadOnlyCollection EmptyEditionCollection = Utils.EmptyReadOnlyCollection(); @@ -722,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 @@ -753,18 +776,21 @@ public class ScriptBlockAst : Ast, IParameterMetadataProvider { private static readonly ReadOnlyCollection s_emptyAttributeList = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyUsingStatementList = Utils.EmptyReadOnlyCollection(); internal bool HadErrors { get; set; } + internal bool IsConfiguration { get; private set; } + internal bool PostParseChecksPerformed { get; set; } /// /// 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. @@ -783,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); @@ -802,21 +868,31 @@ public ScriptBlockAst(IScriptExtent extent, this.ParamBlock = paramBlock; SetParent(paramBlock); } + if (beginBlock != null) { this.BeginBlock = beginBlock; SetParent(beginBlock); } + if (processBlock != null) { this.ProcessBlock = processBlock; SetParent(processBlock); } + if (endBlock != null) { this.EndBlock = endBlock; SetParent(endBlock); } + + if (cleanBlock != null) + { + this.CleanBlock = cleanBlock; + SetParent(cleanBlock); + } + if (dynamicParamBlock != null) { this.DynamicParamBlock = dynamicParamBlock; @@ -828,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. @@ -849,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. /// @@ -872,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 @@ -921,7 +1053,7 @@ public ScriptBlockAst(IScriptExtent extent, ParamBlockAst paramBlock, StatementB /// process block if is true. /// /// True if the script block is a filter, false if it is a function or workflow. - /// True if the script block is a configuration + /// True if the script block is a configuration. /// /// If or is null. /// @@ -935,14 +1067,14 @@ 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 /// process block if is true. /// /// True if the script block is a filter, false if it is a function or workflow. - /// True if the script block is a configuration + /// True if the script block is a configuration. /// /// If or is null. /// @@ -963,7 +1095,7 @@ public ScriptBlockAst(IScriptExtent extent, IEnumerable using /// process block if is true. /// /// True if the script block is a filter, false if it is a function or workflow. - /// True if the script block is a configuration + /// True if the script block is a configuration. /// /// If or is null. /// @@ -977,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. /// @@ -985,7 +1117,7 @@ public ScriptBlockAst(IScriptExtent extent, IEnumerable attributes /// process block if is true. /// /// True if the script block is a filter, false if it is a function or workflow. - /// True if the script block is a configuration + /// True if the script block is a configuration. /// /// If or is null. /// @@ -1007,7 +1139,7 @@ public ScriptBlockAst(IScriptExtent extent, IEnumerable using if (statements == null) { - throw PSTraceSource.NewArgumentNullException("statements"); + throw PSTraceSource.NewArgumentNullException(nameof(statements)); } if (paramBlock != null) @@ -1015,6 +1147,7 @@ public ScriptBlockAst(IScriptExtent extent, IEnumerable using this.ParamBlock = paramBlock; SetParent(paramBlock); } + if (isFilter) { this.ProcessBlock = new NamedBlockAst(statements.Extent, TokenKind.Process, statements, true); @@ -1045,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. @@ -1058,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. @@ -1098,6 +1236,7 @@ public CommentHelpInfo GetHelpContent() { return HelpCommentsParser.GetHelpContents(commentTokens.Item1, commentTokens.Item2); } + return null; } @@ -1118,7 +1257,7 @@ public ScriptBlock GetScriptBlock() // It's ok to report an error from a different part of AST in this case. var root = GetRootScriptBlockAst(); root.PerformPostParseChecks(parser); - if (parser.ErrorList.Any()) + if (parser.ErrorList.Count > 0) { throw new ParseException(parser.ErrorList.ToArray()); } @@ -1145,7 +1284,7 @@ private ScriptBlockAst GetRootScriptBlockAst() } /// - /// Copy the ScriptBlockAst instance + /// Copy the ScriptBlockAst instance. /// public override Ast Copy() { @@ -1153,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() @@ -1177,6 +1324,7 @@ internal string ToStringForSerialization() "There is an incorrect assumption about the extent."); result = result.Substring(1, result.Length - 2); } + return result; } @@ -1206,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; @@ -1216,14 +1364,14 @@ 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; - newScript.Append(script.Substring(startOffset, astStartOffset - startOffset)); + newScript.Append(script.AsSpan(startOffset, astStartOffset - startOffset)); newScript.Append(newVarName); startOffset = astEndOffset; } @@ -1244,13 +1392,13 @@ internal string ToStringForSerialization(Tuple, stri newParams += ",\n"; } - newScript.Append(script.Substring(startOffset, currentOffset - startOffset)); + newScript.Append(script.AsSpan(startOffset, currentOffset - startOffset)); newScript.Append(newParams); startOffset = currentOffset; } } - newScript.Append(script.Substring(startOffset, endOffset - startOffset)); + newScript.Append(script.AsSpan(startOffset, endOffset - startOffset)); string result = newScript.ToString(); if (Parent != null && initialStartOffset == this.Extent.StartOffset && initialEndOffset == this.Extent.EndOffset) @@ -1276,6 +1424,7 @@ internal void PerformPostParseChecks(Parser parser) ParserEventSource.Log.ResolveSymbolsStop(); ParserEventSource.Log.SemanticChecksStart(); } + SemanticChecks.CheckAst(parser, this); if (etwEnabled) ParserEventSource.Log.SemanticChecksStop(); @@ -1295,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) { @@ -1322,17 +1470,28 @@ 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 @@ -1350,6 +1509,7 @@ RuntimeDefinedParameterDictionary IParameterMetadataProvider.GetParameterMetadat { return Compiler.GetParameterMetaData(ParamBlock.Parameters, automaticPositions, ref usesCmdletBinding); } + return new RuntimeDefinedParameterDictionary { Data = RuntimeDefinedParameterDictionary.EmptyParameterArray }; } @@ -1371,6 +1531,64 @@ IEnumerable IParameterMetadataProvider.GetScriptBlockAttributes() } } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + for (int index = 0; index < Attributes.Count; index++) + { + AttributeAst attributeAst = Attributes[index]; + ExperimentalAttribute expAttr = GetExpAttributeHelper(attributeAst); + if (expAttr != null) { yield return expAttr; } + } + + if (ParamBlock != null) + { + for (int index = 0; index < ParamBlock.Attributes.Count; index++) + { + var attributeAst = ParamBlock.Attributes[index]; + var expAttr = GetExpAttributeHelper(attributeAst); + if (expAttr != null) { yield return expAttr; } + } + } + + static ExperimentalAttribute GetExpAttributeHelper(AttributeAst attributeAst) + { + AttributeAst potentialExpAttr = null; + string expAttrTypeName = typeof(ExperimentalAttribute).FullName; + string attrAstTypeName = attributeAst.TypeName.Name; + + if (TypeAccelerators.Get.TryGetValue(attrAstTypeName, out Type attrType) && attrType == typeof(ExperimentalAttribute)) + { + potentialExpAttr = attributeAst; + } + else if (expAttrTypeName.EndsWith(attrAstTypeName, StringComparison.OrdinalIgnoreCase)) + { + // Handle two cases: + // 1. declare the attribute using full type name; + // 2. declare the attribute using partial type name due to 'using namespace'. + int expAttrLength = expAttrTypeName.Length; + int attrAstLength = attrAstTypeName.Length; + if (expAttrLength == attrAstLength || expAttrTypeName[expAttrLength - attrAstLength - 1] == '.') + { + potentialExpAttr = attributeAst; + } + } + + if (potentialExpAttr != null) + { + try + { + return Compiler.GetAttribute(potentialExpAttr) as ExperimentalAttribute; + } + catch (Exception) + { + // catch all and assume it's not a declaration of ExperimentalAttribute + } + } + + return null; + } + } + ReadOnlyCollection IParameterMetadataProvider.Parameters { get { return (ParamBlock != null) ? this.ParamBlock.Parameters : null; } @@ -1410,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) @@ -1445,6 +1661,7 @@ private string GetWithInputHandlingForInvokeCommandImpl(Tuple typeof(CmdletBindingAttribute) == attribute.TypeName.GetReflectionAttributeType()); + usesCmdletBinding = this.ParamBlock.Attributes.Any(static attribute => typeof(CmdletBindingAttribute) == attribute.TypeName.GetReflectionAttributeType()); if (!usesCmdletBinding) { usesCmdletBinding = ParamBlockAst.UsesCmdletBinding(ParamBlock.Parameters); @@ -1476,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; } @@ -1490,7 +1710,7 @@ internal PipelineAst GetSimplePipeline(bool allowMultiplePipelines, out string e return null; } - if (EndBlock.Traps != null && EndBlock.Traps.Any()) + if (EndBlock.Traps != null && EndBlock.Traps.Count > 0) { errorId = "CantConvertScriptBlockWithTrap"; errorMsg = AutomationExceptions.CantConvertScriptBlockWithTrap; @@ -1498,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; @@ -1526,6 +1746,7 @@ public class ParamBlockAst : Ast { private static readonly ReadOnlyCollection s_emptyAttributeList = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyParameterList = Utils.EmptyReadOnlyCollection(); @@ -1565,15 +1786,15 @@ 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 + /// Copy the ParamBlockAst instance. /// public override Ast Copy() { @@ -1603,6 +1824,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue) { for (int index = 0; index < Parameters.Count; index++) @@ -1612,6 +1834,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -1629,6 +1852,7 @@ internal static bool UsesCmdletBinding(IEnumerable parameters) break; } } + return usesCmdletBinding; } } @@ -1640,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 @@ -1652,6 +1876,7 @@ public class NamedBlockAst : Ast /// /// /// + /// /// /// /// @@ -1662,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) @@ -1670,15 +1895,14 @@ 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("blockName"); + throw PSTraceSource.NewArgumentException(nameof(blockName)); } if (statementBlock == null) { - throw PSTraceSource.NewArgumentNullException("statementBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(statementBlock)); } this.Unnamed = unnamed; @@ -1690,10 +1914,11 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs var stmt = statements[index]; stmt.ClearParent(); } + SetParents(statements); var traps = statementBlock.Traps; - if (traps != null && traps.Any()) + if (traps != null && traps.Count > 0) { this.Traps = traps; for (int index = 0; index < traps.Count; index++) @@ -1701,13 +1926,13 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs var trap = traps[index]; trap.ClearParent(); } + SetParents(traps); } 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); @@ -1719,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: @@ -1727,24 +1952,25 @@ 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 + /// Copy the NamedBlockAst instance. /// public override Ast Copy() { @@ -1766,9 +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 CloseCurlyExtent { get; private set; } + internal IScriptExtent OpenCurlyExtent { get; } + + internal IScriptExtent CloseCurlyExtent { get; } #region Visitors @@ -1818,12 +2053,12 @@ public NamedAttributeArgumentAst(IScriptExtent extent, string argumentName, Expr { if (string.IsNullOrEmpty(argumentName)) { - throw PSTraceSource.NewArgumentNullException("argumentName"); + throw PSTraceSource.NewArgumentNullException(nameof(argumentName)); } if (argument == null) { - throw PSTraceSource.NewArgumentNullException("argument"); + throw PSTraceSource.NewArgumentNullException(nameof(argument)); } this.Argument = argument; @@ -1835,21 +2070,21 @@ 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 + /// Copy the NamedAttributeArgumentAst instance. /// public override Ast Copy() { @@ -1896,7 +2131,7 @@ protected AttributeBaseAst(IScriptExtent extent, ITypeName typeName) { if (typeName == null) { - throw PSTraceSource.NewArgumentNullException("typeName"); + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); } this.TypeName = typeName; @@ -1905,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(); } @@ -1917,6 +2152,7 @@ public class AttributeAst : AttributeBaseAst { private static readonly ReadOnlyCollection s_emptyPositionalArguments = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyNamedAttributeArguments = Utils.EmptyReadOnlyCollection(); @@ -1960,15 +2196,15 @@ 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 + /// Copy the AttributeAst instance. /// public override Ast Copy() { @@ -1998,6 +2234,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue) { for (int index = 0; index < NamedArguments.Count; index++) @@ -2007,6 +2244,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -2050,7 +2288,7 @@ public TypeConstraintAst(IScriptExtent extent, Type type) } /// - /// Copy the TypeConstraintAst instance + /// Copy the TypeConstraintAst instance. /// public override Ast Copy() { @@ -2105,7 +2343,7 @@ public ParameterAst(IScriptExtent extent, { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (attributes != null) @@ -2117,6 +2355,7 @@ public ParameterAst(IScriptExtent extent, { this.Attributes = s_emptyAttributeList; } + this.Name = name; SetParent(name); if (defaultValue != null) @@ -2129,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, @@ -2155,12 +2394,13 @@ public Type StaticType { type = typeConstraint.TypeName.GetReflectionType(); } + return type ?? typeof(object); } } /// - /// Copy the ParameterAst instance + /// Copy the ParameterAst instance. /// public override Ast Copy() { @@ -2185,7 +2425,7 @@ internal string GetTooltip() /// This method is used when we call Invoke-Command targeting a PSv2 remote machine. In that case, we might need to call this method /// to process the script block text, since $using prefix cannot be recognized by PSv2. /// - /// A sorted enumerator of using variable asts, ascendingly sorted based on StartOffSet + /// A sorted enumerator of using variable asts, ascendingly sorted based on StartOffSet. /// /// The text of the ParameterAst with $using variable being replaced with a new variable name. /// @@ -2213,11 +2453,12 @@ 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; - newParamText.Append(paramText.Substring(startOffset, astStartOffset - startOffset)); + newParamText.Append(paramText.AsSpan(startOffset, astStartOffset - startOffset)); newParamText.Append(newVarName); startOffset = astEndOffset; } while (orderedUsingVar.MoveNext()); @@ -2228,7 +2469,7 @@ internal string GetParamTextWithDollarUsingHandling(IEnumerator public class StatementBlockAst : Ast { - private static ReadOnlyCollection s_emptyStatementCollection = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyStatementCollection = Utils.EmptyReadOnlyCollection(); /// /// Construct a statement block. @@ -2313,16 +2557,16 @@ 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 + /// Copy the StatementBlockAst instance. /// public override Ast Copy() { @@ -2362,6 +2606,7 @@ internal static AstVisitAction InternalVisit(AstVisitor visitor, if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue && statements != null) { for (int index = 0; index < statements.Count; index++) @@ -2371,6 +2616,7 @@ internal static AstVisitAction InternalVisit(AstVisitor visitor, if (action != AstVisitAction.Continue) break; } } + return action; } @@ -2422,8 +2668,10 @@ public class TypeDefinitionAst : StatementAst { private static readonly ReadOnlyCollection s_emptyAttributeList = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyMembersCollection = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyBaseTypesCollection = Utils.EmptyReadOnlyCollection(); @@ -2431,7 +2679,7 @@ public class TypeDefinitionAst : StatementAst /// Construct a type definition. /// /// The extent of the type definition, from any attributes to the closing curly brace. - /// The name of the type + /// The name of the type. /// The attributes, or null if no attributes were specified. /// The members, or null if no members were specified. /// The attributes (like class or interface) of the type. @@ -2441,7 +2689,7 @@ 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. @@ -2524,6 +2772,7 @@ internal Type Type { return _type; } + set { // The assert may seem a little bit confusing. @@ -2539,7 +2788,7 @@ internal Type Type } /// - /// Copy the TypeDefinitionAst + /// Copy the TypeDefinitionAst. /// public override Ast Copy() { @@ -2553,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) @@ -2666,12 +2914,12 @@ public UsingStatementAst(IScriptExtent extent, UsingStatementKind kind, StringCo { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (kind == UsingStatementKind.Command || kind == UsingStatementKind.Type) { - throw PSTraceSource.NewArgumentException("kind"); + throw PSTraceSource.NewArgumentException(nameof(kind)); } UsingStatementKind = kind; @@ -2684,13 +2932,13 @@ public UsingStatementAst(IScriptExtent extent, UsingStatementKind kind, StringCo /// Construct a simple (one that is not a form of an alias) using module statement with module specification as hashtable. /// /// The extent of the using statement including the using keyword. - /// HashtableAst that describes object + /// HashtableAst that describes object. public UsingStatementAst(IScriptExtent extent, HashtableAst moduleSpecification) : base(extent) { if (moduleSpecification == null) { - throw PSTraceSource.NewArgumentNullException("moduleSpecification"); + throw PSTraceSource.NewArgumentNullException(nameof(moduleSpecification)); } UsingStatementKind = UsingStatementKind.Module; @@ -2714,17 +2962,17 @@ public UsingStatementAst(IScriptExtent extent, UsingStatementKind kind, StringCo { if (aliasName == null) { - throw PSTraceSource.NewArgumentNullException("aliasName"); + throw PSTraceSource.NewArgumentNullException(nameof(aliasName)); } if (resolvedAliasAst == null) { - throw PSTraceSource.NewArgumentNullException("resolvedAliasAst"); + throw PSTraceSource.NewArgumentNullException(nameof(resolvedAliasAst)); } if (kind == UsingStatementKind.Assembly) { - throw PSTraceSource.NewArgumentException("kind"); + throw PSTraceSource.NewArgumentException(nameof(kind)); } UsingStatementKind = kind; @@ -2735,7 +2983,6 @@ public UsingStatementAst(IScriptExtent extent, UsingStatementKind kind, StringCo SetParent(Alias); } - /// /// Construct a using module statement that aliases an item with module specification as hashtable. /// @@ -2747,7 +2994,7 @@ public UsingStatementAst(IScriptExtent extent, StringConstantExpressionAst alias { if (moduleSpecification == null) { - throw PSTraceSource.NewArgumentNullException("moduleSpecification"); + throw PSTraceSource.NewArgumentNullException(nameof(moduleSpecification)); } UsingStatementKind = UsingStatementKind.Module; @@ -2760,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. @@ -2785,7 +3032,7 @@ public UsingStatementAst(IScriptExtent extent, StringConstantExpressionAst alias internal PSModuleInfo ModuleInfo { get; private set; } /// - /// Copy the UsingStatementAst + /// Copy the UsingStatementAst. /// public override Ast Copy() { @@ -2802,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) @@ -2840,7 +3086,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) /// Define imported module and all type definitions imported by this using statement. /// /// - /// return ExportedTypeTable for this module + /// Return ExportedTypeTable for this module. internal ReadOnlyDictionary DefineImportedModule(PSModuleInfo moduleInfo) { var types = moduleInfo.GetExportedTypeDefinitions(); @@ -2851,7 +3097,7 @@ internal ReadOnlyDictionary DefineImportedModule(PSMo /// /// Is UsingStatementKind Module or Assembly. /// - /// true, if it is. + /// True, if it is. internal bool IsUsingModuleOrAssembly() { return UsingStatementKind == UsingStatementKind.Assembly || UsingStatementKind == UsingStatementKind.Module; @@ -2926,13 +3172,13 @@ public PropertyMemberAst(IScriptExtent extent, string name, TypeConstraintAst pr { if (string.IsNullOrWhiteSpace(name)) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if ((propertyAttributes & (PropertyAttributes.Private | PropertyAttributes.Public)) == (PropertyAttributes.Private | PropertyAttributes.Public)) { - throw PSTraceSource.NewArgumentException("propertyAttributes"); + throw PSTraceSource.NewArgumentException(nameof(propertyAttributes)); } Name = name; @@ -2951,6 +3197,7 @@ public PropertyMemberAst(IScriptExtent extent, string name, TypeConstraintAst pr { this.Attributes = s_emptyAttributeList; } + PropertyAttributes = propertyAttributes; InitialValue = initialValue; @@ -2968,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. @@ -3029,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) @@ -3051,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; + } } } @@ -3094,6 +3343,7 @@ public class FunctionMemberAst : MemberAst, IParameterMetadataProvider { private static readonly ReadOnlyCollection s_emptyAttributeList = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyParameterList = Utils.EmptyReadOnlyCollection(); @@ -3112,13 +3362,13 @@ public FunctionMemberAst(IScriptExtent extent, FunctionDefinitionAst functionDef { if (functionDefinitionAst == null) { - throw PSTraceSource.NewArgumentNullException("functionDefinitionAst"); + throw PSTraceSource.NewArgumentNullException(nameof(functionDefinitionAst)); } if ((methodAttributes & (MethodAttributes.Private | MethodAttributes.Public)) == (MethodAttributes.Private | MethodAttributes.Public)) { - throw PSTraceSource.NewArgumentException("methodAttributes"); + throw PSTraceSource.NewArgumentException(nameof(methodAttributes)); } if (returnType != null) @@ -3150,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. @@ -3171,9 +3421,9 @@ public ReadOnlyCollection Parameters public ScriptBlockAst Body { get { return _functionDefinitionAst.Body; } } /// - /// Method attribute flags. + /// Method attribute flags. /// - public MethodAttributes MethodAttributes { get; private set; } + public MethodAttributes MethodAttributes { get; } /// /// Returns true if the method is public. @@ -3186,17 +3436,17 @@ public ReadOnlyCollection Parameters public bool IsPrivate { get { return (MethodAttributes & MethodAttributes.Private) != 0; } } /// - /// Returns true if the method is hidden + /// Returns true if the method is hidden. /// public bool IsHidden { get { return (MethodAttributes & MethodAttributes.Hidden) != 0; } } /// - /// Returns true if the method is static + /// Returns true if the method is static. /// public bool IsStatic { get { return (MethodAttributes & MethodAttributes.Static) != 0; } } /// - /// Returns true if the method is a constructor + /// Returns true if the method is a constructor. /// public bool IsConstructor { @@ -3205,6 +3455,8 @@ public bool IsConstructor internal IScriptExtent NameExtent { get { return _functionDefinitionAst.NameExtent; } } + private string _toolTip; + /// /// Copy a function member ast. /// @@ -3219,25 +3471,56 @@ public override Ast Copy() internal override string GetTooltip() { - var sb = new StringBuilder(); - if (IsStatic) + if (!string.IsNullOrEmpty(_toolTip)) { - sb.Append("static "); + return _toolTip; } - sb.Append(IsReturnTypeVoid() ? "void" : ReturnType.TypeName.FullName); - sb.Append(' '); - sb.Append(Name); - sb.Append('('); - for (int i = 0; i < Parameters.Count; i++) + + var sb = new StringBuilder(); + var classMembers = ((TypeDefinitionAst)Parent).Members; + for (int i = 0; i < classMembers.Count; i++) { - if (i > 0) + var methodMember = classMembers[i] as FunctionMemberAst; + if (methodMember is null || + !Name.Equals(methodMember.Name) || + IsStatic != methodMember.IsStatic) + { + continue; + } + + if (sb.Length > 0) + { + sb.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++) { - sb.Append(", "); + if (j > 0) + { + sb.Append(", "); + } + + sb.Append(methodMember.Parameters[j].GetTooltip()); } - sb.Append(Parameters[i].GetTooltip()); + + sb.Append(')'); } - sb.Append(')'); - return sb.ToString(); + + _toolTip = sb.ToString(); + return _toolTip; } #region Visitors @@ -3245,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) @@ -3263,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; + } } } @@ -3301,6 +3586,11 @@ IEnumerable IParameterMetadataProvider.GetScriptBlockAttributes() return ((IParameterMetadataProvider)_functionDefinitionAst).GetScriptBlockAttributes(); } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)_functionDefinitionAst).GetExperimentalAttributes(); + } + bool IParameterMetadataProvider.UsesCmdletBinding() { return ((IParameterMetadataProvider)_functionDefinitionAst).UsesCmdletBinding(); @@ -3332,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() @@ -3357,7 +3647,7 @@ internal CompilerGeneratedMemberFunctionAst(IScriptExtent extent, TypeDefinition StatementAst statement = null; if (type == SpecialMemberFunctionType.DefaultConstructor) { - var invokeMemberAst = new BaseCtorInvokeMemberExpressionAst(extent, extent, Utils.EmptyArray()); + var invokeMemberAst = new BaseCtorInvokeMemberExpressionAst(extent, extent, Array.Empty()); statement = new CommandExpressionAst(extent, invokeMemberAst, null); } @@ -3374,8 +3664,9 @@ public override string Name get { return DefiningType.Name; } } - internal TypeDefinitionAst DefiningType { get; private set; } - internal SpecialMemberFunctionType Type { get; private set; } + internal TypeDefinitionAst DefiningType { get; } + + internal SpecialMemberFunctionType Type { get; } internal override string GetTooltip() { @@ -3414,13 +3705,19 @@ public IEnumerable GetScriptBlockAttributes() return ((IParameterMetadataProvider)Body).GetScriptBlockAttributes(); } + public IEnumerable GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)Body).GetExperimentalAttributes(); + } + public bool UsesCmdletBinding() { return false; } 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) @@ -3474,17 +3771,17 @@ public FunctionDefinitionAst(IScriptExtent extent, { if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } if (isFilter && isWorkflow) { - throw PSTraceSource.NewArgumentException("isFilter"); + throw PSTraceSource.NewArgumentException(nameof(isFilter)); } this.IsFilter = isFilter; @@ -3496,6 +3793,7 @@ public FunctionDefinitionAst(IScriptExtent extent, this.Parameters = new ReadOnlyCollection(parameters.ToArray()); SetParents(Parameters); } + this.Body = body; SetParent(body); } @@ -3519,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. @@ -3540,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; } @@ -3560,12 +3858,13 @@ public CommentHelpInfo GetHelpContent() { return HelpCommentsParser.GetHelpContents(commentTokens.Item1, commentTokens.Item2); } + return null; } /// - /// Return the help content, if any, for the function. - /// Use this overload when parsing multiple functions within a single scope. + /// Return the help content, if any, for the function. + /// Use this overload when parsing multiple functions within a single scope. /// /// A dictionary that the parser will use to /// map AST nodes to their respective tokens. The parser uses this to improve performance @@ -3575,21 +3874,19 @@ public CommentHelpInfo GetHelpContent() /// public CommentHelpInfo GetHelpContent(Dictionary scriptBlockTokenCache) { - if (scriptBlockTokenCache == null) - { - throw new ArgumentNullException("scriptBlockTokenCache"); - } + ArgumentNullException.ThrowIfNull(scriptBlockTokenCache); var commentTokens = HelpCommentsParser.GetHelpCommentTokens(this, scriptBlockTokenCache); if (commentTokens != null) { return HelpCommentsParser.GetHelpContents(commentTokens.Item1, commentTokens.Item2); } + return null; } /// - /// Copy the FunctionDefinitionAst instance + /// Copy the FunctionDefinitionAst instance. /// public override Ast Copy() { @@ -3610,12 +3907,12 @@ 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; } var sb = new StringBuilder("param("); - string separator = ""; + string separator = string.Empty; if (additionalNewUsingParams != null) { @@ -3633,7 +3930,8 @@ internal string GetParamTextFromParameterList(Tuple, : param.ToString()); separator = ", "; } - sb.Append(")"); + + sb.Append(')'); sb.Append(Environment.NewLine); return sb.ToString(); @@ -3662,9 +3960,11 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue) action = Body.InternalVisit(visitor); } + return visitor.CheckForPostAction(this, action); } @@ -3683,10 +3983,12 @@ RuntimeDefinedParameterDictionary IParameterMetadataProvider.GetParameterMetadat { return Compiler.GetParameterMetaData(Parameters, automaticPositions, ref usesCmdletBinding); } + if (Body.ParamBlock != null) { return Compiler.GetParameterMetaData(Body.ParamBlock.Parameters, automaticPositions, ref usesCmdletBinding); } + return new RuntimeDefinedParameterDictionary { Data = RuntimeDefinedParameterDictionary.EmptyParameterArray }; } @@ -3695,9 +3997,14 @@ IEnumerable IParameterMetadataProvider.GetScriptBlockAttributes() return ((IParameterMetadataProvider)Body).GetScriptBlockAttributes(); } + IEnumerable IParameterMetadataProvider.GetExperimentalAttributes() + { + return ((IParameterMetadataProvider)Body).GetExperimentalAttributes(); + } + 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, @@ -3739,6 +4046,7 @@ bool IParameterMetadataProvider.UsesCmdletBinding() { usesCmdletBinding = ((IParameterMetadataProvider)Body).UsesCmdletBinding(); } + return usesCmdletBinding; } @@ -3768,7 +4076,7 @@ public IfStatementAst(IScriptExtent extent, IEnumerable clauses, State { if (clauses == null || !clauses.Any()) { - throw PSTraceSource.NewArgumentException("clauses"); + throw PSTraceSource.NewArgumentException(nameof(clauses)); } this.Clauses = new ReadOnlyCollection(clauses.ToArray()); @@ -3787,15 +4095,15 @@ 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 + /// Copy the IfStatementAst instance. /// public override Ast Copy() { @@ -3836,10 +4144,12 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue && ElseClause != null) { action = ElseClause.InternalVisit(visitor); } + return visitor.CheckForPostAction(this, action); } @@ -3851,7 +4161,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) /// public class DataStatementAst : StatementAst { - private static readonly ExpressionAst[] s_emptyCommandsAllowed = Utils.EmptyArray(); + private static readonly ExpressionAst[] s_emptyCommandsAllowed = Array.Empty(); /// /// Construct a data statement. @@ -3871,7 +4181,7 @@ public DataStatementAst(IScriptExtent extent, { if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } if (string.IsNullOrWhiteSpace(variableName)) @@ -3884,12 +4194,13 @@ 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 { this.CommandsAllowed = new ReadOnlyCollection(s_emptyCommandsAllowed); } + this.Body = body; SetParent(body); } @@ -3897,20 +4208,20 @@ 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 + /// Copy the DataStatementAst instance. /// public override Ast Copy() { @@ -3919,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 @@ -3982,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; } } /// @@ -4012,7 +4323,7 @@ protected LoopStatementAst(IScriptExtent extent, string label, PipelineBaseAst c { if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } this.Body = body; @@ -4022,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; } } /// @@ -4117,20 +4428,20 @@ 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 + /// Copy the ForEachStatementAst instance. /// public override Ast Copy() { @@ -4202,6 +4513,7 @@ public ForStatementAst(IScriptExtent extent, this.Initializer = initializer; SetParent(initializer); } + if (iterator != null) { this.Iterator = iterator; @@ -4212,15 +4524,15 @@ 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 + /// Copy the ForStatementAst instance. /// public override Ast Copy() { @@ -4266,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. @@ -4278,12 +4590,12 @@ public DoWhileStatementAst(IScriptExtent extent, string label, PipelineBaseAst c { if (condition == null) { - throw PSTraceSource.NewArgumentNullException("condition"); + throw PSTraceSource.NewArgumentNullException(nameof(condition)); } } /// - /// Copy the DoWhileStatementAst instance + /// Copy the DoWhileStatementAst instance. /// public override Ast Copy() { @@ -4334,12 +4646,12 @@ public DoUntilStatementAst(IScriptExtent extent, string label, PipelineBaseAst c { if (condition == null) { - throw PSTraceSource.NewArgumentNullException("condition"); + throw PSTraceSource.NewArgumentNullException(nameof(condition)); } } /// - /// Copy the DoUntilStatementAst instance + /// Copy the DoUntilStatementAst instance. /// public override Ast Copy() { @@ -4390,12 +4702,12 @@ public WhileStatementAst(IScriptExtent extent, string label, PipelineBaseAst con { if (condition == null) { - throw PSTraceSource.NewArgumentNullException("condition"); + throw PSTraceSource.NewArgumentNullException(nameof(condition)); } } /// - /// Copy the WhileStatementAst instance + /// Copy the WhileStatementAst instance. /// public override Ast Copy() { @@ -4478,7 +4790,7 @@ public enum SwitchFlags /// public class SwitchStatementAst : LabeledStatementAst { - private static readonly SwitchClause[] s_emptyClauseArray = Utils.EmptyArray(); + private static readonly SwitchClause[] s_emptyClauseArray = Array.Empty(); /// /// Construct a switch statement. @@ -4510,7 +4822,7 @@ public SwitchStatementAst(IScriptExtent extent, { // Must specify either clauses or default. If neither, just complain about clauses as that's the most likely // invalid argument. - throw PSTraceSource.NewArgumentException("clauses"); + throw PSTraceSource.NewArgumentException(nameof(clauses)); } this.Flags = flags; @@ -4527,22 +4839,22 @@ 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 + /// Copy the SwitchStatementAst instance. /// public override Ast Copy() { @@ -4591,6 +4903,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue && Default != null) action = Default.InternalVisit(visitor); return visitor.CheckForPostAction(this, action); @@ -4625,7 +4938,7 @@ public CatchClauseAst(IScriptExtent extent, IEnumerable catch { if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } if (catchTypes != null) @@ -4646,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. @@ -4657,10 +4970,10 @@ 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 + /// Copy the CatchClauseAst instance. /// public override Ast Copy() { @@ -4687,6 +5000,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; action = catchType.InternalVisit(visitor); } + if (action == AstVisitAction.Continue) action = Body.InternalVisit(visitor); return visitor.CheckForPostAction(this, action); @@ -4727,13 +5041,13 @@ public TryStatementAst(IScriptExtent extent, { if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } if ((catchClauses == null || !catchClauses.Any()) && @finally == null) { // If no catches and no finally, just complain about catchClauses as that's the most likely invalid argument. - throw PSTraceSource.NewArgumentException("catchClauses"); + throw PSTraceSource.NewArgumentException(nameof(catchClauses)); } this.Body = body; @@ -4758,21 +5072,21 @@ 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 + /// Copy the TryStatementAst instance. /// public override Ast Copy() { @@ -4806,6 +5120,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue && Finally != null) action = Finally.InternalVisit(visitor); return visitor.CheckForPostAction(this, action); @@ -4835,7 +5150,7 @@ public TrapStatementAst(IScriptExtent extent, TypeConstraintAst trapType, Statem { if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } if (trapType != null) @@ -4843,6 +5158,7 @@ public TrapStatementAst(IScriptExtent extent, TypeConstraintAst trapType, Statem this.TrapType = trapType; SetParent(trapType); } + this.Body = body; SetParent(body); } @@ -4850,15 +5166,15 @@ 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 + /// Copy the TrapStatementAst instance. /// public override Ast Copy() { @@ -4919,10 +5235,10 @@ 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 + /// Copy the BreakStatementAst instance. /// public override Ast Copy() { @@ -4976,10 +5292,10 @@ 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 + /// Copy the ContinueStatementAst instance. /// public override Ast Copy() { @@ -5033,10 +5349,10 @@ 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 + /// Copy the ReturnStatementAst instance. /// public override Ast Copy() { @@ -5090,10 +5406,10 @@ 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 + /// Copy the ExitStatementAst instance. /// public override Ast Copy() { @@ -5147,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 @@ -5175,14 +5491,16 @@ public bool IsRethrow { return true; } + parent = parent.Parent; } + return false; } } /// - /// Copy the ThrowStatementAst instance + /// Copy the ThrowStatementAst instance. /// public override Ast Copy() { @@ -5210,25 +5528,143 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) #endregion Visitors } - #endregion Flow Control Statements - - #region Pipelines - /// - /// An abstract base class for statements that include command invocations, pipelines, expressions, and assignments. - /// Any statement that does not begin with a keyword is derives from PipelineBastAst. + /// An AST representing a syntax element chainable with '&&' or '||'. /// - public abstract class PipelineBaseAst : StatementAst + public abstract class ChainableAst : PipelineBaseAst { /// - /// Initialize the common parts of a PipelineBaseAst. + /// Initializes a new instance of the new chainable AST with the given extent. /// - /// The extent of the statement. - /// - /// If is null. - /// - protected PipelineBaseAst(IScriptExtent extent) - : base(extent) + /// The script extent of the AST. + protected ChainableAst(IScriptExtent extent) : base(extent) + { + } + } + + /// + /// A command-oriented flow-controlled pipeline chain. + /// E.g. npm build && npm test or Get-Content -Raw ./file.txt || "default". + /// + public class PipelineChainAst : ChainableAst + { + /// + /// Initializes a new instance of the new statement chain AST from two statements and an operator. + /// + /// The extent of the chained statement. + /// The pipeline or pipeline chain to the left of the operator. + /// The pipeline to the right of the operator. + /// The operator used. + /// True when this chain has been invoked with the background operator, false otherwise. + public PipelineChainAst( + IScriptExtent extent, + ChainableAst lhsChain, + PipelineAst rhsPipeline, + TokenKind chainOperator, + bool background = false) + : base(extent) + { + ArgumentNullException.ThrowIfNull(lhsChain); + + ArgumentNullException.ThrowIfNull(rhsPipeline); + + if (chainOperator != TokenKind.AndAnd && chainOperator != TokenKind.OrOr) + { + throw new ArgumentException(nameof(chainOperator)); + } + + LhsPipelineChain = lhsChain; + RhsPipeline = rhsPipeline; + Operator = chainOperator; + Background = background; + + SetParent(LhsPipelineChain); + SetParent(RhsPipeline); + } + + /// + /// Gets the left hand pipeline in the chain. + /// + public ChainableAst LhsPipelineChain { get; } + + /// + /// Gets the right hand pipeline in the chain. + /// + public PipelineAst RhsPipeline { get; } + + /// + /// Gets the chaining operator used. + /// + public TokenKind Operator { get; } + + /// + /// Gets a flag that indicates whether this chain has been invoked with the background operator. + /// + public bool Background { get; } + + /// + /// Create a copy of this Ast. + /// + /// + /// A fresh copy of this PipelineChainAst instance. + /// + public override Ast Copy() + { + return new PipelineChainAst(Extent, CopyElement(LhsPipelineChain), CopyElement(RhsPipeline), Operator, Background); + } + + internal override object Accept(ICustomAstVisitor visitor) + { + return (visitor as ICustomAstVisitor2)?.VisitPipelineChain(this); + } + + internal override AstVisitAction InternalVisit(AstVisitor visitor) + { + AstVisitAction action = AstVisitAction.Continue; + + // Can only visit new AST type if using AstVisitor2 + if (visitor is AstVisitor2 visitor2) + { + action = visitor2.VisitPipelineChain(this); + if (action == AstVisitAction.SkipChildren) + { + return visitor.CheckForPostAction(this, AstVisitAction.Continue); + } + } + + if (action == AstVisitAction.Continue) + { + action = LhsPipelineChain.InternalVisit(visitor); + } + + if (action == AstVisitAction.Continue) + { + action = RhsPipeline.InternalVisit(visitor); + } + + return visitor.CheckForPostAction(this, action); + } + } + + #endregion Flow Control Statements + + #region Pipelines + + /// + /// An abstract base class for statements that include command invocations, pipelines, expressions, and assignments. + /// Any statement that does not begin with a keyword is derives from PipelineBastAst. + /// + public abstract class PipelineBaseAst : StatementAst + { + /// + /// Initialize the common parts of a PipelineBaseAst. + /// + /// The extent of the statement. + /// + /// If is null. + /// + protected PipelineBaseAst(IScriptExtent extent) + : base(extent) { } @@ -5245,14 +5681,14 @@ public virtual ExpressionAst GetPureExpression() /// The ast that represents a PowerShell pipeline, e.g. gci -re . *.cs | select-string Foo or 65..90 | % { [char]$_ }. /// A pipeline must have at least 1 command. The first command may be an expression or a command invocation. /// - public class PipelineAst : PipelineBaseAst + public class PipelineAst : ChainableAst { /// /// Construct a pipeline from a collection of commands. /// /// The extent of the pipeline. /// The collection of commands representing the pipeline. - /// Indicates that this pipeline should be run in the background + /// Indicates that this pipeline should be run in the background. /// /// If is null. /// @@ -5264,7 +5700,7 @@ public PipelineAst(IScriptExtent extent, IEnumerable pipelineEle { if (pipelineElements == null || !pipelineElements.Any()) { - throw PSTraceSource.NewArgumentException("pipelineElements"); + throw PSTraceSource.NewArgumentException(nameof(pipelineElements)); } this.Background = background; @@ -5283,9 +5719,8 @@ public PipelineAst(IScriptExtent extent, IEnumerable pipelineEle /// /// If is null or is an empty collection. /// - public PipelineAst(IScriptExtent extent, IEnumerable pipelineElements) :this (extent, pipelineElements, background: false) + public PipelineAst(IScriptExtent extent, IEnumerable pipelineElements) : this(extent, pipelineElements, background: false) { - } /// @@ -5293,7 +5728,7 @@ public PipelineAst(IScriptExtent extent, IEnumerable pipelineEle /// /// The extent of the pipeline (which should be the extent of the command). /// The command for the pipeline. - /// Indicates that this pipeline should be run in the background + /// Indicates that this pipeline should be run in the background. /// /// If or is null. /// @@ -5302,7 +5737,7 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst, bool backgro { if (commandAst == null) { - throw PSTraceSource.NewArgumentNullException("commandAst"); + throw PSTraceSource.NewArgumentNullException(nameof(commandAst)); } this.Background = background; @@ -5318,20 +5753,19 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst, bool backgro /// /// If or is null. /// - public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) :this (extent, commandAst, background: false) + public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) : this(extent, commandAst, background: false) { - } /// /// 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. /// - public bool Background { get; private set; } + public bool Background { get; internal set; } /// /// If the pipeline represents a pure expression, the expression is returned, otherwise null is returned. @@ -5344,7 +5778,7 @@ public override ExpressionAst GetPureExpression() } CommandExpressionAst expr = PipelineElements[0] as CommandExpressionAst; - if (expr != null && !expr.Redirections.Any()) + if (expr != null && expr.Redirections.Count == 0) { return expr.Expression; } @@ -5353,8 +5787,9 @@ public override ExpressionAst GetPureExpression() } /// - /// Copy the PipelineAst instance + /// Copy the PipelineAst instance. /// + /// A fresh copy of this PipelineAst instance. public override Ast Copy() { var newPipelineElements = CopyElements(this.PipelineElements); @@ -5382,6 +5817,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -5453,6 +5889,7 @@ public CommandParameterAst(IScriptExtent extent, string parameterName, Expressio this.Argument = argument; SetParent(argument); } + this.ErrorPosition = errorPosition; } @@ -5460,22 +5897,22 @@ 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 + /// Copy the CommandParameterAst instance. /// public override Ast Copy() { @@ -5499,6 +5936,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) { action = Argument.InternalVisit(visitor); } + return visitor.CheckForPostAction(this, action); } @@ -5540,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; } } /// @@ -5555,7 +5993,7 @@ public class CommandAst : CommandBaseAst /// The extent of the command, starting with either the optional invocation operator '&' or '.' or the command name /// and ending with the last command element. /// - /// The elements of the command (command name, parameters and expressions.) + /// The elements of the command (command name, parameters and expressions.). /// The invocation operator that was used, if any. /// The redirections for the command, may be null. /// @@ -5572,12 +6010,12 @@ public CommandAst(IScriptExtent extent, { if (commandElements == null || !commandElements.Any()) { - throw PSTraceSource.NewArgumentException("commandElements"); + throw PSTraceSource.NewArgumentException(nameof(commandElements)); } if (invocationOperator != TokenKind.Dot && invocationOperator != TokenKind.Ampersand && invocationOperator != TokenKind.Unknown) { - throw PSTraceSource.NewArgumentException("invocationOperator"); + throw PSTraceSource.NewArgumentException(nameof(invocationOperator)); } this.CommandElements = new ReadOnlyCollection(commandElements.ToArray()); @@ -5588,22 +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. /// @@ -5612,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; } /// @@ -5622,7 +6058,7 @@ public string GetCommandName() public DynamicKeyword DefiningKeyword { get; set; } /// - /// Copy the CommandAst instance + /// Copy the CommandAst instance. /// public override Ast Copy() { @@ -5655,6 +6091,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + if (action == AstVisitAction.Continue) { for (int index = 0; index < Redirections.Count; index++) @@ -5666,6 +6103,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) } } } + return visitor.CheckForPostAction(this, action); } @@ -5693,7 +6131,7 @@ public CommandExpressionAst(IScriptExtent extent, { if (expression == null) { - throw PSTraceSource.NewArgumentNullException("expression"); + throw PSTraceSource.NewArgumentNullException(nameof(expression)); } this.Expression = expression; @@ -5703,10 +6141,10 @@ 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 + /// Copy the CommandExpressionAst instance. /// public override Ast Copy() { @@ -5740,6 +6178,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) } } } + return visitor.CheckForPostAction(this, action); } @@ -5765,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; } } /// @@ -5831,10 +6270,10 @@ 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 + /// Copy the MergingRedirectionAst instance. /// public override Ast Copy() { @@ -5885,7 +6324,7 @@ public FileRedirectionAst(IScriptExtent extent, RedirectionStream stream, Expres { if (file == null) { - throw PSTraceSource.NewArgumentNullException("file"); + throw PSTraceSource.NewArgumentNullException(nameof(file)); } this.Location = file; @@ -5896,15 +6335,15 @@ 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 + /// Copy the FileRedirectionAst instance. /// public override Ast Copy() { @@ -5961,24 +6400,18 @@ public AssignmentStatementAst(IScriptExtent extent, ExpressionAst left, TokenKin if ((@operator.GetTraits() & TokenFlags.AssignmentOperator) == 0) { - throw PSTraceSource.NewArgumentException("operator"); + throw PSTraceSource.NewArgumentException(nameof(@operator)); } // 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; @@ -5992,25 +6425,25 @@ 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 + /// Copy the AssignmentStatementAst instance. /// public override Ast Copy() { @@ -6027,13 +6460,13 @@ 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) { yield return element; } + yield break; } @@ -6061,37 +6494,36 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) #endregion Visitors } - /// - /// Defines types of configuration document + /// Defines types of configuration document. /// public enum ConfigurationType { /// - /// Resource configuration + /// Resource configuration. /// Resource = 0, /// - /// Meta configuration + /// Meta configuration. /// Meta = 1 } /// - /// The ast represents the DSC configuration statement + /// The ast represents the DSC configuration statement. /// public class ConfigurationDefinitionAst : StatementAst { /// - /// Construct a configuration statement + /// Construct a configuration statement. /// /// /// The extent of the expression, starting with the attribute and ending after the expression being attributed. /// - /// of the configuration statement - /// The type of the configuration - /// The configuration name expression + /// of the configuration statement. + /// The type of the configuration. + /// The configuration name expression. /// /// If , , or is null. /// @@ -6102,16 +6534,19 @@ public ConfigurationDefinitionAst(IScriptExtent extent, { if (extent == null) { - throw PSTraceSource.NewArgumentNullException("extent"); + throw PSTraceSource.NewArgumentNullException(nameof(extent)); } + if (body == null) { - throw PSTraceSource.NewArgumentNullException("body"); + throw PSTraceSource.NewArgumentNullException(nameof(body)); } + if (instanceName == null) { - throw PSTraceSource.NewArgumentNullException("instanceName"); + throw PSTraceSource.NewArgumentNullException(nameof(instanceName)); } + this.Body = body; SetParent(body); this.ConfigurationType = type; @@ -6123,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 + /// 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. @@ -6149,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()) }; } @@ -6158,37 +6593,40 @@ 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) return visitor.CheckForPostAction(this, AstVisitAction.Continue); } + if (action == AstVisitAction.Continue) { action = InstanceName.InternalVisit(visitor); } + if (action == AstVisitAction.Continue) { Body.InternalVisit(visitor); } + return visitor.CheckForPostAction(this, action); } #endregion Visitors - #region Internal methods/properties internal Token LCurlyToken { get; set; } + internal Token ConfigurationToken { get; set; } + internal IEnumerable CustomAttributes { get; set; } /// @@ -6199,14 +6637,14 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) internal List DefinedKeywords { get; set; } /// - /// Generate ast that defines a function for this object + /// Generate ast that defines a function for this object. /// /// /// The that defines a function for this object /// internal PipelineAst GenerateSetItemPipelineAst() { - //************************** + // ************************** // Now construct the AST to call the function that will build the actual object. // This is composed of a call to command with the signature // function PSDesiredStateConfiguration\\Configuration @@ -6227,6 +6665,7 @@ internal PipelineAst GenerateSetItemPipelineAst() // $IsMetaConfig = $false # the configuration to generated is a meta configuration // ) // } + var cea = new Collection { new StringConstantExpressionAst(this.Extent, @@ -6245,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); @@ -6278,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 @@ -6291,25 +6730,26 @@ 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); 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); #region "Construct Set-Item pipeline" - //************************** + // ************************** // Now construct the AST to call the set-item cmdlet that will create the function // it will do set-item -Path function:\ConfigurationNameExpr -Value funcBody @@ -6349,10 +6789,9 @@ internal PipelineAst GenerateSetItemPipelineAst() #region static fields/methods /// - /// /// /// - /// Item1 - ResourceName, Item2 - ModuleName, Item3 - ModuleVersion + /// Item1 - ResourceName, Item2 - ModuleName, Item3 - ModuleVersion. /// private static bool IsImportCommand(StatementAst stmt, List> resourceModulePairsToImport) { @@ -6378,17 +6817,17 @@ private static bool IsImportCommand(StatementAst stmt, List(moduleVersionEvaluated); // Use -ModuleVersion only in the case, if -ModuleName specified. @@ -6479,10 +6919,13 @@ private static IEnumerable ConfigurationBuildInParameters } } } + return s_configurationBuildInParameters; } } + private static List s_configurationBuildInParameters; + private static IEnumerable ConfigurationBuildInParameterAttribAsts { get @@ -6512,9 +6955,11 @@ private static IEnumerable ConfigurationBuildInParameterAttribAsts } } } + return s_configurationBuildInParameterAttrAsts; } } + private static List s_configurationBuildInParameterAttrAsts; private static IEnumerable ConfigurationExtraParameterStatements @@ -6542,36 +6987,39 @@ private static IEnumerable ConfigurationExtraParameterStatements } } } + return s_configurationExtraParameterStatements; } } + private static List s_configurationExtraParameterStatements; #endregion } /// - /// The ast represents the DynamicKeyword statement + /// The ast represents the DynamicKeyword statement. /// public class DynamicKeywordStatementAst : StatementAst { /// - /// Construct a DynamicKeyword statement + /// Construct a DynamicKeyword statement. /// /// /// The extent of the expression, starting with the attribute and ending after the expression being attributed. /// - /// A collection of used to invoke specific command + /// A collection of used to invoke specific command. /// /// If is null or empty. /// public DynamicKeywordStatementAst(IScriptExtent extent, IEnumerable commandElements) : base(extent) { - if (commandElements == null || commandElements.Count() <= 0) + if (commandElements == null || !commandElements.Any()) { - throw PSTraceSource.NewArgumentException("commandElements"); + throw PSTraceSource.NewArgumentException(nameof(commandElements)); } + this.CommandElements = new ReadOnlyCollection(commandElements.ToArray()); SetParents(CommandElements); } @@ -6585,9 +7033,9 @@ public DynamicKeywordStatementAst(IScriptExtent extent, /// (2) InstanceName /// (3) Body, could be ScriptBlockExpressionAst (for Node keyword) or a HashtableAst (remaining) /// - /// This property is never null and never empty + /// 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. @@ -6613,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) @@ -6636,6 +7083,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) break; } } + return visitor.CheckForPostAction(this, action); } @@ -6649,20 +7097,29 @@ internal DynamicKeyword Keyword { return _keyword; } + set { _keyword = value.Copy(); } } + 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; + internal PipelineAst GenerateCommandCallPipelineAst() { if (_commandCallPipelineAst != null) @@ -6712,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 @@ -6731,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; // @@ -6807,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", @@ -6905,7 +7361,7 @@ protected ExpressionAst(IScriptExtent extent) public virtual Type StaticType { get { return typeof(object); } } /// - /// Determine if the results of ParenExpression/SubExpression should be preserved in case of exception + /// Determine if the results of ParenExpression/SubExpression should be preserved in case of exception. /// /// /// We should preserve the partial output in case of exception only if the SubExpression/ParenExpression meets following conditions: @@ -6915,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; } @@ -6935,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(); } @@ -6947,13 +7398,111 @@ internal virtual bool ShouldPreserveOutputInCaseOfException() } } + /// + /// The ast representing a ternary expression, e.g. $a ? 1 : 2. + /// + public class TernaryExpressionAst : ExpressionAst + { + /// + /// Initializes a new instance of the a ternary expression. + /// + /// The extent of the expression. + /// The condition operand. + /// The if clause. + /// The else clause. + public TernaryExpressionAst(IScriptExtent extent, ExpressionAst condition, ExpressionAst ifTrue, ExpressionAst ifFalse) + : base(extent) + { + Condition = condition ?? throw PSTraceSource.NewArgumentNullException(nameof(condition)); + IfTrue = ifTrue ?? throw PSTraceSource.NewArgumentNullException(nameof(ifTrue)); + IfFalse = ifFalse ?? throw PSTraceSource.NewArgumentNullException(nameof(ifFalse)); + + SetParent(Condition); + SetParent(IfTrue); + SetParent(IfFalse); + } + + /// + /// Gets the ast for the condition of the ternary expression. The property is never null. + /// + public ExpressionAst Condition { get; } + + /// + /// Gets the ast for the if-operand of the ternary expression. The property is never null. + /// + public ExpressionAst IfTrue { get; } + + /// + /// Gets the ast for the else-operand of the ternary expression. The property is never null. + /// + public ExpressionAst IfFalse { get; } + + /// + /// Copy the TernaryExpressionAst instance. + /// + /// + /// Returns a copy of the ast. + /// + public override Ast Copy() + { + ExpressionAst newCondition = CopyElement(this.Condition); + ExpressionAst newIfTrue = CopyElement(this.IfTrue); + ExpressionAst newIfFalse = CopyElement(this.IfFalse); + return new TernaryExpressionAst(this.Extent, newCondition, newIfTrue, newIfFalse); + } + + #region Visitors + + internal override object Accept(ICustomAstVisitor visitor) + { + if (visitor is ICustomAstVisitor2 visitor2) + { + return visitor2.VisitTernaryExpression(this); + } + + return null; + } + + internal override AstVisitAction InternalVisit(AstVisitor visitor) + { + var action = AstVisitAction.Continue; + if (visitor is AstVisitor2 visitor2) + { + action = visitor2.VisitTernaryExpression(this); + if (action == AstVisitAction.SkipChildren) + { + return visitor.CheckForPostAction(this, AstVisitAction.Continue); + } + } + + if (action == AstVisitAction.Continue) + { + action = Condition.InternalVisit(visitor); + } + + if (action == AstVisitAction.Continue) + { + action = IfTrue.InternalVisit(visitor); + } + + if (action == AstVisitAction.Continue) + { + action = IfFalse.InternalVisit(visitor); + } + + return visitor.CheckForPostAction(this, action); + } + + #endregion Visitors + } + /// /// The ast representing a binary expression, e.g. $a + $b. /// public class BinaryExpressionAst : ExpressionAst { /// - /// Construct a binary expression. + /// Initializes a new instance of the binary expression. /// /// The extent of the expression. /// The left hand operand. @@ -6974,7 +7523,7 @@ public BinaryExpressionAst(IScriptExtent extent, ExpressionAst left, TokenKind @ { if ((@operator.GetTraits() & TokenFlags.BinaryOperator) == 0) { - throw PSTraceSource.NewArgumentException("operator"); + throw PSTraceSource.NewArgumentException(nameof(@operator)); } if (left == null || right == null || errorPosition == null) @@ -6993,25 +7542,25 @@ 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 + /// Copy the BinaryExpressionAst instance. /// public override Ast Copy() { @@ -7036,11 +7585,14 @@ public override Type StaticType case TokenKind.Is: return typeof(bool); } + return typeof(object); } } 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 @@ -7072,7 +7624,7 @@ public class UnaryExpressionAst : ExpressionAst /// /// Construct a unary expression. /// - /// The extent of the expression, including the operator (which may be prefix or postfix.) + /// The extent of the expression, including the operator (which may be prefix or postfix.). /// The unary operator token kind for the operation. /// The expression that the unary operator is applied to. /// @@ -7086,12 +7638,12 @@ public UnaryExpressionAst(IScriptExtent extent, TokenKind tokenKind, ExpressionA { if ((tokenKind.GetTraits() & TokenFlags.UnaryOperator) == 0) { - throw PSTraceSource.NewArgumentException("tokenKind"); + throw PSTraceSource.NewArgumentException(nameof(tokenKind)); } if (child == null) { - throw PSTraceSource.NewArgumentNullException("child"); + throw PSTraceSource.NewArgumentNullException(nameof(child)); } this.TokenKind = tokenKind; @@ -7102,15 +7654,15 @@ 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 + /// Copy the UnaryExpressionAst instance. /// public override Ast Copy() { @@ -7158,7 +7710,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) public class BlockStatementAst : StatementAst { /// - /// Construct a keyword block expression + /// Construct a keyword block expression. /// /// /// @@ -7173,7 +7725,7 @@ public BlockStatementAst(IScriptExtent extent, Token kind, StatementBlockAst bod if (kind.Kind != TokenKind.Sequence && kind.Kind != TokenKind.Parallel) { - throw PSTraceSource.NewArgumentException("kind"); + throw PSTraceSource.NewArgumentException(nameof(kind)); } this.Kind = kind; @@ -7184,15 +7736,15 @@ 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 + /// The keyword name. /// - public Token Kind { get; private set; } + public Token Kind { get; } /// - /// Copy the BlockStatementAst instance + /// Copy the BlockStatementAst instance. /// public override Ast Copy() { @@ -7232,8 +7784,8 @@ public class AttributedExpressionAst : ExpressionAst, ISupportsAssignment, IAssi /// /// The extent of the expression, starting with the attribute and ending after the expression being attributed. /// - /// The attribute being applied to . - /// The expression being attributed by . + /// The attribute being applied to + /// The expression being attributed by /// /// If , , or is null. /// @@ -7254,15 +7806,15 @@ 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 + /// Copy the AttributedExpressionAst instance. /// public override Ast Copy() { @@ -7317,6 +7869,7 @@ private List GetAttributes() attributes.Add(childAttributeAst.Attribute); childAttributeAst = childAttributeAst.Child as AttributedExpressionAst; } + attributes.Reverse(); return attributes; } @@ -7336,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)); } @@ -7376,7 +7928,7 @@ public ConvertExpressionAst(IScriptExtent extent, TypeConstraintAst typeConstrai public TypeConstraintAst Type { get { return (TypeConstraintAst)Attribute; } } /// - /// Copy the ConvertExpressionAst instance + /// Copy the ConvertExpressionAst instance. /// public override Ast Copy() { @@ -7387,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 { @@ -7419,14 +7971,14 @@ 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 // the child as the assignable value. return varExpr; } + return this; } @@ -7445,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 @@ -7474,29 +8026,60 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma this.Static = @static; } + /// + /// Initializes a new instance of the class. + /// + /// + /// The extent of the expression, starting with the expression before the operator '.', '::' or '?.' and ending after + /// membername or expression naming the member. + /// + /// The expression before the member access operator '.', '::' or '?.'. + /// The name or expression naming the member to access. + /// True if the '::' operator was used, false if '.' or '?.' is used. + /// True if '?.' used. + /// + /// If , , or is null. + /// + public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static, bool nullConditional) + : this(extent, expression, member, @static) + { + this.NullConditional = nullConditional; + } + /// /// 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; } /// - /// Copy the MemberExpressionAst instance + /// Gets a value indicating true if the operator used is ?. or ?[]. + /// + public bool NullConditional { get; protected set; } + + /// + /// Copy the MemberExpressionAst instance. /// public override Ast Copy() { var newExpression = CopyElement(this.Expression); var newMember = CopyElement(this.Member); - return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static); + + return new MemberExpressionAst( + this.Extent, + newExpression, + newMember, + this.Static, + this.NullConditional); } #region Visitors @@ -7532,22 +8115,29 @@ 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 /// closing paren after the arguments. /// - /// The expression before the invocation operator ('.' or '::'). + /// 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 '.'. /// + /// 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()) @@ -7555,22 +8145,125 @@ 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) + { + } + + /// + /// 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 '?.'. + /// 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, + 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 + /// Copy the InvokeMemberExpressionAst instance. /// 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); + + return new InvokeMemberExpressionAst( + this.Extent, + newExpression, + newMethod, + newArguments, + this.Static, + this.NullConditional, + this.GenericTypeArguments); } #region Visitors @@ -7604,6 +8297,7 @@ internal AstVisitAction InternalVisitChildren(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return action; } @@ -7649,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) @@ -7665,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 { /// @@ -7687,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. @@ -7702,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; /// @@ -7753,11 +8452,10 @@ public TypeName(IScriptExtent extent, string name) var c = name[0]; if (c == '[' || c == ']' || c == ',') { - throw PSTraceSource.NewArgumentException("name"); + throw PSTraceSource.NewArgumentException(nameof(name)); } - int backtick = name.IndexOf('`'); - if (backtick != -1) + if (name.Contains('`')) { name = name.Replace("``", "`"); } @@ -7783,11 +8481,29 @@ public TypeName(IScriptExtent extent, string name, string assembly) { if (string.IsNullOrEmpty(assembly)) { - throw PSTraceSource.NewArgumentNullException("assembly"); + throw PSTraceSource.NewArgumentNullException(nameof(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. /// @@ -7828,18 +8544,19 @@ internal bool HasDefaultCtor() // we are pessimistic about default ctor presence. return false; } + return reflectionType.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) { // TODO: add check for default values, once default values for parameters supported - if (!function.Parameters.Any()) + if (function.Parameters.Count == 0) { return true; } @@ -7863,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 @@ -7878,9 +8612,11 @@ public Type GetReflectionType() Diagnostics.Assert(_typeDefinitionAst != null, "_typeDefinitionAst can never be null"); return type; } + Interlocked.CompareExchange(ref _type, type, null); } } + return _type; } @@ -7897,13 +8633,18 @@ 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)) { result = null; } } + return result; } @@ -7925,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)) @@ -7969,14 +8709,16 @@ internal bool IsType(Type type) int lastDotIndex = fullTypeName.LastIndexOf('.'); if (lastDotIndex >= 0) { - return fullTypeName.Substring(lastDotIndex + 1).Equals(Name, StringComparison.OrdinalIgnoreCase); + return fullTypeName.AsSpan(lastDotIndex + 1).Equals(Name, StringComparison.OrdinalIgnoreCase); } + return false; } Type ISupportsTypeCaching.CachedType { get { return _type; } + set { _type = value; } } } @@ -8012,9 +8754,10 @@ public GenericTypeName(IScriptExtent extent, ITypeName genericTypeName, IEnumera { throw PSTraceSource.NewArgumentNullException(extent == null ? "extent" : "genericTypeName"); } + if (genericArguments == null) { - throw PSTraceSource.NewArgumentException("genericArguments"); + throw PSTraceSource.NewArgumentException(nameof(genericArguments)); } Extent = extent; @@ -8023,7 +8766,7 @@ public GenericTypeName(IScriptExtent extent, ITypeName genericTypeName, IEnumera if (this.GenericArguments.Count == 0) { - throw PSTraceSource.NewArgumentException("genericArguments"); + throw PSTraceSource.NewArgumentException(nameof(genericArguments)); } } @@ -8047,9 +8790,11 @@ public string FullName { sb.Append(','); } + first = false; sb.Append(typename.FullName); } + sb.Append(']'); var assemblyName = TypeName.AssemblyName; if (assemblyName != null) @@ -8057,8 +8802,10 @@ public string FullName sb.Append(','); sb.Append(assemblyName); } + Interlocked.CompareExchange(ref _cachedFullName, sb.ToString(), null); } + return _cachedFullName; } } @@ -8081,9 +8828,11 @@ public string Name { sb.Append(','); } + first = false; sb.Append(typename.Name); } + sb.Append(']'); return sb.ToString(); } @@ -8107,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. @@ -8128,7 +8877,7 @@ public Type GetReflectionType() { Type generic = GetGenericType(TypeName.GetReflectionType()); - if (generic != null && generic.GetTypeInfo().ContainsGenericParameters) + if (generic != null && generic.ContainsGenericParameters) { var argumentList = new List(); foreach (var arg in GenericArguments) @@ -8157,6 +8906,7 @@ public Type GetReflectionType() { return type; } + Interlocked.CompareExchange(ref _cachedType, type, null); } catch (Exception) @@ -8165,22 +8915,28 @@ public Type GetReflectionType() } } } + return _cachedType; } /// - /// Get the actual generic type if it's necessary + /// Get the actual generic type if it's necessary. /// /// /// internal Type GetGenericType(Type generic) { - if (generic == null || !generic.GetTypeInfo().ContainsGenericParameters) + if (generic == null || !generic.ContainsGenericParameters) { - if (TypeName.FullName.IndexOf("`", StringComparison.OrdinalIgnoreCase) == -1) + 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(); } } @@ -8202,24 +8958,28 @@ public Type GetReflectionAttributeType() if (type == null) { Type generic = TypeName.GetReflectionAttributeType(); - TypeInfo genericTypeInfo = (generic != null) ? generic.GetTypeInfo() : null; - if (genericTypeInfo == null || !genericTypeInfo.ContainsGenericParameters) + if (generic == null || !generic.ContainsGenericParameters) { - if (TypeName.FullName.IndexOf("`", StringComparison.OrdinalIgnoreCase) == -1) + 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(); - genericTypeInfo = (generic != null) ? generic.GetTypeInfo() : null; } } - if (genericTypeInfo != null && genericTypeInfo.ContainsGenericParameters) + if (generic != null && generic.ContainsGenericParameters) { - type = genericTypeInfo.MakeGenericType((from arg in GenericArguments select arg.GetReflectionType()).ToArray()); + type = generic.MakeGenericType((from arg in GenericArguments select arg.GetReflectionType()).ToArray()); Interlocked.CompareExchange(ref _cachedType, type, null); } } + return type; } @@ -8234,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)) @@ -8263,12 +9022,14 @@ public override int GetHashCode() { hash = Utils.CombineHashCodes(hash, GenericArguments[i].GetHashCode()); } + return hash; } Type ISupportsTypeCaching.CachedType { get { return _cachedType; } + set { _cachedType = value; } } } @@ -8302,7 +9063,7 @@ public ArrayTypeName(IScriptExtent extent, ITypeName elementType, int rank) if (rank <= 0) { - throw PSTraceSource.NewArgumentException("rank"); + throw PSTraceSource.NewArgumentException(nameof(rank)); } Extent = extent; @@ -8323,6 +9084,7 @@ private string GetName(bool includeAssemblyName) { sb.Append(',', Rank - 1); } + sb.Append(']'); if (includeAssemblyName) { @@ -8353,6 +9115,7 @@ public string FullName { Interlocked.CompareExchange(ref _cachedFullName, GetName(includeAssemblyName: true), null); } + return _cachedFullName; } } @@ -8383,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. @@ -8424,6 +9187,7 @@ public Type GetReflectionType() { return type; } + Interlocked.CompareExchange(ref _cachedType, type, null); } } @@ -8455,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; @@ -8471,6 +9234,7 @@ public override int GetHashCode() Type ISupportsTypeCaching.CachedType { get { return _cachedType; } + set { _cachedType = value; } } } @@ -8493,7 +9257,7 @@ public ReflectionTypeName(Type type) { if (type == null) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } _type = type; @@ -8512,7 +9276,7 @@ public ReflectionTypeName(Type type) /// /// The name of the assembly. /// - public string AssemblyName { get { return _type.GetTypeInfo().Assembly.FullName; } } + public string AssemblyName { get { return _type.Assembly.FullName; } } /// /// Returns true if the type is an array, false otherwise. @@ -8522,7 +9286,7 @@ public ReflectionTypeName(Type type) /// /// Returns true if the type is a generic, false otherwise. /// - public bool IsGeneric { get { return _type.GetTypeInfo().IsGenericType; } } + public bool IsGeneric { get { return _type.IsGenericType; } } /// /// The extent of the typename. @@ -8556,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; } @@ -8571,6 +9334,7 @@ public override int GetHashCode() Type ISupportsTypeCaching.CachedType { get { return _type; } + set { throw new InvalidOperationException(); } } } @@ -8593,7 +9357,7 @@ public TypeExpressionAst(IScriptExtent extent, ITypeName typeName) { if (typeName == null) { - throw PSTraceSource.NewArgumentNullException("typeName"); + throw PSTraceSource.NewArgumentNullException(nameof(typeName)); } this.TypeName = typeName; @@ -8602,10 +9366,10 @@ 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 + /// Copy the TypeExpressionAst instance. /// public override Ast Copy() { @@ -8647,7 +9411,7 @@ public class VariableExpressionAst : ExpressionAst, ISupportsAssignment, IAssign /// The name of the variable. A leading '$' or '@' is not removed, those characters are assumed to be part of /// the variable name. /// - /// True if splatting, like @PSBoundParameters, false otherwise, like $false. + /// True if splatting, like @PSBoundParameters, false otherwise, like $false /// /// If or is null, or if /// is an empty string. @@ -8658,7 +9422,7 @@ public VariableExpressionAst(IScriptExtent extent, string variableName, bool spl { if (string.IsNullOrEmpty(variableName)) { - throw PSTraceSource.NewArgumentNullException("variableName"); + throw PSTraceSource.NewArgumentNullException(nameof(variableName)); } this.VariablePath = new VariablePath(variableName); @@ -8685,8 +9449,9 @@ public VariableExpressionAst(IScriptExtent extent, VariablePath variablePath, bo { if (variablePath == null) { - throw PSTraceSource.NewArgumentNullException("variablePath"); + throw PSTraceSource.NewArgumentNullException(nameof(variablePath)); } + this.VariablePath = variablePath; this.Splatted = splatted; } @@ -8694,16 +9459,16 @@ 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 + /// Check if the variable is one of $true, $false and $null. /// /// /// True if it is a constant variable @@ -8720,11 +9485,12 @@ public bool IsConstantVariable() return true; } } + return false; } /// - /// Copy the VariableExpressionAst instance + /// Copy the VariableExpressionAst instance. /// public override Ast Copy() { @@ -8774,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() @@ -8817,6 +9584,7 @@ Expression IAssignableValue.SetValue(Compiler compiler, Expression rhs) { rhs = DynamicExpression.Dynamic(PSVariableAssignmentBinder.Get(), typeof(object), rhs); } + rhs = rhs.Convert(targetType); if (!localInTuple) @@ -8885,10 +9653,10 @@ 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 + /// Copy the ConstantExpressionAst instance. /// public override Ast Copy() { @@ -8977,7 +9745,7 @@ public StringConstantExpressionAst(IScriptExtent extent, string value, StringCon { if (value == null) { - throw PSTraceSource.NewArgumentNullException("value"); + throw PSTraceSource.NewArgumentNullException(nameof(value)); } this.StringConstantType = stringConstantType; @@ -8992,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. @@ -9000,7 +9768,7 @@ internal StringConstantExpressionAst(StringToken token) public new string Value { get { return (string)base.Value; } } /// - /// Copy the StringConstantExpressionAst instance + /// Copy the StringConstantExpressionAst instance. /// public override Ast Copy() { @@ -9030,6 +9798,7 @@ internal static StringConstantType MapTokenKindToStringConstantKind(Token token) case TokenKind.Generic: return StringConstantType.BareWord; } + throw PSTraceSource.NewInvalidOperationException(); } @@ -9076,18 +9845,17 @@ public ExpandableStringExpressionAst(IScriptExtent extent, { if (value == null) { - throw PSTraceSource.NewArgumentNullException("value"); + throw PSTraceSource.NewArgumentNullException(nameof(value)); } if (type != StringConstantType.DoubleQuoted && type != StringConstantType.DoubleQuotedHereString && type != StringConstantType.BareWord) { - throw PSTraceSource.NewArgumentException("type"); + throw PSTraceSource.NewArgumentException(nameof(type)); } 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; @@ -9106,6 +9874,7 @@ public ExpandableStringExpressionAst(IScriptExtent extent, { this.NestedExpressions[i].ClearParent(); } + SetParents(this.NestedExpressions); this.Value = value; @@ -9141,21 +9910,21 @@ 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 + /// Copy the ExpandableStringExpressionAst instance. /// public override Ast Copy() { @@ -9174,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 @@ -9197,6 +9966,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -9221,7 +9991,7 @@ public ScriptBlockExpressionAst(IScriptExtent extent, ScriptBlockAst scriptBlock { if (scriptBlock == null) { - throw PSTraceSource.NewArgumentNullException("scriptBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock)); } this.ScriptBlock = scriptBlock; @@ -9231,10 +10001,10 @@ 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 + /// Copy the ScriptBlockExpressionAst instance. /// public override Ast Copy() { @@ -9291,9 +10061,9 @@ public class ArrayLiteralAst : ExpressionAst, ISupportsAssignment public ArrayLiteralAst(IScriptExtent extent, IList elements) : base(extent) { - if (elements == null || !elements.Any()) + if (elements == null || elements.Count == 0) { - throw PSTraceSource.NewArgumentException("elements"); + throw PSTraceSource.NewArgumentException(nameof(elements)); } this.Elements = new ReadOnlyCollection(elements); @@ -9303,10 +10073,10 @@ 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 + /// Copy the ArrayLiteralAst instance. /// public override Ast Copy() { @@ -9340,6 +10110,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -9385,10 +10156,10 @@ 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 + /// Copy the HashtableAst instance. /// public override Ast Copy() { @@ -9416,7 +10187,6 @@ public override Ast Copy() // Indicates that this ast was constructed as part of a schematized object instead of just a plain hash literal. internal bool IsSchemaElement { get; set; } - #region Visitors internal override object Accept(ICustomAstVisitor visitor) @@ -9440,6 +10210,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) if (action != AstVisitAction.Continue) break; } } + return visitor.CheckForPostAction(this, action); } @@ -9465,7 +10236,7 @@ public ArrayExpressionAst(IScriptExtent extent, StatementBlockAst statementBlock { if (statementBlock == null) { - throw PSTraceSource.NewArgumentNullException("statementBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(statementBlock)); } this.SubExpression = statementBlock; @@ -9476,10 +10247,10 @@ 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 + /// Copy the ArrayExpressionAst instance. /// public override Ast Copy() { @@ -9531,7 +10302,7 @@ public ParenExpressionAst(IScriptExtent extent, PipelineBaseAst pipeline) { if (pipeline == null) { - throw PSTraceSource.NewArgumentNullException("pipeline"); + throw PSTraceSource.NewArgumentNullException(nameof(pipeline)); } this.Pipeline = pipeline; @@ -9542,10 +10313,10 @@ 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 + /// Copy the ParenExpressionAst instance. /// public override Ast Copy() { @@ -9597,7 +10368,7 @@ public SubExpressionAst(IScriptExtent extent, StatementBlockAst statementBlock) { if (statementBlock == null) { - throw PSTraceSource.NewArgumentNullException("statementBlock"); + throw PSTraceSource.NewArgumentNullException(nameof(statementBlock)); } this.SubExpression = statementBlock; @@ -9608,10 +10379,10 @@ 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 + /// Copy the SubExpressionAst instance. /// public override Ast Copy() { @@ -9657,7 +10428,7 @@ public UsingExpressionAst(IScriptExtent extent, ExpressionAst expressionAst) { if (expressionAst == null) { - throw PSTraceSource.NewArgumentNullException("expressionAst"); + throw PSTraceSource.NewArgumentNullException(nameof(expressionAst)); } RuntimeUsingIndex = -1; @@ -9669,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 @@ -9679,7 +10450,7 @@ internal int RuntimeUsingIndex } /// - /// Copy the UsingExpressionAst instance + /// Copy the UsingExpressionAst instance. /// public override Ast Copy() { @@ -9694,7 +10465,7 @@ public override Ast Copy() internal const string UsingPrefix = "__using_"; /// - /// Get the underlying "using variable" from a UsingExpressionAst + /// Get the underlying "using variable" from a UsingExpressionAst. /// /// /// A UsingExpressionAst @@ -9705,25 +10476,21 @@ 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("usingExpressionAst"); - } + ArgumentNullException.ThrowIfNull(usingExpressionAst); return ExtractUsingVariableImpl(usingExpressionAst); } /// - /// A UsingExpressionAst must contains a VariableExpressionAst + /// A UsingExpressionAst must contains a VariableExpressionAst. /// /// /// 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) @@ -9734,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) @@ -9746,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) @@ -9812,24 +10577,45 @@ public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, Expression SetParent(index); } + /// + /// Initializes a new instance of the class. + /// + /// The extent of the expression. + /// The expression being indexed. + /// The index expression. + /// Access the index only if the target is not null. + /// + /// If , , or is null. + /// + public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, ExpressionAst index, bool nullConditional) + : this(extent, target, index) + { + this.NullConditional = nullConditional; + } + /// /// 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; } /// - /// Copy the IndexExpressionAst instance + /// Copy the IndexExpressionAst instance. /// public override Ast Copy() { var newTarget = CopyElement(this.Target); var newIndex = CopyElement(this.Index); - return new IndexExpressionAst(this.Extent, newTarget, newIndex); + return new IndexExpressionAst(this.Extent, newTarget, newIndex, this.NullConditional); } #region Visitors diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 4d4940cc302..3cee7580ff9 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -201,7 +200,7 @@ public enum TokenKind /// The addition operator '+'. Plus = 40, - /// The substraction operator '-'. + /// The subtraction operator '-'. Minus = 41, /// The assignment operator '='. @@ -414,6 +413,21 @@ public enum TokenKind /// The PS class base class and implemented interfaces operator ':'. Also used in base class ctor calls. Colon = 99, + /// The ternary operator '?'. + QuestionMark = 100, + + /// The null conditional assignment operator '??='. + QuestionQuestionEquals = 101, + + /// The null coalesce operator '??'. + QuestionQuestion = 102, + + /// The null conditional member access operator '?.'. + QuestionDot = 103, + + /// The null conditional index access operator '?[]'. + QuestionLBracket = 104, + #endregion Operators #region Keywords @@ -571,6 +585,12 @@ public enum TokenKind /// The 'base' keyword Base = 168, + /// The 'default' keyword + Default = 169, + + /// The 'clean' keyword. + Clean = 170, + #endregion Keywords } @@ -590,46 +610,51 @@ public enum TokenFlags /// /// The precedence of the logical operators '-and', '-or', and '-xor'. /// - BinaryPrecedenceLogical = 1, + BinaryPrecedenceLogical = 0x1, /// /// The precedence of the bitwise operators '-band', '-bor', and '-bxor' /// - BinaryPrecedenceBitwise = 2, + BinaryPrecedenceBitwise = 0x2, /// /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', /// and all of the case sensitive variants of these operators, if they exists. /// - BinaryPrecedenceComparison = 3, + BinaryPrecedenceComparison = 0x5, + + /// + /// The precedence of null coalesce operator '??'. + /// + BinaryPrecedenceCoalesce = 0x7, /// /// The precedence of the binary operators '+' and '-'. /// - BinaryPrecedenceAdd = 4, + BinaryPrecedenceAdd = 0x9, /// /// The precedence of the operators '*', '/', and '%'. /// - BinaryPrecedenceMultiply = 5, + BinaryPrecedenceMultiply = 0xa, /// /// The precedence of the '-f' operator. /// - BinaryPrecedenceFormat = 6, + BinaryPrecedenceFormat = 0xc, /// /// The precedence of the '..' operator. /// - BinaryPrecedenceRange = 7, + BinaryPrecedenceRange = 0xd, #endregion Precedence Values /// /// A bitmask to get the precedence of binary operators. /// - BinaryPrecedenceMask = 0x00000007, + BinaryPrecedenceMask = 0x0000000f, /// /// The token is a keyword. @@ -637,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, @@ -656,7 +681,10 @@ public enum TokenFlags /// CaseSensitiveOperator = 0x00000400, - // Unused = 0x00000800, + /// + /// The token is a ternary operator '?'. + /// + TernaryOperator = 0x00000800, /// /// The operators '&', '|', and the member access operators ':' and '::'. @@ -664,7 +692,7 @@ public enum TokenFlags SpecialOperator = 0x00001000, /// - /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', or '%=' + /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '??=' /// AssignmentOperator = 0x00002000, @@ -774,8 +802,8 @@ public static class TokenTraits #region Flags for operators - /* AndAnd */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, - /* OrOr */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, + /* AndAnd */ TokenFlags.ParseModeInvariant, + /* OrOr */ TokenFlags.ParseModeInvariant, /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, @@ -848,11 +876,11 @@ public static class TokenTraits /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Reserved slot 2 */ TokenFlags.None, - /* Reserved slot 3 */ TokenFlags.None, - /* Reserved slot 4 */ TokenFlags.None, - /* Reserved slot 5 */ TokenFlags.None, - /* Reserved slot 6 */ TokenFlags.None, + /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce, + /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionLBracket */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, /* Reserved slot 8 */ TokenFlags.None, /* Reserved slot 9 */ TokenFlags.None, @@ -922,6 +950,8 @@ public static class TokenTraits /* Command */ TokenFlags.Keyword, /* Hidden */ TokenFlags.Keyword, /* Base */ TokenFlags.Keyword, + /* Default */ TokenFlags.Keyword, + /* Clean */ TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName, #endregion Flags for keywords }; @@ -1046,25 +1076,25 @@ public static class TokenTraits /* Shl */ "-shl", /* Shr */ "-shr", /* Colon */ ":", - /* Reserved slot 2 */ "", - /* Reserved slot 3 */ "", - /* Reserved slot 4 */ "", - /* Reserved slot 5 */ "", - /* Reserved slot 6 */ "", - /* Reserved slot 7 */ "", - /* Reserved slot 8 */ "", - /* Reserved slot 9 */ "", - /* Reserved slot 10 */ "", - /* Reserved slot 11 */ "", - /* Reserved slot 12 */ "", - /* Reserved slot 13 */ "", - /* Reserved slot 14 */ "", - /* Reserved slot 15 */ "", - /* Reserved slot 16 */ "", - /* Reserved slot 17 */ "", - /* Reserved slot 18 */ "", - /* Reserved slot 19 */ "", - /* Reserved slot 20 */ "", + /* QuestionMark */ "?", + /* QuestionQuestionEquals */ "??=", + /* QuestionQuestion */ "??", + /* QuestionDot */ "?.", + /* QuestionLBracket */ "?[", + /* Reserved slot 7 */ string.Empty, + /* Reserved slot 8 */ string.Empty, + /* Reserved slot 9 */ string.Empty, + /* Reserved slot 10 */ string.Empty, + /* Reserved slot 11 */ string.Empty, + /* Reserved slot 12 */ string.Empty, + /* Reserved slot 13 */ string.Empty, + /* Reserved slot 14 */ string.Empty, + /* Reserved slot 15 */ string.Empty, + /* Reserved slot 16 */ string.Empty, + /* Reserved slot 17 */ string.Empty, + /* Reserved slot 18 */ string.Empty, + /* Reserved slot 19 */ string.Empty, + /* Reserved slot 20 */ string.Empty, #endregion Text for operators @@ -1120,6 +1150,8 @@ public static class TokenTraits /* Command */ "command", /* Hidden */ "hidden", /* Base */ "base", + /* Default */ "default", + /* Clean */ "clean", #endregion Text for keywords }; @@ -1127,10 +1159,12 @@ public static class TokenTraits #if DEBUG static TokenTraits() { - Diagnostics.Assert(s_staticTokenFlags.Length == ((int)TokenKind.Base + 1), - "Table size out of sync with enum - _staticTokenFlags"); - Diagnostics.Assert(s_tokenText.Length == ((int)TokenKind.Base + 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"); @@ -1146,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) { @@ -1154,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) { @@ -1168,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) { @@ -1237,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}>"); } } @@ -1256,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); } /// @@ -1298,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 ? ":" : ""); + return string.Format( + CultureInfo.InvariantCulture, + "{0}{1}: <-{2}{3}>", + StringUtil.Padding(indent), + Kind, + _parameterName, + _usedColon ? ":" : string.Empty); } } @@ -1322,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); } } @@ -1349,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); } } @@ -1404,6 +1459,7 @@ internal static void ToDebugString(ReadOnlyCollection nestedTokens, public ReadOnlyCollection NestedTokens { get { return _nestedTokens; } + internal set { _nestedTokens = value; } } @@ -1418,12 +1474,12 @@ internal override string ToDebugString(int indent) { ToDebugString(_nestedTokens, sb, indent); } + return sb.ToString(); } } /// - /// /// public class LabelToken : Token { @@ -1434,7 +1490,6 @@ internal LabelToken(InternalScriptExtent scriptExtent, TokenFlags tokenFlags, st } /// - /// /// public string LabelText { get; } } @@ -1476,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; } } /// @@ -1499,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 @@ -1515,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 c951d1f996a..e2aed94cc98 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -1,20 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using Microsoft.PowerShell.Commands; -using Microsoft.PowerShell.DesiredStateConfiguration.Internal; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Linq.Expressions; using System.Numerics; using System.Runtime.CompilerServices; 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 { /// @@ -23,15 +24,15 @@ namespace System.Management.Automation.Language public enum DynamicKeywordNameMode { /// - /// This keyword does not take a name value + /// This keyword does not take a name value. /// NoName = 0, /// - /// Name must be present and simple non-empty bare word + /// Name must be present and simple non-empty bare word. /// SimpleNameRequired = 1, /// - /// Name must be present but can also be an expression + /// Name must be present but can also be an expression. /// NameRequired = 2, /// @@ -39,33 +40,33 @@ public enum DynamicKeywordNameMode /// SimpleOptionalName = 3, /// - /// Name may be optionally present, expression or bare word + /// 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 + /// Defines the body mode for a dynamic keyword. It can be a scriptblock, hashtable or command which means no body. /// public enum DynamicKeywordBodyMode { /// - /// The keyword act like a command + /// The keyword act like a command. /// Command = 0, /// - /// The keyword has a scriptblock body + /// The keyword has a scriptblock body. /// ScriptBlock = 1, /// - /// The keyword has hashtable body + /// The keyword has hashtable body. /// Hashtable = 2, } /// /// Defines the schema/behaviour for a dynamic keyword. - /// a constrained + /// a constrained. /// public class DynamicKeyword { @@ -78,8 +79,7 @@ private static Dictionary DynamicKeywords { get { - return t_dynamicKeywords ?? - (t_dynamicKeywords = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return t_dynamicKeywords ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } @@ -87,17 +87,16 @@ private static Dictionary DynamicKeywords private static Dictionary t_dynamicKeywords; /// - /// stack of DynamicKeywords Cache + /// Stack of DynamicKeywords Cache. /// - /// private static Stack> DynamicKeywordsStack { get { - return t_dynamicKeywordsStack ?? - (t_dynamicKeywordsStack = new Stack>()); + return t_dynamicKeywordsStack ??= new Stack>(); } } + [ThreadStatic] private static Stack> t_dynamicKeywordsStack; @@ -110,7 +109,7 @@ public static void Reset() } /// - /// Push current dynamicKeywords cache into stack + /// Push current dynamicKeywords cache into stack. /// public static void Push() { @@ -119,7 +118,7 @@ public static void Push() } /// - /// Pop up previous dynamicKeywords cache + /// Pop up previous dynamicKeywords cache. /// public static void Pop() { @@ -127,7 +126,6 @@ public static void Pop() } /// - /// /// /// /// @@ -148,7 +146,6 @@ public static List GetKeyword() } /// - /// /// /// /// @@ -156,7 +153,7 @@ public static bool ContainsKeyword(string name) { if (string.IsNullOrEmpty(name)) { - PSArgumentNullException e = PSTraceSource.NewArgumentNullException("name"); + PSArgumentNullException e = PSTraceSource.NewArgumentNullException(nameof(name)); throw e; } @@ -164,14 +161,13 @@ public static bool ContainsKeyword(string name) } /// - /// /// /// public static void AddKeyword(DynamicKeyword keywordToAdd) { if (keywordToAdd == null) { - PSArgumentNullException e = PSTraceSource.NewArgumentNullException("keywordToAdd"); + PSArgumentNullException e = PSTraceSource.NewArgumentNullException(nameof(keywordToAdd)); throw e; } @@ -195,14 +191,15 @@ public static void RemoveKeyword(string name) { if (string.IsNullOrEmpty(name)) { - PSArgumentNullException e = PSTraceSource.NewArgumentNullException("name"); + PSArgumentNullException e = PSTraceSource.NewArgumentNullException(nameof(name)); throw e; } + DynamicKeyword.DynamicKeywords.Remove(name); } /// - /// Check if it is a hidden keyword + /// Check if it is a hidden keyword. /// /// /// @@ -210,7 +207,7 @@ internal static bool IsHiddenKeyword(string name) { if (string.IsNullOrEmpty(name)) { - PSArgumentNullException e = PSTraceSource.NewArgumentNullException("name"); + PSArgumentNullException e = PSTraceSource.NewArgumentNullException(nameof(name)); throw e; } @@ -227,9 +224,9 @@ internal static bool IsHiddenKeyword(string name) #endregion /// - /// Duplicates the DynamicKeyword + /// Duplicates the DynamicKeyword. /// - /// A copy of the DynamicKeyword + /// A copy of the DynamicKeyword. public DynamicKeyword Copy() { DynamicKeyword keyword = new DynamicKeyword() @@ -252,10 +249,12 @@ public DynamicKeyword Copy() { keyword.Properties.Add(entry.Key, entry.Value); } + foreach (KeyValuePair entry in this.Parameters) { keyword.Parameters.Add(entry.Key, entry.Value); } + return keyword; } @@ -271,17 +270,17 @@ public DynamicKeyword Copy() /// /// The keyword string - /// If an alias qualifier exist, use alias + /// If an alias qualifier exist, use alias. /// public string Keyword { get; set; } /// - /// The keyword resource name string + /// The keyword resource name string. /// public string ResourceName { get; set; } /// - /// Set to true if we should be looking for a scriptblock instead of a hashtable + /// Set to true if we should be looking for a scriptblock instead of a hashtable. /// public DynamicKeywordBodyMode BodyMode { get; set; } @@ -290,7 +289,6 @@ public DynamicKeyword Copy() /// rewrite the node as a simple direct function call. /// If NameMode is other than NoName, then the name of the instance /// will be passed as the parameter -InstanceName. - /// /// public bool DirectCall { get; set; } @@ -306,49 +304,49 @@ public DynamicKeyword Copy() public bool MetaStatement { get; set; } /// - /// Indicate that the keyword is reserved for future use by powershell + /// Indicate that the keyword is reserved for future use by powershell. /// public bool IsReservedKeyword { get; set; } /// - /// Contains the list of properties that are reserved for future use + /// Contains the list of properties that are reserved for future use. /// 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); } } + private Dictionary _parameters; /// /// A custom function that gets executed at parsing time before parsing dynamickeyword block - /// The delegate has one parameter: DynamicKeyword + /// The delegate has one parameter: DynamicKeyword. /// public Func PreParse { get; set; } /// - /// A custom function that gets executed at parsing time after parsing dynamickeyword block + /// A custom function that gets executed at parsing time after parsing dynamickeyword block. /// public Func PostParse { get; set; } @@ -358,7 +356,6 @@ public Dictionary Parameters public Func SemanticCheck { get; set; } } - internal static class DynamicKeywordExtension { internal static bool IsMetaDSCResource(this DynamicKeyword keyword) @@ -366,8 +363,17 @@ 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,13 +383,13 @@ 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"}}, + {@"Node", new List {@"Node"}}, }; /// - /// Get allowed keyword list for a given keyword + /// Get allowed keyword list for a given keyword. /// /// /// @@ -391,7 +397,7 @@ internal static bool IsCompatibleWithConfigurationType(this DynamicKeyword keywo internal static IEnumerable GetAllowedKeywords(this DynamicKeyword keyword, IEnumerable allowedKeywords) { string keywordName = keyword.Keyword; - if (String.Compare(keywordName, @"Node", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(keywordName, @"Node", StringComparison.OrdinalIgnoreCase)) { List excludeKeywords; if (s_excludeKeywords.TryGetValue(keywordName, out excludeKeywords)) @@ -401,6 +407,7 @@ internal static IEnumerable GetAllowedKeywords(this DynamicKeywo else return allowedKeywords; } + return null; } } @@ -411,22 +418,23 @@ internal static IEnumerable GetAllowedKeywords(this DynamicKeywo public class DynamicKeywordProperty { /// - /// The name of the property + /// The name of the property. /// public string Name { get; set; } /// - /// The required type of the property + /// The required type of the property. /// public string TypeConstraint { get; set; } /// - /// Any attributes that the property has + /// Any attributes that the property has. /// public List Attributes { - get { return _attributes ?? (_attributes = new List()); } + get { return _attributes ??= new List(); } } + private List _attributes; /// @@ -434,17 +442,19 @@ public List Attributes /// public List Values { - get { return _values ?? (_values = new List()); } + get { return _values ??= new List(); } } + private List _values; /// - /// Mapping the descriptive values to the actual values + /// Mapping the descriptive values to the actual values. /// public Dictionary ValueMap { - get { return _valueMap ?? (_valueMap = new Dictionary(StringComparer.OrdinalIgnoreCase)); } + get { return _valueMap ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } + private Dictionary _valueMap; /// @@ -458,7 +468,7 @@ public Dictionary ValueMap public bool IsKey { get; set; } /// - /// Indicates a range constraint on the property value + /// Indicates a range constraint on the property value. /// public Tuple Range { get; set; } } @@ -471,7 +481,7 @@ public Dictionary ValueMap public class DynamicKeywordParameter : DynamicKeywordProperty { /// - /// Type if this is a switch parameter and takes no argument + /// Type if this is a switch parameter and takes no argument. /// public bool Switch { get; set; } } @@ -484,6 +494,87 @@ internal enum TokenizerMode Signature, // i.e. class or method declaration } + /// + /// Indicates which suffix character(s) are present in the numeric literal being parsed by TryGetNumberValue. + /// + [Flags] + internal enum NumberSuffixFlags + { + /// + /// Indicates no suffix, a raw numeric literal. May be parsed as Int32, Int64, or Double. + /// + None = 0x0, + + /// + /// Indicates 'u' suffix for unsigned integers. May be parsed as UInt32 or UInt64, depending on the value. + /// + Unsigned = 0x1, + + /// + /// Indicates 'y' suffix for signed byte (sbyte) values. + /// + SignedByte = 0x2, + + /// + /// Indicates 'uy' suffix for unsigned byte values. + /// This is a compound value, representing both SignedByte and Unsigned flags being set. + /// + UnsignedByte = 0x3, + + /// + /// Indicates 's' suffix for short (Int16) integers. + /// + Short = 0x4, + + /// + /// Indicates 'us' suffix for ushort (UInt16) integers. + /// This is a compound flag value, representing both Unsigned and Short flags being set. + /// + UnsignedShort = 0x5, + + /// + /// Indicates 'l' suffix for long (Int64) integers. + /// + Long = 0x8, + + /// + /// Indicates 'ul' suffix for ulong (UInt64) integers. + /// This is a compound flag value, representing both Unsigned and Long flags being set. + /// + UnsignedLong = 0x9, + + /// + /// Indicates 'd' suffix for decimal (128-bit) real numbers. + /// + Decimal = 0x10, + + /// + /// Indicates 'N' suffix for BigInteger (arbitrarily large integer) numerals. + /// + BigInteger = 0x20 + } + + /// + /// Indicates the format of a numeric literal. + /// + internal enum NumberFormat + { + /// + /// Indicates standard decimal literal, no necessary prefix. + /// + Decimal = 0x0, + + /// + /// Indicates hexadecimal literal, with '0x' prefix. + /// + Hex = 0x1, + + /// + /// Indicates binary literal, with '0b' prefix. + /// + Binary = 0x2 + } + // // Class used to do a partial snapshot of the state of the tokenizer. // This is used for nested scans on the same string. @@ -494,6 +585,7 @@ internal class TokenizerState internal string Script; internal int TokenStart; internal int CurrentIndex; + internal Token FirstToken; internal Token LastToken; internal BitArray SkippedCharOffsets; internal List TokenList; @@ -504,6 +596,7 @@ internal class Tokenizer { private static readonly Dictionary s_keywordTable = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary s_operatorTable = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -542,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", /*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, /*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[] { @@ -621,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"); @@ -634,17 +727,36 @@ 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. + private bool _forceEndNumberOnTernaryOpChars; + + internal bool ForceEndNumberOnTernaryOpChars + { + get { return _forceEndNumberOnTernaryOpChars; } + set { _forceEndNumberOnTernaryOpChars = value; } + } + 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; } + private List RequiresTokens { get; set; } private bool InCommandMode() { return Mode == TokenizerMode.Command; } + private bool InExpressionMode() { return Mode == TokenizerMode.Expression; } + private bool InTypeNameMode() { return Mode == TokenizerMode.TypeName; } + private bool InSignatureMode() { return Mode == TokenizerMode.Signature; } internal void Initialize(string fileName, string input, List tokenList) @@ -668,20 +780,22 @@ internal void Initialize(string fileName, string input, List tokenList) { i += 1; } + lineStartMap.Add(i + 1); } + if (c == '\n') { lineStartMap.Add(i + 1); } } + _currentIndex = 0; Mode = TokenizerMode.Command; _positionHelper.LineStartMap = lineStartMap.ToArray(); } - internal TokenizerState StartNestedScan(UnscannedSubExprToken nestedText) { TokenizerState ts = new TokenizerState @@ -690,6 +804,7 @@ internal TokenizerState StartNestedScan(UnscannedSubExprToken nestedText) NestedTokensAdjustment = _nestedTokensAdjustment, Script = _script, TokenStart = _tokenStart, + FirstToken = FirstToken, LastToken = LastToken, SkippedCharOffsets = _skippedCharOffsets, TokenList = TokenList, @@ -711,6 +826,7 @@ internal void FinishNestedScan(TokenizerState ts) _nestedTokensAdjustment = ts.NestedTokensAdjustment; _script = ts.Script; _tokenStart = ts.TokenStart; + FirstToken = ts.FirstToken; LastToken = ts.LastToken; _skippedCharOffsets = ts.SkippedCharOffsets; TokenList = ts.TokenList; @@ -720,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. @@ -729,6 +845,7 @@ private char GetChar() { return '\0'; } + return _script[current]; } @@ -741,12 +858,13 @@ 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) { return '\0'; } + return _script[_currentIndex]; } @@ -768,19 +886,20 @@ internal static bool IsKeyword(string str) { return true; } + if (DynamicKeyword.ContainsKeyword(str) && !DynamicKeyword.IsHiddenKeyword(str)) { return true; } + return false; } - - internal void SkipNewlines(bool skipSemis, bool v3) + internal void SkipNewlines(bool skipSemis) { - // We normally don't create any tokens here, but the V2 tokenizer api returns newline tokens, - // so we create them when asked to create them. - + // We normally don't create any tokens in a Skip method, but the + // V2 tokenizer api returns newline, semi-colon, and line + // continuation tokens so we create them as they are encountered. again: char c = GetChar(); switch (c) @@ -796,25 +915,16 @@ internal void SkipNewlines(bool skipSemis, bool v3) case '\r': case '\n': - if (v3) _parser.NoteV3FeatureUsed(); - if (TokenList != null) - { - _tokenStart = _currentIndex - 1; - ScanNewline(c); - NewToken(TokenKind.NewLine); - } + ScanNewline(c); goto again; case ';': if (skipSemis) { - if (TokenList != null) - { - _tokenStart = _currentIndex - 1; - NewToken(TokenKind.Semi); - } + ScanSemicolon(); goto again; } + break; case '#': @@ -830,22 +940,23 @@ internal void SkipNewlines(bool skipSemis, bool v3) ScanBlockComment(); goto again; } + break; case '`': char c1 = GetChar(); if (c1 == '\n' || c1 == '\r') { - _tokenStart = _currentIndex - 2; - ScanNewline(c1); - NewToken(TokenKind.LineContinuation); + ScanLineContinuation(c1); goto again; } + if (char.IsWhiteSpace(c1)) { SkipWhiteSpace(); goto again; } + UngetChar(); break; @@ -855,8 +966,10 @@ internal void SkipNewlines(bool skipSemis, bool v3) SkipWhiteSpace(); goto again; } + break; } + UngetChar(); } @@ -869,10 +982,46 @@ private void SkipWhiteSpace() { break; } + SkipChar(); } } + private void ScanNewline(char c) + { + _tokenStart = _currentIndex - 1; + NormalizeCRLF(c); + + // Memory optimization: only create the token if it will be stored + if (TokenList != null) + { + NewToken(TokenKind.NewLine); + } + } + + private void ScanSemicolon() + { + _tokenStart = _currentIndex - 1; + + // Memory optimization: only create the token if it will be stored + if (TokenList != null) + { + NewToken(TokenKind.Semi); + } + } + + private void ScanLineContinuation(char c) + { + _tokenStart = _currentIndex - 2; + NormalizeCRLF(c); + + // Memory optimization: only create the token if it will be stored + if (TokenList != null) + { + NewToken(TokenKind.LineContinuation); + } + } + internal int GetRestorePoint() { _tokenStart = _currentIndex; @@ -900,12 +1049,13 @@ internal void Resync(int start) } } } + _currentIndex = start - adjustment; if (_currentIndex > _script.Length + 1) { _currentIndex = _script.Length + 1; } - else if (0 > _currentIndex) + else if (_currentIndex < 0) { _currentIndex = 0; } @@ -936,6 +1086,7 @@ internal void RemoveTokensFromListDuringResync(List tokenList, int start) { i -= 1; } + for (; i >= 0; i--) { if (((InternalScriptExtent)tokenList[i].Extent).EndOffset <= start) @@ -960,6 +1111,7 @@ internal void ReplaceSavedTokens(Token firstOldToken, Token lastOldToken, Token lastTokenToReplace = i; continue; } + if (((InternalScriptExtent)TokenList[i].Extent).StartOffset == startOffset) { TokenList.RemoveRange(i, lastTokenToReplace - i + 1); @@ -969,8 +1121,9 @@ internal void ReplaceSavedTokens(Token firstOldToken, Token lastOldToken, Token } } - private void ScanNewline(char c) + private void NormalizeCRLF(char c) { + // CRs in Windows line endings are ignored if (c == '\r' && PeekChar() == '\n') { SkipChar(); @@ -984,38 +1137,40 @@ internal void CheckAstIsBeforeSignature(Ast ast) if (_beginSignatureExtent.StartOffset < ast.Extent.StartOffset) { - ReportError(ast.Extent, () => ParserStrings.TokenAfterEndOfValidScriptText); + ReportError(ast.Extent, + nameof(ParserStrings.TokenAfterEndOfValidScriptText), + ParserStrings.TokenAfterEndOfValidScriptText); } } - private void ReportError(int errorOffset, Expression> message, params object[] args) + private void ReportError(int errorOffset, string errorId, string errorMsg, params object[] args) { - _parser.ReportError(NewScriptExtent(errorOffset, errorOffset + 1), message, args); + _parser.ReportError(NewScriptExtent(errorOffset, errorOffset + 1), errorId, errorMsg, args); } - private void ReportError(IScriptExtent extent, Expression> message) + private void ReportError(IScriptExtent extent, string errorId, string errorMsg) { - _parser.ReportError(extent, message); + _parser.ReportError(extent, errorId, errorMsg); } - private void ReportError(IScriptExtent extent, Expression> message, object arg) + private void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg) { - _parser.ReportError(extent, message, arg); + _parser.ReportError(extent, errorId, errorMsg, arg); } - private void ReportError(IScriptExtent extent, Expression> message, object arg1, object arg2) + private void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg1, object arg2) { - _parser.ReportError(extent, message, arg1, arg2); + _parser.ReportError(extent, errorId, errorMsg, arg1, arg2); } - private void ReportIncompleteInput(int errorOffset, Expression> message) + private void ReportIncompleteInput(int errorOffset, string errorId, string errorMsg) { - _parser.ReportIncompleteInput(NewScriptExtent(errorOffset, _currentIndex), message); + _parser.ReportIncompleteInput(NewScriptExtent(errorOffset, _currentIndex), errorId, errorMsg); } - private void ReportIncompleteInput(int errorOffset, Expression> message, object arg) + private void ReportIncompleteInput(int errorOffset, string errorId, string errorMsg, object arg) { - _parser.ReportIncompleteInput(NewScriptExtent(errorOffset, _currentIndex), message, arg); + _parser.ReportIncompleteInput(NewScriptExtent(errorOffset, _currentIndex), errorId, errorMsg, arg); } private InternalScriptExtent NewScriptExtent(int start, int end) @@ -1038,6 +1193,7 @@ internal InternalScriptExtent CurrentExtent() end += 1; } } + for (; i < end && i < _skippedCharOffsets.Length; ++i) { if (_skippedCharOffsets[i]) @@ -1046,6 +1202,7 @@ internal InternalScriptExtent CurrentExtent() } } } + return new InternalScriptExtent(_positionHelper, start, end); } @@ -1061,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 $^. @@ -1077,13 +1231,12 @@ 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; } + return token; } @@ -1120,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; } @@ -1153,6 +1306,7 @@ private Token NewFileRedirectionToken(int from, bool append, bool fromSpecifiedE { UngetChar(); } + return NewNumberToken(from); } @@ -1199,9 +1353,83 @@ private bool OnlyWhitespaceOrCommentsAfterExtent(InternalScriptExtent extent) return false; } } + return true; } + internal bool IsPipeContinuation(IScriptExtent extent) + { + // If the first non-whitespace & non-comment (regular or block) character following a newline is a pipe, we have + // pipe continuation. + return extent.EndOffset < _script.Length && ContinuationAfterExtent(extent, continuationChar: '|'); + } + + private bool ContinuationAfterExtent(IScriptExtent extent, char continuationChar) + { + bool lastNonWhitespaceIsNewline = true; + int i = extent.EndOffset; + + // Since some token pattern matching looks for multiple characters (e.g. newline or block comment) + // we stop searching at _script.Length - 1 and perform one additional check after the while loop. + // This avoids having to compare i + 1 against the script length in multiple locations inside the + // loop. + while (i < _script.Length - 1) + { + char c = _script[i]; + + if (c.IsWhitespace()) + { + i++; + continue; + } + + if (c == '\n') + { + if (lastNonWhitespaceIsNewline) + { + // blank or whitespace-only lines are not allowed in automatic line continuation + return false; + } + + lastNonWhitespaceIsNewline = true; + i++; + continue; + } + else if (c == '\r') + { + if (lastNonWhitespaceIsNewline) + { + // blank or whitespace-only lines are not allowed in automatic line continuation + return false; + } + + lastNonWhitespaceIsNewline = true; + i += _script[i + 1] == '\n' ? 2 : 1; + continue; + } + + lastNonWhitespaceIsNewline = false; + + if (c == '#') + { + // SkipLineComment will return the position after the comment end + // which is either at the end of the file, or a cr or lf. + i = SkipLineComment(i + 1); + continue; + } + + if (c == '<' && _script[i + 1] == '#') + { + i = SkipBlockComment(i + 2); + continue; + } + + return c == continuationChar; + } + + return _script[_script.Length - 1] == continuationChar; + } + private int SkipLineComment(int i) { for (; i < _script.Length; ++i) @@ -1238,17 +1466,28 @@ private char Backtick(char c, out char surrogateCharacter) switch (c) { - case '0': return '\0'; - case 'a': return '\a'; - case 'b': return '\b'; - case 'e': return '\u001b'; - case 'f': return '\f'; - case 'n': return '\n'; - case 'r': return '\r'; - case 't': return '\t'; - case 'u': return ScanUnicodeEscape(out surrogateCharacter); - case 'v': return '\v'; - default: return c; + case '0': + return '\0'; + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'e': + return '\u001b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'u': + return ScanUnicodeEscape(out surrogateCharacter); + case 'v': + return '\v'; + default: + return c; } } @@ -1263,7 +1502,9 @@ private char ScanUnicodeEscape(out char surrogateCharacter) UngetChar(); IScriptExtent errorExtent = NewScriptExtent(escSeqStartIndex, _currentIndex); - ReportError(errorExtent, () => ParserStrings.InvalidUnicodeEscapeSequence); + ReportError(errorExtent, + nameof(ParserStrings.InvalidUnicodeEscapeSequence), + ParserStrings.InvalidUnicodeEscapeSequence); return s_invalidChar; } @@ -1282,7 +1523,9 @@ private char ScanUnicodeEscape(out char surrogateCharacter) // Sequence must have at least one hex char. Release(sb); IScriptExtent errorExtent = NewScriptExtent(escSeqStartIndex, _currentIndex); - ReportError(errorExtent, () => ParserStrings.InvalidUnicodeEscapeSequence); + ReportError(errorExtent, + nameof(ParserStrings.InvalidUnicodeEscapeSequence), + ParserStrings.InvalidUnicodeEscapeSequence); return s_invalidChar; } @@ -1293,17 +1536,29 @@ private char ScanUnicodeEscape(out char surrogateCharacter) UngetChar(); Release(sb); - ReportError(_currentIndex, - i < s_maxNumberOfUnicodeHexDigits - ? (Expression>)(() => ParserStrings.InvalidUnicodeEscapeSequence) - : () => ParserStrings.MissingUnicodeEscapeSequenceTerminator); + if (i < s_maxNumberOfUnicodeHexDigits) + { + ReportError(_currentIndex, + nameof(ParserStrings.InvalidUnicodeEscapeSequence), + ParserStrings.InvalidUnicodeEscapeSequence); + } + else + { + ReportError(_currentIndex, + nameof(ParserStrings.MissingUnicodeEscapeSequenceTerminator), + ParserStrings.MissingUnicodeEscapeSequenceTerminator); + } + return s_invalidChar; } - else if (i == s_maxNumberOfUnicodeHexDigits) { + else if (i == s_maxNumberOfUnicodeHexDigits) + { UngetChar(); Release(sb); - ReportError(_currentIndex, () => ParserStrings.TooManyDigitsInUnicodeEscapeSequence); + ReportError(_currentIndex, + nameof(ParserStrings.TooManyDigitsInUnicodeEscapeSequence), + ParserStrings.TooManyDigitsInUnicodeEscapeSequence); return s_invalidChar; } @@ -1313,7 +1568,7 @@ private char ScanUnicodeEscape(out char surrogateCharacter) string hexStr = GetStringAndRelease(sb); uint unicodeValue = uint.Parse(hexStr, NumberStyles.AllowHexSpecifier, NumberFormatInfo.InvariantInfo); - if (unicodeValue <= Char.MaxValue) + if (unicodeValue <= char.MaxValue) { return ((char)unicodeValue); } @@ -1325,7 +1580,9 @@ private char ScanUnicodeEscape(out char surrogateCharacter) { // Place the error indicator under only the hex digits in the esc sequence. IScriptExtent errorExtent = NewScriptExtent(escSeqStartIndex + 3, _currentIndex - 1); - ReportError(errorExtent, () => ParserStrings.InvalidUnicodeEscapeSequenceValue); + ReportError(errorExtent, + nameof(ParserStrings.InvalidUnicodeEscapeSequenceValue), + ParserStrings.InvalidUnicodeEscapeSequenceValue); return s_invalidChar; } } @@ -1424,11 +1681,15 @@ private void ScanToEndOfCommentLine(out bool sawBeginSig, out bool matchedRequir { goto case '\n'; } + goto default; case '\r': case '\n': UngetChar(); + // Detect a line comment that disguises itself to look like the beginning of a signature block. + // This could be used to hide code at the bottom of a script, since people might assume there is nothing else after the signature. + // // The token similarity threshold was chosen by instrumenting the tokenizer and // analyzing every comment from PoshCode, Technet Script Center, and Windows. // @@ -1445,15 +1706,16 @@ private void ScanToEndOfCommentLine(out bool sawBeginSig, out bool matchedRequir // // There were only 279 (out of 269,387) comments with a similarity of 11,12,13,14, or 15. // At a similarity of 16-77, there were thousands of comments per similarity bucket. - // - // System.IO.File.AppendAllText(@"c:\temp\signature_similarity.txt", "" + sawBeginTokenSimilarity + ":" + commentLineComparison); const string beginSignatureTextNoSpace = "sig#beginsignatureblock\n"; const int beginTokenSimilarityThreshold = 10; - // Quick exit - the comment line is more than 'threshold' longer. Therefore, + const int beginTokenSimilarityUpperBound = 34; // beginSignatureTextNoSpace.Length + beginTokenSimilarityThreshold + const int beginTokenSimilarityLowerBound = 14; // beginSignatureTextNoSpace.Length - beginTokenSimilarityThreshold + + // Quick exit - the comment line is more than 'threshold' longer, or is less than 'threshold' shorter. Therefore, // its similarity will be over the threshold. - if (commentLine.Length > (beginSignatureTextNoSpace.Length + beginTokenSimilarityThreshold)) + if (commentLine.Length > beginTokenSimilarityUpperBound || commentLine.Length < beginTokenSimilarityLowerBound) { sawBeginSig = false; } @@ -1465,10 +1727,20 @@ private void ScanToEndOfCommentLine(out bool sawBeginSig, out bool matchedRequir // // The average script is 14% comments and parses in about 5.05 ms with this algorithm, // about 4.45 ms with the more simplistic algorithm. - // + string commentLineComparison = commentLine.ToString().ToLowerInvariant(); + if (_beginTokenSimilarity2dArray == null) + { + // Create the 2 dimensional array for edit distance calculation if it hasn't been created yet. + _beginTokenSimilarity2dArray = new int[beginTokenSimilarityUpperBound + 1, beginSignatureTextNoSpace.Length + 1]; + } + else + { + // Zero out the 2 dimensional array before using it. + Array.Clear(_beginTokenSimilarity2dArray, 0, _beginTokenSimilarity2dArray.Length); + } - int sawBeginTokenSimilarity = GetStringSimilarity(commentLineComparison, beginSignatureTextNoSpace); + int sawBeginTokenSimilarity = GetStringSimilarity(commentLineComparison, beginSignatureTextNoSpace, _beginTokenSimilarity2dArray); sawBeginSig = sawBeginTokenSimilarity < beginTokenSimilarityThreshold; } @@ -1485,6 +1757,9 @@ private void ScanToEndOfCommentLine(out bool sawBeginSig, out bool matchedRequir #region Object reuse + // A two-dimensional integer array reused for calculating string similarity. + private int[,] _beginTokenSimilarity2dArray; + private readonly Queue _stringBuilders = new Queue(); private StringBuilder GetStringBuilder() @@ -1526,8 +1801,7 @@ private void ScanLineComment() } else if (matchedRequires && _nestedTokensAdjustment == 0) { - if (RequiresTokens == null) - RequiresTokens = new List(); + RequiresTokens ??= new List(); RequiresTokens.Add(token); } } @@ -1544,14 +1818,17 @@ private void ScanBlockComment() SkipChar(); break; } - if (c == '\r' || c == '\n') + + if (c == '\r') { - ScanNewline(c); + NormalizeCRLF(c); } else if (c == '\0' && AtEof()) { UngetChar(); - ReportIncompleteInput(errorIndex, () => ParserStrings.MissingTerminatorMultiLineComment); + ReportIncompleteInput(errorIndex, + nameof(ParserStrings.MissingTerminatorMultiLineComment), + ParserStrings.MissingTerminatorMultiLineComment); break; } } @@ -1560,15 +1837,15 @@ private void ScanBlockComment() } // Implementation of the Levenshtein Distance algorithm - // http://en.wikipedia.org/wiki/Levenshtein_distance - private static int GetStringSimilarity(string first, string second) + // https://en.wikipedia.org/wiki/Levenshtein_distance + private static int GetStringSimilarity(string first, string second, int[,] distanceMap = null) { Diagnostics.Assert(!string.IsNullOrEmpty(first) && !string.IsNullOrEmpty(second), "Caller never calls us with empty strings"); // Store a distance map to store the number of edits required to // convert the first letters of First to the first // letters of Second. - int[,] distanceMap = new int[first.Length + 1, second.Length + 1]; + distanceMap ??= new int[first.Length + 1, second.Length + 1]; // Initialize the first row and column of the matrix - the number // of edits required when one of the strings is empty is just @@ -1647,7 +1924,9 @@ internal ScriptRequirements GetScriptRequirements() var commandName = commandAst.GetCommandName(); if (!string.Equals(commandName, "requires", StringComparison.OrdinalIgnoreCase)) { - ReportError(commandAst.Extent, () => DiscoveryExceptions.ScriptRequiresInvalidFormat); + ReportError(commandAst.Extent, + nameof(DiscoveryExceptions.ScriptRequiresInvalidFormat), + DiscoveryExceptions.ScriptRequiresInvalidFormat); } var snapinSpecified = false; @@ -1659,10 +1938,8 @@ internal ScriptRequirements GetScriptRequirements() PSSnapinToken.StartsWith(parameter.ParameterName, StringComparison.OrdinalIgnoreCase)) { snapinSpecified = true; - if (requiredSnapins == null) - { - requiredSnapins = new List(); - } + requiredSnapins ??= new List(); + break; } } @@ -1678,9 +1955,12 @@ internal ScriptRequirements GetScriptRequirements() } else { - ReportError(commandAst.CommandElements[i].Extent, () => DiscoveryExceptions.ScriptRequiresInvalidFormat); + ReportError(commandAst.CommandElements[i].Extent, + nameof(DiscoveryExceptions.ScriptRequiresInvalidFormat), + DiscoveryExceptions.ScriptRequiresInvalidFormat); } } + if (snapinName != null) { Diagnostics.Assert(PSSnapInInfo.IsPSSnapinIdValid(snapinName), "we shouldn't set snapinName if it wasn't valid"); @@ -1696,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, @@ -1737,21 +2014,30 @@ private void HandleRequiresParameter(CommandParameterAst parameter, requiresElevation = true; if (argumentAst != null) { - ReportError(parameter.Extent, () => ParserStrings.ParameterCannotHaveArgument, parameter.ParameterName); + ReportError(parameter.Extent, + nameof(ParserStrings.ParameterCannotHaveArgument), + ParserStrings.ParameterCannotHaveArgument, + parameter.ParameterName); } + return; } if (argumentAst == null) { - ReportError(parameter.Extent, () => ParserStrings.ParameterRequiresArgument, parameter.ParameterName); + ReportError(parameter.Extent, + nameof(ParserStrings.ParameterRequiresArgument), + ParserStrings.ParameterRequiresArgument, + parameter.ParameterName); return; } object argumentValue; if (!IsConstantValueVisitor.IsConstant(argumentAst, out argumentValue, forRequires: true)) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresArgumentMustBeConstant); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresArgumentMustBeConstant), + ParserStrings.RequiresArgumentMustBeConstant); return; } @@ -1759,31 +2045,51 @@ private void HandleRequiresParameter(CommandParameterAst parameter, { if (requiredShellId != null) { - ReportError(parameter.Extent, () => ParameterBinderStrings.ParameterAlreadyBound, null, shellIDToken); + ReportError(parameter.Extent, + nameof(ParameterBinderStrings.ParameterAlreadyBound), + ParameterBinderStrings.ParameterAlreadyBound, + null, + shellIDToken); return; } - if (!(argumentValue is string)) + + if (argumentValue is not string) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresInvalidStringArgument, shellIDToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresInvalidStringArgument), + ParserStrings.RequiresInvalidStringArgument, + shellIDToken); return; } + requiredShellId = (string)argumentValue; } else if (PSSnapinToken.StartsWith(parameter.ParameterName, StringComparison.OrdinalIgnoreCase)) { - if (!(argumentValue is string)) + if (argumentValue is not string) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresInvalidStringArgument, PSSnapinToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresInvalidStringArgument), + ParserStrings.RequiresInvalidStringArgument, + PSSnapinToken); return; } + if (snapinName != null) { - ReportError(parameter.Extent, () => ParameterBinderStrings.ParameterAlreadyBound, null, PSSnapinToken); + ReportError(parameter.Extent, + nameof(ParameterBinderStrings.ParameterAlreadyBound), + ParameterBinderStrings.ParameterAlreadyBound, + null, + PSSnapinToken); return; } + if (!PSSnapInInfo.IsPSSnapinIdValid((string)argumentValue)) { - ReportError(argumentAst.Extent, () => MshSnapInCmdletResources.InvalidPSSnapInName); + ReportError(argumentAst.Extent, + nameof(MshSnapInCmdletResources.InvalidPSSnapInName), + MshSnapInCmdletResources.InvalidPSSnapInName); return; } @@ -1793,11 +2099,15 @@ private void HandleRequiresParameter(CommandParameterAst parameter, { if (requiredEditions != null) { - ReportError(parameter.Extent, () => ParameterBinderStrings.ParameterAlreadyBound, null, editionToken); + ReportError(parameter.Extent, + nameof(ParameterBinderStrings.ParameterAlreadyBound), + ParameterBinderStrings.ParameterAlreadyBound, + null, + editionToken); return; } - if (argumentValue is string || !(argumentValue is IEnumerable)) + if (argumentValue is string || argumentValue is not IEnumerable) { requiredEditions = HandleRequiresPSEditionArgument(argumentAst, argumentValue, ref requiredEditions); } @@ -1815,7 +2125,9 @@ private void HandleRequiresParameter(CommandParameterAst parameter, var version = Utils.StringToVersion(argumentText); if (version == null) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresVersionInvalid); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresVersionInvalid), + ParserStrings.RequiresVersionInvalid); return; } @@ -1823,24 +2135,34 @@ private void HandleRequiresParameter(CommandParameterAst parameter, { if (snapinVersion != null) { - ReportError(parameter.Extent, () => ParameterBinderStrings.ParameterAlreadyBound, null, versionToken); + ReportError(parameter.Extent, + nameof(ParameterBinderStrings.ParameterAlreadyBound), + ParameterBinderStrings.ParameterAlreadyBound, + null, + versionToken); return; } + snapinVersion = version; } else { if (requiredVersion != null && !requiredVersion.Equals(version)) { - ReportError(parameter.Extent, () => ParameterBinderStrings.ParameterAlreadyBound, null, versionToken); + ReportError(parameter.Extent, + nameof(ParameterBinderStrings.ParameterAlreadyBound), + ParameterBinderStrings.ParameterAlreadyBound, + null, + versionToken); return; } + requiredVersion = version; } } 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); } @@ -1864,59 +2186,75 @@ private void HandleRequiresParameter(CommandParameterAst parameter, } catch (InvalidCastException e) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresModuleInvalid, e.Message); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresModuleInvalid), + ParserStrings.RequiresModuleInvalid, + e.Message); return; } catch (ArgumentException e) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresModuleInvalid, e.Message); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresModuleInvalid), + ParserStrings.RequiresModuleInvalid, + e.Message); return; } - if (requiredModules == null) - requiredModules = new List(); + + requiredModules ??= new List(); requiredModules.Add(moduleSpecification); } } else { - ReportError(parameter.Extent, () => DiscoveryExceptions.ScriptRequiresInvalidFormat); + ReportError(parameter.Extent, + nameof(DiscoveryExceptions.ScriptRequiresInvalidFormat), + DiscoveryExceptions.ScriptRequiresInvalidFormat); } } private List HandleRequiresAssemblyArgument(Ast argumentAst, object arg, List requiredAssemblies) { - if (!(arg is string)) + if (arg is not string) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresInvalidStringArgument, assemblyToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresInvalidStringArgument), + ParserStrings.RequiresInvalidStringArgument, + assemblyToken); } else { - if (requiredAssemblies == null) - requiredAssemblies = new List(); + requiredAssemblies ??= new List(); if (!requiredAssemblies.Contains((string)arg)) { requiredAssemblies.Add((string)arg); } } + return requiredAssemblies; } private List HandleRequiresPSEditionArgument(Ast argumentAst, object arg, ref List requiredEditions) { - if (!(arg is string)) + if (arg is not string) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresInvalidStringArgument, editionToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresInvalidStringArgument), + ParserStrings.RequiresInvalidStringArgument, + editionToken); } else { - if (requiredEditions == null) - requiredEditions = new List(); + requiredEditions ??= new List(); var edition = (string)arg; if (!Utils.IsValidPSEditionValue(edition)) { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresPSEditionInvalid, editionToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresPSEditionInvalid), + ParserStrings.RequiresPSEditionInvalid, + editionToken); } if (!requiredEditions.Contains(edition, StringComparer.OrdinalIgnoreCase)) @@ -1925,9 +2263,13 @@ private List HandleRequiresPSEditionArgument(Ast argumentAst, object arg } else { - ReportError(argumentAst.Extent, () => ParserStrings.RequiresPSEditionValueIsAlreadySpecified, editionToken); + ReportError(argumentAst.Extent, + nameof(ParserStrings.RequiresPSEditionValueIsAlreadySpecified), + ParserStrings.RequiresPSEditionValueIsAlreadySpecified, + editionToken); } } + return requiredEditions; } #endregion Requires @@ -1968,7 +2310,7 @@ internal StringToken GetVerbatimCommandArgument() } InternalScriptExtent currentExtent = CurrentExtent(); - String tokenValue = currentExtent.Text; + string tokenValue = currentExtent.Text; return NewStringLiteralToken(tokenValue, TokenKind.Generic, TokenFlags.None); } @@ -1988,8 +2330,10 @@ private TokenFlags ScanStringLiteral(StringBuilder sb) { break; } + c = GetChar(); } + sb.Append(c); c = GetChar(); } @@ -1998,7 +2342,10 @@ private TokenFlags ScanStringLiteral(StringBuilder sb) { // error - reached end of input without seeing terminator UngetChar(); - ReportIncompleteInput(errorIndex, () => ParserStrings.TerminatorExpectedAtEndOfString, "'"); + ReportIncompleteInput(errorIndex, + nameof(ParserStrings.TerminatorExpectedAtEndOfString), + ParserStrings.TerminatorExpectedAtEndOfString, + "'"); flags = TokenFlags.TokenInError; } @@ -2038,6 +2385,7 @@ private Token ScanSubExpression(bool hereString) { scanning = false; } + break; case '`': @@ -2056,6 +2404,7 @@ private Token ScanSubExpression(bool hereString) { sb.Append(c); } + break; case '\0': @@ -2063,7 +2412,9 @@ private Token ScanSubExpression(bool hereString) goto default; UngetChar(); - ReportIncompleteInput(_tokenStart, () => ParserStrings.IncompleteDollarSubexpressionReference); + ReportIncompleteInput(_tokenStart, + nameof(ParserStrings.IncompleteDollarSubexpressionReference), + ParserStrings.IncompleteDollarSubexpressionReference); flags = TokenFlags.TokenInError; scanning = false; break; @@ -2137,6 +2488,7 @@ private TokenFlags ScanStringExpandable(StringBuilder sb, StringBuilder formatSb } } } + if (c == '{' || c == '}') { // In the format string, we need to double up the curlies because we're @@ -2144,6 +2496,7 @@ private TokenFlags ScanStringExpandable(StringBuilder sb, StringBuilder formatSb // format expression for string.Format. formatSb.Append(c); } + sb.Append(c); formatSb.Append(c); } @@ -2151,7 +2504,10 @@ private TokenFlags ScanStringExpandable(StringBuilder sb, StringBuilder formatSb if (c == '\0') { UngetChar(); - ReportIncompleteInput(errorIndex, () => ParserStrings.TerminatorExpectedAtEndOfString, "\""); + ReportIncompleteInput(errorIndex, + nameof(ParserStrings.TerminatorExpectedAtEndOfString), + ParserStrings.TerminatorExpectedAtEndOfString, + "\""); flags = TokenFlags.TokenInError; } @@ -2204,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; } @@ -2230,16 +2586,19 @@ private bool ScanAfterHereStringHeader(string header) c = GetChar(); } while (c.IsWhitespace()); - if (c == '\r' || c == '\n') + if (c == '\r') { - ScanNewline(c); + NormalizeCRLF(c); } - else + else if (c != '\n') { if (c == '\0' && AtEof()) { UngetChar(); - ReportIncompleteInput(headerOffset, () => ParserStrings.TerminatorExpectedAtEndOfString, string.Concat(header[1], '@')); + ReportIncompleteInput(headerOffset, + nameof(ParserStrings.TerminatorExpectedAtEndOfString), + ParserStrings.TerminatorExpectedAtEndOfString, + string.Concat(header[1], '@')); return false; } @@ -2249,9 +2608,11 @@ private bool ScanAfterHereStringHeader(string header) // scanning at the end of the line. Don't skip the newline so we have a newline to terminate the current // expression. - ReportError(_currentIndex, () => ParserStrings.UnexpectedCharactersAfterHereStringHeader); + ReportError(_currentIndex, + nameof(ParserStrings.UnexpectedCharactersAfterHereStringHeader), + ParserStrings.UnexpectedCharactersAfterHereStringHeader); - do + while (true) { c = GetChar(); if (c == header[1] && (PeekChar() == '@')) @@ -2259,12 +2620,13 @@ private bool ScanAfterHereStringHeader(string header) SkipChar(); break; } + if (c == '\r' || c == '\n' || (c == '\0' && AtEof())) { UngetChar(); break; } - } while (true); + } return false; } @@ -2307,6 +2669,7 @@ private bool ScanPossibleHereStringFooter(Func test, Action ap // to give a helpful error message. falseFooterOffset = _currentIndex - 1; } + appendChar(GetChar()); // append the '@' } else @@ -2315,6 +2678,7 @@ private bool ScanPossibleHereStringFooter(Func test, Action ap // handle the character. UngetChar(); } + return false; } @@ -2327,7 +2691,7 @@ private Token ScanHereStringLiteral() if (!ScanAfterHereStringHeader("@'")) { - return NewStringLiteralToken("", TokenKind.HereStringLiteral, TokenFlags.TokenInError); + return NewStringLiteralToken(string.Empty, TokenKind.HereStringLiteral, TokenFlags.TokenInError); } TokenFlags flags = TokenFlags.None; @@ -2367,12 +2731,18 @@ private Token ScanHereStringLiteral() UngetChar(); if (falseFooterOffset != -1) { - ReportIncompleteInput(falseFooterOffset, () => ParserStrings.WhitespaceBeforeHereStringFooter); + ReportIncompleteInput(falseFooterOffset, + nameof(ParserStrings.WhitespaceBeforeHereStringFooter), + ParserStrings.WhitespaceBeforeHereStringFooter); } else { - ReportIncompleteInput(headerOffset, () => ParserStrings.TerminatorExpectedAtEndOfString, "'@"); + ReportIncompleteInput(headerOffset, + nameof(ParserStrings.TerminatorExpectedAtEndOfString), + ParserStrings.TerminatorExpectedAtEndOfString, + "'@"); } + flags = TokenFlags.TokenInError; break; } @@ -2390,7 +2760,7 @@ private Token ScanHereStringExpandable() if (!ScanAfterHereStringHeader("@\"")) { - return NewStringExpandableToken("", "", TokenKind.HereStringExpandable, null, TokenFlags.TokenInError); + return NewStringExpandableToken(string.Empty, string.Empty, TokenKind.HereStringExpandable, null, TokenFlags.TokenInError); } TokenFlags flags = TokenFlags.None; @@ -2419,6 +2789,7 @@ private Token ScanHereStringExpandable() sb.Append('\n'); formatSb.Append('\n'); } + if (ScanPossibleHereStringFooter(CharExtensions.IsDoubleQuote, appendChar, ref falseFooterOffset)) { // Remove the last newline appended. @@ -2426,6 +2797,7 @@ private Token ScanHereStringExpandable() formatSb.Length = formatLength; break; } + continue; } @@ -2452,6 +2824,7 @@ private Token ScanHereStringExpandable() } } } + if (c == '{' || c == '}') { // In the format string, we need to double up the curlies because we're @@ -2459,6 +2832,7 @@ private Token ScanHereStringExpandable() // format expression for string.Format. formatSb.Append(c); } + if (c != '\0' || !AtEof()) { sb.Append(c); @@ -2469,12 +2843,18 @@ private Token ScanHereStringExpandable() UngetChar(); if (falseFooterOffset != -1) { - ReportIncompleteInput(falseFooterOffset, () => ParserStrings.WhitespaceBeforeHereStringFooter); + ReportIncompleteInput(falseFooterOffset, + nameof(ParserStrings.WhitespaceBeforeHereStringFooter), + ParserStrings.WhitespaceBeforeHereStringFooter); } else { - ReportIncompleteInput(headerOffset, () => ParserStrings.TerminatorExpectedAtEndOfString, "\"@"); + ReportIncompleteInput(headerOffset, + nameof(ParserStrings.TerminatorExpectedAtEndOfString), + ParserStrings.TerminatorExpectedAtEndOfString, + "\"@"); } + flags = TokenFlags.TokenInError; break; } @@ -2518,12 +2898,14 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) UngetChar(); goto end_braced_variable_scan; } + c = Backtick(c1, out char surrogateCharacter); if (surrogateCharacter != s_invalidChar) { sb.Append(c).Append(surrogateCharacter); continue; } + break; } case '"': @@ -2538,6 +2920,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) UngetChar(); goto end_braced_variable_scan; } + if (c1.IsDoubleQuote()) { c = c1; @@ -2547,9 +2930,12 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) UngetChar(); } } + break; case '{': - ReportError(_currentIndex, () => ParserStrings.OpenBraceNeedsToBeBackTickedInVariableName); + ReportError(_currentIndex, + nameof(ParserStrings.OpenBraceNeedsToBeBackTickedInVariableName), + ParserStrings.OpenBraceNeedsToBeBackTickedInVariableName); break; case '\0': if (AtEof()) @@ -2557,6 +2943,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) UngetChar(); goto end_braced_variable_scan; } + break; } @@ -2568,14 +2955,20 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) string name = GetStringAndRelease(sb); if (c != '}') { - ReportIncompleteInput(errorStartPosition, () => ParserStrings.IncompleteDollarVariableReference); + ReportIncompleteInput(errorStartPosition, + nameof(ParserStrings.IncompleteDollarVariableReference), + ParserStrings.IncompleteDollarVariableReference); } + if (name.Length == 0) { if (c == '}') { - ReportError(_currentIndex - 1, () => ParserStrings.EmptyVariableReference); + ReportError(_currentIndex - 1, + nameof(ParserStrings.EmptyVariableReference), + ParserStrings.EmptyVariableReference); } + name = ":Error:"; } @@ -2598,11 +2991,14 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) { // Enable if we decide we still need to support // "${}" or "$var:" - //if (inStringExpandable) - //{ + // if (inStringExpandable) + // { // return NewToken(TokenKind.Unknown); - //} - ReportError(NewScriptExtent(_tokenStart, _currentIndex), () => ParserStrings.InvalidBracedVariableReference); + // } + + ReportError(NewScriptExtent(_tokenStart, _currentIndex), + nameof(ParserStrings.InvalidBracedVariableReference), + ParserStrings.InvalidBracedVariableReference); } return NewVariableToken(path, false); @@ -2705,6 +3101,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) { sb.Append(c); } + break; case '\0': @@ -2725,7 +3122,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) // occur after a variable. case '.': case '[': - // Something like $a.b or $a[1]. + // Something like $a.b or $a[1]. case '=': // Something like $a= UngetChar(); @@ -2733,7 +3130,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) break; default: - if (Char.IsLetterOrDigit(c)) + if (char.IsLetterOrDigit(c)) { sb.Append(c); } @@ -2748,6 +3145,7 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) UngetChar(); scanning = false; } + break; } } @@ -2762,16 +3160,20 @@ private Token ScanVariable(bool splatted, bool inStringExpandable) path = new VariablePath(GetStringAndRelease(sb)); if (string.IsNullOrEmpty(path.UnqualifiedPath)) { - Expression> msg; + string errorId; + string errorMsg; if (path.IsDriveQualified) { - msg = () => ParserStrings.InvalidVariableReferenceWithDrive; + errorId = nameof(ParserStrings.InvalidVariableReferenceWithDrive); + errorMsg = ParserStrings.InvalidVariableReferenceWithDrive; } else { - msg = () => ParserStrings.InvalidVariableReference; + errorId = nameof(ParserStrings.InvalidVariableReference); + errorMsg = ParserStrings.InvalidVariableReference; } - ReportError(NewScriptExtent(_tokenStart, _currentIndex), msg); + + ReportError(NewScriptExtent(_tokenStart, _currentIndex), errorId, errorMsg); } return NewVariableToken(path, splatted); @@ -2822,6 +3224,7 @@ private Token ScanParameter() { UngetChar(); } + break; case 'a': @@ -2895,6 +3298,7 @@ private Token ScanParameter() sb.Insert(0, _script[_tokenStart]); // Insert the '-' that we skipped. return ScanGenericToken(sb); } + UngetChar(); scanning = false; break; @@ -2909,6 +3313,7 @@ private Token ScanParameter() UngetChar(); scanning = false; } + break; } } @@ -2938,6 +3343,7 @@ private Token CheckOperatorInCommandMode(char c, TokenKind tokenKind) { return ScanGenericToken(c); } + return NewToken(tokenKind); } @@ -2950,10 +3356,10 @@ private Token CheckOperatorInCommandMode(char c1, char c2, TokenKind tokenKind) sb.Append(c2); return ScanGenericToken(sb); } + return NewToken(tokenKind); } - private Token ScanGenericToken(char firstChar) { var sb = GetStringBuilder(); @@ -2969,6 +3375,7 @@ private Token ScanGenericToken(char firstChar, char surrogateCharacter) { sb.Append(surrogateCharacter); } + return ScanGenericToken(sb); } @@ -2996,7 +3403,7 @@ private Token ScanGenericToken(StringBuilder sb) // indeed, '$' is not commonly used in command names. // Make sure our token does not start with any of these characters. - //Contract.Requires(Contract.ForAll("{}()@#;,|&\r\n\t ", c1 => sb[0] != c1)); + // Contract.Requires(Contract.ForAll("{}()@#;,|&\r\n\t ", c1 => sb[0] != c1)); List nestedTokens = new List(); var formatSb = GetStringBuilder(); @@ -3029,6 +3436,7 @@ private Token ScanGenericToken(StringBuilder sb) { formatSb.Append(sb[i]); } + continue; } else if (c.IsDoubleQuote()) @@ -3043,6 +3451,7 @@ private Token ScanGenericToken(StringBuilder sb) continue; } } + sb.Append(c); formatSb.Append(c); if (c == '{' || c == '}') @@ -3050,6 +3459,7 @@ private Token ScanGenericToken(StringBuilder sb) formatSb.Append(c); } } + UngetChar(); var str = GetStringAndRelease(sb); @@ -3057,12 +3467,14 @@ private Token ScanGenericToken(StringBuilder sb) { return NewGenericExpandableToken(str, GetStringAndRelease(formatSb), nestedTokens); } + Release(formatSb); if (DynamicKeyword.ContainsKeyword(str) && !DynamicKeyword.IsHiddenKeyword(str)) { return NewToken(TokenKind.DynamicKeyword); } + return NewGenericToken(str); } @@ -3094,6 +3506,17 @@ private int ScanDecimalDigits(StringBuilder sb) return countDigits; } + private void ScanBinaryDigits(StringBuilder sb) + { + char c = PeekChar(); + while (c.IsBinaryDigit()) + { + SkipChar(); + sb.Append(c); + c = PeekChar(); + } + } + private void ScanExponent(StringBuilder sb, ref int signIndex, ref bool notNumber) { char c = PeekChar(); @@ -3106,6 +3529,7 @@ private void ScanExponent(StringBuilder sb, ref int signIndex, ref bool notNumbe signIndex = sb.Length; sb.Append(c); } + if (ScanDecimalDigits(sb) == 0) { notNumber = true; @@ -3128,130 +3552,325 @@ private void ScanNumberAfterDot(StringBuilder sb, ref int signIndex, ref bool no } } - private static bool TryGetNumberValue(string strNum, bool hex, bool real, char suffix, long multiplier, out object result) + private static bool TryGetNumberValue( + ReadOnlySpan strNum, + NumberFormat format, + NumberSuffixFlags suffix, + bool real, + long multiplier, + out object result) { checked { try { - NumberStyles style = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | - NumberStyles.AllowExponent; - if (suffix == 'd' || suffix == 'D') - { - decimal d; - if (Decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out d)) - { - result = d * multiplier; - return true; - } - - result = null; - return false; - } + NumberStyles style = NumberStyles.AllowLeadingSign + | NumberStyles.AllowDecimalPoint + | NumberStyles.AllowExponent; if (real) { - double d; - if (Double.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out d)) + // Decimal parser does not accept hex literals, and 'd' is a valid hex character, so will + // never be read as Decimal literal + // e.g., 0x1d == 29 + if (suffix == NumberSuffixFlags.Decimal) { - // TryParse incorrectly return +0 when the result should be -0, so check for that case - if (d == 0.0 && strNum[0] == '-') + if (decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out decimal d)) { - d = -0.0; + result = d * multiplier; + return true; } - if (suffix == 'l' || suffix == 'L') + + result = null; + return false; + } + + if (double.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out double doubleValue)) + { + // TryParse incorrectly return +0 when the result should be -0, so check for that case + if (doubleValue == 0.0 && strNum[0] == '-') { - result = ((long)Convert.ChangeType(d, typeof(long), CultureInfo.InvariantCulture)) * multiplier; + doubleValue = -0.0; } - else + + doubleValue *= multiplier; + switch (suffix) { - result = d * multiplier; + case NumberSuffixFlags.None: + result = doubleValue; + return true; + case NumberSuffixFlags.SignedByte: + if (Utils.TryCast(doubleValue.AsBigInt(), out sbyte sb)) + { + result = sb; + return true; + } + + break; + case NumberSuffixFlags.UnsignedByte: + if (Utils.TryCast(doubleValue.AsBigInt(), out byte b)) + { + result = b; + return true; + } + + break; + case NumberSuffixFlags.Short: + if (Utils.TryCast(doubleValue.AsBigInt(), out short s)) + { + result = s; + return true; + } + + break; + case NumberSuffixFlags.Long: + if (Utils.TryCast(doubleValue.AsBigInt(), out long l)) + { + result = l; + return true; + } + + break; + case NumberSuffixFlags.UnsignedShort: + if (Utils.TryCast(doubleValue.AsBigInt(), out ushort us)) + { + result = us; + return true; + } + + break; + case NumberSuffixFlags.Unsigned: + BigInteger testValue = doubleValue.AsBigInt(); + if (Utils.TryCast(testValue, out uint u)) + { + result = u; + return true; + } + else if (Utils.TryCast(testValue, out ulong ul)) + { + result = ul; + return true; + } + + break; + case NumberSuffixFlags.UnsignedLong: + if (Utils.TryCast(doubleValue.AsBigInt(), out ulong ulValue)) + { + result = ulValue; + return true; + } + + break; + case NumberSuffixFlags.BigInteger: + result = doubleValue.AsBigInt(); + return true; } - return true; + + // Invalid NumberSuffixFlags combination, or outside bounds of specified type. + result = null; + return false; } + // TryParse for real numeric literal failed result = null; return false; } - if (hex && !strNum[0].IsHexDigit()) - { - if (strNum[0] == '-') - { - multiplier = -multiplier; - } - strNum = strNum.Substring(1); - } - style = hex ? NumberStyles.AllowHexSpecifier : NumberStyles.AllowLeadingSign; + BigInteger bigValue; - long longValue; - if (suffix == 'l' || suffix == 'L') + switch (format) { - if (long.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out longValue)) - { - result = longValue * multiplier; - return true; - } - result = null; - return false; - } + case NumberFormat.Hex: + if (!strNum[0].IsHexDigit()) + { + if (strNum[0] == '-') + { + multiplier = -multiplier; + } - // From here on - the user hasn't specified the type, so we need to figure it out. + // Remove leading char (expected: - or +) + strNum = strNum.Slice(1); + } - BigInteger bigValue; - TypeCode whichTryParseWorked; - int intValue; - decimal decimalValue; - if (int.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out intValue)) - { - whichTryParseWorked = TypeCode.Int32; - bigValue = intValue; - } - else if (long.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out longValue)) - { - whichTryParseWorked = TypeCode.Int64; - bigValue = longValue; - } - else if (decimal.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out decimalValue)) - { - whichTryParseWorked = TypeCode.Decimal; - bigValue = (BigInteger)decimalValue; - } - else - { - // The result must be double if we get here. - if (!hex) - { - double doubleValue; - if (double.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out doubleValue)) + // If we're expecting a sign bit, remove the leading 0 added in ScanNumberHelper + if (!suffix.HasFlag(NumberSuffixFlags.Unsigned)) { - result = doubleValue * multiplier; - return true; + var expectedLength = suffix switch + { + NumberSuffixFlags.SignedByte => 2, + NumberSuffixFlags.Short => 4, + NumberSuffixFlags.Long => 16, + // No suffix flag can mean int or long depending on input string length + _ => strNum.Length < 16 ? 8 : 16 + }; + + if (strNum.Length == expectedLength + 1) + { + strNum = strNum.Slice(1); + } } - } - result = null; - return false; + style = NumberStyles.AllowHexSpecifier; + if (!BigInteger.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out bigValue)) + { + result = null; + return false; + } + + // If we have a hex literal denoting (u)int64, treat it as such, even if the value is low + if (strNum.Length == 16 && (suffix == NumberSuffixFlags.None || suffix == NumberSuffixFlags.Unsigned)) + { + suffix |= NumberSuffixFlags.Long; + } + + break; + case NumberFormat.Binary: + if (!strNum[0].IsBinaryDigit()) + { + if (strNum[0] == '-') + { + multiplier = -multiplier; + } + + // Remove leading char (expected: - or +) + strNum = strNum.Slice(1); + } + + bigValue = Utils.ParseBinary(strNum, suffix.HasFlag(NumberSuffixFlags.Unsigned)); + + // If we have a binary literal denoting (u)int64, treat it as such + if (strNum.Length == 64 && (suffix == NumberSuffixFlags.None || suffix == NumberSuffixFlags.Unsigned)) + { + suffix |= NumberSuffixFlags.Long; + } + + break; + default: + style = NumberStyles.AllowLeadingSign; + if (!BigInteger.TryParse(strNum, style, NumberFormatInfo.InvariantInfo, out bigValue)) + { + result = null; + return false; + } + + break; } + // Apply multiplier before attempting casting to prevent overflow bigValue *= multiplier; - if (bigValue >= int.MinValue && bigValue <= int.MaxValue && whichTryParseWorked <= TypeCode.Int32) - { - result = (int)bigValue; - } - else if (bigValue >= long.MinValue && bigValue <= long.MaxValue && whichTryParseWorked <= TypeCode.Int64) - { - result = (long)bigValue; - } - else if (bigValue >= (BigInteger)decimal.MinValue && bigValue <= (BigInteger)decimal.MaxValue && whichTryParseWorked <= TypeCode.Decimal) - { - result = (decimal)bigValue; - } - else + + switch (suffix) { - result = (double)bigValue; + case NumberSuffixFlags.SignedByte: + if (Utils.TryCast(bigValue, out sbyte sb)) + { + result = sb; + return true; + } + + break; + case NumberSuffixFlags.UnsignedByte: + if (Utils.TryCast(bigValue, out byte b)) + { + result = b; + return true; + } + + break; + case NumberSuffixFlags.Short: + if (Utils.TryCast(bigValue, out short s)) + { + result = s; + return true; + } + + break; + case NumberSuffixFlags.Long: + if (Utils.TryCast(bigValue, out long l)) + { + result = l; + return true; + } + + break; + case NumberSuffixFlags.UnsignedShort: + if (Utils.TryCast(bigValue, out ushort us)) + { + result = us; + return true; + } + + break; + case NumberSuffixFlags.Unsigned: + if (Utils.TryCast(bigValue, out uint u)) + { + result = u; + return true; + } + else if (Utils.TryCast(bigValue, out ulong ul)) + { + result = ul; + return true; + } + + break; + case NumberSuffixFlags.UnsignedLong: + if (Utils.TryCast(bigValue, out ulong ulValue)) + { + result = ulValue; + return true; + } + + break; + case NumberSuffixFlags.Decimal: + if (Utils.TryCast(bigValue, out decimal dm)) + { + result = dm; + return true; + } + + break; + case NumberSuffixFlags.BigInteger: + result = bigValue; + return true; + case NumberSuffixFlags.None: + // Type not specified; fit value into narrowest signed type available, int32 minimum + if (Utils.TryCast(bigValue, out int i)) + { + result = i; + return true; + } + + if (Utils.TryCast(bigValue, out long lValue)) + { + result = lValue; + return true; + } + + // Result is too big for anything else; fallback to decimal or double + if (format == NumberFormat.Decimal) + { + if (Utils.TryCast(bigValue, out decimal dmValue)) + { + result = dmValue; + return true; + } + + if (Utils.TryCast(bigValue, out double d)) + { + result = d; + return true; + } + } + + // Hex or Binary value, too big for generic non-suffixed parsing + result = null; + return false; } - return true; + + // Value cannot be contained in type specified by suffix, or invalid suffix flags. + result = null; + return false; } catch (Exception) { @@ -3264,14 +3883,12 @@ private static bool TryGetNumberValue(string strNum, bool hex, bool real, char s private Token ScanNumber(char firstChar) { - Diagnostics.Assert(firstChar == '.' || (firstChar >= '0' && firstChar <= '9') + Diagnostics.Assert( + firstChar == '.' || (firstChar >= '0' && firstChar <= '9') || (AllowSignedNumbers && (firstChar == '+' || firstChar.IsDash())), "Number must start with '.', '-', or digit."); - bool hex, real; - char suffix; - long multiplier; + string strNum = ScanNumberHelper(firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier); - string strNum = ScanNumberHelper(firstChar, out hex, out real, out suffix, out multiplier); // the token is not a number. i.e. 77z.exe if (strNum == null) { @@ -3281,7 +3898,7 @@ private Token ScanNumber(char firstChar) } object value; - if (!TryGetNumberValue(strNum, hex, real, suffix, multiplier, out value)) + if (!TryGetNumberValue(strNum, format, suffix, real, multiplier, out value)) { if (!InExpressionMode()) { @@ -3290,27 +3907,34 @@ private Token ScanNumber(char firstChar) return ScanGenericToken(GetStringBuilder()); } - ReportError(_currentIndex, () => ParserStrings.BadNumericConstant, _script.Substring(_tokenStart, _currentIndex - _tokenStart)); + ReportError( + NewScriptExtent(_tokenStart, _currentIndex), + nameof(ParserStrings.BadNumericConstant), + ParserStrings.BadNumericConstant, + _script.Substring(_tokenStart, _currentIndex - _tokenStart)); } return NewNumberToken(value); } - /// the first character - /// indicate if it's a hex number - /// indicate if it's a real number - /// indicate the type suffix - /// indicate the specified multiplier + /// + /// Scans a numeric string to determine its characteristics. + /// + /// The first character. + /// Indicate if it's a hex, binary, or decimal number. + /// Indicate the format suffix. + /// Indicate if the number is real (non-integer). + /// Indicate the specified multiplier. /// - /// return null if the token is not a number + /// Return null if the token is not a number /// OR - /// return the string format of the number + /// Return the string format of the number. /// - private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out char suffix, out long multiplier) + private string ScanNumberHelper(char firstChar, out NumberFormat format, out NumberSuffixFlags suffix, out bool real, out long multiplier) { - hex = false; + format = NumberFormat.Decimal; + suffix = NumberSuffixFlags.None; real = false; - suffix = '\0'; multiplier = 1; bool notNumber = false; @@ -3333,15 +3957,36 @@ private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out else { c = PeekChar(); - if (firstChar == '0' && (c == 'x' || c == 'X')) + bool isHexOrBinary = firstChar == '0' && (c == 'x' || c == 'X' || c == 'b' || c == 'B'); + + if (isHexOrBinary) { SkipChar(); - ScanHexDigits(sb); - if (sb.Length == 0) + + switch (c) { - notNumber = true; + case 'x': + case 'X': + sb.Append('0'); // Prepend a 0 to the number before any numeric digits are added + ScanHexDigits(sb); + if (sb.Length == 0) + { + notNumber = true; + } + + format = NumberFormat.Hex; + break; + case 'b': + case 'B': + ScanBinaryDigits(sb); + if (sb.Length == 0) + { + notNumber = true; + } + + format = NumberFormat.Binary; + break; } - hex = true; } else { @@ -3364,6 +4009,7 @@ private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out real = true; ScanNumberAfterDot(sb, ref signIndex, ref notNumber); } + break; case 'E': case 'e': @@ -3380,19 +4026,101 @@ private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out if (c.IsTypeSuffix()) { SkipChar(); - suffix = c; + switch (c) + { + case 'u': + case 'U': + suffix |= NumberSuffixFlags.Unsigned; + break; + case 's': + case 'S': + suffix |= NumberSuffixFlags.Short; + break; + case 'l': + case 'L': + suffix |= NumberSuffixFlags.Long; + break; + case 'd': + case 'D': + suffix |= NumberSuffixFlags.Decimal; + break; + case 'y': + case 'Y': + suffix |= NumberSuffixFlags.SignedByte; + break; + case 'n': + case 'N': + suffix |= NumberSuffixFlags.BigInteger; + break; + default: + notNumber = true; + break; + } + c = PeekChar(); + + if (c.IsTypeSuffix()) + { + SkipChar(); + switch (suffix) + { + case NumberSuffixFlags.Unsigned: + switch (c) + { + case 'l': + case 'L': + suffix |= NumberSuffixFlags.Long; + break; + case 's': + case 'S': + suffix |= NumberSuffixFlags.Short; + break; + case 'y': + case 'Y': + suffix |= NumberSuffixFlags.SignedByte; + break; + default: + notNumber = true; + break; + } + + break; + default: + notNumber = true; + break; + } + + c = PeekChar(); + } } if (c.IsMultiplierStart()) { SkipChar(); - if (c == 'k' || c == 'K') { multiplier = 1024; } - else if (c == 'm' || c == 'M') { multiplier = 1024 * 1024; } - else if (c == 'g' || c == 'G') { multiplier = 1024 * 1024 * 1024; } - else if (c == 't' || c == 'T') { multiplier = 1024L * 1024 * 1024 * 1024; } - else if (c == 'p' || c == 'P') { multiplier = 1024L * 1024 * 1024 * 1024 * 1024; } + switch (c) + { + case 'k': + case 'K': + multiplier = 1024; + break; + case 'm': + case 'M': + multiplier = 1024 * 1024; + break; + case 'g': + case 'G': + multiplier = 1024 * 1024 * 1024; + break; + case 't': + case 'T': + multiplier = 1024L * 1024 * 1024 * 1024; + break; + case 'p': + case 'P': + multiplier = 1024L * 1024 * 1024 * 1024 * 1024; + break; + } char c1 = PeekChar(); if (c1 == 'b' || c1 == 'B') @@ -3408,7 +4136,7 @@ private string ScanNumberHelper(char firstChar, out bool hex, out bool real, out if (!c.ForceStartNewToken()) { - if (!InExpressionMode() || !c.ForceStartNewTokenAfterNumber()) + if (!InExpressionMode() || !c.ForceStartNewTokenAfterNumber(ForceEndNumberOnTernaryOpChars)) { notNumber = true; } @@ -3472,11 +4200,14 @@ internal Token GetMemberAccessOperator(bool allowLBracket) UngetChar(); return null; } + return NewToken(TokenKind.Dot); } + UngetChar(); return null; } + if (c == ':') { _tokenStart = _currentIndex; @@ -3492,11 +4223,14 @@ internal Token GetMemberAccessOperator(bool allowLBracket) UngetChar(); return null; } + return NewToken(TokenKind.ColonColon); } + UngetChar(); return null; } + if (c == '[' && allowLBracket) { _tokenStart = _currentIndex; @@ -3504,6 +4238,26 @@ internal Token GetMemberAccessOperator(bool allowLBracket) return NewToken(TokenKind.LBracket); } + if (c == '?') + { + _tokenStart = _currentIndex; + SkipChar(); + c = PeekChar(); + if (c == '.') + { + SkipChar(); + return NewToken(TokenKind.QuestionDot); + } + else if (c == '[' && allowLBracket) + { + SkipChar(); + return NewToken(TokenKind.QuestionLBracket); + } + + UngetChar(); + return null; + } + return null; } @@ -3517,12 +4271,14 @@ internal Token GetInvokeMemberOpenParen() SkipChar(); return NewToken(TokenKind.LParen); } + if (c == '{') { _tokenStart = _currentIndex; SkipChar(); return NewToken(TokenKind.LCurly); } + return null; } @@ -3568,6 +4324,7 @@ internal Token GetLBracket() ScanBlockComment(); goto again; } + UngetChar(); break; @@ -3589,6 +4346,7 @@ internal Token GetLBracket() { UngetChar(); } + break; default: @@ -3598,6 +4356,7 @@ internal Token GetLBracket() SkipWhiteSpace(); goto again; } + UngetChar(); break; } @@ -3617,8 +4376,10 @@ private Token ScanDot() UngetChar(); // Unget the second dot, let ScanGenericToken consume it. return ScanGenericToken('.'); } + return NewToken(TokenKind.DotDot); } + if (c.IsDecimalDigit()) { return ScanNumber('.'); @@ -3684,6 +4445,7 @@ private Token ScanIdentifier(char firstChar) if (tokenKind != TokenKind.InlineScript || InWorkflowContext) return NewToken(tokenKind); } + if (DynamicKeyword.ContainsKeyword(ident) && !DynamicKeyword.IsHiddenKeyword(ident)) { return NewToken(TokenKind.DynamicKeyword); @@ -3717,6 +4479,7 @@ private Token ScanTypeName() { continue; } + break; } @@ -3744,6 +4507,7 @@ private void ScanAssemblyNameSpecToken(StringBuilder sb) UngetChar(); break; } + sb.Append(c); } @@ -3754,26 +4518,26 @@ private void ScanAssemblyNameSpecToken(StringBuilder sb) internal string GetAssemblyNameSpec() { - //G assembly-name-spec: - //G assembly-name - //G assembly-name ',' assembly-properties - //G assembly-name: - //G assembly-token - //G assembly-properties: - //G assembly-property - //G assembly-properties ',' assembly-property - //G assembly-property: - //G assembly-property-name '=' assembly-property-value - //G assembly-property-name: one of - //G 'Version' - //G 'PublicKey' - //G 'PublicKeyToken' - //G 'Culture' - //G 'Custom' - //G assembly-property: - //G assembly-token - //G assembly-token: - //G any sequence of characters not ending in whitespace, newlines, ',', '=', or ']'. + // G assembly-name-spec: + // G assembly-name + // G assembly-name ',' assembly-properties + // G assembly-name: + // G assembly-token + // G assembly-properties: + // G assembly-property + // G assembly-properties ',' assembly-property + // G assembly-property: + // G assembly-property-name '=' assembly-property-value + // G assembly-property-name: one of + // G 'Version' + // G 'PublicKey' + // G 'PublicKeyToken' + // G 'Culture' + // G 'Custom' + // G assembly-property: + // G assembly-token + // G assembly-token: + // G any sequence of characters not ending in whitespace, newlines, ',', '=', or ']'. // The above grammar is specified by the CLR (except assembly-token). We defer validation to the CLR, but // use the above grammar to collect the name of the assembly. @@ -3794,7 +4558,7 @@ internal string GetAssemblyNameSpec() if (PeekChar() == '=') { _tokenStart = _currentIndex; - sb.Append("="); + sb.Append('='); SkipChar(); NewToken(TokenKind.Equals); ScanAssemblyNameSpecToken(sb); @@ -3821,6 +4585,7 @@ private Token ScanLabel() UngetChar(); return NewGenericToken(GetStringAndRelease(sb)); } + UngetChar(); return ScanGenericToken(sb); } @@ -3882,52 +4647,68 @@ internal Token NextToken() { return NewToken(TokenKind.AtCurly); } + if (c1 == '(') { return NewToken(TokenKind.AtParen); } + if (c1.IsSingleQuote()) { return ScanHereStringLiteral(); } + if (c1.IsDoubleQuote()) { return ScanHereStringExpandable(); } + UngetChar(); if (c1.IsVariableStart()) { return ScanVariable(true, false); } - ReportError(_currentIndex - 1, () => ParserStrings.UnrecognizedToken); + ReportError(_currentIndex - 1, + nameof(ParserStrings.UnrecognizedToken), + ParserStrings.UnrecognizedToken); return NewToken(TokenKind.Unknown); case '#': ScanLineComment(); goto again; - case '\r': case '\n': - ScanNewline(c); return NewToken(TokenKind.NewLine); + case '\r': + NormalizeCRLF(c); + goto case '\n'; + case '`': c1 = GetChar(); + if (c1 == '\r') + { + NormalizeCRLF(c1); + } + if (c1 == '\n' || c1 == '\r') { - ScanNewline(c1); NewToken(TokenKind.LineContinuation); goto again; } + if (char.IsWhiteSpace(c1)) { SkipWhiteSpace(); goto again; } + if (c1 == '\0' && AtEof()) { - ReportIncompleteInput(_currentIndex, () => ParserStrings.IncompleteString); + ReportIncompleteInput(_currentIndex, + nameof(ParserStrings.IncompleteString), + ParserStrings.IncompleteString); // Unget the EOF so we can return an EOF token. UngetChar(); @@ -3947,15 +4728,18 @@ internal Token NextToken() SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.PlusPlus); } + if (c1 == '=') { SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.PlusEquals); } + if (AllowSignedNumbers && (char.IsDigit(c1) || c1 == '.')) { return ScanNumber(c); } + return CheckOperatorInCommandMode(c, TokenKind.Plus); case '-': @@ -3968,19 +4752,23 @@ internal Token NextToken() SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.MinusMinus); } + if (c1 == '=') { SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.MinusEquals); } + if (char.IsLetter(c1) || c1 == '_' || c1 == '?') { return ScanParameter(); } + if (AllowSignedNumbers && (char.IsDigit(c1) || c1 == '.')) { return ScanNumber(c); } + return CheckOperatorInCommandMode(c, TokenKind.Minus); case '*': @@ -3990,6 +4778,7 @@ internal Token NextToken() SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.MultiplyEquals); } + if (c1 == '>') { SkipChar(); @@ -3999,6 +4788,7 @@ internal Token NextToken() SkipChar(); return NewFileRedirectionToken(0, append: true, fromSpecifiedExplicitly: false); } + if (c1 == '&') { SkipChar(); @@ -4008,6 +4798,7 @@ internal Token NextToken() SkipChar(); return NewMergingRedirectionToken(0, 1); } + UngetChar(); } @@ -4023,6 +4814,7 @@ internal Token NextToken() SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.DivideEquals); } + return CheckOperatorInCommandMode(c, TokenKind.Divide); case '%': @@ -4032,6 +4824,7 @@ internal Token NextToken() SkipChar(); return CheckOperatorInCommandMode(c, c1, TokenKind.RemainderEquals); } + return CheckOperatorInCommandMode(c, TokenKind.Rem); case '$': @@ -4040,6 +4833,7 @@ internal Token NextToken() SkipChar(); return NewToken(TokenKind.DollarParen); } + return ScanVariable(false, false); case '<': @@ -4049,6 +4843,7 @@ internal Token NextToken() ScanBlockComment(); goto again; } + return NewInputRedirectionToken(); case '>': @@ -4057,6 +4852,7 @@ internal Token NextToken() SkipChar(); return NewFileRedirectionToken(1, append: true, fromSpecifiedExplicitly: false); } + return NewFileRedirectionToken(1, append: false, fromSpecifiedExplicitly: false); case 'a': @@ -4123,6 +4919,7 @@ internal Token NextToken() { return ScanGenericToken('['); } + return NewToken(TokenKind.LBracket); case ']': return NewToken(TokenKind.RBracket); @@ -4158,6 +4955,7 @@ internal Token NextToken() SkipChar(); return NewFileRedirectionToken(c - '0', append: true, fromSpecifiedExplicitly: true); } + if (c1 == '&') { SkipChar(); @@ -4167,11 +4965,13 @@ internal Token NextToken() SkipChar(); return NewMergingRedirectionToken(c - '0', c1 - '0'); } + UngetChar(); } return NewFileRedirectionToken(c - '0', append: false, fromSpecifiedExplicitly: true); } + return ScanNumber(c); case '&': @@ -4180,6 +4980,7 @@ internal Token NextToken() SkipChar(); return NewToken(TokenKind.AndAnd); } + return NewToken(TokenKind.Ampersand); case '|': @@ -4188,6 +4989,7 @@ internal Token NextToken() SkipChar(); return NewToken(TokenKind.OrOr); } + return NewToken(TokenKind.Pipe); case '!': @@ -4200,17 +5002,16 @@ internal Token NextToken() if (InExpressionMode() && (char.IsDigit(c1) || c1 == '.')) { - bool hex, real; - char suffix; - long multiplier; - // check if the next token is actually a number - string strNum = ScanNumberHelper(c, out hex, out real, out suffix, out multiplier); + string strNum = ScanNumberHelper(c, out _, out _, out _, out _); // rescan characters after the check _currentIndex = _tokenStart; c = GetChar(); - if (strNum == null) { return ScanGenericToken(c); } + if (strNum == null) + { + return ScanGenericToken(c); + } } return NewToken(TokenKind.Exclaim); @@ -4225,19 +5026,42 @@ internal Token NextToken() sb.Append("::"); return ScanGenericToken(sb); } + return NewToken(TokenKind.ColonColon); } + if (InCommandMode()) { return ScanLabel(); } + return this.NewToken(TokenKind.Colon); + case '?' when InExpressionMode(): + c1 = PeekChar(); + + if (c1 == '?') + { + SkipChar(); + c1 = PeekChar(); + + if (c1 == '=') + { + SkipChar(); + return this.NewToken(TokenKind.QuestionQuestionEquals); + } + + return this.NewToken(TokenKind.QuestionQuestion); + } + + return this.NewToken(TokenKind.QuestionMark); + case '\0': if (AtEof()) { return SaveToken(new Token(NewScriptExtent(_tokenStart + 1, _tokenStart + 1), TokenKind.EndOfInput, TokenFlags.None)); } + return ScanGenericToken(c); default: @@ -4251,6 +5075,7 @@ internal Token NextToken() { return ScanIdentifier(c); } + return ScanGenericToken(c); } } diff --git a/src/System.Management.Automation/engine/pipeline.cs b/src/System.Management.Automation/engine/pipeline.cs index 1d009797821..191f80e1d89 100644 --- a/src/System.Management.Automation/engine/pipeline.cs +++ b/src/System.Management.Automation/engine/pipeline.cs @@ -1,14 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; -using System.Reflection; using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; 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; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -28,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; @@ -42,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 = @@ -60,13 +67,11 @@ internal class PipelineProcessor : IDisposable /// /// This is only public because it implements an interface method. /// The class itself is internal. - /// /// We use the standard IDispose pattern. /// 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 @@ -116,12 +114,18 @@ internal bool ExecutionFailed { return _executionFailed; } + set { _executionFailed = value; } } + /// + /// 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); @@ -155,6 +159,7 @@ internal void LogExecutionError(InvocationInfo invocationInfo, ErrorRecord error } private bool _terminatingErrorLogged = false; + internal void LogExecutionException(Exception exception) { _executionFailed = true; @@ -172,26 +177,26 @@ 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 ""; + return string.Empty; if (invocationInfo.MyCommand != null) { return invocationInfo.MyCommand.Name; } - return ""; + return string.Empty; } - private string GetCommand(Exception exception) + private static string GetCommand(Exception exception) { IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (icer != null && icer.ErrorRecord != null) return GetCommand(icer.ErrorRecord.InvocationInfo); - return ""; + return string.Empty; } private void Log(string logElement, InvocationInfo invocation, PipelineExecutionStatus pipelineExecutionStatus) @@ -218,75 +223,86 @@ 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 ((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 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); + // 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) + { + 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 /// - /// Add a single InternalCommand to the end of the pipeline + /// Add a single InternalCommand to the end of the pipeline. /// - /// Results from last pipeline stage + /// Results from last pipeline stage. /// /// see AddCommand /// /// 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("pipelineProcessor"); - if (_redirectionPipes == null) - _redirectionPipes = new List(); + if (pipelineProcessor is null) + { + throw PSTraceSource.NewArgumentNullException(nameof(pipelineProcessor)); + } + + _redirectionPipes ??= new List(); _redirectionPipes.Add(pipelineProcessor); } @@ -294,12 +310,12 @@ internal void AddRedirectionPipe(PipelineProcessor pipelineProcessor) // should be an int or enum to allow for more queues // 2005/03/08-JonN: This is an internal API /// - /// Add a command to the pipeline + /// Add a command to the pipeline. /// /// - /// reference number of command from which to read, 0 for none - /// read from error queue of command readFromCommand - /// reference number of this command for use in readFromCommand + /// Reference number of command from which to read, 0 for none. + /// Read from error queue of command readFromCommand. + /// Reference number of this command for use in readFromCommand. /// /// /// FirstCommandCannotHaveInput: must be zero @@ -312,38 +328,43 @@ 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 (null == commandProcessor) + if (commandProcessor == null) { - throw PSTraceSource.NewArgumentNullException("commandProcessor"); + throw PSTraceSource.NewArgumentNullException(nameof(commandProcessor)); } - if (null == _commands) + + if (_commands == null) { // "_commands == null" throw PSTraceSource.NewInvalidOperationException(); } + if (_disposed) { throw PSTraceSource.NewObjectDisposedException("PipelineProcessor"); } + if (_executionStarted) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.ExecutionAlreadyStarted); } + if (commandProcessor.AddedToPipelineAlready) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.CommandProcessorAlreadyUsed); } - if (0 == _commands.Count) + + if (_commands.Count == 0) { - if (0 != readFromCommand) + if (readFromCommand != 0) { // "First command cannot have input" throw PSTraceSource.NewArgumentException( - "readFromCommand", + nameof(readFromCommand), PipelineStrings.FirstCommandCannotHaveInput); } @@ -354,25 +375,24 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma { // "invalid command number" throw PSTraceSource.NewArgumentException( - "readFromCommand", + nameof(readFromCommand), PipelineStrings.InvalidCommandNumber); } else { - CommandProcessorBase prevcommandProcessor = _commands[readFromCommand - 1] as CommandProcessorBase; - if (null == prevcommandProcessor || null == prevcommandProcessor.CommandRuntime) - { - // "PipelineProcessor.AddCommand(): previous request object == null" - throw PSTraceSource.NewInvalidOperationException(); - } - Pipe UpstreamPipe = (readErrorQueue) ? - prevcommandProcessor.CommandRuntime.ErrorOutputPipe : prevcommandProcessor.CommandRuntime.OutputPipe; - if (null == UpstreamPipe) + var prevcommandProcessor = _commands[readFromCommand - 1] as CommandProcessorBase; + ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null); + + Pipe UpstreamPipe = (readErrorQueue) + ? prevcommandProcessor.CommandRuntime.ErrorOutputPipe + : prevcommandProcessor.CommandRuntime.OutputPipe; + + if (UpstreamPipe == null) { - // "PipelineProcessor.AddCommand(): UpstreamPipe == null" throw PSTraceSource.NewInvalidOperationException(); } - if (null != UpstreamPipe.DownstreamCmdlet) + + if (UpstreamPipe.DownstreamCmdlet != null) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.PipeAlreadyTaken); @@ -391,32 +411,33 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma for (int i = 0; i < _commands.Count; i++) { prevcommandProcessor = _commands[i]; - if (null == prevcommandProcessor || null == prevcommandProcessor.CommandRuntime) - { - // "PipelineProcessor.AddCommand(): previous request object == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null); + // check whether the error output is already claimed - if (null != prevcommandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet) + if (prevcommandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet != null) continue; - if (null != prevcommandProcessor.CommandRuntime.ErrorOutputPipe.ExternalWriter) + if (prevcommandProcessor.CommandRuntime.ErrorOutputPipe.ExternalWriter != null) continue; // Set the upstream cmdlet's error output to go down // the same pipe as the downstream cmdlet's input prevcommandProcessor.CommandRuntime.ErrorOutputPipe = UpstreamPipe; } - } // if MergeUnclaimedPreviousErrorResults + } } + _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. commandProcessor.CommandRuntime.PipelineProcessor = this; return _commands.Count; - } // AddCommand( CommandProcessorBase commandProcessor, int readFromCommand, bool readErrorQueue ) + } // 2005/03/08-JonN: This is an internal API /// @@ -476,291 +497,387 @@ 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 is not null) + { + firstCommandProcessor.CommandRuntime.InputPipe.ExternalReader = ExternalInput; + } - // Add any input to the first command. - if (null != 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 (null != _firstTerminatingError) - { - 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. - Diagnostics.Assert(null != toRethrowInfo, "Alternate protocol path failure"); + // 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 - } // internal Array SynchronousExecuteEnumerate() + + // 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]; + + if (commandProcessor is null) { - CommandProcessorBase commandProcessor = _commands[i]; + // An internal error that should not happen. + throw PSTraceSource.NewInvalidOperationException(); + } - if (null == commandProcessor) - { - // "null command " + i - throw PSTraceSource.NewInvalidOperationException(); - } + if (object.ReferenceEquals(commandRequestingUpstreamCommandsToStop, commandProcessor)) + { + // Do not call DoComplete/EndProcessing on the command that initiated stopping. + commandRequestingUpstreamCommandsToStop = null; + continue; + } - if (object.ReferenceEquals(commandRequestingUpstreamCommandsToStop, commandProcessor)) - { - commandRequestingUpstreamCommandsToStop = null; - continue; // do not call DoComplete/EndProcessing on the command that initiated stopping - } - 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); + EtwActivity.SetActivityId(commandProcessor.PipelineActivityId); - // Log a command stopped event - MshLog.LogCommandLifecycleEvent( - commandProcessor.Command.Context, - CommandState.Stopped, - commandProcessor.Command.MyInvocation); + // Log a command stopped event + MshLog.LogCommandLifecycleEvent( + commandProcessor.Command.Context, + CommandState.Stopped, + commandProcessor.Command.MyInvocation); - // 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); - } - 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. /// - /// The results of the execution + /// 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 (null != _firstTerminatingError) + 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(null != toRethrowInfo, "Alternate protocol path failure"); - toRethrowInfo.Throw(); - return null; // UNREACHABLE - } // internal Array DoComplete() + } /// - /// 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 + /// 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 (null != _firstTerminatingError) - { - _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 (null != _firstTerminatingError) + 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; } } @@ -775,37 +892,39 @@ 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 (null == commands) + 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 (null == commandProcessor) + 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 - } // for (int i = 0; i < _commands.Count; i++) - } // Stop() + } + } #endregion public_methods @@ -846,43 +965,36 @@ 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 (null != _firstTerminatingError) - { - _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 (null != _firstTerminatingError) + 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; - } - } // internal Array DoStepItems + } /// /// Prepares the pipeline for execution. @@ -921,77 +1033,64 @@ private void Start(bool incomingStream) { throw PSTraceSource.NewObjectDisposedException("PipelineProcessor"); } + if (Stopping) { throw new PipelineStoppedException(); } if (_executionStarted) + { return; + } - if (null == _commands || 0 == _commands.Count) + if (_commands == null || _commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); } CommandProcessorBase firstcommandProcessor = _commands[0]; - if (null == firstcommandProcessor - || null == firstcommandProcessor.CommandRuntime) - { - 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 (null == LastCommandProcessor - || null == LastCommandProcessor.CommandRuntime) - { - // "PipelineProcessor.Start(): LastCommandProcessor == null" - throw PSTraceSource.NewInvalidOperationException(); - } - if (null != ExternalSuccessOutput) + ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null); + + if (ExternalSuccessOutput != null) { - LastCommandProcessor.CommandRuntime.OutputPipe.ExternalWriter - = ExternalSuccessOutput; + LastCommandProcessor.CommandRuntime.OutputPipe.ExternalWriter = ExternalSuccessOutput; } // add ExternalErrorOutput to all commands whose error // output is not yet claimed SetExternalErrorOutput(); - if (null == ExternalInput && !incomingStream) + if (ExternalInput == null && !incomingStream) { // no upstream cmdlet from the first command firstcommandProcessor.CommandRuntime.IsClosed = true; } // 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]; - if (null == commandProcessor) + if (commandProcessor == null) { // "null command " + i throw PSTraceSource.NewInvalidOperationException(); @@ -999,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; @@ -1014,12 +1111,10 @@ private void Start(bool incomingStream) 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; @@ -1049,45 +1144,37 @@ private void Start(bool incomingStream) commandProcessor.DoBegin(); } - } // private void Start + } /// - /// 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() { - if (null != ExternalErrorOutput) + if (ExternalErrorOutput != null) { 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; } } } } /// - /// Clear ErrorVariable as appropriate + /// Clear ErrorVariable as appropriate. /// private void SetupParameterVariables() { - for (int i = 0; i < _commands.Count; i++) + foreach (CommandProcessorBase commandProcessor in _commands) { - CommandProcessorBase commandProcessor = _commands[i]; - if (null == commandProcessor || null == commandProcessor.CommandRuntime) - { - // "null command " + i - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null); commandProcessor.CommandRuntime.SetupOutVariable(); commandProcessor.CommandRuntime.SetupErrorVariable(); @@ -1097,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. @@ -1104,7 +1201,7 @@ private void SetupParameterVariables() /// /// Array of input objects for first stage /// - /// If true, unravel the input otherwise pass as one object + /// If true, unravel the input otherwise pass as one object. /// /// Exception if any cmdlet throws a [terminating] exception /// @@ -1126,12 +1223,7 @@ private void Inject(object input, bool enumerate) { // Add any input to the first command. CommandProcessorBase firstcommandProcessor = _commands[0]; - if (null == firstcommandProcessor - || null == firstcommandProcessor.CommandRuntime) - { - throw PSTraceSource.NewInvalidOperationException( - PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); - } + ValidateCommandProcessorNotNull(firstcommandProcessor, PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); if (input != AutomationNull.Value) { @@ -1158,7 +1250,7 @@ private void Inject(object input, bool enumerate) // Execute the first command - In the streamlet model, Execute of the first command will // automatically call the downstream command incase if there are any objects in the pipe. firstcommandProcessor.DoExecute(); - } // private void Inject + } /// /// Retrieve results from the pipeline. @@ -1169,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 (null == commandProcessor - || null == commandProcessor.CommandRuntime) - { - // "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(); } } @@ -1198,63 +1289,44 @@ 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 (null == LastCommandProcessor - || null == LastCommandProcessor.CommandRuntime) - { - // "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; - } // private Array RetrieveResults + return results is null ? MshCommandRuntime.StaticEmptyArray : results; + } /// /// Links this pipeline to a pre-existing Pipe object. This allows nested pipes /// to write into the parent pipeline. It does this by resetting the terminal /// pipeline object. /// - /// The pipeline to write success objects to + /// The pipeline to write success objects to. internal void LinkPipelineSuccessOutput(Pipe pipeToUse) { Dbg.Assert(pipeToUse != null, "Caller should verify pipeToUse != null"); CommandProcessorBase LastCommandProcessor = _commands[_commands.Count - 1]; - if (null == LastCommandProcessor - || null == LastCommandProcessor.CommandRuntime) - { - // "PipelineProcessor.RetrieveResults(): LastCommandProcessor == null" - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null); LastCommandProcessor.CommandRuntime.OutputPipe = pipeToUse; _linkedSuccessOutput = true; - } // private void SetResultPipe + } 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 (null == commandProcessor - || null == commandProcessor.CommandRuntime) - { - // "null command or request or ErrorOutputPipe " + i - throw PSTraceSource.NewInvalidOperationException(); - } + ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null); if (commandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet == null) { @@ -1275,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 (null != commandProcessor) + 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 (null != commandProcessor.Command) - myInvocation = commandProcessor.Command.MyInvocation; + continue; + } - ProviderInvocationException pie = - e as ProviderInvocationException; - if (null != pie) + // If Dispose throws an exception, record it as a pipeline failure and continue disposing cmdlets. + try + { + // Only cmdlets can have variables defined via the common parameters. + // We handle the cleanup of those variables only if we need to. + if (commandProcessor is CommandProcessor) + { + if (commandProcessor.Command is not PSScriptCmdlet) { - e = new CmdletProviderInvocationException( - pie, - myInvocation); + // For script cmdlets, the variable lists were already removed when exiting a scope. + // So we only need to take care of binary cmdlets here. + commandProcessor.CommandRuntime.RemoveVariableListsInPipe(); } - else - { - e = new CmdletInvocationException( - e, - myInvocation); - // Log a command health event + // Remove the pipeline variable if we need to. + commandProcessor.CommandRuntime.RemovePipelineVariable(); + } - MshLog.LogCommandHealthEvent( - commandProcessor.Command.Context, - e, - Severity.Warning); - } + commandProcessor.Dispose(); + } + catch (Exception e) + { + // The only vaguely plausible reason for a failure here is an exception in 'Command.Dispose'. + // As such, this should be covered by the overall exemption. + InvocationInfo myInvocation = commandProcessor.Command?.MyInvocation; - RecordFailure(e, commandProcessor.Command); + if (e is ProviderInvocationException pie) + { + e = new CmdletProviderInvocationException(pie, myInvocation); } -#pragma warning restore 56500 + else + { + e = new CmdletInvocationException(e, myInvocation); + + // Log a command health event + MshLog.LogCommandHealthEvent(commandProcessor.Command.Context, e, Severity.Warning); + } + + RecordFailure(e, commandProcessor.Command); } } } @@ -1338,62 +1413,68 @@ 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 + /// Error which terminated the pipeline. + /// Command against which to log SecondFailure. + /// True if-and-only-if the pipeline was not already stopped. internal bool RecordFailure(Exception e, InternalCommand command) { bool wasStopping = false; lock (_stopReasonLock) { - if (null == _firstTerminatingError) + if (_firstTerminatingError == null) { _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)) - && null != command && null != command.Context) + // 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) - && (null != ex.InnerException)) + && (ex.InnerException != null)) { ex = ex.InnerException; } - if (!(ex is PipelineStoppedException)) + + if (ex is not PipelineStoppedException) { string message = StringUtil.Format(PipelineStrings.SecondFailure, _firstTerminatingError.GetType().Name, @@ -1401,17 +1482,18 @@ 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); } } + wasStopping = _stopping; _stopping = true; } + return !wasStopping; } @@ -1450,7 +1532,11 @@ internal void ForgetFailure() /// internal PipelineReader ExternalInput { - get { return _externalInputPipe; } + get + { + return _externalInputPipe; + } + set { if (_executionStarted) @@ -1458,6 +1544,7 @@ internal PipelineReader ExternalInput throw PSTraceSource.NewInvalidOperationException( PipelineStrings.ExecutionAlreadyStarted); } + _externalInputPipe = value; } } @@ -1474,7 +1561,11 @@ internal PipelineReader ExternalInput /// internal PipelineWriter ExternalSuccessOutput { - get { return _externalSuccessOutput; } + get + { + return _externalSuccessOutput; + } + set { if (_executionStarted) @@ -1482,6 +1573,7 @@ internal PipelineWriter ExternalSuccessOutput throw PSTraceSource.NewInvalidOperationException( PipelineStrings.ExecutionAlreadyStarted); } + _externalSuccessOutput = value; } } @@ -1499,7 +1591,11 @@ internal PipelineWriter ExternalSuccessOutput /// internal PipelineWriter ExternalErrorOutput { - get { return _externalErrorOutput; } + get + { + return _externalErrorOutput; + } + set { if (_executionStarted) @@ -1530,9 +1626,11 @@ internal bool Stopping } private LocalPipeline _localPipeline; + internal LocalPipeline LocalPipeline { get { return _localPipeline; } + set { _localPipeline = value; } } @@ -1541,10 +1639,13 @@ internal LocalPipeline LocalPipeline /// /// The scope the pipeline should execute in. /// - /// internal SessionStateScope ExecutionScope { - get { return _executionScope; } + get + { + return _executionScope; + } + set { // This needs to be settable so that a steppable pipeline @@ -1564,5 +1665,4 @@ internal enum PipelineExecutionStatus PipelineComplete } } -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index 31ee5d8dfb4..6e0ef9741d6 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 +using System.Buffers; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; @@ -43,60 +43,44 @@ public enum WildcardOptions /// Specifies culture-invariant matching. /// CultureInvariant = 4 - }; + } /// /// Represents a wildcard pattern. /// public sealed class WildcardPattern { - // // char that escapes special chars - // private const char escapeChar = '`'; - // + // Threshold for stack allocation. + // 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; - // + // static match-all delegate that is shared by all WildcardPattern instances + private static readonly Predicate s_matchAll = _ => true; + // wildcard pattern - // internal string Pattern { get; } - // - // options that control match behavior - // - internal WildcardOptions Options { get; } = WildcardOptions.None; - - /// - /// wildcard pattern converted to regex pattern. - /// - internal string PatternConvertedToRegex - { - get - { - var patternRegex = WildcardPatternToRegexParser.Parse(this); - return patternRegex.ToString(); - } - } + // Options that control match behavior. + // Default is WildcardOptions.None. + internal WildcardOptions Options { get; } /// /// Initializes and instance of the WildcardPattern class /// for the specified wildcard pattern. /// - /// The wildcard pattern to match - /// The constructed WildcardPattern object - /// if wildCardType == None, the pattern does not have wild cards - public WildcardPattern(string pattern) + /// The wildcard pattern to match. + /// The constructed WildcardPattern object. + public WildcardPattern(string pattern) : this(pattern, WildcardOptions.None) { - if (pattern == null) - { - throw PSTraceSource.NewArgumentNullException("pattern"); - } - - Pattern = pattern; } /// @@ -105,15 +89,13 @@ public WildcardPattern(string pattern) /// that modify the pattern. /// /// The wildcard pattern to match. - /// Wildcard options - /// The constructed WildcardPattern object - /// if wildCardType == None, the pattern does not have wild cards - public WildcardPattern(string pattern, - WildcardOptions options) + /// Wildcard options. + /// The constructed WildcardPattern object. + public WildcardPattern(string pattern, WildcardOptions options) { if (pattern == null) { - throw PSTraceSource.NewArgumentNullException("pattern"); + throw PSTraceSource.NewArgumentNullException(nameof(pattern)); } Pattern = pattern; @@ -125,13 +107,13 @@ public WildcardPattern(string pattern, /// /// Create a new WildcardPattern, or return an already created one. /// - /// The pattern + /// The pattern. /// /// public static WildcardPattern Get(string pattern, WildcardOptions options) { if (pattern == null) - throw PSTraceSource.NewArgumentNullException("pattern"); + throw PSTraceSource.NewArgumentNullException(nameof(pattern)); if (pattern.Length == 1 && pattern[0] == '*') return s_matchAllIgnoreCasePattern; @@ -142,25 +124,61 @@ public static WildcardPattern Get(string pattern, WildcardOptions options) /// /// Instantiate internal regex member if not already done. /// - /// - /// true on success, false otherwise - /// - /// - /// + /// True on success, false otherwise. private void Init() { - if (_isMatch == null) + StringComparison GetStringComparison() { - if (Pattern.Length == 1 && Pattern[0] == '*') + StringComparison stringComparison; + if (Options.HasFlag(WildcardOptions.IgnoreCase)) { - _isMatch = _ => true; + stringComparison = Options.HasFlag(WildcardOptions.CultureInvariant) + ? StringComparison.InvariantCultureIgnoreCase + : CultureInfo.CurrentCulture.Name.Equals("en-US-POSIX", StringComparison.OrdinalIgnoreCase) + // The collation behavior of the POSIX locale (also known as the C locale) is case sensitive. + // For this specific locale, we use 'OrdinalIgnoreCase'. + ? StringComparison.OrdinalIgnoreCase + : StringComparison.CurrentCultureIgnoreCase; } else { - var matcher = new WildcardPatternMatcher(this); - _isMatch = matcher.IsMatch; + stringComparison = Options.HasFlag(WildcardOptions.CultureInvariant) + ? StringComparison.InvariantCulture + : StringComparison.CurrentCulture; } + + return stringComparison; + } + + if (_isMatch != null) + { + return; + } + + if (Pattern.Length == 1 && Pattern[0] == '*') + { + _isMatch = s_matchAll; + return; + } + + 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()); + return; } + + 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(0, index); + _isMatch = str => str.AsSpan().StartsWith(patternWithoutAsterisk.Span, GetStringComparison()); + return; + } + + var matcher = new WildcardPatternMatcher(this); + _isMatch = matcher.IsMatch; } /// @@ -168,36 +186,68 @@ private void Init() /// constructor finds a match in the input string. /// /// The string to search for a match. - /// true if the wildcard pattern finds a match; otherwise, false + /// True if the wildcard pattern finds a match; otherwise, false. public bool IsMatch(string input) { Init(); 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. /// /// The input string containing the text to convert. - /// Array of characters that not to escape + /// Array of characters that not to escape. /// /// A string of characters with any metacharacters, except for those specified in , converted to their escaped form. /// internal static string Escape(string pattern, char[] charsNotToEscape) { -#pragma warning disable 56506 - if (pattern == null) { - throw PSTraceSource.NewArgumentNullException("pattern"); + throw PSTraceSource.NewArgumentNullException(nameof(pattern)); } if (charsNotToEscape == null) { - throw PSTraceSource.NewArgumentNullException("charsNotToEscape"); + throw PSTraceSource.NewArgumentNullException(nameof(charsNotToEscape)); } - char[] temp = new char[pattern.Length * 2 + 1]; + if (pattern == string.Empty) + { + return pattern; + } + + Span temp = pattern.Length < StackAllocThreshold ? stackalloc char[pattern.Length * 2 + 1] : new char[pattern.Length * 2 + 1]; int tempIndex = 0; for (int i = 0; i < pattern.Length; i++) @@ -205,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; } @@ -217,18 +267,16 @@ internal static string Escape(string pattern, char[] charsNotToEscape) string s = null; - if (tempIndex > 0) + if (tempIndex == pattern.Length) { - s = new string(temp, 0, tempIndex); + s = pattern; } else { - s = String.Empty; + s = new string(temp.Slice(0, tempIndex)); } return s; - -#pragma warning restore 56506 } /// @@ -240,7 +288,7 @@ internal static string Escape(string pattern, char[] charsNotToEscape) /// public static string Escape(string pattern) { - return Escape(pattern, Utils.EmptyArray()); + return Escape(pattern, Array.Empty()); } /// @@ -249,7 +297,7 @@ public static string Escape(string pattern) /// /// String which needs to be checked for the presence of wildcard chars /// - /// true if the string has wild card chars, false otherwise. + /// True if the string has wild card chars, false otherwise.. /// /// Currently { '*', '?', '[' } are considered wild card chars and /// '`' is the escape character. @@ -279,6 +327,44 @@ public static bool ContainsWildcardCharacters(string pattern) ++index; } } + + 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; } @@ -293,16 +379,22 @@ public static bool ContainsWildcardCharacters(string pattern) /// converted to their unescaped form. /// /// - /// If is null. + /// If is null. /// public static string Unescape(string pattern) { if (pattern == null) { - throw PSTraceSource.NewArgumentNullException("pattern"); + throw PSTraceSource.NewArgumentNullException(nameof(pattern)); } - char[] temp = new char[pattern.Length]; + if (pattern == string.Empty) + { + return pattern; + } + + Span temp = pattern.Length < StackAllocThreshold ? stackalloc char[pattern.Length] : new char[pattern.Length]; + int tempIndex = 0; bool prevCharWasEscapeChar = false; @@ -321,6 +413,7 @@ public static string Unescape(string pattern) { prevCharWasEscapeChar = true; } + continue; } @@ -347,17 +440,17 @@ public static string Unescape(string pattern) string s = null; - if (tempIndex > 0) + if (tempIndex == pattern.Length) { - s = new string(temp, 0, tempIndex); + s = pattern; } else { - s = String.Empty; + s = new string(temp.Slice(0, tempIndex)); } return s; - } // Unescape + } private static bool IsWildcardChar(char ch) { @@ -393,7 +486,6 @@ public string ToWql() /// /// Thrown when a wildcard pattern is invalid. /// - [Serializable] public class WildcardPatternException : RuntimeException { /// @@ -404,18 +496,17 @@ public class WildcardPatternException : RuntimeException /// /// ErrorRecord object containing additional information about the error condition. /// - /// constructed object + /// Constructed object. internal WildcardPatternException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord)) { - if (null == errorRecord) - { - throw new ArgumentNullException("errorRecord"); - } + ArgumentNullException.ThrowIfNull(errorRecord); + _errorRecord = errorRecord; } + [NonSerialized] - private ErrorRecord _errorRecord; + private readonly ErrorRecord _errorRecord; /// /// Constructs an instance of the WildcardPatternException object. @@ -428,7 +519,7 @@ public WildcardPatternException() /// Constructs an instance of the WildcardPatternException object taking /// a message parameter to use in constructing the exception. /// - /// The string to use as the exception message + /// The string to use as the exception message. public WildcardPatternException(string message) : base(message) { } @@ -437,7 +528,7 @@ public WildcardPatternException(string message) : base(message) /// Constructor for class WildcardPatternException that takes both a message to use /// and an inner exception to include in this object. /// - /// The exception message to use + /// The exception message to use. /// The innerException object to encapsulate. public WildcardPatternException(string message, Exception innerException) @@ -448,12 +539,13 @@ public WildcardPatternException(string message, /// /// Constructor for class WildcardPatternException for serialization. /// - /// serialization information - /// streaming context + /// 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(); } } @@ -512,7 +604,7 @@ protected virtual void EndWildcardPattern() /// /// Bracket expressions of are /// a greatly simplified version of bracket expressions of POSIX wildcards - /// (http://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html). + /// (https://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html). /// Only literal characters and character ranges are supported. /// Negation (with either '!' or '^' characters), /// character classes ([:alpha:]) @@ -589,8 +681,8 @@ internal void AppendBracketExpression(string brackedExpressionContents, string b /// Parses , calling appropriate overloads /// in /// - /// Pattern to parse - /// Parser to call back + /// Pattern to parse. + /// Parser to call back. public static void Parse(WildcardPattern pattern, WildcardPatternParser parser) { parser.BeginWildcardPattern(pattern); @@ -609,7 +701,7 @@ public static void Parse(WildcardPattern pattern, WildcardPatternParser parser) // An unescaped closing square bracket closes the character set. In other // words, there are no nested square bracket expressions // This is different than the POSIX spec - // (at http://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html), + // (at https://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html), // but we are keeping this behavior for back-compatibility. insideCharacterRange = false; @@ -688,10 +780,10 @@ internal static WildcardPatternException NewWildcardPatternException(string inva return e; } - }; + } /// - /// Convert a string with wild cards into its equivalent regex + /// Convert a string with wild cards into its equivalent regex. /// /// /// A list of glob patterns and their equivalent regexes @@ -711,6 +803,7 @@ internal class WildcardPatternToRegexParser : WildcardPatternParser private RegexOptions _regexOptions; private const string regexChars = "()[.?*{}^$+|\\"; // ']' is missing on purpose + private static bool IsRegexChar(char ch) { for (int i = 0; i < regexChars.Length; i++) @@ -732,10 +825,12 @@ internal static RegexOptions TranslateWildcardOptionsIntoRegexOptions(WildcardOp { regexOptions |= RegexOptions.Compiled; } + if ((options & WildcardOptions.IgnoreCase) != 0) { regexOptions |= RegexOptions.IgnoreCase; } + if ((options & WildcardOptions.CultureInvariant) == WildcardOptions.CultureInvariant) { regexOptions |= RegexOptions.CultureInvariant; @@ -758,6 +853,7 @@ internal static void AppendLiteralCharacter(StringBuilder regexPattern, char c) { regexPattern.Append('\\'); } + regexPattern.Append(c); } @@ -794,6 +890,7 @@ protected override void EndWildcardPattern() { _regexPattern.Remove(0, 3); } + if (regexPatternString.EndsWith(".*$", StringComparison.Ordinal)) { _regexPattern.Remove(_regexPattern.Length - 3, 3); @@ -825,6 +922,7 @@ internal static void AppendLiteralCharacterToBracketExpression(StringBuilder reg AppendLiteralCharacter(regexPattern, c); } } + protected override void AppendLiteralCharacterToBracketExpression(char c) { AppendLiteralCharacterToBracketExpression(_regexPattern, c); @@ -855,7 +953,7 @@ protected override void EndBracketExpression() /// /// Parses a into a /// - /// Wildcard pattern to parse + /// Wildcard pattern to parse. /// Regular expression equivalent to public static Regex Parse(WildcardPattern wildcardPattern) { @@ -863,7 +961,7 @@ public static Regex Parse(WildcardPattern wildcardPattern) WildcardPatternParser.Parse(wildcardPattern, parser); try { - return new Regex(parser._regexPattern.ToString(), parser._regexOptions); + return ParserOps.NewRegex(parser._regexPattern.ToString(), parser._regexOptions); } catch (ArgumentException) { @@ -901,7 +999,7 @@ internal bool IsMatch(string str) // therefore requiring only O(lengthOfPattern) memory for remembering // which states have been already visited // - Wikipedia calls this algorithm the "NFA" algorithm at - // http://en.wikipedia.org/wiki/Regular_expression#Implementations_and_running_times + // https://en.wikipedia.org/wiki/Regular_expression#Implementations_and_running_times var patternPositionsForCurrentStringPosition = new PatternPositionsVisitor(_patternElements.Length); @@ -910,43 +1008,51 @@ internal bool IsMatch(string str) var patternPositionsForNextStringPosition = new PatternPositionsVisitor(_patternElements.Length); - for (int currentStringPosition = 0; - currentStringPosition < str.Length; - currentStringPosition++) + try { - char currentStringCharacter = _characterNormalizer.Normalize(str[currentStringPosition]); - patternPositionsForCurrentStringPosition.StringPosition = currentStringPosition; - patternPositionsForNextStringPosition.StringPosition = currentStringPosition + 1; + for (int currentStringPosition = 0; + currentStringPosition < str.Length; + currentStringPosition++) + { + char currentStringCharacter = _characterNormalizer.Normalize(str[currentStringPosition]); + patternPositionsForCurrentStringPosition.StringPosition = currentStringPosition; + patternPositionsForNextStringPosition.StringPosition = currentStringPosition + 1; - int patternPosition; - while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition)) + int patternPosition; + while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition)) + { + _patternElements[patternPosition].ProcessStringCharacter( + currentStringCharacter, + patternPosition, + patternPositionsForCurrentStringPosition, + patternPositionsForNextStringPosition); + } + + // swap patternPositionsForCurrentStringPosition + // with patternPositionsForNextStringPosition + var tmp = patternPositionsForCurrentStringPosition; + patternPositionsForCurrentStringPosition = patternPositionsForNextStringPosition; + patternPositionsForNextStringPosition = tmp; + } + + int patternPosition2; + while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition2)) { - _patternElements[patternPosition].ProcessStringCharacter( - currentStringCharacter, - patternPosition, - patternPositionsForCurrentStringPosition, - patternPositionsForNextStringPosition); + _patternElements[patternPosition2].ProcessEndOfString( + patternPosition2, + patternPositionsForCurrentStringPosition); } - // swap patternPositionsForCurrentStringPosition - // with patternPositionsForNextStringPosition - var tmp = patternPositionsForCurrentStringPosition; - patternPositionsForCurrentStringPosition = patternPositionsForNextStringPosition; - patternPositionsForNextStringPosition = tmp; + return patternPositionsForCurrentStringPosition.ReachedEndOfPattern; } - - int patternPosition2; - while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition2)) + finally { - _patternElements[patternPosition2].ProcessEndOfString( - patternPosition2, - patternPositionsForCurrentStringPosition); + patternPositionsForCurrentStringPosition.Dispose(); + patternPositionsForNextStringPosition.Dispose(); } - - return patternPositionsForCurrentStringPosition.ReachedEndOfPattern; } - private class PatternPositionsVisitor + private sealed class PatternPositionsVisitor : IDisposable { private readonly int _lengthOfPattern; @@ -961,16 +1067,22 @@ public PatternPositionsVisitor(int lengthOfPattern) _lengthOfPattern = lengthOfPattern; - _isPatternPositionVisitedMarker = new int[lengthOfPattern + 1]; - for (int i = 0; i < _isPatternPositionVisitedMarker.Length; i++) + _isPatternPositionVisitedMarker = ArrayPool.Shared.Rent(_lengthOfPattern + 1); + for (int i = 0; i <= _lengthOfPattern; i++) { _isPatternPositionVisitedMarker[i] = -1; } - _patternPositionsForFurtherProcessing = new int[lengthOfPattern]; + _patternPositionsForFurtherProcessing = ArrayPool.Shared.Rent(_lengthOfPattern); _patternPositionsForFurtherProcessingCount = 0; } + public void Dispose() + { + ArrayPool.Shared.Return(_isPatternPositionVisitedMarker, clearArray: true); + ArrayPool.Shared.Return(_patternPositionsForFurtherProcessing, clearArray: true); + } + public int StringPosition { private get; set; } public void Add(int patternPosition) @@ -980,7 +1092,7 @@ public void Add(int patternPosition) patternPosition <= _lengthOfPattern, "Caller should verify patternPosition <= this._lengthOfPattern"); - // is patternPosition already visited?); + // is patternPosition already visited? if (_isPatternPositionVisitedMarker[patternPosition] == this.StringPosition) { return; @@ -1061,7 +1173,7 @@ public override void ProcessEndOfString( } } - private class LiteralCharacterElement : QuestionMarkElement + private sealed class LiteralCharacterElement : QuestionMarkElement { private readonly char _literalCharacter; @@ -1087,7 +1199,7 @@ public override void ProcessStringCharacter( } } - private class BracketExpressionElement : QuestionMarkElement + private sealed class BracketExpressionElement : QuestionMarkElement { private readonly Regex _regex; @@ -1112,7 +1224,7 @@ public override void ProcessStringCharacter( } } - private class AsterixElement : PatternElement + private sealed class AsterixElement : PatternElement { public override void ProcessStringCharacter( char currentStringCharacter, @@ -1136,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; @@ -1198,22 +1310,22 @@ protected override void AppendCharacterRangeToBracketExpression( protected override void EndBracketExpression() { _bracketExpressionBuilder.Append(']'); - Regex regex = new Regex(_bracketExpressionBuilder.ToString(), _regexOptions); + Regex regex = ParserOps.NewRegex(_bracketExpressionBuilder.ToString(), _regexOptions); _patternElements.Add(new BracketExpressionElement(regex)); } } - 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; } @@ -1238,7 +1350,7 @@ public char Normalize(char x) } /// - /// Translates a into a DOS wildcard + /// Translates a into a DOS wildcard. /// internal class WildcardPatternToDosWildcardParser : WildcardPatternParser { @@ -1277,7 +1389,7 @@ protected override void EndBracketExpression() } /// - /// Converts into a DOS wildcard + /// Converts into a DOS wildcard. /// internal static string Parse(WildcardPattern wildcardPattern) { @@ -1287,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 ed2c05091cd..9d52bcb4dc9 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting.Client; using System.Management.Automation.Runspaces.Internal; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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); @@ -238,7 +233,7 @@ internal void ExecuteVoid(Action writeErrorAction) // Create an error record and write it to the stream. ErrorRecord errorRecord = new ErrorRecord( exception, - PSRemotingErrorId.RemoteHostCallFailed.ToString(), + nameof(PSRemotingErrorId.RemoteHostCallFailed), ErrorCategory.InvalidArgument, _remoteHostCall.MethodName); writeErrorAction(errorRecord); diff --git a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs index 11e3b2e52b9..631e49fc189 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs @@ -1,35 +1,35 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using Dbg = System.Management.Automation.Diagnostics; +using System.Collections.ObjectModel; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; -using System.Collections.ObjectModel; + +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces.Internal { /// /// PowerShell client side proxy base which handles invocation - /// of powershell on a remote machine + /// 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 #region Constructors /// - /// Constructor which creates a client remote powershell + /// Constructor which creates a client remote powershell. /// - /// powershell instance + /// Powershell instance. /// The runspace pool associated with /// this shell internal ClientRemotePowerShell(PowerShell shell, RemoteRunspacePoolInternal runspacePool) @@ -50,7 +50,7 @@ internal ClientRemotePowerShell(PowerShell shell, RemoteRunspacePoolInternal run /// /// Instance Id associated with this - /// client remote powershell + /// client remote powershell. /// internal Guid InstanceId { @@ -61,7 +61,7 @@ internal Guid InstanceId } /// - /// PowerShell associated with this ClientRemotePowerShell + /// PowerShell associated with this ClientRemotePowerShell. /// internal PowerShell PowerShell { @@ -72,16 +72,16 @@ internal PowerShell PowerShell } /// - /// Set the state information of the client powershell + /// Set the state information of the client powershell. /// - /// state information to set + /// State information to set. internal void SetStateInfo(PSInvocationStateInfo stateInfo) { shell.SetStateChanged(stateInfo); } /// - /// whether input is available when this object is created + /// Whether input is available when this object is created. /// internal bool NoInput { @@ -92,7 +92,7 @@ internal bool NoInput } /// - /// Input stream associated with this object + /// Input stream associated with this object. /// internal ObjectStreamBase InputStream { @@ -100,6 +100,7 @@ internal ObjectStreamBase InputStream { return inputstream; } + set { inputstream = value; @@ -116,7 +117,7 @@ internal ObjectStreamBase InputStream } /// - /// Output stream associated with this object + /// Output stream associated with this object. /// internal ObjectStreamBase OutputStream { @@ -124,6 +125,7 @@ internal ObjectStreamBase OutputStream { return outputstream; } + set { outputstream = value; @@ -131,7 +133,7 @@ internal ObjectStreamBase OutputStream } /// - /// data structure handler object + /// Data structure handler object. /// internal ClientPowerShellDataStructureHandler DataStructureHandler { @@ -143,7 +145,7 @@ internal ClientPowerShellDataStructureHandler DataStructureHandler /// /// Invocation settings associated with this - /// ClientRemotePowerShell + /// ClientRemotePowerShell. /// internal PSInvocationSettings Settings { @@ -156,7 +158,7 @@ internal PSInvocationSettings Settings /// /// Close the output, error and other collections /// associated with the shell, so that the - /// enumerator does not block + /// enumerator does not block. /// internal void UnblockCollections() { @@ -164,14 +166,11 @@ internal void UnblockCollections() outputstream.Close(); errorstream.Close(); - if (null != inputstream) - { - inputstream.Close(); - } + inputstream?.Close(); } /// - /// Stop the remote powershell asynchronously + /// Stop the remote powershell asynchronously. /// /// This method will be called from /// within the lock on PowerShell. Hence no need @@ -191,7 +190,7 @@ internal void StopAsync() return; } - // powershell CoreStop would have handled cases + // PowerShell CoreStop would have handled cases // for NotStarted, Stopping and already Stopped // so at this point, there is no need to make any // check. The message simply needs to be sent @@ -201,7 +200,6 @@ internal void StopAsync() } /// - /// /// internal void SendInput() { @@ -210,14 +208,14 @@ internal void SendInput() /// /// This event is raised, when a host call is for a remote pipeline - /// which this remote powershell wraps + /// which this remote powershell wraps. /// internal event EventHandler> HostCallReceived; /// - /// Initialize the client remote powershell instance + /// Initialize the client remote powershell instance. /// - /// input for execution + /// Input for execution. /// error stream to which /// data needs to be written to /// informational buffers @@ -250,28 +248,21 @@ internal void Initialize( dataStructureHandler = runspacePool.DataStructureHandler.CreatePowerShellDataStructureHandler(this); // register for events from the data structure handler - dataStructureHandler.InvocationStateInfoReceived += - new EventHandler>(HandleInvocationStateInfoReceived); - dataStructureHandler.OutputReceived += new EventHandler>(HandleOutputReceived); - dataStructureHandler.ErrorReceived += new EventHandler>(HandleErrorReceived); - dataStructureHandler.InformationalMessageReceived += - new EventHandler>(HandleInformationalMessageReceived); - dataStructureHandler.HostCallReceived += - new EventHandler>(HandleHostCallReceived); - dataStructureHandler.ClosedNotificationFromRunspacePool += - new EventHandler>(HandleCloseNotificationFromRunspacePool); - dataStructureHandler.BrokenNotificationFromRunspacePool += - new EventHandler>(HandleBrokenNotificationFromRunspacePool); - dataStructureHandler.ConnectCompleted += new EventHandler>(HandleConnectCompleted); - dataStructureHandler.ReconnectCompleted += new EventHandler>(HandleConnectCompleted); - dataStructureHandler.RobustConnectionNotification += - new EventHandler(HandleRobustConnectionNotification); - dataStructureHandler.CloseCompleted += - new EventHandler(HandleCloseCompleted); - } - - /// - /// Do any clean up operation per initialization here + dataStructureHandler.InvocationStateInfoReceived += HandleInvocationStateInfoReceived; + dataStructureHandler.OutputReceived += HandleOutputReceived; + dataStructureHandler.ErrorReceived += HandleErrorReceived; + dataStructureHandler.InformationalMessageReceived += HandleInformationalMessageReceived; + dataStructureHandler.HostCallReceived += HandleHostCallReceived; + dataStructureHandler.ClosedNotificationFromRunspacePool += HandleCloseNotificationFromRunspacePool; + dataStructureHandler.BrokenNotificationFromRunspacePool += HandleBrokenNotificationFromRunspacePool; + dataStructureHandler.ConnectCompleted += HandleConnectCompleted; + dataStructureHandler.ReconnectCompleted += HandleConnectCompleted; + dataStructureHandler.RobustConnectionNotification += HandleRobustConnectionNotification; + dataStructureHandler.CloseCompleted += HandleCloseCompleted; + } + + /// + /// Do any clean up operation per initialization here. /// internal void Clear() { @@ -279,7 +270,7 @@ internal void Clear() } /// - /// If this client remote powershell has been initialized + /// If this client remote powershell has been initialized. /// internal bool Initialized { @@ -290,7 +281,6 @@ internal bool Initialized } /// - /// /// /// /// @@ -360,10 +350,10 @@ internal PSConnectionRetryStatus ConnectionRetryStatus /// /// An error record is received from the powershell at the /// server side. It is added to the error collection of the - /// client powershell + /// client powershell. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleErrorReceived(object sender, RemoteDataEventArgs eventArgs) { using (s_tracer.TraceEventHandlers()) @@ -376,10 +366,10 @@ private void HandleErrorReceived(object sender, RemoteDataEventArgs /// /// An output object is received from the powershell at the /// server side. It is added to the output collection of the - /// client powershell + /// client powershell. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleOutputReceived(object sender, RemoteDataEventArgs eventArgs) { using (s_tracer.TraceEventHandlers()) @@ -399,10 +389,10 @@ private void HandleOutputReceived(object sender, RemoteDataEventArgs eve /// /// The invocation state of the server powershell has changed. - /// The state of the client powershell is reflected accordingly + /// The state of the client powershell is reflected accordingly. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleInvocationStateInfoReceived(object sender, RemoteDataEventArgs eventArgs) { @@ -471,7 +461,7 @@ private void HandleInvocationStateInfoReceived(object sender, /// and close the remote runspace/pool if the stop call failed due /// to network outage problems. /// - /// Exception + /// Exception. private void CheckAndCloseRunspaceAfterStop(Exception ex) { PSRemotingTransportException transportException = ex as PSRemotingTransportException; @@ -514,8 +504,8 @@ private void CheckAndCloseRunspaceAfterStop(Exception ex) /// Handler for handling any informational message received /// from the server side. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleInformationalMessageReceived(object sender, RemoteDataEventArgs eventArgs) { @@ -529,18 +519,21 @@ private void HandleInformationalMessageReceived(object sender, { informationalBuffers.AddDebug((DebugRecord)infoMessage.Message); } + break; case RemotingDataType.PowerShellVerbose: { informationalBuffers.AddVerbose((VerboseRecord)infoMessage.Message); } + break; case RemotingDataType.PowerShellWarning: { informationalBuffers.AddWarning((WarningRecord)infoMessage.Message); } + break; case RemotingDataType.PowerShellProgress: @@ -549,19 +542,20 @@ private void HandleInformationalMessageReceived(object sender, typeof(ProgressRecord), System.Globalization.CultureInfo.InvariantCulture); informationalBuffers.AddProgress(progress); } + break; case RemotingDataType.PowerShellInformationStream: { informationalBuffers.AddInformation((InformationRecord)infoMessage.Message); } + break; } } } /// - /// /// /// /// @@ -648,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; @@ -666,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 || @@ -674,9 +668,9 @@ private bool IsFinished(PSInvocationState state) } /// - /// Execute the specified host call + /// Execute the specified host call. /// - /// host call to execute + /// Host call to execute. private void ExecuteHostCall(RemoteHostCall hostcall) { if (hostcall.IsVoidMethod) @@ -696,7 +690,6 @@ private void ExecuteHostCall(RemoteHostCall hostcall) } /// - /// /// /// /// @@ -727,9 +720,9 @@ private void HandleCloseNotificationFromRunspacePool(object sender, /// Handles notification from RunspacePool indicating /// that the pool is broken. This sets the state of /// all the powershell objects associated with the - /// runspace pool to Failed + /// runspace pool to Failed. /// - /// sender of this information, unused + /// Sender of this information, unused. /// arguments describing this event /// contains information on the reason associated with the /// runspace pool entering a Broken state @@ -826,6 +819,7 @@ private void HandleRobustConnectionNotification( new PSConnectionRetryStatusEventArgs(PSConnectionRetryStatus.AutoDisconnectStarting, this.computerName, maxRetryConnectionTimeMinutes, warningRecord); } + break; case ConnectionStatus.AutoDisconnectSucceeded: @@ -850,6 +844,7 @@ private void HandleRobustConnectionNotification( new PSConnectionRetryStatusEventArgs(PSConnectionRetryStatus.InternalErrorAbort, this.computerName, maxRetryConnectionTimeMinutes, errorRecord); } + break; } @@ -904,62 +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,10 +961,10 @@ internal enum PSConnectionRetryStatus AutoDisconnectStarting = 4, AutoDisconnectSucceeded = 5, InternalErrorAbort = 6 - }; + } /// - /// PSConnectionRetryStatusEventArgs + /// PSConnectionRetryStatusEventArgs. /// internal sealed class PSConnectionRetryStatusEventArgs : EventArgs { diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index 4119000e265..adf47d04cab 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -1,21 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Management.Automation.Language; -using System.Management.Automation.Remoting.Internal; -using System.Management.Automation.Tracing; using System.Management.Automation.Remoting; +using System.Management.Automation.Remoting.Internal; using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; +using System.Management.Automation.Tracing; using System.Runtime.Serialization; -using System.Threading; using System.Text; +using System.Threading; + using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; // Stops compiler from warning about unknown warnings @@ -25,29 +26,29 @@ namespace System.Management.Automation { /// /// Enumeration for job status values. Indicates the status - /// of the result object + /// of the result object. /// public enum JobState { /// - /// Execution of command in job not started + /// Execution of command in job not started. /// NotStarted = 0, /// - /// execution of command in progress + /// Execution of command in progress. /// Running = 1, /// - /// execution of command completed in all - /// computernames/runspaces + /// Execution of command completed in all + /// computernames/runspaces. /// Completed = 2, /// /// An error was encountered when trying to executed - /// command in one or more computernames/runspaces + /// command in one or more computernames/runspaces. /// Failed = 3, @@ -63,7 +64,7 @@ public enum JobState Blocked = 5, /// - /// The job has been suspended + /// The job has been suspended. /// Suspended = 6, @@ -73,12 +74,12 @@ public enum JobState Disconnected = 7, /// - /// Suspend is in progress + /// Suspend is in progress. /// Suspending = 8, /// - /// Stop is in progress + /// Stop is in progress. /// Stopping = 9, @@ -92,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 { /// @@ -160,7 +160,7 @@ public InvalidJobStateException(JobState currentState, string actionMessage) /// Initializes a new instance of the InvalidPSJobStateException and defines value of /// CurrentState. /// - /// Current state of powershell + /// Current state of powershell. internal InvalidJobStateException(JobState currentState) : base ( @@ -190,16 +190,17 @@ 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 /// - /// Gets CurrentState of the Job + /// Gets CurrentState of the Job. /// public JobState CurrentState { @@ -213,12 +214,12 @@ public JobState CurrentState /// State of job when exception was thrown. /// [NonSerialized] - private JobState _currState = 0; + private readonly JobState _currState = 0; } /// /// Type which has information about JobState and Exception - /// ,if any, associated with JobState + /// ,if any, associated with JobState. /// public sealed class JobStateInfo { @@ -227,7 +228,7 @@ public sealed class JobStateInfo /// /// Constructor for state changes not resulting from an error. /// - /// Execution state + /// Execution state. public JobStateInfo(JobState state) : this(state, null) { @@ -247,9 +248,9 @@ public JobStateInfo(JobState state, Exception reason) } /// - /// Copy constructor to support cloning + /// Copy constructor to support cloning. /// - /// source information + /// Source information. /// /// ArgumentNullException when is null. /// @@ -259,7 +260,6 @@ internal JobStateInfo(JobStateInfo jobStateInfo) Reason = jobStateInfo.Reason; } - #endregion constructors #region public_properties @@ -285,7 +285,7 @@ internal JobStateInfo(JobStateInfo jobStateInfo) #endregion public_properties /// - /// override for ToString() + /// Override for ToString() /// /// public override string ToString() @@ -294,9 +294,9 @@ public override string ToString() } /// - /// Clones this object + /// Clones this object. /// - /// Cloned object + /// Cloned object. internal JobStateInfo Clone() { return new JobStateInfo(this); @@ -316,7 +316,7 @@ public sealed class JobStateEventArgs : EventArgs #region constructors /// - /// Constructor of JobStateEventArgs + /// Constructor of JobStateEventArgs. /// /// The current state of the job. public JobStateEventArgs(JobStateInfo jobStateInfo) @@ -325,7 +325,7 @@ public JobStateEventArgs(JobStateInfo jobStateInfo) } /// - /// Constructor of JobStateEventArgs + /// Constructor of JobStateEventArgs. /// /// The current state of the job. /// The previous state of the job. @@ -333,8 +333,9 @@ public JobStateEventArgs(JobStateInfo jobStateInfo, JobStateInfo previousJobStat { if (jobStateInfo == null) { - throw PSTraceSource.NewArgumentNullException("jobStateInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(jobStateInfo)); } + JobStateInfo = jobStateInfo; PreviousJobStateInfo = previousJobStateInfo; } @@ -365,24 +366,26 @@ public sealed class JobIdentifier internal JobIdentifier(int id, Guid instanceId) { if (id <= 0) - PSTraceSource.NewArgumentException("id", RemotingErrorIdStrings.JobSessionIdLessThanOne, id); + PSTraceSource.NewArgumentException(nameof(id), RemotingErrorIdStrings.JobSessionIdLessThanOne, id); Id = id; InstanceId = instanceId; } - internal int Id { get; private set; } + internal int Id { get; } + internal Guid InstanceId { get; private set; } } /// /// Interface to expose a job debugger. /// +#nullable enable public interface IJobDebugger { /// - /// Job Debugger + /// Job Debugger. /// - Debugger Debugger + Debugger? Debugger { get; } @@ -396,6 +399,7 @@ bool IsAsync set; } } +#nullable restore /// /// Represents a command running in background. A job object can internally @@ -406,7 +410,7 @@ public abstract class Job : IDisposable #region Constructor /// - /// Default constructor + /// Default constructor. /// protected Job() { @@ -414,9 +418,9 @@ protected Job() } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object + /// Command invoked by this job object. protected Job(string command) : this() { @@ -425,25 +429,25 @@ protected Job(string command) } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object + /// Command invoked by this job object. + /// Friendly name for the job object. protected Job(string command, string name) : this(command) { - if (!String.IsNullOrEmpty(name)) + if (!string.IsNullOrEmpty(name)) { _name = name; } } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object - /// Child jobs of this job object + /// Command invoked by this job object. + /// Friendly name for the job object. + /// Child jobs of this job object. protected Job(string command, string name, IList childJobs) : this(command, name) { @@ -451,20 +455,20 @@ protected Job(string command, string name, IList childJobs) } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object - /// Id and InstanceId pair to be used for this job object + /// Command invoked by this job object. + /// Friendly name for the job object. + /// Id and InstanceId pair to be used for this job object. /// The JobIdentifier is a token that must be issued by PowerShell to allow /// reuse of the Id. This is the only way to set either Id or instance Id. protected Job(string command, string name, JobIdentifier token) { if (token == null) - throw PSTraceSource.NewArgumentNullException("token", RemotingErrorIdStrings.JobIdentifierNull); + throw PSTraceSource.NewArgumentNullException(nameof(token), RemotingErrorIdStrings.JobIdentifierNull); if (token.Id > s_jobIdSeed) { - throw PSTraceSource.NewArgumentException("token", RemotingErrorIdStrings.JobIdNotYetAssigned, token.Id); + throw PSTraceSource.NewArgumentException(nameof(token), RemotingErrorIdStrings.JobIdNotYetAssigned, token.Id); } Command = command; @@ -472,7 +476,7 @@ protected Job(string command, string name, JobIdentifier token) Id = token.Id; InstanceId = token.InstanceId; - if (!String.IsNullOrEmpty(name)) + if (!string.IsNullOrEmpty(name)) { _name = name; } @@ -483,11 +487,11 @@ protected Job(string command, string name, JobIdentifier token) } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object - /// InstanceId to be used for this job object + /// Command invoked by this job object. + /// Friendly name for the job object. + /// InstanceId to be used for this job object. /// The InstanceId may need to be set to maintain job identity across /// instances of the process. protected Job(string command, string name, Guid instanceId) @@ -508,7 +512,7 @@ internal static string GetCommandTextFromInvocationInfo(InvocationInfo invocatio { Dbg.Assert(scriptExtent.StartScriptPosition.ColumnNumber > 0, "Column numbers start at 1"); Dbg.Assert(scriptExtent.StartScriptPosition.ColumnNumber <= scriptExtent.StartScriptPosition.Line.Length, "Column numbers are not greater than the length of a line"); - return scriptExtent.StartScriptPosition.Line.Substring(scriptExtent.StartScriptPosition.ColumnNumber - 1).Trim(); + return scriptExtent.StartScriptPosition.Line.AsSpan(scriptExtent.StartScriptPosition.ColumnNumber - 1).Trim().ToString(); } return invocationInfo.InvocationName; @@ -523,7 +527,7 @@ internal static string GetCommandTextFromInvocationInfo(InvocationInfo invocatio private string _name; private IList _childJobs; internal readonly object syncObject = new object(); // object used for synchronization - //ISSUE: Should Result be public property + // ISSUE: Should Result be public property private PSDataCollection _results = new PSDataCollection(); private bool _resultsOwner = true; private PSDataCollection _error = new PSDataCollection(); @@ -542,18 +546,18 @@ internal static string GetCommandTextFromInvocationInfo(InvocationInfo invocatio private bool _outputOwner = true; /// - /// Static variable which is incremented to generate id + /// Static variable which is incremented to generate id. /// private static int s_jobIdSeed = 0; - private string _jobTypeName = String.Empty; + private string _jobTypeName = string.Empty; #endregion Private Members #region Job Properties /// - /// Command Invoked by this Job + /// Command Invoked by this Job. /// public string Command { get; } @@ -591,25 +595,26 @@ public WaitHandle Finished } /// - /// unique identifier for this job + /// Unique identifier for this job. /// public Guid InstanceId { get; } = Guid.NewGuid(); /// /// Short identifier for this result which will be - /// recycled and used within a process + /// recycled and used within a process. /// - public Int32 Id { get; } + public int Id { get; } /// - /// Name for identifying this job object + /// Name for identifying this job object. /// - public String Name + public string Name { get { return _name; } + set { AssertNotDisposed(); @@ -618,7 +623,7 @@ public String Name } /// - /// List of child jobs contained within this job + /// List of child jobs contained within this job. /// public IList ChildJobs { @@ -628,20 +633,18 @@ public IList ChildJobs { lock (syncObject) { - if (_childJobs == null) - { - _childJobs = new List(); - } + _childJobs ??= new List(); } } + return _childJobs; } } - /// + /// /// Success status of the command execution. /// - public abstract String StatusMessage { get; } + public abstract string StatusMessage { get; } /// /// Indicates that more data is available in this @@ -664,7 +667,11 @@ public IList ChildJobs /// public string PSJobTypeName { - get { return _jobTypeName; } + get + { + return _jobTypeName; + } + protected internal set { _jobTypeName = value ?? this.GetType().ToString(); @@ -676,7 +683,7 @@ protected internal set /// /// Result objects from this job. If this object is not a /// leaf node (with no children), then this will - /// aggregate the results from all child jobs + /// aggregate the results from all child jobs. /// internal PSDataCollection Results { @@ -684,12 +691,14 @@ internal PSDataCollection Results { return _results; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Results"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -701,7 +710,7 @@ internal PSDataCollection Results /// /// Indicates if a particular Job type uses the - /// internal results collection + /// internal results collection. /// internal bool UsesResultsCollection { get; set; } @@ -724,7 +733,7 @@ internal virtual void WriteObject(object outputObject) /// /// Allows propagating of terminating exceptions from remote "throw" statement - /// (normally / by default all remote errors are transformed into non-terminating errors + /// (normally / by default all remote errors are transformed into non-terminating errors. /// internal bool PropagateThrows { get; set; } @@ -743,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 = @@ -772,6 +779,7 @@ internal virtual void WriteError(ErrorRecord errorRecord) return; } } + Results.Add(new PSStreamObject(PSStreamObjectType.Error, errorRecord)); } @@ -779,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) @@ -823,6 +831,7 @@ internal virtual void WriteInformation(InformationRecord informationRecord) } private Lazy _parentActivityId; + internal void SetParentActivityIdGetter(Func parentActivityIdGetter) { Dbg.Assert(parentActivityIdGetter != null, "Caller should verify parentActivityIdGetter != null"); @@ -849,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( @@ -886,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 }; @@ -903,17 +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 @@ -990,25 +1000,32 @@ protected virtual void DoLoadJobStreams() /// /// Unloads job streams information. Enables jobs to - /// clear stream information from memory + /// clear stream information from memory. /// protected virtual void DoUnloadJobStreams() { } /// - /// Load the required job streams + /// Load the required job streams. /// public void LoadJobStreams() { - if (_jobStreamsLoaded) return; + if (_jobStreamsLoaded) + { + return; + } lock (syncObject) { - if (_jobStreamsLoaded) return; + if (_jobStreamsLoaded) + { + return; + } _jobStreamsLoaded = true; } + try { DoLoadJobStreams(); @@ -1024,10 +1041,11 @@ public void LoadJobStreams() } } } + private bool _jobStreamsLoaded; /// - /// Unload the required job streams + /// Unload the required job streams. /// public void UnloadJobStreams() { @@ -1073,12 +1091,14 @@ public PSDataCollection Output LoadJobStreams(); // for delayed loading return _output; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Output"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1107,12 +1127,14 @@ public PSDataCollection Error LoadJobStreams(); // for delayed loading return _error; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Error"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1141,12 +1163,14 @@ public PSDataCollection Progress LoadJobStreams(); // for delayed loading return _progress; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Progress"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1172,12 +1196,14 @@ public PSDataCollection Verbose LoadJobStreams(); // for delayed loading return _verbose; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Verbose"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1206,12 +1232,14 @@ public PSDataCollection Debug LoadJobStreams(); // for delayed loading return _debug; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Debug"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1223,7 +1251,7 @@ public PSDataCollection Debug /// /// Gets or sets the warning buffer. Warnings of job are written to - /// this buffer + /// this buffer. /// /// /// Cannot set to a null value. @@ -1239,12 +1267,14 @@ public PSDataCollection Warning LoadJobStreams(); // for delayed loading return _warning; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Warning"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1257,7 +1287,7 @@ public PSDataCollection Warning /// /// Gets or sets the information buffer. Information records of job are written to - /// this buffer + /// this buffer. /// /// /// Cannot set to a null value. @@ -1273,12 +1303,14 @@ public PSDataCollection Information LoadJobStreams(); // for delayed loading return _information; } + set { - if (null == value) + if (value == null) { throw PSTraceSource.NewArgumentNullException("Information"); } + lock (syncObject) { AssertChangesAreAccepted(); @@ -1290,9 +1322,9 @@ public PSDataCollection Information } /// - /// Indicates a location where this job is running + /// Indicates a location where this job is running. /// - public abstract String Location { get; } + public abstract string Location { get; } #endregion results @@ -1312,7 +1344,7 @@ internal virtual bool CanDisconnect /// Returns runspaces associated with the Job, including /// child jobs. /// - /// IEnumerable of RemoteRunspaces + /// IEnumerable of RemoteRunspaces. internal virtual IEnumerable GetRunspaces() { return null; @@ -1325,7 +1357,7 @@ internal virtual IEnumerable GetRunspaces() #region Job State and State Change Event /// - /// Event raised when state of the job changes + /// Event raised when state of the job changes. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] public event EventHandler StateChanged; @@ -1402,8 +1434,8 @@ internal void SetJobState(JobState state, Exception reason) } #pragma warning disable 56500 - //Exception raised in the eventhandler are not error in job. - //silently ignore them. + // Exception raised in the eventhandler are not error in job. + // silently ignore them. try { tracer.WriteMessage("Job", "SetJobState", Guid.Empty, this, "Invoking StateChanged event", null); @@ -1422,10 +1454,7 @@ internal void SetJobState(JobState state, Exception reason) { lock (syncObject) { - if (_finished != null) - { - _finished.Set(); - } + _finished?.Set(); } } #pragma warning restore 56500 @@ -1449,9 +1478,9 @@ internal void SetJobState(JobState state, Exception reason) /// /// Returns the items in results collection /// after clearing up all the internal - /// structures + /// structures. /// - /// collection of stream objects + /// Collection of stream objects. internal Collection ReadAll() { Output.Clear(); @@ -1465,7 +1494,7 @@ internal Collection ReadAll() } /// - /// Helper function to check if job is finished + /// Helper function to check if job is finished. /// /// /// @@ -1511,13 +1540,13 @@ private void AssertChangesAreAccepted() /// /// Automatically generate a job name if the user - /// does not supply one + /// does not supply one. /// - /// auto generated job name + /// Auto generated job name. /// Since the user can script/program against the /// job name, the auto generated name will not be /// localizable - protected String AutoGenerateJobName() + protected string AutoGenerateJobName() { return "Job" + Id.ToString(System.Globalization.NumberFormatInfo.InvariantInfo); } @@ -1543,15 +1572,22 @@ internal void AssertNotDisposed() internal void CloseAllStreams() { // The Complete() method includes raising public notification events that third parties can - // handle and potentially throw exceptions on the notification thread. We don't want to + // handle and potentially throw exceptions on the notification thread. We don't want to // propagate those exceptions because it prevents this thread from completing its processing. if (_resultsOwner) { try { _results.Complete(); } catch (Exception e) { TraceException(e); } } + if (_outputOwner) { try { _output.Complete(); } catch (Exception e) { TraceException(e); } } + if (_errorOwner) { try { _error.Complete(); } catch (Exception e) { TraceException(e); } } + if (_progressOwner) { try { _progress.Complete(); } catch (Exception e) { TraceException(e); } } + if (_verboseOwner) { try { _verbose.Complete(); } catch (Exception e) { TraceException(e); } } + if (_warningOwner) { try { _warning.Complete(); } catch (Exception e) { TraceException(e); } } + if (_debugOwner) { try { _debug.Complete(); } catch (Exception e) { TraceException(e); } } + if (_informationOwner) { try { _information.Complete(); } catch (Exception e) { TraceException(e); } } } @@ -1564,24 +1600,24 @@ private static void TraceException(Exception e) } /// - /// Gets the job for the specified location + /// Gets the job for the specified location. /// - /// location to filter on - /// collection of jobs - internal List GetJobsForLocation(String location) + /// Location to filter on. + /// Collection of jobs. + internal List GetJobsForLocation(string location) { List returnJobList = new List(); foreach (Job job in ChildJobs) { - if (String.Equals(job.Location, location, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(job.Location, location, StringComparison.OrdinalIgnoreCase)) { returnJobList.Add(job); } } return returnJobList; - } // GetJobsForLocation + } #endregion Private/Internal Methods @@ -1616,7 +1652,7 @@ protected virtual void Dispose(bool disposing) // release the WaitHandle lock (syncObject) { - if (null != _finished) + if (_finished != null) { _finished.Dispose(); _finished = null; @@ -1637,6 +1673,7 @@ protected virtual void Dispose(bool disposing) } } } + private bool _isDisposed; #endregion IDisposable Members @@ -1648,7 +1685,7 @@ protected virtual void Dispose(bool disposing) private bool _processingOutput; /// - /// MonitorOutputProcessing + /// MonitorOutputProcessing. /// internal bool MonitorOutputProcessing { @@ -1679,7 +1716,7 @@ private void HandleOutputProcessingStateChanged(object sender, OutputProcessingS } #endregion - } //Job + } /// /// Top level job object for remoting. This contains multiple child job @@ -1694,7 +1731,7 @@ internal class PSRemotingJob : Job /// /// Internal constructor for initializing PSRemotingJob using - /// computer names + /// computer names. /// /// names of computers for /// which the job object is being created @@ -1706,15 +1743,14 @@ internal class PSRemotingJob : Job /// a friendly name for the job object /// [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal PSRemotingJob(String[] computerNames, - List computerNameHelpers, String remoteCommand, string name) - : - this(computerNames, computerNameHelpers, remoteCommand, 0, name) + internal PSRemotingJob(string[] computerNames, + List computerNameHelpers, string remoteCommand, string name) + : this(computerNames, computerNameHelpers, remoteCommand, 0, name) { } /// /// Internal constructor for initializing job using - /// PSSession objects + /// PSSession objects. /// /// array of runspace info /// objects on which the remote command is executed @@ -1725,15 +1761,13 @@ internal PSRemotingJob(String[] computerNames, /// a friendly name for the job object /// internal PSRemotingJob(PSSession[] remoteRunspaceInfos, - List runspaceHelpers, String remoteCommand, string name) - : - this(remoteRunspaceInfos, runspaceHelpers, remoteCommand, 0, name) + List runspaceHelpers, string remoteCommand, string name) + : this(remoteRunspaceInfos, runspaceHelpers, remoteCommand, 0, name) { } - /// /// Internal constructor for initializing PSRemotingJob using - /// computer names + /// computer names. /// /// names of computers for /// which the result object is being created @@ -1742,23 +1776,23 @@ internal PSRemotingJob(PSSession[] remoteRunspaceInfos, /// /// remote command corresponding to this /// result object - /// Throttle limit to use - /// a friendly name for the job object - internal PSRemotingJob(String[] computerNames, - List computerNameHelpers, String remoteCommand, + /// Throttle limit to use. + /// A friendly name for the job object. + internal PSRemotingJob(string[] computerNames, + List computerNameHelpers, string remoteCommand, int throttleLimit, string name) : base(remoteCommand, name) { // Create child jobs for each object in the list foreach (ExecutionCmdletHelperComputerName helper in computerNameHelpers) { - //Create Child Job and Register for its StateChanged Event + // Create Child Job and Register for its StateChanged Event PSRemotingChildJob childJob = new PSRemotingChildJob(remoteCommand, helper, _throttleManager); - childJob.StateChanged += new EventHandler(HandleChildJobStateChanged); - childJob.JobUnblocked += new EventHandler(HandleJobUnblocked); + childJob.StateChanged += HandleChildJobStateChanged; + childJob.JobUnblocked += HandleJobUnblocked; - //Add this job to list of childjobs + // Add this job to list of childjobs ChildJobs.Add(childJob); } @@ -1767,7 +1801,7 @@ internal PSRemotingJob(String[] computerNames, /// /// Internal constructor for initializing job using - /// PSSession objects + /// PSSession objects. /// /// array of runspace info /// objects on which the remote command is executed @@ -1775,10 +1809,10 @@ internal PSRemotingJob(String[] computerNames, /// runspaces /// remote command corresponding to this /// result object - /// throttle limit to use + /// Throttle limit to use. /// internal PSRemotingJob(PSSession[] remoteRunspaceInfos, - List runspaceHelpers, String remoteCommand, + List runspaceHelpers, string remoteCommand, int throttleLimit, string name) : base(remoteCommand, name) { @@ -1787,15 +1821,16 @@ internal PSRemotingJob(PSSession[] remoteRunspaceInfos, { ExecutionCmdletHelperRunspace helper = (ExecutionCmdletHelperRunspace)runspaceHelpers[i]; - //Create Child Job object and Register for its state changed event + // Create Child Job object and Register for its state changed event PSRemotingChildJob job = new PSRemotingChildJob(remoteCommand, helper, _throttleManager); - job.StateChanged += new EventHandler(HandleChildJobStateChanged); - job.JobUnblocked += new EventHandler(HandleJobUnblocked); + job.StateChanged += HandleChildJobStateChanged; + job.JobUnblocked += HandleJobUnblocked; - //Add the child job to list of child jobs + // Add the child job to list of child jobs ChildJobs.Add(job); } + CommonInit(throttleLimit, runspaceHelpers); } @@ -1819,8 +1854,8 @@ internal PSRemotingJob(List helpers, foreach (ExecutionCmdletHelper helper in helpers) { PSRemotingChildJob job = new PSRemotingChildJob(helper, _throttleManager, aggregateResults); - job.StateChanged += new EventHandler(HandleChildJobStateChanged); - job.JobUnblocked += new EventHandler(HandleJobUnblocked); + job.StateChanged += HandleChildJobStateChanged; + job.JobUnblocked += HandleJobUnblocked; ChildJobs.Add(job); } @@ -1839,17 +1874,17 @@ internal PSRemotingJob(List helpers, } /// - /// Default constructor + /// Default constructor. /// protected PSRemotingJob() { } /// - /// Initialization common to both constructors + /// Initialization common to both constructors. /// private void CommonInit(int throttleLimit, List helpers) { - //Since no results are produced by any streams. We should - //close all the streams + // Since no results are produced by any streams. We should + // close all the streams base.CloseAllStreams(); // set status to "in progress" @@ -1865,20 +1900,19 @@ private void CommonInit(int throttleLimit, List helpers) #region internal methods /// - /// Get entity result for the specified computer + /// Get entity result for the specified computer. /// /// computername for which entity /// result is required - /// entity result - internal List GetJobsForComputer(String computerName) + /// Entity result. + internal List GetJobsForComputer(string computerName) { List returnJobList = new List(); foreach (Job j in ChildJobs) { - PSRemotingChildJob child = j as PSRemotingChildJob; - if (child == null) continue; - if (String.Equals(child.Runspace.ConnectionInfo.ComputerName, computerName, + if (j is not PSRemotingChildJob child) continue; + if (string.Equals(child.Runspace.ConnectionInfo.ComputerName, computerName, StringComparison.OrdinalIgnoreCase)) { returnJobList.Add(child); @@ -1886,36 +1920,36 @@ internal List GetJobsForComputer(String computerName) } return returnJobList; - } // GetResultForComputer + } /// - /// Get entity result for the specified runspace + /// Get entity result for the specified runspace. /// /// runspace for which entity /// result is required - /// entity result + /// Entity result. internal List GetJobsForRunspace(PSSession runspace) { List returnJobList = new List(); 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); } } + return returnJobList; - } // GetResultForRunspace + } /// - /// Get entity result for the specified helper object + /// Get entity result for the specified helper object. /// /// helper for which entity /// result is required - /// entity result + /// Entity result. internal List GetJobsForOperation(IThrottleOperation operation) { List returnJobList = new List(); @@ -1923,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); @@ -1932,7 +1965,7 @@ internal List GetJobsForOperation(IThrottleOperation operation) } return returnJobList; - } // GetResultForHelper + } #endregion internal methods @@ -1986,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 @@ -2018,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) { @@ -2148,6 +2178,7 @@ internal void InternalStopJob() { return; } + _stopIsCalled = true; } @@ -2160,7 +2191,7 @@ internal void InternalStopJob() private bool _moreData = true; /// - /// indicates if more data is available + /// Indicates if more data is available. /// /// /// This has more data if any of the child jobs have more data. @@ -2193,10 +2224,9 @@ public override bool HasMoreData } } - private bool _stopIsCalled = false; /// - /// Stop Job + /// Stop Job. /// public override void StopJob() { @@ -2241,7 +2271,7 @@ public override void StopJob() private string _statusMessage; /// - /// Message indicating status of the job + /// Message indicating status of the job. /// public override string StatusMessage { @@ -2252,34 +2282,38 @@ public override string StatusMessage } /// - /// used by Invoke-Command cmdlet to show/hide computername property value. + /// Used by Invoke-Command cmdlet to show/hide computername property value. /// Format and Output has capability to understand RemoteObjects and this property lets /// Format and Output decide whether to show/hide computername. /// Default is true. /// internal bool HideComputerName { - get { return _hideComputerName; } + get + { + return _hideComputerName; + } + set { _hideComputerName = value; foreach (Job job in this.ChildJobs) { PSRemotingChildJob rJob = job as PSRemotingChildJob; - if (null != rJob) + if (rJob != null) { rJob.HideComputerName = value; } } } } + private bool _hideComputerName = true; - //ISSUE: Implement StatusMessage + // ISSUE: Implement StatusMessage /// - /// Checks the status of remote command execution + /// Checks the status of remote command execution. /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] private void SetStatusMessage() { @@ -2293,7 +2327,7 @@ private void SetStatusMessage() // { // if (finishedCount == ChildJobs.Count) // { - // //ISSUE: Change this code to look in to child jobs for exception + // // ISSUE: Change this code to look in to child jobs for exception // if (errors.Count > 0) // { // statusMessage = "LocalErrors"; @@ -2317,16 +2351,16 @@ private void SetStatusMessage() // } // break; // } - // } // for (int ... + // } // setFinished = true; - // } // if (finishedCount ... - // } // lock... - } // 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 @@ -2342,7 +2376,7 @@ private void SetStatusMessage() private int _debugChildJobsCount = 0; /// - /// Handles the StateChanged event from each of the child job objects + /// Handles the StateChanged event from each of the child job objects. /// /// /// @@ -2388,14 +2422,15 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) } } - //Ignore state changes which are not resulting in state change to finished. + // Ignore state changes which are not resulting in state change to finished. if (!IsFinishedState(e.JobStateInfo.State)) { return; } + if (e.JobStateInfo.State == JobState.Failed) { - //If any of the child job failed, we set status to failed + // If any of the child job failed, we set status to failed _atleastOneChildJobFailed = true; } @@ -2404,18 +2439,19 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) { _finishedChildJobsCount++; - //We are done + // We are done if (_finishedChildJobsCount + _disconnectedChildJobsCount == ChildJobs.Count) { allChildJobsFinished = true; } } + if (allChildJobsFinished) { - //if any child job failed, set status to failed - //If stop was called set, status to stopped - //else completed + // if any child job failed, set status to failed + // If stop was called set, status to stopped + // else completed if (_disconnectedChildJobsCount > 0) { SetJobState(JobState.Disconnected); @@ -2424,7 +2460,7 @@ private void HandleChildJobStateChanged(object sender, JobStateEventArgs e) { SetJobState(JobState.Failed); } - else if (_stopIsCalled == true) + else if (_stopIsCalled) { SetJobState(JobState.Stopped); } @@ -2521,10 +2557,12 @@ protected override void Dispose(bool disposing) { StopJob(); } + foreach (Job job in ChildJobs) { job.Dispose(); } + _throttleManager.Dispose(); } finally @@ -2536,7 +2574,7 @@ protected override void Dispose(bool disposing) private bool _isDisposed = false; - private String ConstructLocation() + private string ConstructLocation() { StringBuilder location = new StringBuilder(); @@ -2545,8 +2583,9 @@ private String ConstructLocation() foreach (PSRemotingChildJob job in ChildJobs) { location.Append(job.Location); - location.Append(","); + location.Append(','); } + location.Remove(location.Length - 1, 1); } @@ -2554,7 +2593,7 @@ private String ConstructLocation() } /// - /// Computers on which this job is running + /// Computers on which this job is running. /// public override string Location { @@ -2583,7 +2622,7 @@ internal override bool CanDisconnect /// Returns runspaces associated with the Job, including /// child jobs. /// - /// IEnumerable of RemoteRunspaces + /// IEnumerable of RemoteRunspaces. internal override IEnumerable GetRunspaces() { List runspaces = new List(); @@ -2591,15 +2630,16 @@ internal override IEnumerable GetRunspaces() { runspaces.Add(job.Runspace as RemoteRunspace); } + return runspaces; } /// /// Handles JobUnblocked event from a child job and decrements /// count of blocked child jobs. When count reaches 0, sets the - /// state of the parent job to running + /// state of the parent job to running. /// - /// sender of this event, unused + /// Sender of this event, unused. /// event arguments, should be empty in this /// case private void HandleJobUnblocked(object sender, EventArgs eventArgs) @@ -2624,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 @@ -2642,7 +2682,7 @@ internal class DisconnectedJobOperation : ExecutionCmdletHelper internal DisconnectedJobOperation(Pipeline pipeline) { this.pipeline = pipeline; - this.pipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); + this.pipeline.StateChanged += HandlePipelineStateChanged; } internal override void StartOperation() @@ -2715,10 +2755,9 @@ internal class PSRemotingChildJob : Job, IJobDebugger #region Internal Constructor /// - /// Creates an instance of PSRemotingChildJob + /// Creates an instance of PSRemotingChildJob. /// - /// command invoked by this job object - /// + /// Command invoked by this job object. /// /// internal PSRemotingChildJob(string remoteCommand, ExecutionCmdletHelper helper, ThrottleManager throttleManager) @@ -2733,7 +2772,7 @@ internal PSRemotingChildJob(string remoteCommand, ExecutionCmdletHelper helper, _throttleManager = throttleManager; RemoteRunspace remoteRS = Runspace as RemoteRunspace; - if ((null != remoteRS) && (remoteRS.RunspaceStateInfo.State == RunspaceState.BeforeOpen)) + if ((remoteRS != null) && (remoteRS.RunspaceStateInfo.State == RunspaceState.BeforeOpen)) { remoteRS.URIRedirectionReported += HandleURIDirectionReported; } @@ -2772,21 +2811,21 @@ internal PSRemotingChildJob(ExecutionCmdletHelper helper, ThrottleManager thrott } else { - _remotePipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); - _remotePipeline.Output.DataReady += new EventHandler(HandleOutputReady); - _remotePipeline.Error.DataReady += new EventHandler(HandleErrorReady); + _remotePipeline.StateChanged += HandlePipelineStateChanged; + _remotePipeline.Output.DataReady += HandleOutputReady; + _remotePipeline.Error.DataReady += HandleErrorReady; } Runspace.AvailabilityChanged += HandleRunspaceAvailabilityChanged; IThrottleOperation operation = helper as IThrottleOperation; - operation.OperationComplete += new EventHandler(HandleOperationComplete); + operation.OperationComplete += HandleOperationComplete; SetJobState(JobState.Disconnected, null); } /// - /// Default constructor + /// Default constructor. /// protected PSRemotingChildJob() { @@ -2813,9 +2852,9 @@ internal void ConnectAsync() #region stop - //bool isStopCalled = false; + // bool isStopCalled = false; /// - /// Stops the job + /// Stops the job. /// public override void StopJob() { @@ -2849,20 +2888,20 @@ public override void StopJob() #region Properties /// - /// Status Message associated with the Job + /// Status Message associated with the Job. /// public override string StatusMessage { get { - //ISSUE implement this. - return ""; + // ISSUE implement this. + return string.Empty; } } /// /// Indicates if there is more data available in - /// this Job + /// this Job. /// public override bool HasMoreData { @@ -2874,7 +2913,7 @@ public override bool HasMoreData /// /// Returns the computer on which this command is - /// running + /// running. /// public override string Location { @@ -2885,37 +2924,41 @@ public override string Location } /// - /// /// public Runspace Runspace { get; } /// - /// helper associated with this entity + /// Helper associated with this entity. /// internal ExecutionCmdletHelper Helper { get; } = null; /// - /// used by Invoke-Command cmdlet to show/hide computername property value. + /// Used by Invoke-Command cmdlet to show/hide computername property value. /// Format and Output has capability to understand RemoteObjects and this property lets /// Format and Output decide whether to show/hide computername. /// Default is true. /// internal bool HideComputerName { - get { return _hideComputerName; } + get + { + return _hideComputerName; + } + set { _hideComputerName = value; foreach (Job job in this.ChildJobs) { PSRemotingChildJob rJob = job as PSRemotingChildJob; - if (null != rJob) + if (rJob != null) { rJob.HideComputerName = value; } } } } + private bool _hideComputerName = true; /// @@ -2934,7 +2977,7 @@ internal override bool CanDisconnect get { RemoteRunspace remoteRS = Runspace as RemoteRunspace; - return (remoteRS != null) ? remoteRS.CanDisconnect : false; + return remoteRS != null && remoteRS.CanDisconnect; } } @@ -2943,7 +2986,7 @@ internal override bool CanDisconnect #region IJobDebugger /// - /// Job Debugger + /// Job Debugger. /// public Debugger Debugger { @@ -2971,6 +3014,7 @@ public Debugger Debugger public bool IsAsync { get { return _isAsync; } + set { _isAsync = true; } } @@ -2981,11 +3025,11 @@ public bool IsAsync /// /// Handler which will handle output ready events of the /// pipeline. The output objects are queued on to the - /// internal stream + /// internal stream. /// /// the pipeline reader which raised /// this event - /// information describing the ready event + /// Information describing the ready event. private void HandleOutputReady(object sender, EventArgs eventArgs) { PSDataCollectionPipelineReader reader = @@ -3027,16 +3071,16 @@ private void HandleOutputReady(object sender, EventArgs eventArgs) this.WriteObject(dataObject); } - } //HandleOutputReady + } /// /// Handler which will handle error ready events of the /// pipeline. The error records are queued on to the - /// internal stream + /// internal stream. /// /// the pipeline reader which raised /// this event - /// information describing the ready event + /// Information describing the ready event. private void HandleErrorReady(object sender, EventArgs eventArgs) { PSDataCollectionPipelineReader reader = @@ -3055,12 +3099,12 @@ private void HandleErrorReady(object sender, EventArgs eventArgs) new RemotingErrorRecord(er, originInfo); errorRecord.PreserveInvocationInfoOnce = true; - //ISSUE: Add an Assert for ErrorRecord. - //Add to the PSRemotingChild jobs streams + // ISSUE: Add an Assert for ErrorRecord. + // Add to the PSRemotingChild jobs streams this.WriteError(errorRecord); } } - } //HandleErrorReady + } /// /// When the client remote session reports a URI redirection, this method will report the @@ -3102,17 +3146,17 @@ private void HandleHostCalls(object sender, EventArgs eventArgs) } } } - }// if (hostCallsStream... - } // HandleHostCalls + } + } /// - /// Handle changes in pipeline states + /// Handle changes in pipeline states. /// /// /// protected virtual void HandlePipelineStateChanged(object sender, PipelineStateEventArgs e) { - if ((null != Runspace) && (e.PipelineStateInfo.State != PipelineState.Running)) + if ((Runspace != null) && (e.PipelineStateInfo.State != PipelineState.Running)) { // since we got state changed event..we dont need to listen on // URI redirections anymore @@ -3132,6 +3176,7 @@ protected virtual void HandlePipelineStateChanged(object sender, PipelineStateEv { SetJobState(JobState.Running); } + break; case PipelineState.Disconnected: @@ -3148,45 +3193,46 @@ protected virtual void HandlePipelineStateChanged(object sender, PipelineStateEv } /// - /// Handle a throttle complete event + /// Handle a throttle complete event. /// - /// sender of this event - /// not used in this method + /// Sender of this event. + /// Not used in this method. private void HandleThrottleComplete(object sender, EventArgs eventArgs) { - //Question: Why do we register for HandleThrottleComplete when we have already - //registered for PipelineStateChangedEvent? - //Answer: Because ThrottleManager at a given time can have some pipelines which are - //still not started. If TM.Stop() is called, then it simply discards those pipelines and - //PipelineStateChangedEvent is not called for them. For such jobs, we depend on - //HandleThrottleComplete to mark the finish of job. - - //Question: So it is possible in some cases DoFinish can be called twice. - //Answer: Yes: One from PipelineStateChangedEvent and Another here. But - //DoFinish has logic to check if it has been already called and second call - //becomes noOp. + // Question: Why do we register for HandleThrottleComplete when we have already + // registered for PipelineStateChangedEvent? + // Answer: Because ThrottleManager at a given time can have some pipelines which are + // still not started. If TM.Stop() is called, then it simply discards those pipelines and + // PipelineStateChangedEvent is not called for them. For such jobs, we depend on + // HandleThrottleComplete to mark the finish of job. + + // Question: So it is possible in some cases DoFinish can be called twice. + // Answer: Yes: One from PipelineStateChangedEvent and Another here. But + // DoFinish has logic to check if it has been already called and second call + // becomes noOp. DoFinish(); - } // HandleThrottleComplete + } /// - /// Handle the operation complete event + /// Handle the operation complete event. /// - /// sender of this event - /// operation complete event args + /// Sender of this event. + /// Operation complete event args. protected virtual void HandleOperationComplete(object sender, OperationStateEventArgs stateEventArgs) { - //Question:Why are we registering for OperationComplete if we already - //registering for StateChangedEvent and ThrottleComplete event - //Answer:Because in case of computer, if Runspace.Open it self fails, - //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. + // Question:Why are we registering for OperationComplete if we already + // registering for StateChangedEvent and ThrottleComplete event + // Answer:Because in case of computer, if Runspace.Open it self fails, + // 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 + // until Operation Complete. ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper; Dbg.Assert(helper != null, "Sender of OperationComplete has to be ExecutionCmdletHelper"); DeterminedAndSetJobState(helper); } + private bool _doFinishCalled = false; /// @@ -3195,12 +3241,12 @@ protected virtual void HandleOperationComplete(object sender, OperationStateEven /// protected virtual void DoFinish() { - if (_doFinishCalled == true) + if (_doFinishCalled) return; lock (SyncObject) { - if (_doFinishCalled == true) + if (_doFinishCalled) return; _doFinishCalled = true; @@ -3229,7 +3275,7 @@ internal ErrorRecord FailureErrorRecord } /// - /// Process the exceptions to decide reason for job failure + /// Process the exceptions to decide reason for job failure. /// /// /// @@ -3267,10 +3313,10 @@ 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}"); } } + failureErrorRecord = new ErrorRecord(helper.InternalException, errorId, ErrorCategory.OperationStopped, helper); @@ -3282,7 +3328,7 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai failureException = runspace.RunspaceStateInfo.Reason; object targetObject = runspace.ConnectionInfo.ComputerName; - String errorDetails = null; + string errorDetails = null; // set the transport message in the error detail so that // the user can directly get to see the message without @@ -3314,28 +3360,31 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai errorDetails += message; } - else if (!String.IsNullOrEmpty(transException.Message)) + else if (!string.IsNullOrEmpty(transException.Message)) { errorDetails += transException.Message; } - else if (!String.IsNullOrEmpty(transException.TransportMessage)) + else if (!string.IsNullOrEmpty(transException.TransportMessage)) { errorDetails += transException.TransportMessage; } } - 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); - } // if (runspace... - else if (pipeline.PipelineStateInfo.State == PipelineState.Failed) + } + 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; failureException = pipeline.PipelineStateInfo.Reason; if (failureException != null) @@ -3346,6 +3395,15 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai if (rException != null) { errorRecord = rException.ErrorRecord; + + // A RemoteException will hide a PipelineStoppedException, which should be ignored. + if (errorRecord != null && + errorRecord.FullyQualifiedErrorId.Equals("PipelineStopped", StringComparison.OrdinalIgnoreCase)) + { + // PipelineStoppedException should not be reported as error. + failureException = null; + return; + } } else { @@ -3360,14 +3418,14 @@ protected void ProcessJobFailure(ExecutionCmdletHelper helper, out Exception fai targetObject); } - String computerName = ((RemoteRunspace)pipeline.GetRunspace()).ConnectionInfo.ComputerName; + string computerName = ((RemoteRunspace)pipeline.GetRunspace()).ConnectionInfo.ComputerName; Guid runspaceId = pipeline.GetRunspace().InstanceId; OriginInfo originInfo = new OriginInfo(computerName, runspaceId); failureErrorRecord = new RemotingErrorRecord(errorRecord, originInfo); - } // if (exception != null... - } // if (pipeline... + } + } } /// @@ -3384,12 +3442,14 @@ protected override void Dispose(bool disposing) { return; } + lock (SyncObject) { if (_isDisposed) { return; } + _isDisposed = true; } @@ -3409,67 +3469,63 @@ protected override void Dispose(bool disposing) private bool _cleanupDone = false; /// - /// Cleanup after state changes to finished + /// Cleanup after state changes to finished. /// protected virtual void DoCleanupOnFinished() { bool doCleanup = false; - if (_cleanupDone == false) + if (!_cleanupDone) { lock (SyncObject) { - if (_cleanupDone == false) + if (!_cleanupDone) { _cleanupDone = true; doCleanup = true; } } } + if (!doCleanup) return; StopAggregateResultsFromHelper(Helper); Runspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged; IThrottleOperation operation = Helper as IThrottleOperation; - operation.OperationComplete -= new EventHandler(HandleOperationComplete); + operation.OperationComplete -= HandleOperationComplete; UnregisterThrottleComplete(_throttleManager); _throttleManager = null; } /// /// Aggregates results from the pipeline associated - /// with the specified helper + /// with the specified helper. /// /// helper whose pipeline results /// need to be aggregated protected void AggregateResultsFromHelper(ExecutionCmdletHelper helper) { - //Get the pipeline associated with this helper and register for appropriate events + // Get the pipeline associated with this helper and register for appropriate events Pipeline pipeline = helper.Pipeline; - pipeline.Output.DataReady += new EventHandler(HandleOutputReady); - pipeline.Error.DataReady += new EventHandler(HandleErrorReady); - pipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); + pipeline.Output.DataReady += HandleOutputReady; + pipeline.Error.DataReady += HandleErrorReady; + pipeline.StateChanged += HandlePipelineStateChanged; // Register handler for method executor object stream. Dbg.Assert(pipeline is RemotePipeline, "pipeline is RemotePipeline"); RemotePipeline remotePipeline = pipeline as RemotePipeline; - remotePipeline.MethodExecutorStream.DataReady += new EventHandler(HandleHostCalls); - remotePipeline.PowerShell.Streams.Progress.DataAdded += - new EventHandler(HandleProgressAdded); - remotePipeline.PowerShell.Streams.Warning.DataAdded += - new EventHandler(HandleWarningAdded); - remotePipeline.PowerShell.Streams.Verbose.DataAdded += - new EventHandler(HandleVerboseAdded); - remotePipeline.PowerShell.Streams.Debug.DataAdded += - new EventHandler(HandleDebugAdded); - remotePipeline.PowerShell.Streams.Information.DataAdded += - new EventHandler(HandleInformationAdded); + remotePipeline.MethodExecutorStream.DataReady += HandleHostCalls; + remotePipeline.PowerShell.Streams.Progress.DataAdded += HandleProgressAdded; + remotePipeline.PowerShell.Streams.Warning.DataAdded += HandleWarningAdded; + remotePipeline.PowerShell.Streams.Verbose.DataAdded += HandleVerboseAdded; + remotePipeline.PowerShell.Streams.Debug.DataAdded += HandleDebugAdded; + remotePipeline.PowerShell.Streams.Information.DataAdded += HandleInformationAdded; // Enable method executor stream so that host methods are queued up // on it instead of being executed asynchronously when they arrive. remotePipeline.IsMethodExecutorStreamEnabled = true; IThrottleOperation operation = helper as IThrottleOperation; - operation.OperationComplete += new EventHandler(HandleOperationComplete); + operation.OperationComplete += HandleOperationComplete; } /// @@ -3477,9 +3533,9 @@ protected void AggregateResultsFromHelper(ExecutionCmdletHelper helper) /// If it is null, then returns the PowerShell with the specified /// instance Id. /// - /// remote pipeline - /// instance as described in event args - /// PowerShell instance + /// Remote pipeline. + /// Instance as described in event args. + /// PowerShell instance. private PowerShell GetPipelinePowerShell(RemotePipeline pipeline, Guid instanceId) { if (pipeline != null) @@ -3492,10 +3548,10 @@ private PowerShell GetPipelinePowerShell(RemotePipeline pipeline, Guid instanceI /// /// When a debug message is raised in the underlying PowerShell - /// add it to the jobs debug stream + /// add it to the jobs debug stream. /// - /// unused - /// arguments describing this event + /// Unused. + /// Arguments describing this event. private void HandleDebugAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -3509,10 +3565,10 @@ private void HandleDebugAdded(object sender, DataAddedEventArgs eventArgs) /// /// When a verbose message is raised in the underlying PowerShell - /// add it to the jobs verbose stream + /// add it to the jobs verbose stream. /// - /// unused - /// arguments describing this event + /// Unused. + /// Arguments describing this event. private void HandleVerboseAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -3526,10 +3582,10 @@ private void HandleVerboseAdded(object sender, DataAddedEventArgs eventArgs) /// /// When a warning message is raised in the underlying PowerShell - /// add it to the jobs warning stream + /// add it to the jobs warning stream. /// - /// unused - /// arguments describing this event + /// Unused. + /// Arguments describing this event. private void HandleWarningAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -3545,10 +3601,10 @@ private void HandleWarningAdded(object sender, DataAddedEventArgs eventArgs) /// /// When a progress message is raised in the underlying PowerShell - /// add it to the jobs progress tream + /// add it to the jobs progress tream. /// - /// unused - /// arguments describing this event + /// Unused. + /// Arguments describing this event. private void HandleProgressAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -3562,10 +3618,10 @@ private void HandleProgressAdded(object sender, DataAddedEventArgs eventArgs) /// /// When a Information message is raised in the underlying PowerShell - /// add it to the jobs Information stream + /// add it to the jobs Information stream. /// - /// unused - /// arguments describing this event + /// Unused. + /// Arguments describing this event. private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -3590,7 +3646,7 @@ private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs) /// /// Stops collecting results from the pipeline associated with - /// the specified helper + /// the specified helper. /// /// helper class whose pipeline results /// aggregation has to be stopped @@ -3610,7 +3666,7 @@ protected void StopAggregateResultsFromHelper(ExecutionCmdletHelper helper) /// This is to support Invoke-Command auto-disconnect where a new PSRemoting /// job must be created to pass back to user for connection. /// - /// helper class + /// Helper class. protected void RemoveAggreateCallbacksFromHelper(ExecutionCmdletHelper helper) { // Remove old data output callbacks from pipeline so new callbacks can be added. @@ -3622,52 +3678,47 @@ protected void RemoveAggreateCallbacksFromHelper(ExecutionCmdletHelper helper) // Remove old data aggregation and host calls. Dbg.Assert(pipeline is RemotePipeline, "pipeline is RemotePipeline"); RemotePipeline remotePipeline = pipeline as RemotePipeline; - remotePipeline.MethodExecutorStream.DataReady -= new EventHandler(HandleHostCalls); + remotePipeline.MethodExecutorStream.DataReady -= HandleHostCalls; if (remotePipeline.PowerShell != null) { - remotePipeline.PowerShell.Streams.Progress.DataAdded -= - new EventHandler(HandleProgressAdded); - remotePipeline.PowerShell.Streams.Warning.DataAdded -= - new EventHandler(HandleWarningAdded); - remotePipeline.PowerShell.Streams.Verbose.DataAdded -= - new EventHandler(HandleVerboseAdded); - remotePipeline.PowerShell.Streams.Debug.DataAdded -= - new EventHandler(HandleDebugAdded); - remotePipeline.PowerShell.Streams.Information.DataAdded -= - new EventHandler(HandleInformationAdded); + remotePipeline.PowerShell.Streams.Progress.DataAdded -= HandleProgressAdded; + remotePipeline.PowerShell.Streams.Warning.DataAdded -= HandleWarningAdded; + remotePipeline.PowerShell.Streams.Verbose.DataAdded -= HandleVerboseAdded; + remotePipeline.PowerShell.Streams.Debug.DataAdded -= HandleDebugAdded; + remotePipeline.PowerShell.Streams.Information.DataAdded -= HandleInformationAdded; remotePipeline.IsMethodExecutorStreamEnabled = false; } } /// - /// register for throttle complete from the specified - /// throttlemanager + /// Register for throttle complete from the specified + /// throttlemanager. /// /// protected void RegisterThrottleComplete(ThrottleManager throttleManager) { - throttleManager.ThrottleComplete += new EventHandler(HandleThrottleComplete); + throttleManager.ThrottleComplete += HandleThrottleComplete; } /// - /// unregister for throttle complete from the specified - /// throttle manager + /// Unregister for throttle complete from the specified + /// throttle manager. /// /// protected void UnregisterThrottleComplete(ThrottleManager throttleManager) { - throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleComplete); + throttleManager.ThrottleComplete -= HandleThrottleComplete; } /// /// Determine the current state of the job based on the underlying - /// pipeline state and set the state accordingly + /// pipeline state and set the state accordingly. /// /// protected void DeterminedAndSetJobState(ExecutionCmdletHelper helper) { Exception failureException; - //Process the reason in case of failure. + // Process the reason in case of failure. ProcessJobFailure(helper, out failureException, out _failureErrorRecord); if (failureException != null) @@ -3676,12 +3727,12 @@ protected void DeterminedAndSetJobState(ExecutionCmdletHelper helper) } else { - //Get the state of the pipeline + // Get the state of the pipeline PipelineState state = helper.Pipeline.PipelineStateInfo.State; if (state == PipelineState.NotStarted) { - //This is a case in which pipeline was not started and TM.Stop was - //called. See comment in HandleThrottleComplete + // This is a case in which pipeline was not started and TM.Stop was + // called. See comment in HandleThrottleComplete SetJobState(JobState.Stopped); } else if (state == PipelineState.Completed) @@ -3698,7 +3749,7 @@ protected void DeterminedAndSetJobState(ExecutionCmdletHelper helper) /// /// Set the state of the current job from blocked to /// running and raise an event indicating to this - /// parent job that this job is unblocked + /// parent job that this job is unblocked. /// internal void UnblockJob() { @@ -3712,10 +3763,10 @@ internal void UnblockJob() } /// - /// Returns the PowerShell for the specified instance id + /// Returns the PowerShell for the specified instance id. /// - /// instance id of powershell - /// powershell instance + /// Instance id of powershell. + /// Powershell instance. internal virtual PowerShell GetPowerShell(Guid instanceId) { // this should be called only in the derived implementation @@ -3742,8 +3793,8 @@ internal PowerShell GetPowerShell() /// job state to Debug. Set back to Running when availability goes back to /// Busy (indicating the script/command is running again). /// - /// Runspace - /// RunspaceAvailabilityEventArgs + /// Runspace. + /// RunspaceAvailabilityEventArgs. private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabilityEventArgs e) { RunspaceAvailability prevAvailability = _prevRunspaceAvailability; @@ -3762,7 +3813,7 @@ private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabili /// /// Event raised by this job to indicate to its parent that - /// its now unblocked by the user + /// its now unblocked by the user. /// internal event EventHandler JobUnblocked; @@ -3770,10 +3821,10 @@ private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabili #region Private Members - //helper associated with this job object - private RemotePipeline _remotePipeline = null; + // helper associated with this job object + private readonly RemotePipeline _remotePipeline = null; - //object used for synchronization + // object used for synchronization protected object SyncObject = new object(); private ThrottleManager _throttleManager; @@ -3796,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 @@ -3807,7 +3858,7 @@ internal sealed class RemotingJobDebugger : Debugger private RemotingJobDebugger() { } /// - /// Constructor + /// Constructor. /// /// Debugger to wrap. /// Remote runspace. @@ -3819,12 +3870,12 @@ public RemotingJobDebugger( { if (debugger == null) { - throw new PSArgumentNullException("debugger"); + throw new PSArgumentNullException(nameof(debugger)); } if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } _wrappedDebugger = debugger; @@ -3844,9 +3895,9 @@ public RemotingJobDebugger( /// Evaluates provided command either as a debugger specific command /// or a PowerShell command. /// - /// PowerShell command - /// Output - /// DebuggerCommandResults + /// PowerShell command. + /// Output. + /// DebuggerCommandResults. public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { // Special handling for the prompt command. @@ -3858,10 +3909,97 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC return _wrappedDebugger.ProcessCommand(command, output); } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints to set. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) => + _wrappedDebugger.SetBreakpoints(breakpoints, runspaceId); + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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 breakpoint with the specified id. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) => + _wrappedDebugger.GetBreakpoint(id, runspaceId); + + /// + /// Returns breakpoints on a runspace. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A list of breakpoints in a runspace. + public override List GetBreakpoints(int? runspaceId) => + _wrappedDebugger.GetBreakpoints(runspaceId); + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The command breakpoint that was set. + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetCommandBreakpoint(command, action, path, runspaceId); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) => + _wrappedDebugger.SetLineBreakpoint(path, line, column, action, runspaceId); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.RemoveBreakpoint(breakpoint, runspaceId); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.EnableBreakpoint(breakpoint, runspaceId); + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.DisableBreakpoint(breakpoint, runspaceId); + /// /// Sets the debugger resume action. /// - /// DebuggerResumeAction + /// DebuggerResumeAction. public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { _wrappedDebugger.SetDebuggerAction(resumeAction); @@ -3879,42 +4017,20 @@ public override void StopProcessCommand() /// Returns current debugger stop event arguments if debugger is in /// debug stop state. Otherwise returns null. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public override DebuggerStopEventArgs GetDebuggerStopArgs() { return _wrappedDebugger.GetDebuggerStopArgs(); } /// - /// Sets the parent debugger, breakpoints, function source and other - /// debugging context information. - /// - /// Parent debugger - /// List of breakpoints - /// Debugger mode - /// PowerShell host - /// Current path - /// Function to source map - public override void SetParent( - Debugger parent, - IEnumerable breakPoints, - DebuggerResumeAction? startAction, - PSHost host, - PathInfo path, - Dictionary functionSourceMap) - { - // For now always enable step mode debugging. - SetDebuggerStepMode(true); - } - - /// - /// Sets the parent debugger and breakpoints. + /// Sets the parent debugger, breakpoints, and other debugging context information. /// - /// Parent debugger - /// List of breakpoints - /// Debugger mode - /// host - /// Current path + /// Parent debugger. + /// List of breakpoints. + /// Debugger mode. + /// PowerShell host. + /// Current path. public override void SetParent( Debugger parent, IEnumerable breakPoints, @@ -3948,22 +4064,19 @@ public override IEnumerable GetCallStack() /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. public override void SetDebuggerStepMode(bool enabled) { _wrappedDebugger.SetDebuggerStepMode(enabled); } /// - /// CheckStateAndRaiseStopEvent + /// CheckStateAndRaiseStopEvent. /// internal void CheckStateAndRaiseStopEvent() { RemoteDebugger remoteDebugger = _wrappedDebugger as RemoteDebugger; - if (remoteDebugger != null) - { - remoteDebugger.CheckStateAndRaiseStopEvent(); - } + remoteDebugger?.CheckStateAndRaiseStopEvent(); } /// @@ -3997,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) @@ -4011,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) { @@ -4041,7 +4148,7 @@ private DebuggerCommandResults HandlePromptCommand(PSDataCollection ou /// /// This job is used for running as a job the results from multiple - /// pipelines. This is used in synchronous Invoke-Expression execution + /// pipelines. This is used in synchronous Invoke-Expression execution. /// /// /// TODO: I am not sure whether to change this internal to just InvokeExpressionSyncJob. @@ -4053,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; @@ -4065,9 +4172,9 @@ internal class PSInvokeExpressionSyncJob : PSRemotingChildJob #region Constructors /// - /// Construct an invoke-expression sync job + /// Construct an invoke-expression sync job. /// - /// list of operations to use + /// List of operations to use. /// throttle manager to use for /// this job internal PSInvokeExpressionSyncJob(List operations, ThrottleManager throttleManager) @@ -4083,7 +4190,7 @@ internal PSInvokeExpressionSyncJob(List operations, Throttle ExecutionCmdletHelper helper = operation as ExecutionCmdletHelper; RemoteRunspace remoteRS = helper.Pipeline.Runspace as RemoteRunspace; - if (null != remoteRS) + if (remoteRS != null) { remoteRS.StateChanged += HandleRunspaceStateChanged; @@ -4092,6 +4199,7 @@ internal PSInvokeExpressionSyncJob(List operations, Throttle remoteRS.URIRedirectionReported += HandleURIDirectionReported; } } + _helpers.Add(helper); AggregateResultsFromHelper(helper); @@ -4108,16 +4216,16 @@ internal PSInvokeExpressionSyncJob(List operations, Throttle private bool _cleanupDone = false; /// - /// Clean up once job is finished + /// Clean up once job is finished. /// protected override void DoCleanupOnFinished() { bool doCleanup = false; - if (_cleanupDone == false) + if (!_cleanupDone) { lock (SyncObject) { - if (_cleanupDone == false) + if (!_cleanupDone) { _cleanupDone = true; doCleanup = true; @@ -4131,7 +4239,7 @@ protected override void DoCleanupOnFinished() { // cleanup remote runspace related handlers RemoteRunspace remoteRS = helper.PipelineRunspace as RemoteRunspace; - if (null != remoteRS) + if (remoteRS != null) { remoteRS.StateChanged -= HandleRunspaceStateChanged; remoteRS.URIRedirectionReported -= HandleURIDirectionReported; @@ -4141,15 +4249,15 @@ protected override void DoCleanupOnFinished() } UnregisterThrottleComplete(_throttleManager); - //throttleManager = null; + // throttleManager = null; Results.DecrementRef(); } /// - /// release all resources + /// Release all resources. /// - /// true if called by Dispose() + /// True if called by Dispose(). protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -4157,17 +4265,17 @@ protected override void Dispose(bool disposing) /// /// Handles operation complete from the operations. Adds an error record - /// to results whenever an error is encountered + /// to results whenever an error is encountered. /// - /// sender of this event - /// arguments describing this event, unused + /// Sender of this event. + /// Arguments describing this event, unused. protected override void HandleOperationComplete(object sender, OperationStateEventArgs stateEventArgs) { ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper; Dbg.Assert(helper != null, "Sender of OperationComplete has to be ExecutionCmdletHelper"); Exception failureException; - //Process the reason in case of failure. + // Process the reason in case of failure. ErrorRecord failureErrorRecord; ProcessJobFailure(helper, out failureException, out failureErrorRecord); @@ -4179,7 +4287,7 @@ protected override void HandleOperationComplete(object sender, OperationStateEve } /// - /// Handle changes in pipeline states + /// Handle changes in pipeline states. /// /// /// @@ -4242,7 +4350,7 @@ private void CheckForAndSetDisconnectedState(PipelineState pipelineState) } /// - /// Used to stop all operations + /// Used to stop all operations. /// public override void StopJob() { @@ -4257,12 +4365,12 @@ public override void StopJob() /// protected override void DoFinish() { - if (_doFinishCalled == true) + if (_doFinishCalled) return; lock (SyncObject) { - if (_doFinishCalled == true) + if (_doFinishCalled) return; _doFinishCalled = true; @@ -4282,10 +4390,10 @@ protected override void DoFinish() } /// - /// Returns the PowerShell instance for the specified id + /// Returns the PowerShell instance for the specified id. /// - /// instance id of PowerShell - /// PowerShell instance + /// Instance id of PowerShell. + /// PowerShell instance. internal override PowerShell GetPowerShell(Guid instanceId) { PowerShell powershell = null; @@ -4309,7 +4417,7 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs e) RemoteRunspace remoteRS = sender as RemoteRunspace; // remote runspace must be connected (or connection failed) // we dont need URI redirection any more..so clear it - if (null != remoteRS) + if (remoteRS != null) { if (e.RunspaceStateInfo.State != RunspaceState.Opening) { @@ -4338,10 +4446,10 @@ internal void StartOperations(List operations) } /// - /// Determines if the job is in a terminal state + /// Determines if the job is in a terminal state. /// - /// true, if job in terminal state - /// false otherwise + /// True, if job in terminal state + /// false otherwise. internal bool IsTerminalState() { return (IsFinishedState(this.JobStateInfo.State) || @@ -4359,6 +4467,7 @@ internal Collection GetPowerShells() { powershellsToReturn.Add(ps); } + return powershellsToReturn; } @@ -4397,11 +4506,7 @@ internal PSRemotingJob CreateDisconnectedRemotingJob() internal class OutputProcessingStateEventArgs : EventArgs { - internal bool ProcessingOutput - { - get; - private set; - } + internal bool ProcessingOutput { get; } internal OutputProcessingStateEventArgs(bool processingOutput) { @@ -4409,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 03b603a6078..a5ac9e5ec0b 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job2.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job2.cs @@ -1,17 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Linq; using System.Collections.Generic; -using System.Threading; -using System.Management.Automation.Runspaces; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Tracing; using System.Runtime.Serialization; using System.Text; -using System.ComponentModel; -using System.Management.Automation.Tracing; -using System.Management.Automation.Language; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; // Stops compiler from warning about unknown warnings @@ -35,7 +35,7 @@ namespace System.Management.Automation /// compatibility of PowerShell job cmdlets they will have /// to work with the old interface and hence deprecating /// the Job class did not add any benefit rather than - /// deriving from the same + /// deriving from the same. /// /// The following are some of the notes about /// why the asynchronous operations are provided this way @@ -55,12 +55,12 @@ public abstract class Job2 : Job /// /// These are the parameters that can be used by a job /// implementation when they want to specify parameters - /// to start a job + /// to start a job. /// private List _parameters; /// - /// Object that will be used for thread synchronization + /// Object that will be used for thread synchronization. /// private readonly object _syncobject = new object(); @@ -81,7 +81,7 @@ public abstract class Job2 : Job /// This is a property because CommandParameterCollection /// does not have a public constructor. Hence the /// infrastructure creates an instance and provides - /// it for the implementations to use + /// it for the implementations to use. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] @@ -93,8 +93,7 @@ public List StartParameters { lock (_syncobject) { - if (_parameters == null) - _parameters = new List(); + _parameters ??= new List(); } } @@ -116,7 +115,6 @@ public List StartParameters } /// - /// /// protected object SyncRoot { @@ -128,55 +126,55 @@ protected object SyncRoot #region Protected Methods /// - /// Default no argument constructor + /// Default no argument constructor. /// protected Job2() : base() { } /// /// Constructor which will initialize the job - /// with the associated command string + /// with the associated command string. /// /// string representation /// of the command the job is running protected Job2(string command) : base(command) { } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object + /// Command invoked by this job object. + /// Friendly name for the job object. protected Job2(string command, string name) : base(command, name) { } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object - /// Child jobs of this job object + /// Command invoked by this job object. + /// Friendly name for the job object. + /// Child jobs of this job object. protected Job2(string command, string name, IList childJobs) : base(command, name, childJobs) { } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command invoked by this job object - /// Friendly name for the job object - /// JobIdentifier token used to assign Id and InstanceId + /// Command invoked by this job object. + /// Friendly name for the job object. + /// JobIdentifier token used to assign Id and InstanceId. protected Job2(string command, string name, JobIdentifier token) : base(command, name, token) { } /// - /// Creates an instance of this class + /// Creates an instance of this class. /// - /// Command string - /// Friendly name for the job + /// Command string. + /// Friendly name for the job. /// Instance ID to allow job identification across sessions. protected Job2(string command, string name, Guid instanceId) : base(command, name, instanceId) @@ -189,9 +187,9 @@ protected Job2(string command, string name, Guid instanceId) /// implementing a job it has to be added here. If the /// original method is made public it has changes of /// colliding with some implementation which may have - /// added that method + /// added that method. /// - /// state of the job + /// State of the job. /// exception associated with the /// job entering this state protected new void SetJobState(JobState state, Exception reason) @@ -204,8 +202,8 @@ protected Job2(string command, string name, Guid instanceId) #region State Management /// - /// start a job. The job will be started with the parameters - /// specified in StartParameters + /// Start a job. The job will be started with the parameters + /// specified in StartParameters. /// /// It is redundant to have a method named StartJob /// on a job class. However, this is done so as to avoid @@ -216,21 +214,21 @@ protected Job2(string command, string name, Guid instanceId) public abstract void StartJob(); /// - /// Start a job asynchronously + /// Start a job asynchronously. /// public abstract void StartJobAsync(); /// /// Event to be raise when the start job activity is completed. /// This event should not be raised for - /// synchronous operation + /// synchronous operation. /// public event EventHandler StartJobCompleted; /// /// Method which can be extended or called by derived /// classes to raise the event when start of - /// the job is completed + /// the job is completed. /// /// arguments describing /// an exception that is associated with the event @@ -242,7 +240,7 @@ protected virtual void OnStartJobCompleted(AsyncCompletedEventArgs eventArgs) /// /// Method which can be extended or called by derived /// classes to raise the event when stopping a - /// job is completed + /// job is completed. /// /// argument describing /// an exception that is associated with the event @@ -254,7 +252,7 @@ protected virtual void OnStopJobCompleted(AsyncCompletedEventArgs eventArgs) /// /// Method which can be extended or called by derived /// classes to raise the event when suspending a - /// job is completed + /// job is completed. /// /// argument describing /// an exception that is associated with the event @@ -266,7 +264,7 @@ protected virtual void OnSuspendJobCompleted(AsyncCompletedEventArgs eventArgs) /// /// Method which can be extended or called by derived /// classes to raise the event when resuming a - /// suspended job is completed + /// suspended job is completed. /// /// argument describing /// an exception that is associated with the event @@ -278,7 +276,7 @@ protected virtual void OnResumeJobCompleted(AsyncCompletedEventArgs eventArgs) /// /// Method which can be extended or called by derived /// classes to raise the event when unblocking a - /// blocked job is completed + /// blocked job is completed. /// /// argument describing /// an exception that is associated with the event @@ -289,7 +287,7 @@ protected virtual void OnUnblockJobCompleted(AsyncCompletedEventArgs eventArgs) /// /// Raises the appropriate event based on the operation - /// and the associated event arguments + /// and the associated event arguments. /// /// operation for which the event /// needs to be raised @@ -307,40 +305,43 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA { handler = StartJobCompleted; } + break; case StopJobOperation: { handler = StopJobCompleted; } + break; case SuspendJobOperation: { handler = SuspendJobCompleted; } + break; case ResumeJobOperation: { handler = ResumeJobCompleted; } + break; case UnblockJobOperation: { handler = UnblockJobCompleted; } + break; default: { Dbg.Assert(false, "this condition should not be hit, check the value of operation that you passed"); } + break; } #pragma warning disable 56500 try { - if (handler != null) - { - handler(this, eventArgs); - } + handler?.Invoke(this, eventArgs); } catch (Exception exception) { @@ -352,36 +353,36 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA } /// - /// Stop a job asynchronously + /// Stop a job asynchronously. /// public abstract void StopJobAsync(); /// /// Event to be raised when the asynchronous stopping of a job /// is completed.This event should not be raised for - /// synchronous operation + /// synchronous operation. /// public event EventHandler StopJobCompleted; /// - /// Suspend a job + /// Suspend a job. /// public abstract void SuspendJob(); /// - /// Asynchronously suspend a job + /// Asynchronously suspend a job. /// public abstract void SuspendJobAsync(); /// /// This event should be raised whenever the asynchronous suspend of /// a job is completed. This event should not be raised for - /// synchronous operation + /// synchronous operation. /// public event EventHandler SuspendJobCompleted; /// - /// Resume a suspended job + /// Resume a suspended job. /// public abstract void ResumeJob(); @@ -393,43 +394,43 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA /// /// This event should be raised whenever the asynchronous resume of /// a suspended job is completed. This event should not be raised for - /// synchronous operation + /// synchronous operation. /// public event EventHandler ResumeJobCompleted; /// - /// Unblock a blocked job + /// Unblock a blocked job. /// public abstract void UnblockJob(); /// - /// Unblock a blocked job asynchronously + /// Unblock a blocked job asynchronously. /// public abstract void UnblockJobAsync(); /// - /// StopJob + /// StopJob. /// /// /// public abstract void StopJob(bool force, string reason); /// - /// StopJobAsync + /// StopJobAsync. /// /// /// public abstract void StopJobAsync(bool force, string reason); /// - /// SuspendJob + /// SuspendJob. /// /// /// public abstract void SuspendJob(bool force, string reason); /// - /// SuspendJobAsync + /// SuspendJobAsync. /// /// /// @@ -438,7 +439,7 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA /// /// This event should be raised whenever the asynchronous unblock /// of a blocked job is completed. This event should not be raised for - /// synchronous operation + /// synchronous operation. /// public event EventHandler UnblockJobCompleted; @@ -447,23 +448,23 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA /// /// Specifies the various thread options that can be used - /// for the ThreadBasedJob + /// for the ThreadBasedJob. /// public enum JobThreadOptions { /// /// Use the default behavior, which is to use a - /// ThreadPoolThread + /// ThreadPoolThread. /// Default = 0, /// - /// Use a thread pool thread + /// Use a thread pool thread. /// UseThreadPoolThread = 1, /// - /// Create a new thread everything and reuse + /// Create a new thread everything and reuse. /// UseNewThread = 2, } @@ -472,7 +473,7 @@ public enum JobThreadOptions /// This job will provide asynchronous behavior by running /// the user specified script block in a separate process. /// There will be options for running the scriptblock - /// in a new process or an existing process + /// in a new process or an existing process. /// /// Jobs for the out-of-process activity manager /// can be implemented using this interface @@ -490,19 +491,21 @@ public override void StartAsync() }*/ /// - /// Top level container job + /// Top level container job. /// public sealed class ContainerParentJob : Job2 { #region Private Members private const string TraceClassName = "ContainerParentJob"; + private bool _moreData = true; private readonly object _syncObject = new object(); private int _isDisposed = 0; + 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; @@ -526,9 +529,14 @@ public sealed class ContainerParentJob : Job2 private readonly PSDataCollection _executionError = new PSDataCollection(); private PSEventManager _eventManager; + internal PSEventManager EventManager { - get { return _eventManager; } + get + { + return _eventManager; + } + set { _tracer.WriteMessage("Setting event manager for Job ", InstanceId); @@ -537,6 +545,7 @@ internal PSEventManager EventManager } private ManualResetEvent _jobRunning; + private ManualResetEvent JobRunning { get @@ -561,6 +570,7 @@ private ManualResetEvent JobRunning } private ManualResetEvent _jobSuspendedOrAborted; + private ManualResetEvent JobSuspendedOrAborted { get @@ -579,6 +589,7 @@ private ManualResetEvent JobSuspendedOrAborted } } } + return _jobSuspendedOrAborted; } } @@ -589,10 +600,10 @@ private ManualResetEvent JobSuspendedOrAborted /// /// Create a container parent job with the - /// specified command string and name + /// specified command string and name. /// - /// command string - /// friendly name for display + /// Command string. + /// Friendly name for display. public ContainerParentJob(string command, string name) : base(command, name) { @@ -601,9 +612,9 @@ public ContainerParentJob(string command, string name) /// /// Create a container parent job with the - /// specified command string + /// specified command string. /// - /// Command string + /// Command string. public ContainerParentJob(string command) : base(command) { @@ -612,11 +623,11 @@ public ContainerParentJob(string command) /// /// Create a container parent job with the - /// specified command string + /// specified command string. /// - /// Command string - /// Friendly name for the job - /// JobIdentifier token that allows reuse of an Id and Instance Id + /// Command string. + /// Friendly name for the job. + /// JobIdentifier token that allows reuse of an Id and Instance Id. public ContainerParentJob(string command, string name, JobIdentifier jobId) : base(command, name, jobId) { @@ -625,10 +636,10 @@ public ContainerParentJob(string command, string name, JobIdentifier jobId) /// /// Create a container parent job with the - /// specified command string + /// specified command string. /// - /// Command string - /// Friendly name for the job + /// Command string. + /// Friendly name for the job. /// Instance ID to allow job identification across sessions. public ContainerParentJob(string command, string name, Guid instanceId) : base(command, name, instanceId) @@ -638,12 +649,12 @@ public ContainerParentJob(string command, string name, Guid instanceId) /// /// Create a container parent job with the - /// specified command string + /// specified command string. /// - /// Command string - /// Friendly name for the job - /// JobIdentifier token that allows reuse of an Id and Instance Id - /// Job type name + /// Command string. + /// Friendly name for the job. + /// JobIdentifier token that allows reuse of an Id and Instance Id. + /// Job type name. public ContainerParentJob(string command, string name, JobIdentifier jobId, string jobType) : base(command, name, jobId) { @@ -653,12 +664,12 @@ public ContainerParentJob(string command, string name, JobIdentifier jobId, stri /// /// Create a container parent job with the - /// specified command string + /// specified command string. /// - /// Command string - /// Friendly name for the job + /// Command string. + /// Friendly name for the job. /// Instance ID to allow job identification across sessions. - /// Job type name + /// Job type name. public ContainerParentJob(string command, string name, Guid instanceId, string jobType) : base(command, name, instanceId) { @@ -670,9 +681,9 @@ public ContainerParentJob(string command, string name, Guid instanceId, string j /// Create a container parent job with the specified command, name, /// job type strings. /// - /// Command string - /// Friendly name for the job - /// Job type name + /// Command string. + /// Friendly name for the job. + /// Job type name. public ContainerParentJob(string command, string name, string jobType) : base(command, name) { @@ -687,18 +698,17 @@ public ContainerParentJob(string command, string name, string jobType) #region Public Methods /// - /// Add a child job to the parent job + /// Add a child job to the parent job. /// - /// child job to add + /// Child job to add. /// Thrown if the job is disposed. /// Thrown if child being added is null. public void AddChildJob(Job2 childJob) { AssertNotDisposed(); - if (childJob == null) - { - throw new ArgumentNullException("childJob"); - } + + ArgumentNullException.ThrowIfNull(childJob); + _tracer.WriteMessage(TraceClassName, "AddChildJob", Guid.Empty, childJob, "Adding Child to Parent with InstanceId : ", InstanceId.ToString()); JobStateInfo childJobStateInfo; @@ -707,14 +717,15 @@ public void AddChildJob(Job2 childJob) // Store job's state and subscribe to State Changed event. Locking here will // ensure that the jobstateinfo we get is the state before any state changed events are handled by ContainerParentJob. childJobStateInfo = childJob.JobStateInfo; - childJob.StateChanged += new EventHandler(HandleChildJobStateChanged); + childJob.StateChanged += HandleChildJobStateChanged; } + ChildJobs.Add(childJob); ParentJobStateCalculation(new JobStateEventArgs(childJobStateInfo, new JobStateInfo(JobState.NotStarted))); } /// - /// indicates if more data is available + /// Indicates if more data is available. /// /// /// This has more data if any of the child jobs have more data. @@ -747,7 +758,7 @@ public override bool HasMoreData } /// - /// Message indicating status of the job + /// Message indicating status of the job. /// public override string StatusMessage { @@ -822,8 +833,7 @@ public override void StartJob() ExecutionError.Add( new ErrorRecord(e.Error, "ContainerParentJobStartError", - ErrorCategory. - InvalidResult, + ErrorCategory.InvalidResult, childJob)); _tracer.WriteMessage(TraceClassName, "StartJob-Handler", Guid.Empty, this, "Child job asynchronously had error, child InstanceId: {0}", childJob.InstanceId.ToString()); @@ -853,6 +863,7 @@ public override void StartJob() ScriptDebugger.SetDebugJobAsync(job as IJobDebugger, false); job.StartJobAsync(); } + completed.WaitOne(); foreach (Job2 job in ChildJobs) { @@ -874,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. @@ -887,6 +898,7 @@ public override void StartJobAsync() OnStartJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null)); return; } + _tracer.WriteMessage(TraceClassName, "StartJobAsync", Guid.Empty, this, "Entering method", null); s_structuredTracer.BeginContainerParentJobExecution(InstanceId); foreach (Job2 job in this.ChildJobs) @@ -1028,6 +1040,7 @@ public override void ResumeJob() "Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString()); job.ResumeJobAsync(); } + completed.WaitOne(); Dbg.Assert(eventHandler != null, "Event handler magically disappeared"); foreach (Job2 job in ChildJobs) @@ -1036,6 +1049,7 @@ public override void ResumeJob() job.ResumeJobCompleted -= eventHandler; } + _tracer.WriteMessage(TraceClassName, "ResumeJob", Guid.Empty, this, "Exiting method", null); // Errors are taken from the Error collection by the cmdlet for ContainerParentJob. @@ -1051,6 +1065,7 @@ public override void ResumeJobAsync() OnResumeJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null)); return; } + _tracer.WriteMessage(TraceClassName, "ResumeJobAsync", Guid.Empty, this, "Entering method", null); foreach (Job2 job in this.ChildJobs) { @@ -1160,7 +1175,7 @@ public override void StopJobAsync() } /// - /// StopJob + /// StopJob. /// /// /// @@ -1170,7 +1185,7 @@ public override void StopJob(bool force, string reason) } /// - /// StopJobAsync + /// StopJobAsync. /// /// /// @@ -1263,6 +1278,7 @@ public override void UnblockJob() "Child job asynchronously, child InstanceId: {0}", job.InstanceId.ToString()); job.UnblockJobAsync(); } + completed.WaitOne(); Dbg.Assert(eventHandler != null, "Event handler magically disappeared"); foreach (Job2 job in ChildJobs) @@ -1271,6 +1287,7 @@ public override void UnblockJob() job.UnblockJobCompleted -= eventHandler; } + _tracer.WriteMessage(TraceClassName, "UnblockJob", Guid.Empty, this, "Exiting method", null); // Errors are taken from the Error collection by the cmdlet for ContainerParentJob. @@ -1287,6 +1304,7 @@ public override void UnblockJobAsync() OnUnblockJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null)); return; } + _tracer.WriteMessage(TraceClassName, "UnblockJobAsync", Guid.Empty, this, "Entering method", null); foreach (Job2 job in this.ChildJobs) { @@ -1436,6 +1454,7 @@ private void SuspendJobInternal(bool? force, string reason) else job.SuspendJobAsync(); } + completed.WaitOne(); Dbg.Assert(eventHandler != null, "Event handler magically disappeared"); foreach (Job2 job in ChildJobs) @@ -1444,13 +1463,14 @@ private void SuspendJobInternal(bool? force, string reason) job.SuspendJobCompleted -= eventHandler; } + _tracer.WriteMessage(TraceClassName, "SuspendJob", Guid.Empty, this, "Exiting method", null); // Errors are taken from the Error collection by the cmdlet for ContainerParentJob. } /// - /// Internal SuspendJobAsync. Calls appropriate method if Force is specified + /// Internal SuspendJobAsync. Calls appropriate method if Force is specified. /// /// /// @@ -1461,6 +1481,7 @@ private void SuspendJobAsyncInternal(bool? force, string reason) OnSuspendJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null)); return; } + _tracer.WriteMessage(TraceClassName, "SuspendJobAsync", Guid.Empty, this, "Entering method", null); foreach (Job2 job in this.ChildJobs) { @@ -1517,7 +1538,7 @@ private void SuspendJobAsyncInternal(bool? force, string reason) } /// - /// StopJob + /// StopJob. /// /// /// @@ -1610,6 +1631,7 @@ private void StopJobInternal(bool? force, string reason) else job.StopJobAsync(); } + completed.WaitOne(); Dbg.Assert(eventHandler != null, "Event handler magically disappeared"); foreach (Job2 job in ChildJobs) @@ -1618,13 +1640,14 @@ private void StopJobInternal(bool? force, string reason) job.StopJobCompleted -= eventHandler; } + _tracer.WriteMessage(TraceClassName, "StopJob", Guid.Empty, this, "Exiting method", null); // Errors are taken from the Error collection by the cmdlet for ContainerParentJob. } /// - /// StopJobAsync + /// StopJobAsync. /// /// /// @@ -1635,6 +1658,7 @@ private void StopJobAsyncInternal(bool? force, string reason) OnStopJobCompleted(new AsyncCompletedEventArgs(new ObjectDisposedException(TraceClassName), false, null)); return; } + _tracer.WriteMessage(TraceClassName, "StopJobAsync", Guid.Empty, this, "Entering method", null); foreach (Job2 job in this.ChildJobs) @@ -1710,6 +1734,7 @@ private void HandleMyStateChanged(object sender, JobStateEventArgs e) JobSuspendedOrAborted.Reset(); } } + break; case JobState.Suspended: @@ -1720,6 +1745,7 @@ private void HandleMyStateChanged(object sender, JobStateEventArgs e) JobRunning.Reset(); } } + break; case JobState.Failed: case JobState.Completed: @@ -1735,12 +1761,13 @@ private void HandleMyStateChanged(object sender, JobStateEventArgs e) JobRunning.Set(); } } + break; } } /// - /// Handles the StateChanged event from each of the child job objects + /// Handles the StateChanged event from each of the child job objects. /// /// /// @@ -1761,6 +1788,7 @@ private void ParentJobStateCalculation(JobStateEventArgs e) { PSBeginTime = DateTime.Now; } + if (!IsFinishedState(JobStateInfo.State) && IsPersistentState(computedState)) { PSEndTime = DateTime.Now; @@ -1777,7 +1805,7 @@ private void ParentJobStateCalculation(JobStateEventArgs e) } /// - /// Handles the StateChanged event from each of the child job objects + /// Handles the StateChanged event from each of the child job objects. /// /// /// @@ -1789,7 +1817,7 @@ private void ParentJobStateCalculation(JobStateEventArgs e) /// /// /// - /// true if the job state needs to be modified, false otherwise + /// True if the job state needs to be modified, false otherwise. internal static bool ComputeJobStateFromChildJobStates(string traceClassName, JobStateEventArgs e, ref int blockedChildJobsCount, ref int suspendedChildJobsCount, ref int suspendingChildJobsCount, ref int finishedChildJobsCount, ref int failedChildJobsCount, ref int stoppedChildJobsCount, int childJobsCount, @@ -1881,7 +1909,6 @@ internal static bool ComputeJobStateFromChildJobStates(string traceClassName, Jo return false; } - // Ignore state changes which are not resulting in state change to finished. // State will be Running once at least one child is running. if ((e.JobStateInfo.State != JobState.Completed && e.JobStateInfo.State != JobState.Failed) && e.JobStateInfo.State != JobState.Stopped) @@ -1897,6 +1924,7 @@ internal static bool ComputeJobStateFromChildJobStates(string traceClassName, Jo // if the job state is blocked, we have already returned. return false; } + if (e.JobStateInfo.State == JobState.Failed) { // If any of the child job failed, we set status to failed @@ -1915,7 +1943,7 @@ internal static bool ComputeJobStateFromChildJobStates(string traceClassName, Jo int finishedChildJobsCountNew = Interlocked.Increment(ref finishedChildJobsCount); - //We are done + // We are done if (finishedChildJobsCountNew == childJobsCount) { allChildJobsFinished = true; @@ -1923,21 +1951,23 @@ internal static bool ComputeJobStateFromChildJobStates(string traceClassName, Jo if (allChildJobsFinished) { - //if any child job failed, set status to failed - //If stop was called set, status to stopped - //else completed); + // if any child job failed, set status to failed + // If stop was called set, status to stopped + // else completed); if (failedChildJobsCount > 0) { tracer.WriteMessage(traceClassName, ": JobState is failed, at least one child job failed."); computedJobState = JobState.Failed; return true; } + if (stoppedChildJobsCount > 0) { tracer.WriteMessage(traceClassName, ": JobState is stopped, stop is called."); computedJobState = JobState.Stopped; return true; } + tracer.WriteMessage(traceClassName, ": JobState is completed."); computedJobState = JobState.Completed; return true; @@ -1989,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 { @@ -2001,15 +2028,15 @@ protected override void Dispose(bool disposing) } } - private String ConstructLocation() + 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; } - private String ConstructStatusMessage() + private string ConstructStatusMessage() { if (ChildJobs == null || ChildJobs.Count == 0) return string.Empty; @@ -2018,14 +2045,14 @@ private String ConstructStatusMessage() for (int i = 0; i < ChildJobs.Count; i++) { - if (!String.IsNullOrEmpty(ChildJobs[i].StatusMessage)) + if (!string.IsNullOrEmpty(ChildJobs[i].StatusMessage)) { sb.Append(ChildJobs[i].StatusMessage); } if (i < (ChildJobs.Count - 1)) { - sb.Append(","); + sb.Append(','); } } @@ -2033,7 +2060,7 @@ private String ConstructStatusMessage() } /// - /// Computers on which this job is running + /// Computers on which this job is running. /// public override string Location { @@ -2049,7 +2076,7 @@ private void UnregisterJobEvent(Job job) _tracer.WriteMessage("Unregistering StateChanged event for job ", job.InstanceId); foreach (PSEventSubscriber subscriber in - EventManager.Subscribers.Where(subscriber => String.Equals(subscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase))) + EventManager.Subscribers.Where(subscriber => string.Equals(subscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase))) { EventManager.UnsubscribeEvent(subscriber); break; @@ -2079,29 +2106,28 @@ private void UnregisterAllJobEvents() /// Container exception for jobs that can map errors and exceptions /// to specific lines in their input. /// - [Serializable] public class JobFailedException : SystemException { /// - /// Creates a new JobFailedException + /// Creates a new JobFailedException. /// public JobFailedException() { } /// - /// Creates a new JobFailedException + /// Creates a new JobFailedException. /// - /// The message of the exception + /// The message of the exception. public JobFailedException(string message) : base(message) { } /// - /// Creates a new JobFailedException + /// Creates a new JobFailedException. /// - /// The message of the exception + /// The message of the exception. /// The actual exception that caused this error. public JobFailedException(string message, Exception innerException) : base(message, innerException) @@ -2109,7 +2135,7 @@ public JobFailedException(string message, Exception innerException) } /// - /// Creates a new JobFailedException + /// Creates a new JobFailedException. /// /// The actual exception that caused this error. /// A ScriptExtent that describes where this error originated from. @@ -2120,47 +2146,32 @@ public JobFailedException(Exception innerException, ScriptExtent displayScriptPo } /// - /// Class constructor + /// Class constructor. /// - /// serialization info - /// streaming context + /// 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(); } /// /// The actual exception that caused this error. /// 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("info"); - base.GetObjectData(info, context); - - info.AddValue("Reason", _reason); - info.AddValue("DisplayScriptPosition", _displayScriptPosition); - } + private readonly ScriptExtent _displayScriptPosition; /// - /// Returns the reason for this exception + /// Returns the reason for this exception. /// public override string Message { diff --git a/src/System.Management.Automation/engine/remoting/client/JobManager.cs b/src/System.Management.Automation/engine/remoting/client/JobManager.cs index 2d7862a9318..3b2baec6654 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobManager.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobManager.cs @@ -1,16 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Management.Automation.Remoting; -using System.Management.Automation.Tracing; using System.Management.Automation.Runspaces; +using System.Management.Automation.Tracing; using System.Reflection; using System.Security; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; // Stops compiler from warning about unknown warnings @@ -54,7 +54,7 @@ internal JobManager() /// Whether the type is registered already. public bool IsRegistered(string typeName) { - if (String.IsNullOrEmpty(typeName)) + if (string.IsNullOrEmpty(typeName)) { return false; } @@ -81,51 +81,43 @@ internal void RegisterJobSourceAdapter(Type jobSourceAdapterType) Dbg.Assert(jobSourceAdapterType != null, "JobSourceAdapterType should never be called with null value."); object instance = null; - if (jobSourceAdapterType.FullName != null && jobSourceAdapterType.FullName.EndsWith("WorkflowJobSourceAdapter", StringComparison.OrdinalIgnoreCase)) + ConstructorInfo constructor = jobSourceAdapterType.GetConstructor(Type.EmptyTypes); + if (!constructor.IsPublic) { - MethodInfo method = jobSourceAdapterType.GetMethod("GetInstance"); - instance = method.Invoke(null, null); + string message = string.Format(CultureInfo.CurrentCulture, + RemotingErrorIdStrings.JobManagerRegistrationConstructorError, + jobSourceAdapterType.FullName); + throw new InvalidOperationException(message); } - else - { - ConstructorInfo constructor = jobSourceAdapterType.GetConstructor(PSTypeExtensions.EmptyTypes); - if (!constructor.IsPublic) - { - string message = String.Format(CultureInfo.CurrentCulture, - RemotingErrorIdStrings.JobManagerRegistrationConstructorError, - jobSourceAdapterType.FullName); - throw new InvalidOperationException(message); - } - try - { - instance = constructor.Invoke(null); - } - catch (MemberAccessException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (TargetInvocationException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (TargetParameterCountException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (NotSupportedException exception) - { - _tracer.TraceException(exception); - throw; - } - catch (SecurityException exception) - { - _tracer.TraceException(exception); - throw; - } + try + { + instance = constructor.Invoke(null); + } + catch (MemberAccessException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (TargetInvocationException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (TargetParameterCountException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (NotSupportedException exception) + { + _tracer.TraceException(exception); + throw; + } + catch (SecurityException exception) + { + _tracer.TraceException(exception); + throw; } if (instance != null) @@ -139,7 +131,7 @@ internal void RegisterJobSourceAdapter(Type jobSourceAdapterType) /// /// Returns a token that allows a job to be constructed with a specific id and instanceId. - /// The original job must have been saved using "SaveJobIdForReconstruction" in the JobSourceAdapter + /// The original job must have been saved using "SaveJobIdForReconstruction" in the JobSourceAdapter. /// /// The instance id desired. /// The requesting type name for JobSourceAdapter implementation. @@ -166,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)); } } @@ -184,10 +180,7 @@ internal static void SaveJobId(Guid instanceId, int id, string typeName) /// public Job2 NewJob(JobDefinition definition) { - if (definition == null) - { - throw new ArgumentNullException("definition"); - } + ArgumentNullException.ThrowIfNull(definition); JobSourceAdapter sourceAdapter = GetJobSourceAdapter(definition); Job2 newJob; @@ -224,14 +217,11 @@ public Job2 NewJob(JobDefinition definition) /// public Job2 NewJob(JobInvocationInfo specification) { - if (specification == null) - { - throw new ArgumentNullException("specification"); - } + ArgumentNullException.ThrowIfNull(specification); if (specification.Definition == null) { - throw new ArgumentException(RemotingErrorIdStrings.NewJobSpecificationError, "specification"); + throw new ArgumentException(RemotingErrorIdStrings.NewJobSpecificationError, nameof(specification)); } JobSourceAdapter sourceAdapter = GetJobSourceAdapter(specification.Definition); @@ -265,17 +255,18 @@ public Job2 NewJob(JobInvocationInfo specification) /// /// Saves the job to a persisted store. /// - /// Job2 type job to persist - /// Job definition containing source adapter information + /// Job2 type job to persist. + /// Job definition containing source adapter information. public void PersistJob(Job2 job, JobDefinition definition) { if (job == null) { - throw new PSArgumentNullException("job"); + throw new PSArgumentNullException(nameof(job)); } + if (definition == null) { - throw new PSArgumentNullException("definition"); + throw new PSArgumentNullException(nameof(definition)); } JobSourceAdapter sourceAdapter = GetJobSourceAdapter(definition); @@ -326,7 +317,7 @@ private JobSourceAdapter AssertAndReturnJobSourceAdapter(string adapterTypeName) /// otherwise load the associated module and the requested source adapter. /// /// JobDefinition supplies the JobSourceAdapter information. - /// JobSourceAdapter + /// JobSourceAdapter. private JobSourceAdapter GetJobSourceAdapter(JobDefinition definition) { string adapterTypeName; @@ -349,6 +340,7 @@ private JobSourceAdapter GetJobSourceAdapter(JobDefinition definition) { adapterFound = _sourceAdapters.TryGetValue(adapterTypeName, out adapter); } + if (!adapterFound) { if (!string.IsNullOrEmpty(definition.ModuleName)) @@ -389,10 +381,6 @@ private JobSourceAdapter GetJobSourceAdapter(JobDefinition definition) { ex = e; } - catch (ThreadAbortException e) - { - ex = e; - } if (ex != null) { @@ -419,7 +407,7 @@ private JobSourceAdapter GetJobSourceAdapter(JobDefinition definition) /// Cmdlet requesting this, for error processing. /// /// - /// job source adapter type names + /// Job source adapter type names. /// Collection of jobs. /// If cmdlet parameter is null, throws exception on error from /// JobSourceAdapter implementation. @@ -441,7 +429,7 @@ internal List GetJobs( /// /// /// - /// job source adapter type names + /// Job source adapter type names. /// Collection of jobs that match the specified /// criteria. /// If cmdlet parameter is null, throws exception on error from @@ -465,7 +453,7 @@ internal List GetJobsByName( /// /// /// - /// job source adapter type names + /// Job source adapter type names. /// Collection of jobs that match the specified /// criteria. /// If cmdlet parameter is null, throws exception on error from @@ -489,7 +477,7 @@ internal List GetJobsByCommand( /// /// /// - /// job source adapter type names + /// Job source adapter type names. /// Collection of jobs with the specified /// state. /// If cmdlet parameter is null, throws exception on error from @@ -527,8 +515,8 @@ internal List GetJobsByFilter(Dictionary filter, Cmdlet cm /// /// Get a filtered list of jobs based on adapter name. /// - /// job id - /// adapter name + /// Job id. + /// Adapter name. /// internal bool IsJobFromAdapter(Guid id, string name) { @@ -556,7 +544,7 @@ internal bool IsJobFromAdapter(Guid id, string name) /// /// /// - /// job source adapter type names + /// Job source adapter type names. /// Filtered list of jobs. /// If cmdlet parameter is null, throws exception on error from /// JobSourceAdapter implementation. @@ -603,7 +591,11 @@ private List GetFilteredJobs( } #pragma warning restore 56500 - if (jobs == null) continue; + if (jobs == null) + { + continue; + } + allJobs.AddRange(jobs); } } @@ -616,7 +608,6 @@ private List GetFilteredJobs( } } - return allJobs; } @@ -627,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 || @@ -652,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()); } @@ -769,12 +760,16 @@ 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) { cmdlet.WriteObject(job); } + return job; } } @@ -792,7 +787,7 @@ private Job2 GetJobThroughId(Guid guid, int id, Cmdlet cmdlet, bool writeErro /// JobSourceAdapter type that contains the job definition. /// Cmdlet making call. /// Whether to write jobsourceadapter errors. - /// List of matching Job2 objects + /// List of matching Job2 objects. internal List GetJobToStart( string definitionName, string definitionPath, @@ -859,7 +854,7 @@ private static void WriteErrorOrWarning(bool writeErrorOnException, Cmdlet cmdle else { // Write a warning - string message = String.Format(CultureInfo.CurrentCulture, + string message = string.Format(CultureInfo.CurrentCulture, RemotingErrorIdStrings.JobSourceAdapterError, exception.Message, sourceAdapter.Name); @@ -877,8 +872,8 @@ private static void WriteErrorOrWarning(bool writeErrorOnException, Cmdlet cmdle /// /// Returns a List of adapter names currently loaded. /// - /// Adapter names to filter on - /// List of names + /// Adapter names to filter on. + /// List of names. internal List GetLoadedAdapterNames(string[] adapterTypeNames) { List adapterNames = new List(); @@ -944,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); @@ -967,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 4424e4fa547..85cd6c7ba1f 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Management.Automation.Runspaces; -using System.IO; -using System.Collections; using System.Runtime.Serialization; // Stops compiler from warning about unknown warnings @@ -17,21 +16,21 @@ namespace System.Management.Automation { /// /// Contains the definition of a job which is defined in a - /// job store + /// job store. /// /// The actual implementation of this class will /// happen in M2 - [Serializable] public class JobDefinition : ISerializable { private string _name; /// - /// A friendly Name for this definition + /// A friendly Name for this definition. /// - public String Name + public string Name { get { return _name; } + set { _name = value; } } @@ -51,6 +50,7 @@ public String Name public string ModuleName { get { return _moduleName; } + set { _moduleName = value; } } @@ -62,19 +62,20 @@ public string ModuleName public string JobSourceAdapterTypeName { get { return _jobSourceAdapterTypeName; } + set { _jobSourceAdapterTypeName = value; } } /// /// Name of the job that needs to be loaded - /// from the specified module + /// from the specified module. /// - public String Command { get; } + public string Command { get; } private Guid _instanceId; /// - /// Unique Guid for this job definition + /// Unique Guid for this job definition. /// public Guid InstanceId { @@ -82,6 +83,7 @@ public Guid InstanceId { return _instanceId; } + set { _instanceId = value; @@ -90,9 +92,9 @@ public Guid InstanceId /// /// Save this definition to the specified - /// file on disk + /// file on disk. /// - /// stream to save to + /// Stream to save to. public virtual void Save(Stream stream) { throw new NotImplementedException(); @@ -100,7 +102,7 @@ public virtual void Save(Stream stream) /// /// Load this definition from the specified - /// file on disk + /// file on disk. /// /// public virtual void Load(Stream stream) @@ -110,7 +112,7 @@ public virtual void Load(Stream stream) /// /// Returns information about this job like - /// name, definition, parameters etc + /// name, definition, parameters etc. /// public CommandInfo CommandInfo { @@ -124,8 +126,8 @@ public CommandInfo CommandInfo /// Public constructor for testing. /// /// Type of adapter to use to create a job. - /// the command string. - /// the job name. + /// The command string. + /// The job name. public JobDefinition(Type jobSourceAdapterType, string command, string name) { JobSourceAdapterType = jobSourceAdapterType; @@ -133,13 +135,13 @@ public JobDefinition(Type jobSourceAdapterType, string command, string name) { _jobSourceAdapterTypeName = jobSourceAdapterType.Name; } + Command = command; _name = name; _instanceId = Guid.NewGuid(); } /// - /// /// /// /// @@ -149,7 +151,6 @@ protected JobDefinition(SerializationInfo info, StreamingContext context) } /// - /// /// /// /// @@ -164,24 +165,24 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte /// be passed to a job so that the job can be /// instantiated without having to specify /// the parameters explicitly. Helps in - /// passing job parameters to disk + /// passing job parameters to disk. /// /// This class is not required if /// CommandParameterCollection adds a public /// constructor.The actual implementation of /// this class will happen in M2 - [Serializable] public class JobInvocationInfo : ISerializable { /// - /// Friendly name associated with this specification + /// Friendly name associated with this specification. /// - public String Name + public string Name { get { return _name; } + set { if (value == null) @@ -189,6 +190,7 @@ public String Name _name = value; } } + private string _name = string.Empty; private string _command; @@ -202,6 +204,7 @@ public string Command { return _command ?? _definition.Command; } + set { _command = value; @@ -211,7 +214,7 @@ public string Command private JobDefinition _definition; /// - /// Definition associated with the job + /// Definition associated with the job. /// public JobDefinition Definition { @@ -219,6 +222,7 @@ public JobDefinition Definition { return _definition; } + set { _definition = value; @@ -228,31 +232,31 @@ public JobDefinition Definition private List _parameters; /// - /// Parameters associated with this specification + /// Parameters associated with this specification. /// public List Parameters { - get { return _parameters ?? (_parameters = new List()); } + get { return _parameters ??= new List(); } } /// - /// Unique identifies for this specification + /// Unique identifies for this specification. /// public Guid InstanceId { get; } = Guid.NewGuid(); /// - /// Save this specification to a file + /// Save this specification to a file. /// - /// stream to save to + /// Stream to save to. public virtual void Save(Stream stream) { throw new NotImplementedException(); } /// - /// Load this specification from a file + /// Load this specification from a file. /// - /// stream to load from + /// Stream to load from. public virtual void Load(Stream stream) { throw new NotImplementedException(); @@ -267,8 +271,8 @@ protected JobInvocationInfo() /// /// Create a new job definition with a single set of parameters. /// - /// The job definition - /// The parameter collection to use + /// The job definition. + /// The parameter collection to use. public JobInvocationInfo(JobDefinition definition, Dictionary parameters) { _definition = definition; @@ -283,8 +287,8 @@ public JobInvocationInfo(JobDefinition definition, Dictionary pa /// Create a new job definition with a multiple sets of parameters. This allows /// different parameters for different machines. /// - /// The job definition - /// Collection of sets of parameters to use for the child jobs + /// The job definition. + /// Collection of sets of parameters to use for the child jobs. [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is forced by the interaction of PowerShell and Workflow.")] public JobInvocationInfo(JobDefinition definition, IEnumerable> parameterCollectionList) @@ -303,7 +307,6 @@ public JobInvocationInfo(JobDefinition definition, IEnumerable - /// /// /// /// @@ -314,7 +317,6 @@ public JobInvocationInfo(JobDefinition definition, CommandParameterCollection pa } /// - /// /// /// /// @@ -329,7 +331,6 @@ public JobInvocationInfo(JobDefinition definition, IEnumerable - /// /// /// /// @@ -339,7 +340,6 @@ protected JobInvocationInfo(SerializationInfo info, StreamingContext context) } /// - /// /// /// /// @@ -349,20 +349,21 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte } /// - /// Utility function to turn a dictionary of name/value pairs into a parameter collection + /// Utility function to turn a dictionary of name/value pairs into a parameter collection. /// - /// The dictionary to convert - /// The converted collection + /// The dictionary to convert. + /// The converted collection. private static CommandParameterCollection ConvertDictionaryToParameterCollection(IEnumerable> parameters) { if (parameters == null) 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); } + return paramCollection; } } @@ -374,9 +375,9 @@ private static CommandParameterCollection ConvertDictionaryToParameterCollection public abstract class JobSourceAdapter { /// - /// Name for this store + /// Name for this store. /// - public String Name { get; set; } = String.Empty; + public string Name { get; set; } = string.Empty; /// /// Get a token that allows for construction of a job with a previously assigned @@ -384,8 +385,8 @@ public abstract class JobSourceAdapter /// creator of the original job. /// The original job must have been saved using "SaveJobIdForReconstruction" /// - /// Instance Id of the job to recreate - /// JobIdentifier to be used in job construction + /// Instance Id of the job to recreate. + /// JobIdentifier to be used in job construction. protected JobIdentifier RetrieveJobIdForReuse(Guid instanceId) { return JobManager.GetJobIdentifier(instanceId, this.GetType().Name); @@ -402,8 +403,9 @@ public void StoreJobIdForReuse(Job2 job, bool recurse) { if (job == null) { - PSTraceSource.NewArgumentNullException("job", RemotingErrorIdStrings.JobSourceAdapterCannotSaveNullJob); + PSTraceSource.NewArgumentNullException(nameof(job), RemotingErrorIdStrings.JobSourceAdapterCannotSaveNullJob); } + JobManager.SaveJobId(job.InstanceId, job.Id, this.GetType().Name); if (recurse && job.ChildJobs != null && job.ChildJobs.Count > 0) { @@ -411,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); } } @@ -428,17 +429,16 @@ 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); } } /// - /// Create a new job with the specified definition + /// Create a new job with the specified definition. /// - /// job definition to use - /// job object + /// Job definition to use. + /// Job object. public Job2 NewJob(JobDefinition definition) { return NewJob(new JobInvocationInfo(definition, new Dictionary())); @@ -450,93 +450,93 @@ public Job2 NewJob(JobDefinition definition) /// then a default location will be used to find the job /// definition by name. /// - /// Job definition name - /// Job definition file path - /// Job2 object + /// Job definition name. + /// Job definition file path. + /// Job2 object. public virtual Job2 NewJob(string definitionName, string definitionPath) { return null; } /// - /// Create a new job with the specified JobSpecification + /// Create a new job with the specified JobSpecification. /// - /// specification - /// job object + /// Specification. + /// Job object. public abstract Job2 NewJob(JobInvocationInfo specification); /// /// Get the list of jobs that are currently available in this - /// store + /// store. /// - /// collection of job objects + /// Collection of job objects. public abstract IList GetJobs(); /// - /// Get list of jobs that matches the specified names + /// 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 + /// Collection of jobs that match the specified + /// criteria. public abstract IList GetJobsByName(string name, bool recurse); /// - /// Get list of jobs that run the specified command + /// Get list of jobs that run the specified command. /// - /// command to match + /// Command to match. /// - /// collection of jobs that match the specified - /// criteria + /// Collection of jobs that match the specified + /// criteria. public abstract IList GetJobsByCommand(string command, bool recurse); /// - /// Get list of jobs that has the specified id + /// Get list of jobs that has the specified id. /// - /// Guid to match + /// Guid to match. /// - /// job with the specified guid + /// Job with the specified guid. public abstract Job2 GetJobByInstanceId(Guid instanceId, bool recurse); /// - /// Get job that has specific session id + /// Get job that has specific session id. /// - /// Id to match + /// Id to match. /// - /// Job with the specified id + /// Job with the specified id. public abstract Job2 GetJobBySessionId(int id, bool recurse); /// - /// Get list of jobs that are in the specified state + /// Get list of jobs that are in the specified state. /// - /// state to match + /// State to match. /// - /// collection of jobs with the specified - /// state + /// Collection of jobs with the specified + /// state. public abstract IList GetJobsByState(JobState state, bool recurse); /// /// Get list of jobs based on the adapter specific - /// filter parameters + /// filter parameters. /// /// dictionary containing name value /// pairs for adapter specific filters /// - /// collection of jobs that match the - /// specified criteria + /// Collection of jobs that match the + /// specified criteria. public abstract IList GetJobsByFilter(Dictionary filter, bool recurse); /// - /// Remove a job from the store + /// Remove a job from the store. /// - /// job object to remove + /// Job object to remove. public abstract void RemoveJob(Job2 job); /// /// Saves the job to a persisted store. /// - /// Job2 type job to persist + /// Job2 type job to persist. public virtual void PersistJob(Job2 job) { // Implemented only if job needs to be told when to persist. diff --git a/src/System.Management.Automation/engine/remoting/client/PSProxyJob.cs b/src/System.Management.Automation/engine/remoting/client/PSProxyJob.cs deleted file mode 100644 index e1d8be0b1b2..00000000000 --- a/src/System.Management.Automation/engine/remoting/client/PSProxyJob.cs +++ /dev/null @@ -1,2948 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using System.Management.Automation.Internal; -using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Tracing; -using System.Threading; -using Dbg = System.Management.Automation.Diagnostics; - -// Stops compiler from warning about unknown warnings -#pragma warning disable 1634, 1691 - -namespace System.Management.Automation -{ - #region Workflow Hosting API - - /// - /// Class that will serve as the API for hosting and executing - /// workflows in PowerShell. This class will have a behavior - /// similar to how the Runspace and PowerShell APIs behave in - /// the remoting scenario. The objects on the client side act - /// as proxies to the real objects on the server - /// - public sealed class PSJobProxy : Job2 - { - #region Constructors - - /// - /// Internal constructor - /// - /// the command to execute - internal PSJobProxy(string command) - : base(command) - { - _tracer.WriteMessage(ClassNameTrace, "ctor", _remoteJobInstanceId, this, - "Constructing proxy job", null); - StateChanged += HandleMyStateChange; - } - - #endregion Constructors - - #region Overrides of Job - - /// - /// Success status of the command execution. - /// - public override string StatusMessage - { - get { return _remoteJobStatusMessage; } - } - - /// - /// Indicates that more data is available in this - /// result object for reading. - /// - public override bool HasMoreData - { - get - { - // moreData is initially set to true, and it - // will remain so until the async result - // object has completed execution. - - if (_moreData && IsFinishedState(JobStateInfo.State)) - { - bool atleastOneChildHasMoreData = ChildJobs.Any(t => t.HasMoreData); - - bool parentHasMoreData = (CollectionHasMoreData(Output) - || CollectionHasMoreData(Error) - || CollectionHasMoreData(Verbose) - || CollectionHasMoreData(Debug) - || CollectionHasMoreData(Warning) - || CollectionHasMoreData(Progress)); - - _moreData = parentHasMoreData || atleastOneChildHasMoreData; - } - - return _moreData; - } - } - - /// - /// This is the location string from the remote job. - /// - public override string Location - { - get { return _remoteJobLocation; } - } - - #endregion - - #region Overrides of Job2 - - /// - /// start a job. The job will be started with the parameters - /// specified in StartParameters - /// - /// Thrown if the job - /// is already running, if there is no runspace or runspace pool - /// assigned. - /// Thrown if the job is - /// otherwise started, finished, or suspended. - /// Thrown if job is - /// disposed. - public override void StartJob() - { - StartJob(null, null, null); - } - - /// - /// Start a job asynchronously - /// - /// When a job is started all the data in the - /// job streams from a previous invocation will be cleared - public override void StartJobAsync() - { - StartJobAsync(null, null, null); - } - - /// - /// Stop a job synchronously. In order to be consistent, this method - /// should be used in place of StopJob which was introduced in the - /// v2 Job API - /// - /// Thrown if job is blocked. - /// Thrown if job is disposed. - public override void StopJob() - { - try - { - // The call is asynchronous because Receive-Job is called - // in the same invocation. Return once the job state changed - // event is received, and the job state is updated to a - // Finished state. - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Stop); - else - DoStopAsync(); - - Finished.WaitOne(); - } - catch (Exception error) - { - _tracer.WriteMessage(ClassNameTrace, "StopJob", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - throw; - } - } - - /// - /// Stop a job asynchronously - /// - public override void StopJobAsync() - { -#pragma warning disable 56500 - try - { - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Stop); - else - DoStopAsync(); - } - catch (Exception error) - { - // Exception transferred using event arguments. - _tracer.WriteMessage(ClassNameTrace, "StopJobAsync", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - OnStopJobCompleted(new AsyncCompletedEventArgs(error, false, null)); - } -#pragma warning restore 56500 - } - - /// - /// StopJob - /// - /// - /// - public override void StopJob(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported); - } - - /// - /// StopJobAsync - /// - /// - /// - public override void StopJobAsync(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported); - } - - /// - /// Suspend a job - /// - /// Throws if the job is not in - /// a running or suspended state. - /// Thrown if job is - /// disposed. - public override void SuspendJob() - { - try - { - // The call is asynchronous because Receive-Job is called - // in the same invocation. Return once the job state changed - // event is received, and the job state is updated to a - // Finished or Suspended state. - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Suspend); - else - DoSuspendAsync(); - - JobSuspendedOrFinished.WaitOne(); - } - catch (Exception error) - { - _tracer.WriteMessage(ClassNameTrace, "SuspendJob", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - throw; - } - } - - /// - /// Asynchronously suspend a job - /// - public override void SuspendJobAsync() - { -#pragma warning disable 56500 - try - { - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Suspend); - else - DoSuspendAsync(); - } - catch (Exception error) - { - // Exception transferred using event arguments. - _tracer.WriteMessage(ClassNameTrace, "SuspendJobAsync", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - OnSuspendJobCompleted(new AsyncCompletedEventArgs(error, false, null)); - } -#pragma warning restore 56500 - } - - /// - /// SuspendJob - /// - /// - /// - public override void SuspendJob(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported); - } - - /// - /// SuspendJobAsync - /// - /// - /// - public override void SuspendJobAsync(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyJobControlNotSupported); - } - - /// - /// Resume a suspended job - /// - /// Throws if the job - /// is not in a suspended or running state. - /// Thrown if job is - /// disposed. - public override void ResumeJob() - { - try - { - // The call is asynchronous because Receive-Job is called - // in the same invocation. Return once the job state changed - // event is received, and the job state is updated to a - // Finished or Running state. - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Resume); - else - DoResumeAsync(); - - JobRunningOrFinished.WaitOne(); - } - catch (Exception error) - { - _tracer.WriteMessage(ClassNameTrace, "ResumeJob", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - throw; - } - } - - /// - /// Resume a suspended job asynchronously. - /// - public override void ResumeJobAsync() - { -#pragma warning disable 56500 - try - { - if (ShouldQueueOperation()) - _pendingOperations.Enqueue(QueueOperation.Resume); - else - DoResumeAsync(); - } - catch (Exception error) - { - // Exception transferred using event arguments. - _tracer.WriteMessage(ClassNameTrace, "ResumeJobAsync", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - OnResumeJobCompleted(new AsyncCompletedEventArgs(error, false, null)); - } -#pragma warning restore 56500 - } - - /// - /// Unblock a blocked job - /// - /// Unblock job is not supported on PSJobProxy. - public override void UnblockJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyUnblockJobNotSupported); - } - - /// - /// Unblock a blocked job asynchronously - /// - public override void UnblockJobAsync() - { - OnUnblockJobCompleted(new AsyncCompletedEventArgs(PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyUnblockJobNotSupported), false, null)); - } - - #endregion - - #region Additional State Management Methods - - /// - /// Start execution of the workflow with the - /// specified input. This input will serve as - /// input to the underlying pipeline - /// - /// collection of input - /// objects - public void StartJobAsync(PSDataCollection input) - { - StartJobAsync(null, null, input); - } - - /// - /// Start execution of the job with the - /// specified input. This input will serve as - /// input to the underlying pipeline - /// - /// - /// Not sure if this method is needed. This has - /// been added just to be in sync with the PowerShell - /// APIs - public void StartJob(PSDataCollection input) - { - StartJob(null, null, input); - } - - /// - /// Start execution of the workflow with the - /// specified input. This input will serve as - /// input to the underlying pipeline. - /// Because the number of child jobs is unknown before starting - /// the job, delegates may be indicated to ensure that no events will be missed - /// after the child job is created if data begins streaming back immediately. - /// - /// delegate used to subscribe to data added events on the child jobs - /// delegate used to subscribe to state changed events on the child jobs - /// collection of input - /// objects - public void StartJob(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection input) - { - try - { - // The call is asynchronous because Receive-Job is called - // in the same invocation. Return once the job object is returned and - // used to initialize the proxy. - DoStartAsync(dataAdded, stateChanged, input); - _jobInitializedWaitHandle.WaitOne(); - } - catch (Exception error) - { - _tracer.WriteMessage(ClassNameTrace, "StartJob", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - throw; - } - } - - /// - /// Start asynchronous execution of the workflow with the - /// specified input. This input will serve as - /// input to the underlying pipeline. - /// Because the number of child jobs is unknown before starting - /// the job, delegates may be indicated to ensure that no events will be missed - /// after the child job is created if data begins streaming back immediately. - /// - /// delegate used to subscribe to data added events on the child jobs - /// delegate used to subscribe to state changed events on the child jobs - /// collection of input - /// objects - public void StartJobAsync(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection input) - { -#pragma warning disable 56500 - try - { - DoStartAsync(dataAdded, stateChanged, input); - } - catch (Exception error) - { - // Exception transferred using event arguments. - _tracer.WriteMessage(ClassNameTrace, "StartJobAsync", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - OnStartJobCompleted(new AsyncCompletedEventArgs(error, false, null)); - } -#pragma warning restore 56500 - } - - /// - /// Removes the job. - /// If remoteJob is true, the job output that has been transferred to this - /// client object will be preserved. - /// - /// Indicates whether the remove operation should - /// be applied to the remote or local job. - /// Force will stop the job on the server before - /// attempting removal. Default value is false. - /// Thrown if the job is not in - /// a completed state. - public void RemoveJob(bool removeRemoteJob, bool force) - { - if (!removeRemoteJob) - { - Dispose(); - return; - } - - lock (SyncRoot) - { - AssertNotDisposed(); - } - try - { - DoRemove(force); - lock (SyncRoot) - { - // If _removeCalled has not been set to true, do not wait. - // There should be an exception during DoRemove in most cases when - // this is true. - if (!_removeCalled) - { - Diagnostics.Assert(false, "remove called is false after calling remove. No exception was thrown."); - return; - } - } - RemoveComplete.WaitOne(); - } - catch (Exception error) - { - _tracer.WriteMessage(ClassNameTrace, "RemoveJob", _remoteJobInstanceId, this, "Error", null); - _tracer.TraceException(error); - throw; - } - } - - /// - /// Removes the job. - /// If remoteJob is true, the job output that has been transferred to this - /// client object will be preserved. - /// - /// Indicates whether the remove operation should - /// be applied to the remote or local job. - /// Thrown if the job is not in - /// a completed state. - public void RemoveJob(bool removeRemoteJob) - { - RemoveJob(removeRemoteJob, false); - } - - /// - /// Removes the job on the remote server. - /// The job output that has been transferred to this client object will be - /// preserved. - /// - /// Indicates whether the remove operation should - /// be applied to the remote or local job. - /// Force will stop the job on the server before - /// attempting removal. - public void RemoveJobAsync(bool removeRemoteJob, bool force) - { - if (!removeRemoteJob) - { - Dispose(); - OnRemoveJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - return; - } - - var asyncOp = AsyncOperationManager.CreateOperation(force); - var workerDelegate = new JobActionWorkerDelegate(JobActionWorker); - workerDelegate.BeginInvoke(asyncOp, ActionType.Remove, null, null); - } - - /// - /// Removes the job on the remote server. - /// The job output that has been transferred to this client object will be - /// preserved. - /// - /// Indicates whether the remove operation should - /// be applied to the remote or local job. - public void RemoveJobAsync(bool removeRemoteJob) - { - RemoveJobAsync(removeRemoteJob, false); - } - - /// - /// This event should be raised whenever the asynchronous removal of - /// a server side job is completed. - /// - public event EventHandler RemoveJobCompleted; - - /// - /// Method to raise the event when removing a - /// server side job is completed - /// - /// argument describing - /// an exception that is associated with the event - private void OnRemoveJobCompleted(AsyncCompletedEventArgs eventArgs) - { - // Make a temporary copy of the event to avoid possibility of - // a race condition if the last subscriber unsubscribes - // immediately after the null check and before the event is raised. - EventHandler handler = RemoveJobCompleted; - -#pragma warning disable 56500 - try - { - if (handler != null) - { - handler(this, eventArgs); - } - } - catch (Exception exception) - { - // errors in the handlers are not errors in the operation - // silently ignore them - _tracer.TraceException(exception); - } -#pragma warning restore 56500 - } - - #endregion Additional State Management Methods - - #region Additional Properties - - /// - /// If set, the remote job will be removed when it has been completed and the data has been received. - /// This can only be set prior to a job being started. - /// - public bool RemoveRemoteJobOnCompletion - { - get - { - return _removeRemoteJobOnCompletion; - } - set - { - AssertChangesCanBeAccepted(); - _removeRemoteJobOnCompletion = value; - } - } - private bool _removeRemoteJobOnCompletion; - - /// - /// The instance ID of the remote job that this proxy interacts with. - /// - public Guid RemoteJobInstanceId - { - get { return _remoteJobInstanceId; } - } - - /// - /// Runspace in which this job will be executed - /// - /// At any point of time only a runspace or a - /// runspacepool may be specified - public Runspace Runspace - { - get - { - return _runspace; - } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - lock (SyncRoot) - { - AssertChangesCanBeAccepted(); - _runspacePool = null; - _runspace = value; - } - } - } - - /// - /// RunspacePool in which this job will be executed - /// - public RunspacePool RunspacePool - { - get - { - return _runspacePool; - } - set - { - if (value == null) - { - throw PSTraceSource.NewArgumentNullException("value"); - } - - lock (SyncRoot) - { - AssertChangesCanBeAccepted(); - _runspace = null; - _runspacePool = value; - } - } - } - - #endregion Additional Properties - - #region Proxy Factory Static Methods - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// Runspace containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// Handler to subscribe to any child job data added events. - /// Handler to subscribe to any child job state changed events. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(Runspace runspace, Hashtable filter, - EventHandler dataAdded, EventHandler stateChanged) - { - return Create(runspace, filter, dataAdded, stateChanged, true); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// Runspace containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// If true, the data streaming will start immediately. If false, - /// the user must call "ReceiveJob()" to start data streaming. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(Runspace runspace, Hashtable filter, bool receiveImmediately) - { - return Create(runspace, filter, null, null, receiveImmediately); - } - - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// Runspace containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(Runspace runspace, Hashtable filter) - { - return Create(runspace, filter, null, null, true); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// Runspace containing the jobs to base the proxy on. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(Runspace runspace) - { - return Create(runspace, null, null, null, true); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// RunspacePool containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// Handler to subscribe to any child job data added events. - /// Handler to subscribe to any child job state changed events. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(RunspacePool runspacePool, Hashtable filter, - EventHandler dataAdded, EventHandler stateChanged) - { - return Create(runspacePool, filter, dataAdded, stateChanged, true); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// RunspacePool containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// If true, the data streaming will start immediately. If false, - /// the user must call "ReceiveJob()" to start data streaming. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(RunspacePool runspacePool, Hashtable filter, bool receiveImmediately) - { - return Create(runspacePool, filter, null, null, receiveImmediately); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// RunspacePool containing the jobs to base the proxy on. - /// Hashtable to use for the Get-Job -filter command. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(RunspacePool runspacePool, Hashtable filter) - { - return Create(runspacePool, filter, null, null, true); - } - - /// - /// Queries the runspace for jobs and constructs a collection of job proxies to interact with them. - /// - /// RunspacePool containing the jobs to base the proxy on. - /// A collection of job proxies that represent the jobs collected based on the filter. - public static ICollection Create(RunspacePool runspacePool) - { - return Create(runspacePool, null, null, null, true); - } - - private static ICollection Create(RunspacePool runspacePool, Hashtable filter, - EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately) - { - if (runspacePool == null) - throw new PSArgumentNullException("runspacePool"); - return Create(null, runspacePool, filter, dataAdded, stateChanged, connectImmediately); - } - - private static ICollection Create(Runspace runspace, Hashtable filter, - EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately) - { - if (runspace == null) - throw new PSArgumentNullException("runspace"); - return Create(runspace, null, filter, dataAdded, stateChanged, connectImmediately); - } - - private static ICollection Create(Runspace runspace, RunspacePool runspacePool, Hashtable filter, - EventHandler dataAdded, EventHandler stateChanged, bool connectImmediately) - { - // run command - Collection jobResults; - - using (PowerShell ps = PowerShell.Create()) - { - Dbg.Assert(runspacePool == null ^ runspace == null, "Either a runspace or a runspacepool should be used, not both."); - - if (runspacePool == null) - { - ps.Runspace = runspace; - } - else - { - ps.RunspacePool = runspacePool; - } - - ps.AddCommand("Get-Job"); - if (filter != null) ps.AddParameter("Filter", filter); - - jobResults = ps.Invoke(); - } - - Collection jobs = new Collection(); - foreach (var deserializedJob in jobResults) - { - if (!Deserializer.IsDeserializedInstanceOfType(deserializedJob, typeof(Job))) - { - // Do not create proxies if jobs are live. - continue; - } - - string command = String.Empty; - if (!TryGetJobPropertyValue(deserializedJob, "Command", out command)) - { - Dbg.Assert(false, "Job object did not contain command when creating proxy."); - } - PSJobProxy job = new PSJobProxy(command); - - job.InitializeExistingJobProxy(deserializedJob, runspace, runspacePool); - // Events will be registered and states will be set by ReceiveJob when it is called. - // This may be later, if connectImmediately is set to false. - - job._receiveIsValidCall = true; - if (connectImmediately) - { - // This will make receive invalid in the future, sets the state to running. - job.ReceiveJob(dataAdded, stateChanged); - } - else - { - Dbg.Assert(dataAdded == null, "DataAdded cannot be specified if not connecting immediately"); - Dbg.Assert(stateChanged == null, "StateChanged cannot be specified if not connecting immediately"); - } - - jobs.Add(job); - } - - return jobs; - } - - #endregion Proxy Factory Static Methods - - /// - /// Will begin streaming data for a job object created by the "create" method that is in a not started state. - /// - public void ReceiveJob() - { - ReceiveJob(null, null); - } - - /// - /// Will begin streaming data for a job object created by the "create" method that is in a not started state. - /// - /// delegate used to subscribe to data added events on the child jobs - /// delegate used to subscribe to state changed events on the child jobs - public void ReceiveJob(EventHandler dataAdded, EventHandler stateChanged) - { - lock (SyncRoot) - { - if (!_receiveIsValidCall) - { - // Receive may only be called once, and is only a valid call after a proxy job has been created in - // a non-streaming state. - throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.JobProxyReceiveInvalid); - } - - Dbg.Assert(JobStateInfo.State == JobState.NotStarted, "ReceiveJob is only valid on a not started PSJobProxy"); - Dbg.Assert(_receivePowerShell.Commands.Commands.Count == 0, "ReceiveJob is only valid if internal pipeline is unused."); - Dbg.Assert(_receivePowerShell.InvocationStateInfo.State == PSInvocationState.NotStarted, "ReceiveJob is only valid if internal pipeline has not been started"); - Dbg.Assert(_dataAddedHandler == null, "DataAdded cannot be specified before calling ReceiveJob"); - Dbg.Assert(_stateChangedHandler == null, "StateChanged cannot be specified before calling ReceiveJob"); - - _receiveIsValidCall = false; - _dataAddedHandler = dataAdded; - _stateChangedHandler = stateChanged; - } - - RegisterChildEvents(); - - // Set job states after events have been registered. - // All jobs begin in the running state. States will be updated when state changed - // event information is gotten by Receive-Job. See DataAddedToOutput. - ValidateAndDoSetJobState(JobState.Running); - foreach (PSChildJobProxy child in ChildJobs) - { - Dbg.Assert(child != null, "Child should always be PSChildJobProxy"); - child.DoSetJobState(JobState.Running); - } - - AssignRunspaceOrRunspacePool(_receivePowerShell); - AddReceiveJobCommandToPowerShell(_receivePowerShell, false); - _receivePowerShell.AddParameter("InstanceId", _remoteJobInstanceId); - _receivePowerShell.BeginInvoke((PSDataCollection)null, _receivePowerShellOutput, null, - CleanupReceivePowerShell, null); - } - - #region Internal Methods - - internal void InitializeJobProxy(PSCommand command, Runspace runspace, RunspacePool runspacePool) - { - Dbg.Assert(command != null, "Command cannot be null"); - _tracer.WriteMessage(ClassNameTrace, "InitializeJobProxy", _remoteJobInstanceId, this, - "Initializing Job Proxy.", null); - _pscommand = command.Clone(); - - var paramCol = new CommandParameterCollection(); - foreach (var parameter in _pscommand.Commands[0].Parameters) - { - paramCol.Add(parameter); - } - - // The StartParameters may be edited or overwritten by the user. - // The parameters will need to be added back to the command object before - // execution. - StartParameters = new List { paramCol }; - _pscommand.Commands[0].Parameters.Clear(); - - CommonInit(runspace, runspacePool); - } - - internal void InitializeExistingJobProxy(PSObject o, Runspace runspace, RunspacePool runspacePool) - { - Dbg.Assert(o != null, "deserialized job cannot be null"); - _tracer.WriteMessage(ClassNameTrace, "InitializeExistingJobProxy", _remoteJobInstanceId, this, - "Initializing job proxy for existing job.", null); - _pscommand = null; - _startCalled = true; - _jobInitialized = true; - CommonInit(runspace, runspacePool); - - PopulateJobProperties(o); - // Do not set child job states, or subscribe to child events. This will - // be done in ReceiveJob whenever it is called. - // We need to wait until then because event handlers may not be specified until then. - - // Construct the parent job's start parameters specific for SM+ - List psParamCollection = new List(); - object psPrivateMetadata = null; - foreach (PSChildJobProxy job in ChildJobs) - { - // Each child job should have StartParameters.Count = 1. - if (job.StartParameters.Count == 0) continue; - Hashtable childJobCol = new Hashtable(); - foreach (CommandParameter p in job.StartParameters[0]) - { - if (psPrivateMetadata == null && p.Name.Equals("PSPrivateMetadata", StringComparison.OrdinalIgnoreCase)) - { - psPrivateMetadata = p.Value; - } - childJobCol.Add(p.Name, p.Value); - } - psParamCollection.Add(childJobCol); - } - - CommandParameterCollection newStartParameters = new CommandParameterCollection(); - newStartParameters.Add(new CommandParameter("PSParameterCollection", psParamCollection)); - if (psPrivateMetadata != null) - { - newStartParameters.Add(new CommandParameter("PSPrivateMetadata", psPrivateMetadata)); - } - StartParameters.Add(newStartParameters); - } - - private void CommonInit(Runspace runspace, RunspacePool runspacePool) - { - _runspacePool = runspacePool; - _runspace = runspace; - - _receivePowerShell.InvocationStateChanged += ReceivePowerShellInvocationStateChanged; - - // Output buffer other than the PSJobProxy output is needed. - // If the proxy's output buffer is used, data added there will raise events - // to the consumer before we have re-distributed the output. - _receivePowerShellOutput.DataAdded += DataAddedToOutput; - _receivePowerShell.Streams.Error.DataAdded += DataAddedToError; - _receivePowerShell.Streams.Debug.DataAdded += DataAddedToDebug; - _receivePowerShell.Streams.Verbose.DataAdded += DataAddedToVerbose; - _receivePowerShell.Streams.Warning.DataAdded += DataAddedToWarning; - _receivePowerShell.Streams.Progress.DataAdded += DataAddedToProgress; - _receivePowerShell.Streams.Information.DataAdded += DataAddedToInformation; - } - - /// - /// Helper to do error checking for getting a property of type T from a PSobject. - /// - /// - /// - /// - /// - /// - internal static bool TryGetJobPropertyValue(PSObject o, string propertyName, out T propertyValue) - { - propertyValue = default(T); - PSPropertyInfo propertyInfo = o.Properties[propertyName]; - if (propertyInfo == null || !(propertyInfo.Value is T)) return false; - propertyValue = (T)propertyInfo.Value; - return true; - } - - #endregion Internal Methods - - #region Async helpers - - private enum ActionType - { - Remove - } - - private class AsyncCompleteContainer - { - internal AsyncCompletedEventArgs EventArgs; - internal ActionType Action; - } - - private delegate void JobActionWorkerDelegate(AsyncOperation asyncOp, ActionType action); - private void JobActionWorker(AsyncOperation asyncOp, ActionType action) - { - Exception exception = null; - -#pragma warning disable 56500 - try - { - switch (action) - { - case ActionType.Remove: - DoRemove(asyncOp.UserSuppliedState); - break; - } - } - catch (Exception e) - { - // Called on a background thread, need to include any exception in - // event arguments. - exception = e; - } -#pragma warning restore 56500 - - var eventArgs = new AsyncCompletedEventArgs(exception, false, asyncOp.UserSuppliedState); - - var container = new AsyncCompleteContainer { EventArgs = eventArgs, Action = action }; - - // End the task. The asyncOp object is responsible - // for marshaling the call. - asyncOp.PostOperationCompleted(JobActionAsyncCompleted, container); - } - - private void JobActionAsyncCompleted(object operationState) - { - var container = operationState as AsyncCompleteContainer; - Dbg.Assert(container != null, "AsyncComplete Container is null; not passed properly."); - _tracer.WriteMessage(ClassNameTrace, "JobActionAsyncCompleted", _remoteJobInstanceId, this, - "Async operation {0} completed", container.Action.ToString()); - switch (container.Action) - { - case ActionType.Remove: - if (container.EventArgs.Error == null) RemoveComplete.WaitOne(); - OnRemoveJobCompleted(container.EventArgs); - break; - } - } - - #endregion Async helpers - - #region Private Methods - - private bool ShouldQueueOperation() - { - bool queueThisOperation; - lock (_inProgressSyncObject) - { - if (!_inProgress) - { - _inProgress = true; - queueThisOperation = false; - } - else - { - queueThisOperation = true; - } - } - - return queueThisOperation; - } - - private void ProcessQueue() - { - bool queueWorkItem = false; - lock (_inProgressSyncObject) - { - if (!_pendingOperations.IsEmpty && !_workerCreated) - { - queueWorkItem = true; - _workerCreated = true; - _inProgress = true; - } - else - _inProgress = false; - } - - if (queueWorkItem) - { - ThreadPool.QueueUserWorkItem(ProcessQueueWorker); - } - } - - private void ProcessQueueWorker(object state) - { - // If this operation has been queued to the thread pool, processing rights - // already belong here. Release only when finished. - while (true) - { - QueueOperation nextOperation; - lock (_inProgressSyncObject) - { - if (!_pendingOperations.TryDequeue(out nextOperation)) - { - _inProgress = false; - _workerCreated = false; - break; - } - } - - switch (nextOperation) - { - case QueueOperation.Stop: -#pragma warning disable 56500 - try - { - DoStopAsync(); - Finished.WaitOne(); - } - catch (Exception e) - { - // Transfer exception via event arguments. - OnStopJobCompleted(new AsyncCompletedEventArgs(e, false, null)); - } - break; - case QueueOperation.Suspend: - try - { - DoSuspendAsync(); - JobSuspendedOrFinished.WaitOne(); - } - catch (Exception e) - { - // Transfer exception via event arguments. - OnSuspendJobCompleted(new AsyncCompletedEventArgs(e, false, null)); - } - break; - case QueueOperation.Resume: - try - { - DoResumeAsync(); - JobRunningOrFinished.WaitOne(); - } - catch (Exception e) - { - // Transfer exception via event arguments. - OnResumeJobCompleted(new AsyncCompletedEventArgs(e, false, null)); - } -#pragma warning restore 56500 - break; - } - } - } - - /// - /// Checks if there is more data in the specified collection - /// - /// Type of the collection - /// collection to check - /// true if the collection has more data - private static bool CollectionHasMoreData(PSDataCollection collection) - { - return (collection.IsOpen || collection.Count > 0); - } - - /// - /// Worker method which starts the job - /// - private void DoStartAsync(EventHandler dataAdded, EventHandler stateChanged, PSDataCollection input) - { - // Checks disposed, and whether the start operation is valid. - AssertJobCanBeStartedAndSetStateToRunning(); - Dbg.Assert(_inProgress == false, "Start should always be able to obtain processing rights if it is a valid call."); - lock (_inProgressSyncObject) { _inProgress = true; } - - // Set input and handlers. Should only be done after we've confirmed that this - // call to StartJob should proceed, which isn't until now. - lock (SyncRoot) - { - Dbg.Assert(_dataAddedHandler == null, "DataAdded cannot be specified before StartJob is called"); - Dbg.Assert(_stateChangedHandler == null, "StateChanged cannot be specified before StartJob is called"); - _dataAddedHandler = dataAdded; - _stateChangedHandler = stateChanged; - } - - _tracer.WriteMessage(ClassNameTrace, "DoStartAsync", _remoteJobInstanceId, this, - "Starting command invocation.", null); - s_structuredTracer.BeginProxyJobExecution(InstanceId); - - DoStartPrepare(); - - _receivePowerShell.BeginInvoke(input, _receivePowerShellOutput, null, CleanupReceivePowerShell, null); - } - - private void DoStartPrepare() - { - // Either a runspace or a runspacepool has to be set - // before the job can be started - if (_runspacePool == null && _runspace == null) - { - throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.RunspaceAndRunspacePoolNull); - } - - AssignRunspaceOrRunspacePool(_receivePowerShell); - - // set the parameters. the user may have changed them. - bool found = false; - if (StartParameters != null && StartParameters.Count > 0) - { - foreach (var parameter in StartParameters[0]) - { - _pscommand.Commands[0].Parameters.Add(parameter); - if (string.Compare(parameter.Name, "AsJob", StringComparison.OrdinalIgnoreCase) == 0) - { - if (!(parameter.Value is bool) || !(bool)parameter.Value) - { - // If the AsJob Parameter has been passed and explicitly set to false, we should - // not proceed with the operation. This is an error. - throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.JobProxyAsJobMustBeTrue); - } - found = true; - } - } - } - - // If the user has overwritten the parameter collection, we should add AsJob back again. - if (!found) - { - _pscommand.Commands[0].Parameters.Add("AsJob", true); - } - - // set the commands for the powershell - _receivePowerShell.Commands = _pscommand; - - // Setup receive-job data streaming. - // Pipe the invocation with as job to Receive-Job, because we do not yet have the job's - // instance ID. - // Use the WriteJob parameter to cause Receive-Job to output the job object. - AddReceiveJobCommandToPowerShell(_receivePowerShell, true); - } - - /// - /// Worker method which stops the job - /// - private void DoStopAsync() - { - if (!AssertStopJobIsValidAndSetToStopping()) - { - OnStopJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - return; - } - - _receivePowerShell.Stop(); - _receivePowerShell.Commands.Clear(); - _receivePowerShell.GenerateNewInstanceId(); - _receivePowerShell.AddCommand("Stop-Job").AddParameter("InstanceId", _remoteJobInstanceId).AddParameter("PassThru"); - AddReceiveJobCommandToPowerShell(_receivePowerShell, false); - _receivePowerShell.BeginInvoke((PSDataCollection)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null); - } - - /// - /// Worker method which suspends the job - /// - private void DoSuspendAsync() - { - if (!AssertSuspendJobIsValidAndSetToSuspending()) - { - OnSuspendJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - return; - } - - _receivePowerShell.Stop(); - _receivePowerShell.Commands.Clear(); - _receivePowerShell.GenerateNewInstanceId(); - _receivePowerShell.AddCommand("Suspend-Job").AddParameter("InstanceId", _remoteJobInstanceId).AddParameter("Wait"); - AddReceiveJobCommandToPowerShell(_receivePowerShell, false); - _receivePowerShell.BeginInvoke((PSDataCollection)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null); - } - - /// - /// Worker method to resume the job - /// - private void DoResumeAsync() - { - AssertResumeJobIsValidAndSetToRunning(); - - // If the job state was not set to running, the job should not be resumed. (True if job is in finished state.) - if (JobStateInfo.State != JobState.Running) - { - OnResumeJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - return; - } - - _receivePowerShell.Stop(); - _receivePowerShell.Commands.Clear(); - _receivePowerShell.GenerateNewInstanceId(); - _receivePowerShell.AddCommand("Resume-Job").AddParameter("InstanceId", _remoteJobInstanceId).AddParameter("Wait"); - AddReceiveJobCommandToPowerShell(_receivePowerShell, false); - _receivePowerShell.BeginInvoke((PSDataCollection)null, _receivePowerShellOutput, null, CleanupReceivePowerShell, null); - } - - /// - /// Worker method to remove the remote job object - /// - /// state information indicates the "force" parameter - private void DoRemove(object state) - { - AssertNotDisposed(); - _tracer.WriteMessage(ClassNameTrace, "DoRemove", _remoteJobInstanceId, this, "Start", null); - Dbg.Assert(state is bool, "State should be boolean indicating 'force'"); - - // Do not check RemoveCalled outside of the lock. We do not want to wait on RemoveComplete unless this lock - // has been released by the thread that continues. - if (_isDisposed || _remoteJobRemoved) return; - lock (SyncRoot) - { - if (_isDisposed || _remoteJobRemoved || _removeCalled) return; - // Only return here if the job has not been started. - if (_remoteJobInstanceId == Guid.Empty && !_startCalled) return; - AssertRemoveJobIsValid(); - Dbg.Assert(!_removeCalled, "removecalled should only be modified within base locks"); - _removeCalled = true; - RemoveComplete.Reset(); - } - - // _receivePowerShell will never be null and so - // there is no need to do a null check - Dbg.Assert(_receivePowerShell != null, "ReceivePowerShell should not be null"); - - try - { - // Ensure that the job has been initialized, if it has been started. If - // the instance ID is Guid.Empty after this, there was an error, and the - // job should not be on the server. If it is, we cannot reliably connect to - // remove it. - // Do this within the Try/Finally block so that the wait handle will be set - // on exit. - _jobInitializedWaitHandle.WaitOne(); - if (_remoteJobInstanceId == Guid.Empty) return; - - // Stop the receive-job command if it's in progress. - _receivePowerShell.Stop(); - - // Remove the job with Remove-Job. - using (PowerShell powershell = PowerShell.Create()) - { - // Either the runspace or runspace pool will be - // not null at this point. This is because of - // the following: - // 1. The job state is running - // 2. The job was started using either a runspace - // or a runspace pool - // 3. Once a job has been started, the values cannot - // be modified until the job reaches a terminal - // state - // Therefore it is not required to validate the above - // here - Dbg.Assert((_runspace != null && _runspacePool == null) || - (_runspace == null && _runspacePool != null), - "Either the runspace or a runspacepool should not be null"); - - AssignRunspaceOrRunspacePool(powershell); - - - // set the commands for the powershell - powershell.Commands.AddCommand("Remove-Job").AddParameter("InstanceId", _remoteJobInstanceId); - - if ((bool)state) powershell.AddParameter("Force", true).AddParameter("ErrorAction", ActionPreference.SilentlyContinue); - - try - { - _tracer.WriteMessage(ClassNameTrace, "DoRemove", _remoteJobInstanceId, this, - - "Invoking Remove-Job", null); - powershell.Invoke(); - } - catch (Exception e) - { - // since this is third party code call out - it is ok - // to catch Exception. In all other cases the specific - // exception must be caught - _tracer.WriteMessage(ClassNameTrace, "DoRemove", _remoteJobInstanceId, this, - "Setting job state to failed since invoking Remove-Job failed.", null); - DoSetJobState(JobState.Failed, e); - throw; - } - - if (powershell.Streams.Error != null && powershell.Streams.Error.Count > 0) - { - throw powershell.Streams.Error[0].Exception; - } - } - - _tracer.WriteMessage(ClassNameTrace, "DoRemove", _remoteJobInstanceId, this, - "Completed Invoking Remove-Job", null); - lock (SyncRoot) - { - _remoteJobRemoved = true; - } - if (!IsFinishedState(JobStateInfo.State)) - { - DoSetJobState(JobState.Stopped); - } - } - catch (Exception) - { - lock (SyncRoot) - { - Dbg.Assert(!_remoteJobRemoved, - "Should never allow DoRemove to be called again if proxy job has removed its server job."); - _removeCalled = false; - } - - throw; - } - finally - { - RemoveComplete.Set(); - } - } - - private void AddReceiveJobCommandToPowerShell(PowerShell powershell, bool writeJob) - { - powershell.AddCommand("Receive-Job").AddParameter("Wait").AddParameter("WriteEvents").AddParameter("Verbose").AddParameter("Debug"); - if (writeJob) - powershell.AddParameter("WriteJobInResults"); - if (RemoveRemoteJobOnCompletion) - powershell.AddParameter("AutoRemoveJob"); - } - - private void CleanupReceivePowerShell(IAsyncResult asyncResult) - { -#pragma warning disable 56500 - try - { - _receivePowerShell.EndInvoke(asyncResult); - - // set the state based on the previous computed state - _tracer.WriteMessage(ClassNameTrace, "CleanupReceivePowerShell", Guid.Empty, this, - "Setting job state to {0} from computed stated", _computedJobState.ToString()); - ValidateAndDoSetJobState(_computedJobState); - } - catch (PipelineStoppedException e) - { - // Raised if the pipeline was stopped. - // Pipeline stopped indicates that the command was interrupted, and it should - // not be used to indicate that a job failed. - _tracer.TraceException(e); - } - catch (PSRemotingDataStructureException e) - { - // Raised if the pipeline was stopped. (remote case) - // Pipeline stopped indicates that the command was interrupted, and it should - // not be used to indicate that a job failed. - _tracer.TraceException(e); - } - catch (RemoteException e) - { - if (Deserializer.IsInstanceOfType(e.SerializedRemoteException, typeof(PipelineStoppedException))) - { - // Raised if the pipeline was stopped. (remote case) - // Pipeline stopped indicates that the command was interrupted, and it should - // not be used to indicate that a job failed. - _tracer.TraceException(e); - } - else - { - _tracer.TraceException(e); - - DoSetJobState(JobState.Failed, e); - } - } - catch (Exception e) - { - // EndInvoke may throw any exception that the command execution could throw - // Since this is third party code, we will catch and trace the exception. - // Exception raised to the user using JobStateInfo.Reason. - _tracer.WriteMessage(ClassNameTrace, "CleanupReceivePowerShell", _remoteJobInstanceId, this, - "Exception calling receivePowerShell.EndInvoke", null); - _tracer.TraceException(e); - - DoSetJobState(JobState.Failed, e); - - // This throw would cause the client application to crash if an exception is thrown in the command invocation. - } -#pragma warning restore 56500 - } - - /// - /// Assigns either a runspace or runspacepool to the specified powershell - /// instance - /// - /// powershell instance to which the set has to - /// happen - private void AssignRunspaceOrRunspacePool(PowerShell powershell) - { - Dbg.Assert(_runspacePool == null ^ _runspace == null, "Either a runspace or a runspacepool should be assigned to the proxy job"); - - if (_runspacePool == null) - { - powershell.Runspace = _runspace; - } - else - { - powershell.RunspacePool = _runspacePool; - } - } - - private void HandleMyStateChange(object sender, JobStateEventArgs e) - { - switch (e.JobStateInfo.State) - { - case JobState.Running: - { - lock (SyncRoot) - { - if (e.PreviousJobStateInfo.State == JobState.NotStarted) - { - PSBeginTime = DateTime.Now; - } - - JobRunningOrFinished.Set(); - JobSuspendedOrFinished.Reset(); - OnResumeJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - } - } - break; - - case JobState.Suspended: - { - lock (SyncRoot) - { - PSEndTime = DateTime.Now; - - JobSuspendedOrFinished.Set(); - JobRunningOrFinished.Reset(); - OnSuspendJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - } - } - break; - case JobState.Failed: - case JobState.Completed: - case JobState.Stopped: - { - lock (SyncRoot) - { - // In a finished state, further state transitions will not occur. - PSEndTime = DateTime.Now; - - // Release any thread waiting for Start, Suspend, Resume - JobRunningOrFinished.Set(); - OnResumeJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null)); - - JobSuspendedOrFinished.Set(); - OnSuspendJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null)); - - _jobInitializedWaitHandle.Set(); - OnStartJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null)); - - OnStopJobCompleted(new AsyncCompletedEventArgs(e.JobStateInfo.Reason, false, null)); - } - } - break; - } - - ProcessQueue(); - } - - /// - /// Event handler for InvocationStateChanged on the powershell - /// object running receive-job - /// - /// sender of this event - /// argument describing this event - private void ReceivePowerShellInvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e) - { - _tracer.WriteMessage(ClassNameTrace, "ReceivePowerShellInvocationStateChanged", _remoteJobInstanceId, this, - "receivePowerShell state changed to {0}", e.InvocationStateInfo.State.ToString()); - - switch (e.InvocationStateInfo.State) - { - case PSInvocationState.Running: - { - return; - } - - case PSInvocationState.Completed: - // Job State in this case is set in CleanupReceivePowerShell. - break; - - case PSInvocationState.Failed: - { - var newState = JobState.Failed; - var reason = e.InvocationStateInfo.Reason == null - ? String.Empty - : e.InvocationStateInfo.Reason.ToString(); - _tracer.WriteMessage(ClassNameTrace, "ReceivePowerShellInvocationStateChanged", _remoteJobInstanceId, this, - "Setting job state to {0} old state was {1} and reason is {2}.", newState.ToString(), JobStateInfo.State.ToString(), reason); - DoSetJobState(newState, e.InvocationStateInfo.Reason); - } - break; - - case PSInvocationState.Stopped: - break; - } - } - - /// - /// Assigns job properties and creates child job tree. - /// - /// Deserialized job object representing the remote job for this proxy. - private void PopulateJobProperties(PSObject o) - { - Dbg.Assert(Deserializer.IsDeserializedInstanceOfType(o, typeof(Job)), - "Cannot populate job members unless o is a deserialized job."); - - TryGetJobPropertyValue(o, "InstanceId", out _remoteJobInstanceId); - TryGetJobPropertyValue(o, "StatusMessage", out _remoteJobStatusMessage); - TryGetJobPropertyValue(o, "Location", out _remoteJobLocation); - - s_structuredTracer.ProxyJobRemoteJobAssociation(InstanceId, _remoteJobInstanceId); - - string name; - TryGetJobPropertyValue(o, "Name", out name); - Name = name; - - // Create proxy jobs for each child job. - // The remote child jobs will be started and managed by their parent. They are provided as - // children on this job for access to the state, properties and for direct management. - PSObject remoteChildrenObject; - if (!TryGetJobPropertyValue(o, "ChildJobs", out remoteChildrenObject)) return; // No ChildJobs property - var remoteChildren = remoteChildrenObject.BaseObject as ArrayList; - Dbg.Assert(remoteChildren != null, "ChildJobs property had unexpected type."); - foreach (PSObject job in remoteChildren.Cast().Where(job => !(job.BaseObject is string))) - { - Guid childJobInstanceId; - if (!TryGetJobPropertyValue(job, "InstanceId", out childJobInstanceId)) - { - Dbg.Assert(false, "ChildJobs should be serialized to include InstanceID, cannot interact with them otherwise."); - continue; - } - var childProxyJob = new PSChildJobProxy(Command, job); // All have the same workflow name. - _childJobsMapping.Add(childJobInstanceId, childProxyJob); - - // Set DataAddedCount for each child to match parent. - // This may otherwise not be available to set until data arrives. - childProxyJob.Output.DataAddedCount = Output.DataAddedCount; - childProxyJob.Error.DataAddedCount = Error.DataAddedCount; - childProxyJob.Progress.DataAddedCount = Progress.DataAddedCount; - childProxyJob.Warning.DataAddedCount = Warning.DataAddedCount; - childProxyJob.Verbose.DataAddedCount = Verbose.DataAddedCount; - childProxyJob.Debug.DataAddedCount = Debug.DataAddedCount; - childProxyJob.Information.DataAddedCount = Information.DataAddedCount; - - // Now see if there are start parameters we can populate. - PSObject childJobStartParametersObject; - if (TryGetJobPropertyValue(job, "StartParameters", out childJobStartParametersObject)) - { - PopulateStartParametersOnChild(childJobStartParametersObject, childProxyJob); - } - ChildJobs.Add(childProxyJob); - } - } - - private void PopulateStartParametersOnChild(PSObject childJobStartParametersObject, PSChildJobProxy childProxyJob) - { - ArrayList childJobStartParameters = childJobStartParametersObject.BaseObject as ArrayList; - if (childJobStartParameters != null) - { - List listComParCol = new List(); - // check childjobstartparameters--was a List - foreach (PSObject paramCollection in childJobStartParameters.Cast().Where(paramCollection => !(paramCollection.BaseObject is string))) - { - ArrayList parameterCollection = paramCollection.BaseObject as ArrayList; - if (parameterCollection != null) - { - CommandParameterCollection newComParCol = new CommandParameterCollection(); - foreach (PSObject deserializedCommandParameter in parameterCollection.Cast().Where(deserializedCommandParameter => !(deserializedCommandParameter.BaseObject is string))) - { - string parameterName; - object parameterValue; - if (TryGetJobPropertyValue(deserializedCommandParameter, "Name", out parameterName) && TryGetJobPropertyValue(deserializedCommandParameter, "Value", out parameterValue)) - { - CommandParameter cp = new CommandParameter(parameterName, parameterValue); - newComParCol.Add(cp); - } - } - listComParCol.Add(newComParCol); - } - } - childProxyJob.StartParameters = listComParCol; - } - } - - private void RegisterChildEvents() - { - if (_childEventsRegistered) return; - lock (SyncRoot) - { - if (_childEventsRegistered) return; - _childEventsRegistered = true; - - // if the data added handler was provided, use it to subscribe to child events. - if (_dataAddedHandler != null) - { - foreach (PSChildJobProxy job in ChildJobs) - { - Dbg.Assert(job != null, "ChildJob that is not child job proxy exists"); - job.JobDataAdded += _dataAddedHandler; - } - } - - // we need to register for our handler to compute parent proxy's state - // after the passed in handlers are registered. This will ensure that - // our handler is invoked last - foreach (var job in ChildJobs) - { - job.StateChanged += HandleChildProxyJobStateChanged; - } - - // likewise for state changed - if (_stateChangedHandler != null) - { - foreach (PSChildJobProxy job in ChildJobs) - { - Dbg.Assert(job != null, "ChildJob that is not child job proxy exists"); - job.StateChanged += _stateChangedHandler; - } - } - } - } - - private void UnregisterChildEvents() - { - lock (SyncRoot) - { - // Check inside the lock only, if the lock is held by the register method - // the childEventsRegistered may be altered before the lock is released. - if (!_childEventsRegistered) return; - - // if the data added handler was provided, use it to unsubscribe from child events. - if (_dataAddedHandler != null) - { - foreach (PSChildJobProxy job in ChildJobs) - { - Dbg.Assert(job != null, "ChildJob that is not child job proxy exists"); - job.JobDataAdded -= _dataAddedHandler; - } - } - - // likewise for state changed - if (_stateChangedHandler != null) - { - foreach (PSChildJobProxy job in ChildJobs) - { - Dbg.Assert(job != null, "ChildJob that is not child job proxy exists"); - job.StateChanged -= _stateChangedHandler; - } - } - - foreach (var job in ChildJobs) - { - job.StateChanged -= HandleChildProxyJobStateChanged; - } - - _childEventsRegistered = false; - } - } - - private void HandleChildProxyJobStateChanged(object sender, JobStateEventArgs e) - { - JobState computedJobState; - if (!ContainerParentJob.ComputeJobStateFromChildJobStates(ClassNameTrace, e, ref _blockedChildJobsCount, - ref _suspendedChildJobsCount, - ref _suspendingChildJobsCount, - ref _finishedChildJobsCount, - ref _failedChildJobsCount, - ref _stoppedChildJobsCount, - ChildJobs.Count, - out computedJobState)) - return; - if (computedJobState == JobState.Suspending) return; // Ignore for proxy job - - _tracer.WriteMessage(ClassNameTrace, "HandleChildProxyJobStateChanged", Guid.Empty, this, - "storing job state to {0}", computedJobState.ToString()); - // we need to store the state and set it only when invocation - // state changed for _receivePowerShell is received. This is to - // enable consumers from disposing the proxy job on its state - // changed handler - _computedJobState = computedJobState; - } - - /// - /// Check if changes to the jobs properties can be accepted - /// - private void AssertChangesCanBeAccepted() - { - lock (SyncRoot) - { - AssertNotDisposed(); - - if (JobStateInfo.State != JobState.NotStarted) - { - throw new InvalidJobStateException(JobStateInfo.State); - } - } - } - - private void AssertJobCanBeStartedAndSetStateToRunning() - { - lock (SyncRoot) - { - AssertNotDisposed(); - - // only a job which is not started or which is in a terminal - // state can be restarted - if (JobStateInfo.State != JobState.NotStarted && !IsFinishedState(JobStateInfo.State)) - { - throw new InvalidJobStateException(JobStateInfo.State, StringUtil.Format(PowerShellStrings.JobCannotBeStartedWhenRunning)); - } - - if (_startCalled) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.JobCanBeStartedOnce); - } - - _startCalled = true; - } - - _tracer.WriteMessage(ClassNameTrace, "AssertJobCanBeStartedAndSetStateToRunning", _remoteJobInstanceId, this, - "Setting job state to running", null); - ValidateAndDoSetJobState(JobState.Running); - } - - private bool AssertStopJobIsValidAndSetToStopping() - { - lock (SyncRoot) - { - AssertNotDisposed(); - - if (JobStateInfo.State == JobState.NotStarted) - { - throw new InvalidJobStateException(JobState.NotStarted); - } - - if (IsFinishedState(JobStateInfo.State)) - return false; - } - - DoSetJobState(JobState.Stopping); - return true; - } - - private bool AssertSuspendJobIsValidAndSetToSuspending() - { - lock (SyncRoot) - { - AssertNotDisposed(); - - // Valid states: Running, Suspended, Suspending - if (JobStateInfo.State != JobState.Suspended - && JobStateInfo.State != JobState.Suspending - && JobStateInfo.State != JobState.Running) - { - throw new InvalidJobStateException(JobStateInfo.State); - } - } - - if (JobStateInfo.State != JobState.Running) return false; - DoSetJobState(JobState.Suspending); - return true; - } - - private void AssertResumeJobIsValidAndSetToRunning() - { - lock (SyncRoot) - { - AssertNotDisposed(); - // Valid states: Running, Suspended, Suspending - if (JobStateInfo.State != JobState.Suspended - && JobStateInfo.State != JobState.Suspending - && JobStateInfo.State != JobState.Running) - { - throw new InvalidJobStateException(JobStateInfo.State); - } - } - - if (JobStateInfo.State != JobState.Running) ValidateAndDoSetJobState(JobState.Running); - foreach (PSChildJobProxy job in ChildJobs) - { - Dbg.Assert(job != null, "child job is not PSChildJobProxy"); - if (!IsFinishedState(job.JobStateInfo.State)) - job.DoSetJobState(JobState.Running); - } - } - - private void AssertRemoveJobIsValid() - { - if (JobStateInfo.State == JobState.NotStarted || _remoteJobInstanceId == Guid.Empty) - { - throw new InvalidJobStateException(JobStateInfo.State); - } - } - - /// - /// Assert if the object is not yet disposed and if so - /// throw an ObjectDisposedException - /// - /// Thrown if - /// the object has already been disposed - /// Method is not thread-safe. Caller has to - /// ensure thread safety - private new void AssertNotDisposed() - { - if (_isDisposed) - { - throw PSTraceSource.NewObjectDisposedException("PSJobProxy"); - } - } - - private void ValidateAndDoSetJobState(JobState state, Exception reason = null) - { - // Do not set the state to running if the job is in the process of suspending or stopping. - // This can happen if Stop or Suspend is called during job initialization. - if ((_previousState == JobState.Stopping || _previousState == JobState.Suspending) && state == JobState.Running) - return; - DoSetJobState(state, reason); - } - - private void DoSetJobState(JobState state, Exception reason = null) - { - // If the object is disposed, the user does not care about state. - if (_isDisposed) return; - - lock (SyncRoot) - { - if (_previousState == state) return; // Do not set the state if the desired state is accurate. - _previousState = state; - } - -#if DEBUG - switch (JobStateInfo.State) - { - case JobState.Running: - Dbg.Assert(state != JobState.NotStarted && state != JobState.Blocked, - "JobProxy invalid state transition from Running."); - break; - case JobState.Stopping: - Dbg.Assert( - state == JobState.Stopped || state == JobState.Failed || state == JobState.Completed, - "JobProxy invalid state transition from Stopping."); - break; - case JobState.Stopped: - Dbg.Assert(false, "JobProxy should never transition after Stopped state."); - break; - case JobState.Suspending: - Dbg.Assert( - state == JobState.Suspended || state == JobState.Completed || state == JobState.Failed || - state == JobState.Stopped || state == JobState.Stopping, - "JobProxy invalid state transition from Suspending."); - break; - case JobState.Suspended: - Dbg.Assert( - state == JobState.Running || state == JobState.Stopping || state == JobState.Stopped, - "JobProxy invalid state transition from Suspended."); - break; - case JobState.Failed: - Dbg.Assert(false, "JobProxy should never transition after Failed state."); - break; - case JobState.Completed: - Dbg.Assert(false, "JobProxy should never transition after Completed state."); - break; - case JobState.Disconnected: - Dbg.Assert(state != JobState.NotStarted && state != JobState.Blocked, - "JobProxy invalid state transition from Disconnected."); - break; - case JobState.Blocked: - Dbg.Assert(false, "JobProxy should never be in a blocked state"); - break; - default: - break; - } -#endif - - try - { - _tracer.WriteMessage(ClassNameTrace, "DoSetJobState", _remoteJobInstanceId, this, - "BEGIN Set job state to {0} and call event handlers", state.ToString()); - s_structuredTracer.EndProxyJobExecution(InstanceId); - s_structuredTracer.BeginProxyJobEventHandler(InstanceId); - SetJobState(state, reason); - s_structuredTracer.EndProxyJobEventHandler(InstanceId); - _tracer.WriteMessage(ClassNameTrace, "DoSetJobState", _remoteJobInstanceId, this, - "END Set job state to {0} and call event handlers", state.ToString()); - } - catch (ObjectDisposedException) - { - _tracer.WriteMessage(ClassNameTrace, "DoSetJobState", _remoteJobInstanceId, this, - "Caught object disposed exception", null); - } - } - - #endregion Private Methods - - #region Private methods for Output management - - private T GetRecord(object sender) - { - lock (SyncRoot) - { - var collection = sender as PSDataCollection; - Dbg.Assert(collection != null, "Sender cannot be null"); - - T data = collection.ReadAndRemoveAt0(); - - Dbg.Assert(data != null, "DataAdded should be raised for each object added"); - return data; - } - } - - private void DataAddedToOutput(object sender, DataAddedEventArgs e) - { - var newObject = GetRecord(sender); - - if (!_jobInitialized) - { - // If the job is not initialized, then this should be the first object received after - // invoking a command. This condition should be met when StartJob is called. - - // Note: These events will always come in sequence, So this is not thread safe. - _jobInitialized = true; - - // Because we have added the AsJob parameter we expect - // the command to write out a job, if it does not - // the command fails.) - // If the command writes an error before any output, the proxy will use this - // as an indication of a failed command execution. - if (!Deserializer.IsDeserializedInstanceOfType(newObject, typeof(Job))) - { - _tracer.WriteMessage(ClassNameTrace, "DataAddedToOutput", _remoteJobInstanceId, this, - "Setting job state to failed. Command did not return a job object.", - null); - Exception reason = (_receivePowerShell.Streams.Error.Count == 0 || - _receivePowerShell.Streams.Error[0].Exception == null) - ? PSTraceSource.NewNotSupportedException(PowerShellStrings.CommandDoesNotWriteJob) - : _receivePowerShell.Streams.Error[0].Exception; - DoSetJobState(JobState.Failed, reason); - _jobInitializedWaitHandle.Set(); - OnStartJobCompleted(new AsyncCompletedEventArgs(reason, false, null)); - return; - } - - PopulateJobProperties(newObject); - - RegisterChildEvents(); - - // Child Job States must be set after events are subscribed. - // All jobs start in the running state. Receive-Job will return - // a persistent job state event for each child job sometime after - // the job object is returned. This will update the job state of - // the child in a subsequent call to DataAddedToOutput. - foreach (PSChildJobProxy child in ChildJobs) - { - Dbg.Assert(child != null, "Child should always be PSChildJobProxy"); - child.DoSetJobState(JobState.Running); - } - - // Release any thread waiting start. - _jobInitializedWaitHandle.Set(); - _tracer.WriteMessage(ClassNameTrace, "DataAddedToOutput", Guid.Empty, this, - "BEGIN Invoke StartJobCompleted event", null); - OnStartJobCompleted(new AsyncCompletedEventArgs(null, false, null)); - _tracer.WriteMessage(ClassNameTrace, "DataAddedToOutput", Guid.Empty, this, - "END Invoke StartJobCompleted event", null); - ProcessQueue(); - return; - } - - if (newObject.Properties[RemotingConstants.EventObject] != null) - { - PSPropertyInfo guidProperty = newObject.Properties[RemotingConstants.SourceJobInstanceId]; - Guid sourceJobId = guidProperty != null ? (Guid)guidProperty.Value : Guid.Empty; - - if (guidProperty == null || sourceJobId == Guid.Empty) - { - Diagnostics.Assert(false, "We should not get guidProperty as null or an empty source job id in non interop scenarios"); - return; - } - - if (!_childJobsMapping.ContainsKey(sourceJobId)) - { - if (sourceJobId != _remoteJobInstanceId) - { - Diagnostics.Assert(false, - "We should not get an unidentified source job id in non interop scenarios"); - } - return; - } - - // If the event args are job state, the event is a Job's StateChanged event. - var jobStateEventArgs = newObject.BaseObject as JobStateEventArgs ?? - Microsoft.PowerShell.DeserializingTypeConverter.RehydrateJobStateEventArgs(newObject); - if (jobStateEventArgs != null) - { - // If the child job sent the event, then change the proxy child's state. - // If the current job raised the event, the state change will come when the - // receive call returns. - _tracer.WriteMessage(ClassNameTrace, "DataAddedToOutput", Guid.Empty, this, - "Updating child job {0} state to {1} ", sourceJobId.ToString(), - jobStateEventArgs.JobStateInfo.State.ToString()); - ((PSChildJobProxy)_childJobsMapping[sourceJobId]).DoSetJobState( - jobStateEventArgs.JobStateInfo.State, jobStateEventArgs.JobStateInfo.Reason); - _tracer.WriteMessage(ClassNameTrace, "DataAddedToOutput", Guid.Empty, this, - "Finished updating child job {0} state to {1} ", sourceJobId.ToString(), - jobStateEventArgs.JobStateInfo.State.ToString()); - } - return; - } - - SortOutputObject(newObject); - } - - private void SortOutputObject(PSObject newObject) - { - PSPropertyInfo guidProperty = newObject.Properties[RemotingConstants.SourceJobInstanceId]; - Guid sourceJobId = guidProperty != null ? (Guid)guidProperty.Value : Guid.Empty; - if (guidProperty == null || sourceJobId == Guid.Empty || !_childJobsMapping.ContainsKey(sourceJobId)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - Output.Add(newObject); - return; - } - - // make sure the tag reflects client side job. - newObject.Properties.Remove(RemotingConstants.SourceJobInstanceId); - newObject.Properties.Add(new PSNoteProperty(RemotingConstants.SourceJobInstanceId, - ((PSChildJobProxy)_childJobsMapping[sourceJobId]).InstanceId)); - ((PSChildJobProxy)_childJobsMapping[sourceJobId]).Output.Add(newObject); - } - - private void DataAddedToError(object sender, DataAddedEventArgs e) - { - var newError = GetRecord(sender); - - Dbg.Assert(newError != null, "DataAdded should be raised once for each piece of data."); - - SortError(newError); - } - - private void SortError(ErrorRecord record) - { - // var newJobError = record as RemotingErrorRecord; - // if (newJobError == null || !_childJobsMapping.ContainsKey(newJobError.OriginInfo.InstanceID)) - Guid id = Guid.Empty; - string computerName = string.Empty; - if (record.ErrorDetails != null) - { - record.ErrorDetails.RecommendedAction = RemoveIdentifierInformation(record.ErrorDetails.RecommendedAction, out id, out computerName); - } - - if (id == Guid.Empty || !_childJobsMapping.ContainsKey(id)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteError(record); - return; - } - - // make sure the tag reflects client side job. - // newJobError.OriginInfo.InstanceID = ((PSChildJobProxy)_childJobsMapping[newJobError.OriginInfo.InstanceID]).InstanceId; - var oi = new OriginInfo(null, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[id]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[id]).WriteError(new RemotingErrorRecord(record, oi)); - } - - private void DataAddedToProgress(object sender, DataAddedEventArgs e) - { - var newRecord = GetRecord(sender); - - Dbg.Assert(newRecord != null, "DataAdded should be raised once for each piece of data."); - - SortProgress(newRecord); - } - - private void SortProgress(ProgressRecord newRecord) - { - // var newJobRecord = newRecord as RemotingProgressRecord; - // if (newJobRecord == null || !_childJobsMapping.ContainsKey(newJobRecord.OriginInfo.InstanceID)) - Guid id; - string computerName; - newRecord.CurrentOperation = RemoveIdentifierInformation(newRecord.CurrentOperation, out id, out computerName); - if (id == Guid.Empty || !_childJobsMapping.ContainsKey(id)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteProgress(newRecord); - return; - } - - // make sure the tag reflects client side job. - // newJobRecord.OriginInfo.InstanceID = ((PSChildJobProxy)_childJobsMapping[newJobRecord.OriginInfo.InstanceID]).InstanceId; - var oi = new OriginInfo(computerName, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[id]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[id]).WriteProgress(new RemotingProgressRecord(newRecord, oi)); - } - - private void DataAddedToDebug(object sender, DataAddedEventArgs e) - { - var record = GetRecord(sender); - - Dbg.Assert(record != null, "DataAdded should be raised once for each piece of data."); - - SortDebug(record); - } - - private void SortDebug(DebugRecord record) - { - Guid remoteJobInstanceId; - string computerName; - string message = RemoveIdentifierInformation(record.Message, out remoteJobInstanceId, out computerName); - if (remoteJobInstanceId == Guid.Empty || !_childJobsMapping.ContainsKey(remoteJobInstanceId)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteDebug(message); - return; - } - - // make sure the tag reflects client side job. - var originInfo = new OriginInfo(computerName, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).Debug.Add(new RemotingDebugRecord(message, originInfo)); - } - - private void DataAddedToWarning(object sender, DataAddedEventArgs e) - { - var record = GetRecord(sender); - - Dbg.Assert(record != null, "DataAdded should be raised once for each piece of data."); - - SortWarning(record); - } - - private void SortWarning(WarningRecord record) - { - Guid remoteJobInstanceId; - string computerName; - string message = RemoveIdentifierInformation(record.Message, out remoteJobInstanceId, out computerName); - if (remoteJobInstanceId == Guid.Empty || !_childJobsMapping.ContainsKey(remoteJobInstanceId)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteWarning(message); - return; - } - - // make sure the tag reflects client side job. - var originInfo = new OriginInfo(computerName, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).Warning.Add(new RemotingWarningRecord(message, originInfo)); - } - - private void DataAddedToVerbose(object sender, DataAddedEventArgs e) - { - var record = GetRecord(sender); - - Dbg.Assert(record != null, "DataAdded should be raised once for each piece of data."); - - SortVerbose(record); - } - - private void SortVerbose(VerboseRecord record) - { - Guid remoteJobInstanceId; - string computerName; - string message = RemoveIdentifierInformation(record.Message, out remoteJobInstanceId, out computerName); - if (remoteJobInstanceId == Guid.Empty || !_childJobsMapping.ContainsKey(remoteJobInstanceId)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteVerbose(message); - return; - } - - // make sure the tag reflects client side job. - var originInfo = new OriginInfo(computerName, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).Verbose.Add(new RemotingVerboseRecord(message, originInfo)); - } - - private void DataAddedToInformation(object sender, DataAddedEventArgs e) - { - var record = GetRecord(sender); - - Dbg.Assert(record != null, "DataAdded should be raised once for each piece of data."); - - SortInformation(record); - } - - private void SortInformation(InformationRecord record) - { - Guid remoteJobInstanceId; - string computerName; - record.Source = RemoveIdentifierInformation(record.Source, out remoteJobInstanceId, out computerName); - if (remoteJobInstanceId == Guid.Empty || !_childJobsMapping.ContainsKey(remoteJobInstanceId)) - { - // If there is no child job matching the source, add it to the parent's collection so that it is not lost. - WriteInformation(record); - return; - } - - // make sure the tag reflects client side job. - var originInfo = new OriginInfo(computerName, Guid.Empty, ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).InstanceId); - ((PSChildJobProxy)_childJobsMapping[remoteJobInstanceId]).Information.Add(new RemotingInformationRecord(record, originInfo)); - } - - private static string RemoveIdentifierInformation(string message, out Guid jobInstanceId, out string computerName) - { - jobInstanceId = Guid.Empty; - computerName = string.Empty; - - if (!string.IsNullOrEmpty(message)) - { - string[] parts = message.Split(Utils.Separators.Colon, 3); - - if (parts.Length == 3) - { - if (!Guid.TryParse(parts[0], out jobInstanceId)) - jobInstanceId = Guid.Empty; - computerName = parts[1]; - return parts[2]; - } - } - return message; - } - - #endregion Output management - - #region IDisposable Overrides - - /// - /// Dispose all managed resources - /// - /// true when being disposed - protected override void Dispose(bool disposing) - { - if (!disposing) return; - if (_isDisposed) return; - - lock (SyncRoot) - { - if (_isDisposed) return; - - _isDisposed = true; - } - if (_receivePowerShell != null) - { - _receivePowerShell.Stop(); - _receivePowerShell.InvocationStateChanged -= ReceivePowerShellInvocationStateChanged; - _receivePowerShell.Streams.Error.DataAdded -= DataAddedToError; - _receivePowerShell.Streams.Warning.DataAdded -= DataAddedToWarning; - _receivePowerShell.Streams.Verbose.DataAdded -= DataAddedToVerbose; - _receivePowerShell.Streams.Progress.DataAdded -= DataAddedToProgress; - _receivePowerShell.Streams.Debug.DataAdded -= DataAddedToDebug; - _receivePowerShell.Streams.Information.DataAdded -= DataAddedToInformation; - _receivePowerShell.Dispose(); - } - - UnregisterChildEvents(); - StateChanged -= HandleMyStateChange; - - _receivePowerShellOutput.DataAdded -= DataAddedToOutput; - if (_receivePowerShellOutput != null) _receivePowerShellOutput.Dispose(); - - if (_removeComplete != null) _removeComplete.Dispose(); - if (_jobRunningOrFinished != null) _jobRunningOrFinished.Dispose(); - _jobInitializedWaitHandle.Dispose(); - if (_jobSuspendedOrFinished != null) _jobSuspendedOrFinished.Dispose(); - - if (ChildJobs != null && ChildJobs.Count > 0) - { - foreach (var job in ChildJobs) - { - // Child Job events are unregistered by UnregisterChildEvents above. - job.Dispose(); - } - } - - _tracer.Dispose(); - } - - #endregion IDisposable Overrides - - #region Private Members - - private enum QueueOperation - { Stop, Suspend, Resume } - private ConcurrentQueue _pendingOperations = new ConcurrentQueue(); - - private ManualResetEvent _removeComplete; - private ManualResetEvent RemoveComplete - { - get - { - if (_removeComplete == null) - { - lock (SyncRoot) - { - if (_removeComplete == null) - { - // this assert is required so that a wait handle - // is not created after the object is disposed - // which will result in a leak - AssertNotDisposed(); - _removeComplete = new ManualResetEvent(false); - } - } - } - - return _removeComplete; - } - } - - private ManualResetEvent _jobRunningOrFinished; - private ManualResetEvent JobRunningOrFinished - { - get - { - if (_jobRunningOrFinished == null) - { - lock (SyncRoot) - { - if (_jobRunningOrFinished == null) - { - // this assert is required so that a wait handle - // is not created after the object is disposed - // which will result in a leak - AssertNotDisposed(); - _jobRunningOrFinished = new ManualResetEvent(false); - } - } - } - - return _jobRunningOrFinished; - } - } - - private readonly ManualResetEvent _jobInitializedWaitHandle = new ManualResetEvent(false); - - private ManualResetEvent _jobSuspendedOrFinished; - private ManualResetEvent JobSuspendedOrFinished - { - get - { - if (_jobSuspendedOrFinished == null) - { - lock (SyncRoot) - { - if (_jobSuspendedOrFinished == null) - { - // this assert is required so that a wait handle - // is not created after the object is disposed - // which will result in a leak - AssertNotDisposed(); - _jobSuspendedOrFinished = new ManualResetEvent(false); - } - } - } - return _jobSuspendedOrFinished; - } - } - - private PSCommand _pscommand; - private Runspace _runspace; - private RunspacePool _runspacePool; - private EventHandler _dataAddedHandler; - private EventHandler _stateChangedHandler; - private const String ResBaseName = "PowerShellStrings"; - private Guid _remoteJobInstanceId = Guid.Empty; - private string _remoteJobStatusMessage = String.Empty; - private string _remoteJobLocation = String.Empty; - private readonly Hashtable _childJobsMapping = new Hashtable(); - private readonly PowerShell _receivePowerShell = PowerShell.Create(); - private readonly PSDataCollection _receivePowerShellOutput = new PSDataCollection(); - private bool _moreData = true; - private JobState _previousState = JobState.NotStarted; - private JobState _computedJobState; - private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); - private static Tracer s_structuredTracer = new Tracer(); - private bool _jobInitialized; - private bool _removeCalled; - private bool _startCalled; - private bool _receiveIsValidCall; - private bool _isDisposed; - private bool _remoteJobRemoved; - private bool _childEventsRegistered; - private object _inProgressSyncObject = new object(); - private bool _inProgress = false; - private bool _workerCreated = false; - private const string ClassNameTrace = "PSJobProxy"; - // count of number of child jobs which have finished - private int _finishedChildJobsCount = 0; - - // count of number of child jobs which are blocked - private int _blockedChildJobsCount = 0; - - // count of number of child jobs which are suspended - private int _suspendedChildJobsCount = 0; - - // count of number of child jobs which are suspending - private int _suspendingChildJobsCount = 0; - - // count of number of child jobs which failed - private int _failedChildJobsCount = 0; - - // count of number of child jobs which stopped - private int _stoppedChildJobsCount = 0; - - #endregion Private Members - } - - /// - /// Job class used for children of PSJobProxy jobs. - /// - public sealed class PSChildJobProxy : Job2 - { - internal PSChildJobProxy(string command, PSObject o) - : base(command) - { - PSJobProxy.TryGetJobPropertyValue(o, "StatusMessage", out _statusMessage); - PSJobProxy.TryGetJobPropertyValue(o, "Location", out _location); - - string name; - PSJobProxy.TryGetJobPropertyValue(o, "Name", out name); - Name = name; - - Output.DataAdded += OutputAdded; - Error.DataAdded += ErrorAdded; - Warning.DataAdded += WarningAdded; - Verbose.DataAdded += VerboseAdded; - Progress.DataAdded += ProgressAdded; - Debug.DataAdded += DebugAdded; - Information.DataAdded += InformationAdded; - } - - internal void AssignDisconnectedState() - { - DoSetJobState(JobState.Disconnected); - } - - /// - /// Method to raise the event when this job has data added. - /// - /// argument describing - /// an exception that is associated with the event - private void OnJobDataAdded(JobDataAddedEventArgs eventArgs) - { -#pragma warning disable 56500 - try - { - _tracer.WriteMessage(ClassNameTrace, "OnJobDataAdded", Guid.Empty, this, "BEGIN call event handlers"); - JobDataAdded.SafeInvoke(this, eventArgs); - _tracer.WriteMessage(ClassNameTrace, "OnJobDataAdded", Guid.Empty, this, "END call event handlers"); - } - catch (Exception exception) - { - // errors in the handlers are not errors in the operation - // silently ignore them - _tracer.WriteMessage(ClassNameTrace, "OnJobDataAdded", Guid.Empty, this, "END Exception thrown in JobDataAdded handler"); - _tracer.TraceException(exception); - } -#pragma warning restore 56500 - } - - private void OutputAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Output, e.Index)); - } - private void ErrorAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Error, e.Index)); - } - private void WarningAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Warning, e.Index)); - } - private void VerboseAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Verbose, e.Index)); - } - private void ProgressAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Progress, e.Index)); - } - private void DebugAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Debug, e.Index)); - } - private void InformationAdded(object sender, DataAddedEventArgs e) - { - OnJobDataAdded(new JobDataAddedEventArgs(this, PowerShellStreamType.Information, e.Index)); - } - - private static Tracer s_structuredTracer = new Tracer(); - internal void DoSetJobState(JobState state, Exception reason = null) - { - if (_disposed) return; - -#if DEBUG - switch (JobStateInfo.State) - { - case JobState.Running: - Dbg.Assert(state != JobState.NotStarted && state != JobState.Blocked, - "ChildJobProxy invalid state transition from Running."); - break; - case JobState.Stopping: - Dbg.Assert( - state == JobState.Stopped || state == JobState.Failed || state == JobState.Completed, - "ChildJobProxy invalid state transition from Stopping."); - break; - case JobState.Stopped: - Dbg.Assert(false, "ChildJobProxy should never transition after Stopped state."); - break; - case JobState.Suspending: - Dbg.Assert( - state == JobState.Suspended || state == JobState.Completed || state == JobState.Failed || - state == JobState.Stopped || state == JobState.Stopping, - "ChildJobProxy invalid state transition from Suspending."); - break; - case JobState.Suspended: - Dbg.Assert( - state == JobState.Running || state == JobState.Stopping || state == JobState.Stopped, - "ChildJobProxy invalid state transition from Suspended."); - break; - case JobState.Failed: - Dbg.Assert(false, "ChildJobProxy should never transition after Failed state."); - break; - case JobState.Completed: - Dbg.Assert(false, "ChildJobProxy should never transition after Completed state."); - break; - case JobState.Disconnected: - Dbg.Assert(state != JobState.NotStarted && state != JobState.Blocked, - "ChildJobProxy invalid state transition from Disconnected."); - break; - case JobState.Blocked: - Dbg.Assert(false, "ChildJobProxy should never be in a blocked state"); - break; - default: - break; - } -#endif - - try - { - Dbg.Assert(state != JobStateInfo.State, "Setting job state should always change the state."); - _tracer.WriteMessage("PSChildJobProxy", "DoSetJobState", Guid.Empty, this, - "BEGIN Set job state to {0} and call event handlers", state.ToString()); - s_structuredTracer.BeginProxyChildJobEventHandler(InstanceId); - SetJobState(state, reason); - s_structuredTracer.EndProxyJobEventHandler(InstanceId); - _tracer.WriteMessage("PSChildJobProxy", "DoSetJobState", Guid.Empty, this, - "END Set job state to {0} and call event handlers", state.ToString()); - } - catch (ObjectDisposedException) - { - // If the object is disposed, the user will not need the state. - } - } - - /// - /// Dispose - /// - /// - protected override void Dispose(bool disposing) - { - if (_disposed) return; - lock (_syncObject) - { - if (_disposed) return; - _disposed = true; - - Output.DataAdded -= OutputAdded; - Error.DataAdded -= ErrorAdded; - Warning.DataAdded -= WarningAdded; - Verbose.DataAdded -= VerboseAdded; - Progress.DataAdded -= ProgressAdded; - Debug.DataAdded -= DebugAdded; - Information.DataAdded -= InformationAdded; - } - base.Dispose(disposing); - } - - #region Control method overrides - - /// - /// Not supported - /// - public override void StartJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void StartJobAsync() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// StopJob - /// - /// - /// - public override void StopJob(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void StopJobAsync() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// StopJobAsync - /// - /// - /// - public override void StopJobAsync(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void SuspendJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void SuspendJobAsync() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// SuspendJob - /// - /// - /// - public override void SuspendJob(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// SuspendJobAsync - /// - /// - /// - public override void SuspendJobAsync(bool force, string reason) - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void ResumeJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void ResumeJobAsync() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void UnblockJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void UnblockJobAsync() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - /// - /// Not supported - /// - public override void StopJob() - { - throw PSTraceSource.NewNotSupportedException(PowerShellStrings.ProxyChildJobControlNotSupported); - } - - #endregion Control method overrides - - #region Public properties - - /// - /// This event will be raised whenever data has been added to one of the job object's - /// 6 collections. The event arguments include the job itself, the data type, indicating - /// which collection has data added, and the index. - /// - public event EventHandler JobDataAdded; - - /// - /// Status message - /// - public override string StatusMessage - { - get { return _statusMessage; } - } - - /// - /// Indicates the job has or can have more data on one or more data collection - /// - public override bool HasMoreData - { - get - { - return (Output.IsOpen || Output.Count > 0 - || Error.IsOpen || Error.Count > 0 - || Verbose.IsOpen || Verbose.Count > 0 - || Debug.IsOpen || Debug.Count > 0 - || Warning.IsOpen || Warning.Count > 0 - || Progress.IsOpen || Progress.Count > 0 - || Information.IsOpen || Information.Count > 0 - ); - } - } - - /// - /// The location of the job - /// - public override string Location - { - get { return _location; } - } - - #endregion Public properties - - #region Private members - - private string _statusMessage; - private string _location; - private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); - private readonly object _syncObject = new object(); - private bool _disposed = false; - private const string ClassNameTrace = "PSChildJobProxy"; - - #endregion - } - - /// - /// Event arguments that indicate data has been added to a child job. - /// - public sealed class JobDataAddedEventArgs : EventArgs - { - /// - /// Constructor - /// - /// The job that contains the data that is added. - /// The type of data that this event is raised for. - /// - /// Index at which the data is added. - /// - internal JobDataAddedEventArgs(Job job, PowerShellStreamType dataType, int index) - { - SourceJob = job; - DataType = dataType; - Index = index; - } - - /// - /// The job that contains the PSDataCollection which is the sender. - /// - public Job SourceJob { get; } - - /// - /// Identifies the type of the sending collection as one of the six collections - /// associated with a job. - /// If data type = output, sender is PSDataCollection of PSObject, Error is of ErrorRecord, etc. - /// - public PowerShellStreamType DataType { get; } - - /// - /// Index at which the data is added. - /// - public int Index { get; } - } - - /// - /// Job data is added to one of these streams. Each - /// type of data implies a different type of object. - /// - public enum PowerShellStreamType - { - /// - /// PSObject - /// - Input = 0, - - /// - /// PSObject - /// - Output = 1, - - /// - /// ErrorRecord - /// - Error = 2, - - /// - /// WarningRecord - /// - Warning = 3, - - /// - /// VerboseRecord - /// - Verbose = 4, - - /// - /// DebugRecord - /// - Debug = 5, - - /// - /// ProgressRecord - /// - Progress = 6, - - /// - /// InformationRecord - /// - Information = 7 - } - - #endregion Workflow Hosting API -} diff --git a/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs b/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs index d0cd4dd1830..df859a7bc2b 100644 --- a/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs +++ b/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -152,6 +151,7 @@ private void Dispose(bool disposing) public PSDataCollection InputStream { get { return _inputStream; } + set { _inputStream = value; } } @@ -162,6 +162,7 @@ public PSDataCollection InputStream public PSDataCollection OutputStream { get { return _outputStream; } + set { _outputStream = value; } } @@ -172,6 +173,7 @@ public PSDataCollection OutputStream public PSDataCollection ErrorStream { get { return _errorStream; } + set { _errorStream = value; } } @@ -182,6 +184,7 @@ public PSDataCollection ErrorStream public PSDataCollection WarningStream { get { return _warningStream; } + set { _warningStream = value; } } @@ -192,6 +195,7 @@ public PSDataCollection WarningStream public PSDataCollection ProgressStream { get { return _progressStream; } + set { _progressStream = value; } } @@ -202,6 +206,7 @@ public PSDataCollection ProgressStream public PSDataCollection VerboseStream { get { return _verboseStream; } + set { _verboseStream = value; } } @@ -212,6 +217,7 @@ public PSDataCollection VerboseStream public PSDataCollection DebugStream { get { return _debugStream; } + set { _debugStream = value; } } @@ -222,6 +228,7 @@ public PSDataCollection DebugStream public PSDataCollection InformationStream { get { return _informationStream; } + set { _informationStream = value; } } @@ -231,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 705eaa49be4..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -1,17 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; +using System.Management.Automation.Host; +using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; +using System.Management.Automation.Remoting.Client; using System.Management.Automation.Tracing; using System.Threading; +using Microsoft.PowerShell.Telemetry; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Remoting; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; -using System.Collections.ObjectModel; -using System.Management.Automation.Remoting.Client; #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif @@ -20,9 +21,9 @@ namespace System.Management.Automation.Runspaces.Internal { /// /// Class which supports pooling remote powerShell runspaces - /// on the client + /// on the client. /// - internal class RemoteRunspacePoolInternal : RunspacePoolInternal, IDisposable + internal sealed class RemoteRunspacePoolInternal : RunspacePoolInternal { #region Constructor @@ -49,7 +50,7 @@ internal class RemoteRunspacePoolInternal : RunspacePoolInternal, IDisposable /// 1. TargetTypeForDeserialization /// 2. TypeConverter /// - /// Host associated with this runspacepool + /// Host associated with this runspacepool. /// /// Application arguments the server can see in /// @@ -80,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; @@ -110,14 +111,16 @@ 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) { throw PSTraceSource.NewArgumentNullException("ConnectCommandInfo[]"); } + if (connectionInfo == null) { throw PSTraceSource.NewArgumentNullException("WSManConnectionInfo"); @@ -125,7 +128,7 @@ internal RemoteRunspacePoolInternal(Guid instanceId, string name, bool isDisconn if (connectionInfo is WSManConnectionInfo) { - _connectionInfo = connectionInfo.InternalCopy(); + _connectionInfo = connectionInfo.Clone(); } else { @@ -169,32 +172,19 @@ private void CreateDSHandler(TypeTable typeTable) DataStructureHandler = new ClientRunspacePoolDataStructureHandler(this, typeTable); // register for events from the data structure handler - DataStructureHandler.RemoteHostCallReceived += - new EventHandler>(HandleRemoteHostCalls); - DataStructureHandler.StateInfoReceived += - new EventHandler>(HandleStateInfoReceived); - DataStructureHandler.RSPoolInitInfoReceived += - new EventHandler>(HandleInitInfoReceived); - DataStructureHandler.ApplicationPrivateDataReceived += - new EventHandler>(HandleApplicationPrivateDataReceived); - DataStructureHandler.SessionClosing += - new EventHandler>(HandleSessionClosing); - DataStructureHandler.SessionClosed += - new EventHandler>(HandleSessionClosed); - DataStructureHandler.SetMaxMinRunspacesResponseReceived += - new EventHandler>(HandleResponseReceived); - DataStructureHandler.URIRedirectionReported += - new EventHandler>(HandleURIDirectionReported); - DataStructureHandler.PSEventArgsReceived += - new EventHandler>(HandlePSEventArgsReceived); - DataStructureHandler.SessionDisconnected += - new EventHandler>(HandleSessionDisconnected); - DataStructureHandler.SessionReconnected += - new EventHandler>(HandleSessionReconnected); - DataStructureHandler.SessionRCDisconnecting += - new EventHandler>(HandleSessionRCDisconnecting); - DataStructureHandler.SessionCreateCompleted += - new EventHandler(HandleSessionCreateCompleted); + DataStructureHandler.RemoteHostCallReceived += HandleRemoteHostCalls; + DataStructureHandler.StateInfoReceived += HandleStateInfoReceived; + DataStructureHandler.RSPoolInitInfoReceived += HandleInitInfoReceived; + DataStructureHandler.ApplicationPrivateDataReceived += HandleApplicationPrivateDataReceived; + DataStructureHandler.SessionClosing += HandleSessionClosing; + DataStructureHandler.SessionClosed += HandleSessionClosed; + DataStructureHandler.SetMaxMinRunspacesResponseReceived += HandleResponseReceived; + DataStructureHandler.URIRedirectionReported += HandleURIDirectionReported; + DataStructureHandler.PSEventArgsReceived += HandlePSEventArgsReceived; + DataStructureHandler.SessionDisconnected += HandleSessionDisconnected; + DataStructureHandler.SessionReconnected += HandleSessionReconnected; + DataStructureHandler.SessionRCDisconnecting += HandleSessionRCDisconnecting; + DataStructureHandler.SessionCreateCompleted += HandleSessionCreateCompleted; } #endregion Constructors @@ -202,7 +192,7 @@ private void CreateDSHandler(TypeTable typeTable) #region Properties /// - /// the connection associated with this runspace pool + /// The connection associated with this runspace pool. /// public override RunspaceConnectionInfo ConnectionInfo { @@ -214,7 +204,7 @@ public override RunspaceConnectionInfo ConnectionInfo /// /// The ClientRunspacePoolDataStructureHandler associated with this - /// runspace pool + /// runspace pool. /// internal ClientRunspacePoolDataStructureHandler DataStructureHandler { get; private set; } @@ -230,6 +220,7 @@ public override RunspaceConnectionInfo ConnectionInfo internal string Name { get { return _friendlyName; } + set { _friendlyName = value ?? string.Empty; } } @@ -280,8 +271,8 @@ public override RunspacePoolAvailability RunspacePoolAvailability /// internal bool IsRemoteDebugStop { - set; get; + set; } #endregion @@ -293,14 +284,14 @@ internal bool IsRemoteDebugStop /// runspace. /// This is currently supported *only* for remote runspaces. /// - /// True if successful + /// True if successful. internal override bool ResetRunspaceState() { // Version check. Reset Runspace is supported only on PSRP protocol // version 2.3 or greater. Version remoteProtocolVersionDeclaredByServer = PSRemotingProtocolVersion; if ((remoteProtocolVersionDeclaredByServer == null) || - (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersionWin10RTM)) + (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersion_2_3)) { throw PSTraceSource.NewInvalidOperationException(RunspacePoolStrings.ResetRunspaceStateNotSupportedOnServer); } @@ -356,14 +347,14 @@ 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 callId = DispatchTable.CreateNewCallId(); DataStructureHandler.SendSetMaxRunspacesToServer(maxRunspaces, callId); - } // lock ... + } // this call blocks until the response is received object response = DispatchTable.GetResponse(callId, false); @@ -419,14 +410,14 @@ 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 callId = DispatchTable.CreateNewCallId(); DataStructureHandler.SendSetMinRunspacesToServer(minRunspaces, callId); - } // lock ... + } // this call blocks until the response is received object response = DispatchTable.GetResponse(callId, false); @@ -446,9 +437,9 @@ internal override bool SetMinRunspaces(int minRunspaces) /// /// Retrieves the number of runspaces available at the time of calling - /// this method from the remote server + /// this method from the remote server. /// - /// The number of runspaces available in the pool + /// The number of runspaces available in the pool. internal override int GetAvailableRunspaces() { int availableRunspaces = 0; @@ -461,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 @@ -477,7 +468,7 @@ internal override int GetAvailableRunspaces() } DataStructureHandler.SendGetAvailableRunspacesToServer(callId); - } // lock ... + } // this call blocks until the response is received object response = DispatchTable.GetResponse(callId, 0); @@ -490,8 +481,8 @@ internal override int GetAvailableRunspaces() /// The server sent application private data. Store the data so that user /// can get it later. /// - /// argument describing this event - /// sender of this event + /// Argument describing this event. + /// Sender of this event. internal void HandleApplicationPrivateDataReceived(object sender, RemoteDataEventArgs eventArgs) { @@ -529,10 +520,10 @@ internal void HandleInitInfoReceived(object sender, /// /// The state of the server RunspacePool has changed. Handle - /// the same and reflect local states accordingly + /// the same and reflect local states accordingly. /// - /// argument describing this event - /// sender of this event + /// Argument describing this event. + /// Sender of this event. internal void HandleStateInfoReceived(object sender, RemoteDataEventArgs eventArgs) { @@ -586,7 +577,7 @@ internal void HandleStateInfoReceived(object sender, // if closeAsyncResult is null, BeginClose is not called. That means // we are getting close event from server, in this case release the // local resources - if (null == _closeAsyncResult) + if (_closeAsyncResult == null) { // Close the local resources. DataStructureHandler.CloseRunspacePoolAsync(); @@ -595,15 +586,15 @@ internal void HandleStateInfoReceived(object sender, // Delay notifying upper layers of finished state change event // until after transport close ack is received (HandleSessionClosed handler). } - } // else if ... + } } /// /// A host call has been proxied from the server which needs to - /// be executed + /// be executed. /// - /// sender of this event - /// arguments describing this event + /// Sender of this event. + /// Arguments describing this event. internal void HandleRemoteHostCalls(object sender, RemoteDataEventArgs eventArgs) { @@ -636,7 +627,7 @@ internal PSHost Host } /// - /// Application arguments to use when opening a remote session + /// Application arguments to use when opening a remote session. /// internal PSPrimitiveDictionary ApplicationArguments { get; } @@ -688,16 +679,16 @@ 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 - /// which this runspace pool wraps + /// which this runspace pool wraps. /// internal event EventHandler> HostCallReceived; /// - /// EventHandler used to report connection URI redirections to the application + /// EventHandler used to report connection URI redirections to the application. /// internal event EventHandler> URIRedirectionReported; @@ -707,7 +698,6 @@ internal override void PropagateApplicationPrivateData(Runspace runspace) internal event EventHandler SessionCreateCompleted; /// - /// /// /// internal void CreatePowerShellOnServerAndInvoke(ClientRemotePowerShell shell) @@ -743,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); } @@ -778,7 +768,7 @@ internal Version PSRemotingProtocolVersion /// /// Push a running PowerShell onto the stack. /// - /// PowerShell + /// PowerShell. internal void PushRunningPowerShell(PowerShell ps) { Dbg.Assert(ps != null, "Caller should not pass in null reference."); @@ -788,7 +778,7 @@ internal void PushRunningPowerShell(PowerShell ps) /// /// Pop the currently running PowerShell from stack. /// - /// PowerShell + /// PowerShell. internal PowerShell PopRunningPowerShell() { PowerShell powershell; @@ -803,7 +793,7 @@ internal PowerShell PopRunningPowerShell() /// /// Return the current running PowerShell. /// - /// PowerShell + /// PowerShell. internal PowerShell GetCurrentRunningPowerShell() { PowerShell powershell; @@ -852,6 +842,8 @@ protected override IAsyncResult CoreOpen(bool isAsync, AsyncCallback callback, PSEtwLog.LogOperationalVerbose(PSEventId.RunspacePoolOpen, PSOpcode.Open, PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational); + // Telemetry here - remote session + ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.RemoteSessionOpen, isAsync.ToString()); #if LEGACYTELEMETRY TelemetryAPI.ReportRemoteSessionCreated(_connectionInfo); #endif @@ -873,7 +865,6 @@ protected override IAsyncResult CoreOpen(bool isAsync, AsyncCallback callback, _openAsyncResult = asyncResult; - // send a message using the data structure handler to open the RunspacePool // on the remote server DataStructureHandler.CreateRunspacePoolAndOpenAsync(); @@ -886,7 +877,7 @@ protected override IAsyncResult CoreOpen(bool isAsync, AsyncCallback callback, #region Public Methods /// - /// Synchronous open + /// Synchronous open. /// public override void Open() { @@ -915,7 +906,7 @@ public override void Close() /// /// Closes the RunspacePool asynchronously. To get the exceptions - /// that might have occurred, call EndOpen + /// that might have occurred, call EndOpen. /// /// /// An AsyncCallback to call once the BeginClose completes @@ -982,7 +973,7 @@ public override IAsyncResult BeginClose(AsyncCallback callback, object asyncStat if (!skipClosing) { - //SetRunspacePoolState(new RunspacePoolStateInfo(RunspacePoolState.Closing, null), true); + // SetRunspacePoolState(new RunspacePoolStateInfo(RunspacePoolState.Closing, null), true); // send a message using the data structure handler to close the RunspacePool // on the remote server @@ -1011,8 +1002,8 @@ public override void Disconnect() /// Asynchronous disconnect. /// /// AsyncCallback object. - /// state object. - /// IAsyncResult + /// State object. + /// IAsyncResult. public override IAsyncResult BeginDisconnect(AsyncCallback callback, object state) { if (!CanDisconnect) @@ -1068,18 +1059,18 @@ public override IAsyncResult BeginDisconnect(AsyncCallback callback, object stat /// IAsyncResult object. public override void EndDisconnect(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } RunspacePoolAsyncResult rsAsyncResult = asyncResult as RunspacePoolAsyncResult; - if ((null == rsAsyncResult) || + if ((rsAsyncResult == null) || (rsAsyncResult.OwnerId != instanceId) || (rsAsyncResult.IsAssociatedWithAsyncOpen)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginOpen"); @@ -1102,8 +1093,8 @@ public override void Connect() /// Asynchronous connect. /// /// ASyncCallback object. - /// state Object. - /// IAsyncResult + /// State Object. + /// IAsyncResult. public override IAsyncResult BeginConnect(AsyncCallback callback, object state) { if (!AvailableForConnection) @@ -1178,18 +1169,18 @@ public override IAsyncResult BeginConnect(AsyncCallback callback, object state) /// IAsyncResult object. public override void EndConnect(IAsyncResult asyncResult) { - if (null == asyncResult) + if (asyncResult == null) { - throw PSTraceSource.NewArgumentNullException("asyncResult"); + throw PSTraceSource.NewArgumentNullException(nameof(asyncResult)); } RunspacePoolAsyncResult rsAsyncResult = asyncResult as RunspacePoolAsyncResult; - if ((null == rsAsyncResult) || + if ((rsAsyncResult == null) || (rsAsyncResult.OwnerId != instanceId) || (rsAsyncResult.IsAssociatedWithAsyncOpen)) { - throw PSTraceSource.NewArgumentException("asyncResult", + throw PSTraceSource.NewArgumentException(nameof(asyncResult), RunspacePoolStrings.AsyncResultNotOwned, "IAsyncResult", "BeginOpen"); @@ -1224,10 +1215,10 @@ public override Collection CreateDisconnectedPowerShells(RunspacePoo return psCollection; } - /// + /// /// Returns RunspacePool capabilities. /// - /// RunspacePoolCapability + /// RunspacePoolCapability. public override RunspacePoolCapability GetCapabilities() { RunspacePoolCapability returnCaps = RunspacePoolCapability.Default; @@ -1246,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(); } @@ -1279,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; } @@ -1318,6 +1307,7 @@ internal static RunspacePool[] GetRemoteRunspacePools(RunspaceConnectionInfo con throw; } + foreach (PSObject cmdObject in commandItems) { PSPropertyInfo pspCommandId = cmdObject.Properties["CommandId"]; @@ -1379,6 +1369,7 @@ private static void UpdateWSManConnectionInfo( wsmanConnectionInfo.IdleTimeout = idleTimeout; } } + if (pspBufferMode != null) { string bufferingMode = pspBufferMode.Value as string; @@ -1392,6 +1383,7 @@ private static void UpdateWSManConnectionInfo( } } } + if (pspResourceUri != null) { string strShellUri = pspResourceUri.Value as string; @@ -1400,6 +1392,7 @@ private static void UpdateWSManConnectionInfo( wsmanConnectionInfo.ShellUri = strShellUri; } } + if (pspLocale != null) { string localString = pspLocale.Value as string; @@ -1413,6 +1406,7 @@ private static void UpdateWSManConnectionInfo( { } } } + if (pspDataLocale != null) { string dataLocalString = pspDataLocale.Value as string; @@ -1426,33 +1420,34 @@ private static void UpdateWSManConnectionInfo( { } } } + if (pspCompressionMode != null) { 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); } } + if (pspEncoding != null) { 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); } } + if (pspProfile != null) { 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); } } + if (pspMaxIdleTimeout != null) { int maxIdleTimeout; @@ -1509,7 +1504,7 @@ private static bool GetTimeIntValue(string timeString, out int value) { if (timeString != null) { - string timeoutString = timeString.Replace("PT", "").Replace("S", ""); + string timeoutString = timeString.Replace("PT", string.Empty).Replace("S", string.Empty); try { // Convert time from seconds to milliseconds. @@ -1536,7 +1531,7 @@ private static bool GetTimeIntValue(string timeString, out int value) /// /// Set the new runspace pool state based on the state of the - /// server RunspacePool + /// server RunspacePool. /// /// state information object /// describing the state change at the server RunspacePool @@ -1547,11 +1542,11 @@ private void SetRunspacePoolState(RunspacePoolStateInfo newStateInfo) /// /// Set the new runspace pool state based on the state of the - /// server RunspacePool and raise events if required + /// server RunspacePool and raise events if required. /// /// state information object /// describing the state change at the server RunspacePool - /// raise state changed events if true + /// Raise state changed events if true. private void SetRunspacePoolState(RunspacePoolStateInfo newStateInfo, bool raiseEvents) { stateInfo = newStateInfo; @@ -1634,10 +1629,10 @@ private void SetReconnectAsCompleted() } /// - /// The session is closing set the state and reason accordingly + /// The session is closing set the state and reason accordingly. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleSessionClosing(object sender, RemoteDataEventArgs eventArgs) { // just capture the reason for closing here..handle the session closed event @@ -1646,10 +1641,10 @@ private void HandleSessionClosing(object sender, RemoteDataEventArgs } /// - /// The session closed, set the state and reason accordingly + /// The session closed, set the state and reason accordingly. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleSessionClosed(object sender, RemoteDataEventArgs eventArgs) { if (eventArgs.Data != null) @@ -1705,7 +1700,7 @@ private void HandleSessionClosed(object sender, RemoteDataEventArgs e } /// - /// Set the async result for open as completed + /// Set the async result for open as completed. /// private void SetOpenAsCompleted() { @@ -1718,7 +1713,7 @@ private void SetOpenAsCompleted() } /// - /// Set the async result for close as completed + /// Set the async result for close as completed. /// private void SetCloseAsCompleted() { @@ -1748,10 +1743,10 @@ private void SetCloseAsCompleted() /// /// When a response to a SetMaxRunspaces or SetMinRunspaces is received, /// from the server, this method sets the response and thereby unblocks - /// corresponding call + /// corresponding call. /// - /// sender of this message, unused - /// contains response and call id + /// Sender of this message, unused. + /// Contains response and call id. private void HandleResponseReceived(object sender, RemoteDataEventArgs eventArgs) { PSObject data = eventArgs.Data; @@ -1769,7 +1764,7 @@ private void HandleResponseReceived(object sender, RemoteDataEventArgs private void HandleURIDirectionReported(object sender, RemoteDataEventArgs eventArgs) { WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo; - if (null != wsmanConnectionInfo) + if (wsmanConnectionInfo != null) { wsmanConnectionInfo.ConnectionUri = eventArgs.Data; URIRedirectionReported.SafeInvoke(this, eventArgs); @@ -1777,7 +1772,7 @@ private void HandleURIDirectionReported(object sender, RemoteDataEventArgs } /// - /// When the server sends a PSEventArgs this method will add it to the local event queue + /// When the server sends a PSEventArgs this method will add it to the local event queue. /// private void HandlePSEventArgsReceived(object sender, RemoteDataEventArgs e) { @@ -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 @@ -1893,32 +1882,22 @@ private void WaitAndRaiseConnectEventsProc(object state) private RunspacePoolAsyncResult _reconnectAsyncResult; // async result object generated on CoreReconnect private bool _isDisposed; - private DispatchTable DispatchTable { get; } + private DispatchTable DispatchTable { get; } 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 + /// Release all resources. /// - /// if true, release all managed resources - public override void Dispose(bool disposing) + /// If true, release all managed resources. + protected override void Dispose(bool disposing) { // dispose the base class before disposing dataStructure handler. base.Dispose(disposing); @@ -1948,7 +1927,7 @@ internal class ConnectCommandInfo /// /// Remote command string. /// - public string Command { get; } = String.Empty; + public string Command { get; } = string.Empty; /// /// Constructs a remote command object. @@ -1997,24 +1976,29 @@ internal static Collection GetRemotePools(WSManConnectionInfo wsmanCon { powerShell.AddParameter("Credential", wsmanConnectionInfo.Credential); } + if (wsmanConnectionInfo.CertificateThumbprint != null) { powerShell.AddParameter("CertificateThumbprint", wsmanConnectionInfo.CertificateThumbprint); } + if (wsmanConnectionInfo.PortSetting != -1) { powerShell.AddParameter("Port", wsmanConnectionInfo.Port); } + if (CheckForSSL(wsmanConnectionInfo)) { powerShell.AddParameter("UseSSL", true); } + if (!string.IsNullOrEmpty(wsmanConnectionInfo.AppName)) { // Remove prepended path character. string appName = wsmanConnectionInfo.AppName.TrimStart('/'); powerShell.AddParameter("ApplicationName", appName); } + powerShell.AddParameter("SessionOption", GetSessionOptions(wsmanConnectionInfo)); result = powerShell.Invoke(); @@ -2039,7 +2023,7 @@ internal static Collection GetRemoteCommands(Guid shellId, WSManConnec powerShell.AddCommand("Get-WSManInstance"); // Add parameters to enumerate commands. - string filterStr = string.Format(CultureInfo.InvariantCulture, "ShellId='{0}'", shellId.ToString().ToUpperInvariant()); + string filterStr = string.Create(CultureInfo.InvariantCulture, $"ShellId='{shellId.ToString().ToUpperInvariant()}'"); powerShell.AddParameter("ResourceURI", @"Shell/Command"); powerShell.AddParameter("Enumerate", true); powerShell.AddParameter("Dialect", "Selector"); @@ -2052,24 +2036,29 @@ internal static Collection GetRemoteCommands(Guid shellId, WSManConnec { powerShell.AddParameter("Credential", wsmanConnectionInfo.Credential); } + if (wsmanConnectionInfo.CertificateThumbprint != null) { powerShell.AddParameter("CertificateThumbprint", wsmanConnectionInfo.CertificateThumbprint); } + if (wsmanConnectionInfo.PortSetting != -1) { powerShell.AddParameter("Port", wsmanConnectionInfo.Port); } + if (CheckForSSL(wsmanConnectionInfo)) { powerShell.AddParameter("UseSSL", true); } + if (!string.IsNullOrEmpty(wsmanConnectionInfo.AppName)) { // Remove prepended path character. string appName = wsmanConnectionInfo.AppName.TrimStart('/'); powerShell.AddParameter("ApplicationName", appName); } + powerShell.AddParameter("SessionOption", GetSessionOptions(wsmanConnectionInfo)); result = powerShell.Invoke(); @@ -2082,8 +2071,8 @@ internal static Collection GetRemoteCommands(Guid shellId, WSManConnec /// Use the WSMan New-WSManSessionOption cmdlet to create a session options /// object used for Get-WSManInstance queries. /// - /// WSManConnectionInfo - /// WSMan session options object + /// WSManConnectionInfo. + /// WSMan session options object. private static object GetSessionOptions(WSManConnectionInfo wsmanConnectionInfo) { Collection result; @@ -2128,7 +2117,7 @@ private static object GetSessionOptions(WSManConnectionInfo wsmanConnectionInfo) private static bool CheckForSSL(WSManConnectionInfo wsmanConnectionInfo) { return (!string.IsNullOrEmpty(wsmanConnectionInfo.Scheme) && - wsmanConnectionInfo.Scheme.IndexOf(WSManConnectionInfo.HttpsScheme, StringComparison.OrdinalIgnoreCase) != -1); + wsmanConnectionInfo.Scheme.Contains(WSManConnectionInfo.HttpsScheme, StringComparison.OrdinalIgnoreCase)); } private static int ConvertPSAuthToWSManAuth(AuthenticationMechanism psAuth) diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs index ebc5040c507..527bdcf9e21 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs @@ -1,22 +1,20 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; -using System.Management.Automation.Remoting; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Remoting; +using System.Runtime.Serialization; using System.Security.Permissions; namespace System.Management.Automation.Runspaces { /// - /// Error record in remoting cases + /// Error record in remoting cases. /// - [Serializable] public class RemotingErrorRecord : ErrorRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { @@ -25,25 +23,30 @@ public OriginInfo OriginInfo return _originInfo; } } - private OriginInfo _originInfo; + + private readonly OriginInfo _originInfo; /// - /// constructor + /// Constructor. /// - /// the error record that is wrapped - /// origin information - public RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo) : this(errorRecord, originInfo, null) { } + /// The error record that is wrapped. + /// Origin information. + public RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo) + : this(errorRecord, originInfo, null) { } /// - /// constructor that is used to wrap an error record + /// Constructor that is used to wrap an error record. /// /// /// /// - private RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo, Exception replaceParentContainsErrorRecordException) : - base(errorRecord, replaceParentContainsErrorRecordException) + private RemotingErrorRecord( + ErrorRecord errorRecord, + OriginInfo originInfo, + Exception replaceParentContainsErrorRecordException) + : base(errorRecord, replaceParentContainsErrorRecordException) { - if (null != errorRecord) + if (errorRecord != null) { base.SetInvocationInfo(errorRecord.InvocationInfo); } @@ -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("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) + /// Serializer information. + /// Streaming 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 @@ -87,7 +72,7 @@ protected RemotingErrorRecord(SerializationInfo info, StreamingContext context) #region Override /// - /// Wrap the current ErrorRecord instance + /// Wrap the current ErrorRecord instance. /// /// /// If the wrapped exception contains a ParentContainsErrorRecordException, the new @@ -103,28 +88,32 @@ internal override ErrorRecord WrapException(Exception replaceParentContainsError } /// - /// Progress record containing origin information + /// Progress record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingProgressRecord : ProgressRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { get { return _originInfo; } } - [DataMemberAttribute()] + + [DataMember] private readonly OriginInfo _originInfo; /// - /// Constructor + /// Constructor. /// - /// the progress record that is wrapped - /// origin information - public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originInfo) : - base(Validate(progressRecord).ActivityId, Validate(progressRecord).Activity, Validate(progressRecord).StatusDescription) + /// The progress record that is wrapped. + /// Origin information. + public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originInfo) + : base( + Validate(progressRecord).ActivityId, + Validate(progressRecord).Activity, + Validate(progressRecord).StatusDescription) { _originInfo = originInfo; if (progressRecord != null) @@ -142,33 +131,35 @@ public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originIn private static ProgressRecord Validate(ProgressRecord progressRecord) { - if (progressRecord == null) throw new ArgumentNullException("progressRecord"); + ArgumentNullException.ThrowIfNull(progressRecord); return progressRecord; } } /// - /// Warning record containing origin information + /// Warning record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingWarningRecord : WarningRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { get { return _originInfo; } } - [DataMemberAttribute()] + + [DataMember] private readonly OriginInfo _originInfo; /// - /// Constructor + /// Constructor. /// - /// The warning message that is wrapped - /// The origin information - public RemotingWarningRecord(string message, OriginInfo originInfo) : base(message) + /// The warning message that is wrapped. + /// The origin information. + public RemotingWarningRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } @@ -176,8 +167,8 @@ public RemotingWarningRecord(string message, OriginInfo originInfo) : base(messa /// /// Constructor taking WarningRecord to wrap and OriginInfo. /// - /// WarningRecord to wrap - /// OriginInfo + /// WarningRecord to wrap. + /// OriginInfo. internal RemotingWarningRecord( WarningRecord warningRecord, OriginInfo originInfo) @@ -188,80 +179,85 @@ internal RemotingWarningRecord( } /// - /// Debug record containing origin information + /// Debug record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingDebugRecord : DebugRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { get { return _originInfo; } } - [DataMemberAttribute()] + + [DataMember] private readonly OriginInfo _originInfo; /// - /// Constructor + /// Constructor. /// - /// The debug message that is wrapped - /// The origin information - public RemotingDebugRecord(string message, OriginInfo originInfo) : base(message) + /// The debug message that is wrapped. + /// The origin information. + public RemotingDebugRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } } /// - /// Verbose record containing origin information + /// Verbose record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingVerboseRecord : VerboseRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { get { return _originInfo; } } - [DataMemberAttribute()] + + [DataMember] private readonly OriginInfo _originInfo; /// - /// Constructor + /// Constructor. /// - /// The verbose message that is wrapped - /// The origin information - public RemotingVerboseRecord(string message, OriginInfo originInfo) : base(message) + /// The verbose message that is wrapped. + /// The origin information. + public RemotingVerboseRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } } /// - /// Information record containing origin information + /// Information record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingInformationRecord : InformationRecord { /// - /// Contains the origin information + /// Contains the origin information. /// public OriginInfo OriginInfo { get { return _originInfo; } } - [DataMemberAttribute()] + + [DataMember] private readonly OriginInfo _originInfo; /// - /// Constructor + /// Constructor. /// - /// The Information message that is wrapped - /// The origin information + /// The Information message that is wrapped. + /// The origin information. public RemotingInformationRecord(InformationRecord record, OriginInfo originInfo) : base(record) { @@ -273,34 +269,34 @@ public RemotingInformationRecord(InformationRecord record, OriginInfo originInfo namespace System.Management.Automation.Remoting { /// - /// Contains OriginInfo for an error record + /// Contains OriginInfo for an error record. /// /// This class should only be used when /// defining origin information for error records. /// In case of output objects, the information /// should directly be added to the object as /// properties - [Serializable] - [DataContract()] + [DataContract] public class OriginInfo { /// /// The HostEntry information for the machine on - /// which this information originated + /// which this information originated. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "PSIP")] - public String PSComputerName + public string PSComputerName { get { return _computerName; } } - [DataMemberAttribute()] - private String _computerName; + + [DataMember] + private readonly string _computerName; /// - /// Runspace instance ID + /// Runspace instance ID. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] public Guid RunspaceID @@ -310,11 +306,12 @@ public Guid RunspaceID return _runspaceID; } } - [DataMemberAttribute()] - private Guid _runspaceID; + + [DataMember] + private readonly Guid _runspaceID; /// - /// Error record source instance ID + /// Error record source instance ID. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] public Guid InstanceID @@ -329,27 +326,28 @@ public Guid InstanceID _instanceId = value; } } - [DataMemberAttribute()] + + [DataMember] private Guid _instanceId; /// - /// public constructor + /// Public constructor. /// - /// machine name - /// instance id of runspace + /// Machine name. + /// Instance id of runspace. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] - public OriginInfo(String computerName, Guid runspaceID) + public OriginInfo(string computerName, Guid runspaceID) : this(computerName, runspaceID, Guid.Empty) { } /// - /// public constructor + /// Public constructor. /// - /// machine name - /// instance id of runspace - /// instance id for the origin object + /// Machine name. + /// Instance id of runspace. + /// Instance id for the origin object. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")] - public OriginInfo(String computerName, Guid runspaceID, Guid instanceID) + public OriginInfo(string computerName, Guid runspaceID, Guid instanceID) { _computerName = computerName; _runspaceID = runspaceID; @@ -357,13 +355,12 @@ public OriginInfo(String computerName, Guid runspaceID, Guid instanceID) } /// - /// Overridden ToString() method + /// Overridden ToString() method. /// - /// returns the computername + /// Returns the computername. public override string ToString() { return PSComputerName; } } } - diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index d2199a65938..441a3f62fed 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs @@ -1,16 +1,15 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Tracing; -using System.Collections.ObjectModel; using System.Collections.Generic; -using System.Threading; +using System.Collections.ObjectModel; using System.Management.Automation.Host; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Runspaces.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Client; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Runspaces.Internal; +using System.Management.Automation.Tracing; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -18,9 +17,9 @@ namespace System.Management.Automation.Internal { /// /// Handles all PowerShell data structure handler communication with the - /// server side RunspacePool + /// server side RunspacePool. /// - internal class ClientRunspacePoolDataStructureHandler : IDisposable + internal sealed class ClientRunspacePoolDataStructureHandler : IDisposable { private bool _reconnecting = false; @@ -28,10 +27,10 @@ internal class ClientRunspacePoolDataStructureHandler : IDisposable /// /// Constructor which takes a client runspace pool and creates - /// an associated ClientRunspacePoolDataStructureHandler + /// an associated ClientRunspacePoolDataStructureHandler. /// - /// client runspace pool object. - /// typetable to use for serialization/deserialization. + /// Client runspace pool object. + /// Typetable to use for serialization/deserialization. internal ClientRunspacePoolDataStructureHandler(RemoteRunspacePoolInternal clientRunspacePool, TypeTable typeTable) { @@ -41,19 +40,14 @@ internal ClientRunspacePoolDataStructureHandler(RemoteRunspacePoolInternal clien _host = clientRunspacePool.Host; _applicationArguments = clientRunspacePool.ApplicationArguments; RemoteSession = CreateClientRemoteSession(clientRunspacePool); - //TODO: Assign remote session name.. should be passed from clientRunspacePool + // TODO: Assign remote session name.. should be passed from clientRunspacePool _transportManager = RemoteSession.SessionDataStructureHandler.TransportManager; _transportManager.TypeTable = typeTable; - RemoteSession.StateChanged += - new EventHandler( - HandleClientRemoteSessionStateChanged); + RemoteSession.StateChanged += HandleClientRemoteSessionStateChanged; _reconnecting = false; - _transportManager.RobustConnectionNotification += - new EventHandler(HandleRobustConnectionNotification); - - _transportManager.CreateCompleted += - new EventHandler(HandleSessionCreateCompleted); + _transportManager.RobustConnectionNotification += HandleRobustConnectionNotification; + _transportManager.CreateCompleted += HandleSessionCreateCompleted; } #endregion Constructors @@ -62,7 +56,7 @@ internal ClientRunspacePoolDataStructureHandler(RemoteRunspacePoolInternal clien /// /// Create a runspace pool asynchronously (and opens) it - /// on the server + /// on the server. /// internal void CreateRunspacePoolAndOpenAsync() { @@ -76,7 +70,7 @@ internal void CreateRunspacePoolAndOpenAsync() } /// - /// Closes the server runspace pool asynchronously + /// Closes the server runspace pool asynchronously. /// internal void CloseRunspacePoolAsync() { @@ -84,7 +78,7 @@ internal void CloseRunspacePoolAsync() } /// - /// Suspends connection to a runspace pool asynchronously + /// Suspends connection to a runspace pool asynchronously. /// internal void DisconnectPoolAsync() { @@ -94,18 +88,18 @@ internal void DisconnectPoolAsync() } /// - /// Restore connection to a runspace pool asynchronously + /// Restore connection to a runspace pool asynchronously. /// internal void ReconnectPoolAsync() { - //TODO: Integrate this into state machine + // TODO: Integrate this into state machine _reconnecting = true; PrepareForConnect(); RemoteSession.ReconnectAsync(); } /// - /// Creates a connection to an existing remote runspace pool + /// Creates a connection to an existing remote runspace pool. /// internal void ConnectPoolAsync() { @@ -115,9 +109,9 @@ internal void ConnectPoolAsync() /// /// Process the data received from the runspace pool - /// on the server + /// on the server. /// - /// data received + /// Data received. internal void ProcessReceivedData(RemoteDataObject receivedData) { // verify if this data structure handler is the intended recipient @@ -141,6 +135,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) RemoteHostCall remoteHostCall = RemoteHostCall.Decode(receivedData.Data); RemoteHostCallReceived.SafeInvoke(this, new RemoteDataEventArgs(remoteHostCall)); } + break; case RemotingDataType.RunspacePoolInitData: @@ -152,6 +147,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) RSPoolInitInfoReceived.SafeInvoke(this, new RemoteDataEventArgs(initInfo)); } + break; case RemotingDataType.RunspacePoolStateInfo: @@ -166,6 +162,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) NotifyAssociatedPowerShells(stateInfo); } + break; case RemotingDataType.ApplicationPrivateData: @@ -176,6 +173,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) ApplicationPrivateDataReceived.SafeInvoke(this, new RemoteDataEventArgs(applicationPrivateData)); } + break; case RemotingDataType.RunspacePoolOperationResponse: @@ -185,6 +183,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) SetMaxMinRunspacesResponseReceived.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; case RemotingDataType.PSEventArgs: @@ -196,22 +195,22 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) PSEventArgsReceived.SafeInvoke(this, new RemoteDataEventArgs(psEventArgs)); } + break; - } // switch ... - } // ProcessReceivedData + } + } /// /// Creates a PowerShell data structure handler instance associated - /// with this runspace pool data structure handler + /// with this runspace pool data structure handler. /// - /// associated powershell - /// PowerShell data structure handler object + /// Associated powershell. + /// PowerShell data structure handler object. internal ClientPowerShellDataStructureHandler CreatePowerShellDataStructureHandler( ClientRemotePowerShell shell) { BaseClientCommandTransportManager clientTransportMgr = - RemoteSession.SessionDataStructureHandler. - CreateClientCommandTransportManager(shell, shell.NoInput); + RemoteSession.SessionDataStructureHandler.CreateClientCommandTransportManager(shell, shell.NoInput); return new ClientPowerShellDataStructureHandler( clientTransportMgr, _clientRunspacePoolId, shell.InstanceId); @@ -219,9 +218,9 @@ internal ClientPowerShellDataStructureHandler CreatePowerShellDataStructureHandl /// /// Creates a PowerShell instances on the server, associates it - /// with this runspace pool and invokes + /// with this runspace pool and invokes. /// - /// the client remote powershell + /// The client remote powershell. internal void CreatePowerShellOnServerAndInvoke(ClientRemotePowerShell shell) { // add to associated powershell list and send request to server @@ -229,12 +228,12 @@ internal void CreatePowerShellOnServerAndInvoke(ClientRemotePowerShell shell) { _associatedPowerShellDSHandlers.Add(shell.InstanceId, shell.DataStructureHandler); } - shell.DataStructureHandler.RemoveAssociation += - new EventHandler(HandleRemoveAssociation); + + shell.DataStructureHandler.RemoveAssociation += HandleRemoveAssociation; // 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); @@ -261,13 +260,13 @@ internal void AddRemotePowerShellDSHandler(Guid psShellInstanceId, ClientPowerSh _associatedPowerShellDSHandlers[psShellInstanceId] = psDSHandler; } - psDSHandler.RemoveAssociation += new EventHandler(HandleRemoveAssociation); + psDSHandler.RemoveAssociation += HandleRemoveAssociation; } /// - /// dispatch the message to the associated powershell data structure handler + /// Dispatch the message to the associated powershell data structure handler. /// - /// message received. + /// Message received. internal void DispatchMessageToPowerShell(RemoteDataObject rcvdData) { ClientPowerShellDataStructureHandler dsHandler = @@ -276,16 +275,13 @@ 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); } /// - /// send the host response to the server + /// Send the host response to the server. /// - /// host response object to send + /// Host response object to send. internal void SendHostResponseToServer(RemoteHostResponse hostResponse) { SendDataAsync(hostResponse.Encode(), DataPriorityType.PromptResponse); @@ -294,7 +290,7 @@ internal void SendHostResponseToServer(RemoteHostResponse hostResponse) /// /// Send a message to the server instructing it to reset its runspace state. /// - /// Caller Id + /// Caller Id. internal void SendResetRunspaceStateToServer(long callId) { RemoteDataObject message = @@ -304,9 +300,9 @@ internal void SendResetRunspaceStateToServer(long callId) } /// - /// sent a message to modify the max runspaces of the runspace pool + /// Sent a message to modify the max runspaces of the runspace pool. /// - /// new maxrunspaces to set + /// New maxrunspaces to set. /// call id on which the calling method will /// be blocked on internal void SendSetMaxRunspacesToServer(int maxRunspaces, long callId) @@ -318,9 +314,9 @@ internal void SendSetMaxRunspacesToServer(int maxRunspaces, long callId) } /// - /// Send a message to modify the min runspaces of the runspace pool + /// Send a message to modify the min runspaces of the runspace pool. /// - /// new minrunspaces to set + /// New minrunspaces to set. /// call id on which the calling method will /// be blocked on internal void SendSetMinRunspacesToServer(int minRunspaces, long callId) @@ -332,7 +328,7 @@ internal void SendSetMinRunspacesToServer(int minRunspaces, long callId) } /// - /// Send a message to get the available runspaces from the server + /// Send a message to get the available runspaces from the server. /// /// call id on which the calling method will /// be blocked on @@ -346,12 +342,12 @@ internal void SendGetAvailableRunspacesToServer(long callId) #region Data Structure Handler events /// - /// Event raised when a host call is received + /// Event raised when a host call is received. /// internal event EventHandler> RemoteHostCallReceived; /// - /// Event raised when state information is received + /// Event raised when state information is received. /// internal event EventHandler> StateInfoReceived; @@ -359,17 +355,17 @@ internal void SendGetAvailableRunspacesToServer(long callId) /// Event raised when RunspacePoolInitInfo is received. This is the first runspace pool message expected /// after connecting to an existing remote runspace pool. RemoteRunspacePoolInternal should use this /// notification to set the state of a reconstructed runspace to "Opened State" and use the - /// minRunspace and MaxRunspaces information to set its state + /// minRunspace and MaxRunspaces information to set its state. /// internal event EventHandler> RSPoolInitInfoReceived; /// - /// Event raised when application private data is received + /// Event raised when application private data is received. /// internal event EventHandler> ApplicationPrivateDataReceived; /// - /// Event raised when a PSEventArgs is received + /// Event raised when a PSEventArgs is received. /// internal event EventHandler> PSEventArgsReceived; @@ -379,12 +375,10 @@ internal void SendGetAvailableRunspacesToServer(long callId) internal event EventHandler> SessionClosed; /// - /// /// internal event EventHandler> SessionDisconnected; /// - /// /// internal event EventHandler> SessionReconnected; @@ -395,12 +389,12 @@ internal void SendGetAvailableRunspacesToServer(long callId) /// /// Event raised when a response to a SetMaxRunspaces or SetMinRunspaces call - /// is received + /// is received. /// internal event EventHandler> SetMaxMinRunspacesResponseReceived; /// - /// EventHandler used to report connection URI redirections to the application + /// EventHandler used to report connection URI redirections to the application. /// internal event EventHandler> URIRedirectionReported; @@ -420,9 +414,9 @@ internal void SendGetAvailableRunspacesToServer(long callId) /// /// Send the data specified as a RemoteDataObject asynchronously - /// to the runspace pool on the remote session + /// to the runspace pool on the remote session. /// - /// data to send + /// Data to send. /// This overload takes a RemoteDataObject and should be /// the one used within the code private void SendDataAsync(RemoteDataObject data) @@ -432,11 +426,11 @@ private void SendDataAsync(RemoteDataObject data) /// /// Send the data asynchronously to runspace pool driver on remote - /// session with the specified priority + /// session with the specified priority. /// /// - /// data to be sent to server - /// priority with which to send data + /// Data to be sent to server. + /// Priority with which to send data. internal void SendDataAsync(RemoteDataObject data, DataPriorityType priority) { _transportManager.DataToBeSentCollection.Add(data, priority); @@ -444,10 +438,10 @@ internal void SendDataAsync(RemoteDataObject data, DataPriorityType priori /// /// Send the data asynchronously to runspace pool driver on remote - /// session with the specified priority + /// session with the specified priority. /// - /// data object to send - /// priority with which to send data + /// Data object to send. + /// Priority with which to send data. internal void SendDataAsync(PSObject data, DataPriorityType priority) { RemoteDataObject dataToBeSent = RemoteDataObject.CreateFrom(RemotingDestination.Server, @@ -457,7 +451,7 @@ internal void SendDataAsync(PSObject data, DataPriorityType priority) } /// - /// Create a client remote session based on the connection info + /// Create a client remote session based on the connection info. /// /// /// The RunspacePool object this session should map to. @@ -472,10 +466,10 @@ private ClientRemoteSessionImpl CreateClientRemoteSession( } /// - /// Handler for handling all session events + /// Handler for handling all session events. /// - /// sender of this event - /// object describing this event + /// Sender of this event. + /// Object describing this event. private void HandleClientRemoteSessionStateChanged( object sender, RemoteSessionStateEventArgs e) { @@ -490,14 +484,15 @@ private void HandleClientRemoteSessionStateChanged( lock (_syncObject) { - //We are doing this check because Established event - //is raised more than once + // We are doing this check because Established event + // is raised more than once if (_createRunspaceCalled) { // TODO: Put an assert here. NegotiationSending cannot // occur multiple time in v2 remoting. return; } + _createRunspaceCalled = true; } @@ -509,6 +504,7 @@ private void HandleClientRemoteSessionStateChanged( _clientRunspacePoolId, _minRunspaces, _maxRunspaces, RemoteSession.RemoteRunspacePoolInternal, _host, argumentsWithVersionTable)); } + if (e.SessionStateInfo.State == RemoteSessionState.NegotiationSendingOnConnect) { // send connect message to the server. @@ -531,6 +527,7 @@ private void HandleClientRemoteSessionStateChanged( { dsHandlers = new List(_associatedPowerShellDSHandlers.Values); } + foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) { dsHandler.CloseConnectionAsync(_closingReason); @@ -592,7 +589,7 @@ private void HandleClientRemoteSessionStateChanged( _closingReason = e.SessionStateInfo.Reason; } } - } // HandleClientRemoteSessionStateChanged + } /// /// Session is reporting that URI is getting redirected. @@ -605,7 +602,7 @@ private void HandleURIDirectionReported(Uri newURI) } /// - /// Notifies associated powershell's of the runspace pool state change + /// Notifies associated powershell's of the runspace pool state change. /// /// state information that need to /// be notified @@ -619,6 +616,7 @@ private void NotifyAssociatedPowerShells(RunspacePoolStateInfo stateInfo) { dsHandlers = new List(_associatedPowerShellDSHandlers.Values); } + foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) { dsHandler.ProcessDisconnect(stateInfo); @@ -644,7 +642,7 @@ private void NotifyAssociatedPowerShells(RunspacePoolStateInfo stateInfo) { dsHandler.SetStateToFailed(stateInfo.Reason); } - } // if ... + } else if (stateInfo.State == RunspacePoolState.Closed) { foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) @@ -658,10 +656,10 @@ private void NotifyAssociatedPowerShells(RunspacePoolStateInfo stateInfo) } /// - /// Gets the ClientPowerShellDataStructureHandler instance for the specified id + /// Gets the ClientPowerShellDataStructureHandler instance for the specified id. /// - /// id of the client remote powershell - /// ClientPowerShellDataStructureHandler object + /// Id of the client remote powershell. + /// ClientPowerShellDataStructureHandler object. private ClientPowerShellDataStructureHandler GetAssociatedPowerShellDataStructureHandler (Guid clientPowerShellId) { @@ -681,10 +679,10 @@ private ClientPowerShellDataStructureHandler GetAssociatedPowerShellDataStructur } /// - /// Remove the association of the powershell from the runspace pool + /// Remove the association of the powershell from the runspace pool. /// - /// sender of this event - /// unused + /// Sender of this event. + /// Unused. private void HandleRemoveAssociation(object sender, EventArgs e) { Dbg.Assert(sender is ClientPowerShellDataStructureHandler, @"sender of the event @@ -749,6 +747,7 @@ private void PrepareForAndStartDisconnect() { dsHandlers = new List(_associatedPowerShellDSHandlers.Values); } + foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) { dsHandler.TransportManager.PrepareForDisconnect(); @@ -767,6 +766,7 @@ private void PrepareForConnect() { dsHandlers = new List(_associatedPowerShellDSHandlers.Values); } + foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) { dsHandler.TransportManager.ReadyForDisconnect -= HandleReadyForDisconnect; @@ -797,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) { @@ -810,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)); } } } @@ -818,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. + } } /// @@ -838,6 +843,7 @@ private void HandleRobustConnectionNotification( { dsHandlers = new List(_associatedPowerShellDSHandlers.Values); } + foreach (ClientPowerShellDataStructureHandler dsHandler in dsHandlers) { dsHandler.ProcessRobustConnectionNotification(e); @@ -847,8 +853,8 @@ private void HandleRobustConnectionNotification( /// /// Forwards the session create completion event. /// - /// transport sender - /// CreateCompleteEventArgs + /// Transport sender. + /// CreateCompleteEventArgs. private void HandleSessionCreateCompleted(object sender, CreateCompleteEventArgs eventArgs) { SessionCreateCompleted.SafeInvoke(this, eventArgs); @@ -858,21 +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 Dictionary _associatedPowerShellDSHandlers + private readonly int _minRunspaces; + private readonly int _maxRunspaces; + private readonly PSHost _host; + private readonly PSPrimitiveDictionary _applicationArguments; + + 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; @@ -883,12 +891,12 @@ private Dictionary _associatedPowerS /// /// The remote session associated with this runspace pool - /// data structure handler + /// data structure handler. /// internal ClientRemoteSession RemoteSession { get; private set; } /// - /// Transport manager used by this data structure handler + /// Transport manager used by this data structure handler. /// internal BaseClientSessionTransportManager TransportManager { @@ -932,7 +940,7 @@ internal bool EndpointSupportsDisconnect get { WSManClientSessionTransportManager wsmanTransportManager = _transportManager as WSManClientSessionTransportManager; - return (wsmanTransportManager != null) ? wsmanTransportManager.SupportsDisconnect : false; + return wsmanTransportManager != null && wsmanTransportManager.SupportsDisconnect; } } @@ -941,7 +949,7 @@ internal bool EndpointSupportsDisconnect #region IDisposable /// - /// public interface for dispose + /// Public interface for dispose. /// public void Dispose() { @@ -951,9 +959,9 @@ public void Dispose() } /// - /// Release all resources + /// Release all resources. /// - /// if true, release all managed resources + /// If true, release all managed resources. public void Dispose(bool disposing) { if (disposing) @@ -971,53 +979,53 @@ public void Dispose(bool disposing) /// /// Base class for ClientPowerShellDataStructureHandler to handle all - /// references + /// references. /// - internal class ClientPowerShellDataStructureHandler + internal sealed class ClientPowerShellDataStructureHandler { #region Data Structure Handler events /// - /// this event is raised when the state of associated + /// This event is raised when the state of associated /// powershell is terminal and the runspace pool has - /// to detach the association + /// to detach the association. /// internal event EventHandler RemoveAssociation; /// /// This event is raised when a state information object - /// is received from the server + /// is received from the server. /// internal event EventHandler> InvocationStateInfoReceived; /// /// This event is raised when an output object is received - /// from the server + /// from the server. /// internal event EventHandler> OutputReceived; /// /// This event is raised when an error record is received - /// from the server + /// from the server. /// internal event EventHandler> ErrorReceived; /// /// This event is raised when an informational message - /// debug, verbose, warning, progress is received from - /// the server + /// the server. /// internal event EventHandler> InformationalMessageReceived; /// /// This event is raised when a host call is targeted to the - /// powershell + /// powershell. /// internal event EventHandler> HostCallReceived; /// /// This event is raised when a runspace pool data structure handler notifies an - /// associated powershell data structure handler that its closed + /// associated powershell data structure handler that its closed. /// internal event EventHandler> ClosedNotificationFromRunspacePool; @@ -1038,17 +1046,17 @@ internal class ClientPowerShellDataStructureHandler /// /// This event is raised when a runspace pool data structure handler notifies an - /// associated powershell data structure handler that its broken + /// associated powershell data structure handler that its broken. /// internal event EventHandler> BrokenNotificationFromRunspacePool; /// - /// This event is raised when reconnect async operation on the associated powershell/pipeline instance is completed + /// This event is raised when reconnect async operation on the associated powershell/pipeline instance is completed. /// internal event EventHandler> ReconnectCompleted; /// - /// This event is raised when connect async operation on the associated powershell/pipeline instance is completed + /// This event is raised when connect async operation on the associated powershell/pipeline instance is completed. /// internal event EventHandler> ConnectCompleted; @@ -1073,13 +1081,13 @@ internal void Start(ClientRemoteSessionDSHandlerStateMachine stateMachine, bool private void HandleDelayStreamRequestProcessed(object sender, EventArgs e) { - //client's request to start pipeline in disconnected mode has been successfully processed + // client's request to start pipeline in disconnected mode has been successfully processed ProcessDisconnect(null); } internal void HandleReconnectCompleted(object sender, EventArgs args) { - Int32 currentState = Interlocked.CompareExchange(ref _connectionState, (Int32)connectionStates.Connected, (Int32)connectionStates.Reconnecting); + int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Connected, (int)connectionStates.Reconnecting); ReconnectCompleted.SafeInvoke(this, new RemoteDataEventArgs(null)); return; @@ -1087,7 +1095,7 @@ internal void HandleReconnectCompleted(object sender, EventArgs args) internal void HandleConnectCompleted(object sender, EventArgs args) { - Int32 currentState = Interlocked.CompareExchange(ref _connectionState, (Int32)connectionStates.Connected, (Int32)connectionStates.Connecting); + int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Connected, (int)connectionStates.Connecting); ConnectCompleted.SafeInvoke(this, new RemoteDataEventArgs(null)); return; @@ -1109,7 +1117,7 @@ internal void HandleTransportError(object sender, TransportErrorOccuredEventArgs } /// - /// Send a stop powershell message to the server + /// Send a stop powershell message to the server. /// internal void SendStopPowerShellMessage() { @@ -1136,16 +1144,16 @@ private void OnSignalCompleted(object sender, EventArgs e) } /// - /// Send the host response to the server + /// Send the host response to the server. /// - /// host response to send + /// Host response to send. internal void SendHostResponseToServer(RemoteHostResponse hostResponse) { RemoteDataObject dataToBeSent = RemoteDataObject.CreateFrom(RemotingDestination.Server, RemotingDataType.RemotePowerShellHostResponseData, - clientRunspacePoolId, - clientPowerShellId, + _clientRunspacePoolId, + _clientPowerShellId, hostResponse.Encode()); TransportManager.DataToBeSentCollection.Add(dataToBeSent, @@ -1154,7 +1162,7 @@ internal void SendHostResponseToServer(RemoteHostResponse hostResponse) /// /// Attach the specified data collection as input - /// to the remote powershell + /// to the remote powershell. /// /// internal void SendInput(ObjectStreamBase inputstream) @@ -1167,7 +1175,7 @@ internal void SendInput(ObjectStreamBase inputstream) { // send input closed information to server SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } else @@ -1182,24 +1190,24 @@ internal void SendInput(ObjectStreamBase inputstream) // registered lock (_inputSyncObject) { - inputstream.DataReady += new EventHandler(HandleInputDataReady); + inputstream.DataReady += HandleInputDataReady; WriteInput(inputstream); } - } // else ... + } } /// /// Process the data received from the runspace pool - /// on the server + /// on the server. /// - /// data received + /// Data received. 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 @@ -1218,6 +1226,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) InvocationStateInfoReceived.SafeInvoke(this, new RemoteDataEventArgs(stateInfo)); } + break; case RemotingDataType.PowerShellOutput: @@ -1235,6 +1244,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) OutputReceived.SafeInvoke(this, new RemoteDataEventArgs(outputObject)); } + break; case RemotingDataType.PowerShellErrorRecord: @@ -1255,6 +1265,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) ErrorReceived.SafeInvoke(this, new RemoteDataEventArgs(errorRecord)); } + break; case RemotingDataType.PowerShellDebug: { @@ -1264,6 +1275,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) new RemoteDataEventArgs( new InformationalMessage(record, RemotingDataType.PowerShellDebug))); } + break; case RemotingDataType.PowerShellVerbose: @@ -1274,6 +1286,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) new RemoteDataEventArgs( new InformationalMessage(record, RemotingDataType.PowerShellVerbose))); } + break; case RemotingDataType.PowerShellWarning: @@ -1284,6 +1297,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) new RemoteDataEventArgs( new InformationalMessage(record, RemotingDataType.PowerShellWarning))); } + break; case RemotingDataType.PowerShellProgress: @@ -1294,6 +1308,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) new RemoteDataEventArgs( new InformationalMessage(record, RemotingDataType.PowerShellProgress))); } + break; case RemotingDataType.PowerShellInformationStream: @@ -1304,6 +1319,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) new RemoteDataEventArgs( new InformationalMessage(record, RemotingDataType.PowerShellInformationStream))); } + break; case RemotingDataType.RemoteHostCallUsingPowerShellHost: @@ -1311,18 +1327,20 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) RemoteHostCall remoteHostCall = RemoteHostCall.Decode(receivedData.Data); HostCallReceived.SafeInvoke(this, new RemoteDataEventArgs(remoteHostCall)); } + break; default: { Dbg.Assert(false, "we should not be encountering this"); } + break; - } // switch ... - } // ProcessReceivedData + } + } /// - /// Set the state of the associated powershell to stopped + /// Set the state of the associated powershell to stopped. /// /// reason why this state change /// should occur @@ -1338,7 +1356,7 @@ internal void SetStateToFailed(Exception reason) } /// - /// Sets the state of the powershell to stopped + /// Sets the state of the powershell to stopped. /// /// reason why the powershell has to be /// set to a stopped state. @@ -1358,9 +1376,9 @@ 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 (null != CloseCompleted) + if (CloseCompleted != null) { // If the provided event args are empty then call CloseCompleted with // RemoteSessionStateEventArgs containing session closed reason exception. @@ -1381,7 +1399,7 @@ internal void CloseConnectionAsync(Exception sessionCloseReason) /// Raise a remove association event. This is raised /// when the powershell has gone into a terminal state /// and the runspace pool need not maintain any further - /// associations + /// associations. /// internal void RaiseRemoveAssociationEvent() { @@ -1390,33 +1408,33 @@ internal void RaiseRemoveAssociationEvent() /// /// Called from runspace DS handler while disconnecting - /// This will set the state of the pipeline DS handler to disconnected + /// This will set the state of the pipeline DS handler to disconnected. /// internal void ProcessDisconnect(RunspacePoolStateInfo rsStateInfo) { - //disconnect may be called on a pipeline that is already disconnected. + // 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"); InvocationStateInfoReceived.SafeInvoke(this, new RemoteDataEventArgs(stateInfo)); - Interlocked.CompareExchange(ref _connectionState, (Int32)connectionStates.Disconnected, (Int32)connectionStates.Connected); + Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Disconnected, (int)connectionStates.Connected); } /// /// 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 + /// WSMan transport ensures that WinRS commands cannot be reconnected when the parent shell is not in connected state. /// internal void ReconnectAsync() { - Int32 currentState = Interlocked.CompareExchange(ref _connectionState, (Int32)connectionStates.Reconnecting, (Int32)connectionStates.Disconnected); - if ((currentState != (Int32)connectionStates.Disconnected)) + int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Reconnecting, (int)connectionStates.Disconnected); + if ((currentState != (int)connectionStates.Disconnected)) { Dbg.Assert(false, "Pipeline DS Handler is in unexpected connection state"); @@ -1427,10 +1445,10 @@ internal void ReconnectAsync() TransportManager.ReconnectAsync(); } - //Called from session DSHandler. Connects to a remote powershell instance. + // Called from session DSHandler. Connects to a remote powershell instance. internal void ConnectAsync() { - Int32 currentState = Interlocked.CompareExchange(ref _connectionState, (Int32)connectionStates.Connecting, (Int32)connectionStates.Disconnected); + int currentState = Interlocked.CompareExchange(ref _connectionState, (int)connectionStates.Connecting, (int)connectionStates.Disconnected); // Connect is called for *reconstruct* connection case and so // we need to set up all transport manager callbacks. @@ -1452,17 +1470,10 @@ internal void ProcessRobustConnectionNotification( #endregion Data Structure Handler Methods - #region Protected Members - - protected Guid clientRunspacePoolId; - protected Guid clientPowerShellId; - - #endregion Protected Members - #region Constructors /// - /// Default internal constructor + /// Default internal constructor. /// /// id of the client /// remote runspace pool associated with this data structure handler @@ -1475,9 +1486,9 @@ internal ClientPowerShellDataStructureHandler(BaseClientCommandTransportManager Guid clientRunspacePoolId, Guid clientPowerShellId) { TransportManager = transportManager; - this.clientRunspacePoolId = clientRunspacePoolId; - this.clientPowerShellId = clientPowerShellId; - transportManager.SignalCompleted += new EventHandler(OnSignalCompleted); + _clientRunspacePoolId = clientRunspacePoolId; + _clientPowerShellId = clientPowerShellId; + transportManager.SignalCompleted += OnSignalCompleted; } #endregion Constructors @@ -1486,18 +1497,18 @@ internal ClientPowerShellDataStructureHandler(BaseClientCommandTransportManager /// /// Client PowerShell Id of the powershell this - /// data structure handler is associated with + /// data structure handler is associated with. /// internal Guid PowerShellId { get { - return clientPowerShellId; + return _clientPowerShellId; } } /// - /// transport manager used by this data structure handler + /// Transport manager used by this data structure handler. /// internal BaseClientCommandTransportManager TransportManager { get; } @@ -1507,9 +1518,9 @@ internal Guid PowerShellId /// /// Send the data specified as a RemoteDataObject asynchronously - /// to the powershell on server + /// to the powershell on server. /// - /// data to send + /// Data to send. /// This overload takes a RemoteDataObject and should be /// the one used within the code private void SendDataAsync(RemoteDataObject data) @@ -1519,10 +1530,10 @@ private void SendDataAsync(RemoteDataObject data) } /// - /// Handle data added to input + /// Handle data added to input. /// - /// sender of this event - /// information describing this event + /// Sender of this event. + /// Information describing this event. private void HandleInputDataReady(object sender, EventArgs e) { // make sure only one thread calls the WriteInput. @@ -1534,30 +1545,29 @@ private void HandleInputDataReady(object sender, EventArgs e) } /// - /// /// /// This method doesn't lock and its the responsibility /// of the caller to actually do the locking /// 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); + // Write any data written after the NonBlockingRead call above. + 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 @@ -1566,7 +1576,7 @@ private void WriteInput(ObjectStreamBase inputstream) inputstream.DataReady -= HandleInputDataReady; // stream close: send end of input SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } @@ -1574,7 +1584,7 @@ private void WriteInput(ObjectStreamBase inputstream) /// Helper method to add transport manager callbacks and set transport /// manager disconnected state. /// - /// Boolean + /// Boolean. private void SetupTransportManager(bool inDisconnectMode) { TransportManager.WSManTransportErrorOccured += HandleTransportError; @@ -1588,16 +1598,19 @@ 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 { Connected = 1, Disconnected = 3, Reconnecting = 4, Connecting = 5 } - private Int32 _connectionState = (Int32)connectionStates.Connected; + private int _connectionState = (int)connectionStates.Connected; // Contains the associated session closed reason exception if any, // otherwise is null. @@ -1606,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 3f1d865ef23..91cf4161a99 100644 --- a/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs +++ b/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs @@ -1,11 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; 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; namespace System.Management.Automation.Remoting @@ -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. @@ -90,7 +91,26 @@ private PSCommand ParsePsCommandUsingScriptBlock(string line, bool? useLocalScop ExecutionContext context = localRunspace.ExecutionContext; // This is trusted input as long as we're in FullLanguage mode - bool isTrustedInput = (localRunspace.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage); + // 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; + + 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); @@ -133,9 +153,9 @@ internal PSCommand CreatePsCommand(string line, bool isScript, bool? useNewScope } /// - /// Creates the PSCommand when the runspace is not overridden + /// 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(); @@ -204,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; @@ -218,8 +235,7 @@ internal Pipeline CreatePipeline(string line, bool addToHistory, bool useNestedP PowerShell shell = remotePipeline.PowerShell; if (shell.RemotePowerShell != null) { - shell.RemotePowerShell.RCConnectionNotification += - new EventHandler(HandleRCConnectionNotification); + shell.RemotePowerShell.RCConnectionNotification += HandleRCConnectionNotification; } // Add callback to write robust connection errors from stream. @@ -270,11 +286,11 @@ internal void Override(RemoteRunspace remoteRunspace) } /// - /// Override inside a safe lock + /// Override inside a safe lock. /// - /// runspace to override - /// object to use in synchronization - /// set is runspace pushed + /// Runspace to override. + /// Object to use in synchronization. + /// Set is runspace pushed. internal void Override(RemoteRunspace remoteRunspace, object syncObject, out bool isRunspacePushed) { lock (_localSyncObject) @@ -310,12 +326,11 @@ 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; - powerShell.RemotePowerShell.HostCallReceived += new EventHandler>(HandleHostCall); + powerShell.RemotePowerShell.HostCallReceived += HandleHostCall; IAsyncResult asyncResult = powerShell.BeginInvoke(); PSDataCollection results = new PSDataCollection(); @@ -346,13 +361,12 @@ 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 @@ -406,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 b9740d7506f..59f4fb5135d 100644 --- a/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs +++ b/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Linq; using System.Management.Automation.Remoting.Internal; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -23,14 +23,14 @@ internal StartableJob(string commandName, string jobName) } /// - /// A job that can throttle execution of child jobs + /// A job that can throttle execution of child jobs. /// internal sealed class ThrottlingJob : Job { #region IDisposable Members /// - /// Releases resources associated with this object + /// Releases resources associated with this object. /// protected override void Dispose(bool disposing) { @@ -47,14 +47,13 @@ protected override void Dispose(bool disposing) childJobsToDispose = new List(this.ChildJobs); this.ChildJobs.Clear(); } + foreach (Job childJob in childJobsToDispose) { childJob.Dispose(); } - if (_jobResultsThrottlingSemaphore != null) - { - _jobResultsThrottlingSemaphore.Dispose(); - } + + _jobResultsThrottlingSemaphore?.Dispose(); _cancellationTokenSource.Dispose(); } } @@ -107,6 +106,7 @@ private void ReportProgress(bool minimizeFrequentUpdates) { return; } + if ((!_progressReportLastTime.Equals(DateTime.MinValue)) && (now - _progressReportLastTime < TimeSpan.FromMilliseconds(200))) { @@ -124,6 +124,7 @@ private void ReportProgress(bool minimizeFrequentUpdates) totalWork = _countOfAllChildJobs; workCompleted = this.CountOfFinishedChildJobs; } + if (totalWork >= 1.0) { percentComplete = (int)(100.0 * workCompleted / totalWork); @@ -132,6 +133,7 @@ private void ReportProgress(bool minimizeFrequentUpdates) { percentComplete = -1; } + percentComplete = Math.Max(-1, Math.Min(100, percentComplete)); var progressRecord = new ProgressRecord( @@ -145,6 +147,7 @@ private void ReportProgress(bool minimizeFrequentUpdates) { return; } + progressRecord.RecordType = ProgressRecordType.Completed; progressRecord.PercentComplete = 100; progressRecord.SecondsRemaining = 0; @@ -158,6 +161,7 @@ private void ReportProgress(bool minimizeFrequentUpdates) { secondsRemaining = ProgressRecord.GetSecondsRemaining(_progressStartTime, (double)percentComplete / 100.0); } + if (secondsRemaining.HasValue) { progressRecord.SecondsRemaining = secondsRemaining.Value; @@ -177,14 +181,14 @@ private void ReportProgress(bool minimizeFrequentUpdates) internal enum ChildJobFlags { /// - /// Child job doesn't have any special properties + /// Child job doesn't have any special properties. /// None = 0, /// /// Child job can call method - /// or - /// or + /// or + /// or /// method /// of the instance it belongs to. /// @@ -193,6 +197,7 @@ internal enum ChildJobFlags private bool _ownerWontSubmitNewChildJobs = false; private readonly HashSet _setOfChildJobsThatCanAddMoreChildJobs = new HashSet(); + private bool IsEndOfChildJobs { get @@ -203,6 +208,7 @@ private bool IsEndOfChildJobs } } } + private bool IsThrottlingJobCompleted { get @@ -223,6 +229,7 @@ private bool IsThrottlingJobCompleted private int _countOfFailedChildJobs; private int _countOfStoppedChildJobs; private int _countOfSuccessfullyCompletedChildJobs; + private int CountOfFinishedChildJobs { get @@ -250,20 +257,20 @@ private int CountOfRunningOrReadyToRunChildJobs /// /// Creates a new object. /// - /// Command invoked by this job object - /// Friendly name for the job object + /// Command invoked by this job object. + /// Friendly name for the job object. /// Name describing job type. /// /// The maximum number of child jobs that can be running at any given point in time. /// 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) @@ -275,6 +282,7 @@ internal ThrottlingJob(string command, string jobName, string jobTypeName, int m { _jobResultsThrottlingSemaphore = new SemaphoreSlim(ForwardingHelper.AggregationQueueMaxCapacity); } + _progressActivityId = new Random(this.GetHashCode()).Next(); this.SetupThrottlingQueue(maximumConcurrentChildJobs); @@ -286,7 +294,7 @@ internal void AddChildJobAndPotentiallyBlock( { using (var jobGotEnqueued = new ManualResetEventSlim(initialState: false)) { - if (childJob == null) throw new ArgumentNullException("childJob"); + ArgumentNullException.ThrowIfNull(childJob); this.AddChildJobWithoutBlocking(childJob, flags, jobGotEnqueued.Set); jobGotEnqueued.Wait(); @@ -300,7 +308,7 @@ internal void AddChildJobAndPotentiallyBlock( { using (var forwardingCancellation = new CancellationTokenSource()) { - if (childJob == null) throw new ArgumentNullException("childJob"); + ArgumentNullException.ThrowIfNull(childJob); this.AddChildJobWithoutBlocking(childJob, flags, forwardingCancellation.Cancel); this.ForwardAllResultsToCmdlet(cmdlet, forwardingCancellation.Token); @@ -308,12 +316,14 @@ internal void AddChildJobAndPotentiallyBlock( } private bool _alreadyDisabledFlowControlForPendingJobsQueue = false; + internal void DisableFlowControlForPendingJobsQueue() { if (!_cmdletMode || _alreadyDisabledFlowControlForPendingJobsQueue) { return; } + _alreadyDisabledFlowControlForPendingJobsQueue = true; lock (_lockObject) @@ -323,21 +333,20 @@ internal void DisableFlowControlForPendingJobsQueue() while (_actionsForUnblockingChildAdditions.Count > 0) { Action a = _actionsForUnblockingChildAdditions.Dequeue(); - if (a != null) - { - a(); - } + a?.Invoke(); } } } private bool _alreadyDisabledFlowControlForPendingCmdletActionsQueue = false; + internal void DisableFlowControlForPendingCmdletActionsQueue() { if (!_cmdletMode || _alreadyDisabledFlowControlForPendingCmdletActionsQueue) { return; } + _alreadyDisabledFlowControlForPendingCmdletActionsQueue = true; long slotsToRelease = (long)(int.MaxValue / 2) - (long)(_jobResultsThrottlingSemaphore.CurrentCount); @@ -350,33 +359,46 @@ internal void DisableFlowControlForPendingCmdletActionsQueue() /// /// Adds and starts a child job. /// - /// Child job to add - /// Flags of the child job - /// action to run after enqueuing the job + /// Child job to add. + /// Flags of the child job. + /// Action to run after enqueuing the job. /// /// Thrown when the child job is not in the state. /// (because this can lead to race conditions - the child job can finish before the parent job has a chance to register for child job events) /// internal void AddChildJobWithoutBlocking(StartableJob childJob, ChildJobFlags flags, Action jobEnqueuedAction = null) { - if (childJob == null) throw new ArgumentNullException("childJob"); - if (childJob.JobStateInfo.State != JobState.NotStarted) throw new ArgumentException(RemotingErrorIdStrings.ThrottlingJobChildAlreadyRunning, "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); } + this.ChildJobs.Add(childJob); _childJobLocations.Add(childJob.Location); _countOfAllChildJobs++; @@ -388,12 +410,10 @@ internal void AddChildJobWithoutBlocking(StartableJob childJob, ChildJobFlags fl } else { - if (jobEnqueuedAction != null) - { - jobEnqueuedAction(); - } + jobEnqueuedAction?.Invoke(); } } + if (newJobStateInfo != null) { this.SetJobState(newJobStateInfo.State, newJobStateInfo.Reason); @@ -405,8 +425,9 @@ internal void AddChildJobWithoutBlocking(StartableJob childJob, ChildJobFlags fl childJob.StateChanged += this.childJob_StateChanged; if (_cmdletMode) { - childJob.Results.DataAdded += new EventHandler(childJob_ResultsAdded); + childJob.Results.DataAdded += childJob_ResultsAdded; } + this.EnqueueReadyToRunChildJob(childJob); this.ReportProgress(minimizeFrequentUpdates: true); @@ -432,6 +453,7 @@ private void childJob_ResultsAdded(object sender, DataAddedEventArgs e) private readonly object _alreadyWroteFlowControlBuffersHighMemoryUsageWarningLock = new object(); private bool _alreadyWroteFlowControlBuffersHighMemoryUsageWarning; + private const long FlowControlBuffersHighMemoryUsageThreshold = 30000; private void WriteWarningAboutHighUsageOfFlowControlBuffers(long currentCount) @@ -452,6 +474,7 @@ private void WriteWarningAboutHighUsageOfFlowControlBuffers(long currentCount) { return; } + _alreadyWroteFlowControlBuffersHighMemoryUsageWarning = true; } @@ -525,10 +548,7 @@ private void StartChildJobIfPossible() } while (false); } - if (readyToRunChildJob != null) - { - readyToRunChildJob.StartJob(); - } + readyToRunChildJob?.StartJob(); } private void EnqueueReadyToRunChildJob(StartableJob childJob) @@ -605,6 +625,7 @@ private void FigureOutIfThrottlingJobIsCompleted() } } } + if (finalJobStateInfo != null) { this.SetJobState(finalJobStateInfo.State, finalJobStateInfo.Reason); @@ -622,6 +643,7 @@ internal void EndOfChildJobs() { _ownerWontSubmitNewChildJobs = true; } + this.FigureOutIfThrottlingJobIsCompleted(); } @@ -679,6 +701,7 @@ private void childJob_StateChanged(object sender, JobStateEventArgs e) parentJobGotUnblocked = true; } } + if (parentJobGotUnblocked) { this.SetJobState(JobState.Running); @@ -693,6 +716,7 @@ private void childJob_StateChanged(object sender, JobStateEventArgs e) { _countOfBlockedChildJobs++; } + this.SetJobState(JobState.Blocked); break; @@ -720,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) @@ -732,11 +753,13 @@ private void childJob_StateChanged(object sender, JobStateEventArgs e) { this.Results.Add(streamObject); } + this.ChildJobs.Remove(childJob); _setOfChildJobsThatCanAddMoreChildJobs.Remove(childJob.InstanceId); childJob.Dispose(); } } + this.ReportProgress(minimizeFrequentUpdates: !this.IsThrottlingJobCompleted); break; @@ -758,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); } } @@ -782,10 +805,11 @@ public override string Location } } } + private readonly HashSet _childJobLocations = new HashSet(StringComparer.OrdinalIgnoreCase); /// - /// Status message associated with the Job + /// Status message associated with the Job. /// public override string StatusMessage { @@ -826,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 @@ -865,15 +889,18 @@ private void StartMonitoringJob(Job job) { return; } + if (_monitoredJobs.Contains(job)) { return; } + _monitoredJobs.Add(job); job.Results.DataAdded += this.MonitoredJobResults_DataAdded; job.StateChanged += MonitoredJob_StateChanged; } + this.AggregateJobResults(job.Results); this.CheckIfMonitoredJobIsComplete(job); } @@ -975,10 +1002,12 @@ private void CheckIfThrottlingJobIsComplete() { resultsToAggregate.Add(registeredJob.Results); } + foreach (Job throttledJob in _throttlingJob.GetChildJobsSnapshot()) { resultsToAggregate.Add(throttledJob.Results); } + resultsToAggregate.Add(_throttlingJob.Results); } @@ -1124,6 +1153,7 @@ private void ForwardResults(Cmdlet cmdlet) } private bool _stoppedMonitoringAllJobs; + private void StopMonitoringAllJobs() { _cancellationTokenSource.Cancel(); @@ -1136,6 +1166,7 @@ private void StopMonitoringAllJobs() { this.StopMonitoringJob(monitoredJob); } + Dbg.Assert(_monitoredJobs.Count == 0, "No monitored jobs should be left after ForwardingHelper is disposed"); if (!_disposed && !_aggregatedResults.IsAddingCompleted) @@ -1180,6 +1211,7 @@ public static void ForwardAllResultsToCmdlet(ThrottlingJob throttlingJob, Cmdlet { cancellationTokenRegistration = cancellationToken.Value.Register(helper.CancelForwarding); } + try { Interlocked.MemoryBarrier(); @@ -1199,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 a717ba21959..499062d7a18 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs @@ -1,11 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; @@ -56,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 @@ -69,7 +68,7 @@ internal abstract class ClientRemoteSession : RemoteSession public abstract void CreateAsync(); /// - /// This event handler is raised when the state of session changes + /// This event handler is raised when the state of session changes. /// public abstract event EventHandler StateChanged; @@ -81,12 +80,12 @@ internal abstract class ClientRemoteSession : RemoteSession public abstract void CloseAsync(); /// - /// Disconnects the remote session in an asynchronous manner + /// Disconnects the remote session in an asynchronous manner. /// public abstract void DisconnectAsync(); /// - /// Reconnects the remote session in an asynchronous manner + /// Reconnects the remote session in an asynchronous manner. /// public abstract void ReconnectAsync(); @@ -108,7 +107,7 @@ internal abstract class ClientRemoteSession : RemoteSession #region URI Redirection /// - /// Delegate used to report connection URI redirections to the application + /// Delegate used to report connection URI redirections to the application. /// /// /// New URI to which the connection is being redirected to. @@ -118,13 +117,13 @@ internal abstract class ClientRemoteSession : RemoteSession #endregion /// - /// ServerRemoteSessionDataStructureHandler instance for this session + /// ServerRemoteSessionDataStructureHandler instance for this session. /// internal ClientRemoteSessionDataStructureHandler SessionDataStructureHandler { get; set; } protected Version _serverProtocolVersion; /// - /// Protocol version negotiated by the server + /// Protocol version negotiated by the server. /// internal Version ServerProtocolVersion { @@ -134,12 +133,10 @@ internal Version ServerProtocolVersion } } - - private RemoteRunspacePoolInternal _remoteRunspacePool; /// - /// remote runspace pool if used, for this session + /// Remote runspace pool if used, for this session. /// internal RemoteRunspacePoolInternal RemoteRunspacePoolInternal { @@ -147,6 +144,7 @@ internal RemoteRunspacePoolInternal RemoteRunspacePoolInternal { return _remoteRunspacePool; } + set { Dbg.Assert(_remoteRunspacePool == null, @"RunspacePool should be @@ -156,7 +154,7 @@ internal RemoteRunspacePoolInternal RemoteRunspacePoolInternal } /// - /// Get the runspace pool with the matching id + /// Get the runspace pool with the matching id. /// /// /// Id of the runspace to get @@ -175,19 +173,19 @@ internal RemoteRunspacePoolInternal GetRunspacePool(Guid clientRunspacePoolId) } /// - /// Remote Session Implementation + /// Remote Session Implementation. /// 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; #region Constructors /// - /// Creates a new instance of ClientRemoteSessionImpl + /// Creates a new instance of ClientRemoteSessionImpl. /// /// /// The RunspacePool object this session should map to. @@ -197,7 +195,7 @@ internal class ClientRemoteSessionImpl : ClientRemoteSession, IDisposable internal ClientRemoteSessionImpl(RemoteRunspacePoolInternal rsPool, URIDirectionReported uriRedirectionHandler) { - Dbg.Assert(null != rsPool, "RunspacePool cannot be null"); + Dbg.Assert(rsPool != null, "RunspacePool cannot be null"); base.RemoteRunspacePoolInternal = rsPool; Context.RemoteAddress = WSManConnectionInfo.ExtractPropertyAsWsManConnectionInfo(rsPool.ConnectionInfo, "ConnectionUri", null); @@ -212,7 +210,7 @@ internal ClientRemoteSessionImpl(RemoteRunspacePoolInternal rsPool, "ShellUri", string.Empty); MySelf = RemotingDestination.Client; - //Create session data structure handler for this session + // Create session data structure handler for this session SessionDataStructureHandler = new ClientRemoteSessionDSHandlerImpl(this, _cryptoHelper, rsPool.ConnectionInfo, @@ -220,36 +218,33 @@ internal ClientRemoteSessionImpl(RemoteRunspacePoolInternal rsPool, BaseSessionDataStructureHandler = SessionDataStructureHandler; _waitHandleForConfigurationReceived = new ManualResetEvent(false); - //Register handlers for various ClientSessiondata structure handler events + // Register handlers for various ClientSessiondata structure handler events SessionDataStructureHandler.NegotiationReceived += HandleNegotiationReceived; SessionDataStructureHandler.ConnectionStateChanged += HandleConnectionStateChanged; - SessionDataStructureHandler.EncryptedSessionKeyReceived += - new EventHandler>(HandleEncryptedSessionKeyReceived); - SessionDataStructureHandler.PublicKeyRequestReceived += - new EventHandler>(HandlePublicKeyRequestReceived); + SessionDataStructureHandler.EncryptedSessionKeyReceived += HandleEncryptedSessionKeyReceived; + SessionDataStructureHandler.PublicKeyRequestReceived += HandlePublicKeyRequestReceived; } #endregion Constructors #region connect/close - /// /// Creates a Remote Session Asynchronously. /// public override void CreateAsync() { - //Raise a CreateSession event in StateMachine. This start the process of connection and negotiation to a new remote session + // Raise a CreateSession event in StateMachine. This start the process of connection and negotiation to a new remote session RemoteSessionStateMachineEventArgs startArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.CreateSession); SessionDataStructureHandler.StateMachine.RaiseEvent(startArg); } /// - /// Connects to a existing Remote Session Asynchronously by executing a Connect negotiation algorithm + /// Connects to a existing Remote Session Asynchronously by executing a Connect negotiation algorithm. /// public override void ConnectAsync() { - //Raise the connectsession event in statemachine. This start the process of connection and negotiation to an existing remote session + // Raise the connectsession event in statemachine. This start the process of connection and negotiation to an existing remote session RemoteSessionStateMachineEventArgs startArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.ConnectSession); SessionDataStructureHandler.StateMachine.RaiseEvent(startArg); } @@ -267,7 +262,7 @@ public override void CloseAsync() } /// - /// Temporarily suspends connection to a connected remote session + /// Temporarily suspends connection to a connected remote session. /// public override void DisconnectAsync() { @@ -276,7 +271,7 @@ public override void DisconnectAsync() } /// - /// Restores connection to a disconnected remote session. Negotiation has already been performed before + /// Restores connection to a disconnected remote session. Negotiation has already been performed before. /// public override void ReconnectAsync() { @@ -285,12 +280,12 @@ public override void ReconnectAsync() } /// - /// This event handler is raised when the state of session changes + /// This event handler is raised when the state of session changes. /// public override event EventHandler StateChanged; /// - /// Handles changes in data structure handler state + /// Handles changes in data structure handler state. /// /// /// @@ -302,10 +297,10 @@ private void HandleConnectionStateChanged(object sender, RemoteSessionStateEvent { if (arg == null) { - throw PSTraceSource.NewArgumentNullException("arg"); + throw PSTraceSource.NewArgumentNullException(nameof(arg)); } - if (arg.SessionStateInfo.State == RemoteSessionState.EstablishedAndKeyReceived) //TODO - Client session would never get into this state... to be removed + if (arg.SessionStateInfo.State == RemoteSessionState.EstablishedAndKeyReceived) // TODO - Client session would never get into this state... to be removed { // send the public key StartKeyExchange(); @@ -331,7 +326,7 @@ private void HandleConnectionStateChanged(object sender, RemoteSessionStateEvent #region KeyExchange /// - /// Start the key exchange process + /// Start the key exchange process. /// internal override void StartKeyExchange() { @@ -367,17 +362,19 @@ internal override void StartKeyExchange() SessionDataStructureHandler.StateMachine.RaiseEvent(eventArgs); } + else + { + // send using data structure handler + eventArgs = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeySent); + SessionDataStructureHandler.StateMachine.RaiseEvent(eventArgs); - // send using data structure handler - eventArgs = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeySent); - SessionDataStructureHandler.StateMachine.RaiseEvent(eventArgs); - - SessionDataStructureHandler.SendPublicKeyAsync(localPublicKey); + SessionDataStructureHandler.SendPublicKeyAsync(localPublicKey); + } } } /// - /// Complete the key exchange process + /// Complete the key exchange process. /// internal override void CompleteKeyExchange() { @@ -385,9 +382,9 @@ internal override void CompleteKeyExchange() } /// - /// Handles an encrypted session key received from the other side + /// Handles an encrypted session key received from the other side. /// - /// sender of this event + /// Sender of this event. /// arguments that contain the remote /// public key private void HandleEncryptedSessionKeyReceived(object sender, RemoteDataEventArgs eventArgs) @@ -417,10 +414,10 @@ private void HandleEncryptedSessionKeyReceived(object sender, RemoteDataEventArg } /// - /// Handles a request for public key from the server + /// Handles a request for public key from the server. /// - /// send of this event, unused - /// arguments describing this event, unused + /// Send of this event, unused. + /// Arguments describing this event, unused. private void HandlePublicKeyRequestReceived(object sender, RemoteDataEventArgs eventArgs) { if (SessionDataStructureHandler.StateMachine.State == RemoteSessionState.Established) @@ -435,7 +432,7 @@ private void HandlePublicKeyRequestReceived(object sender, RemoteDataEventArgs - /// Examines the negotiation packet received from the server + /// Examines the negotiation packet received from the server. /// /// /// @@ -455,12 +452,12 @@ private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEv { if (arg == null) { - throw PSTraceSource.NewArgumentNullException("arg"); + throw PSTraceSource.NewArgumentNullException(nameof(arg)); } if (arg.RemoteSessionCapability == null) { - throw PSTraceSource.NewArgumentException("arg"); + throw PSTraceSource.NewArgumentException(nameof(arg)); } Context.ServerCapability = arg.RemoteSessionCapability; @@ -484,7 +481,7 @@ private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEv } /// - /// Verifies the negotiation packet received from the server + /// Verifies the negotiation packet received from the server. /// /// /// Capabilities of remote session @@ -508,22 +505,13 @@ 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 + // passed negotiation check } else { @@ -574,7 +562,7 @@ private bool RunClientNegotiationAlgorithm(RemoteSessionCapability serverRemoteS #region IDisposable /// - /// Public method for dispose + /// Public method for dispose. /// public void Dispose() { @@ -584,9 +572,9 @@ public void Dispose() } /// - /// Release all resources + /// Release all resources. /// - /// if true, release all managed resources + /// If true, release all managed resources. public void Dispose(bool disposing) { if (disposing) @@ -596,6 +584,7 @@ public void Dispose(bool disposing) _waitHandleForConfigurationReceived.Dispose(); _waitHandleForConfigurationReceived = null; } + ((ClientRemoteSessionDSHandlerImpl)SessionDataStructureHandler).Dispose(); SessionDataStructureHandler = null; _cryptoHelper.Dispose(); @@ -606,5 +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 1ccc951f89e..2dea06d8ced 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; using System.Collections.Generic; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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 + /// [State,Event]=>Action. /// - private EventHandler[,] _stateMachineHandle; - private Queue _clientRemoteSessionStateChangeQueue; + private readonly EventHandler[,] _stateMachineHandle; + private readonly Queue _clientRemoteSessionStateChangeQueue; /// - /// Current state of session + /// 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 @@ -63,28 +64,28 @@ private Queue _processPendingEventsQueue // and processed /// - /// Timer to be used for key exchange + /// Timer to be used for key exchange. /// private Timer _keyExchangeTimer; /// - /// indicates that the client has previously completed the session key exchange + /// Indicates that the client has previously completed the session key exchange. /// private bool _keyExchanged = false; /// - /// this is to queue up a disconnect request when a key exchange is in process + /// This is to queue up a disconnect request when a key exchange is in process /// the session will be disconnect once the exchange is complete - /// intermediate disconnect requests are tracked by this flag + /// intermediate disconnect requests are tracked by this flag. /// private bool _pendingDisconnect = false; /// - /// processes events in the queue. If there are no + /// Processes events in the queue. If there are no /// more events to process, then sets eventsInProcess /// variable to false. This will ensure that another /// thread which raises an event can then take control - /// of processing the events + /// of processing the events. /// private void ProcessEvents() { @@ -99,6 +100,7 @@ private void ProcessEvents() _eventsInProcess = false; break; } + eventArgs = _processPendingEventsQueue.Dequeue(); } @@ -142,7 +144,7 @@ private void HandleFatalError(Exception ex) /// Raises the StateChanged events which are queued /// All StateChanged events will be raised once the /// processing of the State Machine events are - /// complete + /// complete. /// private void RaiseStateMachineEvents() { @@ -158,18 +160,18 @@ private void RaiseStateMachineEvents() /// /// Unique identifier for this state machine. Used - /// in tracing + /// in tracing. /// - private Guid _id; + private readonly Guid _id; /// /// Handler to be used in cases, where setting the state is the /// only task being performed. This method also asserts /// if the specified event is valid for the current state of - /// the state machine + /// the state machine. /// - /// sender of this event - /// event args + /// Sender of this event. + /// Event args. private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs eventArgs) { switch (eventArgs.StateEvent) @@ -180,6 +182,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e "State can be set to Established only when current state is NegotiationReceived"); SetState(RemoteSessionState.Established, null); } + break; case RemoteSessionEvent.NegotiationReceived: @@ -188,11 +191,12 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e "State can be set to NegotiationReceived only when RemoteSessionCapability is not null"); if (eventArgs.RemoteSessionCapability == null) { - throw PSTraceSource.NewArgumentException("eventArgs"); + throw PSTraceSource.NewArgumentException(nameof(eventArgs)); } SetState(RemoteSessionState.NegotiationReceived, null); } + break; case RemoteSessionEvent.NegotiationSendCompleted: @@ -202,6 +206,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e SetState(RemoteSessionState.NegotiationSent, null); } + break; case RemoteSessionEvent.ConnectFailed: @@ -211,18 +216,21 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e SetState(RemoteSessionState.ClosingConnection, eventArgs.Reason); } + break; case RemoteSessionEvent.CloseFailed: { SetState(RemoteSessionState.Closed, eventArgs.Reason); } + break; case RemoteSessionEvent.CloseCompleted: { SetState(RemoteSessionState.Closed, eventArgs.Reason); } + break; case RemoteSessionEvent.KeyRequested: @@ -235,6 +243,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e SetState(RemoteSessionState.EstablishedAndKeyRequested, eventArgs.Reason); } } + break; case RemoteSessionEvent.KeyReceived: @@ -245,22 +254,20 @@ 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); if (_pendingDisconnect) { - //session key exchange is complete, if there is a disconnect pending, process it now + // session key exchange is complete, if there is a disconnect pending, process it now _pendingDisconnect = false; DoDisconnect(sender, eventArgs); } } } + break; case RemoteSessionEvent.KeySent: @@ -268,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) @@ -279,6 +286,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e _keyExchangeTimer = new Timer(HandleKeyExchangeTimeout, null, BaseTransportManager.ClientDefaultOperationTimeoutMs, Timeout.Infinite); } } + break; case RemoteSessionEvent.DisconnectCompleted: { @@ -290,6 +298,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e SetState(RemoteSessionState.Disconnected, eventArgs.Reason); } } + break; case RemoteSessionEvent.DisconnectFailed: { @@ -298,9 +307,10 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e if (_state == RemoteSessionState.Disconnecting) { - SetState(RemoteSessionState.Disconnected, eventArgs.Reason); //set state to disconnected even TODO. Put some ETW event describing the disconnect process failure + SetState(RemoteSessionState.Disconnected, eventArgs.Reason); // set state to disconnected even TODO. Put some ETW event describing the disconnect process failure } } + break; case RemoteSessionEvent.ReconnectCompleted: { @@ -312,38 +322,36 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e SetState(RemoteSessionState.Established, eventArgs.Reason); } } + break; - } // switch... + } } /// - /// Handles the timeout for key exchange + /// Handles the timeout for key exchange. /// - /// sender of this event + /// Sender of this event. 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); RaiseEvent(new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeyReceiveFailed, exception)); - } // SetStateHandler + } /// /// Handler to be used in cases, where raising an event to /// the state needs to be performed. This method also /// asserts if the specified event is valid for - /// the current state of the state machine + /// the current state of the state machine. /// - /// sender of this event - /// event args + /// Sender of this event. + /// Event args. private void SetStateToClosedHandler(object sender, RemoteSessionStateMachineEventArgs eventArgs) { Dbg.Assert(_state == RemoteSessionState.NegotiationReceived && @@ -370,17 +378,17 @@ private void SetStateToClosedHandler(object sender, RemoteSessionStateMachineEve // raise an event to close the state machine RaiseEvent(new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.Close, eventArgs.Reason)); - } //SetStateToClosedHandler + } #region constructor /// - /// Creates an instance of ClientRemoteSessionDSHandlerStateMachine + /// Creates an instance of ClientRemoteSessionDSHandlerStateMachine. /// internal ClientRemoteSessionDSHandlerStateMachine() { _clientRemoteSessionStateChangeQueue = new Queue(); - //Initialize the state machine event handling matrix + // Initialize the state machine event handling matrix _stateMachineHandle = new EventHandler[(int)RemoteSessionState.MaxState, (int)RemoteSessionEvent.MaxEvent]; for (int i = 0; i < _stateMachineHandle.GetLength(0); i++) { @@ -419,7 +427,7 @@ internal ClientRemoteSessionDSHandlerStateMachine() _stateMachineHandle[(int)RemoteSessionState.Established, (int)RemoteSessionEvent.DisconnectStart] += DoDisconnect; _stateMachineHandle[(int)RemoteSessionState.Disconnecting, (int)RemoteSessionEvent.DisconnectCompleted] += SetStateHandler; - _stateMachineHandle[(int)RemoteSessionState.Disconnecting, (int)RemoteSessionEvent.DisconnectFailed] += SetStateHandler; //dont close + _stateMachineHandle[(int)RemoteSessionState.Disconnecting, (int)RemoteSessionEvent.DisconnectFailed] += SetStateHandler; // dont close _stateMachineHandle[(int)RemoteSessionState.Disconnected, (int)RemoteSessionEvent.ReconnectStart] += DoReconnect; _stateMachineHandle[(int)RemoteSessionState.Reconnecting, (int)RemoteSessionEvent.ReconnectCompleted] += SetStateHandler; _stateMachineHandle[(int)RemoteSessionState.Reconnecting, (int)RemoteSessionEvent.ReconnectFailed] += SetStateToClosedHandler; @@ -429,7 +437,7 @@ internal ClientRemoteSessionDSHandlerStateMachine() _stateMachineHandle[(int)RemoteSessionState.Established, (int)RemoteSessionEvent.RCDisconnectStarted] += DoRCDisconnectStarted; _stateMachineHandle[(int)RemoteSessionState.RCDisconnecting, (int)RemoteSessionEvent.DisconnectCompleted] += SetStateHandler; - //Disconnect during key exchange process + // Disconnect during key exchange process _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeySent, (int)RemoteSessionEvent.DisconnectStart] += DoDisconnectDuringKeyExchange; _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyRequested, (int)RemoteSessionEvent.DisconnectStart] += DoDisconnectDuringKeyExchange; @@ -441,9 +449,8 @@ internal ClientRemoteSessionDSHandlerStateMachine() _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeySent, (int)RemoteSessionEvent.KeyReceiveFailed] += SetStateToClosedHandler; // _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 + // TODO: All these are potential unexpected state transitions.. should have a way to track these calls.. + // 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++) @@ -478,7 +485,7 @@ internal bool CanByPassRaiseEvent(RemoteSessionStateMachineEventArgs arg) _state == RemoteSessionState.EstablishedAndKeyReceived || // TODO - Client session would never get into this state... to be removed _state == RemoteSessionState.EstablishedAndKeySent || _state == RemoteSessionState.Disconnecting || // There can be input data until disconnect has been completed - _state == RemoteSessionState.Disconnected) // Data can arrive while state machine is transitioning to disconnected, in a race. + _state == RemoteSessionState.Disconnected) // Data can arrive while state machine is transitioning to disconnected { return true; } @@ -498,7 +505,6 @@ internal bool CanByPassRaiseEvent(RemoteSessionStateMachineEventArgs arg) /// /// optional bool indicating whether to clear currently queued events /// - /// /// /// If the parameter is null. /// @@ -511,6 +517,7 @@ internal void RaiseEvent(RemoteSessionStateMachineEventArgs arg, bool clearQueue { _processPendingEventsQueue.Clear(); } + _processPendingEventsQueue.Enqueue(arg); if (!_eventsInProcess) @@ -535,7 +542,6 @@ internal void RaiseEvent(RemoteSessionStateMachineEventArgs arg, bool clearQueue /// /// The parameter contains the actual FSM event. /// - /// /// /// If the parameter is null. /// @@ -543,7 +549,7 @@ private void RaiseEventPrivate(RemoteSessionStateMachineEventArgs arg) { if (arg == null) { - throw PSTraceSource.NewArgumentNullException("arg"); + throw PSTraceSource.NewArgumentNullException(nameof(arg)); } EventHandler handler = _stateMachineHandle[(int)State, (int)arg.StateEvent]; @@ -585,7 +591,6 @@ internal RemoteSessionState State /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -610,7 +615,6 @@ private void DoCreateSession(object sender, RemoteSessionStateMachineEventArgs a } } - /// /// This is the handler for ConnectSession event of the FSM. This is the beginning of everything /// else. From this moment on, the FSM will proceeds step by step to eventually reach @@ -620,7 +624,6 @@ private void DoCreateSession(object sender, RemoteSessionStateMachineEventArgs a /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -633,8 +636,8 @@ private void DoConnectSession(object sender, RemoteSessionStateMachineEventArgs if (State == RemoteSessionState.Idle) { - //We need to send negotiation and connect algorithm related info - //Change state to let other DSHandlers add appropriate messages to be piggybacked on transport's Create payload + // We need to send negotiation and connect algorithm related info + // Change state to let other DSHandlers add appropriate messages to be piggybacked on transport's Create payload RemoteSessionStateMachineEventArgs sendingArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationSendingOnConnect); RaiseEvent(sendingArg); } @@ -650,7 +653,6 @@ private void DoConnectSession(object sender, RemoteSessionStateMachineEventArgs /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -672,7 +674,7 @@ private void DoNegotiationSending(object sender, RemoteSessionStateMachineEventA private void DoDisconnectDuringKeyExchange(object sender, RemoteSessionStateMachineEventArgs arg) { - //set flag to indicate Disconnect request queue up + // set flag to indicate Disconnect request queue up _pendingDisconnect = true; } @@ -702,7 +704,6 @@ private void DoRCDisconnectStarted(object sender, RemoteSessionStateMachineEvent /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -725,7 +726,7 @@ private void DoClose(object sender, RemoteSessionStateMachineEventArgs arg) case RemoteSessionState.Connecting: case RemoteSessionState.Connected: case RemoteSessionState.Established: - case RemoteSessionState.EstablishedAndKeyReceived: //TODO - Client session would never get into this state... to be removed + case RemoteSessionState.EstablishedAndKeyReceived: // TODO - Client session would never get into this state... to be removed case RemoteSessionState.EstablishedAndKeySent: case RemoteSessionState.NegotiationReceived: case RemoteSessionState.NegotiationSent: @@ -752,10 +753,10 @@ private void DoClose(object sender, RemoteSessionStateMachineEventArgs arg) /// /// Handles a fatal error message. Throws a well defined error message, /// which contains the reason for the fatal error as an inner exception. - /// This way the internal details are not surfaced to the user + /// This way the internal details are not surfaced to the user. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void DoFatal(object sender, RemoteSessionStateMachineEventArgs eventArgs) { PSRemotingDataStructureException fatalError = @@ -769,16 +770,16 @@ private void DoFatal(object sender, RemoteSessionStateMachineEventArgs eventArgs #endregion Event Handlers - private void CleanAll() + private static void CleanAll() { } /// /// Sets the state of the state machine. Since only /// one thread can be manipulating the state at a time - /// the state is not synchronized + /// the state is not synchronized. /// - /// new state of the state machine + /// New state of the state machine. /// reason why the state machine is set /// to the new state private void SetState(RemoteSessionState newState, Exception reason) diff --git a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs index 51207d42c28..cf476cec226 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. - -using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; -using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation.Internal; using System.Management.Automation.Remoting; -using Dbg = System.Management.Automation.Diagnostics; -using System.Threading; +using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; +using System.Threading; + +using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -21,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. @@ -43,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) { @@ -57,7 +56,7 @@ public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvai public RunspaceAvailability NewRunspaceAvailability; } - private bool _performNestedCheck = true; + private readonly bool _performNestedCheck = true; #endregion Private Members @@ -66,9 +65,9 @@ public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvai /// /// Private constructor that does most of the work constructing a remote pipeline object. /// - /// RemoteRunspace object - /// AddToHistory - /// IsNested + /// RemoteRunspace object. + /// AddToHistory. + /// IsNested. private RemotePipeline(RemoteRunspace runspace, bool addToHistory, bool isNested) : base(runspace) { @@ -79,7 +78,7 @@ private RemotePipeline(RemoteRunspace runspace, bool addToHistory, bool isNested _computerName = ((RemoteRunspace)_runspace).ConnectionInfo.ComputerName; _runspaceId = _runspace.InstanceId; - //Initialize streams + // Initialize streams _inputCollection = new PSDataCollection(); _inputCollection.ReleaseOnEnumeration = true; @@ -95,24 +94,24 @@ private RemotePipeline(RemoteRunspace runspace, bool addToHistory, bool isNested SetCommandCollection(_commands); - //Create event which will be signalled when pipeline execution - //is completed/failed/stoped. - //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 - //where Close is called after pipeline is added to list of - //running pipeline but before event is created. + // Create event which will be signalled when pipeline execution + // 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 + // where Close is called after pipeline is added to list of + // running pipeline but before event is created. PipelineFinishedEvent = new ManualResetEvent(false); } /// /// Constructs a remote pipeline for the specified runspace and - /// specified command + /// specified command. /// - /// runspace in which to create the pipeline - /// command as a string, to be used in pipeline creation - /// whether to add the command to the runspaces history - /// whether this pipeline is nested + /// Runspace in which to create the pipeline. + /// Command as a string, to be used in pipeline creation. + /// Whether to add the command to the runspaces history. + /// Whether this pipeline is nested. internal RemotePipeline(RemoteRunspace runspace, string command, bool addToHistory, bool isNested) : this(runspace, addToHistory, isNested) { @@ -127,8 +126,7 @@ internal RemotePipeline(RemoteRunspace runspace, string command, bool addToHisto _powershell.SetIsNested(isNested); - _powershell.InvocationStateChanged += - new EventHandler(HandleInvocationStateChanged); + _powershell.InvocationStateChanged += HandleInvocationStateChanged; } /// @@ -154,28 +152,32 @@ internal RemotePipeline(RemoteRunspace runspace) _powershell = new PowerShell(_connectCmdInfo, _inputStream, _outputStream, _errorStream, ((RemoteRunspace)_runspace).RunspacePool); - _powershell.InvocationStateChanged += - new EventHandler(HandleInvocationStateChanged); + _powershell.InvocationStateChanged += HandleInvocationStateChanged; } /// - /// Creates a cloned pipeline from the specified one + /// Creates a cloned pipeline from the specified one. /// - /// pipeline to clone from + /// 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; // NTRAID#Windows Out Of Band Releases-915851-2005/09/13 // the above comment copied from RemotePipelineBase which // originally copied it from PipelineBase - if (null == pipeline) + if (pipeline == null) { - throw PSTraceSource.NewArgumentNullException("pipeline"); + throw PSTraceSource.NewArgumentNullException(nameof(pipeline)); } + if (pipeline._disposed) { throw PSTraceSource.NewObjectDisposedException("pipeline"); @@ -193,7 +195,7 @@ private RemotePipeline(RemotePipeline pipeline) : } /// - /// override for creating a copy of pipeline + /// Override for creating a copy of pipeline. /// /// /// Pipeline object which is copy of this pipeline @@ -241,7 +243,7 @@ internal Runspace GetRunspace() } /// - /// Is this pipeline nested + /// Is this pipeline nested. /// public override bool IsNested { @@ -252,8 +254,8 @@ public override bool IsNested } /// - /// internal method to set the value of IsNested. This is called - /// by serializer + /// Internal method to set the value of IsNested. This is called + /// by serializer. /// internal void SetIsNested(bool isNested) { @@ -262,8 +264,8 @@ internal void SetIsNested(bool isNested) } /// - /// internal method to set the value of IsSteppable. This is called - /// during DoConcurrentCheck + /// Internal method to set the value of IsSteppable. This is called + /// during DoConcurrentCheck. /// internal void SetIsSteppable(bool isSteppable) { @@ -282,7 +284,7 @@ public override PipelineStateInfo PipelineStateInfo { lock (_syncRoot) { - //Note:We do not return internal state. + // Note:We do not return internal state. return _pipelineStateInfo.Clone(); } } @@ -327,7 +329,7 @@ public override PipelineReader Error } /// - /// String which is added in the history + /// String which is added in the history. /// /// This needs to be internal so that it can be replaced /// by invoke-cmd to place correct string in history. @@ -337,6 +339,7 @@ internal string HistoryString { return _historyString; } + set { _historyString = value; @@ -344,7 +347,7 @@ internal string HistoryString } /// - /// Whether the pipeline needs to be added to history of the runspace + /// Whether the pipeline needs to be added to history of the runspace. /// public bool AddToHistory { @@ -361,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 @@ -386,7 +389,7 @@ protected PSDataCollectionStream InputStream #region Invoke /// - /// Invoke the pipeline asynchronously + /// Invoke the pipeline asynchronously. /// /// /// Results are returned through the reader. @@ -414,7 +417,7 @@ internal override void InvokeAsyncAndDisconnect() /// /// an array of input objects to pass to the pipeline. /// Array may be empty but may not be null - /// An array of zero or more result objects + /// An array of zero or more result objects. /// Caller of synchronous exectute should not close /// input objectWriter. Synchronous invoke will always close the input /// objectWriter. @@ -428,6 +431,7 @@ public override Collection Invoke(System.Collections.IEnumerable input { this.InputStream.Close(); } + InitPowerShell(true); Collection results; @@ -525,7 +529,7 @@ public override void ConnectAsync() #region Stop /// - /// Stop the pipeline synchronously + /// Stop the pipeline synchronously. /// public override void Stop() { @@ -543,7 +547,7 @@ public override void Stop() catch (ObjectDisposedException) { throw PSTraceSource.NewObjectDisposedException("Pipeline"); - }; + } asyncresult.AsyncWaitHandle.WaitOne(); } @@ -576,7 +580,7 @@ public override void StopAsync() } /// - /// Verifies if the pipeline is in a state where it can be stopped + /// Verifies if the pipeline is in a state where it can be stopped. /// private bool CanStopPipeline(out bool isAlreadyStopping) { @@ -594,15 +598,15 @@ private bool CanStopPipeline(out bool isAlreadyStopping) returnResult = false; break; - //If pipeline execution has failed or completed or - //stoped, return silently. + // If pipeline execution has failed or completed or + // stopped, return silently. case PipelineState.Stopped: case PipelineState.Completed: case PipelineState.Failed: return false; - //If pipeline is in Stopping state, ignore the second - //stop. + // If pipeline is in Stopping state, ignore the second + // stop. case PipelineState.Stopping: isAlreadyStopping = true; return false; @@ -634,9 +638,9 @@ private bool CanStopPipeline(out bool isAlreadyStopping) #region Dispose /// - /// Disposes the pipeline + /// Disposes the pipeline. /// - /// true, when called on Dispose() + /// True, when called on Dispose(). protected override void Dispose(bool disposing) { try @@ -661,13 +665,14 @@ protected override void Dispose(bool disposing) // wait for the pipeline to stop..this will block // if the pipeline is already stopping. Stop(); - //_pipelineFinishedEvent.Close(); + // _pipelineFinishedEvent.Close(); if (_powershell != null) { _powershell.Dispose(); _powershell = null; } + _inputCollection.Dispose(); _inputStream.Dispose(); _outputCollection.Dispose(); @@ -717,7 +722,7 @@ private void HandleInvocationStateChanged(object sender, PSInvocationStateChange /// /// Sets the new execution state. /// - /// the new state + /// The new state. /// /// An exception indicating that state change is the result of an error, /// otherwise; null. @@ -748,6 +753,7 @@ private void SetPipelineState(PipelineState state, Exception reason) return; } } + break; case PipelineState.Stopping: { @@ -760,17 +766,19 @@ private void SetPipelineState(PipelineState state, Exception reason) copyState = PipelineState.Stopped; } } + break; } + _pipelineStateInfo = new PipelineStateInfo(copyState, reason); copyStateInfo = _pipelineStateInfo; - //Add _pipelineStateInfo to _executionEventQueue. - //RaisePipelineStateEvents will raise event for each item - //in this queue. - //Note:We are doing clone here instead of passing the member - //_pipelineStateInfo because we donot want outside - //to change pipeline state. + // Add _pipelineStateInfo to _executionEventQueue. + // RaisePipelineStateEvents will raise event for each item + // in this queue. + // Note:We are doing clone here instead of passing the member + // _pipelineStateInfo because we donot want outside + // to change pipeline state. RunspaceAvailability previousAvailability = _runspace.RunspaceAvailability; Guid? cmdInstanceId = (_powershell != null) ? _powershell.InstanceId : (Guid?)null; @@ -781,7 +789,7 @@ private void SetPipelineState(PipelineState state, Exception reason) _pipelineStateInfo.Clone(), previousAvailability, _runspace.RunspaceAvailability)); - } // lock... + } // using the copyStateInfo here as this piece of code is // outside of lock and _pipelineStateInfo might get changed @@ -817,9 +825,9 @@ protected void RaisePipelineStateEvents() } else { - //Clear the events if there are no EventHandlers. This - //ensures that events do not get called for state - //changes prior to their registration. + // Clear the events if there are no EventHandlers. This + // ensures that events do not get called for state + // changes prior to their registration. _executionEventQueue.Clear(); } } @@ -835,8 +843,8 @@ protected void RaisePipelineStateEvents() _runspace.RaiseAvailabilityChangedEvent(queueItem.NewRunspaceAvailability); } - //Exception raised in the eventhandler are not error in pipeline. - //silently ignore them. + // Exception raised in the eventhandler are not error in pipeline. + // silently ignore them. if (stateChanged != null) { try @@ -858,8 +866,8 @@ protected void RaisePipelineStateEvents() /// command will be immediately disconnected after it begins /// running. /// - /// true if called from a sync call - /// Invoke and Disconnect + /// True if called from a sync call. + /// Invoke and Disconnect. private void InitPowerShell(bool syncCall, bool invokeAndDisconnect = false) { if (_commands == null || _commands.Count == 0) @@ -888,8 +896,7 @@ private void InitPowerShell(bool syncCall, bool invokeAndDisconnect = false) _powershell.InitForRemotePipeline(_commands, _inputStream, _outputStream, _errorStream, settings, RedirectShellErrorOutputPipe); - _powershell.RemotePowerShell.HostCallReceived += - new EventHandler>(HandleHostCallReceived); + _powershell.RemotePowerShell.HostCallReceived += HandleHostCallReceived; } /// @@ -925,16 +932,15 @@ private void InitPowerShellForConnect(bool syncCall) _powershell.InitForRemotePipelineConnect(_inputStream, _outputStream, _errorStream, settings, RedirectShellErrorOutputPipe); - _powershell.RemotePowerShell.HostCallReceived += - new EventHandler>(HandleHostCallReceived); + _powershell.RemotePowerShell.HostCallReceived += HandleHostCallReceived; } } /// - /// Handle host call received + /// Handle host call received. /// - /// sender of this event, unused - /// arguments describing the host call to invoke + /// Sender of this event, unused. + /// Arguments describing the host call to invoke. private void HandleHostCallReceived(object sender, RemoteDataEventArgs eventArgs) { ClientMethodExecutor.Dispatch( @@ -948,13 +954,12 @@ private void HandleHostCallReceived(object sender, RemoteDataEventArgs - /// Does the cleanup necessary on pipeline completion + /// Does the cleanup necessary on pipeline completion. /// private void Cleanup() { - //Close the output stream if it is not closed. + // Close the output stream if it is not closed. if (_outputStream.IsOpen) { try @@ -967,7 +972,7 @@ private void Cleanup() } } - //Close the error stream if it is not closed. + // Close the error stream if it is not closed. if (_errorStream.IsOpen) { try @@ -980,7 +985,7 @@ private void Cleanup() } } - //Close the input stream if it is not closed. + // Close the input stream if it is not closed. if (_inputStream.IsOpen) { try @@ -995,9 +1000,9 @@ private void Cleanup() try { - //Runspace object maintains a list of pipelines in execution. - //Remove this pipeline from the list. This method also calls the - //pipeline finished event. + // Runspace object maintains a list of pipelines in execution. + // Remove this pipeline from the list. This method also calls the + // pipeline finished event. ((RemoteRunspace)_runspace).RemoveFromRunningPipelineList(this); PipelineFinishedEvent.Set(); @@ -1013,7 +1018,7 @@ private void Cleanup() /// /// ManualResetEvent which is signaled when pipeline execution is - /// completed/failed/stoped. + /// completed/failed/stopped. /// internal ManualResetEvent PipelineFinishedEvent { get; } @@ -1047,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 && @@ -1092,7 +1097,7 @@ internal void DoConcurrentCheck(bool syncCall) return; } - if (syncCall == false) + if (!syncCall) { throw PSTraceSource.NewInvalidOperationException( RunspaceStrings.NestedPipelineInvokeAsync); @@ -1112,7 +1117,7 @@ internal void DoConcurrentCheck(bool syncCall) /// /// The underlying powershell object on which this remote pipeline - /// is created + /// is created. /// internal PowerShell PowerShell { @@ -1123,9 +1128,9 @@ internal PowerShell PowerShell } /// - /// Sets the history string to the specified string + /// Sets the history string to the specified string. /// - /// new history string to set to + /// New history string to set to. internal override void SetHistoryString(string historyString) { _powershell.HistoryString = historyString; diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index a13b0a90e1e..8448007025f 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -1,22 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Runspaces; -using System.Management.Automation.Host; -using System.Management.Automation.Internal; -using System.Management.Automation.Tracing; -using Dbg = System.Management.Automation.Diagnostics; -using System.Threading; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Management.Automation.Remoting; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using Microsoft.PowerShell.Commands; -using System.Security.Principal; +using System.Management.Automation.Host; +using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; +using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; +using System.Management.Automation.Tracing; +#if !UNIX +using System.Security.Principal; +#endif +using System.Threading; +using Microsoft.PowerShell.Commands; + +using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -30,11 +32,11 @@ internal class RemoteRunspace : Runspace, IDisposable { #region Private Members - private ArrayList _runningPipelines = new ArrayList(); - 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; @@ -46,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. @@ -89,6 +91,7 @@ protected bool ByPassRunspaceStateCheck { return _bypassRunspaceStateCheck; } + set { _bypassRunspaceStateCheck = value; @@ -107,7 +110,7 @@ protected bool ByPassRunspaceStateCheck /// /// Construct a remote runspace based on the connection information - /// and the specified host + /// and the specified host. /// /// /// The TypeTable to use while deserializing/serializing remote objects. @@ -121,7 +124,7 @@ protected bool ByPassRunspaceStateCheck /// /// connection information which identifies /// the remote computer - /// host on the client + /// Host on the client. /// /// Friendly name for remote runspace session. /// Id for remote runspace. @@ -134,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); @@ -153,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); } @@ -167,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(); @@ -200,17 +203,11 @@ private void SetEventHandlers() _eventManager = new PSRemoteEventManager(_connectionInfo.ComputerName, this.InstanceId); - RunspacePool.StateChanged += - new EventHandler(HandleRunspacePoolStateChanged); - RunspacePool.RemoteRunspacePoolInternal.HostCallReceived += - new EventHandler>(HandleHostCallReceived); - RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported += - new EventHandler>(HandleURIDirectionReported); - RunspacePool.ForwardEvent += - new EventHandler(HandleRunspacePoolForwardEvent); - - RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted += - new EventHandler(HandleSessionCreateCompleted); + RunspacePool.StateChanged += HandleRunspacePoolStateChanged; + RunspacePool.RemoteRunspacePoolInternal.HostCallReceived += HandleHostCallReceived; + RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported += HandleURIDirectionReported; + RunspacePool.ForwardEvent += HandleRunspacePoolForwardEvent; + RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted += HandleSessionCreateCompleted; } #endregion Constructors @@ -218,17 +215,13 @@ private void SetEventHandlers() #region Properties /// - /// initialsessionstate information for this runspace + /// Initialsessionstate information for this runspace. /// public override InitialSessionState InitialSessionState { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } @@ -239,16 +232,12 @@ public override JobManager JobManager { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } /// - /// Return version of this runspace + /// Return version of this runspace. /// public override Version Version { get; } = PSVersionInfo.PSVersion; @@ -258,7 +247,7 @@ public override JobManager JobManager internal Version ServerVersion { get; private set; } /// - /// Retrieve information about current state of the runspace + /// Retrieve information about current state of the runspace. /// public override RunspaceStateInfo RunspaceStateInfo { @@ -266,14 +255,14 @@ public override RunspaceStateInfo RunspaceStateInfo { lock (_syncRoot) { - //Do not return internal state. + // Do not return internal state. return _runspaceStateInfo.Clone(); } } } /// - /// This property determines whether a new thread is create for each invocation + /// This property determines whether a new thread is create for each invocation. /// /// /// Any updates to the value of this property must be done before the Runspace is opened @@ -307,16 +296,19 @@ public override PSThreadOptions ThreadOptions } } } + private PSThreadOptions _createThreadOptions = PSThreadOptions.Default; /// - /// Gets the current availability of the Runspace + /// Gets the current availability of the Runspace. /// public override RunspaceAvailability RunspaceAvailability { get { return _runspaceAvailability; } + protected set { _runspaceAvailability = value; } } + private RunspaceAvailability _runspaceAvailability = RunspaceAvailability.None; /// @@ -330,7 +322,7 @@ public override RunspaceAvailability RunspaceAvailability public override event EventHandler AvailabilityChanged; /// - /// Returns true if there are any subscribers to the AvailabilityChanged event + /// Returns true if there are any subscribers to the AvailabilityChanged event. /// internal override bool HasAvailabilityChangedSubscribers { @@ -338,7 +330,7 @@ internal override bool HasAvailabilityChangedSubscribers } /// - /// Raises the AvailabilityChanged event + /// Raises the AvailabilityChanged event. /// protected override void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e) { @@ -357,7 +349,7 @@ protected override void OnAvailabilityChanged(RunspaceAvailabilityEventArgs e) } /// - /// Connection information to this runspace + /// Connection information to this runspace. /// public override RunspaceConnectionInfo ConnectionInfo { @@ -368,12 +360,12 @@ public override RunspaceConnectionInfo ConnectionInfo } /// - /// ConnectionInfo originally supplied by the user + /// ConnectionInfo originally supplied by the user. /// public override RunspaceConnectionInfo OriginalConnectionInfo { get; } /// - /// Gets the event manager + /// Gets the event manager. /// public override PSEventManager Events { @@ -388,7 +380,7 @@ public override PSEventManager Events #pragma warning disable 56503 /// - /// Gets the execution context for this runspace + /// Gets the execution context for this runspace. /// internal override ExecutionContext GetExecutionContext { @@ -399,7 +391,7 @@ internal override ExecutionContext GetExecutionContext } /// - /// Returns true if the internal host is in a nested prompt + /// Returns true if the internal host is in a nested prompt. /// internal override bool InNestedPrompt { @@ -413,7 +405,7 @@ internal override bool InNestedPrompt /// /// Gets the client remote session associated with this - /// runspace + /// runspace. /// /// This member is actually not required /// for the product code. However, there are @@ -466,6 +458,7 @@ internal ConnectCommandInfo RemoteCommand internal string PSSessionName { get { return RunspacePool.RemoteRunspacePoolInternal.Name; } + set { RunspacePool.RemoteRunspacePoolInternal.Name = value; } } @@ -491,7 +484,16 @@ internal bool CanConnect } /// - /// Debugger + /// This is used to indicate a special loopback remote session used for JEA restrictions. + /// + internal bool IsConfiguredLoopBack + { + get; + set; + } + + /// + /// Debugger. /// public override Debugger Debugger { @@ -538,9 +540,7 @@ public override void Open() try { RunspacePool.ThreadOptions = this.ThreadOptions; -#if !CORECLR // No ApartmentState In CoreCLR RunspacePool.ApartmentState = this.ApartmentState; -#endif RunspacePool.Open(); } catch (InvalidRunspacePoolStateException e) @@ -596,9 +596,9 @@ public override void Close() } /// - /// Dispose this runspace + /// Dispose this runspace. /// - /// true if called from Dispose + /// True if called from Dispose. protected override void Dispose(bool disposing) { try @@ -634,30 +634,21 @@ protected override void Dispose(bool disposing) // } - if (_remoteDebugger != null) - { - // Release RunspacePool event forwarding handlers. - _remoteDebugger.Dispose(); - } + // Release RunspacePool event forwarding handlers. + _remoteDebugger?.Dispose(); try { - RunspacePool.StateChanged -= - new EventHandler(HandleRunspacePoolStateChanged); - RunspacePool.RemoteRunspacePoolInternal.HostCallReceived -= - new EventHandler>(HandleHostCallReceived); - RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported -= - new EventHandler>(HandleURIDirectionReported); - RunspacePool.ForwardEvent -= - new EventHandler(HandleRunspacePoolForwardEvent); - - RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted -= - new EventHandler(HandleSessionCreateCompleted); + RunspacePool.StateChanged -= HandleRunspacePoolStateChanged; + RunspacePool.RemoteRunspacePoolInternal.HostCallReceived -= HandleHostCallReceived; + RunspacePool.RemoteRunspacePoolInternal.URIRedirectionReported -= HandleURIDirectionReported; + RunspacePool.ForwardEvent -= HandleRunspacePoolForwardEvent; + RunspacePool.RemoteRunspacePoolInternal.SessionCreateCompleted -= HandleSessionCreateCompleted; _eventManager = null; RunspacePool.Dispose(); - //_runspacePool = null; + // _runspacePool = null; } catch (InvalidRunspacePoolStateException e) { @@ -751,12 +742,12 @@ internal static Runspace[] GetRemoteRunspaces(RunspaceConnectionInfo connectionI /// Creates a single disconnected remote Runspace object based on connection information and /// session / command identifiers. /// - /// Connection object for target machine - /// Session Id to connect to - /// Optional command Id to connect to - /// Optional PSHost - /// Optional TypeTable - /// Disconnect remote Runspace object + /// Connection object for target machine. + /// Session Id to connect to. + /// Optional command Id to connect to. + /// Optional PSHost. + /// Optional TypeTable. + /// Disconnect remote Runspace object. internal static Runspace GetRemoteRunspace(RunspaceConnectionInfo connectionInfo, Guid sessionId, Guid? commandId, PSHost host, TypeTable typeTable) { RunspacePool runspacePool = RemoteRunspacePoolInternal.GetRemoteRunspacePool( @@ -934,7 +925,7 @@ public override PowerShell CreateDisconnectedPowerShell() /// /// Returns Runspace capabilities. /// - /// RunspaceCapability + /// RunspaceCapability. public override RunspaceCapability GetCapabilities() { RunspaceCapability returnCaps = RunspaceCapability.Default; @@ -944,6 +935,11 @@ public override RunspaceCapability GetCapabilities() returnCaps |= RunspaceCapability.SupportsDisconnect; } + if (_connectionInfo is WSManConnectionInfo) + { + return returnCaps; + } + if (_connectionInfo is NamedPipeConnectionInfo) { returnCaps |= RunspaceCapability.NamedPipeTransport; @@ -956,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; } @@ -1005,19 +1005,18 @@ private void UpdatePoolDisconnectOptions() #region CreatePipeline /// - /// Create an empty pipeline + /// Create an empty pipeline. /// - /// An empty pipeline + /// An empty pipeline. public override Pipeline CreatePipeline() { return CoreCreatePipeline(null, false, false); } - /// - /// Create a pipeline from a command string + /// Create a pipeline from a command string. /// - /// A valid command string + /// A valid command string. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -1028,7 +1027,7 @@ public override Pipeline CreatePipeline(string command) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, false, false); @@ -1037,8 +1036,8 @@ public override Pipeline CreatePipeline(string command) /// /// Create a pipeline from a command string. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -1049,7 +1048,7 @@ public override Pipeline CreatePipeline(string command, bool addToHistory) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, false); @@ -1073,8 +1072,8 @@ public override Pipeline CreateNestedPipeline() /// /// Creates a nested pipeline. /// - /// A valid command string - /// if true command is added to history + /// A valid command string. + /// If true command is added to history. /// /// A pipeline pre-filled with Commands specified in commandString. /// @@ -1084,7 +1083,7 @@ public override Pipeline CreateNestedPipeline(string command, bool addToHistory) { if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } return CoreCreatePipeline(command, addToHistory, true); @@ -1099,12 +1098,10 @@ public override Pipeline CreateNestedPipeline(string command, bool addToHistory) /// /// Pipeline to add to the /// list of pipelines in execution - /// /// /// Thrown if the runspace is not in the Opened state. /// . /// - /// /// Thrown if /// is null. /// @@ -1114,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. { @@ -1131,12 +1128,13 @@ internal void AddToRunningPipelineList(RemotePipeline pipeline) { e.Source = this.ConnectionInfo.ComputerName; } + throw e; } - //Add the pipeline to list of Executing pipeline. - //Note:_runningPipelines is always accessed with the lock so - //there is no need to create a synchronized version of list + // Add the pipeline to list of Executing pipeline. + // Note:_runningPipelines is always accessed with the lock so + // there is no need to create a synchronized version of list _runningPipelines.Add(pipeline); } } @@ -1146,7 +1144,6 @@ internal void AddToRunningPipelineList(RemotePipeline pipeline) /// /// Pipeline to remove from the /// list of pipelines in execution - /// /// /// Thrown if is null. /// @@ -1159,9 +1156,9 @@ internal void RemoveFromRunningPipelineList(RemotePipeline pipeline) Dbg.Assert(_runspaceStateInfo.State != RunspaceState.BeforeOpen, "Runspace should not be before open when pipeline is running"); - //Remove the pipeline to list of Executing pipeline. - //Note:_runningPipelines is always accessed with the lock so - //there is no need to create a synchronized version of list + // Remove the pipeline to list of Executing pipeline. + // Note:_runningPipelines is always accessed with the lock so + // there is no need to create a synchronized version of list _runningPipelines.Remove(pipeline); pipeline.PipelineFinishedEvent.Set(); } @@ -1169,25 +1166,25 @@ internal void RemoveFromRunningPipelineList(RemotePipeline pipeline) /// /// Check to see, if there is any other pipeline running in this - /// runspace. If not, then add this to the list of pipelines + /// runspace. If not, then add this to the list of pipelines. /// - /// pipeline to check and add + /// Pipeline to check and add. /// whether this is being called from /// a synchronous method call internal void DoConcurrentCheckAndAddToRunningPipelines(RemotePipeline pipeline, bool syncCall) { - //Concurrency check should be done under runspace lock + // Concurrency check should be done under runspace lock lock (_syncRoot) { - if (_bSessionStateProxyCallInProgress == true) + if (_bSessionStateProxyCallInProgress) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.NoPipelineWhenSessionStateProxyInProgress); } - //Delegate to pipeline to do check if it is fine to invoke if another - //pipeline is running. + // Delegate to pipeline to do check if it is fine to invoke if another + // pipeline is running. pipeline.DoConcurrentCheck(syncCall); - //Finally add to the list of running pipelines. + // Finally add to the list of running pipelines. AddToRunningPipelineList(pipeline); } } @@ -1202,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; @@ -1240,8 +1237,10 @@ private void HandleRunspacePoolStateChanged(object sender, RunspacePoolStateChan _applicationPrivateData = GetApplicationPrivateData(); SetDebugInfo(_applicationPrivateData); } + break; } + break; case RunspaceState.Disconnected: @@ -1352,13 +1351,13 @@ private bool SetDebugInfo(PSPrimitiveDictionary psApplicationPrivateData) } /// - /// Asserts if the current state of the runspace is BeforeOpen + /// Asserts if the current state of the runspace is BeforeOpen. /// private void AssertIfStateIsBeforeOpen() { lock (_syncRoot) { - //Call fails if RunspaceState is not BeforeOpen. + // Call fails if RunspaceState is not BeforeOpen. if (_runspaceStateInfo.State != RunspaceState.BeforeOpen) { InvalidRunspaceStateException e = @@ -1378,11 +1377,11 @@ private void AssertIfStateIsBeforeOpen() /// /// Set the new runspace state. /// - /// the new state + /// The new state. /// An exception indicating the state change is the /// result of an error, otherwise; null. /// - /// Previous runspace state + /// Previous runspace state. /// /// Sets the internal runspace state information member variable. It also /// adds RunspaceStateInfo to a queue. @@ -1399,12 +1398,12 @@ private RunspaceState SetRunspaceState(RunspaceState state, Exception reason) { _runspaceStateInfo = new RunspaceStateInfo(state, reason); - //Add _runspaceStateInfo to _runspaceEventQueue. - //RaiseRunspaceStateEvents will raise event for each item - //in this queue. - //Note:We are doing clone here instead of passing the member - //_runspaceStateInfo because we donot want outside - //to change our runspace state. + // Add _runspaceStateInfo to _runspaceEventQueue. + // RaiseRunspaceStateEvents will raise event for each item + // in this queue. + // Note:We are doing clone here instead of passing the member + // _runspaceStateInfo because we donot want outside + // to change our runspace state. RunspaceAvailability previousAvailability = _runspaceAvailability; this.UpdateRunspaceAvailability(_runspaceStateInfo.State, false); @@ -1445,9 +1444,9 @@ private void RaiseRunspaceStateEvents() } else { - //Clear the events if there are no EventHandlers. This - //ensures that events do not get called for state - //changes prior to their registration. + // Clear the events if there are no EventHandlers. This + // ensures that events do not get called for state + // changes prior to their registration. _runspaceEventQueue.Clear(); } } @@ -1463,8 +1462,8 @@ private void RaiseRunspaceStateEvents() this.OnAvailabilityChanged(new RunspaceAvailabilityEventArgs(queueItem.NewRunspaceAvailability)); } - //Exception raised by events are not error condition for runspace - //object. + // Exception raised by events are not error condition for runspace + // object. if (stateChanged != null) { try @@ -1480,7 +1479,7 @@ private void RaiseRunspaceStateEvents() } /// - /// Creates a pipeline + /// Creates a pipeline. /// /// /// @@ -1498,18 +1497,18 @@ private Pipeline CoreCreatePipeline(string command, bool addToHistory, bool isNe [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Finishof")] private bool WaitForFinishofPipelines() { - //Take a snapshot of list of active pipelines. - //Note:Before we enter to this CloseHelper routine - //CoreClose has already set the state of Runspace - //to closing. So no new pipelines can be executed on this - //runspace and so no new pipelines will be added to - //_runningPipelines. However we still need to lock because - //running pipelines can be removed from this. + // Take a snapshot of list of active pipelines. + // Note:Before we enter to this CloseHelper routine + // CoreClose has already set the state of Runspace + // to closing. So no new pipelines can be executed on this + // runspace and so no new pipelines will be added to + // _runningPipelines. However we still need to lock because + // running pipelines can be removed from this. RemotePipeline[] runningPipelines; lock (_syncRoot) { - runningPipelines = (RemotePipeline[])_runningPipelines.ToArray(typeof(RemotePipeline)); + runningPipelines = _runningPipelines.ToArray(); } if (runningPipelines.Length > 0) @@ -1549,9 +1548,9 @@ internal override Pipeline GetCurrentlyRunningPipeline() } /// - /// Handles any host calls received from the server + /// Handles any host calls received from the server. /// - /// sender of this information, unused + /// Sender of this information, unused. /// arguments describing this event, contains /// a RemoteHostCall object private void HandleHostCallReceived(object sender, RemoteDataEventArgs eventArgs) @@ -1576,7 +1575,7 @@ private void HandleHostCallReceived(object sender, RemoteDataEventArgs eventArgs) { WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo; - if (null != wsmanConnectionInfo) + if (wsmanConnectionInfo != null) { // change the runspace's uri to the new URI. wsmanConnectionInfo.ConnectionUri = eventArgs.Data; @@ -1585,7 +1584,7 @@ private void HandleURIDirectionReported(object sender, RemoteDataEventArgs } /// - /// Forward the events from the runspace pool to the current instance + /// Forward the events from the runspace pool to the current instance. /// private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e) { @@ -1646,13 +1645,13 @@ private void UpdateDisconnectExpiresOn() /// /// Determines if another Invoke-Command is executing /// in this runspace in the currently running local pipeline - /// ahead on the specified invoke-command + /// ahead on the specified invoke-command. /// /// current invoke-command /// instance - /// local pipeline id - /// true, if another invoke-command is running - /// before, false otherwise + /// Local pipeline id. + /// True, if another invoke-command is running + /// before, false otherwise. internal bool IsAnotherInvokeCommandExecuting(InvokeCommandCommand invokeCommand, long localPipelineId) { @@ -1695,11 +1694,11 @@ internal bool IsAnotherInvokeCommandExecuting(InvokeCommandCommand invokeCommand /// /// Keeps track of the current invoke command executing - /// within the current local pipeline + /// within the current local pipeline. /// /// reference to invoke command /// which is currently being processed - /// the local pipeline id + /// The local pipeline id. internal void SetCurrentInvokeCommand(InvokeCommandCommand invokeCommand, long localPipelineId) { @@ -1712,7 +1711,7 @@ internal void SetCurrentInvokeCommand(InvokeCommandCommand invokeCommand, /// /// Clears the current invoke-command reference stored within - /// this remote runspace + /// this remote runspace. /// internal void ClearInvokeCommand() { @@ -1730,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 @@ -1741,12 +1737,12 @@ internal void AbortOpen() #region Misc Properties / Events /// - /// The runspace pool that this remote runspace wraps + /// The runspace pool that this remote runspace wraps. /// internal RunspacePool RunspacePool { get; } /// - /// EventHandler used to report connection URI redirections to the application + /// EventHandler used to report connection URI redirections to the application. /// internal event EventHandler> URIRedirectionReported; @@ -1785,34 +1781,37 @@ internal override void SetApplicationPrivateData(PSPrimitiveDictionary applicati #region Remote Debugger /// - /// RemoteDebugger + /// RemoteDebugger. /// internal sealed class RemoteDebugger : Debugger, IDisposable { #region Members - private RemoteRunspace _runspace; + private readonly RemoteRunspace _runspace; private PowerShell _psDebuggerCommand; private bool _remoteDebugSupported; private bool _isActive; private int _breakpointCount; - private Version _serverPSVersion; + private RemoteDebuggingCapability _remoteDebuggingCapability; + private bool? _remoteBreakpointManagementIsSupported; private volatile bool _handleDebuggerStop; private bool _isDebuggerSteppingEnabled; private UnhandledBreakpointProcessingMode _unhandledBreakpointMode; private bool _detachCommand; - // Impersonation flow +#if !UNIX + // Windows impersonation flow private WindowsIdentity _identityToPersonate; private bool _identityPersonationChecked; +#endif /// - /// RemoteDebuggerStopEvent + /// RemoteDebuggerStopEvent. /// public const string RemoteDebuggerStopEvent = "PSInternalRemoteDebuggerStopEvent"; /// - /// RemoteDebuggerBreakpointUpdatedEvent + /// RemoteDebuggerBreakpointUpdatedEvent. /// public const string RemoteDebuggerBreakpointUpdatedEvent = "PSInternalRemoteDebuggerBreakpointUpdatedEvent"; @@ -1830,15 +1829,16 @@ internal sealed class RemoteDebugger : Debugger, IDisposable private RemoteDebugger() { } /// - /// Constructor + /// Constructor. /// - /// Associated remote runspace + /// Associated remote runspace. public RemoteDebugger(RemoteRunspace runspace) { if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } + _runspace = runspace; _unhandledBreakpointMode = UnhandledBreakpointProcessingMode.Ignore; @@ -1855,9 +1855,9 @@ public RemoteDebugger(RemoteRunspace runspace) /// /// Process debugger command. /// - /// Debugger PSCommand - /// Output - /// DebuggerCommandResults + /// Debugger PSCommand. + /// Output. + /// DebuggerCommandResults. public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { CheckForValidateState(); @@ -1865,12 +1865,12 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC if (command == null) { - throw new PSArgumentNullException("command"); + throw new PSArgumentNullException(nameof(command)); } if (output == null) { - throw new PSArgumentNullException("output"); + throw new PSArgumentNullException(nameof(output)); } if (!DebuggerStopped) @@ -1931,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); @@ -1975,7 +1975,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC } /// - /// StopProcessCommand + /// StopProcessCommand. /// public override void StopProcessCommand() { @@ -1990,9 +1990,268 @@ public override void StopProcessCommand() } /// - /// SetDebuggerAction + /// Adds the provided set of breakpoints to the debugger. + /// + /// Breakpoints to set. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + var functionParameters = new Dictionary + { + { "BreakpointList", breakpoints }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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. + /// The breakpoint with the specified id. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); + + var functionParameters = new Dictionary + { + { "Id", id }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.GetBreakpoint, functionParameters); + } + + /// + /// Returns breakpoints primarily for the Get-PSBreakpoint cmdlet. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A list of breakpoints in a runspace. + public override List GetBreakpoints(int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.GetBreakpoint); + + CheckForValidateState(); + + var breakpoints = new List(); + + using (PowerShell ps = GetNestedPowerShell()) + { + ps.AddCommand(RemoteDebuggingCommands.GetBreakpoint); + + if (runspaceId.HasValue) + { + ps.AddParameter("RunspaceId", runspaceId.Value); + } + + Collection output = ps.Invoke(); + foreach (var item in output) + { + if (item?.BaseObject is Breakpoint bp) + { + breakpoints.Add(bp); + } + else if (TryGetRemoteDebuggerException(item, out Exception ex)) + { + throw ex; + } + } + } + + return breakpoints; + } + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The command breakpoint that was set. + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + Breakpoint breakpoint = new CommandBreakpoint(path, null, command, action); + var functionParameters = new Dictionary + { + { "Breakpoint", breakpoint }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + Breakpoint breakpoint = new LineBreakpoint(path, line, column, action); + + var functionParameters = new Dictionary + { + { "Breakpoint", breakpoint }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.SetBreakpoint); + + Breakpoint breakpoint = new VariableBreakpoint(path, variableName, accessMode, action); + + var functionParameters = new Dictionary + { + { "Breakpoint", breakpoint }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.SetBreakpoint, functionParameters); + } + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.RemoveBreakpoint); + + if (breakpoint == null) + { + return false; + } + + var functionParameters = new Dictionary + { + { "Id", breakpoint.Id }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.RemoveBreakpoint, functionParameters); + } + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.EnableBreakpoint); + + if (breakpoint == null) + { + return null; + } + + var functionParameters = new Dictionary + { + { "Id", breakpoint.Id }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.EnableBreakpoint, functionParameters); + } + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) + { + // This is supported only for PowerShell versions >= 7.0 + CheckRemoteBreakpointManagementSupport(RemoteDebuggingCommands.DisableBreakpoint); + + if (breakpoint == null) + { + return null; + } + + var functionParameters = new Dictionary + { + { "Id", breakpoint.Id }, + }; + + if (runspaceId.HasValue) + { + functionParameters.Add("RunspaceId", runspaceId.Value); + } + + return InvokeRemoteBreakpointFunction(RemoteDebuggingCommands.DisableBreakpoint, functionParameters); + } + + /// + /// SetDebuggerAction. /// - /// DebuggerResumeAction + /// DebuggerResumeAction. public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { CheckForValidateState(); @@ -2001,7 +2260,7 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetDebuggerActionFunctionName).AddParameter("ResumeAction", resumeAction); + ps.AddCommand(RemoteDebuggingCommands.SetDebuggerAction).AddParameter("ResumeAction", resumeAction); ps.Invoke(); // If an error exception is returned then throw it here. @@ -2014,9 +2273,9 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) } /// - /// GetDebuggerStopped + /// GetDebuggerStopped. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public override DebuggerStopEventArgs GetDebuggerStopArgs() { CheckForValidateState(); @@ -2027,11 +2286,12 @@ public override DebuggerStopEventArgs GetDebuggerStopArgs() { using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.GetDebuggerStopArgsFunctionName); + ps.AddCommand(RemoteDebuggingCommands.GetDebuggerStopArgs); Collection output = ps.Invoke(); foreach (var item in output) { if (item == null) { continue; } + rtnArgs = item.BaseObject as DebuggerStopEventArgs; if (rtnArgs != null) { break; } } @@ -2045,7 +2305,7 @@ public override DebuggerStopEventArgs GetDebuggerStopArgs() } /// - /// SetDebugMode + /// SetDebugMode. /// /// public override void SetDebugMode(DebugModes mode) @@ -2063,7 +2323,7 @@ public override void SetDebugMode(DebugModes mode) using (PowerShell ps = GetNestedPowerShell()) { ps.SetIsNested(false); - ps.AddCommand(DebuggerUtils.SetDebugModeFunctionName).AddParameter("Mode", mode); + ps.AddCommand(RemoteDebuggingCommands.SetDebugMode).AddParameter("Mode", mode); ps.Invoke(); } @@ -2075,14 +2335,13 @@ public override void SetDebugMode(DebugModes mode) /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. public override void SetDebuggerStepMode(bool enabled) { CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 - if ((_serverPSVersion == null) || - (_serverPSVersion.Major < PSVersionInfo.PSV5Version.Major)) + if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetDebuggerStepMode)) { return; } @@ -2095,7 +2354,7 @@ public override void SetDebuggerStepMode(bool enabled) // Send Enable-DebuggerStepping virtual command. using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetDebuggerStepMode).AddParameter("Enabled", enabled); + ps.AddCommand(RemoteDebuggingCommands.SetDebuggerStepMode).AddParameter("Enabled", enabled); ps.Invoke(); _isDebuggerSteppingEnabled = enabled; } @@ -2126,7 +2385,7 @@ public override bool InBreakpoint } /// - /// InternalProcessCommand + /// InternalProcessCommand. /// /// /// @@ -2137,7 +2396,7 @@ internal override DebuggerCommand InternalProcessCommand(string command, IList

- /// IsRemote + /// IsRemote. /// internal override bool IsRemote { @@ -2157,13 +2416,13 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode { return _unhandledBreakpointMode; } + set { CheckForValidateState(); // This is supported only for PowerShell versions >= 5.0 - if ((_serverPSVersion == null) || - (_serverPSVersion < PSVersionInfo.PSV5Version)) + if (!_remoteDebuggingCapability.IsCommandSupported(RemoteDebuggingCommands.SetUnhandledBreakpointMode)) { return; } @@ -2173,7 +2432,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode // Send Set-PSUnhandledBreakpointMode virtual command. using (PowerShell ps = GetNestedPowerShell()) { - ps.AddCommand(DebuggerUtils.SetPSUnhandledBreakpointMode).AddParameter("UnhandledBreakpointMode", value); + ps.AddCommand(RemoteDebuggingCommands.SetUnhandledBreakpointMode).AddParameter("UnhandledBreakpointMode", value); ps.Invoke(); } @@ -2186,18 +2445,20 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode #region IDisposable ///

- /// Dispose + /// Dispose. /// public void Dispose() { _runspace.RemoteDebuggerStop -= HandleForwardedDebuggerStopEvent; _runspace.RemoteDebuggerBreakpointUpdated -= HandleForwardedDebuggerBreakpointUpdatedEvent; +#if !UNIX if (_identityToPersonate != null) { _identityToPersonate.Dispose(); _identityToPersonate = null; } +#endif } #endregion @@ -2205,11 +2466,11 @@ public void Dispose() #region Internal Methods /// - /// Internal method that checks the debug state of - /// the remote session and raises the DebuggerStop event - /// if debugger is in stopped state. - /// This is used internally to help clients get back to - /// debug state when reconnecting to remote session in debug state. + /// Internal method that checks the debug state of + /// the remote session and raises the DebuggerStop event + /// if debugger is in stopped state. + /// This is used internally to help clients get back to + /// debug state when reconnecting to remote session in debug state. /// internal void CheckStateAndRaiseStopEvent() { @@ -2221,23 +2482,23 @@ internal void CheckStateAndRaiseStopEvent() } /// - /// IsRemoteDebug + /// IsRemoteDebug. /// internal bool IsRemoteDebug { - private set; get; + private set; } /// /// Sets client debug info state based on server info. /// - /// Debug mode - /// Currently in breakpoint - /// Breakpoint count - /// Break All setting - /// UnhandledBreakpointMode - /// Server PowerShell version + /// Debug mode. + /// Currently in breakpoint. + /// Breakpoint count. + /// Break All setting. + /// UnhandledBreakpointMode. + /// Server PowerShell version. internal void SetClientDebugInfo( DebugModes? debugMode, bool inBreakpoint, @@ -2261,7 +2522,7 @@ internal void SetClientDebugInfo( SetRemoteDebug(true, RunspaceAvailability.RemoteDebug); } - _serverPSVersion = serverPSVersion; + _remoteDebuggingCapability = RemoteDebuggingCapability.CreateDebuggingCapability(serverPSVersion); _breakpointCount = breakpointCount; _isDebuggerSteppingEnabled = breakAll; @@ -2321,7 +2582,7 @@ internal void SendBreakpointUpdatedEvents() } /// - /// IsDebuggerSteppingEnabled + /// IsDebuggerSteppingEnabled. /// internal override bool IsDebuggerSteppingEnabled { @@ -2332,6 +2593,38 @@ internal override bool IsDebuggerSteppingEnabled #region Private Methods + private static bool TryGetRemoteDebuggerException( + PSObject item, + out Exception exception) + { + exception = null; + if (item == null) + { + return false; + } + + bool haveExceptionType = false; + foreach (var typeName in item.TypeNames) + { + if (typeName.Equals("Deserialized.System.Exception")) + { + haveExceptionType = true; + break; + } + } + + if (haveExceptionType) + { + var errorMessage = item.Properties["Message"]?.Value ?? string.Empty; + exception = new RemoteException( + StringUtil.Format( + RemotingErrorIdStrings.RemoteDebuggerError, item.TypeNames[0], errorMessage)); + return true; + } + + return false; + } + // // Event handlers // @@ -2362,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)) @@ -2374,16 +2667,17 @@ private void ProcessDebuggerStopEvent(DebuggerStopEventArgs args) if (!invokedOnBlockedThread) { -#if CORECLR - Threading.ThreadPool.QueueUserWorkItem( - ProcessDebuggerStopEventProc, - args); -#else // Otherwise run on worker thread. +#if !UNIX Utils.QueueWorkItemWithImpersonation( _identityToPersonate, ProcessDebuggerStopEventProc, args); +#else + Threading.ThreadPool.QueueUserWorkItem( + ProcessDebuggerStopEventProc, + args); + #endif } } @@ -2486,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 Windows 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, @@ -2499,19 +2793,15 @@ private void CheckForValidateState() throw new InvalidRunspaceStateException(); } +#if !UNIX if (!_identityPersonationChecked) { _identityPersonationChecked = true; // Save identity to impersonate. - WindowsIdentity currentIdentity = null; - try - { - currentIdentity = WindowsIdentity.GetCurrent(); - } - catch (System.Security.SecurityException) { } - _identityToPersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? currentIdentity : null; + Utils.TryGetWindowsImpersonatedIdentity(out _identityToPersonate); } +#endif } private void SetRemoteDebug(bool remoteDebug, RunspaceAvailability? availability) @@ -2557,6 +2847,7 @@ private void SetIsActive(int breakpointCount) { // Debugger is always inactive if RemoteScript is not selected. if (_isActive) { _isActive = false; } + return; } @@ -2570,6 +2861,61 @@ private void SetIsActive(int breakpointCount) } } + private T InvokeRemoteBreakpointFunction(string functionName, Dictionary parameters) + { + CheckForValidateState(); + + using (PowerShell ps = GetNestedPowerShell()) + { + ps.AddCommand(functionName); + foreach (var parameterName in parameters.Keys) + { + ps.AddParameter(parameterName, parameters[parameterName]); + } + + Collection output = ps.Invoke(); + + // If an error exception is returned then throw it here. + if (ps.ErrorBuffer.Count > 0) + { + Exception e = ps.ErrorBuffer[0].Exception; + if (e != null) + { + throw e; + } + } + + // This helper is only used to return a single output item of type T. + foreach (var item in output) + { + if (item?.BaseObject is T) + { + return (T)item.BaseObject; + } + + if (TryGetRemoteDebuggerException(item, out Exception ex)) + { + throw ex; + } + } + + return default(T); + } + } + + private void CheckRemoteBreakpointManagementSupport(string breakpointCommandNameToCheck) + { + _remoteBreakpointManagementIsSupported ??= _remoteDebuggingCapability.IsCommandSupported(breakpointCommandNameToCheck); + + if (!_remoteBreakpointManagementIsSupported.Value) + { + throw new PSNotSupportedException( + StringUtil.Format( + DebuggerStrings.CommandNotSupportedForRemoteUseInServerDebugger, + RemoteDebuggingCommands.CleanCommandName(breakpointCommandNameToCheck))); + } + } + #endregion } @@ -2579,7 +2925,8 @@ private void SetIsActive(int breakpointCount) internal class RemoteSessionStateProxy : SessionStateProxy { - private RemoteRunspace _runspace; + private readonly RemoteRunspace _runspace; + internal RemoteSessionStateProxy(RemoteRunspace runspace) { Dbg.Assert(runspace != null, "Caller should validate the parameter"); @@ -2593,23 +2940,18 @@ internal RemoteSessionStateProxy(RemoteRunspace runspace) /// /// Set a variable in session state. /// - /// /// /// The name of the item to set. /// - /// /// /// The new value of the item being set. /// - /// /// /// name is null /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2617,7 +2959,7 @@ public override void SetVariable(string name, object value) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } // Verify the runspace has the Set-Variable command. For performance, throw if we got an error @@ -2639,13 +2981,14 @@ public override void SetVariable(string name, object value) } catch (RemoteException e) { - if (String.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) { _setVariableCommandNotFoundException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _setVariableCommandNotFoundException; } else throw; } + if (remotePipeline.Error.Count > 0) { // Don't cache these errors, as they are related to the actual variable being set. @@ -2657,23 +3000,18 @@ public override void SetVariable(string name, object value) /// /// Get a variable out of session state. /// - /// /// /// name of variable to look up /// - /// /// /// The value of the specified variable. /// - /// /// /// name is null /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2681,7 +3019,7 @@ public override object GetVariable(string name) { if (name == null) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } // Verify the runspace has the Get-Variable command. For performance, throw if we got an error @@ -2703,13 +3041,14 @@ public override object GetVariable(string name) } catch (RemoteException e) { - if (String.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("CommandNotFoundException", e.ErrorRecord.FullyQualifiedErrorId, StringComparison.OrdinalIgnoreCase)) { _getVariableCommandNotFoundException = new PSNotSupportedException(RunspaceStrings.NotSupportedOnRestrictedRunspace, e); throw _getVariableCommandNotFoundException; } else throw; } + if (remotePipeline.Error.Count > 0) { // Don't cache these errors, as they are related to the actual variable being set. @@ -2733,11 +3072,9 @@ public override object GetVariable(string name) /// /// Get the list of applications out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2780,11 +3117,9 @@ public override List Applications /// /// Get the list of scripts out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2825,13 +3160,11 @@ public override List Scripts } /// - /// Get the APIs to access drives out of session state + /// Get the APIs to access drives out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2846,11 +3179,9 @@ public override DriveManagementIntrinsics Drive /// /// Get/Set the language mode out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2886,6 +3217,7 @@ public override PSLanguageMode LanguageMode return (PSLanguageMode)LanguagePrimitives.ConvertTo(result[0], typeof(PSLanguageMode), CultureInfo.InvariantCulture); } + set { throw new PSNotSupportedException(); @@ -2895,11 +3227,9 @@ public override PSLanguageMode LanguageMode /// /// Get the module info out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2914,11 +3244,9 @@ public override PSModuleInfo Module /// /// Get the APIs to access paths and locations out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2933,11 +3261,9 @@ public override PathIntrinsics Path /// /// Get the APIs to access a provider out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2952,11 +3278,9 @@ public override CmdletProviderManagementIntrinsics Provider /// /// Get the APIs to access variables out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2971,11 +3295,9 @@ public override PSVariableIntrinsics PSVariable /// /// Get the APIs to build script blocks and execute script out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// @@ -2990,11 +3312,9 @@ public override CommandInvocationIntrinsics InvokeCommand /// /// Gets the instance of the provider interface APIs out of session state. /// - /// /// /// Runspace is not open. /// - /// /// /// Another SessionStateProxy call or another pipeline is in progress. /// diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs index 98089ff1ebc..c1b34691104 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs @@ -1,24 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Runspaces { /// - /// Computer target type + /// Computer target type. /// public enum TargetMachineType { /// - /// Target is a machine with which the session is based on networking + /// Target is a machine with which the session is based on networking. /// RemoteMachine, /// - /// Target is a virtual machine with which the session is based on Hyper-V socket + /// Target is a virtual machine with which the session is based on Hyper-V socket. /// VirtualMachine, @@ -44,9 +44,10 @@ public sealed class PSSession #region Private Members private RemoteRunspace _remoteRunspace; + private string _transportName; /// - /// Static variable which is incremented to generate id + /// Static variable which is incremented to generate id. /// private static int s_seed = 0; @@ -55,14 +56,14 @@ public sealed class PSSession #region Public Properties /// - /// Type of the computer target + /// Type of the computer target. /// public TargetMachineType ComputerType { get; set; } /// - /// Name of the computer target + /// Name of the computer target. /// - public String ComputerName + public string ComputerName { get { @@ -71,9 +72,9 @@ public String ComputerName } /// - /// Id of the container target + /// Id of the container target. /// - public String ContainerId + public string ContainerId { get { @@ -90,9 +91,9 @@ public String ContainerId } /// - /// Name of the virtual machine target + /// Name of the virtual machine target. /// - public String VMName + public string VMName { get { @@ -108,7 +109,7 @@ public String VMName } /// - /// Guid of the virtual machine target + /// Guid of the virtual machine target. /// public Guid? VMId { @@ -127,12 +128,12 @@ public Guid? VMId } /// - /// Shell which is executed in the remote machine + /// Shell which is executed in the remote machine. /// - public String ConfigurationName { get; } + public string ConfigurationName { get; } /// - /// InstanceID that identifies this runspace + /// InstanceID that identifies this runspace. /// public Guid InstanceId { @@ -144,18 +145,18 @@ public Guid InstanceId /// /// SessionId of this runspace. This is unique only across - /// a session + /// a session. /// public int Id { get; } /// - /// Friendly name for identifying this runspace + /// Friendly name for identifying this runspace. /// - public String Name { get; set; } + public string Name { get; set; } /// /// Indicates whether the specified runspace is available - /// for executing commands + /// for executing commands. /// public RunspaceAvailability Availability { @@ -179,7 +180,7 @@ public PSPrimitiveDictionary ApplicationPrivateData /// /// The remote runspace object based on which this information object - /// is derived + /// is derived. /// /// This property is marked internal to allow other cmdlets /// to get access to the RemoteRunspace object and operate on it like @@ -195,20 +196,20 @@ public Runspace Runspace /// /// Name of the transport used. /// - public String Transport => GetTransportName(); + public string Transport => GetTransportName(); #endregion Public Properties #region Public Methods /// - /// ToString method override + /// ToString method override. /// - /// string + /// String. 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); } @@ -222,7 +223,7 @@ public override string ToString() /// new runspace is a reconstructed runspace having the same Guid /// as the existing runspace. /// - /// Runspace to insert + /// Runspace to insert. /// Boolean indicating if runspace was inserted. internal bool InsertRunspace(RemoteRunspace remoteRunspace) { @@ -242,7 +243,7 @@ internal bool InsertRunspace(RemoteRunspace remoteRunspace) /// /// This constructor will be used to created a remote runspace info - /// object with a auto generated name + /// object with a auto generated name. /// /// Remote runspace object for which /// the info object need to be created @@ -272,48 +273,40 @@ internal PSSession(RemoteRunspace remoteRunspace) remoteRunspace.PSSessionName = Name; } - // WSMan session - if (remoteRunspace.ConnectionInfo is WSManConnectionInfo) - { - ComputerType = TargetMachineType.RemoteMachine; - - string fullShellName = WSManConnectionInfo.ExtractPropertyAsWsManConnectionInfo( - remoteRunspace.ConnectionInfo, - "ShellUri", string.Empty); - - ConfigurationName = GetDisplayShellName(fullShellName); - return; - } - - // VM session - VMConnectionInfo vmConnectionInfo = remoteRunspace.ConnectionInfo as VMConnectionInfo; - if (vmConnectionInfo != null) - { - ComputerType = TargetMachineType.VirtualMachine; - ConfigurationName = vmConnectionInfo.ConfigurationName; - return; - } - - // Container session - ContainerConnectionInfo containerConnectionInfo = remoteRunspace.ConnectionInfo as ContainerConnectionInfo; - if (containerConnectionInfo != null) - { - ComputerType = TargetMachineType.Container; - ConfigurationName = containerConnectionInfo.ContainerProc.ConfigurationName; - return; - } - - // SSH session - SSHConnectionInfo sshConnectionInfo = remoteRunspace.ConnectionInfo as SSHConnectionInfo; - if (sshConnectionInfo != null) + switch (remoteRunspace.ConnectionInfo) { - ComputerType = TargetMachineType.RemoteMachine; - ConfigurationName = "DefaultShell"; - return; + case WSManConnectionInfo _: + ComputerType = TargetMachineType.RemoteMachine; + string fullShellName = WSManConnectionInfo.ExtractPropertyAsWsManConnectionInfo( + remoteRunspace.ConnectionInfo, + "ShellUri", string.Empty); + ConfigurationName = GetDisplayShellName(fullShellName); + break; + + case VMConnectionInfo vmConnectionInfo: + ComputerType = TargetMachineType.VirtualMachine; + ConfigurationName = vmConnectionInfo.ConfigurationName; + break; + + case ContainerConnectionInfo containerConnectionInfo: + ComputerType = TargetMachineType.Container; + ConfigurationName = containerConnectionInfo.ContainerProc.ConfigurationName; + break; + + case SSHConnectionInfo _: + ComputerType = TargetMachineType.RemoteMachine; + ConfigurationName = "DefaultShell"; + break; + + case NewProcessConnectionInfo _: + ComputerType = TargetMachineType.RemoteMachine; + break; + + default: + // Default for custom connection and transports. + ComputerType = TargetMachineType.RemoteMachine; + break; } - - // We only support WSMan/VM/Container sessions now. - Dbg.Assert(false, "Invalid Runspace"); } #endregion Constructor @@ -321,49 +314,44 @@ internal PSSession(RemoteRunspace remoteRunspace) #region Private Methods /// - /// Generates and returns the runspace name + /// Generates and returns the runspace name. /// - /// auto generated name + /// Auto generated name. private string GetTransportName() { - if (_remoteRunspace.ConnectionInfo is WSManConnectionInfo) - { - return "WSMan"; - } - else if (_remoteRunspace.ConnectionInfo is SSHConnectionInfo) - { - return "SSH"; - } - else if (_remoteRunspace.ConnectionInfo is NamedPipeConnectionInfo) + switch (_remoteRunspace.ConnectionInfo) { - return "NamedPipe"; - } - else if (_remoteRunspace.ConnectionInfo is ContainerConnectionInfo) - { - return "Container"; - } - else if (_remoteRunspace.ConnectionInfo is NewProcessConnectionInfo) - { - return "Process"; - } - else if (_remoteRunspace.ConnectionInfo is VMConnectionInfo) - { - return "VMBus"; - } - else - { - return "Unknown"; + case WSManConnectionInfo _: + return "WSMan"; + + case SSHConnectionInfo _: + return "SSH"; + + case NamedPipeConnectionInfo _: + return "NamedPipe"; + + case ContainerConnectionInfo _: + return "Container"; + + case NewProcessConnectionInfo _: + return "Process"; + + case VMConnectionInfo _: + return "VMBus"; + + default: + return string.IsNullOrEmpty(_transportName) ? "Custom" : _transportName; } } /// /// Returns shell configuration name with shell prefix removed. /// - /// shell configuration name - /// display shell name - private string GetDisplayShellName(string shell) + /// Shell configuration name. + /// Display shell name. + 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; @@ -373,12 +361,40 @@ 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. /// - /// Returned Id - /// Returned name - internal static String GenerateRunspaceName(out int rtnId) + /// Returned Id. + /// Returned name. + internal static string GenerateRunspaceName(out int rtnId) { int id = GenerateRunspaceId(); rtnId = id; @@ -388,7 +404,7 @@ internal static String GenerateRunspaceName(out int rtnId) /// /// Increments and returns a session unique runspace Id. /// - /// Id + /// Id. internal static int GenerateRunspaceId() { return System.Threading.Interlocked.Increment(ref s_seed); diff --git a/src/System.Management.Automation/engine/remoting/client/remotingprotocol.cs b/src/System.Management.Automation/engine/remoting/client/remotingprotocol.cs index 77d39aa2238..04ac1eaeaa0 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotingprotocol.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotingprotocol.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Remoting.Client; @@ -43,11 +42,11 @@ internal abstract BaseClientCommandTransportManager CreateClientCommandTransport bool noInput); // TODO: If this is not used, remove this. - //internal abstract event EventHandler DataReceived; + // internal abstract event EventHandler DataReceived; - internal abstract event EventHandler> EncryptedSessionKeyReceived; + internal abstract event EventHandler> EncryptedSessionKeyReceived; - internal abstract event EventHandler> PublicKeyRequestReceived; + internal abstract event EventHandler> PublicKeyRequestReceived; internal abstract void SendPublicKeyAsync(string localPublicKey); diff --git a/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs index 0fac991e7a6..d36ddff8be3 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs @@ -1,36 +1,37 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; +using System.Management.Automation.Remoting.Client; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; -using System.Management.Automation.Remoting.Client; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting { /// - /// Implements ServerRemoteSessionDataStructureHandler + /// 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 { @@ -55,7 +56,7 @@ internal override BaseClientCommandTransportManager CreateClientCommandTransport #region constructors /// - /// Creates an instance of ClientRemoteSessionDSHandlerImpl + /// Creates an instance of ClientRemoteSessionDSHandlerImpl. /// internal ClientRemoteSessionDSHandlerImpl(ClientRemoteSession session, PSRemotingCryptoHelper cryptoHelper, @@ -66,12 +67,12 @@ internal ClientRemoteSessionDSHandlerImpl(ClientRemoteSession session, if (session == null) { - throw PSTraceSource.NewArgumentNullException("session"); + throw PSTraceSource.NewArgumentNullException(nameof(session)); } _session = session; - //Create state machine + // Create state machine _stateMachine = new ClientRemoteSessionDSHandlerStateMachine(); _stateMachine.StateChanged += HandleStateChanged; @@ -90,10 +91,10 @@ internal ClientRemoteSessionDSHandlerImpl(ClientRemoteSession session, _transportManager.DisconnectCompleted += HandleDisconnectComplete; _transportManager.ReconnectCompleted += HandleReconnectComplete; - _transportManager.RobustConnectionNotification += new EventHandler(HandleRobustConnectionNotification); + _transportManager.RobustConnectionNotification += HandleRobustConnectionNotification; WSManConnectionInfo wsmanConnectionInfo = _connectionInfo as WSManConnectionInfo; - if (null != wsmanConnectionInfo) + if (wsmanConnectionInfo != null) { // only WSMan transport supports redirection @@ -120,7 +121,7 @@ internal override void CreateAsync() } /// - /// This callback is called on complete of async connect call + /// This callback is called on complete of async connect call. /// /// /// @@ -134,8 +135,8 @@ private void HandleCreateComplete(object sender, EventArgs args) private void HandleConnectComplete(object sender, EventArgs args) { - //No-OP. Once the negotiation messages are exchanged and the session gets into established state, - //it will take care of spawning the receive operation on the connected session + // No-OP. Once the negotiation messages are exchanged and the session gets into established state, + // it will take care of spawning the receive operation on the connected session // There is however a caveat. // A rouge remote server if it does not send the required negotiation data in the Connect Response, // then the state machine can never get into the established state and the runspace can never get into a opened state @@ -152,7 +153,7 @@ internal override void DisconnectAsync() private void HandleDisconnectComplete(object sender, EventArgs args) { - //Set statemachine event + // Set statemachine event RemoteSessionStateMachineEventArgs disconnectCompletedArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.DisconnectCompleted); StateMachine.RaiseEvent(disconnectCompletedArg); } @@ -197,7 +198,7 @@ internal override void ReconnectAsync() private void HandleReconnectComplete(object sender, EventArgs args) { - //Set statemachine event + // Set statemachine event RemoteSessionStateMachineEventArgs reconnectCompletedArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.ReconnectCompleted); StateMachine.RaiseEvent(reconnectCompletedArg); } @@ -230,13 +231,12 @@ private void HandleCloseComplete(object sender, EventArgs args) _stateMachine.RaiseEvent(closeCompletedArg); } - #endregion close #region negotiation /// - /// Sends the negotiation package asynchronously + /// Sends the negotiation package asynchronously. /// internal override void SendNegotiationAsync(RemoteSessionState sessionState) { @@ -244,7 +244,7 @@ internal override void SendNegotiationAsync(RemoteSessionState sessionState) // is prepared for a NegotiationReceived response. Otherwise a race condition can // occur when the transport NegotiationReceived arrives too soon, breaking the session. // This race condition was observed for OutOfProc transport when reusing the OutOfProc process. - //this will change StateMachine to NegotiationSent. + // this will change StateMachine to NegotiationSent. RemoteSessionStateMachineEventArgs negotiationSendCompletedArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.NegotiationSendCompleted); _stateMachine.RaiseEvent(negotiationSendCompletedArg); @@ -279,7 +279,7 @@ private void HandleStateChanged(object sender, RemoteSessionStateEventArgs arg) { if (arg == null) { - throw PSTraceSource.NewArgumentNullException("arg"); + throw PSTraceSource.NewArgumentNullException(nameof(arg)); } // Enqueue session related negotiation packets first @@ -297,7 +297,7 @@ private void HandleStateChanged(object sender, RemoteSessionStateEventArgs arg) SendNegotiationAsync(arg.SessionStateInfo.State); } - //once session is established.. start receiving data (if not already done and only apples to wsmanclientsessionTM) + // once session is established.. start receiving data (if not already done and only apples to wsmanclientsessionTM) if (arg.SessionStateInfo.State == RemoteSessionState.Established) { WSManClientSessionTransportManager tm = _transportManager as WSManClientSessionTransportManager; @@ -315,13 +315,13 @@ private void HandleStateChanged(object sender, RemoteSessionStateEventArgs arg) CloseConnectionAsync(); } - //process disconnect + // process disconnect if (arg.SessionStateInfo.State == RemoteSessionState.Disconnecting) { DisconnectAsync(); } - //process reconnect + // process reconnect if (arg.SessionStateInfo.State == RemoteSessionState.Reconnecting) { ReconnectAsync(); @@ -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() @@ -337,7 +337,7 @@ private void HandleNegotiationSendingStateChange() RemoteSessionCapability clientCapability = _session.Context.ClientCapability; Dbg.Assert(clientCapability.RemotingDestination == RemotingDestination.Server, "Expected clientCapability.RemotingDestination == RemotingDestination.Server"); - //Encode and send the negotiation reply + // Encode and send the negotiation reply RemoteDataObject data = RemotingEncoder.GenerateClientSessionCapability( clientCapability, _session.RemoteRunspacePoolInternal.InstanceId); RemoteDataObject dataAsPSObject = RemoteDataObject.CreateFrom( @@ -363,7 +363,7 @@ internal override ClientRemoteSessionDSHandlerStateMachine StateMachine /// 1. Close the current transport manager to clean resources /// 2. Raise a warning that URI is getting redirected. /// 3. Using the new URI, ask the same transport manager to redirect - /// Step 1 is performed here. Step2-3 is performed in another method + /// Step 1 is performed here. Step2-3 is performed in another method. /// /// /// @@ -434,7 +434,7 @@ private void HandleTransportErrorForRedirection(object sender, TransportErrorOcc /// private void PerformURIRedirectionStep2(System.Uri newURI) { - Dbg.Assert(null != newURI, "Uri cannot be null"); + Dbg.Assert(newURI != null, "Uri cannot be null"); lock (_syncObject) { // if connection is closed by the user..no need to redirect @@ -444,10 +444,7 @@ private void PerformURIRedirectionStep2(System.Uri newURI) } // raise warning to report the redirection - if (null != _uriRedirectionHandler) - { - _uriRedirectionHandler(newURI); - } + _uriRedirectionHandler?.Invoke(newURI); // start a new connection _transportManager.Redirect(newURI, _connectionInfo); @@ -488,7 +485,7 @@ internal void HandleTransportError(object sender, TransportErrorOccuredEventArgs exception = uriFormatException; } // if we are here, there must be an exception constructing a uri - if (null != exception) + if (exception != null) { PSRemotingTransportException newException = new PSRemotingTransportException(PSRemotingErrorId.RedirectedURINotWellFormatted, RemotingErrorIdStrings.RedirectedURINotWellFormatted, @@ -531,7 +528,7 @@ internal void HandleTransportError(object sender, TransportErrorOccuredEventArgs } /// - /// Dispatches data when it arrives from the input queue + /// Dispatches data when it arrives from the input queue. /// /// /// @@ -541,14 +538,14 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataArg) { if (dataArg == null) { - throw PSTraceSource.NewArgumentNullException("dataArg"); + throw PSTraceSource.NewArgumentNullException(nameof(dataArg)); } RemoteDataObject rcvdData = dataArg.ReceivedData; if (rcvdData == null) { - throw PSTraceSource.NewArgumentException("dataArg"); + throw PSTraceSource.NewArgumentException(nameof(dataArg)); } RemotingDestination destination = rcvdData.Destination; @@ -563,20 +560,20 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataArg) { case RemotingTargetInterface.Session: { - //Messages for session can cause statemachine state to change. - //These messages are first processed by Sessiondata structure handler and depending - //on the type of message, appropriate event is raised in state machine + // Messages for session can cause statemachine state to change. + // These messages are first processed by Sessiondata structure handler and depending + // on the type of message, appropriate event is raised in state machine ProcessSessionMessages(dataArg); break; } case RemotingTargetInterface.RunspacePool: case RemotingTargetInterface.PowerShell: - //Non Session messages do not change the state of the statemachine. - //However instead of forwarding them to Runspace/pipeline here, an - //event is raised in state machine which verified that state is - //suitable for accepting these messages. if state is suitable statemachine - //will call DoMessageForwading which will forward the messages appropriately + // Non Session messages do not change the state of the statemachine. + // However instead of forwarding them to Runspace/pipeline here, an + // event is raised in state machine which verified that state is + // suitable for accepting these messages. if state is suitable statemachine + // will call DoMessageForwading which will forward the messages appropriately RemoteSessionStateMachineEventArgs msgRcvArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.MessageReceived, null); if (StateMachine.CanByPassRaiseEvent(msgRcvArg)) { @@ -586,11 +583,13 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataArg) { StateMachine.RaiseEvent(msgRcvArg); } + break; default: { Dbg.Assert(false, "we should not be encountering this"); } + break; } } @@ -600,7 +599,7 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataArg) /// /// This processes the object received from transport which are - /// targeted for session + /// targeted for session. /// /// /// argument contains the data object @@ -609,7 +608,7 @@ private void ProcessSessionMessages(RemoteDataEventArgs arg) { if (arg == null || arg.ReceivedData == null) { - throw PSTraceSource.NewArgumentNullException("arg"); + throw PSTraceSource.NewArgumentNullException(nameof(arg)); } RemoteDataObject rcvdData = arg.ReceivedData; @@ -651,15 +650,17 @@ private void ProcessSessionMessages(RemoteDataEventArgs arg) case RemotingDataType.EncryptedSessionKey: { - String encryptedSessionKey = RemotingDecoder.GetEncryptedSessionKey(rcvdData.Data); + string encryptedSessionKey = RemotingDecoder.GetEncryptedSessionKey(rcvdData.Data); EncryptedSessionKeyReceived.SafeInvoke(this, new RemoteDataEventArgs(encryptedSessionKey)); } + break; case RemotingDataType.PublicKeyRequest: { - PublicKeyRequestReceived.SafeInvoke(this, new RemoteDataEventArgs(String.Empty)); + PublicKeyRequestReceived.SafeInvoke(this, new RemoteDataEventArgs(string.Empty)); } + break; default: @@ -671,7 +672,7 @@ private void ProcessSessionMessages(RemoteDataEventArgs arg) /// /// This processes the object received from transport which are - /// not targeted for session + /// not targeted for session. /// /// /// received data. @@ -681,7 +682,7 @@ internal void ProcessNonSessionMessages(RemoteDataObject rcvdData) // TODO: Consider changing to Dbg.Assert() if (rcvdData == null) { - throw PSTraceSource.NewArgumentNullException("rcvdData"); + throw PSTraceSource.NewArgumentNullException(nameof(rcvdData)); } RemotingTargetInterface targetInterface = rcvdData.TargetInterface; @@ -735,25 +736,11 @@ internal void ProcessNonSessionMessages(RemoteDataObject rcvdData) #region IDisposable /// - /// public method for dispose + /// Release all resources. /// public void Dispose() { - Dispose(true); - - GC.SuppressFinalize(this); - } - - /// - /// release all resources - /// - /// if true, release all managed resources - protected void Dispose(bool disposing) - { - if (disposing) - { - _transportManager.Dispose(); - } + _transportManager.Dispose(); } #endregion IDisposable @@ -764,9 +751,9 @@ protected void Dispose(bool disposing) internal override event EventHandler> PublicKeyRequestReceived; /// - /// Send the specified local public key to the remote end + /// Send the specified local public key to the remote end. /// - /// local public key as a string + /// Local public key as a string. internal override void SendPublicKeyAsync(string localPublicKey) { _transportManager.DataToBeSentCollection.Add( @@ -775,9 +762,9 @@ internal override void SendPublicKeyAsync(string localPublicKey) } /// - /// Raise the public key received event + /// Raise the public key received event. /// - /// received data + /// Received data. /// This method is a hook to be called /// from the transport manager internal override void RaiseKeyExchangeMessageReceived(RemoteDataObject receivedData) diff --git a/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs index d68f5506d10..b7474fe34c2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs @@ -1,18 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; using System.Management.Automation.Remoting.Client; -using System.Management.Automation.Host; +using System.Management.Automation.Runspaces; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -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: @@ -42,12 +42,11 @@ namespace Microsoft.PowerShell.Commands /// > Get-PSSession | Connect-PSSession /// /// Connect all disconnected PS sessions on a remote computer - /// > Connect-PSSession serverName - /// + /// > Connect-PSSession serverName. /// [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] [Cmdlet(VerbsCommunications.Connect, "PSSession", SupportsShouldProcess = true, DefaultParameterSetName = ConnectPSSessionCommand.NameParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=210604", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096694", RemotingCapability = RemotingCapability.OwnedByCommand)] [OutputType(typeof(PSSession))] public class ConnectPSSessionCommand : PSRunspaceCmdlet, IDisposable { @@ -79,27 +78,32 @@ public class ConnectPSSessionCommand : PSRunspaceCmdlet, IDisposable Mandatory = true)] [ValidateNotNullOrEmpty] [Alias("Cn")] - public override String[] ComputerName { get; set; } + public override string[] ComputerName { get; set; } /// /// 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 - /// not specified as well, then "WSMAN" will be used + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's + /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] - public String ApplicationName + public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } + set { _appName = ResolveAppName(value); } } - private String _appName; + + private string _appName; /// /// If this parameter is not specified then the value specified in @@ -114,15 +118,20 @@ public String ApplicationName ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] - public String ConfigurationName + public string ConfigurationName { - get { return _shell; } + get + { + return _shell; + } + set { _shell = ResolveShell(value); } } - private String _shell; + + private string _shell; /// /// A complete URI(s) specified for the remote computer and shell to @@ -147,13 +156,15 @@ public String ConfigurationName public SwitchParameter AllowRedirection { get { return _allowRedirection; } + set { _allowRedirection = value; } } + private bool _allowRedirection = false; /// /// RemoteRunspaceId to retrieve corresponding PSSession - /// object + /// object. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet, Mandatory = true)] @@ -166,20 +177,22 @@ public SwitchParameter AllowRedirection public override Guid[] InstanceId { get { return base.InstanceId; } + set { base.InstanceId = value; } } /// - /// Name of the remote runspaceinfo object + /// Name of the remote runspaceinfo object. /// [Parameter(ParameterSetName = ConnectPSSessionCommand.NameParameterSet, Mandatory = true)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriParameterSet)] [ValidateNotNullOrEmpty] - public override String[] Name + public override string[] Name { get { return base.Name; } + set { base.Name = value; } } @@ -192,10 +205,14 @@ 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 { _psCredential = value; @@ -203,6 +220,7 @@ public PSCredential Credential PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } + private PSCredential _psCredential; /// @@ -214,7 +232,11 @@ public PSCredential Credential [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } + set { _authentication = value; @@ -222,8 +244,8 @@ public AuthenticationMechanism Authentication PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private AuthenticationMechanism _authentication; + private AuthenticationMechanism _authentication; /// /// Specifies the certificate thumbprint to be used to impersonate the user on the @@ -235,7 +257,11 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } + set { _thumbprint = value; @@ -243,8 +269,8 @@ public string CertificateThumbprint PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private string _thumbprint; + private string _thumbprint; /// /// Port specifies the alternate port to be used in case the @@ -259,9 +285,8 @@ public string CertificateThumbprint /// [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.ComputerNameGuidParameterSet)] - [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] - public Int32 Port { get; set; } - + [ValidateRange((int)1, (int)UInt16.MaxValue)] + public int Port { get; set; } /// /// This parameter suggests that the transport scheme to be used for @@ -275,7 +300,6 @@ public string CertificateThumbprint [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSL")] public SwitchParameter UseSSL { get; set; } - /// /// Extended session options. Used in this cmdlet to set server disconnect options. /// @@ -285,7 +309,6 @@ public string CertificateThumbprint [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] public PSSessionOption SessionOption { get; set; } - /// /// Allows the user of the cmdlet to specify a throttling value /// for throttling the number of remote operations that can @@ -299,10 +322,10 @@ public string CertificateThumbprint [Parameter(ParameterSetName = ConnectPSSessionCommand.ConnectionUriGuidParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.NameParameterSet)] [Parameter(ParameterSetName = ConnectPSSessionCommand.InstanceIdParameterSet)] - public Int32 ThrottleLimit { get; set; } = 0; + public int ThrottleLimit { get; set; } = 0; /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] ContainerId { @@ -313,7 +336,7 @@ public override string[] ContainerId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override Guid[] VMId { @@ -324,7 +347,7 @@ public override Guid[] VMId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] VMName { @@ -346,7 +369,7 @@ protected override void BeginProcessing() base.BeginProcessing(); _throttleManager.ThrottleLimit = ThrottleLimit; - _throttleManager.ThrottleComplete += new EventHandler(HandleThrottleConnectComplete); + _throttleManager.ThrottleComplete += HandleThrottleConnectComplete; } /// @@ -420,7 +443,7 @@ protected override void EndProcessing() // Read all objects in the stream pipeline. while (_stream.ObjectReader.Count > 0) { - Object streamObject = _stream.ObjectReader.Read(); + object streamObject = _stream.ObjectReader.Read(); WriteStreamObject((Action)streamObject); } @@ -464,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, @@ -546,10 +569,7 @@ internal override void StartOperation() internal override void StopOperation() { - if (_queryRunspaces != null) - { - _queryRunspaces.StopAllOperations(); - } + _queryRunspaces?.StopAllOperations(); _session.Runspace.StateChanged -= StateCallBackHandler; SendStopComplete(); @@ -624,6 +644,7 @@ private void StateCallBackHandler(object sender, RunspaceStateEventArgs eArgs) { _retryList.Add(_session); } + writeError = false; } } @@ -682,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); } } @@ -717,11 +735,9 @@ private void WriteConnectFailed( StringUtil.Format(RemotingErrorIdStrings.RunspaceConnectFailed, session.Name, session.Runspace.RunspaceStateInfo.State.ToString()), null); } + 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); } } @@ -778,7 +794,7 @@ private Collection QueryForDisconnectedSessions() /// /// Creates a collection of PSSession objects based on cmdlet parameters. /// - /// OverrideParameter + /// OverrideParameter. /// Collection of PSSession objects in disconnected state. private Collection CollectDisconnectedSessions(OverrideParameter overrideParam = OverrideParameter.None) { @@ -900,8 +916,8 @@ private void ConnectSessions(Collection psSessions) /// /// Handles the connect throttling complete event from the ThrottleManager. /// - /// Sender - /// EventArgs + /// Sender. + /// EventArgs. private void HandleThrottleConnectComplete(object sender, EventArgs eventArgs) { _operationsComplete.Set(); @@ -932,6 +948,7 @@ private Collection GetConnectionObjects() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -954,6 +971,7 @@ private Collection GetConnectionObjects() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -1036,7 +1054,7 @@ private void RetryFailedSessions() /// /// Dispose method of IDisposable. Gets called in the following cases: /// 1. Pipeline explicitly calls dispose on cmdlets - /// 2. Called by the garbage collector + /// 2. Called by the garbage collector. /// public void Dispose() { @@ -1047,7 +1065,7 @@ public void Dispose() /// /// Internal dispose method which does the actual - /// dispose operations and finalize suppressions + /// dispose operations and finalize suppressions. /// /// Whether method is called /// from Dispose or destructor @@ -1060,7 +1078,7 @@ private void Dispose(bool disposing) _operationsComplete.WaitOne(); _operationsComplete.Dispose(); - _throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleConnectComplete); + _throttleManager.ThrottleComplete -= HandleThrottleConnectComplete; _retryThrottleManager.Dispose(); _stream.Dispose(); @@ -1072,446 +1090,25 @@ 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(); - - #endregion - } - - #region QueryRunspaces - - internal class QueryRunspaces - { - #region Constructor - - internal QueryRunspaces() - { - _stopProcessing = false; - } - - #endregion - - #region Internal Methods - - /// - /// Queries all remote computers specified in collection of WSManConnectionInfo objects - /// and returns disconnected PSSession objects ready for connection to server. - /// Returned sessions can be matched to Guids or Names. - /// - /// Collection of WSManConnectionInfo objects. - /// Host for PSSession objects. - /// Out stream object. - /// Runspace repository. - /// Throttle limit. - /// Runspace state filter value. - /// Array of session Guids to match to. - /// Array of session Names to match to. - /// Configuration name to match to. - /// Collection of disconnected PSSession objects. - internal Collection GetDisconnectedSessions(Collection connectionInfos, PSHost host, - ObjectStream stream, RunspaceRepository runspaceRepository, - int throttleLimit, SessionFilterState filterState, - Guid[] matchIds, string[] matchNames, string configurationName) - { - Collection filteredPSSessions = new Collection(); - - // Create a query operation for each connection information object. - foreach (WSManConnectionInfo connectionInfo in connectionInfos) - { - Runspace[] runspaces = null; - - try - { - runspaces = Runspace.GetRunspaces(connectionInfo, host, BuiltInTypesTable); - } - catch (System.Management.Automation.RuntimeException e) - { - if (e.InnerException is InvalidOperationException) - { - // The Get-WSManInstance cmdlet used to query remote computers for runspaces will throw - // an Invalid Operation (inner) exception if the connectInfo object is invalid, including - // invalid computer names. - // We don't want to propagate the exception so just write error here. - if (stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) - { - int errorCode; - string msg = StringUtil.Format(RemotingErrorIdStrings.QueryForRunspacesFailed, connectionInfo.ComputerName, ExtractMessage(e.InnerException, out errorCode)); - string FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(errorCode, "RemotePSSessionQueryFailed"); - Exception reason = new RuntimeException(msg, e.InnerException); - ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, connectionInfo); - stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); - } - } - else - { - throw; - } - } - - if (_stopProcessing) - { - break; - } - - // Add all runspaces meeting filter criteria to collection. - if (runspaces != null) - { - // Convert configuration name into shell Uri for comparison. - 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; - } - - foreach (Runspace runspace in runspaces) - { - // Filter returned runspaces by ConfigurationName if provided. - if (shellUri != null) - { - // Compare with returned shell Uri in connection info. - WSManConnectionInfo wsmanConnectionInfo = runspace.ConnectionInfo as WSManConnectionInfo; - if (wsmanConnectionInfo != null && - !shellUri.Equals(wsmanConnectionInfo.ShellUri, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - } - - // Check the repository for an existing viable PSSession for - // this runspace (based on instanceId). Use the existing - // local runspace instead of the one returned from the server - // query. - PSSession existingPSSession = null; - if (runspaceRepository != null) - { - existingPSSession = runspaceRepository.GetItem(runspace.InstanceId); - } - - if (existingPSSession != null && - UseExistingRunspace(existingPSSession.Runspace, runspace)) - { - if (TestRunspaceState(existingPSSession.Runspace, filterState)) - { - filteredPSSessions.Add(existingPSSession); - } - } - else if (TestRunspaceState(runspace, filterState)) - { - filteredPSSessions.Add(new PSSession(runspace as RemoteRunspace)); - } - } - } - } - - // Return only PSSessions that match provided Ids or Names. - if ((matchIds != null) && (filteredPSSessions.Count > 0)) - { - Collection matchIdsSessions = new Collection(); - foreach (Guid id in matchIds) - { - bool matchFound = false; - foreach (PSSession psSession in filteredPSSessions) - { - if (_stopProcessing) - { - break; - } - - if (psSession.Runspace.InstanceId.Equals(id)) - { - matchFound = true; - matchIdsSessions.Add(psSession); - break; - } - } - - if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) - { - string msg = StringUtil.Format(RemotingErrorIdStrings.SessionIdMatchFailed, id); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionIdMatchFail", ErrorCategory.InvalidOperation, id); - stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); - } - } - - // Return all found sessions. - return matchIdsSessions; - } - else if ((matchNames != null) && (filteredPSSessions.Count > 0)) - { - Collection matchNamesSessions = new Collection(); - foreach (string name in matchNames) - { - WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); - bool matchFound = false; - foreach (PSSession psSession in filteredPSSessions) - { - if (_stopProcessing) - { - break; - } - - if (namePattern.IsMatch(((RemoteRunspace)psSession.Runspace).RunspacePool.RemoteRunspacePoolInternal.Name)) - { - matchFound = true; - matchNamesSessions.Add(psSession); - } - } - - if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) - { - string msg = StringUtil.Format(RemotingErrorIdStrings.SessionNameMatchFailed, name); - Exception reason = new RuntimeException(msg); - ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionNameMatchFail", ErrorCategory.InvalidOperation, name); - stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); - } - } - - return matchNamesSessions; - } - else - { - // Return all collected sessions. - return filteredPSSessions; - } - } - - /// - /// Returns true if the existing runspace should be returned to the user - /// a. If the existing runspace is not broken - /// b. If the queried runspace is not connected to a different user. - /// - /// - /// - /// - private static bool UseExistingRunspace( - Runspace existingRunspace, - Runspace queriedrunspace) - { - Dbg.Assert(existingRunspace != null, "Invalid parameter."); - Dbg.Assert(queriedrunspace != null, "Invalid parameter."); - - if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Broken) - { - return false; - } - - if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Disconnected && - queriedrunspace.RunspaceAvailability == RunspaceAvailability.Busy) - { - return false; - } - - // Update existing runspace to have latest DisconnectedOn/ExpiresOn data. - existingRunspace.DisconnectedOn = queriedrunspace.DisconnectedOn; - existingRunspace.ExpiresOn = queriedrunspace.ExpiresOn; - - return true; - } - - /// - /// Returns Exception message. If message is WSMan Xml then - /// the WSMan message and error code is extracted and returned. - /// - /// Exception - /// Returned WSMan error code - /// WSMan message - internal static string ExtractMessage( - Exception e, - out int errorCode) - { - errorCode = 0; - - if (e == null || - e.Message == null) - { - return string.Empty; - } - - string rtnMsg = null; - try - { - System.Xml.XmlReaderSettings xmlReaderSettings = InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); - xmlReaderSettings.MaxCharactersInDocument = 4096; - xmlReaderSettings.MaxCharactersFromEntities = 1024; - xmlReaderSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit; - - using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create( - new System.IO.StringReader(e.Message), xmlReaderSettings)) - { - while (reader.Read()) - { - if (reader.NodeType == System.Xml.XmlNodeType.Element) - { - if (reader.LocalName.Equals("Message", StringComparison.OrdinalIgnoreCase)) - { - rtnMsg = reader.ReadElementContentAsString(); - } - else if (reader.LocalName.Equals("WSManFault", StringComparison.OrdinalIgnoreCase)) - { - string errorCodeString = reader.GetAttribute("Code"); - if (errorCodeString != null) - { - try - { - // WinRM returns both signed and unsigned 32 bit string values. Convert to signed 32 bit integer. - Int64 eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo); - unchecked - { - errorCode = (int)eCode; - } - } - catch (FormatException) - { } - catch (OverflowException) - { } - } - } - } - } - } - } - catch (System.Xml.XmlException) - { } - - return rtnMsg ?? e.Message; - } - - /// - /// Discontinue all remote server query operations. - /// - internal void StopAllOperations() - { - _stopProcessing = true; - } - - /// - /// Compares the runspace filter state with the runspace state. - /// - /// Runspace object to test. - /// Filter state to compare. - /// Result of test. - public static bool TestRunspaceState(Runspace runspace, SessionFilterState filterState) - { - bool result; - - switch (filterState) - { - case SessionFilterState.All: - result = true; - break; - - case SessionFilterState.Opened: - result = (runspace.RunspaceStateInfo.State == RunspaceState.Opened); - break; - - case SessionFilterState.Closed: - result = (runspace.RunspaceStateInfo.State == RunspaceState.Closed); - break; - - case SessionFilterState.Disconnected: - result = (runspace.RunspaceStateInfo.State == RunspaceState.Disconnected); - break; - - case SessionFilterState.Broken: - result = (runspace.RunspaceStateInfo.State == RunspaceState.Broken); - break; - - default: - Dbg.Assert(false, "Invalid SessionFilterState value."); - result = false; - break; - } - - return result; - } - - /// - /// Returns the default type table for built-in PowerShell types. - /// - internal static TypeTable BuiltInTypesTable - { - get - { - if (s_TypeTable == null) - { - lock (s_SyncObject) - { - if (s_TypeTable == null) - { - s_TypeTable = TypeTable.LoadDefaultTypeFiles(); - } - } - } - - return s_TypeTable; - } - } - - #endregion - - #region Private Members - - private bool _stopProcessing; - - private static readonly object s_SyncObject = new object(); - private static TypeTable s_TypeTable; + private readonly ThrottleManager _retryThrottleManager = new ThrottleManager(); + private readonly Collection _failedSessions = new Collection(); #endregion } - - #endregion - - # region Public SessionFilterState Enum - - /// - /// Runspace states that can be used as filters for querying remote runspaces. - /// - public enum SessionFilterState - { - /// - /// Return runspaces in any state. - /// - All = 0, - - /// - /// Return runspaces in Opened state. - /// - Opened = 1, - - /// - /// Return runspaces in Disconnected state. - /// - Disconnected = 2, - - /// - /// Return runspaces in Closed state. - /// - Closed = 3, - - /// - /// Return runspaces in Broken state. - /// - Broken = 4 - } - - #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index f550f163ca4..1d88210e7b6 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -1,42 +1,36 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Text; using System.Globalization; -using System.Threading; -using System.Security.AccessControl; using System.IO; -using System.Diagnostics.CodeAnalysis; - using System.Management.Automation; using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Remoting; using System.Management.Automation.Language; - -using Dbg = System.Management.Automation.Diagnostics; -using WSManNativeApi = System.Management.Automation.Remoting.Client.WSManNativeApi; -using PowerShellApi = System.Management.Automation.PowerShell; - -using Microsoft.Win32; +using System.Management.Automation.Remoting; +using System.Management.Automation.Runspaces; using System.Security; -using System.Collections.Generic; +using System.Security.AccessControl; using System.Security.Principal; +using System.Text; +using System.Threading; +using Dbg = System.Management.Automation.Diagnostics; +using PowerShellApi = System.Management.Automation.PowerShell; +using WSManNativeApi = System.Management.Automation.Remoting.Client.WSManNativeApi; namespace Microsoft.PowerShell.Commands { #region Register-PSSessionConfiguration cmdlet /// - /// Class implementing Register-PSSessionConfiguration + /// Class implementing Register-PSSessionConfiguration. /// [Cmdlet(VerbsLifecycle.Register, RemotingConstants.PSSessionConfigurationNoun, DefaultParameterSetName = PSSessionConfigurationCommandBase.NameParameterSetName, SupportsShouldProcess = true, - ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144306")] + ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096793")] public sealed class RegisterPSSessionConfigurationCommand : PSSessionConfigurationCommandBase { #region Private Data @@ -58,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 @@ -145,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) @@ -188,15 +183,16 @@ function Register-PSSessionConfiguration $securityIdentifierToPurge = $_.securityidentifier }} }} + if (([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Local.Equals($accessMode) -or - ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode)-and $disableNetworkExists)) -and - !$haveDisableACE) + [System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode)) -and $haveDisableACE) {{ - # Add network deny ACE for local access or remote access with PSRemoting disabled ($disableNetworkExists) + # Add network deny ACE for local access or remote access with PSRemoting disabled. $sd.DiscretionaryAcl.AddAccess(""deny"", $networkSID, 268435456, ""None"", ""None"") $newSDDL = $sd.GetSddlForm(""all"") }} - if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode) -and -not $disableNetworkExists -and $haveDisableACE) + + if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode) -and $haveDisableACE) {{ # Remove the specific ACE $sd.discretionaryacl.RemoveAccessSpecific('Deny', $securityIdentifierToPurge, 268435456, 'none', 'none') @@ -232,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 {{ @@ -267,15 +263,16 @@ 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] }} "; + private static readonly ScriptBlock s_newPluginSb; private const string pluginXmlFormat = @" @@ -286,16 +283,19 @@ function Register-PSSessionConfiguration XmlRenderingType='text' {2} {6} {7} {8} {9} {10}> {3} + {5} + {11} "; + private const string architectureAttribFormat = @" Architecture='{0}'"; @@ -313,6 +313,7 @@ function Register-PSSessionConfiguration private const string initParamFormat = @" {2}"; + private const string privateDataFormat = @"{0}"; private const string securityElementFormat = ""; private const string SessionConfigDataFormat = @"{0}"; @@ -339,24 +340,6 @@ function Register-PSSessionConfiguration [ValidateSet("x86", "amd64")] public string ProcessorArchitecture { get; set; } - /// - /// SessionType - /// - /// Only want this on non assemblyName parameter set, since assembly decides the sessiontype. - [Parameter(ParameterSetName = PSSessionConfigurationCommandBase.NameParameterSetName)] - public PSSessionType SessionType - { - get - { - return sessionType; - } - set - { - sessionType = value; - } - } - internal PSSessionType sessionType = PSSessionType.DefaultRemoteShell; - #endregion #region Constructors @@ -366,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); @@ -380,7 +363,6 @@ static RegisterPSSessionConfigurationCommand() #region Cmdlet Overrides /// - /// /// /// /// 1. Either both "AssemblyName" and "ConfigurationTypeName" must be specified @@ -424,6 +406,7 @@ protected override void BeginProcessing() descriptor.DiscretionaryAcl.AddAccess(AccessControlType.Deny, networkSidIdentifier, 268435456, InheritanceFlags.None, PropagationFlags.None); sddl = descriptor.GetSddlForm(AccessControlSections.All); } + break; case PSSessionConfigurationAccessMode.Remote: if (networkDenyAllExists) @@ -449,8 +432,10 @@ protected override void BeginProcessing() SecurityIdentifier iaSidIdentifier = new SecurityIdentifier(InteractiveUsersSID); descriptor.DiscretionaryAcl.AddAccess(AccessControlType.Allow, iaSidIdentifier, 268435456, InheritanceFlags.None, PropagationFlags.None); } + sddl = descriptor.GetSddlForm(AccessControlSections.All); } + break; case PSSessionConfigurationAccessMode.Disabled: break; @@ -494,7 +479,7 @@ protected override void BeginProcessing() { PSInvalidOperationException ioe = new PSInvalidOperationException( StringUtil.Format(RemotingErrorIdStrings.PluginDllMissing, RemotingConstants.PSPluginDLLName)); - ThrowTerminatingError(ioe.ErrorRecord); + ThrowTerminatingError(ioe.ErrorRecord); } } @@ -560,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; @@ -593,7 +576,7 @@ protected override void ProcessRecord() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { file, @@ -605,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 }); @@ -642,7 +626,7 @@ protected override void ProcessRecord() protected override void EndProcessing() { System.Management.Automation.Tracing.Tracer tracer = new System.Management.Automation.Tracing.Tracer(); - tracer.EndpointRegistered(this.Name, this.sessionType.ToString(), WindowsIdentity.GetCurrent().Name); + tracer.EndpointRegistered(this.Name, WindowsIdentity.GetCurrent().Name); } #endregion @@ -657,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."); @@ -665,7 +649,7 @@ private void DeleteFile(string tmpFileName) try { File.Delete(tmpFileName); - //WriteWarning(tmpFileName); + // WriteWarning(tmpFileName); } catch (UnauthorizedAccessException uae) { @@ -709,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"; @@ -723,8 +707,8 @@ private string ConstructTemporaryFile(string pluginContent) { try { - //Make sure the file is not read only - destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + // Make sure the file is not read only + destfile.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); destfile.Delete(); } catch (FileNotFoundException fnf) @@ -814,29 +798,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d destConfigFilePath = null; StringBuilder initParameters = new StringBuilder(); - bool assemblyAndTypeTokensSet = false; - if (sessionType == PSSessionType.Workflow) - { - initParameters.Append(string.Format(CultureInfo.InvariantCulture, - initParamFormat, - ConfigurationDataFromXML.ENDPOINTCONFIGURATIONTYPE, - sessionType, - Environment.NewLine)); - - initParameters.Append(string.Format(CultureInfo.InvariantCulture, - initParamFormat, - ConfigurationDataFromXML.ASSEMBLYTOKEN, - ConfigurationDataFromXML.WORKFLOWCOREASSEMBLY, - Environment.NewLine)); - - initParameters.Append(string.Format(CultureInfo.InvariantCulture, - initParamFormat, - ConfigurationDataFromXML.SHELLCONFIGTYPETOKEN, - ConfigurationDataFromXML.WORKFLOWCORETYPENAME, - Environment.NewLine)); - - assemblyAndTypeTokensSet = true; - } + const bool assemblyAndTypeTokensSet = false; // DISC endpoint if (Path != null) @@ -907,7 +869,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d if (configTable.ContainsKey(ConfigFileConstants.PowerShellVersion)) { - if (isPSVersionSpecified == false) + if (!isPSVersionSpecified) { try { @@ -1020,7 +982,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d // Copy File. string destConfigFileDirectory = System.IO.Path.GetDirectoryName(destConfigFilePath); - // The directory is not auto-created for PowerShell Core. + // The directory is not auto-created for PowerShell. // The call will create it or return its path if it already exists System.IO.Directory.CreateDirectory(destConfigFileDirectory); @@ -1089,7 +1051,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d maxObjectSizeMB.Value, Environment.NewLine)); } -#if !CORECLR // No ApartmentState In CoreCLR + if (threadAptState.HasValue) { initParameters.Append(string.Format(CultureInfo.InvariantCulture, @@ -1098,7 +1060,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d threadAptState.Value, Environment.NewLine)); } -#endif + if (threadOptions.HasValue) { initParameters.Append(string.Format(CultureInfo.InvariantCulture, @@ -1109,7 +1071,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d } // Default value for PSVersion - if (isPSVersionSpecified == false) + if (!isPSVersionSpecified) { psVersion = PSVersionInfo.PSVersion; } @@ -1135,7 +1097,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d } } - string securityParameters = ""; + string securityParameters = string.Empty; if (!string.IsNullOrEmpty(sddl)) { securityParameters = string.Format(CultureInfo.InvariantCulture, @@ -1163,12 +1125,6 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d tempValue); } - - if (sessionType == PSSessionType.Workflow && !isUseSharedProcessSpecified) - { - UseSharedProcess = true; - } - string sharedHostParameter = string.Empty; if (isUseSharedProcessSpecified) { @@ -1209,13 +1165,6 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d StringBuilder sessionConfigurationData = new StringBuilder(); - if (sessionType == PSSessionType.Workflow) - { - List modifiedModulePath = new List(modulesToImport ?? Utils.EmptyArray()); - modifiedModulePath.Insert(0, ConfigurationDataFromXML.PSWORKFLOWMODULE); - modulesToImport = modifiedModulePath.ToArray(); - } - if (modulesToImport != null && modulesToImport.Length > 0) { sessionConfigurationData.Append(string.Format(CultureInfo.InvariantCulture, @@ -1227,7 +1176,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d if (sessionTypeOption != null) { - //TODO: This should probably be a terminating exception for Win8 + // TODO: This should probably be a terminating exception for Win8 string privateData = this.sessionTypeOption.ConstructPrivateData(); if (!string.IsNullOrEmpty(privateData)) { @@ -1257,7 +1206,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d transportOption = transportOption.Clone() as PSTransportOption; } - transportOption.LoadFromDefaults(sessionType, true); + transportOption.LoadFromDefaults(true); // If useSharedHost is set to false, we need to set hostIdleTimeout to 0 as well, else WS-Man throws error if (isUseSharedProcessSpecified && !UseSharedProcess) @@ -1295,7 +1244,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d #region Utils /// - /// Utilities for Custom shell commands + /// Utilities for Custom shell commands. /// internal static class PSSessionConfigurationCommandUtilities { @@ -1334,7 +1283,7 @@ internal static void RestartWinRMService(PSCmdlet cmdlet, bool isErrorReported, cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.RestartWSManServiceMessageV)); ScriptBlock restartServiceScript = cmdlet.InvokeCommand.NewScriptBlock(restartWSManFormat); - var emptyArray = Utils.EmptyArray(); + var emptyArray = Array.Empty(); restartServiceScript.InvokeUsingCmdlet( contextCmdlet: cmdlet, useLocalScope: true, @@ -1359,6 +1308,7 @@ internal static void MoveWinRmToIsolatedServiceHost(bool forVirtualAccount) { $requiredPrivileges += @('SeTcbPrivilege') } + Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\winrm -Name RequiredPrivileges -Value $requiredPrivileges Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\winrm -Name ObjectName -Value 'LocalSystem'"; } @@ -1382,7 +1332,7 @@ internal static void CollectShouldProcessParameters(PSCmdlet cmdlet, out bool wh // confirm is always true to start with confirm = false; MshCommandRuntime cmdRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (null != cmdRuntime) + if (cmdRuntime != null) { whatIf = cmdRuntime.WhatIf; // take the value of confirm only if it is explicitly set by the user @@ -1393,7 +1343,6 @@ internal static void CollectShouldProcessParameters(PSCmdlet cmdlet, out bool wh } } - /// /// Checks if the current thread is running elevated. If not, throws an error. /// @@ -1412,10 +1361,10 @@ internal static void ThrowIfNotAdministrator() } /// - /// Creates a Grouped Managed Service Account credential based on the passed in account name + /// Creates a Grouped Managed Service Account credential based on the passed in account name. /// - /// Group Managed Service Account name - /// PSCredential for GMS account + /// Group Managed Service Account name. + /// PSCredential for GMS account. /// /// Invalid account name. Must be of form 'Domain\UserName'. /// @@ -1424,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])) @@ -1440,7 +1389,7 @@ internal static PSCredential CreateGMSAAccountCredentials(string gmsaAccount) } /// - /// Calculates the MaxPSVersion in the config xml from psVersion + /// Calculates the MaxPSVersion in the config xml from psVersion. /// /// internal static Version CalculateMaxPSVersion(Version psVersion) @@ -1481,14 +1430,16 @@ internal static string GetModulePathAsString(object[] modulePath) } } } + sb.Remove(sb.Length - 1, 1); return sb.ToString(); } + return string.Empty; } /// - /// Converts the version number to the format "Major.Minor" which needs to be persisted in the config xml + /// Converts the version number to the format "Major.Minor" which needs to be persisted in the config xml. /// /// internal static Version ConstructVersionFormatForConfigXml(Version psVersion) @@ -1502,46 +1453,6 @@ internal static Version ConstructVersionFormatForConfigXml(Version psVersion) return result; } - /// - /// 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 - } - } - /// /// Takes array of group name string objects and returns a semicolon delimited string. /// @@ -1551,31 +1462,27 @@ internal static string GetRunAsVirtualAccountGroupsString(string[] groups) { if (groups == null) { return string.Empty; } - return string.Join(";", groups); + return string.Join(';', groups); } /// - /// Returns the default WinRM plugin shell name for this instance of PowerShell + /// Returns the default WinRM plugin shell name for this instance of PowerShell. /// /// internal static string GetWinrmPluginShellName() { - // PowerShell Core uses a versioned directory to hold the plugin - // TODO: This should be PSVersionInfo.PSVersionName once we get - // closer to release. Right now it doesn't support alpha versions. - return System.String.Concat("PowerShell.", PSVersionInfo.GitCommitId); + // PowerShell uses a versioned directory to hold the plugin + return string.Concat("PowerShell.", PSVersionInfo.GitCommitId); } /// - /// Returns the default WinRM plugin DLL file path for this instance of PowerShell + /// Returns the default WinRM plugin DLL file path for this instance of PowerShell. /// /// internal static string GetWinrmPluginDllPath() { - // PowerShell Core uses its versioned directory instead of system32 - // TODO: This should be PSVersionInfo.PSVersionName once we get - // closer to release. Right now it doesn't support alpha versions. - string pluginDllDirectory = System.IO.Path.Combine("%windir%\\system32\\PowerShell", PSVersionInfo.GitCommitId); + // PowerShell 6+ uses its versioned directory instead of system32 + string pluginDllDirectory = System.IO.Path.Combine("%windir%\\system32\\PowerShell", PSVersionInfo.GitCommitId); return System.IO.Path.Combine(pluginDllDirectory, RemotingConstants.PSPluginDLLName); } @@ -1591,7 +1498,7 @@ internal static string GetWinrmPluginDllPath() /// /// /// - /// SDDL + /// SDDL. internal static string ComputeSDDLFromConfiguration( Hashtable configTable, PSSessionConfigurationAccessMode accessMode, @@ -1614,6 +1521,7 @@ internal static string ComputeSDDLFromConfiguration( { sddl = PSSessionConfigurationCommandBase.GetRemoteSddl(); } + CommonSecurityDescriptor descriptor = new CommonSecurityDescriptor(false, false, sddl); // Purge all existing access rules so that only role definition principals are granted access. @@ -1622,6 +1530,7 @@ internal static string ComputeSDDLFromConfiguration( { sidsToRemove.Add(ace.SecurityIdentifier); } + foreach (var sidToRemove in sidsToRemove) { descriptor.PurgeAccessControl(sidToRemove); @@ -1689,7 +1598,7 @@ internal static string UpdateSDDLUsersWithGroupConditional( // O:NSG:BAD:P // epilogue string contains the ending (and optional) SACL components // S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD) - // (https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx) + // (https://msdn.microsoft.com/library/windows/desktop/aa379570(v=vs.85).aspx) string prologue; string epilogue; Collection aces = ParseDACLACEs(sddl, out prologue, out epilogue); @@ -1705,12 +1614,12 @@ internal static string UpdateSDDLUsersWithGroupConditional( // We only manipulate ACEs that we create and we currently do not use the optional resource field, // so we always expect a beginning ACE with exactly 6 fields. // ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid;(resource_attribute) - // (https://msdn.microsoft.com/en-us/library/windows/desktop/aa374928(v=vs.85).aspx) + // (https://msdn.microsoft.com/library/windows/desktop/aa374928(v=vs.85).aspx) // // Converted (Conditional) ACE has exactly 7 required fields. In addition the ACE type // is prepended with 'X' character. // AceType;AceFlags;Rights;ObjectGuid;InheritObjectGuid;AccountSid;(ConditionalExpression) - // (https://msdn.microsoft.com/en-us/library/windows/desktop/dd981030(v=vs.85).aspx) + // (https://msdn.microsoft.com/library/windows/desktop/dd981030(v=vs.85).aspx) // // e.g. // Beginning ACE: (A;;GA;;;BA) @@ -1740,6 +1649,7 @@ internal static string UpdateSDDLUsersWithGroupConditional( { sb.Append(components[i] + ACESeparator); } + sb.Append(conditionalGroupACE); sb.Append(CloseParenChar); @@ -1751,6 +1661,7 @@ internal static string UpdateSDDLUsersWithGroupConditional( } private const string DACLPrefix = "D:"; + private static Collection ParseDACLACEs( string sddl, out string prologue, @@ -1759,7 +1670,7 @@ private static Collection ParseDACLACEs( // // The format of the sddl is expected to be: // owner (O:), primary group (G:), DACL (D:), and SACL (S:). - // (https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx) + // (https://msdn.microsoft.com/library/windows/desktop/aa379570(v=vs.85).aspx) // e.g. // O:NSG:BAD:P(A;;GA;;;BA)(XA;;GA;;;RM)(XA;;GA;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD) // prologue = "O:NSG:BAD:P" @@ -1785,6 +1696,7 @@ private static Collection ParseDACLACEs( epilogue = string.Empty; return sddlACEs; } + prologue = sddl.Substring(0, index); int sddlLength = sddl.Length; @@ -1831,7 +1743,7 @@ private static Collection ParseDACLACEs( // User ACE: (XA;;GA;;;S-1-5-21-2127438184-1604012920-1882527527;ConditionalPart) // ConditionalPart: ((Member_of {SID(2FA_GROUP_1)} || Member_of {SID(2FA_GROUP_2)}) && (Member_of {SID(TRUSTEDHOSTS_1)} || Member_of {TRUSTEDHOSTS_2})) // where: 2FA_GROUP_1, 2FA_GROUP_2, TRUSTEDHOSTS_1, TRUSTEDHOSTS_2 are resolved SIDs of the group names. - // (https://msdn.microsoft.com/en-us/library/windows/desktop/dd981030(v=vs.85).aspx) + // (https://msdn.microsoft.com/library/windows/desktop/dd981030(v=vs.85).aspx) internal static string CreateConditionalACEFromConfig( Hashtable configTable) { @@ -1841,11 +1753,11 @@ 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); } + if (requiredGroupsHash.Count == 0) { return null; @@ -1983,7 +1895,7 @@ private static void ParseValue( #region PSCustomShellCommandBase /// - /// Base class for PSCustomShell commands Register-PSSessionConfiguration, Set-PSSessionConfiguration + /// Base class for PSCustomShell commands Register-PSSessionConfiguration, Set-PSSessionConfiguration. /// public class PSSessionConfigurationCommandBase : PSCmdlet { @@ -2022,24 +1934,6 @@ internal static string GetRemoteSddl() return (Environment.OSVersion.Version >= new Version(6, 2)) ? remoteSDDL_Win8 : remoteSDDL; } - 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( - PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.PSVersionParameterOutOfRange, - version, "PSVersion") - ); - } - } - } - #endregion #region Parameters @@ -2055,8 +1949,10 @@ internal static void CheckPSVersion(Version version) public string Name { get { return shellName; } + set { shellName = value; } } + internal string shellName; /// @@ -2066,13 +1962,18 @@ public string Name [Parameter(Position = 1, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string AssemblyName { - get { return assemblyName; } + get + { + return assemblyName; + } + set { assemblyName = value; isAssemblyNameSpecified = true; } } + internal string assemblyName; internal bool isAssemblyNameSpecified; @@ -2086,13 +1987,18 @@ public string AssemblyName [Parameter(ParameterSetName = AssemblyNameParameterSetName)] public string ApplicationBase { - get { return applicationBase; } + get + { + return applicationBase; + } + set { applicationBase = value; isApplicationBaseSpecified = true; } } + internal string applicationBase; internal bool isApplicationBaseSpecified; @@ -2104,13 +2010,18 @@ public string ApplicationBase [Parameter(Position = 2, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string ConfigurationTypeName { - get { return configurationTypeName; } + get + { + return configurationTypeName; + } + set { configurationTypeName = value; isConfigurationTypeNameSpecified = true; } } + internal string configurationTypeName; internal bool isConfigurationTypeNameSpecified; @@ -2124,16 +2035,17 @@ public PSCredential RunAsCredential { return runAsCredential; } + set { runAsCredential = value; isRunAsCredentialSpecified = true; } } + internal PSCredential runAsCredential; internal bool isRunAsCredentialSpecified; -#if !CORECLR // No ApartmentState In CoreCLR /// /// ApartmentState of the Runspace created for the shell. /// @@ -2150,10 +2062,13 @@ public ApartmentState ThreadApartmentState return ApartmentState.Unknown; } - set { threadAptState = value; } + set + { + threadAptState = value; + } } - internal Nullable threadAptState; -#endif + + internal ApartmentState? threadAptState; /// /// ThreadOptions of the Runspace created for the shell. @@ -2171,28 +2086,37 @@ public PSThreadOptions ThreadOptions return PSThreadOptions.UseCurrentThread; } - set { threadOptions = value; } + set + { + threadOptions = value; + } } - internal Nullable threadOptions; + + internal PSThreadOptions? threadOptions; /// - /// Set access mode + /// Set access mode. /// [Parameter] public PSSessionConfigurationAccessMode AccessMode { - get { return _accessMode; } + get + { + return _accessMode; + } + set { _accessMode = value; accessModeSpecified = true; } } + private PSSessionConfigurationAccessMode _accessMode = PSSessionConfigurationAccessMode.Remote; internal bool accessModeSpecified = false; /// - /// Host mode + /// Host mode. /// [Parameter] public SwitchParameter UseSharedProcess @@ -2201,12 +2125,14 @@ public SwitchParameter UseSharedProcess { return _useSharedProcess; } + set { _useSharedProcess = value; isUseSharedProcessSpecified = true; } } + private bool _useSharedProcess; internal bool isUseSharedProcessSpecified; @@ -2216,13 +2142,18 @@ public SwitchParameter UseSharedProcess [Parameter()] public string StartupScript { - get { return configurationScript; } + get + { + return configurationScript; + } + set { configurationScript = value; isConfigurationScriptSpecified = true; } } + internal string configurationScript; internal bool isConfigurationScriptSpecified; @@ -2232,9 +2163,13 @@ public string StartupScript /// [Parameter()] [AllowNull] - public Nullable MaximumReceivedDataSizePerCommandMB + public double? MaximumReceivedDataSizePerCommandMB { - get { return maxCommandSizeMB; } + get + { + return maxCommandSizeMB; + } + set { if ((value.HasValue) && (value.Value < 0)) @@ -2244,11 +2179,13 @@ public Nullable MaximumReceivedDataSizePerCommandMB value.Value, "MaximumReceivedDataSizePerCommandMB") ); } + maxCommandSizeMB = value; isMaxCommandSizeMBSpecified = true; } } - internal Nullable maxCommandSizeMB; + + internal double? maxCommandSizeMB; internal bool isMaxCommandSizeMBSpecified; /// @@ -2256,9 +2193,13 @@ public Nullable MaximumReceivedDataSizePerCommandMB /// [Parameter()] [AllowNull] - public Nullable MaximumReceivedObjectSizeMB + public double? MaximumReceivedObjectSizeMB { - get { return maxObjectSizeMB; } + get + { + return maxObjectSizeMB; + } + set { if ((value.HasValue) && (value.Value < 0)) @@ -2268,22 +2209,27 @@ public Nullable MaximumReceivedObjectSizeMB value.Value, "MaximumReceivedObjectSizeMB") ); } + maxObjectSizeMB = value; isMaxObjectSizeMBSpecified = true; } } - internal Nullable maxObjectSizeMB; + + internal double? maxObjectSizeMB; internal bool isMaxObjectSizeMBSpecified; /// /// This enables the user to specify an SDDL on the shell. /// The default SDDL is the default used by Wsman. /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sddl")] [Parameter()] public string SecurityDescriptorSddl { - get { return sddl; } + get + { + return sddl; + } + set { if (!string.IsNullOrEmpty(value)) @@ -2292,7 +2238,7 @@ public string SecurityDescriptorSddl CommonSecurityDescriptor c = new CommonSecurityDescriptor(false, false, value); // this will never be the case..as constructor either constructs or throws. // this is used here to avoid FxCop violation. - if (null == c) + if (c == null) { throw new NotSupportedException(); } @@ -2302,6 +2248,7 @@ public string SecurityDescriptorSddl isSddlSpecified = true; } } + internal string sddl; internal bool isSddlSpecified; @@ -2311,13 +2258,18 @@ public string SecurityDescriptorSddl [Parameter()] public SwitchParameter ShowSecurityDescriptorUI { - get { return _showUI; } + get + { + return _showUI; + } + set { _showUI = value; showUISpecified = true; } } + private bool _showUI; internal bool showUISpecified; @@ -2330,8 +2282,10 @@ public SwitchParameter ShowSecurityDescriptorUI public SwitchParameter Force { get { return force; } + set { force = value; } } + internal bool force; /// @@ -2343,8 +2297,10 @@ public SwitchParameter Force public SwitchParameter NoServiceRestart { get { return noRestart; } + set { noRestart = value; } } + internal bool noRestart; /// @@ -2357,23 +2313,23 @@ public SwitchParameter NoServiceRestart [ValidateNotNullOrEmpty] public Version PSVersion { - get { return psVersion; } - set + get { - CheckPSVersion(value); - - // Check if specified version of PowerShell is installed - PSSessionConfigurationCommandUtilities.CheckIfPowerShellVersionIsInstalled(value); + return psVersion; + } - psVersion = value; - isPSVersionSpecified = true; + set + { + // PowerShell 7 remoting endpoints do not support PSVersion. + throw new PSNotSupportedException(RemotingErrorIdStrings.PowerShellVersionNotSupported); } } + internal Version psVersion; internal bool isPSVersionSpecified; /// - /// SessionTypeOption + /// SessionTypeOption. /// [Parameter(ParameterSetName = NameParameterSetName)] [Parameter(ParameterSetName = AssemblyNameParameterSetName)] @@ -2383,15 +2339,17 @@ public PSSessionTypeOption SessionTypeOption { return sessionTypeOption; } + set { sessionTypeOption = value; } } + internal PSSessionTypeOption sessionTypeOption; /// - /// TransportOption + /// TransportOption. /// [Parameter] public PSTransportOption TransportOption @@ -2400,25 +2358,27 @@ public PSTransportOption TransportOption { return transportOption; } + set { transportOption = value; } } + internal PSTransportOption transportOption; /// - /// ModulesToImport + /// ModulesToImport. /// [Parameter(ParameterSetName = NameParameterSetName)] [Parameter(ParameterSetName = AssemblyNameParameterSetName)] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ModulesToImport { get { return modulesToImport; } + set { List modulesToImportList = new List(); @@ -2442,9 +2402,9 @@ public object[] ModulesToImport // Getting the string value of the object if it is not a ModuleSpecification, this is required for the cases like PSObject is specified as ModulesToImport value. string modulepath = s.ToString(); // Add this check after checking if it a path - if (!String.IsNullOrEmpty(modulepath.Trim())) + if (!string.IsNullOrEmpty(modulepath.Trim())) { - if ((modulepath.Contains("\\") || modulepath.Contains(":")) && + if ((modulepath.Contains('\\') || modulepath.Contains(':')) && !(Directory.Exists(modulepath) || File.Exists(modulepath))) { throw new ArgumentException( @@ -2462,11 +2422,12 @@ public object[] ModulesToImport modulePathSpecified = true; } } + internal object[] modulesToImport; internal bool modulePathSpecified = false; /// - /// Declaration initial session config file path + /// Declaration initial session config file path. /// [Parameter(Mandatory = true, ParameterSetName = SessionConfigurationFileParameterSetName)] [ValidateNotNullOrEmpty] @@ -2482,7 +2443,7 @@ public object[] ModulesToImport protected bool RunAsVirtualAccountSpecified { get; set; } = false; /// - /// Comma delimited string specifying groups a virtual account is associated with + /// Comma delimited string specifying groups a virtual account is associated with. /// protected string RunAsVirtualAccountGroups { get; set; } @@ -2505,10 +2466,10 @@ internal PSSessionConfigurationCommandBase() #region Unregister-PSSessionConfiguration cmdlet /// - /// Class implementing Unregister-PSSessionConfiguration + /// Class implementing Unregister-PSSessionConfiguration. /// [Cmdlet(VerbsLifecycle.Unregister, RemotingConstants.PSSessionConfigurationNoun, - SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144308")] + SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096803")] public sealed class UnregisterPSSessionConfigurationCommand : PSCmdlet { #region Private Data @@ -2549,7 +2510,7 @@ function Unregister-PSSessionConfiguration if (($pluginFileName.Value -match 'system32\\{0}') -OR ($pluginFileName.Value -match 'syswow64\\{0}')) {{ - # Filter out WindowsPowerShell endpoints when running as PowerShell Core + # Filter out WindowsPowerShell endpoints when running as PowerShell 6+ return }} }} @@ -2591,6 +2552,7 @@ function Unregister-PSSessionConfiguration Unregister-PSSessionConfiguration -filter $args[0] -whatif:$args[1] -confirm:$args[2] -action $args[3] -targetTemplate $args[4] -shellNotErrMsgFormat $args[5] -force $args[6] -erroraction $args[7] }} "; + private static readonly ScriptBlock s_removePluginSb; private bool _isErrorReported; @@ -2618,11 +2580,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; /// @@ -2637,11 +2601,13 @@ public SwitchParameter NoServiceRestart { return _noRestart; } + set { _noRestart = value; } } + private bool _noRestart; #endregion @@ -2654,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; } @@ -2664,7 +2630,7 @@ static UnregisterPSSessionConfigurationCommand() #region Cmdlet Overrides /// - /// Verifies if remoting cmdlets can be used + /// Verifies if remoting cmdlets can be used. /// protected override void BeginProcessing() { @@ -2703,7 +2669,7 @@ protected override void ProcessRecord() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { Name, @@ -2736,9 +2702,9 @@ protected override void EndProcessing() #region Get-PSSessionConfiguration cmdlet /// - /// Class implementing Get-PSSessionConfiguration + /// Class implementing Get-PSSessionConfiguration. /// - [Cmdlet(VerbsCommon.Get, RemotingConstants.PSSessionConfigurationNoun, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144304")] + [Cmdlet(VerbsCommon.Get, RemotingConstants.PSSessionConfigurationNoun, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096790")] [OutputType("Microsoft.PowerShell.Commands.PSSessionConfigurationCommands#PSSessionConfiguration")] public sealed class GetPSSessionConfigurationCommand : PSCmdlet { @@ -2760,6 +2726,7 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) $s = $s.Replace(""'"", ""'""); $s = $s.Replace(""&"", ""&""); }} + return $s; }} @@ -2778,28 +2745,6 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) Get-Details $pluginDir $h - # Workflow is not supported in PowerShell Core. Attempting to load the - # assembly results in a FileNotFoundException. - if (![System.Management.Automation.Platform]::IsCoreCLR -AND - $h[""AssemblyName""] -eq ""Microsoft.PowerShell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"") {{ - - $serviceCore = [Reflection.Assembly]::Load(""Microsoft.Powershell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"") - - if ($null -ne $serviceCore) {{ - - $ci = new-Object system.management.automation.cmdletinfo ""New-PSWorkflowExecutionOptions"", ([Microsoft.PowerShell.Commands.NewPSWorkflowExecutionOptionCommand]) - $wf = [powershell]::Create(""currentrunspace"").AddCommand($ci).Invoke() - - if($null -ne $wf -and $wf.Count -ne 0) {{ - $wf = $wf[0] - - foreach ($o in $wf.GetType().GetProperties()) {{ - $h[$o.Name] = $o.GetValue($wf, $null) - }} - }} - }} - }} - if (test-path -LiteralPath $pluginDir\InitializationParameters\SessionConfigurationData) {{ $xscd = [xml](Unescape-xml (Unescape-xml (get-item -LiteralPath $pluginDir\InitializationParameters\SessionConfigurationData).Value)) @@ -2857,11 +2802,11 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) {{ # Filter the endpoints based on the typeof PowerShell that is # executing the cmdlet. {1} in another location indicates that it - # is a PowerShell Core endpoint + # is a PowerShell 6+ endpoint if (!($customPluginObject.FileName -match 'system32\\{1}') -AND # WindowsPowerShell !($customPluginObject.FileName -match 'syswow64\\{1}')) # WOW64 WindowsPowerShell {{ - # Add the PowerShell Core endpoint when running as PowerShell Core + # Add the PowerShell 6+ endpoint when running PowerShell 6+ $shellsFound++ $customPluginObject }} @@ -2877,6 +2822,7 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) "; private const string MODULEPATH = "ModulesToImport"; + private static readonly ScriptBlock s_getPluginSb; #endregion @@ -2890,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; } @@ -2901,13 +2847,12 @@ static GetPSSessionConfigurationCommand() /// /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Position = 0, Mandatory = false)] [ValidateNotNullOrEmpty()] public string[] Name { get; set; } /// - /// Force parameter + /// Force parameter. /// [Parameter()] public SwitchParameter Force @@ -2916,11 +2861,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; #endregion @@ -2928,7 +2875,7 @@ public SwitchParameter Force #region Cmdlet Overrides /// - /// Verifies if remoting cmdlets can be used + /// Verifies if remoting cmdlets can be used. /// protected override void BeginProcessing() { @@ -2944,7 +2891,7 @@ protected override void ProcessRecord() string csNotFoundMessageFormat = RemotingErrorIdStrings.CustomShellNotFound; object arguments = "*"; - if (null != Name) + if (Name != null) { arguments = Name; } @@ -2962,7 +2909,7 @@ protected override void ProcessRecord() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { arguments, @@ -2984,12 +2931,12 @@ protected override void ProcessRecord() #region Set-PSSessionConfiguration cmdlet /// - /// Class implementing Set-PSSessionConfiguration + /// Class implementing Set-PSSessionConfiguration. /// [Cmdlet(VerbsCommon.Set, RemotingConstants.PSSessionConfigurationNoun, DefaultParameterSetName = PSSessionConfigurationCommandBase.NameParameterSetName, SupportsShouldProcess = true, - ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144307")] + ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096901")] public sealed class SetPSSessionConfigurationCommand : PSSessionConfigurationCommandBase { #region Private Data @@ -2998,11 +2945,11 @@ public sealed class SetPSSessionConfigurationCommand : PSSessionConfigurationCom // To Escape } -- }} // To Escape " -- "" - private const string getSessionTypeFormat = @"(get-item 'WSMan::localhost\Plugin\{0}\InitializationParameters\sessiontype' -ErrorAction SilentlyContinue).Value"; private const string getCurrentIdleTimeoutmsFormat = @"(Get-Item 'WSMan:\localhost\Plugin\{0}\Quotas\IdleTimeoutms').Value"; private const string getAssemblyNameDataFormat = @"(Get-Item 'WSMan:\localhost\Plugin\{0}\InitializationParameters\assemblyname').Value"; private const string getSessionConfigurationDataSbFormat = @"(Get-Item 'WSMan:\localhost\Plugin\{0}\InitializationParameters\SessionConfigurationData').Value"; + private const string setSessionConfigurationDataSbFormat = @" function Set-SessionConfigurationData([string] $scd) {{ if (test-path 'WSMan:\localhost\Plugin\{0}\InitializationParameters\" + ConfigurationDataFromXML.SESSIONCONFIGTOKEN + @"') @@ -3025,6 +2972,7 @@ public sealed class SetPSSessionConfigurationCommand : PSSessionConfigurationCom if (!$value) {{ $value = [string]::empty; }} + set-item -WarningAction SilentlyContinue ('WSMan:\localhost\Plugin\{0}\Quotas\' + $name) -Value $value -confirm:$false }} }} @@ -3045,7 +2993,6 @@ public sealed class SetPSSessionConfigurationCommand : PSSessionConfigurationCom Set-SessionPluginIdleTimeoutQuotas $args[0] $args[1] $args[2] "; - private const string setSessionConfigurationOptionsSbFormat = @" function Set-SessionPluginOptions([hashtable] $options) {{ if ($options[""UsedSharedProcess""]) {{ @@ -3053,6 +3000,7 @@ public sealed class SetPSSessionConfigurationCommand : PSSessionConfigurationCom set-item -WarningAction SilentlyContinue 'WSMan:\localhost\Plugin\{0}\UseSharedProcess' -Value $value -confirm:$false $options.Remove(""UseSharedProcess""); }} + foreach($v in $options.GetEnumerator()) {{ $name = $v.Name; $value = $v.Value @@ -3120,7 +3068,7 @@ function Set-RunAsCredential{{ }} else {{ - # Filter out WindowsPowerShell endpoints when running as PowerShell Core + # Filter out WindowsPowerShell endpoints when running as PowerShell 6+ if (($pluginFileName.Value -match 'system32\\{0}') -OR ($pluginFileName.Value -match 'syswow64\\{0}')) {{ @@ -3219,6 +3167,7 @@ function Set-RunAsCredential{{ $sd.DiscretionaryAcl.AddAccess(""deny"", $networkSID, 268435456, ""None"", ""None"") $newSDDL = $sd.GetSddlForm(""all"") }} + if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode) -and $disableNetworkExists) {{ # Remove the specific ACE @@ -3266,6 +3215,7 @@ function Set-RunAsCredential{{ Set-PSSessionConfiguration $args[0] $args[1] $args[2] $args[3] $args[4] $args[5] $args[6] $args[7] $args[8] $args[9] $args[10] $args[11] "; + private const string initParamFormat = @""; private const string privateDataFormat = @"{0}"; @@ -3284,9 +3234,7 @@ function Set-RunAsCredential{{ ConfigurationDataFromXML.MAXRCVDOBJSIZETOKEN, ConfigurationDataFromXML.MAXRCVDCMDSIZETOKEN, ConfigurationDataFromXML.THREADOPTIONSTOKEN, -#if !CORECLR // No ApartmentState In CoreCLR ConfigurationDataFromXML.THREADAPTSTATETOKEN, -#endif ConfigurationDataFromXML.PSVERSIONTOKEN, ConfigurationDataFromXML.MAXPSVERSIONTOKEN, ConfigurationDataFromXML.SESSIONCONFIGTOKEN, @@ -3312,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; } @@ -3322,7 +3270,6 @@ static SetPSSessionConfigurationCommand() #region Cmdlet overrides /// - /// /// /// /// 1. Either both "AssemblyName" and "ConfigurationTypeName" must be specified @@ -3410,6 +3357,7 @@ protected override void BeginProcessing() descriptor.DiscretionaryAcl.AddAccess(AccessControlType.Deny, networkSidIdentifier, 268435456, InheritanceFlags.None, PropagationFlags.None); sddl = descriptor.GetSddlForm(AccessControlSections.All); } + break; case PSSessionConfigurationAccessMode.Remote: if (networkDenyAllExists) @@ -3427,6 +3375,7 @@ protected override void BeginProcessing() sddl = descriptor.GetSddlForm(AccessControlSections.All); } } + break; case PSSessionConfigurationAccessMode.Disabled: break; @@ -3439,7 +3388,6 @@ protected override void BeginProcessing() } /// - /// /// protected override void ProcessRecord() { @@ -3483,15 +3431,6 @@ protected override void ProcessRecord() { Dbg.Assert(false, "This should never happen. ps.Invoke always return a Collection"); } - - // UseSharedProcess Can not be False for Workflow session type configuration - // - if (UseSharedProcess == false && - psObjectCollection[0] != null && - String.Compare(psObjectCollection[0].ToString(), "Workflow", StringComparison.OrdinalIgnoreCase) == 0) - { - throw new PSInvalidOperationException(RemotingErrorIdStrings.UseSharedProcessCannotBeFalseForWorkflowSessionType); - } } } @@ -3519,7 +3458,6 @@ protected override void ProcessRecord() File.Copy(_configFilePath, destPath, true); } - string shellNotFoundErrorMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsShellNotFound, shellName); string shellNotPowerShellMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsShellNotPowerShellBased, shellName); string shellForPowerShellCoreMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsPowerShellCoreShellNotModifiable, shellName); @@ -3536,7 +3474,7 @@ protected override void ProcessRecord() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { propertiesToUpdate, @@ -3553,7 +3491,6 @@ protected override void ProcessRecord() accessModeSpecified ? AccessMode : PSSessionConfigurationAccessMode.Disabled, }); - errorList = (ArrayList)Context.DollarErrorVariable; if (errorList.Count > errorCountBefore) @@ -3620,6 +3557,7 @@ private void SetRunAs() if (this.runAsCredential == null) { if (string.IsNullOrEmpty(_gmsaAccount)) { return; } + runAsCredential = PSSessionConfigurationCommandUtilities.CreateGMSAAccountCredentials(_gmsaAccount); } @@ -3632,7 +3570,7 @@ private void SetRunAs() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { runAsCredential.UserName, @@ -3663,62 +3601,12 @@ 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: Utils.EmptyArray(), - 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, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { quotas, }); } @@ -3759,7 +3647,7 @@ Hashtable optionsTable useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { optionsTable, @@ -3778,6 +3666,7 @@ private void SetSessionConfigurationTypeOptions() { Dbg.Assert(false, "This should never happen. Plugin must exist because caller code has already checked this."); } + PSSessionConfigurationData scd = PSSessionConfigurationData.Create(psObjectCollection[0] == null ? string.Empty : PSSessionConfigurationData.Unescape(psObjectCollection[0].BaseObject.ToString())); PSSessionTypeOption original = sessionTypeOption.ConstructObjectFromPrivateData(scd.PrivateData); original.CopyUpdatedValuesFrom(sessionTypeOption); @@ -3785,28 +3674,18 @@ private void SetSessionConfigurationTypeOptions() StringBuilder sessionConfigurationData = new StringBuilder(); string modulePathParameter = null; - string unsetModulePathStr = string.Empty; bool unsetModulePath = false; if (modulePathSpecified) { - bool isWorkflowConfiguration = IsWorkflowConfigurationType(ps); if (modulesToImport == null || modulesToImport.Length == 0 || (modulesToImport.Length == 1 && modulesToImport[0] is string && ((string)modulesToImport[0]).Equals(string.Empty, StringComparison.OrdinalIgnoreCase))) { unsetModulePath = true; - unsetModulePathStr = isWorkflowConfiguration ? ConfigurationDataFromXML.PSWORKFLOWMODULE : string.Empty; } else { modulePathParameter = PSSessionConfigurationCommandUtilities.GetModulePathAsString(this.modulesToImport).Trim(); - // Add the built-in module path if it's a workflow config - if (!string.IsNullOrEmpty(modulePathParameter) && isWorkflowConfiguration) - { - List modifiedModulePath = new List(modulesToImport); - modifiedModulePath.Insert(0, ConfigurationDataFromXML.PSWORKFLOWMODULE); - modulePathParameter = PSSessionConfigurationCommandUtilities.GetModulePathAsString(modifiedModulePath.ToArray()).Trim(); - } } } @@ -3827,7 +3706,7 @@ private void SetSessionConfigurationTypeOptions() { sessionConfigurationData.Append(string.Format(CultureInfo.InvariantCulture, initParamFormat, - PSSessionConfigurationData.ModulesToImportToken, unsetModulePathStr)); + PSSessionConfigurationData.ModulesToImportToken, string.Empty)); } // unsetModulePath is false AND modulePathParameter is not empty. // 1. modulePathSpecified is false. In this case, modulePathParameter will be the original module path. @@ -3847,6 +3726,7 @@ private void SetSessionConfigurationTypeOptions() { sessionConfigurationData.Append(string.Format(CultureInfo.InvariantCulture, privateDataFormat, privateData)); } + if (sessionConfigurationData.Length > 0) { string sessionConfigData = string.Format(CultureInfo.InvariantCulture, @@ -3863,7 +3743,7 @@ private void SetSessionConfigurationTypeOptions() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { encodedSessionConfigData, @@ -3883,6 +3763,7 @@ protected override void EndProcessing() string action = StringUtil.Format(RemotingErrorIdStrings.CSShouldProcessAction, this.CommandInfo.Name); WriteWarning(StringUtil.Format(RemotingErrorIdStrings.WinRMRequiresRestart, action)); } + System.Management.Automation.Tracing.Tracer tracer = new System.Management.Automation.Tracing.Tracer(); tracer.EndpointModified(this.Name, WindowsIdentity.GetCurrent().Name); } @@ -3891,30 +3772,6 @@ protected override void EndProcessing() #region Private Methods - /// - /// Check if the current configuration is a workflow endpoint - /// - /// - private bool IsWorkflowConfigurationType(System.Management.Automation.PowerShell ps) - { - // Get the AssemblyName - ps.AddScript(string.Format(CultureInfo.InvariantCulture, getAssemblyNameDataFormat, 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"); - } - - if (psObjectCollection[0] == null) - { - // Not workflow endpoint, no assembly name - return false; - } - - string assemblyNameOfCurrentConfiguration = psObjectCollection[0].BaseObject.ToString(); - return assemblyNameOfCurrentConfiguration.Equals(ConfigurationDataFromXML.WORKFLOWCOREASSEMBLY, StringComparison.OrdinalIgnoreCase); - } - private PSObject ConstructPropertiesForUpdate() { PSObject result = new PSObject(); @@ -3952,12 +3809,10 @@ private PSObject ConstructPropertiesForUpdate() result.Properties.Add(new PSNoteProperty(ConfigurationDataFromXML.MAXRCVDOBJSIZETOKEN, input)); } -#if !CORECLR // No ApartmentState In CoreCLR if (threadAptState.HasValue) { result.Properties.Add(new PSNoteProperty(ConfigurationDataFromXML.THREADAPTSTATETOKEN, threadAptState.Value)); } -#endif if (threadOptions.HasValue) { @@ -4000,14 +3855,6 @@ private PSObject ConstructPropertiesForUpdate() { using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) { - bool isWorkflowConfiguration = IsWorkflowConfigurationType(ps); - if (!string.IsNullOrEmpty(modulePathParameter) && isWorkflowConfiguration) - { - List modifiedModulePath = new List(modulesToImport); - modifiedModulePath.Insert(0, ConfigurationDataFromXML.PSWORKFLOWMODULE); - modulePathParameter = PSSessionConfigurationCommandUtilities.GetModulePathAsString(modifiedModulePath.ToArray()).Trim(); - } - // Get the SessionConfigurationDataFormat ps.AddScript(string.Format(CultureInfo.InvariantCulture, getSessionConfigurationDataSbFormat, CodeGeneration.EscapeSingleQuotedStringContent(Name))); Collection psObjectCollection = ps.Invoke(new object[] { Name }) as Collection; @@ -4015,6 +3862,7 @@ private PSObject ConstructPropertiesForUpdate() { Dbg.Assert(false, "This should never happen. ps.Invoke always return a Collection"); } + StringBuilder sessionConfigurationData = new StringBuilder(); // SessionConfigurationData doesn't exist in InitializationParameters @@ -4044,10 +3892,9 @@ private PSObject ConstructPropertiesForUpdate() // ModulesToImport exist in the pssessionConfigurationData if (scd.ModulesToImportInternal != null && scd.ModulesToImportInternal.Count != 0) { - string unsetModulePathStr = isWorkflowConfiguration ? ConfigurationDataFromXML.PSWORKFLOWMODULE : string.Empty; sessionConfigurationData.Append(string.Format(CultureInfo.InvariantCulture, initParamFormat, - PSSessionConfigurationData.ModulesToImportToken, unsetModulePathStr)); + PSSessionConfigurationData.ModulesToImportToken, string.Empty)); if (!string.IsNullOrEmpty(privateData)) { sessionConfigurationData.Append(string.Format(CultureInfo.InvariantCulture, privateDataFormat, privateData)); @@ -4078,9 +3925,9 @@ private PSObject ConstructPropertiesForUpdate() string encodedSessionConfigData = SecurityElement.Escape(sessionConfigData); result.Properties.Add(new PSNoteProperty(ConfigurationDataFromXML.SESSIONCONFIGTOKEN, encodedSessionConfigData)); } - } // end of using (...) - } // end of if (unsetModulePath || !string.IsNullOrEmpty(modulePathParameter)) - } // end of if (modulePathSpecified) + } + } + } // DISC endpoint if (Path != null) @@ -4130,10 +3977,10 @@ private PSObject ConstructPropertiesForUpdate() #region Enable/Disable-PSSessionConfiguration /// - /// Class implementing Enable-PSSessionConfiguration cmdlet + /// Class implementing Enable-PSSessionConfiguration cmdlet. /// [Cmdlet(VerbsLifecycle.Enable, RemotingConstants.PSSessionConfigurationNoun, - SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144301")] + SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096785")] public sealed class EnablePSSessionConfigurationCommand : PSCmdlet { #region Private Data @@ -4146,6 +3993,11 @@ public sealed class EnablePSSessionConfigurationCommand : PSCmdlet function Test-WinRMQuickConfigNeeded {{ + # 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 + # Checking the following items #1. Starting or restarting (if already started) the WinRM service #2. Setting the WinRM service startup type to Automatic @@ -4259,6 +4111,7 @@ function Enable-PSSessionConfiguration {{ {0} -force }} + if ($svc.Status -match ""Running"") {{ Restart-Service winrm -force -confirm:$false @@ -4345,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 @@ -4357,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; } @@ -4367,14 +4220,13 @@ static EnablePSSessionConfigurationCommand() #region Parameters /// - /// Configurations to Enable + /// Configurations to Enable. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [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 @@ -4388,18 +4240,19 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; /// /// This enables the user to specify an SDDL for whom the session /// configuration is enabled. /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sddl")] [Parameter()] public string SecurityDescriptorSddl { @@ -4407,6 +4260,7 @@ public string SecurityDescriptorSddl { return sddl; } + set { if (!string.IsNullOrEmpty(value)) @@ -4415,7 +4269,7 @@ public string SecurityDescriptorSddl CommonSecurityDescriptor c = new CommonSecurityDescriptor(false, false, value); // this will never be the case..as constructor either constructs or throws. // this is used here to avoid FxCop violation. - if (null == c) + if (c == null) { throw new NotSupportedException(); } @@ -4425,6 +4279,7 @@ public string SecurityDescriptorSddl isSddlSpecified = true; } } + internal string sddl; internal bool isSddlSpecified; @@ -4436,8 +4291,10 @@ public string SecurityDescriptorSddl public SwitchParameter SkipNetworkProfileCheck { get { return _skipNetworkProfileCheck; } + set { _skipNetworkProfileCheck = value; } } + private bool _skipNetworkProfileCheck; /// @@ -4449,8 +4306,10 @@ public SwitchParameter SkipNetworkProfileCheck public SwitchParameter NoServiceRestart { get { return _noRestart; } + set { _noRestart = value; } } + private bool _noRestart; #endregion @@ -4458,7 +4317,6 @@ public SwitchParameter NoServiceRestart #region Cmdlet Overrides /// - /// /// /// /// 1. Either both "AssemblyName" and "ConfigurationTypeName" must be specified @@ -4472,7 +4330,6 @@ protected override void BeginProcessing() } /// - /// /// protected override void ProcessRecord() { @@ -4486,7 +4343,6 @@ protected override void ProcessRecord() } /// - /// /// protected override void EndProcessing() { @@ -4518,7 +4374,7 @@ protected override void EndProcessing() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: _shellsToEnable, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { _force, @@ -4539,7 +4395,7 @@ protected override void EndProcessing() System.Management.Automation.Tracing.Tracer tracer = new System.Management.Automation.Tracing.Tracer(); StringBuilder sb = new StringBuilder(); - foreach (string endPointName in Name ?? Utils.EmptyArray()) + foreach (string endPointName in Name ?? Array.Empty()) { sb.Append(endPointName); sb.Append(", "); @@ -4557,10 +4413,9 @@ protected override void EndProcessing() } /// - /// /// [Cmdlet(VerbsLifecycle.Disable, RemotingConstants.PSSessionConfigurationNoun, - SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144299")] + SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096692")] public sealed class DisablePSSessionConfigurationCommand : PSCmdlet { #region Private Data @@ -4629,7 +4484,8 @@ 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 @@ -4641,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; } @@ -4651,14 +4507,13 @@ static DisablePSSessionConfigurationCommand() #region Parameters /// - /// Configurations to Enable + /// Configurations to Enable. /// - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [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 @@ -4672,11 +4527,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; /// @@ -4688,8 +4545,10 @@ public SwitchParameter Force public SwitchParameter NoServiceRestart { get { return _noRestart; } + set { _noRestart = value; } } + private bool _noRestart; #endregion @@ -4697,7 +4556,6 @@ public SwitchParameter NoServiceRestart #region Cmdlet Overrides /// - /// /// /// /// 1. Either both "AssemblyName" and "ConfigurationTypeName" must be specified @@ -4711,7 +4569,6 @@ protected override void BeginProcessing() } /// - /// /// protected override void ProcessRecord() { @@ -4725,7 +4582,6 @@ protected override void ProcessRecord() } /// - /// /// protected override void EndProcessing() { @@ -4735,7 +4591,7 @@ protected override void EndProcessing() _shellsToDisable.Add(PSSessionConfigurationCommandUtilities.GetWinrmPluginShellName()); } - //WriteWarning(StringUtil.Format(RemotingErrorIdStrings.DcsWarningMessage)); + // WriteWarning(StringUtil.Format(RemotingErrorIdStrings.DcsWarningMessage)); WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.EcsScriptMessageV, disablePluginSbFormat)); // gather -WhatIf, -Confirm parameter data and pass it to the script block @@ -4753,7 +4609,7 @@ protected override void EndProcessing() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: _shellsToDisable, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { _force, @@ -4767,7 +4623,7 @@ protected override void EndProcessing() System.Management.Automation.Tracing.Tracer tracer = new System.Management.Automation.Tracing.Tracer(); StringBuilder sb = new StringBuilder(); - foreach (string endPointName in Name ?? Utils.EmptyArray()) + foreach (string endPointName in Name ?? Array.Empty()) { sb.Append(endPointName); sb.Append(", "); @@ -4789,10 +4645,9 @@ protected override void EndProcessing() #region Enable-PSRemoting /// - /// /// [Cmdlet(VerbsLifecycle.Enable, RemotingConstants.PSRemotingNoun, - SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144300")] + SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096577")] public sealed class EnablePSRemotingCommand : PSCmdlet { #region Private Data @@ -4801,18 +4656,123 @@ public sealed class EnablePSRemotingCommand : PSCmdlet // To Escape } -- }} // To Escape " -- "" - //TODO: CLR4: Remove the logic for setting the MaxMemoryPerShellMB to 200 MB once IPMO->Get-Command->Get-Help memory usage issue is fixed. + // TODO: CLR4: Remove the logic for setting the MaxMemoryPerShellMB to 200 MB once IPMO->Get-Command->Get-Help memory usage issue is fixed. private const string enableRemotingSbFormat = @" -function Generate-PluginConfigFile +Set-StrictMode -Version Latest + +function New-PluginConfigFile {{ +[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=""Medium"")] param( [Parameter()] [string] $pluginInstallPath ) $pluginConfigFile = Join-Path $pluginInstallPath ""RemotePowerShellConfig.txt"" # This always overwrites the file with a new version of it (if it already exists) - Set-Content -Path $pluginConfigFile -Value ""PSHOMEDIR=$PSHOME"" - Add-Content -Path $pluginConfigFile -Value ""CORECLRDIR=$PSHOME"" + Set-Content -Path $pluginConfigFile -Value ""PSHOMEDIR=$PSHOME"" -ErrorAction Stop + Add-Content -Path $pluginConfigFile -Value ""CORECLRDIR=$PSHOME"" -ErrorAction Stop +}} + +function Copy-PluginToEndpoint +{{ +param( + [Parameter()] [string] $endpointDir +) + $resolvedPluginInstallPath = """" + $pluginInstallPath = Join-Path ([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Windows) + ""\System32\PowerShell"") $endpointDir + if (!(Test-Path $pluginInstallPath)) + {{ + $resolvedPluginInstallPath = New-Item -Type Directory -Path $pluginInstallPath + }} + else + {{ + $resolvedPluginInstallPath = Resolve-Path $pluginInstallPath + }} + + if (!(Test-Path $resolvedPluginInstallPath\{4})) + {{ + Copy-Item -Path $PSHOME\{4} -Destination $resolvedPluginInstallPath -Force -ErrorAction Stop + if (!(Test-Path $resolvedPluginInstallPath\{4})) + {{ + Write-Error ($errorMsgUnableToInstallPlugin -f ""{4}"", $resolvedPluginInstallPath) + return $null + }} + }} + + return $resolvedPluginInstallPath +}} + +function Register-Endpoint +{{ +param( + [Parameter()] [string] $configurationName +) + # + # Section 1: + # Move pwrshplugin.dll from $PSHOME to the endpoint directory + # + # The plugin directory pattern for endpoint configuration is: + # '$env:WINDIR\System32\PowerShell\' + powershell_version, + # so we call Copy-PluginToEndpoint function only with the PowerShell version argument. + + $pwshVersion = $configurationName.Replace(""PowerShell."", """") + $resolvedPluginInstallPath = Copy-PluginToEndpoint $pwshVersion + if (!$resolvedPluginInstallPath) {{ + return + }} + + # + # Section 2: + # Generate the Plugin Configuration File + # + New-PluginConfigFile $resolvedPluginInstallPath + + # + # Section 3: + # Register the endpoint + # + $null = Register-PSSessionConfiguration -Name $configurationName -force -ErrorAction Stop + + set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\$configurationName\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false + set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\$configurationName\Quotas\MaxIdleTimeoutms -value {3} -confirm:$false + restart-service winrm -confirm:$false +}} + +function Register-EndpointIfNotPresent +{{ +[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=""Medium"")] +param( + [Parameter()] [string] $Name, + [Parameter()] [bool] $Force, + [Parameter()] [string] $queryForRegisterDefault, + [Parameter()] [string] $captionForRegisterDefault +) + # + # This cmdlet will make sure default powershell end points exist upon successful completion. + # + # Windows PowerShell: + # Microsoft.PowerShell + # Microsoft.PowerShell32 (wow64) + # + # PowerShell: + # PowerShell. + # + $errorCount = $error.Count + $endPoint = Get-PSSessionConfiguration $Name -Force:$Force -ErrorAction silentlycontinue 2>&1 + $newErrorCount = $error.Count + + # remove the 'No Session Configuration matches criteria' errors + for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++) + {{ + $error.RemoveAt(0) + }} + + $qMessage = $queryForRegisterDefault -f ""$Name"",""Register-PSSessionConfiguration {0} -force"" + if ((!$endpoint) -and + ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) + {{ + Register-Endpoint $Name + }} }} function Enable-PSRemoting @@ -4843,129 +4803,24 @@ function Enable-PSRemoting # first try to enable all the sessions Enable-PSSessionConfiguration @PSBoundParameters - # - # This cmdlet will make sure default powershell end points exist upon successful completion. - # - # Windows PowerShell: - # Microsoft.PowerShell - # Microsoft.PowerShell32 (wow64) - # - # PowerShell Core: - # PowerShell. - # - $errorCount = $error.Count - $endPoint = Get-PSSessionConfiguration {0} -Force:$Force -ErrorAction silentlycontinue 2>&1 - $newErrorCount = $error.Count - - # remove the 'No Session Configuration matches criteria' errors - for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++) - {{ - $error.RemoveAt(0) - }} - - $qMessage = $queryForRegisterDefault -f ""{0}"",""Register-PSSessionConfiguration {0} -force"" - if ((!$endpoint) -and - ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) - {{ - $resolvedPluginInstallPath = """" - # - # Section 1: - # Move pwrshplugin.dll from $PSHOME to the endpoint directory - # - $pluginInstallPath = Join-Path ""$env:WINDIR\System32\PowerShell"" $psversiontable.GitCommitId - if (!(Test-Path $pluginInstallPath)) - {{ - $resolvedPluginInstallPath = New-Item -Type Directory -Path $pluginInstallPath - }} - else - {{ - $resolvedPluginInstallPath = Resolve-Path $pluginInstallPath - }} - if (!(Test-Path $resolvedPluginInstallPath\{5})) - {{ - Copy-Item $PSHOME\{5} $resolvedPluginInstallPath -Force - if (!(Test-Path $resolvedPluginInstallPath\{5})) - {{ - Write-Error ($errorMsgUnableToInstallPlugin -f ""{5}"", $resolvedPluginInstallPath) - return - }} - }} + Register-EndpointIfNotPresent -Name {0} $Force $queryForRegisterDefault $captionForRegisterDefault - # - # Section 2: - # Generate the Plugin Configuration File - # - Generate-PluginConfigFile $resolvedPluginInstallPath - - # - # Section 3: - # Register the endpoint - # - $null = Register-PSSessionConfiguration -Name {0} -force - - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false - restart-service winrm -confirm:$false + # Create the default PSSession configuration, not tied to specific PowerShell version + # e. g. 'PowerShell.6'. + $powershellVersionMajor = $PSVersionTable.PSVersion.ToString() + $dotPos = $powershellVersionMajor.IndexOf(""."") + if ($dotPos -ne -1) {{ + $powershellVersionMajor = $powershellVersionMajor.Substring(0, $dotPos) }} - - # PowerShell Workflow and WOW are not supported for PowerShell Core - if (![System.Management.Automation.Platform]::IsCoreCLR) + # If we are running a Preview version, we don't want to clobber the generic PowerShell.6 endpoint + # but instead create a PowerShell.6-Preview endpoint + if ($PSVersionTable.PSVersion.PreReleaseLabel) {{ - # Check Microsoft.PowerShell.Workflow endpoint - $errorCount = $error.Count - $endPoint = Get-PSSessionConfiguration {0}.workflow -Force:$Force -ErrorAction silentlycontinue 2>&1 - $newErrorCount = $error.Count - - # remove the 'No Session Configuration matches criteria' errors - for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++) - {{ - $error.RemoveAt(0) - }} - - if (!$endpoint) - {{ - $qMessage = $queryForRegisterDefault -f ""Microsoft.PowerShell.Workflow"",""Register-PSSessionConfiguration Microsoft.PowerShell.Workflow -force"" - if ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault)) {{ - $tempxmlfile = [io.path]::Gettempfilename() - ""{1}"" | out-file -force -filepath $tempxmlfile -confirm:$false - $null = winrm create winrm/config/plugin?Name=Microsoft.PowerShell.Workflow -file:$tempxmlfile - remove-item -path $tempxmlfile -force -confirm:$false - restart-service winrm -confirm:$false - }} - }} - - $pa = $env:PROCESSOR_ARCHITECTURE - if ($pa -eq ""x86"") - {{ - # on 64-bit platforms, wow64 bit process has the correct architecture - # available in processor_architew6432 variable - $pa = $env:PROCESSOR_ARCHITEW6432 - }} - if ((($pa -eq ""amd64"")) -and (test-path $env:windir\syswow64\pwrshplugin.dll)) - {{ - # Check availability of WOW64 endpoint. Register if not available. - $errorCount = $error.Count - $endPoint = Get-PSSessionConfiguration {0}32 -Force:$Force -ErrorAction silentlycontinue 2>&1 - $newErrorCount = $error.Count - - # remove the 'No Session Configuration matches criteria' errors - for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++) - {{ - $error.RemoveAt(0) - }} - - $qMessage = $queryForRegisterDefault -f ""{0}32"",""Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force"" - if ((!$endpoint) -and - ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) - {{ - $null = Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false - restart-service winrm -confirm:$false - }} - }} + $powershellVersionMajor += ""-preview"" }} + Register-EndpointIfNotPresent -Name (""PowerShell."" + $powershellVersionMajor) $Force $queryForRegisterDefault $captionForRegisterDefault + # remove the 'network deny all' tag Get-PSSessionConfiguration -Force:$Force | ForEach-Object {{ $sddl = $null @@ -5006,12 +4861,12 @@ function Enable-PSRemoting # Remote Management Users, Win8+ only if ([System.Environment]::OSVersion.Version -ge ""6.2.0.0"") {{ - $rmSidId = new-object system.security.principal.securityidentifier ""{2}"" + $rmSidId = new-object system.security.principal.securityidentifier ""{1}"" $sd.DiscretionaryAcl.AddAccess('Allow', $rmSidId, 268435456, 'none', 'none') }} # Interactive Users - $iaSidId = new-object system.security.principal.securityidentifier ""{3}"" + $iaSidId = new-object system.security.principal.securityidentifier ""{2}"" $sd.DiscretionaryAcl.AddAccess('Allow', $iaSidId, 268435456, 'none', 'none') }} @@ -5035,45 +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 const string _workflowConfigXml = @" - - - - - - - - - - - - - - - -"; - - private static ScriptBlock s_enableRemotingSb; + private static readonly ScriptBlock s_enableRemotingSb; #endregion @@ -5081,19 +4898,13 @@ function Enable-PSRemoting static EnablePSRemotingCommand() { - string workflowConfigXml = string.Format(CultureInfo.InvariantCulture, _workflowConfigXml, - string.Format(CultureInfo.InvariantCulture, "{0}.{1}", PSVersionInfo.PSVersion.Major, PSVersionInfo.PSVersion.Minor), - PSSessionConfigurationCommandBase.GetLocalSddl()); - string enableRemotingScript = string.Format(CultureInfo.InvariantCulture, enableRemotingSbFormat, PSSessionConfigurationCommandUtilities.GetWinrmPluginShellName(), - // Workflow endpoint configuration will be done through Register-PSSessionConfiguration - // when the new features are available. - workflowConfigXml, PSSessionConfigurationCommandBase.RemoteManagementUsersSID, PSSessionConfigurationCommandBase.InteractiveUsersSID, + PSSessionConfigurationCommandBase.RemoteManagementUsersSID, PSSessionConfigurationCommandBase.InteractiveUsersSID, 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; } @@ -5114,11 +4925,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; /// @@ -5129,8 +4942,10 @@ public SwitchParameter Force public SwitchParameter SkipNetworkProfileCheck { get { return _skipNetworkProfileCheck; } + set { _skipNetworkProfileCheck = value; } } + private bool _skipNetworkProfileCheck; #endregion @@ -5138,7 +4953,6 @@ public SwitchParameter SkipNetworkProfileCheck #region Cmdlet Overrides /// - /// /// /// /// 1. Either both "AssemblyName" and "ConfigurationTypeName" must be specified @@ -5152,10 +4966,11 @@ protected override void BeginProcessing() } /// - /// /// protected override void EndProcessing() { + WriteWarning(RemotingErrorIdStrings.PSCoreRemotingEnableWarning); + // gather -WhatIf, -Confirm parameter data and pass it to the script block bool whatIf = false; // confirm is always true to start with @@ -5173,7 +4988,7 @@ protected override void EndProcessing() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { _force, @@ -5197,13 +5012,13 @@ protected override void EndProcessing() /// /// Disable-PSRemoting cmdlet /// Only disable the network access to the Session Configuration. The - /// local access is still enabled + /// local access is still enabled. /// [Cmdlet(VerbsLifecycle.Disable, RemotingConstants.PSRemotingNoun, - SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144298")] + SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096482")] public sealed class DisablePSRemotingCommand : PSCmdlet { - # region Private Data + #region Private Data // To Escape { -- {{ // To Escape } -- }} @@ -5300,7 +5115,8 @@ 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 @@ -5312,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; } @@ -5322,7 +5138,7 @@ static DisablePSRemotingCommand() #region Parameters /// - /// Force parameter + /// Force parameter. /// [Parameter()] public SwitchParameter Force @@ -5331,11 +5147,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force; #endregion Parameters @@ -5343,7 +5161,7 @@ public SwitchParameter Force #region Cmdlet Override /// - /// Check for prerequisites and elevation mode + /// Check for prerequisites and elevation mode. /// protected override void BeginProcessing() { @@ -5353,10 +5171,11 @@ protected override void BeginProcessing() } /// - /// Invoke Disable-PSRemoting + /// Invoke Disable-PSRemoting. /// protected override void EndProcessing() { + WriteWarning(RemotingErrorIdStrings.PSCoreRemotingDisableWarning); WriteWarning(StringUtil.Format(RemotingErrorIdStrings.DcsWarningMessage)); WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.EcsScriptMessageV, disablePSRemotingFormat)); @@ -5375,7 +5194,7 @@ protected override void EndProcessing() useLocalScope: true, errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, dollarUnder: AutomationNull.Value, - input: Utils.EmptyArray(), + input: Array.Empty(), scriptThis: AutomationNull.Value, args: new object[] { _force, @@ -5401,13 +5220,13 @@ protected override void EndProcessing() public sealed class GetPSSessionCapabilityCommand : PSCmdlet { /// - /// Gets or sets the session name that should be queried for its session capabilities + /// Gets or sets the session name that should be queried for its session capabilities. /// [Parameter(Mandatory = true, Position = 0)] public string ConfigurationName { get; set; } /// - /// Gets or sets the user name that should be applied to the session + /// Gets or sets the user name that should be applied to the session. /// [Parameter(Mandatory = true, Position = 1)] public string Username { get; set; } @@ -5419,7 +5238,6 @@ public sealed class GetPSSessionCapabilityCommand : PSCmdlet [Parameter()] public SwitchParameter Full { get; set; } - /// /// protected override void BeginProcessing() @@ -5446,16 +5264,16 @@ 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)) + if (!string.IsNullOrEmpty(this.Username)) { - if (this.Username.IndexOf("\\", StringComparison.OrdinalIgnoreCase) >= 0) + if (this.Username.Contains('\\')) { 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]; @@ -5528,7 +5346,6 @@ namespace Microsoft.PowerShell.Commands.Internal /// /// This class is public for implementation reasons only and should not be used. /// - [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed for resources from scripts")] public static class RemotingErrorResources { /// diff --git a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs index 449c4011f3d..ece0ce45dbf 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DebugJob.cs @@ -1,14 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; using System.Management.Automation.Remoting.Internal; +using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.Commands { @@ -23,7 +22,6 @@ namespace Microsoft.PowerShell.Commands /// When a job is debugged its output data is written to host and the executing job /// script will break into the host debugger, in step mode, at the next stoppable /// execution point. - /// /// [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] [Cmdlet(VerbsDiagnostic.Debug, "Job", SupportsShouldProcess = true, DefaultParameterSetName = DebugJobCommand.JobParameterSet, @@ -99,6 +97,12 @@ public Guid InstanceId set; } + /// + /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target. + /// + [Parameter] + public SwitchParameter BreakAll { get; set; } + #endregion #region Overrides @@ -179,7 +183,7 @@ protected override void EndProcessing() // Set up host script debugger to debug the job. _debugger = runspace.Debugger; - _debugger.DebugJob(_job); + _debugger.DebugJob(_job, breakAll: BreakAll); // Blocking call. Send job output to host UI while debugging and wait for Job completion. WaitAndReceiveJobOutput(); @@ -199,10 +203,7 @@ protected override void StopProcessing() // Unblock the data collection. PSDataCollection debugCollection = _debugCollection; - if (debugCollection != null) - { - debugCollection.Complete(); - } + debugCollection?.Complete(); } #endregion @@ -217,7 +218,7 @@ protected override void StopProcessing() private bool CheckForDebuggableJob() { // Check passed in job object. - bool debuggableJobFound = GetJobDebuggable(_job); ; + bool debuggableJobFound = GetJobDebuggable(_job); if (!debuggableJobFound) { @@ -225,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) { @@ -256,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 78b48f48198..74bcbac2c33 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -11,6 +10,7 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -35,12 +35,11 @@ namespace Microsoft.PowerShell.Commands /// > Disconnect-PSSession -Id $session.Id /// /// Disconnect a collection of PS sessions: - /// > Get-PSSession | Disconnect-PSSession - /// + /// > Get-PSSession | Disconnect-PSSession. /// [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] [Cmdlet(VerbsCommunications.Disconnect, "PSSession", SupportsShouldProcess = true, DefaultParameterSetName = DisconnectPSSessionCommand.SessionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=210605", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096576", RemotingCapability = RemotingCapability.OwnedByCommand)] [OutputType(typeof(PSSession))] public class DisconnectPSSessionCommand : PSRunspaceCmdlet, IDisposable { @@ -69,6 +68,7 @@ public class DisconnectPSSessionCommand : PSRunspaceCmdlet, IDisposable public int IdleTimeoutSec { get { return this.PSSessionOption.IdleTimeout.Seconds; } + set { this.PSSessionOption.IdleTimeout = TimeSpan.FromSeconds(value); } } @@ -82,6 +82,7 @@ public int IdleTimeoutSec public OutputBufferingMode OutputBufferingMode { get { return this.PSSessionOption.OutputBufferingMode; } + set { this.PSSessionOption.OutputBufferingMode = value; } } @@ -94,13 +95,13 @@ public OutputBufferingMode OutputBufferingMode [Parameter(ParameterSetName = PSRunspaceCmdlet.NameParameterSet)] [Parameter(ParameterSetName = PSRunspaceCmdlet.IdParameterSet)] [Parameter(ParameterSetName = PSRunspaceCmdlet.InstanceIdParameterSet)] - public Int32 ThrottleLimit { get; set; } = 0; + public int ThrottleLimit { get; set; } = 0; /// /// Disconnect-PSSession does not support ComputerName parameter set. /// This may change for later versions. /// - public override String[] ComputerName { get; set; } + public override string[] ComputerName { get; set; } private PSSessionOption PSSessionOption { @@ -108,13 +109,14 @@ 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(); } } + private PSSessionOption _sessionOption; /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] ContainerId { @@ -125,7 +127,7 @@ public override string[] ContainerId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override Guid[] VMId { @@ -136,7 +138,7 @@ public override Guid[] VMId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] VMName { @@ -158,7 +160,7 @@ protected override void BeginProcessing() base.BeginProcessing(); _throttleManager.ThrottleLimit = ThrottleLimit; - _throttleManager.ThrottleComplete += new EventHandler(HandleThrottleDisconnectComplete); + _throttleManager.ThrottleComplete += HandleThrottleDisconnectComplete; } /// @@ -307,7 +309,7 @@ protected override void EndProcessing() // Read all objects in the stream pipeline. while (!_stream.ObjectReader.EndOfPipeline) { - Object streamObject = _stream.ObjectReader.Read(); + object streamObject = _stream.ObjectReader.Read(); WriteStreamObject((Action)streamObject); } } @@ -332,8 +334,8 @@ protected override void StopProcessing() /// /// Handles the connect throttling complete event from the ThrottleManager. /// - /// Sender - /// EventArgs + /// Sender. + /// EventArgs. private void HandleThrottleDisconnectComplete(object sender, EventArgs eventArgs) { _stream.ObjectWriter.Close(); @@ -344,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)) @@ -361,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(); @@ -390,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) { @@ -479,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); } } @@ -501,12 +500,10 @@ private void WriteDisconnectFailed(Exception e = null) { msg = StringUtil.Format(RemotingErrorIdStrings.RunspaceDisconnectFailed, _remoteSession.InstanceId); } + 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); } } @@ -519,7 +516,7 @@ private void WriteDisconnectFailed(Exception e = null) /// /// Dispose method of IDisposable. Gets called in the following cases: /// 1. Pipeline explicitly calls dispose on cmdlets - /// 2. Called by the garbage collector + /// 2. Called by the garbage collector. /// public void Dispose() { @@ -530,7 +527,7 @@ public void Dispose() /// /// Internal dispose method which does the actual - /// dispose operations and finalize suppressions + /// dispose operations and finalize suppressions. /// /// Whether method is called /// from Dispose or destructor @@ -543,7 +540,7 @@ private void Dispose(bool disposing) _operationsComplete.WaitOne(); _operationsComplete.Dispose(); - _throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleDisconnectComplete); + _throttleManager.ThrottleComplete -= HandleThrottleDisconnectComplete; _stream.Dispose(); } } @@ -553,15 +550,15 @@ 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 - } // DisconnectPSSessionCommand + } } diff --git a/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs b/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs index 227f6168d78..f7aef0530b8 100644 --- a/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs @@ -1,18 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Diagnostics; 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 System.Management.Automation.Runspaces; using System.Management.Automation.Remoting; -using System.Diagnostics.CodeAnalysis; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Security; +using System.Text; namespace Microsoft.PowerShell.Commands { @@ -24,7 +26,7 @@ namespace Microsoft.PowerShell.Commands /// then an error message will result. /// [Cmdlet(VerbsCommon.Enter, "PSHostProcess", DefaultParameterSetName = EnterPSHostProcessCommand.ProcessIdParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkId=403736")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096580")] public sealed class EnterPSHostProcessCommand : PSCmdlet { #region Members @@ -37,6 +39,7 @@ public sealed class EnterPSHostProcessCommand : PSCmdlet private const string ProcessParameterSet = "ProcessParameterSet"; private const string ProcessNameParameterSet = "ProcessNameParameterSet"; private const string ProcessIdParameterSet = "ProcessIdParameterSet"; + private const string PipeNameParameterSet = "PipeNameParameterSet"; private const string PSHostProcessInfoParameterSet = "PSHostProcessInfoParameterSet"; private const string NamedPipeRunspaceName = "PSAttachRunspace"; @@ -51,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; @@ -73,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; @@ -84,13 +87,23 @@ 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; set; } + /// + /// Gets or sets the custom named pipe name to connect to. This is usually used in conjunction with `pwsh -CustomPipeName`. + /// + [Parameter(Mandatory = true, ParameterSetName = EnterPSHostProcessCommand.PipeNameParameterSet)] + public string CustomPipeName + { + get; + set; + } + /// /// Optional name of AppDomain in process to enter. If not specified then the default AppDomain is used. /// @@ -110,10 +123,23 @@ public string AppDomainName #region Overrides /// - /// End Processing + /// End Processing. /// protected override void EndProcessing() { + // Check if system is in locked down mode, in which case this cmdlet is disabled. + if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) + { + WriteError( + new ErrorRecord( + new PSSecurityException(RemotingErrorIdStrings.EnterPSHostProcessCmdletDisabled), + "EnterPSHostProcessCmdletDisabled", + ErrorCategory.SecurityError, + null)); + + return; + } + // Check for host that supports interactive remote sessions. _interactiveHost = this.Host as IHostSupportsInteractiveSession; if (_interactiveHost == null) @@ -129,24 +155,34 @@ protected override void EndProcessing() } // Check selected process for existence, and whether it hosts PowerShell. + Runspace namedPipeRunspace = null; switch (ParameterSetName) { case ProcessIdParameterSet: Process = GetProcessById(Id); + VerifyProcess(Process); + namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName); break; case ProcessNameParameterSet: Process = GetProcessByName(Name); + VerifyProcess(Process); + namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName); break; case PSHostProcessInfoParameterSet: Process = GetProcessByHostProcessInfo(HostProcessInfo); + VerifyProcess(Process); + + // Create named pipe runspace for selected process and open. + namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName); break; - } - VerifyProcess(Process); - // Create named pipe runspace for selected process and open. - Runspace namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName); + case PipeNameParameterSet: + VerifyPipeName(CustomPipeName); + namedPipeRunspace = CreateNamedPipeRunspace(CustomPipeName); + break; + } // Set runspace prompt. The runspace is closed on pop so we don't // have to reverse this change. @@ -171,24 +207,32 @@ protected override void EndProcessing() } /// - /// Stop Processing + /// Stop Processing. /// protected override void StopProcessing() { RemoteRunspace connectingRunspace = _connectingRemoteRunspace; - if (connectingRunspace != null) - { - connectingRunspace.AbortOpen(); - } + connectingRunspace?.AbortOpen(); } #endregion #region Private Methods + private Runspace CreateNamedPipeRunspace(string customPipeName) + { + NamedPipeConnectionInfo connectionInfo = new NamedPipeConnectionInfo(customPipeName); + return CreateNamedPipeRunspace(connectionInfo); + } + private Runspace CreateNamedPipeRunspace(int procId, string appDomainName) { NamedPipeConnectionInfo connectionInfo = new NamedPipeConnectionInfo(procId, appDomainName); + return CreateNamedPipeRunspace(connectionInfo); + } + + private Runspace CreateNamedPipeRunspace(NamedPipeConnectionInfo connectionInfo) + { TypeTable typeTable = TypeTable.LoadDefaultTypeFiles(); RemoteRunspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo, this.Host, typeTable) as RemoteRunspace; remoteRunspace.Name = NamedPipeRunspaceName; @@ -202,20 +246,40 @@ private Runspace CreateNamedPipeRunspace(int procId, string appDomainName) } catch (RuntimeException e) { - string msgAppDomainName = (!string.IsNullOrEmpty(appDomainName)) ? appDomainName : NamedPipeUtils.DefaultAppDomainName; - // Unwrap inner exception for original error message, if any. string errorMessage = (e.InnerException != null) ? (e.InnerException.Message ?? string.Empty) : string.Empty; - ThrowTerminatingError( - new ErrorRecord( - new RuntimeException( - StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToProcess, - msgAppDomainName, procId, errorMessage), - e.InnerException), - "EnterPSHostProcessCannotConnectToProcess", - ErrorCategory.OperationTimeout, - this)); + if (connectionInfo.CustomPipeName != null) + { + ThrowTerminatingError( + new ErrorRecord( + new RuntimeException( + StringUtil.Format( + RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToPipe, + connectionInfo.CustomPipeName, + errorMessage), + e.InnerException), + "EnterPSHostProcessCannotConnectToPipe", + ErrorCategory.OperationTimeout, + this)); + } + else + { + string msgAppDomainName = connectionInfo.AppDomainName ?? NamedPipeUtils.DefaultAppDomainName; + + ThrowTerminatingError( + new ErrorRecord( + new RuntimeException( + StringUtil.Format( + RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToProcess, + msgAppDomainName, + connectionInfo.ProcessId, + errorMessage), + e.InnerException), + "EnterPSHostProcessCannotConnectToProcess", + ErrorCategory.OperationTimeout, + this)); + } } finally { @@ -225,7 +289,7 @@ private Runspace CreateNamedPipeRunspace(int procId, string appDomainName) return remoteRunspace; } - private void PrepareRunspace(Runspace runspace) + private static void PrepareRunspace(Runspace runspace) { string promptFn = StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessPrompt, @"function global:prompt { """, @@ -251,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) @@ -310,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( @@ -336,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) @@ -344,6 +405,33 @@ private void VerifyProcess(Process process) } } + private void VerifyPipeName(string customPipeName) + { + // Named Pipes are represented differently on Windows vs macOS & Linux + var sb = new StringBuilder(customPipeName.Length); + if (Platform.IsWindows) + { + sb.Append(@"\\.\pipe\"); + } + else + { + sb.Append(Path.GetTempPath()).Append("CoreFxPipe_"); + } + + sb.Append(customPipeName); + + string pipePath = sb.ToString(); + if (!File.Exists(pipePath)) + { + ThrowTerminatingError( + new ErrorRecord( + new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoNamedPipeFound, customPipeName)), + "EnterPSHostProcessNoNamedPipeFound", + ErrorCategory.InvalidArgument, + this)); + } + } + #endregion } @@ -351,13 +439,13 @@ private void VerifyProcess(Process process) /// This cmdlet exits an interactive session with a local process. /// [Cmdlet(VerbsCommon.Exit, "PSHostProcess", - HelpUri = "https://go.microsoft.com/fwlink/?LinkId=403737")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096583")] public sealed class ExitPSHostProcessCommand : PSCmdlet { #region Overrides /// - /// Process Record + /// Process Record. /// protected override void ProcessRecord() { @@ -394,18 +482,25 @@ public sealed class GetPSHostProcessInfoCommand : PSCmdlet private const string ProcessParameterSet = "ProcessParameterSet"; private const string ProcessIdParameterSet = "ProcessIdParameterSet"; private const string ProcessNameParameterSet = "ProcessNameParameterSet"; + +#if UNIX + // CoreFx uses the system temp path to store the file used for named pipes and is not settable. + // This member is only used by Get-PSHostProcessInfo to know where to look for the named pipe files. + private static readonly string NamedPipePath = Path.GetTempPath(); +#else private const string NamedPipePath = @"\\.\pipe\"; +#endif #endregion #region Parameters /// - /// Name of Process + /// Name of Process. /// [Parameter(Position = 0, ParameterSetName = GetPSHostProcessInfoCommand.ProcessNameParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Name { get; @@ -413,11 +508,11 @@ public string[] Name } /// - /// Process + /// Process. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = GetPSHostProcessInfoCommand.ProcessParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public Process[] Process { get; @@ -425,11 +520,11 @@ public Process[] Process } /// - /// Id of process + /// Id of process. /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = GetPSHostProcessInfoCommand.ProcessIdParameterSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public int[] Id { get; @@ -441,7 +536,7 @@ public int[] Id #region Overrides /// - /// End bock processing + /// End bock processing. /// protected override void EndProcessing() { @@ -461,7 +556,7 @@ protected override void EndProcessing() break; default: - Debug.Assert(false, "Unknown parameter set."); + Debug.Fail("Unknown parameter set."); processAppDomainInfo = new ReadOnlyCollection(new Collection()); break; } @@ -473,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) @@ -484,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)) { @@ -498,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. } } } @@ -516,18 +624,21 @@ private int[] GetProcIdsFromNames(string[] names) /// Returns all named pipe AppDomain names for given process Ids or all PowerShell /// processes if procIds parameter is null. /// PowerShell pipe name example: - /// PSHost.130566795082911445.8224.DefaultAppDomain.powershell + /// PSHost.130566795082911445.8224.DefaultAppDomain.powershell. /// - /// Process Ids or null - /// Collection of process AppDomain info + /// Process Ids or null. + /// Collection of process AppDomain info. internal static IReadOnlyCollection GetAppDomainNamesFromProcessId(int[] procIds) { var procAppDomainInfo = new List(); // Get all named pipe 'files' on local machine. - List directories; - List namedPipes; - Utils.NativeEnumerateDirectory(NamedPipePath, out directories, out namedPipes); + List namedPipes = new List(); + var namedPipeDirectory = new DirectoryInfo(NamedPipePath); + foreach (var pipeFileInfo in namedPipeDirectory.EnumerateFiles(NamedPipeUtils.NamedPipeNamePrefixSearch)) + { + namedPipes.Add(Path.Combine(pipeFileInfo.DirectoryName, pipeFileInfo.Name)); + } // Collect all PowerShell named pipes for given process Ids. foreach (string namedPipe in namedPipes) @@ -535,17 +646,16 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc int startIndex = namedPipe.IndexOf(NamedPipeUtils.NamedPipeNamePrefix, StringComparison.OrdinalIgnoreCase); if (startIndex > -1) { - // This is a PowerShell named pipe. Parse the process Id, AppDomain name, and process name. - int pStartTimeIndex = namedPipe.IndexOf(".", startIndex, StringComparison.OrdinalIgnoreCase); + int pStartTimeIndex = namedPipe.IndexOf('.', startIndex); if (pStartTimeIndex > -1) { - int pIdIndex = namedPipe.IndexOf(".", pStartTimeIndex + 1, StringComparison.OrdinalIgnoreCase); + int pIdIndex = namedPipe.IndexOf('.', pStartTimeIndex + 1); if (pIdIndex > -1) { - int pAppDomainIndex = namedPipe.IndexOf(".", pIdIndex + 1, StringComparison.OrdinalIgnoreCase); + int pAppDomainIndex = namedPipe.IndexOf('.', pIdIndex + 1); if (pAppDomainIndex > -1) { - string idString = namedPipe.Substring(pIdIndex + 1, (pAppDomainIndex - pIdIndex - 1)); + ReadOnlySpan idString = namedPipe.AsSpan(pIdIndex + 1, (pAppDomainIndex - pIdIndex - 1)); int id = -1; if (int.TryParse(idString, out id)) { @@ -562,18 +672,62 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc } } - if (!found) { continue; } + if (!found) + { + continue; + } } } + else + { + // Process id is not valid so we'll skip + continue; + } - int pNameIndex = namedPipe.IndexOf(".", pAppDomainIndex + 1, StringComparison.OrdinalIgnoreCase); + int pNameIndex = namedPipe.IndexOf('.', pAppDomainIndex + 1); if (pNameIndex > -1) { string appDomainName = namedPipe.Substring(pAppDomainIndex + 1, (pNameIndex - pAppDomainIndex - 1)); string pName = namedPipe.Substring(pNameIndex + 1); - procAppDomainInfo.Add( - new PSHostProcessInfo(pName, id, appDomainName)); + Process process = null; + try + { + process = PSHostProcessUtils.GetProcessById(id); + } + catch (Exception) + { + // Do nothing if the process no longer exists + } + + if (process == null) + { + try + { + // If the process is gone, try removing the PSHost named pipe + var pipeFile = new FileInfo(namedPipe); + pipeFile.Delete(); + } + catch (Exception) + { + // best effort to cleanup + } + } + else + { + 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. + } + } } } } @@ -601,45 +755,33 @@ internal static IReadOnlyCollection GetAppDomainNamesFromProc /// public sealed class PSHostProcessInfo { + #region Members + + private readonly string _pipeNameFilePath; + + #endregion + #region Properties /// - /// Name of process + /// Name of process. /// - public string ProcessName - { - get; - private set; - } + public string ProcessName { get; } /// - /// Id of process + /// Id of process. /// - public int ProcessId - { - get; - private set; - } + public int ProcessId { get; } /// - /// Name of PowerShell AppDomain in process + /// Name of PowerShell AppDomain in process. /// - public string AppDomainName - { - get; - private set; - } + public string AppDomainName { get; } -#if !CORECLR /// - /// Main window title of the process + /// Main window title of the process. /// - public string MainWindowTitle - { - get; - private set; - } -#endif + public string MainWindowTitle { get; } #endregion @@ -648,33 +790,89 @@ public string MainWindowTitle private PSHostProcessInfo() { } /// - /// Constructor + /// Initializes a new instance of the PSHostProcessInfo type. /// - /// Name of process - /// Id of process - /// Name of process AppDomain - internal PSHostProcessInfo(string processName, int processId, string appDomainName) + /// Name of process. + /// Id of process. + /// Name of process AppDomain. + /// File path of pipe name. + internal PSHostProcessInfo( + string processName, + int processId, + string appDomainName, + string pipeNameFilePath) { - if (string.IsNullOrEmpty(processName)) { throw new PSArgumentNullException("processName"); } - if (string.IsNullOrEmpty(appDomainName)) { throw new PSArgumentNullException("appDomainName"); } + if (string.IsNullOrEmpty(processName)) + { + throw new PSArgumentNullException(nameof(processName)); + } + + if (string.IsNullOrEmpty(appDomainName)) + { + throw new PSArgumentNullException(nameof(appDomainName)); + } -#if !CORECLR - MainWindowTitle = String.Empty; + MainWindowTitle = string.Empty; try { - var proc = System.Diagnostics.Process.GetProcessById(processId); - MainWindowTitle = proc.MainWindowTitle ?? string.Empty; + var process = PSHostProcessUtils.GetProcessById(processId); + MainWindowTitle = process?.MainWindowTitle ?? string.Empty; + } + catch (ArgumentException) + { + // Window title is optional. + } + catch (InvalidOperationException) + { + // Window title is optional. } - catch (ArgumentException) { } - catch (InvalidOperationException) { } -#endif this.ProcessName = processName; this.ProcessId = processId; this.AppDomainName = appDomainName; + _pipeNameFilePath = pipeNameFilePath; } #endregion + + #region Methods + + /// + /// Retrieves the pipe name file path. + /// + /// Pipe name file path. + public string GetPipeNameFilePath() + { + return _pipeNameFilePath; + } + + #endregion + } + + #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 b906542a2ad..fe5946f44f0 100644 --- a/src/System.Management.Automation/engine/remoting/commands/GetJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/GetJob.cs @@ -1,18 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using System.Collections.Generic; -using System; namespace Microsoft.PowerShell.Commands { /// - /// Cmdlet to get available list of results + /// Cmdlet to get available list of results. /// - [Cmdlet(VerbsCommon.Get, "Job", DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113328")] + [Cmdlet(VerbsCommon.Get, "Job", DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096582")] [OutputType(typeof(Job))] public class GetJobCommand : JobCmdletBase { @@ -78,10 +77,9 @@ public class GetJobCommand : JobCmdletBase [Parameter(ParameterSetName = JobCmdletBase.CommandParameterSet)] public int Newest { get; set; } - /// /// SessionId for which job - /// need to be obtained + /// need to be obtained. /// [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = JobCmdletBase.SessionIdParameterSet)] @@ -93,6 +91,7 @@ public override int[] Id { return base.Id; } + set { base.Id = value; @@ -105,15 +104,15 @@ public override int[] Id /// /// Extract result objects corresponding to the specified - /// names or expressions + /// names or expressions. /// 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); - } // ProcessRecord + } #endregion Overrides @@ -122,7 +121,7 @@ protected override void ProcessRecord() /// /// Helper method to find jobs based on parameter set. /// - /// Matching jobs + /// Matching jobs. protected List FindJobs() { List jobList = new List(); @@ -133,12 +132,14 @@ protected List FindJobs() { jobList.AddRange(FindJobsMatchingByName(true, false, true, false)); } + break; case InstanceIdParameterSet: { jobList.AddRange(FindJobsMatchingByInstanceId(true, false, true, false)); } + break; case SessionIdParameterSet: @@ -154,24 +155,28 @@ protected List FindJobs() jobList.AddRange(JobManager.GetJobs(this, true, false, null)); } } + break; case CommandParameterSet: { jobList.AddRange(FindJobsMatchingByCommand(false)); } + break; case StateParameterSet: { jobList.AddRange(FindJobsMatchingByState(false)); } + break; case FilterParameterSet: { jobList.AddRange(FindJobsMatchingByFilter(false)); } + break; default: @@ -190,14 +195,13 @@ protected List FindJobs() #region Private Members /// - /// Filter jobs based on HasMoreData + /// Filter jobs based on HasMoreData. /// - /// /// - /// return the list of jobs after applying HasMoreData filter + /// Return the list of jobs after applying HasMoreData filter. private List ApplyHasMoreDataFiltering(List jobList) { - bool hasMoreDataParameter = MyInvocation.BoundParameters.ContainsKey("HasMoreData"); + bool hasMoreDataParameter = MyInvocation.BoundParameters.ContainsKey(nameof(HasMoreData)); if (!hasMoreDataParameter) { @@ -218,15 +222,14 @@ private List ApplyHasMoreDataFiltering(List jobList) } /// - /// Find the all child jobs with specified ChildJobState in the job list + /// Find the all child jobs with specified ChildJobState in the job list. /// - /// /// - /// returns job list including all child jobs with ChildJobState or all if IncludeChildJob is specified + /// Returns job list including all child jobs with ChildJobState or all if IncludeChildJob is specified. private List FindChildJobs(List jobList) { - bool childJobStateParameter = MyInvocation.BoundParameters.ContainsKey("ChildJobState"); - bool includeChildJobParameter = MyInvocation.BoundParameters.ContainsKey("IncludeChildJob"); + bool childJobStateParameter = MyInvocation.BoundParameters.ContainsKey(nameof(ChildJobState)); + bool includeChildJobParameter = MyInvocation.BoundParameters.ContainsKey(nameof(IncludeChildJob)); List matches = new List(); @@ -253,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); } @@ -271,9 +277,9 @@ private List FindChildJobs(List jobList) /// private List ApplyTimeFiltering(List jobList) { - bool beforeParameter = MyInvocation.BoundParameters.ContainsKey("Before"); - bool afterParameter = MyInvocation.BoundParameters.ContainsKey("After"); - bool newestParameter = MyInvocation.BoundParameters.ContainsKey("Newest"); + bool beforeParameter = MyInvocation.BoundParameters.ContainsKey(nameof(Before)); + bool afterParameter = MyInvocation.BoundParameters.ContainsKey(nameof(After)); + bool newestParameter = MyInvocation.BoundParameters.ContainsKey(nameof(Newest)); if (!beforeParameter && !afterParameter && !newestParameter) { diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 30c1fb5b25a..e340a1acbdb 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -14,6 +13,7 @@ using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -124,14 +124,14 @@ namespace Microsoft.PowerShell.Commands /// "Microsoft.PowerShell" is used. /// [Cmdlet(VerbsLifecycle.Invoke, "Command", DefaultParameterSetName = InvokeCommandCommand.InProcParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135225", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096789", RemotingCapability = RemotingCapability.OwnedByCommand)] public class InvokeCommandCommand : PSExecutionCmdlet, IDisposable { #region Parameters /// /// The PSSession object describing the remote runspace - /// using which the specified cmdlet operation will be performed + /// using which the specified cmdlet operation will be performed. /// [Parameter(Position = 0, ParameterSetName = InvokeCommandCommand.SessionParameterSet)] @@ -144,6 +144,7 @@ public override PSSession[] Session { return base.Session; } + set { base.Session = value; @@ -155,8 +156,7 @@ public override PSSession[] Session /// computer(s). The following formats are supported: /// (a) Computer name /// (b) IPv4 address : 132.3.4.5 - /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18 - /// + /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18. /// [Parameter(Position = 0, ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] @@ -164,12 +164,13 @@ public override PSSession[] Session ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] [Alias("Cn")] [ValidateNotNullOrEmpty] - public override String[] ComputerName + public override string[] ComputerName { get { return base.ComputerName; } + set { base.ComputerName = value; @@ -197,13 +198,14 @@ public override String[] ComputerName ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get { return base.Credential; } + set { base.Credential = value; @@ -224,13 +226,14 @@ public override PSCredential Credential [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] - public override Int32 Port + [ValidateRange((int)1, (int)ushort.MaxValue)] + public override int Port { get { return base.Port; } + set { base.Port = value; @@ -253,6 +256,7 @@ public override SwitchParameter UseSSL { return base.UseSSL; } + set { base.UseSSL = value; @@ -288,12 +292,13 @@ public override SwitchParameter UseSSL ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] - public override String ConfigurationName + public override string ConfigurationName { get { return base.ConfigurationName; } + set { base.ConfigurationName = value; @@ -303,19 +308,20 @@ 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 - /// not specified as well, then "WSMAN" will be used + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's + /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] - public override String ApplicationName + public override string ApplicationName { get { return base.ApplicationName; } + set { base.ApplicationName = value; @@ -325,7 +331,7 @@ public override String ApplicationName /// /// Allows the user of the cmdlet to specify a throttling value /// for throttling the number of remote operations that can - /// be executed simultaneously + /// be executed simultaneously. /// [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SessionParameterSet)] @@ -339,21 +345,22 @@ public override String ApplicationName [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] - public override Int32 ThrottleLimit + public override int ThrottleLimit { - set - { - base.ThrottleLimit = value; - } get { return base.ThrottleLimit; } + + set + { + base.ThrottleLimit = value; + } } /// /// A complete URI(s) specified for the remote computer and shell to - /// connect to and create runspace for + /// connect to and create runspace for. /// [Parameter(Position = 0, ParameterSetName = InvokeCommandCommand.UriParameterSet)] @@ -367,6 +374,7 @@ public override Uri[] ConnectionUri { return base.ConnectionUri; } + set { base.ConnectionUri = value; @@ -374,7 +382,7 @@ public override Uri[] ConnectionUri } /// - /// Specifies if the cmdlet needs to be run asynchronously + /// Specifies if the cmdlet needs to be run asynchronously. /// [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SessionParameterSet)] @@ -398,11 +406,13 @@ public SwitchParameter AsJob { return _asjob; } + set { _asjob = value; } } + private bool _asjob = false; /// @@ -417,6 +427,7 @@ public SwitchParameter AsJob public SwitchParameter InDisconnectedSession { get { return InvokeAndDisconnect; } + set { InvokeAndDisconnect = value; } } @@ -431,6 +442,7 @@ public SwitchParameter InDisconnectedSession public string[] SessionName { get { return DisconnectedSessionName; } + set { DisconnectedSessionName = value; } } @@ -457,12 +469,14 @@ public string[] SessionName public SwitchParameter HideComputerName { get { return _hideComputerName; } + set { _hideComputerName = value; } } + private bool _hideComputerName; /// - /// Friendly name for the job object if AsJob is used + /// Friendly name for the job object if AsJob is used. /// [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SessionParameterSet)] @@ -472,27 +486,31 @@ public SwitchParameter HideComputerName [Parameter(ParameterSetName = InvokeCommandCommand.FilePathUriParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] - public String JobName + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + public string JobName { get { return _name; } + set { - if (!String.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { _name = value; _asjob = true; } } } - private String _name = String.Empty; + + private string _name = string.Empty; /// /// The script block that the user has specified in the /// cmdlet. This will be converted to a powershell before - /// its actually sent to the remote end + /// its actually sent to the remote end. /// [Parameter(Position = 1, Mandatory = true, @@ -515,9 +533,11 @@ public String JobName [Parameter(Position = 1, Mandatory = true, ParameterSetName = InvokeCommandCommand.ContainerIdParameterSet)] - [Parameter(Mandatory = true, + [Parameter(Position = 1, + Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - [Parameter(Mandatory = true, + [Parameter(Position = 1, + Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostHashParameterSet)] [ValidateNotNull] [Alias("Command")] @@ -527,6 +547,7 @@ public override ScriptBlock ScriptBlock { return base.ScriptBlock; } + set { base.ScriptBlock = value; @@ -542,7 +563,7 @@ public override ScriptBlock ScriptBlock /// /// The script block that the user has specified in the /// cmdlet. This will be converted to a powershell before - /// its actually sent to the remote end + /// its actually sent to the remote end. /// [Parameter(Position = 1, Mandatory = true, @@ -574,6 +595,7 @@ public override string FilePath { return base.FilePath; } + set { base.FilePath = value; @@ -581,7 +603,7 @@ public override string FilePath } /// - /// The AllowRedirection parameter enables the implicit redirection functionality + /// The AllowRedirection parameter enables the implicit redirection functionality. /// [Parameter(ParameterSetName = InvokeCommandCommand.UriParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathUriParameterSet)] @@ -591,13 +613,13 @@ public override SwitchParameter AllowRedirection { return base.AllowRedirection; } + set { base.AllowRedirection = value; } } - /// /// Extended Session Options for controlling the session creation. Use /// "New-WSManSessionOption" cmdlet to supply value for this parameter. @@ -612,6 +634,7 @@ public override PSSessionOption SessionOption { return base.SessionOption; } + set { base.SessionOption = value; @@ -619,7 +642,7 @@ public override PSSessionOption SessionOption } /// - /// Authentication mechanism to authenticate the user + /// Authentication mechanism to authenticate the user. /// [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] @@ -631,6 +654,7 @@ public override AuthenticationMechanism Authentication { return base.Authentication; } + set { base.Authentication = value; @@ -650,6 +674,7 @@ public override AuthenticationMechanism Authentication public override SwitchParameter EnableNetworkAccess { get { return base.EnableNetworkAccess; } + set { base.EnableNetworkAccess = value; } } @@ -664,50 +689,78 @@ public override SwitchParameter EnableNetworkAccess public override SwitchParameter RunAsAdministrator { get { return base.RunAsAdministrator; } + set { base.RunAsAdministrator = value; } } #region SSH Parameters /// - /// Host name for an SSH remote connection + /// Host name for an SSH remote connection. /// [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(Mandatory = true, ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override string[] HostName { get { return base.HostName; } + set { base.HostName = value; } } /// - /// User Name + /// User Name. /// [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public override string UserName { get { return base.UserName; } + set { base.UserName = value; } } /// - /// Key Path + /// Key Path. /// [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [Alias("IdentityFilePath")] public override string KeyFilePath { get { return base.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 @@ -720,6 +773,7 @@ public override string KeyFilePath public override SwitchParameter SSHTransport { get { return base.SSHTransport; } + set { base.SSHTransport = value; } } @@ -731,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 @@ -775,7 +848,7 @@ public virtual SwitchParameter RemoteDebug /// /// Creates the helper classes for the specified - /// parameter set + /// parameter set. /// protected override void BeginProcessing() { @@ -785,7 +858,7 @@ protected override void BeginProcessing() throw new InvalidOperationException(RemotingErrorIdStrings.AsJobAndDisconnectedError); } - if (MyInvocation.BoundParameters.ContainsKey("SessionName") && !this.InvokeAndDisconnect) + if (MyInvocation.BoundParameters.ContainsKey(nameof(SessionName)) && !this.InvokeAndDisconnect) { throw new InvalidOperationException(RemotingErrorIdStrings.SessionNameWithoutInvokeDisconnected); } @@ -895,7 +968,7 @@ protected override void BeginProcessing() return; } - if (String.IsNullOrEmpty(ConfigurationName)) + if (string.IsNullOrEmpty(ConfigurationName)) { if ((ParameterSetName == InvokeCommandCommand.ComputerNameParameterSet) || (ParameterSetName == InvokeCommandCommand.UriParameterSet) || @@ -907,8 +980,8 @@ protected override void BeginProcessing() } else { - // convert null to String.Empty for VM/Container session - ConfigurationName = String.Empty; + // convert null to string.Empty for VM/Container session + ConfigurationName = string.Empty; } } @@ -942,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) @@ -953,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; @@ -977,13 +1050,13 @@ 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. break; } + ecHelper.ShouldUseSteppablePipelineOnServer = true; } } @@ -1057,6 +1130,7 @@ protected override void ProcessRecord() WriteObject(job); } } + break; case InvokeCommandCommand.SessionParameterSet: @@ -1069,6 +1143,7 @@ protected override void ProcessRecord() this.JobRepository.Add(job); WriteObject(job); } + break; case InvokeCommandCommand.UriParameterSet: @@ -1076,7 +1151,7 @@ protected override void ProcessRecord() { if (Operations.Count > 0) { - String[] locations = new String[ConnectionUri.Length]; + string[] locations = new string[ConnectionUri.Length]; for (int i = 0; i < locations.Length; i++) { locations[i] = ConnectionUri[i].ToString(); @@ -1090,11 +1165,12 @@ protected override void ProcessRecord() WriteObject(job); } } + break; - } // switch ... - } // else ... + } + } } - } // if (!pipelineinvoked... + } if (InputObject != AutomationNull.Value && !_inputStreamClosed) { @@ -1118,7 +1194,7 @@ protected override void ProcessRecord() } } } - } // ProcessRecord + } /// /// InvokeAsync would have been called in ProcessRecord. Wait here @@ -1172,8 +1248,15 @@ protected override void EndProcessing() // be connected to later. WriteJobResults(false); - // finally dispose the job. - _job.Dispose(); + // Dispose job object if it is not returned to the user. + // The _asjob field can change dynamically and needs to be checked before the job + // object is disposed. For example, if remote sessions are disconnected abruptly + // via WinRM, a disconnected job object is created to facilitate a reconnect. + // If the job object is disposed here, then a session reconnect cannot happen. + if (!_asjob) + { + _job.Dispose(); + } // We no longer need to call ClearInvokeCommandOnRunspaces() here because // this command might finish before the foreach block finishes. previously, @@ -1219,21 +1302,27 @@ protected override void EndProcessing() // be connected to later. WriteJobResults(false); - // finally dispose the job. - _job.Dispose(); - - } // if (needToCollect... - }// else - job == null + // Dispose job object if it is not returned to the user. + // The _asjob field can change dynamically and needs to be checked before the job + // object is disposed. For example, if remote sessions are disconnected abruptly + // via WinRM, a disconnected job object is created to facilitate a reconnect. + // If the job object is disposed here, then a session reconnect cannot happen. + if (!_asjob) + { + _job.Dispose(); + } + } + } } - }// if (!async ... - } // EndProcessing + } + } /// /// This method is called when the user sends a stop signal to the /// cmdlet. The cmdlet will not exit until it has completed /// executing the command on all the runspaces. However, when a stop /// signal is sent, execution needs to be stopped on the pipelines - /// corresponding to these runspaces + /// corresponding to these runspaces. /// /// This is called from a separate thread so need to worry /// about concurrency issues @@ -1283,7 +1372,7 @@ protected override void StopProcessing() _needToCollect = false; } } - }// StopProcessing() + } #endregion Overrides @@ -1305,19 +1394,19 @@ private Debugger GetHostDebugger() /// /// Handle event from the throttle manager indicating that all - /// operations are complete + /// operations are complete. /// /// /// private void HandleThrottleComplete(object sender, EventArgs eventArgs) { _operationsComplete.Set(); - _throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleComplete); - } // HandleThrottleComplete + _throttleManager.ThrottleComplete -= HandleThrottleComplete; + } /// /// Clears the internal invoke command instance on all - /// remote runspaces + /// remote runspaces. /// private void ClearInvokeCommandOnRunspaces() { @@ -1333,7 +1422,7 @@ private void ClearInvokeCommandOnRunspaces() /// /// Sets the throttle limit, creates the invoke expression - /// sync job and executes the same + /// sync job and executes the same. /// private void CreateAndRunSyncJob() { @@ -1342,14 +1431,14 @@ private void CreateAndRunSyncJob() if (!_nojob) { _throttleManager.ThrottleLimit = ThrottleLimit; - _throttleManager.ThrottleComplete += new EventHandler(HandleThrottleComplete); + _throttleManager.ThrottleComplete += HandleThrottleComplete; _operationsComplete.Reset(); Dbg.Assert(_disconnectComplete == null, "disconnectComplete event should only be used once."); _disconnectComplete = new ManualResetEvent(false); _job = new PSInvokeExpressionSyncJob(Operations, _throttleManager); _job.HideComputerName = _hideComputerName; - _job.StateChanged += new EventHandler(HandleJobStateChanged); + _job.StateChanged += HandleJobStateChanged; // Add robust connection retry notification handler. AddConnectionRetryHandler(_job); @@ -1374,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) @@ -1388,16 +1474,13 @@ private void HandleJobStateChanged(object sender, JobStateEventArgs e) state == JobState.Stopped || state == JobState.Failed) { - _job.StateChanged -= new EventHandler(HandleJobStateChanged); + _job.StateChanged -= HandleJobStateChanged; RemoveConnectionRetryHandler(sender as PSInvokeExpressionSyncJob); // Signal that this job has been disconnected, or has ended. lock (_jobSyncObject) { - if (_disconnectComplete != null) - { - _disconnectComplete.Set(); - } + _disconnectComplete?.Set(); } } } @@ -1408,13 +1491,13 @@ private void AddConnectionRetryHandler(PSInvokeExpressionSyncJob job) { return; } + Collection powershells = job.GetPowerShells(); foreach (var ps in powershells) { if (ps.RemotePowerShell != null) { - ps.RemotePowerShell.RCConnectionNotification += - new EventHandler(RCConnectionNotificationHandler); + ps.RemotePowerShell.RCConnectionNotification += RCConnectionNotificationHandler; } } } @@ -1428,13 +1511,13 @@ private void RemoveConnectionRetryHandler(PSInvokeExpressionSyncJob job) { return; } + Collection powershells = job.GetPowerShells(); foreach (var ps in powershells) { if (ps.RemotePowerShell != null) { - ps.RemotePowerShell.RCConnectionNotification -= - new EventHandler(RCConnectionNotificationHandler); + ps.RemotePowerShell.RCConnectionNotification -= RCConnectionNotificationHandler; } } } @@ -1573,9 +1656,9 @@ private List GetDisconnectedSessions(PSInvokeExpressionSyncJob job) } /// - /// Writes an input value to the pipeline + /// Writes an input value to the pipeline. /// - /// input value to write + /// Input value to write. private void WriteInput(object inputValue) { // when there are no input writers, there is no @@ -1614,9 +1697,9 @@ private void WriteInput(object inputValue) } /// - /// Writes the results in the job object + /// Writes the results in the job object. /// - /// Write in a non-blocking manner + /// Write in a non-blocking manner. private void WriteJobResults(bool nonblocking) { if (_job == null) @@ -1808,6 +1891,7 @@ private void HandlePipelinesStopped() break; } } + if (retryCanceled && this.Host != null) { @@ -1828,16 +1912,16 @@ private void StartProgressBar( this.Host); } - private void StopProgressBar( + private static void StopProgressBar( long sourceId) { s_RCProgress.StopProgress(sourceId); } /// - /// Writes the stream objects in the specified collection + /// Writes the stream objects in the specified collection. /// - /// collection to read from + /// Collection to read from. private void WriteStreamObjectsFromCollection(IEnumerable results) { foreach (var result in results) @@ -1853,7 +1937,7 @@ private void WriteStreamObjectsFromCollection(IEnumerable result /// /// Determine if we have to throw for a /// "throw" statement from scripts - /// This means that the local pipeline will be terminated as well + /// This means that the local pipeline will be terminated as well. /// /// /// This is valid when only one pipeline is @@ -1873,6 +1957,7 @@ private void DetermineThrowStatementBehavior() // in proc parameter set - just return return; } + if (!_asjob) { if (ParameterSetName.Equals(InvokeCommandCommand.ComputerNameParameterSet) || @@ -1905,8 +1990,8 @@ private void DetermineThrowStatementBehavior() /// /// Process the stream object before writing it in the specified collection. /// - /// stream object to process - private void PreProcessStreamObject(PSStreamObject streamObject) + /// Stream object to process. + private static void PreProcessStreamObject(PSStreamObject streamObject) { ErrorRecord errorRecord = streamObject.Value as ErrorRecord; @@ -1935,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 @@ -1952,17 +2037,18 @@ private void PreProcessStreamObject(PSStreamObject streamObject) private bool _inputStreamClosed = false; 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"; @@ -1971,7 +2057,7 @@ private void PreProcessStreamObject(PSStreamObject streamObject) #region IDisposable Overrides /// - /// Dispose the cmdlet + /// Dispose the cmdlet. /// public void Dispose() { @@ -1980,9 +2066,9 @@ public void Dispose() } /// - /// internal dispose method which does the actual disposing + /// Internal dispose method which does the actual disposing. /// - /// whether called from dispose or finalize + /// Whether called from dispose or finalize. private void Dispose(bool disposing) { if (disposing) @@ -1998,13 +2084,10 @@ 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 -= new EventHandler(HandleThrottleComplete); + _throttleManager.ThrottleComplete -= HandleThrottleComplete; _throttleManager.Dispose(); _throttleManager = null; } @@ -2026,7 +2109,7 @@ private void Dispose(bool disposing) } } } - } // Dispose + } #endregion IDisposable Overrides } @@ -2042,18 +2125,18 @@ 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; /// - /// Constructor. + /// Constructor. /// public RobustConnectionProgress() { @@ -2078,14 +2161,13 @@ public void StartProgress( { return; } + if (secondsTotal < 1) { return; } - if (string.IsNullOrEmpty(computerName)) - { - throw new ArgumentNullException("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 f99e6a20439..29d107a074f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs +++ b/src/System.Management.Automation/engine/remoting/commands/JobRepository.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Remoting; @@ -8,22 +7,20 @@ namespace System.Management.Automation { /// - /// class which has list of job objects currently active in the system. + /// Class which has list of job objects currently active in the system. /// public abstract class Repository where T : class { #region Public Methods /// - /// Add an item to the repository + /// Add an item to the repository. /// - /// object to add + /// Object to add. public void Add(T item) { - if (item == null) - { - throw new ArgumentNullException(_identifier); - } + ArgumentNullException.ThrowIfNull(item, _identifier); + lock (_syncObject) { Guid instanceId = GetKey(item); @@ -40,22 +37,20 @@ public void Add(T item) } /// - /// Remove the specified item from the repository + /// Remove the specified item from the repository. /// - /// object to remove + /// Object to remove. public void Remove(T item) { - if (item == null) - { - throw new ArgumentNullException(_identifier); - } + ArgumentNullException.ThrowIfNull(item, _identifier); + lock (_syncObject) { Guid instanceId = GetKey(item); if (!_repository.Remove(instanceId)) { - String message = + string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ItemNotFoundInRepository, "Job repository", instanceId.ToString()); @@ -65,7 +60,6 @@ public void Remove(T item) } /// - /// /// /// public List GetItems() @@ -77,14 +71,14 @@ public List GetItems() #region Private/Internal Methods /// - /// Get a key for the specified item + /// Get a key for the specified item. /// - /// item for which the key is required - /// returns a key + /// Item for which the key is required. + /// Returns a key. protected abstract Guid GetKey(T item); /// - /// internal constructor + /// Internal constructor. /// protected Repository(string identifier) { @@ -92,7 +86,7 @@ protected Repository(string identifier) } /// - /// Creates a repository with the specified values + /// Creates a repository with the specified values. /// internal List Items { @@ -106,7 +100,7 @@ internal List Items } /// - /// Gets the specified Item + /// Gets the specified Item. /// /// /// @@ -132,20 +126,20 @@ 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 } /// - /// class which has list of job objects currently active in the system. + /// Class which has list of job objects currently active in the system. /// public class JobRepository : Repository { /// - /// Returns the list of available job objects + /// Returns the list of available job objects. /// public List Jobs { @@ -169,17 +163,17 @@ public Job GetJob(Guid instanceId) #region Internal Methods /// - /// internal constructor + /// Internal constructor. /// internal JobRepository() : base("job") { } /// - /// Returns the instance id of the job as key + /// Returns the instance id of the job as key. /// - /// job for which a key is required - /// returns jobs guid + /// Job for which a key is required. + /// Returns jobs guid. protected override Guid GetKey(Job item) { if (item != null) diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs index 065f37b379a..4e669e891f2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs @@ -1,14 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + using System; -using System.IO; using System.Collections; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Text; -using System.Diagnostics; -using System.Management.Automation.Internal; -using System.Globalization; namespace Microsoft.PowerShell.Commands { @@ -17,13 +20,13 @@ namespace Microsoft.PowerShell.Commands /// /// See Declarative Initial Session State (DISC) /// - [Cmdlet(VerbsCommon.New, "PSSessionConfigurationFile", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217036")] + [Cmdlet(VerbsCommon.New, "PSSessionConfigurationFile", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096791")] public class NewPSSessionConfigurationFileCommand : PSCmdlet { #region Parameters /// - /// Destination path + /// Destination path. /// [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] @@ -33,17 +36,19 @@ public string Path { return _path; } + set { _path = value; } } + private string _path; /// - /// Configuration file schema version + /// Configuration file schema version. /// - [Parameter()] + [Parameter] [ValidateNotNull] public Version SchemaVersion { @@ -51,142 +56,158 @@ public Version SchemaVersion { return _schemaVersion; } + set { _schemaVersion = value; } } + private Version _schemaVersion = new Version("2.0.0.0"); /// - /// Configuration file GUID + /// Configuration file GUID. /// - [Parameter()] + [Parameter] public Guid Guid { get { return _guid; } + set { _guid = value; } } + private Guid _guid = Guid.NewGuid(); /// - /// Author of the configuration file + /// Author of the configuration file. /// - [Parameter()] + [Parameter] public string Author { get { return _author; } + set { _author = value; } } + private string _author; /// - /// Description + /// Description. /// - [Parameter()] + [Parameter] public string Description { get { return _description; } + set { _description = value; } } + private string _description; /// - /// Company name + /// Company name. /// - [Parameter()] + [Parameter] public string CompanyName { get { return _companyName; } + set { _companyName = value; } } + private string _companyName; /// - /// Copyright information + /// Copyright information. /// - [Parameter()] + [Parameter] public string Copyright { get { return _copyright; } + set { _copyright = value; } } + private string _copyright; /// /// Specifies type of initial session state to use. /// - [Parameter()] + [Parameter] public SessionType SessionType { get { return _sessionType; } + set { _sessionType = value; } } + private SessionType _sessionType = SessionType.Default; /// /// Specifies the directory for transcripts to be placed. /// - [Parameter()] + [Parameter] public string TranscriptDirectory { get { return _transcriptDirectory; } + set { _transcriptDirectory = value; } } + private string _transcriptDirectory = null; /// /// 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; } @@ -195,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; @@ -207,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. @@ -218,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; } */ @@ -226,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 + /// Scripts to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ScriptsToProcess { @@ -240,17 +261,19 @@ public string[] ScriptsToProcess { return _scriptsToProcess; } + set { _scriptsToProcess = value; } } - private string[] _scriptsToProcess = Utils.EmptyArray(); + + private string[] _scriptsToProcess = Array.Empty(); /// /// Role definitions for this session configuration (Role name -> Role capability) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary RoleDefinitions { @@ -258,82 +281,92 @@ public IDictionary RoleDefinitions { return _roleDefinitions; } + set { _roleDefinitions = value; } } + private IDictionary _roleDefinitions; /// - /// Specifies account groups that are membership requirements for this session + /// Specifies account groups that are membership requirements for this session. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary RequiredGroups { get { return _requiredGroups; } + set { _requiredGroups = value; } } + private IDictionary _requiredGroups; /// - /// Language mode + /// Language mode. /// - [Parameter()] + [Parameter] public PSLanguageMode LanguageMode { get { return _languageMode; } + set { _languageMode = value; _isLanguageModeSpecified = true; } } + private PSLanguageMode _languageMode = PSLanguageMode.NoLanguage; private bool _isLanguageModeSpecified; /// - /// Execution policy + /// Execution policy. /// - [Parameter()] + [Parameter] public ExecutionPolicy ExecutionPolicy { get { return _executionPolicy; } + set { _executionPolicy = value; } } + private ExecutionPolicy _executionPolicy = ExecutionPolicy.Restricted; /// - /// PowerShell version + /// PowerShell version. /// - [Parameter()] + [Parameter] public Version PowerShellVersion { get { return _powerShellVersion; } + set { _powerShellVersion = value; } } + private Version _powerShellVersion; /// - /// A list of modules to import + /// A list of modules to import. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ModulesToImport { @@ -341,17 +374,19 @@ public object[] ModulesToImport { return _modulesToImport; } + set { _modulesToImport = value; } } + private object[] _modulesToImport; /// - /// A list of visible aliases + /// A list of visible aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleAliases { @@ -359,53 +394,59 @@ public string[] VisibleAliases { return _visibleAliases; } + set { _visibleAliases = value; } } - private string[] _visibleAliases = Utils.EmptyArray(); + + private string[] _visibleAliases = Array.Empty(); /// - /// A list of visible cmdlets + /// A list of visible cmdlets. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Object[] VisibleCmdlets + public object[] VisibleCmdlets { get { return _visibleCmdlets; } + set { _visibleCmdlets = value; } } - private Object[] _visibleCmdlets = null; + + private object[] _visibleCmdlets = null; /// - /// A list of visible functions + /// A list of visible functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Object[] VisibleFunctions + public object[] VisibleFunctions { get { return _visibleFunctions; } + set { _visibleFunctions = value; } } - private Object[] _visibleFunctions = null; + + private object[] _visibleFunctions = null; /// /// A list of visible external commands (scripts and applications) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleExternalCommands { @@ -413,17 +454,19 @@ public string[] VisibleExternalCommands { return _visibleExternalCommands; } + set { _visibleExternalCommands = value; } } - private string[] _visibleExternalCommands = Utils.EmptyArray(); + + private string[] _visibleExternalCommands = Array.Empty(); /// - /// A list of providers + /// A list of providers. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleProviders { @@ -431,17 +474,19 @@ public string[] VisibleProviders { return _visibleProviders; } + set { _visibleProviders = value; } } - private string[] _visibleProviders = Utils.EmptyArray(); + + private string[] _visibleProviders = Array.Empty(); /// - /// A list of aliases + /// A list of aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] AliasDefinitions { @@ -449,17 +494,19 @@ public IDictionary[] AliasDefinitions { return _aliasDefinitions; } + set { _aliasDefinitions = value; } } + private IDictionary[] _aliasDefinitions; /// - /// A list of functions + /// A list of functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] FunctionDefinitions { @@ -467,17 +514,19 @@ public IDictionary[] FunctionDefinitions { return _functionDefinitions; } + set { _functionDefinitions = value; } } + private IDictionary[] _functionDefinitions; /// - /// A list of variables + /// A list of variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object VariableDefinitions { @@ -485,17 +534,19 @@ public object VariableDefinitions { return _variableDefinitions; } + set { _variableDefinitions = value; } } + private object _variableDefinitions; /// - /// A list of environment variables + /// A list of environment variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary EnvironmentVariables @@ -504,17 +555,19 @@ public IDictionary EnvironmentVariables { return _environmentVariables; } + set { _environmentVariables = value; } } + private IDictionary _environmentVariables; /// - /// A list of types to process + /// A list of types to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] TypesToProcess { @@ -522,17 +575,19 @@ public string[] TypesToProcess { return _typesToProcess; } + set { _typesToProcess = value; } } - private string[] _typesToProcess = Utils.EmptyArray(); + + private string[] _typesToProcess = Array.Empty(); /// - /// A list of format data to process + /// A list of format data to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] FormatsToProcess { @@ -540,17 +595,19 @@ public string[] FormatsToProcess { return _formatsToProcess; } + set { _formatsToProcess = value; } } - private string[] _formatsToProcess = Utils.EmptyArray(); + + private string[] _formatsToProcess = Array.Empty(); /// - /// A list of assemblies to load + /// A list of assemblies to load. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] AssembliesToLoad { @@ -558,18 +615,20 @@ public string[] AssembliesToLoad { return _assembliesToLoad; } + set { _assembliesToLoad = value; } } + private 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 @@ -577,11 +636,10 @@ public string[] AssembliesToLoad #region Overrides /// - /// /// protected override void ProcessRecord() { - Debug.Assert(!String.IsNullOrEmpty(_path)); + Debug.Assert(!string.IsNullOrEmpty(_path)); ProviderInfo provider = null; PSDriveInfo drive; @@ -631,24 +689,26 @@ protected override void ProcessRecord() result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Guid, RemotingErrorIdStrings.DISCGUIDComment, SessionConfigurationUtils.QuoteName(_guid), streamWriter, false)); // Author - if (String.IsNullOrEmpty(_author)) + if (string.IsNullOrEmpty(_author)) { _author = Environment.UserName; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Author, RemotingErrorIdStrings.DISCAuthorComment, SessionConfigurationUtils.QuoteName(_author), streamWriter, false)); // Description result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Description, RemotingErrorIdStrings.DISCDescriptionComment, - SessionConfigurationUtils.QuoteName(_description), streamWriter, String.IsNullOrEmpty(_description))); + SessionConfigurationUtils.QuoteName(_description), streamWriter, string.IsNullOrEmpty(_description))); // Company name if (ShouldGenerateConfigurationSnippet("CompanyName")) { - if (String.IsNullOrEmpty(_companyName)) + if (string.IsNullOrEmpty(_companyName)) { _companyName = Modules.DefaultCompanyName; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.CompanyName, RemotingErrorIdStrings.DISCCompanyNameComment, SessionConfigurationUtils.QuoteName(_companyName), streamWriter, false)); } @@ -656,10 +716,11 @@ protected override void ProcessRecord() // Copyright if (ShouldGenerateConfigurationSnippet("Copyright")) { - if (String.IsNullOrEmpty(_copyright)) + if (string.IsNullOrEmpty(_copyright)) { _copyright = StringUtil.Format(Modules.DefaultCopyrightMessage, _author); } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Copyright, RemotingErrorIdStrings.DISCCopyrightComment, SessionConfigurationUtils.QuoteName(_copyright), streamWriter, false)); } @@ -671,9 +732,9 @@ protected override void ProcessRecord() string resultData = null; // Transcript directory - resultData = String.IsNullOrEmpty(_transcriptDirectory) ? "'C:\\Transcripts\\'" : SessionConfigurationUtils.QuoteName(_transcriptDirectory); + resultData = string.IsNullOrEmpty(_transcriptDirectory) ? "'C:\\Transcripts\\'" : SessionConfigurationUtils.QuoteName(_transcriptDirectory); result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.TranscriptDirectory, RemotingErrorIdStrings.DISCTranscriptDirectoryComment, - resultData, streamWriter, String.IsNullOrEmpty(_transcriptDirectory))); + resultData, streamWriter, string.IsNullOrEmpty(_transcriptDirectory))); // Run as virtual account result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.RunAsVirtualAccount, RemotingErrorIdStrings.DISCRunAsVirtualAccountComment, @@ -763,6 +824,7 @@ protected override void ProcessRecord() _languageMode = PSLanguageMode.FullLanguage; } } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.LanguageMode, RemotingErrorIdStrings.DISCLanguageModeComment, SessionConfigurationUtils.QuoteName(_languageMode), streamWriter, false)); } @@ -784,6 +846,7 @@ protected override void ProcessRecord() isExample = true; _powerShellVersion = PSVersionInfo.PSVersion; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.PowerShellVersion, RemotingErrorIdStrings.DISCPowerShellVersionComment, SessionConfigurationUtils.QuoteName(_powerShellVersion), streamWriter, isExample)); } @@ -793,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)); } } @@ -903,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)); @@ -912,9 +975,9 @@ protected override void ProcessRecord() foreach (string functionKey in hashtable.Keys) { - if (!String.Equals(functionKey, ConfigFileConstants.FunctionNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, ConfigFileConstants.FunctionValueToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, ConfigFileConstants.FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(functionKey, ConfigFileConstants.FunctionNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, ConfigFileConstants.FunctionValueToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, ConfigFileConstants.FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, functionKey, ConfigFileConstants.FunctionDefinitions, _path)); @@ -976,8 +1039,8 @@ protected override void ProcessRecord() foreach (string variableKey in hashtable.Keys) { - if (!String.Equals(variableKey, ConfigFileConstants.VariableNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(variableKey, ConfigFileConstants.VariableValueToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(variableKey, ConfigFileConstants.VariableNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(variableKey, ConfigFileConstants.VariableValueToken, StringComparison.OrdinalIgnoreCase)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, variableKey, ConfigFileConstants.VariableDefinitions, _path)); @@ -1036,11 +1099,12 @@ protected override void ProcessRecord() isExample = true; _assembliesToLoad = new string[] { "System.Web", "System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" }; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.AssembliesToLoad, RemotingErrorIdStrings.DISCAssembliesToLoadComment, SessionConfigurationUtils.CombineStringArray(_assembliesToLoad), streamWriter, isExample)); } - result.Append("}"); + result.Append('}'); streamWriter.Write(result.ToString()); } @@ -1073,7 +1137,7 @@ public class NewPSRoleCapabilityFileCommand : PSCmdlet #region Parameters /// - /// Destination path + /// Destination path. /// [Parameter(Position = 0, Mandatory = true)] [ValidateNotNullOrEmpty] @@ -1083,102 +1147,114 @@ public string Path { return _path; } + set { _path = value; } } + private string _path; /// - /// Configuration file GUID + /// Configuration file GUID. /// - [Parameter()] + [Parameter] public Guid Guid { get { return _guid; } + set { _guid = value; } } + private Guid _guid = Guid.NewGuid(); /// - /// Author of the configuration file + /// Author of the configuration file. /// - [Parameter()] + [Parameter] public string Author { get { return _author; } + set { _author = value; } } + private string _author; /// - /// Description + /// Description. /// - [Parameter()] + [Parameter] public string Description { get { return _description; } + set { _description = value; } } + private string _description; /// - /// Company name + /// Company name. /// - [Parameter()] + [Parameter] public string CompanyName { get { return _companyName; } + set { _companyName = value; } } + private string _companyName; /// - /// Copyright information + /// Copyright information. /// - [Parameter()] + [Parameter] public string Copyright { get { return _copyright; } + set { _copyright = value; } } + private string _copyright; /// - /// A list of modules to import + /// A list of modules to import. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object[] ModulesToImport { @@ -1186,17 +1262,19 @@ public object[] ModulesToImport { return _modulesToImport; } + set { _modulesToImport = value; } } + private object[] _modulesToImport; /// - /// A list of visible aliases + /// A list of visible aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleAliases { @@ -1204,53 +1282,59 @@ public string[] VisibleAliases { return _visibleAliases; } + set { _visibleAliases = value; } } - private string[] _visibleAliases = Utils.EmptyArray(); + + private string[] _visibleAliases = Array.Empty(); /// - /// A list of visible cmdlets + /// A list of visible cmdlets. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Object[] VisibleCmdlets + public object[] VisibleCmdlets { get { return _visibleCmdlets; } + set { _visibleCmdlets = value; } } - private Object[] _visibleCmdlets = null; + + private object[] _visibleCmdlets = null; /// - /// A list of visible functions + /// A list of visible functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public Object[] VisibleFunctions + public object[] VisibleFunctions { get { return _visibleFunctions; } + set { _visibleFunctions = value; } } - private Object[] _visibleFunctions = null; + + private object[] _visibleFunctions = null; /// /// A list of visible external commands (scripts and applications) /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleExternalCommands { @@ -1258,17 +1342,19 @@ public string[] VisibleExternalCommands { return _visibleExternalCommands; } + set { _visibleExternalCommands = value; } } - private string[] _visibleExternalCommands = Utils.EmptyArray(); + + private string[] _visibleExternalCommands = Array.Empty(); /// - /// A list of providers + /// A list of providers. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] VisibleProviders { @@ -1276,17 +1362,19 @@ public string[] VisibleProviders { return _visibleProviders; } + set { _visibleProviders = value; } } - private string[] _visibleProviders = Utils.EmptyArray(); + + private string[] _visibleProviders = Array.Empty(); /// - /// Scripts to process + /// Scripts to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ScriptsToProcess { @@ -1294,17 +1382,19 @@ public string[] ScriptsToProcess { return _scriptsToProcess; } + set { _scriptsToProcess = value; } } - private string[] _scriptsToProcess = Utils.EmptyArray(); + + private string[] _scriptsToProcess = Array.Empty(); /// - /// A list of aliases + /// A list of aliases. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] AliasDefinitions { @@ -1312,17 +1402,19 @@ public IDictionary[] AliasDefinitions { return _aliasDefinitions; } + set { _aliasDefinitions = value; } } + private IDictionary[] _aliasDefinitions; /// - /// A list of functions + /// A list of functions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public IDictionary[] FunctionDefinitions { @@ -1330,17 +1422,19 @@ public IDictionary[] FunctionDefinitions { return _functionDefinitions; } + set { _functionDefinitions = value; } } + private IDictionary[] _functionDefinitions; /// - /// A list of variables + /// A list of variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public object VariableDefinitions { @@ -1348,17 +1442,19 @@ public object VariableDefinitions { return _variableDefinitions; } + set { _variableDefinitions = value; } } + private object _variableDefinitions; /// - /// A list of environment variables + /// A list of environment variables. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public IDictionary EnvironmentVariables @@ -1367,17 +1463,19 @@ public IDictionary EnvironmentVariables { return _environmentVariables; } + set { _environmentVariables = value; } } + private IDictionary _environmentVariables; /// - /// A list of types to process + /// A list of types to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] TypesToProcess { @@ -1385,17 +1483,19 @@ public string[] TypesToProcess { return _typesToProcess; } + set { _typesToProcess = value; } } - private string[] _typesToProcess = Utils.EmptyArray(); + + private string[] _typesToProcess = Array.Empty(); /// - /// A list of format data to process + /// A list of format data to process. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] FormatsToProcess { @@ -1403,17 +1503,19 @@ public string[] FormatsToProcess { return _formatsToProcess; } + set { _formatsToProcess = value; } } - private string[] _formatsToProcess = Utils.EmptyArray(); + + private string[] _formatsToProcess = Array.Empty(); /// - /// A list of assemblies to load + /// A list of assemblies to load. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] AssembliesToLoad { @@ -1421,11 +1523,13 @@ public string[] AssembliesToLoad { return _assembliesToLoad; } + set { _assembliesToLoad = value; } } + private string[] _assembliesToLoad; #endregion @@ -1433,11 +1537,10 @@ public string[] AssembliesToLoad #region Overrides /// - /// /// protected override void ProcessRecord() { - Debug.Assert(!String.IsNullOrEmpty(_path)); + Debug.Assert(!string.IsNullOrEmpty(_path)); ProviderInfo provider = null; PSDriveInfo drive; @@ -1483,37 +1586,40 @@ protected override void ProcessRecord() result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Guid, RemotingErrorIdStrings.DISCGUIDComment, SessionConfigurationUtils.QuoteName(_guid), streamWriter, false)); // Author - if (String.IsNullOrEmpty(_author)) + if (string.IsNullOrEmpty(_author)) { _author = Environment.UserName; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Author, RemotingErrorIdStrings.DISCAuthorComment, SessionConfigurationUtils.QuoteName(_author), streamWriter, false)); // Description result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Description, RemotingErrorIdStrings.DISCDescriptionComment, - SessionConfigurationUtils.QuoteName(_description), streamWriter, String.IsNullOrEmpty(_description))); + SessionConfigurationUtils.QuoteName(_description), streamWriter, string.IsNullOrEmpty(_description))); // Company name - if (String.IsNullOrEmpty(_companyName)) + if (string.IsNullOrEmpty(_companyName)) { _companyName = Modules.DefaultCompanyName; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.CompanyName, RemotingErrorIdStrings.DISCCompanyNameComment, SessionConfigurationUtils.QuoteName(_companyName), streamWriter, false)); // Copyright - if (String.IsNullOrEmpty(_copyright)) + if (string.IsNullOrEmpty(_copyright)) { _copyright = StringUtil.Format(Modules.DefaultCopyrightMessage, _author); } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.Copyright, RemotingErrorIdStrings.DISCCopyrightComment, SessionConfigurationUtils.QuoteName(_copyright), streamWriter, false)); // 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 @@ -1606,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)); @@ -1615,9 +1721,9 @@ protected override void ProcessRecord() foreach (string functionKey in hashtable.Keys) { - if (!String.Equals(functionKey, ConfigFileConstants.FunctionNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, ConfigFileConstants.FunctionValueToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, ConfigFileConstants.FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(functionKey, ConfigFileConstants.FunctionNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, ConfigFileConstants.FunctionValueToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, ConfigFileConstants.FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, functionKey, ConfigFileConstants.FunctionDefinitions, _path)); @@ -1676,8 +1782,8 @@ protected override void ProcessRecord() foreach (string variableKey in hashtable.Keys) { - if (!String.Equals(variableKey, ConfigFileConstants.VariableNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(variableKey, ConfigFileConstants.VariableValueToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(variableKey, ConfigFileConstants.VariableNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(variableKey, ConfigFileConstants.VariableValueToken, StringComparison.OrdinalIgnoreCase)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, variableKey, ConfigFileConstants.VariableDefinitions, _path)); @@ -1725,10 +1831,11 @@ protected override void ProcessRecord() isExample = true; _assembliesToLoad = new string[] { "System.Web", "System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" }; } + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.AssembliesToLoad, RemotingErrorIdStrings.DISCAssembliesToLoadComment, SessionConfigurationUtils.CombineStringArray(_assembliesToLoad), streamWriter, isExample)); - result.Append("}"); + result.Append('}'); streamWriter.Write(result.ToString()); } @@ -1744,9 +1851,9 @@ protected override void ProcessRecord() #region SessionConfigurationUtils /// - /// Utility methods for configuration file commands + /// Utility methods for configuration file commands. /// - internal class SessionConfigurationUtils + internal static class SessionConfigurationUtils { /// /// This routine builds a fragment of the config file @@ -1759,31 +1866,32 @@ 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); } /// /// Return a single-quoted string. Any embedded single quotes will be doubled. /// - /// The string to quote - /// The quoted string + /// The string to quote. + /// The quoted string. internal static string QuoteName(object name) { if (name == null) + { return "''"; + } + return "'" + System.Management.Automation.Language.CodeGeneration.EscapeSingleQuotedStringContent(name.ToString()) + "'"; } /// /// Return a script block string wrapped in curly braces. /// - /// The string to wrap - /// The wrapped string + /// The string to wrap. + /// The wrapped string. internal static string WrapScriptBlock(object sb) { if (sb == null) @@ -1812,7 +1920,7 @@ internal static string WriteLong(long longToEmit) } /// - /// Gets the visibility default value + /// Gets the visibility default value. /// internal static string GetVisibilityDefault(object[] values, StreamWriter writer, PSCmdlet caller) { @@ -1827,7 +1935,7 @@ internal static string GetVisibilityDefault(object[] values, StreamWriter writer } /// - /// Combines a hashtable into a single string block + /// Combines a hashtable into a single string block. /// internal static string CombineHashtable(IDictionary table, StreamWriter writer, int? indent = 0) { @@ -1835,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)) + "}", ""); + 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; @@ -1948,7 +2056,7 @@ private static void WriteRequiredGroup(object value, StringBuilder sb) } /// - /// Combines an array of hashtables into a single string block + /// Combines an array of hashtables into a single string block. /// internal static string CombineHashtableArray(IDictionary[] tables, StreamWriter writer, int? indent = 0) { @@ -1968,17 +2076,17 @@ internal static string CombineHashtableArray(IDictionary[] tables, StreamWriter } /// - /// Combines an array of strings into a single string block + /// Combines an array of strings into a single string block. /// - /// string values - /// string block + /// String values. + /// String block. internal static string CombineStringArray(string[] values) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < values.Length; i++) { - if (!String.IsNullOrEmpty(values[i])) + if (!string.IsNullOrEmpty(values[i])) { sb.Append(QuoteName(values[i])); @@ -1993,7 +2101,7 @@ internal static string CombineStringArray(string[] values) } /// - /// Combines an array of strings or hashtables into a single string block + /// Combines an array of strings or hashtables into a single string block. /// internal static string CombineHashTableOrStringArray(object[] values, StreamWriter writer, PSCmdlet caller) { @@ -2001,20 +2109,21 @@ internal static string CombineHashTableOrStringArray(object[] values, StreamWrit for (int i = 0; i < values.Length; i++) { string strVal = values[i] as string; - if (!String.IsNullOrEmpty(strVal)) + if (!string.IsNullOrEmpty(strVal)) { sb.Append(QuoteName(strVal)); } else { Hashtable hashVal = values[i] as Hashtable; - if (null == hashVal) + if (hashVal == null) { string message = StringUtil.Format(RemotingErrorIdStrings.DISCTypeMustBeStringOrHashtableArray, ConfigFileConstants.ModulesToImport); PSArgumentException e = new PSArgumentException(message); caller.ThrowTerminatingError(e.ErrorRecord); } + sb.Append(CombineHashtable(hashVal, writer)); } @@ -2029,4 +2138,4 @@ internal static string CombineHashTableOrStringArray(object[] values, StreamWrit } #endregion -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs index 0823bc7cad8..fdd9d5fc6fe 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs @@ -1,17 +1,16 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Text; using System.Collections; -using System.Management.Automation; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Text; namespace Microsoft.PowerShell.Commands { /// - /// Implementing type for WSManConfigurationOption + /// Implementing type for WSManConfigurationOption. /// public class WSManConfigurationOption : PSTransportOption { @@ -19,99 +18,113 @@ public class WSManConfigurationOption : PSTransportOption private const string QuotasToken = ""; internal const string AttribOutputBufferingMode = "OutputBufferingMode"; - internal static System.Management.Automation.Runspaces.OutputBufferingMode? DefaultOutputBufferingMode = System.Management.Automation.Runspaces.OutputBufferingMode.Block; + + internal static readonly System.Management.Automation.Runspaces.OutputBufferingMode? DefaultOutputBufferingMode = System.Management.Automation.Runspaces.OutputBufferingMode.Block; private System.Management.Automation.Runspaces.OutputBufferingMode? _outputBufferingMode = null; private const string AttribProcessIdleTimeout = "ProcessIdleTimeoutSec"; + internal static readonly int? DefaultProcessIdleTimeout_ForPSRemoting = 0; // in seconds - internal static readonly int? DefaultProcessIdleTimeout_ForWorkflow = 1209600; // in seconds private int? _processIdleTimeoutSec = null; internal const string AttribMaxIdleTimeout = "MaxIdleTimeoutms"; + internal static readonly int? DefaultMaxIdleTimeout = int.MaxValue; private int? _maxIdleTimeoutSec = null; internal const string AttribIdleTimeout = "IdleTimeoutms"; + internal static readonly int? DefaultIdleTimeout = 7200; // 2 hours in seconds private int? _idleTimeoutSec = null; private const string AttribMaxConcurrentUsers = "MaxConcurrentUsers"; + internal static readonly int? DefaultMaxConcurrentUsers = int.MaxValue; private int? _maxConcurrentUsers = null; private const string AttribMaxProcessesPerSession = "MaxProcessesPerShell"; + internal static readonly int? DefaultMaxProcessesPerSession = int.MaxValue; private int? _maxProcessesPerSession = null; private const string AttribMaxMemoryPerSessionMB = "MaxMemoryPerShellMB"; + internal static readonly int? DefaultMaxMemoryPerSessionMB = int.MaxValue; private int? _maxMemoryPerSessionMB = null; private const string AttribMaxSessions = "MaxShells"; + internal static readonly int? DefaultMaxSessions = int.MaxValue; private int? _maxSessions = null; private const string AttribMaxSessionsPerUser = "MaxShellsPerUser"; + internal static readonly int? DefaultMaxSessionsPerUser = int.MaxValue; private int? _maxSessionsPerUser = null; private const string AttribMaxConcurrentCommandsPerSession = "MaxConcurrentCommandsPerShell"; + internal static readonly int? DefaultMaxConcurrentCommandsPerSession = int.MaxValue; private int? _maxConcurrentCommandsPerSession = null; /// - /// Constructor that instantiates with default values + /// Constructor that instantiates with default values. /// internal WSManConfigurationOption() { } /// - /// LoadFromDefaults + /// Override LoadFromDefaults method. /// - /// - /// - protected internal override void LoadFromDefaults(PSSessionType sessionType, bool keepAssigned) + /// Keep old values. + protected internal override void LoadFromDefaults(bool keepAssigned) { if (!keepAssigned || !_outputBufferingMode.HasValue) { _outputBufferingMode = DefaultOutputBufferingMode; } + if (!keepAssigned || !_processIdleTimeoutSec.HasValue) { - _processIdleTimeoutSec - = sessionType == PSSessionType.Workflow - ? DefaultProcessIdleTimeout_ForWorkflow - : DefaultProcessIdleTimeout_ForPSRemoting; + _processIdleTimeoutSec = DefaultProcessIdleTimeout_ForPSRemoting; } + if (!keepAssigned || !_maxIdleTimeoutSec.HasValue) { _maxIdleTimeoutSec = DefaultMaxIdleTimeout; } + if (!keepAssigned || !_idleTimeoutSec.HasValue) { _idleTimeoutSec = DefaultIdleTimeout; } + if (!keepAssigned || !_maxConcurrentUsers.HasValue) { _maxConcurrentUsers = DefaultMaxConcurrentUsers; } + if (!keepAssigned || !_maxProcessesPerSession.HasValue) { _maxProcessesPerSession = DefaultMaxProcessesPerSession; } + if (!keepAssigned || !_maxMemoryPerSessionMB.HasValue) { _maxMemoryPerSessionMB = DefaultMaxMemoryPerSessionMB; } + if (!keepAssigned || !_maxSessions.HasValue) { _maxSessions = DefaultMaxSessions; } + if (!keepAssigned || !_maxSessionsPerUser.HasValue) { _maxSessionsPerUser = DefaultMaxSessionsPerUser; } + if (!keepAssigned || !_maxConcurrentCommandsPerSession.HasValue) { _maxConcurrentCommandsPerSession = DefaultMaxConcurrentCommandsPerSession; @@ -119,7 +132,7 @@ protected internal override void LoadFromDefaults(PSSessionType sessionType, boo } /// - /// ProcessIdleTimeout in Seconds + /// ProcessIdleTimeout in Seconds. /// public int? ProcessIdleTimeoutSec { @@ -127,6 +140,7 @@ public int? ProcessIdleTimeoutSec { return _processIdleTimeoutSec; } + internal set { _processIdleTimeoutSec = value; @@ -134,7 +148,7 @@ internal set } /// - /// MaxIdleTimeout in Seconds + /// MaxIdleTimeout in Seconds. /// public int? MaxIdleTimeoutSec { @@ -142,6 +156,7 @@ public int? MaxIdleTimeoutSec { return _maxIdleTimeoutSec; } + internal set { _maxIdleTimeoutSec = value; @@ -149,7 +164,7 @@ internal set } /// - /// MaxSessions + /// MaxSessions. /// public int? MaxSessions { @@ -157,6 +172,7 @@ public int? MaxSessions { return _maxSessions; } + internal set { _maxSessions = value; @@ -164,7 +180,7 @@ internal set } /// - /// MaxConcurrentCommandsPerSession + /// MaxConcurrentCommandsPerSession. /// public int? MaxConcurrentCommandsPerSession { @@ -172,6 +188,7 @@ public int? MaxConcurrentCommandsPerSession { return _maxConcurrentCommandsPerSession; } + internal set { _maxConcurrentCommandsPerSession = value; @@ -179,7 +196,7 @@ internal set } /// - /// MaxSessionsPerUser + /// MaxSessionsPerUser. /// public int? MaxSessionsPerUser { @@ -187,6 +204,7 @@ public int? MaxSessionsPerUser { return _maxSessionsPerUser; } + internal set { _maxSessionsPerUser = value; @@ -194,7 +212,7 @@ internal set } /// - /// MaxMemoryPerSessionMB + /// MaxMemoryPerSessionMB. /// public int? MaxMemoryPerSessionMB { @@ -202,6 +220,7 @@ public int? MaxMemoryPerSessionMB { return _maxMemoryPerSessionMB; } + internal set { _maxMemoryPerSessionMB = value; @@ -209,7 +228,7 @@ internal set } /// - /// MaxProcessesPerSession + /// MaxProcessesPerSession. /// public int? MaxProcessesPerSession { @@ -217,6 +236,7 @@ public int? MaxProcessesPerSession { return _maxProcessesPerSession; } + internal set { _maxProcessesPerSession = value; @@ -224,7 +244,7 @@ internal set } /// - /// MaxConcurrentUsers + /// MaxConcurrentUsers. /// public int? MaxConcurrentUsers { @@ -232,6 +252,7 @@ public int? MaxConcurrentUsers { return _maxConcurrentUsers; } + internal set { _maxConcurrentUsers = value; @@ -239,7 +260,7 @@ internal set } /// - /// IdleTimeout in Seconds + /// IdleTimeout in Seconds. /// public int? IdleTimeoutSec { @@ -247,6 +268,7 @@ public int? IdleTimeoutSec { return _idleTimeoutSec; } + internal set { _idleTimeoutSec = value; @@ -254,7 +276,7 @@ internal set } /// - /// OutputBufferingMode + /// OutputBufferingMode. /// public System.Management.Automation.Runspaces.OutputBufferingMode? OutputBufferingMode { @@ -262,6 +284,7 @@ public System.Management.Automation.Runspaces.OutputBufferingMode? OutputBufferi { return _outputBufferingMode; } + internal set { _outputBufferingMode = value; @@ -276,6 +299,7 @@ internal override Hashtable ConstructQuotasAsHashtable() { quotas[AttribIdleTimeout] = (1000 * _idleTimeoutSec.Value).ToString(CultureInfo.InvariantCulture); } + if (_maxConcurrentUsers.HasValue) { quotas[AttribMaxConcurrentUsers] = _maxConcurrentUsers.Value.ToString(CultureInfo.InvariantCulture); @@ -285,31 +309,37 @@ internal override Hashtable ConstructQuotasAsHashtable() { quotas[AttribMaxProcessesPerSession] = _maxProcessesPerSession.Value.ToString(CultureInfo.InvariantCulture); } + if (_maxMemoryPerSessionMB.HasValue) { quotas[AttribMaxMemoryPerSessionMB] = _maxMemoryPerSessionMB.Value.ToString(CultureInfo.InvariantCulture); } + if (_maxSessionsPerUser.HasValue) { quotas[AttribMaxSessionsPerUser] = _maxSessionsPerUser.Value.ToString(CultureInfo.InvariantCulture); } + if (_maxConcurrentCommandsPerSession.HasValue) { quotas[AttribMaxConcurrentCommandsPerSession] = _maxConcurrentCommandsPerSession.Value.ToString(CultureInfo.InvariantCulture); } + if (_maxSessions.HasValue) { quotas[AttribMaxSessions] = _maxSessions.Value.ToString(CultureInfo.InvariantCulture); } + if (_maxIdleTimeoutSec.HasValue) { quotas[AttribMaxIdleTimeout] = (1000 * _maxIdleTimeoutSec.Value).ToString(CultureInfo.InvariantCulture); } + return quotas; } /// - /// ConstructQuotas + /// ConstructQuotas. /// /// internal override string ConstructQuotas() @@ -320,6 +350,7 @@ internal override string ConstructQuotas() { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribIdleTimeout, 1000 * _idleTimeoutSec)); } + if (_maxConcurrentUsers.HasValue) { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxConcurrentUsers, _maxConcurrentUsers)); @@ -329,22 +360,27 @@ internal override string ConstructQuotas() { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxProcessesPerSession, _maxProcessesPerSession)); } + if (_maxMemoryPerSessionMB.HasValue) { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxMemoryPerSessionMB, _maxMemoryPerSessionMB)); } + if (_maxSessionsPerUser.HasValue) { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxSessionsPerUser, _maxSessionsPerUser)); } + if (_maxConcurrentCommandsPerSession.HasValue) { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxConcurrentCommandsPerSession, _maxConcurrentCommandsPerSession)); } + if (_maxSessions.HasValue) { sb.Append(string.Format(CultureInfo.InvariantCulture, Token, AttribMaxSessions, _maxSessions)); } + if (_maxIdleTimeoutSec.HasValue) { // Special case max int value for unbounded default. @@ -358,7 +394,7 @@ internal override string ConstructQuotas() } /// - /// ConstructOptionsXmlAttributes + /// ConstructOptionsXmlAttributes. /// /// internal override string ConstructOptionsAsXmlAttributes() @@ -378,7 +414,7 @@ internal override string ConstructOptionsAsXmlAttributes() } /// - /// ConstructOptionsXmlAttributes + /// ConstructOptionsXmlAttributes. /// /// internal override Hashtable ConstructOptionsAsHashtable() @@ -399,16 +435,16 @@ internal override Hashtable ConstructOptionsAsHashtable() } /// - /// Command to create an object for WSManConfigurationOption + /// Command to create an object for WSManConfigurationOption. /// [Cmdlet(VerbsCommon.New, "PSTransportOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=210608", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(WSManConfigurationOption))] public sealed class NewPSTransportOptionCommand : PSCmdlet { - private WSManConfigurationOption _option = new WSManConfigurationOption(); + private readonly WSManConfigurationOption _option = new WSManConfigurationOption(); /// - /// MaxIdleTimeoutSec + /// MaxIdleTimeoutSec. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(60, 2147483)] public int? MaxIdleTimeoutSec @@ -417,6 +453,7 @@ public int? MaxIdleTimeoutSec { return _option.MaxIdleTimeoutSec; } + set { _option.MaxIdleTimeoutSec = value; @@ -424,7 +461,7 @@ public int? MaxIdleTimeoutSec } /// - /// ProcessIdleTimeoutSec + /// ProcessIdleTimeoutSec. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(0, 1209600)] public int? ProcessIdleTimeoutSec @@ -433,6 +470,7 @@ public int? ProcessIdleTimeoutSec { return _option.ProcessIdleTimeoutSec; } + set { _option.ProcessIdleTimeoutSec = value; @@ -440,7 +478,7 @@ public int? ProcessIdleTimeoutSec } /// - /// MaxSessions + /// MaxSessions. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(1, int.MaxValue)] public int? MaxSessions @@ -449,6 +487,7 @@ public int? MaxSessions { return _option.MaxSessions; } + set { _option.MaxSessions = value; @@ -456,7 +495,7 @@ public int? MaxSessions } /// - /// MaxConcurrentCommandsPerSession + /// MaxConcurrentCommandsPerSession. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(1, int.MaxValue)] public int? MaxConcurrentCommandsPerSession @@ -465,6 +504,7 @@ public int? MaxConcurrentCommandsPerSession { return _option.MaxConcurrentCommandsPerSession; } + set { _option.MaxConcurrentCommandsPerSession = value; @@ -472,7 +512,7 @@ public int? MaxConcurrentCommandsPerSession } /// - /// MaxSessionsPerUser + /// MaxSessionsPerUser. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(1, int.MaxValue)] public int? MaxSessionsPerUser @@ -481,6 +521,7 @@ public int? MaxSessionsPerUser { return _option.MaxSessionsPerUser; } + set { _option.MaxSessionsPerUser = value; @@ -488,7 +529,7 @@ public int? MaxSessionsPerUser } /// - /// MaxMemoryPerSessionMB + /// MaxMemoryPerSessionMB. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(5, int.MaxValue)] public int? MaxMemoryPerSessionMB @@ -497,6 +538,7 @@ public int? MaxMemoryPerSessionMB { return _option.MaxMemoryPerSessionMB; } + set { _option.MaxMemoryPerSessionMB = value; @@ -504,7 +546,7 @@ public int? MaxMemoryPerSessionMB } /// - /// MaxProcessesPerSession + /// MaxProcessesPerSession. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(1, int.MaxValue)] public int? MaxProcessesPerSession @@ -513,6 +555,7 @@ public int? MaxProcessesPerSession { return _option.MaxProcessesPerSession; } + set { _option.MaxProcessesPerSession = value; @@ -520,7 +563,7 @@ public int? MaxProcessesPerSession } /// - /// MaxConcurrentUsers + /// MaxConcurrentUsers. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(1, 100)] public int? MaxConcurrentUsers @@ -529,6 +572,7 @@ public int? MaxConcurrentUsers { return _option.MaxConcurrentUsers; } + set { _option.MaxConcurrentUsers = value; @@ -536,7 +580,7 @@ public int? MaxConcurrentUsers } /// - /// IdleTimeoutMs + /// IdleTimeoutMs. /// [Parameter(ValueFromPipelineByPropertyName = true), ValidateRange(60, 2147483)] public int? IdleTimeoutSec @@ -545,6 +589,7 @@ public int? IdleTimeoutSec { return _option.IdleTimeoutSec; } + set { _option.IdleTimeoutSec = value; @@ -552,7 +597,7 @@ public int? IdleTimeoutSec } /// - /// OutputBufferingMode + /// OutputBufferingMode. /// [Parameter(ValueFromPipelineByPropertyName = true)] public System.Management.Automation.Runspaces.OutputBufferingMode? OutputBufferingMode @@ -561,6 +606,7 @@ public System.Management.Automation.Runspaces.OutputBufferingMode? OutputBufferi { return _option.OutputBufferingMode; } + set { _option.OutputBufferingMode = value; @@ -568,7 +614,7 @@ public System.Management.Automation.Runspaces.OutputBufferingMode? OutputBufferi } /// - /// Overriding the base method + /// Overriding the base method. /// protected override void ProcessRecord() { diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs index 7ee8ffc0511..bb7641d2e5b 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Diagnostics.CodeAnalysis; @@ -9,277 +8,32 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; -namespace System.Management.Automation.Remoting -{ - /// - /// IMPORTANT: proxy configuration is supported for HTTPS only; for HTTP, the direct - /// connection to the server is used - /// - [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] - public enum ProxyAccessType - { - /// - /// ProxyAccessType is not specified. That means Proxy information (ProxyAccessType, ProxyAuthenticationMechanism - /// and ProxyCredential)is not passed to WSMan at all. - /// - None = 0, - /// - /// use the Internet Explorer proxy configuration for the current user. - /// Internet Explorer proxy settings for the current active network connection. - /// This option requires the user profile to be loaded, so the option can - /// be directly used when called within a process that is running under - /// an interactive user account identity; if the client application is running - /// under a user context different than the interactive user, the client - /// application has to explicitly load the user profile prior to using this option. - /// - IEConfig = 1, - /// - /// proxy settings configured for WinHTTP, using the ProxyCfg.exe utility - /// - WinHttpConfig = 2, - /// - /// Force autodetection of proxy - /// - AutoDetect = 4, - /// - /// do not use a proxy server - resolves all host names locally - /// - NoProxyServer = 8 - } - /// - /// Options for a remote PSSession - /// - public sealed class PSSessionOption - { - /// - /// Creates a new instance of - /// - public PSSessionOption() - { - } - - /// - /// The MaximumConnectionRedirectionCount parameter enables the implicit redirection functionality. - /// -1 = no limit - /// 0 = no redirection - /// - public int MaximumConnectionRedirectionCount { get; set; } = WSManConnectionInfo.defaultMaximumConnectionRedirectionCount; - - /// - /// If false, underlying WSMan infrastructure will compress data sent on the network. - /// If true, data will not be compressed. Compression improves performance by - /// reducing the amount of data sent on the network. Compression my require extra - /// memory consumption and CPU usage. In cases where available memory / CPU is less, - /// set this property to "true". - /// By default the value of this property is "false". - /// - public bool NoCompression { get; set; } = false; - - /// - /// If true 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). - /// - public bool NoMachineProfile { get; set; } = false; - - /// - /// By default, ProxyAccessType is None, that means Proxy information (ProxyAccessType, - /// ProxyAuthenticationMechanism and ProxyCredential)is not passed to WSMan at all. - /// - public ProxyAccessType ProxyAccessType { get; set; } = ProxyAccessType.None; - - /// - /// The following is the definition of the input parameter "ProxyAuthentication". - /// This parameter takes a set of authentication methods the user can select - /// from. The available options should be as follows: - /// - Negotiate: Use the default authentication (as defined by the underlying - /// protocol) for establishing a remote connection. - /// - Basic: Use basic authentication for establishing a remote connection - /// - Digest: Use Digest authentication for establishing a remote connection - /// - /// Default is Negotiate. - /// - public AuthenticationMechanism ProxyAuthentication - { - get { return _proxyAuthentication; } - set - { - switch (value) - { - case AuthenticationMechanism.Basic: - case AuthenticationMechanism.Negotiate: - case AuthenticationMechanism.Digest: - _proxyAuthentication = value; - break; - default: - string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyAmbiguousAuthentication, - value, - AuthenticationMechanism.Basic.ToString(), - AuthenticationMechanism.Negotiate.ToString(), - AuthenticationMechanism.Digest.ToString()); - throw new ArgumentException(message); - } - } - } - private AuthenticationMechanism _proxyAuthentication = AuthenticationMechanism.Negotiate; - - /// - /// The following is the definition of the input parameter "ProxyCredential". - /// - public PSCredential ProxyCredential { get; set; } - - - /// - /// When connecting over HTTPS, the client does not validate that the server - /// certificate is signed by a trusted certificate authority (CA). Use only when - /// the remote computer is trusted by other means, for example, if the remote - /// computer is part of a network that is physically secure and isolated or the - /// remote computer is listed as a trusted host in WinRM configuration - /// - public bool SkipCACheck { get; set; } - - /// - /// Indicates that certificate common name (CN) of the server need not match the - /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines. - /// - public bool SkipCNCheck { get; set; } - - /// - /// Indicates that certificate common name (CN) of the server need not match the - /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines - /// - public bool SkipRevocationCheck { get; set; } - - /// - /// The duration for which PowerShell remoting waits before timing out - /// for any operation. The user would like to tweak this timeout - /// depending on whether he/she is connecting to a machine in the data - /// center or across a slow WAN. - /// - /// Default: 3*60*1000 == 3minutes - /// - public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromMilliseconds(BaseTransportManager.ClientDefaultOperationTimeoutMs); - - /// - /// Specifies that no encryption will be used when doing remote operations over - /// http. Unencrypted traffic is not allowed by default and must be enabled in - /// the local configuration - /// - public bool NoEncryption { get; set; } - - /// - /// Indicates the request is encoded in UTF16 format rather than UTF8 format; - /// UTF8 is the default. - /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] - public bool UseUTF16 { get; set; } - - /// - /// Uses Service Principal Name (SPN) along with the Port number during authentication. - /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] - public bool IncludePortInSPN { get; set; } - - /// - /// Determines how server in disconnected state deals with cached output - /// data when the cache becomes filled. - /// Default value is 'block mode' where command execution is blocked after - /// the server side data cache becomes filled. - /// - public OutputBufferingMode OutputBufferingMode { get; set; } = WSManConnectionInfo.DefaultOutputBufferingMode; - - /// - /// Number of times a connection will be re-attempted when a connection fails due to network - /// issues. - /// - public int MaxConnectionRetryCount { get; set; } = WSManConnectionInfo.DefaultMaxConnectionRetryCount; - - /// - /// Culture that the remote session should use - /// - public CultureInfo Culture { get; set; } - - /// - /// UI culture that the remote session should use - /// - public CultureInfo UICulture { get; set; } - - /// - /// Total data (in bytes) that can be received from a remote machine - /// targeted towards a command. If null, then the size is unlimited. - /// Default is unlimited data. - /// - public Nullable MaximumReceivedDataSizePerCommand { get; set; } - - /// - /// Maximum size (in bytes) of a deserialized object received from a remote machine. - /// If null, then the size is unlimited. Default is 200MB object size. - /// - public Nullable MaximumReceivedObjectSize { get; set; } = 200 << 20; - - /// - /// Application arguments the server can see in - /// - [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public PSPrimitiveDictionary ApplicationArguments { get; set; } - - /// - /// The duration for which PowerShell remoting waits before timing out on a connection to a remote machine. - /// Simply put, the timeout for a remote runspace creation. - /// The user would like to tweak this timeout depending on whether - /// he/she is connecting to a machine in the data center or across a slow WAN. - /// - /// Default: 3 * 60 * 1000 = 3 minutes - /// - public TimeSpan OpenTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultOpenTimeout); - - /// - /// The duration for which PowerShell should wait before it times out on cancel operations - /// (close runspace or stop powershell). For instance, when the user hits ctrl-C, - /// New-PSSession cmdlet tries to call a stop on all remote runspaces which are in the Opening state. - /// The user wouldn't mind waiting for 15 seconds, but this should be time bound and of a shorter duration. - /// A high timeout here like 3 minutes will give the user a feeling that the PowerShell client is not responding. - /// - /// Default: 60 * 1000 = 1 minute - /// - public TimeSpan CancelTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.defaultCancelTimeout); - - /// - /// The duration for which a Runspace on server needs to wait before it declares the client dead and closes itself down. - /// This is especially important as these values may have to be configured differently for enterprise administration - /// and exchange scenarios. - /// - /// Default: -1 -> Use current server value for IdleTimeout. - /// - public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultIdleTimeout); - } -} - namespace Microsoft.PowerShell.Commands { /// /// This class implements New-PSSessionOption cmdlet. - /// Spec: TBD + /// Spec: TBD. /// - [Cmdlet(VerbsCommon.New, "PSSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144305", RemotingCapability = RemotingCapability.None)] + [Cmdlet(VerbsCommon.New, "PSSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096488", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(PSSessionOption))] public sealed class NewPSSessionOptionCommand : PSCmdlet { #region Parameters (specific to PSSessionOption) +#if !UNIX /// /// The MaximumRedirection parameter enables the implicit redirection functionality /// -1 = no limit - /// 0 = no redirection + /// 0 = no redirection. /// [Parameter] public int MaximumRedirection { get { return _maximumRedirection.Value; } + set { _maximumRedirection = value; } } + private int? _maximumRedirection; /// @@ -294,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). /// @@ -302,14 +56,14 @@ public int MaximumRedirection public SwitchParameter NoMachineProfile { get; set; } /// - /// Culture that the remote session should use + /// Culture that the remote session should use. /// [Parameter] [ValidateNotNull] public CultureInfo Culture { get; set; } /// - /// UI culture that the remote session should use + /// UI culture that the remote session should use. /// [Parameter] [ValidateNotNull] @@ -324,9 +78,11 @@ public int MaximumRedirection public int MaximumReceivedDataSizePerCommand { get { return _maxRecvdDataSizePerCommand.Value; } + set { _maxRecvdDataSizePerCommand = value; } } - private Nullable _maxRecvdDataSizePerCommand; + + private int? _maxRecvdDataSizePerCommand; /// /// Maximum size (in bytes) of a deserialized object received from a remote machine. @@ -336,9 +92,11 @@ public int MaximumReceivedDataSizePerCommand public int MaximumReceivedObjectSize { get { return _maxRecvdObjectSize.Value; } + set { _maxRecvdObjectSize = value; } } - private Nullable _maxRecvdObjectSize; + + private int? _maxRecvdObjectSize; /// /// Specifies the output mode on the server when it is in Disconnected mode @@ -378,11 +136,15 @@ public int OpenTimeout { get { - return _openTimeout.HasValue ? _openTimeout.Value : - RunspaceConnectionInfo.DefaultOpenTimeout; + return _openTimeout ?? RunspaceConnectionInfo.DefaultOpenTimeout; + } + + set + { + _openTimeout = value; } - set { _openTimeout = value; } } + private int? _openTimeout; /// @@ -401,11 +163,15 @@ public int CancelTimeout { get { - return _cancelTimeout.HasValue ? _cancelTimeout.Value : - BaseTransportManager.ClientCloseTimeoutMs; + return _cancelTimeout ?? BaseTransportManager.ClientCloseTimeoutMs; + } + + set + { + _cancelTimeout = value; } - set { _cancelTimeout = value; } } + private int? _cancelTimeout; /// @@ -421,17 +187,23 @@ public int IdleTimeout { get { - return _idleTimeout.HasValue ? _idleTimeout.Value - : RunspaceConnectionInfo.DefaultIdleTimeout; + return _idleTimeout ?? RunspaceConnectionInfo.DefaultIdleTimeout; + } + + set + { + _idleTimeout = value; } - set { _idleTimeout = value; } } + private int? _idleTimeout; +#endif #endregion Parameters #region Parameters copied from New-WSManSessionOption +#if !UNIX /// /// By default, ProxyAccessType is None, that means Proxy information (ProxyAccessType, /// ProxyAuthenticationMechanism and ProxyCredential)is not passed to WSMan at all. @@ -447,7 +219,7 @@ public int IdleTimeout /// - Negotiate: Use the default authentication (as defined by the underlying /// protocol) for establishing a remote connection. /// - Basic: Use basic authentication for establishing a remote connection - /// - Digest: Use Digest authentication for establishing a remote connection + /// - Digest: Use Digest authentication for establishing a remote connection. /// [Parameter] public AuthenticationMechanism ProxyAuthentication { get; set; } = AuthenticationMechanism.Negotiate; @@ -459,6 +231,7 @@ public int IdleTimeout [ValidateNotNullOrEmpty] [Credential] public PSCredential ProxyCredential { get; set; } +#endif /// /// The following is the definition of the input parameter "SkipCACheck". @@ -466,77 +239,94 @@ public int IdleTimeout /// certificate is signed by a trusted certificate authority (CA). Use only when /// the remote computer is trusted by other means, for example, if the remote /// computer is part of a network that is physically secure and isolated or the - /// remote computer is listed as a trusted host in WinRM configuration + /// remote computer is listed as a trusted host in WinRM configuration. /// [Parameter] public SwitchParameter SkipCACheck { get { return _skipcacheck; } + set { _skipcacheck = value; } } + private bool _skipcacheck; /// /// The following is the definition of the input parameter "SkipCNCheck". /// Indicates that certificate common name (CN) of the server need not match the /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines + /// option should only be used for trusted machines. /// [Parameter] public SwitchParameter SkipCNCheck { get { return _skipcncheck; } + set { _skipcncheck = value; } } + private bool _skipcncheck; +#if !UNIX + /// /// The following is the definition of the input parameter "SkipRevocation". /// Indicates that certificate common name (CN) of the server need not match the /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines + /// option should only be used for trusted machines. /// [Parameter] public SwitchParameter SkipRevocationCheck { get { return _skiprevocationcheck; } + set { _skiprevocationcheck = value; } } + private bool _skiprevocationcheck; /// /// The following is the definition of the input parameter "Timeout". - /// Defines the timeout in milliseconds for the wsman operation + /// Defines the timeout in milliseconds for the wsman operation. /// [Parameter] [Alias("OperationTimeoutMSec")] [ValidateRange(0, Int32.MaxValue)] - public Int32 OperationTimeout + public int OperationTimeout { get { - return (_operationtimeout.HasValue ? _operationtimeout.Value : - BaseTransportManager.ClientDefaultOperationTimeoutMs); + return _operationtimeout ?? BaseTransportManager.ClientDefaultOperationTimeoutMs; + } + + set + { + _operationtimeout = value; } - set { _operationtimeout = value; } } - private Int32? _operationtimeout; + + private int? _operationtimeout; /// /// The following is the definition of the input parameter "UnEncrypted". /// Specifies that no encryption will be used when doing remote operations over /// http. Unencrypted traffic is not allowed by default and must be enabled in - /// the local configuration + /// the local configuration. /// [Parameter] public SwitchParameter NoEncryption { - get { return _noencryption; } + get + { + return _noencryption; + } + set { _noencryption = value; } } + private bool _noencryption; /// @@ -548,12 +338,17 @@ public SwitchParameter NoEncryption [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] public SwitchParameter UseUTF16 { - get { return _useutf16; } + get + { + return _useutf16; + } + set { _useutf16 = value; } } + private bool _useutf16; /// @@ -564,10 +359,14 @@ public SwitchParameter UseUTF16 public SwitchParameter IncludePortInSPN { get { return _includePortInSPN; } + set { _includePortInSPN = value; } } + private bool _includePortInSPN; +#endif + #endregion #region Implementation @@ -579,16 +378,20 @@ protected override void BeginProcessing() { PSSessionOption result = new PSSessionOption(); // Begin: WSMan specific options +#if !UNIX result.ProxyAccessType = this.ProxyAccessType; result.ProxyAuthentication = this.ProxyAuthentication; result.ProxyCredential = this.ProxyCredential; +#endif result.SkipCACheck = this.SkipCACheck; result.SkipCNCheck = this.SkipCNCheck; +#if !UNIX result.SkipRevocationCheck = this.SkipRevocationCheck; if (_operationtimeout.HasValue) { result.OperationTimeout = TimeSpan.FromMilliseconds(_operationtimeout.Value); } + result.NoEncryption = this.NoEncryption; result.UseUTF16 = this.UseUTF16; result.IncludePortInSPN = this.IncludePortInSPN; @@ -608,6 +411,7 @@ protected override void BeginProcessing() { result.Culture = this.Culture; } + if (this.UICulture != null) { result.UICulture = this.UICulture; @@ -617,10 +421,12 @@ protected override void BeginProcessing() { result.OpenTimeout = TimeSpan.FromMilliseconds(_openTimeout.Value); } + if (_cancelTimeout.HasValue) { result.CancelTimeout = TimeSpan.FromMilliseconds(_cancelTimeout.Value); } + if (_idleTimeout.HasValue) { result.IdleTimeout = TimeSpan.FromMilliseconds(_idleTimeout.Value); @@ -634,6 +440,7 @@ protected override void BeginProcessing() { result.ApplicationArguments = this.ApplicationArguments; } +#endif this.WriteObject(result); } diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 4672fb541c5..ee06dc22130 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -1,19 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using System.Management.Automation; +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.Client; using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; -using System.Collections; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation.Language; namespace Microsoft.PowerShell.Commands { @@ -22,14 +26,14 @@ namespace Microsoft.PowerShell.Commands /// across remoting cmdlets. /// /// It contains tons of utility functions which are used all - /// across the remoting cmdlets + /// across the remoting cmdlets. /// - public abstract partial class PSRemotingCmdlet : PSCmdlet + public abstract class PSRemotingCmdlet : PSCmdlet { #region Overrides /// - /// Verifies if remoting cmdlets can be used + /// Verifies if remoting cmdlets can be used. /// protected override void BeginProcessing() { @@ -45,19 +49,19 @@ protected override void BeginProcessing() /// /// Handle the object obtained from an ObjectStream's reader - /// based on its type + /// based on its type. /// internal void WriteStreamObject(Action action) { action(this); - }// WriteStreamObject + } /// /// Resolve all the machine names provided. Basically, if a machine - /// name is '.' assume localhost + /// name is '.' assume localhost. /// - /// array of computer names to resolve - /// resolved array of machine names + /// Array of computer names to resolve. + /// Resolved array of machine names. protected void ResolveComputerNames(string[] computerNames, out string[] resolvedComputerNames) { if (computerNames == null) @@ -68,7 +72,7 @@ protected void ResolveComputerNames(string[] computerNames, out string[] resolve } else if (computerNames.Length == 0) { - resolvedComputerNames = Utils.EmptyArray(); + resolvedComputerNames = Array.Empty(); } else { @@ -79,23 +83,23 @@ protected void ResolveComputerNames(string[] computerNames, out string[] resolve resolvedComputerNames[i] = ResolveComputerName(computerNames[i]); } } - }// ResolveComputerNames + } /// /// Resolves a computer name. If its null or empty - /// its assumed to be localhost + /// its assumed to be localhost. /// - /// computer name to resolve - /// resolved computer name + /// Computer name to resolve. + /// Resolved computer name. protected string ResolveComputerName(string computerName) { Diagnostics.Assert(computerName != null, "Null ComputerName"); - if (String.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) { - //tracer.WriteEvent(ref PSEventDescriptors.PS_EVENT_HOSTNAMERESOLVE); - //tracer.Dispose(); - //tracer.OperationalChannel.WriteVerbose(PSEventId.HostNameResolve, PSOpcode.Method, PSTask.CreateRunspace); + // tracer.WriteEvent(ref PSEventDescriptors.PS_EVENT_HOSTNAMERESOLVE); + // tracer.Dispose(); + // tracer.OperationalChannel.WriteVerbose(PSEventId.HostNameResolve, PSOpcode.Method, PSTask.CreateRunspace); return s_LOCALHOST; } else @@ -106,27 +110,26 @@ protected string ResolveComputerName(string computerName) /// /// Load the resource corresponding to the specified errorId and - /// return the message as a string + /// return the message as a string. /// /// resource String which holds the message /// - /// Error message loaded from appropriate resource cache - internal String GetMessage(string resourceString) + /// Error message loaded from appropriate resource cache. + internal string GetMessage(string resourceString) { - String message = GetMessage(resourceString, null); + string message = GetMessage(resourceString, null); return message; - }// GetMessage + } /// - /// /// /// /// /// - internal String GetMessage(string resourceString, params object[] args) + internal string GetMessage(string resourceString, params object[] args) { - String message; + string message; if (args != null) { @@ -136,6 +139,7 @@ internal String GetMessage(string resourceString, params object[] args) { message = resourceString; } + return message; } @@ -143,61 +147,66 @@ 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); + // private PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Cmdlets); #endregion Private Members #region Protected Members /// - /// Computername parameter set + /// Computername parameter set. /// protected const string ComputerNameParameterSet = "ComputerName"; /// - /// Computername with session instance ID parameter set + /// Computername with session instance ID parameter set. /// protected const string ComputerInstanceIdParameterSet = "ComputerInstanceId"; /// - /// Container ID parameter set + /// Container ID parameter set. /// protected const string ContainerIdParameterSet = "ContainerId"; /// - /// VM guid parameter set + /// VM guid parameter set. /// protected const string VMIdParameterSet = "VMId"; /// - /// VM name parameter set + /// VM name parameter set. /// protected const string VMNameParameterSet = "VMName"; /// - /// SSH host parameter set + /// SSH host parameter set. /// protected const string SSHHostParameterSet = "SSHHost"; /// - /// SSH host parmeter set supporting hash connection parameters + /// SSH host parmeter set supporting hash connection parameters. /// protected const string SSHHostHashParameterSet = "SSHHostHashParam"; /// - /// runspace parameter set + /// Runspace parameter set. /// protected const string SessionParameterSet = "Session"; /// - /// Default shellname + /// Parameter set to use Windows PowerShell. + /// + protected const string UseWindowsPowerShellParameterSet = "UseWindowsPowerShellParameterSet"; + + /// + /// 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 + /// Default application name for the connection uri. /// protected const string DefaultPowerShellRemoteShellAppName = "WSMan"; @@ -206,7 +215,7 @@ internal String GetMessage(string resourceString, params object[] args) #region Internal Members /// - /// Skip checking for WinRM + /// Skip checking for WinRM. /// internal bool SkipWinRMCheck { get; set; } = false; @@ -218,14 +227,14 @@ internal String GetMessage(string resourceString, params object[] args) /// Determines the shellname to use based on the following order: /// 1. ShellName parameter specified /// 2. DEFAULTREMOTESHELLNAME variable set - /// 3. PowerShell + /// 3. PowerShell. /// - /// The shell to launch in the remote machine - protected String ResolveShell(String shell) + /// The shell to launch in the remote machine. + protected string ResolveShell(string shell) { - String resolvedShell; + string resolvedShell; - if (!String.IsNullOrEmpty(shell)) + if (!string.IsNullOrEmpty(shell)) { resolvedShell = shell; } @@ -242,15 +251,15 @@ protected String ResolveShell(String shell) /// Determines the appname to be used based on the following order: /// 1. AppName parameter specified /// 2. DEFAULTREMOTEAPPNAME variable set - /// 3. WSMan + /// 3. WSMan. /// - /// application name to resolve - /// resolved appname - protected String ResolveAppName(String appName) + /// Application name to resolve. + /// Resolved appname. + protected string ResolveAppName(string appName) { - String resolvedAppName; + string resolvedAppName; - if (!String.IsNullOrEmpty(appName)) + if (!string.IsNullOrEmpty(appName)) { resolvedAppName = appName; } @@ -268,7 +277,7 @@ protected String ResolveAppName(String appName) } /// - /// Contains SSH connection information + /// Contains SSH connection information. /// internal struct SSHConnection { @@ -276,6 +285,9 @@ internal struct SSHConnection public string UserName; public string KeyFilePath; public int Port; + public string Subsystem; + public int ConnectingTimeout; + public Hashtable Options; } /// @@ -285,15 +297,15 @@ internal struct SSHConnection /// category: /// 1. New-PSSession /// 2. Invoke-Expression - /// 3. Start-PSJob + /// 3. Start-PSJob. /// - public abstract partial class PSRemotingBaseCmdlet : PSRemotingCmdlet + public abstract class PSRemotingBaseCmdlet : PSRemotingCmdlet { #region Enums /// /// State of virtual machine. This is the same as VMState in - /// \vm\ux\powershell\objects\common\Types.cs + /// \vm\ux\powershell\objects\common\Types.cs. /// internal enum VMState { @@ -433,11 +445,42 @@ 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 - //PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace); + // PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Runspace); #endregion Tracer @@ -445,7 +488,7 @@ internal enum VMState /// /// The PSSession object describing the remote runspace - /// using which the specified cmdlet operation will be performed + /// using which the specified cmdlet operation will be performed. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, @@ -459,14 +502,13 @@ internal enum VMState /// computer(s). The following formats are supported: /// (a) Computer name /// (b) IPv4 address : 132.3.4.5 - /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18 - /// + /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Alias("Cn")] - public virtual String[] ComputerName { get; set; } + public virtual string[] ComputerName { get; set; } /// /// Computer names after they have been resolved @@ -475,7 +517,7 @@ internal enum VMState /// If Null or empty string is specified, then localhost is assumed. /// The ResolveComputerNames will include this. /// - protected String[] ResolvedComputerNames { get; set; } + protected string[] ResolvedComputerNames { get; set; } /// /// Guid of target virtual machine. @@ -521,6 +563,7 @@ public virtual PSCredential Credential { return _pscredential; } + set { _pscredential = value; @@ -563,8 +606,8 @@ public virtual PSCredential Credential /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] - [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] - public virtual Int32 Port { get; set; } + [ValidateRange((int)1, (int)ushort.MaxValue)] + public virtual int Port { get; set; } /// /// This parameter suggests that the transport scheme to be used for @@ -580,29 +623,30 @@ 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 - /// not specified as well, then "WSMAN" will be used + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's + /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] - public virtual String ApplicationName + public virtual string ApplicationName { get { return _appName; } + set { _appName = ResolveAppName(value); } } - private String _appName; + private string _appName; /// /// Allows the user of the cmdlet to specify a throttling value /// for throttling the number of remote operations that can - /// be executed simultaneously + /// be executed simultaneously. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SessionParameterSet)] @@ -610,11 +654,11 @@ public virtual String ApplicationName [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] - public virtual Int32 ThrottleLimit { set; get; } = 0; + public virtual int ThrottleLimit { get; set; } = 0; /// /// A complete URI(s) specified for the remote computer and shell to - /// connect to and create runspace for + /// connect to and create runspace for. /// [Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, @@ -624,14 +668,16 @@ public virtual String ApplicationName public virtual Uri[] ConnectionUri { get; set; } /// - /// The AllowRedirection parameter enables the implicit redirection functionality + /// The AllowRedirection parameter enables the implicit redirection functionality. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.UriParameterSet)] public virtual SwitchParameter AllowRedirection { get { return _allowRedirection; } + set { _allowRedirection = value; } } + private bool _allowRedirection = false; /// @@ -653,12 +699,18 @@ public virtual PSSessionOption SessionOption _sessionOption = new PSSessionOption(); } } + return _sessionOption; } - set { _sessionOption = value; } + set + { + _sessionOption = value; + } } + private PSSessionOption _sessionOption; + internal const string DEFAULT_SESSION_OPTION = "PSSessionOption"; // Quota related variables. @@ -673,6 +725,7 @@ public virtual AuthenticationMechanism Authentication { return _authMechanism; } + set { _authMechanism = value; @@ -680,6 +733,7 @@ public virtual AuthenticationMechanism Authentication ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } + private AuthenticationMechanism _authMechanism = AuthenticationMechanism.Default; /// @@ -690,13 +744,18 @@ public virtual AuthenticationMechanism Authentication [Parameter(ParameterSetName = NewPSSessionCommand.UriParameterSet)] public virtual string CertificateThumbprint { - get { return _thumbPrint; } + get + { + return _thumbPrint; + } + set { _thumbPrint = value; ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } + private string _thumbPrint = null; #region SSHHostParameters @@ -714,7 +773,7 @@ public virtual string[] HostName } /// - /// SSH User Name + /// SSH User Name. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] [ValidateNotNullOrEmpty()] @@ -725,7 +784,7 @@ public virtual string UserName } /// - /// SSH Key File Path + /// SSH Key File Path. /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] [ValidateNotNullOrEmpty()] @@ -736,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 @@ -764,6 +837,13 @@ public virtual Hashtable[] SSHConnection set; } + /// + /// Gets or sets the Hashtable containing options to be passed to OpenSSH. + /// + [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] + [ValidateNotNullOrEmpty] + public virtual Hashtable Options { get; set; } + #endregion #endregion Properties @@ -785,7 +865,7 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st { if ((credential != null) && (thumbprint != null)) { - String message = PSRemotingErrorInvariants.FormatResourceString( + string message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication, "CertificateThumbPrint", "Credential"); @@ -794,7 +874,7 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st if ((authentication != AuthenticationMechanism.Default) && (thumbprint != null)) { - String message = PSRemotingErrorInvariants.FormatResourceString( + string message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.NewRunspaceAmbiguousAuthentication, "CertificateThumbPrint", authentication.ToString()); @@ -823,16 +903,56 @@ internal static void ValidateSpecifiedAuthentication(PSCredential credential, st private const string KeyFilePathParameter = "KeyFilePath"; 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 + /// + /// Parse a hostname used with SSH Transport to get embedded + /// username and/or port. + /// + /// Host name to parse. + /// Resolved target host. + /// Resolved target user name. + /// Resolved target port. + protected void ParseSshHostName(string hostname, out string host, out string userName, out int port) + { + host = hostname; + userName = this.UserName; + port = this.Port; + try + { + Uri uri = new System.Uri("ssh://" + hostname); + host = ResolveComputerName(uri.Host); + ValidateComputerName(new string[] { host }); + if (uri.UserInfo != string.Empty) + { + userName = uri.UserInfo; + } + + if (uri.Port != -1) + { + port = uri.Port; + } + } + catch (UriFormatException) + { + ThrowTerminatingError(new ErrorRecord( + new ArgumentException(PSRemotingErrorInvariants.FormatResourceString( + RemotingErrorIdStrings.InvalidComputerName)), "PSSessionInvalidComputerName", + ErrorCategory.InvalidArgument, hostname)); + } + } + /// /// Parse the Connection parameter HashTable array. /// - /// Array of SSHConnection objects + /// 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)) @@ -845,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; @@ -857,8 +977,17 @@ internal SSHConnection[] ParseSSHConnectionHashTable() if (paramName.Equals(ComputerNameParameter, StringComparison.OrdinalIgnoreCase) || paramName.Equals(HostNameAlias, StringComparison.OrdinalIgnoreCase)) { var resolvedComputerName = ResolveComputerName(GetSSHConnectionStringParameter(item[paramName])); - ValidateComputerName(new string[] { resolvedComputerName }); - connectionInfo.ComputerName = resolvedComputerName; + ParseSshHostName(resolvedComputerName, out string host, out string userName, out int port); + connectionInfo.ComputerName = host; + if (userName != string.Empty) + { + connectionInfo.UserName = userName; + } + + if (port != -1) + { + connectionInfo.Port = port; + } } else if (paramName.Equals(UserNameParameter, StringComparison.OrdinalIgnoreCase)) { @@ -872,6 +1001,18 @@ internal SSHConnection[] ParseSSHConnectionHashTable() { connectionInfo.Port = GetSSHConnectionIntParameter(item[paramName]); } + else if (paramName.Equals(SubsystemParameter, StringComparison.OrdinalIgnoreCase)) + { + 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( @@ -910,21 +1051,21 @@ protected void ValidateRemoteRunspacesSpecified() { ThrowTerminatingError(new ErrorRecord(new ArgumentException( GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoHasDuplicates)), - PSRemotingErrorId.RemoteRunspaceInfoHasDuplicates.ToString(), + nameof(PSRemotingErrorId.RemoteRunspaceInfoHasDuplicates), ErrorCategory.InvalidArgument, Session)); } - //BUGBUG: The following is a bogus check + // BUGBUG: The following is a bogus check // Check if the number of PSSession objects specified is greater // than the maximum allowable range if (RemotingCommandUtil.ExceedMaximumAllowableRunspaces(Session)) { ThrowTerminatingError(new ErrorRecord(new ArgumentException( GetMessage(RemotingErrorIdStrings.RemoteRunspaceInfoLimitExceeded)), - PSRemotingErrorId.RemoteRunspaceInfoLimitExceeded.ToString(), + nameof(PSRemotingErrorId.RemoteRunspaceInfoLimitExceeded), ErrorCategory.InvalidArgument, Session)); } - } // ValidateRemoteRunspacesSpecified + } /// /// Updates connection info with the data read from cmdlet's parameters and @@ -933,7 +1074,7 @@ protected void ValidateRemoteRunspacesSpecified() /// 1. MaxURIRedirectionCount /// 2. MaxRecvdDataSizePerSession /// 3. MaxRecvdDataSizePerCommand - /// 4. MaxRecvdObjectSize + /// 4. MaxRecvdObjectSize. /// /// internal void UpdateConnectionInfo(WSManConnectionInfo connectionInfo) @@ -956,19 +1097,19 @@ internal void UpdateConnectionInfo(WSManConnectionInfo connectionInfo) } /// - /// Uri parameter set + /// Uri parameter set. /// protected const string UriParameterSet = "Uri"; /// /// Validates computer names to check if none of them - /// happen to be a Uri. If so this throws an error + /// happen to be a Uri. If so this throws an error. /// /// collection of computer /// names to validate - protected void ValidateComputerName(String[] computerNames) + protected void ValidateComputerName(string[] computerNames) { - foreach (String computerName in computerNames) + foreach (string computerName in computerNames) { UriHostNameType nametype = Uri.CheckHostName(computerName); if (!(nametype == UriHostNameType.Dns || nametype == UriHostNameType.IPv4 || @@ -983,13 +1124,23 @@ protected void ValidateComputerName(String[] computerNames) } /// - /// Validates parameter value and returns as string + /// Validates parameter value and returns as string. /// - /// Parameter value to be validated - /// Parameter value as string + /// Parameter value to be validated. + /// Parameter value as string. private static string GetSSHConnectionStringParameter(object param) { - if (param is string paramValue && !string.IsNullOrEmpty(paramValue)) + string paramValue; + try + { + paramValue = LanguagePrimitives.ConvertTo(param); + } + catch (PSInvalidCastException e) + { + throw new PSArgumentException(e.Message, e); + } + + if (!string.IsNullOrEmpty(paramValue)) { return paramValue; } @@ -997,20 +1148,26 @@ private static string GetSSHConnectionStringParameter(object param) throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter); } - /// - /// Validates parameter value and returns as integer + /// Validates parameter value and returns as integer. /// - /// Parameter value to be validated - /// Parameter value as integer + /// Parameter value to be validated. + /// Parameter value as integer. private static int GetSSHConnectionIntParameter(object param) { - if (param is int paramValue) + if (param == null) { - return paramValue; + throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter); } - throw new PSArgumentException(RemotingErrorIdStrings.InvalidSSHConnectionParameter); + try + { + return LanguagePrimitives.ConvertTo(param); + } + catch (PSInvalidCastException e) + { + throw new PSArgumentException(e.Message, e); + } } #endregion Private Methods @@ -1018,7 +1175,7 @@ private static int GetSSHConnectionIntParameter(object param) #region Overrides /// - /// Resolves shellname and appname + /// Resolves shellname and appname. /// protected override void BeginProcessing() { @@ -1042,7 +1199,7 @@ protected override void BeginProcessing() idleTimeout / 1000, BaseTransportManager.MinimumIdleTimeout / 1000)); } - if (String.IsNullOrEmpty(_appName)) + if (string.IsNullOrEmpty(_appName)) { _appName = ResolveAppName(null); } @@ -1055,9 +1212,9 @@ protected override void BeginProcessing() /// Base class for any cmdlet which has to execute a pipeline. The /// following cmdlets currently fall under this category: /// 1. Invoke-Expression - /// 2. Start-PSJob + /// 2. Start-PSJob. /// - public abstract partial class PSExecutionCmdlet : PSRemotingBaseCmdlet + public abstract class PSExecutionCmdlet : PSRemotingBaseCmdlet { #region Strings @@ -1082,7 +1239,7 @@ public abstract partial class PSExecutionCmdlet : PSRemotingBaseCmdlet protected const string FilePathSSHHostParameterSet = "FilePathSSHHost"; /// - /// SSH Host file path parameter set with HashTable connection parameter + /// SSH Host file path parameter set with HashTable connection parameter. /// protected const string FilePathSSHHostHashParameterSet = "FilePathSSHHostHash"; @@ -1093,7 +1250,7 @@ public abstract partial class PSExecutionCmdlet : PSRemotingBaseCmdlet /// /// Input object which gets assigned to $input when executed /// on the remote machine. This is the only parameter in - /// this cmdlet which will bind with a ValueFromPipeline=true + /// this cmdlet which will bind with a ValueFromPipeline=true. /// [Parameter(ValueFromPipeline = true)] public virtual PSObject InputObject { get; set; } = AutomationNull.Value; @@ -1101,7 +1258,7 @@ public abstract partial class PSExecutionCmdlet : PSRemotingBaseCmdlet /// /// Command to execute specified as a string. This can be a single /// cmdlet, an expression or anything that can be internally - /// converted into a ScriptBlock + /// converted into a ScriptBlock. /// public virtual ScriptBlock ScriptBlock { @@ -1109,17 +1266,19 @@ public virtual ScriptBlock ScriptBlock { return _scriptBlock; } + set { _scriptBlock = value; } } + private ScriptBlock _scriptBlock; /// /// The file containing the script that the user has specified in the /// cmdlet. This will be converted to a powershell before - /// its actually sent to the remote end + /// its actually sent to the remote end. /// [Parameter(Position = 1, Mandatory = true, @@ -1137,43 +1296,45 @@ public virtual string FilePath { return _filePath; } + set { _filePath = value; } } + private string _filePath; /// - /// True if FilePath should be processed as a literal path + /// True if FilePath should be processed as a literal path. /// protected bool IsLiteralPath { get; set; } /// - /// Arguments that are passed to this scriptblock + /// Arguments that are passed to this scriptblock. /// [Parameter()] [Alias("Args")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public virtual Object[] ArgumentList + public virtual object[] ArgumentList { get { return _args; } + set { _args = value; } } - private Object[] _args; + private object[] _args; /// /// Indicates that if a job/command is invoked remotely the connection should be severed /// right have invocation of job/command. /// - /// protected bool InvokeAndDisconnect { get; set; } = false; /// @@ -1262,7 +1423,7 @@ public virtual Object[] ArgumentList ParameterSetName = InvokeCommandCommand.FilePathVMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = InvokeCommandCommand.FilePathVMNameParameterSet)] - public virtual String ConfigurationName { get; set; } + public virtual string ConfigurationName { get; set; } #endregion Parameters @@ -1270,7 +1431,7 @@ public virtual Object[] ArgumentList /// /// Creates helper objects with the command for the specified - /// remote computer names + /// remote computer names. /// protected virtual void CreateHelpersForSpecifiedComputerNames() { @@ -1298,6 +1459,7 @@ protected virtual void CreateHelpersForSpecifiedComputerNames() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -1332,7 +1494,7 @@ protected virtual void CreateHelpersForSpecifiedComputerNames() Operations.Add(operation); } - }// CreateHelpersForSpecifiedComputerNames + } /// /// Creates helper objects for SSH remoting computer names @@ -1340,13 +1502,13 @@ protected virtual void CreateHelpersForSpecifiedComputerNames() /// protected void CreateHelpersForSpecifiedSSHComputerNames() { - ValidateComputerName(ResolvedComputerNames); - foreach (string computerName in ResolvedComputerNames) { - var sshConnectionInfo = new SSHConnectionInfo(this.UserName, computerName, this.KeyFilePath, this.Port); + ParseSshHostName(computerName, out string host, out string userName, out int port); + + 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); @@ -1366,7 +1528,9 @@ protected void CreateHelpersForSpecifiedSSHHashComputerNames() sshConnection.UserName, sshConnection.ComputerName, sshConnection.KeyFilePath, - sshConnection.Port); + sshConnection.Port, + sshConnection.Subsystem, + sshConnection.ConnectingTimeout); var typeTable = TypeTable.LoadDefaultTypeFiles(); var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace; var pipeline = CreatePipeline(remoteRunspace); @@ -1378,7 +1542,7 @@ protected void CreateHelpersForSpecifiedSSHHashComputerNames() /// /// Creates helper objects with the specified command for - /// the specified remote runspaceinfo objects + /// the specified remote runspaceinfo objects. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected void CreateHelpersForSpecifiedRunspaces() @@ -1407,11 +1571,11 @@ protected void CreateHelpersForSpecifiedRunspaces() IThrottleOperation operation = new ExecutionCmdletHelperRunspace(pipelines[i]); Operations.Add(operation); } - } // CreateHelpersForSpecifiedRunspaces + } /// /// Creates helper objects with the command for the specified - /// remote connection uris + /// remote connection uris. /// protected void CreateHelpersForSpecifiedUris() { @@ -1473,7 +1637,7 @@ protected void CreateHelpersForSpecifiedUris() Operations.Add(operation); } - } // CreateHelpersForSpecifiedUris + } /// /// Creates helper objects with the command for the specified @@ -1510,7 +1674,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() ThrowTerminatingError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), - PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), + nameof(PSRemotingErrorId.HyperVModuleNotAvailable), ErrorCategory.NotInstalled, null)); @@ -1525,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; } @@ -1558,7 +1722,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() ThrowTerminatingError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), - PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), + nameof(PSRemotingErrorId.HyperVModuleNotAvailable), ErrorCategory.NotInstalled, null)); @@ -1574,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; } @@ -1594,7 +1758,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMNameNotSingle, this.VMName[index])), - PSRemotingErrorId.InvalidVMNameNotSingle.ToString(), + nameof(PSRemotingErrorId.InvalidVMNameNotSingle), ErrorCategory.InvalidArgument, null)); @@ -1608,7 +1772,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMIdNotSingle, this.VMId[index].ToString(null))), - PSRemotingErrorId.InvalidVMIdNotSingle.ToString(), + nameof(PSRemotingErrorId.InvalidVMIdNotSingle), ErrorCategory.InvalidArgument, null)); @@ -1620,7 +1784,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMState, this.VMName[index])), - PSRemotingErrorId.InvalidVMState.ToString(), + nameof(PSRemotingErrorId.InvalidVMState), ErrorCategory.InvalidArgument, null)); @@ -1666,7 +1830,7 @@ protected virtual void CreateHelpersForSpecifiedVMSession() Operations.Add(operation); } - }// CreateHelpersForSpecifiedVMSession + } /// /// Creates helper objects with the command for the specified @@ -1745,13 +1909,13 @@ protected virtual void CreateHelpersForSpecifiedContainerSession() } ResolvedComputerNames = resolvedNameList.ToArray(); - }// CreateHelpersForSpecifiedContainerSession + } /// - /// Creates a pipeline from the powershell + /// Creates a pipeline from the powershell. /// - /// runspace on which to create the pipeline - /// a pipeline + /// Runspace on which to create the pipeline. + /// A pipeline. internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) { // The fix to WinBlue#475223 changed how UsingExpression is handled on the client/server sides, if the remote end is PSv5 @@ -1759,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); @@ -1778,13 +1940,13 @@ internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) } /// - /// Check the powershell version of the remote server + /// 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; } @@ -1793,58 +1955,37 @@ 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); + PSPrimitiveDictionary.TryPathGet( + psApplicationPrivateData, + out Version 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."); - } - - 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 + /// 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 #region Protected Members / Methods /// - /// List of operations + /// List of operations. /// internal List Operations { get; } = new List(); - /// - /// Closes the input streams on all the pipelines + /// Closes the input streams on all the pipelines. /// protected void CloseAllInputStreams() { @@ -1853,15 +1994,15 @@ protected void CloseAllInputStreams() ExecutionCmdletHelper helper = (ExecutionCmdletHelper)operation; helper.Pipeline.Input.Close(); } - } // CloseAllInputStreams + } /// /// Writes an error record specifying that creation of remote runspace - /// failed + /// failed. /// /// exception which is causing this error record /// to be written - /// Uri which caused this exception + /// Uri which caused this exception. private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) { Dbg.Assert(e is UriFormatException || e is InvalidOperationException || @@ -1872,62 +2013,61 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) ErrorCategory.InvalidArgument, uri); WriteError(errorRecord); - } // WriteErrorCreateRemoteRunspaceFailed + } /// - /// FilePathComputername parameter set + /// FilePathComputername parameter set. /// protected const string FilePathComputerNameParameterSet = "FilePathComputerName"; /// - /// LiteralFilePathComputername parameter set + /// LiteralFilePathComputername parameter set. /// protected const string LiteralFilePathComputerNameParameterSet = "LiteralFilePathComputerName"; /// - /// FilePathRunspace parameter set + /// FilePathRunspace parameter set. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] protected const string FilePathSessionParameterSet = "FilePathRunspace"; /// - /// FilePathUri parameter set + /// FilePathUri parameter set. /// protected const string FilePathUriParameterSet = "FilePathUri"; /// - /// PS version of the remote server + /// PS version of the remote server. /// 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; /// - /// Reads content of file and converts it to a scriptblock + /// Reads content of file and converts it to a scriptblock. /// /// /// /// protected ScriptBlock GetScriptBlockFromFile(string filePath, bool isLiteralPath) { - //Make sure filepath doesn't contain wildcards + // Make sure filepath doesn't contain wildcards if ((!isLiteralPath) && WildcardPattern.ContainsWildcardCharacters(filePath)) { - throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WildCardErrorFilePathParameter), "filePath"); + throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WildCardErrorFilePathParameter), nameof(filePath)); } if (!filePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.FilePathShouldPS1Extension), "filePath"); + throw new ArgumentException(PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.FilePathShouldPS1Extension), nameof(filePath)); } - //Resolve file path + // Resolve file path string resolvedPath = PathResolver.ResolveProviderAndPath(filePath, isLiteralPath, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider); - //read content of file + // read content of file ExternalScriptInfo scriptInfo = new ExternalScriptInfo(filePath, resolvedPath, this.Context); // Skip ShouldRun check for .psd1 files. @@ -1947,7 +2087,7 @@ protected ScriptBlock GetScriptBlockFromFile(string filePath, bool isLiteralPath /// /// Creates the helper classes for the specified - /// parameter set + /// parameter set. /// protected override void BeginProcessing() { @@ -1974,23 +2114,25 @@ protected override void BeginProcessing() case PSExecutionCmdlet.LiteralFilePathComputerNameParameterSet: case PSExecutionCmdlet.ComputerNameParameterSet: { - String[] resolvedComputerNames = null; + string[] resolvedComputerNames = null; ResolveComputerNames(ComputerName, out resolvedComputerNames); ResolvedComputerNames = resolvedComputerNames; CreateHelpersForSpecifiedComputerNames(); } + break; case PSExecutionCmdlet.SSHHostParameterSet: case PSExecutionCmdlet.FilePathSSHHostParameterSet: { - String[] resolvedComputerNames = null; + string[] resolvedComputerNames = null; ResolveComputerNames(HostName, out resolvedComputerNames); ResolvedComputerNames = resolvedComputerNames; CreateHelpersForSpecifiedSSHComputerNames(); } + break; case PSExecutionCmdlet.SSHHostHashParameterSet: @@ -1998,6 +2140,7 @@ protected override void BeginProcessing() { CreateHelpersForSpecifiedSSHHashComputerNames(); } + break; case PSExecutionCmdlet.FilePathSessionParameterSet: @@ -2007,6 +2150,7 @@ protected override void BeginProcessing() CreateHelpersForSpecifiedRunspaces(); } + break; case PSExecutionCmdlet.FilePathUriParameterSet: @@ -2014,6 +2158,7 @@ protected override void BeginProcessing() { CreateHelpersForSpecifiedUris(); } + break; case PSExecutionCmdlet.VMIdParameterSet: @@ -2023,6 +2168,7 @@ protected override void BeginProcessing() { CreateHelpersForSpecifiedVMSession(); } + break; case PSExecutionCmdlet.ContainerIdParameterSet: @@ -2030,18 +2176,18 @@ protected override void BeginProcessing() { CreateHelpersForSpecifiedContainerSession(); } + break; } } - #endregion Overrides #region "Get PowerShell instance" /// /// Get the PowerShell instance for the PSv2 remote end - /// Generate the PowerShell instance by using the text of the scriptblock + /// Generate the PowerShell instance by using the text of the scriptblock. /// /// /// PSv2 doesn't understand the '$using' prefix. To make UsingExpression work on PSv2 remote end, we will have to @@ -2073,6 +2219,7 @@ private System.Management.Automation.PowerShell GetPowerShellForPSv2() break; } } + if (_powershellV2 != null) { return _powershellV2; } } @@ -2104,7 +2251,7 @@ private System.Management.Automation.PowerShell GetPowerShellForPSv2() /// /// Get the PowerShell instance for the PSv3 (or later) remote end - /// Generate the PowerShell instance by using the text of the scriptblock + /// Generate the PowerShell instance by using the text of the scriptblock. /// /// /// In PSv3 and PSv4, if the remote server is PSv3 or later, we generate an object array that contains the value of each using expression in @@ -2143,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; @@ -2197,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) @@ -2214,9 +2361,8 @@ private System.Management.Automation.PowerShell ConvertToPowerShell() #region "UsingExpression Utilities" - /// - /// Get the converted script for a remote PSv2 end + /// Get the converted script for a remote PSv2 end. /// /// /// The new parameter names that we added to the param block @@ -2249,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) { @@ -2319,18 +2466,16 @@ private List GetUsingVariableValues(List paramUsi } /// - /// Get all Using expressions that we care about + /// Get all Using expressions that we care about. /// /// - /// a list of UsingExpressionAsts ordered by the StartOffset - private List GetUsingVariables(ScriptBlock localScriptBlock) + /// A list of UsingExpressionAsts ordered by the StartOffset. + private static List GetUsingVariables(ScriptBlock localScriptBlock) { - if (localScriptBlock == null) - { - throw new ArgumentNullException("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(); + ArgumentNullException.ThrowIfNull(localScriptBlock, "Caller needs to make sure the parameter value is not null"); + + var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressions(localScriptBlock.Ast); + return allUsingExprs.Select(static usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList(); } #endregion "UsingExpression Utilities" @@ -2342,30 +2487,30 @@ private List GetUsingVariables(ScriptBlock localScriptBlo /// 1. Get-PSSession /// 2. Remove-PSSession /// 3. Disconnect-PSSession - /// 4. Connect-PSSession + /// 4. Connect-PSSession. /// - public abstract partial class PSRunspaceCmdlet : PSRemotingCmdlet + public abstract class PSRunspaceCmdlet : PSRemotingCmdlet { #region Parameters /// - /// ContainerIdInstanceId parameter set: container id + session instance id + /// ContainerIdInstanceId parameter set: container id + session instance id. /// protected const string ContainerIdInstanceIdParameterSet = "ContainerIdInstanceId"; /// - /// VMIdInstanceId parameter set: vm id + session instance id + /// VMIdInstanceId parameter set: vm id + session instance id. /// protected const string VMIdInstanceIdParameterSet = "VMIdInstanceId"; /// - /// VMNameInstanceId parameter set: vm name + session instance id + /// VMNameInstanceId parameter set: vm name + session instance id. /// protected const string VMNameInstanceIdParameterSet = "VMNameInstanceId"; /// /// RemoteRunspaceId to retrieve corresponding PSSession - /// object + /// object. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.InstanceIdParameterSet)] @@ -2377,6 +2522,7 @@ public virtual Guid[] InstanceId { return _remoteRunspaceIds; } + set { _remoteRunspaceIds = value; @@ -2386,7 +2532,7 @@ public virtual Guid[] InstanceId private Guid[] _remoteRunspaceIds; /// - /// Session Id of the remoterunspace info object + /// Session Id of the remoterunspace info object. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, @@ -2397,28 +2543,29 @@ public virtual Guid[] InstanceId public int[] Id { get; set; } /// - /// Name of the remote runspaceinfo object + /// Name of the remote runspaceinfo object. /// [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRunspaceCmdlet.NameParameterSet)] [ValidateNotNullOrEmpty()] - public virtual String[] Name + public virtual string[] Name { get { return _names; } + set { _names = value; } } - private String[] _names; + private string[] _names; /// /// Name of the computer for which the runspace needs to be - /// returned + /// returned. /// [Parameter(Position = 0, Mandatory = true, @@ -2426,19 +2573,20 @@ public virtual String[] Name ParameterSetName = PSRunspaceCmdlet.ComputerNameParameterSet)] [ValidateNotNullOrEmpty] [Alias("Cn")] - public virtual String[] ComputerName + public virtual string[] ComputerName { get { return _computerNames; } + set { _computerNames = value; } } - private String[] _computerNames; + private string[] _computerNames; /// /// ID of target container. @@ -2488,13 +2636,13 @@ public virtual String[] ComputerName #region Private / Protected Methods /// - /// Gets the matching runspaces based on the parameterset + /// Gets the matching runspaces based on the parameterset. /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline - /// list of matching runspaces + /// List of matching runspaces. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected Dictionary GetMatchingRunspaces(bool writeobject, bool writeErrorOnNoMatch) @@ -2503,7 +2651,7 @@ protected Dictionary GetMatchingRunspaces(bool writeobject, } /// - /// Gets the matching runspaces based on the parameterset + /// Gets the matching runspaces based on the parameterset. /// /// write an error record when /// no matches are found @@ -2511,12 +2659,12 @@ protected Dictionary GetMatchingRunspaces(bool writeobject, /// the pipeline /// Runspace state filter value. /// Runspace configuration name filter value. - /// list of matching runspaces + /// List of matching runspaces. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] protected Dictionary GetMatchingRunspaces(bool writeobject, bool writeErrorOnNoMatch, SessionFilterState filterState, - String configurationName) + string configurationName) { switch (ParameterSetName) { @@ -2607,13 +2755,13 @@ internal Dictionary GetAllRunspaces(bool writeobject, } /// - /// Gets the matching runspaces by computernames + /// Gets the matching runspaces by computernames. /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline - /// list of matching runspaces + /// List of matching runspaces. private Dictionary GetMatchingRunspacesByComputerName(bool writeobject, bool writeErrorOnNoMatch) { @@ -2669,13 +2817,13 @@ private Dictionary GetMatchingRunspacesByComputerName(bool writ } /// - /// Gets the matching runspaces based on name + /// Gets the matching runspaces based on name. /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline - /// list of matching runspaces + /// List of matching runspaces. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")] protected Dictionary GetMatchingRunspacesByName(bool writeobject, @@ -2728,13 +2876,13 @@ protected Dictionary GetMatchingRunspacesByName(bool writeobjec } /// - /// Gets the matching runspaces based on the runspaces instance id + /// Gets the matching runspaces based on the runspaces instance id. /// /// write an error record when /// no matches are found /// if true write the object down /// the pipeline - /// list of matching runspaces + /// List of matching runspaces. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspaces")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "writeobject")] protected Dictionary GetMatchingRunspacesByRunspaceId(bool writeobject, @@ -2792,7 +2940,7 @@ protected Dictionary GetMatchingRunspacesByRunspaceId(bool writ /// no matches are found /// if true write the object down /// the pipeline - /// list of matching runspaces + /// List of matching runspaces. private Dictionary GetMatchingRunspacesBySessionId(bool writeobject, bool writeErrorOnNoMatch) { @@ -2841,24 +2989,24 @@ private Dictionary GetMatchingRunspacesBySessionId(bool writeob } /// - /// Gets the matching runspaces by vm name or container id with optional session name + /// Gets the matching runspaces by vm name or container id with optional session name. /// - /// if true write the object down the pipeline + /// If true write the object down the pipeline. /// Runspace state filter value. /// Runspace configuration name filter value. - /// if true the target is a container instead of virtual machine - /// list of matching runspaces + /// If true the target is a container instead of virtual machine. + /// List of matching runspaces. private Dictionary GetMatchingRunspacesByVMNameContainerId(bool writeobject, SessionFilterState filterState, - String configurationName, + string configurationName, bool isContainer) { - String[] inputNames; + string[] inputNames; TargetMachineType computerType; bool supportWildChar; - String[] sessionNames = { "*" }; + string[] sessionNames = { "*" }; WildcardPattern configurationNamePattern = - String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; @@ -2890,14 +3038,14 @@ private Dictionary GetMatchingRunspacesByVMNameContainerId(bool foreach (string sessionName in sessionNames) { WildcardPattern sessionNamePattern = - String.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); 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(); @@ -2909,23 +3057,23 @@ private Dictionary GetMatchingRunspacesByVMNameContainerId(bool } /// - /// Gets the matching runspaces by vm name or container id with session instanceid + /// Gets the matching runspaces by vm name or container id with session instanceid. /// - /// if true write the object down the pipeline + /// If true write the object down the pipeline. /// Runspace state filter value. /// Runspace configuration name filter value. - /// if true the target is a container instead of virtual machine - /// list of matching runspaces + /// If true the target is a container instead of virtual machine. + /// List of matching runspaces. private Dictionary GetMatchingRunspacesByVMNameContainerIdSessionInstanceId(bool writeobject, SessionFilterState filterState, - String configurationName, + string configurationName, bool isContainer) { - String[] inputNames; + string[] inputNames; TargetMachineType computerType; bool supportWildChar; WildcardPattern configurationNamePattern = - String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; @@ -2955,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(); @@ -2967,19 +3115,19 @@ private Dictionary GetMatchingRunspacesByVMNameContainerIdSessi } /// - /// Gets the matching runspaces by vm guid and optional session name + /// Gets the matching runspaces by vm guid and optional session name. /// - /// if true write the object down the pipeline + /// If true write the object down the pipeline. /// Runspace state filter value. /// Runspace configuration name filter value. - /// list of matching runspaces + /// List of matching runspaces. private Dictionary GetMatchingRunspacesByVMId(bool writeobject, SessionFilterState filterState, - String configurationName) + string configurationName) { - String[] sessionNames = { "*" }; + string[] sessionNames = { "*" }; WildcardPattern configurationNamePattern = - String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; @@ -2994,13 +3142,13 @@ private Dictionary GetMatchingRunspacesByVMId(bool writeobject, foreach (string sessionName in sessionNames) { WildcardPattern sessionNamePattern = - String.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(sessionName) ? null : WildcardPattern.Get(sessionName, WildcardOptions.IgnoreCase); 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(); @@ -3012,18 +3160,18 @@ private Dictionary GetMatchingRunspacesByVMId(bool writeobject, } /// - /// Gets the matching runspaces by vm guid and session instanceid + /// Gets the matching runspaces by vm guid and session instanceid. /// - /// if true write the object down the pipeline + /// If true write the object down the pipeline. /// Runspace state filter value. /// Runspace configuration name filter value. - /// list of matching runspaces + /// List of matching runspaces. private Dictionary GetMatchingRunspacesByVMIdSessionInstanceId(bool writeobject, SessionFilterState filterState, - String configurationName) + string configurationName) { WildcardPattern configurationNamePattern = - String.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); + string.IsNullOrEmpty(configurationName) ? null : WildcardPattern.Get(configurationName, WildcardOptions.IgnoreCase); Dictionary matches = new Dictionary(); List remoteRunspaceInfos = this.RunspaceRepository.Runspaces; @@ -3035,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(); @@ -3049,9 +3197,9 @@ private Dictionary GetMatchingRunspacesByVMIdSessionInstanceId( /// /// Write the matching runspace objects down the pipeline, or add to the list. /// - /// The matching runspaces - /// if true write the object down the pipeline. Otherwise, add to the list - /// The list we add the matching runspaces to + /// The matching runspaces. + /// If true write the object down the pipeline. Otherwise, add to the list. + /// The list we add the matching runspaces to. private void WriteOrAddMatches(List matchingRunspaceInfos, bool writeobject, ref Dictionary matches) @@ -3081,7 +3229,7 @@ private void WriteOrAddMatches(List matchingRunspaceInfos, /// private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourceString, object errorArgument) { - String message = GetMessage(resourceString, errorArgument); + string message = GetMessage(resourceString, errorArgument); WriteError(new ErrorRecord(new ArgumentException(message), errorId.ToString(), ErrorCategory.InvalidArgument, errorArgument)); @@ -3092,17 +3240,17 @@ private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourc #region Protected Members /// - /// Runspace Id parameter set + /// Runspace Id parameter set. /// protected const string InstanceIdParameterSet = "InstanceId"; /// - /// session id parameter set + /// Session id parameter set. /// protected const string IdParameterSet = "Id"; /// - /// name parameter set + /// Name parameter set. /// protected const string NameParameterSet = "Name"; @@ -3113,12 +3261,12 @@ private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourc /// /// Base class for both the helpers. This is an abstract class - /// and the helpers need to derive from this + /// and the helpers need to derive from this. /// - internal abstract partial class ExecutionCmdletHelper : IThrottleOperation + internal abstract class ExecutionCmdletHelper : IThrottleOperation { /// - /// Pipeline associated with this operation + /// Pipeline associated with this operation. /// internal Pipeline Pipeline { @@ -3127,11 +3275,12 @@ internal Pipeline Pipeline return pipeline; } } + protected Pipeline pipeline; /// /// Exception raised internally when any method of this class - /// is executed + /// is executed. /// internal Exception InternalException { @@ -3140,6 +3289,7 @@ internal Exception InternalException return internalException; } } + protected Exception internalException; /// @@ -3147,8 +3297,8 @@ internal Exception InternalException /// internal Runspace PipelineRunspace { - set; get; + set; } #region Runspace Debug @@ -3192,8 +3342,7 @@ private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) } #endregion - - } // ExecutionCmdletHelper + } /// /// Contains a pipeline and calls InvokeAsync on the pipeline @@ -3202,28 +3351,28 @@ private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args) /// for both the functions. This is because, there is only a /// single state of the pipeline which raises an event on /// a method call. There are no separate events raised as - /// part of method calls + /// part of method calls. /// internal class ExecutionCmdletHelperRunspace : ExecutionCmdletHelper { /// - /// Indicates whether or not the server should be using the steppable pipeline + /// Indicates whether or not the server should be using the steppable pipeline. /// internal bool ShouldUseSteppablePipelineOnServer; /// - /// Internal constructor + /// Internal constructor. /// - /// pipeline object associated with this operation + /// Pipeline object associated with this operation. internal ExecutionCmdletHelperRunspace(Pipeline pipeline) { this.pipeline = pipeline; PipelineRunspace = pipeline.Runspace; - this.pipeline.StateChanged += new EventHandler(HandlePipelineStateChanged); + this.pipeline.StateChanged += HandlePipelineStateChanged; } /// - /// Invokes the pipeline asynchronously + /// Invokes the pipeline asynchronously. /// internal override void StartOperation() { @@ -3231,12 +3380,12 @@ internal override void StartOperation() try { - if (ShouldUseSteppablePipelineOnServer) + if (ShouldUseSteppablePipelineOnServer && pipeline is RemotePipeline rPipeline) { - RemotePipeline rPipeline = pipeline as RemotePipeline; rPipeline.SetIsNested(true); rPipeline.SetIsSteppable(true); } + pipeline.InvokeAsync(); } catch (InvalidRunspaceStateException e) @@ -3254,10 +3403,10 @@ internal override void StartOperation() internalException = e; RaiseOperationCompleteEvent(); } - } // StartOperation + } /// - /// Closes the pipeline asynchronously + /// Closes the pipeline asynchronously. /// internal override void StopOperation() { @@ -3277,16 +3426,16 @@ internal override void StopOperation() // else ThrottleManager will have RaiseOperationCompleteEvent(); } - } // StopOperation + } internal override event EventHandler OperationComplete; /// /// Handles the state changed events for the pipeline. This is registered in both /// StartOperation and StopOperation. Here nothing more is done excepting raising - /// the OperationComplete event appropriately which will be handled by the cmdlet + /// the OperationComplete event appropriately which will be handled by the cmdlet. /// - /// source of this event + /// Source of this event. /// object describing state information about the /// pipeline private void HandlePipelineStateChanged(object sender, PipelineStateEventArgs stateEventArgs) @@ -3303,16 +3452,16 @@ private void HandlePipelineStateChanged(object sender, PipelineStateEventArgs st } RaiseOperationCompleteEvent(stateEventArgs); - } // HandlePipelineStateChanged + } /// /// Raise an OperationComplete Event. The base event will be - /// null in this case + /// null in this case. /// private void RaiseOperationCompleteEvent() { RaiseOperationCompleteEvent(null); - } // RaiseOperationCompleteEvent + } /// /// Raise an operation complete event. @@ -3327,7 +3476,7 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) { // Dispose the pipeline object and release data and remoting resources. // Pipeline object remains to provide information on final state and any errors incurred. - pipeline.StateChanged -= new EventHandler(HandlePipelineStateChanged); + pipeline.StateChanged -= HandlePipelineStateChanged; pipeline.Dispose(); } @@ -3337,12 +3486,9 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; - if (OperationComplete != null) - { - OperationComplete.SafeInvoke(this, operationStateEventArgs); - } - } // RaiseOperationCompleteEvent - } // ExecutionCmdletHelperRunspace + OperationComplete?.SafeInvoke(this, operationStateEventArgs); + } + } /// /// This helper class contains a runspace and @@ -3363,20 +3509,20 @@ 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 - /// parameter set details + /// parameter set details. /// internal RemoteRunspace RemoteRunspace { get; private set; } /// - /// Constructor + /// Constructor. /// /// RemoteRunspace that is associated /// with this operation - /// pipeline created from the remote runspace + /// Pipeline created from the remote runspace. /// Indicates if pipeline should be disconnected after invoking command. internal ExecutionCmdletHelperComputerName(RemoteRunspace remoteRunspace, Pipeline pipeline, bool invokeAndDisconnect = false) { @@ -3387,19 +3533,17 @@ internal ExecutionCmdletHelperComputerName(RemoteRunspace remoteRunspace, Pipeli _invokeAndDisconnect = invokeAndDisconnect; RemoteRunspace = remoteRunspace; - remoteRunspace.StateChanged += - new EventHandler(HandleRunspaceStateChanged); + remoteRunspace.StateChanged += HandleRunspaceStateChanged; Dbg.Assert(pipeline != null, "Pipeline cannot be null or empty"); this.pipeline = pipeline; - pipeline.StateChanged += - new EventHandler(HandlePipelineStateChanged); - } // IREHelperComputerName + pipeline.StateChanged += HandlePipelineStateChanged; + } /// - /// Call OpenAsync() on the RemoteRunspace + /// Call OpenAsync() on the RemoteRunspace. /// internal override void StartOperation() { @@ -3412,10 +3556,10 @@ internal override void StartOperation() internalException = e; RaiseOperationCompleteEvent(); } - } // StartOperation + } /// - /// StopAsync on the pipeline + /// StopAsync on the pipeline. /// internal override void StopOperation() { @@ -3442,15 +3586,15 @@ internal override void StopOperation() // this StopOperation to complete RaiseOperationCompleteEvent(); } - } // StopOperation + } internal override event EventHandler OperationComplete; /// - /// Handles the state changed event for runspace operations + /// Handles the state changed event for runspace operations. /// - /// sender of this information - /// object describing this event + /// Sender of this information. + /// Object describing this event. private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs stateEventArgs) { @@ -3490,12 +3634,14 @@ private void HandleRunspaceStateChanged(object sender, RemoteRunspace.CloseAsync(); } } + break; case RunspaceState.Broken: { RaiseOperationCompleteEvent(stateEventArgs); } + break; case RunspaceState.Closed: { @@ -3510,15 +3656,16 @@ private void HandleRunspaceStateChanged(object sender, RaiseOperationCompleteEvent(); } } + break; - } // switch (state... - } // HandleRunspaceStateChanged + } + } /// /// Handles the state changed event for the pipeline. /// - /// sender of this information - /// object describing this event + /// Sender of this information. + /// Object describing this event. private void HandlePipelineStateChanged(object sender, PipelineStateEventArgs stateEventArgs) { @@ -3534,22 +3681,19 @@ private void HandlePipelineStateChanged(object sender, case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: - if (RemoteRunspace != null) - { - RemoteRunspace.CloseAsync(); - } + RemoteRunspace?.CloseAsync(); break; - } // switch(state... - } // HandlePipelineStateChanged + } + } /// /// Raise an OperationComplete Event. The base event will be - /// null in this case + /// null in this case. /// private void RaiseOperationCompleteEvent() { RaiseOperationCompleteEvent(null); - } // RaiseOperationCompleteEvent + } /// /// Raise an operation complete event. @@ -3562,7 +3706,7 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) { // Dispose the pipeline object and release data and remoting resources. // Pipeline object remains to provide information on final state and any errors incurred. - pipeline.StateChanged -= new EventHandler(HandlePipelineStateChanged); + pipeline.StateChanged -= HandlePipelineStateChanged; pipeline.Dispose(); } @@ -3579,13 +3723,13 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; OperationComplete.SafeInvoke(this, operationStateEventArgs); - } // RaiseOperationCompleteEvent - } // ExecutionCmdletHelperComputerName + } + } #region Path Resolver /// - /// A helper class to resolve the path + /// A helper class to resolve the path. /// internal static class PathResolver { @@ -3593,12 +3737,12 @@ internal static class PathResolver /// Resolves the specified path and verifies the path belongs to /// FileSystemProvider. /// - /// Path to resolve + /// Path to resolve. /// True if wildcard expansion should be suppressed for this path. /// reference to calling cmdlet. This will be used for /// for writing errors /// - /// resource string for error when path is not from filesystem provider + /// Resource string for error when path is not from filesystem provider. /// A fully qualified string representing filename. internal static string ResolveProviderAndPath(string path, bool isLiteralPath, PSCmdlet cmdlet, bool allowNonexistingPaths, string resourceString) { @@ -3614,29 +3758,23 @@ internal static string ResolveProviderAndPath(string path, bool isLiteralPath, P } /// - /// Resolves the specified path to PathInfo objects + /// Resolves the specified path to PathInfo objects. /// - /// /// /// The path to be resolved. Each path may contain glob characters. /// - /// /// /// True if wildcard expansion should be suppressed for pathToResolve. /// - /// /// /// If true, resolves the path even if it doesn't exist. /// - /// /// /// Calling cmdlet /// - /// /// /// A string representing the resolved path. /// - /// private static PathInfo ResolvePath( string pathToResolve, bool isLiteralPath, @@ -3717,7 +3855,7 @@ private static PathInfo ResolvePath( { return results[0]; } - else //if (results.Count > 1) + else // if (results.Count > 1) { Exception e = PSTraceSource.NewNotSupportedException(); cmdlet.ThrowTerminatingError( @@ -3727,10 +3865,680 @@ private static PathInfo ResolvePath( results)); return null; } - } // ResolvePath + } + } + + #endregion + + #region QueryRunspaces + + internal class QueryRunspaces + { + #region Constructor + + internal QueryRunspaces() + { + _stopProcessing = false; + } + + #endregion + + #region Internal Methods + + /// + /// Queries all remote computers specified in collection of WSManConnectionInfo objects + /// and returns disconnected PSSession objects ready for connection to server. + /// Returned sessions can be matched to Guids or Names. + /// + /// Collection of WSManConnectionInfo objects. + /// Host for PSSession objects. + /// Out stream object. + /// Runspace repository. + /// Throttle limit. + /// Runspace state filter value. + /// Array of session Guids to match to. + /// Array of session Names to match to. + /// Configuration name to match to. + /// Collection of disconnected PSSession objects. + internal Collection GetDisconnectedSessions(Collection connectionInfos, PSHost host, + ObjectStream stream, RunspaceRepository runspaceRepository, + int throttleLimit, SessionFilterState filterState, + Guid[] matchIds, string[] matchNames, string configurationName) + { + Collection filteredPSSessions = new Collection(); + + // Create a query operation for each connection information object. + foreach (WSManConnectionInfo connectionInfo in connectionInfos) + { + Runspace[] runspaces = null; + + try + { + runspaces = Runspace.GetRunspaces(connectionInfo, host, BuiltInTypesTable); + } + catch (System.Management.Automation.RuntimeException e) + { + if (e.InnerException is InvalidOperationException) + { + // The Get-WSManInstance cmdlet used to query remote computers for runspaces will throw + // an Invalid Operation (inner) exception if the connectInfo object is invalid, including + // invalid computer names. + // We don't want to propagate the exception so just write error here. + if (stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) + { + int errorCode; + string msg = StringUtil.Format(RemotingErrorIdStrings.QueryForRunspacesFailed, connectionInfo.ComputerName, ExtractMessage(e.InnerException, out errorCode)); + string FQEID = WSManTransportManagerUtils.GetFQEIDFromTransportError(errorCode, "RemotePSSessionQueryFailed"); + Exception reason = new RuntimeException(msg, e.InnerException); + ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, connectionInfo); + stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); + } + } + else + { + throw; + } + } + + if (_stopProcessing) + { + break; + } + + // Add all runspaces meeting filter criteria to collection. + if (runspaces != null) + { + // Convert configuration name into shell Uri for comparison. + string shellUri = null; + if (!string.IsNullOrEmpty(configurationName)) + { + shellUri = configurationName.Contains(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) + ? configurationName + : WSManNativeApi.ResourceURIPrefix + configurationName; + } + + foreach (Runspace runspace in runspaces) + { + // Filter returned runspaces by ConfigurationName if provided. + if (shellUri != null) + { + // Compare with returned shell Uri in connection info. + WSManConnectionInfo wsmanConnectionInfo = runspace.ConnectionInfo as WSManConnectionInfo; + if (wsmanConnectionInfo != null && + !shellUri.Equals(wsmanConnectionInfo.ShellUri, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + } + + // Check the repository for an existing viable PSSession for + // this runspace (based on instanceId). Use the existing + // local runspace instead of the one returned from the server + // query. + PSSession existingPSSession = null; + if (runspaceRepository != null) + { + existingPSSession = runspaceRepository.GetItem(runspace.InstanceId); + } + + if (existingPSSession != null && + UseExistingRunspace(existingPSSession.Runspace, runspace)) + { + if (TestRunspaceState(existingPSSession.Runspace, filterState)) + { + filteredPSSessions.Add(existingPSSession); + } + } + else if (TestRunspaceState(runspace, filterState)) + { + filteredPSSessions.Add(new PSSession(runspace as RemoteRunspace)); + } + } + } + } + + // Return only PSSessions that match provided Ids or Names. + if ((matchIds != null) && (filteredPSSessions.Count > 0)) + { + Collection matchIdsSessions = new Collection(); + foreach (Guid id in matchIds) + { + bool matchFound = false; + foreach (PSSession psSession in filteredPSSessions) + { + if (_stopProcessing) + { + break; + } + + if (psSession.Runspace.InstanceId.Equals(id)) + { + matchFound = true; + matchIdsSessions.Add(psSession); + break; + } + } + + if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) + { + string msg = StringUtil.Format(RemotingErrorIdStrings.SessionIdMatchFailed, id); + Exception reason = new RuntimeException(msg); + ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionIdMatchFail", ErrorCategory.InvalidOperation, id); + stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); + } + } + + // Return all found sessions. + return matchIdsSessions; + } + else if ((matchNames != null) && (filteredPSSessions.Count > 0)) + { + Collection matchNamesSessions = new Collection(); + foreach (string name in matchNames) + { + WildcardPattern namePattern = WildcardPattern.Get(name, WildcardOptions.IgnoreCase); + bool matchFound = false; + foreach (PSSession psSession in filteredPSSessions) + { + if (_stopProcessing) + { + break; + } + + if (namePattern.IsMatch(((RemoteRunspace)psSession.Runspace).RunspacePool.RemoteRunspacePoolInternal.Name)) + { + matchFound = true; + matchNamesSessions.Add(psSession); + } + } + + if (!matchFound && stream.ObjectWriter != null && stream.ObjectWriter.IsOpen) + { + string msg = StringUtil.Format(RemotingErrorIdStrings.SessionNameMatchFailed, name); + Exception reason = new RuntimeException(msg); + ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionNameMatchFail", ErrorCategory.InvalidOperation, name); + stream.ObjectWriter.Write((Action)(cmdlet => cmdlet.WriteError(errorRecord))); + } + } + + return matchNamesSessions; + } + else + { + // Return all collected sessions. + return filteredPSSessions; + } + } + + /// + /// Returns true if the existing runspace should be returned to the user + /// a. If the existing runspace is not broken + /// b. If the queried runspace is not connected to a different user. + /// + /// + /// + /// + private static bool UseExistingRunspace( + Runspace existingRunspace, + Runspace queriedrunspace) + { + Dbg.Assert(existingRunspace != null, "Invalid parameter."); + Dbg.Assert(queriedrunspace != null, "Invalid parameter."); + + if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Broken) + { + return false; + } + + if (existingRunspace.RunspaceStateInfo.State == RunspaceState.Disconnected && + queriedrunspace.RunspaceAvailability == RunspaceAvailability.Busy) + { + return false; + } + + // Update existing runspace to have latest DisconnectedOn/ExpiresOn data. + existingRunspace.DisconnectedOn = queriedrunspace.DisconnectedOn; + existingRunspace.ExpiresOn = queriedrunspace.ExpiresOn; + + return true; + } + + /// + /// Returns Exception message. If message is WSMan Xml then + /// the WSMan message and error code is extracted and returned. + /// + /// Exception. + /// Returned WSMan error code. + /// WSMan message. + internal static string ExtractMessage( + Exception e, + out int errorCode) + { + errorCode = 0; + + if (e == null || + e.Message == null) + { + return string.Empty; + } + + string rtnMsg = null; + try + { + System.Xml.XmlReaderSettings xmlReaderSettings = InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); + xmlReaderSettings.MaxCharactersInDocument = 4096; + xmlReaderSettings.MaxCharactersFromEntities = 1024; + xmlReaderSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit; + + using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create( + new System.IO.StringReader(e.Message), xmlReaderSettings)) + { + while (reader.Read()) + { + if (reader.NodeType == System.Xml.XmlNodeType.Element) + { + if (reader.LocalName.Equals("Message", StringComparison.OrdinalIgnoreCase)) + { + rtnMsg = reader.ReadElementContentAsString(); + } + else if (reader.LocalName.Equals("WSManFault", StringComparison.OrdinalIgnoreCase)) + { + string errorCodeString = reader.GetAttribute("Code"); + if (errorCodeString != null) + { + try + { + // WinRM returns both signed and unsigned 32 bit string values. Convert to signed 32 bit integer. + long eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo); + unchecked + { + errorCode = (int)eCode; + } + } + catch (FormatException) + { } + catch (OverflowException) + { } + } + } + } + } + } + } + catch (System.Xml.XmlException) + { } + + return rtnMsg ?? e.Message; + } + + /// + /// Discontinue all remote server query operations. + /// + internal void StopAllOperations() + { + _stopProcessing = true; + } + + /// + /// Compares the runspace filter state with the runspace state. + /// + /// Runspace object to test. + /// Filter state to compare. + /// Result of test. + public static bool TestRunspaceState(Runspace runspace, SessionFilterState filterState) + { + bool result; + + switch (filterState) + { + case SessionFilterState.All: + result = true; + break; + + case SessionFilterState.Opened: + result = (runspace.RunspaceStateInfo.State == RunspaceState.Opened); + break; + + case SessionFilterState.Closed: + result = (runspace.RunspaceStateInfo.State == RunspaceState.Closed); + break; + + case SessionFilterState.Disconnected: + result = (runspace.RunspaceStateInfo.State == RunspaceState.Disconnected); + break; + + case SessionFilterState.Broken: + result = (runspace.RunspaceStateInfo.State == RunspaceState.Broken); + break; + + default: + Dbg.Assert(false, "Invalid SessionFilterState value."); + result = false; + break; + } + + return result; + } + + /// + /// Returns the default type table for built-in PowerShell types. + /// + internal static TypeTable BuiltInTypesTable + { + get + { + if (s_TypeTable == null) + { + lock (s_SyncObject) + { + s_TypeTable ??= TypeTable.LoadDefaultTypeFiles(); + } + } + + return s_TypeTable; + } + } + + #endregion + + #region Private Members + + private bool _stopProcessing; + + private static readonly object s_SyncObject = new object(); + private static TypeTable s_TypeTable; + + #endregion + } + + #endregion + + #region SessionFilterState Enum + + /// + /// Runspace states that can be used as filters for querying remote runspaces. + /// + public enum SessionFilterState + { + /// + /// Return runspaces in any state. + /// + All = 0, + + /// + /// Return runspaces in Opened state. + /// + Opened = 1, + + /// + /// Return runspaces in Disconnected state. + /// + Disconnected = 2, + + /// + /// Return runspaces in Closed state. + /// + Closed = 3, + + /// + /// Return runspaces in Broken state. + /// + Broken = 4 } #endregion #endregion Helper Classes } + +namespace System.Management.Automation.Remoting +{ + /// + /// IMPORTANT: proxy configuration is supported for HTTPS only; for HTTP, the direct + /// connection to the server is used. + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] + public enum ProxyAccessType + { + /// + /// ProxyAccessType is not specified. That means Proxy information (ProxyAccessType, ProxyAuthenticationMechanism + /// and ProxyCredential)is not passed to WSMan at all. + /// + None = 0, + /// + /// Use the Internet Explorer proxy configuration for the current user. + /// Internet Explorer proxy settings for the current active network connection. + /// This option requires the user profile to be loaded, so the option can + /// be directly used when called within a process that is running under + /// an interactive user account identity; if the client application is running + /// under a user context different than the interactive user, the client + /// application has to explicitly load the user profile prior to using this option. + /// + IEConfig = 1, + /// + /// Proxy settings configured for WinHTTP, using the ProxyCfg.exe utility. + /// + WinHttpConfig = 2, + /// + /// Force autodetection of proxy. + /// + AutoDetect = 4, + /// + /// Do not use a proxy server - resolves all host names locally. + /// + NoProxyServer = 8 + } + /// + /// Options for a remote PSSession. + /// + public sealed class PSSessionOption + { + /// + /// Creates a new instance of + /// + public PSSessionOption() + { + } + + /// + /// The MaximumConnectionRedirectionCount parameter enables the implicit redirection functionality. + /// -1 = no limit + /// 0 = no redirection. + /// + public int MaximumConnectionRedirectionCount { get; set; } = WSManConnectionInfo.defaultMaximumConnectionRedirectionCount; + + /// + /// If false, underlying WSMan infrastructure will compress data sent on the network. + /// If true, data will not be compressed. Compression improves performance by + /// reducing the amount of data sent on the network. Compression my require extra + /// memory consumption and CPU usage. In cases where available memory / CPU is less, + /// set this property to "true". + /// By default the value of this property is "false". + /// + public bool NoCompression { get; set; } = false; + + /// + /// 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). + /// + public bool NoMachineProfile { get; set; } = false; + + /// + /// By default, ProxyAccessType is None, that means Proxy information (ProxyAccessType, + /// ProxyAuthenticationMechanism and ProxyCredential)is not passed to WSMan at all. + /// + public ProxyAccessType ProxyAccessType { get; set; } = ProxyAccessType.None; + + /// + /// The following is the definition of the input parameter "ProxyAuthentication". + /// This parameter takes a set of authentication methods the user can select + /// from. The available options should be as follows: + /// - Negotiate: Use the default authentication (as defined by the underlying + /// protocol) for establishing a remote connection. + /// - Basic: Use basic authentication for establishing a remote connection + /// - Digest: Use Digest authentication for establishing a remote connection + /// + /// Default is Negotiate. + /// + public AuthenticationMechanism ProxyAuthentication + { + get + { + return _proxyAuthentication; + } + + set + { + switch (value) + { + case AuthenticationMechanism.Basic: + case AuthenticationMechanism.Negotiate: + case AuthenticationMechanism.Digest: + _proxyAuthentication = value; + break; + default: + string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyAmbiguousAuthentication, + value, + nameof(AuthenticationMechanism.Basic), + nameof(AuthenticationMechanism.Negotiate), + nameof(AuthenticationMechanism.Digest)); + throw new ArgumentException(message); + } + } + } + + private AuthenticationMechanism _proxyAuthentication = AuthenticationMechanism.Negotiate; + + /// + /// The following is the definition of the input parameter "ProxyCredential". + /// + public PSCredential ProxyCredential { get; set; } + + /// + /// When connecting over HTTPS, the client does not validate that the server + /// certificate is signed by a trusted certificate authority (CA). Use only when + /// the remote computer is trusted by other means, for example, if the remote + /// computer is part of a network that is physically secure and isolated or the + /// remote computer is listed as a trusted host in WinRM configuration. + /// + public bool SkipCACheck { get; set; } + + /// + /// Indicates that certificate common name (CN) of the server need not match the + /// hostname of the server. Used only in remote operations using https. This + /// option should only be used for trusted machines. + /// + public bool SkipCNCheck { get; set; } + + /// + /// Indicates that certificate common name (CN) of the server need not match the + /// hostname of the server. Used only in remote operations using https. This + /// option should only be used for trusted machines. + /// + public bool SkipRevocationCheck { get; set; } + + /// + /// The duration for which PowerShell remoting waits before timing out + /// for any operation. The user would like to tweak this timeout + /// depending on whether he/she is connecting to a machine in the data + /// center or across a slow WAN. + /// + /// Default: 3*60*1000 == 3minutes. + /// + public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromMilliseconds(BaseTransportManager.ClientDefaultOperationTimeoutMs); + + /// + /// Specifies that no encryption will be used when doing remote operations over + /// http. Unencrypted traffic is not allowed by default and must be enabled in + /// the local configuration. + /// + public bool NoEncryption { get; set; } + + /// + /// Indicates the request is encoded in UTF16 format rather than UTF8 format; + /// UTF8 is the default. + /// + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UTF")] + public bool UseUTF16 { get; set; } + + /// + /// Uses Service Principal Name (SPN) along with the Port number during authentication. + /// + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] + public bool IncludePortInSPN { get; set; } + + /// + /// Determines how server in disconnected state deals with cached output + /// data when the cache becomes filled. + /// Default value is 'block mode' where command execution is blocked after + /// the server side data cache becomes filled. + /// + public OutputBufferingMode OutputBufferingMode { get; set; } = WSManConnectionInfo.DefaultOutputBufferingMode; + + /// + /// Number of times a connection will be re-attempted when a connection fails due to network + /// issues. + /// + public int MaxConnectionRetryCount { get; set; } = WSManConnectionInfo.DefaultMaxConnectionRetryCount; + + /// + /// Culture that the remote session should use. + /// + public CultureInfo Culture { get; set; } + + /// + /// UI culture that the remote session should use. + /// + public CultureInfo UICulture { get; set; } + + /// + /// Total data (in bytes) that can be received from a remote machine + /// targeted towards a command. If null, then the size is unlimited. + /// Default is unlimited data. + /// + public int? MaximumReceivedDataSizePerCommand { get; set; } + + /// + /// Maximum size (in bytes) of a deserialized object received from a remote machine. + /// If null, then the size is unlimited. Default is 200MB object size. + /// + public int? MaximumReceivedObjectSize { get; set; } = 200 << 20; + + /// + /// Application arguments the server can see in + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public PSPrimitiveDictionary ApplicationArguments { get; set; } + + /// + /// The duration for which PowerShell remoting waits before timing out on a connection to a remote machine. + /// Simply put, the timeout for a remote runspace creation. + /// The user would like to tweak this timeout depending on whether + /// he/she is connecting to a machine in the data center or across a slow WAN. + /// + /// Default: 3 * 60 * 1000 = 3 minutes. + /// + public TimeSpan OpenTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultOpenTimeout); + + /// + /// The duration for which PowerShell should wait before it times out on cancel operations + /// (close runspace or stop powershell). For instance, when the user hits ctrl-C, + /// New-PSSession cmdlet tries to call a stop on all remote runspaces which are in the Opening state. + /// The user wouldn't mind waiting for 15 seconds, but this should be time bound and of a shorter duration. + /// A high timeout here like 3 minutes will give the user a feeling that the PowerShell client is not responding. + /// + /// Default: 60 * 1000 = 1 minute. + /// + public TimeSpan CancelTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.defaultCancelTimeout); + + /// + /// The duration for which a Runspace on server needs to wait before it declares the client dead and closes itself down. + /// This is especially important as these values may have to be configured differently for enterprise administration + /// and exchange scenarios. + /// + /// Default: -1 -> Use current server value for IdleTimeout. + /// + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMilliseconds(RunspaceConnectionInfo.DefaultIdleTimeout); + } +} diff --git a/src/System.Management.Automation/engine/remoting/commands/PopRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PopRunspaceCommand.cs index a4540fd23d4..5f80dfb5d0c 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PopRunspaceCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PopRunspaceCommand.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Remoting; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands /// /// Exit-PSSession cmdlet. /// - [Cmdlet(VerbsCommon.Exit, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135212")] + [Cmdlet(VerbsCommon.Exit, "PSSession", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096787")] public class ExitPSSessionCommand : PSRemotingCmdlet { /// @@ -28,7 +28,7 @@ protected override void ProcessRecord() WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.HostDoesNotSupportPushRunspace)), - PSRemotingErrorId.HostDoesNotSupportPushRunspace.ToString(), + nameof(PSRemotingErrorId.HostDoesNotSupportPushRunspace), ErrorCategory.InvalidArgument, null)); return; diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs index 9733a601c62..bf456e85db2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs @@ -1,19 +1,21 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Host; -using System.Management.Automation.Remoting; using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; +using System.Threading; + +using Microsoft.PowerShell.Commands.Internal.Format; + using Dbg = System.Management.Automation.Diagnostics; -using System.Collections; namespace Microsoft.PowerShell.Commands { @@ -21,7 +23,7 @@ namespace Microsoft.PowerShell.Commands /// Enter-PSSession cmdlet. /// [Cmdlet(VerbsCommon.Enter, "PSSession", DefaultParameterSetName = "ComputerName", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135210", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096695", RemotingCapability = RemotingCapability.OwnedByCommand)] public class EnterPSSessionCommand : PSRemotingBaseCmdlet { #region Strings @@ -37,7 +39,8 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet /// /// Disable ThrottleLimit parameter inherited from base class. /// - public new Int32 ThrottleLimit { set { } get { return 0; } } + public new int ThrottleLimit { get { return 0; } set { } } + private ObjectStream _stream; private RemoteRunspace _tempRunspace; @@ -48,13 +51,31 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet #region SSH Parameter Set /// - /// Host name for an SSH remote connection + /// Host name for an SSH remote connection. /// [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 /// @@ -107,7 +128,7 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] - public String Name { get; set; } + public string Name { get; set; } /// /// When set and in loopback scenario (localhost) this enables creation of WSMan @@ -149,10 +170,11 @@ 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; } + set { base.Credential = value; } } @@ -183,12 +205,12 @@ public override PSCredential Credential ParameterSetName = EnterPSSessionCommand.VMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = EnterPSSessionCommand.VMNameParameterSet)] - public String ConfigurationName { get; set; } + public string ConfigurationName { get; set; } #region Suppress PSRemotingBaseCmdlet SSH hash parameter set /// - /// Suppress SSHConnection parameter set + /// Suppress SSHConnection parameter set. /// public override Hashtable[] SSHConnection { @@ -202,13 +224,13 @@ public override Hashtable[] SSHConnection #region Overrides /// - /// Resolves shellname and appname + /// Resolves shellname and appname. /// protected override void BeginProcessing() { base.BeginProcessing(); - if (String.IsNullOrEmpty(ConfigurationName)) + if (string.IsNullOrEmpty(ConfigurationName)) { if ((ParameterSetName == EnterPSSessionCommand.ComputerNameParameterSet) || (ParameterSetName == EnterPSSessionCommand.UriParameterSet)) @@ -218,8 +240,8 @@ protected override void BeginProcessing() } else { - // convert null to String.Empty for VM/Container session - ConfigurationName = String.Empty; + // convert null to string.Empty for VM/Container session + ConfigurationName = string.Empty; } } } @@ -236,33 +258,14 @@ protected override void ProcessRecord() WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.HostDoesNotSupportPushRunspace)), - PSRemotingErrorId.HostDoesNotSupportPushRunspace.ToString(), - ErrorCategory.InvalidArgument, - null)); - return; - } - - // Check if current host is remote host. Enter-PSSession on remote host is not - // currently supported. - if (!IsParameterSetForVM() && - !IsParameterSetForContainer() && - !IsParameterSetForVMContainerSession() && - this.Context != null && - this.Context.EngineHostInterface != null && - this.Context.EngineHostInterface.ExternalHost != null && - this.Context.EngineHostInterface.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost) - { - WriteError( - new ErrorRecord( - new ArgumentException(GetMessage(RemotingErrorIdStrings.RemoteHostDoesNotSupportPushRunspace)), - PSRemotingErrorId.RemoteHostDoesNotSupportPushRunspace.ToString(), + nameof(PSRemotingErrorId.HostDoesNotSupportPushRunspace), ErrorCategory.InvalidArgument, null)); return; } // 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; @@ -329,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; @@ -388,7 +394,7 @@ protected override void ProcessRecord() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.EnterPSSessionBrokenSession, sessionName, remoteRunspace.ConnectionInfo.ComputerName, remoteRunspace.InstanceId)), - PSRemotingErrorId.PushedRunspaceMustBeOpen.ToString(), + nameof(PSRemotingErrorId.PushedRunspaceMustBeOpen), ErrorCategory.InvalidArgument, null)); } @@ -397,7 +403,7 @@ protected override void ProcessRecord() WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.PushedRunspaceMustBeOpen)), - PSRemotingErrorId.PushedRunspaceMustBeOpen.ToString(), + nameof(PSRemotingErrorId.PushedRunspaceMustBeOpen), ErrorCategory.InvalidArgument, null)); } @@ -494,7 +500,7 @@ protected override void ProcessRecord() { // A third-party host can throw any exception here..we should // clean the runspace created in this case. - if ((null != remoteRunspace) && (remoteRunspace.ShouldCloseOnPop)) + if ((remoteRunspace != null) && (remoteRunspace.ShouldCloseOnPop)) { remoteRunspace.Close(); } @@ -506,11 +512,11 @@ protected override void ProcessRecord() /// /// This method will until the runspace is opened and warnings if any - /// are reported + /// are reported. /// protected override void EndProcessing() { - if (null != _stream) + if (_stream != null) { while (true) { @@ -519,19 +525,18 @@ protected override void EndProcessing() if (!_stream.ObjectReader.EndOfPipeline) { - Object streamObject = _stream.ObjectReader.Read(); + object streamObject = _stream.ObjectReader.Read(); WriteStreamObject((Action)streamObject); } else { break; } - } // while ... + } } - }// EndProcessing() + } /// - /// /// protected override void StopProcessing() { @@ -543,6 +548,7 @@ protected override void StopProcessing() remoteRunspace.CloseAsync(); } catch (InvalidRunspaceStateException) { } + return; } @@ -552,11 +558,12 @@ protected override void StopProcessing() WriteError( new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.HostDoesNotSupportPushRunspace)), - PSRemotingErrorId.HostDoesNotSupportPushRunspace.ToString(), + nameof(PSRemotingErrorId.HostDoesNotSupportPushRunspace), ErrorCategory.InvalidArgument, null)); return; } + host.PopRunspace(); } @@ -620,7 +627,7 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception exception, object ar // having to mine through the error record details PSRemotingTransportException transException = exception as PSRemotingTransportException; - String errorDetails = null; + string errorDetails = null; if ((transException != null) && (transException.ErrorCode == System.Management.Automation.Remoting.Client.WSManNativeApi.ERROR_WSMAN_REDIRECT_REQUESTED)) @@ -651,7 +658,7 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception exception, object ar /// private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourceString, object errorArgument) { - String message = GetMessage(resourceString, errorArgument); + string message = GetMessage(resourceString, errorArgument); WriteError(new ErrorRecord(new ArgumentException(message), errorId.ToString(), ErrorCategory.InvalidArgument, errorArgument)); } @@ -665,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); } @@ -740,6 +744,7 @@ private RemoteRunspace CreateRunspaceWhenUriParameterSpecified() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); connectionInfo.EnableNetworkAccess = EnableNetworkAccess; @@ -807,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); } /// @@ -823,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); } /// @@ -839,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) @@ -898,6 +896,7 @@ private bool IsParameterSetForVMContainerSession() { remoteRunspace = (RemoteRunspace)this.Session.Runspace; } + break; case InstanceIdParameterSet: @@ -952,7 +951,7 @@ private RemoteRunspace GetRunspaceForVMSession() WriteError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), - PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), + nameof(PSRemotingErrorId.HyperVModuleNotAvailable), ErrorCategory.NotInstalled, null)); @@ -964,7 +963,7 @@ private RemoteRunspace GetRunspaceForVMSession() WriteError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.InvalidVMId), - PSRemotingErrorId.InvalidVMId.ToString(), + nameof(PSRemotingErrorId.InvalidVMId), ErrorCategory.InvalidArgument, null)); @@ -989,7 +988,7 @@ private RemoteRunspace GetRunspaceForVMSession() WriteError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), - PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), + nameof(PSRemotingErrorId.HyperVModuleNotAvailable), ErrorCategory.NotInstalled, null)); @@ -1001,7 +1000,7 @@ private RemoteRunspace GetRunspaceForVMSession() WriteError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.InvalidVMNameNoVM), - PSRemotingErrorId.InvalidVMNameNoVM.ToString(), + nameof(PSRemotingErrorId.InvalidVMNameNoVM), ErrorCategory.InvalidArgument, null)); @@ -1012,7 +1011,7 @@ private RemoteRunspace GetRunspaceForVMSession() WriteError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.InvalidVMNameMultipleVM), - PSRemotingErrorId.InvalidVMNameMultipleVM.ToString(), + nameof(PSRemotingErrorId.InvalidVMNameMultipleVM), ErrorCategory.InvalidArgument, null)); @@ -1026,13 +1025,13 @@ 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( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMState, this.VMName)), - PSRemotingErrorId.InvalidVMState.ToString(), + nameof(PSRemotingErrorId.InvalidVMState), ErrorCategory.InvalidArgument, null)); @@ -1105,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(); @@ -1153,7 +1152,7 @@ private void SetRunspacePrompt(RemoteRunspace remoteRunspace) case ContainerIdParameterSet: targetName = (this.ContainerId.Length <= 15) ? this.ContainerId - : this.ContainerId.Remove(12) + "..."; + : this.ContainerId.Remove(14) + PSObjectHelper.Ellipsis; break; case SessionParameterSet: @@ -1168,6 +1167,7 @@ private void SetRunspacePrompt(RemoteRunspace remoteRunspace) { targetName = remoteRunspace.ConnectionInfo.ComputerName; } + break; default: @@ -1208,7 +1208,7 @@ private RemoteRunspace GetRunspaceForContainerSession() try { - Dbg.Assert(!String.IsNullOrEmpty(ContainerId), "ContainerId has to be set."); + Dbg.Assert(!string.IsNullOrEmpty(ContainerId), "ContainerId has to be set."); ContainerConnectionInfo connectionInfo = null; @@ -1278,11 +1278,12 @@ private RemoteRunspace GetRunspaceForContainerSession() } /// - /// Create remote runspace for SSH session + /// Create remote runspace for SSH session. /// private RemoteRunspace GetRunspaceForSSHSession() { - var sshConnectionInfo = new SSHConnectionInfo(this.UserName, ResolveComputerName(HostName), this.KeyFilePath, this.Port); + ParseSshHostName(HostName, out string host, out string userName, out int port); + 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 3d256fe253b..b1759d6a88e 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceiveJob.cs @@ -1,20 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; -using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Internal; using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Internal; // Stops compiler from warning about unknown warnings #pragma warning disable 1634, 1691 @@ -51,18 +51,17 @@ namespace Microsoft.PowerShell.Commands /// $job = Get-WMIObject '....' -AsJob /// Receive-PSJob -Job $job -ComputerName "Server2" /// The parameter ComputerName cannot be used with jobs which are - /// not PSRemotingJob - /// + /// not PSRemotingJob. /// [Cmdlet(VerbsCommunications.Receive, "Job", DefaultParameterSetName = ReceiveJobCommand.LocationParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113372", RemotingCapability = RemotingCapability.SupportedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096965", RemotingCapability = RemotingCapability.SupportedByCommand)] public class ReceiveJobCommand : JobCmdletBase, IDisposable { #region Properties /// /// Job object from which specific results need to - /// be extracted + /// be extracted. /// [Parameter(Position = 0, Mandatory = true, @@ -86,16 +85,18 @@ public Job[] Job { return _jobs; } + set { _jobs = value; } } + private Job[] _jobs; /// /// Name of the computer for which the results needs to be - /// returned + /// returned. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ReceiveJobCommand.ComputerNameParameterSet, @@ -103,44 +104,48 @@ public Job[] Job [Alias("Cn")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [ValidateNotNullOrEmpty] - public String[] ComputerName + public string[] ComputerName { get { return _computerNames; } + set { _computerNames = value; } } - private String[] _computerNames; + + private string[] _computerNames; /// /// Locations for which the results needs to be returned. /// This will cater to all kinds of jobs and not only - /// remoting jobs + /// remoting jobs. /// [Parameter(ParameterSetName = ReceiveJobCommand.LocationParameterSet, Position = 1)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Location + public string[] Location { get { return _locations; } + set { _locations = value; } } - private String[] _locations; + + private string[] _locations; /// /// Runspaces for which the results needs to be - /// returned + /// returned. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ReceiveJobCommand.SessionParameterSet, @@ -153,58 +158,61 @@ public PSSession[] Session { return _remoteRunspaceInfos; } + set { _remoteRunspaceInfos = value; } } + private PSSession[] _remoteRunspaceInfos; /// /// 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 { return !_flush; } + set { _flush = !value; ValidateWait(); } } + private bool _flush = true; /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter NoRecurse { get { return !_recurse; } + set { _recurse = !value; } } + private bool _recurse = true; /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter Force { get; set; } /// - /// /// public override JobState State { @@ -215,7 +223,6 @@ public override JobState State } /// - /// /// public override Hashtable Filter { @@ -223,7 +230,6 @@ public override Hashtable Filter } /// - /// /// public override string[] Command { @@ -234,20 +240,19 @@ public override string[] Command } /// - /// /// protected const string LocationParameterSet = "Location"; /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter Wait { get { return _wait; } + set { _wait = value; @@ -256,15 +261,15 @@ public SwitchParameter Wait } /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter AutoRemoveJob { get { return _autoRemoveJob; } + set { _autoRemoveJob = value; @@ -272,12 +277,15 @@ public SwitchParameter AutoRemoveJob } /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteEvents { - get { return _writeStateChangedEvents; } + get + { + return _writeStateChangedEvents; + } + set { _writeStateChangedEvents = value; @@ -285,12 +293,15 @@ public SwitchParameter WriteEvents } /// - /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteJobInResults { - get { return _outputJobFirst; } + get + { + return _outputJobFirst; + } + set { _outputJobFirst = value; @@ -321,7 +332,6 @@ public SwitchParameter WriteJobInResults #region Overrides /// - /// /// protected override void BeginProcessing() { @@ -333,7 +343,7 @@ protected override void BeginProcessing() /// /// Retrieve the results for the specified computers or - /// runspaces + /// runspaces. /// protected override void ProcessRecord() { @@ -351,7 +361,7 @@ protected override void ProcessRecord() if (remoteJob == null) { - String message = GetMessage(RemotingErrorIdStrings.RunspaceParamNotSupported); + string message = GetMessage(RemotingErrorIdStrings.RunspaceParamNotSupported); WriteError(new ErrorRecord(new ArgumentException(message), "RunspaceParameterNotSupported", ErrorCategory.InvalidArgument, @@ -360,17 +370,18 @@ protected override void ProcessRecord() continue; } - //Runspace parameter is supported only on PSRemotingJob objects + // Runspace parameter is supported only on PSRemotingJob objects foreach (PSSession remoteRunspaceInfo in _remoteRunspaceInfos) { // get the required child jobs List childJobs = remoteJob.GetJobsForRunspace(remoteRunspaceInfo); jobsToWrite.AddRange(childJobs); - //WriteResultsForJobsInCollection(childJobs, false); + // WriteResultsForJobsInCollection(childJobs, false); - } // foreach(RemoteRunspaceInfo... - } // foreach ... + } + } } + break; case ComputerNameParameterSet: @@ -384,7 +395,7 @@ protected override void ProcessRecord() // ComputerName parameter can only be used with remoting jobs if (remoteJob == null) { - String message = GetMessage(RemotingErrorIdStrings.ComputerNameParamNotSupported); + string message = GetMessage(RemotingErrorIdStrings.ComputerNameParamNotSupported); WriteError(new ErrorRecord(new ArgumentException(message), "ComputerNameParameterNotSupported", ErrorCategory.InvalidArgument, @@ -393,26 +404,27 @@ protected override void ProcessRecord() continue; } - String[] resolvedComputernames = null; + string[] resolvedComputernames = null; ResolveComputerNames(_computerNames, out resolvedComputernames); - foreach (String resolvedComputerName in resolvedComputernames) + foreach (string resolvedComputerName in resolvedComputernames) { // get the required child Job objects List childJobs = remoteJob.GetJobsForComputer(resolvedComputerName); jobsToWrite.AddRange(childJobs); - //WriteResultsForJobsInCollection(childJobs, false); + // WriteResultsForJobsInCollection(childJobs, false); - } // foreach (String... - } // foreach ... + } + } } + break; case "Location": { if (_locations == null) { - //WriteAll(); + // WriteAll(); jobsToWrite.AddRange(_jobs); checkForRecurse = true; } @@ -420,16 +432,17 @@ protected override void ProcessRecord() { foreach (Job job in _jobs) { - foreach (String location in _locations) + foreach (string location in _locations) { // get the required child Job objects List childJobs = job.GetJobsForLocation(location); jobsToWrite.AddRange(childJobs); - //WriteResultsForJobsInCollection(childJobs, false); - } // foreach (String... - } // foreach ... + // WriteResultsForJobsInCollection(childJobs, false); + } + } } } + break; case ReceiveJobCommand.InstanceIdParameterSet: @@ -438,8 +451,9 @@ protected override void ProcessRecord() jobsToWrite.AddRange(jobs); checkForRecurse = true; - //WriteResultsForJobsInCollection(jobs, true); + // WriteResultsForJobsInCollection(jobs, true); } + break; case ReceiveJobCommand.SessionIdParameterSet: @@ -447,8 +461,9 @@ protected override void ProcessRecord() List jobs = FindJobsMatchingBySessionId(true, false, true, false); jobsToWrite.AddRange(jobs); checkForRecurse = true; - //WriteResultsForJobsInCollection(jobs, true); + // WriteResultsForJobsInCollection(jobs, true); } + break; case ReceiveJobCommand.NameParameterSet: @@ -456,8 +471,9 @@ protected override void ProcessRecord() List jobs = FindJobsMatchingByName(true, false, true, false); jobsToWrite.AddRange(jobs); checkForRecurse = true; - //WriteResultsForJobsInCollection(jobs, true); + // WriteResultsForJobsInCollection(jobs, true); } + break; } @@ -520,12 +536,12 @@ protected override void ProcessRecord() { WriteResultsForJobsInCollection(jobsToWrite, checkForRecurse, false); } - } // ProcessRecord + } /// /// StopProcessing - when the command is stopped, /// unregister all the event handlers from the jobs - /// and decrement reference for results + /// and decrement reference for results. /// protected override void StopProcessing() { @@ -566,8 +582,8 @@ protected override void StopProcessing() } /// - /// if we are not stopping, continue writing output - /// as and when they are available + /// If we are not stopping, continue writing output + /// as and when they are available. /// protected override void EndProcessing() { @@ -613,7 +629,6 @@ protected override void EndProcessing() } /// - /// /// public void Dispose() { @@ -622,7 +637,6 @@ public void Dispose() } /// - /// /// /// protected void Dispose(bool disposing) @@ -661,6 +675,7 @@ protected void Dispose(bool disposing) job.Debug.DataAdded -= Debug_DataAdded; job.Information.DataAdded -= Information_DataAdded; } + job.StateChanged -= HandleJobStateChanged; } } @@ -713,11 +728,10 @@ private static void DoUnblockJob(Job job) /// /// Write the results from this Job object. This does not write from the - /// child jobs of this job object + /// child jobs of this job object. /// /// Job object from which to write the results from /// - /// private void WriteJobResults(Job job) { if (job == null) return; @@ -760,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); @@ -809,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); @@ -821,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); @@ -833,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); @@ -845,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); @@ -857,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); } } @@ -871,7 +870,7 @@ private void WriteJobResults(Job job) private void WriteReasonError(Job job) { - //Write better error for the remoting case and generic error for the other case + // Write better error for the remoting case and generic error for the other case PSRemotingChildJob child = job as PSRemotingChildJob; if (child != null && child.FailureErrorRecord != null) { @@ -911,8 +910,8 @@ private void WriteReasonError(Job job) /// /// Returns all the results from supplied PSDataCollection. /// - /// data collection to read from - /// collection with copy of data + /// Data collection to read from. + /// Collection with copy of data. private Collection ReadAll(PSDataCollection psDataCollection) { if (_flush) @@ -927,6 +926,7 @@ private Collection ReadAll(PSDataCollection psDataCollection) { collection.Add(t); } + return collection; } @@ -934,20 +934,20 @@ private Collection ReadAll(PSDataCollection psDataCollection) /// Write the results from this Job object. It also writes the /// results from its child objects recursively. /// - /// - /// Hashtable used for duplicate detection - /// Job whose results are written + /// Hashtable used for duplicate detection. + /// Job whose results are written. /// private void WriteJobResultsRecursivelyHelper(Hashtable duplicate, Job job, bool registerInsteadOfWrite) { - //Check if this object is already visited. If not, add it to the cache + // Check if this object is already visited. If not, add it to the cache if (duplicate.ContainsKey(job)) { return; } + duplicate.Add(job, job); - //Write the results of child jobs + // Write the results of child jobs IList childJobs = job.ChildJobs; foreach (Job childjob in childJobs) @@ -968,16 +968,16 @@ private void WriteJobResultsRecursivelyHelper(Hashtable duplicate, Job job, bool } else { - //Write the results of this job + // Write the results of this job WriteJobResults(job); WriteJobStateInformationIfRequired(job); } - } // WriteAllEntities + } /// - /// Writes the job objects if required by the cmdlet + /// Writes the job objects if required by the cmdlet. /// - /// collection of jobs to write + /// Collection of jobs to write. /// this method is intended to be called only from /// ProcessRecord. When any changes are made ensure that this /// contract is not broken @@ -993,7 +993,6 @@ private void WriteJobsIfRequired(IEnumerable jobsToWrite) } /// - /// /// /// /// this method should always be called before @@ -1051,10 +1050,7 @@ private void AggregateResultsFromJob(Job job) { lock (_syncObject) { - if (_outputProcessingNotification == null) - { - _outputProcessingNotification = new OutputProcessingState(); - } + _outputProcessingNotification ??= new OutputProcessingState(); } } @@ -1071,6 +1067,7 @@ private void ResultsAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); PSDataCollection results = sender as PSDataCollection; @@ -1110,6 +1107,7 @@ private void HandleJobStateChanged(object sender, JobStateEventArgs e) return; } } + if (e.JobStateInfo.State == JobState.Blocked) { DoUnblockJob(job); @@ -1140,6 +1138,7 @@ private void Progress_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1164,13 +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); @@ -1192,6 +1199,7 @@ private void Debug_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1218,6 +1226,7 @@ private void Warning_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1244,6 +1253,7 @@ private void Verbose_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1270,6 +1280,7 @@ private void Information_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1296,6 +1307,7 @@ private void Output_DataAdded(object sender, DataAddedEventArgs e) { if (_isDisposed) return; } + _writeExistingData.WaitOne(); _resultsReaderWriterLock.EnterReadLock(); try @@ -1330,6 +1342,7 @@ private T GetData(PSDataCollection collection, int index) // the data got written return default(T); } + return collection[index]; } @@ -1370,6 +1383,7 @@ private void StopAggregateResultsFromJob(Job job) job.Debug.DataAdded -= Debug_DataAdded; job.Information.DataAdded -= Information_DataAdded; } + job.StateChanged -= HandleJobStateChanged; } @@ -1386,6 +1400,7 @@ private void AutoRemoveJobIfRequired(Job job) _tracer.WriteMessage(ClassNameTrace, "AutoRemoveJobIfRequired", Guid.Empty, job, "Job has data and is being removed."); } + Job2 job2 = job as Job2; if (job2 != null) { @@ -1429,8 +1444,7 @@ private void AddRemoveErrorToResults(Job job, Exception ex) /// Write the results from this Job object. It also writes the /// results from its child objects recursively. /// - /// - /// Job whose results are written + /// Job whose results are written. /// private void WriteJobResultsRecursively(Job job, bool registerInsteadOfWrite) { @@ -1440,7 +1454,6 @@ private void WriteJobResultsRecursively(Job job, bool registerInsteadOfWrite) } /// - /// /// /// /// @@ -1449,16 +1462,6 @@ private void WriteResultsForJobsInCollection(List jobs, bool checkForRecurs { foreach (Job job in jobs) { - if (JobManager.IsJobFromAdapter(job.InstanceId, "PSWorkflowJob") && - job.JobStateInfo.State == JobState.Stopped) - { - MshCommandRuntime mshCommandRuntime = CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteWarning(new WarningRecord(StringUtil.Format(RemotingErrorIdStrings.JobWasStopped, job.Name)), true); - } - } - if (checkForRecurse && _recurse) { WriteJobResultsRecursively(job, registerInsteadOfWrite); @@ -1485,6 +1488,7 @@ private void WriteResultsForJobsInCollection(List jobs, bool checkForRecurs } private readonly Dictionary _eventArgsWritten = new Dictionary(); + private void WriteJobStateInformation(Job job, JobStateEventArgs args = null) { // at any point there will be only one thread which will have diff --git a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs index 1f814cd9ed7..67477e5b36f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -12,6 +11,7 @@ using System.Management.Automation.Remoting.Client; using System.Management.Automation.Runspaces; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -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. @@ -60,12 +60,12 @@ namespace Microsoft.PowerShell.Commands /// > Receive-PSSession $session.Name /// /// Receive a running command from a computer. - /// > $job = Receive-PSSession -ComputerName ServerOne -Name SessionName -OutTarget Job - /// + /// > $job = Receive-PSSession -ComputerName ServerOne -Name SessionName -OutTarget Job. /// [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")] - [Cmdlet(VerbsCommunications.Receive, "PSSession", SupportsShouldProcess = true, DefaultParameterSetName = ReceivePSSessionCommand.SessionParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217037", RemotingCapability = RemotingCapability.OwnedByCommand)] + [Cmdlet(VerbsCommunications.Receive, "PSSession", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low, + DefaultParameterSetName = ReceivePSSessionCommand.SessionParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096800", + RemotingCapability = RemotingCapability.OwnedByCommand)] public class ReceivePSSessionCommand : PSRemotingCmdlet { #region Parameters @@ -88,7 +88,6 @@ public class ReceivePSSessionCommand : PSRemotingCmdlet [ValidateNotNullOrEmpty] public PSSession Session { get; set; } - /// /// Session Id of PSSession object to receive data from. /// @@ -97,8 +96,7 @@ public class ReceivePSSessionCommand : PSRemotingCmdlet ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = ReceivePSSessionCommand.IdParameterSet)] - public Int32 Id { get; set; } - + public int Id { get; set; } /// /// Computer name to receive session data from. @@ -113,27 +111,32 @@ public class ReceivePSSessionCommand : PSRemotingCmdlet ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)] [ValidateNotNullOrEmpty] [Alias("Cn")] - public String ComputerName { get; set; } + public string ComputerName { get; set; } /// /// 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 - /// not specified as well, then "WSMAN" will be used + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's + /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)] - public String ApplicationName + public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } + set { _appName = ResolveAppName(value); } } - private String _appName; + + private string _appName; /// /// If this parameter is not specified then the value specified in @@ -148,15 +151,20 @@ public String ApplicationName ParameterSetName = ReceivePSSessionCommand.ConnectionUriSessionNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] - public String ConfigurationName + public string ConfigurationName { - get { return _shell; } + get + { + return _shell; + } + set { _shell = ResolveShell(value); } } - private String _shell; + + private string _shell; /// /// A complete URI(s) specified for the remote computer and shell to @@ -180,8 +188,10 @@ public String ConfigurationName public SwitchParameter AllowRedirection { get { return _allowRedirection; } + set { _allowRedirection = value; } } + private bool _allowRedirection = false; /// @@ -199,7 +209,6 @@ public SwitchParameter AllowRedirection [ValidateNotNullOrEmpty] public Guid InstanceId { get; set; } - /// /// Name of PSSession object to receive data from. /// @@ -215,7 +224,6 @@ public SwitchParameter AllowRedirection [ValidateNotNullOrEmpty] public string Name { get; set; } - /// /// Determines how running command output is returned on client. /// @@ -243,7 +251,6 @@ public SwitchParameter AllowRedirection [ValidateNotNullOrEmpty] public string JobName { get; set; } = string.Empty; - /// /// Specifies the credentials of the user to impersonate in the /// remote machine. If this parameter is not specified then the @@ -253,10 +260,14 @@ 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 { _psCredential = value; @@ -264,8 +275,8 @@ public PSCredential Credential PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private PSCredential _psCredential; + private PSCredential _psCredential; /// /// Use basic authentication to authenticate the user. @@ -276,7 +287,11 @@ public PSCredential Credential [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } + set { _authentication = value; @@ -284,8 +299,8 @@ public AuthenticationMechanism Authentication PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private AuthenticationMechanism _authentication; + private AuthenticationMechanism _authentication; /// /// Specifies the certificate thumbprint to be used to impersonate the user on the @@ -297,7 +312,11 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = ReceivePSSessionCommand.ConnectionUriInstanceIdParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } + set { _thumbprint = value; @@ -305,8 +324,8 @@ public string CertificateThumbprint PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private string _thumbprint; + private string _thumbprint; /// /// Port specifies the alternate port to be used in case the @@ -321,9 +340,8 @@ public string CertificateThumbprint /// [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerInstanceIdParameterSet)] [Parameter(ParameterSetName = ReceivePSSessionCommand.ComputerSessionNameParameterSet)] - [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] - public Int32 Port { get; set; } - + [ValidateRange((int)1, (int)UInt16.MaxValue)] + public int Port { get; set; } /// /// This parameter suggests that the transport scheme to be used for @@ -386,15 +404,8 @@ protected override void StopProcessing() tmpJob = _job; } - if (tmpPipeline != null) - { - tmpPipeline.StopAsync(); - } - - if (tmpJob != null) - { - tmpJob.StopJob(); - } + tmpPipeline?.StopAsync(); + tmpJob?.StopJob(); } #endregion @@ -435,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 @@ -464,7 +475,7 @@ private void QueryForAndConnectCommands(string name, Guid instanceId) // Find specified session. bool haveMatch = false; if (!string.IsNullOrEmpty(name) && - string.Compare(name, ((RemoteRunspace)runspace).RunspacePool.RemoteRunspacePoolInternal.Name, StringComparison.OrdinalIgnoreCase) == 0) + string.Equals(name, ((RemoteRunspace)runspace).RunspacePool.RemoteRunspacePoolInternal.Name, StringComparison.OrdinalIgnoreCase)) { // Selected by friendly name. haveMatch = true; @@ -536,7 +547,7 @@ private void QueryForAndConnectCommands(string name, Guid instanceId) } else { - String message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, newSession.Name); + string message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, newSession.Name); WriteError(new ErrorRecord(new ArgumentException(message, ex), "ReceivePSSessionCannotConnectSession", ErrorCategory.InvalidOperation, newSession)); } @@ -570,6 +581,7 @@ private WSManConnectionInfo GetConnectionObject() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); } @@ -585,6 +597,7 @@ private WSManConnectionInfo GetConnectionObject() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); } @@ -697,7 +710,7 @@ private void GetAndConnectSessionCommand() if (session == null) { // No luck. Return error. - String message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, oldSession.Name); + string message = StringUtil.Format(RemotingErrorIdStrings.RunspaceCannotBeConnected, oldSession.Name); WriteError(new ErrorRecord(new ArgumentException(message, ex), "ReceivePSSessionCannotConnectSession", ErrorCategory.InvalidOperation, oldSession)); @@ -798,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(); } } @@ -818,7 +825,7 @@ private void WriteDebugStopWarning() { WriteWarning( GetMessage(RemotingErrorIdStrings.ReceivePSSessionInDebugMode)); - WriteObject(""); + WriteObject(string.Empty); } /// @@ -838,12 +845,17 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) // Reconnect the job object and stream data to host. lock (_syncObject) { _job = job; _stopPipelineReceive = new ManualResetEvent(false); } + using (_stopPipelineReceive) using (job) { Job childJob = job.ChildJobs[0]; job.ConnectJobs(); - if (CheckForDebugMode(session, true)) { return; } + if (CheckForDebugMode(session, true)) + { + return; + } + do { // Retrieve and display results from child job as they become @@ -854,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) @@ -868,6 +877,7 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) } while (!job.IsFinishedState(job.JobStateInfo.State)); } + lock (_syncObject) { _job = null; _stopPipelineReceive = null; } return; @@ -889,6 +899,7 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) _remotePipeline = (RemotePipeline)session.Runspace.CreateDisconnectedPipeline(); _stopPipelineReceive = new ManualResetEvent(false); } + using (_stopPipelineReceive) { using (_remotePipeline) @@ -910,9 +921,13 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) _remotePipeline.ConnectAsync(); pipelineConnectedEvent.WaitOne(); } + 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) @@ -1008,6 +1023,7 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) } } } + lock (_syncObject) { _remotePipeline = null; _stopPipelineReceive = null; } } @@ -1015,8 +1031,8 @@ private void ConnectSessionToHost(PSSession session, PSRemotingJob job = null) /// Helper method to append computer name and session GUID /// note properties to the PSObject before it is written. /// - /// PSObject - /// PSSession + /// PSObject. + /// PSSession. private void WriteRemoteObject( PSObject psObject, PSSession session) @@ -1031,10 +1047,12 @@ private void WriteRemoteObject( { psObject.Properties.Add(new PSNoteProperty(RemotingConstants.ComputerNameNoteProperty, session.ComputerName)); } + if (psObject.Properties[RemotingConstants.RunspaceIdNoteProperty] == null) { psObject.Properties.Add(new PSNoteProperty(RemotingConstants.RunspaceIdNoteProperty, session.InstanceId)); } + if (psObject.Properties[RemotingConstants.ShowComputerNameNoteProperty] == null) { psObject.Properties.Add(new PSNoteProperty(RemotingConstants.ShowComputerNameNoteProperty, true)); @@ -1088,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); @@ -1102,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; @@ -1141,12 +1162,11 @@ private PSSession ConnectSession(PSSession session, out Exception ex) /// Helper method to attempt to retrieve a disconnected runspace object /// from the server, based on the provided session object. /// - /// PSSession - /// PSSession + /// PSSession session object. + /// 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; } @@ -1221,7 +1241,7 @@ private PSRemotingJob FindJobForSession(PSSession session) /// /// Id to match. /// PSSession object. - private PSSession GetSessionById(Int32 id) + private PSSession GetSessionById(int id) { foreach (PSSession session in this.RunspaceRepository.Runspaces) { @@ -1276,7 +1296,7 @@ private PSSession GetSessionByInstanceId(Guid instanceId) /// private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourceString, object errorArgument) { - String message = GetMessage(resourceString, errorArgument); + string message = GetMessage(resourceString, errorArgument); WriteError(new ErrorRecord(new ArgumentException(message), errorId.ToString(), ErrorCategory.InvalidArgument, errorArgument)); @@ -1290,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 } @@ -1303,7 +1323,7 @@ private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourc public enum OutTarget { /// - /// Default mode. If + /// Default mode. If. /// Default = 0, @@ -1313,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 a26c947ab34..af98749c4e5 100644 --- a/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/RemoveJob.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; @@ -10,18 +9,19 @@ using System.Management.Automation; using System.Management.Automation.Remoting; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { /// - /// This is the base class for job cmdlet and contains some helper functions + /// This is the base class for job cmdlet and contains some helper functions. /// public class JobCmdletBase : PSRemotingCmdlet { #region Strings - //Parametersets used by job cmdlets + // Parametersets used by job cmdlets internal const string JobParameterSet = "JobParameterSet"; internal const string InstanceIdParameterSet = "InstanceIdParameterSet"; internal const string SessionIdParameterSet = "SessionIdParameterSet"; @@ -30,7 +30,7 @@ public class JobCmdletBase : PSRemotingCmdlet internal const string CommandParameterSet = "CommandParameterSet"; internal const string FilterParameterSet = "FilterParameterSet"; - //common parameter names + // common parameter names internal const string JobParameter = "Job"; internal const string InstanceIdParameter = "InstanceId"; internal const string SessionIdParameter = "SessionId"; @@ -44,15 +44,14 @@ public class JobCmdletBase : PSRemotingCmdlet #region Job Matches /// - /// Find the jobs in repository which match matching the specified names + /// Find the jobs in repository which match matching the specified names. /// - /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// write error if no match is found - /// check if this job can be removed - /// recurse and check in child jobs - /// list of matching jobs + /// in list (an empty list is returned). + /// Write error if no match is found. + /// Check if this job can be removed. + /// Recurse and check in child jobs. + /// List of matching jobs. internal List FindJobsMatchingByName( bool recurse, bool writeobject, @@ -62,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) + foreach (string name in _names) { if (string.IsNullOrEmpty(name)) + { continue; + } // search all jobs in repository. bool jobFound = false; @@ -95,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)); @@ -120,10 +127,10 @@ private bool CheckIfJob2CanBeRemoved(bool checkForRemove, string parameterName, return true; } - private bool FindJobsMatchingByNameHelper(List matches, IList jobsToSearch, String name, + private bool FindJobsMatchingByNameHelper(List matches, IList jobsToSearch, string name, Hashtable duplicateDetector, bool recurse, bool writeobject, bool checkIfJobCanBeRemoved) { - Dbg.Assert(!String.IsNullOrEmpty(name), "Caller should ensure that name is not null or empty"); + Dbg.Assert(!string.IsNullOrEmpty(name), "Caller should ensure that name is not null or empty"); bool jobFound = false; @@ -137,12 +144,13 @@ private bool FindJobsMatchingByNameHelper(List matches, IList jobsToSe { continue; } + duplicateDetector.Add(job.Id, job.Id); // check if the job is available in any of the // top level jobs - //if (String.Equals(job.Name, name, StringComparison.OrdinalIgnoreCase)) + // if (string.Equals(job.Name, name, StringComparison.OrdinalIgnoreCase)) if (pattern.IsMatch(job.Name)) { jobFound = true; @@ -157,7 +165,7 @@ private bool FindJobsMatchingByNameHelper(List matches, IList jobsToSe matches.Add(job); } } - //break; + // break; } // check if the job is available in any of the childjobs @@ -171,28 +179,30 @@ private bool FindJobsMatchingByNameHelper(List matches, IList jobsToSe jobFound = true; } } - } // foreach ... + } return jobFound; } /// - /// Find the jobs in repository which match the specified instanceid + /// Find the jobs in repository which match the specified instanceid. /// - /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// write error if no match is found - /// check if this job can be removed - /// look in all child jobs - /// list of matching jobs + /// in list (an empty list is returned). + /// Write error if no match is found. + /// Check if this job can be removed. + /// Look in all child jobs. + /// List of matching jobs. internal List FindJobsMatchingByInstanceId(bool recurse, bool writeobject, bool writeErrorOnNoMatch, bool checkIfJobCanBeRemoved) { List matches = new List(); Hashtable duplicateDetector = new Hashtable(); - if (_instanceIds == null) return matches; + if (_instanceIds == null) + { + return matches; + } foreach (Guid id in _instanceIds) { @@ -201,7 +211,7 @@ internal List FindJobsMatchingByInstanceId(bool recurse, bool writeobject, bool jobFound = FindJobsMatchingByInstanceIdHelper(matches, JobRepository.Jobs, id, duplicateDetector, recurse, writeobject, checkIfJobCanBeRemoved); - //TODO: optimize this to not search JobManager since matching by InstanceId is unique + // TODO: optimize this to not search JobManager since matching by InstanceId is unique // search all jobs in JobManager Job2 job2 = JobManager.GetJobByInstanceId(id, this, false, writeobject, recurse); @@ -215,9 +225,13 @@ internal List FindJobsMatchingByInstanceId(bool recurse, bool writeobject, matches.Add(job2); } } + jobFound = jobFound || job2Found; - if (jobFound || !writeErrorOnNoMatch) continue; + if (jobFound || !writeErrorOnNoMatch) + { + continue; + } Exception ex = PSTraceSource.NewArgumentException(InstanceIdParameter, RemotingErrorIdStrings.JobWithSpecifiedInstanceIdNotFound, @@ -266,6 +280,7 @@ private bool FindJobsMatchingByInstanceIdHelper(List matches, IList jo { matches.Add(job); } + break; } } @@ -293,20 +308,22 @@ private bool FindJobsMatchingByInstanceIdHelper(List matches, IList jo } /// - /// Find the jobs in repository which match the specified session ids + /// Find the jobs in repository which match the specified session ids. /// - /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// write error if no match is found - /// check if this job can be removed - /// look in child jobs as well - /// list of matching jobs + /// in list (an empty list is returned). + /// Write error if no match is found. + /// Check if this job can be removed. + /// Look in child jobs as well. + /// List of matching jobs. internal List FindJobsMatchingBySessionId(bool recurse, bool writeobject, bool writeErrorOnNoMatch, bool checkIfJobCanBeRemoved) { List matches = new List(); - if (_sessionIds == null) return matches; + if (_sessionIds == null) + { + return matches; + } Hashtable duplicateDetector = new Hashtable(); @@ -328,9 +345,13 @@ internal List FindJobsMatchingBySessionId(bool recurse, bool writeobject, b matches.Add(job2); } } + 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)); @@ -397,18 +418,20 @@ private bool FindJobsMatchingBySessionIdHelper(List matches, IList job } /// - /// Find the jobs in repository which match the specified command + /// Find the jobs in repository which match the specified command. /// - /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// list of matching jobs + /// in list (an empty list is returned). + /// List of matching jobs. internal List FindJobsMatchingByCommand( bool writeobject) { List matches = new List(); - if (_commands == null) return matches; + if (_commands == null) + { + return matches; + } List jobs = new List(); @@ -446,16 +469,16 @@ internal List FindJobsMatchingByCommand( } } } + return matches; } /// - /// Find the jobs in repository which match the specified state + /// Find the jobs in repository which match the specified state. /// - /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// list of matching jobs + /// in list (an empty list is returned). + /// List of matching jobs. internal List FindJobsMatchingByState( bool writeobject) { @@ -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) { @@ -487,6 +513,7 @@ internal List FindJobsMatchingByState( matches.Add(job); } } + return matches; } @@ -529,6 +556,7 @@ internal List FindJobsMatchingByFilter(bool writeobject) matches.Add(job); } } + return matches; } @@ -538,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. @@ -550,13 +578,16 @@ private bool FindJobsMatchingByFilterHelper(List matches, List jobsToS /// /// /// if true, method writes the object instead of returning it - /// in list (an empty list is returned). - /// if true, only jobs which can be removed will be checked + /// in list (an empty list is returned). + /// If true, only jobs which can be removed will be checked. /// 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) { @@ -572,18 +603,19 @@ internal List CopyJobsToList(Job[] jobs, bool writeobject, bool checkIfJobC } } } + return matches; } /// /// Checks that this job object can be removed. If not, writes an error record. /// - /// Job object to be removed + /// Job object to be removed. /// Name of the parameter which is associated with this job object. /// - /// Resource String in case of error - /// Parameters for resource message - /// true if object should be removed, else false + /// Resource String in case of error. + /// Parameters for resource message. + /// True if object should be removed, else false. private bool CheckJobCanBeRemoved(Job job, string parameterName, string resourceString, params object[] list) { if (job.IsFinishedState(job.JobStateInfo.State)) @@ -599,18 +631,19 @@ private bool CheckJobCanBeRemoved(Job job, string parameterName, string resource #region Parameters /// - /// Name of the jobs to retrieve + /// Name of the jobs to retrieve. /// [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true, ParameterSetName = JobCmdletBase.NameParameterSet)] [ValidateNotNullOrEmpty] - public String[] Name + public string[] Name { get { return _names; } + set { _names = value; @@ -618,13 +651,12 @@ public String[] Name } /// - /// /// - private String[] _names; + private string[] _names; /// /// InstanceIds for which job - /// need to be obtained + /// need to be obtained. /// [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true, @@ -636,6 +668,7 @@ public Guid[] InstanceId { return _instanceIds; } + set { _instanceIds = value; @@ -643,13 +676,12 @@ public Guid[] InstanceId } /// - /// /// private Guid[] _instanceIds; /// /// SessionId for which job - /// need to be obtained + /// need to be obtained. /// [Parameter(ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true, @@ -662,6 +694,7 @@ public virtual int[] Id { return _sessionIds; } + set { _sessionIds = value; @@ -669,7 +702,6 @@ public virtual int[] Id } /// - /// /// private int[] _sessionIds; @@ -685,6 +717,7 @@ public virtual JobState State { return _jobstate; } + set { _jobstate = value; @@ -692,7 +725,6 @@ public virtual JobState State } /// - /// /// private JobState _jobstate; @@ -702,12 +734,13 @@ public virtual JobState State [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = RemoveJobCommand.CommandParameterSet)] [ValidateNotNullOrEmpty] - public virtual String[] Command + public virtual string[] Command { get { return _commands; } + set { _commands = value; @@ -715,12 +748,11 @@ public virtual String[] Command } /// - /// /// - private String[] _commands; + private string[] _commands; /// - /// All the job objects matching the values in filter + /// All the job objects matching the values in filter. /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, @@ -730,6 +762,7 @@ public virtual String[] Command public virtual Hashtable Filter { get { return _filter; } + set { _filter = value; } } @@ -745,7 +778,7 @@ public virtual Hashtable Filter /// enabled. This is because jobs are based out of APIs /// and there can be other job implementations like /// eventing or WMI which are not based on PowerShell - /// remoting + /// remoting. /// protected override void BeginProcessing() { @@ -764,7 +797,7 @@ protected override void BeginProcessing() /// through get-psjob command. /// [Cmdlet(VerbsCommon.Remove, "Job", SupportsShouldProcess = true, DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113377")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096868")] [OutputType(typeof(Job), ParameterSetName = new string[] { JobCmdletBase.JobParameterSet })] public class RemoveJobCommand : JobCmdletBase, IDisposable { @@ -772,7 +805,7 @@ public class RemoveJobCommand : JobCmdletBase, IDisposable /// /// Specifies the Jobs objects which need to be - /// removed + /// removed. /// [Parameter(Mandatory = true, Position = 0, @@ -787,11 +820,13 @@ public Job[] Job { return _jobs; } + set { _jobs = value; } } + private Job[] _jobs; /// @@ -809,11 +844,13 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force = false; #endregion Parameters @@ -833,52 +870,61 @@ protected override void ProcessRecord() { listOfJobsToRemove = FindJobsMatchingByName(false, false, true, !_force); } + break; case InstanceIdParameterSet: { listOfJobsToRemove = FindJobsMatchingByInstanceId(true, false, true, !_force); } + break; case SessionIdParameterSet: { listOfJobsToRemove = FindJobsMatchingBySessionId(true, false, true, !_force); } + break; case CommandParameterSet: { listOfJobsToRemove = FindJobsMatchingByCommand(false); } + break; case StateParameterSet: { listOfJobsToRemove = FindJobsMatchingByState(false); } + break; case FilterParameterSet: { listOfJobsToRemove = FindJobsMatchingByFilter(false); } + break; default: { listOfJobsToRemove = CopyJobsToList(_jobs, false, !_force); } + break; } - //Now actually remove the jobs + // 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)) @@ -912,10 +958,10 @@ protected override void ProcessRecord() RemoveJobAndDispose(job, job2 != null); } } - } // ProcessRecord + } /// - /// Wait for all the stop jobs to be completed + /// Wait for all the stop jobs to be completed. /// protected override void EndProcessing() { @@ -932,7 +978,7 @@ protected override void EndProcessing() } /// - /// release waiting for jobs + /// Release waiting for jobs. /// protected override void StopProcessing() { @@ -982,6 +1028,7 @@ private void HandleStopJobCompleted(object sender, AsyncCompletedEventArgs event { _pendingJobs.Remove(job.InstanceId); } + if (_needToCheckForWaitingJobs && _pendingJobs.Count == 0) releaseWait = true; } @@ -995,8 +1042,9 @@ 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 = new Dictionary>(); @@ -1008,7 +1056,6 @@ private void HandleStopJobCompleted(object sender, AsyncCompletedEventArgs event #region Dispose /// - /// /// public void Dispose() { @@ -1017,16 +1064,20 @@ public void Dispose() } /// - /// /// /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.StopJobCompleted -= pair.Value; } + _waitForJobs.Dispose(); } #endregion Dispose diff --git a/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs b/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs index 06c3c85cf8c..0f9d302d6e1 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ResumeJob.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -27,7 +26,7 @@ public class ResumeJobCommand : JobCmdletBase, IDisposable #region Parameters /// /// Specifies the Jobs objects which need to be - /// suspended + /// suspended. /// [Parameter(Mandatory = true, Position = 0, @@ -42,17 +41,18 @@ public Job[] Job { return _jobs; } + set { _jobs = value; } } + private Job[] _jobs; /// - /// /// - public override String[] Command + public override string[] Command { get { @@ -76,7 +76,7 @@ public override String[] Command /// protected override void ProcessRecord() { - //List of jobs to resume + // List of jobs to resume List jobsToResume = null; switch (ParameterSetName) @@ -85,36 +85,42 @@ protected override void ProcessRecord() { jobsToResume = FindJobsMatchingByName(true, false, true, false); } + break; case InstanceIdParameterSet: { jobsToResume = FindJobsMatchingByInstanceId(true, false, true, false); } + break; case SessionIdParameterSet: { jobsToResume = FindJobsMatchingBySessionId(true, false, true, false); } + break; case StateParameterSet: { jobsToResume = FindJobsMatchingByState(false); } + break; case FilterParameterSet: { jobsToResume = FindJobsMatchingByFilter(false); } + break; default: { jobsToResume = CopyJobsToList(_jobs, false, false); } + break; } @@ -179,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) @@ -205,6 +211,7 @@ var e in { _pendingJobs.Remove(job.InstanceId); } + if (_needToCheckForWaitingJobs && _pendingJobs.Count == 0) releaseWait = true; } @@ -224,20 +231,35 @@ 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(); } /// - /// /// protected override void StopProcessing() { @@ -249,7 +271,6 @@ protected override void StopProcessing() #region Dispose /// - /// /// public void Dispose() { @@ -258,16 +279,20 @@ public void Dispose() } /// - /// /// /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.ResumeJobCompleted -= pair.Value; } + _waitForJobs.Dispose(); } #endregion Dispose diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index fd468499dfb..9913ec64ced 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -1,23 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Security; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Collections; namespace Microsoft.PowerShell.Commands { /// /// This cmdlet start invocation of jobs in background. /// - [Cmdlet(VerbsLifecycle.Start, "Job", DefaultParameterSetName = StartJobCommand.ComputerNameParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113405")] + [Cmdlet(VerbsLifecycle.Start, "Job", DefaultParameterSetName = StartJobCommand.ComputerNameParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096796")] [OutputType(typeof(PSRemotingJob))] public class StartJobCommand : PSExecutionCmdlet, IDisposable { @@ -36,12 +36,15 @@ public class StartJobCommand : PSExecutionCmdlet, IDisposable /// [Parameter(Position = 0, Mandatory = true, ParameterSetName = StartJobCommand.DefinitionNameParameterSet)] + [ValidateTrustedData] [ValidateNotNullOrEmpty] public string DefinitionName { get { return _definitionName; } + set { _definitionName = value; } } + private string _definitionName; /// @@ -53,8 +56,10 @@ public string DefinitionName public string DefinitionPath { get { return _definitionPath; } + set { _definitionPath = value; } } + private string _definitionPath; /// @@ -67,12 +72,14 @@ public string DefinitionPath public string Type { get { return _definitionType; } + set { _definitionType = value; } } + private string _definitionType; /// - /// Friendly name for this job object + /// Friendly name for this job object. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] @@ -80,26 +87,28 @@ public string Type ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] - public virtual String Name + public virtual string Name { get { return _name; } + set { - if (!String.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { _name = value; } } } - private String _name; + + private string _name; /// /// Command to execute specified as a string. This can be a single /// cmdlet, an expression or anything that can be internally - /// converted into a ScriptBlock + /// converted into a ScriptBlock. /// /// This is used in the in process case with a /// "ValueFromPipelineProperty" enabled in order to maintain @@ -107,6 +116,7 @@ public virtual String Name [Parameter(Position = 0, Mandatory = true, ParameterSetName = StartJobCommand.ComputerNameParameterSet)] + [ValidateTrustedData] [Alias("Command")] public override ScriptBlock ScriptBlock { @@ -114,6 +124,7 @@ public override ScriptBlock ScriptBlock { return base.ScriptBlock; } + set { base.ScriptBlock = value; @@ -126,7 +137,7 @@ public override ScriptBlock ScriptBlock // which should not be part of Start-PSJob /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override PSSession[] Session { @@ -137,9 +148,9 @@ public override PSSession[] Session } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// - public override String[] ComputerName + public override string[] ComputerName { get { @@ -156,7 +167,7 @@ public override SwitchParameter EnableNetworkAccess } /// - /// Suppress SSHTransport + /// Suppress SSHTransport. /// public override SwitchParameter SSHTransport { @@ -164,7 +175,7 @@ public override SwitchParameter SSHTransport } /// - /// Suppress SSHConnection + /// Suppress SSHConnection. /// public override Hashtable[] SSHConnection { @@ -172,7 +183,7 @@ public override Hashtable[] SSHConnection } /// - /// Suppress UserName + /// Suppress UserName. /// public override string UserName { @@ -180,28 +191,45 @@ public override string UserName } /// - /// Suppress KeyFilePath + /// Suppress KeyFilePath. /// public override string KeyFilePath { get { return null; } } + /// + /// Suppress HostName. + /// + public override string[] HostName + { + get { return null; } + } + + /// + /// Suppress Subsystem. + /// + public override string Subsystem + { + get { return null; } + } + #endregion /// - /// Credential to use for this job + /// Credential to use for this job. /// [Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { get { return base.Credential; } + set { base.Credential = value; @@ -209,9 +237,9 @@ public override PSCredential Credential } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// - public override Int32 Port + public override int Port { get { @@ -220,7 +248,7 @@ public override Int32 Port } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override SwitchParameter UseSSL { @@ -231,14 +259,15 @@ public override SwitchParameter UseSSL } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// - public override String ConfigurationName + public override string ConfigurationName { get { return base.ConfigurationName; } + set { base.ConfigurationName = value; @@ -246,9 +275,9 @@ public override String ConfigurationName } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// - public override Int32 ThrottleLimit + public override int ThrottleLimit { get { @@ -257,9 +286,9 @@ public override Int32 ThrottleLimit } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// - public override String ApplicationName + public override string ApplicationName { get { @@ -268,7 +297,7 @@ public override String ApplicationName } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override Uri[] ConnectionUri { @@ -279,18 +308,20 @@ public override Uri[] ConnectionUri } /// - /// Filepath to execute as a script + /// Filepath to execute as a script. /// [Parameter( Position = 0, Mandatory = true, ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] + [ValidateTrustedData] public override string FilePath { get { return base.FilePath; } + set { base.FilePath = value; @@ -298,18 +329,20 @@ public override string FilePath } /// - /// Literal Filepath to execute as a script + /// Literal Filepath to execute as a script. /// [Parameter( Mandatory = true, ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] - [Alias("PSPath")] + [ValidateTrustedData] + [Alias("PSPath", "LP")] public string LiteralPath { get { return base.FilePath; } + set { base.FilePath = value; @@ -329,6 +362,7 @@ public override AuthenticationMechanism Authentication { return base.Authentication; } + set { base.Authentication = value; @@ -336,7 +370,7 @@ public override AuthenticationMechanism Authentication } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string CertificateThumbprint { @@ -344,6 +378,7 @@ public override string CertificateThumbprint { return base.CertificateThumbprint; } + set { base.CertificateThumbprint = value; @@ -351,7 +386,7 @@ public override string CertificateThumbprint } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override SwitchParameter AllowRedirection { @@ -362,7 +397,7 @@ public override SwitchParameter AllowRedirection } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override Guid[] VMId { @@ -373,7 +408,7 @@ public override Guid[] VMId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] VMName { @@ -384,7 +419,7 @@ public override string[] VMName } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override string[] ContainerId { @@ -395,7 +430,7 @@ public override string[] ContainerId } /// - /// Overriding to suppress this parameter + /// Overriding to suppress this parameter. /// public override SwitchParameter RunAsAdministrator { @@ -419,6 +454,7 @@ public override PSSessionOption SessionOption { return base.SessionOption; } + set { base.SessionOption = value; @@ -434,29 +470,34 @@ public override PSSessionOption SessionOption ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(Position = 1, ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] + [ValidateTrustedData] public virtual ScriptBlock InitializationScript { get { return _initScript; } + set { _initScript = value; } } + private ScriptBlock _initScript; /// - /// Launces the background job as a 32-bit process. This can be used on + /// Gets or sets an initial working directory for the powershell background job. + /// + [Parameter] + [ValidateNotNullOrWhiteSpace] + public string WorkingDirectory { get; set; } + + /// + /// Launches the background job as a 32-bit process. This can be used on /// 64-bit systems to launch a 32-bit wow process for the background job. /// [Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] - public virtual SwitchParameter RunAs32 - { - get { return _shouldRunAs32; } - set { _shouldRunAs32 = value; } - } - private bool _shouldRunAs32; + public virtual SwitchParameter RunAs32 { get; set; } /// - /// Powershell Version to execute the background job + /// Powershell Version to execute the background job. /// [Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)] @@ -464,17 +505,24 @@ public virtual SwitchParameter RunAs32 [ValidateNotNullOrEmpty] public virtual Version PSVersion { - get { return _psVersion; } - set + get { - PSSessionConfigurationCommandBase.CheckPSVersion(value); + return _psVersion; + } - // Check if specified version of PowerShell is installed - PSSessionConfigurationCommandUtilities.CheckIfPowerShellVersionIsInstalled(value); + set + { + // 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; } } + private Version _psVersion; /// @@ -486,9 +534,11 @@ public virtual Version PSVersion ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipeline = true, ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] + [ValidateTrustedData] public override PSObject InputObject { get { return base.InputObject; } + set { base.InputObject = value; } } @@ -498,11 +548,13 @@ public override PSObject InputObject [Parameter(ParameterSetName = StartJobCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = StartJobCommand.LiteralFilePathComputerNameParameterSet)] + [ValidateTrustedData] [Alias("Args")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public override Object[] ArgumentList + public override object[] ArgumentList { get { return base.ArgumentList; } + set { base.ArgumentList = value; } } @@ -514,10 +566,64 @@ public override Object[] ArgumentList /// 1. Set the throttling limit and reset operations complete /// 2. Create helper objects /// 3. For async case, write the async result object down the - /// pipeline + /// pipeline. /// protected override void BeginProcessing() { + if (!File.Exists(PowerShellProcessInstance.PwshExePath)) + { + // The pwsh executable file is not found under $PSHOME. + // This means that PowerShell is currently being hosted in another application, + // and 'Start-Job' is not supported by design in that scenario. + string message = StringUtil.Format( + RemotingErrorIdStrings.IPCPwshExecutableNotFound, + PowerShellProcessInstance.PwshExePath); + + var errorRecord = new ErrorRecord( + new PSNotSupportedException(message), + "IPCPwshExecutableNotFound", + ErrorCategory.NotInstalled, + PowerShellProcessInstance.PwshExePath); + + ThrowTerminatingError(errorRecord); + } + + if (RunAs32.IsPresent && Environment.Is64BitProcess) + { + // We cannot start a 32-bit 'pwsh' process from a 64-bit 'pwsh' installation. + string message = RemotingErrorIdStrings.RunAs32NotSupported; + var errorRecord = new ErrorRecord( + new PSNotSupportedException(message), + "RunAs32NotSupported", + ErrorCategory.InvalidOperation, + targetObject: null); + + ThrowTerminatingError(errorRecord); + } + + if (WorkingDirectory != null && !InvokeProvider.Item.IsContainer(WorkingDirectory)) + { + string message = StringUtil.Format(RemotingErrorIdStrings.StartJobWorkingDirectoryNotFound, WorkingDirectory); + var errorRecord = new ErrorRecord( + new DirectoryNotFoundException(message), + "DirectoryNotFoundException", + ErrorCategory.InvalidOperation, + targetObject: null); + + ThrowTerminatingError(errorRecord); + } + + if (WorkingDirectory == null) + { + try + { + WorkingDirectory = SessionState.Internal.CurrentLocation.Path; + } + catch (PSInvalidOperationException) + { + } + } + CommandDiscovery.AutoloadModulesWithJobSourceAdapters(this.Context, this.CommandOrigin); if (ParameterSetName == DefinitionNameParameterSet) @@ -530,7 +636,7 @@ protected override void BeginProcessing() SkipWinRMCheck = true; base.BeginProcessing(); - } // CoreBeginProcessing + } /// /// Create a throttle operation using NewProcessConnectionInfo @@ -540,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))) @@ -554,10 +660,10 @@ protected override void CreateHelpersForSpecifiedComputerNames() } NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(this.Credential); - connectionInfo.RunAs32 = _shouldRunAs32; connectionInfo.InitializationScript = _initScript; connectionInfo.AuthenticationMechanism = this.Authentication; connectionInfo.PSVersion = this.PSVersion; + connectionInfo.WorkingDirectory = this.WorkingDirectory; RemoteRunspace remoteRunspace = (RemoteRunspace)RunspaceFactory.CreateRunspace(connectionInfo, this.Host, Utils.GetTypeTableFromExecutionContextTLS()); @@ -617,6 +723,7 @@ protected override void ProcessRecord() resolvedPath = paths[0]; } + List jobs = JobManager.GetJobToStart(_definitionName, resolvedPath, _definitionType, this, false); if (jobs.Count == 0) @@ -672,7 +779,8 @@ protected override void ProcessRecord() helper.Pipeline.Input.Write(InputObject); } } - } // ProcessRecord + } + private bool _firstProcessRecord = true; /// @@ -683,14 +791,14 @@ protected override void EndProcessing() { // close the input stream on all the pipelines CloseAllInputStreams(); - } // EndProcessing + } #endregion Overrides #region IDisposable Overrides /// - /// Dispose the cmdlet + /// Dispose the cmdlet. /// public void Dispose() { @@ -699,16 +807,16 @@ public void Dispose() } /// - /// internal dispose method which does the actual disposing + /// Internal dispose method which does the actual disposing. /// - /// whether called from dispose or finalize + /// Whether called from dispose or finalize. private void Dispose(bool disposing) { if (disposing) { CloseAllInputStreams(); } - } // Dispose + } #endregion IDisposable Overrides } diff --git a/src/System.Management.Automation/engine/remoting/commands/StopJob.cs b/src/System.Management.Automation/engine/remoting/commands/StopJob.cs index 829d69b110c..c298b495bb9 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StopJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StopJob.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -17,14 +16,14 @@ namespace Microsoft.PowerShell.Commands /// This cmdlet stops the asynchronously invoked remote operations. /// [Cmdlet(VerbsLifecycle.Stop, "Job", SupportsShouldProcess = true, DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113413")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096795")] [OutputType(typeof(Job))] public class StopJobCommand : JobCmdletBase, IDisposable { #region Parameters /// /// Specifies the Jobs objects which need to be - /// removed + /// removed. /// [Parameter(Mandatory = true, Position = 0, @@ -39,11 +38,13 @@ public Job[] Job { return _jobs; } + set { _jobs = value; } } + private Job[] _jobs; /// @@ -56,17 +57,18 @@ public SwitchParameter PassThru { return _passThru; } + set { _passThru = value; } } + private bool _passThru; /// - /// /// - public override String[] Command + public override string[] Command { get { @@ -83,7 +85,7 @@ public override String[] Command /// protected override void ProcessRecord() { - //List of jobs to stop + // List of jobs to stop List jobsToStop = null; switch (ParameterSetName) @@ -92,36 +94,42 @@ protected override void ProcessRecord() { jobsToStop = FindJobsMatchingByName(true, false, true, false); } + break; case InstanceIdParameterSet: { jobsToStop = FindJobsMatchingByInstanceId(true, false, true, false); } + break; case SessionIdParameterSet: { jobsToStop = FindJobsMatchingBySessionId(true, false, true, false); } + break; case StateParameterSet: { jobsToStop = FindJobsMatchingByState(false); } + break; case FilterParameterSet: { jobsToStop = FindJobsMatchingByFilter(false); } + break; default: { jobsToStop = CopyJobsToList(_jobs, false, false); } + break; } @@ -129,11 +137,16 @@ protected override void ProcessRecord() foreach (Job job in jobsToStop) { - if (this.Stopping) return; + if (this.Stopping) + { + return; + } + if (job.IsFinishedState(job.JobStateInfo.State)) { continue; } + string targetString = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemovePSJobWhatIfTarget, job.Command, job.Id); @@ -167,7 +180,7 @@ protected override void ProcessRecord() } /// - /// Wait for all the stop jobs to be completed + /// Wait for all the stop jobs to be completed. /// protected override void EndProcessing() { @@ -190,7 +203,6 @@ protected override void EndProcessing() } /// - /// /// protected override void StopProcessing() { @@ -229,6 +241,7 @@ var e in { _pendingJobs.Remove(job.InstanceId); } + if (_needToCheckForWaitingJobs && _pendingJobs.Count == 0) releaseWait = true; } @@ -244,6 +257,7 @@ var e in private readonly HashSet _pendingJobs = new HashSet(); private readonly ManualResetEvent _waitForJobs = new ManualResetEvent(false); + private readonly Dictionary> _cleanUpActions = new Dictionary>(); @@ -258,7 +272,6 @@ var e in #region Dispose /// - /// /// public void Dispose() { @@ -267,16 +280,20 @@ public void Dispose() } /// - /// /// /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.StopJobCompleted -= pair.Value; } + _waitForJobs.Dispose(); } #endregion Dispose diff --git a/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs b/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs index e80f7d1b331..e454247ae9a 100644 --- a/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/SuspendJob.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -27,7 +26,7 @@ public class SuspendJobCommand : JobCmdletBase, IDisposable #region Parameters /// /// Specifies the Jobs objects which need to be - /// suspended + /// suspended. /// [Parameter(Mandatory = true, Position = 0, @@ -42,17 +41,18 @@ public Job[] Job { return _jobs; } + set { _jobs = value; } } + private Job[] _jobs; /// - /// /// - public override String[] Command + public override string[] Command { get { @@ -76,15 +76,16 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + private bool _force = false; /// - /// /// [Parameter()] public SwitchParameter Wait @@ -93,13 +94,14 @@ public SwitchParameter Wait { return _wait; } + set { _wait = value; } } - private bool _wait = false; + private bool _wait = false; #endregion Parameters @@ -110,7 +112,7 @@ public SwitchParameter Wait /// protected override void ProcessRecord() { - //List of jobs to suspend + // List of jobs to suspend List jobsToSuspend = null; switch (ParameterSetName) @@ -119,36 +121,42 @@ protected override void ProcessRecord() { jobsToSuspend = FindJobsMatchingByName(true, false, true, false); } + break; case InstanceIdParameterSet: { jobsToSuspend = FindJobsMatchingByInstanceId(true, false, true, false); } + break; case SessionIdParameterSet: { jobsToSuspend = FindJobsMatchingBySessionId(true, false, true, false); } + break; case StateParameterSet: { jobsToSuspend = FindJobsMatchingByState(false); } + break; case FilterParameterSet: { jobsToSuspend = FindJobsMatchingByFilter(false); } + break; default: { jobsToSuspend = CopyJobsToList(_jobs, false, false); } + break; } @@ -266,6 +274,7 @@ private void ProcessExecutionErrorsAndReleaseWaitHandle(Job job) // so if job doesn't present in the _pendingJobs then just return return; } + if (_needToCheckForWaitingJobs && _pendingJobs.Count == 0) releaseWait = true; } @@ -283,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) @@ -316,20 +325,35 @@ 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(); } /// - /// /// protected override void StopProcessing() { @@ -341,7 +365,6 @@ protected override void StopProcessing() #region Dispose /// - /// /// public void Dispose() { @@ -350,16 +373,20 @@ public void Dispose() } /// - /// /// /// protected void Dispose(bool disposing) { - if (!disposing) return; + if (!disposing) + { + return; + } + foreach (var pair in _cleanUpActions) { pair.Key.SuspendJobCompleted -= pair.Value; } + _waitForJobs.Dispose(); } #endregion Dispose diff --git a/src/System.Management.Automation/engine/remoting/commands/TestPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/TestPSSessionConfigurationFile.cs index f575206786a..3e013caa05b 100644 --- a/src/System.Management.Automation/engine/remoting/commands/TestPSSessionConfigurationFile.cs +++ b/src/System.Management.Automation/engine/remoting/commands/TestPSSessionConfigurationFile.cs @@ -1,14 +1,13 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Management.Automation.Remoting; +using System.Collections; using System.Collections.ObjectModel; using System.IO; -using System.Collections; +using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; namespace Microsoft.PowerShell.Commands { @@ -17,7 +16,7 @@ namespace Microsoft.PowerShell.Commands /// /// See Declarative Initial Session Config (DISC) /// - [Cmdlet(VerbsDiagnostic.Test, "PSSessionConfigurationFile", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217039")] + [Cmdlet(VerbsDiagnostic.Test, "PSSessionConfigurationFile", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096797")] [OutputType(typeof(bool))] public class TestPSSessionConfigurationFileCommand : PSCmdlet { @@ -30,8 +29,10 @@ public class TestPSSessionConfigurationFileCommand : PSCmdlet public string Path { get { return _path; } + set { _path = value; } } + private string _path; #endregion @@ -39,7 +40,6 @@ public string Path #region Overrides /// - /// /// protected override void ProcessRecord() { @@ -103,7 +103,6 @@ protected override void ProcessRecord() string scriptName; scriptInfo = DISCUtils.GetScriptInfoForFile(this.Context, filePath, out scriptName); - Hashtable configTable = null; try diff --git a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs index 9d6ff32aabe..6964a1154b5 100644 --- a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Linq; using System.Management.Automation; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell.Commands /// /// This cmdlet waits for job to complete. /// - [Cmdlet(VerbsLifecycle.Wait, "Job", DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113422")] + [Cmdlet(VerbsLifecycle.Wait, "Job", DefaultParameterSetName = JobCmdletBase.SessionIdParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096902")] [OutputType(typeof(Job))] public class WaitJobCommand : JobCmdletBase, IDisposable { @@ -23,7 +23,7 @@ public class WaitJobCommand : JobCmdletBase, IDisposable /// /// Specifies the Jobs objects which need to be - /// removed + /// removed. /// [Parameter(Mandatory = true, Position = 0, @@ -46,18 +46,20 @@ public class WaitJobCommand : JobCmdletBase, IDisposable /// [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, Int32.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int Timeout { get { return _timeoutInSeconds; } + set { _timeoutInSeconds = value; } } + private int _timeoutInSeconds = -1; // -1: infinite, this default is to wait for as long as it takes. /// @@ -68,7 +70,6 @@ public int Timeout public SwitchParameter Force { get; set; } /// - /// /// public override string[] Command { get; set; } #endregion Parameters @@ -104,10 +105,7 @@ private void InvokeEndProcessingAction() } // Invoke action outside lock. - if (endProcessingAction != null) - { - endProcessingAction(); - } + endProcessingAction?.Invoke(); } private void CleanUpEndProcessing() @@ -157,6 +155,7 @@ private void HandleJobStateChangedEvent(object source, JobStateEventArgs eventAr { _warnNotTerminal = true; } + _finishedJobs.Add(job); } else @@ -237,6 +236,7 @@ private List GetFinishedJobs() { jobsToOutput = _jobsToWaitFor.Where(j => ((!Force && j.IsPersistentState(j.JobStateInfo.State)) || (Force && j.IsFinishedState(j.JobStateInfo.State)))).ToList(); } + return jobsToOutput; } @@ -244,7 +244,7 @@ private Job GetOneBlockedJob() { lock (_jobTrackingLock) { - return _jobsToWaitFor.FirstOrDefault(j => j.JobStateInfo.State == JobState.Blocked); + return _jobsToWaitFor.Find(static j => j.JobStateInfo.State == JobState.Blocked); } } @@ -307,7 +307,7 @@ protected override void BeginProcessing() /// protected override void ProcessRecord() { - //List of jobs to wait + // List of jobs to wait List matches; switch (ParameterSetName) diff --git a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs index 2c5a4672b6d..22af9adf9ff 100644 --- a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.ObjectModel; @@ -9,6 +8,8 @@ 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; namespace Microsoft.PowerShell.Commands @@ -39,11 +40,10 @@ namespace Microsoft.PowerShell.Commands /// get-psession -VMName vmName -Name sessionName /// /// Get PSSessions from container. Optionally filter on state, session instanceid or session name. - /// get-psession -ContainerId containerId -InstanceId instanceId - /// + /// get-psession -ContainerId containerId -InstanceId instanceId. /// [Cmdlet(VerbsCommon.Get, "PSSession", DefaultParameterSetName = PSRunspaceCmdlet.NameParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135219", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096697", RemotingCapability = RemotingCapability.OwnedByCommand)] [OutputType(typeof(PSSession))] public class GetPSSessionCommand : PSRunspaceCmdlet, IDisposable { @@ -65,27 +65,32 @@ public class GetPSSessionCommand : PSRunspaceCmdlet, IDisposable ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] [ValidateNotNullOrEmpty] [Alias("Cn")] - public override String[] ComputerName { get; set; } + public override string[] ComputerName { get; set; } /// /// 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 - /// not specified as well, then "WSMAN" will be used + /// then the value specified in DEFAULTREMOTEAPPNAME will be used. If that's + /// not specified as well, then "WSMAN" will be used. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = GetPSSessionCommand.ComputerNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] - public String ApplicationName + public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } + set { _appName = ResolveAppName(value); } } - private String _appName; + + private string _appName; /// /// A complete URI(s) specified for the remote computer and shell to @@ -131,7 +136,7 @@ public String ApplicationName ParameterSetName = GetPSSessionCommand.VMNameParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = GetPSSessionCommand.VMNameInstanceIdParameterSet)] - public String ConfigurationName { get; set; } + public string ConfigurationName { get; set; } /// /// The AllowRedirection parameter enables the implicit redirection functionality. @@ -141,8 +146,10 @@ public String ApplicationName public SwitchParameter AllowRedirection { get { return _allowRedirection; } + set { _allowRedirection = value; } } + private bool _allowRedirection = false; /// @@ -155,10 +162,11 @@ 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; } + set { base.Name = value; } } @@ -182,6 +190,7 @@ public override string[] Name public override Guid[] InstanceId { get { return base.InstanceId; } + set { base.InstanceId = value; } } @@ -194,10 +203,14 @@ 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 { _psCredential = value; @@ -205,8 +218,8 @@ public PSCredential Credential PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private PSCredential _psCredential; + private PSCredential _psCredential; /// /// Use basic authentication to authenticate the user. @@ -217,7 +230,11 @@ public PSCredential Credential [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } + set { _authentication = value; @@ -225,8 +242,8 @@ public AuthenticationMechanism Authentication PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private AuthenticationMechanism _authentication; + private AuthenticationMechanism _authentication; /// /// Specifies the certificate thumbprint to be used to impersonate the user on the @@ -238,7 +255,11 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } + set { _thumbprint = value; @@ -246,8 +267,8 @@ public string CertificateThumbprint PSRemotingBaseCmdlet.ValidateSpecifiedAuthentication(Credential, CertificateThumbprint, Authentication); } } - private string _thumbprint; + private string _thumbprint; /// /// Port specifies the alternate port to be used in case the @@ -262,9 +283,8 @@ public string CertificateThumbprint /// [Parameter(ParameterSetName = GetPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] - [ValidateRange((Int32)1, (Int32)UInt16.MaxValue)] - public Int32 Port { get; set; } - + [ValidateRange((int)1, (int)ushort.MaxValue)] + public int Port { get; set; } /// /// This parameter suggests that the transport scheme to be used for @@ -287,8 +307,7 @@ public string CertificateThumbprint [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] - public Int32 ThrottleLimit { get; set; } = 0; - + public int ThrottleLimit { get; set; } = 0; /// /// Filters returned remote runspaces based on runspace state. @@ -305,7 +324,6 @@ public string CertificateThumbprint [Parameter(ParameterSetName = GetPSSessionCommand.VMNameInstanceIdParameterSet)] public SessionFilterState State { get; set; } - /// /// Session options. /// @@ -320,22 +338,33 @@ public string CertificateThumbprint #region Overrides /// - /// Resolves shellname + /// Resolves shellname. /// 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; } /// /// Get the list of runspaces from the global cache and write them /// down. If no computername or instance id is specified then - /// list all runspaces + /// list all runspaces. /// protected override void ProcessRecord() { @@ -356,7 +385,7 @@ protected override void ProcessRecord() { GetMatchingRunspaces(true, true, this.State, this.ConfigurationName); } - } // ProcessRecord + } /// /// End processing clean up. @@ -442,6 +471,7 @@ private Collection GetConnectionObjects() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -464,6 +494,7 @@ private Collection GetConnectionObjects() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -518,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 21ccb5fe99b..60ca0c35e50 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -9,12 +8,12 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; +using System.Management.Automation.Remoting.Client; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; -using System.Management.Automation.Remoting.Client; using System.Threading; -using Dbg = System.Management.Automation.Diagnostics; +using Dbg = System.Management.Automation.Diagnostics; namespace Microsoft.PowerShell.Commands { @@ -50,11 +49,10 @@ namespace Microsoft.PowerShell.Commands /// /// Create a runspace by connecting to port 8081 on server s1 and run shell named E12. /// This assumes that a shell by the name E12 exists on the remote server - /// $rs = New-PSSession -computername s1 -port 8061 -ShellName E12 + /// $rs = New-PSSession -computername s1 -port 8061 -ShellName E12. /// - /// [Cmdlet(VerbsCommon.New, "PSSession", DefaultParameterSetName = "ComputerName", - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135237", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096484", RemotingCapability = RemotingCapability.OwnedByCommand)] [OutputType(typeof(PSSession))] public class NewPSSessionCommand : PSRemotingBaseCmdlet, IDisposable { @@ -65,8 +63,7 @@ public class NewPSSessionCommand : PSRemotingBaseCmdlet, IDisposable /// computer(s). The following formats are supported: /// (a) Computer name /// (b) IPv4 address : 132.3.4.5 - /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18 - /// + /// (c) IPv6 address: 3ffe:8311:ffff:f70f:0:5efe:172.30.162.18. /// [Parameter(Position = 0, ValueFromPipeline = true, @@ -74,7 +71,7 @@ public class NewPSSessionCommand : PSRemotingBaseCmdlet, IDisposable ParameterSetName = NewPSSessionCommand.ComputerNameParameterSet)] [Alias("Cn")] [ValidateNotNullOrEmpty] - public override String[] ComputerName { get; set; } + public override string[] ComputerName { get; set; } /// /// Specifies the credentials of the user to impersonate in the @@ -91,10 +88,14 @@ 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 { base.Credential = value; @@ -103,7 +104,7 @@ public override PSCredential Credential /// /// The PSSession object describing the remote runspace - /// using which the specified cmdlet operation will be performed + /// using which the specified cmdlet operation will be performed. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true, @@ -116,19 +117,21 @@ public override PSSession[] Session { return _remoteRunspaceInfos; } + set { _remoteRunspaceInfos = value; } } + private PSSession[] _remoteRunspaceInfos; /// - /// Friendly names for the new PSSessions + /// Friendly names for the new PSSessions. /// - [Parameter()] + [Parameter] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public String[] Name { get; set; } + public string[] Name { get; set; } /// /// When set and in loopback scenario (localhost) this enables creation of WSMan @@ -160,7 +163,13 @@ public override PSSession[] Session ParameterSetName = NewPSSessionCommand.VMIdParameterSet)] [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NewPSSessionCommand.VMNameParameterSet)] - public String ConfigurationName { get; set; } + public string ConfigurationName { get; set; } + + /// + /// Gets or sets parameter value that creates connection to a Windows PowerShell process. + /// + [Parameter(Mandatory = true, ParameterSetName = NewPSSessionCommand.UseWindowsPowerShellParameterSet)] + public SwitchParameter UseWindowsPowerShell { get; set; } #endregion Parameters @@ -168,17 +177,16 @@ public override PSSession[] Session /// /// The throttle limit will be set here as it needs to be done - /// only once per cmdlet and not for every call + /// only once per cmdlet and not for every call. /// protected override void BeginProcessing() { base.BeginProcessing(); _operationsComplete.Reset(); _throttleManager.ThrottleLimit = ThrottleLimit; - _throttleManager.ThrottleComplete += - new EventHandler(HandleThrottleComplete); + _throttleManager.ThrottleComplete += HandleThrottleComplete; - if (String.IsNullOrEmpty(ConfigurationName)) + if (string.IsNullOrEmpty(ConfigurationName)) { if ((ParameterSetName == NewPSSessionCommand.ComputerNameParameterSet) || (ParameterSetName == NewPSSessionCommand.UriParameterSet)) @@ -188,17 +196,17 @@ protected override void BeginProcessing() } else { - // convert null to String.Empty for VM/Container session - ConfigurationName = String.Empty; + // convert null to string.Empty for VM/Container session + ConfigurationName = string.Empty; } } - } // BeginProcessing + } /// /// The runspace objects will be created using OpenAsync. /// At the end, the method will check if any runspace /// opened has already become available. If so, then it - /// will be written to the pipeline + /// will be written to the pipeline. /// protected override void ProcessRecord() { @@ -211,18 +219,21 @@ protected override void ProcessRecord() { remoteRunspaces = CreateRunspacesWhenRunspaceParameterSpecified(); } + break; case "Uri": { remoteRunspaces = CreateRunspacesWhenUriParameterSpecified(); } + break; case NewPSSessionCommand.ComputerNameParameterSet: { remoteRunspaces = CreateRunspacesWhenComputerNameParameterSpecified(); } + break; case NewPSSessionCommand.VMIdParameterSet: @@ -230,24 +241,44 @@ protected override void ProcessRecord() { remoteRunspaces = CreateRunspacesWhenVMParameterSpecified(); } + break; case NewPSSessionCommand.ContainerIdParameterSet: { remoteRunspaces = CreateRunspacesWhenContainerParameterSpecified(); } + break; case NewPSSessionCommand.SSHHostParameterSet: { remoteRunspaces = CreateRunspacesForSSHHostParameterSet(); } + break; case NewPSSessionCommand.SSHHostHashParameterSet: { remoteRunspaces = CreateRunspacesForSSHHostHashParameterSet(); } + + break; + + case NewPSSessionCommand.UseWindowsPowerShellParameterSet: + { + 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; default: @@ -255,8 +286,9 @@ protected override void ProcessRecord() Dbg.Assert(false, "Missing parameter set in switch statement"); remoteRunspaces = new List(); // added to avoid prefast warning } + break; - } // switch (ParameterSetName... + } foreach (RemoteRunspace remoteRunspace in remoteRunspaces) { @@ -265,8 +297,7 @@ protected override void ProcessRecord() OpenRunspaceOperation operation = new OpenRunspaceOperation(remoteRunspace); // HandleRunspaceStateChanged callback is added before ThrottleManager complete // callback handlers so HandleRunspaceStateChanged will always be called first. - operation.OperationComplete += - new EventHandler(HandleRunspaceStateChanged); + operation.OperationComplete += HandleRunspaceStateChanged; remoteRunspace.URIRedirectionReported += HandleURIDirectionReported; operations.Add(operation); } @@ -288,8 +319,8 @@ protected override void ProcessRecord() foreach (object streamObject in streamObjects) { WriteStreamObject((Action)streamObject); - } // foreach - }// ProcessRecord() + } + } /// /// OpenAsync would have been called from ProcessRecord. This method @@ -308,15 +339,15 @@ protected override void EndProcessing() if (!_stream.ObjectReader.EndOfPipeline) { - Object streamObject = _stream.ObjectReader.Read(); + object streamObject = _stream.ObjectReader.Read(); WriteStreamObject((Action)streamObject); } else { break; } - } // while ... - }// EndProcessing() + } + } /// /// This method is called when the user sends a stop signal to the @@ -324,7 +355,7 @@ protected override void EndProcessing() /// creating all the runspaces (basically the runspaces its /// waiting on OpenAsync is made available). However, when a stop /// signal is sent, CloseAsyn needs to be called to close all the - /// pending runspaces + /// pending runspaces. /// /// This is called from a separate thread so need to worry /// about concurrency issues @@ -338,7 +369,7 @@ protected override void StopProcessing() // for all the runspaces that have been submitted for opening // call StopOperation on each object and quit _throttleManager.StopAllOperations(); - }// StopProcessing() + } #endregion Cmdlet Overrides @@ -347,7 +378,7 @@ protected override void StopProcessing() /// /// Dispose method of IDisposable. Gets called in the following cases: /// 1. Pipeline explicitly calls dispose on cmdlets - /// 2. Called by the garbage collector + /// 2. Called by the garbage collector. /// public void Dispose() { @@ -361,13 +392,9 @@ public void Dispose() #region Private Methods /// - /// Adds forwarded events to the local queue + /// 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 @@ -378,29 +405,26 @@ 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); } /// - /// Handles state changes for Runspace + /// Handles state changes for Runspace. /// - /// Sender of this event + /// Sender of this event. /// Event information object which describes /// the event which triggered this method private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs stateEventArgs) { if (sender == null) { - throw PSTraceSource.NewArgumentNullException("sender"); + throw PSTraceSource.NewArgumentNullException(nameof(sender)); } if (stateEventArgs == null) { - throw PSTraceSource.NewArgumentNullException("stateEventArgs"); + throw PSTraceSource.NewArgumentNullException(nameof(stateEventArgs)); } RunspaceStateEventArgs runspaceStateEventArgs = @@ -412,7 +436,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s // since we got state changed event..we dont need to listen on // URI redirections anymore - if (null != remoteRunspace) + if (remoteRunspace != null) { remoteRunspace.URIRedirectionReported -= HandleURIDirectionReported; } @@ -431,15 +455,13 @@ 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); } } + break; case RunspaceState.Broken: @@ -452,7 +474,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s // having to mine through the error record details PSRemotingTransportException transException = reason as PSRemotingTransportException; - String errorDetails = null; + string errorDetails = null; int transErrorCode = 0; if (transException != null) { @@ -460,7 +482,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s transErrorCode = transException.ErrorCode; if (senderAsOp != null) { - String host = senderAsOp.OperatedRunspace.ConnectionInfo.ComputerName; + string host = senderAsOp.OperatedRunspace.ConnectionInfo.ComputerName; if (transException.ErrorCode == System.Management.Automation.Remoting.Client.WSManNativeApi.ERROR_WSMAN_REDIRECT_REQUESTED) @@ -480,11 +502,11 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s else { errorDetails = "[" + host + "] "; - if (!String.IsNullOrEmpty(transException.Message)) + if (!string.IsNullOrEmpty(transException.Message)) { errorDetails += transException.Message; } - else if (!String.IsNullOrEmpty(transException.TransportMessage)) + else if (!string.IsNullOrEmpty(transException.TransportMessage)) { errorDetails += transException.TransportMessage; } @@ -501,31 +523,29 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s if (senderAsOp != null) { - String host = senderAsOp.OperatedRunspace.ConnectionInfo.ComputerName; + string host = senderAsOp.OperatedRunspace.ConnectionInfo.ComputerName; errorDetails = "[" + host + "] " + protoException.Message; } } - 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); + errorDetails += System.Environment.NewLine + string.Format(System.Globalization.CultureInfo.CurrentCulture, RemotingErrorIdStrings.RemotingErrorNoLogonSessionExist); } + ErrorRecord errorRecord = new ErrorRecord(reason, remoteRunspace, fullyQualifiedErrorId, 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 @@ -553,6 +573,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s _toDispose.Add(remoteRunspace); } + break; case RunspaceState.Closed: @@ -562,15 +583,12 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s // called when there are open runspaces Uri connectionUri = WSManConnectionInfo.ExtractPropertyAsWsManConnectionInfo(remoteRunspace.ConnectionInfo, "ConnectionUri", null); - String message = + string message = GetMessage(RemotingErrorIdStrings.RemoteRunspaceClosed, (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); @@ -585,19 +603,17 @@ 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); } } } + break; - }// switch - } // HandleRunspaceStateChanged + } + } /// /// Creates the remote runspace objects when PSSession @@ -633,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; } @@ -647,10 +663,10 @@ private List CreateRunspacesWhenRunspaceParameterSpecified() WSManConnectionInfo originalWSManConnectionInfo = remoteRunspace.ConnectionInfo as WSManConnectionInfo; WSManConnectionInfo newWSManConnectionInfo = null; - if (null != originalWSManConnectionInfo) + if (originalWSManConnectionInfo != null) { newWSManConnectionInfo = originalWSManConnectionInfo.Copy(); - newWSManConnectionInfo.EnableNetworkAccess = (newWSManConnectionInfo.EnableNetworkAccess || EnableNetworkAccess) ? true : false; + newWSManConnectionInfo.EnableNetworkAccess = newWSManConnectionInfo.EnableNetworkAccess || EnableNetworkAccess; newConnectionInfo = newWSManConnectionInfo; } else @@ -693,23 +709,20 @@ 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); } } ++rsIndex; - } // foreach + } return remoteRunspaces; - } // CreateRunspacesWhenRunspaceParameterSpecified + } /// /// Creates the remote runspace objects when the URI parameter - /// is specified + /// is specified. /// private List CreateRunspacesWhenUriParameterSpecified() { @@ -766,14 +779,14 @@ private List CreateRunspacesWhenUriParameterSpecified() { WriteErrorCreateRemoteRunspaceFailed(e, ConnectionUri[i]); } - } // for... + } return remoteRunspaces; - } // CreateRunspacesWhenUriParameterSpecified + } /// /// Creates the remote runspace objects when the ComputerName parameter - /// is specified + /// is specified. /// private List CreateRunspacesWhenComputerNameParameterSpecified() { @@ -781,7 +794,7 @@ private List CreateRunspacesWhenComputerNameParameterSpecified() new List(); // Resolve all the machine names - String[] resolvedComputerNames; + string[] resolvedComputerNames; ResolveComputerNames(ComputerName, out resolvedComputerNames); @@ -808,6 +821,7 @@ private List CreateRunspacesWhenComputerNameParameterSpecified() { connectionInfo.Credential = Credential; } + connectionInfo.AuthenticationMechanism = Authentication; UpdateConnectionInfo(connectionInfo); @@ -829,20 +843,17 @@ 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); } - }// end of for + } return remoteRunspaces; - }// CreateRunspacesWhenComputerNameParameterSpecified + } /// /// Creates the remote runspace objects when the VMId or VMName parameter - /// is specified + /// is specified. /// private List CreateRunspacesWhenVMParameterSpecified() { @@ -883,7 +894,7 @@ private List CreateRunspacesWhenVMParameterSpecified() ThrowTerminatingError( new ErrorRecord( new ArgumentException(RemotingErrorIdStrings.HyperVModuleNotAvailable), - PSRemotingErrorId.HyperVModuleNotAvailable.ToString(), + nameof(PSRemotingErrorId.HyperVModuleNotAvailable), ErrorCategory.NotInstalled, null)); @@ -901,7 +912,7 @@ private List CreateRunspacesWhenVMParameterSpecified() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMIdNotSingle, this.VMId[index].ToString(null))), - PSRemotingErrorId.InvalidVMIdNotSingle.ToString(), + nameof(PSRemotingErrorId.InvalidVMIdNotSingle), ErrorCategory.InvalidArgument, null)); @@ -915,7 +926,7 @@ private List CreateRunspacesWhenVMParameterSpecified() new ErrorRecord( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMNameNotSingle, this.VMName[index])), - PSRemotingErrorId.InvalidVMNameNotSingle.ToString(), + nameof(PSRemotingErrorId.InvalidVMNameNotSingle), ErrorCategory.InvalidArgument, null)); @@ -930,13 +941,13 @@ 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( new ArgumentException(GetMessage(RemotingErrorIdStrings.InvalidVMState, this.VMName[index])), - PSRemotingErrorId.InvalidVMState.ToString(), + nameof(PSRemotingErrorId.InvalidVMState), ErrorCategory.InvalidArgument, null)); @@ -982,10 +993,10 @@ private List CreateRunspacesWhenVMParameterSpecified() ResolvedComputerNames = this.VMName; return remoteRunspaces; - }// CreateRunspacesWhenVMParameterSpecified + } /// - /// Creates the remote runspace objects when the ContainerId parameter is specified + /// Creates the remote runspace objects when the ContainerId parameter is specified. /// private List CreateRunspacesWhenContainerParameterSpecified() { @@ -1059,36 +1070,41 @@ private List CreateRunspacesWhenContainerParameterSpecified() ResolvedComputerNames = resolvedNameList.ToArray(); return remoteRunspaces; - }// CreateRunspacesWhenContainerParameterSpecified + } /// - /// CreateRunspacesForSSHHostParameterSet + /// CreateRunspacesForSSHHostParameterSet. /// /// private List CreateRunspacesForSSHHostParameterSet() { // Resolve all the machine names - String[] resolvedComputerNames; + string[] resolvedComputerNames; + ResolveComputerNames(HostName, out resolvedComputerNames); - ValidateComputerName(resolvedComputerNames); var remoteRunspaces = new List(); int index = 0; foreach (var computerName in resolvedComputerNames) { + ParseSshHostName(computerName, out string host, out string userName, out int port); + var sshConnectionInfo = new SSHConnectionInfo( - this.UserName, - computerName, + userName, + host, this.KeyFilePath, - this.Port); + port, + Subsystem, + ConnectingTimeout, + Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); string rsName = GetRunspaceName(index, out int rsIdUnused); index++; - remoteRunspaces.Add(RunspaceFactory.CreateRunspace( connectionInfo : sshConnectionInfo, - host : this.Host, - typeTable : typeTable, - applicationArguments : null, - name : rsName) as RemoteRunspace); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(connectionInfo: sshConnectionInfo, + host: this.Host, + typeTable: typeTable, + applicationArguments: null, + name: rsName) as RemoteRunspace); } return remoteRunspaces; @@ -1105,20 +1121,46 @@ private List CreateRunspacesForSSHHostHashParameterSet() sshConnection.UserName, sshConnection.ComputerName, sshConnection.KeyFilePath, - sshConnection.Port); + sshConnection.Port, + sshConnection.Subsystem, + sshConnection.ConnectingTimeout, + sshConnection.Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); string rsName = GetRunspaceName(index, out int rsIdUnused); index++; - remoteRunspaces.Add(RunspaceFactory.CreateRunspace( connectionInfo : sshConnectionInfo, - host : this.Host, - typeTable : typeTable, - applicationArguments : null, - name : rsName) as RemoteRunspace); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(connectionInfo: sshConnectionInfo, + host: this.Host, + typeTable: typeTable, + applicationArguments: null, + name: rsName) as RemoteRunspace); } return remoteRunspaces; } + /// + /// Helper method to create remote runspace based on UseWindowsPowerShell parameter set. + /// + /// Remote runspace that was created. + private List CreateRunspacesForUseWindowsPowerShellParameterSet() + { + var remoteRunspaces = new List(); + + NewProcessConnectionInfo connectionInfo = new NewProcessConnectionInfo(this.Credential); + connectionInfo.AuthenticationMechanism = this.Authentication; +#if !UNIX + connectionInfo.PSVersion = new Version(5, 1); +#endif + var typeTable = TypeTable.LoadDefaultTypeFiles(); + string runspaceName = GetRunspaceName(0, out int runspaceIdUnused); + remoteRunspaces.Add(RunspaceFactory.CreateRunspace(connectionInfo: connectionInfo, + host: this.Host, + typeTable: typeTable, + applicationArguments: null, + name: runspaceName) as RemoteRunspace); + return remoteRunspaces; + } + /// /// Helper method to either get a user supplied runspace/session name /// or to generate one along with a unique Id. @@ -1145,7 +1187,7 @@ private string GetRunspaceName(int rsIndex, out int rsId) /// /// Internal dispose method which does the actual - /// dispose operations and finalize suppressions + /// dispose operations and finalize suppressions. /// /// Whether method is called /// from Dispose or destructor @@ -1159,7 +1201,7 @@ protected void Dispose(bool disposing) _operationsComplete.WaitOne(); _operationsComplete.Dispose(); - _throttleManager.ThrottleComplete -= new EventHandler(HandleThrottleComplete); + _throttleManager.ThrottleComplete -= HandleThrottleComplete; _throttleManager = null; foreach (RemoteRunspace remoteRunspace in _toDispose) @@ -1178,12 +1220,12 @@ protected void Dispose(bool disposing) _stream.Dispose(); } - } // Dispose + } /// - /// Handles the throttling complete event of the throttle manager + /// Handles the throttling complete event of the throttle manager. /// - /// sender of this event + /// Sender of this event. /// private void HandleThrottleComplete(object sender, EventArgs eventArgs) { @@ -1191,15 +1233,15 @@ private void HandleThrottleComplete(object sender, EventArgs eventArgs) _stream.ObjectWriter.Close(); _operationsComplete.Set(); - } // HandleThrottleComplete + } /// /// Writes an error record specifying that creation of remote runspace - /// failed + /// failed. /// /// exception which is causing this error record /// to be written - /// Uri which caused this exception + /// Uri which caused this exception. private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) { Dbg.Assert(e is UriFormatException || e is InvalidOperationException || @@ -1211,22 +1253,19 @@ 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); - } // WriteErrorCreateRemoteRunspaceFailed + } #endregion Private Methods #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 @@ -1235,22 +1274,22 @@ 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 - }// NewRunspace + } #region Helper Classes /// /// Class that implements the IThrottleOperation in turn wrapping the - /// opening of a runspace asynchronously within it + /// opening of a runspace asynchronously within it. /// internal class OpenRunspaceOperation : IThrottleOperation, IDisposable { @@ -1259,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; } @@ -1268,12 +1307,11 @@ internal OpenRunspaceOperation(RemoteRunspace runspace) _startComplete = true; _stopComplete = true; OperatedRunspace = runspace; - OperatedRunspace.StateChanged += - new EventHandler(HandleRunspaceStateChanged); + OperatedRunspace.StateChanged += HandleRunspaceStateChanged; } /// - /// Opens the runspace asynchronously + /// Opens the runspace asynchronously. /// internal override void StartOperation() { @@ -1281,11 +1319,12 @@ internal override void StartOperation() { _startComplete = false; } + OperatedRunspace.OpenAsync(); - } // StartOperation + } /// - /// Closes the runspace already opened asynchronously + /// Closes the runspace already opened asynchronously. /// internal override void StopOperation() { @@ -1324,7 +1363,8 @@ 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 { add @@ -1334,6 +1374,7 @@ internal override event EventHandler OperationComplete _internalCallbacks.Add(value); } } + remove { lock (_internalCallbacks) @@ -1353,14 +1394,14 @@ 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 (hang). + /// 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 /// an exception since this prevents other event handlers in the chain from /// being called. /// - /// Source of this event + /// Source of this event. /// object describing state information of the /// runspace private void HandleRunspaceStateChanged(object source, RunspaceStateEventArgs stateEventArgs) @@ -1413,6 +1454,7 @@ private void FireEvent(OperationStateEventArgs operationStateEventArgs) copyCallbacks = new EventHandler[_internalCallbacks.Count]; _internalCallbacks.CopyTo(copyCallbacks); } + foreach (var callbackDelegate in copyCallbacks) { // Ensure all callbacks get called to prevent ThrottleManager from not responding. @@ -1437,7 +1479,7 @@ public void Dispose() GC.SuppressFinalize(this); } - } // OpenRunspaceOperation + } #endregion Helper Classes -}//End namespace +} diff --git a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs index eff6d69aed9..3e2603a36fe 100644 --- a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs +++ b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs @@ -1,13 +1,13 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; + using Microsoft.Win32; -using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { @@ -17,11 +17,11 @@ namespace Microsoft.PowerShell.Commands internal enum RunspaceParameterSet { /// - /// Use ComputerName parameter set + /// Use ComputerName parameter set. /// ComputerName, /// - /// Use Runspace Parameter set + /// Use Runspace Parameter set. /// Runspace } @@ -32,22 +32,16 @@ internal enum RunspaceParameterSet /// internal static class RemotingCommandUtil { - /// - /// The existence of the following registry confirms that the host machine is a WinPE - /// HKLM\System\CurrentControlSet\Control\MiniNT - /// - internal static string WinPEIdentificationRegKey = @"System\CurrentControlSet\Control\MiniNT"; - internal static bool HasRepeatingRunspaces(PSSession[] runspaceInfos) { if (runspaceInfos == null) { - throw PSTraceSource.NewArgumentNullException("runspaceInfos"); + throw PSTraceSource.NewArgumentNullException(nameof(runspaceInfos)); } if (runspaceInfos.GetLength(0) == 0) { - throw PSTraceSource.NewArgumentException("runspaceInfos"); + throw PSTraceSource.NewArgumentException(nameof(runspaceInfos)); } for (int i = 0; i < runspaceInfos.GetLength(0); i++) @@ -71,12 +65,12 @@ internal static bool ExceedMaximumAllowableRunspaces(PSSession[] runspaceInfos) { if (runspaceInfos == null) { - throw PSTraceSource.NewArgumentNullException("runspaceInfos"); + throw PSTraceSource.NewArgumentNullException(nameof(runspaceInfos)); } if (runspaceInfos.GetLength(0) == 0) { - throw PSTraceSource.NewArgumentException("runspaceInfos"); + throw PSTraceSource.NewArgumentException(nameof(runspaceInfos)); } return false; @@ -84,7 +78,7 @@ internal static bool ExceedMaximumAllowableRunspaces(PSSession[] runspaceInfos) /// /// Checks the prerequisites for a cmdlet and terminates if the cmdlet - /// is not valid + /// is not valid. /// internal static void CheckRemotingCmdletPrerequisites() { @@ -93,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,7 +159,7 @@ internal static void CheckHostRemotingPrerequisites() if (isWinPEHost) { // WSMan is not supported on this platform - //throw new InvalidOperationException( + // throw new InvalidOperationException( // "WinPE does not support Windows PowerShell remoting"); ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.WinPERemotingNotSupported)), null, ErrorCategory.InvalidOperation, null); throw new InvalidOperationException(errorRecord.ToString()); @@ -173,4 +167,3 @@ internal static void CheckHostRemotingPrerequisites() } } } - diff --git a/src/System.Management.Automation/engine/remoting/commands/removerunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/removerunspacecommand.cs index 5ee1ea92225..d4cf3877fd4 100644 --- a/src/System.Management.Automation/engine/remoting/commands/removerunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/removerunspacecommand.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections.Generic; @@ -28,18 +27,18 @@ namespace Microsoft.PowerShell.Commands /// /// Remove the runspace specified (no need for a parameter name) /// $runspace = New-PSSession - /// Remove-PSSession $runspace + /// Remove-PSSession $runspace. /// [Cmdlet(VerbsCommon.Remove, "PSSession", SupportsShouldProcess = true, DefaultParameterSetName = RemovePSSessionCommand.IdParameterSet, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135250", RemotingCapability = RemotingCapability.OwnedByCommand)] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096963", RemotingCapability = RemotingCapability.OwnedByCommand)] public class RemovePSSessionCommand : PSRunspaceCmdlet { #region Parameters /// /// Specifies the PSSession objects which need to be - /// removed + /// removed. /// [Parameter(Mandatory = true, Position = 0, @@ -92,7 +91,7 @@ public class RemovePSSessionCommand : PSRunspaceCmdlet /// 1. If runspace is in opened state, /// a. stop any execution in process in the runspace /// b. close the runspace - /// 2. Remove the runspace from the global cache + /// 2. Remove the runspace from the global cache. /// protected override void ProcessRecord() { @@ -112,11 +111,13 @@ protected override void ProcessRecord() toRemove = matches.Values; } + break; case RemovePSSessionCommand.SessionParameterSet: { toRemove = Session; } + break; default: Diagnostics.Assert(false, "Invalid Parameter Set"); diff --git a/src/System.Management.Automation/engine/remoting/commands/runspacerepository.cs b/src/System.Management.Automation/engine/remoting/commands/runspacerepository.cs index 8bd4b8fe0d5..3f1f20b4c6c 100644 --- a/src/System.Management.Automation/engine/remoting/commands/runspacerepository.cs +++ b/src/System.Management.Automation/engine/remoting/commands/runspacerepository.cs @@ -1,22 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Runspaces; - namespace System.Management.Automation { /// - /// Repository of remote runspaces available in a local runspace + /// Repository of remote runspaces available in a local runspace. /// public class RunspaceRepository : Repository { #region Public Methods /// - /// Collection of runspaces available + /// Collection of runspaces available. /// public List Runspaces { @@ -31,14 +29,14 @@ public List Runspaces #region Internal Methods /// - /// Internal constructor + /// Internal constructor. /// internal RunspaceRepository() : base("runspace") { } /// - /// Gets a key for the specified item + /// Gets a key for the specified item. /// /// /// diff --git a/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs b/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs index 996a1793833..88a329cb6b6 100644 --- a/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs +++ b/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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 d6abab8e3e8..9dc073e6616 100644 --- a/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs +++ b/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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. @@ -104,7 +104,7 @@ internal T GetResponse(long callId, T defaultValue) // return caller specified value in case there is no response // from remote end. - if (null == remoteHostResponse) + if (remoteHostResponse == null) { return defaultValue; } diff --git a/src/System.Management.Automation/engine/remoting/common/Indexer.cs b/src/System.Management.Automation/engine/remoting/common/Indexer.cs index 5387386967e..019eb9dfce4 100644 --- a/src/System.Management.Automation/engine/remoting/common/Indexer.cs +++ b/src/System.Management.Automation/engine/remoting/common/Indexer.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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 2ab00025256..a5ae0dab19c 100644 --- a/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs +++ b/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; @@ -21,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 0088f2f136f..989ad33e987 100644 --- a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs +++ b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs @@ -1,13 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Internal { /// - /// Defines enumerations for the keywords + /// Defines enumerations for the keywords. /// [Flags] internal enum PSKeyword : ulong @@ -26,7 +25,7 @@ internal enum PSKeyword : ulong } /// - /// Define enumerations for levels + /// Define enumerations for levels. /// internal enum PSLevel : byte { @@ -40,7 +39,7 @@ internal enum PSLevel : byte } /// - /// Defines enumerations for op codes + /// Defines enumerations for op codes. /// internal enum PSOpcode : byte { @@ -65,7 +64,7 @@ internal enum PSOpcode : byte } /// - /// Defines enumerations for event ids + /// Defines enumerations for event ids. /// /// add an entry for a new event that you /// add to the manifest. Set it to the same value @@ -159,6 +158,16 @@ 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, @@ -193,7 +202,7 @@ internal enum PSEventId : int } /// - /// Defines enumerations for channels + /// Defines enumerations for channels. /// /// /// On Windows, PSChannel is the numeric channel id value. @@ -217,7 +226,7 @@ internal enum PSChannel : byte #endif /// - /// Defines enumerations for tasks + /// Defines enumerations for tasks. /// internal enum PSTask : int { @@ -233,13 +242,18 @@ internal enum PSTask : int ProviderStart = 0x68, ProviderStop = 0x69, ExecutePipeline = 0x6A, + ExperimentalFeature = 0x6B, + Telemetry = 0x6C, ScheduledJob = 0x6E, NamedPipe = 0x6F, - ISEOperation = 0x78 + ISEOperation = 0x78, + Amsi = 0X82, + WDAC = 0x83, + WDACAudit = 0x84 } /// - /// Defines enumerations for version + /// Defines enumerations for version. /// /// all messages in V2 timeframe /// should be of version 1 diff --git a/src/System.Management.Automation/engine/remoting/common/PSSessionConfigurationTypeOption.cs b/src/System.Management.Automation/engine/remoting/common/PSSessionConfigurationTypeOption.cs index 1c1728d5e52..616ca252d07 100644 --- a/src/System.Management.Automation/engine/remoting/common/PSSessionConfigurationTypeOption.cs +++ b/src/System.Management.Automation/engine/remoting/common/PSSessionConfigurationTypeOption.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Management.Automation.Runspaces; @@ -14,7 +13,7 @@ namespace System.Management.Automation public abstract class PSSessionTypeOption { /// - /// Returns a xml formatted data that represents the options + /// Returns a xml formatted data that represents the options. /// /// protected internal virtual string ConstructPrivateData() @@ -82,25 +81,19 @@ internal virtual Hashtable ConstructQuotasAsHashtable() throw new NotImplementedException(); } - internal void LoadFromDefaults(PSSessionType sessionType) - { - LoadFromDefaults(sessionType, false); - } - /// /// Sets all the values to default values. /// If keepAssigned is true only those values are set /// which are unassigned. /// - /// - /// - protected internal virtual void LoadFromDefaults(PSSessionType sessionType, bool keepAssigned) + /// Keep old values. + protected internal virtual void LoadFromDefaults(bool keepAssigned) { throw new NotImplementedException(); } /// - /// Clone from ICloneable + /// Clone from ICloneable. /// public object Clone() { diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs index 1f6e49e34a8..8d53adf0ca8 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -1,23 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Tracing; using System.IO; +using System.Management.Automation.Tracing; using System.Net; 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; @@ -45,13 +47,15 @@ public override System.Net.Sockets.AddressFamily AddressFamily public Guid VmId { get { return _vmId; } + set { _vmId = value; } } public Guid ServiceId { get { return _serviceId; } - set { _vmId = value; } + + set { _serviceId = value; } } #endregion @@ -77,7 +81,7 @@ public override EndPoint Create(SocketAddress SockAddr) return endpoint; } - public override bool Equals(Object obj) + public override bool Equals(object obj) { HyperVSocketEndPoint endpoint = (HyperVSocketEndPoint)obj; @@ -136,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 @@ -173,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); @@ -240,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. @@ -255,8 +275,13 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) // if (listenSocket != null) { - try { listenSocket.Dispose(); } - catch (ObjectDisposedException) { } + try + { + listenSocket.Dispose(); + } + catch (ObjectDisposedException) + { + } } } catch (Exception e) @@ -266,34 +291,41 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) if (ex != null) { - Dbg.Assert(false, "Unexpected error in RemoteSessionHyperVSocketServer."); + Dbg.Fail("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}", + errorMessage); throw new PSInvalidOperationException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure), ex, - PSRemotingErrorId.RemoteSessionHyperVSocketServerConstructorFailure.ToString(), + nameof(PSRemotingErrorId.RemoteSessionHyperVSocketServerConstructorFailure), ErrorCategory.InvalidOperation, null); } } - #endregion #region IDisposable /// - /// Dispose + /// Dispose. /// public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } + IsDisposed = true; } @@ -301,6 +333,7 @@ public void Dispose() { try { TextReader.Dispose(); } catch (ObjectDisposedException) { } + TextReader = null; } @@ -308,6 +341,7 @@ public void Dispose() { try { TextWriter.Dispose(); } catch (ObjectDisposedException) { } + TextWriter = null; } @@ -325,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 @@ -332,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 @@ -346,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 /// @@ -356,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. @@ -373,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. /// @@ -385,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; @@ -404,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; } } @@ -434,13 +606,17 @@ internal RemoteSessionHyperVSocketClient( #region IDisposable /// - /// Dispose + /// Dispose. /// public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } + IsDisposed = true; } @@ -448,6 +624,7 @@ public void Dispose() { try { TextReader.Dispose(); } catch (ObjectDisposedException) { } + TextReader = null; } @@ -455,6 +632,7 @@ public void Dispose() { try { TextWriter.Dispose(); } catch (ObjectDisposedException) { } + TextWriter = null; } @@ -475,13 +653,88 @@ 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. /// - /// The credential used for authentication - /// The configuration name of the PS session - /// Whether this is the first connection + /// The credential used for authentication. + /// The configuration name of the PS session. + /// Whether this is the first connection. public bool Connect( NetworkCredential networkCredential, string configurationName, @@ -495,107 +748,58 @@ public bool Connect( // if (isFirstConnection) { - if (String.IsNullOrEmpty(networkCredential.UserName)) + if (string.IsNullOrEmpty(networkCredential.UserName)) { throw new PSDirectException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidUsername)); } } - 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.Compare(responseString, "FAIL", StringComparison.Ordinal) == 0) + 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.Compare(responseString, "CONF", StringComparison.Ordinal) == 0) - { - 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."); } } @@ -607,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; } @@ -616,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 a2452581a73..fc5226a007e 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -1,19 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Tracing; -using System.Management.Automation.Internal; -using System.Management.Automation.Remoting.Server; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Pipes; -using System.Threading; +using System.Management.Automation.Internal; +using System.Management.Automation.Remoting.Server; +using System.Management.Automation.Tracing; +using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; -using System.Runtime.InteropServices; +using System.Threading; + using Microsoft.Win32.SafeHandles; -using System.Diagnostics.CodeAnalysis; + using Dbg = System.Diagnostics.Debug; namespace System.Management.Automation.Remoting @@ -25,9 +26,17 @@ internal static class NamedPipeUtils { #region Strings - internal const string DefaultAppDomainName = "DefaultAppDomain"; internal const string NamedPipeNamePrefix = "PSHost."; +#if UNIX + internal const string DefaultAppDomainName = "None"; + // This `CoreFxPipe` prefix is defined by CoreFx + internal const string NamedPipeNamePrefixSearch = "CoreFxPipe_PSHost*"; +#else + internal const string DefaultAppDomainName = "DefaultAppDomain"; internal const string NamedPipeNamePrefixSearch = "PSHost*"; +#endif + // On non-Windows, .NET named pipes are limited to up to 104 characters + internal const int MaxNamedPipeNameSize = 104; #endregion @@ -37,8 +46,8 @@ internal static class NamedPipeUtils /// Create a pipe name based on process information. /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName" /// - /// Process Id - /// Pipe name + /// Process Id. + /// Pipe name. internal static string CreateProcessPipeName( int procId) { @@ -50,8 +59,8 @@ internal static string CreateProcessPipeName( /// Create a pipe name based on process information. /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName" /// - /// Process object - /// Pipe name + /// Process object. + /// Pipe name. internal static string CreateProcessPipeName( System.Diagnostics.Process proc) { @@ -62,9 +71,9 @@ internal static string CreateProcessPipeName( /// Create a pipe name based on process Id and appdomain name information. /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName" /// - /// Process Id + /// Process Id. /// Name of process app domain to connect to. - /// Pipe name + /// Pipe name. internal static string CreateProcessPipeName( int procId, string appDomainName) @@ -76,16 +85,16 @@ internal static string CreateProcessPipeName( /// Create a pipe name based on process and appdomain name information. /// E.g., "PSHost.ProcessStartTime.ProcessId.DefaultAppDomain.ProcessName" /// - /// Process object + /// Process object. /// Name of process app domain to connect to. - /// Pipe name + /// Pipe name. internal static string CreateProcessPipeName( System.Diagnostics.Process proc, string appDomainName) { if (proc == null) { - throw new PSArgumentNullException("proc"); + throw new PSArgumentNullException(nameof(proc)); } if (string.IsNullOrEmpty(appDomainName)) @@ -93,23 +102,47 @@ internal static string CreateProcessPipeName( appDomainName = DefaultAppDomainName; } - return NamedPipeNamePrefix + - proc.StartTime.ToFileTime().ToString(CultureInfo.InvariantCulture) + "." + - proc.Id.ToString(CultureInfo.InvariantCulture) + "." + - CleanAppDomainNameForPipeName(appDomainName) + "." + - proc.ProcessName; + System.Text.StringBuilder pipeNameBuilder = new System.Text.StringBuilder(MaxNamedPipeNameSize); + pipeNameBuilder.Append(NamedPipeNamePrefix) + // The starttime is there to prevent another process easily guessing the pipe name + // and squatting on it. + // 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").AsSpan(1, 8)) +#else + .Append(proc.StartTime.ToFileTime().ToString(CultureInfo.InvariantCulture)) +#endif + .Append('.') + .Append(proc.Id.ToString(CultureInfo.InvariantCulture)) + .Append('.') + .Append(CleanAppDomainNameForPipeName(appDomainName)) + .Append('.') + .Append(proc.ProcessName); +#if UNIX + int charsToTrim = pipeNameBuilder.Length - MaxNamedPipeNameSize; + if (charsToTrim > 0) + { + // TODO: In the case the pipe name is truncated, the user cannot connect to it using the cmdlet + // unless we add a `-Force` type switch as it attempts to validate the current process name + // matches the process name in the pipe name + pipeNameBuilder.Remove(MaxNamedPipeNameSize + 1, charsToTrim); + } +#endif + + return pipeNameBuilder.ToString(); } private static string CleanAppDomainNameForPipeName(string appDomainName) { // Pipe names cannot contain the ':' character. Remove unwanted characters. - return appDomainName.Replace(":", "").Replace(" ", ""); + return appDomainName.Replace(":", string.Empty).Replace(" ", string.Empty); } /// /// Returns the current process AppDomain name. /// - /// AppDomain Name string + /// AppDomain Name string. internal static string GetCurrentAppDomainName() { #if CORECLR // There is only one AppDomain per application in CoreCLR, which would be the default @@ -123,7 +156,7 @@ internal static string GetCurrentAppDomainName() } /// - /// Native API for Named Pipes + /// Native API for Named Pipes. /// internal static class NamedPipeNative { @@ -156,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 @@ -199,7 +212,7 @@ internal class SECURITY_ATTRIBUTES public bool InheritHandle; /// - /// Initializes a new instance of the SECURITY_ATTRIBUTES class + /// Initializes a new instance of the SECURITY_ATTRIBUTES class. /// public SECURITY_ATTRIBUTES() { @@ -231,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 } @@ -267,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 @@ -291,8 +274,8 @@ private ListenerEndedEventArgs() { } /// /// Constructor. /// - /// Listener end reason - /// Restart listener + /// Listener end reason. + /// Restart listener. public ListenerEndedEventArgs( Exception reason, bool restartListener) @@ -310,24 +293,29 @@ public ListenerEndedEventArgs( /// having correct access restrictions, and provides a listener /// thread loop. /// - internal sealed class RemoteSessionNamedPipeServer : IDisposable + 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; + private const int _maxPipePathLengthLinux = 108; + 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; + // Optional custom server. + private static RemoteSessionNamedPipeServer _customNamedPipeServer; + // Access mask constant taken from PipeSecurity access rights and is equivalent to // PipeAccessRights.FullControl. - // See: https://msdn.microsoft.com/en-us/library/vstudio/bb348408(v=vs.100).aspx + // See: https://msdn.microsoft.com/library/vstudio/bb348408(v=vs.100).aspx // private const int _pipeAccessMaskFullControl = 0x1f019f; @@ -338,37 +326,37 @@ internal sealed class RemoteSessionNamedPipeServer : IDisposable /// /// Returns the Named Pipe stream object. /// - public NamedPipeServerStream Stream { get; } + internal NamedPipeServerStream Stream { get; } /// /// Returns the Named Pipe name. /// - public string PipeName { get; } + internal string PipeName { get; } /// /// Returns true if listener is currently running. /// - public bool IsListenerRunning { get; private set; } + internal bool IsListenerRunning { get; private set; } /// /// Name of session configuration. /// - public string ConfigurationName { get; set; } + internal string ConfigurationName { get; set; } /// /// Accessor for the named pipe reader. /// - public StreamReader TextReader { get; private set; } + internal StreamReader TextReader { get; private set; } /// /// Accessor for the named pipe writer. /// - public StreamWriter TextWriter { get; private set; } + internal StreamWriter TextWriter { get; private set; } /// /// Returns true if object is currently disposed. /// - public bool IsDisposed { get; private set; } + internal bool IsDisposed { get; private set; } /// /// Buffer size for PSRP fragmentor. @@ -386,7 +374,7 @@ internal static int NamedPipeBufferSizeForRemoting /// Event raised when the named pipe server listening thread /// ends. /// - public event EventHandler ListenerEnded; + internal event EventHandler ListenerEnded; #endregion @@ -395,8 +383,8 @@ internal static int NamedPipeBufferSizeForRemoting /// /// Creates a RemoteSessionNamedPipeServer with the current process and AppDomain information. /// - /// RemoteSessionNamedPipeServer - public static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer() + /// RemoteSessionNamedPipeServer. + internal static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer() { string appDomainName = NamedPipeUtils.GetCurrentAppDomainName(); @@ -407,13 +395,13 @@ public static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer() /// /// Constructor. Creates named pipe server with provided pipe name. /// - /// Named Pipe name + /// Named Pipe name. internal RemoteSessionNamedPipeServer( string pipeName) { if (pipeName == null) { - throw new PSArgumentNullException("pipeName"); + throw new PSArgumentNullException(nameof(pipeName)); } _syncObject = new object(); @@ -434,17 +422,20 @@ internal RemoteSessionNamedPipeServer( /// Named pipe namespace name. /// Named pipe core name. /// - /// NamedPipeServerStream - private NamedPipeServerStream CreateNamedPipe( + /// NamedPipeServerStream. + private static NamedPipeServerStream CreateNamedPipe( string serverName, string namespaceName, string coreName, CommonSecurityDescriptor securityDesc) { - if (serverName == null) { throw new PSArgumentNullException("serverName"); } - if (namespaceName == null) { throw new PSArgumentNullException("namespaceName"); } - if (coreName == null) { throw new PSArgumentNullException("coreName"); } + if (serverName == null) { throw new PSArgumentNullException(nameof(serverName)); } + if (namespaceName == null) { throw new PSArgumentNullException(nameof(namespaceName)); } + + if (coreName == null) { throw new PSArgumentNullException(nameof(coreName)); } + +#if !UNIX string fullPipeName = @"\\" + serverName + @"\" + namespaceName + @"\" + coreName; // Create optional security attributes based on provided PipeSecurity. @@ -462,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, @@ -470,10 +461,7 @@ private NamedPipeServerStream CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -495,23 +483,30 @@ private NamedPipeServerStream CreateNamedPipe( pipeHandle.Dispose(); throw; } +#else + return new NamedPipeServerStream( + pipeName: coreName, + direction: PipeDirection.InOut, + maxNumberOfServerInstances: 1, + transmissionMode: PipeTransmissionMode.Byte, + options: PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly, + inBufferSize: _namedPipeBufferSizeForRemoting, + outBufferSize: _namedPipeBufferSizeForRemoting); +#endif } static RemoteSessionNamedPipeServer() { s_syncObject = new object(); - // All PowerShell instances will start with the named pipe - // and listener created and running. - if (Platform.IsWindows) + // 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); + + if (IPCNamedPipeServerEnabled) { - IPCNamedPipeServerEnabled = true; + CreateIPCNamedPipeServerSingleton(); + CreateProcessExitHandler(); } - - CreateIPCNamedPipeServerSingleton(); -#if !CORECLR // There is only one AppDomain per application in CoreCLR, which would be the default - CreateAppDomainUnloadHandler(); -#endif } #endregion @@ -519,13 +514,14 @@ static RemoteSessionNamedPipeServer() #region IDisposable /// - /// Dispose + /// Dispose. /// public void Dispose() { lock (_syncObject) { if (IsDisposed) { return; } + IsDisposed = true; } @@ -533,6 +529,7 @@ public void Dispose() { try { TextReader.Dispose(); } catch (ObjectDisposedException) { } + TextReader = null; } @@ -540,6 +537,7 @@ public void Dispose() { try { TextWriter.Dispose(); } catch (ObjectDisposedException) { } + TextWriter = null; } @@ -554,6 +552,70 @@ public void Dispose() #region Public Methods + /// + /// Creates the custom named pipe server with the given pipename. + /// + /// The name of the pipe to create. + public static void CreateCustomNamedPipeServer(string pipeName) + { + lock (s_syncObject) + { + if (_customNamedPipeServer != null && !_customNamedPipeServer.IsDisposed) + { + if (pipeName == _customNamedPipeServer.PipeName) + { + // we shouldn't recreate the server object if we're using the same pipeName + return; + } + + // Dispose of the current pipe server so we can create a new one with the new pipeName + _customNamedPipeServer.Dispose(); + } + + if (!Platform.IsWindows) + { + int maxNameLength = (Platform.IsLinux ? _maxPipePathLengthLinux : _maxPipePathLengthMacOS) - Path.GetTempPath().Length; + if (pipeName.Length > maxNameLength) + { + throw new InvalidOperationException( + string.Format( + RemotingErrorIdStrings.CustomPipeNameTooLong, + maxNameLength, + pipeName, + pipeName.Length)); + } + } + + try + { + try + { + _customNamedPipeServer = new RemoteSessionNamedPipeServer(pipeName); + } + catch (IOException) + { + // Expected when named pipe server for this process already exists. + // This can happen if process has multiple AppDomains hosting PowerShell (SMA.dll). + return; + } + + // Listener ended callback, used to create listening new pipe server. + _customNamedPipeServer.ListenerEnded += OnCustomNamedPipeServerEnded; + + // Start the pipe server listening thread, and provide client connection callback. + _customNamedPipeServer.StartListening(ClientConnectionCallback); + } + catch (Exception) + { + _customNamedPipeServer = null; + } + } + } + + #endregion + + #region Private Methods + /// /// Starts named pipe server listening thread. When a client connects this thread /// makes a callback to implement the client communication. When the thread ends @@ -561,12 +623,12 @@ public void Dispose() /// and a new listening thread started to handle subsequent client connections. /// /// Connection callback. - public void StartListening( + internal void StartListening( Action clientConnectCallback) { if (clientConnectCallback == null) { - throw new PSArgumentNullException("clientConnectCallback"); + throw new PSArgumentNullException(nameof(clientConnectCallback)); } lock (_syncObject) @@ -575,6 +637,7 @@ public void StartListening( { throw new InvalidOperationException(RemotingErrorIdStrings.NamedPipeAlreadyListening); } + IsListenerRunning = true; // Create listener thread. @@ -582,15 +645,14 @@ public void StartListening( listenerThread.Name = _threadName; listenerThread.IsBackground = true; listenerThread.Start(clientConnectCallback); - } // Lock _syncObject. + } } - #endregion - - #region Private Methods - internal static CommonSecurityDescriptor GetServerPipeSecurity() { +#if UNIX + return null; +#else // Built-in Admin SID SecurityIdentifier adminSID = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, 1); @@ -619,6 +681,7 @@ internal static CommonSecurityDescriptor GetServerPipeSecurity() } return securityDesc; +#endif } /// @@ -632,11 +695,11 @@ private void WaitForConnection() /// /// Process listening thread. /// - /// client callback delegate + /// Client callback delegate. [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. @@ -659,7 +722,11 @@ private void ProcessListeningThread(object state) try { +#if UNIX + userName = System.Environment.UserName; +#else userName = WindowsIdentity.GetCurrent().Name; +#endif } catch (System.Security.SecurityException) { } @@ -680,6 +747,7 @@ private void ProcessListeningThread(object state) { ex = e; } + if (ex != null) { // Error during connection handling. Don't try to restart listening thread. @@ -772,7 +840,7 @@ private void ProcessListeningThread(object state) /// This method supports PowerShell running in "NamedPipeServerMode", which is used for /// PowerShell Direct Windows Server Container connection and management. /// - /// name of the configuration to use + /// Name of the configuration to use. internal static void RunServerMode(string configurationName) { IPCNamedPipeServerEnabled = true; @@ -787,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(); @@ -837,30 +902,31 @@ internal static void CreateIPCNamedPipeServerSingleton() } } -#if !CORECLR // There is only one AppDomain per application in CoreCLR, which would be the default - private static void CreateAppDomainUnloadHandler() + private static void CreateProcessExitHandler() { - // Subscribe to the app domain unload event. - AppDomain.CurrentDomain.DomainUnload += (sender, args) => + AppDomain.CurrentDomain.ProcessExit += (sender, args) => + { + IPCNamedPipeServerEnabled = false; + RemoteSessionNamedPipeServer namedPipeServer = IPCNamedPipeServer; + if (namedPipeServer != null) { - IPCNamedPipeServerEnabled = false; - RemoteSessionNamedPipeServer namedPipeServer = IPCNamedPipeServer; - if (namedPipeServer != null) + try { - try - { - // Terminate the IPC thread. - namedPipeServer.Dispose(); - } - catch (ObjectDisposedException) { } - catch (Exception) - { - // Don't throw an exception on the app domain unload event thread. - } + // Terminate the IPC thread. + namedPipeServer.Dispose(); + } + catch (ObjectDisposedException) + { + // Ignore if object already disposed. + } + catch (Exception) + { + // Don't throw an exception on the app domain unload event thread. } - }; + } + }; } -#endif + private static void OnIPCNamedPipeServerEnded(object sender, ListenerEndedEventArgs args) { if (args.RestartListener) @@ -869,6 +935,14 @@ private static void OnIPCNamedPipeServerEnded(object sender, ListenerEndedEventA } } + private static void OnCustomNamedPipeServerEnded(object sender, ListenerEndedEventArgs args) + { + if (args.RestartListener && sender is RemoteSessionNamedPipeServer server) + { + CreateCustomNamedPipeServer(server.PipeName); + } + } + private static void ClientConnectionCallback(RemoteSessionNamedPipeServer pipeServer) { // Create server mediator object and begin remote session with client. @@ -888,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 @@ -911,29 +983,35 @@ 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(); } catch (ObjectDisposedException) { } + TextReader = null; } @@ -941,6 +1019,7 @@ public void Dispose() { try { TextWriter.Dispose(); } catch (ObjectDisposedException) { } + TextWriter = null; } @@ -959,7 +1038,7 @@ public void Dispose() /// Connect to named pipe server. This is a blocking call until a /// connection occurs or the timeout time has elapsed. /// - /// Connection attempt timeout in milliseconds + /// Connection attempt timeout in milliseconds. public void Connect( int timeout) { @@ -972,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; @@ -1019,35 +1098,33 @@ private RemoteSessionNamedPipeClient() /// Constructor. Creates Named Pipe based on process object. /// /// 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)) + /// AppDomain name or null for default AppDomain. + public RemoteSessionNamedPipeClient(System.Diagnostics.Process process, string appDomainName) + : this(NamedPipeUtils.CreateProcessPipeName(process, appDomainName)) { } /// /// Constructor. Creates Named Pipe based on process Id. /// /// Target process Id for pipe. - /// AppDomain name or null for default AppDomain - public RemoteSessionNamedPipeClient( - int procId, string appDomainName) : - this(NamedPipeUtils.CreateProcessPipeName(procId, appDomainName)) + /// AppDomain name or null for default AppDomain. + public RemoteSessionNamedPipeClient(int procId, string appDomainName) + : this(NamedPipeUtils.CreateProcessPipeName(procId, appDomainName)) { } /// /// Constructor. Creates Named Pipe based on name argument. /// - /// Named Pipe name + /// Named Pipe name. internal RemoteSessionNamedPipeClient( string pipeName) { if (pipeName == null) { - throw new PSArgumentNullException("pipeName"); + throw new PSArgumentNullException(nameof(pipeName)); } - _pipeName = @"\\.\pipe\" + pipeName; + PipeName = pipeName; // Defer creating the .Net NamedPipeClientStream object until we connect. // _clientPipeStream == null. @@ -1064,11 +1141,13 @@ internal RemoteSessionNamedPipeClient( string namespaceName, string coreName) { - if (serverName == null) { throw new PSArgumentNullException("serverName"); } - if (namespaceName == null) { throw new PSArgumentNullException("namespaceName"); } - if (coreName == null) { throw new PSArgumentNullException("coreName"); } + if (serverName == null) { throw new PSArgumentNullException(nameof(serverName)); } + + if (namespaceName == null) { throw new PSArgumentNullException(nameof(namespaceName)); } + + 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. @@ -1090,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. @@ -1097,17 +1179,25 @@ protected override NamedPipeClientStream DoConnect(int timeout) int elapsedTime = 0; _connecting = true; + NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream( + serverName: ".", + pipeName: PipeName, + direction: PipeDirection.InOut, + options: PipeOptions.Asynchronous); + + namedPipeClientStream.ConnectAsync(timeout); + do { - // Wait in 100 mSec increments. - if (!NamedPipeNative.WaitNamedPipe(_pipeName, 100)) + if (!namedPipeClientStream.IsConnected) { + Thread.Sleep(100); elapsedTime = unchecked(Environment.TickCount - startTime); continue; } _connecting = false; - return OpenNamedPipe(); + return namedPipeClientStream; } while (_connecting && (elapsedTime < timeout)); _connecting = false; @@ -1116,50 +1206,6 @@ protected override NamedPipeClientStream DoConnect(int timeout) } #endregion - - #region Private Methods - - /// - /// Helper method to open a named pipe via native APIs and return in - /// .Net NamedPipeClientStream wrapper object. - /// - private NamedPipeClientStream OpenNamedPipe() - { - // Create pipe flags. - uint pipeFlags = NamedPipeNative.FILE_FLAG_OVERLAPPED; - - // Get handle to pipe. - SafePipeHandle 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) - { - throw new System.ComponentModel.Win32Exception(lastError); - } - - try - { - return new NamedPipeClientStream( - PipeDirection.InOut, - true, // IsAsync - true, // IsConnected - pipeHandle); - } - catch (Exception) - { - pipeHandle.Dispose(); - throw; - } - } - - #endregion } /// @@ -1175,22 +1221,22 @@ internal sealed class ContainerSessionNamedPipeClient : NamedPipeClientBase /// Constructor. Creates Named Pipe based on process Id, app domain name and container object root path. /// /// Target process Id for pipe. - /// AppDomain name or null for default AppDomain + /// AppDomain name or null for default AppDomain. /// Container OB root. public ContainerSessionNamedPipeClient( int procId, string appDomainName, string containerObRoot) { - if (String.IsNullOrEmpty(containerObRoot)) + if (string.IsNullOrEmpty(containerObRoot)) { - throw new PSArgumentNullException("containerObRoot"); + throw new PSArgumentNullException(nameof(containerObRoot)); } // // Named pipe inside Windows Server container is under different name space. // - _pipeName = containerObRoot + @"\Device\NamedPipe\" + + PipeName = containerObRoot + @"\Device\NamedPipe\" + NamedPipeUtils.CreateProcessPipeName(procId, appDomainName); } @@ -1204,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); @@ -1248,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 a7aa0f3124e..475dc705674 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -1,25 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Net; -using System.Net.Sockets; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; // Win32Exception using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Collections.Generic; using System.IO; using System.IO.Pipes; -using System.ComponentModel; // Win32Exception -using System.Management.Automation.Tracing; -using System.Management.Automation.Remoting; using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Client; +using System.Management.Automation.Tracing; +using System.Net; +using System.Net.Sockets; using System.Reflection; using System.Runtime.InteropServices; -using System.Threading; using System.Security.AccessControl; +using System.Text; +using System.Threading; + using Microsoft.Win32.SafeHandles; + using Dbg = System.Management.Automation.Diagnostics; using WSManAuthenticationMechanism = System.Management.Automation.Remoting.Client.WSManNativeApi.WSManAuthenticationMechanism; @@ -74,42 +77,24 @@ public enum AuthenticationMechanism Kerberos = 0x6, } - /// - /// Specifies the type of session configuration that - /// should be used for creating a connection info - /// - public enum PSSessionType - { - /// - /// Default PowerShell remoting - /// endpoint - /// - DefaultRemoteShell = 0, - - /// - /// Default Workflow endpoint - /// - Workflow = 1, - } - /// /// Specify the type of access mode that should be - /// used when creating a session configuration + /// used when creating a session configuration. /// public enum PSSessionConfigurationAccessMode { /// - /// Disable the configuration + /// Disable the configuration. /// Disabled = 0, /// - /// Allow local access + /// Allow local access. /// Local = 1, /// - /// Default allow remote access + /// Default allow remote access. /// Remote = 2, } @@ -143,24 +128,24 @@ public enum OutputBufferingMode /// /// Class which defines connection path to a remote runspace /// that needs to be created. Transport specific connection - /// paths will be derived from this + /// paths will be derived from this. /// public abstract class RunspaceConnectionInfo { #region Public Properties /// - /// Name of the computer + /// Name of the computer. /// - public abstract String ComputerName { get; set; } + public abstract string ComputerName { get; set; } /// - /// Credential used for the connection + /// Credential used for the connection. /// public abstract PSCredential Credential { get; set; } /// - /// Authentication mechanism to use while connecting to the server + /// Authentication mechanism to use while connecting to the server. /// public abstract AuthenticationMechanism AuthenticationMechanism { get; set; } @@ -172,7 +157,7 @@ public abstract class RunspaceConnectionInfo public abstract string CertificateThumbprint { get; set; } /// - /// Culture that the remote session should use + /// Culture that the remote session should use. /// public CultureInfo Culture { @@ -180,12 +165,11 @@ public CultureInfo Culture { return _culture; } + set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); + _culture = value; } } @@ -193,7 +177,7 @@ public CultureInfo Culture private CultureInfo _culture = CultureInfo.CurrentCulture; /// - /// UI culture that the remote session should use + /// UI culture that the remote session should use. /// public CultureInfo UICulture { @@ -201,12 +185,11 @@ public CultureInfo UICulture { return _uiCulture; } + set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); + _uiCulture = value; } } @@ -221,7 +204,11 @@ public CultureInfo UICulture /// public int OpenTimeout { - get { return _openTimeout; } + get + { + return _openTimeout; + } + set { _openTimeout = value; @@ -238,16 +225,17 @@ 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; } } } + private int _openTimeout = DefaultOpenTimeout; + internal const int DefaultOpenTimeout = 3 * 60 * 1000; // 3 minutes internal const int DefaultTimeout = -1; internal const int InfiniteTimeout = 0; - /// /// The duration (in ms) for which PowerShell should wait before it times out on cancel operations /// (close runspace or stop powershell). For instance, when the user hits ctrl-C, @@ -265,7 +253,7 @@ public int OpenTimeout /// depending on whether he/she is connecting to a machine in the data /// center or across a slow WAN. /// - /// Default: 3*60*1000 == 3minutes + /// Default: 3*60*1000 == 3minutes. /// public int OperationTimeout { get; set; } = BaseTransportManager.ClientDefaultOperationTimeoutMs; @@ -282,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. @@ -290,10 +278,7 @@ public int OpenTimeout /// public virtual void SetSessionOptions(PSSessionOption options) { - if (options == null) - { - throw new ArgumentNullException("options"); - } + ArgumentNullException.ThrowIfNull(options); if (options.Culture != null) { @@ -333,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( + /// Runspace/Pool instance Id. + /// Session name. + /// PSRemotingCryptoHelper. + public virtual BaseClientSessionTransportManager CreateClientSessionTransportManager( Guid instanceId, string sessionName, PSRemotingCryptoHelper cryptoHelper) @@ -351,38 +356,22 @@ 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 /// - /// Maximum value for port + /// Maximum value for port. /// protected const int MaxPort = 0xFFFF; /// - /// Minimum value for port + /// Minimum value for port. /// protected const int MinPort = 0; @@ -391,14 +380,14 @@ internal virtual void ValidatePortInRange(int port) /// /// Class which defines path to a remote runspace that - /// need to be created + /// need to be created. /// public sealed class WSManConnectionInfo : RunspaceConnectionInfo { #region Public Properties /// - /// Uri associated with this connection path + /// Uri associated with this connection path. /// public Uri ConnectionUri { @@ -406,25 +395,28 @@ public Uri ConnectionUri { return _connectionUri; } + set { if (value == null) { throw PSTraceSource.NewArgumentNullException("value"); } + UpdateUri(value); } } /// - /// Name of the computer + /// Name of the computer. /// - public override String ComputerName + public override string ComputerName { get { return _computerName; } + set { // null or empty value allowed @@ -433,14 +425,15 @@ public override String ComputerName } /// - /// Scheme used for connection + /// Scheme used for connection. /// - public String Scheme + public string Scheme { get { return _scheme; } + set { // null or empty value allowed @@ -449,14 +442,15 @@ public String Scheme } /// - /// Port in which to connect + /// Port in which to connect. /// - public Int32 Port + public int Port { get { return ConnectionUri.Port; } + set { ConstructUri(_scheme, _computerName, value, _appName); @@ -465,23 +459,24 @@ public Int32 Port /// /// AppName which identifies the connection - /// end point in the machine + /// end point in the machine. /// - public String AppName + public string AppName { get { return _appName; } + set { - //null or empty value allowed + // null or empty value allowed ConstructUri(_scheme, _computerName, null, value); } } /// - /// Credential used for the connection + /// Credential used for the connection. /// public override PSCredential Credential { @@ -489,6 +484,7 @@ public override PSCredential Credential { return _credential; } + set { // null or empty value allowed @@ -497,7 +493,6 @@ public override PSCredential Credential } /// - /// /// [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#ShellUri")] public string ShellUri @@ -506,6 +501,7 @@ public string ShellUri { return _shellUri; } + set { _shellUri = ResolveShellUri(value); @@ -513,7 +509,7 @@ public string ShellUri } /// - /// Authentication mechanism to use while connecting to the server + /// Authentication mechanism to use while connecting to the server. /// public override AuthenticationMechanism AuthenticationMechanism { @@ -532,6 +528,7 @@ public override AuthenticationMechanism AuthenticationMechanism { return AuthenticationMechanism.NegotiateWithImplicitCredential; } + return AuthenticationMechanism.Negotiate; case WSManAuthenticationMechanism.WSMAN_FLAG_AUTH_DIGEST: return AuthenticationMechanism.Digest; @@ -542,6 +539,7 @@ public override AuthenticationMechanism AuthenticationMechanism return AuthenticationMechanism.Default; } } + set { switch (value) @@ -571,6 +569,7 @@ public override AuthenticationMechanism AuthenticationMechanism default: throw new PSNotSupportedException(); } + ValidateSpecifiedAuthentication(); } } @@ -582,7 +581,7 @@ public override AuthenticationMechanism AuthenticationMechanism internal WSManAuthenticationMechanism WSManAuthenticationMechanism { get; private set; } = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; /// - /// Allow default credentials for Negotiate + /// Allow default credentials for Negotiate. /// internal bool AllowImplicitCredentialForNegotiate { get; private set; } @@ -599,19 +598,24 @@ public override AuthenticationMechanism AuthenticationMechanism /// public override string CertificateThumbprint { - get { return _thumbPrint; } + get + { + return _thumbPrint; + } + set { if (value == null) { throw PSTraceSource.NewArgumentNullException("value"); } + _thumbPrint = value; } } /// - /// Maximum uri redirection count + /// Maximum uri redirection count. /// public int MaximumConnectionRedirectionCount { get; set; } @@ -622,13 +626,13 @@ public override string CertificateThumbprint /// targeted towards a command. If null, then the size is unlimited. /// Default is unlimited data. /// - public Nullable MaximumReceivedDataSizePerCommand { get; set; } + public int? MaximumReceivedDataSizePerCommand { get; set; } /// /// Maximum size (in bytes) of a deserialized object received from a remote machine. /// If null, then the size is unlimited. Default is unlimited object size. /// - public Nullable MaximumReceivedObjectSize { get; set; } + public int? MaximumReceivedObjectSize { get; set; } /// /// If true, underlying WSMan infrastructure will compress data sent on the network. @@ -641,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). /// @@ -659,7 +663,7 @@ public override string CertificateThumbprint /// application has to explicitly load the user profile prior to using this option. /// /// IMPORTANT: proxy configuration is supported for HTTPS only; for HTTP, the direct - /// connection to the server is used + /// connection to the server is used. /// public ProxyAccessType ProxyAccessType { get; set; } = ProxyAccessType.None; @@ -670,11 +674,15 @@ public override string CertificateThumbprint /// - Negotiate: Use the default authentication (ad defined by the underlying /// protocol) for establishing a remote connection. /// - Basic: Use basic authentication for establishing a remote connection - /// - Digest: Use Digest authentication for establishing a remote connection + /// - Digest: Use Digest authentication for establishing a remote connection. /// public AuthenticationMechanism ProxyAuthentication { - get { return _proxyAuthentication; } + get + { + return _proxyAuthentication; + } + set { switch (value) @@ -687,9 +695,9 @@ public AuthenticationMechanism ProxyAuthentication default: string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ProxyAmbiguousAuthentication, value, - AuthenticationMechanism.Basic.ToString(), - AuthenticationMechanism.Negotiate.ToString(), - AuthenticationMechanism.Digest.ToString()); + nameof(AuthenticationMechanism.Basic), + nameof(AuthenticationMechanism.Negotiate), + nameof(AuthenticationMechanism.Digest)); throw new ArgumentException(message); } } @@ -700,7 +708,11 @@ public AuthenticationMechanism ProxyAuthentication /// public PSCredential ProxyCredential { - get { return _proxyCredential; } + get + { + return _proxyCredential; + } + set { if (ProxyAccessType == ProxyAccessType.None) @@ -714,13 +726,12 @@ public PSCredential ProxyCredential } } - /// /// When connecting over HTTPS, the client does not validate that the server /// certificate is signed by a trusted certificate authority (CA). Use only when /// the remote computer is trusted by other means, for example, if the remote /// computer is part of a network that is physically secure and isolated or the - /// remote computer is listed as a trusted host in WinRM configuration + /// remote computer is listed as a trusted host in WinRM configuration. /// public bool SkipCACheck { get; set; } @@ -734,14 +745,14 @@ public PSCredential ProxyCredential /// /// Indicates that certificate common name (CN) of the server need not match the /// hostname of the server. Used only in remote operations using https. This - /// option should only be used for trusted machines + /// option should only be used for trusted machines. /// public bool SkipRevocationCheck { get; set; } /// /// Specifies that no encryption will be used when doing remote operations over /// http. Unencrypted traffic is not allowed by default and must be enabled in - /// the local configuration + /// the local configuration. /// public bool NoEncryption { get; set; } @@ -785,12 +796,12 @@ public PSCredential ProxyCredential #region Constructors /// - /// Constructor used to create a WSManConnectionInfo + /// Constructor used to create a WSManConnectionInfo. /// - /// computer to connect to - /// scheme to be used for connection - /// port to connect to - /// application end point to connect to + /// Computer to connect to. + /// Scheme to be used for connection. + /// Port to connect to. + /// Application end point to connect to. /// remote shell to launch /// on connection /// credential to be used @@ -800,9 +811,7 @@ public PSCredential ProxyCredential /// Invalid /// scheme or invalid port is specified [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,System.Int64,System.Int64)", MessageId = "4#")] - public WSManConnectionInfo(String scheme, String computerName, Int32 port, String appName, - String shellUri, PSCredential credential, - int openTimeout) + public WSManConnectionInfo(string scheme, string computerName, int port, string appName, string shellUri, PSCredential credential, int openTimeout) { Scheme = scheme; ComputerName = computerName; @@ -814,12 +823,12 @@ public WSManConnectionInfo(String scheme, String computerName, Int32 port, Strin } /// - /// Constructor used to create a WSManConnectionInfo + /// Constructor used to create a WSManConnectionInfo. /// - /// computer to connect to + /// Computer to connect to. /// Scheme to be used for connection. - /// port to connect to - /// application end point to connect to + /// Port to connect to. + /// Application end point to connect to. /// remote shell to launch /// on connection /// credential to be used @@ -829,15 +838,26 @@ public WSManConnectionInfo(String scheme, String computerName, Int32 port, Strin /// 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, Int32 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) { } /// - /// Constructor used to create a WSManConnectionInfo + /// Constructor used to create a WSManConnectionInfo. /// /// /// @@ -846,14 +866,24 @@ public WSManConnectionInfo(String scheme, String computerName, Int32 port, Strin /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] - public WSManConnectionInfo(bool useSsl, string computerName, Int32 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) { } /// - /// /// /// /// @@ -863,11 +893,22 @@ public WSManConnectionInfo(bool useSsl, string computerName, Int32 port, string /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] - public WSManConnectionInfo(bool useSsl, String computerName, Int32 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) { } @@ -877,27 +918,27 @@ public WSManConnectionInfo(bool useSsl, String computerName, Int32 port, String /// life time and default open timeout /// http://localhost/ /// The default shellname Microsoft.PowerShell will be - /// used + /// used. /// public WSManConnectionInfo() { - //ConstructUri(DefaultScheme, DefaultComputerName, DefaultPort, DefaultAppName); + // ConstructUri(DefaultScheme, DefaultComputerName, DefaultPort, DefaultAppName); UseDefaultWSManPort = true; } /// /// Constructor to create a WSManConnectionInfo with a uri /// and explicit credentials - server life time is - /// default and open timeout is default + /// default and open timeout is default. /// - /// uri of remote runspace + /// Uri of remote runspace. /// /// credentials to use to /// connect to the remote runspace /// When an /// uri representing an invalid path is specified [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#.ctor(System.Uri,System.String,System.Management.Automation.PSCredential)", MessageId = "1#")] - public WSManConnectionInfo(Uri uri, String shellUri, PSCredential credential) + public WSManConnectionInfo(Uri uri, string shellUri, PSCredential credential) { if (uri == null) { @@ -930,6 +971,7 @@ public WSManConnectionInfo(Uri uri, String shellUri, PSCredential credential) { ConnectionUri = uri; } + ShellUri = shellUri; Credential = credential; } @@ -938,25 +980,25 @@ public WSManConnectionInfo(Uri uri, String shellUri, PSCredential credential) /// Constructor used to create a WSManConnectionInfo. This constructor supports a certificate thumbprint to /// be used while connecting to a remote machine instead of credential. /// - /// uri of remote runspace + /// Uri of remote runspace. /// /// /// A thumb print of the certificate to use while connecting to the remote machine. /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")] - public WSManConnectionInfo(Uri uri, String shellUri, String certificateThumbprint) + public WSManConnectionInfo(Uri uri, string shellUri, string certificateThumbprint) : this(uri, shellUri, (PSCredential)null) { _thumbPrint = certificateThumbprint; } /// - /// constructor to create a WSManConnectionInfo with a + /// Constructor to create a WSManConnectionInfo with a /// uri specified and the default credentials, /// default server life time and default open - /// timeout + /// timeout. /// - /// uri of remote runspace + /// Uri of remote runspace. /// When an /// uri representing an invalid path is specified public WSManConnectionInfo(Uri uri) @@ -978,10 +1020,7 @@ public WSManConnectionInfo(Uri uri) /// public override void SetSessionOptions(PSSessionOption options) { - if (options == null) - { - throw new ArgumentNullException("options"); - } + ArgumentNullException.ThrowIfNull(options); if ((options.ProxyAccessType == ProxyAccessType.None) && (options.ProxyCredential != null)) { @@ -1017,16 +1056,16 @@ 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(); } /// - /// Does a shallow copy of the current instance + /// Does a shallow copy of the current instance. /// /// public WSManConnectionInfo Copy() @@ -1075,12 +1114,12 @@ public WSManConnectionInfo Copy() } /// - /// string for http scheme + /// String for http scheme. /// public const string HttpScheme = "http"; /// - /// string for https scheme + /// String for https scheme. /// public const string HttpsScheme = "https"; @@ -1088,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, @@ -1101,18 +1147,17 @@ 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)) + if (string.IsNullOrEmpty(resolvedShellUri)) { 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; @@ -1120,7 +1165,7 @@ private String ResolveShellUri(string shell) /// /// Converts to a WSManConnectionInfo. If conversion succeeds extracts - /// the property..otherwise returns default value + /// the property..otherwise returns default value. /// /// /// @@ -1129,8 +1174,7 @@ private String ResolveShellUri(string shell) internal static T ExtractPropertyAsWsManConnectionInfo(RunspaceConnectionInfo rsCI, string property, T defaultValue) { - WSManConnectionInfo wsCI = rsCI as WSManConnectionInfo; - if (null == wsCI) + if (rsCI is not WSManConnectionInfo wsCI) { return defaultValue; } @@ -1140,7 +1184,7 @@ internal static T ExtractPropertyAsWsManConnectionInfo(RunspaceConnectionInfo internal void SetConnectionUri(Uri newUri) { - Dbg.Assert(null != newUri, "newUri cannot be null."); + Dbg.Assert(newUri != null, "newUri cannot be null."); _connectionUri = newUri; } @@ -1155,7 +1199,7 @@ internal void SetConnectionUri(Uri newUri) /// /// /// - internal void ConstructUri(String scheme, String computerName, Nullable port, String appName) + internal void ConstructUri(string scheme, string computerName, int? port, string appName) { // Default scheme is http _scheme = scheme; @@ -1169,15 +1213,15 @@ internal void ConstructUri(String scheme, String computerName, Nullable p || _scheme.Equals(HttpsScheme, StringComparison.OrdinalIgnoreCase) || _scheme.Equals(DefaultScheme, StringComparison.OrdinalIgnoreCase))) { - String message = + string message = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.InvalidSchemeValue, _scheme); ArgumentException e = new ArgumentException(message); throw e; } - //default host is localhost - if (String.IsNullOrEmpty(computerName) || String.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) + // default host is localhost + if (string.IsNullOrEmpty(computerName) || string.Equals(computerName, ".", StringComparison.OrdinalIgnoreCase)) { _computerName = DefaultComputerName; } @@ -1197,6 +1241,7 @@ internal void ConstructUri(String scheme, String computerName, Nullable p } } } + PSEtwLog.LogAnalyticVerbose(PSEventId.ComputerName, PSOpcode.Method, PSTask.CreateRunspace, PSKeyword.Runspace | PSKeyword.UseAlwaysAnalytic, _computerName); @@ -1221,7 +1266,7 @@ internal void ConstructUri(String scheme, String computerName, Nullable p // default appname is WSMan _appName = appName; - if (String.IsNullOrEmpty(_appName)) + if (string.IsNullOrEmpty(_appName)) { _appName = s_defaultAppName; } @@ -1234,7 +1279,7 @@ internal void ConstructUri(String scheme, String computerName, Nullable p } /// - /// returns connection string without the scheme portion. + /// Returns connection string without the scheme portion. /// /// /// The uri from which the string will be extracted @@ -1290,8 +1335,8 @@ private void UpdateUri(Uri uri) (RemotingErrorIdStrings.RelativeUriForRunspacePathNotSupported)); } - if (uri.OriginalString.LastIndexOf(":", StringComparison.OrdinalIgnoreCase) > - uri.AbsoluteUri.IndexOf("//", StringComparison.OrdinalIgnoreCase)) + if (uri.OriginalString.LastIndexOf(':') > + uri.AbsoluteUri.IndexOf("//", StringComparison.Ordinal)) { UseDefaultWSManPort = false; } @@ -1301,7 +1346,7 @@ private void UpdateUri(Uri uri) // http://localhost , http://127.0.0.1 etc. string appname; - if (uri.AbsolutePath.Equals("/", StringComparison.OrdinalIgnoreCase) && + if (uri.AbsolutePath.Equals("/", StringComparison.Ordinal) && string.IsNullOrEmpty(uri.Query) && string.IsNullOrEmpty(uri.Fragment)) { appname = s_defaultAppName; @@ -1330,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; @@ -1353,14 +1398,15 @@ private void UpdateUri(Uri uri) #if NOT_APPLY_PORT_DCR private static string DEFAULT_SCHEME = HTTP_SCHEME; - internal static string DEFAULT_SSL_SCHEME = HTTPS_SCHEME; - private static String DEFAULT_APP_NAME = "wsman"; + internal static readonly string DEFAULT_SSL_SCHEME = HTTPS_SCHEME; + private static string DEFAULT_APP_NAME = "wsman"; /// /// See below for explanation. /// internal bool UseDefaultWSManPort { get { return false; } + set { } } #else @@ -1369,9 +1415,9 @@ internal bool UseDefaultWSManPort /// /// Default appname. This is empty as WSMan configuration has support /// for this. Look at - /// get-item WSMan:\localhost\Client\URLPrefix + /// get-item WSMan:\localhost\Client\URLPrefix. /// - private static readonly String s_defaultAppName = "/wsman"; + private static readonly string s_defaultAppName = "/wsman"; /// /// Default scheme. @@ -1389,41 +1435,40 @@ internal bool UseDefaultWSManPort #endif /// - /// default port for http scheme + /// Default port for http scheme. /// private const int DefaultPortHttp = 80; /// - /// default port for https scheme + /// Default port for https scheme. /// private const int DefaultPortHttps = 443; /// /// This is the default port value which when specified /// results in the default port for the scheme to be - /// assumed + /// assumed. /// private const int DefaultPort = 0; /// - /// default remote host name + /// Default remote host name. /// private const string DefaultComputerName = "localhost"; /// - /// String that represents the local host Uri + /// String that represents the local host Uri. /// - private const String LocalHostUriString = "http://localhost/wsman"; + private const string LocalHostUriString = "http://localhost/wsman"; /// - /// Default value for shell + /// 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 - /// current user + /// current user. /// private const PSCredential DefaultCredential = null; @@ -1443,7 +1488,7 @@ internal bool IsLocalhostAndNetworkAccess return (EnableNetworkAccess && // Interactive token requested (Credential == null && // No credential provided (ComputerName.Equals(DefaultComputerName, StringComparison.OrdinalIgnoreCase) || // Localhost computer name - ComputerName.IndexOf('.') == -1))); // Not FQDN computer name + !ComputerName.Contains('.')))); // Not FQDN computer name } } @@ -1489,41 +1534,6 @@ internal void SetDisconnectedExpiresOnToNow() } #endregion Internal members - - #region V3 Extensions - - private const String DefaultM3PShellName = "Microsoft.PowerShell.Workflow"; - private const String DefaultM3PEndpoint = Remoting.Client.WSManNativeApi.ResourceURIPrefix + DefaultM3PShellName; - - /// - /// Constructor that constructs the configuration name from its type - /// - /// type of configuration to construct - public WSManConnectionInfo(PSSessionType configurationType) : this() - { - ComputerName = string.Empty; - switch (configurationType) - { - case PSSessionType.DefaultRemoteShell: - { - // it is already the default - } - break; - - case PSSessionType.Workflow: - { - ShellUri = DefaultM3PEndpoint; - } - break; - default: - { - Diagnostics.Assert(false, "Unknown value for PSSessionType"); - } - break; - } - } - - #endregion V3 Extensions } /// @@ -1554,7 +1564,12 @@ internal sealed class NewProcessConnectionInfo : RunspaceConnectionInfo public bool RunAs32 { get; set; } /// - /// Powershell version to execute the job in + /// Gets or sets an initial working directory for the powershell background process. + /// + public string WorkingDirectory { get; set; } + + /// + /// Powershell version to execute the job in. /// public Version PSVersion { get; set; } @@ -1570,15 +1585,20 @@ internal sealed class NewProcessConnectionInfo : RunspaceConnectionInfo public override string ComputerName { get { return "localhost"; } + set { throw new NotImplementedException(); } } /// - /// Credential used for the connection + /// Credential used for the connection. /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } + set { _credential = value; @@ -1596,12 +1616,13 @@ public override AuthenticationMechanism AuthenticationMechanism { return _authMechanism; } + set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, - value.ToString(), AuthenticationMechanism.Default.ToString()); + value.ToString(), nameof(AuthenticationMechanism.Default)); } _authMechanism = value; @@ -1617,6 +1638,7 @@ public override AuthenticationMechanism AuthenticationMechanism public override string CertificateThumbprint { get { return string.Empty; } + set { throw new NotImplementedException(); } } @@ -1625,18 +1647,30 @@ public NewProcessConnectionInfo Copy() NewProcessConnectionInfo result = new NewProcessConnectionInfo(_credential); result.AuthenticationMechanism = this.AuthenticationMechanism; result.InitializationScript = this.InitializationScript; + result.WorkingDirectory = this.WorkingDirectory; result.RunAs32 = this.RunAs32; result.PSVersion = this.PSVersion; result.Process = Process; 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, @@ -1650,7 +1684,7 @@ internal override BaseClientSessionTransportManager CreateClientSessionTransport /// /// Creates a connection info instance used to create a runspace on a different - /// process on the local machine + /// process on the local machine. /// internal NewProcessConnectionInfo(PSCredential credential) { @@ -1674,6 +1708,7 @@ public sealed class NamedPipeConnectionInfo : RunspaceConnectionInfo private PSCredential _credential; private AuthenticationMechanism _authMechanism; private string _appDomainName = string.Empty; + private const int _defaultOpenTimeout = 60000; /* 60 seconds. */ #endregion @@ -1695,19 +1730,32 @@ public int ProcessId /// public string AppDomainName { - get { return _appDomainName; } + get + { + return _appDomainName; + } + set { _appDomainName = value ?? string.Empty; } } + /// + /// Gets or sets the custom named pipe name to connect to. This is usually used in conjunction with pwsh -CustomPipeName. + /// + public string CustomPipeName + { + get; + set; + } + #endregion #region Constructors /// - /// Constructor + /// Initializes a new instance of the class. /// public NamedPipeConnectionInfo() { @@ -1715,31 +1763,28 @@ public NamedPipeConnectionInfo() } /// - /// Constructor + /// 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) { } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Process Id to connect to + /// 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) { } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// Process Id to connect to + /// Process Id to connect to. /// Name of application domain to connect to. Connection is to default application domain if blank. - /// Open time out in Milliseconds + /// Open time out in Milliseconds. public NamedPipeConnectionInfo( int processId, string appDomainName, @@ -1750,6 +1795,32 @@ public NamedPipeConnectionInfo( OpenTimeout = openTimeout; } + /// + /// Initializes a new instance of the class. + /// + /// Pipe name to connect to. + public NamedPipeConnectionInfo(string customPipeName) + : this(customPipeName, _defaultOpenTimeout) + { } + + /// + /// Initializes a new instance of the class. + /// + /// Pipe name to connect to. + /// Open time out in Milliseconds. + public NamedPipeConnectionInfo( + string customPipeName, + int openTimeout) + { + if (customPipeName == null) + { + throw new PSArgumentNullException(nameof(customPipeName)); + } + + CustomPipeName = customPipeName; + OpenTimeout = openTimeout; + } + #endregion #region Overrides @@ -1760,15 +1831,19 @@ public NamedPipeConnectionInfo( public override string ComputerName { get { return "localhost"; } + set { throw new NotImplementedException(); } } /// - /// Credential + /// Credential. /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -1778,7 +1853,7 @@ public override PSCredential Credential } /// - /// Authentication + /// Authentication. /// public override AuthenticationMechanism AuthenticationMechanism { @@ -1786,12 +1861,13 @@ public override AuthenticationMechanism AuthenticationMechanism { return _authMechanism; } + set { if (value != Runspaces.AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, - value.ToString(), AuthenticationMechanism.Default.ToString()); + value.ToString(), nameof(AuthenticationMechanism.Default)); } _authMechanism = value; @@ -1799,19 +1875,20 @@ public override AuthenticationMechanism AuthenticationMechanism } /// - /// CertificateThumbprint + /// CertificateThumbprint. /// public override string CertificateThumbprint { get { return string.Empty; } + set { throw new NotImplementedException(); } } /// - /// 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; @@ -1819,11 +1896,19 @@ internal override RunspaceConnectionInfo InternalCopy() newCopy.ProcessId = this.ProcessId; newCopy._appDomainName = _appDomainName; newCopy.OpenTimeout = this.OpenTimeout; + newCopy.CustomPipeName = this.CustomPipeName; 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, @@ -1841,10 +1926,24 @@ 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 /// - /// User Name + /// User Name. /// public string UserName { @@ -1853,18 +1952,47 @@ public string UserName } /// - /// Key File Path + /// Key File Path. + /// + public string KeyFilePath + { + get; + set; + } + + /// + /// Port for connection. + /// + public int Port + { + get; + set; + } + + /// + /// Subsystem to use. + /// + 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. /// - private string KeyFilePath + public int ConnectingTimeout { get; set; } /// - /// Port for connection + /// The SSH options to pass to OpenSSH. + /// Gets or sets the SSH options to pass to OpenSSH. /// - private int Port + private Hashtable Options { get; set; @@ -1875,37 +2003,42 @@ private int Port #region Constructors /// - /// Constructor + /// Initializes a new instance of the class. /// private SSHConnectionInfo() { } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// User Name - /// Computer Name - /// Key File Path + /// User Name. + /// Computer Name. + /// Key File Path. public SSHConnectionInfo( string userName, string computerName, string keyFilePath) { - if (computerName == null) { throw new PSArgumentNullException("computerName"); } + if (computerName == null) + { + throw new PSArgumentNullException(nameof(computerName)); + } - this.UserName = userName; - this.ComputerName = computerName; - this.KeyFilePath = keyFilePath; - this.Port = DefaultPort; + UserName = userName; + ComputerName = computerName; + KeyFilePath = keyFilePath; + Port = 0; + Subsystem = DefaultSubsystem; + ConnectingTimeout = DefaultConnectingTimeoutTime; } /// - /// Constructor + /// Initializes a new instance of the class. /// - /// User Name - /// Computer Name - /// Key File Path - /// Port number for connection (default 22) + /// User Name. + /// Computer Name. + /// Key File Path. + /// Port number for connection (default 22). public SSHConnectionInfo( string userName, string computerName, @@ -1913,8 +2046,67 @@ public SSHConnectionInfo( int port) : this(userName, computerName, keyFilePath) { ValidatePortInRange(port); + Port = port; + } + + /// + /// 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'). + public SSHConnectionInfo( + string userName, + string computerName, + string keyFilePath, + int port, + string subsystem) : this(userName, computerName, keyFilePath, port) + { + 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; + } - this.Port = (port != 0) ? port : DefaultPort; + /// + /// 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 @@ -1931,55 +2123,60 @@ public override string ComputerName } /// - /// Credential + /// Credential. /// public override PSCredential Credential { get { return null; } + set { throw new NotImplementedException(); } } /// - /// Authentication + /// Authentication. /// public override AuthenticationMechanism AuthenticationMechanism { get { return AuthenticationMechanism.Default; } + set { throw new NotImplementedException(); } } /// - /// CertificateThumbprint + /// CertificateThumbprint. /// public override string CertificateThumbprint { get { return string.Empty; } + set { throw new NotImplementedException(); } } /// - /// 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.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, @@ -1992,7 +2189,7 @@ internal override BaseClientSessionTransportManager CreateClientSessionTransport #region Internal Methods /// - /// StartSSHProcess + /// StartSSHProcess. /// /// internal int StartSSHProcess( @@ -2002,88 +2199,148 @@ 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; + } - // Extract an optional domain name if provided. - string domainName = null; - string userName = this.UserName ?? GetCurrentUserName(); -#if !UNIX - var parts = userName.Split(Utils.Separators.Backslash); - if (parts.Length == 2) + var sshCommandPath = Path.Combine(path, sshCommand); + if (File.Exists(sshCommandPath)) + { + filePath = sshCommandPath; + break; + } + } + } + + if (string.IsNullOrEmpty(filePath)) { - domainName = parts[0]; - userName = parts[1]; + throw new CommandNotFoundException( + sshCommand, + null, + "CommandNotFoundException", + DiscoveryExceptions.CommandNotFoundException); } -#endif - // Create client ssh process that hosts powershell as a subsystem and is configured - // to be in server mode for PSRP over SSHD: - // powershell -sshs -NoLogo -NoProfile - // See sshd_configuration file, subsystems section and it will have this entry: - // Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -sshs -NoLogo -NoProfile - string arguments; + // 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] [-o option] -s + // linux|macos: + // 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) + // by adding one of the following Subsystem directives to sshd_config on the remote machine: + // windows: + // Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -SSHServerMode -NoLogo -NoProfile + // Subsystem powershell C:\Program Files\PowerShell\6\pwsh.exe -SSHServerMode -NoLogo -NoProfile + // linux|macos: + // Subsystem powershell /usr/local/bin/pwsh -SSHServerMode -NoLogo -NoProfile + + // 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)); } - arguments = (string.IsNullOrEmpty(domainName)) ? - string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -p {3} -s powershell", this.KeyFilePath, userName, this.ComputerName, this.Port) : - string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -p {4} -s powershell", this.KeyFilePath, userName, domainName, this.ComputerName, this.Port); + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-i ""{this.KeyFilePath}""")); } - else + + // 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)) { - arguments = (string.IsNullOrEmpty(domainName)) ? - string.Format(CultureInfo.InvariantCulture, @"{0}@{1} -p {2} -s powershell", userName, this.ComputerName, this.Port) : - string.Format(CultureInfo.InvariantCulture, @"-l {0}@{1} {2} -p {3} -s powershell", userName, domainName, this.ComputerName, this.Port); + 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.Create(CultureInfo.InvariantCulture, $@"-l {userName}@{domainName}")); + } + else + { + startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-l {this.UserName}")); + } } - System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo( - filePath, - arguments); - startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); - startInfo.CreateNoWindow = true; - startInfo.UseShellExecute = false; + // pass "-p port" command line argument to ssh if Port is set + // 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.Create(CultureInfo.InvariantCulture, $@"-p {this.Port}")); + } - return StartSSHProcessImpl(startInfo, out stdInWriterVar, out stdOutReaderVar, out stdErrReaderVar); - } + // 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}")); + } + } - #endregion + // 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.Create(CultureInfo.InvariantCulture, $@"-s {this.ComputerName.TrimStart('[').TrimEnd(']')} {this.Subsystem}")); - #region Private Methods + startInfo.WorkingDirectory = Path.GetDirectoryName(filePath); + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; - private string GetCurrentUserName() - { -#if UNIX - return System.Environment.GetEnvironmentVariable("USER") ?? string.Empty; -#else - return System.Security.Principal.WindowsIdentity.GetCurrent().Name; -#endif + return StartSSHProcessImpl(startInfo, out stdInWriterVar, out stdOutReaderVar, out stdErrReaderVar); } - #endregion - - #region Constants - /// - /// Default value for port + /// Terminates the SSH process by process Id. /// - private const int DefaultPort = 22; + /// Process id. + internal void KillSSHProcess(int pid) + { + KillSSHProcessImpl(pid); + } #endregion @@ -2123,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 // @@ -2148,7 +2415,7 @@ internal static int StartSSHProcess( string filename = startInfo.FileName; string[] argv = ParseArgv(startInfo); - string[] envp = new string[0]; + string[] envp = CopyEnvVariables(startInfo); string cwd = !string.IsNullOrWhiteSpace(startInfo.WorkingDirectory) ? startInfo.WorkingDirectory : null; // Invoke the shim fork/execve routine. It will create pipes for all requested @@ -2173,21 +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; @@ -2205,6 +2482,21 @@ private static FileStream OpenStream(int fd, FileAccess access) access, StreamBufferSize, isAsync: false); } + /// Copies environment variables from ProcessStartInfo + /// ProcessStartInfo. + /// String array of environment key/value pairs. + private static string[] CopyEnvVariables(ProcessStartInfo psi) + { + var envp = new string[psi.Environment.Count]; + int index = 0; + foreach (var pair in psi.Environment) + { + envp[index++] = pair.Key + "=" + pair.Value; + } + + return envp; + } + /// Converts the filename and arguments information from a ProcessStartInfo into an argv array. /// The ProcessStartInfo. /// The argv array. @@ -2213,9 +2505,9 @@ private static string[] ParseArgv(ProcessStartInfo psi) var argvList = new List(); argvList.Add(psi.FileName); - var argsToParse = psi.Arguments.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 @@ -2375,11 +2668,11 @@ 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) || - (sshProcess.HasExited == true)) + (sshProcess.HasExited)) { throw new InvalidOperationException( StringUtil.Format(RemotingErrorIdStrings.CannotStartSSHClient, (ex != null) ? ex.Message : string.Empty), @@ -2399,8 +2692,10 @@ private static int StartSSHProcessImpl( catch (Exception) { if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } - if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } - if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } + + if (stdOutReaderVar != null) { stdOutReaderVar.Dispose(); } else { stdOutPipeServer.Dispose(); } + + if (stdErrReaderVar != null) { stdErrReaderVar.Dispose(); } else { stdErrPipeServer.Dispose(); } throw; } @@ -2408,12 +2703,23 @@ 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; /// - /// CreateProcessWithRedirectedStd + /// CreateProcessWithRedirectedStd. /// private static Process CreateProcessWithRedirectedStd( ProcessStartInfo startInfo, @@ -2427,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 { @@ -2451,12 +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; } @@ -2468,11 +2774,16 @@ private static Process CreateProcessWithRedirectedStd( try { - var cmdLine = String.Format(CultureInfo.InvariantCulture, @"""{0}"" {1}", startInfo.FileName, startInfo.Arguments); + // Create process start command line with filename and argument list. + var cmdLine = string.Format( + CultureInfo.InvariantCulture, + @"""{0}"" {1}", + 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 @@ -2517,46 +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( @@ -2571,7 +2859,7 @@ private static SafePipeHandle CreateNamedPipe( byte[] securityDescBuffer = new byte[securityDesc.BinaryLength]; securityDesc.GetBinaryForm(securityDescBuffer, 0); securityDescHandle = GCHandle.Alloc(securityDescBuffer, GCHandleType.Pinned); - securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value, true); ; + securityAttributes = NamedPipeNative.GetSecurityAttributes(securityDescHandle.Value, true); } // Create async named pipe. @@ -2586,10 +2874,7 @@ private static SafePipeHandle CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -2614,6 +2899,7 @@ public sealed class VMConnectionInfo : RunspaceConnectionInfo private AuthenticationMechanism _authMechanism; private PSCredential _credential; + private const int _defaultOpenTimeout = 20000; /* 20 seconds. */ #endregion @@ -2644,12 +2930,13 @@ public override AuthenticationMechanism AuthenticationMechanism { return _authMechanism; } + set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, - value.ToString(), AuthenticationMechanism.Default.ToString()); + value.ToString(), nameof(AuthenticationMechanism.Default)); } _authMechanism = value; @@ -2665,6 +2952,7 @@ public override AuthenticationMechanism AuthenticationMechanism public override string CertificateThumbprint { get { return null; } + set { throw new NotImplementedException(); } } @@ -2673,7 +2961,11 @@ public override string CertificateThumbprint /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } + set { _credential = value; @@ -2686,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, @@ -2740,6 +3043,7 @@ public sealed class ContainerConnectionInfo : RunspaceConnectionInfo private AuthenticationMechanism _authMechanism; private PSCredential _credential; + private const int _defaultOpenTimeout = 20000; /* 20 seconds. */ #endregion @@ -2765,12 +3069,13 @@ public override AuthenticationMechanism AuthenticationMechanism { return _authMechanism; } + set { if (value != AuthenticationMechanism.Default) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.IPCSupportsOnlyDefaultAuth, - value.ToString(), AuthenticationMechanism.Default.ToString()); + value.ToString(), nameof(AuthenticationMechanism.Default)); } _authMechanism = value; @@ -2786,6 +3091,7 @@ public override AuthenticationMechanism AuthenticationMechanism public override string CertificateThumbprint { get { return null; } + set { throw new NotImplementedException(); } } @@ -2794,7 +3100,11 @@ public override string CertificateThumbprint /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } + set { _credential = value; @@ -2808,16 +3118,28 @@ public override PSCredential Credential public override string ComputerName { get { return ContainerProc.ContainerId; } + 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) { @@ -2905,57 +3227,67 @@ internal class ContainerProcess private const uint ContainersFeatureNotEnabled = 2; private const uint OtherError = 9999; + private const uint FileNotFoundHResult = 0x80070002; + + // The list of executable to try in order + private static readonly string[] Executables = new string[] { "pwsh.exe", "powershell.exe" }; + #endregion #region Properties /// - /// For Hyper-V container, it is Guid of utility VM hosting Hyper-V container. + /// Gets or Sets, for Hyper-V container, the Guid of utility VM hosting Hyper-V container. /// For Windows Server Container, it is empty. /// public Guid RuntimeId { get; set; } /// - /// OB root of the container. + /// Gets or sets the OB root of the container. /// public string ContainerObRoot { get; set; } /// - /// ID of the container. + /// Gets or sets the ID of the container. /// public string ContainerId { get; set; } /// - /// Process ID of the process created in container. + /// Gets or sets the process ID of the process created in container. /// internal int ProcessId { get; set; } /// - /// Whether the process in container should be launched as high privileged account + /// Gets or sets whether the process in container should be launched as high privileged account /// (RunAsAdmin being true) or low privileged account (RunAsAdmin being false). /// internal bool RunAsAdmin { get; set; } = false; /// - /// The configuration name of the container session. + /// Gets or sets the configuration name of the container session. /// internal string ConfigurationName { get; set; } /// - /// Whether the process in container has terminated. + /// Gets or sets whether the process in container has terminated. /// internal bool ProcessTerminated { get; set; } = false; /// - /// The error code. + /// Gets or sets the error code. /// internal uint ErrorCode { get; set; } = 0; /// - /// The error message for other errors. + /// Gets or sets the error message for other errors. /// internal string ErrorMessage { get; set; } = string.Empty; + /// + /// Gets or sets the PowerShell executable being used to host the runspace. + /// + internal string Executable { get; set; } = string.Empty; + #endregion #region Native HCS (i.e., host compute service) methods @@ -3037,7 +3369,7 @@ public ContainerProcess(string containerId, string containerObRoot, int processI this.RunAsAdmin = runAsAdmin; this.ConfigurationName = configurationName; - Dbg.Assert(!String.IsNullOrEmpty(containerId), "containerId input cannot be empty."); + Dbg.Assert(!string.IsNullOrEmpty(containerId), "containerId input cannot be empty."); GetContainerProperties(); } @@ -3066,7 +3398,7 @@ public void CreateContainerProcess() ContainerId)); case ContainersFeatureNotEnabled: - throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); + throw new PSInvalidOperationException(RemotingErrorIdStrings.ContainersFeatureNotEnabled); // other errors caught with exception case OtherError: @@ -3075,7 +3407,9 @@ public void CreateContainerProcess() // other errors caught without exception default: throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.CannotCreateProcessInContainer, - ContainerId)); + ContainerId, + Executable, + ErrorCode)); } } @@ -3105,7 +3439,7 @@ public void GetContainerProperties() break; case ContainersFeatureNotEnabled: - throw new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.ContainersFeatureNotEnabled)); + throw new PSInvalidOperationException(RemotingErrorIdStrings.ContainersFeatureNotEnabled); case OtherError: throw new PSInvalidOperationException(ErrorMessage); @@ -3119,15 +3453,30 @@ public void GetContainerProperties() /// /// Dynamically load the Host Compute interop assemblies and return useful types. /// - /// The Microsoft.HyperV.Schema.Compute.System.Properties type. + /// The HCS.Compute.System.Properties type. /// The Microsoft.HostCompute.Interop.HostComputeInterop type. private static void GetHostComputeInteropTypes(out Type computeSystemPropertiesType, out Type hostComputeInteropType) { Assembly schemaAssembly = Assembly.Load(new AssemblyName("Microsoft.HyperV.Schema, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); - computeSystemPropertiesType = schemaAssembly.GetType("Microsoft.HyperV.Schema.Compute.System.Properties"); + + // The type name was changed in newer version of Windows so we check for new one first, + // then fallback to previous type name to support older versions of Windows + computeSystemPropertiesType = schemaAssembly.GetType("HCS.Compute.System.Properties"); + if (computeSystemPropertiesType == null) + { + computeSystemPropertiesType = schemaAssembly.GetType("Microsoft.HyperV.Schema.Compute.System.Properties"); + if (computeSystemPropertiesType == null) + { + throw new PSInvalidOperationException(RemotingErrorIdStrings.CannotGetHostInteropTypes); + } + } Assembly hostComputeInteropAssembly = Assembly.Load(new AssemblyName("Microsoft.HostCompute.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); hostComputeInteropType = hostComputeInteropAssembly.GetType("Microsoft.HostCompute.Interop.HostComputeInterop"); + if (hostComputeInteropType == null) + { + throw new PSInvalidOperationException(RemotingErrorIdStrings.CannotGetHostInteropTypes); + } } /// @@ -3146,7 +3495,7 @@ private void CreateContainerProcessInternal() try { IntPtr ComputeSystem = IntPtr.Zero; - string resultString = String.Empty; + string resultString = string.Empty; result = HcsOpenComputeSystem(ContainerId, ref ComputeSystem, ref resultString); if (result != 0) @@ -3159,30 +3508,49 @@ private void CreateContainerProcessInternal() // // Hyper-V container (i.e., RuntimeId is not empty) uses Hyper-V socket transport. // Windows Server container (i.e., RuntimeId is empty) uses named pipe transport for now. - // This code executes `powershell.exe` as it exists in the container which currently is - // expected to be Windows PowerShell as it's inbox in the container. + // This code executes `pwsh.exe` as it exists in the container which currently is + // expected to be PowerShell 6+ as it's inbox in the container. + // If `pwsh.exe` does not exist, fall back to `powershell.exe` which is Windows PowerShell. // - cmd = string.Format(System.Globalization.CultureInfo.InvariantCulture, - @"{{""CommandLine"": ""powershell.exe {0} -NoLogo {1}"",""RestrictedToken"": {2}}}", - (RuntimeId != Guid.Empty) ? "-so -NoProfile" : "-NamedPipeServerMode", - String.IsNullOrEmpty(ConfigurationName) ? String.Empty : String.Concat("-Config ", ConfigurationName), - (RunAsAdmin) ? "false" : "true"); + foreach (string executableToTry in Executables) + { + cmd = GetContainerProcessCommand(executableToTry); - HCS_PROCESS_INFORMATION ProcessInformation = new HCS_PROCESS_INFORMATION(); - IntPtr Process = IntPtr.Zero; + HCS_PROCESS_INFORMATION ProcessInformation = new HCS_PROCESS_INFORMATION(); + IntPtr Process = IntPtr.Zero; - // - // Create PowerShell process inside the container. - // - result = HcsCreateProcess(ComputeSystem, cmd, ref ProcessInformation, ref Process, ref resultString); - if (result != 0) - { - processId = 0; - error = result; - } - else - { - processId = Convert.ToInt32(ProcessInformation.ProcessId); + // + // Create PowerShell process inside the container. + // + result = HcsCreateProcess(ComputeSystem, cmd, ref ProcessInformation, ref Process, ref resultString); + if (result == 0) + { + processId = Convert.ToInt32(ProcessInformation.ProcessId); + + // Reset error to 0 in case this is not the first iteration of the loop. + error = 0; + + // the process was started, exit the loop. + break; + } + else if (result == FileNotFoundHResult) + { + // "The system cannot find the file specified", try the next one + // or exit the loop of none are left to try. + // Set the process and error information in case we exit the loop. + processId = 0; + error = result; + continue; + } + else + { + processId = 0; + error = result; + + // the executable was found but did not work + // exit the loop with the error state. + break; + } } } } @@ -3212,13 +3580,30 @@ private void CreateContainerProcessInternal() ErrorCode = error; } + /// + /// Get Command to launch container process based on instance properties. + /// + /// The name of the executable to use in the command. + /// The command to launch the container process. + private string GetContainerProcessCommand(string executable) + { + Executable = executable; + return string.Format( + System.Globalization.CultureInfo.InvariantCulture, + @"{{""CommandLine"": ""{0} {1} -NoLogo {2}"",""RestrictedToken"": {3}}}", + Executable, + (RuntimeId != Guid.Empty) ? "-SocketServerMode -NoProfile" : "-NamedPipeServerMode", + string.IsNullOrEmpty(ConfigurationName) ? string.Empty : string.Concat("-Config ", ConfigurationName), + RunAsAdmin ? "false" : "true"); + } + /// /// Terminate the process inside container. /// private void TerminateContainerProcessInternal() { IntPtr ComputeSystem = IntPtr.Zero; - string resultString = String.Empty; + string resultString = string.Empty; IntPtr process = IntPtr.Zero; ProcessTerminated = false; @@ -3243,7 +3628,7 @@ private void GetContainerPropertiesInternal() try { IntPtr ComputeSystem = IntPtr.Zero; - string resultString = String.Empty; + string resultString = string.Empty; if (HcsOpenComputeSystem(ContainerId, ref ComputeSystem, ref resultString) == 0) { @@ -3256,14 +3641,49 @@ private void GetContainerPropertiesInternal() var computeSystemPropertiesHandle = getComputeSystemPropertiesInfo.Invoke(null, new object[] { ComputeSystem }); - RuntimeId = (Guid)computeSystemPropertiesType.GetProperty("RuntimeId").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 fieldInfo = computeSystemPropertiesType.GetField("RuntimeId"); + if (fieldInfo != null) + { + RuntimeId = (Guid)fieldInfo.GetValue(computeSystemPropertiesHandle); + } + else + { + var propertyInfo = computeSystemPropertiesType.GetProperty("RuntimeId"); + if (propertyInfo == null) + { + throw new PSInvalidOperationException(RemotingErrorIdStrings.CannotGetHostInteropTypes); + } + + RuntimeId = (Guid)propertyInfo.GetValue(computeSystemPropertiesHandle); + } // // Get container object root for Windows Server container. // 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); + } } } } @@ -3295,18 +3715,8 @@ private void GetContainerPropertiesInternal() /// /// Run some tasks on MTA thread if needed. /// - private void RunOnMTAThread(ThreadStart threadProc) + private static void RunOnMTAThread(ThreadStart threadProc) { - // - // By default, non-OneCore PowerShell is launched with ApartmentState being STA. - // In this case, we need to create a separate thread, set its ApartmentState to MTA, - // and do the work. - // - // For OneCore PowerShell, its ApartmentState is always MTA. - // -#if CORECLR - threadProc(); -#else if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { threadProc(); @@ -3319,13 +3729,12 @@ private void RunOnMTAThread(ThreadStart threadProc) executionThread.Start(); executionThread.Join(); } -#endif } /// /// 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/RunspaceInitInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceInitInfo.cs index 8773def9aa9..ad193fc914b 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceInitInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceInitInfo.cs @@ -1,28 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting { /// - /// Class that encapsulates the information carried by the RunspaceInitInfo PSRP message + /// Class that encapsulates the information carried by the RunspaceInitInfo PSRP message. /// internal class RunspacePoolInitInfo { /// - /// Min Runspaces setting on the server runspace pool + /// Min Runspaces setting on the server runspace pool. /// internal int MinRunspaces { get; } /// - /// Max Runspaces setting on the server runspace pool + /// Max Runspaces setting on the server runspace pool. /// internal int MaxRunspaces { get; } /// - /// Constructor + /// Constructor. /// /// /// @@ -32,4 +31,4 @@ internal RunspacePoolInitInfo(int minRS, int maxRS) MaxRunspaces = maxRS; } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs index e5ea2c5c3e4..fb4807351c3 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspacePoolStateInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Runspaces; @@ -10,7 +9,7 @@ namespace System.Management.Automation { /// /// Defines type which has information about RunspacePoolState - /// and exception associated with that state + /// and exception associated with that state. /// /// This class is created so that a state change along /// with its reason can be transported from the server to the @@ -18,19 +17,19 @@ 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; } /// - /// Exception associated with that state + /// Exception associated with that state. /// public Exception Reason { get; } /// - /// Constructor for creating the state info + /// Constructor for creating the state info. /// - /// state + /// State. /// exception that resulted in this /// state change. Can be null public RunspacePoolStateInfo(RunspacePoolState state, Exception reason) @@ -39,4 +38,4 @@ public RunspacePoolStateInfo(RunspacePoolState state, Exception reason) Reason = reason; } } -} \ No newline at end of file +} 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 3fddda979b3..e26f3421cda 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -1,21 +1,23 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Tracing; -using System.Threading; using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Management.Automation.Internal; +using System.Management.Automation; using System.Management.Automation.Host; +using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces.Internal; -using System.Management.Automation.Remoting; +using System.Management.Automation.Tracing; +using System.Reflection; +using System.Threading; + using Microsoft.PowerShell; using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; -using System.Reflection; namespace System.Management.Automation { @@ -29,7 +31,6 @@ public class RemotingEncodingException : RuntimeException #region Constructors /// - /// /// public RemotingEncodingException() : base() @@ -37,7 +38,6 @@ public RemotingEncodingException() } /// - /// /// /// public RemotingEncodingException(string message) @@ -46,7 +46,6 @@ public RemotingEncodingException(string message) } /// - /// /// /// /// @@ -56,7 +55,6 @@ public RemotingEncodingException(string message, Exception innerException) } /// - /// /// /// /// @@ -76,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. @@ -89,14 +88,21 @@ 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"; internal static readonly string RunspaceIdNoteProperty = "RunspaceId"; internal static readonly string ShowComputerNameNoteProperty = "PSShowComputerName"; internal static readonly string SourceJobInstanceId = "PSSourceJobInstanceId"; - internal static readonly string SourceLength = "Length"; internal static readonly string EventObject = "PSEventObject"; // used by Custom Shell related cmdlets. internal const string PSSessionConfigurationNoun = "PSSessionConfiguration"; @@ -128,231 +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"; @@ -421,15 +202,15 @@ internal static class RemoteDataNameStrings #region StateInfo /// - /// Name of property when Exception is serialized as error record + /// Name of property when Exception is serialized as error record. /// internal const string ExceptionAsErrorRecord = "ExceptionAsErrorRecord"; /// - /// Property used for encoding state of pipeline when serializing PipelineStateInfo + /// Property used for encoding state of pipeline when serializing PipelineStateInfo. /// internal const string PipelineState = "PipelineState"; /// - /// Property used for encoding state of runspace when serializing RunspaceStateInfo + /// Property used for encoding state of runspace when serializing RunspaceStateInfo. /// internal const string RunspaceState = "RunspaceState"; @@ -438,7 +219,7 @@ internal static class RemoteDataNameStrings #region PSEventArgs /// - /// Properties used for serialization of PSEventArgs + /// Properties used for serialization of PSEventArgs. /// internal const string PSEventArgsComputerName = "PSEventArgs.ComputerName"; internal const string PSEventArgsRunspaceId = "PSEventArgs.RunspaceId"; @@ -637,21 +418,21 @@ private static PSNoteProperty CreateHostInfoProperty(HostInfo hostInfo) /// /// This method generates a Remoting data structure handler message for - /// creating a RunspacePool on the server + /// creating a RunspacePool on the server. /// - /// id of the clientRunspacePool + /// Id of the clientRunspacePool. /// minRunspaces for the RunspacePool /// to be created at the server /// maxRunspaces for the RunspacePool /// to be created at the server - /// local runspace pool + /// Local runspace pool. /// host for the runspacepool at the client end /// from this host, information will be extracted and sent to /// server /// /// Application arguments the server can see in /// - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -677,11 +458,7 @@ internal static RemoteDataObject GenerateCreateRunspacePool( dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MinRunspaces, minRunspaces)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MaxRunspaces, maxRunspaces)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ThreadOptions, runspacePool.ThreadOptions)); -#if CORECLR // No ApartmentState In CoreCLR, default to MTA for outgoing objects - ApartmentState poolState = ApartmentState.MTA; -#else ApartmentState poolState = runspacePool.ApartmentState; -#endif dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ApartmentState, poolState)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ApplicationArguments, applicationArguments)); @@ -699,14 +476,14 @@ internal static RemoteDataObject GenerateCreateRunspacePool( /// /// This method generates a Remoting data structure handler message for - /// creating a RunspacePool on the server + /// creating a RunspacePool on the server. /// - /// id of the clientRunspacePool + /// Id of the clientRunspacePool. /// minRunspaces for the RunspacePool /// to be created at the server /// maxRunspaces for the RunspacePool /// to be created at the server - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -729,6 +506,7 @@ internal static RemoteDataObject GenerateConnectRunspacePool( dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MinRunspaces, minRunspaces)); propertyCount++; } + if (maxRunspaces != -1) { dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MaxRunspaces, maxRunspaces)); @@ -755,14 +533,14 @@ internal static RemoteDataObject GenerateConnectRunspacePool( /// /// Generates a response message to ConnectRunspace that includes - /// sufficient information to construction client RunspacePool state + /// sufficient information to construction client RunspacePool state. /// - /// id of the clientRunspacePool + /// Id of the clientRunspacePool. /// minRunspaces for the RunspacePool /// to be created at the server /// maxRunspaces for the RunspacePool /// to be created at the server - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -788,17 +566,15 @@ internal static RemoteDataObject GenerateRunspacePoolInitData( dataAsPSObject); } - - /// /// This method generates a Remoting data structure handler message for - /// modifying the maxrunspaces of the specified runspace pool on the server + /// modifying the maxrunspaces of the specified runspace pool on the server. /// - /// id of the clientRunspacePool + /// Id of the clientRunspacePool. /// new value of maxRunspaces for the /// specified RunspacePool - /// call id of the call at client - /// data structure handler message encoded as RemoteDataObject + /// Call id of the call at client. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -823,13 +599,13 @@ internal static RemoteDataObject GenerateSetMaxRunspaces(Guid clientRunspacePool /// /// This method generates a Remoting data structure handler message for - /// modifying the maxrunspaces of the specified runspace pool on the server + /// modifying the maxrunspaces of the specified runspace pool on the server. /// - /// id of the clientRunspacePool + /// Id of the clientRunspacePool. /// new value of minRunspaces for the /// specified RunspacePool - /// call id of the call at client - /// data structure handler message encoded as RemoteDataObject + /// Call id of the call at client. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -854,12 +630,12 @@ internal static RemoteDataObject GenerateSetMinRunspaces(Guid clientRunspacePool /// /// This method generates a Remoting data structure handler message for - /// that contains a response to SetMaxRunspaces or SetMinRunspaces + /// that contains a response to SetMaxRunspaces or SetMinRunspaces. /// - /// id of the clientRunspacePool - /// call id of the call at client - /// response to the call - /// data structure handler message encoded as RemoteDataObject + /// Id of the clientRunspacePool. + /// Call id of the call at client. + /// Response to the call. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -884,12 +660,12 @@ internal static RemoteDataObject GenerateRunspacePoolOperationResponse(Guid clie /// /// This method generates a Remoting data structure handler message for - /// getting the available runspaces on the server + /// getting the available runspaces on the server. /// /// guid of the runspace pool on which /// this needs to be queried - /// call id of the call at the client - /// data structure handler message encoded as RemoteDataObject + /// Call id of the call at the client. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -912,13 +688,13 @@ internal static RemoteDataObject GenerateGetAvailableRunspaces(Guid clientRunspa /// /// This method generates a remoting data structure handler message for - /// transferring a roles public key to the other side + /// transferring a roles public key to the other side. /// - /// runspace pool id - /// public key to send across + /// Runspace pool id. + /// Public key to send across. /// destination that this message is /// targeted to - /// data structure message + /// Data structure message. /// The message format is as under for this message /// -------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -941,10 +717,10 @@ internal static RemoteDataObject GenerateMyPublicKey(Guid runspacePoolId, /// /// This method generates a remoting data structure handler message for - /// requesting a public key from the client to the server + /// requesting a public key from the client to the server. /// - /// runspace pool id - /// data structure message + /// Runspace pool id. + /// Data structure message. /// The message format is as under for this message /// -------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -963,11 +739,11 @@ internal static RemoteDataObject GeneratePublicKeyRequest(Guid runspacePoolId) /// /// This method generates a remoting data structure handler message for - /// sending an encrypted session key to the client + /// sending an encrypted session key to the client. /// - /// runspace pool id - /// encrypted session key - /// data structure message + /// Runspace pool id. + /// Encrypted session key. + /// Data structure message. /// The message format is as under for this message /// -------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -991,13 +767,13 @@ internal static RemoteDataObject GenerateEncryptedSessionKeyResponse(Guid runspa /// /// This methods generates a Remoting data structure handler message for - /// creating a command discovery pipeline on the server + /// creating a command discovery pipeline on the server. /// /// The client remote powershell from which the /// message needs to be generated. /// The data is extracted from parameters of the first command named "Get-Command". /// - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// ------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -1019,6 +795,7 @@ internal static RemoteDataObject GenerateGetCommandMetadata(ClientRemotePowerShe break; } } + Dbg.Assert(getCommand != null, "Whoever sets PowerShell.IsGetCommandMetadataSpecialPipeline needs to make sure Get-Command is present"); string[] name = null; @@ -1071,11 +848,11 @@ internal static RemoteDataObject GenerateGetCommandMetadata(ClientRemotePowerShe /// /// This methods generates a Remoting data structure handler message for - /// creating a PowerShell on the server + /// creating a PowerShell on the server. /// /// The client remote powershell from which the /// create powershell message needs to be generated - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// ------------------------------------------------------------------------- /// | D | TI | RPID | PID | Data | Type | @@ -1113,11 +890,7 @@ internal static RemoteDataObject GenerateCreatePowerShell(ClientRemotePowerShell hostInfo = new HostInfo(null); hostInfo.UseRunspaceHost = true; -#if CORECLR // No ApartmentState In CoreCLR, default to MTA for outgoing objects - ApartmentState passedApartmentState = ApartmentState.MTA; -#else ApartmentState passedApartmentState = rsPool.ApartmentState; -#endif dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ApartmentState, passedApartmentState)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.RemoteStreamOptions, RemoteStreamOptions.AddInvocationInfo)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.AddToHistory, false)); @@ -1130,11 +903,7 @@ internal static RemoteDataObject GenerateCreatePowerShell(ClientRemotePowerShell hostInfo.UseRunspaceHost = true; } -#if CORECLR // No ApartmentState In CoreCLR, default to MTA for outgoing objects - ApartmentState passedApartmentState = ApartmentState.MTA; -#else ApartmentState passedApartmentState = settings.ApartmentState; -#endif dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ApartmentState, passedApartmentState)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.RemoteStreamOptions, settings.RemoteStreamOptions)); dataAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.AddToHistory, settings.AddToHistory)); @@ -1152,11 +921,11 @@ internal static RemoteDataObject GenerateCreatePowerShell(ClientRemotePowerShell /// /// This method creates a remoting data structure handler message for transporting - /// application private data from server to client + /// application private data from server to client. /// - /// id of the client RunspacePool - /// application private data - /// data structure handler message encoded as RemoteDataObject + /// Id of the client RunspacePool. + /// Application private data. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1181,11 +950,11 @@ internal static RemoteDataObject GenerateApplicationPrivateData( /// /// This method creates a remoting data structure handler message for transporting a state - /// information from server to client + /// information from server to client. /// - /// id of the client RunspacePool - /// State information object - /// data structure handler message encoded as RemoteDataObject + /// Id of the client RunspacePool. + /// State information object. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1200,19 +969,19 @@ internal static RemoteDataObject GenerateRunspacePoolStateInfo( // BUGBUG: This object creation needs to be relooked PSObject dataAsPSObject = CreateEmptyPSObject(); - //Add State Property + // Add State Property PSNoteProperty stateProperty = new PSNoteProperty(RemoteDataNameStrings.RunspaceState, (int)(stateInfo.State)); dataAsPSObject.Properties.Add(stateProperty); - //Add Reason property + // 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); } @@ -1225,11 +994,11 @@ internal static RemoteDataObject GenerateRunspacePoolStateInfo( /// /// This method creates a remoting data structure handler message for transporting a PowerShell - /// event from server to client + /// event from server to client. /// - /// id of the client RunspacePool - /// PowerShell event - /// data structure handler message encoded as RemoteDataObject + /// Id of the client RunspacePool. + /// PowerShell event. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1262,8 +1031,8 @@ internal static RemoteDataObject GeneratePSEventArgs(Guid clientRunspacePoolId, /// the single runspace on the server. /// /// - /// Caller Id - /// Data structure handler message encoded as RemoteDataObject + /// Caller Id. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1284,10 +1053,10 @@ internal static RemoteDataObject GenerateResetRunspaceState(Guid clientRunspaceP } /// - /// Returns the PS remoting protocol version associated with the provided + /// Returns the PS remoting protocol version associated with the provided. /// - /// RunspacePool - /// PS remoting protocol version + /// RunspacePool. + /// PS remoting protocol version. internal static Version GetPSRemotingProtocolVersion(RunspacePool rsPool) { return (rsPool != null && rsPool.RemoteRunspacePoolInternal != null) ? @@ -1300,12 +1069,12 @@ internal static Version GetPSRemotingProtocolVersion(RunspacePool rsPool) /// /// This method creates a remoting data structure handler message for sending a powershell - /// input data from the client to the server + /// input data from the client to the server. /// - /// input data to send - /// client runspace pool id - /// client powershell id - /// data structure handler message encoded as RemoteDataObject + /// Input data to send. + /// Client runspace pool id. + /// Client powershell id. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1325,11 +1094,11 @@ internal static RemoteDataObject GeneratePowerShellInput(object data, Guid clien /// /// This method creates a remoting data structure handler message for signalling - /// end of input data for powershell + /// end of input data for powershell. /// - /// client runspace pool id - /// client powershell id - /// data structure handler message encoded as RemoteDataObject + /// Client runspace pool id. + /// Client powershell id. + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1349,14 +1118,14 @@ internal static RemoteDataObject GeneratePowerShellInputEnd(Guid clientRemoteRun /// /// This method creates a remoting data structure handler message for transporting a - /// powershell output data from server to client + /// powershell output data from server to client. /// - /// data to be sent + /// Data to be sent. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1377,16 +1146,16 @@ internal static RemoteDataObject GeneratePowerShellOutput(PSObject data, Guid cl /// /// This method creates a remoting data structure handler message for transporting a /// powershell informational message (debug/verbose/warning/progress)from - /// server to client + /// server to client. /// - /// data to be sent + /// Data to be sent. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell /// data type of this informational /// message - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1407,14 +1176,14 @@ internal static RemoteDataObject GeneratePowerShellInformational(object data, /// /// This method creates a remoting data structure handler message for transporting a /// powershell progress message from - /// server to client + /// server to client. /// - /// progress record to send + /// Progress record to send. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1427,7 +1196,7 @@ internal static RemoteDataObject GeneratePowerShellInformational(ProgressRecord { if (progressRecord == null) { - throw PSTraceSource.NewArgumentNullException("progressRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(progressRecord)); } return RemoteDataObject.CreateFrom(RemotingDestination.Client, @@ -1440,14 +1209,14 @@ internal static RemoteDataObject GeneratePowerShellInformational(ProgressRecord /// /// This method creates a remoting data structure handler message for transporting a /// powershell information stream message from - /// server to client + /// server to client. /// - /// information record to send + /// Information record to send. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// ----------------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1460,7 +1229,7 @@ internal static RemoteDataObject GeneratePowerShellInformational(InformationReco { if (informationRecord == null) { - throw PSTraceSource.NewArgumentNullException("informationRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(informationRecord)); } return RemoteDataObject.CreateFrom(RemotingDestination.Client, @@ -1472,14 +1241,14 @@ internal static RemoteDataObject GeneratePowerShellInformational(InformationReco /// /// This method creates a remoting data structure handler message for transporting a - /// powershell error record from server to client + /// powershell error record from server to client. /// - /// error record to be sent + /// Error record to be sent. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1499,14 +1268,14 @@ internal static RemoteDataObject GeneratePowerShellError(object errorRecord, /// /// This method creates a remoting data structure handler message for transporting a - /// powershell state information from server to client + /// powershell state information from server to client. /// - /// state information object + /// State information object. /// id of client powershell /// to which this information need to be delivered /// id of client runspacepool /// associated with this powershell - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1517,23 +1286,21 @@ internal static RemoteDataObject GeneratePowerShellError(object errorRecord, internal static RemoteDataObject GeneratePowerShellStateInfo(PSInvocationStateInfo stateInfo, Guid clientPowerShellId, Guid clientRunspacePoolId) { - //Encode Pipeline StateInfo as PSObject + // Encode Pipeline StateInfo as PSObject PSObject dataAsPSObject = CreateEmptyPSObject(); - //Convert the state to int and add as property + // Convert the state to int and add as property PSNoteProperty stateProperty = new PSNoteProperty( RemoteDataNameStrings.PipelineState, (int)(stateInfo.State)); dataAsPSObject.Properties.Add(stateProperty); - //Add exception property + // 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); } @@ -1554,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) { @@ -1565,19 +1332,23 @@ internal static ErrorRecord GetErrorRecordFromException(Exception exception) if (cer != null) { er = cer.ErrorRecord; - //Exception inside the error record is ParentContainsErrorRecordException which - //doesn't have stack trace. Replace it with top level exception. + // Exception inside the error record is ParentContainsErrorRecordException which + // doesn't have stack trace. Replace it with top level exception. er = new ErrorRecord(er, exception); } + return er; } /// /// 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 + /// ErrorId to use if exception is not of type IContainsErrorRecord. + /// ErrorCategory to use if exception is not of type IContainsErrorRecord. /// private static PSNoteProperty GetExceptionProperty(Exception exception, string errorId, ErrorCategory category) { @@ -1596,9 +1367,9 @@ private static PSNoteProperty GetExceptionProperty(Exception exception, string e /// This method creates a remoting data structure handler message for transporting a session /// capability message. Should be used by client. /// - /// RemoteSession capability object to encode + /// RemoteSession capability object to encode. /// - /// data structure handler message encoded as RemoteDataObject + /// Data structure handler message encoded as RemoteDataObject. /// The message format is as under for this message /// -------------------------------------------------------------------------------------- /// | D | TI | RPID | PID | Action | Data | Type | @@ -1611,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); } @@ -1649,10 +1418,10 @@ private static T ConvertPropertyValueTo(string propertyName, object propertyV { if (propertyName == null) // comes from internal caller { - throw PSTraceSource.NewArgumentNullException("propertyName"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyName)); } - if (typeof(T).GetTypeInfo().IsEnum) + if (typeof(T).IsEnum) { if (propertyValue is string) { @@ -1701,14 +1470,12 @@ private static T ConvertPropertyValueTo(string propertyName, object propertyV } else if (propertyValue == null) { - TypeInfo typeInfo = typeof(T).GetTypeInfo(); - - if (!typeInfo.IsValueType) + if (!typeof(T).IsValueType) { return default(T); } - if (typeInfo.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>))) + if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>))) { return default(T); } @@ -1759,12 +1526,12 @@ private static PSPropertyInfo GetProperty(PSObject psObject, string propertyName { if (psObject == null) { - throw PSTraceSource.NewArgumentNullException("psObject"); + throw PSTraceSource.NewArgumentNullException(nameof(psObject)); } if (propertyName == null) { - throw PSTraceSource.NewArgumentNullException("propertyName"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyName)); } PSPropertyInfo property = psObject.Properties[propertyName]; @@ -1781,12 +1548,12 @@ internal static T GetPropertyValue(PSObject psObject, string propertyName) { if (psObject == null) { - throw PSTraceSource.NewArgumentNullException("psObject"); + throw PSTraceSource.NewArgumentNullException(nameof(psObject)); } if (propertyName == null) { - throw PSTraceSource.NewArgumentNullException("propertyName"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyName)); } PSPropertyInfo property = GetProperty(psObject, propertyName); @@ -1798,12 +1565,12 @@ internal static IEnumerable EnumerateListProperty(PSObject psObject, strin { if (psObject == null) { - throw PSTraceSource.NewArgumentNullException("psObject"); + throw PSTraceSource.NewArgumentNullException(nameof(psObject)); } if (propertyName == null) { - throw PSTraceSource.NewArgumentNullException("propertyName"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyName)); } IEnumerable e = GetPropertyValue(psObject, propertyName); @@ -1816,16 +1583,16 @@ 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) { - throw PSTraceSource.NewArgumentNullException("psObject"); + throw PSTraceSource.NewArgumentNullException(nameof(psObject)); } if (propertyName == null) { - throw PSTraceSource.NewArgumentNullException("propertyName"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyName)); } Hashtable h = GetPropertyValue(psObject, propertyName); @@ -1833,24 +1600,24 @@ 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); } } } /// - /// decode and obtain the RunspacePool state info from the - /// data object specified + /// Decode and obtain the RunspacePool state info from the + /// data object specified. /// - /// data object to decode - /// RunspacePoolStateInfo + /// Data object to decode. + /// RunspacePoolStateInfo. internal static RunspacePoolStateInfo GetRunspacePoolStateInfo(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } RunspacePoolState state = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.RunspaceState); @@ -1860,62 +1627,62 @@ internal static RunspacePoolStateInfo GetRunspacePoolStateInfo(PSObject dataAsPS } /// - /// decode and obtain the application private data from the - /// data object specified + /// Decode and obtain the application private data from the + /// data object specified. /// - /// data object to decode - /// application private data + /// Data object to decode. + /// Application private data. internal static PSPrimitiveDictionary GetApplicationPrivateData(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.ApplicationPrivateData); } /// - /// Gets the public key from the encoded message + /// Gets the public key from the encoded message. /// - /// data object to decode - /// public key as string - internal static String GetPublicKey(PSObject dataAsPSObject) + /// Data object to decode. + /// Public key as string. + internal static string GetPublicKey(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } - return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PublicKey); + return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PublicKey); } /// - /// Gets the encrypted session key from the encoded message + /// Gets the encrypted session key from the encoded message. /// - /// data object to decode - /// encrypted session key as string - internal static String GetEncryptedSessionKey(PSObject dataAsPSObject) + /// Data object to decode. + /// Encrypted session key as string. + internal static string GetEncryptedSessionKey(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.EncryptedSessionKey); } /// - /// decode and obtain the RunspacePool state info from the - /// data object specified + /// Decode and obtain the RunspacePool state info from the + /// data object specified. /// - /// data object to decode - /// RunspacePoolStateInfo + /// Data object to decode. + /// RunspacePoolStateInfo. internal static PSEventArgs GetPSEventArgs(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } int eventIdentifier = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PSEventArgsEventIdentifier); @@ -1925,7 +1692,7 @@ internal static PSEventArgs GetPSEventArgs(PSObject dataAsPSObject) string computerName = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PSEventArgsComputerName); Guid runspaceId = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.PSEventArgsRunspaceId); - ArrayList sourceArgs = new ArrayList(); + var sourceArgs = new List(); foreach (object argument in RemotingDecoder.EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.PSEventArgsSourceArgs)) { sourceArgs.Add(argument); @@ -1946,48 +1713,48 @@ internal static PSEventArgs GetPSEventArgs(PSObject dataAsPSObject) } /// - /// decode and obtain the minimum runspaces to create in the - /// runspace pool from the data object specified + /// Decode and obtain the minimum runspaces to create in the + /// runspace pool from the data object specified. /// - /// data object to decode - /// minimum runspaces - internal static Int32 GetMinRunspaces(PSObject dataAsPSObject) + /// Data object to decode. + /// Minimum runspaces. + internal static int GetMinRunspaces(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } - return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MinRunspaces); + return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MinRunspaces); } /// - /// decode and obtain the maximum runspaces to create in the - /// runspace pool from the data object specified + /// Decode and obtain the maximum runspaces to create in the + /// runspace pool from the data object specified. /// - /// data object to decode - /// maximum runspaces - internal static Int32 GetMaxRunspaces(PSObject dataAsPSObject) + /// Data object to decode. + /// Maximum runspaces. + internal static int GetMaxRunspaces(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } - return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MaxRunspaces); + return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MaxRunspaces); } /// - /// decode and obtain the thread options for the runspaces in the - /// runspace pool from the data object specified + /// Decode and obtain the thread options for the runspaces in the + /// runspace pool from the data object specified. /// - /// data object to decode - /// thread options + /// Data object to decode. + /// Thread options. internal static PSPrimitiveDictionary GetApplicationArguments(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } // rehydration might not work yet (there is no type table before a runspace is created) @@ -1996,50 +1763,50 @@ internal static PSPrimitiveDictionary GetApplicationArguments(PSObject dataAsPSO } /// - /// Generates RunspacePoolInitInfo object from a received PSObject + /// Generates RunspacePoolInitInfo object from a received PSObject. /// - /// data object to decode - /// RunspacePoolInitInfo generated + /// Data object to decode. + /// RunspacePoolInitInfo generated. internal static RunspacePoolInitInfo GetRunspacePoolInitInfo(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } - int maxRS = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MaxRunspaces); - int minRS = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MinRunspaces); + int maxRS = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MaxRunspaces); + int minRS = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.MinRunspaces); return new RunspacePoolInitInfo(minRS, maxRS); } /// - /// decode and obtain the thread options for the runspaces in the - /// runspace pool from the data object specified + /// Decode and obtain the thread options for the runspaces in the + /// runspace pool from the data object specified. /// - /// data object to decode - /// thread options + /// Data object to decode. + /// Thread options. internal static PSThreadOptions GetThreadOptions(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.ThreadOptions); } /// - /// decode and obtain the host info for the host - /// associated with the runspace pool + /// Decode and obtain the host info for the host + /// associated with the runspace pool. /// - /// dataAsPSObject object to decode - /// host information + /// DataAsPSObject object to decode. + /// Host information. internal static HostInfo GetHostInfo(PSObject dataAsPSObject) { if (dataAsPSObject == null) { - throw PSTraceSource.NewArgumentNullException("dataAsPSObject"); + throw PSTraceSource.NewArgumentNullException(nameof(dataAsPSObject)); } PSObject propertyValue = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.HostInfo); @@ -2047,24 +1814,24 @@ internal static HostInfo GetHostInfo(PSObject dataAsPSObject) } /// - /// Gets the exception if any from the serialized state info object + /// Gets the exception if any from the serialized state info object. /// /// /// private static Exception GetExceptionFromStateInfoObject(PSObject stateInfo) { - //Check if exception is encoded as errorrecord + // Check if exception is encoded as errorrecord PSPropertyInfo property = stateInfo.Properties[RemoteDataNameStrings.ExceptionAsErrorRecord]; if (property != null && property.Value != null) { return GetExceptionFromSerializedErrorRecord(property.Value); } - //Exception is not present and return null. + // Exception is not present and return null. return null; } /// - /// Get the exception from serialized error record + /// Get the exception from serialized error record. /// /// /// @@ -2083,10 +1850,10 @@ internal static Exception GetExceptionFromSerializedErrorRecord(object serialize } /// - /// Gets the output from the message + /// Gets the output from the message. /// - /// object to decode - /// output object + /// Object to decode. + /// Output object. /// the current implementation does nothing, /// however this method is there in place as the /// packaging of output data may change in the future @@ -2096,14 +1863,13 @@ internal static object GetPowerShellOutput(object data) } /// - /// Gets the PSInvocationStateInfo from the data + /// Gets the PSInvocationStateInfo from the data. /// - /// object to decode - /// PSInvocationInfo + /// Object to decode. + /// 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); @@ -2115,15 +1881,15 @@ internal static PSInvocationStateInfo GetPowerShellStateInfo(object data) } /// - /// Gets the ErrorRecord from the message + /// Gets the ErrorRecord from the message. /// - /// data to decode - /// error record + /// Data to decode. + /// Error record. internal static ErrorRecord GetPowerShellError(object data) { if (data == null) { - throw PSTraceSource.NewArgumentNullException("data"); + throw PSTraceSource.NewArgumentNullException(nameof(data)); } PSObject dataAsPSObject = data as PSObject; @@ -2134,46 +1900,46 @@ internal static ErrorRecord GetPowerShellError(object data) } /// - /// Gets the WarningRecord from the message + /// Gets the WarningRecord from the message. /// internal static WarningRecord GetPowerShellWarning(object data) { if (data == null) { - throw PSTraceSource.NewArgumentNullException("data"); + throw PSTraceSource.NewArgumentNullException(nameof(data)); } return new WarningRecord((PSObject)data); } /// - /// Gets the VerboseRecord from the message + /// Gets the VerboseRecord from the message. /// internal static VerboseRecord GetPowerShellVerbose(object data) { if (data == null) { - throw PSTraceSource.NewArgumentNullException("data"); + throw PSTraceSource.NewArgumentNullException(nameof(data)); } return new VerboseRecord((PSObject)data); } /// - /// Gets the DebugRecord from the message + /// Gets the DebugRecord from the message. /// internal static DebugRecord GetPowerShellDebug(object data) { if (data == null) { - throw PSTraceSource.NewArgumentNullException("data"); + throw PSTraceSource.NewArgumentNullException(nameof(data)); } return new DebugRecord((PSObject)data); } /// - /// Gets the ProgressRecord from the message + /// Gets the ProgressRecord from the message. /// internal static ProgressRecord GetPowerShellProgress(object data) { @@ -2187,7 +1953,7 @@ internal static ProgressRecord GetPowerShellProgress(object data) } /// - /// Gets the InformationRecord from the message + /// Gets the InformationRecord from the message. /// internal static InformationRecord GetPowerShellInformation(object data) { @@ -2201,10 +1967,10 @@ internal static InformationRecord GetPowerShellInformation(object data) } /// - /// Gets the PowerShell object from the specified data + /// Gets the PowerShell object from the specified data. /// - /// data to decode - /// Deserialized PowerShell object + /// Data to decode. + /// Deserialized PowerShell object. internal static PowerShell GetPowerShell(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2218,10 +1984,10 @@ internal static PowerShell GetPowerShell(object data) } /// - /// Gets the PowerShell object from the specified data + /// Gets the PowerShell object from the specified data. /// - /// data to decode - /// Deserialized PowerShell object + /// Data to decode. + /// Deserialized PowerShell object. internal static PowerShell GetCommandDiscoveryPipeline(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2233,7 +1999,7 @@ internal static PowerShell GetCommandDiscoveryPipeline(object data) CommandTypes commandType = GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryType); string[] name; - if (null != GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryName)) + if (GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryName) != null) { IEnumerable tmp = EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.DiscoveryName); name = new List(tmp).ToArray(); @@ -2244,27 +2010,27 @@ internal static PowerShell GetCommandDiscoveryPipeline(object data) } string[] module; - if (null != GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryModule)) + if (GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryModule) != null) { IEnumerable tmp = EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.DiscoveryModule); module = new List(tmp).ToArray(); } else { - module = new string[] { "" }; + module = new string[] { string.Empty }; } ModuleSpecification[] fullyQualifiedName = null; - if (null != DeserializingTypeConverter.GetPropertyValue(dataAsPSObject, - RemoteDataNameStrings.DiscoveryFullyQualifiedModule, - DeserializingTypeConverter.RehydrationFlags.NullValueOk | DeserializingTypeConverter.RehydrationFlags.MissingPropertyOk)) + if (DeserializingTypeConverter.GetPropertyValue(dataAsPSObject, + RemoteDataNameStrings.DiscoveryFullyQualifiedModule, + DeserializingTypeConverter.RehydrationFlags.NullValueOk | DeserializingTypeConverter.RehydrationFlags.MissingPropertyOk) != null) { IEnumerable tmp = EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.DiscoveryFullyQualifiedModule); fullyQualifiedName = new List(tmp).ToArray(); } object[] argumentList; - if (null != GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryArgumentList)) + if (GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.DiscoveryArgumentList) != null) { IEnumerable tmp = EnumerateListProperty(dataAsPSObject, RemoteDataNameStrings.DiscoveryArgumentList); argumentList = new List(tmp).ToArray(); @@ -2286,15 +2052,16 @@ internal static PowerShell GetCommandDiscoveryPipeline(object data) { powerShell.AddParameter("Module", module); } + powerShell.AddParameter("ArgumentList", argumentList); return powerShell; } /// - /// Gets the NoInput setting from the specified data + /// Gets the NoInput setting from the specified data. /// - /// data to decode - /// true if there is no pipeline input; false otherwise + /// Data to decode. + /// if there is no pipeline input; otherwise. internal static bool GetNoInput(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2308,10 +2075,10 @@ internal static bool GetNoInput(object data) } /// - /// Gets the AddToHistory setting from the specified data + /// Gets the AddToHistory setting from the specified data. /// - /// data to decode - /// true if there is addToHistory data; false otherwise + /// Data to decode. + /// if there is addToHistory data; otherwise. internal static bool GetAddToHistory(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2325,10 +2092,10 @@ internal static bool GetAddToHistory(object data) } /// - /// Gets the IsNested setting from the specified data + /// Gets the IsNested setting from the specified data. /// - /// data to decode - /// true if there is IsNested data; false otherwise + /// Data to decode. + /// if there is IsNested data; otherwise. internal static bool GetIsNested(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2341,9 +2108,8 @@ internal static bool GetIsNested(object data) return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.IsNested); } -#if !CORECLR // No ApartmentState In CoreCLR /// - /// Gets the invocation settings information from the message + /// Gets the invocation settings information from the message. /// /// /// @@ -2352,9 +2118,9 @@ internal static ApartmentState GetApartmentState(object data) PSObject dataAsPSObject = PSObject.AsPSObject(data); return GetPropertyValue(dataAsPSObject, RemoteDataNameStrings.ApartmentState); } -#endif + /// - /// Gets the stream options from the message + /// Gets the stream options from the message. /// /// /// @@ -2365,15 +2131,13 @@ internal static RemoteStreamOptions GetRemoteStreamOptions(object data) } /// - /// Decodes a RemoteSessionCapability object + /// Decodes a RemoteSessionCapability object. /// - /// data to decode - /// RemoteSessionCapability object + /// Data to decode. + /// 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); @@ -2388,32 +2152,14 @@ 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; } /// - /// Checks if the server supports batch invocation + /// Checks if the server supports batch invocation. /// - /// runspace instance - /// true if batch invocation is supported, false if not + /// Runspace instance. + /// True if batch invocation is supported, false if not. internal static bool ServerSupportsBatchInvocation(Runspace runspace) { if (runspace == null || runspace.RunspaceStateInfo.State == RunspaceState.BeforeOpen) @@ -2421,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 new file mode 100644 index 00000000000..2c670b1eaaa --- /dev/null +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Management.Automation.Remoting +{ + /// + /// This class contains information about the debugging capability of the server side of the + /// MS-PSRDP connection. The functionality that is supported is determined by the PowerShell + /// version on the server. These capabilities will be used in remote debugging sessions to + /// determine what is supported by the server. + /// + internal sealed class RemoteDebuggingCapability + { + private readonly HashSet _supportedCommands = new HashSet(); + + internal Version PSVersion { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The version of PowerShell used on the remote server debugger. + /// + /// + /// This should only be invoked by the static create method. + /// + private RemoteDebuggingCapability(Version powerShellVersion) + { + PSVersion = powerShellVersion; + + // Commands available in all server versions + _supportedCommands.Add(RemoteDebuggingCommands.GetDebuggerStopArgs); + _supportedCommands.Add(RemoteDebuggingCommands.SetDebuggerAction); + _supportedCommands.Add(RemoteDebuggingCommands.SetDebugMode); + + if (PSVersion == null) + { + return; + } + + // Commands added in v5 + if (PSVersion.Major >= 5) + { + _supportedCommands.Add(RemoteDebuggingCommands.SetDebuggerStepMode); + _supportedCommands.Add(RemoteDebuggingCommands.SetUnhandledBreakpointMode); + } + + // Commands added in v7 + if (PSVersion.Major >= 7) + { + _supportedCommands.Add(RemoteDebuggingCommands.GetBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.SetBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.EnableBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.DisableBreakpoint); + _supportedCommands.Add(RemoteDebuggingCommands.RemoveBreakpoint); + } + } + + /// + /// Creates a instance that can be + /// used to identify the remoting capabilities of the server debugger. + /// + /// + /// The version of PowerShell used on the remote server debugger. + /// + /// + /// A new RemoteDebuggingCapability instance that is based on the version + /// of PowerShell used on the remote server debugger. + /// + internal static RemoteDebuggingCapability CreateDebuggingCapability(Version powerShellVersion) => + new RemoteDebuggingCapability(powerShellVersion); + + /// + /// Checks if a command is supported in the server version used to create + /// this instance. + /// + /// + /// The name of the command to check. + /// + /// + /// True if the command is supported; false otherwise. + /// + internal bool IsCommandSupported(string commandName) => + _supportedCommands.Contains(commandName); + } + + internal static class RemoteDebuggingCommands + { + #region DO NOT REMOVE OR CHANGE THE VALUES OF THESE CONSTANTS - it will break remote debugging compatibility with PowerShell + + // Commands related to debugger stop events + internal const string GetDebuggerStopArgs = "__Get-PSDebuggerStopArgs"; + internal const string SetDebuggerAction = "__Set-PSDebuggerAction"; + + // Miscellaneous debug commands + internal const string SetDebuggerStepMode = "__Set-PSDebuggerStepMode"; + internal const string SetDebugMode = "__Set-PSDebugMode"; + internal const string SetUnhandledBreakpointMode = "__Set-PSUnhandledBreakpointMode"; + + // Breakpoint commands + internal const string GetBreakpoint = "__Get-PSBreakpoint"; + internal const string SetBreakpoint = "__Set-PSBreakpoint"; + internal const string EnableBreakpoint = "__Enable-PSBreakpoint"; + internal const string DisableBreakpoint = "__Disable-PSBreakpoint"; + internal const string RemoveBreakpoint = "__Remove-PSBreakpoint"; + + #endregion + + internal static string CleanCommandName(string commandName) + { + return commandName.TrimStart('_'); + } + } +} 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 3ccc625612b..79b920c22b5 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; using System.Management.Automation.Host; +using System.Reflection; + using Dbg = System.Management.Automation.Diagnostics; using InternalHostUserInterface = System.Management.Automation.Internal.Host.InternalHostUserInterface; @@ -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. @@ -61,7 +61,7 @@ internal long CallId } /// - /// Computer name to be used in messages + /// Computer name to be used in messages. /// private string _computerName; @@ -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. @@ -284,11 +281,17 @@ private RemoteHostResponse ExecuteNonVoidMethodOnObject(object instance) private object SelectTargetObject(PSHost host) { if (host == null || host.UI == null) { return null; } + if (_methodInfo.InterfaceType == typeof(PSHost)) { return host; } + if (_methodInfo.InterfaceType == typeof(IHostSupportsInteractiveSession)) { return host; } + if (_methodInfo.InterfaceType == typeof(PSHostUserInterface)) { return host.UI; } + if (_methodInfo.InterfaceType == typeof(IHostUISupportsMultipleChoiceSelection)) { return host.UI; } + if (_methodInfo.InterfaceType == typeof(PSHostRawUserInterface)) { return host.UI.RawUI; } + throw RemoteHostExceptions.NewUnknownTargetClassException(_methodInfo.InterfaceType.ToString()); } @@ -320,23 +323,23 @@ internal bool IsSetShouldExitOrPopRunspace /// This message performs various security checks on the /// remote host call message. If there is a need to modify /// the message or discard it for security reasons then - /// such modifications will be made here + /// such modifications will be made here. /// /// computer name to use in /// warning messages - /// a collection of remote host calls which will + /// A collection of remote host calls which will /// have to be executed before this host call can be - /// executed - internal Collection PerformSecurityChecksOnHostMessage(String computerName) + /// executed. + internal Collection PerformSecurityChecksOnHostMessage(string computerName) { - Dbg.Assert(!String.IsNullOrEmpty(computerName), + Dbg.Assert(!string.IsNullOrEmpty(computerName), "Computer Name must be passed for use in warning messages"); _computerName = computerName; Collection prerequisiteCalls = new Collection(); // check if the incoming message is a PromptForCredential message // if so, do the following: - // (a) prepend "Windows PowerShell Credential Request" in the title + // (a) prepend "PowerShell Credential Request" in the title // (b) prepend "Message from Server XXXXX" to the text message if (MethodId == RemoteHostMethodId.PromptForCredential1 || MethodId == RemoteHostMethodId.PromptForCredential2) @@ -387,7 +390,7 @@ internal Collection PerformSecurityChecksOnHostMessage(String co computerName, RemotingErrorIdStrings.RemoteHostPromptSecureStringPrompt)); } } - }// end of foreach + } if (havePSCredential) { @@ -400,8 +403,8 @@ internal Collection PerformSecurityChecksOnHostMessage(String co Parameters[0] = modifiedCaption; Parameters[1] = modifiedMessage; } - }// end of if (parameters ... - }// if (remoteHostCall.MethodId ... + } + } // Check if the incoming message is a readline as secure string // if so do the following: @@ -430,11 +433,11 @@ internal Collection PerformSecurityChecksOnHostMessage(String co /// /// Provides the modified caption for the given caption /// Used in ensuring that remote prompt messages are - /// tagged with "Windows PowerShell Credential Request" + /// tagged with "PowerShell Credential Request" /// - /// caption to modify - /// new modified caption - private String ModifyCaption(string caption) + /// Caption to modify. + /// New modified caption. + private static string ModifyCaption(string caption) { string pscaption = CredUI.PromptForCredential_DefaultCaption; @@ -453,13 +456,13 @@ private String ModifyCaption(string caption) /// Provides the modified message for the given one /// Used in ensuring that remote prompt messages /// contain a warning that they originate from a - /// different computer + /// different computer. /// - /// original message to modify + /// Original message to modify. /// computername to include in the /// message - /// message which contains a warning as well - private String ModifyMessage(string message, string computerName) + /// Message which contains a warning as well. + private static string ModifyMessage(string message, string computerName) { string modifiedMessage = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.RemoteHostPromptForCredentialModifiedMessage, @@ -472,14 +475,14 @@ private String ModifyMessage(string message, string computerName) /// /// Creates a warning message which displays to the user a /// warning stating that the remote host computer is - /// actually attempting to read a line as a secure string + /// actually attempting to read a line as a secure string. /// /// computer name to include /// in warning - /// resource string to use - /// a constructed remote host call message - /// which will display the warning - private RemoteHostCall ConstructWarningMessageForSecureString(string computerName, + /// Resource string to use. + /// A constructed remote host call message + /// which will display the warning. + private static RemoteHostCall ConstructWarningMessageForSecureString(string computerName, string resourceString) { string warning = PSRemotingErrorInvariants.FormatResourceString( @@ -494,13 +497,13 @@ private RemoteHostCall ConstructWarningMessageForSecureString(string computerNam /// Creates a warning message which displays to the user a /// warning stating that the remote host computer is /// attempting to read the host's buffer contents and that - /// it was suppressed + /// it was suppressed. /// /// computer name to include /// in warning - /// a constructed remote host call message - /// which will display the warning - private RemoteHostCall ConstructWarningMessageForGetBufferContents(string computerName) + /// A constructed remote host call message + /// which will display the warning. + private static RemoteHostCall ConstructWarningMessageForGetBufferContents(string computerName) { string warning = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.RemoteHostGetBufferContents, @@ -524,7 +527,7 @@ internal class RemoteHostResponse /// /// Call id. /// - private long _callId; + private readonly long _callId; /// /// Call id. @@ -540,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. @@ -612,7 +615,9 @@ private static Exception DecodeException(PSObject psObject) { object result = RemoteHostEncoder.DecodePropertyValue(psObject, RemoteDataNameStrings.MethodException, typeof(Exception)); if (result == null) { return null; } + if (result is Exception) { return (Exception)result; } + throw RemoteHostExceptions.NewDecodingFailedException(); } 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 fd9ccc85034..cdaceda1610 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHostEncoder.cs @@ -1,15 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; -using System.Management.Automation.Host; using System.Globalization; -using System.Security; +using System.Management.Automation.Host; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Security; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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,13 +88,14 @@ 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) { FieldInfo fieldInfo = type.GetField(propertyInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo.Value == null) { throw RemoteHostExceptions.NewDecodingFailedException(); } + object fieldValue = DecodeObject(propertyInfo.Value, fieldInfo.FieldType); if (fieldValue == null) { throw RemoteHostExceptions.NewDecodingFailedException(); } @@ -108,7 +110,7 @@ private static object DecodeClassOrStruct(PSObject psObject, Type type) /// private static bool IsCollection(Type type) { - return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Collection<>)); + return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Collection<>)); } private static bool IsGenericIEnumerableOfInt(Type type) @@ -156,7 +158,7 @@ private static IList DecodeCollection(PSObject psObject, Type collectionType) /// private static bool IsDictionary(Type type) { - return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Dictionary<,>)); + return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Dictionary<,>)); } /// @@ -336,7 +338,7 @@ internal static object EncodeObject(object obj) { return obj; } - else if (type.GetTypeInfo().IsEnum) + else if (type.IsEnum) { return (int)obj; } @@ -441,16 +443,17 @@ internal static object DecodeObject(object obj, Type type) PSCredential cred = null; try { - cred = new PSCredential((String)objAsPSObject.Properties["UserName"].Value, + cred = new PSCredential((string)objAsPSObject.Properties["UserName"].Value, (SecureString)objAsPSObject.Properties["Password"].Value); } catch (GetValueException) { cred = null; } + return cred; } - else if (obj is int && type.GetTypeInfo().IsEnum) + else if (obj is int && type.IsEnum) { return Enum.ToObject(type, (int)obj); } @@ -685,8 +688,10 @@ private static bool IsObjectDictionaryType(Type dictionaryType) { // True if the value-type of the dictionary is object; false otherwise. if (!IsDictionary(dictionaryType)) { return false; } + Type[] elementTypes = dictionaryType.GetGenericArguments(); if (elementTypes.Length != 2) { return false; } + Type valueType = elementTypes[1]; return valueType == typeof(object); } @@ -740,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(); } @@ -767,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 69863ade1b9..add424b8703 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteSessionCapability.cs @@ -1,12 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; 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; namespace System.Management.Automation.Remoting @@ -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 @@ -36,6 +33,7 @@ internal Version ProtocolVersion { return _protocolVersion; } + set { _protocolVersion = value; @@ -43,7 +41,9 @@ internal Version ProtocolVersion } internal Version PSVersion { get { return _psversion; } } + internal Version SerializationVersion { get { return _serversion; } } + internal RemotingDestination RemotingDestination { get { return _remotingDestination; } } /// @@ -57,7 +57,7 @@ internal RemoteSessionCapability(RemotingDestination remotingDestination) // PS Version 3 is fully backward compatible with Version 2 // In the remoting protocol sense, nothing is changing between PS3 and PS2 // For negotiation to succeed with old client/servers we have to use 2. - _psversion = new Version(2,0); //PSVersionInfo.PSVersion; + _psversion = new Version(2, 0); // PSVersionInfo.PSVersion; _serversion = PSVersionInfo.SerializationVersion; _remotingDestination = remotingDestination; } @@ -88,61 +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 (null == _timeZoneInByteFormat) - { - 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 (null != e) - { - _timeZoneInByteFormat = Utils.EmptyArray(); - } - } - - return _timeZoneInByteFormat; - } - - /// - /// Gets the TimeZone of the destination machine. This may be null - /// - internal TimeZoneInfo TimeZone - { - get { return _timeZone; } - set { _timeZone = value; } - } } /// @@ -165,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 @@ -354,7 +298,7 @@ internal bool IsHostNull /// /// Is host ui null. /// - private bool _isHostUINull; + private readonly bool _isHostUINull; /// /// Is host ui null. @@ -370,7 +314,7 @@ internal bool IsHostUINull /// /// Is host raw ui null. /// - private bool _isHostRawUINull; + private readonly bool _isHostRawUINull; private readonly bool _isHostNull; @@ -398,6 +342,7 @@ internal bool IsHostRawUINull internal bool UseRunspaceHost { get { return _useRunspaceHost; } + set { _useRunspaceHost = value; } } @@ -442,11 +387,19 @@ 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 48e13f2b1df..14cad7613b8 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemotingDataObject.cs @@ -1,17 +1,17 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.IO; + using Dbg = System.Management.Automation.Diagnostics; 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 @@ -110,7 +110,6 @@ internal RemotingTargetInterface TargetInterface #endregion Properties /// - /// /// /// /// @@ -127,7 +126,6 @@ internal static RemoteDataObject CreateFrom(RemotingDestination destination, return new RemoteDataObject(destination, dataType, runspacePoolId, powerShellId, data); } - /// /// Creates a RemoteDataObject by deserializing . /// @@ -138,8 +136,8 @@ internal static RemoteDataObject CreateFrom(RemotingDestination destination, /// internal static RemoteDataObject CreateFrom(Stream serializedDataStream, Fragmentor defragmentor) { - Dbg.Assert(null != serializedDataStream, "cannot construct a RemoteDataObject from null data"); - Dbg.Assert(null != defragmentor, "defragmentor cannot be null."); + Dbg.Assert(serializedDataStream != null, "cannot construct a RemoteDataObject from null data"); + Dbg.Assert(defragmentor != null, "defragmentor cannot be null."); if ((serializedDataStream.Length - serializedDataStream.Position) < headerLength) { @@ -179,11 +177,11 @@ internal static RemoteDataObject CreateFrom(Stream serializedDataStream, Frag /// internal virtual void Serialize(Stream streamToWriteTo, Fragmentor fragmentor) { - Dbg.Assert(null != streamToWriteTo, "Stream to write to cannot be null."); - Dbg.Assert(null != fragmentor, "Fragmentor cannot be null."); + Dbg.Assert(streamToWriteTo != null, "Stream to write to cannot be null."); + Dbg.Assert(fragmentor != null, "Fragmentor cannot be null."); SerializeHeader(streamToWriteTo); - if (null != Data) + if (Data != null) { fragmentor.SerializeToBytes(Data, streamToWriteTo); } @@ -201,7 +199,7 @@ internal virtual void Serialize(Stream streamToWriteTo, Fragmentor fragmentor) /// private void SerializeHeader(Stream streamToWriteTo) { - Dbg.Assert(null != streamToWriteTo, "stream to write to cannot be null"); + Dbg.Assert(streamToWriteTo != null, "stream to write to cannot be null"); // Serialize destination SerializeUInt((uint)Destination, streamToWriteTo); @@ -215,9 +213,9 @@ private void SerializeHeader(Stream streamToWriteTo) return; } - private void SerializeUInt(uint data, Stream streamToWriteTo) + private static void SerializeUInt(uint data, Stream streamToWriteTo) { - Dbg.Assert(null != streamToWriteTo, "stream to write to cannot be null"); + Dbg.Assert(streamToWriteTo != null, "stream to write to cannot be null"); byte[] result = new byte[4]; // size of int @@ -243,9 +241,9 @@ 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(null != streamToWriteTo, "stream to write to cannot be null"); + Dbg.Assert(streamToWriteTo != null, "stream to write to cannot be null"); byte[] guidArray = guid.ToByteArray(); @@ -269,12 +267,11 @@ private static Guid DeserializeGuid(Stream serializedDataStream) #endregion } - internal class RemoteDataObject : RemoteDataObject + internal sealed class RemoteDataObject : RemoteDataObject { #region Constructors / Factory /// - /// /// /// /// @@ -290,7 +287,6 @@ private RemoteDataObject(RemotingDestination destination, } /// - /// /// /// /// @@ -298,7 +294,7 @@ private RemoteDataObject(RemotingDestination destination, /// /// /// - internal new static RemoteDataObject CreateFrom(RemotingDestination destination, + internal static new RemoteDataObject CreateFrom(RemotingDestination destination, RemotingDataType dataType, Guid runspacePoolId, Guid powerShellId, diff --git a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs index 8208b3a8aff..c451ba32f45 100644 --- a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs +++ b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs @@ -1,14 +1,14 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Management.Automation.Internal; using System.Management.Automation.Tracing; -using System.Xml; using System.Text; -using System.Management.Automation.Internal; +using System.Xml; + using Dbg = System.Management.Automation.Diagnostics; using TypeTable = System.Management.Automation.Runspaces.TypeTable; @@ -74,7 +74,7 @@ internal class FragmentedRemoteObject #region Constructors /// - /// Default Constructor + /// Default Constructor. /// internal FragmentedRemoteObject() { @@ -98,14 +98,14 @@ internal FragmentedRemoteObject() internal FragmentedRemoteObject(byte[] blob, long objectId, long fragmentId, bool isEndFragment) { - Dbg.Assert((null != blob) || (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; @@ -117,7 +117,7 @@ internal FragmentedRemoteObject(byte[] blob, long objectId, long fragmentId, #region Data Fields being sent /// - /// All fragments of the same PSObject have the same ObjectId + /// All fragments of the same PSObject have the same ObjectId. /// internal long ObjectId { get; set; } @@ -142,7 +142,11 @@ internal FragmentedRemoteObject(byte[] blob, long objectId, long fragmentId, /// internal int BlobLength { - get { return _blobLength; } + get + { + return _blobLength; + } + set { Dbg.Assert(value >= 0, "BlobLength cannot be less than 0."); @@ -155,10 +159,14 @@ internal int BlobLength /// internal byte[] Blob { - get { return _blob; } + get + { + return _blob; + } + set { - Dbg.Assert(null != value, "Blob cannot be null"); + Dbg.Assert(value != null, "Blob cannot be null"); _blob = value; } } @@ -197,17 +205,16 @@ internal byte[] Blob /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// | Blob ... /// +-+-+-+-+-+-+-+- - /// /// /// /// The binary encoded FragmentedRemoteObject to be ready to pass to WinRS Send API. /// 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; @@ -276,7 +283,7 @@ internal byte[] GetBytes() /// internal static long GetObjectId(byte[] fragmentBytes, int startIndex) { - Dbg.Assert(null != fragmentBytes, "fragmentBytes cannot be null"); + Dbg.Assert(fragmentBytes != null, "fragmentBytes cannot be null"); Dbg.Assert(fragmentBytes.Length >= HeaderLength, "not enough data to decode object id"); long objectId = 0; @@ -310,7 +317,7 @@ internal static long GetObjectId(byte[] fragmentBytes, int startIndex) /// internal static long GetFragmentId(byte[] fragmentBytes, int startIndex) { - Dbg.Assert(null != fragmentBytes, "fragmentBytes cannot be null"); + Dbg.Assert(fragmentBytes != null, "fragmentBytes cannot be null"); Dbg.Assert(fragmentBytes.Length >= HeaderLength, "not enough data to decode fragment id"); long fragmentId = 0; int idx = startIndex + _fragmentIdOffset; @@ -345,7 +352,7 @@ internal static long GetFragmentId(byte[] fragmentBytes, int startIndex) /// internal static bool GetIsStartFragment(byte[] fragmentBytes, int startIndex) { - Dbg.Assert(null != fragmentBytes, "fragment cannot be null"); + Dbg.Assert(fragmentBytes != null, "fragment cannot be null"); Dbg.Assert(fragmentBytes.Length >= HeaderLength, "not enough data to decode if it is a start fragment."); if ((fragmentBytes[startIndex + _flagsOffset] & SFlag) != 0) @@ -363,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. @@ -374,7 +381,7 @@ internal static bool GetIsStartFragment(byte[] fragmentBytes, int startIndex) /// internal static bool GetIsEndFragment(byte[] fragmentBytes, int startIndex) { - Dbg.Assert(null != fragmentBytes, "fragment cannot be null"); + Dbg.Assert(fragmentBytes != null, "fragment cannot be null"); Dbg.Assert(fragmentBytes.Length >= HeaderLength, "not enough data to decode if it is an end fragment."); if ((fragmentBytes[startIndex + _flagsOffset] & EFlag) != 0) @@ -403,7 +410,7 @@ internal static bool GetIsEndFragment(byte[] fragmentBytes, int startIndex) /// internal static int GetBlobLength(byte[] fragmentBytes, int startIndex) { - Dbg.Assert(null != fragmentBytes, "fragment cannot be null"); + Dbg.Assert(fragmentBytes != null, "fragment cannot be null"); Dbg.Assert(fragmentBytes.Length >= HeaderLength, "not enough data to decode blob length."); int blobLength = 0; @@ -418,7 +425,6 @@ internal static int GetBlobLength(byte[] fragmentBytes, int startIndex) } } - /// /// A stream used to store serialized data. This stream holds serialized data in the /// form of fragments. Every "fragment size" data will hold a blob identifying the fragment. @@ -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; @@ -467,6 +473,7 @@ internal class SerializedDataStream : Stream, IDisposable /// true if data represents EndFragment of an object. /// internal delegate void OnDataAvailableCallback(byte[] data, bool isEndFragment); + private OnDataAvailableCallback _onDataAvailableCallback; #endregion @@ -505,7 +512,7 @@ internal SerializedDataStream(int fragmentSize) internal SerializedDataStream(int fragmentSize, OnDataAvailableCallback callbackToNotify) : this(fragmentSize) { - if (null != callbackToNotify) + if (callbackToNotify != null) { _notifyOnWriteFragmentImmediately = true; _onDataAvailableCallback = callbackToNotify; @@ -577,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); @@ -639,7 +646,7 @@ internal byte[] ReadOrRegisterCallback(OnDataAvailableCallback callback) } /// - /// Read the currently accumulated data in queued memory streams + /// Read the currently accumulated data in queued memory streams. /// /// internal byte[] Read() @@ -666,7 +673,6 @@ internal byte[] Read() } /// - /// /// /// /// @@ -693,7 +699,7 @@ public override int Read(byte[] buffer, int offset, int count) while (dataWritten < count) { - if (null == _readStream) + if (_readStream == null) { if (_queuedStreams.Count > 0) { @@ -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 @@ -781,7 +787,7 @@ private void WriteCurrentFragmentAndReset() return; } - if (null == _writeStream) + if (_writeStream == null) { _writeStream = new MemoryStream(_fragmentSize); s_trace.WriteLine("Created write stream: {0}", _writeStream.GetHashCode()); @@ -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 (null != _onDataAvailableCallback) - { - _onDataAvailableCallback(data, _currentFragment.IsEndFragment); - } + _onDataAvailableCallback?.Invoke(data, _currentFragment.IsEndFragment); // prepare a new fragment _currentFragment.FragmentId = ++_fragmentId; @@ -864,12 +867,12 @@ protected override void Dispose(bool disposing) } } - if ((null != _readStream) && (_readStream.CanRead)) + if ((_readStream != null) && (_readStream.CanRead)) { _readStream.Dispose(); } - if ((null != _writeStream) && (_writeStream.CanRead)) + if ((_writeStream != null) && (_writeStream.CanRead)) { _writeStream.Dispose(); } @@ -884,27 +887,27 @@ protected override void Dispose(bool disposing) #region Stream Overrides /// - /// /// public override bool CanRead { get { return true; } } + /// - /// /// public override bool CanSeek { get { return false; } } + /// - /// /// public override bool CanWrite { get { return true; } } /// /// Gets the length of the stream in bytes. /// public override long Length { get { return _length; } } + /// - /// /// public override long Position { get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } } /// @@ -914,8 +917,8 @@ public override long Position public override void Flush() { } + /// - /// /// /// /// @@ -924,8 +927,8 @@ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + /// - /// /// /// public override void SetLength(long value) @@ -946,6 +949,7 @@ public override void SetLength(long value) GC.SuppressFinalize(this); _disposed = true; } + base.Dispose(); } @@ -961,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 @@ -1010,8 +1014,8 @@ internal Fragmentor(int fragmentSize, PSRemotingCryptoHelper cryptoHelper) /// internal void Fragment(RemoteDataObject obj, SerializedDataStream dataToBeSent) { - Dbg.Assert(null != obj, "Cannot fragment a null object"); - Dbg.Assert(null != dataToBeSent, "SendDataCollection cannot be null"); + Dbg.Assert(obj != null, "Cannot fragment a null object"); + Dbg.Assert(dataToBeSent != null, "SendDataCollection cannot be null"); dataToBeSent.Enter(); try @@ -1039,6 +1043,7 @@ internal int FragmentSize { return _fragmentSize; } + set { Dbg.Assert(value > 0, "FragmentSize cannot be less than 0."); @@ -1056,8 +1061,8 @@ internal int FragmentSize /// internal void SerializeToBytes(object obj, Stream streamToWriteTo) { - Dbg.Assert(null != obj, "Cannot serialize a null object"); - Dbg.Assert(null != streamToWriteTo, "Stream to write to cannot be null"); + Dbg.Assert(obj != null, "Cannot serialize a null object"); + Dbg.Assert(streamToWriteTo != null, "Stream to write to cannot be null"); XmlWriterSettings xmlSettings = new XmlWriterSettings(); xmlSettings.CheckCharacters = false; @@ -1097,7 +1102,7 @@ internal void SerializeToBytes(object obj, Stream streamToWriteTo) /// internal PSObject DeserializeToPSObject(Stream serializedDataStream) { - Dbg.Assert(null != serializedDataStream, "Cannot Deserialize null data"); + Dbg.Assert(serializedDataStream != null, "Cannot Deserialize null data"); Dbg.Assert(serializedDataStream.Length != 0, "Cannot Deserialize empty data"); object result = null; diff --git a/src/System.Management.Automation/engine/remoting/common/misc.cs b/src/System.Management.Automation/engine/remoting/common/misc.cs index 863b00c7169..e5b1f1edc04 100644 --- a/src/System.Management.Automation/engine/remoting/common/misc.cs +++ b/src/System.Management.Automation/engine/remoting/common/misc.cs @@ -1,8 +1,8 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Remoting; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -17,7 +17,7 @@ internal RemoteSessionNegotiationEventArgs(RemoteSessionCapability remoteSession if (remoteSessionCapability == null) { - throw PSTraceSource.NewArgumentNullException("remoteSessionCapability"); + throw PSTraceSource.NewArgumentNullException(nameof(remoteSessionCapability)); } RemoteSessionCapability = remoteSessionCapability; @@ -36,7 +36,6 @@ internal RemoteSessionNegotiationEventArgs(RemoteSessionCapability remoteSession internal RemoteDataObject RemoteData { get; set; } } - /// /// This event arg is designed to contain generic data received from the other side of the connection. /// It can be used for both the client side and for the server side. @@ -51,7 +50,7 @@ internal RemoteDataEventArgs(RemoteDataObject receivedData) if (receivedData == null) { - throw PSTraceSource.NewArgumentNullException("receivedData"); + throw PSTraceSource.NewArgumentNullException(nameof(receivedData)); } ReceivedData = receivedData; @@ -67,7 +66,7 @@ internal RemoteDataEventArgs(RemoteDataObject receivedData) /// /// This event arg contains data received and is used to pass information - /// from a data structure handler to its object + /// from a data structure handler to its object. /// /// type of data that's associated internal sealed class RemoteDataEventArgs : EventArgs @@ -79,7 +78,7 @@ internal sealed class RemoteDataEventArgs : EventArgs #region Properties /// - /// The data contained within this event + /// The data contained within this event. /// internal T Data { get; } @@ -89,7 +88,7 @@ internal sealed class RemoteDataEventArgs : EventArgs internal RemoteDataEventArgs(object data) { - //Dbg.Assert(data != null, "data passed should not be null"); + // Dbg.Assert(data != null, "data passed should not be null"); Data = (T)data; } @@ -103,14 +102,13 @@ internal RemoteDataEventArgs(object data) internal enum RemoteSessionState { /// - /// Undefined state + /// Undefined state. /// UndefinedState = 0, /// /// This is the state a connect start with. When a connection is closed, /// the connection will eventually come back to this Idle state. - /// /// Idle = 1, @@ -125,12 +123,12 @@ internal enum RemoteSessionState Connected = 3, /// - /// The capability negotiation message is in the process being sent on a create operation + /// The capability negotiation message is in the process being sent on a create operation. /// NegotiationSending = 4, /// - /// The capability negotiation message is in the process being sent on a connect operation + /// The capability negotiation message is in the process being sent on a connect operation. /// NegotiationSendingOnConnect = 5, @@ -166,22 +164,22 @@ internal enum RemoteSessionState /// /// Have sent a public key to the remote end, - /// awaiting a response + /// awaiting a response. /// /// Applicable only to client EstablishedAndKeySent = 12, /// /// Have received a public key from the remote - /// end, need to send a response + /// end, need to send a response. /// /// Applicable only to server EstablishedAndKeyReceived = 13, /// - /// for Server - Have sent a request to the remote end to + /// For Server - Have sent a request to the remote end to /// send a public key - /// for Client - have received a PK request from server + /// for Client - have received a PK request from server. /// /// Applicable to both client and server EstablishedAndKeyRequested = 14, @@ -193,22 +191,19 @@ internal enum RemoteSessionState /// a public key - this is for the server /// (b) Received an encrypted session key from /// remote end after sending a public key - - /// this is for the client + /// this is for the client. /// EstablishedAndKeyExchanged = 15, /// - /// /// Disconnecting = 16, /// - /// /// Disconnected = 17, /// - /// /// Reconnecting = 18, @@ -219,7 +214,7 @@ internal enum RemoteSessionState RCDisconnecting = 19, /// - /// Number of states + /// Number of states. /// MaxState = 20 } @@ -289,13 +284,12 @@ internal RemoteSessionStateInfo(RemoteSessionStateInfo sessionStateInfo) Reason = sessionStateInfo.Reason; } - #endregion Constructors #region Public_Properties /// - /// State of the connection + /// State of the connection. /// internal RemoteSessionState State { get; } @@ -308,7 +302,6 @@ internal RemoteSessionStateInfo(RemoteSessionStateInfo sessionStateInfo) } /// - /// /// This is the event arg that contains the state information. /// internal class RemoteSessionStateEventArgs : EventArgs @@ -321,7 +314,7 @@ internal RemoteSessionStateEventArgs(RemoteSessionStateInfo remoteSessionStateIn if (remoteSessionStateInfo == null) { - PSTraceSource.NewArgumentNullException("remoteSessionStateInfo"); + PSTraceSource.NewArgumentNullException(nameof(remoteSessionStateInfo)); } SessionStateInfo = remoteSessionStateInfo; diff --git a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs index b686d7d9792..f97b2b21d1f 100644 --- a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs +++ b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Runspaces; using System.Text; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting.Internal @@ -15,68 +15,56 @@ namespace System.Management.Automation.Remoting.Internal public enum PSStreamObjectType { /// - /// /// Output = 1, /// - /// /// Error = 2, /// - /// /// MethodExecutor = 3, /// - /// /// Warning = 4, /// - /// /// BlockingError = 5, /// - /// /// ShouldMethod = 6, /// - /// /// WarningRecord = 7, /// - /// /// Debug = 8, /// - /// /// Progress = 9, /// - /// /// Verbose = 10, /// - /// /// Information = 11, /// - /// /// Exception = 12, } /// - /// struct which describes whether an object written + /// Struct which describes whether an object written /// to an ObjectStream is of type - output, error, /// verbose, debug. /// PSStreamObject is for internal (PowerShell) consumption @@ -85,13 +73,14 @@ public enum PSStreamObjectType public class PSStreamObject { /// - /// /// public PSStreamObjectType ObjectType { get; set; } - internal Object Value { get; set; } + + internal object Value { get; set; } + internal Guid Id { get; set; } - internal PSStreamObject(PSStreamObjectType objectType, Object value, Guid id) + internal PSStreamObject(PSStreamObjectType objectType, object value, Guid id) { ObjectType = objectType; Value = value; @@ -99,20 +88,19 @@ 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) { } /// /// Handle the object obtained from an ObjectStream's reader - /// based on its type + /// based on its type. /// - /// cmdlet to use for outputting the object + /// Cmdlet to use for outputting the object. /// Used by Receive-Job to suppress inquire preference. public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) { @@ -124,6 +112,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) { cmdlet.WriteObject(this.Value); } + break; case PSStreamObjectType.Error: @@ -131,11 +120,9 @@ 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; case PSStreamObjectType.Debug: @@ -143,11 +130,9 @@ 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; case PSStreamObjectType.Warning: @@ -155,11 +140,9 @@ 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; case PSStreamObjectType.Verbose: @@ -167,42 +150,34 @@ 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; case PSStreamObjectType.Progress: { MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteProgress((ProgressRecord)Value, overrideInquire); - } + mshCommandRuntime?.WriteProgress((ProgressRecord)Value, overrideInquire); } + break; case PSStreamObjectType.Information: { MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.WriteInformation((InformationRecord)Value, overrideInquire); - } + mshCommandRuntime?.WriteInformation((InformationRecord)Value, overrideInquire); } + break; case PSStreamObjectType.WarningRecord: { WarningRecord warningRecord = (WarningRecord)Value; MshCommandRuntime mshCommandRuntime = cmdlet.CommandRuntime as MshCommandRuntime; - if (mshCommandRuntime != null) - { - mshCommandRuntime.AppendWarningVarList(warningRecord); - } + mshCommandRuntime?.AppendWarningVarList(warningRecord); } + break; case PSStreamObjectType.MethodExecutor: @@ -212,6 +187,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) ClientMethodExecutor methodExecutor = (ClientMethodExecutor)Value; methodExecutor.Execute(cmdlet); } + break; case PSStreamObjectType.BlockingError: @@ -219,6 +195,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) CmdletMethodInvoker methodInvoker = (CmdletMethodInvoker)Value; InvokeCmdletMethodAndWaitForResults(methodInvoker, cmdlet); } + break; case PSStreamObjectType.ShouldMethod: @@ -226,6 +203,7 @@ public void WriteStreamObject(Cmdlet cmdlet, bool overrideInquire = false) CmdletMethodInvoker methodInvoker = (CmdletMethodInvoker)Value; InvokeCmdletMethodAndWaitForResults(methodInvoker, cmdlet); } + break; case PSStreamObjectType.Exception: @@ -247,22 +225,31 @@ 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]; } /// /// Handle the object obtained from an ObjectStream's reader - /// based on its type + /// based on its type. /// - /// cmdlet to use for outputting the object + /// Cmdlet to use for outputting the object. /// /// Suppresses prompt on messages with Inquire preference. /// Needed for Receive-Job @@ -278,8 +265,10 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq if (o != null) AddSourceJobNoteProperty(o, instanceId); } + cmdlet.WriteObject(Value); } + break; case PSStreamObjectType.Error: @@ -310,11 +299,9 @@ 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; case PSStreamObjectType.Warning: @@ -322,11 +309,9 @@ 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; case PSStreamObjectType.Verbose: @@ -334,11 +319,9 @@ 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; case PSStreamObjectType.Progress: @@ -361,11 +344,9 @@ 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; case PSStreamObjectType.Debug: @@ -373,11 +354,9 @@ 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; case PSStreamObjectType.Information: @@ -389,7 +368,7 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq { // if we get a base InformationRecord object, check if the computerName is // populated in the Source field - if (!String.IsNullOrEmpty(informationRecord.Source)) + if (!string.IsNullOrEmpty(informationRecord.Source)) { string computerName; Guid jobInstanceId; @@ -405,11 +384,9 @@ 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; case PSStreamObjectType.WarningRecord: @@ -419,15 +396,16 @@ internal void WriteStreamObject(Cmdlet cmdlet, Guid instanceId, bool overrideInq { WriteStreamObject(cmdlet, overrideInquire); } + break; } } /// /// Handle the object obtained from an ObjectStream's reader - /// based on its type + /// based on its type. /// - /// cmdlet to use for outputting the object + /// Cmdlet to use for outputting the object. /// /// Overrides the inquire preference, used in Receive-Job to suppress prompts. internal void WriteStreamObject(Cmdlet cmdlet, bool writeSourceIdentifier, bool overrideInquire) @@ -457,14 +435,12 @@ private static void InvokeCmdletMethodAndWaitForResults(CmdletMethodInvoker - /// /// public class CmdletMethodInvoker { /// - /// /// public Func Action { get; set; } /// - /// /// public Exception ExceptionThrownOnCmdletThread { get; set; } /// - /// /// public ManualResetEventSlim Finished { get; set; } /// - /// /// public object SyncObject { get; set; } /// - /// /// public T MethodResult { get; set; } } diff --git a/src/System.Management.Automation/engine/remoting/common/remotesession.cs b/src/System.Management.Automation/engine/remoting/common/remotesession.cs index 43a26a440ee..f67b383c291 100644 --- a/src/System.Management.Automation/engine/remoting/common/remotesession.cs +++ b/src/System.Management.Automation/engine/remoting/common/remotesession.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Remoting; @@ -33,4 +32,3 @@ internal abstract class RemoteSession #endregion KeyExchange } } - diff --git a/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs b/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs index 4a307b767bd..8830bb32152 100644 --- a/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs +++ b/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs @@ -1,9 +1,8 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; using System.Management.Automation.Internal; +using System.Runtime.Serialization; using System.Security.Permissions; namespace System.Management.Automation.Remoting @@ -19,6 +18,7 @@ internal enum PSRemotingErrorId : uint // OS related 1-9 DefaultRemotingExceptionMessage = 0, OutOfMemory = 1, + UnsupportedOSForRemoteEnumeration = 2, // Pipeline related range: 10-99 PipelineIdsDoNotMatch = 10, @@ -128,6 +128,7 @@ internal enum PSRemotingErrorId : uint CannotSetStdOutHandle = 822, CannotSetStdErrHandle = 823, InvalidConfigurationName = 824, + ConnectSkipCheckFailed = 825, // Error codes added to support new WSMan Fan-In Model API CreateSessionFailed = 851, CreateExFailed = 853, @@ -209,7 +210,6 @@ internal enum PSRemotingErrorId : uint RemoteRunspaceHasMultipleMatchesForSpecifiedName = 955, RemoteRunspaceDoesNotSupportPushRunspace = 956, HostInNestedPrompt = 957, - RemoteHostDoesNotSupportPushRunspace = 958, InvalidVMId = 959, InvalidVMNameNoVM = 960, InvalidVMNameMultipleVM = 961, @@ -254,7 +254,6 @@ internal enum PSRemotingErrorId : uint IPCExceptionLaunchingProcess = 2107, } - /// /// This static class defines the resource base name used by remoting errors. /// It also provides a convenience method to get the localized strings. @@ -268,7 +267,7 @@ internal static class PSRemotingErrorInvariants /// This parameter holds the string in the resource file. /// /// - /// Optional parameters required by the resource string formating information. + /// Optional parameters required by the resource string formatting information. /// /// /// The formatted localized string. @@ -284,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 @@ -299,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. @@ -311,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. @@ -341,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. @@ -363,12 +361,12 @@ 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,11 +379,9 @@ 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; @@ -414,8 +410,6 @@ public PSRemotingTransportException(string message) SetDefaultErrorRecord(); } - - /// /// This constructor takes a localized message and an inner exception. /// @@ -451,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. @@ -476,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("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("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. /// @@ -527,6 +496,7 @@ public int ErrorCode { return _errorCode; } + set { _errorCode = value; @@ -542,6 +512,7 @@ public string TransportMessage { return _transportMessage; } + set { _transportMessage = value; @@ -553,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 @@ -593,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. @@ -617,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("info"); - } - - RedirectLocation = info.GetString("RedirectLocation"); + throw new NotSupportedException(); } /// @@ -651,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("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. @@ -685,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 f987027c1d9..7d4a88e98bd 100644 --- a/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs +++ b/src/System.Management.Automation/engine/remoting/common/throttlemanager.cs @@ -1,9 +1,9 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -11,35 +11,35 @@ namespace System.Management.Automation.Remoting #region OperationState /// - /// Defines the different states of the operation + /// Defines the different states of the operation. /// internal enum OperationState { /// - /// Start operation completed successfully + /// Start operation completed successfully. /// StartComplete = 0, /// - /// Stop operation completed successfully + /// Stop operation completed successfully. /// StopComplete = 1, } /// - /// class describing event args which a helper class - /// implementing IThrottleOperation need to throw + /// Class describing event args which a helper class + /// implementing IThrottleOperation need to throw. /// internal sealed class OperationStateEventArgs : EventArgs { /// - /// operation state + /// Operation state. /// internal OperationState OperationState { get; set; } /// - /// the original event which actually resulted in this - /// event being raised + /// The original event which actually resulted in this + /// event being raised. /// internal EventArgs BaseEvent { get; set; } } @@ -50,7 +50,7 @@ internal sealed class OperationStateEventArgs : EventArgs /// /// Interface which needs to be implemented by a class which wants to - /// submit operations to the throttle manager + /// submit operations to the throttle manager. /// /// Any synchronization that needs to be performed between /// StartOperation and StopOperation in the class that implements this @@ -70,7 +70,7 @@ internal abstract class IThrottleOperation /// an event is successfully received as a result of this function, /// the handler has to ensure that it raises an OperationComplete /// event with StartComplete or StopComplete for the throttle manager - /// to handle + /// to handle. /// internal abstract void StartOperation(); @@ -85,7 +85,7 @@ internal abstract class IThrottleOperation /// raises an OperationComplete event with StopComplete for the /// throttle manager to handle. It is important that this function /// does not raise a StartComplete which will then result in the - /// ThrottleComplete event not being raised by the throttle manager + /// ThrottleComplete event not being raised by the throttle manager. /// internal abstract void StopOperation(); @@ -101,7 +101,7 @@ internal abstract class IThrottleOperation internal abstract event EventHandler OperationComplete; /// - /// This Property indicates whether an operation has been stopped + /// This Property indicates whether an operation has been stopped. /// /// /// In the initial implementation of ThrottleManager stopping @@ -117,18 +117,20 @@ 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 { return _ignoreStop; } + set { _ignoreStop = true; } } + private bool _ignoreStop = false; #region Runspace Debug @@ -157,18 +159,16 @@ internal bool RunspaceDebugStepInEnabled internal event EventHandler RunspaceDebugStop; /// - /// RaiseRunspaceDebugStopEvent + /// RaiseRunspaceDebugStopEvent. /// - /// Runspace + /// Runspace. internal void RaiseRunspaceDebugStopEvent(System.Management.Automation.Runspaces.Runspace runspace) { RunspaceDebugStop.SafeInvoke(this, new StartRunspaceDebugProcessingEventArgs(runspace)); } - #endregion - - } // IThrottleOperation + } #endregion IThrottleOperation @@ -191,8 +191,7 @@ internal void RaiseRunspaceDebugStopEvent(System.Management.Automation.Runspaces /// handler will start an operation once a previous event is completed. /// /// The queue used is a generic queue of type IThrottleOperations, as it will offer better - /// performance - /// + /// performance. /// /// Throttle limit is currently set to 50. This value may be modified later based /// on a figure that we may arrive at out of experience. @@ -201,10 +200,15 @@ internal class ThrottleManager : IDisposable #region Public (internal) Properties /// - /// Allows the consumer to override the default throttle limit + /// Allows the consumer to override the default throttle limit. /// - internal Int32 ThrottleLimit + internal int ThrottleLimit { + get + { + return _throttleLimit; + } + set { if (value > 0 && value <= s_THROTTLE_LIMIT_MAX) @@ -212,21 +216,18 @@ internal Int32 ThrottleLimit _throttleLimit = value; } } - get - { - return _throttleLimit; - } } - private Int32 _throttleLimit = s_DEFAULT_THROTTLE_LIMIT; + + private int _throttleLimit = s_DEFAULT_THROTTLE_LIMIT; #endregion Public (internal) Properties #region Public (internal) Methods /// - /// Submit a list of operations that need to be throttled + /// Submit a list of operations that need to be throttled. /// - /// list of operations to be throttled + /// List of operations to be throttled. /// Once the operations are added to the queue, the method will /// start operations from the queue /// @@ -254,12 +255,12 @@ internal void SubmitOperations(List operations) // schedule operations here if possible StartOperationsFromQueue(); - } // SubmitOperations + } /// - /// Add a single operation to the queue + /// Add a single operation to the queue. /// - /// Operation to be added + /// Operation to be added. internal void AddOperation(IThrottleOperation operation) { // add item to the queue @@ -282,16 +283,16 @@ internal void AddOperation(IThrottleOperation operation) // start operations from queue if possible StartOperationsFromQueue(); - }// AddOperation + } /// - /// Stop throttling operations + /// Stop throttling operations. /// /// Calling this method will also affect other cmdlets which /// could have potentially submitComplete operations for processing /// - /// number of objects cleared from queue without being - /// stopped + /// Number of objects cleared from queue without being + /// stopped. internal void StopAllOperations() { // if stopping is already in progress, make it a no op @@ -307,7 +308,7 @@ internal void StopAllOperations() { needToReturn = true; } - } // lock ... + } if (needToReturn) { @@ -349,8 +350,8 @@ internal void StopAllOperations() _stopOperationQueue.Add(operation); operation.IgnoreStop = true; - } // foreach... - } // lock... + } + } foreach (IThrottleOperation operation in startOperationsInProcessArray) { @@ -360,12 +361,12 @@ internal void StopAllOperations() // Raise event as it can be that at this point, all operations are // complete RaiseThrottleManagerEvents(); - } // StopAllOperations + } /// - /// Stop the specified operation + /// Stop the specified operation. /// - /// operation which needs to be stopped + /// Operation which needs to be stopped. internal void StopOperation(IThrottleOperation operation) { // StopOperation is being called a second time @@ -408,7 +409,7 @@ internal void StopOperation(IThrottleOperation operation) /// /// Signals that no more operations can be submitComplete - /// for throttling + /// for throttling. /// internal void EndSubmitOperations() { @@ -418,14 +419,14 @@ internal void EndSubmitOperations() } RaiseThrottleManagerEvents(); - } // EndSubmitOperations + } #endregion Public (internal) Methods #region Public (internal) Events /// - /// Event raised when throttling all operations is complete + /// Event raised when throttling all operations is complete. /// internal event EventHandler ThrottleComplete; @@ -434,15 +435,15 @@ internal void EndSubmitOperations() #region Constructors /// - /// Public constructor + /// Public constructor. /// public ThrottleManager() { _operationsQueue = new List(); _startOperationQueue = new List(); _stopOperationQueue = new List(); - _syncObject = new Object(); - }// ThrottleManager + _syncObject = new object(); + } #endregion Constructors @@ -451,9 +452,9 @@ public ThrottleManager() /// /// Handler which handles state change for the object which implements /// the - /// interface + /// interface. /// - /// sender of the event + /// Sender of the event. /// Event information object which describes the event /// which triggered this method private void OperationCompleteHandler(object source, OperationStateEventArgs stateEventArgs) @@ -511,10 +512,10 @@ private void OperationCompleteHandler(object source, OperationStateEventArgs sta // Do necessary things for starting operation for the next item in the queue StartOneOperationFromQueue(); - } // OperationCompleteHandler + } /// - /// Method used to start the operation on one item in the queue + /// Method used to start the operation on one item in the queue. /// private void StartOneOperationFromQueue() { @@ -526,20 +527,16 @@ private void StartOneOperationFromQueue() { operation = _operationsQueue[0]; _operationsQueue.RemoveAt(0); - operation.OperationComplete += - new EventHandler(OperationCompleteHandler); + operation.OperationComplete += OperationCompleteHandler; _startOperationQueue.Add(operation); } } - if (operation != null) - { - operation.StartOperation(); - } - } //StartOneOperationFromQueue + operation?.StartOperation(); + } /// - /// Start operations to the limit possible from the queue + /// Start operations to the limit possible from the queue. /// private void StartOperationsFromQueue() { @@ -563,10 +560,10 @@ private void StartOperationsFromQueue() StartOneOperationFromQueue(); } } - } // StartOperationsFromQueue + } /// - /// Raise the throttle manager events once the conditions are met + /// Raise the throttle manager events once the conditions are met. /// private void RaiseThrottleManagerEvents() { @@ -589,44 +586,44 @@ private void RaiseThrottleManagerEvents() { ThrottleComplete.SafeInvoke(this, EventArgs.Empty); } - } // RaiseThrottleManagerEvents + } #endregion Private Methods #region Private Members /// - /// default throttle limit - the maximum number of operations - /// to be processed at a time + /// 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 + /// 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 + /// All pending operations. /// - private List _operationsQueue; + private readonly List _operationsQueue; /// /// List of items on which a StartOperation has - /// been called + /// been called. /// - private List _startOperationQueue; + private readonly List _startOperationQueue; /// /// List of items on which a StopOperation has - /// been called + /// been called. /// - private List _stopOperationQueue; + private readonly List _stopOperationQueue; /// - /// Object used to synchronize access to the queues + /// 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 @@ -638,7 +635,7 @@ private void RaiseThrottleManagerEvents() /// /// Dispose method of IDisposable. Any cmdlet that uses /// the throttle manager needs to call this method from its - /// Dispose method + /// Dispose method. /// public void Dispose() { @@ -649,7 +646,7 @@ public void Dispose() /// /// Internal dispose method which does the actual dispose - /// operations and finalize suppressions + /// operations and finalize suppressions. /// /// If method is called from /// disposing of destructor @@ -659,7 +656,7 @@ private void Dispose(bool disposing) { StopAllOperations(); } - } // Dispose + } #endregion IDisposable Overrides } @@ -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() { @@ -726,7 +723,7 @@ internal event EventHandler EventHandler { add { - bool firstEntry = (null == InternalEvent); + bool firstEntry = (InternalEvent == null); InternalEvent += value; @@ -735,6 +732,7 @@ internal event EventHandler EventHandler OperationComplete += new EventHandler(Operation_OperationComplete); } } + remove { InternalEvent -= value; diff --git a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs index 25b9bb0b455..c3cf3b278aa 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs @@ -1,6 +1,6 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + /* * Common file that contains interface definitions for generic server and client * transport managers. @@ -15,7 +15,7 @@ using System.Globalization; using System.Threading; using System.Management.Automation.Internal; -#if !CORECLR +#if !UNIX using System.Security.Principal; #endif @@ -33,38 +33,93 @@ 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 + /// Constructor. /// /// /// 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,10 +152,10 @@ internal enum ConnectionStatus AutoDisconnectStarting = 4, AutoDisconnectSucceeded = 5, InternalErrorAbort = 6 - }; + } /// - /// ConnectionStatusEventArgs + /// ConnectionStatusEventArgs. /// internal class ConnectionStatusEventArgs : EventArgs { @@ -117,7 +172,7 @@ internal ConnectionStatusEventArgs(ConnectionStatus notification) #region CreateCompleteEventArgs /// - /// CreateCompleteEventArgs + /// CreateCompleteEventArgs. /// internal class CreateCompleteEventArgs : EventArgs { @@ -134,14 +189,14 @@ internal CreateCompleteEventArgs( /// /// Contains implementation that is common to both client and server - /// transport managers + /// 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. @@ -234,11 +289,12 @@ protected BaseTransportManager(PSRemotingCryptoHelper cryptoHelper) internal TypeTable TypeTable { get { return Fragmentor.TypeTable; } + set { Fragmentor.TypeTable = value; } } /// - /// Uses the "OnDataAvailableCallback" to handle Deserialized objects + /// Uses the "OnDataAvailableCallback" to handle Deserialized objects. /// /// /// data to process @@ -268,7 +324,6 @@ internal virtual void ProcessRawData(byte[] data, string stream) } /// - /// /// /// /// data to process @@ -287,7 +342,7 @@ internal void ProcessRawData(byte[] data, string stream, ReceiveDataCollection.OnDataAvailableCallback dataAvailableCallback) { - Dbg.Assert(null != data, "Cannot process null data"); + Dbg.Assert(data != null, "Cannot process null data"); s_baseTracer.WriteLine("Processing incoming data for stream {0}.", stream); @@ -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)); @@ -320,7 +376,6 @@ internal void ProcessRawData(byte[] data, } /// - /// /// /// /// @@ -335,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); @@ -347,7 +402,7 @@ internal void OnDataAvailableCallback(RemoteDataObject remoteObject) } /// - /// copy the DataReceived event handlers to the supplied transport Manager + /// Copy the DataReceived event handlers to the supplied transport Manager. /// /// public void MigrateDataReadyEventHandlers(BaseTransportManager transportManager) @@ -359,17 +414,17 @@ public void MigrateDataReadyEventHandlers(BaseTransportManager transportManager) } /// - /// Raise the error handlers + /// Raise the error handlers. /// /// - internal virtual void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) + public virtual void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArgs) { WSManTransportErrorOccured.SafeInvoke(this, eventArgs); } /// /// Crypto handler to be used for encrypting/decrypting - /// secure strings + /// secure strings. /// internal PSRemotingCryptoHelper CryptoHelper { get; set; } @@ -394,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) { @@ -408,35 +466,38 @@ 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; // this is used log crimson messages. - //keeps track of whether a receive request has been placed on transport - protected bool receiveDataInitiated; + // keeps track of whether a receive request has been placed on transport + internal bool receiveDataInitiated; #endregion #region Constructors - protected BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) + internal BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(cryptoHelper) { RunspacePoolInstanceId = runspaceId; @@ -514,7 +575,7 @@ protected BaseClientTransportManager(Guid runspaceId, PSRemotingCryptoHelper cry /// /// Indicates successful processing of a delay stream request on a receive operation /// - /// this event is useful when PS wants to invoke a pipeline in disconnected mode + /// this event is useful when PS wants to invoke a pipeline in disconnected mode. /// internal event EventHandler DelayStreamRequestProcessed; @@ -532,12 +593,12 @@ internal PrioritySendDataCollection DataToBeSentCollection } /// - /// Used to log crimson messages + /// Used to log crimson messages. /// internal Guid RunspacePoolInstanceId { get; } /// - /// Raise the Connect completed handler + /// Raise the Connect completed handler. /// internal void RaiseCreateCompleted(CreateCompleteEventArgs eventArgs) { @@ -560,7 +621,7 @@ internal void RaiseReconnectCompleted() } /// - /// Raise the close completed handler + /// Raise the close completed handler. /// internal void RaiseCloseCompleted() { @@ -578,7 +639,7 @@ internal void RaiseReadyForDisconnect() /// /// Queue the robust connection notification event. /// - /// Determines what kind of notification + /// Determines what kind of notification. internal void QueueRobustConnectionNotification(int flags) { ConnectionStatusEventArgs args = null; @@ -617,7 +678,7 @@ internal void QueueRobustConnectionNotification(int flags) /// /// Raise the Robust Connection notification event. /// - /// ConnectionStatusEventArgs + /// ConnectionStatusEventArgs. internal void RaiseRobustConnectionNotification(ConnectionStatusEventArgs args) { RobustConnectionNotification.SafeInvoke(this, args); @@ -700,7 +761,7 @@ internal void EnqueueAndStartProcessingThread(RemoteDataObject remoteO lock (_callbackNotificationQueue) { - if ((null != remoteObject) || (null != transportErrorArgs) || (null != privateData)) + if ((remoteObject != null) || (transportErrorArgs != null) || (privateData != null)) { CallbackNotificationInformation rcvdDataInfo = new CallbackNotificationInformation(); rcvdDataInfo.remoteObject = remoteObject; @@ -737,11 +798,9 @@ internal void EnqueueAndStartProcessingThread(RemoteDataObject remoteO _isServicingCallbacks = true; // Start a thread pool thread to process callbacks. -#if !CORECLR - // Flow thread impersonation as needed. - WindowsIdentity identityToImpersonate = WindowsIdentity.GetCurrent(); - identityToImpersonate = (identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation) ? - identityToImpersonate : null; +#if !UNIX + WindowsIdentity identityToImpersonate; + Utils.TryGetWindowsImpersonatedIdentity(out identityToImpersonate); Utils.QueueWorkItemWithImpersonation( identityToImpersonate, @@ -758,9 +817,9 @@ internal void EnqueueAndStartProcessingThread(RemoteDataObject remoteO /// Helper method to check RemoteDataObject for a host call requiring user /// interaction. /// - /// Remote data object - /// True if remote data object requires a user response - private bool CheckForInteractiveHostCall(RemoteDataObject remoteObject) + /// Remote data object. + /// True if remote data object requires a user response. + private static bool CheckForInteractiveHostCall(RemoteDataObject remoteObject) { bool interactiveHostCall = false; @@ -808,7 +867,7 @@ internal void ServicePendingCallbacks(object objectToProcess) try { - do + while (true) { // if the transport manager is closed return. if (isClosed) @@ -821,7 +880,7 @@ internal void ServicePendingCallbacks(object objectToProcess) { // If queue is empty or if queue servicing is suspended // then break out of loop. - if (_callbackNotificationQueue.Count <= 0 || _suspendQueueServicing) + if (_callbackNotificationQueue.Count == 0 || _suspendQueueServicing) { break; } @@ -829,15 +888,15 @@ internal void ServicePendingCallbacks(object objectToProcess) rcvdDataInfo = _callbackNotificationQueue.Dequeue(); } // Handle callback. - if (null != rcvdDataInfo) + if (rcvdDataInfo != null) { // Handling transport exception in thread-pool thread - if (null != rcvdDataInfo.transportError) + if (rcvdDataInfo.transportError != null) { RaiseErrorHandler(rcvdDataInfo.transportError); break; } - else if (null != rcvdDataInfo.privateData) + else if (rcvdDataInfo.privateData != null) { ProcessPrivateData(rcvdDataInfo.privateData); } @@ -846,7 +905,7 @@ internal void ServicePendingCallbacks(object objectToProcess) base.OnDataAvailableCallback(rcvdDataInfo.remoteObject); } } - } while (true); + } } catch (Exception exception) { @@ -936,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(); @@ -975,7 +1037,7 @@ internal virtual void PrepareForConnect() #region Clean up /// - /// Finalizer + /// Finalizes an instance of the class. /// ~BaseClientTransportManager() { @@ -986,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 { @@ -1004,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; @@ -1020,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) { } @@ -1063,7 +1128,7 @@ internal virtual void RemoveCommandTransportManager(Guid powerShellCmdId) } /// - /// Temporarily disconnect an active session + /// Temporarily disconnect an active session. /// internal virtual void DisconnectAsync() { @@ -1141,6 +1206,7 @@ protected BaseClientCommandTransportManager(ClientRemotePowerShell shell, cmdText.Append(cmd.CommandText); cmdText.Append(" | "); } + cmdText.Remove(cmdText.Length - 3, 3); // remove ending " | " RemoteDataObject message; @@ -1172,7 +1238,7 @@ internal void RaiseSignalCompleted() #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1199,7 +1265,7 @@ internal virtual void ReconnectAsync() } /// - /// Used by powershell/pipeline to send a stop message to the server command + /// Used by powershell/pipeline to send a stop message to the server command. /// internal virtual void SendStopSignal() { @@ -1210,7 +1276,6 @@ internal virtual void SendStopSignal() } } - namespace System.Management.Automation.Remoting.Server { /// @@ -1220,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; @@ -1295,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; @@ -1345,17 +1407,17 @@ internal void SendDataToClient(RemoteDataObject data, bool flush, bool rep private void OnDataAvailable(byte[] dataToSend, bool isEndFragment) { - Dbg.Assert(null != dataToSend, "ServerTransportManager cannot send null fragment"); + Dbg.Assert(dataToSend != null, "ServerTransportManager cannot send null fragment"); // log to crimson log. PSEtwLog.LogAnalyticInformational(PSEventId.ServerSendData, PSOpcode.Send, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, _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); } /// @@ -1394,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); @@ -1424,7 +1486,6 @@ internal void RaiseClosingEvent() #region Abstract interfaces /// - /// /// /// /// @@ -1439,12 +1500,10 @@ internal void RaiseClosingEvent() protected abstract void SendDataToClient(byte[] data, bool flush, bool reportAsPending, bool reportAsDataBoundary); /// - /// /// internal abstract void ReportExecutionStatusAsRunning(); /// - /// /// /// /// message describing why the transport manager must be closed @@ -1515,10 +1574,10 @@ internal static class ServerOperationHelpers /// The input buffer to search. It must be base-64 encoded XML. /// The XML tag used to identify the value to extract. /// The extracted tag converted from a base-64 string. - internal static System.Byte[] ExtractEncodedXmlElement(String xmlBuffer, String xmlTag) + internal static byte[] ExtractEncodedXmlElement(string xmlBuffer, string xmlTag) { - if (null == xmlBuffer || null == xmlTag) - return new System.Byte[1]; + if (xmlBuffer == null || xmlTag == null) + return new byte[1]; // the inboundShellInformation is in Xml format as per the SOAP WSMan spec. // Retrieve the string (Base64 encoded) we are interested in. @@ -1526,22 +1585,20 @@ internal static System.Byte[] ExtractEncodedXmlElement(String xmlBuffer, String readerSettings.CheckCharacters = false; readerSettings.IgnoreComments = true; readerSettings.IgnoreProcessingInstructions = true; -#if !CORECLR // No XmlReaderSettings.XmlResolver in CoreCLR readerSettings.XmlResolver = null; -#endif readerSettings.ConformanceLevel = ConformanceLevel.Fragment; readerSettings.MaxCharactersFromEntities = 1024; readerSettings.DtdProcessing = System.Xml.DtdProcessing.Prohibit; XmlReader reader = XmlReader.Create(new StringReader(xmlBuffer), readerSettings); - String additionalData; - if (XmlNodeType.Element == reader.MoveToContent()) + string additionalData; + if (reader.MoveToContent() == XmlNodeType.Element) { additionalData = reader.ReadElementContentAsString(xmlTag, reader.NamespaceURI); } else // No element found, so return a default value { - return new System.Byte[1]; + return new byte[1]; } return Convert.FromBase64String(additionalData); diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 1d450d7c9dd..07e17e8b63a 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -1,26 +1,29 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// 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.IO; using System.Linq; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; -using Microsoft.PowerShell.Commands; -using Microsoft.Win32; using System.Reflection; -using System.IO; +using System.Threading; using System.Xml; -using System.Globalization; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; -using System.Collections; +using Microsoft.PowerShell.Commands; +using Microsoft.Win32; + +using Dbg = System.Management.Automation.Diagnostics; 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. @@ -44,23 +47,19 @@ internal class ConfigurationDataFromXML internal const string MAXRCVDCMDSIZETOKEN = "psmaximumreceiveddatasizepercommandmb"; internal const string MAXRCVDCMDSIZETOKEN_CamelCase = "PSMaximumReceivedDataSizePerCommandMB"; internal const string THREADOPTIONSTOKEN = "pssessionthreadoptions"; -#if !CORECLR // No ApartmentState In CoreCLR internal const string THREADAPTSTATETOKEN = "pssessionthreadapartmentstate"; -#endif internal const string SESSIONCONFIGTOKEN = "sessionconfigurationdata"; internal const string PSVERSIONTOKEN = "PSVersion"; internal const string MAXPSVERSIONTOKEN = "MaxPSVersion"; internal const string MODULESTOIMPORT = "ModulesToImport"; internal const string HOSTMODE = "hostmode"; - internal const string ENDPOINTCONFIGURATIONTYPE = "sessiontype"; - internal const string WORKFLOWCOREASSEMBLY = "Microsoft.PowerShell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"; - internal const string WORKFLOWCORETYPENAME = "Microsoft.PowerShell.Workflow.PSWorkflowSessionConfiguration"; - internal const string PSWORKFLOWMODULE = "%windir%\\system32\\windowspowershell\\v1.0\\Modules\\PSWorkflow"; internal const string CONFIGFILEPATH = "configfilepath"; internal const string CONFIGFILEPATH_CamelCase = "ConfigFilePath"; #endregion + #region Fields + internal string StartupScript; // this field is used only by an Out-Of-Process (IPC) server process internal string InitializationScriptForOutOfProcessRunspace; @@ -68,19 +67,20 @@ internal class ConfigurationDataFromXML internal string AssemblyName; internal string EndPointConfigurationTypeName; internal Type EndPointConfigurationType; - internal Nullable MaxReceivedObjectSizeMB; - internal Nullable MaxReceivedCommandSizeMB; + internal int? MaxReceivedObjectSizeMB; + internal int? MaxReceivedCommandSizeMB; // Used to set properties on the RunspacePool created for this shell. - internal Nullable ShellThreadOptions; - -#if !CORECLR // No ApartmentState In CoreCLR - internal Nullable ShellThreadApartmentState; -#endif + internal PSThreadOptions? ShellThreadOptions; + internal ApartmentState? ShellThreadApartmentState; internal PSSessionConfigurationData SessionConfigurationData; internal string ConfigFilePath; + #endregion + + #region Methods + /// - /// Using optionName and optionValue updates the current object + /// Using optionName and optionValue updates the current object. /// /// /// @@ -131,24 +131,24 @@ private void Update(string optionName, string optionValue) ShellThreadOptions = (PSThreadOptions)LanguagePrimitives.ConvertTo( optionValue, typeof(PSThreadOptions), CultureInfo.InvariantCulture); break; -#if !CORECLR // No ApartmentState In CoreCLR case THREADAPTSTATETOKEN: AssertValueNotAssigned(THREADAPTSTATETOKEN, ShellThreadApartmentState); - ShellThreadApartmentState = (System.Threading.ApartmentState)LanguagePrimitives.ConvertTo( - optionValue, typeof(System.Threading.ApartmentState), CultureInfo.InvariantCulture); + ShellThreadApartmentState = (ApartmentState)LanguagePrimitives.ConvertTo( + optionValue, typeof(ApartmentState), CultureInfo.InvariantCulture); break; -#endif case SESSIONCONFIGTOKEN: { AssertValueNotAssigned(SESSIONCONFIGTOKEN, SessionConfigurationData); SessionConfigurationData = PSSessionConfigurationData.Create(optionValue); } + break; case CONFIGFILEPATH: { AssertValueNotAssigned(CONFIGFILEPATH, ConfigFilePath); - ConfigFilePath = optionValue.ToString(); + ConfigFilePath = optionValue; } + break; default: // we dont need to evaluate PSVersion and other custom authz @@ -158,14 +158,14 @@ private void Update(string optionName, string optionValue) } /// - /// Checks if the originalValue is empty. If not throws an exception + /// Checks if the originalValue is empty. If not throws an exception. /// /// /// /// /// 1. "optionName" is already defined /// - private void AssertValueNotAssigned(string optionName, object originalValue) + private static void AssertValueNotAssigned(string optionName, object originalValue) { if (originalValue != null) { @@ -182,9 +182,9 @@ private void AssertValueNotAssigned(string optionName, object originalValue) /// /// If value is specified, specified value as int . otherwise null. /// - private static Nullable GetIntValueInBytes(string optionValueInMB) + private static int? GetIntValueInBytes(string optionValueInMB) { - Nullable result = null; + int? result = null; try { double variableValue = (double)LanguagePrimitives.ConvertTo(optionValueInMB, @@ -204,7 +204,6 @@ private static Nullable GetIntValueInBytes(string optionValueInMB) return result; } - /// /// Creates the struct from initialization parameters xml. /// @@ -252,9 +251,6 @@ internal static ConfigurationDataFromXML Create(string initializationParameters) readerSettings.IgnoreProcessingInstructions = true; readerSettings.MaxCharactersInDocument = 10000; readerSettings.ConformanceLevel = ConformanceLevel.Fragment; -#if !CORECLR // No XmlReaderSettings.XmlResolver in CoreCLR - readerSettings.XmlResolver = null; -#endif using (XmlReader reader = XmlReader.Create(new StringReader(initializationParameters), readerSettings)) { @@ -290,21 +286,14 @@ internal static ConfigurationDataFromXML Create(string initializationParameters) } // assign defaults after parsing the xml content. - if (null == result.MaxReceivedObjectSizeMB) - { - result.MaxReceivedObjectSizeMB = BaseTransportManager.MaximumReceivedObjectSize; - } + result.MaxReceivedObjectSizeMB ??= BaseTransportManager.MaximumReceivedObjectSize; - if (null == result.MaxReceivedCommandSizeMB) - { - result.MaxReceivedCommandSizeMB = BaseTransportManager.MaximumReceivedDataSize; - } + result.MaxReceivedCommandSizeMB ??= BaseTransportManager.MaximumReceivedDataSize; return result; } /// - /// /// /// /// @@ -337,6 +326,8 @@ internal PSSessionConfiguration CreateEndPointConfigurationInstance() throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType, EndPointConfigurationTypeName, ConfigurationDataFromXML.INITPARAMETERSTOKEN); } + + #endregion } /// @@ -347,9 +338,9 @@ public abstract class PSSessionConfiguration : IDisposable { #region tracer /// - /// Tracer for Server Remote session + /// Tracer for Server Remote session. /// - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); #endregion tracer @@ -357,7 +348,7 @@ public abstract class PSSessionConfiguration : IDisposable /// /// Derived classes must override this to supply an InitialSessionState - /// to be used to construct a Runspace for the user + /// to be used to construct a Runspace for the user. /// /// /// User Identity for which this information is requested @@ -366,7 +357,6 @@ public abstract class PSSessionConfiguration : IDisposable public abstract InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo); /// - /// /// /// /// @@ -386,7 +376,7 @@ public virtual InitialSessionState GetInitialSessionState(PSSessionConfiguration /// User Identity for which this information is requested /// /// - public virtual Nullable GetMaximumReceivedObjectSize(PSSenderInfo senderInfo) + public virtual int? GetMaximumReceivedObjectSize(PSSenderInfo senderInfo) { return BaseTransportManager.MaximumReceivedObjectSize; } @@ -400,7 +390,7 @@ public virtual Nullable GetMaximumReceivedObjectSize(PSSenderInfo senderInf /// User Identity for which this information is requested /// /// - public virtual Nullable GetMaximumReceivedDataSizePerCommand(PSSenderInfo senderInfo) + public virtual int? GetMaximumReceivedDataSizePerCommand(PSSenderInfo senderInfo) { return BaseTransportManager.MaximumReceivedDataSize; } @@ -415,7 +405,7 @@ public virtual Nullable GetMaximumReceivedDataSizePerCommand(PSSenderInfo s /// /// 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; @@ -436,7 +426,6 @@ public void Dispose() } /// - /// /// /// protected virtual void Dispose(bool isDisposing) @@ -448,7 +437,6 @@ protected virtual void Dispose(bool isDisposing) #region GetInitialSessionState from 3rd party shell ids /// - /// /// /// /// @@ -466,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; @@ -507,7 +496,6 @@ private static void LoadRSConfigProvider(string shellId, string initializationPa } /// - /// /// /// /// shellId for which the assembly is getting loaded @@ -543,15 +531,15 @@ private static Type LoadAndAnalyzeAssembly(string shellId, string applicationBas assemblyName, shellId); assembly = LoadSsnStateProviderAssembly(applicationBase, assemblyName); - if (null == assembly) + if (assembly == null) { - throw PSTraceSource.NewArgumentException("assemblyName", RemotingErrorIdStrings.UnableToLoadAssembly, + throw PSTraceSource.NewArgumentException(nameof(assemblyName), RemotingErrorIdStrings.UnableToLoadAssembly, assemblyName, ConfigurationDataFromXML.INITPARAMETERSTOKEN); } } // configuration xml specified an assembly and typetoload. - if (null != assembly) + if (assembly != null) { try { @@ -560,9 +548,9 @@ private static Type LoadAndAnalyzeAssembly(string shellId, string applicationBas typeToLoad, shellId); Type type = assembly.GetType(typeToLoad, true, true); - if (null == type) + if (type == null) { - throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType, + throw PSTraceSource.NewArgumentException(nameof(typeToLoad), RemotingErrorIdStrings.UnableToLoadType, typeToLoad, ConfigurationDataFromXML.INITPARAMETERSTOKEN); } @@ -589,7 +577,7 @@ private static Type LoadAndAnalyzeAssembly(string shellId, string applicationBas // if we are here, that means we are unable to load the type specified // in the config xml.. notify the same. - throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType, + throw PSTraceSource.NewArgumentException(nameof(typeToLoad), RemotingErrorIdStrings.UnableToLoadType, typeToLoad, ConfigurationDataFromXML.INITPARAMETERSTOKEN); } @@ -676,17 +664,18 @@ private static Assembly LoadSsnStateProviderAssembly(string applicationBase, str s_tracer.TraceWarning("Not able to load assembly {0}: {1}", assemblyName, e.Message); } - if (null != result) + if (result != null) { return result; } + s_tracer.WriteLine("Loading assembly from path {0}", applicationBase); try { - String assemblyPath; + string assemblyPath; if (!Path.IsPathRooted(assemblyName)) { - if (!String.IsNullOrEmpty(applicationBase) && Directory.Exists(applicationBase)) + if (!string.IsNullOrEmpty(applicationBase) && Directory.Exists(applicationBase)) { assemblyPath = Path.Combine(applicationBase, assemblyName); } @@ -697,9 +686,10 @@ private static Assembly LoadSsnStateProviderAssembly(string applicationBase, str } else { - //Rooted path of dll is provided. + // Rooted path of dll is provided. assemblyPath = assemblyName; } + result = Assembly.LoadFrom(assemblyPath); } catch (FileLoadException e) @@ -723,6 +713,7 @@ private static Assembly LoadSsnStateProviderAssembly(string applicationBase, str Directory.SetCurrentDirectory(originalDirectory); } } + return result; } @@ -777,19 +768,19 @@ private static string Dbg.Assert(registryKey != null, "Caller should validate the registryKey parameter"); object value = registryKey.GetValue(name); - if (value == null && mandatory == true) + if (value == null && mandatory) { s_tracer.TraceError("Mandatory property {0} not specified for registry key {1}", name, registryKey.Name); - throw PSTraceSource.NewArgumentException("name", RemotingErrorIdStrings.MandatoryValueNotPresent, name, registryKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), RemotingErrorIdStrings.MandatoryValueNotPresent, name, registryKey.Name); } string s = value as string; - if (string.IsNullOrEmpty(s) && mandatory == true) + if (string.IsNullOrEmpty(s) && mandatory) { s_tracer.TraceError("Value is null or empty for mandatory property {0} in {1}", name, registryKey.Name); - throw PSTraceSource.NewArgumentException("name", RemotingErrorIdStrings.MandatoryValueNotInCorrectFormat, name, registryKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), RemotingErrorIdStrings.MandatoryValueNotInCorrectFormat, name, registryKey.Name); } return s; @@ -798,9 +789,11 @@ private static string private const string configProvidersKeyName = "PSConfigurationProviders"; 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 } @@ -810,30 +803,32 @@ private static string /// internal sealed class DefaultRemotePowerShellConfiguration : PSSessionConfiguration { + #region Method overrides + /// - /// /// /// /// 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("sessionConfigurationData"); + ArgumentNullException.ThrowIfNull(sessionConfigurationData); - if (senderInfo == null) - throw new ArgumentNullException("senderInfo"); + ArgumentNullException.ThrowIfNull(senderInfo); - if (configProviderId == null) - throw new ArgumentNullException("configProviderId"); + ArgumentNullException.ThrowIfNull(configProviderId); InitialSessionState sessionState = InitialSessionState.CreateDefault2(); // now get all the modules in the specified path and import the same @@ -861,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. @@ -875,23 +879,23 @@ public override InitialSessionState GetInitialSessionState(PSSessionConfiguratio public enum SessionType { /// - /// Empty session state + /// Empty session state. /// Empty, /// - /// Restricted remote server + /// Restricted remote server. /// RestrictedRemoteServer, /// - /// Default session state + /// Default session state. /// Default } /// - /// Configuration type entry + /// Configuration type entry. /// internal class ConfigTypeEntry { @@ -901,7 +905,6 @@ internal class ConfigTypeEntry internal TypeValidationCallback ValidationCallback; /// - /// /// /// /// @@ -912,8 +915,12 @@ internal ConfigTypeEntry(string key, TypeValidationCallback callback) } } + #endregion + + #region ConfigFileConstants + /// - /// Configuration file constants + /// Configuration file constants. /// internal static class ConfigFileConstants { @@ -962,46 +969,46 @@ internal static class ConfigFileConstants internal static readonly string VisibleProviders = "VisibleProviders"; internal static readonly string VisibleExternalCommands = "VisibleExternalCommands"; - internal static 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)), + 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)), }; /// - /// Checks if the given key is a valid key + /// Checks if the given key is a valid key. /// /// /// @@ -1013,7 +1020,7 @@ internal static bool IsValidKey(DictionaryEntry de, PSCmdlet cmdlet, string path foreach (ConfigTypeEntry configEntry in ConfigFileKeys) { - if (String.Equals(configEntry.Key, de.Key.ToString(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(configEntry.Key, de.Key.ToString(), StringComparison.OrdinalIgnoreCase)) { validKey = true; @@ -1033,7 +1040,6 @@ internal static bool IsValidKey(DictionaryEntry de, PSCmdlet cmdlet, string path } /// - /// /// /// /// @@ -1044,7 +1050,7 @@ private static bool ISSValidationCallback(string key, object obj, PSCmdlet cmdle { string value = obj as string; - if (!String.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { try { @@ -1065,7 +1071,6 @@ private static bool ISSValidationCallback(string key, object obj, PSCmdlet cmdle } /// - /// /// /// /// @@ -1076,7 +1081,7 @@ private static bool LanguageModeValidationCallback(string key, object obj, PSCmd { string value = obj as string; - if (!String.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { try { @@ -1097,7 +1102,6 @@ private static bool LanguageModeValidationCallback(string key, object obj, PSCmd } /// - /// /// /// /// @@ -1108,7 +1112,7 @@ private static bool ExecutionPolicyValidationCallback(string key, object obj, PS { string value = obj as string; - if (!String.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { try { @@ -1129,7 +1133,6 @@ private static bool ExecutionPolicyValidationCallback(string key, object obj, PS } /// - /// /// /// /// @@ -1150,7 +1153,6 @@ private static bool HashtableTypeValidationCallback(string key, object obj, PSCm } /// - /// /// /// /// @@ -1183,10 +1185,10 @@ private static bool AliasDefinitionsTypeValidationCallback(string key, object ob foreach (string aliasKey in hashtable.Keys) { - if (!String.Equals(aliasKey, AliasNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(aliasKey, AliasValueToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(aliasKey, AliasDescriptionToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(aliasKey, AliasOptionsToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(aliasKey, AliasNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(aliasKey, AliasValueToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(aliasKey, AliasDescriptionToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(aliasKey, AliasOptionsToken, StringComparison.OrdinalIgnoreCase)) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, aliasKey, key, path)); return false; @@ -1198,7 +1200,6 @@ private static bool AliasDefinitionsTypeValidationCallback(string key, object ob } /// - /// /// /// /// @@ -1229,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; @@ -1237,9 +1238,9 @@ private static bool FunctionDefinitionsTypeValidationCallback(string key, object foreach (string functionKey in hashtable.Keys) { - if (!String.Equals(functionKey, FunctionNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, FunctionValueToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(functionKey, FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(functionKey, FunctionNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, FunctionValueToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(functionKey, FunctionOptionsToken, StringComparison.OrdinalIgnoreCase)) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, functionKey, key, path)); return false; @@ -1251,7 +1252,6 @@ private static bool FunctionDefinitionsTypeValidationCallback(string key, object } /// - /// /// /// /// @@ -1284,8 +1284,8 @@ private static bool VariableDefinitionsTypeValidationCallback(string key, object foreach (string variableKey in hashtable.Keys) { - if (!String.Equals(variableKey, VariableNameToken, StringComparison.OrdinalIgnoreCase) && - !String.Equals(variableKey, VariableValueToken, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(variableKey, VariableNameToken, StringComparison.OrdinalIgnoreCase) && + !string.Equals(variableKey, VariableValueToken, StringComparison.OrdinalIgnoreCase)) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeContainsInvalidKey, variableKey, key, path)); return false; @@ -1297,7 +1297,7 @@ private static bool VariableDefinitionsTypeValidationCallback(string key, object } /// - /// Verifies a string type + /// Verifies a string type. /// /// /// @@ -1306,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; @@ -1316,7 +1316,7 @@ private static bool StringTypeValidationCallback(string key, object obj, PSCmdle } /// - /// Verifies a string array type + /// Verifies a string array type. /// /// /// @@ -1336,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; @@ -1347,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; @@ -1357,7 +1357,7 @@ private static bool IntegerTypeValidationCallback(string key, object obj, PSCmdl } /// - /// Verifies that an array contains only string or hashtable elements + /// Verifies that an array contains only string or hashtable elements. /// /// /// @@ -1376,10 +1376,12 @@ private static bool StringOrHashtableArrayTypeValidationCallback(string key, obj } } + #endregion + #region DISC Utilities /// - /// DISC utilities + /// DISC utilities. /// internal static class DISCUtils { @@ -1415,9 +1417,9 @@ internal static class DISCUtils /// /// Create an ExternalScriptInfo object from a file path. /// - /// execution context - /// The path to the file - /// The base name of the script + /// Execution context. + /// The path to the file. + /// The base name of the script. /// The ExternalScriptInfo object. internal static ExternalScriptInfo GetScriptInfoForFile(ExecutionContext context, string fileName, out string scriptName) { @@ -1443,11 +1445,11 @@ internal static ExternalScriptInfo GetScriptInfoForFile(ExecutionContext context } /// - /// Loads the configuration file into a hashtable + /// Loads the configuration file into a hashtable. /// - /// execution context - /// the ExternalScriptInfo object - /// configuration hashtable + /// Execution context. + /// The ExternalScriptInfo object. + /// Configuration hashtable. internal static Hashtable LoadConfigFile(ExecutionContext context, ExternalScriptInfo scriptInfo) { object result; @@ -1472,12 +1474,12 @@ internal static Hashtable LoadConfigFile(ExecutionContext context, ExternalScrip } /// - /// Verifies the configuration hashtable + /// Verifies the configuration hashtable. /// - /// configuration hashtable + /// Configuration hashtable. /// /// - /// true if valid, false otherwise + /// True if valid, false otherwise. internal static bool VerifyConfigTable(Hashtable table, PSCmdlet cmdlet, string path) { bool hasSchemaVersion = false; @@ -1516,7 +1518,6 @@ internal static bool VerifyConfigTable(Hashtable table, PSCmdlet cmdlet, string } /// - /// /// private static void ValidatePS1XMLExtension(string key, string[] paths, string filePath) { @@ -1544,7 +1545,6 @@ private static void ValidatePS1XMLExtension(string key, string[] paths, string f } /// - /// /// private static void ValidatePS1OrPSM1Extension(string key, string[] paths, string filePath) { @@ -1563,7 +1563,7 @@ private static void ValidatePS1OrPSM1Extension(string key, string[] paths, strin !ext.Equals(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.DISCInvalidExtension, key, ext, - String.Join(", ", StringLiterals.PowerShellScriptFileExtension, StringLiterals.PowerShellModuleFileExtension))); + string.Join(", ", StringLiterals.PowerShellScriptFileExtension, StringLiterals.PowerShellModuleFileExtension))); } } catch (ArgumentException argumentException) @@ -1574,7 +1574,6 @@ private static void ValidatePS1OrPSM1Extension(string key, string[] paths, strin } /// - /// /// /// /// @@ -1597,7 +1596,7 @@ internal static void ValidateExtensions(Hashtable table, string filePath) } /// - /// Checks if all paths are absolute paths + /// Checks if all paths are absolute paths. /// /// /// @@ -1621,7 +1620,7 @@ internal static void ValidateAbsolutePaths(SessionState state, Hashtable table, } /// - /// Checks if a path is an absolute path + /// Checks if a path is an absolute path. /// /// /// @@ -1658,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)); @@ -1705,16 +1704,18 @@ internal static void ValidateRoleDefinitions(IDictionary roleDefinitions) #endregion + #region DISCPowerShellConfiguration + /// - /// Creates an initial session state based on the configuration language for PSSC files + /// 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 + /// Gets the configuration hashtable that results from parsing the specified configuration file. /// internal Hashtable ConfigHash { @@ -1722,21 +1723,22 @@ internal Hashtable ConfigHash } /// - /// Creates a new instance of a Declarative Initial Session State Configuration + /// Creates a new instance of a Declarative Initial Session State Configuration. /// - /// The path to the .pssc file representing the initial session state + /// The path to the .pssc file representing the initial session state. /// /// 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(). /// - 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; @@ -1750,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(); @@ -1788,7 +1796,7 @@ private void MergeRoleRulesIntoConfigHash(Func roleVerifier) DISCUtils.ValidateRoleDefinitions(roleEntry); // Go through the Roles hashtable - foreach (Object role in roleEntry.Keys) + foreach (object role in roleEntry.Keys) { // Check if this role applies to the connected user if (roleVerifier(role.ToString())) @@ -1812,6 +1820,7 @@ private void MergeRoleRulesIntoConfigHash(Func roleVerifier) // Takes the "RoleCapabilities" node in the config hash, and merges its values into the base configuration. private const string PSRCExtension = ".psrc"; + private void MergeRoleCapabilitiesIntoConfigHash() { List psrcFiles = new List(); @@ -1825,7 +1834,7 @@ private void MergeRoleCapabilitiesIntoConfigHash() foreach (string roleCapability in roleCapabilities) { string roleCapabilityPath = GetRoleCapabilityPath(roleCapability); - if (String.IsNullOrEmpty(roleCapabilityPath)) + if (string.IsNullOrEmpty(roleCapabilityPath)) { string message = StringUtil.Format(RemotingErrorIdStrings.CouldNotFindRoleCapability, roleCapability, roleCapability + PSRCExtension); PSInvalidOperationException ioe = new PSInvalidOperationException(message); @@ -1877,11 +1886,11 @@ private void MergeRoleCapabilitiesIntoConfigHash() // Merge a role / role capability hashtable into the master configuration hashtable private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) { - foreach (Object customization in childConfigHash.Keys) + foreach (object customization in childConfigHash.Keys) { string customizationString = customization.ToString(); - ArrayList customizationValue = new ArrayList(); + var customizationValue = new List(); // First, take all values from the master config table if (_configHash.ContainsKey(customizationString)) @@ -1889,7 +1898,7 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) IEnumerable existingValueAsCollection = LanguagePrimitives.GetEnumerable(_configHash[customization]); if (existingValueAsCollection != null) { - foreach (Object value in existingValueAsCollection) + foreach (object value in existingValueAsCollection) { customizationValue.Add(value); } @@ -1904,7 +1913,7 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) IEnumerable newValueAsCollection = LanguagePrimitives.GetEnumerable(childConfigHash[customization]); if (newValueAsCollection != null) { - foreach (Object value in newValueAsCollection) + foreach (object value in newValueAsCollection) { customizationValue.Add(value); } @@ -1919,18 +1928,18 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) } } - private string GetRoleCapabilityPath(string roleCapability) + private static string GetRoleCapabilityPath(string roleCapability) { string moduleName = "*"; - if (roleCapability.IndexOf('\\') != -1) + 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 @@ -1980,7 +1989,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn bool providerVisibilityApplied = IsNonDefaultVisibilitySpecified(ConfigFileConstants.VisibleProviders); bool processDefaultSessionStateVisibility = false; - if (!String.IsNullOrEmpty(initialSessionState)) + if (!string.IsNullOrEmpty(initialSessionState)) { sessionType = (SessionType)Enum.Parse(typeof(SessionType), initialSessionState, true); @@ -2030,7 +2039,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn System.Collections.Generic.HashSet addedProviders = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string provider in providers) { - if (!String.IsNullOrEmpty(provider)) + if (!string.IsNullOrEmpty(provider)) { // Look up providers from provider name including wildcards. var providersFound = iss.Providers.LookUpByName(provider); @@ -2076,7 +2085,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn throw ioe; } - if (null != modules) + if (modules != null) { Collection modulesToImport = new Collection(); foreach (object module in modules) @@ -2090,14 +2099,14 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn else { Hashtable moduleHash = module as Hashtable; - if (null != moduleHash) + if (moduleHash != null) { moduleSpec = new ModuleSpecification(moduleHash); } } // Now add the moduleSpec to modulesToImport - if (null != moduleSpec) + if (moduleSpec != null) { if (string.Equals(InitialSessionState.CoreModule, moduleSpec.Name, StringComparison.OrdinalIgnoreCase)) @@ -2118,6 +2127,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn } } } + iss.ImportPSModule(modulesToImport); } } @@ -2174,7 +2184,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn { foreach (string alias in aliases) { - if (!String.IsNullOrEmpty(alias)) + if (!string.IsNullOrEmpty(alias)) { bool found = false; @@ -2243,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; @@ -2285,7 +2295,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn { foreach (string type in types) { - if (!String.IsNullOrEmpty(type)) + if (!string.IsNullOrEmpty(type)) { iss.Types.Add(new SessionStateTypeEntry(type)); } @@ -2302,7 +2312,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn { foreach (string format in formats) { - if (!String.IsNullOrEmpty(format)) + if (!string.IsNullOrEmpty(format)) { iss.Formats.Add(new SessionStateFormatEntry(format)); } @@ -2347,7 +2357,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn { foreach (string script in startupScripts) { - if (!String.IsNullOrEmpty(script)) + if (!string.IsNullOrEmpty(script)) { iss.StartupScripts.Add(script); } @@ -2414,10 +2424,10 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn // Process User Drive if (_configHash.ContainsKey(ConfigFileConstants.MountUserDrive)) { - if (Convert.ToBoolean(_configHash[ConfigFileConstants.MountUserDrive], CultureInfo.InvariantCulture) == true) + 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)) @@ -2468,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); @@ -2480,7 +2490,7 @@ private static void ProcessVisibleCommands(InitialSessionState iss, object[] com commandModuleNames.Add(moduleSpec.Name); } - foreach (Object commandObject in commands) + foreach (object commandObject in commands) { if (commandObject == null) { @@ -2488,8 +2498,8 @@ private static void ProcessVisibleCommands(InitialSessionState iss, object[] com } // If it's just a string, this is a visible command - String command = commandObject as String; - if (!String.IsNullOrEmpty(command)) + string command = commandObject as string; + if (!string.IsNullOrEmpty(command)) { ProcessVisibleCommand(iss, command, commandModuleNames); } @@ -2541,7 +2551,7 @@ private static void ProcessCommandModification(Dictionary com if ((commandName == null) || (parameters == null)) { string hashtableKey = commandName; - if (String.IsNullOrEmpty(hashtableKey)) + if (string.IsNullOrEmpty(hashtableKey)) { IEnumerator errorKey = commandModification.Keys.GetEnumerator(); errorKey.MoveNext(); @@ -2577,7 +2587,7 @@ private static void ProcessCommandModification(Dictionary com foreach (string parameterModification in parameter.Keys) { - if (String.Equals("Name", parameterModification, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("Name", parameterModification, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -2587,11 +2597,12 @@ private static void ProcessCommandModification(Dictionary com { currentParameterModification[parameterModification] = new HashSet(StringComparer.OrdinalIgnoreCase); } + HashSet currentParameterModificationValue = (HashSet)currentParameterModification[parameterModification]; foreach (string parameterModificationValue in TryGetStringArray(parameter[parameterModification])) { - if (!String.IsNullOrEmpty(parameterModificationValue)) + if (!string.IsNullOrEmpty(parameterModificationValue)) { currentParameterModificationValue.Add(parameterModificationValue); } @@ -2639,20 +2650,20 @@ private static void ProcessVisibleCommand(InitialSessionState iss, string comman } /// - /// Creates an alias entry + /// 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); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { return null; } string value = TryGetValue(alias, ConfigFileConstants.AliasValueToken); - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { return null; } @@ -2663,7 +2674,7 @@ private SessionStateAliasEntry CreateSessionStateAliasEntry(Hashtable alias, boo string optionsString = TryGetValue(alias, ConfigFileConstants.AliasOptionsToken); - if (!String.IsNullOrEmpty(optionsString)) + if (!string.IsNullOrEmpty(optionsString)) { options = (ScopedItemOptions)Enum.Parse(typeof(ScopedItemOptions), optionsString, true); } @@ -2678,21 +2689,21 @@ private SessionStateAliasEntry CreateSessionStateAliasEntry(Hashtable alias, boo } /// - /// Creates a function entry + /// 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); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { return null; } string value = TryGetValue(function, ConfigFileConstants.FunctionValueToken); - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { return null; } @@ -2701,7 +2712,7 @@ private SessionStateFunctionEntry CreateSessionStateFunctionEntry(Hashtable func string optionsString = TryGetValue(function, ConfigFileConstants.FunctionOptionsToken); - if (!String.IsNullOrEmpty(optionsString)) + if (!string.IsNullOrEmpty(optionsString)) { options = (ScopedItemOptions)Enum.Parse(typeof(ScopedItemOptions), optionsString, true); } @@ -2719,20 +2730,20 @@ private SessionStateFunctionEntry CreateSessionStateFunctionEntry(Hashtable func } /// - /// Creates a variable entry + /// 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); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { return null; } string value = TryGetValue(variable, ConfigFileConstants.VariableValueToken); - if (String.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { return null; } @@ -2743,7 +2754,7 @@ private SessionStateVariableEntry CreateSessionStateVariableEntry(Hashtable vari string optionsString = TryGetValue(variable, ConfigFileConstants.AliasOptionsToken); - if (!String.IsNullOrEmpty(optionsString)) + if (!string.IsNullOrEmpty(optionsString)) { options = (ScopedItemOptions)Enum.Parse(typeof(ScopedItemOptions), optionsString, true); } @@ -2781,7 +2792,7 @@ private bool IsNonDefaultVisibilitySpecified(string configFileKey) } /// - /// Attempts to get a value from a hashtable + /// Attempts to get a value from a hashtable. /// /// /// @@ -2793,11 +2804,11 @@ internal static string TryGetValue(Hashtable table, string key) return table[key].ToString(); } - return String.Empty; + return string.Empty; } /// - /// Attempts to get a hashtable array from an object + /// Attempts to get a hashtable array from an object. /// /// /// @@ -2825,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; } @@ -2841,7 +2850,7 @@ internal static Hashtable[] TryGetHashtableArray(object hashObj) } /// - /// Attempts to get a string array from a hashtable + /// Attempts to get a string array from a hashtable. /// /// /// @@ -2891,6 +2900,7 @@ internal static T[] TryGetObjectsOfType(object hashObj, IEnumerable typ } } } + return null; } @@ -2907,8 +2917,115 @@ internal static T[] TryGetObjectsOfType(object hashObj, IEnumerable typ return null; } } + return result; } } #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 ebacbb6ce72..47ff6270dba 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. /* * Common file that contains implementation for both server and client transport @@ -10,6 +9,7 @@ * elevation to support local machine remoting). */ +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -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, @@ -124,14 +125,15 @@ internal static string CreateSignalAckPacket(Guid psGuid) /// /// Common method to create a packet that contains only a PS Guid - /// with element name changing + /// with element name changing. /// /// /// /// 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 @@ -191,11 +200,13 @@ internal static void ProcessData(string data, DataProcessingDelegates callbacks) break; case XmlNodeType.EndElement: break; + case XmlNodeType.Text: + throw new PSRemotingTransportException(data); default: throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownNodeType, RemotingErrorIdStrings.IPCUnknownNodeType, reader.NodeType.ToString(), - XmlNodeType.Element.ToString(), - XmlNodeType.EndElement.ToString()); + nameof(XmlNodeType.Element), + nameof(XmlNodeType.EndElement)); } } } @@ -213,7 +224,7 @@ internal static void ProcessData(string data, DataProcessingDelegates callbacks) /// private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates callbacks) { - Dbg.Assert(null != xmlReader, "xmlReader cannot be null."); + Dbg.Assert(xmlReader != null, "xmlReader cannot be null."); Dbg.Assert(xmlReader.NodeType == XmlNodeType.Element, "xmlReader's NodeType should be of type Element"); PowerShellTraceSource tracer = PowerShellTraceSourceFactory.GetTraceSource(); @@ -231,6 +242,7 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_DATA_TAG); } + string stream = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_STREAM_ATTRIBUTE); string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); @@ -266,12 +278,14 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_DATA_ACK_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_DATA_ACK received, psGuid : " + psGuid.ToString()); callbacks.DataAckPacketReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_COMMAND_TAG: { @@ -282,12 +296,14 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_COMMAND_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_COMMAND received, psGuid : " + psGuid.ToString()); callbacks.CommandCreationPacketReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_COMMAND_ACK_TAG: { @@ -298,11 +314,13 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_COMMAND_ACK_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_COMMAND_ACK received, psGuid : " + psGuid.ToString()); callbacks.CommandCreationAckReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_CLOSE_TAG: { @@ -313,12 +331,14 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_CLOSE_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_CLOSE received, psGuid : " + psGuid.ToString()); callbacks.ClosePacketReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_CLOSE_ACK_TAG: { @@ -329,11 +349,13 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_CLOSE_ACK_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_CLOSE_ACK received, psGuid : " + psGuid.ToString()); callbacks.CloseAckPacketReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_TAG: { @@ -344,12 +366,14 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_SIGNAL received, psGuid : " + psGuid.ToString()); callbacks.SignalPacketReceived(psGuid); } + break; case OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_ACK_TAG: { @@ -360,11 +384,13 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, OutOfProcessUtils.PS_OUT_OF_PROC_SIGNAL_ACK_TAG); } + string psGuidString = xmlReader.GetAttribute(OutOfProcessUtils.PS_OUT_OF_PROC_PSGUID_ATTRIBUTE); Guid psGuid = new Guid(psGuidString); tracer.WriteMessage("OutOfProcessUtils.ProcessElement : PS_OUT_OF_PROC_SIGNAL_ACK received, psGuid : " + psGuid.ToString()); callbacks.SignalAckPacketReceived(psGuid); } + break; default: throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, @@ -376,31 +402,47 @@ private static void ProcessElement(XmlReader xmlReader, DataProcessingDelegates #endregion } - /// /// A wrapper around TextWriter to allow for synchronized writing to a stream. /// Synchronization is required to avoid collision when multiple TransportManager's - /// write data at the same time to the same writer + /// write data at the same time to the same writer. /// 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 #region Constructors /// - /// Constructs the wrapper + /// Constructs the wrapper. /// /// - internal OutOfProcessTextWriter(TextWriter writerToWrap) + public OutOfProcessTextWriter(TextWriter writerToWrap) { - Dbg.Assert(null != writerToWrap, "Cannot wrap a null writer."); + if (writerToWrap is null) + { + throw new PSArgumentNullException(nameof(writerToWrap)); + } + _writer = writerToWrap; } @@ -412,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) { @@ -444,23 +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 PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; + private readonly BlockingCollection _sessionMessageQueue; + private readonly BlockingCollection _commandMessageQueue; + 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) @@ -480,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. @@ -492,6 +541,20 @@ internal OutOfProcessClientSessionTransportManagerBase( // timers initialization _closeTimeOutTimer = new Timer(OnCloseTimeOutTimerElapsed, null, Timeout.Infinite, Timeout.Infinite); + // Session message processing + _sessionMessageQueue = new BlockingCollection(); + var sessionThread = new Thread(ProcessMessageProc); + sessionThread.Name = "SessionMessageProcessing"; + sessionThread.IsBackground = true; + sessionThread.Start(_sessionMessageQueue); + + // Command message processing + _commandMessageQueue = new BlockingCollection(); + var commandThread = new Thread(ProcessMessageProc); + commandThread.Name = "CommandMessageProcessing"; + commandThread.IsBackground = true; + commandThread.Start(_commandMessageQueue); + _tracer = PowerShellTraceSourceFactory.GetTraceSource(); } @@ -507,12 +570,12 @@ internal override void ConnectAsync() /// /// Closes the server process. /// - internal override void CloseAsync() + public override void CloseAsync() { bool shouldRaiseCloseCompleted = false; lock (syncObject) { - if (isClosed == true) + if (isClosed) { return; } @@ -521,7 +584,7 @@ internal override void CloseAsync() // will know that we are closing. isClosed = true; - if (null == stdInWriter) + if (_messageWriter == null) { // this will happen if CloseAsync() is called // before ConnectAsync()..in which case we @@ -547,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; @@ -565,7 +628,7 @@ internal override void CloseAsync() } /// - /// Create a transport manager for command + /// Create a transport manager for command. /// /// /// @@ -576,26 +639,27 @@ internal override BaseClientCommandTransportManager CreateClientCommandTransport ClientRemotePowerShell cmd, bool noInput) { - Dbg.Assert(null != cmd, "Cmd cannot be null"); + 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; } /// - /// Kills the server process and disposes other resources + /// 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(); + DisposeMessageQueue(); } } @@ -646,34 +710,91 @@ private OutOfProcessClientCommandTransportManager GetCommandTransportManager(Gui private void OnCloseSessionCompleted() { - //stop timer + // stop timer _closeTimeOutTimer.Change(Timeout.Infinite, Timeout.Infinite); + RaiseCloseCompleted(); CleanupConnection(); } + /// + /// Optional additional connection clean up after a connection is closed. + /// protected abstract void CleanupConnection(); + private void ProcessMessageProc(object state) + { + var messageQueue = state as BlockingCollection; + + try + { + while (true) + { + var data = messageQueue.Take(); + try + { + OutOfProcessUtils.ProcessData(data, _dataProcessingCallbacks); + } + catch (Exception exception) + { + PSRemotingTransportException psrte = + new PSRemotingTransportException( + PSRemotingErrorId.IPCErrorProcessingServerData, + RemotingErrorIdStrings.IPCErrorProcessingServerData, + exception.Message); + RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.ReceiveShellOutputEx)); + } + } + } + catch (InvalidOperationException) + { + // Normal session message processing thread end. + } + } + #endregion #region Event Handlers + 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)) + { + // A null/empty data string indicates a problem in the transport, + // e.g., named pipe emitting a null packet because it closed or some reason. + // In this case we simply ignore the packet. + return; + } + try { - OutOfProcessUtils.ProcessData(data, _dataProcessingCallbacks); + // Route protocol message based on whether it is a session or command message. + if (data.Contains(SESSIONDMESSAGETAG, StringComparison.OrdinalIgnoreCase)) + { + // Session message + _sessionMessageQueue.Add(data); + } + else + { + // Command message + _commandMessageQueue.Add(data); + } } - catch (Exception exception) + catch (InvalidOperationException) { - PSRemotingTransportException psrte = - new PSRemotingTransportException(PSRemotingErrorId.IPCErrorProcessingServerData, - RemotingErrorIdStrings.IPCErrorProcessingServerData, - exception.Message); - RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.ReceiveShellOutputEx)); + // This exception will be thrown by the BlockingCollection message queue objects + // after they have been closed. } } + /// + /// Handles protocol error data. + /// protected void HandleErrorDataReceived(string data) { lock (syncObject) @@ -690,40 +811,20 @@ 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(); - } - PSRemotingTransportException psrte = new PSRemotingTransportException(PSRemotingErrorId.IPCServerProcessExited, - RemotingErrorIdStrings.IPCServerProcessExited); - RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, transportMethod)); - } - #endregion #region Sending Data related Methods + /// + /// Send any data packet in the queue. + /// protected void SendOneItem() { DataPriorityType priorityType; // This will either return data or register callback but doesn't do both. byte[] data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback, out priorityType); - if (null != data) + if (data != null) { SendData(data, priorityType); } @@ -731,7 +832,7 @@ protected void SendOneItem() private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType) { - Dbg.Assert(null != data, "data cannot be null in the data available callback"); + Dbg.Assert(data != null, "data cannot be null in the data available callback"); tracer.WriteLine("Received data to be sent from the callback."); SendData(data, priorityType); @@ -753,7 +854,7 @@ private void SendData(byte[] data, DataPriorityType priorityType) return; } - stdInWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, + _messageWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, priorityType, Guid.Empty)); } @@ -775,7 +876,7 @@ private void OnRemoteSessionSendCompleted() private void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) { string streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_STDOUT; - if (stream.Equals(DataPriorityType.PromptResponse.ToString(), StringComparison.OrdinalIgnoreCase)) + if (stream.Equals(nameof(DataPriorityType.PromptResponse), StringComparison.OrdinalIgnoreCase)) { streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE; } @@ -796,13 +897,11 @@ private void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) { // this is for a command OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (null != cmdTM) - { - // 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); } } @@ -817,13 +916,11 @@ private void OnDataAckPacketReceived(Guid psGuid) { // this is for a command OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (null != cmdTM) - { - // 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(); } } @@ -837,7 +934,7 @@ private void OnCommandCreationPacketReceived(Guid psGuid) private void OnCommandCreationAckReceived(Guid psGuid) { OutOfProcessClientCommandTransportManager cmdTM = GetCommandTransportManager(psGuid); - if (null == cmdTM) + if (cmdTM == null) { throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownCommandGuid, RemotingErrorIdStrings.IPCUnknownCommandGuid, @@ -851,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); } @@ -860,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 (null != cmdTM) - { - cmdTM.OnRemoteCmdSignalCompleted(); - } + cmdTM?.OnRemoteCmdSignalCompleted(); } } @@ -900,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 (null != cmdTM) - { - // 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(); } } @@ -920,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; @@ -950,26 +1106,26 @@ 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 (null != _connectionInfo) + if (_connectionInfo != null) { _processInstance = _connectionInfo.Process ?? new PowerShellProcessInstance(_connectionInfo.PSVersion, _connectionInfo.Credential, _connectionInfo.InitializationScript, - _connectionInfo.RunAs32); + _connectionInfo.RunAs32, + _connectionInfo.WorkingDirectory); if (_connectionInfo.Process != null) { _processCreated = false; } - // _processInstance.Start(); } PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateShell, PSOpcode.Connect, @@ -994,29 +1150,12 @@ internal override void CreateAsync() _processInstance.RunspacePool.Dispose(); } - stdInWriter = _processInstance.StdInWriter; - //if (stdInWriter == null) - { - _serverProcess.OutputDataReceived += new DataReceivedEventHandler(OnOutputDataReceived); - _serverProcess.ErrorDataReceived += new DataReceivedEventHandler(OnErrorDataReceived); - } - _serverProcess.Exited += new EventHandler(OnExited); - - //serverProcess.Start(); + _serverProcess.Exited += OnExited; _processInstance.Start(); - if (stdInWriter != null) - { - _serverProcess.CancelErrorRead(); - _serverProcess.CancelOutputRead(); - } - - // Start asynchronous reading of output/errors - _serverProcess.BeginOutputReadLine(); - _serverProcess.BeginErrorReadLine(); - - stdInWriter = new OutOfProcessTextWriter(_serverProcess.StandardInput); - _processInstance.StdInWriter = stdInWriter; + StartRedirectionReaderThreads(_serverProcess); + SetMessageWriter(_serverProcess.StandardInput); + _processInstance.StdInWriter = _messageWriter; } } catch (System.ComponentModel.Win32Exception w32e) @@ -1042,17 +1181,97 @@ internal override void CreateAsync() SendOneItem(); } + private void StartRedirectionReaderThreads(Process serverProcess) + { + Thread outputThread = new Thread(ProcessOutputData); + outputThread.IsBackground = true; + outputThread.Name = "Out-of-Proc Job Output Thread"; + + Thread errorThread = new Thread(ProcessErrorData); + errorThread.IsBackground = true; + errorThread.Name = "Out-of-Proc Job Error Thread"; + + outputThread.Start(serverProcess.StandardOutput); + errorThread.Start(serverProcess.StandardError); + } + + private void ProcessOutputData(object arg) + { + if (arg is StreamReader reader) + { + try + { + string data = reader.ReadLine(); + while (data != null) + { + HandleOutputDataReceived(data); + data = reader.ReadLine(); + } + } + catch (IOException) + { + // Treat this as EOF, the same as what 'Process.BeginOutputReadLine()' does. + } + catch (Exception e) + { + _tracer.WriteMessage( + "OutOfProcessClientSessionTransportManager", + "ProcessOutputThread", + Guid.Empty, + "Transport manager output reader thread ended with error: {0}", + e.Message ?? string.Empty); + } + } + else + { + Dbg.Assert(false, "Invalid argument. Expecting a StreamReader object."); + } + } + + private void ProcessErrorData(object arg) + { + if (arg is StreamReader reader) + { + try + { + string data = reader.ReadLine(); + while (data != null) + { + HandleErrorDataReceived(data); + data = reader.ReadLine(); + } + } + catch (IOException) + { + // Treat this as EOF, the same as what 'Process.BeginErrorReadLine()' does. + } + catch (Exception e) + { + _tracer.WriteMessage( + "OutOfProcessClientSessionTransportManager", + "ProcessErrorThread", + Guid.Empty, + "Transport manager error reader thread ended with error: {0}", + e.Message ?? string.Empty); + } + } + else + { + Dbg.Assert(false, "Invalid argument. Expecting a StreamReader object."); + } + } + /// - /// Kills the server process and disposes other resources + /// 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) { KillServerProcess(); - if (null != _serverProcess && _processCreated) + if (_serverProcess != null && _processCreated) { // null can happen if Dispose is called before ConnectAsync() _serverProcess.Dispose(); @@ -1068,25 +1287,11 @@ protected override void CleanupConnection() #endregion - #region Event Handlers - - private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) - { - HandleOutputDataReceived(e.Data); - } - - private void OnErrorDataReceived(object sender, DataReceivedEventArgs e) - { - HandleErrorDataReceived(e.Data); - } - - #endregion - #region Helper Methods private void KillServerProcess() { - if (null == _serverProcess) + if (_serverProcess == null) { // this can happen if Dispose is called before ConnectAsync() return; @@ -1100,13 +1305,8 @@ private void KillServerProcess() if (_processCreated) { - _serverProcess.CancelOutputRead(); - _serverProcess.CancelErrorRead(); _serverProcess.Kill(); } - - _serverProcess.OutputDataReceived -= new DataReceivedEventHandler(OnOutputDataReceived); - _serverProcess.ErrorDataReceived -= new DataReceivedEventHandler(OnErrorDataReceived); } } catch (System.ComponentModel.Win32Exception) @@ -1129,14 +1329,62 @@ 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 protected RemoteSessionHyperVSocketClient _client; + private const string _threadName = "HyperVSocketTransport Reader Thread"; #endregion @@ -1153,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(); } } @@ -1231,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); @@ -1253,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 @@ -1272,7 +1517,7 @@ internal VMHyperVSocketClientSessionTransportManager( { if (connectionInfo == null) { - throw new PSArgumentNullException("connectionInfo"); + throw new PSArgumentNullException(nameof(connectionInfo)); } _connectionInfo = connectionInfo; @@ -1297,36 +1542,40 @@ 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( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.VMSessionConnectFailed), null, - PSRemotingErrorId.VMSessionConnectFailed.ToString(), + nameof(PSRemotingErrorId.VMSessionConnectFailed), 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( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.VMSessionConnectFailed), null, - PSRemotingErrorId.VMSessionConnectFailed.ToString(), + nameof(PSRemotingErrorId.VMSessionConnectFailed), ErrorCategory.InvalidOperation, null); } // Create writer for Hyper-V socket. - stdInWriter = new OutOfProcessTextWriter(_client.TextWriter); + SetMessageWriter(_client.TextWriter); // Create reader thread for Hyper-V socket. StartReaderThread(_client.TextReader); @@ -1339,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 @@ -1355,7 +1604,7 @@ internal ContainerHyperVSocketClientSessionTransportManager( { if (connectionInfo == null) { - throw new PSArgumentNullException("connectionInfo"); + throw new PSArgumentNullException(nameof(connectionInfo)); } _connectionInfo = connectionInfo; @@ -1370,22 +1619,24 @@ internal ContainerHyperVSocketClientSessionTransportManager( /// Create a Hyper-V socket connection to the target process and set up /// transport reader/writer. /// - internal override void CreateAsync() + public override void CreateAsync() { - _client = new RemoteSessionHyperVSocketClient(_targetGuid, false, true); - if (!_client.Connect(null, String.Empty, false)) + // 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(); throw new PSInvalidOperationException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.ContainerSessionConnectFailed), null, - PSRemotingErrorId.ContainerSessionConnectFailed.ToString(), + nameof(PSRemotingErrorId.ContainerSessionConnectFailed), ErrorCategory.InvalidOperation, null); } // Create writer for Hyper-V socket. - stdInWriter = new OutOfProcessTextWriter(_client.TextWriter); + SetMessageWriter(_client.TextWriter); // Create reader thread for Hyper-V socket. StartReaderThread(_client.TextReader); @@ -1394,16 +1645,18 @@ 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"; #endregion @@ -1425,7 +1678,7 @@ internal SSHClientSessionTransportManager( #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1444,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( @@ -1456,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(); @@ -1479,29 +1775,30 @@ internal override void CloseAsync() private void CloseConnection() { - var stdInWriter = _stdInWriter; - if (stdInWriter != null) { stdInWriter.Dispose(); } + // Ensure message queue is disposed. + DisposeMessageQueue(); + + var connectionTimer = Interlocked.Exchange(ref _connectionTimer, null); + connectionTimer?.Dispose(); - var stdOutReader = _stdOutReader; - if (stdOutReader != null) { stdOutReader.Dispose(); } + var stdInWriter = Interlocked.Exchange(ref _stdInWriter, null); + stdInWriter?.Dispose(); - var stdErrReader = _stdErrReader; - if (stdErrReader != null) { stdErrReader.Dispose(); } + var stdOutReader = Interlocked.Exchange(ref _stdOutReader, null); + stdOutReader?.Dispose(); + + var stdErrReader = Interlocked.Exchange(ref _stdErrReader, null); + 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 // and terminating the SSH process just once. - var sshProcessId = _sshProcessId; - _sshProcessId = 0; + var sshProcessId = Interlocked.Exchange(ref _sshProcessId, 0); if (sshProcessId != 0) { 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) { } @@ -1528,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) { @@ -1536,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) @@ -1550,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); @@ -1567,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.IndexOf("WARNING:", StringComparison.OrdinalIgnoreCase) > -1) - { - // 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) { @@ -1647,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 @@ -1671,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); } @@ -1683,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 @@ -1704,7 +1964,7 @@ internal NamedPipeClientSessionTransportManagerBase( { if (connectionInfo == null) { - throw new PSArgumentNullException("connectionInfo"); + throw new PSArgumentNullException(nameof(connectionInfo)); } _connectionInfo = connectionInfo; @@ -1716,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(); } } @@ -1777,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) @@ -1798,10 +2045,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("NamedPipeClientSessionTransportManager", "StartReaderThread", Guid.Empty, "Transport manager reader thread ended with error: {0}", errorMsg); } @@ -1814,7 +2061,8 @@ internal sealed class NamedPipeClientSessionTransportManager : NamedPipeClientSe { #region Private Data - private NamedPipeConnectionInfo _connectionInfo; + private readonly NamedPipeConnectionInfo _connectionInfo; + private const string _threadName = "NamedPipeTransport Reader Thread"; #endregion @@ -1829,7 +2077,7 @@ internal NamedPipeClientSessionTransportManager( { if (connectionInfo == null) { - throw new PSArgumentNullException("connectionInfo"); + throw new PSArgumentNullException(nameof(connectionInfo)); } _connectionInfo = connectionInfo; @@ -1843,15 +2091,17 @@ 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 = new RemoteSessionNamedPipeClient(_connectionInfo.ProcessId, _connectionInfo.AppDomainName); + _clientPipe = string.IsNullOrEmpty(_connectionInfo.CustomPipeName) ? + new RemoteSessionNamedPipeClient(_connectionInfo.ProcessId, _connectionInfo.AppDomainName) : + new RemoteSessionNamedPipeClient(_connectionInfo.CustomPipeName); // Wait for named pipe to connect. _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); @@ -1864,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 } @@ -1879,7 +2123,8 @@ internal sealed class ContainerNamedPipeClientSessionTransportManager : NamedPip { #region Private Data - private ContainerConnectionInfo _connectionInfo; + private readonly ContainerConnectionInfo _connectionInfo; + private const string _threadName = "ContainerNamedPipeTransport Reader Thread"; #endregion @@ -1894,7 +2139,7 @@ internal ContainerNamedPipeClientSessionTransportManager( { if (connectionInfo == null) { - throw new PSArgumentNullException("connectionInfo"); + throw new PSArgumentNullException(nameof(connectionInfo)); } _connectionInfo = connectionInfo; @@ -1908,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, @@ -1919,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); @@ -1947,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 @@ -1958,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; @@ -1976,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, @@ -1986,11 +2231,11 @@ internal override void CreateAsync() _stdInWriter.WriteLine(OutOfProcessUtils.CreateCommandPacket(powershellInstanceId)); } - internal override void CloseAsync() + public override void CloseAsync() { lock (syncObject) { - if (isClosed == true) + if (isClosed) { return; } @@ -2040,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) @@ -2135,7 +2380,7 @@ internal void OnRemoteCmdSignalCompleted() internal void OnSignalTimeOutTimerElapsed(object source) { - //Signal timer is triggered only once + // Signal timer is triggered only once if (isClosed) { @@ -2164,7 +2409,7 @@ private void StopSignalTimerAndDecrementOperations() /// internal override void ProcessPrivateData(object privateData) { - Dbg.Assert(null != privateData, "privateData cannot be null."); + Dbg.Assert(privateData != null, "privateData cannot be null."); // For this version...only a boolean can be used for privateData. bool shouldRaiseSignalCompleted = (bool)privateData; @@ -2202,7 +2447,7 @@ private void SendOneItem() data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback, out priorityType); } - if (null != data) + if (data != null) { SendData(data, priorityType); } @@ -2232,7 +2477,7 @@ private void SendData(byte[] data, DataPriorityType priorityType) private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType) { - Dbg.Assert(null != data, "data cannot be null in the data available callback"); + Dbg.Assert(data != null, "data cannot be null in the data available callback"); tracer.WriteLine("Received data from dataToBeSent store."); SendData(data, priorityType); @@ -2248,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 @@ -2260,11 +2505,17 @@ internal class OutOfProcessServerSessionTransportManager : AbstractServerSession internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWriter, OutOfProcessTextWriter errWriter, PSRemotingCryptoHelperServer cryptoHelper) : base(BaseTransportManager.DefaultFragmentSize, cryptoHelper) { - Dbg.Assert(null != outWriter, "outWriter cannot be null."); - Dbg.Assert(null != errWriter, "errWriter cannot be null."); + Dbg.Assert(outWriter != null, "outWriter cannot be null."); + Dbg.Assert(errWriter != null, "errWriter cannot be null."); _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 @@ -2292,7 +2543,7 @@ protected override void SendDataToClient(byte[] data, bool flush, bool reportAsP internal override void ReportExecutionStatusAsRunning() { - //No-OP for outofProc TMs + // No-OP for outofProc TMs } internal void CreateCommandTransportManager(Guid powerShellCmdId) @@ -2346,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 @@ -2398,7 +2649,7 @@ internal override void ProcessRawData(byte[] data, string stream) internal override void ReportExecutionStatusAsRunning() { - //No-OP for outofProc TMs + // No-OP for outofProc TMs } protected override void SendDataToClient(byte[] data, bool flush, bool reportAsPending, bool reportAsDataBoundary) @@ -2427,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 dc8acb41f51..b6a3f212b33 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PSPrincipal.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. /* * Contains definition for PSSenderInfo, PSPrincipal, PSIdentity which are @@ -19,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 @@ -35,7 +32,7 @@ public sealed class PSSenderInfo : ISerializable #region Serialization /// - /// Serialization + /// Serialization. /// /// /// @@ -46,7 +43,7 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) } /// - /// Deserialization constructor + /// Deserialization constructor. /// /// /// @@ -82,8 +79,6 @@ private PSSenderInfo(SerializationInfo info, StreamingContext context) UserInfo = senderInfo.UserInfo; ConnectionString = senderInfo.ConnectionString; _applicationArguments = senderInfo._applicationArguments; - - ClientTimeZone = senderInfo.ClientTimeZone; } catch (Exception) { @@ -117,9 +112,11 @@ public PSSenderInfo(PSPrincipal userPrincipal, string httpUrl) #region Properties /// - /// Contains information related to the user connecting to the server + /// Contains information related to the user connecting to the server. /// - public PSPrincipal UserInfo { get; + public PSPrincipal UserInfo + { + get; // No public set because PSSenderInfo/PSPrincipal is used by PSSessionConfiguration's // and usually they dont cache this data internally..so did not want to give // cmdlets/scripts a chance to modify these. @@ -128,17 +125,15 @@ public PSSenderInfo(PSPrincipal userPrincipal, string httpUrl) /// /// 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 /// directly taken from WSMAN_SENDER_DETAILS struct (from wsman.h) /// - public string ConnectionString { get; + public string ConnectionString + { + get; // No public set because PSSenderInfo/PSPrincipal is used by PSSessionConfiguration's // and usually they dont cache this data internally..so did not want to give // cmdlets/scripts a chance to modify these. @@ -150,11 +145,12 @@ public TimeZoneInfo ClientTimeZone public PSPrimitiveDictionary ApplicationArguments { get { return _applicationArguments; } + internal set { _applicationArguments = value; } } /// - /// "ConfigurationName" from the sever remote session + /// "ConfigurationName" from the sever remote session. /// public string ConfigurationName { get; internal set; } @@ -173,7 +169,9 @@ public sealed class PSPrincipal : IPrincipal /// /// Gets the identity of the current user principal. /// - public PSIdentity Identity { get; + public PSIdentity Identity + { + get; // No public set because PSSenderInfo/PSPrincipal is used by PSSessionConfiguration's // and usually they dont cache this data internally..so did not want to give // cmdlets/scripts a chance to modify these. @@ -185,7 +183,9 @@ public sealed class PSPrincipal : IPrincipal /// a domain etc. This property tries to convert the Identity to WindowsIdentity /// using the user token supplied. /// - public WindowsIdentity WindowsIdentity { get; + public WindowsIdentity WindowsIdentity + { + get; // No public set because PSSenderInfo/PSPrincipal is used by PSSessionConfiguration's // and usually they dont cache this data internally..so did not want to give // cmdlets/scripts a chance to modify these. @@ -211,7 +211,7 @@ IIdentity IPrincipal.Identity /// public bool IsInRole(string role) { - if (null != WindowsIdentity) + if (WindowsIdentity != null) { // Get Windows Principal for this identity WindowsPrincipal windowsPrincipal = new WindowsPrincipal(WindowsIdentity); @@ -224,11 +224,11 @@ public bool IsInRole(string role) } /// - /// Internal overload of IsInRole() taking a WindowsBuiltInRole enum value + /// Internal overload of IsInRole() taking a WindowsBuiltInRole enum value. /// internal bool IsInRole(WindowsBuiltInRole role) { - if (null != WindowsIdentity) + if (WindowsIdentity != null) { // Get Windows Principal for this identity WindowsPrincipal windowsPrincipal = new WindowsPrincipal(WindowsIdentity); @@ -243,7 +243,7 @@ internal bool IsInRole(WindowsBuiltInRole role) #region Constructor /// - /// Constructs PSPrincipal using PSIdentity and a WindowsIdentity + /// Constructs PSPrincipal using PSIdentity and a WindowsIdentity. /// /// /// An instance of PSIdentity @@ -280,7 +280,7 @@ public sealed class PSIdentity : IIdentity /// WSMAN_AUTH_BASIC /// WSMAN_AUTH_KERBEROS /// WSMAN_AUTH_CLIENT_CERTIFICATE - /// WSMAN_AUTH_LIVEID + /// WSMAN_AUTH_LIVEID. /// public string AuthenticationType { get; } @@ -302,7 +302,7 @@ public sealed class PSIdentity : IIdentity #region Public Constructor /// - /// Constructor used to construct a PSIdentity object + /// Constructor used to construct a PSIdentity object. /// /// /// Type of authentication used to authenticate this user. @@ -363,7 +363,7 @@ public sealed class PSCertificateDetails #region Constructor /// - /// Constructor used to construct a PSCertificateDetails object + /// Constructor used to construct a PSCertificateDetails object. /// /// /// Subject of the certificate. @@ -383,4 +383,4 @@ public PSCertificateDetails(string subject, string issuerName, string issuerThum #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs b/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs index 44dfdac2546..1d5f6916981 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs @@ -1,29 +1,30 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; using System.IO; +using System.Text; using System.Xml; + using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; -using System.Collections.Generic; -using System.Text; namespace System.Management.Automation.Remoting { /// - /// /// 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 /// - /// /// public List ModulesToImport { @@ -42,14 +43,14 @@ internal List ModulesToImportInternal } /// - /// /// - public String PrivateData + public string PrivateData { get { return _privateData; } + internal set { _privateData = value; @@ -79,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); @@ -110,7 +114,7 @@ internal static PSSessionConfigurationData Create(string configurationData) string optionName = reader.Value; - if (String.Equals(optionName, PrivateDataToken, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(optionName, PrivateDataToken, StringComparison.OrdinalIgnoreCase)) { // this is a PrivateData element which we // need to process @@ -156,7 +160,7 @@ internal static PSSessionConfigurationData Create(string configurationData) private string _privateData; /// - /// Checks if the originalValue is empty. If not throws an exception + /// Checks if the originalValue is empty. If not throws an exception. /// /// /// @@ -173,7 +177,7 @@ private static void AssertValueNotAssigned(string optionName, object originalVal } /// - /// Using optionName and optionValue updates the current object + /// Using optionName and optionValue updates the current object. /// /// /// @@ -190,7 +194,7 @@ private void Update(string optionName, string optionValue) AssertValueNotAssigned(ModulesToImportToken, _modulesToImport); _modulesToImport = new List(); _modulesToImportInternal = new List(); - object[] modulesToImport = optionValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + object[] modulesToImport = optionValue.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (var module in modulesToImport) { var s = module as string; @@ -210,19 +214,21 @@ private void Update(string optionName, string optionValue) } } } + break; default: { Dbg.Assert(false, "Unknown option specified"); } + break; } } 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 552668fba40..a0f38a78a07 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -1,16 +1,15 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Tracing; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting { /// - /// /// internal enum DataPriorityType : int { @@ -56,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. @@ -89,13 +88,17 @@ internal PrioritySendDataCollection() internal Fragmentor Fragmentor { - get { return _fragmentor; } + get + { + return _fragmentor; + } + set { - Dbg.Assert(null != value, "Fragmentor cannot be null."); + 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++) @@ -121,9 +124,9 @@ internal Fragmentor Fragmentor /// internal void Add(RemoteDataObject data, DataPriorityType priority) { - Dbg.Assert(null != data, "Cannot send null data object"); - Dbg.Assert(null != _fragmentor, "Fragmentor cannot be null while adding objects"); - Dbg.Assert(null != _dataToBeSent, "Serialized streams are not initialized"); + Dbg.Assert(data != null, "Cannot send null data object"); + Dbg.Assert(_fragmentor != null, "Fragmentor cannot be null while adding objects"); + Dbg.Assert(_dataToBeSent != null, "Serialized streams are not initialized"); // make sure the only one object is fragmented and added to the collection // at any give time. This way the order of fragment is maintained @@ -161,14 +164,14 @@ internal void Clear() causing an unhandled exception in finalize and a process crash. Verify arrays and dataToBeSent objects before referencing. */ - if (null != _dataSyncObjects && null != _dataToBeSent) + 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]) { - if (null != _dataToBeSent[promptResponseIndex]) + if (_dataToBeSent[promptResponseIndex] != null) { _dataToBeSent[promptResponseIndex].Dispose(); _dataToBeSent[promptResponseIndex] = null; @@ -177,7 +180,7 @@ Verify arrays and dataToBeSent objects before referencing. lock (_dataSyncObjects[defaultIndex]) { - if (null != _dataToBeSent[defaultIndex]) + if (_dataToBeSent[defaultIndex] != null) { _dataToBeSent[defaultIndex].Dispose(); _dataToBeSent[defaultIndex] = null; @@ -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 (null == result) + 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. - if (null == result) + + // No data to return..so register the callback. + if (result == null) { - // register callback. + // Register callback. _onDataAvailableCallback = callback; } @@ -253,13 +264,13 @@ private void OnDataAvailable(byte[] data, bool isEndFragment) _isHandlingCallback = true; } - if (null != _onDataAvailableCallback) + if (_onDataAvailableCallback != null) { DataPriorityType prType; // now get the fragment and call the callback.. byte[] result = ReadOrRegisterCallback(_onDataAvailableCallback, out prType); - if (null != result) + if (result != null) { // reset the onDataAvailableCallback so that we dont notify // multiple times. we are resetting before actually calling @@ -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. @@ -309,9 +320,9 @@ internal class ReceiveDataCollection : IDisposable private long _currentObjectId; private long _currentFrgId; // max deserialized object size in bytes - private Nullable _maxReceivedObjectSize; + 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 @@ -346,7 +357,6 @@ internal class ReceiveDataCollection : IDisposable #region Constructor /// - /// /// /// /// Defragmentor used to deserialize an object. @@ -357,7 +367,7 @@ internal class ReceiveDataCollection : IDisposable /// internal ReceiveDataCollection(Fragmentor defragmentor, bool createdByClientTM) { - Dbg.Assert(null != defragmentor, "ReceiveDataCollection needs a defragmentor to work with"); + Dbg.Assert(defragmentor != null, "ReceiveDataCollection needs a defragmentor to work with"); // Memory streams created with an unsigned byte array provide a non-resizable stream view // of the data, and can only be written to. When using a byte array, you can neither append @@ -377,7 +387,7 @@ internal ReceiveDataCollection(Fragmentor defragmentor, bool createdByClientTM) /// /// Limits the deserialized object size received from a remote machine. /// - internal Nullable MaximumReceivedObjectSize + internal int? MaximumReceivedObjectSize { set { _maxReceivedObjectSize = value; } } @@ -397,7 +407,7 @@ internal void AllowTwoThreadsToProcessRawData() /// Prepares the collection for a stream connect /// When reconnecting from same client, its possible that fragment stream get interrupted if server is dropping data /// When connecting from a new client, its possible to get trailing fragments of a previously partially transmitted object - /// Logic based on this flag, ensures such offsync/trailing fragments get ignored until the next full object starts flowing + /// Logic based on this flag, ensures such offsync/trailing fragments get ignored until the next full object starts flowing. /// internal void PrepareForStreamConnect() { @@ -432,8 +442,8 @@ internal void PrepareForStreamConnect() /// internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) { - Dbg.Assert(null != data, "Cannot process null data"); - Dbg.Assert(null != callback, "Callback cannot be null"); + Dbg.Assert(data != null, "Cannot process null data"); + Dbg.Assert(callback != null, "Callback cannot be null"); lock (_syncObject) { @@ -456,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) { @@ -551,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; @@ -569,14 +579,14 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) // reset incoming stream. _pendingDataStream.Dispose(); _pendingDataStream = new MemoryStream(); - if (null != extraData) + if (extraData != null) { _pendingDataStream.Write(extraData, 0, extraData.Length); } if (sFlag) { - _canIgnoreOffSyncFragments = false; //reset this upon receiving a start fragment of a fresh object + _canIgnoreOffSyncFragments = false; // reset this upon receiving a start fragment of a fresh object _currentObjectId = objectId; // Memory streams created with an unsigned byte array provide a non-resizable stream view // of the data, and can only be written to. When using a byte array, you can neither append @@ -591,7 +601,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) if (objectId != _currentObjectId) { s_baseTracer.WriteLine("ObjectId != CurrentObjectId"); - //TODO - drop an ETW event + // TODO - drop an ETW event ResetReceiveData(); if (!_canIgnoreOffSyncFragments) { @@ -608,7 +618,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) if (fragmentId != (_currentFrgId + 1)) { s_baseTracer.WriteLine("Fragment Id is not in sequence."); - //TODO - drop an ETW event + // TODO - drop an ETW event ResetReceiveData(); if (!_canIgnoreOffSyncFragments) { @@ -652,7 +662,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) break; } } - } while (true); + } } finally { @@ -662,6 +672,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) { ReleaseResources(); } + _numberOfThreadsProcessing--; } } @@ -673,10 +684,8 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) private void ResetReceiveData() { // reset resources used to store incoming data (for a single object) - if (null != _dataToProcessStream) - { - _dataToProcessStream.Dispose(); - } + _dataToProcessStream?.Dispose(); + _currentObjectId = 0; _currentFrgId = 0; _totalReceivedObjectSizeSoFar = 0; @@ -684,13 +693,13 @@ private void ResetReceiveData() private void ReleaseResources() { - if (null != _pendingDataStream) + if (_pendingDataStream != null) { _pendingDataStream.Dispose(); _pendingDataStream = null; } - if (null != _dataToProcessStream) + if (_dataToProcessStream != null) { _dataToProcessStream.Dispose(); _dataToProcessStream = null; @@ -737,16 +746,16 @@ 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 #region Constructor /// - /// Construct a priority receive data collection + /// Construct a priority receive data collection. /// /// Defragmentor used to deserialize an object. /// @@ -756,12 +765,13 @@ 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++) { _recvdData[index] = new ReceiveDataCollection(defragmentor, createdByClientTM); } + _isCreateByClientTM = createdByClientTM; } #endregion @@ -771,7 +781,7 @@ internal PriorityReceiveDataCollection(Fragmentor defragmentor, bool createdByCl /// /// Limits the total data received from a remote machine. /// - internal Nullable MaximumReceivedDataSize + internal int? MaximumReceivedDataSize { set { @@ -782,7 +792,7 @@ internal Nullable MaximumReceivedDataSize /// /// Limits the deserialized object size received from a remote machine. /// - internal Nullable MaximumReceivedObjectSize + internal int? MaximumReceivedObjectSize { set { @@ -793,9 +803,8 @@ internal Nullable MaximumReceivedObjectSize } } - /// - /// Prepares receive data streams for a reconnection + /// Prepares receive data streams for a reconnection. /// internal void PrepareForStreamConnect() { @@ -805,7 +814,6 @@ internal void PrepareForStreamConnect() } } - /// /// This might be needed only for ServerCommandTransportManager case /// where the command is run in the same thread that runs ProcessRawData @@ -855,7 +863,7 @@ internal void ProcessRawData(byte[] data, DataPriorityType priorityType, ReceiveDataCollection.OnDataAvailableCallback callback) { - Dbg.Assert(null != data, "Cannot process null data"); + Dbg.Assert(data != null, "Cannot process null data"); try { @@ -902,7 +910,7 @@ public void Dispose() internal virtual void Dispose(bool isDisposing) { - if (null != _recvdData) + if (_recvdData != null) { for (int index = 0; index < _recvdData.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 2fcd0a2f889..d7bce634620 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs @@ -1,11 +1,10 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Text; -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; using Dbg = System.Diagnostics.Debug; @@ -24,11 +23,13 @@ internal static class WSManNativeApi internal const string ResourceURIPrefix = @"http://schemas.microsoft.com/powershell/"; internal const string NoProfile = "WINRS_NOPROFILE"; internal const string CodePage = "WINRS_CODEPAGE"; + internal static readonly Version WSMAN_STACK_VERSION = new Version(3, 0); + internal const int WSMAN_FLAG_REQUESTED_API_VERSION_1_1 = 1; - //WSMan's default max env size in V2 + // WSMan's default max env size in V2 internal const int WSMAN_DEFAULT_MAX_ENVELOPE_SIZE_KB_V2 = 150; - //WSMan's default max env size in V3 + // WSMan's default max env size in V3 internal const int WSMAN_DEFAULT_MAX_ENVELOPE_SIZE_KB_V3 = 500; #region WSMan errors @@ -37,7 +38,7 @@ internal static class WSManNativeApi /// The WinRM service cannot process the request because the request needs to be sent /// to a different machine. /// Use the redirect information to send the request to a new machine. - /// 0x8033819B from sdk\inc\wsmerror.h + /// 0x8033819B from sdk\inc\wsmerror.h. /// internal const int ERROR_WSMAN_REDIRECT_REQUESTED = -2144108135; @@ -173,7 +174,7 @@ internal MarshalledObject(IntPtr dataPtr) /// Must be a value type. /// /// - /// MarshalledObject + /// MarshalledObject. internal static MarshalledObject Create(T obj) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); @@ -191,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; @@ -199,7 +200,7 @@ public void Dispose() } /// - /// Implicit cast to IntPtr + /// Implicit cast to IntPtr. /// /// /// @@ -216,7 +217,7 @@ public static implicit operator IntPtr(MarshalledObject obj) /// /// Different Authentication Mechanisms supported by WSMan. /// TODO: By the look of it, this appears like a Flags enum. - /// Need to confirm the behavior with WSMan + /// Need to confirm the behavior with WSMan. /// /// /// Please keep in sync with WSManAuthenticationMechanism @@ -226,15 +227,15 @@ public static implicit operator IntPtr(MarshalledObject obj) internal enum WSManAuthenticationMechanism : int { /// - /// Use the default authentication + /// Use the default authentication. /// WSMAN_FLAG_DEFAULT_AUTHENTICATION = 0x0, /// - /// Use no authentication for a remote operation + /// Use no authentication for a remote operation. /// WSMAN_FLAG_NO_AUTHENTICATION = 0x1, /// - /// Use digest authentication for a remote operation + /// Use digest authentication for a remote operation. /// WSMAN_FLAG_AUTH_DIGEST = 0x2, /// @@ -242,19 +243,19 @@ internal enum WSManAuthenticationMechanism : int /// WSMAN_FLAG_AUTH_NEGOTIATE = 0x4, /// - /// Use basic authentication for a remote operation + /// Use basic authentication for a remote operation. /// WSMAN_FLAG_AUTH_BASIC = 0x8, /// - /// Use kerberos authentication for a remote operation + /// Use kerberos authentication for a remote operation. /// WSMAN_FLAG_AUTH_KERBEROS = 0x10, /// - /// Use client certificate authentication for a remote operation + /// Use client certificate authentication for a remote operation. /// WSMAN_FLAG_AUTH_CLIENT_CERTIFICATE = 0x20, /// - /// Use CredSSP authentication for a remote operation + /// Use CredSSP authentication for a remote operation. /// WSMAN_FLAG_AUTH_CREDSSP = 0x80, } @@ -289,24 +290,20 @@ protected virtual void Dispose(bool isDisposing) internal class WSManUserNameAuthenticationCredentials : BaseWSManAuthenticationCredentials { /// - /// /// [StructLayout(LayoutKind.Sequential)] internal struct WSManUserNameCredentialStruct { /// - /// /// internal WSManAuthenticationMechanism authenticationMechanism; /// - /// /// [MarshalAs(UnmanagedType.LPWStr)] internal string userName; /// - /// making password secure. + /// Making password secure. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr password; } @@ -314,7 +311,7 @@ internal struct WSManUserNameCredentialStruct private MarshalledObject _data; /// - /// Default constructor + /// Default constructor. /// internal WSManUserNameAuthenticationCredentials() { @@ -346,7 +343,7 @@ internal WSManUserNameAuthenticationCredentials(string name, _cred = new WSManUserNameCredentialStruct(); _cred.authenticationMechanism = authMechanism; _cred.userName = name; - if (null != pwd) + if (pwd != null) { _cred.password = Marshal.SecureStringToCoTaskMemUnicode(pwd); } @@ -355,7 +352,7 @@ internal WSManUserNameAuthenticationCredentials(string name, } /// - /// gets a structure representation (used for marshalling) + /// Gets a structure representation (used for marshalling) /// internal WSManUserNameCredentialStruct CredentialStruct { @@ -363,7 +360,7 @@ internal WSManUserNameCredentialStruct CredentialStruct } /// - /// Marshalled Data + /// Marshalled Data. /// /// public override MarshalledObject GetMarshalledObject() @@ -372,7 +369,7 @@ public override MarshalledObject GetMarshalledObject() } /// - /// Dispose of the resources + /// Dispose of the resources. /// /// protected override void Dispose(bool isDisposing) @@ -388,22 +385,18 @@ protected override void Dispose(bool isDisposing) } /// - /// /// internal class WSManCertificateThumbprintCredentials : BaseWSManAuthenticationCredentials { /// - /// /// [StructLayout(LayoutKind.Sequential)] private struct WSManThumbprintStruct { /// - /// /// internal WSManAuthenticationMechanism authenticationMechanism; /// - /// /// [MarshalAs(UnmanagedType.LPWStr)] internal string certificateThumbprint; @@ -413,6 +406,7 @@ private struct WSManThumbprintStruct /// internal IntPtr reserved; } + private MarshalledObject _data; /// @@ -432,7 +426,7 @@ internal WSManCertificateThumbprintCredentials(string thumbPrint) } /// - /// Marshalled Data + /// Marshalled Data. /// /// public override MarshalledObject GetMarshalledObject() @@ -441,7 +435,7 @@ public override MarshalledObject GetMarshalledObject() } /// - /// Dispose of the resources + /// Dispose of the resources. /// /// protected override void Dispose(bool isDisposing) @@ -463,31 +457,31 @@ internal enum WSManSessionOption : int #region TimeOuts /// - /// int - default timeout in ms that applies to all operations on the client side + /// Int - default timeout in ms that applies to all operations on the client side. /// WSMAN_OPTION_DEFAULT_OPERATION_TIMEOUTMS = 1, /// - /// int - Robust connection maximum retry time in minutes. + /// Int - Robust connection maximum retry time in minutes. /// WSMAN_OPTION_MAX_RETRY_TIME = 11, /// - /// int - timeout in ms for WSManCreateShellEx operations + /// Int - timeout in ms for WSManCreateShellEx operations. /// WSMAN_OPTION_TIMEOUTMS_CREATE_SHELL = 12, /// - /// int - timeout in ms for WSManReceiveShellOutputEx operations + /// Int - timeout in ms for WSManReceiveShellOutputEx operations. /// WSMAN_OPTION_TIMEOUTMS_RECEIVE_SHELL_OUTPUT = 14, /// - /// int - timeout in ms for WSManSendShellInputEx operations + /// Int - timeout in ms for WSManSendShellInputEx operations. /// WSMAN_OPTION_TIMEOUTMS_SEND_SHELL_INPUT = 15, /// - /// int - timeout in ms for WSManSignalShellEx operations + /// Int - timeout in ms for WSManSignalShellEx operations. /// WSMAN_OPTION_TIMEOUTMS_SIGNAL_SHELL = 16, /// - /// int - timeout in ms for WSManCloseShellOperationEx operations + /// Int - timeout in ms for WSManCloseShellOperationEx operations. /// WSMAN_OPTION_TIMEOUTMS_CLOSE_SHELL_OPERATION = 17, @@ -496,32 +490,32 @@ internal enum WSManSessionOption : int #region Connection Options /// - /// int - 1 to not validate the CA on the server certificate; 0 - default + /// Int - 1 to not validate the CA on the server certificate; 0 - default. /// WSMAN_OPTION_SKIP_CA_CHECK = 18, /// - /// int - 1 to not validate the CN on the server certificate; 0 - default + /// Int - 1 to not validate the CN on the server certificate; 0 - default. /// WSMAN_OPTION_SKIP_CN_CHECK = 19, /// - /// int - 1 to not encrypt the messages; 0 - default + /// Int - 1 to not encrypt the messages; 0 - default. /// WSMAN_OPTION_UNENCRYPTED_MESSAGES = 20, /// - /// int - 1 Send all network packets for remote operations in UTF16; 0 - default is UTF8 + /// Int - 1 Send all network packets for remote operations in UTF16; 0 - default is UTF8. /// WSMAN_OPTION_UTF16 = 21, /// - /// int - 1 When using negotiate, include port number in the connection SPN; 0 - default + /// Int - 1 When using negotiate, include port number in the connection SPN; 0 - default. /// WSMAN_OPTION_ENABLE_SPN_SERVER_PORT = 22, /// - /// int - Used when not talking to the main OS on a machine but, for instance, a BMC - /// 1 Identify this machine to the server by including the MachineID header; 0 - default + /// Int - Used when not talking to the main OS on a machine but, for instance, a BMC + /// 1 Identify this machine to the server by including the MachineID header; 0 - default. /// WSMAN_OPTION_MACHINE_ID = 23, /// - /// int -1 Enables host process to be created with interactive token. + /// Int -1 Enables host process to be created with interactive token. /// WSMAN_OPTION_USE_INTERACTIVE_TOKEN = 34, @@ -529,11 +523,11 @@ internal enum WSManSessionOption : int #region Locale /// - /// string - RFC 3066 language code + /// String - RFC 3066 language code. /// WSMAN_OPTION_LOCALE = 25, /// - /// string - RFC 3066 language code + /// String - RFC 3066 language code. /// WSMAN_OPTION_UI_LANGUAGE = 26, @@ -541,7 +535,7 @@ internal enum WSManSessionOption : int #region Other /// - /// int - max SOAP envelope size (kb) - default 150kb from winrm config + /// Int - max SOAP envelope size (kb) - default 150kb from winrm config /// (see 'winrm help config' for more details); the client SOAP packet size cannot surpass /// this value; this value will be also sent to the server in the SOAP request as a /// MaxEnvelopeSize header; the server will use min(MaxEnvelopeSizeKb from server configuration, @@ -549,7 +543,7 @@ internal enum WSManSessionOption : int /// WSMAN_OPTION_MAX_ENVELOPE_SIZE_KB = 28, /// - /// int (read only) - max data size (kb) provided by the client, guaranteed by + /// Int (read only) - max data size (kb) provided by the client, guaranteed by /// the winrm client implementation to fit into one SOAP packet; this is an /// approximate value calculated based on the WSMAN_OPTION_MAX_ENVELOPE_SIZE_KB (default 150kb), /// the maximum possible size of the SOAP headers and the overhead of the base64 @@ -558,27 +552,27 @@ internal enum WSManSessionOption : int /// WSMAN_OPTION_SHELL_MAX_DATA_SIZE_PER_MESSAGE_KB = 29, /// - /// string - + /// String - /// WSMAN_OPTION_REDIRECT_LOCATION = 30, /// - /// DWORD - 1 to not validate the revocation status on the server certificate; 0 - default + /// DWORD - 1 to not validate the revocation status on the server certificate; 0 - default. /// WSMAN_OPTION_SKIP_REVOCATION_CHECK = 31, /// - /// DWORD - 1 to allow default credentials for Negotiate (this is for SSL only); 0 - default + /// DWORD - 1 to allow default credentials for Negotiate (this is for SSL only); 0 - default. /// WSMAN_OPTION_ALLOW_NEGOTIATE_IMPLICIT_CREDENTIALS = 32, /// - /// DWORD - When using just a machine name in the connection string use an SSL connection. - /// 0 means HTTP, 1 means HTTPS. Default is 0. + /// DWORD - When using just a machine name in the connection string use an SSL connection. + /// 0 means HTTP, 1 means HTTPS. Default is 0. /// WSMAN_OPTION_USE_SSL = 33 #endregion } /// - /// Enum representing WSMan Shell specific options + /// Enum representing WSMan Shell specific options. /// internal enum WSManShellFlag : int { @@ -590,15 +584,15 @@ internal enum WSManShellFlag : int /// WSMAN_FLAG_NO_COMPRESSION = 1, /// - /// Enable the service to drop operation output when running disconnected + /// Enable the service to drop operation output when running disconnected. /// WSMAN_FLAG_SERVER_BUFFERING_MODE_DROP = 0x4, /// - /// Enable the service to block operation progress when output buffers are full + /// Enable the service to block operation progress when output buffers are full. /// WSMAN_FLAG_SERVER_BUFFERING_MODE_BLOCK = 0x8, /// - /// Enable receive call to not immediately retrieve results. Only applicable for Receive calls on commands + /// Enable receive call to not immediately retrieve results. Only applicable for Receive calls on commands. /// WSMAN_FLAG_RECEIVE_DELAY_OUTPUT_STREAM = 0X10 } @@ -607,7 +601,7 @@ internal enum WSManShellFlag : int #region WSManData /// - /// types of supported WSMan data. + /// Types of supported WSMan data. /// PowerShell uses only Text and DWORD (in some places). /// internal enum WSManDataType : uint @@ -617,8 +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 @@ -631,7 +624,7 @@ internal class WSManDataStruct internal class WSManBinaryOrTextDataStruct { internal int bufferLength; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + internal IntPtr data; } @@ -640,10 +633,10 @@ internal class WSManBinaryOrTextDataStruct /// internal class WSManData_ManToUn : IDisposable { - private WSManDataStruct _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + private readonly WSManDataStruct _internalData; + private IntPtr _marshalledObject = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + private IntPtr _marshalledBuffer = IntPtr.Zero; /// @@ -653,7 +646,7 @@ internal class WSManData_ManToUn : IDisposable /// internal WSManData_ManToUn(byte[] data) { - Dbg.Assert(null != data, "Data cannot be null"); + Dbg.Assert(data != null, "Data cannot be null"); _internalData = new WSManDataStruct(); _internalData.binaryOrTextData = new WSManBinaryOrTextDataStruct(); @@ -676,7 +669,7 @@ internal WSManData_ManToUn(byte[] data) /// internal WSManData_ManToUn(string data) { - Dbg.Assert(null != data, "Data cannot be null"); + Dbg.Assert(data != null, "Data cannot be null"); _internalData = new WSManDataStruct(); _internalData.binaryOrTextData = new WSManBinaryOrTextDataStruct(); @@ -690,24 +683,13 @@ 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. /// internal uint Type { get { return _internalData.type; } + set { _internalData.type = value; } } @@ -717,6 +699,7 @@ internal uint Type internal int BufferLength { get { return _internalData.binaryOrTextData.bufferLength; } + set { _internalData.binaryOrTextData.bufferLength = value; } } @@ -740,6 +723,7 @@ private void Dispose(bool isDisposing) Marshal.FreeHGlobal(_marshalledBuffer); _marshalledBuffer = IntPtr.Zero; } + if (_marshalledObject != IntPtr.Zero) { Marshal.FreeHGlobal(_marshalledObject); @@ -748,13 +732,21 @@ private void Dispose(bool isDisposing) } /// - /// implicit IntPtr conversion + /// Finalizes an instance of the class. + /// + ~WSManData_ManToUn() + { + Dispose(false); + } + + /// + /// Implicit IntPtr conversion. /// /// /// public static implicit operator IntPtr(WSManData_ManToUn data) { - if (null != data) + if (data != null) { return data._marshalledObject; } @@ -771,9 +763,11 @@ internal class WSManData_UnToMan /// Gets the type of data. /// private uint _type; + internal uint Type { get { return _type; } + set { _type = value; } } @@ -781,14 +775,16 @@ internal uint Type /// Gets the buffer length of data. /// private int _bufferLength; + internal int BufferLength { get { return _bufferLength; } + set { _bufferLength = value; } } - private string _text; + internal string Text { get @@ -796,19 +792,20 @@ internal string Text if (this.Type == (uint)WSManDataType.WSMAN_DATA_TYPE_TEXT) return _text; else - return String.Empty; + return string.Empty; } } - private System.Byte[] _data; - internal System.Byte[] Data + private byte[] _data; + + internal byte[] Data { get { if (this.Type == (uint)WSManDataType.WSMAN_DATA_TYPE_BINARY) return _data; else - return Utils.EmptyArray(); + return Array.Empty(); } } @@ -832,12 +829,13 @@ internal static WSManData_UnToMan UnMarshal(WSManDataStruct dataStruct) string tempText = Marshal.PtrToStringUni(dataStruct.binaryOrTextData.data, dataStruct.binaryOrTextData.bufferLength); newData._text = tempText; } + break; case (uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_BINARY: if (dataStruct.binaryOrTextData.bufferLength > 0) { // copy data from unmanaged heap to managed heap. - System.Byte[] dataRecvd = new System.Byte[dataStruct.binaryOrTextData.bufferLength]; + byte[] dataRecvd = new byte[dataStruct.binaryOrTextData.bufferLength]; Marshal.Copy( dataStruct.binaryOrTextData.data, dataRecvd, @@ -845,10 +843,12 @@ internal static WSManData_UnToMan UnMarshal(WSManDataStruct dataStruct) dataStruct.binaryOrTextData.bufferLength); newData._data = dataRecvd; } + break; default: throw new NotSupportedException(); } + return newData; } @@ -861,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); @@ -877,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; /// @@ -928,7 +928,7 @@ private struct WSManDWordDataInternal internal struct WSManStreamIDSetStruct { internal int streamIDsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + internal IntPtr streamIDs; } @@ -938,7 +938,7 @@ internal struct WSManStreamIDSet_ManToUn private MarshalledObject _data; /// - /// Constructor + /// Constructor. /// /// internal WSManStreamIDSet_ManToUn(string[] streamIds) @@ -963,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++) @@ -971,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; @@ -986,7 +986,7 @@ internal void Dispose() } /// - /// Implicit cast to IntPtr + /// Implicit cast to IntPtr. /// /// /// @@ -1010,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); @@ -1042,6 +1042,7 @@ internal static WSManStreamIDSet_UnToMan UnMarshal(IntPtr unmanagedData) result.streamIDs = idsArray; result.streamIDsCount = resultInternal.streamIDsCount; } + return result; } } @@ -1054,17 +1055,17 @@ internal static WSManStreamIDSet_UnToMan UnMarshal(IntPtr unmanagedData) internal struct WSManOption { /// - /// Underlying type = PCWSTR + /// Underlying type = PCWSTR. /// [MarshalAs(UnmanagedType.LPWStr)] internal string name; /// - /// Underlying type = PCWSTR + /// Underlying type = PCWSTR. /// [MarshalAs(UnmanagedType.LPWStr)] internal string value; /// - /// Underlying type = BOOL + /// Underlying type = BOOL. /// internal bool mustComply; } @@ -1077,16 +1078,16 @@ internal struct WSManOptionSetStruct { internal int optionsCount; /// - /// Pointer to an array of WSManOption objects + /// Pointer to an array of WSManOption objects. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr options; + internal bool optionsMustUnderstand; } /// /// Option set struct used to pass optional information - /// with WSManCreateShellEx + /// with WSManCreateShellEx. /// internal struct WSManOptionSet : IDisposable { @@ -1101,7 +1102,7 @@ internal struct WSManOptionSet : IDisposable /// internal WSManOptionSet(WSManOption[] options) { - Dbg.Assert(null != options, "options cannot be null"); + Dbg.Assert(options != null, "options cannot be null"); int sizeOfOption = Marshal.SizeOf(); _optionSet = new WSManOptionSetStruct(); @@ -1126,11 +1127,10 @@ 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; @@ -1164,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(); } @@ -1209,7 +1209,6 @@ internal static WSManOptionSet UnMarshal(WSManOptionSetStruct resultInternal) } /// - /// /// internal struct WSManCommandArgSet : IDisposable { @@ -1217,11 +1216,12 @@ internal struct WSManCommandArgSet : IDisposable 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 @@ -1246,14 +1246,13 @@ internal WSManCommandArgSet(byte[] firstArgument) this.argsCount = 0; } - /// /// Free resources. /// public void Dispose() { IntPtr firstArgAddress = Marshal.ReadIntPtr(_internalData.args); - if (IntPtr.Zero != firstArgAddress) + if (firstArgAddress != IntPtr.Zero) { Marshal.FreeHGlobal(firstArgAddress); } @@ -1264,7 +1263,7 @@ public void Dispose() } /// - /// Implicit cast to IntPtr + /// Implicit cast to IntPtr. /// /// /// @@ -1291,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); @@ -1310,6 +1309,7 @@ internal static WSManCommandArgSet UnMarshal(IntPtr unmanagedData) result.argsCount = resultInternal.argsCount; result.args = tempArgs; } + return result; } @@ -1322,7 +1322,7 @@ internal struct WSManShellDisconnectInfo : IDisposable private struct WSManShellDisconnectInfoInternal { /// - /// new idletimeout for the server shell that overrides the original idletimeout specified in WSManCreateShell + /// New idletimeout for the server shell that overrides the original idletimeout specified in WSManCreateShell. /// internal uint idleTimeoutMs; } @@ -1347,7 +1347,7 @@ public void Dispose() } /// - /// implicit IntPtr. + /// Implicit IntPtr. /// /// /// @@ -1373,11 +1373,11 @@ internal struct WSManShellStartupInfoStruct /// internal IntPtr outputStreamSet; /// - /// Idle timeout + /// Idle timeout. /// internal uint idleTimeoutMs; /// - /// working directory of the shell. + /// Working directory of the shell. /// [MarshalAs(UnmanagedType.LPWStr)] internal string workingDirectory; @@ -1396,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 { @@ -1440,7 +1440,7 @@ public void Dispose() } /// - /// implicit IntPtr. + /// Implicit IntPtr. /// /// /// @@ -1474,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); @@ -1486,6 +1486,7 @@ internal static WSManShellStartupInfo_UnToMan UnMarshal(IntPtr unmanagedData) result.environmentVariableSet = WSManEnvironmentVariableSet.UnMarshal(resultInternal.environmentVariableSet); result.name = resultInternal.name; } + return result; } } @@ -1509,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); @@ -1531,6 +1532,7 @@ internal static WSManEnvironmentVariableSet UnMarshal(IntPtr unmanagedData) result.vars = varsArray; result.varsCount = resultInternal.varsCount; } + return result; } @@ -1546,13 +1548,14 @@ internal struct WSManEnvironmentVariableInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string name; + [MarshalAs(UnmanagedType.LPWStr)] internal string value; } } /// - /// Proxy Info used with WSManCreateSession + /// Proxy Info used with WSManCreateSession. /// internal class WSManProxyInfo : IDisposable { @@ -1562,10 +1565,10 @@ private struct WSManProxyInfoInternal public int proxyAccessType; public WSManUserNameAuthenticationCredentials.WSManUserNameCredentialStruct proxyAuthCredentialsStruct; } + private MarshalledObject _data; /// - /// /// /// /// @@ -1577,7 +1580,7 @@ internal WSManProxyInfo(ProxyAccessType proxyAccessType, internalInfo.proxyAuthCredentialsStruct = new WSManUserNameAuthenticationCredentials.WSManUserNameCredentialStruct(); internalInfo.proxyAuthCredentialsStruct.authenticationMechanism = WSManAuthenticationMechanism.WSMAN_FLAG_DEFAULT_AUTHENTICATION; - if (null != authCredentials) + if (authCredentials != null) { internalInfo.proxyAuthCredentialsStruct = authCredentials.CredentialStruct; } @@ -1593,7 +1596,7 @@ public void Dispose() } /// - /// implicit IntPtr + /// Implicit IntPtr. /// /// /// @@ -1608,8 +1611,8 @@ public static implicit operator IntPtr(WSManProxyInfo proxyInfo) #region WSMan Shell Async /// - /// flags used by all callback functions: WSMAN_COMPLETION_FUNCTION, - /// WSMAN_SUBSCRIPTION_COMPLETION_FUNCTION and WSMAN_SHELL_COMPLETION_FUNCTION + /// Flags used by all callback functions: WSMAN_COMPLETION_FUNCTION, + /// WSMAN_SUBSCRIPTION_COMPLETION_FUNCTION and WSMAN_SHELL_COMPLETION_FUNCTION. /// internal enum WSManCallbackFlags { @@ -1715,14 +1718,14 @@ IntPtr data /// /// Struct which holds reference to the callback(delegate) passed to WSMan - /// API + /// API. /// 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) { @@ -1752,6 +1755,7 @@ internal struct WSManShellAsyncInternal internal IntPtr operationContext; internal IntPtr asyncCallback; } + private MarshalledObject _data; private WSManShellAsyncInternal _internalData; @@ -1783,15 +1787,15 @@ internal struct WSManError { internal int errorCode; /// - /// extended error description from the fault; + /// Extended error description from the fault; /// internal string errorDetail; /// - /// language for error description (RFC 3066 language code); it can be NULL + /// Language for error description (RFC 3066 language code); it can be NULL. /// internal string language; /// - /// machine id; it can be NULL + /// Machine id; it can be NULL. /// internal string machineName; @@ -1824,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); @@ -1836,6 +1840,7 @@ internal static WSManCreateShellDataResult UnMarshal(IntPtr unmanagedData) result.data = connectData; } + return result; } @@ -1908,15 +1913,15 @@ internal class WSManReceiveDataResult /// internal byte[] data; /// - /// Stream the data belongs to + /// Stream the data belongs to. /// internal string stream; /// /// Constructs a WSManReceiveDataResult from the unmanaged pointer. /// This involves copying data from unmanaged memory to managed heap. - /// Currently PowerShell supports only text data on the wire, so this - /// method asserts if the data is not text. + /// Currently PowerShell supports only binary data on the wire, so this + /// method asserts if the data is not binary. /// /// /// Pointer to unmanaged data. @@ -1928,7 +1933,7 @@ internal static WSManReceiveDataResult UnMarshal(IntPtr unmanagedData) WSManReceiveDataResultInternal result1 = Marshal.PtrToStructure(unmanagedData); - //copy data from unmanaged heap to managed heap. + // copy data from unmanaged heap to managed heap. byte[] dataRecvd = null; if (result1.data.binaryData.bufferLength > 0) { @@ -1938,8 +1943,11 @@ internal static WSManReceiveDataResult UnMarshal(IntPtr unmanagedData) 0, result1.data.binaryData.bufferLength); } + +#if !UNIX Dbg.Assert(result1.data.type == (uint)WSManDataType.WSMAN_DATA_TYPE_BINARY, - "ReceiveDataResult can receive only text data"); + "ReceiveDataResult can receive only binary data"); +#endif WSManReceiveDataResult result = new WSManReceiveDataResult(); result.data = dataRecvd; @@ -1953,9 +1961,12 @@ private struct WSManReceiveDataResultInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string streamId; + internal WSManDataStruct data; + [MarshalAs(UnmanagedType.LPWStr)] internal string commandState; + internal int exitCode; } @@ -1986,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; @@ -2031,10 +2042,10 @@ internal IntPtr shutdownNotificationHandle /// internal static WSManPluginRequest UnMarshal(IntPtr unmanagedData) { - //Dbg.Assert(IntPtr.Zero != unmanagedData, "unmanagedData must be non-null. This means WinRM sent a bad pointer."); + // 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); @@ -2046,25 +2057,28 @@ internal static WSManPluginRequest UnMarshal(IntPtr unmanagedData) result._internalDetails = resultInternal; result.unmanagedHandle = unmanagedData; } + return result; } /// - /// Representation of WSMAN_PLUGIN_REQUEST + /// Representation of WSMAN_PLUGIN_REQUEST. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WSManPluginRequestInternal { /// - /// WSManSenderDetails + /// WSManSenderDetails. /// internal IntPtr senderDetails; + [MarshalAs(UnmanagedType.LPWStr)] internal string locale; + [MarshalAs(UnmanagedType.LPWStr)] internal string resourceUri; /// - /// WSManOperationInfo + /// WSManOperationInfo. /// internal IntPtr operationInfo; internal bool shutdownNotification; @@ -2077,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; /// @@ -2089,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); @@ -2100,24 +2114,27 @@ internal static WSManSenderDetails UnMarshal(IntPtr unmanagedData) result.clientToken = resultInternal.clientToken; // TODO: UnMarshaling needed here!!!! result.httpUrl = resultInternal.httpUrl; } + return result; } /// - /// Managed representation of WSMAN_SENDER_DETAILS + /// Managed representation of WSMAN_SENDER_DETAILS. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WSManSenderDetailsInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string senderName; + [MarshalAs(UnmanagedType.LPWStr)] internal string authenticationMechanism; /// - /// WSManCertificateDetails + /// WSManCertificateDetails. /// internal IntPtr certificateDetails; internal IntPtr clientToken; + [MarshalAs(UnmanagedType.LPWStr)] internal string httpUrl; } @@ -2139,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); @@ -2149,20 +2166,24 @@ internal static WSManCertificateDetails UnMarshal(IntPtr unmanagedData) result.issuerThumbprint = resultInternal.issuerThumbprint; result.subjectName = resultInternal.subjectName; } + return result; } /// - /// Managed representation of WSMAN_CERTIFICATE_DETAILS + /// Managed representation of WSMAN_CERTIFICATE_DETAILS. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WSManCertificateDetailsInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string subject; + [MarshalAs(UnmanagedType.LPWStr)] internal string issuerName; + [MarshalAs(UnmanagedType.LPWStr)] internal string issuerThumbprint; + [MarshalAs(UnmanagedType.LPWStr)] internal string subjectName; } @@ -2184,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); @@ -2194,6 +2215,7 @@ internal static WSManOperationInfo UnMarshal(IntPtr unmanagedData) result.selectorSet = WSManSelectorSet.UnMarshal(resultInternal.selectorSet); result.optionSet = WSManOptionSet.UnMarshal(resultInternal.optionSet); } + return result; } /// @@ -2211,25 +2233,27 @@ private struct WSManOperationInfoInternal } /// - /// Managed representation of WSMAN_FRAGMENT + /// Managed representation of WSMAN_FRAGMENT. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct WSManFragmentInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string path; + [MarshalAs(UnmanagedType.LPWStr)] internal string dialect; } /// - /// Managed representation of WSMAN_FILTER + /// Managed representation of WSMAN_FILTER. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct WSManFilterInternal { [MarshalAs(UnmanagedType.LPWStr)] internal string filter; + [MarshalAs(UnmanagedType.LPWStr)] internal string dialect; } @@ -2269,7 +2293,7 @@ internal static WSManSelectorSet UnMarshal(WSManSelectorSetStruct resultInternal } /// - /// Managed representation of WSMAN_SELECTOR_SET + /// Managed representation of WSMAN_SELECTOR_SET. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct WSManSelectorSetStruct @@ -2282,13 +2306,14 @@ internal struct WSManSelectorSetStruct } /// - /// Managed representation of WSMAN_OPTION_SET + /// Managed representation of WSMAN_OPTION_SET. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct WSManKeyStruct { [MarshalAs(UnmanagedType.LPWStr)] internal string key; + [MarshalAs(UnmanagedType.LPWStr)] internal string value; } @@ -2319,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 @@ -2352,17 +2377,17 @@ 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; /// this is sync call it is recommended that all pending operations are either /// completed or cancelled before calling this API. Returns a non zero error - /// code upon failure + /// code upon failure. /// /// /// @@ -2372,7 +2397,7 @@ internal static extern void WSManCloseSession(IntPtr wsManSessionHandle, /// /// WSManSetSessionOption API - set session options - /// Returns a non zero error code upon failure + /// Returns a non zero error code upon failure. /// /// /// @@ -2398,16 +2423,16 @@ internal static extern int WSManSetSessionOption(IntPtr wsManSessionHandle, /// /// WSManGetSessionOptionAsDword API - get a session option. Returns a non - /// zero error code upon failure + /// zero error code upon failure. /// /// /// /// /// 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); @@ -2416,20 +2441,20 @@ internal static extern void WSManGetSessionOptionAsDword(IntPtr wsManSessionHand /// will be used as the language code to get the error message in. /// /// - /// session option to get + /// Session option to get. /// 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 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; } @@ -2442,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; } @@ -2515,16 +2540,15 @@ 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); /// - /// /// /// /// @@ -2537,16 +2561,14 @@ 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); /// - /// /// /// /// @@ -2559,7 +2581,6 @@ internal static extern void WSManDisconnectShellEx(IntPtr wsManSessionHandle, IntPtr asyncCallback); /// - /// /// /// /// @@ -2569,7 +2590,6 @@ internal static extern void WSManReconnectShellEx(IntPtr wsManSessionHandle, int flags, IntPtr asyncCallback); - [DllImport(WSManNativeApi.WSManClientApiDll, EntryPoint = "WSManReconnectShellCommand", SetLastError = false, CharSet = CharSet.Unicode)] internal static extern void WSManReconnectShellCommandEx(IntPtr wsManCommandHandle, int flags, @@ -2611,7 +2631,6 @@ internal static extern void WSManRunShellCommandEx(IntPtr shellOperationHandle, IntPtr asyncCallback, ref IntPtr commandOperationHandle); - [DllImport(WSManNativeApi.WSManClientApiDll, EntryPoint = "WSManConnectShellCommand", SetLastError = false, CharSet = CharSet.Unicode)] internal static extern void WSManConnectShellCommandEx(IntPtr shellOperationHandle, int flags, @@ -2622,7 +2641,6 @@ internal static extern void WSManConnectShellCommandEx(IntPtr shellOperationHand IntPtr asyncCallback, ref IntPtr commandOperationHandle); - /// /// Registers a callback with WSMan to receive output from the remote end. /// If commandOperationHandle is null, then the receive callback is registered @@ -2631,7 +2649,7 @@ internal static extern void WSManConnectShellCommandEx(IntPtr shellOperationHand /// command + shell. There will be only 1 callback active per command or per shell. /// So if there are multiple commands active, then there can be 1 callback active /// for each of them. - /// TODO: How to unregister the callback + /// TODO: How to unregister the callback. /// /// /// Shell Operation Handle. @@ -2640,7 +2658,7 @@ internal static extern void WSManConnectShellCommandEx(IntPtr shellOperationHand /// Command Operation Handle. If null, the receive request corresponds /// to the shell. /// - /// + /// /// /// /// callback which receives the data asynchronously. @@ -2654,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. @@ -2695,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 @@ -2703,7 +2721,7 @@ private static extern void WSManSendShellInputExInternal(IntPtr shellOperationHa /// the function waits for the callback to finish; If the operation was not finished, /// the operation is cancelled and the operation callback is called with /// WSMAN_ERROR_OPERATION_ABORTED error; then the WSManCloseShellOperationEx callback - /// is called with WSMAN_FLAG_CALLBACK_END_OF_OPERATION flag as result of this operation + /// is called with WSMAN_FLAG_CALLBACK_END_OF_OPERATION flag as result of this operation. /// /// /// Shell handle to Close. @@ -2719,7 +2737,7 @@ internal static extern void WSManCloseShell(IntPtr shellHandle, /// /// Closes a command (signals the termination of a command); the WSManCloseCommand callback - /// is called with WSMAN_FLAG_CALLBACK_END_OF_OPERATION flag as result of this operation + /// is called with WSMAN_FLAG_CALLBACK_END_OF_OPERATION flag as result of this operation. /// /// /// Command handle to Close. @@ -2733,7 +2751,6 @@ internal static extern void WSManCloseCommand(IntPtr cmdHandle, int flags, IntPtr asyncCallback); - /// /// Sends a signal. If is null, then the signal will /// be sent to shell. @@ -2775,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; @@ -2783,11 +2800,11 @@ internal static string WSManGetErrorMessage(IntPtr wsManAPIHandle, int errorCode // get language code. string langCode = CultureInfo.CurrentUICulture.Name; - string returnval = ""; + 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; } @@ -2800,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; } @@ -2824,7 +2841,7 @@ internal static string WSManGetErrorMessage(IntPtr wsManAPIHandle, int errorCode } /// - /// Function that retrieves WSMan error messages with a particular error code and a language code + /// Function that retrieves WSMan error messages with a particular error code and a language code. /// /// /// The handle returned by WSManInitialize API call. It cannot be NULL. @@ -2886,7 +2903,7 @@ internal static extern int WSManPluginGetOperationParameters( IntPtr requestDetails, int flags, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WSManDataStruct data); - //[In, Out] ref IntPtr data); + // [In, Out] ref IntPtr data); /// /// Reports the completion of an operation by all operation entry points except for the @@ -2904,7 +2921,6 @@ internal static extern int WSManPluginOperationComplete( int errorCode, [MarshalAs(UnmanagedType.LPWStr)] string extendedInformation); - internal enum WSManFlagReceive : int { /// @@ -2973,7 +2989,7 @@ internal static extern int WSManPluginReportContext( /// Registers the shutdown callback. /// /// Specifies the resource URI, options, locale, shutdown flag, and handle for the request. - /// Callback to be executed on shutdown + /// Callback to be executed on shutdown. /// /// [DllImport(WSManNativeApi.WSManProviderApiDll, SetLastError = false, CharSet = CharSet.Unicode)] @@ -2988,8 +3004,9 @@ internal static extern void WSManPluginRegisterShutdownCallback( /// /// Interface to enable stubbing of the WSManNativeApi PInvoke calls for /// unit testing. - /// Note: It is implemented as a class to avoid exposing it outside the module + /// 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! @@ -3008,7 +3025,7 @@ int WSManPluginOperationComplete( int WSManPluginReceiveResult( IntPtr requestDetails, int flags, - string stream, + string? stream, IntPtr streamResult, string commandState, int exitCode); @@ -3023,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 1abef92b3bc..79d583e063f 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs @@ -1,8 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. // ---------------------------------------------------------------------- @@ -84,7 +83,7 @@ internal enum WSManPluginErrorCodes : int } /// - /// class that holds plugin + shell context information used to handle + /// Class that holds plugin + shell context information used to handle /// shutdown notifications. /// /// Explicit destruction and release of the IntPtrs is not required because @@ -129,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. @@ -139,7 +138,7 @@ internal class WSManPluginInstance /// It is static because static instances of this class use the facade. Otherwise, /// it would be passed in via a parameterized constructor. /// - internal static IWSManNativeApiFacade wsmanPinvokeStatic = new WSManNativeApiFacade(); + internal static readonly IWSManNativeApiFacade wsmanPinvokeStatic = new WSManNativeApiFacade(); #endregion @@ -148,11 +147,11 @@ internal class WSManPluginInstance internal WSManPluginInstance() { _activeShellSessions = new Dictionary(); - _syncObject = new System.Object(); + _syncObject = new object(); } /// - /// static constructor to listen to unhandled exceptions + /// Static constructor to listen to unhandled exceptions /// from the AppDomain and log the errors /// Note: It is not necessary to instantiate IWSManNativeApi here because it is not used. /// @@ -189,7 +188,16 @@ internal void CreateShell( WSManNativeApi.WSManShellStartupInfo_UnToMan startupInfo, WSManNativeApi.WSManData_UnToMan inboundShellInformation) { - if (null == requestDetails) + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateRemoteSession, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + "null", + "CreateShell: Create a new shell in the plugin context", + string.Empty); + + if (requestDetails == null) { // Nothing can be done because requestDetails are required to report operation complete PSEtwLog.LogAnalyticInformational(PSEventId.ReportOperationComplete, @@ -201,12 +209,12 @@ internal void CreateShell( RemotingErrorIdStrings.WSManPluginNullInvalidInput, "requestDetails", "WSManPluginShell"), - String.Empty); + string.Empty); return; } - if ((null == requestDetails.senderDetails) || - (null == requestDetails.operationInfo)) + if ((requestDetails.senderDetails == null) || + (requestDetails.operationInfo == null)) { ReportOperationComplete( requestDetails, @@ -218,7 +226,7 @@ internal void CreateShell( return; } - if (null == startupInfo) + if (startupInfo == null) { ReportOperationComplete( requestDetails, @@ -230,7 +238,16 @@ internal void CreateShell( return; } - if ((0 == startupInfo.inputStreamSet.streamIDsCount) || (0 == startupInfo.outputStreamSet.streamIDsCount)) + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateRemoteSession, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + requestDetails.ToString(), + "CreateShell: NULL checks being performed", + string.Empty); + + if ((startupInfo.inputStreamSet.streamIDsCount == 0) || (startupInfo.outputStreamSet.streamIDsCount == 0)) { ReportOperationComplete( requestDetails, @@ -242,7 +259,7 @@ internal void CreateShell( return; } - if (String.IsNullOrEmpty(extraInfo)) + if (string.IsNullOrEmpty(extraInfo)) { ReportOperationComplete( requestDetails, @@ -265,7 +282,7 @@ internal void CreateShell( int result = WSManPluginConstants.ExitCodeSuccess; WSManPluginShellSession mgdShellSession; WSManPluginOperationShutdownContext context; - System.Byte[] convertedBase64 = null; + byte[] convertedBase64 = null; try { @@ -279,22 +296,28 @@ internal void CreateShell( { serverTransportMgr = new WSManPluginServerTransportManager(BaseTransportManager.DefaultFragmentSize, new PSRemotingCryptoHelperServer()); } - else { serverTransportMgr = new WSManPluginServerTransportManager(BaseTransportManager.DefaultFragmentSize, null); } - PSEtwLog.LogAnalyticInformational(PSEventId.ServerCreateRemoteSession, + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateRemoteSession, 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); - if (null == remoteShellSession) + 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) { WSManPluginInstance.ReportWSManOperationComplete( requestDetails, @@ -303,7 +326,7 @@ internal void CreateShell( } context = new WSManPluginOperationShutdownContext(pluginContext, requestDetails.unmanagedHandle, IntPtr.Zero, false); - if (null == context) + if (context == null) { ReportOperationComplete(requestDetails, WSManPluginErrorCodes.OutOfMemory); return; @@ -312,11 +335,11 @@ internal void CreateShell( // Create a shell session wrapper to track and service future interactions. mgdShellSession = new WSManPluginShellSession(requestDetails, serverTransportMgr, remoteShellSession, context); AddToActiveShellSessions(mgdShellSession); - mgdShellSession.SessionClosed += new EventHandler(HandleShellSessionClosed); + mgdShellSession.SessionClosed += HandleShellSessionClosed; - if (null != inboundShellInformation) + 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( @@ -337,13 +360,14 @@ internal void CreateShell( } // now report the shell context to WSMan. - PSEtwLog.LogAnalyticInformational(PSEventId.ReportContext, + PSEtwLog.LogAnalyticInformational( + PSEventId.ReportContext, PSOpcode.Connect, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, requestDetails.ToString(), requestDetails.ToString()); result = wsmanPinvokeStatic.WSManPluginReportContext(requestDetails.unmanagedHandle, 0, requestDetails.unmanagedHandle); - if (WSManPluginConstants.ExitCodeSuccess != result) + if (result != WSManPluginConstants.ExitCodeSuccess) { ReportOperationComplete( requestDetails, @@ -372,7 +396,7 @@ internal void CreateShell( bool isRegisterWaitForSingleObjectSucceeded = true; - //always synchronize calls to OperationComplete once notification handle is registered.. else duplicate OperationComplete calls are bound to happen + // always synchronize calls to OperationComplete once notification handle is registered.. else duplicate OperationComplete calls are bound to happen lock (mgdShellSession.shellSyncObject) { mgdShellSession.registeredShutdownNotification = 1; @@ -387,9 +411,9 @@ internal void CreateShell( } else { - //On non-windows platforms the shutdown notification is done through a callback instead of a windows event handle. - //Register the callback and this will then signal the event. Note, the gch object is deleted in the shell shutdown - //notification that will always come in to shut down the operation. + // On non-windows platforms the shutdown notification is done through a callback instead of a windows event handle. + // Register the callback and this will then signal the event. Note, the gch object is deleted in the shell shutdown + // notification that will always come in to shut down the operation. GCHandle gch = GCHandle.Alloc(eventWaitHandle); IntPtr p = GCHandle.ToIntPtr(gch); @@ -400,14 +424,13 @@ internal void CreateShell( p); } - mgdShellSession.registeredShutDownWaitHandle = ThreadPool.RegisterWaitForSingleObject( eventWaitHandle, new WaitOrTimerCallback(WSManPluginManagedEntryWrapper.PSPluginOperationShutdownCallback), context, -1, // INFINITE true); // TODO: Do I need to worry not being able to set missing WT_TRANSFER_IMPERSONATION? - if (null == mgdShellSession.registeredShutDownWaitHandle) + if (mgdShellSession.registeredShutDownWaitHandle == null) { isRegisterWaitForSingleObjectSucceeded = false; } @@ -442,12 +465,22 @@ internal void CreateShell( bool ignore = mgdShellSession.registeredShutDownWaitHandle.Unregister(null); mgdShellSession.registeredShutDownWaitHandle = null; - //this will called OperationComplete + // this will called OperationComplete PerformCloseOperation(context); } + return; } + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateRemoteSession, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + requestDetails.ToString(), + "CreateShell: Completed", + string.Empty); + return; } @@ -458,18 +491,20 @@ internal void CreateShell( internal void CloseShellOperation( WSManPluginOperationShutdownContext context) { - PSEtwLog.LogAnalyticInformational(PSEventId.ServerCloseOperation, - PSOpcode.Disconnect, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCloseOperation, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, ((IntPtr)context.shellContext).ToString(), ((IntPtr)context.commandContext).ToString(), context.isReceiveOperation.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(context.shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { // this should never be the case. this will protect the service. - //Dbg.Assert(false, "context.shellContext not matched"); + // Dbg.Assert(false, "context.shellContext not matched"); return; } @@ -481,31 +516,49 @@ internal void CloseShellOperation( System.Exception reasonForClose = new System.Exception(RemotingErrorIdStrings.WSManPluginOperationClose); mgdShellSession.CloseOperation(context, reasonForClose); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCloseOperation, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + "CloseShellOperation: Completed", + string.Empty); } internal void CloseCommandOperation( WSManPluginOperationShutdownContext context) { - PSEtwLog.LogAnalyticInformational(PSEventId.ServerCloseOperation, - PSOpcode.Disconnect, PSTask.None, + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCloseOperation, + PSOpcode.Disconnect, + PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, context.shellContext.ToString(), context.commandContext.ToString(), context.isReceiveOperation.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(context.shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { // this should never be the case. this will protect the service. - //Dbg.Assert(false, "context.shellContext not matched"); + // Dbg.Assert(false, "context.shellContext not matched"); return; } mgdShellSession.CloseCommandOperation(context); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCloseOperation, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + "CloseCommandOperation: Completed", + string.Empty); } /// - /// adds shell session to activeShellSessions store and returns the id + /// Adds shell session to activeShellSessions store and returns the id /// at which the session is added. /// /// @@ -516,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)) { @@ -527,7 +580,7 @@ private void AddToActiveShellSessions( } } - if (-1 != count) + if (count != -1) { // Raise session count changed event WSManServerChannelEvents.RaiseActiveSessionsChangedEvent(new ActiveSessionsChangedEventArgs(count)); @@ -537,8 +590,8 @@ private void AddToActiveShellSessions( /// /// Retrieves a WSManPluginShellSession if matched. /// - /// Shell context (WSManPluginRequest.unmanagedHandle) - /// null WSManPluginShellSession if not matched. The object if matched. + /// Shell context (WSManPluginRequest.unmanagedHandle). + /// Null WSManPluginShellSession if not matched. The object if matched. private WSManPluginShellSession GetFromActiveShellSessions( IntPtr key) { @@ -566,7 +619,8 @@ private void DeleteFromActiveShellSessions( count = _activeShellSessions.Count; } } - if (-1 != count) + + if (count != -1) { // Raise session count changed event WSManServerChannelEvents.RaiseActiveSessionsChangedEvent(new ActiveSessionsChangedEventArgs(count)); @@ -576,31 +630,32 @@ private void DeleteFromActiveShellSessions( /// /// Triggers a shell close from an event handler. /// - /// Shell context + /// Shell context. /// private void HandleShellSessionClosed( - Object source, + object source, EventArgs e) { DeleteFromActiveShellSessions((IntPtr)source); } /// - /// Helper function to validate incoming values + /// Helper function to validate incoming values. /// /// /// /// /// - private bool validateIncomingContexts( + private static bool validateIncomingContexts( WSManNativeApi.WSManPluginRequest requestDetails, IntPtr shellContext, string inputFunctionName) { - if (null == requestDetails) + if (requestDetails == null) { // Nothing can be done because requestDetails are required to report operation complete - PSEtwLog.LogAnalyticInformational(PSEventId.ReportOperationComplete, + PSEtwLog.LogAnalyticInformational( + PSEventId.ReportOperationComplete, PSOpcode.Close, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, "null", @@ -609,11 +664,11 @@ private bool validateIncomingContexts( RemotingErrorIdStrings.WSManPluginNullInvalidInput, "requestDetails", inputFunctionName), - String.Empty); + string.Empty); return false; } - if (IntPtr.Zero == shellContext) + if (shellContext == IntPtr.Zero) { ReportOperationComplete( requestDetails, @@ -624,6 +679,7 @@ private bool validateIncomingContexts( inputFunctionName)); return false; } + return true; } @@ -644,6 +700,15 @@ internal void CreateCommand( string commandLine, WSManNativeApi.WSManCommandArgSet arguments) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateCommandSession, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "CreateCommand: Create a new command in the shell context", + string.Empty); + if (!validateIncomingContexts(requestDetails, shellContext, "WSManRunShellCommandEx")) { return; @@ -651,13 +716,14 @@ internal void CreateCommand( SetThreadProperties(requestDetails); - PSEtwLog.LogAnalyticInformational(PSEventId.ServerCreateCommandSession, - PSOpcode.Connect, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateCommandSession, + PSOpcode.Connect, PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, ((IntPtr)shellContext).ToString(), requestDetails.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { ReportOperationComplete( requestDetails, @@ -668,6 +734,15 @@ internal void CreateCommand( } mgdShellSession.CreateCommand(pluginContext, requestDetails, flags, commandLine, arguments); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerCreateCommandSession, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "CreateCommand: Create a new command in the shell context completed", + string.Empty); } internal void StopCommand( @@ -675,10 +750,11 @@ internal void StopCommand( IntPtr shellContext, IntPtr commandContext) { - if (null == requestDetails) + if (requestDetails == null) { // Nothing can be done because requestDetails are required to report operation complete - PSEtwLog.LogAnalyticInformational(PSEventId.ReportOperationComplete, + PSEtwLog.LogAnalyticInformational( + PSEventId.ReportOperationComplete, PSOpcode.Close, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, "null", @@ -687,21 +763,23 @@ internal void StopCommand( RemotingErrorIdStrings.WSManPluginNullInvalidInput, "requestDetails", "StopCommand"), - String.Empty); + string.Empty); return; } SetThreadProperties(requestDetails); - PSEtwLog.LogAnalyticInformational(PSEventId.ServerStopCommand, - PSOpcode.Disconnect, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, - ((IntPtr)shellContext).ToString(), - ((IntPtr)commandContext).ToString(), - requestDetails.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerStopCommand, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { ReportOperationComplete( requestDetails, @@ -712,7 +790,7 @@ internal void StopCommand( } WSManPluginCommandSession mgdCommandSession = mgdShellSession.GetCommandSession(commandContext); - if (null == mgdCommandSession) + if (mgdCommandSession == null) { ReportOperationComplete( requestDetails, @@ -723,13 +801,22 @@ internal void StopCommand( } mgdCommandSession.Stop(requestDetails); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerStopCommand, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + "StopCommand: completed", + string.Empty); } internal void Shutdown() { - PSEtwLog.LogAnalyticInformational(PSEventId.WSManPluginShutdown, - PSOpcode.ShuttingDown, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManPluginShutdown, + PSOpcode.ShuttingDown, PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic); // all active shells should be closed at this point Dbg.Assert(_activeShellSessions.Count == 0, "All active shells should be closed"); @@ -739,7 +826,7 @@ internal void Shutdown() } /// - /// Connect + /// Connect. /// /// /// @@ -753,20 +840,34 @@ internal void ConnectShellOrCommand( IntPtr commandContext, WSManNativeApi.WSManData_UnToMan inboundConnectInformation) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "ConnectShellOrCommand: Connect", + string.Empty); + if (!validateIncomingContexts(requestDetails, shellContext, "ConnectShellOrCommand")) { return; } - //TODO... What does this mean from a new client that has specified diff locale from original client? + // TODO... What does this mean from a new client that has specified diff locale from original client? SetThreadProperties(requestDetails); - //TODO.. Add new ETW events and log - /*etwTracer.AnalyticChannel.WriteInformation(PSEventId.ServerReceivedData, - PSOpcode.Open, PSTask.None, - ((IntPtr)shellContext).ToString(), ((IntPtr)commandContext).ToString(), ((IntPtr)requestDetails).ToString());*/ + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { ReportOperationComplete( requestDetails, @@ -776,7 +877,7 @@ internal void ConnectShellOrCommand( return; } - if (IntPtr.Zero == commandContext) + if (commandContext == IntPtr.Zero) { mgdShellSession.ExecuteConnect(requestDetails, flags, inboundConnectInformation); return; @@ -784,7 +885,7 @@ internal void ConnectShellOrCommand( // this connect is on a command WSManPluginCommandSession mgdCmdSession = mgdShellSession.GetCommandSession(commandContext); - if (null == mgdCmdSession) + if (mgdCmdSession == null) { ReportOperationComplete( requestDetails, @@ -794,7 +895,25 @@ internal void ConnectShellOrCommand( return; } + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); + mgdCmdSession.ExecuteConnect(requestDetails, flags, inboundConnectInformation); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Connect, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "ConnectShellOrCommand: ExecuteConnect invoked", + string.Empty); } /// @@ -814,6 +933,15 @@ internal void SendOneItemToShellOrCommand( string stream, WSManNativeApi.WSManData_UnToMan inboundData) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "SendOneItemToShellOrCommand: Send data to the shell / command specified", + string.Empty); + if (!validateIncomingContexts(requestDetails, shellContext, "SendOneItemToShellOrCommand")) { return; @@ -821,13 +949,17 @@ internal void SendOneItemToShellOrCommand( SetThreadProperties(requestDetails); - PSEtwLog.LogAnalyticInformational(PSEventId.ServerReceivedData, - PSOpcode.Open, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, - ((IntPtr)shellContext).ToString(), ((IntPtr)commandContext).ToString(), requestDetails.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { ReportOperationComplete( requestDetails, @@ -838,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); @@ -847,7 +979,7 @@ internal void SendOneItemToShellOrCommand( // the data is destined for command. WSManPluginCommandSession mgdCmdSession = mgdShellSession.GetCommandSession(commandContext); - if (null == mgdCmdSession) + if (mgdCmdSession == null) { ReportOperationComplete( requestDetails, @@ -857,11 +989,29 @@ internal void SendOneItemToShellOrCommand( return; } + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); + mgdCmdSession.SendOneItemToSession(requestDetails, flags, stream, inboundData); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "SendOneItemToShellOrCommand: SendOneItemToSession invoked", + string.Empty); } /// - /// unlock the shell / command specified so that the shell / command + /// Unlock the shell / command specified so that the shell / command /// starts sending data to the client. /// /// @@ -878,6 +1028,15 @@ internal void EnableShellOrCommandToSendDataToClient( IntPtr commandContext, WSManNativeApi.WSManStreamIDSet_UnToMan streamSet) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerClientReceiveRequest, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "EnableShellOrCommandToSendDataToClient: unlock the shell / command specified so that the shell / command starts sending data to the client.", + string.Empty); + if (!validateIncomingContexts(requestDetails, shellContext, "EnableShellOrCommandToSendDataToClient")) { return; @@ -885,15 +1044,17 @@ internal void EnableShellOrCommandToSendDataToClient( SetThreadProperties(requestDetails); - PSEtwLog.LogAnalyticInformational(PSEventId.ServerClientReceiveRequest, - PSOpcode.Open, PSTask.None, - PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, - ((IntPtr)shellContext).ToString(), - ((IntPtr)commandContext).ToString(), - requestDetails.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerClientReceiveRequest, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + ((IntPtr)shellContext).ToString(), + ((IntPtr)commandContext).ToString(), + requestDetails.ToString()); WSManPluginShellSession mgdShellSession = GetFromActiveShellSessions(shellContext); - if (null == mgdShellSession) + if (mgdShellSession == null) { ReportOperationComplete( requestDetails, @@ -905,13 +1066,22 @@ internal void EnableShellOrCommandToSendDataToClient( } WSManPluginOperationShutdownContext ctxtToReport = new WSManPluginOperationShutdownContext(pluginContext, shellContext, IntPtr.Zero, true); - if (null == ctxtToReport) + if (ctxtToReport == null) { ReportOperationComplete(requestDetails, WSManPluginErrorCodes.OutOfMemory); return; } - if (IntPtr.Zero == commandContext) + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerClientReceiveRequest, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "EnableShellOrCommandToSendDataToClient: Instruction destined to shell or for command", + string.Empty); + + if (commandContext == IntPtr.Zero) { // the instruction is destined for shell (runspace) session. so let shell handle it if (mgdShellSession.EnableSessionToSendDataToClient(requestDetails, flags, streamSet, ctxtToReport)) @@ -925,7 +1095,7 @@ internal void EnableShellOrCommandToSendDataToClient( ctxtToReport.commandContext = commandContext; WSManPluginCommandSession mgdCmdSession = mgdShellSession.GetCommandSession(commandContext); - if (null == mgdCmdSession) + if (mgdCmdSession == null) { ReportOperationComplete( requestDetails, @@ -943,20 +1113,20 @@ internal void EnableShellOrCommandToSendDataToClient( } /// - /// used to create PSPrincipal object from senderDetails struct. + /// Used to create PSPrincipal object from senderDetails struct. /// /// /// - private PSSenderInfo GetPSSenderInfo( + private static PSSenderInfo GetPSSenderInfo( WSManNativeApi.WSManSenderDetails senderDetails) { // senderDetails will not be null. - Dbg.Assert(null != senderDetails, "senderDetails cannot be null"); + Dbg.Assert(senderDetails != null, "senderDetails cannot be null"); // Construct PSIdentity PSCertificateDetails psCertDetails = null; // Construct Certificate Details - if (null != senderDetails.certificateDetails) + if (senderDetails.certificateDetails != null) { psCertDetails = new PSCertificateDetails( senderDetails.certificateDetails.subject, @@ -1003,8 +1173,8 @@ private PSSenderInfo GetPSSenderInfo( /// Helper method to retrieve the WSMan client token from the __WINRM_RUNAS_CLIENT_TOKEN__ /// environment variable, which is set in the WSMan layer for Virtual or RunAs accounts. /// - /// ClientToken IntPtr - private IntPtr GetRunAsClientToken() + /// ClientToken IntPtr. + private static IntPtr GetRunAsClientToken() { string clientTokenStr = System.Environment.GetEnvironmentVariable(WSManRunAsClientTokenName); if (clientTokenStr != null) @@ -1023,7 +1193,7 @@ private IntPtr GetRunAsClientToken() } /// - /// Was private. Made protected internal for easier testing + /// Was private. Made protected internal for easier testing. /// /// /// @@ -1037,16 +1207,17 @@ protected internal bool EnsureOptionsComply( { WSManNativeApi.WSManOption option = options[i]; - if (String.Equals(option.name, WSManPluginConstants.PowerShellStartupProtocolVersionName, StringComparison.Ordinal)) + if (string.Equals(option.name, WSManPluginConstants.PowerShellStartupProtocolVersionName, StringComparison.Ordinal)) { if (!EnsureProtocolVersionComplies(requestDetails, option.value)) { return false; } + 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) { @@ -1089,7 +1260,7 @@ protected internal bool EnsureProtocolVersionComplies( WSManNativeApi.WSManPluginRequest requestDetails, string clientVersionString) { - if (String.Equals(clientVersionString, WSManPluginConstants.PowerShellStartupProtocolVersionValue, StringComparison.Ordinal)) + if (string.Equals(clientVersionString, WSManPluginConstants.PowerShellStartupProtocolVersionValue, StringComparison.Ordinal)) { return true; } @@ -1100,7 +1271,7 @@ protected internal bool EnsureProtocolVersionComplies( System.Version clientVersion = Utils.StringToVersion(clientVersionString); System.Version serverVersion = Utils.StringToVersion(WSManPluginConstants.PowerShellStartupProtocolVersionValue); - if ((null != clientVersion) && (null != serverVersion) && + if ((clientVersion != null) && (serverVersion != null) && (clientVersion.Major == serverVersion.Major) && (clientVersion.Minor >= serverVersion.Minor)) { @@ -1119,7 +1290,7 @@ protected internal bool EnsureProtocolVersionComplies( } /// - /// static func to take care of unmanaged to managed transitions. + /// Static func to take care of unmanaged to managed transitions. /// /// /// @@ -1135,14 +1306,23 @@ internal static void PerformWSManPluginShell( IntPtr startupInfo, // WSMAN_SHELL_STARTUP_INFO* IntPtr inboundShellInformation) // WSMAN_DATA* { + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginShell: static func to take care of unmanaged to managed transitions.", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { lock (s_activePlugins) { pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { // create a new plugin WSManPluginInstance mgdPlugin = new WSManPluginInstance(); @@ -1157,7 +1337,24 @@ internal static void PerformWSManPluginShell( WSManNativeApi.WSManShellStartupInfo_UnToMan startupInfoInstance = WSManNativeApi.WSManShellStartupInfo_UnToMan.UnMarshal(startupInfo); WSManNativeApi.WSManData_UnToMan inboundShellInfo = WSManNativeApi.WSManData_UnToMan.UnMarshal(inboundShellInformation); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + requestDetailsInstance.ToString(), + requestDetailsInstance.resourceUri); + pluginToUse.CreateShell(pluginContext, requestDetailsInstance, flags, extraInfo, startupInfoInstance, inboundShellInfo); + + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginShell: Completed", + string.Empty); } internal static void PerformWSManPluginCommand( @@ -1168,9 +1365,18 @@ internal static void PerformWSManPluginCommand( [MarshalAs(UnmanagedType.LPWStr)] string commandLine, IntPtr arguments) // WSMAN_COMMAND_ARG_SET* { + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginCommand: static func to take care of unmanaged to managed transitions.", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { ReportOperationComplete( requestDetails, @@ -1184,7 +1390,24 @@ internal static void PerformWSManPluginCommand( WSManNativeApi.WSManPluginRequest request = WSManNativeApi.WSManPluginRequest.UnMarshal(requestDetails); WSManNativeApi.WSManCommandArgSet argSet = WSManNativeApi.WSManCommandArgSet.UnMarshal(arguments); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + request.ToString(), + request.resourceUri); + pluginToUse.CreateCommand(pluginContext, request, flags, shellContext, commandLine, argSet); + + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginCommand: Completed", + string.Empty); } internal static void PerformWSManPluginConnect( @@ -1195,9 +1418,18 @@ internal static void PerformWSManPluginConnect( IntPtr commandContext, IntPtr inboundConnectInformation) { + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginConnect: static func to take care of unmanaged to managed transitions.", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { ReportOperationComplete( requestDetails, @@ -1211,7 +1443,24 @@ internal static void PerformWSManPluginConnect( WSManNativeApi.WSManPluginRequest request = WSManNativeApi.WSManPluginRequest.UnMarshal(requestDetails); WSManNativeApi.WSManData_UnToMan connectInformation = WSManNativeApi.WSManData_UnToMan.UnMarshal(inboundConnectInformation); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + request.ToString(), + request.resourceUri); + pluginToUse.ConnectShellOrCommand(request, flags, shellContext, commandContext, connectInformation); + + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginConnect: Completed", + string.Empty); } internal static void PerformWSManPluginSend( @@ -1223,9 +1472,18 @@ internal static void PerformWSManPluginSend( string stream, IntPtr inboundData) // WSMAN_DATA* { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginSend: Invoked", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { ReportOperationComplete( requestDetails, @@ -1240,6 +1498,15 @@ internal static void PerformWSManPluginSend( WSManNativeApi.WSManData_UnToMan data = WSManNativeApi.WSManData_UnToMan.UnMarshal(inboundData); pluginToUse.SendOneItemToShellOrCommand(request, flags, shellContext, commandContext, stream, data); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginSend: Completed", + string.Empty); } internal static void PerformWSManPluginReceive( @@ -1250,9 +1517,18 @@ internal static void PerformWSManPluginReceive( IntPtr commandContext, IntPtr streamSet) // WSMAN_STREAM_ID_SET* { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginReceive: Invoked", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { ReportOperationComplete( requestDetails, @@ -1266,7 +1542,24 @@ internal static void PerformWSManPluginReceive( WSManNativeApi.WSManPluginRequest request = WSManNativeApi.WSManPluginRequest.UnMarshal(requestDetails); WSManNativeApi.WSManStreamIDSet_UnToMan streamIdSet = WSManNativeApi.WSManStreamIDSet_UnToMan.UnMarshal(streamSet); + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + request.ToString(), + request.resourceUri); + pluginToUse.EnableShellOrCommandToSendDataToClient(pluginContext, request, flags, shellContext, commandContext, streamIdSet); + + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginReceive: Completed", + string.Empty); } internal static void PerformWSManPluginSignal( @@ -1277,16 +1570,25 @@ internal static void PerformWSManPluginSignal( IntPtr commandContext, // PVOID string code) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformWSManPluginSignal: Invoked", + string.Empty); + 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)) + if (!string.Equals(code, WSManPluginConstants.CtrlCSignal, StringComparison.Ordinal)) { // Close operations associated with this command.. WSManPluginOperationShutdownContext cmdCtxt = new WSManPluginOperationShutdownContext(pluginContext, shellContext, commandContext, false); - if (null != cmdCtxt) + if (cmdCtxt != null) { PerformCloseOperation(cmdCtxt); } @@ -1301,7 +1603,7 @@ internal static void PerformWSManPluginSignal( // we got crtl_c (stop) message from client. so stop powershell WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { ReportOperationComplete( request, @@ -1328,14 +1630,23 @@ internal static void PerformWSManPluginSignal( internal static void PerformCloseOperation( WSManPluginOperationShutdownContext context) { + PSEtwLog.LogAnalyticInformational( + PSEventId.ServerReceivedData, + PSOpcode.Open, + PSTask.None, + PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, + string.Empty, + "PerformCloseOperation: Invoked", + string.Empty); + WSManPluginInstance pluginToUse = GetFromActivePlugins(context.pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { return; } - if (IntPtr.Zero == context.commandContext) + if (context.commandContext == IntPtr.Zero) { // this is targeted at shell pluginToUse.CloseShellOperation(context); @@ -1348,7 +1659,7 @@ internal static void PerformCloseOperation( } /// - /// performs deinitialization during shutdown. + /// Performs deinitialization during shutdown. /// /// internal static void PerformShutdown( @@ -1356,7 +1667,7 @@ internal static void PerformShutdown( { WSManPluginInstance pluginToUse = GetFromActivePlugins(pluginContext); - if (null == pluginToUse) + if (pluginToUse == null) { return; } @@ -1389,7 +1700,7 @@ private static void AddToActivePlugins(IntPtr pluginContext, WSManPluginInstance #region Utilities /// - /// report operation complete to WSMan and supply a reason (if any) + /// Report operation complete to WSMan and supply a reason (if any) /// /// /// @@ -1397,21 +1708,22 @@ internal static void ReportWSManOperationComplete( WSManNativeApi.WSManPluginRequest requestDetails, WSManPluginErrorCodes errorCode) { - Dbg.Assert(null != requestDetails, "requestDetails cannot be null in operation complete."); + Dbg.Assert(requestDetails != null, "requestDetails cannot be null in operation complete."); - PSEtwLog.LogAnalyticInformational(PSEventId.ReportOperationComplete, + PSEtwLog.LogAnalyticInformational( + PSEventId.ReportOperationComplete, PSOpcode.Close, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, (requestDetails.unmanagedHandle).ToString(), Convert.ToString(errorCode, CultureInfo.InvariantCulture), - String.Empty, - String.Empty); + string.Empty, + string.Empty); ReportOperationComplete(requestDetails.unmanagedHandle, errorCode); } /// - /// extract message from exception (if any) and report operation complete with it to WSMan + /// Extract message from exception (if any) and report operation complete with it to WSMan. /// /// /// @@ -1419,20 +1731,21 @@ internal static void ReportWSManOperationComplete( WSManNativeApi.WSManPluginRequest requestDetails, Exception reasonForClose) { - Dbg.Assert(null != requestDetails, "requestDetails cannot be null in operation complete."); + Dbg.Assert(requestDetails != null, "requestDetails cannot be null in operation complete."); WSManPluginErrorCodes error = WSManPluginErrorCodes.NoError; - String errorMessage = String.Empty; - String stackTrace = String.Empty; + string errorMessage = string.Empty; + string stackTrace = string.Empty; - if (null != reasonForClose) + if (reasonForClose != null) { error = WSManPluginErrorCodes.ManagedException; errorMessage = reasonForClose.Message; stackTrace = reasonForClose.StackTrace; } - PSEtwLog.LogAnalyticInformational(PSEventId.ReportOperationComplete, + PSEtwLog.LogAnalyticInformational( + PSEventId.ReportOperationComplete, PSOpcode.Close, PSTask.None, PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, requestDetails.ToString(), @@ -1440,7 +1753,7 @@ internal static void ReportWSManOperationComplete( errorMessage, stackTrace); - if (null != reasonForClose) + if (reasonForClose != null) { // report operation complete to wsman with the error message (if any). ReportOperationComplete( @@ -1469,31 +1782,31 @@ internal static void SetThreadProperties( WSManNativeApi.WSManPluginRequest requestDetails) { // requestDetails cannot not be null. - Dbg.Assert(null != requestDetails, "requestDetails cannot be null"); + Dbg.Assert(requestDetails != null, "requestDetails cannot be null"); - //IntPtr nativeLocaleData = IntPtr.Zero; + // IntPtr nativeLocaleData = IntPtr.Zero; WSManNativeApi.WSManDataStruct outputStruct = new WSManNativeApi.WSManDataStruct(); int hResult = wsmanPinvokeStatic.WSManPluginGetOperationParameters( requestDetails.unmanagedHandle, WSManPluginConstants.WSManPluginParamsGetRequestedLocale, outputStruct); - //ref nativeLocaleData); - bool retrievingLocaleSucceeded = (0 == hResult); + // ref nativeLocaleData); + bool retrievingLocaleSucceeded = (hResult == 0); WSManNativeApi.WSManData_UnToMan localeData = WSManNativeApi.WSManData_UnToMan.UnMarshal(outputStruct); // nativeLocaleData - //IntPtr nativeDataLocaleData = IntPtr.Zero; + // IntPtr nativeDataLocaleData = IntPtr.Zero; hResult = wsmanPinvokeStatic.WSManPluginGetOperationParameters( requestDetails.unmanagedHandle, WSManPluginConstants.WSManPluginParamsGetRequestedDataLocale, outputStruct); - //ref nativeDataLocaleData); - bool retrievingDataLocaleSucceeded = ((int)WSManPluginErrorCodes.NoError == hResult); + // ref nativeDataLocaleData); + 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; @@ -1507,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; @@ -1552,14 +1865,14 @@ internal static void UnhandledExceptionHandler( /// /// /// - /// Pre-formatted localized string + /// Pre-formatted localized string. /// internal static void ReportOperationComplete( WSManNativeApi.WSManPluginRequest requestDetails, WSManPluginErrorCodes errorCode, string errorMessage) { - if (null != requestDetails) + if (requestDetails != null) { ReportOperationComplete(requestDetails.unmanagedHandle, errorCode, errorMessage); } @@ -1575,8 +1888,8 @@ internal static void ReportOperationComplete( WSManNativeApi.WSManPluginRequest requestDetails, WSManPluginErrorCodes errorCode) { - if (null != requestDetails && - IntPtr.Zero != requestDetails.unmanagedHandle) + if (requestDetails != null && + requestDetails.unmanagedHandle != IntPtr.Zero) { wsmanPinvokeStatic.WSManPluginOperationComplete( requestDetails.unmanagedHandle, @@ -1599,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; @@ -1616,4 +1929,4 @@ internal static void ReportOperationComplete( #endregion } -} // namespace System.Management.Automation.Remoting +} diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs index 226438e2172..2d99a42cfb9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs @@ -1,8 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. // ---------------------------------------------------------------------- @@ -27,15 +26,14 @@ namespace System.Management.Automation.Remoting // pointers (otherwise we end up storing the delegate into a GCRoot). /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PCWSTR - /// WSMAN_SHELL_STARTUP_INFO* - /// WSMAN_DATA* - internal delegate void WSMPluginShellDelegate( // TODO: Rename to WSManPluginShellDelegate once I remove the MC++ module. + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PCWSTR. + /// WSMAN_SHELL_STARTUP_INFO*. + /// WSMAN_DATA*. + internal delegate void WSManPluginShellDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -44,24 +42,22 @@ internal delegate void WSMPluginShellDelegate( // TODO: Rename to WSManPluginShe IntPtr inboundShellInformation); /// - /// /// - /// PVOID - /// PVOID - internal delegate void WSMPluginReleaseShellContextDelegate( + /// PVOID. + /// PVOID. + internal delegate void WSManPluginReleaseShellContextDelegate( IntPtr pluginContext, IntPtr shellContext); /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// WSMAN_DATA* optional - internal delegate void WSMPluginConnectDelegate( + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// WSMAN_DATA* optional. + internal delegate void WSManPluginConnectDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -70,15 +66,14 @@ internal delegate void WSMPluginConnectDelegate( IntPtr inboundConnectInformation); /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PCWSTR - /// WSMAN_COMMAND_ARG_SET* - internal delegate void WSMPluginCommandDelegate( + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PCWSTR. + /// WSMAN_COMMAND_ARG_SET*. + internal delegate void WSManPluginCommandDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -87,34 +82,32 @@ internal delegate void WSMPluginCommandDelegate( IntPtr arguments); /// - /// Delegate that is passed to native layer for callback on operation shutdown notifications + /// Delegate that is passed to native layer for callback on operation shutdown notifications. /// - /// IntPtr - internal delegate void WSMPluginOperationShutdownDelegate( + /// IntPtr. + internal delegate void WSManPluginOperationShutdownDelegate( IntPtr shutdownContext); /// - /// /// - /// PVOID - /// PVOID - /// PVOID - internal delegate void WSMPluginReleaseCommandContextDelegate( + /// PVOID. + /// PVOID. + /// PVOID. + internal delegate void WSManPluginReleaseCommandContextDelegate( IntPtr pluginContext, IntPtr shellContext, IntPtr commandContext); /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID - /// PCWSTR - /// WSMAN_DATA* - internal delegate void WSMPluginSendDelegate( + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID. + /// PCWSTR. + /// WSMAN_DATA*. + internal delegate void WSManPluginSendDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -124,15 +117,14 @@ internal delegate void WSMPluginSendDelegate( IntPtr inboundData); /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// WSMAN_STREAM_ID_SET* optional - internal delegate void WSMPluginReceiveDelegate( + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// WSMAN_STREAM_ID_SET* optional. + internal delegate void WSManPluginReceiveDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -141,15 +133,14 @@ internal delegate void WSMPluginReceiveDelegate( IntPtr streamSet); /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// PCWSTR - internal delegate void WSMPluginSignalDelegate( + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// PCWSTR. + internal delegate void WSManPluginSignalDelegate( IntPtr pluginContext, IntPtr requestDetails, int flags, @@ -158,7 +149,7 @@ internal delegate void WSMPluginSignalDelegate( [MarshalAs(UnmanagedType.LPWStr)] string code); /// - /// Callback that handles shell shutdown notification events. + /// Callback that handles shell shutdown notification events. /// /// /// @@ -167,21 +158,20 @@ internal delegate void WaitOrTimerCallbackDelegate( bool timedOut); /// - /// /// - /// PVOID - internal delegate void WSMShutdownPluginDelegate( + /// PVOID. + internal delegate void WSManShutdownPluginDelegate( IntPtr pluginContext); /// - /// /// 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 { get { return _unmanagedStruct; } @@ -202,13 +192,13 @@ internal WSManPluginEntryDelegatesInternal UnmanagedStruct private GCHandle _pluginSignalGCHandle; private GCHandle _pluginConnectGCHandle; private GCHandle _shutdownPluginGCHandle; - private GCHandle _WSMPluginOperationShutdownGCHandle; + private GCHandle _WSManPluginOperationShutdownGCHandle; #endregion #region Constructor /// - /// Initializes the delegate struct for later use + /// Initializes the delegate struct for later use. /// internal WSManPluginEntryDelegates() { @@ -244,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() { @@ -269,67 +255,67 @@ 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); } } /// - /// /// private void CleanUpDelegates() { // Free GCHandles so that the memory they point to may be unpinned (garbage collected) - if (null != _pluginShellGCHandle) + if (_pluginShellGCHandle.IsAllocated) { _pluginShellGCHandle.Free(); _pluginReleaseShellContextGCHandle.Free(); @@ -342,7 +328,7 @@ private void CleanUpDelegates() _shutdownPluginGCHandle.Free(); if (!Platform.IsWindows) { - _WSMPluginOperationShutdownGCHandle.Free(); + _WSManPluginOperationShutdownGCHandle.Free(); } } } @@ -355,63 +341,53 @@ private void CleanUpDelegates() internal class WSManPluginEntryDelegatesInternal { /// - /// wsManPluginShutdownPluginCallbackNative + /// WsManPluginShutdownPluginCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownPluginCallbackNative; /// - /// WSManPluginShellCallbackNative + /// WSManPluginShellCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShellCallbackNative; /// - /// WSManPluginReleaseShellContextCallbackNative + /// WSManPluginReleaseShellContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseShellContextCallbackNative; /// - /// WSManPluginCommandCallbackNative + /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginCommandCallbackNative; /// - /// WSManPluginReleaseCommandContextCallbackNative + /// WSManPluginReleaseCommandContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseCommandContextCallbackNative; /// - /// WSManPluginSendCallbackNative + /// WSManPluginSendCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSendCallbackNative; /// - /// WSManPluginReceiveCallbackNative + /// WSManPluginReceiveCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReceiveCallbackNative; /// - /// WSManPluginSignalCallbackNative + /// WSManPluginSignalCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSignalCallbackNative; /// - /// WSManPluginConnectCallbackNative + /// WSManPluginConnectCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginConnectCallbackNative; /// - /// WSManPluginCommandCallbackNative + /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownCallbackNative; } } @@ -430,7 +406,7 @@ private WSManPluginManagedEntryWrapper() { } /// /// Immutable container that holds the delegates and their unmanaged pointers. /// - internal static WSManPluginEntryDelegates workerPtrs = new WSManPluginEntryDelegates(); + internal static readonly WSManPluginEntryDelegates workerPtrs = new WSManPluginEntryDelegates(); #region Managed Entry Points @@ -438,36 +414,16 @@ private WSManPluginManagedEntryWrapper() { } /// Called only once after the assembly is loaded..This is used to perform /// various initializations. /// - /// IntPtr to WSManPluginEntryDelegates.WSManPluginEntryDelegatesInternal - /// 0 = Success, 1 = Failure + /// IntPtr to WSManPluginEntryDelegates.WSManPluginEntryDelegatesInternal. + /// 0 = Success, 1 = Failure. public static int InitPlugin( IntPtr wkrPtrs) { - if (IntPtr.Zero == wkrPtrs) + if (wkrPtrs == IntPtr.Zero) { return WSManPluginConstants.ExitCodeFailure; } -#if !CORECLR - // For long-path support, Full .NET requires some AppContext switches; - // (for CoreCLR this is Not needed, because CoreCLR supports long paths by default) - // internally in .NET they are cached once retrieved and are typically hit very early during an application run; - // so per .NET team's recommendation, we are setting them as soon as we enter managed code. - // We build against CLR4.5 so we can run on Win7/Win8, but we want to use apis added to CLR 4.6, so we use reflection - try - { - Type appContextType = Type.GetType("System.AppContext"); // type is in mscorlib, so it is sufficient to supply the type name qualified by its namespace - object[] blockLongPathsSwitch = new object[] { "Switch.System.IO.BlockLongPaths", false }; - object[] useLegacyPathHandlingSwitch = new object[] { "Switch.System.IO.UseLegacyPathHandling", false }; - - appContextType.InvokeMember("SetSwitch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.InvokeMethod, null, null, blockLongPathsSwitch, CultureInfo.InvariantCulture); - appContextType.InvokeMember("SetSwitch", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.InvokeMethod, null, null, useLegacyPathHandlingSwitch, CultureInfo.InvariantCulture); - } - catch (Exception) - { - // If there are any non-critical exceptions (e.g. we are running on CLR prior to 4.6.2), we won't be able to use long paths - } -#endif Marshal.StructureToPtr(workerPtrs.UnmanagedStruct, wkrPtrs, false); return WSManPluginConstants.ExitCodeSuccess; } @@ -475,27 +431,22 @@ public static int InitPlugin( /// /// Called only once during shutdown. This is used to perform various deinitializations. /// - /// PVOID + /// PVOID. public static void ShutdownPlugin( IntPtr pluginContext) { WSManPluginInstance.PerformShutdown(pluginContext); - - if (null != workerPtrs) - { - workerPtrs.Dispose(); - } + workerPtrs?.Dispose(); } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// WSMAN_DATA* optional + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// WSMAN_DATA* optional. public static void WSManPluginConnect( IntPtr pluginContext, IntPtr requestDetails, @@ -504,7 +455,7 @@ public static void WSManPluginConnect( IntPtr commandContext, IntPtr inboundConnectInformation) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -521,14 +472,13 @@ public static void WSManPluginConnect( } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PCWSTR - /// WSMAN_SHELL_STARTUP_INFO* - /// WSMAN_DATA* + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PCWSTR. + /// WSMAN_SHELL_STARTUP_INFO*. + /// WSMAN_DATA*. public static void WSManPluginShell( IntPtr pluginContext, IntPtr requestDetails, @@ -537,7 +487,7 @@ public static void WSManPluginShell( IntPtr startupInfo, IntPtr inboundShellInformation) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -566,10 +516,9 @@ public static void WSManPluginShell( } /// - /// /// - /// PVOID - /// PVOID + /// PVOID. + /// PVOID. public static void WSManPluginReleaseShellContext( IntPtr pluginContext, IntPtr shellContext) @@ -579,14 +528,13 @@ public static void WSManPluginReleaseShellContext( } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PCWSTR - /// WSMAN_COMMAND_ARG_SET* optional + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PCWSTR. + /// WSMAN_COMMAND_ARG_SET* optional. public static void WSManPluginCommand( IntPtr pluginContext, IntPtr requestDetails, @@ -595,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, @@ -614,7 +562,7 @@ public static void WSManPluginCommand( /// /// Operation shutdown notification that was registered with the native layer for each of the shellCreate operations. /// - /// IntPtr + /// IntPtr. public static void WSManPSShutdown( IntPtr shutdownContext) { @@ -625,11 +573,10 @@ public static void WSManPSShutdown( } /// - /// /// - /// PVOID - /// PVOID - /// PVOID + /// PVOID. + /// PVOID. + /// PVOID. public static void WSManPluginReleaseCommandContext( IntPtr pluginContext, IntPtr shellContext, @@ -640,15 +587,14 @@ public static void WSManPluginReleaseCommandContext( } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID - /// PCWSTR - /// WSMAN_DATA* + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID. + /// PCWSTR. + /// WSMAN_DATA*. public static void WSManPluginSend( IntPtr pluginContext, IntPtr requestDetails, @@ -658,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, @@ -675,14 +621,13 @@ public static void WSManPluginSend( } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// WSMAN_STREAM_ID_SET* optional + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// WSMAN_STREAM_ID_SET* optional. public static void WSManPluginReceive( IntPtr pluginContext, IntPtr requestDetails, @@ -691,7 +636,7 @@ public static void WSManPluginReceive( IntPtr commandContext, IntPtr streamSet) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -708,14 +653,13 @@ public static void WSManPluginReceive( } /// - /// /// - /// PVOID - /// WSMAN_PLUGIN_REQUEST* - /// DWORD - /// PVOID - /// PVOID optional - /// PCWSTR + /// PVOID. + /// WSMAN_PLUGIN_REQUEST*. + /// DWORD. + /// PVOID. + /// PVOID optional. + /// PCWSTR. public static void WSManPluginSignal( IntPtr pluginContext, IntPtr requestDetails, @@ -724,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, @@ -745,14 +689,14 @@ public static void WSManPluginSignal( /// Conforms to: /// public delegate void WaitOrTimerCallback( Object state, bool timedOut ) /// - /// PVOID - /// BOOLEAN + /// PVOID. + /// BOOLEAN. /// public static void PSPluginOperationShutdownCallback( object operationContext, bool timedOut) { - if (null == operationContext) + if (operationContext == null) { return; } @@ -804,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() { @@ -820,7 +760,7 @@ private void Dispose(bool disposing) #region Delegate Handling /// - /// Matches signature for WSManPluginManagedEntryWrapper.InitPlugin + /// Matches signature for WSManPluginManagedEntryWrapper.InitPlugin. /// /// /// @@ -845,4 +785,4 @@ public IntPtr GetEntryDelegate() #endregion } -} // namespace System.Management.Automation.Remoting +} diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs index d8d98c2d8e3..54a7b66d0c1 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs @@ -1,8 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. // ---------------------------------------------------------------------- @@ -22,11 +21,11 @@ namespace System.Management.Automation.Remoting { /// - /// Abstract class that defines common functionality for WinRM Plugin API Server Sessions + /// Abstract class that defines common functionality for WinRM Plugin API Server Sessions. /// internal abstract class WSManPluginServerSession : IDisposable { - private object _syncObject; + private readonly object _syncObject; protected bool isClosed; protected bool isContextReported; @@ -43,7 +42,7 @@ internal abstract class WSManPluginServerSession : IDisposable // shell context. internal RegisteredWaitHandle registeredShutDownWaitHandle; internal WSManPluginServerTransportManager transportMgr; - internal System.Int32 registeredShutdownNotification; + internal int registeredShutdownNotification; // event that gets raised when session is closed.."source" will provide // IntPtr for "creationRequestDetails" which can be used to free @@ -57,14 +56,12 @@ protected WSManPluginServerSession( WSManNativeApi.WSManPluginRequest creationRequestDetails, WSManPluginServerTransportManager transportMgr) { - _syncObject = new Object(); + _syncObject = new object(); this.creationRequestDetails = creationRequestDetails; this.transportMgr = transportMgr; - transportMgr.PrepareCalled += - new EventHandler(this.HandlePrepareFromTransportManager); - transportMgr.WSManTransportErrorOccured += - new EventHandler(this.HandleTransportError); + transportMgr.PrepareCalled += this.HandlePrepareFromTransportManager; + transportMgr.WSManTransportErrorOccured += this.HandleTransportError; } public void Dispose() @@ -99,7 +96,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { // Dispose managed resources. - //Close(false); + // Close(false); } // Call the appropriate methods to clean up @@ -114,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() { @@ -134,8 +127,8 @@ internal void SendOneItemToSession( string stream, WSManNativeApi.WSManData_UnToMan inboundData) { - if ((!String.Equals(stream, WSManPluginConstants.SupportedInputStream, StringComparison.Ordinal)) && - (!String.Equals(stream, WSManPluginConstants.SupportedPromptResponseStream, StringComparison.Ordinal))) + if ((!string.Equals(stream, WSManPluginConstants.SupportedInputStream, StringComparison.Ordinal)) && + (!string.Equals(stream, WSManPluginConstants.SupportedPromptResponseStream, StringComparison.Ordinal))) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -146,7 +139,7 @@ internal void SendOneItemToSession( return; } - if (null == inboundData) + if (inboundData == null) { // no data is supplied..just ignore. WSManPluginInstance.ReportOperationComplete( @@ -155,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( @@ -169,7 +162,7 @@ internal void SendOneItemToSession( lock (_syncObject) { - if (true == isClosed) + if (isClosed) { WSManPluginInstance.ReportWSManOperationComplete(requestDetails, lastErrorReported); return; @@ -186,7 +179,7 @@ internal void SendOneItemToSession( } internal void SendOneItemToSessionHelper( - System.Byte[] data, + byte[] data, string stream) { transportMgr.ProcessRawData(data, stream); @@ -198,14 +191,14 @@ internal bool EnableSessionToSendDataToClient( WSManNativeApi.WSManStreamIDSet_UnToMan streamSet, WSManPluginOperationShutdownContext ctxtToReport) { - if (true == isClosed) + if (isClosed) { WSManPluginInstance.ReportWSManOperationComplete(requestDetails, lastErrorReported); return false; } - if ((null == streamSet) || - (1 != streamSet.streamIDsCount)) + if ((streamSet == null) || + (streamSet.streamIDsCount != 1)) { // only "stdout" is the supported output stream. WSManPluginInstance.ReportOperationComplete( @@ -217,7 +210,7 @@ internal bool EnableSessionToSendDataToClient( return false; } - if (!String.Equals(streamSet.streamIDs[0], WSManPluginConstants.SupportedOutputStream, StringComparison.Ordinal)) + if (!string.Equals(streamSet.streamIDs[0], WSManPluginConstants.SupportedOutputStream, StringComparison.Ordinal)) { // only "stdout" is the supported output stream. WSManPluginInstance.ReportOperationComplete( @@ -243,7 +236,7 @@ internal void ReportContext() lock (_syncObject) { - if (true == isClosed) + if (isClosed) { return; } @@ -256,10 +249,10 @@ internal void ReportContext() PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic, creationRequestDetails.ToString(), creationRequestDetails.ToString()); - //RACE TO BE FIXED - As soon as this API is called, WinRM service will send CommandResponse back and Signal is expected anytime + // 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; @@ -275,7 +268,7 @@ internal void ReportContext() shutDownContext, -1, // INFINITE true); // TODO: Do I need to worry not being able to set missing WT_TRANSFER_IMPERSONATION? - if (null == this.registeredShutDownWaitHandle) + if (this.registeredShutDownWaitHandle == null) { isRegisterWaitForSingleObjectFailed = true; registeredShutdownNotification = 0; @@ -284,7 +277,7 @@ internal void ReportContext() } } - if ((WSManPluginConstants.ExitCodeSuccess != result) || (isRegisterWaitForSingleObjectFailed)) + if ((result != WSManPluginConstants.ExitCodeSuccess) || (isRegisterWaitForSingleObjectFailed)) { string errorMessage; if (isRegisterWaitForSingleObjectFailed) @@ -307,28 +300,29 @@ internal void ReportContext() /// /// /// - protected internal void SafeInvokeSessionClosed(Object sender, EventArgs eventArgs) + protected internal void SafeInvokeSessionClosed(object sender, EventArgs eventArgs) { SessionClosed.SafeInvoke(sender, eventArgs); } // handle transport manager related errors - internal void HandleTransportError(Object sender, TransportErrorOccuredEventArgs eventArgs) + internal void HandleTransportError(object sender, TransportErrorOccuredEventArgs eventArgs) { Exception reasonForClose = null; - if (null != eventArgs) + if (eventArgs != null) { reasonForClose = eventArgs.Exception; } + Close(reasonForClose); } // handle prepare from transport by reporting context to WSMan. - internal void HandlePrepareFromTransportManager(Object sender, EventArgs eventArgs) + internal void HandlePrepareFromTransportManager(object sender, EventArgs eventArgs) { ReportContext(); ReportSendOperationComplete(); - transportMgr.PrepareCalled -= new EventHandler(this.HandlePrepareFromTransportManager); + transportMgr.PrepareCalled -= this.HandlePrepareFromTransportManager; } internal void Close(bool isShuttingDown) @@ -336,7 +330,7 @@ internal void Close(bool isShuttingDown) if (Interlocked.Exchange(ref registeredShutdownNotification, 0) == 1) { // release the shutdown notification handle. - if (null != registeredShutDownWaitHandle) + if (registeredShutDownWaitHandle != null) { registeredShutDownWaitHandle.Unregister(null); registeredShutDownWaitHandle = null; @@ -346,20 +340,19 @@ internal void Close(bool isShuttingDown) // Delete the context only if isShuttingDown != true. isShuttingDown will // be true only when the method is called from RegisterWaitForSingleObject // handler..in which case the context will be freed from the callback. - if (null != shutDownContext) + if (shutDownContext != null) { shutDownContext = null; } - transportMgr.WSManTransportErrorOccured -= - new EventHandler(this.HandleTransportError); + transportMgr.WSManTransportErrorOccured -= this.HandleTransportError; // We should not use request details again after so releasing the resource. // Remember not to free this memory as this memory is allocated and owned by WSMan. creationRequestDetails = null; // if already disposing..no need to let finalizer thread // put resources to clean this object. - //System.GC.SuppressFinalize(this); // TODO: This is already called in Dispose(). + // System.GC.SuppressFinalize(this); // TODO: This is already called in Dispose(). } // close current session and transport manager because of an exception @@ -375,7 +368,7 @@ internal void ReportSendOperationComplete() { lock (_syncObject) { - if (null != sendRequestDetails) + if (sendRequestDetails != null) { // report and clear the send request details WSManPluginInstance.ReportWSManOperationComplete(sendRequestDetails, lastErrorReported); @@ -387,6 +380,7 @@ internal void ReportSendOperationComplete() #region Pure virtual methods internal abstract void CloseOperation(WSManPluginOperationShutdownContext context, Exception reasonForClose); + internal abstract void ExecuteConnect( WSManNativeApi.WSManPluginRequest requestDetails, // in int flags, // in @@ -396,14 +390,13 @@ internal abstract void ExecuteConnect( } /// - /// /// internal class WSManPluginShellSession : WSManPluginServerSession { #region Private Members - private Dictionary _activeCommandSessions; - private ServerRemoteSession _remoteSession; + private readonly Dictionary _activeCommandSessions; + private readonly ServerRemoteSession _remoteSession; #endregion @@ -426,7 +419,7 @@ internal WSManPluginShellSession( new EventHandler(this.HandleServerRemoteSessionClosed); _activeCommandSessions = new Dictionary(); - this.shellSyncObject = new System.Object(); + this.shellSyncObject = new object(); this.shutDownContext = shutDownContext; } #endregion @@ -434,7 +427,7 @@ internal WSManPluginShellSession( /// /// Main Routine for Connect on a Shell. /// Calls in server remotesessions ExecuteConnect to run the Connect algorithm - /// This call is synchronous. i.e WSManOperationComplete will be called before the routine completes + /// This call is synchronous. i.e WSManOperationComplete will be called before the routine completes. /// /// /// @@ -444,7 +437,7 @@ internal override void ExecuteConnect( int flags, // in WSManNativeApi.WSManData_UnToMan inboundConnectInformation) // in optional { - if (null == inboundConnectInformation) + if (inboundConnectInformation == null) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -456,33 +449,34 @@ internal override void ExecuteConnect( return; } - //not registering shutdown event as this is a synchronous operation. + // not registering shutdown event as this is a synchronous operation. IntPtr responseXml = IntPtr.Zero; try { - System.Byte[] inputData; - System.Byte[] outputData; + byte[] inputData; + byte[] outputData; // Retrieve the string (Base64 encoded) inputData = ServerOperationHelpers.ExtractEncodedXmlElement( inboundConnectInformation.Text, WSManNativeApi.PS_CONNECT_XML_TAG); - //this will raise exceptions on failure + // this will raise exceptions on failure try { _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)); + // 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)); - //TODO: currently using OperationComplete to report back the responseXml. This will need to change to use WSManReportObject - //that is currently internal. + // TODO: currently using OperationComplete to report back the responseXml. This will need to change to use WSManReportObject + // that is currently internal. WSManPluginInstance.ReportOperationComplete(requestDetails, WSManPluginErrorCodes.NoError, responseData); } catch (PSRemotingDataStructureException ex) @@ -524,7 +518,7 @@ internal void CreateCommand( WSManPluginCommandSession mgdCmdSession = new WSManPluginCommandSession(requestDetails, serverCmdTransportMgr, _remoteSession); AddToActiveCmdSessions(mgdCmdSession); - mgdCmdSession.SessionClosed += new EventHandler(this.HandleCommandSessionClosed); + mgdCmdSession.SessionClosed += this.HandleCommandSessionClosed; mgdCmdSession.shutDownContext = new WSManPluginOperationShutdownContext( pluginContext, @@ -566,7 +560,7 @@ internal void CloseCommandOperation( WSManPluginOperationShutdownContext context) { WSManPluginCommandSession mgdCmdSession = GetCommandSession(context.commandContext); - if (null == mgdCmdSession) + if (mgdCmdSession == null) { // this should never be the case. this will protect the service. return; @@ -594,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)) { @@ -641,9 +635,10 @@ private void CloseAndClearCommandSessions( WSManPluginCommandSession cmdSession = cmdSessionEnumerator.Current; // we are not interested in session closed events anymore as we are initiating the close // anyway/ - cmdSession.SessionClosed -= new EventHandler(this.HandleCommandSessionClosed); + cmdSession.SessionClosed -= this.HandleCommandSessionClosed; cmdSession.Close(reasonForClose); } + copyCmdSessions.Clear(); } @@ -661,19 +656,20 @@ internal WSManPluginCommandSession GetCommandSession( } private void HandleServerRemoteSessionClosed( - Object sender, + object sender, RemoteSessionStateMachineEventArgs eventArgs) { Exception reasonForClose = null; - if (null != eventArgs) + if (eventArgs != null) { reasonForClose = eventArgs.Reason; } + Close(reasonForClose); } private void HandleCommandSessionClosed( - Object source, + object source, EventArgs e) { // command context is passed as "source" parameter @@ -687,7 +683,7 @@ internal override void CloseOperation( // let command sessions to close. lock (shellSyncObject) { - if (true == isClosed) + if (isClosed) { return; } @@ -730,7 +726,7 @@ internal class WSManPluginCommandSession : WSManPluginServerSession { #region Private Members - private ServerRemoteSession _remoteSession; + private readonly ServerRemoteSession _remoteSession; #endregion @@ -749,7 +745,7 @@ internal WSManPluginCommandSession( : base(creationRequestDetails, transportMgr) { _remoteSession = remoteSession; - cmdSyncObject = new System.Object(); + cmdSyncObject = new object(); } #endregion @@ -757,25 +753,24 @@ internal WSManPluginCommandSession( internal bool ProcessArguments( WSManNativeApi.WSManCommandArgSet arguments) { - if (1 != arguments.argsCount) + if (arguments.argsCount != 1) { return false; } - System.Byte[] convertedBase64 = Convert.FromBase64String(arguments.args[0]); + byte[] convertedBase64 = Convert.FromBase64String(arguments.args[0]); transportMgr.ProcessRawData(convertedBase64, WSManPluginConstants.SupportedInputStream); return true; } /// - /// /// /// 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); @@ -788,7 +783,7 @@ internal override void CloseOperation( // let command sessions to close. lock (cmdSyncObject) { - if (true == isClosed) + if (isClosed) { return; } @@ -831,7 +826,7 @@ internal override void CloseOperation( /// /// Main routine for connect on a command/pipeline.. Currently NO-OP /// will be enhanced later to support intelligent connect... like ending input streams on pipelines - /// that are still waiting for input data + /// that are still waiting for input data. /// /// /// diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs index 923c65894fa..9cc10ce435e 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs @@ -1,8 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + // ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. // ---------------------------------------------------------------------- @@ -24,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. @@ -50,7 +49,7 @@ internal WSManPluginServerTransportManager( PSRemotingCryptoHelper cryptoHelper) : base(fragmentSize, cryptoHelper) { - _syncObject = new Object(); + _syncObject = new object(); _activeCmdTransportManagers = new Dictionary(); _waitHandle = new ManualResetEvent(false); } @@ -65,7 +64,6 @@ internal override void Close( } /// - /// /// /// true if the method is called from RegisterWaitForSingleObject /// callback. This boolean is used to decide whether to UnregisterWait or @@ -109,9 +107,10 @@ internal void DoClose( { cmdTransportKvp.Value.Close(reasonForClose); } + _activeCmdTransportManagers.Clear(); - if (null != _registeredShutDownWaitHandle) + if (_registeredShutDownWaitHandle != null) { // This will not wait for the callback to complete. _registeredShutDownWaitHandle.Unregister(null); @@ -121,14 +120,14 @@ internal void DoClose( // Delete the context only if isShuttingDown != true. isShuttingDown will // be true only when the method is called from RegisterWaitForSingleObject // handler..in which case the context will be freed from the callback. - if (null != _shutDownContext) + if (_shutDownContext != null) { _shutDownContext = null; } // This might happen when client did not send a receive request // but the server is closing - if (null != _requestDetails) + if (_requestDetails != null) { // Notify that no more data is being sent on this transport. WSManNativeApi.WSManPluginReceiveResult( @@ -154,19 +153,19 @@ internal void DoClose( } /// - /// used by powershell DS handler. notifies transport that powershell is back to running state - /// no payload + /// Used by powershell DS handler. notifies transport that powershell is back to running state + /// no payload. /// internal override void ReportExecutionStatusAsRunning() { - if (true == _isClosed) + if (_isClosed) { return; } int result = (int)WSManPluginErrorCodes.NoError; - //there should have been a receive request in place already + // there should have been a receive request in place already lock (_syncObject) { @@ -182,14 +181,14 @@ internal override void ReportExecutionStatusAsRunning() } } - if ((int)WSManPluginErrorCodes.NoError != result) + if (result != (int)WSManPluginErrorCodes.NoError) { ReportError(result, "WSManPluginReceiveResult"); } } /// - /// if flush is true, data will be sent immediately to the client. This is accomplished + /// If flush is true, data will be sent immediately to the client. This is accomplished /// by using WSMAN_FLAG_RECEIVE_FLUSH flag provided by WSMan API. /// /// @@ -202,7 +201,7 @@ protected override void SendDataToClient( bool reportAsPending, bool reportAsDataBoundary) { - if (true == _isClosed) + if (_isClosed) { return; } @@ -233,7 +232,7 @@ protected override void SendDataToClient( if (flush) flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_FLUSH; if (reportAsDataBoundary) - //currently assigning hardcoded value for this flag, this is a new change in wsman.h and needs to be replaced with the actual definition once + // currently assigning hardcoded value for this flag, this is a new change in wsman.h and needs to be replaced with the actual definition once // modified wsman.h is in public headers flags |= (int)WSManNativeApi.WSManFlagReceive.WSMAN_FLAG_RECEIVE_RESULT_DATA_BOUNDARY; @@ -247,7 +246,8 @@ protected override void SendDataToClient( } } } - if ((int)WSManPluginErrorCodes.NoError != result) + + if (result != (int)WSManPluginErrorCodes.NoError) { ReportError(result, "WSManPluginReceiveResult"); } @@ -263,7 +263,6 @@ internal override void Prepare() } /// - /// /// /// /// @@ -305,6 +304,7 @@ internal override void RemoveCommandTransportManager( _activeCmdTransportManagers.Remove(cmdId); } } + #endregion internal bool EnableTransportManagerSendDataToClient( WSManNativeApi.WSManPluginRequest requestDetails, @@ -345,7 +345,7 @@ internal bool EnableTransportManagerSendDataToClient( _shutDownContext, -1, // INFINITE true); // TODO: Do I need to worry not being able to set missing WT_TRANSFER_IMPERSONATION? - if (null == _registeredShutDownWaitHandle) + if (_registeredShutDownWaitHandle == null) { isRegisterWaitForSingleObjectSucceeded = false; } @@ -388,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 @@ -401,7 +401,7 @@ internal WSManPluginCommandTransportManager(WSManPluginServerTransportManager sr internal void Initialize() { - this.PowerShellGuidObserver += new System.EventHandler(OnPowershellGuidReported); + this.PowerShellGuidObserver += OnPowershellGuidReported; this.MigrateDataReadyEventHandlers(_serverTransportMgr); } @@ -409,7 +409,7 @@ private void OnPowershellGuidReported(object src, System.EventArgs args) { _cmdId = (System.Guid)src; _serverTransportMgr.ReportTransportMgrForCmd(_cmdId, this); - this.PowerShellGuidObserver -= new System.EventHandler(this.OnPowershellGuidReported); + this.PowerShellGuidObserver -= this.OnPowershellGuidReported; } } -} // namespace System.Management.Automation.Remoting \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs index bc9c88859f8..d4ee779b5a2 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. /* * Common file that contains implementation for both server and client transport @@ -8,24 +7,20 @@ * */ -using System.Management.Automation.Tracing; -using System.IO; -using System.Xml; using System.Collections.Generic; -using System.Globalization; -using System.Threading; -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; using System.Management.Automation.Internal; -#if !CORECLR -using System.Security.Principal; -#endif - -// Don't expose the System.Management.Automation namespace here. This is transport layer -// and it shouldn't know anything about the engine. using System.Management.Automation.Remoting.Server; -// TODO: this seems ugly...Remoting datatypes should be in remoting namespace using System.Management.Automation.Runspaces.Internal; +using System.Management.Automation.Tracing; +using System.Runtime.InteropServices; +#if !UNIX +using System.Security.Principal; +#endif +using System.Xml; +using System.Threading; using PSRemotingCryptoHelper = System.Management.Automation.Internal.PSRemotingCryptoHelper; using WSManConnectionInfo = System.Management.Automation.Runspaces.WSManConnectionInfo; @@ -36,14 +31,14 @@ namespace System.Management.Automation.Remoting.Client { /// - /// WSMan TransportManager related utils + /// WSMan TransportManager related utils. /// 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"}, @@ -89,7 +84,7 @@ internal static class WSManTransportManagerUtils #region Helper Methods /// - /// Constructs a WSManTransportErrorOccuredEventArgs instance from the supplied data + /// Constructs a WSManTransportErrorOccuredEventArgs instance from the supplied data. /// /// /// WSMan API handle to use to get error messages from WSMan error id(s) @@ -121,10 +116,9 @@ internal static TransportErrorOccuredEventArgs ConstructTransportErrorEventArgs( { PSRemotingTransportException e; - //For the first two special error conditions, it is remotely possible that the wsmanSessionTM is null when the failures are returned - //as part of command TM operations (could be returned because of RC retries under the hood) - //Not worth to handle these cases separately as there are very corner scenarios, but need to make sure wsmanSessionTM is not referenced - + // For the first two special error conditions, it is remotely possible that the wsmanSessionTM is null when the failures are returned + // as part of command TM operations (could be returned because of RC retries under the hood) + // Not worth to handle these cases separately as there are very corner scenarios, but need to make sure wsmanSessionTM is not referenced // Destination server is reporting that URI redirect is required for this user. if ((errorStruct.errorCode == WSManNativeApi.ERROR_WSMAN_REDIRECT_REQUESTED) && (wsmanSessionTM != null)) @@ -208,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; } @@ -230,6 +223,7 @@ internal static string ParseEscapeWSManErrorMessage(string errorMessage) { msgSB.Insert(currentToken.EndColumn - 1, ",", 1); } + break; } } @@ -256,9 +250,9 @@ internal enum tmStartModes /// Helper method to convert a transport error code value /// to a fully qualified error Id string. /// - /// transport error code - /// Default FQEID - /// Fully qualified error Id string + /// Transport error code. + /// Default FQEID. + /// Fully qualified error Id string. internal static string GetFQEIDFromTransportError( int transportErrorCode, string defaultFQEID) @@ -293,7 +287,7 @@ internal sealed class WSManClientSessionTransportManager : BaseClientSessionTran /// internal const string MAX_URI_REDIRECTION_COUNT_VARIABLE = "WSManMaxRedirectionCount"; /// - /// Default max uri redirection count - wsman + /// Default max uri redirection count - wsman. /// internal const int MAX_URI_REDIRECTION_COUNT = 5; @@ -310,7 +304,7 @@ private enum CompletionNotification #region CompletionEventArgs - private class CompletionEventArgs : EventArgs + private sealed class CompletionEventArgs : EventArgs { internal CompletionEventArgs(CompletionNotification notification) { @@ -323,24 +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; @@ -364,7 +356,6 @@ internal CompletionEventArgs(CompletionNotification notification) private int _connectionRetryCount; - private const string resBaseName = "remotingerroridstrings"; // Robust connections maximum retry time value in milliseconds. @@ -455,10 +446,12 @@ 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 + + // generate unique session id private static long GetNextSessionTMHandleId() { return System.Threading.Interlocked.Increment(ref s_sessionTMSeed); @@ -501,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 @@ -554,14 +547,17 @@ static WSManClientSessionTransportManager() /// /// Connection info to use while connecting to the remote machine. /// - /// crypto helper - /// session friendly name + /// Crypto helper. + /// Session friendly name. /// /// 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(); @@ -570,7 +566,8 @@ internal WSManClientSessionTransportManager(Guid runspacePoolInstanceId, throw new PSRemotingTransportException( StringUtil.Format(RemotingErrorIdStrings.WSManInitFailed, WSManAPIData.ErrorCode)); } - Dbg.Assert(null != connectionInfo, "connectionInfo cannot be null"); + + Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null"); CryptoHelper = cryptoHelper; dataToBeSent.Fragmentor = base.Fragmentor; @@ -654,7 +651,7 @@ internal void SetConnectTimeOut(int milliseconds) } /// - /// Sets timeout for Close operation in milliseconds + /// Sets timeout for Close operation in milliseconds. /// /// /// @@ -682,7 +679,7 @@ internal void SetCloseTimeOut(int milliseconds) } /// - /// Sets timeout for SendShellInput operation in milliseconds + /// Sets timeout for SendShellInput operation in milliseconds. /// /// /// @@ -710,7 +707,7 @@ internal void SetSendTimeOut(int milliseconds) } /// - /// Sets timeout for Receive operation in milliseconds + /// Sets timeout for Receive operation in milliseconds. /// /// /// @@ -738,7 +735,7 @@ internal void SetReceiveTimeOut(int milliseconds) } /// - /// Sets timeout for Signal operation in milliseconds + /// Sets timeout for Signal operation in milliseconds. /// /// /// @@ -822,7 +819,6 @@ internal void SetWSManSessionOption(WSManNativeApi.WSManSessionOption option, st internal bool SupportsDisconnect { get; private set; } - internal override void DisconnectAsync() { Dbg.Assert(!isClosed, "object already disposed"); @@ -836,7 +832,7 @@ internal override void DisconnectAsync() // startup info WSManNativeApi.WSManShellDisconnectInfo disconnectInfo = new WSManNativeApi.WSManShellDisconnectInfo(uIdleTimeout); - //Add ETW traces + // Add ETW traces // disconnect Callback _disconnectSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionDisconnectCallback); @@ -874,7 +870,7 @@ internal override void ReconnectAsync() Dbg.Assert(!isClosed, "object already disposed"); ReceivedDataCollection.PrepareForStreamConnect(); - //Add ETW traces + // Add ETW traces // reconnect Callback _reconnectSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionReconnectCallback); @@ -902,7 +898,7 @@ internal override void ReconnectAsync() /// /// Starts connecting to an existing remote session. This will result in a WSManConnectShellEx WSMan /// async call. Piggy backs available data in input stream as openXml in connect SOAP. - /// DSHandler will push negotiation related messages through the open content + /// DSHandler will push negotiation related messages through the open content. /// /// /// WSManConnectShellEx failed. @@ -910,34 +906,36 @@ internal override void ReconnectAsync() internal override void ConnectAsync() { Dbg.Assert(!isClosed, "object already disposed"); - Dbg.Assert(!String.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty."); + Dbg.Assert(!string.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty."); ReceivedDataCollection.PrepareForStreamConnect(); // additional content with connect shell call. Negotiation and connect related messages // should be included in payload - if (null == _openContent) + if (_openContent == null) { DataPriorityType additionalDataType; byte[] additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType); - if (null != additionalData) + if (additionalData != null) { // 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)); _openContent = new WSManNativeApi.WSManData_ManToUn(base64EncodedDataInXml); } - //THERE SHOULD BE NO ADDITIONAL DATA. If there is, it means we are not able to push all initial negotiation related data + // THERE SHOULD BE NO ADDITIONAL DATA. If there is, it means we are not able to push all initial negotiation related data // as part of Connect SOAP. The connect algorithm is based on this assumption. So bail out. additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType); if (additionalData != null) { - //Negotiation payload does not fit in ConnectShell. bail out. - //Assert for now. should be replaced with raising an exception so upper layers can catch. + // Negotiation payload does not fit in ConnectShell. bail out. + // Assert for now. should be replaced with raising an exception so upper layers can catch. Dbg.Assert(false, "Negotiation payload does not fit in ConnectShell"); return; } @@ -947,7 +945,7 @@ internal override void ConnectAsync() _sessionContextID = GetNextSessionTMHandleId(); AddSessionTransportManager(_sessionContextID, this); - //session is implicitly assumed to support disconnect + // session is implicitly assumed to support disconnect SupportsDisconnect = true; // Create Callback @@ -973,7 +971,7 @@ internal override void ConnectAsync() WSManNativeApi.WSManConnectShellEx(_wsManSessionHandle, flags, ConnectionInfo.ShellUri, - RunspacePoolInstanceId.ToString().ToUpperInvariant(), //wsman is case sensitive wrt shellId. so consistently using upper case + RunspacePoolInstanceId.ToString().ToUpperInvariant(), // wsman is case sensitive wrt shellId. so consistently using upper case IntPtr.Zero, _openContent, _connectSessionCallback, @@ -1011,7 +1009,8 @@ internal override void StartReceivingData() receiveDataInitiated = true; tracer.WriteLine("Client Session TM: Placing Receive request using WSManReceiveShellOutputEx"); - PSEtwLog.LogAnalyticInformational(PSEventId.WSManReceiveShellOutputEx, + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManReceiveShellOutputEx, PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, RunspacePoolInstanceId.ToString(), Guid.Empty.ToString()); @@ -1031,10 +1030,10 @@ 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."); + Dbg.Assert(!string.IsNullOrEmpty(ConnectionInfo.ShellUri), "shell uri cannot be null or empty."); Dbg.Assert(WSManAPIData != null, "WSManApiData should always be created before session creation."); List shellOptions = new List(WSManAPIData.CommonOptionSet); @@ -1069,7 +1068,7 @@ internal override void CreateAsync() // additional content with create shell call. Piggy back first fragment from // the dataToBeSent buffer. - if (null == _openContent) + if (_openContent == null) { DataPriorityType additionalDataType; byte[] additionalData = dataToBeSent.ReadOrRegisterCallback(null, out additionalDataType); @@ -1090,11 +1089,13 @@ internal override void CreateAsync() #endregion - if (null != additionalData) + if (additionalData != null) { // 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)); @@ -1115,7 +1116,9 @@ internal override void CreateAsync() _createSessionCallbackGCHandle = GCHandle.Alloc(_createSessionCallback); } - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateShell, PSOpcode.Connect, + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShell, + PSOpcode.Connect, PSTask.CreateRunspace, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, RunspacePoolInstanceId.ToString()); @@ -1184,13 +1187,13 @@ 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. lock (syncObject) { - if (isClosed == true) + if (isClosed) { return; } @@ -1202,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; } @@ -1228,11 +1231,13 @@ internal override void CloseAsync() { RemoveSessionTransportManager(_sessionContextID); } + return; } - //TODO - On unexpected failures on a reconstructed session... we dont want to close server session - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCloseShell, + // TODO - On unexpected failures on a reconstructed session... we dont want to close server session + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseShell, PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, RunspacePoolInstanceId.ToString()); _closeSessionCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_sessionContextID), s_sessionCloseCallback); @@ -1245,10 +1250,10 @@ internal override void CloseAsync() /// With default configuration remoting from V3 client to V2 server will break as V3 client can send upto 500KB in a single Send packet /// So if server version is known to be V2, we'll downgrade the max env size to 150KB (V2's default) if the current value is 500KB (V3 default) /// - /// server negotiated protocol version + /// Server negotiated protocol version. internal void AdjustForProtocolVariations(Version serverProtocolVersion) { - if (serverProtocolVersion <= RemotingConstants.ProtocolVersionWin7RTM) + if (serverProtocolVersion <= RemotingConstants.ProtocolVersion_2_1) { int maxEnvSize; WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle, @@ -1270,7 +1275,7 @@ internal void AdjustForProtocolVariations(Version serverProtocolVersion) throw exception; } - //retrieve the packet size again + // retrieve the packet size again int packetSize; WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle, WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SHELL_MAX_DATA_SIZE_PER_MESSAGE_KB, @@ -1311,11 +1316,15 @@ internal override void Redirect(Uri newUri, RunspaceConnectionInfo connectionInf { CloseSessionAndClearResources(); tracer.WriteLine("Redirecting to URI: {0}", newUri); - PSEtwLog.LogAnalyticInformational(PSEventId.URIRedirection, - PSOpcode.Connect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - RunspacePoolInstanceId.ToString(), newUri.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.URIRedirection, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + RunspacePoolInstanceId.ToString(), + newUri.ToString()); Initialize(newUri, (WSManConnectionInfo)connectionInfo); - //reset startmode + // reset startmode _startMode = WSManTransportManagerUtils.tmStartModes.None; CreateAsync(); } @@ -1337,10 +1346,10 @@ internal override void Redirect(Uri newUri, RunspaceConnectionInfo connectionInf internal override BaseClientCommandTransportManager CreateClientCommandTransportManager(RunspaceConnectionInfo connectionInfo, ClientRemotePowerShell cmd, bool noInput) { - Dbg.Assert(null != cmd, "Cmd cannot be null"); + Dbg.Assert(cmd != null, "Cmd cannot be null"); WSManConnectionInfo wsmanConnectionInfo = connectionInfo as WSManConnectionInfo; - Dbg.Assert(null != wsmanConnectionInfo, "ConnectionInfo must be WSManConnectionInfo"); + Dbg.Assert(wsmanConnectionInfo != null, "ConnectionInfo must be WSManConnectionInfo"); WSManClientCommandTransportManager result = new WSManClientCommandTransportManager(wsmanConnectionInfo, _wsManShellOperationHandle, cmd, noInput, this); @@ -1361,7 +1370,7 @@ internal override BaseClientCommandTransportManager CreateClientCommandTransport /// private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) { - Dbg.Assert(null != connectionInfo, "connectionInfo cannot be null."); + Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null."); ConnectionInfo = connectionInfo; @@ -1384,10 +1393,12 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) { additionalUriSuffixString = ";MSP=7a83d074-bb86-4e52-aa3e-6cc73cc066c8"; } + 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. @@ -1401,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; @@ -1419,7 +1431,7 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) // use credential based authentication string userName = null; System.Security.SecureString password = null; - if ((null != connectionInfo.Credential) && (!string.IsNullOrEmpty(connectionInfo.Credential.UserName))) + if ((connectionInfo.Credential != null) && (!string.IsNullOrEmpty(connectionInfo.Credential.UserName))) { userName = connectionInfo.Credential.UserName; password = connectionInfo.Credential.Password; @@ -1464,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); @@ -1474,26 +1486,15 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) { result = WSManNativeApi.WSManCreateSession(WSManAPIData.WSManAPIHandle, connectionStr, 0, authCredentials.GetMarshalledObject(), - (null == proxyInfo) ? IntPtr.Zero : (IntPtr)proxyInfo, + (proxyInfo == null) ? IntPtr.Zero : (IntPtr)proxyInfo, ref _wsManSessionHandle); } finally { // release resources - if (null != proxyAuthCredentials) - { - proxyAuthCredentials.Dispose(); - } - - if (null != proxyInfo) - { - proxyInfo.Dispose(); - } - - if (null != authCredentials) - { - authCredentials.Dispose(); - } + proxyAuthCredentials?.Dispose(); + proxyInfo?.Dispose(); + authCredentials?.Dispose(); } if (result != 0) @@ -1530,6 +1531,26 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) // config provider. SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_USE_SSL, 1); } + +#if UNIX + // Explicitly disallow Basic auth over HTTP on Unix. + if (connectionInfo.AuthenticationMechanism == AuthenticationMechanism.Basic && !isSSLSpecified && connectionUri.Scheme != Uri.UriSchemeHttps) + { + throw new PSRemotingTransportException(PSRemotingErrorId.ConnectFailed, RemotingErrorIdStrings.BasicAuthOverHttpNotSupported); + } + + // 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); + } +#endif + if (connectionInfo.NoEncryption) { // send unencrypted messages @@ -1542,6 +1563,7 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) WSManNativeApi.WSManSessionOption.WSMAN_OPTION_ALLOW_NEGOTIATE_IMPLICIT_CREDENTIALS, new WSManNativeApi.WSManDataDWord(1)); } + if (connectionInfo.UseUTF16) { SetWSManSessionOption(WSManNativeApi.WSManSessionOption.WSMAN_OPTION_UTF16, 1); @@ -1596,7 +1618,7 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) /// /// Handle transport error - calls EnqueueAndStartProcessingThread to process transport exception /// in a different thread - /// Logic in transport callbacks should always use this to process a transport error + /// Logic in transport callbacks should always use this to process a transport error. /// internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArgs) { @@ -1607,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; @@ -1626,14 +1648,16 @@ internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArg } // Write errors into both Operational and Analytical channels - PSEtwLog.LogOperationalError(PSEventId.TransportError, PSOpcode.Open, PSTask.None, PSKeyword.UseAlwaysOperational, + PSEtwLog.LogOperationalError( + PSEventId.TransportError, PSOpcode.Open, PSTask.None, PSKeyword.UseAlwaysOperational, RunspacePoolInstanceId.ToString(), Guid.Empty.ToString(), eventArgs.Exception.ErrorCode.ToString(CultureInfo.InvariantCulture), eventArgs.Exception.Message, stackTrace); - PSEtwLog.LogAnalyticError(PSEventId.TransportError_Analytic, + PSEtwLog.LogAnalyticError( + PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, RunspacePoolInstanceId.ToString(), @@ -1646,7 +1670,7 @@ internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArg } /// - /// receive/send operation handles and callback handles should be released/disposed from + /// Receive/send operation handles and callback handles should be released/disposed from /// receive/send callback only. Releasing them after CloseOperation() may not cover all /// the scenarios, as WSMan does not guarantee that a rcv/send callback is not called after /// Close completed callback. @@ -1657,14 +1681,14 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) { if (shouldClearSend) { - if (null != _sendToRemoteCompleted) + if (_sendToRemoteCompleted != null) { _sendToRemoteCompleted.Dispose(); _sendToRemoteCompleted = null; } // For send..clear always - if (IntPtr.Zero != _wsManSendOperationHandle) + if (_wsManSendOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0); _wsManSendOperationHandle = IntPtr.Zero; @@ -1675,13 +1699,13 @@ 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; } - if (null != _receivedFromRemote) + if (_receivedFromRemote != null) { _receivedFromRemote.Dispose(); _receivedFromRemote = null; @@ -1693,7 +1717,7 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) /// /// Call back from worker thread / queue to raise Robust Connection notification event. /// - /// ConnectionStatusEventArgs + /// ConnectionStatusEventArgs. internal override void ProcessPrivateData(object privateData) { // Raise the Robust @@ -1725,7 +1749,7 @@ internal override void ProcessPrivateData(object privateData) } /// - /// Robust connection maximum retry time in milliseconds + /// Robust connection maximum retry time in milliseconds. /// internal int MaxRetryConnectionTime { @@ -1751,11 +1775,14 @@ internal IntPtr SessionHandle /// session create/connect retry attempt may be beneficial then do the /// retry attempt. /// - /// Error code returned from Create response + /// Error code returned from Create response. /// 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) @@ -1840,8 +1867,11 @@ private static void OnCreateSessionCallback(IntPtr operationContext, return; } - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateShellCallbackReceived, - PSOpcode.Connect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateShellCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, sessionTM.RunspacePoolInstanceId.ToString()); // TODO: 188098 wsManShellOperationHandle should be populated by WSManCreateShellEx, @@ -1860,13 +1890,13 @@ private static void OnCreateSessionCallback(IntPtr operationContext, } } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); // Test error code for possible session connection retry. if (sessionTM.RetrySessionCreation(errorStruct.errorCode)) @@ -1889,13 +1919,13 @@ 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; + // check if the session supports disconnect + 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 // is successfully established. - if (null != sessionTM._openContent) + if (sessionTM._openContent != null) { sessionTM._openContent.Dispose(); sessionTM._openContent = null; @@ -1953,17 +1983,21 @@ private static void OnCloseSessionCompleted(IntPtr operationContext, return; } - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCloseShellCallbackReceived, - PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - sessionTM.RunspacePoolInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseShellCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + sessionTM.RunspacePoolInstanceId.ToString(), + "OnCloseSessionCompleted"); - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2000,22 +2034,29 @@ private static void OnRemoteSessionDisconnectCompleted(IntPtr operationContext, return; } - //LOG ETW EVENTS + // LOG ETW EVENTS + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseShellCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + sessionTM.RunspacePoolInstanceId.ToString(), + "OnRemoteSessionDisconnectCompleted"); // Dispose the OnDisconnect callback as it is not needed anymore - if (null != sessionTM._disconnectSessionCompleted) + if (sessionTM._disconnectSessionCompleted != null) { sessionTM._disconnectSessionCompleted.Dispose(); sessionTM._disconnectSessionCompleted = null; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2043,7 +2084,14 @@ private static void OnRemoteSessionDisconnectCompleted(IntPtr operationContext, sessionTM.EnqueueAndStartProcessingThread(null, null, new CompletionEventArgs(CompletionNotification.DisconnectCompleted)); - //Log ETW traces + // Log ETW traces + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseShellCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + sessionTM.RunspacePoolInstanceId.ToString(), + "OnRemoteSessionReconnectCompleted: DisconnectCompleted"); } return; @@ -2068,22 +2116,29 @@ private static void OnRemoteSessionReconnectCompleted(IntPtr operationContext, return; } - //Add ETW events + // Add ETW events + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseShellCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + sessionTM.RunspacePoolInstanceId.ToString(), + "OnRemoteSessionReconnectCompleted"); // Dispose the OnCreate callback as it is not needed anymore - if (null != sessionTM._reconnectSessionCompleted) + if (sessionTM._reconnectSessionCompleted != null) { sessionTM._reconnectSessionCompleted.Dispose(); sessionTM._reconnectSessionCompleted = null; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2164,6 +2219,13 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, { tracer.WriteLine("Client Session TM: Connect callback received"); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSendShellInputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnRemoteSessionConnectCallback:Client Session TM: Connect callback received"); + long sessionTMHandle = 0; WSManClientSessionTransportManager sessionTM = null; if (!TryGetSessionTransportManager(operationContext, out sessionTM, out sessionTMHandle)) @@ -2180,13 +2242,13 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2201,8 +2263,8 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, } } - //dispose openContent - if (null != sessionTM._openContent) + // dispose openContent + if (sessionTM._openContent != null) { sessionTM._openContent.Dispose(); sessionTM._openContent = null; @@ -2218,8 +2280,8 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, } } - //process returned Xml - Dbg.Assert(data != null, "WSManConnectShell callback returned null data"); + // process returned Xml + Dbg.Assert(data != IntPtr.Zero, "WSManConnectShell callback returned null data"); WSManNativeApi.WSManConnectDataResult connectData = WSManNativeApi.WSManConnectDataResult.UnMarshal(data); if (connectData.data != null) { @@ -2257,9 +2319,13 @@ private static void OnRemoteSessionSendCompleted(IntPtr operationContext, } // do the logging for this send - PSEtwLog.LogAnalyticInformational(PSEventId.WSManSendShellInputExCallbackReceived, - PSOpcode.Connect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - sessionTM.RunspacePoolInstanceId.ToString(), Guid.Empty.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSendShellInputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + sessionTM.RunspacePoolInstanceId.ToString(), + Guid.Empty.ToString()); if (!shellOperationHandle.Equals(sessionTM._wsManShellOperationHandle)) { @@ -2283,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); @@ -2292,7 +2358,7 @@ private static void OnRemoteSessionSendCompleted(IntPtr operationContext, // way of notifying the same using state change events. if ((errorStruct.errorCode != 0) && (errorStruct.errorCode != 995)) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2353,13 +2419,13 @@ private static void OnRemoteSessionDataReceived(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) { - tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode, errorStruct.errorDetail); + tracer.WriteLine("Got error with error code {0}. Message {1}", errorStruct.errorCode.ToString(), errorStruct.errorDetail); TransportErrorOccuredEventArgs eventargs = WSManTransportManagerUtils.ConstructTransportErrorEventArgs( sessionTM.WSManAPIData.WSManAPIHandle, @@ -2375,7 +2441,7 @@ private static void OnRemoteSessionDataReceived(IntPtr operationContext, } WSManNativeApi.WSManReceiveDataResult dataReceived = WSManNativeApi.WSManReceiveDataResult.UnMarshal(data); - if (null != dataReceived.data) + if (dataReceived.data != null) { tracer.WriteLine("Session Received Data : {0}", dataReceived.data.Length); PSEtwLog.LogAnalyticInformational( @@ -2398,7 +2464,7 @@ private void SendOneItem() // This will either return data or register callback but doesn't do both. byte[] data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback, out priorityType); - if (null != data) + if (data != null) { SendData(data, priorityType); } @@ -2406,7 +2472,7 @@ private void SendOneItem() private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType) { - Dbg.Assert(null != data, "data cannot be null in the data available callback"); + Dbg.Assert(data != null, "data cannot be null in the data available callback"); tracer.WriteLine("Received data to be sent from the callback."); SendData(data, priorityType); @@ -2469,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); @@ -2480,7 +2546,7 @@ internal override void Dispose(bool isDisposing) // openContent is used by redirection ie., while redirecting to // a new machine and hence this is cleared only when the session // is disposing. - if (isDisposing && (null != _openContent)) + if (isDisposing && (_openContent != null)) { _openContent.Dispose(); _openContent = null; @@ -2506,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) @@ -2518,7 +2584,7 @@ private void CloseSessionAndClearResources() // remove session context from session handles dictionary RemoveSessionTransportManager(_sessionContextID); - if (null != _closeSessionCompleted) + if (_closeSessionCompleted != null) { _closeSessionCompleted.Dispose(); _closeSessionCompleted = null; @@ -2527,7 +2593,7 @@ private void CloseSessionAndClearResources() // Dispose the create session completed callback here, since it is // used for periodic robust connection retry/auto-disconnect // notifications while the shell is active. - if (null != _createSessionCallback) + if (_createSessionCallback != null) { _createSessionCallbackGCHandle.Free(); _createSessionCallback.Dispose(); @@ -2535,7 +2601,7 @@ private void CloseSessionAndClearResources() } // Dispose the OnConnect callback if one present - if (null != _connectSessionCallback) + if (_connectSessionCallback != null) { _connectSessionCallback.Dispose(); _connectSessionCallback = null; @@ -2549,17 +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 @@ -2572,28 +2638,25 @@ 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(); -#if !CORECLR - private WindowsIdentity _identityToImpersonate; + private readonly object _syncObject = new object(); +#if !UNIX + private readonly WindowsIdentity _identityToImpersonate; #endif /// - /// Initializes handle by calling WSManInitialize API + /// Initializes handle by calling WSManInitialize API. /// internal WSManAPIDataCommon() { -#if !CORECLR +#if !UNIX // Check for thread impersonation and save identity for later de-initialization. - _identityToImpersonate = WindowsIdentity.GetCurrent(); - _identityToImpersonate = (_identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation) ? - _identityToImpersonate : null; + Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate); #endif _handle = IntPtr.Zero; @@ -2626,7 +2689,6 @@ internal WSManAPIDataCommon() _outputStreamSet = new WSManNativeApi.WSManStreamIDSet_ManToUn( new string[] { WSManNativeApi.WSMAN_STREAM_ID_STDOUT }); - // startup options common to all connections WSManNativeApi.WSManOption protocolStartupOption = new WSManNativeApi.WSManOption(); protocolStartupOption.name = RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME; @@ -2638,13 +2700,17 @@ internal WSManAPIDataCommon() } internal int ErrorCode { get; } + internal WSManNativeApi.WSManStreamIDSet_ManToUn InputStreamSet { get { return _inputStreamSet; } } + internal WSManNativeApi.WSManStreamIDSet_ManToUn OutputStreamSet { get { return _outputStreamSet; } } + internal List CommonOptionSet { get; } + internal IntPtr WSManAPIHandle { get { return _handle; } } /// - /// Dispose + /// Dispose. /// // Suppress this message. The result is actually used, but only in checked builds.... [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", @@ -2654,51 +2720,38 @@ public void Dispose() { lock (_syncObject) { - if (_isDisposed) { return; } + if (_isDisposed) + { + return; + } + _isDisposed = true; } _inputStreamSet.Dispose(); _outputStreamSet.Dispose(); - if (IntPtr.Zero != _handle) + if (_handle != IntPtr.Zero) { -#if !CORECLR - // If we initialized with thread impersonation make sure de-initialize is run with same. - WindowsImpersonationContext impersonationContext = null; + int result = 0; + +#if !UNIX + // If we initialized with thread impersonation, make sure de-initialize is run with the same. if (_identityToImpersonate != null) { - try - { - _identityToImpersonate.Impersonate(); - } - catch (ObjectDisposedException) - { - _handle = IntPtr.Zero; - return; - } + result = WindowsIdentity.RunImpersonated( + _identityToImpersonate.AccessToken, + () => WSManNativeApi.WSManDeinitialize(_handle, 0)); } - - try + else { #endif - int result = WSManNativeApi.WSManDeinitialize(_handle, 0); - Dbg.Assert(result == 0, "WSManDeinitialize returned non-zero value"); -#if !CORECLR - } - finally - { - if (impersonationContext != null) - { - try - { - impersonationContext.Undo(); - impersonationContext.Dispose(); - } - catch (System.Security.SecurityException) { } - } + result = WSManNativeApi.WSManDeinitialize(_handle, 0); +#if !UNIX } #endif + + Dbg.Assert(result == 0, "WSManDeinitialize returned non-zero value"); _handle = IntPtr.Zero; } } @@ -2731,19 +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; @@ -2770,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) { @@ -2855,15 +2905,16 @@ 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(null != connectionInfo, "connectionInfo cannot be null"); + Dbg.Assert(wsManShellOperationHandle != IntPtr.Zero, "Shell operation handle cannot be IntPtr.Zero."); + Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null"); _wsManShellOperationHandle = wsManShellOperationHandle; @@ -2885,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 @@ -2952,15 +3003,15 @@ internal override void ConnectAsync() } /// - /// + /// Begin connection creation. /// /// /// WSManRunShellCommandEx failed. /// - internal override void CreateAsync() + public override void CreateAsync() { byte[] cmdPart1 = serializedPipeline.ReadOrRegisterCallback(null); - if (null != cmdPart1) + if (cmdPart1 != null) { #region SHIM: Redirection code for command code send. @@ -2984,9 +3035,13 @@ internal override void CreateAsync() _cmdContextId = GetNextCmdTMHandleId(); AddCmdTransportManager(_cmdContextId, this); - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateCommand, PSOpcode.Connect, - PSTask.CreateRunspace, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateCommand, + PSOpcode.Connect, + PSTask.CreateRunspace, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + RunspacePoolInstanceId.ToString(), + powershellInstanceId.ToString()); _createCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdCreateCallback); _createCmdCompletedGCHandle = GCHandle.Alloc(_createCmdCompleted); @@ -3025,7 +3080,7 @@ internal override void CreateAsync() } /// - /// Restores connection on a disconnected command + /// Restores connection on a disconnected command. /// internal override void ReconnectAsync() { @@ -3041,13 +3096,13 @@ internal override void ReconnectAsync() } } /// - /// Used by powershell/pipeline to send a stop message to the server command + /// Used by powershell/pipeline to send a stop message to the server command. /// internal override void SendStopSignal() { lock (syncObject) { - if (isClosed == true) + if (isClosed) { return; } @@ -3064,9 +3119,14 @@ internal override void SendStopSignal() _isStopSignalPending = false; tracer.WriteLine("Sending stop signal with command context: {0} Operation Context {1}", _cmdContextId, _wsManCmdOperationHandle); - PSEtwLog.LogAnalyticInformational(PSEventId.WSManSignal, - PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString(), StopSignal); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSignal, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + RunspacePoolInstanceId.ToString(), + powershellInstanceId.ToString(), + StopSignal); _signalCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdSignalCallback); WSManNativeApi.WSManSignalShellEx(_wsManShellOperationHandle, _wsManCmdOperationHandle, 0, @@ -3077,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); @@ -3085,7 +3145,7 @@ internal override void CloseAsync() // then let other threads release the lock before we cleaning up the resources. lock (syncObject) { - if (isClosed == true) + if (isClosed) { return; } @@ -3096,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; } @@ -3114,12 +3174,17 @@ internal override void CloseAsync() { RemoveCmdTransportManager(_cmdContextId); } + return; } - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCloseCommand, - PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseCommand, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + RunspacePoolInstanceId.ToString(), + powershellInstanceId.ToString()); _closeCmdCompleted = new WSManNativeApi.WSManShellAsync(new IntPtr(_cmdContextId), s_cmdCloseCallback); Dbg.Assert((IntPtr)_closeCmdCompleted != IntPtr.Zero, "closeCmdCompleted callback is null in cmdTM.CloseAsync()"); WSManNativeApi.WSManCloseCommand(_wsManCmdOperationHandle, 0, _closeCmdCompleted); @@ -3128,7 +3193,7 @@ internal override void CloseAsync() /// /// Handle transport error - calls EnqueueAndStartProcessingThread to process transport exception /// in a different thread - /// Logic in transport callbacks should always use this to process a transport error + /// Logic in transport callbacks should always use this to process a transport error. /// internal void ProcessWSManTransportError(TransportErrorOccuredEventArgs eventArgs) { @@ -3139,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; @@ -3188,7 +3253,7 @@ internal override void RaiseErrorHandler(TransportErrorOccuredEventArgs eventArg /// internal override void ProcessPrivateData(object privateData) { - Dbg.Assert(null != privateData, "privateData cannot be null."); + Dbg.Assert(privateData != null, "privateData cannot be null."); // For this version...only a boolean can be used for privateData. bool shouldRaiseSignalCompleted = (bool)privateData; @@ -3199,7 +3264,7 @@ internal override void ProcessPrivateData(object privateData) } /// - /// receive/send operation handles and callback handles should be released/disposed from + /// Receive/send operation handles and callback handles should be released/disposed from /// receive/send callback only. Releasing them after CloseOperation() may not cover all /// the scenarios, as WSMan does not guarantee that a rcv/send callback is not called after /// Close completed callback. @@ -3210,14 +3275,14 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) { if (shouldClearSend) { - if (null != _sendToRemoteCompleted) + if (_sendToRemoteCompleted != null) { _sendToRemoteCompleted.Dispose(); _sendToRemoteCompleted = null; } // For send..clear always - if (IntPtr.Zero != _wsManSendOperationHandle) + if (_wsManSendOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0); _wsManSendOperationHandle = IntPtr.Zero; @@ -3228,13 +3293,13 @@ 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; } - if (null != _receivedFromRemote) + if (_receivedFromRemote != null) { _receivedFromRemote.Dispose(); _receivedFromRemote = null; @@ -3294,12 +3359,16 @@ private static void OnCreateCmdCompleted(IntPtr operationContext, return; } - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCreateCommandCallbackReceived, - PSOpcode.Connect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - cmdTM.RunspacePoolInstanceId.ToString(), cmdTM.powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateCommandCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + cmdTM.RunspacePoolInstanceId.ToString(), + cmdTM.powershellInstanceId.ToString()); // dispose the cmdCompleted callback as it is not needed any more - if (null != cmdTM._createCmdCompleted) + if (cmdTM._createCmdCompleted != null) { cmdTM._createCmdCompletedGCHandle.Free(); cmdTM._createCmdCompleted.Dispose(); @@ -3313,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) @@ -3386,6 +3455,13 @@ private static void OnConnectCmdCompleted(IntPtr operationContext, { tracer.WriteLine("OnConnectCmdCompleted callback received"); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCreateCommandCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnConnectCmdCompleted: OnConnectCmdCompleted callback received"); + long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) @@ -3396,7 +3472,7 @@ private static void OnConnectCmdCompleted(IntPtr operationContext, } // dispose the cmdCompleted callback as it is not needed any more - if (null != cmdTM._connectCmdCompleted) + if (cmdTM._connectCmdCompleted != null) { cmdTM._connectCmdCompleted.Dispose(); cmdTM._connectCmdCompleted = null; @@ -3404,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) @@ -3475,6 +3551,13 @@ private static void OnCloseCmdCompleted(IntPtr operationContext, { tracer.WriteLine("OnCloseCmdCompleted callback received for operation context {0}", commandOperationHandle); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseCommandCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnCloseCmdCompleted: OnCloseCmdCompleted callback received"); + long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) @@ -3485,9 +3568,13 @@ private static void OnCloseCmdCompleted(IntPtr operationContext, } tracer.WriteLine("Close completed callback received for command: {0}", cmdTM._cmdContextId); - PSEtwLog.LogAnalyticInformational(PSEventId.WSManCloseCommandCallbackReceived, - PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - cmdTM.RunspacePoolInstanceId.ToString(), cmdTM.powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManCloseCommandCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + cmdTM.RunspacePoolInstanceId.ToString(), + cmdTM.powershellInstanceId.ToString()); if (cmdTM._isDisconnectPending) { @@ -3507,6 +3594,13 @@ private static void OnRemoteCmdSendCompleted(IntPtr operationContext, { tracer.WriteLine("SendComplete callback received"); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSendShellInputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnRemoteCmdSendCompleted: SendComplete callback received"); + long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) @@ -3519,9 +3613,13 @@ private static void OnRemoteCmdSendCompleted(IntPtr operationContext, cmdTM._isSendingInput = false; // do the logging for this send - PSEtwLog.LogAnalyticInformational(PSEventId.WSManSendShellInputExCallbackReceived, - PSOpcode.Connect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - cmdTM.RunspacePoolInstanceId.ToString(), cmdTM.powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSendShellInputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + cmdTM.RunspacePoolInstanceId.ToString(), + cmdTM.powershellInstanceId.ToString()); if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) || (!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle))) @@ -3553,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 @@ -3590,6 +3688,13 @@ private static void OnRemoteCmdDataReceived(IntPtr operationContext, { tracer.WriteLine("Remote Command DataReceived callback."); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManReceiveShellOutputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnRemoteCmdDataReceived: Remote Command DataReceived callback"); + long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) @@ -3623,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) @@ -3651,7 +3756,7 @@ private static void OnRemoteCmdDataReceived(IntPtr operationContext, } WSManNativeApi.WSManReceiveDataResult dataReceived = WSManNativeApi.WSManReceiveDataResult.UnMarshal(data); - if (null != dataReceived.data) + if (dataReceived.data != null) { tracer.WriteLine("Cmd Received Data : {0}", dataReceived.data.Length); PSEtwLog.LogAnalyticInformational( @@ -3674,6 +3779,14 @@ private static void OnReconnectCmdCompleted(IntPtr operationContext, { long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; + + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManReceiveShellOutputExCallbackReceived, + PSOpcode.Connect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + "OnReconnectCmdCompleted"); + if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) { // We dont have the command TM handle..just return. @@ -3695,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) @@ -3733,6 +3846,8 @@ private static void OnRemoteCmdSignalCompleted(IntPtr operationContext, { tracer.WriteLine("Signal Completed callback received."); + PSEtwLog.LogAnalyticInformational(PSEventId.WSManSignalCallbackReceived, PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, "OnRemoteCmdSignalCompleted"); + long cmdContextId = 0; WSManClientCommandTransportManager cmdTM = null; if (!TryGetCmdTransportManager(operationContext, out cmdTM, out cmdContextId)) @@ -3743,9 +3858,13 @@ private static void OnRemoteCmdSignalCompleted(IntPtr operationContext, } // log the callback received event. - PSEtwLog.LogAnalyticInformational(PSEventId.WSManSignalCallbackReceived, - PSOpcode.Disconnect, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - cmdTM.RunspacePoolInstanceId.ToString(), cmdTM.powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManSignalCallbackReceived, + PSOpcode.Disconnect, + PSTask.None, + PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + cmdTM.RunspacePoolInstanceId.ToString(), + cmdTM.powershellInstanceId.ToString()); if ((!shellOperationHandle.Equals(cmdTM._wsManShellOperationHandle)) || (!commandOperationHandle.Equals(cmdTM._wsManCmdOperationHandle))) @@ -3762,13 +3881,13 @@ 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; } - if (null != cmdTM._signalCmdCompleted) + if (cmdTM._signalCmdCompleted != null) { cmdTM._signalCmdCompleted.Dispose(); cmdTM._signalCmdCompleted = null; @@ -3781,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) @@ -3840,7 +3959,7 @@ private void SendOneItem() data = dataToBeSent.ReadOrRegisterCallback(_onDataAvailableToSendCallback, out priorityType); } - if (null != data) + if (data != null) { _isSendingInput = true; SendData(data, priorityType); @@ -3854,7 +3973,7 @@ private void SendOneItem() private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType) { - Dbg.Assert(null != data, "data cannot be null in the data available callback"); + Dbg.Assert(data != null, "data cannot be null in the data available callback"); tracer.WriteLine("Received data from dataToBeSent store."); Dbg.Assert(_chunkToSend == null, "data callback received while a chunk is pending to be sent"); @@ -3864,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 @@ -3922,9 +4041,11 @@ private void SendData(byte[] data, DataPriorityType priorityType) internal override void StartReceivingData() { - PSEtwLog.LogAnalyticInformational(PSEventId.WSManReceiveShellOutputEx, - PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString()); + PSEtwLog.LogAnalyticInformational( + PSEventId.WSManReceiveShellOutputEx, + PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, + RunspacePoolInstanceId.ToString(), powershellInstanceId.ToString()); + // We should call Receive only once.. WSMan will call the callback multiple times. _shouldStartReceivingData = false; lock (syncObject) @@ -3956,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); @@ -3965,19 +4086,19 @@ internal override void Dispose(bool isDisposing) RemoveCmdTransportManager(_cmdContextId); // unregister event handlers - if (null != _sessnTm) + if (_sessnTm != null) { _sessnTm.RobustConnectionsInitiated -= HandleRobustConnectionsInitiated; _sessnTm.RobustConnectionsCompleted -= HandleRobusConnectionsCompleted; } - if (null != _closeCmdCompleted) + if (_closeCmdCompleted != null) { _closeCmdCompleted.Dispose(); _closeCmdCompleted = null; } - if (null != _reconnectCmdCompleted) + if (_reconnectCmdCompleted != null) { _reconnectCmdCompleted.Dispose(); _reconnectCmdCompleted = null; @@ -3992,11 +4113,12 @@ 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; - //Generate command transport manager unique id + // Generate command transport manager unique id private static long GetNextCmdTMHandleId() { return System.Threading.Interlocked.Increment(ref 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 85c3beeb9b3..e24f8961b9a 100644 --- a/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs +++ b/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Host; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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 75fc934800f..6c794e21b24 100644 --- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs +++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs @@ -1,13 +1,14 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Tracing; using System.IO; +using System.Management.Automation.Internal; +using System.Management.Automation.Tracing; using System.Threading; +#if !UNIX using System.Security.Principal; -using System.Management.Automation.Internal; -using Microsoft.Win32.SafeHandles; +#endif + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting.Server @@ -27,11 +28,13 @@ internal abstract class OutOfProcessMediatorBase protected string _initialCommand; protected ManualResetEvent allcmdsClosedEvent; +#if !UNIX // Thread impersonation. protected WindowsIdentity _windowsIdentityToImpersonate; +#endif /// - /// Count of commands in progress + /// Count of commands in progress. /// protected int _inProgressCommandsCount = 0; @@ -69,21 +72,15 @@ 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); } catch (Exception e) { PSEtwLog.LogOperationalError( - PSEventId.TransportError, PSOpcode.Open, PSTask.None, + PSEventId.TransportError, + PSOpcode.Open, + PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -92,7 +89,9 @@ protected void ProcessingThreadStart(object state) e.StackTrace); PSEtwLog.LogAnalyticError( - PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, + PSEventId.TransportError_Analytic, + PSOpcode.Open, + PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -112,12 +111,12 @@ protected void ProcessingThreadStart(object state) protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) { string streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_STDIN; - if (stream.Equals(DataPriorityType.PromptResponse.ToString(), StringComparison.OrdinalIgnoreCase)) + if (stream.Equals(nameof(DataPriorityType.PromptResponse), StringComparison.OrdinalIgnoreCase)) { streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE; } - if (Guid.Empty == psGuid) + if (psGuid == Guid.Empty) { lock (_syncObject) { @@ -134,7 +133,7 @@ protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) cmdTM = sessionTM.GetCommandTransportManager(psGuid); } - if (null != cmdTM) + if (cmdTM != null) { // not throwing when there is no associated command as the command might have // legitimately closed while the client is sending data. however the client @@ -154,7 +153,9 @@ protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) protected void OnDataAckPacketReceived(Guid psGuid) { - throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived, + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCUnknownElementReceived, + RemotingErrorIdStrings.IPCUnknownElementReceived, OutOfProcessUtils.PS_OUT_OF_PROC_DATA_ACK_TAG); } @@ -199,10 +200,7 @@ protected void OnSignalPacketReceived(Guid psGuid) } // dont throw if there is no cmdTM as it might have legitimately closed - if (null != cmdTM) - { - cmdTM.Close(null); - } + cmdTM?.Close(null); } finally { @@ -243,12 +241,10 @@ 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; } @@ -266,10 +262,7 @@ protected void OnClosePacketReceived(Guid psGuid) } // dont throw if there is no cmdTM as it might have legitimately closed - if (null != cmdTM) - { - cmdTM.Close(null); - } + cmdTM?.Close(null); lock (_syncObject) { @@ -296,46 +289,73 @@ protected void OnCloseAckPacketReceived(Guid psGuid) #region Methods - protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper) + protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager( + string configurationName, + string configurationFile, + PSRemotingCryptoHelperServer cryptoHelper, + string workingDirectory) { PSSenderInfo senderInfo; #if !UNIX WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); - PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, currentIdentity.Name, null), + PSPrincipal userPrincipal = new PSPrincipal( + new PSIdentity(string.Empty, true, currentIdentity.Name, null), currentIdentity); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #else - PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity("", true, "", null), + PSPrincipal userPrincipal = new PSPrincipal( + new PSIdentity(string.Empty, true, string.Empty, null), null); senderInfo = new PSSenderInfo(userPrincipal, "http://localhost"); #endif - OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper); - - ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo, - _initialCommand, tm, configurationName); + var tm = new OutOfProcessServerSessionTransportManager( + originalStdOut, + originalStdErr, + cryptoHelper); + + ServerRemoteSession.CreateServerRemoteSession( + 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 configurationName = null) + protected void Start( + string initialCommand, + PSRemotingCryptoHelperServer cryptoHelper, + string workingDirectory, + string configurationName, + string configurationFile) { _initialCommand = initialCommand; - sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper); + 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); - } + sessionTM ??= CreateSessionTransportManager( + configurationName: configurationName, + configurationFile: configurationFile, + cryptoHelper: cryptoHelper, + workingDirectory: workingDirectory); } + if (string.IsNullOrEmpty(data)) { lock (_syncObject) @@ -345,26 +365,31 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH sessionTM.Close(null); sessionTM = null; } - throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, - RemotingErrorIdStrings.IPCUnknownElementReceived, string.Empty); + + throw new PSRemotingTransportException( + PSRemotingErrorId.IPCUnknownElementReceived, + RemotingErrorIdStrings.IPCUnknownElementReceived, + string.Empty); } // process data in a thread pool thread..this way Runspace, Command // data can be processed concurrently. -#if CORECLR - ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data); -#else +#if !UNIX Utils.QueueWorkItemWithImpersonation( - _windowsIdentityToImpersonate, - new WaitCallback(ProcessingThreadStart), - data); + _windowsIdentityToImpersonate, + new WaitCallback(ProcessingThreadStart), + data); +#else + ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data); #endif - } while (true); + } } catch (Exception e) { PSEtwLog.LogOperationalError( - PSEventId.TransportError, PSOpcode.Open, PSTask.None, + PSEventId.TransportError, + PSOpcode.Open, + PSTask.None, PSKeyword.UseAlwaysOperational, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -373,7 +398,9 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH e.StackTrace); PSEtwLog.LogAnalyticError( - PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None, + PSEventId.TransportError_Analytic, + PSOpcode.Open, + PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, Guid.Empty.ToString(), Guid.Empty.ToString(), @@ -392,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 @@ -428,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 @@ -441,83 +447,23 @@ 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); - - // replacing StdErr with Null so that no other app messes with the - // original stream - originalStdErr = new OutOfProcessTextWriter(Console.Error); - Console.SetError(TextWriter.Null); - } - - #endregion - - #region Static Methods - - /// - /// - internal static void Run(string initialCommand) - { - lock (SyncObject) + if (combineErrOutStream) { - if (null != s_singletonInstance) - { - Dbg.Assert(false, "Run should not be called multiple times"); - return; - } - - s_singletonInstance = new OutOfProcessMediator(); + originalStdErr = new FormattedErrorTextWriter(Console.Out); + } + else + { + originalStdErr = new OutOfProcessTextWriter(Console.Error); } -#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()); - } - - #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 + // 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); } #endregion @@ -525,10 +471,19 @@ private SSHProcessMediator() : base(true) #region Static Methods /// - /// + /// Starts the out-of-process powershell server instance. /// - /// - internal static void Run(string initialCommand) + /// Specifies the initialization script. + /// Specifies the initial working directory. The working directory is set before the initial command. + /// 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) { @@ -538,17 +493,15 @@ internal static void Run(string initialCommand) return; } - s_singletonInstance = new SSHProcessMediator(); + s_singletonInstance = new StdIOProcessMediator(combineErrOutStream); } - PSRemotingCryptoHelperServer cryptoHelper; -#if !UNIX - cryptoHelper = new PSRemotingCryptoHelperServer(); -#else - cryptoHelper = null; -#endif - - s_singletonInstance.Start(initialCommand, cryptoHelper); + s_singletonInstance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: workingDirectory, + configurationName: configurationName, + configurationFile: configurationFile); } #endregion @@ -582,7 +535,7 @@ private NamedPipeProcessMediator( { if (namedPipeServer == null) { - throw new PSArgumentNullException("namedPipeServer"); + throw new PSArgumentNullException(nameof(namedPipeServer)); } _namedPipeServer = namedPipeServer; @@ -590,17 +543,12 @@ 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); - // Flow impersonation if requested. - WindowsIdentity currentIdentity = null; - try - { - currentIdentity = WindowsIdentity.GetCurrent(); - } - catch (System.Security.SecurityException) { } - _windowsIdentityToImpersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? - currentIdentity : null; +#if !UNIX + // Flow impersonation as needed. + Utils.TryGetWindowsImpersonatedIdentity(out _windowsIdentityToImpersonate); +#endif } #endregion @@ -622,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) { } @@ -659,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); } @@ -699,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 @@ -712,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 } @@ -751,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 19e0c78ecd2..d98239c3c33 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerMethodExecutor.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Remoting.Server; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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. @@ -81,7 +81,7 @@ internal void AbortAllCalls() /// internal void ExecuteVoidMethod(RemoteHostMethodId methodId) { - ExecuteVoidMethod(methodId, Utils.EmptyArray()); + ExecuteVoidMethod(methodId, Array.Empty()); } /// @@ -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. @@ -112,7 +112,7 @@ internal void ExecuteVoidMethod(RemoteHostMethodId methodId, object[] parameters /// internal T ExecuteMethod(RemoteHostMethodId methodId) { - return ExecuteMethod(methodId, Utils.EmptyArray()); + return ExecuteMethod(methodId, Array.Empty()); } /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs index 765bd6c6658..ddeb81aae17 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs @@ -1,68 +1,72 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Security.Principal; -using System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Remoting; +using System.Collections.Generic; using System.Management.Automation.Internal; +using System.Management.Automation.Remoting; +using System.Management.Automation.Runspaces; +using System.Security.Principal; using System.Threading; +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// This class wraps a PowerShell object. It is used to function - /// as a server side powershell + /// as a server side powershell. /// internal class ServerPowerShellDriver { #region Private Members private bool _extraPowerShellAlreadyScheduled; - private PowerShell _extraPowerShell; // extra PowerShell at the server to be run after localPowerShell - private PSDataCollection _localPowerShellOutput; // output buffer for the local PowerShell - // that is associated with this - // powershell driver + + // extra PowerShell at the server to be run after localPowerShell + 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 readonly PSDataCollection _localPowerShellOutput; + + // if the remaining data has been sent to the client before sending state information + private readonly bool[] _datasent = new bool[2]; + + // sync object for synchronizing sending data to client + private readonly object _syncObject = new object(); + + // there is no input when this driver was created + private readonly bool _noInput; + private readonly bool _addToHistory; + + // the server remote host instance // associated with this powershell - // data structure handler object to handle all - // communications with the client - private bool[] _datasent = new bool[2]; // if the remaining data has been sent - // to the client before sending state - // information - private object _syncObject = new object(); // sync object for synchronizing sending - // data to client - private bool _noInput; // there is no input when this driver - // was created - private bool _addToHistory; - private ServerRemoteHost _remoteHost; // the server remote host instance - // associated with this powershell -#if !CORECLR // No ApartmentState In CoreCLR - private ApartmentState apartmentState; // apartment state for this powershell -#endif - - private IRSPDriverInvoke _psDriverInvoker; // Handles nested invocation of PS drivers. + private readonly ServerRemoteHost _remoteHost; + + // apartment state for this powershell + private readonly ApartmentState apartmentState; + + // Handles nested invocation of PS drivers. + private readonly IRSPDriverInvoke _psDriverInvoker; #endregion Private Members #region Constructors -#if !CORECLR /// - /// Default constructor for creating ServerPowerShellDrivers + /// Default constructor for creating ServerPowerShellDrivers. /// - /// decoded powershell object - /// extra pipeline to be run after completes - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id + /// Decoded powershell object. + /// Extra pipeline to be run after completes. + /// Whether there is input for this powershell. + /// The client powershell id. + /// The client runspacepool id. /// runspace pool driver /// which is creating this powershell driver - /// apartment state for this powershell + /// Apartment state for this powershell. /// host info using which the host for /// this powershell will be constructed - /// serialization options for the streams in this powershell + /// Serialization options for the streams in this powershell. /// /// true if the command is to be added to history list of the runspace. false, otherwise. /// @@ -78,80 +82,21 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel apartmentState, hostInfo, streamOptions, addToHistory, rsToUse, null) { } -#else - /// - /// Default constructor for creating ServerPowerShellDrivers - /// - /// decoded powershell object - /// extra pipeline to be run after completes - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id - /// runspace pool driver - /// which is creating this powershell driver - /// host info using which the host for - /// this powershell will be constructed - /// serialization options for the streams in this powershell - /// - /// true if the command is to be added to history list of the runspace. false, otherwise. - /// - /// - /// If not null, this Runspace will be used to invoke Powershell. - /// If null, the RunspacePool pointed by will be used. - /// - internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId, - Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, - HostInfo hostInfo, RemoteStreamOptions streamOptions, - bool addToHistory, Runspace rsToUse) - : this(powershell, extraPowerShell, noInput, clientPowerShellId, clientRunspacePoolId, runspacePoolDriver, - hostInfo, streamOptions, addToHistory, rsToUse, null) - { - } -#endif -#if CORECLR /// - /// Default constructor for creating ServerPowerShellDrivers + /// Default constructor for creating ServerPowerShellDrivers. /// - /// decoded powershell object - /// extra pipeline to be run after completes - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id + /// Decoded powershell object. + /// Extra pipeline to be run after completes. + /// Whether there is input for this powershell. + /// The client powershell id. + /// The client runspacepool id. /// runspace pool driver /// which is creating this powershell driver + /// Apartment state for this powershell. /// host info using which the host for /// this powershell will be constructed - /// serialization options for the streams in this powershell - /// - /// true if the command is to be added to history list of the runspace. false, otherwise. - /// - /// - /// If not null, this Runspace will be used to invoke Powershell. - /// If null, the RunspacePool pointed by will be used. - /// - /// - /// If not null, this is used as another source of output sent to the client. - /// - internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShell, bool noInput, Guid clientPowerShellId, - Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, - HostInfo hostInfo, RemoteStreamOptions streamOptions, - bool addToHistory, Runspace rsToUse, PSDataCollection output) -#else - /// - /// Default constructor for creating ServerPowerShellDrivers - /// - /// decoded powershell object - /// extra pipeline to be run after completes - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id - /// runspace pool driver - /// which is creating this powershell driver - /// apartment state for this powershell - /// host info using which the host for - /// this powershell will be constructed - /// serialization options for the streams in this powershell + /// Serialization options for the streams in this powershell. /// /// true if the command is to be added to history list of the runspace. false, otherwise. /// @@ -166,14 +111,11 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, ApartmentState apartmentState, HostInfo hostInfo, RemoteStreamOptions streamOptions, bool addToHistory, Runspace rsToUse, PSDataCollection output) -#endif { InstanceId = clientPowerShellId; RunspacePoolId = clientRunspacePoolId; RemoteStreamOptions = streamOptions; -#if !CORECLR // No ApartmentState In CoreCLR this.apartmentState = apartmentState; -#endif LocalPowerShell = powershell; _extraPowerShell = extraPowerShell; _localPowerShellOutput = new PSDataCollection(); @@ -188,7 +130,7 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel { InputCollection = new PSDataCollection(); InputCollection.ReleaseOnEnumeration = true; - InputCollection.IdleEvent += new EventHandler(HandleIdleEvent); + InputCollection.IdleEvent += HandleIdleEvent; } RegisterPipelineOutputEventHandlers(_localPowerShellOutput); @@ -208,7 +150,7 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel RegisterDataStructureHandlerEventHandlers(DataStructureHandler); // set the runspace pool and invoke this powershell - if (null != rsToUse) + if (rsToUse != null) { LocalPowerShell.Runspace = rsToUse; if (extraPowerShell != null) @@ -246,24 +188,24 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel #region Internal Methods /// - /// Input collection sync object + /// Input collection sync object. /// internal PSDataCollection InputCollection { get; } /// - /// Local PowerShell instance + /// Local PowerShell instance. /// internal PowerShell LocalPowerShell { get; } /// /// Instance id by which this powershell driver is /// identified. This is the same as the id of the - /// powershell on the client side + /// powershell on the client side. /// internal Guid InstanceId { get; } /// - /// Serialization options for the streams in this powershell + /// Serialization options for the streams in this powershell. /// internal RemoteStreamOptions RemoteStreamOptions { get; } @@ -272,13 +214,13 @@ internal ServerPowerShellDriver(PowerShell powershell, PowerShell extraPowerShel /// this object. This is the same as the id of /// the runspace pool at the client side which /// is associated with the powershell on the - /// client side + /// client side. /// internal Guid RunspacePoolId { get; } /// /// ServerPowerShellDataStructureHandler associated with this - /// powershell driver + /// powershell driver. /// internal ServerPowerShellDataStructureHandler DataStructureHandler { get; } @@ -291,9 +233,7 @@ private PSInvocationSettings PrepInvoke(bool startMainPowerShell) } PSInvocationSettings settings = new PSInvocationSettings(); -#if !CORECLR // No ApartmentState In CoreCLR settings.ApartmentState = apartmentState; -#endif settings.Host = _remoteHost; // Flow the impersonation policy to pipeline execution thread @@ -337,7 +277,7 @@ private IAsyncResult Start(bool startMainPowerShell) } /// - /// invokes the powershell asynchronously + /// Invokes the powershell asynchronously. /// internal IAsyncResult Start() { @@ -350,7 +290,8 @@ internal IAsyncResult Start() /// commands that sets debugger state but doesn't run any command /// on the server runspace. /// - internal void RunNoOpCommand() + /// The output from preprocessing that we want to send to the client. + internal void RunNoOpCommand(IReadOnlyCollection output) { if (LocalPowerShell != null) { @@ -361,6 +302,14 @@ internal void RunNoOpCommand() new PSInvocationStateInfo( PSInvocationState.Running, null)); + foreach (var item in output) + { + if (item != null) + { + _localPowerShellOutput.Add(PSObject.AsPSObject(item)); + } + } + LocalPowerShell.SetStateChanged( new PSInvocationStateInfo( PSInvocationState.Completed, null)); @@ -460,9 +409,9 @@ private void UnregisterPipelineOutputEventHandlers(PSDataCollection pi /// /// Handle state changed information from PowerShell - /// and send it to the client + /// and send it to the client. /// - /// sender of this event + /// Sender of this event. /// arguments describing state changed /// information for this powershell private void HandlePowerShellInvocationStateChanged(object sender, @@ -478,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. @@ -514,6 +466,7 @@ private void HandlePowerShellInvocationStateChanged(object sender, { UnregisterPowerShellEventHandlers(_extraPowerShell); } + UnregisterDataStructureHandlerEventHandlers(DataStructureHandler); UnregisterPipelineOutputEventHandlers(_localPowerShellOutput); @@ -521,9 +474,10 @@ private void HandlePowerShellInvocationStateChanged(object sender, // be disposed as raising the events is // not done towards the end. Need to fix // powershell in order to get this enabled - //localPowerShell.Dispose(); + // localPowerShell.Dispose(); } } + break; case PSInvocationState.Stopping: @@ -531,15 +485,16 @@ private void HandlePowerShellInvocationStateChanged(object sender, // abort all pending host calls _remoteHost.ServerMethodExecutor.AbortAllCalls(); } + break; } } /// - /// Handles DataAdded event from the Output of the powershell + /// Handles DataAdded event from the Output of the powershell. /// - /// sender of this information - /// arguments describing this event + /// Sender of this information. + /// Arguments describing this event. private void HandleOutputDataAdded(object sender, DataAddedEventArgs e) { int index = e.Index; @@ -557,14 +512,14 @@ private void HandleOutputDataAdded(object sender, DataAddedEventArgs e) // send the output data to the client DataStructureHandler.SendOutputDataToClient(data); } - } // lock .. + } } /// - /// Handles DataAdded event from Error of the PowerShell + /// Handles DataAdded event from Error of the PowerShell. /// - /// sender of this event - /// arguments describing this event + /// Sender of this event. + /// Arguments describing this event. private void HandleErrorDataAdded(object sender, DataAddedEventArgs e) { int index = e.Index; @@ -582,14 +537,14 @@ private void HandleErrorDataAdded(object sender, DataAddedEventArgs e) // send the error record to the client DataStructureHandler.SendErrorRecordToClient(errorRecord); } - } // lock ... + } } /// - /// Handles DataAdded event from Progress of PowerShell + /// Handles DataAdded event from Progress of PowerShell. /// - /// sender of this information, unused - /// arguments describing this event + /// Sender of this information, unused. + /// Arguments describing this event. private void HandleProgressAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -607,14 +562,14 @@ private void HandleProgressAdded(object sender, DataAddedEventArgs eventArgs) // send the output data to the client DataStructureHandler.SendProgressRecordToClient(data); } - } // lock .. + } } /// - /// Handles DataAdded event from Warning of PowerShell + /// Handles DataAdded event from Warning of PowerShell. /// - /// sender of this information, unused - /// arguments describing this event + /// Sender of this information, unused. + /// Arguments describing this event. private void HandleWarningAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -632,14 +587,14 @@ private void HandleWarningAdded(object sender, DataAddedEventArgs eventArgs) // send the output data to the client DataStructureHandler.SendWarningRecordToClient(data); } - } // lock .. + } } /// - /// Handles DataAdded from Verbose of PowerShell + /// Handles DataAdded from Verbose of PowerShell. /// - /// sender of this information, unused - /// sender of this information + /// Sender of this information, unused. + /// Sender of this information. private void HandleVerboseAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -657,14 +612,14 @@ private void HandleVerboseAdded(object sender, DataAddedEventArgs eventArgs) // send the output data to the client DataStructureHandler.SendVerboseRecordToClient(data); } - } // lock .. + } } /// - /// Handles DataAdded from Debug of PowerShell + /// Handles DataAdded from Debug of PowerShell. /// - /// sender of this information, unused - /// sender of this information + /// Sender of this information, unused. + /// Sender of this information. private void HandleDebugAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -682,14 +637,14 @@ private void HandleDebugAdded(object sender, DataAddedEventArgs eventArgs) // send the output data to the client DataStructureHandler.SendDebugRecordToClient(data); } - } // lock .. + } } /// - /// Handles DataAdded from Information of PowerShell + /// Handles DataAdded from Information of PowerShell. /// - /// sender of this information, unused - /// sender of this information + /// Sender of this information, unused. + /// Sender of this information. private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs) { int index = eventArgs.Index; @@ -707,12 +662,12 @@ private void HandleInformationAdded(object sender, DataAddedEventArgs eventArgs) // send the output data to the client DataStructureHandler.SendInformationRecordToClient(data); } - } // lock .. + } } /// /// Send the remaining output and error information to - /// client + /// client. /// /// This method should be called before /// sending the state information. The client will @@ -738,14 +693,16 @@ private void SendRemainingData() PSObject data = _localPowerShellOutput[i]; DataStructureHandler.SendOutputDataToClient(data); } + _localPowerShellOutput.Clear(); - //foreach (ErrorRecord errorRecord in localPowerShell.Error) + // foreach (ErrorRecord errorRecord in localPowerShell.Error) for (int i = 0; i < LocalPowerShell.Streams.Error.Count; i++) { ErrorRecord errorRecord = LocalPowerShell.Streams.Error[i]; DataStructureHandler.SendErrorRecordToClient(errorRecord); } + LocalPowerShell.Streams.Error.Clear(); } finally @@ -759,10 +716,10 @@ private void SendRemainingData() } /// - /// Stop the local powershell + /// Stop the local powershell. /// - /// sender of this event, unused - /// unused + /// Sender of this event, unused. + /// Unused. private void HandleStopReceived(object sender, EventArgs eventArgs) { do // false loop @@ -811,10 +768,10 @@ private void HandleStopReceived(object sender, EventArgs eventArgs) } /// - /// Add input to the local powershell's input collection + /// Add input to the local powershell's input collection. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleInputReceived(object sender, RemoteDataEventArgs eventArgs) { // This can be called in pushed runspace scenarios for error reporting (pipeline stopped). @@ -826,10 +783,10 @@ private void HandleInputReceived(object sender, RemoteDataEventArgs even } /// - /// Close the input collection of the local powershell + /// Close the input collection of the local powershell. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleInputEndReceived(object sender, EventArgs eventArgs) { // This can be called in pushed runspace scenarios for error reporting (pipeline stopped). @@ -842,27 +799,25 @@ private void HandleInputEndReceived(object sender, EventArgs eventArgs) private void HandleSessionConnected(object sender, EventArgs eventArgs) { - //Close input if its active. no need to synchronize as input stream would have already been processed + // 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(); } /// - /// Handle a host message response received + /// Handle a host message response received. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleHostResponseReceived(object sender, RemoteDataEventArgs eventArgs) { _remoteHost.ServerMethodExecutor.HandleRemoteHostResponseFromClient(eventArgs.Data); } /// - /// Handles the PSDataCollection idle event + /// Handles the PSDataCollection idle event. /// /// /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs index 5cfe0c0e538..71dda0d0b7a 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Remoting.Server; using System.Management.Automation.Runspaces; -using System.Globalization; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -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. @@ -43,9 +43,9 @@ internal class ServerRemoteHost : PSHost, IHostSupportsInteractiveSession protected AbstractServerTransportManager _transportManager; /// - /// ServerDriverRemoteHost + /// ServerDriverRemoteHost. /// - private ServerDriverRemoteHost _serverDriverRemoteHost; + private readonly ServerDriverRemoteHost _serverDriverRemoteHost; #endregion @@ -151,16 +151,6 @@ public virtual bool IsRunspacePushed /// internal HostInfo HostInfo { get; } - /// - /// Allows a push runspace on this remote server host instance, regardless of - /// transport being used. - /// - internal virtual bool AllowPushRunspace - { - get { return (_serverDriverRemoteHost != null) ? _serverDriverRemoteHost.AllowPushRunspace : false; } - set { if (_serverDriverRemoteHost != null) { _serverDriverRemoteHost.AllowPushRunspace = value; } } - } - #endregion #region Method Overrides @@ -311,7 +301,7 @@ internal ServerDriverRemoteHost( #region Overrides /// - /// True if runspace is pushed + /// True if runspace is pushed. /// public override bool IsRunspacePushed { @@ -322,23 +312,11 @@ public override bool IsRunspacePushed } /// - /// Push runspace to use for remote command execution + /// Push runspace to use for remote command execution. /// - /// RemoteRunspace + /// RemoteRunspace. public override void PushRunspace(Runspace runspace) { - // Double session hop is currently allowed only for WSMan (non-OutOfProc) sessions, where - // the second session is either through a named pipe or hyperV socket connection. - if (!AllowPushRunspace && - ((_transportManager is OutOfProcessServerSessionTransportManager) || - !(runspace.ConnectionInfo is NamedPipeConnectionInfo || - runspace.ConnectionInfo is VMConnectionInfo || - runspace.ConnectionInfo is ContainerConnectionInfo)) - ) - { - throw new PSNotSupportedException(); - } - if (_debugger == null) { throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNoDebuggerToPush); @@ -349,16 +327,15 @@ runspace.ConnectionInfo is VMConnectionInfo || 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); @@ -369,16 +346,13 @@ runspace.ConnectionInfo is VMConnectionInfo || } /// - /// Pop runspace + /// Pop runspace. /// public override void PopRunspace() { if (_pushedRunspace != null) { - if (_debugger != null) - { - _debugger.PopDebugger(); - } + _debugger?.PopDebugger(); if (_hostSupportsPSEdit) { @@ -389,6 +363,7 @@ public override void PopRunspace() { _pushedRunspace.Close(); } + _pushedRunspace = null; } } @@ -398,32 +373,23 @@ public override void PopRunspace() #region Properties /// - /// Server Debugger + /// Server Debugger. /// internal Debugger ServerDebugger { get { return _debugger; } + set { _debugger = value as ServerRemoteDebugger; } } /// - /// Pushed runspace or null + /// Pushed runspace or null. /// internal Runspace PushedRunspace { get { return _pushedRunspace; } } - /// - /// Allows a push runspace on this remote server host instance, regardless of - /// transport being used. - /// - internal override bool AllowPushRunspace - { - get; - set; - } - /// /// When true will propagate pop call to client after popping runspace from this /// host. Used for OutOfProc remote sessions in a restricted (pushed) remote runspace, @@ -441,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; @@ -461,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)) @@ -487,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 9f5abf2238e..34c10e0a9a0 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Host; + using Dbg = System.Management.Automation.Diagnostics; // Stops compiler from warning about unknown warnings #pragma warning disable 1634, 1691 @@ -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,20 +343,15 @@ 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("source"); - } + ArgumentNullException.ThrowIfNull(source); + return source.Length; } // more performant than the default implementation provided by PSHostRawUserInterface public override int LengthInBufferCells(string source, int offset) { - if (source == null) - { - throw new ArgumentNullException("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 663b76b342e..76265089677 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Management.Automation.Host; using System.Security; + using Dbg = System.Management.Automation.Diagnostics; using InternalHostUserInterface = System.Management.Automation.Internal.Host.InternalHostUserInterface; @@ -20,7 +20,7 @@ internal class ServerRemoteHostUserInterface : PSHostUserInterface, IHostUISuppo /// /// Server method executor. /// - private ServerMethodExecutor _serverMethodExecutor; + private readonly ServerMethodExecutor _serverMethodExecutor; /// /// Constructor for ServerRemoteHostUserInterface. @@ -64,7 +64,7 @@ public override int PromptForChoice(string caption, string message, Collection - /// Prompt for choice. User can select multiple choices + /// Prompt for choice. User can select multiple choices. /// /// /// @@ -122,6 +122,7 @@ public override Dictionary Prompt(string caption, string messa /// public override void Write(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.Write1, new object[] { message }); } @@ -130,6 +131,7 @@ public override void Write(string message) /// public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.Write2, new object[] { foregroundColor, backgroundColor, message }); } @@ -146,6 +148,7 @@ public override void WriteLine() /// public override void WriteLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteLine2, new object[] { message }); } @@ -154,6 +157,7 @@ public override void WriteLine(string message) /// public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteLine3, new object[] { foregroundColor, backgroundColor, message }); } @@ -162,6 +166,7 @@ public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgr /// public override void WriteErrorLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteErrorLine, new object[] { message }); } @@ -170,6 +175,7 @@ public override void WriteErrorLine(string message) /// public override void WriteDebugLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteDebugLine, new object[] { message }); } @@ -186,6 +192,7 @@ public override void WriteProgress(long sourceId, ProgressRecord record) /// public override void WriteVerboseLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteVerboseLine, new object[] { message }); } @@ -194,6 +201,7 @@ public override void WriteVerboseLine(string message) /// public override void WriteWarningLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteWarningLine, new object[] { message }); } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs index 4d2661c53e9..11761c25af8 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs @@ -1,18 +1,18 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Remoting; -using System.Management.Automation.Runspaces; using System.Management.Automation.Remoting.Server; +using System.Management.Automation.Runspaces; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// Handles all data structure handler communication with the client - /// runspace pool + /// runspace pool. /// internal class ServerRunspacePoolDataStructureHandler { @@ -20,7 +20,7 @@ internal class ServerRunspacePoolDataStructureHandler /// /// Constructor which takes a server runspace pool driver and - /// creates an associated ServerRunspacePoolDataStructureHandler + /// creates an associated ServerRunspacePoolDataStructureHandler. /// /// /// @@ -36,10 +36,10 @@ internal ServerRunspacePoolDataStructureHandler(ServerRunspacePoolDriver driver, #region Data Structure Handler Methods /// - /// Send a message with application private data to the client + /// Send a message with application private data to the client. /// - /// applicationPrivateData to send - /// server capability negotiated during initial exchange of remoting messages / session capabilities of client and server + /// ApplicationPrivateData to send. + /// Server capability negotiated during initial exchange of remoting messages / session capabilities of client and server. internal void SendApplicationPrivateDataToClient(PSPrimitiveDictionary applicationPrivateData, RemoteSessionCapability serverCapability) { // make server's PSVersionTable available to the client using ApplicationPrivateData @@ -61,9 +61,9 @@ internal void SendApplicationPrivateDataToClient(PSPrimitiveDictionary applicati } /// - /// Send a message with the RunspacePoolStateInfo to the client + /// Send a message with the RunspacePoolStateInfo to the client. /// - /// state info to send + /// State info to send. internal void SendStateInfoToClient(RunspacePoolStateInfo stateInfo) { RemoteDataObject data = RemotingEncoder.GenerateRunspacePoolStateInfo( @@ -73,9 +73,9 @@ internal void SendStateInfoToClient(RunspacePoolStateInfo stateInfo) } /// - /// Send a message with the PSEventArgs to the client + /// Send a message with the PSEventArgs to the client. /// - /// event to send + /// Event to send. internal void SendPSEventArgsToClient(PSEventArgs e) { RemoteDataObject data = RemotingEncoder.GeneratePSEventArgs(_clientRunspacePoolId, e); @@ -84,8 +84,8 @@ internal void SendPSEventArgsToClient(PSEventArgs e) } /// - /// called when session is connected from a new client - /// call into the sessionconnect handlers for each associated powershell dshandler + /// Called when session is connected from a new client + /// call into the sessionconnect handlers for each associated powershell dshandler. /// internal void ProcessConnect() { @@ -94,6 +94,7 @@ internal void ProcessConnect() { dsHandlers = new List(_associatedShells.Values); } + foreach (var dsHandler in dsHandlers) { dsHandler.ProcessConnect(); @@ -102,14 +103,14 @@ internal void ProcessConnect() /// /// Process the data received from the runspace pool on - /// the server + /// the server. /// - /// data received + /// Data received. internal void ProcessReceivedData(RemoteDataObject receivedData) { if (receivedData == null) { - throw PSTraceSource.NewArgumentNullException("receivedData"); + throw PSTraceSource.NewArgumentNullException(nameof(receivedData)); } Dbg.Assert(receivedData.TargetInterface == RemotingTargetInterface.RunspacePool, @@ -124,6 +125,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) CreateAndInvokePowerShell.SafeInvoke(this, new RemoteDataEventArgs>(receivedData)); } + break; case RemotingDataType.GetCommandMetadata: @@ -133,6 +135,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) GetCommandMetadata.SafeInvoke(this, new RemoteDataEventArgs>(receivedData)); } + break; case RemotingDataType.RemoteRunspaceHostResponseData: @@ -142,12 +145,13 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) RemoteHostResponse remoteHostResponse = RemoteHostResponse.Decode(receivedData.Data); - //part of host message robustness algo. Now the host response is back, report to transport that + // part of host message robustness algo. Now the host response is back, report to transport that // execution status is back to running _transportManager.ReportExecutionStatusAsRunning(); HostResponseReceived.SafeInvoke(this, new RemoteDataEventArgs(remoteHostResponse)); } + break; case RemotingDataType.SetMaxRunspaces: @@ -157,6 +161,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) SetMaxRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; case RemotingDataType.SetMinRunspaces: @@ -166,6 +171,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) SetMinRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; case RemotingDataType.AvailableRunspaces: @@ -175,6 +181,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) GetAvailableRunspacesReceived.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; case RemotingDataType.ResetRunspaceState: @@ -184,18 +191,19 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) ResetRunspaceState.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; - } // switch... + } } /// - /// Creates a powershell data structure handler from this runspace pool + /// Creates a powershell data structure handler from this runspace pool. /// - /// powershell instance id - /// runspace pool id - /// remote stream options - /// local PowerShell object - /// ServerPowerShellDataStructureHandler + /// Powershell instance id. + /// Runspace pool id. + /// Remote stream options. + /// Local PowerShell object. + /// ServerPowerShellDataStructureHandler. internal ServerPowerShellDataStructureHandler CreatePowerShellDataStructureHandler( Guid instanceId, Guid runspacePoolId, RemoteStreamOptions remoteStreamOptions, PowerShell localPowerShell) { @@ -216,7 +224,7 @@ internal ServerPowerShellDataStructureHandler CreatePowerShellDataStructureHandl _associatedShells.Add(dsHandler.PowerShellId, dsHandler); } - dsHandler.RemoveAssociation += new EventHandler(HandleRemoveAssociation); + dsHandler.RemoveAssociation += HandleRemoveAssociation; return dsHandler; } @@ -243,13 +251,14 @@ internal ServerPowerShellDataStructureHandler GetPowerShellDataStructureHandler( } } } + return null; } /// - /// dispatch the message to the associated powershell data structure handler + /// Dispatch the message to the associated powershell data structure handler. /// - /// message to dispatch + /// Message to dispatch. internal void DispatchMessageToPowerShell(RemoteDataObject rcvdData) { ServerPowerShellDataStructureHandler dsHandler = @@ -257,18 +266,15 @@ 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); } /// /// Send the specified response to the client. The client call will - /// be blocked on the same + /// be blocked on the same. /// - /// call id on the client - /// response to send + /// Call id on the client. + /// Response to send. internal void SendResponseToClient(long callId, object response) { RemoteDataObject message = @@ -283,6 +289,7 @@ internal void SendResponseToClient(long callId, object response) internal TypeTable TypeTable { get { return _transportManager.TypeTable; } + set { _transportManager.TypeTable = value; } } @@ -292,36 +299,36 @@ internal TypeTable TypeTable /// /// This event is raised whenever there is a request from the - /// client to create a powershell on the server and invoke it + /// client to create a powershell on the server and invoke it. /// internal event EventHandler>> CreateAndInvokePowerShell; /// /// This event is raised whenever there is a request from the - /// client to run command discovery pipeline + /// client to run command discovery pipeline. /// internal event EventHandler>> GetCommandMetadata; /// - /// This event is raised when a host call response is received + /// This event is raised when a host call response is received. /// internal event EventHandler> HostResponseReceived; /// /// This event is raised when there is a request to modify the - /// maximum runspaces in the runspace pool + /// maximum runspaces in the runspace pool. /// internal event EventHandler> SetMaxRunspacesReceived; /// /// This event is raised when there is a request to modify the - /// minimum runspaces in the runspace pool + /// minimum runspaces in the runspace pool. /// internal event EventHandler> SetMinRunspacesReceived; /// /// This event is raised when there is a request to get the - /// available runspaces in the runspace pool + /// available runspaces in the runspace pool. /// internal event EventHandler> GetAvailableRunspacesReceived; @@ -337,25 +344,25 @@ internal TypeTable TypeTable /// /// Send the data specified as a RemoteDataObject asynchronously - /// to the runspace pool on the remote session + /// to the runspace pool on the remote session. /// - /// data to send + /// 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) { - Dbg.Assert(null != data, "Cannot send null object."); + Dbg.Assert(data != null, "Cannot send null object."); _transportManager.SendDataToClient(data, true); } /// /// Get the associated powershell data structure handler for the specified - /// powershell id + /// powershell id. /// /// powershell id for the /// powershell data structure handler - /// ServerPowerShellDataStructureHandler + /// ServerPowerShellDataStructureHandler. internal ServerPowerShellDataStructureHandler GetAssociatedPowerShellDataStructureHandler (Guid clientPowerShellId) { @@ -370,14 +377,15 @@ internal ServerPowerShellDataStructureHandler GetAssociatedPowerShellDataStructu dsHandler = null; } } + return dsHandler; } /// - /// Remove the association of the powershell from the runspace pool + /// Remove the association of the powershell from the runspace pool. /// - /// sender of this event - /// unused + /// Sender of this event. + /// Unused. private void HandleRemoveAssociation(object sender, EventArgs e) { Dbg.Assert(sender is ServerPowerShellDataStructureHandler, @"sender of the event @@ -398,16 +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 Dictionary _associatedShells + private readonly AbstractServerSessionTransportManager _transportManager; + + 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 @@ -415,7 +425,7 @@ private Dictionary _associatedShells /// /// Handles all PowerShell data structure handler communication - /// with the client side PowerShell + /// with the client side PowerShell. /// internal class ServerPowerShellDataStructureHandler { @@ -423,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 @@ -435,13 +445,13 @@ internal class ServerPowerShellDataStructureHandler /// /// Default constructor for creating ServerPowerShellDataStructureHandler - /// instance + /// instance. /// - /// powershell instance id - /// runspace pool id - /// remote stream options - /// transport manager - /// local powershell object + /// Powershell instance id. + /// Runspace pool id. + /// Remote stream options. + /// Transport manager. + /// Local powershell object. internal ServerPowerShellDataStructureHandler(Guid instanceId, Guid runspacePoolId, RemoteStreamOptions remoteStreamOptions, AbstractServerTransportManager transportManager, PowerShell localPowerShell) { @@ -453,8 +463,7 @@ internal ServerPowerShellDataStructureHandler(Guid instanceId, Guid runspacePool if (localPowerShell != null) { - localPowerShell.RunspaceAssigned += - new EventHandler>(LocalPowerShell_RunspaceAssigned); + localPowerShell.RunspaceAssigned += LocalPowerShell_RunspaceAssigned; } } @@ -481,7 +490,7 @@ internal void Prepare() } /// - /// Send the state information to the client + /// Send the state information to the client. /// /// state information to be /// sent to the client @@ -509,9 +518,9 @@ internal void SendStateChangedInformationToClient(PSInvocationStateInfo } /// - /// Send the output data to the client + /// Send the output data to the client. /// - /// data to send + /// Data to send. internal void SendOutputDataToClient(PSObject data) { SendDataAsync(RemotingEncoder.GeneratePowerShellOutput(data, @@ -519,9 +528,9 @@ internal void SendOutputDataToClient(PSObject data) } /// - /// Send the error record to client + /// Send the error record to client. /// - /// error record to send + /// Error record to send. internal void SendErrorRecordToClient(ErrorRecord errorRecord) { errorRecord.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToErrorRecord) != 0; @@ -531,9 +540,9 @@ internal void SendErrorRecordToClient(ErrorRecord errorRecord) } /// - /// Send the specified warning record to client + /// Send the specified warning record to client. /// - /// warning record + /// Warning record. internal void SendWarningRecordToClient(WarningRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToWarningRecord) != 0; @@ -543,9 +552,9 @@ internal void SendWarningRecordToClient(WarningRecord record) } /// - /// Send the specified debug record to client + /// Send the specified debug record to client. /// - /// debug record + /// Debug record. internal void SendDebugRecordToClient(DebugRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToDebugRecord) != 0; @@ -555,9 +564,9 @@ internal void SendDebugRecordToClient(DebugRecord record) } /// - /// Send the specified verbose record to client + /// Send the specified verbose record to client. /// - /// warning record + /// Warning record. internal void SendVerboseRecordToClient(VerboseRecord record) { record.SerializeExtendedInfo = (_streamSerializationOptions & RemoteStreamOptions.AddInvocationInfoToVerboseRecord) != 0; @@ -567,9 +576,9 @@ internal void SendVerboseRecordToClient(VerboseRecord record) } /// - /// Send the specified progress record to client + /// Send the specified progress record to client. /// - /// progress record + /// Progress record. internal void SendProgressRecordToClient(ProgressRecord record) { SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( @@ -577,9 +586,9 @@ internal void SendProgressRecordToClient(ProgressRecord record) } /// - /// Send the specified information record to client + /// Send the specified information record to client. /// - /// information record + /// Information record. internal void SendInformationRecordToClient(InformationRecord record) { SendDataAsync(RemotingEncoder.GeneratePowerShellInformational( @@ -587,10 +596,10 @@ internal void SendInformationRecordToClient(InformationRecord record) } /// - /// called when session is connected from a new client + /// Called when session is connected from a new client /// calls into observers of this event. /// observers include corresponding driver that shutdown - /// input stream is present + /// input stream is present. /// internal void ProcessConnect() { @@ -599,14 +608,14 @@ internal void ProcessConnect() /// /// Process the data received from the powershell on - /// the client + /// the client. /// - /// data received + /// Data received. internal void ProcessReceivedData(RemoteDataObject receivedData) { if (receivedData == null) { - throw PSTraceSource.NewArgumentNullException("receivedData"); + throw PSTraceSource.NewArgumentNullException(nameof(receivedData)); } Dbg.Assert(receivedData.TargetInterface == RemotingTargetInterface.PowerShell, @@ -620,6 +629,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) "ServerPowerShellDriver should subscribe to all data structure handler events"); StopPowerShellReceived.SafeInvoke(this, EventArgs.Empty); } + break; case RemotingDataType.PowerShellInput: @@ -628,6 +638,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) "ServerPowerShellDriver should subscribe to all data structure handler events"); InputReceived.SafeInvoke(this, new RemoteDataEventArgs(receivedData.Data)); } + break; case RemotingDataType.PowerShellInputEnd: @@ -636,6 +647,7 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) "ServerPowerShellDriver should subscribe to all data structure handler events"); InputEndReceived.SafeInvoke(this, EventArgs.Empty); } + break; case RemotingDataType.RemotePowerShellHostResponseData: @@ -645,21 +657,22 @@ internal void ProcessReceivedData(RemoteDataObject receivedData) RemoteHostResponse remoteHostResponse = RemoteHostResponse.Decode(receivedData.Data); - //part of host message robustness algo. Now the host response is back, report to transport that + // part of host message robustness algo. Now the host response is back, report to transport that // execution status is back to running _transportManager.ReportExecutionStatusAsRunning(); HostResponseReceived.SafeInvoke(this, new RemoteDataEventArgs(remoteHostResponse)); } + break; - } // switch ... + } } /// /// Raise a remove association event. This is raised /// when the powershell has gone into a terminal state /// and the runspace pool need not maintain any further - /// associations + /// associations. /// internal void RaiseRemoveAssociationEvent() { @@ -704,37 +717,37 @@ internal ServerRemoteHost GetHostAssociatedWithPowerShell( #region Data Structure Handler events /// - /// this event is raised when the state of associated + /// This event is raised when the state of associated /// powershell is terminal and the runspace pool has - /// to detach the association + /// to detach the association. /// internal event EventHandler RemoveAssociation; /// - /// this event is raised when the a message to stop the - /// powershell is received from the client + /// This event is raised when the a message to stop the + /// powershell is received from the client. /// internal event EventHandler StopPowerShellReceived; /// /// This event is raised when an input object is received - /// from the client + /// from the client. /// internal event EventHandler> InputReceived; /// /// This event is raised when end of input is received from - /// the client + /// the client. /// internal event EventHandler InputEndReceived; /// - /// raised when server session is connected from a new client + /// Raised when server session is connected from a new client. /// internal event EventHandler OnSessionConnected; /// - /// This event is raised when a host response is received + /// This event is raised when a host response is received. /// internal event EventHandler> HostResponseReceived; @@ -743,7 +756,7 @@ internal ServerRemoteHost GetHostAssociatedWithPowerShell( #region Internal Methods /// - /// client powershell id + /// Client powershell id. /// internal Guid PowerShellId { @@ -768,15 +781,15 @@ internal Runspace RunspaceUsedToInvokePowerShell /// /// Send the data specified as a RemoteDataObject asynchronously - /// to the runspace pool on the remote session + /// to the runspace pool on the remote session. /// - /// data to send + /// 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) { - Dbg.Assert(null != data, "Cannot send null object."); + Dbg.Assert(data != null, "Cannot send null object."); // this is from a command execution..let transport manager collect // as much data as possible and send bigger buffer to client. _transportManager.SendDataToClient(data, false); diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index 7cf5643e2c9..a5e0da4968e 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -1,22 +1,26 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; -using System.Management.Automation.Runspaces; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Remoting.Server; -using Dbg = System.Management.Automation.Diagnostics; -using System.Threading; +using System.Management.Automation.Runspaces; using System.Management.Automation.Security; -using System.Diagnostics; +#if !UNIX using System.Security.Principal; +#endif +using System.Threading; + +using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { @@ -24,16 +28,20 @@ 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 - /// as a server side runspacepool + /// as a server side runspacepool. /// internal class ServerRunspacePoolDriver : IRSPDriverInvoke { @@ -41,8 +49,11 @@ internal class ServerRunspacePoolDriver : IRSPDriverInvoke // local runspace pool at the server + // Optional initial location of the PowerShell session + 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; @@ -54,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. @@ -93,72 +104,31 @@ private Dictionary _associatedShells #region Constructors - -#if CORECLR // No ApartmentState In CoreCLR /// - /// Creates the runspace pool driver + /// Initializes a new instance of the runspace pool driver. /// - /// client runspace pool id to associate - /// transport manager associated with this - /// runspace pool driver - /// maximum runspaces to open - /// minimum runspaces to open - /// threading options for the runspaces in the pool - /// host information about client side host - /// - /// Contains: + /// Client runspace pool id to associate. + /// Transport manager associated with this + /// runspace pool driver. + /// Maximum runspaces to open. + /// Minimum runspaces to open. + /// Threading options for the runspaces in the pool. + /// Apartment state for the runspaces in the pool. + /// Host information about client side host. + /// Contains: /// 1. Script to run after a RunspacePool/Runspace is created in this session. /// For RunspacePool case, every newly created Runspace (in the pool) will run /// this script. /// 2. ThreadOptions for RunspacePool/Runspace /// 3. ThreadApartment for RunspacePool/Runspace /// - /// configuration of the runspace - /// application private data - /// True if the driver is being created by an administrator - /// server capability reported to the client during negotiation (not the actual capability) - /// Client PowerShell version. - /// Optional endpoint configuration name to create a pushed configured runspace. - internal ServerRunspacePoolDriver( - Guid clientRunspacePoolId, - int minRunspaces, - int maxRunspaces, - PSThreadOptions threadOptions, - HostInfo hostInfo, - InitialSessionState initialSessionState, - PSPrimitiveDictionary applicationPrivateData, - ConfigurationDataFromXML configData, - AbstractServerSessionTransportManager transportManager, - bool isAdministrator, - RemoteSessionCapability serverCapability, - Version psClientVersion, - string configurationName) -#else - /// - /// Creates the runspace pool driver - /// - /// client runspace pool id to associate - /// transport manager associated with this - /// runspace pool driver - /// maximum runspaces to open - /// minimum runspaces to open - /// threading options for the runspaces in the pool - /// apartment state for the runspaces in the pool - /// host information about client side host - /// - /// Contains: - /// 1. Script to run after a RunspacePool/Runspace is created in this session. - /// For RunspacePool case, every newly created Runspace (in the pool) will run - /// this script. - /// 2. ThreadOptions for RunspacePool/Runspace - /// 3. ThreadApartment for RunspacePool/Runspace - /// - /// configuration of the runspace - /// application private data - /// True if the driver is being created by an administrator - /// server capability reported to the client during negotiation (not the actual capability) + /// Configuration of the runspace. + /// Application private data. + /// True if the driver is being created by an administrator. + /// Server capability reported to the client during negotiation (not the actual capability). /// Client PowerShell version. /// Optional endpoint configuration name to create a pushed configured runspace. + /// Optional initial location of the powershell. internal ServerRunspacePoolDriver( Guid clientRunspacePoolId, int minRunspaces, @@ -173,20 +143,21 @@ internal ServerRunspacePoolDriver( bool isAdministrator, RemoteSessionCapability serverCapability, Version psClientVersion, - string configurationName) -#endif + string configurationName, + string initialLocation) { - Dbg.Assert(null != configData, "ConfigurationData cannot be null"); + Dbg.Assert(configData != null, "ConfigurationData cannot be null"); _serverCapability = serverCapability; _clientPSVersion = psClientVersion; _configurationName = configurationName; + _initialLocation = initialLocation; // Create a new server host and associate for host call // integration - _remoteHost = new ServerDriverRemoteHost(clientRunspacePoolId, - Guid.Empty, hostInfo, transportManager, null); + _remoteHost = new ServerDriverRemoteHost( + clientRunspacePoolId, Guid.Empty, hostInfo, transportManager, null); _configData = configData; _applicationPrivateData = applicationPrivateData; @@ -197,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; @@ -212,9 +183,8 @@ internal ServerRunspacePoolDriver( RunspacePool.ThreadOptions = threadOptions; } -#if !CORECLR // No ApartmentState In CoreCLR // 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) { @@ -224,7 +194,6 @@ internal ServerRunspacePoolDriver( { RunspacePool.ApartmentState = apartmentState; } -#endif // If we have a runspace pool with a single runspace then we can run nested pipelines on // on it in a single pipeline invoke thread. @@ -239,30 +208,21 @@ internal ServerRunspacePoolDriver( DataStructureHandler = new ServerRunspacePoolDataStructureHandler(this, transportManager); // handle the StateChanged event of the runspace pool - RunspacePool.StateChanged += - new EventHandler(HandleRunspacePoolStateChanged); + RunspacePool.StateChanged += HandleRunspacePoolStateChanged; // listen for events on the runspace pool - RunspacePool.ForwardEvent += - new EventHandler(HandleRunspacePoolForwardEvent); + RunspacePool.ForwardEvent += HandleRunspacePoolForwardEvent; RunspacePool.RunspaceCreated += HandleRunspaceCreated; // register for all the events from the data structure handler - DataStructureHandler.CreateAndInvokePowerShell += - new EventHandler>>(HandleCreateAndInvokePowerShell); - DataStructureHandler.GetCommandMetadata += - new EventHandler>>(HandleGetCommandMetadata); - DataStructureHandler.HostResponseReceived += - new EventHandler>(HandleHostResponseReceived); - DataStructureHandler.SetMaxRunspacesReceived += - new EventHandler>(HandleSetMaxRunspacesReceived); - DataStructureHandler.SetMinRunspacesReceived += - new EventHandler>(HandleSetMinRunspacesReceived); - DataStructureHandler.GetAvailableRunspacesReceived += - new EventHandler>(HandleGetAvailableRunspacesReceived); - DataStructureHandler.ResetRunspaceState += - new EventHandler>(HandleResetRunspaceState); + DataStructureHandler.CreateAndInvokePowerShell += HandleCreateAndInvokePowerShell; + DataStructureHandler.GetCommandMetadata += HandleGetCommandMetadata; + DataStructureHandler.HostResponseReceived += HandleHostResponseReceived; + DataStructureHandler.SetMaxRunspacesReceived += HandleSetMaxRunspacesReceived; + DataStructureHandler.SetMinRunspacesReceived += HandleSetMinRunspacesReceived; + DataStructureHandler.GetAvailableRunspacesReceived += HandleGetAvailableRunspacesReceived; + DataStructureHandler.ResetRunspaceState += HandleResetRunspaceState; } #endregion Constructors @@ -270,7 +230,7 @@ internal ServerRunspacePoolDriver( #region Internal Methods /// - /// data structure handler for communicating with client + /// Data structure handler for communicating with client. /// internal ServerRunspacePoolDataStructureHandler DataStructureHandler { get; } @@ -283,13 +243,13 @@ internal ServerRemoteHost ServerRemoteHost } /// - /// the client runspacepool id + /// The client runspacepool id. /// internal Guid InstanceId { get; } /// /// The local runspace pool associated with - /// this driver + /// this driver. /// internal RunspacePool RunspacePool { get; private set; } @@ -306,15 +266,12 @@ internal void Start() /// /// Send application private data to client /// will be called during runspace creation - /// and each time a new client connects to the server session + /// and each time a new client connects to the server session. /// internal void SendApplicationPrivateDataToClient() { // Include Debug mode information. - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); if (_serverRemoteDebugger != null) { @@ -378,7 +335,7 @@ internal void SendApplicationPrivateDataToClient() } /// - /// Dispose the runspace pool driver and release all its resources + /// Dispose the runspace pool driver and release all its resources. /// internal void Close() { @@ -390,19 +347,14 @@ internal void Close() { Runspace runspaceToDispose = _remoteHost.PushedRunspace; _remoteHost.PopRunspace(); - if (runspaceToDispose != null) - { - runspaceToDispose.Dispose(); - } + runspaceToDispose?.Dispose(); } DisposeRemoteDebugger(); RunspacePool.Close(); - RunspacePool.StateChanged -= - new EventHandler(HandleRunspacePoolStateChanged); - RunspacePool.ForwardEvent -= - new EventHandler(HandleRunspacePoolForwardEvent); + RunspacePool.StateChanged -= HandleRunspacePoolStateChanged; + RunspacePool.ForwardEvent -= HandleRunspacePoolForwardEvent; RunspacePool.Dispose(); RunspacePool = null; @@ -412,6 +364,7 @@ internal void Close() _rsToUseForSteppablePipeline.Dispose(); _rsToUseForSteppablePipeline = null; } + Closed.SafeInvoke(this, EventArgs.Empty); } } @@ -491,10 +444,7 @@ private void HandleRunspaceCreatedForTypeTable(object sender, RunspaceCreatedEve { // Let exceptions propagate. RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(_configurationName, _remoteHost); - - _remoteHost.AllowPushRunspace = true; _remoteHost.PropagatePop = true; - _remoteHost.PushRunspace(remoteRunspace); } } @@ -522,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); @@ -530,16 +480,10 @@ private void SetupRemoteDebugger(Runspace runspace) } } - private void DisposeRemoteDebugger() - { - if (_serverRemoteDebugger != null) - { - _serverRemoteDebugger.Dispose(); - } - } + private void DisposeRemoteDebugger() => _serverRemoteDebugger?.Dispose(); /// - /// Invokes a script + /// Invokes a script. /// /// /// @@ -548,7 +492,9 @@ private PSDataCollection InvokeScript(Command cmdToRun, RunspaceCreate { Debug.Assert(cmdToRun != null, "cmdToRun shouldn't be null"); - cmdToRun.CommandOrigin = CommandOrigin.Internal; + // Don't invoke initialization script as trusted (CommandOrigin == Internal) if the system is in lock down mode. + cmdToRun.CommandOrigin = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? CommandOrigin.Runspace : CommandOrigin.Internal; + cmdToRun.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); PowerShell powershell = PowerShell.Create(); powershell.AddCommand(cmdToRun).AddCommand("out-default"); @@ -557,7 +503,7 @@ private PSDataCollection InvokeScript(Command cmdToRun, RunspaceCreate } /// - /// Invokes a PowerShell instance + /// Invokes a PowerShell instance. /// /// /// @@ -575,9 +521,7 @@ private PSDataCollection InvokePowerShell(PowerShell powershell, Runsp Guid.Empty, this.InstanceId, this, -#if !CORECLR // No ApartmentState In CoreCLR args.Runspace.ApartmentState, -#endif hostInfo, RemoteStreamOptions.AddInvocationInfo, false, @@ -605,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 { @@ -634,11 +578,7 @@ private void HandleRunspaceCreated(object sender, RunspaceCreatedEventArgs args) // If the system lockdown policy says "Enforce", do so (unless it's in the // more restrictive NoLanguage mode) - if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) && - (args.Runspace.ExecutionContext.LanguageMode != PSLanguageMode.NoLanguage)) - { - args.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; - } + Utils.EnforceSystemLockDownLanguageMode(args.Runspace.ExecutionContext); // Set the current location to MyDocuments folder for this runspace. // This used to be set to the Personal folder but was changed to MyDocuments folder for @@ -660,6 +600,13 @@ private void HandleRunspaceCreated(object sender, RunspaceCreatedEventArgs args) // to ignore all but critical errors. } + if (!string.IsNullOrWhiteSpace(_initialLocation)) + { + var setLocationCommand = new Command("Set-Location"); + setLocationCommand.Parameters.Add(new CommandParameter("LiteralPath", _initialLocation)); + InvokeScript(setLocationCommand, args); + } + // Run startup scripts InvokeStartupScripts(args); @@ -680,7 +627,7 @@ private void InvokeStartupScripts(RunspaceCreatedEventArgs args) cmdToRun = new Command(_configData.InitializationScriptForOutOfProcessRunspace, true, false); } - if (null != cmdToRun) + if (cmdToRun != null) { InvokeScript(cmdToRun, args); @@ -703,9 +650,9 @@ private void InvokeStartupScripts(RunspaceCreatedEventArgs args) } /// - /// handler to the runspace pool state changed events + /// Handler to the runspace pool state changed events. /// - /// sender of this events + /// Sender of this events. /// arguments which describe the /// RunspacePool's StateChanged event private void HandleRunspacePoolStateChanged(object sender, @@ -722,6 +669,7 @@ private void HandleRunspacePoolStateChanged(object sender, { DataStructureHandler.SendStateInfoToClient(new RunspacePoolStateInfo(state, reason)); } + break; case RunspacePoolState.Opened: @@ -729,12 +677,13 @@ private void HandleRunspacePoolStateChanged(object sender, SendApplicationPrivateDataToClient(); DataStructureHandler.SendStateInfoToClient(new RunspacePoolStateInfo(state, reason)); } + break; } } /// - /// handler to the runspace pool psevents + /// Handler to the runspace pool psevents. /// private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e) { @@ -745,11 +694,11 @@ private void HandleRunspacePoolForwardEvent(object sender, PSEventArgs e) } /// - /// Handle the invocation of powershell + /// Handle the invocation of powershell. /// - /// sender of this event, unused - /// arguments describing this event - private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs> eventArgs) + /// Sender of this event, unused. + /// Arguments describing this event. + private void HandleCreateAndInvokePowerShell(object _, RemoteDataEventArgs> eventArgs) { RemoteDataObject data = eventArgs.Data; @@ -758,9 +707,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< // invoked from within the driver HostInfo hostInfo = RemotingDecoder.GetHostInfo(data.Data); -#if !CORECLR // No ApartmentState In CoreCLR ApartmentState apartmentState = RemotingDecoder.GetApartmentState(data.Data); -#endif RemoteStreamOptions streamOptions = RemotingDecoder.GetRemoteStreamOptions(data.Data); PowerShell powershell = RemotingDecoder.GetPowerShell(data.Data); @@ -769,7 +716,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< bool isNested = false; // The server would've dropped the protocol version of an older client was connecting - if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM) + if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_2) { isNested = RemotingDecoder.GetIsNested(data.Data); } @@ -779,36 +726,52 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< { DebuggerCommandArgument commandArgument; bool terminateImmediate = false; - var result = PreProcessDebuggerCommand(powershell.Commands, _serverRemoteDebugger.IsActive, _serverRemoteDebugger.IsRemote, out commandArgument); + Collection preProcessOutput = new Collection(); - switch (result) + try { - case PreProcessCommandResult.SetDebuggerAction: - // Run this directly on the debugger and terminate the remote command. - _serverRemoteDebugger.SetDebuggerAction(commandArgument.ResumeAction.Value); - terminateImmediate = true; - break; - - case PreProcessCommandResult.SetDebugMode: - // Set debug mode directly and terminate remote command. - _serverRemoteDebugger.SetDebugMode(commandArgument.Mode.Value); - terminateImmediate = true; - break; + var result = PreProcessDebuggerCommand(powershell.Commands, _serverRemoteDebugger, preProcessOutput, out commandArgument); - case PreProcessCommandResult.SetDebuggerStepMode: - // Enable debugger and set to step action, then terminate remote command. - _serverRemoteDebugger.SetDebuggerStepMode(commandArgument.DebuggerStepEnabled.Value); - terminateImmediate = true; - break; - - case PreProcessCommandResult.SetPreserveUnhandledBreakpointMode: - _serverRemoteDebugger.UnhandledBreakpointMode = commandArgument.UnhandledBreakpointMode.Value; - terminateImmediate = true; - break; + switch (result) + { + case PreProcessCommandResult.SetDebuggerAction: + // Run this directly on the debugger and terminate the remote command. + _serverRemoteDebugger.SetDebuggerAction(commandArgument.ResumeAction.Value); + terminateImmediate = true; + break; + + case PreProcessCommandResult.SetDebugMode: + // Set debug mode directly and terminate remote command. + _serverRemoteDebugger.SetDebugMode(commandArgument.Mode.Value); + terminateImmediate = true; + break; + + case PreProcessCommandResult.SetDebuggerStepMode: + // Enable debugger and set to step action, then terminate remote command. + _serverRemoteDebugger.SetDebuggerStepMode(commandArgument.DebuggerStepEnabled.Value); + terminateImmediate = true; + break; + + case PreProcessCommandResult.SetPreserveUnhandledBreakpointMode: + _serverRemoteDebugger.UnhandledBreakpointMode = commandArgument.UnhandledBreakpointMode.Value; + terminateImmediate = true; + break; + + case PreProcessCommandResult.ValidNotProcessed: + terminateImmediate = true; + break; + + case PreProcessCommandResult.BreakpointManagement: + terminateImmediate = true; + break; + } + } + catch (Exception ex) + { + terminateImmediate = true; - case PreProcessCommandResult.ValidNotProcessed: - terminateImmediate = true; - break; + preProcessOutput.Add( + PSObject.AsPSObject(ex)); } // If we don't want to run or queue a command to run in the server session then @@ -822,15 +785,13 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< data.PowerShellId, data.RunspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, streamOptions, addToHistory, null); - noOpDriver.RunNoOpCommand(); + noOpDriver.RunNoOpCommand(preProcessOutput); return; } } @@ -839,10 +800,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< { // If we have a pushed runspace then execute there. // Ensure debugger is enabled to the original mode it was set to. - if (_serverRemoteDebugger != null) - { - _serverRemoteDebugger.CheckDebuggerState(); - } + _serverRemoteDebugger?.CheckDebuggerState(); StartPowerShellCommandOnPushedRunspace( powershell, @@ -862,7 +820,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< { if (_driverNestedInvoker != null && _driverNestedInvoker.IsActive) { - if (_driverNestedInvoker.IsAvailable == false) + if (!_driverNestedInvoker.IsAvailable) { // A nested command is already running. throw new PSInvalidOperationException( @@ -881,9 +839,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< data.PowerShellId, data.RunspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, streamOptions, addToHistory, @@ -902,9 +858,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< data.PowerShellId, data.RunspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif _remoteHost, hostInfo, streamOptions, @@ -914,8 +868,8 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< } else if (powershell.Commands.Commands.Count == 1 && !powershell.Commands.Commands[0].IsScript && - ((powershell.Commands.Commands[0].CommandText.IndexOf("Get-PSDebuggerStopArgs", StringComparison.OrdinalIgnoreCase) != -1) || - (powershell.Commands.Commands[0].CommandText.IndexOf("Set-PSDebuggerAction", StringComparison.OrdinalIgnoreCase) != -1))) + (powershell.Commands.Commands[0].CommandText.Contains("Get-PSDebuggerStopArgs", StringComparison.OrdinalIgnoreCase) || + powershell.Commands.Commands[0].CommandText.Contains("Set-PSDebuggerAction", StringComparison.OrdinalIgnoreCase))) { // We do not want to invoke debugger commands in the steppable pipeline. // Consider adding IsSteppable message to PSRP to handle this. @@ -935,9 +889,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< data.PowerShellId, data.RunspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, streamOptions, addToHistory, @@ -956,10 +908,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< // Invoke command normally. Ensure debugger is enabled to the // original mode it was set to. - if (_serverRemoteDebugger != null) - { - _serverRemoteDebugger.CheckDebuggerState(); - } + _serverRemoteDebugger?.CheckDebuggerState(); // Invoke PowerShell on driver runspace pool. ServerPowerShellDriver driver = new ServerPowerShellDriver( @@ -969,9 +918,7 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< data.PowerShellId, data.RunspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, streamOptions, addToHistory, @@ -982,7 +929,8 @@ private void HandleCreateAndInvokePowerShell(object sender, RemoteDataEventArgs< } private bool? _initialSessionStateIncludesGetCommandWithListImportedSwitch; - private object _initialSessionStateIncludesGetCommandWithListImportedSwitchLock = new object(); + private readonly object _initialSessionStateIncludesGetCommandWithListImportedSwitchLock = new object(); + private bool DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch() { if (!_initialSessionStateIncludesGetCommandWithListImportedSwitch.HasValue) @@ -998,7 +946,7 @@ private bool DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch() { IEnumerable 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) { @@ -1026,10 +974,10 @@ private bool DoesInitialSessionStateIncludeGetCommandWithListImportedSwitch() } /// - /// Handle the invocation of command discovery pipeline + /// Handle the invocation of command discovery pipeline. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleGetCommandMetadata(object sender, RemoteDataEventArgs> eventArgs) { RemoteDataObject data = eventArgs.Data; @@ -1039,6 +987,7 @@ private void HandleGetCommandMetadata(object sender, RemoteDataEventArgs - /// Handles host responses + /// Handles host responses. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleHostResponseReceived(object sender, RemoteDataEventArgs eventArgs) { @@ -1105,9 +1053,9 @@ private void HandleHostResponseReceived(object sender, } /// - /// Sets the maximum runspace of the runspace pool and sends a response back + /// Sets the maximum runspace of the runspace pool and sends a response back. /// - /// sender of this event, unused + /// Sender of this event, unused. /// contains information about the new maxRunspaces /// and the callId at the client private void HandleSetMaxRunspacesReceived(object sender, RemoteDataEventArgs eventArgs) @@ -1121,9 +1069,9 @@ private void HandleSetMaxRunspacesReceived(object sender, RemoteDataEventArgs - /// Sets the minimum runspace of the runspace pool and sends a response back + /// Sets the minimum runspace of the runspace pool and sends a response back. /// - /// sender of this event, unused + /// Sender of this event, unused. /// contains information about the new minRunspaces /// and the callId at the client private void HandleSetMinRunspacesReceived(object sender, RemoteDataEventArgs eventArgs) @@ -1138,10 +1086,10 @@ private void HandleSetMinRunspacesReceived(object sender, RemoteDataEventArgs /// Gets the available runspaces from the server and sends it across - /// to the client + /// to the client. /// - /// sender of this event, unused - /// contains information on the callid + /// Sender of this event, unused. + /// Contains information on the callid. private void HandleGetAvailableRunspacesReceived(object sender, RemoteDataEventArgs eventArgs) { PSObject data = eventArgs.Data; @@ -1153,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. /// /// /// @@ -1191,16 +1139,16 @@ private bool ResetRunspaceState() } /// - /// Starts the PowerShell command on the currently pushed Runspace + /// Starts the PowerShell command on the currently pushed Runspace. /// - /// PowerShell command or script - /// PowerShell command to run after first completes - /// PowerShell Id - /// RunspacePool Id - /// Host Info - /// Remote stream options - /// False when input is provided - /// Add to history + /// PowerShell command or script. + /// PowerShell command to run after first completes. + /// PowerShell Id. + /// RunspacePool Id. + /// Host Info. + /// Remote stream options. + /// False when input is provided. + /// Add to history. private void StartPowerShellCommandOnPushedRunspace( PowerShell powershell, PowerShell extraPowerShell, @@ -1220,9 +1168,7 @@ private void StartPowerShellCommandOnPushedRunspace( powershellId, runspacePoolId, this, -#if !CORECLR // No ApartmentState In CoreCLR ApartmentState.MTA, -#endif hostInfo, streamOptions, addToHistory, @@ -1251,7 +1197,8 @@ private void StartPowerShellCommandOnPushedRunspace( private enum PreProcessCommandResult { /// - /// No debugger pre-processing + /// No debugger pre-processing. "Get" commands use this so that the + /// data they retrieve can be sent back to the caller. /// None = 0, @@ -1262,32 +1209,32 @@ private enum PreProcessCommandResult ValidNotProcessed, /// - /// GetDebuggerStopArgs - /// - GetDebuggerStopArgs, - - /// - /// SetDebuggerAction + /// SetDebuggerAction. /// SetDebuggerAction, /// - /// SetDebugMode + /// SetDebugMode. /// SetDebugMode, /// - /// SetDebuggerStepMode + /// SetDebuggerStepMode. /// SetDebuggerStepMode, /// - /// SetPreserveUnhandledBreakpointMode + /// SetPreserveUnhandledBreakpointMode. + /// + SetPreserveUnhandledBreakpointMode, + + /// + /// The PreProcessCommandResult used for managing breakpoints. /// - SetPreserveUnhandledBreakpointMode - }; + BreakpointManagement, + } - private class DebuggerCommandArgument + private sealed class DebuggerCommandArgument { public DebugModes? Mode { get; set; } @@ -1302,56 +1249,52 @@ private class DebuggerCommandArgument /// Pre-processor for debugger commands. /// Parses special debugger commands and converts to equivalent script for remote execution as needed. /// - /// PSCommand - /// True if debugger is active. - /// True if active debugger is pushed and is a remote debugger. + /// The PSCommand. + /// The debugger that can be used to invoke debug operations via API. + /// A Collection that can be used to send output to the client. /// Command argument. /// PreProcessCommandResult type if preprocessing occurred. private static PreProcessCommandResult PreProcessDebuggerCommand( PSCommand commands, - bool isDebuggerActive, - bool isDebuggerRemote, + ServerRemoteDebugger serverRemoteDebugger, + Collection preProcessOutput, out DebuggerCommandArgument commandArgument) { commandArgument = new DebuggerCommandArgument(); PreProcessCommandResult result = PreProcessCommandResult.None; - if ((commands.Commands.Count == 0) || (commands.Commands[0].IsScript)) + if (commands.Commands.Count == 0 || commands.Commands[0].IsScript) { return result; } var command = commands.Commands[0]; string commandText = command.CommandText; - if (commandText.Equals(DebuggerUtils.GetDebuggerStopArgsFunctionName, StringComparison.OrdinalIgnoreCase)) + if (commandText.Equals(RemoteDebuggingCommands.GetDebuggerStopArgs, StringComparison.OrdinalIgnoreCase)) { - // // __Get-PSDebuggerStopArgs private virtual command. // No input parameters. // Returns DebuggerStopEventArgs object. - // // Evaluate this command only if the debugger is activated. - if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; } - - // Translate into debugger method call. - ScriptBlock scriptBlock = ScriptBlock.Create("$host.Runspace.Debugger.GetDebuggerStopArgs()"); - scriptBlock.LanguageMode = PSLanguageMode.FullLanguage; - commands.Clear(); - commands.AddCommand("Invoke-Command").AddParameter("ScriptBlock", scriptBlock).AddParameter("NoNewScope", true); + if (!serverRemoteDebugger.IsActive) + { + return PreProcessCommandResult.ValidNotProcessed; + } - result = PreProcessCommandResult.GetDebuggerStopArgs; + ReplaceVirtualCommandWithScript(commands, "$host.Runspace.Debugger.GetDebuggerStopArgs()"); } - else if (commandText.Equals(DebuggerUtils.SetDebuggerActionFunctionName, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebuggerAction, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebuggerAction private virtual command. // DebuggerResumeAction enum input parameter. // Returns void. - // // Evaluate this command only if the debugger is activated. - if (!isDebuggerActive) { return PreProcessCommandResult.ValidNotProcessed; } + if (!serverRemoteDebugger.IsActive) + { + return PreProcessCommandResult.ValidNotProcessed; + } if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("ResumeAction", StringComparison.OrdinalIgnoreCase))) @@ -1367,25 +1310,20 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( { resumeAction = (DebuggerResumeAction)resumeObject.BaseObject; } - catch (InvalidCastException) { } - } - - if (resumeAction == null) - { - throw new PSArgumentException("ResumeAction"); + catch (InvalidCastException) + { + // Do nothing. + } } - commandArgument.ResumeAction = resumeAction; + commandArgument.ResumeAction = resumeAction ?? throw new PSArgumentException("ResumeAction"); result = PreProcessCommandResult.SetDebuggerAction; } - else if (commandText.Equals(DebuggerUtils.SetDebugModeFunctionName, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebugMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebugMode private virtual command. // DebugModes enum input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("Mode", StringComparison.OrdinalIgnoreCase))) { @@ -1400,25 +1338,20 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( { mode = (DebugModes)modeObject.BaseObject; } - catch (InvalidCastException) { } - } - - if (mode == null) - { - throw new PSArgumentException("Mode"); + catch (InvalidCastException) + { + // Do nothing. + } } - commandArgument.Mode = mode; + commandArgument.Mode = mode ?? throw new PSArgumentException("Mode"); result = PreProcessCommandResult.SetDebugMode; } - else if (commandText.Equals(DebuggerUtils.SetDebuggerStepMode, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetDebuggerStepMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSDebuggerStepMode private virtual command. // Boolean Enabled input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("Enabled", StringComparison.OrdinalIgnoreCase))) { @@ -1429,14 +1362,11 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( commandArgument.DebuggerStepEnabled = enabled; result = PreProcessCommandResult.SetDebuggerStepMode; } - else if (commandText.Equals(DebuggerUtils.SetPSUnhandledBreakpointMode, StringComparison.OrdinalIgnoreCase)) + else if (commandText.Equals(RemoteDebuggingCommands.SetUnhandledBreakpointMode, StringComparison.OrdinalIgnoreCase)) { - // // __Set-PSUnhandledBreakpointMode private virtual command. // UnhandledBreakpointMode input parameter. // Returns void. - // - if ((command.Parameters == null) || (command.Parameters.Count == 0) || (!command.Parameters[0].Name.Equals("UnhandledBreakpointMode", StringComparison.OrdinalIgnoreCase))) { @@ -1451,21 +1381,184 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( { mode = (UnhandledBreakpointProcessingMode)modeObject.BaseObject; } - catch (InvalidCastException) { } + catch (InvalidCastException) + { + // Do nothing. + } } - if (mode == null) + commandArgument.UnhandledBreakpointMode = mode ?? throw new PSArgumentException("Mode"); + result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode; + } + else if (commandText.Equals(RemoteDebuggingCommands.GetBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Get-PSBreakpoint private virtual command. + // Input parameters: + // [-Id ] + // Returns Breakpoint object(s). + TryGetParameter(command, "RunspaceId", out int? runspaceId); + if (TryGetParameter(command, "Id", out int breakpointId)) { - throw new PSArgumentException("Mode"); + preProcessOutput.Add(serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId)); + } + else + { + foreach (Breakpoint breakpoint in serverRemoteDebugger.GetBreakpoints(runspaceId)) + { + preProcessOutput.Add(breakpoint); + } } - commandArgument.UnhandledBreakpointMode = mode; - result = PreProcessCommandResult.SetPreserveUnhandledBreakpointMode; + result = PreProcessCommandResult.BreakpointManagement; + } + else if (commandText.Equals(RemoteDebuggingCommands.SetBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Set-PSBreakpoint private virtual command. + // Input parameters: + // -Breakpoint or -BreakpointList > + // [-RunspaceId ] + // Returns Breakpoint object(s). + TryGetParameter(command, "Breakpoint", out Breakpoint breakpoint); + TryGetParameter(command, "BreakpointList", out ArrayList breakpoints); + if (breakpoint == null && breakpoints == null) + { + throw new PSArgumentException(DebuggerStrings.BreakpointOrBreakpointListNotSpecified); + } + + TryGetParameter(command, "RunspaceId", out int? runspaceId); + + commands.Clear(); + + // Any collection comes through remoting as an ArrayList of Objects so we convert each object + // into a breakpoint and add it to the list. + var bps = new List(); + if (breakpoints != null) + { + foreach (object obj in breakpoints) + { + if (!LanguagePrimitives.TryConvertTo(obj, out Breakpoint bp)) + { + throw new PSArgumentException(DebuggerStrings.BreakpointListContainedANonBreakpoint); + } + + bps.Add(bp); + } + } + else + { + bps.Add(breakpoint); + } + + serverRemoteDebugger.SetBreakpoints(bps, runspaceId); + + foreach (var bp in bps) + { + preProcessOutput.Add(bp); + } + + result = PreProcessCommandResult.BreakpointManagement; + } + else if (commandText.Equals(RemoteDebuggingCommands.RemoveBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Remove-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // [-RunspaceId ] + // Returns bool. + int breakpointId = GetParameter(command, "Id"); + TryGetParameter(command, "RunspaceId", out int? runspaceId); + + Breakpoint breakpoint = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId); + preProcessOutput.Add( + breakpoint != null && serverRemoteDebugger.RemoveBreakpoint(breakpoint, runspaceId)); + + result = PreProcessCommandResult.BreakpointManagement; + } + else if (commandText.Equals(RemoteDebuggingCommands.EnableBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Enable-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // [-RunspaceId ] + // Returns Breakpoint object. + int breakpointId = GetParameter(command, "Id"); + TryGetParameter(command, "RunspaceId", out int? runspaceId); + + Breakpoint bp = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId); + if (bp != null) + { + preProcessOutput.Add(serverRemoteDebugger.EnableBreakpoint(bp, runspaceId)); + } + + result = PreProcessCommandResult.BreakpointManagement; + } + else if (commandText.Equals(RemoteDebuggingCommands.DisableBreakpoint, StringComparison.OrdinalIgnoreCase)) + { + // __Disable-PSBreakpoint private virtual command. + // Input parameters: + // -Id + // [-RunspaceId ] + // Returns Breakpoint object. + int breakpointId = GetParameter(command, "Id"); + TryGetParameter(command, "RunspaceId", out int? runspaceId); + + Breakpoint bp = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId); + if (bp != null) + { + preProcessOutput.Add(serverRemoteDebugger.DisableBreakpoint(bp, runspaceId)); + } + + result = PreProcessCommandResult.BreakpointManagement; } return result; } + private static void ReplaceVirtualCommandWithScript(PSCommand commands, string script) + { + ScriptBlock scriptBlock = ScriptBlock.Create(script); + scriptBlock.LanguageMode = PSLanguageMode.FullLanguage; + commands.Clear(); + commands.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", scriptBlock) + .AddParameter("NoNewScope", true); + } + + private static T GetParameter(Command command, string parameterName) + { + if (command.Parameters?.Count == 0) + { + throw new PSArgumentException(parameterName); + } + + foreach (CommandParameter param in command.Parameters) + { + if (string.Equals(param.Name, parameterName, StringComparison.OrdinalIgnoreCase)) + { + return LanguagePrimitives.ConvertTo(param.Value); + } + } + + throw new PSArgumentException(parameterName); + } + + private static bool TryGetParameter(Command command, string parameterName, out T value) + { + try + { + value = GetParameter(command, parameterName); + return true; + } + catch (Exception ex) when ( + ex is PSArgumentException || + ex is InvalidCastException || + ex is PSInvalidCastException) + { + value = default(T); + return false; + } + } + #endregion #region Private Classes @@ -1478,14 +1571,14 @@ private sealed class PowerShellDriverInvoker { #region Private Members - private ConcurrentStack _invokePumpStack; + private readonly ConcurrentStack _invokePumpStack; #endregion #region Constructor /// - /// Constructor + /// Constructor. /// public PowerShellDriverInvoker() { @@ -1497,7 +1590,7 @@ public PowerShellDriverInvoker() #region Properties /// - /// IsActive + /// IsActive. /// public bool IsActive { @@ -1517,7 +1610,7 @@ public bool IsAvailable pump = null; } - return (pump != null) ? !(pump.IsBusy) : false; + return (pump != null) && !(pump.IsBusy); } } @@ -1528,7 +1621,7 @@ public bool IsAvailable /// /// Submit a driver object to be invoked. /// - /// ServerPowerShellDriver + /// ServerPowerShellDriver. public void InvokeDriverAsync(ServerPowerShellDriver driver) { InvokePump currentPump; @@ -1581,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; @@ -1693,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; @@ -1727,16 +1820,19 @@ internal ServerRemoteDebugger( { if (driverInvoker == null) { - throw new PSArgumentNullException("driverInvoker"); + throw new PSArgumentNullException(nameof(driverInvoker)); } + if (runspace == null) { - throw new PSArgumentNullException("runspace"); + throw new PSArgumentNullException(nameof(runspace)); } + if (debugger == null) { - throw new PSArgumentNullException("debugger"); + throw new PSArgumentNullException(nameof(debugger)); } + _driverInvoker = driverInvoker; _runspace = runspace; @@ -1760,10 +1856,97 @@ public override bool InBreakpoint get { return _inDebugMode; } } + /// + /// Adds the provided set of breakpoints to the debugger. + /// + /// List of breakpoints. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + public override void SetBreakpoints(IEnumerable breakpoints, int? runspaceId) => + _wrappedDebugger.Value.SetBreakpoints(breakpoints, runspaceId); + + /// + /// Get a breakpoint by id, primarily for Enable/Disable/Remove-PSBreakpoint cmdlets. + /// + /// 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. + /// The breakpoint with the specified id. + public override Breakpoint GetBreakpoint(int id, int? runspaceId) => + _wrappedDebugger.Value.GetBreakpoint(id, runspaceId); + + /// + /// Returns breakpoints on a runspace. + /// + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// A list of breakpoints in a runspace. + public override List GetBreakpoints(int? runspaceId) => + _wrappedDebugger.Value.GetBreakpoints(runspaceId); + + /// + /// Sets a command breakpoint in the debugger. + /// + /// The name of the command that will trigger the breakpoint. This value may not be null. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the command is invoked. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The command breakpoint that was set. + public override CommandBreakpoint SetCommandBreakpoint(string command, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.Value.SetCommandBreakpoint(command, action, path, runspaceId); + + /// + /// Sets a line breakpoint in the debugger. + /// + /// The path to the script file where the breakpoint may be hit. This value may not be null. + /// The line in the script file where the breakpoint may be hit. This value must be greater than or equal to 1. + /// The column in the script file where the breakpoint may be hit. If 0, the breakpoint will trigger on any statement on the line. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The line breakpoint that was set. + public override LineBreakpoint SetLineBreakpoint(string path, int line, int column, ScriptBlock action, int? runspaceId) => + _wrappedDebugger.Value.SetLineBreakpoint(path, line, column, action, runspaceId); + + /// + /// Sets a variable breakpoint in the debugger. + /// + /// The name of the variable that will trigger the breakpoint. This value may not be null. + /// The variable access mode that will trigger the breakpoint. + /// The action to take when the breakpoint is hit. If null, PowerShell will break into the debugger when the breakpoint is hit. + /// The path to the script file where the breakpoint may be hit. If null, the breakpoint may be hit anywhere the variable is accessed using the specified access mode. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The variable breakpoint that was set. + public override VariableBreakpoint SetVariableBreakpoint(string variableName, VariableAccessMode accessMode, ScriptBlock action, string path, int? runspaceId) => + _wrappedDebugger.Value.SetVariableBreakpoint(variableName, accessMode, action, path, runspaceId); + + /// + /// Removes a breakpoint from the debugger. + /// + /// The breakpoint to remove from the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// True if the breakpoint was removed from the debugger; false otherwise. + public override bool RemoveBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.Value.RemoveBreakpoint(breakpoint, runspaceId); + + /// + /// Enables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint EnableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.Value.EnableBreakpoint(breakpoint, runspaceId); + + /// + /// Disables a breakpoint in the debugger. + /// + /// The breakpoint to enable in the debugger. This value may not be null. + /// The runspace id of the runspace you want to interact with. A null value will use the current runspace. + /// The updated breakpoint if it was found; null if the breakpoint was not found in the debugger. + public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspaceId) => + _wrappedDebugger.Value.DisableBreakpoint(breakpoint, runspaceId); + /// /// Exits debugger mode with the provided resume action. /// - /// DebuggerResumeAction + /// DebuggerResumeAction. public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { if (!_inDebugMode) @@ -1778,17 +1961,17 @@ public override void SetDebuggerAction(DebuggerResumeAction resumeAction) /// /// Returns debugger stop event args if in debugger stop state. /// - /// DebuggerStopEventArgs + /// DebuggerStopEventArgs. public override DebuggerStopEventArgs GetDebuggerStopArgs() { return _wrappedDebugger.Value.GetDebuggerStopArgs(); } /// - /// ProcessCommand + /// ProcessCommand. /// - /// Command - /// Output + /// Command. + /// Output. /// public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection output) { @@ -1803,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 @@ -1820,7 +2000,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC } /// - /// StopProcessCommand + /// StopProcessCommand. /// public override void StopProcessCommand() { @@ -1830,14 +2010,11 @@ public override void StopProcessCommand() } ThreadCommandProcessing threadCommandProcessing = _threadCommandProcessing; - if (threadCommandProcessing != null) - { - threadCommandProcessing.Stop(); - } + threadCommandProcessing?.Stop(); } /// - /// SetDebugMode + /// SetDebugMode. /// /// public override void SetDebugMode(DebugModes mode) @@ -1861,11 +2038,11 @@ public override bool IsActive /// /// Sets debugger stepping mode. /// - /// True if stepping is to be enabled + /// True if stepping is to be enabled. 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); @@ -1873,7 +2050,7 @@ public override void SetDebuggerStepMode(bool enabled) } /// - /// InternalProcessCommand + /// InternalProcessCommand. /// /// /// @@ -1887,19 +2064,21 @@ internal override DebuggerCommand InternalProcessCommand(string command, IList

/// - /// Job object that is either a debuggable job or a container - /// of debuggable child jobs. + /// Job object that is either a debuggable job or a container of + /// debuggable child jobs. /// - internal override void DebugJob(Job job) - { - _wrappedDebugger.Value.DebugJob(job); - } + /// + /// If true, the debugger automatically invokes a break all when it + /// attaches to the job. + /// + internal override void DebugJob(Job job, bool breakAll) => + _wrappedDebugger.Value.DebugJob(job, breakAll); ///

/// Removes job from debugger job list and pops its /// debugger from the active debugger stack. /// - /// Job + /// Job. internal override void StopDebugJob(Job job) { _wrappedDebugger.Value.StopDebugJob(job); @@ -1908,23 +2087,29 @@ internal override void StopDebugJob(Job job) /// /// Sets up debugger to debug provided Runspace in a nested debug session. /// - /// Runspace to debug - internal override void DebugRunspace(Runspace runspace) + /// + /// Runspace to debug. + /// + /// + /// When true, this command will invoke a BreakAll when the debugger is + /// first attached. + /// + internal override void DebugRunspace(Runspace runspace, bool breakAll) { - _wrappedDebugger.Value.DebugRunspace(runspace); + _wrappedDebugger.Value.DebugRunspace(runspace, breakAll); } /// /// Removes the provided Runspace from the nested "active" debugger state. /// - /// Runspace + /// Runspace. internal override void StopDebugRunspace(Runspace runspace) { _wrappedDebugger.Value.StopDebugRunspace(runspace); } /// - /// IsPushed + /// IsPushed. /// internal override bool IsPushed { @@ -1935,7 +2120,7 @@ internal override bool IsPushed } /// - /// IsRemote + /// IsRemote. /// internal override bool IsRemote { @@ -1946,7 +2131,7 @@ internal override bool IsRemote } /// - /// IsDebuggerSteppingEnabled + /// IsDebuggerSteppingEnabled. /// internal override bool IsDebuggerSteppingEnabled { @@ -1957,7 +2142,7 @@ internal override bool IsDebuggerSteppingEnabled } /// - /// UnhandledBreakpointMode + /// UnhandledBreakpointMode. /// internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode { @@ -1965,6 +2150,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode { return _wrappedDebugger.Value.UnhandledBreakpointMode; } + set { _wrappedDebugger.Value.UnhandledBreakpointMode = value; @@ -1978,7 +2164,7 @@ internal override UnhandledBreakpointProcessingMode UnhandledBreakpointMode } /// - /// IsPendingDebugStopEvent + /// IsPendingDebugStopEvent. /// internal override bool IsPendingDebugStopEvent { @@ -1986,7 +2172,7 @@ internal override bool IsPendingDebugStopEvent } /// - /// ReleaseSavedDebugStop + /// ReleaseSavedDebugStop. /// internal override void ReleaseSavedDebugStop() { @@ -2002,12 +2188,17 @@ public override IEnumerable GetCallStack() return _wrappedDebugger.Value.GetCallStack(); } + internal override void Break(object triggerObject = null) + { + _wrappedDebugger.Value.Break(triggerObject); + } + #endregion #region IDisposable /// - /// Dispose + /// Dispose. /// public void Dispose() { @@ -2017,15 +2208,8 @@ public void Dispose() ExitDebugMode(DebuggerResumeAction.Stop); } - if (_nestedDebugStopCompleteEvent != null) - { - _nestedDebugStopCompleteEvent.Dispose(); - } - - if (_processCommandCompleteEvent != null) - { - _processCommandCompleteEvent.Dispose(); - } + _nestedDebugStopCompleteEvent?.Dispose(); + _processCommandCompleteEvent?.Dispose(); } #endregion @@ -2035,13 +2219,15 @@ 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 private WindowsIdentity _identityToImpersonate; +#endif // Constructors private ThreadCommandProcessing() { } @@ -2061,14 +2247,10 @@ public ThreadCommandProcessing( // Methods public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent) { +#if !UNIX // Get impersonation information to flow if any. - WindowsIdentity currentIdentity = null; - try - { - currentIdentity = WindowsIdentity.GetCurrent(); - } - catch (System.Security.SecurityException) { } - _identityToImpersonate = ((currentIdentity != null) && (currentIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) ? currentIdentity : null; + Utils.TryGetWindowsImpersonatedIdentity(out _identityToImpersonate); +#endif // Signal thread to process command. Dbg.Assert(!_commandCompleteEvent.IsSet, "Command complete event shoulds always be non-signaled here."); @@ -2078,8 +2260,13 @@ public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent) // Wait for completion. _commandCompleteEvent.Wait(); _commandCompleteEvent.Reset(); - - _identityToImpersonate = null; +#if !UNIX + if (_identityToImpersonate != null) + { + _identityToImpersonate.Dispose(); + _identityToImpersonate = null; + } +#endif // Propagate exception. if (_exception != null) @@ -2094,26 +2281,22 @@ public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent) public void Stop() { Debugger debugger = _wrappedDebugger; - if (debugger != null) - { - debugger.StopProcessCommand(); - } + debugger?.StopProcessCommand(); } internal void DoInvoke() { -#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImplemented to replace it. - // Flow impersonation onto thread if needed. - WindowsImpersonationContext impersonationContext = null; - if ((_identityToImpersonate != null) && - (_identityToImpersonate.ImpersonationLevel == TokenImpersonationLevel.Impersonation)) - { - impersonationContext = _identityToImpersonate.Impersonate(); - } -#endif - try { +#if !UNIX + if (_identityToImpersonate != null) + { + _results = WindowsIdentity.RunImpersonated( + _identityToImpersonate.AccessToken, + () => _wrappedDebugger.ProcessCommand(_command, _output)); + return; + } +#endif _results = _wrappedDebugger.ProcessCommand(_command, _output); } catch (Exception e) @@ -2123,19 +2306,6 @@ internal void DoInvoke() finally { _commandCompleteEvent.Set(); - -#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImplemented to replace it. - // Restore previous context to thread. - if (impersonationContext != null) - { - try - { - impersonationContext.Undo(); - impersonationContext.Dispose(); - } - catch (System.Security.SecurityException) { } - } -#endif } } } @@ -2211,12 +2381,15 @@ private void RemoveDebuggerCallbacks() } /// - /// Handler for debugger events + /// Handler for debugger events. /// private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) { // Ignore if we are in restricted mode. - if (!IsDebuggingSupported()) { return; } + if (!IsDebuggingSupported()) + { + return; + } if (LocalDebugMode) { @@ -2265,14 +2438,17 @@ private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) } /// - /// HandleBreakpointUpdated + /// HandleBreakpointUpdated. /// /// /// private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { // Ignore if we are in restricted mode. - if (!IsDebuggingSupported()) { return; } + if (!IsDebuggingSupported()) + { + return; + } if (LocalDebugMode) { @@ -2321,12 +2497,10 @@ private void EnterDebugMode(bool isNestedStop) if (isNestedStop) { - // Blocking call for nested debugger execution (Workflow) stop events. + // 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); } @@ -2412,15 +2586,15 @@ private void ExitDebugMode(DebuggerResumeAction resumeAction) private void SubscribeWrappedDebugger(Debugger wrappedDebugger) { - wrappedDebugger.DebuggerStop += HandleDebuggerStop; ; - wrappedDebugger.BreakpointUpdated += HandleBreakpointUpdated; ; + wrappedDebugger.DebuggerStop += HandleDebuggerStop; + wrappedDebugger.BreakpointUpdated += HandleBreakpointUpdated; wrappedDebugger.NestedDebuggingCancelledEvent += HandleNestedDebuggingCancelEvent; } private void UnsubscribeWrappedDebugger(Debugger wrappedDebugger) { - wrappedDebugger.DebuggerStop -= HandleDebuggerStop; ; - wrappedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; ; + wrappedDebugger.DebuggerStop -= HandleDebuggerStop; + wrappedDebugger.BreakpointUpdated -= HandleBreakpointUpdated; wrappedDebugger.NestedDebuggingCancelledEvent -= HandleNestedDebuggingCancelEvent; } @@ -2445,7 +2619,7 @@ private bool IsDebuggingSupported() #region Internal Methods /// - /// HandleStopSignal + /// HandleStopSignal. /// /// True if stop signal is handled. internal bool HandleStopSignal() @@ -2489,17 +2663,14 @@ internal void StartPowerShellCommand( Guid powershellId, Guid runspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, -#if !CORECLR // No ApartmentState In CoreCLR ApartmentState apartmentState, -#endif ServerRemoteHost remoteHost, HostInfo hostInfo, RemoteStreamOptions streamOptions, bool addToHistory) { // For nested debugger command processing, invoke command on new local runspace since - // the root script debugger runspace is unavailable (it is running a PS script or a - // workflow function script). + // the root script debugger runspace is unavailable (it is running a PS script). Runspace runspace = (remoteHost != null) ? RunspaceFactory.CreateRunspace(remoteHost) : RunspaceFactory.CreateRunspace(); @@ -2510,9 +2681,10 @@ internal void StartPowerShellCommand( powershell.InvocationStateChanged += HandlePowerShellInvocationStateChanged; powershell.SetIsNested(false); - string script = @" + const string script = @" param ($Debugger, $Commands, $output) trap { throw $_ } + $Debugger.ProcessCommand($Commands, $output) "; @@ -2527,9 +2699,7 @@ internal void StartPowerShellCommand( powershellId, runspacePoolId, runspacePoolDriver, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, streamOptions, addToHistory, @@ -2598,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 1dcf430923c..3a9388625e6 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs @@ -1,22 +1,22 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; using System.Collections.Generic; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Remoting; using System.Management.Automation.Host; +using System.Management.Automation.Remoting; +using System.Management.Automation.Runspaces; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// Execution context used for stepping + /// 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; @@ -37,8 +37,7 @@ internal static ExecutionContextForStepping PrepareExecutionContext( = ctxt.InternalHost.InternalUI.GetInformationalMessageBuffers(); result._originalHost = ctxt.InternalHost.ExternalHost; - ctxt.InternalHost. - InternalUI.SetInformationalMessageBuffers(newBuffers); + ctxt.InternalHost.InternalUI.SetInformationalMessageBuffers(newBuffers); ctxt.InternalHost.SetHostRef(newHost); return result; @@ -49,8 +48,7 @@ internal static ExecutionContextForStepping PrepareExecutionContext( // resetting unmanaged resources. void IDisposable.Dispose() { - _executionContext.InternalHost. - InternalUI.SetInformationalMessageBuffers(_originalInformationalBuffers); + _executionContext.InternalHost.InternalUI.SetInformationalMessageBuffers(_originalInformationalBuffers); _executionContext.InternalHost.SetHostRef(_originalHost); GC.SuppressFinalize(this); } @@ -58,7 +56,7 @@ void IDisposable.Dispose() /// /// This class wraps a RunspacePoolInternal object. It is used to function - /// as a server side runspacepool + /// as a server side runspacepool. /// internal class ServerSteppablePipelineDriver { @@ -69,37 +67,35 @@ internal class ServerSteppablePipelineDriver // associated with this powershell // data structure handler object to handle all // communications with the client - //private bool datasent = false; // if the remaining data has been sent + // private bool datasent = false; // if the remaining data has been sent // to the client before sending state // information // data to client // was created - private bool _addToHistory; + private readonly bool _addToHistory; // associated with this powershell -#if !CORECLR // No ApartmentState In CoreCLR - private ApartmentState apartmentState; // apartment state for this powershell -#endif + 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 -#if CORECLR // No ApartmentState In CoreCLR /// /// Default constructor for creating ServerSteppablePipelineDriver...Used by server to concurrently /// run 2 pipelines. /// - /// decoded powershell object - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id + /// Decoded powershell object. + /// Whether there is input for this powershell. + /// The client powershell id. + /// The client runspacepool id. /// runspace pool driver /// which is creating this powershell driver + /// Apartment state for this powershell. /// host info using which the host for /// this powershell will be constructed - /// serialization options for the streams in this powershell + /// Serialization options for the streams in this powershell. /// /// true if the command is to be added to history list of the runspace. false, otherwise. /// @@ -110,50 +106,17 @@ internal class ServerSteppablePipelineDriver /// /// Steppable pipeline event subscriber /// - /// input collection of the PowerShell pipeline - internal ServerSteppablePipelineDriver(PowerShell powershell, bool noInput, Guid clientPowerShellId, - Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, - HostInfo hostInfo, RemoteStreamOptions streamOptions, - bool addToHistory, Runspace rsToUse, ServerSteppablePipelineSubscriber eventSubscriber, PSDataCollection powershellInput) -#else - /// - /// Default constructor for creating ServerSteppablePipelineDriver...Used by server to concurrently - /// run 2 pipelines. - /// - /// decoded powershell object - /// whether there is input for this powershell - /// the client powershell id - /// the client runspacepool id - /// runspace pool driver - /// which is creating this powershell driver - /// apartment state for this powershell - /// host info using which the host for - /// this powershell will be constructed - /// serialization options for the streams in this powershell - /// - /// true if the command is to be added to history list of the runspace. false, otherwise. - /// - /// - /// If not null, this Runspace will be used to invoke Powershell. - /// If null, the RunspacePool pointed by will be used. - /// - /// - /// Steppable pipeline event subscriber - /// - /// input collection of the PowerShell pipeline + /// Input collection of the PowerShell pipeline. internal ServerSteppablePipelineDriver(PowerShell powershell, bool noInput, Guid clientPowerShellId, Guid clientRunspacePoolId, ServerRunspacePoolDriver runspacePoolDriver, ApartmentState apartmentState, HostInfo hostInfo, RemoteStreamOptions streamOptions, bool addToHistory, Runspace rsToUse, ServerSteppablePipelineSubscriber eventSubscriber, PSDataCollection powershellInput) -#endif { LocalPowerShell = powershell; InstanceId = clientPowerShellId; RunspacePoolId = clientRunspacePoolId; RemoteStreamOptions = streamOptions; -#if !CORECLR // No ApartmentState In CoreCLR this.apartmentState = apartmentState; -#endif NoInput = noInput; _addToHistory = addToHistory; _eventSubscriber = eventSubscriber; @@ -167,12 +130,11 @@ internal ServerSteppablePipelineDriver(PowerShell powershell, bool noInput, Guid RemoteHost = DataStructureHandler.GetHostAssociatedWithPowerShell(hostInfo, runspacePoolDriver.ServerRemoteHost); // subscribe to various data structure handler events - DataStructureHandler.InputEndReceived += new EventHandler(HandleInputEndReceived); - DataStructureHandler.InputReceived += new EventHandler>(HandleInputReceived); - DataStructureHandler.StopPowerShellReceived += new EventHandler(HandleStopReceived); - DataStructureHandler.HostResponseReceived += - new EventHandler>(HandleHostResponseReceived); - DataStructureHandler.OnSessionConnected += new EventHandler(HandleSessionConnected); + DataStructureHandler.InputEndReceived += HandleInputEndReceived; + DataStructureHandler.InputReceived += HandleInputReceived; + DataStructureHandler.StopPowerShellReceived += HandleStopReceived; + DataStructureHandler.HostResponseReceived += HandleHostResponseReceived; + DataStructureHandler.OnSessionConnected += HandleSessionConnected; if (rsToUse == null) { @@ -189,24 +151,24 @@ internal ServerSteppablePipelineDriver(PowerShell powershell, bool noInput, Guid #region Internal Methods /// - /// Local PowerShell instance + /// Local PowerShell instance. /// internal PowerShell LocalPowerShell { get; } /// /// Instance id by which this powershell driver is /// identified. This is the same as the id of the - /// powershell on the client side + /// powershell on the client side. /// internal Guid InstanceId { get; } /// - /// Server remote host + /// Server remote host. /// internal ServerRemoteHost RemoteHost { get; } /// - /// Serialization options for the streams in this powershell + /// Serialization options for the streams in this powershell. /// internal RemoteStreamOptions RemoteStreamOptions { get; } @@ -215,63 +177,63 @@ internal ServerSteppablePipelineDriver(PowerShell powershell, bool noInput, Guid /// this object. This is the same as the id of /// the runspace pool at the client side which /// is associated with the powershell on the - /// client side + /// client side. /// internal Guid RunspacePoolId { get; } /// /// ServerPowerShellDataStructureHandler associated with this - /// powershell driver + /// powershell driver. /// internal ServerPowerShellDataStructureHandler DataStructureHandler { get; } /// - /// Pipeline invocation state + /// Pipeline invocation state. /// internal PSInvocationState PipelineState { get; private set; } /// - /// Checks if the steppable pipeline has input + /// Checks if the steppable pipeline has input. /// internal bool NoInput { get; } /// - /// Steppablepipeline object + /// Steppablepipeline object. /// internal SteppablePipeline SteppablePipeline { get; set; } /// - /// Synchronization object + /// Synchronization object. /// internal object SyncObject { get; } = new object(); /// - /// Processing input + /// Processing input. /// internal bool ProcessingInput { get; set; } /// - /// Input enumerator + /// Input enumerator. /// internal IEnumerator InputEnumerator { get; } /// - /// Input collection + /// Input collection. /// internal PSDataCollection Input { get; } /// - /// Is the pipeline pulsed + /// Is the pipeline pulsed. /// internal bool Pulsed { get; set; } /// - /// Total objects processed + /// Total objects processed. /// internal int TotalObjectsProcessed { get; set; } /// - /// Starts the exectution + /// Starts the exectution. /// internal void Start() { @@ -279,10 +241,7 @@ internal void Start() _eventSubscriber.FireStartSteppablePipeline(this); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } #endregion Internal Methods @@ -290,47 +249,41 @@ internal void Start() #region DataStructure related event handling / processing /// - /// Close the input collection of the local powershell + /// Close the input collection of the local powershell. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. internal void HandleInputEndReceived(object sender, EventArgs eventArgs) { Input.Complete(); 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 + // 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(); } /// - /// Handle a host message response received + /// Handle a host message response received. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. internal void HandleHostResponseReceived(object sender, RemoteDataEventArgs eventArgs) { RemoteHost.ServerMethodExecutor.HandleRemoteHostResponseFromClient(eventArgs.Data); } /// - /// Stop the local powershell + /// Stop the local powershell. /// - /// sender of this event, unused - /// unused + /// Sender of this event, unused. + /// Unused. private void HandleStopReceived(object sender, EventArgs eventArgs) { lock (SyncObject) @@ -340,17 +293,14 @@ private void HandleStopReceived(object sender, EventArgs eventArgs) PerformStop(); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } /// - /// Add input to the local powershell's input collection + /// Add input to the local powershell's input collection. /// - /// sender of this event, unused - /// arguments describing this event + /// Sender of this event, unused. + /// Arguments describing this event. private void HandleInputReceived(object sender, RemoteDataEventArgs eventArgs) { Dbg.Assert(!NoInput, "Input data should not be received for powershells created with no input"); @@ -361,12 +311,10 @@ private void HandleInputReceived(object sender, RemoteDataEventArgs even { Input.Add(eventArgs.Data); } + CheckAndPulseForProcessing(false); - if (_powershellInput != null) - { - _powershellInput.Pulse(); - } + _powershellInput?.Pulse(); } } @@ -407,7 +355,7 @@ internal void CheckAndPulseForProcessing(bool complete) } /// - /// Performs the stop operation + /// Performs the stop operation. /// internal void PerformStop() { @@ -454,6 +402,7 @@ internal void SetState(PSInvocationState newState, Exception reason) break; } } + break; case PSInvocationState.Running: @@ -475,6 +424,7 @@ internal void SetState(PSInvocationState newState, Exception reason) break; } } + break; case PSInvocationState.Stopping: @@ -494,6 +444,7 @@ internal void SetState(PSInvocationState newState, Exception reason) throw new InvalidOperationException(); } } + break; case PSInvocationState.Stopped: @@ -523,4 +474,4 @@ internal void SetState(PSInvocationState newState, Exception reason) #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs index 01e48b022b8..cce8b0bbcd3 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; @@ -11,7 +10,7 @@ namespace System.Management.Automation { /// - /// Event handler argument + /// Event handler argument. /// internal class ServerSteppablePipelineDriverEventArg : EventArgs { @@ -24,13 +23,13 @@ internal ServerSteppablePipelineDriverEventArg(ServerSteppablePipelineDriver dri } /// - /// Steppable pipeline driver event handler class + /// Steppable pipeline driver event handler class. /// 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; @@ -54,6 +53,7 @@ internal void SubscribeEvents(ServerSteppablePipelineDriver driver) _processSubscriber = _eventManager.SubscribeEvent(this, "RunProcessRecord", Guid.NewGuid().ToString(), null, new PSEventReceivedEventHandler(this.HandleProcessRecord), true, false, true); } + _initialized = true; } } @@ -62,10 +62,11 @@ internal void SubscribeEvents(ServerSteppablePipelineDriver driver) #region Events and Handlers public event EventHandler StartSteppablePipeline; + public event EventHandler RunProcessRecord; /// - /// Handles the start pipeline event, this is called by the event manager + /// Handles the start pipeline event, this is called by the event manager. /// /// /// @@ -109,7 +110,7 @@ private void HandleStartEvent(object sender, PSEventArgs args) } /// - /// Handles process record event + /// Handles process record event. /// /// /// @@ -131,6 +132,7 @@ private void HandleProcessRecord(object sender, PSEventArgs args) { return; } + driver.ProcessingInput = true; driver.Pulsed = false; } @@ -160,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; } } @@ -175,6 +177,7 @@ private void HandleProcessRecord(object sender, PSEventArgs args) { output = driver.SteppablePipeline.Process(driver.InputEnumerator.Current); } + foreach (object o in output) { if (driver.PipelineState != PSInvocationState.Running) @@ -264,34 +267,28 @@ private void HandleProcessRecord(object sender, PSEventArgs args) } /// - /// Fires the start event + /// Fires the start event. /// - /// steppable pipeline driver + /// Steppable pipeline driver. 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); } } /// - /// Fires the process record event + /// Fires the process record event. /// - /// steppable pipeline driver + /// Steppable pipeline driver. 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 d09127c86be..9fbcb9e0cf9 100644 --- a/src/System.Management.Automation/engine/remoting/server/WSManChannelEvents.cs +++ b/src/System.Management.Automation/engine/remoting/server/WSManChannelEvents.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation.Remoting.WSMan { @@ -31,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); } /// @@ -43,18 +38,14 @@ 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 } /// - /// Holds the event arguments when active sessions count changed + /// Holds the event arguments when active sessions count changed. /// public sealed class ActiveSessionsChangedEventArgs : EventArgs { @@ -68,7 +59,7 @@ public ActiveSessionsChangedEventArgs(int activeSessionsCount) } /// - /// ActiveSessionsCount + /// ActiveSessionsCount. /// public int ActiveSessionsCount { diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index ada9ba95ab6..b1e05909d2c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -1,19 +1,17 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; -using System.Management.Automation.Runspaces; -using System.Management.Automation.Internal; using System.IO; +using System.Management.Automation.Internal; using System.Management.Automation.Remoting.Server; +using System.Management.Automation.Runspaces; +using System.Threading; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting { /// - /// /// By design, on the server side, each remote connection is represented by /// a ServerRemoteSession object, which contains one instance of this class. /// @@ -68,30 +66,36 @@ internal ServerRemoteSessionContext() /// /// This class controls a remote connection by using a Session data structure handler, which /// in turn contains a Finite State Machine, and a transport mechanism. - /// /// 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; // used to apply quotas on command and session transportmanagers. - private Nullable _maxRecvdObjectSize; - private Nullable _maxRecvdDataSizeCommand; + private int? _maxRecvdObjectSize; + 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; + #region Events /// /// Raised when session is closed. @@ -124,7 +128,7 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, string initializationParameters, AbstractServerSessionTransportManager transportManager) { - Dbg.Assert(null != transportManager, "transportManager cannot be null."); + Dbg.Assert(transportManager != null, "transportManager cannot be null."); // let input,output and error from native commands redirect as we have // to send (or receive) them back to client for action. @@ -132,12 +136,8 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, _senderInfo = senderInfo; _configProviderId = configurationProviderId; _initParameters = initializationParameters; -#if !UNIX _cryptoHelper = (PSRemotingCryptoHelperServer)transportManager.CryptoHelper; _cryptoHelper.Session = this; -#else - _cryptoHelper = null; -#endif Context = new ServerRemoteSessionContext(); SessionDataStructureHandler = new ServerRemoteSessionDSHandlerImpl(this, transportManager); @@ -145,8 +145,7 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, SessionDataStructureHandler.CreateRunspacePoolReceived += HandleCreateRunspacePool; SessionDataStructureHandler.NegotiationReceived += HandleNegotiationReceived; SessionDataStructureHandler.SessionClosing += HandleSessionDSHandlerClosing; - SessionDataStructureHandler.PublicKeyReceived += - new EventHandler>(HandlePublicKeyReceived); + SessionDataStructureHandler.PublicKeyReceived += HandlePublicKeyReceived; transportManager.Closing += HandleResourceClosing; // update the quotas from sessionState..start with default size..and @@ -163,9 +162,9 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, transportManager.ReceivedDataCollection.MaximumReceivedDataSize = null; } -#endregion Constructors + #endregion Constructors -#region Creation Factory + #region Creation Factory /// /// Creates a server remote session for the supplied @@ -178,7 +177,10 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, /// xml. /// /// - /// Optional configuration endpoint name for OutOfProc sessions + /// 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. /// /// /// InitialSessionState provider with does @@ -191,13 +193,18 @@ internal ServerRemoteSession(PSSenderInfo senderInfo, ... */ - internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo, + internal static ServerRemoteSession CreateServerRemoteSession( + PSSenderInfo senderInfo, string configurationProviderId, string initializationParameters, AbstractServerSessionTransportManager transportManager, - string configurationName = null) + string initialCommand, + string configurationName, + string configurationFile, + string initialLocation) { - Dbg.Assert((senderInfo != null) & (senderInfo.UserInfo != null), + Dbg.Assert( + (senderInfo != null) && (senderInfo.UserInfo != null), "senderInfo and userInfo cannot be null."); s_trace.WriteLine("Finding InitialSessionState provider for id : {0}", configurationProviderId); @@ -206,15 +213,20 @@ internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo sende { 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(senderInfo, + ServerRemoteSession result = new ServerRemoteSession( + senderInfo, configurationProviderId, initializationParameters, transportManager) { - _configurationName = configurationName + _initScriptForOutOfProcRS = initialCommand, + _configurationName = configurationName, + _configurationFile = configurationFile, + _initialLocation = initialLocation }; // start state machine. @@ -224,28 +236,9 @@ internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo sende return result; } - /// - /// Used by OutOfProcessServerMediator to create a remote session. - /// - /// - /// - /// - /// - /// - internal static ServerRemoteSession CreateServerRemoteSession(PSSenderInfo senderInfo, - string initializationScriptForOutOfProcessRunspace, - AbstractServerSessionTransportManager transportManager, - string configurationName) - { - ServerRemoteSession result = CreateServerRemoteSession(senderInfo, - "Microsoft.PowerShell", "", transportManager, configurationName); - result._initScriptForOutOfProcRS = initializationScriptForOutOfProcessRunspace; - return result; - } - -#endregion + #endregion -#region Overrides + #region Overrides /// /// This indicates the remote session object is Client, Server or Listener. @@ -274,15 +267,12 @@ 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. /// @@ -290,14 +280,14 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataEven { if (dataEventArg == null) { - throw PSTraceSource.NewArgumentNullException("dataEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(dataEventArg)); } RemoteDataObject rcvdData = dataEventArg.ReceivedData; if (rcvdData == null) { - throw PSTraceSource.NewArgumentException("dataEventArg"); + throw PSTraceSource.NewArgumentException(nameof(dataEventArg)); } RemotingDestination destination = rcvdData.Destination; @@ -334,6 +324,7 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataEven { SessionDataStructureHandler.StateMachine.RaiseEvent(messageReceivedArg); } + break; case RemotingDataType.CloseSession: @@ -353,6 +344,7 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataEven break; } } + break; // TODO: Directly calling an event handler in StateMachine bypassing the StateMachine's @@ -372,21 +364,22 @@ internal void DispatchInputQueueData(object sender, RemoteDataEventArgs dataEven { SessionDataStructureHandler.StateMachine.RaiseEvent(messageReceivedArg); } + break; } } /// /// Have received a public key from the other side - /// Import or take other action based on the state + /// Import or take other action based on the state. /// - /// sender of this event, unused + /// Sender of this event, unused. /// event arguments which contains the /// remote public key private void HandlePublicKeyReceived(object sender, RemoteDataEventArgs eventArgs) { if (SessionDataStructureHandler.StateMachine.State == RemoteSessionState.Established || - SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyRequested || //this is only for legacy clients + SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyRequested || // this is only for legacy clients SessionDataStructureHandler.StateMachine.State == RemoteSessionState.EstablishedAndKeyExchanged) { string remotePublicKey = eventArgs.Data; @@ -409,7 +402,7 @@ private void HandlePublicKeyReceived(object sender, RemoteDataEventArgs } /// - /// Start the key exchange process + /// Start the key exchange process. /// internal override void StartKeyExchange() { @@ -425,7 +418,7 @@ internal override void StartKeyExchange() } /// - /// Complete the Key exchange process + /// Complete the Key exchange process. /// internal override void CompleteKeyExchange() { @@ -433,7 +426,7 @@ internal override void CompleteKeyExchange() } /// - /// Send an encrypted session key to the client + /// Send an encrypted session key to the client. /// internal void SendEncryptedSessionKey() { @@ -460,9 +453,9 @@ internal void SendEncryptedSessionKey() SessionDataStructureHandler.StateMachine.RaiseEvent(args); } -#endregion Overrides + #endregion Overrides -#region Properties + #region Properties /// /// This property returns the ServerRemoteSessionContext object created inside @@ -476,9 +469,9 @@ internal void SendEncryptedSessionKey() /// internal ServerRemoteSessionDataStructureHandler SessionDataStructureHandler { get; } -#endregion + #endregion -#region Private/Internal Methods + #region Private/Internal Methods /// /// Let the session clear its resources. @@ -517,11 +510,11 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) if (totalDataLen < FragmentedRemoteObject.HeaderLength) { - //raise exception + // raise exception throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //TODO: Follow up on comment from Krishna regarding having the serialization/deserialization separate for this + // TODO: Follow up on comment from Krishna regarding having the serialization/deserialization separate for this // operation. This could be integrated as helper functions in fragmentor/serializer components long fragmentId = FragmentedRemoteObject.GetFragmentId(connectData, 0); bool sFlag = FragmentedRemoteObject.GetIsStartFragment(connectData, 0); @@ -530,7 +523,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) if (blobLength > totalDataLen - FragmentedRemoteObject.HeaderLength) { - //raise exception + // raise exception throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } @@ -539,7 +532,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //if session is not in expected state + // if session is not in expected state RemoteSessionState currentState = SessionDataStructureHandler.StateMachine.State; if (currentState != RemoteSessionState.Established && currentState != RemoteSessionState.EstablishedAndKeyExchanged) @@ -547,7 +540,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnServerStateValidation); } - //process first message + // process first message MemoryStream serializedStream = new MemoryStream(); serializedStream.Write(connectData, FragmentedRemoteObject.HeaderLength, blobLength); @@ -564,7 +557,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //process second message + // process second message int secondFragmentLength = totalDataLen - FragmentedRemoteObject.HeaderLength - blobLength; if (secondFragmentLength < FragmentedRemoteObject.HeaderLength) { @@ -589,7 +582,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //process second message + // process second message serializedStream = new MemoryStream(); serializedStream.Write(secondFragment, FragmentedRemoteObject.HeaderLength, blobLength); @@ -606,7 +599,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //We have the two objects required for validating the connect operation + // We have the two objects required for validating the connect operation RemoteSessionCapability clientCapability; try { @@ -623,12 +616,12 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) { RunServerNegotiationAlgorithm(clientCapability, true); } - catch (PSRemotingDataStructureException ex) + catch (PSRemotingDataStructureException) { - throw ex; + throw; } - //validate client connect_runspacepool request + // validate client connect_runspacepool request int clientRequestedMinRunspaces = -1; int clientRequestedMaxRunspaces = -1; bool clientRequestedRunspaceCount = false; @@ -646,7 +639,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) } } - //these should be positive and max should be greater than min + // these should be positive and max should be greater than min if (clientRequestedRunspaceCount && (clientRequestedMinRunspaces == -1 || clientRequestedMaxRunspaces == -1 || clientRequestedMinRunspaces > clientRequestedMaxRunspaces)) { @@ -658,16 +651,16 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnServerStateValidation); } - //currently only one runspace pool per session is allowed. make sure this ID in connect message is the same + // currently only one runspace pool per session is allowed. make sure this ID in connect message is the same if (connectRunspacePoolObject.RunspacePoolId != _runspacePoolDriver.InstanceId) { throw new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnInputValidation); } - //we currently dont support adjusting runspace count on a connect operation. - //there is a potential race here where in the runspace pool driver is still yet to process a queued - //setMax or setMinrunspaces request. - //TODO: resolve this race.. probably by letting the runspace pool consume all messages before we execute this. + // we currently dont support adjusting runspace count on a connect operation. + // there is a potential conflict here where in the runspace pool driver is still yet to process a queued + // setMax or setMinrunspaces request. + // TODO: resolve this.. probably by letting the runspace pool consume all messages before we execute this. if (clientRequestedRunspaceCount && (_runspacePoolDriver.RunspacePool.GetMaxRunspaces() != clientRequestedMaxRunspaces) && (_runspacePoolDriver.RunspacePool.GetMinRunspaces() != clientRequestedMinRunspaces)) @@ -682,11 +675,11 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) _runspacePoolDriver.RunspacePool.GetMaxRunspaces(), _runspacePoolDriver.RunspacePool.GetMinRunspaces()); - //having this stream operating separately will result in out of sync fragment Ids. but this is still OK - //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 + // having this stream operating separately will result in out of sync fragment Ids. but this is still OK + // 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(); @@ -696,13 +689,13 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) byte[] outbuffer = stream.Read(); Dbg.Assert(outbuffer != null, "connect response data should be serialized"); stream.Dispose(); - //we are done + // we are done connectResponseData = outbuffer; - //enqueue a connect event in state machine to let session do any other post-connect operation + // 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); @@ -712,17 +705,10 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) return; } - //pass on application private data when session is connected from new client - internal void HandlePostConnect() - { - if (_runspacePoolDriver != null) - { - _runspacePoolDriver.SendApplicationPrivateDataToClient(); - } - } + // pass on application private data when session is connected from new client + internal void HandlePostConnect() => _runspacePoolDriver?.SendApplicationPrivateDataToClient(); /// - /// /// /// /// @@ -734,25 +720,18 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR { if (createRunspaceEventArg == null) { - throw PSTraceSource.NewArgumentNullException("createRunspaceEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(createRunspaceEventArg)); } + 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 @@ -760,20 +739,15 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR _maxRecvdObjectSize = configurationData.MaxReceivedObjectSizeMB; _maxRecvdDataSizeCommand = configurationData.MaxReceivedCommandSizeMB; - DISCPowerShellConfiguration discProvider = null; - - if (String.IsNullOrEmpty(configurationData.ConfigFilePath)) + if (string.IsNullOrEmpty(configurationData.ConfigFilePath)) { _sessionConfigProvider = configurationData.CreateEndPointConfigurationInstance(); } 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 @@ -784,6 +758,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR if (configurationData.SessionConfigurationData != null) { + // Use the provided WinRM endpoint runspace configuration information. try { rsSessionStateToUse = @@ -794,12 +769,25 @@ 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); } - if (null == rsSessionStateToUse) + if (rsSessionStateToUse == null) { throw PSTraceSource.NewInvalidOperationException(RemotingErrorIdStrings.InitialSessionStateNull, _configProviderId); } @@ -814,31 +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; } } @@ -848,6 +819,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR _maxRecvdObjectSize = _sessionConfigProvider.GetMaximumReceivedObjectSize(_senderInfo); _maxRecvdDataSizeCommand = _sessionConfigProvider.GetMaximumReceivedDataSizePerCommand(_senderInfo); } + SessionDataStructureHandler.TransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = _maxRecvdObjectSize; // MaximumReceivedDataSize is not set for session transport manager...see the constructor // for more info. @@ -856,9 +828,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR int minRunspaces = RemotingDecoder.GetMinRunspaces(rcvdData.Data); int maxRunspaces = RemotingDecoder.GetMaxRunspaces(rcvdData.Data); PSThreadOptions threadOptions = RemotingDecoder.GetThreadOptions(rcvdData.Data); -#if !CORECLR // No ApartmentState In CoreCLR ApartmentState apartmentState = RemotingDecoder.GetApartmentState(rcvdData.Data); -#endif HostInfo hostInfo = RemotingDecoder.GetHostInfo(rcvdData.Data); if (_runspacePoolDriver != null) @@ -878,9 +848,7 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR minRunspaces, maxRunspaces, threadOptions, -#if !CORECLR // No ApartmentState In CoreCLR apartmentState, -#endif hostInfo, rsSessionStateToUse, applicationPrivateData, @@ -889,7 +857,8 @@ private void HandleCreateRunspacePool(object sender, RemoteDataEventArgs createR isAdministrator, Context.ServerCapability, psClientVersion, - _configurationName); + _configurationName, + _initialLocation); // attach the necessary event handlers and start the driver. Interlocked.Exchange(ref _runspacePoolDriver, tmpDriver); @@ -898,14 +867,13 @@ 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. /// /// /// /// This parameter contains the client negotiation capability packet. /// - /// /// /// If the parameter is null. /// @@ -913,7 +881,7 @@ private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEv { if (negotiationEventArg == null) { - throw PSTraceSource.NewArgumentNullException("negotiationEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(negotiationEventArg)); } try @@ -953,14 +921,11 @@ private void HandleNegotiationReceived(object sender, RemoteSessionNegotiationEv /// private void HandleSessionDSHandlerClosing(object sender, EventArgs eventArgs) { - if (null != _runspacePoolDriver) - { - _runspacePoolDriver.Close(); - } + _runspacePoolDriver?.Close(); // dispose the session configuration object..this will let them // clean their resources. - if (null != _sessionConfigProvider) + if (_sessionConfigProvider != null) { _sessionConfigProvider.Dispose(); _sessionConfigProvider = null; @@ -1008,34 +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. - if ((serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) && - (clientProtocolVersion.Major == serverProtocolVersion.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, @@ -1044,47 +996,23 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil RemotingConstants.ProtocolVersion); throw reasonOfFailure; } + + // All other minor versions are supported and the server returns its full capability. + // Client protocol: v2.4, v2.5 ... } else { - // Win10 server can support Win8 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is Win8 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - // Win8, Win10 server can support Win7 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is Win7 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - // Win7, Win8, Win10 server can support Win7 RC client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RC && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) + if (clientProtocolVersion == RemotingConstants.ProtocolVersion_2_0 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_1 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { - // - report that server is RC version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RC; + // We support the those client versions and report the server as the same version to the client. + serverProtocolVersion = clientProtocolVersion; Context.ServerCapability.ProtocolVersion = serverProtocolVersion; } - - if (!((clientProtocolVersion.Major == serverProtocolVersion.Major) && - (clientProtocolVersion.Minor >= serverProtocolVersion.Minor))) + else if (!(clientProtocolVersion.Major == serverProtocolVersion.Major && + clientProtocolVersion.Minor >= serverProtocolVersion.Minor)) { PSRemotingDataStructureException reasonOfFailure = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed, @@ -1130,7 +1058,6 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil } /// - /// /// /// /// @@ -1158,11 +1085,11 @@ internal ServerRunspacePoolDriver GetRunspacePoolDriver(Guid clientRunspacePoolI /// internal void ApplyQuotaOnCommandTransportManager(AbstractServerTransportManager cmdTransportManager) { - Dbg.Assert(null != cmdTransportManager, "cmdTransportManager cannot be null"); + Dbg.Assert(cmdTransportManager != null, "cmdTransportManager cannot be null"); cmdTransportManager.ReceivedDataCollection.MaximumReceivedDataSize = _maxRecvdDataSizeCommand; cmdTransportManager.ReceivedDataCollection.MaximumReceivedObjectSize = _maxRecvdObjectSize; } -#endregion + #endregion } } diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs index e87557155f4..67d47ea404c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs @@ -1,9 +1,9 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; using System.Collections.Generic; +using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Remoting @@ -31,13 +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 readonly ServerRemoteSession _session; + private readonly object _syncObject; - private ServerRemoteSession _session; - private 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 @@ -46,17 +48,16 @@ private Queue _processPendingEventsQueue // and processed private bool _eventsInProcess = false; - private EventHandler[,] _stateMachineHandle; + private readonly EventHandler[,] _stateMachineHandle; private RemoteSessionState _state; /// - /// timer used for key exchange + /// Timer used for key exchange. /// private Timer _keyExchangeTimer; #region Constructors - /// /// This constructor instantiates a FSM object for the server side to control the remote connection. /// It initializes the event handling matrix with event handlers. @@ -72,7 +73,7 @@ internal ServerRemoteSessionDSHandlerStateMachine(ServerRemoteSession session) { if (session == null) { - throw PSTraceSource.NewArgumentNullException("session"); + throw PSTraceSource.NewArgumentNullException(nameof(session)); } _session = session; @@ -123,13 +124,11 @@ internal ServerRemoteSessionDSHandlerStateMachine(ServerRemoteSession session) _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyReceived, (int)RemoteSessionEvent.KeySendFailed] += DoKeyExchange; _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyReceived, (int)RemoteSessionEvent.KeySent] += DoKeyExchange; - //with connect, a new client can negotiate a key change to a server that has already negotiated key exchange with a previous client + // with connect, a new client can negotiate a key change to a server that has already negotiated key exchange with a previous client _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyExchanged, (int)RemoteSessionEvent.KeyReceived] += DoKeyExchange; // _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyExchanged, (int)RemoteSessionEvent.KeyRequested] += DoKeyExchange; // _stateMachineHandle[(int)RemoteSessionState.EstablishedAndKeyExchanged, (int)RemoteSessionEvent.KeyReceiveFailed] += DoKeyExchange; // - - for (int i = 0; i < _stateMachineHandle.GetLength(0); i++) { for (int j = 0; j < _stateMachineHandle.GetLength(1); j++) @@ -171,7 +170,7 @@ internal bool CanByPassRaiseEvent(RemoteSessionStateMachineEventArgs arg) if (arg.StateEvent == RemoteSessionEvent.MessageReceived) { if (_state == RemoteSessionState.Established || - _state == RemoteSessionState.EstablishedAndKeySent || //server session will never be in this state.. TODO- remove this + _state == RemoteSessionState.EstablishedAndKeySent || // server session will never be in this state.. TODO- remove this _state == RemoteSessionState.EstablishedAndKeyReceived || _state == RemoteSessionState.EstablishedAndKeyExchanged) { @@ -205,6 +204,7 @@ internal void RaiseEvent(RemoteSessionStateMachineEventArgs fsmEventArg) { return; } + _eventsInProcess = true; } @@ -212,15 +212,15 @@ internal void RaiseEvent(RemoteSessionStateMachineEventArgs fsmEventArg) // currently server state machine doesn't raise events // this will allow server state machine to raise events. - //RaiseStateMachineEvents(); + // RaiseStateMachineEvents(); } /// - /// processes events in the queue. If there are no + /// Processes events in the queue. If there are no /// more events to process, then sets eventsInProcess /// variable to false. This will ensure that another /// thread which raises an event can then take control - /// of processing the events + /// of processing the events. /// private void ProcessEvents() { @@ -235,6 +235,7 @@ private void ProcessEvents() _eventsInProcess = false; break; } + eventArgs = _processPendingEventsQueue.Dequeue(); } @@ -251,7 +252,6 @@ private void ProcessEvents() /// /// The parameter contains the actual FSM event. /// - /// /// /// If the parameter is null. /// @@ -259,7 +259,7 @@ private void RaiseEventPrivate(RemoteSessionStateMachineEventArgs fsmEventArg) { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } EventHandler handler = _stateMachineHandle[(int)_state, (int)fsmEventArg.StateEvent]; @@ -284,7 +284,6 @@ private void RaiseEventPrivate(RemoteSessionStateMachineEventArgs fsmEventArg) /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -294,7 +293,7 @@ private void DoCreateSession(object sender, RemoteSessionStateMachineEventArgs f { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.CreateSession, "StateEvent must be CreateSession"); @@ -323,7 +322,7 @@ private void DoNegotiationPending(object sender, RemoteSessionStateMachineEventA { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert((_state == RemoteSessionState.Idle) || (_state == RemoteSessionState.NegotiationSent), @@ -341,7 +340,6 @@ private void DoNegotiationPending(object sender, RemoteSessionStateMachineEventA /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -349,14 +347,13 @@ 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()) { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.NegotiationReceived, "StateEvent must be NegotiationReceived"); @@ -365,12 +362,12 @@ private void DoNegotiationReceived(object sender, RemoteSessionStateMachineEvent if (fsmEventArg.StateEvent != RemoteSessionEvent.NegotiationReceived) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } if (fsmEventArg.RemoteSessionCapability == null) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } SetState(RemoteSessionState.NegotiationReceived, null); @@ -386,16 +383,14 @@ private void DoNegotiationReceived(object sender, RemoteSessionStateMachineEvent /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// - private void DoNegotiationSending(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.NegotiationSending, "Event must be NegotiationSending"); @@ -414,18 +409,16 @@ private void DoNegotiationSending(object sender, RemoteSessionStateMachineEventA /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// - private void DoNegotiationCompleted(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { using (s_trace.TraceEventHandlers()) { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(_state == RemoteSessionState.NegotiationSending, "State must be NegotiationSending"); @@ -443,18 +436,16 @@ private void DoNegotiationCompleted(object sender, RemoteSessionStateMachineEven /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// - private void DoEstablished(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { using (s_trace.TraceEventHandlers()) { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(_state == RemoteSessionState.NegotiationSent, "State must be NegotiationReceived"); @@ -462,7 +453,7 @@ private void DoEstablished(object sender, RemoteSessionStateMachineEventArgs fsm if (fsmEventArg.StateEvent != RemoteSessionEvent.NegotiationCompleted) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } if (_state != RemoteSessionState.NegotiationSent) @@ -482,7 +473,6 @@ private void DoEstablished(object sender, RemoteSessionStateMachineEventArgs fsm /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -495,18 +485,18 @@ internal void DoMessageReceived(object sender, RemoteSessionStateMachineEventArg { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } if (fsmEventArg.RemoteData == null) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } Dbg.Assert(_state == RemoteSessionState.Established || _state == RemoteSessionState.EstablishedAndKeyExchanged || _state == RemoteSessionState.EstablishedAndKeyReceived || - _state == RemoteSessionState.EstablishedAndKeySent, //server session will never be in this state.. TODO- remove this + _state == RemoteSessionState.EstablishedAndKeySent, // server session will never be in this state.. TODO- remove this "State must be Established or EstablishedAndKeySent or EstablishedAndKeyReceived or EstablishedAndKeyExchanged"); RemotingTargetInterface targetInterface = fsmEventArg.RemoteData.TargetInterface; @@ -514,7 +504,7 @@ internal void DoMessageReceived(object sender, RemoteSessionStateMachineEventArg Guid clientRunspacePoolId; ServerRunspacePoolDriver runspacePoolDriver; - //string errorMessage = null; + // string errorMessage = null; RemoteDataEventArgs remoteDataForSessionArg = null; @@ -535,6 +525,7 @@ internal void DoMessageReceived(object sender, RemoteSessionStateMachineEventArg break; } } + break; case RemotingTargetInterface.RunspacePool: @@ -587,7 +578,6 @@ internal void DoMessageReceived(object sender, RemoteSessionStateMachineEventArg /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -600,14 +590,14 @@ private void DoConnectFailed(object sender, RemoteSessionStateMachineEventArgs f { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.ConnectFailed, "StateEvent must be ConnectFailed"); if (fsmEventArg.StateEvent != RemoteSessionEvent.ConnectFailed) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } Dbg.Assert(_state == RemoteSessionState.Connecting, "session State must be Connecting"); @@ -625,11 +615,9 @@ private void DoConnectFailed(object sender, RemoteSessionStateMachineEventArgs f /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// - /// /// /// If the parameter does not contains FatalError event. /// @@ -639,14 +627,14 @@ private void DoFatalError(object sender, RemoteSessionStateMachineEventArgs fsmE { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.FatalError, "StateEvent must be FatalError"); if (fsmEventArg.StateEvent != RemoteSessionEvent.FatalError) { - throw PSTraceSource.NewArgumentException("fsmEventArg"); + throw PSTraceSource.NewArgumentException(nameof(fsmEventArg)); } DoClose(this, fsmEventArg); @@ -655,7 +643,7 @@ private void DoFatalError(object sender, RemoteSessionStateMachineEventArgs fsmE /// /// Handle connect event - this is raised when a new client tries to connect to an existing session - /// No changes to state. Calls into the session to handle any post connect operations + /// No changes to state. Calls into the session to handle any post connect operations. /// /// /// @@ -675,7 +663,6 @@ private void DoConnect(object sender, RemoteSessionStateMachineEventArgs fsmEven /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -685,7 +672,7 @@ private void DoClose(object sender, RemoteSessionStateMachineEventArgs fsmEventA { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } RemoteSessionState oldState = _state; @@ -700,7 +687,7 @@ private void DoClose(object sender, RemoteSessionStateMachineEventArgs fsmEventA case RemoteSessionState.Connecting: case RemoteSessionState.Connected: case RemoteSessionState.Established: - case RemoteSessionState.EstablishedAndKeySent: //server session will never be in this state.. TODO- remove this + case RemoteSessionState.EstablishedAndKeySent: // server session will never be in this state.. TODO- remove this case RemoteSessionState.EstablishedAndKeyReceived: case RemoteSessionState.EstablishedAndKeyExchanged: case RemoteSessionState.NegotiationReceived: @@ -730,7 +717,6 @@ private void DoClose(object sender, RemoteSessionStateMachineEventArgs fsmEventA /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -740,7 +726,7 @@ private void DoCloseFailed(object sender, RemoteSessionStateMachineEventArgs fsm { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.CloseFailed, "StateEvent must be CloseFailed"); @@ -761,7 +747,6 @@ private void DoCloseFailed(object sender, RemoteSessionStateMachineEventArgs fsm /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -771,7 +756,7 @@ private void DoCloseCompleted(object sender, RemoteSessionStateMachineEventArgs { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.CloseCompleted, "StateEvent must be CloseCompleted"); @@ -792,7 +777,6 @@ private void DoCloseCompleted(object sender, RemoteSessionStateMachineEventArgs /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -802,7 +786,7 @@ private void DoNegotiationFailed(object sender, RemoteSessionStateMachineEventAr { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.NegotiationFailed, "StateEvent must be NegotiationFailed"); @@ -822,7 +806,6 @@ private void DoNegotiationFailed(object sender, RemoteSessionStateMachineEventAr /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -832,7 +815,7 @@ private void DoNegotiationTimeout(object sender, RemoteSessionStateMachineEventA { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.NegotiationTimeout, "StateEvent must be NegotiationTimeout"); @@ -858,7 +841,6 @@ private void DoNegotiationTimeout(object sender, RemoteSessionStateMachineEventA /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -868,7 +850,7 @@ private void DoSendFailed(object sender, RemoteSessionStateMachineEventArgs fsmE { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.SendFailed, "StateEvent must be SendFailed"); @@ -888,7 +870,6 @@ private void DoSendFailed(object sender, RemoteSessionStateMachineEventArgs fsmE /// /// This parameter contains the FSM event. /// - /// /// /// If the parameter is null. /// @@ -898,7 +879,7 @@ private void DoReceiveFailed(object sender, RemoteSessionStateMachineEventArgs f { if (fsmEventArg == null) { - throw PSTraceSource.NewArgumentNullException("fsmEventArg"); + throw PSTraceSource.NewArgumentNullException(nameof(fsmEventArg)); } Dbg.Assert(fsmEventArg.StateEvent == RemoteSessionEvent.ReceiveFailed, "StateEvent must be ReceivedFailed"); @@ -911,15 +892,15 @@ private void DoReceiveFailed(object sender, RemoteSessionStateMachineEventArgs f /// /// This method contains all the logic for handling the state machine - /// for key exchange. All the different scenarios are covered in this + /// for key exchange. All the different scenarios are covered in this. /// - /// sender of this event, unused - /// event args + /// Sender of this event, unused. + /// Event args. private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eventArgs) { - //There are corner cases with disconnect that can result in client receiving outdated key exchange packets - //***TODO*** Deal with this on the client side. Key exchange packets should have additional information - //that identify the context of negotiation. Just like callId in SetMax and SetMinRunspaces messages + // There are corner cases with disconnect that can result in client receiving outdated key exchange packets + // ***TODO*** Deal with this on the client side. Key exchange packets should have additional information + // that identify the context of negotiation. Just like callId in SetMax and SetMinRunspaces messages Dbg.Assert(_state >= RemoteSessionState.Established, "Key Receiving can only be raised after reaching the Established state"); @@ -927,15 +908,12 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve { case RemoteSessionEvent.KeyReceived: { - //does the server ever start key exchange process??? This may not be required + // does the server ever start key exchange process??? This may not be required if (_state == RemoteSessionState.EstablishedAndKeyRequested) { // reset the timer Timer tmp = Interlocked.Exchange(ref _keyExchangeTimer, null); - if (tmp != null) - { - tmp.Dispose(); - } + tmp?.Dispose(); } // the key import would have been done @@ -945,6 +923,7 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve // you need to send an encrypted session key to the client _session.SendEncryptedSessionKey(); } + break; case RemoteSessionEvent.KeySent: @@ -955,6 +934,7 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve SetState(RemoteSessionState.EstablishedAndKeyExchanged, eventArgs.Reason); } } + break; case RemoteSessionEvent.KeyRequested: @@ -968,6 +948,7 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve _keyExchangeTimer = new Timer(HandleKeyExchangeTimeout, null, BaseTransportManager.ServerDefaultKeepAliveTimeoutMs, Timeout.Infinite); } } + break; case RemoteSessionEvent.KeyReceiveFailed: @@ -979,35 +960,34 @@ private void DoKeyExchange(object sender, RemoteSessionStateMachineEventArgs eve DoClose(this, eventArgs); } + break; case RemoteSessionEvent.KeySendFailed: { DoClose(this, eventArgs); } + break; } } /// - /// Handles the timeout for key exchange + /// Handles the timeout for key exchange. /// - /// sender of this event + /// Sender of this event. 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); RaiseEvent(new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.KeyReceiveFailed, exception)); - } // SetStateHandler + } #endregion Event Handlers @@ -1016,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/serverremotingprotocol.cs b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocol.cs index 88010ddb3e2..cb8b8060a52 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotingprotocol.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocol.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; @@ -62,7 +61,7 @@ internal ServerRemoteSessionDataStructureHandler() /// /// This event indicates a request for creating a new runspace pool - /// has been received on the server side + /// has been received on the server side. /// internal abstract event EventHandler CreateRunspacePoolReceived; @@ -75,7 +74,7 @@ internal abstract ServerRemoteSessionDSHandlerStateMachine StateMachine } /// - /// Transport manager used by this data structure handler + /// Transport manager used by this data structure handler. /// internal abstract AbstractServerSessionTransportManager TransportManager { @@ -90,7 +89,7 @@ internal abstract AbstractServerSessionTransportManager TransportManager /// internal abstract void RaiseDataReceivedEvent(RemoteDataEventArgs arg); // this is the API the Transport calls - internal abstract event EventHandler> PublicKeyReceived; + internal abstract event EventHandler> PublicKeyReceived; internal abstract void SendRequestForPublicKey(); diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs index a2d9604d14c..06fbca348cc 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs @@ -1,6 +1,5 @@ -/********************************************************************++ - * Copyright (c) Microsoft Corporation. All rights reserved. - * --********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Remoting.Server; @@ -13,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 { @@ -37,8 +36,8 @@ internal override AbstractServerSessionTransportManager TransportManager internal ServerRemoteSessionDSHandlerImpl(ServerRemoteSession session, AbstractServerSessionTransportManager transportManager) { - Dbg.Assert(null != session, "session cannot be null."); - Dbg.Assert(null != transportManager, "transportManager cannot be null."); + Dbg.Assert(session != null, "session cannot be null."); + Dbg.Assert(transportManager != null, "transportManager cannot be null."); _session = session; _stateMachine = new ServerRemoteSessionDSHandlerStateMachine(session); @@ -92,7 +91,7 @@ internal override void SendNegotiationAsync() internal override event EventHandler> PublicKeyReceived; /// - /// Send the encrypted session key to the client side + /// Send the encrypted session key to the client side. /// /// encrypted session key /// as a string @@ -103,7 +102,7 @@ internal override void SendEncryptedSessionKey(string encryptedSessionKey) } /// - /// Send request to the client for sending a public key + /// Send request to the client for sending a public key. /// internal override void SendRequestForPublicKey() { @@ -112,9 +111,9 @@ internal override void SendRequestForPublicKey() } /// - /// Raise the public key received event + /// Raise the public key received event. /// - /// received data + /// Received data. /// This method is a hook to be called /// from the transport manager internal override void RaiseKeyExchangeMessageReceived(RemoteDataObject receivedData) @@ -144,7 +143,7 @@ internal override void CloseConnectionAsync(Exception reasonForClose) /// /// This event indicates that the client has requested to create a new runspace pool - /// on the server side + /// on the server side. /// internal override event EventHandler CreateRunspacePoolReceived; @@ -166,7 +165,6 @@ internal override ServerRemoteSessionDSHandlerStateMachine StateMachine /// /// The received client data. /// - /// /// /// If the parameter is null. /// @@ -174,7 +172,7 @@ internal override void RaiseDataReceivedEvent(RemoteDataEventArgs dataArg) { if (dataArg == null) { - throw PSTraceSource.NewArgumentNullException("dataArg"); + throw PSTraceSource.NewArgumentNullException(nameof(dataArg)); } RemoteDataObject rcvdData = dataArg.ReceivedData; @@ -192,6 +190,7 @@ internal override void RaiseDataReceivedEvent(RemoteDataEventArgs dataArg) // need to import the clients public key CreateRunspacePoolReceived.SafeInvoke(this, dataArg); } + break; case RemotingDataType.CloseSession: @@ -224,6 +223,7 @@ internal override void RaiseDataReceivedEvent(RemoteDataEventArgs dataArg) negotiationArg.RemoteData = rcvdData; NegotiationReceived.SafeInvoke(this, negotiationArg); } + break; case RemotingDataType.PublicKey: @@ -231,6 +231,7 @@ internal override void RaiseDataReceivedEvent(RemoteDataEventArgs dataArg) string remotePublicKey = RemotingDecoder.GetPublicKey(rcvdData.Data); PublicKeyReceived.SafeInvoke(this, new RemoteDataEventArgs(remotePublicKey)); } + break; default: @@ -241,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 536350cc661..027c4886380 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -1,7 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -13,14 +13,15 @@ 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; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; - namespace System.Management.Automation.Language { // Item1 - member name @@ -80,7 +81,7 @@ internal static DynamicMetaObject WriteToDebugLog(this DynamicMetaObject obj, Dy internal static BindingRestrictions GetSimpleTypeRestriction(this DynamicMetaObject obj) { - if (obj.Value == null || ClrFacade.IsTransparentProxy(obj.Value)) + if (obj.Value == null) { return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value); } @@ -94,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; @@ -133,7 +134,7 @@ internal static BindingRestrictions PSGetStaticMemberRestriction(this DynamicMet return obj.Restrictions; } - if (obj.Value == null || ClrFacade.IsTransparentProxy(obj.Value)) + if (obj.Value == null) { return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value); } @@ -188,7 +189,7 @@ internal static BindingRestrictions PSGetTypeRestriction(this DynamicMetaObject return obj.Restrictions; } - if (obj.Value == null || ClrFacade.IsTransparentProxy(obj.Value)) + if (obj.Value == null) { return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value); } @@ -248,6 +249,7 @@ internal static Expression CastOrConvertMethodArgument(this DynamicMetaObject ta Type parameterType, string parameterName, string methodName, + bool allowCastingToByRefLikeType, List temps, List initTemps) { @@ -268,13 +270,27 @@ internal static Expression CastOrConvertMethodArgument(this DynamicMetaObject ta return target.Expression.Cast(parameterType); } + ConversionRank? rank = null; + if (parameterType.IsByRefLike && allowCastingToByRefLikeType) + { + var conversionResult = PSConvertBinder.ConvertToByRefLikeTypeViaCasting(target, parameterType); + if (conversionResult != null) + { + return conversionResult; + } + + rank = ConversionRank.None; + } + var exceptionParam = Expression.Variable(typeof(Exception)); var targetTemp = Expression.Variable(target.Expression.Type); - bool debase; + bool debase = false; // ConstrainedLanguage note - calls to this conversion are covered by the method resolution algorithm // (which ignores method arguments with disallowed types) - var conversion = LanguagePrimitives.FigureConversion(target.Value, parameterType, out debase); + var conversion = rank == ConversionRank.None + ? LanguagePrimitives.NoConversion + : LanguagePrimitives.FigureConversion(target.Value, parameterType, out debase); var invokeConverter = PSConvertBinder.InvokeConverter(conversion, targetTemp, parameterType, debase, ExpressionCache.InvariantCulture); var expr = Expression.Block(new[] { targetTemp }, @@ -403,6 +419,7 @@ private static Expression ProcessOnePSObject(DynamicMetaObject arg, ref BindingR expr = arg.Expression; restrictions = restrictions.Merge(arg.PSGetTypeRestriction()); } + return expr; } @@ -412,7 +429,7 @@ internal static DynamicMetaObject UpdateComRestrictionsForPsObject(this DynamicM BindingRestrictions newRestrictions = binder.Restrictions; newRestrictions = args.Aggregate(newRestrictions, (current, arg) => { - if (arg.LimitType.GetTypeInfo().IsValueType) + if (arg.LimitType.IsValueType) { return current.Merge(arg.GetSimpleTypeRestriction()); } @@ -429,7 +446,7 @@ internal static DynamicMetaObject UpdateComRestrictionsForPsObject(this DynamicM } } - internal class BinderUtils + internal static class BinderUtils { internal static BindingRestrictions GetVersionCheck(DynamicMetaObjectBinder binder, int expectedVersionNumber) { @@ -462,7 +479,7 @@ internal static BindingRestrictions GetLanguageModeCheckIfHasEverUsedConstrained return BindingRestrictions.GetExpressionRestriction( Expression.Block( - new[] {tmp}, + new[] { tmp }, Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS), test)); } @@ -498,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(); @@ -535,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); } @@ -570,7 +587,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna if (target.Value == AutomationNull.Value) { return new DynamicMetaObject( - Expression.Call(Expression.Constant(Utils.EmptyArray()), typeof(Array).GetMethod("GetEnumerator")), + Expression.Call(Expression.Constant(Array.Empty()), typeof(Array).GetMethod("GetEnumerator")), BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this); } @@ -584,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); } @@ -621,7 +638,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna GetRestrictions(target))).WriteToDebugLog(this); } - if (Utils.IsComObject(targetValue)) + if (Marshal.IsComObject(targetValue)) { // Pretend that all com objects are enumerable, even if they aren't. We do this because it's technically impossible // to know if a com object is enumerable without just trying to cast it to IEnumerable. We could generate a rule like: @@ -648,7 +665,7 @@ public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, Dyna foreach (var i in targetValue.GetType().GetInterfaces()) { - if (i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return (new DynamicMetaObject( MaybeDebase(this, e => Expression.Call( @@ -659,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); } @@ -668,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); } @@ -691,7 +708,7 @@ internal static bool IsStaticTypePossiblyEnumerable(Type type) return false; } - if (type.GetTypeInfo().IsSealed && !typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IEnumerator).IsAssignableFrom(type)) + if (type.IsSealed && !typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IEnumerator).IsAssignableFrom(type)) { return false; } @@ -711,13 +728,22 @@ internal static DynamicMetaObject IsEnumerable(DynamicMetaObject target) private static IEnumerator AutomationNullRule(CallSite site, object obj) { return obj == AutomationNull.Value - ? Utils.EmptyArray().GetEnumerator() + ? Array.Empty().GetEnumerator() : ((CallSite>)site).Update(site, obj); } private static IEnumerator NotEnumerableRule(CallSite site, object obj) { - if (!(obj is PSObject) && !(obj is IEnumerable) && !(obj is IEnumerator) && !(obj is DataTable) && !Utils.IsComObject(obj)) + if (obj == null) + { + return null; + } + + if (obj is not PSObject + && obj is not IEnumerable + && obj is not IEnumerator + && obj is not DataTable + && !Marshal.IsComObject(obj)) { return null; } @@ -756,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(); @@ -785,7 +811,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje if (target.Value == AutomationNull.Value) { - return new DynamicMetaObject(Expression.Constant(Utils.EmptyArray()), + return new DynamicMetaObject(Expression.Constant(Array.Empty()), BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this); } @@ -801,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); } @@ -812,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(); @@ -851,12 +877,11 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value))).WriteToDebugLog(this); } - var enumerable = PSEnumerableBinder.IsEnumerable(target); if (enumerable == null) { - var bindingResult = PSVariableAssignmentBinder.Get().Bind(target, Utils.EmptyArray()); - var restrictions = target.LimitType.GetTypeInfo().IsValueType + var bindingResult = PSVariableAssignmentBinder.Get().Bind(target, Array.Empty()); + var restrictions = target.LimitType.IsValueType ? bindingResult.Restrictions : target.PSGetTypeRestriction(); return (new DynamicMetaObject( @@ -866,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, @@ -907,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; @@ -920,6 +945,7 @@ internal static PSArrayAssignmentRHSBinder Get(int i) { s_binders.Add(null); } + return s_binders[i] ?? (s_binders[i] = new PSArrayAssignmentRHSBinder(i)); } } @@ -931,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); } } @@ -979,6 +1005,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje newArrayElements[i] = Expression.Call(temp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i)); } + for (; i < _elements; ++i) { newArrayElements[i] = ExpressionCache.NullConstant; @@ -1018,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(); @@ -1087,9 +1114,9 @@ internal static Expression InvokeToString(Expression context, Expression target) } /// - /// This binder is used to optimize the conversion of the result + /// 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(); @@ -1115,10 +1142,11 @@ 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)); } + var countExpr = Expression.Property( Expression.Convert(ilistExpr, typeof(ICollection)), CachedReflectionInfo.ICollection_Count); @@ -1149,14 +1177,14 @@ 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) { return x.Item1.Equals(y.Item1) && - x.Item2 == null ? y.Item2 == null : x.Item2.Equals(y.Item2) && + ((x.Item2 == null) ? y.Item2 == null : x.Item2.Equals(y.Item2)) && x.Item3 == y.Item3 && x.Item4 == y.Item4 && x.Item5 == y.Item5; @@ -1176,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); @@ -1186,6 +1214,7 @@ internal static PSInvokeDynamicMemberBinder Get(CallInfo callInfo, TypeDefinitio s_binderCache.Add(key, result); } } + return result; } @@ -1259,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()); @@ -1269,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)) { @@ -1283,6 +1312,7 @@ internal static PSGetDynamicMemberBinder Get(TypeDefinitionAst classScope, bool private readonly bool _static; private readonly Type _classScope; + private PSGetDynamicMemberBinder(Type classScope, bool @static) { _static = @static; @@ -1363,11 +1393,12 @@ internal static object GetIDictionaryMember(IDictionary hash, object key) throw new PropertyNotFoundException("PropertyNotFoundStrict", null, ParserStrings.PropertyNotFoundStrict, LanguagePrimitives.ConvertTo(key)); } + return null; } } - internal class PSSetDynamicMemberBinder : DynamicMetaObjectBinder + internal sealed class PSSetDynamicMemberBinder : DynamicMetaObjectBinder { private static readonly Dictionary s_binderCache = new Dictionary(new PSDynamicGetOrSetBinderKeyComparer()); @@ -1377,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)) { @@ -1459,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 @@ -1579,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(); @@ -1594,6 +1625,7 @@ internal static PSAttributeGenerator Get(CallInfo callInfo) binder = new PSAttributeGenerator(callInfo); s_binderCache.Add(callInfo, binder); } + return binder; } } @@ -1618,8 +1650,17 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe bool expandParamsOnBest; bool callNonVirtually; var positionalArgCount = CallInfo.ArgumentCount - CallInfo.ArgumentNames.Count; - var bestMethod = Adapter.FindBestMethod(newConstructors, null, - args.Take(positionalArgCount).Select(arg => arg.Value).ToArray(), ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually); + + var bestMethod = Adapter.FindBestMethod( + newConstructors, + invocationConstraints: null, + allowCastingToByRefLikeType: false, + args.Take(positionalArgCount).Select(static arg => arg.Value).ToArray(), + ref errorId, + ref errorMsg, + out expandParamsOnBest, + out callNonVirtually); + if (bestMethod == null) { return errorSuggestion ?? new DynamicMetaObject( @@ -1648,7 +1689,7 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe // This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling // against CoreCLR. So we need to add a null check until this is fixed in CLR. var paramArrayAttrs = parameterInfo[argIndex].GetCustomAttributes(typeof(ParamArrayAttribute), true); - if (paramArrayAttrs != null && paramArrayAttrs.Any() && expandParamsOnBest) + if (paramArrayAttrs != null && paramArrayAttrs.Length > 0 && expandParamsOnBest) { var elementType = parameterInfo[argIndex].ParameterType.GetElementType(); var paramsArray = new List(); @@ -1673,20 +1714,13 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe } else { - bool debase; - - // ConstrainedLanguage note - calls to this conversion are done by constructors with params arguments. - // Protection against conversions are covered by the method resolution algorithm - // (which ignores method arguments with disallowed types) - var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, resultType, out debase); - Diagnostics.Assert(conversion.Rank != ConversionRank.None, "FindBestMethod should have failed if there is no conversion"); - + var conversion = LanguagePrimitives.FigureConversion(args[argIndex].Value, resultType, out bool debase); ctorArgs[argIndex] = PSConvertBinder.InvokeConverter(conversion, args[argIndex].Expression, resultType, debase, ExpressionCache.InvariantCulture); } } Expression result = Expression.New(constructorInfo, ctorArgs); - if (CallInfo.ArgumentNames.Any()) + if (CallInfo.ArgumentNames.Count > 0) { var tmp = Expression.Parameter(result.Type); var blockExprs = new List(); @@ -1694,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), @@ -1712,6 +1747,7 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "PropertyIsReadOnly", ParserStrings.PropertyIsReadOnly, Expression.Constant(name)); } + propertyType = propertyInfo.PropertyType; lhs = Expression.Property(tmp.Cast(propertyInfo.DeclaringType), propertyInfo); } @@ -1755,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(); @@ -1791,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(); @@ -1829,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; @@ -1874,7 +1910,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje { var baseObjType = baseObject.GetType(); restrictions = restrictions.Merge(BindingRestrictions.GetTypeRestriction(baseObjExpr, baseObjType)); - if (baseObjType.GetTypeInfo().IsValueType) + if (baseObjType.IsValueType) { expr = GetExprForValueType(baseObjType, Expression.Convert(baseObjExpr, baseObjType), expr, ref restrictions); } @@ -1891,7 +1927,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje } var type = value.GetType(); - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { var expr = target.Expression; var restrictions = target.PSGetTypeRestriction(); @@ -1955,30 +1991,35 @@ private static Expression GetExprForValueType(Type type, { restrictions = restrictions.Merge(GetVersionCheck(s_mutableValueWithInstanceMemberVersion)); } + return expr; } private static object EnumRule(CallSite site, object obj) { if (obj is Enum) { return obj; } + return ((CallSite>)site).Update(site, obj); } private static object BoolRule(CallSite site, object obj) { if (obj is bool) { return obj; } + return ((CallSite>)site).Update(site, obj); } private static object IntRule(CallSite site, object obj) { if (obj is int) { return obj; } + return ((CallSite>)site).Update(site, 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); } @@ -1996,37 +2037,39 @@ private static object NullRule(CallSite site, object obj) internal static bool IsValueTypeMutable(Type type) { - TypeInfo typeInfo = type.GetTypeInfo(); - if (typeInfo.IsPrimitive || typeInfo.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).Any()) + // 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; } @@ -2035,7 +2078,7 @@ internal static bool IsValueTypeMutable(Type type) internal static void NoteTypeHasInstanceMemberOrTypeName(Type type) { - if (!type.GetTypeInfo().IsValueType || !IsValueTypeMutable(type)) + if (!type.IsValueType || !IsValueTypeMutable(type)) { return; } @@ -2048,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()); @@ -2075,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 @@ -2094,6 +2135,7 @@ internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignor s_binderCache.Add(key, result); } } + return result; } @@ -2128,6 +2170,7 @@ private Func GetScalarCompareDelegate() lvalExpr, rvalExpr).Compile(); Interlocked.CompareExchange(ref _compareDelegate, compareDelegate, null); } + return _compareDelegate; } @@ -2198,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" : "", _version); + return string.Format( + CultureInfo.InvariantCulture, + "PSBinaryOperationBinder {0}{1} ver:{2}", + GetOperatorText(), + _scalarCompare ? " scalarOnly" : string.Empty, + _version); } internal static void InvalidateCache() @@ -2234,8 +2282,9 @@ private string GetOperatorText() case ExpressionType.LeftShift: return TokenKind.Shl.Text(); case ExpressionType.RightShift: return TokenKind.Shr.Text(); } + Diagnostics.Assert(false, "Unexpected operator"); - return ""; + return string.Empty; } private static DynamicMetaObject CallImplicitOp(string methodName, DynamicMetaObject target, DynamicMetaObject arg, string errorOperator, DynamicMetaObject errorSuggestion) @@ -2421,6 +2470,7 @@ private DynamicMetaObject BinaryNumericOp(string methodName, DynamicMetaObject t target.CombineRestrictions(arg)); } } + opImplType = typeof(DecimalOps); argType = typeof(decimal); } @@ -2460,7 +2510,7 @@ private DynamicMetaObject BinaryNumericOp(string methodName, DynamicMetaObject t catchExpr); } - if (target.LimitType.GetTypeInfo().IsEnum) + if (target.LimitType.IsEnum) { // Make sure the result type is an enum unless we were expecting a bool. switch (Operation) @@ -2550,7 +2600,12 @@ internal static Expression ConvertStringToNumber(Expression expr, Type toType) // would string. Fall back to int if Parser.ScanNumber fails. toType = typeof(int); } - return Expression.Call(CachedReflectionInfo.Parser_ScanNumber, expr.Cast(typeof(string)), Expression.Constant(toType, typeof(Type))); + + return Expression.Call( + CachedReflectionInfo.Parser_ScanNumber, + expr.Cast(typeof(string)), + Expression.Constant(toType, typeof(Type)), + Expression.Constant(true)); } private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject arg, Type targetType) @@ -2561,9 +2616,9 @@ private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject ar } bool boolToDecimal = false; - if (arg.LimitType.IsNumericOrPrimitive() && !arg.LimitType.GetTypeInfo().IsEnum) + if (arg.LimitType.IsNumericOrPrimitive() && !arg.LimitType.IsEnum) { - if (!(targetType == typeof(Decimal) && arg.LimitType == typeof(bool))) + if (!(targetType == typeof(decimal) && arg.LimitType == typeof(bool))) { return arg; } @@ -2576,7 +2631,7 @@ private static DynamicMetaObject GetArgAsNumericOrPrimitive(DynamicMetaObject ar // ConstrainedLanguage note - calls to this conversion only target numeric types. var conversion = LanguagePrimitives.FigureConversion(arg.Value, targetType, out debase); - if (conversion.Rank == ConversionRank.ImplicitCast || boolToDecimal || arg.LimitType.GetTypeInfo().IsEnum) + if (conversion.Rank == ConversionRank.ImplicitCast || boolToDecimal || arg.LimitType.IsEnum) { return new DynamicMetaObject( PSConvertBinder.InvokeConverter(conversion, arg.Expression, targetType, debase, ExpressionCache.InvariantCulture), @@ -2594,6 +2649,7 @@ private static Type GetBitwiseOpType(TypeCode opTypeCode) else if ((int)opTypeCode <= (int)TypeCode.Int64) { opType = typeof(long); } // Because we use unsigned for -bnot, to be consistent, we promote to unsigned here too (-band,-bor,-xor) else { opType = typeof(ulong); } + return opType; } @@ -2608,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) @@ -2662,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 @@ -2832,6 +2896,7 @@ private DynamicMetaObject Shift(DynamicMetaObject target, DynamicMetaObject arg, { return PSConvertBinder.ThrowNoConversion(arg, typeof(int), this, _version); } + var numericArg = PSConvertBinder.InvokeConverter(conversion, arg.Expression, resultType, debase, ExpressionCache.InvariantCulture); if (typeCode == TypeCode.Decimal || typeCode == TypeCode.Double || typeCode == TypeCode.Single) @@ -2890,8 +2955,8 @@ private DynamicMetaObject BinaryBitwiseOp(DynamicMetaObject target, return new DynamicMetaObject(ExpressionCache.Constant(0).Cast(typeof(object)), target.CombineRestrictions(arg)); } - var targetUnderlyingType = (target.LimitType.GetTypeInfo().IsEnum) ? Enum.GetUnderlyingType(target.LimitType) : target.LimitType; - var argUnderlyingType = (arg.LimitType.GetTypeInfo().IsEnum) ? Enum.GetUnderlyingType(arg.LimitType) : arg.LimitType; + var targetUnderlyingType = (target.LimitType.IsEnum) ? Enum.GetUnderlyingType(target.LimitType) : target.LimitType; + var argUnderlyingType = (arg.LimitType.IsEnum) ? Enum.GetUnderlyingType(arg.LimitType) : arg.LimitType; if (targetUnderlyingType.IsNumericOrPrimitive() || argUnderlyingType.IsNumericOrPrimitive()) { @@ -2921,6 +2986,7 @@ private DynamicMetaObject BinaryBitwiseOp(DynamicMetaObject target, numericTarget = target; numericArg = arg; } + if (opTypeCode == TypeCode.Decimal) { opImplType = typeof(DecimalOps); @@ -2955,7 +3021,7 @@ private DynamicMetaObject BinaryBitwiseOp(DynamicMetaObject target, var expr = exprGenerator(numericTarget.Expression.Cast(numericTarget.LimitType).Cast(opType), numericArg.Expression.Cast(numericArg.LimitType).Cast(opType)); - if (target.LimitType.GetTypeInfo().IsEnum) + if (target.LimitType.IsEnum) { expr = expr.Cast(target.LimitType); } @@ -3030,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)); @@ -3134,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, @@ -3154,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, @@ -3176,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, @@ -3198,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) @@ -3208,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)); @@ -3274,7 +3340,7 @@ private DynamicMetaObject BinaryComparison(DynamicMetaObject target, DynamicMeta { foreach (var i in target.Value.GetType().GetInterfaces()) { - if (i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IComparable<>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IComparable<>)) { return new DynamicMetaObject( toResult(Expression.Call(Expression.Convert(target.Expression, i), @@ -3339,6 +3405,7 @@ private DynamicMetaObject BinaryComparisonCommon(DynamicMetaObject targetAsEnume case ExpressionType.LessThan: numericMethod = "CompareLt"; break; case ExpressionType.LessThanOrEqual: numericMethod = "CompareLe"; break; } + return BinaryNumericOp(numericMethod, target, numericArg); } @@ -3357,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; @@ -3395,6 +3462,7 @@ internal static PSUnaryOperationBinder Get(ExpressionType operation) Interlocked.CompareExchange(ref s_decrementBinder, new PSUnaryOperationBinder(operation), null); return s_decrementBinder; } + throw new NotImplementedException("Unimplemented unary operation"); } @@ -3429,12 +3497,13 @@ public override DynamicMetaObject FallbackUnaryOperation(DynamicMetaObject targe case ExpressionType.Decrement: return IncrDecr(target, -1, errorSuggestion).WriteToDebugLog(this); } + throw new NotImplementedException(); } 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) @@ -3526,7 +3595,7 @@ internal DynamicMetaObject BNot(DynamicMetaObject target, DynamicMetaObject erro var typeCode = LanguagePrimitives.GetTypeCode(target.LimitType); if (typeCode < TypeCode.Int32) { - targetExpr = target.LimitType.GetTypeInfo().IsEnum + targetExpr = target.LimitType.IsEnum ? target.Expression.Cast(Enum.GetUnderlyingType(target.LimitType)) : target.Expression.Cast(target.LimitType); targetExpr = targetExpr.Cast(typeof(int)); @@ -3534,10 +3603,11 @@ internal DynamicMetaObject BNot(DynamicMetaObject target, DynamicMetaObject erro else if (typeCode <= TypeCode.UInt64) { var targetConvertType = target.LimitType; - if (targetConvertType.GetTypeInfo().IsEnum) + if (targetConvertType.IsEnum) { targetConvertType = Enum.GetUnderlyingType(targetConvertType); } + targetExpr = target.Expression.Cast(targetConvertType); } else @@ -3554,10 +3624,11 @@ internal DynamicMetaObject BNot(DynamicMetaObject target, DynamicMetaObject erro if (targetExpr != null) { Expression result = Expression.OnesComplement(targetExpr); - if (target.LimitType.GetTypeInfo().IsEnum) + if (target.LimitType.IsEnum) { result = result.Cast(target.LimitType); } + return new DynamicMetaObject(result.Cast(typeof(object)), target.PSGetTypeRestriction()); } @@ -3584,6 +3655,7 @@ private DynamicMetaObject UnaryPlus(DynamicMetaObject target, DynamicMetaObject // promote to int, unary plus doesn't support byte directly. expr = expr.Cast(typeof(int)); } + return new DynamicMetaObject( Expression.UnaryPlus(expr).Cast(typeof(object)), target.PSGetTypeRestriction()); @@ -3616,6 +3688,7 @@ private DynamicMetaObject UnaryMinus(DynamicMetaObject target, DynamicMetaObject // promote to int, unary plus doesn't support byte directly. expr = expr.Cast(typeof(int)); } + return new DynamicMetaObject( Expression.Negate(expr).Cast(typeof(object)), target.PSGetTypeRestriction()); @@ -3655,7 +3728,7 @@ private DynamicMetaObject IncrDecr(DynamicMetaObject target, int valueToAdd, Dyn } return errorSuggestion ?? target.ThrowRuntimeError( - Utils.EmptyArray(), + Array.Empty(), BindingRestrictions.Empty, "OperatorRequiresNumber", ParserStrings.OperatorRequiresNumber, @@ -3667,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; @@ -3684,6 +3757,7 @@ public static PSConvertBinder Get(Type type) s_binderCache.Add(type, result); } } + return result; } @@ -3731,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() @@ -3746,7 +3824,6 @@ internal static void InvalidateCache() } } - internal static DynamicMetaObject ThrowNoConversion(DynamicMetaObject target, Type toType, DynamicMetaObjectBinder binder, int currentVersion, params DynamicMetaObject[] args) { @@ -3759,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)); } @@ -3770,7 +3847,40 @@ internal static DynamicMetaObject ThrowNoConversion(DynamicMetaObject target, Ty return new DynamicMetaObject(expr, bindingRestrictions); } - internal static Expression InvokeConverter(LanguagePrimitives.ConversionData conversion, + /// + /// Convert argument to a ByRef-like type via implicit or explicit conversion. + /// + /// + /// The argument to be converted to a ByRef-like type. + /// + /// + /// The ByRef-like type to convert to. + /// + internal static Expression ConvertToByRefLikeTypeViaCasting(DynamicMetaObject argument, Type resultType) + { + 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 not PSObject) + { + Type fromType = baseObject.GetType(); + ConversionRank rank = ConversionRank.None; + + LanguagePrimitives.FigureCastConversion(fromType, resultType, ref rank); + if (rank != ConversionRank.None) + { + var valueToConvert = baseObject == argument.Value + ? argument.Expression + : Expression.Call(CachedReflectionInfo.PSObject_Base, argument.Expression); + + return Expression.Convert(valueToConvert.Cast(fromType), resultType); + } + } + + return null; + } + + internal static Expression InvokeConverter(LanguagePrimitives.IConversionData conversion, Expression value, Type resultType, bool debase, @@ -3797,6 +3907,7 @@ internal static Expression InvokeConverter(LanguagePrimitives.ConversionData con valueToConvert = value.Cast(typeof(object)); valueAsPSObject = ExpressionCache.NullPSObject; } + conv = Expression.Call( Expression.Constant(conversion.Converter), conversion.Converter.GetType().GetMethod("Invoke"), @@ -3813,10 +3924,12 @@ internal static Expression InvokeConverter(LanguagePrimitives.ConversionData con { return conv; } - if (resultType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(resultType) == null) + + if (resultType.IsValueType && Nullable.GetUnderlyingType(resultType) == null) { return Expression.Unbox(conv, resultType); } + return Expression.Convert(conv, resultType); } @@ -3830,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>(); @@ -3850,6 +3963,7 @@ public static PSGetIndexBinder Get(int argCount, PSMethodInvocationConstraints c binder = new PSGetIndexBinder(tuple); s_binderCache.Add(tuple, binder); } + return binder; } } @@ -3864,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 ? "" : " slicing disallowed", - _constraints == null ? "" : " 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() @@ -3884,16 +3999,15 @@ 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); } @@ -3928,9 +4042,11 @@ public override DynamicMetaObject FallbackGetIndex(DynamicMetaObject target, Dyn return GetIndexArray(target, indexes, errorSuggestion).WriteToDebugLog(this); } + var defaultMember = target.LimitType.GetCustomAttributes(true).FirstOrDefault(); + PropertyInfo lengthProperty = null; foreach (var i in target.LimitType.GetInterfaces()) { - if (i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var result = GetIndexDictionary(target, indexes, i); if (result != null) @@ -3938,12 +4054,23 @@ public override DynamicMetaObject FallbackGetIndex(DynamicMetaObject target, Dyn return result.WriteToDebugLog(this); } } + + // If the type explicitly implements an indexer specified by an interface + // then the DefaultMemberAttribute will not carry over to the implementation. + // This check will catch those cases. + if (defaultMember == null) + { + defaultMember = i.GetCustomAttributes(inherit: false).FirstOrDefault(); + if (defaultMember != null) + { + lengthProperty = i.GetProperty("Count") ?? i.GetProperty("Length"); + } + } } - var defaultMember = target.LimitType.GetCustomAttributes(true).FirstOrDefault(); if (defaultMember != null) { - return InvokeIndexer(target, indexes, errorSuggestion, defaultMember.MemberName).WriteToDebugLog(this); + return InvokeIndexer(target, indexes, errorSuggestion, defaultMember.MemberName, lengthProperty).WriteToDebugLog(this); } return errorSuggestion ?? CannotIndexTarget(target, indexes).WriteToDebugLog(this); @@ -3975,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); } @@ -4006,6 +4133,7 @@ private DynamicMetaObject GetIndexDictionary(DynamicMetaObject target, // slicing, or possibly just invoke the indexer. return null; } + if (indexes[0].LimitType.IsArray && !keyType.IsArray) { // There was a conversion, but it's far more likely (and backwards compatible) that we want to do slicing @@ -4027,9 +4155,29 @@ private DynamicMetaObject GetIndexDictionary(DynamicMetaObject target, bindingRestrictions); } - internal static bool CanIndexFromEndWithNegativeIndex(DynamicMetaObject target) - { - var limitType = target.LimitType; + internal static bool CanIndexFromEndWithNegativeIndex( + DynamicMetaObject target, + MethodInfo indexer, + ParameterInfo[] getterParams) + { + // PowerShell supports negative indexing for types that meet the following criteria: + // - Indexer method accepts one parameter that is typed as int + // - The int parameter is not a type argument from a constructed generic type + // (this is to exclude indexers for types that could use a negative index as + // a valid key like System.Linq.ILookup) + // - Declares a "Count" or "Length" property + // - Does not inherit from IDictionary<> as that is handled earlier in the binder + // For those types, generate special code to check for negative indices, otherwise just generate + // the call. Before we test for the above criteria explicitly, we will determine if the + // target is of a type known to be compatible. This is done to avoid the call to Module.ResolveMethod + // when possible. + + if (getterParams.Length != 1 || getterParams[0].ParameterType != typeof(int)) + { + return false; + } + + Type limitType = target.LimitType; if (limitType.IsArray || limitType == typeof(string) || limitType == typeof(StringBuilder)) { return true; @@ -4046,7 +4194,16 @@ internal static bool CanIndexFromEndWithNegativeIndex(DynamicMetaObject target) } // target implements IList? - return limitType.GetInterfaces().Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)); + if (limitType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))) + { + return true; + } + + // Get the base method definition of the indexer to determine if the int + // parameter is a generic type parameter. Module.ResolveMethod is used + // because the indexer could be a method from a constructed generic type. + MethodBase baseMethod = indexer.Module.ResolveMethod(indexer.MetadataToken); + return !baseMethod.GetParameters()[0].ParameterType.IsGenericParameter; } private DynamicMetaObject IndexWithNegativeChecks( @@ -4055,8 +4212,6 @@ private DynamicMetaObject IndexWithNegativeChecks( PropertyInfo lengthProperty, Func generateIndexOperation) { - Diagnostics.Assert(CanIndexFromEndWithNegativeIndex(target), "Unexpected target type to index from end with negative value"); - // Generate: // try { // len = obj.Length @@ -4067,6 +4222,7 @@ private DynamicMetaObject IndexWithNegativeChecks( // if (StrictMode(3)) { throw } // $null // } + var targetTmp = Expression.Parameter(target.LimitType, "target"); var lenTmp = Expression.Parameter(typeof(int), "len"); var indexTmp = Expression.Parameter(typeof(int), "index"); @@ -4129,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) @@ -4175,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) @@ -4197,7 +4353,8 @@ private DynamicMetaObject GetIndexMultiDimensionArray(DynamicMetaObject target, private DynamicMetaObject InvokeIndexer(DynamicMetaObject target, DynamicMetaObject[] indexes, DynamicMetaObject errorSuggestion, - string methodName) + string methodName, + PropertyInfo lengthProperty) { MethodInfo getter = PSInvokeMemberBinder.FindBestMethod(target, indexes, "get_" + methodName, false, _constraints); @@ -4229,12 +4386,30 @@ private DynamicMetaObject InvokeIndexer(DynamicMetaObject target, } } + if (getter.ReturnType.IsByRefLike) + { + // We cannot return a ByRef-like value in PowerShell, so we disallow getting such an indexer. + return errorSuggestion ?? new DynamicMetaObject( + Expression.Block( + Expression.IfThen( + Compiler.IsStrictMode(3), + Compiler.ThrowRuntimeError( + nameof(ParserStrings.CannotIndexWithByRefLikeReturnType), + ParserStrings.CannotIndexWithByRefLikeReturnType, + Expression.Constant(target.LimitType, typeof(Type)), + Expression.Constant(getter.ReturnType, typeof(Type)))), + GetNullResult()), + target.PSGetTypeRestriction()); + } + Expression[] indexExprs = new Expression[getterParams.Length]; for (int i = 0; i < getterParams.Length; ++i) { var parameterType = getterParams[i].ParameterType; + indexExprs[i] = parameterType.IsByRefLike + ? PSConvertBinder.ConvertToByRefLikeTypeViaCasting(indexes[i], parameterType) + : ConvertIndex(indexes[i], parameterType); - indexExprs[i] = ConvertIndex(indexes[i], parameterType); if (indexExprs[i] == null) { // Calling the indexer will fail because we can't convert an index to the correct type. @@ -4242,13 +4417,14 @@ private DynamicMetaObject InvokeIndexer(DynamicMetaObject target, } } - if (getterParams.Length == 1 && getterParams[0].ParameterType == typeof(int) && CanIndexFromEndWithNegativeIndex(target)) + if (CanIndexFromEndWithNegativeIndex(target, getter, getterParams)) { - // PowerShell supports negative indexing for some types (specifically, types implementing IList or IList). - // For those types, generate special code to check for negative indices, otherwise just generate - // the call. - PropertyInfo lengthProperty = target.LimitType.GetProperty("Count") ?? - target.LimitType.GetProperty("Length"); // for string + if (lengthProperty == null) + { + // Count is declared by most supported types, Length will catch some edge cases like strings. + lengthProperty = target.LimitType.GetProperty("Count") ?? + target.LimitType.GetProperty("Length"); + } if (lengthProperty != null) { @@ -4275,10 +4451,8 @@ private DynamicMetaObject InvokeIndexer(DynamicMetaObject target, internal static Expression ConvertIndex(DynamicMetaObject index, Type resultType) { - bool debase; - // ConstrainedLanguage note - Calls to this conversion are protected by the binding rules that call it. - var conversion = LanguagePrimitives.FigureConversion(index.Value, resultType, out debase); + var conversion = LanguagePrimitives.FigureConversion(index.Value, resultType, out bool debase); return conversion.Rank == ConversionRank.None ? null : PSConvertBinder.InvokeConverter(conversion, index.Expression, resultType, debase, ExpressionCache.InvariantCulture); @@ -4321,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)); } @@ -4361,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>(); @@ -4380,6 +4554,7 @@ public static PSSetIndexBinder Get(int argCount, PSMethodInvocationConstraints c binder = new PSSetIndexBinder(tuple); s_binderCache.Add(tuple, binder); } + return binder; } } @@ -4393,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 ? "" : " 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() @@ -4415,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); } @@ -4485,37 +4664,38 @@ private DynamicMetaObject InvokeIndexer( } var setterParams = setter.GetParameters(); - if (setterParams.Length != indexes.Length + 1) - { -#if false - if (setterParams.Length == 1 && _allowSlicing) - { - // We have a slicing operation. - return InvokeSlicingIndexer(target, indexes); - } -#endif + int paramLength = setterParams.Length; + if (paramLength != indexes.Length + 1) + { // Calling the indexer will fail because there are either too many or too few indices. return errorSuggestion ?? CannotIndexTarget(target, indexes, value); } -#if false - if (setterParams.Length == 1) + if (setterParams[paramLength - 1].ParameterType.IsByRefLike) { - // The getter takes a single argument, so first check if we're slicing. - var slicingResult = CheckForSlicing(target, indexes); - if (slicingResult != null) - { - return slicingResult; - } + // In theory, it's possible to call the setter with a value that can be implicitly/explicitly casted to the target ByRef-like type. + // However, the set-property/set-indexer semantics in PowerShell requires returning the value after the setting operation. We cannot + // return a ByRef-like value back, so we just disallow setting an indexer that takes a ByRef-like type value. + return errorSuggestion ?? new DynamicMetaObject( + Compiler.ThrowRuntimeError( + nameof(ParserStrings.CannotIndexWithByRefLikeReturnType), + ParserStrings.CannotIndexWithByRefLikeReturnType, + Expression.Constant(target.LimitType, typeof(Type)), + Expression.Constant(setterParams[paramLength - 1].ParameterType, typeof(Type))), + target.PSGetTypeRestriction()); } -#endif - Expression[] indexExprs = new Expression[setterParams.Length]; - for (int i = 0; i < setterParams.Length; ++i) + Expression[] indexExprs = new Expression[paramLength]; + for (int i = 0; i < paramLength; ++i) { var parameterType = setterParams[i].ParameterType; - indexExprs[i] = PSGetIndexBinder.ConvertIndex(i == setterParams.Length - 1 ? value : indexes[i], parameterType); + var argument = (i == paramLength - 1) ? value : indexes[i]; + + indexExprs[i] = parameterType.IsByRefLike + ? PSConvertBinder.ConvertToByRefLikeTypeViaCasting(argument, parameterType) + : PSGetIndexBinder.ConvertIndex(argument, parameterType); + if (indexExprs[i] == null) { // Calling the indexer will fail because we can't convert an index to the correct type. @@ -4523,7 +4703,9 @@ private DynamicMetaObject InvokeIndexer( } } - if (setterParams.Length == 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 @@ -4634,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)); @@ -4644,7 +4826,6 @@ private DynamicMetaObject SetIndexArray(DynamicMetaObject target, PSConvertBinder.ThrowNoConversion(indexes[0], typeof(int), this, _version, target, value); } - var elementType = target.LimitType.GetElementType(); var valueExpr = PSGetIndexBinder.ConvertIndex(value, elementType); if (valueExpr == null) @@ -4657,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, @@ -4700,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]; @@ -4724,11 +4905,11 @@ private DynamicMetaObject SetIndexMultiDimensionArray(DynamicMetaObject target, } /// - /// The binder for getting a member of a class, like $foo.bar or [foo]::bar + /// The binder for getting a member of a class, like $foo.bar or [foo]::bar. /// internal class PSGetMemberBinder : GetMemberBinder { - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSGetMemberBinderKeyType x, PSGetMemberBinderKeyType y) { @@ -4752,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) { @@ -4785,6 +4966,7 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy targetExpr = target.Expression.Convert(typeof(PSObject)); break; } + Diagnostics.Assert(mi != null, "ReservedMemberBinder doesn't support member Name"); return new DynamicMetaObject(WrapGetMemberInTry(Expression.Call(mi, targetExpr)), target.PSGetTypeRestriction()); @@ -4820,7 +5002,9 @@ static PSGetMemberBinder() internal int _version; private bool _hasInstanceMember; + internal bool HasInstanceMember { get { return _hasInstanceMember; } } + internal static void SetHasInstanceMember(string memberName) { // We must invalidate dynamic sites (if any) when the first instance member (for this binder) @@ -4845,11 +5029,11 @@ 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) { - if (!binderList.Any()) + if (binderList.Count == 0) { // Force one binder to be created if one hasn't been created already. PSGetMemberBinder.Get(memberName, (Type)null, @static: false); @@ -4873,9 +5057,10 @@ internal static void SetHasInstanceMember(string memberName) } private bool _hasTypeTableMember; + 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) { @@ -4898,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) { @@ -4911,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) @@ -4946,14 +5131,15 @@ 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.Any()) + if (binderList.Count > 0) { result._hasInstanceMember = binderList[0]._hasInstanceMember; result._hasTypeTableMember = binderList[0]._hasTypeTableMember; } + binderList.Add(result); Diagnostics.Assert(binderList.All(b => b._hasInstanceMember == result._hasInstanceMember), @@ -4963,9 +5149,11 @@ private static PSGetMemberBinder Get(string memberName, Type classScope, bool @s } } } + s_binderCache.Add(tuple, result); } } + return result; } @@ -4980,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" : "", _nonEnumerating ? " nonEnumerating" : "", _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) @@ -4994,8 +5187,8 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy // Defer COM objects or arguments wrapped in PSObjects if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value)) { - Object baseObject = PSObject.Base(target.Value); - if (baseObject != null && Utils.IsComObject(baseObject)) + object baseObject = PSObject.Base(target.Value); + if (baseObject != null && Marshal.IsComObject(baseObject)) { // We unwrap only if the 'base' is a COM object. It's unnecessary to unwrap in other cases, // especially in the case of strings, we would lose instance members on the PSObject. @@ -5007,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); @@ -5059,7 +5252,7 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy bool canOptimize; Type aliasConversionType; - memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType); + memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property); if (!canOptimize) { @@ -5091,7 +5284,13 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy var adapterData = property.adapterData as DotNetAdapter.PropertyCacheEntry; Diagnostics.Assert(adapterData != null, "We have an unknown PSProperty that we aren't correctly optimizing."); - if (!adapterData.member.DeclaringType.GetTypeInfo().IsGenericTypeDefinition) + if (adapterData.member.DeclaringType.IsGenericTypeDefinition || adapterData.propertyType.IsByRefLike) + { + // We really should throw an error, but accessing property getter + // doesn't throw error in PowerShell since V2, even in strict mode. + expr = ExpressionCache.NullConstant; + } + else { // For static property access, the target expr must be null. For non-static, we must convert // because target.Expression is typeof(object) because this is a dynamic site. @@ -5099,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 { @@ -5114,11 +5324,6 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy expr = Expression.Field(targetExpr, (FieldInfo)adapterData.member); } } - else - { - // This is kinda lame - we really should throw an error, but V2 did the same thing (even in strict mode). - expr = ExpressionCache.NullConstant; - } } var scriptProperty = propertyInfo as PSScriptProperty; @@ -5168,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) @@ -5205,7 +5406,7 @@ internal static bool IsGenericDictionary(object value, ref Type genericTypeArg) bool isGeneric = false; foreach (var i in value.GetType().GetInterfaces()) { - if (i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { isGeneric = true; var genericArguments = i.GetGenericArguments(); @@ -5217,6 +5418,7 @@ internal static bool IsGenericDictionary(object value, ref Type genericTypeArg) } } } + return isGeneric; } @@ -5231,39 +5433,27 @@ internal static Expression GetTargetExpr(DynamicMetaObject target, Type castToTy // If the target value is actually a deserialized PSObject, we should use the original value var psobj = value as PSObject; - if (psobj != null && psobj != AutomationNull.Value && !psobj.isDeserialized) + if (psobj != null && psobj != AutomationNull.Value && !psobj.IsDeserialized) { expr = Expression.Call(CachedReflectionInfo.PSObject_Base, expr); value = PSObject.Base(value); } var type = castToType ?? ((value != null) ? value.GetType() : typeof(object)); -#if CORECLR - var typeInfo = type.GetTypeInfo(); - // 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(typeInfo) && DotNetAdapter.DisallowPrivateReflection(typeInfo)) - { - var publicType = DotNetAdapter.GetFirstPublicParentType(typeInfo); - if (publicType != null) - { - type = publicType; - } - // else we'll probably fail, but the error message might be more helpful than NullReferenceException - } -#endif + if (expr.Type != type) { // Unbox value types (or use Nullable.Value) to avoid a copy in case the value is mutated. // In case that castToType is System.Object and expr.Type is Nullable, expr.Cast(System.Object) will - // get the underlying value by default. So "GetTargetExpr(target).Cast(typeof(Object))" is actually the same as - // "GetTargetExpr(target, typeof(Object))". - expr = type.GetTypeInfo().IsValueType + // get the underlying value by default. So "GetTargetExpr(target).Cast(typeof(object))" is actually the same as + // "GetTargetExpr(target, typeof(object))". + expr = type.IsValueType ? (Nullable.GetUnderlyingType(expr.Type) != null ? (Expression)Expression.Property(expr, "Value") : Expression.Unbox(expr, type)) : expr.Cast(type); } + return expr; } @@ -5327,24 +5517,39 @@ 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; } - internal static bool IsAllowedInConstrainedLanguage(Object targetValue, string name, bool isStatic) + internal static bool IsAllowedInConstrainedLanguage(object targetValue, string name, bool isStatic) { // ToString allowed on any type - if (String.Equals(name, "ToString", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(name, "ToString", StringComparison.OrdinalIgnoreCase)) { return true; } @@ -5396,7 +5601,7 @@ private static Expression WrapGetMemberInTry(Expression expr) private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject target, HashSet aliases, List aliasRestrictions) { - Diagnostics.Assert(null != aliasRestrictions, "aliasRestrictions cannot be null"); + Diagnostics.Assert(aliasRestrictions != null, "aliasRestrictions cannot be null"); if (aliases == null) { aliases = new HashSet { alias.Name }; @@ -5407,6 +5612,7 @@ private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject targe { throw new ExtendedTypeSystemException("CycleInAliasLookup", null, ExtendedTypeSystem.CycleInAlias, alias.Name); } + aliases.Add(alias.Name); } @@ -5421,8 +5627,8 @@ private PSMemberInfo ResolveAlias(PSAliasProperty alias, DynamicMetaObject targe return null; } - PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, aliases, aliasRestrictions); - + PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, + MemberTypes.Property, aliases, aliasRestrictions); return result; } @@ -5430,6 +5636,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, out BindingRestrictions restrictions, out bool canOptimize, out Type aliasConversionType, + MemberTypes memberTypeToOperateOn, HashSet aliases = null, List aliasRestrictions = null) { @@ -5455,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) { @@ -5493,13 +5699,13 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, // // - If not, we want to use the base object, so that we might generate optimized code. var psobj = target.Value as PSObject; - bool isTargetDeserializedObject = (psobj != null) && (psobj.isDeserialized); + bool isTargetDeserializedObject = (psobj != null) && (psobj.IsDeserialized); object value = isTargetDeserializedObject ? target.Value : PSObject.Base(target.Value); var adapterSet = PSObject.GetMappedAdapter(value, typeTable); if (memberInfo == null) { - canOptimize = adapterSet.OriginalAdapter.SiteBinderCanOptimize; + canOptimize = adapterSet.OriginalAdapter.CanSiteBinderOptimize(memberTypeToOperateOn); // Don't bother looking for the member if we're not going to use it. if (canOptimize) { @@ -5517,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 (null == aliasRestrictions) - { - aliasRestrictions = new List(); - } + aliasRestrictions ??= new List(); memberInfo = ResolveAlias(alias, target, aliases, aliasRestrictions); if (memberInfo == null) @@ -5546,7 +5746,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, } } - if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.dotNetInstanceAdapter) + if (_classScope != null && (target.LimitType == _classScope || target.LimitType.IsSubclassOf(_classScope)) && adapterSet.OriginalAdapter == PSObject.DotNetInstanceAdapter) { List candidateMethods = null; foreach (var member in _classScope.GetMembers(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic)) @@ -5559,10 +5759,10 @@ 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)); + memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); } } else @@ -5570,20 +5770,18 @@ 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)); + memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); } } 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); } } @@ -5597,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; } @@ -5608,8 +5806,8 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, } else { - DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods.ToArray()); - memberInfo = PSMethod.Create(this.Name, PSObject.dotNetInstanceAdapter, null, method); + DotNetAdapter.MethodCacheEntry method = new DotNetAdapter.MethodCacheEntry(candidateMethods); + memberInfo = PSMethod.Create(this.Name, PSObject.DotNetInstanceAdapter, null, method); } } } @@ -5630,7 +5828,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, if (isTargetDeserializedObject) { restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction( - Expression.Field(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_isDeserialized))); + Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized))); } if (hasTypeTableMember) @@ -5668,17 +5866,14 @@ internal static object GetAdaptedValue(object obj, string member) { ConsolidatedString typenames = PSObject.GetTypeNames(obj); memberInfo = context.TypeTable.GetMembers(typenames)[member]; - if (null != memberInfo) + if (memberInfo != null) { memberInfo = CloneMemberInfo(memberInfo, obj); } } - 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) { @@ -5701,6 +5896,7 @@ internal static object GetAdaptedValue(object obj, string member) throw new PropertyNotFoundException("PropertyNotFoundStrict", null, ParserStrings.PropertyNotFoundStrict, LanguagePrimitives.ConvertTo(member)); } + return null; } @@ -5712,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) @@ -5751,6 +5947,7 @@ internal static bool TryGetGenericDictionaryValue(IDictionary hash value = result; return true; } + value = null; return false; } @@ -5759,11 +5956,11 @@ internal static bool TryGetGenericDictionaryValue(IDictionary hash } /// - /// The binder for setting a member, like $foo.bar = 1 or [foo]::bar = 1 + /// The binder for setting a member, like $foo.bar = 1 or [foo]::bar = 1. /// internal class PSSetMemberBinder : SetMemberBinder { - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSSetMemberBinderKeyType x, PSSetMemberBinderKeyType y) { @@ -5784,6 +5981,7 @@ public int GetHashCode(PSSetMemberBinderKeyType obj) obj.Item3.GetHashCode()); } } + private static readonly Dictionary s_binderCache = new Dictionary(new KeyComparer()); @@ -5793,9 +5991,10 @@ 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); } + public static PSSetMemberBinder Get(string memberName, Type classScope, bool @static) { PSSetMemberBinder result; @@ -5809,6 +6008,7 @@ public static PSSetMemberBinder Get(string memberName, Type classScope, bool @st s_binderCache.Add(tuple, result); } } + return result; } @@ -5822,20 +6022,27 @@ 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 " : "", 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) { return originalExpression; } + var attributesArray = transformationAttributes.ToArray(); if (attributesArray.Length == 0) { return originalExpression; } + Expression transformedExpression = originalExpression.Convert(typeof(object)); var engineIntrinsicsTempVar = Expression.Variable(typeof(EngineIntrinsics)); // apply transformation attributes from right to left @@ -5846,6 +6053,7 @@ private Expression GetTransformedExpression(IEnumerable(typenames)[member]; - if (null != memberInfo) + if (memberInfo != null) { memberInfo = PSGetMemberBinder.CloneMemberInfo(memberInfo, obj); } } - 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) { @@ -6236,6 +6495,7 @@ internal static object SetAdaptedValue(object obj, string member, object value) throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), null, "PropertyAssignmentException", ParserStrings.PropertyNotFound, member); } + return value; } catch (SetValueException) @@ -6274,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, @@ -6285,7 +6560,7 @@ internal enum MethodInvocationType NonVirtual, } - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSInvokeMemberBinderKeyType x, PSInvokeMemberBinderKeyType y) { @@ -6342,6 +6617,7 @@ private static PSInvokeMemberBinder Get(string memberName, Type classScope, Call s_binderCache.Add(key, result); } } + return result; } @@ -6370,24 +6646,30 @@ 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 " : "", _propertySetter ? "propset " : "", - Name, _getMemberBinder._version, CallInfo.ArgumentCount, _invocationConstraints != null ? _invocationConstraints.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); } 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 && Utils.IsComObject(baseObject)) + object baseObject = PSObject.Base(target.Value); + if (baseObject != null && Marshal.IsComObject(baseObject)) { // We unwrap only if the 'base' of 'target' is a COM object. It's unnecessary to unwrap in other cases, // especially in the case that 'target' is a string, we would lose instance members on the PSObject. @@ -6415,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); } @@ -6460,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), @@ -6470,8 +6754,8 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, BindingRestrictions restrictions; bool canOptimize; Type aliasConversionType; - var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType) as PSMethodInfo; - restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); + var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Method) as PSMethodInfo; + 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 @@ -6480,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) { @@ -6503,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 @@ -6513,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); @@ -6522,8 +6805,8 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, // If the target value is a PSObject and its base object happens to be a Hashtable or ArrayList, // we might have three interesting cases here: // (1) the target value could be a regular PSObject that wraps the Hashtable/ArrayList, i.e. $target = [PSObject]::AsPSObject($hash) - // (2) the target value could be a deserialized object (PSObject) with the 'isDeserialized' field to be false, i.e. deserialized Hashtable/ArrayList/Dictionary[string, string] - // (3) the target value could be a deserialized object (PSObject) with the 'isDeserialized' field to be true, i.e. deserialized XmlElement + // (2) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be false, i.e. deserialized Hashtable/ArrayList/Dictionary[string, string] + // (3) the target value could be a deserialized object (PSObject) with the 'IsDeserialized' property to be true, i.e. deserialized XmlElement // For the first two cases, it's OK to call a .NET method from the base object, such as $target.Add(). // For the third case, calling a .NET method from the base object is incorrect, because the original type of the deserialized object doesn't have the method. // example: XmlElement derives from IEnumerable, so it's treated as a container object when powershell does the serialization -- using an ArrayList to hold @@ -6539,11 +6822,11 @@ 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( - Expression.Not(Expression.Field(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_isDeserialized)))); + Expression.Not(Expression.Property(target.Expression.Cast(typeof(PSObject)), CachedReflectionInfo.PSObject_IsDeserialized)))); } } @@ -6565,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); } @@ -6581,6 +6864,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, { expr = expr.Cast(typeof(object)); } + return new DynamicMetaObject(expr, restrictions).WriteToDebugLog(this); } @@ -6602,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); } @@ -6647,6 +6931,7 @@ internal static DynamicMetaObject InvokeDotNetMethod( { numArgs -= 1; } + object[] argValues = new object[numArgs]; for (int i = 0; i < numArgs; ++i) { @@ -6654,16 +6939,16 @@ internal static DynamicMetaObject InvokeDotNetMethod( argValues[i] = arg == AutomationNull.Value ? null : arg; } - if (ClrFacade.IsTransparentProxy(target.Value) && (psMethodInvocationConstraints == null || psMethodInvocationConstraints.MethodTargetType == null)) - { - var argTypes = (psMethodInvocationConstraints == null) - ? new Type[numArgs] - : psMethodInvocationConstraints.ParameterTypes.ToArray(); - psMethodInvocationConstraints = new PSMethodInvocationConstraints(target.Value.GetType(), argTypes); - } + var result = Adapter.FindBestMethod( + mi, + psMethodInvocationConstraints, + allowCastingToByRefLikeType: true, + argValues, + ref errorId, + ref errorMsg, + out expandParamsOnBest, + out callNonVirtually); - var result = Adapter.FindBestMethod(mi, psMethodInvocationConstraints, - argValues, ref errorId, ref errorMsg, out expandParamsOnBest, out callNonVirtually); if (callNonVirtually && methodInvocationType != MethodInvocationType.BaseCtor) { methodInvocationType = MethodInvocationType.NonVirtual; @@ -6678,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 @@ -6694,7 +6990,7 @@ internal static DynamicMetaObject InvokeDotNetMethod( // Likewise, when calling methods in types defined by PowerShell, we don't // want to wrap the exception. - if (methodInfo.DeclaringType.GetTypeInfo().Assembly.GetCustomAttributes(typeof(DynamicClassImplementationAssemblyAttribute)).Any()) + if (methodInfo.DeclaringType.Assembly.GetCustomAttributes(typeof(DynamicClassImplementationAssemblyAttribute)).Any()) { return new DynamicMetaObject(expr, restrictions); } @@ -6730,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)) )); @@ -6744,7 +7040,7 @@ internal static MethodInfo FindBestMethod(DynamicMetaObject target, { MethodInfo result = null; - var psMethod = PSObject.dotNetInstanceAdapter.GetDotNetMethod(PSObject.Base(target.Value), methodName); + var psMethod = PSObject.DotNetInstanceAdapter.GetDotNetMethod(PSObject.Base(target.Value), methodName); if (psMethod != null) { var data = (DotNetAdapter.MethodCacheEntry)psMethod.adapterData; @@ -6753,29 +7049,100 @@ internal static MethodInfo FindBestMethod(DynamicMetaObject target, string errorMsg = null; bool expandParameters; bool callNonVirtually; - var mi = Adapter.FindBestMethod(data.methodInformationStructures, invocationConstraints, - args.Select(arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(), - ref errorId, ref errorMsg, out expandParameters, out callNonVirtually); + + var mi = Adapter.FindBestMethod( + data.methodInformationStructures, + invocationConstraints, + allowCastingToByRefLikeType: true, + args.Select(static arg => arg.Value == AutomationNull.Value ? null : arg.Value).ToArray(), + ref errorId, + ref errorMsg, + out expandParameters, + out callNonVirtually); + if (mi != null) { result = (MethodInfo)mi.method; } } + return result; } - internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, DynamicMetaObject[] args, bool expandParameters, MethodInvocationType invocationInvocationType) + internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, DynamicMetaObject[] args, bool expandParameters, MethodInvocationType invocationType) { List temps = new List(); List initTemps = new List(); List copyOutTemps = new List(); + ConstructorInfo constructorInfo = null; + MethodInfo methodInfo = mi as MethodInfo; + if (methodInfo != null) + { + Type returnType = methodInfo.ReturnType; + if (returnType.IsByRefLike) + { + ConstructorInfo exceptionCtorInfo; + switch (invocationType) + { + case MethodInvocationType.Getter: + exceptionCtorInfo = CachedReflectionInfo.GetValueException_ctor; + break; + case MethodInvocationType.Setter: + exceptionCtorInfo = CachedReflectionInfo.SetValueException_ctor; + break; + default: + exceptionCtorInfo = CachedReflectionInfo.MethodException_ctor; + break; + } + + return Expression.Throw( + Expression.New( + exceptionCtorInfo, + Expression.Constant(nameof(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType)), + Expression.Constant(null, typeof(Exception)), + Expression.Constant(ExtendedTypeSystem.CannotCallMethodWithByRefLikeReturnType), + Expression.NewArrayInit( + typeof(object), + Expression.Constant(methodInfo.Name), + Expression.Constant(returnType, typeof(Type)))), + typeof(object)); + } + } + else + { + constructorInfo = (ConstructorInfo)mi; + Type declaringType = constructorInfo.DeclaringType; + if (declaringType.IsByRefLike) + { + return Expression.Throw( + Expression.New( + CachedReflectionInfo.MethodException_ctor, + Expression.Constant(nameof(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType)), + Expression.Constant(null, typeof(Exception)), + Expression.Constant(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType), + Expression.NewArrayInit( + typeof(object), + Expression.Constant(declaringType, typeof(Type)))), + typeof(object)); + } + } + + // Invoking a base constructor or a base method (non-virtual call) depends reflection invocation + // via helper methods, and thus all arguments need to be casted to 'object'. The ByRef-like types + // cannot be boxed and won't work with reflection. + bool allowCastingToByRefLikeType = + invocationType != MethodInvocationType.BaseCtor && + 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) { + Type parameterType = parameters[i].ParameterType; string paramName = parameters[i].Name; - if (string.IsNullOrWhiteSpace(parameters[i].Name)) + if (string.IsNullOrWhiteSpace(paramName)) { paramName = i.ToString(CultureInfo.InvariantCulture); } @@ -6787,31 +7154,68 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, // This inconsistent behavior affects OneCore powershell because we are using the extension method here when compiling // against CoreCLR. So we need to add a null check until this is fixed in CLR. var paramArrayAttrs = parameters[i].GetCustomAttributes(typeof(ParamArrayAttribute), false); - if (paramArrayAttrs != null && paramArrayAttrs.Any()) + if (paramArrayAttrs != null && paramArrayAttrs.Length > 0) { Diagnostics.Assert(i == parameters.Length - 1, "vararg parameter is not the last"); - var paramElementType = parameters[i].ParameterType.GetElementType(); + var paramElementType = parameterType.GetElementType(); if (expandParameters) { - argExprs[i] = Expression.NewArrayInit(paramElementType, - args.Skip(i).Select( - a => a.CastOrConvertMethodArgument(paramElementType, paramName, mi.Name, temps, initTemps))); + IEnumerable elements = args + .Skip(i) + .Select(a => + a.CastOrConvertMethodArgument( + paramElementType, + paramName, + mi.Name, + allowCastingToByRefLikeType: false, + temps, + 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 { - var arg = args[i].CastOrConvertMethodArgument(parameters[i].ParameterType, paramName, mi.Name, temps, initTemps); + var arg = args[i].CastOrConvertMethodArgument( + parameterType, + paramName, + mi.Name, + allowCastingToByRefLikeType: false, + temps, + initTemps); + argExprs[i] = arg; + argsToLog.Add(arg); } } else if (i >= args.Length) { - Diagnostics.Assert(parameters[i].IsOptional, + // We don't log the default value for an optional parameter, as it's not specified by the user. + Diagnostics.Assert( + parameters[i].IsOptional, "if there are too few arguments, FindBestMethod should only succeed if parameters are optional"); + var argValue = parameters[i].DefaultValue; if (argValue == null) { - argExprs[i] = Expression.Default(parameters[i].ParameterType); + 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 { @@ -6823,9 +7227,9 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, } else { - if (parameters[i].ParameterType.IsByRef) + 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[]) }, @@ -6833,31 +7237,38 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, new object[] { i + 1, typeof(PSReference).FullName, "[ref]" }); } - var temp = Expression.Variable(parameters[i].ParameterType.GetElementType()); + var temp = Expression.Variable(parameterType.GetElementType()); temps.Add(temp); 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(parameters[i].ParameterType, paramName, mi.Name, temps, initTemps); + 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); } } } - ConstructorInfo constructorInfo = null; - var methodInfo = mi as MethodInfo; - if (methodInfo == null) - { - constructorInfo = (ConstructorInfo)mi; - } - Expression call; if (constructorInfo != null) { - if (invocationInvocationType == MethodInvocationType.BaseCtor) + if (invocationType == MethodInvocationType.BaseCtor) { var targetExpr = target.Value is PSObject ? target.Expression.Cast(constructorInfo.DeclaringType) @@ -6866,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 { @@ -6875,7 +7286,7 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, } else { - if (invocationInvocationType == MethodInvocationType.NonVirtual && !methodInfo.IsStatic) + if (invocationType == MethodInvocationType.NonVirtual && !methodInfo.IsStatic) { call = Expression.Call( methodInfo.ReturnType == typeof(void) @@ -6883,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 { @@ -6895,9 +7306,15 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, } } - if (temps.Any()) + // 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.Any()) + if (call.Type != typeof(void) && copyOutTemps.Count > 0) { var retValue = Expression.Variable(call.Type); temps.Add(retValue); @@ -6905,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)); @@ -6929,18 +7351,16 @@ 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; } - /// The target to operate against + /// The target to operate against. /// /// Arguments to the operator. The first argument must be either a scriptblock /// or a string representing a 'simple where' expression. The second is an enum that controls @@ -7014,12 +7434,13 @@ private DynamicMetaObject InvokeForEachOnCollection(DynamicMetaObject targetEnum this.ReturnType), targetEnumerator.Restrictions.Merge(restrictions)); } + var lhsEnumerator = PSEnumerableBinder.IsEnumerable(targetEnumerator).Expression; Expression argsToPass; 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 { @@ -7034,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) @@ -7073,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) { @@ -7099,9 +7528,9 @@ 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(); + var enumerator = (new object[] { obj }).GetEnumerator(); switch (args.Length) { case 1: @@ -7115,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], Utils.EmptyArray()); + var enumerator = (new object[] { obj }).GetEnumerator(); + 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, @@ -7128,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) { @@ -7162,6 +7604,7 @@ internal static bool TryGetInstanceMethod(object value, string memberName, out P throw InterpreterError.NewInterpreterException(memberName, typeof(RuntimeException), null, "MethodNotFound", ParserStrings.MethodNotFound, ParserOps.GetTypeFullName(value), memberName); } + return true; } @@ -7177,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 } @@ -7187,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) @@ -7206,9 +7698,7 @@ public int GetHashCode(Tuple obj) private static readonly Dictionary, PSCreateInstanceBinder> s_binderCache = - new Dictionary - , PSCreateInstanceBinder>( - new KeyComparer()); + new Dictionary, PSCreateInstanceBinder>(new KeyComparer()); public static PSCreateInstanceBinder Get(CallInfo callInfo, PSMethodInvocationConstraints constraints, bool publicTypeOnly = false) { @@ -7223,8 +7713,10 @@ public static PSCreateInstanceBinder Get(CallInfo callInfo, PSMethodInvocationCo s_binderCache.Add(key, result); } } + return result; } + internal static void InvalidateCache() { // Invalidate binders @@ -7237,7 +7729,6 @@ internal static void InvalidateCache() } } - internal PSCreateInstanceBinder(CallInfo callInfo, PSMethodInvocationConstraints constraints, bool publicTypeOnly) : base(callInfo) { @@ -7248,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() : ""); + 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()); } @@ -7268,17 +7763,35 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe var instanceType = targetValue as Type ?? targetValue.GetType(); BindingRestrictions restrictions; - TypeInfo instanceTypeInfo = instanceType.GetTypeInfo(); - if (_publicTypeOnly && !TypeResolver.IsPublic(instanceTypeInfo)) + if (instanceType.IsByRefLike) + { + // ByRef-like types are not boxable and should be used only on stack + restrictions = BindingRestrictions.GetExpressionRestriction( + Expression.Call(CachedReflectionInfo.PSCreateInstanceBinder_IsTargetTypeByRefLike, target.Expression)); + + return target.ThrowRuntimeError( + restrictions, + nameof(ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType), + ExtendedTypeSystem.CannotInstantiateBoxedByRefLikeType, + Expression.Call( + CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName, + target.Expression)).WriteToDebugLog(this); + } + + if (_publicTypeOnly && !TypeResolver.IsPublic(instanceType)) { // If 'publicTypeOnly' specified, we only support creating instance for public types. restrictions = BindingRestrictions.GetExpressionRestriction( Expression.Call(CachedReflectionInfo.PSCreateInstanceBinder_IsTargetTypeNonPublic, target.Expression)); - return target.ThrowRuntimeError(restrictions, "MethodNotFound", ParserStrings.MethodNotFound, - Expression.Call( - CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName, - target.Expression), - Expression.Constant("new")).WriteToDebugLog(this); + + return target.ThrowRuntimeError( + restrictions, + nameof(ParserStrings.MethodNotFound), + ParserStrings.MethodNotFound, + Expression.Call( + CachedReflectionInfo.PSCreateInstanceBinder_GetTargetTypeName, + target.Expression), + Expression.Constant("new")).WriteToDebugLog(this); } var ctors = instanceType.GetConstructors(); @@ -7288,7 +7801,7 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe : BindingRestrictions.GetInstanceRestriction(target.Expression, instanceType) : target.PSGetTypeRestriction(); restrictions = restrictions.Merge(BinderUtils.GetOptionalVersionAndLanguageCheckForType(this, instanceType, _version)); - if (ctors.Length == 0 && _callInfo.ArgumentCount == 0 && instanceTypeInfo.IsValueType) + if (ctors.Length == 0 && _callInfo.ArgumentCount == 0 && instanceType.IsValueType) { // No ctors, just call the default ctor return new DynamicMetaObject(Expression.New(instanceType).Cast(this.ReturnType), restrictions).WriteToDebugLog(this); @@ -7297,17 +7810,40 @@ 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); } /// - /// Check if the target type is not public + /// Check if the target type is ByRef-like. + /// + internal static bool IsTargetTypeByRefLike(object target) + { + var targetValue = PSObject.Base(target); + if (targetValue == null) { return false; } + + var instanceType = targetValue as Type ?? targetValue.GetType(); + return instanceType.IsByRefLike; + } + + /// + /// Check if the target type is not public. /// internal static bool IsTargetTypeNonPublic(object target) { @@ -7319,7 +7855,7 @@ internal static bool IsTargetTypeNonPublic(object target) } /// - /// Return the full name of the target type + /// Return the full name of the target type. /// internal static string GetTargetTypeName(object target) { @@ -7336,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) @@ -7354,9 +7890,7 @@ public int GetHashCode(Tuple obj) private static readonly Dictionary, PSInvokeBaseCtorBinder> s_binderCache = - new Dictionary - , PSInvokeBaseCtorBinder>( - new KeyComparer()); + new Dictionary, PSInvokeBaseCtorBinder>(new KeyComparer()); public static PSInvokeBaseCtorBinder Get(CallInfo callInfo, PSMethodInvocationConstraints constraints) { @@ -7371,6 +7905,7 @@ public static PSInvokeBaseCtorBinder Get(CallInfo callInfo, PSMethodInvocationCo s_binderCache.Add(key, result); } } + return result; } @@ -7383,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()); } @@ -7393,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)); } @@ -7405,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 6b69f89d997..f4829bc3547 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Concurrent; @@ -11,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.Security.Cryptography.X509Certificates; 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 @@ -36,10 +34,10 @@ internal enum ScriptBlockClauseToInvoke Begin, Process, End, + Clean, ProcessBlockOnly, } - internal class CompiledScriptBlockData { internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter) @@ -51,7 +49,7 @@ internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter) internal CompiledScriptBlockData(string scriptText, bool isProductCode) { - this.IsProductCode = isProductCode; + _isProductCode = isProductCode; _scriptText = scriptText; this.Id = Guid.NewGuid(); } @@ -98,16 +96,16 @@ private void InitializeMetadata() CmdletBindingAttribute cmdletBindingAttribute = null; if (!Ast.HasAnyScriptBlockAttributes()) { - attributes = Utils.EmptyArray(); + attributes = Array.Empty(); } else { attributes = Ast.GetScriptBlockAttributes().ToArray(); foreach (var attribute in attributes) { - if (attribute is CmdletBindingAttribute) + if (attribute is CmdletBindingAttribute c) { - cmdletBindingAttribute = cmdletBindingAttribute ?? (CmdletBindingAttribute)attribute; + cmdletBindingAttribute ??= c; } else if (attribute is DebuggerHiddenAttribute) { @@ -118,10 +116,13 @@ private void InitializeMetadata() DebuggerStepThrough = true; } } + _usesCmdletBinding = cmdletBindingAttribute != null; } + bool automaticPosition = cmdletBindingAttribute == null || cmdletBindingAttribute.PositionalBinding; - var runtimeDefinedParameterDictionary = Ast.GetParameterMetadata(automaticPosition, ref _usesCmdletBinding); + var runtimeDefinedParameterDictionary = + Ast.GetParameterMetadata(automaticPosition, ref _usesCmdletBinding); // Initialize these fields last - if there were any exceptions, we don't want the partial results cached. _attributes = attributes; @@ -161,20 +162,19 @@ private void CompileOptimized() private void ReallyCompile(bool optimize) { +#if LEGACYTELEMETRY var sw = new Stopwatch(); sw.Start(); - - if (!IsProductCode && SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File)) - { - this.IsProductCode = true; - } - +#endif bool etwEnabled = ParserEventSource.Log.IsEnabled(); if (etwEnabled) { var extent = _ast.Body.Extent; var text = extent.Text; - ParserEventSource.Log.CompileStart(ParserEventSource.GetFileOrScript(extent.File, text), text.Length, optimize); + ParserEventSource.Log.CompileStart( + FileName: ParserEventSource.GetFileOrScript(extent.File, text), + text.Length, + optimize); } PerformSecurityChecks(); @@ -188,23 +188,50 @@ 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; } - // Call the AMSI API to determine if the script block has malicious content var scriptExtent = scriptBlockAst.Extent; - if (AmsiUtils.ScanContent(scriptExtent.Text, scriptExtent.File) == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED) + var scriptFile = scriptExtent.File; + + if (scriptFile != null + && scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) + && IsScriptBlockInFactASafeHashtable()) + { + // Skip the scan for .psd1 files if their content is in fact a safe HashtableAst. + return; + } + + // Call the AMSI API to determine if the script block has malicious content + var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptFile); + + if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED) + { + var parseError = new ParseError( + scriptExtent, + "ScriptContainedMaliciousContent", + ParserStrings.ScriptContainedMaliciousContent); + throw new ParseException(new[] { parseError }); + } + else if (amsiResult >= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN + && amsiResult <= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_END) { - var parseError = new ParseError(scriptExtent, "ScriptContainedMaliciousContent", ParserStrings.ScriptContainedMaliciousContent); + // Certain policies set by an administrator blocked this content on this machine + var parseError = new ParseError( + scriptExtent, + "ScriptHasAdminBlockedContent", + StringUtil.Format(ParserStrings.ScriptHasAdminBlockedContent, amsiResult)); throw new ParseException(new[] { parseError }); } @@ -212,11 +239,54 @@ private void PerformSecurityChecks() { HasSuspiciousContent = true; } + + // A local function to check if the ScriptBlockAst is in fact a safe HashtableAst. + bool IsScriptBlockInFactASafeHashtable() + { + // NOTE: The code below depends on the current member structure of 'ScriptBlockAst' + // to determine if the ScriptBlockAst is in fact just a HashtableAst. If AST types + // are enhanced, such as new members added to 'ScriptBlockAst', the code here needs + // to be reviewed and changed accordingly. + + if (scriptBlockAst.BeginBlock != null + || scriptBlockAst.ProcessBlock != null + || scriptBlockAst.CleanBlock != null + || scriptBlockAst.ParamBlock != null + || scriptBlockAst.DynamicParamBlock != null + || scriptBlockAst.ScriptRequirements != null + || scriptBlockAst.UsingStatements.Count > 0 + || scriptBlockAst.Attributes.Count > 0) + { + return false; + } + + NamedBlockAst endBlock = scriptBlockAst.EndBlock; + if (!endBlock.Unnamed || endBlock.Traps != null || endBlock.Statements.Count != 1) + { + return false; + } + + if (endBlock.Statements[0] is not PipelineAst pipelineAst) + { + return false; + } + + if (pipelineAst.GetPureExpression() is not HashtableAst hashtableAst) + { + return false; + } + + // After the above steps, we know the ScriptBlockAst is in fact just a HashtableAst, + // now we need to check if the HashtableAst is safe. + return IsSafeValueVisitor.Default.IsAstSafe(hashtableAst); + } } // We delay parsing scripts loaded on startup, so we save the text. private string _scriptText; - internal IParameterMetadataProvider Ast { get { return _ast ?? DelayParseScriptText(); } } + + internal IParameterMetadataProvider Ast { get => _ast ?? DelayParseScriptText(); } + private IParameterMetadataProvider _ast; private IParameterMetadataProvider DelayParseScriptText() @@ -224,7 +294,9 @@ private IParameterMetadataProvider DelayParseScriptText() lock (this) { if (_ast != null) + { return _ast; + } ParseError[] errors; _ast = (new Parser()).Parse(null, _scriptText, null, out errors, ParseMode.Default); @@ -239,33 +311,70 @@ 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; private Attribute[] _attributes; private bool _usesCmdletBinding; private bool _compiledOptimized; private bool _compiledUnoptimized; private bool _hasSuspiciousContent; + 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 IsFilter { get; private set; } - internal bool IsProductCode { get; private set; } + + internal bool SkipLogging { get; set; } + + internal bool IsFilter { get; } + + internal bool IsProductCode + { + get + { + _isProductCode ??= SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); + + return _isProductCode.Value; + } + } internal bool GetIsConfiguration() { @@ -280,10 +389,13 @@ internal bool HasSuspiciousContent { get { - Diagnostics.Assert(_compiledOptimized || _compiledUnoptimized, "HasSuspiciousContent is not set correctly before being compiled"); + Diagnostics.Assert( + _compiledOptimized || _compiledUnoptimized, + "HasSuspiciousContent is not set correctly before being compiled"); return _hasSuspiciousContent; } - set { _hasSuspiciousContent = value; } + + set => _hasSuspiciousContent = value; } private MergedCommandParameterMetadata _parameterMetadata; @@ -295,7 +407,9 @@ internal List GetAttributes() InitializeMetadata(); } - Diagnostics.Assert(_attributes != null, "after initialization, attributes is never null, must be an empty list if no attributes."); + Diagnostics.Assert( + _attributes != null, + "after initialization, attributes is never null, must be an empty list if no attributes."); return _attributes.ToList(); } @@ -320,6 +434,7 @@ internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters { InitializeMetadata(); } + return _runtimeDefinedParameterDictionary; } } @@ -333,7 +448,9 @@ internal CmdletBindingAttribute CmdletBindingAttribute InitializeMetadata(); } - return _usesCmdletBinding ? (CmdletBindingAttribute)_attributes.FirstOrDefault(attr => attr is CmdletBindingAttribute) : null; + return _usesCmdletBinding + ? (CmdletBindingAttribute)Array.Find(_attributes, static attr => attr is CmdletBindingAttribute) + : null; } } @@ -346,10 +463,31 @@ internal ObsoleteAttribute ObsoleteAttribute InitializeMetadata(); } - return (ObsoleteAttribute)_attributes.FirstOrDefault(attr => attr is ObsoleteAttribute); + return (ObsoleteAttribute)Array.Find(_attributes, static attr => attr is ObsoleteAttribute); } } + internal ExperimentalAttribute ExperimentalAttribute + { + get + { + if (_expAttribute == ExperimentalAttribute.None) + { + lock (this) + { + if (_expAttribute == ExperimentalAttribute.None) + { + _expAttribute = Ast.GetExperimentalAttributes().FirstOrDefault(); + } + } + } + + return _expAttribute; + } + } + + private ExperimentalAttribute _expAttribute = ExperimentalAttribute.None; + public MergedCommandParameterMetadata GetParameterMetadata(ScriptBlock scriptBlock) { if (_parameterMetadata == null) @@ -358,21 +496,26 @@ public MergedCommandParameterMetadata GetParameterMetadata(ScriptBlock scriptBlo { if (_parameterMetadata == null) { - CommandMetadata metadata = new CommandMetadata(scriptBlock, "", LocalPipeline.GetExecutionContextFromTLS()); + CommandMetadata metadata = new CommandMetadata( + scriptBlock, + string.Empty, + LocalPipeline.GetExecutionContextFromTLS()); _parameterMetadata = metadata.StaticCommandParameterMetadata; } } } + return _parameterMetadata; } public override string ToString() { if (_scriptText != null) + { return _scriptText; + } - var sbAst = _ast as ScriptBlockAst; - if (sbAst != null) + if (_ast is ScriptBlockAst sbAst) { return sbAst.ToStringForSerialization(); } @@ -396,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)) { } @@ -430,12 +572,14 @@ 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) { } private static readonly ConcurrentDictionary, ScriptBlock> s_cachedScripts = new ConcurrentDictionary, ScriptBlock>(); + internal static ScriptBlock TryGetCachedScriptBlock(string fileName, string fileContents) { if (InternalTestHooks.IgnoreScriptBlockCache) @@ -447,24 +591,20 @@ internal static ScriptBlock TryGetCachedScriptBlock(string fileName, string file var key = Tuple.Create(fileName, fileContents); if (s_cachedScripts.TryGetValue(key, out scriptBlock)) { - Diagnostics.Assert(scriptBlock.SessionStateInternal == null, - "A cached scriptblock should not have it's session state bound, that causes a memory leak."); + Diagnostics.Assert( + scriptBlock.SessionStateInternal == null, + "A cached scriptblock should not have it's session state bound, that causes a memory leak."); return scriptBlock.Clone(); } + return null; } private static bool IsDynamicKeyword(Ast ast) - { - var cmdAst = ast as CommandAst; - return (cmdAst != null && cmdAst.DefiningKeyword != null); - } + => ast is CommandAst cmdAst && cmdAst.DefiningKeyword != null; private static bool IsUsingTypes(Ast ast) - { - var cmdAst = ast as UsingStatementAst; - return (cmdAst != null && cmdAst.IsUsingModuleOrAssembly()); - } + => ast is UsingStatementAst cmdAst && cmdAst.IsUsingModuleOrAssembly(); internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, string fileContents) { @@ -473,18 +613,16 @@ internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, return; } - // // Don't cache scriptblocks that have // a) dynamic keywords // b) 'using module' or 'using assembly' // The definition of the dynamic keyword could change, consequently changing how the source text should be parsed. // Exported types definitions from 'using module' could change, we need to do all parse-time checks again. // 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; } @@ -493,19 +631,21 @@ internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, { s_cachedScripts.Clear(); } + var key = Tuple.Create(fileName, fileContents); s_cachedScripts.TryAdd(key, scriptBlock); } /// - /// Clears the cached scriptblocks + /// Clears the cached scriptblocks. /// internal static void ClearScriptBlockCache() { s_cachedScripts.Clear(); } - internal static ScriptBlock EmptyScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock("", isProductCode: true); + internal static readonly ScriptBlock EmptyScriptBlock = + ScriptBlock.CreateDelayParsedScriptBlock(string.Empty, isProductCode: true); internal static ScriptBlock Create(Parser parser, string fileName, string fileContents) { @@ -515,8 +655,7 @@ internal static ScriptBlock Create(Parser parser, string fileName, string fileCo return scriptBlock; } - ParseError[] errors; - var ast = parser.Parse(fileName, fileContents, null, out errors, ParseMode.Default); + var ast = parser.Parse(fileName, fileContents, null, out ParseError[] errors, ParseMode.Default); if (errors.Length != 0) { throw new ParseException(errors); @@ -531,18 +670,12 @@ internal static ScriptBlock Create(Parser parser, string fileName, string fileCo return result.Clone(); } - internal ScriptBlock Clone() - { - return new ScriptBlock(_scriptBlockData); - } + internal ScriptBlock Clone() => new ScriptBlock(_scriptBlockData); /// /// Returns the text of the script block. The return value might not match the original text exactly. /// - public override string ToString() - { - return _scriptBlockData.ToString(); - } + public override string ToString() => _scriptBlockData.ToString(); /// /// Returns the text of the script block with the handling of $using expressions. @@ -579,36 +712,32 @@ internal string ToStringWithDollarUsingHandling( return sbText; } - /// - /// Support for . - /// - public virtual void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw PSTraceSource.NewArgumentNullException("info"); - } - - string serializedContent = this.ToString(); - info.AddValue("ScriptText", serializedContent); - info.SetType(typeof(ScriptBlockSerializationHelper)); - } - - internal PowerShell GetPowerShellImpl(ExecutionContext context, Dictionary variables, bool isTrustedInput, - bool filterNonUsingVariables, bool? createLocalScope, params object[] args) + internal PowerShell GetPowerShellImpl( + ExecutionContext context, + Dictionary variables, + bool isTrustedInput, + bool filterNonUsingVariables, + bool? createLocalScope, + params object[] args) { - return AstInternal.GetPowerShell(context, variables, isTrustedInput, filterNonUsingVariables, createLocalScope, args); + return AstInternal.GetPowerShell( + context, + variables, + isTrustedInput, + filterNonUsingVariables, + createLocalScope, + args); } internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, object[] args) { - var pipelineAst = GetSimplePipeline(resourceString => { throw PSTraceSource.NewInvalidOperationException(resourceString); }); + var pipelineAst = GetSimplePipeline( + 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); + throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.CantConvertEmptyPipeline); } return PipelineOps.GetSteppablePipeline(pipelineAst, commandOrigin, this, args); @@ -616,30 +745,31 @@ 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); } var ast = AstInternal; var statements = ast.Body.EndBlock.Statements; - if (!statements.Any()) + if (statements.Count == 0) { return errorHandler(AutomationExceptions.CantConvertEmptyPipeline); } + if (statements.Count > 1) { return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline); } - if (ast.Body.EndBlock.Traps != null && ast.Body.EndBlock.Traps.Any()) + + if (ast.Body.EndBlock.Traps != null && ast.Body.EndBlock.Traps.Count > 0) { return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap); } - var pipeAst = statements[0] as PipelineAst; - if (pipeAst == null) + if (statements[0] is not PipelineAst pipeAst) { return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline); } @@ -651,88 +781,86 @@ private PipelineAst GetSimplePipeline(Func errorHandler) return pipeAst; } - internal List GetAttributes() - { - return _scriptBlockData.GetAttributes(); - } + internal List GetAttributes() => _scriptBlockData.GetAttributes(); - internal string GetFileName() - { - return AstInternal.Body.Extent.File; - } + internal string GetFileName() => AstInternal.Body.Extent.File; - internal bool IsMetaConfiguration() - { - // GetAttributes() is asserted never return null - return GetAttributes().OfType().Any(); - } + // GetAttributes() is asserted never return null + internal bool IsMetaConfiguration() => GetAttributes().OfType().Any(); - internal PSToken GetStartPosition() - { - return new PSToken(Ast.Extent); - } + internal PSToken GetStartPosition() => new PSToken(Ast.Extent); internal MergedCommandParameterMetadata ParameterMetadata { - get { return _scriptBlockData.GetParameterMetadata(this); } + get => _scriptBlockData.GetParameterMetadata(this); } - internal bool UsesCmdletBinding - { - get { return _scriptBlockData.UsesCmdletBinding; } - } + internal bool UsesCmdletBinding { get => _scriptBlockData.UsesCmdletBinding; } - internal bool HasDynamicParameters - { - get { return AstInternal.Body.DynamicParamBlock != null; } - } + internal bool HasDynamicParameters { get => AstInternal.Body.DynamicParamBlock != null; } /// - /// DebuggerHidden + /// DebuggerHidden. /// public bool DebuggerHidden { - get { return _scriptBlockData.DebuggerHidden; } - set { _scriptBlockData.DebuggerHidden = value; } + get => _scriptBlockData.DebuggerHidden; + set => _scriptBlockData.DebuggerHidden = value; } /// /// The unique ID of this script block. /// - public Guid Id - { - get { return _scriptBlockData.Id; } - } + public Guid Id { get => _scriptBlockData.Id; } internal bool DebuggerStepThrough { get { return _scriptBlockData.DebuggerStepThrough; } + set { _scriptBlockData.DebuggerStepThrough = value; } } internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters { - get { return _scriptBlockData.RuntimeDefinedParameters; } + get => _scriptBlockData.RuntimeDefinedParameters; } internal bool HasLogged { - get { return _scriptBlockData.HasLogged; } - set { _scriptBlockData.HasLogged = value; } + get => _scriptBlockData.HasLogged; + set => _scriptBlockData.HasLogged = value; } - internal Assembly AssemblyDefiningPSTypes { set; get; } + internal bool SkipLogging + { + get { return _scriptBlockData.SkipLogging; } + + set { _scriptBlockData.SkipLogging = value; } + } - internal HelpInfo GetHelpInfo(ExecutionContext context, CommandInfo commandInfo, bool dontSearchOnRemoteComputer, - Dictionary scriptBlockTokenCache, out string helpFile, out string helpUriFromDotLink) + internal Assembly AssemblyDefiningPSTypes { get; set; } + + internal HelpInfo GetHelpInfo( + ExecutionContext context, + CommandInfo commandInfo, + bool dontSearchOnRemoteComputer, + Dictionary scriptBlockTokenCache, + out string helpFile, + out string helpUriFromDotLink) { helpUriFromDotLink = null; var commentTokens = HelpCommentsParser.GetHelpCommentTokens(AstInternal, scriptBlockTokenCache); if (commentTokens != null) { - return HelpCommentsParser.CreateFromComments(context, commandInfo, commentTokens.Item1, - commentTokens.Item2, dontSearchOnRemoteComputer, out helpFile, out helpUriFromDotLink); + return HelpCommentsParser.CreateFromComments( + context, + commandInfo, + commentTokens.Item1, + commentTokens.Item2, + dontSearchOnRemoteComputer, + out helpFile, + out helpUriFromDotLink); } helpFile = null; @@ -749,34 +877,48 @@ internal HelpInfo GetHelpInfo(ExecutionContext context, CommandInfo commandInfo, /// any variable will be allowed. /// /// The environment variables that are allowed. - public void CheckRestrictedLanguage(IEnumerable allowedCommands, IEnumerable allowedVariables, bool allowEnvironmentVariables) + public void CheckRestrictedLanguage( + IEnumerable allowedCommands, + IEnumerable allowedVariables, + bool allowEnvironmentVariables) { 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(errorAst.Extent, () => ParserStrings.InvalidScriptBlockInDataSection); + parser.ReportError( + errorAst.Extent, + nameof(ParserStrings.InvalidScriptBlockInDataSection), + ParserStrings.InvalidScriptBlockInDataSection); } if (HasEndBlock) { - RestrictedLanguageChecker rlc = new RestrictedLanguageChecker(parser, allowedCommands, allowedVariables, allowEnvironmentVariables); - var endBlock = ast.Body.EndBlock; - StatementBlockAst.InternalVisit(rlc, endBlock.Traps, endBlock.Statements, AstVisitAction.Continue); + var rlc = new RestrictedLanguageChecker( + parser, + allowedCommands, + allowedVariables, + allowEnvironmentVariables); + + StatementBlockAst.InternalVisit( + rlc, + ast.Body.EndBlock.Traps, + ast.Body.EndBlock.Statements, + AstVisitAction.Continue); } - if (parser.ErrorList.Any()) + if (parser.ErrorList.Count > 0) { throw new ParseException(parser.ErrorList.ToArray()); } } - internal string GetWithInputHandlingForInvokeCommand() - { - return AstInternal.GetWithInputHandlingForInvokeCommand(); - } + internal string GetWithInputHandlingForInvokeCommand() => AstInternal.GetWithInputHandlingForInvokeCommand(); internal string GetWithInputHandlingForInvokeCommandWithUsingExpression( Tuple, string> usingVariablesTuple) @@ -785,107 +927,109 @@ internal string GetWithInputHandlingForInvokeCommandWithUsingExpression( AstInternal.GetWithInputHandlingForInvokeCommandWithUsingExpression(usingVariablesTuple); // result.Item1 is ParamText; result.Item2 is ScriptBlockText - if (result.Item1 == null) - return result.Item2; - return result.Item1 + result.Item2; - } - - internal bool IsUsingDollarInput() - { - return AstSearcher.IsUsingDollarInput(this.Ast); - } - - internal void InvokeWithPipeImpl(bool createLocalScope, - Dictionary functionsToDefine, - List variablesToDefine, - ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - Pipe outputPipe, - InvocationInfo invocationInfo, - params object[] args) - { - InvokeWithPipeImpl(ScriptBlockClauseToInvoke.ProcessBlockOnly, createLocalScope, - functionsToDefine, - variablesToDefine, - errorHandlingBehavior, - dollarUnder, - input, - scriptThis, - outputPipe, - invocationInfo, - args); - } - - internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, - bool createLocalScope, - Dictionary functionsToDefine, - List variablesToDefine, - ErrorHandlingBehavior errorHandlingBehavior, - object dollarUnder, - object input, - object scriptThis, - Pipe outputPipe, - InvocationInfo invocationInfo, - params object[] args) - { - if (clauseToInvoke == ScriptBlockClauseToInvoke.Begin && !HasBeginBlock) - { - return; - } - else if (clauseToInvoke == ScriptBlockClauseToInvoke.Process && !HasProcessBlock) - { - return; - } - else if (clauseToInvoke == ScriptBlockClauseToInvoke.End && !HasEndBlock) + return result.Item1 == null ? result.Item2 : result.Item1 + result.Item2; + } + + internal bool IsUsingDollarInput() => AstSearcher.IsUsingDollarInput(this.Ast); + + internal void InvokeWithPipeImpl( + bool createLocalScope, + Dictionary functionsToDefine, + List variablesToDefine, + ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + Pipe outputPipe, + InvocationInfo invocationInfo, + params object[] args) + { + InvokeWithPipeImpl( + ScriptBlockClauseToInvoke.ProcessBlockOnly, + createLocalScope, + functionsToDefine, + variablesToDefine, + errorHandlingBehavior, + dollarUnder, + input, + scriptThis, + outputPipe, + invocationInfo, + args); + } + + internal void InvokeWithPipeImpl( + ScriptBlockClauseToInvoke clauseToInvoke, + bool createLocalScope, + Dictionary functionsToDefine, + List variablesToDefine, + ErrorHandlingBehavior errorHandlingBehavior, + object dollarUnder, + object input, + object scriptThis, + Pipe outputPipe, + 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)) { return; } ExecutionContext context = GetContextFromTLS(); - Diagnostics.Assert(SessionStateInternal == null || SessionStateInternal.ExecutionContext == context, - "The scriptblock is being invoked in a runspace different than the one where it was created"); + Diagnostics.Assert( + SessionStateInternal == null || SessionStateInternal.ExecutionContext == context, + "The scriptblock is being invoked in a runspace different than the one where it was created"); if (context.CurrentPipelineStopping) { throw new PipelineStoppedException(); } - // Validate at the arguments are consistent. The only public API that gets you here never sets createLocalScope to false... - Diagnostics.Assert(createLocalScope == true || functionsToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'functionsToDefine' != null then 'createLocalScope' must be true"); - Diagnostics.Assert(createLocalScope == true || variablesToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'variablesToDefine' != null then 'createLocalScope' must be true"); + // 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"); + Diagnostics.Assert( + createLocalScope || variablesToDefine == null, + "When calling ScriptBlock.InvokeWithContext(), if 'variablesToDefine' != null then 'createLocalScope' must be true"); - if (args == null) - { - args = Utils.EmptyArray(); - } + 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 }; + return; } + // 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); if (dollarUnder != AutomationNull.Value) { locals.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnder, context); } + if (input != AutomationNull.Value) { locals.SetAutomaticVariable(AutomaticVariable.Input, input, context); } + if (scriptThis != AutomationNull.Value) { locals.SetAutomaticVariable(AutomaticVariable.This, scriptThis, context); } + SetPSScriptRootAndPSCommandPath(locals, context); var oldShellFunctionErrorOutputPipe = context.ShellFunctionErrorOutputPipe; @@ -893,15 +1037,35 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, 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) { - oldLanguageMode = context.LanguageMode; - newLanguageMode = this.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. + if (this.LanguageMode != PSLanguageMode.FullLanguage + || createLocalScope + || context.EngineSessionState.Module?.LanguageMode == PSLanguageMode.FullLanguage) + { + 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; @@ -920,7 +1084,9 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, locals.SetAutomaticVariable(AutomaticVariable.MyInvocation, myInvocationInfo, context); if (SessionStateInternal != null) + { context.EngineSessionState = SessionStateInternal; + } // If we don't want errors written, hide the error pipe. switch (errorHandlingBehavior) @@ -955,6 +1121,7 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, e.SetErrorId("EmptyFunctionNameInFunctionDefinitionDictionary"); throw e; } + if (def.Value == null) { PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException( @@ -963,6 +1130,7 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, e.SetErrorId("NullFunctionBodyInFunctionDefinitionDictionary"); throw e; } + newScope.FunctionTable.Add(def.Key, new FunctionInfo(def.Key, def.Value, context)); } } @@ -981,8 +1149,10 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, e.SetErrorId("NullEntryInVariablesDefinitionList"); throw e; } + string name = psvar.Name; - Diagnostics.Assert(!(string.Equals(name, "this") || string.Equals(name, "_") || string.Equals(name, "input")), + Diagnostics.Assert( + !(string.Equals(name, "this") || string.Equals(name, "_") || string.Equals(name, "input")), "The list of variables to set in the scriptblock's scope cannot contain 'this', '_' or 'input'. These variables should be removed before passing the collection to this routine."); index++; newScope.Variables.Add(name, psvar); @@ -993,7 +1163,7 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, { if (context.EngineSessionState.CurrentScope.LocalsTuple == null) { - // If the locals tuple is, that means either: + // If the locals tuple is null, that means either: // * we're invoking a script block for a module // * something unexpected context.EngineSessionState.CurrentScope.LocalsTuple = locals; @@ -1013,7 +1183,11 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, args = BindArgumentsForScriptblockInvoke( (RuntimeDefinedParameter[])RuntimeDefinedParameters.Data, - args, context, !createLocalScope, backupWhenDotting, locals); + args, + context, + !createLocalScope, + backupWhenDotting, + locals); locals.SetAutomaticVariable(AutomaticVariable.Args, args, context); context.EngineSessionState.CurrentScope.ScopeOrigin = CommandOrigin.Internal; @@ -1030,7 +1204,7 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, _sequencePoints = SequencePoints, }; - ScriptBlock.LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); + LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); try { @@ -1038,7 +1212,7 @@ internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, } finally { - ScriptBlock.LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); + LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); } } catch (TargetInvocationException tie) @@ -1093,16 +1267,21 @@ internal MutableTuple MakeLocalsTuple(bool createLocalScope) MutableTuple locals; if (createLocalScope) { - locals = MutableTuple.MakeTuple(_scriptBlockData.LocalsMutableTupleType, _scriptBlockData.NameToIndexMap, _scriptBlockData.LocalsMutableTupleCreator); + locals = MutableTuple.MakeTuple( + _scriptBlockData.LocalsMutableTupleType, + _scriptBlockData.NameToIndexMap, + _scriptBlockData.LocalsMutableTupleCreator); } else { - locals = MutableTuple.MakeTuple(_scriptBlockData.UnoptimizedLocalsMutableTupleType, + locals = MutableTuple.MakeTuple( + _scriptBlockData.UnoptimizedLocalsMutableTupleType, UsesCmdletBinding ? Compiler.DottedScriptCmdletLocalsNameIndexMap : Compiler.DottedLocalsNameIndexMap, _scriptBlockData.UnoptimizedLocalsMutableTupleCreator); } + return locals; } @@ -1134,6 +1313,7 @@ internal static object[] BindArgumentsForScriptblockInvoke( // We pass in a null SessionStateInternal because the current scope is already set correctly. valueToBind = ((Compiler.DefaultValueExpressionWrapper)valueToBind).GetValue(context, null); } + wasDefaulted = true; } else @@ -1144,7 +1324,8 @@ internal static object[] BindArgumentsForScriptblockInvoke( bool valueSet = false; if (dotting && backupWhenDotting != null) { - backupWhenDotting[parameter.Name] = context.EngineSessionState.GetVariableAtScope(parameter.Name, "local"); + backupWhenDotting[parameter.Name] = + context.EngineSessionState.GetVariableAtScope(parameter.Name, "local"); } else { @@ -1153,7 +1334,11 @@ internal static object[] BindArgumentsForScriptblockInvoke( if (!valueSet) { - var variable = new PSVariable(parameter.Name, valueToBind, ScopedItemOptions.None, parameter.Attributes); + var variable = new PSVariable( + parameter.Name, + valueToBind, + ScopedItemOptions.None, + parameter.Attributes); context.EngineSessionState.SetVariable(variable, false, CommandOrigin.Internal); } @@ -1164,12 +1349,15 @@ internal static object[] BindArgumentsForScriptblockInvoke( } } - locals.SetAutomaticVariable(AutomaticVariable.PSBoundParameters, boundParameters.GetValueToBindToPSBoundParameters(), context); + locals.SetAutomaticVariable( + AutomaticVariable.PSBoundParameters, + boundParameters.GetValueToBindToPSBoundParameters(), + context); var leftOverArgs = args.Length - parameters.Length; if (leftOverArgs <= 0) { - return Utils.EmptyArray(); + return Array.Empty(); } object[] result = new object[leftOverArgs]; @@ -1178,13 +1366,12 @@ internal static object[] BindArgumentsForScriptblockInvoke( } internal static void SetAutomaticVariable(AutomaticVariable variable, object value, MutableTuple locals) - { - locals.SetValue((int)variable, value); - } + => locals.SetValue((int)variable, value); private Action GetCodeToInvoke(ref bool optimized, ScriptBlockClauseToInvoke clauseToInvoke) { - if (clauseToInvoke == ScriptBlockClauseToInvoke.ProcessBlockOnly && (HasBeginBlock || (HasEndBlock && HasProcessBlock))) + if (clauseToInvoke == ScriptBlockClauseToInvoke.ProcessBlockOnly + && (HasBeginBlock || HasCleanBlock || (HasEndBlock && HasProcessBlock))) { throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.ScriptBlockInvokeOnOneClauseOnly); } @@ -1201,10 +1388,13 @@ 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; } } + switch (clauseToInvoke) { case ScriptBlockClauseToInvoke.Begin: @@ -1213,82 +1403,87 @@ 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; } } - internal CmdletBindingAttribute CmdletBindingAttribute - { - get { return _scriptBlockData.CmdletBindingAttribute; } - } + internal CmdletBindingAttribute CmdletBindingAttribute { get => _scriptBlockData.CmdletBindingAttribute; } - internal ObsoleteAttribute ObsoleteAttribute - { - get { return _scriptBlockData.ObsoleteAttribute; } - } + internal ObsoleteAttribute ObsoleteAttribute { get => _scriptBlockData.ObsoleteAttribute; } - internal bool Compile(bool optimized) - { - return _scriptBlockData.Compile(optimized); - } + internal ExperimentalAttribute ExperimentalAttribute { get => _scriptBlockData.ExperimentalAttribute; } + + internal bool Compile(bool optimized) => _scriptBlockData.Compile(optimized); internal static void LogScriptBlockCreation(ScriptBlock scriptBlock, bool force) { + if (scriptBlock.HasLogged && !InternalTestHooks.ForceScriptBlockLogging) + { + // Fast exit if the script block is already logged and we are not force re-logging in tests. + return; + } + ScriptBlockLogging logSetting = GetScriptBlockLoggingSetting(); if (force || logSetting?.EnableScriptBlockLogging == true) { - if (!scriptBlock.HasLogged || InternalTestHooks.ForceScriptBlockLogging) + // If script block logging is explicitly disabled, or it's from a trusted + // file or internal, skip logging. + if (logSetting?.EnableScriptBlockLogging == false + || scriptBlock.ScriptBlockData.IsProductCode) { - // If script block logging is explicitly disabled, or it's from a trusted - // file or internal, skip logging. - if (logSetting?.EnableScriptBlockLogging == false || - scriptBlock.ScriptBlockData.IsProductCode) - { - return; - } + scriptBlock.SkipLogging = true; + return; + } - string scriptBlockText = scriptBlock.Ast.Extent.Text; - bool written = false; + string scriptBlockText = scriptBlock.Ast.Extent.Text; + bool written = false; - // Maximum size of ETW events is 64kb. Split a message if it is larger than 20k (Unicode) characters. - if (scriptBlockText.Length < 20000) - { - written = WriteScriptBlockToLog(scriptBlock, 0, 1, scriptBlock.Ast.Extent.Text); - } - else + // Maximum size of ETW events is 64kb. Split a message if it is larger than 20k (Unicode) characters. + if (scriptBlockText.Length < 20000) + { + written = WriteScriptBlockToLog(scriptBlock, 0, 1, scriptBlock.Ast.Extent.Text); + } + else + { + // 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 + Random.Shared.Next(10000); + int segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1; + int currentLocation = 0; + int currentSegmentSize = 0; + + for (int segment = 0; segment < segments; segment++) { - // 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 segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1; - int currentLocation = 0; - int currentSegmentSize = 0; - - for (int segment = 0; segment < segments; segment++) - { - currentLocation = segment * segmentSize; - currentSegmentSize = Math.Min(segmentSize, scriptBlockText.Length - currentLocation); + currentLocation = segment * segmentSize; + currentSegmentSize = Math.Min(segmentSize, scriptBlockText.Length - currentLocation); - string textToLog = scriptBlockText.Substring(currentLocation, currentSegmentSize); - written = WriteScriptBlockToLog(scriptBlock, segment, segments, textToLog); - } + string textToLog = scriptBlockText.Substring(currentLocation, currentSegmentSize); + written = WriteScriptBlockToLog(scriptBlock, segment, segments, textToLog); } + } - if (written) - { - scriptBlock.HasLogged = true; - } + if (written) + { + scriptBlock.HasLogged = true; } } + else + { + scriptBlock.SkipLogging = true; + } } private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, int segments, string textToLog) { // See if we need to encrypt the event log message. This info is all cached by Utils.GetPolicySetting(), // so we're not hitting the configuration file for every script block we compile. - ProtectedEventLogging logSetting = Utils.GetPolicySetting(Utils.SystemWideOnlyConfig); + ProtectedEventLogging logSetting = + Utils.GetPolicySetting(Utils.SystemWideOnlyConfig); + bool wasEncoded = false; if (logSetting != null) { lock (s_syncObject) @@ -1302,14 +1497,20 @@ private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, return false; } - // If we have recipients to encrypt to, then do so. Otherwise, we'll just log the plain text - // version. + // If we have recipients to encrypt to, then do so. + // Otherwise, we'll just log the plain text version. if (s_encryptionRecipients != null) { + // Encrypt the raw text from the scriptblock. + // The user may have to deal with any control characters in the data. ExecutionContext executionContext = LocalPipeline.GetExecutionContextFromTLS(); ErrorRecord error = null; byte[] contentBytes = System.Text.Encoding.UTF8.GetBytes(textToLog); - string encodedContent = CmsUtils.Encrypt(contentBytes, s_encryptionRecipients, executionContext.SessionState, out error); + string encodedContent = CmsUtils.Encrypt( + contentBytes, + s_encryptionRecipients, + executionContext.SessionState, + out error); // Can't cache the reporting of encryption errors, as they are likely content-based. if (error != null) @@ -1320,33 +1521,112 @@ private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, // attacker seeing potentially sensitive data. Because if they aren't detected, then // they can just wait on the compromised box and see the sensitive data eventually anyways. - string errorMessage = StringUtil.Format(SecuritySupportStrings.CouldNotEncryptContent, textToLog, error.ToString()); - PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational, - 0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? String.Empty); + string errorMessage = StringUtil.Format( + SecuritySupportStrings.CouldNotEncryptContent, + textToLog, + error.ToString()); + PSEtwLog.LogOperationalError( + id: PSEventId.ScriptBlock_Compile_Detail, + opcode: PSOpcode.Create, + task: PSTask.ExecuteCommand, + keyword: PSKeyword.UseAlwaysOperational, + 0, + 0, + errorMessage, + scriptBlock.Id.ToString(), + scriptBlock.File ?? string.Empty); } else { textToLog = encodedContent; + wasEncoded = true; } } } } + if (!wasEncoded) + { + textToLog = FormatLogString(textToLog); + } + if (scriptBlock._scriptBlockData.HasSuspiciousContent) { - PSEtwLog.LogOperationalWarning(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational, - segment + 1, segments, textToLog, scriptBlock.Id.ToString(), scriptBlock.File ?? String.Empty); + PSEtwLog.LogOperationalWarning( + id: PSEventId.ScriptBlock_Compile_Detail, + opcode: PSOpcode.Create, + task: PSTask.ExecuteCommand, + keyword: PSKeyword.UseAlwaysOperational, + segment + 1, + segments, + textToLog, + scriptBlock.Id.ToString(), + scriptBlock.File ?? string.Empty); } else { - PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational, - segment + 1, segments, textToLog, scriptBlock.Id.ToString(), scriptBlock.File ?? String.Empty); + PSEtwLog.LogOperationalVerbose( + id: PSEventId.ScriptBlock_Compile_Detail, + opcode: PSOpcode.Create, + task: PSTask.ExecuteCommand, + keyword: PSKeyword.UseAlwaysOperational, + segment + 1, + segments, + textToLog, + scriptBlock.Id.ToString(), + scriptBlock.File ?? string.Empty); } return true; } - private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, ProtectedEventLogging logSetting) + private static string FormatLogString(string textToLog) + { + const char NullControlChar = '\u0000'; + + // The null symbol - `␀` + const char NullSymbolChar = '\u2400'; + + // No logging mechanism(s) cannot handle null and rendering may not be able to handle + // null as we have the string defined as a null terminated string in the manifest. + // So, replace null characters with the Unicode `SYMBOL FOR NULL` + // We don't just remove the characters to preserve the fact that a null character was there. +#if UNIX + const char LinefeedControlChar = '\u000A'; + const char CarriageReturnControlChar = '\u000D'; + + // We chose the return symbol because we believe it is more associated with these concepts + // than the carriage return (␍), line feed (␊), or new line (␤) symbols. + // The return symbol - `⏎` + const char ReturnSymbolChar = '\u23CE'; + + if (Platform.IsLinux) + { + // Because the creation of the string builder is expensive + // We only do this on Linux where we are doing multiple replace operations + StringBuilder logBuilder = new StringBuilder(textToLog); + + logBuilder.Replace(NullControlChar, NullSymbolChar); + + // Syslog (only used on Linux) encodes CR and NL to their octal values. + // We will replace them with a unicode 'RETURN SYMBOL' (U+23CE) charater for easier viewing + logBuilder.Replace(LinefeedControlChar, ReturnSymbolChar); + logBuilder.Replace(CarriageReturnControlChar, ReturnSymbolChar); + + return logBuilder.ToString(); + } + else + { + return textToLog.Replace(NullControlChar, NullSymbolChar); + } +#else + return textToLog.Replace(NullControlChar, NullSymbolChar); +#endif + } + + private static bool GetAndValidateEncryptionRecipients( + ScriptBlock scriptBlock, + ProtectedEventLogging logSetting) { // See if protected event logging is enabled if (logSetting.EnableProtectedEventLogging == true) @@ -1372,7 +1652,7 @@ private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, return false; } - string fullCertificateContent = String.Join(Environment.NewLine, logSetting.EncryptionCertificate); + string fullCertificateContent = string.Join(Environment.NewLine, logSetting.EncryptionCertificate); // If the certificate has changed, drop all of our cached information ResetCertificateCacheIfNeeded(fullCertificateContent); @@ -1404,9 +1684,19 @@ private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, // being able to detect that an attacker has compromised a box outweighs the danger of the // attacker seeing potentially sensitive data. Because if they aren't detected, then // they can just wait on the compromised box and see the sensitive data eventually anyways. - string errorMessage = StringUtil.Format(SecuritySupportStrings.CouldNotUseCertificate, error.ToString()); - PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational, - 0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? String.Empty); + string errorMessage = StringUtil.Format( + SecuritySupportStrings.CouldNotUseCertificate, + error.ToString()); + PSEtwLog.LogOperationalError( + id: PSEventId.ScriptBlock_Compile_Detail, + opcode: PSOpcode.Create, + task: PSTask.ExecuteCommand, + keyword: PSKeyword.UseAlwaysOperational, + 0, + 0, + errorMessage, + scriptBlock.Id.ToString(), + scriptBlock.File ?? string.Empty); return true; } @@ -1429,9 +1719,19 @@ private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, certificateForLog = logSetting.EncryptionCertificate[1]; } - string errorMessage = StringUtil.Format(SecuritySupportStrings.CertificateContainsPrivateKey, certificateForLog); - PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational, - 0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? String.Empty); + string errorMessage = StringUtil.Format( + SecuritySupportStrings.CertificateContainsPrivateKey, + certificateForLog); + PSEtwLog.LogOperationalError( + id: PSEventId.ScriptBlock_Compile_Detail, + opcode: PSOpcode.Create, + task: PSTask.ExecuteCommand, + keyword: PSKeyword.UseAlwaysOperational, + 0, + 0, + errorMessage, + scriptBlock.Id.ToString(), + scriptBlock.File ?? string.Empty); } } } @@ -1440,15 +1740,19 @@ private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, return true; } - private static object s_syncObject = new Object(); - private static string s_lastSeenCertificate = String.Empty; + 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 readonly Lazy s_sbLoggingSettingCache = new Lazy( + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + isThreadSafe: true); + // Reset any static caches if the certificate has changed private static void ResetCertificateCacheIfNeeded(string certificate) { - if (!String.Equals(s_lastSeenCertificate, certificate, StringComparison.Ordinal)) + if (!string.Equals(s_lastSeenCertificate, certificate, StringComparison.Ordinal)) { s_hasProcessedCertificate = false; s_lastSeenCertificate = certificate; @@ -1458,7 +1762,12 @@ private static void ResetCertificateCacheIfNeeded(string certificate) private static ScriptBlockLogging GetScriptBlockLoggingSetting() { - return Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig); + if (InternalTestHooks.BypassGroupPolicyCaching) + { + return Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig); + } + + return s_sbLoggingSettingCache.Value; } // Quick check for script blocks that may have suspicious content. If this @@ -1473,12 +1782,12 @@ internal static string CheckSuspiciousContent(Ast scriptBlockAst) if (scriptBlockAst.HasSuspiciousContent) { - Ast foundAst = scriptBlockAst.Find(ast => - { - // Try to find the lowest AST that was not considered suspicious, but its parent - // was. - return (!ast.HasSuspiciousContent) && (ast.Parent.HasSuspiciousContent); - }, true); + Ast foundAst = scriptBlockAst.Find( + ast => + { + // Try to find the lowest AST that was not considered suspicious, but its parent was. + return (!ast.HasSuspiciousContent) && ast.Parent.HasSuspiciousContent; + }, true); if (foundAst != null) { @@ -1493,7 +1802,7 @@ internal static string CheckSuspiciousContent(Ast scriptBlockAst) return null; } - class SuspiciousContentChecker + private static class SuspiciousContentChecker { // Based on a (bad) random number generator, but good enough // for our simple needs. @@ -1508,7 +1817,7 @@ class SuspiciousContentChecker /// code - needed only to generate this switch statement below.) /// /// The string matching the hash, or null. - static string LookupHash(uint h) + private static string LookupHash(uint h) { switch (h) { @@ -1661,20 +1970,25 @@ 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. private static string CheckForMatches(uint[] runningHash, int upTo) { var upToMax = runningHash.Length; - if (upTo == 0) upTo = upToMax; - else if (upTo > upToMax) upTo = upToMax; + if (upTo == 0 || upTo > upToMax) + { + upTo = upToMax; + } for (var i = 0; i < upTo; i++) { var result = LookupHash(runningHash[i]); - if (result != null) return result; + if (result != null) + { + return result; + } } return null; @@ -1711,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 == '-')) { @@ -1721,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: @@ -1742,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; + } } } @@ -1750,7 +2067,7 @@ public static string Match(string text) } #if false - //This code can be used when adding a new pattern. + // This code can be used when adding a new pattern. internal static uint HashNewPattern(string pattern) { char ToLower(char c) @@ -1759,11 +2076,13 @@ char ToLower(char c) { c = (char) (c | 0x20); } + return c; } if (pattern.Length > 29) { - throw new Exception("Update runningHash in match for new longest string.\n" + + throw new Exception( + "Update runningHash in match for new longest string.\n" + "Also a longer maximum length could greatly affect the performance of this algorithm, so only increase with care."); } @@ -1780,22 +2099,26 @@ char ToLower(char c) internal static void LogScriptBlockStart(ScriptBlock scriptBlock, Guid runspaceId) { - // When invoking, log the creation of the script block if it has suspicious - // content - bool forceLogCreation = false; - if (scriptBlock._scriptBlockData.HasSuspiciousContent) + // Fast exit if the script block can skip logging. + if (!scriptBlock.SkipLogging) { - forceLogCreation = true; - } + // When invoking, log the creation of the script block if it has suspicious content + bool forceLogCreation = scriptBlock._scriptBlockData.HasSuspiciousContent; - // We delay logging the creation util the 'Start' so that we can be sure we've - // properly analyzed the script block's security. - LogScriptBlockCreation(scriptBlock, forceLogCreation); + // We delay logging the creation until the 'Start' so that we can be sure we've + // properly analyzed the script block's security. + LogScriptBlockCreation(scriptBlock, forceLogCreation); + } if (GetScriptBlockLoggingSetting()?.EnableScriptBlockInvocationLogging == true) { - PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Invoke_Start_Detail, PSOpcode.Create, PSTask.CommandStart, PSKeyword.UseAlwaysOperational, - scriptBlock.Id.ToString(), runspaceId.ToString()); + PSEtwLog.LogOperationalVerbose( + id: PSEventId.ScriptBlock_Invoke_Start_Detail, + opcode: PSOpcode.Create, + task: PSTask.CommandStart, + keyword: PSKeyword.UseAlwaysOperational, + scriptBlock.Id.ToString(), + runspaceId.ToString()); } } @@ -1803,70 +2126,54 @@ internal static void LogScriptBlockEnd(ScriptBlock scriptBlock, Guid runspaceId) { if (GetScriptBlockLoggingSetting()?.EnableScriptBlockInvocationLogging == true) { - PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Invoke_Complete_Detail, PSOpcode.Create, PSTask.CommandStop, PSKeyword.UseAlwaysOperational, - scriptBlock.Id.ToString(), runspaceId.ToString()); + PSEtwLog.LogOperationalVerbose( + id: PSEventId.ScriptBlock_Invoke_Complete_Detail, + opcode: PSOpcode.Create, + task: PSTask.CommandStop, + keyword: PSKeyword.UseAlwaysOperational, + scriptBlock.Id.ToString(), + runspaceId.ToString()); } } - internal CompiledScriptBlockData ScriptBlockData { get { return _scriptBlockData; } } + internal CompiledScriptBlockData ScriptBlockData { get => _scriptBlockData; } /// /// Returns the AST corresponding to the script block. /// - public Ast Ast { get { return (Ast)_scriptBlockData.Ast; } } - internal IParameterMetadataProvider AstInternal { get { return _scriptBlockData.Ast; } } - - internal IScriptExtent[] SequencePoints { get { return _scriptBlockData.SequencePoints; } } - - internal Action DynamicParamBlock { get { return _scriptBlockData.DynamicParamBlock; } } - internal Action UnoptimizedDynamicParamBlock { get { return _scriptBlockData.UnoptimizedDynamicParamBlock; } } - internal Action BeginBlock { get { return _scriptBlockData.BeginBlock; } } - internal Action UnoptimizedBeginBlock { get { return _scriptBlockData.UnoptimizedBeginBlock; } } - internal Action ProcessBlock { get { return _scriptBlockData.ProcessBlock; } } - internal Action UnoptimizedProcessBlock { get { return _scriptBlockData.UnoptimizedProcessBlock; } } - internal Action EndBlock { get { return _scriptBlockData.EndBlock; } } - internal Action UnoptimizedEndBlock { get { return _scriptBlockData.UnoptimizedEndBlock; } } - - internal bool HasBeginBlock { get { return AstInternal.Body.BeginBlock != null; } } - internal bool HasProcessBlock { get { return AstInternal.Body.ProcessBlock != null; } } - internal bool HasEndBlock { get { return AstInternal.Body.EndBlock != null; } } - } + public Ast Ast { get => (Ast)_scriptBlockData.Ast; } - [Serializable] - internal class ScriptBlockSerializationHelper : ISerializable, IObjectReference - { - private readonly string _scriptText; + internal IParameterMetadataProvider AstInternal { get => _scriptBlockData.Ast; } - private ScriptBlockSerializationHelper(SerializationInfo info, StreamingContext context) - { - if (info == null) throw new ArgumentNullException("info"); + internal IScriptExtent[] SequencePoints { get => _scriptBlockData.SequencePoints; } - _scriptText = info.GetValue("ScriptText", typeof(string)) as string; - if (_scriptText == null) - { - throw PSTraceSource.NewArgumentNullException("info"); - } - } + internal Action DynamicParamBlock { get => _scriptBlockData.DynamicParamBlock; } - /// - /// 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) - { - return ScriptBlock.Create(_scriptText); - } + internal Action UnoptimizedDynamicParamBlock { get => _scriptBlockData.UnoptimizedDynamicParamBlock; } - /// - /// 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 Action BeginBlock { get => _scriptBlockData.BeginBlock; } + + internal Action UnoptimizedBeginBlock { get => _scriptBlockData.UnoptimizedBeginBlock; } + + internal Action ProcessBlock { get => _scriptBlockData.ProcessBlock; } + + internal Action UnoptimizedProcessBlock { get => _scriptBlockData.UnoptimizedProcessBlock; } + + internal Action EndBlock { get => _scriptBlockData.EndBlock; } + + 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; } + + internal bool HasCleanBlock { get => AstInternal.Body.CleanBlock != null; } } internal sealed class PSScriptCmdlet : PSCmdlet, IDynamicParameters, IDisposable @@ -1876,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); @@ -1920,7 +2229,10 @@ protected override void BeginProcessing() if (_scriptBlock.HasBeginBlock) { - RunClause(_runOptimized ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, AutomationNull.Value, _input); + RunClause( + clause: _runOptimized ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, + dollarUnderbar: AutomationNull.Value, + inputToProcess: _input); } } @@ -1941,9 +2253,13 @@ internal override void DoProcessRecord() dollarUnder = CurrentPipelineObject; _input.Add(dollarUnder); } + if (_scriptBlock.HasProcessBlock) { - RunClause(_runOptimized ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, dollarUnder, _input); + RunClause( + clause: _runOptimized ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, + dollarUnderbar: dollarUnder, + inputToProcess: _input); _input.Clear(); } } @@ -1957,7 +2273,38 @@ internal override void DoEndProcessing() if (_scriptBlock.HasEndBlock) { - RunClause(_runOptimized ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock, AutomationNull.Value, _input.ToArray()); + RunClause( + clause: _runOptimized ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock, + dollarUnderbar: AutomationNull.Value, + inputToProcess: _input.ToArray()); + } + } + + 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; + } } } @@ -1973,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; @@ -2026,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; @@ -2050,7 +2398,9 @@ private void RunClause(Action clause, object dollarUnderbar, ob this.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, exitCode); if (exitCode != 0) + { _commandRuntime.PipelineProcessor.ExecutionFailed = true; + } } } @@ -2063,13 +2413,17 @@ public object GetDynamicParameters() var resultList = new List(); Diagnostics.Assert(_functionContext._outputPipe == null, "Output pipe should not be set yet."); _functionContext._outputPipe = new Pipe(resultList); - RunClause(_runOptimized ? _scriptBlock.DynamicParamBlock : _scriptBlock.UnoptimizedDynamicParamBlock, - AutomationNull.Value, AutomationNull.Value); + RunClause( + clause: _runOptimized ? _scriptBlock.DynamicParamBlock : _scriptBlock.UnoptimizedDynamicParamBlock, + dollarUnderbar: AutomationNull.Value, + inputToProcess: AutomationNull.Value); if (resultList.Count > 1) { - throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.DynamicParametersWrongType, + throw PSTraceSource.NewInvalidOperationException( + AutomationExceptions.DynamicParametersWrongType, PSObject.ToStringParser(this.Context, resultList)); } + return resultList.Count == 0 ? null : PSObject.Base(resultList[0]); } @@ -2088,58 +2442,68 @@ internal void SetLocalsTupleForNewScope(SessionStateScope scope) /// /// If the script cmdlet is dotted, this method is used to push the locals to the 'DottedScopes' of the current scope. /// - internal void PushDottedScope(SessionStateScope scope) - { - scope.DottedScopes.Push(_localsTuple); - } + internal void PushDottedScope(SessionStateScope scope) => scope.DottedScopes.Push(_localsTuple); /// /// If the script cmdlet is dotted, this method is used to pop the locals from the 'DottedScopes' of the current scope. /// - internal void PopDottedScope(SessionStateScope scope) - { - scope.DottedScopes.Pop(); - } + internal void PopDottedScope(SessionStateScope scope) => scope.DottedScopes.Pop(); internal void PrepareForBinding(CommandLineParameters commandLineParameters) { - _localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters, - commandLineParameters.GetValueToBindToPSBoundParameters(), this.Context); - _localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, MyInvocation, this.Context); + _localsTuple.SetAutomaticVariable( + AutomaticVariable.PSBoundParameters, + value: commandLineParameters.GetValueToBindToPSBoundParameters(), + this.Context); + _localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, value: MyInvocation, this.Context); } private void SetPreferenceVariables() { if (_commandRuntime.IsDebugFlagSet) { - _localsTuple.SetPreferenceVariable(PreferenceVariable.Debug, - _commandRuntime.Debug ? ActionPreference.Inquire : ActionPreference.SilentlyContinue); + _localsTuple.SetPreferenceVariable( + PreferenceVariable.Debug, + _commandRuntime.Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue); } + if (_commandRuntime.IsVerboseFlagSet) { - _localsTuple.SetPreferenceVariable(PreferenceVariable.Verbose, - _commandRuntime.Verbose ? ActionPreference.Continue : ActionPreference.SilentlyContinue); + _localsTuple.SetPreferenceVariable( + PreferenceVariable.Verbose, + _commandRuntime.Verbose ? ActionPreference.Continue : ActionPreference.SilentlyContinue); } + if (_commandRuntime.IsErrorActionSet) { _localsTuple.SetPreferenceVariable(PreferenceVariable.Error, _commandRuntime.ErrorAction); } + if (_commandRuntime.IsWarningActionSet) { _localsTuple.SetPreferenceVariable(PreferenceVariable.Warning, _commandRuntime.WarningPreference); } + if (_commandRuntime.IsInformationActionSet) { _localsTuple.SetPreferenceVariable(PreferenceVariable.Information, _commandRuntime.InformationPreference); } + + if (_commandRuntime.IsProgressActionSet) + { + _localsTuple.SetPreferenceVariable(PreferenceVariable.Progress, _commandRuntime.ProgressPreference); + } + if (_commandRuntime.IsWhatIfFlagSet) { _localsTuple.SetPreferenceVariable(PreferenceVariable.WhatIf, _commandRuntime.WhatIf); } + if (_commandRuntime.IsConfirmFlagSet) { - _localsTuple.SetPreferenceVariable(PreferenceVariable.Confirm, - _commandRuntime.Confirm ? ConfirmImpact.Low : ConfirmImpact.None); + _localsTuple.SetPreferenceVariable( + PreferenceVariable.Confirm, + _commandRuntime.Confirm ? ConfirmImpact.Low : ConfirmImpact.None); } } @@ -2164,20 +2528,19 @@ protected override void StopProcessing() /// /// IDisposable implementation /// When the command is complete, release the associated scope - /// and other members + /// and other members. /// public void Dispose() { if (_disposed) + { return; + } this.DisposingEvent.SafeInvoke(this, EventArgs.Empty); 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 26a45dacffa..53c40ef3810 100644 --- a/src/System.Management.Automation/engine/runtime/MutableTuple.cs +++ b/src/System.Management.Automation/engine/runtime/MutableTuple.cs @@ -30,6 +30,7 @@ namespace System.Management.Automation internal abstract class MutableTuple { private const int MaxSize = 128; + private static readonly Dictionary s_sizeDict = new Dictionary(); private int _size; @@ -47,11 +48,13 @@ internal bool IsValueSet(int index) // slow path MutableTuple nestedTuple = this; var accessPath = GetAccessPath(_size, index).ToArray(); - for (int i = 0; i < accessPath.Length - 1; ++i) + int length = accessPath.Length; + for (int i = 0; i < length - 1; ++i) { nestedTuple = (MutableTuple)nestedTuple.GetValueImpl(accessPath[i]); } - return nestedTuple._valuesSet[accessPath.Last()]; + + return nestedTuple._valuesSet[accessPath[length - 1]]; } internal void SetAutomaticVariable(AutomaticVariable auto, object value, ExecutionContext context) @@ -60,12 +63,15 @@ internal void SetAutomaticVariable(AutomaticVariable auto, object value, Executi { context.Debugger.CheckVariableWrite(SpecialVariables.AutomaticVariables[(int)auto]); } + SetValue((int)auto, value); } + internal object GetAutomaticVariable(AutomaticVariable auto) { return GetValue((int)auto); } + internal void SetPreferenceVariable(PreferenceVariable pref, object value) { SetValue((int)pref, value); @@ -80,6 +86,7 @@ internal bool TryGetLocalVariable(string name, bool fromNewOrSet, out PSVariable result = new LocalVariable(name, this, index); return true; } + result = null; return false; } @@ -93,6 +100,7 @@ internal bool TrySetParameter(string name, object value) SetValue(index, value); return true; } + return false; } @@ -105,6 +113,7 @@ internal PSVariable TrySetVariable(string name, object value) SetValue(index, value); return new LocalVariable(name, this, index); } + return null; } @@ -140,6 +149,7 @@ public void SetValue(int index, object value) } protected abstract object GetValueImpl(int index); + protected abstract void SetValueImpl(int index, object value); /// @@ -164,8 +174,10 @@ private void SetNestedValue(int size, int index, object value) { res = (MutableTuple)res.GetValueImpl(lastAccess); } + lastAccess = i; } + res.SetValueImpl(lastAccess, value); } } @@ -189,6 +201,7 @@ private object GetNestedValue(int size, int index) { res = ((MutableTuple)res).GetValueImpl(i); } + return res; } } @@ -255,7 +268,7 @@ private static Type GetTupleType(int size) /// public static Type MakeTupleType(params Type[] types) { - //ContractUtils.RequiresNotNull(types, "types"); + // ContractUtils.RequiresNotNull(types, "types"); return MakeTupleType(types, 0, types.Length); } @@ -265,10 +278,14 @@ public static Type MakeTupleType(params Type[] types) /// public static int GetSize(Type tupleType) { - //ContractUtils.RequiresNotNull(tupleType, "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) @@ -281,10 +298,14 @@ public static int GetSize(Type tupleType) { types.Push(subtype); } + continue; } - if (t == typeof(DynamicNull)) continue; + if (t == typeof(DynamicNull)) + { + continue; + } count++; } @@ -295,6 +316,7 @@ public static int GetSize(Type tupleType) private static readonly ConcurrentDictionary> s_tupleCreators = new ConcurrentDictionary>(concurrencyLevel: 3, capacity: 100); + public static Func TupleCreator(Type type) { return s_tupleCreators.GetOrAdd(type, @@ -311,8 +333,8 @@ public static Func TupleCreator(Type type) /// public static MutableTuple MakeTuple(Type tupleType, Dictionary nameToIndexMap, Func creator = null) { - //ContractUtils.RequiresNotNull(tupleType, "tupleType"); - //ContractUtils.RequiresNotNull(args, "args"); + // ContractUtils.RequiresNotNull(tupleType, "tupleType"); + // ContractUtils.RequiresNotNull(args, "args"); int size = GetSize(tupleType); var bitArray = new BitArray(size); @@ -326,7 +348,7 @@ public static MutableTuple MakeTuple(Type tupleType, Dictionary nam /// public static object[] GetTupleValues(MutableTuple tuple) { - //ContractUtils.RequiresNotNull(tuple, "tuple"); + // ContractUtils.RequiresNotNull(tuple, "tuple"); List res = new List(); @@ -348,13 +370,13 @@ public static IEnumerable GetAccessPath(Type tupleType, int index) /// internal static IEnumerable GetAccessProperties(Type tupleType, int size, int index) { - //ContractUtils.RequiresNotNull(tupleType, "tupleType"); + // ContractUtils.RequiresNotNull(tupleType, "tupleType"); if (index < 0 || index >= size) throw new ArgumentException("index"); 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; @@ -422,9 +444,10 @@ private static MutableTuple MakeTuple(Func creator, Type tupleType { size = (size + MutableTuple.MaxSize - 1) / MutableTuple.MaxSize; } + 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)); } } @@ -449,6 +472,7 @@ private static Type MakeTupleType(Type[] types, int start, int end) { typeArr[index++] = typeof(DynamicNull); } + return type.MakeGenericType(typeArr); } @@ -468,6 +492,7 @@ private static Type MakeTupleType(Type[] types, int start, int end) int newEnd = System.Math.Min(end, start + ((i + 1) * multiplier)); nestedTypes[i] = MakeTupleType(types, newStart, newEnd); } + for (int i = size; i < nestedTypes.Length; i++) { nestedTypes[i] = typeof(DynamicNull); @@ -486,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) @@ -494,7 +519,7 @@ private static int PowerOfTwoRound(int value) int res = 1; while (value > res) { - res = res << 1; + res <<= 1; } return res; @@ -515,13 +540,14 @@ internal static Expression CreateNew(Type tupleType, int start, int end, Express size = (size + MutableTuple.MaxSize - 1) / MutableTuple.MaxSize; multiplier *= MutableTuple.MaxSize; } + newValues = new Expression[PowerOfTwoRound(size)]; for (int i = 0; i < size; i++) { 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); } @@ -545,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); } } @@ -570,6 +596,7 @@ public MutableTuple(T0 item0) public T0 Item000 { get { return _item0; } + set { _item0 = value; _valuesSet[0] = true; } } @@ -590,6 +617,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -598,6 +626,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -614,6 +643,7 @@ public MutableTuple(T0 item0, T1 item1) public T1 Item001 { get { return _item1; } + set { _item1 = value; _valuesSet[1] = true; } } @@ -636,6 +666,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -644,6 +675,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -662,11 +694,14 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3) public T2 Item002 { get { return _item2; } + set { _item2 = value; _valuesSet[2] = true; } } + public T3 Item003 { get { return _item3; } + set { _item3 = value; _valuesSet[3] = true; } } @@ -693,6 +728,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -701,6 +737,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -723,21 +760,28 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, public T4 Item004 { get { return _item4; } + set { _item4 = value; _valuesSet[4] = true; } } + public T5 Item005 { get { return _item5; } + set { _item5 = value; _valuesSet[5] = true; } } + public T6 Item006 { get { return _item6; } + set { _item6 = value; _valuesSet[6] = true; } } + public T7 Item007 { get { return _item7; } + set { _item7 = value; _valuesSet[7] = true; } } @@ -772,6 +816,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -780,6 +825,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -810,41 +856,56 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, public T8 Item008 { get { return _item8; } + set { _item8 = value; _valuesSet[8] = true; } } + public T9 Item009 { get { return _item9; } + set { _item9 = value; _valuesSet[9] = true; } } + public T10 Item010 { get { return _item10; } + set { _item10 = value; _valuesSet[10] = true; } } + public T11 Item011 { get { return _item11; } + set { _item11 = value; _valuesSet[11] = true; } } + public T12 Item012 { get { return _item12; } + set { _item12 = value; _valuesSet[12] = true; } } + public T13 Item013 { get { return _item13; } + set { _item13 = value; _valuesSet[13] = true; } } + public T14 Item014 { get { return _item14; } + set { _item14 = value; _valuesSet[14] = true; } } + public T15 Item015 { get { return _item15; } + set { _item15 = value; _valuesSet[15] = true; } } @@ -895,6 +956,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -903,6 +965,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -949,81 +1012,112 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, public T16 Item016 { get { return _item16; } + set { _item16 = value; _valuesSet[16] = true; } } + public T17 Item017 { get { return _item17; } + set { _item17 = value; _valuesSet[17] = true; } } + public T18 Item018 { get { return _item18; } + set { _item18 = value; _valuesSet[18] = true; } } + public T19 Item019 { get { return _item19; } + set { _item19 = value; _valuesSet[19] = true; } } + public T20 Item020 { get { return _item20; } + set { _item20 = value; _valuesSet[20] = true; } } + public T21 Item021 { get { return _item21; } + set { _item21 = value; _valuesSet[21] = true; } } + public T22 Item022 { get { return _item22; } + set { _item22 = value; _valuesSet[22] = true; } } + public T23 Item023 { get { return _item23; } + set { _item23 = value; _valuesSet[23] = true; } } + public T24 Item024 { get { return _item24; } + set { _item24 = value; _valuesSet[24] = true; } } + public T25 Item025 { get { return _item25; } + set { _item25 = value; _valuesSet[25] = true; } } + public T26 Item026 { get { return _item26; } + set { _item26 = value; _valuesSet[26] = true; } } + public T27 Item027 { get { return _item27; } + set { _item27 = value; _valuesSet[27] = true; } } + public T28 Item028 { get { return _item28; } + set { _item28 = value; _valuesSet[28] = true; } } + public T29 Item029 { get { return _item29; } + set { _item29 = value; _valuesSet[29] = true; } } + public T30 Item030 { get { return _item30; } + set { _item30 = value; _valuesSet[30] = true; } } + public T31 Item031 { get { return _item31; } + set { _item31 = value; _valuesSet[31] = true; } } @@ -1106,6 +1200,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -1114,6 +1209,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -1192,161 +1288,224 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, public T32 Item032 { get { return _item32; } + set { _item32 = value; _valuesSet[32] = true; } } + public T33 Item033 { get { return _item33; } + set { _item33 = value; _valuesSet[33] = true; } } + public T34 Item034 { get { return _item34; } + set { _item34 = value; _valuesSet[34] = true; } } + public T35 Item035 { get { return _item35; } + set { _item35 = value; _valuesSet[35] = true; } } + public T36 Item036 { get { return _item36; } + set { _item36 = value; _valuesSet[36] = true; } } + public T37 Item037 { get { return _item37; } + set { _item37 = value; _valuesSet[37] = true; } } + public T38 Item038 { get { return _item38; } + set { _item38 = value; _valuesSet[38] = true; } } + public T39 Item039 { get { return _item39; } + set { _item39 = value; _valuesSet[39] = true; } } + public T40 Item040 { get { return _item40; } + set { _item40 = value; _valuesSet[40] = true; } } + public T41 Item041 { get { return _item41; } + set { _item41 = value; _valuesSet[41] = true; } } + public T42 Item042 { get { return _item42; } + set { _item42 = value; _valuesSet[42] = true; } } + public T43 Item043 { get { return _item43; } + set { _item43 = value; _valuesSet[43] = true; } } + public T44 Item044 { get { return _item44; } + set { _item44 = value; _valuesSet[44] = true; } } + public T45 Item045 { get { return _item45; } + set { _item45 = value; _valuesSet[45] = true; } } + public T46 Item046 { get { return _item46; } + set { _item46 = value; _valuesSet[46] = true; } } + public T47 Item047 { get { return _item47; } + set { _item47 = value; _valuesSet[47] = true; } } + public T48 Item048 { get { return _item48; } + set { _item48 = value; _valuesSet[48] = true; } } + public T49 Item049 { get { return _item49; } + set { _item49 = value; _valuesSet[49] = true; } } + public T50 Item050 { get { return _item50; } + set { _item50 = value; _valuesSet[50] = true; } } + public T51 Item051 { get { return _item51; } + set { _item51 = value; _valuesSet[51] = true; } } + public T52 Item052 { get { return _item52; } + set { _item52 = value; _valuesSet[52] = true; } } + public T53 Item053 { get { return _item53; } + set { _item53 = value; _valuesSet[53] = true; } } + public T54 Item054 { get { return _item54; } + set { _item54 = value; _valuesSet[54] = true; } } + public T55 Item055 { get { return _item55; } + set { _item55 = value; _valuesSet[55] = true; } } + public T56 Item056 { get { return _item56; } + set { _item56 = value; _valuesSet[56] = true; } } + public T57 Item057 { get { return _item57; } + set { _item57 = value; _valuesSet[57] = true; } } + public T58 Item058 { get { return _item58; } + set { _item58 = value; _valuesSet[58] = true; } } + public T59 Item059 { get { return _item59; } + set { _item59 = value; _valuesSet[59] = true; } } + public T60 Item060 { get { return _item60; } + set { _item60 = value; _valuesSet[60] = true; } } + public T61 Item061 { get { return _item61; } + set { _item61 = value; _valuesSet[61] = true; } } + public T62 Item062 { get { return _item62; } + set { _item62 = value; _valuesSet[62] = true; } } + public T63 Item063 { get { return _item63; } + set { _item63 = value; _valuesSet[63] = true; } } @@ -1493,6 +1652,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get @@ -1501,6 +1661,7 @@ public override int Capacity } } } + [GeneratedCode("DLR", "2.0")] internal class MutableTuple : MutableTuple { @@ -1643,321 +1804,448 @@ public MutableTuple(T0 item0, T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, public T64 Item064 { get { return _item64; } + set { _item64 = value; _valuesSet[64] = true; } } + public T65 Item065 { get { return _item65; } + set { _item65 = value; _valuesSet[65] = true; } } + public T66 Item066 { get { return _item66; } + set { _item66 = value; _valuesSet[66] = true; } } + public T67 Item067 { get { return _item67; } + set { _item67 = value; _valuesSet[67] = true; } } + public T68 Item068 { get { return _item68; } + set { _item68 = value; _valuesSet[68] = true; } } + public T69 Item069 { get { return _item69; } + set { _item69 = value; _valuesSet[69] = true; } } + public T70 Item070 { get { return _item70; } + set { _item70 = value; _valuesSet[70] = true; } } + public T71 Item071 { get { return _item71; } + set { _item71 = value; _valuesSet[71] = true; } } + public T72 Item072 { get { return _item72; } + set { _item72 = value; _valuesSet[72] = true; } } + public T73 Item073 { get { return _item73; } + set { _item73 = value; _valuesSet[73] = true; } } + public T74 Item074 { get { return _item74; } + set { _item74 = value; _valuesSet[74] = true; } } + public T75 Item075 { get { return _item75; } + set { _item75 = value; _valuesSet[75] = true; } } + public T76 Item076 { get { return _item76; } + set { _item76 = value; _valuesSet[76] = true; } } + public T77 Item077 { get { return _item77; } + set { _item77 = value; _valuesSet[77] = true; } } + public T78 Item078 { get { return _item78; } + set { _item78 = value; _valuesSet[78] = true; } } + public T79 Item079 { get { return _item79; } + set { _item79 = value; _valuesSet[79] = true; } } + public T80 Item080 { get { return _item80; } + set { _item80 = value; _valuesSet[80] = true; } } + public T81 Item081 { get { return _item81; } + set { _item81 = value; _valuesSet[81] = true; } } + public T82 Item082 { get { return _item82; } + set { _item82 = value; _valuesSet[82] = true; } } + public T83 Item083 { get { return _item83; } + set { _item83 = value; _valuesSet[83] = true; } } + public T84 Item084 { get { return _item84; } + set { _item84 = value; _valuesSet[84] = true; } } + public T85 Item085 { get { return _item85; } + set { _item85 = value; _valuesSet[85] = true; } } + public T86 Item086 { get { return _item86; } + set { _item86 = value; _valuesSet[86] = true; } } + public T87 Item087 { get { return _item87; } + set { _item87 = value; _valuesSet[87] = true; } } + public T88 Item088 { get { return _item88; } + set { _item88 = value; _valuesSet[88] = true; } } + public T89 Item089 { get { return _item89; } + set { _item89 = value; _valuesSet[89] = true; } } + public T90 Item090 { get { return _item90; } + set { _item90 = value; _valuesSet[90] = true; } } + public T91 Item091 { get { return _item91; } + set { _item91 = value; _valuesSet[91] = true; } } + public T92 Item092 { get { return _item92; } + set { _item92 = value; _valuesSet[92] = true; } } + public T93 Item093 { get { return _item93; } + set { _item93 = value; _valuesSet[93] = true; } } + public T94 Item094 { get { return _item94; } + set { _item94 = value; _valuesSet[94] = true; } } + public T95 Item095 { get { return _item95; } + set { _item95 = value; _valuesSet[95] = true; } } + public T96 Item096 { get { return _item96; } + set { _item96 = value; _valuesSet[96] = true; } } + public T97 Item097 { get { return _item97; } + set { _item97 = value; _valuesSet[97] = true; } } + public T98 Item098 { get { return _item98; } + set { _item98 = value; _valuesSet[98] = true; } } + public T99 Item099 { get { return _item99; } + set { _item99 = value; _valuesSet[99] = true; } } + public T100 Item100 { get { return _item100; } + set { _item100 = value; _valuesSet[100] = true; } } + public T101 Item101 { get { return _item101; } + set { _item101 = value; _valuesSet[101] = true; } } + public T102 Item102 { get { return _item102; } + set { _item102 = value; _valuesSet[102] = true; } } + public T103 Item103 { get { return _item103; } + set { _item103 = value; _valuesSet[103] = true; } } + public T104 Item104 { get { return _item104; } + set { _item104 = value; _valuesSet[104] = true; } } + public T105 Item105 { get { return _item105; } + set { _item105 = value; _valuesSet[105] = true; } } + public T106 Item106 { get { return _item106; } + set { _item106 = value; _valuesSet[106] = true; } } + public T107 Item107 { get { return _item107; } + set { _item107 = value; _valuesSet[107] = true; } } + public T108 Item108 { get { return _item108; } + set { _item108 = value; _valuesSet[108] = true; } } + public T109 Item109 { get { return _item109; } + set { _item109 = value; _valuesSet[109] = true; } } + public T110 Item110 { get { return _item110; } + set { _item110 = value; _valuesSet[110] = true; } } + public T111 Item111 { get { return _item111; } + set { _item111 = value; _valuesSet[111] = true; } } + public T112 Item112 { get { return _item112; } + set { _item112 = value; _valuesSet[112] = true; } } + public T113 Item113 { get { return _item113; } + set { _item113 = value; _valuesSet[113] = true; } } + public T114 Item114 { get { return _item114; } + set { _item114 = value; _valuesSet[114] = true; } } + public T115 Item115 { get { return _item115; } + set { _item115 = value; _valuesSet[115] = true; } } + public T116 Item116 { get { return _item116; } + set { _item116 = value; _valuesSet[116] = true; } } + public T117 Item117 { get { return _item117; } + set { _item117 = value; _valuesSet[117] = true; } } + public T118 Item118 { get { return _item118; } + set { _item118 = value; _valuesSet[118] = true; } } + public T119 Item119 { get { return _item119; } + set { _item119 = value; _valuesSet[119] = true; } } + public T120 Item120 { get { return _item120; } + set { _item120 = value; _valuesSet[120] = true; } } + public T121 Item121 { get { return _item121; } + set { _item121 = value; _valuesSet[121] = true; } } + public T122 Item122 { get { return _item122; } + set { _item122 = value; _valuesSet[122] = true; } } + public T123 Item123 { get { return _item123; } + set { _item123 = value; _valuesSet[123] = true; } } + public T124 Item124 { get { return _item124; } + set { _item124 = value; _valuesSet[124] = true; } } + public T125 Item125 { get { return _item125; } + set { _item125 = value; _valuesSet[125] = true; } } + public T126 Item126 { get { return _item126; } + set { _item126 = value; _valuesSet[126] = true; } } + public T127 Item127 { get { return _item127; } + set { _item127 = value; _valuesSet[127] = true; } } @@ -2232,6 +2520,7 @@ protected override void SetValueImpl(int index, object value) default: throw new ArgumentOutOfRangeException("index"); } } + public override int Capacity { get diff --git a/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs index 5c44e281e28..4fd68cdd201 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/ArrayOps.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // ReSharper disable UnusedMember.Global @@ -13,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]; @@ -32,15 +40,16 @@ internal static object[] SlicingIndex(object target, object[] indexes, Func - /// Efficiently multiplies collection by integer + /// Efficiently multiplies collection by integer. /// - /// collection to multiply - /// number of times the collection is to be multiplied/copied - /// collection multiplied by integer + /// Collection to multiply. + /// Number of times the collection is to be multiplied/copied. + /// Collection multiplied by integer. internal static T[] Multiply(T[] array, uint times) { Diagnostics.Assert(array != null, "Caller should verify the arguments for array multiplication"); @@ -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(); @@ -112,6 +124,7 @@ internal static object GetMDArrayValue(Array array, int[] indexes, bool slicing) { indexes[i] = indexes[i] + ub + 1; } + if (indexes[i] < lb || indexes[i] > ub) { // In strict mode, don't return, fall through and let Array.GetValue raise an exception. @@ -206,6 +219,7 @@ internal static object GetMDArrayValueOrSlice(Array array, object indexes) Array.Copy(result, shortResult, j); return shortResult; } + return result; } @@ -229,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; } @@ -285,4 +299,4 @@ internal static object GetNonIndexable(object target, object[] indices) ParserStrings.CannotIndex, target.GetType()); } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs index 28cab722d0c..2b741a29c31 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs @@ -1,15 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Reflection; using System.Reflection.Emit; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Management.Automation.Language; using System.Threading; // These APIs are not part of the public contract. @@ -23,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. @@ -43,24 +42,23 @@ internal SessionStateKeeper() internal void RegisterRunspace() { - SessionStateInternal ssInMap = null; - Runspace rsToUse = Runspace.DefaultRunspace; - SessionStateInternal ssToUse = rsToUse.ExecutionContext.EngineSessionState; + SessionStateInternal sessionStateInMap = null; + Runspace runspaceToUse = Runspace.DefaultRunspace; + SessionStateInternal sessionStateToUse = runspaceToUse.ExecutionContext.EngineSessionState; // Different threads will operate on different key/value pairs (default-runspace/session-state pairs), // and a ConditionalWeakTable itself is thread safe, so there won't be race condition here. - if (!_stateMap.TryGetValue(rsToUse, out ssInMap)) + if (!_stateMap.TryGetValue(runspaceToUse, out sessionStateInMap)) { // If the key doesn't exist yet, add it - _stateMap.Add(rsToUse, ssToUse); + _stateMap.Add(runspaceToUse, sessionStateToUse); } - else if (!ssInMap.Equals(ssToUse)) + else if (sessionStateInMap != sessionStateToUse) { // If the key exists but the corresponding value is not what we should use, then remove the key/value pair and add the new pair. // This could happen when a powershell class is defined in a module and the module gets reloaded. In such case, the same TypeDefinitionAst // instance will get reused, but should be associated with the SessionState from the new module, instead of the one from the old module. - _stateMap.Remove(rsToUse); - _stateMap.Add(rsToUse, ssToUse); + _stateMap.AddOrUpdate(runspaceToUse, sessionStateToUse); } // If the key exists and the corresponding value is the one we should use, then do nothing. } @@ -78,7 +76,7 @@ internal void RegisterRunspace() /// However, if the instantiation happens in a different Runspace where the class is not defined, or it happens on /// a thread without a default Runspace, then the created instance won't be bound to any session state. /// - /// SessionStateInternal + /// SessionStateInternal. public object GetSessionState() { SessionStateInternal ss = null; @@ -91,6 +89,7 @@ public object GetSessionState() { _stateMap.TryGetValue(defaultRunspace, out ss); } + return ss; } } @@ -99,7 +98,7 @@ public object GetSessionState() public class ScriptBlockMemberMethodWrapper { /// Used in codegen - public static readonly object[] _emptyArgumentArray = Utils.EmptyArray(); // See TypeDefiner.DefineTypeHelper.DefineMethodBody + public static readonly object[] _emptyArgumentArray = Array.Empty(); // See TypeDefiner.DefineTypeHelper.DefineMethodBody /// /// Indicate the wrapper is for a static member method. @@ -116,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; /// @@ -163,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. @@ -175,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) @@ -218,13 +217,14 @@ private void PrepareScriptBlockToInvoke(object instance, object sessionStateInte } } } + _boundScriptBlock.Value.SessionStateInternal = sessionStateToUse; } /// /// - /// target object or null for static call - /// sessionStateInternal from private field of instance or null for static call + /// Target object or null for static call. + /// SessionStateInternal from private field of instance or null for static call. /// public void InvokeHelper(object instance, object sessionStateInternal, object[] args) { @@ -246,8 +246,8 @@ public void InvokeHelper(object instance, object sessionStateInternal, object[] /// /// /// - /// target object or null for static call - /// sessionStateInternal from private field of instance or null for static call + /// Target object or null for static call. + /// SessionStateInternal from private field of instance or null for static call. /// /// public T InvokeHelperT(object instance, object sessionStateInternal, object[] args) @@ -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); @@ -294,9 +294,9 @@ public static void ValidateSetProperty(Type type, string propertyName, object va /// /// Performs base ctor call as a method call. /// - /// object for invocation - /// ctor info for invocation - /// arguments for invocation + /// Object for invocation. + /// Ctor info for invocation. + /// Arguments for invocation. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public static void CallBaseCtor(object target, ConstructorInfo ci, object[] args) { @@ -306,9 +306,9 @@ public static void CallBaseCtor(object target, ConstructorInfo ci, object[] args /// /// Performs non-virtual method call with return value. Main usage: base class method call inside subclass method. /// - /// object for invocation - /// method info for invocation - /// arguments for invocation + /// Object for invocation. + /// Method info for invocation. + /// Arguments for invocation. public static object CallMethodNonVirtually(object target, MethodInfo mi, object[] args) { return CallMethodNonVirtuallyImpl(target, mi, args); @@ -317,9 +317,9 @@ public static object CallMethodNonVirtually(object target, MethodInfo mi, object /// /// Performs non-virtual void method call. Main usage: base class method call inside subclass method. /// - /// object for invocation - /// method info for invocation - /// arguments for invocation + /// Object for invocation. + /// Method info for invocation. + /// Arguments for invocation. public static void CallVoidMethodNonVirtually(object target, MethodInfo mi, object[] args) { CallMethodNonVirtuallyImpl(target, mi, args); @@ -335,9 +335,9 @@ public static void CallVoidMethodNonVirtually(object target, MethodInfo mi, obje /// /// Implementation of non-virtual method call. /// - /// object for invocation - /// method info for invocation - /// arguments for invocation + /// Object for invocation. + /// Method info for invocation. + /// Arguments for invocation. private static object CallMethodNonVirtuallyImpl(object target, MethodInfo mi, object[] args) { DynamicMethod dm = s_nonVirtualCallCache.GetValue(mi, CreateDynamicMethod); @@ -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(); @@ -364,6 +364,7 @@ private static DynamicMethod CreateDynamicMethod(MethodInfo mi) { il.Emit(OpCodes.Ldarg, i); } + il.Emit(OpCodes.Tailcall); il.EmitCall(OpCodes.Call, mi, null); il.Emit(OpCodes.Ret); diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index df5fc5f837d..d584666ab62 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -14,10 +13,14 @@ 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; + using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.Commands.Internal.Format; // ReSharper disable UnusedMember.Global @@ -52,6 +55,26 @@ 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) + { + 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; commandIndex += 1; } @@ -94,14 +117,17 @@ 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)) { - throw InterpreterError.NewInterpreterException(command, typeof(RuntimeException), - commandExtent, "BadExpression", - ParserStrings.BadExpression, - dotSource ? "." : "&"); + throw InterpreterError.NewInterpreterException( + command, + typeof(RuntimeException), + commandExtent, + "BadExpression", + ParserStrings.BadExpression, + dotSource ? "." : "&"); } try @@ -137,6 +163,7 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, { InvocationName = invocationName }; rte.ErrorRecord.SetInvocationInfo(invocationInfo); } + throw; } } @@ -147,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]; @@ -160,7 +188,7 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, } } - if (cpi.ArgumentSplatted) + if (cpi.ArgumentToBeSplatted) { foreach (var splattedCpi in Splat(cpi.ArgumentValue, cpi.ArgumentAst)) { @@ -193,9 +221,24 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, bool redirectedInformation = false; if (redirections != null) { - foreach (var redirection in redirections) + if (isNativeCommand) + { + foreach (CommandRedirection redirection in redirections) + { + if (redirection is MergingRedirection) + { + redirection.Bind(pipe, commandProcessor, context); + } + } + } + + foreach (CommandRedirection redirection in redirections) { - redirection.Bind(pipe, commandProcessor, context); + if (!isNativeCommand || redirection is not MergingRedirection) + { + redirection.Bind(pipe, commandProcessor, context); + } + switch (redirection.FromStream) { case RedirectionStream.Error: @@ -241,21 +284,25 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, commandProcessor.CommandRuntime.ErrorOutputPipe.ExternalWriter = context.ExternalErrorOutput; } } + if (!redirectedWarning && (context.ExpressionWarningOutputPipe != null)) { commandProcessor.CommandRuntime.WarningOutputPipe = context.ExpressionWarningOutputPipe; redirectedWarning = true; } + if (!redirectedVerbose && (context.ExpressionVerboseOutputPipe != null)) { commandProcessor.CommandRuntime.VerboseOutputPipe = context.ExpressionVerboseOutputPipe; redirectedVerbose = true; } + if (!redirectedDebug && (context.ExpressionDebugOutputPipe != null)) { commandProcessor.CommandRuntime.DebugOutputPipe = context.ExpressionDebugOutputPipe; redirectedDebug = true; } + if (!redirectedInformation && (context.ExpressionInformationOutputPipe != null)) { commandProcessor.CommandRuntime.InformationOutputPipe = context.ExpressionInformationOutputPipe; @@ -296,6 +343,15 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, internal static IEnumerable Splat(object splattedValue, Ast splatAst) { splattedValue = PSObject.Base(splattedValue); + + var markUntrustedData = false; + if (ExecutionContext.HasEverUsedConstrainedLanguage) + { + // If the value to be splatted is untrusted, then make sure sub-values held by it are + // also marked as untrusted. + markUntrustedData = ExecutionContext.IsMarkedAsUntrusted(splattedValue); + } + IDictionary splattedTable = splattedValue as IDictionary; if (splattedTable != null) { @@ -305,9 +361,19 @@ internal static IEnumerable Splat(object splattedValue object parameterValue = de.Value; string parameterText = GetParameterText(parameterName); + if (markUntrustedData) + { + ExecutionContext.MarkObjectAsUntrusted(parameterValue); + } + 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 @@ -317,6 +383,11 @@ internal static IEnumerable Splat(object splattedValue { foreach (object obj in enumerableValue) { + if (markUntrustedData) + { + ExecutionContext.MarkObjectAsUntrusted(obj); + } + yield return SplatEnumerableElement(obj, splatAst); } } @@ -365,8 +436,9 @@ 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; } @@ -383,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) { @@ -406,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); } @@ -428,7 +497,7 @@ internal static void InvokePipeline(object input, { pipelineProcessor.Commands.RemoveAt(commandsCount - 1); commandProcessor = nextToLastCommand; - nextToLastCommand.CommandRuntime.OutputPipe = new Pipe {NullPipe = true}; + nextToLastCommand.CommandRuntime.OutputPipe = new Pipe { NullPipe = true }; } } @@ -467,7 +536,7 @@ internal static void InvokePipeline(object input, } internal static void InvokePipelineInBackground( - PipelineAst pipelineAst, + PipelineBaseAst pipelineAst, FunctionContext funcContext) { PipelineProcessor pipelineProcessor = new PipelineProcessor(); @@ -476,53 +545,67 @@ 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 ; "; - // 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); + var variables = pipelineAst.FindAll(static x => x is VariableExpressionAst, true); + + // 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: foreach (var v in variables) { - var vName = ((VariableExpressionAst) v).VariablePath.UserPath; + var variableName = ((VariableExpressionAst)v).VariablePath.UserPath; + // Skip variables that don't exist - if (funcContext._executionContext.EngineSessionState.GetVariable(vName) == null) + if (funcContext._executionContext.EngineSessionState.GetVariable(variableName) == null) + { continue; + } + // Skip PowerShell magic variables - if (Regex.Match(vName, + 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.Substring(position, v.Extent.StartOffset - pipelineOffset - position)); + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); updatedScriptblock.Append("${using:"); - updatedScriptblock.Append(CodeGeneration.EscapeVariableName(vName)); + updatedScriptblock.Append(CodeGeneration.EscapeVariableName(variableName)); updatedScriptblock.Append('}'); position = v.Extent.EndOffset - pipelineOffset; } } - updatedScriptblock.Append(scriptblockBodyString.Substring(position)); + + updatedScriptblock.Append(scriptblockBodyString.AsSpan(position)); 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( - /*parameterAst*/pipelineAst, "ScriptBlock", null, - /*argumentAst*/pipelineAst, sb, - false); - commandProcessor.AddParameter(parameter); + commandProcessor = context.CommandDiscovery.LookupCommandProcessor(commandInfo, CommandOrigin.Internal, false, context.EngineSessionState); + + var workingDirectoryParameter = CommandParameterInternal.CreateParameterWithArgument( + parameterAst: pipelineAst, + parameterName: "WorkingDirectory", + parameterText: null, + argumentAst: pipelineAst, + 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())); @@ -559,6 +642,7 @@ internal static object CheckAutomationNullInCommandArgument(object obj) { return null; } + var objAsArray = obj as object[]; return objAsArray != null ? CheckAutomationNullInCommandArgumentArray(objAsArray) : obj; } @@ -629,7 +713,19 @@ internal static SteppablePipeline GetSteppablePipeline(PipelineAst pipelineAst, // GetSteppablePipeline() is called on an arbitrary script block with the intention // of invoking it. So the trustworthiness is defined by the trustworthiness of the // script block's language mode. - bool isTrusted = (scriptBlock.LanguageMode == PSLanguageMode.FullLanguage); + 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()) { @@ -645,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)); } @@ -713,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); @@ -740,6 +836,7 @@ internal static object PipelineResult(List resultList) { return AutomationNull.Value; } + var result = resultCount == 1 ? resultList[0] : resultList.ToArray(); // Clear the array list so that we don't write the results of the pipe when flushing the pipe. resultList.Clear(); @@ -779,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) { @@ -794,7 +888,7 @@ internal static void CheckForInterrupts(ExecutionContext context) internal static void Nop() { } } -#region Redirections + #region Redirections internal abstract class CommandRedirection { @@ -803,7 +897,8 @@ 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); internal void UnbindForExpression(FunctionContext funcContext, Pipe[] pipes) @@ -860,17 +955,17 @@ internal MergingRedirection(RedirectionStream from, RedirectionStream to) ParserStrings.RedirectionStreamCanOnlyMergeToOutputStream); } - //this.ToStream = to; + // this.ToStream = to; } 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; } + // private RedirectionStream ToStream { get; set; } // Handle merging redirections for commands, like: // dir 2>&1 @@ -976,21 +1071,44 @@ 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 bool Appending { get; private set; } + internal string File { get; } + + internal bool Appending { get; } + private PipelineProcessor PipelineProcessor { get; set; } // Handle binding file redirection for commands, like: // 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) @@ -1000,11 +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; @@ -1015,11 +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; @@ -1097,36 +1207,60 @@ internal Pipe[] BindForExpression(FunctionContext funcContext) context.ExpressionInformationOutputPipe = pipe; break; } + return oldPipes; } internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor parentPipelineProcessor) { - if (String.IsNullOrWhiteSpace(File)) + if (string.IsNullOrWhiteSpace(File)) { 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(); @@ -1142,19 +1276,23 @@ 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); } @@ -1162,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; @@ -1190,17 +1325,14 @@ private void Dispose(bool disposing) if (disposing) { - if (PipelineProcessor != null) - { - PipelineProcessor.Dispose(); - } + PipelineProcessor?.Dispose(); } _disposed = true; } } -#endregion Redirections + #endregion Redirections internal static class FunctionOps { @@ -1213,13 +1345,16 @@ internal static void DefineFunction(ExecutionContext context, ScriptBlock scriptBlock = scriptBlockExpressionWrapper.GetScriptBlock( context, functionDefinitionAst.IsFilter); - context.EngineSessionState.SetFunctionRaw(functionDefinitionAst.Name, - scriptBlock, context.EngineSessionState.CurrentScope.ScopeOrigin); + var expAttribute = scriptBlock.ExperimentalAttribute; + if (expAttribute == null || expAttribute.ToShow) + { + context.EngineSessionState.SetFunctionRaw(functionDefinitionAst.Name, + scriptBlock, context.EngineSessionState.CurrentScope.ScopeOrigin); + } } catch (Exception exception) { - var rte = exception as RuntimeException; - if (rte == null) + if (exception is not RuntimeException rte) { throw ExceptionHandlingOps.ConvertToRuntimeException(exception, functionDefinitionAst.Extent); } @@ -1247,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) @@ -1272,7 +1418,7 @@ internal static void AddKeyValuePair(IDictionary hashtable, object key, object v if (errorKeyString.Length > 40) { - errorKeyString = errorKeyString.Substring(0, 40) + "..."; + errorKeyString = errorKeyString.Substring(0, 40) + PSObjectHelper.Ellipsis; } throw InterpreterError.NewInterpreterException(hashtable, typeof(RuntimeException), errorExtent, @@ -1296,15 +1442,15 @@ internal static object Add(IDictionary lvalDict, IDictionary rvalDict) } // Add key and values from left hand side... - foreach (object myKey in lvalDict.Keys) + foreach (object key in lvalDict.Keys) { - newDictionary.Add(myKey, lvalDict[myKey]); + newDictionary.Add(key, lvalDict[key]); } // and the right-hand side - foreach (object myKey in rvalDict.Keys) + foreach (object key in rvalDict.Keys) { - newDictionary.Add(myKey, rvalDict[myKey]); + newDictionary.Add(key, rvalDict[key]); } return newDictionary; @@ -1316,9 +1462,9 @@ internal static class ExceptionHandlingOps internal class CatchAll { } /// - /// Represent a handler search result + /// Represent a handler search result. /// - private class HandlerSearchResult + private sealed class HandlerSearchResult { internal HandlerSearchResult() { @@ -1357,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. @@ -1386,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, @@ -1410,7 +1559,7 @@ private static void FindAndProcessHandler(Type[] types, int[] ranks, } /// - /// Find the matching handler for the caught exception + /// Find the matching handler for the caught exception. /// internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte, Type[] types, ExecutionContext context) { @@ -1418,8 +1567,9 @@ internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte int[] ranks = RankExceptionTypes(types); var current = new HandlerSearchResult(); - do { - // Always assume no need to repeat the search for another interation + do + { + // 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; @@ -1476,11 +1626,12 @@ internal static int FindMatchingHandler(MutableTuple tuple, RuntimeException rte var errorRecord = new ErrorRecord(current.ErrorRecordToPass, current.ExceptionToPass); tuple.SetAutomaticVariable(AutomaticVariable.Underbar, errorRecord, context); } + return current.Handler; } /// - /// Find the matching handler by the exception type + /// Find the matching handler by the exception type. /// private static int FindMatchingHandlerByType(Type exceptionType, Type[] types) { @@ -1515,16 +1666,34 @@ private static int FindMatchingHandlerByType(Type exceptionType, Type[] types) internal static bool SuspendStoppingPipeline(ExecutionContext context) { - LocalPipeline lpl = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); - bool oldIsStopping = lpl.Stopper.IsStopping; - lpl.Stopper.IsStopping = false; - return oldIsStopping; + var localPipeline = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); + return SuspendStoppingPipelineImpl(localPipeline); } internal static void RestoreStoppingPipeline(ExecutionContext context, bool oldIsStopping) { - LocalPipeline lpl = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); - lpl.Stopper.IsStopping = oldIsStopping; + var localPipeline = (LocalPipeline)context.CurrentRunspace.GetCurrentlyRunningPipeline(); + RestoreStoppingPipelineImpl(localPipeline, oldIsStopping); + } + + internal static bool SuspendStoppingPipelineImpl(LocalPipeline localPipeline) + { + if (localPipeline is not null) + { + bool oldIsStopping = localPipeline.Stopper.IsStopping; + localPipeline.Stopper.IsStopping = false; + return oldIsStopping; + } + + return false; + } + + internal static void RestoreStoppingPipelineImpl(LocalPipeline localPipeline, bool oldIsStopping) + { + if (localPipeline is not null) + { + localPipeline.Stopper.IsStopping = oldIsStopping; + } } internal static void CheckActionPreference(FunctionContext funcContext, Exception exception) @@ -1545,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; @@ -1554,25 +1726,38 @@ internal static void CheckActionPreference(FunctionContext funcContext, Exceptio // set $? to false indicating an error context.QuestionMarkVariableValue = false; - bool anyTrapHandlers = funcContext._traps.Any() && funcContext._traps.Last().Item2 != null; + ActionPreference preference = GetErrorActionPreference(context); - if (!anyTrapHandlers && !ExceptionHandlingOps.NeedToQueryForActionPreference(rte, context)) + // If the exception was not rethrown and we are not currently + // handling an exception, then the exception is new, and we + // can break on it if requested. + if (!rte.WasRethrown && + context.CurrentExceptionBeingHandled == null && + preference == ActionPreference.Break) { - throw rte; + context.Debugger?.Break(rte); } - ActionPreference preference; + // Item2 in the trap tuples is the action (script) for the trap. + // A null action script is only used to indicate when exceptions + // should be thrown up to a higher level, and doesn't count as an + // actual trap handler in the function context. + bool anyTrapHandlers = funcContext._traps.Count > 0 && funcContext._traps[funcContext._traps.Count - 1].Item2 != null; + if (anyTrapHandlers) { + // update the action preference according to how the exception is + // handled in the trap statement(s). preference = ProcessTraps(funcContext, rte); } - else + else if (ExceptionCannotBeStoppedContinuedOrIgnored(rte, context)) { - preference = ExceptionHandlingOps.QueryForAction(rte, rte.Message, context); + throw rte; + } + else if (preference == ActionPreference.Inquire && !rte.SuppressPromptInInterpreter) + { + preference = InquireForActionPreference(rte.Message, context); } - - // set the value of $? here in case it is reset in trap handling. - context.QuestionMarkVariableValue = false; if ((preference == ActionPreference.SilentlyContinue) || (preference == ActionPreference.Ignore)) @@ -1596,12 +1781,7 @@ internal static void CheckActionPreference(FunctionContext funcContext, Exceptio throw rte; } - bool b = ExceptionHandlingOps.ReportErrorRecord(extent, rte, context); - - // set the value of $? here in case it is reset in error reporting - context.QuestionMarkVariableValue = false; - - if (!b) + if (!ReportErrorRecord(extent, rte, context)) { throw rte; } @@ -1639,16 +1819,15 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, if (handler != -1) { Diagnostics.Assert(exception != null, "Exception object can't be null."); + + var context = funcContext._executionContext; + try { ErrorRecord err = rte.ErrorRecord; - var context = funcContext._executionContext; // 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 { @@ -1664,6 +1843,7 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, { locals.SetValue(i, funcContext._localsTuple.GetValue(i)); } + var newScope = context.EngineSessionState.NewScope(false); context.EngineSessionState.CurrentScope = newScope; newScope.LocalsTuple = locals; @@ -1705,18 +1885,42 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, // Terminate this block of statements. return ActionPreference.Stop; } + finally + { + // The questionmark variable will always be false when we process a trap, so + // set it to false to ensure it didn't change as a result of anything done + // inside the trap + context.QuestionMarkVariableValue = false; + } } return ActionPreference.Stop; } /// - /// Determine if we should continue or not after and error or exception.... + /// Gets the current error action preference value. /// - /// The RuntimeException which was reported - /// The message to display - /// The execution context - /// The preference the user selected + /// The execution context. + /// The preference the user selected. + /// + /// Error action is decided by error action preference. If preference is inquire, we will + /// prompt user for their preference. + /// + internal static ActionPreference GetErrorActionPreference(ExecutionContext context) + { + return context.GetEnumPreference( + SpecialVariables.ErrorActionPreferenceVarPath, + ActionPreference.Continue, + out _); + } + + /// + /// Determine if we should continue or not after an error or exception. + /// + /// The RuntimeException which was reported. + /// The message to display. + /// The execution context. + /// The preference the user selected. /// /// Error action is decided by error action preference. If preference is inquire, we will /// prompt user for their preference. @@ -1724,10 +1928,11 @@ private static ActionPreference ProcessTraps(FunctionContext funcContext, internal static ActionPreference QueryForAction(RuntimeException rte, string message, ExecutionContext context) { // 906264 "$ErrorActionPreference="Inquire" prevents original non-terminating error from being reported to $error" - bool defaultUsed; ActionPreference preference = - context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, - ActionPreference.Continue, out defaultUsed); + context.GetEnumPreference( + SpecialVariables.ErrorActionPreferenceVarPath, + ActionPreference.Continue, + out _); if (preference != ActionPreference.Inquire || rte.SuppressPromptInInterpreter) return preference; @@ -1739,7 +1944,7 @@ internal static ActionPreference QueryForAction(RuntimeException rte, string mes /// This is a helper function for prompting for user preference. /// /// - /// The execution context + /// The execution context. /// /// /// This method will allow user to enter suspend mode. @@ -1766,12 +1971,16 @@ internal static ActionPreference InquireForActionPreference(string message, Exec string caption = ParserStrings.ExceptionActionPromptCaption; + bool oldQuestionMarkVariableValue = context.QuestionMarkVariableValue; + int choice; while ((choice = ui.PromptForChoice(caption, message, choices, 0)) == 3) { context.EngineHostInterface.EnterNestedPrompt(); } + context.QuestionMarkVariableValue = oldQuestionMarkVariableValue; + if (choice == 0) return ActionPreference.Continue; @@ -1782,12 +1991,12 @@ internal static ActionPreference InquireForActionPreference(string message, Exec } /// - /// Set error variables like $error and $stacktrace + /// Set error variables like $error and $stacktrace. /// /// /// - /// The execution context - /// the output pipe of the statement + /// The execution context. + /// The output pipe of the statement. internal static void SetErrorVariables(IScriptExtent extent, RuntimeException rte, ExecutionContext context, Pipe outputPipe) { string stack = null; @@ -1796,7 +2005,7 @@ internal static void SetErrorVariables(IScriptExtent extent, RuntimeException rt int i = 0; while (e != null && i++ < 10) { - if (!String.IsNullOrEmpty(e.StackTrace)) + if (!string.IsNullOrEmpty(e.StackTrace)) { stack = e.StackTrace; } @@ -1810,33 +2019,30 @@ 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); } } - internal static bool NeedToQueryForActionPreference(RuntimeException rte, ExecutionContext context) + internal static bool ExceptionCannotBeStoppedContinuedOrIgnored(RuntimeException rte, ExecutionContext context) { - return !context.PropagateExceptionsToEnclosingStatementBlock - && context.ShellFunctionErrorOutputPipe != null - && !context.CurrentPipelineStopping - && !rte.SuppressPromptInInterpreter - && !(rte is PipelineStoppedException); + return context.PropagateExceptionsToEnclosingStatementBlock + || context.ShellFunctionErrorOutputPipe == null + || context.CurrentPipelineStopping + || rte.SuppressPromptInInterpreter + || rte is PipelineStoppedException; } /// /// Report error into error pipe. /// /// - /// The runtime error to report - /// The execution context - /// True if it was able to report the error + /// The runtime error to report. + /// The execution context. + /// True if it was able to report the error. internal static bool ReportErrorRecord(IScriptExtent extent, RuntimeException rte, ExecutionContext context) { if (context.ShellFunctionErrorOutputPipe == null) @@ -1848,8 +2054,7 @@ internal static bool ReportErrorRecord(IScriptExtent extent, RuntimeException rt rte.ErrorRecord.SetInvocationInfo(new InvocationInfo(null, extent, context)); PSObject errorWrap = PSObject.AsPSObject(new ErrorRecord(rte.ErrorRecord, rte)); - PSNoteProperty note = new PSNoteProperty("writeErrorStream", true); - errorWrap.Properties.Add(note); + errorWrap.WriteStream = WriteStreamType.Error; // If this is an error pipe for a hosting application (i.e.: no downstream cmdlet), // and we are logging, then create a temporary PowerShell to log the error. @@ -1860,10 +2065,13 @@ internal static bool ReportErrorRecord(IScriptExtent extent, RuntimeException rt context.ShellFunctionErrorOutputPipe.Add(errorWrap); + // set the value of $? here in case it is reset in error reporting. + context.QuestionMarkVariableValue = false; + return true; } - internal static RuntimeException ConvertToException(object result, IScriptExtent extent) + internal static RuntimeException ConvertToException(object result, IScriptExtent extent, bool rethrow) { result = PSObject.Base(result); @@ -1872,13 +2080,14 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent { InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); runtimeException.WasThrownFromThrowStatement = true; + runtimeException.WasRethrown = rethrow; return runtimeException; } ErrorRecord er = result as ErrorRecord; if (er != null) { - runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); return runtimeException; @@ -1888,7 +2097,7 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent if (exception != null) { er = new ErrorRecord(exception, exception.Message, ErrorCategory.OperationStopped, null); - runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); return runtimeException; } @@ -1899,7 +2108,7 @@ internal static RuntimeException ConvertToException(object result, IScriptExtent exception = new RuntimeException(message, null); er = new ErrorRecord(exception, message, ErrorCategory.OperationStopped, null); - runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true }; + runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow }; runtimeException.SetTargetObject(result); InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent); @@ -2023,7 +2232,7 @@ internal static Type ResolveTypeName(ITypeName typeName, IScriptExtent errorPos) try { - if (generic != null && generic.GetTypeInfo().ContainsGenericParameters) + if (generic != null && generic.ContainsGenericParameters) generic.MakeGenericType(typeArgs); } catch (Exception e) @@ -2160,7 +2369,6 @@ internal static string[] GetNamespacesForTypeResolutionState(IEnumerable /// /// @@ -2188,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.GetTypeInfo().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)) { @@ -2328,7 +2541,7 @@ internal static string ResolveFilePath(IScriptExtent errorExtent, object obj, Ex FileInfo file = obj as FileInfo; string filePath = file != null ? file.FullName : PSObject.ToStringParser(context, obj); - if (String.IsNullOrEmpty(filePath)) + if (string.IsNullOrEmpty(filePath)) { throw InterpreterError.NewInterpreterException(filePath, typeof(RuntimeException), errorExtent, "InvalidFilenameOption", ParserStrings.InvalidFilenameOption); @@ -2382,7 +2595,7 @@ internal static string ResolveFilePath(IScriptExtent errorExtent, object obj, Ex public enum WhereOperatorSelectionMode { /// - /// Return all matches + /// Return all matches. /// Default = 0, /// @@ -2390,15 +2603,15 @@ public enum WhereOperatorSelectionMode /// First = 1, /// - /// Return the last matching element + /// Return the last matching element. /// Last = 2, // return last match /// - /// Skip until the condition is true, then return the rest + /// Skip until the condition is true, then return the rest. /// SkipUntil = 3, /// - /// Return elements until the condition is true then skip the rest + /// Return elements until the condition is true then skip the rest. /// Until = 4, /// @@ -2410,9 +2623,9 @@ public enum WhereOperatorSelectionMode internal static class EnumerableOps { /// - /// Implements the Where(expression) operation on collections + /// Implements the Where(expression) operation on collections. /// - /// The enumerator over the collection to search + /// The enumerator over the collection to search. /// /// A ScriptBlock where its result is treated as a boolean, or null to /// return all collection objects with WhereOperatorSelectionMode. @@ -2421,7 +2634,7 @@ internal static class EnumerableOps /// Sets the WhereOperatorSelectionMode for operator, defaults to All. /// This is of type object to allow either enum values or strings to be passed. /// - /// The number of elements to return + /// The number of elements to return. /// internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, WhereOperatorSelectionMode selectionMode, int numberToReturn) { @@ -2429,7 +2642,7 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W if (numberToReturn < 0) { - throw new ArgumentOutOfRangeException("numberToReturn", numberToReturn, ParserStrings.NumberToReturnMustBeGreaterThanZero); + throw new ArgumentOutOfRangeException(nameof(numberToReturn), numberToReturn, ParserStrings.NumberToReturnMustBeGreaterThanZero); } var context = Runspace.DefaultRunspace.ExecutionContext; @@ -2445,7 +2658,7 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W } var rest = new List(); - Object current = null; + object current = null; int index = 0; if (numberToReturn == 0) @@ -2465,7 +2678,6 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W rest.Add(Current(enumerator)); } - return rest.ToArray(); } @@ -2484,14 +2696,16 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W } } } + if (numberToReturn == 1) { - return new Object[] { current }; + return new object[] { current }; } + return rest.ToArray(); } - System.Object[] first = new System.Object[numberToReturn]; + object[] first = new object[numberToReturn]; while (MoveNext(context, enumerator)) { current = Current(enumerator); @@ -2519,7 +2733,8 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W var e = Current(enumerator); rest.Add(e); } - return new System.Object[] { first, rest.ToArray() }; + + return new object[] { first, rest.ToArray() }; } return first; @@ -2531,6 +2746,7 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W { notMatched = new Collection(); } + var resultCollection = new List(); Pipe outputPipe = new Pipe(resultCollection); bool returnTheRest = false; @@ -2638,17 +2854,17 @@ internal static object Where(IEnumerator enumerator, ScriptBlock expressionSB, W var ie = Current(enumerator); notMatched.Add(ie == null ? null : PSObject.AsPSObject(ie)); } - return new System.Object[] { matches, notMatched }; + + return new object[] { matches, notMatched }; } return matches; } - /// /// Implements the ForEach() operator. /// - /// The collection to operate over + /// The collection to operate over. /// /// /// @@ -2657,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("expression"); - } + + ArgumentNullException.ThrowIfNull(expression); var context = Runspace.DefaultRunspace.ExecutionContext; @@ -2685,11 +2899,12 @@ internal static object ForEach(IEnumerator enumerator, object expression, object object current = Current(enumerator); list.Add(current); } + return LanguagePrimitives.ConvertTo(list, targetType, CultureInfo.InvariantCulture); } // If it's a generic type then make sure it only has one type argument - if (targetType.GetTypeInfo().IsGenericType) + if (targetType.IsGenericType) { Type[] ta = targetType.GetGenericArguments(); if (ta.Length != 1) @@ -2697,6 +2912,7 @@ internal static object ForEach(IEnumerator enumerator, object expression, object throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException), null, "ForEachBadGenericConversionTypeSpecified", ParserStrings.ForEachBadGenericConversionTypeSpecified, ParserOps.ConvertTo(targetType, null)); } + resultCollection = PSObject.AsPSObject(Activator.CreateInstance(targetType)); while (MoveNext(context, enumerator)) { @@ -2737,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) { @@ -2814,6 +3035,7 @@ internal static object ForEach(IEnumerator enumerator, object expression, object ExtendedTypeSystem.MethodInvocationException, name, arguments.Length, nullRefException.Message); } + continue; } @@ -2860,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); } } @@ -2916,6 +3148,7 @@ internal static object SlicingIndex(object target, IEnumerator indexes, Func(); always return a new instance. + return new object[0]; +#pragma warning restore CA1825 // Avoid zero-length array allocations } return ArrayOps.Multiply(originalList.ToArray(), times); @@ -3182,18 +3419,19 @@ internal object GetNonEnumerableObject() internal static IEnumerator GetCOMEnumerator(object obj) { object targetValue = PSObject.Base(obj); + try + { + var enumerator = (targetValue as IEnumerable)?.GetEnumerator(); + if (enumerator != null) + { + return enumerator; + } + } + catch (Exception) + { + } - // We use ComEnumerator to enumerate COM collections because the following code doesn't work in .NET Core - // IEnumerable enumerable = targetValue as IEnumerable; - // if (enumerable != null) - // { - // var enumerator = enumerable.GetEnumerator(); - // ... - // } - // The call to 'GetEnumerator()' throws exception because COM is not supported in .NET Core. - // See https://github.com/dotnet/corefx/issues/19731 for more information. - // When COM support is back to .NET Core, we need to change back to the original implementation. - return ComEnumerator.Create(targetValue) ?? NonEnumerableObjectEnumerator.Create(obj); + return targetValue as IEnumerator ?? NonEnumerableObjectEnumerator.Create(obj); } internal static IEnumerator GetGenericEnumerator(IEnumerable enumerable) @@ -3217,15 +3455,14 @@ internal static IEnumerator GetGenericEnumerator(IEnumerable enumerable) } } - /// /// A routine used to advance an enumerator and catch errors that might occur - /// performing the operation + /// performing the operation. /// - /// The execution context used to see if the pipeline is stopping + /// The execution context used to see if the pipeline is stopping. /// THe enumerator to advance. - /// An error occurred moving to the next element in the enumeration - /// True if the move succeeded + /// An error occurred moving to the next element in the enumeration. + /// True if the move succeeded. internal static bool MoveNext(ExecutionContext context, IEnumerator enumerator) { try @@ -3258,7 +3495,7 @@ internal static bool MoveNext(ExecutionContext context, IEnumerator enumerator) /// /// Wrapper caller for enumerator.Current - handles and republishes errors... /// - /// The enumerator to read from + /// The enumerator to read from. /// internal static object Current(IEnumerator enumerator) { @@ -3373,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(); } } } @@ -3388,6 +3622,7 @@ internal static object[] ToArray(IEnumerator enumerator) { result.Add(Current(enumerator)); } + return result.ToArray(); } @@ -3402,7 +3637,115 @@ internal static object[] GetSlice(IList list, int startIndex) { result[j++] = list[i++]; } + 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/NumericOps.cs b/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs index a1957f7732a..f5670899c23 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. // ReSharper disable UnusedMember.Global // ReSharper disable RedundantCast @@ -9,8 +8,8 @@ namespace System.Management.Automation { internal static class Boxed { - internal static object True = (object)true; - internal static object False = (object)false; + internal static readonly object True = (object)true; + internal static readonly object False = (object)false; } internal static class IntOps @@ -22,6 +21,7 @@ internal static object Add(int lhs, int rhs) { return (int)result; } + return (double)result; } @@ -32,6 +32,7 @@ internal static object Sub(int lhs, int rhs) { return (int)result; } + return (double)result; } @@ -42,6 +43,7 @@ internal static object Multiply(int lhs, int rhs) { return (int)result; } + return (double)result; } @@ -93,10 +95,15 @@ internal static object Remainder(int lhs, int rhs) } internal static object CompareEq(int lhs, int rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(int lhs, int rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(int lhs, int rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(int lhs, int rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(int lhs, int rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(int lhs, int rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } internal static object[] Range(int lower, int upper) @@ -130,6 +137,7 @@ internal static object Add(uint lhs, uint rhs) { return (uint)result; } + return (double)result; } @@ -140,6 +148,7 @@ internal static object Sub(uint lhs, uint rhs) { return (uint)result; } + return (double)result; } @@ -150,6 +159,7 @@ internal static object Multiply(uint lhs, uint rhs) { return (uint)result; } + return (double)result; } @@ -188,10 +198,15 @@ internal static object Remainder(uint lhs, uint rhs) } internal static object CompareEq(uint lhs, uint rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(uint lhs, uint rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(uint lhs, uint rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(uint lhs, uint rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(uint lhs, uint rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(uint lhs, uint rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } } @@ -204,6 +219,7 @@ internal static object Add(long lhs, long rhs) { return (long)result; } + return (double)result; } @@ -214,6 +230,7 @@ internal static object Sub(long lhs, long rhs) { return (long)result; } + return (double)result; } @@ -227,6 +244,7 @@ internal static object Multiply(long lhs, long rhs) { return (long)biResult; } + return (double)biResult; } @@ -279,10 +297,15 @@ internal static object Remainder(long lhs, long rhs) } internal static object CompareEq(long lhs, long rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(long lhs, long rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(long lhs, long rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(long lhs, long rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(long lhs, long rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(long lhs, long rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } } @@ -295,6 +318,7 @@ internal static object Add(ulong lhs, ulong rhs) { return (ulong)result; } + return (double)result; } @@ -305,6 +329,7 @@ internal static object Sub(ulong lhs, ulong rhs) { return (ulong)result; } + return (double)result; } @@ -318,6 +343,7 @@ internal static object Multiply(ulong lhs, ulong rhs) { return (ulong)biResult; } + return (double)biResult; } @@ -356,10 +382,15 @@ internal static object Remainder(ulong lhs, ulong rhs) } internal static object CompareEq(ulong lhs, ulong rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(ulong lhs, ulong rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(ulong lhs, ulong rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(ulong lhs, ulong rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(ulong lhs, ulong rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(ulong lhs, ulong rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } } @@ -473,6 +504,7 @@ internal static object BOr(decimal lhs, decimal rhs) return (long)(l | r); } } + return l | r; } @@ -489,6 +521,7 @@ internal static object BXor(decimal lhs, decimal rhs) return (long)(l ^ r); } } + return l ^ r; } @@ -505,6 +538,7 @@ internal static object BAnd(decimal lhs, decimal rhs) return (long)(l & r); } } + return l & r; } @@ -518,6 +552,7 @@ private static ulong ConvertToUlong(decimal val) long lValue = LanguagePrimitives.ConvertTo(val); return unchecked((ulong)lValue); } + return LanguagePrimitives.ConvertTo(val); } @@ -576,10 +611,15 @@ internal static object RightShift(decimal val, int count) } internal static object CompareEq(decimal lhs, decimal rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(decimal lhs, decimal rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(decimal lhs, decimal rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(decimal lhs, decimal rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(decimal lhs, decimal rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(decimal lhs, decimal rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } private static object CompareWithDouble(decimal left, double right, @@ -617,17 +657,27 @@ private static object CompareWithDouble(double left, decimal right, } internal static object CompareEq1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareEq, CompareEq); } + internal static object CompareNe1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareNe, CompareNe); } + internal static object CompareLt1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareLt, CompareLt); } + internal static object CompareLe1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareLe, CompareLe); } + internal static object CompareGt1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareGt, CompareGt); } + internal static object CompareGe1(double lhs, decimal rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareGe, CompareGe); } internal static object CompareEq2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareEq, CompareEq); } + internal static object CompareNe2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareNe, CompareNe); } + internal static object CompareLt2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareLt, CompareLt); } + internal static object CompareLe2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareLe, CompareLe); } + internal static object CompareGt2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareGt, CompareGt); } + internal static object CompareGe2(decimal lhs, double rhs) { return CompareWithDouble(lhs, rhs, DoubleOps.CompareGe, CompareGe); } } @@ -707,6 +757,7 @@ internal static object BOr(double lhs, double rhs) return (long)(l | r); } } + return l | r; } @@ -723,6 +774,7 @@ internal static object BXor(double lhs, double rhs) return (long)(l ^ r); } } + return l ^ r; } @@ -739,6 +791,7 @@ internal static object BAnd(double lhs, double rhs) return (long)(l & r); } } + return l & r; } @@ -752,6 +805,7 @@ private static ulong ConvertToUlong(double val) long lValue = LanguagePrimitives.ConvertTo(val); return unchecked((ulong)lValue); } + return LanguagePrimitives.ConvertTo(val); } @@ -816,10 +870,15 @@ internal static object RightShift(double val, int count) } internal static object CompareEq(double lhs, double rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + internal static object CompareNe(double lhs, double rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLt(double lhs, double rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + internal static object CompareLe(double lhs, double rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGt(double lhs, double rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + internal static object CompareGe(double lhs, double rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } } diff --git a/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs b/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs index c513cc569da..0ac1db53ff9 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/StringOps.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; using System.Management.Automation.Internal; @@ -29,15 +28,12 @@ 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("times"); - } + // TODO: this should be a runtime error. + ArgumentOutOfRangeException.ThrowIfNegative(times); if (times == 0 || s.Length == 0) { - return String.Empty; + return string.Empty; } var context = LocalPipeline.GetExecutionContextFromTLS(); @@ -54,12 +50,16 @@ internal static string Multiply(string s, int times) return new string(s[0], times); } - // Convert the string to a char array, use the array multiplication code, - // then construct a new string from the resulting char array. This uses - // extra memory compared to the naive algorithm, but is faster (measured - // against a V2 CLR, should be measured against V4 as the StringBuilder - // implementation changed.) - return new string(ArrayOps.Multiply(s.ToCharArray(), (uint)times)); + return string.Create(s.Length * times, (s, times), (dst, args) => + { + ReadOnlySpan src = args.s.AsSpan(); + int length = src.Length; + for (int i = 0; i < args.times; i++) + { + src.CopyTo(dst); + dst = dst.Slice(length); + } + }); } internal static string FormatOperator(string formatString, object formatArgs) @@ -85,7 +85,7 @@ internal static string FormatOperator(string formatString, object formatArgs) /// StringComparison.InvariantCulture is not in CoreCLR, so we need to use /// CultureInfo.InvariantCulture.CompareInfo.Compare(string, string, CompareOptions) /// to substitute - /// String.Compare(string, string, StringComparison) + /// string.Compare(string, string, StringComparison) /// internal static int Compare(string strA, string strB, CultureInfo culture, CompareOptions option) { @@ -97,7 +97,7 @@ internal static int Compare(string strA, string strB, CultureInfo culture, Compa /// StringComparison.InvariantCulture is not in CoreCLR, so we need to use /// CultureInfo.InvariantCulture.CompareInfo.Compare(string, string, CompareOptions) == 0 /// to substitute - /// String.Equals(string, string, StringComparison) + /// string.Equals(string, string, StringComparison) /// internal static bool Equals(string strA, string strB, CultureInfo culture, CompareOptions option) { @@ -105,4 +105,4 @@ internal static bool Equals(string strA, string strB, CultureInfo culture, Compa return culture.CompareInfo.Compare(strA, strB, option) == 0; } } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs b/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs index 402257788c5..72126c726c9 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/VariableOps.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Linq; using System.Management.Automation.Internal; @@ -47,6 +46,15 @@ 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); if (executionContext._debuggingMode > 0) @@ -54,49 +62,60 @@ internal static object SetVariableValue(VariablePath variablePath, object value, executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath); } } - else if (attributeAsts != null) + else { - // Use bytewise operation directly instead of 'var.IsReadOnly || var.IsConstant' on - // a hot path (setting variable with type constraint) to get better performance. - if ((var.Options & (ScopedItemOptions.ReadOnly | ScopedItemOptions.Constant)) != ScopedItemOptions.None) + if (attributeAsts != null) { - SessionStateUnauthorizedAccessException e = - new SessionStateUnauthorizedAccessException( - var.Name, - SessionStateCategory.Variable, - "VariableNotWritable", - SessionStateStrings.VariableNotWritable); - throw e; - } + // Use bytewise operation directly instead of 'var.IsReadOnly || var.IsConstant' on + // a hot path (setting variable with type constraint) to get better performance. + if ((var.Options & (ScopedItemOptions.ReadOnly | ScopedItemOptions.Constant)) != ScopedItemOptions.None) + { + SessionStateUnauthorizedAccessException e = + new SessionStateUnauthorizedAccessException( + var.Name, + SessionStateCategory.Variable, + "VariableNotWritable", + SessionStateStrings.VariableNotWritable); + throw e; + } + + var attributes = GetAttributeCollection(attributeAsts); + value = PSVariable.TransformValue(attributes, value); + if (!PSVariable.IsValidValue(attributes, value)) + { + ValidationMetadataException e = new ValidationMetadataException( + "ValidateSetFailure", + null, + Metadata.InvalidValueFailure, + var.Name, + (value != null) ? value.ToString() : "$null"); + + throw e; + } + + var.SetValueRaw(value, true); + // Don't update the PSVariable's attributes until we successfully set the value + var.Attributes.Clear(); + var.AddParameterAttributesNoChecks(attributes); - var attributes = GetAttributeCollection(attributeAsts); - value = PSVariable.TransformValue(attributes, value); - if (!PSVariable.IsValidValue(attributes, value)) + if (executionContext._debuggingMode > 0) + { + executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath); + } + } + else { - ValidationMetadataException e = new ValidationMetadataException( - "ValidateSetFailure", - null, - Metadata.InvalidValueFailure, - var.Name, - ((value != null) ? value.ToString() : "$null")); - - throw e; + // The setter will handle checking for variable writes. + var.Value = value; } - var.SetValueRaw(value, true); - // Don't update the PSVariable's attributes until we successfully set the value - var.Attributes.Clear(); - var.AddParameterAttributesNoChecks(attributes); - if (executionContext._debuggingMode > 0) + if (executionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) { - executionContext.Debugger.CheckVariableWrite(variablePath.UnqualifiedPath); + // Mark untrusted values for assignments to 'Global:' variables, and 'Script:' variables in + // a module scope, if it's necessary. + ExecutionContext.MarkObjectAsUntrustedForVariableAssignment(var, scope, sessionState); } } - else - { - // The setter will handle checking for variable writes. - var.Value = value; - } return value; } @@ -124,8 +143,10 @@ private static bool ThrowStrictModeUndefinedVariable(ExecutionContext executionC { return false; } + parent = parent.Parent; } + return true; } @@ -153,6 +174,7 @@ internal static object GetAutomaticVariableValue(int tupleIndex, ExecutionContex result = null; } + return result; } @@ -217,6 +239,7 @@ internal static PSReference GetVariableAsRef(VariablePath variablePath, Executio staticType = value.GetType(); } } + if (staticType == null) { var declaredType = var.Attributes.OfType().FirstOrDefault(); @@ -233,6 +256,7 @@ private static Collection GetAttributeCollection(AttributeBaseAst[] a { result.Add(attributeAst.GetAttribute()); } + return result; } @@ -260,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 774dfc834f7..defd87662e8 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -17,7 +16,9 @@ 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; } public override AstVisitAction VisitParameter(ParameterAst parameterAst) @@ -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 = Utils.EmptyArray(); - } + 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 @@ -252,6 +253,7 @@ internal static PowerShell Convert(ScriptBlockAst body, parameter.InternalVisit(checker); } } + body.InternalVisit(checker); // When the context is null (or they haven't supplied any variables), throw, but only if we really need the @@ -299,6 +301,7 @@ internal static PowerShell Convert(ScriptBlockAst body, converter._powershell.AddStatement(); converter.ConvertPipeline(pipeline, isTrustedInput); } + return converter._powershell; } finally @@ -310,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. /// @@ -329,7 +482,7 @@ internal static object[] GetUsingValuesAsArray(ScriptBlock scriptBlock, bool isT /// /// Collect values for UsingExpressions, in the form of a dictionary and an array. /// - The dictionary form is used when the remote server is PSv5 and later version for handling UsingExpression in Invoke-Command/Start-Job - /// - The array form is used when the remote server is PSv3 and PSv4 for handling UsingExpression in Invoke-Command + /// - The array form is used when the remote server is PSv3 and PSv4 for handling UsingExpression in Invoke-Command. /// /// /// We still keep the array-form using values because we want to avoid any breaking changes when running Invoke-Command @@ -340,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; @@ -384,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); @@ -407,10 +564,7 @@ private static Tuple, object[]> GetUsingValues(Ast bo // Collect UsingExpression value as a dictionary string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst); - if (!usingValueMap.ContainsKey(usingAstKey)) - { - usingValueMap.Add(usingAstKey, value); - } + usingValueMap.TryAdd(usingAstKey, value); } } catch (RuntimeException rte) @@ -457,12 +611,12 @@ 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 - /// The top level Ast, should be either ScriptBlockAst or FunctionDefinitionAst - /// The ScriptBlockAst that represents the scope of the previously analyzed UsingExpressions + /// The UsingExpression to analyze. + /// The top level Ast, should be either ScriptBlockAst or FunctionDefinitionAst. + /// The ScriptBlockAst that represents the scope of the previously analyzed UsingExpressions. private static bool HasUsingExpressionsInDifferentScopes(UsingExpressionAst usingExpr, Ast topLevelParent, ref ScriptBlockAst sbClosestToPreviousUsingExpr) { Diagnostics.Assert(topLevelParent is ScriptBlockAst || topLevelParent is FunctionDefinitionAst, @@ -548,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) { @@ -582,7 +735,7 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) break; } - command.MergeMyResults(fromType, toType); + command.MergeMyResults(fromType, toResult: PipelineResultTypes.Output); } _powershell.AddCommand(command); @@ -615,7 +768,7 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) var arguments = usingValue as System.Collections.IEnumerable; if (arguments != null) { - foreach (Object argument in arguments) + foreach (object argument in arguments) { _powershell.AddArgument(argument); } @@ -631,6 +784,7 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) { _powershell.AddArgument(usingValue); } + continue; } @@ -643,7 +797,9 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) { var constantExprAst = ast as ConstantExpressionAst; object argument; - if (constantExprAst != null && LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType))) + if (constantExprAst != null + && (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType)) + || constantExprAst.StaticType == typeof(System.Numerics.BigInteger))) { var commandArgumentText = constantExprAst.Extent.Text; argument = constantExprAst.Value; @@ -677,6 +833,7 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) argument = GetExpressionValue(exprAst, isTrustedInput); } } + _powershell.AddArgument(argument); } } @@ -745,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); } } @@ -758,10 +915,12 @@ private object GetExpressionValue(ExpressionAst exprAst, bool isTrustedInput) rs.Open(); _context = rs.ExecutionContext; } + if (!isTrustedInput) // if it's not trusted, call the safe value visitor { return GetSafeValueVisitor.GetSafeValue(exprAst, _context, GetSafeValueVisitor.SafeValueContext.GetPowerShell); } + return Compiler.GetExpressionValue(exprAst, isTrustedInput, _context, _usingValueMap); } @@ -781,13 +940,13 @@ private void AddParameter(CommandParameterAst commandParameterAst, bool isTruste } else { - nameSuffix = ""; + nameSuffix = string.Empty; argument = null; } // 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 3d40f11d344..d5d1dfbc61c 100644 --- a/src/System.Management.Automation/engine/scriptparameterbinder.cs +++ b/src/System.Management.Automation/engine/scriptparameterbinder.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Management.Automation.Internal; @@ -12,31 +11,25 @@ namespace System.Management.Automation /// /// The parameter binder for shell functions. /// - /// internal class ScriptParameterBinder : ParameterBinderBase { #region ctor /// - /// Constructs a ScriptParameterBinder with the specified context + /// Constructs a ScriptParameterBinder with the specified context. /// - /// /// /// The script block representing the code being run /// - /// /// /// The invocation information about the code that is being run. /// - /// /// /// The context under which the shell function is executing. /// - /// /// /// The command instance that represents the script in a pipeline. May be null. /// - /// /// /// If binding in a new local scope, the scope to set variables in. If dotting, the value is null. /// @@ -55,6 +48,7 @@ internal ScriptParameterBinder( private readonly CallSite> _copyMutableValueSite = CallSite>.Create(PSVariableAssignmentBinder.Get()); + internal object CopyMutableValues(object o) { // The variable assignment binder copies mutable values and returns other values as is. @@ -68,18 +62,15 @@ internal object CopyMutableValues(object o) #region Parameter default values /// - /// Gets the default value for the specified parameter + /// Gets the default value for the specified parameter. /// - /// /// /// The name of the parameter to get the default value of. /// - /// /// /// The default value of the specified parameter. /// - /// - /// see SessionStateInternal.GetVariableValue + /// See SessionStateInternal.GetVariableValue. internal override object GetDefaultParameterValue(string name) { RuntimeDefinedParameter runtimeDefinedParameter; @@ -96,7 +87,7 @@ internal override object GetDefaultParameterValue(string name) #region Parameter binding /// - /// Binds the parameters to local variables in the function scope + /// Binds the parameters to local variables in the function scope. /// /// /// The name of the parameter to bind the value to. @@ -143,7 +134,7 @@ internal override void BindParameter(string name, object value, CompiledCommandP // to do so again. variable.AddParameterAttributesNoChecks(runtimeDefinedParameter.Attributes); } - } // BindParameter + } /// /// Return the default value of a script parameter, evaluating the parse tree if necessary. @@ -168,12 +159,12 @@ internal object GetDefaultScriptParameterValue(RuntimeDefinedParameter parameter #region private members /// - /// The script that is being bound to + /// The script that is being bound to. /// - internal ScriptBlock Script { get; private set; } + internal ScriptBlock Script { get; } internal SessionStateScope LocalScope { get; set; } #endregion private members - } // ScriptParameterBinder -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs b/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs index cde402ebbd3..5d946b4d60a 100644 --- a/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs +++ b/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; @@ -12,16 +11,14 @@ namespace System.Management.Automation /// This is the interface between the ScriptCommandProcessor and the /// parameter binders required to bind parameters to a shell function. /// - /// internal class ScriptParameterBinderController : ParameterBinderController { #region ctor /// /// Initializes the cmdlet parameter binder controller for - /// the specified cmdlet and engine context + /// the specified cmdlet and engine context. /// - /// /// /// The script that contains the parameter metadata. /// @@ -66,32 +63,24 @@ 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 + /// Binds the command line parameters for shell functions/filters/scripts/scriptblocks. /// - /// /// /// The arguments to be bound. /// - /// /// /// True if binding was successful or false otherwise. /// 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 = @@ -124,20 +113,16 @@ internal void BindCommandLineParameters(Collection arg /// Passes the binding directly through to the parameter binder. /// It does no verification against metadata. /// - /// /// /// The name and value of the variable to bind. /// - /// /// /// Ignored. /// - /// /// /// True if the parameter was successfully bound. Any error condition /// produces an exception. /// - /// internal override bool BindParameter(CommandParameterInternal argument, ParameterBindingFlags flags) { // Just pass the binding straight through. No metadata to verify the parameter against. @@ -145,84 +130,13 @@ 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, - "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 + /// them to $args. /// - /// /// /// The remaining unbound arguments. /// - /// /// /// An array containing the values that were bound to $args. /// @@ -274,6 +188,7 @@ private void HandleRemainingArguments(Collection argum { args.Add(argValue); } + continue; } @@ -284,7 +199,7 @@ private void HandleRemainingArguments(Collection argum // foo "-abc" // This is important when splatting, we reconstruct the parameter if the // value is splatted. - var parameterText = new PSObject(new String(parameter.ParameterText.ToCharArray())); + var parameterText = new PSObject(new string(parameter.ParameterText)); if (parameterText.Properties[NotePropertyNameForSplattingParametersInArgs] == null) { var noteProperty = new PSNoteProperty(NotePropertyNameForSplattingParametersInArgs, @@ -292,6 +207,7 @@ private void HandleRemainingArguments(Collection argum { IsHidden = true }; parameterText.Properties.Add(noteProperty); } + args.Add(parameterText); } diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index feadb81279f..add0eab25dc 100644 --- a/src/System.Management.Automation/engine/serialization.cs +++ b/src/System.Management.Automation/engine/serialization.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -12,6 +12,7 @@ using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; using System.Net.Mail; @@ -20,11 +21,12 @@ using System.Security; using System.Text; using System.Xml; + using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Serialization; using Microsoft.PowerShell.Commands; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Remoting; namespace System.Management.Automation { @@ -79,29 +81,27 @@ internal SerializationContext(int depth, SerializationOptions options, PSRemotin } /// - /// This class provides public functionality for serializing a PSObject + /// This class provides public functionality for serializing a PSObject. /// - public class PSSerializer + public static class PSSerializer { - internal PSSerializer() { } - /// - /// Serializes an object into PowerShell CliXml + /// Serializes an object into PowerShell CliXml. /// - /// The input object to serialize. Serializes to a default depth of 1 - /// The serialized object, as CliXml - public static string Serialize(Object source) + /// The input object to serialize. Serializes to a default depth of 1. + /// The serialized object, as CliXml. + public static string Serialize(object source) { return Serialize(source, s_mshDefaultSerializationDepth); } /// - /// Serializes an object into PowerShell CliXml + /// Serializes an object into PowerShell CliXml. /// - /// The input object to serialize - /// The depth of the members to serialize - /// The serialized object, as CliXml - public static string Serialize(Object source, int depth) + /// The input object to serialize. + /// The depth of the members to serialize. + /// The serialized object, as CliXml. + public static string Serialize(object source, int depth) { // Create an xml writer StringBuilder sb = new StringBuilder(); @@ -122,14 +122,53 @@ 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. /// /// The CliXml the represents the object to deserialize. - /// An object that represents the serialized content + /// An object that represents the serialized content. public static object Deserialize(string source) { - Object[] results = DeserializeAsList(source); + object[] results = DeserializeAsList(source); // Return the results if (results.Length == 0) @@ -150,7 +189,7 @@ public static object Deserialize(string source) /// Deserializes PowerShell CliXml into a list of objects. /// /// The CliXml the represents the object to deserialize. - /// An object array represents the serialized content + /// An object array represents the serialized content. public static object[] DeserializeAsList(string source) { List results = new List(); @@ -171,13 +210,13 @@ public static object[] DeserializeAsList(string source) } /// - /// Default depth of serialization + /// Default depth of serialization. /// - private static int s_mshDefaultSerializationDepth = 1; + private static readonly int s_mshDefaultSerializationDepth = 1; } /// - /// This class provides functionality for serializing a PSObject + /// This class provides functionality for serializing a PSObject. /// internal class Serializer { @@ -186,21 +225,21 @@ internal class Serializer private readonly InternalSerializer _serializer; /// - /// Creates a Serializer using default serialization context + /// Creates a Serializer using default serialization context. /// - /// writer to be used for serialization + /// Writer to be used for serialization. internal Serializer(XmlWriter writer) : this(writer, new SerializationContext()) { } /// - /// Creates a Serializer using specified serialization context + /// Creates a Serializer using specified serialization context. /// - /// writer to be used for serialization - /// depth of serialization + /// 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) @@ -209,19 +248,20 @@ internal Serializer(XmlWriter writer, int depth, bool useDepthFromTypes) } /// - /// Creates a Serializer using specified serialization context + /// Creates a Serializer using specified serialization context. /// - /// writer to be used for serialization - /// serialization context + /// Writer to be used for serialization. + /// Serialization context. internal Serializer(XmlWriter writer, SerializationContext context) { if (writer == null) { - throw PSTraceSource.NewArgumentException("writer"); + throw PSTraceSource.NewArgumentException(nameof(writer)); } + if (context == null) { - throw PSTraceSource.NewArgumentException("context"); + throw PSTraceSource.NewArgumentException(nameof(context)); } _serializer = new InternalSerializer(writer, context); @@ -240,13 +280,14 @@ internal Serializer(XmlWriter writer, SerializationContext context) internal TypeTable TypeTable { get { return _serializer.TypeTable; } + set { _serializer.TypeTable = value; } } /// - /// Serializes the object + /// Serializes the object. /// - /// object to be serialized + /// Object to be serialized. /// /// Please note that this method shouldn't throw any exceptions. /// If it throws - please open a bug. @@ -257,7 +298,7 @@ internal void Serialize(object source) } /// - /// Serializes passed in object + /// Serializes passed in object. /// /// /// object to be serialized @@ -275,7 +316,7 @@ internal void Serialize(object source, string streamName) } /// - /// Write the end of root element + /// Write the end of root element. /// internal void Done() { @@ -319,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 Nullable MaximumAllowedMemory { set; get; } + internal int? MaximumAllowedMemory { get; set; } /// /// Logs that memory used by deserialized objects is not related to the size of input xml. @@ -343,7 +384,7 @@ internal void LogExtraMemoryUsage(int amountOfExtraMemory) throw new XmlException(message); } - _totalDataProcessedSoFar = _totalDataProcessedSoFar + amountOfExtraMemory; + _totalDataProcessedSoFar += amountOfExtraMemory; } } @@ -352,7 +393,7 @@ internal void LogExtraMemoryUsage(int amountOfExtraMemory) internal readonly DeserializationOptions options; internal readonly PSRemotingCryptoHelper cryptoHelper; - internal static int MaxItemsInCimClassCache = 100; + internal static readonly int MaxItemsInCimClassCache = 100; internal readonly CimClassDeserializationCache cimClassSerializationIdCache = new CimClassDeserializationCache(); } @@ -366,6 +407,7 @@ internal void AddCimClassToCache(TKey key, CimClass cimClass) { _cimClassIdToClass.Clear(); } + _cimClassIdToClass.Add(key, cimClass); /* PRINTF DEBUG @@ -415,6 +457,7 @@ internal void AddClassToCache(TKey key) { _cimClassesHeldByDeserializer.Clear(); } + _cimClassesHeldByDeserializer.Add(key); /* PRINTF DEBUG @@ -436,13 +479,16 @@ public CimClassSerializationId(string className, string namespaceName, string co } public string ClassName { get { return this.Item1; } } + public string NamespaceName { get { return this.Item2; } } + public string ComputerName { get { return this.Item3; } } + public int ClassHashCode { get { return this.Item4; } } } /// - /// This class provides functionality for deserializing a PSObject + /// This class provides functionality for deserializing a PSObject. /// internal class Deserializer { @@ -453,9 +499,9 @@ internal class Deserializer private readonly DeserializationContext _context; /// - /// Creates a Deserializer using default deserialization context + /// Creates a Deserializer using default deserialization context. /// - /// reader to be used for deserialization + /// Reader to be used for deserialization. /// /// Thrown when the xml is in an incorrect format /// @@ -465,10 +511,10 @@ internal Deserializer(XmlReader reader) } /// - /// Creates a Deserializer using specified serialization context + /// Creates a Deserializer using specified serialization context. /// - /// reader to be used for deserialization - /// serialization context + /// Reader to be used for deserialization. + /// Serialization context. /// /// Thrown when the xml is in an incorrect format /// @@ -476,7 +522,7 @@ internal Deserializer(XmlReader reader, DeserializationContext context) { if (reader == null) { - throw PSTraceSource.NewArgumentNullException("reader"); + throw PSTraceSource.NewArgumentNullException(nameof(reader)); } _reader = reader; @@ -516,6 +562,7 @@ private static void ReportExceptionForETW(XmlException exception) internal TypeTable TypeTable { get { return _deserializer.TypeTable; } + set { _deserializer.TypeTable = value; } } @@ -529,9 +576,9 @@ private void Start() Dbg.Assert(_reader.NodeType == XmlNodeType.None, "When deserialization starts we should have XmlReader.NodeType == None"); _reader.Read(); - //If version is not provided, we assume it is the default + // 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; } @@ -541,27 +588,28 @@ private void Start() _reader.MoveToContent(); Dbg.Assert(_reader.EOF || (_reader.NodeType == XmlNodeType.Element), "When deserialization starts reading we should have XmlReader.NodeType == Element"); - //Read version attribute and validate it. + // Read version attribute and validate it. string versionAttribute = _reader.GetAttribute(SerializationStrings.VersionAttribute); if (versionAttribute != null) { version = versionAttribute; } - //If the root element tag is empty, there are no objects to read. + // If the root element tag is empty, there are no objects to read. if (!_deserializer.ReadStartElementAndHandleEmpty(SerializationStrings.RootElementTag)) { _done = true; } } + _deserializer.ValidateVersion(version); } 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; } @@ -578,10 +626,12 @@ internal bool Done() ReportExceptionForETW(exception); throw; } + _done = true; } } } + return _done; } @@ -605,7 +655,7 @@ internal object Deserialize() /// /// Deserializes next object. /// - /// stream the object belongs to (i.e. "Error", "Output", etc.) + /// Stream the object belongs to (i.e. "Error", "Output", etc.). /// /// Thrown when the xml is in an incorrect format /// @@ -632,7 +682,7 @@ internal object Deserialize(out string streamName) #region Helper methods for dealing with "Deserialized." prefix /// - /// Adds "Deserialized." prefix to passed in argument if not already present + /// Adds "Deserialized." prefix to passed in argument if not already present. /// /// internal static void AddDeserializationPrefix(ref string type) @@ -649,13 +699,14 @@ 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) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } + if (o == null) { return false; @@ -669,13 +720,14 @@ 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) { - throw PSTraceSource.NewArgumentNullException("type"); + throw PSTraceSource.NewArgumentNullException(nameof(type)); } + if (o == null) { return false; @@ -720,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. /// /// @@ -730,7 +782,7 @@ internal static string MaskDeserializationPrefix(string typeName) /// internal static Collection MaskDeserializationPrefix(Collection typeNames) { - Dbg.Assert(null != typeNames, "typeNames cannot be null"); + Dbg.Assert(typeNames != null, "typeNames cannot be null"); bool atleastOneDeserializedTypeFound = false; @@ -767,7 +819,7 @@ internal static Collection MaskDeserializationPrefix(Collection } /// - /// Types of known type container supported by monad + /// Types of known type container supported by monad. /// internal enum ContainerType { @@ -777,7 +829,7 @@ internal enum ContainerType List, Enumerable, None - }; + } /// /// This internal helper class provides methods for serializing mshObject. @@ -789,12 +841,12 @@ internal class InternalSerializer internal const string DefaultVersion = "1.1.0.1"; /// - /// Xml writer to be used + /// Xml writer to be used. /// private readonly XmlWriter _writer; /// - /// Serialization context + /// Serialization context. /// private readonly SerializationContext _context; @@ -826,6 +878,7 @@ internal InternalSerializer(XmlWriter writer, SerializationContext context) { objectRefIdDictionary = new WeakReferenceDictionary(); } + _objectRefIdHandler = new ReferenceIdHandlerForSerializer(objectRefIdDictionary); _typeRefIdHandler = new ReferenceIdHandlerForSerializer( @@ -842,15 +895,16 @@ internal InternalSerializer(XmlWriter writer, SerializationContext context) internal TypeTable TypeTable { get { return _typeTable; } + set { _typeTable = value; } } /// - /// Writes the start of root element + /// Writes the start of root element. /// 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); @@ -858,21 +912,22 @@ internal void Start() } /// - /// Writes the end of root element + /// Writes the end of root element. /// internal void End() { - if (SerializationOptions.NoRootElement != (_context.options & SerializationOptions.NoRootElement)) + if ((_context.options & SerializationOptions.NoRootElement) != SerializationOptions.NoRootElement) { _writer.WriteEndElement(); } + _writer.Flush(); } private bool _isStopping = false; /// - /// Called from a separate thread will stop the serialization process + /// Called from a separate thread will stop the serialization process. /// internal void Stop() { @@ -889,7 +944,7 @@ private void CheckIfStopping() internal static bool IsPrimitiveKnownType(Type input) { - //Check if source is of primitive known type + // Check if source is of primitive known type TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfo(input); return (pktInfo != null); } @@ -946,8 +1001,8 @@ int depth return; } - //Object is not of primitive known type. Check if this has - //already been serialized. + // Object is not of primitive known type. Check if this has + // already been serialized. string refId = _objectRefIdHandler.GetRefId(source); if (refId != null) { @@ -960,16 +1015,16 @@ int depth return; } - //Note: We do not use containers in depth calculation. i.e even if the - //current depth is zero, we serialize the container. All contained items will - //get serialized with depth zero. + // Note: We do not use containers in depth calculation. i.e even if the + // current depth is zero, we serialize the container. All contained items will + // get serialized with depth zero. if (HandleKnownContainerTypes(source, streamName, property, depth)) { return; } PSObject mshSource = PSObject.AsPSObject(source); - //If depth is zero, complex type should be serialized as string. + // If depth is zero, complex type should be serialized as string. if (depth == 0 || SerializeAsString(mshSource)) { HandlePSObjectAsString(mshSource, streamName, property, depth); @@ -1021,13 +1076,14 @@ string property { Dbg.Assert(source != null, "caller should validate the parameter"); - //Check if source is of primitive known type + // Check if source is of primitive known type TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfo(source.GetType()); if (pktInfo != null) { WriteOnePrimitiveKnownType(this, streamName, property, source, pktInfo); return true; } + return false; } @@ -1035,7 +1091,7 @@ string property /// Handles primitive known type by first converting it to a PSObject.In W8, extended /// property data is stored external to PSObject. By converting to PSObject, we will /// be able to retrieve and serialize the extended properties. This is tracked by - /// Win8: 414042 + /// Win8: 414042. /// /// /// @@ -1050,19 +1106,18 @@ int depth ) { Dbg.Assert(source != null, "caller should validate the parameter"); - //Check if source is of primitive known type + // Check if source is of primitive known type TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfo(source.GetType()); if (pktInfo != null) { PSObject pktInfoPSObject = PSObject.AsPSObject(source); return HandlePrimitiveKnownTypePSObject(pktInfoPSObject, streamName, property, depth); } + return false; } - /// - /// /// /// /// @@ -1085,7 +1140,7 @@ private bool HandleSecureString(object source, string streamName, string propert moSource = source as PSObject; } - if (moSource != null && !moSource.immediateBaseObjectIsEmpty) + if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) { // check if source is of type secure string secureString = moSource.ImmediateBaseObject as SecureString; @@ -1093,55 +1148,50 @@ private bool HandleSecureString(object source, string streamName, string propert { // the principle used in serialization is that serialization // never throws, and if something can't be serialized nothing - // is written. So we write the elements only if encryption succeeds - try + // is written. So we write the elements only if encryption succeeds. + // However, in the case for non-Windows where secure string encryption + // is not yet supported, a PSCryptoException will be thrown. + string encryptedString; + if (_context.cryptoHelper != null) { - String encryptedString; - if (_context.cryptoHelper != null) - { - encryptedString = _context.cryptoHelper.EncryptSecureString(secureString); - } - else - { - encryptedString = Microsoft.PowerShell.SecureStringHelper.Protect(secureString); - } + encryptedString = _context.cryptoHelper.EncryptSecureString(secureString); + } + else + { + encryptedString = Microsoft.PowerShell.SecureStringHelper.Protect(secureString); + } - if (property != null) - { - WriteStartElement(SerializationStrings.SecureStringTag); - WriteNameAttribute(property); - } - else - { - WriteStartElement(SerializationStrings.SecureStringTag); - } + if (property != null) + { + WriteStartElement(SerializationStrings.SecureStringTag); + WriteNameAttribute(property); + } + else + { + WriteStartElement(SerializationStrings.SecureStringTag); + } - if (streamName != null) - { - WriteAttribute(SerializationStrings.StreamNameAttribute, streamName); - } + if (streamName != null) + { + WriteAttribute(SerializationStrings.StreamNameAttribute, streamName); + } - //Note: We do not use WriteRaw for serializing secure string. WriteString - //does necessary escaping which may be needed for certain - //characters. - _writer.WriteString(encryptedString); + // Note: We do not use WriteRaw for serializing secure string. WriteString + // does necessary escaping which may be needed for certain + // characters. + _writer.WriteString(encryptedString); - _writer.WriteEndElement(); + _writer.WriteEndElement(); - return true; - } - catch (PSCryptoException) - { - // do nothing - } - } // if (source ... + return true; + } } return false; } /// - /// Serializes PSObject whose base objects are of primitive known type + /// Serializes PSObject whose base objects are of primitive known type. /// /// /// @@ -1162,9 +1212,9 @@ int depth bool sourceHandled = false; PSObject moSource = source as PSObject; - if (moSource != null && !moSource.immediateBaseObjectIsEmpty) + if (moSource != null && !moSource.ImmediateBaseObjectIsEmpty) { - //Check if baseObject is primitive known type + // Check if baseObject is primitive known type object baseObject = moSource.ImmediateBaseObject; TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfo(baseObject.GetType()); if (pktInfo != null) @@ -1173,6 +1223,7 @@ int depth sourceHandled = true; } } + return sourceHandled; } @@ -1191,13 +1242,13 @@ int depth IEnumerable enumerable = null; IDictionary dictionary = null; - //If passed in object is PSObject with no baseobject, return false. - if (mshSource != null && mshSource.immediateBaseObjectIsEmpty) + // If passed in object is PSObject with no baseobject, return false. + if (mshSource != null && mshSource.ImmediateBaseObjectIsEmpty) { return false; } - //Check if source (or baseobject in mshSource) is known container type + // Check if source (or baseobject in mshSource) is known container type SerializationUtilities.GetKnownContainerTypeInfo(mshSource != null ? mshSource.ImmediateBaseObject : source, out ct, out dictionary, out enumerable); @@ -1241,7 +1292,7 @@ int depth // So on roundtrip it will show up as List. // We serialize properties of enumerable and on deserialization mark the object as Deserialized. // So if object is marked deserialized, we should write properties. - if (ct == ContainerType.Enumerable || (mshSource != null && mshSource.isDeserialized)) + if (ct == ContainerType.Enumerable || (mshSource != null && mshSource.IsDeserialized)) { PSObject sourceAsPSObject = PSObject.AsPSObject(source); PSMemberInfoInternalCollection specificPropertiesToSerialize = SerializationUtilities.GetSpecificPropertiesToSerialize(sourceAsPSObject, AllPropertiesCollection, _typeTable); @@ -1263,7 +1314,7 @@ int depth #region Write PSObject /// - /// Writes PSObject Reference Element + /// Writes PSObject Reference Element. /// private void WritePSObjectReference ( @@ -1279,10 +1330,12 @@ string refId { WriteAttribute(SerializationStrings.StreamNameAttribute, streamName); } + if (property != null) { WriteNameAttribute(property); } + WriteAttribute(SerializationStrings.ReferenceIdAttribute, refId); _writer.WriteEndElement(); } @@ -1447,7 +1500,7 @@ int depth bool isPSObject = false; bool isCimInstance = false; - if (!mshSource.immediateBaseObjectIsEmpty) + if (!mshSource.ImmediateBaseObjectIsEmpty) { do // false loop { @@ -1482,7 +1535,7 @@ int depth bool writeToString = true; if (mshSource.ToStringFromDeserialization == null) // continue to write ToString from deserialized objects, but... { - if (mshSource.immediateBaseObjectIsEmpty) // ... don't write ToString for property bags + if (mshSource.ImmediateBaseObjectIsEmpty) // ... don't write ToString for property bags { writeToString = false; } @@ -1528,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) { @@ -1564,6 +1617,7 @@ private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance c string miXmlString = Encoding.Unicode.GetString(miXmlBytes, 0, miXmlBytes.Length); psoClass.Properties.Add(new PSNoteProperty(InternalDeserializer.CimMiXmlProperty, miXmlString)); } + psoClasses.Reverse(); // @@ -1594,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) { @@ -1617,7 +1671,7 @@ private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance c instanceMetadata.Properties.Add( new PSNoteProperty( InternalDeserializer.CimModifiedProperties, - string.Join(" ", namesOfModifiedProperties))); + string.Join(' ', namesOfModifiedProperties))); } } @@ -1628,8 +1682,8 @@ private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance c /// /// /// - /// if true, TypeName information is written, else not. - /// if not null then ToString information is written + /// If true, TypeName information is written, else not. + /// If not null then ToString information is written. private void WriteStartOfPSObject ( PSObject mshObject, @@ -1642,7 +1696,7 @@ string toStringValue { Dbg.Assert(mshObject != null, "caller should validate the parameter"); - //Write PSObject start element. + // Write PSObject start element. WriteStartElement(SerializationStrings.PSObjectTag); if (streamName != null) @@ -1662,7 +1716,7 @@ string toStringValue if (writeTypeNames) { - //Write TypeNames + // Write TypeNames ConsolidatedString typeNames = mshObject.InternalTypeNames; if (typeNames.Count > 0) { @@ -1671,7 +1725,7 @@ string toStringValue { WriteStartElement(SerializationStrings.TypeNamesTag); - //Create a new refId and write it as attribute + // Create a new refId and write it as attribute string tnRefId = _typeRefIdHandler.SetRefId(typeNames); Dbg.Assert(tnRefId != null, "SetRefId should always succeed for strings"); WriteAttribute(SerializationStrings.ReferenceIdAttribute, tnRefId); @@ -1680,6 +1734,7 @@ string toStringValue { WriteEncodedElementString(SerializationStrings.TypeNamesItemTag, type); } + _writer.WriteEndElement(); } else @@ -1712,6 +1767,7 @@ private static bool PSObjectHasNotes(PSObject source) { return true; } + return false; } @@ -1758,6 +1814,7 @@ bool writeEnclosingMemberSetElementTag { continue; } + int depthOfMember = info.IsInstance ? depth : depth - 1; if (info.MemberType == (info.MemberType & PSMemberTypes.Properties)) @@ -1782,9 +1839,11 @@ bool writeEnclosingMemberSetElementTag enclosingTagWritten = true; WriteStartElement(SerializationStrings.MemberSet); } + WriteMemberSet((PSMemberSet)info, depthOfMember); } } + if (enclosingTagWritten) { _writer.WriteEndElement(); @@ -1806,6 +1865,7 @@ int depth { return; } + WriteStartElement(SerializationStrings.MemberSet); WriteNameAttribute(set.Name); WriteMemberInfoCollection(set.Members, depth, false); @@ -1817,7 +1877,7 @@ int depth #region properties /// - /// Serializes properties of PSObject + /// Serializes properties of PSObject. /// private void WritePSObjectProperties ( @@ -1828,7 +1888,7 @@ IEnumerable specificPropertiesToSerialize { Dbg.Assert(source != null, "caller should validate the information"); - //Depth available for each property is one less + // Depth available for each property is one less --depth; Dbg.Assert(depth >= 0, "depth should be greater or equal to zero"); @@ -1856,7 +1916,7 @@ private void SerializeInstanceProperties int depth ) { - //Serialize instanceMembers + // Serialize instanceMembers Dbg.Assert(source != null, "caller should validate the information"); PSMemberInfoCollection instanceMembers = source.InstanceMembers; if (instanceMembers != null) @@ -1866,21 +1926,23 @@ int depth } private Collection> _extendedMembersCollection; + private Collection> ExtendedMembersCollection { - get { - return _extendedMembersCollection ?? - (_extendedMembersCollection = - PSObject.GetMemberCollection(PSMemberViewTypes.Extended, _typeTable)); + get + { + return _extendedMembersCollection ??= + PSObject.GetMemberCollection(PSMemberViewTypes.Extended, _typeTable); } } private Collection> _allPropertiesCollection; + private Collection> AllPropertiesCollection { - get { - return _allPropertiesCollection ?? - (_allPropertiesCollection = PSObject.GetPropertyCollection(PSMemberViewTypes.All, _typeTable)); + get + { + return _allPropertiesCollection ??= PSObject.GetPropertyCollection(PSMemberViewTypes.All, _typeTable); } } @@ -1932,7 +1994,7 @@ IEnumerable specificPropertiesToSerialize } /// - /// Serializes properties from collection + /// Serializes properties from collection. /// /// /// Collection of properties to serialize @@ -1956,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; } @@ -1989,7 +2050,7 @@ int depth #region enumerable and dictionary /// - /// Serializes IEnumerable + /// Serializes IEnumerable. /// /// /// enumerable which is serialized @@ -2007,7 +2068,7 @@ int depth Dbg.Assert(enumerable != null, "caller should validate the parameter"); Dbg.Assert(!string.IsNullOrEmpty(tag), "caller should validate the parameter"); - //Start element + // Start element WriteStartElement(tag); IEnumerator enumerator = null; @@ -2020,7 +2081,7 @@ int depth } catch (System.NotSupportedException) { - //ignore exceptions thrown when the enumerator doesn't support Reset() method as in win8:948569 + // ignore exceptions thrown when the enumerator doesn't support Reset() method as in win8:948569 } } catch (Exception exception) @@ -2035,8 +2096,8 @@ int depth enumerator = null; } - //AD has incorrect implementation of IEnumerable where they returned null - //for GetEnumerator instead of empty enumerator + // AD has incorrect implementation of IEnumerable where they returned null + // for GetEnumerator instead of empty enumerator if (enumerator != null) { while (true) @@ -2064,17 +2125,18 @@ int depth break; } + WriteOneObject(item, null, null, depth); } } - //End element + // End element _writer.WriteEndElement(); } /// - /// Serializes IDictionary + /// Serializes IDictionary. /// - /// dictionary which is serialized + /// Dictionary which is serialized. /// /// private void WriteDictionary @@ -2087,7 +2149,7 @@ int depth Dbg.Assert(dictionary != null, "caller should validate the parameter"); Dbg.Assert(!string.IsNullOrEmpty(tag), "caller should validate the parameter"); - //Start element + // Start element WriteStartElement(tag); IDictionaryEnumerator dictionaryEnum = null; @@ -2136,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); @@ -2145,7 +2210,7 @@ int depth } } - //End element + // End element _writer.WriteEndElement(); } @@ -2178,11 +2243,9 @@ private void HandlePSObjectAsString( /// This string is used for serializing the PSObject at depth 0 /// or when pso.SerializationMethod == SerializationMethod.String. /// - /// /// /// PSObject to be converted to string /// - /// /// /// string value to use for serializing this PSObject. /// @@ -2225,10 +2288,10 @@ private string GetSerializationString(PSObject source) /// /// Reads the information the PSObject /// and returns true if this object should be serialized as - /// string + /// string. /// - /// PSObject to be serialized - /// true if the object needs to be serialized as a string + /// PSObject to be serialized. + /// True if the object needs to be serialized as a string. private bool SerializeAsString(PSObject source) { SerializationMethod method = source.GetSerializationMethod(_typeTable); @@ -2251,10 +2314,10 @@ private bool SerializeAsString(PSObject source) #endregion serialize as string /// - /// compute the serialization depth for an PSObject instance subtree + /// Compute the serialization depth for an PSObject instance subtree. /// - /// PSObject whose serialization depth has to be computed - /// current depth + /// PSObject whose serialization depth has to be computed. + /// Current depth. /// private int GetDepthOfSerialization(object source, int depth) { @@ -2286,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 @@ -2306,9 +2369,9 @@ private int GetDepthOfSerialization(object source, int depth) } } - if (0 != (_context.options & SerializationOptions.PreserveSerializationSettingOfOriginal)) + if ((_context.options & SerializationOptions.PreserveSerializationSettingOfOriginal) != 0) { - if ((pso.isDeserialized) && (depth <= 0)) + if ((pso.IsDeserialized) && (depth <= 0)) { return 1; } @@ -2318,7 +2381,7 @@ private int GetDepthOfSerialization(object source, int depth) } /// - /// Writes null + /// Writes null. /// /// /// @@ -2335,19 +2398,20 @@ private void WriteNull(string streamName, string property) { WriteNameAttribute(property); } + _writer.WriteEndElement(); } #region known type serialization /// - /// Writes raw string as item or property in Monad namespace + /// Writes raw string as item or property in Monad namespace. /// /// The serializer to which the object is serialized. - /// name of the stream to write. Do not write if null. - /// name of property. Pass null for item - /// string to write - /// serialization information + /// Name of the stream to write. Do not write if null. + /// Name of property. Pass null for item. + /// String to write. + /// Serialization information. private static void WriteRawString ( InternalSerializer serializer, @@ -2381,13 +2445,13 @@ TypeSerializationInfo entry } /// - /// Writes an item or property in Monad namespace + /// Writes an item or property in Monad namespace. /// /// The serializer to which the object is serialized. /// - /// name of property. Pass null for item - /// object to be written - /// serialization information about source + /// Name of property. Pass null for item. + /// Object to be written. + /// Serialization information about source. private static void WriteOnePrimitiveKnownType ( InternalSerializer serializer, @@ -2416,13 +2480,13 @@ TypeSerializationInfo entry } /// - /// Writes DateTime as item or property + /// Writes DateTime as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// DateTime to write - /// serialization information about source + /// Name of property. pass null for item. + /// DateTime to write. + /// Serialization information about source. internal static void WriteDateTime(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2433,13 +2497,13 @@ internal static void WriteDateTime(InternalSerializer serializer, string streamN } /// - /// Writes Version + /// Writes Version. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// Version to write - /// serialization information about source + /// Name of property. pass null for item. + /// Version to write. + /// Serialization information about source. internal static void WriteVersion(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2451,13 +2515,13 @@ internal static void WriteVersion(InternalSerializer serializer, string streamNa } /// - /// Writes SemanticVersion + /// Writes SemanticVersion. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// Version to write - /// serialization information about source + /// Name of property. pass null for item. + /// Version to write. + /// Serialization information about source. internal static void WriteSemanticVersion(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2469,13 +2533,13 @@ internal static void WriteSemanticVersion(InternalSerializer serializer, string } /// - /// Serialize scriptblock as item or property + /// Serialize scriptblock as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// scriptblock to write - /// serialization information about source + /// Name of property. pass null for item. + /// Scriptblock to write. + /// Serialization information about source. internal static void WriteScriptBlock(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2487,13 +2551,13 @@ internal static void WriteScriptBlock(InternalSerializer serializer, string stre } /// - /// Serialize URI as item or property + /// Serialize URI as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// URI to write - /// serialization information about source + /// Name of property. pass null for item. + /// URI to write. + /// Serialization information about source. internal static void WriteUri(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2504,15 +2568,14 @@ internal static void WriteUri(InternalSerializer serializer, string streamName, WriteEncodedString(serializer, streamName, property, Convert.ToString(source, CultureInfo.InvariantCulture), entry); } - /// - /// Serialize string as item or property + /// Serialize string as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// string to write - /// serialization information about source + /// Name of property. pass null for item. + /// String to write. + /// Serialization information about source. internal static void WriteEncodedString(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2535,9 +2598,9 @@ internal static void WriteEncodedString(InternalSerializer serializer, string st serializer.WriteAttribute(SerializationStrings.StreamNameAttribute, streamName); } - //Note: We do not use WriteRaw for serializing string. WriteString - //does necessary escaping which may be needed for certain - //characters. + // Note: We do not use WriteRaw for serializing string. WriteString + // does necessary escaping which may be needed for certain + // characters. Dbg.Assert(source is string, "Caller should verify that typeof(source) is String"); string s = (string)source; string encoded = EncodeString(s); @@ -2547,65 +2610,65 @@ internal static void WriteEncodedString(InternalSerializer serializer, string st } /// - /// Writes Double as item or property + /// Writes Double as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// Double to write - /// serialization information about source + /// Name of property. pass null for item. + /// Double to write. + /// Serialization information about source. internal static void WriteDouble(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); Dbg.Assert(source != null, "caller should have validated the information"); Dbg.Assert(entry != null, "caller should have validated the information"); - WriteRawString(serializer, streamName, property, XmlConvert.ToString((Double)source), entry); + WriteRawString(serializer, streamName, property, XmlConvert.ToString((double)source), entry); } /// - /// Writes Char as item or property + /// Writes Char as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// Char to write - /// serialization information about source + /// Name of property. pass null for item. + /// Char to write. + /// Serialization information about source. internal static void WriteChar(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); Dbg.Assert(source != null, "caller should have validated the information"); Dbg.Assert(entry != null, "caller should have validated the information"); - //Char is defined as unsigned short in schema - WriteRawString(serializer, streamName, property, XmlConvert.ToString((UInt16)(Char)source), entry); + // Char is defined as unsigned short in schema + WriteRawString(serializer, streamName, property, XmlConvert.ToString((UInt16)(char)source), entry); } /// - /// Writes Boolean as item or property + /// Writes Boolean as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// Boolean to write - /// serialization information about source + /// Name of property. pass null for item. + /// Boolean to write. + /// Serialization information about source. internal static void WriteBoolean(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); Dbg.Assert(source != null, "caller should have validated the information"); Dbg.Assert(entry != null, "caller should have validated the information"); - WriteRawString(serializer, streamName, property, XmlConvert.ToString((Boolean)source), entry); + WriteRawString(serializer, streamName, property, XmlConvert.ToString((bool)source), entry); } /// - /// Writes Single as item or property + /// Writes Single as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// single to write - /// serialization information about source + /// Name of property. pass null for item. + /// Single to write. + /// Serialization information about source. internal static void WriteSingle(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2616,13 +2679,13 @@ internal static void WriteSingle(InternalSerializer serializer, string streamNam } /// - /// Writes TimeSpan as item or property + /// Writes TimeSpan as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// DateTime to write - /// serialization information about source + /// Name of property. pass null for item. + /// DateTime to write. + /// Serialization information about source. internal static void WriteTimeSpan(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); @@ -2633,20 +2696,20 @@ internal static void WriteTimeSpan(InternalSerializer serializer, string streamN } /// - /// Writes Single as item or property + /// Writes Single as item or property. /// /// The serializer to which the object is serialized. /// - /// name of property. pass null for item - /// bytearray to write - /// serialization information about source + /// Name of property. pass null for item. + /// Bytearray to write. + /// Serialization information about source. internal static void WriteByteArray(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry) { Dbg.Assert(serializer != null, "caller should have validated the information"); Dbg.Assert(source != null, "caller should have validated the information"); Dbg.Assert(entry != null, "caller should have validated the information"); - Byte[] bytes = (Byte[])source; + byte[] bytes = (byte[])source; if (property != null) { serializer.WriteStartElement(entry.PropertyTag); @@ -2688,6 +2751,7 @@ internal static void WriteProgressRecord(InternalSerializer serializer, string s { serializer.WriteNameAttribute(property); } + if (streamName != null) { serializer.WriteAttribute(SerializationStrings.StreamNameAttribute, streamName); @@ -2719,13 +2783,13 @@ internal static void WriteSecureString(InternalSerializer serializer, string str #region misc /// - /// Writes start element in Monad namespace + /// Writes start element in Monad namespace. /// - /// tag of element + /// Tag of element. 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); } @@ -2736,10 +2800,10 @@ private void WriteStartElement(string elementTag) } /// - /// Writes attribute in monad namespace + /// Writes attribute in monad namespace. /// - /// name of attribute - /// value of attribute + /// Name of attribute. + /// Value of attribute. private void WriteAttribute(string name, string value) { Dbg.Assert(!string.IsNullOrEmpty(name), "Caller should validate the parameter"); @@ -2758,8 +2822,8 @@ private void WriteNameAttribute(string value) /// /// Encodes the string to escape characters which would make XmlWriter.WriteString throw an exception. /// - /// string to encode - /// encoded string + /// String to encode. + /// Encoded string. /// /// Output from this method can be reverted using XmlConvert.DecodeName method /// (or InternalDeserializer.DecodeString). @@ -2803,11 +2867,11 @@ 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 - /// encoded string + /// String to encode. + /// IndexOfFirstEncodableCharacter. + /// Encoded string. private static string EncodeString(string s, int indexOfFirstEncodableCharacter) { Dbg.Assert(s != null, "Caller should validate the 's' parameter"); @@ -2866,12 +2930,12 @@ private static string EncodeString(string s, int indexOfFirstEncodableCharacter) rlen += 7; } } - return new String(result, 0, rlen); - } + return new string(result, 0, rlen); + } /// - /// Writes element string in monad namespace + /// Writes element string in monad namespace. /// /// /// @@ -2883,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); } @@ -2904,12 +2968,12 @@ internal class InternalDeserializer #region constructor /// - /// XmlReader from which object is deserialized + /// XmlReader from which object is deserialized. /// private readonly XmlReader _reader; /// - /// Deserialization context + /// Deserialization context. /// private readonly DeserializationContext _context; @@ -2919,19 +2983,23 @@ internal class InternalDeserializer private TypeTable _typeTable; /// - /// If true, unknowntags are allowed inside PSObject + /// If true, unknowntags are allowed inside PSObject. /// private bool UnknownTagsAllowed { get { Dbg.Assert(_version.Major <= 1, "Deserializer assumes clixml version is <= 1.1"); - //If minor version is greater than 1, it means that there can be - //some unknown tags in xml. Deserialization should ignore such element. + // If minor version is greater than 1, it means that there can be + // some unknown tags in xml. Deserialization should ignore such element. return (_version.Minor > 1); } } + [SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Accesses instance members in preprocessor branch.")] private bool DuplicateRefIdsAllowed { get @@ -2947,12 +3015,12 @@ private bool DuplicateRefIdsAllowed } /// - /// Depth below top level - used to prevent stack overflow during deserialization + /// Depth below top level - used to prevent stack overflow during deserialization. /// private int _depthBelowTopLevel; /// - /// Version declared by the clixml being read + /// Version declared by the clixml being read. /// private Version _version; @@ -2962,7 +3030,6 @@ private bool DuplicateRefIdsAllowed private readonly ReferenceIdHandlerForDeserializer _typeRefIdHandler; /// - /// /// /// /// @@ -2981,22 +3048,22 @@ 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 { - typeof(Boolean), + typeof(bool), typeof(byte), typeof(char), typeof(DateTime), - typeof(Decimal), - typeof(Double), + typeof(decimal), + typeof(double), typeof(Int16), typeof(Int32), typeof(Int64), - typeof(SByte), + typeof(sbyte), typeof(Single), - typeof(String), + typeof(string), typeof(TimeSpan), typeof(UInt16), typeof(UInt32), @@ -3017,6 +3084,7 @@ internal InternalDeserializer(XmlReader reader, DeserializationContext context) internal TypeTable TypeTable { get { return _typeTable; } + set { _typeTable = value; } } @@ -3045,20 +3113,21 @@ internal void ValidateVersion(string version) { exceptionToRethrow = e; } + if (exceptionToRethrow != null) { throw NewXmlException(Serialization.InvalidVersion, exceptionToRethrow); } - //Versioning Note:Future version of serialization can add new known types. - //This version will ignore those known types, if they are base object. - //It is expected that future version will still put information in base - //and adapter properties which this serializer can read and use. - //For example, assume the version 2 serialization engine supports a new known - //type IPAddress. The version 1 deserializer doesn't know IPAddress as known - //type and it must retrieve it as an PSObject. The version 2 serializer - //can serialize this as follows: - // + // Versioning Note:Future version of serialization can add new known types. + // This version will ignore those known types, if they are base object. + // It is expected that future version will still put information in base + // and adapter properties which this serializer can read and use. + // For example, assume the version 2 serialization engine supports a new known + // type IPAddress. The version 1 deserializer doesn't know IPAddress as known + // type and it must retrieve it as an PSObject. The version 2 serializer + // can serialize this as follows: + // // ... // // 120.23.35.53 @@ -3067,13 +3136,13 @@ internal void ValidateVersion(string version) // 120.23.34.53 // A // - // + // // In above example, V1 serializer will ignore element and read // properties from // V2 serializer can read tag and ignore properties. // Read serialization note doc for information. - //Now validate the major version number is 1 + // Now validate the major version number is 1 if (_version.Major != 1) { throw NewXmlException(Serialization.UnexpectedVersion, null, _version.Major); @@ -3085,7 +3154,7 @@ private object ReadOneDeserializedObject(out string streamName, out bool isKnown if (_reader.NodeType != XmlNodeType.Element) { throw NewXmlException(Serialization.InvalidNodeType, null, - _reader.NodeType.ToString(), XmlNodeType.Element.ToString()); + _reader.NodeType.ToString(), nameof(XmlNodeType.Element)); } s_trace.WriteLine("Processing start node {0}", _reader.LocalName); @@ -3093,14 +3162,14 @@ private object ReadOneDeserializedObject(out string streamName, out bool isKnown streamName = _reader.GetAttribute(SerializationStrings.StreamNameAttribute); isKnownPrimitiveType = false; - //handle nil node + // handle nil node if (IsNextElement(SerializationStrings.NilTag)) { Skip(); return null; } - //Handle reference to previous deserialized object. + // Handle reference to previous deserialized object. if (IsNextElement(SerializationStrings.ReferenceTag)) { string refId = _reader.GetAttribute(SerializationStrings.ReferenceIdAttribute); @@ -3120,7 +3189,7 @@ private object ReadOneDeserializedObject(out string streamName, out bool isKnown return duplicate; } - //Handle primitive known types + // Handle primitive known types TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfoFromItemTag(_reader.LocalName); if (pktInfo != null) { @@ -3129,15 +3198,15 @@ private object ReadOneDeserializedObject(out string streamName, out bool isKnown return ReadPrimaryKnownType(pktInfo); } - //Handle PSObject + // Handle PSObject if (IsNextElement(SerializationStrings.PSObjectTag)) { s_trace.WriteLine("PSObject Element"); return ReadPSObject(); } - //If we are here, we have an unknown node. Unknown nodes may - //be allowed inside PSObject. We do not allow them at top level. + // If we are here, we have an unknown node. Unknown nodes may + // be allowed inside PSObject. We do not allow them at top level. s_trace.TraceError("Invalid element {0} tag found", _reader.LocalName); throw NewXmlException(Serialization.InvalidElementTag, null, _reader.LocalName); @@ -3146,7 +3215,7 @@ private object ReadOneDeserializedObject(out string streamName, out bool isKnown private bool _isStopping = false; /// - /// Called from a separate thread will stop the serialization process + /// Called from a separate thread will stop the serialization process. /// internal void Stop() { @@ -3170,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) @@ -3185,6 +3254,7 @@ private bool RehydrateCimInstanceProperty( { cimInstance.SetCimSessionComputerName(psComputerNameValue); } + return true; } @@ -3205,27 +3275,33 @@ private bool RehydrateCimInstanceProperty( { return false; } + string originalArrayTypeName = Deserializer.MaskDeserializationPrefix(psoPropertyValue.InternalTypeNames[0]); if (originalArrayTypeName == null) { return false; } + Type originalArrayType; if (!LanguagePrimitives.TryConvertTo(originalArrayTypeName, CultureInfo.InvariantCulture, out originalArrayType)) { return false; } + if (!originalArrayType.IsArray || !s_knownCimArrayTypes.Value.Contains(originalArrayType.GetElementType())) { return false; } + object newPropertyValue; if (!LanguagePrimitives.TryConvertTo(propertyValue, originalArrayType, CultureInfo.InvariantCulture, out newPropertyValue)) { return false; } + psoPropertyValue = PSObject.AsPSObject(newPropertyValue); } + propertyValue = psoPropertyValue.BaseObject; } @@ -3264,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) { @@ -3294,47 +3370,51 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) { return null; } + 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; } + var hashCodeObject = hashCodeProperty.Value; if (hashCodeObject == null) { return null; } + if (hashCodeObject is PSObject) { hashCodeObject = ((PSObject)hashCodeObject).BaseObject; } - if (!(hashCodeObject is int)) + + if (hashCodeObject is not int) { return null; } + int hashCode = (int)hashCodeObject; CimClassSerializationId cimClassSerializationId = new CimClassSerializationId(cimClassName, cimNamespace, computerName, hashCode); @@ -3349,6 +3429,7 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) { return null; } + string miXmlString = miXmlProperty.Value.ToString(); byte[] miXmlBytes = Encoding.Unicode.GetBytes(miXmlString); uint offset = 0; @@ -3383,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; } @@ -3394,6 +3475,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) { return deserializedObject; } + CimInstance cimInstance; try { @@ -3403,6 +3485,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) { return deserializedObject; } + PSObject psoCimInstance = PSObject.AsPSObject(cimInstance); // process __InstanceMetadata @@ -3416,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); } @@ -3424,12 +3507,11 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) } // process properties that were originally "adapted" properties - if (deserializedObject.adaptedMembers != null) + if (deserializedObject.AdaptedMembers != null) { - foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.adaptedMembers) + foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.AdaptedMembers) { - PSPropertyInfo deserializedProperty = deserializedMemberInfo as PSPropertyInfo; - if (deserializedProperty == null) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -3449,14 +3531,13 @@ 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; } // skip adapted properties - if ((deserializedObject.adaptedMembers != null) && (deserializedObject.adaptedMembers[deserializedProperty.Name] != null)) + if ((deserializedObject.AdaptedMembers != null) && (deserializedObject.AdaptedMembers[deserializedProperty.Name] != null)) { continue; } @@ -3503,7 +3584,7 @@ internal object ReadOneObject(out string streamName) bool isKnownPrimitiveType; object result = ReadOneDeserializedObject(out streamName, out isKnownPrimitiveType); - if (null == result) + if (result == null) { return null; } @@ -3518,7 +3599,7 @@ internal object ReadOneObject(out string streamName) // Convert deserialized object to a user-defined type (specified in a types.ps1xml file) Type targetType = mshSource.GetTargetTypeForDeserialization(_typeTable); - if (null != targetType) + if (targetType != null) { Exception rehydrationException = null; try @@ -3571,20 +3652,20 @@ private object ReadOneObject() return ReadOneObject(out ignore); } - //Reads one PSObject + // Reads one PSObject private PSObject ReadPSObject() { PSObject dso = ReadAttributeAndCreatePSObject(); - //Read start element tag - if (ReadStartElementAndHandleEmpty(SerializationStrings.PSObjectTag) == false) + // Read start element tag + if (!ReadStartElementAndHandleEmpty(SerializationStrings.PSObjectTag)) { - //Empty element. + // Empty element. return dso; } bool overrideTypeInfo = true; - //Process all the child nodes + // Process all the child nodes while (_reader.NodeType == XmlNodeType.Element) { if (IsNextElement(SerializationStrings.TypeNamesTag) || @@ -3604,7 +3685,7 @@ private PSObject ReadPSObject() else if (IsNextElement(SerializationStrings.ToStringElementTag)) { dso.ToStringFromDeserialization = ReadDecodedElementString(SerializationStrings.ToStringElementTag); - dso.InstanceMembers.Add(PSObject.dotNetInstanceAdapter.GetDotNetMethod(dso, "ToString")); + dso.InstanceMembers.Add(PSObject.DotNetInstanceAdapter.GetDotNetMethod(dso, "ToString")); PSGetMemberBinder.SetHasInstanceMember("ToString"); // Fix for Win8:75437 // The TokenText property is used in type conversion and it is not being populated during deserialization @@ -3616,11 +3697,11 @@ private PSObject ReadPSObject() } else { - //Handle BaseObject + // Handle BaseObject object baseObject = null; ContainerType ct = ContainerType.None; - //Check if tag is PrimaryKnownType. + // Check if tag is PrimaryKnownType. TypeSerializationInfo pktInfo = KnownTypes.GetTypeSerializationInfoFromItemTag(_reader.LocalName); if (pktInfo != null) { @@ -3630,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)) { @@ -3639,7 +3720,7 @@ private PSObject ReadPSObject() } else { - //We have an unknown tag + // We have an unknown tag s_trace.WriteLine("Unknown tag {0} encountered", _reader.LocalName); if (UnknownTagsAllowed) { @@ -3650,6 +3731,7 @@ private PSObject ReadPSObject() throw NewXmlException(Serialization.InvalidElementTag, null, _reader.LocalName); } } + if (baseObject != null) { dso.SetCoreOnDeserialization(baseObject, overrideTypeInfo); @@ -3670,25 +3752,26 @@ private PSObject ReadPSObject() /// /// This function reads the refId attribute and creates a - /// mshObject for that attribute + /// mshObject for that attribute. /// - /// mshObject which is created for refId + /// MshObject which is created for refId. private PSObject ReadAttributeAndCreatePSObject() { string refId = _reader.GetAttribute(SerializationStrings.ReferenceIdAttribute); PSObject sh = new PSObject(); - //RefId is not mandatory attribute + // RefId is not mandatory attribute if (refId != null) { s_trace.WriteLine("Read PSObject with refId: {0}", refId); _objectRefIdHandler.SetRefId(sh, refId, this.DuplicateRefIdsAllowed); } + return sh; } /// - /// Read type names + /// Read type names. /// /// /// PSObject to which TypeNames are added @@ -3702,7 +3785,7 @@ private void ReadTypeNames(PSObject dso) { Collection typeNames = new Collection(); - //Read refId attribute if available + // Read refId attribute if available string refId = _reader.GetAttribute(SerializationStrings.ReferenceIdAttribute); s_trace.WriteLine("Processing TypeNamesTag with refId {0}", refId); @@ -3725,8 +3808,10 @@ private void ReadTypeNames(PSObject dso) throw NewXmlException(Serialization.InvalidElementTag, null, _reader.LocalName); } } + ReadEndElement(); } + dso.InternalTypeNames = new ConsolidatedString(typeNames); if (refId != null) @@ -3760,8 +3845,7 @@ private void ReadTypeNames(PSObject dso) ); dso.InternalTypeNames = new ConsolidatedString(typeNames); - - //Skip the node + // Skip the node Skip(); } else @@ -3771,35 +3855,36 @@ private void ReadTypeNames(PSObject dso) } /// - /// Read properties + /// Read properties. /// private void ReadProperties(PSObject dso) { Dbg.Assert(dso != null, "caller should validate the parameter"); Dbg.Assert(_reader.NodeType == XmlNodeType.Element, "NodeType should be Element"); - //Since we are adding baseobject properties as propertybag, - //mark the object as deserialized. - dso.isDeserialized = true; - dso.adaptedMembers = new PSMemberInfoInternalCollection(); + // Since we are adding baseobject properties as propertybag, + // mark the object as deserialized. + dso.IsDeserialized = true; + dso.AdaptedMembers = new PSMemberInfoInternalCollection(); - //Add the GetType method to the instance members, so that it works on deserialized psobjects - dso.InstanceMembers.Add(PSObject.dotNetInstanceAdapter.GetDotNetMethod(dso, "GetType")); + // Add the GetType method to the instance members, so that it works on deserialized psobjects + dso.InstanceMembers.Add(PSObject.DotNetInstanceAdapter.GetDotNetMethod(dso, "GetType")); PSGetMemberBinder.SetHasInstanceMember("GetType"); - //Set Clr members to a collection which is empty - dso.clrMembers = new PSMemberInfoInternalCollection(); + // Set Clr members to a collection which is empty + dso.ClrMembers = new PSMemberInfoInternalCollection(); if (ReadStartElementAndHandleEmpty(SerializationStrings.AdapterProperties)) { - //Read one or more property elements + // Read one or more property elements while (_reader.NodeType == XmlNodeType.Element) { string property = ReadNameAttribute(); object value = ReadOneObject(); PSProperty prop = new PSProperty(property, value); - dso.adaptedMembers.Add(prop); + dso.AdaptedMembers.Add(prop); } + ReadEndElement(); } } @@ -3843,7 +3928,7 @@ private void ReadMemberSet(PSMemberInfoCollection collection) } /// - /// read note + /// Read note. /// /// private PSNoteProperty ReadNoteProperty() @@ -3890,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: @@ -3910,7 +3995,7 @@ private object ReadKnownContainer(ContainerType ct) } /// - /// Read List Containers + /// Read List Containers. /// /// private object ReadListContainer(ContainerType ct) @@ -3927,8 +4012,10 @@ private object ReadListContainer(ContainerType ct) { list.Add(ReadOneObject()); } + ReadEndElement(); } + if (ct == ContainerType.Stack) { list.Reverse(); @@ -3938,88 +4025,127 @@ private object ReadListContainer(ContainerType ct) { return new Queue(list); } + return list; } /// - /// Deserialize Dictionary + /// 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) { ReadStartElement(SerializationStrings.DictionaryEntryTag); - //Read Key + // Read Key if (_reader.NodeType != XmlNodeType.Element) { throw NewXmlException(Serialization.DictionaryKeyNotSpecified, null); } + string name = ReadNameAttribute(); - if (string.Compare(name, SerializationStrings.DictionaryKey, StringComparison.OrdinalIgnoreCase) != 0) + if (!string.Equals(name, SerializationStrings.DictionaryKey, StringComparison.OrdinalIgnoreCase)) { throw NewXmlException(Serialization.InvalidDictionaryKeyName, null); } + object key = ReadOneObject(); if (key == null) { throw NewXmlException(Serialization.NullAsDictionaryKey, null); } - //Read Value + // Read Value if (_reader.NodeType != XmlNodeType.Element) { throw NewXmlException(Serialization.DictionaryValueNotSpecified, null); } + name = ReadNameAttribute(); - if (string.Compare(name, SerializationStrings.DictionaryValue, StringComparison.OrdinalIgnoreCase) != 0) + if (!string.Equals(name, SerializationStrings.DictionaryValue, StringComparison.OrdinalIgnoreCase)) { throw NewXmlException(Serialization.InvalidDictionaryValueName, null); } - 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; - } + object value = ReadOneObject(); try { - //Add entry to hashtable - table.Add(key, value); + // Add entry to hashtable + dictionary.Add(key, value); } catch (ArgumentException e) { @@ -4028,10 +4154,11 @@ private object ReadDictionary(ContainerType ct) ReadEndElement(); } + ReadEndElement(); } - return table; + return dictionary.DictionaryObject; } #endregion known containers @@ -4049,7 +4176,7 @@ private static XmlReaderSettings GetXmlReaderSettingsForCliXml() xrs.CheckCharacters = false; xrs.CloseInput = false; - //The XML data needs to be in conformance to the rules for a well-formed XML 1.0 document. + // The XML data needs to be in conformance to the rules for a well-formed XML 1.0 document. xrs.ConformanceLevel = ConformanceLevel.Document; xrs.IgnoreComments = true; xrs.IgnoreProcessingInstructions = true; @@ -4116,6 +4243,7 @@ internal static object DeserializeByte(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(byte).FullName); } @@ -4125,7 +4253,7 @@ internal static object DeserializeChar(InternalDeserializer deserializer) Exception recognizedException = null; try { - return (Char)XmlConvert.ToUInt16(deserializer._reader.ReadElementContentAsString()); + return (char)XmlConvert.ToUInt16(deserializer._reader.ReadElementContentAsString()); } catch (FormatException e) { @@ -4135,6 +4263,7 @@ internal static object DeserializeChar(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(char).FullName); } @@ -4167,6 +4296,7 @@ internal static object DeserializeDecimal(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(decimal).FullName); } @@ -4186,6 +4316,7 @@ internal static object DeserializeDouble(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(double).FullName); } @@ -4208,6 +4339,7 @@ internal static object DeserializeGuid(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Guid).FullName); } @@ -4231,6 +4363,7 @@ internal static object DeserializeVersion(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Version).FullName); } @@ -4254,6 +4387,7 @@ internal static object DeserializeSemanticVersion(InternalDeserializer deseriali { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Version).FullName); } @@ -4273,6 +4407,7 @@ internal static object DeserializeInt16(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Int16).FullName); } @@ -4292,6 +4427,7 @@ internal static object DeserializeInt32(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Int32).FullName); } @@ -4311,6 +4447,7 @@ internal static object DeserializeInt64(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(Int64).FullName); } @@ -4330,6 +4467,7 @@ internal static object DeserializeSByte(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(sbyte).FullName); } @@ -4349,6 +4487,7 @@ internal static object DeserializeSingle(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(float).FullName); } @@ -4356,13 +4495,13 @@ 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); } else { - //Scriptblock is deserialized as string + // Scriptblock is deserialized as string return scriptBlockBody; } } @@ -4402,6 +4541,7 @@ internal static object DeserializeUInt16(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(UInt16).FullName); } @@ -4421,6 +4561,7 @@ internal static object DeserializeUInt32(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(UInt32).FullName); } @@ -4440,6 +4581,7 @@ internal static object DeserializeUInt64(InternalDeserializer deserializer) { recognizedException = e; } + throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(UInt64).FullName); } @@ -4512,6 +4654,7 @@ internal static XmlDocument LoadUnsafeXmlDocument(TextReader textReader, bool pr { settings.MaxCharactersInDocument = maxCharactersInDocument.Value; } + if (preserveNonElements) { settings.IgnoreWhitespace = false; @@ -4586,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); @@ -4602,6 +4745,7 @@ internal static object DeserializeProgressRecord(InternalDeserializer deserializ { recognizedException = e; } + if (recognizedException != null) { throw deserializer.NewXmlException(Serialization.InvalidPrimitiveType, recognizedException, typeof(UInt64).FullName); @@ -4668,31 +4812,32 @@ 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)); } /// - /// Read start element in monad namespace + /// Read start element in monad namespace. /// - /// element tag to read - /// true if not an empty element else false + /// Element tag to read. + /// True if not an empty element else false. internal bool ReadStartElementAndHandleEmpty(string element) { Dbg.Assert(!string.IsNullOrEmpty(element), "Caller should validate the parameter"); - //IsEmpty is set to true when element is of the form + // IsEmpty is set to true when element is of the form bool isEmpty = _reader.IsEmptyElement; this.ReadStartElement(element); - //This takes care of the case: or . In - //this case isEmpty is false. - if (isEmpty == false && _reader.NodeType == XmlNodeType.EndElement) + // This takes care of the case: or . In + // this case isEmpty is false. + if (!isEmpty && _reader.NodeType == XmlNodeType.EndElement) { ReadEndElement(); isEmpty = true; } + return !isEmpty; } @@ -4700,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); } @@ -4708,6 +4853,7 @@ private void ReadStartElement(string element) { _reader.ReadStartElement(element, SerializationStrings.MonadNamespace); } + _reader.MoveToContent(); } @@ -4723,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); } @@ -4731,6 +4877,7 @@ private string ReadDecodedElementString(string element) { temp = _reader.ReadElementContentAsString(element, SerializationStrings.MonadNamespace); } + _reader.MoveToContent(); temp = DecodeString(temp); return temp; @@ -4747,7 +4894,7 @@ private void Skip() } /// - /// Reads Primary known type + /// Reads Primary known type. /// /// /// @@ -4762,7 +4909,7 @@ private object ReadPrimaryKnownType(TypeSerializationInfo pktInfo) private object ReadSecureString() { - String encryptedString = _reader.ReadElementContentAsString(); + string encryptedString = _reader.ReadElementContentAsString(); try { @@ -4786,7 +4933,7 @@ private object ReadSecureString() } /// - /// Helper function for building XmlException + /// Helper function for building XmlException. /// /// /// resource String @@ -4832,6 +4979,7 @@ private string ReadNameAttribute() { throw NewXmlException(Serialization.AttributeExpected, null, SerializationStrings.NameAttribute); } + return DecodeString(encodedName); } @@ -4843,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"); } @@ -4855,7 +5003,7 @@ internal class ReferenceIdHandlerForSerializer where T : class /// /// Get new reference id. /// - /// New reference id + /// New reference id. private UInt64 GetNewReferenceId() { UInt64 refId = _seed++; @@ -4863,7 +5011,7 @@ private UInt64 GetNewReferenceId() } /// - /// Seed is incremented by one after each reference generation + /// Seed is incremented by one after each reference generation. /// private UInt64 _seed; @@ -4878,10 +5026,10 @@ internal ReferenceIdHandlerForSerializer(IDictionary dictionary) } /// - /// Assigns a RefId to the given object + /// Assigns a RefId to the given object. /// - /// object to assign a RefId to - /// RefId assigned to the object + /// Object to assign a RefId to. + /// RefId assigned to the object. internal string SetRefId(T t) { if (_object2refId != null) @@ -4898,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. /// /// /// @@ -4947,11 +5095,11 @@ internal T GetReferencedObject(string refId) } /// - /// A delegate for serializing known type + /// A delegate for serializing known type. /// internal delegate void TypeSerializerDelegate(InternalSerializer serializer, string streamName, string property, object source, TypeSerializationInfo entry); /// - /// A delegate for deserializing known type + /// A delegate for deserializing known type. /// internal delegate object TypeDeserializerDelegate(InternalDeserializer deserializer); @@ -4961,13 +5109,13 @@ internal T GetReferencedObject(string refId) internal class TypeSerializationInfo { /// - /// Constructor + /// Constructor. /// - /// Type for which this entry is created - /// ItemTag for the type - /// PropertyTag for the type - /// TypeSerializerDelegate for serializing the type - /// TypeDeserializerDelegate for deserializing the type + /// Type for which this entry is created. + /// ItemTag for the type. + /// PropertyTag for the type. + /// TypeSerializerDelegate for serializing the type. + /// TypeDeserializerDelegate for deserializing the type. internal TypeSerializationInfo(Type type, string itemTag, string propertyTag, TypeSerializerDelegate serializer, TypeDeserializerDelegate deserializer) { Type = type; @@ -4985,22 +5133,22 @@ internal TypeSerializationInfo(Type type, string itemTag, string propertyTag, Ty internal Type Type { get; } /// - /// Get the item tag for this type + /// Get the item tag for this type. /// internal string ItemTag { get; } /// - /// Get the Property tag for this type + /// Get the Property tag for this type. /// internal string PropertyTag { get; } /// - /// Gets the delegate to serialize this type + /// Gets the delegate to serialize this type. /// internal TypeSerializerDelegate Serializer { get; } /// - /// Gets the delegate to deserialize this type + /// Gets the delegate to deserialize this type. /// internal TypeDeserializerDelegate Deserializer { get; } @@ -5013,13 +5161,13 @@ 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 { /// - /// Static constructor + /// Static constructor. /// static KnownTypes() { @@ -5031,10 +5179,10 @@ static KnownTypes() } /// - /// Gets the type serialization information about a type + /// Gets the type serialization information about a type. /// - /// Type for which information is retrieved - /// TypeSerializationInfo for the type, null if it doesn't exist + /// Type for which information is retrieved. + /// TypeSerializationInfo for the type, null if it doesn't exist. internal static TypeSerializationInfo GetTypeSerializationInfo(Type type) { TypeSerializationInfo temp; @@ -5042,14 +5190,15 @@ internal static TypeSerializationInfo GetTypeSerializationInfo(Type type) { temp = s_xdInfo; } + return temp; } /// - /// Get TypeSerializationInfo using ItemTag as key + /// Get TypeSerializationInfo using ItemTag as key. /// - /// ItemTag for which TypeSerializationInfo is to be fetched - /// TypeSerializationInfo entry, null if no entry exist for the tag + /// ItemTag for which TypeSerializationInfo is to be fetched. + /// TypeSerializationInfo entry, null if no entry exist for the tag. internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string itemTag) { TypeSerializationInfo temp; @@ -5059,7 +5208,7 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string #region private_fields - //TypeSerializationInfo for XmlDocument + // TypeSerializationInfo for XmlDocument private static readonly TypeSerializationInfo s_xdInfo = new TypeSerializationInfo(typeof(XmlDocument), SerializationStrings.XmlDocumentTag, @@ -5072,19 +5221,19 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string /// private static readonly TypeSerializationInfo[] s_typeSerializationInfo = new TypeSerializationInfo[] { - new TypeSerializationInfo(typeof(Boolean), + new TypeSerializationInfo(typeof(bool), SerializationStrings.BooleanTag, SerializationStrings.BooleanTag, InternalSerializer.WriteBoolean, InternalDeserializer.DeserializeBoolean), - new TypeSerializationInfo(typeof(Byte), + new TypeSerializationInfo(typeof(byte), SerializationStrings.UnsignedByteTag, SerializationStrings.UnsignedByteTag, null, InternalDeserializer.DeserializeByte), - new TypeSerializationInfo(typeof(Char), + new TypeSerializationInfo(typeof(char), SerializationStrings.CharTag, SerializationStrings.CharTag, InternalSerializer.WriteChar, @@ -5096,13 +5245,13 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string InternalSerializer.WriteDateTime, InternalDeserializer.DeserializeDateTime), - new TypeSerializationInfo(typeof(Decimal), + new TypeSerializationInfo(typeof(decimal), SerializationStrings.DecimalTag, SerializationStrings.DecimalTag, null, InternalDeserializer.DeserializeDecimal), - new TypeSerializationInfo(typeof(Double), + new TypeSerializationInfo(typeof(double), SerializationStrings.DoubleTag, SerializationStrings.DoubleTag, InternalSerializer.WriteDouble, @@ -5131,7 +5280,7 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string null, InternalDeserializer.DeserializeInt64), - new TypeSerializationInfo(typeof(SByte), + new TypeSerializationInfo(typeof(sbyte), SerializationStrings.ByteTag, SerializationStrings.ByteTag, null, @@ -5149,7 +5298,7 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string InternalSerializer.WriteScriptBlock, InternalDeserializer.DeserializeScriptBlock), - new TypeSerializationInfo(typeof(String), + new TypeSerializationInfo(typeof(string), SerializationStrings.StringTag, SerializationStrings.StringTag, InternalSerializer.WriteEncodedString, @@ -5197,12 +5346,6 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string InternalSerializer.WriteVersion, InternalDeserializer.DeserializeVersion), - new TypeSerializationInfo(typeof(SemanticVersion), - SerializationStrings.SemanticVersionTag, - SerializationStrings.SemanticVersionTag, - InternalSerializer.WriteSemanticVersion, - InternalDeserializer.DeserializeSemanticVersion), - s_xdInfo, new TypeSerializationInfo(typeof(ProgressRecord), @@ -5225,7 +5368,7 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string private static readonly Dictionary s_knownTableKeyType = new Dictionary(); /// - /// Hashtable of knowntypes. Key is ItemTag + /// Hashtable of knowntypes. Key is ItemTag. /// private static readonly Dictionary s_knownTableKeyItemTag = new Dictionary(); @@ -5233,12 +5376,12 @@ internal static TypeSerializationInfo GetTypeSerializationInfoFromItemTag(string } /// - /// This class contains helper routined for serialization/deserialization + /// This class contains helper routined for serialization/deserialization. /// internal static class SerializationUtilities { /// - /// Extracts the value of a note property from a PSObject; returns null if the property does not exist + /// Extracts the value of a note property from a PSObject; returns null if the property does not exist. /// internal static object GetPropertyValue(PSObject psObject, string propertyName) { @@ -5253,7 +5396,7 @@ internal static object GetPropertyValue(PSObject psObject, string propertyName) } /// - /// Returns the BaseObject of a note property encoded as a PSObject; returns null if the property does not exist + /// Returns the BaseObject of a note property encoded as a PSObject; returns null if the property does not exist. /// internal static object GetPsObjectPropertyBaseObject(PSObject psObject, string propertyName) { @@ -5269,7 +5412,7 @@ internal static object GetPsObjectPropertyBaseObject(PSObject psObject, string p /// /// Checks if source is known container type and returns appropriate - /// information + /// information. /// /// /// @@ -5313,7 +5456,7 @@ internal static void GetKnownContainerTypeInfo( else { Type gt = source.GetType(); - if (gt.GetTypeInfo().IsGenericType) + if (gt.IsGenericType) { if (DerivesFromGenericType(gt, typeof(Stack<>))) { @@ -5356,15 +5499,15 @@ internal static void GetKnownContainerTypeInfo( } } - //Check if type is IEnumerable - //(LanguagePrimitives.GetEnumerable above should be enough - the check below is to preserve + // Check if type is IEnumerable + // (LanguagePrimitives.GetEnumerable above should be enough - the check below is to preserve // backcompatibility in some corner-cases (see bugs in Windows7 - #372562 and #372563)) if (ct == ContainerType.None) { enumerable = source as IEnumerable; if (enumerable != null) { - //WinBlue: 206515 - There are no elements in the source. The source is of type XmlLinkedNode (which derives from XmlNode which implements IEnumerable). + // WinBlue: 206515 - There are no elements in the source. The source is of type XmlLinkedNode (which derives from XmlNode which implements IEnumerable). // So, adding an additional check to see if this contains any elements IEnumerator enumerator = enumerable.GetEnumerator(); if (enumerator != null && enumerator.MoveNext()) @@ -5376,7 +5519,7 @@ internal static void GetKnownContainerTypeInfo( } /// - /// Checks if derived is of type baseType or a type derived from baseType + /// Checks if derived is of type baseType or a type derived from baseType. /// /// /// @@ -5387,26 +5530,26 @@ private static bool DerivesFromGenericType(Type derived, Type baseType) Dbg.Assert(baseType != null, "caller should validate the parameter"); while (derived != null) { - if (derived.GetTypeInfo().IsGenericType) + if (derived.IsGenericType) derived = derived.GetGenericTypeDefinition(); if (derived == baseType) { return true; } - derived = derived.GetTypeInfo().BaseType; + + derived = derived.BaseType; } + return false; } /// /// Gets the "ToString" from PSObject. /// - /// /// /// PSObject to be converted to string /// - /// /// /// "ToString" value /// @@ -5566,12 +5709,12 @@ internal static object GetPropertyValueInThreadSafeManner(PSPropertyInfo propert /// A dictionary from object to T where /// 1) keys are objects, /// 2) keys use reference equality, - /// 3) dictionary keeps only weak references to keys + /// 3) dictionary keeps only weak references to keys. /// /// type of dictionary values internal class WeakReferenceDictionary : IDictionary { - private class WeakReferenceEqualityComparer : IEqualityComparer + private sealed class WeakReferenceEqualityComparer : IEqualityComparer { public bool Equals(WeakReference x, WeakReference y) { @@ -5635,6 +5778,7 @@ private void CleanUp() alive.Add(weakKeyValuePair.Key, weakKeyValuePair.Value); } } + _dictionary = alive; _cleanupTriggerSize = initialCleanupTriggerSize + this.Count * 2; } @@ -5666,6 +5810,7 @@ public ICollection Keys keys.Add(key); } } + return keys; } } @@ -5695,6 +5840,7 @@ public T this[object key] { return _dictionary[new WeakReference(key)]; } + set { _dictionary[new WeakReference(key)] = value; @@ -5742,6 +5888,7 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) { rawList.Add(keyValuePair); } + rawList.CopyTo(array, arrayIndex); } @@ -5802,13 +5949,12 @@ 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 /// - /// Initializes a new empty instance of the class + /// Initializes a new empty instance of the class. /// public PSPrimitiveDictionary() : base(StringComparer.OrdinalIgnoreCase) @@ -5819,7 +5965,7 @@ public PSPrimitiveDictionary() /// Initializes a new instance of the class with contents /// copied from the hashtable. /// - /// hashtable to copy into the new instance of + /// Hashtable to copy into the new instance of /// /// This constructor will throw if the hashtable contains keys that are not a strings /// or values that are not one of primitive types that will work during PowerShell remoting handshake. @@ -5828,10 +5974,7 @@ public PSPrimitiveDictionary() public PSPrimitiveDictionary(Hashtable other) : base(StringComparer.OrdinalIgnoreCase) { - if (other == null) - { - throw new ArgumentNullException("other"); - } + ArgumentNullException.ThrowIfNull(other); foreach (DictionaryEntry entry in other) { @@ -5849,7 +5992,7 @@ public PSPrimitiveDictionary(Hashtable other) #if !CORECLR // No .NET Serialization In CoreCLR /// - /// Support for .NET serialization + /// Support for .NET serialization. /// private PSPrimitiveDictionary(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) @@ -5860,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; @@ -5877,19 +6020,19 @@ private string VerifyKey(object key) } private static readonly Type[] s_handshakeFriendlyTypes = new Type[] { - typeof(Boolean), - typeof(Byte), - typeof(Char), + typeof(bool), + typeof(byte), + typeof(char), typeof(DateTime), - typeof(Decimal), - typeof(Double), + typeof(decimal), + typeof(double), typeof(Guid), typeof(Int32), typeof(Int64), - typeof(SByte), + typeof(sbyte), typeof(Single), // typeof(ScriptBlock) - don't want ScriptBlocks, because they are deserialized into strings - typeof(String), + typeof(string), typeof(TimeSpan), typeof(UInt16), typeof(UInt32), @@ -5903,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) @@ -5939,6 +6082,7 @@ private void VerifyValue(object value) { VerifyValue(o); } + return; } @@ -5948,10 +6092,10 @@ private void VerifyValue(object value) } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. /// /// This method will throw if the is not a string and the /// is not one of primitive types that will work during PowerShell remoting handshake. @@ -5959,18 +6103,18 @@ 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); } /// /// Gets or sets the value associated with the specified key. /// - /// The key whose value to get or set + /// 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. /// /// @@ -5984,10 +6128,11 @@ public override object this[object key] { return base[key]; } + set { - string keyAsString = this.VerifyKey(key); - this.VerifyValue(value); + string keyAsString = VerifyKey(key); + VerifyValue(value); base[keyAsString] = value; } } @@ -5995,10 +6140,10 @@ public override object this[object key] /// /// Gets or sets the value associated with the specified key. /// - /// The key whose value to get or set + /// 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. /// /// @@ -6012,9 +6157,10 @@ public object this[string key] { return base[key]; } + set { - this.VerifyValue(value); + VerifyValue(value); base[key] = value; } } @@ -6033,380 +6179,380 @@ public override object Clone() } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Boolean value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, bool value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Boolean[] value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, bool[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Byte value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, byte value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, byte[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Char value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, char value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Char[] value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, char[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, DateTime value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, DateTime[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Decimal value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Decimal[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Double value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, double value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, Double[] value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, double[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Guid value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Guid[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Int32 value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Int32[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Int64 value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Int64[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, SByte value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, sbyte value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, SByte[] value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, sbyte[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Single value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Single[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, String value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, string value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add - public void Add(string key, String[] value) + /// The key of the element to add. + /// The value of the element to add. + public void Add(string key, string[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, TimeSpan value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, TimeSpan[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt16 value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt16[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt32 value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt32[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt64 value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, UInt64[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Uri value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Uri[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Version value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, Version[] value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, PSPrimitiveDictionary value) { this.Add((object)key, (object)value); } /// - /// Adds an element with the specified key and value into the Hashtable + /// Adds an element with the specified key and value into the Hashtable. /// - /// The key of the element to add - /// The value of the element to add + /// The key of the element to add. + /// The value of the element to add. public void Add(string key, PSPrimitiveDictionary[] value) { this.Add((object)key, (object)value); @@ -6418,21 +6564,21 @@ 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. /// /// /// internal static PSPrimitiveDictionary CloneAndAddPSVersionTable(PSPrimitiveDictionary originalHash) { - if ((null != originalHash) && + if ((originalHash != null) && (originalHash.ContainsKey(PSVersionInfo.PSVersionTableName))) { return (PSPrimitiveDictionary)originalHash.Clone(); } PSPrimitiveDictionary result = originalHash; - if (null != originalHash) + if (originalHash != null) { result = (PSPrimitiveDictionary)originalHash.Clone(); } @@ -6440,6 +6586,7 @@ internal static PSPrimitiveDictionary CloneAndAddPSVersionTable(PSPrimitiveDicti { result = new PSPrimitiveDictionary(); } + PSPrimitiveDictionary versionTable = new PSPrimitiveDictionary(PSVersionInfo.GetPSVersionTableForDownLevel()) { {"PSSemanticVersion", PSVersionInfo.PSVersion.ToString()} @@ -6455,10 +6602,10 @@ internal static PSPrimitiveDictionary CloneAndAddPSVersionTable(PSPrimitiveDicti /// TryPathGet<string>($sessionInfo.ApplicationPrivateData, out myHash, "ImplicitRemoting", "Hash"). /// /// Expected type of the value - /// The root dictionary + /// 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 + /// A chain of keys leading from the root dictionary () to the value. + /// 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"); @@ -6519,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 { @@ -6583,8 +6729,8 @@ static DeserializingTypeConverter() /// /// Determines if the converter can convert the parameter to the parameter. /// - /// The value to convert from - /// The type to convert to + /// 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(PSObject sourceValue, Type destinationType) { @@ -6600,26 +6746,26 @@ public override bool CanConvertFrom(PSObject sourceValue, Type destinationType) } /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// 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 + /// 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(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { if (destinationType == null) { - throw PSTraceSource.NewArgumentNullException("destinationType"); + throw PSTraceSource.NewArgumentNullException(nameof(destinationType)); } if (sourceValue == null) { throw new PSInvalidCastException( "InvalidCastWhenRehydratingFromNull", - PSTraceSource.NewArgumentNullException("sourceValue"), + PSTraceSource.NewArgumentNullException(nameof(sourceValue)), ExtendedTypeSystem.InvalidCastFromNull, destinationType.ToString()); } @@ -6639,6 +6785,7 @@ public override object ConvertFrom(PSObject sourceValue, Type destinationType, I null, ExtendedTypeSystem.InvalidCastException, sourceValue, + typeof(PSObject), destinationType); } @@ -6677,10 +6824,10 @@ private static object ConvertFrom(PSObject o, Func converter) } /// - /// Returns true if the converter can convert the parameter to the parameter + /// Returns true if the converter can convert the parameter to the parameter. /// - /// The value to convert from - /// The type to convert to + /// 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) { @@ -6688,21 +6835,21 @@ public override bool CanConvertTo(object sourceValue, Type destinationType) } /// - /// Converts the parameter to the parameter using formatProvider and ignoreCase + /// 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 + /// 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 PSTraceSource.NewNotSupportedException(); } /// - /// This method is not implemented - an overload taking a PSObject is implemented instead + /// This method is not implemented - an overload taking a PSObject is implemented instead. /// public override bool CanConvertFrom(object sourceValue, Type destinationType) { @@ -6710,7 +6857,7 @@ public override bool CanConvertFrom(object sourceValue, Type destinationType) } /// - /// This method is not implemented - an overload taking a PSObject is implemented instead + /// This method is not implemented - an overload taking a PSObject is implemented instead. /// public override bool CanConvertTo(PSObject sourceValue, Type destinationType) { @@ -6718,7 +6865,7 @@ public override bool CanConvertTo(PSObject sourceValue, Type destinationType) } /// - /// This method is not implemented - an overload taking a PSObject is implemented instead + /// This method is not implemented - an overload taking a PSObject is implemented instead. /// public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { @@ -6726,7 +6873,7 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo } /// - /// This method is not implemented - an overload taking a PSObject is implemented instead + /// This method is not implemented - an overload taking a PSObject is implemented instead. /// public override object ConvertTo(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase) { @@ -6753,8 +6900,8 @@ internal enum RehydrationFlags /// Can throw any exception (which is ok - LanguagePrimitives.ConvertTo will catch that). /// /// Expected type of the property - /// Deserialized object - /// Property name + /// Deserialized object. + /// Property name. /// private static T GetPropertyValue(PSObject pso, string propertyName) { @@ -6765,8 +6912,8 @@ private static T GetPropertyValue(PSObject pso, string propertyName) /// Gets value of a property. Can throw any exception (which is ok - LanguagePrimitives.ConvertTo will catch that). /// /// Expected type of the property - /// Deserialized object - /// Property name + /// Deserialized object. + /// Property name. /// /// internal static T GetPropertyValue(PSObject pso, string propertyName, RehydrationFlags flags) @@ -6775,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); } @@ -6794,29 +6941,30 @@ 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); } + return newList; } } @@ -6897,6 +7045,7 @@ private static CommandCompletion RehydrateCommandCompletion(PSObject pso) { completions.Add((CompletionResult)match); } + var currentMatchIndex = GetPropertyValue(pso, "CurrentMatchIndex"); var replacementIndex = GetPropertyValue(pso, "ReplacementIndex"); var replacementLength = GetPropertyValue(pso, "ReplacementLength"); @@ -6975,8 +7124,8 @@ internal static PSSessionOption RehydratePSSessionOption(PSObject pso) option.Culture = GetPropertyValue(pso, "Culture"); option.IdleTimeout = GetPropertyValue(pso, "IdleTimeout"); option.MaximumConnectionRedirectionCount = GetPropertyValue(pso, "MaximumConnectionRedirectionCount"); - option.MaximumReceivedDataSizePerCommand = GetPropertyValue>(pso, "MaximumReceivedDataSizePerCommand"); - option.MaximumReceivedObjectSize = GetPropertyValue>(pso, "MaximumReceivedObjectSize"); + option.MaximumReceivedDataSizePerCommand = GetPropertyValue(pso, "MaximumReceivedDataSizePerCommand"); + option.MaximumReceivedObjectSize = GetPropertyValue(pso, "MaximumReceivedObjectSize"); option.NoCompression = GetPropertyValue(pso, "NoCompression"); option.NoEncryption = GetPropertyValue(pso, "NoEncryption"); option.NoMachineProfile = GetPropertyValue(pso, "NoMachineProfile"); @@ -7014,7 +7163,7 @@ internal static LineBreakpoint RehydrateLineBreakpoint(PSObject pso) internal static CommandBreakpoint RehydrateCommandBreakpoint(PSObject pso) { - string script = GetPropertyValue(pso, "Script", RehydrationFlags.MissingPropertyOk); + string script = GetPropertyValue(pso, "Script", RehydrationFlags.MissingPropertyOk | RehydrationFlags.NullValueOk); string command = GetPropertyValue(pso, "Command"); int id = GetPropertyValue(pso, "Id"); bool enabled = GetPropertyValue(pso, "Enabled"); @@ -7029,7 +7178,7 @@ internal static CommandBreakpoint RehydrateCommandBreakpoint(PSObject pso) internal static VariableBreakpoint RehydrateVariableBreakpoint(PSObject pso) { - string script = GetPropertyValue(pso, "Script", RehydrationFlags.MissingPropertyOk); + string script = GetPropertyValue(pso, "Script", RehydrationFlags.MissingPropertyOk | RehydrationFlags.NullValueOk); string variableName = GetPropertyValue(pso, "Variable"); int id = GetPropertyValue(pso, "Id"); bool enabled = GetPropertyValue(pso, "Enabled"); @@ -7106,7 +7255,7 @@ private static PSCredential RehydratePSCredential(PSObject pso) string userName = GetPropertyValue(pso, "UserName"); System.Security.SecureString password = GetPropertyValue(pso, "Password"); - if (String.IsNullOrEmpty(userName)) + if (string.IsNullOrEmpty(userName)) { return PSCredential.Empty; } @@ -7135,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; @@ -7144,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) @@ -7180,7 +7330,7 @@ private static T RehydrateObjectSecurity(PSObject pso) #region Rehydration of types needed by implicit remoting /// - /// Gets the boolean properties of ParameterSetMetadata object encoded as an integer + /// Gets the boolean properties of ParameterSetMetadata object encoded as an integer. /// /// /// The PSObject for which to obtain the flags @@ -7192,13 +7342,12 @@ public static UInt32 GetParameterSetMetadataFlags(PSObject instance) { if (instance == null) { - throw PSTraceSource.NewArgumentNullException("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("instance"); + throw PSTraceSource.NewArgumentNullException(nameof(instance)); } return (UInt32)(parameterSetMetadata.Flags); @@ -7214,13 +7363,12 @@ public static PSObject GetInvocationInfo(PSObject instance) { if (instance == null) { - throw PSTraceSource.NewArgumentNullException("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("instance"); + throw PSTraceSource.NewArgumentNullException(nameof(instance)); } if (dbgStopEventArgs.InvocationInfo == null) @@ -7392,8 +7540,9 @@ private static CustomItemBase RehydrateCustomItemBase(PSObject deserializedItem) } else { - throw PSTraceSource.NewArgumentException("deserializedItem"); + throw PSTraceSource.NewArgumentException(nameof(deserializedItem)); } + return result; } @@ -7461,7 +7610,7 @@ private static PSControlGroupBy RehydrateGroupBy(PSObject deserializedGroupBy) } /// - /// Gets the boolean properties of ParameterSetMetadata object encoded as an integer + /// Gets the boolean properties of ParameterSetMetadata object encoded as an integer. /// /// /// The PSObject for which to obtain the flags @@ -7473,13 +7622,12 @@ public static Guid GetFormatViewDefinitionInstanceId(PSObject instance) { if (instance == null) { - throw PSTraceSource.NewArgumentNullException("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("instance"); + throw PSTraceSource.NewArgumentNullException(nameof(instance)); } return formatViewDefinition.InstanceId; @@ -7522,10 +7670,10 @@ private static ExtendedTypeDefinition RehydrateExtendedTypeDefinition(PSObject d result.TypeNames.Add(typeNames[i]); } } + return result; } #endregion } } - diff --git a/src/System.Management.Automation/help/AliasHelpInfo.cs b/src/System.Management.Automation/help/AliasHelpInfo.cs index da026404d97..5d02e754397 100644 --- a/src/System.Management.Automation/help/AliasHelpInfo.cs +++ b/src/System.Management.Automation/help/AliasHelpInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; // for fxcop @@ -9,12 +8,11 @@ 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. /// - /// /// /// The constructor is private. The only way to create an /// AliasHelpInfo object is through static method @@ -29,21 +27,20 @@ private AliasHelpInfo(AliasInfo aliasInfo) this.ForwardTarget = name; // A Cmdlet/Function/Script etc can have alias. this.ForwardHelpCategory = HelpCategory.Cmdlet | - HelpCategory.Function | HelpCategory.ExternalScript | HelpCategory.ScriptCommand | HelpCategory.Filter | HelpCategory.Workflow; + HelpCategory.Function | HelpCategory.ExternalScript | HelpCategory.ScriptCommand | HelpCategory.Filter; - if (!String.IsNullOrEmpty(aliasInfo.Name)) + if (!string.IsNullOrEmpty(aliasInfo.Name)) { Name = aliasInfo.Name.Trim(); } - if (!String.IsNullOrEmpty(name)) + if (!string.IsNullOrEmpty(name)) { Synopsis = name.Trim(); } _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"); } @@ -52,16 +49,16 @@ private AliasHelpInfo(AliasInfo aliasInfo) /// Returns the name of alias help. /// /// Name of alias help. - internal override string Name { get; } = ""; + internal override string Name { get; } = string.Empty; /// /// Returns synopsis of alias help. /// /// Synopsis of alias help. - internal override string Synopsis { get; } = ""; + internal override string Synopsis { get; } = string.Empty; /// - /// Help category for alias help. This is always HelpCategory.Alias + /// Help category for alias help. This is always HelpCategory.Alias. /// /// Help category for alias help internal override HelpCategory HelpCategory @@ -72,7 +69,7 @@ internal override HelpCategory HelpCategory } } - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; /// /// Returns full help object for alias help. @@ -102,7 +99,7 @@ internal static AliasHelpInfo GetHelpInfo(AliasInfo aliasInfo) AliasHelpInfo aliasHelpInfo = new AliasHelpInfo(aliasInfo); - if (String.IsNullOrEmpty(aliasHelpInfo.Name)) + if (string.IsNullOrEmpty(aliasHelpInfo.Name)) return null; aliasHelpInfo.AddCommonHelpProperties(); diff --git a/src/System.Management.Automation/help/AliasHelpProvider.cs b/src/System.Management.Automation/help/AliasHelpProvider.cs index e6c268d2dfa..d93aa76b2f3 100644 --- a/src/System.Management.Automation/help/AliasHelpProvider.cs +++ b/src/System.Management.Automation/help/AliasHelpProvider.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -11,7 +10,6 @@ namespace System.Management.Automation /// /// Implements the help provider for alias help. /// - /// /// /// Unlike other help providers, AliasHelpProvider directly inherits from HelpProvider /// instead of HelpProviderWithCache. This is because alias can be created/removed/updated @@ -35,14 +33,14 @@ internal AliasHelpProvider(HelpSystem helpSystem) : base(helpSystem) private readonly ExecutionContext _context; /// - /// Session state for current Microsoft Command Shell session + /// Session state for current Microsoft Command Shell session. /// /// /// _sessionState is mainly used for alias help search in the case /// of wildcard search patterns. This is currently not achievable /// through _commandDiscovery. /// - private SessionState _sessionState; + private readonly SessionState _sessionState; /// /// Command Discovery object for current session. @@ -52,12 +50,12 @@ 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 /// - /// Name of alias help provider + /// Name of alias help provider. /// /// Name of alias help provider internal override string Name @@ -92,8 +90,8 @@ internal override HelpCategory HelpCategory /// a. use _commandDiscovery object to retrieve AliasInfo object. /// b. Create AliasHelpInfo object based on AliasInfo object /// - /// help request object - /// help info found + /// Help request object. + /// Help info found. internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { CommandInfo commandInfo = null; @@ -128,14 +126,14 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) /// a. use _sessionState object to get a list of alias that match the target. /// b. for each alias, retrieve help info as in ExactMatchHelp. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content. Individual /// provider can decide which content to search in. /// /// If false, searches for pattern in the command names. /// - /// a IEnumerable of helpinfo object + /// A IEnumerable of helpinfo object. internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool searchOnlyContent) { // aliases do not have help content...so doing nothing in that case @@ -143,7 +141,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool { string target = helpRequest.Target; string pattern = target; - Hashtable hashtable = new Hashtable(StringComparer.OrdinalIgnoreCase); + var allAliases = new HashSet(StringComparer.OrdinalIgnoreCase); if (!WildcardPattern.ContainsWildcardCharacters(target)) { @@ -163,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)) @@ -171,12 +169,12 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool continue; } - if (hashtable.ContainsKey(name)) + if (allAliases.Contains(name)) { continue; } - hashtable.Add(name, null); + allAliases.Add(name); yield return helpInfo; } @@ -210,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)) @@ -218,12 +216,12 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool continue; } - if (hashtable.ContainsKey(name)) + if (allAliases.Contains(name)) { continue; } - hashtable.Add(name, null); + allAliases.Add(name); yield return helpInfo; } @@ -245,12 +243,12 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool HelpInfo helpInfo = AliasHelpInfo.GetHelpInfo(alias); - if (hashtable.ContainsKey(name)) + if (allAliases.Contains(name)) { continue; } - hashtable.Add(name, null); + allAliases.Add(name); yield return helpInfo; } @@ -263,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; } @@ -308,11 +306,11 @@ private static bool Match(string target, string[] patterns) private static bool Match(string target, string pattern) { - if (String.IsNullOrEmpty(pattern)) + if (string.IsNullOrEmpty(pattern)) return true; - if (String.IsNullOrEmpty(target)) - target = ""; + if (string.IsNullOrEmpty(target)) + target = string.Empty; WildcardPattern matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); diff --git a/src/System.Management.Automation/help/BaseCommandHelpInfo.cs b/src/System.Management.Automation/help/BaseCommandHelpInfo.cs index 9c0c8b3c409..c24e45ee788 100644 --- a/src/System.Management.Automation/help/BaseCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/BaseCommandHelpInfo.cs @@ -1,11 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Management.Automation.Runspaces; -using System.Globalization; using System.Text; using Dbg = System.Management.Automation.Diagnostics; @@ -54,18 +53,18 @@ internal override string Name PSObject commandDetails = this.Details; if (commandDetails == null) { - return ""; + return string.Empty; } if (commandDetails.Properties["Name"] == null || commandDetails.Properties["Name"].Value == null) { - return ""; + return string.Empty; } string name = commandDetails.Properties["Name"].Value.ToString(); if (name == null) - return ""; + return string.Empty; return name.Trim(); } @@ -82,13 +81,13 @@ internal override string Synopsis PSObject commandDetails = this.Details; if (commandDetails == null) { - return ""; + return string.Empty; } if (commandDetails.Properties["Description"] == null || commandDetails.Properties["Description"].Value == null) { - return ""; + return string.Empty; } object[] synopsisItems = (object[])LanguagePrimitives.ConvertTo( @@ -97,7 +96,7 @@ internal override string Synopsis CultureInfo.InvariantCulture); if (synopsisItems == null || synopsisItems.Length == 0) { - return ""; + return string.Empty; } PSObject firstSynopsisItem = synopsisItems[0] == null ? null : PSObject.AsPSObject(synopsisItems[0]); @@ -105,13 +104,13 @@ internal override string Synopsis firstSynopsisItem.Properties["Text"] == null || firstSynopsisItem.Properties["Text"].Value == null) { - return ""; + return string.Empty; } string synopsis = firstSynopsisItem.Properties["Text"].Value.ToString(); if (synopsis == null) { - return ""; + return string.Empty; } return synopsis.Trim(); @@ -205,7 +204,7 @@ internal Uri LookupUriFromCommandInfo() if (this.FullHelp.Properties["ModuleName"] != null) { PSNoteProperty moduleNameNP = this.FullHelp.Properties["ModuleName"] as PSNoteProperty; - if (null != moduleNameNP) + if (moduleNameNP != null) { LanguagePrimitives.TryConvertTo(moduleNameNP.Value, CultureInfo.InvariantCulture, out moduleName); @@ -215,12 +214,11 @@ 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(); - if (null == context) + if (context == null) { return null; } @@ -253,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]; } @@ -307,7 +305,7 @@ internal static Uri GetUriFromCommandPSObject(PSObject commandFullHelp) PSObject navigationLink = PSObject.AsPSObject(navigationLinkAsObject); PSNoteProperty uriNP = navigationLink.Properties["uri"] as PSNoteProperty; - if (null != uriNP) + if (uriNP != null) { string uriString = string.Empty; LanguagePrimitives.TryConvertTo(uriNP.Value, CultureInfo.InvariantCulture, out uriString); @@ -320,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]; } @@ -346,35 +344,29 @@ internal static Uri GetUriFromCommandPSObject(PSObject commandFullHelp) /// The underlying code will usually run pattern.IsMatch() on /// content it wants to search. /// Cmdlet help info looks for pattern in Synopsis and - /// DetailedDescription + /// DetailedDescription. /// /// /// internal override bool MatchPatternInContent(WildcardPattern pattern) { - Dbg.Assert(null != pattern, "pattern cannot be null"); + Dbg.Assert(pattern != null, "pattern cannot be null"); string synopsis = Synopsis; string detailedDescription = DetailedDescription; - if (null == synopsis) - { - synopsis = string.Empty; - } + synopsis ??= string.Empty; - if (null == detailedDescription) - { - detailedDescription = string.Empty; - } + detailedDescription ??= string.Empty; return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription); } /// - /// Returns help information for a parameter(s) identified by pattern + /// Returns help information for a parameter(s) identified by pattern. /// - /// pattern to search for parameters - /// A collection of parameters that match pattern + /// Pattern to search for parameters. + /// A collection of parameters that match pattern. internal override PSObject[] GetParameter(string pattern) { // this object knows Maml format... @@ -394,8 +386,19 @@ internal override PSObject[] GetParameter(string pattern) return base.GetParameter(pattern); } + // The Maml format simplifies array fields containing only one object + // by transforming them into the objects themselves. To ensure the consistency + // of the help command result we change it back into an array. + var param = prmts.Properties["parameter"].Value; + PSObject[] paramAsPSObjArray = new PSObject[1]; + + if (param is PSObject paramPSObj) + { + paramAsPSObjArray[0] = paramPSObj; + } + PSObject[] prmtArray = (PSObject[])LanguagePrimitives.ConvertTo( - prmts.Properties["parameter"].Value, + paramAsPSObjArray[0] != null ? paramAsPSObjArray : param, typeof(PSObject[]), CultureInfo.InvariantCulture); @@ -435,12 +438,12 @@ internal string DetailedDescription get { if (this.FullHelp == null) - return ""; + return string.Empty; if (this.FullHelp.Properties["Description"] == null || this.FullHelp.Properties["Description"].Value == null) { - return ""; + return string.Empty; } object[] descriptionItems = (object[])LanguagePrimitives.ConvertTo( @@ -449,10 +452,10 @@ internal string DetailedDescription CultureInfo.InvariantCulture); if (descriptionItems == null || descriptionItems.Length == 0) { - return ""; + 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. @@ -465,9 +468,9 @@ internal string DetailedDescription } PSObject descriptionObject = PSObject.AsPSObject(descriptionItem); - if ((null == descriptionObject) || - (null == descriptionObject.Properties["Text"]) || - (null == descriptionObject.Properties["Text"].Value)) + if ((descriptionObject == null) || + (descriptionObject.Properties["Text"] == null) || + (descriptionObject.Properties["Text"].Value == null)) { continue; } diff --git a/src/System.Management.Automation/help/CabinetAPI.cs b/src/System.Management.Automation/help/CabinetAPI.cs index a28dcf990e3..9ccdc08baa6 100644 --- a/src/System.Management.Automation/help/CabinetAPI.cs +++ b/src/System.Management.Automation/help/CabinetAPI.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; @@ -13,11 +12,11 @@ namespace System.Management.Automation.Internal internal abstract class ICabinetExtractor : IDisposable { /// - /// Extracts a cabinet file + /// Extracts a cabinet file. /// - /// cabinet file name - /// cabinet directory name, must be back slash terminated - /// destination directory name, must be back slash terminated + /// Cabinet file name. + /// Cabinet directory name, must be back slash terminated. + /// Destination directory name, must be back slash terminated. internal abstract bool Extract(string cabinetName, string srcPath, string destPath); #region IDisposable Interface @@ -64,11 +63,10 @@ protected virtual void Dispose(bool disposing) #endregion } - /// /// Abstract class which defines a CabinetExtractor loader. An implementation /// of this class will be instantiated onetime from the C++/CLI - /// assembly using reflection + /// assembly using reflection. /// /// The C++/CLI implementation of this class needs to be /// static @@ -78,15 +76,15 @@ internal abstract class ICabinetExtractorLoader } /// - /// Used to create a CabinetExtractor class + /// Used to create a CabinetExtractor class. /// - internal class CabinetExtractorFactory + internal static class CabinetExtractorFactory { - private static ICabinetExtractorLoader s_cabinetLoader; - internal static ICabinetExtractor EmptyExtractor = new EmptyCabinetExtractor(); + private static readonly ICabinetExtractorLoader s_cabinetLoader; + internal static readonly ICabinetExtractor EmptyExtractor = new EmptyCabinetExtractor(); /// - /// Static constructor + /// Static constructor. /// [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] static CabinetExtractorFactory() @@ -95,9 +93,9 @@ static CabinetExtractorFactory() } /// - /// Provider a CabinetExtractor instance + /// Provider a CabinetExtractor instance. /// - /// Tracer instance + /// Tracer instance. internal static ICabinetExtractor GetCabinetExtractor() { if (s_cabinetLoader != null) @@ -112,16 +110,16 @@ internal static ICabinetExtractor GetCabinetExtractor() } /// - /// Dummy cabinet extractor implementation + /// Dummy cabinet extractor implementation. /// internal sealed class EmptyCabinetExtractor : ICabinetExtractor { /// - /// Extracts a cabinet file + /// Extracts a cabinet file. /// - /// cabinet file name - /// cabinet directory name, must be back slash terminated - /// destination directory name, must be back slash terminated + /// Cabinet file name. + /// Cabinet directory name, must be back slash terminated. + /// Destination directory name, must be back slash terminated. internal override bool Extract(string cabinetName, string srcPath, string destPath) { // its intentional that this method has no definition @@ -129,7 +127,7 @@ internal override bool Extract(string cabinetName, string srcPath, string destPa } /// - /// Disposes the instance + /// Disposes the instance. /// /// protected override void Dispose(bool disposing) @@ -139,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 4c3521e20bf..fd369ca03be 100644 --- a/src/System.Management.Automation/help/CabinetNativeApi.cs +++ b/src/System.Management.Automation/help/CabinetNativeApi.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.IO; using System.Runtime.InteropServices; + using Microsoft.Win32.SafeHandles; namespace System.Management.Automation.Internal @@ -70,10 +70,7 @@ protected override void Dispose(bool disposing) } // Free managed objects within 'if (disposing)' if needed - if (null != fdiContext) - { - 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() { @@ -147,12 +144,12 @@ private void populateDelegates() } /// - /// Frees all the delegate handles + /// Frees all the delegate handles. /// private void CleanUpDelegates() { // Free GCHandles so that the memory they point to may be unpinned (garbage collected) - if (null != _fdiAllocHandle) + 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 { @@ -226,14 +223,24 @@ internal delegate IntPtr FdiOpenDelegate( internal static IntPtr FdiOpen(string filename, int oflag, int pmode) { FileMode mode = CabinetNativeApi.ConvertOpflagToFileMode(oflag); + FileAccess access = CabinetNativeApi.ConvertPermissionModeToFileAccess(pmode); FileShare share = CabinetNativeApi.ConvertPermissionModeToFileShare(pmode); + // This method is used for opening the cab file as well as saving the extracted files. + // When we are opening the cab file we only need read permissions. + // We force read permissions so that non-elevated users can extract cab files. + if (mode == FileMode.Open || mode == FileMode.OpenOrCreate) + { + access = FileAccess.Read; + share = FileShare.Read; + } + try { FileStream stream = new FileStream(filename, mode, access, share); - if (null == stream) + if (stream == null) { return new IntPtr(-1); } @@ -311,7 +318,7 @@ internal static int FdiClose(IntPtr fp) GCHandle handle = GCHandle.FromIntPtr(fp); FileStream stream = (FileStream)handle.Target; - if (null == stream) + if (stream == null) { return -1; } @@ -411,6 +418,7 @@ internal static IntPtr FdiNotify(FdiNotificationType fdint, FdiNotification fdin return new IntPtr(1); } } + return new IntPtr(0); } @@ -419,7 +427,7 @@ internal static IntPtr FdiNotify(FdiNotificationType fdint, FdiNotification fdin #region Helper methods for non-trivial conversions /// - /// Converts an unmanaged define into a known managed value + /// Converts an unmanaged define into a known managed value. /// /// Defined in stdio.h. /// The appropriate System.IO.SeekOrigin value. @@ -441,33 +449,33 @@ internal static SeekOrigin ConvertOriginToSeekOrigin(int origin) /// /// Converts an unmanaged define into a known managed type. /// - /// Operation mode defined in fcntl.h + /// Operation mode defined in fcntl.h. /// The appropriate System.IO.FileMode type. 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; } @@ -480,21 +488,21 @@ internal static FileMode ConvertOpflagToFileMode(int oflag) /// /// Converts an unmanaged define into a known managed type. /// - /// Permission mode defined in stat.h + /// Permission mode defined in stat.h. /// The appropriate System.IO.FileAccess type. 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; } @@ -507,21 +515,21 @@ internal static FileAccess ConvertPermissionModeToFileAccess(int pmode) /// /// Converts an unmanaged define into a known managed type. /// - /// Permission mode defined in stat.h + /// Permission mode defined in stat.h. /// The appropriate System.IO.FileShare type. 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; } @@ -535,7 +543,7 @@ internal static FileShare ConvertPermissionModeToFileShare(int pmode) #region IO classes, structures, and enums - [FlagsAttribute] + [Flags] internal enum PermissionMode : int { None = 0x0000, @@ -543,7 +551,7 @@ internal enum PermissionMode : int Read = 0x0100 } - [FlagsAttribute] + [Flags] internal enum OpFlags : int { RdOnly = 0x0000, @@ -567,7 +575,7 @@ internal class FdiNotification { internal int cb; // LONG internal string psz1; // char FAR * - internal string psz2; //char FAR * + internal string psz2; // char FAR * internal string psz3; // char FAR * internal IntPtr pv; // void FAR * // In this case, it is the destination path internal IntPtr hf; // INT_PTR @@ -578,7 +586,7 @@ internal class FdiNotification internal short iCabinet; // USHORT internal short iFolder; // USHORT internal int fdie; // FDIERROR - }; + } internal enum FdiNotificationType : int { @@ -596,7 +604,7 @@ internal class FdiERF internal int erfOper; internal int erfType; internal bool fError; - }; + } internal sealed class FdiContextHandle : SafeHandleZeroOrMinusOneIsInvalid { @@ -616,17 +624,17 @@ protected override bool ReleaseHandle() #region PInvoke Definitions /// - /// Creates an FDI context + /// Creates an FDI context. /// - /// _In_ PFNALLOC - Memory allocation delegate - /// _In_ PFNFREE - Memory free delegate - /// _In_ PFNOPEN - File open delegate - /// _In_ PFNREAD - File read delegate - /// _In_ PFNWRITE - File write delegate - /// _In_ PFNCLOSE - File close delegate - /// _In_ PFNSEEK - File seek delegate - /// _In_ int - CPU type - /// _Inout_ PERF - Error structure containing error information + /// _In_ PFNALLOC - Memory allocation delegate. + /// _In_ PFNFREE - Memory free delegate. + /// _In_ PFNOPEN - File open delegate. + /// _In_ PFNREAD - File read delegate. + /// _In_ PFNWRITE - File write delegate. + /// _In_ PFNCLOSE - File close delegate. + /// _In_ PFNSEEK - File seek delegate. + /// _In_ int - CPU type. + /// _Inout_ PERF - Error structure containing error information. /// [DllImport("cabinet.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, SetLastError = true)] internal static extern FdiContextHandle FDICreate( @@ -641,15 +649,15 @@ internal static extern FdiContextHandle FDICreate( FdiERF erf); /// - /// Extracts files from cabinets + /// Extracts files from cabinets. /// - /// _In_ HFDI - A valid FDI context handle returned by FDICreate - /// _In_ LPSTR - The name of the cabinet file - /// _In_ LPSTR - The path to the cabinet file excluding the file name - /// _In_ int - Not defined - /// _In_ PFNFDINOTIFY - Pointer to the notification callback delegate - /// _In_ PFNFDIDECRYPT - Not used - /// _In_opt_ void FAR * - Path string passed to the notification function + /// _In_ HFDI - A valid FDI context handle returned by FDICreate. + /// _In_ LPSTR - The name of the cabinet file. + /// _In_ LPSTR - The path to the cabinet file excluding the file name. + /// _In_ int - Not defined. + /// _In_ PFNFDINOTIFY - Pointer to the notification callback delegate. + /// _In_ PFNFDIDECRYPT - Not used. + /// _In_opt_ void FAR * - Path string passed to the notification function. /// [DllImport("cabinet.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, SetLastError = true, BestFitMapping = false)] internal static extern bool FDICopy( @@ -662,9 +670,9 @@ internal static extern bool FDICopy( IntPtr pvUser); /// - /// Deletes an open FDI context + /// Deletes an open FDI context. /// - /// _In_ HFDI - The FDI context handle to destroy + /// _In_ HFDI - The FDI context handle to destroy. /// [DllImport("cabinet.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, SetLastError = true)] internal static extern bool FDIDestroy( diff --git a/src/System.Management.Automation/help/CommandHelpProvider.cs b/src/System.Management.Automation/help/CommandHelpProvider.cs index 4c0967338f9..15af80745db 100644 --- a/src/System.Management.Automation/help/CommandHelpProvider.cs +++ b/src/System.Management.Automation/help/CommandHelpProvider.cs @@ -1,17 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; -using System.Xml; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; +using System.Xml; using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation.Help; @@ -22,7 +21,6 @@ namespace System.Management.Automation /// /// Class CommandHelpProvider implement the help provider for commands. /// - /// /// /// Command Help information are stored in 'help.xml' files. Location of these files /// can be found from through the engine execution context. @@ -30,7 +28,7 @@ namespace System.Management.Automation internal class CommandHelpProvider : HelpProviderWithCache { /// - /// Constructor for CommandHelpProvider + /// Constructor for CommandHelpProvider. /// internal CommandHelpProvider(HelpSystem helpSystem) : base(helpSystem) { @@ -38,7 +36,6 @@ internal CommandHelpProvider(HelpSystem helpSystem) : base(helpSystem) } /// - /// /// static CommandHelpProvider() { @@ -51,14 +48,14 @@ 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; #region Common Properties /// - /// Name of this provider + /// Name of this provider. /// /// Name of this provider internal override string Name @@ -87,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"); @@ -106,7 +103,7 @@ private void GetModulePaths(CommandInfo commandInfo, out string moduleName, out moduleName = commandInfo.Module.Name; moduleDir = commandInfo.Module.ModuleBase; - if (!String.IsNullOrEmpty(commandInfo.Prefix)) + if (!string.IsNullOrEmpty(commandInfo.Prefix)) { testWithoutPrefix = true; cmdNameWithoutPrefix = Microsoft.PowerShell.Commands.ModuleCmdletBase.RemovePrefixFromCommandName(commandInfo.Name, commandInfo.Prefix); @@ -125,9 +122,7 @@ private void GetModulePaths(CommandInfo commandInfo, out string moduleName, out } else if (scriptCommandInfo != null && (nestedModule.ExportedFunctions.ContainsKey(commandInfo.Name) || - nestedModule.ExportedWorkflows.ContainsKey(commandInfo.Name) || - (testWithoutPrefix && nestedModule.ExportedFunctions.ContainsKey(cmdNameWithoutPrefix)) || - (testWithoutPrefix && nestedModule.ExportedWorkflows.ContainsKey(cmdNameWithoutPrefix)))) + (testWithoutPrefix && nestedModule.ExportedFunctions.ContainsKey(cmdNameWithoutPrefix)))) { nestedModulePath = nestedModule.Path; break; @@ -137,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"); @@ -147,10 +142,11 @@ private string GetHelpName(CommandInfo commandInfo) { return cmdletInfo.FullName; } + return commandInfo.Name; } - private HelpInfo GetHelpInfoFromHelpFile(CommandInfo commandInfo, string helpFileToFind, Collection searchPaths, bool reportErrors, out string helpFile) + private HelpInfo GetHelpInfoFromHelpFile(CommandInfo commandInfo, string helpFileToFind, Collection searchPaths, bool reportErrors, out string helpFile) { Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null"); Dbg.Assert(helpFileToFind != null, "Caller should verify that helpFileToFind != null"); @@ -162,7 +158,7 @@ private HelpInfo GetHelpInfoFromHelpFile(CommandInfo commandInfo, string helpFil helpFile = MUIFileSearcher.LocateFile(helpFileToFind, searchPaths); - if (!String.IsNullOrEmpty(helpFile)) + if (!string.IsNullOrEmpty(helpFile)) { if (!_helpFiles.Contains(helpFile)) { @@ -225,14 +221,15 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se { result = GetFromCommandCache(cmdletInfo.ModuleName, cmdletInfo.Name, cmdletInfo.HelpCategory); - if (null == result) + if (result == null) { // Try load the help file specified by CmdletInfo.HelpFile property helpFile = FindHelpFile(cmdletInfo); - if (!String.IsNullOrEmpty(helpFile) && !_helpFiles.Contains(helpFile)) + if (!string.IsNullOrEmpty(helpFile) && !_helpFiles.Contains(helpFile)) { LoadHelpFile(helpFile, cmdletInfo.ModuleName, cmdletInfo.Name, reportErrors); } + result = GetFromCommandCacheOrCmdletInfo(cmdletInfo); } } @@ -240,18 +237,19 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se { // Try load the help file specified by FunctionInfo.HelpFile property helpFile = functionInfo.HelpFile; - if (!String.IsNullOrEmpty(helpFile)) + if (!string.IsNullOrEmpty(helpFile)) { if (!_helpFiles.Contains(helpFile)) { LoadHelpFile(helpFile, helpFile, commandInfo.Name, reportErrors); } + result = GetFromCommandCache(helpFile, commandInfo); } } // For scripts, try to retrieve the help from the file specified by .ExternalHelp directive - if (null == result && isScriptCommand) + if (result == null && isScriptCommand) { ScriptBlock sb = null; try @@ -274,7 +272,7 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se result = sb.GetHelpInfo(_context, commandInfo, searchOnlyContent, HelpSystem.ScriptBlockTokenCache, out helpFile, out helpUriFromDotLink); - if (!String.IsNullOrEmpty(helpUriFromDotLink)) + if (!string.IsNullOrEmpty(helpUriFromDotLink)) { try { @@ -297,12 +295,13 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se } } - if (!String.IsNullOrEmpty(helpFile) && !InternalTestHooks.BypassOnlineHelpRetrieval) + if (!string.IsNullOrEmpty(helpFile) && !InternalTestHooks.BypassOnlineHelpRetrieval) { if (!_helpFiles.Contains(helpFile)) { LoadHelpFile(helpFile, helpFile, commandInfo.Name, reportErrors); } + result = GetFromCommandCache(helpFile, commandInfo) ?? result; } } @@ -312,26 +311,34 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se // in the appropriate UI culture subfolder of ModuleBase, and retrieve help // If still not able to get help, try search for a file called -Help.xml // under the ModuleBase and the NestedModule's directory, and retrieve help - if (null == result && !InternalTestHooks.BypassOnlineHelpRetrieval) + if (result == null && !InternalTestHooks.BypassOnlineHelpRetrieval) { // Get the name and ModuleBase directory of the command's module // and the nested module that implements the command GetModulePaths(commandInfo, out moduleName, out moduleDir, out nestedModulePath); - Collection searchPaths = new Collection(); - if (!String.IsNullOrEmpty(moduleDir)) + var userHomeHelpPath = HelpUtils.GetUserHomeHelpSearchPath(); + + Collection searchPaths = new Collection() { userHomeHelpPath }; + + if (!string.IsNullOrEmpty(moduleDir)) { searchPaths.Add(moduleDir); } - if (!String.IsNullOrEmpty(moduleName) && !String.IsNullOrEmpty(moduleDir)) + if (!string.IsNullOrEmpty(userHomeHelpPath) && !string.IsNullOrEmpty(moduleName)) + { + searchPaths.Add(Path.Combine(userHomeHelpPath, moduleName)); + } + + if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir)) { // Search for -Help.xml under ModuleBase folder string helpFileToFind = moduleName + "-Help.xml"; result = GetHelpInfoFromHelpFile(commandInfo, helpFileToFind, searchPaths, reportErrors, out helpFile); } - if (null == result && !String.IsNullOrEmpty(nestedModulePath)) + if (result == null && !string.IsNullOrEmpty(nestedModulePath)) { // Search for -Help.xml under both ModuleBase and NestedModule's directory searchPaths.Add(Path.GetDirectoryName(nestedModulePath)); @@ -341,7 +348,7 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se } // Set the HelpFile property to the file that contains the help content - if (null != result && !String.IsNullOrEmpty(helpFile)) + if (result != null && !string.IsNullOrEmpty(helpFile)) { if (isCmdlet) { @@ -354,7 +361,7 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se } // If the above fails to get help, construct an HelpInfo object using the syntax and definition of the command - if (null == result) + if (result == null) { if (commandInfo.CommandType == CommandTypes.ExternalScript || commandInfo.CommandType == CommandTypes.Script) @@ -374,15 +381,15 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se } } - if (null != result) + if (result != null) { if (isScriptCommand && result.GetUriForOnlineHelp() == null) { - if (!String.IsNullOrEmpty(commandInfo.CommandMetadata.HelpUri)) + if (!string.IsNullOrEmpty(commandInfo.CommandMetadata.HelpUri)) { DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(result.FullHelp, commandInfo.CommandMetadata.HelpUri); } - else if (!String.IsNullOrEmpty(helpUri)) + else if (!string.IsNullOrEmpty(helpUri)) { DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(result.FullHelp, helpUri); } @@ -405,7 +412,6 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se /// /// ExactMatchHelp implementation for this help provider. /// - /// /// /// ExactMatchHelp is overridden instead of DoExactMatchHelp to make sure /// all help item retrieval will go through command discovery. Because each @@ -414,14 +420,14 @@ private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool se /// help item. Forcing each ExactMatchHelp to go through command discovery /// will make sure helpInfo for invalid command will not be returned. /// - /// help request object + /// Help request object. /// internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { int countHelpInfosFound = 0; string target = helpRequest.Target; // this is for avoiding duplicate result from help output. - Hashtable hashtable = new Hashtable(StringComparer.OrdinalIgnoreCase); + var allHelpNames = new HashSet(StringComparer.OrdinalIgnoreCase); CommandSearcher searcher = GetCommandSearcherForExactMatch(target, _context); @@ -439,7 +445,7 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) HelpInfo helpInfo = GetHelpInfo(current, true, false); string helpName = GetHelpName(current); - if (helpInfo != null && !String.IsNullOrEmpty(helpName)) + if (helpInfo != null && !string.IsNullOrEmpty(helpName)) { if (helpInfo.ForwardHelpCategory == helpRequest.HelpCategory && helpInfo.ForwardTarget.Equals(helpRequest.Target, StringComparison.OrdinalIgnoreCase)) @@ -447,7 +453,7 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) throw new PSInvalidOperationException(HelpErrors.CircularDependencyInHelpForwarding); } - if (hashtable.ContainsKey(helpName)) + if (allHelpNames.Contains(helpName)) continue; if (!Match(helpInfo, helpRequest, current)) @@ -456,7 +462,7 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) } countHelpInfosFound++; - hashtable.Add(helpName, null); + allHelpNames.Add(helpName); yield return helpInfo; if ((countHelpInfosFound >= helpRequest.MaxResults) && (helpRequest.MaxResults > 0)) @@ -473,7 +479,7 @@ private static string GetCmdletAssemblyPath(CmdletInfo cmdletInfo) if (cmdletInfo.ImplementingType == null) return null; - return Path.GetDirectoryName(cmdletInfo.ImplementingType.GetTypeInfo().Assembly.Location); + return Path.GetDirectoryName(cmdletInfo.ImplementingType.Assembly.Location); } /// @@ -482,7 +488,6 @@ private static string GetCmdletAssemblyPath(CmdletInfo cmdletInfo) /// This will avoid one help file getting loaded again and again. /// (Which should not happen unless some commandlet is pointing /// to a help file that actually doesn't contain the help for it). - /// /// private readonly Hashtable _helpFiles = new Hashtable(); @@ -498,7 +503,7 @@ private string GetHelpFile(string helpFile, CmdletInfo cmdletInfo) // 2. If PSSnapInInfo exists, then always look in the application base of the mshsnapin // Otherwise, // Look in the default search path and cmdlet assembly path - Collection searchPaths = new Collection(); + Collection searchPaths = new Collection(); if (!File.Exists(helpFileToLoad)) { @@ -512,14 +517,18 @@ private string GetHelpFile(string helpFile, CmdletInfo cmdletInfo) // we have to search only in the application base for a mshsnapin... // if you create an absolute path for helpfile, then MUIFileSearcher // will look only in that path. - helpFileToLoad = Path.Combine(mshSnapInInfo.ApplicationBase, helpFile); + + searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath()); + searchPaths.Add(mshSnapInInfo.ApplicationBase); } - else if (cmdletInfo.Module != null && !string.IsNullOrEmpty(cmdletInfo.Module.Path)) + else if (cmdletInfo.Module != null && !string.IsNullOrEmpty(cmdletInfo.Module.Path) && !string.IsNullOrEmpty(cmdletInfo.Module.ModuleBase)) { - helpFileToLoad = Path.Combine(cmdletInfo.Module.ModuleBase, helpFile); + searchPaths.Add(HelpUtils.GetModuleBaseForUserHelp(cmdletInfo.Module.ModuleBase, cmdletInfo.Module.Name)); + searchPaths.Add(cmdletInfo.Module.ModuleBase); } else { + searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath()); searchPaths.Add(GetDefaultShellSearchPath()); searchPaths.Add(GetCmdletAssemblyPath(cmdletInfo)); } @@ -533,7 +542,7 @@ private string GetHelpFile(string helpFile, CmdletInfo cmdletInfo) // let caller take care of getting help info in a different way // like "get-command -syntax" - if (String.IsNullOrEmpty(location)) + if (string.IsNullOrEmpty(location)) { s_tracer.WriteLine("Unable to load file {0}", helpFileToLoad); } @@ -542,7 +551,7 @@ private string GetHelpFile(string helpFile, CmdletInfo cmdletInfo) } /// - /// Finds a help file associated with the given cmdlet + /// Finds a help file associated with the given cmdlet. /// /// /// @@ -557,13 +566,13 @@ private string FindHelpFile(CmdletInfo cmdletInfo) if (cmdletInfo == null) { - throw PSTraceSource.NewArgumentNullException("cmdletInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(cmdletInfo)); } // Get the help file name from the cmdlet metadata string helpFile = cmdletInfo.HelpFile; - if (String.IsNullOrEmpty(helpFile)) + if (string.IsNullOrEmpty(helpFile)) { if (cmdletInfo.Module != null) { @@ -593,15 +602,15 @@ private string FindHelpFile(CmdletInfo cmdletInfo) // Actual help file name: Microsoft.PowerShell.Commands.Management.dll-Help.xml // Make sure that the assembly name contains more than '.ni.dll' - string assemblyName = helpFile.Replace(".ni.dll-Help.xml", ""); + string assemblyName = helpFile.Replace(".ni.dll-Help.xml", string.Empty); - if (!String.IsNullOrEmpty(assemblyName)) + if (!string.IsNullOrEmpty(assemblyName)) { // In the first try, we remove '.ni' from the assembly name and we attempt to find the corresponding help file. string helpFileName = cmdletInfo.HelpFile.Replace(".ni.dll-Help.xml", ".dll-Help.xml"); location = GetHelpFile(helpFileName, cmdletInfo); - if (String.IsNullOrEmpty(location)) + if (string.IsNullOrEmpty(location)) { // If the help file could not be found, then it is possible that the actual assembly name is something like // .ni.dll, e.g., MyAssembly.ni.dll, so let's try to find the original help file in the cmdlet metadata. @@ -687,7 +696,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) for (int i = 0; i < doc.ChildNodes.Count; i++) { XmlNode node = doc.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase)) { helpItemsNode = node; break; @@ -710,7 +719,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++) { XmlNode node = helpItemsNode.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.LocalName, "command", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "command", StringComparison.OrdinalIgnoreCase)) { MamlCommandHelpInfo helpInfo = null; @@ -726,7 +735,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) } } - if (node.NodeType == XmlNodeType.Element && String.Compare(node.Name, "UserDefinedData", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "UserDefinedData", StringComparison.OrdinalIgnoreCase)) { UserDefinedHelpData userDefinedHelpData = UserDefinedHelpData.Load(node); @@ -748,7 +757,7 @@ private void ProcessUserDefinedHelpData(string mshSnapInId, UserDefinedHelpData if (userDefinedHelpData == null) return; - if (String.IsNullOrEmpty(userDefinedHelpData.Name)) + if (string.IsNullOrEmpty(userDefinedHelpData.Name)) return; HelpInfo helpInfo = GetFromCommandCache(mshSnapInId, userDefinedHelpData.Name, HelpCategory.Cmdlet); @@ -756,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); @@ -769,9 +776,9 @@ private void ProcessUserDefinedHelpData(string mshSnapInId, UserDefinedHelpData /// /// Gets the HelpInfo object corresponding to the command. /// - /// help file identifier (either name of PSSnapIn or simply full path to help file) + /// Help file identifier (either name of PSSnapIn or simply full path to help file). /// Name of the command. - /// + /// /// HelpInfo object. private HelpInfo GetFromCommandCache(string helpFileIdentifier, string commandName, HelpCategory helpCategory) { @@ -786,7 +793,7 @@ private HelpInfo GetFromCommandCache(string helpFileIdentifier, string commandNa HelpInfo result = GetCache(key); // Win8: Win8:477680: When Function/Workflow Use External Help, Category Property is "Cmdlet" - if ((null != result) && (result.HelpCategory != helpCategory)) + if ((result != null) && (result.HelpCategory != helpCategory)) { MamlCommandHelpInfo original = (MamlCommandHelpInfo)result; result = original.Copy(helpCategory); @@ -798,20 +805,20 @@ private HelpInfo GetFromCommandCache(string helpFileIdentifier, string commandNa /// /// Gets the HelpInfo object corresponding to the CommandInfo. /// - /// help file identifier (simply full path to help file) + /// Help file identifier (simply full path to help file). /// /// HelpInfo object. private HelpInfo GetFromCommandCache(string helpFileIdentifier, CommandInfo commandInfo) { - Debug.Assert(null != commandInfo, "commandInfo cannot be null"); + Debug.Assert(commandInfo != null, "commandInfo cannot be null"); HelpInfo result = GetFromCommandCache(helpFileIdentifier, commandInfo.Name, commandInfo.HelpCategory); - if (null == result) + if (result == null) { // check if the command is prefixed and try retrieving help by removing the prefix if ((commandInfo.Module != null) && (!string.IsNullOrEmpty(commandInfo.Prefix))) { MamlCommandHelpInfo newMamlHelpInfo = GetFromCommandCacheByRemovingPrefix(helpFileIdentifier, commandInfo); - if (null != newMamlHelpInfo) + if (newMamlHelpInfo != null) { // caching the changed help content under the prefixed name for faster // retrieval later. @@ -833,7 +840,7 @@ private HelpInfo GetFromCommandCache(string helpFileIdentifier, CommandInfo comm /// private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo) { - Debug.Assert(null != cmdletInfo, "cmdletInfo cannot be null"); + Debug.Assert(cmdletInfo != null, "cmdletInfo cannot be null"); HelpInfo result = GetFromCommandCache(cmdletInfo.ModuleName, cmdletInfo.Name, cmdletInfo.HelpCategory); if (result == null) { @@ -842,7 +849,7 @@ private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo) { MamlCommandHelpInfo newMamlHelpInfo = GetFromCommandCacheByRemovingPrefix(cmdletInfo.ModuleName, cmdletInfo); - if (null != newMamlHelpInfo) + if (newMamlHelpInfo != null) { // Noun exists only for cmdlets...since prefix will change the Noun, updating // the help content accordingly @@ -854,6 +861,7 @@ private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo) { commandDetails.Properties.Remove("Noun"); } + commandDetails.Properties.Add(new PSNoteProperty("Noun", cmdletInfo.Noun)); } @@ -871,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 @@ -884,15 +892,14 @@ private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo) /// private MamlCommandHelpInfo GetFromCommandCacheByRemovingPrefix(string helpIdentifier, CommandInfo cmdInfo) { - Dbg.Assert(null != cmdInfo, "cmdInfo cannot be null"); + Dbg.Assert(cmdInfo != null, "cmdInfo cannot be null"); MamlCommandHelpInfo result = null; MamlCommandHelpInfo originalHelpInfo = GetFromCommandCache(helpIdentifier, Microsoft.PowerShell.Commands.ModuleCmdletBase.RemovePrefixFromCommandName(cmdInfo.Name, cmdInfo.Prefix), cmdInfo.HelpCategory) as MamlCommandHelpInfo; - - if (null != originalHelpInfo) + if (originalHelpInfo != null) { result = originalHelpInfo.Copy(); // command's name can be changed using -Prefix while importing module.To give better user experience for @@ -902,6 +909,7 @@ private MamlCommandHelpInfo GetFromCommandCacheByRemovingPrefix(string helpIdent { result.FullHelp.Properties.Remove("Name"); } + result.FullHelp.Properties.Add(new PSNoteProperty("Name", cmdInfo.Name)); if (result.FullHelp.Properties["Details"] != null && @@ -916,6 +924,7 @@ private MamlCommandHelpInfo GetFromCommandCacheByRemovingPrefix(string helpIdent { commandDetails.Properties.Remove("Name"); } + commandDetails.Properties.Add(new PSNoteProperty("Name", cmdInfo.Name)); // Note we made the change to a copy..so assigning the copy back to @@ -941,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); @@ -963,15 +978,14 @@ private void AddToCommandCache(string mshSnapInId, string cmdletName, MamlComman /// a. If the help file has an extension .maml. /// b. If HelpItems node (which should be the top node of any command help file) /// has an attribute "schema" with value "maml", its content is in maml - /// schema - /// + /// schema. /// /// /// /// internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode) { - if (helpFile.EndsWith(".maml", StringComparison.CurrentCultureIgnoreCase)) + if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase)) return true; if (helpItemsNode.Attributes == null) @@ -992,7 +1006,7 @@ internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode) /// /// Search help for a specific target. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content of all cmdlets. /// Otherwise, searches for pattern in the cmdlet names. @@ -1014,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 + "*"); } @@ -1043,8 +1057,8 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool int countOfHelpInfoObjectsFound = 0; // this is for avoiding duplicate result from help output. - Hashtable hashtable = new Hashtable(StringComparer.OrdinalIgnoreCase); - Hashtable hiddenCommands = new Hashtable(StringComparer.OrdinalIgnoreCase); + var set = new HashSet(StringComparer.OrdinalIgnoreCase); + var hiddenCommands = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string pattern in patternList) { CommandSearcher searcher = GetCommandSearcherForSearch(pattern, _context); @@ -1061,20 +1075,18 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool HelpInfo helpInfo = GetHelpInfo(current, !decoratedSearch, searchOnlyContent); string helpName = GetHelpName(current); - if (helpInfo != null && !String.IsNullOrEmpty(helpName)) + if (helpInfo != null && !string.IsNullOrEmpty(helpName)) { if (!SessionState.IsVisible(helpRequest.CommandOrigin, current)) { // this command is not visible to the user (from CommandOrigin) so // dont show help topic for it. - if (!hiddenCommands.ContainsKey(helpName)) - { - hiddenCommands.Add(helpName, null); - } + hiddenCommands.Add(helpName); + continue; } - if (hashtable.ContainsKey(helpName)) + if (set.Contains(helpName)) continue; // filter out the helpInfo object depending on user request @@ -1089,7 +1101,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool continue; } - hashtable.Add(helpName, null); + set.Add(helpName); countOfHelpInfoObjectsFound++; yield return helpInfo; @@ -1117,12 +1129,12 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool HelpInfo helpInfo = GetHelpInfo(current, !decoratedSearch, searchOnlyContent); string helpName = GetHelpName(current); - if (helpInfo != null && !String.IsNullOrEmpty(helpName)) + if (helpInfo != null && !string.IsNullOrEmpty(helpName)) { - if (hashtable.ContainsKey(helpName)) + if (set.Contains(helpName)) continue; - if (hiddenCommands.ContainsKey(helpName)) + if (hiddenCommands.Contains(helpName)) continue; // filter out the helpInfo object depending on user request @@ -1137,7 +1149,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool continue; } - hashtable.Add(helpName, null); + set.Add(helpName); countOfHelpInfoObjectsFound++; yield return helpInfo; @@ -1162,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)) @@ -1190,11 +1202,11 @@ private static bool Match(HelpInfo helpInfo, HelpRequest helpRequest, CommandInf private static bool Match(string target, string pattern) { - if (String.IsNullOrEmpty(pattern)) + if (string.IsNullOrEmpty(pattern)) return true; - if (String.IsNullOrEmpty(target)) - target = ""; + if (string.IsNullOrEmpty(target)) + target = string.Empty; WildcardPattern matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); @@ -1205,8 +1217,8 @@ private static bool Match(string target, string pattern) /// Checks whether matches any of the patterns /// present in /// - /// content to search in. - /// string patterns to look for. + /// Content to search in. + /// String patterns to look for. /// /// true if contains any of the patterns /// present in @@ -1233,16 +1245,16 @@ 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 - /// Help request object - /// The result helpInfo objects after processing + /// HelpInfo that is forwarded over. + /// Help request object. + /// The result helpInfo objects after processing. internal override IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest) { - HelpCategory categoriesHandled = (HelpCategory.Alias - | HelpCategory.ExternalScript | HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand | HelpCategory.Workflow); + const HelpCategory categoriesHandled = (HelpCategory.Alias + | HelpCategory.ExternalScript | HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand); if ((helpInfo.HelpCategory & categoriesHandled) != 0) { @@ -1266,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 } } @@ -1330,7 +1342,6 @@ internal virtual CommandSearcher GetCommandSearcherForSearch(string pattern, Exe CommandTypes.Cmdlet, context); - return searcher; } @@ -1351,13 +1362,13 @@ 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() { } - internal Dictionary Properties { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal Dictionary Properties { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); private string _name = null; @@ -1393,7 +1404,7 @@ internal static UserDefinedHelpData Load(XmlNode dataNode) userDefinedHelpData._name = name; - if (String.IsNullOrEmpty(userDefinedHelpData.Name)) + if (string.IsNullOrEmpty(userDefinedHelpData.Name)) return null; return userDefinedHelpData; diff --git a/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs b/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs index 610bdb5795c..bce76b005a2 100644 --- a/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs +++ b/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs @@ -1,26 +1,24 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using System.Globalization; -using System.Text; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections; using System.Diagnostics; +using System.Globalization; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; using System.Reflection; +using System.Text; namespace System.Management.Automation.Help { /// - /// Positional parameter comparer + /// Positional parameter comparer. /// internal class PositionalParameterComparer : IComparer { /// - /// /// /// /// @@ -42,14 +40,14 @@ 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 string TypeNameForDefaultHelp = "ExtendedCmdletHelpInfo"; + internal static readonly string TypeNameForDefaultHelp = "ExtendedCmdletHelpInfo"; /// - /// Generates a HelpInfo PSObject from a CmdletInfo object + /// Generates a HelpInfo PSObject from a CmdletInfo object. /// - /// command info - /// HelpInfo PSObject + /// Command info. + /// HelpInfo PSObject. internal static PSObject GetPSObjectFromCmdletInfo(CommandInfo input) { // Create a copy of commandInfo for GetCommandCommand so that we can generate parameter @@ -59,28 +57,24 @@ 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"); - if (commandInfo is CmdletInfo) + if (commandInfo is CmdletInfo cmdletInfo) { - CmdletInfo cmdletInfo = commandInfo as CmdletInfo; bool common = false; - bool commonWorkflow = false; if (cmdletInfo.Parameters != null) { common = HasCommonParameters(cmdletInfo.Parameters); - commonWorkflow = ((cmdletInfo.CommandType & CommandTypes.Workflow) == CommandTypes.Workflow); } obj.Properties.Add(new PSNoteProperty("CommonParameters", common)); - obj.Properties.Add(new PSNoteProperty("WorkflowCommonParameters", commonWorkflow)); AddDetailsProperties(obj, cmdletInfo.Name, cmdletInfo.Noun, cmdletInfo.Verb, TypeNameForDefaultHelp); - AddSyntaxProperties(obj, cmdletInfo.Name, cmdletInfo.ParameterSets, common, commonWorkflow, TypeNameForDefaultHelp); - AddParametersProperties(obj, cmdletInfo.Parameters, common, commonWorkflow, TypeNameForDefaultHelp); + AddSyntaxProperties(obj, cmdletInfo.Name, cmdletInfo.ParameterSets, common, TypeNameForDefaultHelp); + AddParametersProperties(obj, cmdletInfo.Parameters, common, TypeNameForDefaultHelp); AddInputTypesProperties(obj, cmdletInfo.Parameters); AddRelatedLinksProperties(obj, commandInfo.CommandMetadata.HelpUri); @@ -106,17 +100,14 @@ internal static PSObject GetPSObjectFromCmdletInfo(CommandInfo input) obj.Properties.Add(new PSNoteProperty("PSSnapIn", cmdletInfo.PSSnapIn)); } - else if (commandInfo is FunctionInfo) + else if (commandInfo is FunctionInfo funcInfo) { - FunctionInfo funcInfo = commandInfo as FunctionInfo; bool common = HasCommonParameters(funcInfo.Parameters); - bool commonWorkflow = ((commandInfo.CommandType & CommandTypes.Workflow) == CommandTypes.Workflow); obj.Properties.Add(new PSNoteProperty("CommonParameters", common)); - obj.Properties.Add(new PSNoteProperty("WorkflowCommonParameters", commonWorkflow)); - AddDetailsProperties(obj, funcInfo.Name, String.Empty, String.Empty, TypeNameForDefaultHelp); - AddSyntaxProperties(obj, funcInfo.Name, funcInfo.ParameterSets, common, commonWorkflow, TypeNameForDefaultHelp); - AddParametersProperties(obj, funcInfo.Parameters, common, commonWorkflow, TypeNameForDefaultHelp); + AddDetailsProperties(obj, funcInfo.Name, string.Empty, string.Empty, TypeNameForDefaultHelp); + AddSyntaxProperties(obj, funcInfo.Name, funcInfo.ParameterSets, common, TypeNameForDefaultHelp); + AddParametersProperties(obj, funcInfo.Parameters, common, TypeNameForDefaultHelp); AddInputTypesProperties(obj, funcInfo.Parameters); AddRelatedLinksProperties(obj, funcInfo.CommandMetadata.HelpUri); @@ -146,7 +137,7 @@ internal static PSObject GetPSObjectFromCmdletInfo(CommandInfo input) obj.Properties.Add(new PSNoteProperty("examples", null)); obj.Properties.Add(new PSNoteProperty("Synopsis", commandInfo.Syntax)); obj.Properties.Add(new PSNoteProperty("ModuleName", commandInfo.ModuleName)); - obj.Properties.Add(new PSNoteProperty("nonTerminatingErrors", String.Empty)); + obj.Properties.Add(new PSNoteProperty("nonTerminatingErrors", string.Empty)); obj.Properties.Add(new PSNoteProperty("xmlns:command", "http://schemas.microsoft.com/maml/dev/command/2004/10")); obj.Properties.Add(new PSNoteProperty("xmlns:dev", "http://schemas.microsoft.com/maml/dev/2004/10")); obj.Properties.Add(new PSNoteProperty("xmlns:maml", "http://schemas.microsoft.com/maml/2004/10")); @@ -155,21 +146,21 @@ internal static PSObject GetPSObjectFromCmdletInfo(CommandInfo input) } /// - /// Adds the details properties + /// Adds the details properties. /// - /// HelpInfo object - /// command name - /// command noun - /// command verb - /// type name for help - /// synopsis + /// HelpInfo object. + /// Command name. + /// Command noun. + /// Command verb. + /// Type name for help. + /// Synopsis. internal static void AddDetailsProperties(PSObject obj, string name, string noun, string verb, string typeNameForHelp, string synopsis = null) { 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)); @@ -189,36 +180,34 @@ internal static void AddDetailsProperties(PSObject obj, string name, string noun } /// - /// Adds the syntax properties + /// Adds the syntax properties. /// - /// HelpInfo object - /// command name - /// parameter sets - /// common parameters - /// common workflow parameters - /// type name for help - internal static void AddSyntaxProperties(PSObject obj, string cmdletName, ReadOnlyCollection parameterSets, bool common, bool commonWorkflow, string typeNameForHelp) + /// HelpInfo object. + /// Command name. + /// Parameter sets. + /// Common parameters. + /// Type name for help. + internal static void AddSyntaxProperties(PSObject obj, string cmdletName, ReadOnlyCollection parameterSets, bool common, string typeNameForHelp) { 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, commonWorkflow, typeNameForHelp); + AddSyntaxItemProperties(mshObject, cmdletName, parameterSets, common, typeNameForHelp); obj.Properties.Add(new PSNoteProperty("Syntax", mshObject)); } /// - /// Add the syntax item properties + /// Add the syntax item properties. /// - /// HelpInfo object - /// cmdlet name, you can't get this from parameterSets - /// a collection of parameter sets - /// common parameters - /// common workflow parameters - /// type name for help - private static void AddSyntaxItemProperties(PSObject obj, string cmdletName, ReadOnlyCollection parameterSets, bool common, bool commonWorkflow, string typeNameForHelp) + /// HelpInfo object. + /// Cmdlet name, you can't get this from parameterSets. + /// A collection of parameter sets. + /// Common parameters. + /// Type name for help. + private static void AddSyntaxItemProperties(PSObject obj, string cmdletName, ReadOnlyCollection parameterSets, bool common, string typeNameForHelp) { ArrayList mshObjects = new ArrayList(); @@ -227,22 +216,19 @@ 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)); - mshObject.Properties.Add(new PSNoteProperty("WorkflowCommonParameters", commonWorkflow)); Collection parameters = new Collection(); // GenerateParameters parameters in display order // ie., Positional followed by // Named Mandatory (in alpha numeric) followed by // Named (in alpha numeric) - parameterSet.GenerateParametersInDisplayOrder(commonWorkflow, - parameters.Add, - delegate { }); + parameterSet.GenerateParametersInDisplayOrder(parameters.Add, delegate { }); - AddSyntaxParametersProperties(mshObject, parameters, common, commonWorkflow, parameterSet.Name); + AddSyntaxParametersProperties(mshObject, parameters, common, parameterSet.Name); mshObjects.Add(mshObject); } @@ -253,28 +239,22 @@ private static void AddSyntaxItemProperties(PSObject obj, string cmdletName, Rea /// /// Add the syntax parameters properties (these parameters are used to create the syntax section) /// - /// HelpInfo object + /// HelpInfo object. /// /// a collection of parameters in display order /// ie., Positional followed by /// Named Mandatory (in alpha numeric) followed by /// Named (in alpha numeric) /// - /// common parameters - /// common workflow - /// Name of the parameter set for which the syntax is generated + /// Common parameters. + /// Name of the parameter set for which the syntax is generated. private static void AddSyntaxParametersProperties(PSObject obj, IEnumerable parameters, - bool common, bool commonWorkflow, string parameterSetName) + bool common, string parameterSetName) { ArrayList mshObjects = new ArrayList(); foreach (CommandParameterInfo parameter in parameters) { - if (commonWorkflow && IsCommonWorkflowParameter(parameter.Name)) - { - continue; - } - if (common && Cmdlet.CommonParameters.Contains(parameter.Name)) { continue; @@ -283,7 +263,7 @@ private static void AddSyntaxParametersProperties(PSObject obj, IEnumerable attributes = new Collection(parameter.Attributes); @@ -307,19 +287,19 @@ private static void AddSyntaxParametersProperties(PSObject obj, IEnumerable /// Adds a parameter value group (for enums) /// - /// object - /// parameter group values + /// Object. + /// Parameter group values. private static void AddParameterValueGroupProperties(PSObject obj, string[] values) { 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); @@ -370,17 +350,16 @@ private static void AddParameterValueGroupProperties(PSObject obj, string[] valu /// /// Add the parameters properties (these parameters are used to create the parameters section) /// - /// HelpInfo object - /// parameters - /// common parameters - /// common workflow parameters - /// type name for help - internal static void AddParametersProperties(PSObject obj, Dictionary parameters, bool common, bool commonWorkflow, string typeNameForHelp) + /// HelpInfo object. + /// Parameters. + /// Common parameters. + /// Type name for help. + internal static void AddParametersProperties(PSObject obj, Dictionary parameters, bool common, string typeNameForHelp) { PSObject paramsObject = new PSObject(); paramsObject.TypeNames.Clear(); - paramsObject.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#parameters", typeNameForHelp)); + paramsObject.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{typeNameForHelp}#parameters")); ArrayList paramObjects = new ArrayList(); @@ -398,11 +377,6 @@ internal static void AddParametersProperties(PSObject obj, Dictionary - /// Adds the parameter properties + /// Adds the parameter properties. /// - /// HelpInfo object - /// parameter name - /// parameter aliases - /// is dynamic parameter? - /// parameter type - /// parameter attributes - /// Name of the parameter set for which the syntax is generated + /// HelpInfo object. + /// Parameter name. + /// Parameter aliases. + /// Is dynamic parameter? + /// Parameter type. + /// Parameter attributes. + /// Name of the parameter set for which the syntax is generated. private static void AddParameterProperties(PSObject obj, string name, Collection aliases, bool dynamic, Type type, Collection attributes, string parameterSetName = null) { @@ -442,13 +416,14 @@ private static void AddParameterProperties(PSObject obj, string name, Collection if (attribs.Count == 0) { - obj.Properties.Add(new PSNoteProperty("required", "")); - obj.Properties.Add(new PSNoteProperty("pipelineInput", "")); - obj.Properties.Add(new PSNoteProperty("isDynamic", "")); - obj.Properties.Add(new PSNoteProperty("parameterSetName", "")); - obj.Properties.Add(new PSNoteProperty("description", "")); - obj.Properties.Add(new PSNoteProperty("position", "")); - obj.Properties.Add(new PSNoteProperty("aliases", "")); + obj.Properties.Add(new PSNoteProperty("required", string.Empty)); + obj.Properties.Add(new PSNoteProperty("pipelineInput", string.Empty)); + obj.Properties.Add(new PSNoteProperty("isDynamic", string.Empty)); + obj.Properties.Add(new PSNoteProperty("parameterSetName", string.Empty)); + obj.Properties.Add(new PSNoteProperty("description", string.Empty)); + obj.Properties.Add(new PSNoteProperty("position", string.Empty)); + obj.Properties.Add(new PSNoteProperty("aliases", string.Empty)); + obj.Properties.Add(new PSNoteProperty("globbing", string.Empty)); } else { @@ -468,6 +443,7 @@ private static void AddParameterProperties(PSObject obj, string name, Collection obj.Properties.Add(new PSNoteProperty("required", CultureInfo.CurrentCulture.TextInfo.ToLower(paramAttribute.Mandatory.ToString()))); obj.Properties.Add(new PSNoteProperty("pipelineInput", GetPipelineInputString(paramAttribute))); obj.Properties.Add(new PSNoteProperty("isDynamic", CultureInfo.CurrentCulture.TextInfo.ToLower(dynamic.ToString()))); + AddParameterGlobbingProperties(obj, attributes); if (paramAttribute.ParameterSetName.Equals(ParameterAttribute.AllParameterSets, StringComparison.OrdinalIgnoreCase)) { @@ -544,17 +520,38 @@ private static void AddParameterProperties(PSObject obj, string name, Collection } /// - /// Adds the parameterType properties + /// Adds the globbing properties. /// - /// HelpInfo object - /// the type of a parameter - /// the attributes of the parameter (needed to look for PSTypeName) + /// 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. + /// + /// HelpInfo object. + /// The type of a parameter. + /// The attributes of the parameter (needed to look for PSTypeName). private static void AddParameterTypeProperties(PSObject obj, Type parameterType, IEnumerable attributes) { 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)); @@ -563,11 +560,11 @@ private static void AddParameterTypeProperties(PSObject obj, Type parameterType, } /// - /// Adds the parameterValue properties + /// Adds the parameterValue properties. /// - /// HelpInfo object - /// the type of a parameter - /// the attributes of the parameter (needed to look for PSTypeName) + /// HelpInfo object. + /// The type of a parameter. + /// The attributes of the parameter (needed to look for PSTypeName). private static void AddParameterValueProperties(PSObject obj, Type parameterType, IEnumerable attributes) { PSObject mshObject; @@ -592,10 +589,10 @@ private static void AddParameterValueProperties(PSObject obj, Type parameterType } /// - /// Adds the InputTypes properties + /// Adds the InputTypes properties. /// - /// HelpInfo object - /// command parameters + /// HelpInfo object. + /// Command parameters. internal static void AddInputTypesProperties(PSObject obj, Dictionary parameters) { Collection inputs = new Collection(); @@ -608,9 +605,7 @@ internal static void AddInputTypesProperties(PSObject obj, Dictionary - /// Adds the OutputTypes properties + /// Adds the OutputTypes properties. /// - /// HelpInfo object - /// output types + /// HelpInfo object. + /// Output types. private static void AddOutputTypesProperties(PSObject obj, ReadOnlyCollection outputTypes) { PSObject returnValuesObj = new PSObject(); returnValuesObj.TypeNames.Clear(); - returnValuesObj.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#returnValues", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + returnValuesObj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#returnValues")); PSObject returnValueObj = new PSObject(); returnValueObj.TypeNames.Clear(); - returnValueObj.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#returnValue", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + returnValueObj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#returnValue")); PSObject typeObj = new PSObject(); typeObj.TypeNames.Clear(); - typeObj.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#type", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + typeObj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#type")); if (outputTypes.Count == 0) { @@ -698,11 +693,11 @@ private static void AddOutputTypesProperties(PSObject obj, ReadOnlyCollection - /// Adds the aliases properties + /// Adds the aliases properties. /// - /// HelpInfo object - /// command name - /// execution context + /// HelpInfo object. + /// Command name. + /// Execution context. private static void AddAliasesProperties(PSObject obj, string name, ExecutionContext context) { StringBuilder sb = new StringBuilder(); @@ -727,14 +722,14 @@ private static void AddAliasesProperties(PSObject obj, string name, ExecutionCon } /// - /// Adds the remarks properties + /// Adds the remarks properties. /// - /// HelpInfo object + /// HelpInfo object. /// /// private static void AddRemarksProperties(PSObject obj, string cmdletName, string helpUri) { - if (String.IsNullOrEmpty(helpUri)) + if (string.IsNullOrEmpty(helpUri)) { obj.Properties.Add(new PSNoteProperty("remarks", StringUtil.Format(HelpDisplayStrings.GetLatestHelpContentWithoutHelpUri, cmdletName))); } @@ -745,18 +740,18 @@ private static void AddRemarksProperties(PSObject obj, string cmdletName, string } /// - /// Adds the related links properties + /// Adds the related links properties. /// /// /// internal static void AddRelatedLinksProperties(PSObject obj, string relatedLink) { - if (!String.IsNullOrEmpty(relatedLink)) + if (!string.IsNullOrEmpty(relatedLink)) { PSObject navigationLinkObj = new PSObject(); navigationLinkObj.TypeNames.Clear(); - navigationLinkObj.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#navigationLinks", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + navigationLinkObj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#navigationLinks")); navigationLinkObj.Properties.Add(new PSNoteProperty("uri", relatedLink)); @@ -792,7 +787,7 @@ internal static void AddRelatedLinksProperties(PSObject obj, string relatedLink) PSObject relatedLinksObj = new PSObject(); relatedLinksObj.TypeNames.Clear(); - relatedLinksObj.TypeNames.Add(String.Format(CultureInfo.InvariantCulture, "{0}#relatedLinks", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + relatedLinksObj.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#relatedLinks")); relatedLinksObj.Properties.Add(new PSNoteProperty("navigationLink", navigationLinkValues.ToArray())); obj.Properties.Add(new PSNoteProperty("relatedLinks", relatedLinksObj)); @@ -800,10 +795,10 @@ internal static void AddRelatedLinksProperties(PSObject obj, string relatedLink) } /// - /// Gets the parameter attribute from parameter metadata + /// Gets the parameter attribute from parameter metadata. /// - /// parameter attributes - /// parameter attributes + /// Parameter attributes. + /// Collection of parameter attributes. private static Collection GetParameterAttribute(Collection attributes) { Collection paramAttributes = new Collection(); @@ -822,10 +817,10 @@ private static Collection GetParameterAttribute(Collection - /// Gets the validate set attribute from parameter metadata + /// Gets the validate set attribute from parameter metadata. /// - /// parameter attributes - /// parameter attributes + /// Parameter attributes. + /// Collection of parameter attributes. private static Collection GetValidateSetAttribute(Collection attributes) { Collection validateSetAttributes = new Collection(); @@ -844,10 +839,10 @@ private static Collection GetValidateSetAttribute(Collecti } /// - /// Gets the pipeline input type + /// Gets the pipeline input type. /// - /// parameter attribute - /// pipeline input type + /// Parameter attribute. + /// Pipeline input type. private static string GetPipelineInputString(ParameterAttribute paramAttrib) { Debug.Assert(paramAttrib != null); @@ -858,10 +853,12 @@ private static string GetPipelineInputString(ParameterAttribute paramAttrib) { values.Add(StringUtil.Format(HelpDisplayStrings.PipelineByValue)); } + if (paramAttrib.ValueFromPipelineByPropertyName) { values.Add(StringUtil.Format(HelpDisplayStrings.PipelineByPropertyName)); } + if (paramAttrib.ValueFromRemainingArguments) { values.Add(StringUtil.Format(HelpDisplayStrings.PipelineFromRemainingArguments)); @@ -887,16 +884,16 @@ private static string GetPipelineInputString(ParameterAttribute paramAttrib) } } - sb.Append(")"); + sb.Append(')'); return sb.ToString(); } /// - /// Checks if a set of parameters contains any of the common parameters + /// Checks if a set of parameters contains any of the common parameters. /// - /// parameters to check - /// true if it contains common parameters, false otherwise + /// Parameters to check. + /// True if it contains common parameters, false otherwise. internal static bool HasCommonParameters(Dictionary parameters) { Collection commonParams = new Collection(); @@ -913,23 +910,7 @@ internal static bool HasCommonParameters(Dictionary p } /// - /// Checks if a parameter is a common workflow parameter - /// - /// parameter name - /// true if it is a common parameter, false if not - private static bool IsCommonWorkflowParameter(string name) - { - foreach (string parameter in CommonParameters.CommonWorkflowParameters) - { - if (name == parameter) - return true; - } - - return false; - } - - /// - /// Checks if the module contains HelpInfoUri + /// Checks if the module contains HelpInfoUri. /// /// /// @@ -937,7 +918,7 @@ private static bool IsCommonWorkflowParameter(string name) private static bool HasHelpInfoUri(PSModuleInfo module, string moduleName) { // The core module is really a SnapIn, so module will be null - if (!String.IsNullOrEmpty(moduleName) && moduleName.Equals(InitialSessionState.CoreModule, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(moduleName) && moduleName.Equals(InitialSessionState.CoreModule, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -947,7 +928,7 @@ private static bool HasHelpInfoUri(PSModuleInfo module, string moduleName) return false; } - return !String.IsNullOrEmpty(module.HelpInfoUri); + return !string.IsNullOrEmpty(module.HelpInfoUri); } } } diff --git a/src/System.Management.Automation/help/DefaultHelpProvider.cs b/src/System.Management.Automation/help/DefaultHelpProvider.cs index 4586a78a400..9bbaea3f08d 100644 --- a/src/System.Management.Automation/help/DefaultHelpProvider.cs +++ b/src/System.Management.Automation/help/DefaultHelpProvider.cs @@ -1,23 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; namespace System.Management.Automation { /// - /// /// Class DefaultHelpProvider implement the help provider for commands. /// /// Command Help information are stored in 'help.xml' files. Location of these files /// can be found from CommandDiscovery. - /// /// internal class DefaultHelpProvider : HelpFileHelpProvider { /// - /// Constructor for HelpProvider + /// Constructor for HelpProvider. /// internal DefaultHelpProvider(HelpSystem helpSystem) : base(helpSystem) @@ -27,7 +24,6 @@ internal DefaultHelpProvider(HelpSystem helpSystem) #region Common Properties /// - /// /// /// internal override string Name @@ -39,7 +35,6 @@ internal override string Name } /// - /// /// /// internal override HelpCategory HelpCategory @@ -55,9 +50,8 @@ internal override HelpCategory HelpCategory #region Help Provider Interface /// - /// /// - /// help request object + /// Help request object. /// internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { diff --git a/src/System.Management.Automation/help/DscResourceHelpProvider.cs b/src/System.Management.Automation/help/DscResourceHelpProvider.cs index ab363ca5e17..c33a03dc899 100644 --- a/src/System.Management.Automation/help/DscResourceHelpProvider.cs +++ b/src/System.Management.Automation/help/DscResourceHelpProvider.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Xml; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -15,7 +15,7 @@ namespace System.Management.Automation internal class DscResourceHelpProvider : HelpProviderWithCache { /// - /// Constructor for DscResourceHelpProvider + /// Constructor for DscResourceHelpProvider. /// internal DscResourceHelpProvider(HelpSystem helpSystem) : base(helpSystem) @@ -24,7 +24,7 @@ internal DscResourceHelpProvider(HelpSystem helpSystem) } /// - /// Execution context of the HelpSystem + /// Execution context of the HelpSystem. /// private readonly ExecutionContext _context; @@ -32,7 +32,6 @@ internal DscResourceHelpProvider(HelpSystem helpSystem) /// This is a hashtable to track which help files are loaded already. /// /// This will avoid one help file getting loaded again and again. - /// /// private readonly Hashtable _helpFiles = new Hashtable(); @@ -50,7 +49,7 @@ internal override string Name } /// - /// Supported Help Categories + /// Supported Help Categories. /// internal override HelpCategory HelpCategory { @@ -140,13 +139,13 @@ private IEnumerable GetHelpInfo(DscResourceSearcher searcher) { moduleName = current.Module.Name; } - else if (!String.IsNullOrEmpty(moduleDir)) + else if (!string.IsNullOrEmpty(moduleDir)) { - string[] splitPath = moduleDir.Split(Utils.Separators.Backslash); + string[] splitPath = moduleDir.Split('\\'); moduleName = splitPath[splitPath.Length - 1]; } - if (!String.IsNullOrEmpty(moduleName) && !String.IsNullOrEmpty(moduleDir)) + if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir)) { string helpFileToFind = moduleName + "-Help.xml"; @@ -173,17 +172,16 @@ private IEnumerable GetHelpInfo(DscResourceSearcher searcher) /// a. If the help file has an extension .maml. /// b. If HelpItems node (which should be the top node of any command help file) /// has an attribute "schema" with value "maml", its content is in maml - /// schema - /// + /// schema. /// /// File name. /// Nodes to check. /// internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode) { - Debug.Assert(!String.IsNullOrEmpty(helpFile), "helpFile cannot be null."); + Debug.Assert(!string.IsNullOrEmpty(helpFile), "helpFile cannot be null."); - if (helpFile.EndsWith(".maml", StringComparison.CurrentCultureIgnoreCase)) + if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase)) return true; if (helpItemsNode.Attributes == null) @@ -213,9 +211,9 @@ private HelpInfo GetHelpInfoFromHelpFile(DscResourceInfo resourceInfo, string he if (!File.Exists(helpFile)) return null; - if (!String.IsNullOrEmpty(helpFile)) + if (!string.IsNullOrEmpty(helpFile)) { - //Load the help file only once. Then use it from the cache. + // Load the help file only once. Then use it from the cache. if (!_helpFiles.Contains(helpFile)) { LoadHelpFile(helpFile, helpFile, resourceInfo.Name, reportErrors); @@ -230,7 +228,7 @@ private HelpInfo GetHelpInfoFromHelpFile(DscResourceInfo resourceInfo, string he /// /// Gets the HelpInfo object corresponding to the command. /// - /// help file identifier (either name of PSSnapIn or simply full path to help file) + /// Help file identifier (either name of PSSnapIn or simply full path to help file). /// Help Category for search. /// HelpInfo object. private HelpInfo GetFromResourceHelpCache(string helpFileIdentifier, HelpCategory helpCategory) @@ -281,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)) { @@ -301,8 +299,8 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier, string com /// private void LoadHelpFile(string helpFile, string helpFileIdentifier) { - Dbg.Assert(!String.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty."); - Dbg.Assert(!String.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty."); + Dbg.Assert(!string.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty."); + Dbg.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty."); XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument( new FileInfo(helpFile), @@ -319,7 +317,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) for (int i = 0; i < doc.ChildNodes.Count; i++) { XmlNode node = doc.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase)) { helpItemsNode = node; break; @@ -345,7 +343,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) string nodeLocalName = node.LocalName; - bool isDscResource = (String.Compare(nodeLocalName, "dscResource", StringComparison.OrdinalIgnoreCase) == 0); + bool isDscResource = (string.Equals(nodeLocalName, "dscResource", StringComparison.OrdinalIgnoreCase)); if (node.NodeType == XmlNodeType.Element && isDscResource) { @@ -370,4 +368,4 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/help/HelpCategoryInvalidException.cs b/src/System.Management.Automation/help/HelpCategoryInvalidException.cs index df48e634250..8ab4fa37ca6 100644 --- a/src/System.Management.Automation/help/HelpCategoryInvalidException.cs +++ b/src/System.Management.Automation/help/HelpCategoryInvalidException.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 @@ -17,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(); @@ -33,7 +32,8 @@ public HelpCategoryInvalidException(string helpCategory) : base() /// /// Initializes a new instance of the HelpCategoryInvalidException class. /// - public HelpCategoryInvalidException() : base() + public HelpCategoryInvalidException() + : base() { CreateErrorRecord(); } @@ -42,9 +42,11 @@ public HelpCategoryInvalidException() : base() /// Initializes a new instance of the HelpCategoryInvalidException class. /// /// 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) + /// The inner exception of this exception. + public HelpCategoryInvalidException(string helpCategory, Exception innerException) + : base( + (innerException != null) ? innerException.Message : string.Empty, + innerException) { _helpCategory = helpCategory; CreateErrorRecord(); @@ -56,7 +58,7 @@ public HelpCategoryInvalidException(string helpCategory, Exception innerExceptio private void CreateErrorRecord() { _errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(this), "HelpCategoryInvalid", ErrorCategory.InvalidArgument, null); - _errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpCategoryInvalidException).GetTypeInfo().Assembly, "HelpErrors", "HelpCategoryInvalid", _helpCategory); + _errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpCategoryInvalidException).Assembly, "HelpErrors", "HelpCategoryInvalid", _helpCategory); } private ErrorRecord _errorRecord; @@ -73,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. @@ -110,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("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 95951fd17cb..3bed2a26820 100644 --- a/src/System.Management.Automation/help/HelpCommands.cs +++ b/src/System.Management.Automation/help/HelpCommands.cs @@ -1,20 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Management.Automation.Runspaces; -using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; -using System.Management.Automation; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Management.Automation.Internal; +using System.IO; +using System.Linq; +using System.Management.Automation; using System.Management.Automation.Help; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; -using System.IO; #if !UNIX using Microsoft.Win32; #endif @@ -22,13 +21,13 @@ namespace Microsoft.PowerShell.Commands { /// - /// This class implements get-help command + /// This class implements get-help command. /// - [Cmdlet(VerbsCommon.Get, "Help", DefaultParameterSetName = "AllUsersView", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113316")] + [Cmdlet(VerbsCommon.Get, "Help", DefaultParameterSetName = "AllUsersView", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096483")] public sealed class GetHelpCommand : PSCmdlet { /// - /// Help Views + /// Help Views. /// internal enum HelpView { @@ -39,7 +38,7 @@ internal enum HelpView } /// - /// Default constructor for the GetHelpCommand class + /// Default constructor for the GetHelpCommand class. /// public GetHelpCommand() { @@ -48,11 +47,11 @@ public GetHelpCommand() #region Cmdlet Parameters /// - /// Target to search for help + /// Target to search for help. /// [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty()] - public string Name { get; set; } = ""; + public string Name { get; set; } = string.Empty; /// /// Path to provider location that user is curious about. @@ -61,39 +60,18 @@ public GetHelpCommand() public string Path { get; set; } /// - /// List of help categories to search for help + /// List of help categories to search for help. /// [Parameter] [ValidateSet( - "Alias", "Cmdlet", "Provider", "General", "FAQ", "Glossary", "HelpFile", "ScriptCommand", "Function", "Filter", "ExternalScript", "All", "DefaultHelp", "Workflow", "DscResource", "Class", "Configuration", + "Alias", "Cmdlet", "Provider", "General", "FAQ", "Glossary", "HelpFile", "ScriptCommand", "Function", "Filter", "ExternalScript", "All", "DefaultHelp", "DscResource", "Class", "Configuration", IgnoreCase = true)] public string[] Category { get; set; } - /// - /// List of Component's to search on. - /// - /// - [Parameter] - public string[] Component { get; set; } = null; - - /// - /// List of Functionality's to search on. - /// - /// - [Parameter] - public string[] Functionality { get; set; } = null; + private readonly string _provider = string.Empty; /// - /// List of Role's to search on. - /// - /// - [Parameter] - public string[] Role { get; set; } = null; - - private string _provider = ""; - - /// - /// Changes the view of HelpObject returned + /// Changes the view of HelpObject returned. /// /// /// Currently we support following views: @@ -120,7 +98,7 @@ public SwitchParameter Detailed } /// - /// Changes the view of HelpObject returned + /// Changes the view of HelpObject returned. /// /// /// Currently we support following views: @@ -147,7 +125,7 @@ public SwitchParameter Full } /// - /// Changes the view of HelpObject returned + /// Changes the view of HelpObject returned. /// /// /// Currently we support following views: @@ -179,7 +157,25 @@ public SwitchParameter Examples /// Support WildCard strings as supported by WildcardPattern class. /// [Parameter(ParameterSetName = "Parameters", Mandatory = true)] - public string Parameter { set; get; } + public string[] Parameter { get; set; } + + /// + /// Gets and sets list of Component's to search on. + /// + [Parameter] + public string[] Component { get; set; } + + /// + /// Gets and sets list of Functionality's to search on. + /// + [Parameter] + public string[] Functionality { get; set; } + + /// + /// Gets and sets list of Role's to search on. + /// + [Parameter] + public string[] Role { get; set; } /// /// This parameter,if true, will direct get-help cmdlet to @@ -189,6 +185,11 @@ public SwitchParameter Examples [Parameter(ParameterSetName = "Online", Mandatory = true)] public SwitchParameter Online { + get + { + return _showOnlineHelp; + } + set { _showOnlineHelp = value; @@ -197,18 +198,41 @@ public SwitchParameter Online VerifyParameterForbiddenInRemoteRunspace(this, "Online"); } } + } + + private bool _showOnlineHelp; + +#if !UNIX + private GraphicalHostReflectionWrapper graphicalHostReflectionWrapper; + private bool showWindow; + + /// + /// Gets or sets a value indicating whether the help should be displayed in a separate window. + /// + [Parameter(ParameterSetName = "ShowWindow", Mandatory = true)] + public SwitchParameter ShowWindow + { get { - return _showOnlineHelp; + return showWindow; + } + + set + { + showWindow = value; + if (showWindow) + { + VerifyParameterForbiddenInRemoteRunspace(this, "ShowWindow"); + } } } - private bool _showOnlineHelp; +#endif // 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 @@ -217,34 +241,41 @@ public SwitchParameter Online #region Cmdlet API implementation /// - /// Implements the BeginProcessing() method for get-help command + /// Implements the BeginProcessing() method for get-help command. /// protected override void BeginProcessing() { - _timer.Start(); - - if (!Online.IsPresent && UpdatableHelpSystem.ShouldPromptToUpdateHelp() && HostUtilities.IsProcessInteractive(MyInvocation) && HasInternetConnection()) - { - if (ShouldContinue(HelpDisplayStrings.UpdateHelpPromptBody, HelpDisplayStrings.UpdateHelpPromptTitle)) - { - System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("Update-Help").Invoke(); #if LEGACYTELEMETRY - _updatedHelp = true; + _timer.Start(); #endif - } - - UpdatableHelpSystem.SetDisablePromptToUpdateHelp(); - } } /// - /// Implements the ProcessRecord() method for get-help command + /// Implements the ProcessRecord() method for get-help command. /// 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 { - this.Context.HelpSystem.OnProgress += new HelpSystem.HelpProgressHandler(HelpSystem_OnProgress); +#if !UNIX + if (this.ShowWindow) + { + this.graphicalHostReflectionWrapper = GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(this, "Microsoft.PowerShell.Commands.Internal.HelpWindowHelper"); + } +#endif + helpSystem.OnProgress += HelpSystem_OnProgress; bool failed = false; HelpCategory helpCategory = ToHelpCategory(Category, ref failed); @@ -270,7 +301,7 @@ protected override void ProcessRecord() // the idea is to use yield statement in the help lookup to speed up // perceived user experience....So HelpSystem.GetHelp returns an // IEnumerable.. - IEnumerable helpInfos = this.Context.HelpSystem.GetHelp(helpRequest); + IEnumerable helpInfos = helpSystem.GetHelp(helpRequest); // HelpCommand acts differently when there is just one help object and when // there are more than one object...so handling this behavior through // some variables. @@ -284,14 +315,14 @@ protected override void ProcessRecord() return; } - if (0 == countOfHelpInfos) + if (countOfHelpInfos == 0) { firstHelpInfoObject = helpInfo; } else { // write first help object only once. - if (null != firstHelpInfoObject) + if (firstHelpInfoObject != null) { WriteObjectsOrShowOnlineHelp(firstHelpInfoObject, false); firstHelpInfoObject = null; @@ -299,17 +330,18 @@ protected override void ProcessRecord() WriteObjectsOrShowOnlineHelp(helpInfo, false); } + 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); } @@ -320,13 +352,13 @@ protected override void ProcessRecord() // show errors only if there is no wildcard search or VerboseHelpErrors is true. if (((countOfHelpInfos == 0) && (!WildcardPattern.ContainsWildcardCharacters(helpRequest.Target))) - || this.Context.HelpSystem.VerboseHelpErrors) + || helpSystem.VerboseHelpErrors) { // Check if there is any error happened. If yes, // pipe out errors. - if (this.Context.HelpSystem.LastErrors.Count > 0) + if (helpSystem.LastErrors.Count > 0) { - foreach (ErrorRecord errorRecord in this.Context.HelpSystem.LastErrors) + foreach (ErrorRecord errorRecord in helpSystem.LastErrors) { WriteError(errorRecord); } @@ -335,9 +367,11 @@ protected override void ProcessRecord() } finally { - this.Context.HelpSystem.OnProgress -= new HelpSystem.HelpProgressHandler(HelpSystem_OnProgress); + helpSystem.OnProgress -= HelpSystem_OnProgress; + HelpSystem_OnComplete(); + // finally clear the ScriptBlockAst -> Token[] cache - this.Context.HelpSystem.ClearScriptBlockTokenCache(); + helpSystem.ClearScriptBlockTokenCache(); } } @@ -401,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 @@ -417,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); } @@ -434,7 +468,27 @@ private PSObject TransformView(PSObject originalHelpObject) } /// - /// Gets the parameter info for patterns identified Parameter property. + /// Gets the parameter info for patterns identified by Parameter property. + /// + /// HelpInfo object to look for the parameter. + /// Array of parameter infos. + private PSObject[] GetParameterInfo(HelpInfo helpInfo) + { + List parameterInfosList = new List(Parameter.Length); + + foreach (var parameter in Parameter) + { + foreach (var parameterInfo in helpInfo.GetParameter(parameter)) + { + parameterInfosList.Add(parameterInfo); + } + } + + return parameterInfosList.ToArray(); + } + + /// + /// Gets the parameter info for patterns identified by Parameter property. /// Writes the parameter info(s) to the output stream. An error is thrown /// if a parameter with a given pattern is not found. /// @@ -442,7 +496,8 @@ private PSObject TransformView(PSObject originalHelpObject) private void GetAndWriteParameterInfo(HelpInfo helpInfo) { s_tracer.WriteLine("Searching parameters for {0}", helpInfo.Name); - PSObject[] pInfos = helpInfo.GetParameter(Parameter); + + PSObject[] pInfos = GetParameterInfo(helpInfo); if ((pInfos == null) || (pInfos.Length == 0)) { @@ -460,11 +515,11 @@ private void GetAndWriteParameterInfo(HelpInfo helpInfo) } /// - /// Validates input parameters + /// Validates input parameters. /// - /// Category specified by the user + /// Category specified by the user. /// - /// If the request cant be serviced. + /// If the request can't be serviced. /// private void ValidateAndThrowIfError(HelpCategory cat) { @@ -474,13 +529,13 @@ 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 | HelpCategory.Workflow; + HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand; if ((cat & supportedCategories) == 0) { - if (!string.IsNullOrEmpty(Parameter)) + if (Parameter != null) { throw PSTraceSource.NewArgumentException("Parameter", HelpErrors.ParamNotSupported, "-Parameter"); @@ -524,7 +579,7 @@ private void WriteObjectsOrShowOnlineHelp(HelpInfo helpInfo, bool showFullHelp) // show online help s_tracer.WriteLine("Preparing to show help online."); Uri onlineUri = helpInfo.GetUriForOnlineHelp(); - if (null != onlineUri) + if (onlineUri != null) { onlineUriFound = true; LaunchOnlineHelp(onlineUri); @@ -536,12 +591,18 @@ private void WriteObjectsOrShowOnlineHelp(HelpInfo helpInfo, bool showFullHelp) throw PSTraceSource.NewInvalidOperationException(HelpErrors.NoURIFound); } } +#if !UNIX + else if (showFullHelp && ShowWindow) + { + graphicalHostReflectionWrapper.CallStaticMethod("ShowHelpWindow", helpInfo.FullHelp, this); + } +#endif else { // show inline help if (showFullHelp) { - if (!string.IsNullOrEmpty(Parameter)) + if (Parameter != null) { GetAndWriteParameterInfo(helpInfo); } @@ -554,9 +615,10 @@ private void WriteObjectsOrShowOnlineHelp(HelpInfo helpInfo, bool showFullHelp) } else { - if (!string.IsNullOrEmpty(Parameter)) + if (Parameter != null) { - PSObject[] pInfos = helpInfo.GetParameter(Parameter); + PSObject[] pInfos = GetParameterInfo(helpInfo); + if ((pInfos == null) || (pInfos.Length == 0)) { return; @@ -576,7 +638,7 @@ private void WriteObjectsOrShowOnlineHelp(HelpInfo helpInfo, bool showFullHelp) /// private void LaunchOnlineHelp(Uri uriToLaunch) { - Diagnostics.Assert(null != uriToLaunch, "uriToLaunch should not be null"); + Diagnostics.Assert(uriToLaunch != null, "uriToLaunch should not be null"); if (!uriToLaunch.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && !uriToLaunch.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) @@ -600,11 +662,7 @@ private void LaunchOnlineHelp(Uri uriToLaunch) { this.WriteVerbose(string.Format(CultureInfo.InvariantCulture, HelpDisplayStrings.OnlineHelpUri, uriToLaunch.OriginalString)); System.Diagnostics.Process browserProcess = new System.Diagnostics.Process(); -#if UNIX - browserProcess.StartInfo.FileName = Platform.IsLinux ? "xdg-open" : /* macOS */ "open"; - browserProcess.StartInfo.Arguments = uriToLaunch.OriginalString; - browserProcess.Start(); -#else + if (Platform.IsNanoServer || Platform.IsIoT) { // We cannot open the URL in browser on headless SKUs. @@ -613,12 +671,10 @@ private void LaunchOnlineHelp(Uri uriToLaunch) } else { - // We can call ShellExecute directly on Full Windows. browserProcess.StartInfo.FileName = uriToLaunch.OriginalString; browserProcess.StartInfo.UseShellExecute = true; browserProcess.Start(); } -#endif } catch (InvalidOperationException ioe) { @@ -629,7 +685,7 @@ private void LaunchOnlineHelp(Uri uriToLaunch) exception = we; } - if (null != exception) + if (exception != null) { if (wrapCaughtException) throw PSTraceSource.NewInvalidOperationException(exception, HelpErrors.CannotLaunchURI, uriToLaunch.OriginalString); @@ -640,22 +696,24 @@ private void LaunchOnlineHelp(Uri uriToLaunch) #endregion - private void HelpSystem_OnProgress(object sender, HelpProgressInfo arg) + private void HelpSystem_OnProgress(object sender, HelpProgressEventArgs arg) { - ProgressRecord record = new ProgressRecord(0, this.CommandInfo.Name, arg.Activity); - - record.PercentComplete = arg.PercentComplete; + var record = new ProgressRecord(0, this.CommandInfo.Name, arg.Activity) + { + PercentComplete = arg.PercentComplete + }; WriteProgress(record); } - /// - /// Checks if we can connect to the internet - /// - /// - private bool HasInternetConnection() + private void HelpSystem_OnComplete() { - return true; // TODO:CORECLR wininet.dll is not present on NanoServer + var record = new ProgressRecord(0, this.CommandInfo.Name, "Completed") + { + RecordType = ProgressRecordType.Completed + }; + + WriteProgress(record); } #region Helper methods for verification of parameters against NoLanguage mode @@ -676,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 } @@ -689,33 +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 ((null != getHelpCmdlet) && (getHelpCmdlet.ImplementingType.Equals(typeof(GetHelpCommand)))) - { - return true; - } + return false; } + + getHelpEntry = getHelpEntries[i]; } - return false; + + return getHelpEntry is SessionStateCmdletEntry getHelpCmdlet + && getHelpCmdlet.ImplementingType == typeof(GetHelpCommand); } /// @@ -732,14 +793,15 @@ private static bool DoesCurrentRunspaceIncludeCoreHelpCmdlet() [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings")] public static string GetHelpUri(PSObject commandInfoPSObject) { - if (null == commandInfoPSObject) + if (commandInfoPSObject == null) { return string.Empty; } + CommandInfo cmdInfo = PSObject.Base(commandInfoPSObject) as CommandInfo; // GetHelpUri helper method is expected to be used only by System.Management.Automation.CommandInfo // objects from types.ps1xml - if ((null == cmdInfo) || (string.IsNullOrEmpty(cmdInfo.Name))) + if ((cmdInfo == null) || (string.IsNullOrEmpty(cmdInfo.Name))) { return string.Empty; } @@ -757,8 +819,8 @@ public static string GetHelpUri(PSObject commandInfoPSObject) } AliasInfo aliasInfo = cmdInfo as AliasInfo; - if ((null != aliasInfo) && - (null != aliasInfo.ExternalCommandMetadata) && + if ((aliasInfo != null) && + (aliasInfo.ExternalCommandMetadata != null) && (!string.IsNullOrEmpty(aliasInfo.ExternalCommandMetadata.HelpUri))) { return aliasInfo.ExternalCommandMetadata.HelpUri; @@ -768,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()) @@ -777,7 +838,7 @@ public static string GetHelpUri(PSObject commandInfoPSObject) // Win8: 651300 if core get-help is present in the runspace (and it is the only get-help command), use // help system directly and avoid perf penalty. var currentContext = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS(); - if ((null != currentContext) && (null != currentContext.HelpSystem)) + if ((currentContext != null) && (currentContext.HelpSystem != null)) { HelpRequest helpRequest = new HelpRequest(cmdName, cmdInfo.HelpCategory); helpRequest.ProviderContext = new ProviderContext( @@ -788,7 +849,7 @@ public static string GetHelpUri(PSObject commandInfoPSObject) foreach ( Uri result in currentContext.HelpSystem.ExactMatchHelp(helpRequest).Select( - helpInfo => helpInfo.GetUriForOnlineHelp()).Where(result => null != result)) + helpInfo => helpInfo.GetUriForOnlineHelp()).Where(static result => result != null)) { return result.OriginalString; } @@ -802,15 +863,15 @@ Uri result in // 2. This method is primarily used to get uri faster while serializing the CommandInfo objects (from Get-Command) // 3. Exchange uses Get-Help proxy to not call Get-Help cmdlet at-all while serializing CommandInfo objects // 4. Using HelpSystem directly will not allow Get-Help proxy to do its job. - System.Management.Automation.PowerShell getHelpPS = System.Management.Automation.PowerShell.Create( - RunspaceMode.CurrentRunspace).AddCommand("get-help"). - AddParameter("Name", cmdName).AddParameter("Category", - cmdInfo.HelpCategory.ToString()); + var getHelpPS = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace) + .AddCommand("get-help") + .AddParameter("Name", cmdName) + .AddParameter("Category", cmdInfo.HelpCategory.ToString()); try { Collection helpInfos = getHelpPS.Invoke(); - if (null != helpInfos) + if (helpInfos != null) { for (int index = 0; index < helpInfos.Count; index++) { @@ -818,7 +879,7 @@ Uri result in if (LanguagePrimitives.TryConvertTo(helpInfos[index], out helpInfo)) { Uri result = helpInfo.GetUriForOnlineHelp(); - if (null != result) + if (result != null) { return result.OriginalString; } @@ -841,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 99226dba1a3..b4a21e04d69 100644 --- a/src/System.Management.Automation/help/HelpCommentsParser.cs +++ b/src/System.Management.Automation/help/HelpCommentsParser.cs @@ -1,27 +1,26 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.IO; using System.Linq; +using System.Management.Automation.Help; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Xml; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Management.Automation.Help; -using System.Reflection; +using System.Xml; namespace System.Management.Automation { /// - /// Parses help comments and turns them into HelpInfo objects + /// Parses help comments and turns them into HelpInfo objects. /// - internal class HelpCommentsParser + internal sealed class HelpCommentsParser { private HelpCommentsParser() { @@ -49,6 +48,7 @@ private HelpCommentsParser(CommandInfo commandInfo, List parameterDescri _commandName = si.Path; } } + _commandMetadata = commandInfo.CommandMetadata; _parameterDescriptions = parameterDescriptions; } @@ -61,15 +61,16 @@ 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"; internal static readonly string commandURI = "http://schemas.microsoft.com/maml/dev/command/2004/10"; internal static readonly string devURI = "http://schemas.microsoft.com/maml/dev/2004/10"; + private const string directive = @"^\s*\.(\w+)(\s+(\S.*))?\s*$"; private const string blankline = @"^\s*$"; // Although "http://msh" is the default namespace, it still must be explicitly qualified with non-empty prefix, @@ -91,6 +92,7 @@ private void DetermineParameterDescriptions() _parameters.Add(parameterName.ToUpperInvariant(), _parameterDescriptions[i]); } } + ++i; } } @@ -118,7 +120,7 @@ private XmlElement BuildXmlForParameter( { XmlElement command_parameter = _doc.CreateElement("command:parameter", commandURI); command_parameter.SetAttribute("required", isMandatory ? "true" : "false"); - //command_parameter.SetAttribute("variableLength", "unknown"); + // command_parameter.SetAttribute("variableLength", "unknown"); command_parameter.SetAttribute("globbing", supportsWildcards ? "true" : "false"); string fromPipeline; if (valueFromPipeline && valueFromPipelineByPropertyName) @@ -137,6 +139,7 @@ private XmlElement BuildXmlForParameter( { fromPipeline = "false"; } + command_parameter.SetAttribute("pipelineInput", fromPipeline); command_parameter.SetAttribute("position", position); @@ -154,10 +157,9 @@ private XmlElement BuildXmlForParameter( if (type == null) type = typeof(object); - var typeInfo = type.GetTypeInfo(); - var elementType = typeInfo.IsArray ? typeInfo.GetElementType() : type; + var elementType = type.IsArray ? type.GetElementType() : type; - if (elementType.GetTypeInfo().IsEnum) + if (elementType.IsEnum) { XmlElement parameterValueGroup = _doc.CreateElement("command:parameterValueGroup", commandURI); foreach (string valueName in Enum.GetNames(elementType)) @@ -167,6 +169,7 @@ private XmlElement BuildXmlForParameter( XmlText parameterValue_text = _doc.CreateTextNode(valueName); parameterValueGroup.AppendChild(parameterValue).AppendChild(parameterValue_text); } + command_parameter.AppendChild(parameterValueGroup); } else @@ -176,7 +179,7 @@ private XmlElement BuildXmlForParameter( { XmlElement parameterValue = _doc.CreateElement("command:parameterValue", commandURI); parameterValue.SetAttribute("required", isSwitchParameter ? "false" : "true"); - //parameterValue.SetAttribute("variableLength", "unknown"); + // parameterValue.SetAttribute("variableLength", "unknown"); XmlText parameterValue_text = _doc.CreateTextNode(type.Name); command_parameter.AppendChild(parameterValue).AppendChild(parameterValue_text); } @@ -200,7 +203,7 @@ private XmlElement BuildXmlForParameter( /// /// Create the maml xml after a successful analysis of the comments. /// - /// The xml node for the command constructed + /// The xml node for the command constructed. internal XmlDocument BuildXmlFromComments() { Diagnostics.Assert(!string.IsNullOrEmpty(_commandName), "Name can never be null"); @@ -258,6 +261,7 @@ internal XmlDocument BuildXmlFromComments() { continue; } + string parameterName = pair.Key; string description = GetParameterDescription(parameterName); @@ -274,6 +278,7 @@ internal XmlDocument BuildXmlFromComments() { parameterSetData = parameter.GetParameterSetData(1u << i++); } + if (parameterSetData != null) { isMandatory = parameterSetData.IsMandatory; @@ -285,7 +290,7 @@ internal XmlDocument BuildXmlFromComments() var compiledAttributes = parameter.CompiledAttributes; bool supportsWildcards = compiledAttributes.OfType().Any(); - string defaultValueStr = ""; + string defaultValueStr = string.Empty; object defaultValue = null; var defaultValueAttribute = compiledAttributes.OfType().FirstOrDefault(); if (defaultValueAttribute != null) @@ -324,6 +329,7 @@ internal XmlDocument BuildXmlFromComments() parameter.Type, description, supportsWildcards, defaultValueStr, forSyntax: false); commandParameters.AppendChild(parameterElement); } + command.AppendChild(commandParameters); #endregion Parameters @@ -335,6 +341,7 @@ internal XmlDocument BuildXmlFromComments() XmlText description_text = _doc.CreateTextNode(_sections.Description); command.AppendChild(description).AppendChild(description_para).AppendChild(description_text); } + if (!string.IsNullOrEmpty(_sections.Notes)) { XmlElement alertSet = _doc.CreateElement("maml:alertSet", mamlURI); @@ -343,6 +350,7 @@ internal XmlDocument BuildXmlFromComments() XmlText alert_para_text = _doc.CreateTextNode(_sections.Notes); command.AppendChild(alertSet).AppendChild(alert).AppendChild(alert_para).AppendChild(alert_para_text); } + if (_examples.Count > 0) { XmlElement examples = _doc.CreateElement("command:examples", commandURI); @@ -354,7 +362,7 @@ internal XmlDocument BuildXmlFromComments() // The title is automatically generated XmlElement title = _doc.CreateElement("maml:title", mamlURI); string titleStr = string.Format(CultureInfo.InvariantCulture, - " -------------------------- {0} {1} --------------------------", + "\t\t\t\t-------------------------- {0} {1} --------------------------", HelpDisplayStrings.ExampleUpperCase, count++); XmlText title_text = _doc.CreateTextNode(titleStr); example_node.AppendChild(title).AppendChild(title_text); @@ -385,10 +393,13 @@ internal XmlDocument BuildXmlFromComments() { remarks.AppendChild(_doc.CreateElement("maml:para", mamlURI)); } + examples.AppendChild(example_node); } + command.AppendChild(examples); } + if (_inputs.Count > 0) { XmlElement inputTypes = _doc.CreateElement("command:inputTypes", commandURI); @@ -400,6 +411,7 @@ internal XmlDocument BuildXmlFromComments() XmlText maml_name_text = _doc.CreateTextNode(inputStr); inputTypes.AppendChild(inputType).AppendChild(type).AppendChild(maml_name).AppendChild(maml_name_text); } + command.AppendChild(inputTypes); } // For outputs, we prefer what was specified in the comments, but if there are no comments @@ -413,6 +425,7 @@ internal XmlDocument BuildXmlFromComments() { outputs = _scriptBlock.OutputType; } + if (outputs != null) { XmlElement returnValues = _doc.CreateElement("command:returnValues", commandURI); @@ -425,20 +438,23 @@ internal XmlDocument BuildXmlFromComments() XmlText maml_name_text = _doc.CreateTextNode(returnValueStr); returnValues.AppendChild(returnValue).AppendChild(type).AppendChild(maml_name).AppendChild(maml_name_text); } + command.AppendChild(returnValues); } + if (_links.Count > 0) { XmlElement links = _doc.CreateElement("maml:relatedLinks", mamlURI); 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); links.AppendChild(navigationLink).AppendChild(linkText).AppendChild(linkText_text); } + command.AppendChild(links); } @@ -466,63 +482,46 @@ 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, parameterSetData.IsPositional ? (1 + parameterSetData.Position).ToString(CultureInfo.InvariantCulture) : "named", - parameter.Type, description, supportsWildcards, defaultValue: "", forSyntax: true); + parameter.Type, description, supportsWildcards, defaultValue: string.Empty, forSyntax: true); syntaxItem.AppendChild(parameterElement); } + command.AppendChild(syntax).AppendChild(syntaxItem); } private static void GetExampleSections(string content, out string prompt_str, out string code_str, out string remarks_str) { - prompt_str = code_str = ""; - StringBuilder builder = new StringBuilder(); + const string default_prompt_str = "PS > "; - int collectingPart = 1; - foreach (char c in content) + var promptMatch = Regex.Match(content, "^.*?>"); + prompt_str = promptMatch.Success ? promptMatch.Value : default_prompt_str; + if (promptMatch.Success) { - if (c == '>' && collectingPart == 1) - { - builder.Append(c); - prompt_str = builder.ToString().Trim(); - builder = new StringBuilder(); - ++collectingPart; - continue; - } - if (c == '\n' && collectingPart < 3) - { - if (collectingPart == 1) - { - prompt_str = "PS C:\\>"; - } - code_str = builder.ToString().Trim(); - builder = new StringBuilder(); - collectingPart = 3; - continue; - } - builder.Append(c); + content = content.Substring(prompt_str.Length); } - if (collectingPart == 1) + var codeAndRemarksMatch = Regex.Match(content, "^(?.*?)\r?\n\r?\n(?.*)$", RegexOptions.Singleline); + if (codeAndRemarksMatch.Success) { - prompt_str = "PS C:\\>"; - code_str = builder.ToString().Trim(); - remarks_str = ""; + code_str = codeAndRemarksMatch.Groups["code"].Value.Trim(); + remarks_str = codeAndRemarksMatch.Groups["remarks"].Value; } else { - remarks_str = builder.ToString(); + code_str = content.Trim(); + remarks_str = string.Empty; } } /// /// Split the text in the comment token into multiple lines, appending commentLines. /// - /// A single line or multiline comment token + /// A single line or multiline comment token. /// private static void CollectCommentText(Token comment, List commentLines) { @@ -554,9 +553,11 @@ private static void CollectCommentText(string text, List commentLines) { i++; } + start = i + 1; } } + commentLines.Add(text.Substring(start, i - start)); } else @@ -570,6 +571,7 @@ private static void CollectCommentText(string text, List commentLines) break; } } + commentLines.Add(text.Substring(i)); } } @@ -596,6 +598,7 @@ private static string GetSection(List commentLines, ref int i) // Skip blank lines before capturing anything in the section. continue; } + if (Regex.IsMatch(line, directive)) { // Break on any directive even if we haven't started capturing. @@ -613,6 +616,7 @@ private static string GetSection(List commentLines, ref int i) j++; } } + capturing = true; // Skip leading whitespace based on the first line in the section, skipping @@ -623,9 +627,11 @@ private static string GetSection(List commentLines, ref int i) { start++; } - sb.Append(line.Substring(start)); + + sb.Append(line.AsSpan(start)); sb.Append('\n'); } + return sb.ToString(); } @@ -637,7 +643,7 @@ internal string GetHelpFile(CommandInfo commandInfo) } string helpFileToLoad = _sections.MamlHelpFile; - Collection searchPaths = new Collection(); + Collection searchPaths = new Collection(); string scriptFile = ((IScriptCommandInfo)commandInfo).ScriptBlock.File; if (!string.IsNullOrEmpty(scriptFile)) { @@ -647,6 +653,7 @@ internal string GetHelpFile(CommandInfo commandInfo) { helpFileToLoad = Path.Combine(Path.GetDirectoryName(commandInfo.Module.Path), _sections.MamlHelpFile); } + string location = MUIFileSearcher.LocateFile(helpFileToLoad, searchPaths); return location; @@ -684,7 +691,7 @@ internal RemoteHelpInfo GetRemoteHelpInfo(ExecutionContext context, CommandInfo /// Look for special comments indicating the comment block is meant /// to be used for help. /// - /// The list of comments to process + /// The list of comments to process. /// True if any special comments are found, false otherwise. internal bool AnalyzeCommentBlock(List comments) { @@ -724,6 +731,7 @@ private bool AnalyzeCommentBlock(List commentLines) { _parameters.Add(param, section); } + break; } case "FORWARDHELPTARGETNAME": @@ -889,6 +897,7 @@ internal static HelpInfo CreateFromComments(ExecutionContext context, CommandInf { localHelpInfo.ForwardTarget = helpCommentsParser._sections.ForwardHelpTargetName; } + if (!string.IsNullOrEmpty(helpCommentsParser._sections.ForwardHelpCategory)) { try @@ -907,27 +916,10 @@ internal static HelpInfo CreateFromComments(ExecutionContext context, CommandInf HelpCategory.ExternalScript | HelpCategory.Filter | HelpCategory.Function | - HelpCategory.ScriptCommand | - HelpCategory.Workflow); + HelpCategory.ScriptCommand); } } - WorkflowInfo workflowInfo = commandInfo as WorkflowInfo; - if (workflowInfo != null) - { - bool common = DefaultCommandHelpObjectBuilder.HasCommonParameters(commandInfo.Parameters); - bool commonWorkflow = ((commandInfo.CommandType & CommandTypes.Workflow) == - CommandTypes.Workflow); - - localHelpInfo.FullHelp.Properties.Add(new PSNoteProperty("CommonParameters", common)); - localHelpInfo.FullHelp.Properties.Add(new PSNoteProperty("WorkflowCommonParameters", commonWorkflow)); - DefaultCommandHelpObjectBuilder.AddDetailsProperties(obj: localHelpInfo.FullHelp, name: workflowInfo.Name, - noun: workflowInfo.Noun, verb: workflowInfo.Verb, - typeNameForHelp: "MamlCommandHelpInfo", synopsis: localHelpInfo.Synopsis); - DefaultCommandHelpObjectBuilder.AddSyntaxProperties(localHelpInfo.FullHelp, workflowInfo.Name, - workflowInfo.ParameterSets, common, commonWorkflow, "MamlCommandHelpInfo"); - } - // Add HelpUri if necessary if (localHelpInfo.GetUriForOnlineHelp() == null) { @@ -942,7 +934,7 @@ internal static HelpInfo CreateFromComments(ExecutionContext context, CommandInf /// Analyze a block of comments to determine if it is a special help block. /// /// The block of comments to analyze. - /// true if the block is our special comment block for help, false otherwise. + /// True if the block is our special comment block for help, false otherwise. internal static bool IsCommentHelpText(List commentBlock) { if ((commentBlock == null) || (commentBlock.Count == 0)) @@ -959,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++) { @@ -1014,6 +1006,7 @@ internal static bool IsCommentHelpText(List commentBlock) break; } } + result.Reverse(); return result; } @@ -1028,6 +1021,7 @@ private static int FirstTokenInExtent(Language.Token[] tokens, IScriptExtent ext break; } } + return index; } @@ -1041,6 +1035,7 @@ private static int LastTokenInExtent(Language.Token[] tokens, IScriptExtent exte break; } } + return index - 1; } @@ -1088,10 +1083,10 @@ private static List GetParameterComments(Language.Token[] tokens, IParam } } - int n = -1; result.Add(GetSection(commentLines, ref n)); } + return result; } @@ -1173,10 +1168,10 @@ private static List GetParameterComments(Language.Token[] tokens, IParam // $sb = { } // set-item function:foo $sb // help foo - startTokenIndex = savedStartIndex = FirstTokenInExtent(tokens, ast.Extent) - 1; + startTokenIndex = savedStartIndex = FirstTokenInExtent(tokens, ast.Extent) + 1; lastTokenIndex = LastTokenInExtent(tokens, ast.Extent, startTokenIndex); - Diagnostics.Assert(tokens[startTokenIndex + 1].Kind == TokenKind.LCurly, + Diagnostics.Assert(tokens[startTokenIndex - 1].Kind == TokenKind.LCurly, "Unexpected first token in script block"); Diagnostics.Assert(tokens[lastTokenIndex].Kind == TokenKind.RCurly, "Unexpected last token in script block"); @@ -1209,6 +1204,7 @@ private static List GetParameterComments(Language.Token[] tokens, IParam { return Tuple.Create(commentBlock, GetParameterComments(tokens, ipmp, savedStartIndex)); } + break; } } diff --git a/src/System.Management.Automation/help/HelpErrorTracer.cs b/src/System.Management.Automation/help/HelpErrorTracer.cs index 913913466cc..e5a9fea9e67 100644 --- a/src/System.Management.Automation/help/HelpErrorTracer.cs +++ b/src/System.Management.Automation/help/HelpErrorTracer.cs @@ -1,10 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; namespace System.Management.Automation { @@ -34,7 +32,6 @@ namespace System.Management.Automation /// /// When the TraceFrame instance is disposed, all errorRecords stored will be /// dumped into HelpSystem.LastErrors with context information attached. - /// /// internal class HelpErrorTracer { @@ -55,12 +52,12 @@ internal class HelpErrorTracer internal sealed class TraceFrame : IDisposable { // Following are help context information - private string _helpFile = ""; + 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. /// @@ -108,7 +105,7 @@ public void Dispose() if (_helpTracer.HelpSystem.VerboseHelpErrors && _errors.Count > 0) { ErrorRecord errorRecord = new ErrorRecord(new ParentContainsErrorRecordException("Help Load Error"), "HelpLoadError", ErrorCategory.SyntaxError, null); - errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpErrorTracer).GetTypeInfo().Assembly, "HelpErrors", "HelpLoadError", _helpFile, _errors.Count); + errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpErrorTracer).Assembly, "HelpErrors", "HelpLoadError", _helpFile, _errors.Count); _helpTracer.HelpSystem.LastErrors.Add(errorRecord); foreach (ErrorRecord error in _errors) @@ -136,10 +133,10 @@ internal HelpErrorTracer(HelpSystem helpSystem) /// /// This tracks all live TraceFrame objects, which forms a stack. /// - private ArrayList _traceFrames = new ArrayList(); + private readonly List _traceFrames = new List(); /// - /// This is the API to use for starting a help trace scope + /// This is the API to use for starting a help trace scope. /// /// /// @@ -159,10 +156,10 @@ internal IDisposable Trace(string helpFile) /// internal void TraceError(ErrorRecord errorRecord) { - if (_traceFrames.Count <= 0) + if (_traceFrames.Count == 0) return; - TraceFrame traceFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame traceFrame = _traceFrames[_traceFrames.Count - 1]; traceFrame.TraceError(errorRecord); } @@ -174,20 +171,20 @@ internal void TraceError(ErrorRecord errorRecord) /// internal void TraceErrors(Collection errorRecords) { - if (_traceFrames.Count <= 0) + if (_traceFrames.Count == 0) return; - TraceFrame traceFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame traceFrame = _traceFrames[_traceFrames.Count - 1]; traceFrame.TraceErrors(errorRecords); } internal void PopFrame(TraceFrame traceFrame) { - if (_traceFrames.Count <= 0) + if (_traceFrames.Count == 0) return; - TraceFrame lastFrame = (TraceFrame)_traceFrames[_traceFrames.Count - 1]; + TraceFrame lastFrame = _traceFrames[_traceFrames.Count - 1]; if (lastFrame == traceFrame) { diff --git a/src/System.Management.Automation/help/HelpFileHelpInfo.cs b/src/System.Management.Automation/help/HelpFileHelpInfo.cs index 257dabb14da..104111777ef 100644 --- a/src/System.Management.Automation/help/HelpFileHelpInfo.cs +++ b/src/System.Management.Automation/help/HelpFileHelpInfo.cs @@ -1,30 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.IO; 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 + /// Constructor for HelpFileHelpInfo. /// /// /// This is made private intentionally so that the only way to create object of this type /// is through /// GetHelpInfo(string name, string text, string filename) /// - /// help topic name - /// help text - /// file name that contains the help text + /// Help topic name. + /// Help text. + /// File name that contains the help text. private HelpFileHelpInfo(string name, string text, string filename) { FullHelp = PSObject.AsPSObject(text); @@ -41,22 +38,22 @@ private HelpFileHelpInfo(string name, string text, string filename) else { // make sure _synopsis is never null - _synopsis = ""; + _synopsis = string.Empty; } _filename = filename; } /// - /// Name for the help info + /// Name for the help info. /// /// Name for the help info - internal override string Name { get; } = ""; + internal override string Name { get; } = string.Empty; - private string _filename = ""; - private string _synopsis = ""; + private readonly string _filename = string.Empty; + private readonly string _synopsis = string.Empty; /// - /// Synopsis for the help info + /// Synopsis for the help info. /// /// Synopsis for the help info internal override string Synopsis @@ -68,7 +65,7 @@ internal override string Synopsis } /// - /// Help category for the help info + /// Help category for the help info. /// /// Help category for the help info internal override HelpCategory HelpCategory @@ -80,26 +77,26 @@ internal override HelpCategory HelpCategory } /// - /// Full help object for this help info + /// Full help object for this help info. /// /// Full help object for this help info internal override PSObject FullHelp { get; } /// - /// Get help info based on name, text and filename + /// Get help info based on name, text and filename. /// - /// help topic name - /// help text - /// file name that contains the help text - /// HelpFileHelpInfo object created based on information provided + /// Help topic name. + /// Help text. + /// File name that contains the help text. + /// HelpFileHelpInfo object created based on information provided. internal static HelpFileHelpInfo GetHelpInfo(string name, string text, string filename) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) return null; HelpFileHelpInfo helpfileHelpInfo = new HelpFileHelpInfo(name, text, filename); - if (String.IsNullOrEmpty(helpfileHelpInfo.Name)) + if (string.IsNullOrEmpty(helpfileHelpInfo.Name)) return null; helpfileHelpInfo.AddCommonHelpProperties(); @@ -110,14 +107,14 @@ internal static HelpFileHelpInfo GetHelpInfo(string name, string text, string fi /// /// Get the text corresponding to a line in input text. /// - /// text to get the line for - /// line number - /// the part of string in text that is in specified line + /// Text to get the line for. + /// Line number. + /// The part of string in text that is in specified line. private static string GetLine(string text, int line) { StringReader reader = new StringReader(text); - String result = null; + string result = null; for (int i = 0; i < line; i++) { @@ -132,7 +129,7 @@ private static string GetLine(string text, int line) internal override bool MatchPatternInContent(WildcardPattern pattern) { - Diagnostics.Assert(null != pattern, "pattern cannot be null."); + Diagnostics.Assert(pattern != null, "pattern cannot be null."); string helpContent = string.Empty; LanguagePrimitives.TryConvertTo(FullHelp, out helpContent); diff --git a/src/System.Management.Automation/help/HelpFileHelpProvider.cs b/src/System.Management.Automation/help/HelpFileHelpProvider.cs index cfccf547731..a6c21a3bacc 100644 --- a/src/System.Management.Automation/help/HelpFileHelpProvider.cs +++ b/src/System.Management.Automation/help/HelpFileHelpProvider.cs @@ -1,30 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Management.Automation.Internal; namespace System.Management.Automation { /// - /// /// Class HelpFileHelpProvider implement the help provider for help.txt kinds of /// help contents. /// /// Help File help information are stored in '.help.txt' files. These files are /// located in the Monad / CustomShell Path as well as in the Application Base - /// of PSSnapIns - /// + /// of PSSnapIns. /// internal class HelpFileHelpProvider : HelpProviderWithCache { /// - /// Constructor for HelpProvider + /// Constructor for HelpProvider. /// internal HelpFileHelpProvider(HelpSystem helpSystem) : base(helpSystem) { @@ -33,7 +30,7 @@ internal HelpFileHelpProvider(HelpSystem helpSystem) : base(helpSystem) #region Common Properties /// - /// Name of the provider + /// Name of the provider. /// /// Name of the provider internal override string Name @@ -45,7 +42,7 @@ internal override string Name } /// - /// Help category of the provider + /// Help category of the provider. /// /// Help category of the provider internal override HelpCategory HelpCategory @@ -110,7 +107,7 @@ private Collection FilterToLatestModuleVersion(Collection filesM if (filesMatched.Count > 1) { - //Dictionary<, > + // Dictionary<, > Dictionary, Tuple> modulesAndVersion = new Dictionary, Tuple>(); HashSet filesProcessed = new HashSet(); @@ -128,33 +125,33 @@ private Collection FilterToLatestModuleVersion(Collection filesM string moduleName = null; GetModuleNameAndVersion(psModulePath, fileFullName, out moduleName, out moduleVersionFromPath); - //Skip modules whose root we cannot determine or which do not have versions. + // Skip modules whose root we cannot determine or which do not have versions. if (moduleVersionFromPath != null && moduleName != null) { Tuple moduleVersion = null; Tuple key = new Tuple(moduleName, fileName); if (modulesAndVersion.TryGetValue(key, out moduleVersion)) { - //Consider for further processing only if the help file name is same. + // Consider for further processing only if the help file name is same. if (filesProcessed.Contains(fileName)) { if (moduleVersionFromPath > moduleVersion.Item2) { modulesAndVersion[key] = new Tuple(fileFullName, moduleVersionFromPath); - //Remove the old file since we found a newer version. + // Remove the old file since we found a newer version. matchedFilesToRemove.Add(moduleVersion.Item1); } else { - //Remove the new file as higher version item is already in dictionary. + // Remove the new file as higher version item is already in dictionary. matchedFilesToRemove.Add(fileFullName); } } } else { - //Add the module to the dictionary as it was not processes earlier. + // Add the module to the dictionary as it was not processes earlier. modulesAndVersion.Add(new Tuple(moduleName, fileName), new Tuple(fileFullName, moduleVersionFromPath)); } @@ -165,6 +162,24 @@ private Collection FilterToLatestModuleVersion(Collection filesM } } + // Deduplicate by filename to compensate for two sources, currentuser scope and allusers scope. + // This is done after the version check filtering to ensure we do not remove later version files. + HashSet fileNameHash = new HashSet(); + + foreach (var file in filesMatched) + { + string fileName = Path.GetFileName(file); + + if (!fileNameHash.Add(fileName)) + { + // If the file need to be removed, add it to matchedFilesToRemove, if not already present. + if (!matchedFilesToRemove.Contains(file)) + { + matchedFilesToRemove.Add(file); + } + } + } + return matchedFilesToRemove; } @@ -199,7 +214,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool pattern += ".help.txt"; - Collection files = MUIFileSearcher.SearchFiles(pattern, GetExtendedSearchPaths()); + Collection files = MUIFileSearcher.SearchFiles(pattern, GetExtendedSearchPaths()); var matchedFilesToRemove = FilterToLatestModuleVersion(files); @@ -249,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; @@ -272,17 +286,17 @@ private void GetModuleNameAndVersion(string psmodulePathRoot, string filePath, o /// /// Load help file based on the file path. /// - /// file path to load help from - /// Help info object loaded from the file + /// File path to load help from. + /// Help info object loaded from the file. private HelpInfo LoadHelpFile(string path) { string fileName = Path.GetFileName(path); - //Bug906435: Get-help for special devices throws an exception - //There might be situations where path does not end with .help.txt extension - //The assumption that path ends with .help.txt is broken under special - //conditions when user uses "get-help" with device names like "prn","com1" etc. - //First check whether path ends with .help.txt. + // Bug906435: Get-help for special devices throws an exception + // There might be situations where path does not end with .help.txt extension + // The assumption that path ends with .help.txt is broken under special + // conditions when user uses "get-help" with device names like "prn","com1" etc. + // First check whether path ends with .help.txt. // If path does not end with ".help.txt" return. if (!path.EndsWith(".help.txt", StringComparison.OrdinalIgnoreCase)) @@ -290,7 +304,7 @@ private HelpInfo LoadHelpFile(string path) string name = fileName.Substring(0, fileName.Length - 9 /* ".help.txt".Length */); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) return null; HelpInfo helpInfo = GetCache(path); @@ -317,13 +331,14 @@ private HelpInfo LoadHelpFile(string path) /// Gets the extended search paths for about_topics help. To be able to get about_topics help from unloaded modules, /// we will add $pshome and the folders under PS module paths to the collection of paths to search. /// - /// a collection of string representing locations + /// A collection of string representing locations. internal Collection GetExtendedSearchPaths() { - Collection searchPaths = GetSearchPaths(); + Collection searchPaths = GetSearchPaths(); // Add $pshome at the top of the list - String defaultShellSearchPath = GetDefaultShellSearchPath(); + string defaultShellSearchPath = GetDefaultShellSearchPath(); + int index = searchPaths.IndexOf(defaultShellSearchPath); if (index != 0) { @@ -331,9 +346,13 @@ internal Collection GetExtendedSearchPaths() { searchPaths.RemoveAt(index); } + searchPaths.Insert(0, defaultShellSearchPath); } + // Add the CurrentUser help path. + searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath()); + // Add modules that are not loaded. Since using 'get-module -listavailable' is very expensive, // we load all the directories (which are not empty) under the module path. foreach (string psModulePath in ModuleIntrinsics.GetModulePath(false, this.HelpSystem.ExecutionContext)) @@ -344,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.IsPossibleModuleDirectory(directory)); + var possibleModuleDirectories = directories.Where(static directory => !ModuleUtils.IsPossibleResourceDirectory(directory)); foreach (string directory in possibleModuleDirectories) { @@ -367,6 +386,7 @@ internal Collection GetExtendedSearchPaths() catch (System.Security.SecurityException) { } } } + return searchPaths; } @@ -390,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/HelpInfo.cs b/src/System.Management.Automation/help/HelpInfo.cs index f73baefd1a7..207d8b54d51 100644 --- a/src/System.Management.Automation/help/HelpInfo.cs +++ b/src/System.Management.Automation/help/HelpInfo.cs @@ -1,13 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; namespace System.Management.Automation { /// - /// /// Class HelpInfo keeps track of help information to be returned by help system. /// /// HelpInfo includes information in following aspect, @@ -22,19 +20,18 @@ namespace System.Management.Automation /// etc. /// /// In general, there will be a specific helpInfo child class for each kind of help provider. - /// /// internal abstract class HelpInfo { /// - /// Constructor for HelpInfo + /// Constructor for HelpInfo. /// internal HelpInfo() { } /// - /// Name for help info + /// Name for help info. /// /// Name for help info internal abstract string Name @@ -43,7 +40,7 @@ internal abstract string Name } /// - /// Synopsis for help info + /// Synopsis for help info. /// /// Synopsis for help info internal abstract string Synopsis @@ -79,7 +76,7 @@ internal virtual string Functionality } /// - /// Help category for help info + /// Help category for help info. /// /// Help category for help info internal abstract HelpCategory HelpCategory @@ -88,7 +85,7 @@ internal abstract HelpCategory HelpCategory } /// - /// Forward help category for this help info + /// Forward help category for this help info. /// /// /// If this is not HelpCategory.None, then some other help provider @@ -108,7 +105,7 @@ internal abstract HelpCategory HelpCategory /// alias. /// /// forward target object name - internal string ForwardTarget { get; set; } = ""; + internal string ForwardTarget { get; set; } = string.Empty; /// /// Full help object for this help item. @@ -140,16 +137,16 @@ internal PSObject ShortHelp } /// - /// Returns help information for a parameter(s) identified by pattern + /// Returns help information for a parameter(s) identified by pattern. /// - /// pattern to search for parameters - /// A collection of parameters that match pattern + /// Pattern to search for parameters. + /// A collection of parameters that match pattern. /// /// The base method returns an empty list. /// internal virtual PSObject[] GetParameter(string pattern) { - return new PSObject[0]; + return Array.Empty(); } /// @@ -206,7 +203,7 @@ protected void AddCommonHelpProperties() if (this.FullHelp.Properties["Name"] == null) { - this.FullHelp.Properties.Add(new PSNoteProperty("Name", this.Name.ToString())); + this.FullHelp.Properties.Add(new PSNoteProperty("Name", this.Name)); } if (this.FullHelp.Properties["Category"] == null) @@ -216,7 +213,7 @@ protected void AddCommonHelpProperties() if (this.FullHelp.Properties["Synopsis"] == null) { - this.FullHelp.Properties.Add(new PSNoteProperty("Synopsis", this.Synopsis.ToString())); + this.FullHelp.Properties.Add(new PSNoteProperty("Synopsis", this.Synopsis)); } if (this.FullHelp.Properties["Component"] == null) diff --git a/src/System.Management.Automation/help/HelpNotFoundException.cs b/src/System.Management.Automation/help/HelpNotFoundException.cs index e1b4b9a55b7..109cd21dbd9 100644 --- a/src/System.Management.Automation/help/HelpNotFoundException.cs +++ b/src/System.Management.Automation/help/HelpNotFoundException.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 @@ -16,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(); @@ -32,7 +31,8 @@ public HelpNotFoundException(string helpTopic) : base() /// /// Initializes a new instance of the HelpNotFoundException class. /// - public HelpNotFoundException() : base() + public HelpNotFoundException() + : base() { CreateErrorRecord(); } @@ -43,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(); @@ -61,7 +63,7 @@ private void CreateErrorRecord() // Don't do ParentContainsErrorRecordException(this), as this causes recursion, and creates a // segmentation fault on Linux _errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(errMessage), "HelpNotFound", ErrorCategory.ResourceUnavailable, null); - _errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpNotFoundException).GetTypeInfo().Assembly, "HelpErrors", "HelpNotFound", _helpTopic); + _errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpNotFoundException).Assembly, "HelpErrors", "HelpNotFound", _helpTopic); } private ErrorRecord _errorRecord; @@ -78,7 +80,7 @@ public ErrorRecord ErrorRecord } } - private string _helpTopic = ""; + private readonly string _helpTopic = string.Empty; /// /// Gets help topic for which help is not found. @@ -116,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("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 4675113e04c..3280a8d2540 100644 --- a/src/System.Management.Automation/help/HelpProvider.cs +++ b/src/System.Management.Automation/help/HelpProvider.cs @@ -1,10 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Reflection; using System.Management.Automation.Runspaces; @@ -12,7 +11,6 @@ namespace System.Management.Automation { /// - /// /// Class HelpProvider defines the interface to be implemented by help providers. /// /// Help Providers: @@ -40,20 +38,19 @@ namespace System.Management.Automation /// 1. Initialize: /// 2. ExactMatchHelp: /// 3. SearchHelp: - /// 4. ProcessForwardedHelp - /// + /// 4. ProcessForwardedHelp. /// internal abstract class HelpProvider { /// - /// Constructor for HelpProvider + /// Constructor for HelpProvider. /// internal HelpProvider(HelpSystem helpSystem) { _helpSystem = helpSystem; } - private HelpSystem _helpSystem; + private readonly HelpSystem _helpSystem; internal HelpSystem HelpSystem { @@ -66,7 +63,7 @@ internal HelpSystem HelpSystem #region Common Properties /// - /// Name for the help provider + /// Name for the help provider. /// /// Name for the help provider /// Derived classes should set this. @@ -76,7 +73,7 @@ internal abstract string Name } /// - /// Help category for the help provider + /// Help category for the help provider. /// /// Help category for the help provider /// Derived classes should set this. @@ -100,7 +97,7 @@ virtual internal string AssemblyName } /// - /// Class that implements the help provider + /// Class that implements the help provider. /// /// Class name virtual internal string ClassName @@ -142,21 +139,21 @@ internal PSObject ProviderInfo /// /// Retrieve help info that exactly match the target. /// - /// help request object - /// List of HelpInfo objects retrieved + /// Help request object. + /// List of HelpInfo objects retrieved. internal abstract IEnumerable ExactMatchHelp(HelpRequest helpRequest); /// /// Search help info that match the target search pattern. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content. Individual /// provider can decide which content to search in. /// /// If false, searches for pattern in the command names. /// - /// a collection of help info objects + /// A collection of help info objects. internal abstract IEnumerable SearchHelp(HelpRequest helpRequest, bool searchOnlyContent); /// @@ -171,14 +168,14 @@ internal PSObject ProviderInfo /// since the helpInfo object passed in is usually stored in cache, which can /// used in later queries. /// - /// helpInfo passed over by another HelpProvider - /// help request object + /// HelpInfo passed over by another HelpProvider. + /// Help request object. /// internal virtual IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest) { // 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; } @@ -207,7 +204,6 @@ internal virtual void Reset() /// /// This will be called either from search help or exact match help /// to find the error. - /// /// /// /// @@ -215,39 +211,32 @@ internal virtual void Reset() internal void ReportHelpFileError(Exception exception, string target, string helpFile) { ErrorRecord errorRecord = new ErrorRecord(exception, "LoadHelpFileForTargetFailed", ErrorCategory.OpenError, null); - errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpProvider).GetTypeInfo().Assembly, "HelpErrors", "LoadHelpFileForTargetFailed", target, helpFile, exception.Message); + errorRecord.ErrorDetails = new ErrorDetails(typeof(HelpProvider).Assembly, "HelpErrors", "LoadHelpFileForTargetFailed", target, helpFile, exception.Message); this.HelpSystem.LastErrors.Add(errorRecord); return; } /// /// Each Shell ( minishell ) will have its own path specified by the - /// application base folder, which should be the same as $pshome + /// application base folder, which should be the same as $pshome. /// - /// string representing base directory of the executing shell. + /// String representing base directory of the executing shell. 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. - string returnValue = Utils.GetApplicationBase(shellID); - - if (returnValue == null) - { - // use executing assemblies location in case registry entry not found - returnValue = Path.GetDirectoryName(PsUtils.GetMainModule(System.Diagnostics.Process.GetCurrentProcess()).FileName); - } - - return returnValue; + // We use executing assemblies location in case registry entry not found + return Utils.GetApplicationBase(shellID) ?? Path.GetDirectoryName(Environment.ProcessPath); } /// /// Gets the search paths. If the current shell is single-shell based, then the returned - /// search path contains all the directories of currently active PSSnapIns + /// search path contains all the directories of currently active PSSnapIns. /// - /// a collection of string representing locations + /// A collection of string representing locations. internal Collection GetSearchPaths() { - Collection searchPaths = this.HelpSystem.GetSearchPaths(); + Collection searchPaths = this.HelpSystem.GetSearchPaths(); Diagnostics.Assert(searchPaths != null, "HelpSystem returned an null search path"); diff --git a/src/System.Management.Automation/help/HelpProviderWithCache.cs b/src/System.Management.Automation/help/HelpProviderWithCache.cs index 895a4515ca5..495ec6a0b5d 100644 --- a/src/System.Management.Automation/help/HelpProviderWithCache.cs +++ b/src/System.Management.Automation/help/HelpProviderWithCache.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -8,16 +7,14 @@ namespace System.Management.Automation { /// - /// /// Class HelpProviderWithCache provides a pseudo implementation of HelpProvider /// at which results are cached in a hashtable so that later retrieval can be /// faster. - /// /// internal abstract class HelpProviderWithCache : HelpProvider { /// - /// Constructor for HelpProviderWithCache + /// Constructor for HelpProviderWithCache. /// internal HelpProviderWithCache(HelpSystem helpSystem) : base(helpSystem) { @@ -26,18 +23,18 @@ internal HelpProviderWithCache(HelpSystem helpSystem) : base(helpSystem) #region Help Provider Interface /// - /// _helpCache is a hashtable to stores helpInfo + /// _helpCache is a hashtable to stores helpInfo. /// /// /// 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. /// - /// Help request object - /// The HelpInfo found. Null if nothing is found + /// Help request object. + /// The HelpInfo found. Null if nothing is found. internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { string target = helpRequest.Target; @@ -80,8 +77,8 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) /// /// This is for implementing custom match algorithm. /// - /// target to search - /// key used in cache table + /// Target to search. + /// Key used in cache table. /// protected virtual bool CustomMatch(string target, string key) { @@ -97,22 +94,22 @@ protected virtual bool CustomMatch(string target, string key) /// If DoExactMatchHelp is overridden, cache check will be done first in ExactMatchHelp before the /// logic in DoExactMatchHelp is in place. /// - /// help request object + /// Help request object. internal virtual void DoExactMatchHelp(HelpRequest helpRequest) { } /// - /// Search help for a target + /// Search help for a target. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content. Individual /// provider can decide which content to search in. /// /// If false, searches for pattern in the command names. /// - /// a collection of help info objects + /// A collection of help info objects. internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool searchOnlyContent) { string target = helpRequest.Target; @@ -124,7 +121,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool if (!this.CacheFullyLoaded) { IEnumerable result = DoSearchHelp(searchHelpRequest); - if (null != result) + if (result != null) { foreach (HelpInfo helpInfoToReturn in result) { @@ -162,8 +159,8 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool /// /// Child class of this one may choose to override this function. /// - /// target string - /// wild card pattern created + /// Target string. + /// Wild card pattern created. internal virtual string GetWildCardPattern(string target) { if (WildcardPattern.ContainsWildcardCharacters(target)) @@ -179,8 +176,8 @@ internal virtual string GetWildCardPattern(string target) /// Child class can choose to override SearchHelp of DoSearchHelp depending on /// whether it want to reuse the logic in SearchHelp for this class. /// - /// help request object - /// a collection of help info objects + /// Help request object. + /// A collection of help info objects. internal virtual IEnumerable DoSearchHelp(HelpRequest helpRequest) { yield break; @@ -189,18 +186,18 @@ internal virtual IEnumerable DoSearchHelp(HelpRequest helpRequest) /// /// Add an help entry to cache. /// - /// the key of the help entry - /// helpInfo object as the value of the help entry + /// The key of the help entry. + /// HelpInfo object as the value of the help entry. internal void AddCache(string target, HelpInfo helpInfo) { _helpCache[target] = helpInfo; } /// - /// Get help entry from cache + /// Get help entry from cache. /// - /// the key for the help entry to retrieve - /// the HelpInfo in cache corresponding the key specified + /// The key for the help entry to retrieve. + /// The HelpInfo in cache corresponding the key specified. internal HelpInfo GetCache(string target) { return (HelpInfo)_helpCache[target]; diff --git a/src/System.Management.Automation/help/HelpProviderWithFullCache.cs b/src/System.Management.Automation/help/HelpProviderWithFullCache.cs index 81221cf37c4..379ee0ac1c6 100644 --- a/src/System.Management.Automation/help/HelpProviderWithFullCache.cs +++ b/src/System.Management.Automation/help/HelpProviderWithFullCache.cs @@ -1,13 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; namespace System.Management.Automation { /// - /// /// Class HelpProviderWithFullCache provides a pseudo implementation of HelpProvider /// at which results are fully cached in a hashtable after initial cache load. /// @@ -15,12 +13,11 @@ namespace System.Management.Automation /// help contents for this provider can be loaded once and be used for later /// search. So logically class derived from this class only need to provide /// a way to load and initialize help cache. - /// /// internal abstract class HelpProviderWithFullCache : HelpProviderWithCache { /// - /// Constructor for HelpProviderWithFullCache + /// Constructor for HelpProviderWithFullCache. /// internal HelpProviderWithFullCache(HelpSystem helpSystem) : base(helpSystem) { @@ -30,14 +27,15 @@ internal HelpProviderWithFullCache(HelpSystem helpSystem) : base(helpSystem) /// Exact match help for a target. This function will be sealed right here /// since this is no need for children class to override this member. /// - /// help request object - /// The HelpInfo found. Null if nothing is found + /// Help request object. + /// The HelpInfo found. Null if nothing is found. internal sealed override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { if (!this.CacheFullyLoaded) { LoadCache(); } + this.CacheFullyLoaded = true; return base.ExactMatchHelp(helpRequest); @@ -47,7 +45,7 @@ internal sealed override IEnumerable ExactMatchHelp(HelpRequest helpRe /// Do exact match help for a target. This member is sealed right here since /// children class don't need to override this member. /// - /// help request object + /// Help request object. internal sealed override void DoExactMatchHelp(HelpRequest helpRequest) { } @@ -56,20 +54,21 @@ internal sealed override void DoExactMatchHelp(HelpRequest helpRequest) /// Search help for a target. This function will be sealed right here /// since this is no need for children class to override this member. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content. Individual /// provider can decide which content to search in. /// /// If false, searches for pattern in the command names. /// - /// a collection of help info objects + /// A collection of help info objects. internal sealed override IEnumerable SearchHelp(HelpRequest helpRequest, bool searchOnlyContent) { if (!this.CacheFullyLoaded) { LoadCache(); } + this.CacheFullyLoaded = true; return base.SearchHelp(helpRequest, searchOnlyContent); @@ -79,8 +78,8 @@ internal sealed override IEnumerable SearchHelp(HelpRequest helpReques /// Do search help. This function will be sealed right here /// since this is no need for children class to override this member. /// - /// help request object - /// a collection of help info objects + /// Help request object. + /// A collection of help info objects. internal sealed override IEnumerable DoSearchHelp(HelpRequest helpRequest) { return null; diff --git a/src/System.Management.Automation/help/HelpRequest.cs b/src/System.Management.Automation/help/HelpRequest.cs index fab35c51f5d..7e4de9f1569 100644 --- a/src/System.Management.Automation/help/HelpRequest.cs +++ b/src/System.Management.Automation/help/HelpRequest.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -23,7 +22,7 @@ namespace System.Management.Automation internal class HelpRequest { /// - /// Constructor for HelpRequest + /// Constructor for HelpRequest. /// /// /// @@ -65,7 +64,7 @@ internal HelpRequest Clone() internal string Target { get; set; } /// - /// Help category filter + /// Help category filter. /// /// internal HelpCategory HelpCategory { get; set; } = HelpCategory.None; @@ -78,7 +77,6 @@ internal HelpRequest Clone() /// /// If provider is set and helpCategory is 'Command', this will add provider specific help /// to provider. - /// /// /// internal string Provider { get; set; } @@ -122,13 +120,12 @@ internal HelpRequest Clone() /// 4. If command help is requested, search for alias also. /// 5. If help category is none, set it to be all. /// 6. Don't do default help. - /// /// internal void Validate() { - if (String.IsNullOrEmpty(Target) + if (string.IsNullOrEmpty(Target) && HelpCategory == HelpCategory.None - && String.IsNullOrEmpty(Provider) + && string.IsNullOrEmpty(Provider) && Component == null && Role == null && Functionality == null @@ -139,9 +136,9 @@ internal void Validate() return; } - if (String.IsNullOrEmpty(Target)) + if (string.IsNullOrEmpty(Target)) { - if (!String.IsNullOrEmpty(Provider) && + if (!string.IsNullOrEmpty(Provider) && (HelpCategory == HelpCategory.None || HelpCategory == HelpCategory.Provider) ) { @@ -158,7 +155,7 @@ internal void Validate() if ((!(Component == null && Role == null && Functionality == null)) && (HelpCategory == HelpCategory.None)) { - HelpCategory = HelpCategory.Alias | HelpCategory.Cmdlet | HelpCategory.Function | HelpCategory.Filter | HelpCategory.ExternalScript | HelpCategory.ScriptCommand | HelpCategory.Workflow; + HelpCategory = HelpCategory.Alias | HelpCategory.Cmdlet | HelpCategory.Function | HelpCategory.Filter | HelpCategory.ExternalScript | HelpCategory.ScriptCommand; return; } diff --git a/src/System.Management.Automation/help/HelpSystem.cs b/src/System.Management.Automation/help/HelpSystem.cs index d12f7e32048..8880e022cac 100644 --- a/src/System.Management.Automation/help/HelpSystem.cs +++ b/src/System.Management.Automation/help/HelpSystem.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Management.Automation.Language; using System.Globalization; +using System.Management.Automation.Language; + using Microsoft.PowerShell.Commands; using System.Management.Automation.Runspaces; @@ -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,25 +73,24 @@ 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 - /// + /// b. localization. /// internal class HelpSystem { /// /// Constructor for HelpSystem. /// - /// Execution context for this help system + /// Execution context for this help system. internal HelpSystem(ExecutionContext context) { if (context == null) @@ -104,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. /// /// @@ -122,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. @@ -150,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) @@ -174,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 @@ -210,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 @@ -237,25 +234,26 @@ internal bool VerboseHelpErrors // Cache of search paths that are currently active. // This will save a lot time when help providers do their searching - private Collection _searchPaths = null; + private Collection _searchPaths = null; /// /// 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 + /// A collection of strings representing locations. internal Collection GetSearchPaths() { // return the cache if already present. - if (null != _searchPaths) + if (_searchPaths != null) { return _searchPaths; } - _searchPaths = new Collection(); + + _searchPaths = new Collection(); // add loaded modules paths to the search path - if (null != ExecutionContext.Modules) + if (ExecutionContext.Modules != null) { foreach (PSModuleInfo loadedModule in ExecutionContext.Modules.ModuleTable.Values) { @@ -272,17 +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 + /// Help request object. + /// An array of HelpInfo objects private IEnumerable DoGetHelp(HelpRequest helpRequest) { _lastErrors.Clear(); @@ -291,7 +288,7 @@ private IEnumerable DoGetHelp(HelpRequest helpRequest) _lastHelpCategory = helpRequest.HelpCategory; - if (String.IsNullOrEmpty(helpRequest.Target)) + if (string.IsNullOrEmpty(helpRequest.Target)) { HelpInfo helpInfo = GetDefaultHelp(); @@ -324,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) { @@ -344,15 +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. + /// HelpInfo object retrieved (can be null) internal IEnumerable ExactMatchHelp(HelpRequest helpRequest) { bool isHelpInfoFound = false; @@ -374,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; @@ -388,15 +384,13 @@ internal IEnumerable ExactMatchHelp(HelpRequest helpRequest) /// This is used in the following known scenarios so far /// 1. Alias: helpInfo returned by Alias is not what end user needed. /// The real help can be retrieved from Command help provider. - /// /// /// - /// Help request object + /// Help request object. /// Never returns null. /// helpInfos is not null or empty. private IEnumerable ForwardHelp(HelpInfo helpInfo, HelpRequest helpRequest) { - Collection result = new Collection(); // findout if this helpInfo needs to be processed further.. if (helpInfo.ForwardHelpCategory == HelpCategory.None && string.IsNullOrEmpty(helpInfo.ForwardTarget)) { @@ -457,17 +451,17 @@ private HelpInfo GetDefaultHelp() } /// - /// Get help that exactly match the target + /// Get help that exactly match the target. /// - /// help request object - /// An IEnumerable of HelpInfo object + /// Help request object. + /// An IEnumerable of HelpInfo object. private IEnumerable SearchHelp(HelpRequest helpRequest) { int countOfHelpInfosFound = 0; 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; @@ -544,10 +538,10 @@ 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 + /// Return the list of help providers initialized. /// /// a list of help providers internal ArrayList HelpProviders @@ -604,15 +598,15 @@ private void InitializeHelpProviders() // help providers using reflection. This is not in v1 right now. // private static HelpProviderInfo[] _providerInfos = new HelpProviderInfo[] - { new HelpProviderInfo("", "AliasHelpProvider", HelpCategory.Alias), - new HelpProviderInfo("", "CommandHelpProvider", HelpCategory.Command), - new HelpProviderInfo("", "ProviderHelpProvider", HelpCategory.Provider), - new HelpProviderInfo("", "OverviewHelpProvider", HelpCategory.Overview), - new HelpProviderInfo("", "GeneralHelpProvider", HelpCategory.General), - new HelpProviderInfo("", "FAQHelpProvider", HelpCategory.FAQ), - new HelpProviderInfo("", "GlossaryHelpProvider", HelpCategory.Glossary), - new HelpProviderInfo("", "HelpFileHelpProvider", HelpCategory.HelpFile), - new HelpProviderInfo("", "DefaultHelpHelpProvider", HelpCategory.DefaultHelp) + { new HelpProviderInfo(string.Empty, "AliasHelpProvider", HelpCategory.Alias), + new HelpProviderInfo(string.Empty, "CommandHelpProvider", HelpCategory.Command), + new HelpProviderInfo(string.Empty, "ProviderHelpProvider", HelpCategory.Provider), + new HelpProviderInfo(string.Empty, "OverviewHelpProvider", HelpCategory.Overview), + new HelpProviderInfo(string.Empty, "GeneralHelpProvider", HelpCategory.General), + new HelpProviderInfo(string.Empty, "FAQHelpProvider", HelpCategory.FAQ), + new HelpProviderInfo(string.Empty, "GlossaryHelpProvider", HelpCategory.Glossary), + new HelpProviderInfo(string.Empty, "HelpFileHelpProvider", HelpCategory.HelpFile), + new HelpProviderInfo(string.Empty, "DefaultHelpHelpProvider", HelpCategory.DefaultHelp) }; private void InitializeHelpProviders() @@ -633,7 +627,7 @@ private HelpProvider GetHelpProvider(HelpProviderInfo providerInfo) { Assembly providerAssembly = null; - if (String.IsNullOrEmpty(providerInfo.AssemblyName)) + if (string.IsNullOrEmpty(providerInfo.AssemblyName)) { providerAssembly = Assembly.GetExecutingAssembly(); } @@ -681,7 +675,7 @@ private HelpProvider GetHelpProvider(HelpProviderInfo providerInfo) private HelpErrorTracer _helpErrorTracer; /// - /// The error tracer for this help system + /// The error tracer for this help system. /// /// internal HelpErrorTracer HelpErrorTracer @@ -693,7 +687,7 @@ internal HelpErrorTracer HelpErrorTracer } /// - /// Start a trace frame for a help file + /// Start a trace frame for a help file. /// /// /// @@ -765,7 +759,6 @@ private void ValidateHelpCulture() /// /// Normally help providers will remove cached help content to make sure new help /// requests will be served with content of right culture. - /// /// internal void ResetHelpProviders() { @@ -805,13 +798,15 @@ internal void ClearScriptBlockTokenCache() } /// - /// Help progress info + /// 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; } } /// @@ -819,16 +814,16 @@ internal class HelpProgressInfo /// internal class HelpProviderInfo { - internal string AssemblyName = ""; - internal string ClassName = ""; + internal string AssemblyName = string.Empty; + internal string ClassName = string.Empty; internal HelpCategory HelpCategory = HelpCategory.None; /// - /// Constructor + /// Constructor. /// - /// assembly that contains this help provider - /// the class that implements this help provider - /// help category of this help provider + /// Assembly that contains this help provider. + /// The class that implements this help provider. + /// Help category of this help provider. internal HelpProviderInfo(string assemblyName, string className, HelpCategory helpCategory) { this.AssemblyName = assemblyName; @@ -838,63 +833,63 @@ internal HelpProviderInfo(string assemblyName, string className, HelpCategory he } /// - /// Help categories + /// Help categories. /// [Flags] internal enum HelpCategory { /// - /// Undefined help category + /// Undefined help category. /// None = 0x00, /// - /// Alias help + /// Alias help. /// Alias = 0x01, /// - /// Cmdlet help + /// Cmdlet help. /// Cmdlet = 0x02, /// - /// Provider help + /// Provider help. /// Provider = 0x04, /// - /// General keyword help + /// General keyword help. /// General = 0x10, /// - /// FAQ's + /// FAQ's. /// FAQ = 0x20, /// - /// Glossary and term definitions + /// Glossary and term definitions. /// Glossary = 0x40, /// - /// Help that is contained in help file + /// Help that is contained in help file. /// HelpFile = 0x80, /// - /// Help from a script block + /// Help from a script block. /// ScriptCommand = 0x100, /// - /// Help for a function + /// Help for a function. /// Function = 0x200, /// - /// Help for a filter + /// Help for a filter. /// Filter = 0x400, @@ -908,28 +903,23 @@ internal enum HelpCategory /// All = 0xFFFFF, - /// - /// Default Help + /// + /// Default Help. /// DefaultHelp = 0x1000, - /// - /// Help for a Workflow - /// - Workflow = 0x2000, - - /// - /// Help for a Configuration + /// + /// Help for a Configuration. /// Configuration = 0x4000, /// - /// Help for DSC Resource + /// Help for DSC Resource. /// DscResource = 0x8000, /// - /// Help for PS Classes + /// Help for PS Classes. /// Class = 0x10000 } diff --git a/src/System.Management.Automation/help/HelpUtils.cs b/src/System.Management.Automation/help/HelpUtils.cs new file mode 100644 index 00000000000..ab4a39f362a --- /dev/null +++ b/src/System.Management.Automation/help/HelpUtils.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Help; + +using Microsoft.PowerShell.Commands; + +namespace System.Management.Automation +{ + internal static class HelpUtils + { + private static string userHomeHelpPath = null; + + /// + /// Get the path to $HOME. + /// + internal static string GetUserHomeHelpSearchPath() + { + if (userHomeHelpPath == null) + { +#if UNIX + var userModuleFolder = Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); + string userScopeRootPath = System.IO.Path.GetDirectoryName(userModuleFolder); +#else + string userScopeRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "PowerShell"); +#endif + userHomeHelpPath = Path.Combine(userScopeRootPath, "Help"); + } + + return userHomeHelpPath; + } + + internal static string GetModuleBaseForUserHelp(string moduleBase, string moduleName) + { + string newModuleBase = moduleBase; + + // In case of inbox modules, the help is put under $PSHOME/, + // since the dlls are not published under individual module folders, but under $PSHome. + // 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 scope is AllUsers, then the help goes under moduleBase. + + var userHelpPath = GetUserHomeHelpSearchPath(); + string moduleBaseParent = Directory.GetParent(moduleBase).Name; + + if (moduleBase.EndsWith(moduleName, StringComparison.OrdinalIgnoreCase)) + { + // This module is not an inbox module, so help goes under / + newModuleBase = Path.Combine(userHelpPath, moduleName); + } + else if (string.Equals(moduleBaseParent, moduleName, StringComparison.OrdinalIgnoreCase)) + { + // This module has version folder. + var moduleVersion = Path.GetFileName(moduleBase); + newModuleBase = Path.Combine(userHelpPath, moduleName, moduleVersion); + } + else + { + // This module is inbox module, help should be under + newModuleBase = userHelpPath; + } + + return newModuleBase; + } + } +} diff --git a/src/System.Management.Automation/help/MUIFileSearcher.cs b/src/System.Management.Automation/help/MUIFileSearcher.cs index 7e12f26cdbb..dd437fa90c0 100644 --- a/src/System.Management.Automation/help/MUIFileSearcher.cs +++ b/src/System.Management.Automation/help/MUIFileSearcher.cs @@ -1,17 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.IO; using System.Text.RegularExpressions; 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. @@ -20,7 +19,7 @@ internal class MUIFileSearcher /// /// /// - private MUIFileSearcher(string target, Collection searchPaths, SearchMode searchMode) + private MUIFileSearcher(string target, Collection searchPaths, SearchMode searchMode) { Target = target; SearchPaths = searchPaths; @@ -32,7 +31,7 @@ private MUIFileSearcher(string target, Collection searchPaths, SearchMod /// /// /// - private MUIFileSearcher(string target, Collection searchPaths) + private MUIFileSearcher(string target, Collection searchPaths) : this(target, searchPaths, SearchMode.Unique) { } @@ -51,25 +50,33 @@ private MUIFileSearcher(string target, Collection searchPaths) /// /// Search path as provided by user. /// - internal Collection SearchPaths { get; } = null; + internal Collection SearchPaths { get; } = null; /// /// Search mode for this file search. /// internal SearchMode SearchMode { get; } = SearchMode.Unique; - private Collection _result = null; + private static readonly System.IO.EnumerationOptions _enumerationOptions = new() + { + IgnoreInaccessible = false, + AttributesToSkip = 0, + MatchType = MatchType.Win32, + MatchCasing = MatchCasing.CaseInsensitive, + }; + + private Collection _result = null; /// /// Result of the search. /// - internal Collection Result + internal Collection Result { get { if (_result == null) { - _result = new Collection(); + _result = new Collection(); // SearchForFiles will fill the result collection. SearchForFiles(); @@ -87,21 +94,21 @@ 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. + /// Search for files using the target, searchPaths member of this class. /// private void SearchForFiles() { - if (String.IsNullOrEmpty(this.Target)) + if (string.IsNullOrEmpty(this.Target)) return; string pattern = Path.GetFileName(this.Target); - if (String.IsNullOrEmpty(pattern)) + if (string.IsNullOrEmpty(pattern)) return; - Collection normalizedSearchPaths = NormalizeSearchPaths(this.Target, this.SearchPaths); + Collection normalizedSearchPaths = NormalizeSearchPaths(this.Target, this.SearchPaths); foreach (string directory in normalizedSearchPaths) { @@ -114,51 +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. - ArrayList result = new ArrayList(); - 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 (String[])result.ToArray(typeof(string)); -#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); @@ -182,6 +149,7 @@ private void AddFiles(string muiDirectory, string directory, string pattern) _result.Add(path); _uniqueMatches[uniqueToDirectory] = true; } + break; case SearchMode.First: @@ -207,18 +175,20 @@ private void SearchForFiles(string pattern, string directory) List cultureNameList = new List(); CultureInfo culture = CultureInfo.CurrentUICulture; - while (culture != null && !String.IsNullOrEmpty(culture.Name)) + while (culture != null && !string.IsNullOrEmpty(culture.Name)) { cultureNameList.Add(culture.Name); culture = culture.Parent; } - cultureNameList.Add(""); + + cultureNameList.Add(string.Empty); // Add en-US and en as fallback languages if (!cultureNameList.Contains("en-US")) { cultureNameList.Add("en-US"); } + if (!cultureNameList.Contains("en")) { cultureNameList.Add("en"); @@ -235,6 +205,7 @@ private void SearchForFiles(string pattern, string directory) return; } } + return; } @@ -250,13 +221,13 @@ private void SearchForFiles(string pattern, string directory) /// /// /// - private static Collection NormalizeSearchPaths(string target, Collection searchPaths) + private static Collection NormalizeSearchPaths(string target, Collection searchPaths) { - Collection result = new Collection(); + Collection result = new Collection(); // step 1: if target has path attached, directly locate // file from there. - if (!String.IsNullOrEmpty(target) && !String.IsNullOrEmpty(Path.GetDirectoryName(target))) + if (!string.IsNullOrEmpty(target) && !string.IsNullOrEmpty(Path.GetDirectoryName(target))) { string directory = Path.GetDirectoryName(target); @@ -265,8 +236,8 @@ private static Collection NormalizeSearchPaths(string target, Collection result.Add(Path.GetFullPath(directory)); } - //user specifically wanted to search in a particular directory - //so return.. + // user specifically wanted to search in a particular directory + // so return.. return result; } @@ -303,9 +274,9 @@ private static Collection NormalizeSearchPaths(string target, Collection /// /// /// - internal static Collection SearchFiles(string pattern) + internal static Collection SearchFiles(string pattern) { - return SearchFiles(pattern, new Collection()); + return SearchFiles(pattern, new Collection()); } /// @@ -314,7 +285,7 @@ internal static Collection SearchFiles(string pattern) /// /// /// - internal static Collection SearchFiles(string pattern, Collection searchPaths) + internal static Collection SearchFiles(string pattern, Collection searchPaths) { MUIFileSearcher searcher = new MUIFileSearcher(pattern, searchPaths); @@ -322,13 +293,13 @@ internal static Collection SearchFiles(string pattern, Collection - /// Locate a file in default search paths + /// Locate a file in default search paths. /// /// /// internal static string LocateFile(string file) { - return LocateFile(file, new Collection()); + return LocateFile(file, new Collection()); } /// @@ -336,12 +307,11 @@ internal static string LocateFile(string file) /// /// The file name to search is the filename part of path parameter. (Normally path /// parameter should contain only the filename part). - /// /// - /// This is the path to the file. If it has a path, we need to search under that path first - /// Additional search paths + /// This is the path to the file. If it has a path, we need to search under that path first. + /// Additional search paths. /// - internal static string LocateFile(string file, Collection searchPaths) + internal static string LocateFile(string file, Collection searchPaths) { MUIFileSearcher searcher = new MUIFileSearcher(file, searchPaths, SearchMode.First); @@ -355,7 +325,7 @@ internal static string LocateFile(string file, Collection searchPaths) } /// - /// This enum defines different search mode for the MUIFileSearcher + /// This enum defines different search mode for the MUIFileSearcher. /// internal enum SearchMode { @@ -369,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 4bc7c03db3e..579d72c87fe 100644 --- a/src/System.Management.Automation/help/MamlClassHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlClassHelpInfo.cs @@ -1,16 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Xml; namespace System.Management.Automation { /// - /// /// Class MamlClassHelpInfo keeps track of help information to be returned by /// class help provider. - /// /// internal class MamlClassHelpInfo : HelpInfo { @@ -45,21 +42,21 @@ private MamlClassHelpInfo(XmlNode xmlNode, HelpCategory helpCategory) /// /// PSObject representation on help. /// - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; #region Load /// /// Create a MamlClassHelpInfo object from an XmlNode. /// - /// xmlNode that contains help info - /// help category this maml object fits into - /// MamlCommandHelpInfo object created + /// XmlNode that contains help info. + /// Help category this maml object fits into. + /// MamlCommandHelpInfo object created. internal static MamlClassHelpInfo Load(XmlNode xmlNode, HelpCategory helpCategory) { MamlClassHelpInfo mamlClassHelpInfo = new MamlClassHelpInfo(xmlNode, helpCategory); - if (String.IsNullOrEmpty(mamlClassHelpInfo.Name)) + if (string.IsNullOrEmpty(mamlClassHelpInfo.Name)) return null; mamlClassHelpInfo.AddCommonHelpProperties(); @@ -85,11 +82,11 @@ internal MamlClassHelpInfo Copy() /// Clone the help object with a new category. /// /// - /// MamlClassHelpInfo + /// MamlClassHelpInfo. 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 d122a91a12f..8ae7b0a32cd 100644 --- a/src/System.Management.Automation/help/MamlCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlCommandHelpInfo.cs @@ -1,18 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Globalization; -using System.Xml; using System.Text; +using System.Xml; namespace System.Management.Automation { /// - /// /// Class MamlCommandHelpInfo keeps track of help information to be returned by /// command help provider. - /// /// internal class MamlCommandHelpInfo : BaseCommandHelpInfo { @@ -37,10 +34,12 @@ internal MamlCommandHelpInfo(PSObject helpObject, HelpCategory helpCategory) { _component = helpObject.Properties["Component"].Value as string; } + if (helpObject.Properties["Role"] != null) { _role = helpObject.Properties["Role"].Value as string; } + if (helpObject.Properties["Functionality"] != null) { _functionality = helpObject.Properties["Functionality"].Value as string; @@ -99,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. @@ -146,7 +145,6 @@ private string Notes } } - #endregion #region Component, Role, Features @@ -181,7 +179,7 @@ internal override string Component private string _role = null; /// - /// Role for this command + /// Role for this command. /// /// internal override string Role @@ -194,7 +192,7 @@ internal override string Role private string _functionality = null; /// - /// Functionality for this command + /// Functionality for this command. /// /// internal override string Functionality @@ -219,7 +217,7 @@ internal void SetAdditionalDataFromHelpComment(string component, string function /// /// Add user-defined command help data to command help. /// - /// User defined data object + /// User defined data object. internal void AddUserDefinedData(UserDefinedHelpData userDefinedData) { if (userDefinedData == null) @@ -253,14 +251,14 @@ internal void AddUserDefinedData(UserDefinedHelpData userDefinedData) /// /// Create a MamlCommandHelpInfo object from an XmlNode. /// - /// xmlNode that contains help info - /// help category this maml object fits into - /// MamlCommandHelpInfo object created + /// XmlNode that contains help info. + /// Help category this maml object fits into. + /// MamlCommandHelpInfo object created. internal static MamlCommandHelpInfo Load(XmlNode xmlNode, HelpCategory helpCategory) { MamlCommandHelpInfo mamlCommandHelpInfo = new MamlCommandHelpInfo(xmlNode, helpCategory); - if (String.IsNullOrEmpty(mamlCommandHelpInfo.Name)) + if (string.IsNullOrEmpty(mamlCommandHelpInfo.Name)) return null; mamlCommandHelpInfo.AddCommonHelpProperties(); @@ -281,9 +279,9 @@ internal static MamlCommandHelpInfo Load(XmlNode xmlNode, HelpCategory helpCateg /// /// A new MamlCommandHelpInfo is created to avoid polluting the provider help cache. /// - /// provider-specific cmdletHelp to merge into current MamlCommandHelpInfo object - /// provider-specific dynamic parameter help to merge into current MamlCommandHelpInfo object - /// merged command help info object + /// Provider-specific cmdletHelp to merge into current MamlCommandHelpInfo object. + /// Provider-specific dynamic parameter help to merge into current MamlCommandHelpInfo object. + /// Merged command help info object. internal MamlCommandHelpInfo MergeProviderSpecificHelp(PSObject cmdletHelp, PSObject[] dynamicParameterHelp) { if (this._fullHelpObject == null) @@ -310,14 +308,14 @@ internal MamlCommandHelpInfo MergeProviderSpecificHelp(PSObject cmdletHelp, PSOb #region Helper Methods and Overloads /// - /// Extracts text for a given property from the full help object + /// Extracts text for a given property from the full help object. /// - /// FullHelp object + /// FullHelp object. /// /// 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; @@ -338,14 +336,14 @@ private string ExtractTextForHelpProperty(PSObject psObject, string propertyName /// /// /// - private string ExtractText(PSObject psObject) + private static string ExtractText(PSObject psObject) { - if (null == 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. @@ -373,6 +371,7 @@ private string ExtractText(PSObject psObject) { result.Append(ExtractText(item)); } + break; case "system.management.automation.psobject": result.Append(ExtractText(PSObject.AsPSObject(propertyInfo.Value))); @@ -392,13 +391,13 @@ private string ExtractText(PSObject psObject) /// The underlying code will usually run pattern.IsMatch() on /// content it wants to search. /// Cmdlet help info looks for pattern in Synopsis and - /// DetailedDescription + /// DetailedDescription. /// /// /// internal override bool MatchPatternInContent(WildcardPattern pattern) { - System.Management.Automation.Diagnostics.Assert(null != pattern, "pattern cannot be null"); + System.Management.Automation.Diagnostics.Assert(pattern != null, "pattern cannot be null"); string synopsis = Synopsis; if ((!string.IsNullOrEmpty(synopsis)) && (pattern.IsMatch(synopsis))) @@ -442,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 2bf558910e7..ecf565c942b 100644 --- a/src/System.Management.Automation/help/MamlNode.cs +++ b/src/System.Management.Automation/help/MamlNode.cs @@ -1,18 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.ObjectModel; using System.Globalization; -using System.Xml; using System.Reflection; using System.Text; +using System.Xml; namespace System.Management.Automation { /// - /// /// MamlNode is an xml node in MAML schema. Maml schema includes formatting oriented tags like para, list /// etc, which needs to be taken care of during display. As a result, xml node in Maml schema can't be /// converted into PSObject directly with XmlNodeAdapter. @@ -46,7 +44,6 @@ namespace System.Management.Automation /// /// /// - /// /// After processing, content of these three tags will be converted into textItem and its derivations, /// 1. para => paraTextItem /// @@ -67,21 +64,20 @@ namespace System.Management.Automation /// definition text here /// /// - /// internal class MamlNode { /// - /// Constructor for HelpInfo + /// Constructor for HelpInfo. /// internal MamlNode(XmlNode xmlNode) { _xmlNode = xmlNode; } - private XmlNode _xmlNode; + private readonly XmlNode _xmlNode; /// - /// Underline xmlNode for this MamlNode object + /// Underline xmlNode for this MamlNode object. /// /// internal XmlNode XmlNode @@ -95,7 +91,7 @@ internal XmlNode XmlNode private PSObject _mshObject; /// - /// mshObject which is converted from XmlNode. + /// MshObject which is converted from XmlNode. /// /// internal PSObject PSObject @@ -127,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 @@ -181,7 +177,6 @@ internal PSObject PSObject /// /// /// - /// /// In this case, an PSObject based on an PSObject array will be created. The inside PSObject array /// will contain following items /// . a MamlParaTextItem based on "para 1" @@ -192,7 +187,6 @@ internal PSObject PSObject /// /// The outside PSObject will have a property /// attribute => "value" - /// /// /// /// @@ -215,14 +209,14 @@ 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) { - if (String.Compare(xmlNode.Attributes["type"].Value, "field", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(xmlNode.Attributes["type"].Value, "field", StringComparison.OrdinalIgnoreCase)) mshObject.TypeNames.Add("MamlPSClassHelpInfo#field"); - else if (String.Compare(xmlNode.Attributes["type"].Value, "method", StringComparison.OrdinalIgnoreCase) == 0) + else if (string.Equals(xmlNode.Attributes["type"].Value, "method", StringComparison.OrdinalIgnoreCase)) mshObject.TypeNames.Add("MamlPSClassHelpInfo#method"); } @@ -299,7 +293,6 @@ private PSObject GetInsidePSObject(XmlNode xmlNode) /// We are making each property value is an array (PSObject[]) to start with. /// At the end, SimplifyProperties will be called to reduce PSObject[] containing /// only one element to PSObject itself. - /// /// /// /// @@ -328,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.. @@ -365,7 +358,6 @@ private void RemoveUnsupportedNodes(XmlNode xmlNode) /// an node with maml formatting node inside is treated. The side effect of this /// is that the properties for outside mshObject will be lost. An example of this /// is that, - /// /// /// /// @@ -373,16 +365,15 @@ private void RemoveUnsupportedNodes(XmlNode xmlNode) /// /// /// - /// /// After the processing, PSObject corresponding to command will have an property /// with name "description" and a value of an PSObject array created based on /// maml formatting node inside "description" node. The attribute of description node /// "attrib1" will be lost. This seems to be OK with current practice of authoring /// monad command help. /// - /// property hashtable - /// property name - /// property value + /// Property hashtable. + /// Property name. + /// Property value. private static void AddProperty(Hashtable properties, string name, PSObject mshObject) { ArrayList propertyValues = (ArrayList)properties[name]; @@ -518,7 +509,7 @@ private static bool IncludeMamlFormatting(XmlNode xmlNode) /// Check whether a node is for maml formatting. This include following nodes, /// a. para /// b. list - /// c. definitionList + /// c. definitionList. /// /// /// @@ -590,7 +581,6 @@ private static bool IsMamlFormattingPSObject(PSObject mshObject) /// /// /// - /// /// In this case, an PSObject based on an PSObject array will be created. The inside PSObject array /// will contain following items /// . a MamlParaTextItem based on "para 1" @@ -607,13 +597,14 @@ private PSObject[] GetMamlFormattingPSObjects(XmlNode xmlNode) int paraNodes = GetParaMamlNodeCount(xmlNode.ChildNodes); int count = 0; - + // Don't trim the content if this is an "introduction" node. + bool trim = !string.Equals(xmlNode.Name, "maml:introduction", StringComparison.OrdinalIgnoreCase); foreach (XmlNode childNode in xmlNode.ChildNodes) { if (childNode.LocalName.Equals("para", StringComparison.OrdinalIgnoreCase)) { ++count; - PSObject paraPSObject = GetParaPSObject(childNode, count != paraNodes); + PSObject paraPSObject = GetParaPSObject(childNode, count != paraNodes, trim: trim); if (paraPSObject != null) mshObjects.Add(paraPSObject); continue; @@ -651,11 +642,11 @@ private PSObject[] GetMamlFormattingPSObjects(XmlNode xmlNode) } /// - /// Gets the number of para nodes + /// Gets the number of para nodes. /// /// /// - private int GetParaMamlNodeCount(XmlNodeList nodes) + private static int GetParaMamlNodeCount(XmlNodeList nodes) { int i = 0; @@ -663,10 +654,11 @@ private int GetParaMamlNodeCount(XmlNodeList nodes) { if (childNode.LocalName.Equals("para", StringComparison.OrdinalIgnoreCase)) { - if (childNode.InnerText.Trim().Equals(String.Empty)) + if (childNode.InnerText.Trim().Equals(string.Empty)) { continue; } + ++i; } } @@ -682,7 +674,7 @@ private int GetParaMamlNodeCount(XmlNodeList nodes) private void WriteMamlInvalidChildNodeError(XmlNode node, XmlNode childNode) { ErrorRecord errorRecord = new ErrorRecord(new ParentContainsErrorRecordException("MamlInvalidChildNodeError"), "MamlInvalidChildNodeError", ErrorCategory.SyntaxError, null); - errorRecord.ErrorDetails = new ErrorDetails(typeof(MamlNode).GetTypeInfo().Assembly, "HelpErrors", "MamlInvalidChildNodeError", node.LocalName, childNode.LocalName, GetNodePath(node)); + errorRecord.ErrorDetails = new ErrorDetails(typeof(MamlNode).Assembly, "HelpErrors", "MamlInvalidChildNodeError", node.LocalName, childNode.LocalName, GetNodePath(node)); this.Errors.Add(errorRecord); } @@ -695,14 +687,14 @@ private void WriteMamlInvalidChildNodeError(XmlNode node, XmlNode childNode) private void WriteMamlInvalidChildNodeCountError(XmlNode node, string childNodeName, int count) { ErrorRecord errorRecord = new ErrorRecord(new ParentContainsErrorRecordException("MamlInvalidChildNodeCountError"), "MamlInvalidChildNodeCountError", ErrorCategory.SyntaxError, null); - errorRecord.ErrorDetails = new ErrorDetails(typeof(MamlNode).GetTypeInfo().Assembly, "HelpErrors", "MamlInvalidChildNodeCountError", node.LocalName, childNodeName, count, GetNodePath(node)); + errorRecord.ErrorDetails = new ErrorDetails(typeof(MamlNode).Assembly, "HelpErrors", "MamlInvalidChildNodeCountError", node.LocalName, childNodeName, count, GetNodePath(node)); this.Errors.Add(errorRecord); } private static string GetNodePath(XmlNode xmlNode) { if (xmlNode == null) - return ""; + return string.Empty; if (xmlNode.ParentNode == null) return "\\" + xmlNode.LocalName; @@ -713,7 +705,7 @@ private static string GetNodePath(XmlNode xmlNode) private static string GetNodeIndex(XmlNode xmlNode) { if (xmlNode == null || xmlNode.ParentNode == null) - return ""; + return string.Empty; int index = 0; int total = 0; @@ -737,7 +729,7 @@ private static string GetNodeIndex(XmlNode xmlNode) return "[" + index.ToString("d", CultureInfo.CurrentCulture) + "]"; } - return ""; + return string.Empty; } /// @@ -749,12 +741,12 @@ private static string GetNodeIndex(XmlNode xmlNode) /// /// In this case, an PSObject of type "MamlParaTextItem" will be created with following property /// a. text="para text" - /// /// /// /// + /// /// - private static PSObject GetParaPSObject(XmlNode xmlNode, bool newLine) + private static PSObject GetParaPSObject(XmlNode xmlNode, bool newLine, bool trim = true) { if (xmlNode == null) return null; @@ -766,13 +758,19 @@ private static PSObject GetParaPSObject(XmlNode xmlNode, bool newLine) StringBuilder sb = new StringBuilder(); - if (newLine && !xmlNode.InnerText.Trim().Equals(String.Empty)) + if (newLine && !xmlNode.InnerText.Trim().Equals(string.Empty)) { sb.AppendLine(xmlNode.InnerText.Trim()); } else { - sb.Append(xmlNode.InnerText.Trim()); + var innerText = xmlNode.InnerText; + if (trim) + { + innerText = innerText.Trim(); + } + + sb.Append(innerText); } mshObject.Properties.Add(new PSNoteProperty("Text", sb.ToString())); @@ -800,13 +798,11 @@ private static PSObject GetParaPSObject(XmlNode xmlNode, bool newLine) /// /// /// - /// /// In this case, an array of PSObject, each of type "MamlOrderedListText" will be created with following /// two properties, /// a. tag=" 1. " or " 2. " /// b. text="text for list item 1" or "text for list item 2" /// In the case of unordered list, similar PSObject will created with type to be "MamlUnorderedListText" and tag="*" - /// /// /// /// @@ -885,7 +881,7 @@ private PSObject GetListItemPSObject(XmlNode xmlNode, bool ordered, ref int inde if (!xmlNode.LocalName.Equals("listItem", StringComparison.OrdinalIgnoreCase)) return null; - string text = ""; + string text = string.Empty; if (xmlNode.ChildNodes.Count > 1) { @@ -903,7 +899,7 @@ private PSObject GetListItemPSObject(XmlNode xmlNode, bool ordered, ref int inde WriteMamlInvalidChildNodeError(xmlNode, childNode); } - string tag = ""; + string tag = string.Empty; if (ordered) { tag = index.ToString("d2", CultureInfo.CurrentCulture); @@ -1023,7 +1019,7 @@ private PSObject GetDefinitionListItemPSObject(XmlNode xmlNode) WriteMamlInvalidChildNodeError(xmlNode, childNode); } - if (String.IsNullOrEmpty(term)) + if (string.IsNullOrEmpty(term)) return null; PSObject mshObject = new PSObject(); @@ -1052,14 +1048,14 @@ private string GetDefinitionText(XmlNode xmlNode) return null; if (xmlNode.ChildNodes == null || xmlNode.ChildNodes.Count == 0) - return ""; + return string.Empty; if (xmlNode.ChildNodes.Count > 1) { WriteMamlInvalidChildNodeCountError(xmlNode, "para", 1); } - string text = ""; + string text = string.Empty; foreach (XmlNode childNode in xmlNode.ChildNodes) { @@ -1087,14 +1083,12 @@ private string GetDefinitionText(XmlNode xmlNode) /// is to remove that fixed amount from the text. /// /// For example, in xml, - /// /// /// void function() /// { /// // call some other function here; /// } /// - /// /// we can find that the preformatted text are indented unanimously /// by 4 spaces because of its position in xml. /// @@ -1111,15 +1105,15 @@ private string GetDefinitionText(XmlNode xmlNode) /// private static string GetPreformattedText(string text) { - //we are assuming tabsize=4 here. - //It is discouraged to use tab in preformatted text. + // we are assuming tabsize=4 here. + // 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) - return ""; + return string.Empty; int minIndentation = GetMinIndentation(trimedLines); @@ -1148,8 +1142,8 @@ private static string GetPreformattedText(string text) /// /// Trim empty lines from the either end of an string array. /// - /// lines to trim - /// an string array with empty lines trimed on either end + /// Lines to trim. + /// An string array with empty lines trimed on either end. private static string[] TrimLines(string[] lines) { if (lines == null || lines.Length == 0) @@ -1185,7 +1179,7 @@ private static string[] TrimLines(string[] lines) } /// - /// Get minimum indentation of a paragraph + /// Get minimum indentation of a paragraph. /// /// /// @@ -1218,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; } @@ -1232,11 +1226,11 @@ private static int GetIndentation(string line) /// private static bool IsEmptyLine(string line) { - if (String.IsNullOrEmpty(line)) + if (string.IsNullOrEmpty(line)) return true; string trimedLine = line.Trim(); - if (String.IsNullOrEmpty(trimedLine)) + if (string.IsNullOrEmpty(trimedLine)) return true; return false; diff --git a/src/System.Management.Automation/help/MamlUtil.cs b/src/System.Management.Automation/help/MamlUtil.cs index 29fe3c165b4..d809655f97f 100644 --- a/src/System.Management.Automation/help/MamlUtil.cs +++ b/src/System.Management.Automation/help/MamlUtil.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Help; using System.Collections.Generic; +using System.Management.Automation.Help; namespace System.Management.Automation { /// /// The MamlUtil class. /// - internal class MamlUtil + internal static class MamlUtil { /// /// Takes Name value from maml2 and overrides it in maml1. @@ -34,10 +33,11 @@ internal static void OverridePSTypeNames(PSObject maml1, PSObject maml2) { if (typename.StartsWith(DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp, StringComparison.OrdinalIgnoreCase)) { - //Win8: 638494 if the original help is auto-generated, let the Provider help decide the format. + // Win8: 638494 if the original help is auto-generated, let the Provider help decide the format. return; } } + maml1.TypeNames.Clear(); // User request at the top.. foreach (string typeName in maml2.TypeNames) @@ -47,7 +47,7 @@ internal static void OverridePSTypeNames(PSObject maml1, PSObject maml2) } /// - /// Adds common properties like PSSnapIn,ModuleName from maml2 to maml1 + /// Adds common properties like PSSnapIn,ModuleName from maml2 to maml1. /// /// /// @@ -56,7 +56,7 @@ internal static void AddCommonProperties(PSObject maml1, PSObject maml2) if (maml1.Properties["PSSnapIn"] == null) { PSPropertyInfo snapInProperty = maml2.Properties["PSSnapIn"]; - if (null != snapInProperty) + if (snapInProperty != null) { maml1.Properties.Add(new PSNoteProperty("PSSnapIn", snapInProperty.Value)); } @@ -65,7 +65,7 @@ internal static void AddCommonProperties(PSObject maml1, PSObject maml2) if (maml1.Properties["ModuleName"] == null) { PSPropertyInfo moduleNameProperty = maml2.Properties["ModuleName"]; - if (null != moduleNameProperty) + if (moduleNameProperty != null) { maml1.Properties.Add(new PSNoteProperty("ModuleName", moduleNameProperty.Value)); } @@ -131,10 +131,10 @@ internal static void OverrideParameters(PSObject maml1, PSObject maml2) for (int index = 0; index < maml2items.Count; index++) { PSObject m2paramObj = PSObject.AsPSObject(maml2items[index]); - string param2Name = ""; + string param2Name = string.Empty; PSPropertyInfo m2propertyInfo = m2paramObj.Properties["Name"]; - if (null != m2propertyInfo) + if (m2propertyInfo != null) { if (!LanguagePrimitives.TryConvertTo(m2propertyInfo.Value, out param2Name)) { @@ -145,10 +145,10 @@ internal static void OverrideParameters(PSObject maml1, PSObject maml2) bool isParamFoundInMaml1 = false; foreach (PSObject m1ParamObj in maml1items) { - string param1Name = ""; + string param1Name = string.Empty; PSPropertyInfo m1PropertyInfo = m1ParamObj.Properties["Name"]; - if (null != m1PropertyInfo) + if (m1PropertyInfo != null) { if (!LanguagePrimitives.TryConvertTo(m1PropertyInfo.Value, out param1Name)) { @@ -192,7 +192,7 @@ internal static void PrependNotes(PSObject maml1, PSObject maml2) /// internal static PSPropertyInfo GetPropertyInfo(PSObject psObject, string[] path) { - if (path.Length <= 0) + if (path.Length == 0) { return null; } @@ -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; } @@ -240,7 +240,7 @@ internal static void PrependPropertyValue(PSObject maml1, PSObject maml2, string // For maml2: Add as collection or single item. No-op if PSPropertyInfo propertyInfo2 = GetPropertyInfo(maml2, path); - if (null != propertyInfo2) + if (propertyInfo2 != null) { var array = propertyInfo2.Value as Array; if (array != null) @@ -258,7 +258,7 @@ internal static void PrependPropertyValue(PSObject maml1, PSObject maml2, string // For maml1: Add as collection or single item. Do nothing if null or some other type. PSPropertyInfo propertyInfo1 = GetPropertyInfo(maml1, path); - if (null != propertyInfo1) + if (propertyInfo1 != null) { if (!shouldOverride) { @@ -290,7 +290,7 @@ internal static void PrependPropertyValue(PSObject maml1, PSObject maml2, string /// internal static void EnsurePropertyInfoPathExists(PSObject psObject, string[] path) { - if (path.Length <= 0) + if (path.Length == 0) { return; } @@ -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 ec418adfd86..2d80b2cc863 100644 --- a/src/System.Management.Automation/help/PSClassHelpProvider.cs +++ b/src/System.Management.Automation/help/PSClassHelpProvider.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Xml; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -15,7 +15,7 @@ namespace System.Management.Automation internal class PSClassHelpProvider : HelpProviderWithCache { /// - /// Constructor for PSClassHelpProvider + /// Constructor for PSClassHelpProvider. /// internal PSClassHelpProvider(HelpSystem helpSystem) : base(helpSystem) @@ -24,7 +24,7 @@ internal PSClassHelpProvider(HelpSystem helpSystem) } /// - /// Execution context of the HelpSystem + /// Execution context of the HelpSystem. /// private readonly ExecutionContext _context; @@ -32,7 +32,6 @@ internal PSClassHelpProvider(HelpSystem helpSystem) /// This is a hashtable to track which help files are loaded already. /// /// This will avoid one help file getting loaded again and again. - /// /// private readonly Hashtable _helpFiles = new Hashtable(); @@ -50,7 +49,7 @@ internal override string Name } /// - /// Supported Help Categories + /// Supported Help Categories. /// internal override HelpCategory HelpCategory { @@ -81,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)) { @@ -109,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)) { @@ -137,7 +131,7 @@ private IEnumerable GetHelpInfo(PSClassSearcher searcher) string moduleName = current.Module.Name; string moduleDir = current.Module.ModuleBase; - if (!String.IsNullOrEmpty(moduleName) && !String.IsNullOrEmpty(moduleDir)) + if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir)) { string helpFileToFind = moduleName + "-Help.xml"; @@ -148,7 +142,7 @@ private IEnumerable GetHelpInfo(PSClassSearcher searcher) string externalHelpFile = current.HelpFile; - if (!String.IsNullOrEmpty(externalHelpFile)) + if (!string.IsNullOrEmpty(externalHelpFile)) { FileInfo helpFileInfo = new FileInfo(externalHelpFile); DirectoryInfo dirToSearch = helpFileInfo.Directory; @@ -156,7 +150,7 @@ private IEnumerable GetHelpInfo(PSClassSearcher searcher) if (dirToSearch.Exists) { searchPaths.Add(dirToSearch.FullName); - helpFileToFind = helpFileInfo.Name; //If external help file is specified. Then use it. + helpFileToFind = helpFileInfo.Name; // If external help file is specified. Then use it. } } @@ -178,17 +172,16 @@ private IEnumerable GetHelpInfo(PSClassSearcher searcher) /// a. If the help file has an extension .maml. /// b. If HelpItems node (which should be the top node of any command help file) /// has an attribute "schema" with value "maml", its content is in maml - /// schema - /// + /// schema. /// /// File name. /// Nodes to check. /// internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode) { - Debug.Assert(!String.IsNullOrEmpty(helpFile), "helpFile cannot be null."); + Debug.Assert(!string.IsNullOrEmpty(helpFile), "helpFile cannot be null."); - if (helpFile.EndsWith(".maml", StringComparison.CurrentCultureIgnoreCase)) + if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase)) return true; if (helpItemsNode.Attributes == null) @@ -218,9 +211,9 @@ private HelpInfo GetHelpInfoFromHelpFile(PSClassInfo classInfo, string helpFileT if (!File.Exists(helpFile)) return null; - if (!String.IsNullOrEmpty(helpFile)) + if (!string.IsNullOrEmpty(helpFile)) { - //Load the help file only once. Then use it from the cache. + // Load the help file only once. Then use it from the cache. if (!_helpFiles.Contains(helpFile)) { LoadHelpFile(helpFile, helpFile, classInfo.Name, reportErrors); @@ -235,7 +228,7 @@ private HelpInfo GetHelpInfoFromHelpFile(PSClassInfo classInfo, string helpFileT /// /// Gets the HelpInfo object corresponding to the command. /// - /// help file identifier (either name of PSSnapIn or simply full path to help file) + /// Help file identifier (either name of PSSnapIn or simply full path to help file). /// Help Category for search. /// HelpInfo object. private HelpInfo GetFromPSClassHelpCache(string helpFileIdentifier, HelpCategory helpCategory) @@ -286,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)) { @@ -306,8 +299,8 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier, string com /// private void LoadHelpFile(string helpFile, string helpFileIdentifier) { - Dbg.Assert(!String.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty."); - Dbg.Assert(!String.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty."); + Dbg.Assert(!string.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty."); + Dbg.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty."); XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument( new FileInfo(helpFile), @@ -324,7 +317,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) for (int i = 0; i < doc.ChildNodes.Count; i++) { XmlNode node = doc.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase)) { helpItemsNode = node; break; @@ -350,7 +343,7 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) string nodeLocalName = node.LocalName; - bool isClass = (String.Compare(nodeLocalName, "class", StringComparison.OrdinalIgnoreCase) == 0); + bool isClass = (string.Equals(nodeLocalName, "class", StringComparison.OrdinalIgnoreCase)); if (node.NodeType == XmlNodeType.Element && isClass) { @@ -375,4 +368,4 @@ private void LoadHelpFile(string helpFile, string helpFileIdentifier) #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs b/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs index b092467768a..d51fb716b82 100644 --- a/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using Dbg = System.Management.Automation.Diagnostics; @@ -14,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 993376789cf..817e2b3f0e6 100644 --- a/src/System.Management.Automation/help/ProviderContext.cs +++ b/src/System.Management.Automation/help/ProviderContext.cs @@ -1,10 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Management.Automation.Internal; using System.Management.Automation.Provider; using System.Xml; -using System.Management.Automation.Internal; using Dbg = System.Management.Automation.Diagnostics; @@ -41,7 +40,7 @@ internal ProviderContext( ExecutionContext executionContext, PathIntrinsics pathIntrinsics) { - Dbg.Assert(null != executionContext, "ExecutionContext cannot be null."); + Dbg.Assert(executionContext != null, "ExecutionContext cannot be null."); _requestedPath = requestedPath; _executionContext = executionContext; _pathIntrinsics = pathIntrinsics; @@ -109,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; } @@ -144,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 6ec2faa4d34..80859a2ddf8 100644 --- a/src/System.Management.Automation/help/ProviderHelpInfo.cs +++ b/src/System.Management.Automation/help/ProviderHelpInfo.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Xml; @@ -8,15 +7,13 @@ 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 + /// Constructor for HelpProvider. /// private ProviderHelpInfo(XmlNode xmlNode) { @@ -40,24 +37,24 @@ internal override string Name get { if (_fullHelpObject == null) - return ""; + return string.Empty; if (_fullHelpObject.Properties["Name"] == null) - return ""; + return string.Empty; if (_fullHelpObject.Properties["Name"].Value == null) - return ""; + return string.Empty; string name = _fullHelpObject.Properties["Name"].Value.ToString(); if (name == null) - return ""; + return string.Empty; return name.Trim(); } } /// - /// Synopsis in the provider help info + /// Synopsis in the provider help info. /// /// Synopsis in the provider help info internal override string Synopsis @@ -65,24 +62,24 @@ internal override string Synopsis get { if (_fullHelpObject == null) - return ""; + return string.Empty; if (_fullHelpObject.Properties["Synopsis"] == null) - return ""; + return string.Empty; if (_fullHelpObject.Properties["Synopsis"].Value == null) - return ""; + return string.Empty; string synopsis = _fullHelpObject.Properties["Synopsis"].Value.ToString(); if (synopsis == null) - return ""; + return string.Empty; return synopsis.Trim(); } } /// - /// Detailed description in the provider help info + /// Detailed description in the provider help info. /// /// Detailed description in the provider help info internal string DetailedDescription @@ -90,30 +87,30 @@ internal string DetailedDescription get { if (this.FullHelp == null) - return ""; + return string.Empty; if (this.FullHelp.Properties["DetailedDescription"] == null || this.FullHelp.Properties["DetailedDescription"].Value == null) { - return ""; + return string.Empty; } IList descriptionItems = FullHelp.Properties["DetailedDescription"].Value as IList; if (descriptionItems == null || descriptionItems.Count == 0) { - return ""; + 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); foreach (object descriptionItem in descriptionItems) { PSObject descriptionObject = PSObject.AsPSObject(descriptionItem); - if ((null == descriptionObject) || - (null == descriptionObject.Properties["Text"]) || - (null == descriptionObject.Properties["Text"].Value)) + if ((descriptionObject == null) || + (descriptionObject.Properties["Text"] == null) || + (descriptionObject.Properties["Text"].Value == null)) { continue; } @@ -128,7 +125,7 @@ internal string DetailedDescription } /// - /// Help category for this provider help info, which is constantly HelpCategory.Provider + /// Help category for this provider help info, which is constantly HelpCategory.Provider. /// /// Help category for this provider help info internal override HelpCategory HelpCategory @@ -139,10 +136,10 @@ internal override HelpCategory HelpCategory } } - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; /// - /// Full help object for this provider help info + /// Full help object for this provider help info. /// /// Full help object for this provider help info internal override PSObject FullHelp @@ -153,33 +150,26 @@ internal override PSObject FullHelp } } - /// /// Returns true if help content in help info matches the /// pattern contained in . /// The underlying code will usually run pattern.IsMatch() on /// content it wants to search. /// Provider help info looks for pattern in Synopsis and - /// DetailedDescription + /// DetailedDescription. /// /// /// internal override bool MatchPatternInContent(WildcardPattern pattern) { - Diagnostics.Assert(null != pattern, "pattern cannot be null"); + Diagnostics.Assert(pattern != null, "pattern cannot be null"); string synopsis = Synopsis; string detailedDescription = DetailedDescription; - if (null == synopsis) - { - synopsis = string.Empty; - } + synopsis ??= string.Empty; - if (null == detailedDescription) - { - detailedDescription = string.Empty; - } + detailedDescription ??= string.Empty; return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription); } @@ -192,13 +182,13 @@ internal override bool MatchPatternInContent(WildcardPattern pattern) private Hashtable _cmdletHelps; /// - /// Return the provider-specific cmdlet help based on input cmdletName + /// Return the provider-specific cmdlet help based on input cmdletName. /// - /// cmdletName on which to get provider-specific help - /// An mshObject that contains provider-specific commandlet help + /// CmdletName on which to get provider-specific help. + /// An mshObject that contains provider-specific commandlet help. internal PSObject GetCmdletHelp(string cmdletName) { - if (String.IsNullOrEmpty(cmdletName)) + if (string.IsNullOrEmpty(cmdletName)) return null; LoadCmdletHelps(); @@ -263,10 +253,10 @@ private void LoadCmdletHelps() private Hashtable _dynamicParameterHelps; /// - /// Return the provider-specific dynamic parameter help based on input parameter name + /// Return the provider-specific dynamic parameter help based on input parameter name. /// - /// an array of parameters to retrieve help - /// an array of mshObject that contains the parameter help + /// An array of parameters to retrieve help. + /// An array of mshObject that contains the parameter help. internal PSObject[] GetDynamicParameterHelp(string[] parameters) { if (parameters == null || parameters.Length == 0) @@ -347,15 +337,15 @@ private void LoadDynamicParameterHelps() #region Load Help /// - /// Create providerHelpInfo from an xmlNode + /// Create providerHelpInfo from an xmlNode. /// - /// xml node that contains the provider help info - /// the providerHelpInfo object created + /// Xml node that contains the provider help info. + /// The providerHelpInfo object created. internal static ProviderHelpInfo Load(XmlNode xmlNode) { ProviderHelpInfo providerHelpInfo = new ProviderHelpInfo(xmlNode); - if (String.IsNullOrEmpty(providerHelpInfo.Name)) + if (string.IsNullOrEmpty(providerHelpInfo.Name)) return null; providerHelpInfo.AddCommonHelpProperties(); diff --git a/src/System.Management.Automation/help/ProviderHelpProvider.cs b/src/System.Management.Automation/help/ProviderHelpProvider.cs index 7323aeba6cf..afc0c71c6c2 100644 --- a/src/System.Management.Automation/help/ProviderHelpProvider.cs +++ b/src/System.Management.Automation/help/ProviderHelpProvider.cs @@ -1,14 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; -using System.Xml; +using System.IO; using System.Reflection; +using System.Xml; namespace System.Management.Automation { @@ -22,7 +21,7 @@ namespace System.Management.Automation internal class ProviderHelpProvider : HelpProviderWithCache { /// - /// Constructor for HelpProvider + /// Constructor for HelpProvider. /// internal ProviderHelpProvider(HelpSystem helpSystem) : base(helpSystem) { @@ -64,7 +63,7 @@ internal override HelpCategory HelpCategory /// /// Do exact match help based on the target. /// - /// help request object + /// Help request object. internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) { Collection matchingProviders = null; @@ -83,7 +82,7 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) if (this.HelpSystem.LastHelpCategory == HelpCategory.Provider) { ErrorRecord errorRecord = new ErrorRecord(e, "ProviderLoadError", ErrorCategory.ResourceUnavailable, null); - errorRecord.ErrorDetails = new ErrorDetails(typeof(ProviderHelpProvider).GetTypeInfo().Assembly, "HelpErrors", "ProviderLoadError", helpRequest.Target, e.Message); + errorRecord.ErrorDetails = new ErrorDetails(typeof(ProviderHelpProvider).Assembly, "HelpErrors", "ProviderLoadError", helpRequest.Target, e.Message); this.HelpSystem.LastErrors.Add(errorRecord); } } @@ -119,7 +118,6 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) } } - private static string GetProviderAssemblyPath(ProviderInfo providerInfo) { if (providerInfo == null) @@ -128,7 +126,7 @@ private static string GetProviderAssemblyPath(ProviderInfo providerInfo) if (providerInfo.ImplementingType == null) return null; - return Path.GetDirectoryName(providerInfo.ImplementingType.GetTypeInfo().Assembly.Location); + return Path.GetDirectoryName(providerInfo.ImplementingType.Assembly.Location); } /// @@ -137,7 +135,6 @@ private static string GetProviderAssemblyPath(ProviderInfo providerInfo) /// This will avoid one help file getting loaded again and again. /// (Which should not happen unless some provider is pointing /// to a help file that actually doesn't contain the help for it). - /// /// private readonly Hashtable _helpFiles = new Hashtable(); @@ -147,17 +144,17 @@ private static string GetProviderAssemblyPath(ProviderInfo providerInfo) /// /// This will load providerHelpInfo from help file into help cache. /// - /// providerInfo for which to locate help. + /// ProviderInfo for which to locate help. private void LoadHelpFile(ProviderInfo providerInfo) { if (providerInfo == null) { - throw PSTraceSource.NewArgumentNullException("providerInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInfo)); } string helpFile = providerInfo.HelpFile; - if (String.IsNullOrEmpty(helpFile) || _helpFiles.Contains(helpFile)) + if (string.IsNullOrEmpty(helpFile) || _helpFiles.Contains(helpFile)) { return; } @@ -172,7 +169,7 @@ private void LoadHelpFile(ProviderInfo providerInfo) // of the mshsnapin // Otherwise, // Look in the default search path and cmdlet assembly path - Collection searchPaths = new Collection(); + Collection searchPaths = new Collection(); if (mshSnapInInfo != null) { Diagnostics.Assert(!string.IsNullOrEmpty(mshSnapInInfo.ApplicationBase), @@ -194,7 +191,7 @@ private void LoadHelpFile(ProviderInfo providerInfo) } string location = MUIFileSearcher.LocateFile(helpFileToLoad, searchPaths); - if (String.IsNullOrEmpty(location)) + if (string.IsNullOrEmpty(location)) throw new FileNotFoundException(helpFile); XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument( @@ -212,7 +209,7 @@ private void LoadHelpFile(ProviderInfo providerInfo) for (int i = 0; i < doc.ChildNodes.Count; i++) { XmlNode node = doc.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.Name, "helpItems", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "helpItems", StringComparison.OrdinalIgnoreCase)) { helpItemsNode = node; break; @@ -230,7 +227,7 @@ private void LoadHelpFile(ProviderInfo providerInfo) for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++) { XmlNode node = helpItemsNode.ChildNodes[i]; - if (node.NodeType == XmlNodeType.Element && String.Compare(node.Name, "providerHelp", StringComparison.OrdinalIgnoreCase) == 0) + if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "providerHelp", StringComparison.OrdinalIgnoreCase)) { HelpInfo helpInfo = ProviderHelpInfo.Load(node); @@ -239,15 +236,22 @@ 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); } } @@ -259,7 +263,7 @@ private void LoadHelpFile(ProviderInfo providerInfo) /// /// Search for provider help based on a search target. /// - /// help request object + /// Help request object. /// /// If true, searches for pattern in the help content. Individual /// provider can decide which content to search in. @@ -300,7 +304,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool PSSnapinQualifiedName snapinQualifiedNameForPattern = PSSnapinQualifiedName.GetInstance(pattern); - if (null == snapinQualifiedNameForPattern) + if (snapinQualifiedNameForPattern == null) { yield break; } @@ -374,9 +378,9 @@ internal override IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, /// 1. check whether provider-specific commandlet help exists. /// 2. merge found provider-specific help with commandlet help provided. /// - /// helpInfo forwarded in - /// help request object - /// The help info object after processing + /// HelpInfo forwarded in. + /// Help request object. + /// The help info object after processing. override internal HelpInfo ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest) { if (helpInfo == null) @@ -388,7 +392,7 @@ override internal HelpInfo ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest h } string providerName = helpRequest.Provider; - if (String.IsNullOrEmpty(providerName)) + if (string.IsNullOrEmpty(providerName)) { providerName = this._sessionState.Path.CurrentLocation.Provider.Name; } diff --git a/src/System.Management.Automation/help/RemoteHelpInfo.cs b/src/System.Management.Automation/help/RemoteHelpInfo.cs index 4e581a98cb2..1aa113dd86b 100644 --- a/src/System.Management.Automation/help/RemoteHelpInfo.cs +++ b/src/System.Management.Automation/help/RemoteHelpInfo.cs @@ -1,21 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// /// Class MamlCommandHelpInfo keeps track of help information to be returned by /// command help provider. - /// /// internal class RemoteHelpInfo : BaseCommandHelpInfo { - private PSObject _deserializedRemoteHelp; + private readonly PSObject _deserializedRemoteHelp; internal RemoteHelpInfo( ExecutionContext context, @@ -35,6 +33,7 @@ internal RemoteHelpInfo( { powerShell.AddParameter("Category", remoteHelpCategory); } + powerShell.Runspace = remoteRunspace; Collection helpResults; @@ -60,6 +59,7 @@ internal RemoteHelpInfo( { nameInfo.Value = localCommandName; } + PSObject commandDetails = this.Details; if (commandDetails != null) { diff --git a/src/System.Management.Automation/help/SaveHelpCommand.cs b/src/System.Management.Automation/help/SaveHelpCommand.cs index 4329fa25b8a..5df8f974f5c 100644 --- a/src/System.Management.Automation/help/SaveHelpCommand.cs +++ b/src/System.Management.Automation/help/SaveHelpCommand.cs @@ -1,31 +1,30 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Help; -using System.IO; -using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Management.Automation; +using System.Management.Automation.Help; +using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { /// - /// This class implements the Save-Help cmdlet + /// This class implements the Save-Help cmdlet. /// [Cmdlet(VerbsData.Save, "Help", DefaultParameterSetName = SaveHelpCommand.PathParameterSetName, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=210612")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096794")] public sealed class SaveHelpCommand : UpdatableHelpCommandBase { #region Constructor /// - /// Class constructor + /// Class constructor. /// public SaveHelpCommand() : base(UpdatableHelpCommandType.SaveHelpCommand) { @@ -38,10 +37,11 @@ public SaveHelpCommand() : base(UpdatableHelpCommandType.SaveHelpCommand) #region Parameters /// - /// Specifies the paths to save updates to + /// Specifies the paths to save updates to. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = PathParameterSetName)] [ValidateNotNull] + [Alias("Path")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] DestinationPath { @@ -49,18 +49,20 @@ public string[] DestinationPath { return _path; } + set { _path = value; } } + private string[] _path; /// - /// Specifies the literal path to save updates to + /// Specifies the literal path to save updates to. /// [Parameter(Mandatory = true, ParameterSetName = LiteralPathParameterSetName)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath @@ -69,27 +71,29 @@ public string[] LiteralPath { return _path; } + set { _path = value; _isLiteralPath = true; } } + private bool _isLiteralPath = false; /// - /// Specifies the modules to update + /// Specifies the modules to update. /// [Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = PathParameterSetName)] [Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = LiteralPathParameterSetName)] [Alias("Name")] [ValidateNotNull] - [ArgumentToModuleTransformationAttribute()] + [ArgumentToModuleTransformation] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PSModuleInfo[] Module { get; set; } /// - /// Specifies the Module Specifications to update + /// Specifies the Module Specifications to update. /// [Parameter(ParameterSetName = PathParameterSetName, ValueFromPipelineByPropertyName = true)] [Parameter(ParameterSetName = LiteralPathParameterSetName, ValueFromPipelineByPropertyName = true)] @@ -99,7 +103,6 @@ public string[] LiteralPath #endregion - #region Implementation /// @@ -158,11 +161,11 @@ protected override void ProcessRecord() } /// - /// Process a single module with a given culture + /// Process a single module with a given culture. /// - /// module to process - /// culture to use - /// true if the module has been processed, false if not + /// Module to process. + /// Culture to use. + /// True if the module has been processed, false if not. internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, string culture) { Collection resolvedPaths = new Collection(); @@ -174,7 +177,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, try { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.PathNullOrEmpty)); WriteError(e.ErrorRecord); @@ -185,11 +188,11 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, if (_credential != null) { - if (path.Contains("*")) + if (path.Contains('*')) { // Deal with wildcards - int index = path.IndexOf("*", StringComparison.OrdinalIgnoreCase); + int index = path.IndexOf('*'); if (index == 0) { @@ -257,10 +260,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpInfoDrive != null) - { - helpInfoDrive.Dispose(); - } + helpInfoDrive?.Dispose(); } } @@ -269,7 +269,6 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, return true; } - bool installed = false; foreach (string path in resolvedPaths) @@ -405,10 +404,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpContentDrive != null) - { - helpContentDrive.Dispose(); - } + helpContentDrive?.Dispose(); } } } @@ -482,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/ScriptCommandHelpProvider.cs b/src/System.Management.Automation/help/ScriptCommandHelpProvider.cs index 8501b963d78..5071451962e 100644 --- a/src/System.Management.Automation/help/ScriptCommandHelpProvider.cs +++ b/src/System.Management.Automation/help/ScriptCommandHelpProvider.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -16,7 +15,7 @@ namespace System.Management.Automation internal class ScriptCommandHelpProvider : CommandHelpProvider { /// - /// Constructor for CommandHelpProvider + /// Constructor for CommandHelpProvider. /// internal ScriptCommandHelpProvider(HelpSystem helpSystem) : base(helpSystem) @@ -38,8 +37,7 @@ internal override HelpCategory HelpCategory HelpCategory.Filter | HelpCategory.Function | HelpCategory.Configuration | - HelpCategory.ScriptCommand | - HelpCategory.Workflow; + HelpCategory.ScriptCommand; } } @@ -80,4 +78,4 @@ internal override CommandSearcher GetCommandSearcherForSearch(string pattern, Ex #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/help/SyntaxHelpInfo.cs b/src/System.Management.Automation/help/SyntaxHelpInfo.cs index 4e2081dbe2d..261fdd7f849 100644 --- a/src/System.Management.Automation/help/SyntaxHelpInfo.cs +++ b/src/System.Management.Automation/help/SyntaxHelpInfo.cs @@ -1,19 +1,16 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. 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 + /// Constructor for SyntaxHelpInfo. /// private SyntaxHelpInfo(string name, string text, HelpCategory category) : base(category) @@ -24,38 +21,38 @@ private SyntaxHelpInfo(string name, string text, HelpCategory category) } /// - /// Name for the help info + /// Name for the help info. /// /// Name for the help info - internal override string Name { get; } = ""; + internal override string Name { get; } = string.Empty; /// - /// Synopsis for the help info + /// Synopsis for the help info. /// /// Synopsis for the help info - internal override string Synopsis { get; } = ""; + internal override string Synopsis { get; } = string.Empty; /// - /// Full help object for this help info + /// Full help object for this help info. /// /// Full help object for this help info internal override PSObject FullHelp { get; } /// - /// Get help info based on name, text and filename + /// Get help info based on name, text and filename. /// - /// help topic name - /// help text - /// help category - /// SyntaxHelpInfo object created based on information provided + /// Help topic name. + /// Help text. + /// Help category. + /// SyntaxHelpInfo object created based on information provided. internal static SyntaxHelpInfo GetHelpInfo(string name, string text, HelpCategory category) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) return null; SyntaxHelpInfo syntaxHelpInfo = new SyntaxHelpInfo(name, text, category); - if (String.IsNullOrEmpty(syntaxHelpInfo.Name)) + if (string.IsNullOrEmpty(syntaxHelpInfo.Name)) return null; syntaxHelpInfo.AddCommonHelpProperties(); diff --git a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs index d3cabf516e2..687faa68246 100644 --- a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs +++ b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs @@ -1,21 +1,20 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.IO; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Help; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using System.Management.Automation.Help; -using System.Net; -using System.IO; -using System.Globalization; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Tracing; +using System.Net; namespace Microsoft.PowerShell.Commands { @@ -32,12 +31,12 @@ public class UpdatableHelpCommandBase : PSCmdlet internal bool _stopping; internal int activityId; - private Dictionary _exceptions; + private readonly Dictionary _exceptions; #region Parameters /// - /// Specifies the languages to update + /// Specifies the languages to update. /// [Parameter(Position = 2)] [ValidateNotNull] @@ -55,11 +54,17 @@ public CultureInfo[] UICulture result[index] = new CultureInfo(_language[index]); } } + return result; } + set { - if (value == null) return; + if (value == null) + { + return; + } + _language = new string[value.Length]; for (int index = 0; index < value.Length; index++) { @@ -67,23 +72,25 @@ public CultureInfo[] UICulture } } } + internal string[] _language; /// - /// Gets or sets the credential parameter + /// Gets or sets the credential parameter. /// - /// - [Parameter()] - [Credential()] + [Parameter] + [Credential] public PSCredential Credential { get { return _credential; } + set { _credential = value; } } + internal PSCredential _credential; /// - /// Directs System.Net.WebClient whether or not to use default credentials + /// Directs System.Net.WebClient whether or not to use default credentials. /// [Parameter] public SwitchParameter UseDefaultCredentials @@ -92,15 +99,17 @@ public SwitchParameter UseDefaultCredentials { return _useDefaultCredentials; } + set { _useDefaultCredentials = value; } } - internal bool _useDefaultCredentials = false; + + private bool _useDefaultCredentials = false; /// - /// Forces the operation to complete + /// Forces the operation to complete. /// [Parameter] public SwitchParameter Force @@ -109,22 +118,34 @@ public SwitchParameter Force { return _force; } + set { _force = value; } } + internal bool _force; + /// + /// Sets the scope to which help is saved. + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public UpdateHelpScope Scope + { + get; + set; + } + #endregion #region Events /// - /// Handles help system progress events + /// Handles help system progress events. /// - /// event sender - /// event arguments + /// Event sender. + /// Event arguments. private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs e) { Debug.Assert(e.CommandType == UpdatableHelpCommandType.UpdateHelpCommand @@ -144,55 +165,52 @@ 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://go.microsoft.com/fwlink/?linkid=855954"); - s_metadataCache.Add("Microsoft.PowerShell.Core", "https://go.microsoft.com/fwlink/?linkid=855953"); - s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://go.microsoft.com/fwlink/?linkid=855960"); - s_metadataCache.Add("Microsoft.PowerShell.Host", "https://go.microsoft.com/fwlink/?linkid=855956"); - s_metadataCache.Add("Microsoft.PowerShell.Management", "https://go.microsoft.com/fwlink/?linkid=855958"); - s_metadataCache.Add("Microsoft.PowerShell.Security", "https://go.microsoft.com/fwlink/?linkid=855959"); - s_metadataCache.Add("Microsoft.WSMan.Management", "https://go.microsoft.com/fwlink/?linkid=855961"); + 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"); } /// /// Checks if a module is a system module, a module is a system module /// if it exists in the metadata cache. /// - /// module name - /// true if system module, false if not + /// Module name. + /// True if system module, false if not. internal static bool IsSystemModule(string module) { return s_metadataCache.ContainsKey(module); } /// - /// Class constructor + /// Class constructor. /// - /// command type + /// Command type. internal UpdatableHelpCommandBase(UpdatableHelpCommandType commandType) { _commandType = commandType; _helpSystem = new UpdatableHelpSystem(this, _useDefaultCredentials); _exceptions = new Dictionary(); - _helpSystem.OnProgressChanged += new EventHandler(HandleProgressChanged); - - Random rand = new Random(); + _helpSystem.OnProgressChanged += HandleProgressChanged; - activityId = rand.Next(); + activityId = Random.Shared.Next(); } #endregion @@ -219,7 +237,7 @@ private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext con return; } - if (String.IsNullOrEmpty(module.HelpInfoUri)) + if (string.IsNullOrEmpty(module.HelpInfoUri)) { if (!noErrors) { @@ -227,8 +245,10 @@ private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext con "HelpInfoUriNotFound", StringUtil.Format(HelpDisplayStrings.HelpInfoUriNotFound), ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null)); } + return; } + if (!(module.HelpInfoUri.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || module.HelpInfoUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { if (!noErrors) @@ -237,6 +257,7 @@ private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext con "InvalidHelpInfoUriFormat", StringUtil.Format(HelpDisplayStrings.InvalidHelpInfoUriFormat, module.HelpInfoUri), ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null)); } + return; } @@ -248,13 +269,13 @@ private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext con } /// - /// Gets a list of modules from the given pattern + /// Gets a list of modules from the given pattern. /// - /// execution context - /// pattern to search - /// Module Specification - /// do not generate errors for modules without HelpInfoUri - /// a list of modules + /// Execution context. + /// Pattern to search. + /// Module Specification. + /// Do not generate errors for modules without HelpInfoUri. + /// A list of modules. private Dictionary, UpdatableHelpModuleInfo> GetModuleInfo(ExecutionContext context, string pattern, ModuleSpecification fullyQualifiedName, bool noErrors) { List modules = null; @@ -280,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) { @@ -295,7 +316,7 @@ private Dictionary, UpdatableHelpModuleInfo> GetModuleInf if (!helpModules.ContainsKey(keyTuple)) { List availableModules = Utils.GetModules(name.Key, context); - if (null != availableModules) + if (availableModules != null) { foreach (PSModuleInfo module in availableModules) { @@ -329,7 +350,7 @@ private Dictionary, UpdatableHelpModuleInfo> GetModuleInf } /// - /// Handles Ctrl+C + /// Handles Ctrl+C. /// protected override void StopProcessing() { @@ -338,7 +359,7 @@ protected override void StopProcessing() } /// - /// End processing + /// End processing. /// protected override void EndProcessing() { @@ -354,7 +375,7 @@ protected override void EndProcessing() // multiple cultures or multiple modules are involved. e = new UpdatableHelpExceptionContext(new UpdatableHelpSystemException( "HelpCultureNotSupported", StringUtil.Format(HelpDisplayStrings.CannotMatchUICulturePattern, - String.Join(", ", exception.Cultures)), + string.Join(", ", exception.Cultures)), ErrorCategory.InvalidArgument, exception.Cultures, null)); e.Modules = exception.Modules; e.Cultures = exception.Cultures; @@ -372,13 +393,13 @@ protected override void EndProcessing() } /// - /// Main cmdlet logic for processing module names or fully qualified module names + /// Main cmdlet logic for processing module names or fully qualified module names. /// - /// module names given by the user - /// fullyQualifiedNames + /// Module names given by the user. + /// FullyQualifiedNames. internal void Process(IEnumerable moduleNames, IEnumerable fullyQualifiedNames) { - _helpSystem.WebClient.UseDefaultCredentials = _useDefaultCredentials; + _helpSystem.UseDefaultCredentials = _useDefaultCredentials; if (moduleNames != null) { @@ -419,12 +440,15 @@ internal void Process(IEnumerable moduleNames, IEnumerable - /// Processing module objects for Save-Help + /// Processing module objects for Save-Help. /// - /// module objects given by the user + /// Module 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>(); @@ -440,12 +464,12 @@ internal void Process(IEnumerable modules) } /// - /// Processes a module with potential globbing + /// Processes a module with potential globbing. /// - /// module name with globbing + /// Module name with globbing. private void ProcessModuleWithGlobbing(string name) { - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.ModuleNameNullOrEmpty)); WriteError(e.ErrorRecord); @@ -459,9 +483,9 @@ private void ProcessModuleWithGlobbing(string name) } /// - /// Processes a ModuleSpecification with potential globbing + /// Processes a ModuleSpecification with potential globbing. /// - /// ModuleSpecification + /// ModuleSpecification. private void ProcessModuleWithGlobbing(ModuleSpecification fullyQualifiedName) { foreach (KeyValuePair, UpdatableHelpModuleInfo> module in GetModuleInfo(null, fullyQualifiedName, false)) @@ -471,9 +495,9 @@ private void ProcessModuleWithGlobbing(ModuleSpecification fullyQualifiedName) } /// - /// Processes a single module with multiple cultures + /// Processes a single module with multiple cultures. /// - /// module to process + /// Module to process. private void ProcessModule(UpdatableHelpModuleInfo module) { _helpSystem.CurrentModule = module.ModuleName; @@ -490,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) { @@ -530,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; @@ -539,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 { @@ -562,21 +594,27 @@ 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); + } } /// - /// Process a single module with a given culture + /// Process a single module with a given culture. /// - /// module to process - /// culture to use - /// true if the module has been processed, false if not + /// Module to process. + /// Culture to use. + /// True if the module has been processed, false if not. internal virtual bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, string culture) { return false; @@ -587,12 +625,12 @@ internal virtual bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, s #region Common methods /// - /// Gets a list of modules from the given pattern or ModuleSpecification + /// Gets a list of modules from the given pattern or ModuleSpecification. /// - /// pattern to match - /// ModuleSpecification - /// skip errors - /// a list of modules + /// Pattern to match. + /// ModuleSpecification. + /// Skip errors. + /// A list of modules. internal Dictionary, UpdatableHelpModuleInfo> GetModuleInfo(string pattern, ModuleSpecification fullyQualifiedName, bool noErrors) { Dictionary, UpdatableHelpModuleInfo> modules = GetModuleInfo(Context, pattern, fullyQualifiedName, noErrors); @@ -612,14 +650,14 @@ internal Dictionary, UpdatableHelpModuleInfo> GetModuleIn } /// - /// Checks if it is necessary to update help + /// Checks if it is necessary to update help. /// - /// ModuleInfo - /// current HelpInfo.xml - /// new HelpInfo.xml - /// current culture - /// force update - /// true if it is necessary to update help, false if not + /// ModuleInfo. + /// Current HelpInfo.xml. + /// New HelpInfo.xml. + /// Current culture. + /// Force update. + /// True if it is necessary to update help, false if not. internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInfo currentHelpInfo, UpdatableHelpInfo newHelpInfo, CultureInfo culture, bool force) { @@ -633,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, @@ -650,14 +688,14 @@ internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInf } /// - /// Checks if the user has attempted to update more than once per day per module + /// Checks if the user has attempted to update more than once per day per module. /// - /// module name - /// path to help info - /// help info file name - /// current time (UTC) - /// if -Force is specified - /// true if we are okay to update, false if not + /// Module name. + /// Path to help info. + /// Help info file name. + /// Current time (UTC). + /// If -Force is specified. + /// True if we are okay to update, false if not. internal bool CheckOncePerDayPerModule(string moduleName, string path, string filename, DateTime time, bool force) { // Update if -Force is specified @@ -695,12 +733,12 @@ internal bool CheckOncePerDayPerModule(string moduleName, string path, string fi } /// - /// Resolves a given path to a list of directories + /// Resolves a given path to a list of directories. /// - /// path to resolve - /// resolve recursively? + /// Path to resolve. + /// Resolve recursively? /// Treat the path / start path as a literal path?/// - /// a list of directories + /// A list of directories. internal IEnumerable ResolvePath(string path, bool recurse, bool isLiteralPath) { List resolvedPaths = new List(); @@ -755,17 +793,17 @@ internal IEnumerable ResolvePath(string path, bool recurse, bool isLiter } /// - /// Resolves a given path to a list of directories recursively + /// Resolves a given path to a list of directories recursively. /// - /// path to resolve - /// a list of directories - private IEnumerable RecursiveResolvePathHelper(string path) + /// Path to resolve. + /// A list of directories. + 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)) { @@ -784,7 +822,7 @@ private IEnumerable RecursiveResolvePathHelper(string path) /// /// Validates the provider of the path, only FileSystem provider is accepted. /// - /// path to validate + /// Path to validate. internal void ValidatePathProvider(PathInfo path) { if (path.Provider == null || path.Provider.Name != FileSystemProvider.ProviderName) @@ -799,17 +837,13 @@ internal void ValidatePathProvider(PathInfo path) #region Logging /// - /// Logs a command message + /// Logs a command message. /// - /// message to log + /// Message to log. internal void LogMessage(string message) { - List details = new List(); - - details.Add(message); -#if !CORECLR // TODO:CORECLR Uncomment when we add PSEtwLog support + List details = new List() { message }; PSEtwLog.LogPipelineExecutionDetailEvent(MshLog.GetLogContext(Context, Context.CurrentCommandProcessor.Command.MyInvocation), details); -#endif } #endregion @@ -817,11 +851,11 @@ internal void LogMessage(string message) #region Exception processing /// - /// Processes an exception for help cmdlets + /// Processes an exception for help cmdlets. /// - /// module name - /// culture info - /// exception to check + /// Module name. + /// Culture info. + /// Exception to check. internal void ProcessException(string moduleName, string culture, Exception e) { UpdatableHelpSystemException except = null; @@ -863,4 +897,20 @@ internal void ProcessException(string moduleName, string culture, Exception e) #endregion } + + /// + /// Scope to which the help should be saved. + /// + public enum UpdateHelpScope + { + /// + /// Save the help content to the user directory. + /// + CurrentUser, + + /// + /// Save the help content to the module directory. This is the default behavior. + /// + AllUsers + } } diff --git a/src/System.Management.Automation/help/UpdatableHelpInfo.cs b/src/System.Management.Automation/help/UpdatableHelpInfo.cs index 467de5a53f1..8170de90654 100644 --- a/src/System.Management.Automation/help/UpdatableHelpInfo.cs +++ b/src/System.Management.Automation/help/UpdatableHelpInfo.cs @@ -1,25 +1,26 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Management.Automation.Internal; -using System.Globalization; +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; -using System.Collections.ObjectModel; namespace System.Management.Automation.Help { /// - /// Represents each supported culture + /// Represents each supported culture. /// internal class CultureSpecificUpdatableHelp { /// - /// Class constructor + /// Class constructor. /// - /// culture info - /// version info + /// Culture info. + /// Version info. internal CultureSpecificUpdatableHelp(CultureInfo culture, Version version) { Debug.Assert(version != null); @@ -30,26 +31,64 @@ internal CultureSpecificUpdatableHelp(CultureInfo culture, Version version) } /// - /// Culture version + /// Culture version. /// internal Version Version { get; set; } /// - /// Supported culture + /// 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); + } } /// - /// This class represents the HelpInfo metadata XML + /// This class represents the HelpInfo metadata XML. /// internal class UpdatableHelpInfo { /// - /// Class constructor + /// Class constructor. /// - /// unresolved help content URI - /// supported UI cultures + /// Unresolved help content URI. + /// Supported UI cultures. internal UpdatableHelpInfo(string unresolvedUri, CultureSpecificUpdatableHelp[] cultures) { Debug.Assert(cultures != null); @@ -60,26 +99,26 @@ internal UpdatableHelpInfo(string unresolvedUri, CultureSpecificUpdatableHelp[] } /// - /// Unresolved URI + /// Unresolved URI. /// internal string UnresolvedUri { get; } /// - /// Link to the actual help content + /// Link to the actual help content. /// internal Collection HelpContentUriCollection { get; } /// - /// Supported UI cultures + /// Supported UI cultures. /// internal CultureSpecificUpdatableHelp[] UpdatableHelpItems { get; } /// - /// Checks if the other HelpInfo has a newer version + /// Checks if the other HelpInfo has a newer version. /// - /// HelpInfo object to check - /// culture to check - /// true if the other HelpInfo is newer, false if not + /// HelpInfo object to check. + /// Culture to check. + /// True if the other HelpInfo is newer, false if not. internal bool IsNewerVersion(UpdatableHelpInfo helpInfo, CultureInfo culture) { Debug.Assert(helpInfo != null); @@ -94,34 +133,24 @@ internal bool IsNewerVersion(UpdatableHelpInfo helpInfo, CultureInfo culture) return true; } - return v1 > v2; ; + return v1 > v2; } /// - /// Checks if a culture is supported + /// Checks if a culture is supported. /// - /// culture to check - /// true if supported, false if not - internal bool IsCultureSupported(CultureInfo culture) + /// Name of the culture to check. + /// True if supported, false if not. + internal bool IsCultureSupported(string cultureName) { - Debug.Assert(culture != null); - - foreach (CultureSpecificUpdatableHelp updatableHelpItem in UpdatableHelpItems) - { - if (String.Compare(updatableHelpItem.Culture.Name, culture.Name, - StringComparison.OrdinalIgnoreCase) == 0) - { - return true; - } - } - - return false; + Debug.Assert(cultureName != null, $"{nameof(cultureName)} may not be null"); + return UpdatableHelpItems.Any(item => item.IsCultureSupported(cultureName)); } /// - /// Gets a string representation of the supported cultures + /// Gets a string representation of the supported cultures. /// - /// supported cultures in string + /// Supported cultures in string. internal string GetSupportedCultures() { if (UpdatableHelpItems.Length == 0) @@ -145,16 +174,16 @@ internal string GetSupportedCultures() } /// - /// Gets the culture version + /// Gets the culture version. /// - /// culture info - /// culture version + /// Culture info. + /// Culture version. internal Version GetCultureVersion(CultureInfo culture) { foreach (CultureSpecificUpdatableHelp updatableHelpItem in UpdatableHelpItems) { - if (String.Compare(updatableHelpItem.Culture.Name, culture.Name, - StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(updatableHelpItem.Culture.Name, culture.Name, + StringComparison.OrdinalIgnoreCase)) { return updatableHelpItem.Version; } diff --git a/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs b/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs index c525ed0cf51..ccf0b95f0f6 100644 --- a/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs +++ b/src/System.Management.Automation/help/UpdatableHelpModuleInfo.cs @@ -1,14 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Globalization; using System.Diagnostics; +using System.Globalization; namespace System.Management.Automation.Help { /// - /// Updatable help system internal representation of the PSModuleInfo class + /// Updatable help system internal representation of the PSModuleInfo class. /// internal class UpdatableHelpModuleInfo { @@ -20,18 +19,17 @@ internal class UpdatableHelpModuleInfo internal static readonly string HelpIntoXmlName = "HelpInfo.xml"; /// - /// Class constructor + /// Class constructor. /// - /// module name - /// module GUID - /// module path - /// HelpInfo URI + /// Module name. + /// Module GUID. + /// Module path. + /// HelpInfo URI. 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)); + Debug.Assert(!string.IsNullOrEmpty(name)); + Debug.Assert(!string.IsNullOrEmpty(path)); + Debug.Assert(!string.IsNullOrEmpty(uri)); ModuleName = name; _moduleGuid = guid; @@ -40,12 +38,12 @@ internal UpdatableHelpModuleInfo(string name, Guid guid, string path, string uri } /// - /// Module name + /// Module name. /// internal string ModuleName { get; } /// - /// Module GUID + /// Module GUID. /// internal Guid ModuleGuid { @@ -54,23 +52,24 @@ internal Guid ModuleGuid return _moduleGuid; } } - private Guid _moduleGuid; + + private readonly Guid _moduleGuid; /// - /// Module path + /// Module path. /// internal string ModuleBase { get; } /// - /// HelpInfo URI + /// HelpInfo URI. /// internal string HelpInfoUri { get; } /// - /// Gets the combined HelpContent.zip name + /// Gets the combined HelpContent.zip name. /// - /// current culture - /// HelpContent name + /// Current culture. + /// HelpContent name. internal string GetHelpContentName(CultureInfo culture) { Debug.Assert(culture != null); @@ -79,9 +78,9 @@ internal string GetHelpContentName(CultureInfo culture) } /// - /// Gets the combined HelpInfo.xml name + /// Gets the combined HelpInfo.xml name. /// - /// HelpInfo name + /// HelpInfo name. internal string GetHelpInfoName() { return ModuleName + "_" + _moduleGuid.ToString() + "_" + HelpIntoXmlName; diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index cadd34fa5e6..14edabf9613 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -1,47 +1,43 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Globalization; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Net; using System.ComponentModel; -using System.Management.Automation.Configuration; -using System.Management.Automation.Internal; using System.Diagnostics; -using System.Threading; +using System.Globalization; using System.IO; -using System.Xml; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Text; -using Microsoft.PowerShell.Commands; -using Microsoft.Win32; -using System.Security; -#if CORECLR using System.IO.Compression; +using System.Management.Automation.Configuration; +using System.Management.Automation.Internal; +using System.Net; using System.Net.Http; +using System.Runtime.Serialization; +using System.Security; +using System.Text; +using System.Threading; using System.Threading.Tasks; -#else +using System.Xml; using System.Xml.Schema; -#endif + +using Microsoft.PowerShell.Commands; +using Microsoft.Win32; namespace System.Management.Automation.Help { /// - /// Updatable help system exception + /// Updatable help system exception. /// - [Serializable] internal class UpdatableHelpSystemException : Exception { /// - /// Class constructor + /// Class constructor. /// - /// FullyQualifiedErrorId - /// exception message - /// category - /// target object - /// inner exception + /// FullyQualifiedErrorId. + /// Exception message. + /// Category. + /// Target object. + /// Inner exception. internal UpdatableHelpSystemException(string errorId, string message, ErrorCategory cat, object targetObject, Exception innerException) : base(message, innerException) { @@ -52,10 +48,10 @@ internal UpdatableHelpSystemException(string errorId, string message, ErrorCateg #if !CORECLR /// - /// Class constructor + /// Class constructor. /// - /// serialization info - /// streaming context + /// Serialization info. + /// Streaming context. protected UpdatableHelpSystemException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { @@ -63,30 +59,30 @@ protected UpdatableHelpSystemException(SerializationInfo serializationInfo, Stre #endif /// - /// Fully qualified error id + /// Fully qualified error id. /// internal string FullyQualifiedErrorId { get; } /// - /// Error category + /// Error category. /// internal ErrorCategory ErrorCategory { get; } /// - /// Target object + /// Target object. /// internal object TargetObject { get; } } /// - /// Exception context + /// Exception context. /// internal class UpdatableHelpExceptionContext { /// - /// Class constructor + /// Class constructor. /// - /// exception to wrap + /// Exception to wrap. internal UpdatableHelpExceptionContext(UpdatableHelpSystemException exception) { Exception = exception; @@ -95,25 +91,25 @@ internal UpdatableHelpExceptionContext(UpdatableHelpSystemException exception) } /// - /// A list of modules + /// A list of modules. /// internal HashSet Modules { get; set; } /// - /// A list of UI cultures + /// A list of UI cultures. /// internal HashSet Cultures { get; set; } /// - /// Gets the help system exception + /// Gets the help system exception. /// internal UpdatableHelpSystemException Exception { get; } /// - /// Creates an error record from this context + /// Creates an error record from this context. /// - /// command type - /// error record + /// Command type. + /// Error record. internal ErrorRecord CreateErrorRecord(UpdatableHelpCommandType commandType) { Debug.Assert(Modules.Count != 0); @@ -123,17 +119,17 @@ internal ErrorRecord CreateErrorRecord(UpdatableHelpCommandType commandType) } /// - /// Gets the exception message + /// Gets the exception message. /// /// /// internal string GetExceptionMessage(UpdatableHelpCommandType commandType) { - string message = ""; + string message = string.Empty; SortedSet sortedModules = new SortedSet(Modules, StringComparer.CurrentCultureIgnoreCase); SortedSet sortedCultures = new SortedSet(Cultures, StringComparer.CurrentCultureIgnoreCase); - string modules = String.Join(", ", sortedModules); - string cultures = String.Join(", ", sortedCultures); + string modules = string.Join(", ", sortedModules); + string cultures = string.Join(", ", sortedCultures); if (commandType == UpdatableHelpCommandType.UpdateHelpCommand) { @@ -163,7 +159,7 @@ internal string GetExceptionMessage(UpdatableHelpCommandType commandType) } /// - /// Enumeration showing Update or Save help + /// Enumeration showing Update or Save help. /// internal enum UpdatableHelpCommandType { @@ -173,19 +169,19 @@ internal enum UpdatableHelpCommandType } /// - /// Progress event arguments + /// Progress event arguments. /// internal class UpdatableHelpProgressEventArgs : EventArgs { /// - /// Class constructor + /// Class constructor. /// - /// module name - /// progress status - /// progress percentage + /// Module name. + /// Progress status. + /// Progress percentage. internal UpdatableHelpProgressEventArgs(string moduleName, string status, int percent) { - Debug.Assert(!String.IsNullOrEmpty(status)); + Debug.Assert(!string.IsNullOrEmpty(status)); CommandType = UpdatableHelpCommandType.UnknownCommand; ProgressStatus = status; @@ -194,15 +190,15 @@ internal UpdatableHelpProgressEventArgs(string moduleName, string status, int pe } /// - /// Class constructor + /// Class constructor. /// - /// module name - /// command type - /// progress status - /// progress percentage + /// Module name. + /// Command type. + /// Progress status. + /// Progress percentage. internal UpdatableHelpProgressEventArgs(string moduleName, UpdatableHelpCommandType type, string status, int percent) { - Debug.Assert(!String.IsNullOrEmpty(status)); + Debug.Assert(!string.IsNullOrEmpty(status)); CommandType = type; ProgressStatus = status; @@ -211,60 +207,51 @@ internal UpdatableHelpProgressEventArgs(string moduleName, UpdatableHelpCommandT } /// - /// Progress status + /// Progress status. /// internal string ProgressStatus { get; } /// - /// Progress percentage + /// Progress percentage. /// internal int ProgressPercent { get; } - /// - /// Module name + /// Module name. /// internal string ModuleName { get; } /// - /// Command type + /// Command type. /// internal UpdatableHelpCommandType CommandType { get; set; } } /// - /// This class implements the Updatable Help System common operations + /// This class implements the Updatable Help System common operations. /// internal class UpdatableHelpSystem : IDisposable { -#if CORECLR - private TimeSpan _defaultTimeout; -#else - private AutoResetEvent _completionEvent; - private bool _completed; -#endif - 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 HttpClient HttpClient { get; } - internal WebClient WebClient { get; } + internal bool UseDefaultCredentials; internal string CurrentModule { get; set; } /// - /// Class constructor + /// Class constructor. /// internal UpdatableHelpSystem(UpdatableHelpCommandBase cmdlet, bool useDefaultCredentials) { - WebClient = new WebClient(); -#if CORECLR + HttpClient = new HttpClient(); _defaultTimeout = new TimeSpan(0, 0, 30); -#else - _completionEvent = new AutoResetEvent(false); - _completed = false; -#endif _progressEvents = new Collection(); Errors = new Collection(); _stopping = false; @@ -272,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); @@ -281,7 +268,7 @@ internal UpdatableHelpSystem(UpdatableHelpCommandBase cmdlet, bool useDefaultCre } /// - /// Disposes the help system + /// Disposes the help system. /// public void Dispose() { @@ -289,60 +276,54 @@ public void Dispose() _completionEvent.Dispose(); #endif _cancelTokenSource.Dispose(); - WebClient.Dispose(); + HttpClient.Dispose(); GC.SuppressFinalize(this); } /// - /// Help system errors + /// Help system errors. /// internal Collection Errors { get; } /// /// Gets the current UIculture (includes the fallback chain) /// - /// a list of cultures + /// A list of cultures. 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 /// - /// Gets an internal help URI + /// Gets an internal help URI. /// - /// internal module information - /// help content culture - /// internal help uri representation + /// Internal module information. + /// Help content culture. + /// Internal help uri representation. internal UpdatableHelpUri GetHelpInfoUri(UpdatableHelpModuleInfo module, CultureInfo culture) { return new UpdatableHelpUri(module.ModuleName, module.ModuleGuid, culture, ResolveUri(module.HelpInfoUri, false)); } /// - /// Gets the HelpInfo xml from the given URI + /// Gets the HelpInfo xml from the given URI. /// - /// command type - /// HelpInfo URI - /// module name - /// module GUID - /// current UI culture - /// HelpInfo object + /// Command type. + /// HelpInfo URI. + /// Module name. + /// Module GUID. + /// Current UI culture. + /// HelpInfo object. internal UpdatableHelpInfo GetHelpInfo(UpdatableHelpCommandType commandType, string uri, string moduleName, Guid moduleGuid, string culture) { try @@ -350,11 +331,10 @@ internal UpdatableHelpInfo GetHelpInfo(UpdatableHelpCommandType commandType, str OnProgressChanged(this, new UpdatableHelpProgressEventArgs(CurrentModule, commandType, StringUtil.Format( HelpDisplayStrings.UpdateProgressLocating), 0)); -#if CORECLR string xml; using (HttpClientHandler handler = new HttpClientHandler()) { - handler.UseDefaultCredentials = WebClient.UseDefaultCredentials; + handler.UseDefaultCredentials = UseDefaultCredentials; using (HttpClient client = new HttpClient(handler)) { client.Timeout = _defaultTimeout; @@ -366,9 +346,7 @@ internal UpdatableHelpInfo GetHelpInfo(UpdatableHelpCommandType commandType, str } } } -#else - string xml = WebClient.DownloadString(uri); -#endif + UpdatableHelpInfo helpInfo = CreateHelpInfo(xml, moduleName, moduleGuid, currentCulture: culture, pathOverride: null, verbose: true, shouldResolveUri: true, ignoreValidationException: false); @@ -391,12 +369,12 @@ internal UpdatableHelpInfo GetHelpInfo(UpdatableHelpCommandType commandType, str /// /// Sends a standard HTTP request to get the resolved URI (potential FwLinks) /// - /// base URI + /// Base URI. /// - /// resolved URI + /// Resolved URI. private string ResolveUri(string baseUri, bool verbose) { - Debug.Assert(!String.IsNullOrEmpty(baseUri)); + Debug.Assert(!string.IsNullOrEmpty(baseUri)); // Directory.Exists checks if baseUri is a network drive or // a local directory. If baseUri is local, we don't need to resolve it. @@ -407,7 +385,7 @@ private string ResolveUri(string baseUri, bool verbose) // Like if you send a request to www.technet.com/powershell you will get // a 301/203 response with the response URI set to www.technet.com/powershell/ // - if (Directory.Exists(baseUri) || baseUri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (Directory.Exists(baseUri) || baseUri.EndsWith('/')) { if (verbose) { @@ -434,14 +412,14 @@ private string ResolveUri(string baseUri, bool verbose) return uri; } -#if CORECLR 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) { @@ -469,14 +447,14 @@ private string ResolveUri(string baseUri, bool verbose) _cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.URIRedirectWarningToHost, uri)); } - if (uri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (uri.EndsWith('/')) { return uri; } } else if (response.StatusCode == HttpStatusCode.OK) { - if (uri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (uri.EndsWith('/')) { return uri; } @@ -489,64 +467,6 @@ private string ResolveUri(string baseUri, bool verbose) } } } -#else - HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); - - request.AllowAutoRedirect = false; - request.Timeout = 30000; - - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - - WebHeaderCollection headers = response.Headers; - - try - { - if (response.StatusCode == HttpStatusCode.Found || - response.StatusCode == HttpStatusCode.Redirect || - response.StatusCode == HttpStatusCode.Moved || - response.StatusCode == HttpStatusCode.MovedPermanently) - { - Uri responseUri = new Uri(headers["Location"], UriKind.RelativeOrAbsolute); - - if (responseUri.IsAbsoluteUri) - { - uri = responseUri.ToString(); - } - else - { - uri = uri.Replace(request.Address.AbsolutePath, responseUri.ToString()); - } - - uri = uri.Trim(); - - if (verbose) - { - _cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.URIRedirectWarningToHost, uri)); - } - - if (uri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) - { - return uri; - } - } - else if (response.StatusCode == HttpStatusCode.OK) - { - if (uri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) - { - return uri; - } - else - { - throw new UpdatableHelpSystemException("InvalidHelpInfoUri", StringUtil.Format(HelpDisplayStrings.InvalidHelpInfoUri, uri), - ErrorCategory.InvalidOperation, null, null); - } - } - } - finally - { - response.Close(); - } -#endif } } catch (UriFormatException e) @@ -559,7 +479,7 @@ private string ResolveUri(string baseUri, bool verbose) } /// - /// HelpInfo.xml schema + /// HelpInfo.xml schema. /// private const string HelpInfoXmlSchema = @" "; + 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 + /// Creates a HelpInfo object. /// - /// XML text - /// module name - /// module GUID - /// current UI cultures - /// overrides the path contained within HelpInfo.xml + /// XML text. + /// Module name. + /// Module GUID. + /// Current UI cultures. + /// Overrides the path contained within HelpInfo.xml. /// /// /// Resolve the uri retrieved from the content. The uri is resolved /// to handle redirections if any. /// - /// ignore the xsd validation exception and return null in such case - /// HelpInfo object + /// Ignore the xsd validation exception and return null in such case. + /// HelpInfo object. internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid moduleGuid, string currentCulture, string pathOverride, bool verbose, bool shouldResolveUri, bool ignoreValidationException) { @@ -611,9 +535,7 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo try { document = CreateValidXmlDocument(xml, HelpInfoXmlNamespace, HelpInfoXmlSchema, -#if !CORECLR new ValidationEventHandler(HelpInfoValidationHandler), -#endif true); } catch (UpdatableHelpSystemException e) @@ -627,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); @@ -636,7 +561,7 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo string uri = pathOverride; string unresolvedUri = document["HelpInfo"]["HelpContentURI"].InnerText; - if (String.IsNullOrEmpty(pathOverride)) + if (string.IsNullOrEmpty(pathOverride)) { if (shouldResolveUri) { @@ -661,22 +586,18 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo UpdatableHelpInfo helpInfo = new UpdatableHelpInfo(unresolvedUri, updatableHelpItem); - if (!String.IsNullOrEmpty(currentCulture)) + 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)); } } } - - if (!String.IsNullOrEmpty(currentCulture) && helpInfo.HelpContentUriCollection.Count == 0) + if (!string.IsNullOrEmpty(currentCulture) && helpInfo.HelpContentUriCollection.Count == 0) { // throw exception throw new UpdatableHelpSystemException("HelpCultureNotSupported", @@ -687,55 +608,15 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo return helpInfo; } -#if CORECLR - /// - /// Creates a valid xml document + /// Creates a valid xml document. /// - /// input xml - /// schema namespace - /// xml schema + /// Input xml. + /// Schema namespace. + /// Xml schema. + /// Validation event handler. /// HelpInfo or HelpContent? - private XmlDocument CreateValidXmlDocument(string xml, string ns, string schema, bool helpInfo) - { - XmlReaderSettings settings = new XmlReaderSettings(); - - XmlReader reader = XmlReader.Create(new StringReader(xml), settings); - XmlDocument document = new XmlDocument(); - - try - { - document.Load(reader); - } - catch (XmlException e) - { - if (helpInfo) - { - throw new UpdatableHelpSystemException(HelpInfoXmlValidationFailure, - StringUtil.Format(HelpDisplayStrings.HelpInfoXmlValidationFailure, e.Message), - ErrorCategory.InvalidData, null, e); - } - else - { - throw new UpdatableHelpSystemException("HelpContentXmlValidationFailure", - StringUtil.Format(HelpDisplayStrings.HelpContentXmlValidationFailure, e.Message), - ErrorCategory.InvalidData, null, e); - } - } - return document; - } - -#else - - /// - /// Creates a valid xml document - /// - /// input xml - /// schema namespace - /// 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(); @@ -766,14 +647,15 @@ private XmlDocument CreateValidXmlDocument(string xml, string ns, string schema, ErrorCategory.InvalidData, null, e); } } + return document; } /// - /// Handles HelpInfo XML validation events + /// Handles HelpInfo XML validation events. /// - /// event sender - /// event arguments + /// Event sender. + /// Event arguments. private void HelpInfoValidationHandler(object sender, ValidationEventArgs arg) { switch (arg.Severity) @@ -790,10 +672,10 @@ private void HelpInfoValidationHandler(object sender, ValidationEventArgs arg) } /// - /// Handles Help content MAML validation events + /// Handles Help content MAML validation events. /// - /// event sender - /// event arguments + /// Event sender. + /// Event arguments. private void HelpContentValidationHandler(object sender, ValidationEventArgs arg) { switch (arg.Severity) @@ -809,42 +691,31 @@ private void HelpContentValidationHandler(object sender, ValidationEventArgs arg } } -#endif - #endregion #region Help Content Retrieval /// - /// Cancels all asynchronous download operations + /// Cancels all asynchronous download operations. /// internal void CancelDownload() { -#if CORECLR _cancelTokenSource.Cancel(); -#else - if (WebClient.IsBusy) - { - WebClient.CancelAsync(); - _completed = true; - _completionEvent.Set(); - } -#endif _stopping = true; } /// - /// Downloads and installs help content + /// Downloads and installs help content. /// - /// command type - /// execution context - /// destination paths - /// file names - /// culture to update - /// help content uri - /// path of the maml XSDs - /// files installed - /// true if the operation succeeded, false if not + /// Command type. + /// Execution context. + /// Destination paths. + /// File names. + /// Culture to update. + /// Help content uri. + /// Path of the maml XSDs. + /// Files installed. + /// True if the operation succeeded, false if not. internal bool DownloadAndInstallHelpContent(UpdatableHelpCommandType commandType, ExecutionContext context, Collection destPaths, string fileName, CultureInfo culture, string helpContentUri, string xsdPath, out Collection installed) { @@ -868,14 +739,14 @@ internal bool DownloadAndInstallHelpContent(UpdatableHelpCommandType commandType } /// - /// Downloads the help content + /// Downloads the help content. /// - /// command type - /// destination path - /// help content uri - /// combined file name - /// culture name - /// true if the operation succeeded, false if not + /// Command type. + /// Destination path. + /// Help content uri. + /// Combined file name. + /// Culture name. + /// True if the operation succeeded, false if not. internal bool DownloadHelpContent(UpdatableHelpCommandType commandType, string path, string helpContentUri, string fileName, string culture) { if (_stopping) @@ -893,17 +764,11 @@ internal bool DownloadHelpContent(UpdatableHelpCommandType commandType, string p string uri = helpContentUri + fileName; -#if CORECLR return DownloadHelpContentHttpClient(uri, Path.Combine(path, fileName), commandType); -#else - return DownloadHelpContentWebClient(uri, Path.Combine(path, fileName), culture, commandType); -#endif } -#if CORECLR /// - /// Downloads the help content and saves it to a directory. The goal is to achieve - /// functional parity with WebClient.DownloadFileAsync() using CoreCLR-compatible APIs. + /// Downloads the help content and saves it to a directory. /// /// /// @@ -915,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? @@ -959,9 +825,11 @@ private bool DownloadHelpContentHttpClient(string uri, string fileName, Updatabl } } } + SendProgressEvents(commandType); } } + return (Errors.Count == 0); } @@ -984,34 +852,6 @@ private void WriteResponseToFile(HttpResponseMessage response, string fileName) } } -#else - - /// - /// Downloads help content and saves it in the specified file. - /// - /// - /// - /// - /// - /// - private bool DownloadHelpContentWebClient(string uri, string fileName, string culture, UpdatableHelpCommandType commandType) - { - WebClient.DownloadFileAsync(new Uri(uri), fileName, culture); - - OnProgressChanged(this, new UpdatableHelpProgressEventArgs(CurrentModule, commandType, StringUtil.Format( - HelpDisplayStrings.UpdateProgressConnecting), 100)); - - while (!_completed || WebClient.IsBusy) - { - _completionEvent.WaitOne(); - - SendProgressEvents(commandType); - } - - return (Errors.Count == 0); - } -#endif - private void SendProgressEvents(UpdatableHelpCommandType commandType) { // Send progress events @@ -1032,16 +872,16 @@ private void SendProgressEvents(UpdatableHelpCommandType commandType) } /// - /// Installs HelpInfo.xml + /// Installs HelpInfo.xml. /// /// /// - /// culture updated - /// version updated - /// help content uri - /// destination name - /// combined file name - /// forces the file to copy + /// Culture updated. + /// Version updated. + /// Help content uri. + /// Destination name. + /// Combined file name. + /// Forces the file to copy. internal void GenerateHelpInfo(string moduleName, Guid moduleGuid, string contentUri, string culture, Version version, string destPath, string fileName, bool force) { Debug.Assert(Directory.Exists(destPath)); @@ -1151,10 +991,10 @@ internal void GenerateHelpInfo(string moduleName, Guid moduleGuid, string conten } /// - /// Removes the read only attribute + /// Removes the read only attribute. /// /// - private void RemoveReadOnly(string path) + private static void RemoveReadOnly(string path) { if (File.Exists(path)) { @@ -1162,24 +1002,24 @@ private void RemoveReadOnly(string path) if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { - attributes = (attributes & ~FileAttributes.ReadOnly); + attributes &= ~FileAttributes.ReadOnly; File.SetAttributes(path, attributes); } } } /// - /// Installs (unzips) the help content + /// Installs (unzips) the help content. /// - /// command type - /// execution context - /// source directory - /// destination paths - /// help content file name - /// temporary path - /// current culture - /// path of the maml XSDs - /// files installed + /// Command type. + /// Execution context. + /// Source directory. + /// Destination paths. + /// Help content file name. + /// Temporary path. + /// Current culture. + /// Path of the maml XSDs. + /// Files installed. /// /// Directory pointed by (if any) will be deleted. /// @@ -1249,21 +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 (FileStream archiveFileStream = new FileStream(source, IO.FileMode.Open, FileAccess.Read)) - using (ZipArchive zipArchive = new ZipArchive(archiveFileStream, ZipArchiveMode.Read, false)) + using (ZipArchive zipArchive = ZipFile.Open(source, ZipArchiveMode.Read)) { - foreach (ZipArchiveEntry entry in zipArchive.Entries) - { - string extractPath = Path.Combine(destination, entry.FullName); - entry.ExtractToFile(extractPath); - } - sucessfulDecompression = true; + zipArchive.ExtractToDirectory(destination); + successfulDecompression = true; } } catch (ArgumentException) { } @@ -1275,18 +1110,18 @@ private bool ExpandArchive(string source, string destination) catch (UnauthorizedAccessException) { } catch (ObjectDisposedException) { } - return sucessfulDecompression; + return successfulDecompression; } #endif /// - /// Unzips to help content to a given location + /// Unzips to help content to a given location. /// - /// execution context - /// 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) + /// Execution context. + /// 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 static void UnzipHelpContent(ExecutionContext context, string srcPath, string destPath, out bool needToCopy) { needToCopy = true; @@ -1296,24 +1131,24 @@ 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("\\", StringComparison.Ordinal)) + if (!sourceDirectory.EndsWith('\\')) { sourceDirectory += "\\"; } - if (!destPath.EndsWith("\\", StringComparison.Ordinal)) + if (!destPath.EndsWith('\\')) { 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); @@ -1380,29 +1215,25 @@ private void UnzipHelpContent(ExecutionContext context, string srcPath, string d } /// - /// Validates all XML files within a given path + /// Validates all XML files within a given path. /// - /// path containing files to validate - /// destination paths - /// culture name - /// path of the maml XSDs - /// installed files + /// Path containing files to validate. + /// Destination paths. + /// Culture name. + /// Path of the maml XSDs. + /// Installed files. private void ValidateAndCopyHelpContent(string sourcePath, Collection destPaths, string culture, string xsdPath, out Collection installed) { installed = new Collection(); -#if CORECLR // TODO:CORECLR Disabling this because XML Schemas are not supported for CoreCLR - string xsd = "Remove this when adding schema support"; -#else string xsd = LoadStringFromPath(_cmdlet, xsdPath, null); -#endif // We only accept txt files and xml files foreach (string file in Directory.GetFiles(sourcePath)) { - if (!String.Equals(Path.GetExtension(file), ".xml", StringComparison.OrdinalIgnoreCase) - && !String.Equals(Path.GetExtension(file), ".txt", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(Path.GetExtension(file), ".xml", StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(file), ".txt", StringComparison.OrdinalIgnoreCase)) { throw new UpdatableHelpSystemException("HelpContentContainsInvalidFiles", StringUtil.Format(HelpDisplayStrings.HelpContentContainsInvalidFiles), ErrorCategory.InvalidData, @@ -1413,7 +1244,7 @@ private void ValidateAndCopyHelpContent(string sourcePath, Collection de // xml validation foreach (string file in Directory.GetFiles(sourcePath)) { - if (String.Equals(Path.GetExtension(file), ".xml", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(Path.GetExtension(file), ".xml", StringComparison.OrdinalIgnoreCase)) { if (xsd == null) { @@ -1474,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) @@ -1484,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 { @@ -1496,25 +1325,23 @@ 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, -#if !CORECLR + CreateValidXmlDocument(node.OuterXml, MamlXmlNamespace, xsd, new ValidationEventHandler(HelpContentValidationHandler), -#endif false); } } } } - else if (String.Equals(Path.GetExtension(file), ".txt", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(Path.GetExtension(file), ".txt", StringComparison.OrdinalIgnoreCase)) { FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read); @@ -1579,12 +1406,12 @@ private void ValidateAndCopyHelpContent(string sourcePath, Collection de } /// - /// Loads string from the given path + /// Loads string from the given path. /// - /// cmdlet instance - /// path to load - /// credential - /// string loaded + /// Cmdlet instance. + /// Path to load. + /// Credential. + /// String loaded. internal static string LoadStringFromPath(PSCmdlet cmdlet, string path, PSCredential credential) { Debug.Assert(path != null); @@ -1630,91 +1457,46 @@ internal static string LoadStringFromPath(PSCmdlet cmdlet, string path, PSCreden /// internal static string GetFilePath(string path) { + FileInfo item = new FileInfo(path); + + // We use 'FileInfo.Attributes' (not 'FileInfo.Exist') + // because we want to get exceptions + // like UnauthorizedAccessException or IOException. + if ((int)item.Attributes != -1) + { + return path; + } #if UNIX // On Linux, file paths are case sensitive. // The user does not have control over the files (HelpInfo.xml, .zip, and cab) that are generated by the Publishing team. // The logic below is to support updating help content via sourcepath parameter for case insensitive files. - FileInfo item = new FileInfo(path); - string directoryPath = item.Directory.FullName; + var dirInfo = item.Directory; string fileName = item.Name; // Prerequisite: The directory in the given path must exist and it is case sensitive. - if (Utils.NativeDirectoryExists(directoryPath)) + if (dirInfo.Exists) { // Get the list of files in the directory. - string[] fileList = Directory.GetFiles(directoryPath); - foreach (string filePath in fileList) + FileInfo[] fileList = dirInfo.GetFiles(searchPattern: fileName, new System.IO.EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }); + + if (fileList.Length > 0) { - if (filePath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) - { - return filePath; - } + return fileList[0].FullName; } } -#else - if (Utils.NativeFileExists(path)) - { - return path; - } #endif return null; } /// - /// Gets the default source path from GP + /// Gets the default source path from GP. /// /// internal string GetDefaultSourcePath() { var updatableHelpSetting = Utils.GetPolicySetting(Utils.SystemWideOnlyConfig); string defaultSourcePath = updatableHelpSetting?.DefaultSourcePath; - return String.IsNullOrEmpty(defaultSourcePath) ? null : defaultSourcePath; - } - - /// - /// Sets the DisablePromptToUpdatableHelp regkey - /// - internal static void SetDisablePromptToUpdateHelp() - { - try - { - PowerShellConfig.Instance.SetDisablePromptToUpdateHelp(true); - } - catch (UnauthorizedAccessException) - { - // Ignore AccessDenied related exceptions - } - catch (SecurityException) - { - // Ignore AccessDenied related exceptions - } - } - - /// - /// Checks if it is necessary to prompt to update help - /// - /// - internal static bool ShouldPromptToUpdateHelp() - { -#if UNIX - // TODO: This workaround needs to be removed once updatable help - // works on Linux. - return false; -#else - try - { - if (!Utils.IsAdministrator()) - { - return false; - } - - return PowerShellConfig.Instance.GetDisablePromptToUpdateHelp(); - } - catch (SecurityException) - { - return false; - } -#endif + return string.IsNullOrEmpty(defaultSourcePath) ? null : defaultSourcePath; } #endregion @@ -1726,10 +1508,10 @@ internal static bool ShouldPromptToUpdateHelp() #if !CORECLR /// - /// Handles the download completion event + /// Handles the download completion event. /// - /// event sender - /// event arguments + /// Event sender. + /// Event arguments. private void HandleDownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { if (_stopping) @@ -1766,10 +1548,10 @@ private void HandleDownloadFileCompleted(object sender, AsyncCompletedEventArgs } /// - /// Handles the download progress changed event + /// Handles the download progress changed event. /// - /// event sender - /// event arguments + /// Event sender. + /// Event arguments. private void HandleDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { if (_stopping) @@ -1791,15 +1573,15 @@ private void HandleDownloadProgressChanged(object sender, DownloadProgressChange } /// - /// Controls the updatable help system drive + /// Controls the updatable help system drive. /// internal class UpdatableHelpSystemDrive : IDisposable { - private string _driveName; - private PSCmdlet _cmdlet; + private readonly string _driveName; + private readonly PSCmdlet _cmdlet; /// - /// Gets the drive name + /// Gets the drive name. /// internal string DriveName { @@ -1810,7 +1592,6 @@ internal string DriveName } /// - /// /// /// /// @@ -1823,7 +1604,7 @@ internal UpdatableHelpSystemDrive(PSCmdlet cmdlet, string path, PSCredential cre _cmdlet = cmdlet; // Need to get rid of the trailing \, otherwise New-PSDrive will not work... - if (path.EndsWith("\\", StringComparison.OrdinalIgnoreCase) || path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (path.EndsWith('\\') || path.EndsWith('/')) { path = path.Remove(path.Length - 1); } @@ -1847,7 +1628,7 @@ internal UpdatableHelpSystemDrive(PSCmdlet cmdlet, string path, PSCredential cre } mappedDrive = new PSDriveInfo(_driveName, cmdlet.SessionState.Internal.GetSingleProvider("FileSystem"), - path, String.Empty, credential); + path, string.Empty, credential); cmdlet.SessionState.Drive.New(mappedDrive, "local"); @@ -1856,7 +1637,7 @@ internal UpdatableHelpSystemDrive(PSCmdlet cmdlet, string path, PSCredential cre } /// - /// Disposes the class + /// Disposes the class. /// public void Dispose() { diff --git a/src/System.Management.Automation/help/UpdatableHelpUri.cs b/src/System.Management.Automation/help/UpdatableHelpUri.cs index c8c4286505c..82bbb4016fb 100644 --- a/src/System.Management.Automation/help/UpdatableHelpUri.cs +++ b/src/System.Management.Automation/help/UpdatableHelpUri.cs @@ -1,29 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Globalization; using System.Diagnostics; +using System.Globalization; namespace System.Management.Automation.Help { /// - /// This class represents a help system URI + /// This class represents a help system URI. /// internal class UpdatableHelpUri { /// - /// Class constructor + /// Class constructor. /// - /// module name - /// module guid - /// UI culture - /// resolved URI + /// Module name. + /// Module guid. + /// UI culture. + /// Resolved URI. internal UpdatableHelpUri(string moduleName, Guid moduleGuid, CultureInfo culture, string resolvedUri) { - Debug.Assert(!String.IsNullOrEmpty(moduleName)); - Debug.Assert(moduleGuid != null); - Debug.Assert(!String.IsNullOrEmpty(resolvedUri)); + Debug.Assert(!string.IsNullOrEmpty(moduleName)); + Debug.Assert(!string.IsNullOrEmpty(resolvedUri)); ModuleName = moduleName; ModuleGuid = moduleGuid; @@ -32,22 +30,22 @@ internal UpdatableHelpUri(string moduleName, Guid moduleGuid, CultureInfo cultur } /// - /// Module name + /// Module name. /// internal string ModuleName { get; } /// - /// Module GUID + /// Module GUID. /// internal Guid ModuleGuid { get; } /// - /// UI Culture + /// UI Culture. /// internal CultureInfo Culture { get; } /// - /// Resolved URI + /// Resolved URI. /// internal string ResolvedUri { get; } } diff --git a/src/System.Management.Automation/help/UpdateHelpCommand.cs b/src/System.Management.Automation/help/UpdateHelpCommand.cs index a7eca22ee38..9df82699a32 100644 --- a/src/System.Management.Automation/help/UpdateHelpCommand.cs +++ b/src/System.Management.Automation/help/UpdateHelpCommand.cs @@ -1,31 +1,31 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Help; -using System.IO; -using System.Globalization; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Diagnostics; +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; namespace Microsoft.PowerShell.Commands { /// - /// This class implements the Update-Help cmdlet + /// This class implements the Update-Help cmdlet. /// [Cmdlet(VerbsData.Update, "Help", DefaultParameterSetName = PathParameterSetName, SupportsShouldProcess = true, - HelpUri = "https://go.microsoft.com/fwlink/?LinkID=210614")] + HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096805")] public sealed class UpdateHelpCommand : UpdatableHelpCommandBase { #region Constructor /// - /// Class constructor + /// Class constructor. /// public UpdateHelpCommand() : base(UpdatableHelpCommandType.UpdateHelpCommand) { @@ -38,7 +38,7 @@ public UpdateHelpCommand() : base(UpdatableHelpCommandType.UpdateHelpCommand) #region Parameters /// - /// Specifies the modules to update + /// Specifies the modules to update. /// [Parameter(Position = 0, ParameterSetName = PathParameterSetName, ValueFromPipelineByPropertyName = true)] [Parameter(Position = 0, ParameterSetName = LiteralPathParameterSetName, ValueFromPipelineByPropertyName = true)] @@ -51,15 +51,17 @@ public string[] Module { return _module; } + set { _module = value; } } + private string[] _module; /// - /// Specifies the Module Specifications to update + /// Specifies the Module Specifications to update. /// [Parameter(ParameterSetName = PathParameterSetName, ValueFromPipelineByPropertyName = true)] [Parameter(ParameterSetName = LiteralPathParameterSetName, ValueFromPipelineByPropertyName = true)] @@ -68,10 +70,11 @@ public string[] Module public ModuleSpecification[] FullyQualifiedModule { get; set; } /// - /// Specifies the paths to update from + /// Specifies the paths to update from. /// [Parameter(Position = 1, ParameterSetName = PathParameterSetName)] [ValidateNotNull] + [Alias("Path")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] SourcePath { @@ -79,18 +82,20 @@ public string[] SourcePath { return _path; } + set { _path = value; } } + private string[] _path; /// - /// Specifies the literal path to save updates to + /// Specifies the literal path to save updates to. /// [Parameter(ParameterSetName = LiteralPathParameterSetName, ValueFromPipelineByPropertyName = true)] - [Alias("PSPath")] + [Alias("PSPath", "LP")] [ValidateNotNull] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] LiteralPath @@ -99,17 +104,18 @@ public string[] LiteralPath { return _path; } + set { _path = value; _isLiteralPath = true; } } - private bool _isLiteralPath = false; + private bool _isLiteralPath = false; /// - /// Scans paths recursively + /// Scans paths recursively. /// [Parameter] public SwitchParameter Recurse @@ -118,11 +124,13 @@ public SwitchParameter Recurse { return _recurse; } + set { _recurse = value; } } + private bool _recurse; #endregion @@ -132,13 +140,10 @@ public SwitchParameter Recurse #region Implementation /// - /// Begin processing + /// Begin processing. /// protected override void BeginProcessing() { - // Disable Get-Help prompt - UpdatableHelpSystem.SetDisablePromptToUpdateHelp(); - if (_path == null) { // Pull default source path from GP @@ -152,7 +157,7 @@ protected override void BeginProcessing() } /// - /// Main cmdlet logic + /// Main cmdlet logic. /// protected override void ProcessRecord() { @@ -174,8 +179,19 @@ protected override void ProcessRecord() PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.CannotSpecifyRecurseWithoutPath)); ThrowTerminatingError(e.ErrorRecord); } + _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); @@ -202,21 +218,36 @@ protected override void ProcessRecord() } /// - /// Process a single module with a given culture + /// Process a single module with a given culture. /// - /// module to process - /// culture to use - /// true if the module has been processed, false if not + /// Module to process. + /// Culture to use. + /// 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; + string moduleBase = module.ModuleBase; + + if (this.Scope == UpdateHelpScope.CurrentUser) + { + moduleBase = HelpUtils.GetModuleBaseForUserHelp(moduleBase, module.ModuleName); + } + // reading the xml file even if force is specified // Reason: we need the current version for ShouldProcess string xml = UpdatableHelpSystem.LoadStringFromPath(this, - SessionState.Path.Combine(module.ModuleBase, module.GetHelpInfoName()), + SessionState.Path.Combine(moduleBase, module.GetHelpInfoName()), null); if (xml != null) @@ -231,7 +262,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } // Don't update too frequently - if (!_alreadyCheckedOncePerDayPerModule && !CheckOncePerDayPerModule(module.ModuleName, module.ModuleBase, module.GetHelpInfoName(), DateTime.UtcNow, _force)) + if (!_alreadyCheckedOncePerDayPerModule && !CheckOncePerDayPerModule(module.ModuleName, moduleBase, module.GetHelpInfoName(), DateTime.UtcNow, _force)) { return true; } @@ -248,7 +279,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, // Search for the HelpInfo XML foreach (string path in _path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.PathNullOrEmpty)); WriteError(e.ErrorRecord); @@ -310,10 +341,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } finally { - if (helpInfoDrive != null) - { - helpInfoDrive.Dispose(); - } + helpInfoDrive?.Dispose(); } } else @@ -336,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, @@ -348,7 +376,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, continue; } - if (Utils.IsUnderProductFolder(module.ModuleBase) && (!Utils.IsAdministrator())) + if (Utils.IsUnderProductFolder(moduleBase) && (!Utils.IsAdministrator())) { string message = StringUtil.Format(HelpErrors.UpdatableHelpRequiresElevation); ProcessException(module.ModuleName, null, new UpdatableHelpSystemException("UpdatableHelpSystemRequiresElevation", @@ -376,7 +404,12 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, // Gather destination paths Collection destPaths = new Collection(); - destPaths.Add(module.ModuleBase); + if (!Directory.Exists(moduleBase)) + { + Directory.CreateDirectory(moduleBase); + } + + destPaths.Add(moduleBase); #if !CORECLR // Side-By-Side directories are not present in OneCore environments. if (IsSystemModule(module.ModuleName) && Environment.Is64BitOperatingSystem) @@ -443,7 +476,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } _helpSystem.GenerateHelpInfo(module.ModuleName, module.ModuleGuid, newHelpInfo.UnresolvedUri, contentUri.Culture.Name, newHelpInfo.GetCultureVersion(contentUri.Culture), - module.ModuleBase, module.GetHelpInfoName(), _force); + moduleBase, module.GetHelpInfoName(), _force); foreach (string fileInstalled in filesInstalled) { @@ -467,11 +500,11 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, } /// - /// Throws PathMustBeValidContainers exception + /// Throws PathMustBeValidContainers exception. /// /// /// - 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/LogContext.cs b/src/System.Management.Automation/logging/LogContext.cs index 97af8b6d7ad..84df2e34be4 100644 --- a/src/System.Management.Automation/logging/LogContext.cs +++ b/src/System.Management.Automation/logging/LogContext.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -11,18 +10,17 @@ namespace System.Management.Automation /// LogContext info is collected by Msh Log Engine and passed on to log provider /// interface. /// - /// internal class LogContext { #region Context Properties - internal String Severity { get; set; } = ""; + internal string Severity { get; set; } = string.Empty; /// /// Name of the host. /// /// - internal string HostName { get; set; } = ""; + internal string HostName { get; set; } = string.Empty; /// /// Name of the host application. @@ -37,37 +35,37 @@ internal string HostApplication /// Version of the host. /// /// - internal string HostVersion { get; set; } = ""; + internal string HostVersion { get; set; } = string.Empty; /// /// Id of the host that is hosting current monad engine. /// /// - internal string HostId { get; set; } = ""; + internal string HostId { get; set; } = string.Empty; /// /// Version of monad engine. /// /// - internal string EngineVersion { get; set; } = ""; + internal string EngineVersion { get; set; } = string.Empty; /// - /// Id for currently running runspace + /// Id for currently running runspace. /// /// - internal string RunspaceId { get; set; } = ""; + internal string RunspaceId { get; set; } = string.Empty; /// - /// PipelineId of current running pipeline + /// PipelineId of current running pipeline. /// /// - internal string PipelineId { get; set; } = ""; + internal string PipelineId { get; set; } = string.Empty; /// - /// Command text that is typed in from commandline + /// Command text that is typed in from commandline. /// /// - internal string CommandName { get; set; } = ""; + internal string CommandName { get; set; } = string.Empty; /// /// Type of the command, which can be Alias, CommandLet, Script, Application, etc. @@ -75,32 +73,32 @@ internal string HostApplication /// The value of this property is a usually conversion of CommandTypes enum into a string. /// /// - internal string CommandType { get; set; } = ""; + internal string CommandType { get; set; } = string.Empty; /// /// Script file name if current command is executed as a result of script run. /// - internal string ScriptName { get; set; } = ""; + internal string ScriptName { get; set; } = string.Empty; /// /// Path to the command executable file. /// - internal string CommandPath { get; set; } = ""; + internal string CommandPath { get; set; } = string.Empty; /// /// Extension for the command executable file. /// - internal string CommandLine { get; set; } = ""; + internal string CommandLine { get; set; } = string.Empty; /// /// Sequence Id for the event to be logged. /// - internal string SequenceNumber { get; set; } = ""; + internal string SequenceNumber { get; set; } = string.Empty; /// /// Current user. /// - internal string User { get; set; } = ""; + internal string User { get; set; } = string.Empty; /// /// The user connected to the machine, if being done with @@ -109,9 +107,9 @@ internal string HostApplication internal string ConnectedUser { get; set; } /// - /// Event happening time + /// Event happening time. /// - internal string Time { get; set; } = ""; + internal string Time { get; set; } = string.Empty; #endregion @@ -128,7 +126,7 @@ internal string HostApplication #region Execution context /// - /// Execution context is necessary for GetVariableValue + /// Execution context is necessary for GetVariableValue. /// internal ExecutionContext ExecutionContext { get; set; } diff --git a/src/System.Management.Automation/logging/LogProvider.cs b/src/System.Management.Automation/logging/LogProvider.cs index c5445c010e9..e02807a38e8 100644 --- a/src/System.Management.Automation/logging/LogProvider.cs +++ b/src/System.Management.Automation/logging/LogProvider.cs @@ -1,10 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; -using System.Text; using System.Management.Automation.Internal; +using System.Text; namespace System.Management.Automation { @@ -27,15 +26,13 @@ namespace System.Management.Automation /// b. EngineLifecycleEvent /// c. CommandLifecycleEvent /// d. ProviderLifecycleEvent - /// e. SettingsEvent - /// + /// e. SettingsEvent. /// internal abstract class LogProvider { /// - /// constructor + /// Constructor. /// - /// internal LogProvider() { } @@ -43,37 +40,34 @@ internal LogProvider() #region Provider api /// - /// Provider interface function for logging health event + /// Provider interface function for logging health event. /// /// /// /// /// - /// - internal abstract void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo); + internal abstract void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo); /// - /// Provider interface function for logging engine lifecycle event + /// Provider interface function for logging engine lifecycle event. /// /// /// /// - /// internal abstract void LogEngineLifecycleEvent(LogContext logContext, EngineState newState, EngineState previousState); /// - /// Provider interface function for logging command health event + /// Provider interface function for logging command health event. /// /// /// internal abstract void LogCommandHealthEvent(LogContext logContext, Exception exception); /// - /// Provider interface function for logging command lifecycle event + /// Provider interface function for logging command lifecycle event. /// /// /// - /// internal abstract void LogCommandLifecycleEvent(Func getLogContext, CommandState newState); /// @@ -81,38 +75,66 @@ internal LogProvider() /// /// /// - internal abstract void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail); + internal abstract void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail); /// - /// Provider interface function for logging provider health event + /// Provider interface function for logging provider health event. /// /// /// /// - /// internal abstract void LogProviderHealthEvent(LogContext logContext, string providerName, Exception exception); /// - /// Provider interface function for logging provider lifecycle event + /// Provider interface function for logging provider lifecycle event. /// /// /// /// - /// internal abstract void LogProviderLifecycleEvent(LogContext logContext, string providerName, ProviderState newState); /// - /// Provider interface function for logging settings event + /// Provider interface function for logging settings event. /// /// /// /// /// - /// internal abstract void LogSettingsEvent(LogContext logContext, string variableName, string value, string previousValue); /// - /// True if the log provider needs to use logging variables + /// 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. /// /// internal virtual bool UseLoggingVariables() @@ -156,14 +178,14 @@ protected static string GetPSLogUserData(ExecutionContext context) { if (context == null) { - return String.Empty; + return string.Empty; } object logData = context.GetVariableValue(SpecialVariables.PSLogUserDataPath); if (logData == null) { - return String.Empty; + return string.Empty; } return logData.ToString(); @@ -172,15 +194,13 @@ protected static string GetPSLogUserData(ExecutionContext context) /// /// Appends exception information. /// - /// string builder. - /// exception. + /// String builder. + /// Exception. 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; @@ -201,13 +221,13 @@ protected static void AppendException(StringBuilder sb, Exception except) /// /// Appends additional information. /// - /// string builder. - /// additional information. - protected static void AppendAdditionalInfo(StringBuilder sb, Dictionary additionalInfo) + /// String builder. + /// Additional information. + protected static void AppendAdditionalInfo(StringBuilder sb, Dictionary additionalInfo) { if (additionalInfo != null) { - foreach (KeyValuePair value in additionalInfo) + foreach (KeyValuePair value in additionalInfo) { sb.AppendLine(StringUtil.Format("{0} = {1}", value.Key, value.Value)); } @@ -217,7 +237,7 @@ protected static void AppendAdditionalInfo(StringBuilder sb, Dictionary /// Gets PSLevel from severity. /// - /// error severity. + /// Error severity. /// PS log level. protected static PSLevel GetPSLevelFromSeverity(string severity) { @@ -237,13 +257,13 @@ protected static PSLevel GetPSLevelFromSeverity(string severity) // Estimated length of all Strings.* values // Rough estimate of values // max path for Command path - const int LogContextInitialSize = 30 * 16 + 13 * 20 + 255; + private const int LogContextInitialSize = 30 * 16 + 13 * 20 + 255; /// - /// Converts log context to string + /// Converts log context to string. /// - /// log context - /// string representation + /// Log context. + /// String representation. protected static string LogContextToString(LogContext context) { StringBuilder sb = new StringBuilder(LogContextInitialSize); @@ -292,9 +312,8 @@ protected static string LogContextToString(LogContext context) internal class DummyLogProvider : LogProvider { /// - /// constructor + /// Constructor. /// - /// internal DummyLogProvider() { } @@ -302,44 +321,40 @@ internal DummyLogProvider() #region Provider api /// - /// DummyLogProvider does nothing to Logging EngineHealthEvent + /// DummyLogProvider does nothing to Logging EngineHealthEvent. /// /// /// /// /// - /// - internal override void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo) + internal override void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo) { } /// - /// DummyLogProvider does nothing to Logging EngineLifecycleEvent + /// DummyLogProvider does nothing to Logging EngineLifecycleEvent. /// /// /// /// - /// internal override void LogEngineLifecycleEvent(LogContext logContext, EngineState newState, EngineState previousState) { } /// - /// Provider interface function for logging command health event + /// Provider interface function for logging command health event. /// /// /// - /// internal override void LogCommandHealthEvent(LogContext logContext, Exception exception) { } /// - /// DummyLogProvider does nothing to Logging CommandLifecycleEvent + /// DummyLogProvider does nothing to Logging CommandLifecycleEvent. /// /// /// - /// internal override void LogCommandLifecycleEvent(Func getLogContext, CommandState newState) { } @@ -349,44 +364,78 @@ internal override void LogCommandLifecycleEvent(Func getLogContext, /// /// /// - internal override void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail) + internal override void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail) { } /// - /// Provider interface function for logging provider health event + /// Provider interface function for logging provider health event. /// /// /// /// - /// internal override void LogProviderHealthEvent(LogContext logContext, string providerName, Exception exception) { } /// - /// DummyLogProvider does nothing to Logging ProviderLifecycleEvent + /// DummyLogProvider does nothing to Logging ProviderLifecycleEvent. /// /// /// /// - /// internal override void LogProviderLifecycleEvent(LogContext logContext, string providerName, ProviderState newState) { } /// - /// DummyLogProvider does nothing to Logging SettingsEvent + /// DummyLogProvider does nothing to Logging SettingsEvent. /// /// /// /// /// - /// internal override void LogSettingsEvent(LogContext logContext, string variableName, string value, string previousValue) { } + /// + /// 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 354da12f6b6..aae65271892 100644 --- a/src/System.Management.Automation/logging/MshLog.cs +++ b/src/System.Management.Automation/logging/MshLog.cs @@ -1,20 +1,18 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Globalization; using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; using System.Security; using System.Threading; -using System.Globalization; namespace System.Management.Automation { /// - /// /// Monad Logging in general is a two layer architecture. At the upper layer are the /// Msh Log Engine and Logging Api. At the lower layer is the Provider Interface /// and Log Providers. This architecture is adopted to achieve independency between @@ -62,16 +60,16 @@ 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 + /// Static constructor. /// static MshLog() { @@ -87,7 +85,6 @@ static MshLog() /// In the longer turn, we may need to use a "Provider Catalog" for /// log provider loading. /// - /// /// /// private static IEnumerable GetLogProvider(string shellId) @@ -96,16 +93,15 @@ private static IEnumerable GetLogProvider(string shellId) } /// - /// Get Log Provider based on Execution Context + /// Get Log Provider based on Execution Context. /// - /// /// /// private static IEnumerable GetLogProvider(ExecutionContext executionContext) { if (executionContext == null) { - throw PSTraceSource.NewArgumentNullException("executionContext"); + throw PSTraceSource.NewArgumentNullException(nameof(executionContext)); } string shellId = executionContext.ShellID; @@ -114,15 +110,14 @@ private static IEnumerable GetLogProvider(ExecutionContext executio } /// - /// Get Log Provider based on Log Context + /// Get Log Provider based on Log Context. /// - /// /// /// private static IEnumerable GetLogProvider(LogContext logContext) { System.Diagnostics.Debug.Assert(logContext != null); - System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(logContext.ShellId)); + System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(logContext.ShellId)); return GetLogProvider(logContext.ShellId); } @@ -139,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); @@ -175,7 +165,6 @@ private static Collection CreateLogProvider(string shellId) /// /// This will set the current log provider to be dummy log. /// - /// /// internal static void SetDummyLog(string shellId) { @@ -196,35 +185,37 @@ internal static void SetDummyLog(string shellId) /// Variant form of this function is defined below, which will make parameters additionalInfo /// and newEngineState optional. /// - /// Execution context for the engine that is running - /// EventId for the event to be logged - /// Exception associated with this event - /// Severity of this event - /// Additional information for this event - /// New engine state + /// Execution context for the engine that is running. + /// EventId for the event to be logged. + /// Exception associated with this event. + /// Severity of this event. + /// Additional information for this event. + /// New engine state. internal static void LogEngineHealthEvent(ExecutionContext executionContext, int eventId, Exception exception, Severity severity, - Dictionary additionalInfo, + Dictionary additionalInfo, EngineState newEngineState) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } if (exception == null) { - PSTraceSource.NewArgumentNullException("exception"); + PSTraceSource.NewArgumentNullException(nameof(exception)); return; } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogEngineHealthEvent(provider, executionContext)) @@ -241,7 +232,7 @@ internal static void LogEngineHealthEvent(ExecutionContext executionContext, /// /// This is a variation of LogEngineHealthEvent api to make additionalInfo and newEngineState - /// optional + /// optional. /// /// /// @@ -273,7 +264,7 @@ internal static void LogEngineHealthEvent(ExecutionContext executionContext, /// /// This is a variation of LogEngineHealthEvent api to make newEngineState - /// optional + /// optional. /// /// /// @@ -284,14 +275,14 @@ internal static void LogEngineHealthEvent(ExecutionContext executionContext, int eventId, Exception exception, Severity severity, - Dictionary additionalInfo) + Dictionary additionalInfo) { LogEngineHealthEvent(executionContext, eventId, exception, severity, additionalInfo, EngineState.None); } /// /// This is a variation of LogEngineHealthEvent api to make additionalInfo - /// optional + /// optional. /// /// /// @@ -314,25 +305,25 @@ internal static void LogEngineHealthEvent(ExecutionContext executionContext, /// /// This API is currently used only by runspace before engine start. /// - /// logContext to be - /// EventId for the event to be logged - /// Exception associated with this event - /// Additional information for this event + /// LogContext to be. + /// EventId for the event to be logged. + /// Exception associated with this event. + /// Additional information for this event. internal static void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, - Dictionary additionalInfo + Dictionary additionalInfo ) { if (logContext == null) { - PSTraceSource.NewArgumentNullException("logContext"); + PSTraceSource.NewArgumentNullException(nameof(logContext)); return; } if (exception == null) { - PSTraceSource.NewArgumentNullException("exception"); + PSTraceSource.NewArgumentNullException(nameof(exception)); return; } @@ -356,16 +347,16 @@ Dictionary additionalInfo /// Variant form of this function is defined below, which will make parameter additionalInfo /// optional. /// - /// execution context for current engine instance - /// new engine state - /// invocationInfo for current command that is running + /// Execution context for current engine instance. + /// New engine state. + /// InvocationInfo for current command that is running. internal static void LogEngineLifecycleEvent(ExecutionContext executionContext, EngineState engineState, InvocationInfo invocationInfo) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -402,11 +393,10 @@ internal static void LogEngineLifecycleEvent(ExecutionContext executionContext, /// /// LogProviderHealthEvent: Log a command health event. - /// /// - /// Execution context for the engine that is running - /// Exception associated with this event - /// Severity of this event + /// Execution context for the engine that is running. + /// Exception associated with this event. + /// Severity of this event. internal static void LogCommandHealthEvent(ExecutionContext executionContext, Exception exception, Severity severity @@ -414,20 +404,22 @@ Severity severity { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } if (exception == null) { - PSTraceSource.NewArgumentNullException("exception"); + PSTraceSource.NewArgumentNullException(nameof(exception)); return; } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogCommandHealthEvent(provider, executionContext)) @@ -446,22 +438,22 @@ Severity severity /// /// This is the only form of CommandLifecycleEvent logging api. /// - /// Execution Context for the current running engine - /// new command state - /// invocation data for current command that is running + /// Execution Context for the current running engine. + /// New command state. + /// Invocation data for current command that is running. internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, CommandState commandState, InvocationInfo invocationInfo) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } if (invocationInfo == null) { - PSTraceSource.NewArgumentNullException("invocationInfo"); + PSTraceSource.NewArgumentNullException(nameof(invocationInfo)); return; } @@ -476,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); } } } @@ -488,16 +481,16 @@ internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, /// of invocationInfo. It is likely that invocationInfo is not available if /// the command failed security check. /// - /// Execution Context for the current running engine - /// new command state - /// current command that is running + /// Execution Context for the current running engine. + /// New command state. + /// Current command that is running. internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, CommandState commandState, string commandName) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -514,6 +507,7 @@ internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, logContext = GetLogContext(executionContext, null); logContext.CommandName = commandName; } + return logContext; }, commandState); } @@ -526,19 +520,18 @@ internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, /// /// LogPipelineExecutionDetailEvent: Log a pipeline execution detail event. - /// /// - /// Execution Context for the current running engine - /// detail to be logged for this pipeline execution detail - /// invocation data for current command that is running + /// Execution Context for the current running engine. + /// Detail to be logged for this pipeline execution detail. + /// Invocation data for current command that is running. internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionContext, - List detail, + List detail, InvocationInfo invocationInfo) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -558,18 +551,18 @@ internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionC /// instead of invocationInfo. This will save the need to fill in the commandName for /// this event. /// - /// Execution Context for the current running engine - /// detail to be logged for this pipeline execution detail - /// script that is currently running - /// command line that is currently running + /// Execution Context for the current running engine. + /// Detail to be logged for this pipeline execution detail. + /// Script that is currently running. + /// Command line that is currently running. internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionContext, - List detail, + List detail, string scriptName, string commandLine) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -592,12 +585,11 @@ internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionC /// /// LogProviderHealthEvent: Log a Provider health event. - /// /// - /// Execution context for the engine that is running - /// Name of the provider - /// Exception associated with this event - /// Severity of this event + /// Execution context for the engine that is running. + /// Name of the provider. + /// Exception associated with this event. + /// Severity of this event. internal static void LogProviderHealthEvent(ExecutionContext executionContext, string providerName, Exception exception, @@ -606,20 +598,22 @@ Severity severity { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } if (exception == null) { - PSTraceSource.NewArgumentNullException("exception"); + PSTraceSource.NewArgumentNullException(nameof(exception)); return; } InvocationInfo invocationInfo = null; - IContainsErrorRecord icer = exception as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (exception is IContainsErrorRecord icer && icer.ErrorRecord != null) + { invocationInfo = icer.ErrorRecord.InvocationInfo; + } + foreach (LogProvider provider in GetLogProvider(executionContext)) { if (NeedToLogProviderHealthEvent(provider, executionContext)) @@ -638,16 +632,16 @@ Severity severity /// /// This is the only form of ProviderLifecycleEvent logging api. /// - /// Execution Context for current engine that is running - /// Provider name - /// New provider state + /// Execution Context for current engine that is running. + /// Provider name. + /// New provider state. internal static void LogProviderLifecycleEvent(ExecutionContext executionContext, string providerName, ProviderState providerState) { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -670,11 +664,11 @@ internal static void LogProviderLifecycleEvent(ExecutionContext executionContext /// This is the basic form of LoggingSettingsEvent API. Variation of this function defined /// below will make parameter invocationInfo optional. /// - /// Execution context for current running engine - /// Variable name - /// New value for the variable - /// Previous value for the variable - /// Invocation data for the command that is currently running + /// Execution context for current running engine. + /// Variable name. + /// New value for the variable. + /// Previous value for the variable. + /// Invocation data for the command that is currently running. internal static void LogSettingsEvent(ExecutionContext executionContext, string variableName, string newValue, @@ -683,7 +677,7 @@ internal static void LogSettingsEvent(ExecutionContext executionContext, { if (executionContext == null) { - PSTraceSource.NewArgumentNullException("executionContext"); + PSTraceSource.NewArgumentNullException(nameof(executionContext)); return; } @@ -783,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) { @@ -816,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; } @@ -1018,7 +1010,7 @@ private static bool NeedToLogSettingsEvent(LogProvider logProvider, ExecutionCon private static int s_nextSequenceNumber = 0; /// - /// generate next sequence id to be attached to current event. + /// Generate next sequence id to be attached to current event. /// /// private static string NextSequenceNumber @@ -1033,20 +1025,20 @@ private static string NextSequenceNumber #region EventId Constants - //General health issues. + // General health issues. internal const int EVENT_ID_GENERAL_HEALTH_ISSUE = 100; // Dependency. resource not available internal const int EVENT_ID_RESOURCE_NOT_AVAILABLE = 101; - //Connectivity. network connection failure + // Connectivity. network connection failure internal const int EVENT_ID_NETWORK_CONNECTIVITY_ISSUE = 102; - //Settings. fail to set some configuration settings + // Settings. fail to set some configuration settings internal const int EVENT_ID_CONFIGURATION_FAILURE = 103; - //Performance. system is experiencing some performance issues + // Performance. system is experiencing some performance issues internal const int EVENT_ID_PERFORMANCE_ISSUE = 104; - //Security: system is experiencing some security issues + // Security: system is experiencing some security issues internal const int EVENT_ID_SECURITY_ISSUE = 105; - //Workload. system is overloaded. + // Workload. system is overloaded. internal const int EVENT_ID_SYSTEM_OVERLOADED = 106; // Beta 1 only -- Unexpected Exception @@ -1056,7 +1048,7 @@ private static string NextSequenceNumber } /// - /// Log context cache + /// Log context cache. /// internal class LogContextCache { @@ -1066,7 +1058,7 @@ internal class LogContextCache #region Command State and Provider State /// - /// Severity of the event + /// Severity of the event. /// internal enum Severity { @@ -1093,44 +1085,39 @@ internal enum Severity /// Informational. /// Informational - }; + } /// - /// enum for command states + /// Enum for command states. /// internal enum CommandState { /// - /// /// Started = 0, /// - /// /// Stopped = 1, /// - /// /// Terminated = 2 - }; + } /// - /// enum for provider states + /// Enum for provider states. /// internal enum ProviderState { /// - /// /// Started = 0, /// - /// /// 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 084f6f2b12e..00000000000 --- a/src/System.Management.Automation/logging/eventlog/EventLogLogProvider.cs +++ /dev/null @@ -1,688 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Resources; -using System.ComponentModel; -using System.Globalization; -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 (null != icer && null != icer.ErrorRecord) - { - 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"] = ""; - mapArgs["ErrorId"] = ""; - 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 (null != icer && null != icer.ErrorRecord) - { - 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"] = ""; - mapArgs["ErrorId"] = ""; - 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 (null != icer && null != icer.ErrorRecord) - { - 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"] = ""; - mapArgs["ErrorId"] = ""; - 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] = ""; - mapArgs["AdditionalInfo_Value" + id] = ""; - } - - 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] = ""; - mapArgs["AdditionalInfo_Value" + id] = ""; - } - } - - 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 messageTemplate = _resourceManager.GetString(messageId); - - if (String.IsNullOrEmpty(messageTemplate)) - return ""; - - 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 b8c6557dfcc..c51d8e35128 100644 --- a/src/System.Management.Automation/namespaces/AliasProvider.cs +++ b/src/System.Management.Automation/namespaces/AliasProvider.cs @@ -1,15 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. - -using System; -using Dbg = System.Management.Automation; using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Provider; +using Dbg = System.Management.Automation; + namespace Microsoft.PowerShell.Commands { /// @@ -26,7 +24,7 @@ namespace Microsoft.PowerShell.Commands public sealed class AliasProvider : SessionStateProviderBase { /// - /// Gets the name of the provider + /// Gets the name of the provider. /// public const string ProviderName = "Alias"; @@ -38,20 +36,18 @@ public sealed class AliasProvider : SessionStateProviderBase /// public AliasProvider() { - } // constructor + } #endregion Constructor #region DriveCmdletProvider overrides /// - /// Initializes the alias drive + /// Initializes the alias drive. /// - /// /// /// An array of a single PSDriveInfo object representing the alias drive. /// - /// protected override Collection InitializeDefaultDrives() { string description = SessionStateStrings.AliasDriveDescription; @@ -60,62 +56,53 @@ protected override Collection InitializeDefaultDrives() new PSDriveInfo( DriveNames.AliasDrive, ProviderInfo, - String.Empty, + string.Empty, description, null); Collection drives = new Collection(); drives.Add(aliasDrive); return drives; - } // InitializeDefaultDrives + } #endregion DriveCmdletProvider overrides #region Dynamic Parameters /// - /// Gets the dynamic parameters for the NewItem cmdlet + /// Gets the dynamic parameters for the NewItem cmdlet. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// An instance of AliasProviderDynamicParameters which is the dynamic parameters for /// NewItem. /// - /// protected override object NewItemDynamicParameters(string path, string type, object newItemValue) { return new AliasProviderDynamicParameters(); } /// - /// Gets the dynamic parameters for the NewItem cmdlet + /// Gets the dynamic parameters for the NewItem cmdlet. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// An instance of AliasProviderDynamicParameters which is the dynamic parameters for /// SetItem. /// - /// protected override object SetItemDynamicParameters(string path, object value) { return new AliasProviderDynamicParameters(); @@ -125,48 +112,40 @@ protected override object SetItemDynamicParameters(string path, object value) #region protected members - /// - /// Gets a alias from session state + /// Gets a alias from session state. /// - /// /// /// The name of the alias to retrieve. /// - /// /// /// A DictionaryEntry that represents the value of the alias. /// - /// internal override object GetSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); AliasInfo value = SessionState.Internal.GetAlias(name, Context.Origin); return value; - } // GetSessionStateItem + } /// /// Since items are often more than their value, this method should - /// be overridden to provide the value for an item + /// be overridden to provide the value for an item. /// - /// /// /// The item to extract the value from. /// - /// /// /// The value of the specified item. /// - /// /// /// The default implementation will get /// the Value property of a DictionaryEntry /// - /// internal override object GetValueOfItem(object item) { Dbg.Diagnostics.Assert( @@ -180,30 +159,27 @@ internal override object GetValueOfItem(object item) { value = aliasInfo.Definition; } + return value; - } // GetValueOfItem + } /// - /// Sets the alias of the specified name to the specified value + /// Sets the alias of the specified name to the specified value. /// - /// /// /// The name of the alias to set. /// - /// /// /// The new value for the alias. /// - /// /// /// If true, the item that was set should be written to WriteItemObject. /// - /// #pragma warning disable 0162 internal override void SetSessionStateItem(string name, object value, bool writeItem) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); AliasProviderDynamicParameters dynamicParameters = @@ -218,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 { @@ -244,6 +216,7 @@ internal override void SetSessionStateItem(string name, object value, bool write { item = SessionState.Internal.SetAliasValue(name, stringValue, Force, Context.Origin); } + break; } @@ -261,11 +234,12 @@ internal override void SetSessionStateItem(string name, object value, bool write { newAliasInfo.SetOptions(dynamicParameters.Options, Force); } + item = SessionState.Internal.SetAliasItem(newAliasInfo, Force, Context.Origin); break; } - throw PSTraceSource.NewArgumentException("value"); + throw PSTraceSource.NewArgumentException(nameof(value)); } while (false); } @@ -273,53 +247,46 @@ internal override void SetSessionStateItem(string name, object value, bool write { WriteItemObject(item, item.Name, false); } - } // SetSessionStateItem + } #pragma warning restore 0162 /// /// Removes the specified alias from session state. /// - /// /// /// The name of the alias to remove from session state. /// - /// internal override void RemoveSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); SessionState.Internal.RemoveAlias(name, Force); - } // RemoveSessionStateItem + } /// - /// Gets a flattened view of the alias in session state + /// Gets a flattened view of the alias in session state. /// - /// /// /// An IDictionary representing the flattened view of the aliases in /// session state. /// - /// internal override IDictionary GetSessionStateTable() { return (IDictionary)SessionState.Internal.GetAliasTable(); - } // GetSessionStateTable + } /// /// Determines if the item can be renamed. Derived classes that need /// to perform a check should override this method. /// - /// /// /// The item to verify if it can be renamed. /// - /// /// /// true if the item can be renamed or false otherwise. /// - /// internal override bool CanRenameItem(object item) { bool result = false; @@ -345,31 +312,34 @@ internal override bool CanRenameItem(object item) return result; } - #endregion protected members - } // AliasProvider + #endregion protected members + } /// - /// The dynamic parameter object for the AliasProvider SetItem and NewItem commands + /// The dynamic parameter object for the AliasProvider SetItem and NewItem commands. /// public class AliasProviderDynamicParameters { /// - /// Gets or sets the option parameter for the alias + /// Gets or sets the option parameter for the alias. /// - /// [Parameter] public ScopedItemOptions Options { - get { return _options; } + get + { + return _options; + } + set { _optionsSet = true; _options = value; } } - private ScopedItemOptions _options; + private ScopedItemOptions _options; /// /// Determines if the Options parameter was set. @@ -379,7 +349,7 @@ internal bool OptionsSet { get { return _optionsSet; } } + private bool _optionsSet = false; } } - diff --git a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs index b67a2f9f46f..2a600097108 100644 --- a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; @@ -12,7 +11,6 @@ namespace System.Management.Automation.Provider /// /// The base class for Cmdlet providers that expose a single level of items. /// - /// /// /// The ContainerCmdletProvider class is base class that a provider derives from /// to implement methods that allow @@ -38,30 +36,24 @@ public abstract class ContainerCmdletProvider : ItemCmdletProvider /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path (or name in a flat namespace) to the item from which to retrieve the children. /// - /// /// /// True if all children in a subtree should be retrieved, false if only a single /// level of children should be retrieved. This parameter should only be true for /// the NavigationCmdletProvider derived class. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing is returned, but all children should be written to the Write*Object or /// Write*Objects method. /// - /// internal void GetChildItems( string path, bool recurse, @@ -73,28 +65,24 @@ internal void GetChildItems( // Call virtual method GetChildItems(path, recurse, depth); - } // GetChildItems + } /// /// Gives the provider to attach additional parameters to /// the get-childitem cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// True if all children in a subtree should be retrieved, false if only a single /// level of children should be retrieved. This parameter should only be true for /// the NavigationCmdletProvider derived class. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -102,7 +90,6 @@ internal void GetChildItems( /// /// The default implementation returns null. (no additional parameters) /// - /// internal object GetChildItemsDynamicParameters( string path, bool recurse, @@ -110,39 +97,33 @@ internal object GetChildItemsDynamicParameters( { Context = context; return GetChildItemsDynamicParameters(path, recurse); - } // GetChildItemsDynamicParameters + } /// /// Internal wrapper for the GetChildNames protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item from which to retrieve the child names. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing is returned, but all names should be written to the Write*Object or /// Write*Objects method. /// - /// /// /// The child names are the leaf portion of the path. Example, for the file system /// the name for the path c:\windows\system32\foo.dll would be foo.dll or for /// the directory c:\windows\system32 would be system32. For Active Directory the /// child names would be RDN values of the child objects of the container. /// - /// internal void GetChildNames( string path, ReturnContainers returnContainers, @@ -152,13 +133,12 @@ internal void GetChildNames( // Call virtual method GetChildNames(path, returnContainers); - } // GetChildNames + } /// /// Gets a new provider-specific path and filter (if any) that corresponds to the given /// path. /// - /// /// /// The path to the item. Unlike most other provider APIs, this path is likely to /// contain PowerShell wildcards. @@ -175,11 +155,9 @@ internal void GetChildNames( /// /// The context under which this method is being called. /// - /// /// /// True if the path or filter were altered. False otherwise. /// - /// /// /// Providers override this method if they support a native filtering syntax that /// can offer performance improvements over wildcard matching done by the PowerShell @@ -209,21 +187,17 @@ internal virtual bool ConvertPath( return ConvertPath(path, filter, ref updatedPath, ref updatedFilter); } - /// /// Gives the provider to attach additional parameters to /// the get-childitem -name cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -231,39 +205,33 @@ internal virtual bool ConvertPath( /// /// The default implementation returns null. (no additional parameters) /// - /// internal object GetChildNamesDynamicParameters( string path, CmdletProviderContext context) { Context = context; return GetChildNamesDynamicParameters(path); - } // GetChildItemsNamesDynamicParameters + } /// /// Internal wrapper for the RenameItem protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to rename. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing is returned, but all renamed items should be written to the Write*Object or /// Write*Objects. /// - /// internal void RenameItem( string path, string newName, @@ -274,32 +242,27 @@ internal void RenameItem( // Call virtual method RenameItem(path, newName); - } // RenameItem + } /// /// Gives the provider to attach additional parameters to /// the rename-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object RenameItemDynamicParameters( string path, string newName, @@ -307,37 +270,30 @@ internal object RenameItemDynamicParameters( { Context = context; return RenameItemDynamicParameters(path, newName); - } // RenameItemDynamicParameters - + } /// /// Internal wrapper for the New protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to create. /// - /// /// /// The provider defined type of the item to create. /// - /// /// /// This is a provider specific type that the provider can use to create a new /// instance of an item at the specified path. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing is returned, but all new items should be written to the Write*Object or /// Write*Objects. /// - /// internal void NewItem( string path, string type, @@ -349,36 +305,30 @@ internal void NewItem( // Call virtual method NewItem(path, type, newItemValue); - } // NewItem + } /// /// Gives the provider to attach additional parameters to /// the new-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The provider defined type of the item to create. /// - /// /// /// This is a provider specific type that the provider can use to create a new /// instance of an item at the specified path. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object NewItemDynamicParameters( string path, string type, @@ -387,28 +337,24 @@ internal object NewItemDynamicParameters( { Context = context; return NewItemDynamicParameters(path, type, newItemValue); - } // NewItemDynamicParameters + } /// /// Internal wrapper for the Remove protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to remove. /// - /// /// /// True if all children in a subtree should be removed, false if only a single /// level of children should be removed. This parameter should only be true for /// NavigationCmdletProvider and its derived classes. /// - /// /// /// The context under which this method is being called. /// - /// internal void RemoveItem( string path, bool recurse, @@ -419,33 +365,28 @@ internal void RemoveItem( // Call virtual method RemoveItem(path, recurse); - } // Remove + } /// /// Gives the provider to attach additional parameters to /// the remove-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// True if all children in a subtree should be removed, false if only a single /// level of children should be removed. This parameter should only be true for /// NavigationCmdletProvider and its derived classes. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object RemoveItemDynamicParameters( string path, bool recurse, @@ -453,34 +394,28 @@ internal object RemoveItemDynamicParameters( { Context = context; return RemoveItemDynamicParameters(path, recurse); - } // RemoveItemDynamicParameters - + } /// /// Internal wrapper for the HasChildItems protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to see if it has children. /// - /// /// /// The context under which this method is being called. /// - /// /// /// True if the item has children, false otherwise. /// - /// /// /// For implementers of ContainerCmdletProvider classes and those derived from it, /// if a null or empty path is passed, /// the provider should consider any items in the data store to be children /// and return true. /// - /// internal bool HasChildItems(string path, CmdletProviderContext context) { Context = context; @@ -488,35 +423,29 @@ internal bool HasChildItems(string path, CmdletProviderContext context) // Call virtual method return HasChildItems(path); - } // HasChildItems + } /// /// Internal wrapper for the Copy protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path of the item to copy. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing. All objects that are copied should be written to the Write*Object or /// Write*Objects methods. /// - /// internal void CopyItem( string path, string copyPath, @@ -528,36 +457,29 @@ internal void CopyItem( // Call virtual method CopyItem(path, copyPath, recurse); - } // CopyItem - + } /// /// Gives the provider to attach additional parameters to /// the copy-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object CopyItemDynamicParameters( string path, string destination, @@ -566,7 +488,7 @@ internal object CopyItemDynamicParameters( { Context = context; return CopyItemDynamicParameters(path, destination, recurse); - } // CopyItemDynamicParameters + } #endregion Internal members @@ -575,21 +497,17 @@ internal object CopyItemDynamicParameters( /// /// Gets the children of the item at the specified path. /// - /// /// /// The path (or name in a flat namespace) to the item from which to retrieve the children. /// - /// /// /// True if all children in a subtree should be retrieved, false if only a single /// level of children should be retrieved. This parameter should only be true for /// the NavigationCmdletProvider derived class. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user access to the provider objects using /// the get-childitem cmdlets. @@ -623,25 +541,20 @@ protected virtual void GetChildItems( /// /// Gets the children of the item at the specified path. /// - /// /// /// The path (or name in a flat namespace) to the item from which to retrieve the children. /// - /// /// /// True if all children in a subtree should be retrieved, false if only a single /// level of children should be retrieved. This parameter should only be true for /// the NavigationCmdletProvider derived class. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user access to the provider objects using /// the get-childitem cmdlets. @@ -684,18 +597,15 @@ protected virtual void GetChildItems( /// Gives the provider an opportunity to attach additional parameters to /// the get-childitem cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// True if all children in a subtree should be retrieved, false if only a single /// level of children should be retrieved. This parameter should only be true for /// the NavigationCmdletProvider derived class. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -709,26 +619,21 @@ protected virtual object GetChildItemsDynamicParameters(string path, bool recurs { return null; } - } // GetChildItemsDynamicParameters - + } /// /// Gets names of the children of the specified path. /// - /// /// /// The path to the item from which to retrieve the child names. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// Nothing is returned, but all objects should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user access to the provider objects using /// the get-childitem -name cmdlet. @@ -765,7 +670,6 @@ protected virtual void GetChildNames( /// Gets a new provider-specific path and filter (if any) that corresponds to the given /// path. /// - /// /// /// The path to the item. Unlike most other provider APIs, this path is likely to /// contain PowerShell wildcards. @@ -779,11 +683,9 @@ protected virtual void GetChildNames( /// /// The new filter. /// - /// /// /// True if the path or filter were altered. False otherwise. /// - /// /// /// Providers override this method if they support a native filtering syntax that /// can offer performance improvements over wildcard matching done by the PowerShell @@ -819,12 +721,10 @@ protected virtual bool ConvertPath( /// Gives the provider an opportunity to attach additional parameters to /// the get-childitem -name cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -838,26 +738,21 @@ protected virtual object GetChildNamesDynamicParameters(string path) { return null; } - } // GetChildNamesDynamicParameters - + } /// /// Renames the item at the specified path to the new name provided. /// - /// /// /// The path to the item to rename. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// Nothing is returned, but the renamed items should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to rename provider objects using /// the rename-item cmdlet. @@ -892,17 +787,14 @@ protected virtual void RenameItem( /// Gives the provider an opportunity to attach additional parameters to /// the rename-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name to which the item should be renamed. This name should always be /// relative to the parent container. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -916,29 +808,24 @@ protected virtual object RenameItemDynamicParameters(string path, string newName { return null; } - } // RenameItemDynamicParameters + } /// /// Creates a new item at the specified path. /// - /// /// /// The path to the item to create. /// - /// /// /// The provider defined type for the object to create. /// - /// /// /// This is a provider specific type that the provider can use to create a new /// instance of an item at the specified path. /// - /// /// /// Nothing is returned, but the renamed items should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to create new provider objects using /// the new-item cmdlet. @@ -953,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 . @@ -975,21 +862,17 @@ protected virtual void NewItem( /// Gives the provider an opportunity to attach additional parameters to /// the new-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The provider defined type of the item to create. /// - /// /// /// This is a provider specific type that the provider can use to create a new /// instance of an item at the specified path. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -1006,26 +889,22 @@ protected virtual object NewItemDynamicParameters( { return null; } - } // NewItemDynamicParameters + } /// - /// Removes (deletes) the item at the specified path + /// Removes (deletes) the item at the specified path. /// - /// /// /// The path to the item to remove. /// - /// /// /// True if all children in a subtree should be removed, false if only a single /// level of children should be removed. This parameter should only be true for /// NavigationCmdletProvider and its derived classes. /// - /// /// /// Nothing should be returned or written from this method. /// - /// /// /// Providers override this method to allow the user the ability to remove provider objects using /// the remove-item cmdlet. @@ -1060,18 +939,15 @@ protected virtual void RemoveItem( /// Gives the provider an opportunity to attach additional parameters to /// the remove-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// True if all children in a subtree should be removed, false if only a single /// level of children should be removed. This parameter should only be true for /// NavigationCmdletProvider and its derived classes. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -1087,24 +963,20 @@ protected virtual object RemoveItemDynamicParameters( { return null; } - } // RemoveItemDynamicParameters + } /// /// Determines if the item at the specified path has children. /// - /// /// /// The path to the item to see if it has children. /// - /// /// /// True if the item has children, false otherwise. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the provider infrastructure the ability to determine /// if a particular provider object has children without having to retrieve all the child items. @@ -1126,28 +998,21 @@ 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. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// - /// /// /// Nothing is returned, but all the objects that were copied should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to copy provider objects using /// the copy-item cmdlet. @@ -1186,20 +1051,16 @@ protected virtual void CopyItem( /// Gives the provider an opportunity to attach additional parameters to /// the copy-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The path of the item to copy to. /// - /// /// /// Tells the provider to recurse sub-containers when copying. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -1216,11 +1077,10 @@ protected virtual object CopyItemDynamicParameters( { return null; } - } // CopyItemDynamicParameters + } #endregion Protected members - } // ContainerCmdletProvider + } #endregion ContainerCmdletProvider -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/CoreCommandContext.cs b/src/System.Management.Automation/namespaces/CoreCommandContext.cs index 3ecbd9c5163..cf8c43c4b3e 100644 --- a/src/System.Management.Automation/namespaces/CoreCommandContext.cs +++ b/src/System.Management.Automation/namespaces/CoreCommandContext.cs @@ -1,8 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -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."); @@ -44,20 +44,17 @@ internal sealed class CmdletProviderContext /// Constructs the context under which the core command providers /// operate. /// - /// /// /// The context of the engine. /// - /// /// /// If is null. /// - /// internal CmdletProviderContext(ExecutionContext executionContext) { if (executionContext == null) { - throw PSTraceSource.NewArgumentNullException("executionContext"); + throw PSTraceSource.NewArgumentNullException(nameof(executionContext)); } ExecutionContext = executionContext; @@ -68,61 +65,51 @@ internal CmdletProviderContext(ExecutionContext executionContext) { _command = (Cmdlet)executionContext.CurrentCommandProcessor.Command; } - } // CmdletProviderContext constructor + } /// /// Constructs the context under which the core command providers /// operate. /// - /// /// /// The context of the engine. /// - /// /// /// The origin of the caller of this API /// - /// /// /// If is null. /// - /// internal CmdletProviderContext(ExecutionContext executionContext, CommandOrigin origin) { if (executionContext == null) { - throw PSTraceSource.NewArgumentNullException("executionContext"); + throw PSTraceSource.NewArgumentNullException(nameof(executionContext)); } ExecutionContext = executionContext; Origin = origin; - } // CmdletProviderContext constructor + } /// /// Constructs the context under which the core command providers /// operate. /// - /// /// /// The command object that is running. /// - /// /// /// The credentials the core command provider should use. /// - /// /// /// The drive under which this context should operate. /// - /// /// /// If is null. /// - /// /// /// If contains a null Host or Context reference. /// - /// internal CmdletProviderContext( PSCmdlet command, PSCredential credentials, @@ -131,7 +118,7 @@ internal CmdletProviderContext( // verify the command parameter if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } _command = command; @@ -153,35 +140,31 @@ internal CmdletProviderContext( { throw PSTraceSource.NewArgumentException("command.Context"); } + ExecutionContext = command.Context; // Stream will default to true because command methods will be used. PassThru = true; _streamErrors = true; - } // CmdletProviderContext constructor + } /// /// Constructs the context under which the core command providers /// operate. /// - /// /// /// The command object that is running. /// - /// /// /// The credentials the core command provider should use. /// - /// /// /// If is null. /// - /// /// /// If contains a null Host or Context reference. /// - /// internal CmdletProviderContext( PSCmdlet command, PSCredential credentials) @@ -189,7 +172,7 @@ internal CmdletProviderContext( // verify the command parameter if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } _command = command; @@ -209,38 +192,35 @@ internal CmdletProviderContext( { throw PSTraceSource.NewArgumentException("command.Context"); } + ExecutionContext = command.Context; // Stream will default to true because command methods will be used. PassThru = true; _streamErrors = true; - } // CmdletProviderContext constructor + } /// /// Constructs the context under which the core command providers /// operate. /// - /// /// /// The command object that is running. /// - /// /// /// If is null. /// - /// /// /// If contains a null Host or Context reference. /// - /// internal CmdletProviderContext( Cmdlet command) { // verify the command parameter if (command == null) { - throw PSTraceSource.NewArgumentNullException("command"); + throw PSTraceSource.NewArgumentNullException(nameof(command)); } _command = command; @@ -250,36 +230,34 @@ internal CmdletProviderContext( { throw PSTraceSource.NewArgumentException("command.Context"); } + ExecutionContext = command.Context; // Stream will default to true because command methods will be used. PassThru = true; _streamErrors = true; - } // CmdletProviderContext constructor + } /// /// Constructs the context under which the core command providers /// operate using an existing context. /// - /// /// /// A CmdletProviderContext instance to copy the filters, ExecutionContext, /// Credentials, Drive, and Force options from. /// - /// /// /// If is null. /// - /// - internal CmdletProviderContext( CmdletProviderContext contextToCopyFrom) { if (contextToCopyFrom == null) { - throw PSTraceSource.NewArgumentNullException("contextToCopyFrom"); + throw PSTraceSource.NewArgumentNullException(nameof(contextToCopyFrom)); } + ExecutionContext = contextToCopyFrom.ExecutionContext; _command = contextToCopyFrom._command; @@ -306,7 +284,7 @@ internal CmdletProviderContext( contextToCopyFrom.StopReferrals.Add(this); _copiedContext = contextToCopyFrom; - } // CmdletProviderContext constructor + } #endregion Constructor @@ -316,34 +294,31 @@ 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 /// should try to perform an operation. /// - /// private bool _force; - /// /// The command which defines the context. This should not be /// 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 + /// This makes the origin of the provider request visible to the internals. /// internal CommandOrigin Origin { get; } = CommandOrigin.Internal; - /// /// This defines the default behavior for the WriteError method. /// If it is true, a call to this method will result in an immediate call @@ -352,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(); @@ -376,38 +351,34 @@ internal CmdletProviderContext( #region Internal properties /// - /// Gets the execution context of the engine + /// Gets the execution context of the engine. /// - /// internal ExecutionContext ExecutionContext { get; } /// /// Gets or sets the provider instance for the current /// execution context. /// - /// internal System.Management.Automation.Provider.CmdletProvider ProviderInstance { get { return _providerInstance; - } // get + } set { _providerInstance = value; - } // set - } // ProviderInstance + } + } /// /// Copies the include, exclude, and provider filters from /// the specified context to this context. /// - /// /// /// The context to copy the filters from. /// - /// private void CopyFilters(CmdletProviderContext context) { Dbg.Diagnostics.Assert( @@ -417,27 +388,21 @@ private void CopyFilters(CmdletProviderContext context) Include = context.Include; Exclude = context.Exclude; Filter = context.Filter; - } // CopyFilters - - internal void RemoveStopReferral() - { - if (_copiedContext != null) - { - _copiedContext.StopReferrals.Remove(this); - } } + + internal void RemoveStopReferral() => _copiedContext?.StopReferrals.Remove(this); + #endregion Internal properties #region Public properties /// - /// Gets or sets the dynamic parameters for the context + /// Gets or sets the dynamic parameters for the context. /// - /// internal object DynamicParameters { get; set; } /// - /// Returns MyInvocation from the underlying cmdlet + /// Returns MyInvocation from the underlying cmdlet. /// internal InvocationInfo MyInvocation { @@ -458,17 +423,14 @@ internal InvocationInfo MyInvocation /// Determines if the Write* calls should be passed through to the command /// instance if there is one. The default value is true. /// - /// internal bool PassThru { get; set; } /// /// The drive associated with this context. /// - /// /// /// If is null on set. /// - /// internal PSDriveInfo Drive { get; set; } /// @@ -489,7 +451,7 @@ internal PSCredential Credential return result; } - } // Credential + } #region Transaction Support @@ -529,7 +491,7 @@ public bool TransactionAvailable() /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// public PSTransactionContext CurrentPSTransaction { @@ -545,14 +507,13 @@ public PSTransactionContext CurrentPSTransaction } #endregion Transaction Support - /// /// Gets or sets the Force property that is passed to providers. /// - /// internal SwitchParameter Force { get { return _force; } + set { _force = value; } } @@ -560,21 +521,18 @@ internal SwitchParameter Force /// The provider specific filter that should be used when determining /// which items an action should take place on. /// - /// internal string Filter { get; set; } /// /// A glob string that signifies which items should be included when determining /// which items the action should occur on. /// - /// internal Collection Include { get; private set; } /// /// A glob string that signifies which items should be excluded when determining /// which items the action should occur on. /// - /// internal Collection Exclude { get; private set; } /// @@ -583,18 +541,17 @@ internal SwitchParameter Force /// expansion. This is set when the user specifies the /// -LiteralPath parameter to one of the core commands. /// - /// public bool SuppressWildcardExpansion { get; internal set; } #region User feedback mechanisms /// - /// Confirm the operation with the user + /// Confirm the operation with the user. /// /// /// 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 @@ -611,16 +568,16 @@ internal bool ShouldProcess( } return result; - } // ShouldProcess + } /// - /// Confirm the operation with the user + /// Confirm the operation with the user. /// /// /// Name of the target resource being acted upon /// - /// What action was being performed - /// true iff the action should be performed + /// What action was being 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 @@ -638,10 +595,10 @@ internal bool ShouldProcess( } return result; - } // ShouldProcess + } /// - /// Confirm the operation with the user + /// Confirm the operation with the user. /// /// /// This should contain a textual description of the action to be @@ -659,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 @@ -681,10 +638,10 @@ internal bool ShouldProcess( } return result; - } // ShouldProcess + } /// - /// Confirm the operation with the user + /// Confirm the operation with the user. /// /// /// This should contain a textual description of the action to be @@ -708,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 @@ -736,26 +693,22 @@ internal bool ShouldProcess( } return result; - } // ShouldProcess + } /// - /// Ask the user whether to continue/stop or break to a subshell + /// Ask the user whether to continue/stop or break to a subshell. /// - /// /// /// Message to display to the user. This routine will append /// the text "Continue" to ensure that people know what question /// they are answering. /// - /// /// /// Dialog caption if the host uses a dialog. /// - /// /// /// True if the user wants to continue, false if not. /// - /// internal bool ShouldContinue( string query, string caption) @@ -767,34 +720,28 @@ internal bool ShouldContinue( } return result; - } // ShouldContinue + } /// - /// Ask the user whether to continue/stop or break to a subshell + /// Ask the user whether to continue/stop or break to a subshell. /// - /// /// /// Message to display to the user. This routine will append /// the text "Continue" to ensure that people know what question /// they are answering. /// - /// /// /// Dialog caption if the host uses a dialog. /// - /// /// /// Indicates whether the user selected YesToAll /// - /// /// /// Indicates whether the user selected NoToAll /// - /// /// /// True if the user wants to continue, false if not. /// - /// internal bool ShouldContinue( string query, string caption, @@ -814,79 +761,37 @@ internal bool ShouldContinue( } return result; - } // ShouldContinue + } /// /// Writes the object to the Verbose pipe. /// - /// /// /// The string that needs to be written. /// - /// - internal void WriteVerbose(string text) - { - if (_command != null) - { - _command.WriteVerbose(text); - } - } // WriteVerbose + internal void WriteVerbose(string text) => _command?.WriteVerbose(text); /// /// Writes the object to the Warning pipe. /// - /// /// /// The string that needs to be written. /// - /// - internal void WriteWarning(string text) - { - if (_command != null) - { - _command.WriteWarning(text); - } - } // WriteWarning + internal void WriteWarning(string text) => _command?.WriteWarning(text); - internal void WriteProgress(ProgressRecord record) - { - if (_command != null) - { - _command.WriteProgress(record); - } - } // WriteProgress + internal void WriteProgress(ProgressRecord record) => _command?.WriteProgress(record); /// /// Writes a debug string. /// - /// /// /// The String that needs to be written. /// - /// - internal void WriteDebug(string text) - { - if (_command != null) - { - _command.WriteDebug(text); - } - } // WriteDebug + internal void WriteDebug(string text) => _command?.WriteDebug(text); - internal void WriteInformation(InformationRecord record) - { - if (_command != null) - { - _command.WriteInformation(record); - } - } // WriteInformation + internal void WriteInformation(InformationRecord record) => _command?.WriteInformation(record); - internal void WriteInformation(Object messageData, string[] tags) - { - if (_command != null) - { - _command.WriteInformation(messageData, tags); - } - } // WriteInformation + internal void WriteInformation(object messageData, string[] tags) => _command?.WriteInformation(messageData, tags); #endregion User feedback mechanisms @@ -897,38 +802,32 @@ internal void WriteInformation(Object messageData, string[] tags) /// /// Sets the filters that are used within this context. /// - /// /// /// The include filters which determines which items are included in /// operations within this context. /// - /// /// /// The exclude filters which determines which items are excluded from /// operations within this context. /// - /// /// /// The provider specific filter for the operation. /// - /// internal void SetFilters(Collection include, Collection exclude, string filter) { Include = include; Exclude = exclude; Filter = filter; - } // SetFilters + } /// /// Gets an array of the objects that have been accumulated /// and the clears the collection. /// - /// /// /// An object array of the objects that have been accumulated /// through the WriteObject method. /// - /// internal Collection GetAccumulatedObjects() { // Get the contents as an array @@ -939,18 +838,16 @@ internal Collection GetAccumulatedObjects() // Return the array return results; - } // GetAccumulatedObjects + } /// /// Gets an array of the error objects that have been accumulated /// and the clears the collection. /// - /// /// /// An object array of the objects that have been accumulated /// through the WriteError method. /// - /// internal Collection GetAccumulatedErrorObjects() { // Get the contents as an array @@ -961,17 +858,15 @@ internal Collection GetAccumulatedErrorObjects() // Return the array return results; - } // GetAccumulatedErrorObjects + } /// /// If there are any errors accumulated, the first error is thrown. /// - /// /// /// If a CmdletProvider wrote any exceptions to the error pipeline, it is /// wrapped and then thrown. /// - /// internal void ThrowFirstErrorOrDoNothing() { ThrowFirstErrorOrDoNothing(true); @@ -980,24 +875,20 @@ internal void ThrowFirstErrorOrDoNothing() /// /// If there are any errors accumulated, the first error is thrown. /// - /// /// /// If true, the error will be wrapped in a ProviderInvocationException before /// being thrown. If false, the error will be thrown as is. /// - /// /// /// If is true, the /// first exception that was written to the error pipeline by a CmdletProvider /// is wrapped and thrown. /// - /// /// /// If is false, /// the first exception that was written to the error pipeline by a CmdletProvider /// is thrown. /// - /// internal void ThrowFirstErrorOrDoNothing(bool wrapExceptionInProviderException) { if (HasErrors()) @@ -1040,22 +931,19 @@ internal void ThrowFirstErrorOrDoNothing(bool wrapExceptionInProviderException) } /// - /// Writes all the accumulated errors to the specified context using WriteError + /// Writes all the accumulated errors to the specified context using WriteError. /// - /// /// /// The context to write the errors to. /// - /// /// /// If is null. /// - /// internal void WriteErrorsToContext(CmdletProviderContext errorContext) { if (errorContext == null) { - throw PSTraceSource.NewArgumentNullException("errorContext"); + throw PSTraceSource.NewArgumentNullException(nameof(errorContext)); } if (HasErrors()) @@ -1070,11 +958,9 @@ internal void WriteErrorsToContext(CmdletProviderContext errorContext) /// /// Writes an object to the output. /// - /// /// /// The object to be written. /// - /// /// /// If streaming is on and the writeObjectHandler was specified then the object /// gets written to the writeObjectHandler. If streaming is on and the writeObjectHandler @@ -1083,17 +969,14 @@ internal void WriteErrorsToContext(CmdletProviderContext errorContext) /// If streaming is off the object gets written to an accumulator collection. The collection /// of written object can be retrieved using the AccumulatedObjects method. /// - /// /// /// The CmdletProvider could not stream the results because no /// cmdlet was specified to stream the output through. /// - /// /// /// If the pipeline has been signaled for stopping but /// the provider calls this method. /// - /// internal void WriteObject(object obj) { // Making sure to obey the StopProcessing by @@ -1143,27 +1026,23 @@ internal void WriteObject(object obj) _accumulatedObjects.Add(newObj); } - } // WriteObject + } /// /// Writes the error to the pipeline or accumulates the error in an internal /// buffer. /// - /// /// /// The error record to write to the pipeline or the internal buffer. /// - /// /// /// The CmdletProvider could not stream the error because no /// cmdlet was specified to stream the output through. /// - /// /// /// If the pipeline has been signaled for stopping but /// the provider calls this method. /// - /// internal void WriteError(ErrorRecord errorRecord) { // Making sure to obey the StopProcessing by @@ -1199,8 +1078,8 @@ internal void WriteError(ErrorRecord errorRecord) // Since we are not streaming, just add the object to the accumulatedErrorObjects _accumulatedErrorObjects.Add(errorRecord); - if (null != errorRecord.ErrorDetails - && null != errorRecord.ErrorDetails.TextLookupError) + if (errorRecord.ErrorDetails != null + && errorRecord.ErrorDetails.TextLookupError != null) { Exception textLookupError = errorRecord.ErrorDetails.TextLookupError; errorRecord.ErrorDetails.TextLookupError = null; @@ -1211,40 +1090,33 @@ internal void WriteError(ErrorRecord errorRecord) Severity.Warning); } } - } // WriteError + } /// /// If the error pipeline hasn't been supplied a delegate or a command then this method /// will determine if any errors have accumulated. /// - /// /// /// True if the errors are being accumulated and some errors have been accumulated. False otherwise. /// - /// internal bool HasErrors() { return _accumulatedErrorObjects != null && _accumulatedErrorObjects.Count > 0; - } // HasErrors + } /// /// Call this on a separate thread when a provider is using /// this context to do work. This method will call the StopProcessing /// method of the provider. /// - /// 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 @@ -1252,7 +1124,7 @@ internal void StopProcessing() { referralContext.StopProcessing(); } - } // StopProcessing + } internal bool Stopping { get; private set; } @@ -1260,7 +1132,6 @@ internal void StopProcessing() /// The list of contexts to which the StopProcessing calls /// should be referred. /// - /// internal Collection StopReferrals { get; } = new Collection(); internal bool HasIncludeOrExclude @@ -1273,7 +1144,5 @@ internal bool HasIncludeOrExclude } #endregion Public methods - } // CmdletProviderContext + } } - - diff --git a/src/System.Management.Automation/namespaces/DriveProviderBase.cs b/src/System.Management.Automation/namespaces/DriveProviderBase.cs index 323bcaeddf7..2fc44e50bdc 100644 --- a/src/System.Management.Automation/namespaces/DriveProviderBase.cs +++ b/src/System.Management.Automation/namespaces/DriveProviderBase.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; -using Dbg = System.Management.Automation; using System.Management.Automation.Internal; namespace System.Management.Automation.Provider @@ -11,9 +9,8 @@ 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 /// cases one should derive from , @@ -31,19 +28,15 @@ public abstract class DriveCmdletProvider : CmdletProvider /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The PSDriveInfo object the represents the drive to be mounted. /// - /// /// /// The context under which this method is being called. /// - /// /// /// The drive that was returned from the protected NewDrive method. /// - /// internal PSDriveInfo NewDrive(PSDriveInfo drive, CmdletProviderContext context) { Context = context; @@ -60,73 +53,63 @@ internal PSDriveInfo NewDrive(PSDriveInfo drive, CmdletProviderContext context) } return NewDrive(drive); - } // NewDrive + } /// /// Gives the provider to attach additional parameters to /// the New-PSDrive cmdlet. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object NewDriveDynamicParameters(CmdletProviderContext context) { Context = context; return NewDriveDynamicParameters(); - } // NewDriveDynamicParameters + } /// /// Internal wrapper for the RemoveDrive protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The PSDriveInfo object the represents the mounted drive. /// - /// /// /// The context under which this method is being called. /// - /// /// /// The drive that was returned from the protected RemoveDrive method. /// - /// internal PSDriveInfo RemoveDrive(PSDriveInfo drive, CmdletProviderContext context) { Context = context; return RemoveDrive(drive); - } // RemoveDrive + } /// /// Internal wrapper for the InitializeDefaultDrives protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An array of drives returned from the protected InitializeDefaultDrives method. /// - /// internal Collection InitializeDefaultDrives(CmdletProviderContext context) { Context = context; Context.Drive = null; return InitializeDefaultDrives(); - } // InitializeDefaultDrives + } #endregion DriveCmdletProvider method wrappers @@ -141,11 +124,9 @@ internal Collection InitializeDefaultDrives(CmdletProviderContext c /// reliability reasons or to provide extra data to all calls using /// the Drive. /// - /// /// /// The proposed new drive. /// - /// /// /// The new drive that is to be added to the MSH namespace. This /// can either be the same object that @@ -153,7 +134,6 @@ internal Collection InitializeDefaultDrives(CmdletProviderContext c /// /// The default implementation returns the drive that was passed. /// - /// /// /// This method gives the provider an opportunity to associate /// provider specific data with a drive. This is done by deriving @@ -173,14 +153,13 @@ protected virtual PSDriveInfo NewDrive(PSDriveInfo drive) using (PSTransactionManager.GetEngineProtectionScope()) { return drive; - } // TraceMethod - } // NewDrive + } + } /// /// Gives the provider an opportunity to attach additional parameters to /// the New-PSDrive cmdlet. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -194,17 +173,15 @@ protected virtual object NewDriveDynamicParameters() { return null; } - } // NewDriveDynamicParameters + } /// /// Gives the provider an opportunity to clean up any provider specific data /// for the drive that is going to be removed. /// - /// /// /// The Drive object the represents the mounted drive. /// - /// /// /// If the drive can be removed it should return the drive that was passed /// in. If the drive cannot be removed, null should be returned or an exception @@ -212,7 +189,6 @@ protected virtual object NewDriveDynamicParameters() /// /// The default implementation returns the drive that was passed. /// - /// /// /// A provider should override this method to free any resources that may be associated with /// the drive being removed. @@ -222,20 +198,17 @@ protected virtual PSDriveInfo RemoveDrive(PSDriveInfo drive) using (PSTransactionManager.GetEngineProtectionScope()) { return drive; - } // TraceMethod - } // RemoveDrive - + } + } /// /// Gives the provider the ability to map drives after initialization. /// - /// /// /// A collection of the drives the provider wants to be added to the session upon initialization. /// /// The default implementation returns an empty collection. /// - /// /// /// After the Start method is called on a provider, the InitializeDefaultDrives /// method is called. This is an opportunity for the provider to @@ -255,13 +228,11 @@ protected virtual Collection InitializeDefaultDrives() using (PSTransactionManager.GetEngineProtectionScope()) { return new Collection(); - } // TraceMethod - } // InitializeDefaultDrives + } + } #endregion Public methods - } // DriveCmdletProvider + } #endregion DriveCmdletProvider -} // namespace System.Management.Automation - - +} diff --git a/src/System.Management.Automation/namespaces/EnvironmentProvider.cs b/src/System.Management.Automation/namespaces/EnvironmentProvider.cs index 5f4dcf71db8..1ea71d1b1ed 100644 --- a/src/System.Management.Automation/namespaces/EnvironmentProvider.cs +++ b/src/System.Management.Automation/namespaces/EnvironmentProvider.cs @@ -1,16 +1,15 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using Dbg = System.Management.Automation; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Provider; +using Dbg = System.Management.Automation; + namespace Microsoft.PowerShell.Commands { /// @@ -22,7 +21,7 @@ namespace Microsoft.PowerShell.Commands public sealed class EnvironmentProvider : SessionStateProviderBase { /// - /// Gets the name of the provider + /// Gets the name of the provider. /// public const string ProviderName = "Environment"; @@ -34,20 +33,18 @@ public sealed class EnvironmentProvider : SessionStateProviderBase /// public EnvironmentProvider() { - } // constructor + } #endregion Constructor #region DriveCmdletProvider overrides /// - /// Initializes the alias drive + /// Initializes the alias drive. /// - /// /// /// An array of a single PSDriveInfo object representing the alias drive. /// - /// protected override Collection InitializeDefaultDrives() { string description = SessionStateStrings.EnvironmentDriveDescription; @@ -56,36 +53,32 @@ protected override Collection InitializeDefaultDrives() new PSDriveInfo( DriveNames.EnvironmentDrive, ProviderInfo, - String.Empty, + string.Empty, description, null); Collection drives = new Collection(); drives.Add(envDrive); return drives; - } // InitializeDefaultDrives + } #endregion DriveCmdletProvider overrides #region protected members - /// - /// Gets a environment variable from session state + /// Gets a environment variable from session state. /// - /// /// /// The name of the environment variable to retrieve. /// - /// /// /// A DictionaryEntry that represents the value of the environment variable. /// - /// internal override object GetSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); object result = null; @@ -96,29 +89,26 @@ internal override object GetSessionStateItem(string name) { result = new DictionaryEntry(name, value); } + return result; - } // GetSessionStateItem + } /// - /// Sets the environment variable of the specified name to the specified value + /// Sets the environment variable of the specified name to the specified value. /// - /// /// /// The name of the environment variable to set. /// - /// /// /// The new value for the environment variable. /// - /// /// /// If true, the item that was set should be written to WriteItemObject. /// - /// internal override void SetSessionStateItem(string name, object value, bool writeItem) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); if (value == null) @@ -144,6 +134,7 @@ internal override void SetSessionStateItem(string name, object value, bool write PSObject wrappedObject = PSObject.AsPSObject(value); stringValue = wrappedObject.ToString(); } + Environment.SetEnvironmentVariable(name, stringValue); DictionaryEntry item = new DictionaryEntry(name, stringValue); @@ -153,34 +144,30 @@ internal override void SetSessionStateItem(string name, object value, bool write WriteItemObject(item, name, false); } } - } // SetSessionStateItem + } /// /// Removes the specified environment variable from session state. /// - /// /// /// The name of the environment variable to remove from session state. /// - /// internal override void RemoveSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); Environment.SetEnvironmentVariable(name, null); - } // RemoveSessionStateItem + } /// - /// Gets a flattened view of the environment variables in session state + /// Gets a flattened view of the environment variables in session state. /// - /// /// /// An IDictionary representing the flattened view of the environment variables in /// session state. /// - /// internal override IDictionary GetSessionStateTable() { // Environment variables are case-sensitive on Unix and @@ -201,24 +188,44 @@ internal override IDictionary GetSessionStateTable() IDictionary environmentTable = Environment.GetEnvironmentVariables(); foreach (DictionaryEntry entry in environmentTable) { - providerTable.Add((string)entry.Key, entry); + if (!providerTable.TryAdd((string)entry.Key, entry)) + { // Windows only: duplicate key (variable name that differs only in case) + // NOTE: Even though this shouldn't happen, it can, e.g. when npm + // creates duplicate environment variables that differ only in case - + // see https://github.com/PowerShell/PowerShell/issues/6305. + // However, because retrieval *by name* later is invariably + // case-INsensitive, in effect only a *single* variable exists. + // We simply ask Environment.GetEnvironmentVariable() for the effective value + // and use that as the only entry, because for a given key 'foo' (and all its case variations), + // that is guaranteed to match what $env:FOO and [environment]::GetEnvironmentVariable('foo') return. + // (If, by contrast, we just used `entry` as-is every time a duplicate is encountered, + // it could - intermittently - represent a value *other* than the effective one.) + string effectiveValue = Environment.GetEnvironmentVariable((string)entry.Key); + if (((string)entry.Value).Equals(effectiveValue, StringComparison.Ordinal)) + { // We've found the effective definition. + // Note: We *recreate* the entry so that the specific name casing of the + // effective definition is also reflected. However, if the case variants + // define the same value, it is unspecified which name variant is reflected + // in Get-Item env: output; given the always case-insensitive nature of the retrieval, + // that shouldn't matter. + providerTable.Remove((string)entry.Key); + providerTable.Add((string)entry.Key, entry); + } + } } return providerTable; - } // GetSessionStateTable + } /// - /// Gets the Value property of the DictionaryEntry item + /// Gets the Value property of the DictionaryEntry item. /// - /// /// /// The item to get the value from. /// - /// /// /// The value of the item. /// - /// internal override object GetValueOfItem(object item) { Dbg.Diagnostics.Assert( @@ -231,10 +238,10 @@ internal override object GetValueOfItem(object item) { value = ((DictionaryEntry)item).Value; } + return value; - } // GetValueOfItem + } #endregion protected members - } // EnvironmentProvider + } } - diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index 7402e52bf9b..d0f8f08deab 100644 --- a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs +++ b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs @@ -1,18 +1,19 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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.Runtime.InteropServices; -using System.Text; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Provider; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; + using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands @@ -21,13 +22,11 @@ namespace Microsoft.PowerShell.Commands /// The content stream class for the file system provider. It implements both /// the IContentReader and IContentWriter interfaces. /// - /// /// /// Note, this class does no specific error handling. All errors are allowed to /// propagate to the caller so that they can be written to the error pipeline /// if necessary. /// - /// internal class FileSystemContentReaderWriter : IContentReader, IContentWriter { #region tracer @@ -36,31 +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 string _delimiter = "\n"; - private bool _usingDelimiter; + private readonly bool _usingByteEncoding; + + private const char DefaultDelimiter = '\n'; + + 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; @@ -71,111 +75,106 @@ 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 + /// Constructor for the content stream. /// - /// /// /// The path to the file to get the content from. /// - /// /// /// The file mode to open the file with. /// - /// /// /// The file access requested in the file. /// - /// /// /// The file share to open the file with /// - /// /// /// The encoding of the file to be read or written. /// - /// /// /// If true, bytes will be read from the file. If false, the specified encoding /// will be used to read the file. /// - /// /// /// If true, we will perform blocking reads on the file, waiting for new content to be appended /// - /// /// /// The CmdletProvider invoking this stream /// - /// /// /// 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) { } /// - /// Constructor for the content stream + /// Constructor for the content stream. /// - /// /// /// The path to the file to get the content from. /// - /// /// /// The name of the Alternate Data Stream to get the content from. If null or empty, returns /// the file's primary content. /// - /// /// /// The file mode to open the file with. /// - /// /// /// The file access requested in the file. /// - /// /// /// The file share to open the file with /// - /// /// /// The encoding of the file to be read or written. /// - /// /// /// If true, bytes will be read from the file. If false, the specified encoding /// will be used to read the file. /// - /// /// /// If true, we will perform blocking reads on the file, waiting for new content to be appended /// - /// /// /// The CmdletProvider invoking this stream /// - /// /// /// Indicates raw stream. /// - /// public FileSystemContentReaderWriter( string path, string streamName, FileMode mode, FileAccess access, FileShare share, Encoding encoding, bool usingByteEncoding, bool waitForChanges, CmdletProvider provider, bool isRawStream) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (s_tracer.IsEnabled) @@ -200,55 +199,43 @@ public FileSystemContentReaderWriter( } /// - /// Constructor for the content stream + /// Constructor for the content stream. /// - /// /// /// The path to the file to get the content from. /// - /// /// /// The name of the Alternate Data Stream to get the content from. If null or empty, returns /// the file's primary content. /// - /// /// /// The file mode to open the file with. /// - /// /// /// The file access requested in the file. /// - /// /// /// The file share to open the file with /// - /// /// /// The encoding of the file to be read or written. /// - /// /// /// If true, bytes will be read from the file. If false, the specified encoding /// will be used to read the file. /// - /// /// /// If true, we will perform blocking reads on the file, waiting for new content to be appended /// - /// /// /// The CmdletProvider invoking this stream /// - /// /// /// Indicates raw stream. /// - /// /// /// False to add a newline to the end of the output string, true if not. /// - /// public FileSystemContentReaderWriter( string path, string streamName, FileMode mode, FileAccess access, FileShare share, Encoding encoding, bool usingByteEncoding, bool waitForChanges, CmdletProvider provider, @@ -259,105 +246,40 @@ public FileSystemContentReaderWriter( } /// - /// Constructor for the content stream - /// - /// - /// - /// The path to the file to get the content from. - /// - /// - /// - /// The file mode to open the file with. - /// - /// - /// - /// The file access requested in the file. - /// - /// - /// - /// The file share to open the file with - /// - /// - /// - /// The delimiter to use when reading strings. Each time read is called, all contents up to an including - /// the delimiter is read. - /// - /// - /// - /// The encoding of the file to be read or written. - /// - /// - /// - /// If true, we will perform blocking reads on the file, waiting for new content to be appended - /// - /// - /// - /// The CmdletProvider invoking this stream - /// - /// - /// - /// Indicates raw stream. - /// - /// - public FileSystemContentReaderWriter( - string path, - FileMode mode, - FileAccess access, - FileShare share, - string delimiter, - Encoding encoding, - bool waitForChanges, - CmdletProvider provider, - bool isRawStream) : this(path, null, mode, access, share, encoding, false, waitForChanges, provider, isRawStream) - { - } - - /// - /// Constructor for the content stream + /// Constructor for the content stream. /// - /// /// /// The path to the file to get the content from. /// - /// /// /// The name of the Alternate Data Stream to get the content from. If null or empty, returns /// the file's primary content. /// - /// /// /// The file mode to open the file with. /// - /// /// /// The file access requested in the file. /// - /// /// /// The file share to open the file with /// - /// /// /// The delimiter to use when reading strings. Each time read is called, all contents up to an including /// the delimiter is read. /// - /// /// /// The encoding of the file to be read or written. /// - /// /// /// If true, we will perform blocking reads on the file, waiting for new content to be appended /// - /// /// /// The CmdletProvider invoking this stream /// - /// /// /// Indicates raw stream. /// - /// public FileSystemContentReaderWriter( string path, string streamName, @@ -371,26 +293,64 @@ public FileSystemContentReaderWriter( bool isRawStream) : this(path, streamName, mode, access, share, encoding, false, waitForChanges, provider, isRawStream) { - _delimiter = delimiter; - _usingDelimiter = true; - } + // If the delimiter is default ('\n') we'll use ReadLine() method. + // Otherwise allocate temporary structures for ReadDelimited() method. + if (!(delimiter.Length == 1 && delimiter[0] == DefaultDelimiter)) + { + _delimiter = delimiter; + _usingDelimiter = true; + + // We expect that we are parsing files where line lengths can be relatively long. + const int DefaultLineLength = 256; + _currentLineContent = new StringBuilder(DefaultLineLength); + + // For Boyer-Moore string search algorithm. + // Populate the offset lookups. + // These will tell us the maximum number of characters + // we can read to generate another possible match (safe shift). + // If we read more characters than this, we risk consuming + // more of the stream than we need. + // + // Because an unicode character size is 2 byte we would to have use + // very large array with 65535 size to keep this safe offsets. + // One solution is to pack unicode character to byte. + // The workaround is to use low byte from unicode character. + // This allow us to use small array with size 256. + // This workaround is the fastest and provides excellent results + // in regular search scenarios when the file contains + // mostly characters from the same alphabet. + _offsetDictionary = new int[256]; + + // If next char from file is not in search pattern safe shift is the search pattern length. + for (var n = 0; n < _offsetDictionary.Length; n++) + { + _offsetDictionary[n] = _delimiter.Length; + } + // If next char from file is in search pattern we should calculate a safe shift. + char currentChar; + byte lowByte; + for (var i = 0; i < _delimiter.Length; i++) + { + currentChar = _delimiter[i]; + lowByte = Unsafe.As(ref currentChar); + _offsetDictionary[lowByte] = _delimiter.Length - i - 1; + } + } + } /// /// Reads the specified number of characters or a lines from the file. /// - /// /// /// If less than 1, then the entire file is read at once. If 1 or greater, then /// readCount is used to determine how many items (ie: lines, bytes, delimited tokens) /// to read per call. /// - /// /// /// An array of strings representing the character(s) or line(s) read from /// the file. /// - /// public IList Read(long readCount) { if (_isRawStream && _waitForChanges) @@ -402,7 +362,7 @@ public IList Read(long readCount) s_tracer.WriteLine("blocks requested = {0}", readCount); - ArrayList blocks = new ArrayList(); + var blocks = new List(); bool readToEnd = (readCount <= 0); if (_alreadyDetectEncoding && _reader.BaseStream.Position == 0) @@ -425,19 +385,19 @@ public IList Read(long readCount) if (_usingByteEncoding) { - if (!ReadByteEncoded(waitChanges, blocks, false)) + if (!ReadByteEncoded(waitChanges, blocks, readBackward: false)) break; } else { if (_usingDelimiter || _isRawStream) { - if (!ReadDelimited(waitChanges, blocks, false, _delimiter)) + if (!ReadDelimited(waitChanges, blocks, readBackward: false, _delimiter)) break; } else { - if (!ReadByLine(waitChanges, blocks, false)) + if (!ReadByLine(waitChanges, blocks, readBackward: false)) break; } } @@ -453,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; } @@ -465,7 +425,7 @@ public IList Read(long readCount) } /// - /// Read the content regardless of the 'waitForChanges' flag + /// Read the content regardless of the 'waitForChanges' flag. /// /// /// @@ -493,7 +453,7 @@ internal void SeekItemsBackward(int backCount) if (backCount < 0) { // The caller needs to guarantee that 'backCount' is greater or equals to 0 - throw PSTraceSource.NewArgumentException("backCount"); + throw PSTraceSource.NewArgumentException(nameof(backCount)); } if (_isRawStream && _waitForChanges) @@ -503,7 +463,7 @@ internal void SeekItemsBackward(int backCount) s_tracer.WriteLine("blocks seek backwards = {0}", backCount); - ArrayList blocks = new ArrayList(); + var blocks = new List(); if (_reader != null) { // Make the reader automatically detect the encoding @@ -511,6 +471,7 @@ internal void SeekItemsBackward(int backCount) _reader.Peek(); _alreadyDetectEncoding = true; } + Seek(0, SeekOrigin.End); if (backCount == 0) @@ -520,12 +481,17 @@ internal void SeekItemsBackward(int backCount) return; } - StringBuilder builder = new StringBuilder(); - foreach (char character in _delimiter) - { - builder.Insert(0, character); - } - string actualDelimiter = builder.ToString(); + string actualDelimiter = string.Create( + _delimiter.Length, + _delimiter, + (chars, buf) => + { + for (int i = 0, j = buf.Length - 1; i < chars.Length; i++, j--) + { + chars[i] = buf[j]; + } + }); + long currentBlock = 0; string lastDelimiterMatch = null; @@ -544,14 +510,14 @@ internal void SeekItemsBackward(int backCount) { if (_usingByteEncoding) { - if (!ReadByteEncoded(false, blocks, true)) + if (!ReadByteEncoded(waitChanges: false, blocks, readBackward: true)) break; } else { if (_usingDelimiter) { - if (!ReadDelimited(false, blocks, true, actualDelimiter)) + if (!ReadDelimited(waitChanges: false, blocks, readBackward: true, actualDelimiter)) break; // If the delimiter is at the end of the file, we need to read one more // to get to the right position. For example: @@ -564,7 +530,7 @@ internal void SeekItemsBackward(int backCount) } else { - if (!ReadByLine(false, blocks, true)) + if (!ReadByLine(waitChanges: false, blocks, readBackward: true)) break; } } @@ -601,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 @@ -609,7 +575,7 @@ internal void SeekItemsBackward(int backCount) } } - private bool ReadByLine(bool waitChanges, ArrayList blocks, bool readBackward) + private bool ReadByLine(bool waitChanges, List blocks, bool readBackward) { // Reading lines as strings string line = readBackward ? _backReader.ReadLine() : _reader.ReadLine(); @@ -623,136 +589,154 @@ private bool ReadByLine(bool waitChanges, ArrayList blocks, bool readBackward) { WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding); line = _reader.ReadLine(); - } while ((line == null) && (!_provider.Stopping)); + } + while ((line == null) && (!_provider.Stopping)); } } if (line != null) + { blocks.Add(line); + } int peekResult = readBackward ? _backReader.Peek() : _reader.Peek(); if (peekResult == -1) + { return false; + } else + { return true; + } } - private bool ReadDelimited(bool waitChanges, ArrayList blocks, bool readBackward, string actualDelimiter) + private bool ReadDelimited(bool waitChanges, List blocks, bool readBackward, string actualDelimiter) { + if (_isRawStream) + { + // when -Raw is used we want to anyway read the whole thing + // so avoiding the while loop by reading the entire content. + string contentRead = _reader.ReadToEnd(); + if (contentRead.Length > 0) + { + blocks.Add(contentRead); + } + + // We already read whole file so return EOF. + return false; + } + // Since the delimiter is a string, we're essentially // dealing with a "find the substring" algorithm, but with // the additional restriction that we cannot read past the - // end of the delimiter. If we read past the end of the delimiter, + // end of the delimiter. If we read past the end of the delimiter, // then we'll eat up bytes that we need from the filestream. // The solution is a modified Boyer-Moore string search algorithm. // This version retains the sub-linear search performance (via the - // lookup tables,) but offloads much of the dirty work to the - // very efficient BCL String.IndexOf(, StringComparison.CurrentCulture) method. + // lookup tables). int numRead = 0; int currentOffset = actualDelimiter.Length; - StringBuilder content = new StringBuilder(); - - // Populate the offset lookups - // These will tell us the maximum number of characters - // we can read to generate another possible match. - // If we read more characters than this, we risk consuming - // more of the stream than we need. - Dictionary offsetDictionary = new Dictionary(); - foreach (char currentChar in actualDelimiter) - offsetDictionary[currentChar] = actualDelimiter.Length - actualDelimiter.LastIndexOf(currentChar) - 1; + Span readBuffer = stackalloc char[currentOffset]; + bool delimiterNotFound = true; + _currentLineContent.Clear(); do { - if (_isRawStream) + // Read in the required batch of characters + numRead = readBackward + ? _backReader.Read(readBuffer.Slice(0, currentOffset)) + : _reader.Read(readBuffer.Slice(0, currentOffset)); + + // If we want to wait for changes, then we'll keep on attempting to read + // until we fill the buffer. + if (numRead == 0) { - // when -Raw is used we want to anyway read the whole thing - // so avoiding the while loop by reading the entire content. - string contentRead = _reader.ReadToEnd(); - numRead = contentRead.Length; - content.Append(contentRead); + if (waitChanges) + { + // But stop reading if the provider is stopping + while ((numRead < currentOffset) && (!_provider.Stopping)) + { + // Get the change, and try to read more characters + // We only wait for changes when read forwards, so here we don't need to check if 'readBackward' is + // true or false, we only use 'reader'. The member 'reader' will be updated by WaitForChanges. + WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding); + numRead += _reader.Read(readBuffer.Slice(0, currentOffset - numRead)); + } + } } - else + + if (numRead > 0) { - // Read in the required batch of characters - var readBuffer = new char[currentOffset]; - numRead = readBackward - ? _backReader.Read(readBuffer, 0, currentOffset) - : _reader.Read(readBuffer, 0, currentOffset); - - // If we want to wait for changes, then we'll keep on attempting to read - // until we fill the buffer. - if (numRead == 0) + _currentLineContent.Append(readBuffer.Slice(0, numRead)); + + // Look up the final character in our offset table. + // If the character doesn't exist in the lookup table, then it's not in + // our search key. That means the match must happen strictly /after/ the + // current position. Because of that, we can feel confident reading in the + // number of characters in the search key, without the risk of reading too many. + var currentChar = _currentLineContent[_currentLineContent.Length - 1]; + currentOffset = _offsetDictionary[Unsafe.As(ref currentChar)]; + + // We want to keep reading if delimiter not found and we haven't hit the end of file + delimiterNotFound = true; + + // If the final letters matched, then we will get an offset of "0". + // In that case, we'll either have a match (and break from the while loop,) + // or we need to move the scan forward one position. + if (currentOffset == 0) { - if (waitChanges) + currentOffset = 1; + + if (actualDelimiter.Length <= _currentLineContent.Length) { - // But stop reading if the provider is stopping - while ((numRead < currentOffset) && (!_provider.Stopping)) + delimiterNotFound = false; + int i = 0; + int j = _currentLineContent.Length - actualDelimiter.Length; + for (; i < actualDelimiter.Length; i++, j++) { - // Get the change, and try to read more characters - // We only wait for changes when read forwards, so here we don't need to check if 'readBackward' is - // true or false, we only use 'reader'. The member 'reader' will be updated by WaitForChanges. - WaitForChanges(_path, _mode, _access, _share, _reader.CurrentEncoding); - numRead += _reader.Read(readBuffer, 0, (currentOffset - numRead)); + if (actualDelimiter[i] != _currentLineContent[j]) + { + delimiterNotFound = true; + break; + } } } } - - if (numRead > 0) - { - content.Append(readBuffer, 0, numRead); - - // Look up the final character in our offset table. - // If the character doesn't exist in the lookup table, then it's not in - // our search key. That means the match must happen strictly /after/ the - // current position. Because of that, we can feel confident reading in the - // number of characters in the search key, without the risk of reading too many. - if (!offsetDictionary.TryGetValue(content[content.Length - 1], out currentOffset)) - currentOffset = actualDelimiter.Length; - - // If the final letters matched, then we will get an offset of "0". - // In that case, we'll either have a match (and break from the while loop,) - // or we need to move the scan forward one position. - if (currentOffset == 0) - currentOffset = 1; - } } - - // Two cases where we want to keep reading: - // 1. Raw stream and we haven't hit the end of file - // 2. Delimiter not found and we haven't hit the end of file - } while ((_isRawStream && (numRead != 0)) || - ((content.ToString().IndexOf(actualDelimiter, StringComparison.Ordinal) < 0) && (numRead != 0))); + } + while (delimiterNotFound && (numRead != 0)); // We've reached the end of file or end of line. - if (content.Length > 0) + 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. // (Trimming it during backward reading would not only be unnecessary, but could interfere with determining the correct start position.) - string contentString = content.ToString(); blocks.Add( - !readBackward && contentString.EndsWith(actualDelimiter, StringComparison.Ordinal) && !_isRawStream - ? contentString.Substring(0, content.Length - actualDelimiter.Length) - : contentString - ); + !readBackward && !delimiterNotFound + ? _currentLineContent.ToString(0, _currentLineContent.Length - actualDelimiter.Length) + : _currentLineContent.ToString()); } int peekResult = readBackward ? _backReader.Peek() : _reader.Peek(); if (peekResult != -1) + { return true; + } else { - if (readBackward && content.Length > 0) + if (readBackward && _currentLineContent.Length > 0) { return true; } + return false; } } - private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) + private bool ReadByteEncoded(bool waitChanges, List blocks, bool readBackward) { if (_isRawStream) { @@ -768,7 +752,9 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) // Break when the end of the file is reached. if (n == 0) + { break; + } numBytesRead += n; numBytesToRead -= n; @@ -785,7 +771,7 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) } } - if (readBack) + if (readBackward) { if (_stream.Position == 0) { @@ -808,7 +794,7 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) // the changes if (waitChanges) { - WaitForChanges(_path, _mode, _access, _share, ClrFacade.GetDefaultEncoding()); + WaitForChanges(_path, _mode, _access, _share, Encoding.Default); byteRead = _stream.ReadByte(); } } @@ -820,8 +806,10 @@ private bool ReadByteEncoded(bool waitChanges, ArrayList blocks, bool readBack) return true; } else + { return false; - } // Read + } + } private void CreateStreams(string filePath, string streamName, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, Encoding fileEncoding) { @@ -840,6 +828,7 @@ private void CreateStreams(string filePath, string streamName, FileMode fileMode { attributesToClear |= FileAttributes.ReadOnly; } + File.SetAttributes(_path, (File.GetAttributes(filePath) & ~attributesToClear)); } @@ -854,7 +843,7 @@ private void CreateStreams(string filePath, string streamName, FileMode fileMode try { #if !UNIX - if (!String.IsNullOrEmpty(streamName)) + if (!string.IsNullOrEmpty(streamName)) { _stream = AlternateDataStreamUtilities.CreateFileStream(filePath, streamName, fileMode, fileAccess, fileShare); } @@ -867,7 +856,7 @@ private void CreateStreams(string filePath, string streamName, FileMode fileMode catch (IOException) { #if !UNIX - if (!String.IsNullOrEmpty(streamName)) + if (!string.IsNullOrEmpty(streamName)) { _stream = AlternateDataStreamUtilities.CreateFileStream(filePath, streamName, fileMode, requestedAccess, fileShare); } @@ -908,11 +897,11 @@ private void CreateStreams(string filePath, string streamName, FileMode fileMode /// and then monitors for changes. Once a change appears, it reopens the streams /// and seeks to the last read position. /// - /// The path of the file to read / monitor - /// The FileMode of the file (ie: Open / Append) - /// The access properties of the file (ie: Read / Write) - /// The sharing properties of the file (ie: Read / ReadWrite) - /// The encoding of the file + /// The path of the file to read / monitor. + /// The FileMode of the file (ie: Open / Append). + /// The access properties of the file (ie: Read / Write). + /// The sharing properties of the file (ie: Read / ReadWrite). + /// The encoding of the file. private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, Encoding fileEncoding) { // Close the old stream, and store our current position. @@ -930,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; @@ -953,7 +942,7 @@ private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileA { bool isTaskCompleted = tcs.Task.Wait(500); - if (null != errorEventArgs) + if (errorEventArgs != null) { throw errorEventArgs.GetException(); } @@ -998,32 +987,29 @@ 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(); } /// - /// Moves the current stream position in the file + /// Moves the current stream position in the file. /// - /// /// /// The offset from the origin to move the position to. /// - /// /// /// The origin from which the offset is calculated. /// - /// 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(); } - } // Seek + _writer?.Flush(); + _reader?.DiscardBufferedData(); + _backReader?.DiscardBufferedData(); + } /// /// Closes the file. @@ -1068,20 +1054,17 @@ public void Close() { File.SetAttributes(_path, _oldAttributes); } - } // Close + } /// - /// Writes the specified object to the file + /// Writes the specified object to the file. /// - /// /// /// The objects to write to the file /// - /// /// /// The objects written to the file. /// - /// public IList Write(IList content) { foreach (object line in content) @@ -1099,8 +1082,9 @@ public IList Write(IList content) WriteObject(line); } } + return content; - } // Write + } private void WriteObject(object content) { @@ -1119,7 +1103,7 @@ private void WriteObject(object content) } catch (InvalidCastException) { - throw PSTraceSource.NewArgumentException("content", FileSystemProviderStrings.ByteEncodingError); + throw PSTraceSource.NewArgumentException(nameof(content), FileSystemProviderStrings.ByteEncodingError); } } else @@ -1133,32 +1117,28 @@ private void WriteObject(object content) _writer.WriteLine(content.ToString()); } } - } // WriteObject + } /// - /// Closes the file stream + /// Closes the file stream. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); - } // Dispose + } 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(); } } - } // class FileSystemContentStream + } internal sealed class FileStreamBackReader : StreamReader { @@ -1187,46 +1167,17 @@ internal FileStreamBackReader(FileStream fileStream, Encoding encoding) private readonly Encoding _defaultAnsiEncoding; private const int BuffSize = 4096; + private readonly byte[] _byteBuff = new byte[BuffSize]; private readonly char[] _charBuff = new char[BuffSize]; 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 + /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter. /// /// /// @@ -1239,7 +1190,7 @@ public override int ReadBlock(char[] buffer, int index, int count) } /// - /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter + /// We don't support this method because it is not used by the ReadBackward method in FileStreamContentReaderWriter. /// /// public override string ReadToEnd() @@ -1262,7 +1213,7 @@ public override string ReadToEnd() } /// - /// Return the current actual stream position + /// Return the current actual stream position. /// /// internal long GetCurrentPosition() @@ -1277,7 +1228,7 @@ internal long GetCurrentPosition() /// /// Get the number of bytes the delimiter will - /// be encoded to + /// be encoded to. /// /// /// @@ -1288,9 +1239,9 @@ internal int GetByteCount(string delimiter) } /// - /// Peek the next character + /// Peek the next character. /// - /// Return -1 if we reach the head of the file + /// Return -1 if we reach the head of the file. public override int Peek() { if (_charCount == 0) @@ -1306,9 +1257,9 @@ public override int Peek() } /// - /// Read the next character + /// Read the next character. /// - /// Return -1 if we reach the head of the file + /// Return -1 if we reach the head of the file. public override int Read() { if (_charCount == 0) @@ -1324,16 +1275,34 @@ public override int Read() } /// - /// Read a specific maximum of characters from the current stream into a buffer + /// Read a specific maximum of characters from the current stream into a buffer. /// - /// - /// - /// - /// Return the number of characters read, or -1 if we reach the head of the file + /// Output buffer. + /// Start position to write with. + /// Number of bytes to read. + /// Return the number of characters read, or -1 if we reach the head of the file. + /// Return the number of characters read, or -1 if we reach the head of the file. public override int Read(char[] buffer, int index, int count) + { + return ReadSpan(new Span(buffer, index, count)); + } + + /// + /// Read characters from the current stream into a Span buffer. + /// + /// Output buffer. + /// Return the number of characters read, or -1 if we reach the head of the file. + public override int Read(Span buffer) + { + return ReadSpan(buffer); + } + + private int ReadSpan(Span buffer) { // deal with the argument validation int charRead = 0; + int index = 0; + int count = buffer.Length; do { @@ -1351,15 +1320,16 @@ public override int Read(char[] buffer, int index, int count) { buffer[index++] = _charBuff[--_charCount]; } - } while (count > 0); + } + while (count > 0); return charRead; } /// - /// Read a line from the current stream + /// Read a line from the current stream. /// - /// Return null if we reach the head of the file + /// Return null if we reach the head of the file. public override string ReadLine() { if (_charCount == 0 && RefillCharBuffer() == -1) @@ -1391,7 +1361,7 @@ public override string ReadLine() } } - do + while (true) { while (_charCount > 0) { @@ -1412,11 +1382,11 @@ public override string ReadLine() line.Remove(line.Length - charsToRemove, charsToRemove); return line.ToString(); } - } while (true); + } } /// - /// Refill the internal character buffer + /// Refill the internal character buffer. /// /// private int RefillCharBuffer() @@ -1431,7 +1401,7 @@ private int RefillCharBuffer() } /// - /// Refill the internal byte buffer + /// Refill the internal byte buffer. /// /// private int RefillByteBuff() @@ -1446,7 +1416,7 @@ private int RefillByteBuff() int toRead = lengthLeft > BuffSize ? BuffSize : (int)lengthLeft; _stream.Seek(-toRead, SeekOrigin.Current); - if (_currentEncoding.Equals(Encoding.UTF8)) + if (_currentEncoding is UTF8Encoding) { // It's UTF-8, we need to detect the starting byte of a character do @@ -1472,14 +1442,11 @@ private int RefillByteBuff() _byteCount += _stream.Read(_byteBuff, _byteCount, (int)(lengthLeft - _stream.Position)); _stream.Position = _currentPosition; } - else if (_currentEncoding.Equals(Encoding.Unicode) || - _currentEncoding.Equals(Encoding.BigEndianUnicode) || - _currentEncoding.Equals(Encoding.UTF32) || - _currentEncoding.Equals(Encoding.ASCII) || - IsSingleByteCharacterSet()) + else if (_currentEncoding is UnicodeEncoding || + _currentEncoding is UTF32Encoding || + _currentEncoding.IsSingleByte) { // Unicode -- two bytes per character - // BigEndianUnicode -- two types per character // UTF-32 -- four bytes per character // ASCII -- one byte per character // The BufferSize will be a multiple of 4, so we can just read toRead number of bytes @@ -1508,39 +1475,10 @@ private int RefillByteBuff() 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)] - internal static extern bool GetCPInfo(uint codePage, out CPINFO lpCpInfo); - } } /// - /// The exception that indicates the encoding is not supported when reading backward + /// The exception that indicates the encoding is not supported when reading backward. /// [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")] @@ -1558,8 +1496,8 @@ internal BackReaderEncodingNotSupportedException(string encodingName) } /// - /// Get the encoding name + /// Get the encoding name. /// internal string EncodingName { get; } } -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 904bbf83cf2..05c8fff76e0 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -1,12 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -14,15 +14,18 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Provider; +using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; using System.Security; using System.Security.AccessControl; using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.XPath; + using Microsoft.Win32.SafeHandles; + using Dbg = System.Management.Automation; -using System.Runtime.InteropServices; -using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.Commands { @@ -34,15 +37,16 @@ namespace Microsoft.PowerShell.Commands /// [CmdletProvider(FileSystemProvider.ProviderName, ProviderCapabilities.Credentials | ProviderCapabilities.Filter | ProviderCapabilities.ShouldProcess)] [OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)] - [OutputType(typeof(String), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] + [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)] - [OutputType(typeof(Byte), typeof(String), ProviderCmdlet = ProviderCmdlet.GetContent)] + [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(Boolean), typeof(String), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)] - [OutputType(typeof(Boolean), 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(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), 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, @@ -53,24 +57,26 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, // 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"; - /// /// 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"); /// - /// Gets the name of the provider + /// Gets the name of the provider. /// public const string ProviderName = "FileSystem"; @@ -84,56 +90,162 @@ public FileSystemProvider() private Collection _excludeMatcher = null; + private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + { + MatchType = MatchType.Win32, + MatchCasing = MatchCasing.CaseInsensitive, + AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior + }; + /// /// Converts all / in the path to \ /// - /// /// /// The path to normalize. /// - /// /// /// 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)); + } + + /// + /// Get the correct casing for a path. This method assumes it's being called by NormalizePath() + /// so that the path is already normalized. + /// + /// + /// The path to retrieve. + /// + /// + /// The path with accurate casing if item exists, otherwise it returns path that was passed in. + /// + private static string GetCorrectCasedPath(string path) { - return path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); - } // NormalizePath + // Only apply to directories where there are issues with some tools if the casing + // doesn't match the source like git + if (Directory.Exists(path)) + { + string exactPath = string.Empty; + int itemsToSkip = 0; + if (Utils.PathIsUnc(path)) + { + // With the Split method, a UNC path like \\server\share, we need to skip + // trying to enumerate the server and share, so skip the first two empty + // strings, then server, and finally share name. + itemsToSkip = 4; + } + + 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 += items[i] + StringLiterals.DefaultPathSeparator; + continue; + } + else if (string.IsNullOrEmpty(exactPath)) + { + // This handles the drive letter or / root path start + exactPath = items[i] + StringLiterals.DefaultPathSeparator; + } + else if (string.IsNullOrEmpty(items[i]) && i == items.Length - 1) + { + // This handles the trailing slash case + if (!exactPath.EndsWith(StringLiterals.DefaultPathSeparator)) + { + exactPath += StringLiterals.DefaultPathSeparator; + } + + break; + } + else if (items[i].Contains('~')) + { + // This handles short path names + exactPath += StringLiterals.DefaultPathSeparator + items[i]; + } + else + { + // Use GetFileSystemEntries to get the correct casing of this element + try + { + var entries = Directory.GetFileSystemEntries(exactPath, items[i]); + if (entries.Length > 0) + { + exactPath = entries[0]; + } + else + { + // If previous call didn't return anything, something failed so we just return the path we were given + return path; + } + } + catch + { + // If we can't enumerate, we stop and just return the original path + return path; + } + } + } + return exactPath; + } + else + { + return path; + } + } /// - /// Checks if the item exist at the specified path. if it exists then creates + /// Checks if the item exist at the specified path. if it exists then creates /// appropriate directoryinfo or fileinfo object. /// /// - /// refers to the item for which we are checking for existence and creating filesysteminfo object. + /// Refers to the item for which we are checking for existence and creating filesysteminfo object. /// /// - /// return true if path points to a directory else returns false. + /// Return true if path points to a directory else returns false. /// - /// - private static FileSystemInfo GetFileSystemInfo(string path, ref bool isContainer) + /// FileInfo or DirectoryInfo object. + /// + /// The path is null. + /// + /// + /// I/O error occurs. + /// + /// + /// An I/O error or a specific type of security error. + /// + private static FileSystemInfo GetFileSystemInfo(string path, out bool isContainer) { - isContainer = false; - - if (Utils.NativeFileExists(path)) - { - return new FileInfo(path); - } + // We use 'FileInfo.Attributes' (not 'FileInfo.Exist') + // because we want to get exceptions + // like UnauthorizedAccessException or IOException. + FileSystemInfo fsinfo = new FileInfo(path); + var attr = fsinfo.Attributes; + var exists = (int)attr != -1; + isContainer = exists && attr.HasFlag(FileAttributes.Directory); - if (Utils.NativeDirectoryExists(path)) + if (exists) { - isContainer = true; - return new DirectoryInfo(path); + if (isContainer) + { + return new DirectoryInfo(path); + } + else + { + return fsinfo; + } } return null; } /// - /// overrides the method of CmdletProvider, considering the additional - /// dynamic parameters of FileSystemProvider + /// Overrides the method of CmdletProvider, considering the additional + /// dynamic parameters of FileSystemProvider. /// /// /// whether the filter or attribute filter is set. @@ -145,7 +257,7 @@ internal override bool IsFilterSet() if (fspDynamicParam != null) { attributeFilterSet = ( - (null != fspDynamicParam.Attributes) + (fspDynamicParam.Attributes != null) || (fspDynamicParam.Directory) || (fspDynamicParam.File) || (fspDynamicParam.Hidden) @@ -163,12 +275,10 @@ internal override bool IsFilterSet() /// "Attributes" that returns an enum evaluator for the /// given expression. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. @@ -185,16 +295,13 @@ protected override object GetChildNamesDynamicParameters(string path) /// "Attributes" that returns an enum evaluator for the /// given expression. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// Ignored. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. @@ -220,7 +327,7 @@ protected override object CopyItemDynamicParameters(string path, string destinat /// /// Implementation of ICmdletProviderSupportsHelp interface. - /// Gets provider-specific help content for the corresponding cmdlet + /// Gets provider-specific help content for the corresponding cmdlet. /// /// /// Name of command that the help is requested for. @@ -233,7 +340,6 @@ protected override object CopyItemDynamicParameters(string path, string destinat /// public string GetHelpMaml(string helpItemName, string path) { - // // Get the verb and noun from helpItemName // string verb = null; @@ -242,27 +348,27 @@ public string GetHelpMaml(string helpItemName, string path) try { - if (!String.IsNullOrEmpty(helpItemName)) + if (!string.IsNullOrEmpty(helpItemName)) { CmdletInfo.SplitCmdletName(helpItemName, out verb, out noun); } else { - return String.Empty; + return string.Empty; } - if (String.IsNullOrEmpty(verb) || String.IsNullOrEmpty(noun)) + if (string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(noun)) { - return String.Empty; + return string.Empty; } // Load the help file from the current UI culture subfolder XmlDocument document = new XmlDocument(); CultureInfo currentUICulture = CultureInfo.CurrentUICulture; string fullHelpPath = Path.Combine( - string.IsNullOrEmpty(this.ProviderInfo.ApplicationBase) ? "" : this.ProviderInfo.ApplicationBase, + string.IsNullOrEmpty(this.ProviderInfo.ApplicationBase) ? string.Empty : this.ProviderInfo.ApplicationBase, currentUICulture.ToString(), - string.IsNullOrEmpty(this.ProviderInfo.HelpFile) ? "" : this.ProviderInfo.HelpFile); + string.IsNullOrEmpty(this.ProviderInfo.HelpFile) ? string.Empty : this.ProviderInfo.HelpFile); XmlReaderSettings settings = new XmlReaderSettings(); settings.XmlResolver = null; @@ -275,7 +381,7 @@ public string GetHelpMaml(string helpItemName, string path) nsMgr.AddNamespace("command", HelpCommentsParser.commandURI); // Compose XPath query to select the appropriate node based on the cmdlet - string xpathQuery = String.Format( + string xpathQuery = string.Format( CultureInfo.InvariantCulture, HelpCommentsParser.ProviderHelpCommandXPath, "[@id='FileSystem']", @@ -291,49 +397,49 @@ public string GetHelpMaml(string helpItemName, string path) } catch (XmlException) { - return String.Empty; + return string.Empty; } catch (PathTooLongException) { - return String.Empty; + return string.Empty; } catch (IOException) { - return String.Empty; + return string.Empty; } catch (UnauthorizedAccessException) { - return String.Empty; + return string.Empty; } catch (NotSupportedException) { - return String.Empty; + return string.Empty; } catch (SecurityException) { - return String.Empty; + return string.Empty; } catch (XPathException) { - return String.Empty; + return string.Empty; } finally { - if (null != reader) + if (reader != null) { ((IDisposable)reader).Dispose(); } } - return String.Empty; + + return string.Empty; } #endregion #region CmdletProvider members - /// - /// Starts the File System provider. This method sets the Home for the + /// Starts the File System provider. This method sets the Home for the /// provider to providerInfo.Home if specified, and %USERPROFILE% /// otherwise. /// @@ -359,11 +465,29 @@ protected override ProviderInfo Start(ProviderInfo providerInfo) providerInfo.Home = homeDirectory; } else + { s_tracer.WriteLine("Not setting home directory {0} - does not exist", homeDirectory); + } + } + } + + // OneDrive placeholder support (issue #8315) + // make it so OneDrive placeholders are perceived as such with *all* their attributes accessible +#if !UNIX + // The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode + // are only supported starting with Windows 10 version 1803 (build 17134) + 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 (Interop.Windows.RtlQueryProcessPlaceholderCompatibilityMode() == Interop.Windows.PHCM_DISGUISE_PLACEHOLDER) + { + Interop.Windows.RtlSetProcessPlaceholderCompatibilityMode(Interop.Windows.PHCM_EXPOSE_PLACEHOLDERS); } } +#endif + return providerInfo; - } // Start + } #endregion CmdletProvider members @@ -372,11 +496,9 @@ protected override ProviderInfo Start(ProviderInfo providerInfo) /// /// Determines if the specified drive can be mounted. /// - /// /// /// The drive that is going to be mounted. /// - /// /// /// The same drive that was passed in, if the drive can be mounted. /// null if the drive cannot be mounted. @@ -390,13 +512,12 @@ protected override ProviderInfo Start(ProviderInfo providerInfo) protected override PSDriveInfo NewDrive(PSDriveInfo drive) { // verify parameters - if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } - if (String.IsNullOrEmpty(drive.Root)) + if (string.IsNullOrEmpty(drive.Root)) { throw PSTraceSource.NewArgumentException("drive.Root"); } @@ -412,14 +533,13 @@ 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 // drive is not a fixed drive. We want to allow // a drive to exist for floppies and other such\ // removable media, even if the media isn't in place. - bool driveIsFixed = true; PSDriveInfo result = null; @@ -449,18 +569,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) if (driveIsFixed) { // Since the drive is fixed, ensure the root is valid. - try - { - validDrive = Utils.NativeDirectoryExists(drive.Root); - } - catch (IOException) - { - // Ignore, the network path may not be found. - } - catch (UnauthorizedAccessException) - { - // Ignore, we may be running in an AppContainer - } + validDrive = SafeDoesPathExist(drive.Root); } if (validDrive) @@ -469,7 +578,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) } else { - String error = StringUtil.Format(FileSystemProviderStrings.DriveRootError, drive.Root); + string error = StringUtil.Format(FileSystemProviderStrings.DriveRootError, drive.Root); Exception e = new IOException(error); WriteError(new ErrorRecord(e, "DriveRootError", ErrorCategory.ReadError, drive)); } @@ -477,38 +586,24 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) drive.Trace(); return result; - } // NewDrive + } /// /// 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 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; - // By default the connection is not persisted. - int CONNECT_TYPE = CONNECT_NOPERSIST; + int connectType = Interop.Windows.CONNECT_NOPERSIST; string driveName = null; byte[] passwd = null; @@ -518,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); } @@ -540,25 +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 errorCode = Interop.Windows.WNetAddConnection2(driveName, drive.Root, passwd, userName, connectType); - int code = NativeMethods.WNetAddConnection2(ref resource, passwd, userName, CONNECT_TYPE); - - 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; @@ -573,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 } /// @@ -585,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))); @@ -606,22 +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; - - 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 @@ -631,18 +708,18 @@ private PSDriveInfo WinRemoveDrive(PSDriveInfo drive) driveName = drive.Root; } - // You need to actually remove the drive. - int code = NativeMethods.WNetCancelConnection2(driveName, flags, true); + 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 } /// @@ -653,14 +730,14 @@ 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) { char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture); - if (Char.ToUpperInvariant(driveChar) >= 'A' && Char.ToUpperInvariant(driveChar) <= 'Z') + if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z') { isSupportedDriveForPersistence = true; } @@ -671,7 +748,7 @@ private bool IsSupportedDriveForPersistence(PSDriveInfo drive) /// /// Return the UNC path for a given network drive - /// using the Windows API + /// using the Windows API. /// /// /// @@ -680,46 +757,19 @@ internal static string GetUNCForNetworkDrive(string driveName) #if UNIX return driveName; #else - return WinGetUNCForNetworkDrive(driveName); -#endif - } - - private static string WinGetUNCForNetworkDrive(string driveName) - { 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 += ':'; - - // Call the windows API - int errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize); + int errorCode = Interop.Windows.GetUNCForNetworkDrive(driveName[0], out uncPath); - // 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 } /// @@ -737,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) @@ -747,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(":", StringComparison.OrdinalIgnoreCase)) - { - // 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 http://msdn.microsoft.com/en-us/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. @@ -856,12 +842,10 @@ internal static string GetRootPathForNetworkDriveOrDosDevice(DriveInfo driveInfo /// /// Returns a collection of all logical drives in the system. /// - /// /// /// A collection of PSDriveInfo objects, one for each logical drive returned from /// System.Environment.GetLogicalDrives(). /// - /// protected override Collection InitializeDefaultDrives() { Collection results = new Collection(); @@ -884,7 +868,7 @@ protected override Collection InitializeDefaultDrives() { string newDriveName = newDrive.Name.Substring(0, 1); - string description = String.Empty; + string description = string.Empty; string root = newDrive.Name; string displayRoot = null; @@ -924,7 +908,7 @@ protected override Collection InitializeDefaultDrives() if (newDrive.DriveType == DriveType.Fixed) { - if (!newDrive.RootDirectory.Exists) + if (!SafeDoesPathExist(newDrive.RootDirectory.FullName)) { continue; } @@ -955,8 +939,11 @@ protected override Collection InitializeDefaultDrives() break; } } + if (skipDuplicate) + { continue; + } // Create a new VirtualDrive for each logical drive PSDriveInfo newPSDriveInfo = @@ -1000,20 +987,30 @@ protected override Collection InitializeDefaultDrives() catch (System.UnauthorizedAccessException) { } - } // foreach + } } - return results; - } // InitializeDefaultDrives + results.Add( + new PSDriveInfo( + DriveNames.TempDrive, + ProviderInfo, + Path.GetTempPath(), + SessionStateStrings.TempDriveDescription, + credential: null, + displayRoot: null) + ); + + return results; + } #endregion DriveCmdletProvider methods #region ItemCmdletProvider methods /// - /// Retrieves the dynamic parameters required for the Get-Item cmdlet + /// Retrieves the dynamic parameters required for the Get-Item cmdlet. /// - /// The path of the file to process + /// The path of the file to process. /// An instance of the FileSystemProviderGetItemDynamicParameters class that represents the dynamic parameters. protected override object GetItemDynamicParameters(string path) { @@ -1023,26 +1020,23 @@ protected override object GetItemDynamicParameters(string path) /// /// Determines if the specified path is syntactically and semantically valid. /// An example path looks like this - /// C:\WINNT\Media\chimes.wav + /// C:\WINNT\Media\chimes.wav. /// - /// /// /// The fully qualified path to validate. /// - /// /// /// True if the path is valid, false otherwise. /// protected override bool IsValidPath(string path) { - //Path passed should be fully qualified path. - if (String.IsNullOrEmpty(path)) + // Path passed should be fully qualified path. + if (string.IsNullOrEmpty(path)) { return false; } - - //Normalize the path + // Normalize the path path = NormalizePath(path); path = EnsureDriveIsRooted(path); @@ -1057,8 +1051,8 @@ protected override bool IsValidPath(string path) } #endif - //Make sure the path is either drive rooted or UNC Path - if (!IsAbsolutePath(path) && !IsUNCPath(path)) + // Make sure the path is either drive rooted or UNC Path + if (!IsAbsolutePath(path) && !Utils.PathIsUnc(path)) { return false; } @@ -1082,7 +1076,23 @@ protected override bool IsValidPath(string path) return false; } else + { throw; + } + } + + // .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; @@ -1091,30 +1101,26 @@ protected override bool IsValidPath(string path) /// /// Gets the item at the specified path. /// - /// /// /// A fully qualified path representing a file or directory in the /// file system. /// - /// /// /// Nothing. FileInfo and DirectoryInfo objects are written to the /// context's pipeline. /// - /// /// /// path is null or empty. /// protected override void GetItem(string path) { // Validate the argument - bool isContainer = false; - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { // The parameter was null, throw an exception - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } try @@ -1158,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)); } } } @@ -1199,7 +1206,7 @@ protected override void GetItem(string path) } else { - String error = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -1210,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); } @@ -1218,21 +1225,47 @@ protected override void GetItem(string path) { WriteError(new ErrorRecord(accessException, "GetItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } - } // GetItem + } + + 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); - FileSystemInfo result = null; + var attributes = result.Attributes; + bool hidden = attributes.HasFlag(FileAttributes.Hidden); + isContainer = attributes.HasFlag(FileAttributes.Directory); - // First see if the path is to a file by - // constructing a FileInfo object - - int attribs = SafeGetFileAttributes(path); - bool exists = (attribs != -1); - bool directory = (attribs & ((int)NativeMethods.FileAttributes.Directory)) == ((int)NativeMethods.FileAttributes.Directory); - bool hidden = (attribs & ((int)NativeMethods.FileAttributes.Hidden)) == ((int)NativeMethods.FileAttributes.Hidden); + // 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; @@ -1246,76 +1279,63 @@ private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool bool filterHidden = false; // "Hidden" is specified somewhere in the expression bool switchFilterHidden = false; // "Hidden" is specified somewhere in the parameters - if (null != evaluator) + if (evaluator != null) { filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden); } - if (null != switchEvaluator) + + if (switchEvaluator != null) { switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden); } // if "Hidden" is specified in the attribute filter dynamic parameters // also return the object - if (exists && !directory && (!hidden || Force || showHidden || filterHidden || switchFilterHidden)) + if (!isContainer) { - FileInfo fileObj = new FileInfo(path); - - result = fileObj; - s_tracer.WriteLine("Got FileInfo: {0}", fileObj); + if (!hidden || Force || showHidden || filterHidden || switchFilterHidden) + { + s_tracer.WriteLine("Got file info: {0}", result); + return result; + } } else { - // if its not a file, maybe its a directory - - DirectoryInfo directoryObj = - new DirectoryInfo(path); - // Check to see if the path is the root of a file system drive. // Since all root paths are hidden we need to show the directory // anyway - bool isRootPath = - String.Compare( + string.Equals( Path.GetPathRoot(path), - directoryObj.FullName, - StringComparison.OrdinalIgnoreCase) == 0; + result.FullName, + StringComparison.OrdinalIgnoreCase); // if "Hidden" is specified in the attribute filter dynamic parameters // also return the object - if (exists && (isRootPath || !hidden || Force || showHidden || filterHidden || switchFilterHidden)) + if (isRootPath || !hidden || Force || showHidden || filterHidden || switchFilterHidden) { - Dbg.Diagnostics.Assert( - (directoryObj.Attributes & - FileAttributes.Directory) == - FileAttributes.Directory, - "The object is not a directory?"); - - result = directoryObj; - - isContainer = true; - s_tracer.WriteLine("Got DirectoryInfo: {0}", directoryObj); + s_tracer.WriteLine("Got directory info: {0}", result); + return new DirectoryInfo(path); } } - return result; - } // GetFileSystemItem + + return null; + } /// /// Invokes the item at the path using ShellExecute semantics. /// - /// /// /// The item to invoke. /// - /// /// /// path is null or empty. /// protected override void InvokeDefaultAction(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -1327,13 +1347,14 @@ 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 invokeDefaultProgram = false; + bool useShellExecute = false; if (Directory.Exists(path)) { // Path points to a directory. We have to use xdg-open/open on Linux/macOS. - invokeDefaultProgram = true; + useShellExecute = true; } else { @@ -1346,19 +1367,13 @@ protected override void InvokeDefaultAction(string path) { // Error code 13 -- Permission denied // The file is possibly not an executable. We try xdg-open/open on Linux/macOS. - invokeDefaultProgram = true; + useShellExecute = true; } } - if (invokeDefaultProgram) + if (useShellExecute) { - const string quoteFormat = "\"{0}\""; - invokeProcess.StartInfo.FileName = Platform.IsLinux ? "xdg-open" : /* macOS */ "open"; - if (NativeCommandParameterBinder.NeedQuotes(path)) - { - path = string.Format(CultureInfo.InvariantCulture, quoteFormat, path); - } - invokeProcess.StartInfo.Arguments = path; + invokeProcess.StartInfo.UseShellExecute = true; invokeProcess.Start(); } #else @@ -1367,7 +1382,7 @@ protected override void InvokeDefaultAction(string path) invokeProcess.Start(); #endif } - } // InvokeDefaultAction + } #endregion ItemCmdletProvider members @@ -1377,24 +1392,19 @@ protected override void InvokeDefaultAction(string path) /// /// Gets the child items of a given directory. /// - /// /// /// The full path of the directory to enumerate. /// - /// /// /// If true, recursively enumerates the child items as well. /// - /// /// /// Limits the depth of recursion; uint.MaxValue performs full recursion. /// - /// /// /// Nothing. FileInfo and DirectoryInfo objects that match the filter are written to the /// context's pipeline. /// - /// /// /// path is null or empty. /// @@ -1404,29 +1414,24 @@ protected override void GetChildItems( uint depth) { GetPathItems(path, recurse, depth, false, ReturnContainers.ReturnMatchingContainers); - } // GetChildItems + } #endregion GetChildItems - #region GetChildNames /// /// Gets the path names for all children of the specified /// directory that match the given filter. /// - /// /// /// The full path of the directory to enumerate. /// - /// /// /// Determines if all containers should be returned or only those containers that match the /// filter(s). /// - /// /// /// Nothing. Child names are written to the context's pipeline. /// - /// /// /// path is null or empty. /// @@ -1435,14 +1440,13 @@ protected override void GetChildNames( ReturnContainers returnContainers) { GetPathItems(path, false, uint.MaxValue, true, returnContainers); - } // GetChildNames + } #endregion GetChildNames /// /// Gets a new provider-specific path and filter (if any) that corresponds to the given /// path. /// - /// /// /// The path to the item. Unlike most other provider APIs, this path is likely to /// contain PowerShell wildcards. @@ -1456,17 +1460,14 @@ protected override void GetChildNames( /// /// The new filter. /// - /// /// /// True if the path or filter were altered. False otherwise. /// - /// /// /// Makes no attempt to filter if the user has already specified a filter, or /// if the path contains directory separators. Those are not supported by the /// FileSystem filter. /// - /// protected override bool ConvertPath( string path, string filter, @@ -1475,9 +1476,9 @@ protected override bool ConvertPath( { // Don't handle full paths, paths that the user is already trying to // filter, or paths they are trying to escape. - if ((!String.IsNullOrEmpty(filter)) || - (path.Contains(StringLiterals.DefaultPathSeparatorString)) || - (path.Contains(StringLiterals.AlternatePathSeparatorString)) || + if ((!string.IsNullOrEmpty(filter)) || + (path.Contains(StringLiterals.DefaultPathSeparator, StringComparison.Ordinal)) || + (path.Contains(StringLiterals.AlternatePathSeparator, StringComparison.Ordinal)) || (path.Contains(StringLiterals.EscapeCharacter))) { return false; @@ -1501,7 +1502,6 @@ protected override bool ConvertPath( // Our algorithm here is pretty simple. The filesystem can handle // * and ? in PowerShell wildcards, just not character ranges [a-z]. // We replace character ranges with the single-character wildcard, '?'. - updatedPath = path; updatedFilter = System.Text.RegularExpressions.Regex.Replace(path, "\\[.*?\\]", "?"); @@ -1516,28 +1516,19 @@ private void GetPathItems( ReturnContainers returnContainers) { // Verify parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); - // Get the directory object - bool isDirectory; - Exception accessException; - bool exists = Utils.NativeItemExists(path, out isDirectory, out accessException); - - if (accessException != null) - { - throw accessException; - } + var fsinfo = GetFileSystemInfo(path, out bool isDirectory); - if (exists) + if (fsinfo != null) { if (isDirectory) { - DirectoryInfo directory = new DirectoryInfo(path); InodeTracker tracker = null; if (recurse) @@ -1545,18 +1536,15 @@ private void GetPathItems( GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters; if (fspDynamicParam != null && fspDynamicParam.FollowSymlink) { - tracker = new InodeTracker(directory.FullName); + tracker = new InodeTracker(fsinfo.FullName); } } // Enumerate the directory - Dir(directory, recurse, depth, nameOnly, returnContainers, tracker); + Dir((DirectoryInfo)fsinfo, recurse, depth, nameOnly, returnContainers, tracker); } else { - // Maybe the path is a file name so try a FileInfo instead - FileInfo fileInfo = new FileInfo(path); - FlagsExpression evaluator = null; FlagsExpression switchEvaluator = null; GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters; @@ -1571,18 +1559,19 @@ private void GetPathItems( bool filterHidden = false; // "Hidden" is specified somewhere in the expression bool switchFilterHidden = false; // "Hidden" is specified somewhere in the parameters - if (null != evaluator) + if (evaluator != null) { - attributeFilter = evaluator.Evaluate(fileInfo.Attributes); // expressions + attributeFilter = evaluator.Evaluate(fsinfo.Attributes); // expressions filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden); } - if (null != switchEvaluator) + + if (switchEvaluator != null) { - switchAttributeFilter = switchEvaluator.Evaluate(fileInfo.Attributes); // switch parameters + switchAttributeFilter = switchEvaluator.Evaluate(fsinfo.Attributes); // switch parameters switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden); } - bool hidden = (fileInfo.Attributes & FileAttributes.Hidden) != 0; + bool hidden = (fsinfo.Attributes & FileAttributes.Hidden) != 0; // if "Hidden" is explicitly specified anywhere in the attribute filter, then override // default hidden attribute filter. @@ -1592,18 +1581,20 @@ private void GetPathItems( if (nameOnly) { WriteItemObject( - fileInfo.Name, - fileInfo.FullName, + fsinfo.Name, + fsinfo.FullName, false); } else - WriteItemObject(fileInfo, path, false); + { + WriteItemObject(fsinfo, path, false); + } } } } else { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -1637,7 +1628,7 @@ private void Dir( else { // Filter the directories - target.Add(directory.EnumerateDirectories(Filter)); + target.Add(directory.EnumerateDirectories(Filter, _enumerationOptions)); } // Making sure to obey the StopProcessing. @@ -1648,7 +1639,7 @@ private void Dir( // Use the specified filter when retrieving the // children - target.Add(directory.EnumerateFiles(Filter)); + target.Add(directory.EnumerateFiles(Filter, _enumerationOptions)); } else { @@ -1678,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) { @@ -1697,12 +1688,13 @@ private void Dir( // 'Hidden' is specified somewhere in the parameters bool switchFilterHidden = false; - if (null != evaluator) + if (evaluator != null) { attributeFilter = evaluator.Evaluate(filesystemInfo.Attributes); filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden); } - if (null != switchEvaluator) + + if (switchEvaluator != null) { switchAttributeFilter = switchEvaluator.Evaluate(filesystemInfo.Attributes); switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden); @@ -1735,9 +1727,13 @@ private void Dir( else { if (filesystemInfo is FileInfo) + { WriteItemObject(filesystemInfo, filesystemInfo.FullName, false); + } else + { WriteItemObject(filesystemInfo, filesystemInfo.FullName, true); + } } } } @@ -1749,18 +1745,18 @@ private void Dir( { WriteError(new ErrorRecord(ex, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName)); } - }// foreach - }// foreach - + } + } bool isFilterHiddenSpecified = false; // "Hidden" is specified somewhere in the expression bool isSwitchFilterHiddenSpecified = false; // "Hidden" is specified somewhere in the parameters - if (null != evaluator) + if (evaluator != null) { isFilterHiddenSpecified = evaluator.ExistsInExpression(FileAttributes.Hidden); } - if (null != switchEvaluator) + + if (switchEvaluator != null) { isSwitchFilterHiddenSpecified = switchEvaluator.ExistsInExpression(FileAttributes.Hidden); } @@ -1782,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 @@ -1795,9 +1796,10 @@ private void Dir( // a) the user has asked to with the -FollowSymLinks switch parameter and // b) the directory pointed to by the symlink has not already been visited, // preventing symlink loops. + // c) it is not a reparse point with a target (not OneDrive or an AppX link). if (tracker == null) { - if (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(recursiveDirectory)) + if (checkReparsePoint && InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(recursiveDirectory)) { continue; } @@ -1811,9 +1813,9 @@ private void Dir( Dir(recursiveDirectory, recurse, depth - 1, nameOnly, returnContainers, tracker); } - }//foreach - }//if - }//if + } + } + } } catch (ArgumentException argException) { @@ -1829,7 +1831,7 @@ private void Dir( // 2004/10/13-JonN removed ResourceActionFailedException wrapper WriteError(new ErrorRecord(uae, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName)); } - } // Dir + } /// /// Create an enum expression evaluator for user-specified attribute filtering @@ -1850,18 +1852,22 @@ private FlagsExpression FormatAttributeSwitchParameters() { sb.Append("+Directory"); } + if (((GetChildDynamicParameters)DynamicParameters).File) { sb.Append("+!Directory"); } + if (((GetChildDynamicParameters)DynamicParameters).System) { sb.Append("+System"); } + if (((GetChildDynamicParameters)DynamicParameters).ReadOnly) { sb.Append("+ReadOnly"); } + if (((GetChildDynamicParameters)DynamicParameters).Hidden) { sb.Append("+Hidden"); @@ -1869,7 +1875,7 @@ private FlagsExpression FormatAttributeSwitchParameters() string switchParamString = sb.ToString(); - if (!String.IsNullOrEmpty(switchParamString)) + if (!string.IsNullOrEmpty(switchParamString)) { // Remove unnecessary PLUS sign switchParamEvaluator = new FlagsExpression(switchParamString.Substring(1)); @@ -1879,32 +1885,124 @@ private FlagsExpression FormatAttributeSwitchParameters() } /// - /// Provides a mode property for FileSystemInfo + /// Provides a mode property for FileSystemInfo. + /// + /// Instance of PSObject wrapping a FileSystemInfo. + /// A string representation of the FileAttributes, with one letter per attribute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] + public static string Mode(PSObject instance) => Mode(instance, excludeHardLink: false); + + /// + /// Provides a ModeWithoutHardLink property for FileSystemInfo, without HardLinks for performance reasons. /// - /// instance of PSObject wrapping a FileSystemInfo + /// Instance of PSObject wrapping a FileSystemInfo. + /// A string representation of the FileAttributes, with one letter per attribute. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - public static string Mode(PSObject instance) + public static string ModeWithoutHardLink(PSObject instance) => Mode(instance, excludeHardLink: true); + + private static string Mode(PSObject instance, bool excludeHardLink) + { + string ToModeString(FileSystemInfo fileSystemInfo) + { + FileAttributes fileAttributes = fileSystemInfo.Attributes; + + bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileSystemInfo); + bool isLink = isReparsePoint || (!excludeHardLink && InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileSystemInfo)); + if (!isLink) + { + // special casing for the common cases - no allocations + switch (fileAttributes) + { + case FileAttributes.Archive: + return "-a---"; + case FileAttributes.Directory: + return "d----"; + case FileAttributes.Normal: + return "-----"; + case FileAttributes.Directory | FileAttributes.ReadOnly: + return "d-r--"; + case FileAttributes.Archive | FileAttributes.ReadOnly: + return "-ar--"; + } + } + + 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); + } + + return instance?.BaseObject is FileSystemInfo fileInfo + ? ToModeString(fileInfo) + : string.Empty; + } + + /// + /// Provides a NameString property for FileSystemInfo. + /// + /// Instance of PSObject wrapping a FileSystemInfo. + /// Name if a file or directory, Name -> Target if symlink. + public static string NameString(PSObject instance) { - if (instance == null) - return 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; + } + } - FileSystemInfo fileInfo = (FileSystemInfo)instance.BaseObject; - if (fileInfo == null) - return String.Empty; + return string.Empty; + } - char[] mode = new char[6]; - mode[0] = (fileInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory ? 'd' : '-'; - mode[1] = (fileInfo.Attributes & FileAttributes.Archive) == FileAttributes.Archive ? 'a' : '-'; - mode[2] = (fileInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ? 'r' : '-'; - mode[3] = (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ? 'h' : '-'; - mode[4] = (fileInfo.Attributes & FileAttributes.System) == FileAttributes.System ? 's' : '-'; - // Mark the last bit as a "l" if it's a reparsepoint (symbolic link or junction) - // Porting note: these need to be handled specially - bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileInfo); - bool isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileInfo); - mode[5] = isReparsePoint || isHardLink ? 'l' : '-'; + /// + /// Provides a LengthString property for FileSystemInfo. + /// + /// Instance of PSObject wrapping a FileSystemInfo. + /// Length as a string. + public static string LengthString(PSObject instance) + { + return instance?.BaseObject is FileInfo fileInfo + ? fileInfo.Attributes.HasFlag(FileAttributes.Offline) + ? $"({fileInfo.Length})" + : fileInfo.Length.ToString() + : string.Empty; + } - return new string(mode); + /// + /// Provides a LastWriteTimeString property for FileSystemInfo. + /// + /// Instance of PSObject wrapping a FileSystemInfo. + /// LastWriteTime formatted as short date + short time. + public static string LastWriteTimeString(PSObject instance) + { + return instance?.BaseObject is FileSystemInfo fileInfo + ? string.Create(CultureInfo.CurrentCulture, $"{fileInfo.LastWriteTime,10:d} {fileInfo.LastWriteTime,8:t}") + : string.Empty; } #region RenameItem @@ -1912,20 +2010,16 @@ public static string Mode(PSObject instance) /// /// Renames a file or directory. /// - /// /// /// The current full path to the file or directory. /// - /// /// /// The new full path to the file or directory. /// - /// /// /// Nothing. The renamed DirectoryInfo or FileInfo object is /// written to the context's pipeline. /// - /// /// /// path is null or empty. /// newName is null or empty @@ -1935,38 +2029,36 @@ protected override void RenameItem( string newName) { // Check the parameters - - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); - if (String.IsNullOrEmpty(newName)) - + if (string.IsNullOrEmpty(newName)) { - throw PSTraceSource.NewArgumentException("newName"); + throw PSTraceSource.NewArgumentException(nameof(newName)); } // Clean up "newname" to fix some common usability problems: // Rename .\foo.txt .\bar.txt - // Rename c:\temp\foo.txt c:\temp\bar.txt + // Rename C:\temp\foo.txt C:\temp\bar.txt if (newName.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) || newName.StartsWith("./", StringComparison.OrdinalIgnoreCase)) { newName = newName.Remove(0, 2); } - else if (String.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(newName), StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(newName), StringComparison.OrdinalIgnoreCase)) { newName = Path.GetFileName(newName); } - //Check to see if the target specified is just filename. We dont allow rename to move the file to a different directory. - //If a path is specified for the newName then we flag that as an error. - if (String.Compare(Path.GetFileName(newName), newName, StringComparison.OrdinalIgnoreCase) != 0) + // Check to see if the target specified is just filename. We dont allow rename to move the file to a different directory. + // If a path is specified for the newName then we flag that as an error. + if (!string.Equals(Path.GetFileName(newName), newName, StringComparison.OrdinalIgnoreCase)) { - throw PSTraceSource.NewArgumentException("newName", FileSystemProviderStrings.RenameError); + throw PSTraceSource.NewArgumentException(nameof(newName), FileSystemProviderStrings.RenameError); } // Verify that the target doesn't represent a device name @@ -1984,17 +2076,14 @@ protected override void RenameItem( if (isContainer) { // Get the DirectoryInfo - DirectoryInfo dir = new DirectoryInfo(path); // Generate the new path which the directory will // be renamed to. - string parentDirectory = dir.Parent.FullName; string newPath = MakePath(parentDirectory, newName); // Confirm the rename with the user - string action = FileSystemProviderStrings.RenameItemActionDirectory; string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, dir.FullName, newPath); @@ -2011,16 +2100,13 @@ protected override void RenameItem( else { // Get the FileInfo - FileInfo file = new FileInfo(path); // Generate the new path which the file will be renamed to. - string parentDirectory = file.DirectoryName; string newPath = MakePath(parentDirectory, newName); // Confirm the rename with the user - string action = FileSystemProviderStrings.RenameItemActionFile; string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, file.FullName, newPath); @@ -2041,14 +2127,14 @@ 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) { WriteError(new ErrorRecord(accessException, "RenameItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } - } // RenameItem + } #endregion RenameItem @@ -2057,26 +2143,21 @@ protected override void RenameItem( /// /// Creates a file or directory with the given path. /// - /// /// /// 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. /// - /// /// /// Nothing. The new DirectoryInfo or FileInfo object is /// written to the context's pipeline. /// - /// /// /// path is null or empty. /// type is null or empty. @@ -2089,13 +2170,12 @@ protected override void NewItem( ItemType itemType = ItemType.Unknown; // Verify parameters - - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(type)) + if (string.IsNullOrEmpty(type)) { type = "file"; } @@ -2126,11 +2206,9 @@ protected override void NewItem( { // If force is specified, overwrite the existing // file - fileMode = FileMode.Create; } - string action = FileSystemProviderStrings.NewItemActionFile; string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path); @@ -2139,7 +2217,6 @@ protected override void NewItem( { // Create the file with read/write access and // not allowing sharing. - using (FileStream newFile = new FileStream( path, @@ -2162,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) @@ -2174,20 +2251,24 @@ protected override void NewItem( { string action = null; if (itemType == ItemType.SymbolicLink) + { action = FileSystemProviderStrings.NewItemActionSymbolicLink; + } else if (itemType == ItemType.HardLink) + { action = FileSystemProviderStrings.NewItemActionHardLink; + } string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path); if (ShouldProcess(resource, action)) { bool isDirectory = false; - string strTargetPath = value.ToString(); + string strTargetPath = value?.ToString(); - if (String.IsNullOrEmpty(strTargetPath)) + if (string.IsNullOrEmpty(strTargetPath)) { - throw PSTraceSource.NewArgumentNullException("value"); + throw PSTraceSource.NewArgumentNullException(nameof(value)); } bool exists = false; @@ -2197,9 +2278,29 @@ protected override void NewItem( // non-existing targets on either Windows or Linux. try { - exists = CheckItemExists(strTargetPath, out isDirectory); if (itemType == ItemType.SymbolicLink) - exists = true; // pretend the target exists if we're making a symbolic link + { + exists = true; + + // 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); + } + 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; + } } catch (Exception e) { @@ -2216,8 +2317,8 @@ protected override void NewItem( if (itemType == ItemType.HardLink) { - //Hard links can only be to files, not directories. - if (isDirectory == true) + // Hard links can only be to files, not directories. + if (isDirectory) { string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFile, strTargetPath); WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotFile", ErrorCategory.InvalidOperation, strTargetPath)); @@ -2230,7 +2331,7 @@ protected override void NewItem( try { - symLinkExists = CheckItemExists(path, out isSymLinkDirectory); + symLinkExists = GetFileSystemInfo(path, out isSymLinkDirectory) != null; } catch (Exception e) { @@ -2240,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) @@ -2266,7 +2374,9 @@ protected override void NewItem( WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path)); } else + { throw; + } } } else @@ -2294,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 } @@ -2308,7 +2418,7 @@ protected override void NewItem( #if UNIX if (Platform.Unix.GetErrorCategory(errorCode) == ErrorCategory.PermissionDenied) #else - if (errorCode == 1314) //ERROR_PRIVILEGE_NOT_HELD + if (errorCode == 1314) // ERROR_PRIVILEGE_NOT_HELD #endif { string message = FileSystemProviderStrings.ElevationRequired; @@ -2316,13 +2426,17 @@ protected override void NewItem( return; } - if (errorCode == 1) //ERROR_INVALID_FUNCTION + if (errorCode == 1) // ERROR_INVALID_FUNCTION { string message = null; if (itemType == ItemType.SymbolicLink) + { message = FileSystemProviderStrings.SymbolicLinkNotSupported; + } else + { message = FileSystemProviderStrings.HardLinkNotSupported; + } WriteError(new ErrorRecord(new InvalidOperationException(message, w32Exception), "NewItemInvalidOperation", ErrorCategory.InvalidOperation, value.ToString())); return; @@ -2353,13 +2467,20 @@ protected override void NewItem( if (ShouldProcess(resource, action)) { bool isDirectory = false; - string strTargetPath = value.ToString(); + string strTargetPath = value?.ToString(); 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 = CheckItemExists(strTargetPath, out isDirectory); + exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null; } catch (Exception e) { @@ -2369,11 +2490,12 @@ protected override void NewItem( if (!exists) { - WriteError(new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.ItemNotFound), "ItemNotFound", ErrorCategory.ObjectNotFound, value)); + string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, strTargetPath); + WriteError(new ErrorRecord(new ItemNotFoundException(message), "ItemNotFound", ErrorCategory.ObjectNotFound, strTargetPath)); return; } - //Junctions can only be directories. + // Junctions can only be directories. if (!isDirectory) { string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, value); @@ -2382,12 +2504,11 @@ protected override void NewItem( } bool isPathDirectory = false; - - bool pathExists = false; + FileSystemInfo pathDirInfo; try { - pathExists = CheckItemExists(path, out isPathDirectory); + pathDirInfo = GetFileSystemInfo(path, out isPathDirectory); } catch (Exception e) { @@ -2395,11 +2516,11 @@ protected override void NewItem( return; } - DirectoryInfo pathDirInfo = new DirectoryInfo(path); + bool pathExists = pathDirInfo != null; if (pathExists) { - //Junctions can only be directories. + // Junctions can only be directories. if (!isPathDirectory) { string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, path); @@ -2407,38 +2528,36 @@ protected override void NewItem( return; } - //Junctions cannot have files - if (DirectoryInfoHasChildItems(pathDirInfo)) + // Junctions cannot have files + 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); - } + + CreateDirectory(path, streamOutput: false); + pathDirInfo = new DirectoryInfo(path); try { @@ -2448,7 +2567,7 @@ protected override void NewItem( { WriteItemObject(pathDirInfo, path, true); } - else //rollback the directory creation if we created it. + else // rollback the directory creation if we created it. { if (!pathExists) { @@ -2458,7 +2577,7 @@ protected override void NewItem( } catch (Exception exception) { - //rollback the directory creation if it was created. + // rollback the directory creation if it was created. if (!pathExists) { pathDirInfo.Delete(); @@ -2478,28 +2597,33 @@ protected override void NewItem( WriteError(new ErrorRecord(exception, "NewItemCreateIOError", ErrorCategory.WriteError, path)); } else + { throw; + } } } } else { - throw PSTraceSource.NewArgumentException("type", FileSystemProviderStrings.UnknownType); + throw PSTraceSource.NewArgumentException(nameof(type), FileSystemProviderStrings.UnknownType); } - } // NewItem + } +#if !UNIX private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory) { - int created = NativeMethods.CreateSymbolicLink(path, strTargetPath, (isDirectory ? 1 : 0)); - bool success = (created == 1) ? true : false; - return success; - } + // The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer + var flags = isDirectory ? Interop.Windows.SymbolicLinkFlags.Directory : Interop.Windows.SymbolicLinkFlags.File; - private static bool WinCreateHardLink(string path, string strTargetPath) - { - bool success = NativeMethods.CreateHardLink(path, strTargetPath, IntPtr.Zero); - return success; + if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 14972, 0)) + { + flags |= Interop.Windows.SymbolicLinkFlags.AllowUnprivilegedCreate; + } + + var created = Interop.Windows.CreateSymbolicLink(path, strTargetPath, flags); + return created; } +#endif private static bool WinCreateJunction(string path, string strTargetPath) { @@ -2507,26 +2631,6 @@ private static bool WinCreateJunction(string path, string strTargetPath) return junctionCreated; } - /// - /// Checks if the item exists and throws exception on access. - /// - /// - /// - /// - private static bool CheckItemExists(string strTargetPath, out bool isDirectory) - { - Exception accessException; - - bool exists = Utils.NativeItemExists(strTargetPath, out isDirectory, out accessException); - - if (accessException != null) - { - throw accessException; - } - - return exists; - } - private enum ItemType { Unknown, @@ -2535,8 +2639,7 @@ private enum ItemType SymbolicLink, Junction, HardLink - }; - + } private static ItemType GetItemType(string input) { @@ -2573,33 +2676,24 @@ private static ItemType GetItemType(string input) } /// - /// Creates a directory at the specified path + /// Creates a directory at the specified path. /// - /// /// /// The path of the directory to create /// - /// /// /// Determines if the directory should be streamed out after being created. /// - /// private void CreateDirectory(string path, bool streamOutput) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(path), + !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)) { - String errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path); + string errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path); Exception e = new IOException(errorMessage); WriteError(new ErrorRecord( @@ -2625,10 +2719,7 @@ private void CreateDirectory(string path, bool streamOutput) if (ShouldProcess(resource, action)) { - // Use the parent directory to create the sub-directory - - DirectoryInfo parentDirectory = new DirectoryInfo(parentPath); - DirectoryInfo result = parentDirectory.CreateSubdirectory(childName); + var result = Directory.CreateDirectory(path); if (streamOutput) { @@ -2643,11 +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)); } } @@ -2655,29 +2752,28 @@ private void CreateDirectory(string path, bool streamOutput) { WriteError(new ErrorRecord(accessException, "CreateDirectoryUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } - } // CreateDirectory + } private bool CreateIntermediateDirectories(string path) { bool result = false; - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } try { // Push the paths of the missing directories onto a stack such that the highest missing // parent in the tree is at the top of the stack. - - Stack missingDirectories = new Stack(); + Stack missingDirectories = new Stack(); string previousParent = path; do { - string root = String.Empty; + string root = string.Empty; if (PSDriveInfo != null) { @@ -2686,11 +2782,11 @@ private bool CreateIntermediateDirectories(string path) string parentPath = GetParentPath(path, root); - if (!String.IsNullOrEmpty(parentPath) && - String.Compare( + if (!string.IsNullOrEmpty(parentPath) && + !string.Equals( parentPath, previousParent, - StringComparison.OrdinalIgnoreCase) != 0) + StringComparison.OrdinalIgnoreCase)) { if (!ItemExists(parentPath)) { @@ -2705,15 +2801,16 @@ private bool CreateIntermediateDirectories(string path) { break; } + previousParent = parentPath; - } while (!String.IsNullOrEmpty(previousParent)); + } while (!string.IsNullOrEmpty(previousParent)); // Now create the missing directories - foreach (string directoryPath in missingDirectories) { CreateDirectory(directoryPath, false); } + result = true; } catch (ArgumentException argException) @@ -2722,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) @@ -2731,8 +2828,7 @@ private bool CreateIntermediateDirectories(string path) } return result; - } // CreateIntermediateDirectories - + } #endregion NewItem @@ -2741,23 +2837,20 @@ private bool CreateIntermediateDirectories(string path) /// /// Removes the specified file or directory. /// - /// /// /// The full path to the file or directory to be removed. /// - /// /// /// Specifies if the operation should also remove child items. /// - /// /// /// path is null or empty. /// protected override void RemoveItem(string path, bool recurse) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } try @@ -2796,16 +2889,28 @@ protected override void RemoveItem(string path, bool recurse) } #endif - bool iscontainer = false; - FileSystemInfo fsinfo = GetFileSystemInfo(path, ref iscontainer); + FileSystemInfo fsinfo = GetFileSystemInfo(path, out bool iscontainer); if (fsinfo == null) { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord(e, "ItemDoesNotExist", ErrorCategory.ObjectNotFound, path)); 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) { @@ -2833,10 +2938,14 @@ 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; - string action = String.Format( + string action = string.Format( CultureInfo.InvariantCulture, FileSystemProviderStrings.StreamAction, stream.Stream, fsinfo.FullName); @@ -2865,24 +2974,34 @@ 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) { WriteError(new ErrorRecord(accessException, "RemoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } - } // RemoveItem + } /// - /// Retrieves the dynamic parameters required for the Remove-Item cmdlet + /// Retrieves the dynamic parameters required for the Remove-Item cmdlet. /// - /// The path of the file to process - /// Whether to recurse into containers + /// The path of the file to process. + /// Whether to recurse into containers. /// An instance of the FileSystemProviderRemoveItemDynamicParameters class that represents the dynamic parameters. protected override object RemoveItemDynamicParameters(string path, bool recurse) { @@ -2899,26 +3018,21 @@ protected override object RemoveItemDynamicParameters(string path, bool recurse) /// /// Removes a directory from the file system. /// - /// /// /// The DirectoryInfo object representing the directory to be removed. /// - /// /// /// If true, ShouldProcess will be called for each item in the subtree. /// If false, ShouldProcess will only be called for the directory item. /// - /// /// /// If true, attempts to modify the file attributes in case of a failure so that /// the file can be removed. /// - /// /// /// True if the DirectoryInfo being passed in is the root of the tree being removed. /// ShouldProcess will be called if this is true or if recurse is true. /// - /// private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool force, bool rootOfRemoval) { Dbg.Diagnostics.Assert(directory != null, "Caller should always check directory"); @@ -2934,42 +3048,35 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool continueRemoval = ShouldProcess(directory.FullName, action); } - if ((directory.Attributes & FileAttributes.ReparsePoint) != 0) + if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(directory)) { - bool success = InternalSymbolicLinkLinkCodeMethods.DeleteJunction(directory.FullName); - - if (!success) + void WriteErrorHelper(Exception exception) { - string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName); - Exception e = new IOException(error); - WriteError(new ErrorRecord(e, "DeleteJunctionFailed", ErrorCategory.WriteError, directory)); - return; + WriteError(new ErrorRecord(exception, errorId: "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory)); } - bool isDirectory; - Exception accessException; - - if (!Utils.NativeItemExists(directory.FullName, out isDirectory, out accessException)) + try { - return; + if (InternalTestHooks.OneDriveTestOn) + { + WriteErrorHelper(new IOException()); + return; + } + else + { + // Name surrogates should just be detached. + directory.Delete(); + } } - - if (accessException != null) + catch (Exception e) { - ErrorRecord errorRecord = new ErrorRecord(accessException, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, directory); - - ErrorDetails errorDetails = - new ErrorDetails(this, "FileSystemProviderStrings", - "CannotRemoveItem", - directory.FullName, - accessException.Message); - - errorRecord.ErrorDetails = errorDetails; - - WriteError(errorRecord); - return; + string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName, e.Message); + var exception = new IOException(error, e); + WriteErrorHelper(exception); } - } + + return; + } if (continueRemoval) { @@ -2992,7 +3099,7 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool // Loop through each of the contained files and remove them. IEnumerable files = null; - if (!String.IsNullOrEmpty(Filter)) + if (!string.IsNullOrEmpty(Filter)) { files = directory.EnumerateFiles(Filter); } @@ -3011,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 @@ -3023,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); + } + } } } @@ -3031,7 +3159,7 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool if (hasChildren && !force) { - String error = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, directory.FullName); + string error = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, directory.FullName); Exception e = new IOException(error); WriteError(new ErrorRecord(e, "DirectoryNotEmpty", ErrorCategory.WriteError, directory)); } @@ -3040,22 +3168,19 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool // Finally, remove the directory RemoveFileSystemItem(directory, force); } - } // ShouldProcess - } // RemoveDirectoryInfoItem + } + } /// /// Removes a file from the file system. /// - /// /// /// The FileInfo object representing the file to be removed. /// - /// /// /// If true, attempts to modify the file attributes in case of a failure so that /// the file can be removed. /// - /// private void RemoveFileInfoItem(FileInfo file, bool force) { Dbg.Diagnostics.Assert( @@ -3067,34 +3192,31 @@ private void RemoveFileInfoItem(FileInfo file, bool force) if (ShouldProcess(file.FullName, action)) { RemoveFileSystemItem(file, force); - } // ShouldProcess - } // RemoveFileInfoItem + } + } /// /// Removes the file system object from the file system. /// - /// /// /// The FileSystemInfo object representing the file or directory to be removed. /// - /// /// /// If true, the readonly and hidden attributes will be masked off in the case of /// an error, and the removal will be attempted again. If false, exceptions are /// written to the error pipeline. /// - /// private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force) { Dbg.Diagnostics.Assert( fileSystemInfo != null, "Caller should always check fileSystemInfo"); - //First check if we can delete this file when force is not specified. + // First check if we can delete this file when force is not specified. if (!Force && (fileSystemInfo.Attributes & (FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly)) != 0) { - String error = StringUtil.Format(FileSystemProviderStrings.PermissionError); + string error = StringUtil.Format(FileSystemProviderStrings.PermissionError); Exception e = new IOException(error); ErrorDetails errorDetails = @@ -3120,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; } @@ -3203,7 +3325,7 @@ private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force) } } } - } // RemoveFileSystemItem + } #endregion RemoveItem @@ -3212,19 +3334,15 @@ private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force) /// /// Determines if a file or directory exists at the specified path. /// - /// /// /// The path of the item to check. /// - /// /// /// True if a file or directory exists at the specified path, false otherwise. /// - /// /// /// path is null or empty. /// - /// protected override bool ItemExists(string path) { ErrorRecord error = null; @@ -3234,38 +3352,34 @@ protected override bool ItemExists(string path) { WriteError(error); } + return result; } /// /// Implementation of ItemExists for the provider. This implementation /// allows the caller to decide if it wants to WriteError or not based - /// on the returned ErrorRecord + /// on the returned ErrorRecord. /// - /// /// /// The path of the object to check /// - /// /// /// An error record is returned in this parameter if there was an error. /// - /// /// /// True if an object exists at the specified path, false otherwise. /// - /// /// /// path is null or empty. /// - /// private bool ItemExists(string path, out ErrorRecord error) { error = null; - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } bool result = false; @@ -3274,19 +3388,8 @@ private bool ItemExists(string path, out ErrorRecord error) try { - bool notUsed; - Exception accessException; - - // First see if the file exists - if (Utils.NativeItemExists(path, out notUsed, out accessException)) - { - result = true; - } - - if (accessException != null) - { - throw accessException; - } + var fsinfo = GetFileSystemInfo(path, out bool _); + result = fsinfo != null; FileSystemItemProviderDynamicParameters itemExistsDynamicParameters = DynamicParameters as FileSystemItemProviderDynamicParameters; @@ -3294,15 +3397,16 @@ private bool ItemExists(string path, out ErrorRecord error) // If the items see if we need to check the age of the file... if (result && itemExistsDynamicParameters != null) { - DateTime lastWriteTime = File.GetLastWriteTime(path); + DateTime lastWriteTime = fsinfo.LastWriteTime; 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; } } } @@ -3328,17 +3432,15 @@ private bool ItemExists(string path, out ErrorRecord error) } return result; - } // Exists + } /// /// Adds -OlderThan, -NewerThan dynamic properties. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -3352,8 +3454,7 @@ protected override object ItemExistsDynamicParameters(string path) { return new FileSystemItemProviderDynamicParameters(); } - } // ItemExistsDynamicParameters - + } #endregion ItemExists @@ -3362,16 +3463,13 @@ protected override object ItemExistsDynamicParameters(string path) /// /// Determines if the given path is a directory, and has children. /// - /// /// /// The full path to the directory. /// - /// /// /// True if the path refers to a directory that contains other /// directories or files. False otherwise. /// - /// /// /// path is null or empty. /// @@ -3380,10 +3478,9 @@ protected override bool HasChildItems(string path) bool result = false; // verify parameters - - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -3405,7 +3502,6 @@ protected override bool HasChildItems(string path) // Since we couldn't convert the path to a DirectoryInfo // the path could not be a file system container with // children - result = false; } catch (ArgumentException) @@ -3413,7 +3509,6 @@ protected override bool HasChildItems(string path) // Since we couldn't convert the path to a DirectoryInfo // the path could not be a file system container with // children - result = false; } catch (UnauthorizedAccessException) @@ -3421,7 +3516,6 @@ protected override bool HasChildItems(string path) // Since we couldn't convert the path to a DirectoryInfo // the path could not be a file system container with // children - result = false; } catch (IOException) @@ -3429,7 +3523,6 @@ protected override bool HasChildItems(string path) // Since we couldn't convert the path to a DirectoryInfo // the path could not be a file system container with // children - result = false; } catch (NotSupportedException) @@ -3439,7 +3532,7 @@ protected override bool HasChildItems(string path) } return result; - } // HasChildItems + } private static bool DirectoryInfoHasChildItems(DirectoryInfo directory) { @@ -3457,7 +3550,7 @@ private static bool DirectoryInfoHasChildItems(DirectoryInfo directory) } return result; - } // DirectoryInfoHasChildItems + } #endregion HasChildItems @@ -3466,24 +3559,19 @@ private static bool DirectoryInfoHasChildItems(DirectoryInfo directory) /// /// Copies an item at the specified path to the given destination. /// - /// /// /// The path of the item to copy. /// - /// /// /// The path of the destination. /// - /// /// /// Specifies if the operation should also copy child items. /// - /// /// /// path is null or empty. /// destination path is null or empty. /// - /// /// /// Nothing. Copied items are written to the context's pipeline. /// @@ -3492,14 +3580,14 @@ protected override void CopyItem( string destinationPath, bool recurse) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(destinationPath)) + if (string.IsNullOrEmpty(destinationPath)) { - throw PSTraceSource.NewArgumentException("destinationPath"); + throw PSTraceSource.NewArgumentException(nameof(destinationPath)); } path = NormalizePath(path); @@ -3527,7 +3615,7 @@ protected override void CopyItem( // if the source and destination path are same (for a local copy) then flag it as error. if ((toSession == null) && (fromSession == null) && InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(path, destinationPath)) { - String error = StringUtil.Format(FileSystemProviderStrings.CopyError, path); + string error = StringUtil.Format(FileSystemProviderStrings.CopyError, path); Exception e = new IOException(error); e.Data[SelfCopyDataKey] = destinationPath; WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, path)); @@ -3538,7 +3626,6 @@ protected override void CopyItem( { CopyItemFromRemoteSession(path, destinationPath, recurse, Force, fromSession); } - else { // Copy-Item to session @@ -3550,17 +3637,74 @@ protected override void CopyItem( CopyItemLocalOrToSession(path, destinationPath, recurse, Force, ps); } } - - // Copy-Item local - else + else // Copy-Item local { + if (Context != null && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference && progressPreference == ActionPreference.Continue) + { + { + Task.Run(() => + { + GetTotalFiles(path, recurse); + }); + _copyStopwatch.Start(); + } + } + CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null); + if (Stopping || _copiedFiles == _totalFiles) + { + _copyStopwatch.Stop(); + var progress = new ProgressRecord(COPY_FILE_ACTIVITY_ID, " ", " "); + progress.RecordType = ProgressRecordType.Completed; + WriteProgress(progress); + } } } _excludeMatcher.Clear(); _excludeMatcher = null; - } //CopyItem + } + + 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) { @@ -3579,7 +3723,7 @@ private void CopyItemFromRemoteSession(string path, string destinationPath, bool Hashtable op = SafeInvokeCommand.Invoke(ps, this, null); if (op == null) { - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile, path)); + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile, path)); WriteError(new ErrorRecord(e, "CopyItemRemotelyFailedToReadFile", ErrorCategory.WriteError, path)); return; } @@ -3592,8 +3736,6 @@ private void CopyItemFromRemoteSession(string path, string destinationPath, bool if (op["Items"] != null) { - bool destinationPathIsFile = Utils.NativeFileExists(destinationPath); - PSObject obj = (PSObject)op["Items"]; ArrayList itemsList = (ArrayList)obj.BaseObject; foreach (PSObject item in itemsList) @@ -3605,9 +3747,9 @@ private void CopyItemFromRemoteSession(string path, string destinationPath, bool if (isContainer) { - if (destinationPathIsFile) + if (File.Exists(destinationPath)) { - Exception e = new IOException(String.Format( + Exception e = new IOException(string.Format( CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyDestinationIsFile, path, @@ -3641,7 +3783,7 @@ private void CopyItemFromRemoteSession(string path, string destinationPath, bool RemoveFunctionsPSCopyFileFromRemoteSession(ps); } } - } // CopyItemFromRemoteSession + } private void CopyItemLocalOrToSession(string path, string destinationPath, bool recurse, bool Force, System.Management.Automation.PowerShell ps) { @@ -3657,7 +3799,6 @@ private void CopyItemLocalOrToSession(string path, string destinationPath, bool DirectoryInfo dir = new DirectoryInfo(path); // Now copy the directory to the destination - CopyDirectoryInfoItem(dir, destinationPath, recurse, Force, ps); } else // !isContainer @@ -3672,7 +3813,7 @@ private void CopyItemLocalOrToSession(string path, string destinationPath, bool { RemoveFunctionPSCopyFileToRemoteSession(ps); } - } // CopyItem + } private void CopyDirectoryInfoItem( DirectoryInfo directory, @@ -3710,7 +3851,6 @@ private void CopyDirectoryInfoItem( s_tracer.WriteLine("destination = {0}", destination); // Confirm the copy with the user - string action = FileSystemProviderStrings.CopyItemActionDirectory; string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, directory.FullName, destination); @@ -3719,7 +3859,6 @@ private void CopyDirectoryInfoItem( { // Create the new directory // CreateDirectory does the WriteItemObject for the new DirectoryInfo - if (ps == null) { CreateDirectory(destination, true); @@ -3729,7 +3868,7 @@ private void CopyDirectoryInfoItem( // Verify that the destination is not a file on the remote end if (RemoteDestinationPathIsFile(destination, ps)) { - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemoteDestinationIsFile, destination)); WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destination)); @@ -3746,10 +3885,9 @@ private void CopyDirectoryInfoItem( if (recurse) { // Now copy all the files to that directory - IEnumerable files = null; - if (String.IsNullOrEmpty(Filter)) + if (string.IsNullOrEmpty(Filter)) { files = directory.EnumerateFiles(); } @@ -3771,7 +3909,6 @@ private void CopyDirectoryInfoItem( try { // CopyFileInfoItem does the WriteItemObject for the new FileInfo - CopyFileInfoItem(file, destination, force, ps); } catch (ArgumentException argException) @@ -3780,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) @@ -3788,10 +3925,9 @@ private void CopyDirectoryInfoItem( WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file)); } } - } // for files + } // Now copy all the directories to that directory - foreach (DirectoryInfo childDir in directory.EnumerateDirectories()) { // Making sure to obey the StopProcessing. @@ -3812,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) @@ -3820,10 +3956,10 @@ private void CopyDirectoryInfoItem( WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, childDir)); } } - } // for directories + } } - } // ShouldProcess - } // CopyDirectoryInfoItem + } + } private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, System.Management.Automation.PowerShell ps) { @@ -3833,7 +3969,6 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, // If the destination is a container, add the file name // to the destination path. - if (ps == null) { if (IsItemContainer(destinationPath)) @@ -3841,10 +3976,10 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, destinationPath = MakePath(destinationPath, file.Name); } - //if the source and destination path are same then flag it as error. + // if the source and destination path are same then flag it as error. if (InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(destinationPath, file.FullName)) { - String error = StringUtil.Format(FileSystemProviderStrings.CopyError, destinationPath); + string error = StringUtil.Format(FileSystemProviderStrings.CopyError, destinationPath); Exception e = new IOException(error); e.Data[SelfCopyDataKey] = file.FullName; WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath)); @@ -3858,7 +3993,6 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, } // Confirm the copy with the user - string action = FileSystemProviderStrings.CopyItemActionFile; string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, file.FullName, destinationPath); @@ -3876,11 +4010,29 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, // Now copy the file // We assume that if we get called we want to make // the copy even if the destination already exists. - file.CopyTo(destinationPath, true); 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 { @@ -3898,11 +4050,9 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, // If the destination exists and force is specified, // mask of the readonly and hidden attributes and // try again - FileInfo destinationItem = new FileInfo(destinationPath); - destinationItem.Attributes = - destinationItem.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + destinationItem.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); } else { @@ -3921,22 +4071,28 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file)); } else + { throw; + } } file.CopyTo(destinationPath, true); FileInfo result = new FileInfo(destinationPath); WriteItemObject(result, destinationPath, false); - } // force + } else { WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file)); } } - } // ShouldProcess - }// ExcludeFile - } // CopyFileInfoItem + catch (IOException ioException) + { + WriteError(new ErrorRecord(ioException, "CopyFileInfoItemIOError", ErrorCategory.WriteError, file)); + } + } + } + } private void CopyDirectoryFromRemoteSession( string sourceDirectoryName, @@ -3966,7 +4122,7 @@ private void CopyDirectoryFromRemoteSession( CreateDirectory(destination, false); // If failed to create directory - if (!Utils.NativeDirectoryExists(destination)) + if (!Directory.Exists(destination)) { return; } @@ -3980,7 +4136,7 @@ private void CopyDirectoryFromRemoteSession( Hashtable op = SafeInvokeCommand.Invoke(ps, this, null); if (op == null) { - Exception e = new IOException(String.Format( + Exception e = new IOException(string.Format( CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToGetDirectoryChildItems, sourceDirectoryFullName)); @@ -4019,7 +4175,7 @@ private void CopyDirectoryFromRemoteSession( fileSize); } } - } // for files + } if (op["Directories"] != null) { @@ -4046,10 +4202,10 @@ private void CopyDirectoryFromRemoteSession( recurse, ps); } - } // for directories + } } - } // ShouldProcess - } // CopyDirectoryFromRemoteSession + } + } private ArrayList GetRemoteSourceAlternateStreams(System.Management.Automation.PowerShell ps, string path) { @@ -4076,7 +4232,10 @@ private ArrayList GetRemoteSourceAlternateStreams(System.Management.Automation.P private void InitializeFunctionPSCopyFileFromRemoteSession(System.Management.Automation.PowerShell ps) { - if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; } + if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) + { + return; + } ps.AddScript(CopyFileRemoteUtils.AllCopyFromRemoteScripts); SafeInvokeCommand.Invoke(ps, this, null, false); @@ -4084,9 +4243,12 @@ private void InitializeFunctionPSCopyFileFromRemoteSession(System.Management.Aut private void RemoveFunctionsPSCopyFileFromRemoteSession(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:PSCopyFromSessionHelper -ea SilentlyContinue -Force Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force "; @@ -4094,9 +4256,12 @@ 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)) { return false; } + if (runspace is not RemoteRunspace) + { + return false; + } PSLanguageMode languageMode = runspace.SessionStateProxy.LanguageMode; if (languageMode == PSLanguageMode.ConstrainedLanguage || languageMode == PSLanguageMode.NoLanguage) @@ -4129,6 +4294,7 @@ private void SetFileMetadata(string sourceFileFullName, FileInfo destinationFile { destinationFile.LastWriteTimeUtc = (DateTime)metadata["LastWriteTimeUtc"]; } + if (metadata["LastWriteTime"] != null) { destinationFile.LastWriteTime = (DateTime)metadata["LastWriteTime"]; @@ -4140,21 +4306,21 @@ private void SetFileMetadata(string sourceFileFullName, FileInfo destinationFile PSObject obj = (PSObject)metadata["Attributes"]; foreach (string value in (ArrayList)obj.BaseObject) { - if (String.Equals(value, "ReadOnly", StringComparison.OrdinalIgnoreCase)) + 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)) + 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)) + 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)) + else if (string.Equals(value, "System", StringComparison.OrdinalIgnoreCase)) { - destinationFile.Attributes = destinationFile.Attributes | FileAttributes.System; + destinationFile.Attributes |= FileAttributes.System; } } } @@ -4216,18 +4382,18 @@ private void CopyFileFromRemoteSession( { SetFileMetadata(sourceFileFullName, destinationFile, ps); } - } // ShouldProcess + } } private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInfo destinationFile, string destinationPath, bool force, System.Management.Automation.PowerShell ps, long fileSize, bool isAlternateDataStream, string streamName) { bool success = false; - string activity = String.Format(CultureInfo.InvariantCulture, + string activity = string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyProgressActivity, sourceFileFullName, destinationFile.FullName); - string statusDescription = String.Format(CultureInfo.InvariantCulture, + string statusDescription = string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyStatusDescription, ps.Runspace.ConnectionInfo.ComputerName, "localhost"); @@ -4247,8 +4413,9 @@ 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); } @@ -4259,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; @@ -4271,14 +4438,14 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf ps.AddParameter("copyFromNumBytes", fragmentSize); if (force) { - ps.AddParameter("force", true); + ps.AddParameter(nameof(force), true); } #if !UNIX if (isAlternateDataStream) { ps.AddParameter("isAlternateStream", true); - ps.AddParameter("streamName", streamName); + ps.AddParameter(nameof(streamName), streamName); } #endif @@ -4288,7 +4455,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf if (op == null) { errorWhileCopyRemoteFile = true; - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile, sourceFileFullName)); WriteError(new ErrorRecord(e, "FailedToCopyFileFromRemoteSession", ErrorCategory.WriteError, sourceFileFullName)); @@ -4306,12 +4473,13 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf } } - // To accomodate empty files - String content = ""; + // To accommodate empty files + string content = string.Empty; if (op["b64Fragment"] != null) { - content = (String)op["b64Fragment"]; + content = (string)op["b64Fragment"]; } + bool more = (bool)op["moreAvailable"]; currentIndex += fragmentSize; byte[] bytes = System.Convert.FromBase64String(content); @@ -4359,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)) @@ -4375,12 +4540,16 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf } } } + return success; } 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); @@ -4388,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 "; @@ -4401,7 +4573,6 @@ private void RemoveFunctionPSCopyFileToRemoteSession(System.Management.Automatio // If the target supports alternate data streams the following must be true: // 1) The remote session must be PowerShell V3 or higher to support Streams // 2) The target drive must be NTFS - // private bool RemoteTargetSupportsAlternateStreams(System.Management.Automation.PowerShell ps, string path) { bool supportsAlternateStreams = false; @@ -4431,7 +4602,7 @@ private string MakeRemotePath(System.Management.Automation.PowerShell ps, string string path = null; ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName); - ps.AddParameter("remotePath", remotePath); + ps.AddParameter(nameof(remotePath), remotePath); Hashtable op = SafeInvokeCommand.Invoke(ps, this, null); if (op != null) @@ -4440,10 +4611,12 @@ private string MakeRemotePath(System.Management.Automation.PowerShell ps, string { isDirectoryInfo = (bool)op["IsDirectoryInfo"]; } + if (op["IsFileInfo"] != null) { isFileInfo = (bool)op["IsFileInfo"]; } + if (op["ParentIsDirectoryInfo"] != null) { parentIsDirectoryInfo = (bool)op["ParentIsDirectoryInfo"]; @@ -4492,11 +4665,11 @@ private bool RemoteDirectoryExist(System.Management.Automation.PowerShell ps, st private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath, System.Management.Automation.PowerShell ps, bool isAlternateStream, string streamName) { - string activity = String.Format(CultureInfo.InvariantCulture, + string activity = string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyProgressActivity, file.FullName, destinationPath); - string statusDescription = String.Format(CultureInfo.InvariantCulture, + string statusDescription = string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyStatusDescription, "localhost", ps.Runspace.ConnectionInfo.ComputerName); @@ -4507,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; @@ -4526,7 +4699,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath fStream = AlternateDataStreamUtilities.CreateFileStream(file.FullName, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } #endif - long remainingFileSize = fStream.Length; + long remainingFileSize = fStream != null ? fStream.Length : 0; do { if (Stopping) @@ -4540,6 +4713,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath { toRead = (int)remainingFileSize; } + if (fragment == null) { fragment = new byte[toRead]; @@ -4554,6 +4728,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath { readSoFar += fStream.Read(fragment, 0, toRead); } + remainingFileSize -= readSoFar; string b64Fragment = System.Convert.ToBase64String(fragment); @@ -4588,21 +4763,21 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName); ps.AddParameter("copyToFilePath", destinationPath); ps.AddParameter("b64Fragment", b64Fragment); - ps.AddParameter("streamName", streamName); + ps.AddParameter(nameof(streamName), streamName); } Hashtable op = SafeInvokeCommand.Invoke(ps, this, null); if (op == null || op["BytesWritten"] == null) { - //write error to stream - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file)); + // write error to stream + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file)); WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName)); return false; } if ((int)(op["BytesWritten"]) != toRead) { - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file)); + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file)); WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName)); return false; } @@ -4638,17 +4813,15 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath } finally { - if (fStream != null) - { - fStream.Dispose(); - } + fStream?.Dispose(); } + return success; } // 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(); @@ -4679,9 +4852,9 @@ private bool PerformCopyFileToRemoteSession(FileInfo file, string destinationPat // Make the remote path var remoteFilePath = MakeRemotePath(ps, destinationPath, file.Name); - if (String.IsNullOrEmpty(remoteFilePath)) + if (string.IsNullOrEmpty(remoteFilePath)) { - Exception e = new ArgumentException(String.Format(CultureInfo.InvariantCulture, SessionStateStrings.PathNotFound, destinationPath)); + Exception e = new ArgumentException(string.Format(CultureInfo.InvariantCulture, SessionStateStrings.PathNotFound, destinationPath)); WriteError(new ErrorRecord(e, "RemotePathNotFound", ErrorCategory.WriteError, destinationPath)); return false; } @@ -4696,7 +4869,7 @@ private bool PerformCopyFileToRemoteSession(FileInfo file, string destinationPat { foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(file.FullName)) { - if (!(String.Equals(":$DATA", stream.Stream, StringComparison.OrdinalIgnoreCase))) + if (!(string.Equals(":$DATA", stream.Stream, StringComparison.OrdinalIgnoreCase))) { result = CopyFileStreamToRemoteSession(file, remoteFilePath, ps, true, stream.Stream); if (!result) @@ -4713,7 +4886,7 @@ private bool PerformCopyFileToRemoteSession(FileInfo file, string destinationPat } return result; - } // PerformCopyFileToRemoteSession + } private bool RemoteDestinationPathIsFile(string destination, System.Management.Automation.PowerShell ps) { @@ -4724,7 +4897,7 @@ private bool RemoteDestinationPathIsFile(string destination, System.Management.A if (op == null || op["IsFileInfo"] == null) { - Exception e = new IOException(String.Format( + Exception e = new IOException(string.Format( CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToValidateIfDestinationIsFile, destination)); @@ -4741,7 +4914,7 @@ private string CreateDirectoryOnRemoteSession(string destination, bool force, Sy ps.AddParameter("createDirectoryPath", destination); if (force) { - ps.AddParameter("force", true); + ps.AddParameter(nameof(force), true); } Hashtable op = SafeInvokeCommand.Invoke(ps, this, null); @@ -4756,13 +4929,14 @@ private string CreateDirectoryOnRemoteSession(string destination, bool force, Sy if (force && (op["DirectoryPath"] == null)) { - Exception e = new IOException(String.Format(CultureInfo.InvariantCulture, + Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToCreateDirectory, destination)); WriteError(new ErrorRecord(e, "FailedToCreateDirectory", ErrorCategory.WriteError, destination)); return null; } - string path = (String)(op["DirectoryPath"]); + + string path = (string)(op["DirectoryPath"]); if ((!force) && (bool)op["PathExists"]) { @@ -4772,7 +4946,7 @@ private string CreateDirectoryOnRemoteSession(string destination, bool force, Sy } return path; - } // CreateDirectoryOnRemoteSession + } // Returns true if the destination path represents a device name, and write an error to the user. private bool PathIsReservedDeviceName(string destinationPath, string errorId) @@ -4781,7 +4955,7 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId) if (Utils.IsReservedDeviceName(destinationPath)) { pathIsReservedDeviceName = true; - String error = StringUtil.Format(FileSystemProviderStrings.TargetCannotContainDeviceName, destinationPath); + string error = StringUtil.Format(FileSystemProviderStrings.TargetCannotContainDeviceName, destinationPath); Exception e = new IOException(error); WriteError(new ErrorRecord(e, errorId, ErrorCategory.WriteError, destinationPath)); } @@ -4789,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 @@ -4798,22 +4983,19 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId) /// /// Gets the parent of the given path. /// - /// /// /// The path of which to get the parent. /// - /// /// /// The root of the drive. /// - /// /// /// The parent of the given path. /// protected override string GetParentPath(string path, string root) { string parentPath = base.GetParentPath(path, root); - if (!IsUNCPath(path)) + if (!Utils.PathIsUnc(path)) { parentPath = EnsureDriveIsRooted(parentPath); } @@ -4823,36 +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; - } // GetParentPath + } // 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; - } - - - private static bool IsUNCPath(string path) - { - return path.StartsWith("\\\\", StringComparison.Ordinal); + return path.Contains(':'); } /// @@ -4861,22 +5037,19 @@ private static bool IsUNCPath(string path) /// one path separator is found we know the path is in the form /// "\\server\share" and is a valid UNC root. /// - /// /// /// The path to check to see if its a UNC root. /// - /// /// /// True if the path is a UNC root, or false otherwise. /// - /// private static bool IsUNCRoot(string path) { bool result = false; - if (!String.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path)) { - if (IsUNCPath(path)) + if (Utils.PathIsUnc(path)) { int lastIndex = path.Length - 1; @@ -4893,11 +5066,13 @@ private static bool IsUNCRoot(string path) { break; } + --lastIndex; if (lastIndex < 3) { break; } + ++separatorsFound; } while (lastIndex > 3); @@ -4907,29 +5082,27 @@ private static bool IsUNCRoot(string path) } } } + return result; } /// - /// Determines if the specified path is either a drive root or a UNC root + /// Determines if the specified path is either a drive root or a UNC root. /// - /// /// /// The path /// - /// /// /// True if the path is either a drive root or a UNC root, or false otherwise. /// - /// private static bool IsPathRoot(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { return false; } - bool isDriveRoot = String.Equals(path, Path.GetPathRoot(path), StringComparison.OrdinalIgnoreCase); + bool isDriveRoot = string.Equals(path, Path.GetPathRoot(path), StringComparison.OrdinalIgnoreCase); bool isUNCRoot = IsUNCRoot(path); bool result = isDriveRoot || isUNCRoot; s_tracer.WriteLine("result = {0}; isDriveRoot = {1}; isUNCRoot = {2}", result, isDriveRoot, isUNCRoot); @@ -4940,20 +5113,16 @@ private static bool IsPathRoot(string path) /// Normalizes the path that was passed in and returns it as a normalized /// path relative to the given basePath. /// - /// /// /// A fully qualifiedpath to an item. The item must exist, /// or the provider writes out an error. /// - /// /// /// The path that the normalized path should be relative to. /// - /// /// /// A normalized path, relative to the given basePath. /// - /// /// /// path is null or empty. /// @@ -4961,16 +5130,12 @@ protected override string NormalizeRelativePath( string path, string basePath) { - if (String.IsNullOrEmpty(path) || !IsValidPath(path)) + if (string.IsNullOrEmpty(path) || !IsValidPath(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - - if (basePath == null) - { - basePath = String.Empty; - } + basePath ??= string.Empty; s_tracer.WriteLine("basePath = {0}", basePath); @@ -4988,7 +5153,7 @@ protected override string NormalizeRelativePath( basePath = EnsureDriveIsRooted(basePath); result = path; - if (String.IsNullOrEmpty(result)) + if (string.IsNullOrEmpty(result)) { break; } @@ -4996,25 +5161,24 @@ protected override string NormalizeRelativePath( try { string originalPathComparison = path; - if (!originalPathComparison.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase)) + if (!originalPathComparison.EndsWith(StringLiterals.DefaultPathSeparator)) { originalPathComparison += StringLiterals.DefaultPathSeparator; } string basePathComparison = basePath; - if (!basePathComparison.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase)) + if (!basePathComparison.EndsWith(StringLiterals.DefaultPathSeparator)) { basePathComparison += StringLiterals.DefaultPathSeparator; } if (originalPathComparison.StartsWith(basePathComparison, StringComparison.OrdinalIgnoreCase)) { - bool isUNCPath = IsUNCPath(result); - if (!isUNCPath) + if (!Utils.PathIsUnc(result)) { // Add the base path back on so that it can be used for // processing - if (!result.StartsWith(basePath, StringComparison.CurrentCulture)) + if (!result.StartsWith(basePath, StringComparison.Ordinal)) { result = MakePath(basePath, result); } @@ -5028,22 +5192,21 @@ protected override string NormalizeRelativePath( { // Now ensure that we have the proper casing by // getting the names of the files and directories that match + string directoryPath = GetParentPath(result, string.Empty); - string directoryPath = GetParentPath(result, String.Empty); - - if (String.IsNullOrEmpty(directoryPath)) + if (string.IsNullOrEmpty(directoryPath)) { - return String.Empty; + return string.Empty; } #if UNIX - // We don't use the Directory class for Unix because the path + // 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 (!Utils.NativeItemExists(result)) + if (!File.Exists(result) && !Directory.Exists(result)) { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -5057,7 +5220,6 @@ protected override string NormalizeRelativePath( // Use the Directory class to get the real path (this will // ensure the proper casing - IEnumerable files = Directory.EnumerateFiles(directoryPath, leafName); if (files == null || !files.Any()) @@ -5067,7 +5229,7 @@ protected override string NormalizeRelativePath( if (files == null || !files.Any()) { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -5080,13 +5242,13 @@ protected override string NormalizeRelativePath( result = files.First(); #endif - if (result.StartsWith(basePath, StringComparison.CurrentCulture)) + if (result.StartsWith(basePath, StringComparison.Ordinal)) { result = result.Substring(basePath.Length); } else { - String error = StringUtil.Format(FileSystemProviderStrings.PathOutSideBasePath, path); + string error = StringUtil.Format(FileSystemProviderStrings.PathOutSideBasePath, path); Exception e = new ArgumentException(error); WriteError(new ErrorRecord( @@ -5111,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; } @@ -5123,28 +5285,24 @@ protected override string NormalizeRelativePath( } while (false); return result; - } // NormalizeRelativePath + } /// /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// A fully qualified provider specific path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// A normalized path that is relative to the basePath that was passed. The /// provider should parse the path parameter, normalize the path, and then /// return the normalized path relative to the basePath. /// - /// /// /// This method does not have to be purely syntactical parsing of the path. It /// is encouraged that the provider actually use the path to lookup in its store @@ -5154,36 +5312,32 @@ protected override string NormalizeRelativePath( /// to normalize the path and then make it relative to basePath. All string comparisons /// are done using StringComparison.InvariantCultureIgnoreCase. /// - /// private string NormalizeRelativePathHelper(string path, string basePath) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (path.Length == 0) { - return String.Empty; + return string.Empty; } - if (basePath == null) - { - basePath = String.Empty; - } + basePath ??= string.Empty; s_tracer.WriteLine("basePath = {0}", basePath); #if !UNIX // Remove alternate data stream references // See if they've used the inline stream syntax. They have more than one colon. - string alternateDataStream = String.Empty; + string alternateDataStream = string.Empty; int firstColon = path.IndexOf(':'); int secondColon = path.IndexOf(':', firstColon + 1); if (secondColon > 0) { string newPath = path.Substring(0, secondColon); - alternateDataStream = path.Replace(newPath, ""); + alternateDataStream = path.Replace(newPath, string.Empty); path = newPath; } #endif @@ -5204,8 +5358,8 @@ private string NormalizeRelativePathHelper(string path, string basePath) // See if the base and the path are already the same. We resolve this to // ..\Leaf, since resolving "." to "." doesn't offer much information. - if (String.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) && - (!originalPath.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase))) + if (string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) && + (!originalPath.EndsWith(StringLiterals.DefaultPathSeparator))) { string childName = GetChildName(path); result = MakePath("..", childName); @@ -5221,16 +5375,16 @@ private string NormalizeRelativePathHelper(string path, string basePath) if (( !(path + StringLiterals.DefaultPathSeparator).StartsWith( basePath + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase)) && - (!String.IsNullOrEmpty(basePath)) + (!string.IsNullOrEmpty(basePath)) ) { - result = String.Empty; + result = string.Empty; string commonBase = GetCommonBase(path, basePath); Stack parentNavigationStack = TokenizePathToStack(basePath, commonBase); int parentPopCount = parentNavigationStack.Count; - if (String.IsNullOrEmpty(commonBase)) + if (string.IsNullOrEmpty(commonBase)) { parentPopCount--; } @@ -5249,10 +5403,10 @@ private string NormalizeRelativePathHelper(string path, string basePath) // ..\..\dir* // In that case (as above,) we keep the ..\..\directory1 // instead of ".." as would usually be returned - if (!String.IsNullOrEmpty(commonBase)) + if (!string.IsNullOrEmpty(commonBase)) { - if (String.Equals(path, commonBase, StringComparison.OrdinalIgnoreCase) && - (!path.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase))) + if (string.Equals(path, commonBase, StringComparison.OrdinalIgnoreCase) && + (!path.EndsWith(StringLiterals.DefaultPathSeparator))) { string childName = GetChildName(path); result = MakePath("..", result); @@ -5276,14 +5430,14 @@ private string NormalizeRelativePathHelper(string path, string basePath) // on the value of basePath. if (IsPathRoot(path)) { - if (String.IsNullOrEmpty(basePath)) + if (string.IsNullOrEmpty(basePath)) { result = path; break; } else { - result = String.Empty; + result = string.Empty; break; } } @@ -5311,31 +5465,31 @@ private string NormalizeRelativePathHelper(string path, string basePath) } while (false); #if !UNIX - if (!String.IsNullOrEmpty(alternateDataStream)) + if (!string.IsNullOrEmpty(alternateDataStream)) { - result = result + alternateDataStream; + result += alternateDataStream; } #endif return result; - } // NormalizeRelativePathHelper + } private string RemoveRelativeTokens(string path) { string testPath = path.Replace('/', '\\'); if ( - (testPath.IndexOf("\\", StringComparison.OrdinalIgnoreCase) < 0) || - testPath.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) || - testPath.StartsWith("..\\", StringComparison.OrdinalIgnoreCase) || - testPath.EndsWith("\\.", StringComparison.OrdinalIgnoreCase) || - testPath.EndsWith("\\..", StringComparison.OrdinalIgnoreCase) || - (testPath.IndexOf("\\.\\", StringComparison.OrdinalIgnoreCase) > 0) || - (testPath.IndexOf("\\..\\", StringComparison.OrdinalIgnoreCase) > 0)) + !testPath.Contains('\\') || + testPath.StartsWith(".\\", StringComparison.Ordinal) || + testPath.StartsWith("..\\", StringComparison.Ordinal) || + testPath.EndsWith("\\.", StringComparison.Ordinal) || + testPath.EndsWith("\\..", StringComparison.Ordinal) || + testPath.Contains("\\.\\", StringComparison.Ordinal) || + testPath.Contains("\\..\\", StringComparison.Ordinal)) { try { - Stack tokenizedPathStack = TokenizePathToStack(path, ""); - Stack normalizedPath = NormalizeThePath("", tokenizedPathStack); + Stack tokenizedPathStack = TokenizePathToStack(path, string.Empty); + Stack normalizedPath = NormalizeThePath(string.Empty, tokenizedPathStack); return CreateNormalizedRelativePathFromStack(normalizedPath); } catch (UnauthorizedAccessException) @@ -5348,17 +5502,16 @@ private string RemoveRelativeTokens(string path) } /// - /// Get the common base path of two paths + /// Get the common base path of two paths. /// - /// One path - /// Another path + /// One path. + /// Another path. private string GetCommonBase(string path1, string path2) { // Always see if the shorter path is a substring of the // longer path. If it is not, take the child off of the longer // path and compare again. - - while (!String.Equals(path1, path2, StringComparison.OrdinalIgnoreCase)) + while (!string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase)) { if (path2.Length > path1.Length) { @@ -5374,22 +5527,18 @@ private string GetCommonBase(string path1, string path2) } /// - /// Tokenizes the specified path onto a stack + /// Tokenizes the specified path onto a stack. /// - /// /// /// The path to tokenize. /// - /// /// /// The base part of the path that should not be tokenized. /// - /// /// /// A stack containing the tokenized path with leaf elements on the bottom /// of the stack and the most ancestral parent at the top. /// - /// private Stack TokenizePathToStack(string path, string basePath) { Stack tokenizedPathStack = new Stack(); @@ -5401,9 +5550,8 @@ private Stack TokenizePathToStack(string path, string basePath) { // Get the child name and push it onto the stack // if its valid - string childName = GetChildName(tempPath); - if (String.IsNullOrEmpty(childName)) + if (string.IsNullOrEmpty(childName)) { // Push the parent on and then stop s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath); @@ -5421,47 +5569,44 @@ private Stack TokenizePathToStack(string path, string basePath) // - a UNC path that is the root of a UNC share // - not a UNC path and the string length is less than or // equal to 3. "C:\" - tempPath = GetParentPath(tempPath, basePath); if (tempPath.Length >= previousParent.Length || IsPathRoot(tempPath)) { - if (String.IsNullOrEmpty(basePath)) + if (string.IsNullOrEmpty(basePath)) { s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath); tokenizedPathStack.Push(tempPath); } + break; } + previousParent = tempPath; } return tokenizedPathStack; - } // TokenizePathToStack + } /// /// Given the tokenized path, the relative path elements are removed. /// - /// /// /// String containing basepath for which we are trying to find the relative path. /// - /// /// /// A stack containing path elements where the leaf most element is at /// the bottom of the stack and the most ancestral parent is on the top. /// Generally this stack comes from TokenizePathToStack(). /// - /// /// /// A stack in reverse order with the path elements normalized and all relative /// path tokens removed. /// - /// private Stack NormalizeThePath(string basepath, Stack tokenizedPathStack) { Stack normalizedPathStack = new Stack(); - String currentPath = basepath; + string currentPath = basepath; while (tokenizedPathStack.Count > 0) { @@ -5481,14 +5626,14 @@ private Stack NormalizeThePath(string basepath, Stack tokenizedP { // Pop the result and continue processing string poppedName = normalizedPathStack.Pop(); - //update our currentpath to reflect the change. + // update our currentpath to reflect the change. if (currentPath.Length > poppedName.Length) { currentPath = currentPath.Substring(0, currentPath.Length - poppedName.Length - 1); } else { - currentPath = ""; + currentPath = string.Empty; } s_tracer.WriteLine("normalizedPathStack.Pop() : {0}", poppedName); @@ -5503,8 +5648,7 @@ private Stack NormalizeThePath(string basepath, Stack tokenizedP { currentPath = MakePath(currentPath, childName); - Boolean isContainer = false; - FileSystemInfo fsinfo = GetFileSystemInfo(currentPath, ref isContainer); + var fsinfo = GetFileSystemInfo(currentPath, out bool _); // Clean up the child name to proper casing and short-path // expansion if required. Also verify that .NET hasn't over-normalized @@ -5527,45 +5671,41 @@ private Stack NormalizeThePath(string basepath, Stack tokenizedP else { // We couldn't find the item - if ((!isContainer) && - (tokenizedPathStack.Count == 0)) + if (tokenizedPathStack.Count == 0) { throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath); } } } + s_tracer.WriteLine("normalizedPathStack.Push({0})", childName); normalizedPathStack.Push(childName); } return normalizedPathStack; - } // NormalizeThePath + } /// - /// Pops each leaf element of the stack and uses MakePath to generate the relative path + /// Pops each leaf element of the stack and uses MakePath to generate the relative path. /// - /// /// /// The stack containing the leaf elements of the path. /// - /// /// /// A path that is made up of the leaf elements on the given stack. /// - /// /// /// The elements on the stack start from the leaf element followed by its parent /// followed by its parent, etc. Each following element on the stack is the parent /// of the one before it. /// - /// private string CreateNormalizedRelativePathFromStack(Stack normalizedPathStack) { - string leafElement = String.Empty; + string leafElement = string.Empty; while (normalizedPathStack.Count > 0) { - if (String.IsNullOrEmpty(leafElement)) + if (string.IsNullOrEmpty(leafElement)) { leafElement = normalizedPathStack.Pop(); } @@ -5575,22 +5715,19 @@ private string CreateNormalizedRelativePathFromStack(Stack normalizedPat leafElement = MakePath(parentElement, leafElement); } } - return leafElement; - } // CreateNormalizedRelativePathFromStack + return leafElement; + } /// /// Gets the name of the leaf element of the specified path. /// - /// /// /// The fully qualified path to the item. /// - /// /// /// The leaf element of the specified path. /// - /// /// /// path is null or empty. /// @@ -5598,17 +5735,15 @@ protected override string GetChildName(string path) { // Verify the parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } // Normalize the path - path = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); // Trim trailing back slashes - path = path.TrimEnd(StringLiterals.DefaultPathSeparator); string result = null; @@ -5618,7 +5753,6 @@ protected override string GetChildName(string path) if (separatorIndex == -1) { // Since there was no path separator return an empty string - result = EnsureDriveIsRooted(path); } else @@ -5627,21 +5761,19 @@ protected override string GetChildName(string path) } return result; - } // GetChildName + } private static string EnsureDriveIsRooted(string path) { string result = path; // Find the drive separator - int index = path.IndexOf(':'); if (index != -1) { // if the drive separator is the end of the path, add // the root path separator back - if (index + 1 == path.Length) { result = path + StringLiterals.DefaultPathSeparator; @@ -5649,34 +5781,31 @@ private static string EnsureDriveIsRooted(string path) } return result; - } // EnsureDriveIsRooted + } /// /// Determines if the item at the specified path is a directory. /// - /// /// /// The path to the file or directory to check. /// - /// /// /// True if the item at the specified path is a directory. /// False otherwise. /// - /// /// /// path is null or empty. /// protected override bool IsItemContainer(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); - return Utils.NativeDirectoryExists(path); + return Directory.Exists(path); } #region MoveItem @@ -5684,19 +5813,15 @@ protected override bool IsItemContainer(string path) /// /// Moves an item at the specified path to the given destination. /// - /// /// /// The path of the item to move. /// - /// /// /// The path of the destination. /// - /// /// /// Nothing. Moved items are written to the context's pipeline. /// - /// /// /// path is null or empty. /// destination is null or empty. @@ -5707,14 +5832,14 @@ protected override void MoveItem( { // Check the parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(destination)) + if (string.IsNullOrEmpty(destination)) { - throw PSTraceSource.NewArgumentException("destination"); + throw PSTraceSource.NewArgumentException(nameof(destination)); } path = NormalizePath(path); @@ -5734,7 +5859,6 @@ protected override void MoveItem( if (isContainer) { // Get the DirectoryInfo - DirectoryInfo dir = new DirectoryInfo(path); if (ItemExists(destination) && @@ -5743,25 +5867,32 @@ protected override void MoveItem( destination = MakePath(destination, dir.Name); } - - // Get the confirmation text + // 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; string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, dir.FullName, destination); - // Confirm the move with the user if (ShouldProcess(resource, action)) { // Now move the directory - MoveDirectoryInfoItem(dir, destination, Force); } } else { // Get the FileInfo - FileInfo file = new FileInfo(path); Dbg.Diagnostics.Assert( @@ -5773,19 +5904,15 @@ protected override void MoveItem( { // Construct the new file path from the destination // directory and the file name - destination = MakePath(destination, file.Name); } // Get the confirmation text - string action = FileSystemProviderStrings.MoveItemActionFile; string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, file.FullName, destination); - // Confirm the move with the user - if (ShouldProcess(resource, action)) { MoveFileInfoItem(file, destination, Force, true); @@ -5798,14 +5925,14 @@ 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) { WriteError(new ErrorRecord(accessException, "MoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } - } // MoveItem + } private void MoveFileInfoItem( FileInfo file, @@ -5818,7 +5945,7 @@ private void MoveFileInfoItem( "The caller should verify file."); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(destination), + !string.IsNullOrEmpty(destination), "THe caller should verify destination."); try @@ -5837,15 +5964,12 @@ private void MoveFileInfoItem( catch (System.UnauthorizedAccessException unauthorizedAccess) { // This error is thrown when the readonly bit is set. - if (force) { 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); @@ -5884,7 +6008,7 @@ private void MoveFileInfoItem( } catch (IOException ioException) { - //check if destination file exists. if force is specified then we should delete the destination before moving + // check if destination file exists. if force is specified then we should delete the destination before moving if (force && File.Exists(destination)) { FileInfo destfile = new FileInfo(destination); @@ -5892,8 +6016,8 @@ private void MoveFileInfoItem( { try { - //Make sure the file is not read only - destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + // Make sure the file is not read only + destfile.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); destfile.Delete(); file.MoveTo(destination); @@ -5914,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 @@ -5923,17 +6047,17 @@ 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)); } } - } // MoveFileInfoItem + } private void MoveDirectoryInfoItem( DirectoryInfo directory, @@ -5945,20 +6069,12 @@ private void MoveDirectoryInfoItem( "The caller should verify directory."); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(destination), + !string.IsNullOrEmpty(destination), "The caller should verify destination."); try { - if (!IsSameVolume(directory.FullName, destination)) - { - CopyAndDelete(directory, destination, force); - } - else - { - // Move the file - directory.MoveTo(destination); - } + MoveDirectoryInfoUnchecked(directory, destination, force); WriteItemObject( directory, @@ -5973,18 +6089,9 @@ private void MoveDirectoryInfoItem( try { // mask off the readonly and hidden bits and try again + directory.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); - directory.Attributes = - directory.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); - - if (!IsSameVolume(directory.FullName, destination)) - { - CopyAndDelete(directory, destination, force); - } - else - { - directory.MoveTo(destination); - } + MoveDirectoryInfoUnchecked(directory, destination, force); WriteItemObject(directory, directory.FullName, true); } @@ -6017,10 +6124,47 @@ 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)); } - } // MoveDirectoryItem + } + + /// + /// Implements the file move operation for directories without handling any error scenarios. + /// In particular, this attempts to rename or copy+delete the file, + /// but passes any exceptional behavior through to the caller. + /// + /// The directory to move. + /// The destination path to move the directory to. + /// If true, force move the directory, overwriting anything at the destination. + private void MoveDirectoryInfoUnchecked(DirectoryInfo directory, string destinationPath, bool force) + { + try + { + if (InternalTestHooks.ThrowExdevErrorOnMoveDirectory) + { + throw new IOException("Invalid cross-device link"); + } + + directory.MoveTo(destinationPath); + } +#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); + } + } private void CopyAndDelete(DirectoryInfo directory, string destination, bool force) { @@ -6030,7 +6174,7 @@ private void CopyAndDelete(DirectoryInfo directory, string destination, bool for } else if (ItemExists(destination) && !IsItemContainer(destination)) { - String errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, destination); + string errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, destination); Exception e = new IOException(errorMessage); WriteError(new ErrorRecord( @@ -6057,14 +6201,6 @@ private void CopyAndDelete(DirectoryInfo directory, string destination, bool for } } - private bool IsSameVolume(string source, string destination) - { - FileInfo src = new FileInfo(source); - FileInfo dest = new FileInfo(destination); - - return (src.Directory.Root.Name == dest.Directory.Root.Name); - } - #endregion MoveItem #endregion NavigationCmdletProvider members @@ -6078,14 +6214,14 @@ private bool IsSameVolume(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) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -6094,32 +6230,11 @@ public void GetProperty(string path, Collection providerSpecificPickList try { - FileSystemInfo fileSystemObject = null;// Get the directory object - bool isDirectory; - Exception accessException; - bool exists = Utils.NativeItemExists(path, out isDirectory, out accessException); - - if (accessException != null) - { - throw accessException; - } - - if (exists) - { - if (isDirectory) - { - fileSystemObject = new DirectoryInfo(path); - } - else - { - // Maybe the path is a file name so try a FileInfo instead - fileSystemObject = new FileInfo(path); - } - } + var fileSystemObject = GetFileSystemInfo(path, out bool isDirectory); if (fileSystemObject == null) { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -6148,15 +6263,13 @@ 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)); } else { - String error = + string error = StringUtil.Format( FileSystemProviderStrings.PropertyNotFound, property); @@ -6169,7 +6282,7 @@ public void GetProperty(string path, Collection providerSpecificPickList WriteError(new ErrorRecord(exception, "GetValueError", ErrorCategory.ReadError, property)); } } - } // foreach (property in providerSpecificPickList + } } } } @@ -6179,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) @@ -6191,23 +6304,20 @@ public void GetProperty(string path, Collection providerSpecificPickList { WritePropertyObject(result, path); } - } // GetProperty + } /// /// Gets the dynamic property parameters required by the get-itemproperty cmdlet. /// This feature is not required by the File System provider. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// Null. This feature is not required by the File System provider. /// @@ -6221,73 +6331,45 @@ public object GetPropertyDynamicParameters( /// /// Sets the specified properties on the item at the given path. /// - /// /// /// The path of the item on which to set the properties. /// - /// /// /// A PSObject which contains a collection of the names and values /// of the properties to be set. The File System provider supports setting /// only the "Attributes" property. /// - /// /// /// path is null or empty. /// - /// /// /// propertyToSet is null. /// - /// public void SetProperty(string path, PSObject propertyToSet) { // verify parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } if (propertyToSet == null) { - throw PSTraceSource.NewArgumentNullException("propertyToSet"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyToSet)); } path = NormalizePath(path); PSObject results = new PSObject(); - PSObject fileSystemInfoShell = null; - bool isContainer = false; - - // Create a PSObject with either a DirectoryInfo or FileInfo object - // at its core. - bool isDirectory; - Exception accessException; - bool exists = Utils.NativeItemExists(path, out isDirectory, out accessException); + var fsinfo = GetFileSystemInfo(path, out bool isDirectory); - if (accessException != null) + // Create a PSObject with either a DirectoryInfo or FileInfo object at its core. + if (fsinfo != null) { - throw accessException; - } - - if (exists) - { - if (isDirectory) - { - isContainer = true; - fileSystemInfoShell = PSObject.AsPSObject(new DirectoryInfo(path)); - } - else - { - // Maybe the path is a file name so try a FileInfo instead - fileSystemInfoShell = PSObject.AsPSObject(new FileInfo(path)); - } - } + PSObject fileSystemInfoShell = PSObject.AsPSObject(fsinfo); - if (fileSystemInfoShell != null) - { bool propertySet = false; foreach (PSMemberInfo property in propertyToSet.Properties) @@ -6297,7 +6379,7 @@ public void SetProperty(string path, PSObject propertyToSet) // Get the confirmation text string action = null; - if (isContainer) + if (isDirectory) { action = FileSystemProviderStrings.SetPropertyActionDirectory; } @@ -6325,7 +6407,7 @@ public void SetProperty(string path, PSObject propertyToSet) } string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -6340,7 +6422,7 @@ public void SetProperty(string path, PSObject propertyToSet) if (member != null) { - if (string.Compare(property.Name, "attributes", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(property.Name, "attributes", StringComparison.OrdinalIgnoreCase)) { FileAttributes attributes; @@ -6352,7 +6434,7 @@ public void SetProperty(string path, PSObject propertyToSet) if ((attributes & ~(FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.System)) != 0) { - String error = + string error = StringUtil.Format( FileSystemProviderStrings.AttributesNotSupported, property); @@ -6368,15 +6450,15 @@ public void SetProperty(string path, PSObject propertyToSet) } else { - String error = + string error = StringUtil.Format( FileSystemProviderStrings.PropertyNotFound, property); Exception e = new IOException(error); WriteError(new ErrorRecord(e, "SetPropertyError", ErrorCategory.ReadError, property)); } - } // ShouldProcess - } // foreach property + } + } if (propertySet) { @@ -6385,7 +6467,7 @@ public void SetProperty(string path, PSObject propertyToSet) } else { - String error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); + string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path); Exception e = new IOException(error); WriteError(new ErrorRecord( e, @@ -6393,27 +6475,23 @@ public void SetProperty(string path, PSObject propertyToSet) ErrorCategory.ObjectNotFound, path)); } - } // SetProperty + } /// /// Gets the dynamic property parameters required by the set-itemproperty cmdlet. /// This feature is not required by the File System provider. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to set the dynamic parameters. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// Null. This feature is not required by the File System provider. /// - /// public object SetPropertyDynamicParameters( string path, PSObject propertyValue) @@ -6425,20 +6503,16 @@ public object SetPropertyDynamicParameters( /// Clears the specified properties on the item at the given path. /// The File System provider supports only the "Attributes" property. /// - /// /// /// The path of the item on which to clear the properties. /// - /// /// /// A collection of the names of the properties to clear. The File System /// provider supports clearing only the "Attributes" property. /// - /// /// /// Path is null or empty. /// - /// /// /// propertiesToClear is null or count is zero. /// @@ -6446,9 +6520,9 @@ public void ClearProperty( string path, Collection propertiesToClear) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -6456,25 +6530,22 @@ public void ClearProperty( if (propertiesToClear == null || propertiesToClear.Count == 0) { - throw PSTraceSource.NewArgumentNullException("propertiesToClear"); + throw PSTraceSource.NewArgumentNullException(nameof(propertiesToClear)); } // Only the attributes property can be cleared - if (propertiesToClear.Count > 1 || Host.CurrentCulture.CompareInfo.Compare("Attributes", propertiesToClear[0], CompareOptions.IgnoreCase) != 0) { - throw PSTraceSource.NewArgumentException("propertiesToClear", FileSystemProviderStrings.CannotClearProperty); + throw PSTraceSource.NewArgumentException(nameof(propertiesToClear), FileSystemProviderStrings.CannotClearProperty); } try { // Now the only entry in the array should be the Attributes, so clear them - FileSystemInfo fileSystemInfo = null; // Get the confirmation text - string action = null; bool isContainer = IsItemContainer(path); @@ -6494,14 +6565,13 @@ public void ClearProperty( string resourceTemplate = FileSystemProviderStrings.ClearPropertyResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, fileSystemInfo.FullName, propertiesToClear[0]); // Confirm the set with the user - if (ShouldProcess(resource, action)) { fileSystemInfo.Attributes = FileAttributes.Normal; @@ -6509,9 +6579,8 @@ public void ClearProperty( result.Properties.Add(new PSNoteProperty(propertiesToClear[0], fileSystemInfo.Attributes)); // Now write out the attribute that was cleared. - WritePropertyObject(result, path); - } // ShouldProcess + } } catch (UnauthorizedAccessException unauthorizedAccessException) { @@ -6523,25 +6592,22 @@ 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)); } - } // ClearProperty + } /// /// Gets the dynamic property parameters required by the clear-itemproperty cmdlet. /// This feature is not required by the File System provider. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to set the dynamic parameters. /// - /// /// /// A collection of the names of the properties to clear. /// - /// /// /// Null. This feature is not required by the File System provider. /// @@ -6561,30 +6627,27 @@ public object ClearPropertyDynamicParameters( /// the specified file for reading, and returns the IContentReader interface /// to it. /// - /// /// /// The path of the file to be opened for reading. /// - /// /// /// An IContentReader for the specified file. /// - /// /// /// path is null or empty. /// public IContentReader GetContentReader(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); // Defaults for the file read operation string delimiter = "\n"; - Encoding encoding = ClrFacade.GetDefaultEncoding(); + Encoding encoding = Encoding.Default; bool waitForChanges = false; bool streamTypeSpecified = false; @@ -6634,8 +6697,8 @@ public IContentReader GetContentReader(string path) // Get the stream name streamName = dynParams.Stream; #endif - } // dynParams != null - } // DynamicParameters != null + } + } #if !UNIX // See if they've used the inline stream syntax. They have more than one colon. @@ -6652,6 +6715,21 @@ public IContentReader GetContentReader(string path) try { + // 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); + WriteError(error); + return stream; + } + // Users can't both read as bytes, and specify a delimiter if (delimiterSpecified) { @@ -6694,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) @@ -6707,24 +6785,22 @@ public IContentReader GetContentReader(string path) } return stream; - } // GetContentReader + } /// /// Gets the dynamic property parameters required by the get-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// public object GetContentReaderDynamicParameters(string path) { - return new FileSystemContentReaderDynamicParameters(); + return new FileSystemContentReaderDynamicParameters(this); } /// @@ -6732,23 +6808,20 @@ public object GetContentReaderDynamicParameters(string path) /// the specified file for writing, and returns the IContentReader interface /// to it. /// - /// /// /// The path of the file to be opened for writing. /// - /// /// /// An IContentWriter for the specified file. /// - /// /// /// path is null or empty. /// public IContentWriter GetContentWriter(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -6756,13 +6829,12 @@ 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; // Get the dynamic parameters - if (DynamicParameters != null) { FileSystemContentWriterDynamicParameters dynParams = @@ -6787,7 +6859,7 @@ public IContentWriter GetContentWriter(string path) streamName = dynParams.Stream; #endif suppressNewline = dynParams.NoNewline.IsPresent; - } // dynParams != null + } } #if !UNIX @@ -6805,7 +6877,22 @@ public IContentWriter GetContentWriter(string path) try { - stream = new FileSystemContentReaderWriter(path, streamName, filemode, FileAccess.Write, FileShare.Write, encoding, usingByteEncoding, false, this, false, suppressNewline); + // 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); + WriteError(error); + return stream; + } + + stream = new FileSystemContentReaderWriter(path, streamName, filemode, FileAccess.Write, FileShare.ReadWrite, encoding, usingByteEncoding, false, this, false, suppressNewline); } catch (PathTooLongException pathTooLong) { @@ -6825,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) @@ -6838,43 +6925,39 @@ public IContentWriter GetContentWriter(string path) } return stream; - } // GetContentWriter + } /// /// Gets the dynamic property parameters required by the set-content and /// add-content cmdlets. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// public object GetContentWriterDynamicParameters(string path) { - return new FileSystemContentWriterDynamicParameters(); + return new FileSystemContentWriterDynamicParameters(this); } /// /// Clears the content of the specified file. /// - /// /// /// The path to the file of which to clear the contents. /// - /// /// /// path is null or empty. /// public void ClearContent(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); @@ -6908,7 +6991,7 @@ public void ClearContent(string path) streamName = writerDynamicParameters.Stream; } - if (String.IsNullOrEmpty(streamName)) + if (string.IsNullOrEmpty(streamName)) { // See if they've used the inline stream syntax. They have more than one colon. int firstColon = path.IndexOf(':'); @@ -6925,16 +7008,36 @@ public void ClearContent(string path) // If they're just working on the DATA stream, don't use the Alternate Data Stream // utils to clear the stream - otherwise, the Win32 API will trash the other streams. - if (String.Equals(":$DATA", streamName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(":$DATA", streamName, StringComparison.OrdinalIgnoreCase)) { 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; - string streamAction = String.Format( + string streamAction = string.Format( CultureInfo.InvariantCulture, FileSystemProviderStrings.StreamAction, streamName, path); @@ -6967,7 +7070,7 @@ public void ClearContent(string path) } // For filesystem once content is cleared - WriteItemObject("", path, false); + WriteItemObject(string.Empty, path, false); } catch (ArgumentException argException) { @@ -6975,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) @@ -6993,8 +7096,8 @@ public void ClearContent(string path) FileStream fileStream = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.Write); fileStream.Dispose(); - //For filesystem once content is cleared - WriteItemObject("", path, false); + // For filesystem once content is cleared + WriteItemObject(string.Empty, path, false); } catch (UnauthorizedAccessException failure) { @@ -7011,17 +7114,15 @@ public void ClearContent(string path) WriteError(new ErrorRecord(accessException, "ClearContentUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); } } - } // ClearContent + } /// /// Gets the dynamic property parameters required by the clear-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item for which to get the dynamic parameters. /// - /// /// /// A FileSystemClearContentDynamicParameters that provides access to the -Stream dynamic parameter. /// @@ -7032,88 +7133,6 @@ public object ClearContentDynamicParameters(string path) #endregion IContentCmdletProvider - internal static int SafeGetFileAttributes(string path) - { -#if UNIX - System.IO.FileAttributes attr = System.IO.File.GetAttributes(path); - - int result = 0; - if ((attr & FileAttributes.Archive) == FileAttributes.Archive) - result |= 0x20; - if ((attr & FileAttributes.Compressed) == FileAttributes.Compressed) - result |= 0x800; - if ((attr & FileAttributes.Device) == FileAttributes.Device) - result |= 0x40; - if ((attr & FileAttributes.Directory) == FileAttributes.Directory) - result |= 0x10; - if ((attr & FileAttributes.Encrypted) == FileAttributes.Encrypted) - result |= 0x4000; - if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden) - result |= 0x2; - if ((attr & FileAttributes.IntegrityStream) == FileAttributes.IntegrityStream) - result |= 0x8000; - if ((attr & FileAttributes.Normal) == FileAttributes.Normal) - result |= 0x80; - if ((attr & FileAttributes.NoScrubData) == FileAttributes.NoScrubData) - result |= 0x20000; - if ((attr & FileAttributes.NotContentIndexed) == FileAttributes.NotContentIndexed) - result |= 0x2000; - if ((attr & FileAttributes.Offline) == FileAttributes.Offline) - result |= 0x1000; - if ((attr & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - result |= 0x1; - if ((attr & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) - result |= 0x400; - if ((attr & FileAttributes.SparseFile) == FileAttributes.SparseFile) - result |= 0x200; - if ((attr & FileAttributes.System) == FileAttributes.System) - result |= 0x4; - if ((attr & FileAttributes.Temporary) == FileAttributes.Temporary) - result |= 0x100; - - return result; -#else - return WinSafeGetFileAttributes(path); -#endif - } - - internal static int WinSafeGetFileAttributes(string path) - { - int result = Utils.NativeMethods.GetFileAttributes(path); - if (result == -1) - { - int errorCode = Marshal.GetLastWin32Error(); - if (errorCode == 5) - { - // Handle "Access denied" specifically. - Win32Exception win32Exception = new Win32Exception(errorCode); - throw new UnauthorizedAccessException(win32Exception.Message, win32Exception); - } - else if (errorCode == 32) - { - // Errorcode 32 is 'ERROR_SHARING_VIOLATION' i.e. - // The process cannot access the file because it is being used by another process. - // GetFileAttributes may return INVALID_FILE_ATTRIBUTES for a system file or directory because of this error. - // GetFileAttributes function tries to open the file with FILE_READ_ATTRIBUTES access right but it fails if the - // sharing flag for the file is set to 0x00000000.This flag prevents it from opening a file for delete, read, or - // write access. For example: C:\pagefile.sys is always opened by OS with sharing flag 0x00000000. - // But FindFirstFile is still able to get attributes as this api retrieves the required information using a find - // handle generated with FILE_LIST_DIRECTORY access. - // Fall back to FindFirstFile to check if the file actually exists. - IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); - Utils.NativeMethods.WIN32_FIND_DATA findData; - IntPtr findHandle = Utils.NativeMethods.FindFirstFile(path, out findData); - if (findHandle != INVALID_HANDLE_VALUE) - { - Utils.NativeMethods.FindClose(findHandle); - return (int)findData.dwFileAttributes; - } - } - } - - return result; - } - /// /// -raw is not allowed when -first,-last or -wait is specified /// this call will validate that and throws. @@ -7163,226 +7182,54 @@ 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); - - /// - /// Determines if a path string is a valid Universal Naming Convention (UNC) path, as opposed to a path based on a drive letter. - /// - /// - /// Path of the file being executed - /// - /// Returns TRUE if the string is a valid UNC path; otherwise, FALSE. - [DllImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool PathIsUNC(string path); - - /// - /// 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 (PathIsUNC(path)) + if (Utils.PathIsUnc(path, networkOnly : true)) { return true; } - // 0 - 25 corresponding to 'A' - 'Z' - int driveId = PathGetDriveNumber(path); - if (driveId >= 0 && driveId < 26) + if (path.Length > 1 && path[1] == ':' && char.IsAsciiLetter(path[0])) { - string driveName = (char)('A' + driveId) + ":"; - - int bufferSize = 260; // MAX_PATH from EhStorIoctl.h - StringBuilder uncBuffer = new StringBuilder(bufferSize); - int errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + // 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; } } + 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. - /// 0 for destination as file and 1 for destination as directory. - /// 1 on successful creation. - [DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int CreateSymbolicLink(string name, string destination, int destinationType); - - /// - /// 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)] - internal static extern bool CreateHardLink(string name, string existingFileName, IntPtr SecurityAttributes); - - [Flags] - internal enum FileAttributes - { - Hidden = 0x0002, - Directory = 0x0010 - } - } - - /// - /// 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 + /// Construct a new InodeTracker with an initial path. /// internal InodeTracker(string path) { @@ -7418,7 +7265,7 @@ internal bool TryVisitPath(string path) } #endregion - } // class FileSystemProvider + } internal static class SafeInvokeCommand { @@ -7426,6 +7273,7 @@ public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileS { return Invoke(ps, fileSystemContext, cmdletContext, true); } + public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileSystemProvider fileSystemContext, CmdletProviderContext cmdletContext, bool shouldHaveOutput) { bool useFileSystemProviderContext = (cmdletContext == null); @@ -7452,6 +7300,7 @@ public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileS cmdletContext.WriteError(new ErrorRecord(e, "CopyFileRemoteExecutionError", ErrorCategory.InvalidOperation, ps)); ps.Commands.Clear(); } + return null; } @@ -7480,6 +7329,7 @@ public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileS Dbg.Diagnostics.Assert(output[0] != null, "Expected an output from the remote call."); return null; } + return (Hashtable)output[0]; } @@ -7503,12 +7353,12 @@ internal sealed class CopyItemDynamicParameters } /// - /// Defines the container cmdlet dynamic providers + /// Defines the container cmdlet dynamic providers. /// internal sealed class GetChildDynamicParameters { /// - /// Gets or sets the attribute filtering enum evaluator + /// Gets or sets the attribute filtering enum evaluator. /// [Parameter] public FlagsExpression Attributes { get; set; } @@ -7520,63 +7370,73 @@ internal sealed class GetChildDynamicParameters public SwitchParameter FollowSymlink { get; set; } /// - /// Gets or sets the filter directory flag + /// Gets or sets the filter directory flag. /// [Parameter] - [Alias("ad", "d")] + [Alias("ad")] public SwitchParameter Directory { get { return _attributeDirectory; } + set { _attributeDirectory = value; } } + private bool _attributeDirectory; /// - /// Gets or sets the filter file flag + /// Gets or sets the filter file flag. /// [Parameter] [Alias("af")] public SwitchParameter File { get { return _attributeFile; } + set { _attributeFile = value; } } + private bool _attributeFile; /// - /// Gets or sets the filter hidden flag + /// Gets or sets the filter hidden flag. /// [Parameter] [Alias("ah", "h")] public SwitchParameter Hidden { get { return _attributeHidden; } + set { _attributeHidden = value; } } + private bool _attributeHidden; /// - /// Gets or sets the filter readonly flag + /// Gets or sets the filter readonly flag. /// [Parameter] [Alias("ar")] public SwitchParameter ReadOnly { get { return _attributeReadOnly; } + set { _attributeReadOnly = value; } } + private bool _attributeReadOnly; /// - /// Gets or sets the filter system flag + /// Gets or sets the filter system flag. /// [Parameter] [Alias("as")] public SwitchParameter System { get { return _attributeSystem; } + set { _attributeSystem = value; } } + private bool _attributeSystem; } @@ -7585,23 +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()] - [ArgumentCompletions( - EncodingConversion.Ascii, - EncodingConversion.BigEndianUnicode, - EncodingConversion.OEM, - EncodingConversion.Unicode, - EncodingConversion.Utf7, - EncodingConversion.Utf8, - EncodingConversion.Utf8Bom, - EncodingConversion.Utf8NoBom, - EncodingConversion.Utf32 - )] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -7609,17 +7466,25 @@ public Encoding Encoding { return _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 + /// Return file contents as a byte stream or create file from a series of bytes. /// [Parameter] public SwitchParameter AsByteStream { get; set; } @@ -7629,7 +7494,7 @@ public Encoding Encoding /// A parameter to return a stream of an item. /// [Parameter] - public String Stream { get; set; } + public string Stream { get; set; } #endif /// @@ -7637,8 +7502,7 @@ public Encoding Encoding /// if the stream was opened with a user-specified encoding, false otherwise. /// public bool WasStreamTypeSpecified { get; private set; } - - } // class FileSystemContentDynamicParametersBase + } /// /// Defines the dynamic parameters used by the Clear-Content cmdlet. @@ -7650,9 +7514,9 @@ public class FileSystemClearContentDynamicParameters /// A parameter to return a stream of an item. /// [Parameter] - public String Stream { get; set; } + public string Stream { get; set; } #endif - } //FileSystemContentWriterDynamicParameters + } /// /// Defines the dynamic parameters used by the set-content and @@ -7660,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. /// @@ -7670,6 +7536,7 @@ public SwitchParameter NoNewline { return _suppressNewline; } + set { _suppressNewline = value; @@ -7677,13 +7544,15 @@ public SwitchParameter NoNewline } private bool _suppressNewline = false; - } //FileSystemContentWriterDynamicParameters + } /// /// Defines the dynamic parameters used by the get-content cmdlet. /// 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. @@ -7702,6 +7571,7 @@ public string Delimiter _delimiter = value; } } + private string _delimiter = "\n"; /// @@ -7715,13 +7585,14 @@ public SwitchParameter Wait get { return _wait; - } // get + } set { _wait = value; - } // set + } } + private bool _wait; /// @@ -7735,22 +7606,25 @@ public SwitchParameter Raw { return _isRaw; } + set { _isRaw = value; } } + private bool _isRaw; /// /// Gets the status of the delimiter parameter. Returns true /// if the delimiter was explicitly specified by the user, false otherwise. /// - public bool DelimiterSpecified { get; private set; -// get - } // DelimiterSpecified - } // class FileSystemContentReaderDynamicParameters - + public bool DelimiterSpecified + { + get; private set; + // get + } + } /// /// Provides the dynamic parameters for test-path on the file system. @@ -7764,11 +7638,11 @@ public class FileSystemItemProviderDynamicParameters public DateTime? OlderThan { get; set; } /// - /// A parameter to test if a file is newer than a certain time or date + /// A parameter to test if a file is newer than a certain time or date. /// [Parameter] public DateTime? NewerThan { get; set; } - } // class FileSystemItemProviderDynamicParameters + } /// /// Provides the dynamic parameters for Get-Item on the file system. @@ -7784,7 +7658,7 @@ public class FileSystemProviderGetItemDynamicParameters [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Stream { get; set; } #endif - } // class FileSystemItemProviderDynamicParameters + } /// /// Provides the dynamic parameters for Remove-Item on the file system. @@ -7800,7 +7674,7 @@ public class FileSystemProviderRemoveItemDynamicParameters [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Stream { get; set; } #endif - } // class FileSystemItemProviderDynamicParameters + } #endregion @@ -7809,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; @@ -7830,60 +7695,9 @@ public static class InternalSymbolicLinkLinkCodeMethods private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; - private const string NonInterpretedPathPrefix = @"\??\"; - - [Flags] - //dwDesiredAccess of CreateFile - internal enum FileDesiredAccess : uint - { - GenericRead = 0x80000000, - GenericWrite = 0x40000000, - GenericExecute = 0x20000000, - GenericAll = 0x10000000, - } + private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B; - [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 - } + private const string NonInterpretedPathPrefix = @"\??\"; [StructLayout(LayoutKind.Sequential)] private struct REPARSE_DATA_BUFFER_SYMBOLICLINK @@ -7896,6 +7710,7 @@ private struct REPARSE_DATA_BUFFER_SYMBOLICLINK public ushort PrintNameOffset; public ushort PrintNameLength; public uint Flags; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] public byte[] PathBuffer; } @@ -7910,6 +7725,7 @@ private struct REPARSE_DATA_BUFFER_MOUNTPOINT public ushort SubstituteNameLength; public ushort PrintNameOffset; public ushort PrintNameLength; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] public byte[] PathBuffer; } @@ -7918,9 +7734,9 @@ private struct REPARSE_DATA_BUFFER_MOUNTPOINT 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; @@ -7929,94 +7745,61 @@ 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); -#if !CORECLR - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern IntPtr FindFirstFileName( - string lpFileName, - uint flags, - ref UInt32 StringLength, - StringBuilder LinkName); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool FindNextFileName( - IntPtr hFindStream, - ref UInt32 StringLength, - StringBuilder LinkName); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool FindClose(IntPtr hFindFile); - -#endif - - [DllImport(PinvokeDllNames.GetFileInformationByHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool GetFileInformationByHandle( + [LibraryImport(PinvokeDllNames.GetFileInformationByHandleDllName)] + [return: MarshalAs(UnmanagedType.Bool)] + 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); - /// /// Gets the target of the specified reparse point. /// /// The object of FileInfo or DirectoryInfo type. - /// The target of the reparse point - public static IEnumerable GetTarget(PSObject instance) + /// 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) { - FileSystemInfo fileSysInfo = instance.BaseObject as FileSystemInfo; - - if (fileSysInfo != null) + if (instance.BaseObject is FileSystemInfo fileSysInfo) { - if (Platform.IsWindows) + if (!fileSysInfo.Exists) { - using (SafeFileHandle handle = OpenReparsePoint(fileSysInfo.FullName, FileDesiredAccess.GenericRead)) - { - string linkTarget = InternalGetTarget(handle); - - if (linkTarget != null) - return (new string[] { linkTarget }); - } + throw new ArgumentException( + StringUtil.Format(SessionStateStrings.PathNotFound, fileSysInfo.FullName)); } - return InternalGetTarget(fileSysInfo.FullName); + return fileSysInfo.LinkTarget; } - else - return null; + + 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; } /// @@ -8032,107 +7815,28 @@ public static string GetLinkType(PSObject instance) { return InternalGetLinkType(fileSysInfo); } - else - return null; - } - - private static List InternalGetTarget(string filePath) - { - var links = new List(); -#if UNIX - string link = Platform.NonWindowsInternalGetTarget(filePath); - if (!String.IsNullOrEmpty(link)) - { - links.Add(link); - } - else - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - -#elif !CORECLR //FindFirstFileName, FindNextFileName and FindClose are not available on Core Clr - UInt32 linkStringLength = 0; - var linkName = new StringBuilder(); - - // First get the length for the linkName buffer. - IntPtr fileHandle = InternalSymbolicLinkLinkCodeMethods.FindFirstFileName(filePath, 0, ref linkStringLength, linkName); - int lastError = Marshal.GetLastWin32Error(); - - // Return handle is INVALID_HANDLE_VALUE and LastError was ERROR_MORE_DATA - if ((fileHandle == (IntPtr)(-1)) && (lastError == 234)) - { - linkName = new StringBuilder((int)linkStringLength); - fileHandle = InternalSymbolicLinkLinkCodeMethods.FindFirstFileName(filePath, 0, ref linkStringLength, linkName); - lastError = Marshal.GetLastWin32Error(); - } - - if (fileHandle == (IntPtr)(-1)) - { - throw new Win32Exception(lastError); - } - - bool continueFind = false; - - try - { - do - { - StringBuilder fullName = new StringBuilder(); - fullName.Append(Path.GetPathRoot(filePath)); //hard link source and target must be on the same drive. So we can use the source for find the path root. - fullName.Append(linkName.ToString()); - FileInfo fInfo = new FileInfo(fullName.ToString()); - - //Don't add the target link to the list. - - if (String.Compare(fInfo.FullName, filePath, StringComparison.OrdinalIgnoreCase) != 0) - links.Add(fInfo.FullName); - - continueFind = InternalSymbolicLinkLinkCodeMethods.FindNextFileName(fileHandle, ref linkStringLength, linkName); - - lastError = Marshal.GetLastWin32Error(); - if (!continueFind && lastError == 234) // ERROR_MORE_DATA - { - linkName = new StringBuilder((int)linkStringLength); - continueFind = InternalSymbolicLinkLinkCodeMethods.FindNextFileName(fileHandle, ref linkStringLength, linkName); - } - - if (!continueFind && lastError != 38) //ERROR_HANDLE_EOF. No more links. - { - throw new Win32Exception(lastError); - } - } - while (continueFind); - } - finally - { - InternalSymbolicLinkLinkCodeMethods.FindClose(fileHandle); - } -#endif - return links; + return null; } 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(); - } - - using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericRead)) + // 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 = WinOpenReparsePoint(filePath, (FileAccess)0)) { int outBufferSize = Marshal.SizeOf(); @@ -8144,35 +7848,44 @@ private static string WinInternalGetLinkType(string filePath) int bytesReturned; string linkType = null; - //OACR warning 62001 about using DeviceIOControl has been disabled. + // OACR warning 62001 about using DeviceIOControl has been disabled. // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used. - handle.DangerousAddRef(ref success); - //Get Buffer size + // 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); - if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) - linkType = "SymbolicLink"; - else if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) - linkType = "Junction"; - else + switch (reparseDataBuffer.ReparseTag) { - linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null; + case IO_REPARSE_TAG_SYMLINK: + linkType = "SymbolicLink"; + break; + + case IO_REPARSE_TAG_MOUNT_POINT: + linkType = "Junction"; + break; + + default: + linkType = null; + break; } return linkType; @@ -8188,65 +7901,79 @@ private static string WinInternalGetLinkType(string filePath) } } } +#endif internal static bool IsHardLink(FileSystemInfo fileInfo) { #if UNIX return Platform.NonWindowsIsHardLink(fileInfo); #else - return WinIsHardLink(fileInfo); -#endif - } + bool isHardLink = false; - internal static bool IsReparsePoint(FileSystemInfo fileInfo) - { - if (Platform.IsWindows) - { - // Note that this class also has a enum called FileAttributes, so use fully qualified name - return (fileInfo.Attributes & System.IO.FileAttributes.ReparsePoint) - == System.IO.FileAttributes.ReparsePoint; - } - else + // only check for hard link if the item is not directory + if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory) { - return Platform.NonWindowsIsSymLink(fileInfo); + 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 + } + + internal static bool IsReparsePoint(FileSystemInfo fileInfo) + { + return fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint); } - internal static bool WinIsHardLink(FileSystemInfo fileInfo) + internal static bool IsReparsePointLikeSymlink(FileSystemInfo fileInfo) { - bool isHardLink = false; +#if UNIX + // Reparse point on Unix is a symlink. + return IsReparsePoint(fileInfo); +#else + if (InternalTestHooks.OneDriveTestOn && fileInfo.Name == InternalTestHooks.OneDriveTestSymlinkName) + { + return !InternalTestHooks.OneDriveTestRecurseOn; + } - // only check for hard link if the item is not directory - if (!((fileInfo.Attributes & System.IO.FileAttributes.Directory) == System.IO.FileAttributes.Directory)) + Interop.Windows.WIN32_FIND_DATA data = default; + using (Interop.Windows.SafeFindHandle handle = Interop.Windows.FindFirstFile(fileInfo.FullName, ref data)) { - IntPtr nativeHandle = InternalSymbolicLinkLinkCodeMethods.CreateFile( - fileInfo.FullName, - InternalSymbolicLinkLinkCodeMethods.FileDesiredAccess.GenericRead, - InternalSymbolicLinkLinkCodeMethods.FileShareMode.Read, - IntPtr.Zero, - InternalSymbolicLinkLinkCodeMethods.FileCreationDisposition.OpenExisting, - InternalSymbolicLinkLinkCodeMethods.FileAttributes.Normal, - IntPtr.Zero); + if (handle.IsInvalid) + { + // 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); + } - 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) @@ -8261,22 +7988,19 @@ 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) { BY_HANDLE_FILE_INFORMATION infoOne; BY_HANDLE_FILE_INFORMATION infoTwo; - if ( GetFileInformationByHandle(sfOne.DangerousGetHandle(), out infoOne) + if (GetFileInformationByHandle(sfOne.DangerousGetHandle(), out infoOne) && GetFileInformationByHandle(sfTwo.DangerousGetHandle(), out infoTwo)) { - return infoOne.VolumeSerialNumber == infoTwo.VolumeSerialNumber + return infoOne.VolumeSerialNumber == infoTwo.VolumeSerialNumber && infoOne.FileIndexHigh == infoTwo.FileIndexHigh && infoOne.FileIndexLow == infoTwo.FileIndexLow; } @@ -8300,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) { @@ -8324,308 +8045,95 @@ private static bool WinGetInodeData(string path, out System.ValueTuple 1) - { - return true; - } - - return false; - } - - private static string InternalGetTarget(SafeFileHandle handle) - { - if (Platform.IsWindows) - { - return WinInternalGetTarget(handle); - } - else - { - return Platform.NonWindowsInternalGetTarget(handle); - } + return succeeded && (handleInfo.NumberOfLinks > 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; - - //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_GET_REPARSE_POINT, - IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); - - if (!result) - { - int lastError = Marshal.GetLastWin32Error(); - if (lastError == ERROR_NOT_A_REPARSE_POINT) - return null; - - throw new Win32Exception(lastError); - } + byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); - //Unmarshal to symbolic link to look for tags. - REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); + 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); - if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_SYMLINK && reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) - return null; - - string targetDir = null; - - if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) - { - targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); - } + int nativeBufferSize = Marshal.SizeOf(mountPoint); + IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize); + bool success = false; - if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) + try { - //Since this is a junction we need to unmarshal to the correct structure. - REPARSE_DATA_BUFFER_MOUNTPOINT reparseDataBufferMountPoint = Marshal.PtrToStructure(outBuffer); - - targetDir = Encoding.Unicode.GetString(reparseDataBufferMountPoint.PathBuffer, reparseDataBufferMountPoint.SubstituteNameOffset, reparseDataBufferMountPoint.SubstituteNameLength); - } - - if (targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase)) - targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); + Marshal.StructureToPtr(mountPoint, nativeBuffer, false); - return targetDir; - } - finally - { - if (success) - { - handle.DangerousRelease(); - } + int bytesReturned = 0; - Marshal.FreeHGlobal(outBuffer); - } - } + // OACR warning 62001 about using DeviceIOControl has been disabled. + // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used. + handle.DangerousAddRef(ref success); - 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; - } - } + bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - [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 (!result) { - 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(); - } - } + throw new Win32Exception(Marshal.GetLastWin32Error()); } - } - else - { - throw new ArgumentNullException("target"); - } - } - else - { - throw new ArgumentNullException("path"); - } - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - internal static bool DeleteJunction(string junctionPath) - { - bool result = false; - if (!String.IsNullOrEmpty(junctionPath)) - { - if (!Platform.IsWindows) - { - // For non-Windows platform, treat it as a file. Just delete it. - try - { - File.Delete(junctionPath); - return true; - } - catch - { - return false; - } + return result; } - - using (SafeHandle handle = OpenReparsePoint(junctionPath, FileDesiredAccess.GenericWrite)) + finally { - bool success = false; - int inOutBufferSize = Marshal.SizeOf(); - IntPtr outBuffer = Marshal.AllocHGlobal(inOutBufferSize); - IntPtr inBuffer = Marshal.AllocHGlobal(inOutBufferSize); - - try - { - handle.DangerousAddRef(ref success); - IntPtr dangerousHandle = handle.DangerousGetHandle(); - int bytesReturned; - - // Do a FSCTL_GET_REPARSE_POINT first because the ReparseTag could be - // IO_REPARSE_TAG_MOUNT_POINT or IO_REPARSE_TAG_SYMLINK. - // Using the wrong one results in mismatched-tag error. - - REPARSE_GUID_DATA_BUFFER junctionData = new REPARSE_GUID_DATA_BUFFER(); - Marshal.StructureToPtr(junctionData, outBuffer, false); + Marshal.FreeHGlobal(nativeBuffer); - result = DeviceIoControl(dangerousHandle, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, - outBuffer, inOutBufferSize, out bytesReturned, IntPtr.Zero); - if (!result) - { - int lastError = Marshal.GetLastWin32Error(); - throw new Win32Exception(lastError); - } - - junctionData = Marshal.PtrToStructure(outBuffer); - junctionData.ReparseDataLength = 0; - junctionData.DataBuffer = new char[MAX_REPARSE_SIZE]; - - Marshal.StructureToPtr(junctionData, inBuffer, false); - - // To delete a reparse point: - // ReparseDataLength must be 0 - // inBufferSize must be REPARSE_GUID_DATA_BUFFER_HEADER_SIZE - result = DeviceIoControl(dangerousHandle, FSCTL_DELETE_REPARSE_POINT, inBuffer, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - if (!result) - { - int lastError = Marshal.GetLastWin32Error(); - throw new Win32Exception(lastError); - } - } - finally + if (success) { - if (success) - { - handle.DangerousRelease(); - } - - Marshal.FreeHGlobal(outBuffer); - Marshal.FreeHGlobal(inBuffer); + handle.DangerousRelease(); } } } - else - { - throw new ArgumentNullException("junctionPath"); - } - - return result; - } - - 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 @@ -8658,20 +8166,20 @@ public class AlternateStreamData } /// - /// Provides access to alternate data streams on a file + /// Provides access to alternate data streams on a file. /// [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 + /// List all of the streams on a file. /// /// The fully-qualified path to the file. /// The list of streams (and their size) in the file. internal static List GetStreams(string path) { - if (path == null) throw new ArgumentNullException("path"); + ArgumentNullException.ThrowIfNull(path); List alternateStreams = new List(); @@ -8680,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 { @@ -8690,17 +8216,16 @@ 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"; - if (!String.Equals(findStreamData.Name, dataStream, StringComparison.OrdinalIgnoreCase)) + const string dataStream = ":$DATA"; + if (!string.Equals(findStreamData.Name, dataStream, StringComparison.OrdinalIgnoreCase)) { - findStreamData.Name = findStreamData.Name.Replace(dataStream, ""); + findStreamData.Name = findStreamData.Name.Replace(dataStream, string.Empty); } AlternateStreamData data = new AlternateStreamData(); data.Stream = findStreamData.Name; data.Length = findStreamData.Length; - data.FileName = path.Replace(data.Stream, ""); - data.FileName = data.FileName.Trim(Utils.Separators.Colon); + data.FileName = path; alternateStreams.Add(data); findStreamData = new AlternateStreamNativeData(); @@ -8709,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(); } @@ -8717,7 +8244,7 @@ internal static List GetStreams(string path) } /// - /// Creates a file stream on a file + /// Creates a file stream on a file. /// /// The fully-qualified path to the file. /// The name of the alternate data stream to open. @@ -8727,24 +8254,48 @@ internal static List GetStreams(string path) /// A FileStream that can be used to interact with the file. internal static FileStream CreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share) { - if (path == null) throw new ArgumentNullException("path"); - if (streamName == null) throw new ArgumentNullException("streamName"); + if (!TryCreateFileStream(path, streamName, mode, access, share, out var stream)) + { + string errorMessage = StringUtil.Format( + FileSystemProviderStrings.AlternateDataStreamNotFound, streamName, path); + throw new FileNotFoundException(errorMessage, $"{path}:{streamName}"); + } - string adjustedStreamName = streamName.Trim(); - adjustedStreamName = ":" + adjustedStreamName; - string resultPath = path + adjustedStreamName; + return stream; + } + + /// + /// Tries to create a file stream on a file. + /// + /// The fully-qualified path to the file. + /// The name of the alternate data stream to open. + /// The FileMode of the file. + /// The FileAccess of the file. + /// The FileShare of the file. + /// A FileStream that can be used to interact with the 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) + { + ArgumentNullException.ThrowIfNull(path); + + ArgumentNullException.ThrowIfNull(streamName); + + if (mode == FileMode.Append) + { + mode = FileMode.OpenOrCreate; + } - if (mode == FileMode.Append) mode = FileMode.OpenOrCreate; + var resultPath = $"{path}:{streamName}"; SafeFileHandle handle = NativeMethods.CreateFile(resultPath, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero); if (handle.IsInvalid) { - string errorMessage = StringUtil.Format( - FileSystemProviderStrings.AlternateDataStreamNotFound, streamName, path); - throw new FileNotFoundException(errorMessage, resultPath); + stream = null; + return false; } - return new FileStream(handle, access); + stream = new FileStream(handle, access); + return true; } /// @@ -8754,21 +8305,19 @@ internal static FileStream CreateFileStream(string path, string streamName, File /// The name of the alternate data stream to delete. internal static void DeleteFileStream(string path, string streamName) { - if (path == null) throw new ArgumentNullException("path"); - if (streamName == null) throw new ArgumentNullException("streamName"); + ArgumentNullException.ThrowIfNull(path); + + ArgumentNullException.ThrowIfNull(streamName); string adjustedStreamName = streamName.Trim(); - if (adjustedStreamName.IndexOf(':') != 0) + if (!adjustedStreamName.StartsWith(':')) { adjustedStreamName = ":" + adjustedStreamName; } + string resultPath = path + adjustedStreamName; - if (!NativeMethods.DeleteFile(resultPath)) - { - int error = Marshal.GetLastWin32Error(); - throw new Win32Exception(error); - } + File.Delete(resultPath); } internal static void SetZoneOfOrigin(string path, SecurityZone securityZone) @@ -8785,28 +8334,27 @@ 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); - [DllImport(PinvokeDllNames.DeleteFileDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool DeleteFile(string lpFileName); - - [DllImport(PinvokeDllNames.FindFirstStreamDllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")] internal static extern SafeFindHandle FindFirstStreamW( string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] AlternateStreamNativeData lpFindStreamData, uint dwFlags); - [DllImport(PinvokeDllNames.FindNextStreamDllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")] internal static extern bool FindNextStreamW( @@ -8815,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) { } @@ -8824,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); } /// @@ -8862,14 +8410,17 @@ internal static class CopyFileRemoteUtils #region PSCopyToSessionHelper 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 ( [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")] [Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")] {0} + [string] $copyToFilePath, [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"", Mandatory=$false)] @@ -8889,10 +8440,12 @@ internal static class CopyFileRemoteUtils [Parameter(ParameterSetName=""PSTargetSupportsAlternateStreams"", Mandatory=$true)] {0} + [string] $supportAltStreamPath, [Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)] {0} + [string] $metaDataFilePath, [Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)] @@ -8901,14 +8454,17 @@ internal static class CopyFileRemoteUtils [Parameter(ParameterSetName=""PSRemoteDestinationPathIsFile"", Mandatory=$true)] {0} + [string] $isFilePath, [Parameter(ParameterSetName=""PSGetRemotePathInfo"", Mandatory=$true)] {0} + [string] $remotePath, [Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"", Mandatory=$true)] {0} + [string] $createDirectoryPath, [Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"")] @@ -8931,6 +8487,7 @@ function CheckPSDriveSize Microsoft.PowerShell.Management\Get-ChildItem -LiteralPath ($resolvedPath.Drive.Name + "":"") -Recurse | ForEach-Object {{ Microsoft.PowerShell.Management\Get-Item -LiteralPath $_.FullName -Stream * | ForEach-Object {{ $dirSize += $_.Length }} }} + if (($dirSize + $fragmentLength) -gt $maxUserSize) {{ $msg = ""{1}"" -f $maxUserSize @@ -9114,6 +8671,7 @@ function PSSetFileMetadata {{ $item.LastWriteTimeUtc = $metaDataToSet['LastWriteTimeUtc'] }} + if ($metaDataToSet['LastWriteTime']) {{ $item.LastWriteTime = $metaDataToSet['LastWriteTime'] @@ -9207,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. @@ -9304,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} }; @@ -9320,12 +8878,15 @@ function PSCreateDirectoryOnRemoteSession #region PSCopyFromSessionHelper 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 ( [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)] {0} + [string] $copyFromFilePath, [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)] @@ -9348,18 +8909,22 @@ function PSCreateDirectoryOnRemoteSession [Parameter(ParameterSetName=""PSSourceSupportsAlternateStreams"", Mandatory=$true)] {0} + [string] $supportAltStreamPath, [Parameter(ParameterSetName=""PSGetFileMetadata"", Mandatory=$true)] {0} + [string] $getMetaFilePath, [Parameter(ParameterSetName=""PSGetPathItems"", Mandatory=$true)] {0} + [string] $getPathItems, [Parameter(ParameterSetName=""PSGetPathDirAndFiles"", Mandatory=$true)] {0} + [string] $getPathDir ) @@ -9435,6 +9000,7 @@ function PerformCopyFileFromRemoteSession $op['moreAvailable'] = $true }} }} + $op }} finally @@ -9519,6 +9085,7 @@ function WriteException {{ WriteException $e }} + $finalResult.ExceptionThrown = $true }} }} @@ -9753,7 +9320,6 @@ function PSGetPathDirAndFiles return $op }} - # # Call helper function based on bound parameter set # @@ -9787,13 +9353,13 @@ PSGetPathDirAndFiles @params }} "; - internal static string PSCopyFromSessionHelper = functionToken + PSCopyFromSessionHelperName + @" + internal static readonly string PSCopyFromSessionHelper = functionToken + PSCopyFromSessionHelperName + @" { " + s_PSCopyFromSessionHelperDefinition + @" } "; - private static Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() { + private static readonly Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() { {nameToken, PSCopyFromSessionHelperName}, {definitionToken, s_PSCopyFromSessionHelperDefinitionRestricted} }; @@ -9803,16 +9369,20 @@ PSGetPathDirAndFiles @params #region PSCopyRemoteUtils internal const string PSCopyRemoteUtilsName = @"PSCopyRemoteUtils"; - internal static string PSCopyRemoteUtilsDefinition = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateNotNullOrEmpty()]", PSValidatePathFunction); - private static string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction); + + internal static readonly string PSCopyRemoteUtilsDefinition = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateNotNullOrEmpty()]", PSValidatePathFunction); + private static readonly string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction); + private const string PSCopyRemoteUtilsDefinitionFormat = @" param ( [Parameter(ParameterSetName=""PSRemoteDirectoryExist"", Mandatory=$true)] {0} + [string] $dirPathExists, [Parameter(ParameterSetName=""PSValidatePath"", Mandatory=$true)] {0} + [string] $pathToValidate, [Parameter(ParameterSetName=""PSValidatePath"")] @@ -9829,6 +9399,7 @@ function PSRemoteDirectoryExist ) $result = @{{ Exists = (Microsoft.PowerShell.Management\Test-Path $dirPathExists -PathType Container) }} + return $result }} @@ -9956,27 +9527,29 @@ function SafeGetDriveRoot return $result "; - internal static string PSCopyRemoteUtils = functionToken + PSCopyRemoteUtilsName + @" + internal static readonly string PSCopyRemoteUtils = functionToken + PSCopyRemoteUtilsName + @" { " + PSCopyRemoteUtilsDefinition + @" } "; - internal static Hashtable PSCopyRemoteUtilsFunction = new Hashtable() { + internal static readonly Hashtable PSCopyRemoteUtilsFunction = new Hashtable() { {nameToken, PSCopyRemoteUtilsName}, {definitionToken, s_PSCopyRemoteUtilsDefinitionRestricted} }; #endregion - internal static string AllCopyToRemoteScripts = s_PSCopyToSessionHelper + PSCopyRemoteUtils; + internal static readonly string AllCopyToRemoteScripts = s_PSCopyToSessionHelper + PSCopyRemoteUtils; + internal static IEnumerable GetAllCopyToRemoteScriptFunctions() { yield return s_PSCopyToSessionHelperFunction; yield return PSCopyRemoteUtilsFunction; } - internal static string AllCopyFromRemoteScripts = PSCopyFromSessionHelper + PSCopyRemoteUtils; + internal static readonly string AllCopyFromRemoteScripts = PSCopyFromSessionHelper + PSCopyRemoteUtils; + internal static IEnumerable GetAllCopyFromRemoteScriptFunctions() { yield return s_PSCopyFromSessionHelperFunction; diff --git a/src/System.Management.Automation/namespaces/FileSystemSecurity.cs b/src/System.Management.Automation/namespaces/FileSystemSecurity.cs index 22f07c89cf5..9af94c2d604 100644 --- a/src/System.Management.Automation/namespaces/FileSystemSecurity.cs +++ b/src/System.Management.Automation/namespaces/FileSystemSecurity.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; using System.IO; @@ -15,7 +14,6 @@ namespace Microsoft.PowerShell.Commands /// The FileSystemProvider provides stateless namespace navigation /// of the file system. /// - /// public sealed partial class FileSystemProvider : NavigationCmdletProvider, IContentCmdletProvider, IPropertyCmdletProvider, ISecurityDescriptorCmdletProvider { #region ISecurityDescriptorCmdletProvider members @@ -24,21 +22,17 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, ICont /// Gets the SecurityDescriptor at the specified path, including only the specified /// AccessControlSections. /// - /// /// /// The path of the item to retrieve. It may be a drive or provider-qualified path and may include. /// glob characters. /// - /// /// /// The sections of the security descriptor to include. /// - /// /// /// Nothing. An object that represents the security descriptor for the item /// specified by path is written to the context's pipeline. /// - /// /// /// path is null or empty. /// path doesn't exist @@ -50,14 +44,14 @@ public void GetSecurityDescriptor(string path, ObjectSecurity sd = null; path = NormalizePath(path); - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if ((sections & ~AccessControlSections.All) != 0) { - throw PSTraceSource.NewArgumentException("sections"); + throw PSTraceSource.NewArgumentException(nameof(sections)); } var currentPrivilegeState = new PlatformInvokes.TOKEN_PRIVILEGE(); @@ -89,21 +83,17 @@ public void GetSecurityDescriptor(string path, /// /// Sets the SecurityDescriptor at the specified path. /// - /// /// /// The path of the item to set the security descriptor on. /// It may be a drive or provider-qualified path and may include. /// glob characters. /// - /// /// /// The new security descriptor for the item. /// - /// /// /// path is null or empty. /// - /// /// /// securitydescriptor is null. /// @@ -111,16 +101,16 @@ public void SetSecurityDescriptor( string path, ObjectSecurity securityDescriptor) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } path = NormalizePath(path); if (securityDescriptor == null) { - throw PSTraceSource.NewArgumentNullException("securityDescriptor"); + throw PSTraceSource.NewArgumentNullException(nameof(securityDescriptor)); } if (!File.Exists(path) && !Directory.Exists(path)) @@ -133,7 +123,7 @@ public void SetSecurityDescriptor( if (sd == null) { - throw PSTraceSource.NewArgumentException("securityDescriptor"); + throw PSTraceSource.NewArgumentException(nameof(securityDescriptor)); } else { @@ -151,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 @@ -178,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)) { @@ -192,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; } @@ -208,7 +200,7 @@ public void SetSecurityDescriptor( SetSecurityDescriptor(path, sd, sections); } } - } // SetSecurityDescriptor + } private void SetSecurityDescriptor(string path, ObjectSecurity sd, AccessControlSections sections) { @@ -232,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)) { @@ -260,16 +252,13 @@ private void SetSecurityDescriptor(string path, ObjectSecurity sd, AccessControl /// the item specified by the path. If "path" points to a file system directory, /// then the descriptor returned will be of type DirectorySecurity. /// - /// /// /// Path of the item to use to determine the type of resulting /// SecurityDescriptor. /// - /// /// /// The sections of the security descriptor to create. /// - /// /// /// A new ObjectSecurity object of the same type as /// the item specified by the path. @@ -295,20 +284,16 @@ public ObjectSecurity NewSecurityDescriptorFromPath( /// /// Creates a new empty security descriptor of the specified type. /// - /// /// /// The type of Security Descriptor to create. Valid types are /// "file", "directory," and "container." /// - /// /// /// The sections of the security descriptor to create. /// - /// /// /// A new ObjectSecurity object of the specified type. /// - /// public ObjectSecurity NewSecurityDescriptorOfType( string type, AccessControlSections sections) @@ -357,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 0ec824c5362..ea3d9a7509c 100644 --- a/src/System.Management.Automation/namespaces/FunctionProvider.cs +++ b/src/System.Management.Automation/namespaces/FunctionProvider.cs @@ -1,15 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. - -using System; -using Dbg = System.Management.Automation; using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Provider; +using Dbg = System.Management.Automation; + namespace Microsoft.PowerShell.Commands { /// @@ -27,7 +25,7 @@ namespace Microsoft.PowerShell.Commands public sealed class FunctionProvider : SessionStateProviderBase { /// - /// Gets the name of the provider + /// Gets the name of the provider. /// public const string ProviderName = "Function"; @@ -39,20 +37,18 @@ public sealed class FunctionProvider : SessionStateProviderBase /// public FunctionProvider() { - } // constructor + } #endregion Constructor #region DriveCmdletProvider overrides /// - /// Initializes the function drive + /// Initializes the function drive. /// - /// /// /// An array of a single PSDriveInfo object representing the functions drive. /// - /// protected override Collection InitializeDefaultDrives() { string description = SessionStateStrings.FunctionDriveDescription; @@ -61,62 +57,53 @@ protected override Collection InitializeDefaultDrives() new PSDriveInfo( DriveNames.FunctionDrive, ProviderInfo, - String.Empty, + string.Empty, description, null); Collection drives = new Collection(); drives.Add(functionDrive); return drives; - } // InitializeDefaultDrives + } #endregion DriveCmdletProvider overrides #region Dynamic Parameters /// - /// Gets the dynamic parameters for the NewItem cmdlet + /// Gets the dynamic parameters for the NewItem cmdlet. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// An instance of FunctionProviderDynamicParameters which is the dynamic parameters for /// NewItem. /// - /// protected override object NewItemDynamicParameters(string path, string type, object newItemValue) { return new FunctionProviderDynamicParameters(); } /// - /// Gets the dynamic parameters for the NewItem cmdlet + /// Gets the dynamic parameters for the NewItem cmdlet. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// An instance of FunctionProviderDynamicParameters which is the dynamic parameters for /// SetItem. /// - /// protected override object SetItemDynamicParameters(string path, object value) { return new FunctionProviderDynamicParameters(); @@ -127,49 +114,42 @@ protected override object SetItemDynamicParameters(string path, object value) #region protected members /// - /// Gets a function from session state + /// Gets a function from session state. /// - /// /// /// The name of the function to retrieve. /// - /// /// /// A ScriptBlock that represents the function. /// - /// internal override object GetSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); CommandInfo function = SessionState.Internal.GetFunction(name, Context.Origin); return function; - } // GetSessionStateItem + } /// - /// Sets the function of the specified name to the specified value + /// Sets the function of the specified name to the specified value. /// - /// /// /// The name of the function to set. /// - /// /// /// The new value for the function. /// - /// /// /// If true, the item that was set should be written to WriteItemObject. /// - /// #pragma warning disable 0162 internal override void SetSessionStateItem(string name, object value, bool writeItem) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); FunctionProviderDynamicParameters dynamicParameters = @@ -222,6 +202,7 @@ internal override void SetSessionStateItem(string name, object value, bool write { modifiedItem = SessionState.Internal.SetFunction(name, scriptBlockValue, null, Force, Context.Origin); } + break; } @@ -239,7 +220,7 @@ internal override void SetSessionStateItem(string name, object value, bool write break; } - String stringValue = value as string; + string stringValue = value as string; if (stringValue != null) { ScriptBlock scriptBlock = ScriptBlock.Create(Context.ExecutionContext, stringValue); @@ -252,10 +233,11 @@ internal override void SetSessionStateItem(string name, object value, bool write { modifiedItem = SessionState.Internal.SetFunction(name, scriptBlock, null, Force, Context.Origin); } + break; } - throw PSTraceSource.NewArgumentException("value"); + throw PSTraceSource.NewArgumentException(nameof(value)); } while (false); if (writeItem && modifiedItem != null) @@ -263,7 +245,7 @@ internal override void SetSessionStateItem(string name, object value, bool write WriteItemObject(modifiedItem, modifiedItem.Name, false); } } - } // SetSessionStateItem + } #pragma warning restore 0162 private static void SetOptions(CommandInfo function, ScopedItemOptions options) @@ -274,38 +256,32 @@ private static void SetOptions(CommandInfo function, ScopedItemOptions options) /// /// Removes the specified function from session state. /// - /// /// /// The name of the function to remove from session state. /// - /// internal override void RemoveSessionStateItem(string name) { Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(name), + !string.IsNullOrEmpty(name), "The caller should verify this parameter"); SessionState.Internal.RemoveFunction(name, Force); - } // RemoveSessionStateItem + } /// /// Since items are often more than their value, this method should - /// be overridden to provide the value for an item + /// be overridden to provide the value for an item. /// - /// /// /// The item to extract the value from. /// - /// /// /// The value of the specified item. /// - /// /// /// The default implementation will get /// the Value property of a DictionaryEntry /// - /// internal override object GetValueOfItem(object item) { Dbg.Diagnostics.Assert( @@ -321,35 +297,30 @@ internal override object GetValueOfItem(object item) } return value; - } // GetValueOfItem + } /// - /// Gets a flattened view of the functions in session state + /// Gets a flattened view of the functions in session state. /// - /// /// /// An IDictionary representing the flattened view of the functions in /// session state. /// - /// internal override IDictionary GetSessionStateTable() { - return SessionState.Internal.GetFunctionTable(); - } // GetSessionStateTable + return (IDictionary)SessionState.Internal.GetFunctionTable(); + } /// /// Determines if the item can be renamed. Derived classes that need /// to perform a check should override this method. /// - /// /// /// The item to verify if it can be renamed. /// - /// /// /// true if the item can be renamed or false otherwise. /// - /// internal override bool CanRenameItem(object item) { bool result = false; @@ -377,29 +348,32 @@ internal override bool CanRenameItem(object item) } #endregion protected members - } // FunctionProvider + } /// - /// The dynamic parameter object for the FunctionProvider SetItem and NewItem commands + /// The dynamic parameter object for the FunctionProvider SetItem and NewItem commands. /// public class FunctionProviderDynamicParameters { /// - /// Gets or sets the option parameter for the function + /// Gets or sets the option parameter for the function. /// - /// [Parameter] public ScopedItemOptions Options { - get { return _options; } + get + { + return _options; + } + set { _optionsSet = true; _options = value; } } - private ScopedItemOptions _options = ScopedItemOptions.None; + private ScopedItemOptions _options = ScopedItemOptions.None; /// /// Determines if the Options parameter was set. @@ -409,7 +383,7 @@ internal bool OptionsSet { get { return _optionsSet; } } + private bool _optionsSet; } } - diff --git a/src/System.Management.Automation/namespaces/IContentProvider.cs b/src/System.Management.Automation/namespaces/IContentProvider.cs index dee126fffad..b3773ca0973 100644 --- a/src/System.Management.Automation/namespaces/IContentProvider.cs +++ b/src/System.Management.Automation/namespaces/IContentProvider.cs @@ -1,7 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#nullable enable namespace System.Management.Automation.Provider { #region IContentCmdletProvider @@ -10,7 +10,6 @@ namespace System.Management.Automation.Provider /// An interface that can be implemented on a Cmdlet provider to expose an item's /// content. /// - /// /// /// An IContentCmdletProvider provider implements a set of methods that allows /// the use of a set of core commands against the data store that the provider @@ -32,11 +31,9 @@ public interface IContentCmdletProvider /// /// Gets the content reader for the item at the specified path. /// - /// /// /// The path to the item to get the content reader for. /// - /// /// /// Overrides of this method should return an /// for the item specified by the path. @@ -50,18 +47,16 @@ 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 /// get-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -69,20 +64,17 @@ 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. /// - /// /// /// The path to the item to get the content writer for. /// - /// /// /// An IContentWriter for the item at the specified path. /// - /// /// /// Overrides of this method should return an /// for the item specified by the path. @@ -96,18 +88,16 @@ 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 /// set-content and add-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -115,16 +105,14 @@ 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. /// - /// /// /// The path to the item to clear the content from. /// - /// /// /// Overrides of this method should remove any content from the object but /// not remove (delete) the object itself. @@ -143,12 +131,10 @@ public interface IContentCmdletProvider /// Gives the provider an opportunity to attach additional parameters to the /// clear-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -156,9 +142,8 @@ public interface IContentCmdletProvider /// /// The default implementation returns null. (no additional parameters) /// - object ClearContentDynamicParameters(string path); - } // IContentCmdletProvider + object? ClearContentDynamicParameters(string path); + } #endregion IContentCmdletProvider -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/IContentReader.cs b/src/System.Management.Automation/namespaces/IContentReader.cs index 359bb89f551..6046fee73ed 100644 --- a/src/System.Management.Automation/namespaces/IContentReader.cs +++ b/src/System.Management.Automation/namespaces/IContentReader.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.IO; +#nullable enable namespace System.Management.Automation.Provider { #region IContentReader @@ -20,15 +20,12 @@ public interface IContentReader : IDisposable /// /// Reads the content from the item. /// - /// /// /// The number of "blocks" of data to be read from the item. /// - /// /// /// An array of the blocks of data read from the item. /// - /// /// /// A "block" of content is provider specific. For the file system /// a "block" may be considered a line of text, a byte, a character, or delimited string. @@ -43,15 +40,12 @@ public interface IContentReader : IDisposable /// Moves the current "block" to be read to a position relative to a place /// in the reader. /// - /// /// /// An offset of the number of blocks to seek from the origin. /// - /// /// /// The place in the stream to start the seek from. /// - /// /// /// The implementation of this method moves the content reader /// number of blocks from the specified . See @@ -63,14 +57,12 @@ public interface IContentReader : IDisposable /// Closes the reader. Further reads should fail if the reader /// has been closed. /// - /// /// /// The implementation of this method should close any resources held open by the /// reader. /// void Close(); - } // IContentReader + } #endregion IContentReader -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/IContentWriter.cs b/src/System.Management.Automation/namespaces/IContentWriter.cs index 03e89f6829f..621808af80c 100644 --- a/src/System.Management.Automation/namespaces/IContentWriter.cs +++ b/src/System.Management.Automation/namespaces/IContentWriter.cs @@ -1,10 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.IO; +#nullable enable namespace System.Management.Automation.Provider { #region IContentWriter @@ -20,15 +20,12 @@ public interface IContentWriter : IDisposable /// /// Writes content to the item. /// - /// /// /// An array of content "blocks" to be written to the item. /// - /// /// /// The blocks of content that were successfully written to the item. /// - /// /// /// A "block" of content is provider specific. For the file system /// a "block" may be considered a byte, a character, or delimited string. @@ -44,15 +41,12 @@ public interface IContentWriter : IDisposable /// Moves the current "block" to be written to a position relative to a place /// in the writer. /// - /// /// /// An offset of the number of blocks to seek from the origin. /// - /// /// /// The place in the stream to start the seek from. /// - /// /// /// The implementation of this method moves the content writer /// number of blocks from the specified . See @@ -64,14 +58,12 @@ public interface IContentWriter : IDisposable /// Closes the writer. Further writes should fail if the writer /// has been closed. /// - /// /// /// The implementation of this method should close any resources held open by the /// writer. /// void Close(); - } // IContentWriter + } #endregion IContentWriter -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs b/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs index 274b147b3d5..232f3f5d78b 100644 --- a/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs +++ b/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs @@ -1,16 +1,14 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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. /// - /// /// /// An IDynamicPropertyCmdletProvider provider implements a set of methods that allows /// the use of a set of core commands against the data store that the provider @@ -32,27 +30,21 @@ public interface IDynamicPropertyCmdletProvider : IPropertyCmdletProvider /// /// Creates a new property on the specified item. /// - /// /// /// The path to the item on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// Nothing. The new property that was created should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to add properties to provider objects /// using the new-itemproperty cmdlet. @@ -69,30 +61,25 @@ void NewProperty( string path, string propertyName, string propertyTypeName, - object value); + object? value); /// /// Gives the provider an opportunity to attach additional parameters to the /// new-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -100,28 +87,24 @@ 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. /// - /// /// /// The path to the item on which the property should be removed. /// - /// /// /// The name of the property to be removed. /// - /// /// /// Nothing. /// - /// /// /// Providers override this method to give the user the ability to remove properties from provider objects /// using the remove-itemproperty cmdlet. @@ -142,16 +125,13 @@ void RemoveProperty( /// Gives the provider an opportunity to attach additional parameters to the /// remove-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be removed. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -164,25 +144,20 @@ object RemovePropertyDynamicParameters( string propertyName); /// - /// Renames a property of the item at the specified path + /// Renames a property of the item at the specified path. /// - /// /// /// The path to the item on which to rename the property. /// - /// /// /// The property to rename. /// - /// /// /// The new name of the property. /// - /// /// /// Nothing. The new property that was renamed should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to rename properties of provider objects /// using the rename-itemproperty cmdlet. @@ -195,7 +170,6 @@ object RemovePropertyDynamicParameters( /// 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. /// - /// void RenameProperty( string path, string sourceProperty, @@ -205,20 +179,16 @@ void RenameProperty( /// Gives the provider an opportunity to attach additional parameters to the /// rename-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The property to rename. /// - /// /// /// The new name of the property. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -226,7 +196,7 @@ void RenameProperty( /// /// The default implementation returns null. (no additional parameters) /// - object RenamePropertyDynamicParameters( + object? RenamePropertyDynamicParameters( string path, string sourceProperty, string destinationProperty); @@ -235,27 +205,21 @@ object RenamePropertyDynamicParameters( /// Copies a property of the item at the specified path to a new property on the /// destination item. /// - /// /// /// The path to the item on which to copy the property. /// - /// /// /// The name of the property to copy. /// - /// /// /// The path to the item on which to copy the property to. /// - /// /// /// The destination property to copy to. /// - /// /// /// Nothing. The new property that was copied to should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to copy properties of provider objects /// using the copy-itemproperty cmdlet. @@ -278,24 +242,19 @@ void CopyProperty( /// Gives the provider an opportunity to attach additional parameters to the /// copy-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property to copy. /// - /// /// /// The path to the item on which to copy the property to. /// - /// /// /// The destination property to copy to. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -303,7 +262,7 @@ void CopyProperty( /// /// The default implementation returns null. (no additional parameters) /// - object CopyPropertyDynamicParameters( + object? CopyPropertyDynamicParameters( string sourcePath, string sourceProperty, string destinationPath, @@ -312,27 +271,21 @@ object CopyPropertyDynamicParameters( /// /// Moves a property on an item specified by the path. /// - /// /// /// The path to the item on which to move the property. /// - /// /// /// The name of the property to move. /// - /// /// /// The path to the item on which to move the property to. /// - /// /// /// The destination property to move to. /// - /// /// /// Nothing. The new property that was created should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to move properties from one provider object /// to another using the move-itemproperty cmdlet. @@ -355,24 +308,19 @@ void MoveProperty( /// Gives the provider an opportunity to attach additional parameters to the /// move-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property to copy. /// - /// /// /// The path to the item on which to copy the property to. /// - /// /// /// The destination property to copy to. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -380,13 +328,12 @@ void MoveProperty( /// /// The default implementation returns null. (no additional parameters) /// - object MovePropertyDynamicParameters( + object? MovePropertyDynamicParameters( string sourcePath, string sourceProperty, string destinationPath, string destinationProperty); - } // IDynamicPropertyCmdletProvider + } #endregion IDynamicPropertyCmdletProvider -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/IPermissionProvider.cs b/src/System.Management.Automation/namespaces/IPermissionProvider.cs index 29b6f33aad0..c4f548e08a7 100644 --- a/src/System.Management.Automation/namespaces/IPermissionProvider.cs +++ b/src/System.Management.Automation/namespaces/IPermissionProvider.cs @@ -1,7 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#nullable enable using System.Security.AccessControl; namespace System.Management.Automation.Provider @@ -17,27 +17,22 @@ namespace System.Management.Automation.Provider /// , , /// , or . /// - /// /// /// A namespace provider should implement this interface if items in the /// namespace are protected by Security Descriptors. /// - /// public interface ISecurityDescriptorCmdletProvider { /// /// Gets the security descriptor for the item at the specified path. /// - /// /// /// The path of the item to from which to retrieve the security descriptor. /// - /// /// /// The sections of the security descriptor to retrieve, if your provider /// supports them. /// - /// /// /// Nothing. Write the security descriptor to the context's pipeline for /// the item specified by the path using the WriteSecurityDescriptorObject @@ -50,22 +45,18 @@ void GetSecurityDescriptor( /// /// Sets the security descriptor for the item at the specified path. /// - /// /// /// The path of the item to for which to set the security descriptor. /// - /// /// /// The new security descriptor for the item. This should replace the /// previously existing security descriptor. /// - /// /// /// Nothing. After setting the security descriptor to the value passed in, /// write the new security descriptor to the context's pipeline for the /// item specified by the path using the WriteSecurityDescriptorObject method. /// - /// void SetSecurityDescriptor( string path, ObjectSecurity securityDescriptor); @@ -76,16 +67,13 @@ void SetSecurityDescriptor( /// to a file system directory, the descriptor returned will be /// of type DirectorySecurity. /// - /// /// /// Path of the item to use to determine the type of resulting /// SecurityDescriptor. /// - /// /// /// The sections of the security descriptor to create. /// - /// /// /// A new ObjectSecurity object of the same type as /// the item specified by the path. @@ -99,7 +87,6 @@ ObjectSecurity NewSecurityDescriptorFromPath( /// This method is used as a convenience function for consumers of /// your provider. /// - /// /// /// The type of Security Descriptor to create. Your provider should /// understand a string representation for each of the types of @@ -108,18 +95,16 @@ ObjectSecurity NewSecurityDescriptorFromPath( /// FileSecurity descriptor, and "directory" or "container" for a /// DirectorySecurity descriptor. /// - /// /// /// The sections of the security descriptor to create. /// - /// /// /// A new ObjectSecurity object of the specified type. /// ObjectSecurity NewSecurityDescriptorOfType( string type, AccessControlSections includeSections); - } // ISecurityDescriptorCmdletProvider + } #endregion ISecurityDescriptorCmdletProvider -} // namespace System.Management.Automation +} diff --git a/src/System.Management.Automation/namespaces/IPropertiesProvider.cs b/src/System.Management.Automation/namespaces/IPropertiesProvider.cs index 47f92c7fb7e..1f4a6c168fe 100644 --- a/src/System.Management.Automation/namespaces/IPropertiesProvider.cs +++ b/src/System.Management.Automation/namespaces/IPropertiesProvider.cs @@ -1,9 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; +#nullable enable namespace System.Management.Automation.Provider { #region IPropertyCmdletProvider @@ -11,7 +11,6 @@ namespace System.Management.Automation.Provider /// /// An interface that can be implemented by a Cmdlet provider to expose properties of an item. /// - /// /// /// An IPropertyCmdletProvider provider implements a set of methods that allows /// the use of a set of core commands against the data store that the provider @@ -32,20 +31,16 @@ public interface IPropertyCmdletProvider /// /// Gets the properties of the item specified by the path. /// - /// /// /// The path to the item to retrieve properties from. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// Nothing. The property that was retrieved should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to add properties to provider objects /// using the get-itemproperty cmdlet. @@ -64,23 +59,20 @@ public interface IPropertyCmdletProvider /// void GetProperty( string path, - Collection providerSpecificPickList); + Collection? providerSpecificPickList); /// /// Gives the provider an opportunity to attach additional parameters to the /// get-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -88,27 +80,23 @@ 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. /// - /// /// /// The path to the item to set the properties on. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// Nothing. The property that was set should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to set the value of provider object properties /// using the set-itemproperty cmdlet. @@ -124,7 +112,6 @@ object GetPropertyDynamicParameters( /// An can be used as a property bag for the /// properties that need to be returned if the contains /// multiple properties to write. - /// /// is a property bag containing the properties that should be set. /// See for more information. /// @@ -136,17 +123,14 @@ void SetProperty( /// Gives the provider an opportunity to attach additional parameters to the /// get-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -154,26 +138,22 @@ void SetProperty( /// /// The default implementation returns null. (no additional parameters) /// - object SetPropertyDynamicParameters( + object? SetPropertyDynamicParameters( string path, PSObject propertyValue); /// /// Clears a property of the item at the specified path. /// - /// /// /// The path to the item on which to clear the property. /// - /// /// /// The name of the property to clear. /// - /// /// /// Nothing. The property that was cleared should be passed to the WritePropertyObject method. /// - /// /// /// Providers override this method to give the user the ability to clear the value of provider object properties /// using the clear-itemproperty cmdlet. @@ -198,16 +178,13 @@ void ClearProperty( /// Gives the provider an opportunity to attach additional parameters to the /// clear-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property to clear. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -215,12 +192,10 @@ void ClearProperty( /// /// The default implementation returns null. (no additional parameters) /// - object ClearPropertyDynamicParameters( + object? ClearPropertyDynamicParameters( string path, Collection propertyToClear); - } // IPropertyCmdletProvider + } #endregion IPropertyCmdletProvider -} // namespace System.Management.Automation - - +} diff --git a/src/System.Management.Automation/namespaces/ItemProviderBase.cs b/src/System.Management.Automation/namespaces/ItemProviderBase.cs index 9dff69f286c..e282093de49 100644 --- a/src/System.Management.Automation/namespaces/ItemProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ItemProviderBase.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Internal; @@ -9,16 +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 @@ -30,19 +28,15 @@ public abstract class ItemCmdletProvider : DriveCmdletProvider /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to retrieve. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteObject method. /// - /// internal void GetItem(string path, CmdletProviderContext context) { Context = context; @@ -50,22 +44,19 @@ internal void GetItem(string path, CmdletProviderContext context) // Call virtual method GetItem(path); - } // GetItem + } /// /// Gives the provider to attach additional parameters to /// the get-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -73,35 +64,29 @@ internal void GetItem(string path, CmdletProviderContext context) /// /// The default implementation returns null. (no additional parameters) /// - /// internal object GetItemDynamicParameters(string path, CmdletProviderContext context) { Context = context; return GetItemDynamicParameters(path); - } // GetItemDynamicParameters + } /// /// Internal wrapper for the SetItem protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to set. /// - /// /// /// The value of the item specified by the path. /// - /// /// /// The context under which this method is being called. /// - /// /// /// The item that was set at the specified path. /// - /// internal void SetItem( string path, object value, @@ -114,26 +99,22 @@ internal void SetItem( // Call virtual method SetItem(path, value); - } // SetItem + } /// /// Gives the provider to attach additional parameters to /// the set-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The value of the item specified by the path. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -141,7 +122,6 @@ internal void SetItem( /// /// The default implementation returns null. (no additional parameters) /// - /// internal object SetItemDynamicParameters( string path, object value, @@ -149,22 +129,19 @@ internal object SetItemDynamicParameters( { Context = context; return SetItemDynamicParameters(path, value); - } // SetItemDynamicParameters + } /// /// Internal wrapper for the ClearItem protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to clear. /// - /// /// /// The context under which this method is being called. /// - /// internal void ClearItem( string path, CmdletProviderContext context) @@ -176,22 +153,19 @@ internal void ClearItem( // Call virtual method ClearItem(path); - } // ClearItem + } /// /// Gives the provider to attach additional parameters to /// the clear-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -199,30 +173,25 @@ internal void ClearItem( /// /// The default implementation returns null. (no additional parameters) /// - /// internal object ClearItemDynamicParameters( string path, CmdletProviderContext context) { Context = context; return ClearItemDynamicParameters(path); - } // ClearItemDynamicParameters - + } /// /// Internal wrapper for the InvokeDefaultAction protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to perform the default action on. /// - /// /// /// The context under which this method is being called. /// - /// internal void InvokeDefaultAction( string path, CmdletProviderContext context) @@ -234,22 +203,19 @@ internal void InvokeDefaultAction( // Call virtual method InvokeDefaultAction(path); - } // InvokeDefaultAction + } /// /// Gives the provider to attach additional parameters to /// the invoke-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -257,33 +223,28 @@ internal void InvokeDefaultAction( /// /// The default implementation returns null. (no additional parameters) /// - /// internal object InvokeDefaultActionDynamicParameters( string path, CmdletProviderContext context) { Context = context; return InvokeDefaultActionDynamicParameters(path); - } // InvokeDefaultActionDynamicParameters + } /// /// Internal wrapper for the Exists protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to see if it exists. /// - /// /// /// The context under which this method is being called. /// - /// /// /// True if the item exists, false otherwise. /// - /// internal bool ItemExists(string path, CmdletProviderContext context) { Context = context; @@ -302,22 +263,19 @@ internal bool ItemExists(string path, CmdletProviderContext context) } return itemExists; - } // Exists + } /// /// Gives the provider to attach additional parameters to /// the test-path cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -325,41 +283,35 @@ internal bool ItemExists(string path, CmdletProviderContext context) /// /// The default implementation returns null. (no additional parameters) /// - /// internal object ItemExistsDynamicParameters( string path, CmdletProviderContext context) { Context = context; return ItemExistsDynamicParameters(path); - } // ItemExistsDynamicParameters + } /// /// Internal wrapper for the IsValidPath protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to check for validity. /// - /// /// /// The context under which this method is being called. /// - /// /// /// True if the path is syntactically and semantically valid for the provider, or /// false otherwise. /// - /// /// /// This test should not verify the existence of the item at the path. It should /// only perform syntactic and semantic validation of the path. For instance, for /// the file system provider, that path should be canonicalized, syntactically verified, /// and ensure that the path does not refer to a device. /// - /// internal bool IsValidPath(string path, CmdletProviderContext context) { Context = context; @@ -367,7 +319,7 @@ internal bool IsValidPath(string path, CmdletProviderContext context) // Call virtual method return IsValidPath(path); - } // IsValidPath + } /// /// Internal wrapper for the ExpandPath protected method. It is called instead @@ -375,45 +327,37 @@ internal bool IsValidPath(string path, CmdletProviderContext context) /// context of the command can be set. Only called for providers that declare /// the ExpandWildcards capability. /// - /// /// /// The path to expand. Expansion must be consistent with the wildcarding /// rules of PowerShell's WildcardPattern class. /// - /// /// /// The context under which this method is being called. /// - /// /// /// A list of provider paths that this path expands to. They must all exist. /// - /// internal string[] ExpandPath(string path, CmdletProviderContext context) { Context = context; // Call virtual method return ExpandPath(path); - } // IsValidPath + } #endregion internal methods - #region Protected methods /// /// Gets the item at the specified path. /// - /// /// /// The path to the item to retrieve. /// - /// /// /// Nothing is returned, but all objects should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user access to the provider objects using /// the get-item and get-childitem cmdlets. @@ -442,12 +386,10 @@ protected virtual void GetItem(string path) /// Gives the provider an opportunity to attach additional parameters to /// the get-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -461,24 +403,20 @@ protected virtual object GetItemDynamicParameters(string path) { return null; } - } // GetItemDynamicParameters + } /// /// Sets the item specified by the path. /// - /// /// /// The path to the item to set. /// - /// /// /// The value of the item specified by the path. /// - /// /// /// Nothing. The item that was set should be passed to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to modify provider objects using /// the set-item cmdlet. @@ -509,16 +447,13 @@ protected virtual void SetItem( /// Gives the provider an opportunity to attach additional parameters to /// the set-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The value of the item specified by the path. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -532,20 +467,17 @@ protected virtual object SetItemDynamicParameters(string path, object value) { return null; } - } // SetItemDynamicParameters + } /// /// Clears the item specified by the path. /// - /// /// /// The path to the item to clear. /// - /// /// /// Nothing. The item that was cleared should be passed to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to clear provider objects using /// the clear-item cmdlet. @@ -575,12 +507,10 @@ protected virtual void ClearItem( /// Gives the provider an opportunity to attach additional parameters to /// the clear-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -594,20 +524,17 @@ protected virtual object ClearItemDynamicParameters(string path) { return null; } - } // ClearItemDynamicParameters + } /// /// Invokes the default action on the specified item. /// - /// /// /// The path to the item to perform the default action on. /// - /// /// /// Nothing. The item that was set should be passed to the WriteItemObject method. /// - /// /// /// The default implementation does nothing. /// @@ -638,12 +565,10 @@ protected virtual void InvokeDefaultAction( /// Gives the provider an opportunity to attach additional parameters to /// the invoke-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -657,24 +582,20 @@ protected virtual object InvokeDefaultActionDynamicParameters(string path) { return null; } - } // InvokeDefaultActionDynamicParameters + } /// /// Determines if an item exists at the specified path. /// - /// /// /// The path to the item to see if it exists. /// - /// /// /// True if the item exists, false otherwise. /// - /// /// /// Nothing. The item that was set should be passed to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to check for the existence of provider objects using /// the set-item cmdlet. @@ -704,12 +625,10 @@ protected virtual bool ItemExists(string path) /// Gives the provider an opportunity to attach additional parameters to /// the test-path cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -723,22 +642,19 @@ protected virtual object ItemExistsDynamicParameters(string path) { return null; } - } // ItemExistsDynamicParameters + } /// /// Providers must override this method to verify the syntax and semantics /// of their paths. /// - /// /// /// The path to check for validity. /// - /// /// /// True if the path is syntactically and semantically valid for the provider, or /// false otherwise. /// - /// /// /// This test should not verify the existence of the item at the path. It should /// only perform syntactic and semantic validation of the path. For instance, for @@ -752,27 +668,23 @@ protected virtual object ItemExistsDynamicParameters(string path) /// paths that the path represents.Only called for providers that declare /// the ExpandWildcards capability. /// - /// /// /// The path to expand. Expansion must be consistent with the wildcarding /// rules of PowerShell's WildcardPattern class. /// - /// /// /// A list of provider paths that this path expands to. They must all exist. /// - /// protected virtual string[] ExpandPath(string path) { using (PSTransactionManager.GetEngineProtectionScope()) { return new string[] { path }; } - } // IsValidPath + } #endregion Protected methods - } // ItemCmdletProvider + } #endregion ItemCmdletProvider -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index 5afeb0cd228..65c28f1586d 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -1,11 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation.Provider; using System.Text; + using Dbg = System.Management.Automation; namespace System.Management.Automation @@ -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.", @@ -46,26 +46,23 @@ internal sealed class LocationGlobber #region Constructor /// - /// Constructs an instance of the LocationGlobber from the current SessionState + /// Constructs an instance of the LocationGlobber from the current SessionState. /// - /// /// /// The instance of session state on which this location globber acts. /// - /// /// - /// If is null. + /// If is null. /// - /// internal LocationGlobber(SessionState sessionState) { if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _sessionState = sessionState; - } // LocationGlobber(SessionState) + } #endregion Constructor @@ -76,52 +73,41 @@ internal LocationGlobber(SessionState sessionState) /// Converts a PowerShell path containing glob characters to PowerShell paths that match /// the glob string. /// - /// /// /// A PowerShell path containing glob characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The provider instance used to resolve the path. /// - /// /// /// The PowerShell paths that match the glob string. /// - /// /// /// If is null. /// - /// /// /// If is a provider-qualified path /// and the specified provider does not exist. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// /// /// If the provider does not support multiple items. /// - /// /// /// If the home location for the provider is not set and /// starts with a "~". /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// internal Collection GetGlobbedMonadPathsFromMonadPath( string path, bool allowNonexistingPaths, @@ -131,67 +117,54 @@ internal Collection GetGlobbedMonadPathsFromMonadPath( new CmdletProviderContext(_sessionState.Internal.ExecutionContext); return GetGlobbedMonadPathsFromMonadPath(path, allowNonexistingPaths, context, out providerInstance); - } // GetGlobbedMonadPathsFromMonadPath + } /// /// Converts a PowerShell path containing glob characters to PowerShell paths that match /// the glob string. /// - /// /// /// A PowerShell path containing glob characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The context under which the command is running. /// - /// /// /// The instance of the provider used to resolve the path. /// - /// /// /// The PowerShell paths that match the glob string. /// - /// /// /// If or is null. /// - /// /// /// If is a provider-qualified path /// and the specified provider does not exist. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// /// /// If the provider does not support multiple items. /// - /// /// /// If the home location for the provider is not set and /// starts with a "~". /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// internal Collection GetGlobbedMonadPathsFromMonadPath( string path, bool allowNonexistingPaths, @@ -201,12 +174,12 @@ internal Collection GetGlobbedMonadPathsFromMonadPath( providerInstance = null; if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } Collection result; @@ -270,8 +243,9 @@ internal Collection GetGlobbedMonadPathsFromMonadPath( throw pathNotFound; } } + return result; - } // GetGlobbedMonadPathsFromMonadPath + } private Collection ResolveProviderPathFromProviderPath( string providerPath, @@ -434,7 +408,7 @@ out providerInstance else { constructedProviderPath = - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, "{0}::{1}", providerId, @@ -461,7 +435,13 @@ private Collection ResolveDriveQualifiedPath( s_pathResolutionTracer.WriteLine("Path is DRIVE-QUALIFIED"); - string relativePath = GetDriveRootRelativePathFromPSPath(path, context, true, out drive, out providerInstance); + string relativePath = + GetDriveRootRelativePathFromPSPath( + path, + context, + !context.SuppressWildcardExpansion, + out drive, + out providerInstance); Dbg.Diagnostics.Assert( drive != null, @@ -498,24 +478,11 @@ private Collection ResolveDriveQualifiedPath( userPath = GetDriveQualifiedPath(relativePath, drive); itemPath = GetProviderPath(path, context); } + s_pathResolutionTracer.WriteLine("PROVIDER path: {0}", itemPath); Collection stringResult = new Collection(); - // if the directory exists, just return it - try - { - if (Utils.NativeDirectoryExists(userPath)) - { - result.Add(new PathInfo(drive, provider, userPath, _sessionState)); - return result; - } - } - catch - { - // in cases of Access Denied or other errors, fallback to previous behavior and let provider handle it - } - if (!context.SuppressWildcardExpansion) { // See if the provider will expand the wildcard @@ -637,71 +604,56 @@ private Collection ResolveDriveQualifiedPath( /// Converts a PowerShell path containing glob characters to the provider /// specific paths matching the glob strings. /// - /// /// /// A PowerShell path containing glob characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// Returns the information of the provider that was used to do the globbing. /// - /// /// /// The instance of the provider used to resolve the path. /// - /// /// /// An array of provider specific paths that matched the PowerShell glob path. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection GetGlobbedProviderPathsFromMonadPath( string path, bool allowNonexistingPaths, @@ -711,88 +663,72 @@ internal Collection GetGlobbedProviderPathsFromMonadPath( providerInstance = null; if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = new CmdletProviderContext(_sessionState.Internal.ExecutionContext); return GetGlobbedProviderPathsFromMonadPath(path, allowNonexistingPaths, context, out provider, out providerInstance); - } // GetGlobbedProviderPathsFromMonadPath + } /// /// Converts a PowerShell path containing glob characters to the provider /// specific paths matching the glob strings. /// - /// /// /// A PowerShell path containing glob characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The context under which the command is running. /// - /// /// /// Returns the information of the provider that was used to do the globbing. /// - /// /// /// The instance of the provider used to resolve the path. /// - /// /// /// An array of provider specific paths that matched the PowerShell glob path. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection GetGlobbedProviderPathsFromMonadPath( string path, bool allowNonexistingPaths, @@ -802,12 +738,12 @@ internal Collection GetGlobbedProviderPathsFromMonadPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } using (s_pathResolutionTracer.TraceScope("Resolving MSH path \"{0}\" to PROVIDER-INTERNAL path", path)) @@ -837,7 +773,6 @@ internal Collection GetGlobbedProviderPathsFromMonadPath( context.Drive = drive; } - Collection paths = new Collection(); foreach (PathInfo currentPath in @@ -852,7 +787,7 @@ internal Collection GetGlobbedProviderPathsFromMonadPath( return paths; } - } // GetGlobbedProviderPathsFromMonadPath + } #endregion Provider paths from Monad path globbing @@ -863,60 +798,47 @@ internal Collection GetGlobbedProviderPathsFromMonadPath( /// will perform the globbing using the specified provider and return the /// matching provider specific paths. /// - /// /// /// The path containing the glob characters to resolve. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The ID of the provider to use to do the resolution. /// - /// /// /// The instance of the provider that was used to resolve the path. /// - /// /// /// An array of provider specific paths that match the glob path. /// - /// /// /// If is null. /// - /// /// /// If references a provider that does not exist. /// - /// /// /// If the references a provider that is not /// a ContainerCmdletProvider. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection GetGlobbedProviderPathsFromProviderPath( string path, bool allowNonexistingPaths, @@ -927,7 +849,7 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = @@ -951,69 +873,57 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( throw errorRecord.Exception; } } + return results; - } // GetGlobbedProviderPathsFromProviderPath + } /// /// Given a provider specific path that contains glob characters, this method /// will perform the globbing using the specified provider and return the /// matching provider specific paths. /// - /// /// /// The path containing the glob characters to resolve. The path must be in the /// form providerId::providerPath. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The provider identifier for the provider to use to do the globbing. /// - /// /// /// The context under which the command is occurring. /// - /// /// /// An instance of the provider that was used to perform the globbing. /// - /// /// /// An array of provider specific paths that match the glob path. /// - /// /// /// If , , or /// is null. /// - /// /// /// If references a provider that does not exist. /// - /// /// /// If the references a provider that is not /// a ContainerCmdletProvider. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection GetGlobbedProviderPathsFromProviderPath( string path, bool allowNonexistingPaths, @@ -1025,17 +935,17 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (providerId == null) { - throw PSTraceSource.NewArgumentNullException("providerId"); + throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } using (s_pathResolutionTracer.TraceScope("Resolving PROVIDER-INTERNAL path \"{0}\" to PROVIDER-INTERNAL path", path)) @@ -1049,131 +959,105 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( context, out providerInstance); } - } // GetGlobbedProviderPathsFromProviderPath - + } #endregion Provider path to provider paths globbing - #region Path manipulation /// /// Gets a provider specific path when given an Msh path without resolving the /// glob characters. /// - /// /// /// An Msh path. /// - /// /// /// A provider specific path that the Msh path represents. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal string GetProviderPath(string path) { ProviderInfo provider = null; return GetProviderPath(path, out provider); - } // GetProviderPath - + } /// /// Gets a provider specific path when given an Msh path without resolving the /// glob characters. /// - /// /// /// An Msh path. /// - /// /// /// The information of the provider that was used to resolve the path. /// - /// /// /// A provider specific path that the Msh path represents. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal string GetProviderPath(string path, out ProviderInfo provider) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } CmdletProviderContext context = @@ -1196,68 +1080,56 @@ internal string GetProviderPath(string path, out ProviderInfo provider) } return result; - } // GetProviderPath + } /// /// Gets a provider specific path when given an Msh path without resolving the /// glob characters. /// - /// /// /// An Msh path. /// - /// /// /// The context of the command. /// - /// /// /// A provider specific path that the Msh path represents. /// - /// /// /// If is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal string GetProviderPath(string path, CmdletProviderContext context) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } PSDriveInfo drive = null; @@ -1266,66 +1138,53 @@ internal string GetProviderPath(string path, CmdletProviderContext context) string result = GetProviderPath(path, context, out provider, out drive); return result; - } // GetProviderPath + } /// /// Returns a provider specific path for given PowerShell path. /// - /// /// /// Either a PowerShell path or a provider path in the form providerId::providerPath /// - /// /// /// The command context under which this operation is occurring. /// - /// /// /// This parameter is filled with the provider information for the given path. /// - /// /// /// This parameter is filled with the PowerShell drive that represents the given path. If a /// provider path is given drive will be null. /// - /// /// /// The provider specific path generated from the given path. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// internal string GetProviderPath( string path, CmdletProviderContext context, @@ -1343,11 +1202,11 @@ internal string GetProviderPath( /// /// Returns a provider specific path for given PowerShell path. /// - /// Path to resolve - /// Cmdlet context - /// When true bypass trust check - /// Provider - /// Drive + /// Path to resolve. + /// Cmdlet context. + /// When true bypass trust check. + /// Provider. + /// Drive. /// internal string GetProviderPath( string path, @@ -1358,12 +1217,12 @@ internal string GetProviderPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } string result = null; @@ -1439,6 +1298,7 @@ internal string GetProviderPath( { result = GetProviderSpecificPath(drive, relativePath, context); } + provider = drive.Provider; } @@ -1469,26 +1329,22 @@ internal string GetProviderPath( } return result; - } // GetProviderPath + } /// /// Determines if the specified path is a provider. This is done by looking for /// two colons in a row. Anything before the colons is considered the provider ID, /// and everything after is considered a namespace specific path. /// - /// /// /// The path to check to see if it is a provider path. /// - /// /// /// True if the path is a provider path, false otherwise. /// - /// /// /// If is null. /// - /// internal static bool IsProviderQualifiedPath(string path) { string providerId = null; @@ -1500,30 +1356,25 @@ internal static bool IsProviderQualifiedPath(string path) /// two colons in a row. Anything before the colons is considered the provider ID, /// and everything after is considered a namespace specific path. /// - /// /// /// The path to check to see if it is a provider path. /// - /// /// /// The name of the provider if the path is a provider qualified path. /// - /// /// /// True if the path is a provider path, false otherwise. /// - /// /// /// If is null. /// - /// internal static bool IsProviderQualifiedPath(string path, out string providerId) { // Verify parameters if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } providerId = null; @@ -1577,58 +1428,51 @@ internal static bool IsProviderQualifiedPath(string path, out string providerId) } while (false); return result; - } // IsProviderQualifiedPath + } /// /// Determines if the given path is absolute while on a single root filesystem. /// - /// /// /// Porting notes: absolute paths on non-Windows filesystems start with a '/' (no "C:" drive /// prefix, the slash is the prefix). We compare against both '/' and '\' (default and /// alternate path separator) in order for PowerShell to be slash agnostic. /// - /// /// /// The path used in the determination /// - /// /// /// Returns true if we're on a single root filesystem and the path is absolute. /// internal static bool IsSingleFileSystemAbsolutePath(string path) { #if UNIX - return path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) - || path.StartsWith(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal); + return path.StartsWith(StringLiterals.DefaultPathSeparator) + || path.StartsWith(StringLiterals.AlternatePathSeparator); #else return false; #endif - } // IsSingleFileSystemAbsolutePath + } /// - /// Determines if the given path is relative or absolute + /// Determines if the given path is relative or absolute. /// - /// /// /// The path used in the determination /// - /// /// /// true if the path is an absolute path, false otherwise. /// - /// /// /// If is null. /// - /// internal static bool IsAbsolutePath(string path) { // Verify parameters if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } bool result = false; @@ -1662,7 +1506,7 @@ internal static bool IsAbsolutePath(string path) break; } - int index = path.IndexOf(":", StringComparison.Ordinal); + int index = path.IndexOf(':'); if (index == -1) { @@ -1681,11 +1525,12 @@ internal static bool IsAbsolutePath(string path) { // see if there are any path separators before the colon which would mean the // colon is part of a file or folder name and not a drive: ./foo:bar vs foo:bar - int separator = path.IndexOf(StringLiterals.DefaultPathSeparator, 0, index-1); + int separator = path.IndexOf(StringLiterals.DefaultPathSeparator, 0, index - 1); if (separator == -1) { - separator = path.IndexOf(StringLiterals.AlternatePathSeparator, 0, index-1); + separator = path.IndexOf(StringLiterals.AlternatePathSeparator, 0, index - 1); } + if (separator == -1 || index < separator) { // We must have a drive specified @@ -1695,37 +1540,32 @@ internal static bool IsAbsolutePath(string path) } while (false); return result; - } // IsAbsolutePath + } /// - /// Determines if the given path is relative or absolute + /// Determines if the given path is relative or absolute. /// - /// /// /// The path used in the determination /// - /// /// /// If the path is absolute, this out parameter will be the /// drive name of the drive that is referenced. /// - /// /// /// true if the path is an absolute path, false otherwise. /// - /// internal bool IsAbsolutePath(string path, out string driveName) { // Verify parameters if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } bool result = false; - if (_sessionState.Drive.Current != null) { driveName = _sessionState.Drive.Current.Name; @@ -1764,7 +1604,7 @@ internal bool IsAbsolutePath(string path, out string driveName) break; } - int index = path.IndexOf(":", StringComparison.CurrentCulture); + int index = path.IndexOf(':'); if (index == -1) { @@ -1803,7 +1643,7 @@ internal bool IsAbsolutePath(string path, out string driveName) #endif return result; - } // IsAbsolutePath + } #endregion Path manipulation @@ -1814,150 +1654,127 @@ 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. /// - /// /// /// The path to remove the glob escaping from. /// - /// /// /// The path with the glob characters unescaped. /// - /// /// - /// If is null. + /// If is null. /// - /// private static string RemoveGlobEscaping(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } string result = WildcardPattern.Unescape(path); return result; - } // RemoveGlobEscaping + } #region Path manipulation methods - /// /// Determines if the given drive name is a "special" name defined /// by the shell. For instance, "default", "current", "global", and "scope[##]" are scopes /// for variables and are considered shell virtual drives. /// - /// /// /// The name of the drive to check to see if it is a shell virtual drive. /// - /// /// /// This out parameter is filled with the scope that the drive name represents. /// It will be null if the driveName does not represent a scope. /// - /// /// /// true, if the drive name is a shell virtual drive like "Default" or "global", /// false otherwise. /// - /// /// - /// If is null. + /// If is null. /// - /// /// /// The comparison is done using a case-insensitive comparison using the /// Invariant culture. /// /// This is internal so that it is accessible to SessionState. /// - /// /// /// If is null. /// - /// internal bool IsShellVirtualDrive(string driveName, out SessionStateScope scope) { if (driveName == null) { - throw PSTraceSource.NewArgumentNullException("driveName"); + throw PSTraceSource.NewArgumentNullException(nameof(driveName)); } bool result = false; - // Is it the global scope? - - if (String.Compare( + if (string.Equals( driveName, StringLiterals.Global, - StringComparison.OrdinalIgnoreCase) == 0) + StringComparison.OrdinalIgnoreCase)) { + // It's the global scope. s_tracer.WriteLine("match found: {0}", StringLiterals.Global); result = true; scope = _sessionState.Internal.GlobalScope; - } // globalScope - - // Is it the local scope? - - else if (String.Compare( + } + else if (string.Equals( driveName, StringLiterals.Local, - StringComparison.OrdinalIgnoreCase) == 0) + StringComparison.OrdinalIgnoreCase)) { + // It's the local scope. s_tracer.WriteLine("match found: {0}", driveName); result = true; scope = _sessionState.Internal.CurrentScope; - } // currentScope + } else { scope = null; } return result; - } // IsShellVirtualDrive + } /// /// Gets a provider specific path that represents the specified path and is relative /// to the root of the PowerShell drive. /// - /// /// /// Can be a relative or absolute path. /// - /// /// /// The context which the core command is running. /// - /// /// /// Escape the wildcards in the current location. Use when this path will be /// passed through globbing. /// - /// /// /// 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. /// /// If the path refers to a non-existent drive, this parameter is set to null, and an exception is thrown. - /// /// - /// /// /// The provider instance that was used. /// - /// /// /// A provider specific relative path to the root of the drive. /// - /// /// /// The path is parsed to determine if it is a relative path to the /// current working drive or if it is an absolute path. If @@ -1971,30 +1788,24 @@ 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. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If the provider is not a NavigationCmdletProvider. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// internal string GetDriveRootRelativePathFromPSPath( string path, CmdletProviderContext context, @@ -2006,7 +1817,7 @@ internal string GetDriveRootRelativePathFromPSPath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } workingDriveForPath = null; @@ -2052,15 +1863,13 @@ internal string GetDriveRootRelativePathFromPSPath( string normalizedRoot = _sessionState.Drive.Current.Root.Replace( StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); - if (normalizedRoot.IndexOf(":", StringComparison.CurrentCulture) >= 0) + if (normalizedRoot.Contains(':')) { string normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); if (normalizedPath.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase)) { isPathForCurrentDrive = true; - path = path.Substring(normalizedRoot.Length); - path = path.TrimStart(StringLiterals.DefaultPathSeparator); - path = StringLiterals.DefaultPathSeparator + path; + path = string.Concat(StringLiterals.DefaultPathSeparatorString, path.AsSpan(normalizedRoot.Length).TrimStart(StringLiterals.DefaultPathSeparator)); workingDriveForPath = _sessionState.Drive.Current; } } @@ -2118,7 +1927,9 @@ internal string GetDriveRootRelativePathFromPSPath( // have access to it. context.Drive = workingDriveForPath; - string relativePath = + string relativePath = string.Empty; + + relativePath = GenerateRelativePath( workingDriveForPath, path, @@ -2134,9 +1945,9 @@ internal string GetDriveRootRelativePathFromPSPath( // always be empty providerInstance = null; - return ""; + return string.Empty; } - } // GetDriveRootRelativePathFromPSPath + } private string GetDriveRootRelativePathFromProviderPath( string providerPath, @@ -2144,7 +1955,7 @@ private string GetDriveRootRelativePathFromProviderPath( CmdletProviderContext context ) { - string childPath = ""; + string childPath = string.Empty; CmdletProvider providerInstance = _sessionState.Internal.GetContainerProviderInstance(drive.Provider); @@ -2156,13 +1967,12 @@ 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)) && + while ((!string.IsNullOrEmpty(providerPath)) && (!providerPath.Equals(driveRoot, StringComparison.OrdinalIgnoreCase))) { - if (!String.IsNullOrEmpty(childPath)) + if (!string.IsNullOrEmpty(childPath)) { childPath = _sessionState.Internal.MakePath( providerInstance, @@ -2187,55 +1997,44 @@ CmdletProviderContext context /// /// Builds a provider specific path from the current working - /// directory using the specified relative path + /// directory using the specified relative path. /// - /// /// /// The drive to generate the provider specific path from. /// - /// /// /// The relative path to add to the absolute path in the drive. /// - /// /// /// Escape the wildcards in the current location. Use when this path will be /// passed through globbing. /// - /// /// /// An instance of the provider to use if MakePath or GetParentPath /// need to be called. /// - /// /// /// The context which the core command is running. /// - /// /// /// A string with the joined current working path and relative /// path. /// - /// /// /// If or is null. /// - /// /// /// If the provider specified by threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If the provider is not a NavigationCmdletProvider. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// internal string GenerateRelativePath( PSDriveInfo drive, string path, @@ -2247,12 +2046,12 @@ internal string GenerateRelativePath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } // This string will be filled in with the @@ -2260,13 +2059,16 @@ internal string GenerateRelativePath( // the supplied path string driveRootRelativeWorkingPath = drive.CurrentLocation; - if ((!String.IsNullOrEmpty(driveRootRelativeWorkingPath) && + if ((!string.IsNullOrEmpty(driveRootRelativeWorkingPath) && (driveRootRelativeWorkingPath.StartsWith(drive.Root, StringComparison.Ordinal)))) { 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 @@ -2285,7 +2087,7 @@ internal string GenerateRelativePath( // We don't want to process other relative path // symbols in this case - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { // Just fall-through } @@ -2298,7 +2100,7 @@ internal string GenerateRelativePath( // is that for file paths a path that is already relative to the drive // root is the same thing as an absolute path (both start with /). - driveRootRelativeWorkingPath = String.Empty; + driveRootRelativeWorkingPath = string.Empty; // Remove the \ or / from the drive relative // path @@ -2335,7 +2137,7 @@ internal string GenerateRelativePath( (pathLengthEqualsParentDirSymbol || pathDirSymbolFollowedBySeparator)) { - if (!String.IsNullOrEmpty(driveRootRelativeWorkingPath)) + if (!string.IsNullOrEmpty(driveRootRelativeWorkingPath)) { // Use the provider to get the current path @@ -2395,13 +2197,12 @@ internal string GenerateRelativePath( continue; } - // Process the current directory symbol "." if (path.Equals(currentDirSymbol, StringComparison.OrdinalIgnoreCase)) { processedSomething = true; - path = String.Empty; + path = string.Empty; break; } @@ -2434,13 +2235,13 @@ internal string GenerateRelativePath( // the loop. break; } - } // while - } // if (path.StartsWith(@"\", StringComparison.CurrentCulture)) + } + } // If more relative path remains add that to // the known absolute path - if (!String.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path)) { driveRootRelativeWorkingPath = _sessionState.Internal.MakePath( @@ -2456,7 +2257,7 @@ internal string GenerateRelativePath( string rootedPath = _sessionState.Internal.MakePath(context.Drive.Root, driveRootRelativeWorkingPath, context); string normalizedRelativePath = navigationProvider.ContractRelativePath(rootedPath, context.Drive.Root, false, context); - if (!String.IsNullOrEmpty(normalizedRelativePath)) + if (!string.IsNullOrEmpty(normalizedRelativePath)) { if (normalizedRelativePath.StartsWith(context.Drive.Root, StringComparison.Ordinal)) driveRootRelativeWorkingPath = normalizedRelativePath.Substring(context.Drive.Root.Length); @@ -2464,7 +2265,7 @@ internal string GenerateRelativePath( driveRootRelativeWorkingPath = normalizedRelativePath; } else - driveRootRelativeWorkingPath = ""; + driveRootRelativeWorkingPath = string.Empty; } s_tracer.WriteLine( @@ -2472,9 +2273,9 @@ internal string GenerateRelativePath( driveRootRelativeWorkingPath); return driveRootRelativeWorkingPath; - } // GenerateRelativePath + } - private bool HasRelativePathTokens(string path) + private static bool HasRelativePathTokens(string path) { string comparePath = path.Replace('/', '\\'); @@ -2487,40 +2288,33 @@ private bool HasRelativePathTokens(string path) comparePath.EndsWith("\\.", StringComparison.OrdinalIgnoreCase) || comparePath.StartsWith("..\\", StringComparison.OrdinalIgnoreCase) || comparePath.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) || - comparePath.StartsWith("~", StringComparison.OrdinalIgnoreCase)); + comparePath.StartsWith('~')); } /// /// Uses the drive and a relative working path to construct - /// a string which has a fully qualified provider specific path + /// a string which has a fully qualified provider specific path. /// - /// /// /// The drive to use as the root of the path. /// - /// /// /// The relative working directory to the specified drive. /// - /// /// /// The context which the core command is running. /// - /// /// /// A string which is contains the fully qualified path in provider /// specific form. /// - /// /// /// If or is null. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// private string GetProviderSpecificPath( PSDriveInfo drive, string workingPath, @@ -2528,12 +2322,12 @@ private string GetProviderSpecificPath( { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } if (workingPath == null) { - throw PSTraceSource.NewArgumentNullException("workingPath"); + throw PSTraceSource.NewArgumentNullException(nameof(workingPath)); } // Trace the inputs @@ -2561,40 +2355,33 @@ private string GetProviderSpecificPath( } return result; - } // GetProviderSpecificPath - + } /// /// Parses the provider-qualified path into the provider name and /// the provider-internal path. /// - /// /// /// The provider-qualified path to parse. /// - /// /// /// The name of the provider specified by the path is returned through /// this out parameter. /// - /// /// /// The provider-internal path. /// - /// /// /// If is null. /// - /// /// /// If is not in the correct format. /// - /// private static string ParseProviderPath(string path, out string providerId) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } int providerIdSeparatorIndex = path.IndexOf(StringLiterals.ProviderPathSeparator, StringComparison.Ordinal); @@ -2603,7 +2390,7 @@ private static string ParseProviderPath(string path, out string providerId) { ArgumentException e = PSTraceSource.NewArgumentException( - "path", + nameof(path), SessionStateStrings.NotProviderQualifiedPath); throw e; } @@ -2612,8 +2399,7 @@ private static string ParseProviderPath(string path, out string providerId) string result = path.Substring(providerIdSeparatorIndex + StringLiterals.ProviderPathSeparator.Length); return result; - } // ParseProviderPath - + } #endregion Path manipulation methods @@ -2626,58 +2412,46 @@ private static string ParseProviderPath(string path, out string providerId) /// will perform the globbing using the specified provider and return the /// matching provider specific paths. /// - /// /// /// The path containing the glob characters to resolve. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// 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. /// - /// /// /// An array of provider specific paths that match the glob path and /// filter (if supplied via the context). /// - /// /// /// This method is internal because we don't want to expose the /// provider instances outside the engine. /// - /// /// /// If , , or /// is null. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection GetGlobbedProviderPathsFromProviderPath( string path, bool allowNonexistingPaths, @@ -2686,17 +2460,17 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (containerProvider == null) { - throw PSTraceSource.NewArgumentNullException("containerProvider"); + throw PSTraceSource.NewArgumentNullException(nameof(containerProvider)); } if (context == null) { - throw PSTraceSource.NewArgumentNullException("context"); + throw PSTraceSource.NewArgumentNullException(nameof(context)); } Collection expandedPaths = @@ -2707,53 +2481,45 @@ internal Collection GetGlobbedProviderPathsFromProviderPath( context); return expandedPaths; - } // GetGlobbedProviderPathsFromProviderPath + } /// /// Determines if the specified path contains any globing characters. These /// characters are defined as '?' and '*'. /// - /// /// /// The path to search for globing characters. /// - /// /// /// True if the path contains any of the globing characters, false otherwise. /// - /// /// /// If is null. /// - /// internal static bool StringContainsGlobCharacters(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } return WildcardPattern.ContainsWildcardCharacters(path); - } // StringContainsGlobCharacters + } /// /// Determines if the path and context are such that we need to run through /// the globbing algorithm. /// - /// /// /// The path to check for glob characters. /// - /// /// /// The context to check for filter, include, or exclude expressions. /// - /// /// /// True if globbing should be performed (the path has glob characters, or the context /// has either a an include, or an exclude expression). False otherwise. /// - /// internal static bool ShouldPerformGlobbing(string path, CmdletProviderContext context) { bool pathContainsGlobCharacters = false; @@ -2783,38 +2549,31 @@ internal static bool ShouldPerformGlobbing(string path, CmdletProviderContext co s_pathResolutionTracer.WriteLine("Path contains wildcard characters: {0}", pathContainsGlobCharacters); return (pathContainsGlobCharacters || contextContainsIncludeExclude) && (!contextContainsNoGlob); - } // ShouldPerformGlobbing + } /// /// Generates an array of provider specific paths from the single provider specific /// path using globing rules. /// - /// /// /// A path that may or may not contain globing characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The drive that the path is relative to. /// - /// /// /// The provider that implements the namespace for the path that we are globing over. /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// An array of path strings that match the globing rules applied to the path parameter. /// - /// /// /// First the path is checked to see if it contains any globing characters ('?' or '*'). /// If it doesn't then the path is returned as the only element in the array. @@ -2836,49 +2595,39 @@ internal static bool ShouldPerformGlobbing(string path, CmdletProviderContext co /// Calling this method for the path above would return all files that end in 'a' and /// any other two characters followed by ".cs" in all the subdirectories of /// foo that have a bar subdirectory. - /// /// - /// /// /// If , , or /// is null. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If does not contain glob characters and /// could not be found. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// private Collection ExpandMshGlobPath( string path, bool allowNonexistingPaths, @@ -2888,18 +2637,19 @@ private Collection ExpandMshGlobPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } + s_tracer.WriteLine("path = {0}", path); NavigationCmdletProvider navigationProvider = provider as NavigationCmdletProvider; @@ -2920,7 +2670,7 @@ private Collection ExpandMshGlobPath( // Each leaf element that is pulled off the path is pushed on the stack in // order such that we can generate the path again. - Stack leafElements = new Stack(); + Stack leafElements = new Stack(); using (s_pathResolutionTracer.TraceScope("Tokenizing path")) { @@ -2945,7 +2695,7 @@ private Collection ExpandMshGlobPath( leafElement = navigationProvider.GetChildName(path, context); } - if (String.IsNullOrEmpty(leafElement)) + if (string.IsNullOrEmpty(leafElement)) { break; } @@ -2966,7 +2716,7 @@ private Collection ExpandMshGlobPath( string newParentPath = navigationProvider.GetParentPath(path, drive.Root, context); - if (String.Equals( + if (string.Equals( newParentPath, path, StringComparison.OrdinalIgnoreCase)) @@ -2984,6 +2734,7 @@ private Collection ExpandMshGlobPath( path); throw invalidOperation; } + path = newParentPath; } else @@ -2992,7 +2743,7 @@ private Collection ExpandMshGlobPath( // it can have only one segment in its path. So after removing // the leaf all we have left is the empty string. - path = String.Empty; + path = string.Empty; } s_tracer.WriteLine("New path: {0}", path); @@ -3014,14 +2765,14 @@ private Collection ExpandMshGlobPath( { leafElement = navigationProvider.GetChildName(path, context); - if (!String.IsNullOrEmpty(leafElement)) + if (!string.IsNullOrEmpty(leafElement)) { path = navigationProvider.GetParentPath(path, null, context); } } else { - path = String.Empty; + path = string.Empty; } leafElements.Push(leafElement); @@ -3121,7 +2872,7 @@ private Collection ExpandMshGlobPath( } } } - } // while (leafElements.Count > 0) + } Dbg.Diagnostics.Assert( dirs != null, @@ -3159,7 +2910,7 @@ private Collection ExpandMshGlobPath( } else { - if (path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal)) + if (path.StartsWith(StringLiterals.DefaultPathSeparator)) { formatString = "{0}:{1}"; } @@ -3172,7 +2923,7 @@ private Collection ExpandMshGlobPath( } string resolvedPath = - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, formatString, drive.Name, @@ -3205,34 +2956,28 @@ private Collection ExpandMshGlobPath( "This method should at least return the path or more if it has glob characters"); return result; - } // ExpandMshGlobPath + } /// /// Gets either a drive-qualified or provider-qualified path based on the drive /// information. /// - /// /// /// The path to create a qualified path from. /// - /// /// /// The drive used to qualify the path. /// - /// /// /// Either a drive-qualified or provider-qualified Msh path. /// - /// /// /// The drive's Hidden property is used to determine if the path returned /// should be provider (hidden=true) or drive (hidden=false) qualified. /// - /// /// /// If or is null. /// - /// internal static string GetMshQualifiedPath(string path, PSDriveInfo drive) { Dbg.Diagnostics.Assert( @@ -3258,25 +3003,21 @@ internal static string GetMshQualifiedPath(string path, PSDriveInfo drive) } return result; - } // GetMshQualifiedPath + } /// /// Removes the provider or drive qualifier from a Msh path. /// - /// /// /// The path to remove the qualifier from. /// - /// /// /// The drive information used to determine if a provider qualifier /// or drive qualifier should be removed from the path. /// - /// /// /// The path with the Msh qualifier removed. /// - /// internal static string RemoveMshQualifier(string path, PSDriveInfo drive) { Dbg.Diagnostics.Assert( @@ -3299,40 +3040,34 @@ internal static string RemoveMshQualifier(string path, PSDriveInfo drive) } return result; - } // RemoveMshQualifier + } /// /// Given an Msh relative or absolute path, returns a drive-qualified absolute path. /// No globbing or relative path character expansion is done. /// - /// /// /// The path to get the drive qualified path from. /// - /// /// /// The drive the path should be qualified with. /// - /// /// /// A drive-qualified absolute Msh path. /// - /// /// /// If or is null. /// - /// internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } - if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } string result = path; @@ -3353,8 +3088,7 @@ internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) } else { - string possibleDriveName = path.Substring(0, index); - if (String.Equals(possibleDriveName, drive.Name, StringComparison.OrdinalIgnoreCase)) + if (path.AsSpan(0, index).Equals(drive.Name, StringComparison.OrdinalIgnoreCase)) { treatAsRelative = false; } @@ -3365,8 +3099,8 @@ internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) // Check if the path begins with "\" or "/" (UNC Path or Path in Unix). // Ignore if the path resolves to a drive path, this will happen when path is equal to "\" or "/". // Drive path still need formatting, so treat them as relative. - if (path.Length > 1 && (path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) || - path.StartsWith(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal))) + if (path.Length > 1 && (path.StartsWith(StringLiterals.DefaultPathSeparator) || + path.StartsWith(StringLiterals.AlternatePathSeparator))) { treatAsRelative = false; } @@ -3386,7 +3120,7 @@ internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) if (drive.VolumeSeparatedByColon) { formatString = "{0}:" + StringLiterals.DefaultPathSeparator + "{1}"; - if (path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal)) + if (path.StartsWith(StringLiterals.DefaultPathSeparator)) { formatString = "{0}:{1}"; } @@ -3397,7 +3131,7 @@ internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) } result = - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, formatString, drive.Name, @@ -3405,20 +3139,17 @@ internal static string GetDriveQualifiedPath(string path, PSDriveInfo drive) } return result; - } // GetDriveQualifiedPath + } /// - /// Removes the drive qualifier from a drive qualified MSH path + /// Removes the drive qualifier from a drive qualified MSH path. /// - /// /// /// The path to remove the drive qualifier from. /// - /// /// /// The path without the drive qualifier. /// - /// private static string RemoveDriveQualifier(string path) { Dbg.Diagnostics.Assert( @@ -3429,7 +3160,7 @@ private static string RemoveDriveQualifier(string path) // Find the drive separator only if it's before a path separator - int index = path.IndexOf(":", StringComparison.Ordinal); + int index = path.IndexOf(':'); if (index != -1) { int separator = path.IndexOf(StringLiterals.DefaultPathSeparator, 0, index); @@ -3437,6 +3168,7 @@ private static string RemoveDriveQualifier(string path) { separator = path.IndexOf(StringLiterals.AlternatePathSeparator, 0, index); } + if (separator == -1 || index < separator) { // Remove the \ or / if it follows the drive indicator @@ -3451,40 +3183,34 @@ private static string RemoveDriveQualifier(string path) } return result; - } // RemoveDriveQualifier + } /// /// Given an Msh path, returns a provider-qualified path. /// No globbing or relative path character expansion is done. /// - /// /// /// The path to get the drive qualified path from. /// - /// /// /// The provider the path should be qualified with. /// - /// /// /// A drive-qualified absolute Msh path. /// - /// /// /// If or is null. /// - /// internal static string GetProviderQualifiedPath(string path, ProviderInfo provider) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } - if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } string result = path; @@ -3506,7 +3232,7 @@ internal static string GetProviderQualifiedPath(string path, ProviderInfo provid if (!pathResolved) { result = - String.Format( + string.Format( System.Globalization.CultureInfo.InvariantCulture, "{0}{1}{2}", provider.FullName, @@ -3515,20 +3241,17 @@ internal static string GetProviderQualifiedPath(string path, ProviderInfo provid } return result; - } // GetProviderQualifiedPath + } /// - /// Removes the provider qualifier from a provider-qualified MSH path + /// Removes the provider qualifier from a provider-qualified MSH path. /// - /// /// /// The path to remove the provider qualifier from. /// - /// /// /// The path without the provider qualifier. /// - /// internal static string RemoveProviderQualifier(string path) { Dbg.Diagnostics.Assert( @@ -3548,85 +3271,70 @@ internal static string RemoveProviderQualifier(string path) } return result; - } // RemoveProviderQualifier + } /// /// 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. /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// A collection of fully qualified namespace paths whose leaf element matches the - /// expression. + /// expression. /// - /// /// - /// If or + /// If or /// is null. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// private List GenerateNewPSPathsWithGlobLeaf( List currentDirs, PSDriveInfo drive, @@ -3637,12 +3345,12 @@ private List GenerateNewPSPathsWithGlobLeaf( { if (currentDirs == null) { - throw PSTraceSource.NewArgumentNullException("currentDirs"); + throw PSTraceSource.NewArgumentNullException(nameof(currentDirs)); } if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } NavigationCmdletProvider navigationProvider = provider as NavigationCmdletProvider; @@ -3671,7 +3379,6 @@ private List GenerateNewPSPathsWithGlobLeaf( context.Include, WildcardOptions.IgnoreCase); - // Construct the exclude filter Collection excludeMatcher = @@ -3693,7 +3400,7 @@ private List GenerateNewPSPathsWithGlobLeaf( // Now continue on with the names that were returned - string mshQualifiedParentPath = String.Empty; + string mshQualifiedParentPath = string.Empty; Collection childNamesObjectArray = GetChildNamesInDir( dir, @@ -3712,7 +3419,6 @@ private List GenerateNewPSPathsWithGlobLeaf( continue; } - // Loop through each child to see if they match the glob expression foreach (PSObject childObject in childNamesObjectArray) @@ -3723,7 +3429,7 @@ private List GenerateNewPSPathsWithGlobLeaf( throw new PipelineStoppedException(); } - string child = String.Empty; + string child = string.Empty; if (IsChildNameAMatch( childObject, @@ -3738,12 +3444,7 @@ private List GenerateNewPSPathsWithGlobLeaf( { string parentPath = RemoveMshQualifier(mshQualifiedParentPath, drive); - childPath = - _sessionState.Internal. - MakePath( - parentPath, - child, - context); + childPath = _sessionState.Internal.MakePath(parentPath, child, context); childPath = GetMshQualifiedPath(childPath, drive); } @@ -3757,10 +3458,10 @@ private List GenerateNewPSPathsWithGlobLeaf( childPath = isLastLeaf ? childPath : WildcardPattern.Escape(childPath); newDirs.Add(childPath); } - } // foreach (child in childNames) + } } - } // foreach (dir in currentDirs) - } // if (StringContainsGlobCharacters(leafElement)) + } + } else { s_tracer.WriteLine( @@ -3790,17 +3491,10 @@ private List GenerateNewPSPathsWithGlobLeaf( { string parentPath = RemoveMshQualifier(resolvedPath, drive); - childPath = - _sessionState.Internal. - MakePath( - parentPath, - backslashEscapedLeafElement, - context); - + childPath = _sessionState.Internal.MakePath(parentPath, backslashEscapedLeafElement, context); childPath = GetMshQualifiedPath(childPath, drive); } - if (_sessionState.Internal.ItemExists(childPath, context)) { s_tracer.WriteLine("Adding child path to dirs {0}", childPath); @@ -3809,39 +3503,32 @@ private List GenerateNewPSPathsWithGlobLeaf( newDirs.Add(childPath); } } - } // foreach (dir in currentDirs) - } // if (StringContainsGlobCharacters(leafElement)) - + } + } return newDirs; - } // GenerateNewPSPathsWithGlobLeaf + } /// /// Generates an array of provider specific paths from the single provider specific /// path using globing rules. /// - /// /// /// A path that may or may not contain globing characters. /// - /// /// /// If true, a ItemNotFoundException will not be thrown for non-existing /// paths. Instead an appropriate path will be returned as if it did exist. /// - /// /// /// The provider that implements the namespace for the path that we are globing over. /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// An array of path strings that match the globing rules applied to the path parameter. /// - /// /// /// First the path is checked to see if it contains any globing characters ('?' or '*'). /// If it doesn't then the path is returned as the only element in the array. @@ -3863,35 +3550,27 @@ private List GenerateNewPSPathsWithGlobLeaf( /// Calling this method for the path above would return all files that end in 'a' and /// any other two characters followed by ".cs" in all the subdirectories of /// foo that have a bar subdirectory. - /// /// - /// /// /// If or is null. /// - /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// or if the provider is implemented in such a way as to cause the globber to go /// into an infinite loop. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal Collection ExpandGlobPath( string path, bool allowNonexistingPaths, @@ -3900,12 +3579,12 @@ internal Collection ExpandGlobPath( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } // See if the provider wants to convert the path and filter @@ -3949,7 +3628,7 @@ internal Collection ExpandGlobPath( // Each leaf element that is pulled off the path is pushed on the stack in // order such that we can generate the path again. - Stack leafElements = new Stack(); + Stack leafElements = new Stack(); using (s_pathResolutionTracer.TraceScope("Tokenizing path")) { @@ -3974,7 +3653,7 @@ internal Collection ExpandGlobPath( leafElement = navigationProvider.GetChildName(path, context); } - if (String.IsNullOrEmpty(leafElement)) + if (string.IsNullOrEmpty(leafElement)) { break; } @@ -3993,7 +3672,7 @@ internal Collection ExpandGlobPath( { // See if we can get the root from the context - string root = String.Empty; + string root = string.Empty; if (context != null) { @@ -4009,7 +3688,7 @@ internal Collection ExpandGlobPath( string newParentPath = navigationProvider.GetParentPath(path, root, context); - if (String.Equals( + if (string.Equals( newParentPath, path, StringComparison.OrdinalIgnoreCase)) @@ -4027,6 +3706,7 @@ internal Collection ExpandGlobPath( path); throw invalidOperation; } + path = newParentPath; } else @@ -4035,7 +3715,7 @@ internal Collection ExpandGlobPath( // it can have only one segment in its path. So after removing // the leaf all we have left is the empty string. - path = String.Empty; + path = string.Empty; } s_tracer.WriteLine("New path: {0}", path); @@ -4056,15 +3736,16 @@ internal Collection ExpandGlobPath( { leafElement = navigationProvider.GetChildName(path, context); - if (!String.IsNullOrEmpty(leafElement)) + if (!string.IsNullOrEmpty(leafElement)) { path = navigationProvider.GetParentPath(path, null, context); } } else { - path = String.Empty; + path = string.Empty; } + leafElements.Push(leafElement); s_pathResolutionTracer.WriteLine("Leaf element: {0}", leafElement); } @@ -4156,7 +3837,7 @@ internal Collection ExpandGlobPath( } } } - } // while (leafElements.Count > 0) + } Dbg.Diagnostics.Assert( dirs != null, @@ -4198,6 +3879,7 @@ internal Collection ExpandGlobPath( } } } + Dbg.Diagnostics.Assert( result != null, "This method should at least return the path or more if it has glob characters"); @@ -4208,71 +3890,58 @@ internal Collection ExpandGlobPath( } return result; - } // 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. /// - /// /// /// The context the provider uses when performing the operation. /// - /// /// /// A collection of fully qualified namespace paths whose leaf element matches the - /// expression. + /// expression. /// - /// /// - /// If or + /// If or /// is null. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// internal List GenerateNewPathsWithGlobLeaf( List currentDirs, string leafElement, @@ -4282,12 +3951,12 @@ internal List GenerateNewPathsWithGlobLeaf( { if (currentDirs == null) { - throw PSTraceSource.NewArgumentNullException("currentDirs"); + throw PSTraceSource.NewArgumentNullException(nameof(currentDirs)); } if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } NavigationCmdletProvider navigationProvider = provider as NavigationCmdletProvider; @@ -4316,7 +3985,6 @@ internal List GenerateNewPathsWithGlobLeaf( context.Include, WildcardOptions.IgnoreCase); - // Construct the exclude filter Collection excludeMatcher = @@ -4359,29 +4027,24 @@ internal List GenerateNewPathsWithGlobLeaf( throw new PipelineStoppedException(); } - string child = String.Empty; + string child = string.Empty; if (IsChildNameAMatch(childObject, stringMatcher, includeMatcher, excludeMatcher, out child)) { string childPath = child; if (navigationProvider != null) { - childPath = - navigationProvider. - MakePath( - unescapedDir, - child, - context); + childPath = navigationProvider.MakePath(unescapedDir, child, context); } s_tracer.WriteLine("Adding child path to dirs {0}", childPath); newDirs.Add(childPath); } - } // foreach (child in childNames) + } } - } // foreach (dir in currentDirs) - } // if (StringContainsGlobCharacters(leafElement)) + } + } else { s_tracer.WriteLine( @@ -4416,7 +4079,6 @@ internal List GenerateNewPathsWithGlobLeaf( context); } - if (provider.ItemExists(childPath, context)) { s_tracer.WriteLine("Adding child path to dirs {0}", childPath); @@ -4426,102 +4088,82 @@ internal List GenerateNewPathsWithGlobLeaf( s_pathResolutionTracer.WriteLine("Valid intermediate container: {0}", childPath); } } - } // foreach (dir in currentDirs) - } // if (StringContainsGlobCharacters(leafElement)) - + } + } return newDirs; - } // GenerateNewPathsWithGlobLeaf + } /// - /// Gets the child names in the specified path by using the provider + /// Gets the child names in the specified path by using the provider. /// - /// /// /// The path of the directory to get the child names from. If this is an Msh Path, /// dirIsProviderPath must be false, If this is a provider-internal path, /// dirIsProviderPath must be true. /// - /// /// /// The element that we are ultimately looking for. Used to set filters on the context /// if desired. /// - /// /// /// Determines if the GetChildNames call should get all containers even if they don't /// match the filter. /// - /// /// /// The context to be used for the command. The context is copied to a new context, the /// results are accumulated and then returned. /// - /// /// /// Specifies whether the dir parameter is a provider-internal path (true) or Msh Path (false). /// - /// /// /// The drive to use to qualify the Msh path if dirIsProviderPath is false. /// - /// /// /// The provider to use to get the child names. /// - /// /// /// Returns the modified dir path. If dirIsProviderPath is true, this is the unescaped dir path. /// If dirIsProviderPath is false, this is the unescaped resolved provider path. /// - /// /// /// A collection of PSObjects whose BaseObject is a string that contains the name of the child. /// - /// /// /// If or is null. /// - /// /// /// If the path is a provider-qualified path for a provider that is /// not loaded into the system. /// - /// /// /// If the refers to a drive that could not be found. /// - /// /// /// If the provider used to build the path threw an exception. /// - /// /// /// If the provider that the represents is not a NavigationCmdletProvider /// or ContainerCmdletProvider. /// - /// /// /// If the starts with "~" and the home location is not set for /// the provider. /// - /// /// /// If the provider associated with the threw an /// exception when its GetParentPath or MakePath was called while /// processing the . /// - /// /// /// If has been signaled for /// StopProcessing. /// - /// /// /// Any exception can be thrown by the provider that is called to build /// the provider path. /// - /// private Collection GetChildNamesInDir( string dir, string leafElement, @@ -4662,38 +4304,31 @@ private Collection GetChildNamesInDir( { getChildNamesContext.RemoveStopReferral(); } - } // GetChildNamesInDir + } /// /// Determines if the specified PSObject contains a string that matches the specified /// wildcard patterns. /// - /// /// /// The PSObject that contains the child names. /// - /// /// /// The glob matcher. /// - /// /// /// The include matcher wildcard patterns. /// - /// /// /// The exclude matcher wildcard patterns. /// - /// /// /// The name of the child which was extracted from the childObject and used for the matches. /// - /// /// /// True if the string in the childObject matches the stringMatcher and includeMatcher wildcard patterns, /// and does not match the exclude wildcard patterns. False otherwise. /// - /// private static bool IsChildNameAMatch( PSObject childObject, WildcardPattern stringMatcher, @@ -4767,59 +4402,53 @@ private static bool IsChildNameAMatch( } } while (false); - s_tracer.WriteLine("result = {0}; childName = {1}", result, childName); + s_tracer.WriteLine("result = {0}; childName = {1}", result.ToString(), childName); return result; - } //IsChildNameAMatch + } /// /// Converts a back tick '`' escape into back slash escape for /// all occurrences in the string. /// - /// /// /// A string that may or may not have back ticks as escape characters. /// - /// /// /// A string that has the back ticks replaced with back slashes except /// in the case where there are two back ticks in a row. In that case a single /// back tick is returned. /// - /// /// /// The following rules apply to the conversion: /// 1. All \ characters are expanded to be \\ /// 2. Any ` not followed by a ` is converted to a \ /// 3. Any ` that is followed by a ` collapses the two into a single ` /// 4. Any other character is immediately appended to the result. - /// /// - /// /// /// If is null. /// - /// private static string ConvertMshEscapeToRegexEscape(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } const char mshEscapeChar = '`'; const char regexEscapeChar = '\\'; - char[] workerArray = path.ToCharArray(); + ReadOnlySpan workerArray = path; StringBuilder result = new StringBuilder(); - for (int index = 0; index < workerArray.GetLength(0); ++index) + for (int index = 0; index < workerArray.Length; ++index) { // look for an escape character if (workerArray[index] == mshEscapeChar) { - if (index + 1 < workerArray.GetLength(0)) + if (index + 1 < workerArray.Length) { if (workerArray[index + 1] == mshEscapeChar) { @@ -4865,7 +4494,7 @@ private static string ConvertMshEscapeToRegexEscape(string path) result.Append(workerArray[index]); } - } // for () + } s_tracer.WriteLine( "Original path: {0} Converted to: {1}", @@ -4873,31 +4502,27 @@ private static string ConvertMshEscapeToRegexEscape(string path) result.ToString()); return result.ToString(); - } // ConvertMshEscapeToRegexEscape + } /// /// Determines if the path is relative to a provider home based on /// the ~ character. /// - /// /// /// The path to determine if it is a home path. /// - /// /// /// True if the path contains a ~ at the beginning of the path or immediately /// following a provider designator ("provider::") /// - /// /// /// Is is null. /// - /// internal static bool IsHomePath(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } bool result = false; @@ -4914,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) @@ -4927,78 +4552,66 @@ internal static bool IsHomePath(string path) } return result; - } // IsHomePath + } /// /// Determines if the specified path looks like a remote path. (starts with /// // or \\. /// - /// /// /// The path to check to determine if it is a remote path. /// - /// /// /// True if the path starts with // or \\, or false otherwise. /// - /// /// /// If is null. /// - /// internal static bool IsProviderDirectPath(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } return path.StartsWith(StringLiterals.DefaultRemotePathPrefix, StringComparison.Ordinal) || path.StartsWith(StringLiterals.AlternateRemotePathPrefix, StringComparison.Ordinal); - } // IsRemotePath + } /// /// Generates the path for the home location for a provider when given a /// path starting with ~ or "provider:~" followed by a relative path. /// - /// /// /// The path to generate into a home path. /// - /// /// /// The path representing the path to the home location for a provider. This /// may be either a fully qualified provider path or a PowerShell path. /// - /// /// /// If is null. /// - /// /// /// If is a provider-qualified path /// and the specified provider does not exist. /// - /// /// /// If the provider throws an exception when its MakePath gets /// called. /// - /// /// /// If the provider does not support multiple items. /// - /// /// /// If the home location for the provider is not set and /// starts with a "~". /// - /// internal string GetHomeRelativePath(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } string result = path; @@ -5023,9 +4636,9 @@ internal string GetHomeRelativePath(string path) provider = _sessionState.Internal.GetSingleProvider(providerName); path = path.Substring(index + StringLiterals.ProviderPathSeparator.Length); } - } // IsProviderPath + } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Strip of the ~ and the \ or / if present @@ -5052,7 +4665,7 @@ internal string GetHomeRelativePath(string path) s_pathResolutionTracer.WriteLine("Getting home path for provider: {0}", provider.Name); s_pathResolutionTracer.WriteLine("Provider HOME path: {0}", provider.Home); - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { path = provider.Home; } @@ -5074,18 +4687,19 @@ internal string GetHomeRelativePath(string path) throw e; } } + result = path; - } // IsHomePath + } return result; - } // GetHomeRelativePath + } private static void TraceFilters(CmdletProviderContext context) { if ((s_pathResolutionTracer.Options & PSTraceSourceOptions.WriteLine) != 0) { // Trace the filter - s_pathResolutionTracer.WriteLine("Filter: {0}", context.Filter ?? String.Empty); + s_pathResolutionTracer.WriteLine("Filter: {0}", context.Filter ?? string.Empty); if (context.Include != null) { @@ -5093,8 +4707,9 @@ 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()); } @@ -5104,13 +4719,14 @@ 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()); } } } #endregion internal methods - } // class LocationGlobber -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/namespaces/NavigationProviderBase.cs b/src/System.Management.Automation/namespaces/NavigationProviderBase.cs index 46eca2d3d00..cf656e633a3 100644 --- a/src/System.Management.Automation/namespaces/NavigationProviderBase.cs +++ b/src/System.Management.Automation/namespaces/NavigationProviderBase.cs @@ -1,10 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Management.Automation.Internal; -using System.Text; namespace System.Management.Automation.Provider { @@ -13,7 +11,6 @@ namespace System.Management.Automation.Provider /// /// The base class for a Cmdlet provider that expose a hierarchy of items and containers. /// - /// /// /// The NavigationCmdletProvider class is a base class that provider can derive from /// to implement a set of methods that allow @@ -30,24 +27,19 @@ public abstract class NavigationCmdletProvider : ContainerCmdletProvider /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The parent segment of a path to be joined with the child. /// - /// /// /// The child segment of a path to be joined with the parent. /// - /// /// /// The context under which this method is being called. /// - /// /// /// A string that represents the parent and child segments of the path /// joined by a path separator. /// - /// /// /// This method should use lexical joining of two path segments with a path /// separator character. It should not validate the path as a legal fully @@ -63,7 +55,6 @@ public abstract class NavigationCmdletProvider : ContainerCmdletProvider /// in the provider namespace. These characters are most likely being used /// for globbing and should not be removed by the implementation of this method. /// - /// internal string MakePath( string parent, string child, @@ -74,40 +65,34 @@ internal string MakePath( // Call virtual method return MakePath(parent, child); - } // MakePath + } /// /// Internal wrapper for the GetParentPath protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// A fully qualified provider specific path to an item. The item may or /// may not exist. /// - /// /// /// The fully qualified path to the root of a drive. This parameter may be null /// or empty if a mounted drive is not in use for this operation. If this parameter /// is not null or empty the result of the method should not be a path to a container /// that is a parent or in a different tree than the root. /// - /// /// /// The context under which this method is being called. /// - /// /// /// The path of the parent of the path parameter. /// - /// /// /// This should be a lexical splitting of the path on the path separator character /// for the provider namespace. For example, the file system provider should look /// for the last "\" and return everything to the left of the "\". /// - /// internal string GetParentPath( string path, string root, @@ -118,39 +103,33 @@ internal string GetParentPath( // Call virtual method return GetParentPath(path, root); - } // GetParentPath + } /// /// Internal wrapper for the NormalizeRelativePath method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// A fully qualified provider specific path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// A normalized path that is relative to the basePath that was passed. The /// provider should parse the path parameter, normalize the path, and then /// return the normalized path relative to the basePath. /// - /// /// /// This method does not have to be purely syntactical parsing of the path. It /// is encouraged that the provider actually use the path to lookup in its store /// and create a relative path that matches the casing, and standardized path syntax. /// - /// internal string NormalizeRelativePath( string path, string basePath, @@ -161,33 +140,28 @@ internal string NormalizeRelativePath( // Call virtual method return NormalizeRelativePath(path, basePath); - } // NormalizeRelativePath + } /// /// Internal wrapper for the GetChildName protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The fully qualified path to the item /// - /// /// /// The leaf element in the path. /// - /// /// /// The context under which this method is being called. /// - /// /// /// This should be implemented as a split on the path separator. The characters /// in the fullPath may not be legal characters in the namespace but may be /// used in globing or regular expression matching. The provider should not error /// unless there are no path separators in the fully qualified path. /// - /// internal string GetChildName( string path, CmdletProviderContext context) @@ -197,26 +171,22 @@ internal string GetChildName( // Call virtual method return GetChildName(path); - } // GetChildName + } /// /// Internal wrapper for the IsItemContainer protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to determine if it is a container. /// - /// /// /// The context under which this method is being called. /// - /// /// /// true if the item specified by path is a container, false otherwise. /// - /// internal bool IsItemContainer( string path, CmdletProviderContext context) @@ -226,30 +196,25 @@ internal bool IsItemContainer( // Call virtual method return IsItemContainer(path); - } // IsItemContainer + } /// /// Internal wrapper for the MoveItem protected method. It is called instead /// of the protected method that is overridden by derived classes so that the /// context of the command can be set. /// - /// /// /// The path to the item to be moved. /// - /// /// /// The path of the destination container. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Nothing. All objects that are moved should be written to the WriteObject method. /// - /// internal void MoveItem( string path, string destination, @@ -260,31 +225,26 @@ internal void MoveItem( // Call virtual method MoveItem(path, destination); - } // MoveItem + } /// /// Gives the provider to attach additional parameters to /// the move-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The path of the destination container. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object MoveItemDynamicParameters( string path, string destination, @@ -292,7 +252,7 @@ internal object MoveItemDynamicParameters( { Context = context; return MoveItemDynamicParameters(path, destination); - } // MoveItemDynamicParameters + } #endregion Internal methods @@ -301,20 +261,16 @@ internal object MoveItemDynamicParameters( /// /// Joins two strings with a path a provider specific path separator. /// - /// /// /// The parent segment of a path to be joined with the child. /// - /// /// /// The child segment of a path to be joined with the parent. /// - /// /// /// A string that represents the parent and child segments of the path /// joined by a path separator. /// - /// /// /// This method should use lexical joining of two path segments with a path /// separator character. It should not validate the path as a legal fully @@ -333,30 +289,25 @@ internal object MoveItemDynamicParameters( protected virtual string MakePath(string parent, string child) { return MakePath(parent, child, childIsLeaf: false); - } // MakePath + } /// /// Joins two strings with a path a provider specific path separator. /// - /// /// /// The parent segment of a path to be joined with the child. /// - /// /// /// The child segment of a path to be joined with the parent. /// - /// /// /// Indicate that the is the name of a child item that's guaranteed to exist /// - /// /// /// If the is True, then we don't normalize the child path, and would do /// some checks to decide whether to normalize the parent path. /// - /// - /// + /// New path string. protected string MakePath(string parent, string child, bool childIsLeaf) { using (PSTransactionManager.GetEngineProtectionScope()) @@ -366,34 +317,25 @@ protected string MakePath(string parent, string child, bool childIsLeaf) if (parent == null && child == null) { - // If both are null it is an error - - throw PSTraceSource.NewArgumentException("parent"); + throw PSTraceSource.NewArgumentException(nameof(parent)); } - else if (String.IsNullOrEmpty(parent) && - String.IsNullOrEmpty(child)) - { - // If both are empty, just return the empty string. - result = String.Empty; + if (string.IsNullOrEmpty(parent) && + string.IsNullOrEmpty(child)) + { + result = string.Empty; } - else if (String.IsNullOrEmpty(parent) && - !String.IsNullOrEmpty(child)) + else if (string.IsNullOrEmpty(parent) && + !string.IsNullOrEmpty(child)) { - // If the parent is empty but the child is not, return the - // child - - result = child.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + result = NormalizePath(child); } - else if (!String.IsNullOrEmpty(parent) && - String.IsNullOrEmpty(child)) + else if (!string.IsNullOrEmpty(parent) && + (string.IsNullOrEmpty(child) || + child.Equals(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) || + child.Equals(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal))) { - // If the child is empty but the parent is not, return the - // parent with the path separator appended. - - // Append the default path separator - - if (parent.EndsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal)) + if (parent.EndsWith(StringLiterals.DefaultPathSeparator)) { result = parent; } @@ -405,7 +347,6 @@ protected string MakePath(string parent, string child, bool childIsLeaf) else { // Both parts are not empty so join them - // 'childIsLeaf == true' indicates that 'child' is actually the name of a child item and // guaranteed to exist. In this case, we don't normalize the child path. if (childIsLeaf) @@ -416,50 +357,17 @@ protected string MakePath(string parent, string child, bool childIsLeaf) { // Normalize the path so that only the default path separator is used as a // separator even if the user types the alternate slash. - - parent = parent.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); - child = child.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + parent = NormalizePath(parent); + child = NormalizePath(child); } - // Joins the paths - - StringBuilder builder = new StringBuilder(parent, parent.Length + child.Length + 1); - - if (parent.EndsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal)) + ReadOnlySpan appendChild = child.AsSpan(); + if (child.StartsWith(StringLiterals.DefaultPathSeparator)) { - if (child.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal)) - { - builder.Append(child, 1, child.Length - 1); - } - else - { - builder.Append(child); - } - } - else - { - if (child.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.CurrentCulture)) - { - if (parent.Length == 0) - { - builder.Append(child, 1, child.Length - 1); - } - else - { - builder.Append(child); - } - } - else - { - if (parent.Length > 0 && child.Length > 0) - { - builder.Append(StringLiterals.DefaultPathSeparator); - } - builder.Append(child); - } + appendChild = appendChild.Slice(1); } - result = builder.ToString(); + result = IO.Path.Join(parent.AsSpan(), appendChild); } return result; @@ -470,23 +378,19 @@ protected string MakePath(string parent, string child, bool childIsLeaf) /// Removes the child segment of a path and returns the remaining parent /// portion. /// - /// /// /// A fully qualified provider specific path to an item. The item may or /// may not exist. /// - /// /// /// The fully qualified path to the root of a drive. This parameter may be null /// or empty if a mounted drive is not in use for this operation. If this parameter /// is not null or empty the result of the method should not be a path to a container /// that is a parent or in a different tree than the root. /// - /// /// /// The path of the parent of the path parameter. /// - /// /// /// This should be a lexical splitting of the path on the path separator character /// for the provider namespace. For example, the file system provider should look @@ -500,9 +404,9 @@ protected virtual string GetParentPath(string path, string root) // Verify the parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } if (root == null) @@ -517,7 +421,7 @@ protected virtual string GetParentPath(string path, string root) path = NormalizePath(path); path = path.TrimEnd(StringLiterals.DefaultPathSeparator); - string rootPath = String.Empty; + string rootPath = string.Empty; if (root != null) { @@ -527,12 +431,12 @@ protected virtual string GetParentPath(string path, string root) // Check to see if the path is equal to the root // of the virtual drive - if (String.Compare( + if (string.Equals( path, rootPath, - StringComparison.OrdinalIgnoreCase) == 0) + StringComparison.OrdinalIgnoreCase)) { - parentPath = String.Empty; + parentPath = string.Empty; } else { @@ -550,33 +454,30 @@ protected virtual string GetParentPath(string path, string root) } else { - parentPath = String.Empty; + parentPath = string.Empty; } } + return parentPath; } - } // GetParentPath + } /// /// Normalizes the path that was passed in and returns the normalized path /// as a relative path to the basePath that was passed. /// - /// /// /// A fully qualified provider specific path to an item. The item should exist /// or the provider should write out an error. /// - /// /// /// The path that the return value should be relative to. /// - /// /// /// A normalized path that is relative to the basePath that was passed. The /// provider should parse the path parameter, normalize the path, and then /// return the normalized path relative to the basePath. /// - /// /// /// This method does not have to be purely syntactical parsing of the path. It /// is encouraged that the provider actually use the path to lookup in its store @@ -594,7 +495,7 @@ protected virtual string NormalizeRelativePath( { return ContractRelativePath(path, basePath, false, Context); } - } // NormalizeRelativePath + } internal string ContractRelativePath( string path, @@ -606,18 +507,15 @@ internal string ContractRelativePath( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (path.Length == 0) { - return String.Empty; + return string.Empty; } - if (basePath == null) - { - basePath = String.Empty; - } + basePath ??= string.Empty; providerBaseTracer.WriteLine("basePath = {0}", basePath); @@ -641,7 +539,7 @@ internal string ContractRelativePath( // Active Directory team. // // WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND - if (!String.Equals(context.ProviderInstance.ProviderInfo.FullName, + if (!string.Equals(context.ProviderInstance.ProviderInfo.FullName, @"Microsoft.ActiveDirectory.Management\ActiveDirectory", StringComparison.OrdinalIgnoreCase)) { normalizedPath = NormalizePath(path); @@ -654,18 +552,18 @@ internal string ContractRelativePath( string originalPath = path; Stack tokenizedPathStack = null; - if (path.EndsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.OrdinalIgnoreCase)) + if (path.EndsWith(StringLiterals.DefaultPathSeparator)) { path = path.TrimEnd(StringLiterals.DefaultPathSeparator); originalPathHadTrailingSlash = true; } - basePath = basePath.TrimEnd(StringLiterals.DefaultPathSeparator); + basePath = basePath.TrimEnd(StringLiterals.DefaultPathSeparator); // See if the base and the path are already the same. We resolve this to // ..\Leaf, since resolving "." to "." doesn't offer much information. - if (String.Equals(normalizedPath, normalizedBasePath, StringComparison.OrdinalIgnoreCase) && - (!originalPath.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase))) + if (string.Equals(normalizedPath, normalizedBasePath, StringComparison.OrdinalIgnoreCase) && + (!originalPath.EndsWith(StringLiterals.DefaultPathSeparator))) { string childName = GetChildName(path); result = MakePath("..", childName); @@ -677,13 +575,13 @@ internal string ContractRelativePath( if (!normalizedPath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase) && (basePath.Length > 0)) { - result = String.Empty; + result = string.Empty; string commonBase = GetCommonBase(normalizedPath, normalizedBasePath); Stack parentNavigationStack = TokenizePathToStack(normalizedBasePath, commonBase); int parentPopCount = parentNavigationStack.Count; - if (String.IsNullOrEmpty(commonBase)) + if (string.IsNullOrEmpty(commonBase)) { parentPopCount--; } @@ -702,10 +600,10 @@ internal string ContractRelativePath( // ..\..\dir* // In that case (as above,) we keep the ..\..\directory1 // instead of ".." as would usually be returned - if (!String.IsNullOrEmpty(commonBase)) + if (!string.IsNullOrEmpty(commonBase)) { - if (String.Equals(normalizedPath, commonBase, StringComparison.OrdinalIgnoreCase) && - (!normalizedPath.EndsWith("" + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase))) + if (string.Equals(normalizedPath, commonBase, StringComparison.OrdinalIgnoreCase) && + (!normalizedPath.EndsWith(StringLiterals.DefaultPathSeparator))) { string childName = GetChildName(path); result = MakePath("..", result); @@ -749,24 +647,24 @@ internal string ContractRelativePath( if (originalPathHadTrailingSlash) { - result = result + StringLiterals.DefaultPathSeparator; + result += StringLiterals.DefaultPathSeparator; } return result; - } // ContractRelativePath + } /// - /// Get the common base path of two paths + /// Get the common base path of two paths. /// - /// One path - /// Another path + /// One path. + /// Another path. private string GetCommonBase(string path1, string path2) { // Always see if the shorter path is a substring of the // longer path. If it is not, take the child off of the longer // path and compare again. - while (!String.Equals(path1, path2, StringComparison.OrdinalIgnoreCase)) + while (!string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase)) { if (path2.Length > path1.Length) { @@ -784,15 +682,12 @@ private string GetCommonBase(string path1, string path2) /// /// Gets the name of the leaf element in the specified path. /// - /// /// /// The fully qualified path to the item /// - /// /// /// The leaf element in the path. /// - /// /// /// This should be implemented as a split on the path separator. The characters /// in the fullPath may not be legal characters in the namespace but may be @@ -805,9 +700,9 @@ protected virtual string GetChildName(string path) { // Verify the parameters - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } // Normalize the path @@ -829,7 +724,7 @@ protected virtual string GetChildName(string path) string parentPath = GetParentPath(path, null); // No parent, return the entire path - if (String.IsNullOrEmpty(parentPath)) + if (string.IsNullOrEmpty(parentPath)) result = path; // If the parent path ends with the path separator, we can't split // the path based on that @@ -852,21 +747,17 @@ protected virtual string GetChildName(string path) return result; } - } // GetChildName - + } /// /// Determines if the item specified by the path is a container. /// - /// /// /// The path to the item to determine if it is a container. /// - /// /// /// true if the item specified by path is a container, false otherwise. /// - /// /// /// Providers override this method to give the user the ability to check /// to see if a provider object is a container using the test-path -container cmdlet. @@ -887,23 +778,18 @@ protected virtual bool IsItemContainer(string path) } } - /// /// Moves the item specified by path to the specified destination. /// - /// /// /// The path to the item to be moved. /// - /// /// /// The path of the destination container. /// - /// /// /// Nothing is returned, but all the objects that were moved should be written to the WriteItemObject method. /// - /// /// /// Providers override this method to give the user the ability to move provider objects using /// the move-item cmdlet. @@ -937,16 +823,13 @@ protected virtual void MoveItem( /// Gives the provider an opportunity to attach additional parameters to /// the move-item cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The path of the destination container. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -962,8 +845,7 @@ protected virtual object MoveItemDynamicParameters( { return null; } - } // MoveItemDynamicParameters - + } #endregion Protected methods @@ -974,45 +856,58 @@ protected virtual object MoveItemDynamicParameters( /// normalizing the path. This method does some smart checks to reduce the chances of making /// those errors. /// - /// /// - /// The path to normalize + /// The path to normalize. /// - /// /// - /// Normalized path or the original path + /// Normalized path or the original path. /// private string NormalizePath(string path) { // If we have a mix of slashes, then we may introduce an error by normalizing the path. // For example: path HKCU:\Test\/ is pointing to a subkey '/' of 'HKCU:\Test', if we // normalize it, then we will get a wrong path. - bool pathHasForwardSlash = path.IndexOf(StringLiterals.AlternatePathSeparator) != -1; - bool pathHasBackSlash = path.IndexOf(StringLiterals.DefaultPathSeparator) != -1; - bool pathHasMixedSlashes = pathHasForwardSlash && pathHasBackSlash; - bool shouldNormalizePath = true; + // + // Fast return if nothing to normalize. + if (!path.Contains(StringLiterals.AlternatePathSeparator)) + { + return path; + } - string normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + bool pathHasBackSlash = path.Contains(StringLiterals.DefaultPathSeparator); + string normalizedPath; // There is a mix of slashes & the path is rooted & the path exists without normalization. // In this case, we might want to skip the normalization to the path. - if (pathHasMixedSlashes && IsAbsolutePath(path) && ItemExists(path)) + if (pathHasBackSlash && IsAbsolutePath(path) && ItemExists(path)) { // 1. The path exists and ends with a forward slash, in this case, it's very possible the ending forward slash // make sense to the underlying provider, so we skip normalization // 2. The path exists, but not anymore after normalization, then we skip normalization - bool parentEndsWithForwardSlash = path.EndsWith(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal); - if (parentEndsWithForwardSlash || !ItemExists(normalizedPath)) + if (path.EndsWith(StringLiterals.AlternatePathSeparator)) { - shouldNormalizePath = false; + return path; + } + + normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + + if (!ItemExists(normalizedPath)) + { + return path; + } + else + { + return normalizedPath; } } - return shouldNormalizePath ? normalizedPath : path; + normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + + return normalizedPath; } /// - /// Test if the path is an absolute path + /// Test if the path is an absolute path. /// /// /// @@ -1024,7 +919,7 @@ private bool IsAbsolutePath(string path) { result = true; } - else if (this.PSDriveInfo != null && !String.IsNullOrEmpty(this.PSDriveInfo.Root) && + else if (this.PSDriveInfo != null && !string.IsNullOrEmpty(this.PSDriveInfo.Root) && path.StartsWith(this.PSDriveInfo.Root, StringComparison.OrdinalIgnoreCase)) { result = true; @@ -1034,22 +929,18 @@ private bool IsAbsolutePath(string path) } /// - /// Tokenizes the specified path onto a stack + /// Tokenizes the specified path onto a stack. /// - /// /// /// The path to tokenize. /// - /// /// /// The base part of the path that should not be tokenized. /// - /// /// /// A stack containing the tokenized path with leaf elements on the bottom /// of the stack and the most ancestral parent at the top. /// - /// private Stack TokenizePathToStack(string path, string basePath) { Stack tokenizedPathStack = new Stack(); @@ -1062,7 +953,7 @@ private Stack TokenizePathToStack(string path, string basePath) // if its valid string childName = GetChildName(tempPath); - if (String.IsNullOrEmpty(childName)) + if (string.IsNullOrEmpty(childName)) { // Push the parent on and then stop tokenizedPathStack.Push(tempPath); @@ -1080,39 +971,34 @@ private Stack TokenizePathToStack(string path, string basePath) { break; } + previousParent = tempPath; } return tokenizedPathStack; - } // TokenizePathToStack + } /// /// Given the tokenized path, the relative path elements are removed. /// - /// /// /// A stack containing path elements where the leaf most element is at /// the bottom of the stack and the most ancestral parent is on the top. /// Generally this stack comes from TokenizePathToStack(). /// - /// /// /// The path being normalized. Just used for error reporting. /// - /// /// /// The base path to make the path relative to. Just used for error reporting. /// - /// /// /// Determines whether to throw an exception on non-existing paths. /// - /// /// /// A stack in reverse order with the path elements normalized and all relative /// path tokens removed. /// - /// private static Stack NormalizeThePath( Stack tokenizedPathStack, string path, string basePath, bool allowNonExistingPaths) @@ -1147,9 +1033,8 @@ private static Stack NormalizeThePath( if (!allowNonExistingPaths) { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( - "path", + (PSArgumentException)PSTraceSource.NewArgumentException( + nameof(path), SessionStateStrings.NormalizeRelativePathOutsideBase, path, basePath); @@ -1157,38 +1042,35 @@ private static Stack NormalizeThePath( } } } + providerBaseTracer.WriteLine("normalizedPathStack.Push({0})", childName); normalizedPathStack.Push(childName); } return normalizedPathStack; - } // NormalizeThePath + } /// - /// Pops each leaf element of the stack and uses MakePath to generate the relative path + /// Pops each leaf element of the stack and uses MakePath to generate the relative path. /// - /// /// /// The stack containing the leaf elements of the path. /// - /// /// /// A path that is made up of the leaf elements on the given stack. /// - /// /// /// The elements on the stack start from the leaf element followed by its parent /// followed by its parent, etc. Each following element on the stack is the parent /// of the one before it. /// - /// private string CreateNormalizedRelativePathFromStack(Stack normalizedPathStack) { - string leafElement = String.Empty; + string leafElement = string.Empty; while (normalizedPathStack.Count > 0) { - if (String.IsNullOrEmpty(leafElement)) + if (string.IsNullOrEmpty(leafElement)) { leafElement = normalizedPathStack.Pop(); } @@ -1198,12 +1080,12 @@ private string CreateNormalizedRelativePathFromStack(Stack normalizedPat leafElement = MakePath(parentElement, leafElement); } } + return leafElement; - } // CreateNormalizedRelativePathFromStack + } #endregion private members - } // NavigationCmdletProvider + } #endregion NavigationCmdletProvider -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/namespaces/PathInfo.cs b/src/System.Management.Automation/namespaces/PathInfo.cs index 2643dbd94b8..0c5483ceffa 100644 --- a/src/System.Management.Automation/namespaces/PathInfo.cs +++ b/src/System.Management.Automation/namespaces/PathInfo.cs @@ -1,8 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -using Dbg = System.Management.Automation; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -28,7 +25,7 @@ public PSDriveInfo Drive return result; } - } // Drive + } /// /// Gets the provider that contains the path. @@ -39,29 +36,25 @@ public ProviderInfo Provider { return _provider; } - } // Provider + } /// /// This is the internal mechanism to get the hidden drive. /// - /// /// /// The drive associated with this PathInfo. /// - /// internal PSDriveInfo GetDrive() { return _drive; - } // GetDrive + } /// /// Gets the provider internal path for the PSPath that this PathInfo represents. /// - /// /// /// The provider encountered an error when resolving the path. /// - /// /// /// The path was a home relative path but the home path was not /// set for the provider. @@ -81,11 +74,12 @@ public string ProviderPath return _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 { @@ -93,24 +87,22 @@ public string Path { return this.ToString(); } - } // 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() { string result = _path; - if (_drive == null || _drive.Hidden) { @@ -126,54 +118,48 @@ public override string ToString() } return result; - } // ToString + } /// /// The constructor of the PathInfo object. /// - /// /// /// The drive that contains the path /// - /// /// /// The provider that contains the path. /// - /// /// /// The path this object represents. /// - /// /// /// The session state associated with the drive, provider, and path information. /// - /// /// /// If , , /// , or is null. /// - /// internal PathInfo(PSDriveInfo drive, ProviderInfo provider, string path, SessionState sessionState) { if (provider == null) { - throw PSTraceSource.NewArgumentNullException("provider"); + throw PSTraceSource.NewArgumentNullException(nameof(provider)); } if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (sessionState == null) { - throw PSTraceSource.NewArgumentNullException("sessionState"); + throw PSTraceSource.NewArgumentNullException(nameof(sessionState)); } _drive = drive; _provider = provider; _path = path; _sessionState = sessionState; - } // constructor - } // PathInfo -} // namespace System.Management.Automation + } + } +} diff --git a/src/System.Management.Automation/namespaces/ProviderBase.cs b/src/System.Management.Automation/namespaces/ProviderBase.cs index 69ba4aaa608..4345ec9f8ec 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -1,25 +1,26 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 using System.Collections.ObjectModel; +using System.IO; 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,13 +39,12 @@ 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 /// /// The base class for Cmdlet provider. /// - /// /// /// Although it is possible to derive from this base class to implement a Cmdlet Provider, in most /// cases one should derive from , @@ -76,10 +76,10 @@ 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 PSTraceSource providerBaseTracer = PSTraceSource.GetTracer( + internal static readonly PSTraceSource providerBaseTracer = PSTraceSource.GetTracer( "CmdletProviderClasses", "The namespace provider base classes tracer"); @@ -89,24 +89,21 @@ public abstract partial class CmdletProvider : IResourceSupplier /// Sets the provider information that is stored in the Monad engine into the /// provider base class. /// - /// /// /// The provider information that is stored by the Monad engine. /// - /// /// /// If is null. /// - /// internal void SetProviderInformation(ProviderInfo providerInfoToSet) { if (providerInfoToSet == null) { - throw PSTraceSource.NewArgumentNullException("providerInfoToSet"); + throw PSTraceSource.NewArgumentNullException(nameof(providerInfoToSet)); } _providerInformation = providerInfoToSet; - } // SetProviderInformation + } /// /// Checks whether the filter of the provider is set. @@ -117,7 +114,7 @@ internal void SetProviderInformation(ProviderInfo providerInfoToSet) /// internal virtual bool IsFilterSet() { - bool filterSet = !String.IsNullOrEmpty(Filter); + bool filterSet = !string.IsNullOrEmpty(Filter); return filterSet; } @@ -126,13 +123,11 @@ internal virtual bool IsFilterSet() /// /// Gets or sets the context for the running command. /// - /// /// /// On set, if the context contains credentials and the provider /// doesn't support credentials, or if the context contains a filter /// parameter and the provider does not support filters. /// - /// internal CmdletProviderContext Context { get @@ -167,7 +162,7 @@ internal CmdletProviderContext Context } // Check that the provider supports the use of filters - if ((!String.IsNullOrEmpty(value.Filter)) && + if ((!string.IsNullOrEmpty(value.Filter)) && (!CmdletProviderManagementIntrinsics.CheckProviderCapabilities(ProviderCapabilities.Filter, _providerInformation))) { throw PSTraceSource.NewNotSupportedException( @@ -186,67 +181,59 @@ internal CmdletProviderContext Context _contextBase = value; _contextBase.ProviderInstance = this; } - } // Context + } /// /// Called when the provider is first initialized. It sets the context /// of the call and then calls the derived providers Start method. /// - /// /// /// The information about the provider. /// - /// /// /// The context under which this method is being called. /// - /// internal ProviderInfo Start(ProviderInfo providerInfo, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; return Start(providerInfo); - } // Start + } /// /// Gets an object that defines the additional parameters for the Start implementation /// for a provider. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object StartDynamicParameters(CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; return StartDynamicParameters(); - } // StartDynamicParameter + } /// /// Called when the provider is being removed. It sets the context /// of the call and then calls the derived providers Stop method. /// - /// /// /// The context under which this method is being called. - /// - /// + /// internal void Stop(CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; Stop(); - } // Stop + } /// protected internal virtual void StopProcessing() { - } // StopProcessing + } #endregion CmdletProvider method wrappers @@ -256,20 +243,16 @@ protected internal virtual void StopProcessing() /// Internal wrapper for the GetProperty protected method. This method will /// only be called if the provider implements the IPropertyCmdletProvider interface. /// - /// /// /// The path to the item to retrieve properties from. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// The context under which this method is being called. /// - /// internal void GetProperty( string path, Collection providerSpecificPickList, @@ -277,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( @@ -289,32 +270,27 @@ internal void GetProperty( // Call interface method propertyProvider.GetProperty(path, providerSpecificPickList); - } // GetProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the get-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object GetPropertyDynamicParameters( string path, Collection providerSpecificPickList, @@ -322,33 +298,28 @@ internal object GetPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.GetPropertyDynamicParameters(path, providerSpecificPickList); - } // GetPropertyDynamicParameters + } /// /// Internal wrapper for the SetProperty protected method. This method will /// only be called if the provider implements the IPropertyCmdletProvider interface. /// - /// /// /// The path to the item to set the properties on. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// The context under which this method is being called. /// - /// internal void SetProperty( string path, PSObject propertyValue, @@ -356,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( @@ -368,32 +337,27 @@ internal void SetProperty( // Call interface method propertyProvider.SetProperty(path, propertyValue); - } // SetProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the set-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object SetPropertyDynamicParameters( string path, PSObject propertyValue, @@ -401,37 +365,31 @@ internal object SetPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.SetPropertyDynamicParameters(path, propertyValue); - } // SetPropertyDynamicParameters + } /// /// Internal wrapper for the ClearProperty protected method. This method will /// only be called if the provider implements the IPropertyCmdletProvider interface. /// - /// /// /// The path to the item from which the property should be cleared. /// - /// /// /// The name of the property that should be cleared. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic clearing of properties. /// - /// internal void ClearProperty( string path, Collection propertyName, @@ -439,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( @@ -451,32 +407,27 @@ internal void ClearProperty( // Call interface method propertyProvider.ClearProperty(path, propertyName); - } // ClearProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the clear-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A list of properties that should be cleared. If this parameter is null /// or empty, all properties should be cleared. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object ClearPropertyDynamicParameters( string path, Collection providerSpecificPickList, @@ -484,14 +435,13 @@ internal object ClearPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.ClearPropertyDynamicParameters(path, providerSpecificPickList); - } // ClearPropertyDynamicParameters + } #endregion IPropertyCmdletProvider @@ -501,32 +451,25 @@ internal object ClearPropertyDynamicParameters( /// Internal wrapper for the NewProperty protected method. This method will /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface. /// - /// /// /// The path to the item on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic creation of properties. /// - /// internal void NewProperty( string path, string propertyName, @@ -536,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( @@ -548,39 +489,32 @@ internal void NewProperty( // Call interface method propertyProvider.NewProperty(path, propertyName, propertyTypeName, value); - } // NewProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the new-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object NewPropertyDynamicParameters( string path, string propertyName, @@ -590,37 +524,31 @@ internal object NewPropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.NewPropertyDynamicParameters(path, propertyName, propertyTypeName, value); - } // NewPropertyDynamicParameters + } /// /// Internal wrapper for the RemoveProperty protected method. This method will /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface. /// - /// /// /// The path to the item on which the property should be removed. /// - /// /// /// The name of the property to be removed /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic removal of properties. /// - /// internal void RemoveProperty( string path, string propertyName, @@ -628,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( @@ -640,31 +566,26 @@ internal void RemoveProperty( // Call interface method propertyProvider.RemoveProperty(path, propertyName); - } // RemoveProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the remove-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be removed. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object RemovePropertyDynamicParameters( string path, string propertyName, @@ -672,41 +593,34 @@ internal object RemovePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.RemovePropertyDynamicParameters(path, propertyName); - } // RemovePropertyDynamicParameters + } /// /// Internal wrapper for the RenameProperty protected method. This method will /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface. /// - /// /// /// The path to the item on which the property should be renamed. /// - /// /// /// The name of the property that should be renamed. /// - /// /// /// The new name for the property. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic renaming of properties. /// - /// internal void RenameProperty( string path, string propertyName, @@ -715,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( @@ -727,35 +639,29 @@ internal void RenameProperty( // Call interface method propertyProvider.RenameProperty(path, propertyName, newPropertyName); - } // RenameProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the rename-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be renamed. /// - /// /// /// The name of the property to rename it to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object RenamePropertyDynamicParameters( string path, string sourceProperty, @@ -764,45 +670,37 @@ internal object RenamePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.RenamePropertyDynamicParameters(path, sourceProperty, destinationProperty); - } // RenamePropertyDynamicParameters + } /// /// Internal wrapper for the CopyProperty protected method. This method will /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface. /// - /// /// /// The path to the item from which the property should be copied. /// - /// /// /// The name of the property that should be copied. /// - /// /// /// The path to the item to which the property should be copied. /// - /// /// /// The name of the property that should be copied to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic copying of properties. /// - /// internal void CopyProperty( string sourcePath, string sourceProperty, @@ -812,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( @@ -824,39 +720,32 @@ internal void CopyProperty( // Call interface method propertyProvider.CopyProperty(sourcePath, sourceProperty, destinationPath, destinationProperty); - } // CopyProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the copy-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be copied. /// - /// /// /// The path to the item to which the property should be copied. /// - /// /// /// The name of the property that should be copied to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object CopyPropertyDynamicParameters( string path, string sourceProperty, @@ -866,45 +755,37 @@ internal object CopyPropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.CopyPropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty); - } // CopyPropertyDynamicParameters + } /// /// Internal wrapper for the MoveProperty protected method. This method will /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface. /// - /// /// /// The path to the item from which the property should be moved. /// - /// /// /// The name of the property that should be moved. /// - /// /// /// The path to the item to which the property should be moved. /// - /// /// /// The name of the property that should be moved to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// Implement this method when you are providing access to a data store /// that allows dynamic moving of properties. /// - /// internal void MoveProperty( string sourcePath, string sourceProperty, @@ -914,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( @@ -926,39 +805,32 @@ internal void MoveProperty( // Call interface method propertyProvider.MoveProperty(sourcePath, sourceProperty, destinationPath, destinationProperty); - } // MoveProperty + } /// /// Gives the provider a chance to attach additional parameters to /// the move-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property that should be copied. /// - /// /// /// The path to the item to which the property should be copied. /// - /// /// /// The name of the property that should be copied to. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object MovePropertyDynamicParameters( string path, string sourceProperty, @@ -968,14 +840,13 @@ internal object MovePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } + return propertyProvider.MovePropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty); - } // MovePropertyDynamicParameters + } #endregion IDynamicPropertyCmdletProvider method wrappers @@ -985,28 +856,22 @@ internal object MovePropertyDynamicParameters( /// Internal wrapper for the GetContentReader protected method. This method will /// only be called if the provider implements the IContentCmdletProvider interface. /// - /// /// /// The path to the item to retrieve content from. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An instance of the IContentReader for the specified path. /// - /// internal IContentReader GetContentReader( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1016,69 +881,57 @@ internal IContentReader GetContentReader( // Call interface method return contentProvider.GetContentReader(path); - } // GetContentReader + } /// /// Gives the provider a chance to attach additional parameters to /// the get-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object GetContentReaderDynamicParameters( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } - return contentProvider.GetContentReaderDynamicParameters(path); - } // GetContentReaderDynamicParameters + return contentProvider.GetContentReaderDynamicParameters(path); + } /// /// Internal wrapper for the GetContentWriter protected method. This method will /// only be called if the provider implements the IContentCmdletProvider interface. /// - /// /// /// The path to the item to set content on. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An instance of the IContentWriter for the specified path. /// - /// internal IContentWriter GetContentWriter( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1088,65 +941,54 @@ internal IContentWriter GetContentWriter( // Call interface method return contentProvider.GetContentWriter(path); - } // GetContentWriter + } /// /// Gives the provider a chance to attach additional parameters to /// the add-content and set-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object GetContentWriterDynamicParameters( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } - return contentProvider.GetContentWriterDynamicParameters(path); - } // GetContentWriterDynamicParameters + return contentProvider.GetContentWriterDynamicParameters(path); + } /// /// Internal wrapper for the ClearContent protected method. This method will /// only be called if the provider implements the IContentCmdletProvider interface. /// - /// /// /// The path to the item to clear the content from. /// - /// /// /// The context under which this method is being called. /// - /// internal void ClearContent( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1156,42 +998,36 @@ internal void ClearContent( // Call interface method contentProvider.ClearContent(path); - } // ClearContent + } /// /// Gives the provider a chance to attach additional parameters to /// the clear-content cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The context under which this method is being called. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. /// - /// internal object ClearContentDynamicParameters( string path, CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } - return contentProvider.ClearContentDynamicParameters(path); - } // ClearContentDynamicParameters + return contentProvider.ClearContentDynamicParameters(path); + } #endregion IContentCmdletProvider method wrappers @@ -1202,11 +1038,9 @@ internal object ClearContentDynamicParameters( /// /// Gives the provider the opportunity to initialize itself. /// - /// /// /// The information about the provider that is being started. /// - /// /// /// The default implementation returns the ProviderInfo instance that /// was passed. @@ -1230,7 +1064,6 @@ protected virtual ProviderInfo Start(ProviderInfo providerInfo) /// Gets an object that defines the additional parameters for the Start implementation /// for a provider. /// - /// /// /// Overrides of this method should return an object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class or a @@ -1249,7 +1082,6 @@ protected virtual object StartDynamicParameters() /// /// Called by session state when the provider is being removed. /// - /// /// /// A provider should override this method to free up any resources that the provider /// was using. @@ -1277,9 +1109,9 @@ public bool Stopping "The context should always be set"); return Context.Stopping; - } // TraceProperty - } // get - } // Stopping + } + } + } /// /// Gets the instance of session state for the current runspace. @@ -1295,9 +1127,9 @@ public SessionState SessionState "The context should always be set"); return new SessionState(Context.ExecutionContext.EngineSessionState); - } // TraceProperty - } // get - } // SessionState + } + } + } /// /// Gets the instance of the provider interface APIs for the current runspace. @@ -1313,9 +1145,9 @@ public ProviderIntrinsics InvokeProvider "The context should always be set"); return new ProviderIntrinsics(Context.ExecutionContext.EngineSessionState); - } // TraceProperty - } // get - } // InvokeProvider + } + } + } /// /// Gets the instance of the command invocation APIs for the current runspace. @@ -1330,11 +1162,10 @@ public CommandInvocationIntrinsics InvokeCommand Context != null, "The context should always be set"); - return new CommandInvocationIntrinsics(Context.ExecutionContext); - } // TraceProperty - } // get - } // InvokeCommand + } + } + } /// /// Gets the credentials under which the operation should run. @@ -1358,7 +1189,6 @@ public PSCredential Credential /// The information about the provider that is stored in the runspace /// on behalf of the provider. /// - /// /// /// If a derived type of ProviderInfo was returned from the Start method, it /// will be set here in all subsequent calls to the provider. @@ -1370,8 +1200,8 @@ protected internal ProviderInfo ProviderInfo using (PSTransactionManager.GetEngineProtectionScope()) { return _providerInformation; - } // TraceProperty - } // get + } + } } /// @@ -1388,9 +1218,9 @@ protected PSDriveInfo PSDriveInfo "The context should always be set"); return Context.Drive; - } // TraceProperty - } // get - } // PSDriveInfo + } + } + } /// /// The dynamic parameters object populated with the values as specified @@ -1407,14 +1237,13 @@ protected object DynamicParameters "The context should always be set"); return Context.DynamicParameters; - } // TraceProperty - } // get - } // DynamicParameters + } + } + } /// /// Gets the force property. /// - /// /// /// Gives the provider guidance on how vigorous it should be about performing /// the operation. If true, the provider should do everything possible to perform @@ -1435,9 +1264,9 @@ public SwitchParameter Force "The context should always be set"); return Context.Force; - } // TraceProperty - } // get - } // Force + } + } + } /// /// Gets the provider specific filter that was supplied by the caller. @@ -1453,9 +1282,9 @@ public string Filter "The context should always be set"); return Context.Filter; - } // TraceProperty - } // get - } // Filter + } + } + } /// /// Gets the include wildcard patterns which is used to determine which items @@ -1472,9 +1301,9 @@ public Collection Include "The context should always be set"); return Context.Include; - } // TraceProperty - } // get - } // Include + } + } + } /// /// Gets the exclude wildcard patterns which is used to determine which items @@ -1491,9 +1320,9 @@ public Collection Exclude "The context should always be set"); return Context.Exclude; - } // TraceProperty - } // get - } // Exclude + } + } + } /// /// Gets the host interaction APIs. @@ -1511,7 +1340,22 @@ public PSHost Host return Context.ExecutionContext.EngineHostInterface; } } - } // Host + } + + /// + /// Gets the default item separator character for this provider. + /// + public virtual char ItemSeparator => Path.DirectorySeparatorChar; + + /// + /// Gets the alternate item separator character for this provider. + /// + public virtual char AltItemSeparator => +#if UNIX + '\\'; +#else + Path.AltDirectorySeparatorChar; +#endif #region IResourceSupplier /// @@ -1519,19 +1363,15 @@ public PSHost Host /// resourceId from the current assembly. You should override /// this if you require a different behavior. /// - /// /// /// the base resource name /// - /// /// /// the resource id /// - /// /// /// the resource string corresponding to baseName and resourceId /// - /// /// /// When overriding this method, the resource string for the specified /// resource should be retrieved from a localized resource assembly. @@ -1540,19 +1380,19 @@ public virtual string GetResourceString(string baseName, string resourceId) { using (PSTransactionManager.GetEngineProtectionScope()) { - if (String.IsNullOrEmpty(baseName)) + if (string.IsNullOrEmpty(baseName)) { - throw PSTraceSource.NewArgumentException("baseName"); + throw PSTraceSource.NewArgumentException(nameof(baseName)); } - if (String.IsNullOrEmpty(resourceId)) + if (string.IsNullOrEmpty(resourceId)) { - throw PSTraceSource.NewArgumentException("resourceId"); + throw PSTraceSource.NewArgumentException(nameof(resourceId)); } ResourceManager manager = ResourceManagerCache.GetResourceManager( - this.GetType().GetTypeInfo().Assembly, + this.GetType().Assembly, baseName); string retValue = null; @@ -1564,31 +1404,33 @@ public virtual string GetResourceString(string baseName, string resourceId) } catch (MissingManifestResourceException) { - throw PSTraceSource.NewArgumentException("baseName", GetErrorText.ResourceBaseNameFailure, baseName); + throw PSTraceSource.NewArgumentException(nameof(baseName), GetErrorText.ResourceBaseNameFailure, baseName); } + if (retValue == null) { - throw PSTraceSource.NewArgumentException("resourceId", GetErrorText.ResourceIdFailure, resourceId); + throw PSTraceSource.NewArgumentException(nameof(resourceId), GetErrorText.ResourceIdFailure, resourceId); } return retValue; } - } // GetResourceString + } #endregion IResourceSupplier #region ThrowTerminatingError /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { using (PSTransactionManager.GetEngineProtectionScope()) { - if (null == errorRecord) + if (errorRecord == null) { - throw PSTraceSource.NewArgumentNullException("errorRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(errorRecord)); } - if (null != errorRecord.ErrorDetails - && null != errorRecord.ErrorDetails.TextLookupError) + if (errorRecord.ErrorDetails != null + && errorRecord.ErrorDetails.TextLookupError != null) { Exception textLookupError = errorRecord.ErrorDetails.TextLookupError; errorRecord.ErrorDetails.TextLookupError = null; @@ -1634,7 +1476,7 @@ public bool ShouldProcess( return Context.ShouldProcess(target); } - } // ShouldProcess + } /// public bool ShouldProcess( @@ -1649,7 +1491,7 @@ public bool ShouldProcess( return Context.ShouldProcess(target, action); } - } // ShouldProcess + } /// public bool ShouldProcess( @@ -1668,7 +1510,7 @@ public bool ShouldProcess( verboseWarning, caption); } - } // ShouldProcess + } /// public bool ShouldProcess( @@ -1689,7 +1531,7 @@ public bool ShouldProcess( caption, out shouldProcessReason); } - } // ShouldProcess + } /// public bool ShouldContinue( @@ -1704,7 +1546,7 @@ public bool ShouldContinue( return Context.ShouldContinue(query, caption); } - } // ShouldContinue + } /// public bool ShouldContinue( @@ -1722,7 +1564,7 @@ public bool ShouldContinue( return Context.ShouldContinue( query, caption, ref yesToAll, ref noToAll); } - } // ShouldContinue + } #region Transaction Support @@ -1742,7 +1584,7 @@ public bool TransactionAvailable() /// /// Gets an object that surfaces the current PowerShell transaction. - /// When this object is disposed, PowerShell resets the active transaction + /// When this object is disposed, PowerShell resets the active transaction. /// public PSTransactionContext CurrentPSTransaction { @@ -1767,7 +1609,7 @@ public void WriteVerbose(string text) Context.WriteVerbose(text); } - } // WriteVerbose + } /// public void WriteWarning(string text) @@ -1780,8 +1622,7 @@ public void WriteWarning(string text) Context.WriteWarning(text); } - } // WriteVerbose - + } /// public void WriteProgress(ProgressRecord progressRecord) @@ -1794,13 +1635,12 @@ public void WriteProgress(ProgressRecord progressRecord) if (progressRecord == null) { - throw PSTraceSource.NewArgumentNullException("progressRecord"); + throw PSTraceSource.NewArgumentNullException(nameof(progressRecord)); } Context.WriteProgress(progressRecord); } - } // WriteProgress - + } /// public void WriteDebug(string text) @@ -1813,7 +1653,7 @@ public void WriteDebug(string text) Context.WriteDebug(text); } - } // WriteDebug + } /// public void WriteInformation(InformationRecord record) @@ -1826,10 +1666,10 @@ public void WriteInformation(InformationRecord record) Context.WriteInformation(record); } - } // WriteInformation + } /// - public void WriteInformation(Object messageData, string[] tags) + public void WriteInformation(object messageData, string[] tags) { using (PSTransactionManager.GetEngineProtectionScope()) { @@ -1839,25 +1679,21 @@ public void WriteInformation(Object messageData, string[] tags) Context.WriteInformation(messageData, tags); } - } // WriteInformation + } /// /// Converts the incoming object to a PSObject and then adds extra /// data as notes. Then it writes the shell object to the context. /// - /// /// /// The item being written out. /// - /// /// /// The path of the item being written out. /// - /// /// /// True if the item is a container, false otherwise. /// - /// private void WriteObject( object item, string path, @@ -1875,21 +1711,18 @@ private void WriteObject( "The context should always be set"); Context.WriteObject(result); - } // WriteObject + } /// /// Converts the incoming object to a PSObject and then adds extra /// data as notes. Then it writes the shell object to the context. /// - /// /// /// The item being written out. /// - /// /// /// The path of the item being written out. /// - /// private void WriteObject( object item, string path) @@ -1901,38 +1734,34 @@ private void WriteObject( "The context should always be set"); Context.WriteObject(result); - } // WriteObject + } /// /// Wraps the item in a PSObject and attaches some notes to the /// object that deal with path information. /// - /// /// /// The item to be wrapped. /// - /// /// /// The path to the item. /// - /// /// /// A PSObject that wraps the item and has path information attached /// as notes. /// - /// /// /// if is null. /// - /// private PSObject WrapOutputInPSObject( object item, string path) { if (item == null) { - throw PSTraceSource.NewArgumentNullException("item"); + throw PSTraceSource.NewArgumentNullException(nameof(item)); } + PSObject result = new PSObject(item); Diagnostics.Assert( @@ -1950,7 +1779,7 @@ private PSObject WrapOutputInPSObject( // Construct a provider qualified path as the Path note - String providerQualifiedPath = + string providerQualifiedPath = LocationGlobber.GetProviderQualifiedPath(path, ProviderInfo); result.AddOrSetProperty("PSPath", providerQualifiedPath); @@ -1971,16 +1800,17 @@ private PSObject WrapOutputInPSObject( } else { - parentPath = navProvider.GetParentPath(path, String.Empty, Context); + parentPath = navProvider.GetParentPath(path, string.Empty, Context); } - string providerQualifiedParentPath = String.Empty; + string providerQualifiedParentPath = string.Empty; - if (!String.IsNullOrEmpty(parentPath)) + if (!string.IsNullOrEmpty(parentPath)) { providerQualifiedParentPath = LocationGlobber.GetProviderQualifiedPath(parentPath, ProviderInfo); } + result.AddOrSetProperty("PSParentPath", providerQualifiedParentPath); providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSParentPath", providerQualifiedParentPath); @@ -1988,9 +1818,29 @@ private PSObject WrapOutputInPSObject( string childName = navProvider.GetChildName(path, Context); - result.AddOrSetProperty("PSChildName", childName); providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSChildName", childName); +#if UNIX + + // Add a commonstat structure to file system objects + if (ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider)) + { + try + { + // 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); + } + catch + { + // If there is *any* problem in retrieving the stat information + // set the property to null. There is no specific exception which + // would result in different behavior. + result.AddOrSetProperty("UnixStat", value: null); + } + } +#endif } // PSDriveInfo @@ -2007,25 +1857,21 @@ private PSObject WrapOutputInPSObject( providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSProvider", this.ProviderInfo); return result; - } // WrapOutputInPSObject + } /// /// Writes an item to the output as a PSObject with extra data attached /// as notes. /// - /// /// /// The item to be written. /// - /// /// /// The path of the item being written. /// - /// /// /// True if the item is a container, false otherwise. /// - /// /// -#if CORECLR //System.Transaction namespace is not in CoreClr. +#if CORECLR // System.Transaction namespace is not in CoreClr. [CmdletProvider(RegistryProvider.ProviderName, ProviderCapabilities.ShouldProcess)] #else [CmdletProvider(RegistryProvider.ProviderName, ProviderCapabilities.ShouldProcess | ProviderCapabilities.Transactions)] #endif - [OutputType(typeof(String), ProviderCmdlet = ProviderCmdlet.MoveItemProperty)] + [OutputType(typeof(string), ProviderCmdlet = ProviderCmdlet.MoveItemProperty)] [OutputType(typeof(RegistryKey), typeof(string), ProviderCmdlet = ProviderCmdlet.GetChildItem)] [OutputType(typeof(RegistryKey), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(System.Security.AccessControl.RegistrySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)] @@ -61,6 +61,9 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(RegistryKey), ProviderCmdlet = ProviderCmdlet.GetItem)] [OutputType(typeof(RegistryKey), typeof(string), typeof(Int32), typeof(Int64), ProviderCmdlet = ProviderCmdlet.GetItemProperty)] [OutputType(typeof(RegistryKey), ProviderCmdlet = ProviderCmdlet.NewItem)] + [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)] + [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)] public sealed partial class RegistryProvider : NavigationCmdletProvider, IPropertyCmdletProvider, @@ -76,17 +79,26 @@ 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"); #endregion tracer /// - /// Gets the name of the provider + /// Gets the name of the provider. /// public const string ProviderName = "Registry"; + #region CmdletProvider overrides + + /// + /// Gets the alternate item separator character for this provider. + /// + public override char AltItemSeparator => ItemSeparator; + + #endregion + #region DriveCmdletProvider overrides /// @@ -104,7 +116,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) { if (drive == null) { - throw PSTraceSource.NewArgumentNullException("drive"); + throw PSTraceSource.NewArgumentNullException(nameof(drive)); } if (!ItemExists(drive.Root)) @@ -116,8 +128,9 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) ErrorCategory.InvalidArgument, drive.Root)); } + return drive; - } // NewDrive + } /// /// Creates HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER registry drives during provider initialization. @@ -150,7 +163,7 @@ protected override Collection InitializeDefaultDrives() null)); return drives; - } // InitializeDefaultDrives + } #endregion DriveCmdletProvider overrides @@ -159,11 +172,9 @@ protected override Collection InitializeDefaultDrives() /// /// Determines if the specified is syntactically and semantically valid. /// - /// /// /// The path to validate. /// - /// /// /// True if the path is valid, or False otherwise. /// @@ -187,7 +198,7 @@ protected override bool IsValidPath(string path) root = root.Substring(0, pathSeparator); } - if (String.IsNullOrEmpty(root)) + if (string.IsNullOrEmpty(root)) { // An empty path means that we are at the root and should // enumerate the hives. So that is a valid path. @@ -209,7 +220,6 @@ protected override bool IsValidPath(string path) /// and writes it to the pipeline using the WriteObject method. /// Any non-terminating exceptions are written to the WriteError method. /// - /// /// /// The path to the key to retrieve. /// @@ -227,26 +237,23 @@ protected override void GetItem(string path) // Write out the result WriteRegistryItemObject(result, path); - } // GetItem - + } /// /// Sets registry values at to the specified. /// - /// /// /// The path to the item that is to be set. Only registry values can be set using /// this method. /// - /// /// /// The new value for the registry value. /// protected override void SetItem(string path, object value) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } // Confirm the set item with the user @@ -256,7 +263,7 @@ protected override void SetItem(string path, object value) string resourceTemplate = RegistryProviderStrings.SetItemResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -325,7 +332,7 @@ protected override void SetItem(string path, object value) return; } } - } // DynamicParameters != null + } if (!valueSet) { @@ -373,21 +380,18 @@ protected override void SetItem(string path, object value) key.Close(); WriteItemObject(result, path, false); - } // ShouldProcess - } // SetItem + } + } /// /// Gets the dynamic parameters for the SetItem method. /// - /// /// /// Ignored. /// - /// /// /// Ignored. /// - /// /// /// An instance of the class which /// contains a parameter for the Type. @@ -395,7 +399,7 @@ protected override void SetItem(string path, object value) protected override object SetItemDynamicParameters(string path, object value) { return new RegistryProviderSetItemDynamicParameter(); - } // SetItemDynamicParameters + } /// /// Clears the item at the specified . @@ -411,9 +415,9 @@ protected override object SetItemDynamicParameters(string path, object value) /// protected override void ClearItem(string path) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } // Confirm the clear item with the user @@ -423,7 +427,7 @@ protected override void ClearItem(string path) string resourceTemplate = RegistryProviderStrings.ClearItemResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path); @@ -503,8 +507,8 @@ protected override void ClearItem(string path) // Write out the key WriteRegistryItemObject(key, path); - } // ShouldProcess - } // ClearItem + } + } #endregion ItemCmdletProvider overrides @@ -533,7 +537,7 @@ protected override void GetChildItems( if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (IsHiveContainer(path)) @@ -579,7 +583,7 @@ protected override void GetChildItems( return; } - if (!String.IsNullOrEmpty(subkeyName)) + if (!string.IsNullOrEmpty(subkeyName)) { string keypath = path; @@ -589,7 +593,7 @@ protected override void GetChildItems( keypath = MakePath(path, subkeyName, childIsLeaf: true); - if (!String.IsNullOrEmpty(keypath)) + if (!string.IsNullOrEmpty(keypath)) { // Call GetItem to retrieve the RegistryKey object // and write it to the WriteObject method. @@ -635,8 +639,8 @@ protected override void GetChildItems( WriteError(new ErrorRecord(unauthorizedAccessException, unauthorizedAccessException.GetType().FullName, ErrorCategory.PermissionDenied, keypath)); } } - } // foreach subkeyName in keyNames - } // keyNames != null + } + } } catch (System.IO.IOException ioException) { @@ -660,16 +664,14 @@ protected override void GetChildItems( WriteError(new ErrorRecord(unauthorizedAccessException, unauthorizedAccessException.GetType().FullName, ErrorCategory.PermissionDenied, path)); } } - } // GetChildItems + } /// /// Gets all the child key and value names of the key at the specified . /// - /// /// /// The path to the key to get the child names from. /// - /// /// /// Ignored since the registry provider does not implement filtering. /// Normally, if this parameter is ReturnAllContainers then all subkeys should be @@ -682,7 +684,7 @@ protected override void GetChildNames( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (path.Length == 0) @@ -756,7 +758,7 @@ protected override void GetChildNames( WriteError(new ErrorRecord(unauthorizedAccessException, unauthorizedAccessException.GetType().FullName, ErrorCategory.PermissionDenied, path)); } } - } // GetChildNames + } private const string charactersThatNeedEscaping = ".*?[]:"; @@ -764,11 +766,9 @@ protected override void GetChildNames( /// Escapes the characters in the registry key path that are used by globbing and /// path. /// - /// /// /// The path to escape. /// - /// /// /// The escaped path. /// @@ -786,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()) { @@ -801,26 +801,23 @@ 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); } return result.ToString(); - } // EscapeSpecialChars - + } /// /// Escapes the characters in the registry key name that are used by globbing and /// path. /// - /// /// /// The name to escape. /// - /// /// /// The escaped name. /// @@ -838,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()) { @@ -853,24 +850,22 @@ 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); } return result.ToString(); - } // EscapeChildName + } /// /// Renames the key at the specified to . /// - /// /// /// The path to the key to rename. /// - /// /// /// The new name of the key. /// @@ -878,14 +873,14 @@ protected override void RenameItem( string path, string newName) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(newName)) + if (string.IsNullOrEmpty(newName)) { - throw PSTraceSource.NewArgumentException("newName"); + throw PSTraceSource.NewArgumentException(nameof(newName)); } s_tracer.WriteLine("newName = {0}", newName); @@ -915,7 +910,7 @@ protected override void RenameItem( string resourceTemplate = RegistryProviderStrings.RenameItemResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -926,23 +921,19 @@ protected override void RenameItem( // Implement rename as a move operation MoveRegistryItem(path, newPath); - } // ShouldProcess - } // RenameItem - + } + } /// /// Creates a new registry key or value at the specified . /// - /// /// /// The path to the new key to create. /// - /// /// /// The type is ignored because this provider only creates /// registry keys. /// - /// /// /// The newItem is ignored because the provider creates the /// key based on the path. @@ -952,9 +943,9 @@ protected override void NewItem( string type, object newItem) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } // Confirm the new item with the user @@ -964,7 +955,7 @@ protected override void NewItem( string resourceTemplate = RegistryProviderStrings.NewItemResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path); @@ -1034,7 +1025,8 @@ protected override void NewItem( { return; } - SetRegistryValue(newKey, String.Empty, newItem, kind, path, false); + + SetRegistryValue(newKey, string.Empty, newItem, kind, path, false); } } catch (Exception exception) @@ -1093,17 +1085,15 @@ protected override void NewItem( { WriteError(new ErrorRecord(notSupportedException, notSupportedException.GetType().FullName, ErrorCategory.InvalidOperation, path)); } - } // ShouldProcess - } // NewItem + } + } /// - /// Removes the specified registry key and all sub-keys + /// Removes the specified registry key and all sub-keys. /// - /// /// /// The path to the key to remove. /// - /// /// /// Ignored. All removes are recursive because the /// registry provider does not support filters. @@ -1112,9 +1102,9 @@ protected override void RemoveItem( string path, bool recurse) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } s_tracer.WriteLine("recurse = {0}", recurse); @@ -1140,7 +1130,7 @@ protected override void RemoveItem( string resourceTemplate = RegistryProviderStrings.RemoveKeyResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path); @@ -1180,19 +1170,17 @@ protected override void RemoveItem( { WriteError(new ErrorRecord(notSupportedException, notSupportedException.GetType().FullName, ErrorCategory.InvalidOperation, path)); } - } // ShouldProcess + } key.Close(); - } // RemoveItem + } /// /// Determines if the key at the specified path exists. /// - /// /// /// The path to the key to determine if it exists. /// - /// /// /// True if the key at the specified path exists, false otherwise. /// @@ -1202,7 +1190,7 @@ protected override bool ItemExists(string path) if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } try @@ -1239,16 +1227,14 @@ protected override bool ItemExists(string path) } return result; - } // ItemExists + } /// /// Determines if the specified key has subkeys. /// - /// /// /// The path to the key to determine if it has sub keys. /// - /// /// /// True if the specified key has subkeys, false otherwise. /// @@ -1258,7 +1244,7 @@ protected override bool HasChildItems(string path) if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } try @@ -1294,20 +1280,17 @@ protected override bool HasChildItems(string path) } return result; - } // HasChildItems + } /// /// Copies the specified registry key to the specified . /// - /// /// /// The path of the registry key to copy. /// - /// /// /// The path to copy the key to. /// - /// /// /// If true all subkeys should be copied. If false, only the /// specified key should be copied. @@ -1317,14 +1300,14 @@ protected override void CopyItem( string destination, bool recurse) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(destination)) + if (string.IsNullOrEmpty(destination)) { - throw PSTraceSource.NewArgumentException("destination"); + throw PSTraceSource.NewArgumentException(nameof(destination)); } s_tracer.WriteLine("destination = {0}", destination); @@ -1364,8 +1347,7 @@ protected override void CopyItem( } key.Close(); - } // CopyItem - + } private bool CopyRegistryKey( IRegistryWrapper key, @@ -1393,11 +1375,11 @@ private bool CopyRegistryKey( "The key should have been validated by the caller"); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(path), + !string.IsNullOrEmpty(path), "The path should have been validated by the caller"); Dbg.Diagnostics.Assert( - !String.IsNullOrEmpty(destination), + !string.IsNullOrEmpty(destination), "The destination should have been validated by the caller"); s_tracer.WriteLine("destination = {0}", destination); @@ -1428,7 +1410,6 @@ private bool CopyRegistryKey( string destinationPath = MakePath(destinationParent, destinationName); - // Confirm the copy item with the user string action = RegistryProviderStrings.CopyKeyAction; @@ -1436,7 +1417,7 @@ private bool CopyRegistryKey( string resourceTemplate = RegistryProviderStrings.CopyKeyResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -1527,7 +1508,7 @@ private bool CopyRegistryKey( } return result; - } // CopyRegistryKey + } private bool ErrorIfDestinationIsSourceOrChildOfSource( string sourcePath, @@ -1540,14 +1521,14 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( bool result = false; - do + while (true) { // See if the paths are equal - if (String.Compare( + if (string.Equals( sourcePath, destinationPath, - StringComparison.OrdinalIgnoreCase) == 0) + StringComparison.OrdinalIgnoreCase)) { result = true; break; @@ -1555,17 +1536,17 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( string newDestinationPath = GetParentPath(destinationPath, null); - if (String.IsNullOrEmpty(newDestinationPath)) + if (string.IsNullOrEmpty(newDestinationPath)) { // We reached the root so the destination must not be a child // of the source break; } - if (String.Compare( + if (string.Equals( newDestinationPath, destinationPath, - StringComparison.OrdinalIgnoreCase) == 0) + StringComparison.OrdinalIgnoreCase)) { // We reached the root so the destination must not be a child // of the source @@ -1573,7 +1554,7 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( } destinationPath = newDestinationPath; - } while (true); + } if (result) { @@ -1586,8 +1567,9 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( ErrorCategory.InvalidArgument, destinationPath)); } + return result; - } // ErrorIfDestinationIsSourceOrChildOfSource + } #endregion ContainerCmdletProvider overrides @@ -1608,7 +1590,7 @@ protected override bool IsItemContainer(string path) { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } bool result = false; @@ -1647,16 +1629,14 @@ protected override bool IsItemContainer(string path) } return result; - } // IsItemContainer + } /// /// Moves the specified key. /// - /// /// /// The path of the key to move. /// - /// /// /// The path to move the key to. /// @@ -1664,14 +1644,14 @@ protected override void MoveItem( string path, string destination) { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - throw PSTraceSource.NewArgumentException("path"); + throw PSTraceSource.NewArgumentException(nameof(path)); } - if (String.IsNullOrEmpty(destination)) + if (string.IsNullOrEmpty(destination)) { - throw PSTraceSource.NewArgumentException("destination"); + throw PSTraceSource.NewArgumentException(nameof(destination)); } s_tracer.WriteLine("destination = {0}", destination); @@ -1683,7 +1663,7 @@ protected override void MoveItem( string resourceTemplate = RegistryProviderStrings.MoveItemResourceTemplate; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -1693,7 +1673,7 @@ protected override void MoveItem( { MoveRegistryItem(path, destination); } - } // MoveItem + } private void MoveRegistryItem(string path, string destination) { @@ -1747,7 +1727,7 @@ private void MoveRegistryItem(string path, string destination) // If the destination is the same container as the source container don't do remove // the source item because the source and destination are the same. - if (String.Equals(sourceParent, destination, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(sourceParent, destination, StringComparison.OrdinalIgnoreCase)) { continueWithRemove = false; } @@ -1783,7 +1763,7 @@ private void MoveRegistryItem(string path, string destination) return; } } - } // MoveRegistryItem + } #endregion NavigationCmdletProvider overrides @@ -1792,16 +1772,13 @@ private void MoveRegistryItem(string path, string destination) /// /// Gets the properties of the item specified by the . /// - /// /// /// The path to the item to retrieve properties from. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// Nothing. An instance of PSObject representing the properties that were retrieved /// should be passed to the WriteObject() method. @@ -1812,7 +1789,7 @@ public void GetProperty( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (!CheckOperationNotAllowedOnHiveContainer(path)) @@ -1839,38 +1816,48 @@ public void GetProperty( foreach (string valueName in filteredPropertyCollection) { string notePropertyName = valueName; - if (String.IsNullOrEmpty(valueName)) + if (string.IsNullOrEmpty(valueName)) { // If the value name is empty then using "(default)" // as the property name when adding the note, as // PSObject does not allow an empty propertyName - notePropertyName = GetLocalizedDefaultToken(); + notePropertyName = LocalizedDefaultToken; + } + + 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)); } - propertyResults.Properties.Add(new PSNoteProperty(notePropertyName, key.GetValue(valueName))); - valueAdded = true; } + key.Close(); if (valueAdded) { WritePropertyObject(propertyResults, path); } - } // GetProperty + } /// /// Sets the specified properties of the item at the specified . /// - /// /// /// The path to the item to set the properties on. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// Nothing. An instance of PSObject representing the properties that were set /// should be passed to the WriteObject() method. @@ -1881,7 +1868,7 @@ public void SetProperty( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (!CheckOperationNotAllowedOnHiveContainer(path)) @@ -1891,7 +1878,7 @@ public void SetProperty( if (propertyValue == null) { - throw PSTraceSource.NewArgumentNullException("propertyValue"); + throw PSTraceSource.NewArgumentNullException(nameof(propertyValue)); } IRegistryWrapper key = GetRegkeyForPathWriteIfError(path, true); @@ -1925,7 +1912,7 @@ public void SetProperty( object newPropertyValue = property.Value; string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -1966,25 +1953,20 @@ public void SetProperty( } key.Close(); - } // SetProperty - - + } /// /// Gives the provider a chance to attach additional parameters to the /// get-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A PSObject which contains a collection of the name, type, value /// of the properties to be set. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. @@ -1999,11 +1981,9 @@ public object SetPropertyDynamicParameters( /// /// Clears a property of the item at the specified . /// - /// /// /// The path to the item on which to clear the property. /// - /// /// /// The name of the property to clear. /// @@ -2013,7 +1993,7 @@ public void ClearProperty( { if (path == null) { - throw PSTraceSource.NewArgumentNullException("path"); + throw PSTraceSource.NewArgumentNullException(nameof(path)); } if (!CheckOperationNotAllowedOnHiveContainer(path)) @@ -2045,7 +2025,7 @@ public void ClearProperty( foreach (string valueName in filteredPropertyCollection) { string resource = - String.Format( + string.Format( Host.CurrentCulture, resourceTemplate, path, @@ -2056,14 +2036,16 @@ public void ClearProperty( // reset the value of the property to its default value object defaultValue = ResetRegistryKeyValue(key, valueName); string propertyNameToAdd = valueName; - if (String.IsNullOrEmpty(valueName)) + if (string.IsNullOrEmpty(valueName)) { - propertyNameToAdd = GetLocalizedDefaultToken(); + propertyNameToAdd = LocalizedDefaultToken; } + result.Properties.Add(new PSNoteProperty(propertyNameToAdd, defaultValue)); addedOnce = true; } } + key.Close(); if (addedOnce) @@ -2072,24 +2054,20 @@ public void ClearProperty( } } - #region Unimplemented methods /// /// Gives the provider a chance to attach additional parameters to the /// get-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// A list of properties that should be retrieved. If this parameter is null /// or empty, all properties should be retrieved. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. @@ -2101,21 +2079,17 @@ public object GetPropertyDynamicParameters( return null; } - /// /// Gives the provider a chance to attach additional parameters to the /// clear-itemproperty cmdlet. /// - /// /// /// If the path was specified on the command line, this is the path /// to the item to get the dynamic parameters for. /// - /// /// /// The name of the property to clear. /// - /// /// /// An object that has properties and fields decorated with /// parsing attributes similar to a cmdlet class. @@ -2133,30 +2107,24 @@ public object ClearPropertyDynamicParameters( #region IDynamicPropertyCmdletProvider /// - /// Creates a new property on the specified item + /// Creates a new property on the specified item. /// - /// /// /// The path to the item on which the new property should be created. /// - /// /// /// The name of the property that should be created. /// - /// /// /// The type of the property that should be created. /// - /// /// /// The new value of the property that should be created. /// - /// /// /// Nothing. A PSObject representing the property that was created should /// be passed to the WriteObject() method. /// - /// /// + 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/DebuggerStrings.resx b/src/System.Management.Automation/resources/DebuggerStrings.resx index 15465afae54..466ee067ceb 100644 --- a/src/System.Management.Automation/resources/DebuggerStrings.resx +++ b/src/System.Management.Automation/resources/DebuggerStrings.resx @@ -210,9 +210,6 @@ The current session does not support debugging; operation will continue. The debugger cannot set a resume action because the debugger in the remote session is not in a Stopped state. - - Workflow debugging is available, but is not supported by the current host. Use the Windows PowerShell console or Windows PowerShell ISE to debug workflows. - The job cannot be debugged because the debugger is currently busy. @@ -240,6 +237,9 @@ The current session does not support debugging; operation will continue. Cannot push a debugger object onto itself. + + The {0} command is not supported for remote use in the version of PowerShell that is running in the remote runspace. + Process @@ -249,4 +249,16 @@ The current session does not support debugging; operation will continue. The debugger detach command is not applicable. The detach command only applies when debugging jobs and runspaces with the Debug-Job or Debug-Runspace cmdlets. + + Invalid runspace id: {0} + + + Unable to get Runspace. + + + Breakpoint or BreakpointList must be specified. + + + The BreakpointList contained an item that was not a breakpoint. + diff --git a/src/System.Management.Automation/resources/DescriptionsStrings.resx b/src/System.Management.Automation/resources/DescriptionsStrings.resx index e548fa78e8b..dd8cb10c17e 100644 --- a/src/System.Management.Automation/resources/DescriptionsStrings.resx +++ b/src/System.Management.Automation/resources/DescriptionsStrings.resx @@ -1,4 +1,4 @@ - + @@ -165,4 +165,29 @@ The specified operator requires both the -Property and -Value parameters. Provide values for both parameters, and then try the command again. - + + This method cannot be run on the current thread. It can only be called on the cmdlet thread. + + + A ForEach-Object -Parallel using variable cannot be a script block. Passed-in script block variables are not supported with ForEach-Object -Parallel, and can result in undefined behavior. + + + A ForEach-Object -Parallel piped input object cannot be a script block. Passed-in script block variables are not supported with ForEach-Object -Parallel, and can result in undefined behavior. + + + The 'TimeoutSeconds' parameter cannot be used with the 'AsJob' parameter. + + + The following common parameters are not currently supported in the Parallel parameter set: +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/InternalHostUserInterfaceStrings.resx b/src/System.Management.Automation/resources/InternalHostUserInterfaceStrings.resx index b55e117bac8..7a8882643da 100644 --- a/src/System.Management.Automation/resources/InternalHostUserInterfaceStrings.resx +++ b/src/System.Management.Automation/resources/InternalHostUserInterfaceStrings.resx @@ -186,9 +186,6 @@ DEBUG: {0} - - INFO: {0} - The host is not currently transcribing. @@ -206,6 +203,12 @@ Machine: {4} ({5}) Host Application: {6} Process ID: {7} {8} +********************** + + + ********************** +PowerShell transcript start +Start time: {0:yyyyMMddHHmmss} ********************** diff --git a/src/System.Management.Automation/resources/Logging.resx b/src/System.Management.Automation/resources/Logging.resx index 7a4d98e5009..c91871fe191 100644 --- a/src/System.Management.Automation/resources/Logging.resx +++ b/src/System.Management.Automation/resources/Logging.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 5f9b75345fe..a76527cfbab 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 66df4cfbdef..39fbc3469f2 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -117,19 +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. - - - Rule must be a ScriptBlock for dynamic match types. + 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: - - 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 cac3bc3759e..298de92da3c 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -232,7 +232,7 @@ Containment operator - case insensitive. Returns TRUE when the test value (right operand) exactly matches at least one of the values in the left operand. - Containment operator - incase sensitive. Returns TRUE when the test value (right operand) exactly matches at least one of the values in the left operand. + Containment operator - case insensitive. Returns TRUE when the test value (right operand) exactly matches at least one of the values in the left operand. Containment operator - case sensitive. Returns TRUE only when the test value (right operand) exactly matches at least one of the values in the left operand. @@ -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 d5f51dc0218..3a7720616d1 100644 --- a/src/System.Management.Automation/security/Authenticode.cs +++ b/src/System.Management.Automation/security/Authenticode.cs @@ -1,27 +1,28 @@ -#pragma warning disable 1634, 1691 - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #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 { /// /// Defines the options that control what data is embedded in the - /// signature blob + /// signature blob. /// - /// public enum SigningOption { /// @@ -48,12 +49,14 @@ public enum SigningOption } /// - /// Helper functions for signature functionality + /// Helper functions for signature functionality. /// internal static class SignatureHelper { + private static Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); + /// - /// tracer for SignatureHelper + /// Tracer for SignatureHelper. /// [Dbg.TraceSource("SignatureHelper", "tracer for SignatureHelper")] @@ -62,36 +65,24 @@ internal static class SignatureHelper "tracer for SignatureHelper"); /// - /// Sign a file + /// Sign a file. /// - /// - /// option that controls what gets embedded in the signature blob - /// - /// name of file to sign - /// - /// signing cert - /// - /// URL of time stamping server - /// + /// Option that controls what gets embedded in the signature blob. + /// Name of file to sign. + /// Signing cert. + /// URL of time stamping server. /// The name of the hash - /// algorithm to use. - /// - /// Does not return a value - /// - /// + /// algorithm to use. + /// Does not return a value. /// /// Thrown if argument fileName or certificate is null. /// - /// - /// /// /// Thrown if /// -- argument fileName is empty OR /// -- the specified certificate is not suitable for /// signing code /// - /// - /// /// /// This exception can be thrown if any cryptographic error occurs. /// It is not possible to know exactly what went wrong. @@ -102,15 +93,9 @@ internal static class SignatureHelper /// -- certificate password mismatch /// -- etc /// - /// - /// /// /// Thrown if the file specified by argument fileName is not found /// - /// - /// - /// - [ArchitectureSensitive] internal static Signature SignFile(SigningOption option, string fileName, X509Certificate2 certificate, @@ -120,26 +105,27 @@ 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 (!String.IsNullOrEmpty(timeStampServerUrl)) + // 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( - "certificate", + nameof(certificate), Authenticode.TimeStampUrlRequired); } } // Validate that the hash algorithm is valid - if (!String.IsNullOrEmpty(hashAlgorithm)) + if (!string.IsNullOrEmpty(hashAlgorithm)) { IntPtr intptrAlgorithm = Marshal.StringToHGlobalUni(hashAlgorithm); @@ -147,13 +133,12 @@ internal static Signature SignFile(SigningOption option, intptrAlgorithm, 0); - // If we couldn't find an OID for the hash // algorithm, it was invalid. if (oidPtr == IntPtr.Zero) { throw PSTraceSource.NewArgumentException( - "certificate", + nameof(certificate), Authenticode.InvalidHashAlgorithm); } else @@ -168,12 +153,12 @@ internal static Signature SignFile(SigningOption option, if (!SecuritySupport.CertIsGoodForSigning(certificate)) { throw PSTraceSource.NewArgumentException( - "certificate", + nameof(certificate), Authenticode.CertNotGoodForSigning); } SecuritySupport.CheckIfFileExists(fileName); - //SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); + // SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); try { @@ -182,7 +167,7 @@ internal static Signature SignFile(SigningOption option, // It expects null, only. Instead, it randomly AVs if you // try. string timeStampServerUrlForCryptUI = null; - if (!String.IsNullOrEmpty(timeStampServerUrl)) + if (!string.IsNullOrEmpty(timeStampServerUrl)) { timeStampServerUrlForCryptUI = timeStampServerUrl; } @@ -207,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 enable 56523 +#pragma warning restore 56523 - if (si.pSignExtInfo != null) + if (si.pSignExtInfo != IntPtr.Zero) { Marshal.DestroyStructure(si.pSignExtInfo); Marshal.FreeCoTaskMem(si.pSignExtInfo); @@ -248,7 +233,7 @@ internal static Signature SignFile(SigningOption option, if (error == Win32Errors.NTE_BAD_ALGID) { throw PSTraceSource.NewArgumentException( - "certificate", + nameof(certificate), Authenticode.InvalidHashAlgorithm); } @@ -263,7 +248,7 @@ internal static Signature SignFile(SigningOption option, } else { - signature = new Signature(fileName, (DWORD)error); + signature = new Signature(fileName, (uint)error); } } finally @@ -276,44 +261,32 @@ internal static Signature SignFile(SigningOption option, } /// - /// Get signature on the specified file + /// Get signature on the specified file. /// - /// - /// name of file to check - /// - /// content of file to check - /// - /// Signature object - /// + /// Name of file to check. + /// Content of file to check. + /// Signature object. /// /// Thrown if argument fileName is empty. /// - /// - /// /// /// Thrown if argument fileName is null /// - /// - /// /// /// 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); @@ -322,160 +295,122 @@ 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\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.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); + + // SecurityUtils.CheckIfFileSmallerThan4Bytes(fileName); } try @@ -489,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) { @@ -504,90 +443,82 @@ private static Signature GetSignatureFromWinVerifyTrust(string fileName, string return signature; } - [ArchitectureSensitive] - private static DWORD GetWinTrustData(string fileName, string fileContent, - out NativeMethods.WINTRUST_DATA wtData) + private static uint GetWinTrustData( + string fileName, + byte[] fileContent, + out WinTrustMethods.WINTRUST_DATA wtData) { - DWORD dwResult = Win32Errors.E_FAIL; - IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero; - IntPtr wtdBuffer = IntPtr.Zero; - - Guid actionVerify = - new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); - - try + wtData = new() { - WINTRUST_ACTION_GENERIC_VERIFY_V2 = - Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify)); - Marshal.StructureToPtr(actionVerify, - WINTRUST_ACTION_GENERIC_VERIFY_V2, - false); + cbStruct = (uint)Marshal.SizeOf(), + dwUIChoice = WinTrustUIChoice.WTD_UI_NONE, + dwStateAction = WinTrustAction.WTD_STATEACTION_VERIFY, + }; - NativeMethods.WINTRUST_DATA wtd; - - 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 enable 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 enable 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); @@ -629,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 enable 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 fea05cfe09c..4892f7434e4 100644 --- a/src/System.Management.Automation/security/CatalogHelper.cs +++ b/src/System.Management.Automation/security/CatalogHelper.cs @@ -1,29 +1,23 @@ -#if !UNIX +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +#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 { /// - /// Defines the possible status when validating integrity of catalog + /// Defines the possible status when validating integrity of catalog. /// public enum CatalogValidationStatus { @@ -39,63 +33,62 @@ public enum CatalogValidationStatus } /// - /// Object returned by Catalog Cmdlets + /// Object returned by Catalog Cmdlets. /// public class CatalogInformation { /// - /// status of catalog + /// Status of catalog. /// public CatalogValidationStatus Status { get; set; } /// - /// Hash Algorithm used to calculate the hashes of files in Catalog + /// Hash Algorithm used to calculate the hashes of files in Catalog. /// public string HashAlgorithm { get; set; } /// - /// Dictionary mapping files relative paths to their hash values found from Catalog + /// Dictionary mapping files relative paths to their hash values found from Catalog. /// - public Dictionary CatalogItems { get; set; } + public Dictionary CatalogItems { get; set; } /// - /// Dictionary mapping files relative paths to their hash values + /// Dictionary mapping files relative paths to their hash values. /// - public Dictionary PathItems { get; set; } + public Dictionary PathItems { get; set; } /// - /// Signature for the catalog + /// Signature for the catalog. /// public Signature Signature { get; set; } } /// - /// Helper functions for Windows Catalog functionality + /// Helper functions for Windows Catalog functionality. /// 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; /// - /// Find out the Version of Catalog by reading its Meta data. We can have either version 1 or version 2 catalog + /// Find out the Version of Catalog by reading its Meta data. We can have either version 1 or version 2 catalog. /// - /// Handle to open catalog file - /// Version of the catalog - private static int GetCatalogVersion(IntPtr catalogHandle) + /// Handle to open catalog file. + /// Version of the catalog. + 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) { @@ -118,14 +111,15 @@ private static int GetCatalogVersion(IntPtr catalogHandle) ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null); _cmdlet.ThrowTerminatingError(errorRecord); } + return catalogVersion; } /// /// HashAlgorithm used by the Catalog. It is based on the version of Catalog. /// - /// Path of the output catalog file - /// Version of the catalog + /// Path of the output catalog file. + /// Version of the catalog. private static string GetCatalogHashAlgorithm(int catalogVersion) { string hashAlgorithm = string.Empty; @@ -148,23 +142,19 @@ private static string GetCatalogHashAlgorithm(int catalogVersion) ErrorRecord errorRecord = new ErrorRecord(exception, "UnKnownCatalogVersion", ErrorCategory.InvalidOperation, null); _cmdlet.ThrowTerminatingError(errorRecord); } + return hashAlgorithm; } /// - /// Generate the Catalog Definition File representing files and folders + /// Generate the Catalog Definition File representing files and folders. /// - /// - /// Path of expected output .cdf file - /// - /// Path of the output catalog file - /// - /// Path of the catalog definition file - /// - /// Version of catalog - /// - /// hash method used to generate hashes for the Catalog - /// HashSet for the relative Path for files in Catalog + /// Path of expected output .cdf file. + /// Path of the output catalog file. + /// Path of the catalog definition file. + /// Version of catalog. + /// Hash method used to generate hashes for the Catalog. + /// HashSet for the relative Path for files in Catalog. internal static string GenerateCDFFile(Collection Path, string catalogFilePath, string cdfFilePath, int catalogVersion, string hashAlgorithm) { HashSet relativePaths = new HashSet(); @@ -202,36 +192,36 @@ internal static string GenerateCDFFile(Collection Path, string catalogFi fileWriter.WriteLine(); fileWriter.WriteLine(cdfFilesContent); } + return cdfFilePath; } /// - /// Get file attribute (Relative path in our case) from catalog + /// Get file attribute (Relative path in our case) from catalog. /// - /// file to hash - /// directory information about file needed to calculate relative file path. - /// working set of relative paths of all files. - /// content to be added in CatalogHeader section of cdf File - /// content to be added in CatalogFiles section of cdf File - /// indicating the current no of catalog header level attributes - /// void + /// File to hash. + /// Directory information about file needed to calculate relative file path. + /// Working set of relative paths of all files. + /// Content to be added in CatalogHeader section of cdf File. + /// Content to be added in CatalogFiles section of cdf File. + /// Indicating the current no of catalog header level attributes. + /// Void. internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileToHash, DirectoryInfo dirInfo, ref HashSet relativePaths, ref string cdfHeaderContent, ref string cdfFilesContent, ref int catAttributeCount) { string relativePath = string.Empty; if (dirInfo != null) { - //Relative path of the file is the path inside the containing folder excluding folder Name - relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\'); + // Relative path of the file is the path inside the containing folder excluding folder Name + relativePath = fileToHash.FullName.AsSpan(dirInfo.FullName.Length).TrimStart('\\').ToString(); } else { 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; @@ -252,26 +242,37 @@ internal static void ProcessFileToBeAddedInCatalogDefinitionFile(FileInfo fileTo } } /// - /// Generate the Catalog file for Input Catalog Definition File + /// Generate the Catalog file for Input Catalog Definition File. /// - /// - /// Path to the Input .cdf file + /// 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) { @@ -282,73 +283,59 @@ 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); } } /// - /// To generate Catalog for the folder + /// To generate Catalog for the folder. /// - /// - /// Path to folder or File - /// Catalog File Path - /// Catalog File Path - /// Instance of cmdlet calling this method - /// true if able to generate .cat file or false + /// Path to folder or File. + /// Catalog File Path. + /// Catalog File Path. + /// Instance of cmdlet calling this method. + /// True if able to generate .cat file or false. internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection Path, string catalogFilePath, int catalogVersion) { _cmdlet = cmdlet; string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion); - if (!String.IsNullOrEmpty(hashAlgorithm)) + if (!string.IsNullOrEmpty(hashAlgorithm)) { // 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); @@ -372,19 +359,20 @@ internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection Pat File.Delete(cdfFilePath); } } + return null; } /// - /// Get file attribute (Relative path in our case) from catalog + /// Get file attribute (Relative path in our case) from catalog. /// - /// Pointer to current attribute of catalog member. - /// value of the attribute + /// Pointer to current attribute of catalog member. + /// Value of the attribute. 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 @@ -402,188 +390,187 @@ internal static string ProcessFilePathAttributeInCatalog(IntPtr memberAttrInfo) } /// - /// Make a hash for the file + /// Make a hash for the file. /// - /// - /// Path of the file - /// Used to calculate Hash - /// HashValue for the file + /// Path of the file. + /// Used to calculate Hash. + /// HashValue for the file. 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("-", ""); - } - 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); } + + hashValue = Convert.ToHexString(hashBytes); } - 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); - } + return hashValue; } /// - /// Make list of hashes for given Catalog File + /// Make list of hashes for given Catalog File. /// - /// Path to the folder having catalog file + /// Path to the folder having catalog file. /// - /// The version of input catalog we read from catalog meta data after opening it. - /// Dictionary mapping files relative paths to HashValues - internal static Dictionary GetHashesFromCatalog(string catalogFilePath, WildcardPattern[] excludedPatterns, out int catalogVersion) + /// The version of input catalog we read from catalog meta data after opening it. + /// 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); + 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; } /// - /// Process file in path for its relative paths + /// Process file in path for its relative paths. /// - /// relative path of file found in catalog - /// hash of file found in catalog. - /// skip file from validation if it matches these patterns - /// collection of hashes of catalog - /// void - internal static void ProcessCatalogFile(string relativePath, string fileHash, WildcardPattern[] excludedPatterns, ref Dictionary catalogHashes) + /// Relative path of file found in catalog. + /// Hash of file found in catalog. + /// Skip file from validation if it matches these patterns. + /// Collection of hashes of catalog. + /// Void. + internal static void ProcessCatalogFile(string relativePath, string fileHash, WildcardPattern[] excludedPatterns, ref Dictionary catalogHashes) { // Found the attribute we are looking for _cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileHashInCatalogItem, relativePath, fileHash)); @@ -601,23 +588,23 @@ internal static void ProcessCatalogFile(string relativePath, string fileHash, Wi } } /// - /// Process file in path for its relative paths + /// Process file in path for its relative paths. /// - /// file to hash - /// directory information about file needed to calculate relative file path. - /// Used to calculate Hash - /// skip file if it matches these patterns - /// collection of hashes of files - /// void - internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, string hashAlgorithm, WildcardPattern[] excludedPatterns, ref Dictionary fileHashes) + /// File to hash. + /// Directory information about file needed to calculate relative file path. + /// Used to calculate Hash. + /// Skip file if it matches these patterns. + /// Collection of hashes of files. + /// Void. + internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, string hashAlgorithm, WildcardPattern[] excludedPatterns, ref Dictionary fileHashes) { string relativePath = string.Empty; string exclude = string.Empty; if (dirInfo != null) { - //Relative path of the file is the path inside the containing folder excluding folder Name - relativePath = fileToHash.FullName.Substring(dirInfo.FullName.Length).TrimStart('\\'); + // Relative path of the file is the path inside the containing folder excluding folder Name + relativePath = fileToHash.FullName.AsSpan(dirInfo.FullName.Length).TrimStart('\\').ToString(); exclude = fileToHash.Name; } else @@ -635,9 +622,8 @@ internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, fileHash = CalculateFileHash(fileToHash.FullName, hashAlgorithm); } - if (!fileHashes.ContainsKey(relativePath)) + if (fileHashes.TryAdd(relativePath, fileHash)) { - fileHashes.Add(relativePath, fileHash); _cmdlet.WriteVerbose(StringUtil.Format(CatalogStrings.FoundFileInPath, relativePath, fileHash)); } else @@ -654,18 +640,17 @@ internal static void ProcessPathFile(FileInfo fileToHash, DirectoryInfo dirInfo, } /// - /// Generate the hashes of all the files in given folder + /// Generate the hashes of all the files in given folder. /// - /// - /// Path to folder or File - /// catalog file path it should be skipped when calculating the hashes - /// Used to calculate Hash + /// Path to folder or File. + /// Catalog file path it should be skipped when calculating the hashes. + /// Used to calculate Hash. /// - /// Dictionary mapping file relative paths to hashes. - internal static Dictionary CalculateHashesFromPath(Collection folderPaths, string catalogFilePath, string hashAlgorithm, WildcardPattern[] excludedPatterns) + /// Dictionary mapping file relative paths to hashes.. + internal static Dictionary CalculateHashesFromPath(Collection folderPaths, string catalogFilePath, string hashAlgorithm, WildcardPattern[] excludedPatterns) { // Create a HashTable of file Hashes - Dictionary fileHashes = new Dictionary(StringComparer.CurrentCultureIgnoreCase); + Dictionary fileHashes = new Dictionary(StringComparer.CurrentCultureIgnoreCase); foreach (string folderPath in folderPaths) { @@ -675,7 +660,7 @@ internal static Dictionary CalculateHashesFromPath(Collection CalculateHashesFromPath(Collection - /// Compare Dictionary objects + /// Compare Dictionary objects. /// - /// - /// Hashes extracted from Catalog - /// - /// Hashes created from folders path - /// - /// True if both collections are same - internal static bool CompareDictionaries(Dictionary catalogItems, Dictionary pathItems) + /// Hashes extracted from Catalog. + /// Hashes created from folders path. + /// True if both collections are same. + internal static bool CompareDictionaries(Dictionary catalogItems, Dictionary pathItems) { bool Status = true; 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(); - //Found extra hashes in Folder + // Found extra hashes in Folder if ((relativePathsNotInFolder.Count != 0) || (relativePathsNotInCatalog.Count != 0)) { Status = false; } - foreach (KeyValuePair item in catalogItems) + foreach (KeyValuePair item in catalogItems) { string catalogHashValue = (string)catalogItems[item.Key]; if (pathItems.ContainsKey(item.Key)) @@ -732,32 +715,32 @@ internal static bool CompareDictionaries(Dictionary catalogItems } } } + return Status; } /// - /// To Validate the Integrity of Catalog + /// To Validate the Integrity of Catalog. /// - /// - /// Folder for which catalog is created - /// File Name of the Catalog + /// Folder for which catalog is created. + /// File Name of the Catalog. /// - /// Instance of cmdlet calling this method - /// Information about Catalog - internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection catalogFolders, String catalogFilePath, WildcardPattern[] excludedPatterns) + /// Instance of cmdlet calling this method. + /// Information about Catalog. + internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection catalogFolders, string catalogFilePath, WildcardPattern[] excludedPatterns) { _cmdlet = cmdlet; int catalogVersion = 0; - Dictionary catalogHashes = GetHashesFromCatalog(catalogFilePath, excludedPatterns, out catalogVersion); + Dictionary catalogHashes = GetHashesFromCatalog(catalogFilePath, excludedPatterns, out catalogVersion); string hashAlgorithm = GetCatalogHashAlgorithm(catalogVersion); - if (!String.IsNullOrEmpty(hashAlgorithm)) + if (!string.IsNullOrEmpty(hashAlgorithm)) { - Dictionary fileHashes = CalculateHashesFromPath(catalogFolders, catalogFilePath, hashAlgorithm, excludedPatterns); + Dictionary fileHashes = CalculateHashesFromPath(catalogFolders, catalogFilePath, hashAlgorithm, excludedPatterns); CatalogInformation catalog = new CatalogInformation(); catalog.CatalogItems = catalogHashes; catalog.PathItems = fileHashes; bool status = CompareDictionaries(catalogHashes, fileHashes); - if (status == true) + if (status) { catalog.Status = CatalogValidationStatus.Valid; } @@ -765,19 +748,21 @@ internal static CatalogInformation ValidateCatalog(PSCmdlet cmdlet, Collection - /// Check if file meets the skip validation criteria + /// Check if file meets the skip validation criteria. /// /// /// - /// True if match is found else false + /// True if match is found else false. internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] excludedPatterns) { if (excludedPatterns != null) @@ -790,20 +775,26 @@ internal static bool CheckExcludedCriteria(string filename, WildcardPattern[] ex } } } + return false; } /// - /// Call back when error is thrown by catalog API's + /// 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) { case NativeConstants.CRYPTCAT_E_CDF_MEMBER_FILE_PATH: @@ -824,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 127adaa9cb4..1ea19691ae7 100644 --- a/src/System.Management.Automation/security/CredentialParameter.cs +++ b/src/System.Management.Automation/security/CredentialParameter.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 @@ -17,12 +16,10 @@ public sealed class CredentialAttribute : ArgumentTransformationAttribute /// /// Transforms the input data to an PSCredential. /// - /// /// /// The engine APIs for the context under which the transformation is being /// made. /// - /// /// /// If Null, the transformation prompts for both Username and Password /// If a string, the transformation uses the input for a username, and prompts @@ -40,7 +37,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input (engineIntrinsics.Host == null) || (engineIntrinsics.Host.UI == null)) { - throw PSTraceSource.NewArgumentNullException("engineIntrinsics"); + throw PSTraceSource.NewArgumentNullException(nameof(engineIntrinsics)); } if (inputData == null) @@ -72,7 +69,6 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input string caption = null; string prompt = null; - caption = CredentialAttributeStrings.CredentialAttribute_Prompt_Caption; prompt = CredentialAttributeStrings.CredentialAttribute_Prompt; @@ -81,7 +77,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input caption, prompt, userName, - ""); + string.Empty); } return cred; @@ -93,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 61b8e569918..7cbaf98d3a5 100644 --- a/src/System.Management.Automation/security/MshSignature.cs +++ b/src/System.Management.Automation/security/MshSignature.cs @@ -1,14 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. - -using Dbg = System.Management.Automation; +using System.ComponentModel; using System.Management.Automation.Internal; using System.Security.Cryptography.X509Certificates; -using System.ComponentModel; -using DWORD = System.UInt32; +using DWORD = System.UInt32; namespace System.Management.Automation { @@ -74,7 +71,7 @@ public enum SignatureStatus /// with the current system. /// Incompatible - }; + } /// /// Defines the valid types of signatures. @@ -95,7 +92,7 @@ public enum SignatureType /// The signature is a catalog signature. /// Catalog = 2 - }; + } /// /// Represents a digital signature on a signed @@ -107,13 +104,13 @@ public sealed class Signature private SignatureStatus _status = SignatureStatus.UnknownError; private DWORD _win32Error; private X509Certificate2 _signerCert; - private string _statusMessage = String.Empty; + private string _statusMessage = string.Empty; private X509Certificate2 _timeStamperCert; - //private DateTime signedOn = new DateTime(0); + // private DateTime signedOn = new DateTime(0); // 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. @@ -179,33 +176,30 @@ public string Path } /// - /// Returns the signature type of the signature + /// Returns the signature type of the signature. /// public SignatureType SignatureType { get; internal set; } /// - /// True if the item is signed as part of an operating system release + /// True if the item is signed as part of an operating system release. /// 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 /// /// Call this to create a validated time-stamped signature object. /// - /// - /// this signature is found in this file - /// - /// win32 error code - /// - /// cert of the signer - /// - /// cert of the time stamper - /// - /// constructed object - /// - /// - /// + /// This signature is found in this file. + /// Win32 error code. + /// Cert of the signer. + /// Cert of the time stamper. + /// Constructed object. internal Signature(string filePath, DWORD error, X509Certificate2 signer, @@ -223,15 +217,9 @@ internal Signature(string filePath, /// /// Call this to create a validated signature object. /// - /// - /// this signature is found in this file - /// - /// cert of the signer - /// - /// constructed object - /// - /// - /// + /// This signature is found in this file. + /// Cert of the signer. + /// Constructed object. internal Signature(string filePath, X509Certificate2 signer) { @@ -244,19 +232,12 @@ internal Signature(string filePath, /// /// Constructor for class Signature /// - /// Call this ctor when creating an invalid signature object + /// Call this ctor when creating an invalid signature object. /// - /// - /// this signature is found in this file - /// - /// win32 error code - /// - /// cert of the signer - /// - /// constructed object - /// - /// - /// + /// This signature is found in this file. + /// Win32 error code. + /// Cert of the signer. + /// Constructed object. internal Signature(string filePath, DWORD error, X509Certificate2 signer) @@ -270,17 +251,11 @@ internal Signature(string filePath, /// /// Constructor for class Signature /// - /// Call this ctor when creating an invalid signature object + /// Call this ctor when creating an invalid signature object. /// - /// - /// this signature is found in this file - /// - /// win32 error code - /// - /// constructed object - /// - /// - /// + /// This signature is found in this file. + /// Win32 error code. + /// Constructed object. internal Signature(string filePath, DWORD error) { Utils.CheckArgForNullOrEmpty(filePath, "filePath"); @@ -307,8 +282,10 @@ 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) { @@ -374,6 +351,7 @@ private static string GetSignatureStatusMessage(SignatureStatus status, { resourceString = MshSignature.MshSignature_Incompatible; } + arg = filePath; break; @@ -396,11 +374,12 @@ private static string GetSignatureStatusMessage(SignatureStatus status, resourceString = MshSignature.MshSignature_NotSupportedFileFormat; arg = System.IO.Path.GetExtension(filePath); - if (String.IsNullOrEmpty(arg)) + if (string.IsNullOrEmpty(arg)) { resourceString = MshSignature.MshSignature_NotSupportedFileFormat_NoExtension; arg = null; } + break; } @@ -418,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 ea30e395cf0..5ff5881bab4 100644 --- a/src/System.Management.Automation/security/SecureStringHelper.cs +++ b/src/System.Management.Automation/security/SecureStringHelper.cs @@ -1,28 +1,27 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System; -using System.IO; -using System.Security; -using System.Security.Cryptography; -using System.Runtime.InteropServices; +using System.Diagnostics; using System.Globalization; +using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Cryptography; using System.Text; namespace Microsoft.PowerShell { /// - /// helper class for secure string related functionality + /// Helper class for secure string related functionality. /// - /// internal static class SecureStringHelper { // Some random hex characters to identify the beginning of a // V2-exported SecureString. - internal static string SecureStringExportHeader = "76492d1116743f0423413b16050a5345"; + internal static readonly string SecureStringExportHeader = "76492d1116743f0423413b16050a5345"; /// /// Create a new SecureString based on the specified binary data. @@ -30,17 +29,14 @@ internal static class SecureStringHelper /// The binary data must be byte[] version of unicode char[], /// otherwise the results are unpredictable. /// - /// - /// input data - /// - /// a SecureString - /// - private static SecureString New(byte[] data) + /// Input data. + /// A SecureString . + internal static SecureString New(byte[] data) { if ((data.Length % 2) != 0) { // If the data is not an even length, they supplied an invalid key - String error = Serialization.InvalidKey; + string error = Serialization.InvalidKey; throw new PSArgumentException(error); } @@ -68,14 +64,10 @@ private static SecureString New(byte[] data) } /// - /// get the contents of a SecureString as byte[] + /// Get the contents of a SecureString as byte[] /// - /// - /// input string - /// - /// contents of s (char[]) converted to byte[] - /// - [ArchitectureSensitive] + /// Input string. + /// Contents of s (char[]) converted to byte[]. internal static byte[] GetData(SecureString s) { // @@ -107,11 +99,8 @@ internal static byte[] GetData(SecureString s) /// method can be changed to use a better encoding /// such as base64. /// - /// - /// binary data to encode - /// - /// a string representing encoded data - /// + /// Binary data to encode. + /// A string representing encoded data. internal static string ByteArrayToString(byte[] data) { StringBuilder sb = new StringBuilder(); @@ -128,11 +117,8 @@ internal static string ByteArrayToString(byte[] data) /// Convert a string obtained using ByteArrayToString() /// back to byte[] format. /// - /// - /// encoded input string - /// - /// bin data as byte[] - /// + /// Encoded input string. + /// Bin data as byte[]. internal static byte[] ByteArrayFromString(string s) { // @@ -145,7 +131,7 @@ internal static byte[] ByteArrayFromString(string s) { for (int i = 0; i < dataLen; i++) { - data[i] = byte.Parse(s.Substring(2 * i, 2), + data[i] = byte.Parse(s.AsSpan(2 * i, 2), NumberStyles.AllowHexSpecifier, System.Globalization.CultureInfo.InvariantCulture); } @@ -155,29 +141,31 @@ internal static byte[] ByteArrayFromString(string s) } /// - /// return contents of the SecureString after encrypting - /// using DPAPI and encoding the encrypted blob as a string + /// Return contents of the SecureString after encrypting + /// using DPAPI and encoding the encrypted blob as a string. /// - /// - /// SecureString to protect - /// - /// a string (see summary) - /// + /// SecureString to protect. + /// A string (see summary) . internal static string Protect(SecureString input) { Utils.CheckSecureStringArg(input, "input"); - string output = ""; + string output = string.Empty; byte[] data = null; byte[] protectedData = null; data = GetData(input); +#if UNIX + // DPAPI doesn't exist on UNIX so we simply use the string as a byte-array + protectedData = data; +#else protectedData = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser); for (int i = 0; i < data.Length; i++) { data[i] = 0; } +#endif output = ByteArrayToString(protectedData); @@ -190,17 +178,14 @@ internal static string Protect(SecureString input) /// /// The string must be obtained earlier by a call to Protect() /// - /// - /// encrypted string - /// - /// SecureString - /// + /// Encrypted string. + /// SecureString . internal static SecureString Unprotect(string input) { Utils.CheckArgForNullOrEmpty(input, "input"); if ((input.Length % 2) != 0) { - throw PSTraceSource.NewArgumentException("input", Serialization.InvalidEncryptedString, input); + throw PSTraceSource.NewArgumentException(nameof(input), Serialization.InvalidEncryptedString, input); } byte[] data = null; @@ -209,31 +194,28 @@ internal static SecureString Unprotect(string input) protectedData = ByteArrayFromString(input); +#if UNIX + // DPAPI isn't supported in UNIX, so we just translate the byte-array back to a string + data = protectedData; +#else data = ProtectedData.Unprotect(protectedData, null, DataProtectionScope.CurrentUser); +#endif s = New(data); return s; } /// - /// return contents of the SecureString after encrypting - /// using the specified key and encoding the encrypted blob as a string + /// Return contents of the SecureString after encrypting + /// using the specified key and encoding the encrypted blob as a string. /// - /// - /// input string to encrypt - /// - /// encryption key - /// - /// a string (see summary) - /// - /// - /// + /// Input string to encrypt. + /// Encryption key. + /// A string (see summary). internal static EncryptionResult Encrypt(SecureString input, SecureString key) { - EncryptionResult output = null; - // // get clear text key from the SecureString key // @@ -242,29 +224,23 @@ 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); + } } /// - /// return contents of the SecureString after encrypting - /// using the specified key and encoding the encrypted blob as a string + /// Return contents of the SecureString after encrypting + /// using the specified key and encoding the encrypted blob as a string. /// - /// - /// input string to encrypt - /// - /// encryption key - /// - /// a string (see summary) - /// - /// - /// + /// Input string to encrypt. + /// Encryption key. + /// A string (see summary). internal static EncryptionResult Encrypt(SecureString input, byte[] key) { return Encrypt(input, key, null); @@ -275,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); + } } } @@ -326,19 +297,12 @@ internal static EncryptionResult Encrypt(SecureString input, byte[] key, byte[] /// /// The string must be obtained earlier by a call to Encrypt() /// - /// - /// encrypted string - /// - /// encryption key - /// - /// encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV - /// - /// SecureString - /// + /// Encrypted string. + /// Encryption key. + /// Encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV. + /// SecureString . internal static SecureString Decrypt(string input, SecureString key, byte[] IV) { - SecureString output = null; - // // get clear text key from the SecureString key // @@ -347,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); + } } /// @@ -363,67 +327,70 @@ internal static SecureString Decrypt(string input, SecureString key, byte[] IV) /// /// The string must be obtained earlier by a call to Encrypt() /// - /// - /// encrypted string - /// - /// encryption key - /// - /// encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV - /// - /// SecureString - /// + /// Encrypted string. + /// Encryption key. + /// Encryption initialization vector. If this is set to null, the method uses internally computed strong random number as IV. + /// SecureString . 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 } /// /// Helper class to return encryption results, and the IV used to - /// do the encryption + /// do the encryption. /// - /// internal class EncryptionResult { internal EncryptionResult(string encrypted, string IV) @@ -433,17 +400,17 @@ internal EncryptionResult(string encrypted, string IV) } /// - /// Gets the encrypted data + /// Gets the encrypted data. /// - internal String EncryptedData { get; } + internal string EncryptedData { get; } /// - /// Gets the IV used to encrypt the data + /// Gets the IV used to encrypt the data. /// - internal String IV { get; } + internal string IV { get; } } -#if CORECLR +#if !UNIX // The DPAPIs implemented in this section are temporary workaround. // CoreCLR team will bring 'ProtectedData' type to Project K eventually. @@ -459,12 +426,11 @@ internal enum DataProtectionScope internal static class ProtectedData { /// - /// Protect + /// Protect. /// public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtectionScope scope) { - if (userData == null) - throw new ArgumentNullException("userData"); + ArgumentNullException.ThrowIfNull(userData); GCHandle pbDataIn = new GCHandle(); GCHandle pOptionalEntropy = new GCHandle(); @@ -483,18 +449,20 @@ public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtec entropy.cbData = (uint)optionalEntropy.Length; entropy.pbData = pOptionalEntropy.AddrOfPinnedObject(); } + uint dwFlags = CAPI.CRYPTPROTECT_UI_FORBIDDEN; if (scope == DataProtectionScope.LocalMachine) dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE; unsafe { - if (!CAPI.CryptProtectData(new IntPtr(&dataIn), - String.Empty, - new IntPtr(&entropy), - IntPtr.Zero, - IntPtr.Zero, - dwFlags, - new IntPtr(&blob))) + if (!CAPI.CryptProtectData( + pDataIn: new IntPtr(&dataIn), + szDataDescr: string.Empty, + pOptionalEntropy: new IntPtr(&entropy), + pvReserved: IntPtr.Zero, + pPromptStruct: IntPtr.Zero, + dwFlags: dwFlags, + pDataBlob: new IntPtr(&blob))) { int lastWin32Error = Marshal.GetLastWin32Error(); @@ -515,7 +483,9 @@ public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtec // In some cases, the API would fail due to OOM but simply return a null pointer. if (blob.pbData == IntPtr.Zero) + { throw new OutOfMemoryException(); + } byte[] encryptedData = new byte[(int)blob.cbData]; Marshal.Copy(blob.pbData, encryptedData, 0, encryptedData.Length); @@ -525,9 +495,13 @@ public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtec finally { if (pbDataIn.IsAllocated) + { pbDataIn.Free(); + } if (pOptionalEntropy.IsAllocated) + { pOptionalEntropy.Free(); + } if (blob.pbData != IntPtr.Zero) { CAPI.ZeroMemory(blob.pbData, blob.cbData); @@ -537,12 +511,11 @@ public static byte[] Protect(byte[] userData, byte[] optionalEntropy, DataProtec } /// - /// Unprotect + /// Unprotect. /// public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope) { - if (encryptedData == null) - throw new ArgumentNullException("encryptedData"); + ArgumentNullException.ThrowIfNull(encryptedData); GCHandle pbDataIn = new GCHandle(); GCHandle pOptionalEntropy = new GCHandle(); @@ -561,24 +534,33 @@ public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, Dat entropy.cbData = (uint)optionalEntropy.Length; entropy.pbData = pOptionalEntropy.AddrOfPinnedObject(); } + uint dwFlags = CAPI.CRYPTPROTECT_UI_FORBIDDEN; if (scope == DataProtectionScope.LocalMachine) + { dwFlags |= CAPI.CRYPTPROTECT_LOCAL_MACHINE; + } + unsafe { - if (!CAPI.CryptUnprotectData(new IntPtr(&dataIn), - IntPtr.Zero, - new IntPtr(&entropy), - IntPtr.Zero, - IntPtr.Zero, - dwFlags, - new IntPtr(&userData))) + if (!CAPI.CryptUnprotectData( + pDataIn: new IntPtr(&dataIn), + ppszDataDescr: IntPtr.Zero, + pOptionalEntropy: new IntPtr(&entropy), + pvReserved: IntPtr.Zero, + pPromptStruct: IntPtr.Zero, + dwFlags: dwFlags, + pDataBlob: new IntPtr(&userData))) + { throw new CryptographicException(Marshal.GetLastWin32Error()); + } } // In some cases, the API would fail due to OOM but simply return a null pointer. if (userData.pbData == IntPtr.Zero) + { throw new OutOfMemoryException(); + } byte[] data = new byte[(int)userData.cbData]; Marshal.Copy(userData.pbData, data, 0, data.Length); @@ -588,9 +570,13 @@ public static byte[] Unprotect(byte[] encryptedData, byte[] optionalEntropy, Dat finally { if (pbDataIn.IsAllocated) + { pbDataIn.Free(); + } if (pOptionalEntropy.IsAllocated) + { pOptionalEntropy.Free(); + } if (userData.pbData != IntPtr.Zero) { CAPI.ZeroMemory(userData.pbData, userData.cbData); @@ -608,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; @@ -623,23 +609,25 @@ 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)] @@ -653,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 19dcfdbe13b..137daecc5b4 100644 --- a/src/System.Management.Automation/security/SecurityManager.cs +++ b/src/System.Management.Automation/security/SecurityManager.cs @@ -1,24 +1,25 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using Dbg = System.Management.Automation; using System; -using System.Management.Automation; -using System.Management.Automation.Security; -using System.Management.Automation.Internal; -using System.IO; using System.Collections.ObjectModel; +using System.IO; +using System.Management.Automation; using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Security; using System.Security; using System.Security.Cryptography.X509Certificates; +using System.Text; + +using Dbg = System.Management.Automation; 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,18 +40,17 @@ 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 - /// + /// Bypass - No files must be signed, and internet origin is not verified. /// public sealed class PSAuthorizationManager : AuthorizationManager { @@ -68,8 +68,8 @@ internal enum RunPromptDecision // execution policy that dictates what can run in msh private ExecutionPolicy _executionPolicy; - //shellId supplied by runspace configuration - private string _shellId; + // shellId supplied by runspace configuration + private readonly string _shellId; /// /// Initializes a new instance of the PSAuthorizationManager @@ -84,8 +84,9 @@ public PSAuthorizationManager(string shellId) { if (string.IsNullOrEmpty(shellId)) { - throw PSTraceSource.NewArgumentNullException("shellId"); + throw PSTraceSource.NewArgumentNullException(nameof(shellId)); } + _shellId = shellId; } @@ -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; -#if !CORECLR // 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); @@ -182,7 +182,14 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r return false; } } -#endif + + // 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 @@ -195,7 +202,7 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r if (!IsLocalFile(fi.FullName)) { // Get the signature of the file. - if (String.IsNullOrEmpty(script.ScriptContents)) + if (string.IsNullOrEmpty(script.ScriptContents)) { reasonMessage = StringUtil.Format(Authenticode.Reason_FileContentUnavailable, path); reason = new UnauthorizedAccessException(reasonMessage); @@ -266,7 +273,7 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r // make it so. // Get the signature of the file. - if (String.IsNullOrEmpty(script.ScriptContents)) + if (string.IsNullOrEmpty(script.ScriptContents)) { reasonMessage = StringUtil.Format(Authenticode.Reason_FileContentUnavailable, path); reason = new UnauthorizedAccessException(reasonMessage); @@ -321,12 +328,13 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r // But accept mshxml files from publishers that we // trust, or files in the system protected directories bool reasonSet = false; - if (String.Equals(fi.Extension, ".ps1xml", StringComparison.OrdinalIgnoreCase)) + 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; @@ -433,14 +445,19 @@ 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 (string.Equals(trustedCertificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) + { + 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; @@ -452,7 +469,7 @@ private bool IsUntrustedPublisher(Signature signature, string file) foreach (X509Certificate2 trustedCertificate in trustedPublishers.Certificates) { - if (String.Equals(trustedCertificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(trustedCertificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) return true; } @@ -460,10 +477,10 @@ private bool IsUntrustedPublisher(Signature signature, string file) } /// - /// Trust a publisher by adding it to the "Trusted Publishers" store + /// 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 /// @@ -537,7 +578,6 @@ private Signature GetSignatureWithEncodingRetry(string path, ExternalScriptInfo /// class summary for an overview of the semantics enforced by this /// authorization manager. /// - /// /// /// The command to be run. /// @@ -551,20 +591,16 @@ private Signature GetSignatureWithEncodingRetry(string path, ExternalScriptInfo /// If access is denied, this parameter provides a specialized /// Exception as the reason. /// - /// /// /// True if the command should be run. False otherwise. /// - /// /// /// CommandInfo is invalid. This may occur if /// commandInfo.Name is null or empty. /// - /// /// /// CommandInfo is null. /// - /// /// /// The file specified by commandInfo.Path is not found. /// @@ -596,10 +632,8 @@ protected internal override bool ShouldRun(CommandInfo commandInfo, allowRun = true; break; - case CommandTypes.Function: case CommandTypes.Filter: - case CommandTypes.Workflow: case CommandTypes.Configuration: // // we do not check functions/filters. @@ -617,18 +651,25 @@ 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; case CommandTypes.Application: @@ -642,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) { @@ -703,7 +744,6 @@ private RunPromptDecision AuthenticodePrompt(string path, break; - // // if the publisher is not trusted, we prompt and // ask the user if s/he wants to allow it to run @@ -716,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)) { @@ -746,7 +786,7 @@ private RunPromptDecision RemoteFilePrompt(string path, PSHost host) } } - private Collection GetAuthenticodePromptChoices() + private static Collection GetAuthenticodePromptChoices() { Collection choices = new Collection(); @@ -759,7 +799,6 @@ private Collection GetAuthenticodePromptChoices() string alwaysRun = Authenticode.Choice_AlwaysRun; string alwaysRunHelp = Authenticode.Choice_AlwaysRun_Help; - choices.Add(new ChoiceDescription(neverRun, neverRunHelp)); choices.Add(new ChoiceDescription(doNotRun, doNotRunHelp)); choices.Add(new ChoiceDescription(runOnce, runOnceHelp)); @@ -768,7 +807,7 @@ private Collection GetAuthenticodePromptChoices() return choices; } - private Collection GetRemoteFilePromptChoices() + private static Collection GetRemoteFilePromptChoices() { Collection choices = new Collection(); @@ -779,7 +818,6 @@ private Collection GetRemoteFilePromptChoices() string suspend = Authenticode.Choice_Suspend; string suspendHelp = Authenticode.Choice_Suspend_Help; - choices.Add(new ChoiceDescription(doNotRun, doNotRunHelp)); choices.Add(new ChoiceDescription(runOnce, runOnceHelp)); choices.Add(new ChoiceDescription(suspend, suspendHelp)); @@ -788,5 +826,3 @@ private Collection GetRemoteFilePromptChoices() } } } - - diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 625590e74df..dc6d048c5b1 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1,26 +1,26 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56523 +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.PowerShell; -using Microsoft.PowerShell.Commands; -using Microsoft.Win32; +using System.Globalization; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; +using System.Management.Automation.Security; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Globalization; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Runtime.InteropServices; +using Microsoft.PowerShell; +using Microsoft.PowerShell.Commands; + using DWORD = System.UInt32; namespace Microsoft.PowerShell @@ -28,28 +28,27 @@ namespace Microsoft.PowerShell /// /// Defines the different Execution Policies supported by the /// PSAuthorizationManager class. - /// /// 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, @@ -64,14 +63,13 @@ public enum ExecutionPolicy /// Default - The most restrictive policy available. /// Default = Restricted - }; + } /// /// Defines the available configuration scopes for an execution /// policy. They are in the following priority, with successive /// elements overriding the items that precede them: - /// LocalMachine -> CurrentUser -> Runspace - /// + /// LocalMachine -> CurrentUser -> Runspace. /// public enum ExecutionPolicyScope { @@ -100,8 +98,7 @@ public enum ExecutionPolicyScope namespace System.Management.Automation.Internal { /// - /// The SAFER policy associated with this file - /// + /// The SAFER policy associated with this file. /// internal enum SaferPolicy { @@ -116,7 +113,7 @@ internal enum SaferPolicy } /// - /// Security Support APIs + /// Security Support APIs. /// public static class SecuritySupport { @@ -146,31 +143,35 @@ internal static void SetExecutionPolicy(ExecutionPolicyScope scope, ExecutionPol switch (policy) { case ExecutionPolicy.Restricted: - executionPolicy = "Restricted"; break; + executionPolicy = "Restricted"; + break; case ExecutionPolicy.AllSigned: - executionPolicy = "AllSigned"; break; + executionPolicy = "AllSigned"; + break; case ExecutionPolicy.RemoteSigned: - executionPolicy = "RemoteSigned"; break; + executionPolicy = "RemoteSigned"; + break; case ExecutionPolicy.Unrestricted: - executionPolicy = "Unrestricted"; break; + executionPolicy = "Unrestricted"; + break; case ExecutionPolicy.Bypass: - executionPolicy = "Bypass"; break; + executionPolicy = "Bypass"; + break; } // Set the execution policy switch (scope) { case ExecutionPolicyScope.Process: - { + if (policy == ExecutionPolicy.Undefined) executionPolicy = null; Environment.SetEnvironmentVariable("PSExecutionPolicyPreference", executionPolicy); break; - } case ExecutionPolicyScope.CurrentUser: - { + // They want to remove it if (policy == ExecutionPolicy.Undefined) { @@ -180,22 +181,22 @@ internal static void SetExecutionPolicy(ExecutionPolicyScope scope, ExecutionPol { PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.CurrentUser, shellId, executionPolicy); } + break; - } case ExecutionPolicyScope.LocalMachine: - { + // They want to remove it if (policy == ExecutionPolicy.Undefined) { - PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.SystemWide, shellId); + PowerShellConfig.Instance.RemoveExecutionPolicy(ConfigScope.AllUsers, shellId); } else { - PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.SystemWide, shellId, executionPolicy); + PowerShellConfig.Instance.SetExecutionPolicy(ConfigScope.AllUsers, shellId, executionPolicy); } + break; - } } #endif } @@ -212,12 +213,11 @@ internal static ExecutionPolicy GetExecutionPolicy(string shellId) return ExecutionPolicy.Restricted; } - private static bool? _hasGpScriptParent; /// /// A value indicating that the current process was launched by GPScript.exe - /// Used to determine execution policy when group policies are in effect + /// Used to determine execution policy when group policies are in effect. /// /// /// This is somewhat expensive to determine and does not change within the lifetime of the current process @@ -230,6 +230,7 @@ private static bool HasGpScriptParent { _hasGpScriptParent = IsCurrentProcessLaunchedByGpScript(); } + return _hasGpScriptParent.Value; } } @@ -246,8 +247,8 @@ private static bool IsCurrentProcessLaunchedByGpScript() { while (currentProcess != null) { - if (String.Equals(gpScriptPath, - PsUtils.GetMainModule(currentProcess).FileName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(gpScriptPath, + currentProcess.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) { foundGpScriptParent = true; break; @@ -272,6 +273,7 @@ private static bool IsCurrentProcessLaunchedByGpScript() // our goal here is to not have the Group Policy execution policy // affect logon / logoff scripts. } + return foundGpScriptParent; } @@ -286,7 +288,7 @@ internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPoli { string policy = Environment.GetEnvironmentVariable("PSExecutionPolicyPreference"); - if (!String.IsNullOrEmpty(policy)) + if (!string.IsNullOrEmpty(policy)) return ParseExecutionPolicy(policy); else return ExecutionPolicy.Undefined; @@ -297,7 +299,7 @@ internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPoli { string policy = GetLocalPreferenceValue(shellId, scope); - if (!String.IsNullOrEmpty(policy)) + if (!string.IsNullOrEmpty(policy)) return ParseExecutionPolicy(policy); else return ExecutionPolicy.Undefined; @@ -313,10 +315,11 @@ internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPoli // Be sure we aren't being called by Group Policy // itself. A group policy should never block a logon / // logoff script. - if (String.IsNullOrEmpty(groupPolicyPreference) || HasGpScriptParent) + if (string.IsNullOrEmpty(groupPolicyPreference) || HasGpScriptParent) { return ExecutionPolicy.Undefined; } + return ParseExecutionPolicy(groupPolicyPreference); } } @@ -327,27 +330,27 @@ internal static ExecutionPolicy GetExecutionPolicy(string shellId, ExecutionPoli internal static ExecutionPolicy ParseExecutionPolicy(string policy) { - if (String.Equals(policy, "Bypass", + if (string.Equals(policy, "Bypass", StringComparison.OrdinalIgnoreCase)) { return ExecutionPolicy.Bypass; } - else if (String.Equals(policy, "Unrestricted", + else if (string.Equals(policy, "Unrestricted", StringComparison.OrdinalIgnoreCase)) { return ExecutionPolicy.Unrestricted; } - else if (String.Equals(policy, "RemoteSigned", + else if (string.Equals(policy, "RemoteSigned", StringComparison.OrdinalIgnoreCase)) { return ExecutionPolicy.RemoteSigned; } - else if (String.Equals(policy, "AllSigned", + else if (string.Equals(policy, "AllSigned", StringComparison.OrdinalIgnoreCase)) { return ExecutionPolicy.AllSigned; } - else if (String.Equals(policy, "Restricted", + else if (string.Equals(policy, "Restricted", StringComparison.OrdinalIgnoreCase)) { return ExecutionPolicy.Restricted; @@ -362,23 +365,29 @@ internal static string GetExecutionPolicy(ExecutionPolicy policy) { switch (policy) { - case ExecutionPolicy.Bypass: return "Bypass"; - case ExecutionPolicy.Unrestricted: return "Unrestricted"; - case ExecutionPolicy.RemoteSigned: return "RemoteSigned"; - case ExecutionPolicy.AllSigned: return "AllSigned"; - case ExecutionPolicy.Restricted: return "Restricted"; - default: return "Restricted"; + case ExecutionPolicy.Bypass: + return "Bypass"; + case ExecutionPolicy.Unrestricted: + return "Unrestricted"; + case ExecutionPolicy.RemoteSigned: + return "RemoteSigned"; + case ExecutionPolicy.AllSigned: + return "AllSigned"; + case ExecutionPolicy.Restricted: + return "Restricted"; + default: + return "Restricted"; } } /// - /// Returns true if file has product binary signature + /// Returns true if file has product binary signature. /// - /// Name of file to check - /// True when file has product binary signature + /// Name of file to check. + /// True when file has product binary signature. public static bool IsProductBinary(string file) { - if (String.IsNullOrEmpty(file) || (!IO.File.Exists(file))) + if (string.IsNullOrEmpty(file) || (!IO.File.Exists(file))) { return false; } @@ -403,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. @@ -418,19 +427,84 @@ public static bool IsProductBinary(string file) #endif } -#if !CORECLR /// - /// Get the pass / fail result of calling the SAFER API + /// Returns the value of the Execution Policy as retrieved + /// from group policy. + /// + /// NULL if it is not defined at this level. + private static string GetGroupPolicyValue(string shellId, ExecutionPolicyScope scope) + { + ConfigScope[] scopeKey = null; + + switch (scope) + { + case ExecutionPolicyScope.MachinePolicy: + scopeKey = Utils.SystemWideOnlyConfig; + break; + + case ExecutionPolicyScope.UserPolicy: + scopeKey = Utils.CurrentUserOnlyConfig; + break; + } + + var scriptExecutionSetting = Utils.GetPolicySetting(scopeKey); + if (scriptExecutionSetting != null) + { + if (scriptExecutionSetting.EnableScripts == false) + { + // Script execution is explicitly disabled + return "Restricted"; + } + else if (scriptExecutionSetting.EnableScripts == true) + { + // Script execution is explicitly enabled + return scriptExecutionSetting.ExecutionPolicy; + } + } + + return null; + } + + /// + /// Returns the value of the Execution Policy as retrieved + /// from the local preference. /// - /// - /// The path to the file in question + /// NULL if it is not defined at this level. + private static string GetLocalPreferenceValue(string shellId, ExecutionPolicyScope scope) + { + switch (scope) + { + // 1: Look up the current-user preference + case ExecutionPolicyScope.CurrentUser: + return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.CurrentUser, shellId); + + // 2: Look up the system-wide preference + case ExecutionPolicyScope.LocalMachine: + return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.AllUsers, shellId); + } + + return null; + } + + #endregion execution policy + + private static bool _saferIdentifyLevelApiSupported = true; + + /// + /// Get the pass / fail result of calling the SAFER API. + /// + /// 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) { SaferPolicy status = SaferPolicy.Allowed; + if (!_saferIdentifyLevelApiSupported) + { + return status; + } + SAFER_CODE_PROPERTIES codeProperties = new SAFER_CODE_PROPERTIES(); IntPtr hAuthzLevel; @@ -497,83 +571,25 @@ internal static SaferPolicy GetSaferPolicy(string path, SafeHandle handle) } else { - throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); - } - - return status; - } -#endif - /// - /// Returns the value of the Execution Policy as retrieved - /// from group policy. - /// - /// NULL if it is not defined at this level - private static string GetGroupPolicyValue(string shellId, ExecutionPolicyScope scope) - { - ConfigScope[] scopeKey = null; - - switch (scope) - { - case ExecutionPolicyScope.MachinePolicy: - scopeKey = Utils.SystemWideOnlyConfig; - break; - - case ExecutionPolicyScope.UserPolicy: - scopeKey = Utils.CurrentUserOnlyConfig; - break; - } - - var scriptExecutionSetting = Utils.GetPolicySetting(scopeKey); - if (scriptExecutionSetting != null) - { - if (scriptExecutionSetting.EnableScripts == false) + int lastError = Marshal.GetLastWin32Error(); + if (lastError == NativeConstants.FUNCTION_NOT_SUPPORTED) { - // Script execution is explicitly disabled - return "Restricted"; + _saferIdentifyLevelApiSupported = false; } - else if (scriptExecutionSetting.EnableScripts == true) + else { - // Script execution is explicitly enabled - return scriptExecutionSetting.ExecutionPolicy; + throw new System.ComponentModel.Win32Exception(lastError); } } - return null; - } - - /// - /// Returns the value of the Execution Policy as retrieved - /// from the local preference. - /// - /// NULL if it is not defined at this level - private static string GetLocalPreferenceValue(string shellId, ExecutionPolicyScope scope) - { - switch (scope) - { - // 1: Look up the current-user preference - case ExecutionPolicyScope.CurrentUser: - return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.CurrentUser, shellId); - - // 2: Look up the system-wide preference - case ExecutionPolicyScope.LocalMachine: - return PowerShellConfig.Instance.GetExecutionPolicy(ConfigScope.SystemWide, shellId); - } - - return null; + return status; } -#endregion execution policy - /// - /// throw if file does not exist + /// Throw if file does not exist. /// - /// - /// path to file - /// - /// Does not return a value - /// - /// - /// + /// Path to file. + /// Does not return a value. internal static void CheckIfFileExists(string filePath) { if (!File.Exists(filePath)) @@ -583,19 +599,14 @@ internal static void CheckIfFileExists(string filePath) } /// - /// check to see if the specified cert is suitable to be - /// used as a code signing cert + /// Check to see if the specified cert is suitable to be + /// used as a code signing cert. /// - /// - /// certificate object - /// - /// true on success, false otherwise - /// - /// - /// + /// Certificate object. + /// True on success, false otherwise. internal static bool CertIsGoodForSigning(X509Certificate2 c) { - if (!CertHasPrivatekey(c)) + if (!c.HasPrivateKey) { return false; } @@ -604,17 +615,12 @@ internal static bool CertIsGoodForSigning(X509Certificate2 c) } /// - /// check to see if the specified cert is suitable to be + /// Check to see if the specified cert is suitable to be /// used as an encryption cert for PKI encryption. Note /// that this cert doesn't require the private key. /// - /// - /// certificate object - /// - /// true on success, false otherwise - /// - /// - /// + /// Certificate object. + /// True on success, false otherwise. internal static bool CertIsGoodForEncryption(X509Certificate2 c) { return ( @@ -623,18 +629,33 @@ internal static bool CertIsGoodForEncryption(X509Certificate2 c) CertHasKeyUsage(c, X509KeyUsageFlags.KeyEncipherment))); } - private static bool CertHasOid(X509Certificate2 c, string oid) + /// + /// Check to see if the specified cert is expiring by the time. + /// + /// Certificate object. + /// Certificate expire time. + /// True on success, false otherwise. + internal static bool CertExpiresByTime(X509Certificate2 c, DateTime expiring) { - Collection ekus = GetCertEKU(c); + return c.NotAfter < expiring; + } - foreach (string testOid in ekus) + private static bool CertHasOid(X509Certificate2 c, string oid) + { + foreach (var extension in c.Extensions) { - if (testOid == oid) + if (extension is X509EnhancedKeyUsageExtension ext) { - return true; + foreach (Oid ekuOid in ext.EnhancedKeyUsages) + { + if (ekuOid.Value == oid) + { + return true; + } + } + break; } } - return false; } @@ -642,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) { @@ -652,36 +672,14 @@ private static bool CertHasKeyUsage(X509Certificate2 c, X509KeyUsageFlags keyUsa break; } } - return false; } /// - /// check if the specified cert has a private key in it - /// - /// - /// certificate object - /// - /// true on success, false otherwise - /// - /// - /// - internal static bool CertHasPrivatekey(X509Certificate2 cert) - { - return cert.HasPrivateKey; - } - - /// - /// Get the EKUs of a cert + /// Get the EKUs of a cert. /// - /// - /// certificate object - /// - /// a collection of cert eku strings - /// - /// - /// - [ArchitectureSensitive] + /// Certificate object. + /// A collection of cert eku strings. internal static Collection GetCertEKU(X509Certificate2 cert) { Collection ekus = new Collection(); @@ -703,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; @@ -735,15 +732,10 @@ internal static Collection GetCertEKU(X509Certificate2 cert) } /// - /// convert an int to a DWORD + /// Convert an int to a DWORD. /// - /// - /// signed int number - /// - /// DWORD - /// - /// - /// + /// Signed int number. + /// DWORD. internal static DWORD GetDWORDFromInt(int n) { UInt32 result = BitConverter.ToUInt32(BitConverter.GetBytes(n), 0); @@ -751,15 +743,10 @@ internal static DWORD GetDWORDFromInt(int n) } /// - /// convert a DWORD to int + /// Convert a DWORD to int. /// - /// - /// number - /// - /// int - /// - /// - /// + /// Number. + /// Int. internal static int GetIntFromDWORD(DWORD n) { Int64 n64 = n - 0x100000000L; @@ -768,7 +755,7 @@ internal static int GetIntFromDWORD(DWORD n) } /// - /// information used for filtering a set of certs + /// Information used for filtering a set of certs. /// internal sealed class CertificateFilterInfo { @@ -777,165 +764,58 @@ internal CertificateFilterInfo() } /// - /// purpose of a certificate + /// Gets or sets purpose of a certificate. /// internal CertificatePurpose Purpose { - get { return _purpose; } - set { _purpose = value; } - } + get; + set; + } = CertificatePurpose.NotSpecified; /// - /// SSL Server Authentication + /// Gets or sets SSL Server Authentication. /// internal bool SSLServerAuthentication { - get { return _sslServerAuthentication; } - set { _sslServerAuthentication = value; } - } + get; - /// - /// DNS name of a certificate - /// - internal string DnsName - { - set { _dnsName = value; } + set; } /// - /// EKU OID list of a certificate + /// Gets or sets DNS name of a certificate. /// - internal string[] Eku + internal WildcardPattern DnsName { - set { _eku = value; } + get; + set; } /// - /// remaining validity period in days for a certificate + /// Gets or sets EKU OID list of a certificate. /// - internal int ExpiringInDays + internal List Eku { - set { _expiringInDays = value; } + get; + set; } /// - /// combine properties into a filter string + /// Gets or sets validity time for a certificate. /// - internal string FilterString + internal DateTime Expiring { - get - { - string filterString = ""; - - if (_dnsName != null) - { - filterString = AppendFilter(filterString, "dns", _dnsName); - } - - string ekuT = ""; - if (_eku != null) - { - for (int i = 0; i < _eku.Length; i++) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - ekuT = ekuT + _eku[i]; - } - } - if (_purpose == CertificatePurpose.CodeSigning) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - ekuT = ekuT + CodeSigningOid; - } - if (_purpose == CertificatePurpose.DocumentEncryption) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - ekuT = ekuT + DocumentEncryptionOid; - } - if (_sslServerAuthentication) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - ekuT = ekuT + szOID_PKIX_KP_SERVER_AUTH; - } - if (ekuT.Length != 0) - { - filterString = AppendFilter(filterString, "eku", ekuT); - if (_purpose == CertificatePurpose.CodeSigning || - _sslServerAuthentication) - { - filterString = AppendFilter(filterString, "key", "*"); - } - } - if (_expiringInDays >= 0) - { - filterString = AppendFilter( - filterString, - "ExpiringInDays", - _expiringInDays.ToString(System.Globalization.CultureInfo.InvariantCulture)); - } - - if (filterString.Length == 0) - { - filterString = null; - } - return filterString; - } - } - - private string AppendFilter( - string filterString, - string name, - string value) - { - string newfilter = value; - - // append a "name=value" filter to the existing filter string. - // insert a separating "&" if existing filter string is not empty. - - // if the value is empty, do nothing. - - if (newfilter.Length != 0) - { - // if the value contains an equal sign or an ampersand, throw - // an exception to avoid compromising the native code parser. - - if (newfilter.Contains("=") || newfilter.Contains("&")) - { - throw Marshal.GetExceptionForHR( - Security.NativeMethods.E_INVALID_DATA); - } - newfilter = name + "=" + newfilter; - if (filterString.Length != 0) - { - newfilter = "&" + newfilter; - } - } - return filterString + newfilter; - } - - private CertificatePurpose _purpose = 0; - private bool _sslServerAuthentication = false; - private string _dnsName = null; - private string[] _eku = null; - private int _expiringInDays = -1; + get; + set; + } = DateTime.MinValue; internal const string CodeSigningOid = "1.3.6.1.5.5.7.3.3"; - internal const string szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"; + internal const string OID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"; // 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"; } } @@ -972,13 +852,13 @@ internal enum CertificatePurpose } } - namespace System.Management.Automation { + using System.Management.Automation.Tracing; using System.Security.Cryptography.Pkcs; /// - /// Utility class for CMS (Cryptographic Message Syntax) related operations + /// Utility class for CMS (Cryptographic Message Syntax) related operations. /// internal static class CmsUtils { @@ -988,7 +868,7 @@ internal static string Encrypt(byte[] contentBytes, CmsMessageRecipient[] recipi if ((contentBytes == null) || (contentBytes.Length == 0)) { - return String.Empty; + return string.Empty; } // After review with the crypto board, NIST_AES256_CBC is more appropriate @@ -1028,16 +908,16 @@ internal static string Encrypt(byte[] contentBytes, CmsMessageRecipient[] recipi return encodedContent; } - internal static string BEGIN_CMS_SIGIL = "-----BEGIN CMS-----"; - internal static string END_CMS_SIGIL = "-----END CMS-----"; + internal static readonly string BEGIN_CMS_SIGIL = "-----BEGIN CMS-----"; + internal static readonly string END_CMS_SIGIL = "-----END CMS-----"; - internal static string BEGIN_CERTIFICATE_SIGIL = "-----BEGIN CERTIFICATE-----"; - internal static string END_CERTIFICATE_SIGIL = "-----END CERTIFICATE-----"; + internal static readonly string BEGIN_CERTIFICATE_SIGIL = "-----BEGIN CERTIFICATE-----"; + internal static readonly string END_CERTIFICATE_SIGIL = "-----END CERTIFICATE-----"; /// - /// Adds Ascii armour to a byte stream in Base64 format + /// Adds Ascii armour to a byte stream in Base64 format. /// - /// The bytes to encode + /// The bytes to encode. internal static string GetAsciiArmor(byte[] bytes) { StringBuilder output = new StringBuilder(); @@ -1051,13 +931,13 @@ internal static string GetAsciiArmor(byte[] bytes) } /// - /// Removes Ascii armour from a byte stream + /// Removes Ascii armour from a byte stream. /// - /// The Ascii armored content - /// The marker of the start of the Base64 content - /// The marker of the end of the Base64 content - /// The beginning of where the Ascii armor was detected - /// The end of where the Ascii armor was detected + /// The Ascii armored content. + /// The marker of the start of the Base64 content. + /// The marker of the end of the Base64 content. + /// The beginning of where the Ascii armor was detected. + /// The end of where the Ascii armor was detected. internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker, string endMarker, out int startIndex, out int endIndex) { byte[] messageBytes = null; @@ -1080,7 +960,7 @@ internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker int startContent = startIndex + beginMarker.Length; int endContent = endIndex - endMarker.Length; string encodedContent = actualContent.Substring(startContent, endContent - startContent); - encodedContent = System.Text.RegularExpressions.Regex.Replace(encodedContent, "\\s", ""); + encodedContent = System.Text.RegularExpressions.Regex.Replace(encodedContent, "\\s", string.Empty); messageBytes = Convert.FromBase64String(encodedContent); return messageBytes; @@ -1093,12 +973,12 @@ internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker public class CmsMessageRecipient { /// - /// Creates an instance of the CmsMessageRecipient class + /// Creates an instance of the CmsMessageRecipient class. /// internal CmsMessageRecipient() { } /// - /// Creates an instance of the CmsMessageRecipient class + /// Creates an instance of the CmsMessageRecipient class. /// /// /// The identifier of the CmsMessageRecipient. @@ -1113,10 +993,11 @@ public CmsMessageRecipient(string identifier) _identifier = identifier; this.Certificates = new X509Certificate2Collection(); } - private string _identifier = null; + + private readonly string _identifier; /// - /// Creates an instance of the CmsMessageRecipient class + /// Creates an instance of the CmsMessageRecipient class. /// /// The certificate to use. public CmsMessageRecipient(X509Certificate2 certificate) @@ -1124,10 +1005,11 @@ public CmsMessageRecipient(X509Certificate2 certificate) _pendingCertificate = certificate; this.Certificates = new X509Certificate2Collection(); } - private X509Certificate2 _pendingCertificate = null; + + private readonly X509Certificate2 _pendingCertificate; /// - /// Gets the certificate associated with this recipient + /// Gets the certificate associated with this recipient. /// public X509Certificate2Collection Certificates { @@ -1148,8 +1030,10 @@ public void Resolve(SessionState sessionState, ResolutionPurpose purpose, out Er // Process the certificate if that was supplied exactly if (_pendingCertificate != null) { - ProcessResolvedCertificates(purpose, - new List { _pendingCertificate }, out error); + ProcessResolvedCertificates( + purpose, + new X509Certificate2Collection(_pendingCertificate), + out error); if ((error != null) || (Certificates.Count != 0)) { return; @@ -1172,15 +1056,8 @@ public void Resolve(SessionState sessionState, ResolutionPurpose purpose, out Er return; } - // Then by thumbprint - ResolveFromThumbprint(sessionState, purpose, out error); - if ((error != null) || (Certificates.Count != 0)) - { - return; - } - - // Then by Subject Name - ResolveFromSubjectName(sessionState, purpose, out error); + // Then by cert store + ResolveFromStoreById(purpose, out error); if ((error != null) || (Certificates.Count != 0)) { return; @@ -1196,7 +1073,7 @@ public void Resolve(SessionState sessionState, ResolutionPurpose purpose, out Er { error = new ErrorRecord( new ArgumentException( - String.Format(CultureInfo.InvariantCulture, + string.Format(CultureInfo.InvariantCulture, SecuritySupportStrings.NoCertificateFound, _identifier)), "NoCertificateFound", ErrorCategory.ObjectNotFound, _identifier); } @@ -1225,10 +1102,13 @@ private void ResolveFromBase64Encoding(ResolutionPurpose purpose, out ErrorRecor return; } - List certificatesToProcess = new List(); + var certificatesToProcess = new X509Certificate2Collection(); try { + #pragma warning disable SYSLIB0057 X509Certificate2 newCertificate = new X509Certificate2(messageBytes); + #pragma warning restore SYSLIB0057 + certificatesToProcess.Add(newCertificate); } catch (Exception) @@ -1262,11 +1142,11 @@ private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpos if ((resolvedPaths != null) && (resolvedPaths.Count != 0)) { // Ensure the path is from the file system provider - if (!String.Equals(pathProvider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(pathProvider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase)) { error = new ErrorRecord( new ArgumentException( - String.Format(CultureInfo.InvariantCulture, + string.Format(CultureInfo.InvariantCulture, SecuritySupportStrings.CertificatePathMustBeFileSystemPath, _identifier)), "CertificatePathMustBeFileSystemPath", ErrorCategory.ObjectNotFound, pathProvider); return; @@ -1299,14 +1179,16 @@ private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpos resolvedPaths.Remove(path); } - List certificatesToProcess = new List(); + var certificatesToProcess = new X509Certificate2Collection(); foreach (string path in resolvedPaths) { X509Certificate2 certificate = null; try { + #pragma warning disable SYSLIB0057 certificate = new X509Certificate2(path); + #pragma warning restore SYSLIB0057 } catch (Exception) { @@ -1321,102 +1203,54 @@ private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpos } } - private void ResolveFromThumbprint(SessionState sessionState, ResolutionPurpose purpose, out ErrorRecord error) + private void ResolveFromStoreById(ResolutionPurpose purpose, out ErrorRecord error) { - // Quickly check that this is a thumbprint-like pattern (just hex) - if (!System.Text.RegularExpressions.Regex.IsMatch(_identifier, "^[a-f0-9]+$", Text.RegularExpressions.RegexOptions.IgnoreCase)) - { - error = null; - return; - } - - Collection certificates = new Collection(); + error = null; + WildcardPattern subjectNamePattern = WildcardPattern.Get(_identifier, WildcardOptions.IgnoreCase); try { - // Get first from 'My' store - string certificatePath = sessionState.Path.Combine("Microsoft.PowerShell.Security\\Certificate::CurrentUser\\My", _identifier); - if (sessionState.InvokeProvider.Item.Exists(certificatePath)) - { - foreach (PSObject certificateObject in sessionState.InvokeProvider.Item.Get(certificatePath)) - { - certificates.Add(certificateObject); - } - } + var certificatesToProcess = new X509Certificate2Collection(); - // Second from 'LocalMachine' store - certificatePath = sessionState.Path.Combine("Microsoft.PowerShell.Security\\Certificate::LocalMachine\\My", _identifier); - if (sessionState.InvokeProvider.Item.Exists(certificatePath)) + using (var storeCU = new X509Store("my", StoreLocation.CurrentUser)) { - foreach (PSObject certificateObject in sessionState.InvokeProvider.Item.Get(certificatePath)) + storeCU.Open(OpenFlags.ReadOnly); + X509Certificate2Collection storeCerts = storeCU.Certificates; + + if (Platform.IsWindows) { - certificates.Add(certificateObject); + using (var storeLM = new X509Store("my", StoreLocation.LocalMachine)) + { + storeLM.Open(OpenFlags.ReadOnly); + storeCerts.AddRange(storeLM.Certificates); + } } - } - } - catch (SessionStateException) - { - // If we got an ItemNotFound / etc., then this didn't represent a valid path. - } - - List certificatesToProcess = new List(); - foreach (PSObject certificateObject in certificates) - { - X509Certificate2 certificate = certificateObject.BaseObject as X509Certificate2; - if (certificate != null) - { - certificatesToProcess.Add(certificate); - } - } - - ProcessResolvedCertificates(purpose, certificatesToProcess, out error); - } - - private void ResolveFromSubjectName(SessionState sessionState, ResolutionPurpose purpose, out ErrorRecord error) - { - Collection certificates = new Collection(); - WildcardPattern subjectNamePattern = WildcardPattern.Get(_identifier, WildcardOptions.IgnoreCase); - try - { - // Get first from 'My' store, then 'LocalMachine' - string[] certificatePaths = new string[] { - "Microsoft.PowerShell.Security\\Certificate::CurrentUser\\My", - "Microsoft.PowerShell.Security\\Certificate::LocalMachine\\My" }; + certificatesToProcess.AddRange(storeCerts.Find(X509FindType.FindByThumbprint, _identifier, validOnly: false)); - foreach (string certificatePath in certificatePaths) - { - foreach (PSObject certificateObject in sessionState.InvokeProvider.ChildItem.Get(certificatePath, false)) + if (certificatesToProcess.Count == 0) { - if (subjectNamePattern.IsMatch(certificateObject.Properties["Subject"].Value.ToString())) + foreach (var cert in storeCerts) { - certificates.Add(certificateObject); + if (subjectNamePattern.IsMatch(cert.Subject) || subjectNamePattern.IsMatch(cert.GetNameInfo(X509NameType.SimpleName, forIssuer: false))) + { + certificatesToProcess.Add(cert); + } } } + + ProcessResolvedCertificates(purpose, certificatesToProcess, out error); } } catch (SessionStateException) { - // If we got an ItemNotFound / etc., then this didn't represent a valid path. } - - List certificatesToProcess = new List(); - foreach (PSObject certificateObject in certificates) - { - X509Certificate2 certificate = certificateObject.BaseObject as X509Certificate2; - if (certificate != null) - { - certificatesToProcess.Add(certificate); - } - } - - ProcessResolvedCertificates(purpose, certificatesToProcess, out error); } - private void ProcessResolvedCertificates(ResolutionPurpose purpose, List certificatesToProcess, out ErrorRecord error) + private void ProcessResolvedCertificates(ResolutionPurpose purpose, X509Certificate2Collection certificatesToProcess, out ErrorRecord error) { error = null; - HashSet processedThumbprints = new HashSet(); + HashSet processedThumbprints = new HashSet(); foreach (X509Certificate2 certificate in certificatesToProcess) { @@ -1428,9 +1262,14 @@ private void ProcessResolvedCertificates(ResolutionPurpose purpose, List - /// Defines the purpose for resolution of a CmsMessageRecipient + /// Defines the purpose for resolution of a CmsMessageRecipient. /// public enum ResolutionPurpose { /// - /// This message recipient is intended to be used for message encryption + /// This message recipient is intended to be used for message encryption. /// Encryption, /// - /// This message recipient is intended to be used for message decryption + /// This message recipient is intended to be used for message decryption. /// Decryption } - internal class AmsiUtils + internal static class AmsiUtils { + static AmsiUtils() + { +#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() { Diagnostics.Assert(s_amsiContext == IntPtr.Zero, "Init should be called just once"); 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); + appName = string.Concat("PowerShell_", Environment.ProcessPath, "_", PSVersionInfo.ProductVersion); } - catch (ComponentModel.Win32Exception) + catch (Exception) { - // This exception can be thrown during thread impersonation (Access Denied for process module access). - // Use command line arguments or process name. - string[] cmdLineArgs = Environment.GetCommandLineArgs(); - string processPath = (cmdLineArgs.Length > 0) ? cmdLineArgs[0] : currentProcess.ProcessName; - hostname = string.Concat("PowerShell_", processPath, ".exe_0.0.0.0"); + // Fall back to 'Process.ProcessName' in case 'Environment.ProcessPath' throws exception. + Process currentProcess = Process.GetCurrentProcess(); + appName = string.Concat("PowerShell_", currentProcess.ProcessName, ".exe_", PSVersionInfo.ProductVersion); } -#if !CORECLR AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; -#endif - - 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; } } @@ -1539,29 +1388,32 @@ internal static int Init() /// Caller is responsible for calling AmsiCloseSession when a "session" (script) /// is complete, and for calling AmsiUninitialize when the runspace is being torn down. /// - /// The string to be scanned - /// Information about the source (filename, etc.) + /// The string to be scanned. + /// Information about the source (filename, etc.). /// AMSI_RESULT_DETECTED if malware was detected in the sample. internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string sourceMetadata) { #if UNIX return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; #else - return WinScanContent(content, sourceMetadata); + return WinScanContent(content, sourceMetadata, warmUp: false); #endif } - internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata) + internal static AmsiNativeMethods.AMSI_RESULT WinScanContent( + string content, + string sourceMetadata, + bool warmUp) { - if (String.IsNullOrEmpty(sourceMetadata)) + if (string.IsNullOrEmpty(sourceMetadata)) { - sourceMetadata = String.Empty; + sourceMetadata = string.Empty; } 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; } @@ -1570,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; } @@ -1577,52 +1430,47 @@ 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) + if (!CheckAmsiInit()) { - hr = Init(); - - if (!Utils.Succeeded(hr)) - { - s_amsiInitFailed = true; - return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; - } + 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 (warmUp) { - 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; - } + // We are warming up the AMSI component in console startup, and that means we initialize AMSI + // and create a AMSI session, but don't really scan anything. + return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN; - hr = AmsiNativeMethods.AmsiScanString( - s_amsiContext, - content, - sourceMetadata, - s_amsiSession, - ref result); + // Run AMSI content scan + int hr; + unsafe + { + fixed (char* buffer = content) + { + var buffPtr = new IntPtr(buffer); + hr = AmsiNativeMethods.AmsiScanBuffer( + s_amsiContext, + buffPtr, + (uint)(content.Length * sizeof(char)), + sourceMetadata, + s_amsiSession, + ref result); + } + } 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; } @@ -1630,26 +1478,142 @@ 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; } } } - internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) + /// + /// 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 - VerifyAmsiUninitializeCalled(); +#if UNIX + return false; +#else + return WinReportContent(name, content); #endif } - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + 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) + { + Uninitialize(); + } + } + 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) @@ -1681,7 +1645,7 @@ internal static void WinCloseSession() } /// - /// Uninitialize the AMSI interface + /// Uninitialize the AMSI interface. /// internal static void Uninitialize() { @@ -1701,6 +1665,9 @@ internal static void WinUninitialize() { CloseSession(); + // Unregister the event handler. + AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit; + // Uninitialize the AMSI interface. AmsiCleanedUp = true; AmsiNativeMethods.AmsiUninitialize(s_amsiContext); @@ -1709,16 +1676,12 @@ internal static void WinUninitialize() } } } + public static bool AmsiUninitializeCalled = false; public static bool AmsiInitialized = false; public static bool AmsiCleanedUp = false; - private static void VerifyAmsiUninitializeCalled() - { - Debug.Assert((!AmsiInitialized) || AmsiUninitializeCalled, "AMSI should have been uninitialized."); - } - - internal class AmsiNativeMethods + internal static class AmsiNativeMethods { internal enum AMSI_RESULT { @@ -1728,6 +1691,10 @@ internal enum AMSI_RESULT /// AMSI_RESULT_NOT_DETECTED -> 1 AMSI_RESULT_NOT_DETECTED = 1, + /// Certain policies set by administrator blocked this content on this machine + AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN = 0x4000, + AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff, + /// AMSI_RESULT_DETECTED -> 32768 AMSI_RESULT_DETECTED = 32768, } @@ -1735,31 +1702,31 @@ internal enum AMSI_RESULT /// Return Type: HRESULT->LONG->int ///appName: LPCWSTR->WCHAR* ///amsiContext: HAMSICONTEXT* - [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__* - [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* - [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__* - [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 ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* ///buffer: PVOID->void* @@ -1767,11 +1734,30 @@ internal static extern int AmsiInitialize( ///contentName: LPCWSTR->WCHAR* ///amsiSession: HAMSISESSION->HAMSISESSION__* ///result: AMSI_RESULT* - [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__* @@ -1779,10 +1765,11 @@ internal static extern int AmsiScanBuffer( ///contentName: LPCWSTR->WCHAR* ///amsiSession: HAMSISESSION->HAMSISESSION__* ///result: AMSI_RESULT* - [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 7e486e6ef69..6dd8f59d613 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -1,8 +1,5 @@ -#pragma warning disable 1634, 1691 - -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56523 @@ -10,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; @@ -27,74 +23,119 @@ internal partial class NativeConstants // Safer native constants internal partial class NativeConstants { - /// SAFER_TOKEN_NULL_IF_EQUAL -> 0x00000001 + /// + /// SAFER_TOKEN_NULL_IF_EQUAL -> 0x00000001. + /// public const int SAFER_TOKEN_NULL_IF_EQUAL = 1; - /// SAFER_TOKEN_COMPARE_ONLY -> 0x00000002 + /// + /// SAFER_TOKEN_COMPARE_ONLY -> 0x00000002. + /// public const int SAFER_TOKEN_COMPARE_ONLY = 2; - /// SAFER_TOKEN_MAKE_INERT -> 0x00000004 + /// + /// SAFER_TOKEN_MAKE_INERT -> 0x00000004. + /// public const int SAFER_TOKEN_MAKE_INERT = 4; - - /// SAFER_CRITERIA_IMAGEPATH -> 0x00001 + /// + /// SAFER_CRITERIA_IMAGEPATH -> 0x00001. + /// public const int SAFER_CRITERIA_IMAGEPATH = 1; - /// SAFER_CRITERIA_NOSIGNEDHASH -> 0x00002 + /// + /// SAFER_CRITERIA_NOSIGNEDHASH -> 0x00002. + /// public const int SAFER_CRITERIA_NOSIGNEDHASH = 2; - /// SAFER_CRITERIA_IMAGEHASH -> 0x00004 + /// + /// SAFER_CRITERIA_IMAGEHASH -> 0x00004. + /// public const int SAFER_CRITERIA_IMAGEHASH = 4; - /// SAFER_CRITERIA_AUTHENTICODE -> 0x00008 + /// + /// SAFER_CRITERIA_AUTHENTICODE -> 0x00008. + /// public const int SAFER_CRITERIA_AUTHENTICODE = 8; - /// SAFER_CRITERIA_URLZONE -> 0x00010 + /// + /// SAFER_CRITERIA_URLZONE -> 0x00010. + /// public const int SAFER_CRITERIA_URLZONE = 16; - /// SAFER_CRITERIA_IMAGEPATH_NT -> 0x01000 + /// + /// SAFER_CRITERIA_IMAGEPATH_NT -> 0x01000. + /// public const int SAFER_CRITERIA_IMAGEPATH_NT = 4096; - /// WTD_UI_NONE -> 0x00002 + /// + /// WTD_UI_NONE -> 0x00002. + /// public const int WTD_UI_NONE = 2; + /// /// S_OK -> ((HRESULT)0L) + /// public const int S_OK = 0; + /// /// S_FALSE -> ((HRESULT)1L) + /// public const int S_FALSE = 1; - /// ERROR_MORE_DATA -> 234L + /// + /// ERROR_MORE_DATA -> 234L. + /// public const int ERROR_MORE_DATA = 234; - /// ERROR_ACCESS_DISABLED_BY_POLICY -> 1260L + /// + /// ERROR_ACCESS_DISABLED_BY_POLICY -> 1260L. + /// public const int ERROR_ACCESS_DISABLED_BY_POLICY = 1260; - /// ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY -> 786L + /// + /// ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY -> 786L. + /// public const int ERROR_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY = 786; - /// SAFER_MAX_HASH_SIZE -> 64 + /// + /// SAFER_MAX_HASH_SIZE -> 64. + /// public const int SAFER_MAX_HASH_SIZE = 64; + /// /// SRP_POLICY_SCRIPT -> L"SCRIPT" + /// public const string SRP_POLICY_SCRIPT = "SCRIPT"; - /// SIGNATURE_DISPLAYNAME_LENGTH -> MAX_PATH + /// + /// SIGNATURE_DISPLAYNAME_LENGTH -> MAX_PATH. + /// internal const int SIGNATURE_DISPLAYNAME_LENGTH = NativeConstants.MAX_PATH; - /// SIGNATURE_PUBLISHER_LENGTH -> 128 + /// + /// SIGNATURE_PUBLISHER_LENGTH -> 128. + /// internal const int SIGNATURE_PUBLISHER_LENGTH = 128; - /// SIGNATURE_HASH_LENGTH -> 64 + /// + /// SIGNATURE_HASH_LENGTH -> 64. + /// internal const int SIGNATURE_HASH_LENGTH = 64; - /// MAX_PATH -> 260 + /// + /// MAX_PATH -> 260. + /// internal const int MAX_PATH = 260; - } + /// + /// This function is not supported on this system. + /// + internal const int FUNCTION_NOT_SUPPORTED = 120; + } /// - /// pinvoke methods from crypt32.dll + /// Pinvoke methods from crypt32.dll. /// internal static partial class NativeMethods { @@ -110,7 +151,7 @@ bool CertEnumSystemStore(CertStoreFlags Flags, CertEnumSystemStoreCallBackProto fn); /// - /// signature of call back function used by CertEnumSystemStore + /// Signature of call back function used by CertEnumSystemStore. /// internal delegate bool CertEnumSystemStoreCallBackProto([MarshalAs(UnmanagedType.LPWStr)] @@ -121,16 +162,15 @@ bool CertEnumSystemStoreCallBackProto([MarshalAs(UnmanagedType.LPWStr)] IntPtr notUsed3); /// - /// signature of cert enumeration function + /// Signature of cert enumeration function. /// [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern IntPtr CertEnumCertificatesInStore(IntPtr storeHandle, IntPtr certContext); - /// - /// signature of cert find function + /// Signature of cert find function. /// [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern @@ -153,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); @@ -171,8 +210,6 @@ internal enum CertStoreFlags CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE = 9 << 16, } - - [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool CertGetEnhancedKeyUsage(IntPtr pCertContext, // PCCERT_CONTEXT @@ -433,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); @@ -459,8 +495,8 @@ internal static extern [DllImport(PinvokeDllNames.CryptAcquireContextDllName, SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool CryptAcquireContext(ref IntPtr hProv, - String strContainerName, - String strProviderName, + string strContainerName, + string strProviderName, int nProviderType, uint uiProviderFlags); @@ -475,19 +511,20 @@ internal static extern unsafe [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)] internal static extern int NCryptOpenStorageProvider(ref IntPtr hProv, - String strProviderName, + string strProviderName, uint dwFlags); [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)] internal static extern int NCryptOpenKey(IntPtr hProv, ref IntPtr hKey, - String strKeyName, + string strKeyName, uint dwLegacySpec, uint dwFlags); + [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)] internal static extern unsafe - int NCryptSetProperty(IntPtr hProv, String pszProperty, void* pbInput, int cbInput, int dwFlags); + int NCryptSetProperty(IntPtr hProv, string pszProperty, void* pbInput, int cbInput, int dwFlags); [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)] internal static extern @@ -519,55 +556,61 @@ internal enum CryptUIFlags { CRYPTUI_WIZ_NO_UI = 0x0001 // other flags not used - }; - + } [StructLayout(LayoutKind.Sequential)] internal struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO { internal DWORD dwSize; internal DWORD dwSubjectChoice; + [MarshalAs(UnmanagedType.LPWStr)] internal string pwszFileName; + internal DWORD dwSigningCertChoice; internal IntPtr pSigningCertContext; // PCCERT_CONTEXT + [MarshalAs(UnmanagedType.LPWStr)] internal string pwszTimestampURL; + 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 - }; + // CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_BLOB = 0x02 NotUsed + } [Flags] internal enum SignInfoCertChoice { CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 0x01 - //CRYPTUI_WIZ_DIGITAL_SIGN_STORE = 0x02, NotUsed - //CRYPTUI_WIZ_DIGITAL_SIGN_PVK = 0x03, NotUsed - }; + // 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 { internal DWORD dwSize; internal DWORD dwAttrFlagsNotUsed; + [MarshalAs(UnmanagedType.LPWStr)] internal string pwszDescription; + [MarshalAs(UnmanagedType.LPWStr)] internal string pwszMoreInfoLocation; + [MarshalAs(UnmanagedType.LPStr)] internal string pszHashAlg; @@ -575,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, @@ -601,7 +643,6 @@ internal static CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO siex.pszHashAlg = hashAlgorithm; } - return siex; } @@ -612,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 @@ -682,8 +723,6 @@ internal static extern IntPtr CryptFindOIDInfo( System.IntPtr pvKey, uint dwGroupId); - - [ArchitectureSensitive] internal static DWORD GetCertChoiceFromSigningOption( SigningOption option) { @@ -711,7 +750,6 @@ internal static DWORD GetCertChoiceFromSigningOption( return cc; } - [ArchitectureSensitive] internal static CRYPTUI_WIZ_DIGITAL_SIGN_INFO InitSignInfoStruct(string fileName, X509Certificate2 signingCert, @@ -730,7 +768,7 @@ internal static CRYPTUI_WIZ_DIGITAL_SIGN_INFO si.dwAdditionalCertChoice = GetCertChoiceFromSigningOption(option); CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO siex = - InitSignInfoExtendedStruct("", "", hashAlgorithm); + InitSignInfoExtendedStruct(string.Empty, string.Empty, hashAlgorithm); IntPtr pSiexBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(siex)); Marshal.StructureToPtr(siex, pSiexBuffer, false); si.pSignExtInfo = pSiexBuffer; @@ -738,443 +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; - } - - [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - internal struct GUID - { - /// unsigned int - internal uint Data1; - - /// unsigned short - internal ushort Data2; - - /// unsigned short - internal ushort Data3; - - /// unsigned char[8] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - internal byte[] Data4; - } - - [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.Data1 = 0x603bcc1f; - bi.gSubject.Data2 = 0x4b59; - bi.gSubject.Data3 = 0x4e08; - bi.gSubject.Data4 = 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 enable 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 enable 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 } // @@ -1185,10 +821,10 @@ internal static void FreeWVTStateData(System.IntPtr phWVTStateData) internal struct CERT_ENHKEY_USAGE { internal DWORD cUsageIdentifier; - //[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeParamIndex=0)] - //internal string[] rgpszUsageIdentifier; // LPSTR* + // [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeParamIndex=0)] + // internal string[] rgpszUsageIdentifier; // LPSTR* internal IntPtr rgpszUsageIdentifier; - }; + } internal enum SIGNATURE_STATE { @@ -1262,7 +898,7 @@ internal enum SIGNATURE_INFO_TYPE SIT_CATALOG, } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct SIGNATURE_INFO { /// DWORD->unsigned int @@ -1281,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 @@ -1311,7 +947,7 @@ internal struct SIGNATURE_INFO internal int fOSBinary; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_INFO { /// DWORD->unsigned int @@ -1351,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 @@ -1372,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 @@ -1382,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 @@ -1395,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 @@ -1411,7 +1047,7 @@ internal struct CERT_EXTENSION } /// - /// pinvoke methods from certca.dll + /// Pinvoke methods from certca.dll. /// internal static partial class NativeMethods { @@ -1440,25 +1076,6 @@ internal enum CryptDecodeFlags : uint } } - #region Check_UI_Allowed - - /// - /// Used in CertificateProvider to detect if UI is allowed. - /// - internal static partial class NativeMethods - { - [DllImport("kernel32.dll")] - internal static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId); - - [DllImport("kernel32.dll")] - internal static extern IntPtr GetConsoleWindow(); - - [DllImport("user32.dll")] - internal static extern IntPtr GetDesktopWindow(); - } - - #endregion Check_UI_Allowed - #region SAFER_APIs // SAFER native methods @@ -1469,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 @@ -1486,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, @@ -1499,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 @@ -1520,7 +1137,7 @@ internal struct SAFER_CODE_PROPERTIES public uint dwCheckFlags; /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] public string ImagePath; /// HANDLE->void* @@ -1530,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)] @@ -1555,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 @@ -1588,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 @@ -1601,7 +1218,7 @@ internal struct Anonymous_947eb392_1446_4e25_bbd4_10e98165f3a9 #endregion SAFER_APIs /// - /// pinvoke methods from advapi32.dll + /// Pinvoke methods from advapi32.dll. /// internal static partial class NativeMethods { @@ -1670,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; @@ -1701,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; @@ -1709,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; @@ -1717,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; @@ -1725,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; @@ -1760,11 +1377,13 @@ internal static extern uint SetNamedSecurityInfo( IntPtr pSacl); [DllImport(PinvokeDllNames.ConvertStringSidToSidDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ConvertStringSidToSid( string StringSid, out IntPtr Sid); [DllImport(PinvokeDllNames.IsValidSidDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool IsValidSid(IntPtr pSid); [DllImport(PinvokeDllNames.GetLengthSidDllName, CharSet = CharSet.Unicode)] @@ -1781,6 +1400,7 @@ internal static extern uint LsaQueryCAPs( internal static extern uint LsaFreeMemory(IntPtr Buffer); [DllImport(PinvokeDllNames.InitializeAclDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool InitializeAcl( IntPtr pAcl, uint nAclLength, @@ -1801,12 +1421,14 @@ internal static extern uint AddScopedPolicyIDAce( internal static extern IntPtr GetCurrentThread(); [DllImport(PinvokeDllNames.OpenProcessTokenDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenProcessToken( IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); [DllImport(PinvokeDllNames.OpenThreadTokenDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenThreadToken( IntPtr ThreadHandle, uint DesiredAccess, @@ -1814,12 +1436,14 @@ internal static extern bool OpenThreadToken( out IntPtr TokenHandle); [DllImport(PinvokeDllNames.LookupPrivilegeValueDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LookupPrivilegeValue( string lpSystemName, string lpName, ref LUID lpLuid); [DllImport(PinvokeDllNames.AdjustTokenPrivilegesDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool AdjustTokenPrivileges( IntPtr TokenHandle, bool DisableAllPrivileges, @@ -1843,41 +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)] - 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 @@ -1919,219 +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")] - 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")] - 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)] - 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)] - 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)] - 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 e79c1678f3a..ab49f927614 100644 --- a/src/System.Management.Automation/security/wldpNativeMethods.cs +++ b/src/System.Management.Automation/security/wldpNativeMethods.cs @@ -1,13 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// Application white listing policies such as AppLocker and DeviceGuard UMCI are only implemented on Windows OSs +// +#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. - /// /// // Internal Note: Current code that consumes this enum assumes that anything but 'Enforce' means // that the script is allowed, and that a system lockdown policy that is anything but 'None' means @@ -27,7 +67,7 @@ public enum SystemEnforcementMode /// /// Support class for dealing with the Windows Lockdown Policy, - /// Device Guard, and Constrained PowerShell + /// Device Guard, and Constrained PowerShell. /// public sealed class SystemPolicy { @@ -36,69 +76,253 @@ private SystemPolicy() } /// - /// Gets the system lockdown policy + /// Writes to PowerShell WDAC Audit mode ETW log. /// - /// An EnforcementMode that describes the system policy + /// 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_wasSystemPolicyDebugPolicy || (s_systemLockdownPolicy == null)) + if (s_systemLockdownPolicy == null) { lock (s_systemLockdownPolicyLock) { - if (s_wasSystemPolicyDebugPolicy || (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 Nullable s_systemLockdownPolicy = null; - private static bool s_wasSystemPolicyDebugPolicy = false; + 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 + /// Gets lockdown policy as applied to a file. /// - /// An EnforcementMode that describes policy + /// 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 { - return lockdownPolicy; + 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 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", @@ -106,14 +330,13 @@ public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle ha private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handle) { // If the WLDP assembly is missing (such as windows 7 or down OS), return default/None to skip WLDP validation - if (s_hadMissingWldpAssembly || !IO.File.Exists(IO.Path.Combine(Environment.SystemDirectory, "wldp.dll"))) + if (s_hadMissingWldpAssembly) { - s_hadMissingWldpAssembly = true; return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None); } // If path is NULL, see if we have the cached system-wide lockdown policy. - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { if ((s_cachedWldpSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching)) { @@ -127,7 +350,7 @@ private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handl hostInformation.dwRevision = WldpNativeConstants.WLDP_HOST_INFORMATION_REVISION; hostInformation.dwHostId = WLDP_HOST_ID.WLDP_HOST_ID_POWERSHELL; - if (!String.IsNullOrEmpty(path)) + if (!string.IsNullOrEmpty(path)) { hostInformation.szSource = path; @@ -141,12 +364,13 @@ 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); // If this is a query for the system-wide lockdown policy, cache it. - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { s_cachedWldpSystemPolicy = resultingLockdownPolicy; } @@ -159,14 +383,18 @@ 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); } } + private static SystemEnforcementMode? s_cachedWldpSystemPolicy = null; + private const string AppLockerTestFileName = "__PSScriptPolicyTest_"; + private const string AppLockerTestFileContents = "# PowerShell test file to determine AppLocker lockdown mode "; private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle handle) { @@ -176,7 +404,7 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle // Since there is no way to get that from AppLocker, we will test the policy // against a random non-existent script and module. If that is allowed, then there is // no AppLocker script policy. - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { if ((s_cachedSaferSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching)) { @@ -210,37 +438,53 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle IO.Directory.CreateDirectory(tempPath); } - testPathScript = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".ps1"); - testPathModule = IO.Path.Combine(tempPath, IO.Path.GetRandomFileName() + ".psm1"); + testPathScript = IO.Path.Combine(tempPath, AppLockerTestFileName + IO.Path.GetRandomFileName() + ".ps1"); + testPathModule = IO.Path.Combine(tempPath, AppLockerTestFileName + IO.Path.GetRandomFileName() + ".psm1"); // AppLocker fails when you try to check a policy on a file // with no content. So create a scratch file and test on that. - IO.File.WriteAllText(testPathScript, "1"); - IO.File.WriteAllText(testPathModule, "1"); + 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/en-us/library/dd378457.aspx + // https://msdn.microsoft.com/library/dd378457.aspx Guid AppDatalocalLowFolderId = new Guid("A520A1A4-1780-4FF6-BD18-167343C5AF16"); tempPath = GetKnownFolderPath(AppDatalocalLowFolderId) + @"\Temp"; - } // end while loop + } // Test policy. result = TestSaferPolicy(testPathScript, testPathModule); @@ -267,8 +511,10 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle } finally { - if (IO.File.Exists(testPathScript)) { IO.File.Delete(testPathScript); } - if (IO.File.Exists(testPathModule)) { IO.File.Delete(testPathModule); } + // Ok to leave the test scripts in the temp folder if they happen to be in use + // so that PowerShell will still startup. + PathUtils.TryDeleteFile(testPathScript); + PathUtils.TryDeleteFile(testPathModule); } s_cachedSaferSystemPolicy = result; @@ -295,6 +541,7 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle return SystemEnforcementMode.None; } } + private static SaferPolicy? s_cachedSaferSystemPolicy = null; private static string GetKnownFolderPath(Guid knownFolderId) @@ -326,81 +573,69 @@ private static SaferPolicy TestSaferPolicy(string testPathScript, string testPat { result = SecuritySupport.GetSaferPolicy(testPathModule, null); } + return result; } - private static SystemEnforcementMode GetDebugLockdownPolicy(string path) + private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement) { - if (PsUtils.IsRunningOnProcessorArchitectureARM()) - { - return SystemEnforcementMode.Enforce; - } - - s_wasSystemPolicyDebugPolicy = true; + s_allowDebugOverridePolicy = true; // Support fall-back debug hook for path exclusions on non-WOA platforms if (path != null) { // 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); } // Support fall-back debug hook for system-wide policy on non-WOA platforms uint pdwLockdownState = 0; - Object result = Environment.GetEnvironmentVariable("__PSLockdownPolicy", EnvironmentVariableTarget.Machine); + object result = Environment.GetEnvironmentVariable("__PSLockdownPolicy", EnvironmentVariableTarget.Machine); 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; } - private static bool s_hadMissingWldpAssembly = false; - + private static bool s_hadMissingWldpAssembly = false; /// - /// Gets lockdown policy as applied to a COM object + /// Gets lockdown policy as applied to a COM object. /// /// 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(); @@ -417,9 +652,9 @@ internal static bool IsClassInApprovedList(Guid clsid) // Hook for testability. If we've got an environmental override, say that ADODB.Parameter // is not allowed. // 0000050b-0000-0010-8000-00aa006d2ea4 = ADODB.Parameter - if (s_wasSystemPolicyDebugPolicy) + if (s_allowDebugOverridePolicy) { - if (String.Equals(clsid.ToString(), "0000050b-0000-0010-8000-00aa006d2ea4", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(clsid.ToString(), "0000050b-0000-0010-8000-00aa006d2ea4", StringComparison.OrdinalIgnoreCase)) { return false; } @@ -436,7 +671,7 @@ internal static bool IsClassInApprovedList(Guid clsid) // Hook for testability. IsClassInApprovedList is only called when the system is in global lockdown mode, // so this wouldn't be allowed in regular ConstrainedLanguage mode. // f6d90f11-9c73-11d3-b32e-00c04f990bb4 = MSXML2.DOMDocument - if (String.Equals(clsid.ToString(), "f6d90f11-9c73-11d3-b32e-00c04f990bb4", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(clsid.ToString(), "f6d90f11-9c73-11d3-b32e-00c04f990bb4", StringComparison.OrdinalIgnoreCase)) { return true; } @@ -465,7 +700,7 @@ private static SystemEnforcementMode GetLockdownPolicyForResult(uint pdwLockdown internal static string DumpLockdownState(uint pdwLockdownState) { - string returnValue = ""; + string returnValue = string.Empty; if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG) { @@ -499,9 +734,9 @@ internal static string DumpLockdownState(uint pdwLockdownState) internal static bool XamlWorkflowSupported { get; set; } /// - /// Native constants for dealing with the lockdown policy + /// Native constants for dealing with the lockdown policy. /// - internal class WldpNativeConstants + internal static class WldpNativeConstants { internal const uint WLDP_HOST_INFORMATION_REVISION = 0x00000001; @@ -514,7 +749,7 @@ internal class WldpNativeConstants } /// - /// The different host IDs understood by the lockdown policy + /// The different host IDs understood by the lockdown policy. /// internal enum WLDP_HOST_ID { @@ -529,7 +764,7 @@ internal enum WLDP_HOST_ID } /// - /// Host information structure to contain the lockdown policy request + /// Host information structure to contain the lockdown policy request. /// [StructLayoutAttribute(LayoutKind.Sequential)] internal struct WLDP_HOST_INFORMATION @@ -549,25 +784,79 @@ internal struct WLDP_HOST_INFORMATION } /// - /// Native methods for dealing with the lockdown policy + /// 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* /// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION* /// ptIsApproved: PBOOL->BOOL* /// 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( @@ -578,4 +867,6 @@ internal static extern int SHGetKnownFolderPath( out IntPtr pszPath); } } -} \ No newline at end of file +} + +#endif diff --git a/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs b/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs index eee78a4205a..be4fa6c2edb 100644 --- a/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshConsoleLoadException.cs @@ -1,11 +1,10 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; -using System.Text; using System.Collections.ObjectModel; +using System.Runtime.Serialization; using System.Security.Permissions; +using System.Text; namespace System.Management.Automation.Runspaces { @@ -21,7 +20,6 @@ namespace System.Management.Automation.Runspaces /// 1. PSSnapin name /// 2. Inner exception. /// --> - [Serializable] public class PSConsoleLoadException : SystemException, IContainsErrorRecord { /// @@ -34,7 +32,7 @@ public PSConsoleLoadException() : base() /// /// Initiate an instance of PSConsoleLoadException. /// - /// Error message + /// Error message. public PSConsoleLoadException(string message) : base(message) { @@ -43,8 +41,8 @@ public PSConsoleLoadException(string message) /// /// Initiate an instance of PSConsoleLoadException. /// - /// Error message - /// Inner exception + /// Error message. + /// Inner exception. public PSConsoleLoadException(string message, Exception innerException) : base(message, innerException) { @@ -79,7 +77,7 @@ private void CreateErrorRecord() { foreach (PSSnapInException e in PSSnapInExceptions) { - sb.Append("\n"); + sb.Append('\n'); sb.Append(e.Message); } } @@ -87,7 +85,8 @@ 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 { get @@ -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 56784516778..df01b053665 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs @@ -1,36 +1,36 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; -using Microsoft.Win32; -using System.Security; +using System.Globalization; using System.IO; using System.Reflection; +using System.Security; using System.Text; -using System.Globalization; -using Regex = System.Text.RegularExpressions.Regex; -using Dbg = System.Management.Automation.Diagnostics; using Microsoft.PowerShell.Commands; +using Microsoft.Win32; + +using Dbg = System.Management.Automation.Diagnostics; +using Regex = System.Text.RegularExpressions.Regex; namespace System.Management.Automation { internal static class RegistryStrings { /// - /// Root key path under HKLM + /// Root key path under HKLM. /// internal const string MonadRootKeyPath = "Software\\Microsoft\\PowerShell"; /// - /// Root key name + /// Root key name. /// internal const string MonadRootKeyName = "PowerShell"; /// - /// Key for monad engine + /// Key for monad engine. /// internal const string MonadEngineKey = "PowerShellEngine"; @@ -42,16 +42,15 @@ internal static class RegistryStrings internal const string MonadEngine_MonadVersion = "PowerShellVersion"; /// - /// Key under which all the mshsnapin live + /// Key under which all the mshsnapin live. /// internal const string MshSnapinKey = "PowerShellSnapIns"; - //Name of various values for each mshsnapin + // Name of various values for each mshsnapin internal const string MshSnapin_ApplicationBase = "ApplicationBase"; internal const string MshSnapin_AssemblyName = "AssemblyName"; internal const string MshSnapin_ModuleName = "ModuleName"; internal const string MshSnapin_MonadVersion = "PowerShellVersion"; - internal const string MshSnapin_CustomPSSnapInType = "CustomPSSnapInType"; internal const string MshSnapin_BuiltInTypes = "Types"; internal const string MshSnapin_BuiltInFormats = "Formats"; internal const string MshSnapin_Description = "Description"; @@ -61,8 +60,7 @@ internal static class RegistryStrings internal const string MshSnapin_VendorResource = "VendorIndirect"; internal const string MshSnapin_LogPipelineExecutionDetails = "LogPipelineExecutionDetails"; - - //Name of default mshsnapins + // Name of default mshsnapins internal const string CoreMshSnapinName = "Microsoft.PowerShell.Core"; internal const string HostMshSnapinName = "Microsoft.PowerShell.Host"; internal const string ManagementMshSnapinName = "Microsoft.PowerShell.Management"; @@ -71,7 +69,7 @@ internal static class RegistryStrings } /// - /// Contains information about a mshsnapin + /// Contains information about a PSSnapin. /// public class PSSnapInInfo { @@ -87,69 +85,46 @@ internal PSSnapInInfo Collection types, Collection formats, string descriptionFallback, - string vendorFallback, - string customPSSnapInType + string vendorFallback ) { if (string.IsNullOrEmpty(name)) { - throw PSTraceSource.NewArgumentNullException("name"); + throw PSTraceSource.NewArgumentNullException(nameof(name)); } if (string.IsNullOrEmpty(applicationBase)) { - throw PSTraceSource.NewArgumentNullException("applicationBase"); + throw PSTraceSource.NewArgumentNullException(nameof(applicationBase)); } if (string.IsNullOrEmpty(assemblyName)) { - throw PSTraceSource.NewArgumentNullException("assemblyName"); + throw PSTraceSource.NewArgumentNullException(nameof(assemblyName)); } if (string.IsNullOrEmpty(moduleName)) { - throw PSTraceSource.NewArgumentNullException("moduleName"); + throw PSTraceSource.NewArgumentNullException(nameof(moduleName)); } if (psVersion == null) { - throw PSTraceSource.NewArgumentNullException("psVersion"); + throw PSTraceSource.NewArgumentNullException(nameof(psVersion)); } -#if CORECLR // CustomPSSnapIn Not Supported On CSS. - // CustomPSSnapIn derives from System.Configuration.Install, which is not in CoreCLR. - if (customPSSnapInType != null) - { - throw PSTraceSource.NewArgumentException( - "customPSSnapInType", - MshSnapInCmdletResources.CustomPSSnapInNotSupportedInPowerShellCore); - } -#endif - if (version == null) { 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; @@ -160,7 +135,6 @@ string customPSSnapInType Version = version; Types = types; Formats = formats; - CustomPSSnapInType = customPSSnapInType; _descriptionFallback = descriptionFallback; _vendorFallback = vendorFallback; } @@ -179,10 +153,9 @@ internal PSSnapInInfo string description, string descriptionFallback, string vendor, - string vendorFallback, - string customPSSnapInType + string vendorFallback ) - : this(name, isDefault, applicationBase, assemblyName, moduleName, psVersion, version, types, formats, descriptionFallback, vendorFallback, customPSSnapInType) + : this(name, isDefault, applicationBase, assemblyName, moduleName, psVersion, version, types, formats, descriptionFallback, vendorFallback) { _description = description; _vendor = vendor; @@ -204,9 +177,8 @@ internal PSSnapInInfo string descriptionIndirect, string vendor, string vendorFallback, - string vendorIndirect, - string customPSSnapInType - ) : this(name, isDefault, applicationBase, assemblyName, moduleName, psVersion, version, types, formats, description, descriptionFallback, vendor, vendorFallback, customPSSnapInType) + string vendorIndirect + ) : this(name, isDefault, applicationBase, assemblyName, moduleName, psVersion, version, types, formats, description, descriptionFallback, vendor, vendorFallback) { // add descriptionIndirect and vendorIndirect only if the mshsnapin is a default mshsnapin if (isDefault) @@ -217,27 +189,27 @@ string customPSSnapInType } /// - /// 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; } /// - /// Name of PSSnapIn module + /// Name of PSSnapIn module. /// public string ModuleName { get; } @@ -245,7 +217,7 @@ internal string AbsoluteModulePath { get { - if (String.IsNullOrEmpty(ModuleName) || Path.IsPathRooted(ModuleName)) + if (string.IsNullOrEmpty(ModuleName) || Path.IsPathRooted(ModuleName)) { return ModuleName; } @@ -259,17 +231,12 @@ internal string AbsoluteModulePath } /// - /// Type of custom mshsnapin. - /// - internal string CustomPSSnapInType { get; } - - /// - /// Monad version used by mshsnapin + /// PowerShell version used by PSSnapin. /// public Version PSVersion { get; } /// - /// Version of mshsnapin + /// Version of PSSnapin. /// public Version Version { get; } @@ -279,15 +246,15 @@ internal string AbsoluteModulePath public Collection Types { get; } /// - /// Collection of file names containing format information for PSSnapIn + /// Collection of file names containing format information for PSSnapIn. /// 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 { @@ -297,15 +264,16 @@ public string Description { LoadIndirectResources(); } + return _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 { @@ -315,6 +283,7 @@ public string Vendor { LoadIndirectResources(); } + return _vendor; } } @@ -325,7 +294,7 @@ public string Vendor public bool LogPipelineExecutionDetails { get; set; } = false; /// - /// Overrides ToString + /// Overrides ToString. /// /// /// Name of the PSSnapIn @@ -354,11 +323,11 @@ internal RegistryKey MshSnapinKey catch (System.IO.IOException) { } + return mshsnapinKey; } } - internal void LoadIndirectResources() { using (RegistryStringResourceIndirect resourceReader = RegistryStringResourceIndirect.GetResourceIndirectReader()) @@ -404,52 +373,60 @@ internal void LoadIndirectResources(RegistryStringResourceIndirect resourceReade } } - if (String.IsNullOrEmpty(_description)) + if (string.IsNullOrEmpty(_description)) { _description = _descriptionFallback; } - if (String.IsNullOrEmpty(_vendor)) + if (string.IsNullOrEmpty(_vendor)) { _vendor = _vendorFallback; } } - - internal PSSnapInInfo Clone() { - PSSnapInInfo cloned = new PSSnapInInfo(Name, - IsDefault, ApplicationBase, AssemblyName, - ModuleName, PSVersion, Version, new Collection(Types), - new Collection(Formats), _description, - _descriptionFallback, _descriptionIndirect, _vendor, _vendorFallback, _vendorIndirect, - CustomPSSnapInType - ); + PSSnapInInfo cloned = new PSSnapInInfo( + Name, + IsDefault, + ApplicationBase, + AssemblyName, + ModuleName, + PSVersion, + Version, + new Collection(Types), + new Collection(Formats), + _description, + _descriptionFallback, + _descriptionIndirect, + _vendor, + _vendorFallback, + _vendorIndirect); return cloned; } - /// - /// 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 + /// PSSnapIn Id to validate. internal static bool IsPSSnapinIdValid(string psSnapinId) { - if (String.IsNullOrEmpty(psSnapinId)) + if (string.IsNullOrEmpty(psSnapinId)) { return false; } + return Regex.IsMatch(psSnapinId, "^[A-Za-z0-9-_\x2E]*$"); } /// - /// 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 + /// PSSnapIn Id to validate. /// /// 1. Specified PSSnapIn is not valid /// @@ -459,7 +436,7 @@ internal static void VerifyPSSnapInFormatThrowIfError(string psSnapinId) // argument exception if (!IsPSSnapinIdValid(psSnapinId)) { - throw PSTraceSource.NewArgumentException("mshSnapInId", + throw PSTraceSource.NewArgumentException(nameof(psSnapinId), MshSnapInCmdletResources.InvalidPSSnapInName, psSnapinId); } @@ -469,10 +446,8 @@ internal static void VerifyPSSnapInFormatThrowIfError(string psSnapinId) } } - - /// - /// Internal class to read information about a mshsnapin + /// Internal class to read information about a mshsnapin. /// internal static class PSSnapInReader { @@ -522,24 +497,26 @@ internal static Collection ReadAll() { continue; } - //found a key which is not version + // found a key which is not version if (!MeetsVersionFormat(version)) { continue; } + Collection oneVersionMshSnapins = null; try { oneVersionMshSnapins = ReadAll(monadRootKey, version); } - //If we cannot get information for one version, continue with other - //versions + // If we cannot get information for one version, continue with other + // versions catch (SecurityException) { } catch (ArgumentException) { } + if (oneVersionMshSnapins != null) { foreach (PSSnapInInfo info in oneVersionMshSnapins) @@ -570,12 +547,12 @@ bool MeetsVersionFormat(string version) { r = false; } + return r; } - /// - /// Reads all registered mshsnapin for specified psVersion + /// Reads all registered mshsnapin for specified psVersion. /// /// /// A collection of PSSnapInInfo objects @@ -590,14 +567,15 @@ internal static Collection ReadAll(string psVersion) { if (string.IsNullOrEmpty(psVersion)) { - throw PSTraceSource.NewArgumentNullException("psVersion"); + throw PSTraceSource.NewArgumentNullException(nameof(psVersion)); } + RegistryKey monadRootKey = GetMonadRootKey(); return ReadAll(monadRootKey, psVersion); } /// - /// Reads all the mshsnapins for a given psVersion + /// Reads all the mshsnapins for a given psVersion. /// /// /// The User doesn't have required permission to read the registry key for this version. @@ -617,7 +595,7 @@ private static Collection ReadAll(RegistryKey monadRootKey, string RegistryKey versionRoot = GetVersionRootKey(monadRootKey, psVersion); RegistryKey mshsnapinRoot = GetMshSnapinRootKey(versionRoot, psVersion); - //get name of all mshsnapin for this version + // get name of all mshsnapin for this version string[] mshsnapinIds = mshsnapinRoot.GetSubKeyNames(); foreach (string id in mshsnapinIds) @@ -626,11 +604,12 @@ private static Collection ReadAll(RegistryKey monadRootKey, string { continue; } + try { mshsnapins.Add(ReadOne(mshsnapinRoot, id)); } - //If we cannot read some mshsnapins, we should continue + // If we cannot read some mshsnapins, we should continue catch (SecurityException) { } @@ -642,9 +621,8 @@ private static Collection ReadAll(RegistryKey monadRootKey, string return mshsnapins; } - /// - /// Read mshsnapin for specified mshsnapinId and psVersion + /// Read mshsnapin for specified mshsnapinId and psVersion. /// /// /// MshSnapin info object @@ -666,11 +644,12 @@ internal static PSSnapInInfo Read(string psVersion, string mshsnapinId) { if (string.IsNullOrEmpty(psVersion)) { - throw PSTraceSource.NewArgumentNullException("psVersion"); + throw PSTraceSource.NewArgumentNullException(nameof(psVersion)); } + if (string.IsNullOrEmpty(mshsnapinId)) { - throw PSTraceSource.NewArgumentNullException("mshsnapinId"); + throw PSTraceSource.NewArgumentNullException(nameof(mshsnapinId)); } // PSSnapIn Reader wont service invalid mshsnapins // Monad has specific restrictions on the mshsnapinid like @@ -684,10 +663,8 @@ internal static PSSnapInInfo Read(string psVersion, string mshsnapinId) return ReadOne(mshsnapinRoot, mshsnapinId); } - - /// - /// Reads the mshsnapin info for a specific key under specific monad version + /// Reads the mshsnapin info for a specific key under specific monad version. /// /// /// ReadOne will never create a default PSSnapInInfo object. @@ -710,7 +687,7 @@ private static PSSnapInInfo ReadOne(RegistryKey mshSnapInRoot, string mshsnapinI if (mshsnapinKey == null) { s_mshsnapinTracer.TraceError("Error opening registry key {0}\\{1}.", mshSnapInRoot.Name, mshsnapinId); - throw PSTraceSource.NewArgumentException("mshsnapinId", MshSnapinInfo.MshSnapinDoesNotExist, mshsnapinId); + throw PSTraceSource.NewArgumentException(nameof(mshsnapinId), MshSnapinInfo.MshSnapinDoesNotExist, mshsnapinId); } string applicationBase = ReadStringValue(mshsnapinKey, RegistryStrings.MshSnapin_ApplicationBase, true); @@ -725,6 +702,7 @@ private static PSSnapInInfo ReadOne(RegistryKey mshSnapInRoot, string mshsnapinI s_mshsnapinTracer.WriteLine("No description is specified for mshsnapin {0}. Using empty string for description.", mshsnapinId); description = string.Empty; } + string vendor = ReadStringValue(mshsnapinKey, RegistryStrings.MshSnapin_Vendor, false); if (vendor == null) { @@ -734,30 +712,24 @@ private static PSSnapInInfo ReadOne(RegistryKey mshSnapInRoot, string mshsnapinI bool logPipelineExecutionDetails = false; string logPipelineExecutionDetailsStr = ReadStringValue(mshsnapinKey, RegistryStrings.MshSnapin_LogPipelineExecutionDetails, false); - if (!String.IsNullOrEmpty(logPipelineExecutionDetailsStr)) + if (!string.IsNullOrEmpty(logPipelineExecutionDetailsStr)) { - if (String.Compare("1", logPipelineExecutionDetailsStr, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals("1", logPipelineExecutionDetailsStr, StringComparison.OrdinalIgnoreCase)) logPipelineExecutionDetails = true; } - string customPSSnapInType = ReadStringValue(mshsnapinKey, RegistryStrings.MshSnapin_CustomPSSnapInType, false); - if (string.IsNullOrEmpty(customPSSnapInType)) - { - customPSSnapInType = null; - } - Collection types = ReadMultiStringValue(mshsnapinKey, RegistryStrings.MshSnapin_BuiltInTypes, false); Collection formats = ReadMultiStringValue(mshsnapinKey, RegistryStrings.MshSnapin_BuiltInFormats, false); s_mshsnapinTracer.WriteLine("Successfully read registry values for mshsnapin {0}. Constructing PSSnapInInfo object.", mshsnapinId); - PSSnapInInfo mshSnapinInfo = new PSSnapInInfo(mshsnapinId, false, applicationBase, assemblyName, moduleName, monadVersion, version, types, formats, description, vendor, customPSSnapInType); + PSSnapInInfo mshSnapinInfo = new PSSnapInInfo(mshsnapinId, false, applicationBase, assemblyName, moduleName, monadVersion, version, types, formats, description, vendor); mshSnapinInfo.LogPipelineExecutionDetails = logPipelineExecutionDetails; return mshSnapinInfo; } /// - /// Gets multistring value for name + /// Gets multistring value for name. /// /// /// @@ -776,7 +748,7 @@ private static Collection ReadMultiStringValue(RegistryKey mshsnapinKey, { s_mshsnapinTracer.TraceError("Mandatory property {0} not specified for registry key {1}", name, mshsnapinKey.Name); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.MandatoryValueNotPresent, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.MandatoryValueNotPresent, name, mshsnapinKey.Name); } else { @@ -789,9 +761,8 @@ 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) + // Check if the value is in string format + if (value is string singleValue) { msv = new string[1]; msv[0] = singleValue; @@ -804,7 +775,7 @@ private static Collection ReadMultiStringValue(RegistryKey mshsnapinKey, { s_mshsnapinTracer.TraceError("Cannot get string/multi-string value for mandatory property {0} in registry key {1}", name, mshsnapinKey.Name); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.MandatoryValueNotInCorrectFormatMultiString, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.MandatoryValueNotInCorrectFormatMultiString, name, mshsnapinKey.Name); } else { @@ -818,7 +789,7 @@ private static Collection ReadMultiStringValue(RegistryKey mshsnapinKey, } /// - /// Get the value for name + /// Get the value for name. /// /// /// @@ -833,19 +804,19 @@ internal static string ReadStringValue(RegistryKey mshsnapinKey, string name, bo Dbg.Assert(mshsnapinKey != null, "Caller should validate the parameter"); object value = mshsnapinKey.GetValue(name); - if (value == null && mandatory == true) + if (value == null && mandatory) { s_mshsnapinTracer.TraceError("Mandatory property {0} not specified for registry key {1}", name, mshsnapinKey.Name); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.MandatoryValueNotPresent, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.MandatoryValueNotPresent, name, mshsnapinKey.Name); } string s = value as string; - if (string.IsNullOrEmpty(s) && mandatory == true) + if (string.IsNullOrEmpty(s) && mandatory) { s_mshsnapinTracer.TraceError("Value is null or empty for mandatory property {0} in {1}", name, mshsnapinKey.Name); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.MandatoryValueNotInCorrectFormat, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.MandatoryValueNotInCorrectFormat, name, mshsnapinKey.Name); } s_mshsnapinTracer.WriteLine("Successfully read value {0} for property {1} from {2}", @@ -872,38 +843,40 @@ internal static Version ReadVersionValue(RegistryKey mshsnapinKey, string name, catch (ArgumentOutOfRangeException) { s_mshsnapinTracer.TraceError("Cannot convert value {0} to version format", temp); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); } catch (ArgumentException) { s_mshsnapinTracer.TraceError("Cannot convert value {0} to version format", temp); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); } catch (OverflowException) { s_mshsnapinTracer.TraceError("Cannot convert value {0} to version format", temp); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); } catch (FormatException) { s_mshsnapinTracer.TraceError("Cannot convert value {0} to version format", temp); - throw PSTraceSource.NewArgumentException("name", MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); + throw PSTraceSource.NewArgumentException(nameof(name), MshSnapinInfo.VersionValueInCorrect, name, mshsnapinKey.Name); } s_mshsnapinTracer.WriteLine("Successfully converted string {0} to version format.", v); 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)); + Dbg.Assert( + !string.IsNullOrEmpty(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)); + Dbg.Assert( + psVersion != null, + 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 @@ -912,28 +885,25 @@ 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).GetTypeInfo().Assembly; - assemblyVersion = currentAssembly.GetName().Version; - byte[] publicTokens = currentAssembly.GetName().GetPublicKeyToken(); + AssemblyName assemblyName = typeof(PSSnapInReader).Assembly.GetName(); + assemblyVersion = assemblyName.Version; + byte[] publicTokens = assemblyName.GetPublicKeyToken(); if (publicTokens.Length == 0) { throw PSTraceSource.NewArgumentException("PublicKeyToken", MshSnapinInfo.PublicKeyTokenAccessFailed); } + publicKeyToken = ConvertByteArrayToString(publicTokens); + // 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"; } /// - /// PublicKeyToken is in the form of byte[]. Use this function to convert to a string + /// PublicKeyToken is in the form of byte[]. Use this function to convert to a string. /// - /// array of byte's + /// Array of byte's. /// internal static string ConvertByteArrayToString(byte[] tokens) { @@ -948,74 +918,100 @@ internal static string ConvertByteArrayToString(byte[] tokens) } /// - /// Reads core snapin for monad engine + /// Reads core snapin for monad engine. /// /// /// A PSSnapInInfo object /// 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"); - PSSnapInInfo coreMshSnapin = new PSSnapInInfo(s_coreSnapin.PSSnapInName, true, applicationBase, - strongName, moduleName, psVersion, assemblyVersion, types, formats, null, - s_coreSnapin.Description, s_coreSnapin.DescriptionIndirect, null, null, - s_coreSnapin.VendorIndirect, null); + PSSnapInInfo coreMshSnapin = new PSSnapInInfo( + s_coreSnapin.PSSnapInName, + isDefault: true, + applicationBase, + strongName, + moduleName, + psVersion, + assemblyVersion, + types, + formats, + description: null, + s_coreSnapin.Description, + s_coreSnapin.DescriptionIndirect, + vendor: null, + vendorFallback: null, + s_coreSnapin.VendorIndirect); +#if !UNIX + // NOTE: On Unix, logging has to be deferred until after command-line parsing + // complete. On Windows, deferring the call is not needed + // and this is in the startup code path. SetSnapInLoggingInformation(coreMshSnapin); +#endif return coreMshSnapin; } /// - /// Reads all registered mshsnapins for currently executing monad engine + /// Reads all registered mshsnapins for currently executing monad engine. /// /// /// A collection of PSSnapInInfo objects /// 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" }); // create default mshsnapininfo objects.. Collection engineMshSnapins = new Collection(); + string assemblyVersionString = assemblyVersion.ToString(); for (int item = 0; item < DefaultMshSnapins.Count; item++) { DefaultPSSnapInInformation defaultMshSnapinInfo = DefaultMshSnapins[item]; - string strongName = string.Format(CultureInfo.InvariantCulture, "{0}, Version={1}, Culture={2}, PublicKeyToken={3}, ProcessorArchitecture={4}", - defaultMshSnapinInfo.AssemblyName, assemblyVersion.ToString(), culture, publicKeyToken, architecture); + string strongName = string.Format( + CultureInfo.InvariantCulture, + "{0}, Version={1}, Culture={2}, PublicKeyToken={3}", + defaultMshSnapinInfo.AssemblyName, + assemblyVersionString, + culture, + publicKeyToken); Collection formats = null; Collection types = null; @@ -1025,8 +1021,7 @@ internal static Collection ReadEnginePSSnapIns() formats = smaFormats; types = smaTypes; } - else if (defaultMshSnapinInfo.AssemblyName.Equals("Microsoft.PowerShell.Commands.Diagnostics", - StringComparison.OrdinalIgnoreCase)) + else if (defaultMshSnapinInfo.AssemblyName.Equals("Microsoft.PowerShell.Commands.Diagnostics", StringComparison.OrdinalIgnoreCase)) { types = new Collection(new string[] { "GetEvent.types.ps1xml" }); formats = new Collection(new string[] { "Event.format.ps1xml", "Diagnostics.format.ps1xml" }); @@ -1038,19 +1033,22 @@ internal static Collection ReadEnginePSSnapIns() string moduleName = Path.Combine(applicationBase, defaultMshSnapinInfo.AssemblyName + ".dll"); - if (File.Exists(moduleName)) - { - moduleName = Path.Combine(applicationBase, defaultMshSnapinInfo.AssemblyName + ".dll"); - } - else - { - moduleName = defaultMshSnapinInfo.AssemblyName; - } - - PSSnapInInfo defaultMshSnapin = new PSSnapInInfo(defaultMshSnapinInfo.PSSnapInName, true, applicationBase, - strongName, moduleName, psVersion, assemblyVersion, types, formats, null, - defaultMshSnapinInfo.Description, defaultMshSnapinInfo.DescriptionIndirect, null, null, - defaultMshSnapinInfo.VendorIndirect, null); + PSSnapInInfo defaultMshSnapin = new PSSnapInInfo( + defaultMshSnapinInfo.PSSnapInName, + isDefault: true, + applicationBase, + strongName, + moduleName, + psVersion, + assemblyVersion, + types, + formats, + description: null, + defaultMshSnapinInfo.Description, + defaultMshSnapinInfo.DescriptionIndirect, + vendor: null, + vendorFallback: null, + defaultMshSnapinInfo.VendorIndirect); SetSnapInLoggingInformation(defaultMshSnapin); engineMshSnapins.Add(defaultMshSnapin); @@ -1060,11 +1058,11 @@ internal static Collection ReadEnginePSSnapIns() } /// - /// Enable Snapin logging based on group policy + /// Enable Snapin logging based on group policy. /// private static void SetSnapInLoggingInformation(PSSnapInInfo psSnapInInfo) { - IEnumerable names; + IEnumerable names; ModuleCmdletBase.ModuleLoggingGroupPolicyStatus status = ModuleCmdletBase.GetModuleLoggingInformation(out names); if (status != ModuleCmdletBase.ModuleLoggingGroupPolicyStatus.Undefined) { @@ -1073,7 +1071,7 @@ private static void SetSnapInLoggingInformation(PSSnapInInfo psSnapInInfo) } /// - /// Enable Snapin logging based on group policy + /// Enable Snapin logging based on group policy. /// private static void SetSnapInLoggingInformation(PSSnapInInfo psSnapInInfo, ModuleCmdletBase.ModuleLoggingGroupPolicyStatus status, IEnumerable moduleOrSnapinNames) { @@ -1098,7 +1096,7 @@ private static void SetSnapInLoggingInformation(PSSnapInInfo psSnapInInfo, Modul } /// - /// Get the key to monad root + /// Get the key to monad root. /// /// /// @@ -1112,19 +1110,20 @@ internal static RegistryKey GetMonadRootKey() RegistryKey rootKey = Registry.LocalMachine.OpenSubKey(RegistryStrings.MonadRootKeyPath); if (rootKey == null) { - //This should never occur because this code is running - //because monad is installed. { well this can occur if someone - //deletes the registry key after starting monad + // This should never occur because this code is running + // because monad is installed. { well this can occur if someone + // deletes the registry key after starting monad Dbg.Assert(false, "Root Key of Monad installation is not present"); throw PSTraceSource.NewArgumentException("monad", MshSnapinInfo.MonadRootRegistryAccessFailed); } + return rootKey; } /// /// Get the registry key to PSEngine. /// - /// RegistryKey + /// RegistryKey. /// Major version in string format. /// /// Monad registration information is not available. @@ -1155,7 +1154,7 @@ internal static RegistryKey GetPSEngineKey(string psVersion) } /// - /// Gets the version root key for specified monad version + /// Gets the version root key for specified monad version. /// /// /// @@ -1177,13 +1176,14 @@ internal static RegistryKey versionRoot = rootKey.OpenSubKey(versionKey); if (versionRoot == null) { - throw PSTraceSource.NewArgumentException("psVersion", MshSnapinInfo.SpecifiedVersionNotFound, versionKey); + throw PSTraceSource.NewArgumentException(nameof(psVersion), MshSnapinInfo.SpecifiedVersionNotFound, versionKey); } + return versionRoot; } /// - /// Gets the mshsnapin root key for specified monad version + /// Gets the mshsnapin root key for specified monad version. /// /// /// @@ -1203,13 +1203,14 @@ private static RegistryKey mshsnapinRoot = versionRootKey.OpenSubKey(RegistryStrings.MshSnapinKey); if (mshsnapinRoot == null) { - throw PSTraceSource.NewArgumentException("psVersion", MshSnapinInfo.NoMshSnapinPresentForVersion, psVersion); + throw PSTraceSource.NewArgumentException(nameof(psVersion), MshSnapinInfo.NoMshSnapinPresentForVersion, psVersion); } + return mshsnapinRoot; } /// - /// Gets the mshsnapin key for specified monad version and mshsnapin name + /// Gets the mshsnapin key for specified monad version and mshsnapin name. /// /// /// @@ -1229,8 +1230,9 @@ internal static RegistryKey mshsnapinRoot = versionRootKey.OpenSubKey(RegistryStrings.MshSnapinKey); if (mshsnapinRoot == null) { - throw PSTraceSource.NewArgumentException("psVersion", MshSnapinInfo.NoMshSnapinPresentForVersion, psVersion); + throw PSTraceSource.NewArgumentException(nameof(psVersion), MshSnapinInfo.NoMshSnapinPresentForVersion, psVersion); } + RegistryKey mshsnapinKey = mshsnapinRoot.OpenSubKey(mshSnapInName); return mshsnapinKey; } @@ -1268,9 +1270,7 @@ public DefaultPSSnapInInformation(string sName, new DefaultPSSnapInInformation("Microsoft.PowerShell.Core", "System.Management.Automation", null, "CoreMshSnapInResources,Description", "CoreMshSnapInResources,Vendor"); - /// - /// /// private static IList DefaultMshSnapins { @@ -1280,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() { @@ -1289,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 @@ -1317,13 +1319,12 @@ private static IList DefaultMshSnapins return s_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 c3ac0e252d8..71c81611440 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs @@ -1,9 +1,8 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; using System.Reflection; +using System.Runtime.Serialization; using System.Security.Permissions; namespace System.Management.Automation.Runspaces @@ -20,13 +19,12 @@ namespace System.Management.Automation.Runspaces /// 1. PSSnapin name /// 2. Inner exception. /// --> - [Serializable] public class PSSnapInException : RuntimeException { /// /// Initiate an instance of PSSnapInException. /// - /// PSSnapin for the exception + /// PSSnapin for the exception. /// Message with load failure detail. internal PSSnapInException(string PSSnapin, string message) : base() @@ -39,7 +37,7 @@ internal PSSnapInException(string PSSnapin, string message) /// /// Initiate an instance of PSSnapInException. /// - /// PSSnapin for the exception + /// PSSnapin for the exception. /// Message with load failure detail. /// Whether this is just a warning for PSSnapin load. internal PSSnapInException(string PSSnapin, string message, bool warning) @@ -54,9 +52,9 @@ internal PSSnapInException(string PSSnapin, string message, bool warning) /// /// Initiate an instance of PSSnapInException. /// - /// PSSnapin for the exception + /// PSSnapin for the exception. /// Message with load failure detail. - /// Exception for PSSnapin load failure + /// Exception for PSSnapin load failure. internal PSSnapInException(string PSSnapin, string message, Exception exception) : base(message, exception) { @@ -75,7 +73,7 @@ public PSSnapInException() : base() /// /// Initiate an instance of PSSnapInException. /// - /// Error message + /// Error message. public PSSnapInException(string message) : base(message) { @@ -84,8 +82,8 @@ public PSSnapInException(string message) /// /// Initiate an instance of PSSnapInException. /// - /// Error message - /// Inner exception + /// Error message. + /// Inner exception. public PSSnapInException(string message, Exception innerException) : base(message, innerException) { @@ -100,24 +98,24 @@ private void CreateErrorRecord() // if _PSSnapin or _reason is empty, this exception is created using default // constructor. Don't create the error record since there is // no useful information anyway. - if (!String.IsNullOrEmpty(_PSSnapin) && !String.IsNullOrEmpty(_reason)) + if (!string.IsNullOrEmpty(_PSSnapin) && !string.IsNullOrEmpty(_reason)) { - Assembly currentAssembly = typeof(PSSnapInException).GetTypeInfo().Assembly; + Assembly currentAssembly = typeof(PSSnapInException).Assembly; if (_warning) { _errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(this), "PSSnapInLoadWarning", ErrorCategory.ResourceUnavailable, null); - _errorRecord.ErrorDetails = new ErrorDetails(String.Format(ConsoleInfoErrorStrings.PSSnapInLoadWarning, _PSSnapin, _reason)); + _errorRecord.ErrorDetails = new ErrorDetails(string.Format(ConsoleInfoErrorStrings.PSSnapInLoadWarning, _PSSnapin, _reason)); } else { _errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(this), "PSSnapInLoadFailure", ErrorCategory.ResourceUnavailable, null); - _errorRecord.ErrorDetails = new ErrorDetails(String.Format(ConsoleInfoErrorStrings.PSSnapInLoadFailure, _PSSnapin, _reason)); + _errorRecord.ErrorDetails = new ErrorDetails(string.Format(ConsoleInfoErrorStrings.PSSnapInLoadFailure, _PSSnapin, _reason)); } } } - private bool _warning = false; + private readonly bool _warning = false; private ErrorRecord _errorRecord; private bool _isErrorRecordOriginallyNull; @@ -133,7 +131,7 @@ public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) + if (_errorRecord == null) { _isErrorRecordOriginallyNull = true; _errorRecord = new ErrorRecord( @@ -142,12 +140,13 @@ public override ErrorRecord ErrorRecord ErrorCategory.NotSpecified, null); } + return _errorRecord; } } - private string _PSSnapin = ""; - private string _reason = ""; + private readonly string _PSSnapin = string.Empty; + private readonly string _reason = string.Empty; /// /// Gets message for this exception. @@ -170,38 +169,15 @@ public override string Message /// /// Initiate a PSSnapInException instance. /// - /// Serialization information - /// Streaming context + /// 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("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 5843ec7308a..00000000000 --- a/src/System.Management.Automation/utils/ArchitectureSensitiveAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ - -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 724ce27bb39..ac74a32d352 100644 --- a/src/System.Management.Automation/utils/BackgroundDispatcher.cs +++ b/src/System.Management.Automation/utils/BackgroundDispatcher.cs @@ -1,8 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -73,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 1aca451d08c..058c33a68bf 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -1,25 +1,16 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// 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.Loader; +using System.Security; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Security; -using Microsoft.Win32.SafeHandles; -using System.Runtime.InteropServices.ComTypes; 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); } } @@ -50,7 +51,7 @@ internal static IEnumerable GetAssemblies(TypeResolutionState typeReso } /// - /// Facade for AppDomain.GetAssemblies + /// Facade for AppDomain.GetAssemblies. /// /// /// In CoreCLR context, if it's for string-to-type conversion and the namespace qualified type name is known, pass it in so that @@ -58,19 +59,46 @@ internal static IEnumerable GetAssemblies(TypeResolutionState typeReso /// internal static IEnumerable GetAssemblies(string namespaceQualifiedTypeName = null) { - return PSAssemblyLoadContext.GetAssembly(namespaceQualifiedTypeName) ?? - AppDomain.CurrentDomain.GetAssemblies().Where(a => - !TypeDefiner.DynamicClassAssemblyName.Equals(a.GetName().Name, StringComparison.Ordinal)); + return PSAssemblyLoadContext.GetAssembly(namespaceQualifiedTypeName) ?? GetPSVisibleAssemblies(); } /// - /// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell Core. + /// Return assemblies from the default load context and the 'individual' load contexts. + /// The 'individual' load contexts are the ones holding assemblies loaded via 'Assembly.Load(byte[])' and 'Assembly.LoadFile'. + /// Assemblies loaded in any custom load contexts are not consider visible to PowerShell to avoid type identity issues. + /// + private static IEnumerable GetPSVisibleAssemblies() + { + const string IndividualAssemblyLoadContext = "System.Runtime.Loader.IndividualAssemblyLoadContext"; + + foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies) + { + if (!assembly.FullName.StartsWith(TypeDefiner.DynamicClassAssemblyFullNamePrefix, StringComparison.Ordinal)) + { + yield return assembly; + } + } + + foreach (AssemblyLoadContext context in AssemblyLoadContext.All) + { + if (IndividualAssemblyLoadContext.Equals(context.GetType().FullName, StringComparison.Ordinal)) + { + foreach (Assembly assembly in context.Assemblies) + { + yield return assembly; + } + } + } + } + + /// + /// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell. /// This is used for type name auto-completion in PS engine. /// internal static IEnumerable AvailableDotNetTypeNames => PSAssemblyLoadContext.AvailableDotNetTypeNames; /// - /// Get the assembly names of all available .NET Core assemblies shipped with PowerShell Core. + /// Get the assembly names of all available .NET Core assemblies shipped with PowerShell. /// This is used for type name auto-completion in PS engine. /// internal static HashSet AvailableDotNetAssemblyNames => PSAssemblyLoadContext.AvailableDotNetAssemblyNames; @@ -81,52 +109,27 @@ internal static IEnumerable GetAssemblies(string namespaceQualifiedTyp #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 + /// OEM encodings work on all platforms, or rather codepage 437 is available on both Windows and Non-Windows. /// 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 } + return s_oemEncoding; } 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 @@ -138,21 +141,12 @@ private static void EncodingRegisterProvider() internal static SecurityZone GetFileSecurityZone(string filePath) { Diagnostics.Assert(Path.IsPathRooted(filePath), "Caller makes sure the path is rooted."); - Diagnostics.Assert(Utils.NativeFileExists(filePath), "Caller makes sure the file exists."); - string sysRoot = System.Environment.GetEnvironmentVariable("SystemRoot"); - string urlmonPath = Path.Combine(sysRoot, @"System32\urlmon.dll"); - if (Utils.NativeFileExists(urlmonPath)) - { - return MapSecurityZoneWithUrlmon(filePath); - } - return MapSecurityZoneWithoutUrlmon(filePath); + Diagnostics.Assert(File.Exists(filePath), "Caller makes sure the file exists."); + return MapSecurityZone(filePath); } - #region WithoutUrlmon - /// - /// Map the file to SecurityZone without using urlmon.dll. - /// This is needed on NanoServer because urlmon.dll is not in OneCore. + /// Map the file to SecurityZone. /// /// /// The algorithm is as follows: @@ -187,10 +181,21 @@ internal static SecurityZone GetFileSecurityZone(string filePath) /// (2) When it's a UNC path and is actually a loopback (\\127.0.0.1\c$\test.txt), "Zone.CreateFromUrl" returns "Internet", but /// the above algorithm changes it to be "MyComputer" because it's actually the same computer. /// - private static SecurityZone MapSecurityZoneWithoutUrlmon(string filePath) + private static SecurityZone MapSecurityZone(string filePath) { + // WSL introduces a new filesystem path to access the Linux filesystem from Windows, like '\\wsl$\ubuntu'. + // If the given file path is such a special case, we consider it's in 'MyComputer' zone. + if (filePath.StartsWith(Utils.WslRootPath, StringComparison.OrdinalIgnoreCase)) + { + return SecurityZone.MyComputer; + } + SecurityZone reval = ReadFromZoneIdentifierDataStream(filePath); - if (reval != SecurityZone.NoZone) { return reval; } + if (reval != SecurityZone.NoZone) + { + return reval; + } + // If it reaches here, then we either couldn't get the ZoneId information, or the ZoneId is invalid. // In this case, we try to determine the SecurityZone by analyzing the file path. Uri uri = new Uri(filePath); @@ -211,7 +216,7 @@ private static SecurityZone MapSecurityZoneWithoutUrlmon(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.IndexOf('.') == -1 ? SecurityZone.Intranet : SecurityZone.Internet; + return hostName.Contains('.') ? SecurityZone.Internet : SecurityZone.Intranet; } string root = Path.GetPathRoot(filePath); @@ -234,116 +239,65 @@ private static SecurityZone MapSecurityZoneWithoutUrlmon(string filePath) /// private static SecurityZone ReadFromZoneIdentifierDataStream(string filePath) { - try + if (!AlternateDataStreamUtilities.TryCreateFileStream(filePath, "Zone.Identifier", FileMode.Open, FileAccess.Read, FileShare.Read, out var zoneDataStream)) { - FileStream zoneDataSteam = AlternateDataStreamUtilities.CreateFileStream( - filePath, "Zone.Identifier", FileMode.Open, - FileAccess.Read, FileShare.Read); + return SecurityZone.NoZone; + } - // If we successfully get the zone data stream, try to read the ZoneId information - using (StreamReader zoneDataReader = new StreamReader(zoneDataSteam, GetDefaultEncoding())) + // If we successfully get the zone data stream, try to read the ZoneId information + using (StreamReader zoneDataReader = new StreamReader(zoneDataStream, Encoding.Default)) + { + string line = null; + bool zoneTransferMatched = false; + + // After a lot experiments with Zone.CreateFromUrl/Zone.SecurityZone, the way it handles the alternate + // data stream 'Zone.Identifier' is observed as follows: + // 1. Read content of the data stream line by line. Each line is trimmed. + // 2. Try to match the current line with '^\[ZoneTransfer\]'. + // - if matching, then do step #3 starting from the next line + // - if not matching, then continue to do step #2 with the next line. + // 3. Try to match the current line with '^ZoneId\s*=\s*(.*)' + // - if matching, check if the ZoneId is valid. Then return the corresponding SecurityZone if valid, or 'NoZone' if invalid. + // - if not matching, then continue to do step #3 with the next line. + // 4. Reach EOF, then return 'NoZone'. + while ((line = zoneDataReader.ReadLine()) != null) { - string line = null; - bool zoneTransferMatched = false; - - // After a lot experiments with Zone.CreateFromUrl/Zone.SecurityZone, the way it handles the alternate - // data stream 'Zone.Identifier' is observed as follows: - // 1. Read content of the data stream line by line. Each line is trimmed. - // 2. Try to match the current line with '^\[ZoneTransfer\]'. - // - if matching, then do step #3 starting from the next line - // - if not matching, then continue to do step #2 with the next line. - // 3. Try to match the current line with '^ZoneId\s*=\s*(.*)' - // - if matching, check if the ZoneId is valid. Then return the corresponding SecurityZone if valid, or 'NoZone' if invalid. - // - if not matching, then continue to do step #3 with the next line. - // 4. Reach EOF, then return 'NoZone'. - while ((line = zoneDataReader.ReadLine()) != null) + line = line.Trim(); + if (!zoneTransferMatched) + { + zoneTransferMatched = Regex.IsMatch(line, @"^\[ZoneTransfer\]", RegexOptions.IgnoreCase); + } + else { - line = line.Trim(); - if (!zoneTransferMatched) + Match match = Regex.Match(line, @"^ZoneId\s*=\s*(.*)", RegexOptions.IgnoreCase); + if (!match.Success) { - zoneTransferMatched = Regex.IsMatch(line, @"^\[ZoneTransfer\]", RegexOptions.IgnoreCase); + continue; } - else - { - Match match = Regex.Match(line, @"^ZoneId\s*=\s*(.*)", RegexOptions.IgnoreCase); - 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; } - string zoneId = match.Groups[0].Value; - SecurityZone result; - return LanguagePrimitives.TryConvertTo(zoneId, out result) ? result : SecurityZone.NoZone; + // Match found. Validate ZoneId value. + string zoneIdRawValue = match.Groups[1].Value; + match = Regex.Match(zoneIdRawValue, @"^[+-]?\d+", RegexOptions.IgnoreCase); + if (!match.Success) + { + return SecurityZone.NoZone; } + + string zoneId = match.Groups[0].Value; + SecurityZone result; + return LanguagePrimitives.TryConvertTo(zoneId, out result) ? result : SecurityZone.NoZone; } } } - catch (FileNotFoundException) - { - // FileNotFoundException may be thrown by AlternateDataStreamUtilities.CreateFileStream when the data stream 'Zone.Identifier' - // does not exist, or when the underlying file system doesn't support alternate data stream. - } return SecurityZone.NoZone; } - #endregion WithoutUrlmon - - /// - /// Map the file to SecurityZone using urlmon.dll, depending on 'IInternetSecurityManager::MapUrlToZone'. - /// - private static SecurityZone MapSecurityZoneWithUrlmon(string filePath) - { - uint zoneId; - object curSecMgr = null; - const UInt32 MUTZ_DONT_USE_CACHE = 0x00001000; - - int hr = NativeMethods.CoInternetCreateSecurityManager(null, out curSecMgr, 0); - if (hr != NativeMethods.S_OK) - { - // Returns an error value if it's not S_OK - throw new System.ComponentModel.Win32Exception(hr); - } - - try - { - NativeMethods.IInternetSecurityManager ism = (NativeMethods.IInternetSecurityManager)curSecMgr; - hr = ism.MapUrlToZone(filePath, out zoneId, MUTZ_DONT_USE_CACHE); - if (hr == NativeMethods.S_OK) - { - SecurityZone result; - return LanguagePrimitives.TryConvertTo(zoneId, out result) ? result : SecurityZone.NoZone; - } - return SecurityZone.NoZone; - } - finally - { - if (curSecMgr != null) - { - Marshal.ReleaseComObject(curSecMgr); - } - } - } #endregion Security #endif #region Misc - /// - /// Facade for RemotingServices.IsTransparentProxy(object) - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsTransparentProxy(object obj) - { -#if CORECLR // Namespace System.Runtime.Remoting is not in CoreCLR - return false; -#else - return System.Runtime.Remoting.RemotingServices.IsTransparentProxy(obj); -#endif - } - /// /// Facade for ManagementDateTimeConverter.ToDmtfDateTime(DateTime) /// @@ -356,12 +310,12 @@ internal static string ToDmtfDateTime(DateTime date) // it's recommended to use TimeZoneInfo.Local whenever possible. const int maxsizeUtcDmtf = 999; - string UtcString = String.Empty; + string UtcString = string.Empty; // Fill up the UTC field in the DMTF date with the current zones UTC value TimeZoneInfo curZone = TimeZoneInfo.Local; TimeSpan tickOffset = curZone.GetUtcOffset(date); long OffsetMins = (tickOffset.Ticks / TimeSpan.TicksPerMinute); - IFormatProvider frmInt32 = (IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(Int32)); + IFormatProvider frmInt32 = (IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(int)); // If the offset is more than that what can be specified in DMTF format, then // convert the date to UniversalTime @@ -383,27 +337,28 @@ 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; // fill the microseconds field - String strMicrosec = microsec.ToString((IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(Int64))); + string strMicrosec = microsec.ToString((IFormatProvider)CultureInfo.InvariantCulture.GetFormat(typeof(Int64))); if (strMicrosec.Length > 6) { 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 @@ -411,98 +366,6 @@ internal static string ToDmtfDateTime(DateTime date) #endif } - /// - /// Facade for ProfileOptimization.SetProfileRoot - /// - /// The full path to the folder where profile files are stored for the current application domain. - internal static void SetProfileOptimizationRoot(string directoryPath) - { - PSAssemblyLoadContext.SetProfileOptimizationRootImpl(directoryPath); - } - - /// - /// Facade for ProfileOptimization.StartProfile - /// - /// The file name of the profile to use. - internal static void StartProfileOptimization(string profile) - { - PSAssemblyLoadContext.StartProfileOptimizationImpl(profile); - } - #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(); - - /// - /// Pinvoke for GetACP to get the Windows operating system code page. - /// - [DllImport(PinvokeDllNames.GetACPDllName, SetLastError = false, CharSet = CharSet.Unicode)] - internal static extern uint GetACP(); - - public const int S_OK = 0x00000000; - - /// - /// Pinvoke to create an IInternetSecurityManager interface.. - /// - [DllImport("urlmon.dll", ExactSpelling = true)] - internal static extern int CoInternetCreateSecurityManager([MarshalAs(UnmanagedType.Interface)] object pIServiceProvider, - [MarshalAs(UnmanagedType.Interface)] out object ppISecurityManager, - int dwReserved); - - /// - /// IInternetSecurityManager interface - /// - [ComImport, ComVisible(false), Guid("79EAC9EE-BAF9-11CE-8C82-00AA004BA90B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IInternetSecurityManager - { - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int SetSecuritySite([In] IntPtr pSite); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int GetSecuritySite([Out] IntPtr pSite); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int MapUrlToZone([In, MarshalAs(UnmanagedType.LPWStr)] string pwszUrl, out uint pdwZone, uint dwFlags); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int GetSecurityId([MarshalAs(UnmanagedType.LPWStr)] string pwszUrl, - [MarshalAs(UnmanagedType.LPArray)] byte[] pbSecurityId, - ref uint pcbSecurityId, uint dwReserved); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int ProcessUrlAction([In, MarshalAs(UnmanagedType.LPWStr)] string pwszUrl, - uint dwAction, out byte pPolicy, uint cbPolicy, - byte pContext, uint cbContext, uint dwFlags, - uint dwReserved); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int QueryCustomPolicy([In, MarshalAs(UnmanagedType.LPWStr)] string pwszUrl, - ref Guid guidKey, ref byte ppPolicy, ref uint pcbPolicy, - ref byte pContext, uint cbContext, uint dwReserved); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int SetZoneMapping(uint dwZone, [In, MarshalAs(UnmanagedType.LPWStr)] string lpszPattern, uint dwFlags); - - [return: MarshalAs(UnmanagedType.I4)] - [PreserveSig] - int GetZoneMappings(uint dwZone, out IEnumString ppenumString, uint dwFlags); - } - } } } diff --git a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs index 41fdfed9efd..89580932359 100644 --- a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs +++ b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs @@ -1,43 +1,35 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; -using System.Text; using System.Collections.ObjectModel; using System.Management.Automation.Internal; -using System.Security.Permissions; +using System.Runtime.Serialization; +using System.Text; namespace System.Management.Automation { /// /// This exception is thrown when a command cannot be found. /// - [Serializable] public class CommandNotFoundException : RuntimeException { /// /// Constructs a CommandNotFoundException. This is the recommended constructor. /// - /// /// /// The name of the command that could not be found. /// - /// /// /// The inner exception. /// - /// /// /// This string is message template string /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// DiscoveryExceptions.txt. /// - /// /// /// Additional arguments to format into the message. /// @@ -56,78 +48,43 @@ internal CommandNotFoundException( /// /// Constructs a CommandNotFoundException. /// - public CommandNotFoundException() : base() {; } + public CommandNotFoundException() : base() { } /// - /// Constructs a CommandNotFoundException + /// Constructs a CommandNotFoundException. /// - /// /// /// The message used in the exception. /// - public CommandNotFoundException(string message) : base(message) {; } + public CommandNotFoundException(string message) : base(message) { } /// - /// Constructs a CommandNotFoundException + /// Constructs a CommandNotFoundException. /// - /// /// /// The message used in the exception. /// - /// /// /// An exception that led to this exception. /// - public CommandNotFoundException(string message, Exception innerException) : base(message, innerException) {; } + public CommandNotFoundException(string message, Exception innerException) : base(message, innerException) { } - #region Serialization /// - /// Serialization constructor for class CommandNotFoundException + /// Serialization constructor for class CommandNotFoundException. /// - /// /// /// serialization information /// - /// /// /// 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("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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("CommandName", _commandName); - } - #endregion Serialization - #region Properties /// /// Gets the ErrorRecord information for this exception. @@ -136,17 +93,16 @@ public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _commandName); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _commandName); + return _errorRecord; } } + private ErrorRecord _errorRecord; /// @@ -155,15 +111,17 @@ public override ErrorRecord ErrorRecord public string CommandName { get { return _commandName; } + set { _commandName = value; } } - private string _commandName = String.Empty; + + private string _commandName = string.Empty; #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, @@ -172,7 +130,7 @@ params object[] messageArgs ) { object[] a; - if (null != messageArgs && 0 < messageArgs.Length) + if (messageArgs != null && messageArgs.Length > 0) { a = new object[messageArgs.Length + 1]; a[0] = commandName; @@ -183,6 +141,7 @@ params object[] messageArgs a = new object[1]; a[0] = commandName; } + return StringUtil.Format(resourceStr, a); } #endregion Private @@ -191,26 +150,21 @@ 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 { /// /// Constructs an ScriptRequiresException. Recommended constructor for the class for /// #requires -shellId MyShellId. /// - /// /// /// The name of the script containing the #requires statement. /// - /// /// /// The ID of the shell that is incompatible with the current shell. /// - /// /// /// The path to the shell specified in the #requires -shellId statement. /// - /// /// /// The error id for this exception. /// @@ -234,19 +188,15 @@ internal ScriptRequiresException( /// Constructs an ScriptRequiresException. Recommended constructor for the class for /// #requires -version N. /// - /// /// /// The name of the script containing the #requires statement. /// - /// /// /// The Msh version that the script requires. /// - /// /// /// The current Msh version /// - /// /// /// The error id for this exception. /// @@ -258,7 +208,7 @@ internal ScriptRequiresException( : base(BuildMessage(commandName, requiresPSVersion.ToString(), currentPSVersion, false)) { Diagnostics.Assert(!string.IsNullOrEmpty(commandName), "commandName is null or empty when constructing ScriptRequiresException"); - Diagnostics.Assert(null != requiresPSVersion, "requiresPSVersion is null or empty when constructing ScriptRequiresException"); + Diagnostics.Assert(requiresPSVersion != null, "requiresPSVersion is null or empty when constructing ScriptRequiresException"); Diagnostics.Assert(!string.IsNullOrEmpty(errorId), "errorId is null or empty when constructing ScriptRequiresException"); _commandName = commandName; _requiresPSVersion = requiresPSVersion; @@ -271,19 +221,15 @@ internal ScriptRequiresException( /// Constructs an ScriptRequiresException. Recommended constructor for the class for the /// #requires -PSSnapin MyPSSnapIn statement. /// - /// /// /// The name of the script containing the #requires statement. /// - /// /// /// The missing snap-ins/modules that the script requires. /// - /// /// /// /// Indicates whether the error message needs to be constructed for missing snap-ins/ missing modules. /// - /// /// /// The error id for this exception. /// @@ -300,23 +246,18 @@ internal ScriptRequiresException( /// Constructs an ScriptRequiresException. Recommended constructor for the class for the /// #requires -PSSnapin MyPSSnapIn statement. /// - /// /// /// The name of the script containing the #requires statement. /// - /// /// /// The missing snap-ins/modules that the script requires. /// - /// /// /// /// Indicates whether the error message needs to be constructed for missing snap-ins/ missing modules. /// - /// /// /// The error id for this exception. /// - /// /// /// The error Record for this exception. /// @@ -342,11 +283,9 @@ internal ScriptRequiresException( /// Constructs an ScriptRequiresException. Recommended constructor for the class for /// #requires -RunAsAdministrator statement. /// - /// /// /// The name of the script containing the #requires statement. /// - /// /// /// The error id for this exception. /// @@ -364,80 +303,46 @@ internal ScriptRequiresException( } /// - /// Constructs an PSVersionNotCompatibleException + /// Constructs an PSVersionNotCompatibleException. /// - public ScriptRequiresException() : base() {; } + public ScriptRequiresException() : base() { } /// - /// Constructs an PSVersionNotCompatibleException + /// Constructs an PSVersionNotCompatibleException. /// - /// /// /// The message used in the exception. /// - public ScriptRequiresException(string message) : base(message) {; } + public ScriptRequiresException(string message) : base(message) { } /// - /// Constructs an PSVersionNotCompatibleException + /// Constructs an PSVersionNotCompatibleException. /// - /// /// /// The message used in the exception. /// - /// /// /// The exception that led to this exception. /// - public ScriptRequiresException(string message, Exception innerException) : base(message, innerException) {; } + public ScriptRequiresException(string message, Exception innerException) : base(message, innerException) { } #region Serialization /// /// Constructs an PSVersionNotCompatibleException using serialized data. /// - /// /// /// serialization information /// - /// /// /// 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("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 @@ -449,25 +354,28 @@ public string CommandName { get { return _commandName; } } - private string _commandName = String.Empty; + + private readonly string _commandName = string.Empty; /// - /// Gets the PSVersion that the script requires + /// Gets the PSVersion that the script requires. /// public Version RequiresPSVersion { get { return _requiresPSVersion; } } - private Version _requiresPSVersion; + + private readonly Version _requiresPSVersion; /// - /// Gets the missing snap-ins that the script requires + /// Gets the missing snap-ins that the script requires. /// public ReadOnlyCollection MissingPSSnapIns { get { return _missingPSSnapIns; } } - private ReadOnlyCollection _missingPSSnapIns = new ReadOnlyCollection(new string[0]); + + private readonly ReadOnlyCollection _missingPSSnapIns = new ReadOnlyCollection(Array.Empty()); /// /// Gets or sets the ID of the shell. @@ -476,23 +384,23 @@ public string RequiresShellId { get { return _requiresShellId; } } - private string _requiresShellId; + + private readonly string _requiresShellId; /// - /// Gets or sets the path to the incompatible shell + /// Gets or sets the path to the incompatible shell. /// public string RequiresShellPath { get { return _requiresShellPath; } } - private string _requiresShellPath; + + private readonly string _requiresShellPath; #endregion Properties #region Private - - private static string BuildMessage( string commandName, Collection missingItems, @@ -501,12 +409,14 @@ private static string BuildMessage( StringBuilder sb = new StringBuilder(); if (missingItems == null) { - throw PSTraceSource.NewArgumentNullException("missingItems"); + throw PSTraceSource.NewArgumentNullException(nameof(missingItems)); } + foreach (string missingItem in missingItems) { sb.Append(missingItem).Append(", "); } + if (sb.Length > 1) { sb.Remove(sb.Length - 2, 2); @@ -538,13 +448,13 @@ private static string BuildMessage( if (forShellId) { - if (String.IsNullOrEmpty(first)) + if (string.IsNullOrEmpty(first)) { resourceStr = DiscoveryExceptions.RequiresShellIDInvalidForSingleShell; } else { - resourceStr = String.IsNullOrEmpty(second) + resourceStr = string.IsNullOrEmpty(second) ? DiscoveryExceptions.RequiresInterpreterNotCompatibleNoPath : DiscoveryExceptions.RequiresInterpreterNotCompatible; } @@ -564,5 +474,4 @@ private static string BuildMessage(string commandName) #endregion Private } -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs index ece59d5a15a..668c95a25e6 100644 --- a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs +++ b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; @@ -9,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 @@ -23,20 +21,21 @@ public class ApplicationFailedException : RuntimeException /// Initializes a new instance of the ApplicationFailedException class and defines the serialization information, /// and streaming context. /// - /// The serialization information to use when initializing this object - /// The streaming context to use when initializing this object - /// constructed object + /// 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 /// /// Initializes a new instance of the class ApplicationFailedException. /// - /// constructed object + /// Constructed object. public ApplicationFailedException() : base() { base.SetErrorId(errorIdString); @@ -46,8 +45,8 @@ public ApplicationFailedException() : base() /// /// Initializes a new instance of the ApplicationFailedException class and defines the error message. /// - /// The error message to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// Constructed object. public ApplicationFailedException(string message) : base(message) { base.SetErrorId(errorIdString); @@ -58,9 +57,9 @@ public ApplicationFailedException(string message) : base(message) /// Initializes a new instance of the ApplicationFailedException class and defines the error message and /// errorID. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// Constructed object. internal ApplicationFailedException(string message, string errorId) : base(message) { base.SetErrorId(errorId); @@ -71,10 +70,10 @@ internal ApplicationFailedException(string message, string errorId) : base(messa /// Initializes a new instance of the ApplicationFailedException class and defines the error message, /// error ID and inner exception. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. internal ApplicationFailedException(string message, string errorId, Exception innerException) : base(message, innerException) { @@ -86,9 +85,9 @@ internal ApplicationFailedException(string message, string errorId, Exception in /// Initializes a new instance of the ApplicationFailedException class and defines the error message and /// inner exception. /// - /// The error message to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. public ApplicationFailedException(string message, Exception innerException) : base(message, innerException) @@ -97,5 +96,5 @@ public ApplicationFailedException(string message, base.SetErrorCategory(ErrorCategory.ResourceUnavailable); } #endregion ctor - } // ApplicationFailedException -} // namespace System.Management.Automation + } +} diff --git a/src/System.Management.Automation/utils/CoreProviderCmdlets.cs b/src/System.Management.Automation/utils/CoreProviderCmdlets.cs index 591de52d813..c91c3ce69fb 100644 --- a/src/System.Management.Automation/utils/CoreProviderCmdlets.cs +++ b/src/System.Management.Automation/utils/CoreProviderCmdlets.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation { @@ -11,187 +10,187 @@ namespace System.Management.Automation public static class ProviderCmdlet { /// - /// Add-Content cmdlet + /// Add-Content cmdlet. /// public const string AddContent = "Add-Content"; /// - /// Clear-Content cmdlet + /// Clear-Content cmdlet. /// public const string ClearContent = "Clear-Content"; /// - /// Clear-Item cmdlet + /// Clear-Item cmdlet. /// public const string ClearItem = "Clear-Item"; /// - /// Clear-ItemProperty cmdlet + /// Clear-ItemProperty cmdlet. /// public const string ClearItemProperty = "Clear-ItemProperty"; /// - /// Convert-Path cmdlet + /// Convert-Path cmdlet. /// public const string ConvertPath = "Convert-Path"; /// - /// Copy-Item cmdlet + /// Copy-Item cmdlet. /// public const string CopyItem = "Copy-Item"; /// - /// Copy-ItemProperty cmdlet + /// Copy-ItemProperty cmdlet. /// public const string CopyItemProperty = "Copy-ItemProperty"; /// - /// Get-Acl cmdlet + /// Get-Acl cmdlet. /// public const string GetAcl = "Get-Acl"; /// - /// Get-ChildItem cmdlet + /// Get-ChildItem cmdlet. /// public const string GetChildItem = "Get-ChildItem"; /// - /// Get-Content cmdlet + /// Get-Content cmdlet. /// public const string GetContent = "Get-Content"; /// - /// Get-Item cmdlet + /// Get-Item cmdlet. /// public const string GetItem = "Get-Item"; /// - /// Get-ItemProperty cmdlet + /// Get-ItemProperty cmdlet. /// public const string GetItemProperty = "Get-ItemProperty"; /// - /// Get-Location cmdlet + /// Get-Location cmdlet. /// public const string GetLocation = "Get-Location"; /// - /// Get-PSDrive cmdlet + /// Get-PSDrive cmdlet. /// public const string GetPSDrive = "Get-PSDrive"; /// - /// Get-PSProvider cmdlet + /// Get-PSProvider cmdlet. /// public const string GetPSProvider = "Get-PSProvider"; /// - /// Invoke-Item cmdlet + /// Invoke-Item cmdlet. /// public const string InvokeItem = "Invoke-Item"; /// - /// Join-Path cmdlet + /// Join-Path cmdlet. /// public const string JoinPath = "Join-Path"; /// - /// Move-Item cmdlet + /// Move-Item cmdlet. /// public const string MoveItem = "Move-Item"; /// - /// Move-ItemProperty cmdlet + /// Move-ItemProperty cmdlet. /// public const string MoveItemProperty = "Move-ItemProperty"; /// - /// New-Item cmdlet + /// New-Item cmdlet. /// public const string NewItem = "New-Item"; /// - /// New-ItemProperty cmdlet + /// New-ItemProperty cmdlet. /// public const string NewItemProperty = "New-ItemProperty"; /// - /// New-PSDrive cmdlet + /// New-PSDrive cmdlet. /// public const string NewPSDrive = "New-PSDrive"; /// - /// Pop-Location cmdlet + /// Pop-Location cmdlet. /// public const string PopLocation = "Pop-Location"; /// - /// Push-Location cmdlet + /// Push-Location cmdlet. /// public const string PushLocation = "Push-Location"; /// - /// Remove-Item cmdlet + /// Remove-Item cmdlet. /// public const string RemoveItem = "Remove-Item"; /// - /// Remove-ItemProperty cmdlet + /// Remove-ItemProperty cmdlet. /// public const string RemoveItemProperty = "Remove-ItemProperty"; /// - /// Remove-PSDrive cmdlet + /// Remove-PSDrive cmdlet. /// public const string RemovePSDrive = "Remove-PSDrive"; /// - /// Rename-Item cmdlet + /// Rename-Item cmdlet. /// public const string RenameItem = "Rename-Item"; /// - /// Rename-ItemProperty cmdlet + /// Rename-ItemProperty cmdlet. /// public const string RenameItemProperty = "Rename-ItemProperty"; /// - /// Resolve-Path cmdlet + /// Resolve-Path cmdlet. /// public const string ResolvePath = "Resolve-Path"; /// - /// Set-Acl cmdlet + /// Set-Acl cmdlet. /// public const string SetAcl = "Set-Acl"; /// - /// Set-Content cmdlet + /// Set-Content cmdlet. /// public const string SetContent = "Set-Content"; /// - /// Set-Item cmdlet + /// Set-Item cmdlet. /// public const string SetItem = "Set-Item"; /// - /// Set-ItemProperty cmdlet + /// Set-ItemProperty cmdlet. /// public const string SetItemProperty = "Set-ItemProperty"; /// - /// Set-Location cmdlet + /// Set-Location cmdlet. /// public const string SetLocation = "Set-Location"; /// - /// Split-Path cmdlet + /// Split-Path cmdlet. /// public const string SplitPath = "Split-Path"; /// - /// Test-Path cmdlet + /// Test-Path cmdlet. /// public const string TestPath = "Test-Path"; } diff --git a/src/System.Management.Automation/utils/CryptoUtils.cs b/src/System.Management.Automation/utils/CryptoUtils.cs index 2a6acccd556..c82c96ecdc3 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -1,330 +1,274 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Text; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Management.Automation.Remoting; +using System.Runtime.Serialization; using System.Security; +using System.Security.Cryptography; +using System.Text; using System.Threading; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; -using System.Runtime.ConstrainedExecution; -using Microsoft.Win32.SafeHandles; -using System.Diagnostics.CodeAnalysis; + using Dbg = System.Management.Automation.Diagnostics; -using System.Management.Automation.Remoting; namespace System.Management.Automation.Internal { /// - /// Class that encapsulates native crypto provider handles and provides a - /// mechanism for resources released by them + /// This class provides the converters for all Native CAPI key blob formats. /// - // [SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] - // [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode=true)] - internal class PSSafeCryptProvHandle : SafeHandleZeroOrMinusOneIsInvalid + internal static class PSCryptoNativeConverter { + #region Constants + /// - /// This safehandle instance "owns" the handle, hence base(true) - /// is being called. When safehandle is no longer in use it will - /// call this class's ReleaseHandle method which will release - /// the resources + /// The blob version is fixed. /// - internal PSSafeCryptProvHandle() : base(true) { } + public const uint CUR_BLOB_VERSION = 0x00000002; /// - /// Release the crypto handle held by this instance + /// RSA Key. /// - /// true on success, false otherwise - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - protected override bool ReleaseHandle() - { - return PSCryptoNativeUtils.CryptReleaseContext(handle, 0); - } - } + public const uint CALG_RSA_KEYX = 0x000000a4; - /// - /// Class the encapsulates native crypto key handles and provides a - /// mechanism to release resources used by it - /// - //[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] - //[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode=true)] - internal class PSSafeCryptKey : SafeHandleZeroOrMinusOneIsInvalid - { /// - /// This safehandle instance "owns" the handle, hence base(true) - /// is being called. When safehandle is no longer in use it will - /// call this class's ReleaseHandle method which will release the - /// resources + /// AES 256 symmetric key. /// - internal PSSafeCryptKey() : base(true) { } + public const uint CALG_AES_256 = 0x00000010; /// - /// Release the crypto handle held by this instance + /// Option for exporting public key blob. /// - /// true on success, false otherwise - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - protected override bool ReleaseHandle() - { - return PSCryptoNativeUtils.CryptDestroyKey(handle); - } + public const uint PUBLICKEYBLOB = 0x00000006; /// - /// Equivalent of IntPtr.Zero for the safe crypt key + /// PUBLICKEYBLOB header length. /// - internal static PSSafeCryptKey Zero { get; } = new PSSafeCryptKey(); - } + public const int PUBLICKEYBLOB_HEADER_LEN = 20; + + /// + /// Option for exporting a session key. + /// + public const uint SIMPLEBLOB = 0x00000001; + + /// + /// SIMPLEBLOB header length. + /// + public const int SIMPLEBLOB_HEADER_LEN = 12; + + #endregion Constants - /// - /// This class provides the wrapper for all Native CAPI functions - /// - internal class PSCryptoNativeUtils - { #region Functions - /// Return Type: BOOL->int - ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int - ///Algid: ALG_ID->unsigned int - ///dwFlags: DWORD->unsigned int - ///phKey: HCRYPTKEY* - [DllImportAttribute(PinvokeDllNames.CryptGenKeyDllName, EntryPoint = "CryptGenKey")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptGenKey(PSSafeCryptProvHandle hProv, - uint Algid, - uint dwFlags, - ref PSSafeCryptKey phKey); - - /// Return Type: BOOL->int - ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int - [DllImportAttribute(PinvokeDllNames.CryptDestroyKeyDllName, EntryPoint = "CryptDestroyKey")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptDestroyKey(IntPtr hKey); - - /// Return Type: BOOL->int - ///phProv: HCRYPTPROV* - ///szContainer: LPCWSTR->WCHAR* - ///szProvider: LPCWSTR->WCHAR* - ///dwProvType: DWORD->unsigned int - ///dwFlags: DWORD->unsigned int - [DllImportAttribute(PinvokeDllNames.CryptAcquireContextDllName, EntryPoint = "CryptAcquireContext")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptAcquireContext(ref PSSafeCryptProvHandle phProv, - [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string szContainer, - [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string szProvider, - uint dwProvType, - uint dwFlags); - - /// Return Type: BOOL->int - ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int - ///dwFlags: DWORD->unsigned int - [DllImportAttribute(PinvokeDllNames.CryptReleaseContextDllName, EntryPoint = "CryptReleaseContext")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags); - - /// Return Type: BOOL->int - ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///hHash: HCRYPTHASH->ULONG_PTR->unsigned int - ///Final: BOOL->int - ///dwFlags: DWORD->unsigned int - ///pbData: BYTE* - ///pdwDataLen: DWORD* - ///dwBufLen: DWORD->unsigned int - [DllImportAttribute(PinvokeDllNames.CryptEncryptDllName, EntryPoint = "CryptEncrypt")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptEncrypt(PSSafeCryptKey hKey, - IntPtr hHash, - [MarshalAsAttribute(UnmanagedType.Bool)] bool Final, - uint dwFlags, - byte[] pbData, - ref int pdwDataLen, - int dwBufLen); - - - /// Return Type: BOOL->int - ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///hHash: HCRYPTHASH->ULONG_PTR->unsigned int - ///Final: BOOL->int - ///dwFlags: DWORD->unsigned int - ///pbData: BYTE* - ///pdwDataLen: DWORD* - [DllImportAttribute(PinvokeDllNames.CryptDecryptDllName, EntryPoint = "CryptDecrypt")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptDecrypt(PSSafeCryptKey hKey, - IntPtr hHash, - [MarshalAsAttribute(UnmanagedType.Bool)] bool Final, - uint dwFlags, - byte[] pbData, - ref int pdwDataLen); - - /// Return Type: BOOL->int - ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///hExpKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///dwBlobType: DWORD->unsigned int - ///dwFlags: DWORD->unsigned int - ///pbData: BYTE* - ///pdwDataLen: DWORD* - [DllImportAttribute(PinvokeDllNames.CryptExportKeyDllName, EntryPoint = "CryptExportKey")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptExportKey(PSSafeCryptKey hKey, - PSSafeCryptKey hExpKey, - uint dwBlobType, - uint dwFlags, - byte[] pbData, - ref uint pdwDataLen); - - - /// Return Type: BOOL->int - ///hProv: HCRYPTPROV->ULONG_PTR->unsigned int - ///pbData: BYTE* - ///dwDataLen: DWORD->unsigned int - ///hPubKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///dwFlags: DWORD->unsigned int - ///phKey: HCRYPTKEY* - [DllImportAttribute(PinvokeDllNames.CryptImportKeyDllName, EntryPoint = "CryptImportKey")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptImportKey(PSSafeCryptProvHandle hProv, - byte[] pbData, - int dwDataLen, - PSSafeCryptKey hPubKey, - uint dwFlags, - ref PSSafeCryptKey phKey); - - - /// Return Type: BOOL->int - ///hKey: HCRYPTKEY->ULONG_PTR->unsigned int - ///pdwReserved: DWORD* - ///dwFlags: DWORD->unsigned int - ///phKey: HCRYPTKEY* - [DllImportAttribute(PinvokeDllNames.CryptDuplicateKeyDllName, EntryPoint = "CryptDuplicateKey")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - public static extern bool CryptDuplicateKey(PSSafeCryptKey hKey, - ref uint pdwReserved, - uint dwFlags, - ref PSSafeCryptKey phKey); - - /// Return Type: DWORD->unsigned int - [DllImportAttribute(PinvokeDllNames.GetLastErrorDllName, EntryPoint = "GetLastError")] - public static extern uint GetLastError(); + private static int ToInt32LE(byte[] bytes, int offset) + { + return (bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset]; + } - #endregion Functions + private static uint ToUInt32LE(byte[] bytes, int offset) + { + return (uint)((bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset]); + } - #region Constants + private static byte[] GetBytesLE(int val) + { + return new[] { + (byte)(val & 0xff), + (byte)((val >> 8) & 0xff), + (byte)((val >> 16) & 0xff), + (byte)((val >> 24) & 0xff) + }; + } - /// - /// Do not use persisted private key - /// - public const uint CRYPT_VERIFYCONTEXT = 0xF0000000; + private static byte[] CreateReverseByteArray(byte[] data) + { + byte[] reverseData = new byte[data.Length]; + Array.Copy(data, reverseData, data.Length); + Array.Reverse(reverseData); + return reverseData; + } - /// - /// Mark the key for export - /// - public const uint CRYPT_EXPORTABLE = 0x00000001; + internal static RSA FromCapiPublicKeyBlob(byte[] blob) + { + return FromCapiPublicKeyBlob(blob, 0); + } - /// - /// Automatically assign a salt value when creating a - /// session key - /// - public const int CRYPT_CREATE_SALT = 4; + private static RSA FromCapiPublicKeyBlob(byte[] blob, int offset) + { + ArgumentNullException.ThrowIfNull(blob); - /// - /// RSA Provider - /// - public const int PROV_RSA_FULL = 1; + if (offset > blob.Length) + { + throw new ArgumentException(SecuritySupportStrings.InvalidOffset); + } - /// - /// RSA Provider that supports AES - /// encryption - /// - public const int PROV_RSA_AES = 24; + var rsap = GetParametersFromCapiPublicKeyBlob(blob, offset); - /// - /// Public key to be used for encryption - /// - public const int AT_KEYEXCHANGE = 1; + try + { + RSA rsa = RSA.Create(); + rsa.ImportParameters(rsap); + return rsa; + } + catch (Exception ex) + { + throw new CryptographicException(SecuritySupportStrings.CannotImportPublicKey, ex); + } + } - /// - /// RSA Key - /// - public const int CALG_RSA_KEYX = - (PSCryptoNativeUtils.ALG_CLASS_KEY_EXCHANGE | - (PSCryptoNativeUtils.ALG_TYPE_RSA | PSCryptoNativeUtils.ALG_SID_RSA_ANY)); + private static RSAParameters GetParametersFromCapiPublicKeyBlob(byte[] blob, int offset) + { + ArgumentNullException.ThrowIfNull(blob); - /// - /// Create a key for encryption - /// - public const int ALG_CLASS_KEY_EXCHANGE = (5) << (13); + if (offset > blob.Length) + { + throw new ArgumentException(SecuritySupportStrings.InvalidOffset); + } - /// - /// Create a RSA key pair - /// - public const int ALG_TYPE_RSA = (2) << (9); + if (blob.Length < PUBLICKEYBLOB_HEADER_LEN) + { + throw new ArgumentException(SecuritySupportStrings.InvalidPublicKey); + } - /// - /// - /// - public const int ALG_SID_RSA_ANY = 0; + try + { + if ((blob[offset] != PUBLICKEYBLOB) || // PUBLICKEYBLOB (0x06) + (blob[offset + 1] != CUR_BLOB_VERSION) || // Version (0x02) + (blob[offset + 2] != 0x00) || // Reserved (word) + (blob[offset + 3] != 0x00) || + (ToUInt32LE(blob, offset + 8) != 0x31415352)) // DWORD magic = RSA1 + { + throw new CryptographicException(SecuritySupportStrings.InvalidPublicKey); + } - /// - /// Option for exporting public key blob - /// - public const int PUBLICKEYBLOB = 6; + // DWORD bitlen + int bitLen = ToInt32LE(blob, offset + 12); - /// - /// Option for exporting a session key - /// - public const int SIMPLEBLOB = 1; + // DWORD public exponent + RSAParameters rsap = new RSAParameters(); + rsap.Exponent = new byte[3]; + rsap.Exponent[0] = blob[offset + 18]; + rsap.Exponent[1] = blob[offset + 17]; + rsap.Exponent[2] = blob[offset + 16]; - /// - /// AES 256 symmetric key - /// - public const int CALG_AES_256 = (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256); + int pos = offset + 20; + int byteLen = (bitLen >> 3); + rsap.Modulus = new byte[byteLen]; + Buffer.BlockCopy(blob, pos, rsap.Modulus, 0, byteLen); + Array.Reverse(rsap.Modulus); - /// - /// ALG_CLASS_DATA_ENCRYPT - /// - public const int ALG_CLASS_DATA_ENCRYPT = (3) << (13); + return rsap; + } + catch (Exception ex) + { + throw new CryptographicException(SecuritySupportStrings.InvalidPublicKey, ex); + } + } - /// - /// ALG_TYPE_BLOCK - /// - public const int ALG_TYPE_BLOCK = (3) << (9); + internal static byte[] ToCapiPublicKeyBlob(RSA rsa) + { + ArgumentNullException.ThrowIfNull(rsa); + + RSAParameters p = rsa.ExportParameters(false); + int keyLength = p.Modulus.Length; // in bytes + byte[] blob = new byte[PUBLICKEYBLOB_HEADER_LEN + keyLength]; + + blob[0] = (byte)PUBLICKEYBLOB; // Type - PUBLICKEYBLOB (0x06) + blob[1] = (byte)CUR_BLOB_VERSION; // Version - Always CUR_BLOB_VERSION (0x02) + // [2], [3] // RESERVED - Always 0 + blob[5] = (byte)CALG_RSA_KEYX; // ALGID - Always 00 a4 00 00 (for CALG_RSA_KEYX) + blob[8] = 0x52; // Magic - RSA1 (ASCII in hex) + blob[9] = 0x53; + blob[10] = 0x41; + blob[11] = 0x31; + + byte[] bitlen = GetBytesLE(keyLength << 3); + blob[12] = bitlen[0]; // bitlen + blob[13] = bitlen[1]; + blob[14] = bitlen[2]; + blob[15] = bitlen[3]; + + // public exponent (DWORD) + int pos = 16; + int n = p.Exponent.Length; + + Dbg.Assert(n <= 4, "RSA exponent byte length cannot exceed allocated segment"); + + while (n > 0) + { + blob[pos++] = p.Exponent[--n]; + } - /// - /// ALG_SID_AES_256 -> 16 - /// - public const int ALG_SID_AES_256 = 16; + // modulus + pos = 20; + byte[] key = p.Modulus; + Array.Reverse(key); + Buffer.BlockCopy(key, 0, blob, pos, keyLength); - /// CALG_AES_128 -> (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128) - public const int CALG_AES_128 = (ALG_CLASS_DATA_ENCRYPT - | (ALG_TYPE_BLOCK | ALG_SID_AES_128)); + return blob; + } - /// ALG_SID_AES_128 -> 14 - public const int ALG_SID_AES_128 = 14; + internal static byte[] FromCapiSimpleKeyBlob(byte[] blob) + { + ArgumentNullException.ThrowIfNull(blob); - #endregion Constants + if (blob.Length < SIMPLEBLOB_HEADER_LEN) + { + throw new ArgumentException(SecuritySupportStrings.InvalidSessionKey); + } + + // just ignore the header of the capi blob and go straight for the key + return CreateReverseByteArray(blob.Skip(SIMPLEBLOB_HEADER_LEN).ToArray()); + } + + internal static byte[] ToCapiSimpleKeyBlob(byte[] encryptedKey) + { + ArgumentNullException.ThrowIfNull(encryptedKey); + + // formulate the PUBLICKEYSTRUCT + byte[] blob = new byte[SIMPLEBLOB_HEADER_LEN + encryptedKey.Length]; + + blob[0] = (byte)SIMPLEBLOB; // Type - SIMPLEBLOB (0x01) + blob[1] = (byte)CUR_BLOB_VERSION; // Version - Always CUR_BLOB_VERSION (0x02) + // [2], [3] // RESERVED - Always 0 + blob[4] = (byte)CALG_AES_256; // AES-256 algo id (0x10) + blob[5] = 0x66; // ?? + // [6], [7], [8] // 0x00 + blob[9] = (byte)CALG_RSA_KEYX; // 0xa4 + // [10], [11] // 0x00 + + // create a reversed copy and add the encrypted key + byte[] reversedKey = CreateReverseByteArray(encryptedKey); + Buffer.BlockCopy(reversedKey, 0, blob, SIMPLEBLOB_HEADER_LEN, reversedKey.Length); + + return blob; + } + + #endregion Functions } /// /// Defines a custom exception which is thrown when - /// a native CAPI call results in an error + /// a native CAPI call results in an error. /// /// This exception is currently internal as it's not /// surfaced to the user. However, if we decide to surface errors /// 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 #region Internal Properties /// - /// Error code returned by the native CAPI call + /// Error code returned by the native CAPI call. /// internal uint ErrorCode { @@ -339,16 +283,17 @@ internal uint ErrorCode #region Constructors /// - /// Default constructor + /// 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 + /// Constructor that will be used from within CryptoUtils. /// /// error code returned by native /// crypto application - /// error message associated with this failure + /// Error message associated with this failure. public PSCryptoException(uint errorCode, StringBuilder message) : base(message.ToString()) { @@ -356,89 +301,70 @@ public PSCryptoException(uint errorCode, StringBuilder message) } /// - /// Constructor with just message but no inner exception + /// Constructor with just message but no inner exception. /// - /// error message associated with this failure - public PSCryptoException(String message) : this(message, null) { } + /// Error message associated with this failure. + public PSCryptoException(string message) + : this(message, null) { } /// - /// Constructor with inner exception + /// Constructor with inner exception. /// - /// error message - /// inner exception + /// Error message. + /// 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); } /// - /// Constructor which has type specific serialization logic + /// Constructor which has type specific serialization logic. /// - /// serialization info - /// context in which this constructor is called + /// Serialization info. + /// 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 } /// - /// One of the issues with RSACryptoServiceProvider is that it never uses CRYPT_VERIFYCONTEXT - /// to create ephemeral keys. This class is a facade written on top of native CAPI APIs - /// to create ephemeral keys. + /// 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 - private PSSafeCryptProvHandle _hProv; - // handle to the provider - private bool _canEncrypt = false; // this flag indicates that this class has a key - // imported from the remote end and so can be - // used for encryption - private PSSafeCryptKey _hRSAKey; - // handle to the RSA key with which the session - // key is exchange. This can either be generated - // or imported - private PSSafeCryptKey _hSessionKey; - // handle to the session key. This can either - // be generated or imported - private bool _sessionKeyGenerated = false; + // handle session key encryption/decryption + private RSA _rsa; + + // 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 + // 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 PSSafeCryptProvHandle s_hStaticProv; - private static PSSafeCryptKey s_hStaticRSAKey; - private static bool s_keyPairGenerated = false; - private static object s_syncObject = new object(); + private static readonly object s_syncObject = new object(); #endregion Private Members #region Constructors /// - /// Private constructor + /// Private constructor. /// /// indicates if this service /// provider is operating in server mode @@ -446,21 +372,11 @@ private PSRSACryptoServiceProvider(bool serverMode) { if (serverMode) { - _hProv = new PSSafeCryptProvHandle(); - - // We need PROV_RSA_AES to support AES-256 symmetric key - // encryption. PROV_RSA_FULL supports only RC2 and RC4 - bool ret = PSCryptoNativeUtils.CryptAcquireContext(ref _hProv, - null, - null, - PSCryptoNativeUtils.PROV_RSA_AES, - PSCryptoNativeUtils.CRYPT_VERIFYCONTEXT); - - CheckStatus(ret); - - _hRSAKey = new PSSafeCryptKey(); + GenerateKeyPair(); } - _hSessionKey = new PSSafeCryptKey(); + + _aes = Aes.Create(); + _aes.IV = new byte[16]; // iv should be 0 } #endregion Constructors @@ -468,41 +384,20 @@ private PSRSACryptoServiceProvider(bool serverMode) #region Internal Methods /// - /// Get the public key as a base64 encoded string + /// Get the public key, in CAPI-compatible form, as a base64 encoded string. /// - /// public key as base64 encoded string + /// Public key as base64 encoded string. internal string GetPublicKeyAsBase64EncodedString() { - uint publicKeyLength = 0; - - // Get key length first - bool ret = PSCryptoNativeUtils.CryptExportKey(_hRSAKey, - PSSafeCryptKey.Zero, - PSCryptoNativeUtils.PUBLICKEYBLOB, - 0, - null, - ref publicKeyLength); - CheckStatus(ret); - - // Create enough buffer and get the actual data - byte[] publicKey = new byte[publicKeyLength]; - ret = PSCryptoNativeUtils.CryptExportKey(_hRSAKey, - PSSafeCryptKey.Zero, - PSCryptoNativeUtils.PUBLICKEYBLOB, - 0, - publicKey, - ref publicKeyLength); - CheckStatus(ret); - - // Convert the public key into base64 encoding so that it can be exported to - // the other end. - string result = Convert.ToBase64String(publicKey); - - return result; + Dbg.Assert(_rsa != null, "No public key available."); + + byte[] capiPublicKeyBlob = PSCryptoNativeConverter.ToCapiPublicKeyBlob(_rsa); + + return Convert.ToBase64String(capiPublicKeyBlob); } /// - /// Generates an AEX-256 session key if one is not already generated + /// Generates an AEX-256 session key if one is not already generated. /// internal void GenerateSessionKey() { @@ -513,13 +408,9 @@ internal void GenerateSessionKey() { if (!_sessionKeyGenerated) { - bool ret = PSCryptoNativeUtils.CryptGenKey(_hProv, - PSCryptoNativeUtils.CALG_AES_256, - 0x01000000 | // key length = 256 bits - PSCryptoNativeUtils.CRYPT_EXPORTABLE | - PSCryptoNativeUtils.CRYPT_CREATE_SALT, - ref _hSessionKey); - CheckStatus(ret); + // 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; _canEncrypt = true; // we can encrypt and decrypt once session key is available } @@ -530,67 +421,42 @@ internal void GenerateSessionKey() /// 1. Generate a AES-256 session key /// 2. Encrypt the session key with the Imported /// RSA public key - /// 3. Encode result above as base 64 string and export + /// 3. Encode result above as base 64 string and export. /// - /// session key encrypted with receivers public key - /// and encoded as a base 64 string + /// Session key encrypted with receivers public key + /// and encoded as a base 64 string. internal string SafeExportSessionKey() { - //generate one if not already done. + Dbg.Assert(_rsa != null, "No public key available."); + + // generate one if not already done. GenerateSessionKey(); - uint length = 0; - - // get key length first - bool ret = PSCryptoNativeUtils.CryptExportKey(_hSessionKey, - _hRSAKey, - PSCryptoNativeUtils.SIMPLEBLOB, - 0, - null, - ref length); - CheckStatus(ret); - - // allocate buffer and export the key - byte[] sessionkey = new byte[length]; - ret = PSCryptoNativeUtils.CryptExportKey(_hSessionKey, - _hRSAKey, - PSCryptoNativeUtils.SIMPLEBLOB, - 0, - sessionkey, - ref length); - CheckStatus(ret); - - // now we can encrypt as we have the session key - _canEncrypt = true; + // 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 base64 before exporting - return Convert.ToBase64String(sessionkey); + // convert the key to capi simpleblob format before exporting + byte[] simpleKeyBlob = PSCryptoNativeConverter.ToCapiSimpleKeyBlob(encryptedKey); + return Convert.ToBase64String(simpleKeyBlob); } /// /// Import a public key into the provider whose context - /// has been obtained + /// has been obtained. /// - /// base64 encoded public key to import + /// Base64 encoded public key to import. internal void ImportPublicKeyFromBase64EncodedString(string publicKey) { Dbg.Assert(!string.IsNullOrEmpty(publicKey), "key cannot be null or empty"); - byte[] convertedBase64 = Convert.FromBase64String(publicKey); - - bool ret = PSCryptoNativeUtils.CryptImportKey(_hProv, - convertedBase64, - convertedBase64.Length, - PSSafeCryptKey.Zero, - 0, - ref _hRSAKey); - - CheckStatus(ret); + byte[] publicKeyBlob = Convert.FromBase64String(publicKey); + _rsa = PSCryptoNativeConverter.FromCapiPublicKeyBlob(publicKeyBlob); } /// /// Import a session key from the remote side into - /// the current CSP + /// the current CSP. /// /// encrypted session key as a /// base64 encoded string @@ -598,15 +464,11 @@ internal void ImportSessionKeyFromBase64EncodedString(string sessionKey) { Dbg.Assert(!string.IsNullOrEmpty(sessionKey), "key cannot be null or empty"); - byte[] convertedBase64 = Convert.FromBase64String(sessionKey); + byte[] sessionKeyBlob = Convert.FromBase64String(sessionKey); + byte[] rsaEncryptedKey = PSCryptoNativeConverter.FromCapiSimpleKeyBlob(sessionKeyBlob); - bool ret = PSCryptoNativeUtils.CryptImportKey(_hProv, - convertedBase64, - convertedBase64.Length, - _hRSAKey, - 0, - ref _hSessionKey); - CheckStatus(ret); + // 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 // encrypt using the session key @@ -614,166 +476,60 @@ internal void ImportSessionKeyFromBase64EncodedString(string sessionKey) } /// - /// Encrypt the specified byte array + /// Encrypt the specified byte array. /// - /// data to encrypt - /// encrypted byte array + /// Data to encrypt. + /// Encrypted byte array. internal byte[] EncryptWithSessionKey(byte[] data) { - // first make a copy of the original data.This is needed - // as CryptEncrypt uses the same buffer to write the encrypted data - // into. Dbg.Assert(_canEncrypt, "Remote key has not been imported to encrypt"); - byte[] encryptedData = new byte[data.Length]; - Array.Copy(data, 0, encryptedData, 0, data.Length); - - int dataLength = encryptedData.Length; - - // encryption always happens using the session key - bool ret = PSCryptoNativeUtils.CryptEncrypt(_hSessionKey, - IntPtr.Zero, - true, - 0, - encryptedData, - ref dataLength, - data.Length); - - // if encryption failed, then dataLength will contain the length - // of buffer needed to store the encrypted contents. Recreate - // the buffer - if (false == ret) + using (ICryptoTransform encryptor = _aes.CreateEncryptor()) + using (MemoryStream targetStream = new MemoryStream()) + using (MemoryStream sourceStream = new MemoryStream(data)) { - // before reallocating the encryptedData buffer, - // zero out its contents - for (int i = 0; i < encryptedData.Length; i++) + using (CryptoStream cryptoStream = new CryptoStream(targetStream, encryptor, CryptoStreamMode.Write)) { - encryptedData[i] = 0; + sourceStream.CopyTo(cryptoStream); } - encryptedData = new byte[dataLength]; - - Array.Copy(data, 0, encryptedData, 0, data.Length); - dataLength = data.Length; - ret = PSCryptoNativeUtils.CryptEncrypt(_hSessionKey, - IntPtr.Zero, - true, - 0, - encryptedData, - ref dataLength, - encryptedData.Length); - - CheckStatus(ret); + return targetStream.ToArray(); } - - // make sure we copy only appropriate data - // dataLength will contain the length of the encrypted - // data buffer - byte[] result = new byte[dataLength]; - Array.Copy(encryptedData, 0, result, 0, dataLength); - return result; } /// - /// Decrypt the specified buffer + /// Decrypt the specified buffer. /// - /// data to decrypt - /// decrypted buffer + /// Data to decrypt. + /// Decrypted buffer. internal byte[] DecryptWithSessionKey(byte[] data) { - // first make a copy of the original data.This is needed - // as CryptDecrypt uses the same buffer to write the decrypted data - // into. - byte[] decryptedData = new byte[data.Length]; - - Array.Copy(data, 0, decryptedData, 0, data.Length); - - int dataLength = decryptedData.Length; - - bool ret = PSCryptoNativeUtils.CryptDecrypt(_hSessionKey, - IntPtr.Zero, - true, - 0, - decryptedData, - ref dataLength); - - // if decryption failed, then dataLength will contain the length - // of buffer needed to store the decrypted contents. Recreate - // the buffer - if (false == ret) + using (ICryptoTransform decryptor = _aes.CreateDecryptor()) + using (MemoryStream sourceStream = new MemoryStream(data)) + using (MemoryStream targetStream = new MemoryStream()) { - decryptedData = new byte[dataLength]; - - Array.Copy(data, 0, decryptedData, 0, data.Length); - ret = PSCryptoNativeUtils.CryptDecrypt(_hSessionKey, - IntPtr.Zero, - true, - 0, - decryptedData, - ref dataLength); - CheckStatus(ret); - } - - // make sure we copy only appropriate data - // dataLength will contain the length of the encrypted - // data buffer - byte[] result = new byte[dataLength]; - - Array.Copy(decryptedData, 0, result, 0, dataLength); + using (CryptoStream csDecrypt = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read)) + { + csDecrypt.CopyTo(targetStream); + } - // zero out the decryptedData buffer - for (int i = 0; i < decryptedData.Length; i++) - { - decryptedData[i] = 0; + return targetStream.ToArray(); } - - return result; } /// /// Generates key pair in a thread safe manner - /// the first time when required + /// the first time when required. /// internal void GenerateKeyPair() { - if (!s_keyPairGenerated) - { - lock (s_syncObject) - { - if (!s_keyPairGenerated) - { - s_hStaticProv = new PSSafeCryptProvHandle(); - // We need PROV_RSA_AES to support AES-256 symmetric key - // encryption. PROV_RSA_FULL supports only RC2 and RC4 - bool ret = PSCryptoNativeUtils.CryptAcquireContext(ref s_hStaticProv, - null, - null, - PSCryptoNativeUtils.PROV_RSA_AES, - PSCryptoNativeUtils.CRYPT_VERIFYCONTEXT); - - CheckStatus(ret); - - s_hStaticRSAKey = new PSSafeCryptKey(); - ret = PSCryptoNativeUtils.CryptGenKey(s_hStaticProv, - PSCryptoNativeUtils.AT_KEYEXCHANGE, - 0x08000000 | PSCryptoNativeUtils.CRYPT_EXPORTABLE, // key length -> 2048 - ref s_hStaticRSAKey); - - CheckStatus(ret); - - // key needs to be generated once - s_keyPairGenerated = true; - } - } - } - - _hProv = s_hStaticProv; - _hRSAKey = s_hStaticRSAKey; + _rsa = RSA.Create(); + _rsa.KeySize = 2048; } /// /// Indicates if a key exchange is complete - /// and this provider can encrypt + /// and this provider can encrypt. /// internal bool CanEncrypt { @@ -781,6 +537,7 @@ internal bool CanEncrypt { return _canEncrypt; } + set { _canEncrypt = value; @@ -794,125 +551,37 @@ internal bool CanEncrypt /// /// Returns a crypto service provider for use in the /// client. This will reuse the key that has been - /// generated + /// generated. /// - /// crypto service provider for - /// the client side + /// Crypto service provider for + /// the client side. internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForClient() { - PSRSACryptoServiceProvider cryptoProvider = new PSRSACryptoServiceProvider(false); - - // set the handles for provider and rsa key - cryptoProvider._hProv = s_hStaticProv; - cryptoProvider._hRSAKey = s_hStaticRSAKey; - - return cryptoProvider; + return new PSRSACryptoServiceProvider(false); } /// /// Returns a crypto service provider for use in the - /// server. This will not generate a key pair + /// server. This will not generate a key pair. /// - /// crypto service provider for - /// the server side + /// Crypto service provider for + /// the server side. internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer() { - PSRSACryptoServiceProvider cryptoProvider = new PSRSACryptoServiceProvider(true); - - return cryptoProvider; + return new PSRSACryptoServiceProvider(true); } #endregion Internal Static Methods - #region Private Methods - - /// - /// Checks the status of a call, if it had resulted in an error - /// then obtains the last error, wraps it in an exception and - /// throws the same - /// - /// value to examine - private void CheckStatus(bool value) - { - if (value) - { - return; - } - - uint errorCode = PSCryptoNativeUtils.GetLastError(); - StringBuilder errorMessage = new StringBuilder(new ComponentModel.Win32Exception(unchecked((int)errorCode)).Message); - - throw new PSCryptoException(errorCode, errorMessage); - } - - #endregion Private Methods - #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 (null != _hSessionKey) - { - if (!_hSessionKey.IsInvalid) - { - _hSessionKey.Dispose(); - } - _hSessionKey = null; - } - - // we need to dismiss the provider and key - // only if the static members are not allocated - // since otherwise, these are just references - // to the static members - - if (null == s_hStaticRSAKey) - { - if (null != _hRSAKey) - { - if (!_hRSAKey.IsInvalid) - { - _hRSAKey.Dispose(); - } - _hRSAKey = null; - } - } - - if (null == s_hStaticProv) - { - if (null != _hProv) - { - if (!_hProv.IsInvalid) - { - _hProv.Dispose(); - } - _hProv = null; - } - } - } - } - - /// - /// 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 @@ -920,9 +589,9 @@ protected void Dispose(bool disposing) /// /// Helper for exchanging keys and encrypting/decrypting - /// secure strings for serialization in remoting + /// secure strings for serialization in remoting. /// - internal abstract class PSRemotingCryptoHelper : IDisposable + public abstract class PSRemotingCryptoHelper : IDisposable { #region Protected Members @@ -930,25 +599,24 @@ internal abstract class PSRemotingCryptoHelper : IDisposable /// Crypto provider which will be used for importing remote /// public key as well as generating a session key, exporting /// it and performing symmetric key operations using the - /// session key + /// session key. /// - protected PSRSACryptoServiceProvider _rsaCryptoProvider; + internal PSRSACryptoServiceProvider _rsaCryptoProvider; /// /// Key exchange has been completed and both keys - /// available + /// available. /// protected ManualResetEvent _keyExchangeCompleted = new ManualResetEvent(false); /// - /// Object for synchronizing key exchange + /// Object for synchronizing key exchange. /// protected object syncObject = new object(); private bool _keyExchangeStarted = false; /// - /// /// protected void RunKeyExchangeIfRequired() { @@ -983,29 +651,94 @@ protected void RunKeyExchangeIfRequired() } /// - /// Core logic to encrypt a string. Assumes session key is already generated + /// 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. /// /// /// secure string to be encrypted /// /// - protected String EncryptSecureStringCore(SecureString secureString) + protected string EncryptSecureStringCore(SecureString secureString) { - String encryptedDataAsString = null; + string encryptedDataAsString = null; 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); @@ -1013,29 +746,26 @@ protected String EncryptSecureStringCore(SecureString secureString) } finally { - for (int j = 0; j < data.Length; j++) - { - data[j] = 0; - } + Array.Clear(data); } - } // if (ptr ... - } // if (rsa != null... + } + } else { - Dbg.Assert(false, "Session key not available to encrypt secure string"); + throw new PSCryptoException(SecuritySupportStrings.CannotEncryptSecureString); } return encryptedDataAsString; } /// - /// Core logic to decrypt a secure string. Assumes session key is already available + /// Core logic to decrypt a secure string. Assumes session key is already available. /// /// /// encrypted string to be decrypted /// /// - protected SecureString DecryptSecureStringCore(String encryptedString) + protected SecureString DecryptSecureStringCore(string encryptedString) { // removing an earlier assert from here. It is // possible to encrypt and decrypt empty @@ -1046,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) { @@ -1058,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 { @@ -1101,31 +802,29 @@ protected SecureString DecryptSecureStringCore(String encryptedString) #region Internal Methods - /// - /// Encrypt a secure string + /// Encrypt a secure string. /// - /// secure string to encrypt - /// encrypted string + /// Secure string to encrypt. + /// Encrypted string. /// This method zeroes out all interim buffers used - internal abstract String EncryptSecureString(SecureString secureString); + internal abstract string EncryptSecureString(SecureString secureString); /// /// Decrypt a string and construct a secure string from its - /// contents + /// contents. /// - /// encrypted string - /// secure string object + /// Encrypted string. + /// Secure string object. /// This method zeroes out any interim buffers used - internal abstract SecureString DecryptSecureString(String encryptedString); + internal abstract SecureString DecryptSecureString(string encryptedString); /// - /// Represents the session to be used for requesting public key + /// Represents the session to be used for requesting public key. /// internal abstract RemoteSession Session { get; set; } /// - /// /// public void Dispose() { @@ -1134,17 +833,13 @@ public void Dispose() } /// - /// /// /// public void Dispose(bool disposing) { if (disposing) { - if (_rsaCryptoProvider != null) - { - _rsaCryptoProvider.Dispose(); - } + _rsaCryptoProvider?.Dispose(); _rsaCryptoProvider = null; _keyExchangeCompleted.Dispose(); @@ -1152,7 +847,7 @@ public void Dispose(bool disposing) } /// - /// Resets the wait for key exchange + /// Resets the wait for key exchange. /// internal void CompleteKeyExchange() { @@ -1164,7 +859,7 @@ internal void CompleteKeyExchange() /// /// Helper for exchanging keys and encrypting/decrypting - /// secure strings for serialization in remoting + /// secure strings for serialization in remoting. /// internal class PSRemotingCryptoHelperServer : PSRemotingCryptoHelper { @@ -1172,7 +867,7 @@ internal class PSRemotingCryptoHelperServer : PSRemotingCryptoHelper /// /// This is the instance of runspace pool data structure handler - /// to use for negotiations + /// to use for negotiations. /// private RemoteSession _session; @@ -1182,15 +877,11 @@ internal class PSRemotingCryptoHelperServer : PSRemotingCryptoHelper /// /// Creates the encryption provider, but generates no key. - /// The key will be imported later + /// The key will be imported later. /// internal PSRemotingCryptoHelperServer() { -#if UNIX - _rsaCryptoProvider = null; -#else _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForServer(); -#endif } #endregion Constructors @@ -1199,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 + bool initiateKeyExchange = true; - //session!=null check required for DRTs TestEncryptSecureString* entries in CryptoUtilsTest/UTUtils.dll - //for newer clients, server will never initiate key exchange. - //for server, just the session key is required to encrypt/decrypt anything - if ((session != null) && (session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM)) + if (Session is ServerRemoteSession session) { - _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(); } @@ -1218,22 +922,28 @@ 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); } /// - /// Imports a public key from its base64 encoded string representation + /// Imports a public key from its base64 encoded string representation. /// - /// public key in its string representation - /// true on success - internal bool ImportRemotePublicKey(String publicKeyAsString) + /// Public key in its string representation. + /// True on success. + internal bool ImportRemotePublicKey(string publicKeyAsString) { - Dbg.Assert(!String.IsNullOrEmpty(publicKeyAsString), "public key passed in cannot be null"); + Dbg.Assert(!string.IsNullOrEmpty(publicKeyAsString), "public key passed in cannot be null"); // generate the crypto provider to use for encryption - //_rsaCryptoProvider = GenerateCryptoServiceProvider(false); + // _rsaCryptoProvider = GenerateCryptoServiceProvider(false); try { @@ -1248,7 +958,7 @@ internal bool ImportRemotePublicKey(String publicKeyAsString) } /// - /// Represents the session to be used for requesting public key + /// Represents the session to be used for requesting public key. /// internal override RemoteSession Session { @@ -1256,6 +966,7 @@ internal override RemoteSession Session { return _session; } + set { _session = value; @@ -1263,7 +974,6 @@ internal override RemoteSession Session } /// - /// /// /// /// @@ -1275,7 +985,7 @@ internal bool ExportEncryptedSessionKey(out string encryptedSessionKey) } catch (PSCryptoException) { - encryptedSessionKey = String.Empty; + encryptedSessionKey = string.Empty; return false; } @@ -1283,9 +993,9 @@ internal bool ExportEncryptedSessionKey(out string encryptedSessionKey) } /// - /// Gets a helper with a test session + /// Gets a helper with a test session. /// - /// helper for testing + /// Helper for testing. /// To be used only for testing internal static PSRemotingCryptoHelperServer GetTestRemotingCryptHelperServer() { @@ -1300,7 +1010,7 @@ internal static PSRemotingCryptoHelperServer GetTestRemotingCryptHelperServer() /// /// Helper for exchanging keys and encrypting/decrypting - /// secure strings for serialization in remoting + /// secure strings for serialization in remoting. /// internal class PSRemotingCryptoHelperClient : PSRemotingCryptoHelper { @@ -1312,13 +1022,11 @@ internal class PSRemotingCryptoHelperClient : PSRemotingCryptoHelper /// /// Creates the encryption provider, but generates no key. - /// The key will be imported later + /// The key will be imported later. /// internal PSRemotingCryptoHelperClient() { _rsaCryptoProvider = PSRSACryptoServiceProvider.GetRSACryptoServiceProviderForClient(); - - //_session = new RemoteSession(); } #endregion Constructors @@ -1331,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); @@ -1338,18 +1052,24 @@ 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); } /// - /// Export the public key as a base64 encoded string + /// Export the public key as a base64 encoded string. /// /// on execution will contain /// the public key as string - /// true on success - internal bool ExportLocalPublicKey(out String publicKeyAsString) + /// True on success. + internal bool ExportLocalPublicKey(out string publicKeyAsString) { // generate keys - the method already takes of creating // only when its not already created @@ -1372,7 +1092,7 @@ internal bool ExportLocalPublicKey(out String publicKeyAsString) } catch (PSCryptoException) { - publicKeyAsString = String.Empty; + publicKeyAsString = string.Empty; return false; } @@ -1380,13 +1100,12 @@ internal bool ExportLocalPublicKey(out String publicKeyAsString) } /// - /// /// /// /// internal bool ImportEncryptedSessionKey(string encryptedSessionKey) { - Dbg.Assert(!String.IsNullOrEmpty(encryptedSessionKey), "encrypted session key passed in cannot be null"); + Dbg.Assert(!string.IsNullOrEmpty(encryptedSessionKey), "encrypted session key passed in cannot be null"); try { @@ -1401,14 +1120,14 @@ internal bool ImportEncryptedSessionKey(string encryptedSessionKey) } /// - /// Represents the session to be used for requesting public key + /// Represents the session to be used for requesting public key. /// internal override RemoteSession Session { get; set; } /// - /// Gets a helper with a test session + /// Gets a helper with a test session. /// - /// helper for testing + /// Helper for testing. /// To be used only for testing internal static PSRemotingCryptoHelperClient GetTestRemotingCryptHelperClient() { diff --git a/src/System.Management.Automation/utils/EncodingUtils.cs b/src/System.Management.Automation/utils/EncodingUtils.cs index 126314d7d38..cdb467d213a 100644 --- a/src/System.Management.Automation/utils/EncodingUtils.cs +++ b/src/System.Management.Automation/utils/EncodingUtils.cs @@ -1,67 +1,76 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text; using System.Management.Automation.Internal; namespace System.Management.Automation { - internal static class EncodingConversion { - internal const string Unknown = "unknown"; + internal const string ANSI = "ansi"; + internal const string Ascii = "ascii"; + internal const string BigEndianUnicode = "bigendianunicode"; + internal const string BigEndianUtf32 = "bigendianutf32"; + internal const string Default = "default"; + internal const string OEM = "oem"; internal const string String = "string"; internal const string Unicode = "unicode"; - internal const string BigEndianUnicode = "bigendianunicode"; - internal const string Ascii = "ascii"; + 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, OEM, Unicode, Utf7, Utf8, Utf8Bom, Utf8NoBom, Utf32 + ANSI, Ascii, BigEndianUnicode, BigEndianUtf32, OEM, Unicode, Utf7, Utf8, Utf8Bom, Utf8NoBom, Utf32 }; - internal static Dictionary encodingMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + internal static readonly Dictionary encodingMap = new(StringComparer.OrdinalIgnoreCase) { - { Ascii, System.Text.Encoding.ASCII }, - { BigEndianUnicode, System.Text.Encoding.BigEndianUnicode }, - { Default, ClrFacade.GetDefaultEncoding() }, + { ANSI, Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.ANSICodePage) }, + { Ascii, Encoding.ASCII }, + { BigEndianUnicode, Encoding.BigEndianUnicode }, + { BigEndianUtf32, new UTF32Encoding(bigEndian: true, byteOrderMark: true) }, + { 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 }, }; /// - /// retrieve the encoding parameter from the command line - /// it throws if the encoding does not match the known ones + /// Retrieve the encoding parameter from the command line + /// it throws if the encoding does not match the known ones. /// - /// a System.Text.Encoding object (null if no encoding specified) + /// A System.Text.Encoding object (null if no encoding specified). 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 +90,20 @@ 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,15 +115,45 @@ internal sealed class ArgumentToEncodingTransformationAttribute : ArgumentTransf { public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) { - string encodingName = inputData as String; - Encoding foundEncoding; - if (encodingName != null && EncodingConversion.encodingMap.TryGetValue(encodingName, out foundEncoding)) + inputData = PSObject.Base(inputData); + + switch (inputData) { - return foundEncoding; + case string stringName: + if (EncodingConversion.encodingMap.TryGetValue(stringName, out Encoding foundEncoding)) + { + return foundEncoding; + } + else + { + return Encoding.GetEncoding(stringName); + } + case int intName: + return Encoding.GetEncoding(intName); } + return inputData; } - } + /// + /// Provides the set of Encoding values for tab completion of an Encoding parameter. + /// + internal sealed class ArgumentEncodingCompletionsAttribute : ArgumentCompletionsAttribute + { + public ArgumentEncodingCompletionsAttribute() : base( + EncodingConversion.ANSI, + EncodingConversion.Ascii, + EncodingConversion.BigEndianUnicode, + EncodingConversion.BigEndianUtf32, + EncodingConversion.OEM, + EncodingConversion.Unicode, + EncodingConversion.Utf7, + EncodingConversion.Utf8, + EncodingConversion.Utf8Bom, + EncodingConversion.Utf8NoBom, + EncodingConversion.Utf32 + ) + { } + } } diff --git a/src/System.Management.Automation/utils/ExecutionExceptions.cs b/src/System.Management.Automation/utils/ExecutionExceptions.cs index c80c9e038ea..ff41e045bbb 100644 --- a/src/System.Management.Automation/utils/ExecutionExceptions.cs +++ b/src/System.Management.Automation/utils/ExecutionExceptions.cs @@ -1,15 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma warning disable 1634, 1691 #pragma warning disable 56506 - 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 @@ -22,23 +19,20 @@ namespace System.Management.Automation /// /// InnerException is the error which the cmdlet hit. /// - [Serializable] public class CmdletInvocationException : RuntimeException { #region ctor /// - /// Instantiates a new instance of the CmdletInvocationException class + /// Instantiates a new instance of the CmdletInvocationException class. /// /// internal CmdletInvocationException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord), RetrieveException(errorRecord)) { - if (null == errorRecord) - { - throw new ArgumentNullException("errorRecord"); - } + ArgumentNullException.ThrowIfNull(errorRecord); + _errorRecord = errorRecord; - if (null != errorRecord.Exception) + if (errorRecord.Exception != null) { // 2005/04/13-JonN Can't do this in an unsealed class: HelpLink = errorRecord.Exception.HelpLink; // Exception.Source is set by Throw @@ -47,9 +41,9 @@ internal CmdletInvocationException(ErrorRecord errorRecord) } /// - /// Instantiates a new instance of the CmdletInvocationException class + /// Instantiates a new instance of the CmdletInvocationException class. /// - /// wrapped exception + /// Wrapped exception. /// /// identity of cmdlet, null is unknown /// @@ -57,14 +51,10 @@ internal CmdletInvocationException(Exception innerException, InvocationInfo invocationInfo) : base(RetrieveMessage(innerException), innerException) { - if (null == innerException) - { - throw new ArgumentNullException("innerException"); - } + ArgumentNullException.ThrowIfNull(innerException); // invocationInfo may be null - IContainsErrorRecord icer = innerException as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (innerException is IContainsErrorRecord icer && icer.ErrorRecord != null) { _errorRecord = new ErrorRecord(icer.ErrorRecord, innerException); } @@ -78,6 +68,7 @@ internal CmdletInvocationException(Exception innerException, ErrorCategory.NotSpecified, null); } + _errorRecord.SetInvocationInfo(invocationInfo); // 2005/04/13-JonN Can't do this in an unsealed class: HelpLink = innerException.HelpLink; // Exception.Source is set by Throw @@ -85,7 +76,7 @@ internal CmdletInvocationException(Exception innerException, } /// - /// Instantiates a new instance of the CmdletInvocationException class + /// Instantiates a new instance of the CmdletInvocationException class. /// public CmdletInvocationException() : base() @@ -93,21 +84,21 @@ public CmdletInvocationException() } /// - /// Instantiates a new instance of the CmdletInvocationException class + /// Instantiates a new instance of the CmdletInvocationException class. /// - /// - /// constructed object + /// + /// Constructed object. public CmdletInvocationException(string message) : base(message) { } /// - /// Instantiates a new instance of the CmdletInvocationException class + /// Instantiates a new instance of the CmdletInvocationException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public CmdletInvocationException(string message, Exception innerException) : base(message, innerException) @@ -120,64 +111,41 @@ public CmdletInvocationException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - bool hasErrorRecord = (null != _errorRecord); - info.AddValue("HasErrorRecord", hasErrorRecord); - if (hasErrorRecord) - info.AddValue("ErrorRecord", _errorRecord); + throw new NotSupportedException(); } #endregion Serialization #endregion ctor #region Properties /// - /// The error reported by the cmdlet + /// The error reported by the cmdlet. /// /// never null public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CmdletInvocationException", - ErrorCategory.NotSpecified, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CmdletInvocationException", + ErrorCategory.NotSpecified, + null); + return _errorRecord; } } + private ErrorRecord _errorRecord = null; #endregion Properties - } // class CmdletInvocationException + } #endregion CmdletInvocationException #region CmdletProviderInvocationException @@ -187,34 +155,31 @@ 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 /// - /// Instantiates a new instance of the CmdletProviderInvocationException class + /// Instantiates a new instance of the CmdletProviderInvocationException class. /// - /// wrapped exception + /// Wrapped exception. /// /// identity of cmdlet, null is unknown /// - /// constructed object + /// Constructed object. internal CmdletProviderInvocationException( ProviderInvocationException innerException, InvocationInfo myInvocation) : base(GetInnerException(innerException), myInvocation) { - if (null == innerException) - { - throw new ArgumentNullException("innerException"); - } + ArgumentNullException.ThrowIfNull(innerException); + _providerInvocationException = innerException; } /// - /// Instantiates a new instance of the CmdletProviderInvocationException class + /// Instantiates a new instance of the CmdletProviderInvocationException class. /// - /// constructed object + /// Constructed object. public CmdletProviderInvocationException() : base() { @@ -225,32 +190,32 @@ public CmdletProviderInvocationException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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(); } /// - /// Instantiates a new instance of the CmdletProviderInvocationException class + /// Instantiates a new instance of the CmdletProviderInvocationException class. /// - /// - /// constructed object + /// + /// Constructed object. public CmdletProviderInvocationException(string message) : base(message) { } /// - /// Instantiates a new instance of the CmdletProviderInvocationException class + /// Instantiates a new instance of the CmdletProviderInvocationException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public CmdletProviderInvocationException(string message, Exception innerException) : base(message, innerException) @@ -261,7 +226,7 @@ public CmdletProviderInvocationException(string message, #region Properties /// - /// InnerException as ProviderInvocationException + /// InnerException as ProviderInvocationException. /// /// ProviderInvocationException public ProviderInvocationException ProviderInvocationException @@ -271,8 +236,9 @@ public ProviderInvocationException ProviderInvocationException return _providerInvocationException; } } + [NonSerialized] - private ProviderInvocationException _providerInvocationException; + private readonly ProviderInvocationException _providerInvocationException; /// /// This is the ProviderInfo associated with the provider which @@ -283,9 +249,7 @@ public ProviderInfo ProviderInfo { get { - return (null == _providerInvocationException) - ? null - : _providerInvocationException.ProviderInfo; + return _providerInvocationException?.ProviderInfo; } } @@ -294,10 +258,10 @@ public ProviderInfo ProviderInfo #region Internal private static Exception GetInnerException(Exception e) { - return (e == null) ? null : e.InnerException; + return e?.InnerException; } #endregion Internal - } // CmdletProviderInvocationException + } #endregion CmdletProviderInvocationException #region PipelineStoppedException @@ -310,22 +274,21 @@ 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 /// - /// Instantiates a new instance of the PipelineStoppedException class + /// Instantiates a new instance of the PipelineStoppedException class. /// - /// constructed object + /// Constructed object. public PipelineStoppedException() : base(GetErrorText.PipelineStoppedException) { @@ -338,40 +301,39 @@ public PipelineStoppedException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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(); } /// - /// Instantiates a new instance of the PipelineStoppedException class + /// Instantiates a new instance of the PipelineStoppedException class. /// - /// - /// constructed object + /// + /// Constructed object. public PipelineStoppedException(string message) : base(message) { } /// - /// Instantiates a new instance of the PipelineStoppedException class + /// Instantiates a new instance of the PipelineStoppedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PipelineStoppedException(string message, Exception innerException) : base(message, innerException) { } #endregion ctor - } // PipelineStoppedException + } #endregion PipelineStoppedException #region PipelineClosedException @@ -381,35 +343,34 @@ public PipelineStoppedException(string message, /// been stopped. /// /// - [Serializable] public class PipelineClosedException : RuntimeException { #region ctor /// - /// Instantiates a new instance of the PipelineClosedException class + /// Instantiates a new instance of the PipelineClosedException class. /// - /// constructed object + /// Constructed object. public PipelineClosedException() : base() { } /// - /// Instantiates a new instance of the PipelineClosedException class + /// Instantiates a new instance of the PipelineClosedException class. /// - /// - /// constructed object + /// + /// Constructed object. public PipelineClosedException(string message) : base(message) { } /// - /// Instantiates a new instance of the PipelineClosedException class + /// Instantiates a new instance of the PipelineClosedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PipelineClosedException(string message, Exception innerException) : base(message, innerException) @@ -423,16 +384,17 @@ public PipelineClosedException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 - } // PipelineClosedException + } #endregion PipelineClosedException #region ActionPreferenceStopException @@ -444,42 +406,39 @@ 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 /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// - /// constructed object + /// Constructed object. public ActionPreferenceStopException() : this(GetErrorText.ActionPreferenceStop) { } /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// /// /// Non-terminating error which triggered the Stop /// - /// constructed object + /// Constructed object. internal ActionPreferenceStopException(ErrorRecord error) : this(RetrieveMessage(error)) { - if (null == error) - { - throw new ArgumentNullException("error"); - } + ArgumentNullException.ThrowIfNull(error); + _errorRecord = error; } /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. internal ActionPreferenceStopException(InvocationInfo invocationInfo, string message) : this(message) { @@ -487,17 +446,15 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, string mes } /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// internal ActionPreferenceStopException(InvocationInfo invocationInfo, ErrorRecord errorRecord, string message) : this(invocationInfo, message) { - if (errorRecord == null) - { - throw new ArgumentNullException("errorRecord"); - } + ArgumentNullException.ThrowIfNull(errorRecord); + _errorRecord = errorRecord; } @@ -507,58 +464,22 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 (null != info) - { - bool hasErrorRecord = (null != _errorRecord); - 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 /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// - /// - /// constructed object + /// + /// Constructed object. public ActionPreferenceStopException(string message) : base(message) { @@ -574,11 +495,11 @@ public ActionPreferenceStopException(string message) } /// - /// Instantiates a new instance of the ActionPreferenceStopException class + /// Instantiates a new instance of the ActionPreferenceStopException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public ActionPreferenceStopException(string message, Exception innerException) : base(message, innerException) @@ -597,7 +518,7 @@ public ActionPreferenceStopException(string message, #region Properties /// - /// see + /// See /// /// ErrorRecord /// @@ -609,23 +530,23 @@ public override ErrorRecord ErrorRecord { get { return _errorRecord ?? base.ErrorRecord; } } + private readonly ErrorRecord _errorRecord = null; #endregion Properties - } // ActionPreferenceStopException + } #endregion ActionPreferenceStopException #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 @@ -633,7 +554,7 @@ public class ParentContainsErrorRecordException : SystemException /// Instantiates a new instance of the ParentContainsErrorRecordException class. /// Note that this sets the Message and not the InnerException. /// - /// constructed object + /// Constructed object. /// /// I leave this non-standard constructor form public. /// @@ -649,30 +570,30 @@ public ParentContainsErrorRecordException(Exception wrapperException) #pragma warning restore 56506 /// - /// Instantiates a new instance of the ParentContainsErrorRecordException class + /// Instantiates a new instance of the ParentContainsErrorRecordException class. /// - /// - /// constructed object + /// + /// Constructed object. public ParentContainsErrorRecordException(string message) { _message = message; } /// - /// Instantiates a new instance of the ParentContainsErrorRecordException class + /// Instantiates a new instance of the ParentContainsErrorRecordException class. /// - /// constructed object + /// Constructed object. public ParentContainsErrorRecordException() : base() { } /// - /// Instantiates a new instance of the ParentContainsErrorRecordException class + /// Instantiates a new instance of the ParentContainsErrorRecordException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public ParentContainsErrorRecordException(string message, Exception innerException) : base(message, innerException) @@ -687,41 +608,26 @@ public ParentContainsErrorRecordException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// doesn't return - /// always + /// Serialization information. + /// 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 /// - /// Gets the message for the exception + /// Gets the message for the exception. /// public override string Message { - get { - return _message ?? (_message = (null != _wrapperException) ? _wrapperException.Message : String.Empty); - } - } - - /// - /// Serializer for - /// - /// serialization information - /// context - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) + get { - throw new PSArgumentNullException("info"); + return _message ??= (_wrapperException != null) ? _wrapperException.Message : string.Empty; } - - base.GetObjectData(info, context); - info.AddValue("ParentContainsErrorRecordException_Message", this.Message); } #region Private Data @@ -743,14 +649,13 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// /// in the ErrorRecord which contains this exception. /// - [Serializable] public class RedirectedException : RuntimeException { #region constructors /// - /// Instantiates a new instance of the RedirectedException class + /// Instantiates a new instance of the RedirectedException class. /// - /// constructed object + /// Constructed object. public RedirectedException() : base() { @@ -759,10 +664,10 @@ public RedirectedException() } /// - /// Instantiates a new instance of the RedirectedException class + /// Instantiates a new instance of the RedirectedException class. /// - /// - /// constructed object + /// + /// Constructed object. public RedirectedException(string message) : base(message) { @@ -771,11 +676,11 @@ public RedirectedException(string message) } /// - /// Instantiates a new instance of the RedirectedException class + /// Instantiates a new instance of the RedirectedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public RedirectedException(string message, Exception innerException) : base(message, innerException) @@ -789,16 +694,17 @@ public RedirectedException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 - } // class RedirectedException + } #endregion RedirectedException #region ScriptCallDepthException @@ -808,42 +714,41 @@ 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 /// - /// Instantiates a new instance of the ScriptCallDepthException class + /// Instantiates a new instance of the ScriptCallDepthException class. /// - /// constructed object + /// Constructed object. public ScriptCallDepthException() : base(GetErrorText.ScriptCallDepthException) { } /// - /// Instantiates a new instance of the ScriptCallDepthException class + /// Instantiates a new instance of the ScriptCallDepthException class. /// - /// - /// constructed object + /// + /// Constructed object. public ScriptCallDepthException(string message) : base(message) { } /// - /// Instantiates a new instance of the ScriptCallDepthException class + /// Instantiates a new instance of the ScriptCallDepthException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public ScriptCallDepthException(string message, Exception innerException) : base(message, innerException) @@ -857,30 +762,20 @@ public ScriptCallDepthException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 #region properties /// - /// see + /// See /// /// /// @@ -890,17 +785,16 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CallDepthOverflow", - ErrorCategory.InvalidOperation, - CallDepth); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CallDepthOverflow", + ErrorCategory.InvalidOperation, + CallDepth); + return _errorRecord; } } + private ErrorRecord _errorRecord = null; /// @@ -911,7 +805,7 @@ public int CallDepth get { return 0; } } #endregion properties - } // ScriptCallDepthException + } #endregion ScriptCallDepthException #region PipelineDepthException @@ -922,35 +816,34 @@ public int CallDepth /// /// /// - [Serializable] public class PipelineDepthException : SystemException, IContainsErrorRecord { #region ctor /// - /// Instantiates a new instance of the PipelineDepthException class + /// Instantiates a new instance of the PipelineDepthException class. /// - /// constructed object + /// Constructed object. public PipelineDepthException() : base(GetErrorText.PipelineDepthException) { } /// - /// Instantiates a new instance of the PipelineDepthException class + /// Instantiates a new instance of the PipelineDepthException class. /// - /// - /// constructed object + /// + /// Constructed object. public PipelineDepthException(string message) : base(message) { } /// - /// Instantiates a new instance of the PipelineDepthException class + /// Instantiates a new instance of the PipelineDepthException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PipelineDepthException(string message, Exception innerException) : base(message, innerException) @@ -964,30 +857,20 @@ public PipelineDepthException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 #region properties /// - /// see + /// See /// /// /// @@ -998,17 +881,16 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "CallDepthOverflow", - ErrorCategory.InvalidOperation, - CallDepth); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "CallDepthOverflow", + ErrorCategory.InvalidOperation, + CallDepth); + return _errorRecord; } } + private ErrorRecord _errorRecord = null; /// @@ -1020,7 +902,7 @@ public int CallDepth get { return 0; } } #endregion properties - } // PipelineDepthException + } #endregion #region HaltCommandException @@ -1037,35 +919,34 @@ 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 /// - /// Instantiates a new instance of the HaltCommandException class + /// Instantiates a new instance of the HaltCommandException class. /// - /// constructed object + /// Constructed object. public HaltCommandException() : base(StringUtil.Format(AutomationExceptions.HaltCommandException)) { } /// - /// Instantiates a new instance of the HaltCommandException class + /// Instantiates a new instance of the HaltCommandException class. /// - /// - /// constructed object + /// + /// Constructed object. public HaltCommandException(string message) : base(message) { } /// - /// Instantiates a new instance of the HaltCommandException class + /// Instantiates a new instance of the HaltCommandException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public HaltCommandException(string message, Exception innerException) : base(message, innerException) @@ -1079,17 +960,18 @@ public HaltCommandException(string message, /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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 - } // HaltCommandException + } #endregion HaltCommandException -} // namespace System.Management.Automation +} #pragma warning restore 56506 diff --git a/src/System.Management.Automation/utils/ExtensionMethods.cs b/src/System.Management.Automation/utils/ExtensionMethods.cs index 783f7b6909e..f3dc94c047b 100644 --- a/src/System.Management.Automation/utils/ExtensionMethods.cs +++ b/src/System.Management.Automation/utils/ExtensionMethods.cs @@ -1,6 +1,5 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.Generic; using System.Linq; @@ -13,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); } } @@ -37,24 +30,26 @@ internal static IEnumerable Prepend(this IEnumerable collection, T elem yield return t; } - internal static int SequenceGetHashCode(this IEnumerable xs) where T : class + internal static int SequenceGetHashCode(this IEnumerable xs) { - // algorithm based on http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode + // algorithm based on https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode if (xs == null) { return 82460653; // random number } + unchecked { 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(); } } + return hash; } } @@ -67,18 +62,13 @@ internal static int SequenceGetHashCode(this IEnumerable xs) where T : cla /// * If you want to add an extension method that will be used only by CoreCLR powershell, please add it to the partial /// 'PSTypeExtensions' class in 'CorePsExtensions.cs'. /// - internal static partial class PSTypeExtensions + internal static class PSTypeExtensions { - /// - /// Type.EmptyTypes is not in CoreCLR. Use this one to replace it. - /// - internal static Type[] EmptyTypes = new Type[0]; - /// /// Check does the type have an instance default constructor with visibility that allows calling it from subclass. /// - /// type - /// true when type has a default ctor. + /// Type. + /// True when type has a default ctor. internal static bool HasDefaultCtor(this Type type) { var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); @@ -100,12 +90,12 @@ internal static bool IsNumeric(this Type type) internal static bool IsNumericOrPrimitive(this Type type) { - return type.GetTypeInfo().IsPrimitive || LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)); + return type.IsPrimitive || LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)); } internal static bool IsSafePrimitive(this Type type) { - return type.GetTypeInfo().IsPrimitive && (type != typeof(IntPtr)) && (type != typeof(UIntPtr)); + return type.IsPrimitive && (type != typeof(IntPtr)) && (type != typeof(UIntPtr)); } internal static bool IsFloating(this Type type) @@ -127,7 +117,7 @@ internal static TypeCode GetTypeCode(this Type type) internal static IEnumerable GetCustomAttributes(this Type type, bool inherit) where T : Attribute { - return from attr in type.GetTypeInfo().GetCustomAttributes(typeof(T), inherit) + return from attr in type.GetCustomAttributes(typeof(T), inherit) where attr is T select (T)attr; } diff --git a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs index 9af3de5e71d..87da1d2a1d7 100644 --- a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs +++ b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs @@ -1,15 +1,12 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.IO; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; -using System.Text; -using System.Management.Automation.Host; -using System.Collections.Concurrent; +using System.IO; using System.Management.Automation.Internal; +using System.Text; namespace System.Management.Automation.Runspaces { @@ -49,14 +46,19 @@ internal PSSnapInTypeAndFormatErrors(string psSnapinName, ExtendedTypeDefinition } internal ExtendedTypeDefinition FormatData { get; } + internal TypeData TypeData { get; } + internal bool IsRemove { get; } + internal string FullPath { get; } + internal FormatTable FormatTable { get; } internal ConcurrentBag Errors { get; set; } internal string PSSnapinName { get { return psSnapinName; } } + internal bool FailToLoadFile; } @@ -71,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( @@ -115,7 +117,7 @@ internal static void ThrowExceptionOnError( string errorId, Collection independentErrors, Collection PSSnapinFilesCollection, - RunspaceConfigurationCategory category) + Category category) { Collection errors = new Collection(); if (independentErrors != null) @@ -148,16 +150,17 @@ internal static void ThrowExceptionOnError( allErrors.Append('\n'); } - string message = ""; - if (category == RunspaceConfigurationCategory.Types) + string message = string.Empty; + if (category == Category.Types) { message = StringUtil.Format(ExtendedTypeSystem.TypesXmlError, allErrors.ToString()); } - else if (category == RunspaceConfigurationCategory.Formats) + else if (category == Category.Formats) { message = StringUtil.Format(FormatAndOutXmlLoadingStrings.FormatLoadingErrors, allErrors.ToString()); } + RuntimeException ex = new RuntimeException(message); ex.SetErrorId(errorId); throw ex; @@ -166,9 +169,9 @@ internal static void ThrowExceptionOnError( internal static void ThrowExceptionOnError( string errorId, ConcurrentBag errors, - RunspaceConfigurationCategory category) + Category category) { - if (errors.Count == 0) + if (errors.IsEmpty) { return; } @@ -182,22 +185,26 @@ internal static void ThrowExceptionOnError( allErrors.Append('\n'); } - string message = ""; - if (category == RunspaceConfigurationCategory.Types) + string message = string.Empty; + if (category == Category.Types) { message = StringUtil.Format(ExtendedTypeSystem.TypesXmlError, allErrors.ToString()); } - else if (category == RunspaceConfigurationCategory.Formats) + else if (category == Category.Formats) { message = StringUtil.Format(FormatAndOutXmlLoadingStrings.FormatLoadingErrors, allErrors.ToString()); } + RuntimeException ex = new RuntimeException(message); ex.SetErrorId(errorId); throw ex; } + + internal enum Category + { + Types, + Formats, + } } } - - - diff --git a/src/System.Management.Automation/utils/FuzzyMatch.cs b/src/System.Management.Automation/utils/FuzzyMatch.cs new file mode 100644 index 00000000000..c4e542a3ca5 --- /dev/null +++ b/src/System.Management.Automation/utils/FuzzyMatch.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Globalization; + +namespace System.Management.Automation +{ + internal class FuzzyMatcher + { + internal readonly uint MinimumDistance; + + internal FuzzyMatcher(uint minimumDistance) + { + MinimumDistance = minimumDistance; + } + + /// + /// Determine if the two strings are considered similar. + /// + 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. + internal bool IsFuzzyMatch(string candidate, string pattern, out int score) + { + score = GetDamerauLevenshteinDistance(candidate, pattern); + return score <= MinimumDistance; + } + + /// + /// Compute the case-insensitive distance between two strings. + /// Based off https://www.csharpstar.com/csharp-string-distance-algorithm/. + /// + /// 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. + internal static int GetDamerauLevenshteinDistance(string string1, string string2) + { + string1 = string1.ToUpper(CultureInfo.CurrentCulture); + string2 = string2.ToUpper(CultureInfo.CurrentCulture); + + var bounds = new { Height = string1.Length + 1, Width = string2.Length + 1 }; + + 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 = 1; height < bounds.Height; height++) + { + for (int width = 1; width < bounds.Width; width++) + { + int cost = (string1[height - 1] == string2[width - 1]) ? 0 : 1; + int insertion = matrix[height, width - 1] + 1; + int deletion = matrix[height - 1, width] + 1; + int substitution = matrix[height - 1, width - 1] + cost; + + int distance = Math.Min(insertion, Math.Min(deletion, substitution)); + + if (height > 1 && width > 1 && string1[height - 1] == string2[width - 2] && string1[height - 2] == string2[width - 1]) + { + distance = Math.Min(distance, matrix[height - 2, width - 2] + cost); + } + + matrix[height, width] = distance; + } + } + + return matrix[bounds.Height - 1, bounds.Width - 1]; + } + } +} diff --git a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs index 42facc45013..ec780222345 100644 --- a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs +++ b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs @@ -1,11 +1,5 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// -// Implements GraphicalHostReflectionWrapper -// -//----------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. namespace System.Management.Automation.Internal { @@ -22,9 +16,9 @@ namespace System.Management.Automation.Internal /// Microsoft.PowerShell.GraphicalHost.dll contains: /// 1) out-gridview window implementation (the actual cmdlet is in Microsoft.PowerShell.Commands.Utility.dll) /// 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 + /// 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. @@ -32,17 +26,17 @@ internal class GraphicalHostReflectionWrapper private Assembly _graphicalHostAssembly; /// - /// A type in Microsoft.PowerShell.GraphicalHost.dll we want to invoke members on + /// A type in Microsoft.PowerShell.GraphicalHost.dll we want to invoke members on. /// private Type _graphicalHostHelperType; /// - /// An object in Microsoft.PowerShell.GraphicalHost.dll of type graphicalHostHelperType + /// An object in Microsoft.PowerShell.GraphicalHost.dll of type graphicalHostHelperType. /// private object _graphicalHostHelperObject; /// - /// Prevents a default instance of the GraphicalHostReflectionWrapper class from being created + /// Prevents a default instance of the GraphicalHostReflectionWrapper class from being created. /// private GraphicalHostReflectionWrapper() { @@ -50,38 +44,38 @@ private GraphicalHostReflectionWrapper() /// /// Retrieves a wrapper used to invoke members of the type with name - /// in Microsoft.PowerShell.GraphicalHost.dll + /// in Microsoft.PowerShell.GraphicalHost.dll. /// - /// the cmdlet requesting the wrapper (used to throw terminating errors) - /// the type name we want to invoke members from + /// The cmdlet requesting the wrapper (used to throw terminating errors). + /// The type name we want to invoke members from. /// /// wrapper used to invoke members of the type with name /// in Microsoft.PowerShell.GraphicalHost.dll /// - /// When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly + /// 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); } /// /// Retrieves a wrapper used to invoke members of the type with name - /// in Microsoft.PowerShell.GraphicalHost.dll + /// in Microsoft.PowerShell.GraphicalHost.dll. /// - /// the cmdlet requesting the wrapper (used to throw terminating errors) - /// the type name we want to invoke members from - /// used for error messages + /// The cmdlet requesting the wrapper (used to throw terminating errors). + /// The type name we want to invoke members from. + /// Used for error messages. /// /// wrapper used to invoke members of the type with name /// in Microsoft.PowerShell.GraphicalHost.dll /// - /// When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly + /// When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly. [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)), @@ -93,10 +87,11 @@ 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.CultureInfo = new CultureInfo(String.Empty); // Neutral culture + graphicalHostAssemblyName.Version = smaAssemblyName.Version; + graphicalHostAssemblyName.CultureInfo = new CultureInfo(string.Empty); // Neutral culture graphicalHostAssemblyName.SetPublicKeyToken(new byte[] { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }); try @@ -130,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"); } @@ -147,10 +142,10 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper } /// - /// Used to escape characters that are not friendly to WPF binding + /// Used to escape characters that are not friendly to WPF binding. /// - /// property name to be used in binding - /// string with escaped characters + /// Property name to be used in binding. + /// String with escaped characters. internal static string EscapeBinding(string propertyName) { return propertyName.Replace("/", " ").Replace(".", " "); @@ -159,9 +154,9 @@ internal static string EscapeBinding(string propertyName) /// /// Calls an instance method with name passing the /// - /// name of the method to call - /// arguments to call the method with - /// The method return value + /// Name of the method to call. + /// Arguments to call the method with. + /// The method return value. internal object CallMethod(string methodName, params object[] arguments) { Diagnostics.Assert(_graphicalHostHelperObject != null, "there should be a constructor in order to call an instance method"); @@ -173,9 +168,9 @@ internal object CallMethod(string methodName, params object[] arguments) /// /// Calls a static method with name passing the /// - /// name of the method to call - /// arguments to call the method with - /// The method return value + /// Name of the method to call. + /// Arguments to call the method with. + /// The method return value. internal object CallStaticMethod(string methodName, params object[] arguments) { MethodInfo method = _graphicalHostHelperType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); @@ -186,36 +181,36 @@ internal object CallStaticMethod(string methodName, params object[] arguments) /// /// Gets the value of an instance property with name /// - /// name of the instance property to get the value from - /// the value of an instance property with name + /// Name of the instance property to get the value from. + /// The value of an instance property with name 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()); } /// /// Gets the value of a static property with name /// - /// name of the static property to get the value from - /// the value of a static property with name + /// Name of the static property to get the value from. + /// The value of a static property with name 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()); } /// - /// Returns true if the is being run remotely + /// Returns true if the is being run remotely. /// - /// cmdlet we want to see if is running remotely - /// true if the is being run remotely + /// Cmdlet we want to see if is running remotely. + /// True if the is being run remotely. private static bool IsInputFromRemoting(PSCmdlet parentCmdlet) { - Diagnostics.Assert(null != parentCmdlet.SessionState, "SessionState should always be available."); + Diagnostics.Assert(parentCmdlet.SessionState != null, "SessionState should always be available."); PSVariable senderInfo = parentCmdlet.SessionState.PSVariable.Get("PSSenderInfo"); return senderInfo != null; diff --git a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs index f5fdbb88d8d..df36d248d18 100644 --- a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs +++ b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs @@ -1,74 +1,54 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.Serialization; using System.Management.Automation.Internal; +using System.Runtime.Serialization; 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 { #region ctors /// - /// - /// Initializes a new instance of the HostException class - /// + /// 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(); } /// - /// - /// Initializes a new instance of the HostException class and defines the error message - /// + /// Initializes a new instance of the HostException class and defines the error message. /// /// - /// /// The error message that explains the reason for the exception. - /// /// - public - HostException(string message) : base(message) + HostException(string message) + : base(message) { SetDefaultErrorRecord(); } - /// - /// /// Initializes a new instance of the HostException class and defines the error message and /// inner exception. - /// /// /// - /// /// The error message that explains the reason for the exception. - /// /// /// - /// /// The exception that is the cause of the current exception. If the /// 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) @@ -77,68 +57,56 @@ class HostException : RuntimeException } /// - /// /// Initializes a new instance of the HostException class and defines the error message, /// inner exception, the error ID, and the error category. - /// /// /// - /// /// The error message that explains the reason for the exception. - /// /// /// - /// /// The exception that is the cause of the current exception. If the /// parameter is not a null reference, the current exception is raised in a catch /// block that handles the inner exception. - /// /// /// - /// /// The string that should uniquely identifies the situation where the exception is thrown. /// The string should not contain white space. - /// /// /// - /// /// The ErrorCategory into which this exception situation falls - /// /// /// /// 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); } /// - /// /// Initializes a new instance of the HostException class and defines the SerializationInfo /// and the StreamingContext. - /// /// /// - /// /// The object that holds the serialized object data. - /// /// /// - /// /// 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() @@ -151,65 +119,47 @@ private void SetDefaultErrorRecord() } /// - /// /// Defines the exception thrown when an error occurs from prompting for a command parameter. - /// /// - - [Serializable] public class PromptingException : HostException { #region ctors /// - /// - /// Initializes a new instance of the PromptingException class - /// + /// 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(); } /// - /// - /// Initializes a new instance of the PromptingException class and defines the error message - /// + /// Initializes a new instance of the PromptingException class and defines the error message. /// /// - /// /// The error message that explains the reason for the exception. - /// /// - public - PromptingException(string message) : base(message) + PromptingException(string message) + : base(message) { SetDefaultErrorRecord(); } - /// - /// /// Initializes a new instance of the PromptingException class and defines the error message and /// inner exception. - /// /// /// - /// /// The error message that explains the reason for the exception. - /// /// /// - /// /// The exception that is the cause of the current exception. If the /// 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) @@ -218,65 +168,52 @@ class PromptingException : HostException } /// - /// /// Initializes a new instance of the PromptingException class and defines the error message, /// inner exception, the error ID, and the error category. - /// /// /// - /// /// The error message that explains the reason for the exception. - /// /// /// - /// /// The exception that is the cause of the current exception. If the /// parameter is not a null reference, the current exception is raised in a catch /// block that handles the inner exception. - /// /// /// - /// /// The string that should uniquely identifies the situation where the exception is thrown. /// The string should not contain white space. - /// /// /// - /// /// The ErrorCategory into which this exception situation falls - /// /// /// /// 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) { } /// - /// /// Initializes a new instance of the HostException class and defines the SerializationInfo /// and the StreamingContext. - /// /// /// - /// /// The object that holds the serialized object data. - /// /// /// - /// /// 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 5962ce5a65b..c2ef1d6cf32 100644 --- a/src/System.Management.Automation/utils/IObjectReader.cs +++ b/src/System.Management.Automation/utils/IObjectReader.cs @@ -1,11 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Threading; using System.Collections.Generic; using System.Collections.ObjectModel; -using Dbg = System.Management.Automation.Diagnostics; +using System.Threading; namespace System.Management.Automation.Runspaces { @@ -18,12 +16,12 @@ namespace System.Management.Automation.Runspaces public abstract class PipelineReader { /// - /// Event fired when data is added to the buffer + /// Event fired when data is added to the buffer. /// public abstract event EventHandler DataReady; /// - /// Signaled when data is available + /// Signaled when data is available. /// public abstract WaitHandle WaitHandle { @@ -58,7 +56,7 @@ public abstract bool IsOpen } /// - /// Returns the number of objects currently available in the underlying stream + /// Returns the number of objects currently available in the underlying stream. /// public abstract int Count { @@ -66,7 +64,7 @@ public abstract int Count } /// - /// Get the capacity of the stream + /// Get the capacity of the stream. /// /// /// The capacity of the stream. @@ -82,7 +80,7 @@ public abstract int MaxCapacity } /// - /// Close the stream + /// Close the stream. /// /// /// Causes subsequent calls to IsOpen to return false and calls to @@ -95,10 +93,10 @@ public abstract int MaxCapacity public abstract void Close(); /// - /// Read at most objects + /// Read at most objects. /// - /// The maximum number of objects to read - /// The objects read + /// The maximum number of objects to read. + /// The objects read. /// /// This method blocks if the number of objects in the stream is less than /// and the stream is not closed. @@ -106,9 +104,9 @@ public abstract int MaxCapacity public abstract Collection Read(int count); /// - /// Read a single object from the stream + /// Read a single object from the stream. /// - /// the next object in the stream + /// The next object in the stream. /// This method blocks if the stream is empty public abstract T Read(); @@ -153,13 +151,13 @@ public abstract int MaxCapacity /// /// The next object in the stream or AutomationNull.Value if the stream is empty /// - /// The stream is closed + /// The stream is closed. public abstract T Peek(); #region IEnumerable Members /// - /// Returns an enumerator that reads the items in the pipeline + /// Returns an enumerator that reads the items in the pipeline. /// internal IEnumerator GetReadEnumerator() { diff --git a/src/System.Management.Automation/utils/IObjectWriter.cs b/src/System.Management.Automation/utils/IObjectWriter.cs index b33282bacbf..49181e34584 100644 --- a/src/System.Management.Automation/utils/IObjectWriter.cs +++ b/src/System.Management.Automation/utils/IObjectWriter.cs @@ -1,13 +1,11 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Threading; namespace System.Management.Automation.Runspaces { - using System; - using System.Threading; - using System.Collections; - /// /// PipelineWriter allows the caller to provide an asynchronous stream of objects /// as input to a . @@ -37,7 +35,7 @@ public abstract bool IsOpen } /// - /// Returns the number of objects currently in the underlying stream + /// Returns the number of objects currently in the underlying stream. /// public abstract int Count { @@ -45,7 +43,7 @@ public abstract int Count } /// - /// Get the capacity of the stream + /// Get the capacity of the stream. /// /// /// The capacity of the stream. @@ -61,7 +59,7 @@ public abstract int MaxCapacity } /// - /// Close the stream + /// Close the stream. /// /// /// Causes subsequent calls to IsOpen to return false and calls to @@ -83,9 +81,9 @@ public abstract int MaxCapacity public abstract void Flush(); /// - /// Write a single object into the underlying stream + /// Write a single object into the underlying stream. /// - /// The object to add to the stream + /// The object to add to the stream. /// /// One, if the write was successful, otherwise; /// zero if the stream was closed before the object could be written, @@ -100,9 +98,9 @@ public abstract int MaxCapacity public abstract int Write(object obj); /// - /// Write multiple objects to the underlying stream + /// Write multiple objects to the underlying stream. /// - /// object or enumeration to read from + /// Object or enumeration to read from. /// /// If enumerateCollection is true, and /// is an enumeration according to LanguagePrimitives.GetEnumerable, @@ -110,13 +108,13 @@ public abstract int MaxCapacity /// written separately. Otherwise, /// will be written as a single object. /// - /// The number of objects written + /// The number of objects written. /// /// The underlying stream is already closed /// /// /// 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. /// @@ -125,19 +123,22 @@ 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 { get { return _waitHandle; } } private bool _isOpen = true; + public override bool IsOpen { get { return _isOpen; } } private int _count = 0; + public override int Count { get { return _count; } @@ -159,7 +160,7 @@ public override void Flush() public override int Write(object obj) { - int numberOfObjectsWritten = 1; + const int numberOfObjectsWritten = 1; _count += numberOfObjectsWritten; return numberOfObjectsWritten; } @@ -190,5 +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 5253113fe62..24660d348e7 100644 --- a/src/System.Management.Automation/utils/MetadataExceptions.cs +++ b/src/System.Management.Automation/utils/MetadataExceptions.cs @@ -1,35 +1,34 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; -using System.Runtime.Serialization; using System.Management.Automation.Internal; +using System.Runtime.Serialization; namespace System.Management.Automation { /// - /// Defines the exception thrown for all Metadata errors + /// Defines the exception thrown for all Metadata errors. /// - [Serializable] public class MetadataException : RuntimeException { internal const string MetadataMemberInitialization = "MetadataMemberInitialization"; internal const string BaseName = "Metadata"; /// - /// Initializes a new instance of MetadataException with serialization parameters + /// Initializes a new instance of MetadataException with serialization parameters. /// - /// serialization information - /// streaming context - protected MetadataException(SerializationInfo info, StreamingContext context) : base(info, context) + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected MetadataException(SerializationInfo info, StreamingContext context) { - SetErrorCategory(ErrorCategory.MetadataError); + throw new NotSupportedException(); } /// /// Initializes a new instance of MetadataException with the message set - /// to typeof(MetadataException).FullName + /// to typeof(MetadataException).FullName. /// public MetadataException() : base(typeof(MetadataException).FullName) { @@ -37,27 +36,32 @@ public MetadataException() : base(typeof(MetadataException).FullName) } /// - /// Initializes a new instance of MetadataException setting the message + /// Initializes a new instance of MetadataException setting the message. /// - /// the exception's message + /// The exception's message. public MetadataException(string message) : base(message) { SetErrorCategory(ErrorCategory.MetadataError); } /// - /// Initializes a new instance of MetadataException setting the message and innerException + /// Initializes a new instance of MetadataException setting the message and innerException. /// - /// the exception's message - /// the exceptions's inner exception + /// The exception's message. + /// 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,10 +69,8 @@ internal MetadataException(string errorId, Exception innerException, string reso } /// - /// Defines the exception thrown for all Validate attributes + /// Defines the exception thrown for all Validate attributes. /// - [Serializable] - [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class ValidationMetadataException : MetadataException { internal const string ValidateRangeElementType = "ValidateRangeElementType"; @@ -82,10 +84,8 @@ public class ValidationMetadataException : MetadataException internal const string ValidateRangeGreaterThanMaxRangeFailure = "ValidateRangeGreaterThanMaxRangeFailure"; internal const string ValidateRangeSmallerThanMinRangeFailure = "ValidateRangeSmallerThanMinRangeFailure"; - internal const string ValidateFailureResult = "ValidateFailureResult"; - internal const string ValidatePatternFailure = "ValidatePatternFailure"; internal const string ValidateScriptFailure = "ValidateScriptFailure"; @@ -103,31 +103,39 @@ public class ValidationMetadataException : MetadataException internal const string InvalidValueFailure = "InvalidValueFailure"; /// - /// Initializes a new instance of ValidationMetadataException with serialization parameters + /// Initializes a new instance of ValidationMetadataException with serialization parameters. /// - /// serialization information - /// streaming context - protected ValidationMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + /// Serialization information. + /// Streaming 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 + /// to typeof(ValidationMetadataException).FullName. /// public ValidationMetadataException() : base(typeof(ValidationMetadataException).FullName) { } /// - /// Initializes a new instance of ValidationMetadataException setting the message + /// Initializes a new instance of ValidationMetadataException setting the message. /// - /// the exception's message + /// The exception's message. public ValidationMetadataException(string message) : this(message, false) { } /// - /// Initializes a new instance of ValidationMetadataException setting the message and innerException + /// Initializes a new instance of ValidationMetadataException setting the message and innerException. /// - /// the exception's message - /// the exceptions's inner exception + /// The exception's message. + /// 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) { } @@ -146,9 +154,8 @@ internal ValidationMetadataException(string message, bool swallowException) : ba } /// - /// Make the positional binding swallow this exception when it's set to true + /// Make the positional binding swallow this exception when it's set to true. /// - /// /// /// This property is only used internally in the positional binding phase /// @@ -156,83 +163,107 @@ internal bool SwallowException { get { return _swallowException; } } - private bool _swallowException = false; + + private readonly bool _swallowException = false; } /// - /// Defines the exception thrown for all ArgumentTransformation attributes + /// Defines the exception thrown for all ArgumentTransformation attributes. /// - [Serializable] public class ArgumentTransformationMetadataException : MetadataException { internal const string ArgumentTransformationArgumentsShouldBeStrings = "ArgumentTransformationArgumentsShouldBeStrings"; /// - /// Initializes a new instance of ArgumentTransformationMetadataException with serialization parameters + /// Initializes a new instance of ArgumentTransformationMetadataException with serialization parameters. /// - /// serialization information - /// streaming context - protected ArgumentTransformationMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + /// Serialization information. + /// Streaming 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 + /// 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 + /// Initializes a new instance of ArgumentTransformationMetadataException setting the message. /// - /// the exception's message - public ArgumentTransformationMetadataException(string message) : base(message) { } + /// The exception's message. + public ArgumentTransformationMetadataException(string message) + : base(message) { } + /// - /// Initializes a new instance of ArgumentTransformationMetadataException setting the message and innerException + /// 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 message. + /// 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) { } } /// - /// Defines the exception thrown for all parameter binding exceptions related to metadata attributes + /// Defines the exception thrown for all parameter binding exceptions related to metadata attributes. /// - [Serializable] public class ParsingMetadataException : MetadataException { internal const string ParsingTooManyParameterSets = "ParsingTooManyParameterSets"; /// - /// Initializes a new instance of ParsingMetadataException with serialization parameters + /// Initializes a new instance of ParsingMetadataException with serialization parameters. /// - /// serialization information - /// streaming context - protected ParsingMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + /// Serialization information. + /// Streaming 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 + /// 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 + /// Initializes a new instance of ParsingMetadataException setting the message. /// - /// the exception's message - public ParsingMetadataException(string message) : base(message) { } + /// The exception's message. + public ParsingMetadataException(string message) + : base(message) { } + /// - /// Initializes a new instance of ParsingMetadataException setting the message and innerException + /// 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 message. + /// 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 9be5db46b12..452527d1c18 100644 --- a/src/System.Management.Automation/utils/MshArgumentException.cs +++ b/src/System.Management.Automation/utils/MshArgumentException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,7 +23,7 @@ public class PSArgumentException /// /// Initializes a new instance of the PSArgumentException class. /// - /// constructed object + /// Constructed object. public PSArgumentException() : base() { @@ -35,8 +32,8 @@ public PSArgumentException() /// /// Initializes a new instance of the PSArgumentException class. /// - /// - /// constructed object + /// + /// Constructed object. /// /// Per MSDN, the parameter is message. /// I confirm this experimentally as well. @@ -49,9 +46,9 @@ public PSArgumentException(string message) /// /// Initializes a new instance of the PSArgumentException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. /// /// Note the unusual order of the construction parameters. /// ArgumentException has this ctor form and we imitate it here. @@ -68,42 +65,24 @@ public PSArgumentException(string message, string paramName) /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - info.AddValue("PSArgumentException_MessageOverride", _message); - } #endregion Serialization /// /// Initializes a new instance of the PSArgumentException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSArgumentException(string message, Exception innerException) : base(message, innerException) @@ -113,7 +92,7 @@ public PSArgumentException(string message, #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -124,22 +103,21 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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 + /// See /// /// /// Exception.Message is get-only, but you can effectively @@ -148,9 +126,9 @@ public ErrorRecord ErrorRecord /// public override string Message { - get { return String.IsNullOrEmpty(_message) ? base.Message : _message; } + get { return string.IsNullOrEmpty(_message) ? base.Message : _message; } } - private string _message; - } // PSArgumentException -} // System.Management.Automation + private readonly string _message; + } +} diff --git a/src/System.Management.Automation/utils/MshArgumentNullException.cs b/src/System.Management.Automation/utils/MshArgumentNullException.cs index ac439801f62..16ef442e5ec 100644 --- a/src/System.Management.Automation/utils/MshArgumentNullException.cs +++ b/src/System.Management.Automation/utils/MshArgumentNullException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,7 +23,7 @@ public class PSArgumentNullException /// /// Initializes a new instance of the PSArgumentNullException class. /// - /// constructed object + /// Constructed object. public PSArgumentNullException() : base() { @@ -35,8 +32,8 @@ public PSArgumentNullException() /// /// Initializes a new instance of the PSArgumentNullException class. /// - /// - /// constructed object + /// + /// Constructed object. /// /// Per MSDN, the parameter is paramName and not message. /// I confirm this experimentally as well. @@ -49,9 +46,9 @@ public PSArgumentNullException(string paramName) /// /// Initializes a new instance of the PSArgumentNullException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSArgumentNullException(string message, Exception innerException) : base(message, innerException) { @@ -61,9 +58,9 @@ public PSArgumentNullException(string message, Exception innerException) /// /// Initializes a new instance of the PSArgumentNullException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. /// /// ArgumentNullException has this ctor form and we imitate it here. /// @@ -79,39 +76,20 @@ public PSArgumentNullException(string paramName, string message) /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - info.AddValue("PSArgumentNullException_MessageOverride", _message); + throw new NotSupportedException(); } #endregion Serialization #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -122,22 +100,21 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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 + /// See /// /// /// Exception.Message is get-only, but you can effectively @@ -146,9 +123,9 @@ public ErrorRecord ErrorRecord /// public override string Message { - get { return String.IsNullOrEmpty(_message) ? base.Message : _message; } + get { return string.IsNullOrEmpty(_message) ? base.Message : _message; } } - private string _message; - } // PSArgumentNullException -} // System.Management.Automation + private readonly string _message; + } +} diff --git a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs index 3bf01bf663c..4e65ed0acb1 100644 --- a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs +++ b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,18 +13,17 @@ 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 { #region ctor /// - /// Constructor for class PSArgumentOutOfRangeException + /// Constructor for class PSArgumentOutOfRangeException. /// - /// constructed object + /// Constructed object. public PSArgumentOutOfRangeException() : base() { @@ -35,8 +32,8 @@ public PSArgumentOutOfRangeException() /// /// Initializes a new instance of the PSArgumentOutOfRangeException class. /// - /// - /// constructed object + /// + /// Constructed object. /// /// Per MSDN, the parameter is paramName and not message. /// I confirm this experimentally as well. @@ -49,10 +46,10 @@ public PSArgumentOutOfRangeException(string paramName) /// /// Initializes a new instance of the PSArgumentOutOfRangeException class. /// - /// - /// - /// - /// constructed object + /// + /// + /// + /// Constructed object. /// /// ArgumentOutOfRangeException has this ctor form and we imitate it here. /// @@ -67,40 +64,24 @@ public PSArgumentOutOfRangeException(string paramName, object actualValue, strin /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } #endregion Serialization /// /// Initializes a new instance of the PSArgumentOutOfRangeException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSArgumentOutOfRangeException(string message, Exception innerException) : base(message, innerException) @@ -109,7 +90,7 @@ public PSArgumentOutOfRangeException(string message, #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -120,19 +101,17 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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"; - } // PSArgumentOutOfRangeException -} // System.Management.Automation + private ErrorRecord _errorRecord; + private readonly string _errorId = "ArgumentOutOfRange"; + } +} diff --git a/src/System.Management.Automation/utils/MshInvalidOperationException.cs b/src/System.Management.Automation/utils/MshInvalidOperationException.cs index a05810a5fc0..8400e762360 100644 --- a/src/System.Management.Automation/utils/MshInvalidOperationException.cs +++ b/src/System.Management.Automation/utils/MshInvalidOperationException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,7 +23,7 @@ public class PSInvalidOperationException /// /// Initializes a new instance of the PSInvalidOperationException class. /// - /// constructed object + /// Constructed object. public PSInvalidOperationException() : base() { @@ -38,39 +35,22 @@ public PSInvalidOperationException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } #endregion Serialization /// /// Initializes a new instance of the PSInvalidOperationException class. /// - /// - /// constructed object + /// + /// Constructed object. public PSInvalidOperationException(string message) : base(message) { @@ -79,9 +59,9 @@ public PSInvalidOperationException(string message) /// /// Initializes a new instance of the PSInvalidOperationException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSInvalidOperationException(string message, Exception innerException) : base(message, innerException) @@ -91,12 +71,12 @@ public PSInvalidOperationException(string message, /// /// Initializes a new instance of the PSInvalidOperationException class. /// - /// - /// - /// - /// - /// - /// constructed object + /// + /// + /// + /// + /// + /// Constructed object. internal PSInvalidOperationException(string message, Exception innerException, string errorId, ErrorCategory errorCategory, object target) : base(message, innerException) { @@ -107,7 +87,7 @@ internal PSInvalidOperationException(string message, Exception innerException, s #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -118,26 +98,25 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _target); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _target); + return _errorRecord; } } + private ErrorRecord _errorRecord; private string _errorId = "InvalidOperation"; + internal void SetErrorId(string errorId) { _errorId = errorId; } - private ErrorCategory _errorCategory = ErrorCategory.InvalidOperation; - private object _target = null; - } // PSInvalidOperationException -} // System.Management.Automation - + 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 f5ab26deaa9..1b925053410 100644 --- a/src/System.Management.Automation/utils/MshNotImplementedException.cs +++ b/src/System.Management.Automation/utils/MshNotImplementedException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,7 +23,7 @@ public class PSNotImplementedException /// /// Initializes a new instance of the PSNotImplementedException class. /// - /// constructed object + /// Constructed object. public PSNotImplementedException() : base() { @@ -38,39 +35,22 @@ public PSNotImplementedException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); + throw new NotSupportedException(); } #endregion Serialization /// /// Initializes a new instance of the PSNotImplementedException class. /// - /// - /// constructed object + /// + /// Constructed object. public PSNotImplementedException(string message) : base(message) { @@ -79,9 +59,9 @@ public PSNotImplementedException(string message) /// /// Initializes a new instance of the PSNotImplementedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSNotImplementedException(string message, Exception innerException) : base(message, innerException) @@ -90,7 +70,7 @@ public PSNotImplementedException(string message, #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -101,19 +81,17 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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"; - } // PSNotImplementedException -} // System.Management.Automation + private ErrorRecord _errorRecord; + private readonly string _errorId = "NotImplemented"; + } +} diff --git a/src/System.Management.Automation/utils/MshNotSupportedException.cs b/src/System.Management.Automation/utils/MshNotSupportedException.cs index 93cc9754604..a1e519a960e 100644 --- a/src/System.Management.Automation/utils/MshNotSupportedException.cs +++ b/src/System.Management.Automation/utils/MshNotSupportedException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,7 +23,7 @@ public class PSNotSupportedException /// /// Initializes a new instance of the PSNotSupportedException class. /// - /// constructed object + /// Constructed object. public PSNotSupportedException() : base() { @@ -38,39 +35,23 @@ public PSNotSupportedException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } #endregion Serialization /// /// Initializes a new instance of the PSNotSupportedException class. /// - /// - /// constructed object + /// + /// Constructed object. public PSNotSupportedException(string message) : base(message) { @@ -79,9 +60,9 @@ public PSNotSupportedException(string message) /// /// Initializes a new instance of the PSNotSupportedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSNotSupportedException(string message, Exception innerException) : base(message, innerException) @@ -90,7 +71,7 @@ public PSNotSupportedException(string message, #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -101,19 +82,17 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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"; - } // PSNotSupportedException -} // System.Management.Automation + private ErrorRecord _errorRecord; + private readonly string _errorId = "NotSupported"; + } +} diff --git a/src/System.Management.Automation/utils/MshObjectDisposedException.cs b/src/System.Management.Automation/utils/MshObjectDisposedException.cs index 0a83ba5db21..1bbf1f846d4 100644 --- a/src/System.Management.Automation/utils/MshObjectDisposedException.cs +++ b/src/System.Management.Automation/utils/MshObjectDisposedException.cs @@ -1,9 +1,7 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -15,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 { @@ -26,8 +23,8 @@ public class PSObjectDisposedException /// /// Initializes a new instance of the PSObjectDisposedException class. /// - /// - /// constructed object + /// + /// Constructed object. /// /// Per MSDN, the parameter is objectName and not message. /// I confirm this experimentally as well. @@ -41,9 +38,9 @@ public PSObjectDisposedException(string objectName) /// /// Initializes a new instance of the PSObjectDisposedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSObjectDisposedException(string objectName, string message) : base(objectName, message) { @@ -52,9 +49,9 @@ public PSObjectDisposedException(string objectName, string message) /// /// Initializes a new instance of the PSObjectDisposedException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public PSObjectDisposedException(string message, Exception innerException) : base(message, innerException) { @@ -66,37 +63,20 @@ public PSObjectDisposedException(string message, Exception innerException) /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object - protected PSObjectDisposedException(SerializationInfo info, - StreamingContext context) - : base(info, context) + /// Serialization information. + /// Streaming context. + /// Constructed object. + [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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - } #endregion Serialization #endregion ctor /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -107,19 +87,17 @@ public ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _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"; - } // PSObjectDisposedException -} // System.Management.Automation + private ErrorRecord _errorRecord; + private readonly string _errorId = "ObjectDisposed"; + } +} diff --git a/src/System.Management.Automation/utils/MshTraceSource.cs b/src/System.Management.Automation/utils/MshTraceSource.cs index 8d2f5c888fc..dd7d3214ff3 100644 --- a/src/System.Management.Automation/utils/MshTraceSource.cs +++ b/src/System.Management.Automation/utils/MshTraceSource.cs @@ -1,6 +1,6 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #define TRACE using System.Reflection; @@ -10,16 +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. /// - /// /// /// - /// /// /// The position for the command or parameter that caused the error. /// If position is null, the one from the InvocationInfo is used. @@ -43,43 +36,36 @@ public class ParameterBindingException : RuntimeException /// token.OffsetInLine == {5} /// --> /// - /// /// /// The parameter on which binding caused the error. /// /// - /// /// /// The Type the parameter was expecting. /// /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// - /// /// /// If or /// is null or empty. @@ -96,14 +82,14 @@ internal ParameterBindingException( params object[] args) : base(errorCategory, invocationInfo, errorPosition, errorId, null, null) { - if (String.IsNullOrEmpty(resourceString)) + if (string.IsNullOrEmpty(resourceString)) { - throw PSTraceSource.NewArgumentException("resourceString"); + throw PSTraceSource.NewArgumentException(nameof(resourceString)); } - if (String.IsNullOrEmpty(errorId)) + if (string.IsNullOrEmpty(errorId)) { - throw PSTraceSource.NewArgumentException("errorId"); + throw PSTraceSource.NewArgumentException(nameof(errorId)); } _invocationInfo = invocationInfo; @@ -121,11 +107,13 @@ internal ParameterBindingException( { errorPosition = invocationInfo.ScriptPosition; } + if (errorPosition != null) { _line = errorPosition.StartLineNumber; _offset = errorPosition.StartColumnNumber; } + _resourceString = resourceString; _errorId = errorId; @@ -136,23 +124,19 @@ internal ParameterBindingException( } /// - /// Constructs a ParameterBindingException + /// Constructs a ParameterBindingException. /// - /// /// /// The inner exception. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// If position is null, the one from the InvocationInfo is used. @@ -160,48 +144,39 @@ internal ParameterBindingException( /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If is null. /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingException( Exception innerException, ErrorCategory errorCategory, @@ -217,17 +192,17 @@ internal ParameterBindingException( { if (invocationInfo == null) { - throw PSTraceSource.NewArgumentNullException("invocationInfo"); + throw PSTraceSource.NewArgumentNullException(nameof(invocationInfo)); } - if (String.IsNullOrEmpty(resourceString)) + if (string.IsNullOrEmpty(resourceString)) { - throw PSTraceSource.NewArgumentException("resourceString"); + throw PSTraceSource.NewArgumentException(nameof(resourceString)); } - if (String.IsNullOrEmpty(errorId)) + if (string.IsNullOrEmpty(errorId)) { - throw PSTraceSource.NewArgumentException("errorId"); + throw PSTraceSource.NewArgumentException(nameof(errorId)); } _invocationInfo = invocationInfo; @@ -236,10 +211,8 @@ internal ParameterBindingException( _parameterType = parameterType; _typeSpecified = typeSpecified; - if (errorPosition == null) - { - errorPosition = invocationInfo.ScriptPosition; - } + errorPosition ??= invocationInfo.ScriptPosition; + if (errorPosition != null) { _line = errorPosition.StartLineNumber; @@ -256,7 +229,6 @@ internal ParameterBindingException( } /// - /// /// /// /// @@ -267,16 +239,16 @@ internal ParameterBindingException( ParameterBindingException pbex, string resourceString, params object[] args) - : base(String.Empty, innerException) + : base(string.Empty, innerException) { if (pbex == null) { - throw PSTraceSource.NewArgumentNullException("pbex"); + throw PSTraceSource.NewArgumentNullException(nameof(pbex)); } - if (String.IsNullOrEmpty(resourceString)) + if (string.IsNullOrEmpty(resourceString)) { - throw PSTraceSource.NewArgumentException("resourceString"); + throw PSTraceSource.NewArgumentException(nameof(resourceString)); } _invocationInfo = pbex.CommandInvocation; @@ -284,6 +256,7 @@ internal ParameterBindingException( { _commandName = _invocationInfo.MyCommand.Name; } + IScriptExtent errorPosition = null; if (_invocationInfo != null) { @@ -318,49 +291,18 @@ internal ParameterBindingException( /// /// Constructors a ParameterBindingException using serialized data. /// - /// /// /// serialization information /// - /// /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingException( SerializationInfo info, StreamingContext context) - : base(info, context) - { - _message = info.GetString("ParameterBindingException_Message"); - _parameterName = info.GetString("ParameterName"); - _line = info.GetInt64("Line"); - _offset = info.GetInt64("Offset"); - } - - /// - /// Serializes 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ParameterBindingException_Message", this.Message); - info.AddValue("ParameterName", _parameterName); - info.AddValue("Line", _line); - info.AddValue("Offset", _offset); + throw new NotSupportedException(); } #endregion serialization @@ -369,37 +311,31 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// /// Constructs a ParameterBindingException. /// - /// /// /// DO NOT USE!!! /// - public ParameterBindingException() : base() {; } + public ParameterBindingException() : base() { } /// - /// Constructors a ParameterBindingException + /// Constructors a ParameterBindingException. /// - /// /// /// Message to be included in exception. /// - /// /// /// DO NOT USE!!! /// public ParameterBindingException(string message) : base(message) { _message = message; } /// - /// Constructs a ParameterBindingException + /// Constructs a ParameterBindingException. /// - /// /// /// Message to be included in the exception. /// - /// /// /// exception that led to this exception /// - /// /// /// DO NOT USE!!! /// @@ -414,11 +350,11 @@ public ParameterBindingException( #region Properties /// - /// Gets the message for the exception + /// Gets the message for the exception. /// public override string Message { - get { return _message ?? (_message = BuildMessage()); } + get { return _message ??= BuildMessage(); } } private string _message; @@ -434,7 +370,8 @@ public string ParameterName return _parameterName; } } - private string _parameterName = String.Empty; + + private readonly string _parameterName = string.Empty; /// /// Gets the type the parameter is expecting. @@ -446,10 +383,11 @@ public Type ParameterType return _parameterType; } } - private Type _parameterType; + + private readonly Type _parameterType; /// - /// Gets the Type that was specified as the parameter value + /// Gets the Type that was specified as the parameter value. /// public Type TypeSpecified { @@ -458,10 +396,11 @@ public Type TypeSpecified return _typeSpecified; } } - private Type _typeSpecified; + + private readonly Type _typeSpecified; /// - /// Gets the errorId of this ParameterBindingException + /// Gets the errorId of this ParameterBindingException. /// public string ErrorId { @@ -470,7 +409,8 @@ public string ErrorId return _errorId; } } - private string _errorId; + + private readonly string _errorId; /// /// Gets the line in the script at which the error occurred. @@ -482,7 +422,8 @@ public Int64 Line return _line; } } - private Int64 _line = Int64.MinValue; + + private readonly Int64 _line = Int64.MinValue; /// /// Gets the offset on the line in the script at which the error occurred. @@ -494,7 +435,8 @@ public Int64 Offset return _offset; } } - private Int64 _offset = Int64.MinValue; + + private readonly Int64 _offset = Int64.MinValue; /// /// Gets the invocation information about the command. @@ -506,18 +448,19 @@ public InvocationInfo CommandInvocation return _invocationInfo; } } - private InvocationInfo _invocationInfo; + + private readonly InvocationInfo _invocationInfo; #endregion Properties #region private - private string _resourceString; - private object[] _args = new object[0]; - private string _commandName; + private readonly string _resourceString; + private readonly object[] _args = Array.Empty(); + private readonly string _commandName; private string BuildMessage() { - object[] messageArgs = new object[0]; + object[] messageArgs = Array.Empty(); if (_args != null) { @@ -531,81 +474,70 @@ private string BuildMessage() _args.CopyTo(messageArgs, 6); } - string result = String.Empty; + string result = string.Empty; - if (!String.IsNullOrEmpty(_resourceString)) + if (!string.IsNullOrEmpty(_resourceString)) { result = StringUtil.Format(_resourceString, messageArgs); } + return result; } #endregion Private } - [Serializable] internal class ParameterBindingValidationException : ParameterBindingException { #region Preferred constructors /// - /// Constructs a ParameterBindingValidationException + /// Constructs a ParameterBindingValidationException. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingValidationException( ErrorCategory errorCategory, InvocationInfo invocationInfo, @@ -630,71 +562,58 @@ internal ParameterBindingValidationException( } /// - /// Constructs a ParameterBindingValidationException + /// Constructs a ParameterBindingValidationException. /// - /// /// /// The inner exception. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If is null. /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingValidationException( Exception innerException, ErrorCategory errorCategory, @@ -718,8 +637,7 @@ internal ParameterBindingValidationException( errorId, args) { - ValidationMetadataException validationException = innerException as ValidationMetadataException; - if (validationException != null && validationException.SwallowException) + if (innerException is ValidationMetadataException validationException && validationException.SwallowException) { _swallowException = true; } @@ -728,21 +646,20 @@ internal ParameterBindingValidationException( #region serialization /// - /// Constructs a ParameterBindingValidationException from serialized data + /// Constructs a ParameterBindingValidationException from serialized data. /// - /// /// /// serialization information /// - /// /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingValidationException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion serialization @@ -752,7 +669,6 @@ protected ParameterBindingValidationException( /// /// Make the positional binding ignore this validation exception when it's set to true. /// - /// /// /// This property is only used internally in the positional binding phase /// @@ -760,74 +676,63 @@ internal bool SwallowException { get { return _swallowException; } } + private readonly bool _swallowException = false; #endregion Property } - [Serializable] internal class ParameterBindingArgumentTransformationException : ParameterBindingException { #region Preferred constructors /// - /// Constructs a ParameterBindingArgumentTransformationException + /// Constructs a ParameterBindingArgumentTransformationException. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingArgumentTransformationException( ErrorCategory errorCategory, InvocationInfo invocationInfo, @@ -852,71 +757,58 @@ internal ParameterBindingArgumentTransformationException( } /// - /// Constructs a ParameterBindingArgumentTransformationException + /// Constructs a ParameterBindingArgumentTransformationException. /// - /// /// /// The inner exception. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If is null. /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingArgumentTransformationException( Exception innerException, ErrorCategory errorCategory, @@ -944,89 +836,76 @@ internal ParameterBindingArgumentTransformationException( #endregion Preferred constructors #region serialization /// - /// Constructs a ParameterBindingArgumentTransformationException using serialized data + /// Constructs a ParameterBindingArgumentTransformationException using serialized data. /// - /// /// /// serialization information /// - /// /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingArgumentTransformationException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion serialization } - [Serializable] internal class ParameterBindingParameterDefaultValueException : ParameterBindingException { #region Preferred constructors /// - /// Constructs a ParameterBindingParameterDefaultValueException + /// Constructs a ParameterBindingParameterDefaultValueException. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingParameterDefaultValueException( ErrorCategory errorCategory, InvocationInfo invocationInfo, @@ -1051,71 +930,58 @@ internal ParameterBindingParameterDefaultValueException( } /// - /// Constructs a ParameterBindingParameterDefaultValueException + /// Constructs a ParameterBindingParameterDefaultValueException. /// - /// /// /// The inner exception. /// - /// /// /// The category for the error. /// - /// /// /// The information about the command that encountered the error. /// /// InvocationInfo.MyCommand.Name == {0} /// - /// /// /// The position for the command or parameter that caused the error. /// /// token.LineNumber == {4} /// token.OffsetInLine == {5} /// - /// /// /// The parameter on which binding caused the error. /// /// parameterName == {1} /// - /// /// /// The Type the parameter was expecting. /// /// parameterType == {2} /// - /// /// /// The Type that was attempted to be bound to the parameter. /// /// typeSpecified == {3} /// - /// /// /// The format string for the exception message. /// - /// /// /// The error ID. /// - /// /// /// Additional arguments to pass to the format string. /// /// starts at {6} /// - /// /// /// If is null. /// - /// /// /// If or /// is null or empty. /// - /// internal ParameterBindingParameterDefaultValueException( Exception innerException, ErrorCategory errorCategory, @@ -1144,24 +1010,22 @@ internal ParameterBindingParameterDefaultValueException( #region serialization /// - /// Constructs a ParameterBindingParameterDefaultValueException using serialized data + /// Constructs a ParameterBindingParameterDefaultValueException using serialized data. /// - /// /// /// serialization information /// - /// /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingParameterDefaultValueException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion serialization } -} // namespace System.Management.Automation - +} diff --git a/src/System.Management.Automation/utils/ParserException.cs b/src/System.Management.Automation/utils/ParserException.cs index 52a208466fd..aadf52490b4 100644 --- a/src/System.Management.Automation/utils/ParserException.cs +++ b/src/System.Management.Automation/utils/ParserException.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -10,13 +9,13 @@ namespace System.Management.Automation { /// - /// Defines the exception thrown when a syntax error occurs while parsing msh script text. + /// Defines the exception thrown when a syntax error occurs while parsing PowerShell script text. /// - [Serializable] public class ParseException : RuntimeException { private const string errorIdString = "Parse"; - private ParseError[] _errors; + + private readonly ParseError[] _errors; /// /// The list of parser errors. @@ -31,29 +30,14 @@ public ParseError[] Errors /// Initializes a new instance of the ParseException class and defines the serialization information, /// and streaming context. /// - /// The serialization information to use when initializing this object - /// The streaming context to use when initializing this object - /// constructed object + /// 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 ParseException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - _errors = (ParseError[])info.GetValue("Errors", typeof(ParseError[])); - } - - - /// - /// Add private data for serialization. - /// - public override void GetObjectData(SerializationInfo info, StreamingContext context) { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - base.GetObjectData(info, context); - info.AddValue("Errors", _errors); + throw new NotSupportedException(); } #endregion Serialization @@ -61,9 +45,9 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont #region ctor /// - /// Initializes a new instance of the class ParseException + /// Initializes a new instance of the class ParseException. /// - /// constructed object + /// Constructed object. public ParseException() : base() { base.SetErrorId(errorIdString); @@ -73,8 +57,8 @@ public ParseException() : base() /// /// Initializes a new instance of the ParseException class and defines the error message. /// - /// The error message to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// Constructed object. public ParseException(string message) : base(message) { base.SetErrorId(errorIdString); @@ -85,9 +69,9 @@ public ParseException(string message) : base(message) /// Initializes a new instance of the ParseException class and defines the error message and /// errorID. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// Constructed object. internal ParseException(string message, string errorId) : base(message) { base.SetErrorId(errorId); @@ -98,10 +82,10 @@ internal ParseException(string message, string errorId) : base(message) /// Initializes a new instance of the ParseException class and defines the error message, /// error ID and inner exception. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. internal ParseException(string message, string errorId, Exception innerException) : base(message, innerException) { @@ -113,9 +97,9 @@ internal ParseException(string message, string errorId, Exception innerException /// Initializes a new instance of the ParseException class and defines the error message and /// inner exception. /// - /// The error message to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. public ParseException(string message, Exception innerException) : base(message, innerException) @@ -128,13 +112,11 @@ public ParseException(string message, /// Initializes a new instance of the ParseException class with a collection of error messages. /// /// The collection of error messages. - [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", - Justification = "ErrorRecord is not overridden in classes deriving from ParseException")] - public ParseException(Language.ParseError[] errors) + public ParseException(ParseError[] errors) { - if ((errors == null) || (errors.Length == 0)) + if (errors is null || errors.Length == 0) { - throw new ArgumentNullException("errors"); + throw new ArgumentNullException(nameof(errors)); } _errors = errors; @@ -162,24 +144,22 @@ public override string Message // Report at most the first 10 errors var errorsToReport = (_errors.Length > 10) - ? _errors.Take(10).Select(e => e.ToString()).Append(ParserStrings.TooManyErrors) - : _errors.Select(e => e.ToString()); + ? _errors.Take(10).Select(static e => e.ToString()).Append(ParserStrings.TooManyErrors) + : _errors.Select(static e => e.ToString()); return string.Join(Environment.NewLine + Environment.NewLine, errorsToReport); } } - } // ParseException - + } /// - /// Defines the exception thrown when a incomplete parse error occurs while parsing msh script text. + /// Defines the exception thrown when a incomplete parse error occurs while parsing PowerShell script text. /// /// /// This is a variation on a parsing error that indicates that the parse was incomplete /// rather than irrecoverably wrong. A host can catch this exception and then prompt for additional /// input to complete the parse. /// - [Serializable] public class IncompleteParseException : ParseException { @@ -194,20 +174,21 @@ public class IncompleteParseException /// Initializes a new instance of the IncompleteParseException class and defines the serialization information, /// and streaming context. /// - /// The serialization information to use when initializing this object - /// The streaming context to use when initializing this object - /// constructed object + /// 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 IncompleteParseException(SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } #endregion Serialization /// - /// Initializes a new instance of the class IncompleteParseException + /// Initializes a new instance of the class IncompleteParseException. /// - /// constructed object + /// Constructed object. public IncompleteParseException() : base() { // Error category is set in base constructor @@ -217,8 +198,8 @@ public IncompleteParseException() : base() /// /// Initializes a new instance of the IncompleteParseException class and defines the error message. /// - /// The error message to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// Constructed object. public IncompleteParseException(string message) : base(message) { // Error category is set in base constructor @@ -229,9 +210,9 @@ public IncompleteParseException(string message) : base(message) /// Initializes a new instance of the IncompleteParseException class and defines the error message and /// errorID. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// Constructed object. internal IncompleteParseException(string message, string errorId) : base(message, errorId) { // Error category is set in base constructor @@ -241,10 +222,10 @@ internal IncompleteParseException(string message, string errorId) : base(message /// Initializes a new instance of the IncompleteParseException class and defines the error message, /// error ID and inner exception. /// - /// The error message to use when initializing this object - /// The errorId to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The errorId to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. internal IncompleteParseException(string message, string errorId, Exception innerException) : base(message, errorId, innerException) { @@ -255,9 +236,9 @@ internal IncompleteParseException(string message, string errorId, Exception inne /// Initializes a new instance of the IncompleteParseException class and defines the error message and /// inner exception. /// - /// The error message to use when initializing this object - /// The inner exception to use when initializing this object - /// constructed object + /// The error message to use when initializing this object. + /// The inner exception to use when initializing this object. + /// Constructed object. public IncompleteParseException(string message, Exception innerException) : base(message, innerException) @@ -267,4 +248,4 @@ public IncompleteParseException(string message, } #endregion ctor } -} // System.Management.Automation +} diff --git a/src/System.Management.Automation/utils/PathUtils.cs b/src/System.Management.Automation/utils/PathUtils.cs index 6b8b0de42da..3afea30a962 100644 --- a/src/System.Management.Automation/utils/PathUtils.cs +++ b/src/System.Management.Automation/utils/PathUtils.cs @@ -1,19 +1,22 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Text; - using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.PowerShell.Commands; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// - /// Defines generic utilities and helper methods for PowerShell + /// Defines generic utilities and helper methods for PowerShell. /// internal static class PathUtils { @@ -21,16 +24,16 @@ internal static class PathUtils /// THE method for opening a file for writing. /// Should be used by all cmdlets that write to a file. /// - /// cmdlet that is opening the file (used mainly for error reporting) - /// path to the file (as specified on the command line - this method will resolve the path) - /// encoding (this method will convert the command line string to an Encoding instance) - /// if true, then we will use default .NET encoding instead of the encoding specified in parameter + /// Cmdlet that is opening the file (used mainly for error reporting). + /// Path to the file (as specified on the command line - this method will resolve the path). + /// Encoding (this method will convert the command line string to an Encoding instance). + /// If , then we will use default .NET encoding instead of the encoding specified in parameter. /// /// /// - /// Result1: opened for writing - /// Result2: (inherits from ) opened for writing - /// Result3: file info that should be used to restore file attributes after done with the file (null is this is not needed) + /// Result1: opened for writing. + /// Result2: (inherits from ) opened for writing. + /// Result3: file info that should be used to restore file attributes after done with the file ( is this is not needed). /// True if wildcard expansion should be bypassed. internal static void MasterStreamOpen( PSCmdlet cmdlet, @@ -55,16 +58,16 @@ bool isLiteralPath /// THE method for opening a file for writing. /// Should be used by all cmdlets that write to a file. /// - /// cmdlet that is opening the file (used mainly for error reporting) - /// path to the file (as specified on the command line - this method will resolve the path) - /// encoding (this method will convert the command line string to an Encoding instance) - /// if true, then we will use default .NET encoding instead of the encoding specified in parameter + /// Cmdlet that is opening the file (used mainly for error reporting). + /// Path to the file (as specified on the command line - this method will resolve the path). + /// Encoding (this method will convert the command line string to an Encoding instance). + /// If , then we will use default .NET encoding instead of the encoding specified in parameter. /// /// /// - /// Result1: opened for writing - /// Result2: (inherits from ) opened for writing - /// Result3: file info that should be used to restore file attributes after done with the file (null is this is not needed) + /// Result1: opened for writing. + /// Result2: (inherits from ) opened for writing. + /// Result3: file info that should be used to restore file attributes after done with the file ( is this is not needed). /// True if wildcard expansion should be bypassed. internal static void MasterStreamOpen( PSCmdlet cmdlet, @@ -82,55 +85,19 @@ internal static void MasterStreamOpen( fileStream = null; streamWriter = null; readOnlyFileInfo = null; - - // resolve the path and the encoding string resolvedPath = ResolveFilePath(filePath, cmdlet, isLiteralPath); - try { - // variable to track file open mode - // this is controlled by append/force parameters - FileMode mode = FileMode.Create; - if (Append) - { - mode = FileMode.Append; - } - else if (NoClobber) - { - // throw IOException if file exists - mode = FileMode.CreateNew; - } - - if (Force && (Append || !NoClobber)) - { - if (File.Exists(resolvedPath)) - { - FileInfo fInfo = new FileInfo(resolvedPath); - if ((fInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - { - // remember to reset the read-only attribute later - readOnlyFileInfo = fInfo; - // Clear the read-only attribute - fInfo.Attributes &= ~(FileAttributes.ReadOnly); - } - } - } - - // if the user knows what he/she is doing and uses "-Force" switch, - // then we let more than 1 process write to the same file at the same time - FileShare fileShare = Force ? FileShare.ReadWrite : FileShare.Read; - - // mode is controlled by force and ShouldContinue() - fileStream = new FileStream(resolvedPath, mode, FileAccess.Write, fileShare); - - // create stream writer - // NTRAID#Windows Out Of Band Releases-931008-2006/03/27 - // For some reason, calling this without specifying - // the encoding is different from passing Encoding.Default. - if (defaultEncoding) - streamWriter = new StreamWriter(fileStream); - else - streamWriter = new StreamWriter(fileStream, resolvedEncoding); + MasterStreamOpenImpl( + resolvedPath, + resolvedEncoding, + defaultEncoding, + Append, + Force, + NoClobber, + out fileStream, + out streamWriter, + out readOnlyFileInfo); } // These are the known exceptions for File.Load and StreamWriter.ctor catch (ArgumentException e) @@ -143,7 +110,7 @@ internal static void MasterStreamOpen( if (NoClobber && File.Exists(resolvedPath)) { // This probably happened because the file already exists - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( e, "NoClobber", ErrorCategory.ResourceExists, resolvedPath); errorRecord.ErrorDetails = new ErrorDetails( cmdlet, @@ -173,9 +140,178 @@ internal static void MasterStreamOpen( // NOTE: this call will throw ReportFileOpenFailure(cmdlet, resolvedPath, e); } - } // void MasterStreamOpen + } + + /// + /// THE method for opening a file for writing. + /// Should be used by all cmdlets that write to a file. + /// + /// Path to the file (as specified on the command line - this method will resolve the path). + /// Encoding (this method will convert the command line string to an Encoding instance). + /// If , then we will use default .NET encoding instead of the encoding specified in parameter. + /// + /// + /// + /// Result1: opened for writing. + /// Result2: (inherits from ) opened for writing. + /// Result3: file info that should be used to restore file attributes after done with the file ( is this is not needed). + /// True if wildcard expansion should be bypassed. + internal static void MasterStreamOpen( + string filePath, + Encoding resolvedEncoding, + bool defaultEncoding, + bool Append, + bool Force, + bool NoClobber, + out FileStream fileStream, + out StreamWriter streamWriter, + out FileInfo readOnlyFileInfo, + bool isLiteralPath) + { + fileStream = null; + streamWriter = null; + readOnlyFileInfo = null; + string resolvedPath = ResolveFilePath(filePath, isLiteralPath); + try + { + MasterStreamOpenImpl( + resolvedPath, + resolvedEncoding, + defaultEncoding, + Append, + Force, + NoClobber, + out fileStream, + out streamWriter, + out readOnlyFileInfo); + } + // These are the known exceptions for File.Load and StreamWriter.ctor + catch (ArgumentException e) + { + AddFileOpenErrorRecord(e); + throw; + } + catch (IOException e) + { + if (NoClobber && File.Exists(resolvedPath)) + { + string msg = StringUtil.Format( + PathUtilsStrings.UtilityFileExistsNoClobber, + filePath, + "NoClobber"); + + // This probably happened because the file already exists + ErrorRecord errorRecord = new ErrorRecord( + e, "NoClobber", ErrorCategory.ResourceExists, resolvedPath); + errorRecord.ErrorDetails = new ErrorDetails(msg); + + e.Data[typeof(ErrorRecord)] = errorRecord; + throw; + } + + AddFileOpenErrorRecord(e); + throw; + } + catch (UnauthorizedAccessException e) + { + AddFileOpenErrorRecord(e); + throw; + } + catch (NotSupportedException e) + { + AddFileOpenErrorRecord(e); + throw; + } + catch (System.Security.SecurityException e) + { + AddFileOpenErrorRecord(e); + throw; + } + + static void AddFileOpenErrorRecord(Exception e) + { + ErrorRecord errorRecord = new ErrorRecord( + e, + "FileOpenFailure", + ErrorCategory.OpenError, + null); + + e.Data[typeof(ErrorRecord)] = errorRecord; + } + } + + /// + /// THE method for opening a file for writing. + /// Should be used by all cmdlets that write to a file. + /// + /// Path to the file (as specified on the command line - this method will resolve the path). + /// Encoding (this method will convert the command line string to an Encoding instance). + /// If , then we will use default .NET encoding instead of the encoding specified in parameter. + /// + /// + /// + /// Result1: opened for writing. + /// Result2: (inherits from ) opened for writing. + /// Result3: file info that should be used to restore file attributes after done with the file ( is this is not needed). + internal static void MasterStreamOpenImpl( + string resolvedPath, + Encoding resolvedEncoding, + bool defaultEncoding, + bool Append, + bool Force, + bool NoClobber, + out FileStream fileStream, + out StreamWriter streamWriter, + out FileInfo readOnlyFileInfo) + { + fileStream = null; + streamWriter = null; + readOnlyFileInfo = null; + // variable to track file open mode + // this is controlled by append/force parameters + FileMode mode = FileMode.Create; + if (Append) + { + mode = FileMode.Append; + } + else if (NoClobber) + { + // throw IOException if file exists + mode = FileMode.CreateNew; + } + + if (Force && (Append || !NoClobber)) + { + if (File.Exists(resolvedPath)) + { + FileInfo fInfo = new FileInfo(resolvedPath); + if ((fInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + { + // remember to reset the read-only attribute later + readOnlyFileInfo = fInfo; + // Clear the read-only attribute + fInfo.Attributes &= ~(FileAttributes.ReadOnly); + } + } + } + // if the user knows what he/she is doing and uses "-Force" switch, + // then we let more than 1 process write to the same file at the same time + FileShare fileShare = Force ? FileShare.ReadWrite : FileShare.Read; + + // mode is controlled by force and ShouldContinue() + fileStream = new FileStream(resolvedPath, mode, FileAccess.Write, fileShare); + + // create stream writer + // NTRAID#Windows Out Of Band Releases-931008-2006/03/27 + // For some reason, calling this without specifying + // the encoding is different from passing Encoding.Default. + if (defaultEncoding) + streamWriter = new StreamWriter(fileStream); + else + streamWriter = new StreamWriter(fileStream, resolvedEncoding); + } internal static void ReportFileOpenFailure(Cmdlet cmdlet, string filePath, Exception e) { @@ -188,6 +324,20 @@ internal static void ReportFileOpenFailure(Cmdlet cmdlet, string filePath, Excep cmdlet.ThrowTerminatingError(errorRecord); } + internal static void ReportFileOpenFailure(string filePath, Exception e) + { + ErrorRecord errorRecord = new ErrorRecord( + e, + "FileOpenFailure", + ErrorCategory.OpenError, + null); + + throw new RuntimeException( + e.Message, + errorRecord.Exception, + errorRecord); + } + internal static StreamReader OpenStreamReader(PSCmdlet command, string filePath, Encoding encoding, bool isLiteralPath) { FileStream fileStream = OpenFileStream(filePath, command, isLiteralPath); @@ -231,8 +381,8 @@ internal static FileStream OpenFileStream(string filePath, PSCmdlet command, boo } /// - /// resolve a user provided file name or path (including globbing characters) - /// to a fully qualified file path, using the file system provider + /// Resolve a user provided file name or path (including globbing characters) + /// to a fully qualified file path, using the file system provider. /// /// /// @@ -243,8 +393,8 @@ internal static string ResolveFilePath(string filePath, PSCmdlet command) } /// - /// resolve a user provided file name or path (including globbing characters) - /// to a fully qualified file path, using the file system provider + /// Resolve a user provided file name or path (including globbing characters) + /// to a fully qualified file path, using the file system provider. /// /// /// @@ -278,6 +428,7 @@ internal static string ResolveFilePath(string filePath, PSCmdlet command, bool i { ReportMultipleFilesNotSupported(command); } + if (filePaths.Count == 0) { ReportWildcardingFailure(command, filePath); @@ -289,6 +440,7 @@ internal static string ResolveFilePath(string filePath, PSCmdlet command, bool i { path = null; } + if (string.IsNullOrEmpty(path)) { CmdletProviderContext cmdletProviderContext = new CmdletProviderContext(command); @@ -303,6 +455,64 @@ internal static string ResolveFilePath(string filePath, PSCmdlet command, bool i ReportWrongProviderType(command, provider.FullName); } } + + return path; + } + + /// + /// Resolve a user provided file name or path (including globbing characters) + /// to a fully qualified file path, using the file system provider. + /// + /// + /// + /// + internal static string ResolveFilePath(string filePath, bool isLiteralPath) + { + string path = null; + + SessionState sessionState = LocalPipeline.GetExecutionContextFromTLS()?.EngineSessionState?.PublicSessionState; + if (sessionState is null) + { + return null; + } + + try + { + ProviderInfo provider = null; + PSDriveInfo drive = null; + List filePaths = new(); + + if (isLiteralPath) + { + filePaths.Add(sessionState.Path.GetUnresolvedProviderPathFromPSPath(filePath, out provider, out drive)); + } + else + { + filePaths.AddRange(sessionState.Path.GetResolvedProviderPathFromPSPath(filePath, out provider)); + } + + if (!provider.NameEquals(FileSystemProvider.ProviderName)) + { + ReportWrongProviderType(provider.FullName); + } + + if (filePaths.Count > 1) + { + ReportMultipleFilesNotSupported(); + } + + if (filePaths.Count == 0) + { + ReportWildcardingFailure(filePath); + } + + path = filePaths[0]; + } + catch (ItemNotFoundException) + { + path = null; + } + return path; } @@ -320,6 +530,23 @@ internal static void ReportWrongProviderType(Cmdlet cmdlet, string providerId) cmdlet.ThrowTerminatingError(errorRecord); } + internal static void ReportWrongProviderType(string providerId) + { + string msg = StringUtil.Format(PathUtilsStrings.OutFile_ReadWriteFileNotFileSystemProvider, providerId); + + PSInvalidOperationException exception = PSTraceSource.NewInvalidOperationException(); + + ErrorRecord errorRecord = new( + exception, + "ReadWriteFileNotFileSystemProvider", + ErrorCategory.InvalidArgument, + null); + + errorRecord.ErrorDetails = new ErrorDetails(msg); + exception.Data[typeof(ErrorRecord)] = errorRecord; + throw exception; + } + internal static void ReportMultipleFilesNotSupported(Cmdlet cmdlet) { string msg = StringUtil.Format(PathUtilsStrings.OutFile_MultipleFilesNotSupported); @@ -334,6 +561,23 @@ internal static void ReportMultipleFilesNotSupported(Cmdlet cmdlet) cmdlet.ThrowTerminatingError(errorRecord); } + internal static void ReportMultipleFilesNotSupported() + { + string msg = StringUtil.Format(PathUtilsStrings.OutFile_MultipleFilesNotSupported); + + PSInvalidOperationException exception = PSTraceSource.NewInvalidOperationException(); + + ErrorRecord errorRecord = new( + exception, + "ReadWriteMultipleFilesNotSupported", + ErrorCategory.InvalidArgument, + null); + + errorRecord.ErrorDetails = new ErrorDetails(msg); + exception.Data[typeof(ErrorRecord)] = errorRecord; + throw exception; + } + internal static void ReportWildcardingFailure(Cmdlet cmdlet, string filePath) { string msg = StringUtil.Format(PathUtilsStrings.OutFile_DidNotResolveFile, filePath); @@ -348,6 +592,22 @@ internal static void ReportWildcardingFailure(Cmdlet cmdlet, string filePath) cmdlet.ThrowTerminatingError(errorRecord); } + internal static void ReportWildcardingFailure(string filePath) + { + string msg = StringUtil.Format(PathUtilsStrings.OutFile_DidNotResolveFile, filePath); + + FileNotFoundException exception = new(); + ErrorRecord errorRecord = new( + exception, + "FileOpenFailure", + ErrorCategory.OpenError, + filePath); + + errorRecord.ErrorDetails = new ErrorDetails(msg); + exception.Data[typeof(ErrorRecord)] = errorRecord; + throw exception; + } + internal static DirectoryInfo CreateModuleDirectory(PSCmdlet cmdlet, string moduleNameOrPath, bool force) { Dbg.Assert(cmdlet != null, "Caller should verify cmdlet != null"); @@ -356,16 +616,36 @@ internal static DirectoryInfo CreateModuleDirectory(PSCmdlet cmdlet, string modu DirectoryInfo directoryInfo = null; try { - string rootedPath = Microsoft.PowerShell.Commands.ModuleCmdletBase.ResolveRootedFilePath(moduleNameOrPath, cmdlet.Context); - if (string.IsNullOrEmpty(rootedPath) && moduleNameOrPath.StartsWith(".", StringComparison.OrdinalIgnoreCase)) + // Even if 'moduleNameOrPath' is a rooted path, 'ResolveRootedFilePath' may return null when the path doesn't exist yet, + // or when it contains wildcards but cannot be resolved to a single path. + string rootedPath = ModuleCmdletBase.ResolveRootedFilePath(moduleNameOrPath, cmdlet.Context); + if (string.IsNullOrEmpty(rootedPath) && moduleNameOrPath.StartsWith('.')) { PathInfo currentPath = cmdlet.CurrentProviderLocation(cmdlet.Context.ProviderNames.FileSystem); rootedPath = Path.Combine(currentPath.ProviderPath, moduleNameOrPath); } + if (string.IsNullOrEmpty(rootedPath)) { - string personalModuleRoot = ModuleIntrinsics.GetPersonalModulePath(); - rootedPath = Path.Combine(personalModuleRoot, moduleNameOrPath); + if (Path.IsPathRooted(moduleNameOrPath)) + { + rootedPath = moduleNameOrPath; + } + else + { + string personalModuleRoot = ModuleIntrinsics.GetPersonalModulePath(); + if (string.IsNullOrEmpty(personalModuleRoot)) + { + cmdlet.ThrowTerminatingError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(PathUtilsStrings.ExportPSSession_ErrorModuleNameOrPath, moduleNameOrPath)), + "ExportPSSession_ErrorModuleNameOrPath", + ErrorCategory.InvalidArgument, + cmdlet)); + } + + rootedPath = Path.Combine(personalModuleRoot, moduleNameOrPath); + } } directoryInfo = new DirectoryInfo(rootedPath); @@ -428,5 +708,200 @@ internal static DirectoryInfo CreateTemporaryDirectory() Directory.CreateDirectory(moduleDirectory.FullName); return new DirectoryInfo(moduleDirectory.FullName); } + + internal static bool TryDeleteFile(string filepath) + { + if (IO.File.Exists(filepath)) + { + try + { + IO.File.Delete(filepath); + return true; + } + catch (IOException) + { + // file is in use on Windows + } + catch (UnauthorizedAccessException) + { + // user does not have permissions + } + } + + return false; + } + + #region Helpers for long paths from .Net Runtime + + // Code here is copied from .NET's internal path helper implementation: + // https://github.com/dotnet/runtime/blob/dcce0f56e10f5ac9539354b049341a2d7c0cdebf/src/libraries/System.Private.CoreLib/src/System/IO/PathInternal.Windows.cs + // It has been left as a verbatim copy. + +#nullable enable + + /// + /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, + /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended + /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten + /// away from paths during normalization, but if we see such a path at this point it should be + /// normalized and has retained the final characters. (Typically from one of the *Info classes). + /// + /// File path. + /// File path (with extended prefix if the path is long path). + [return: NotNullIfNotNull(nameof(path))] + internal static string? EnsureExtendedPrefixIfNeeded(string? path) + { + if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path))) + { + return EnsureExtendedPrefix(path); + } + else + { + return path; + } + } + + internal static string EnsureExtendedPrefix(string path) + { + if (IsPartiallyQualified(path) || IsDevice(path)) + return path; + + // Given \\server\share in longpath becomes \\?\UNC\server\share + if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase)) + return path.Insert(2, UncDevicePrefixToInsert); + + return ExtendedDevicePathPrefix + path; + } + + private const string ExtendedDevicePathPrefix = @"\\?\"; + private const string UncPathPrefix = @"\\"; + private const string UncDevicePrefixToInsert = @"?\UNC\"; + private const string UncExtendedPathPrefix = @"\\?\UNC\"; + private const string DevicePathPrefix = @"\\.\"; + private const int MaxShortPath = 260; + + // \\?\, \\.\, \??\ + private const int DevicePrefixLength = 4; + + private static bool EndsWithPeriodOrSpace(string? path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + char c = path[path.Length - 1]; + return c == ' ' || c == '.'; + } + + /// + /// Returns true if the given character is a valid drive letter + /// + private static bool IsValidDriveChar(char value) + { + return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); + } + + private static bool IsDevice(string path) + { + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + private static bool IsExtended(string path) + { + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// + /// + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// + private static bool IsPartiallyQualified(string path) + { + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return !((path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0])); + } + /// + /// True if the given character is a directory separator. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDirectorySeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; + } + + #endregion + + #region Helpers for checking invalid paths using SearchValues + + /// + /// Contains characters that are invalid in file names. + /// + private static readonly SearchValues s_invalidFileNameChars + = SearchValues.Create(Path.GetInvalidFileNameChars()); + + /// + /// Contains characters that are invalid in path names. + /// + private static readonly SearchValues s_invalidPathChars + = SearchValues.Create(Path.GetInvalidPathChars()); + + /// + /// Checks if the specified filename contains any characters that are invalid in file names. + /// + /// The path to check. + /// True if the filename contains invalid file name characters, otherwise false. + internal static bool ContainsInvalidFileNameChars(ReadOnlySpan filename) + => filename.ContainsAny(s_invalidFileNameChars); + + /// + /// Checks if the specified path contains any characters that are invalid in path names. + /// + /// The path to check. + /// True if the path contains invalid path characters, otherwise false. + internal static bool ContainsInvalidPathChars(ReadOnlySpan path) + => path.ContainsAny(s_invalidPathChars); + + #endregion } } diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs index 028d768c0b6..f9e78f5d1d3 100644 --- a/src/System.Management.Automation/utils/PlatformInvokes.cs +++ b/src/System.Management.Automation/utils/PlatformInvokes.cs @@ -1,25 +1,17 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + using Microsoft.Win32.SafeHandles; -using System.Security.Permissions; -using System.Runtime.ConstrainedExecution; namespace System.Management.Automation { - internal class PlatformInvokes + internal static class PlatformInvokes { - [DllImport(PinvokeDllNames.GetFileAttributesDllName, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int GetFileAttributes(string lpFileName); - - internal const int MAX_PATH = 260; - internal const int MAX_ALTERNATE = 14; - [StructLayout(LayoutKind.Sequential)] - internal class FILETIME + internal sealed class FILETIME { internal uint dwLowDateTime; internal uint dwHighDateTime; @@ -40,36 +32,10 @@ public long ToTicks() { return ((long)dwHighDateTime << 32) + dwLowDateTime; } - }; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct WIN32_FIND_DATA - { - internal FileAttributes dwFileAttributes; - internal FILETIME ftCreationTime; - internal FILETIME ftLastAccessTime; - internal FILETIME ftLastWriteTime; - internal uint nFileSizeHigh; //changed all to uint, otherwise you run into unexpected overflow - internal uint nFileSizeLow; //| - internal uint dwReserved0; //| - internal uint dwReserved1; //v - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] - internal string cFileName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)] - internal string cAlternate; } - [DllImport(PinvokeDllNames.FindFirstFileDllName, CharSet = CharSet.Unicode)] - internal static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); - - [DllImport(PinvokeDllNames.FindNextFileDllName, CharSet = CharSet.Unicode)] - internal static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData); - - [DllImport(PinvokeDllNames.FindCloseDllName, CharSet = CharSet.Unicode)] - internal static extern bool FindClose(IntPtr hFindFile); - [Flags] - //dwDesiredAccess of CreateFile + // dwDesiredAccess of CreateFile internal enum FileDesiredAccess : uint { GenericRead = 0x80000000, @@ -79,7 +45,7 @@ internal enum FileDesiredAccess : uint } [Flags] - //dwShareMode of CreateFile + // dwShareMode of CreateFile internal enum FileShareMode : uint { None = 0x00000000, @@ -88,7 +54,7 @@ internal enum FileShareMode : uint Delete = 0x00000004, } - //dwCreationDisposition of CreateFile + // dwCreationDisposition of CreateFile internal enum FileCreationDisposition : uint { New = 1, @@ -99,7 +65,7 @@ internal enum FileCreationDisposition : uint } [Flags] - //dwFlagsAndAttributes + // dwFlagsAndAttributes internal enum FileAttributes : uint { ReadOnly = 0x00000001, @@ -126,11 +92,12 @@ internal enum FileAttributes : uint } [StructLayout(LayoutKind.Sequential)] - internal class SecurityAttributes + internal sealed class SecurityAttributes { internal int nLength; internal SafeLocalMemHandle lpSecurityDescriptor; internal bool bInheritHandle; + internal SecurityAttributes() { this.nLength = 12; @@ -147,15 +114,15 @@ internal SafeLocalMemHandle() { } - [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) { base.SetHandle(existingHandle); } - [DllImport(PinvokeDllNames.LocalFreeDllName), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport(PinvokeDllNames.LocalFreeDllName)] private static extern IntPtr LocalFree(IntPtr hMem); + protected override bool ReleaseHandle() { return (LocalFree(base.handle) == IntPtr.Zero); @@ -266,23 +233,25 @@ internal static extern IntPtr CreateFile( /// This can happen if you close a handle twice, or if you call CloseHandle on a handle /// returned by the FindFirstFile function. /// - [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true)]//, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - //[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] internal static extern bool CloseHandle(IntPtr handle); [DllImport(PinvokeDllNames.DosDateTimeToFileTimeDllName, SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DosDateTimeToFileTime( short wFatDate, // _In_ WORD short wFatTime, // _In_ WORD FILETIME lpFileTime); // _Out_ LPFILETIME [DllImport(PinvokeDllNames.LocalFileTimeToFileTimeDllName, SetLastError = false, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LocalFileTimeToFileTime( FILETIME lpLocalFileTime, // _In_ const FILETIME * FILETIME lpFileTime); // _Out_ LPFILETIME [DllImport(PinvokeDllNames.SetFileTimeDllName, SetLastError = false, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetFileTime( IntPtr hFile, // _In_ HANDLE FILETIME lpCreationTime, // _In_opt_ const FILETIME * @@ -290,6 +259,7 @@ internal static extern bool SetFileTime( FILETIME lpLastWriteTime); // _In_opt_ const FILETIME * [DllImport(PinvokeDllNames.SetFileAttributesWDllName, SetLastError = false, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetFileAttributesW( [MarshalAs(UnmanagedType.LPWStr)] string lpFileName, // _In_ LPCTSTR FileAttributes dwFileAttributes); // _In_ DWORD @@ -368,6 +338,7 @@ internal static bool EnableTokenPrivilege(string privilegeName, ref TOKEN_PRIVIL { CloseHandle(tokenHandler); } + CloseHandle(processHandler); } } @@ -376,7 +347,7 @@ internal static bool EnableTokenPrivilege(string privilegeName, ref TOKEN_PRIVIL } /// - /// Restore the previous privilege state + /// Restore the previous privilege state. /// /// /// @@ -423,6 +394,7 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI { CloseHandle(tokenHandler); } + CloseHandle(processHandler); } } @@ -439,7 +411,6 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// /// [DllImport(PinvokeDllNames.LookupPrivilegeValueDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid); @@ -451,7 +422,6 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// /// [DllImport(PinvokeDllNames.PrivilegeCheckDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PrivilegeCheck(IntPtr tokenHandler, ref PRIVILEGE_SET requiredPrivileges, out bool pfResult); @@ -468,35 +438,34 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// /// [DllImport(PinvokeDllNames.AdjustTokenPrivilegesDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool AdjustTokenPrivileges(IntPtr tokenHandler, bool disableAllPrivilege, ref TOKEN_PRIVILEGE newPrivilegeState, int bufferLength, out TOKEN_PRIVILEGE previousPrivilegeState, ref int returnLength); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct PRIVILEGE_SET { internal uint PrivilegeCount; @@ -505,23 +474,21 @@ internal struct PRIVILEGE_SET } /// - /// Get the pseudo handler of the current process + /// Get the pseudo handler of the current process. /// /// [DllImport(PinvokeDllNames.GetCurrentProcessDllName)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] internal static extern IntPtr GetCurrentProcess(); /// /// Retrieves the current process token. - /// This function exists just for backward compatibility. It is prefered to use the other override that takes 'SafeHandle' as parameter. + /// This function exists just for backward compatibility. It is preferred to use the other override that takes 'SafeHandle' as parameter. /// - /// process handle - /// token access - /// process token + /// Process handle. + /// Token access. + /// Process token. /// The current process token. [DllImport(PinvokeDllNames.OpenProcessTokenDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); @@ -553,17 +520,17 @@ internal struct PRIVILEGE_SET // Fields internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); - internal static UInt32 GENERIC_READ = 0x80000000; - internal static UInt32 GENERIC_WRITE = 0x40000000; - internal static UInt32 FILE_ATTRIBUTE_NORMAL = 0x80000000; - internal static UInt32 CREATE_ALWAYS = 2; - internal static UInt32 FILE_SHARE_WRITE = 0x00000002; - internal static UInt32 FILE_SHARE_READ = 0x00000001; - internal static UInt32 OF_READWRITE = 0x00000002; - internal static UInt32 OPEN_EXISTING = 3; + 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; [StructLayout(LayoutKind.Sequential)] - internal class PROCESS_INFORMATION + internal sealed class PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; @@ -577,7 +544,7 @@ public PROCESS_INFORMATION() } /// - /// Dispose + /// Dispose. /// public void Dispose() { @@ -585,7 +552,7 @@ public void Dispose() } /// - /// Dispose + /// Dispose. /// /// private void Dispose(bool disposing) @@ -608,7 +575,7 @@ private void Dispose(bool disposing) } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; @@ -628,6 +595,7 @@ internal class STARTUPINFO public SafeFileHandle hStdInput; public SafeFileHandle hStdOutput; public SafeFileHandle hStdError; + public STARTUPINFO() { this.lpReserved = IntPtr.Zero; @@ -638,7 +606,6 @@ public STARTUPINFO() this.hStdOutput = new SafeFileHandle(IntPtr.Zero, false); this.hStdError = new SafeFileHandle(IntPtr.Zero, false); this.cb = Marshal.SizeOf(this); - } public void Dispose(bool disposing) @@ -650,11 +617,13 @@ public void Dispose(bool disposing) this.hStdInput.Dispose(); this.hStdInput = null; } + if ((this.hStdOutput != null) && !this.hStdOutput.IsInvalid) { this.hStdOutput.Dispose(); this.hStdOutput = null; } + if ((this.hStdError != null) && !this.hStdError.IsInvalid) { this.hStdError.Dispose(); @@ -670,11 +639,12 @@ public void Dispose() } [StructLayout(LayoutKind.Sequential)] - internal class SECURITY_ATTRIBUTES + internal sealed class SECURITY_ATTRIBUTES { public int nLength; public SafeLocalMemHandle lpSecurityDescriptor; public bool bInheritHandle; + public SECURITY_ATTRIBUTES() { this.nLength = 12; @@ -683,10 +653,8 @@ public SECURITY_ATTRIBUTES() } } - // Methods - // - [DllImport(PinvokeDllNames.CreateProcessDllName, CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CreateProcess( [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine, @@ -701,100 +669,11 @@ internal static extern bool CreateProcess( [DllImport(PinvokeDllNames.ResumeThreadDllName, CharSet = CharSet.Unicode, SetLastError = true)] public static extern uint ResumeThread(IntPtr threadHandle); - internal static uint RESUME_THREAD_FAILED = System.UInt32.MaxValue; // (DWORD)-1 - - [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern System.IntPtr CreateFileW( - [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, - UInt32 dwDesiredAccess, - UInt32 dwShareMode, - SECURITY_ATTRIBUTES lpSecurityAttributes, - UInt32 dwCreationDisposition, - UInt32 dwFlagsAndAttributes, - System.IntPtr hTemplateFile); - -#endif - - #endregion - #region GetStdHandle - -#if !UNIX - - internal enum StandardHandleId : uint - { - Error = unchecked((uint)-12), - Output = unchecked((uint)-11), - Input = unchecked((uint)-10), - } - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - public static extern IntPtr GetStdHandle(uint handleId); - -#endif - - #endregion - - #region CreateToolhelp32Snapshot - -#if !UNIX - - [DllImport(PinvokeDllNames.CreateToolhelp32SnapshotDllName, SetLastError = true)] - internal static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id); - [DllImport(PinvokeDllNames.Process32FirstDllName, SetLastError = true)] - internal static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); - [DllImport(PinvokeDllNames.Process32NextDllName, SetLastError = true)] - internal static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe); - - internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid - { - internal SafeSnapshotHandle() : base(true) - { - } - - [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] - internal SafeSnapshotHandle(IntPtr handle) : base(true) - { - base.SetHandle(handle); - } - - protected override bool ReleaseHandle() - { - return CloseHandle(base.handle); - } - } - - [Flags] - internal enum SnapshotFlags : uint - { - HeapList = 0x00000001, - Process = 0x00000002, - Thread = 0x00000004, - Module = 0x00000008, - Module32 = 0x00000010, - All = (HeapList | Process | Thread | Module), - Inherit = 0x80000000, - NoHeaps = 0x40000000 - } - [StructLayout(LayoutKind.Sequential)] - internal struct PROCESSENTRY32 - { - public uint dwSize; - public uint cntUsage; - public uint th32ProcessID; - public IntPtr th32DefaultHeapID; - public uint th32ModuleID; - public uint cntThreads; - public uint th32ParentProcessID; - public int pcPriClassBase; - public uint dwFlags; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile; - }; - - internal const int ERROR_NO_MORE_FILES = 0x12; + internal static readonly uint RESUME_THREAD_FAILED = System.UInt32.MaxValue; // (DWORD)-1 #endif #endregion } -} \ No newline at end of file +} diff --git a/src/System.Management.Automation/utils/PowerShellETWTracer.cs b/src/System.Management.Automation/utils/PowerShellETWTracer.cs index 39990d8a30d..c22e738a35f 100644 --- a/src/System.Management.Automation/utils/PowerShellETWTracer.cs +++ b/src/System.Management.Automation/utils/PowerShellETWTracer.cs @@ -1,7 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #if !UNIX -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// + using System.Globalization; using System.Management.Automation.Runspaces; using System.Text; @@ -10,11 +11,11 @@ namespace System.Management.Automation.Tracing { - //pragma warning disable 16001,16003 -#region Constants + // pragma warning disable 16001,16003 + #region Constants /// - /// Defines enumerations for event ids + /// Defines enumerations for event ids. /// /// add an entry for a new event that you /// add to the manifest. Set it to the same value @@ -27,244 +28,244 @@ public enum PowerShellTraceEvent : int None = 0, /// - /// HostNameResolve + /// HostNameResolve. /// HostNameResolve = 0x1001, /// - /// SchemeResolve + /// SchemeResolve. /// SchemeResolve = 0x1002, /// - /// ShellResolve + /// ShellResolve. /// ShellResolve = 0x1003, /// - /// RunspaceConstructor + /// RunspaceConstructor. /// RunspaceConstructor = 0x2001, /// - /// RunspacePoolConstructor + /// RunspacePoolConstructor. /// RunspacePoolConstructor = 0x2002, /// - /// RunspacePoolOpen + /// RunspacePoolOpen. /// RunspacePoolOpen = 0x2003, /// - /// OperationalTransferEventRunspacePool + /// OperationalTransferEventRunspacePool. /// OperationalTransferEventRunspacePool = 0x2004, /// - /// RunspacePort + /// RunspacePort. /// RunspacePort = 0x2F01, /// - /// AppName + /// AppName. /// AppName = 0x2F02, /// - /// ComputerName + /// ComputerName. /// ComputerName = 0x2F03, /// - /// Scheme + /// Scheme. /// Scheme = 0x2F04, /// - /// TestAnalytic + /// TestAnalytic. /// TestAnalytic = 0x2F05, /// - /// WSManConnectionInfoDump + /// WSManConnectionInfoDump. /// WSManConnectionInfoDump = 0x2F06, /// - /// AnalyticTransferEventRunspacePool + /// AnalyticTransferEventRunspacePool. /// AnalyticTransferEventRunspacePool = 0x2F07, // Start: Transport related events /// - /// TransportReceivedObject + /// TransportReceivedObject. /// TransportReceivedObject = 0x8001, /// - /// AppDomainUnhandledExceptionAnalytic + /// AppDomainUnhandledExceptionAnalytic. /// AppDomainUnhandledExceptionAnalytic = 0x8007, /// - /// TransportErrorAnalytic + /// TransportErrorAnalytic. /// TransportErrorAnalytic = 0x8008, /// - /// AppDomainUnhandledException + /// AppDomainUnhandledException. /// AppDomainUnhandledException = 0x8009, /// - /// TransportError + /// TransportError. /// TransportError = 0x8010, /// - /// WSManCreateShell + /// WSManCreateShell. /// WSManCreateShell = 0x8011, /// - /// WSManCreateShellCallbackReceived + /// WSManCreateShellCallbackReceived. /// WSManCreateShellCallbackReceived = 0x8012, /// - /// WSManCloseShell + /// WSManCloseShell. /// WSManCloseShell = 0x8013, /// - /// WSManCloseShellCallbackReceived + /// WSManCloseShellCallbackReceived. /// WSManCloseShellCallbackReceived = 0x8014, /// - /// WSManSendShellInputExtended + /// WSManSendShellInputExtended. /// WSManSendShellInputExtended = 0x8015, /// - /// WSManSendShellInputExCallbackReceived + /// WSManSendShellInputExCallbackReceived. /// WSManSendShellInputExtendedCallbackReceived = 0x8016, /// - /// WSManReceiveShellOutputExtended + /// WSManReceiveShellOutputExtended. /// WSManReceiveShellOutputExtended = 0x8017, /// - /// WSManReceiveShellOutputExCallbackReceived + /// WSManReceiveShellOutputExCallbackReceived. /// WSManReceiveShellOutputExtendedCallbackReceived = 0x8018, /// - /// WSManCreateCommand + /// WSManCreateCommand. /// WSManCreateCommand = 0x8019, /// - /// WSManCreateCommandCallbackReceived + /// WSManCreateCommandCallbackReceived. /// WSManCreateCommandCallbackReceived = 0x8020, /// - /// WSManCloseCommand + /// WSManCloseCommand. /// WSManCloseCommand = 0x8021, /// - /// WSManCloseCommandCallbackReceived + /// WSManCloseCommandCallbackReceived. /// WSManCloseCommandCallbackReceived = 0x8022, /// - /// WSManSignal + /// WSManSignal. /// WSManSignal = 0x8023, /// - /// WSManSignalCallbackReceived + /// WSManSignalCallbackReceived. /// WSManSignalCallbackReceived = 0x8024, /// - /// UriRedirection + /// UriRedirection. /// UriRedirection = 0x8025, /// - /// ServerSendData + /// ServerSendData. /// ServerSendData = 0x8051, /// - /// ServerCreateRemoteSession + /// ServerCreateRemoteSession. /// ServerCreateRemoteSession = 0x8052, /// - /// ReportContext + /// ReportContext. /// ReportContext = 0x8053, /// - /// ReportOperationComplete + /// ReportOperationComplete. /// ReportOperationComplete = 0x8054, /// - /// ServerCreateCommandSession + /// ServerCreateCommandSession. /// ServerCreateCommandSession = 0x8055, /// - /// ServerStopCommand + /// ServerStopCommand. /// ServerStopCommand = 0x8056, /// - /// ServerReceivedData + /// ServerReceivedData. /// ServerReceivedData = 0x8057, /// - /// ServerClientReceiveRequest + /// ServerClientReceiveRequest. /// ServerClientReceiveRequest = 0x8058, /// - /// ServerCloseOperation + /// ServerCloseOperation. /// ServerCloseOperation = 0x8059, /// - /// LoadingPSCustomShellAssembly + /// LoadingPSCustomShellAssembly. /// LoadingPSCustomShellAssembly = 0x8061, /// - /// LoadingPSCustomShellType + /// LoadingPSCustomShellType. /// LoadingPSCustomShellType = 0x8062, /// - /// ReceivedRemotingFragment + /// ReceivedRemotingFragment. /// ReceivedRemotingFragment = 0x8063, /// - /// SentRemotingFragment + /// SentRemotingFragment. /// SentRemotingFragment = 0x8064, /// - /// WSManPluginShutdown + /// WSManPluginShutdown. /// WSManPluginShutdown = 0x8065, // End: Transport related events @@ -272,120 +273,120 @@ public enum PowerShellTraceEvent : int // Start: Serialization related events /// - /// SerializerWorkflowLoadSuccess + /// SerializerWorkflowLoadSuccess. /// SerializerWorkflowLoadSuccess = 0x7001, /// - /// SerializerWorkflowLoadFailure + /// SerializerWorkflowLoadFailure. /// SerializerWorkflowLoadFailure = 0x7002, /// - /// SerializerDepthOverride + /// SerializerDepthOverride. /// SerializerDepthOverride = 0x7003, /// - /// SerializerModeOverride + /// SerializerModeOverride. /// SerializerModeOverride = 0x7004, /// - /// SerializerScriptPropertyWithoutRunspace + /// SerializerScriptPropertyWithoutRunspace. /// SerializerScriptPropertyWithoutRunspace = 0x7005, /// - /// SerializerPropertyGetterFailed + /// SerializerPropertyGetterFailed. /// SerializerPropertyGetterFailed = 0x7006, /// - /// SerializerEnumerationFailed + /// SerializerEnumerationFailed. /// SerializerEnumerationFailed = 0x7007, /// - /// SerializerToStringFailed + /// SerializerToStringFailed. /// SerializerToStringFailed = 0x7008, /// - /// SerializerMaxDepthWhenSerializing + /// SerializerMaxDepthWhenSerializing. /// SerializerMaxDepthWhenSerializing = 0x700A, /// - /// SerializerXmlExceptionWhenDeserializing + /// SerializerXmlExceptionWhenDeserializing. /// SerializerXmlExceptionWhenDeserializing = 0x700B, /// - /// SerializerSpecificPropertyMissing + /// SerializerSpecificPropertyMissing. /// SerializerSpecificPropertyMissing = 0x700C, // End: Serialization related events // Start: PerformanceTrack related events /// - /// PerformanceTrackConsoleStartupStart + /// PerformanceTrackConsoleStartupStart. /// PerformanceTrackConsoleStartupStart = 0xA001, /// - /// PerformanceTrackConsoleStartupStop + /// PerformanceTrackConsoleStartupStop. /// PerformanceTrackConsoleStartupStop = 0xA002, // End: Preftrack related events /// - /// ErrorRecord + /// ErrorRecord. /// ErrorRecord = 0xB001, /// - /// Exception + /// Exception. /// Exception = 0xB002, /// - /// PowerShellObject + /// PowerShellObject. /// PowerShellObject = 0xB003, /// - /// Job + /// Job. /// Job = 0xB004, /// - /// Writing a simple trace message from code + /// Writing a simple trace message from code. /// TraceMessage = 0xB005, /// - /// Trace the WSManConnectionInfo used for this connection + /// Trace the WSManConnectionInfo used for this connection. /// TraceWSManConnectionInfo = 0xB006, /// /// Writing a simple trace message from code with 2 - /// strings + /// strings. /// TraceMessage2 = 0xC001, /// /// Writing a simple trace message from code with 2 - /// strings + /// strings. /// TraceMessageGuid = 0xC002, } /// - /// Defines enumerations for channels + /// Defines enumerations for channels. /// - //pragma warning disable 16001 + // pragma warning disable 16001 public enum PowerShellTraceChannel { /// @@ -393,65 +394,65 @@ public enum PowerShellTraceChannel /// None = 0, /// - /// Operational Channel + /// Operational Channel. /// Operational = 0x10, /// - /// Analytic Channel + /// Analytic Channel. /// Analytic = 0x11, /// - /// Debug Channel + /// Debug Channel. /// Debug = 0x12, } - //pragma warning restore 16001 + // pragma warning restore 16001 /// - /// Define enumerations for levels + /// Define enumerations for levels. /// public enum PowerShellTraceLevel { /// - /// LogAlways + /// LogAlways. /// LogAlways = 0, /// - /// Critical + /// Critical. /// Critical = 1, /// - /// Error + /// Error. /// Error = 2, /// - /// Warning + /// Warning. /// Warning = 3, /// - /// Informational + /// Informational. /// Informational = 4, /// - /// Verbose + /// Verbose. /// Verbose = 5, /// - /// Debug + /// Debug. /// Debug = 20, } /// - /// Defines enumerations for op codes + /// Defines enumerations for op codes. /// public enum PowerShellTraceOperationCode { @@ -461,153 +462,153 @@ public enum PowerShellTraceOperationCode None = 0, /// - /// Open + /// Open. /// Open = 10, /// - /// Close + /// Close. /// Close = 11, /// - /// Connect + /// Connect. /// Connect = 12, /// - /// Disconnect + /// Disconnect. /// Disconnect = 13, /// - /// Negotiate + /// Negotiate. /// Negotiate = 14, /// - /// Create + /// Create. /// Create = 15, /// - /// Constructor + /// Constructor. /// Constructor = 16, /// - /// Dispose + /// Dispose. /// Dispose = 17, /// - /// EventHandler + /// EventHandler. /// EventHandler = 18, /// - /// Exception + /// Exception. /// Exception = 19, /// - /// Method + /// Method. /// Method = 20, /// - /// Send + /// Send. /// Send = 21, /// - /// Receive + /// Receive. /// Receive = 22, /// - /// WorkflowLoad + /// WorkflowLoad. /// WorkflowLoad = 23, /// - /// SerializationSettings + /// SerializationSettings. /// SerializationSettings = 24, /// - /// WinInfo + /// WinInfo. /// WinInfo, /// - /// WinStart + /// WinStart. /// WinStart, /// - /// WinStop + /// WinStop. /// WinStop, /// - /// WinDCStart + /// WinDCStart. /// WinDCStart, /// - /// WinDCStop + /// WinDCStop. /// WinDCStop, /// - /// WinExtension + /// WinExtension. /// WinExtension, /// - /// WinReply + /// WinReply. /// WinReply, /// - /// WinResume + /// WinResume. /// WinResume, /// - /// WinSuspend + /// WinSuspend. /// WinSuspend, } /// - /// Defines Tasks + /// Defines Tasks. /// public enum PowerShellTraceTask { /// - /// None + /// None. /// None = 0, /// - /// CreateRunspace + /// CreateRunspace. /// CreateRunspace = 1, /// - /// ExecuteCommand + /// ExecuteCommand. /// ExecuteCommand = 2, /// - /// Serialization + /// Serialization. /// Serialization = 3, /// - /// PowerShellConsoleStartup + /// PowerShellConsoleStartup. /// PowerShellConsoleStartup = 4, } @@ -620,83 +621,80 @@ public enum PowerShellTraceTask public enum PowerShellTraceKeywords : ulong { /// - /// None + /// None. /// None = 0, /// - /// Runspace + /// Runspace. /// Runspace = 0x1, /// - /// Pipeline + /// Pipeline. /// Pipeline = 0x2, /// - /// Protocol + /// Protocol. /// Protocol = 0x4, /// - /// Transport + /// Transport. /// Transport = 0x8, /// - /// Host + /// Host. /// Host = 0x10, /// - /// Cmdlets + /// Cmdlets. /// Cmdlets = 0x20, /// - /// Serializer + /// Serializer. /// Serializer = 0x40, /// - /// Session + /// Session. /// Session = 0x80, /// - /// ManagedPlugIn + /// ManagedPlugIn. /// ManagedPlugIn = 0x100, /// - /// /// UseAlwaysDebug = 0x2000000000000000, /// - /// /// UseAlwaysOperational = 0x8000000000000000, /// - /// /// UseAlwaysAnalytic = 0x4000000000000000, } -#endregion + #endregion /// /// BaseChannelWriter is the abstract base class defines event specific methods that are used to write a trace. - /// The default implementation does not write any message to any trace channel + /// The default implementation does not write any message to any trace channel. /// public abstract class BaseChannelWriter : IDisposable { private bool disposed; /// - /// Dispose method + /// Dispose method. /// public virtual void Dispose() { @@ -708,7 +706,7 @@ public virtual void Dispose() } /// - /// TraceError + /// TraceError. /// public virtual bool TraceError(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -716,7 +714,7 @@ public virtual bool TraceError(PowerShellTraceEvent traceEvent, PowerShellTraceO } /// - /// TraceWarning + /// TraceWarning. /// public virtual bool TraceWarning(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -724,7 +722,7 @@ public virtual bool TraceWarning(PowerShellTraceEvent traceEvent, PowerShellTrac } /// - /// TraceInformational + /// TraceInformational. /// public virtual bool TraceInformational(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -732,7 +730,7 @@ public virtual bool TraceInformational(PowerShellTraceEvent traceEvent, PowerShe } /// - /// TraceVerbose + /// TraceVerbose. /// public virtual bool TraceVerbose(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -740,7 +738,7 @@ public virtual bool TraceVerbose(PowerShellTraceEvent traceEvent, PowerShellTrac } /// - /// TraceDebug + /// TraceDebug. /// public virtual bool TraceDebug(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -748,7 +746,7 @@ public virtual bool TraceDebug(PowerShellTraceEvent traceEvent, PowerShellTraceO } /// - /// TraceLogAlways + /// TraceLogAlways. /// public virtual bool TraceLogAlways(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -756,7 +754,7 @@ public virtual bool TraceLogAlways(PowerShellTraceEvent traceEvent, PowerShellTr } /// - /// TraceCritical + /// TraceCritical. /// public virtual bool TraceCritical(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -764,7 +762,6 @@ public virtual bool TraceCritical(PowerShellTraceEvent traceEvent, PowerShellTra } /// - /// /// public virtual PowerShellTraceKeywords Keywords { @@ -772,6 +769,7 @@ public virtual PowerShellTraceKeywords Keywords { return PowerShellTraceKeywords.None; } + set { PowerShellTraceKeywords powerShellTraceKeywords = value; @@ -788,7 +786,7 @@ public virtual PowerShellTraceKeywords Keywords public sealed class NullWriter : BaseChannelWriter { /// - /// Static Instance property + /// Static Instance property. /// public static BaseChannelWriter Instance { get; } = new NullWriter(); @@ -815,7 +813,6 @@ public sealed class PowerShellChannelWriter : BaseChannelWriter private PowerShellTraceKeywords _keywords; /// - /// /// public override PowerShellTraceKeywords Keywords { @@ -823,6 +820,7 @@ public override PowerShellTraceKeywords Keywords { return _keywords; } + set { _keywords = value; @@ -836,7 +834,7 @@ internal PowerShellChannelWriter(PowerShellTraceChannel traceChannel, PowerShell } /// - /// Dispose method + /// Dispose method. /// public override void Dispose() { @@ -868,11 +866,11 @@ private bool Trace(PowerShellTraceEvent traceEvent, PowerShellTraceLevel level, } } - return _provider.WriteEvent(ref ed, args); + return _provider.WriteEvent(in ed, args); } /// - /// TraceError + /// TraceError. /// public override bool TraceError(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -880,7 +878,7 @@ public override bool TraceError(PowerShellTraceEvent traceEvent, PowerShellTrace } /// - /// TraceWarning + /// TraceWarning. /// public override bool TraceWarning(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -888,7 +886,7 @@ public override bool TraceWarning(PowerShellTraceEvent traceEvent, PowerShellTra } /// - /// TraceInformational + /// TraceInformational. /// public override bool TraceInformational(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -896,7 +894,7 @@ public override bool TraceInformational(PowerShellTraceEvent traceEvent, PowerSh } /// - /// TraceVerbose + /// TraceVerbose. /// public override bool TraceVerbose(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -904,7 +902,7 @@ public override bool TraceVerbose(PowerShellTraceEvent traceEvent, PowerShellTra } /// - /// TraceDebug + /// TraceDebug. /// public override bool TraceDebug(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -914,7 +912,7 @@ public override bool TraceDebug(PowerShellTraceEvent traceEvent, PowerShellTrace } /// - /// TraceLogAlways + /// TraceLogAlways. /// public override bool TraceLogAlways(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -922,7 +920,7 @@ public override bool TraceLogAlways(PowerShellTraceEvent traceEvent, PowerShellT } /// - /// TraceCritical + /// TraceCritical. /// public override bool TraceCritical(PowerShellTraceEvent traceEvent, PowerShellTraceOperationCode operationCode, PowerShellTraceTask task, params object[] args) { @@ -940,7 +938,7 @@ public sealed class PowerShellTraceSource : IDisposable private bool disposed; /// - /// Constructor + /// Constructor. /// internal PowerShellTraceSource(PowerShellTraceTask task, PowerShellTraceKeywords keywords) { @@ -981,16 +979,16 @@ public void Dispose() } /// - /// Keywords that were set through constructor when object was instantiated + /// Keywords that were set through constructor when object was instantiated. /// public PowerShellTraceKeywords Keywords { get; } = PowerShellTraceKeywords.None; /// - /// Task that was set through constructor + /// Task that was set through constructor. /// public PowerShellTraceTask Task { get; set; } = PowerShellTraceTask.None; - private bool IsEtwSupported + private static bool IsEtwSupported { get { @@ -999,7 +997,7 @@ private bool IsEtwSupported } /// - /// TraceErrorRecord + /// TraceErrorRecord. /// public bool TraceErrorRecord(ErrorRecord errorRecord) { @@ -1019,6 +1017,7 @@ public bool TraceErrorRecord(ErrorRecord errorRecord) { message = errorRecord.ErrorDetails.Message; } + return DebugChannel.TraceError(PowerShellTraceEvent.ErrorRecord, PowerShellTraceOperationCode.Exception, PowerShellTraceTask.None, message, @@ -1035,7 +1034,7 @@ public bool TraceErrorRecord(ErrorRecord errorRecord) } /// - /// TraceException + /// TraceException. /// public bool TraceException(Exception exception) { @@ -1060,7 +1059,7 @@ public bool TraceException(Exception exception) } /// - /// TracePowerShellObject + /// TracePowerShellObject. /// public bool TracePowerShellObject(PSObject powerShellObject) { @@ -1069,7 +1068,7 @@ public bool TracePowerShellObject(PSObject powerShellObject) } /// - /// TraceJob + /// TraceJob. /// public bool TraceJob(Job job) { @@ -1085,16 +1084,15 @@ public bool TraceJob(Job job) { return DebugChannel.TraceDebug(PowerShellTraceEvent.Job, PowerShellTraceOperationCode.Method, PowerShellTraceTask.None, - "", "", "NULL job"); + string.Empty, string.Empty, "NULL job"); } } /// - /// /// /// /// - public bool WriteMessage(String message) + public bool WriteMessage(string message) { return DebugChannel.TraceInformational(PowerShellTraceEvent.TraceMessage, PowerShellTraceOperationCode.None, @@ -1102,7 +1100,6 @@ public bool WriteMessage(String message) } /// - /// /// /// /// @@ -1115,7 +1112,6 @@ public bool WriteMessage(string message1, string message2) } /// - /// /// /// /// @@ -1127,9 +1123,7 @@ public bool WriteMessage(string message, Guid instanceId) PowerShellTraceTask.None, message, instanceId); } - /// - /// /// /// /// @@ -1144,14 +1138,13 @@ public void WriteMessage(string className, string methodName, Guid workflowId, s PSKeyword.UseAlwaysAnalytic, className, methodName, workflowId.ToString(), parameters == null ? message : StringUtil.Format(message, parameters), - String.Empty, // Job - String.Empty, // Activity name - String.Empty, // Activity GUID - String.Empty); + string.Empty, // Job + string.Empty, // Activity name + string.Empty, // Activity GUID + string.Empty); } /// - /// /// /// /// @@ -1196,10 +1189,10 @@ public void WriteMessage(string className, string methodName, Guid workflowId, J PSKeyword.UseAlwaysAnalytic, className, methodName, workflowId.ToString(), parameters == null ? message : StringUtil.Format(message, parameters), - sb.ToString(),// Job - String.Empty, // Activity name - String.Empty, // Activity GUID - String.Empty); + sb.ToString(), // Job + string.Empty, // Activity name + string.Empty, // Activity GUID + string.Empty); } /// @@ -1450,7 +1443,6 @@ public void WriteISEHitBreakpointEvent(params object[] args) } /// - /// /// /// /// @@ -1467,14 +1459,13 @@ public void WriteMessage(string className, string methodName, Guid workflowId, s PSKeyword.UseAlwaysAnalytic, className, methodName, workflowId.ToString(), parameters == null ? message : StringUtil.Format(message, parameters), - String.Empty, // Job + string.Empty, // Job activityName, activityId.ToString(), - String.Empty); + string.Empty); } /// - /// /// /// /// @@ -1484,17 +1475,17 @@ public bool TraceWSManConnectionInfo(WSManConnectionInfo connectionInfo) } /// - /// Gives access to Debug channel writer + /// Gives access to Debug channel writer. /// public BaseChannelWriter DebugChannel { get; } /// - /// Gives access to analytical channel writer + /// Gives access to analytical channel writer. /// public BaseChannelWriter AnalyticChannel { get; } /// - /// Gives access to operational channel writer + /// Gives access to operational channel writer. /// public BaseChannelWriter OperationalChannel { get; } } @@ -1543,7 +1534,7 @@ public static PowerShellTraceSource GetTraceSource(PowerShellTraceTask task, Pow return new PowerShellTraceSource(task, keywords); } } - //pragma warning restore 16001,16003 + // pragma warning restore 16001,16003 } #endif diff --git a/src/System.Management.Automation/utils/PowerShellExecutionHelper.cs b/src/System.Management.Automation/utils/PowerShellExecutionHelper.cs index 0278962d6b9..981680e3e45 100644 --- a/src/System.Management.Automation/utils/PowerShellExecutionHelper.cs +++ b/src/System.Management.Automation/utils/PowerShellExecutionHelper.cs @@ -1,6 +1,5 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; using System.Collections.Generic; @@ -19,7 +18,7 @@ internal PowerShellExecutionHelper(PowerShell powershell) { if (powershell == null) { - throw PSTraceSource.NewArgumentNullException("powershell"); + throw PSTraceSource.NewArgumentNullException(nameof(powershell)); } CurrentPowerShell = powershell; @@ -47,12 +46,6 @@ internal PowerShellExecutionHelper(PowerShell powershell) #region Command Execution - internal Collection ExecuteCommand(string command) - { - Exception unused; - return ExecuteCommand(command, true, out unused, null); - } - internal bool ExecuteCommandAndGetResultAsBool() { Exception exceptionThrown; @@ -67,78 +60,19 @@ internal bool ExecuteCommandAndGetResultAsBool() return (streamResults.Count > 1) || (LanguagePrimitives.IsTrue(streamResults[0])); } - internal string ExecuteCommandAndGetResultAsString() - { - Exception exceptionThrown; - Collection streamResults = ExecuteCurrentPowerShell(out exceptionThrown); - - if (exceptionThrown != null || streamResults == null || streamResults.Count == 0) - { - return null; - } - - // we got back one or more objects. Pick off the first result. - if (streamResults[0] == null) - return String.Empty; - - // 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. - return SafeToString(streamResults[0]); - } - - internal Collection ExecuteCommand(string command, bool isScript, out Exception exceptionThrown, Hashtable args) + internal Collection ExecuteCurrentPowerShell(out Exception exceptionThrown, IEnumerable input = null) { - Diagnostics.Assert(command != null, "caller to verify command is not null"); - - exceptionThrown = null; - - // This flag indicates a previous call to this method had its pipeline cancelled - if (CancelTabCompletion) - { - return new Collection(); - } - - CurrentPowerShell.AddCommand(command); - - Command cmd = new Command(command, isScript); - if (args != null) - { - foreach (DictionaryEntry arg in args) - { - cmd.Parameters.Add((string)(arg.Key), arg.Value); - } - } - - Collection results = null; - try - { - // blocks until all results are retrieved. - //results = this.ExecuteCommand(cmd); - - // If this pipeline has been stopped lets set a flag to cancel all future tab completion calls - // untill the next completion - if (IsStopped) - { - results = new Collection(); - CancelTabCompletion = true; - } - } - catch (Exception e) - { - exceptionThrown = e; - } - - return results; + return ExecuteCurrentPowerShell(out exceptionThrown, out _, input); } - internal Collection ExecuteCurrentPowerShell(out Exception exceptionThrown, IEnumerable input = null) + internal Collection ExecuteCurrentPowerShell(out Exception exceptionThrown, out bool hadErrors, IEnumerable input = null) { exceptionThrown = null; // This flag indicates a previous call to this method had its pipeline cancelled if (CancelTabCompletion) { + hadErrors = false; return new Collection(); } @@ -148,7 +82,7 @@ internal Collection ExecuteCurrentPowerShell(out Exception exceptionTh results = CurrentPowerShell.Invoke(input); // If this pipeline has been stopped lets set a flag to cancel all future tab completion calls - // untill the next completion + // until the next completion if (IsStopped) { results = new Collection(); @@ -161,6 +95,7 @@ internal Collection ExecuteCurrentPowerShell(out Exception exceptionTh } finally { + hadErrors = CurrentPowerShell.HadErrors; CurrentPowerShell.Commands.Clear(); } @@ -174,7 +109,7 @@ internal Collection ExecuteCurrentPowerShell(out Exception exceptionTh /// /// Converts an object to a string safely... /// - /// The object to convert + /// The object to convert. /// The result of the conversion... internal static string SafeToString(object obj) { @@ -185,12 +120,11 @@ internal static string SafeToString(object obj) try { - PSObject pso = obj as PSObject; string result; - if (pso != null) + if (obj is PSObject pso) { object baseObject = pso.BaseObject; - if (baseObject != null && !(baseObject is PSCustomObject)) + if (baseObject != null && baseObject is not PSCustomObject) result = baseObject.ToString(); else result = pso.ToString(); @@ -199,6 +133,7 @@ internal static string SafeToString(object obj) { result = obj.ToString(); } + return result; } catch (Exception) @@ -209,9 +144,9 @@ internal static string SafeToString(object obj) } /// - /// Converts an object to a string adn, if the string is not empty, adds it to the list + /// Converts an object to a string adn, if the string is not empty, adds it to the list. /// - /// The list to update + /// The list to update. /// The object to convert to a string... internal static void SafeAddToStringList(List list, object obj) { @@ -236,7 +171,7 @@ internal static PowerShell AddCommandWithPreferenceSetting(this PowerShellExecut internal static PowerShell AddCommandWithPreferenceSetting(this PowerShell powershell, string command, Type type = null) { Diagnostics.Assert(powershell != null, "the passed-in powershell cannot be null"); - Diagnostics.Assert(!String.IsNullOrWhiteSpace(command), + Diagnostics.Assert(!string.IsNullOrWhiteSpace(command), "the passed-in command name should not be null or whitespaces"); if (type != null) @@ -249,6 +184,7 @@ internal static PowerShell AddCommandWithPreferenceSetting(this PowerShell power { powershell.AddCommand(command); } + powershell .AddParameter("ErrorAction", ActionPreference.Ignore) .AddParameter("WarningAction", ActionPreference.Ignore) diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index 70d5a7cde28..0a4682bcdd6 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -1,94 +1,28 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; +using System.Management.Automation.Language; +using System.Net.NetworkInformation; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Security; using System.Text; using System.Xml; -using Microsoft.Win32; -using System.Collections.Generic; -using System.Management.Automation.Language; namespace System.Management.Automation { /// - /// Defines generic utilities and helper methods for PowerShell + /// Defines generic utilities and helper methods for PowerShell. /// internal static class PsUtils { - internal static string ArmArchitecture = "ARM"; - - /// - /// Safely retrieves the MainModule property of a - /// process. Version 2.0 and below of the .NET Framework are - /// impacted by a Win32 API usability knot that throws an - /// exception if API tries to enumerate the process' modules - /// while it is still loading them. This generates the error - /// message: Only part of a ReadProcessMemory or - /// WriteProcessMemory request was completed. - /// The BCL fix in V3 was to just try more, so we do the same - /// thing. - /// - /// Note: If you attempt to retrieve the MainModule of a 64-bit - /// process from a WOW64 (32-bit) process, the Win32 API has a fatal - /// flaw that causes this to return the same error. - /// - /// If you need the MainModule of a 64-bit process from a WOW64 - /// process, you will need to write the P/Invoke yourself. - /// - /// - /// The process from which to - /// retrieve the MainModule - /// - /// You are trying to access the MainModule property for a process that is running - /// on a remote computer. This property is available only for processes that are - /// running on the local computer. - /// - /// - /// The process Id is not available (or) The process has exited. - /// - /// - /// - /// - internal static ProcessModule GetMainModule(Process targetProcess) - { - int caughtCount = 0; - ProcessModule mainModule = null; - - while (mainModule == null) - { - try - { - mainModule = targetProcess.MainModule; - } - catch (System.ComponentModel.Win32Exception e) - { - // If this is an Access Denied error (which can happen with thread impersonation) - // then re-throw immediately. - if (e.NativeErrorCode == 5) - throw; - - // Otherwise retry to ensure module is loaded. - caughtCount++; - System.Threading.Thread.Sleep(100); - if (caughtCount == 5) - throw; - } - } - - return mainModule; - } - // Cache of the current process' parentId private static int? s_currentParentProcessId; - private static readonly int s_currentProcessId = Process.GetCurrentProcess().Id; + private static readonly int s_currentProcessId = Environment.ProcessId; /// /// Retrieve the parent process of a process. @@ -97,7 +31,6 @@ internal static ProcessModule GetMainModule(Process targetProcess) /// tzres.dll and tzres.mui.dll being loaded into every process to convert the time information to local format. /// For perf reasons, we resort to P/Invoke. /// - /// /// The process we want to find the /// parent of internal static Process GetParentProcess(Process current) @@ -141,96 +74,27 @@ internal static Process GetParentProcess(Process current) } /// - /// Returns processor architecture for the current process. - /// If powershell is running inside Wow64, then is returned. - /// - /// processor architecture for the current process - internal static ProcessorArchitecture GetProcessorArchitecture(out bool isRunningOnArm) - { - var sysInfo = new NativeMethods.SYSTEM_INFO(); - NativeMethods.GetSystemInfo(ref sysInfo); - ProcessorArchitecture result; - isRunningOnArm = false; - switch (sysInfo.wProcessorArchitecture) - { - case NativeMethods.PROCESSOR_ARCHITECTURE_IA64: - result = ProcessorArchitecture.IA64; - break; - case NativeMethods.PROCESSOR_ARCHITECTURE_AMD64: - result = ProcessorArchitecture.Amd64; - break; - case NativeMethods.PROCESSOR_ARCHITECTURE_INTEL: - result = ProcessorArchitecture.X86; - break; - case NativeMethods.PROCESSOR_ARCHITECTURE_ARM: - result = ProcessorArchitecture.None; - isRunningOnArm = true; - break; - - default: - result = ProcessorArchitecture.None; - break; - } - - return result; - } - - /// - /// Return true/false to indicate whether the processor architecture is ARM + /// Return true/false to indicate whether the process architecture is ARM. /// /// - internal static bool IsRunningOnProcessorArchitectureARM() + internal static bool IsRunningOnProcessArchitectureARM() { - Architecture arch = RuntimeInformation.OSArchitecture; + Architecture arch = RuntimeInformation.ProcessArchitecture; return arch == Architecture.Arm || arch == Architecture.Arm64; } - /// - /// Get a temporary directory to use, needs to be unique to avoid collision - /// - internal static string GetTemporaryDirectory() - { - string tempDir = String.Empty; - string tempPath = Path.GetTempPath(); - do - { - tempDir = Path.Combine(tempPath,System.Guid.NewGuid().ToString()); - } - while (Directory.Exists(tempDir)); - - try - { - Directory.CreateDirectory(tempDir); - } - catch (UnauthorizedAccessException) - { - tempDir = String.Empty; // will become current working directory - } - return tempDir; - } - internal static string GetHostName() { - // Note: non-windows CoreCLR does not support System.Net yet - if (Platform.IsWindows) - { - return WinGetHostName(); - } - else - { - return Platform.NonWindowsGetHostName(); - } - } - - internal static string WinGetHostName() - { - System.Net.NetworkInformation.IPGlobalProperties ipProperties = - System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); + IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); string hostname = ipProperties.HostName; - if (!String.IsNullOrEmpty(ipProperties.DomainName)) + string domainName = ipProperties.DomainName; + + // CoreFX on Unix calls GLibc getdomainname() + // which returns "(none)" if a domain name is not set by setdomainname() + if (!string.IsNullOrEmpty(domainName) && !domainName.Equals("(none)", StringComparison.Ordinal)) { - hostname = hostname + "." + ipProperties.DomainName; + hostname = hostname + "." + domainName; } return hostname; @@ -241,45 +105,10 @@ internal static uint GetNativeThreadId() #if UNIX return Platform.NonWindowsGetThreadId(); #else - return NativeMethods.GetCurrentThreadId(); + return Interop.Windows.GetCurrentThreadId(); #endif } - private static class NativeMethods - { - // Important: - // this clone has a clone in SMA in admin\monad\src\m3p\product\ServiceCore\WorkflowCore\WorkflowRuntimeCompilation.cs - // if you are making any changes specific to this class then update the clone as well. - - internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; - internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5; - internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; - internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; - internal const ushort PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF; - - [StructLayout(LayoutKind.Sequential)] - internal struct SYSTEM_INFO - { - public ushort wProcessorArchitecture; - public ushort wReserved; - public uint dwPageSize; - public IntPtr lpMinimumApplicationAddress; - public IntPtr lpMaximumApplicationAddress; - public UIntPtr dwActiveProcessorMask; - public uint dwNumberOfProcessors; - public uint dwProcessorType; - public uint dwAllocationGranularity; - public ushort wProcessorLevel; - public ushort wProcessorRevision; - }; - - [DllImport(PinvokeDllNames.GetSystemInfoDllName)] - internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo); - - [DllImport(PinvokeDllNames.GetCurrentThreadIdDllName)] - internal static extern uint GetCurrentThreadId(); - } - #region ASTUtils /// @@ -292,8 +121,8 @@ internal struct SYSTEM_INFO /// to the remote end that contains the key of each UsingExpressionAst and its value. This method /// is used to generate the key. /// - /// A using expression - /// Base64 encoded string as the key of the UsingExpressionAst + /// A using expression. + /// Base64 encoded string as the key of the UsingExpressionAst. internal static string GetUsingExpressionKey(Language.UsingExpressionAst usingAst) { Diagnostics.Assert(usingAst != null, "Caller makes sure the parameter is not null"); @@ -314,6 +143,7 @@ internal static string GetUsingExpressionKey(Language.UsingExpressionAst usingAs { usingAstText = usingAstText.ToLowerInvariant(); } + return StringToBase64Converter.StringToBase64String(usingAstText); } @@ -322,7 +152,7 @@ internal static string GetUsingExpressionKey(Language.UsingExpressionAst usingAs #region EvaluatePowerShellDataFile /// - /// Evaluate a powershell data file as if it's a module manifest + /// Evaluate a powershell data file as if it's a module manifest. /// /// /// @@ -381,9 +211,20 @@ internal static Hashtable EvaluatePowerShellDataFile( bool allowEnvironmentVariables, bool skipPathValidation) { - if (!skipPathValidation && string.IsNullOrEmpty(parameterName)) { throw PSTraceSource.NewArgumentNullException("parameterName"); } - if (string.IsNullOrEmpty(psDataFilePath)) { throw PSTraceSource.NewArgumentNullException("psDataFilePath"); } - if (context == null) { throw PSTraceSource.NewArgumentNullException("context"); } + if (!skipPathValidation && string.IsNullOrEmpty(parameterName)) + { + throw PSTraceSource.NewArgumentNullException(nameof(parameterName)); + } + + if (string.IsNullOrEmpty(psDataFilePath)) + { + throw PSTraceSource.NewArgumentNullException(nameof(psDataFilePath)); + } + + if (context == null) + { + throw PSTraceSource.NewArgumentNullException(nameof(context)); + } string resolvedPath; if (skipPathValidation) @@ -467,8 +308,7 @@ internal static Hashtable EvaluatePowerShellDataFile( ex.Message); } - var retResult = evaluationResult as Hashtable; - if (retResult == null) + if (evaluationResult is not Hashtable retResult) { throw PSTraceSource.NewInvalidOperationException( ParserStrings.InvalidPowerShellDataFile, @@ -484,11 +324,23 @@ internal static Hashtable EvaluatePowerShellDataFile( internal static readonly string[] ManifestModuleVersionPropertyName = new[] { "ModuleVersion" }; internal static readonly string[] ManifestGuidPropertyName = new[] { "GUID" }; - internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[] { "AliasesToExport", "CmdletsToExport", "FunctionsToExport", "NestedModules", "RootModule", "ModuleToProcess", "ModuleVersion" }; + internal static readonly string[] ManifestPrivateDataPropertyName = new[] { "PrivateData" }; + + internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[] + { + "AliasesToExport", + "CmdletsToExport", + "CompatiblePSEditions", + "FunctionsToExport", + "NestedModules", + "RootModule", + "ModuleToProcess", + "ModuleVersion" + }; internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys) { - string dataFileContents = ScriptAnalysis.ReadScript(psDataFilePath); + string dataFileContents = File.ReadAllText(psDataFilePath, Encoding.Default); ParseError[] parseErrors; var ast = (new Parser()).Parse(psDataFilePath, dataFileContents, null, out parseErrors, ParseMode.ModuleAnalysis); if (parseErrors.Length > 0) @@ -501,35 +353,29 @@ internal static Hashtable GetModuleManifestProperties(string psDataFilePath, str pe.Message); } - string unused1; - string unused2; - var pipeline = ast.GetSimplePipeline(false, out unused1, out unused2); - if (pipeline != null) + var pipeline = ast.GetSimplePipeline(false, out _, out _); + if (pipeline?.GetPureExpression() is HashtableAst hashtableAst) { - var hashtableAst = pipeline.GetPureExpression() as HashtableAst; - if (hashtableAst != null) + var result = new Hashtable(StringComparer.OrdinalIgnoreCase); + foreach (var pair in hashtableAst.KeyValuePairs) { - var result = new Hashtable(StringComparer.OrdinalIgnoreCase); - foreach (var pair in hashtableAst.KeyValuePairs) + if (pair.Item1 is StringConstantExpressionAst key && keys.Contains(key.Value, StringComparer.OrdinalIgnoreCase)) { - var key = pair.Item1 as StringConstantExpressionAst; - if (key != null && keys.Contains(key.Value, StringComparer.OrdinalIgnoreCase)) + try + { + var val = pair.Item2.SafeGetValue(); + result[key.Value] = val; + } + catch { - try - { - var val = pair.Item2.SafeGetValue(); - result[key.Value] = val; - } - catch - { - throw PSTraceSource.NewInvalidOperationException( - ParserStrings.InvalidPowerShellDataFile, - psDataFilePath); - } + throw PSTraceSource.NewInvalidOperationException( + ParserStrings.InvalidPowerShellDataFile, + psDataFilePath); } } - return result; } + + return result; } throw PSTraceSource.NewInvalidOperationException( @@ -540,23 +386,24 @@ internal static Hashtable GetModuleManifestProperties(string psDataFilePath, str /// /// This class provides helper methods for converting to/fro from - /// string to base64string + /// string to base64string. /// internal static class StringToBase64Converter { /// - /// Converts string to base64 encoded string + /// Converts string to base64 encoded string. /// - /// string to encode - /// base64 encoded string + /// String to encode. + /// Base64 encoded string. internal static string StringToBase64String(string input) { // NTRAID#Windows Out Of Band Releases-926471-2005/12/27-JonN // shell crashes if you pass an empty script block to a native command - if (null == input) + if (input == null) { - throw PSTraceSource.NewArgumentNullException("input"); + throw PSTraceSource.NewArgumentNullException(nameof(input)); } + string base64 = Convert.ToBase64String ( Encoding.Unicode.GetBytes(input.ToCharArray()) @@ -565,22 +412,23 @@ internal static string StringToBase64String(string input) } /// - /// Decodes base64 encoded string + /// Decodes base64 encoded string. /// - /// base64 string to decode - /// decoded string + /// Base64 string to decode. + /// Decoded string. internal static string Base64ToString(string base64) { if (string.IsNullOrEmpty(base64)) { - throw PSTraceSource.NewArgumentNullException("base64"); + throw PSTraceSource.NewArgumentNullException(nameof(base64)); } + string output = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64))); return output; } /// - /// Decodes base64 encoded string in to args array + /// Decodes base64 encoded string in to args array. /// /// /// @@ -588,35 +436,34 @@ internal static object[] Base64ToArgsConverter(string base64) { if (string.IsNullOrEmpty(base64)) { - throw PSTraceSource.NewArgumentNullException("base64"); + throw PSTraceSource.NewArgumentNullException(nameof(base64)); } + string decoded = new string(Encoding.Unicode.GetChars(Convert.FromBase64String(base64))); - //Deserialize string + // Deserialize string XmlReader reader = XmlReader.Create(new StringReader(decoded), InternalDeserializer.XmlReaderSettingsForCliXml); object dso; Deserializer deserializer = new Deserializer(reader); dso = deserializer.Deserialize(); - if (deserializer.Done() == false) + if (!deserializer.Done()) { - //This helper function should move to host and it should provide appropriate - //error message there. + // This helper function should move to host and it should provide appropriate + // error message there. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - PSObject mo = dso as PSObject; - if (mo == null) + if (dso is not PSObject mo) { - //This helper function should move the host. Provide appropriate error message. - //Format of args parameter is not correct. + // This helper function should move the host. Provide appropriate error message. + // Format of args parameter is not correct. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - var argsList = mo.BaseObject as ArrayList; - if (argsList == null) + if (mo.BaseObject is not ArrayList argsList) { - //This helper function should move the host. Provide appropriate error message. - //Format of args parameter is not correct. + // This helper function should move the host. Provide appropriate error message. + // Format of args parameter is not correct. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } @@ -624,10 +471,70 @@ internal static object[] Base64ToArgsConverter(string base64) } } + /// + /// A simple implementation of CRC32. + /// See "CRC-32 algorithm" in https://en.wikipedia.org/wiki/Cyclic_redundancy_check. + /// + internal static class CRC32Hash + { + // CRC-32C polynomial representations + private const uint polynomial = 0x1EDC6F41; + + private static readonly uint[] table; + + static CRC32Hash() + { + uint temp = 0; + table = new uint[256]; + + for (int i = 0; i < table.Length; i++) + { + temp = (uint)i; + for (int j = 0; j < 8; j++) + { + if ((temp & 1) == 1) + { + temp = (temp >> 1) ^ polynomial; + } + else + { + temp >>= 1; + } + } + + table[i] = temp; + } + } + + private static uint Compute(byte[] buffer) + { + uint crc = 0xFFFFFFFF; + for (int i = 0; i < buffer.Length; ++i) + { + var index = (byte)(crc ^ buffer[i] & 0xff); + crc = (crc >> 8) ^ table[index]; + } + + return ~crc; + } + + internal static byte[] ComputeHash(byte[] buffer) + { + uint crcResult = Compute(buffer); + return BitConverter.GetBytes(crcResult); + } + + internal static string ComputeHash(string input) + { + byte[] hashBytes = ComputeHash(Encoding.UTF8.GetBytes(input)); + return Convert.ToHexString(hashBytes); + } + } + #region ReferenceEqualityComparer /// - /// Equality comparer based on Object Identity + /// Equality comparer based on Object Identity. /// internal class ReferenceEqualityComparer : IEqualityComparer { @@ -653,4 +560,3 @@ int IEqualityComparer.GetHashCode(object obj) #endregion } - diff --git a/src/System.Management.Automation/utils/ResourceManagerCache.cs b/src/System.Management.Automation/utils/ResourceManagerCache.cs index 373947dca2b..f5e029ede25 100644 --- a/src/System.Management.Automation/utils/ResourceManagerCache.cs +++ b/src/System.Management.Automation/utils/ResourceManagerCache.cs @@ -1,15 +1,13 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -using System.Reflection; using System.Collections.Generic; +using System.Reflection; using System.Resources; namespace System.Management.Automation { /// - /// /// internal static class ResourceManagerCache { @@ -18,43 +16,39 @@ internal static class ResourceManagerCache /// to the default resource assembly. The value is another dictionary that is keyed based on the base /// name for the resource that is being retrieved. The value for this dictionary is the ResourceManager. /// - private static Dictionary> s_resourceManagerCache = + private static readonly Dictionary> s_resourceManagerCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); /// - /// Used to synchronize access to the ResourceManagerCache + /// Used to synchronize access to the ResourceManagerCache. /// - private static object s_syncRoot = new Object(); + private static readonly object s_syncRoot = new object(); /// /// Gets the ResourceManager from the cache or gets an instance of the ResourceManager /// and returns it if it isn't already present in the cache. /// - /// /// /// The assembly to be used as the base for resource lookup. /// - /// /// /// The base name of the resources to get the ResourceManager for. /// - /// /// /// A ResourceManager instance for the assembly and base name that were specified. /// - /// internal static ResourceManager GetResourceManager( Assembly assembly, string baseName) { if (assembly == null) { - throw PSTraceSource.NewArgumentNullException("assembly"); + throw PSTraceSource.NewArgumentNullException(nameof(assembly)); } - if (String.IsNullOrEmpty(baseName)) + if (string.IsNullOrEmpty(baseName)) { - throw PSTraceSource.NewArgumentException("baseName"); + throw PSTraceSource.NewArgumentException(nameof(baseName)); } // Check to see if the manager is already in the cache @@ -97,7 +91,7 @@ internal static ResourceManager GetResourceManager( // cache entry and then add it into the cache keyed by the assembly // location - var baseNameCacheEntry = new Dictionary(); + var baseNameCacheEntry = new Dictionary(); baseNameCacheEntry[baseName] = manager; @@ -113,43 +107,39 @@ internal static ResourceManager GetResourceManager( "If the manager was not already created, it should have been dynamically created or an exception should have been thrown"); return manager; - } // GetResourceManager + } /// - /// Design For Testability -- assert on failed resource lookup + /// Design For Testability -- assert on failed resource lookup. /// private static bool s_DFT_monitorFailingResourceLookup = true; + internal static bool DFT_DoMonitorFailingResourceLookup { get { return ResourceManagerCache.s_DFT_monitorFailingResourceLookup; } + set { ResourceManagerCache.s_DFT_monitorFailingResourceLookup = value; } } /// /// Gets the string from the resource manager based on the assembly, - /// base name, resource ID, and culture specified + /// base name, resource ID, and culture specified. /// - /// /// /// The base assembly from which to get the resources from. /// - /// /// /// The base name of the resource to retrieve the string from. /// - /// /// /// Resource ID for which the localized string needs to be retrieved /// - /// /// /// Localized String, or null if the string does not exist /// - /// /// /// The current thread's UI culture is used. /// - /// /// /// ArgumentException if or /// are null or empty.. @@ -164,17 +154,17 @@ internal static string GetResourceString( { if (assembly == null) { - throw PSTraceSource.NewArgumentNullException("assembly"); + throw PSTraceSource.NewArgumentNullException(nameof(assembly)); } - if (String.IsNullOrEmpty(baseName)) + if (string.IsNullOrEmpty(baseName)) { - throw PSTraceSource.NewArgumentException("baseName"); + throw PSTraceSource.NewArgumentException(nameof(baseName)); } - if (String.IsNullOrEmpty(resourceId)) + if (string.IsNullOrEmpty(resourceId)) { - throw PSTraceSource.NewArgumentException("resourceId"); + throw PSTraceSource.NewArgumentException(nameof(resourceId)); } ResourceManager resourceManager = null; @@ -209,11 +199,12 @@ internal static string GetResourceString( text = resourceManager.GetString(resourceId); } - if (String.IsNullOrEmpty(text) && s_DFT_monitorFailingResourceLookup) + if (string.IsNullOrEmpty(text) && s_DFT_monitorFailingResourceLookup) { Diagnostics.Assert(false, "Lookup failure: baseName " + baseName + " resourceId " + resourceId); } + return text; } @@ -228,7 +219,7 @@ internal static string GetResourceString( /// /// The main Assembly for the resources /// - /// Resource Manager instance + /// Resource Manager instance. /// /// Thrown if the resource manager instance could not be created /// @@ -244,11 +235,10 @@ private static ResourceManager InitRMWithAssembly(string baseName, Assembly asse { // 2004/10/11-JonN Do we need a better error message? I don't think so, // since this is private. - throw PSTraceSource.NewArgumentException("assemblyToUse"); + throw PSTraceSource.NewArgumentException(nameof(assemblyToUse)); } return rm; } - } // class ResourceManagerCache -} // namespace System.Management.Automation - + } +} diff --git a/src/System.Management.Automation/utils/RuntimeException.cs b/src/System.Management.Automation/utils/RuntimeException.cs index 53011603272..ab013359d5c 100644 --- a/src/System.Management.Automation/utils/RuntimeException.cs +++ b/src/System.Management.Automation/utils/RuntimeException.cs @@ -1,26 +1,23 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Management.Automation.Language; using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { /// /// RuntimeException is the base class for exceptions likely to occur - /// while a Monad command is running. + /// while a PowerShell command is running. /// /// - /// Monad scripts can trap RuntimeException using the + /// PowerShell scripts can trap RuntimeException using the /// "trap (exceptionclass) {handler}" script construct. /// /// 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 RuntimeException : SystemException, IContainsErrorRecord { @@ -28,7 +25,7 @@ public class RuntimeException /// /// Initializes a new instance of the RuntimeException class. /// - /// constructed object + /// Constructed object. public RuntimeException() : base() { @@ -40,41 +37,22 @@ public RuntimeException() /// using data serialized via /// /// - /// serialization information - /// streaming context - /// constructed object + /// Serialization information. + /// Streaming context. + /// Constructed object. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RuntimeException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _errorId = info.GetString("ErrorId"); - _errorCategory = (ErrorCategory)info.GetInt32("ErrorCategory"); - } - - /// - /// 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("info"); - } - - base.GetObjectData(info, context); - info.AddValue("ErrorId", _errorId); - info.AddValue("ErrorCategory", (Int32)_errorCategory); + throw new NotSupportedException(); } #endregion Serialization /// /// Initializes a new instance of the RuntimeException class. /// - /// - /// constructed object + /// + /// Constructed object. public RuntimeException(string message) : base(message) { @@ -83,9 +61,9 @@ public RuntimeException(string message) /// /// Initializes a new instance of the RuntimeException class. /// - /// - /// - /// constructed object + /// + /// + /// Constructed object. public RuntimeException(string message, Exception innerException) : base(message, innerException) @@ -99,7 +77,7 @@ public RuntimeException(string message, /// /// /// - /// constructed object + /// Constructed object. public RuntimeException(string message, Exception innerException, ErrorRecord errorRecord) @@ -124,7 +102,11 @@ internal RuntimeException(ErrorCategory errorCategory, errorPosition = invocationInfo.ScriptPosition; } - if (invocationInfo == null) return; + if (invocationInfo == null) + { + return; + } + _errorRecord = new ErrorRecord( new ParentContainsErrorRecordException(this), _errorId, @@ -133,7 +115,6 @@ internal RuntimeException(ErrorCategory errorCategory, _errorRecord.SetInvocationInfo(new InvocationInfo(invocationInfo.MyCommand, errorPosition)); } - #endregion ctor #region ErrorRecord @@ -144,7 +125,7 @@ internal RuntimeException(ErrorCategory errorCategory, // will clean the cached ErrorRecord and erase any other changes, // so the ErrorId etc. should be set first. /// - /// Additional information about the error + /// Additional information about the error. /// /// /// @@ -155,17 +136,16 @@ public virtual ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _targetObject); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _targetObject); + return _errorRecord; } } + private ErrorRecord _errorRecord; private string _errorId = "RuntimeException"; private ErrorCategory _errorCategory = ErrorCategory.NotSpecified; @@ -177,7 +157,7 @@ public virtual ErrorRecord ErrorRecord /// to change this before writing to ErrorRecord.ErrorDetails /// or the like. /// - /// per ErrorRecord constructors + /// Per ErrorRecord constructors. internal void SetErrorId(string errorId) { if (_errorId != errorId) @@ -216,74 +196,75 @@ internal void SetErrorCategory(ErrorCategory errorCategory) internal void SetTargetObject(object targetObject) { _targetObject = targetObject; - if (_errorRecord != null) - _errorRecord.SetTargetObject(targetObject); + _errorRecord?.SetTargetObject(targetObject); } #endregion ErrorRecord #region Internal internal static string RetrieveMessage(ErrorRecord errorRecord) { - if (null == errorRecord) - return ""; - if (null != errorRecord.ErrorDetails && - !String.IsNullOrEmpty(errorRecord.ErrorDetails.Message)) + if (errorRecord == null) + return string.Empty; + if (errorRecord.ErrorDetails != null && + !string.IsNullOrEmpty(errorRecord.ErrorDetails.Message)) { return errorRecord.ErrorDetails.Message; } - if (null == errorRecord.Exception) - return ""; + + if (errorRecord.Exception == null) + return string.Empty; return errorRecord.Exception.Message; } internal static string RetrieveMessage(Exception e) { - if (null == e) - return ""; + if (e == null) + return string.Empty; - IContainsErrorRecord icer = e as IContainsErrorRecord; - if (null == icer) + if (e is not IContainsErrorRecord icer) return e.Message; ErrorRecord er = icer.ErrorRecord; - if (null == er) + if (er == null) return e.Message; ErrorDetails ed = er.ErrorDetails; - if (null == ed) + if (ed == null) return e.Message; string detailsMessage = ed.Message; - return (String.IsNullOrEmpty(detailsMessage)) ? e.Message : detailsMessage; + return (string.IsNullOrEmpty(detailsMessage)) ? e.Message : detailsMessage; } internal static Exception RetrieveException(ErrorRecord errorRecord) { - if (null == errorRecord) + if (errorRecord == null) return null; return errorRecord.Exception; } /// - /// /// public bool WasThrownFromThrowStatement { - get { return _thrownByThrowStatement; } + get + { + return _thrownByThrowStatement; + } + set { _thrownByThrowStatement = value; - if (_errorRecord != null) + if (_errorRecord?.Exception is RuntimeException exception) { - RuntimeException exception = _errorRecord.Exception as RuntimeException; - if (exception != null) - { - exception.WasThrownFromThrowStatement = value; - } + exception.WasThrownFromThrowStatement = value; } } } + private bool _thrownByThrowStatement; + internal bool WasRethrown { get; set; } + /// - /// fix for BUG: Windows Out Of Band Releases: 906263 and 906264 + /// 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, @@ -292,23 +273,27 @@ public bool WasThrownFromThrowStatement internal bool SuppressPromptInInterpreter { get { return _suppressPromptInInterpreter; } + set { _suppressPromptInInterpreter = value; } } + private bool _suppressPromptInInterpreter; #endregion Internal private Token _errorToken; + internal Token ErrorToken { get { return _errorToken; } + set { _errorToken = value; } } - } // RuntimeException -} // System.Management.Automation + } +} diff --git a/src/System.Management.Automation/utils/SessionStateExceptions.cs b/src/System.Management.Automation/utils/SessionStateExceptions.cs index f19ff72be24..4121eac17eb 100644 --- a/src/System.Management.Automation/utils/SessionStateExceptions.cs +++ b/src/System.Management.Automation/utils/SessionStateExceptions.cs @@ -1,11 +1,9 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. using System.Collections.ObjectModel; -using System.Runtime.Serialization; using System.Management.Automation.Internal; -using System.Security.Permissions; +using System.Runtime.Serialization; namespace System.Management.Automation { @@ -14,39 +12,36 @@ namespace System.Management.Automation /// callers of the provider APIs to be able to catch a single exception no matter /// what any of the various providers may have thrown. /// - [Serializable] public class ProviderInvocationException : RuntimeException { #region Constructors /// - /// Constructs a ProviderInvocationException + /// Constructs a ProviderInvocationException. /// public ProviderInvocationException() : base() { } /// - /// Constructs a ProviderInvocationException using serialized data + /// Constructs a ProviderInvocationException using serialized data. /// - /// /// /// serialization information /// - /// /// /// streaming context /// + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ProviderInvocationException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } /// - /// Constructs a ProviderInvocationException with a message + /// Constructs a ProviderInvocationException with a message. /// - /// /// /// The message for the exception. /// @@ -59,11 +54,9 @@ public ProviderInvocationException(string message) /// /// Constructs a ProviderInvocationException with provider information and an inner exception. /// - /// /// /// Information about the provider to be used in formatting the message. /// - /// /// /// The inner exception for this exception. /// @@ -73,8 +66,7 @@ internal ProviderInvocationException(ProviderInfo provider, Exception innerExcep _message = base.Message; _providerInfo = provider; - IContainsErrorRecord icer = innerException as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (innerException is IContainsErrorRecord icer && icer.ErrorRecord != null) { _errorRecord = new ErrorRecord(icer.ErrorRecord, innerException); } @@ -92,11 +84,9 @@ internal ProviderInvocationException(ProviderInfo provider, Exception innerExcep /// Constructs a ProviderInvocationException with provider information and an /// ErrorRecord. /// - /// /// /// Information about the provider to be used in formatting the message. /// - /// /// /// Detailed error information /// @@ -104,10 +94,7 @@ internal ProviderInvocationException(ProviderInfo provider, ErrorRecord errorRec : base(RuntimeException.RetrieveMessage(errorRecord), RuntimeException.RetrieveException(errorRecord)) { - if (null == errorRecord) - { - throw new ArgumentNullException("errorRecord"); - } + ArgumentNullException.ThrowIfNull(errorRecord); _message = base.Message; _providerInfo = provider; @@ -118,11 +105,9 @@ internal ProviderInvocationException(ProviderInfo provider, ErrorRecord errorRec /// Constructs a ProviderInvocationException with a message /// and inner exception. /// - /// /// /// The message for the exception. /// - /// /// /// The inner exception for this exception. /// @@ -133,32 +118,26 @@ public ProviderInvocationException(string message, Exception innerException) } /// - /// Constructs a ProviderInvocationException + /// Constructs a ProviderInvocationException. /// - /// /// /// This string will be used to construct the FullyQualifiedErrorId, /// which is a global identifier of the error condition. Pass a /// non-empty string which is specific to this error condition in /// this context. /// - /// /// /// This string is the message template string. /// - /// /// /// The provider information used to format into the message. /// - /// /// /// The path that was being processed when the exception occurred. /// - /// /// /// The exception that was thrown by the provider. /// - /// internal ProviderInvocationException( string errorId, string resourceStr, @@ -170,37 +149,30 @@ internal ProviderInvocationException( } /// - /// Constructor to make it easy to wrap a provider exception + /// Constructor to make it easy to wrap a provider exception. /// - /// /// /// This string will be used to construct the FullyQualifiedErrorId, /// which is a global identifier of the error condition. Pass a /// non-empty string which is specific to this error condition in /// this context. /// - /// /// /// This is the message template string /// - /// /// /// The provider information used to format into the message. /// - /// /// /// The path that was being processed when the exception occurred. /// - /// /// /// The exception that was thrown by the provider. /// - /// /// /// If true, the message from the inner exception will be used if the exception contains /// an ErrorRecord. If false, the error message retrieved using the errorId will be used. /// - /// internal ProviderInvocationException( string errorId, string resourceStr, @@ -226,8 +198,7 @@ internal ProviderInvocationException( errorRecordException = new ParentContainsErrorRecordException(this); } - IContainsErrorRecord icer = innerException as IContainsErrorRecord; - if (null != icer && null != icer.ErrorRecord) + if (innerException is IContainsErrorRecord icer && icer.ErrorRecord != null) { _errorRecord = new ErrorRecord(icer.ErrorRecord, errorRecordException); } @@ -247,6 +218,7 @@ internal ProviderInvocationException( /// Gets the provider information of the provider that threw an exception. /// public ProviderInfo ProviderInfo { get { return _providerInfo; } } + [NonSerialized] internal ProviderInfo _providerInfo; @@ -257,17 +229,16 @@ public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - "ProviderInvocationException", - ErrorCategory.NotSpecified, - null); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + "ProviderInvocationException", + ErrorCategory.NotSpecified, + null); + return _errorRecord; } } + [NonSerialized] private ErrorRecord _errorRecord; #endregion Properties @@ -280,19 +251,21 @@ private static string RetrieveMessage( string path, Exception innerException) { - if (null == innerException) + if (innerException == null) { Diagnostics.Assert(false, "ProviderInvocationException.RetrieveMessage needs innerException"); - return ""; + return string.Empty; } - if (String.IsNullOrEmpty(errorId)) + + if (string.IsNullOrEmpty(errorId)) { Diagnostics.Assert(false, "ProviderInvocationException.RetrieveMessage needs errorId"); return RuntimeException.RetrieveMessage(innerException); } - if (null == provider) + + if (provider == null) { Diagnostics.Assert(false, "ProviderInvocationException.RetrieveMessage needs provider"); @@ -300,18 +273,19 @@ private static string RetrieveMessage( } string format = resourceStr; - if (String.IsNullOrEmpty(format)) + if (string.IsNullOrEmpty(format)) { Diagnostics.Assert(false, "ProviderInvocationException.RetrieveMessage bad errorId " + errorId); return RuntimeException.RetrieveMessage(innerException); } + string result = null; if (path == null) { result = - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, format, provider.Name, @@ -320,31 +294,33 @@ private static string RetrieveMessage( else { result = - String.Format( + string.Format( System.Globalization.CultureInfo.CurrentCulture, format, provider.Name, path, RuntimeException.RetrieveMessage(innerException)); } + return result; } /// - /// Gets the exception message + /// Gets the exception message. /// public override string Message { - get { return (String.IsNullOrEmpty(_message)) ? base.Message : _message; } + get { return (string.IsNullOrEmpty(_message)) ? base.Message : _message; } } + [NonSerialized] - private string _message /* = null */; + private readonly string _message /* = null */; #endregion Private/Internal } /// - /// Categories of session state objects, used by SessionStateException + /// Categories of session state objects, used by SessionStateException. /// public enum SessionStateCategory { @@ -404,23 +380,21 @@ public enum SessionStateCategory /// session state objects: variables, aliases, functions, filters, /// drives, or providers. /// - [Serializable] public class SessionStateException : RuntimeException { #region ctor /// - /// Constructs a SessionStateException + /// Constructs a SessionStateException. /// - /// - /// name of session state object - /// category of session state object + /// Name of session state object. + /// Category of session state object. /// This string is the message template string. /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// ErrorRecord.CategoryInfo.Category + /// ErrorRecord.CategoryInfo.Category. /// /// Additional insertion strings used to construct the message. /// Note that itemName is always the first insertion string. @@ -441,7 +415,7 @@ internal SessionStateException( } /// - /// Constructs a SessionStateException + /// Constructs a SessionStateException. /// public SessionStateException() : base() @@ -449,9 +423,8 @@ public SessionStateException() } /// - /// Constructs a SessionStateException + /// Constructs a SessionStateException. /// - /// /// /// The message used in the exception. /// @@ -461,13 +434,11 @@ public SessionStateException(string message) } /// - /// Constructs a SessionStateException + /// Constructs a SessionStateException. /// - /// /// /// The message used in the exception. /// - /// /// /// The exception that caused the error. /// @@ -477,39 +448,18 @@ public SessionStateException(string message, { } #endregion ctor - - #region Serialization - /// +/// /// Constructs a SessionStateException using serialized data. /// - /// serialization information - /// streaming context + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected SessionStateException(SerializationInfo info, StreamingContext context) - : base(info, context) { - _sessionStateCategory = (SessionStateCategory)info.GetInt32("SessionStateCategory"); // CODEWORK test this + throw new NotSupportedException(); } - /// - /// 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("info"); - } - - base.GetObjectData(info, context); - // If there are simple fields, serialize them with info.AddValue - info.AddValue("SessionStateCategory", (Int32)_sessionStateCategory); - } - #endregion Serialization - #region Properties /// /// Gets the error record information for this exception. @@ -518,17 +468,16 @@ public override ErrorRecord ErrorRecord { get { - if (null == _errorRecord) - { - _errorRecord = new ErrorRecord( - new ParentContainsErrorRecordException(this), - _errorId, - _errorCategory, - _itemName); - } + _errorRecord ??= new ErrorRecord( + new ParentContainsErrorRecordException(this), + _errorId, + _errorCategory, + _itemName); + return _errorRecord; } } + private ErrorRecord _errorRecord; /// @@ -538,7 +487,8 @@ public string ItemName { get { return _itemName; } } - private string _itemName = String.Empty; + + private readonly string _itemName = string.Empty; /// /// Gets the category of session state object the error occurred on. @@ -547,12 +497,13 @@ public SessionStateCategory SessionStateCategory { get { return _sessionStateCategory; } } - private SessionStateCategory _sessionStateCategory = SessionStateCategory.Variable; + + private readonly SessionStateCategory _sessionStateCategory = SessionStateCategory.Variable; #endregion Properties #region Private - private string _errorId = "SessionStateException"; - private ErrorCategory _errorCategory = ErrorCategory.InvalidArgument; + private readonly string _errorId = "SessionStateException"; + private readonly ErrorCategory _errorCategory = ErrorCategory.InvalidArgument; private static string BuildMessage( string itemName, @@ -560,7 +511,7 @@ private static string BuildMessage( params object[] messageArgs) { object[] a; - if (null != messageArgs && 0 < messageArgs.Length) + if (messageArgs != null && messageArgs.Length > 0) { a = new object[messageArgs.Length + 1]; a[0] = itemName; @@ -571,10 +522,11 @@ private static string BuildMessage( a = new object[1]; a[0] = itemName; } + return StringUtil.Format(resourceStr, a); } #endregion Private - } // SessionStateException + } /// /// SessionStateUnauthorizedAccessException occurs when @@ -583,28 +535,23 @@ private static string BuildMessage( /// an object which is declared constant cannot be removed /// or made non-constant. /// - [Serializable] public class SessionStateUnauthorizedAccessException : SessionStateException { #region ctor /// - /// Constructs a SessionStateUnauthorizedAccessException + /// Constructs a SessionStateUnauthorizedAccessException. /// - /// /// /// The name of the session state object the error occurred on. /// - /// /// /// The category of session state object. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in @@ -622,7 +569,20 @@ string resourceStr } /// - /// Constructs a SessionStateUnauthorizedAccessException + /// Constructs a SessionStateUnauthorizedAccessException using serialized data. + /// + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected SessionStateUnauthorizedAccessException( + SerializationInfo info, + StreamingContext context) + { + throw new NotSupportedException(); + } + + /// + /// Constructs a SessionStateUnauthorizedAccessException. /// public SessionStateUnauthorizedAccessException() : base() @@ -630,7 +590,7 @@ public SessionStateUnauthorizedAccessException() } /// - /// Constructs a SessionStateUnauthorizedAccessException + /// Constructs a SessionStateUnauthorizedAccessException. /// /// /// The message used by the exception. @@ -641,12 +601,11 @@ public SessionStateUnauthorizedAccessException(string message) } /// - /// Constructs a SessionStateUnauthorizedAccessException + /// Constructs a SessionStateUnauthorizedAccessException. /// /// /// The message used by the exception. /// - /// /// /// The exception that caused the error. /// @@ -656,53 +615,32 @@ public SessionStateUnauthorizedAccessException(string message, { } #endregion ctor - - #region Serialization - /// - /// Constructs a SessionStateUnauthorizedAccessException using serialized data. - /// - /// - /// serialization information - /// streaming context - protected SessionStateUnauthorizedAccessException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - #endregion Serialization - } // SessionStateUnauthorizedAccessException + } /// /// ProviderNotFoundException occurs when no provider can be found /// with the specified name. /// - [Serializable] public class ProviderNotFoundException : SessionStateException { #region ctor /// - /// Constructs a ProviderNotFoundException + /// Constructs a ProviderNotFoundException. /// - /// /// /// The name of provider that could not be found. /// - /// /// /// The category of session state object /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// /// /// This string is the message template string /// - /// /// /// Additional arguments to build the message from. /// @@ -723,7 +661,7 @@ internal ProviderNotFoundException( } /// - /// Constructs a ProviderNotFoundException + /// Constructs a ProviderNotFoundException. /// public ProviderNotFoundException() : base() @@ -731,7 +669,7 @@ public ProviderNotFoundException() } /// - /// Constructs a ProviderNotFoundException + /// Constructs a ProviderNotFoundException. /// /// /// The messaged used by the exception. @@ -742,7 +680,7 @@ public ProviderNotFoundException(string message) } /// - /// Constructs a ProviderNotFoundException + /// Constructs a ProviderNotFoundException. /// /// /// The message used by the exception. @@ -756,53 +694,33 @@ public ProviderNotFoundException(string message, { } #endregion ctor - - #region Serialization - /// - /// Constructs a ProviderNotFoundException using serialized data. - /// - /// serialization information - /// streaming context - protected ProviderNotFoundException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - #endregion Serialization - } // ProviderNotFoundException + } /// /// ProviderNameAmbiguousException occurs when more than one provider exists /// for a given name and the request did not contain the PSSnapin name qualifier. /// - [Serializable] public class ProviderNameAmbiguousException : ProviderNotFoundException { #region ctor /// - /// Constructs a ProviderNameAmbiguousException + /// Constructs a ProviderNameAmbiguousException. /// - /// /// /// The name of provider that was ambiguous. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// /// /// This string is the message template string /// - /// /// /// The provider information for the providers that match the specified /// name. /// - /// /// /// Additional arguments to build the message from. /// @@ -823,7 +741,7 @@ internal ProviderNameAmbiguousException( } /// - /// Constructs a ProviderNameAmbiguousException + /// Constructs a ProviderNameAmbiguousException. /// public ProviderNameAmbiguousException() : base() @@ -831,7 +749,7 @@ public ProviderNameAmbiguousException() } /// - /// Constructs a ProviderNameAmbiguousException + /// Constructs a ProviderNameAmbiguousException. /// /// /// The messaged used by the exception. @@ -842,7 +760,7 @@ public ProviderNameAmbiguousException(string message) } /// - /// Constructs a ProviderNameAmbiguousException + /// Constructs a ProviderNameAmbiguousException. /// /// /// The message used by the exception. @@ -857,19 +775,18 @@ public ProviderNameAmbiguousException(string message, } #endregion ctor - #region Serialization /// /// Constructs a ProviderNameAmbiguousException using serialized data. /// - /// serialization information - /// streaming context + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ProviderNameAmbiguousException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } - #endregion Serialization #region public properties @@ -884,32 +801,30 @@ public ReadOnlyCollection PossibleMatches return _possibleMatches; } } - private ReadOnlyCollection _possibleMatches; + + private readonly ReadOnlyCollection _possibleMatches; #endregion public properties - } // ProviderNameAmbiguousException + } /// /// DriveNotFoundException occurs when no drive can be found /// with the specified name. /// - [Serializable] public class DriveNotFoundException : SessionStateException { #region ctor /// - /// Constructs a DriveNotFoundException + /// Constructs a DriveNotFoundException. /// /// /// The name of the drive that could not be found. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// /// /// This string is the message template string /// @@ -924,7 +839,7 @@ string resourceStr } /// - /// Constructs a DriveNotFoundException + /// Constructs a DriveNotFoundException. /// public DriveNotFoundException() : base() @@ -932,7 +847,7 @@ public DriveNotFoundException() } /// - /// Constructs a DriveNotFoundException + /// Constructs a DriveNotFoundException. /// /// /// The message that will be used by the exception. @@ -943,12 +858,11 @@ public DriveNotFoundException(string message) } /// - /// Constructs a DriveNotFoundException + /// Constructs a DriveNotFoundException. /// /// /// The message that will be used by the exception. /// - /// /// /// The exception that caused the error. /// @@ -959,42 +873,38 @@ public DriveNotFoundException(string message, } #endregion ctor - #region Serialization /// /// Constructs a DriveNotFoundException using serialized data. /// - /// serialization information - /// streaming context + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected DriveNotFoundException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } - #endregion Serialization - } // DriveNotFoundException + } /// /// ItemNotFoundException occurs when the path contained no wildcard characters /// and an item at that path could not be found. /// - [Serializable] public class ItemNotFoundException : SessionStateException { #region ctor /// - /// Constructs a ItemNotFoundException + /// Constructs a ItemNotFoundException. /// /// /// The path that was not found. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in /// SessionStateStrings.txt. /// - /// /// /// This string is the ErrorId passed to the ErrorRecord, and is also /// the resourceId used to look up the message template string in @@ -1011,7 +921,7 @@ string resourceStr } /// - /// Constructs a ItemNotFoundException + /// Constructs a ItemNotFoundException. /// public ItemNotFoundException() : base() @@ -1019,7 +929,7 @@ public ItemNotFoundException() } /// - /// Constructs a ItemNotFoundException + /// Constructs a ItemNotFoundException. /// /// /// The message used by the exception. @@ -1030,7 +940,7 @@ public ItemNotFoundException(string message) } /// - /// Constructs a ItemNotFoundException + /// Constructs a ItemNotFoundException. /// /// /// The message used by the exception. @@ -1045,19 +955,17 @@ public ItemNotFoundException(string message, } #endregion ctor - #region Serialization /// /// Constructs a ItemNotFoundException using serialized data. /// - /// serialization information - /// streaming context + /// Serialization information. + /// Streaming context. + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ItemNotFoundException( SerializationInfo info, StreamingContext context) - : base(info, context) { + throw new NotSupportedException(); } - #endregion Serialization - } // ItemNotFoundException -} // namespace System.Management.Automation - + } +} diff --git a/src/System.Management.Automation/utils/StringUtil.cs b/src/System.Management.Automation/utils/StringUtil.cs index 685866297d6..8daf31866df 100644 --- a/src/System.Management.Automation/utils/StringUtil.cs +++ b/src/System.Management.Automation/utils/StringUtil.cs @@ -1,46 +1,32 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Collections.Generic; +using System.Globalization; using System.Management.Automation.Host; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; + using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Internal { - internal static - class StringUtil + internal static class StringUtil { - internal static - string - Format(string formatSpec, object o) - { - return String.Format(System.Globalization.CultureInfo.CurrentCulture, formatSpec, o); - } - - - - internal static - string - Format(string formatSpec, object o1, object o2) - { - return String.Format(System.Globalization.CultureInfo.CurrentCulture, formatSpec, o1, o2); - } - - + internal static string Format(string format, object arg0) + => string.Format(CultureInfo.CurrentCulture, format, arg0); - internal static - string - Format(string formatSpec, params object[] o) - { - return String.Format(System.Globalization.CultureInfo.CurrentCulture, formatSpec, o); - } + internal static string Format(string format, object arg0, object arg1) + => string.Format(CultureInfo.CurrentCulture, format, arg0, arg1); + internal static string Format(string format, object arg0, object arg1, object arg2) + => string.Format(CultureInfo.CurrentCulture, format, arg0, arg1, arg2); + internal static string Format(string format, params object[] args) + => string.Format(CultureInfo.CurrentCulture, format, args); - internal static - string - TruncateToBufferCellWidth(PSHostRawUserInterface rawUI, string toTruncate, int maxWidthInBufferCells) + internal static string TruncateToBufferCellWidth(PSHostRawUserInterface rawUI, string toTruncate, int maxWidthInBufferCells) { Dbg.Assert(rawUI != null, "need a reference"); Dbg.Assert(maxWidthInBufferCells >= 0, "maxWidthInBufferCells must be positive"); @@ -48,7 +34,7 @@ internal static string result; int i = Math.Min(toTruncate.Length, maxWidthInBufferCells); - do + while (true) { result = toTruncate.Substring(0, i); int cellCount = rawUI.LengthInBufferCells(result); @@ -65,29 +51,223 @@ internal static // be characters taking more 2 buffer cells --i; } - } while (true); + } return result; } // 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]; + + private static readonly string[] s_indentCache = new string[IndentCacheMax]; + internal static string Padding(int countOfSpaces) { if (countOfSpaces >= IndentCacheMax) return new string(' ', countOfSpaces); - var result = IndentCache[countOfSpaces]; + var result = s_indentCache[countOfSpaces]; if (result == null) { - Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), null); - result = IndentCache[countOfSpaces]; + Interlocked.CompareExchange(ref s_indentCache[countOfSpaces], new string(' ', countOfSpaces), null); + result = s_indentCache[countOfSpaces]; } return result; } - } -} // namespace + private const int DashCacheMax = 120; + + private static readonly string[] s_dashCache = new string[DashCacheMax]; + + internal static string DashPadding(int count) + { + if (count >= DashCacheMax) + return new string('-', count); + + var result = s_dashCache[count]; + + if (result == null) + { + Interlocked.CompareExchange(ref s_dashCache[count], new string('-', count), null); + result = s_dashCache[count]; + } + + return result; + } + + /// + /// Substring implementation that takes into account the VT escape sequences. + /// + /// 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. + /// + /// The requested substring. + internal static string VtSubstring(this string str, int startOffset) + { + return VtSubstring(str, startOffset, int.MaxValue, prependStr: null, appendStr: null); + } + + /// + /// Substring implementation that takes into account the VT escape sequences. + /// + /// 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 non-escape-sequence characters to be included in the substring. + /// The requested substring. + internal static string VtSubstring(this string str, int startOffset, int length) + { + return VtSubstring(str, startOffset, length, prependStr: null, appendStr: null); + } + + /// + /// Substring implementation that takes into account the VT escape sequences. + /// + /// 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. + /// The string to be prepended to the substring. + /// The string to be appended to the substring. + /// The requested substring. + internal static string VtSubstring(this string str, int startOffset, string prependStr, string appendStr) + { + return VtSubstring(str, startOffset, int.MaxValue, prependStr, appendStr); + } + + /// + /// Substring implementation that takes into account the VT escape sequences. + /// + /// 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 non-escape-sequence characters to be included in the substring. + /// The string to be prepended to the substring. + /// The string to be appended to the substring. + /// The requested substring. + internal static string VtSubstring(this string str, int startOffset, int length, string prependStr, string appendStr) + { + var valueStrDec = new ValueStringDecorated(str); + if (valueStrDec.IsDecorated) + { + // Handle strings with VT sequences. + bool copyStarted = startOffset == 0; + bool hasEscSeqs = false; + bool firstNonEscChar = true; + StringBuilder sb = new(capacity: str.Length); + Dictionary vtRanges = valueStrDec.EscapeSequenceRanges; + + for (int i = 0, offset = 0; i < str.Length; i++) + { + // Keep all leading ANSI escape sequences. + if (vtRanges.TryGetValue(i, out int len)) + { + hasEscSeqs = true; + sb.Append(str.AsSpan(i, len)); + + i += len - 1; + continue; + } + + // OK, now we get a non-escape-sequence character. + if (copyStarted) + { + if (firstNonEscChar) + { + // Prepend the string before we copy the first non-escape-sequence character. + sb.Append(prependStr); + firstNonEscChar = false; + } + + // Copy this character if we've started the copy. + sb.Append(str[i]); + + // Increment 'offset' to keep track of number of non-escape-sequence characters we've copied. + offset++; + } + else if (++offset == startOffset) + { + // We've skipped enough non-escape-sequence characters, and will be copying the next one. + copyStarted = true; + + // Reset 'offset' and from now on use it to track the number of copied non-escape-sequence characters. + offset = 0; + continue; + } + + // If the number of copied non-escape-sequence characters has reached the specified length, done copying. + if (copyStarted && offset == length) + { + break; + } + } + + if (hasEscSeqs) + { + string resetStr = PSStyle.Instance.Reset; + bool endsWithReset = sb.EndsWith(resetStr); + if (endsWithReset) + { + // Append the given string before the reset VT sequence. + sb.Insert(sb.Length - resetStr.Length, appendStr); + } + else + { + // Append the given string and add the reset VT sequence. + sb.Append(appendStr).Append(resetStr); + } + } + else + { + sb.Append(appendStr); + } + + return sb.ToString(); + } + + // Handle strings without VT sequences. + if (length == int.MaxValue) + { + length = str.Length - startOffset; + } + + if (prependStr is null && appendStr is null) + { + return str.Substring(startOffset, length); + } + else + { + int capacity = length + prependStr?.Length ?? 0 + appendStr?.Length ?? 0; + return new StringBuilder(prependStr, capacity) + .Append(str, startOffset, length) + .Append(appendStr) + .ToString(); + } + } + + internal static bool EndsWith(this StringBuilder sb, string value) + { + if (sb.Length < value.Length) + { + return false; + } + + int offset = sb.Length - value.Length; + for (int i = 0; i < value.Length; i++) + { + if (sb[offset + i] != value[i]) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/System.Management.Automation/utils/StructuredTraceSource.cs b/src/System.Management.Automation/utils/StructuredTraceSource.cs index 1eb71840bd0..0122e9e26d8 100644 --- a/src/System.Management.Automation/utils/StructuredTraceSource.cs +++ b/src/System.Management.Automation/utils/StructuredTraceSource.cs @@ -1,6 +1,6 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #define TRACE using System.Collections.Generic; @@ -25,7 +25,7 @@ namespace System.Management.Automation public enum PSTraceSourceOptions { /// - /// All tracing off + /// All tracing off. /// /// - - - - - - - - - - - - - - true - - - diff --git a/src/powershell-native/nativemsh/pwrshexe/PowerShell_securitybadge.ico b/src/powershell-native/nativemsh/pwrshexe/PowerShell_securitybadge.ico deleted file mode 100644 index 0aa11972d82..00000000000 Binary files a/src/powershell-native/nativemsh/pwrshexe/PowerShell_securitybadge.ico and /dev/null differ diff --git a/src/powershell-native/nativemsh/pwrshexe/version.rc b/src/powershell-native/nativemsh/pwrshexe/version.rc deleted file mode 100644 index 796fc8c0390..00000000000 --- a/src/powershell-native/nativemsh/pwrshexe/version.rc +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -#include -#include - -#define VER_FILETYPE VFT_APP -#define VER_FILESUBTYPE VFT2_UNKNOWN -#define VER_FILEDESCRIPTION_STR "Windows PowerShell" -#define VER_INTERNALNAME_STR "POWERSHELL" -#define VER_ORIGINALFILENAME_STR "PowerShell.EXE" - -#include diff --git a/src/powershell-native/nativemsh/pwrshplugin/CMakeLists.txt b/src/powershell-native/nativemsh/pwrshplugin/CMakeLists.txt deleted file mode 100644 index 2090d7de2b2..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# Builds pwrshplugin.dll, the WinRM plugin for PowerShell remote hosting. -# -add_library(pwrshplugin SHARED - entrypoints.cpp - pwrshclrhost.cpp - pwrshpluginerrorcodes.mc - #pwrshpluginerrorcodes.rc - pwrshpluginResources.rc - pwrshplugin.def - ) - -if (BUILD_ONECORE) - # Libraries to use when creating this binary for Windows on OneCore-based SKUs - set(PWRSHPLUGIN_WINDOWS_LIBS - ${STATIC_MT_CRT_LIB} - ${STATIC_MT_VCRT_LIB} - kernel32.lib - advapi32.lib - ole32.lib - wsmsvc.lib - ) - set_target_properties(pwrshplugin PROPERTIES COMPILE_DEFINITIONS "CORECLR") -else () - # Libraries to use when creating this binary for Windows on full SKUs - set(PWRSHPLUGIN_WINDOWS_LIBS - MUILoad.lib - mscoree.lib - wsmsvc.lib - # CoreCLR lib - ${STATIC_MT_CRT_LIB} - ${STATIC_MT_VCRT_LIB} - legacy_stdio_definitions.lib # Resolves: LNK2019: unresolved external symbol _vsnwprintf - ) - set(pwrshplugin_definitions ${common_pwrsh_definitions}) -endif (BUILD_ONECORE) - -target_link_libraries(pwrshplugin - ${PWRSHPLUGIN_WINDOWS_LIBS}) - -target_link_libraries(pwrshplugin pwrshcommon) diff --git a/src/powershell-native/nativemsh/pwrshplugin/entrypoints.cpp b/src/powershell-native/nativemsh/pwrshplugin/entrypoints.cpp deleted file mode 100644 index 74355bb6af4..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/entrypoints.cpp +++ /dev/null @@ -1,776 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Entry points for PowerShell plugin used to host powershell -// in a WSMan service. -// ---------------------------------------------------------------------- - -#include "pwrshplugin.h" -// [Porting note] SQM is for Telemetry in Windows. Temporarily disabled. -//#include -//#include "common/WindowsSqmDataID.h" -//#include "common/winrmsqm.h" - -#if !CORECLR -#include -#endif - -HINSTANCE g_hResourceInstance = 0; // TODO: Where is this freed? FreeMUILibrary for nonCoreClr and FreeLibrary for CoreCLR -LPCWSTR g_MAIN_BINARY_NAME = L"pwrshplugin.dll"; - -// [Porting note] SQM is for Telemetry in Windows. Temporarily disabled. -// typedef VOID (NTAPI *PFN_WinSqmSetDWORD)( -// __in_opt HSESSION hSession, -// __in DWORD dwDatapointId, -// __in DWORD dwDatapointValue -// ); - -// gets the error message from the resources section of the current module. -// the caller should free pwszErrorMessage using LocalFree(). -// returns: If the function succeeds the return value is the number of CHARs stored int the output -// buffer, excluding the terminating null character. If the function fails the return value is zero. -_Success_(return > 0) -DWORD GetFormattedErrorMessage(__out PWSTR * pwszErrorMessage, DWORD dwMessageId, va_list* arguments) -{ - DWORD dwLength = 0; - LPWSTR wszSystemErrorMessage = NULL; - - do - { - if (NULL == pwszErrorMessage) - { - break; - } - *pwszErrorMessage = NULL; - - if (NULL == g_hResourceInstance) - { -#ifdef CORECLR - g_hResourceInstance = LoadLibraryEx(g_MAIN_BINARY_NAME, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); -#else - g_hResourceInstance = LoadMUILibraryW(g_MAIN_BINARY_NAME, MUI_LANGUAGE_NAME, 0); -#endif - } - - dwLength = FormatMessageW( - FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER, - g_hResourceInstance, - dwMessageId, - 0, - (LPWSTR)&wszSystemErrorMessage, - 0, - arguments); - - if (dwLength == 0) - { - break; - } - - *pwszErrorMessage = new WCHAR[dwLength + 1]; - if (*pwszErrorMessage == NULL) - { - dwLength = 0; - break; - } - - if (FAILED(StringCchCopyW(*pwszErrorMessage, dwLength + 1, wszSystemErrorMessage))) - { - dwLength = 0; - delete [] (*pwszErrorMessage); - *pwszErrorMessage = NULL; - } - - }while(false); - - if (NULL != wszSystemErrorMessage) - { - LocalFree(wszSystemErrorMessage); - } - - return dwLength; -} - -DWORD GetFormattedErrorMessage(__out PWSTR * pwszErrorMessage, DWORD dwMessageId, ...) -{ - DWORD result = 0; - - va_list args; - va_start(args, dwMessageId); - - result = GetFormattedErrorMessage(pwszErrorMessage, dwMessageId, &args); - - va_end(args); - - return result; -} - -#pragma prefast(push) -#pragma prefast (disable: 6101) -#pragma prefast (disable: 6054) - -unsigned int ConstructPowerShellVersion(int iPSMajorVersion, - int iPSMinorVersion, - __deref_out_opt PWSTR *pwszMonadVersion) -{ - unsigned int exitCode = EXIT_CODE_SUCCESS; - wchar_t* wszMajorVersion = new wchar_t[10]; - wchar_t* wszMinorVersion = new wchar_t[10]; - - do - { - if ((NULL == pwszMonadVersion) || ( 0 > iPSMajorVersion) || (0 > iPSMinorVersion)) - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - - if (0 != _itow_s(iPSMajorVersion, wszMajorVersion, 10, 10)) - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - - if (0 != _itow_s(iPSMinorVersion, wszMinorVersion, 10, 10)) - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - - size_t iMajorLength; - size_t iMinorLength; - - if (SUCCEEDED(StringCchLength(wszMajorVersion, 10, &iMajorLength)) && - SUCCEEDED(StringCchLength(wszMinorVersion, 10, &iMinorLength))) - { - size_t totalLength = iMajorLength + iMinorLength + 2; - *pwszMonadVersion = new wchar_t[totalLength]; - if (NULL == *pwszMonadVersion) - { - exitCode = ERROR_NOT_ENOUGH_MEMORY; - break; - } - - *pwszMonadVersion[0] = L'\0'; - if (SUCCEEDED(StringCchCopyW(*pwszMonadVersion, totalLength, wszMajorVersion)) && - SUCCEEDED(StringCchCatW(*pwszMonadVersion, totalLength, L".")) && - SUCCEEDED(StringCchCatW(*pwszMonadVersion, totalLength, wszMinorVersion))) - { - break; - } - else - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - } - else - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - }while(false); - - if (NULL != wszMajorVersion) - { - delete[] wszMajorVersion; - } - - if (NULL != wszMinorVersion) - { - delete[] wszMinorVersion; - } - - return exitCode; -} - -#pragma prefast(pop) - -static PwrshCommon sPwrshCommon; - -// Gets the CLR Version for a given PowerShell Version. PowerShell Version is -// supplied with 2 parameters iPSMajorVersion (PowerShell major version) and -// iPSMinorVersion (PowerShell minor version). The CLR version is returned through -// pwszRuntimeVersion and pRuntimeVersionLength represents the size of pwszRuntimeVersion. -// returns: 0 on success, non-zero on failure. -_Success_(return == 0) //EXIT_CODE_SUCCESS -extern "C" -unsigned int GetCLRVersionForPSVersion(int iPSMajorVersion, - int iPSMinorVersion, - size_t runtimeVersionLength, - __inout_ecount_part(runtimeVersionLength, *pRuntimeVersionLength) wchar_t* pwszRuntimeVersion, - __out_ecount(1) size_t* pRuntimeVersionLength) -{ - unsigned int exitCode = EXIT_CODE_SUCCESS; - wchar_t * wszMonadVersion = NULL; - wchar_t* wszConsoleHostAssemblyName = NULL; - wchar_t* wszTempVersion = NULL; - size_t tempVersionLength = 0; - - if (NULL != pRuntimeVersionLength) - { - // Initialize the output size to zero prior to attempting the copy - *pRuntimeVersionLength = 0; - } - - do - { - int requestedMonadMajorVersion = iPSMajorVersion; - int requestedMonadMinorVersion = iPSMinorVersion; - - // For GetRegistryInfo call, monadMajorVersion is used to calculate the version key in registry. - // For PowerShell V2, version key in registry is 1. - if (2 == requestedMonadMajorVersion) - { - requestedMonadMajorVersion = 1; - } - - // For PowerShell 3, 4 and 5, the registry is 3. - if ((requestedMonadMajorVersion == 4) || (requestedMonadMajorVersion == 5)) - { - requestedMonadMajorVersion = 3; - } - - exitCode = ConstructPowerShellVersion(iPSMajorVersion, iPSMinorVersion, &wszMonadVersion); - if (EXIT_CODE_SUCCESS != exitCode) - { - break; - } - - exitCode = sPwrshCommon.GetRegistryInfo( - &wszMonadVersion, - &requestedMonadMajorVersion, - requestedMonadMinorVersion, - &wszTempVersion, - &wszConsoleHostAssemblyName); - - if (EXIT_CODE_SUCCESS != exitCode) - { - break; - } - - HRESULT hResult = StringCchLength(wszTempVersion, STRSAFE_MAX_CCH, &tempVersionLength); - - if (FAILED(hResult)) - { - exitCode = EXIT_CODE_READ_REGISTRY_FAILURE; - break; - } - - if (NULL != pwszRuntimeVersion) - { - // +1 for the '\0' - if (runtimeVersionLength < (tempVersionLength + 1)) - { - exitCode = EXIT_CODE_BAD_INPUT; - break; - } - - hResult = StringCchCopy(pwszRuntimeVersion, tempVersionLength + 1, wszTempVersion); - - if (FAILED(hResult)) - { - exitCode = EXIT_CODE_READ_REGISTRY_FAILURE; - break; - } - - if (NULL != pRuntimeVersionLength) - { - // OACR warning 26030: Postcondition violation that could result in overflow - // pRuntimeVersionLength should only be populated if the copy operation succeeded - // +1 for the '\0' - *pRuntimeVersionLength = tempVersionLength + 1; - } - } - }while(false); - - if (NULL != wszMonadVersion) - { - delete [] wszMonadVersion; - wszMonadVersion = NULL; - } - - if (NULL != wszTempVersion) - { - delete [] wszTempVersion; - wszTempVersion = NULL; - } - - if (NULL != wszConsoleHostAssemblyName) - { - delete [] wszConsoleHostAssemblyName; - wszConsoleHostAssemblyName = NULL; - } - - return exitCode; -} - -DWORD ReportOperationComplete(WSMAN_PLUGIN_REQUEST *requestDetails, DWORD errorCode) -{ - if (NULL == requestDetails) - { - // cannot report if requestDetails is NULL. - return EXIT_CODE_SUCCESS; - } - - DWORD result = EXIT_CODE_SUCCESS; - PWSTR pwszErrorMessage = NULL; - if (GetFormattedErrorMessage(&pwszErrorMessage, errorCode) == 0) - { - /* if an error message could not be loaded, generate a fallback - message to provide a level of diagnosability. - */ - WCHAR szFallbackMessageMessage[64] = L"\0"; - swprintf_s(szFallbackMessageMessage, _countof(szFallbackMessageMessage), L"ReportOperationComplete: %d", errorCode); - - result = WSManPluginOperationComplete(requestDetails, 0, errorCode, szFallbackMessageMessage); - } - else - { - result = WSManPluginOperationComplete(requestDetails, 0, errorCode, pwszErrorMessage); - delete[] pwszErrorMessage; - } - - return result; -} - -// ----------------------------------------------------------------------------- -// Each plug-in needs to support the Startup callback. A plug-in may be -// initialized more than once within the same process, but only once per -// applicationIdentification. -// ----------------------------------------------------------------------------- -extern "C" -DWORD WINAPI WSManPluginStartup( - __in DWORD flags, - __in PCWSTR applicationIdentification, - __in_opt PCWSTR extraInfo, - __out PVOID *pluginContext - ) -{ -// -#ifdef REMOTINGDEBUG - // This loop is added to assist debugging server. - // Attach a debugger to the server and set this variable to true - // from the debugger. - bool isDebuggerAttached = false; - do - { - Sleep(1); - }while(!isDebuggerAttached); -#endif - - PwrshPlugIn* result = NULL; - try - { - *pluginContext = NULL; - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(extraInfo); - - PwrshPlugIn* result = pluginMediator->CreatePwrshPlugIn(applicationIdentification, extraInfo); - *pluginContext = (PVOID)result; - - // Using global SQM session - // WinSQMSetDWORD is not available on Vista - - // [Porting note] SQM is for Telemetry in Windows. Temporarily disabled. - // HMODULE hModule; - // PFN_WinSqmSetDWORD pfnWinSqmSetDWORD; - - // hModule = GetModuleHandleW(L"ntdll"); - // if (hModule) - // { - // pfnWinSqmSetDWORD = (PFN_WinSqmSetDWORD) GetProcAddress(hModule, "WinSqmSetDWORD"); - // if (pfnWinSqmSetDWORD) - // { - // pfnWinSqmSetDWORD( - // NULL, - // DATAID_WINRMREMOTEENABLED, - // WINRM_SQM_DATA_REMOTEENABLED - // ); - // } - // } - - return NO_ERROR; - } - catch(PlugInException* e) - { - DWORD errorId = g_CREATION_FAILED; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - if (NULL != result) - { - delete result; - } - - return errorId; - } -} - -// ------------------------------------------------------------------------------------ -//The WSManPluginShutdown method is called after all operations have been cancelled and -//right before the DLL is unloaded. The DLL entry point name must be WSManPluginShutdown. -//This method has an important purpose of making sure all plug-in threads are shut down -//before this method returns. If the plug-in only handles synchronous operations and all -//threads report a cancellation result before they return then this method does not have to -//do anything too complex other than plug-in cleanup. However for an asynchronous plug-in, -//any threads that are used to process the plug-in threads, including the ones that just reported -//the cancellation for all operations need to completely shutdown. Not doing this will cause -//potential crashes in the DLL because code may be executed after the DLL is unloaded. -// ------------------------------------------------------------------------------------ -// reason: If this is a system shutdown this will be WSMAN_PLUGIN_SHUTDOWN_SYSTEM. -// For WSMan service shutdown this will be WSMAN_PLUGIN_SHUTDOWN_SERVICE. For an IIS host -//shutdown this will be WSMAN_PLUGIN_SHUTDOWN_IISHOST. -extern "C" -DWORD WINAPI WSManPluginShutdown( - __in PVOID pluginContext, - __in DWORD flags, - __in DWORD reason - ) -{ - if (NULL == pluginContext) - { - return g_NULL_PLUGIN_CONTEXT; - } - - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->Shutdown(flags, reason); - } - catch(PlugInException* e) - { - // ignore plugin exceptions during shutdown. - if (NULL != e) - { - delete e; - } - } - - // free resources occupied by this plugin.. - // WSMan frees shell/command resources before calling - // plugin shutdown. - PwrshPlugIn* plugIn = reinterpret_cast(pluginContext); - delete plugIn; - - return NO_ERROR; -} - -#ifndef WIN32_FROM_HRESULT -#define WIN32_FROM_HRESULT(hr) (HRESULT_FACILITY(hr) == FACILITY_WIN32 ? HRESULT_CODE(hr) : hr) -#endif - -// ----------------------------------------------------------------------------- -// A plug-in that supports the Shell operations needs to implement this callback -// to allow commands to be created and to allow data to be streamed into either -// a shell or command. The plug-in must call WSManPluginReportContext to -// report the shell context. Once the shell is completed or when it is closed -// via the operationClosed boolean value or operationClosedHandle in the -// requestDetails the plug-in needs to call WSManPluginOperationComplete. -// The shell is active until this time. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginShell( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in_opt WSMAN_SHELL_STARTUP_INFO *startupInfo, - __in_opt WSMAN_DATA *inboundShellInformation - ) -{ - PwrshPlugIn* plugIn = (NULL != pluginContext) ? (PwrshPlugIn*)pluginContext : NULL; - bool comInitialized = false; - - if (NULL == plugIn) - { - ReportOperationComplete(requestDetails, g_INVALID_PLUGIN_CONTEXT); - return; - } - - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - - HRESULT hr = CoInitializeEx(0,COINIT_MULTITHREADED); - if (hr == S_OK) - { - comInitialized = true; - } - else if (hr == S_FALSE) - { - CoUninitialize(); - comInitialized = false; - } - else if (hr == RPC_E_CHANGED_MODE) - { - comInitialized = false; //ignore - } - else - { - ReportOperationComplete(requestDetails, WIN32_FROM_HRESULT(hr)); - return; - } - - pluginMediator->CreateShell(plugIn, requestDetails, flags, startupInfo, inboundShellInformation); - - if (comInitialized) - { - CoUninitialize(); - comInitialized = false; - } - } - catch(PlugInException* e) - { - DWORD errorId = g_CREATION_FAILED; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - if (comInitialized) - { - CoUninitialize(); - comInitialized = false; - } - - ReportOperationComplete(requestDetails, errorId); - } -} - -// ----------------------------------------------------------------------------- -// WS-Man calls the WSMAN_PLUGIN_RELEASE_SHELL_CONTEXT entry point during shell -// shutdown when it is safe to delete the plug-in shell context. Any context -// reported through WSManPluginReportContext may not be deleted until the -// corresponding release function has been called. Failure to follow the contract -// will result in errors being generated. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginReleaseShellContext(__in PVOID shellContext) -{ - if ((NULL == shellContext)) - { - return; - } - - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->ReleaseShell(shellContext); - } - catch(PlugInException* e) - { - // ignore plugin exceptions. - if (NULL != e) - { - delete e; - } - } -} - -// -// ----------------------------------------------------------------------------- -// A plug-in that supports the Shell operations and needs to create commands -// that are associated with the shell needs to implement this callback. -// The plug-in must call WSManPluginReportContext to -// report the command context. Once the command is completed or when it is closed -// via the operationClosed boolean value or operationClosedHandle in the -// requestDetails the plug-in needs to call WSManPluginOperationComplete. -// The command is active until this time. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginCommand( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in PCWSTR commandLine, - __in_opt WSMAN_COMMAND_ARG_SET *arguments -) -{ - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->CreateCommand(requestDetails, flags, shellContext, commandLine, arguments); - } - catch(PlugInException* e) - { - DWORD errorId = g_CREATION_FAILED; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - ReportOperationComplete(requestDetails, errorId); - } -} - -// --------------------------------------------------------------------------------- -// WS-Man calls the WSMAN_PLUGIN_RELEASE_COMMAND_CONTEXT entry point during command -// shutdown when it is safe to delete the plug-in shell context. Any context -// reported through WSManPluginReportContext may not be deleted until the -// corresponding release function has been called. Failure to follow the contract -// will result in errors being generated. -// --------------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginReleaseCommandContext( - __in PVOID shellContext, - __in PVOID commandContext - ) -{ - if ((NULL == shellContext) || (NULL == commandContext)) - { - return; - } - - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->ReleaseCommand(shellContext, commandContext); - } - catch(PlugInException* e) - { - // ignore plugin exceptions. - if (NULL != e) - { - delete e; - } - } -} - -// ----------------------------------------------------------------------------- -// A plug-in receives an inbound data stream to either the shell or command -// via this callback. Each piece of data causes the callback to be called once. -// For each piece of data the plug-in calls WSManPluginResultComplete to -// acknowledge receipt and to allow the next piece of data to be delivered. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginSend( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR stream, - __in WSMAN_DATA *inboundData - ) -{ - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->SendOneItemToShellOrCommand(requestDetails, flags, shellContext, commandContext, stream, inboundData); - } - catch(PlugInException* e) - { - DWORD errorId = g_INVALID_PLUGIN_CONTEXT; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - ReportOperationComplete(requestDetails, errorId); - } -} - -// ----------------------------------------------------------------------------- -// A plug-in sends an outbound data stream from either the shell or command -// via this callback. This API is called when an inbound request from a client -// is received. This callback may be called against the shell and/or command -// based on the client request. Each piece of data that needs to be sent back -// to the client is done so through the WSManPluginReceiveResult API. Once -// all data has been send, when the stream is terminated via some internal means, -// or if the receive call is cancelled through the operationClosed boolean -// value or operationClosedHandle, the plug-in needs to call -// WSManPluginResultComplete. The operation is marked as active until this -// time. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginReceive( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_STREAM_ID_SET *streamSet - ) -{ - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->EnableShellOrCommandToSendDataToClient(requestDetails, flags, shellContext, commandContext, streamSet); - } - catch(PlugInException* e) - { - DWORD errorId = g_INVALID_PLUGIN_CONTEXT; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - ReportOperationComplete(requestDetails, errorId); - } -} - -// ----------------------------------------------------------------------------- -// A plug-in receives an inbound signal to either the shell or command -// via this callback. Each signal causes the callback to be called once. -// For each callthe plug-in calls WSManPluginResultComplete to -// acknowledge receipt and to allow the next signal to be received. -// A signal can cause the shell or command to be terminated, so the result -// of this callback may be many completion calls for the Signal, Receive, Command -// and Shell operations. -// ----------------------------------------------------------------------------- -extern "C" -VOID WINAPI WSManPluginSignal( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR code) -{ - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->SignalShellOrCmd(requestDetails, flags, shellContext, commandContext, code); - } - catch(PlugInException* e) - { - DWORD errorId = g_INVALID_PLUGIN_CONTEXT; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - ReportOperationComplete(requestDetails, errorId); - } -} - -extern "C" -VOID WINAPI WSManPluginConnect( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_DATA *inboundConnectInformation) -{ - try - { - PwrshPlugInMediator* pluginMediator = PwrshPlugInMediator::GetPwrshPlugInMediator(NULL); - pluginMediator->ExecuteConnectToShellOrCommand(requestDetails, flags, shellContext, commandContext, inboundConnectInformation); - } - catch(PlugInException* e) - { - DWORD errorId = g_INVALID_PLUGIN_CONTEXT; - if (NULL != e) - { - errorId = e->dwMessageId; - delete e; - } - - ReportOperationComplete(requestDetails, errorId); - } -} diff --git a/src/powershell-native/nativemsh/pwrshplugin/entrypoints.h b/src/powershell-native/nativemsh/pwrshplugin/entrypoints.h deleted file mode 100644 index c6105729be9..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/entrypoints.h +++ /dev/null @@ -1,18 +0,0 @@ -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Headers used by pwrshplugin. -// pwrshplugin is totally unmanaged. -// ---------------------------------------------------------------------- - -#pragma once - -#include -#include -#include "pwrshpluginerrorcodes.h" - -const int EXIT_CODE_BAD_INPUT = 100; - -extern DWORD GetFormattedErrorMessage(__out PWSTR * pwszErrorMessage, DWORD dwMessageId, ...); -extern unsigned int ConstructPowerShellVersion(int iPSMajorVersion, int iPSMinorVersion, __deref_out_opt PWSTR *pwszMonadVersion); - diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.cpp b/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.cpp deleted file mode 100644 index edae970c290..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.cpp +++ /dev/null @@ -1,447 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Source code for abstraction of CLR and worker differences between PowerShell versions. -// pwrshplugin is totally unmanaged. -// ---------------------------------------------------------------------- - -#pragma once - -#include "pwrshclrhost.h" -#include "NativeMsh.h" -#include "entrypoints.h" -#include "WinSystemCallFacade.h" - -#if !CORECLR - -// include the tlb for mscorlib for access to the default AppDomain through COM Interop -#import raw_interfaces_only high_property_prefixes("_get","_put","_putref")\ - rename("ReportEvent", "CLRReportEvent") - -using namespace mscorlib; - -#endif - -using namespace NativeMsh; - -// Init function calls are handled internally here within the context of the mediator singleton -typedef void (WINAPI *InitPluginFuncPtr)(); // Original PS init -typedef DWORD (WINAPI *InitPluginWkrPtrsFuncPtr)(__out PwrshPluginWkr_Ptrs* wkrPtrs); // Updated PS init - -#ifdef CORECLR - -unsigned int PowerShellCoreClrWorker::LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName) -{ - return commonLib->LaunchCoreCLR(hostWrapper, hostEnvironment, friendlyName); -} - -unsigned int PowerShellCoreClrWorker::LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ wchar_t* wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException) -{ - - *pPluginException = NULL; - - // Call into powershell entry point - InitPluginWkrPtrsFuncPtr entryPointDelegate = NULL; - - // Create the function pointer for the managed entry point - // It must be targeted at a static method in the managed code. - HRESULT hr = hostWrapper->CreateDelegate( - "System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "System.Management.Automation.Remoting.WSManPluginManagedEntryWrapper", - "InitPlugin", - (void**)&entryPointDelegate); - - if (FAILED(hr)) - { - output->DisplayMessage(false, g_CREATING_MSH_ENTRANCE_FAILED, hr); - - return EXIT_CODE_INIT_FAILURE; - } - - // Passes empty pointer structure to the function, expects a filled struct if successful - return entryPointDelegate(workerCallbackPtrs); -} - -PowerShellCoreClrWorker::PowerShellCoreClrWorker() - : systemCalls(new WinSystemCallFacade()), - hostWrapper(new CoreClrHostingApiWrapper()), - output(new PwrshPluginOutputDefault()), - commonLib(new PwrshCommon()) -{ -} - -// -// sysCalls is expected to be new'd by the caller. -// It will be freed in PowerShellCoreClrWorker's destructor. -// -PowerShellCoreClrWorker::PowerShellCoreClrWorker( - SystemCallFacade* sysCalls, - ClrHostWrapper* hstWrp, - PwrshCommon* cmnLib) - : systemCalls(sysCalls), - hostWrapper(hstWrp), - output(new PwrshPluginOutputDefault()), - commonLib(cmnLib) -{ - if (NULL == systemCalls) - { - // Instantiate it even if one is not provided to guarantee that it will - // always be non-NULL during execution. - systemCalls = new WinSystemCallFacade(); - } - - if (NULL == hostWrapper) - { - // Instantiate it even if one is not provided to guarantee that it will - // always be non-NULL during execution. - hostWrapper = new CoreClrHostingApiWrapper(); - } - - if (NULL == commonLib) - { - commonLib = new PwrshCommon(new PwrshPluginOutputDefault(), new ConfigFileReader(), new WinSystemCallFacade()); - } -} - -PowerShellCoreClrWorker::~PowerShellCoreClrWorker() -{ - unsigned int exitCode = hostWrapper->CleanUpHostWrapper(); - if (EXIT_CODE_SUCCESS != exitCode) - { - output->DisplayMessage(false, exitCode); - } - - if (systemCalls) - { - delete systemCalls; - systemCalls = NULL; - } - - if (hostWrapper) - { - delete hostWrapper; - hostWrapper = NULL; - } - - if (output) - { - delete output; - output = NULL; - } - - if (commonLib) - { - delete commonLib; - commonLib = NULL; - } -} - -#else // !CORECLR - -PowerShellClrWorker::PowerShellClrWorker() : - pHost(NULL), - hManagedPluginModule(NULL), - systemCalls(new WinSystemCallFacade()), - g_INIT_PLUGIN("InitPlugin"), - g_SHUTDOWN_PLUGIN("ShutdownPlugin"), - g_MANAGED_PLUGIN_CREATE_SHELL("WSManPluginShell"), - g_MANAGED_PLUGIN_RELEASE_SHELL("WSManPluginReleaseShellContext"), - g_MANAGED_PLUGIN_CREATE_COMMAND("WSManPluginCommand"), - g_MANAGED_PLUGIN_RELEASE_COMMAND("WSManPluginReleaseCommandContext"), - g_MANAGED_PLUGIN_SEND("WSManPluginSend"), - g_MANAGED_PLUGIN_RECEIVE("WSManPluginReceive"), - g_MANAGED_PLUGIN_SIGNAL("WSManPluginSignal"), - g_MANAGED_PLUGIN_CONNECT("WSManPluginConnect"), - output(new PwrshPluginOutputDefault()) -{} - -PowerShellClrWorker::PowerShellClrWorker( - SystemCallFacade* sysCalls) - : pHost(NULL), - hManagedPluginModule(NULL), - systemCalls(sysCalls), - g_INIT_PLUGIN("InitPlugin"), - g_SHUTDOWN_PLUGIN("ShutdownPlugin"), - g_MANAGED_PLUGIN_CREATE_SHELL("WSManPluginShell"), - g_MANAGED_PLUGIN_RELEASE_SHELL("WSManPluginReleaseShellContext"), - g_MANAGED_PLUGIN_CREATE_COMMAND("WSManPluginCommand"), - g_MANAGED_PLUGIN_RELEASE_COMMAND("WSManPluginReleaseCommandContext"), - g_MANAGED_PLUGIN_SEND("WSManPluginSend"), - g_MANAGED_PLUGIN_RECEIVE("WSManPluginReceive"), - g_MANAGED_PLUGIN_SIGNAL("WSManPluginSignal"), - g_MANAGED_PLUGIN_CONNECT("WSManPluginConnect"), - output(new PwrshPluginOutputDefault()) -{ - if (NULL == systemCalls) - { - systemCalls = new WinSystemCallFacade(); - } -} - -PowerShellClrWorker::~PowerShellClrWorker() -{ - if (NULL != hManagedPluginModule) - { - FreeLibrary(hManagedPluginModule); - hManagedPluginModule = NULL; - } - - if (output) - { - delete output; - output = NULL; - } - - if (systemCalls) - { - delete systemCalls; - systemCalls = NULL; - } -} - -unsigned int PowerShellClrWorker::LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName) -{ - return commonLib.LaunchCLR(wszMonadVersion, wszRuntimeVersion, &pHost); -} - -unsigned int PowerShellClrWorker::LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ wchar_t* wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException) -{ - *pPluginException = NULL; -#pragma prefast(push) -#pragma prefast (disable: 28752) - if (wszMgdPlugInFileName) - { - hManagedPluginModule = systemCalls->LoadLibraryExW(wszMgdPlugInFileName, NULL, 0); - } -#pragma prefast(pop) - - unsigned int exitCode = EXIT_CODE_SUCCESS; - - if (NULL == hManagedPluginModule) - { - // Get Extended error information.. to know why load failed. - PWSTR msg = NULL; - exitCode = g_MANAGED_PLUGIN_LOAD_FAILED; - commonLib.GetSystemErrorMessage(GetLastError(), &msg); - *pPluginException = new PlugInException(exitCode, msg); - return exitCode; - } - - // Now get the proc address for all the plugin API. - // Init is handled differently because it is not used in the updated - // worker module. - InitPluginFuncPtr hInitPluginMethodAddress = (InitPluginFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_INIT_PLUGIN); - - // This is not strictly necessary (they can be directly assigned), but I am doing this - // to keep all the code paths common - workerCallbackPtrs->shutdownPluginFuncPtr = (ShutdownPluginFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_SHUTDOWN_PLUGIN); - workerCallbackPtrs->wsManPluginShellFuncPtr = (WSManPluginShellFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_CREATE_SHELL); - workerCallbackPtrs->wsManPluginReleaseShellContextFuncPtr = (WSManPluginReleaseShellContextFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_RELEASE_SHELL); - workerCallbackPtrs->wsManPluginCommandFuncPtr = (WSManPluginCommandFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_CREATE_COMMAND); - workerCallbackPtrs->wsManPluginReleaseCommandContextFuncPtr = (WSManPluginReleaseCommandContextFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_RELEASE_COMMAND); - workerCallbackPtrs->wsManPluginSendFuncPtr = (WSManPluginSendFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_SEND); - workerCallbackPtrs->wsManPluginReceiveFuncPtr = (WSManPluginReceiveFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_RECEIVE); - workerCallbackPtrs->wsManPluginSignalFuncPtr = (WSManPluginSignalFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_SIGNAL); - workerCallbackPtrs->wsManPluginConnectFuncPtr = (WSManPluginConnectFuncPtr)systemCalls->GetProcAddress(hManagedPluginModule, g_MANAGED_PLUGIN_CONNECT); - - // Initialize the loaded module (pspluginwkr.dll) - if (hInitPluginMethodAddress) - { - // Only the "old" version of the worker requires a stand-alone init call. - // This object is a member of the PwrshPlugInMediator singleton, so this - // call is made from that context. - (hInitPluginMethodAddress)(); - } - else - { - PWSTR msg = NULL; - exitCode = g_MANAGED_METHOD_RESOLUTION_FAILED; - /* NOTE: don't use a string literal for a fallback; PlugInException expects msg to allocated - and will free it but also supports NULL. - */ - (void) GetFormattedErrorMessage(&msg, exitCode); - *pPluginException = new PlugInException(exitCode, msg); - return exitCode; - } - - return exitCode; -} - -unsigned int PowerShellClrManagedWorker::LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName) -{ - return commonLib.LaunchCLR(wszMonadVersion, wszRuntimeVersion, &pHost); -} - -unsigned int PowerShellClrManagedWorker::LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ wchar_t* wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException) -{ - unsigned int exitCode = EXIT_CODE_SUCCESS; - HRESULT hr = S_OK; - *pPluginException = NULL; - - do - { - // Get a pointer to the default AppDomain - CComPtr<_AppDomain> spDefaultDomain = NULL; - CComPtr spAppDomainPunk = NULL; - - hr = pHost->GetDefaultDomain(&spAppDomainPunk); - if (FAILED(hr) || spAppDomainPunk == NULL) - { - output->DisplayMessage(false, g_GETTING_DEFAULT_DOMAIN_FAILED, hr); - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - hr = spAppDomainPunk->QueryInterface(__uuidof(_AppDomain), (PVOID*)&spDefaultDomain); - if (FAILED(hr) || spDefaultDomain == NULL) - { - output->DisplayMessage(false, g_GETTING_DEFAULT_DOMAIN_FAILED, hr); - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - CComPtr<_ObjectHandle> spObjectHandle; - - // use CreateInstance because we use the assembly strong name (as opposed to CreateInstanceFrom) - // - // wszMgdPlugInFileName is system.management.automation.dll within the powershell install path. - // For inbox PowerShell, this is %systemdir%\Windows\System32\WindowsPowerShell\v1.0\system.management.automation.dll (Aka $PSHOME\system.management.automation.dll) - _bstr_t bstrConsoleHostAssemblyName = _bstr_t(L"System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); - _bstr_t bstrUnmanagedMshEntryClass = _bstr_t(L"System.Management.Automation.Remoting.WSManPluginManagedEntryInstanceWrapper"); - - hr = spDefaultDomain->CreateInstance( - bstrConsoleHostAssemblyName, - bstrUnmanagedMshEntryClass, - &spObjectHandle); - if (FAILED(hr) || spObjectHandle == NULL) - { - output->DisplayMessage(false, g_CREATING_MSH_ENTRANCE_FAILED, hr); - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - CComVariant VntUnwrapped; - hr = spObjectHandle->Unwrap(&VntUnwrapped); - if (FAILED(hr)) - { - output->DisplayMessage(false, g_CREATING_MSH_ENTRANCE_FAILED, hr); - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - CComPtr pDisp; - pDisp = VntUnwrapped.pdispVal; - - OLECHAR FAR * wszMember = L"GetEntryDelegate"; - - DISPID dispid; - //Retrieve the DISPID - hr = pDisp->GetIDsOfNames( - IID_NULL, - &wszMember, - 1, - LOCALE_SYSTEM_DEFAULT, - &dispid); - - if (FAILED(hr)) - { - output->DisplayMessage(false, g_GETTING_DISPATCH_ID_FAILED, hr); - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - DISPPARAMS dispparamsOneArg = { NULL, NULL, 0 }; - VARIANT varResult; - VariantInit(&varResult); - EXCEPINFO exception; - unsigned int uArgErr = 0; - - //Invoke the method on the Dispatch Interface - hr = pDisp->Invoke( - dispid, - IID_NULL, - LOCALE_SYSTEM_DEFAULT, - DISPATCH_METHOD, - &dispparamsOneArg, - &varResult, - &exception, - &uArgErr); - - InitPluginWkrPtrsFuncPtr entryPointDelegate = (InitPluginWkrPtrsFuncPtr)varResult.byref; - - if (FAILED(hr) || - NULL == entryPointDelegate) - { - if (DISP_E_EXCEPTION == hr) - { - output->DisplayMessage(false, g_MANAGED_MSH_EXCEPTION, exception.bstrDescription); - } - else - { - output->DisplayMessage(false, g_INOVKING_MSH_ENTRANCE_FAILED, hr); - } - exitCode = EXIT_CODE_INIT_FAILURE; - break; - } - - // Passes empty pointer structure to the function, expects a filled struct if successful - exitCode = entryPointDelegate(workerCallbackPtrs); - } - while (false); - - return exitCode; -} - -PowerShellClrManagedWorker::~PowerShellClrManagedWorker() -{ - // No need to call pHost->Stop() because, - // as the common language runtime is automatically unloaded when the process exits. - if (output) - { - delete output; - output = NULL; - } -} - -#endif - -// -// This factory method analyzes the wszMgdPlugInFileName and determines the -// appropriate IPowerShellClrHost to instantiate. It is the caller's responsibility -// to free the IPowerShellClrHost once finished using it. -// -IPowerShellClrHost* PowerShellClrWorkerFactory( - _In_ PWSTR wszMgdPlugInFileName) -{ -#ifdef CORECLR - return new PowerShellCoreClrWorker(); // Calls into System.Management.Automation.dll -#else // !CORECLR - std::wstring pluginFileName(wszMgdPlugInFileName); - // This is a case-sensitive search. It checks to see if the file name portion of the path matches the v3 module name. - if (std::wstring::npos != pluginFileName.rfind(g_MANAGED_PLUGIN_FILENAME_V3_STRING)) - { - return new PowerShellClrManagedWorker(); // Calls into System.Management.Automation.dll - } - return new PowerShellClrWorker(); // Calls into pspluginwkr.dll (previous generation of plugin worker) -#endif // !CORECLR -} diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.h b/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.h deleted file mode 100644 index 169e9803ae2..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshclrhost.h +++ /dev/null @@ -1,171 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Headers used by pwrshplugin. -// pwrshplugin is totally unmanaged. -// ---------------------------------------------------------------------- - -#pragma once - -#include "pwrshplugindefs.h" // PwrshPluginWkr_Ptrs and PlugInException -#if !CORECLR -#include -#endif -#include -#include -#include "SystemCallFacade.h" -#include "ClrHostWrapper.h" -#include "NativeMsh.h" - -class PwrshPluginOutputDefault : public NativeMsh::IPwrshCommonOutput -{ -public: - virtual VOID DisplayMessage( - bool bUseStdOut, - DWORD dwMessageId, - ...) - { - return; - } - - virtual void DisplayErrorWithSystemError( - LONG lSystemErrorCode, - int messageId, - LPCWSTR insertionParam) - { - return; - } -}; - -class IPowerShellClrHost -{ -public: - // Virtual destructor to ensure that derived destructors are called - // during base class destruction. - virtual ~IPowerShellClrHost() {} - - virtual unsigned int LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName) = 0; - - virtual unsigned int LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ PWSTR wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException) = 0; -}; - -#if !CORECLR // See note for LaunchCLR for an explanation. - -class PowerShellClrWorker : public IPowerShellClrHost -{ -private: - CComPtr pHost; - HMODULE hManagedPluginModule; - NativeMsh::IPwrshCommonOutput* output; - NativeMsh::PwrshCommon commonLib; - - const LPCSTR g_INIT_PLUGIN; - const LPCSTR g_SHUTDOWN_PLUGIN; - const LPCSTR g_MANAGED_PLUGIN_CREATE_SHELL; - const LPCSTR g_MANAGED_PLUGIN_RELEASE_SHELL; - const LPCSTR g_MANAGED_PLUGIN_CREATE_COMMAND; - const LPCSTR g_MANAGED_PLUGIN_RELEASE_COMMAND; - const LPCSTR g_MANAGED_PLUGIN_SEND; - const LPCSTR g_MANAGED_PLUGIN_RECEIVE; - const LPCSTR g_MANAGED_PLUGIN_SIGNAL; - const LPCSTR g_MANAGED_PLUGIN_CONNECT; - - NativeMsh::SystemCallFacade* systemCalls; - -public: - PowerShellClrWorker(); - PowerShellClrWorker(NativeMsh::SystemCallFacade* sysCalls); - - virtual ~PowerShellClrWorker(); - - // - // IPowerShellClrHost Methods - // - virtual unsigned int LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName); - - virtual unsigned int LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ wchar_t* wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException); -}; - -class PowerShellClrManagedWorker : public IPowerShellClrHost -{ -private: - CComPtr pHost; - NativeMsh::IPwrshCommonOutput* output; - NativeMsh::PwrshCommon commonLib; - -public: - PowerShellClrManagedWorker() : pHost(NULL), output(new PwrshPluginOutputDefault()) - {} - - virtual ~PowerShellClrManagedWorker(); - - // - // IPowerShellClrHost Methods - // - virtual unsigned int LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName); - - virtual unsigned int LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ wchar_t* wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException); -}; - -#else // CORECLR - -class PowerShellCoreClrWorker : public IPowerShellClrHost -{ -private: - NativeMsh::ClrHostWrapper* hostWrapper; - NativeMsh::SystemCallFacade* systemCalls; - NativeMsh::PwrshCommon* commonLib; - NativeMsh::IPwrshCommonOutput* output; - NativeMsh::HostEnvironment hostEnvironment; - -public: - PowerShellCoreClrWorker(); - - PowerShellCoreClrWorker( - NativeMsh::SystemCallFacade* sysCalls, - NativeMsh::ClrHostWrapper* hstWrp, - NativeMsh::PwrshCommon* cmnLib); - - virtual ~PowerShellCoreClrWorker(); - - std::wstring GetHostDirectory() { return std::wstring(hostEnvironment.GetHostDirectoryPathW()); } - std::wstring GetClrDirectory() { return std::wstring(hostEnvironment.GetCoreCLRDirectoryPathW()); } - - // - // IPowerShellClrHost Methods - // - virtual unsigned int LaunchClr( - _In_ LPCWSTR wszMonadVersion, - _In_ LPCWSTR wszRuntimeVersion, - _In_ LPCSTR friendlyName); - - virtual unsigned int LoadWorkerCallbackPtrs( - _In_ PwrshPluginWkr_Ptrs* workerCallbackPtrs, - _In_z_ PWSTR wszMgdPlugInFileName, - _Outptr_result_maybenull_ PlugInException** pPluginException); -}; - -#endif // CORECLR - -IPowerShellClrHost* PowerShellClrWorkerFactory( - _In_ PWSTR wszMgdPlugInFileName); diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshheaders.h b/src/powershell-native/nativemsh/pwrshplugin/pwrshheaders.h deleted file mode 100644 index bfc12fb27c9..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshheaders.h +++ /dev/null @@ -1,23 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Headers used by internal windows teams to access certain -// Powershell functionality -// ---------------------------------------------------------------------- - -#pragma once - -// Gets the CLR Version for a given PowerShell Version. PowerShell Version is -// supplied with 2 parameters iPSMajorVersion (PowerShell major version) and -// iPSMinorVersion (PowerShell minor version). The CLR version is returned through -// pwszRuntimeVersion and pRuntimeVersionLength represents the size of pwszRuntimeVersion. -// returns: 0 on success, non-zero on failure. -_Success_(return == 0) // EXIT_CODE_SUCCESS -extern "C" -unsigned int GetCLRVersionForPSVersion(int iPSMajorVersion, - int iPSMinorVersion, - size_t runtimeVersionLength, - __inout_ecount_part(runtimeVersionLength , *pRuntimeVersionLength) wchar_t* pwszRuntimeVersion, - __out_ecount(1) size_t* pRuntimeVersionLength); diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.def b/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.def deleted file mode 100644 index ef1208d3875..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.def +++ /dev/null @@ -1,17 +0,0 @@ -LIBRARY pwrshplugin - -EXPORTS - -GetCLRVersionForPSVersion - -WSManPluginStartup -WSManPluginShutdown - -WSManPluginShell -WSManPluginReleaseShellContext -WSManPluginCommand -WSManPluginReleaseCommandContext -WSManPluginSend -WSManPluginSignal -WSManPluginReceive -WSManPluginConnect diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.dll.manifest b/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.dll.manifest deleted file mode 100644 index ef7b2351c9d..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.dll.manifest +++ /dev/null @@ -1,17 +0,0 @@ - - - PowerShell Plugin - - - - - - - - diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.h b/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.h deleted file mode 100644 index faf7b2378ae..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugin.h +++ /dev/null @@ -1,1101 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Headers used by pwrshplugin. -// pwrshplugin is totally unmanaged. -// ---------------------------------------------------------------------- - -#pragma once - -#include "NativeMsh.h" -#include "NativeMshConstants.h" -#include "entrypoints.h" -#include "pwrshheaders.h" -#include "pwrshplugindefs.h" -#include "pwrshclrhost.h" - -// include wsman.h header file..it is required to declare the version -// number of the wsman API to use -#define WSMAN_API_VERSION_1_0 -#include - -using namespace NativeMsh; - -// Forward declaration of class PwrshPlugIn -class PwrshPlugIn; - -class PwrshPlugIn -{ -private: - PCWSTR pAppIdentifier; - PCWSTR initParameters; - -public: - // applicationIdentification: - // This relates to the application HTTP suffix that is hosting the plug-in. - // For the main WSMan service by default this would be "wsman", whereas for - // an IIS host it would relate to the application endpoint for that host and - // would be something like "MyCompany/MyApplication". - PwrshPlugIn(PCWSTR applicationIdentification, PCWSTR pInitParams) - { - pAppIdentifier = applicationIdentification; - initParameters = pInitParams; - } - - ~PwrshPlugIn() - { - if (NULL != initParameters) - { - delete[] initParameters; - } - } - - PCWSTR GetApplicationIdentifier() - { - return pAppIdentifier; - } - - PCWSTR GetInitParameters() - { - return initParameters; - } -}; - -class PwrshPlugInMediator -{ -private: - // handle to managed powershell plugin - ShutdownPluginFuncPtr hShutdownPluginMethodAddress; - WSManPluginShellFuncPtr hCreateShellMethodAddress; - WSManPluginReleaseShellContextFuncPtr hReleaseShellMethodAddress; - WSManPluginCommandFuncPtr hCreateCommandMethodAddress; - WSManPluginReleaseCommandContextFuncPtr hReleaseCommandMethodAddress; - WSManPluginSendFuncPtr hSendMethodAddress; - WSManPluginReceiveFuncPtr hReceiveMethodAddress; - WSManPluginSignalFuncPtr hSignalMethodAddress; - WSManPluginConnectFuncPtr hConnectMethodAddress; - - // boolean to keep track if the managed plugin is loaded successfully - bool bIsPluginLoaded; - int iMajorVersion; - wchar_t* wszCLRVersion; - wchar_t* wszAppBase; - bool bIsDisposed; - - // fields needed to validate 2 plugins initializing at the same time - CRITICAL_SECTION criticalSection; - bool isCSInitSucceeded; - - // Abstraction of the differences between CLR hosting environments with - // respect to the interface with pspluginwkr. - IPowerShellClrHost* powerShellClrHost; - - // Default no-op implementation used for the output functions. - NativeMsh::PwrshCommon pwrshCommon; - - // private constructor..to make Mediator Singleton - PwrshPlugInMediator() - { - bIsPluginLoaded = false; - bIsDisposed = false; - iMajorVersion = 0; - wszCLRVersion = NULL; - wszAppBase = NULL; - - hShutdownPluginMethodAddress = NULL; - hCreateShellMethodAddress = NULL; - hReleaseShellMethodAddress = NULL; - hCreateCommandMethodAddress = NULL; - hReleaseCommandMethodAddress = NULL; - hSendMethodAddress = NULL; - hReceiveMethodAddress = NULL; - hSignalMethodAddress = NULL; - hConnectMethodAddress = NULL; - - isCSInitSucceeded = false; - - powerShellClrHost = NULL; - - // spin count is set to 1024 - if (InitializeCriticalSectionAndSpinCount(&criticalSection, 0x80000400)) - { - isCSInitSucceeded = true; - } - } - - // Clear plugin specific resources. - void CleanUp() - { - if (!bIsDisposed) - { - bIsDisposed = true; - - if (NULL != powerShellClrHost) - { - delete powerShellClrHost; - powerShellClrHost = NULL; - } - - if (NULL != wszCLRVersion) - { - delete[] wszCLRVersion; - wszCLRVersion = NULL; - } - - if (NULL != wszAppBase) - { - delete[] wszAppBase; - wszAppBase = NULL; - } - } - } - -public: - - static PwrshPlugInMediator* GetPwrshPlugInMediator(__in_opt PCWSTR extraInfo) throw (...) - { - // to make plugin mediator singleton - // Create object, when called for the first time - static PwrshPlugInMediator singletonInstance; - - if (!singletonInstance.isCSInitSucceeded) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_INIT_CRITICALSECTION_FAILED); - throw new PlugInException(g_INIT_CRITICALSECTION_FAILED, msg); - } - - // this check avoids entering/exiting critical sections - // frequently - if (!singletonInstance.bIsPluginLoaded) - { - EnterCriticalSection(&singletonInstance.criticalSection); - try - { - if (!singletonInstance.bIsPluginLoaded) - { - // process extra info initializes the pwrshplugin - // by initializing the CLR version and obtaining access - // pointers for the plugin worker or for System.Management.Automation.dll. - singletonInstance.ProcessExtraInfo(extraInfo, NULL); - } - } - catch(PlugInException* e) - { - LeaveCriticalSection(&singletonInstance.criticalSection); - throw e; - } - - LeaveCriticalSection(&singletonInstance.criticalSection); - } - - return &singletonInstance; - } - - ~PwrshPlugInMediator() - { - if (isCSInitSucceeded) - { - DeleteCriticalSection(&criticalSection); - } - - CleanUp(); - } - - // Clear plugin specific resources. - DWORD Shutdown(__in DWORD flags, __in DWORD reason) throw (...) - { - // this null condition should never occur.. but for server process safety ensure we - // fail safely.. - if (NULL != hShutdownPluginMethodAddress) - { - (hShutdownPluginMethodAddress)((PVOID)this); - } - - CleanUp(); - return NO_ERROR; - } - - PwrshPlugIn* CreatePwrshPlugIn(__in PCWSTR applicationIdentification, - __in_opt PCWSTR extraInfo) throw (...) - { - PWSTR initParameters = NULL; - // ProcessExtraInfo might throw... - // storing the extra info for plugin use later. - VerifyAndStoreExtraInfo(extraInfo, &initParameters); - PwrshPlugIn* result = new PwrshPlugIn(applicationIdentification, initParameters); - return result; - } - - VOID CreateShell( - __in PwrshPlugIn *plugInToUseWithCreateShell, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in_opt WSMAN_SHELL_STARTUP_INFO *startupInfo, - __in_opt WSMAN_DATA *inboundShellInformation) - { - if ((NULL == plugInToUseWithCreateShell) || - (NULL == requestDetails) || - (NULL == startupInfo) || - (NULL == requestDetails->operationInfo)) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginShell"); - return; - } - - // this null condition should never occur.. but for server process safety ensure we - // fail safely.. - if (NULL == hCreateShellMethodAddress) - { - ReportError(requestDetails, g_MANAGED_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - PCWSTR initParameters = plugInToUseWithCreateShell->GetInitParameters(); - (hCreateShellMethodAddress)((PVOID)this, requestDetails, flags, initParameters, startupInfo, inboundShellInformation); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - } - - VOID ReleaseShell(__in PVOID shellContext) - { - // this null condition should never occur.. but for server process safety ensure we - // fail safely.. - if (NULL != hReleaseShellMethodAddress) - { - (hReleaseShellMethodAddress)((PVOID)this, shellContext); - } - } - - VOID CreateCommand( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in PCWSTR commandLine, - __in_opt WSMAN_COMMAND_ARG_SET *arguments) - { - if (NULL == requestDetails) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginCommand"); - return; - } - - if (NULL == hCreateCommandMethodAddress) - { - ReportError(requestDetails, g_MANAGED_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - (hCreateCommandMethodAddress)((PVOID)this, requestDetails, flags, shellContext, commandLine, arguments); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - } - - VOID ReleaseCommand(__in PVOID shellContext, - __in PVOID commandContext) - { - // this null condition should never occur.. but for server process safety ensure we - // fail safely.. - if (NULL != hReleaseShellMethodAddress) - { - (hReleaseCommandMethodAddress)((PVOID)this, shellContext, commandContext); - } - } - - VOID ExecuteConnectToShellOrCommand( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_DATA *inboundConnectInformation) - { - if (NULL == requestDetails) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginConnect"); - return; - } - - if (NULL == hConnectMethodAddress) - { - ReportError(requestDetails, g_MANAGED_CONNECT_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - (hConnectMethodAddress)((PVOID)this, requestDetails, flags, shellContext, commandContext, inboundConnectInformation); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - - } - - VOID SendOneItemToShellOrCommand( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR stream, - __in WSMAN_DATA *inboundData) - { - if (NULL == requestDetails) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginSend"); - return; - } - - if (NULL == hSendMethodAddress) - { - ReportError(requestDetails, g_MANAGED_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - (hSendMethodAddress)((PVOID)this, requestDetails, flags, shellContext, commandContext, stream, inboundData); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - } - - VOID EnableShellOrCommandToSendDataToClient( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_STREAM_ID_SET* streamSet) - { - if (NULL == requestDetails) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginReceive"); - return; - } - - if (NULL == hReceiveMethodAddress) - { - ReportError(requestDetails, g_MANAGED_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - (hReceiveMethodAddress)((PVOID)this, requestDetails, flags, shellContext, commandContext, streamSet); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - } - - VOID SignalShellOrCmd( - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR code) - { - if (NULL == requestDetails) - { - ReportError(requestDetails, g_INVALID_INPUT, L"WSManPluginSignal"); - return; - } - - if (NULL == hSignalMethodAddress) - { - ReportError(requestDetails, g_MANAGED_METHOD_RESOLUTION_FAILED); - return; - } - - __try - { - (hSignalMethodAddress)((PVOID)this, requestDetails, flags, shellContext, commandContext, code); - } - __except(ProcessException(requestDetails, GetExceptionCode())) - { - } - } - -private: - // checks if the current plugin is active. - inline bool IsActive() - { - return bIsDisposed; - } - - /* Process error code from the exception. The current code always - * returns EXCEPTION_CONTINUE_SEARCH after logging the error code. - * - * requestDetails is used to report WSMan operation complete - */ - int ProcessException(WSMAN_PLUGIN_REQUEST *requestDetails, unsigned int errorCode) - { - WSManPluginOperationComplete(requestDetails, 0, errorCode, NULL); - return EXCEPTION_CONTINUE_EXECUTION; - } - - DWORD ReportError(WSMAN_PLUGIN_REQUEST *requestDetails, DWORD dwMessageId, ...) - { - PWSTR extendedErrorInformation = NULL; - - va_list args; - va_start(args, dwMessageId); - - GetFormattedErrorMessage(&extendedErrorInformation, dwMessageId, &args); - - va_end(args); - - DWORD errorCode = dwMessageId; - DWORD result = WSManPluginOperationComplete(requestDetails, 0, errorCode, extendedErrorInformation); - - if (NULL != extendedErrorInformation) - { - delete[] extendedErrorInformation; - } - - return result; - } - - DWORD ReportError(WSMAN_PLUGIN_REQUEST *requestDetails, PlugInException* e) - { - DWORD errorCode = e->dwMessageId; - - DWORD result = WSManPluginOperationComplete(requestDetails, 0, errorCode, e->extendedErrorInformation); - return result; - } - - unsigned int CreateMgdPluginFileName( - int iVPSMajorVersion, - int iVPSMinorVersion, - _In_ PWSTR wszVAppBase, - __out PWSTR* wszMgdPlugInFileName) - { - int monadMajorVersion = iVPSMajorVersion; - int monadMinorVersion = iVPSMinorVersion; - wchar_t * wszMonadVersion = NULL; - wchar_t* wszTempCLRVersion = NULL; - unsigned int exitCode = EXIT_CODE_SUCCESS; - - do - { - iMajorVersion = iVPSMajorVersion; - - if (NULL != wszMgdPlugInFileName) - { - *wszMgdPlugInFileName = NULL; - } - - exitCode = ConstructPowerShellVersion(iVPSMajorVersion, iVPSMinorVersion, &wszMonadVersion); - if (EXIT_CODE_SUCCESS != exitCode) - { - break; - } - - // Read managed plugin's full path from registry, if such a path exists. - exitCode = pwrshCommon.GetRegistryInfo( - &wszMonadVersion, - &monadMajorVersion, - monadMinorVersion, - &wszTempCLRVersion, - g_PSPLUGINWKRV3_REGISTRY_KEY, - wszMgdPlugInFileName); - - if (*wszMgdPlugInFileName == NULL) - { - // Reset exit code to EXIT_CODE_SUCCESS - exitCode = EXIT_CODE_SUCCESS; - - // construct managed plugin's full path - size_t iAppBaseLength; - size_t iFileNameLength; - size_t iTotalLength; - - if (FAILED(StringCchLength(wszVAppBase, STRSAFE_MAX_CCH, &iAppBaseLength)) || - FAILED(StringCchLength(g_MANAGED_PLUGIN_FILENAME_STRING, STRSAFE_MAX_CCH, &iFileNameLength))) - { - exitCode = g_INVALID_INPUT; - break; - } - - iTotalLength = iAppBaseLength + iFileNameLength + 2; - - *wszMgdPlugInFileName = new wchar_t[iTotalLength]; - if (NULL == *wszMgdPlugInFileName) - { - exitCode = E_OUTOFMEMORY; - break; - } - (*wszMgdPlugInFileName)[0] = '\0'; - - if (FAILED(StringCchCopyW(*wszMgdPlugInFileName, iTotalLength, wszVAppBase)) || - FAILED(StringCchCatW(*wszMgdPlugInFileName, iTotalLength, L"\\")) || - FAILED(StringCchCatW(*wszMgdPlugInFileName, iTotalLength, g_MANAGED_PLUGIN_FILENAME_STRING))) - { - exitCode = g_MANAGED_PLUGIN_PATH_CONSTRUCTION_ERROR; - break; - } - } - } while (false); - - if (NULL != wszMonadVersion) - { - delete[] wszMonadVersion; - } - - if (NULL != wszTempCLRVersion) - { - delete[] wszTempCLRVersion; - } - return exitCode; - } - - // returns non-zero code on error + plugin exception is populated in some cases - // like plugin load error. so the caller is expected to check both these - // to see if there is any error. - unsigned int LoadManagedPlugIn( - _In_ PWSTR wszMgdPlugInFileName, - _In_ PWSTR wszVCLRVersion, // Conditionally set to wszCLRVersion on success - _In_ PWSTR wszVAppBase, // Conditionally set to wszAppBase on success - __out PlugInException** pPluginException ) - { - unsigned int exitCode = EXIT_CODE_SUCCESS; - - if (NULL != pPluginException) - { - *pPluginException = NULL; - } - else - { - return g_INVALID_INPUT; - } - - if (bIsPluginLoaded) - { - return g_MANAGED_PLUGIN_ALREADY_LOADED; - } - - if ((NULL == wszVCLRVersion) || (NULL == wszVAppBase)) - { - return g_INVALID_INPUT; - } - - do - { - // Setting global AppBase and CLR Version - wszCLRVersion = wszVCLRVersion; - wszAppBase = wszVAppBase; - - // Load managed plugin worker.. - - PwrshPluginWkr_Ptrs workerCallbackPtrs; - memset(&workerCallbackPtrs, 0, sizeof(PwrshPluginWkr_Ptrs)); - - // The loader will obtain the worker pointers from the appropriate DLL. - // If this call succeeds, PwrshPluginWkr_Ptrs should be populated. - exitCode = powerShellClrHost->LoadWorkerCallbackPtrs(&workerCallbackPtrs, wszMgdPlugInFileName, pPluginException); - - if (EXIT_CODE_SUCCESS == exitCode) - { - // Now get the proc address for all the plugin API.. - hShutdownPluginMethodAddress = workerCallbackPtrs.shutdownPluginFuncPtr; - hCreateShellMethodAddress = workerCallbackPtrs.wsManPluginShellFuncPtr; - hReleaseShellMethodAddress = workerCallbackPtrs.wsManPluginReleaseShellContextFuncPtr; - hCreateCommandMethodAddress = workerCallbackPtrs.wsManPluginCommandFuncPtr; - hReleaseCommandMethodAddress = workerCallbackPtrs.wsManPluginReleaseCommandContextFuncPtr; - hSendMethodAddress = workerCallbackPtrs.wsManPluginSendFuncPtr; - hReceiveMethodAddress = workerCallbackPtrs.wsManPluginReceiveFuncPtr; - hSignalMethodAddress = workerCallbackPtrs.wsManPluginSignalFuncPtr; - hConnectMethodAddress = workerCallbackPtrs.wsManPluginConnectFuncPtr; - } - - if (// if pwrsplugin v2 plugin, this function does not exist - // (NULL == hShutdownPluginMethodAddress) || - (NULL == hCreateShellMethodAddress) || (NULL == hReleaseShellMethodAddress) || - (NULL == hCreateCommandMethodAddress) || (NULL == hReleaseCommandMethodAddress) || - (NULL == hSendMethodAddress) || (NULL == hReceiveMethodAddress) || - (NULL == hSignalMethodAddress)) - { - PWSTR msg = NULL; - exitCode = g_MANAGED_METHOD_RESOLUTION_FAILED; - GetFormattedErrorMessage(&msg, g_MANAGED_METHOD_RESOLUTION_FAILED); - *pPluginException = new PlugInException(exitCode, msg); - break; - } - - bIsPluginLoaded = true; - }while(false); - - if (exitCode != EXIT_CODE_SUCCESS) - { - wszCLRVersion = NULL; - wszAppBase = NULL; - } - - return exitCode; - } - - void LoadPowerShell(PCWSTR version) throw (...) - { - // Verify incoming powershell version format. - int iPSMajorVersion = 0; - int iPSMinorVersion = 0; - if (!pwrshCommon.VerifyMonadVersionFormat(version, &iPSMajorVersion, &iPSMinorVersion, true, false)) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_OPTION_SET_NOT_COMPLY, g_BUILD_VERSION); - throw new PlugInException(g_OPTION_SET_NOT_COMPLY, msg); - } - - // client is requesting powershell 1.x version. Remoting doesn't support - // powershell 1.0, remoting is supported from 2.0 - if (1 >= iPSMajorVersion) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_OPTION_SET_NOT_COMPLY, g_BUILD_VERSION); - throw new PlugInException(g_OPTION_SET_NOT_COMPLY, msg); - } - - // the reg key from 2 and 1 is 1.. - int requestedMonadMajorVersion = iPSMajorVersion; - if (2 == requestedMonadMajorVersion) - { - requestedMonadMajorVersion = 1; - } - - wchar_t* wszMonadVersion = NULL; // Allocated via ConstructPowerShellVersion || GetRegistryInfo - wchar_t* wszTempCLRVersion = NULL; // Allocated via GetRegistryInfo - wchar_t* wszTempAppBase = NULL; // Allocated via GetRegistryInfo - PWSTR wszMgdPlugInFileName = NULL; // Allocated in CreateMgdPluginFileName - unsigned int exitCode = EXIT_CODE_SUCCESS; - PlugInException* pErrorMsg = NULL; - - do - { - exitCode = ConstructPowerShellVersion(iPSMajorVersion, iPSMinorVersion, &wszMonadVersion); - if (exitCode != EXIT_CODE_SUCCESS) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_OPTION_SET_NOT_COMPLY, g_BUILD_VERSION); - pErrorMsg = new PlugInException(exitCode, msg); - break; - } - - // Read CLR version from registry - exitCode = pwrshCommon.GetRegistryInfo( - &wszMonadVersion, - &requestedMonadMajorVersion, - iPSMinorVersion, - &wszTempCLRVersion, - L"ApplicationBase", - &wszTempAppBase); - if (EXIT_CODE_SUCCESS != exitCode) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_OPTION_SET_NOT_COMPLY, g_BUILD_VERSION); - pErrorMsg = new PlugInException(exitCode, msg); - break; - } - - exitCode = CreateMgdPluginFileName(requestedMonadMajorVersion, iPSMinorVersion, wszTempAppBase, &wszMgdPlugInFileName); - if (EXIT_CODE_SUCCESS != exitCode) - { - break; - } - - if (!bIsPluginLoaded) - { - this->powerShellClrHost = PowerShellClrWorkerFactory(wszMgdPlugInFileName); - if (NULL == this->powerShellClrHost) - { - exitCode = ERROR_NOT_ENOUGH_MEMORY; - break; - } - - exitCode = powerShellClrHost->LaunchClr(wszMonadVersion, wszTempCLRVersion, "PwrshPlugin"); - if (EXIT_CODE_SUCCESS != exitCode) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, g_CLR_LOAD_FAILED, wszTempCLRVersion); - pErrorMsg = new PlugInException(g_CLR_LOAD_FAILED, msg); - break; - } - - exitCode = LoadManagedPlugIn(wszMgdPlugInFileName, wszTempCLRVersion, wszTempAppBase, &pErrorMsg); - } - else - { - if (requestedMonadMajorVersion != iMajorVersion) - { - exitCode = g_OPTION_SET_MAJOR_VERSION_NOT_MATCH; - } - else if (0 != _wcsnicmp(wszTempCLRVersion, wszCLRVersion, 20)) - { - exitCode = g_OPTION_SET_CLR_VERSION_NOT_MATCH; - } - else if (0 != _wcsicmp(wszTempAppBase, wszAppBase)) - { - exitCode = g_OPTION_SET_APP_BASE_NOT_MATCH; - } - } - } while (false); - - if (NULL != wszMonadVersion) - { - delete[] wszMonadVersion; - } - - if (NULL != wszMgdPlugInFileName) - { - delete[] wszMgdPlugInFileName; - } - - if (exitCode != EXIT_CODE_SUCCESS) - { - // Assigned to this->wszCLRVersion on success in LoadManagedPlugIn, so it shouldn't be freed here on success - if (NULL != wszTempCLRVersion) - { - delete[] wszTempCLRVersion; - } - - // Assigned to this->wszAppBase on success LoadManagedPlugIn, so it shouldn't be freed here on success - if (NULL != wszTempAppBase) - { - delete[] wszTempAppBase; - } - - if (NULL != pErrorMsg) - { - throw pErrorMsg; - } - else - { - PWSTR msg = NULL; - (void) GetFormattedErrorMessage(&msg, g_OPTION_SET_NOT_COMPLY, g_BUILD_VERSION); - /* NOTE: Passing possible NULL msg. PlugInExpecption requires allocated - or NULL. Literal strings are not supported. */ - throw new PlugInException(exitCode, msg); - } - } - } - -public: - // extraInfo is supplied by WSMan and WSMan validates the XML syntax - // before supplying this value to plugin..because of this, the following - // method will not check for xml element syntax. - void VerifyAndStoreExtraInfo(PCWSTR extraInfo, - __deref_opt_out PWSTR *initParameters) throw(...) - { - if (NULL == extraInfo) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_PSVERSION_NOT_FOUND_IN_CONFIG, g_PSVERSION_CONFIG, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_PSVERSION_NOT_FOUND_IN_CONFIG, msg); - } - - size_t initParamsLength; - if (FAILED(StringCchLength(extraInfo, STRSAFE_MAX_CCH, &initParamsLength))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - - if (NULL != initParameters) - { - // make a local copy of the extra info for future use - // this will be used whenever a new shell is created. - *initParameters = new wchar_t[initParamsLength + 1]; - if (FAILED(StringCchCopyNW(*initParameters, initParamsLength + 1, extraInfo, initParamsLength))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - } - } - - void ProcessExtraInfo(PCWSTR extraInfo, - __deref_opt_out PWSTR *initParameters) throw(...) - { - VerifyAndStoreExtraInfo(extraInfo, initParameters); - - // Get PSVersion and MaxPSVersion values from the config xml - wchar_t* psversion = NULL; - wchar_t* maxpsversion = NULL; - // Win8: 97936: To support backward compatability, if PSVersion = 2.0 and AssemblyToken - // is specified we set maxPSVersion = 2.0..so that the endpoint is not automatically - // transferred to PS 3.0 - wchar_t* assemblyToken = NULL; - - // This will hold the powershell version calculated from psversion and maxpsversion - wchar_t* version = NULL; - - size_t initParamsLength; - if (FAILED(StringCchLength(extraInfo, STRSAFE_MAX_CCH, &initParamsLength))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - - PCWSTR param = wcsstr(extraInfo, L" 0) - { - version = new wchar_t[length + 1]; - - try - { - if (FAILED(StringCchCopyNW(version, length + 1, startVersion, length))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - return version; - } - catch(...) - { - if (NULL != version) - { - delete[] version; - } - throw; - } - - // delete the version in non-throw case. - if (NULL != version) - { - delete[] version; - } - } - } - return NULL; - } - - wchar_t* CalculatePowershellVersion(__in wchar_t* psversion, - __in_opt wchar_t* maxpsversion) - { - wchar_t* version = NULL; - int majorPSVersionNumber = -1, minorPSVersionNumber = -1; - int majorMaxPSVersionNumber = -1, minorMaxPSVersionNumber = -1 ; - if (pwrshCommon.VerifyMonadVersionFormat(psversion, &majorPSVersionNumber, &minorPSVersionNumber, true, true)) - { - if (maxpsversion != NULL) - { - if (pwrshCommon.VerifyMonadVersionFormat(maxpsversion, &majorMaxPSVersionNumber, &minorMaxPSVersionNumber, true, true)) - { - // TODO: Don't understand this. - if (majorMaxPSVersionNumber > majorPSVersionNumber) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - - // TODO: why hardcoding is needed here - if (majorPSVersionNumber == 3 && majorMaxPSVersionNumber == 2) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - - if (majorPSVersionNumber == 2 && majorMaxPSVersionNumber == 2) - { - size_t length; - if (FAILED(StringCchLength(psversion, STRSAFE_MAX_CCH, &length))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - - version = new wchar_t[length + 1]; - // Set version as the psversion - if (FAILED(StringCchCopyNW(version, length + 1, psversion, length))) - { - PWSTR msg = NULL; - GetFormattedErrorMessage(&msg, - g_BAD_INITPARAMETERS, g_INITIALIZATIONPARAM_CONFIG); - throw new PlugInException(g_BAD_INITPARAMETERS, msg); - } - } - } - } - else - { - version = L"3.0"; - } - } - return version; - } -}; diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginResources.rc b/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginResources.rc deleted file mode 100644 index cbf92dff714..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginResources.rc +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// resource script for msh.exe -// - -#include - -#define MANIFEST_RESOURCE_ID 1 - -MANIFEST_RESOURCE_ID RT_MANIFEST "pwrshplugin.dll.manifest" - -#include "pwrshpluginerrorcodes.rc" -#include "version.rc" \ No newline at end of file diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugindefs.h b/src/powershell-native/nativemsh/pwrshplugin/pwrshplugindefs.h deleted file mode 100644 index 7cfa8c5ce26..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshplugindefs.h +++ /dev/null @@ -1,163 +0,0 @@ -// ---------------------------------------------------------------------- -// -// Microsoft Windows NT -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Contents: Headers used by pwrshplugin. -// pwrshplugin is totally unmanaged. -// ---------------------------------------------------------------------- - -#pragma once - -#include -#include - -// include wsman.h header file..it is required to declare the version -// number of the wsman API to use -#define WSMAN_API_VERSION_1_0 -#include - -// plugin errors start from 1000 -const int g_NULL_PLUGIN_CONTEXT = 1100; -const int g_CREATION_FAILED = 1101; -const int g_MANAGED_PLUGIN_ALREADY_LOADED = 1102; -const int g_MANAGED_PLUGIN_PATH_CONSTRUCTION_ERROR = 1103; -const int g_MANAGED_PLUGIN_LOAD_FAILED = 1104; - -const int g_SHELL_CREATION_FAILED = 1201; -const int g_OPTION_SET_MAJOR_VERSION_NOT_MATCH = 1202; -const int g_OPTION_SET_CLR_VERSION_NOT_MATCH = 1203; -const int g_OPTION_SET_APP_BASE_NOT_MATCH = 1204; - -const PCWSTR g_VERSION_OPTION_STRING = L"version"; -const PCWSTR g_PSPLUGINWKRV3_REGISTRY_KEY = L"PSPluginWkrModuleName"; -const PCWSTR g_MANAGED_PLUGIN_FILENAME_STRING = L"pspluginwkr.dll"; -const PCWSTR g_MANAGED_PLUGIN_FILENAME_V3_STRING = L"system.management.automation.dll"; -const PCWSTR g_PSVERSION_CONFIG = L"PSVersion"; -const PCWSTR g_INITIALIZATIONPARAM_CONFIG = L"InitializationParameters"; - -// The following VER_ macros are defined in ntverp.h -const PCWSTR g_BUILD_VERSION = LVER_PRODUCTVERSION_STR; - -// Managed and mixed-mode plugin worker method entrypoints -// -// The v3 version of the worker is a mixed-mode managed C++ DLL, so it can -// be accessed directly using GetProcAddress. -// The CoreCLR-compliant worker is included in System.Management.Automation.dll, -// so we have to use alternate means to acquire the function pointers. -// Once we have the pointers, they may be used in the same way regardless of the -// method used to obtain them. - -typedef void (WINAPI *ShutdownPluginFuncPtr)(__in PVOID pluginContext); - -typedef void (WINAPI *WSManPluginShellFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PCWSTR extraInfo, - __in_opt WSMAN_SHELL_STARTUP_INFO *startupInfo, - __in_opt WSMAN_DATA *inboundShellInformation - ); - -typedef void (WINAPI *WSManPluginConnectFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_DATA *inboundConnectInformation - ); - -typedef void (WINAPI *WSManPluginReleaseShellContextFuncPtr)( - __in PVOID pluginContext, - __in PVOID shellContext - ); - -typedef void (WINAPI *WSManPluginCommandFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in PCWSTR commandLine, - __in_opt WSMAN_COMMAND_ARG_SET *arguments - ); - -typedef void (WINAPI *WSManPluginReleaseCommandContextFuncPtr)( - __in PVOID pluginContext, - __in PVOID shellContext, - __in PVOID commandContext - ); - -typedef void (WINAPI *WSManPluginSendFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR stream, - __in WSMAN_DATA *inboundData - ); - -typedef void (WINAPI *WSManPluginReceiveFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in_opt WSMAN_STREAM_ID_SET* streamSet - ); - -typedef void (WINAPI *WSManPluginSignalFuncPtr)( - __in PVOID pluginContext, - __in WSMAN_PLUGIN_REQUEST *requestDetails, - __in DWORD flags, - __in PVOID shellContext, - __in_opt PVOID commandContext, - __in PCWSTR code); - -typedef void (WINAPI *WSManPluginOperationShutdownFuncPtr)( - __in PVOID pluginContext); - -typedef struct _PwrshPluginWkr_Ptrs -{ - ShutdownPluginFuncPtr shutdownPluginFuncPtr; - WSManPluginShellFuncPtr wsManPluginShellFuncPtr; - WSManPluginReleaseShellContextFuncPtr wsManPluginReleaseShellContextFuncPtr; - WSManPluginCommandFuncPtr wsManPluginCommandFuncPtr; - WSManPluginReleaseCommandContextFuncPtr wsManPluginReleaseCommandContextFuncPtr; - WSManPluginSendFuncPtr wsManPluginSendFuncPtr; - WSManPluginReceiveFuncPtr wsManPluginReceiveFuncPtr; - WSManPluginSignalFuncPtr wsManPluginSignalFuncPtr; - WSManPluginConnectFuncPtr wsManPluginConnectFuncPtr; - WSManPluginOperationShutdownFuncPtr wsmanPluginOperationShutdownFuncPtr; // This ptr is not used in this environment, but is required to keep the memory layout identical between unmanaged and managed code. -} PwrshPluginWkr_Ptrs; - -class PlugInException -{ -public: - DWORD dwMessageId; - // This message is allocated outside the exception, but the exception frees - // the memory. - PWSTR extendedErrorInformation; - - PlugInException(DWORD msgId, __in_opt PWSTR msg) - { - dwMessageId = msgId; - extendedErrorInformation = msg; - } - - ~PlugInException() - { - if (NULL != extendedErrorInformation) - { - delete[] extendedErrorInformation; - extendedErrorInformation = NULL; - } - } -private: - // Provided without implementation to prevent automatic instantiation and copying - // since copying will lead to double frees given the existing code. - PlugInException(); - PlugInException(const PlugInException&); - PlugInException& operator=(const PlugInException&); -}; diff --git a/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginerrorcodes.mc b/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginerrorcodes.mc deleted file mode 100644 index 45e389e42da..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/pwrshpluginerrorcodes.mc +++ /dev/null @@ -1,80 +0,0 @@ -;// Copyright (c) Microsoft Corporation. All rights reserved. - -;#undef FACILITY_POWERSHELL -FacilityNames=(PowerShell=84:FACILITY_POWERSHELL) -SeverityNames=(Success=0x0 - CoError=0x2 - ) - -MessageId=1001 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_INVALID_PLUGIN_CONTEXT -Language=English -The supplied plugin context is invalid. -. - -MessageId=1002 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_INVALID_INPUT -Language=English -Invalid parameter to plugin method %1!ls!. -. - -MessageId=1003 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_MANAGED_METHOD_RESOLUTION_FAILED -Language=English -Unable to find plugin methods WSManPluginShell, WSManPluginCommand, WSManPluginSend, WSManPluginReceive in the Managed plugin module. -. - -MessageId=1004 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_OPTION_SET_NOT_COMPLY -Language=English -Powershell plugin does not support the options requested. Make sure client is compatible with build %1!ls! of PowerShell. -. - -MessageId=1005 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_PSVERSION_NOT_FOUND_IN_CONFIG -Language=English -A "%1!ls!" name-value pair is expected in the "%2!ls!" element of the configuration xml. Contact your administrator. -. - -MessageId=1006 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_BAD_INITPARAMETERS -Language=English -The "%1!ls!" element in the configuration xml is badly formatted. Contact your administrator. -. - -MessageId=1007 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_INIT_CRITICALSECTION_FAILED -Language=English -An error occurred while initializing the plugin. Contact your administrator. -. - -MessageId=1008 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_CLR_LOAD_FAILED -Language=English -An error occurred while loading version %1!ls! of CLR. Contact your administrator. -. - -MessageId=1009 -Facility=POWERSHELL -Severity=CoError -SymbolicName=g_MANAGED_CONNECT_METHOD_RESOLUTION_FAILED -Language=English -An error occured while processing the connect operation. A relevant entry point is not found in the managed plugin module -Make sure that the configured plugin supports connect operation. -. diff --git a/src/powershell-native/nativemsh/pwrshplugin/version.rc b/src/powershell-native/nativemsh/pwrshplugin/version.rc deleted file mode 100644 index 016c85d0b83..00000000000 --- a/src/powershell-native/nativemsh/pwrshplugin/version.rc +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -#include -#include - -#define VER_FILETYPE VFT_DLL -#define VER_FILESUBTYPE VFT2_UNKNOWN -#define VER_FILEDESCRIPTION_STR "pwrshplugin.dll" -#define VER_INTERNALNAME_STR "pwrshplugin.dll" -#define VER_ORIGINALFILENAME_STR "pwrshplugin.dll" - -#include diff --git a/src/powershell-native/windows-compiler-override.txt b/src/powershell-native/windows-compiler-override.txt deleted file mode 100644 index f6cb16856cd..00000000000 --- a/src/powershell-native/windows-compiler-override.txt +++ /dev/null @@ -1,16 +0,0 @@ -SET (CMAKE_C_FLAGS_INIT "/Wall /FC") -SET (CMAKE_C_FLAGS_DEBUG_INIT "/Od /Zi") -SET (CLR_C_FLAGS_CHECKED_INIT "/O1 /Zi") -SET (CMAKE_C_FLAGS_RELEASE_INIT "/Ox /Zi") -SET (CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/O2 /Zi") - -SET (CMAKE_CXX_FLAGS_INIT "/Wall /FC") -SET (CMAKE_CXX_FLAGS_DEBUG_INIT "/Od /Zi") -SET (CLR_CXX_FLAGS_CHECKED_INIT "/O1 /Zi") -SET (CMAKE_CXX_FLAGS_RELEASE_INIT "/Ox /Zi") -SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/O2 /Zi") - -SET (CLR_DEFINES_DEBUG_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) -SET (CLR_DEFINES_CHECKED_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) -SET (CLR_DEFINES_RELEASE_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) -SET (CLR_DEFINES_RELWITHDEBINFO_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) \ No newline at end of file diff --git a/src/powershell-unix/powershell-unix.csproj b/src/powershell-unix/powershell-unix.csproj index 947af707237..20c61247d24 100644 --- a/src/powershell-unix/powershell-unix.csproj +++ b/src/powershell-unix/powershell-unix.csproj @@ -1,4 +1,4 @@ - + @@ -6,7 +6,10 @@ PowerShell top-level project with .NET CLI host pwsh Exe - linux-x64;osx.10.12-x64; + true + true + true + linux-x64;osx-x64; @@ -16,7 +19,25 @@ PreserveNewest PreserveNewest - + + Schemas\PSMaml\%(FileName)%(Extension) + PreserveNewest + PreserveNewest + + + PreserveNewest + PreserveNewest + + + PreserveNewest + PreserveNewest + + + en-US\default.help.txt + PreserveNewest + PreserveNewest + + PreserveNewest PreserveNewest @@ -24,30 +45,6 @@ - - - - - - - - - - $(DefineConstants);CORECLR - - - - portable - - - - $(DefineConstants);UNIX - - - - full - - - + diff --git a/src/powershell-unix/runtimeconfig.template.json b/src/powershell-unix/runtimeconfig.template.json new file mode 100644 index 00000000000..4a5e3e367ec --- /dev/null +++ b/src/powershell-unix/runtimeconfig.template.json @@ -0,0 +1,4 @@ +// This is required to roll forward to supported minor.patch versions of the runtime. +{ + "rollForwardOnNoCandidateFx": 1 +} diff --git a/src/powershell-win-core/Properties/launchSettings.json b/src/powershell-win-core/Properties/launchSettings.json new file mode 100644 index 00000000000..da47be91394 --- /dev/null +++ b/src/powershell-win-core/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "powershell-win-core": { + "commandName": "Executable", + "executablePath": ".\\win7-x64\\publish\\pwsh.exe" + } + } +} diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index 3774aa40dc1..dddbb915eca 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -1,25 +1,48 @@ - + + - PowerShell Core on Windows top-level project + PowerShell on Windows top-level project pwsh Exe - win7-x86;win7-x64 + true + true + true + win-x86;win-x64 Microsoft.PowerShell + ..\..\assets\pwsh.manifest + Windows + 8.0 + + - - powershell.config.json + + Modules\%(RecursiveDir)\%(FileName)%(Extension) + PreserveNewest PreserveNewest - - Modules\%(RecursiveDir)\%(FileName)%(Extension) + + Schemas\PSMaml\%(FileName)%(Extension) + PreserveNewest + PreserveNewest + + + PreserveNewest + PreserveNewest + + PreserveNewest PreserveNewest - + + preview\pwsh-preview.cmd + PreserveNewest + + + en-US\default.help.txt PreserveNewest PreserveNewest @@ -27,32 +50,12 @@ - + - - - - - - - - $(DefineConstants);CORECLR - - - - portable - - - - $(DefineConstants);UNIX - - - - full - + diff --git a/src/powershell-win-core/pwsh-preview.cmd b/src/powershell-win-core/pwsh-preview.cmd new file mode 100644 index 00000000000..1dd3fc1b4f9 --- /dev/null +++ b/src/powershell-win-core/pwsh-preview.cmd @@ -0,0 +1 @@ +@"%~dp0\..\pwsh.exe" %* diff --git a/src/powershell-win-core/runtimeconfig.template.json b/src/powershell-win-core/runtimeconfig.template.json new file mode 100644 index 00000000000..4a5e3e367ec --- /dev/null +++ b/src/powershell-win-core/runtimeconfig.template.json @@ -0,0 +1,4 @@ +// This is required to roll forward to supported minor.patch versions of the runtime. +{ + "rollForwardOnNoCandidateFx": 1 +} diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index e208c9cd754..1de961674df 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -1,26 +1,500 @@ -/********************************************************************++ -Copyright (c) Microsoft Corporation. All rights reserved. ---********************************************************************/ +// 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; namespace Microsoft.PowerShell { /// - /// Defines an entry point for the .NET CLI "powershell" app + /// Defines an entry point for the .NET CLI "powershell" app. /// public sealed class ManagedPSEntry { +#if UNIX /// - /// Starts the managed MSH + /// Exception to signify an early startup failure. + /// + private sealed class StartupException : Exception + { + /// + /// Construct a new startup exception instance. + /// + /// The name of the native call that failed. + /// The exit code the native call returned. + public StartupException(string callName, int exitCode) + { + CallName = callName; + ExitCode = exitCode; + } + + /// + /// The name of the native call that failed. + /// + public string CallName { get; } + + /// + /// The exit code returned by the failed native call. + /// + public int ExitCode { get; } + } + + // Environment variable used to short circuit second login check + private const string LOGIN_ENV_VAR_NAME = "__PWSH_LOGIN_CHECKED"; + private const string LOGIN_ENV_VAR_VALUE = "1"; + + // Linux p/Invoke constants + private const int LINUX_PATH_MAX = 4096; + + // MacOS p/Invoke constants + private const int MACOS_CTL_KERN = 1; + private const int MACOS_KERN_ARGMAX = 8; + private const int MACOS_KERN_PROCARGS2 = 49; + private const int MACOS_PROC_PIDPATHINFO_MAXSIZE = 4096; +#endif + + /// + /// Starts PowerShell. /// /// - /// Command line arguments to the managed MSH + /// Command line arguments to PowerShell /// public static int Main(string[] args) { - return UnmanagedPSEntry.Start(string.Empty, args, args.Length); +#if UNIX + AttemptExecPwshLogin(args); +#endif + return UnmanagedPSEntry.Start(args, args.Length); } + +#if UNIX + /// + /// Checks whether pwsh has been started as a login shell + /// and if so, proceeds with the login process. + /// This method will return early if pwsh was not started as a login shell + /// and will throw if it detects a native call has failed. + /// In the event of success, we use an exec() call, so this method never returns. + /// + /// The startup arguments to pwsh. + private static void AttemptExecPwshLogin(string[] args) + { + // If the login environment variable is set, we have already done the login logic and have been exec'd + if (Environment.GetEnvironmentVariable(LOGIN_ENV_VAR_NAME) != null) + { + Environment.SetEnvironmentVariable(LOGIN_ENV_VAR_NAME, null); + return; + } + + 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; + + // On Linux, we can simply use the /proc filesystem + if (isLinux) + { + // Read the process name byte + using (FileStream fs = File.OpenRead("/proc/self/cmdline")) + { + procNameFirstByte = (byte)fs.ReadByte(); + } + + // Run login detection logic + if (!IsLogin(procNameFirstByte, args)) + { + return; + } + + // Read the symlink to the startup executable + IntPtr linkPathPtr = Marshal.AllocHGlobal(LINUX_PATH_MAX); + IntPtr bufSize = ReadLink("/proc/self/exe", linkPathPtr, (UIntPtr)LINUX_PATH_MAX); + pwshPath = Marshal.PtrToStringAnsi(linkPathPtr, (int)bufSize); + Marshal.FreeHGlobal(linkPathPtr); + + ArgumentNullException.ThrowIfNull(pwshPath); + + // exec pwsh + ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: false)); + return; + } + + // At this point, we are on macOS + + // Set up the mib array and the query for process maximum args size + Span mib = [MACOS_CTL_KERN, MACOS_KERN_ARGMAX]; + int size = IntPtr.Size / 2; + int argmax = 0; + + // Get the process args size + unsafe + { + fixed (int *mibptr = mib) + { + ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mib.Length, &argmax, &size, IntPtr.Zero, 0)); + } + } + + // Get the PID so we can query this process' args + int pid = GetPid(); + + // The following logic is based on https://gist.github.com/nonowarn/770696 + + // Now read the process args into the allocated space + IntPtr procargs = Marshal.AllocHGlobal(argmax); + IntPtr executablePathPtr = IntPtr.Zero; + try + { + mib = new int[] { MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid }; + + unsafe + { + fixed (int *mibptr = mib) + { + 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 + // that looks something like this: + // + // | argc | + // | exec_path | ... \0 + // | argv[0] | ... \0 + // | argv[1] | ... \0 + // ... + // + // We care about argv[0], since that's the name the process was started with. + // If argv[0][0] == '-', we have been invoked as login. + // Doing this, the buffer we populated also recorded `exec_path`, + // which is the path to our executable `pwsh`. + // 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)); + + // Skip over exec_path + byte *argvPtr = (byte *)executablePathPtr; + while (*argvPtr != 0) { argvPtr++; } + while (*argvPtr == 0) { argvPtr++; } + + // First char in argv[0] + procNameFirstByte = *argvPtr; + } + + if (!IsLogin(procNameFirstByte, args)) + { + return; + } + + // Get the pwshPath from exec_path + pwshPath = Marshal.PtrToStringAnsi(executablePathPtr); + + ArgumentNullException.ThrowIfNull(pwshPath); + + // exec pwsh + ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: true)); + } + finally + { + Marshal.FreeHGlobal(procargs); + } + } + + /// + /// Checks args to see if -Login has been specified. + /// + /// The first byte of the name of the currently running process. + /// Arguments passed to the program. + /// + private static bool IsLogin( + byte procNameFirstByte, + string[] args) + { + // Process name starting with '-' means this is a login shell + if (procNameFirstByte == 0x2D) + { + return true; + } + + // Look at the first parameter to see if it is -Login + // NOTE: -Login is only supported as the first parameter to PowerShell + return args.Length > 0 + && args[0].Length > 1 + && args[0][0] == '-' + && IsParam(args[0], "login", "LOGIN"); + } + + /// + /// Determines if a given parameter is the one we're looking for. + /// Assumes any prefix determines that parameter (true for -l, -c and -f). + /// + /// The argument to check. + /// The lowercase name of the parameter to check. + /// The uppercase name of the parameter to check. + /// + private static bool IsParam( + string arg, + string paramToCheck, + string paramToCheckUpper) + { + // Quick fail if the argument is longer than the parameter + if (arg.Length > paramToCheck.Length + 1) + { + return false; + } + + // 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]) + { + return false; + } + } + + return true; + } + + /// + /// Create the exec call to /bin/{z}sh -l -c 'exec pwsh "$@"' and run it. + /// + /// The argument vector passed to pwsh. + /// True if we are running on macOS. + /// Absolute path to the pwsh executable. + /// + /// The exit code of exec if it fails. + /// If exec succeeds, this process is overwritten so we never actually return. + /// + private static int ExecPwshLogin(string[] args, string pwshPath, bool isMacOS) + { + // Create input for /bin/sh that execs pwsh + int quotedPwshPathLength = GetQuotedPathLength(pwshPath); + + string pwshInvocation = string.Create( + 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]; + + // 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[1] = "-l"; // Login flag + execArgs[2] = "-c"; // Command parameter + execArgs[3] = pwshInvocation; // Command to execute + + // The /bin/sh option spec looks like: + // sh -c command_string [command_name [argument...]] + // We must provide a command_name before arguments, + // but this is never used since "$@" takes argv[1] - argv[n] + // and the `exec` builtin provides its own argv[0]. + // See https://pubs.opengroup.org/onlinepubs/9699919799.2016edition/ + // + // Since command_name is ignored and we can't use null (it's the terminator) + // we use empty string + execArgs[4] = string.Empty; + + // Add the arguments passed to pwsh on the end. + args.CopyTo(execArgs, 5); + + // A null is required by exec. + execArgs[execArgs.Length - 1] = null; + + // We can't use Environment.SetEnvironmentVariable() here. + // See https://github.com/dotnet/corefx/issues/40130#issuecomment-519420648. + ThrowOnFailure("setenv", SetEnv(LOGIN_ENV_VAR_NAME, LOGIN_ENV_VAR_VALUE, overwrite: true)); + + // On macOS, sh doesn't support login, so we run /bin/zsh in sh emulation mode. + if (isMacOS) + { + return Exec("/bin/zsh", execArgs); + } + + return Exec("/bin/sh", execArgs); + } + + /// + /// Gets what the length of the given string will be if it's + /// quote escaped for /bin/sh. + /// + /// The string to quote escape. + /// The length of the string when it's quote escaped. + private static int GetQuotedPathLength(string str) + { + int length = 2; + foreach (char c in str) + { + length++; + + if (c == '\'') + { + length++; + } + } + + return length; + } + + /// + /// Implements a SpanAction<T> for string.Create() + /// that builds the shell invocation for the login pwsh session. + /// + /// The buffer of the string to be created. + /// Information used to build the required string. + private static void CreatePwshInvocation( + Span strBuf, + (string path, int quotedLength) invocationInfo) + { + // "exec " + const string prefix = "exec "; + prefix.AsSpan().CopyTo(strBuf); + + // The quoted path to pwsh, like "'/opt/microsoft/powershell/7/pwsh'" + int i = prefix.Length; + Span pathSpan = strBuf.Slice(i, invocationInfo.quotedLength); + QuoteAndWriteToSpan(invocationInfo.path, pathSpan); + i += invocationInfo.quotedLength; + + // ' "$@"' the argument vector splat to pass pwsh arguments through + const string suffix = " \"$@\""; + Span bufSuffix = strBuf.Slice(i); + suffix.AsSpan().CopyTo(bufSuffix); + } + + /// + /// Quotes (and sh quote escapes) a string and writes it to the given span. + /// + /// The string to quote. + /// The span to write to. + private static void QuoteAndWriteToSpan(string arg, Span span) + { + span[0] = '\''; + + int i = 0; + int j = 1; + for (; i < arg.Length; i++, j++) + { + char c = arg[i]; + + if (c == '\'') + { + // /bin/sh quote escaping uses backslashes + span[j] = '\\'; + j++; + } + + span[j] = c; + } + + span[j] = '\''; + } + + /// + /// If the given exit code is negative, throws a StartupException. + /// + /// The native call that was attempted. + /// The exit code it returned. + private static void ThrowOnFailure(string call, int code) + { + if (code < 0) + { + code = Marshal.GetLastWin32Error(); + Console.Error.WriteLine($"Call to '{call}' failed with errno {code}"); + throw new StartupException(call, code); + } + } + + /// + /// 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. + /// + /// + /// An exit code if exec failed, but if successful the calling process will be overwritten. + /// + [DllImport("libc", + EntryPoint = "execv", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int Exec(string path, string?[] args); + + /// + /// The `readlink` POSIX syscall we use to read the symlink from /proc/self/exe + /// to get the executable path of pwsh on Linux. + /// + /// The path to the symlink to read. + /// Pointer to a buffer to fill with the result. + /// The size of the buffer we have supplied. + /// The number of bytes placed in the buffer. + [DllImport("libc", + EntryPoint = "readlink", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern IntPtr ReadLink(string pathname, IntPtr buf, UIntPtr size); + + /// + /// The `getpid` POSIX syscall we use to quickly get the current process PID on macOS. + /// + /// The pid of the current process. + [DllImport("libc", + EntryPoint = "getpid", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int GetPid(); + + /// + /// The `setenv` POSIX syscall used to set an environment variable in the process. + /// + /// The name of the environment variable. + /// The value of the environment variable. + /// If true, will overwrite an existing environment variable of the same name. + /// 0 if successful, -1 on error. errno indicates the reason for failure. + [DllImport("libc", + EntryPoint = "setenv", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int SetEnv(string name, string value, bool overwrite); + + /// + /// The `sysctl` BSD sycall used to get system information on macOS. + /// + /// The Management Information Base name, used to query information. + /// The length of the MIB name. + /// The object passed out of sysctl (may be null) + /// The size of the object passed out of sysctl. + /// The object passed in to sysctl. + /// The length of the object passed in to sysctl. + /// + [DllImport("libc", + EntryPoint = "sysctl", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + 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 new file mode 100644 index 00000000000..9653436690c --- /dev/null +++ b/stylecop.json @@ -0,0 +1,52 @@ +{ + "$schema" : "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings" : { + "indentation" : { + "indentationSize" : 4, + "tabSize" : 4, + "useTabs" : false + }, + "spacingRules" : { + }, + "readabilityRules" : { + "allowBuiltInTypeAliases" : true + }, + "orderingRules" : { + "elementOrder" : [ + "kind", + "constant", + "accessibility", + "static", + "readonly" + ], + "systemUsingDirectivesFirst" : true, + "usingDirectivesPlacement" : "outsideNamespace", + "blankLinesBetweenUsingGroups" : "allow" + }, + "namingRules" : { + "allowCommonHungarianPrefixes" : true, + "allowedHungarianPrefixes" : [ "n", "r", "l", "i", "io", "is", "fs", "lp", "dw", "h", "rs", "ps", "op", "sb", "my", "vt" ] + }, + "maintainabilityRules" : { + "topLevelTypes" : [ + "class", + "interface" + ] + }, + "layoutRules" : { + "newlineAtEndOfFile" : "require", + "allowConsecutiveUsings" : false + }, + "documentationRules" : { + "copyrightText" : "Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.", + "xmlHeader" : false, + "documentInterfaces" : true, + "documentExposedElements" : true, + "documentInternalElements" : false, + "documentPrivateElements" : false, + "documentPrivateFields" : false, + "documentationCulture" : "en-US", + "fileNamingConvention" : "stylecop" + } + } +} diff --git a/test/PSReadLine/App.config b/test/PSReadLine/App.config deleted file mode 100644 index 8e15646352e..00000000000 --- a/test/PSReadLine/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/test/PSReadLine/AssemblyInfo.cs b/test/PSReadLine/AssemblyInfo.cs deleted file mode 100644 index 4b8e4a20134..00000000000 --- a/test/PSReadLine/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TestPSReadLine")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TestPSReadLine")] -[assembly: AssemblyCopyright("Copyright (c) 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2892ed9f-6820-48a0-819b-0452c3b5401b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/PSReadLine/PSReadLine.tests.csproj b/test/PSReadLine/PSReadLine.tests.csproj deleted file mode 100644 index b8e2dd0e2e8..00000000000 --- a/test/PSReadLine/PSReadLine.tests.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - PSReadLine basic tests - TestPSReadLine - Exe - win7-x86;win7-x64;osx.10.12-x64;linux-x64 - - - - - - - diff --git a/test/PSReadLine/Program.cs b/test/PSReadLine/Program.cs deleted file mode 100644 index 0604aeb1048..00000000000 --- a/test/PSReadLine/Program.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using Microsoft.PowerShell; - -namespace TestPSReadLine -{ - class Program - { - static void CauseCrash(ConsoleKeyInfo? key = null, object arg = null) - { - throw new Exception("intentional crash for test purposes"); - } - - [STAThread] - static void Main() - { - //Box(new List {"abc", " def", "this is something coo"}); - - var iss = InitialSessionState.CreateDefault2(); - var rs = RunspaceFactory.CreateRunspace(iss); - rs.Open(); - Runspace.DefaultRunspace = rs; - - PSConsoleReadLine.SetOptions(new SetPSReadlineOption - { - EditMode = EditMode.Emacs, - HistoryNoDuplicates = true, - }); - PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+LeftArrow"}, PSConsoleReadLine.ShellBackwardWord, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+RightArrow"}, PSConsoleReadLine.ShellNextWord, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"F4"}, PSConsoleReadLine.HistorySearchBackward, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"F5"}, PSConsoleReadLine.HistorySearchForward, "", ""); - //PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+E"}, PSConsoleReadLine.EnableDemoMode, "", ""); - //PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+D"}, PSConsoleReadLine.DisableDemoMode, "", ""); - // PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+C"}, PSConsoleReadLine.CaptureScreen, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+P"}, PSConsoleReadLine.InvokePrompt, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"Ctrl+D,Ctrl+X"}, CauseCrash, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"F6"}, PSConsoleReadLine.PreviousLine, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"F7"}, PSConsoleReadLine.NextLine, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"F2"}, PSConsoleReadLine.ValidateAndAcceptLine, "", ""); - PSConsoleReadLine.SetKeyHandler(new[] {"Enter"}, PSConsoleReadLine.AcceptLine, "", ""); - - - EngineIntrinsics executionContext; - using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace)) - { - executionContext = - ps.AddScript("$ExecutionContext").Invoke().FirstOrDefault(); - - // This is a workaround to ensure the command analysis cache has been created before - // we enter into ReadLine. It's a little slow and infrequently needed, so just - // uncomment host stops responding, run it once, then comment it out again. - //ps.Commands.Clear(); - //ps.AddCommand("Get-Command").Invoke(); - } - - while (true) - { - Console.Write("TestHostPS> "); - - var line = PSConsoleReadLine.ReadLine(null, executionContext); - Console.WriteLine(line); - line = line.Trim(); - if (line.Equals("exit")) - Environment.Exit(0); - if (line.Equals("cmd")) - PSConsoleReadLine.SetOptions(new SetPSReadlineOption {EditMode = EditMode.Windows}); - if (line.Equals("emacs")) - PSConsoleReadLine.SetOptions(new SetPSReadlineOption {EditMode = EditMode.Emacs}); - if (line.Equals("vi")) - PSConsoleReadLine.SetOptions(new SetPSReadlineOption {EditMode = EditMode.Vi}); - if (line.Equals("nodupes")) - PSConsoleReadLine.SetOptions(new SetPSReadlineOption {HistoryNoDuplicates = true}); - } - } - } -} diff --git a/test/PSReadLine/packages.config b/test/PSReadLine/packages.config deleted file mode 100644 index cae6c6db89a..00000000000 --- a/test/PSReadLine/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/test/README.md b/test/README.md index 08e07d7a54b..d511995cbe0 100644 --- a/test/README.md +++ b/test/README.md @@ -4,8 +4,7 @@ Testing The tests are organized by testing language. Thus Pester tests, which are written in the PowerShell language, are in [./powershell](./powershell) and xUnit tests, written in C#, are in -[./csharp](./csharp). The sanity tests for the Full .NET build of -PowerShell are in [./fullclr](./fullclr), and the third-party +[./xUnit](./xUnit). The third-party [shebang][] test is in [./shebang](./shebang). [shebang]: https://en.wikipedia.org/wiki/Shebang_(Unix) diff --git a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 new file mode 100644 index 00000000000..1aa87ba3d69 --- /dev/null +++ b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 @@ -0,0 +1,405 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +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" { + 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" { + 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 = 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 = 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 = 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 = 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' + UserName = whoami + Port = 22 + KeyFilePath = "$HOME/.ssh/id_rsa" + Subsystem = 'powershell' + }, + @{ + HostName = 'localhost' + KeyFilePath = "$HOME/.ssh/id_rsa" + Subsystem = 'powershell' + }) + $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 { + param ( + [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 + { + $ps.Dispose() + } + } + + Context "SSH Remoting API Tests" { + + AfterEach { + 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 = @( + @{ + testName = 'Verifies connection with implicit user' + UserName = $null + ComputerName = 'localhost' + KeyFilePath = $null + Port = 0 + Subsystem = $null + }, + @{ + testName = 'Verifies connection with UserName' + UserName = whoami + ComputerName = 'localhost' + KeyFilePath = $null + Port = 0 + Subsystem = $null + }, + @{ + testName = 'Verifies connection with KeyFilePath' + UserName = whoami + ComputerName = 'localhost' + KeyFilePath = "$HOME/.ssh/id_rsa" + Port = 0 + Subsystem = $null + }, + @{ + testName = 'Verifies connection with Port specified' + UserName = whoami + ComputerName = 'localhost' + KeyFilePath = "$HOME/.ssh/id_rsa" + Port = 22 + Subsystem = $null + }, + @{ + testName = 'Verifies connection with Subsystem specified' + UserName = whoami + ComputerName = 'localhost' + KeyFilePath = "$HOME/.ssh/id_rsa" + Port = 22 + Subsystem = 'powershell' + } + ) + + It "" -TestCases $testCases { + param ( + $UserName, + $ComputerName, + $KeyFilePath, + $Port, + $SubSystem, + $TestName + ) + + 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 308e1cdbe84..8a5522e9eaa 100644 --- a/test/Test.Common.props +++ b/test/Test.Common.props @@ -1,13 +1,30 @@ + + - PowerShell Core Test + PowerShell Test Microsoft Corporation - (c) Microsoft Corporation. All rights reserved. + (c) Microsoft Corporation. - netcoreapp2.0 - 2.0.5 + net11.0 + preview true true + true + true + true + + + + strict + + + + true + + + + $(DefineConstants);UNIX diff --git a/test/common/markdown/gulpfile.js b/test/common/markdown/gulpfile.js deleted file mode 100644 index 4da3762d063..00000000000 --- a/test/common/markdown/gulpfile.js +++ /dev/null @@ -1,55 +0,0 @@ -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. - var 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'); - 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 = new Buffer(resultString); - } - next(err, file); - }); - })) - .pipe(concat("markdownissues.txt", { newLine: "\r\n" })) - .pipe(gulp.dest(".")); -}); diff --git a/test/common/markdown/markdown.tests.ps1 b/test/common/markdown/markdown.tests.ps1 deleted file mode 100644 index edd26487313..00000000000 --- a/test/common/markdown/markdown.tests.ps1 +++ /dev/null @@ -1,123 +0,0 @@ -$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 { - # Skip if not windows, We don't need these tests to run on linux (the tests run fine in travis-ci) - $skip = !$IsWindows - if ( !$skip ) - { - $NpmInstalled = "not installed" - if (Get-Command -Name 'npm' -ErrorAction SilentlyContinue) - { - $NpmInstalled = "Installed" - Write-Verbose -Message "NPM is checking Gulp is installed. This may take a few moments." -Verbose - Start-Process ` - -FilePath "npm" ` - -ArgumentList @('install','--silent') ` - -Wait ` - -WorkingDirectory $PSScriptRoot ` - -NoNewWindow - Start-Process ` - -FilePath "npm" ` - -ArgumentList @('install','-g','gulp','--silent') ` - -Wait ` - -WorkingDirectory $PSScriptRoot ` - -NoNewWindow - } - elseif( -not $env:AppVeyor) - { - <# - On Windows, but not an AppVeyor and 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 npm are required to run this test" - $skip = $true - } - } - } - - AfterAll { - if ( !$skip ) - { - <# - NPM install all the tools needed to run this test in the test folder. - We will now clean these up. - We're using this tool to delete the node_modules folder because it gets too long - for PowerShell to remove. - #> - Start-Process ` - -FilePath "npm" ` - -ArgumentList @('install','rimraf','-g','--silent') ` - -Wait ` - -WorkingDirectory $PSScriptRoot ` - -NoNewWindow - Start-Process ` - -FilePath "rimraf" ` - -ArgumentList @(Join-Path -Path $PSScriptRoot -ChildPath 'node_modules') ` - -Wait ` - -WorkingDirectory $PSScriptRoot ` - -NoNewWindow - } - } - - It "Should not have errors in any markdown files" -Skip:$skip { - $NpmInstalled | should BeExactly "Installed" - $mdErrors = 0 - Push-Location -Path $PSScriptRoot - try - { - $docsToTest = @( - './.github/CONTRIBUTING.md' - './*.md' - './demos/SSHRemoting/*.md' - './docker/*.md' - './docs/*.md' - './docs/building/*.md' - './docs/cmdlet-example/*.md' - './docs/git/submodules.md' - './docs/installation/*.md' - './docs/maintainers/*.md' - './docs/testing-guidelines/testing-guidelines.md' - './test/powershell/README.md' - './tools/*.md' - ) - $filter = ($docsToTest -join ',') - &"gulp" test-mdsyntax --silent ` - --rootpath $repoRootPath ` - --filter $filter - - } - catch - { - Write-Warning -Message ("Unable to run gulp to test markdown files. Please " + ` - "be sure that you have installed nodejs and have " + ` - "run 'npm install -g gulp' in order to have this " + ` - "text execute.") - } - finally - { - Pop-Location - } - - $LASTEXITCODE | Should beexactly 0 - - $mdIssuesPath = Join-Path -Path $PSScriptRoot -ChildPath "markdownissues.txt" - - $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 | Should BeExactly "--EMPTY--" - } -} diff --git a/test/common/markdown/package-lock.json b/test/common/markdown/package-lock.json deleted file mode 100644 index 690e1a3b376..00000000000 --- a/test/common/markdown/package-lock.json +++ /dev/null @@ -1,1567 +0,0 @@ -{ - "name": "powershell.common.markdown.tests", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "1.1.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" - }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" - }, - "array-slice": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.0.0.tgz", - "integrity": "sha1-5zA08A3MH0CHYAj9IP6ud71LfC8=" - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" - }, - "cloneable-readable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz", - "integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "process-nextick-args": "1.0.7", - "through2": "2.0.3" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-with-sourcemaps": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz", - "integrity": "sha1-9Vs74q60dgGxCi1SWcz7cP0vHdY=", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "requires": { - "clone": "1.0.2" - } - }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=" - }, - "detect-file": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", - "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", - "requires": { - "fs-exists-sync": "0.1.0" - } - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "requires": { - "readable-stream": "1.1.14" - } - }, - "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", - "requires": { - "once": "1.3.3" - } - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "requires": { - "fill-range": "2.2.3" - } - }, - "expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", - "requires": { - "os-homedir": "1.0.2" - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "1.0.0" - } - }, - "fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", - "requires": { - "chalk": "1.1.3", - "time-stamp": "1.1.0" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" - }, - "findup-sync": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", - "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", - "requires": { - "detect-file": "0.1.0", - "is-glob": "2.0.1", - "micromatch": "2.3.11", - "resolve-dir": "0.1.1" - } - }, - "fined": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", - "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", - "requires": { - "expand-tilde": "2.0.2", - "is-plain-object": "2.0.4", - "object.defaults": "1.1.0", - "object.pick": "1.3.0", - "parse-filepath": "1.0.1" - }, - "dependencies": { - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "requires": { - "homedir-polyfill": "1.0.1" - } - } - } - }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=" - }, - "flagged-respawn": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", - "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "1.0.2" - } - }, - "fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=" - }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", - "requires": { - "globule": "0.1.0" - } - }, - "get-own-enumerable-property-symbols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", - "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==", - "dev": true - }, - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "2.0.10", - "once": "1.3.3" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "2.0.1" - } - }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "requires": { - "glob": "4.5.3", - "glob2base": "0.0.12", - "minimatch": "2.0.10", - "ordered-read-streams": "0.1.0", - "through2": "0.6.5", - "unique-stream": "1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" - } - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "requires": { - "gaze": "0.5.2" - } - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "requires": { - "find-index": "0.1.1" - } - }, - "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "requires": { - "global-prefix": "0.1.5", - "is-windows": "0.2.0" - } - }, - "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "requires": { - "homedir-polyfill": "1.0.1", - "ini": "1.3.4", - "is-windows": "0.2.0", - "which": "1.3.0" - } - }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "requires": { - "glob": "3.1.21", - "lodash": "1.0.2", - "minimatch": "0.2.14" - }, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "requires": { - "graceful-fs": "1.2.3", - "inherits": "1.0.2", - "minimatch": "0.2.14" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" - } - } - } - }, - "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", - "requires": { - "sparkles": "1.0.0" - } - }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "1.1.0" - } - }, - "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "requires": { - "archy": "1.0.0", - "chalk": "1.1.3", - "deprecated": "0.0.1", - "gulp-util": "3.0.8", - "interpret": "1.0.4", - "liftoff": "2.3.0", - "minimist": "1.2.0", - "orchestrator": "0.3.8", - "pretty-hrtime": "1.0.3", - "semver": "4.3.6", - "tildify": "1.2.0", - "v8flags": "2.1.1", - "vinyl-fs": "0.3.14" - } - }, - "gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", - "dev": true, - "requires": { - "concat-with-sourcemaps": "1.0.4", - "through2": "2.0.3", - "vinyl": "2.1.0" - }, - "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", - "dev": true, - "requires": { - "clone": "2.1.1", - "clone-buffer": "1.0.0", - "clone-stats": "1.0.0", - "cloneable-readable": "1.0.0", - "remove-trailing-separator": "1.1.0", - "replace-ext": "1.0.0" - } - } - } - }, - "gulp-debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/gulp-debug/-/gulp-debug-3.1.0.tgz", - "integrity": "sha1-TakVaLVJFb6ANpbKqsEMiVssCnE=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "gulp-util": "3.0.8", - "plur": "2.1.2", - "stringify-object": "3.2.1", - "through2": "2.0.3", - "tildify": "1.2.0" - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "requires": { - "array-differ": "1.0.0", - "array-uniq": "1.0.3", - "beeper": "1.1.1", - "chalk": "1.1.3", - "dateformat": "2.2.0", - "fancy-log": "1.3.0", - "gulplog": "1.0.0", - "has-gulplog": "0.1.0", - "lodash._reescape": "3.0.0", - "lodash._reevaluate": "3.0.0", - "lodash._reinterpolate": "3.0.0", - "lodash.template": "3.6.2", - "minimist": "1.2.0", - "multipipe": "0.1.2", - "object-assign": "3.0.0", - "replace-ext": "0.0.1", - "through2": "2.0.3", - "vinyl": "0.5.3" - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "requires": { - "glogg": "1.0.0" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "requires": { - "sparkles": "1.0.0" - } - }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "requires": { - "parse-passwd": "1.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.3.3", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" - }, - "interpret": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", - "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=" - }, - "irregular-plurals": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.3.0.tgz", - "integrity": "sha512-njf5A+Mxb3kojuHd1DzISjjIl+XhyzovXEOyPPSzdQozq/Lf2tN27mOrAAsxEPZxpn6I4MGzs1oo9TxXxPFpaA==", - "dev": true - }, - "is-absolute": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", - "requires": { - "is-relative": "0.2.1", - "is-windows": "0.2.0" - } - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "requires": { - "kind-of": "3.2.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-relative": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", - "requires": { - "is-unc-path": "0.1.2" - } - }, - "is-unc-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", - "requires": { - "unc-path-regex": "0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "1.1.5" - } - }, - "liftoff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", - "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", - "requires": { - "extend": "3.0.1", - "findup-sync": "0.4.3", - "fined": "1.1.0", - "flagged-respawn": "0.3.2", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.mapvalues": "4.6.0", - "rechoir": "0.6.2", - "resolve": "1.4.0" - } - }, - "linkify-it": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", - "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", - "requires": { - "uc.micro": "1.0.3" - } - }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=" - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "requires": { - "lodash._root": "3.0.1" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, - "lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=" - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "requires": { - "lodash._basecopy": "3.0.1", - "lodash._basetostring": "3.0.1", - "lodash._basevalues": "3.0.0", - "lodash._isiterateecall": "3.0.9", - "lodash._reinterpolate": "3.0.0", - "lodash.escape": "3.2.0", - "lodash.keys": "3.1.2", - "lodash.restparam": "3.6.1", - "lodash.templatesettings": "3.1.1" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "requires": { - "lodash._reinterpolate": "3.0.0", - "lodash.escape": "3.2.0" - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "markdown-it": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-7.0.1.tgz", - "integrity": "sha1-8S2LiKk+ZCVDSN/Rg71wv2BWekI=", - "requires": { - "argparse": "1.0.9", - "entities": "1.1.1", - "linkify-it": "2.0.3", - "mdurl": "1.0.1", - "uc.micro": "1.0.3" - } - }, - "markdownlint": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.2.0.tgz", - "integrity": "sha1-BaS6Oup7tdoI0rJ8mK+gPrQpsmI=", - "requires": { - "markdown-it": "7.0.1" - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "requires": { - "duplexer2": "0.0.2" - } - }, - "natives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=" - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" - }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "requires": { - "array-each": "1.0.1", - "array-slice": "1.0.0", - "for-own": "1.0.0", - "isobject": "3.0.1" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "requires": { - "for-in": "1.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "requires": { - "wrappy": "1.0.2" - } - }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "requires": { - "end-of-stream": "0.1.5", - "sequencify": "0.0.7", - "stream-consume": "0.1.0" - } - }, - "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "parse-filepath": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", - "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", - "requires": { - "is-absolute": "0.2.6", - "map-cache": "0.2.2", - "path-root": "0.1.1" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "requires": { - "path-root-regex": "0.1.2" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" - }, - "plur": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true, - "requires": { - "irregular-plurals": "1.3.0" - } - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "1.1.5" - } - } - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "1.4.0" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" - }, - "resolve": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", - "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", - "requires": { - "expand-tilde": "1.2.2", - "global-modules": "0.2.3" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" - }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=" - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "sparkles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "stream-consume": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", - "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "stringify-object": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz", - "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "2.0.1", - "is-obj": "1.0.1", - "is-regexp": "1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "requires": { - "first-chunk-stream": "1.0.0", - "is-utf8": "0.2.1" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "requires": { - "readable-stream": "2.3.3", - "xtend": "4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - } - } - }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "requires": { - "os-homedir": "1.0.2" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" - }, - "uc.micro": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz", - "integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI=" - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" - }, - "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=" - }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "requires": { - "user-home": "1.1.1" - } - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "requires": { - "clone": "1.0.2", - "clone-stats": "0.0.1", - "replace-ext": "0.0.1" - } - }, - "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "requires": { - "defaults": "1.0.3", - "glob-stream": "3.1.18", - "glob-watcher": "0.0.6", - "graceful-fs": "3.0.11", - "mkdirp": "0.5.1", - "strip-bom": "1.0.0", - "through2": "0.6.5", - "vinyl": "0.4.6" - }, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "requires": { - "clone": "0.2.0", - "clone-stats": "0.0.1" - } - } - } - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "requires": { - "isexe": "2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - } - } -} diff --git a/test/common/markdown/package.json b/test/common/markdown/package.json deleted file mode 100644 index f1b4661e622..00000000000 --- a/test/common/markdown/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "powershell.common.markdown.tests", - "version": "1.0.0", - "description": "The PowerShell Common MarkDown Tests.", - "main": "gulpfile.js", - "dependencies": { - "gulp": "^3.9.1", - "through2": "^2.0.1", - "markdownlint": "^0.2.0" - }, - "devDependencies": { - "gulp-concat": "^2.6.1", - "gulp-debug": "^3.0.0", - "markdownlint": "^0.4.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/csharp/README.md b/test/csharp/README.md deleted file mode 100644 index 18979b0552a..00000000000 --- a/test/csharp/README.md +++ /dev/null @@ -1,20 +0,0 @@ -xUnit Tests -=========== - -These tests are completely Linux specific. - -Every test class *must* belong to -`[Collection("AssemblyLoadContext")]`. This ensures that PowerShell's -AssemblyLoadContext is initialized before any other code is executed. -When this is not the case, late initialization fails with -`System.InvalidOperationException : Binding model is already locked -for the AppDomain and cannot be reset.` - -Having every class in the same collection is as close to an xUnit -global init hook as can be done. - -Running xUnit Tests -------------------- - -Go to the top level of the PowerShell repository and run: -`Start-PSxUnit` inside a self-hosted copy of PowerShell. diff --git a/test/csharp/csharp.tests.csproj b/test/csharp/csharp.tests.csproj deleted file mode 100644 index dcd35236b3a..00000000000 --- a/test/csharp/csharp.tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - PowerShell xUnit Tests - powershell-tests - win7-x86;win7-x64;osx.10.12-x64;linux-x64 - - - - true - ../../src/signing/visualstudiopublic.snk - true - - - - - - - - - - - - - - - - diff --git a/test/csharp/test_FileSystemProvider.cs b/test/csharp/test_FileSystemProvider.cs deleted file mode 100644 index 340c648356b..00000000000 --- a/test/csharp/test_FileSystemProvider.cs +++ /dev/null @@ -1,194 +0,0 @@ -using Xunit; -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.Host; -using System.Management.Automation.Internal; -using System.Management.Automation.Internal.Host; -using System.Management.Automation.Provider; -using System.Management.Automation.Runspaces; -using Microsoft.PowerShell; -using Microsoft.PowerShell.Commands; -using System.Reflection; - -namespace PSTests.Parallel -{ - public class FileSystemProviderTests: IDisposable - { - private string testPath; - private string testContent; - public FileSystemProviderTests() - { - testPath = Path.Combine(Path.GetTempPath(),"test"); - testContent = "test content!"; - if(File.Exists(testPath)) File.Delete(testPath); - File.AppendAllText(testPath,testContent); - } - void IDisposable.Dispose() - { - File.Delete(testPath); - } - - private ExecutionContext GetExecutionContext() - { - CultureInfo currentCulture = CultureInfo.CurrentCulture; - PSHost hostInterface = new DefaultHost(currentCulture,currentCulture); - InitialSessionState iss = InitialSessionState.CreateDefault2(); - AutomationEngine engine = new AutomationEngine(hostInterface, iss); - ExecutionContext executionContext = new ExecutionContext(engine, hostInterface, iss); - return executionContext; - } - private ProviderInfo GetProvider() - { - ExecutionContext executionContext = GetExecutionContext(); - SessionStateInternal sessionState = new SessionStateInternal(executionContext); - - SessionStateProviderEntry providerEntry = new SessionStateProviderEntry("FileSystem",typeof(FileSystemProvider), null); - sessionState.AddSessionStateEntry(providerEntry); - ProviderInfo matchingProvider = sessionState.ProviderList.ToList()[0]; - - return matchingProvider; - } - - [Fact] - public void TestCreateJunctionFails() - { - if(!Platform.IsWindows) - { - Assert.False(InternalSymbolicLinkLinkCodeMethods.CreateJunction("","")); - } - else - { - Assert.Throws(delegate { InternalSymbolicLinkLinkCodeMethods.CreateJunction("",""); }); - } - } - - [Fact] - public void TestGetHelpMaml() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - Assert.Equal(fileSystemProvider.GetHelpMaml(String.Empty,String.Empty),String.Empty); - Assert.Equal(fileSystemProvider.GetHelpMaml("helpItemName",String.Empty),String.Empty); - Assert.Equal(fileSystemProvider.GetHelpMaml(String.Empty,"path"),String.Empty); - } - - [Fact] - public void TestMode() - { - Assert.Equal(FileSystemProvider.Mode(null),String.Empty); - FileSystemInfo directoryObject = null; - FileSystemInfo fileObject = null; - FileSystemInfo executableObject = null; - - if(!Platform.IsWindows) - { - directoryObject = new DirectoryInfo(@"/"); - fileObject = new FileInfo(@"/etc/hosts"); - executableObject = new FileInfo(@"/bin/echo"); - } - else - { - directoryObject = new DirectoryInfo(System.Environment.CurrentDirectory); - fileObject = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location); - executableObject = new FileInfo(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); - } - - Assert.Equal("d-----", FileSystemProvider.Mode(PSObject.AsPSObject(directoryObject)).Replace("r","-")); - Assert.Equal("------", FileSystemProvider.Mode(PSObject.AsPSObject(fileObject)).Replace("r","-").Replace("a","-")); - Assert.Equal("------", FileSystemProvider.Mode(PSObject.AsPSObject(executableObject)).Replace("r","-").Replace("a","-")); - } - - [Fact] - public void TestGetProperty() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - PSObject pso=new PSObject(); - pso.AddOrSetProperty("IsReadOnly",false); - fileSystemProvider.SetProperty(testPath, pso); - fileSystemProvider.GetProperty(testPath, new Collection(){"IsReadOnly"}); - FileInfo fileSystemObject1 = new FileInfo(testPath); - PSObject psobject1=PSObject.AsPSObject(fileSystemObject1); - foreach(PSPropertyInfo property in psobject1.Properties) - { - if(property.Name == "IsReadOnly") - { - Assert.False((bool)property.Value); - } - } - } - - [Fact] - public void TestSetProperty() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - fileSystemProvider.GetProperty(testPath, new Collection(){"Name"}); - FileInfo fileSystemObject1 = new FileInfo(testPath); - PSObject psobject1=PSObject.AsPSObject(fileSystemObject1); - foreach(PSPropertyInfo property in psobject1.Properties) - { - if(property.Name == "Name") - { - Assert.Equal("test", property.Value); - } - } - } - - [Fact] - public void TestClearProperty() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - fileSystemProvider.ClearProperty(testPath, new Collection(){"Attributes"}); - } - - [Fact] - public void TestGetContentReader() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - - IContentReader contentReader = fileSystemProvider.GetContentReader(testPath); - Assert.Equal(contentReader.Read(1)[0],testContent); - contentReader.Close(); - } - - [Fact] - public void TestGetContentWriter() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - - IContentWriter contentWriter = fileSystemProvider.GetContentWriter(testPath); - contentWriter.Write(new List(){"contentWriterTestContent"}); - contentWriter.Close(); - Assert.Equal(File.ReadAllText(testPath), testContent+@"contentWriterTestContent"+ System.Environment.NewLine); - } - - [Fact] - public void TestClearContent() - { - FileSystemProvider fileSystemProvider = new FileSystemProvider(); - ProviderInfo providerInfoToSet = GetProvider(); - fileSystemProvider.SetProviderInformation(providerInfoToSet); - fileSystemProvider.Context = new CmdletProviderContext(GetExecutionContext()); - fileSystemProvider.ClearContent(testPath); - Assert.Empty(File.ReadAllText(testPath)); - } - } -} diff --git a/test/csharp/test_Runspace.cs b/test/csharp/test_Runspace.cs deleted file mode 100644 index 0407cbae6cc..00000000000 --- a/test/csharp/test_Runspace.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Xunit; -using System; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace PSTests.Parallel -{ - // NOTE: do not call AddCommand("out-host") after invoking or MergeMyResults, - // otherwise Invoke will not return any objects - - public class RunspaceTests - { - private static int count = 1; - private static string script = String.Format($"get-command get-command"); - - [Fact] - public void TestRunspaceWithPipeline() - { - using (Runspace runspace = RunspaceFactory.CreateRunspace()) - { - runspace.Open(); - - using (var pipeline = runspace.CreatePipeline(script)) - { - int objCount = 0; - foreach (var result in pipeline.Invoke()) - { - ++objCount; - Assert.NotNull(result); - } - Assert.Equal(count, objCount); - } - - runspace.Close(); - } - } - - [Fact] - public void TestRunspaceWithPowerShell() - { - using (var runspace = RunspaceFactory.CreateRunspace()) - { - runspace.Open(); - - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspace; - - powerShell.AddScript(script); - - int objCount = 0; - foreach (var result in powerShell.Invoke()) - { - ++objCount; - Assert.NotNull(result); - } - Assert.Equal(count, objCount); - } - - runspace.Close(); - } - } - - - [Fact] - public void TestRunspaceWithPowerShellAndInitialSessionState() - { - InitialSessionState iss = InitialSessionState.CreateDefault2(); - - // NOTE: instantiate custom host myHost for the next line to capture stdout and stderr output - // in addition to just the PSObjects - using (Runspace runspace = RunspaceFactory.CreateRunspace(/*myHost,*/iss)) - { - runspace.Open(); - using (PowerShell powerShell = PowerShell.Create()) - { - powerShell.Runspace = runspace; - powerShell.AddScript("Import-Module Microsoft.PowerShell.Utility -Force"); - powerShell.AddScript(script); - - int objCount = 0; - - var results = powerShell.Invoke(); - - foreach (var result in results) - { - // this is how an object would be captured here and looked at, - // each result is a PSObject with the data from the pipeline - ++objCount; - Assert.NotNull(result); - } - Assert.Equal(count, objCount); - powerShell.Dispose(); - } - } - } - } -} diff --git a/test/csharp/test_SecuritySupport.cs b/test/csharp/test_SecuritySupport.cs deleted file mode 100644 index 040c90dcc4d..00000000000 --- a/test/csharp/test_SecuritySupport.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Xunit; -using System; -using System.Management.Automation; - -namespace PSTests.Parallel -{ - public static class SecuritySupportTests - { - [Fact] - public static void TestScanContent() - { - Assert.Equal(AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED, AmsiUtils.ScanContent("", "")); - } - - [Fact] - public static void TestCloseSession() - { - AmsiUtils.CloseSession(); - } - - [Fact] - public static void TestUninitialize() - { - AmsiUtils.Uninitialize(); - } - } -} diff --git a/test/csharp/test_Utils.cs b/test/csharp/test_Utils.cs deleted file mode 100644 index ea9fc915065..00000000000 --- a/test/csharp/test_Utils.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Xunit; -using System; -using System.Management.Automation; -using System.Reflection; - -namespace PSTests.Parallel -{ - public static class UtilsTests - { - [SkippableFact] - public static void TestIsWinPEHost() - { - Skip.IfNot(Platform.IsWindows); - Assert.False(Utils.IsWinPEHost()); - } - } -} diff --git a/test/docker/networktest/DockerRemoting.Tests.ps1 b/test/docker/networktest/DockerRemoting.Tests.ps1 deleted file mode 100644 index 06e61b89350..00000000000 --- a/test/docker/networktest/DockerRemoting.Tests.ps1 +++ /dev/null @@ -1,87 +0,0 @@ -$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 $timeout - Write-Verbose -verbose "setting up docker container PowerShell client" - $client = docker run -d $imageName powershell -c start-sleep $timeout - - # get fullpath to installed core powershell - Write-Verbose -verbose "Getting path to PowerShell core" - $powershellcorepath = docker exec $server powershell -c "(get-childitem 'c:\program files\powershell\*\pwsh.exe').fullname" - if ( ! $powershellcorepath ) - { - $pending = $true - write-warning "Cannot find powershell core 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 core version" - $coreVersion = docker exec $client "$powershellcorepath" -c "`$psversiontable.psversion.tostring()" - if ( ! $coreVersion ) - { - $pending = $true - write-warning "Cannot determine PowerShell core 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 core 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 core 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/docker/networktest/New-DockerTestBuild.ps1 b/test/docker/networktest/New-DockerTestBuild.ps1 deleted file mode 100644 index 5cb9927d305..00000000000 --- a/test/docker/networktest/New-DockerTestBuild.ps1 +++ /dev/null @@ -1,292 +0,0 @@ -param ( [switch]$Force, [switch]$UseExistingMsi ) - -$script:Constants = @{ - AccountName = 'PowerShell' - UrlBase = 'https://ci.appveyor.com' - ApiUrl = 'https://ci.appveyor.com/api' - ProjectName = 'powershell-f975h' - TestImageName = "remotetestimage" - MsiName = "PSCore.msi" - Token = "" # in this particular use we don't need a token -} - -function Get-AppVeyorBuildArtifact { - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [string]$build, - [string]$downloadFolder = '.', - [string]$artifactFilter = '*.msi', - [string]$artifactLocalFileName = $Constants.MsiName - ) - - $headers = @{ - 'Authorization' = "Bearer {0}" -f $Constants.Token - 'Content-type' = 'application/json' - } - - # get project with last build details - $URL = "{0}/projects/{1}/{2}/build/$build" -f $Constants.ApiUrl,$Constants.AccountName,$Constants.ProjectName,$build - $project = Invoke-RestMethod -Method Get -Uri $URL -Headers $headers - - # we assume here that build has a single job - # get this job id - $jobId = $project.build.jobs[0].jobId - - # get job artifacts (just to see what we've got) - $URL = "{0}/buildjobs/{1}/artifacts" -f $Constants.ApiUrl,$jobid - $artifacts = Invoke-RestMethod -Method Get -Uri $URL -Headers $headers - - # here we just take the first artifact, but you could specify its file name - # $artifactFileName = 'MyWebApp.zip' - $artifact = $artifacts | Where-Object {$_.fileName -like $artifactFilter} | Select-Object -First 1 - $artifactFileName = $artifact.fileName - - # artifact will be downloaded as - $localArtifactPath = "$downloadFolder\$artifactLocalFileName" - - # download artifact - # -OutFile - is local file name where artifact will be downloaded into - $URI = "{0}/buildjobs/{1}/artifacts/{2}" -f $Constants.ApiUrl,$jobId,$artifactFileName - Invoke-RestMethod -Method Get -Uri $URI -OutFile $localArtifactPath -Headers $headers - - return $localArtifactPath -} - -# Get a collection of appveyor objects representing the builds for the last day -function Get-AppVeyorBuilds -{ - [CmdletBinding()] - param( - [ValidateNotNullOrEmpty()] - [string]$ExtensionBranch = 'master', - [ValidateRange(1,7)][int]$Days = 7, - [int]$Last = 1 - ) - - [datetime]$start = ((get-date).AddDays(-${Days})) - $results = Get-AppVeyorBuildRange -Start $start -LastBuildId $null -ExtensionBranch $ExtensionBranch - $results -} - -function Get-AppVeyorBuildRange -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory=$true, Position=0)] - [string] - $ExtensionBranch = 'dev', - - [Parameter(Mandatory=$true, Position=1)] - [datetime] - $Start, - - [Parameter(Mandatory=$true, Position=2)] - [AllowNull()] - [Object] - $LastBuildId, - - [Parameter(Mandatory=$false, Position=3)] - [int] - $Records = 20, - - [switch] - $IncludeRunning - ) - - $result = @{ - builds = @() - LastBuildId = '' - FoundLast = $false - } - - if($LastBuildId) - { - $startBuildIdString = "&startBuildId=$LastBuildId" - } - - $URI = "{0}/projects/{1}/{2}/history?recordsNumber={3}{4}&branch{5}" -f $Constants.ApiUrl,$Constants.AccountName, - $Constants.ProjectName,$Records,$startBuildIdString,$ExtensionBranch - $project = Invoke-RestMethod -Method Get -Uri $URI - - foreach($build in $project.builds) - { - if($build.Status -ne 'running' -or $IncludeRunning) - { - $createdString = $build.created - $created = [datetime]::Parse($createdString) - - if($created -gt $Start) - { - $version = $build.version - $result.lastBuildId = $build.buildId - $URI = "{0}/projects/{1}/{2}/build/{3}" -f $Constants.ApiUrl, $Constants.AccountName, - $Constants.ProjectName,$version - $buildProject = Invoke-RestMethod -Method Get -Uri $URI -Headers $headers -verbose:$false - - $result.builds += Convert-AppVeyorBuildJson -build $buildProject.Build - } - else - { - $result.foundLast = $true - } - } - } - - return $result -} - -# Convert AppVeyor Build Json into a more usable PSObject -function Convert-AppVeyorBuildJson -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory=$true, Position=0)] - [Object] - $build - ) - - $Job = $build.jobs[0] - $status = $build.status - [datetime] $started = [datetime]::MinValue - [datetime] $finished = [datetime]::MaxValue - - if($build.started) - { - [datetime] $started = [datetime]::Parse($build.started) - } - if($build.finished) - { - [datetime] $finished = [datetime]::Parse($build.finished) - } - - $duration = $null - - if($status -ne 'running') - { - $duration = $finished.Subtract($started) - } - - $version = $build.version - - $link = 'Results' -f $Constants.BaseUrl,$Constants.AccountName, $Constants.ProjectName, $version - $tests = @() - $tag = [string]::Empty - - if($build.message.StartsWith('[') -and $build.message.Contains(']')) - { - $tag = $build.message.Substring(1,$build.message.IndexOf(']')).Replace(']','') - if($tag.Contains('(')) - { - $tagParts = $tag.Split('(') - $tag = $tagParts[0] - for($i=1;$i -lt $tagParts.Count; $i++) - { - $tests += $tagParts[$i].Replace(')','') - } - } - $tag = $tag - } - - $testString = $tests -join ',' - - $result = [PsCustomObject]@{ - version = $version - Id = $build.BuildId - author = $build.authorName - branch = $Build.branch - status = $status - started = $started - StartedDay = [datetime]$started.Date - finished = $finished - duration = $duration - message = $build.message - testsCount = $Job.testsCount - passedTestsCount = $Job.passedTestsCount - failedTestsCount = $Job.failedTestsCount - link = $link - JobId = $Job.JobId - Tests = $testString - Tag = $tag - } - - $result.pstypenames.Clear() - $result.pstypenames.Add('AppVeyorBuildSummary') - $result -} - -############ -### MAIN ### -############ - -#### DOCKER OPS ##### -# is docker installed? -$dockerExe = get-command docker -ea silentlycontinue -if ( $dockerExe.name -ne "docker.exe" ) { - throw "Cannot find docker, is it installed?" -} -# Check to see if we already have an image, and if so -# delete it if -Force was used, otherwise throw and exit -$TestImage = docker images $Constants.TestImageName --format '{{.Repository}}' -if ( $TestImage -eq $Constants.TestImageName) -{ - if ( $Force ) - { - docker rmi $Constants.TestImageName - } - else - { - throw ("{0} already exists, use '-Force' to remove" -f $Constants.TestImageName) - } -} -# check again - there could be some permission problems -$TestImage = docker images $Constants.TestImageName --format '{{.Repository}}' -if ( $TestImage -eq $Constants.TestImageName) -{ - throw ("'{0}' still exists, giving up" -f $Constants.TestImageName) -} - -#### MSI CHECKS #### -# check to see if the MSI is present -$MsiExists = test-path $Constants.MsiName -$msg = "{0} exists, use -Force to remove or -UseExistingMsi to use" -f $Constants.MsiName -if ( $MsiExists -and ! ($force -or $useExistingMsi)) -{ - throw $msg -} - -# remove the msi -if ( $MsiExists -and $Force -and ! $UseExistingMsi ) -{ - Remove-Item -force $Constants.MsiName - $MsiExists = $false -} - -# a couple of checks before downloading or using the existing one -# if the msi exists and -UseExistingMsi is present, we'll use the -# one we found -if ( ! $MsiExists -and $UseExistingMsi ) -{ - throw ("{0} does not exist" -f $Constants.MsiName) -} -elseif ( $MsiExists -and ! $UseExistingMsi ) -{ - throw $msg -} -elseif ( ! ($MsiExists -and $UseExistingMsi) ) # download the msi -{ - $builds = Get-AppVeyorBuilds -ExtensionBranch master - $build = $builds.builds | sort-object finished | select-object -last 1 - Get-AppVeyorBuildArtifact -build $build.version -} - -# last check before bulding the image -if ( ! (test-path $Constants.MsiName) ) -{ - throw ("{0} does not exist, giving up" -f $Constants.MsiName) -} - -# collect the builds and select the last one -Docker build --tag $Constants.TestImageName . 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/hosting.tests.csproj b/test/hosting/hosting.tests.csproj new file mode 100644 index 00000000000..1c40094c7ff --- /dev/null +++ b/test/hosting/hosting.tests.csproj @@ -0,0 +1,32 @@ + + + + + + PowerShell hosting SDK xUnit Tests + powershell-hosting-tests + + + + true + ../../src/signing/visualstudiopublic.snk + true + + + + + + + + + + + + + + + + + + + diff --git a/test/hosting/test_HostingBasic.cs b/test/hosting/test_HostingBasic.cs new file mode 100644 index 00000000000..1b4a67707e2 --- /dev/null +++ b/test/hosting/test_HostingBasic.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Management.Automation; +using System.Security; +using Microsoft.Management.Infrastructure; +using Microsoft.PowerShell; +using Xunit; + +namespace PowerShell.Hosting.SDK.Tests +{ + public static class HostingTests + { + [Fact] + public static void TestCommandFromUtility() + { + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript("Get-Verb -Verb get").Invoke(); + + foreach (dynamic item in results) + { + Assert.Equal("Get", item.Verb); + } + } + } + + [Fact] + public static void TestCommandFromManagement() + { + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var path = Environment.CurrentDirectory; + var results = ps.AddCommand("Test-Path").AddParameter("Path", path).Invoke(); + + foreach (dynamic item in results) + { + Assert.True(item); + } + } + } + + [Fact] + public static void TestCommandFromCore() + { + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript(@"$i = 0 ; 1..3 | ForEach-Object { $i += $_} ; $i").Invoke(); + + foreach (dynamic item in results) + { + Assert.Equal(6, item); + } + } + } + + [SkippableFact] + public static void TestCommandFromMMI() + { + // Test is disabled since we do not have a CimCmdlets module released in the SDK. + Skip.IfNot(Platform.IsWindows); + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript("[Microsoft.Management.Infrastructure.CimInstance]::new('Win32_Process')").Invoke(); + Assert.True(results.Count > 0); + } + } + + [SkippableFact] + public static void TestCommandFromDiagnostics() + { + Skip.IfNot(Platform.IsWindows); + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript("Get-WinEvent -ListLog Application").Invoke(); + + foreach (dynamic item in results) + { + Assert.Equal("Application", item.LogName); + } + } + } + + [SkippableFact] + public static void TestCommandFromSecurity() + { + Skip.IfNot(Platform.IsWindows); + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript("ConvertTo-SecureString -String test -AsPlainText -Force").Invoke(); + Assert.IsType(results[0]); + } + } + + [SkippableFact] + public static void TestCommandFromWSMan() + { + Skip.IfNot(Platform.IsWindows); + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + var results = ps.AddScript("Test-WSMan").Invoke(); + + foreach (dynamic item in results) + { + Assert.Equal("Microsoft Corporation", item.ProductVendor); + } + } + } + + [Fact] + public static void TestCommandFromNative() + { + var fs = File.Create(Path.GetTempFileName()); + fs.Close(); + + string target = fs.Name; + string path = Path.GetTempFileName(); + + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + // New-Item -ItemType SymbolicLink uses libpsl-native, hence using it for validating native dependencies. + string command = $"New-Item -ItemType SymbolicLink -Path {path} -Target {target}"; + var results = ps.AddScript(command).Invoke(); + + foreach (var item in results) + { + Assert.Equal(path, item.FullName); + } + } + + if (File.Exists(path)) + { + File.Delete(path); + } + + if (File.Exists(target)) + { + File.Delete(target); + } + } + + /// + /// Reference assemblies should be handled correctly so that Add-Type works in the hosting scenario. + /// + [Fact] + public static void TestAddTypeCmdletInHostScenario() + { + string code = @" + using System; + public class Foo + { + public Foo(string name, string path) + { + this.Name = name; + this.Path = path; + } + + public string Name; + public string Path; + } + "; + + using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) + { + ps.AddCommand("Add-Type").AddParameter("TypeDefinition", code).Invoke(); + ps.Commands.Clear(); + + var results = ps.AddScript("[Foo]::new('Joe', 'Unknown')").Invoke(); + Assert.Single(results); + + dynamic foo = results[0]; + Assert.Equal("Joe", foo.Name); + Assert.Equal("Unknown", foo.Path); + } + } + + [Fact] + public static void TestConsoleShellScenario() + { + int ret = ConsoleShell.Start("Hello", string.Empty, new string[] { "-noprofile", "-c", "exit 42" }); + 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() + { + var iss = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault2(); + if (System.Management.Automation.Platform.IsWindows) + { + iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; + } + + using var runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(iss); + runspace.Open(); + + using var ps = System.Management.Automation.PowerShell.Create(runspace); + var results_1 = ps.AddScript("Write-Output Hello > $null; Get-Module").Invoke(); + Assert.Single(results_1); + + var module = results_1[0]; + Assert.Equal("Microsoft.PowerShell.Utility", module.Name); + + ps.Commands.Clear(); + var results_2 = ps.AddScript("Join-Path $PSHOME 'Modules' 'Microsoft.PowerShell.Utility' 'Microsoft.PowerShell.Utility.psd1'").Invoke(); + var moduleManifestPath = results_2[0]; + Assert.Equal(moduleManifestPath, module.Path, ignoreCase: true); + } + } +} 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/nanoserver/nanoserver.tests.ps1 b/test/nanoserver/nanoserver.tests.ps1 new file mode 100644 index 00000000000..85112f461f0 --- /dev/null +++ b/test/nanoserver/nanoserver.tests.ps1 @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Verify PowerShell Runs" { + BeforeAll{ + $options = (Get-PSOptions) + $path = Split-Path -Path $options.Output + Write-Verbose "Path: '$path'" -Verbose + $rootPath = Split-Path -Path $path + $mount = 'C:\powershell' + $container = 'mcr.microsoft.com/powershell:nanoserver-1803' + } + + It "Verify Version " { + $version = docker run --rm -v "${rootPath}:${mount}" ${container} "${mount}\publish\pwsh" -NoLogo -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' + $version | Should -Match '^7\.' + } +} 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 new file mode 100644 index 00000000000..14dc40a6ff2 --- /dev/null +++ b/test/packaging/windows/msi.tests.ps1 @@ -0,0 +1,397 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe -Name "Windows MSI" -Fixture { + BeforeAll { + Set-StrictMode -Off + 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 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)] + [Switch]$Install, + + [Parameter(ParameterSetName = 'Uninstall', Mandatory)] + [Switch]$Uninstall, + + [Parameter(Mandatory)] + [ValidateScript({Test-Path -Path $_})] + [String]$MsiPath, + + [Parameter(ParameterSetName = 'Install')] + [HashTable] $Properties + + ) + $action = "$($PSCmdlet.ParameterSetName)ing" + if ($Install.IsPresent) { + $switch = '/I' + } else { + $switch = '/x' + } + + $additionalOptions = @() + if ($Properties) { + foreach ($key in $Properties.Keys) { + $additionalOptions += "$key=$($Properties.$key)" + } + } + + $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 + throw "$action MSI failed and returned error code $exitCode." + } + } + + $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 ';' | + Where-Object {$_ -like '*files\powershell*'}) + + $msiLog = Join-Path -Path $TestDrive -ChildPath 'msilog.txt' + + foreach ($pathPart in $beforePath) { + Write-Warning "Found existing PowerShell path: $pathPart" + } + + if (!(Test-Elevated)) { + Write-Warning "Tests must be elevated" + } + $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() + Remove-Item -Path $propertiesRegKeyPath -ErrorAction SilentlyContinue + } + + Context "Upgrade code" { + BeforeAll { + Write-Verbose "cr-$channel-$runtime" -Verbose + $pwshPath = Join-Path $env:ProgramFiles -ChildPath "PowerShell" + $pwshx86Path = Join-Path ${env:ProgramFiles(x86)} -ChildPath "PowerShell" + $regKeyPath = "HKLM:\SOFTWARE\Microsoft\PowerShellCore\InstalledVersions" + + switch ("$channel-$runtime") { + "preview-win7-x64" { + $versionPath = Join-Path -Path $pwshPath -ChildPath '7-preview' + $revisionRange = 0, 99 + $msiUpgradeCode = '39243d76-adaf-42b1-94fb-16ecf83237c8' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode + } + "stable-win7-x64" { + $versionPath = Join-Path -Path $pwshPath -ChildPath '7' + $revisionRange = 500, 500 + $msiUpgradeCode = '31ab5147-9a97-4452-8443-d9709f0516e1' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode + } + "preview-win7-x86" { + $versionPath = Join-Path -Path $pwshx86Path -ChildPath '7-preview' + $revisionRange = 0, 99 + $msiUpgradeCode = '86abcfbd-1ccc-4a88-b8b2-0facfde29094' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode + } + "stable-win7-x86" { + $versionPath = Join-Path -Path $pwshx86Path -ChildPath '7' + $revisionRange = 500, 500 + $msiUpgradeCode = '1d00683b-0f84-4db8-a64f-2f98ad42fe06' + $regKeyPath = Join-Path $regKeyPath -ChildPath $msiUpgradeCode + } + default { + throw "'$_' not a valid channel runtime combination" + } + } + } + + 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)) { + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 1} + } | 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 "Revision should be in correct range" -Skip:(!(Test-Elevated)) { + $pwshDllPath = Join-Path -Path $versionPath -ChildPath "pwsh.dll" + [version] $version = (Get-ChildItem $pwshDllPath).VersionInfo.FileVersion + Write-Verbose "pwsh.dll version: $version" -Verbose + $version.Revision | Should -BeGreaterOrEqual $revisionRange[0] -Because "$channel revision should between $($revisionRange[0]) and $($revisionRange[1])" + $version.Revision | Should -BeLessOrEqual $revisionRange[1] -Because "$channel revision should between $($revisionRange[0]) and $($revisionRange[1])" + } + + It 'MSI should add ProductCode in registry' -Skip:(!(Test-Elevated)) { + + $productCode = if ($msiUpgradeCode -eq '39243d76-adaf-42b1-94fb-16ecf83237c8' -or + $msiUpgradeCode -eq '31ab5147-9a97-4452-8443-d9709f0516e1') { + # x64 + $regKeyPath | Should -Exist + Get-ItemPropertyValue -Path $regKeyPath -Name 'ProductCode' + } elseif ($msiUpgradeCode -eq '86abcfbd-1ccc-4a88-b8b2-0facfde29094' -or + $msiUpgradeCode -eq '1d00683b-0f84-4db8-a64f-2f98ad42fe06') { + # x86 - need to open the 32bit reghive + $wow32RegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry32) + $subKey = $wow32RegKey.OpenSubKey("Software\Microsoft\PowerShellCore\InstalledVersions\$msiUpgradeCode") + $subKey.GetValue("ProductCode") + } + + $productCode | Should -Not -BeNullOrEmpty + $productCodeGuid = [Guid]$productCode + $productCodeGuid | Should -BeOfType "Guid" + $productCodeGuid.Guid | Should -Not -Be $msiUpgradeCode + } + + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Uninstall -MsiPath $msiX64Path + } | Should -Not -Throw + } + } + + 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; 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 } + + $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 + } | Should -Not -Throw + } + } + + Context "Add Path enabled" { + It "MSI should install without error" -Skip:(!(Test-Elevated)) { + { + Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 1} + } | Should -Not -Throw + } + + 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-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 977da42dfeb..203d214e937 100644 --- a/test/powershell/Host/Base-Directory.Tests.ps1 +++ b/test/powershell/Host/Base-Directory.Tests.ps1 @@ -1,7 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Configuration file locations" -tags "CI","Slow" { BeforeAll { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" $profileName = "Microsoft.PowerShell_profile.ps1" } @@ -38,24 +40,24 @@ Describe "Configuration file locations" -tags "CI","Slow" { } It @ItArgs "Profile location should be correct" { - & $powershell -noprofile -c `$PROFILE | Should Be $expectedProfile + & $powershell -noprofile -c `$PROFILE | Should -Be $expectedProfile } 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)) + $actual | Should -Match ([regex]::Escape($expectedModule)) } It @ItArgs "PSReadLine history save location should be correct" { - & $powershell -noprofile { (Get-PSReadlineOption).HistorySavePath } | Should Be $expectedReadline + & $powershell -noprofile { (Get-PSReadLineOption).HistorySavePath } | Should -Be $expectedReadline } # This feature (and thus test) has been disabled because of the AssemblyLoadContext scenario It "JIT cache should be created correctly" -Skip { Remove-Item -ErrorAction SilentlyContinue $expectedCache & $powershell -noprofile { exit } - $expectedCache | Should Exist + $expectedCache | Should -Exist } # The ModuleAnalysisCache cannot be forced to exist, thus we cannot test it @@ -88,21 +90,21 @@ Describe "Configuration file locations" -tags "CI","Slow" { It @ItArgs "Profile should respect XDG_CONFIG_HOME" { $env:XDG_CONFIG_HOME = $TestDrive $expected = [IO.Path]::Combine($TestDrive, "powershell", $profileName) - & $powershell -noprofile -c `$PROFILE | Should Be $expected + & $powershell -noprofile -c `$PROFILE | Should -Be $expected } 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 - $actual | Should Match $expected + $actual | Should -Match $expected } It @ItArgs "PSReadLine history should respect XDG_DATA_HOME" { $env:XDG_DATA_HOME = $TestDrive $expected = [IO.Path]::Combine($TestDrive, "powershell", "PSReadLine", "ConsoleHost_history.txt") - & $powershell -noprofile { (Get-PSReadlineOption).HistorySavePath } | Should Be $expected + & $powershell -noprofile { (Get-PSReadLineOption).HistorySavePath } | Should -Be $expected } # This feature (and thus test) has been disabled because of the AssemblyLoadContext scenario @@ -111,7 +113,7 @@ Describe "Configuration file locations" -tags "CI","Slow" { $expected = [IO.Path]::Combine($TestDrive, "powershell", "StartupProfileData-NonInteractive") Remove-Item -ErrorAction SilentlyContinue $expected & $powershell -noprofile { exit } - $expected | Should Exist + $expected | Should -Exist } } } @@ -127,7 +129,8 @@ Describe "Working directory on startup" -Tag "CI" { Set-Location $currentDirectory } - It "Can start in directory where name contains wildcard characters" { + # https://github.com/PowerShell/PowerShell/issues/5752 + It "Can start in directory where name contains wildcard characters" -Pending { Set-Location -LiteralPath $testPath.FullName if ($IsMacOS) { # on macOS, /tmp is a symlink to /private so the real path is under /private/tmp @@ -135,6 +138,6 @@ Describe "Working directory on startup" -Tag "CI" { } else { $expectedPath = $testPath.FullName } - & $powershell -noprofile -c { $PWD.Path } | Should BeExactly $expectedPath + & $powershell -noprofile -c { $PWD.Path } | Should -BeExactly $expectedPath } } diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 44cc5069510..534cce2d356 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. using namespace System.Diagnostics # Minishell (Singleshell) is a powershell concept. @@ -9,30 +11,31 @@ using namespace System.Diagnostics Describe 'minishell for native executables' -Tag 'CI' { BeforeAll { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" } Context 'Streams from minishell' { It 'gets a hashtable object from minishell' { $output = & $powershell -noprofile { @{'a' = 'b'} } - ($output | Measure-Object).Count | Should Be 1 - $output | Should BeOfType 'Hashtable' - $output['a'] | Should Be 'b' + ($output | Measure-Object).Count | Should -Be 1 + $output | Should -BeOfType Hashtable + $output['a'] | Should -Be 'b' } 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' - $output.FullyQualifiedErrorId | Should Be 'Microsoft.PowerShell.Commands.WriteErrorException' + ($output | Measure-Object).Count | Should -Be 1 + $output | Should -BeOfType System.Management.Automation.ErrorRecord + $output.FullyQualifiedErrorId | Should -Be 'Microsoft.PowerShell.Commands.WriteErrorException' } It 'gets the information stream from minishell' { $output = & $powershell -noprofile { Write-Information 'foo' } 6>&1 - ($output | Measure-Object).Count | Should Be 1 - $output | Should BeOfType 'System.Management.Automation.InformationRecord' - $output | Should Be 'foo' + ($output | Measure-Object).Count | Should -Be 1 + $output | Should -BeOfType System.Management.Automation.InformationRecord + $output | Should -Be 'foo' } } @@ -40,10 +43,10 @@ Describe 'minishell for native executables' -Tag 'CI' { It "passes input into minishell" { $a = 1,2,3 $val = $a | & $powershell -noprofile -command { $input } - $val.Count | Should Be 3 - $val[0] | Should Be 1 - $val[1] | Should Be 2 - $val[2] | Should Be 3 + $val.Count | Should -Be 3 + $val[0] | Should -Be 1 + $val[1] | Should -Be 2 + $val[2] | Should -Be 3 } } } @@ -51,7 +54,7 @@ Describe 'minishell for native executables' -Tag 'CI' { Describe "ConsoleHost unit tests" -tags "Feature" { BeforeAll { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" $ExitCodeBadCommandLineParameter = 64 function NewProcessStartInfo([string]$CommandLine, [switch]$RedirectStdIn) @@ -79,80 +82,52 @@ Describe "ConsoleHost unit tests" -tags "Feature" { if (!$process.HasExited) { - $process.HasExited | Should Be $true + $process.HasExited | Should -BeTrue $process.Kill() } } } AfterEach { - $Error.Clear() + $error.Clear() } - Context "ShellInterop" { - It "Verify Parsing Error Output Format Single Shell should throw exception" { - try - { - & $powershell -outp blah -comm { $input } - Throw "Test execution should not reach here!" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "IncorrectValueForFormatParameter" - } + 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." } - It "Verify Validate Dollar Error Populated should throw exception" { - $origEA = $ErrorActionPreference - $ErrorActionPreference = "Stop" - try - { - $a = 1,2,3 - $a | & $powershell -noprofile -command { wgwg-wrwrhqwrhrh35h3h3} - Throw "Test execution should not reach here!" - } - catch - { - $_.ToString() | Should Match "wgwg-wrwrhqwrhrh35h3h3" - $_.FullyQualifiedErrorId | Should Be "CommandNotFoundException" - } - finally - { - $ErrorActionPreference = $origEA - } + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' + } + + Context "ShellInterop" { + It "Verify Parsing Error Output Format Single Shell should throw exception" { + { & $powershell -outp blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter" } - It "Verify Validate Output Format As Text Explicitly Child Single Shell should works" { + It "Verify Validate Output Format As Text Explicitly Child Single Shell does not throw" { { - $a="blahblah" - $a | & $powershell -noprofile -out text -com { $input } - } | Should Not Throw + "blahblah" | & $powershell -noprofile -out text -com { $input } + } | Should -Not -Throw } It "Verify Parsing Error Input Format Single Shell should throw exception" { - try - { - & $powershell -input blah -comm { $input } - Throw "Test execution should not reach here!" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "IncorrectValueForFormatParameter" - } + { & $powershell -input blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter" } } + Context "CommandLine" { It "simple -args" { - & $powershell -noprofile { $args[0] } -args "hello world" | Should Be "hello world" + & $powershell -noprofile { $args[0] } -args "hello world" | Should -Be "hello world" } It "array -args" { - & $powershell -noprofile { $args[0] } -args 1,(2,3) | Should Be 1 - (& $powershell -noprofile { $args[1] } -args 1,(2,3))[1] | Should Be 3 + & $powershell -noprofile { $args[0] } -args 1,(2,3) | Should -Be 1 + (& $powershell -noprofile { $args[1] } -args 1,(2,3))[1] | Should -Be 3 } foreach ($x in "--help", "-help", "-h", "-?", "--he", "-hel", "--HELP", "-hEl") { It "Accepts '$x' as a parameter for help" { - & $powershell -noprofile $x | Where-Object { $_ -match "pwsh[.exe] -Help | -? | /?" } | Should Not BeNullOrEmpty + & $powershell -noprofile $x | Where-Object { $_ -match "pwsh[.exe] -Help | -? | /?" } | Should -Not -BeNullOrEmpty } } @@ -162,7 +137,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { # We don't compare to `Get-Location` directly because object and formatted output comparisons are difficult $expected = & $powershell -noprofile -command $commandString $actual = & $powershell -noprofile -EncodedCommand $encodedCommand - $actual | Should Be $expected + $actual | Should -Be $expected } It "-Version should return the engine version using: -version " -TestCases @( @@ -172,32 +147,43 @@ Describe "ConsoleHost unit tests" -tags "Feature" { ) { $currentVersion = "PowerShell " + $PSVersionTable.GitCommitId.ToString() $observed = & $powershell -version $value 2>&1 - $observed | should be $currentVersion - $LASTEXITCODE | Should Be 0 + $observed | Should -Be $currentVersion + $LASTEXITCODE | Should -Be 0 } It "-File should be default parameter" { - Set-Content -Path $testdrive/test -Value "'hello'" - $observed = & $powershell -NoProfile $testdrive/test - $observed | Should Be "hello" + 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" + $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 - $observed.Count | Should Be 2 - $observed[0] | Should Be "foo" - $observed[1] | Should Be "bar" + $observed.Count | Should -Be 2 + $observed[0] | Should -Be "foo" + $observed[1] | Should -Be "bar" } It "-File should be able to pass bool string values as string to parameters: " -TestCases @( @@ -210,7 +196,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { param([string]$BoolString) Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool' $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -Bool $BoolString - $observed | Should Be $BoolString + $observed | Should -Be $BoolString } It "-File should be able to pass bool string values as string to positional parameters: " -TestCases @( @@ -223,7 +209,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { param([string]$BoolString) Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool' $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 $BoolString - $observed | Should BeExactly $BoolString + $observed | Should -BeExactly $BoolString } It "-File should be able to pass bool string values as bool to switches: " -TestCases @( @@ -235,17 +221,119 @@ Describe "ConsoleHost unit tests" -tags "Feature" { param([string]$BoolString, [string]$BoolValue) Set-Content -Path $testdrive/test.ps1 -Value 'param([switch]$switch) $switch.IsPresent' $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -switch:$BoolString - $observed | Should Be $BoolValue + $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 + $LASTEXITCODE | Should -Be 123 + } + + It "A single dash should be passed as an arg" { + $testScript = @' + [CmdletBinding()]param( + [string]$p1, + [string]$p2, + [Parameter(ValueFromPipeline)][string]$InputObject + ) + process{ + $input.replace($p1, $p2) + } +'@ + $testFilePath = Join-Path $TestDrive "test.ps1" + Set-Content -Path $testFilePath -Value $testScript + $observed = echo hello | & $powershell -noprofile $testFilePath e - + $observed | Should -BeExactly "h-llo" + } + + 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 + } + } + + Context "-Login pwsh switch" { + BeforeAll { + $profilePath = "~/.profile" + $backupProfilePath = "profile.bak" + if (Test-Path $profilePath) { + Move-Item -Path $profilePath -Destination $backupProfilePath -Force + } + + $envVarName = 'PSTEST_PROFILE_LOAD' + + $guid = New-Guid + + Set-Content -Force -Path $profilePath -Value @" +export $envVarName='$guid' +"@ + } + + AfterAll { + if (Test-Path $backupProfilePath) { + Move-Item -Path $backupProfilePath -Destination $profilePath -Force + } + } + + It "Doesn't run the login profile when -Login not used" { + $result = & $powershell -noprofile -Command "`$env:$envVarName" + $result | Should -BeNullOrEmpty + $LASTEXITCODE | Should -Be 0 + } + + It "Doesn't falsely recognise -Login when elsewhere in the invocation" { + $result = & $powershell -nop -c 'Write-Output "-login"' + $result | Should -BeExactly '-login' + $LASTEXITCODE | Should -Be 0 + } + + It "Doesn't falsely recognise -Login when used after -Command" { + $result = & $powershell -nop -c 'Write-Output' -Login + $result | Should -BeExactly '-Login' + $LASTEXITCODE | Should -Be 0 + } + + It "Accepts the switch for -Login and behaves correctly" -TestCases @( + @{ LoginSwitch = '-l' } + @{ LoginSwitch = '-L' } + @{ LoginSwitch = '-login' } + @{ LoginSwitch = '-Login' } + @{ LoginSwitch = '-LOGIN' } + @{ LoginSwitch = '-log' } + ) { + param($LoginSwitch) + + $result = & $powershell $LoginSwitch -NoProfile -Command "`$env:$envVarName" + + if ($IsWindows) { + $result | Should -BeNullOrEmpty + $LASTEXITCODE | Should -Be 0 + return + } + + $result | Should -BeExactly $guid + $LASTEXITCODE | Should -Be 0 + } + + It "Starts as a login shell with '-' prepended to name" -Skip:(-not (Get-Command -Name /bin/bash -ErrorAction Ignore)) { + $quoteEscapedPwsh = $powershell.Replace("'", "\'") + $pwshCommand = "`$env:$envVarName" + $bashCommand = "exec -a '-pwsh' '$quoteEscapedPwsh' -NoProfile -Command '`$env:$envVarName' ''" + $result = /bin/bash -c $bashCommand + $result | Should -BeExactly $guid + $LASTEXITCODE | Should -Be 0 # Exit code will be PowerShell's since it was exec'd } } @@ -268,12 +356,12 @@ Describe "ConsoleHost unit tests" -tags "Feature" { # must use an explicit scope of LocalMachine to ensure the setting is written to the expected file. # Skip the tests on Unix platforms because *-ExecutionPolicy cmdlets don't work by design. - It "Verifies PowerShell reads from the custom -settingsFile" -skip:(!$IsWindows) { + It "Verifies PowerShell reads from the custom -settingsFile" -Skip:(!$IsWindows) { $actualValue = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {(Get-ExecutionPolicy -Scope LocalMachine).ToString()} - $actualValue | Should Be $DefaultExecutionPolicy + $actualValue | Should -Be $DefaultExecutionPolicy } - It "Verifies PowerShell writes to the custom -settingsFile" -skip:(!$IsWindows) { + It "Verifies PowerShell writes to the custom -settingsFile" -Skip:(!$IsWindows) { $expectedValue = 'AllSigned' # Update the execution policy; this should update the settings file. @@ -281,44 +369,119 @@ Describe "ConsoleHost unit tests" -tags "Feature" { # ensure the setting was written to the settings file. $content = (Get-Content -Path $CustomSettingsFile | ConvertFrom-Json) - $content.'Microsoft.PowerShell:ExecutionPolicy' | Should Be $expectedValue + $content.'Microsoft.PowerShell:ExecutionPolicy' | Should -Be $expectedValue # ensure the setting is applied on next run $actualValue = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {(Get-ExecutionPolicy -Scope LocalMachine).ToString()} - $actualValue | Should Be $expectedValue + $actualValue | Should -Be $expectedValue } - It "Verify PowerShell removes a setting from the custom -settingsFile" -skip:(!$IsWindows) { + It "Verify PowerShell removes a setting from the custom -settingsFile" -Skip:(!$IsWindows) { # Remove the LocalMachine execution policy; this should update the settings file. & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope LocalMachine } # ensure the setting was removed from the settings file. $content = (Get-Content -Path $CustomSettingsFile | ConvertFrom-Json) - $content.'Microsoft.PowerShell:ExecutionPolicy' | Should Be $null + $content.'Microsoft.PowerShell:ExecutionPolicy' | Should -Be $null + } + } + + 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 - $p | & $powershell -noprofile -inputFormat xml { $input | Foreach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should Be 30 + $p | & $powershell -noprofile { $input | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should -Be 30 + $p | & $powershell -noprofile -inputFormat xml { $input | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should -Be 30 } It "text input" { # Join (multiple lines) and remove whitespace (we don't care about spacing) to verify we converted to string (by generating a table) - $p | & $powershell -noprofile -inputFormat text { -join ($input -replace "\s","") } | Should Be "XY--1020" + $p | & $powershell -noprofile -inputFormat text { -join ($input -replace "\s","") } | Should -Be "XY--1020" } It "xml output" { - & $powershell -noprofile { [PSCustomObject]@{X=10;Y=20} } | Foreach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should Be 30 - & $powershell -noprofile -outputFormat xml { [PSCustomObject]@{X=10;Y=20} } | Foreach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should Be 30 + & $powershell -noprofile { [PSCustomObject]@{X=10;Y=20} } | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should -Be 30 + & $powershell -noprofile -outputFormat xml { [PSCustomObject]@{X=10;Y=20} } | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should -Be 30 } 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('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 = $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 } } @@ -326,7 +489,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { It "Simple redirected output" { $si = NewProcessStartInfo "-noprofile -c 1+1" $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } } @@ -339,28 +502,28 @@ Describe "ConsoleHost unit tests" -tags "Feature" { It "Redirected input w/ implicit -Command w/ -NonInteractive" { $si = NewProcessStartInfo "-NonInteractive -noprofile -c 1+1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } It "Redirected input w/ implicit -Command w/o -NonInteractive" { $si = NewProcessStartInfo "-noprofile -c 1+1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } It "Redirected input w/ explicit -Command w/ -NonInteractive" { $si = NewProcessStartInfo "-NonInteractive -noprofile -Command 1+1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } It "Redirected input w/ explicit -Command w/o -NonInteractive" { $si = NewProcessStartInfo "-noprofile -Command 1+1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } @@ -368,7 +531,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" { '1+1' | Out-File -Encoding Ascii -FilePath TestDrive:test.ps1 -Force $si = NewProcessStartInfo "-noprofile -NonInteractive -File $testDrive\test.ps1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } @@ -376,18 +539,26 @@ Describe "ConsoleHost unit tests" -tags "Feature" { '1+1' | Out-File -Encoding Ascii -FilePath TestDrive:test.ps1 -Force $si = NewProcessStartInfo "-noprofile -File $testDrive\test.ps1" -RedirectStdIn $process = RunPowerShell $si - $process.StandardOutput.ReadToEnd() | Should Be 2 + $process.StandardOutput.ReadToEnd() | Should -Be 2 EnsureChildHasExited $process } } 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. - It "Interactive redirected input: " -TestCases @( + It "Interactive redirected input: " -Pending:($IsWindows) -TestCases @( @{InteractiveSwitch = ""} @{InteractiveSwitch = " -IntERactive"} @{InteractiveSwitch = " -i"} @@ -398,58 +569,58 @@ Describe "ConsoleHost unit tests" -tags "Feature" { $process.StandardInput.Write("`$function:prompt = { 'PS> ' }`n") $null = $process.StandardOutput.ReadLine() $process.StandardInput.Write("1+1`n") - $process.StandardOutput.ReadLine() | Should Be "PS> 1+1" - $process.StandardOutput.ReadLine() | Should Be "2" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1" + $process.StandardOutput.ReadLine() | Should -Be "2" $process.StandardInput.Write("1+2`n") - $process.StandardOutput.ReadLine() | Should Be "PS> 1+2" - $process.StandardOutput.ReadLine() | Should Be "3" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2" + $process.StandardOutput.ReadLine() | Should -Be "3" # Backspace should work as expected $process.StandardInput.Write("1+2`b3`n") # A real console should render 2`b3 as just 3, but we're just capturing exactly what is written - $process.StandardOutput.ReadLine() | Should Be "PS> 1+2`b3" - $process.StandardOutput.ReadLine() | Should Be "4" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2`b3" + $process.StandardOutput.ReadLine() | Should -Be "4" $process.StandardInput.Close() - $process.StandardOutput.ReadToEnd() | Should Be "PS> " + $process.StandardOutput.ReadToEnd() | Should -Be "PS> " EnsureChildHasExited $process } - It "Interactive redirected input w/ initial command" { + It "Interactive redirected input w/ initial command" -Pending:($IsWindows) { $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS> ' }""" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("1+1`n") - $process.StandardOutput.ReadLine() | Should Be "PS> 1+1" - $process.StandardOutput.ReadLine() | Should Be "2" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1" + $process.StandardOutput.ReadLine() | Should -Be "2" $process.StandardInput.Write("1+2`n") - $process.StandardOutput.ReadLine() | Should Be "PS> 1+2" - $process.StandardOutput.ReadLine() | Should Be "3" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2" + $process.StandardOutput.ReadLine() | Should -Be "3" $process.StandardInput.Close() - $process.StandardOutput.ReadToEnd() | Should Be "PS> " + $process.StandardOutput.ReadToEnd() | Should -Be "PS> " EnsureChildHasExited $process } - It "Redirected input explicit prompting (-File -)" { + It "Redirected input explicit prompting (-File -)" -Pending:($IsWindows) { $si = NewProcessStartInfo "-noprofile -" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("`$function:prompt = { 'PS> ' }`n") $null = $process.StandardOutput.ReadLine() $process.StandardInput.Write("1+1`n") - $process.StandardOutput.ReadLine() | Should Be "PS> 1+1" - $process.StandardOutput.ReadLine() | Should Be "2" + $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1" + $process.StandardOutput.ReadLine() | Should -Be "2" $process.StandardInput.Close() - $process.StandardOutput.ReadToEnd() | Should Be "PS> " + $process.StandardOutput.ReadToEnd() | Should -Be "PS> " EnsureChildHasExited $process } - It "Redirected input no prompting (-Command -)" { + It "Redirected input no prompting (-Command -)" -Pending:($IsWindows) { $si = NewProcessStartInfo "-noprofile -Command -" -RedirectStdIn $process = RunPowerShell $si $process.StandardInput.Write("1+1`n") - $process.StandardOutput.ReadLine() | Should Be "2" + $process.StandardOutput.ReadLine() | Should -Be "2" # Multi-line input $process.StandardInput.Write("if (1)`n{`n 42`n}`n`n") - $process.StandardOutput.ReadLine() | Should Be "42" + $process.StandardOutput.ReadLine() | Should -Be "42" $process.StandardInput.Write(@" function foo { @@ -459,51 +630,75 @@ function foo foo "@) - $process.StandardOutput.ReadLine() | Should Be "in foo" + $process.StandardOutput.ReadLine() | Should -Be "in foo" # Backspace sent through stdin should be in the final string $process.StandardInput.Write("`"a`bc`".Length`n") - $process.StandardOutput.ReadLine() | Should Be "3" + $process.StandardOutput.ReadLine() | Should -Be "3" # Last command with no newline - should be accepted and # produce output after closing stdin. $process.StandardInput.Write('22 + 22') $process.StandardInput.Close() - $process.StandardOutput.ReadLine() | Should Be "44" + $process.StandardOutput.ReadLine() | Should -Be "44" EnsureChildHasExited $process } - It "Redirected input w/ nested prompt" { - $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS' + ('>'*(`$nestedPromptLevel+1)) + ' ' }""" -RedirectStdIn + 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("`$host.EnterNestedPrompt()`n") - $process.StandardOutput.ReadLine() | Should Be "PS> `$host.EnterNestedPrompt()" + $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") - $process.StandardOutput.ReadLine() | Should Be "PS>> exit" + $process.StandardOutput.ReadLine() | Should -Be "PS>> exit" $process.StandardInput.Close() - $process.StandardOutput.ReadToEnd() | Should Be "PS> " + $process.StandardOutput.ReadToEnd() | Should -Be "PS> " EnsureChildHasExited $process } } Context "Exception handling" { - It "Should handle a CallDepthOverflow" { - # Infinite recursion - function recurse - { - recurse $args + BeforeAll { + # the default stack size in PowerShell is 10000000, set the stack + # to something much smaller which will produce the error much faster + # I saw a reduction from 65 seconds to 79 milliseconds. + $classDefinition = @' +using System; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Threading; +namespace StackTest { + public class StackDepthTest { + public static PowerShell ps; + public static int size = 512 * 1024; + public static void CauseError() { + Thread t = new Thread(RunPS, size); + t.Start(); + t.Join(); + } + public static void RunPS() { + InitialSessionState iss = InitialSessionState.CreateDefault2(); + iss.ThreadOptions = PSThreadOptions.UseCurrentThread; + ps = PowerShell.Create(iss); + ps.AddScript("function recurse { recurse }; recurse").Invoke(); + } + public static void GetPSError() { + if ( ps.Streams.Error.Count > 0) { + throw ps.Streams.Error[0].Exception.InnerException; } + } + } +} +'@ + $TestType = Add-Type -PassThru -TypeDefinition $classDefinition + } - try - { - recurse "args" - Throw "Incorrect exception" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "CallDepthOverflow" - } + It "Should handle a CallDepthOverflow" { + $TestType::CauseError() + { $TestType::GetPSError() } | Should -Throw -ErrorId "CallDepthOverflow" } } @@ -520,28 +715,42 @@ foo $env:XDG_CONFIG_HOME = $XDG_CONFIG_HOME } - It "Should start if Data, Config, and Cache location is not accessible" -skip:($IsWindows) { + It "Should start if Data, Config, and Cache location is not accessible" -Skip:($IsWindows) { $env:XDG_CACHE_HOME = "/dev/cpu" $env:XDG_DATA_HOME = "/dev/cpu" $env:XDG_CONFIG_HOME = "/dev/cpu" - $output = & $powershell -noprofile -Command { (get-command).count } - [int]$output | Should BeGreaterThan 0 + $output = & $powershell -noprofile -Command { (Get-Command).count } + [int]$output | Should -BeGreaterThan 0 } } Context "HOME environment variable" { - It "Should start if HOME is not defined" -skip:($IsWindows) { - bash -c "unset HOME;$powershell -c '1+1'" | Should BeExactly 2 + 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" { It "`$PSHOME should be in front so that pwsh.exe starts current running PowerShell" { - pwsh -v | Should Match $psversiontable.GitCommitId + & $powershell -v | Should -Match $PSVersionTable.GitCommitId } 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 } } @@ -552,14 +761,242 @@ foo ) { param($testArg, $expectedMatches) $output = & $powershell $testArg -File foo 2>&1 - $LASTEXITCODE | Should Be $ExitCodeBadCommandLineParameter + $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter $outString = [String]::Join(",", $output) foreach ($expectedMatch in $expectedMatches) { - $outString | Should Match $expectedMatch + $outString | Should -Match $expectedMatch } } } + + Context "-WorkingDirectory parameter" { + BeforeAll { + $folderName = (New-Guid).ToString() + " test"; + New-Item -Path ~/$folderName -ItemType Directory + $ExitCodeBadCommandLineParameter = 64 + } + + AfterAll { + Remove-Item ~/$folderName -Force -ErrorAction SilentlyContinue + } + + It "Can set working directory to ''" -TestCases @( + @{ value = "~" ; expectedPath = $((Get-Item ~).FullName) }, + @{ value = "~/$folderName"; expectedPath = $((Get-Item ~/$folderName).FullName) }, + @{ value = "~\$folderName"; expectedPath = $((Get-Item ~\$folderName).FullName) } + ) { + param($value, $expectedPath) + $output = & $powershell -NoProfile -WorkingDirectory "$value" -Command '(Get-Location).Path' + $output | Should -BeExactly $expectedPath + } + + It "Can use '' to set working directory" -TestCases @( + @{ parameter = '-workingdirectory' }, + @{ parameter = '-wd' }, + @{ parameter = '-wo' } + ) { + param($parameter) + $output = & $powershell -NoProfile $parameter ~ -Command "`$PWD.Path" + $output | Should -BeExactly $((Get-Item ~).FullName) + } + + It "Error case if -WorkingDirectory isn't given argument as last on command line" { + $output = & $powershell -WorkingDirectory 2>&1 + $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter + $output | Should -Not -BeNullOrEmpty + } + + It "-WorkingDirectory should be processed before profiles" { + + if (Test-Path $PROFILE) { + $currentProfile = Get-Content $PROFILE + } + else { + New-Item -ItemType File -Path $PROFILE -Force + } + + @" + (Get-Location).Path + Set-Location $testdrive +"@ > $PROFILE + + try { + $out = & $powershell -workingdirectory ~ -c '(Get-Location).Path' + $out | Should -HaveCount 2 + $out[0] | Should -BeExactly (Get-Item ~).FullName + $out[1] | Should -BeExactly "$testdrive" + } + finally { + if ($currentProfile) { + Set-Content $PROFILE -Value $currentProfile + } + else { + Remove-Item $PROFILE + } + } + } + } + + Context "CustomPipeName startup tests" { + + It "Should create pipe file if CustomPipeName is specified" { + $pipeName = [System.IO.Path]::GetRandomFileName() + $pipePath = Get-PipePath $pipeName + + # The pipePath should be created by the time the -Command is executed. + & $powershell -CustomPipeName $pipeName -Command "Test-Path '$pipePath'" | Should -BeTrue + } + + It "Should throw if CustomPipeName is too long on Linux or macOS" -Skip:($IsWindows) { + # Generate a string that is larger than the max pipe name length. + $longPipeName = [string]::new("A", 200) + + "`$PID" | & $powershell -CustomPipeName $longPipeName -c - + $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter + } + } + + Context "ApartmentState WPF tests" -Tag Slow { + + It "WPF requires STA and will work" -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) { + Add-Type -AssemblyName presentationframework + + $xaml = [xml]@" + + +"@ + + $reader = [System.Xml.XmlNodeReader]::new($xaml) + $Window = [System.Windows.Markup.XamlReader]::Load($reader) + # This will throw an exception if MTA + { $Window.Show() } | Should -Not -Throw + $Window.Close() + } + + } + + Context "ApartmentState tests" { + + It "Default apartment state for main thread is STA" -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) { + [System.Threading.Thread]::CurrentThread.GetApartmentState() | Should -BeExactly "STA" + } + + It "Default apartment state for new runspace is MTA" -Skip:(!$IsWindows) { + $ps = [powershell]::Create() + $ps.AddScript({[System.Threading.Thread]::CurrentThread.GetApartmentState()}) + $ps.Invoke() | Should -BeExactly "MTA" + } + + It "Should be able to set apartment state to: " -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) -TestCases @( + @{ apartment = "STA"; switch = "-sta" } + @{ apartment = "MTA"; switch = "-mta" } + ) { + param ($apartment, $switch) + + & $powershell $switch -noprofile -command "[System.Threading.Thread]::CurrentThread.GetApartmentState()" | Should -BeExactly $apartment + } + + It "Should fail to set apartment state to: " -Skip:($IsWindows -and ![System.Management.Automation.Platform]::IsNanoServer) -TestCases @( + @{ switch = "-sta" } + @{ switch = "-mta" } + ) { + param ($switch) + + & $powershell $switch -noprofile -command exit + $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 { @@ -575,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; } @@ -611,53 +1053,201 @@ 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) - $ps = Start-Process pwsh -ArgumentList "-WindowStyle $WindowStyle -noexit -interactive" -PassThru - $startTime = Get-Date - $showCmd = "Unknown" - while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and $showCmd -ne $WindowStyle) - { - Start-Sleep -Milliseconds 100 - $showCmd = ([Test.User32]::GetPlacement($ps.MainWindowHandle)).showCmd + + 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 + $showCmd = "Unknown" + while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and $showCmd -ne $WindowStyle) { + Start-Sleep -Milliseconds 100 + $showCmd = ([Test.User32]::GetPlacement($ps.MainWindowHandle)).showCmd + } + + $showCmd | Should -BeExactly $WindowStyle + } finally { + $ps | Stop-Process -Force } - $showCmd | Should BeExactly $WindowStyle - $ps | Stop-Process -Force } It "Invalid -WindowStyle returns error" { - pwsh -WindowStyle invalid - $LASTEXITCODE | Should Be $ExitCodeBadCommandLineParameter + & $powershell -WindowStyle invalid + $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter } } Describe "Console host api tests" -Tag CI { - Context "String escape sequences" { + Context "String escape and control sequences" { $esc = [char]0x1b + $csi = [char]0x9b $testCases = @{InputObject = "abc"; Length = 3; Name = "No escapes"}, @{InputObject = "${esc} [31mabc"; Length = 9; Name = "Malformed escape - extra space"}, @{InputObject = "${esc}abc"; Length = 4; Name = "Malformed escape - no csi"}, @{InputObject = "[31mabc"; Length = 7; Name = "Malformed escape - no escape"} - $testCases += if ($host.UI.SupportsVirtualTerminal) + $testCases += if ($Host.UI.SupportsVirtualTerminal) { @{InputObject = "$esc[31mabc"; Length = 3; Name = "Escape at start"} @{InputObject = "$esc[31mabc$esc[0m"; Length = 3; Name = "Escape at start and end"} + @{InputObject = "${csi}31mabc"; Length = 3; Name = "C1 CSI at start"} + @{InputObject = "${csi}31mabc${csi}0m"; Length = 3; Name = "C1 CSI at start and end"} + @{InputObject = "abc${csi}m"; Length = 3; Name = "C1 CSI, no params"} + @{InputObject = "abc${csi}#{"; Length = 3; Name = "C1 CSI, XTPUSHSGR"} + @{InputObject = "abc${csi}#}"; Length = 3; Name = "C1 CSI, XTPOPSGR"} + @{InputObject = "abc${csi}#p"; Length = 3; Name = "C1 CSI, XTPUSHSGR (alias)"} + @{InputObject = "abc${csi}#q"; Length = 3; Name = "C1 CSI, XTPOPSGR (alias)"} + @{InputObject = "abc${esc}[0#p"; Length = 3; Name = "XTPUSHSGR, with param"} + @{InputObject = "${esc}[0;1#qabc"; Length = 3; Name = "XTPOPSGR, with multiple params"} } else { @{InputObject = "$esc[31mabc"; Length = 8; Name = "Escape at start - no virtual term support"} @{InputObject = "$esc[31mabc$esc[0m"; Length = 12; Name = "Escape at start and end - no virtual term support"} + @{InputObject = "${csi}31mabc"; Length = 7; Name = "C1 CSI at start - no virtual term support"} + @{InputObject = "${csi}31mabc${csi}0m"; Length = 10; Name = "C1 CSI at start and end - no virtual term support"} + @{InputObject = "abc${csi}m"; Length = 5; Name = "C1 CSI, no params - no virtual term support"} + @{InputObject = "abc${csi}#{"; Length = 6; Name = "C1 CSI, XTPUSHSGR - no virtual term support"} + @{InputObject = "abc${csi}#}"; Length = 6; Name = "C1 CSI, XTPOPSGR - no virtual term support"} + @{InputObject = "abc${csi}#p"; Length = 6; Name = "C1 CSI, XTPUSHSGR (alias) - no virtual term support"} + @{InputObject = "abc${csi}#q"; Length = 6; Name = "C1 CSI, XTPOPSGR (alias) - no virtual term support"} + @{InputObject = "abc${esc}[0#p"; Length = 8; Name = "XTPUSHSGR, with param - no virtual term support"} + @{InputObject = "${esc}[0;1#qabc"; Length = 10; Name = "XTPOPSGR, with multiple params - no virtual term support"} } It "Should properly calculate buffer cell width of ''" -TestCases $testCases { param($InputObject, $Length) - $host.UI.RawUI.LengthInBufferCells($InputObject) | Should Be $Length + $Host.UI.RawUI.LengthInBufferCells($InputObject) | Should -Be $Length + } + } +} + +Describe "Pwsh exe resources tests" -Tag CI { + It "Resource strings are embedded in the executable" -Skip:(!$IsWindows) { + $pwsh = Get-Item -Path "$PSHOME\pwsh.exe" + $pwsh.VersionInfo.FileVersion | Should -Match $PSVersionTable.PSVersion.ToString().Split("-")[0] + $productVersion = $pwsh.VersionInfo.ProductVersion.Replace("-dirty","").Replace(" Commits: ","-").Replace(" SHA: ","-g") + if ($PSVersionTable.GitCommitId.Contains("-g")) { + $productVersion | Should -BeExactly $PSVersionTable.GitCommitId + } else { + $productVersion | Should -Match $PSVersionTable.GitCommitId + } + $pwsh.VersionInfo.ProductName | Should -BeExactly "PowerShell" + } + + It "Manifest contains compatibility section" -Skip:(!$IsWindows) { + $osversion = [System.Environment]::OSVersion.Version + $PSVersionTable.os | Should -MatchExactly "$($osversion.Major).$($osversion.Minor)" + } +} + +Describe 'Pwsh startup in directories that contain wild cards' -Tag CI { + BeforeAll { + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" + $dirnames = "[T]est","[Test","T][est","Test" + $testcases = @() + foreach ( $d in $dirnames ) { + $null = New-Item -type Directory -Path "${TESTDRIVE}/$d" + $testcases += @{ Dirname = $d } + } + } + + It "pwsh can startup in a directory named " -TestCases $testcases { + param ( $dirname ) + try { + Push-Location -LiteralPath "${TESTDRIVE}/${dirname}" + $result = & $powershell -noprofile -c '(Get-Item .).Name' + $result | Should -BeExactly $dirname + } + finally { + Pop-Location + } + } +} + +Describe 'Pwsh startup and PATH' -Tag CI { + BeforeEach { + $oldPath = $env:PATH + } + + AfterEach { + $env:PATH = $oldPath + } + + It 'Calling pwsh starts the same version of PowerShell as currently running' { + $version = pwsh -v + $version | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)" + } + + It 'pwsh starts even if PATH is not defined' { + $pwsh = Join-Path -Path $PSHOME -ChildPath "pwsh" + Remove-Item Env:\PATH + $path = & $pwsh -noprofile -command '$env:PATH' + $path | Should -BeExactly ($PSHOME + [System.IO.Path]::PathSeparator) + } +} + +Describe 'Console host name' -Tag CI { + 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 bb8fe6d6a9d..e151a2cfc2b 100644 --- a/test/powershell/Host/HostUtilities.Tests.ps1 +++ b/test/powershell/Host/HostUtilities.Tests.ps1 @@ -1,34 +1,20 @@ -Describe "InvokeOnRunspace method argument error handling" -tags "Feature" { +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe "InvokeOnRunspace method argument error handling" -tags "Feature" { BeforeAll { $command = [System.Management.Automation.PSCommand]::new() - $localRunspace = $host.Runspace + $localRunspace = $Host.Runspace } It "Null argument exception should be thrown for null PSCommand argument" { - - try - { - [System.Management.Automation.HostUtilities]::InvokeOnRunspace($null, $localRunspace) - throw "InvokeOnRunspace method did not throw expected PSArgumentNullException exception" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "PSArgumentNullException" - } + { [System.Management.Automation.HostUtilities]::InvokeOnRunspace($null, $localRunspace) } | + Should -Throw -ErrorId "PSArgumentNullException" } It "Null argument exception should be thrown for null Runspace argument" { - - try - { - [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $null) - throw "InvokeOnRunspace method did not throw expected PSArgumentNullException exception" - } - catch - { - $_.FullyQualifiedErrorId | Should Be "PSArgumentNullException" - } + { [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $null) } | + Should -Throw -ErrorId "PSArgumentNullException" } } @@ -38,21 +24,24 @@ Describe "InvokeOnRunspace method as nested command" -tags "Feature" { $command = [System.Management.Automation.PSCommand]::new() $command.AddScript('"Hello!"') - $currentRunspace = $host.Runspace + $currentRunspace = $Host.Runspace $results = [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $currentRunspace) - $results[0] | Should Be "Hello!" + $results[0] | Should -Be "Hello!" } } -Describe "InvokeOnRunspace method on remote runspace" -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 { @@ -62,13 +51,48 @@ Describe "InvokeOnRunspace method on remote runspace" -tags "Feature" { } } - 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!"') $results = [System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $script:remoteRunspace) - $results[0] | Should Be "Hello!" + $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 new file mode 100644 index 00000000000..53798dc1c3c --- /dev/null +++ b/test/powershell/Host/Logging.Tests.ps1 @@ -0,0 +1,508 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +using namespace System.Text + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +Import-Module HelpersCommon +Import-Module PSSysLog + +<# + Define enums that mirror the internal enums used + in product code. These are used to configure + syslog logging. +#> +enum LogLevel +{ + LogAlways = 0x0 + Critical = 0x1 + Error = 0x2 + Warning = 0x3 + Informational = 0x4 + Verbose = 0x5 + Debug = 0x14 +} + +enum LogChannel +{ + Operational = 0x10 + Analytic = 0x11 +} + +enum LogKeyword +{ + Runspace = 0x1 + Pipeline = 0x2 + Protocol = 0x4 + Transport = 0x8 + Host = 0x10 + Cmdlets = 0x20 + Serializer = 0x40 + Session = 0x80 + 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 + +.PARAMETER logId + The identifier to use for logging + +.PARAMETER logLevel + The optional logging level, see the LogLevel enum + +.PARAMETER logChannels + The optional logging channels to enable; see the LogChannel enum + +.PARAMETER logKeywords + The optional keywords to enable ; see the LogKeyword enum +#> +function WriteLogSettings +{ + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $LogId, + + [System.Nullable[LogLevel]] $LogLevel = $null, + + [LogChannel[]] $LogChannels = $null, + + [LogKeyword[]] $LogKeywords = $null, + + [switch] $ScriptBlockLogging + ) + + $filename = [Guid]::NewGuid().ToString('N') + $fullPath = Join-Path -Path $TestDrive -ChildPath "$filename.config.json" + + $values = @{} + $values['LogIdentity'] = $LogId + + if ($LogChannels -ne $null) + { + $values['LogChannels'] = $LogChannels -join ', ' + } + + if ($LogKeywords -ne $null) + { + $values['LogKeywords'] = $LogKeywords -join ', ' + } + + if ($LogLevel) + { + $values['LogLevel'] = $LogLevel.ToString() + } + + if($IsWindows) + { + $values["Microsoft.PowerShell:ExecutionPolicy"] = "RemoteSigned" + } + + if($ScriptBlockLogging.IsPresent) + { + $powerShellPolicies = @{ + ScriptBlockLogging = @{ + EnableScriptBlockLogging = $ScriptBlockLogging.IsPresent + EnableScriptBlockInvocationLogging = $true + } + } + + $values['PowerShellPolicies'] = $powerShellPolicies + } + + ConvertTo-Json -InputObject $values | Set-Content -Path $fullPath -ErrorAction Stop + return $fullPath +} + +function Get-RegEx +{ + param($SimpleMatch) + + $regex = $SimpleMatch -replace '\\', '\\' + $regex = $regex -replace '\(', '\(' + $regex = $regex -replace '\)', '\)' + $regex = $regex -replace '\[', '\[' + $regex = $regex -replace '\]', '\]' + $regex = $regex -replace '\-', '\-' + $regex = $regex -replace '\$', '\$' + $regex = $regex -replace '\^', '\^' + return $regex +} + +Describe 'Basic SysLog tests on Linux' -Tag @('CI','RequireSudoOnUnix') { + BeforeAll { + [bool] $IsSupportedEnvironment = $IsLinux + [string] $SysLogFile = [string]::Empty + + if ($IsSupportedEnvironment) + { + # TODO: Update to use a PowerShell specific syslog file + if (Test-Path -Path '/var/log/syslog') + { + $SysLogFile = '/var/log/syslog' + } + elseif (Test-Path -Path '/var/log/messages') + { + $SysLogFile = '/var/log/messages' + } + else + { + # TODO: Look into journalctl and other variations. + Write-Warning -Message 'Unsupported Linux syslog configuration.' + $IsSupportedEnvironment = $false + } + [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh' + $scriptBlockCreatedRegExTemplate = @" +Creating Scriptblock text \(1 of 1\):#012{0}(⏎|#012)*ScriptBlock ID: [0-9a-z\-]*#012Path:.* +"@ + + } + } + + BeforeEach { + # generate a unique log application id + [string] $logId = [Guid]::NewGuid().ToString('N') + } + + It 'Verifies basic logging with no customizations' -Skip:(!$IsSupportedEnvironment) { + $configFile = WriteLogSettings -LogId $logId + & $powershell -NoProfile -SettingsFile $configFile -Command '$env:PSModulePath | out-null' + + # Get log entries from the last 100 that match our id and are after the time we launched Powershell + $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 3 + + $items | Should -Not -Be $null + $items.Length | Should -BeGreaterThan 1 + $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.Length -gt 3) + { + # Force reporting of the first unexpected item to help diagnosis + $items[3] | Should -Be $null + } + } + + # 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 +& ([scriptblock]::create("Write-Verbose 'testheader123' ;Write-verbose 'after'")) +'@ + $testFileName = 'test01.ps1' + $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName + $script | Out-File -FilePath $testScriptPath -Force + $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath + + # Get log entries from the last 100 that match our id and are after the time we launched Powershell + $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 18 + + $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") + + # 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 executing the created scriptblock + $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123' ;Write\-verbose 'after'") + } + + # 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 +& ([scriptblock]::create("Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'")) +'@ + $testFileName = 'test01.ps1' + $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName + $script | Out-File -FilePath $testScriptPath -Force + $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath + + # Get log entries from the last 100 that match our id and are after the time we launched Powershell + $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 18 + + $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") + + # 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 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 + $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. 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 + } +} + +Describe 'Basic os_log tests on MacOS' -Tag @('CI','RequireSudoOnUnix') { + BeforeAll { + [bool] $IsSupportedEnvironment = $IsMacOS + [bool] $persistenceEnabled = $false + + $currentWarningPreference = $WarningPreference + $WarningPreference = "SilentlyContinue" + + if ($IsSupportedEnvironment) + { + # Check the current state. + $persistenceEnabled = (Get-OSLogPersistence).Enabled + if (!$persistenceEnabled) + { + # enable powershell log persistence to support exporting log entries + # for each test + Set-OsLogPersistence -Enable + } + } + [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh' + $scriptBlockCreatedRegExTemplate = @' +Creating Scriptblock text \(1 of 1\): +{0} +ScriptBlock ID: [0-9a-z\-]* +Path:.* +'@ + } + + BeforeEach { + if ($IsSupportedEnvironment) + { + # generate a unique log application id + [string] $logId = [Guid]::NewGuid().ToString('N') + + # Generate a working directory and content file for Export-OSLog + [string] $workingDirectory = Join-Path -Path $TestDrive -ChildPath $logId + $null = New-Item -Path $workingDirectory -ItemType Directory -ErrorAction Stop + + [string] $contentFile = Join-Path -Path $workingDirectory -ChildPath ('pwsh.log.txt') + # get log items after current time. + [DateTime] $after = [DateTime]::Now + } + } + + AfterAll { + $WarningPreference = $currentWarningPreference + if ($IsSupportedEnvironment -and !$persistenceEnabled) + { + # disable persistence if it wasn't enabled + Set-OsLogPersistence -Disable + } + } + + 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' + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId + + $items | Should -Not -Be $null + $items.Count | Should -BeGreaterThan 2 + $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) { + Send-VstsLogFile -Path $contentFile + } + throw + } + } + + It 'Verifies scriptblock logging' -Skip:(!$IsMacOS) { + try { + $script = @' +$PID +& ([scriptblock]::create("Write-Verbose 'testheader123' ;Write-verbose 'after'")) +'@ + $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId -LogLevel Verbose + $testFileName = 'test01.ps1' + $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName + $script | Out-File -FilePath $testScriptPath -Force + $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath + $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].EventMessage | Should -Match $testFileName + + # Verify we log that we are the script to create the scriptblock + $createdEvents[1].EventMessage | Should -Match (Get-RegEx -SimpleMatch $Script) + + # 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) { + Send-VstsLogFile -Path $contentFile + } + throw + } + } + + 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 = 'test02.ps1' + $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName + $script | Out-File -FilePath $testScriptPath -Force + $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath | Select-Object -First 1 + + $items = Get-MacOsSyslogItems -processId $testPid -logId $logId + $items | convertto-json | set-content /tmp/items.json + + $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'} + + # Verify we log that we are executing a file + $createdEvents[0].EventMessage | Should -Match $testFileName + + # 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) { + 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) + } +} + +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' + script = "Write-Verbose 'testheader123' ;Write-verbose 'after'" + expectedText="Write-Verbose 'testheader123' ;Write-verbose 'after'`r`n" + } + @{ + name = 'script block with Null' + script = "Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'" + expectedText="Write-Verbose 'testheader123␀' ;Write-verbose 'after'`r`n" + } + ) + + if ($IsSupportedEnvironment) + { + & "$PSHOME\RegisterManifest.ps1" + } + } + + AfterAll { + $WarningPreference = $currentWarningPreference + } + + BeforeEach { + if ($IsSupportedEnvironment) + { + # generate a unique log application id + [string] $logId = [Guid]::NewGuid().ToString('N') + + $logName = 'PowerShellCore' + + # get log items after current time. + [DateTime] $after = [DateTime]::Now + Clear-PSEventLog -Name "$logName/Operational" + } + } + + It 'Verifies scriptblock logging: ' -Skip:(!$IsSupportedEnvironment) -TestCases $scriptBlockLoggingCases { + param( + [string] $script, + [string] $expectedText, + [string] $name + ) + $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId + $testFileName = 'test01.ps1' + $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName + $script | Out-File -FilePath $testScriptPath -Force + $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath + + $created = Wait-PSWinEvent -FilterHashtable @{ ProviderName=$logName; Id = 4104 } ` + -PropertyName Message -PropertyValue $expectedText + + $created | Should -Not -BeNullOrEmpty + $created.Properties[0].Value | Should -Be 1 + $created.Properties[1].Value | Should -Be 1 + $created.Properties[2].Value | Should -Be $expectedText + } +} diff --git a/test/powershell/Host/PSVersionTable.Tests.ps1 b/test/powershell/Host/PSVersionTable.Tests.ps1 index 587867b2fb3..777ccb6c132 100644 --- a/test/powershell/Host/PSVersionTable.Tests.ps1 +++ b/test/powershell/Host/PSVersionTable.Tests.ps1 @@ -1,105 +1,108 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "PSVersionTable" -Tags "CI" { BeforeAll { - $sma = Get-Item (Join-Path $PSHome "System.Management.Automation.dll") + Set-StrictMode -Version 3 + $sma = Get-Item (Join-Path $PSHOME "System.Management.Automation.dll") $formattedVersion = $sma.VersionInfo.ProductVersion $mainVersionPattern = "(\d+\.\d+\.\d+)(-.+)?" - $fullVersionPattern = "^v(\d+\.\d+\.\d+)(-.+)?-(\d+)-g(.+)$" + $fullVersionPattern = "^(\d+\.\d+\.\d+)(-.+)?-(\d+)-g(.+)$" $expectedPSVersion = ($formattedVersion -split " ")[0] $expectedVersionPattern = "^$mainVersionPattern$" if ($formattedVersion.Contains(" Commits: ")) { - $rawGitCommitId = "v" + $formattedVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g") + $rawGitCommitId = $formattedVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g") $expectedGitCommitIdPattern = $fullVersionPattern $unexpectectGitCommitIdPattern = "qwerty" } else { - $rawGitCommitId = "v" + ($formattedVersion -split " SHA: ")[0] - $expectedGitCommitIdPattern = "^v$mainVersionPattern$" + $rawGitCommitId = ($formattedVersion -split " SHA: ")[0] + $expectedGitCommitIdPattern = "^$mainVersionPattern$" $unexpectectGitCommitIdPattern = $fullVersionPattern } } It "Should have version table entries" { - $PSVersionTable.Count | Should Be 9 + $PSVersionTable.Count | Should -Be 9 } It "Should have the right version table entries" { - $PSVersionTable.ContainsKey("PSVersion") | Should Be True - $PSVersionTable.ContainsKey("PSEdition") | Should Be True - $PSVersionTable.ContainsKey("WSManStackVersion") | Should Be True - $PSVersionTable.ContainsKey("SerializationVersion") | Should Be True - $PSVersionTable.ContainsKey("PSCompatibleVersions") | Should Be True - $PSVersionTable.ContainsKey("PSRemotingProtocolVersion") | Should Be True - $PSVersionTable.ContainsKey("GitCommitId") | Should Be True - $PSVersionTable.ContainsKey("Platform") | Should Be True - $PSVersionTable.ContainsKey("OS") | Should Be True + $PSVersionTable.ContainsKey("PSVersion") | Should -BeTrue + $PSVersionTable.ContainsKey("PSEdition") | Should -BeTrue + $PSVersionTable.ContainsKey("WSManStackVersion") | Should -BeTrue + $PSVersionTable.ContainsKey("SerializationVersion") | Should -BeTrue + $PSVersionTable.ContainsKey("PSCompatibleVersions") | Should -BeTrue + $PSVersionTable.ContainsKey("PSRemotingProtocolVersion") | Should -BeTrue + $PSVersionTable.ContainsKey("GitCommitId") | Should -BeTrue + $PSVersionTable.ContainsKey("Platform") | Should -BeTrue + $PSVersionTable.ContainsKey("OS") | Should -BeTrue } It "PSVersion property" { - $PSVersionTable.PSVersion | Should BeOfType "System.Management.Automation.SemanticVersion" - $PSVersionTable.PSVersion | Should BeExactly $expectedPSVersion - $PSVersionTable.PSVersion | Should Match $expectedVersionPattern - $PSVersionTable.PSVersion.Major | Should Be 6 + $PSVersionTable.PSVersion | Should -BeOfType System.Management.Automation.SemanticVersion + $PSVersionTable.PSVersion | Should -BeExactly $expectedPSVersion + $PSVersionTable.PSVersion | Should -Match $expectedVersionPattern + $PSVersionTable.PSVersion.Major | Should -Be 7 } It "GitCommitId property" { - $PSVersionTable.GitCommitId | Should BeOfType "System.String" - $PSVersionTable.GitCommitId | Should Match $expectedGitCommitIdPattern - $PSVersionTable.GitCommitId | Should Not Match $unexpectectGitCommitIdPattern - $PSVersionTable.GitCommitId | Should BeExactly $rawGitCommitId + $PSVersionTable.GitCommitId | Should -BeOfType System.String + $PSVersionTable.GitCommitId | Should -Match $expectedGitCommitIdPattern + $PSVersionTable.GitCommitId | Should -Not -Match $unexpectectGitCommitIdPattern + $PSVersionTable.GitCommitId | Should -BeExactly $rawGitCommitId } It "Should have the correct platform info" { $platform = [String][System.Environment]::OSVersion.Platform - [String]$PSVersionTable["Platform"] | Should Be $platform + [String]$PSVersionTable["Platform"] | Should -Be $platform } It "Should have the correct OS info" { if ($IsCoreCLR) { $OSDescription = [String][System.Runtime.InteropServices.RuntimeInformation]::OSDescription - [String]$PSVersionTable["OS"] | Should Be $OSDescription + [String]$PSVersionTable["OS"] | Should -Be $OSDescription } else { $OSDescription = [String][System.Environment]::OSVersion - [String]$PSVersionTable["OS"] | Should Be $OSDescription + [String]$PSVersionTable["OS"] | Should -Be $OSDescription } } It "Verify `$PSVersionTable.PSEdition" { - if ($isCoreCLR) { + if ($IsCoreCLR) { $edition = "Core" } else { $edition = "Desktop" } - $PSVersionTable["PSEdition"] | Should Be $edition + $PSVersionTable["PSEdition"] | Should -Be $edition } It "Verify `$PSVersionTable is ordered and 'PSVersion' is on first place" { $PSVersionName = "PSVersion" $keys1 = ($PSVersionTable | Format-Table -HideTableHeaders -Property Name | Out-String) -split [System.Environment]::NewLine | Where-Object {$_} | ForEach-Object {$_.Trim()} - $keys1[0] | Should Be "PSVersion" - $keys1[1] | Should Be "PSEdition" + $keys1[0] | Should -Be "PSVersion" + $keys1[1] | Should -Be "PSEdition" $keys1last = $keys1[2..($keys1.length-1)] $keys1sortedlast = $keys1last | Sort-Object - Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should Be $null + Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should -Be $null } It "Verify `$PSVersionTable can be formatted correctly when it has non-string key" { try { $key = Get-Item $PSScriptRoot $PSVersionTable.Add($key, "TEST") - { $PSVersionTable | Format-Table } | Should Not Throw + { $PSVersionTable | Format-Table } | Should -Not -Throw } finally { $PSVersionTable.Remove($key) } @@ -111,12 +114,12 @@ Describe "PSVersionTable" -Tags "CI" { $PSVersionTable.Remove("PSVersion") $keys1 = ($PSVersionTable | Format-Table -HideTableHeaders -Property Name | Out-String) -split [System.Environment]::NewLine | Where-Object {$_} | ForEach-Object {$_.Trim()} - $keys1[0] | Should Be "PSEdition" - $keys1.Length | Should Be $PSVersionTable.Count + $keys1[0] | Should -Be "PSEdition" + $keys1.Length | Should -Be $PSVersionTable.Count $keys1last = $keys1[1..($keys1.length-1)] $keys1sortedlast = $keys1last | Sort-Object - Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should Be $null + Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should -Be $null } finally { $PSVersionTable.Add("PSVersion", $VersionValue) } @@ -128,12 +131,12 @@ Describe "PSVersionTable" -Tags "CI" { $PSVersionTable.Remove("PSEdition") $keys1 = ($PSVersionTable | Format-Table -HideTableHeaders -Property Name | Out-String) -split [System.Environment]::NewLine | Where-Object {$_} | ForEach-Object {$_.Trim()} - $keys1[0] | Should Be "PSVersion" - $keys1.Length | Should Be $PSVersionTable.Count + $keys1[0] | Should -Be "PSVersion" + $keys1.Length | Should -Be $PSVersionTable.Count $keys1last = $keys1[1..($keys1.length-1)] $keys1sortedlast = $keys1last | Sort-Object - Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should Be $null + Compare-Object -ReferenceObject $keys1last -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should -Be $null } finally { $PSVersionTable.Add("PSEdition", $EditionValue) } @@ -147,13 +150,41 @@ Describe "PSVersionTable" -Tags "CI" { $PSVersionTable.Remove("PSEdition") $keys1 = ($PSVersionTable | Format-Table -HideTableHeaders -Property Name | Out-String) -split [System.Environment]::NewLine | Where-Object {$_} | ForEach-Object {$_.Trim()} - $keys1.Length | Should Be $PSVersionTable.Count + $keys1.Length | Should -Be $PSVersionTable.Count $keys1sortedlast = $keys1 | Sort-Object - Compare-Object -ReferenceObject $keys1 -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should Be $null + Compare-Object -ReferenceObject $keys1 -DifferenceObject $keys1sortedlast -SyncWindow 0 | Should -Be $null } finally { $PSVersionTable.Add("PSVersion", $VersionValue) $PSVersionTable.Add("PSEdition", $EditionValue) } } + + 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 "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/Read-Host.Tests.ps1 b/test/powershell/Host/Read-Host.Tests.ps1 index 739c4b85a02..dc225362ed0 100644 --- a/test/powershell/Host/Read-Host.Tests.ps1 +++ b/test/powershell/Host/Read-Host.Tests.ps1 @@ -1,7 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Read-Host" -Tags "Slow","Feature" { Context "[Console]::ReadKey() implementation on non-Windows" { BeforeAll { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" $assetsDir = Join-Path -Path $PSScriptRoot -ChildPath assets if ($IsWindows) { $ItArgs = @{ skip = $true } @@ -10,11 +12,17 @@ Describe "Read-Host" -Tags "Slow","Feature" { } else { $ItArgs = @{ } } + + $expectFile = Join-Path $assetsDir "Read-Host.Output.expect" + + if (-not $IsWindows) { + chmod a+x $expectFile + } } It @ItArgs "Should output correctly" { - & (Join-Path $assetsDir "Read-Host.Output.expect") $powershell | Out-Null - $LASTEXITCODE | Should Be 0 + & $expectFile $powershell | Out-Null + $LASTEXITCODE | Should -Be 0 } } } diff --git a/test/powershell/Host/Startup.Tests.ps1 b/test/powershell/Host/Startup.Tests.ps1 new file mode 100644 index 00000000000..35c22fefe58 --- /dev/null +++ b/test/powershell/Host/Startup.Tests.ps1 @@ -0,0 +1,114 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Validate start of console host" -Tag CI { + BeforeAll { + $allowedAssemblies = @( + 'Microsoft.ApplicationInsights.dll' + 'Microsoft.Management.Infrastructure.dll' + 'Microsoft.PowerShell.ConsoleHost.dll' + 'Microsoft.Win32.Primitives.dll' + 'Microsoft.Win32.Registry.dll' + 'netstandard.dll' + 'Newtonsoft.Json.dll' + 'pwsh.dll' + 'System.Collections.Concurrent.dll' + 'System.Collections.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.Process.dll' + 'System.Diagnostics.TraceSource.dll' + 'System.Diagnostics.Tracing.dll' + 'System.IO.FileSystem.AccessControl.dll' + 'System.IO.FileSystem.DriveInfo.dll' + 'System.IO.Pipes.dll' + 'System.Linq.dll' + 'System.Linq.Expressions.dll' + 'System.Management.Automation.dll' + 'System.Memory.dll' + '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' + 'System.Private.Xml.dll' + 'System.Reflection.Emit.ILGeneration.dll' + 'System.Reflection.Emit.Lightweight.dll' + 'System.Reflection.Primitives.dll' + 'System.Runtime.dll' + 'System.Runtime.InteropServices.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.dll' + 'System.Security.Principal.Windows.dll' + 'System.Text.Encoding.CodePages.dll' + 'System.Text.Encoding.Extensions.dll' + 'System.Text.RegularExpressions.dll' + 'System.Threading.dll' + 'System.Threading.Tasks.Parallel.dll' + 'System.Threading.Thread.dll' + 'System.Threading.ThreadPool.dll' + 'System.Xml.ReaderWriter.dll' + ) + + if ($IsWindows) { + $allowedAssemblies += @( + 'Microsoft.PowerShell.CoreCLR.Eventing.dll' + 'System.DirectoryServices.dll' + 'System.Management.dll' + 'System.Security.Claims.dll' + 'System.Threading.Overlapped.dll' + ) + } + else { + $allowedAssemblies += @( + 'System.Diagnostics.DiagnosticSource.dll' + 'System.Net.Sockets.dll' + ) + } + + if ($IsWindows) { + $profileDataFile = Join-Path $env:LOCALAPPDATA "Microsoft\PowerShell\StartupProfileData-NonInteractive" + } else { + $profileDataFile = Join-Path ([System.Management.Automation.Platform]::SelectProductNameForDirectory("CACHE")) "StartupProfileData-NonInteractive" + } + + if (Test-Path $profileDataFile) { + Remove-Item $profileDataFile -Force + } + + $loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike "<*>" } | ForEach-Object { $_.Name }' + } + + It "No new assemblies are loaded" { + if ( (Get-PlatformInfo).Platform -eq "alpine" ) { + Set-ItResult -Pending -Because "Missing MI library causes list to be different" + return + } + + $diffs = Compare-Object -ReferenceObject $allowedAssemblies -DifferenceObject $loadedAssemblies + + if ($null -ne $diffs) { + $assembliesAllowedButNotLoaded = $diffs | Where-Object SideIndicator -EQ "<=" | ForEach-Object InputObject + $assembliesLoadedButNotAllowed = $diffs | Where-Object SideIndicator -EQ "=>" | ForEach-Object InputObject + + if ($assembliesAllowedButNotLoaded) { + Write-Host ("Assemblies that are expected but not loaded: {0}" -f ($assembliesAllowedButNotLoaded -join ", ")) + } + if ($assembliesLoadedButNotAllowed) { + Write-Host ("Assemblies that are loaded but not expected: {0}" -f ($assembliesLoadedButNotAllowed -join ", ")) + } + } + + $diffs | Should -BeExactly $null + } +} diff --git a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 index 291dc1165c7..2280d141ac3 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -1,40 +1,70 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Tab completion bug fix" -Tags "CI" { It "Issue#682 - '[system.manage' should work" { $result = TabExpansion2 -inputScript "[system.manage" -cursorColumn "[system.manage".Length - $result | Should Not BeNullOrEmpty - $result.CompletionMatches.Count | Should Be 1 - $result.CompletionMatches[0].CompletionText | Should Be "System.Management" + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "System.Management" } It "Issue#1350 - '1 -sp' should work" { $result = TabExpansion2 -inputScript "1 -sp" -cursorColumn "1 -sp".Length - $result | Should Not BeNullOrEmpty - $result.CompletionMatches.Count | Should Be 1 - $result.CompletionMatches[0].CompletionText | Should Be "-split" + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "-split" } It "Issue#1350 - '1 -a' should work" { $result = TabExpansion2 -inputScript "1 -a" -cursorColumn "1 -a".Length - $result | Should Not BeNullOrEmpty - $result.CompletionMatches.Count | Should Be 2 - $result.CompletionMatches[0].CompletionText | Should Be "-and" - $result.CompletionMatches[1].CompletionText | Should Be "-as" + $result.CompletionMatches | Should -HaveCount 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "-and" + $result.CompletionMatches[1].CompletionText | Should -BeExactly "-as" } It "Issue#2295 - '[pscu' should expand to [pscustomobject]" { $result = TabExpansion2 -inputScript "[pscu" -cursorColumn "[pscu".Length - $result | Should Not BeNullOrEmpty - $result.CompletionMatches.Count | Should Be 1 - $result.CompletionMatches[0].CompletionText | Should Be "pscustomobject" + $result.CompletionMatches | Should -HaveCount 1 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "pscustomobject" } It "Issue#1345 - 'Import-Module -n' should work" { $cmd = "Import-Module -n" $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches.Count | Should Be 3 - $result.CompletionMatches[0].CompletionText | Should Be "-Name" - $result.CompletionMatches[1].CompletionText | Should Be "-NoClobber" - $result.CompletionMatches[2].CompletionText | Should Be "-NoOverwrite" + $result.CompletionMatches | Should -HaveCount 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "-Name" + $result.CompletionMatches[1].CompletionText | Should -BeExactly "-NoClobber" } + + It "Issue#11227 - [CompletionCompleters]::CompleteVariable and [CompletionCompleters]::CompleteType should work" { + $result = [System.Management.Automation.CompletionCompleters]::CompleteType("CompletionComple") + $result.Count | Should -BeExactly 1 + $result[0].CompletionText | Should -BeExactly 'System.Management.Automation.CompletionCompleters' + + $result = [System.Management.Automation.CompletionCompleters]::CompleteVariable("errorAction") + $result.Count | Should -BeExactly 1 + $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 @@ -42,25 +72,227 @@ Describe "Tab completion bug fix" -Tags "CI" { It "Issue#3416 - 'Select-Object -ExcludeProperty ' should work" { $cmd = "Get-Date | Select-Object -ExcludeProperty " $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches.Count | Should Be $DatetimeProperties.Count - $result.CompletionMatches[0].CompletionText | Should Be $DatetimeProperties[0].Name # Date - $result.CompletionMatches[1].CompletionText | Should Be $DatetimeProperties[1].Name # DateTime + $result.CompletionMatches | Should -HaveCount $DatetimeProperties.Count + $result.CompletionMatches[0].CompletionText | Should -BeExactly $DatetimeProperties[0].Name # Date + $result.CompletionMatches[1].CompletionText | Should -BeExactly $DatetimeProperties[1].Name # DateTime } It "Issue#3416 - 'Select-Object -ExpandProperty ' should work" { $cmd = "Get-Date | Select-Object -ExpandProperty " $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches.Count | Should Be $DatetimeProperties.Count - $result.CompletionMatches[0].CompletionText | Should Be $DatetimeProperties[0].Name # Date - $result.CompletionMatches[1].CompletionText | Should Be $DatetimeProperties[1].Name # DateTime + $result.CompletionMatches | Should -HaveCount $DatetimeProperties.Count + $result.CompletionMatches[0].CompletionText | Should -BeExactly $DatetimeProperties[0].Name # Date + $result.CompletionMatches[1].CompletionText | Should -BeExactly $DatetimeProperties[1].Name # DateTime } } - + It "Issue#3628 - 'Sort-Object @{' should work" { $cmd = "Get-Date | Sort-Object @{" $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $result.CompletionMatches.Count | Should Be 3 - $result.CompletionMatches[0].CompletionText | Should Be 'Expression' - $result.CompletionMatches[1].CompletionText | Should Be 'Ascending' - $result.CompletionMatches[2].CompletionText | Should Be 'Descending' + $result.CompletionMatches | Should -HaveCount 3 + $result.CompletionMatches[0].CompletionText | Should -BeExactly 'Expression' + $result.CompletionMatches[1].CompletionText | Should -BeExactly 'Ascending' + $result.CompletionMatches[2].CompletionText | Should -BeExactly 'Descending' + } + + It "'Get-Date | Sort-Object @{Expression=' should work without completion" { + $cmd = "Get-Date | Sort-Object @{Expression=" + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches | Should -HaveCount 0 + } + + It "Issue#5322 - 'Get-Date | Sort-Object @{Expression=...;' should work" { + $cmd = "Get-Date | Sort-Object @{Expression=...;" + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CurrentMatchIndex | Should -Be -1 + $result.ReplacementIndex | Should -Be 40 + $result.ReplacementLength | Should -Be 0 + $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 b199918dd7b..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "TabCompletion" -Tags CI { BeforeAll { $separator = [System.IO.Path]::DirectorySeparatorChar @@ -5,124 +7,1934 @@ Describe "TabCompletion" -Tags CI { It 'Should complete Command' { $res = TabExpansion2 -inputScript 'Get-Com' -cursorColumn 'Get-Com'.Length - $res.CompletionMatches[0].CompletionText | Should be Get-Command + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Get-Command' + } + + It 'Should complete abbreviated cmdlet' { + $res = (TabExpansion2 -inputScript 'i-psdf' -cursorColumn 'pschr'.Length).CompletionMatches.CompletionText + $res | Should -HaveCount 1 + $res | Should -BeExactly 'Import-PowerShellDataFile' + } + + It 'Should complete abbreviated function' { + function Test-AbbreviatedFunctionExpansion {} + $res = (TabExpansion2 -inputScript 't-afe' -cursorColumn 't-afe'.Length).CompletionMatches.CompletionText + $res.Count | Should -BeGreaterOrEqual 1 + $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 be notepad.exe + $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' { + $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' { + $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(' + } + + It "Should complete Magic where" { + $res = TabExpansion2 -inputScript '(1..10).wh' -cursorColumn '(1..10).wh'.Length + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Where(' + } + + It 'Should complete types' { + $res = TabExpansion2 -inputScript '[pscu' -cursorColumn '[pscu'.Length + $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' + } + + It 'Should complete format-table hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Table @{ ' -cursorColumn 'Get-ChildItem | Format-Table @{ '.Length + $res.CompletionMatches | Should -HaveCount 5 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Alignment Expression FormatString Label Width' + } + + It 'Should complete format-* hashtable on GroupBy: ' -TestCases ( + @{cmd = 'Format-Table'}, + @{cmd = 'Format-List'}, + @{cmd = 'Format-Wide'}, + @{cmd = 'Format-Custom'} + ) { + param($cmd) + $res = TabExpansion2 -inputScript "Get-ChildItem | $cmd -GroupBy @{ " -cursorColumn "Get-ChildItem | $cmd -GroupBy @{ ".Length + $res.CompletionMatches | Should -HaveCount 3 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Expression FormatString Label' + } + + It 'Should complete format-list hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-List @{ ' -cursorColumn 'Get-ChildItem | Format-List @{ '.Length + $res.CompletionMatches | Should -HaveCount 3 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Expression FormatString Label' + } + + It 'Should complete format-wide hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Wide @{ ' -cursorColumn 'Get-ChildItem | Format-Wide @{ '.Length + $res.CompletionMatches | Should -HaveCount 2 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Expression FormatString' + } + + It 'Should complete format-custom hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Custom @{ ' -cursorColumn 'Get-ChildItem | Format-Custom @{ '.Length + $res.CompletionMatches | Should -HaveCount 2 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Depth Expression' + } + + It 'Should complete Select-Object hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Select-Object @{ ' -cursorColumn 'Get-ChildItem | Select-Object @{ '.Length + $res.CompletionMatches | Should -HaveCount 2 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Expression Name' + } + + It 'Should complete Sort-Object hashtable' { + $res = TabExpansion2 -inputScript 'Get-ChildItem | Sort-Object @{ ' -cursorColumn 'Get-ChildItem | Sort-Object @{ '.Length + $res.CompletionMatches | Should -HaveCount 3 + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly 'Ascending Descending Expression' + } + + 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){} + } + 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 'Should complete dotnet method' { - $res = TabExpansion2 -inputScript '(1).ToSt' -cursorColumn '(1).ToSt'.Length - $res.CompletionMatches[0].CompletionText | Should be 'ToString(' - } + 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' { + Set-StrictMode -Version 3.0 + $cmd = 'Get-Process -Id ' + [System.Management.Automation.CommandCompletion]$res = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $res.CompletionMatches[0].CompletionText -match '^\d+$' | Should -BeTrue + $res.CompletionMatches[0].ListItemText -match '^\d+ -' | Should -BeTrue + $res.CompletionMatches[0].ToolTip -match '^\d+ -' | Should -BeTrue + } + + It 'Should complete "Get-Process" with process names' { + $cmd = "Get-Process " + $res = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + # Can't compare to number of processes since macOS has a large number of processes + # that have empty Name which should be skipped + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + } + + 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' + $res.CompletionMatches[1].CompletionText | Should -BeExactly '-Functionality' + } + + It 'Should first suggest -Full and then -Functionality when using help -Fu' -Skip { + $res = TabExpansion2 -inputScript 'help -Fu' -cursorColumn 'help -Fu'.Length + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Full' + $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 } + @{ inputStr = '$ErrorActionPreference="'; filter = ''; doubleQuotes = $true } + @{ inputStr = '$ErrorActionPreference = ''s'; filter = '| Where-Object { $_ -like "''s*" }'; doubleQuotes = $false } + @{ inputStr = '$ErrorActionPreference = "siL'; filter = '| Where-Object { $_ -like ''"sil*'' }'; doubleQuotes = $true } + @{ inputStr = '[System.Management.Automation.ActionPreference]$e='; filter = ''; doubleQuotes = $false } + @{ inputStr = '[System.Management.Automation.ActionPreference]$e = '; filter = ''; doubleQuotes = $false } + @{ inputStr = '[System.Management.Automation.ActionPreference]$e = "'; filter = ''; doubleQuotes = $true } + @{ inputStr = '[System.Management.Automation.ActionPreference]$e = "s'; filter = '| Where-Object { $_ -like """s*" }'; doubleQuotes = $true } + @{ inputStr = '[System.Management.Automation.ActionPreference]$e = "x'; filter = '| Where-Object { $_ -like """x*" }'; doubleQuotes = $true } + ){ + param($inputStr, $filter, $doubleQuotes) + + $quote = '''' + if ($doubleQuotes) { + $quote = '"' + } + + $sb = [scriptblock]::Create(@" + [cmdletbinding()] param([Parameter(ValueFromPipeline=`$true)]`$obj) process { `$obj $filter } +"@) + + $expectedValues = [enum]::GetValues("System.Management.Automation.ActionPreference") | ForEach-Object { $quote + $_.ToString() + $quote } | & $sb | Sort-Object + if ($expectedValues.Count -gt 0) { + $expected = [string]::Join(",",$expectedValues) + } + else { + $expected = '' + } + + $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 + } + } + + AfterAll { + if ($IsWindows) { + Remove-Item -Path $registryPath -Force + Remove-Item -LiteralPath $registryLiteralPath -Force + Remove-Item -Path $fileSystemPath -Force + Remove-Item -LiteralPath $fileSystemLiteralPathDir -Recurse -Force + } + } + } + + Context 'Get-Command -Noun parameter completion' { + BeforeAll { + function GetModuleCommandNouns( + [string]$Module, + [string]$Verb, + [switch]$SingleQuote, + [switch]$DoubleQuote) + { + + $commandParams = @{} + + if ($PSBoundParameters.ContainsKey('Module')) { + $commandParams['Module'] = $Module + } - It 'Should complete Magic foreach' { - $res = TabExpansion2 -inputScript '(1..10).Fo' -cursorColumn '(1..10).Fo'.Length - $res.CompletionMatches[0].CompletionText | Should be 'Foreach(' - } + if ($PSBoundParameters.ContainsKey('Verb')) { + $commandParams['Verb'] = $Verb + } - It "Should complete Magic where" { - $res = TabExpansion2 -inputScript '(1..10).wh' -cursorColumn '(1..10).wh'.Length - $res.CompletionMatches[0].CompletionText | Should be 'Where(' - } + $nouns = (Get-Command @commandParams).Noun - It 'Should complete types' { - $res = TabExpansion2 -inputScript '[pscu' -cursorColumn '[pscu'.Length - $res.CompletionMatches[0].CompletionText | Should be 'pscustomobject' - } + if ($SingleQuote) { + return ($nouns | ForEach-Object { "'$_'" }) + } + elseif ($DoubleQuote) { + return ($nouns | ForEach-Object { """$_""" }) + } - It 'Should complete namespaces' { - $res = TabExpansion2 -inputScript 'using namespace Sys' -cursorColumn 'using namespace Sys'.Length - $res.CompletionMatches[0].CompletionText | Should be 'System' - } + return $nouns + } - It 'Should complete format-table hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Table @{ ' -cursorColumn 'Get-ChildItem | Format-Table @{ '.Length - $res.CompletionMatches.Count | Should Be 5 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' ' | Should Be 'Alignment Expression FormatString Label Width' - } + $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*"} - It 'Should complete format-* hashtable on GroupBy: ' -TestCases ( - @{cmd = 'Format-Table'}, - @{cmd = 'Format-List'}, - @{cmd = 'Format-Wide'}, - @{cmd = 'Format-Custom'} - ) { - param($cmd) - $res = TabExpansion2 -inputScript "Get-ChildItem | $cmd -GroupBy @{ " -cursorColumn "Get-ChildItem | $cmd -GroupBy @{ ".Length - $res.CompletionMatches.Count | Should Be 3 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' ' | Should Be 'Expression FormatString Label' - } + $allUtilityCommandNounsWithConvertToVerb = GetModuleCommandNouns -Module $utilityModuleName -Verb 'ConvertTo' + $allUtilityCommandNounsWithConvertToVerbSingleQuote = GetModuleCommandNouns -Module $utilityModuleName -SingleQuote -Verb 'ConvertTo' + $allUtilityCommandNounsWithConvertToVerbDoubleQuote = GetModuleCommandNouns -Module $utilityModuleName -DoubleQuote -Verb 'ConvertTo' + $utilityCommandNounsWithConvertToVerb = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv', 'Html', 'Json', 'Xml' } + $utilityCommandNounsWithConvertToVerbSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'", "'Html'", "'Json'", "'Xml'" } + $utilityCommandNounsWithConvertToVerbDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""", """Html""", """Json""", """Xml""" } + $utilityCommandNounsWithConvertToVerbStartingWithC = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv' } + $utilityCommandNounsWithConvertToVerbStartingWithCSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'" } + $utilityCommandNounsWithConvertToVerbStartingWithCDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""" } + } - It 'Should complete format-list hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-List @{ ' -cursorColumn 'Get-ChildItem | Format-List @{ '.Length - $res.CompletionMatches.Count | Should Be 3 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' ' | Should Be 'Expression FormatString Label' - } + 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 - It 'Should complete format-wide hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Wide @{ ' -cursorColumn 'Get-ChildItem | Format-Wide @{ '.Length - $res.CompletionMatches.Count | Should Be 2 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' ' | Should Be 'Expression FormatString' - } + # 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 + } - It 'Should complete format-custom hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Custom @{ ' -cursorColumn 'Get-ChildItem | Format-Custom @{ '.Length - $res.CompletionMatches.Count | Should Be 2 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' ' | Should Be 'Depth Expression' + $completionText -join ' ' | Should -BeExactly ($sortedSetExpectedNouns -join ' ') + } } - It 'Should complete Select-Object hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Select-Object @{ ' -cursorColumn 'Get-ChildItem | Select-Object @{ '.Length - $res.CompletionMatches.Count | Should Be 2 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' '| Should Be 'Expression Name' - } + Context "Get-ExperimentalFeature -Name parameter completion" { + BeforeAll { + function GetExperimentalFeatureNames([switch]$SingleQuote, [switch]$DoubleQuote) { + $features = (Get-ExperimentalFeature).Name - It 'Should complete Sort-Object hashtable' { - $res = TabExpansion2 -inputScript 'Get-ChildItem | Sort-Object @{ ' -cursorColumn 'Get-ChildItem | Sort-Object @{ '.Length - $res.CompletionMatches.Count | Should Be 3 - $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' '| Should Be 'Ascending Descending Expression' - } + if ($SingleQuote) { + return ($features | ForEach-Object { "'$_'" }) + } + elseif ($DoubleQuote) { + return ($features | ForEach-Object { """$_""" }) + } + + return $features + } + + $allExperimentalFeatures = GetExperimentalFeatureNames + $allExperimentalFeaturesSingleQuote = GetExperimentalFeatureNames -SingleQuote + $allExperimentalFeaturesDoubleQuote = GetExperimentalFeatureNames -DoubleQuote + $experimentalFeaturesStartingWithPS = $allExperimentalFeatures | Where-Object { $_ -like 'PS*'} + $experimentalFeaturesStartingWithPSSingleQuote = $allExperimentalFeaturesSingleQuote | Where-Object { $_ -like "'PS*" } + $experimentalFeaturesStartingWithPSDoubleQuote = $allExperimentalFeaturesDoubleQuote | Where-Object { $_ -like """PS*" } + } - It 'Should complete New-Object hashtable' { - class X { - $A - $B - $C + 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 ' ') } - $res = TabExpansion2 -inputScript 'New-Object -TypeName X -Property @{ ' -cursorColumn 'New-Object -TypeName X -Property @{ '.Length - $res.CompletionMatches.Count | Should Be 3 - $res.CompletionMatches.CompletionText -join ' ' | Should Be 'A B C' } - It 'Should complete "Get-Process -Id " with Id and name in tooltip' { - Set-StrictMode -Version latest - $cmd = 'Get-Process -Id ' - [System.Management.Automation.CommandCompletion]$res = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $res.CompletionMatches[0].CompletionText -match '^\d+$' | Should be true - $res.CompletionMatches[0].ListItemText -match '^\d+ -' | Should be true - $res.CompletionMatches[0].ToolTip -match '^\d+ -' | Should be true + Context "Join-String -Separator & -FormatString parameter completion" { + BeforeAll { + if ($IsWindows) { + $allSeparators = "',' ', ' ';' '; ' ""``r``n"" '-' ' '" + $allFormatStrings = "'[{0}]' '{0:N2}' ""``r``n ```${0}"" ""``r``n [string] ```${0}""" + $newlineSeparator = """``r``n""" + $newlineFormatStrings = """``r``n ```${0}"" ""``r``n [string] ```${0}""" + } + else { + $allSeparators = "',' ', ' ';' '; ' ""``n"" '-' ' '" + $allFormatStrings = "'[{0}]' '{0:N2}' ""``n ```${0}"" ""``n [string] ```${0}""" + $newlineSeparator = """``n""" + $newlineFormatStrings = """``n ```${0}"" ""``n [string] ```${0}""" + } + + $commaSeparators = "',' ', '" + $semiColonSeparators = "';' '; '" + + $squareBracketFormatString = "'[{0}]'" + $curlyBraceFormatString = "'{0:N2}'" + } + + It "Should complete for ''" -TestCases @( + @{ TextInput = "Join-String -Separator "; Expected = $allSeparators } + @{ TextInput = "Join-String -Separator '"; Expected = $allSeparators } + @{ TextInput = "Join-String -Separator """; Expected = $allSeparators.Replace("'", """") } + @{ TextInput = "Join-String -Separator ',"; Expected = $commaSeparators } + @{ TextInput = "Join-String -Separator "","; Expected = $commaSeparators.Replace("'", """") } + @{ TextInput = "Join-String -Separator ';"; Expected = $semiColonSeparators } + @{ TextInput = "Join-String -Separator "";"; Expected = $semiColonSeparators.Replace("'", """") } + @{ TextInput = "Join-String -FormatString "; Expected = $allFormatStrings } + @{ TextInput = "Join-String -FormatString '"; Expected = $allFormatStrings } + @{ TextInput = "Join-String -FormatString """; Expected = $allFormatStrings.Replace("'", """") } + @{ TextInput = "Join-String -FormatString ["; Expected = $squareBracketFormatString } + @{ TextInput = "Join-String -FormatString '["; Expected = $squareBracketFormatString } + @{ TextInput = "Join-String -FormatString ""["; Expected = $squareBracketFormatString.Replace("'", """") } + @{ TextInput = "Join-String -FormatString '{"; Expected = $curlyBraceFormatString } + @{ TextInput = "Join-String -FormatString ""{"; Expected = $curlyBraceFormatString.Replace("'", """") } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } + + It "Should complete for ''" -Skip:(!$IsWindows) -TestCases @( + @{ TextInput = "Join-String -Separator '``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``r"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``r"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``r``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``r``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -FormatString '``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``r"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``r"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``r``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``r``"; Expected = $newlineFormatStrings } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } + + It "Should complete for ''" -Skip:($IsWindows) -TestCases @( + @{ TextInput = "Join-String -Separator '``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``n"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``n"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -FormatString '``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``n"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``n"; Expected = $newlineFormatStrings } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } } - It 'Should complete keyword' -skip { - $res = TabExpansion2 -inputScript 'using nam' -cursorColumn 'using nam'.Length - $res.CompletionMatches[0].CompletionText | Should Be 'namespace' + Context "Format cmdlet's View paramter completion" { + BeforeAll { + $viewDefinition = @' + + + + + R A M + + System.Diagnostics.Process + + + + + + 40 + Center + + + + 40 + Center + + + + 40 + Center + + + + + + + Center + Name + + + Center + PagedMemorySize + + + Center + PeakWorkingSet + + + + + + + + +'@ + + $tempViewFile = Join-Path -Path $TestDrive -ChildPath 'processViewDefinition.ps1xml' + Set-Content -LiteralPath $tempViewFile -Value $viewDefinition -Force + + $ps = [PowerShell]::Create() + $null = $ps.AddScript("Update-FormatData -AppendPath $tempViewFile") + $ps.Invoke() + $ps.HadErrors | Should -BeFalse + $ps.Commands.Clear() + + Remove-Item -LiteralPath $tempViewFile -Force -ErrorAction SilentlyContinue + } + + It 'Should complete Get-ChildItem | -View' -TestCases ( + @{ cmd = 'Format-Table'; expected = "children childrenWithHardlink$(if (!$IsWindows) { ' childrenWithUnixStat' })" }, + @{ cmd = 'Format-List'; expected = 'children' }, + @{ cmd = 'Format-Wide'; expected = 'children' }, + @{ cmd = 'Format-Custom'; expected = '' } + ) { + param($cmd, $expected) + + # The completion is based on OutputTypeAttribute() of the cmdlet. + $res = TabExpansion2 -inputScript "Get-ChildItem | $cmd -View " -cursorColumn "Get-ChildItem | $cmd -View ".Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $expected + } + + It 'Should complete $processList = Get-Process; $processList | ' -TestCases ( + @{ cmd = 'Format-Table -View '; expected = "'R A M'", "Priority", "process", "ProcessModule", "ProcessWithUserName", "StartTime" }, + @{ cmd = 'Format-List -View '; expected = '' }, + @{ cmd = 'Format-Wide -View '; expected = 'process' }, + @{ cmd = 'Format-Custom -View '; expected = '' }, + @{ cmd = 'Format-Table -View S'; expected = "StartTime" }, + @{ cmd = "Format-Table -View 'S"; expected = "'StartTime'" }, + @{ cmd = "Format-Table -View R"; expected = "'R A M'" } + ) { + param($cmd, $expected) + + $null = $ps.AddScript({ + param ($cmd) + $processList = Get-Process + $res = TabExpansion2 -inputScript "`$processList | $cmd" -cursorColumn "`$processList | $cmd".Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText + }).AddArgument($cmd) + + $result = $ps.Invoke() + $ps.Commands.Clear() + $expected = ($expected | Sort-Object) -join ' ' + $result -join ' ' | Should -BeExactly $expected + } } Context NativeCommand { BeforeAll { - $nativeCommand = (Get-Command -CommandType Application -TotalCount 1).Name + ## 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) @@ -135,8 +1947,8 @@ Describe "TabCompletion" -Tags CI { } $line = "$nativeCommand -" $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches.CompletionText | Should Be "-flag" + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-flag" } It 'Completes native commands with --' { @@ -151,8 +1963,8 @@ Describe "TabCompletion" -Tags CI { } $line = "$nativeCommand --" $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches.CompletionText | Should Be "--flag" + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "--flag" } It 'Completes native commands with --f' { @@ -166,9 +1978,9 @@ Describe "TabCompletion" -Tags CI { } } $line = "$nativeCommand --f" - $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches.CompletionText | Should Be "--flag" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "--flag" } It 'Completes native commands with -o' { @@ -182,17 +1994,224 @@ Describe "TabCompletion" -Tags CI { } } $line = "$nativeCommand -o" - $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches.CompletionText | Should Be "-option" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $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 { $res = TabExpansion2 -inputScript 'Export-Counter -FileFormat ' -cursorColumn 'Export-Counter -FileFormat '.Length - $res.CompletionMatches.Count | Should Be 3 + $res.CompletionMatches | Should -HaveCount 3 $completionText = $res.CompletionMatches.CompletionText | Sort-Object - $completionText -join ' '| Should Be 'blg csv tsv' + $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 "" + Setup -f 'remove-powershell.ps1' -Content "" + + $scriptWithWildcardCases = @( + @{ + command = '.\install-*.ps1' + expectedCommand = Join-Path -Path '.' -ChildPath 'install-powershell.ps1' + name = "'$(Join-Path -Path '.' -ChildPath 'install-powershell.ps1')'" + } + @{ + command = (Join-Path ${TestDrive} -ChildPath 'install-*.ps1') + expectedCommand = (Join-Path ${TestDrive} -ChildPath 'install-powershell.ps1') + name = "'$(Join-Path -Path '.' -ChildPath 'install-powershell.ps1')' by fully qualified path" + } + @{ + command = '.\?emove-powershell.ps1' + expectedCommand = Join-Path -Path '.' -ChildPath 'remove-powershell.ps1' + name = "'$(Join-Path -Path '.' -ChildPath '?emove-powershell.ps1')'" + } + @{ + # [] cause the parser to create a new token. + # So, the command must be quoted to tab complete. + command = "'.\[ra]emove-powershell.ps1'" + expectedCommand = "'$(Join-Path -Path '.' -ChildPath 'remove-powershell.ps1')'" + name = "'$(Join-Path -Path '.' -ChildPath '[ra]emove-powershell.ps1')'" + } + ) + + Push-Location ${TestDrive}\ + } + + AfterAll { + Pop-Location + } + + It "Input should successfully complete" -TestCases $scriptWithWildcardCases { + param($command, $expectedCommand) + $res = TabExpansion2 -inputScript $command -cursorColumn $command.Length + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expectedCommand + } + } + + 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" { @@ -201,6 +2220,7 @@ Describe "TabCompletion" -Tags CI { $oneSubDir = Join-Path -Path $tempDir -ChildPath "oneSubDir" $oneSubDirPrime = Join-Path -Path $tempDir -ChildPath "prime" $twoSubDir = Join-Path -Path $oneSubDir -ChildPath "twoSubDir" + $caseTestPath = Join-Path $testdrive "CaseTest" New-Item -Path $tempDir -ItemType Directory -Force > $null New-Item -Path $oneSubDir -ItemType Directory -Force > $null @@ -233,12 +2253,17 @@ Describe "TabCompletion" -Tags CI { } } + BeforeEach { + New-Item -ItemType Directory -Path $caseTestPath > $null + } + AfterAll { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } AfterEach { Pop-Location + Remove-Item -Path $caseTestPath -Recurse -Force -ErrorAction SilentlyContinue } It "Input '' should successfully complete" -TestCases $testCases { @@ -246,8 +2271,8 @@ Describe "TabCompletion" -Tags CI { Push-Location -Path $tempDir $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $localExpected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $localExpected } It "Input '' should successfully complete with relative path '..\'" -TestCases $testCases { @@ -256,8 +2281,8 @@ Describe "TabCompletion" -Tags CI { Push-Location -Path $oneSubDir $inputStr = "..\${inputStr}" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $oneSubExpected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $oneSubExpected } It "Input '' should successfully complete with relative path '..\..\'" -TestCases $testCases { @@ -266,8 +2291,8 @@ Describe "TabCompletion" -Tags CI { Push-Location -Path $twoSubDir $inputStr = "../../${inputStr}" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $twoSubExpected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $twoSubExpected } It "Input '' should successfully complete with relative path '..\..\..\ba*\'" -TestCases $testCases { @@ -276,8 +2301,8 @@ Describe "TabCompletion" -Tags CI { Push-Location -Path $twoSubDir $inputStr = "..\..\..\ba*\${inputStr}" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $twoSubExpected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $twoSubExpected } It "Test relative path" { @@ -285,8 +2310,8 @@ Describe "TabCompletion" -Tags CI { $beforeTab = "twoSubDir/../../pri" $afterTab = "..${separator}prime" $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be $afterTab + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } It "Test path with both '\' and '/'" { @@ -294,30 +2319,226 @@ Describe "TabCompletion" -Tags CI { $beforeTab = "..\../..\ba*/ab" $afterTab = "..${separator}..${separator}abc" $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be $afterTab + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab + } + + It "Test case insensitive path" -Skip:(!$IsLinux) -TestCases @( + @{ type = "File" ; beforeTab = "Get-Content f" }, + @{ type = "Directory"; beforeTab = "cd f" } + ) { + param ($type, $beforeTab) + + $testItems = "foo", "Foo", "fOO" + $testItems | ForEach-Object { + $itemPath = Join-Path $caseTestPath $_ + New-Item -ItemType $type -Path $itemPath + } + Push-Location $caseTestPath + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches | Should -HaveCount $testItems.Count + + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($testItems | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + $completions | Should -BeExactly $expected + } + + It "Test case insensitive file and folder path completing for " -Skip:(!$IsLinux) -TestCases @( + @{ type = "File" ; beforeTab = "Get-Content f"; expected = "foo","Foo" }, # Get-Content passes thru to provider + @{ type = "Directory"; beforeTab = "cd f" ; expected = "Foo" } # Set-Location is aware of Files vs Folders + ) { + param ($beforeTab, $expected) + + $filePath = Join-Path $caseTestPath "foo" + $folderPath = Join-Path $caseTestPath "Foo" + New-Item -ItemType File -Path $filePath + New-Item -ItemType Directory -Path $folderPath + Push-Location $caseTestPath + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches | Should -HaveCount $expected.Count + + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($expected | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + } + + It "PSScriptRoot path completion when AST extent has file identity" { + $scriptText = '"$PSScriptRoot\BugFix.Tests"' + $tokens = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput( + $scriptText, + $PSCommandPath, + [ref] $tokens, + [ref] $null) + + $cursorPosition = $scriptAst.Extent.StartScriptPosition. + GetType(). + GetMethod('CloneWithNewOffset', [System.Reflection.BindingFlags]'NonPublic, Instance'). + Invoke($scriptAst.Extent.StartScriptPosition, @($scriptText.Length - 1)) + + $res = TabExpansion2 -ast $scriptAst -tokens $tokens -positionOfCursor $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $expectedPath = Join-Path $PSScriptRoot -ChildPath BugFix.Tests.ps1 + $res.CompletionMatches[0].CompletionText | Should -Be "`"$expectedPath`"" + } + + It "Relative path completion for using statement when AST extent has file identity" -TestCases @( + @{UsingKind = "module"; ExpectedFileName = 'UsingFileCompletionModuleTest.psm1'} + @{UsingKind = "assembly";ExpectedFileName = 'UsingFileCompletionAssemblyTest.dll'} + ) -test { + param($UsingKind, $ExpectedFileName) + $scriptText = "using $UsingKind .\UsingFileCompletion" + $tokens = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput( + $scriptText, + (Join-Path -Path $tempDir -ChildPath ScriptInEditor.ps1), + [ref] $tokens, + [ref] $null) + + $cursorPosition = $scriptAst.Extent.StartScriptPosition. + GetType(). + GetMethod('CloneWithNewOffset', [System.Reflection.BindingFlags]'NonPublic, Instance'). + Invoke($scriptAst.Extent.StartScriptPosition, @($scriptText.Length - 1)) + + Push-Location -LiteralPath $PSHOME + $TestFile = Join-Path -Path $tempDir -ChildPath $ExpectedFileName + $null = New-Item -Path $TestFile + $res = TabExpansion2 -ast $scriptAst -tokens $tokens -positionOfCursor $cursorPosition + Pop-Location + + $ExpectedPath = Join-Path -Path '.\' -ChildPath $ExpectedFileName + $res.CompletionMatches.CompletionText | Where-Object {$_ -Like "*$ExpectedFileName"} | Should -Be $ExpectedPath + } + + It "Should handle '~' in completiontext when it's used to refer to home in input" { + $res = TabExpansion2 -inputScript "~$separator" + # select the first answer which does not have a space in the completion (those completions look like & '3D Objects') + $observedResult = $res.CompletionMatches.Where({$_.CompletionText.IndexOf("&") -eq -1})[0].CompletionText + $completedText = $res.CompletionMatches.CompletionText -join "," + if ($IsWindows) { + $observedResult | Should -BeLike "$home$separator*" -Because "$completedText" + } else { + $observedResult | Should -BeLike "~$separator*" -Because "$completedText" + } + } + + It "Should use '~' as relative filter text when not followed by separator" { + $TempDirName = "~TempDir" + $TempDirPath = Join-Path -Path $TestDrive -ChildPath "~TempDir" + $TempDir = New-Item -Path $TempDirPath -ItemType Directory -Force + Push-Location -Path $TestDrive + $res = TabExpansion2 -inputScript ~ + $res.CompletionMatches[0].CompletionText | Should -Be ".${separator}${TempDirName}" + } + + It 'Escapes backtick properly for path: ' -TestCases @( + @{LiteralPath = 'BacktickTest['; BacktickSingle = 1; BacktickDouble = 2; LiteralBacktickSingle = 0; LiteralBacktickDouble = 0} + @{LiteralPath = 'BacktickTest`['; BacktickSingle = 3; BacktickDouble = 6; LiteralBacktickSingle = 1; LiteralBacktickDouble = 2} + @{LiteralPath = 'BacktickTest``['; BacktickSingle = 5; BacktickDouble = 10; LiteralBacktickSingle = 2; LiteralBacktickDouble = 4} + @{LiteralPath = 'BacktickTest$'; BacktickSingle = 0; BacktickDouble = 1; LiteralBacktickSingle = 0; LiteralBacktickDouble = 1} + @{LiteralPath = 'BacktickTest`$'; BacktickSingle = 2; BacktickDouble = 3; LiteralBacktickSingle = 1; LiteralBacktickDouble = 3} + @{LiteralPath = 'BacktickTest``$'; BacktickSingle = 4; BacktickDouble = 7; LiteralBacktickSingle = 2; LiteralBacktickDouble = 5} + ) { + param($LiteralPath, $BacktickSingle, $BacktickDouble, $LiteralBacktickSingle, $LiteralBacktickDouble) + $NewPath = Join-Path -Path $TestDrive -ChildPath $LiteralPath + $null = New-Item -Path $NewPath -Force + Push-Location $TestDrive + + $InputText = "Get-ChildItem -Path {0}.${separator}BacktickTest" + $InputTextLiteral = "Get-ChildItem -LiteralPath {0}.${separator}BacktickTest" + + $Text = (TabExpansion2 -inputScript ($InputText -f "'")).CompletionMatches[0].CompletionText + $Text.Length - $Text.Replace('`','').Length | Should -Be $BacktickSingle + + $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 = "set-alia?"; expected = "set-alias" } - @{ inputStr = "s*-alias"; expected = "set-alias" } - @{ inputStr = "se*-alias"; expected = "set-alias" } - @{ inputStr = "set-al"; expected = "set-alias" } - @{ inputStr = "set-a?i"; expected = "set-alias" } - @{ inputStr = "set-?lias"; expected = "set-alias" } - @{ inputStr = "get-*ditem"; expected = "get-childitem" } - @{ inputStr = "Microsoft.PowerShell.Management\get-c*item"; expected = "Microsoft.PowerShell.Management\get-childitem" } - @{ inputStr = "Microsoft.PowerShell.Utility\set-alia?"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Utility\s*-alias"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Utility\se*-alias"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Utility\set-al"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Utility\set-a?i"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Utility\set-?lias"; expected = "Microsoft.PowerShell.Utility\set-alias" } - @{ inputStr = "Microsoft.PowerShell.Management\get-*ditem"; expected = "Microsoft.PowerShell.Management\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" } + @{ inputStr = "set-al"; expected = "Set-Alias" } + @{ inputStr = "set-a?i"; expected = "Set-Alias" } + @{ inputStr = "set-?lias"; expected = "Set-Alias" } + @{ inputStr = "get-c*ditem"; expected = "Get-ChildItem" } + @{ inputStr = "Microsoft.PowerShell.Management\get-c*item"; expected = "Microsoft.PowerShell.Management\Get-ChildItem" } + @{ inputStr = "Microsoft.PowerShell.Utility\set-alia?"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Utility\s*-alias"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Utility\se*-alias"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Utility\set-al"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Utility\set-a?i"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Utility\set-?lias"; expected = "Microsoft.PowerShell.Utility\Set-Alias" } + @{ inputStr = "Microsoft.PowerShell.Management\get-*ditem"; expected = "Microsoft.PowerShell.Management\Get-ChildItem" } ) } @@ -325,7 +2546,7 @@ Describe "TabCompletion" -Tags CI { param($inputStr, $expected) $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } } @@ -336,21 +2557,23 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = "get-childitem -Fil"; expected = "-Filter"; setup = $null } @{ inputStr = '$arg'; expected = '$args'; setup = $null } @{ inputStr = '$args.'; expected = 'Count'; setup = $null } - @{ inputStr = '$host.UI.Ra'; expected = 'RawUI'; setup = $null } - @{ inputStr = '$host.UI.WriteD'; expected = 'WriteDebugLine('; setup = $null } + @{ inputStr = '$Host.UI.Ra'; expected = 'RawUI'; setup = $null } + @{ inputStr = '$Host.UI.WriteD'; expected = 'WriteDebugLine('; setup = $null } @{ inputStr = '$MaximumHistoryCount.'; expected = 'CompareTo('; setup = $null } @{ inputStr = '$A=[datetime]::now;$A.'; expected = 'Date'; setup = $null } + @{ inputStr = '$e=$null;try { 1/0 } catch {$e=$_};$e.'; expected = 'CategoryInfo'; setup = $null } @{ inputStr = '$x= gps pwsh;$x.*pm'; expected = 'NPM'; setup = $null } + @{ inputStr = 'function Get-ScrumData {}; Get-Scrum'; expected = 'Get-ScrumData'; setup = $null } @{ inputStr = 'function write-output {param($abcd) $abcd};Write-Output -a'; expected = '-abcd'; setup = $null } @{ inputStr = 'function write-output {param($abcd) $abcd};Microsoft.PowerShell.Utility\Write-Output -'; expected = '-InputObject'; setup = $null } - @{ inputStr = '[math]::Co'; expected = 'Cos('; setup = $null } + @{ inputStr = '[math]::Co'; expected = 'CopySign('; setup = $null } @{ inputStr = '[math]::PI.GetT'; expected = 'GetType('; setup = $null } @{ inputStr = '[math]'; expected = '::E'; setup = $null } @{ inputStr = '[math].'; expected = 'Assembly'; setup = $null } @{ 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} } } @@ -366,7 +2589,7 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = 'Get-PSDrive -PSProvider Variable '; expected = 'Variable'; setup = $null } @{ inputStr = 'Get-Command Get-Chil'; expected = 'Get-ChildItem'; setup = $null } @{ inputStr = 'Get-Variable psver'; expected = 'PSVersionTable'; setup = $null } - @{ inputStr = 'Get-Help *child'; expected = 'Get-ChildItem'; setup = $null } + @{ inputStr = 'Get-Help get-c*ditem'; expected = 'Get-ChildItem'; setup = $null } @{ inputStr = 'Trace-Command e'; expected = 'ETS'; setup = $null } @{ inputStr = 'Get-TraceSource e'; expected = 'ETS'; setup = $null } @{ inputStr = '[int]:: max'; expected = 'MaxValue'; setup = $null } @@ -392,7 +2615,7 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = 'Set-ExecutionPolicy -exe:b'; expected = 'Bypass'; setup = $null } @{ inputStr = 'Set-ExecutionPolicy -ExecutionPolicy:'; expected = 'AllSigned'; setup = $null } @{ inputStr = 'Set-ExecutionPolicy by -for:'; expected = '$true'; setup = $null } - @{ inputStr = 'Import-Csv -Encoding '; expected = 'ASCII'; setup = $null } + @{ inputStr = 'Import-Csv -Encoding '; expected = 'ascii'; setup = $null } @{ inputStr = 'Get-Process | % ModuleM'; expected = 'ModuleMemorySize'; setup = $null } @{ inputStr = 'Get-Process | % {$_.MainModule} | % Com'; expected = 'Company'; setup = $null } @{ inputStr = 'Get-Process | % MainModule | % Com'; expected = 'Company'; setup = $null } @@ -400,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 } @@ -408,7 +2632,7 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = "function bar { [OutputType('System.IO.FileInfo')][OutputType('System.Diagnostics.Process')]param() }; bar | ? { `$_.LastAc"; expected = 'LastAccessTime'; setup = $null } @{ inputStr = "& 'get-comm"; expected = "'Get-Command'"; setup = $null } @{ inputStr = 'alias:dir'; expected = Join-Path 'Alias:' 'dir'; setup = $null } - @{ inputStr = 'gc alias::ipm'; expected = 'Alias::ipmo'; setup = $null } + @{ inputStr = 'gc alias::ipm'; expected = 'alias::ipmo'; setup = $null } @{ inputStr = 'gc enVironment::psmod'; expected = 'enVironment::PSModulePath'; setup = $null } ## tab completion safe expression evaluator tests @{ inputStr = '@{a=$(exit)}.Ke'; expected = 'Keys'; setup = $null } @@ -419,10 +2643,11 @@ Describe "TabCompletion" -Tags CI { @{ inputStr = '$PSMod'; expected = '$PSModuleAutoLoadingPreference'; setup = $null } ## tab completion for variable in path ## 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 = Join-Path $PSHOME 'System.Management.Automation.dll'; setup = $null } + @{ 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 = "`"$(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 @{ inputStr = 'get-date | ForEach-Object { $PSItem.h'; expected = 'Hour'; setup = $null } @{ inputStr = '$a=gps;$a[0].h'; expected = 'Handle'; setup = $null } @@ -435,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 } ) } @@ -444,46 +2669,64 @@ Describe "TabCompletion" -Tags CI { if ($null -ne $setup) { . $setup } $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches.CompletionText | Should -Contain $expected } 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 Be $afterTab + $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.Count | Should BeExactly 1 - $res.CompletionMatches[0].CompletionText | Should Be $afterTab + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } 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.Count | Should BeExactly 1 - $res.CompletionMatches[0].CompletionText | Should Be $afterTab + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } It "Tab completion for filesystem provider qualified path" { - if ($IsWindows) { - $beforeTab = 'filesystem::{0}\Wind' -f $env:SystemDrive - $afterTab = 'filesystem::{0}\Windows' -f $env:SystemDrive - } else { - $beforeTab = 'filesystem::/us' -f $env:SystemDrive - $afterTab = 'filesystem::/usr' -f $env:SystemDrive + $tempFolder = [System.IO.Path]::GetTempPath() + try + { + New-Item -ItemType Directory -Path "$tempFolder/helloworld" > $null + $tempFolder | Should -Exist + $beforeTab = 'filesystem::{0}hello' -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 + } + finally + { + Remove-Item -Path "$tempFolder/helloworld" -Force -ErrorAction SilentlyContinue } - $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $afterTab } It "Tab completion dynamic parameter of a custom function" { @@ -510,10 +2753,10 @@ Describe "TabCompletion" -Tags CI { $inputStr = "Test-DynamicParam -D" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 3 - $res.CompletionMatches[0].CompletionText | Should Be '-DeFirst' - $res.CompletionMatches[1].CompletionText | Should Be '-DeSecond' - $res.CompletionMatches[2].CompletionText | Should Be '-DeThird' + $res.CompletionMatches.Count | Should -BeGreaterThan 3 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-DeFirst' + $res.CompletionMatches[1].CompletionText | Should -BeExactly '-DeSecond' + $res.CompletionMatches[2].CompletionText | Should -BeExactly '-DeThird' } It "Tab completion dynamic parameter '-CodeSigningCert'" -Skip:(!$IsWindows) { @@ -521,7 +2764,7 @@ Describe "TabCompletion" -Tags CI { Push-Location cert:\ $inputStr = "gci -co" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches[0].CompletionText | Should Be '-CodeSigningCert' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-CodeSigningCert' } finally { Pop-Location } @@ -535,9 +2778,9 @@ Describe "TabCompletion" -Tags CI { $inputStr = "myf" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 2 - $res.CompletionMatches[0].CompletionText | Should Be (Resolve-Path myf -Relative) - $res.CompletionMatches[1].CompletionText | Should Be "MyFunction" + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly (Resolve-Path myf -Relative) + $res.CompletionMatches[1].CompletionText | Should -BeExactly "MyFunction" } finally { Remove-Item -Path myf -Force Pop-Location @@ -548,40 +2791,151 @@ Describe "TabCompletion" -Tags CI { function foo { param([ValidateSet('cat','dog')]$p) } $inputStr = "foo " $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 2 - $res.CompletionMatches[0].CompletionText | Should be 'cat' - $res.CompletionMatches[1].CompletionText | Should be 'dog' + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'cat' + $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 { + param ( + [String]$TestVal + ) + } + [scriptblock]$completer = { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + @('Val1', 'Val2') + } + Register-ArgumentCompleter -CommandName Test-Completion -ParameterName TestVal -ScriptBlock $completer + } + $pwsh = [PowerShell]::Create() + $pwsh.AddScript($scriptBl) + $pwsh.Invoke() + + $completeInput_Input = $scriptBl.ToString() + $completeInput_Input += "`nTest-Completion -TestVal " + $res = [System.Management.Automation.CommandCompletion]::CompleteInput($completeInput_Input, $completeInput_Input.Length, $null, $pwsh) + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Val1' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Val2' } It "Tab completion for enum type parameter of a custom function" { function baz ([consolecolor]$name, [ValidateSet('cat','dog')]$p){} $inputStr = "baz -name " $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 16 - $res.CompletionMatches[0].CompletionText | Should Be 'Black' + $res.CompletionMatches | Should -HaveCount 16 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Black' $inputStr = "baz Black " $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 2 - $res.CompletionMatches[0].CompletionText | Should be 'cat' - $res.CompletionMatches[1].CompletionText | Should be 'dog' + $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 'Alias' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'All' + } + + It "Tab completion for enum members between colon with space and space with value" -TestCases @( + @{ LeftSpace = 0; RightSpace = 0 } + @{ LeftSpace = 0; RightSpace = 1 } + @{ LeftSpace = 1; RightSpace = 0 } + @{ LeftSpace = 1; RightSpace = 1 } + ) { + param ($LeftSpace, $RightSpace) + $inputStrEndsWithCursor = "Get-Command -Type:$(' ' * $LeftSpace)" + $inputStr = $inputStrEndsWithCursor + "$(' ' * $RightSpace)Alias" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStrEndsWithCursor.Length + $expectedArray = [enum]::GetNames([System.Management.Automation.CommandTypes]) | Sort-Object + $res.CompletionMatches.CompletionText | Should -Be $expectedArray + } + + It "Tab completion for enum members between comma with space and space with parameter" -TestCases @( + @{ LeftSpace = 0; RightSpace = 0 } + @{ LeftSpace = 0; RightSpace = 1 } + @{ LeftSpace = 1; RightSpace = 0 } + @{ LeftSpace = 1; RightSpace = 1 } + ) { + param ($LeftSpace, $RightSpace) + $inputStrEndsWithCursor = "Get-Command -Type Alias,$(' ' * $LeftSpace)" + $inputStr = $inputStrEndsWithCursor + "$(' ' * $RightSpace)-All" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStrEndsWithCursor.Length + $expectedArray = [enum]::GetNames([System.Management.Automation.CommandTypes]) | Sort-Object + $res.CompletionMatches.CompletionText | Should -Be $expectedArray } It "Tab completion for enum members after comma" { $inputStr = "Get-Command -Type Alias,c" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 2 - $res.CompletionMatches[0].CompletionText | Should Be 'Cmdlet' - $res.CompletionMatches[1].CompletionText | Should Be 'Configuration' + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Cmdlet' + $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 - $res.CompletionMatches.Count | Should BeExactly 2 - $res.GetNextResult($false).CompletionText | Should Be 'Configuration' - $res.GetNextResult($true).CompletionText | Should Be 'Cmdlet' - $res.GetNextResult($true).CompletionText | Should Be 'Configuration' + $res.CompletionMatches | Should -HaveCount 2 + $res.GetNextResult($false).CompletionText | Should -BeExactly 'Configuration' + $res.GetNextResult($true).CompletionText | Should -BeExactly 'Cmdlet' + $res.GetNextResult($true).CompletionText | Should -BeExactly 'Configuration' } It "Test history completion" { @@ -595,16 +2949,141 @@ Describe "TabCompletion" -Tags CI { } Add-History -InputObject $history $res = TabExpansion2 -inputScript "#" -cursorColumn 1 - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be "Test history completion" + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $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) - $res.CompletionMatches.Count | Should Be 10 + $res.CompletionMatches | Should -HaveCount 10 $entry = $res.CompletionMatches | Where-Object CompletionText -EQ "Position" - $entry.CompletionText | Should Be "Position" + $entry.CompletionText | Should -BeExactly "Position" + } + + It "Test Attribute member completion multiple members" { + $inputStr = "function bar { [parameter(Position,]param() }" + $res = TabExpansion2 -inputScript $inputStr -cursorColumn ($inputStr.IndexOf(',') + 1) + $res.CompletionMatches | Should -HaveCount 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" { @@ -613,8 +3092,120 @@ dir -Recurse ` -Lite '@ $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be "-LiteralPath" + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "-LiteralPath" + } + + It "Test member completion of a static method invocation" { + $inputStr = '[powershell]::Create().' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 33 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Commands" + } + + It "Test completion with common parameters" { + $inputStr = 'invoke-webrequest -out' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 3 + [string]::Join(',', ($res.CompletionMatches.completiontext | Sort-Object)) | Should -BeExactly "-OutBuffer,-OutFile,-OutVariable" + } + + 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 "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 } } @@ -633,24 +3224,24 @@ 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.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be ".${separator}testModule.psm1" + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly ".${separator}testModule.psm1" } It "Test complete module name" { $inputStr = "using module PSRead" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be "PSReadLine" + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "PSReadLine" } It "Test complete module name with wildcard" { $inputStr = "using module *ReadLi" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be "PSReadLine" + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "PSReadLine" } } @@ -681,30 +3272,45 @@ dir -Recurse ` $inputStr = "dir .\commaA.txt," $expected = ".${separator}commaA.txt" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches | Should -HaveCount 1 + $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 - $res.CompletionMatches.Count | Should Be ([System.Enum]::GetNames([System.Management.Automation.CommandTypes]).Count) - $res.CompletionMatches[0].CompletionText | Should Be "Alias" + $res.CompletionMatches | Should -HaveCount ([System.Enum]::GetNames([System.Management.Automation.CommandTypes]).Count) + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Alias" } It "Test redirection operator ''" -TestCases $redirectionTestCases { param($inputStr, $expected) $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } It "Test complete the minus token to operators" { $inputStr = "55 -" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should Be ([System.Management.Automation.CompletionCompleters]::CompleteOperator("").Count) - $res.CompletionMatches[0].CompletionText | Should Be '-and' + $res.CompletionMatches | Should -HaveCount ([System.Management.Automation.CompletionCompleters]::CompleteOperator("").Count) + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-and' } } @@ -738,15 +3344,15 @@ dir -Recurse ` param($inputStr, $expected) $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } It "Complete file name starting with special char" { $inputStr = ")" $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should Be "& '.${separator})file.txt'" + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "& '.${separator})file.txt'" } } @@ -770,33 +3376,8 @@ dir -Recurse ` ) $res = TabExpansion2 -ast $ast -tokens $tokens -positionOfCursor $elementAst.Extent.EndScriptPosition - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected - } - } - - 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.Count | Should BeExactly 1 - $res.CompletionMatches[0].CompletionText | Should Be '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.Count | Should BeExactly 1 - $res.CompletionMatches[0].CompletionText | Should Be "Overridden-TabExpansion-Alias" + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } } @@ -813,21 +3394,28 @@ dir -Recurse ` param($inputStr) $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeExactly 0 + $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" { + Context "Tab completion error tests" { BeforeAll { $ast = {}.Ast; $tokens = [System.Management.Automation.Language.Token[]]@() $testCases = @( - @{ inputStr = {[System.Management.Automation.CommandCompletion]::MapStringInputToParsedInput('$pid.', 7)}; expected = "PSArgumentException" } + @{ inputStr = {[System.Management.Automation.CommandCompletion]::MapStringInputToParsedInput('$PID.', 7)}; expected = "PSArgumentException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($null, $null, $null, $null)}; expected = "PSArgumentNullException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($ast, $null, $null, $null)}; expected = "PSArgumentNullException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($ast, $tokens, $null, $null)}; expected = "PSArgumentNullException" } - @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput('$pid.', 7, $null, $null)}; expected = "PSArgumentException" } - @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput('$pid.', 5, $null, $null)}; expected = "PSArgumentNullException" } + @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput('$PID.', 7, $null, $null)}; expected = "PSArgumentException" } + @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput('$PID.', 5, $null, $null)}; expected = "PSArgumentNullException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($null, $null, $null, $null, $null)}; expected = "PSArgumentNullException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($ast, $null, $null, $null, $null)}; expected = "PSArgumentNullException" } @{ inputStr = {[System.Management.Automation.CommandCompletion]::CompleteInput($ast, $tokens, $null, $null, $null)}; expected = "PSArgumentNullException" } @@ -837,7 +3425,15 @@ dir -Recurse ` It "Input '' should throw in tab completion" -TestCases $testCases { param($inputStr, $expected) - $inputStr | ShouldBeErrorId $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 } } @@ -876,9 +3472,14 @@ 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 Be $expected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } } @@ -887,7 +3488,7 @@ dir -Recurse ` $testCases = @( @{ inputStr = "Invoke-CimMethod -ClassName Win32_Process -MethodName Crea"; expected = "Create" } @{ inputStr = "Get-CimInstance -ClassName Win32_Process | Invoke-CimMethod -MethodName AttachDeb"; expected = "AttachDebugger" } - @{ inputStr = 'Get-CimInstance Win32_Process | ?{ $_.ProcessId -eq $Pid } | Get-CimAssociatedInstance -ResultClassName Win32_Co*uterSyst'; expected = "Win32_ComputerSystem" } + @{ inputStr = 'Get-CimInstance Win32_Process | ?{ $_.ProcessId -eq $PID } | Get-CimAssociatedInstance -ResultClassName Win32_Co*uterSyst'; expected = "Win32_ComputerSystem" } @{ inputStr = "Get-CimInstance -ClassName Win32_Environm"; expected = "Win32_Environment" } @{ inputStr = "New-CimInstance -ClassName Win32_Environm"; expected = "Win32_Environment" } @{ inputStr = 'New-CimInstance -ClassName Win32_Process | %{ $_.Captio'; expected = "Caption" } @@ -897,7 +3498,7 @@ dir -Recurse ` @{ inputStr = 'Invoke-CimMethod -Namespace root/StandardCimv2 -ClassName MSFT_NetIPAddress -MethodName Crea'; expected = 'Create' } @{ inputStr = '$win32_process = Get-CimInstance -ClassName Win32_Process; $win32_process | Invoke-CimMethod -MethodName AttachDe'; expected = 'AttachDebugger' } @{ inputStr = '$win32_process = Get-CimInstance -ClassName Win32_Process; Invoke-CimMethod -InputObject $win32_process -MethodName AttachDe'; expected = 'AttachDebugger' } - @{ inputStr = 'Get-CimInstance Win32_Process | ?{ $_.ProcessId -eq $Pid } | Get-CimAssociatedInstance -ResultClassName Win32_ComputerS'; expected = 'Win32_ComputerSystem' } + @{ inputStr = 'Get-CimInstance Win32_Process | ?{ $_.ProcessId -eq $PID } | Get-CimAssociatedInstance -ResultClassName Win32_ComputerS'; expected = 'Win32_ComputerSystem' } @{ inputStr = 'Get-CimInstance -Namespace root/Interop -ClassName Win32_PowerSupplyP'; expected = 'Win32_PowerSupplyProfile' } @{ inputStr = 'Get-CimInstance __NAMESP'; expected = '__NAMESPACE' } @{ inputStr = 'Get-CimInstance -Namespace root/Inter'; expected = 'root/Interop' } @@ -910,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 " } ) } @@ -917,47 +3530,373 @@ dir -Recurse ` param($inputStr, $expected) $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $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.Count | Should Be $expected.Count - $completionOptions = "" - foreach ($completion in $res.CompletionMatches) { - $completionOptions += $completion.ListItemText + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly ($expected -join ' ') + } + } + + Context "Tab completion help test" { + BeforeAll { + 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) + $userHelpRoot = Join-Path $userModulesRoot -ChildPath ".." -AdditionalChildPath "Help" + } + } + + It 'Should complete about help topic' { + $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. + $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) + { + } +} +'@ } - $completionOptions | Should Be ([string]::Join("", $expected)) + @{ + 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 + } + } -Describe "Tab completion help test" -Tags @('RequireAdminOnWindows', 'CI') { - It 'Should complete about help topic' { + 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 + } - $aboutHelpPath = Join-Path $PSHOME (Get-Culture).Name + 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 + } +} - ## If help content does not exist, tab completion will not work. So update it first. - if (-not (Test-Path (Join-Path $aboutHelpPath "about_Splatting.help.txt"))) +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) { - Update-Help -Force -ErrorAction SilentlyContinue + Remove-SmbShare -InputObject $Share -Force -Confirm:$false } - - $res = TabExpansion2 -inputScript 'get-help about_spla' -cursorColumn 'get-help about_spla'.Length - $res.CompletionMatches.Count | Should Be 1 - $res.CompletionMatches[0].CompletionText | Should BeExactly 'about_Splatting' } } -Describe "Tab completion tests with remote Runspace" -Tags Feature { +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 @@ -975,11 +3914,16 @@ Describe "Tab completion tests with remote Runspace" -Tags Feature { ) } 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 { @@ -990,8 +3934,8 @@ Describe "Tab completion tests with remote Runspace" -Tags Feature { It "Input '' should successfully complete in remote runspace" -TestCases $testCases { param($inputStr, $expected) $res = [System.Management.Automation.CommandCompletion]::CompleteInput($inputStr, $inputStr.Length, $null, $powershell) - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } It "Input '' should successfully complete via AST in remote runspace" -TestCases $testCasesWithAst { @@ -1005,8 +3949,8 @@ Describe "Tab completion tests with remote Runspace" -Tags Feature { ) $res = [System.Management.Automation.CommandCompletion]::CompleteInput($ast, $tokens, $elementAst.Extent.EndScriptPosition, $null, $powershell) - $res.CompletionMatches.Count | Should BeGreaterThan 0 - $res.CompletionMatches[0].CompletionText | Should Be $expected + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly $expected } } @@ -1025,14 +3969,12 @@ Describe "WSMan Config Provider tab complete tests" -Tags Feature,RequireAdminOn $path = "wsman:\localhost\listener\listener" $res = TabExpansion2 -inputScript $path -cursorColumn $path.Length $listener = Get-ChildItem WSMan:\localhost\Listener - $res.CompletionMatches.Count | Should Be $listener.Count - for ($i = 0; $i -lt $res.CompletionMatches.Count; $i++) { - $res.CompletionMatches[$i].ListItemText | Should Be $listener[$i].Name - } + $res.CompletionMatches.Count | Should -Be $listener.Count + $res.CompletionMatches.ListItemText | Should -BeIn $listener.Name } It "Tab completion gets dynamic parameters for '' using ''" -TestCases @( - @{path = ""; parameter = "-co"; expected = "ConnectionURI"}, + @{path = ""; parameter = "-conn"; expected = "ConnectionURI"}, @{path = ""; parameter = "-op"; expected = "OptionSet"}, @{path = ""; parameter = "-au"; expected = "Authentication"}, @{path = ""; parameter = "-ce"; expected = "CertificateThumbprint"}, @@ -1049,19 +3991,19 @@ 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"} ) { param($path, $parameter, $expected) $script = "new-item wsman:\$path $parameter" - $res = TabExpansion2 -inputScript $script -cursorColumn $script.Length - $res.CompletionMatches.Count | Should Be $expected.Count + $res = TabExpansion2 -inputScript $script + $res.CompletionMatches | Should -HaveCount $expected.Count $completionOptions = "" foreach ($completion in $res.CompletionMatches) { $completionOptions += $completion.ListItemText } - $completionOptions | Should Be ([string]::Join("", $expected)) + $completionOptions | Should -BeExactly ([string]::Join("", $expected)) } It "Tab completion get dynamic parameters for initialization parameters" -Pending -TestCases @( @@ -1070,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 bf1eba54d04..00000000000 --- a/test/powershell/Installer/WindowsInstaller.Tests.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -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 Be $true - } - - ## 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/MSFT_778492.psm1 b/test/powershell/Language/Classes/MSFT_778492.psm1 index f54aefda072..e17710f0e8e 100644 --- a/test/powershell/Language/Classes/MSFT_778492.psm1 +++ b/test/powershell/Language/Classes/MSFT_778492.psm1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. $foo = 'MSFT_778492 script scope' diff --git a/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 index d10e5a2ed44..d9dc808b0d7 100644 --- a/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 +++ b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Add-Type -WarningAction Ignore @' public class Base @@ -139,50 +141,50 @@ class Derived2 : Base {} '@ Describe "Protected Member Access - w/ default ctor" -Tags "CI" { - It "Method Access" { $derived1.TestMethodAccess() | Should Be 42 } - It "Dynamic Method Access" { $derived1.TestDynamicMethodAccess() | Should Be 42 } - It "Field Access" { $derived1.TestFieldAccess() | Should Be 11 } - It "Dynamic Field Access" { $derived1.TestDynamicFieldAccess() | Should Be 12 } - It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should Be 1111 } - It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should Be 2111 } - It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should Be 3111 } - It "Dynamic Property Access" { $derived1.TestDynamicPropertyAccess() | Should Be 1112 } - - It "Method Access - overloaded 1a" { $derived1.TestOverloadedMethodAccess1a() | Should Be 84 } - It "Method Access - overloaded 1b" { $derived1.TestOverloadedMethodAccess1b() | Should Be 11 } - It "Method Access - overloaded 2a" { $derived1.TestOverloadedMethodAccess2a() | Should Be 84 } - It "Method Access - overloaded 2b" { $derived1.TestOverloadedMethodAccess2b() | Should Be 11 } - It "Method Access - overloaded 3a" { $derived1.TestOverloadedMethodAccess3a() | Should Be 84 } - It "Method Access - overloaded 3b" { $derived1.TestOverloadedMethodAccess3b() | Should Be 11 } - It "Implicit ctor calls protected ctor" { $derived3.OverloadedMethod2(42) | Should Be 84 } + It "Method Access" { $derived1.TestMethodAccess() | Should -Be 42 } + It "Dynamic Method Access" { $derived1.TestDynamicMethodAccess() | Should -Be 42 } + It "Field Access" { $derived1.TestFieldAccess() | Should -Be 11 } + It "Dynamic Field Access" { $derived1.TestDynamicFieldAccess() | Should -Be 12 } + It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should -Be 1111 } + It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should -Be 2111 } + It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should -Be 3111 } + It "Dynamic Property Access" { $derived1.TestDynamicPropertyAccess() | Should -Be 1112 } + + It "Method Access - overloaded 1a" { $derived1.TestOverloadedMethodAccess1a() | Should -Be 84 } + It "Method Access - overloaded 1b" { $derived1.TestOverloadedMethodAccess1b() | Should -Be 11 } + It "Method Access - overloaded 2a" { $derived1.TestOverloadedMethodAccess2a() | Should -Be 84 } + It "Method Access - overloaded 2b" { $derived1.TestOverloadedMethodAccess2b() | Should -Be 11 } + It "Method Access - overloaded 3a" { $derived1.TestOverloadedMethodAccess3a() | Should -Be 84 } + It "Method Access - overloaded 3b" { $derived1.TestOverloadedMethodAccess3b() | Should -Be 11 } + It "Implicit ctor calls protected ctor" { $derived3.OverloadedMethod2(42) | Should -Be 84 } } Describe "Protected Member Access - w/ non-default ctor" -Tags "CI" { - It "Method Access" { $derived2.TestMethodAccess() | Should Be 52 } - It "Dynamic Method Access" { $derived2.TestDynamicMethodAccess() | Should Be 52 } - It "Field Access" { $derived2.TestFieldAccess() | Should Be 11 } - It "Dynamic Field Access" { $derived2.TestDynamicFieldAccess() | Should Be 12 } - It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should Be 1111 } - It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should Be 2111 } - It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should Be 3111 } - It "Dynamic Property Access" { $derived2.TestDynamicPropertyAccess() | Should Be 1112 } - - It "Method Access - overloaded 1a" { $derived2.TestOverloadedMethodAccess1a() | Should Be 94 } - It "Method Access - overloaded 1b" { $derived2.TestOverloadedMethodAccess1b() | Should Be 21 } - It "Method Access - overloaded 2a" { $derived2.TestOverloadedMethodAccess2a() | Should Be 94 } - It "Method Access - overloaded 2b" { $derived2.TestOverloadedMethodAccess2b() | Should Be 21 } - It "Method Access - overloaded 3a" { $derived2.TestOverloadedMethodAccess3a() | Should Be 94 } - It "Method Access - overloaded 3b" { $derived2.TestOverloadedMethodAccess3b() | Should Be 21 } + It "Method Access" { $derived2.TestMethodAccess() | Should -Be 52 } + It "Dynamic Method Access" { $derived2.TestDynamicMethodAccess() | Should -Be 52 } + It "Field Access" { $derived2.TestFieldAccess() | Should -Be 11 } + It "Dynamic Field Access" { $derived2.TestDynamicFieldAccess() | Should -Be 12 } + It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should -Be 1111 } + It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should -Be 2111 } + It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should -Be 3111 } + It "Dynamic Property Access" { $derived2.TestDynamicPropertyAccess() | Should -Be 1112 } + + It "Method Access - overloaded 1a" { $derived2.TestOverloadedMethodAccess1a() | Should -Be 94 } + It "Method Access - overloaded 1b" { $derived2.TestOverloadedMethodAccess1b() | Should -Be 21 } + It "Method Access - overloaded 2a" { $derived2.TestOverloadedMethodAccess2a() | Should -Be 94 } + It "Method Access - overloaded 2b" { $derived2.TestOverloadedMethodAccess2b() | Should -Be 21 } + It "Method Access - overloaded 3a" { $derived2.TestOverloadedMethodAccess3a() | Should -Be 94 } + It "Method Access - overloaded 3b" { $derived2.TestOverloadedMethodAccess3b() | Should -Be 21 } } Describe "Protected Member Access - members not visible outside class" -Tags "CI" { Set-StrictMode -v 3 - It "Invalid protected field Get Access" { { $derived1.Field } | Should Throw } - It "Invalid protected property Get Access" { { $derived1.Property } | Should Throw } - It "Invalid protected field Set Access" { { $derived1.Field = 1 } | Should Throw } - It "Invalid protected property Set Access" { { $derived1.Property = 1 } | Should Throw } + It "Invalid protected field Get Access" { { $derived1.Field } | Should -Throw -ErrorId "PropertyNotFoundStrict" } + It "Invalid protected property Get Access" { { $derived1.Property } | Should -Throw -ErrorId "PropertyNotFoundStrict" } + It "Invalid protected field Set Access" { { $derived1.Field = 1 } | Should -Throw -ErrorId "PropertyAssignmentException"} + It "Invalid protected property Set Access" { { $derived1.Property = 1 } | Should -Throw -ErrorId "PropertyAssignmentException" } - It "Invalid protected constructor Access" { { [Base]::new() } | Should Throw } - It "Invalid protected method Access" { { $derived1.Method() } | Should Throw } + It "Invalid protected constructor Access" { { [Base]::new() } | Should -Throw -ErrorId "MethodCountCouldNotFindBest" } + It "Invalid protected method Access" { { $derived1.Method() } | Should -Throw -ErrorId "MethodNotFound" } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 index 4a6cb7053fb..3df42931855 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Attributes Test' -Tags "CI" { BeforeAll { @@ -48,23 +50,13 @@ namespace Dummy Add-Type -TypeDefinition $dummyAttributesSource } - - Context 'Property.Instance.ValidateSet.String' { class C1 { [ValidateSet("Present", "Absent")][string]$Ensure } # This call should not throw exception [C1]::new().Ensure = "Present" It 'Error when ValidateSet should be ExceptionWhenSetting' { - try - { - [C1]::new().Ensure = "foo" - throw "Exception expected" - } - catch - { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { [C1]::new().Ensure = "foo" } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -73,13 +65,7 @@ namespace Dummy # This call should not throw exception [C1]::Ensure = "Present" It 'Error when ValidateSet should be ExceptionWhenSetting'{ - try { - [C1]::Ensure = "foo" - throw "Exception expected" - } - catch { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { [C1]::Ensure = "foo" } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -89,13 +75,7 @@ namespace Dummy [C1]::new().f = 10 [C1]::new().f = 1 It 'Error when ValidateSet should be ExceptionWhenSetting'{ - try { - [C1]::new().f = 20 - throw "Exception expected" - } - catch { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { [C1]::new().f = 20 } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -104,13 +84,7 @@ namespace Dummy # This call should not throw exception [C1]::f = 5 It 'Error when ValidateSet should be ExceptionWhenSetting'{ - try { - [C1]::f = 20 - throw "Exception expected" - } - catch { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { [C1]::f = 20 } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -120,13 +94,7 @@ namespace Dummy [C1]::o = "abc" [C1]::o = 5 It 'Error when ValidateSet should be ExceptionWhenSetting'{ - try { - [C1]::o = 1 - throw "Exception expected" - } - catch { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { [C1]::o = 1 } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -142,11 +110,11 @@ namespace Dummy It 'Implicitly Transform to 100' { $c.arg = 100 - $c.arg | should be 100 + $c.arg | Should -Be 100 } It 'Implicitly Transform to foo' { $c.arg = "foo" - $c.arg | should be "foofoo" + $c.arg | Should -BeExactly "foofoo" } } @@ -154,7 +122,7 @@ namespace Dummy $c = [scriptblock]::Create('class C1 { [Dummy.DoubleStringTransformation()][string]$arg }; [C1]::new()').Invoke()[0] It 'set to foo' { $c.arg = "foo" - $c.arg | should be "foofoo" + $c.arg | Should -BeExactly "foofoo" } } @@ -162,16 +130,10 @@ namespace Dummy $c = [scriptblock]::Create('class C1 { [Dummy.DoubleInt()][int]$arg }; [C1]::new()').Invoke()[0] It 'arg should be 200' { $c.arg = 100 - $c.arg | should be 200 + $c.arg | Should -Be 200 } It 'Set to string should fail with ExceptionWhenSetting' { - try { - $c.arg = "abc" - throw "Exception expected" - } - catch { - $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' - } + { $c.arg = "abc" } | Should -Throw -ErrorId 'ExceptionWhenSetting' } } @@ -179,7 +141,7 @@ namespace Dummy $c = [scriptblock]::Create('class C1 { [Nullable[int]][Dummy.DoubleStringTransformation()]$arg }; [C1]::new()').Invoke()[0] It 'arg should be 100' { $c.arg = 100 - $c.arg | should be 100 + $c.arg | Should -Be 100 } } @@ -187,12 +149,12 @@ namespace Dummy $c = [scriptblock]::Create('class C1 { [Dummy.DoubleStringTransformation()][Dummy.AppendStringTransformation()]$arg }; [C1]::new()').Invoke()[0] It 'arg should be 100' { $c.arg = 100 - $c.arg | should be 100 + $c.arg | Should -Be 100 } It 'arg should be foo___foo___g' { $c.arg = "foo" - $c.arg | should be "foo___foo___" + $c.arg | Should -BeExactly "foo___foo___" } } } @@ -211,7 +173,7 @@ Describe 'Type resolution with attributes' -Tag "CI" { [void] OnEvent([string]$Message) {} } - [MyEventSource]::new() | Should Not Be $null + [MyEventSource]::new() | Should -Not -BeNullOrEmpty } } @@ -268,7 +230,6 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } } - /// Implement of test IValidateSetValuesGenerator public class GenValuesForParamNull : IValidateSetValuesGenerator { @@ -290,7 +251,7 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } '@ - $cls = Add-Type -TypeDefinition $a -PassThru | select -First 1 + $cls = Add-Type -TypeDefinition $a -PassThru | Select-Object -First 1 $testModule = Import-Module $cls.Assembly -PassThru } @@ -299,18 +260,18 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } It 'Throw if IValidateSetValuesGenerator is not implemented' { - { Get-TestValidateSet0 -Param1 "TestString" -ErrorAction Stop } | ShouldBeErrorId "Argument" + { Get-TestValidateSet0 -Param1 "TestString" -ErrorAction Stop } | Should -Throw -ErrorId "Argument" } It 'Dynamically generated set works in C# with default (immediate) cache expire' { - Get-TestValidateSet4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1" + Get-TestValidateSet4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should -BeExactly "TestString1" } It 'Empty dynamically generated set throws in C#' { $exc = { Get-TestValidateSet5 -Param1 "TestString1" -ErrorAction Stop - } | ShouldBeErrorId "ParameterArgumentValidationError,Test.Language.TestValidateSetCommand5" - $exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should BeExactly "ValidateSetGeneratedValidValuesListIsNull" + } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test.Language.TestValidateSetCommand5" -PassThru + $exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "ValidateSetGeneratedValidValuesListIsNull" } } @@ -391,24 +352,24 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } It 'Dynamically generated set works in PowerShell script with default (immediate) cache expire' { - Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1" + Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should -BeExactly "TestString1" } It 'Get the appropriate error message' { - {Get-TestValidateSetPS4 -Param1 "TestStringWrong" -ErrorAction Stop} | ShouldBeErrorId "ParameterArgumentValidationError,Get-TestValidateSetPS4" + {Get-TestValidateSetPS4 -Param1 "TestStringWrong" -ErrorAction Stop} | Should -Throw -ErrorId "ParameterArgumentValidationError,Get-TestValidateSetPS4" } It 'Empty dynamically generated set throws in PowerShell script' { $exc = { Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction Stop - } | ShouldBeErrorId "ParameterArgumentValidationError,Get-TestValidateSetPS5" - $exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should BeExactly "ValidateSetGeneratedValidValuesListIsNull" + } | Should -Throw -ErrorId "ParameterArgumentValidationError,Get-TestValidateSetPS5" -PassThru + $exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "ValidateSetGeneratedValidValuesListIsNull" } It 'Unimplemented valid values generator type throws in PowerShell script' { { Get-TestValidateSetPS6 -Param1 "AnyTestString" -ErrorAction Stop - } | ShouldBeErrorId "TypeNotFound" + } | Should -Throw -ErrorId "TypeNotFound" } It 'IValidateSetValuesGenerator works in PowerShell module' { @@ -438,9 +399,9 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { try { Import-Module -Name $moduleFile -Force - Test-ValidateSet 'Hello' | Should Be 'Hello' + Test-ValidateSet 'Hello' | Should -BeExactly 'Hello' } finally { - Remove-Module -Name $moduleFile -Force + Remove-Module -Name $moduleFile -Force -ErrorAction SilentlyContinue } } } @@ -477,7 +438,6 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } } - function Get-TestValidateSetPS4 { [CmdletBinding()] @@ -504,14 +464,14 @@ Describe 'ValidateSet support a dynamically generated set' -Tag "CI" { } It 'Can implement CachedValidValuesGeneratorBase in PowerShell' { - Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1" + Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should -BeExactly "TestString1" } It 'Can implement CachedValidValuesGeneratorBase with cache expiration in PowerShell' { - Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1" - Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1" - Start-Sleep 3 - Get-TestValidateSetPS5 -Param1 "TestString2" -ErrorAction SilentlyContinue | Should BeExactly "TestString2" + Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should -BeExactly "TestString1" + Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should -BeExactly "TestString1" + Start-Sleep -Seconds 3 + Get-TestValidateSetPS5 -Param1 "TestString2" -ErrorAction SilentlyContinue | Should -BeExactly "TestString2" } } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index b9655a8bccc..6674697ca2f 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -1,6 +1,5 @@ -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Positive Parse Properties Tests' -Tags "CI" { It 'PositiveParsePropertiesTest' { @@ -106,17 +105,27 @@ Describe 'Positive Parse Properties Tests' -Tags "CI" { class C12c { [void] f() { [System.Management.Automation.Host.Rectangle]$foo = [System.Management.Automation.Host.Rectangle]::new(0, 0, 0, 0) } } } - context "Positive ParseMethods return type Test" { + Context "Positive ParseMethods return type Test" { # Method with return type of self class C9 { [C9] f() { return [C9]::new() } } $c9 = [C9]::new().f() - It "Expected a C9 returned" { $c9.GetType().Name | should be C9 } + It "Expected a C9 returned" { $c9.GetType().Name | Should -Be C9 } class C9a { [C9a[]] f() { return [C9a]::new() } } $c9a = [C9a]::new().f() - It "Expected a C9a[] returned" { $c9a.GetType().Name | should be C9a[] } + It "Expected a C9a[] returned" { $c9a.GetType().Name | Should -Be C9a[] } class C9b { [System.Collections.Generic.List[C9b]] f() { return [C9b]::new() } } $c9b = [C9b]::new().f() - It "Expected a System.Collections.Generic.List[C9b] returned" { $c9b -is [System.Collections.Generic.List[C9b]] | should be $true } + It "Expected a System.Collections.Generic.List[C9b] returned" { $c9b -is [System.Collections.Generic.List[C9b]] | Should -BeTrue } + It 'Methods returning object should return $null if no output was produced' { + class Foo { + [object] Bar1() { return & {} } + static [object] Bar2() { return & {} } + } + # Test instance method + [Foo]::new().Bar1() | Should -BeNullOrEmpty + # Test static method + [foo]::Bar2() | Should -BeNullOrEmpty + } } It 'Positive ParseProperty Attributes Test' { @@ -217,10 +226,10 @@ Describe 'Positive Parse Properties Tests' -Tags "CI" { { $LASTEXITCODE $lastexitcode - '111' -match '1' + '111' -Match '1' $Matches $mAtches - $Error[0] + $error[0] $error $pwd foreach ($i in 1..10) {$foreach} @@ -243,7 +252,7 @@ Describe 'Positive Parse Properties Tests' -Tags "CI" { $this.h = [ordered] @{} } } - [A]::new().h.GetType().Name | Should Be 'OrderedDictionary' + [A]::new().h.GetType().Name | Should -BeExactly 'OrderedDictionary' } } @@ -258,7 +267,6 @@ Describe 'Negative Parsing Tests' -Tags "CI" { ShouldBeParseError 'class foo {} class foo {}' MemberAlreadyDefined 13 ShouldBeParseError 'class foo { $x; $x; }' MemberAlreadyDefined 16 -SkipAndCheckRuntimeError ShouldBeParseError 'class foo { [int][string]$x; }' TooManyTypes 17 - ShouldBeParseError 'class foo { [int][string]$x; }' TooManyTypes 17 ShouldBeParseError 'class foo { static static $x; }' DuplicateQualifier 19 ShouldBeParseError 'class foo { [zz]$x; }' TypeNotFound 13 ShouldBeParseError 'class foo { [zz]f() { return 0 } }' TypeNotFound 13 @@ -287,19 +295,14 @@ Describe 'Negative Parsing Tests' -Tags "CI" { ShouldBeParseError 'class C { $x; static bar() { $this.x = 1 } }' NonStaticMemberAccessInStaticMember 29 ShouldBeParseError 'class C { $x; static $y = $this.x }' NonStaticMemberAccessInStaticMember 26 - ShouldBeParseError 'class C { $x; static bar() { $this.x = 1 } }' NonStaticMemberAccessInStaticMember 29 - ShouldBeParseError 'class C { $x; static $y = $this.x }' NonStaticMemberAccessInStaticMember 26 - ShouldBeParseError 'class C { $x; static bar() { $This.x = 1 } }' NonStaticMemberAccessInStaticMember 29 - ShouldBeParseError 'class C { $x; static $y = $This.x }' NonStaticMemberAccessInStaticMember 26 - ShouldBeParseError 'class C { [void]foo() { try { throw "foo"} finally { return } } }' ControlLeavingFinally 53 ShouldBeParseError 'class C { [int]foo() { return; return 1 } }' NonVoidMethodMissingReturnValue 23 ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch { } } }' MethodHasCodePathNotReturn 15 ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch [ArgumentException] {} catch {throw $_} } }' MethodHasCodePathNotReturn 15 ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch [ArgumentException] {return 1} catch {} } }' MethodHasCodePathNotReturn 15 ShouldBeParseError 'class C { [int]foo() { while ($false) { return 1 } } }' MethodHasCodePathNotReturn 15 - ShouldBeParseError 'class C { [int]foo() { try { mkdir foo } finally { rm -rec foo } } }' MethodHasCodePathNotReturn 15 - ShouldBeParseError 'class C { [int]foo() { try { mkdir foo; return 1 } catch { } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { New-Item -ItemType Directory foo } finally { rm -rec foo } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { New-Item -ItemType Directory foo; return 1 } catch { } } }' MethodHasCodePathNotReturn 15 ShouldBeParseError 'class C { [bool] Test() { if ($false) { return $true; } } }' MethodHasCodePathNotReturn 17 ShouldBeParseError 'class C { [int]$i; [void] foo() {$i = 10} }' MissingThis 33 @@ -372,31 +375,31 @@ Describe 'Negative ClassAttributes Tests' -Tags "CI" { [System.Management.Automation.Cmdlet("Get", "Thing")]class C{} $t = [C].GetCustomAttributes($false) - It "Should have one attribute" {$t.Count | should be 1} - It "Should have instance of CmdletAttribute" {$t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + It "Should have one attribute (class C)" {$t.Count | Should -Be 1} + It "Should have instance of CmdletAttribute (class C)" {$t[0] | Should -BeOfType System.Management.Automation.CmdletAttribute } [System.Management.Automation.CmdletAttribute]$c = $t[0] - It "Verb should be Get" {$c.VerbName | should be 'Get'} - It "Noun should be Thing" {$c.NounName | should be 'Thing'} + It "Verb should be Get (class C)" {$c.VerbName | Should -BeExactly 'Get'} + It "Noun should be Thing (class C)" {$c.NounName | Should -BeExactly 'Thing'} [System.Management.Automation.Cmdlet("Get", "Thing", SupportsShouldProcess = $true, SupportsPaging = $true)]class C2{} $t = [C2].GetCustomAttributes($false) - It "Should have one attribute" { $t.Count | should be 1 } - It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + It "Should have one attribute (class C2)" { $t.Count | Should -Be 1 } + It "Should have instance of CmdletAttribute (class C2)" { $t[0] | Should -BeOfType System.Management.Automation.CmdletAttribute } [System.Management.Automation.CmdletAttribute]$c = $t[0] - It "Verb should be Get" {$c.VerbName | should be 'Get'} - It "Noun should be Thing" {$c.NounName | should be 'Thing'} + It "Verb should be Get (class C2)" {$c.VerbName | Should -BeExactly 'Get'} + It "Noun should be Thing (class C2)" {$c.NounName | Should -BeExactly 'Thing'} - It "SupportsShouldProcess should be $true" { $c.ConfirmImpact | should be $true } - It "SupportsPaging should be `$true" { $c.SupportsPaging | should be $true } + It "SupportsShouldProcess should be $true" { $c.SupportsShouldProcess | Should -BeTrue } + It "SupportsPaging should be `$true" { $c.SupportsPaging | Should -BeTrue } Context "Support ConfirmImpact as an attribute" { - It "ConfirmImpact should be high" -pending { - [System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact = 'High', SupportsPaging = $true)]class C3{} + It "ConfirmImpact should be high" { + [System.Management.Automation.Cmdlet("Get", "Thing", SupportsShouldProcess = $true, ConfirmImpact = 'High', SupportsPaging = $true)]class C3{} $t = [C3].GetCustomAttributes($false) - It "Should have one attribute" { $t.Count | should be 1 } - It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + $t.Count | Should -Be 1 + $t[0] | Should -BeOfType System.Management.Automation.CmdletAttribute [System.Management.Automation.CmdletAttribute]$c = $t[0] - $c.ConfirmImpact | should be 'High' + $c.ConfirmImpact | Should -BeExactly 'High' } } @@ -406,19 +409,132 @@ Describe 'Property Attributes Test' -Tags "CI" { class C { [ValidateSet('a', 'b')]$p; } $t = [C].GetProperty('p').GetCustomAttributes($false) - It "Should have one attribute" { $t.Count | should be 1 } + It "Should have one attribute" { $t.Count | Should -Be 1 } [ValidateSet]$v = $t[0] - It "Should have 2 valid values" { $v.ValidValues.Count | should be 2 } - It "first value should be a" { $v.ValidValues[0] | should be 'a' } - It "second value should be b" { $v.ValidValues[1] | should be 'b' } + It "Should have 2 valid values" { $v.ValidValues.Count | Should -Be 2 } + It "first value should be a" { $v.ValidValues[0] | Should -Be 'a' } + It "second value should be b" { $v.ValidValues[1] | Should -Be 'b' } +} + +Describe 'Testing Method Names can be Keywords' -Tags "CI" { + BeforeAll { + [powershell] $script:PowerShell = [powershell]::Create() + + function Invoke-PowerShell { + [CmdletBinding()] + param([string] $Script) + + try { + $script:PowerShell.AddScript($Script).Invoke() + } + finally { + $script:PowerShell.Commands.Clear() + } + } + + function Reset-PowerShell { + if ($script:PowerShell) { + $script:PowerShell.Dispose() + } + + $script:PowerShell = [powershell]::Create() + } + } + + AfterEach { + Reset-PowerShell + } + + It 'Permits class methods to be named after keywords' { + $TestScript = @' + class TestMethodNames : IDisposable { + [string] Begin() { return "Begin" } + + [string] Process() { return "Process" } + + [string] End() { return "End" } + + hidden $Data = "Secrets" + + Dispose() { + $this.Data = [string]::Empty + } + } + $Object = [TestMethodNames]::new() + [PSCustomObject]@{ + BeginTest = $Object.Begin() + ProcessTest = $Object.Process() + EndTest = $Object.End() + CheckData1 = $Object.Data + CheckData2 = $( $Object.Dispose(); $Object.Data ) + } +'@ + $Results = Invoke-PowerShell -Script $TestScript + + $Results.BeginTest | Should -BeExactly 'Begin' + $Results.ProcessTest | Should -BeExactly 'Process' + $Results.EndTest | Should -BeExactly 'End' + $Results.CheckData1 | Should -BeExactly 'Secrets' + $Results.CheckData2 | Should -BeNullOrEmpty + } + + It 'Permits class methods to be named after DynamicKeywords' { + $DefineKeyword = @' + function GetData { + param( + $KeywordData, + $Name, + $Value, + $SourceMetadata + ) + end { + return $PSBoundParameters + } + } + + $keyword = [System.Management.Automation.Language.DynamicKeyword]::new() + $keyword.NameMode = [System.Management.Automation.Language.DynamicKeywordNameMode]::SimpleNameRequired + $keyword.Keyword = 'GetData' + $keyword.BodyMode = [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable + + $property = [System.Management.Automation.Language.DynamicKeywordProperty]::new() + $property.Name = 'Hey' + $property.TypeConstraint = 'int' + $keyword.Properties.Add('Hey', $property) + + [System.Management.Automation.Language.DynamicKeyword]::AddKeyword($keyword) +'@ + $DefineClass = @' + class Test { + hidden $Data = 'TOP SECRET' + + [string] GetData() { + return $this.Data + } + } +'@ + $TestScript = @' + $Object = [Test]::new() + $Object.GetData() +'@ + Invoke-PowerShell -Script $DefineKeyword + Invoke-PowerShell -Script $DefineClass + + Invoke-PowerShell -Script $TestScript | Should -BeExactly 'TOP SECRET' + } + + AfterAll { + # Ensure we don't leave any dynamic keywords in the session. + [System.Management.Automation.Language.DynamicKeyword]::Reset() + } } Describe 'Method Attributes Test' -Tags "CI" { class C { [Obsolete("aaa")][int]f() { return 1 } } $t = [C].GetMethod('f').GetCustomAttributes($false) - It "Should have one attribute" {$t.Count | should be 1 } - It "Attribute type should be ObsoleteAttribute" { $t[0].GetType().FullName | should be System.ObsoleteAttribute } + It "Should have one attribute" {$t.Count | Should -Be 1 } + It "Attribute type should be ObsoleteAttribute" { $t[0].GetType().FullName | Should -Be System.ObsoleteAttribute } } Describe 'Positive SelfClass Type As Parameter Test' -Tags "CI" { @@ -434,22 +550,22 @@ Describe 'Positive SelfClass Type As Parameter Test' -Tags "CI" { Print() { Write-Host "[`$x=$($this.x) `$y=$($this.y)]" } Set($x, $y) { $this.x = $x; $this.y = $y } } - It "[Point]::Add works" { + It "[Point]::Add works construction via ::new" { $point = [Point]::new(100,200) $point2 = [Point]::new(1,2) $point.Add($point2) - $point.x | should be 101 - $point.y | should be 202 + $point.x | Should -Be 101 + $point.y | Should -Be 202 } - It "[Point]::Add works" { + It "[Point]::Add works construction via new-object" { $point = New-Object Point 100,200 $point2 = New-Object Point 1,2 $point.Add($point2) - $point.x | should be 101 - $point.y | should be 202 + $point.x | Should -Be 101 + $point.y | Should -Be 202 } } @@ -467,7 +583,7 @@ Describe 'PositiveReturnSelfClassTypeFromMemberFunction Test' -Tags "CI" { } $f = [ReturnObjectFromMemberFunctionTest]::new() $z = $f.CreateInstance() # Line 13 - It "CreateInstance works" { $z.SayHello() | should be 'Hello1' } + It "CreateInstance works" { $z.SayHello() | Should -BeExactly 'Hello1' } } Describe 'TestMultipleArguments Test' -Tags "CI" { @@ -518,8 +634,8 @@ $ctorAssignments `$inst = [Foo]::new($methodArguments) `$sum = $addUpProperties - It "ExpectedTotal" { `$sum | should be $expectedTotal } - It "ExpectedTotal"{ `$inst.DoSomething($methodArguments) | should be $expectedTotal } + It "ExpectedTotal: Sum should be $expectedTotal" { `$sum | Should -Be $expectedTotal } + It "ExpectedTotal: Invocation should return $expectedTotal" { `$inst.DoSomething($methodArguments) | Should -Be $expectedTotal } "@ Invoke-Expression $class @@ -549,7 +665,7 @@ Describe 'Check PS Class Assembly Test' -Tags "CI" { $assem = [C1].Assembly $attrs = @($assem.GetCustomAttributes($true)) $expectedAttr = @($attrs | Where-Object { $_ -is [System.Management.Automation.DynamicClassImplementationAssemblyAttribute] }) - It "Expected a DynamicClassImplementationAssembly attribute" { $expectedAttr.Length | should be 1} + It "Expected a DynamicClassImplementationAssembly attribute" { $expectedAttr.Length | Should -Be 1} } Describe 'ScriptScopeAccessFromClassMethod' -Tags "CI" { @@ -557,7 +673,7 @@ Describe 'ScriptScopeAccessFromClassMethod' -Tags "CI" { try { $c = Get-MSFT_778492 - It "Method should have found variable in module scope" { $c.F() | should be 'MSFT_778492 script scope'} + It "Method should have found variable in module scope" { $c.F() | Should -BeExactly 'MSFT_778492 script scope'} } finally { @@ -566,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 @@ -576,33 +705,40 @@ Describe 'Hidden Members Test ' -Tags "CI" { # Create an instance $instance = [C1]@{ visibleX = 10; visibleY = 12; hiddenZ = 42 } - It "Access hidden property should still work" { $instance.hiddenZ | should be 42 } + It "Access hidden property should still work" { $instance.hiddenZ | Should -Be 42 } + + It "Table formatting should not include hidden member hiddenZ" { + $expectedTable = @" +visibleX visibleY +-------- -------- + 10 12 - # Formatting should not include hidden members by default - $tableOutput = $instance | Format-Table -HideTableHeaders -AutoSize | Out-String - It "Table formatting should not have included hidden member hiddenZ - should contain 10" { $tableOutput.Contains(10) | should be $true} - It "Table formatting should not have included hidden member hiddenZ- should contain 12" { $tableOutput.Contains(12) | should be $true} - It "Table formatting should not have included hidden member hiddenZ - should not contain 42" { $tableOutput.Contains(42) | should be $false} + +"@ + + $tableOutput = $instance | Format-Table -AutoSize | Out-String + $tableOutput.Replace("`r","") | Should -BeExactly $expectedTable.Replace("`r","") + } # Get-Member should not include hidden members by default $member = $instance | Get-Member hiddenZ - it "Get-Member should not find hidden member w/o -Force" { $member | should be $null } + It "Get-Member should not find hidden member w/o -Force" { $member | Should -BeNullOrEmpty } # Get-Member should include hidden members with -Force $member = $instance | Get-Member hiddenZ -Force - It "Get-Member should find hidden member w/ -Force" { $member | should not be $null } + It "Get-Member should find hidden member w/ -Force" { $member | Should -Not -BeNullOrEmpty } # Tab completion should not return a hidden member $line = 'class C2 { hidden [int]$hiddenZ } [C2]::new().h' $completions = [System.Management.Automation.CommandCompletion]::CompleteInput($line, $line.Length, $null) - It "Tab completion should not return a hidden member" { $completions.CompletionMatches.Count | should be 0 } + It "Tab completion should not return a hidden member" { $completions.CompletionMatches.Count | Should -Be 0 } } Describe 'BaseMethodCall Test ' -Tags "CI" { - It "Derived class method call" {"abc".ToString() | should be "abc" } + It "Derived class method call" {"abc".ToString() | Should -BeExactly "abc" } # call [object] ToString() method as a base class method. - It "Base class method call" {([object]"abc").ToString() | should be "System.String" } + It "Base class method call" {([object]"abc").ToString() | Should -BeExactly "System.String" } } Describe 'Scoped Types Test' -Tags "CI" { @@ -619,18 +755,17 @@ Describe 'Scoped Types Test' -Tags "CI" { { class C1 { [string] GetContext() { return "f2 scope" } } - return (new-object C1).GetContext() + return (New-Object C1).GetContext() } - It "New-Object at test scope" { (new-object C1).GetContext() | should be "Test scope" } - It "[C1]::new() at test scope" { [C1]::new().GetContext() | should be "Test scope" } - - It "[C1]::new() in nested scope" { (f1) | should be "f1 scope" } - It "'new-object C1' in nested scope" { (f2) | should be "f2 scope" } + It "New-Object at test scope" { (New-Object C1).GetContext() | Should -BeExactly "Test scope" } + It "[C1]::new() at test scope" { [C1]::new().GetContext() | Should -BeExactly "Test scope" } + It "[C1]::new() in nested scope" { (f1) | Should -BeExactly "f1 scope" } + It "'new-object C1' in nested scope" { (f2) | Should -BeExactly "f2 scope" } - It "[C1]::new() in nested scope (in pipeline)" { (1 | f1 | f2 | f1) | should be "f1 scope" } - It "'new-object C1' in nested scope (in pipeline)" { (1 | f2 | f1 | f2) | should be "f2 scope" } + It "[C1]::new() in nested scope (in pipeline)" { (1 | f1 | f2 | f1) | Should -BeExactly "f1 scope" } + It "'new-object C1' in nested scope (in pipeline)" { (1 | f2 | f1 | f2) | Should -BeExactly "f2 scope" } } Describe 'ParameterOfClassTypeInModule Test' -Tags "CI" { @@ -642,11 +777,11 @@ function test-it([EE]$ee){$ee} '@) $mod = New-Module $sb -Name MSFT_2081529 | Import-Module $result = test-it -ee one - It "Parameter of class/enum type defined in module should work" { $result | should be 1 } + It "Parameter of class/enum type defined in module should work" { $result | Should -Be 1 } } finally { - Remove-Module -ea ignore MSFT_2081529 + Remove-Module -ErrorAction ignore MSFT_2081529 } } @@ -656,7 +791,7 @@ Describe 'Type building' -Tags "CI" { 1..10 | ForEach-Object { class C {} if ($a) { - $a -eq [C] | Should Be $true + $a -eq [C] | Should -BeTrue } $a = [C] } @@ -666,8 +801,8 @@ Describe 'Type building' -Tags "CI" { $sb = [scriptblock]::Create('class A {static [int] $a }; [A]::new()') 1..2 | ForEach-Object { $a = $sb.Invoke()[0] - ++$a::a | Should Be 1 - ++$a::a | Should Be 2 + ++$a::a | Should -Be 1 + ++$a::a | Should -Be 2 } } @@ -676,13 +811,13 @@ Describe 'Type building' -Tags "CI" { $a = [C].Assembly.GetCustomAttributes($false).Where{ $_ -is [System.Management.Automation.DynamicClassImplementationAssemblyAttribute]} - $a.ScriptFile | Should BeExactly $PSCommandPath + $a.ScriptFile | Should -BeExactly $PSCommandPath } } Describe 'RuntimeType created for TypeDefinitionAst' -Tags "CI" { - It 'can make cast to the right RuntimeType in two different contexts' -pending { + It 'can make cast to the right RuntimeType in two different contexts' -Pending { $ssfe = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new("foo", @' class Base @@ -698,21 +833,21 @@ class Derived : Base [Derived]::new().foo() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) - $ps.AddCommand("foo").Invoke() | Should be 200 - $ps.Streams.Error | Should Be $null + $ps.AddCommand("foo").Invoke() | Should -Be 200 + $ps.Streams.Error | Should -BeNullOrEmpty $ps1 = [powershell]::Create($iss) - $ps1.AddCommand("foo").Invoke() | Should be 200 - $ps1.Streams.Error | Should Be $null + $ps1.AddCommand("foo").Invoke() | Should -Be 200 + $ps1.Streams.Error | Should -BeNullOrEmpty $ps.Commands.Clear() $ps.Streams.Error.Clear() - $ps.AddScript(". foo").Invoke() | Should be 200 - $ps.Streams.Error | Should Be $null + $ps.AddScript(". foo").Invoke() | Should -Be 200 + $ps.Streams.Error | Should -BeNullOrEmpty } } @@ -733,11 +868,11 @@ class B '@).Invoke()[0] It 'can do type lookup by name' { - $b.getA1() | Should Be 'A' + $b.getA1() | Should -BeExactly 'A' } It 'can do type lookup by [type]' { - $b.getA2() | Should Be 'A' + $b.getA2() | Should -BeExactly 'A' } } } @@ -754,7 +889,7 @@ namespace Foo } '@ - It 'doesn''t allow protected methods access outside of inheritance chain' -pending { + It 'doesn''t allow protected methods access outside of inheritance chain' { $a = [scriptblock]::Create(@' class A { @@ -765,7 +900,7 @@ class A [int] GetX([Foo.Bar]$bar) { - Set-StrictMode -Version latest + Set-StrictMode -Version 3.0 return $bar.x } } @@ -773,20 +908,8 @@ class A '@).Invoke() $bar = [Foo.Bar]::new() - $throwCount = 0 - try { - $a.SetX($bar, 42) - } catch { - $_.FullyQualifiedErrorId | Should Be PropertyAssignmentException - $throwCount++ - } - try { - $a.GetX($bar) - } catch { - $_.FullyQualifiedErrorId | Should Be PropertyNotFoundStrict - $throwCount++ - } - $throwCount | Should Be 2 + { $a.SetX($bar, 42) } | Should -Throw -ErrorId 'PropertyAssignmentException' + { $a.GetX($bar) } | Should -Throw -ErrorId 'PropertyNotFoundStrict' } It 'can call protected methods sequentially from two different contexts' { @@ -806,22 +929,22 @@ 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) $a = $ps.AddCommand("foo").Invoke()[0] - $ps.Streams.Error | Should Be $null + $ps.Streams.Error | Should -BeNullOrEmpty $ps1 = [powershell]::Create($iss) $a1 = $ps1.AddCommand("foo").Invoke()[0] - $ps1.Streams.Error | Should Be $null + $ps1.Streams.Error | Should -BeNullOrEmpty $a.SetX(101) $a1.SetX(103) - $a.GetX() | Should Be 101 - $a1.GetX() | Should Be 103 + $a.GetX() | Should -Be 101 + $a1.GetX() | Should -Be 103 } } @@ -838,6 +961,6 @@ Describe 'variable analysis' -Tags "CI" { } } - [B]::getA().getFoo() | Should Be 'foo' + [B]::getA().getFoo() | Should -BeExactly 'foo' } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 index 3274bf80947..20aab127abc 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Break statements with classes' -Tags "CI" { function Get-Errors([string]$sourceCode) { @@ -19,8 +21,8 @@ class A } } '@ - $errors.Count | Should be 1 - $errors[0].ErrorId | Should be 'LabelNotFound' + $errors.Count | Should -Be 1 + $errors[0].ErrorId | Should -BeExactly 'LabelNotFound' } It 'reports parse error for break outside of loop' { @@ -34,8 +36,8 @@ class A } } '@ - $errors.Count | Should be 1 - $errors[0].ErrorId | Should be 'LabelNotFound' + $errors.Count | Should -Be 1 + $errors[0].ErrorId | Should -BeExactly 'LabelNotFound' } It 'work fine, when break is legit' { @@ -49,7 +51,7 @@ class A return $i } } - [C]::foo() | Should be 101 + [C]::foo() | Should -Be 101 } } @@ -65,8 +67,8 @@ class A } } '@ - $errors.Count | Should be 1 - $errors[0].ErrorId | Should be 'LabelNotFound' + $errors.Count | Should -Be 1 + $errors[0].ErrorId | Should -BeExactly 'LabelNotFound' } } @@ -88,10 +90,10 @@ class A $canary = $false try { - [C]::getInt() | Should Be 123 + [C]::getInt() | Should -Be 123 $canary = $true } finally { - $canary | Should be $true + $canary | Should -BeTrue } } @@ -119,10 +121,10 @@ class A $canary = $false try { - [C]::getInt() | Should Be (123 + 4*4) + [C]::getInt() | Should -Be (123 + 4*4) $canary = $true } finally { - $canary | Should be $true + $canary | Should -BeTrue } } } @@ -138,7 +140,7 @@ function foo() return 1 } '@ - $errors.Count | Should be 0 + $errors.Count | Should -Be 0 } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 index 258354c5062..d4b6e77a290 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Exceptions flow for classes' -Tags "CI" { $canaryHashtable = @{} @@ -40,7 +42,7 @@ class C } catch {} - $canaryHashtable['canary'] | Should Be 42 + $canaryHashtable['canary'] | Should -Be 42 } It 'does not execute statements after static method with exception' { @@ -69,7 +71,7 @@ class C } catch {} - $canaryHashtable['canary'] | Should Be 43 + $canaryHashtable['canary'] | Should -Be 43 } It 'does not execute statements after instance method with exception and deep stack' { @@ -125,7 +127,7 @@ class C } catch {} - $canaryHashtable['canary'] | Should Be 11111 + $canaryHashtable['canary'] | Should -Be 11111 } } @@ -145,7 +147,6 @@ class C } } - function m2() { $canary = Get-Canary @@ -184,7 +185,7 @@ function ImThrow() } catch {} - $canaryHashtable['canaryM'] | Should Be 45 + $canaryHashtable['canaryM'] | Should -Be 45 } It 'does not execute statements after function with exception called from static method' { @@ -197,7 +198,7 @@ function ImThrow() } catch {} - $canaryHashtable['canaryS'] | Should Be 46 + $canaryHashtable['canaryS'] | Should -Be 46 } } @@ -219,7 +220,7 @@ $canaryHashtable['canary'] += 100 } catch {} - $canaryHashtable['canary'] | Should Be 111 + $canaryHashtable['canary'] | Should -Be 111 } } } @@ -230,43 +231,27 @@ Describe "Exception error position" -Tags "CI" { static f1() { [MSFT_3090412]::bar = 42 } static f2() { throw "an error in f2" } static f3() { "".Substring(0, 10) } - static f4() { dir nosuchfile -ea Stop } + static f4() { Get-ChildItem nosuchfile -ErrorAction Stop } } It "Setting a property that doesn't exist" { - try { - [MSFT_3090412]::f1() - throw "f1 should have thrown" - } catch { - $_.InvocationInfo.Line | Should Match ([regex]::Escape('[MSFT_3090412]::bar = 42')) - } + $e = { [MSFT_3090412]::f1() } | Should -Throw -PassThru -ErrorId 'PropertyAssignmentException' + $e.InvocationInfo.Line | Should -Match ([regex]::Escape('[MSFT_3090412]::bar = 42')) } It "Throwing an exception" { - try { - [MSFT_3090412]::f2() - throw "f2 should have thrown" - } catch { - $_.InvocationInfo.Line | Should Match ([regex]::Escape('throw "an error in f2"')) - } + $e = { [MSFT_3090412]::f2() } | Should -Throw -PassThru -ErrorId 'an error in f2' + $e.InvocationInfo.Line | Should -Match ([regex]::Escape('throw "an error in f2"')) } It "Calling a .Net method that throws" { - try { - [MSFT_3090412]::f3() - throw "f3 should have thrown" - } catch { - $_.InvocationInfo.Line | Should Match ([regex]::Escape('"".Substring(0, 10)')) - } + $e = { [MSFT_3090412]::f3() } | Should -Throw -PassThru -ErrorId 'ArgumentOutOfRangeException' + $e.InvocationInfo.Line | Should -Match ([regex]::Escape('"".Substring(0, 10)')) } It "Terminating error" { - try { - [MSFT_3090412]::f4() - throw "f4 should have thrown" - } catch { - $_.InvocationInfo.Line | Should Match ([regex]::Escape('dir nosuchfile -ea Stop')) - } + $e = { [MSFT_3090412]::f4() } | Should -Throw -PassThru -ErrorId 'PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand' + $e.InvocationInfo.Line | Should -Match ([regex]::Escape('Get-ChildItem nosuchfile -ErrorAction Stop')) } } @@ -294,56 +279,26 @@ Describe "Exception from initializer" -Tags "CI" { } It "instance member w/ ctor" { - try { - [MSFT_6397334a]::new() - throw "[MSFT_6397334a]::new() should have thrown" - } - catch - { - $e = $_ - $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger - $e.InvocationInfo.Line | Should Match 'a = "zz"' - } + $e = { [MSFT_6397334a]::new() } | Should -Throw -ErrorId 'InvalidCastFromStringToInteger' -PassThru + $e.InvocationInfo.Line | Should -Match 'a = "zz"' } It "instance member w/o ctor" { - try { - [MSFT_6397334b]::new() - throw "[MSFT_6397334b]::new() should have thrown" - } - catch - { - $e = $_ - $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger - $e.InvocationInfo.Line | Should Match 'a = "zz"' - } + $e = { [MSFT_6397334b]::new() } | Should -Throw -ErrorId 'InvalidCastFromStringToInteger' -PassThru + $e.InvocationInfo.Line | Should -Match 'a = "zz"' } It "static member w/ ctor" { - try { - $null = [MSFT_6397334c]::a - throw "No Exception!" - } - catch - { - $_.Exception | Should BeOfType System.TypeInitializationException - $e = $_.Exception.InnerException.InnerException.ErrorRecord - $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger - $e.InvocationInfo.Line | Should Match 'a = "zz"' - } + $e = { $null = [MSFT_6397334c]::a } | Should -Throw -PassThru + $e.Exception | Should -BeOfType System.TypeInitializationException + $e.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly 'InvalidCastFromStringToInteger' + $e.Exception.InnerException.InnerException.ErrorRecord.InvocationInfo.Line | Should -Match 'a = "zz"' } It "static member w/o ctor" { - try { - $null = [MSFT_6397334d]::a - throw "No Exception!" - } - catch - { - $_.Exception | Should BeOfType System.TypeInitializationException - $e = $_.Exception.InnerException.InnerException.ErrorRecord - $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger - $e.InvocationInfo.Line | Should Match 'a = "zz"' - } + $e = { $null = [MSFT_6397334d]::a } | Should -Throw -PassThru + $e.Exception | Should -BeOfType System.TypeInitializationException + $e.Exception.InnerException.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly 'InvalidCastFromStringToInteger' + $e.Exception.InnerException.InnerException.ErrorRecord.InvocationInfo.Line | Should -Match 'a = "zz"' } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 index 45162a1dcba..11880bf7dce 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Misc Test' -Tags "CI" { Context 'Where' { @@ -13,10 +15,10 @@ Describe 'Misc Test' -Tags "CI" { } } It 'Invoke Where' { - [C1]::new().Foo() | should be "1;2;3" + [C1]::new().Foo() | Should -Be "1;2;3" } It 'Pipe to where' { - [C1]::new().Bar() | should be "1;2;3" + [C1]::new().Bar() | Should -Be "1;2;3" } } @@ -37,10 +39,10 @@ Describe 'Misc Test' -Tags "CI" { } } It 'Invoke Foreach' { - [C1]::new().Foo() | should be "1;2;3;" + [C1]::new().Foo() | Should -Be "1;2;3;" } It 'Pipe to Foreach' { - [C1]::new().Bar() | should be "1;2;3;" + [C1]::new().Bar() | Should -Be "1;2;3;" } } @@ -61,10 +63,10 @@ Describe 'Misc Test' -Tags "CI" { $NewRunspaceFunctionDefinitions = @" ## Define 'Get-TestText' in the new Runspace function Get-TestText { return '$ExpectedTextFromUnboundInstance' } - + ## Define the function to create an instance of the given type using the default constructor function New-UnboundInstance([Type]`$type) { `$type::new() } - + ## Define the function to call 'Foo()' on the given C1 instance, and return the result function Run-Foo(`$C1Instance) { `$C1Instance.Foo() } "@ @@ -76,7 +78,7 @@ Describe 'Misc Test' -Tags "CI" { function InstantiateInNewRunspace([Type]$type) { try { $result = $powershell.AddCommand("New-UnboundInstance").AddParameter("type", $type).Invoke() - $result.Count | Should Be 1 > $null + $result.Count | Should -Be 1 return $result[0] } finally { $powershell.Commands.Clear() @@ -86,7 +88,7 @@ Describe 'Misc Test' -Tags "CI" { function RunFooInNewRunspace($instance) { try { $result = $powershell.AddCommand("Run-Foo").AddParameter("C1Instance", $instance).Invoke() - $result.Count | Should Be 1 > $null + $result.Count | Should -Be 1 return $result[0] } finally { $powershell.Commands.Clear() @@ -102,16 +104,16 @@ Describe 'Misc Test' -Tags "CI" { $instance = [C1]::new() ## For a bound class instance, the execution of an instance method is ## done in the Runspace/SessionState the instance is bound to. - $instance.Foo() | Should Be $ExpectedTextFromBoundInstance - RunFooInNewRunspace $instance | Should Be $ExpectedTextFromBoundInstance + $instance.Foo() | Should -BeExactly $ExpectedTextFromBoundInstance + RunFooInNewRunspace $instance | Should -BeExactly $ExpectedTextFromBoundInstance } It "Create instance that is NOT bound to a SessionState" { $instance = InstantiateInNewRunspace ([C1]) ## For an unbound class instance, the execution of an instance method is done in ## the Runspace/SessionState where the call to the instance method is made. - $instance.Foo() | Should Be $ExpectedTextFromBoundInstance - RunFooInNewRunspace $instance | Should Be $ExpectedTextFromUnboundInstance + $instance.Foo() | Should -BeExactly $ExpectedTextFromBoundInstance + RunFooInNewRunspace $instance | Should -BeExactly $ExpectedTextFromUnboundInstance } } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 index c25cb49c5c6..c2e376f10cf 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 @@ -1,7 +1,9 @@ -Describe 'PSModuleInfo.GetExportedTypeDefinitions()' -Tags "CI" { +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe 'PSModuleInfo.GetExportedTypeDefinitions()' -Tags "CI" { It "doesn't throw for any module" { $discard = Get-Module -ListAvailable | ForEach-Object { $_.GetExportedTypeDefinitions() } - $true | Should Be $true # we only verify that we didn't throw. This line contains a dummy Should to make pester happy. + $true | Should -BeTrue # we only verify that we didn't throw. This line contains a dummy Should to make pester happy. } } @@ -24,7 +26,7 @@ Describe 'use of a module from two runspaces' -Tags "CI" { } New-ModuleManifest @manifestParams - if ($env:PSModulePath -notlike "*$TestModulePath*") { + if ($env:PSModulePath -NotLike "*$TestModulePath*") { $env:PSModulePath += "$([System.IO.Path]::PathSeparator)$TestModulePath" } } @@ -60,10 +62,10 @@ Import-Module Random } } - $res.Count | Should Be 4 - $res[0] | Should Not Be $res[1] - $res[0] | Should Be $res[2] - $res[1] | Should Be $res[3] + $res.Count | Should -Be 4 + $res[0] | Should -Not -Be $res[1] + $res[0] | Should -Be $res[2] + $res[1] | Should -Be $res[3] } } finally { @@ -91,23 +93,23 @@ function Get-PassedArgsNoRoot { $passedArgs } It "Class execution reflects changes in module reloading with '-Force'" { Import-Module TestDrive:\TestModule.psm1 -ArgumentList $Arg_Hello - Get-PassedArgsRoot | Should Be $Arg_Hello - Get-PassedArgsNoRoot | Should Be $Arg_Hello + Get-PassedArgsRoot | Should -BeExactly $Arg_Hello + Get-PassedArgsNoRoot | Should -BeExactly $Arg_Hello Import-Module TestDrive:\TestModule.psm1 -ArgumentList $Arg_World -Force - Get-PassedArgsRoot | Should Be $Arg_World - Get-PassedArgsNoRoot | Should Be $Arg_World + Get-PassedArgsRoot | Should -BeExactly $Arg_World + Get-PassedArgsNoRoot | Should -BeExactly $Arg_World } It "Class execution reflects changes in module reloading with 'Remove-Module' and 'Import-Module'" { Import-Module TestDrive:\TestModule.psm1 -ArgumentList $Arg_Hello - Get-PassedArgsRoot | Should Be $Arg_Hello - Get-PassedArgsNoRoot | Should Be $Arg_Hello + Get-PassedArgsRoot | Should -BeExactly $Arg_Hello + Get-PassedArgsNoRoot | Should -BeExactly $Arg_Hello Remove-Module TestModule Import-Module TestDrive:\TestModule.psm1 -ArgumentList $Arg_World - Get-PassedArgsRoot | Should Be $Arg_World - Get-PassedArgsNoRoot | Should Be $Arg_World + Get-PassedArgsRoot | Should -BeExactly $Arg_World + Get-PassedArgsNoRoot | Should -BeExactly $Arg_World } } 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.RunPath.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.RunPath.Tests.ps1 index d37635616c4..e6545d0ed28 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.RunPath.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.RunPath.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Script with a class definition run path" -Tags "CI" { $TestCases = @( @@ -15,6 +17,6 @@ class MyClass { static [string]$MyProperty = 'Some value' } [MyClass]::MyProperty '@ | Out-File -FilePath $FilePath - ( . $FilePath ) | Should Match 'Some value' + ( . $FilePath ) | Should -Match 'Some value' } } diff --git a/test/powershell/Language/Classes/Scripting.Classes.StaticMethod.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.StaticMethod.Tests.ps1 index 4c014092ecd..e99a96a504a 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.StaticMethod.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.StaticMethod.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Additional static method tests" -Tags "CI" { Context "Basic static member methods" { @@ -11,7 +13,7 @@ Describe "Additional static method tests" -Tags "CI" { static Foo() { [Foo]::Name = Get-Name } } - [Foo]::Name | Should Be "Yes" + [Foo]::Name | Should -Be "Yes" } It "test basic static method" { @@ -19,7 +21,7 @@ Describe "Additional static method tests" -Tags "CI" { static [string] GetName() { return (Get-Name) } } - [Foo]::GetName() | Should Be "Yes" + [Foo]::GetName() | Should -Be "Yes" } } @@ -30,7 +32,7 @@ class Foo { static [string] $Name static Foo() { [Foo]::Name = Get-Name } - + static [string] GetName() { return (Get-AnotherName) @@ -78,26 +80,26 @@ class Foo } It "Static constructor should run in the triggering Runspace if the class has been defined in that Runspace" { - + ## The static constructor is triggered by accessing '[Foo]::Name' which happens in the current Runspace. ## The class 'Foo' has been defined in the current Runspace, so it uses the current Runspace to run the ## static constructor. - [Foo]::Name | Should Be "Default Runspace - Name" + [Foo]::Name | Should -BeExactly "Default Runspace - Name" ## Static constructor runs only once, so accessing the Name property in the PS1 Runspace will just return ## the existing value. - RunScriptInPS -PowerShell $ps1 -Script "[Foo]::Name" | Should Be "Default Runspace - Name" + RunScriptInPS -PowerShell $ps1 -Script "[Foo]::Name" | Should -BeExactly "Default Runspace - Name" } It "Static method use the Runspace where the call happens if the class has been defined in that Runspace" { ## We call the static method in the current Runspace. The class 'Foo' has been defined ## in the current Runspace, so it will use it to run the method. - [Foo]::GetName() | Should Be "Default Runspace - AnotherName" + [Foo]::GetName() | Should -BeExactly "Default Runspace - AnotherName" ## We call the static method in PS1 Runspace. The class 'Foo' has been defined in the ## PS1 Runspace, so it will use it to run the method. - RunScriptInPS -PowerShell $ps1 -Script "[Foo]::GetName()" | Should Be 'PS1 Runspace - AnotherName' + RunScriptInPS -PowerShell $ps1 -Script "[Foo]::GetName()" | Should -BeExactly 'PS1 Runspace - AnotherName' } It "Static method use the default SessionState if it's called in a Runspace where the class is not defined" { @@ -105,7 +107,7 @@ class Foo ## Define the functions that [Foo] depends on in PS2 Runspace. RunScriptInPS -PowerShell $ps2 -Script "function Get-Name { 'PS2 Runspace - Name' }" -IgnoreResult RunScriptInPS -PowerShell $ps2 -Script "function Get-AnotherName { 'PS2 Runspace - AnotherName' }" -IgnoreResult - + ## Define the function to call the static method 'GetName' on the passed-in type RunScriptInPS -PowerShell $ps2 -Script 'function Call-GetName([type] $type) { $type::GetName() }' -IgnoreResult @@ -114,7 +116,7 @@ class Foo ## is always the one where the class was defined most recently. In this case, the class ## was lastly defined in PS1 Runspace, os the method will be invoked in PS1 Runspace. $result = $ps2.AddCommand("Call-GetName").AddParameter("type", [Foo]).Invoke() - $result | Should Be 'PS1 Runspace - AnotherName' + $result | Should -BeExactly 'PS1 Runspace - AnotherName' } } } diff --git a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 index f16a39db1c9..97f69982e21 100644 --- a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 @@ -1,42 +1,42 @@ -Describe 'NestedModules' -Tags "CI" { - - function New-TestModule { - param( - [string]$Name, - [string]$Content, - [string[]]$NestedContents - ) +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. - new-item -type directory -Force "TestDrive:\$Name" > $null - $manifestParams = @{ - Path = "TestDrive:\$Name\$Name.psd1" - } +Describe 'NestedModules' -Tags "CI" { + BeforeAll { + function New-TestModule { + param( + [string]$Name, + [string]$Content, + [string[]]$NestedContents + ) + + New-Item -type directory -Force "TestDrive:\$Name" > $null + $manifestParams = @{ + Path = "TestDrive:\$Name\$Name.psd1" + } - if ($Content) { - Set-Content -Path "${TestDrive}\$Name\$Name.psm1" -Value $Content - $manifestParams['RootModule'] = "$Name.psm1" - } + if ($Content) { + Set-Content -Path "${TestDrive}\$Name\$Name.psm1" -Value $Content + $manifestParams['RootModule'] = "$Name.psm1" + } - if ($NestedContents) { - $manifestParams['NestedModules'] = 1..$NestedContents.Count | ForEach-Object { - $null = new-item -type directory TestDrive:\$Name\Nested$_ - $null = Set-Content -Path "${TestDrive}\$Name\Nested$_\Nested$_.psm1" -Value $NestedContents[$_ - 1] - "Nested$_" + if ($NestedContents) { + $manifestParams['NestedModules'] = 1..$NestedContents.Count | ForEach-Object { + $null = New-Item -type directory TestDrive:\$Name\Nested$_ + $null = Set-Content -Path "${TestDrive}\$Name\Nested$_\Nested$_.psm1" -Value $NestedContents[$_ - 1] + "Nested$_" + } } - } - New-ModuleManifest @manifestParams + New-ModuleManifest @manifestParams - $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\)[0].FullName) - if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { - $env:PSModulePath += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" + $resolvedTestDrivePath = Split-Path ((Get-ChildItem TestDrive:\)[0].FullName) + if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { + $env:PSModulePath += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" + } } - } - - $originalPSModulePath = $env:PSModulePath - - try { + $originalPSModulePath = $env:PSModulePath # Create modules in TestDrive:\ New-TestModule -Name NoRoot -NestedContents @( @@ -53,72 +53,74 @@ Describe 'NestedModules' -Tags "CI" { 'class A { [string] foo() { return "A"} }', 'class B { [string] foo() { return "B"} }' ) -Content 'class C { [string] foo() { return "C"} }' + } - It 'Get-Module is able to find types' { - $module = Get-Module NoRoot -ListAvailable - $module.GetExportedTypeDefinitions().Count | Should Be 1 + AfterAll { + $env:PSModulePath = $originalPSModulePath + Get-Module @('ABC', 'NoRoot', 'WithRoot') | Remove-Module + } - $module = Get-Module WithRoot -ListAvailable - $module.GetExportedTypeDefinitions().Count | Should Be 1 - $module = Get-Module ABC -ListAvailable - $module.GetExportedTypeDefinitions().Count | Should Be 3 - } + It 'Get-Module is able to find types' { + $module = Get-Module NoRoot -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should -Be 1 + + $module = Get-Module WithRoot -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should -Be 1 - It 'Import-Module pick the right type' { - $module = Import-Module ABC -PassThru - $module.GetExportedTypeDefinitions().Count | Should Be 3 - $module = Import-Module ABC -PassThru -Force - $module.GetExportedTypeDefinitions().Count | Should Be 3 - - $module = Import-Module NoRoot -PassThru - $module.GetExportedTypeDefinitions().Count | Should Be 1 - $module = Import-Module NoRoot -PassThru -Force - $module.GetExportedTypeDefinitions().Count | Should Be 1 - [scriptblock]::Create(@' + $module = Get-Module ABC -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should -Be 3 + } + + It 'Import-Module pick the right type' { + $module = Import-Module ABC -PassThru + $module.GetExportedTypeDefinitions().Count | Should -Be 3 + $module = Import-Module ABC -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should -Be 3 + + $module = Import-Module NoRoot -PassThru + $module.GetExportedTypeDefinitions().Count | Should -Be 1 + $module = Import-Module NoRoot -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should -Be 1 + [scriptblock]::Create(@' using module NoRoot [A]::new().foo() '@ -).Invoke() | Should Be A2 +).Invoke() | Should -Be A2 - $module = Import-Module WithRoot -PassThru - $module.GetExportedTypeDefinitions().Count | Should Be 1 - $module = Import-Module WithRoot -PassThru -Force - $module.GetExportedTypeDefinitions().Count | Should Be 1 - [scriptblock]::Create(@' + $module = Import-Module WithRoot -PassThru + $module.GetExportedTypeDefinitions().Count | Should -Be 1 + $module = Import-Module WithRoot -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should -Be 1 + [scriptblock]::Create(@' using module WithRoot [A]::new().foo() '@ -).Invoke() | Should Be A0 - } - - Context 'execute type creation in the module context' { - - # let's define types to make it more fun - class A { [string] foo() { return "local"} } - class B { [string] foo() { return "local"} } - class C { [string] foo() { return "local"} } - - # We need to think about it: should it work or not. - # Currently, types are resolved in compile-time to the 'local' versions - # So at runtime we don't call the module versions. - It 'Can execute type creation in the module context with new()' -pending { - & (Get-Module ABC) { [C]::new().foo() } | Should Be C - & (Get-Module NoRoot) { [A]::new().foo() } | Should Be A2 - & (Get-Module WithRoot) { [A]::new().foo() } | Should Be A0 - & (Get-Module ABC) { [A]::new().foo() } | Should Be A - } +).Invoke() | Should -Be A0 + } - It 'Can execute type creation in the module context with New-Object' { - & (Get-Module ABC) { (New-Object C).foo() } | Should Be C - & (Get-Module NoRoot) { (New-Object A).foo() } | Should Be A2 - & (Get-Module WithRoot) { (New-Object A).foo() } | Should Be A0 - & (Get-Module ABC) { (New-Object A).foo() } | Should Be A - } + Context 'execute type creation in the module context' { + + # let's define types to make it more fun + class A { [string] foo() { return "local"} } + class B { [string] foo() { return "local"} } + class C { [string] foo() { return "local"} } + + # We need to think about it: should it work or not. + # Currently, types are resolved in compile-time to the 'local' versions + # So at runtime we don't call the module versions. + It 'Can execute type creation in the module context with new()' -Pending { + & (Get-Module ABC) { [C]::new().foo() } | Should -Be C + & (Get-Module NoRoot) { [A]::new().foo() } | Should -Be A2 + & (Get-Module WithRoot) { [A]::new().foo() } | Should -Be A0 + & (Get-Module ABC) { [A]::new().foo() } | Should -Be A } - } finally { - $env:PSModulePath = $originalPSModulePath - Get-Module @('ABC', 'NoRoot', 'WithRoot') | Remove-Module + It 'Can execute type creation in the module context with New-Object' { + & (Get-Module ABC) { (New-Object C).foo() } | Should -Be C + & (Get-Module NoRoot) { (New-Object A).foo() } | Should -Be A2 + & (Get-Module WithRoot) { (New-Object A).foo() } | Should -Be A0 + & (Get-Module ABC) { (New-Object A).foo() } | Should -Be A + } } } diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index 13e71cc3bbf..6b24b8b6ce0 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -1,6 +1,5 @@ -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Classes inheritance syntax' -Tags "CI" { @@ -9,8 +8,8 @@ Describe 'Classes inheritance syntax' -Tags "CI" { class C2a : C1 {} class C2b:C1 {} - [C2a]::new().GetType().BaseType.Name | Should Be "C1" - [C2b].BaseType.Name | Should Be "C1" + [C2a]::new().GetType().BaseType.Name | Should -Be "C1" + [C2b].BaseType.Name | Should -Be "C1" } It 'inheritance from abstract base class with no abstract methods and protected ctor' { @@ -44,13 +43,13 @@ Describe 'Classes inheritance syntax' -Tags "CI" { } } - [C2a].GetInterface("System.IDisposable") | Should Not Be $null - [C2b].GetInterface("System.IDisposable") | Should Not Be $null + [C2a].GetInterface("System.IDisposable") | Should -Not -BeNullOrEmpty + [C2b].GetInterface("System.IDisposable") | Should -Not -BeNullOrEmpty } It 'can subclass .NET type' { class MyIntList : system.collections.generic.list[int] {} - [MyIntList]::new().GetType().BaseType.FullName.StartsWith('System.Collections.Generic.List') | Should Be $true + [MyIntList]::new().GetType().BaseType.FullName.StartsWith('System.Collections.Generic.List') | Should -BeTrue } It 'can implement .NET interface' { @@ -61,18 +60,120 @@ Describe 'Classes inheritance syntax' -Tags "CI" { return 0; } } - [MyComparable].GetInterface("System.IComparable") | Should Not Be $null + [MyComparable].GetInterface("System.IComparable") | Should -Not -BeNullOrEmpty + } + + It 'can implement .NET interface properties' { + Add-Type -TypeDefinition 'public interface InterfaceWithProperty { int Integer { get; set; } }' + $C1 = Invoke-Expression 'class ClassWithInterfaceProperty : InterfaceWithProperty { [int]$Integer } [ClassWithInterfaceProperty]::new()' + $getter = $C1.GetType().GetMember('get_Integer') + $getter.ReturnType.FullName | Should -Be System.Int32 + $getter.Attributes -band [System.Reflection.MethodAttributes]::Virtual | Should -Be ([System.Reflection.MethodAttributes]::Virtual) + } + + It 'can implement inherited .NET interface properties' { + Add-Type -TypeDefinition 'public interface IParent { int ParentInteger { get; set; } } + public interface IChild : IParent { int ChildInteger { get; set; } }' + $C1 = Invoke-Expression 'class ClassWithInheritedInterfaces : IChild { [int]$ParentInteger; [int]$ChildInteger } [ClassWithInheritedInterfaces]' + $getter = $C1.GetMember('get_ParentInteger') + $getter.ReturnType.FullName | Should -Be System.Int32 + $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 {} [A]::b = [B]::new() - try { - [A]::b = "bla" - throw "No Exception!" - } catch { - $_.Exception | Should BeOfType 'System.Management.Automation.SetValueInvocationException' + { [A]::b = "bla" } | Should -Throw -ErrorId 'ExceptionWhenSetting' + } + + Context "Inheritance from abstract .NET classes" { + BeforeAll { + class TestHost : System.Management.Automation.Host.PSHost + { + [String]$myName = "MyHost" + [Version]$myVersion = [Version]"1.0.0.0" + [Guid]$myInstanceId = [guid]::NewGuid() + [System.Globalization.CultureInfo]$myCurrentCulture = "en-us" + [System.Globalization.CultureInfo]$myCurrentUICulture = "en-us" + [System.Management.Automation.Host.PSHostUserInterface]$myUI = $null + [bool]$IsInteractive + [void]SetShouldExit([int]$exitCode) { } + [void]EnterNestedPrompt(){ throw "EnterNestedPrompt-NotSupported" } + [void]ExitNestedPrompt(){ throw "Unsupported" } + [void]NotifyBeginApplication() { } + [void]NotifyEndApplication() { } + [string]get_Name() { return $this.myName; Write-Host "MyName" } + [version]get_Version() { return $this.myVersion } + [System.Globalization.CultureInfo]get_CurrentCulture() { return $this.myCurrentCulture } + [System.Globalization.CultureInfo]get_CurrentUICulture() { return $this.myCurrentUICulture } + [System.Management.Automation.Host.PSHostUserInterface]get_UI() { return $this.myUI } + [guid]get_InstanceId() { return $this.myInstanceId } + TestHost() { + } + TestHost([bool]$isInteractive) { + $this.IsInteractive = $isInteractive + } + } + } + + It 'can subclass .NET abstract class' { + $th = [TestHost]::new() + $th.myName | Should -BeExactly "MyHost" + $th.myVersion | Should -Be ([Version]"1.0.0.0") + } + + It 'overrides abstract base class properties' { + $th = [TestHost]::new() + $th.Name | Should -BeExactly "MyHost" + } + + It 'overrides abstract base class methods' { + $th = [TestHost]::new() + { $th.EnterNestedPrompt() } | Should -Throw "EnterNestedPrompt-NotSupported" } } } @@ -124,7 +225,7 @@ Describe 'Classes methods with inheritance' -Tags "CI" { [int]foo() {return 100500} } class baz : bar {} - [baz]::new().foo() | Should Be 100500 + [baz]::new().foo() | Should -Be 100500 } It 'can call static method on base class' { @@ -133,7 +234,7 @@ Describe 'Classes methods with inheritance' -Tags "CI" { static [int]foo() {return 100500} } class baz : bar {} - [baz]::foo() | Should Be 100500 + [baz]::foo() | Should -Be 100500 } It 'can access static and instance base class property' { @@ -152,8 +253,8 @@ Describe 'Classes methods with inheritance' -Tags "CI" { } $b = [B]::new() $b.foo() - [A]::si | Should Be 1001 - ($b.i) | Should Be 1003 + [A]::si | Should -Be 1001 + ($b.i) | Should -Be 1003 } It 'works with .NET types' { @@ -161,9 +262,9 @@ Describe 'Classes methods with inheritance' -Tags "CI" { $intList = [MyIntList]::new() $intList.Add(100501) $intList.Add(100502) - $intList.Count | Should Be 2 - $intList[0] | Should Be 100501 - $intList[1] | Should Be 100502 + $intList.Count | Should -Be 2 + $intList[0] | Should -Be 100501 + $intList[1] | Should -Be 100502 } It 'overrides instance method' { @@ -175,10 +276,72 @@ Describe 'Classes methods with inheritance' -Tags "CI" { { [int]foo() {return 200600} } - [baz]::new().foo() | Should Be 200600 + [baz]::new().foo() | Should -Be 200600 } - It 'allows base class method call and doesn''t fall into recursion' { + It 'allows base .NET class method call and doesn''t fall into recursion' { + Add-Type -TypeDefinition @' + public class BaseMembersTestClass + { + public virtual int PublicMethod() + { + return 1001; + } + + protected virtual int FamilyMethod() + { + return 2002; + } + + protected internal virtual int FamilyOrAssemblyMethod() + { + return 3003; + } + } +'@ + $derived = Invoke-Expression @' + class BaseCallTestClass : BaseMembersTestClass + { + hidden [int] $publicMethodCallCounter + [int] PublicMethod() + { + if ($this.publicMethodCallCounter++ -gt 0) + { + throw "Recursion happens" + } + return 3 * ([BaseMembersTestClass]$this).PublicMethod() + } + + hidden [int] $familyMethodCallCounter + [int] FamilyMethod() + { + if ($this.familyMethodCallCounter++ -gt 0) + { + throw "Recursion happens" + } + return 3 * ([BaseMembersTestClass]$this).FamilyMethod() + } + + hidden [int] $familyOrAssemblyMethodCallCounter + [int] FamilyOrAssemblyMethod() + { + if ($this.familyOrAssemblyMethodCallCounter++ -gt 0) + { + throw "Recursion happens" + } + return 3 * ([BaseMembersTestClass]$this).FamilyOrAssemblyMethod() + } + } + + [BaseCallTestClass]::new() +'@ + + $derived.PublicMethod() | Should -Be 3003 + $derived.FamilyMethod() | Should -Be 6006 + $derived.FamilyOrAssemblyMethod() | Should -Be 9009 + } + + It 'allows base PowerShell class method call and doesn''t fall into recursion' { class bar { [int]foo() {return 1001} @@ -197,7 +360,7 @@ Describe 'Classes methods with inheritance' -Tags "CI" { } $res = [baz]::new().foo() - $res | Should Be 3003 + $res | Should -Be 3003 } It 'case insensitive for base class method calls' { @@ -219,7 +382,7 @@ Describe 'Classes methods with inheritance' -Tags "CI" { } $res = [baz]::new().foo() - $res | Should Be 2002 + $res | Should -Be 2002 } It 'allows any call from the inheritance hierarchy' { @@ -241,11 +404,11 @@ Describe 'Classes methods with inheritance' -Tags "CI" { } $d = [D]::new() - ([A]$d).GetName() | Should Be "A" - ([B]$d).GetName() | Should Be "B" - ([C]$d).GetName() | Should Be "C" - ([D]$d).GetName() | Should Be "D" - $d.GetName() | Should Be "D" + ([A]$d).GetName() | Should -Be "A" + ([B]$d).GetName() | Should -Be "B" + ([C]$d).GetName() | Should -Be "C" + ([D]$d).GetName() | Should -Be "D" + $d.GetName() | Should -Be "D" } It 'can call base method with params' { @@ -258,8 +421,8 @@ Describe 'Classes methods with inheritance' -Tags "CI" { [string]ToStr([int]$a) {return "B" + $a} } $b = [B]::new() - ([A]$b).ToStr(101) | Should Be "A101" - $b.ToStr(100) | Should Be "B100" + ([A]$b).ToStr(101) | Should -Be "A101" + $b.ToStr(100) | Should -Be "B100" } It 'can call base method with many params' { @@ -290,8 +453,8 @@ Describe 'Classes methods with inheritance' -Tags "CI" { # we don't really care about methods results, we only checks that calls doesn't throw # 14 args is a limit - $b.ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should Be 'B' - ([A]$b).ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should Be 'A' + $b.ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should -Be 'B' + ([A]$b).ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should -Be 'A' # 14 args is a limit $b.Noop(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) @@ -312,13 +475,13 @@ Describe 'Classes methods with inheritance' -Tags "CI" { } $b = [B]::new() ([A]$b).SetStr(101) - $script:voidOverrideVar | Should Be "A101" + $script:voidOverrideVar | Should -Be "A101" $b.SetStr(100) - $script:voidOverrideVar | Should Be "B100" + $script:voidOverrideVar | Should -Be "B100" ([A]$b).SetStr() - $script:voidOverrideVar | Should Be "A" + $script:voidOverrideVar | Should -Be "A" $b.SetStr() - $script:voidOverrideVar | Should Be "B" + $script:voidOverrideVar | Should -Be "B" } It 'hides final .NET method' { @@ -333,8 +496,8 @@ Describe 'Classes methods with inheritance' -Tags "CI" { $intList = [MyIntList]::new() $intList.Add(100201) - $intList.Count | Should Be 1 - $intList[0] | Should Be 200402 + $intList.Count | Should -Be 1 + $intList[0] | Should -Be 200402 } } @@ -353,16 +516,15 @@ Describe 'Classes methods with inheritance' -Tags "CI" { # MSFT:1911652 # MSFT:2973835 It 'doesn''t affect static method call on type' -Skip { - ([A]$b)::ToStr(101) | Should Be "A101" + ([A]$b)::ToStr(101) | Should -Be "A101" } It 'overrides static method call on instance' { - $b::ToStr(100) | Should Be "B100" + $b::ToStr(100) | Should -Be "B100" } } } - Describe 'Classes inheritance ctors syntax errors' -Tags "CI" { #DotNet.Interface.NotImplemented @@ -395,7 +557,7 @@ Describe 'Classes inheritance ctors' -Tags "CI" { } $b = [B]::new(101) - $b.a | Should Be 202 + $b.a | Should -Be 202 } # TODO: can we detect it in the parse time? @@ -413,12 +575,7 @@ Describe 'Classes inheritance ctors' -Tags "CI" { B([int]$a) : base($a * 2, 100) {} } - try { - [B]::new(101) - throw "No Exception!" - } catch { - $_.Exception | Should BeOfType "System.Management.Automation.MethodException" - } + { [B]::new(101) } | Should -Throw -ErrorId "MethodCountCouldNotFindBest" } It 'call default base ctor implicitly' { @@ -441,21 +598,14 @@ Describe 'Classes inheritance ctors' -Tags "CI" { $b = [B]::new() $c = [C]::new() - $b.a | Should Be 1007 - $c.a | Should Be 1007 + $b.a | Should -Be 1007 + $c.a | Should -Be 1007 } It 'doesn''t allow base ctor as an explicit method call' { $o = [object]::new() - try { - # we should not allow direct .ctor call. - $o.{.ctor}() - } catch { - $_.FullyQualifiedErrorId | Should Be MethodNotFound - return - } - # Fail - '' | Should Be "Exception expected" + # we should not allow direct .ctor call. + { $o.{.ctor}() } | Should -Throw -ErrorId "MethodNotFound" } It 'allow use conversion [string -> int] in base ctor call' { @@ -473,7 +623,7 @@ Describe 'Classes inheritance ctors' -Tags "CI" { } $b = [B]::new() - $b.a | Should Be 103 + $b.a | Should -Be 103 } It 'resolves ctor call based on argument type' { @@ -497,8 +647,8 @@ Describe 'Classes inheritance ctors' -Tags "CI" { $b1 = [B]::new("foo") $b2 = [B]::new(1001) - $b1.s | Should Be "foo" - $b2.i | Should Be 1001 + $b1.s | Should -Be "foo" + $b2.i | Should -Be 1001 } } @@ -517,7 +667,257 @@ class Derived : Base [Derived]::new().foo() '@) - $sb.Invoke() | Should Be 200 - $sb.Invoke() | Should Be 200 + $sb.Invoke() | Should -Be 200 + $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 b4120d072d4..d4e86e341b1 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'using module' -Tags "CI" { BeforeAll { $originalPSModulePath = $env:PSModulePath @@ -12,15 +14,15 @@ Describe 'using module' -Tags "CI" { ) if ($manifest) { - new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name\$Version" > $null + New-Item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name\$Version" > $null Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psm1" -Value $Content New-ModuleManifest -RootModule "$Name.psm1" -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psd1" -ModuleVersion $Version } else { - new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name" > $null + New-Item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name" > $null Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Name.psm1" -Value $Content } - $resolvedTestDrivePath = Split-Path ((get-childitem "${TestDrive}\$ModulePathPrefix")[0].FullName) + $resolvedTestDrivePath = Split-Path ((Get-ChildItem "${TestDrive}\$ModulePathPrefix")[0].FullName) if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { $env:PSModulePath += "$([System.IO.Path]::PathSeparator)$resolvedTestDrivePath" } @@ -39,7 +41,7 @@ Describe 'using module' -Tags "CI" { $module = Import-Module Foo -PassThru try { - $module.ImplementingAssembly | Should Not Be $null + $module.ImplementingAssembly | Should -Not -BeNullOrEmpty } finally { $module | Remove-Module } @@ -52,7 +54,7 @@ class Bar : Foo {} [Bar] "@).Invoke() - $barType.BaseType.Name | Should Be 'Foo' + $barType.BaseType.Name | Should -BeExactly 'Foo' } It "can use class from another module in New-Object" { @@ -63,9 +65,9 @@ New-Object FooWithManifest.Foo New-Object Foo.Foo "@).Invoke() - $foo.Count | Should Be 2 - $foo[0].GetModuleName() | Should Be 'FooWithManifest' - $foo[1].GetModuleName() | Should Be 'Foo' + $foo.Count | Should -Be 2 + $foo[0].GetModuleName() | Should -BeExactly 'FooWithManifest' + $foo[1].GetModuleName() | Should -BeExactly 'Foo' } It "can use class from another module by full name as base class and [type]" { @@ -74,7 +76,7 @@ using module Foo class Bar : Foo.Foo {} [Foo.Foo]::new() "@).Invoke() - $fooObject.GetModuleName() | Should Be 'Foo' + $fooObject.GetModuleName() | Should -BeExactly 'Foo' } It "can use modules with classes collision" { @@ -97,11 +99,11 @@ class Bar : Foo {} (New-Object Foo).GetModuleName() # This "@).Invoke() - $fooModuleName.Count | Should Be 4 - $fooModuleName[0] | Should Be 'Foo' - $fooModuleName[1] | Should Be 'FooWithManifest' - $fooModuleName[2] | Should Be 'This' - $fooModuleName[3] | Should Be 'This' + $fooModuleName.Count | Should -Be 4 + $fooModuleName[0] | Should -BeExactly 'Foo' + $fooModuleName[1] | Should -BeExactly 'FooWithManifest' + $fooModuleName[2] | Should -BeExactly 'This' + $fooModuleName[3] | Should -BeExactly 'This' } It "doesn't mess up two consecutive scripts" { @@ -119,8 +121,8 @@ class Bar : Foo {} [Bar]::new().GetModuleName() "@) - $sb1.Invoke() | Should Be 'Foo' - $sb2.Invoke() | Should Be 'This' + $sb1.Invoke() | Should -BeExactly 'Foo' + $sb2.Invoke() | Should -BeExactly 'This' } It "can use modules with classes collision simple" { @@ -139,12 +141,12 @@ class Bar : Foo {} (New-Object Foo).GetModuleName() # This "@).Invoke() - $fooModuleName.Count | Should Be 5 - $fooModuleName[0] | Should Be 'Foo' - $fooModuleName[1] | Should Be 'Foo' - $fooModuleName[2] | Should Be 'This' - $fooModuleName[3] | Should Be 'This' - $fooModuleName[4] | Should Be 'This' + $fooModuleName.Count | Should -Be 5 + $fooModuleName[0] | Should -BeExactly 'Foo' + $fooModuleName[1] | Should -BeExactly 'Foo' + $fooModuleName[2] | Should -BeExactly 'This' + $fooModuleName[3] | Should -BeExactly 'This' + $fooModuleName[4] | Should -BeExactly 'This' } It "can use class from another module as a base class with using module with manifest" { @@ -154,7 +156,7 @@ class Bar : Foo {} [Bar] "@).Invoke() - $barType.BaseType.Name | Should Be 'Foo' + $barType.BaseType.Name | Should -BeExactly 'Foo' } It "can instantiate class from another module" { @@ -163,7 +165,7 @@ using module Foo [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo' + $foo.GetModuleName() | Should -BeExactly 'Foo' } It "cannot instantiate class from another module without using statement" { @@ -171,7 +173,7 @@ using module Foo #using module Foo [Foo]::new() "@ - $err.FullyQualifiedErrorId | Should Be TypeNotFound + $err.FullyQualifiedErrorId | Should -BeExactly 'TypeNotFound' } It "can use class from another module in New-Object by short name" { @@ -179,7 +181,7 @@ using module Foo using module FooWithManifest New-Object Foo "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' + $foo.GetModuleName() | Should -BeExactly 'FooWithManifest' } It "can use class from this module in New-Object by short name" { @@ -187,7 +189,7 @@ New-Object Foo class Foo {} New-Object Foo "@).Invoke() - $foo | Should Not Be $null + $foo | Should -Not -BeNullOrEmpty } # Pending reason: @@ -197,90 +199,90 @@ New-Object Foo using module @{ ModuleName = 'FooWithManifest'; ModuleVersion = '1.0' } New-Object Foo "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' + $foo.GetModuleName() | Should -BeExactly 'FooWithManifest' } Context 'parse time errors' { It "report an error about not found module" { $err = Get-ParseResults "using module ThisModuleDoesntExist" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'ModuleNotFoundDuringParse' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'ModuleNotFoundDuringParse' } It "report an error about misformatted module specification" { $err = Get-ParseResults "using module @{ Foo = 'Foo' }" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'RequiresModuleInvalid' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'RequiresModuleInvalid' } It "report an error about wildcard in the module name" { $err = Get-ParseResults "using module fo*" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'WildCardModuleNameError' } It "report an error about wildcard in the module path" { $err = Get-ParseResults "using module C:\fo*" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'WildCardModuleNameError' } It "report an error about wildcard in the module name inside ModuleSpecification hashtable" { $err = Get-ParseResults "using module @{ModuleName = 'Fo*'; RequiredVersion = '1.0'}" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'WildCardModuleNameError' } # MSFT:5246105 It "report an error when tokenizer encounters comma" { $err = Get-ParseResults "using module ,FooWithManifest" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'MissingUsingItemName' } It "report an error when tokenizer encounters nothing" { $err = Get-ParseResults "using module " - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'MissingUsingItemName' } It "report an error on badly formatted RequiredVersion" { $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; RequiredVersion = 1. }" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'RequiresModuleInvalid' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'RequiresModuleInvalid' } # MSFT:6897275 It "report an error on incomplete using input" { $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; FooWithManifest = 1." # missing closing bracket - $err.Count | Should Be 2 - $err[0].ErrorId | Should Be 'MissingEndCurlyBrace' - $err[1].ErrorId | Should Be 'RequiresModuleInvalid' + $err.Count | Should -Be 2 + $err[0].ErrorId | Should -BeExactly 'MissingEndCurlyBrace' + $err[1].ErrorId | Should -BeExactly 'RequiresModuleInvalid' } It "report an error when 'using module' terminating by NewLine" { $err = Get-ParseResults "using module" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'MissingUsingItemName' } It "report an error when 'using module' terminating by Semicolon" { $err = Get-ParseResults "using module; $testvar=1" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'MissingUsingItemName' } It "report an error when a value after 'using module' is a unallowed expression" { $err = Get-ParseResults "using module )" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'InvalidValueForUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'InvalidValueForUsingItemName' } It "report an error when a value after 'using module' is not a valid module name" { $err = Get-ParseResults "using module 123" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'InvalidValueForUsingItemName' + $err.Count | Should -Be 1 + $err[0].ErrorId | Should -BeExactly 'InvalidValueForUsingItemName' } } @@ -291,7 +293,7 @@ using module Foo using module FooWithManifest class Bar : Foo {} "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + $err.FullyQualifiedErrorId | Should -Be AmbiguousTypeReference } It "cannot use as [...]" { @@ -300,7 +302,7 @@ using module Foo using module FooWithManifest [Foo] "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + $err.FullyQualifiedErrorId | Should -BeExactly 'AmbiguousTypeReference' } It "cannot use in New-Object" { @@ -309,7 +311,7 @@ using module Foo using module FooWithManifest New-Object Foo "@ - $err.FullyQualifiedErrorId | Should Be 'AmbiguousTypeReference,Microsoft.PowerShell.Commands.NewObjectCommand' + $err.FullyQualifiedErrorId | Should -BeExactly 'AmbiguousTypeReference,Microsoft.PowerShell.Commands.NewObjectCommand' } It "cannot use [type] cast from string" { @@ -318,7 +320,7 @@ using module Foo using module FooWithManifest [type]"Foo" "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + $err.FullyQualifiedErrorId | Should -BeExactly 'AmbiguousTypeReference' } } @@ -333,11 +335,11 @@ using module Foo [Foo]::new().GetModuleName() "@).Invoke() - $moduleName | Should Be 'Foo2' + $moduleName | Should -BeExactly 'Foo2' } } - Context 'Side by side' { + Context 'Side by side' { BeforeAll { # Add side-by-side module $newVersion = '3.4.5' @@ -355,7 +357,7 @@ using module Foo using module FooWithManifest [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo230' + $foo.GetModuleName() | Should -BeExactly 'Foo230' } It "uses right version, when RequiredModule=1.0 specified" { @@ -363,7 +365,7 @@ using module FooWithManifest using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '1.0'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' + $foo.GetModuleName() | Should -BeExactly 'FooWithManifest' } It "uses right version, when RequiredModule=2.3.0 specified" { @@ -371,7 +373,7 @@ using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '1.0'} using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '2.3.0'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo230' + $foo.GetModuleName() | Should -BeExactly 'Foo230' } It "uses right version, when RequiredModule=3.4.5 specified" { @@ -379,7 +381,7 @@ using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '2.3.0'} using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '3.4.5'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo345' + $foo.GetModuleName() | Should -BeExactly 'Foo345' } } @@ -397,7 +399,7 @@ using module ModuleWithRuntimeError [Foo]::new().GetModuleName() "@ - $err | Should Be 'error' + $err | Should -Be 'error' } } @@ -414,26 +416,25 @@ function foo() } '@ # resolve name to absolute path - $scriptToProcessPath = (get-childitem $scriptToProcessPath).FullName - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $scriptToProcessPath = (Get-ChildItem $scriptToProcessPath).FullName + $iss = [initialsessionstate]::CreateDefault() $iss.StartupScripts.Add($scriptToProcessPath) $ps = [powershell]::Create($iss) - $ps.AddCommand("foo").Invoke() | Should be Foo - $ps.Streams.Error | Should Be $null + $ps.AddCommand("foo").Invoke() | Should -BeExactly 'Foo' + $ps.Streams.Error | Should -BeNullOrEmpty $ps1 = [powershell]::Create($iss) - $ps1.AddCommand("foo").Invoke() | Should be Foo - $ps1.Streams.Error | Should Be $null + $ps1.AddCommand("foo").Invoke() | Should -BeExactly 'Foo' + $ps1.Streams.Error | Should -BeNullOrEmpty $ps.Commands.Clear() $ps.Streams.Error.Clear() - $ps.AddScript(". foo").Invoke() | Should be Foo - $ps.Streams.Error | Should Be $null + $ps.AddScript(". foo").Invoke() | Should -BeExactly 'Foo' + $ps.Streams.Error | Should -BeNullOrEmpty } } - # here we are back to normal $env:PSModulePath, but all modules are there Context "Module by path" { BeforeAll { @@ -441,7 +442,7 @@ function foo() New-TestModule -Name FooForPaths -Content 'class Foo { [string] GetModuleName() { return "FooForPaths" } }' $env:PSModulePath = $originalPSModulePath - new-item -type directory -Force TestDrive:\FooRelativeConsumer + New-Item -type directory -Force TestDrive:\FooRelativeConsumer Set-Content -Path "${TestDrive}\FooRelativeConsumer\FooRelativeConsumer.ps1" -Value @' using module ..\modules\FooForPaths class Bar : Foo {} @@ -456,38 +457,38 @@ class Bar : Foo {} } It 'use non-modified PSModulePath' { - $env:PSModulePath | Should Be $originalPSModulePath + $env:PSModulePath | Should -BeExactly $originalPSModulePath } It "can be accessed by relative path" { $barObject = & TestDrive:\FooRelativeConsumer\FooRelativeConsumer.ps1 - $barObject.GetModuleName() | Should Be 'FooForPaths' + $barObject.GetModuleName() | Should -BeExactly 'FooForPaths' } It "cannot be accessed by relative path without .\ from a script" { $err = Get-RuntimeError '& TestDrive:\FooRelativeConsumerErr.ps1' - $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + $err.FullyQualifiedErrorId | Should -BeExactly 'ModuleNotFoundDuringParse' } It "can be accessed by absolute path" { - $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\modules)[0].FullName) + $resolvedTestDrivePath = Split-Path ((Get-ChildItem TestDrive:\modules)[0].FullName) $s = @" using module $resolvedTestDrivePath\FooForPaths [Foo]::new() "@ $err = Get-ParseResults $s - $err.Count | Should Be 0 + $err.Count | Should -Be 0 $barObject = [scriptblock]::Create($s).Invoke() - $barObject.GetModuleName() | Should Be 'FooForPaths' + $barObject.GetModuleName() | Should -BeExactly 'FooForPaths' } It "can be accessed by absolute path with file extension" { - $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\modules)[0].FullName) + $resolvedTestDrivePath = Split-Path ((Get-ChildItem TestDrive:\modules)[0].FullName) $barObject = [scriptblock]::Create(@" using module $resolvedTestDrivePath\FooForPaths\FooForPaths.psm1 [Foo]::new() "@).Invoke() - $barObject.GetModuleName() | Should Be 'FooForPaths' + $barObject.GetModuleName() | Should -BeExactly 'FooForPaths' } It "can be accessed by relative path without file" { @@ -496,7 +497,7 @@ using module $resolvedTestDrivePath\FooForPaths\FooForPaths.psm1 using module .\FooForPaths [Foo]::new() "@ - $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + $err.FullyQualifiedErrorId | Should -BeExactly 'ModuleNotFoundDuringParse' Push-Location TestDrive:\modules try { @@ -504,7 +505,7 @@ using module .\FooForPaths using module .\FooForPaths [Foo]::new() "@).Invoke() - $barObject.GetModuleName() | Should Be 'FooForPaths' + $barObject.GetModuleName() | Should -BeExactly 'FooForPaths' } finally { Pop-Location } @@ -517,11 +518,24 @@ using module .\FooForPaths using module FooForPaths [Foo]::new() "@ - $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + $err.FullyQualifiedErrorId | Should -BeExactly 'ModuleNotFoundDuringParse' } finally { Pop-Location } } + + It 'can be accessed by relative path with .' -TestCases @( + @{ Separator = '\' }, + @{ Separator = '/' } + ) { + param([string]$Separator) + $name = 'relative-slash-paths' + 'function Get-TestString { "Worked" }' | Set-Content "TestDrive:\modules\$name.psm1" + + "using module .$Separator$name.psm1; Get-TestString" | Set-Content "TestDrive:\modules\$name.ps1" + + & "TestDrive:\modules\$name.ps1" | Should -BeExactly "Worked" + } } Context "module has non-terminating error handled with 'SilentlyContinue'" { @@ -542,8 +556,7 @@ class TestClass { [string] GetName() { return "TestClass" } } using module $testFile [TestClass]::new() "@).Invoke() - $result.GetName() | Should Be "TestClass" + $result.GetName() | Should -BeExactly "TestClass" } } } - diff --git a/test/powershell/Language/Classes/scripting.enums.tests.ps1 b/test/powershell/Language/Classes/scripting.enums.tests.ps1 index daf8fbb432f..f11f294a0a5 100644 --- a/test/powershell/Language/Classes/scripting.enums.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.enums.tests.ps1 @@ -1,6 +1,5 @@ -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'enums' -Tags "CI" { Context 'basic enums' { @@ -11,11 +10,11 @@ Describe 'enums' -Tags "CI" { e2 } - It "has correct value 0" { [E1]::e0 | Should Be ([E1]0) } - It "has correct value 1" { [E1]::e1 | Should Be ([E1]1) } - It "has correct value 2" { [E1]::e2 | Should Be ([E1]2) } - It "cast from string" { [E1]::e1 | Should Be 'e1' } - It "cast to string" { 'e2' | Should Be ([E1]::e2) } + It "has correct value 0" { [E1]::e0 | Should -Be ([E1]0) } + It "has correct value 1" { [E1]::e1 | Should -Be ([E1]1) } + It "has correct value 2" { [E1]::e2 | Should -Be ([E1]2) } + It "cast from string" { [E1]::e1 | Should -Be 'e1' } + It "cast to string" { 'e2' | Should -Be ([E1]::e2) } } Context 'Basic enum with initial value' { @@ -26,11 +25,11 @@ Describe 'enums' -Tags "CI" { e2 } - It "has correct value 0" { [E2]::e0 | Should Be ([E2]0) } - It "has correct value 5" { [E2]::e1 | Should Be ([E2]5) } - It "has correct value 6" { [E2]::e2 | Should Be ([E2]6) } - It "cast from string" { [E2]::e1 | Should Be 'e1' } - It "cast to string" { 'e2' | Should Be ([E2]::e2) } + It "has correct value 0" { [E2]::e0 | Should -Be ([E2]0) } + It "has correct value 5" { [E2]::e1 | Should -Be ([E2]5) } + It "has correct value 6" { [E2]::e2 | Should -Be ([E2]6) } + It "cast from string" { [E2]::e1 | Should -Be 'e1' } + It "cast to string" { 'e2' | Should -Be ([E2]::e2) } } Context 'Basic enum with initial value expression' { @@ -42,12 +41,12 @@ Describe 'enums' -Tags "CI" { e3 = 1 # This shouldn't be an error even though previous member was max int } - It "has correct value 0" { [E3]::e0 | Should Be ([E3]0) } - It "has correct value 5" { [E3]::e1 | Should Be ([E3]5) } - It "has correct value [int]::MaxValue" { [E3]::e2 | Should Be ([E3]([int]::MaxValue)) } - It "has correct value 1" { [E3]::e3 | Should Be ([E3]1) } - It "cast from string" { [E3]::e2 | Should Be 'e2' } - It "cast to string" { 'e3' | Should Be ([E3]::e3) } + It "has correct value 0" { [E3]::e0 | Should -Be ([E3]0) } + It "has correct value 5" { [E3]::e1 | Should -Be ([E3]5) } + It "has correct value [int]::MaxValue" { [E3]::e2 | Should -Be ([E3]([int]::MaxValue)) } + It "has correct value 1" { [E3]::e3 | Should -Be ([E3]1) } + It "cast from string" { [E3]::e2 | Should -Be 'e2' } + It "cast to string" { 'e3' | Should -Be ([E3]::e3) } } Context 'Enum with complicated initial value' { @@ -67,9 +66,43 @@ Describe 'enums' -Tags "CI" { e0 =38 } - It 'E4 has correct value' { [E4]::e0 | Should Be ([E4]42) } - It 'E5 has correct value' { [E5]::e0 | Should Be ([E5]40) } - It 'E6 has correct value' { [E6]::e0 | Should Be ([E6]38) } + It 'E4 has correct value' { [E4]::e0 | Should -Be ([E4]42) } + It 'E5 has correct value' { [E5]::e0 | Should -Be ([E5]40) } + It 'E6 has correct value' { [E6]::e0 | Should -Be ([E6]38) } + } + + Context 'Enum with non-default underlying type' { + enum EX1 : byte { A;B;C;D } + enum EX2 : sbyte { A;B;C;D } + enum EX3 : short { A;B;C;D } + enum EX4 : ushort { A;B;C;D } + enum EX5 : int { A;B;C;D } + enum EX6 : uint { A;B;C;D } + enum EX7 : long { A;B;C;D } + enum EX8 : ulong { A;B;C;D } + + It 'EX1 has the specified underlying type' { [Enum]::GetUnderlyingType([EX1]) | Should -Be ([byte]) } + It 'EX2 has the specified underlying type' { [Enum]::GetUnderlyingType([EX2]) | Should -Be ([sbyte]) } + It 'EX3 has the specified underlying type' { [Enum]::GetUnderlyingType([EX3]) | Should -Be ([short]) } + It 'EX4 has the specified underlying type' { [Enum]::GetUnderlyingType([EX4]) | Should -Be ([ushort]) } + It 'EX5 has the specified underlying type' { [Enum]::GetUnderlyingType([EX5]) | Should -Be ([int]) } + It 'EX6 has the specified underlying type' { [Enum]::GetUnderlyingType([EX6]) | Should -Be ([uint]) } + It 'EX7 has the specified underlying type' { [Enum]::GetUnderlyingType([EX7]) | Should -Be ([long]) } + It 'EX8 has the specified underlying type' { [Enum]::GetUnderlyingType([EX8]) | Should -Be ([ulong]) } + } + + Context 'Enum with negative user-specified values' { + enum V1 { + A = -4 + B = [int]::MinValue + C + } + + It 'Negative values are correctly assigned to members' { + [V1]::A.value__ | Should -Be -4 + [V1]::B.value__ | Should -Be -2147483648 + [V1]::C.value__ | Should -Be -2147483647 + } } } @@ -83,9 +116,11 @@ Describe 'Basic enum errors' -Tags "CI" { ShouldBeParseError 'enum foo { x; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError ShouldBeParseError 'enum foo { X; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError ShouldBeParseError 'enum foo1 { x = [foo2]::x } enum foo2 { x = [foo1]::x }' CycleInEnumInitializers,CycleInEnumInitializers 0,28 -SkipAndCheckRuntimeError - ShouldBeParseError 'enum foo { e = [int]::MaxValue; e2 }' EnumeratorValueTooLarge 33 -SkipAndCheckRuntimeError - ShouldBeParseError 'enum foo { e = [int]::MaxValue + 1 }' EnumeratorValueTooLarge 15 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = [int]::MaxValue; e2 }' EnumeratorValueOutOfBounds 33 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = [int]::MaxValue + 1 }' EnumeratorValueOutOfBounds 15 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo : byte { e = -1 }' EnumeratorValueOutOfBounds 22 -SkipAndCheckRuntimeError ShouldBeParseError 'enum foo { e = $foo }' EnumeratorValueMustBeConstant 15 -SkipAndCheckRuntimeError ShouldBeParseError 'enum foo { e = "hello" }' CannotConvertValue 15 -SkipAndCheckRuntimeError ShouldBeParseError 'enum foo { a;b;c;' MissingEndCurlyBrace 10 + ShouldBeParseError 'enum foo : string { a }' InvalidUnderlyingType 11 -SkipAndCheckRuntimeError } diff --git a/test/powershell/Language/CompletionTestSupport.psm1 b/test/powershell/Language/CompletionTestSupport.psm1 index a15b57a4cec..a5942d4a259 100644 --- a/test/powershell/Language/CompletionTestSupport.psm1 +++ b/test/powershell/Language/CompletionTestSupport.psm1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. class CompletionResult { @@ -111,7 +113,7 @@ function Test-Completions { It "Checking for duplicates of: $($expected.CompletionText)" { # We should only find 1 of each expected result - $expected.Found | Should Be $false + $expected.Found | Should -BeFalse } $expected.Found = $true } @@ -121,7 +123,7 @@ function Test-Completions foreach ($expected in $test.ExpectedResults) { It "Checking for presence of expected result: $($expected.CompletionText)" { - $expected.Found | Should Be $true + $expected.Found | Should -BeTrue } } @@ -130,7 +132,7 @@ function Test-Completions foreach ($result in $results.CompletionMatches) { It "Checking for results that should not be found: $notExpected" { - $result.CompletionText -cne $notExpected | Should Be $true + $result.CompletionText | Should -Not -Be $notExpected } } } diff --git a/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 b/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 index 9cc0b6fccd4..65b9e61a6a7 100644 --- a/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 +++ b/test/powershell/Language/Interop/DotNet/DotNetAPI.Tests.ps1 @@ -1,25 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + Describe "DotNetAPI" -Tags "CI" { - $posh_E = 2.718281828459045 - $posh_pi = 3.14159265358979 It "Should be able to use static .NET classes and get a constant" { - [System.Math]::E | Should Match $posh_E.ToString() - [System.Math]::PI | Should Match $posh_pi.ToString() + [System.Math]::E | Should -Be 2.718281828459045 + [System.Math]::PI | Should -Be 3.141592653589793 } It "Should be able to invoke a method" { - [System.Environment]::GetEnvironmentVariable("PATH") | Should Be $env:PATH + [System.Environment]::GetEnvironmentVariable("PATH") | Should -Be $env:PATH } It "Should not require 'system' in front of static classes" { - [Environment]::CommandLine | Should Be ([System.Environment]::CommandLine) - - [Math]::E | Should Be ([System.Math]::E) + [Environment]::CommandLine | Should -Be ([System.Environment]::CommandLine) + [Math]::E | Should -Be ([System.Math]::E) } It "Should be able to create a new instance of a .Net object" { - [System.Guid]$guidVal = [System.Guid]::NewGuid() + [System.Guid]$guidVal = [System.Guid]::NewGuid() + $guidVal | Should -BeOfType Guid + } - $guidVal | Should BeOfType Guid + It "Should access types in System.Console" { + $type = "System.Console" -as [type] + $type.GetTypeInfo().FullName | Should -BeExactly "System.Console" } } diff --git a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 index b8a609008f6..42426b8279a 100644 --- a/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 +++ b/test/powershell/Language/Interop/DotNet/DotNetInterop.Tests.ps1 @@ -1,5 +1,318 @@ -Describe ".NET class interoperability" -Tags "CI" { - It "Should access types in System.Console" { - [System.Console]::TreatControlCAsInput | Should Be $false +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Handle ByRef-like types gracefully" -Tags "CI" { + + BeforeAll { + $code = @' +using System; +using System.Management.Automation; +namespace DotNetInterop +{ + public class Test + { + public Span this[int i] + { + get { return default(Span); } + set { DoNothing(value); } + } + + public static Span Space + { + get { return default(Span); } + set { DoNothing(value); } + } + + public Span Room + { + get { return default(Span); } + set { DoNothing(value); } + } + + private static void DoNothing(Span param) + { + } + + public string PrintMySpan(string str, ReadOnlySpan mySpan = default) + { + if (mySpan.Length == 0) + { + return str; + } + else + { + return str + mySpan.Length; + } + } + + public Span GetSpan(int[] array) + { + return array.AsSpan(); + } + } + + public class Test2 + { + public Test2(ReadOnlySpan span) + { + name = $"Number of chars: {span.Length}"; + } + + public Test2() {} + + private string name = "Hello World"; + public string this[ReadOnlySpan span] + { + get { return name; } + set { name = value; } + } + + public string Name => name; + } + + public class CodeMethods + { + public static ReadOnlySpan GetProperty(PSObject instance) + { + return default(ReadOnlySpan); + } + + public static void SetProperty(PSObject instance, ReadOnlySpan span) + { + } + + public static string RunMethod(PSObject instance, string str, ReadOnlySpan span) + { + return str + span.Length; + } + } + + public ref struct MyByRefLikeType + { + public MyByRefLikeType(int i) { } + public static int Index; + } + + public class ExampleProblemClass + { + public void ProblemMethod(ref MyByRefLikeType value) + { + } + } +} +'@ + if (-not ("DotNetInterop.Test" -as [type])) + { + Add-Type -TypeDefinition $code -IgnoreWarnings + } + + $testObj = [DotNetInterop.Test]::new() + $test2Obj = [DotNetInterop.Test2]::new() + } + + It "New-Object should fail gracefully when used for a ByRef-like type" { + { New-Object -TypeName 'System.Span[string]' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand" + { New-Object -TypeName 'DotNetInterop.MyByRefLikeType' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand" + } + + It "The 'new' method call should fail gracefully when used on a ByRef-like type" { + { [System.Span[string]]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType" + { [DotNetInterop.MyByRefLikeType]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType" + } + + It "Calling constructor of a ByRef-like type via dotnet adapter should fail gracefully - " -TestCases @( + @{ Number = 1; Script = { [System.Span[string]]::new.Invoke([ref]$null) } } + @{ Number = 2; Script = { [DotNetInterop.MyByRefLikeType]::new.Invoke(2) } } + ) { + param($Script) + $expectedError = $null + try { + & $Script + } catch { + $expectedError = $_ + } + + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotInstantiateBoxedByRefLikeType" + } + + It "Cast to a ByRef-like type should fail gracefully" { + { [System.Span[int]] ([int[]]1,2,3) } | Should -Throw -ErrorId "InvalidCastToByRefLikeType" + { [DotNetInterop.MyByRefLikeType] "text" } | Should -Throw -ErrorId "InvalidCastToByRefLikeType" + } + + It "LanguagePrimitives.ConvertTo should fail gracefully for a ByRef-like type ''" -TestCases @( + @{ Name = "Span"; Type = [System.Span[int]] } + @{ Name = "MyByRefLikeType"; Type = [DotNetInterop.MyByRefLikeType] } + ) { + param($Type) + $expectedError = $null + try { + [System.Management.Automation.LanguagePrimitives]::ConvertTo(([int[]]1,2,3), $Type) + } catch { + $expectedError = $_ + } + + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "InvalidCastToByRefLikeType" + } + + It "Getting value of a ByRef-like type instance property should not throw and should return null, even in strict mode - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { [System.Text.Encoding]::ASCII.Preamble } } + @{ Mechanism = "Dotnet-Adapter"; Script = { [System.Text.Encoding]::ASCII.PSObject.Properties["Preamble"].Value } } + ) { + param($Script) + + try { + Set-StrictMode -Version 3.0 + & $Script | Should -Be $null + } finally { + Set-StrictMode -Off + } + } + + It "Setting value of a ByRef-like type instance property should fail gracefully - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { $testObj.Room = [int[]](1,2,3) } } + @{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.PSObject.Properties["Room"].Value = [int[]](1,2,3) } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField" + } + + It " value of a ByRef-like type static property should fail gracefully" -TestCases @( + @{ Action = "Getting"; Script = { [DotNetInterop.Test]::Space } } + @{ Action = "Setting"; Script = { [DotNetInterop.Test]::Space = "blah" } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField" + } + + It "Invoke a method with optional ByRef-like parameter could work" { + $testObj.PrintMySpan("Hello") | Should -BeExactly "Hello" + } + + It "Invoke a method with ByRef-like parameter should fail gracefully - " -TestCases @( + @{ Mechanism = "Compiler/Binder"; Script = { $testObj.PrintMySpan("Hello", 1) } } + @{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.psobject.Methods["PrintMySpan"].Invoke("Hello", 1) } } + ) { + param($Script) + $Script | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument" + } + + It "Invoke a method with ByRef-like return type should fail gracefully - Compiler/Binder" { + { $testObj.GetSpan([int[]]@(1,2,3)) } | Should -Throw -ErrorId "CannotCallMethodWithByRefLikeReturnType" + } + + It "Invoke a method with ByRef-like return type should fail gracefully - Dotnet-Adapter" { + $expectedError = $null + try { + $testObj.psobject.Methods["GetSpan"].Invoke([int[]]@(1,2,3)) + } catch { + $expectedError = $_ + } + $expectedError | Should -Not -BeNullOrEmpty + $expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotCallMethodWithByRefLikeReturnType" + } + + It "Access static property of a ByRef-like type" { + [DotNetInterop.MyByRefLikeType]::Index = 10 + [DotNetInterop.MyByRefLikeType]::Index | Should -Be 10 + } + + It "Get access of an indexer that returns ByRef-like type should return null in no-strict mode" { + $testObj[1] | Should -Be $null + } + + It "Get access of an indexer that returns ByRef-like type should fail gracefully in strict mode" { + try { + Set-StrictMode -Version 3.0 + { $testObj[1] } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType" + } finally { + Set-StrictMode -Off + } + } + + It "Set access of an indexer that accepts ByRef-like type value should fail gracefully" { + { $testObj[1] = 1 } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType" + } + + It "Create instance of type with method that use a ByRef-like type as a ByRef parameter" { + $obj = [DotNetInterop.ExampleProblemClass]::new() + $obj | Should -BeOfType DotNetInterop.ExampleProblemClass + } + + Context "Passing value that is implicitly/explicitly castable to ByRef-like parameter in method invocation" { + + BeforeAll { + $ps = [powershell]::Create() + + # Define the CodeMethod 'RunTest' + $ps.AddCommand("Update-TypeData"). + AddParameter("TypeName", "DotNetInterop.Test2"). + AddParameter("MemberType", "CodeMethod"). + AddParameter("MemberName", "RunTest"). + AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('RunMethod')).Invoke() + $ps.Commands.Clear() + + # Define the CodeProperty 'TestName' + $ps.AddCommand("Update-TypeData"). + AddParameter("TypeName", "DotNetInterop.Test2"). + AddParameter("MemberType", "CodeProperty"). + AddParameter("MemberName", "TestName"). + AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('GetProperty')). + AddParameter("SecondValue", [DotNetInterop.CodeMethods].GetMethod('SetProperty')).Invoke() + $ps.Commands.Clear() + + $ps.AddScript('$test = [DotNetInterop.Test2]::new()').Invoke() + $ps.Commands.Clear() + } + + AfterAll { + $ps.Dispose() + } + + It "Support method calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" { + $testObj.PrintMySpan("abc", "def") | Should -BeExactly "abc3" + $testObj.PrintMySpan("abc", "Hello".ToCharArray()) | Should -BeExactly "abc5" + { $testObj.PrintMySpan("abc", 12) } | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument" + + $path = [System.IO.Path]::GetTempPath() + [System.IO.Path]::IsPathRooted($path.ToCharArray()) | Should -BeTrue + } + + It "Support constructor calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" { + $result = [DotNetInterop.Test2]::new("abc") + $result.Name | Should -BeExactly "Number of chars: 3" + + { [DotNetInterop.Test2]::new(12) } | Should -Throw -ErrorId "MethodCountCouldNotFindBest" + } + + It "Support indexing operation with ByRef-like index as long as the argument can be casted to the ByRef-like type" { + $test2Obj["abc"] | Should -BeExactly "Hello World" + $test2Obj["abc"] = "pwsh" + $test2Obj["abc"] | Should -BeExactly "pwsh" + } + + It "Support CodeMethod with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" { + $result = $ps.AddScript('$test.RunTest("Hello", "World".ToCharArray())').Invoke() + $ps.Commands.Clear() + $result.Count | Should -Be 1 + $result[0] | Should -Be 'Hello5' + } + + It "Return null for getter access of a CodeProperty that returns a ByRef-like type, even in strict mode" { + $result = $ps.AddScript( + 'try { Set-StrictMode -Version 3.0; $test.TestName } finally { Set-StrictMode -Off }').Invoke() + $ps.Commands.Clear() + $result.Count | Should -Be 1 + $result[0] | Should -Be $null + } + + It "Fail gracefully for setter access of a CodeProperty that returns a ByRef-like type" { + $result = $ps.AddScript('$test.TestName = "Hello"').Invoke() + $ps.Commands.Clear() + $result.Count | Should -Be 0 + $ps.Streams.Error[0].FullyQualifiedErrorId | Should -Be "CannotAccessByRefLikePropertyOrField" + } } } diff --git a/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 b/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 index c3357854ac8..ddc6498eb6d 100644 --- a/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 @@ -1,4 +1,6 @@ -Describe "ComparisonOperator" -tag "CI" { +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe "ComparisonOperator" -Tag "CI" { It "Should be for " -TestCases @( @{lhs = 1; operator = "-lt"; rhs = 2; result = $true}, @@ -19,7 +21,7 @@ Describe "ComparisonOperator" -tag "CI" { @{lhs = 0; operator = "-or"; rhs = 0; result = $false} ) { param($lhs, $operator, $rhs, $result) - Invoke-Expression "$lhs $operator $rhs" | Should Be $result + Invoke-Expression "$lhs $operator $rhs" | Should -Be $result } It "Should be for " -TestCases @( @@ -31,7 +33,7 @@ Describe "ComparisonOperator" -tag "CI" { @{operator = "!"; rhs = "0"; result = $true} ) { param($operator, $rhs, $result) - Invoke-Expression "$operator$rhs" | Should Be $result + Invoke-Expression "$operator$rhs" | Should -Be $result } It "Should be for " -TestCases @( @@ -46,7 +48,7 @@ Describe "ComparisonOperator" -tag "CI" { @{lhs = "'Hello world'"; operator = "-notlike"; rhs = "'Hello*'"; result = $false} ) { param($lhs, $operator, $rhs, $result) - Invoke-Expression "$lhs $operator $rhs" | Should Be $result + Invoke-Expression "$lhs $operator $rhs" | Should -Be $result } It "Should return error if right hand is not a valid type: 'hello' " -TestCases @( @@ -56,7 +58,7 @@ Describe "ComparisonOperator" -tag "CI" { @{operator = "-isnot"; type = "[foo]"; expectedError='TypeNotFound,Microsoft.PowerShell.Commands.InvokeExpressionCommand'} ) { param($operator, $type, $expectedError) - { Invoke-Expression "'Hello' $operator $type" } | ShouldBeErrorId $expectedError + { Invoke-Expression "'Hello' $operator $type" } | Should -Throw -ErrorId $expectedError } It "Should succeed in comparing type: " -TestCases @( @@ -69,7 +71,7 @@ Describe "ComparisonOperator" -tag "CI" { @{lhs = '"hello"'; operator = '-isnot'; rhs = "[int]"} ) { param($lhs, $operator, $rhs) - Invoke-Expression "$lhs $operator $rhs" | Should Be $true + Invoke-Expression "$lhs $operator $rhs" | Should -BeTrue } It "Should fail in comparing type: " -TestCases @( @@ -78,45 +80,60 @@ Describe "ComparisonOperator" -tag "CI" { @{lhs = '"hello"'; operator = '-isnot'; rhs = "[string]"} ) { param($lhs, $operator, $rhs) - Invoke-Expression "$lhs $operator $rhs" | Should Be $false + 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" { +Describe "Bytewise Operator" -Tag "CI" { It "Test -bor on enum with [byte] as underlying type" { $result = [System.Security.AccessControl.AceFlags]::ObjectInherit -bxor ` [System.Security.AccessControl.AceFlags]::ContainerInherit - $result.ToString() | Should Be "ObjectInherit, ContainerInherit" + $result.ToString() | Should -BeExactly "ObjectInherit, ContainerInherit" } It "Test -bor on enum with [int] as underlying type" { $result = [System.Management.Automation.CommandTypes]::Alias -bor ` [System.Management.Automation.CommandTypes]::Application - $result.ToString() | Should Be "Alias, Application" + $result.ToString() | Should -BeExactly "Alias, Application" } It "Test -band on enum with [byte] as underlying type" { $result = [System.Security.AccessControl.AceFlags]::ObjectInherit -band ` [System.Security.AccessControl.AceFlags]::ContainerInherit - $result.ToString() | Should Be "None" + $result.ToString() | Should -BeExactly "None" } It "Test -band on enum with [int] as underlying type" { $result = [System.Management.Automation.CommandTypes]::Alias -band ` [System.Management.Automation.CommandTypes]::All - $result.ToString() | Should Be "Alias" + $result.ToString() | Should -BeExactly "Alias" } It "Test -bxor on enum with [byte] as underlying type" { $result = [System.Security.AccessControl.AceFlags]::ObjectInherit -bxor ` [System.Security.AccessControl.AceFlags]::ContainerInherit - $result.ToString() | Should Be "ObjectInherit, ContainerInherit" + $result.ToString() | Should -BeExactly "ObjectInherit, ContainerInherit" } It "Test -bxor on enum with [int] as underlying type" { $result = [System.Management.Automation.CommandTypes]::Alias -bxor ` [System.Management.Automation.CommandTypes]::Application - $result.ToString() | Should Be "Alias, Application" + $result.ToString() | Should -BeExactly "Alias, Application" } } diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 new file mode 100644 index 00000000000..237b4d8c987 --- /dev/null +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -0,0 +1,372 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'NullCoalesceOperations' -Tags 'CI' { + BeforeAll { + $someGuid = New-Guid + $typesTests = @( + @{ name = 'string'; valueToSet = 'hello' } + @{ name = 'dotnetType'; valueToSet = $someGuid } + @{ name = 'byte'; valueToSet = [byte]0x94 } + @{ name = 'intArray'; valueToSet = 1..2 } + @{ name = 'stringArray'; valueToSet = 'a'..'c' } + @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } + ) + } + + Context "Null conditional assignment operator ??=" { + It 'Variable doesnot exist' { + + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force + + $variableDoesNotExist ??= 1 + $variableDoesNotExist | Should -Be 1 + + $variableDoesNotExist ??= 2 + $variableDoesNotExist | Should -Be 1 + } + + It 'Variable exists and is null' { + $variableDoesNotExist = $null + + $variableDoesNotExist ??= 2 + $variableDoesNotExist | Should -Be 2 + } + + It 'Validate types - can be set' -TestCases $typesTests { + param ($name, $valueToSet) + + $x = $null + $x ??= $valueToSet + $x | Should -Be $valueToSet + } + + It 'Validate hashtable can be set' { + $x = $null + $x ??= @{ 1 = '1' } + $x.Keys | Should -Be @(1) + } + + It 'Validate lhs is returned' { + $x = 100 + $x ??= 200 + $x | Should -Be 100 + } + + It 'Rhs is a cmdlet' { + $x = $null + $x ??= (Get-Alias -Name 'where') + $x.Definition | Should -BeExactly 'Where-Object' + } + + It 'Lhs is DBNull' { + $x = [System.DBNull]::Value + $x ??= 200 + $x | Should -Be ([System.DBNull]::Value) + } + + It 'Lhs is AutomationNull' { + $x = [System.Management.Automation.Internal.AutomationNull]::Value + $x ??= 200 + $x | Should -Be 200 + } + + It 'Lhs is NullString' { + $x = [NullString]::Value + $x ??= 200 + $x | Should -Be ([NullString]::Value) + } + + It 'Lhs is empty string' { + $x = '' + $x ??= 20 + $x | Should -BeExactly '' + } + + It 'Error case' { + $e = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ??= 100', [ref] $null, [ref] $e) + $e[0].ErrorId | Should -BeExactly 'InvalidLeftHandSide' + } + + It 'Variable is non-null' { + $num = 10 + $num ??= 20 + + $num | Should -Be 10 + } + + It 'Lhs is $?' { + { $???=$false} + $? | Should -BeTrue + } + } + + Context 'Null coalesce operator ??' { + BeforeEach { + $x = $null + } + + It 'Variable does not exist' { + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force + $variableDoesNotExist ?? 100 | Should -Be 100 + } + + It 'Variable exists but is null' { + $x ?? 100 | Should -Be 100 + } + + It 'Lhs is not null' { + $x = 100 + $x ?? 200 | Should -Be 100 + } + + It 'Lhs is a non-null constant' { + 1 ?? 2 | Should -Be 1 + } + + It 'Lhs is `$null' { + $null ?? 'string value' | Should -BeExactly 'string value' + } + + It 'Check precedence of ?? expression resolution' { + $x ?? $null ?? 100 | Should -Be 100 + $null ?? $null ?? 100 | Should -Be 100 + $null ?? $null ?? $null | Should -Be $null + $x ?? 200 ?? $null | Should -Be 200 + $x ?? 200 ?? 300 | Should -Be 200 + 100 ?? $x ?? 200 | Should -Be 100 + $null ?? 100 ?? $null ?? 200 | Should -Be 100 + } + + It 'Rhs is a cmdlet' { + $result = $x ?? (Get-Alias -Name 'where') + $result.Definition | Should -BeExactly 'Where-Object' + } + + It 'Lhs is DBNull' { + $x = [System.DBNull]::Value + $x ?? 200 | Should -Be ([System.DBNull]::Value) + } + + It 'Lhs is AutomationNull' { + $x = [System.Management.Automation.Internal.AutomationNull]::Value + $x ?? 200 | Should -Be 200 + } + + It 'Lhs is NullString' { + $x = [NullString]::Value + $x ?? 200 | Should -Be ([NullString]::Value) + } + + It 'Rhs is a get variable expression' { + $x = $null + $y = 2 + $x ?? $y | Should -Be 2 + } + + It 'Lhs is a constant' { + [System.DBNull]::Value ?? 2 | Should -Be ([System.DBNull]::Value) + } + + It 'Lhs is $?' { + {$???$false} | Should -BeTrue + } + + It 'Should only evaluate LHS once when it IS null' { + $testState = [pscustomobject]@{ Value = 0 } + (& { [void]$testState.Value++ }) ?? 'Nothing' | Should -BeExactly 'Nothing' + $testState.Value | Should -Be 1 + } + + It 'Should only evaluate LHS once when it is NOT null' { + $testState = [pscustomobject]@{ Value = 0 } + (& { 'Test'; [void]$testState.Value++ }) ?? 'Nothing' | Should -BeExactly 'Test' + $testState.Value | Should -Be 1 + } + } + + Context 'Null Coalesce ?? operator precedence' { + It '?? precedence over -and' { + $true -and $null ?? $true | Should -BeTrue + } + + It '?? precedence over -band' { + 1 -band $null ?? 1 | Should -Be 1 + } + + It '?? precedence over -eq' { + 'x' -eq $null ?? 'x' | Should -BeTrue + $null -eq $null ?? 'x' | Should -BeFalse + } + + It '?? precedence over -as' { + 'abc' -as [datetime] ?? 1 | Should -BeNullOrEmpty + } + + It '?? precedence over -replace' { + 'x' -replace 'x',$null ?? 1 | Should -Be ([string]::empty) + } + + It '+ precedence over ??' { + 2 + $null ?? 3 | Should -Be 2 + } + + It '* precedence over ??' { + 2 * $null ?? 3 | Should -Be 0 + } + + It '-f precedence over ??' { + "{0}" -f $null ?? 'b' | Should -Be ([string]::empty) + } + + It '.. precedence ove ??' { + 1..$null ?? 2 | Should -BeIn 1,0 + } + } + + Context 'Combined usage of null conditional operators' { + + BeforeAll { + function GetNull { + return $null + } + + function GetHello { + return "Hello" + } + } + + BeforeEach { + $x = $null + } + + It '?? and ??= used together' { + $x ??= 100 ?? 200 + $x | Should -Be 100 + } + + It '?? and ??= chaining' { + $x ??= $x ?? (GetNull) ?? (GetHello) + $x | Should -BeExactly 'Hello' + } + + It 'First two are null' { + $z ??= $null ?? 100 + $z | Should -Be 100 + } + } +} + +Describe 'NullConditionalMemberAccess' -Tag 'CI' { + + Context '?. operator tests' { + BeforeAll { + $psObj = [psobject]::new() + $psObj | Add-Member -Name 'name' -Value 'value' -MemberType NoteProperty + $psObj | Add-Member -Name 'nested' -Value @{name = 'valuenested'} -MemberType NoteProperty + + $psobj2 = [psobject]::new() + $psobj2 | Add-Member -Name 'GetHello' -Value { "hello" } -MemberType ScriptMethod + $psObj | Add-Member -Name 'nestedMethod' -Value $psobj2 -MemberType NoteProperty + + $array = 1..3 + $hash = @{ a = 1; b = 2} + + $null = New-Item -ItemType File -Path "$TestDrive/testfile.txt" -Force + } + + It 'Can get member value of a non-null variable' { + ${psObj}?.name | Should -BeExactly 'value' + ${array}?.length | Should -Be 3 + ${hash}?.a | Should -Be 1 + + (Get-Item $TestDrive)?.EnumerateFiles()?.Name | Should -BeExactly 'testfile.txt' + + [int32]::MaxValue?.ToString() | Should -BeExactly '2147483647' + } + + It 'Can get null when variable is null' { + ${nonExistent}?.name | Should -BeNullOrEmpty + ${nonExistent}?.MyMethod() | Should -BeNullOrEmpty + + (get-process -Name doesnotexist -ErrorAction SilentlyContinue)?.Id | Should -BeNullOrEmpty + } + + It 'Use ?. operator multiple times in statement' { + ${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' + } + + It 'Use ?. on a dynamic method name' { + $methodName = 'ToLongDateString' + (Get-Date '11/11/2019')?.$methodName() | Should -BeExactly 'Monday, November 11, 2019' + + ${doesNotExist}?.$methodName() | Should -BeNullOrEmpty + } + + It 'Use ?. on a dynamic method name that does not exist' { + $methodName = 'DoesNotExist' + { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' + } + + It 'Use ?. on a dynamic method name that does not exist' { + $methodName = $null + { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' + } + + It 'Use ?. on a dynamic property name' { + $propName = 'SI' + (Get-Process -Id $PID)?.$propName | Should -Be (Get-Process -id $PID).SessionId + + ${doesNotExist}?.$propName() | Should -BeNullOrEmpty + } + + It 'Should throw error when method does not exist' { + { ${psObj}?.nestedMethod?.NonExistent() } | Should -Throw -ErrorId 'MethodNotFound' + } + } + + Context '?[] operator tests' { + BeforeAll { + $array = 1..3 + $hash = @{ a = 1; b = 2} + + $dateArray = @( + (Get-Date '11/1/2019'), + (Get-Date '11/2/2019'), + (Get-Date '11/3/2019')) + } + + It 'Can index can call properties' { + ${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 + } + + It 'Indexing in null items should be null' { + ${doesnotExist}?[0] | Should -BeNullOrEmpty + ${doesnotExist}?[0,1] | Should -BeNullOrEmpty + ${doesnotExist}?[0..2] | Should -BeNullOrEmpty + ${doesnotExist}?[-2] | Should -BeNullOrEmpty + + ${doesnotExist}?['a'] | Should -BeNullOrEmpty + } + + It 'Can call methods on indexed items' { + ${dateArray}?[0]?.ToLongDateString() | Should -BeExactly 'Friday, November 1, 2019' + } + + It 'Calling a method on nonexistent item give null' { + ${dateArray}?[1234]?.ToLongDateString() | Should -BeNullOrEmpty + ${doesNotExist}?[0]?.MyGetMethod() | Should -BeNullOrEmpty + } + } +} diff --git a/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 b/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 new file mode 100644 index 00000000000..15340dd18e6 --- /dev/null +++ b/test/powershell/Language/Operators/PipelineChainOperator.Tests.ps1 @@ -0,0 +1,346 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Experimental Feature: && and || operators - Feature-Enabled" -Tag CI { + BeforeAll { + function Test-SuccessfulCommand + { + Write-Output "SUCCESS" + } + + filter Test-NonTerminatingError + { + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + [object[]] + $input + ) + + if ($input -ne 2) + { + return $input + } + + $exception = [System.Exception]::new("NTERROR") + $errorId = 'NTERROR' + $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified + + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) + $PSCmdlet.WriteError($errorRecord) + } + + $simpleTestCases = @( + # Two native commands + @{ Statement = 'testexe -returncode -1 && testexe -echoargs "A"'; Output = @('-1') } + @{ Statement = '& "testexe" -returncode -1 && & "testexe" -echoargs "A"'; Output = @('-1') } + @{ Statement = 'testexe -returncode -1 || testexe -echoargs "A"'; Output = '-1','Arg 0 is ' } + @{ Statement = 'testexe -returncode 0 && testexe -echoargs "A"'; Output = '0','Arg 0 is ' } + @{ Statement = 'testexe -returncode 0 || testexe -echoargs "A"'; Output = @('0') } + @{ Statement = 'testexe -returncode 0 > $null && testexe -returncode 1'; Output = @('1') } + @{ Statement = 'testexe -returncode 0 && testexe -echoargs "A" > $null'; Output = @('0') } + + # Three native commands + @{ Statement = 'testexe -returncode -1 && testexe -returncode -2 && testexe -echoargs "A"'; Output = @('-1') } + @{ Statement = 'testexe -echoargs "A" && testexe -returncode -2 && testexe -echoargs "A"'; Output = @('Arg 0 is ', '-2') } + @{ Statement = 'testexe -echoargs "A" && testexe -returncode -2 || testexe -echoargs "B"'; Output = @('Arg 0 is ', '-2', 'Arg 0 is ') } + @{ 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 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') } + @{ Statement = 'testexe -returncode 1 && Test-SuccessfulCommand'; Output = @('1') } + + # Native command and non-terminating unsuccessful cmdlet + @{ Statement = '1,2 | Test-NonTerminatingError && testexe -returncode 0'; Output = @(1) } + @{ Statement = 'testexe -returncode 0 && 1,2 | Test-NonTerminatingError'; Output = @('0', 1) } + @{ Statement = '1,2 | Test-NonTerminatingError || testexe -returncode 0'; Output = @(1, '0') } + @{ Statement = 'testexe -returncode 0 || 1, 2 | Test-NonTerminatingError'; Output = @('0') } + + # Expression and native command + @{ Statement = '"hi" && testexe -returncode 0'; Output = @('hi', '0') } + @{ Statement = 'testexe -returncode 0 && "Hi"'; Output = @('0', 'Hi') } + @{ Statement = '"hi" || testexe -returncode 0'; Output = @('hi') } + @{ Statement = 'testexe -returncode 0 || "hi"'; Output = @('0') } + @{ Statement = '"hi" && testexe -returncode 1'; Output = @('hi', '1') } + @{ Statement = 'testexe -returncode 1 && "Hi"'; Output = @('1') } + @{ Statement = 'testexe -returncode 1 || "hi"'; Output = @('1', 'hi') } + @{ Statement = '"hello" && get-item macarena_doesnotexist || "there"; "hello" || "there"'; Output = @('hello', 'there', 'hello') } + @{ Statement = '1 + 1 && "Hi"'; Output = @(2, 'Hi') } + + # Pipeline and native command + @{ Statement = '1,2,3 | % { $_ + 1 } && testexe -returncode 0'; Output = @('2','3','4','0') } + @{ Statement = 'testexe -returncode 0 && 1,2,3 | % { $_ + 1 }'; Output = @('0','2','3','4') } + @{ Statement = 'testexe -returncode 1 && 1,2,3 | % { $_ + 1 }'; Output = @('1') } + @{ Statement = 'testexe -returncode 1 || 1,2,3 | % { $_ + 1 }'; Output = @('1','2','3','4') } + @{ Statement = 'testexe -returncode 1 | % { [int]$_ + 1 } && testexe -returncode 0'; Output = @('2') } + @{ Statement = 'testexe -returncode 1 | % { [int]$_ + 1 } || testexe -returncode 0'; Output = @('2', '0') } + @{ Statement = '0,1 | % { testexe -returncode $_ } && testexe -returncode 0'; Output = @('0','1','0') } + @{ Statement = '1,2 | % { testexe -returncode $_ } && testexe -returncode 0'; Output = @('1','2','0') } + + # Subpipeline and subexpression cases + @{ Statement = '(testexe -returncode 0 && testexe -returncode 1) && "Hi"'; Output = @('0','1') } + @{ Statement = '$(testexe -returncode 0 && testexe -returncode 1) && "Hi"'; Output = @('0','1') } + @{ Statement = '(testexe -returncode 0 && testexe -returncode 1) || "Bad"'; Output = @('0','1','Bad') } + @{ Statement = '$(testexe -returncode 0 && testexe -returncode 1) && "Bad"'; Output = @('0','1') } + @{ Statement = '(testexe -returncode 1 || testexe -returncode 1) && "Hi"'; Output = @('1','1') } + + # Control flow statements + @{ Statement = 'foreach ($v in 0,1,2) { testexe -returncode $v || $(break) }'; Output = @('0', '1') } + @{ Statement = 'foreach ($v in 0,1,2) { testexe -returncode $v || $(continue); $v + 1 }'; Output = @('0', 1, '1', '2') } + + # Use in conditionals + @{ Statement = 'if ($false && $true) { "Hi" }'; Output = 'Hi' } + @{ Statement = 'if ("Hello" && testexe -return 1) { $?; 1 } else { throw "else" }'; Output = $true,1 } + @{ Statement = 'if ("Hello" && Write-Error "Bad" -ErrorAction Ignore) { $?; 1 } else { throw "else" }'; Output = $true,1 } + @{ Statement = 'if (Write-Error "Bad" -ErrorAction Ignore && "Hello") { throw "if" } else { $?; 1 }'; Output = $true,1 } + @{ Statement = 'if ($y = $x = "Hello" && testexe -returncode 1) { $?; $x; $y } else { throw "else" }'; Output = $true,'Hello',1,'Hello',1 } + ) + + $variableTestCases = @( + @{ Statement = '$x = testexe -returncode 0 && testexe -returncode 1'; Variables = @{ x = '0','1' } } + @{ Statement = '$x = testexe -returncode 0 || testexe -returncode 1'; Variables = @{ x = '0' } } + @{ Statement = '$x = testexe -returncode 1 || testexe -returncode 0'; Variables = @{ x = '1','0' } } + @{ Statement = '$x = testexe -returncode 1 && testexe -returncode 0'; Variables = @{ x = '1' } } + @{ Statement = '$x = @(1); $x += testexe -returncode 0 && testexe -returncode 1'; Variables = @{ x = 1,'0','1' } } + @{ Statement = '$x = $y = testexe -returncode 0 && testexe -returncode 1'; Variables = @{ x = '0','1'; y = '0','1' } } + @{ Statement = '$x, $y = $z = testexe -returncode 0 && testexe -returncode 1'; Variables = @{ x = '0'; y = '1'; z = '0','1' } } + @{ Statement = '$x = @(1); $v = $w, $y = $x += $z = testexe -returncode 0 && testexe -returncode 1'; Variables = @{ v = '1',@('0','1'); w = '1'; y = '0','1'; x = '1','0','1'; z = '0','1' } } + @{ Statement = '$x = 1 && @(2, 3)'; Variables = @{ x = 1,2,3 } } + @{ Statement = '$x = 1 && ,@(2, 3)'; Variables = @{ x = 1,@(2,3) } } + @{ Statement = '$x = 1 && 2,@(3, 4)'; Variables = @{ x = 1,2,@(3,4) } } + ) + + $jobTestCases = @( + @{ Statement = 'testexe -returncode 0 && testexe -returncode 1 &'; Output = @('0', '1') } + @{ Statement = 'testexe -returncode 1 && testexe -returncode 0 &'; Output = @('1') } + @{ Statement = '$x = testexe -returncode 0 && Write-Output "mice" &'; Output = '0','mice'; Variable = 'x' } + ) + + $invalidSyntaxCases = @( + @{ Statement = 'testexe -returncode 0 & && testexe -returncode 1'; ErrorID = 'BackgroundOperatorInPipelineChain' } + @{ Statement = 'testexe -returncode 0 && '; ErrorID = 'EmptyPipelineChainElement'; IncompleteInput = $true } + @{ Statement = 'testexe -returncode 0 && testexe -returncode 1 && &'; ErrorID = 'MissingExpression' } + ) + } + + It "Gets the correct output with statement ''" -TestCases $simpleTestCases { + param($Statement, $Output) + + $result = Invoke-Expression -Command $Statement 2>$null + $result | Should -Be $Output + } + + It "Sets the variable correctly with statement ''" -TestCases $variableTestCases { + param($Statement, $Variables) + + Invoke-Expression -Command $Statement + foreach ($variableName in $Variables.get_Keys()) + { + (Get-Variable -Name $variableName -ErrorAction Ignore).Value | Should -Be $Variables[$variableName] -Because "variable is '`$$variableName'" + } + } + + It "Runs the statement chain '' as a job" -TestCases $jobTestCases { + param($Statement, $Output, $Variable) + + $resultJob = Invoke-Expression -Command $Statement + + if ($Variable) + { + $resultJob = (Get-Variable $Variable).Value + } + + $resultJob | Wait-Job | Receive-Job | Should -Be $Output + } + + It "Rejects invalid syntax usage in ''" -TestCases $invalidSyntaxCases { + param([string]$Statement, [string]$ErrorID, [bool]$IncompleteInput) + + $tokens = $errors = $null + [System.Management.Automation.Language.Parser]::ParseInput($Statement, [ref]$tokens, [ref]$errors) + + $errors.Count | Should -BeExactly 1 + $errors[0].ErrorId | Should -BeExactly $ErrorID + $errors[0].IncompleteInput | Should -Be $IncompleteInput + } + + Context "File redirection with && and ||" { + BeforeAll { + $redirectionTestCases = @( + @{ Statement = "testexe -returncode 0 > '$TestDrive/1.txt' && testexe -returncode 1 > '$TestDrive/2.txt'"; Files = @{ "$TestDrive/1.txt" = '0'; "$TestDrive/2.txt" = '1' } } + @{ Statement = "testexe -returncode 1 > '$TestDrive/1.txt' && testexe -returncode 1 > '$TestDrive/2.txt'"; Files = @{ "$TestDrive/1.txt" = '1'; "$TestDrive/2.txt" = $null } } + @{ Statement = "testexe -returncode 1 > '$TestDrive/1.txt' || testexe -returncode 1 > '$TestDrive/2.txt'"; Files = @{ "$TestDrive/1.txt" = '1'; "$TestDrive/2.txt" = '1' } } + @{ Statement = "testexe -returncode 0 > '$TestDrive/1.txt' || testexe -returncode 1 > '$TestDrive/2.txt'"; Files = @{ "$TestDrive/1.txt" = '0'; "$TestDrive/2.txt" = $null } } + @{ Statement = "(testexe -returncode 0 && testexe -returncode 1) > '$TestDrive/3.txt'"; Files = @{ "$TestDrive/3.txt" = "0$([System.Environment]::NewLine)1$([System.Environment]::NewLine)" } } + @{ Statement = "(testexe -returncode 0 && testexe -returncode 1 > '$TestDrive/2.txt') > '$TestDrive/3.txt'"; Files = @{ "$TestDrive/2.txt" = '1'; "$TestDrive/3.txt" = '0' } } + @{ Statement = "(testexe -returncode 0 > '$TestDrive/1.txt' && testexe -returncode 1 > '$TestDrive/2.txt') > '$TestDrive/3.txt'"; Files = @{ "$TestDrive/1.txt" = '0'; "$TestDrive/2.txt" = '1'; "$TestDrive/3.txt" = '' } } + ) + } + + BeforeEach { + Remove-Item -Path $TestDrive/* + } + + It "Handles redirection correctly with statement ''" -TestCases $redirectionTestCases { + param($Statement, $Files) + + Invoke-Expression -Command $Statement + + foreach ($file in $Files.get_Keys()) + { + $expectedValue = $Files[$file] + + if ($null -eq $expectedValue) + { + $file | Should -Not -Exist + continue + } + + # Special case for empty file + if ($expectedValue -eq '') + { + (Get-Item $file).Length | Should -Be 0 + continue + } + + $file | Should -FileContentMatchMultiline $expectedValue + } + } + } + + Context "Pipeline chain error semantics" { + BeforeAll { + $pwsh = [powershell]::Create() + + $pwsh.AddScript(@' +filter Test-NonTerminatingError +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline)] + [object[]] + $input + ) + + if ($input -ne 2) + { + return $input + } + + $exception = [System.Exception]::new("NTERROR") + $errorId = "NTERROR" + $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified + + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) + $PSCmdlet.WriteError($errorRecord) +} + +filter Test-PipelineTerminatingError +{ + [CmdletBinding()] + param([Parameter(ValueFromPipeline)][int[]]$input) + + if ($input -ne 4) + { + return $input + } + + $exception = [System.Exception]::new("PIPELINE") + $errorId = "PIPELINE" + $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified + + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorId, $errorCategory, $null) + + $PSCmdlet.ThrowTerminatingError($errorRecord) +} + +function Test-FullyTerminatingError +{ + throw 'TERMINATE' +} +'@).Invoke() + + $errorSemanticsCases = @( + # Simple error semantics + @{ Statement = '1,2,3 | Test-NonTerminatingError || Write-Output 4'; Output = @(1, 3, 4); NTErrors = @('NTError') } + @{ Statement = '1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2'; Output = @(1, 3, 2); NTErrors = @('PIPELINE') } + @{ Statement = 'Test-FullyTerminatingError || Write-Output 2'; ThrownError = 'TERMINATE' } + @{ Statement = '1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4'; ThrownError = 'NTERROR,Test-NonTerminatingError' } + + # Assignment error semantics + @{ Statement = '$x = 1,2,3 | Test-NonTerminatingError || Write-Output 4; $x'; Output = @(1, 3, 4); NTErrors = @('NTError') } + @{ Statement = '$x = 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2; $x'; Output = @(1, 3, 2); NTErrors = @('PIPELINE') } + + # Try/catch semantics + @{ Statement = 'try { Write-Output 2 && Test-FullyTerminatingError } catch {}'; Output = @(2) } + @{ Statement = 'try { 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2 } catch {}'; Output = @(1, 3) } + @{ Statement = 'try { 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4 } catch {}'; Output = @(1) } + @{ Statement = 'try { 1,2,3 | Test-NonTerminatingError || Write-Output 4 } catch {}'; Output = @(1, 3, 4); NTErrors = @('NTError') } + @{ Statement = 'try { $result = Write-Output 2 && Test-FullyTerminatingError } catch {}; $result'; Output = @() } + @{ Statement = 'try { $result = 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2 } catch {}; $result'; Output = @() } + @{ Statement = 'try { $result = 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4 } catch {}; $result'; Output = @() } + @{ Statement = 'try { $result = 1,2,3 | Test-NonTerminatingError || Write-Output 4 } catch {}; $result'; Output = @(1, 3, 4); NTErrors = @('NTError') } + @{ Statement = 'try { "Hi" && "Bye" } catch { "Nothing" }'; Output = @("Hi", "Bye") } + @{ Statement = 'try { "Hi" && "Bye" } catch { "Nothing" } finally { "Final" }'; Output = @("Hi", "Bye", "Final") } + @{ Statement = 'try { "Hi" && Test-FullyTerminatingError || "Bye" } catch { "Nothing" } finally { "Final" }'; Output = @("Hi", "Nothing", "Final") } + + # Trap continue semantics + @{ Statement = 'trap { continue }; Write-Output 2 && Test-FullyTerminatingError'; Output = @(2) } + @{ Statement = 'trap { continue }; Test-FullyTerminatingError && Write-Output 2'; Output = @() } + @{ Statement = 'trap { continue }; Test-FullyTerminatingError || Write-Output 2'; Output = @(2) } + @{ Statement = 'trap { continue }; 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2'; Output = @(1,3,2) } + @{ Statement = 'trap { continue }; 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4'; Output = @(1,4) } + @{ Statement = 'trap { continue }; 1,2,3 | Test-NonTerminatingError || Write-Output 4'; Output = @(1,3,4); NTErrors = @('NTError') } + @{ Statement = 'trap { continue }; $result = Write-Output 2 && Test-FullyTerminatingError'; Output = @() } + @{ Statement = 'trap { continue }; $result = 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2; $result'; Output = @(1,3,2) } + @{ Statement = 'trap { continue }; $result = 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4; $result'; Output = @(1,4) } + @{ Statement = 'trap { continue }; $result = 1,2,3 | Test-NonTerminatingError || Write-Output 4; $result'; Output = @(1,3,4); NTErrors = @('NTError') } + + # Trap break semantics + @{ Statement = 'trap { break }; 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2'; ThrownError = 'PIPELINE,Test-PipelineTerminatingError' } + @{ Statement = 'trap { break }; 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4'; ThrownError = 'NTERROR,Test-NonTerminatingError' } + @{ Statement = 'trap { break }; 1,2,3 | Test-NonTerminatingError || Write-Output 4'; Output = @(1,3,4); NTErrors = @('NTError') } + @{ Statement = 'trap { break }; $result = Write-Output 2 && Test-FullyTerminatingError'; ThrownError = 'TERMINATE' } + @{ Statement = 'trap { break }; $result = 1,3,4,5 | Test-PipelineTerminatingError || Write-Output 2; $result'; ThrownError = 'PIPELINE,Test-PipelineTerminatingError' } + @{ Statement = 'trap { break }; $result = 1,2,3 | Test-NonTerminatingError -ErrorAction Stop || Write-Output 4; $result'; ThrownError = 'NTERROR,Test-NonTerminatingError' } + @{ Statement = 'trap { break }; $result = 1,2,3 | Test-NonTerminatingError || Write-Output 4; $result'; Output = @(1,3,4); NTErrors = @('NTError') } + ) + } + + AfterEach { + $pwsh.Commands.Clear() + $pwsh.AddScript('Remove-Variable -Name result').Invoke() + $pwsh.Commands.Clear() + $pwsh.Streams.ClearStreams() + } + + AfterAll { + $pwsh.Dispose() + } + + It "Uses the correct error semantics with statement ''" -TestCases $errorSemanticsCases { + param([string]$Statement, $Output, [array]$NTErrors, [string]$ThrownError) + + try + { + $result = $pwsh.AddScript($Statement).Invoke() + } + catch + { + $_.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly $ThrownError + } + + $result | Should -Be $Output + $pwsh.Streams.Error | Should -Be $NTErrors + } + } + + It "Recognises invalid assignment" { + { + Invoke-Expression -Command '$x = $x, $y += $z = testexe -returncode 0 && testexe -returncode 1' + } | Should -Throw -ErrorId 'InvalidLeftHandSide,Microsoft.PowerShell.Commands.InvokeExpressionCommand' + } +} diff --git a/test/powershell/Language/Operators/RangeOperator.Tests.ps1 b/test/powershell/Language/Operators/RangeOperator.Tests.ps1 index 055de78d572..56941827fcc 100644 --- a/test/powershell/Language/Operators/RangeOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/RangeOperator.Tests.ps1 @@ -1,97 +1,258 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Range Operator" -Tags CI { Context "Range integer operations" { It "Range operator generates arrays of integers" { $Range = 5..8 - $Range.count | Should Be 4 - $Range[0] | Should BeOfType [int] - $Range[1] | Should BeOfType [int] - $Range[2] | Should BeOfType [int] - $Range[3] | Should BeOfType [int] + $Range.count | Should -Be 4 + $Range[0] | Should -BeOfType int + $Range[1] | Should -BeOfType int + $Range[2] | Should -BeOfType int + $Range[3] | Should -BeOfType int - $Range[0] | Should Be 5 - $Range[1] | Should Be 6 - $Range[2] | Should Be 7 - $Range[3] | Should Be 8 + $Range[0] | Should -Be 5 + $Range[1] | Should -Be 6 + $Range[2] | Should -Be 7 + $Range[3] | Should -Be 8 } It "Range operator accepts negative integer values" { $Range = -8..-5 - $Range.count | Should Be 4 - $Range[0] | Should Be -8 - $Range[1] | Should Be -7 - $Range[2] | Should Be -6 - $Range[3] | Should Be -5 + $Range.count | Should -Be 4 + $Range[0] | Should -Be -8 + $Range[1] | Should -Be -7 + $Range[2] | Should -Be -6 + $Range[3] | Should -Be -5 } It "Range operator support single-item sequences" { $Range = 0..0 - $Range.count | Should Be 1 - $Range[0] | Should BeOfType [int] - $Range[0] | Should Be 0 + $Range.count | Should -Be 1 + $Range[0] | Should -BeOfType int + $Range[0] | Should -Be 0 } It "Range operator works in descending order" { $Range = 4..3 - $Range.count | Should Be 2 - $Range[0] | Should Be 4 - $Range[1] | Should Be 3 + $Range.count | Should -Be 2 + $Range[0] | Should -Be 4 + $Range[1] | Should -Be 3 } It "Range operator works for sequences of both negative and positive numbers" { $Range = -2..2 - $Range.count | Should Be 5 - $Range[0] | Should Be -2 - $Range[1] | Should Be -1 - $Range[2] | Should Be 0 - $Range[3] | Should Be 1 - $Range[4] | Should Be 2 + $Range.count | Should -Be 5 + $Range[0] | Should -Be -2 + $Range[1] | Should -Be -1 + $Range[2] | Should -Be 0 + $Range[3] | Should -Be 1 + $Range[4] | Should -Be 2 + } + + It "Range operator enumerator works" { + $Range = -2..2 | ForEach-Object { $_ } + $Range.count | Should -Be 5 + $Range[0] | Should -Be -2 + $Range[1] | Should -Be -1 + $Range[2] | Should -Be 0 + $Range[3] | Should -Be 1 + $Range[4] | Should -Be 2 + } + + It "Range operator works with variables" { + $var1 = -1 + $var2 = 1 + $Range = $var1..$var2 + $Range.count | Should -Be 3 + $Range[0] | Should -Be -1 + $Range[1] | Should -Be 0 + $Range[2] | Should -Be 1 + + $Range = [int]$var2..[int]$var1 + $Range.count | Should -Be 3 + $Range[0] | Should -Be 1 + $Range[1] | Should -Be 0 + $Range[2] | Should -Be -1 + + $Range = $var1..$var2 | ForEach-Object { $_ } + $Range.count | Should -Be 3 + $Range[0] | Should -Be -1 + $Range[1] | Should -Be 0 + $Range[2] | Should -Be 1 + + $Range = [int]$var2..[int]$var1 | ForEach-Object { $_ } + $Range.count | Should -Be 3 + $Range[0] | Should -Be 1 + $Range[1] | Should -Be 0 + $Range[2] | Should -Be -1 } } Context "Character expansion" { - It "Range operator generates an array of [char] from single-character string operands" { + It "Range operator generates an array of [char] from single-character operands" { $CharRange = 'A'..'E' - $CharRange.count | Should Be 5 - $CharRange[0] | Should BeOfType [char] - $CharRange[1] | Should BeOfType [char] - $CharRange[2] | Should BeOfType [char] - $CharRange[3] | Should BeOfType [char] - $CharRange[4] | Should BeOfType [char] + $CharRange.count | Should -Be 5 + $CharRange[0] | Should -BeOfType char + $CharRange[1] | Should -BeOfType char + $CharRange[2] | Should -BeOfType char + $CharRange[3] | Should -BeOfType char + $CharRange[4] | Should -BeOfType char + } + + It "Range operator enumerator generates an array of [string] from single-character operands" { + $CharRange = 'A'..'E' | ForEach-Object { $_ } + $CharRange.count | Should -Be 5 + $CharRange[0] | Should -BeOfType char + $CharRange[1] | Should -BeOfType char + $CharRange[2] | Should -BeOfType char + $CharRange[3] | Should -BeOfType char + $CharRange[4] | Should -BeOfType char } It "Range operator works in ascending and descending order" { - $CharRange = 'a'..'b' - $CharRange.count | Should Be 2 - $CharRange[0] | Should Be ([char]'a') - $CharRange[1] | Should Be ([char]'b') + $CharRange = 'a'..'c' + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'a') + $CharRange[1] | Should -BeExactly ([char]'b') + $CharRange[2] | Should -BeExactly ([char]'c') + + $CharRange = 'C'..'A' + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'C') + $CharRange[1] | Should -BeExactly ([char]'B') + $CharRange[2] | Should -BeExactly ([char]'A') + } + + It "Range operator works in ascending and descending order with [char] cast" { + $CharRange = [char]'a'..[char]'c' + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'a') + $CharRange[1] | Should -BeExactly ([char]'b') + $CharRange[2] | Should -BeExactly ([char]'c') + + $CharRange = [char]"a".."c" + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'a') + $CharRange[1] | Should -BeExactly ([char]'b') + $CharRange[2] | Should -BeExactly ([char]'c') + + $CharRange = "a"..[char]"c" + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'a') + $CharRange[1] | Should -BeExactly ([char]'b') + $CharRange[2] | Should -BeExactly ([char]'c') + + # The same works in reverse order. + $CharRange = [char]'C'..[char]'A' + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'C') + $CharRange[1] | Should -BeExactly ([char]'B') + $CharRange[2] | Should -BeExactly ([char]'A') + + $CharRange = [char]"C".."A" + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'C') + $CharRange[1] | Should -BeExactly ([char]'B') + $CharRange[2] | Should -BeExactly ([char]'A') - $CharRange = 'b'..'a' - $CharRange.count | Should Be 2 - $CharRange[0] | Should Be ([char]'b') - $CharRange[1] | Should Be ([char]'a') + $CharRange = "C"..[char]"A" + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly ([char]'C') + $CharRange[1] | Should -BeExactly ([char]'B') + $CharRange[2] | Should -BeExactly ([char]'A') + } + + It "Range operator enumerator works in ascending and descending order" { + $CharRange = 'a'..'c' | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "a" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "c" + + $CharRange = 'C'..'A' | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "C" + $CharRange[1] | Should -BeExactly "B" + $CharRange[2] | Should -BeExactly "A" + } + + It "Range operator enumerator works in ascending and descending order with [char] cast" { + $CharRange = [char]'a'..[char]'c' | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "a" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "c" + + $CharRange = [char]'C'..[char]'A' | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "C" + $CharRange[1] | Should -BeExactly "B" + $CharRange[2] | Should -BeExactly "A" + } + + It "Range operator works with variables" { + $var1 = 'a' + $var2 = 'c' + $CharRange = $var1..$var2 + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "a" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "c" + + $CharRange = [char]$var2..[char]$var1 + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "c" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "a" + + $CharRange = $var1..$var2 | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "a" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "c" + + $CharRange = [char]$var2..[char]$var1 | ForEach-Object { $_ } + $CharRange.count | Should -Be 3 + $CharRange[0] | Should -BeExactly "c" + $CharRange[1] | Should -BeExactly "b" + $CharRange[2] | Should -BeExactly "a" } It "Range operator works with 16-bit unicode characters" { $UnicodeRange = "`u{0110}".."`u{0114}" - $UnicodeRange.count | Should Be 5 - $UnicodeRange[0] | Should Be "`u{0110}"[0] - $UnicodeRange[1] | Should Be "`u{0111}"[0] - $UnicodeRange[2] | Should Be "`u{0112}"[0] - $UnicodeRange[3] | Should Be "`u{0113}"[0] - $UnicodeRange[4] | Should Be "`u{0114}"[0] - $UnicodeRange.Where({$_ -is [char]}).count | Should Be 5 + $UnicodeRange.count | Should -Be 5 + $UnicodeRange[0] | Should -Be "`u{0110}"[0] + $UnicodeRange[1] | Should -Be "`u{0111}"[0] + $UnicodeRange[2] | Should -Be "`u{0112}"[0] + $UnicodeRange[3] | Should -Be "`u{0113}"[0] + $UnicodeRange[4] | Should -Be "`u{0114}"[0] + $UnicodeRange.Where({$_ -is [char]}).count | Should -Be 5 + } + + It "Range operator with special ranges" { + $SpecRange = "0".."9" + $SpecRange.count | Should -Be 10 + $SpecRange.Where({$_ -is [int]}).count | Should -Be 10 + + $SpecRange = '0'..'9' + $SpecRange.count | Should -Be 10 + $SpecRange.Where({$_ -is [int]}).count | Should -Be 10 + + $SpecRange = [char]'0'..[char]'9' + $SpecRange.count | Should -Be 10 + $SpecRange.Where({$_ -is [char]}).count | Should -Be 10 } } Context "Range operator operand types" { It "Range operator works on [decimal]" { $Range = 1.1d..3.9d - $Range.count | Should Be 4 - $Range[0] | Should Be 1 - $Range[1] | Should Be 2 - $Range[2] | Should Be 3 - $Range[3] | Should Be 4 - $Range.Where({$_ -is [int]}).count | Should Be 4 + $Range.count | Should -Be 4 + $Range[0] | Should -Be 1 + $Range[1] | Should -Be 2 + $Range[2] | Should -Be 3 + $Range[3] | Should -Be 4 + $Range.Where({$_ -is [int]}).count | Should -Be 4 } } } diff --git a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 new file mode 100644 index 00000000000..074351d3414 --- /dev/null +++ b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 @@ -0,0 +1,105 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Replace Operator" -Tags CI { + Context "Replace operator" { + It "Replace operator can replace string values using regular expressions" { + $res = "Get-Process" -replace "Get", "Stop" + $res | Should -BeExactly "Stop-Process" + + $res = "image.gif" -replace "\.gif$",".jpg" + $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" + + $res = "book" -ireplace "B","C" + $res | Should -BeExactly "Cook" + + $res = "book" -creplace "B","C" + $res | Should -BeExactly "book" + } + + It "Replace operator can take 2 arguments, a mandatory pattern, and an optional substitution" { + $res = "PowerPoint" -replace "Point","Shell" + $res | Should -BeExactly "PowerShell" + + $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" { + It "Replace operator supports numbered substitution groups using ```$n" { + $res = "domain.example" -replace ".*\.(\w+)$","Tld of '`$0' is - '`$1'" + $res | Should -BeExactly "Tld of 'domain.example' is - 'example'" + } + + It "Replace operator supports named substitution groups using ```${name}" { + $res = "domain.example" -replace ".*\.(?\w+)$","`${tld}" + $res | Should -BeExactly "example" + } + + It "Replace operator can take a ScriptBlock in place of a substitution string" { + $res = "ID ABC123" -replace "\b[A-C]+", {return "X" * $_.Value.Length} + $res | Should -BeExactly "ID XXX123" + } + + It "Replace operator can take a MatchEvaluator in place of a substitution string" { + $matchEvaluator = {return "X" * $args[0].Value.Length} -as [System.Text.RegularExpressions.MatchEvaluator] + $res = "ID ABC123" -replace "\b[A-C]+", $matchEvaluator + $res | Should -BeExactly "ID XXX123" + } + + It "Replace operator can take a static PSMethod in place of a substitution string" { + class R { + static [string] Replace([System.Text.RegularExpressions.Match]$Match) { + return "X" * $Match.Value.Length + } + } + $substitutionMethod = [R]::Replace + $res = "ID 0000123" -replace "\b0+", $substitutionMethod + $res | Should -BeExactly "ID XXXX123" + } + } + + Describe "Culture-invariance tests for -split and -replace" -Tags CI { + BeforeAll { + $prevCulture = [cultureinfo]::CurrentCulture + # The French culture uses "," as the decimal mark. + [cultureinfo]::CurrentCulture = 'fr' + } + + AfterAll { + [cultureinfo]::CurrentCulture = $prevCulture + } + + It "-split: LHS stringification is not culture-sensitive" { + 1.2 -split ',' | Should -Be '1.2' + } + + It "-replace: LHS stringification is not culture-sensitive" { + 1.2 -replace ',' | Should -Be '1.2' + } + } +} diff --git a/test/powershell/Language/Operators/SplitOperator.Tests.ps1 b/test/powershell/Language/Operators/SplitOperator.Tests.ps1 index e132043838d..1ebbbff4fa2 100644 --- a/test/powershell/Language/Operators/SplitOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/SplitOperator.Tests.ps1 @@ -1,106 +1,174 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Split Operator" -Tags CI { Context "Binary split operator" { It "Binary split operator can split array of value" { $res = "a b", "c d" -split " " - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" } It "Binary split operator can split a string" { $res = "a b c d" -split " " - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" } It "Binary split operator can works with max substring limit" { $res = "a b c d" -split " ", 2 - $res.count | Should Be 2 - $res[0] | Should Be "a" - $res[1] | Should Be "b c d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b c d" $res = "a b c d" -split " ", 0 - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" + + $res = "a b c d" -split " ", -2 + $res.count | Should -Be 2 + $res[0] | Should -Be "a b c" + $res[1] | Should -Be "d" $res = "a b c d" -split " ", -1 - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 1 + $res[0] | Should -Be "a b c d" + } + + It "Binary split operator can work with different delimeter than split string" { + $res = "a b c d" -split " ",8 + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" + + $res = "a b c d" -split " ",-8 + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" + + $res = " " -split " ",-2 + $res.count | Should -Be 2 + $res[0] | Should -Be "" + $res[1] | Should -Be "" + } + + It "Binary split operator with predicate can work with negative numbers" { + $res = "a b c d" -split {$_ -like ' '},-2 + $res.count | Should -Be 2 + $res[0] | Should -Be "a b c" + $res[1] | Should -Be "d" + + $res = "a b c d" -split {$_ -like ' '},-4 + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" + + $res = "a b c d" -split {$_ -like ' '},-8 + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" + + $res = " " -split {$_ -like ' '},-4 + $res.count | Should -Be 2 + $res[0] | Should -Be "" + $res[1] | Should -Be "" + + $res = "folder/path/to/file" -split {$_ -like '/'}, -2 + $res.count | Should -Be 2 + $res[0] | Should -Be "folder/path/to" + $res[1] | Should -Be "file" + } + + It "Binary split operator can work with regex expression" { + $res = "a2b3c4d" -split '\d+',2 + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b3c4d" + + $res = "a2b3c4d" -split '\d+',-2 + $res.count | Should -Be 2 + $res[0] | Should -Be "a2b3c" + $res[1] | Should -Be "d" } It "Binary split operator can works with freeform delimiter" { $res = "a::b::c::d" -split "::" - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" } It "Binary split operator can preserve delimiter" { $res = "a1:b1:c1:d" -split "(1:)" - $res.count | Should Be 7 - $res[0] | Should Be "a" - $res[1] | Should Be "1:" - $res[2] | Should Be "b" - $res[3] | Should Be "1:" - $res[4] | Should Be "c" - $res[5] | Should Be "1:" - $res[6] | Should Be "d" + $res.count | Should -Be 7 + $res[0] | Should -Be "a" + $res[1] | Should -Be "1:" + $res[2] | Should -Be "b" + $res[3] | Should -Be "1:" + $res[4] | Should -Be "c" + $res[5] | Should -Be "1:" + $res[6] | Should -Be "d" $res = "a1:b1:c1:d" -split "1(:)" - $res.count | Should Be 7 - $res[0] | Should Be "a" - $res[1] | Should Be ":" - $res[2] | Should Be "b" - $res[3] | Should Be ":" - $res[4] | Should Be "c" - $res[5] | Should Be ":" - $res[6] | Should Be "d" + $res.count | Should -Be 7 + $res[0] | Should -Be "a" + $res[1] | Should -Be ":" + $res[2] | Should -Be "b" + $res[3] | Should -Be ":" + $res[4] | Should -Be "c" + $res[5] | Should -Be ":" + $res[6] | Should -Be "d" } It "Binary split operator can be case-insensitive and case-sensitive" { $res = "abcBd" -split "B" - $res.count | Should Be 3 - $res[0] | Should Be "a" - $res[1] | Should Be "c" - $res[2] | Should Be "d" + $res.count | Should -Be 3 + $res[0] | Should -Be "a" + $res[1] | Should -Be "c" + $res[2] | Should -Be "d" $res = "abcBd" -isplit "B" - $res.count | Should Be 3 - $res[0] | Should Be "a" - $res[1] | Should Be "c" - $res[2] | Should Be "d" + $res.count | Should -Be 3 + $res[0] | Should -Be "a" + $res[1] | Should -Be "c" + $res[2] | Should -Be "d" $res = "abcBd" -csplit "B" - $res.count | Should Be 2 - $res[0] | Should Be "abc" - $res[1] | Should Be "d" + $res.count | Should -Be 2 + $res[0] | Should -Be "abc" + $res[1] | Should -Be "d" $res = "abcBd" -csplit "B", 0 , 'IgnoreCase' - $res.count | Should Be 3 - $res[0] | Should Be "a" - $res[1] | Should Be "c" - $res[2] | Should Be "d" + $res.count | Should -Be 3 + $res[0] | Should -Be "a" + $res[1] | Should -Be "c" + $res[2] | Should -Be "d" } It "Binary split operator can works with script block" { $res = "a::b::c::d" -split {$_ -eq "b" -or $_ -eq "C"} - $res.count | Should Be 3 - $res[0] | Should Be "a::" - $res[1] | Should Be "::" - $res[2] | Should Be "::d" + $res.count | Should -Be 3 + $res[0] | Should -Be "a::" + $res[1] | Should -Be "::" + $res[2] | Should -Be "::d" } } @@ -118,125 +186,125 @@ Describe "Split Operator" -Tags CI { param($testText, $testText2, $newLine) # Multiline isn't default $res = $testText -split '^b' - $res.count | Should Be 1 + $res.count | Should -Be 1 # Singleline isn't default $res = $testText -split 'b.+c' - $res.count | Should Be 1 + $res.count | Should -Be 1 } It "Binary split operator works with Singleline (new line = '')" -TestCases $testCases { param($testText, $testText2, $newLine) $res = $testText -split 'b.+c', 0, 'Singleline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)" - $res[1] | Should Be "$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)" + $res[1] | Should -Be "$($newLine)d78d" $res = $testText2 -split 'b.+c', 0, 'Singleline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)%" - $res[1] | Should Be "$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)%" + $res[1] | Should -Be "$($newLine)d78d" } It "Binary split operator works with Multiline (new line = '')" -TestCases $testCases { param($testText, $testText2, $newLine) $res = $testText -split '^b', 0, 'Multiline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)" - $res[1] | Should Be "34b$($newLine)c56c$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)" + $res[1] | Should -Be "34b$($newLine)c56c$($newLine)d78d" } It "Binary split operator works with Singleline,Multiline (new line = '')" -TestCases $testCases { param($testText, $testText2, $newLine) $res = $testText -split 'b.+c', 0, 'Singleline,Multiline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)" - $res[1] | Should Be "$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)" + $res[1] | Should -Be "$($newLine)d78d" $res = $testText2 -split 'b.+c', 0, 'Singleline,Multiline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)%" - $res[1] | Should Be "$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)%" + $res[1] | Should -Be "$($newLine)d78d" $res = $testText -split '^b.+c', 0, 'Singleline,Multiline' - $res.count | Should Be 2 - $res[0] | Should Be "a12a$($newLine)" - $res[1] | Should Be "$($newLine)d78d" + $res.count | Should -Be 2 + $res[0] | Should -Be "a12a$($newLine)" + $res[1] | Should -Be "$($newLine)d78d" $res = $testText2 -split '^b.+c', 0, 'Singleline,Multiline' - $res.count | Should Be 1 + $res.count | Should -Be 1 } It "Binary split operator works with IgnorePatternWhitespace" { $res = "a: b:c" -split ': ' - $res.count | Should Be 2 - $res[0] | Should Be "a" - $res[1] | Should Be "b:c" + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b:c" $res = "a: b:c" -split ': ',0,'IgnorePatternWhitespace' - $res.count | Should Be 3 - $res[0] | Should Be "a" - $res[1] | Should Be " b" - $res[2] | Should Be "c" + $res.count | Should -Be 3 + $res[0] | Should -Be "a" + $res[1] | Should -Be " b" + $res[2] | Should -Be "c" } It "Binary split operator works with ExplicitCapture" { $res = "a:b" -split "(:)" - $res.count | Should Be 3 - $res[0] | Should Be "a" - $res[1] | Should Be ":" - $res[2] | Should Be "b" + $res.count | Should -Be 3 + $res[0] | Should -Be "a" + $res[1] | Should -Be ":" + $res[2] | Should -Be "b" $res = "a:b" -split "(:)", 0, 'ExplicitCapture' - $res.count | Should Be 2 - $res[0] | Should Be "a" - $res[1] | Should Be "b" + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" } It "Binary split operator works with SimpleMatch" { $res = "abc" -split "B", 0, 'SimpleMatch,IgnoreCase' - $res.count | Should Be 2 - $res[0] | Should Be "a" - $res[1] | Should Be "c" + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "c" } It "Binary split operator works with RegexMatch" { $res = "abc" -split "B", 0, 'RegexMatch,Singleline' - $res.count | Should Be 2 - $res[0] | Should Be "a" - $res[1] | Should Be "c" + $res.count | Should -Be 2 + $res[0] | Should -Be "a" + $res[1] | Should -Be "c" } It "Binary split operator doesn't works with RegexMatch,SimpleMatch" { - { "abc" -split "B", 0, 'RegexMatch,SimpleMatch' } | ShouldBeErrorId "InvalidSplitOptionCombination" + { "abc" -split "B", 0, 'RegexMatch,SimpleMatch' } | Should -Throw -ErrorId "InvalidSplitOptionCombination" } } Context "Unary split operator" { It "Unary split operator has higher precedence than a comma" { $res = -split "a b", "c d" - $res.count | Should Be 2 - $res[0][0] | Should Be "a" - $res[0][1] | Should Be "b" - $res[1] | Should Be "c d" + $res.count | Should -Be 2 + $res[0][0] | Should -Be "a" + $res[0][1] | Should -Be "b" + $res[1] | Should -Be "c d" } It "Unary split operator can split array of values" { $res = -split ("a b", "c d") - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" } It "Unary split operator can split a string" { $res = -split "a b c d" - $res.count | Should Be 4 - $res[0] | Should Be "a" - $res[1] | Should Be "b" - $res[2] | Should Be "c" - $res[3] | Should Be "d" + $res.count | Should -Be 4 + $res[0] | Should -Be "a" + $res[1] | Should -Be "b" + $res[2] | Should -Be "c" + $res[3] | Should -Be "d" } } } diff --git a/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 b/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 new file mode 100644 index 00000000000..32928474144 --- /dev/null +++ b/test/powershell/Language/Operators/TernaryOperator.Tests.ps1 @@ -0,0 +1,103 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Using of ternary operator" -Tags CI { + BeforeAll { + $testCases = @( + ## Condition: variable and constant expressions + @{ Script = { $true ? 1 : 2 }; ExpectedValue = 1 } + @{ Script = { $true? ?1 :2 }; ExpectedValue = 2 } + @{ Script = { ${true}?1:2 }; ExpectedValue = 1 } + @{ Script = { 1 ? 1kb : 0xf }; ExpectedValue = 1kb } + @{ Script = { 0 ?1kb:0xf }; ExpectedValue = 15 } + @{ Script = { 's' ?1kb:0xf }; ExpectedValue = 1kb } + @{ Script = { $null ?1kb:0xf }; ExpectedValue = 15 } + @{ Script = { '' ?1kb:0xf }; ExpectedValue = 15 } + + ## Condition: other primary expressions + @{ Script = { 1,2,3,4 ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @(1,2,3,4) ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'} ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'}.name ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { @{name = 'name'}.Contains('name') ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { (Test-Path Env:\NonExist) ? 'true' : 'false' }; ExpectedValue = 'false' } + @{ Script = { (Test-Path Env:\PSModulePath) ? 'true' : 'false' }; ExpectedValue = 'true' } + @{ Script = { $($p = Get-Process -Id $PID; $p.Id -eq $PID) ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { ($a = 1) ? 2 : 3 }; ExpectedValue = 2 } + @{ Script = { $($a = 1) ? 2 : 3 }; ExpectedValue = 3 } + @{ Script = { (Write-Warning -Message warning -WarningAction SilentlyContinue) ? 1 : 2 }; ExpectedValue = 2 } + @{ Script = { (Write-Error -Message error -ErrorAction SilentlyContinue) ? 1 : 2 }; ExpectedValue = 2 } + + ## Condition: unary and binary expression expressions + @{ Script = { -not $IsCoreCLR ? 'Desktop' : 'Core' }; ExpectedValue = 'Core' } + @{ Script = { $PSEdition -eq 'Core' ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { $IsCoreCLR -and (Get-Process -Id $PID).Id -eq $PID ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { $IsCoreCLR -and 'pwsh' -match 'p.*h' ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + @{ Script = { 1,2,3 -contains 2 ? 'Core' : 'Desktop' }; ExpectedValue = 'Core' } + + ## Nested ternary expressions + @{ Script = { $IsCoreCLR ? $false ? 'nested-if-true' : 'nested-if-false' : 'if-false' }; ExpectedValue = 'nested-if-false' } + @{ Script = { $IsCoreCLR ? $false ? 'nested-if-true' : $true ? 'nested-nested-if-true' : 'nested-nested-if-false' : 'if-false' }; ExpectedValue = 'nested-nested-if-true' } + + ## Binary operator has higher precedence order than ternary + @{ Script = { !$IsCoreCLR ? 'Core' : 'Desktop' -EQ 'Core' }; ExpectedValue = !$IsCoreCLR ? 'Core' : ('Desktop' -eq 'Core') } + @{ Script = { ($IsCoreCLR ? 'Core' : 'Desktop') -eq 'Core' }; ExpectedValue = $true } + ) + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + + It "Ternary expression ' + +'@ + } + @{ + 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^> +'@ + } + ) + + # 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 + } - # 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" { - $lines = testexe -echoargs 'a\"b c\"d' "a\`"b c\`"d" - $lines.Count | Should Be 2 - $lines[0] | Should Be 'Arg 0 is ' - $lines[1] | Should Be 'Arg 1 is ' + # save the passing style + $passingStyle = $PSNativeCommandArgumentPassing + # explicitely set the passing style to Windows + $PSNativeCommandArgumentPassing = "Windows" } - It "Should correctly quote paths with spaces: " -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 Be "Arg $i is <$($expected[$i])>" + 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 { + $results = & $scriptPath $a 'a"b c"d' a"b c"d "a'b c'd" 2> "${TESTDRIVE}/error.txt" } + $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] + } +} + + +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 + } +} + +Describe "find.exe uses legacy behavior on Windows" -Tag 'CI' { + BeforeAll { + $currentSetting = $PSNativeCommandArgumentPassing + $PSNativeCommandArgumentPassing = "Windows" + $testCases = @{ pattern = "" }, + @{ pattern = "blat" }, + @{ pattern = "bl at" } + } + 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' + } +} + +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 ' + } + + # 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 "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 "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 " + } + + 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 "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 + } + + $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 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 Be "Arg $i is <$($expected[$i])>" + 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 f3823b93563..542f1724303 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 @@ -1,14 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Native pipeline should have proper encoding' -tags 'CI' { It '$OutputEncoding should be set to UTF8 without BOM' { - $OutputEncoding.BodyName | Should Be "utf-8" - $OutputEncoding.GetPreamble().Length | Should Be 0 + $OutputEncoding.BodyName | Should -Be "utf-8" + $OutputEncoding.GetPreamble().Length | Should -Be 0 } } Describe 'native commands with pipeline' -tags 'Feature' { BeforeAll { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" } It "native | ps | native doesn't block" { @@ -25,7 +27,7 @@ Describe 'native commands with pipeline' -tags 'Feature' { # waiting 30 seconds, because powershell startup time could be long on the slow machines, # such as CI - Wait-UntilTrue { $rs.RunspaceAvailability -eq 'Available' } -timeout 30000 -interval 100 | Should Be $true + Wait-UntilTrue { $rs.RunspaceAvailability -eq 'Available' } -timeout 30000 -interval 100 | Should -BeTrue $ps.Stop() $rs.ResetRunspaceState() @@ -35,12 +37,68 @@ Describe 'native commands with pipeline' -tags 'Feature' { if ($IsWindows) { $result = @(ping.exe | findstr.exe count | findstr.exe ping) - $result[0] | Should Match "Usage: ping" + $result[0] | Should -Match "Usage: ping" } else { $result = @(ps aux | grep pwsh | grep -v grep) - $result[0] | Should Match "pwsh" + $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" { @@ -58,10 +116,10 @@ Describe "Native Command Processor" -tags "Feature" { $ps.AddArgument("-createchildprocess") $ps.AddArgument($numToCreate) $async = $ps.BeginInvoke() - $ps.InvocationStateInfo.State | Should Be "Running" + $ps.InvocationStateInfo.State | Should -Be "Running" [bool] $childrenCreated = $false - while (-not $childrenCreated) + while (-Not $childrenCreated) { $childprocesses = Get-Process testexe -ErrorAction SilentlyContinue if ($childprocesses.count -eq $numToCreate+1) @@ -83,13 +141,17 @@ Describe "Native Command Processor" -tags "Feature" { $childprocesses = Get-Process testexe $count = $childprocesses.count $childprocesses | Stop-Process - $count | Should Be 0 + $count | Should -Be 0 } 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 } + Get-Process -Name notepad -ErrorAction Ignore | Where-Object { $_.Id -NotIn $dontKill } } # We need to kill the windows process we start and can't know the process id, so get a list of @@ -111,11 +173,11 @@ Describe "Native Command Processor" -tags "Feature" { # Stop the new instance of notepad $newNotepad = FindNewNotepad - $newNotepad | Should Not Be $null + $newNotepad | Should -Not -BeNullOrEmpty $newNotepad | Stop-Process - $async.IsCompleted | Should Be $true - $ps.EndInvoke($async) | Should Be "ran notepad" + $async.IsCompleted | Should -BeTrue + $ps.EndInvoke($async) | Should -Be "ran notepad" } finally { @@ -126,6 +188,37 @@ Describe "Native Command Processor" -tags "Feature" { $ps.Dispose() } } + + It "OutputEncoding should be used" -Skip:(!$IsWindows -or !(Get-Command sfc.exe)) { + + $originalOutputEncoding = [Console]::OutputEncoding + try { + [Console]::OutputEncoding = [System.Text.Encoding]::Unicode + sfc | Out-String | Should -Not -Match "`0" + } + finally { + [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") { @@ -184,6 +277,7 @@ Categories=Application; It "Should open text file without error" -Skip:(!$supportedEnvironment) { if ($IsMacOS) { + Set-TestInconclusive -Message "AppleScript is not currently reliable on Az Pipelines" $expectedTitle = Split-Path $TestFile -Leaf open -F -a TextEdit $beforeCount = [int]('tell application "TextEdit" to count of windows' | osascript) @@ -195,8 +289,8 @@ Categories=Application; $title = 'tell application "TextEdit" to get name of front window' | osascript } $afterCount = [int]('tell application "TextEdit" to count of windows' | osascript) - $afterCount | Should Be ($beforeCount + 1) - $title | Should Be $expectedTitle + $afterCount | Should -Be ($beforeCount + 1) + $title | Should -BeExactly $expectedTitle "tell application ""TextEdit"" to close window ""$expectedTitle""" | osascript 'tell application "TextEdit" to quit' | osascript } @@ -204,18 +298,107 @@ Categories=Application; # Validate on Linux by reassociating default app for text file & $TestFile # It may take time for handler to start - Wait-FileToBePresent -File "$HOME/nativeCommandProcessor.Success" -TimeoutInSeconds 10 -IntervalInMilliseconds 100 - Get-Content $HOME/nativeCommandProcessor.Success | Should Be $TestFile + Wait-FileToBePresent -File "$HOME/nativeCommandProcessor.Success" -TimeoutInSeconds 10 -IntervalInMilliseconds 100 | Should -BeTrue + Get-Content $HOME/nativeCommandProcessor.Success | Should -BeExactly $TestFile } else { & $TestFile - Wait-FileToBePresent -File $TestDrive\foo.txt -TimeoutInSeconds 10 -IntervalInMilliseconds 100 - "$TestDrive\foo.txt" | Should Exist - Get-Content $TestDrive\foo.txt | Should BeExactly $TestFile + Wait-FileToBePresent -File $TestDrive\foo.txt -TimeoutInSeconds 10 -IntervalInMilliseconds 100 | Should -BeTrue + Get-Content $TestDrive\foo.txt | Should -BeExactly $TestFile } } It "Opening a file with an unregistered extension on Windows should fail" -Skip:(!$IsWindows) { - { $dllFile = "$PSHOME\System.Management.Automation.dll"; & $dllFile } | ShouldBeErrorId "NativeCommandFailed" + { $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/NativeLinuxCommands.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeLinuxCommands.Tests.ps1 index a333adb4091..b0c7ce4e53a 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeLinuxCommands.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeLinuxCommands.Tests.ps1 @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "NativeLinuxCommands" -tags "CI" { BeforeAll { $originalDefaultParams = $PSDefaultParameterValues.Clone() @@ -12,27 +14,27 @@ Describe "NativeLinuxCommands" -tags "CI" { } It "Should find Application grep" { - (get-command grep).CommandType | Should Be Application + (Get-Command grep).CommandType | Should -Be Application } It "Should pipe to grep and get result" { - "hello world" | grep hello | Should Be "hello world" + "hello world" | grep hello | Should -BeExactly "hello world" } It "Should find Application touch" { - (get-command touch).CommandType | Should Be Application + (Get-Command touch).CommandType | Should -Be Application } It "Should not redirect standard input if native command is the first command in pipeline (1)" { df | ForEach-Object -Begin { $out = @() } -Process { $out += $_ } - $out.Length -gt 0 | Should Be $true - $out[0] -like "Filesystem*Available*" | Should Be $true + $out.Length -gt 0 | Should -BeTrue + $out[0] -like "Filesystem*Available*" | Should -BeTrue } It "Should not redirect standard input if native command is the first command in pipeline (2)" { $out = df - $out.Length -gt 0 | Should Be $true - $out[0] -like "Filesystem*Available*" | Should Be $true + $out.Length -gt 0 | Should -BeTrue + $out[0] -like "Filesystem*Available*" | Should -BeTrue } It "Should find command before script with same name" { @@ -44,7 +46,7 @@ echo 'command' Set-Content "$TestDrive\foo.ps1" -Value @" 'script' "@ -Encoding Ascii - foo | Should BeExactly 'command' + foo | Should -BeExactly 'command' } } @@ -61,10 +63,10 @@ Describe "Scripts with extensions" -tags "CI" { } It "Should run a script with its full name" { - testScript.ps1 | Should Be $data + testScript.ps1 | Should -BeExactly $data } It "Should run a script with its short name" { - testScript | Should Be $data + testScript | Should -BeExactly $data } } diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 index f4aa0b5d994..53ed45fe4da 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 @@ -1,5 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe "Native streams behavior with PowerShell" -Tags 'CI' { - $powershell = Join-Path -Path $PsHome -ChildPath "pwsh" + BeforeAll { + $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh" + } Context "Error stream" { # we are using powershell itself as an example of a native program. @@ -8,57 +12,60 @@ 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' { - ($out | measure).Count | Should BeGreaterThan 1 + ($out | Measure-Object).Count | Should -BeGreaterThan 1 - $out[0] | Should BeOfType 'System.Management.Automation.ErrorRecord' - $out[0].FullyQualifiedErrorId | Should Be 'NativeCommandError' + $out[0] | Should -BeOfType System.Management.Automation.ErrorRecord + $out[0].FullyQualifiedErrorId | Should -Be 'NativeCommandError' $out | Select-Object -Skip 1 | ForEach-Object { - $_ | Should BeOfType 'System.Management.Automation.ErrorRecord' - $_.FullyQualifiedErrorId | Should Be 'NativeCommandErrorMessage' + $_ | Should -BeOfType System.Management.Automation.ErrorRecord + $_.FullyQualifiedErrorId | Should -Be 'NativeCommandErrorMessage' } } It 'uses correct exception messages for error stream' { - ($out | measure).Count | Should Be 9 - $out[0].Exception.Message | Should Be 'foo' - $out[1].Exception.Message | Should Be '' - $out[2].Exception.Message | Should Be 'bar' - $out[3].Exception.Message | Should Be '' - $out[4].Exception.Message | Should Be 'bazmiddlefoo' - $out[5].Exception.Message | Should Be '' - $out[6].Exception.Message | Should Be 'bar' - $out[7].Exception.Message | Should Be '' - $out[8].Exception.Message | Should Be 'baz' + ($out | Measure-Object).Count | Should -Be 9 + $out[0].Exception.Message | Should -BeExactly 'foo' + $out[1].Exception.Message | Should -BeExactly '' + $out[2].Exception.Message | Should -BeExactly 'bar' + $out[3].Exception.Message | Should -BeExactly '' + $out[4].Exception.Message | Should -BeExactly 'bazmiddlefoo' + $out[5].Exception.Message | Should -BeExactly '' + $out[6].Exception.Message | Should -BeExactly 'bar' + $out[7].Exception.Message | Should -BeExactly '' + $out[8].Exception.Message | Should -BeExactly 'baz' } It 'preserves error stream as is with Out-String' { - ($out | Out-String).Replace("`r", '') | Should Be "foo`n`nbar`n`nbazmiddlefoo`n`nbar`n`nbaz`n" + ($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 } - pwsh -c "& { [Console]::Error.WriteLine('$longtext') }" 2>&1 > $testdrive\error.txt + & $powershell -c "& { [Console]::Error.WriteLine('$longtext') }" 2>&1 > $testdrive\error.txt $e = Get-Content -Path $testdrive\error.txt - $e.Count | Should BeExactly 1 - $e | Should BeExactly $longtext + $e.Count | Should -Be 1 + $e | Should -BeExactly $longtext } } } @@ -69,6 +76,6 @@ Describe 'piping powershell objects to finished native executable' -Tags 'CI' { Start-Sleep -Milliseconds 100 # yield some multi-line formatted object @{'a' = 'b'} - } | testexe -echoargs | Should Be $null + } | testexe -echoargs | Should -BeNullOrEmpty } } diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1 index 1adeba86040..ae12076fed3 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeUnixGlobbing.Tests.ps1 @@ -1,8 +1,10 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. Describe 'Native UNIX globbing tests' -tags "CI" { BeforeAll { - if (-not $IsWindows ) + if (-Not $IsWindows ) { "" > "$TESTDRIVE/abc.txt" "" > "$TESTDRIVE/bbb.txt" @@ -19,37 +21,43 @@ Describe 'Native UNIX globbing tests' -tags "CI" { # Test * expansion It 'The globbing pattern *.txt should match 3 files' { - (/bin/ls $TESTDRIVE/*.txt).Length | Should Be 3 + (/bin/ls $TESTDRIVE/*.txt).Length | Should -Be 3 } It 'The globbing pattern *b.txt should match 2 files whose basenames end in "b"' { - (/bin/ls $TESTDRIVE/*b.txt).Length | Should Be 2 + (/bin/ls $TESTDRIVE/*b.txt).Length | Should -Be 2 } # Test character classes It 'The globbing pattern should match 2 files whose names start with either "a" or "b"' { - (/bin/ls $TESTDRIVE/[ab]*.txt).Length | Should Be 2 + (/bin/ls $TESTDRIVE/[ab]*.txt).Length | Should -Be 2 } It 'Globbing abc.* should return one file name "abc.txt"' { - /bin/ls $TESTDRIVE/abc.* | Should Match "abc.txt" + /bin/ls $TESTDRIVE/abc.* | Should -Match "abc.txt" } # Test that ? matches any single character It 'Globbing [cde]b?.* should return one file name "cbb.txt"' { - /bin/ls $TESTDRIVE/[cde]b?.* | Should Match "cbb.txt" + /bin/ls $TESTDRIVE/[cde]b?.* | Should -Match "cbb.txt" } # Test globbing with expressions It 'Globbing should work with unquoted expressions' { $v = "$TESTDRIVE/abc*" - /bin/ls $v | Should Match "abc.txt" + /bin/ls $v | Should -Match "abc.txt" $h = [pscustomobject]@{P=$v} - /bin/ls $h.P | Should Match "abc.txt" + /bin/ls $h.P | Should -Match "abc.txt" $a = $v,$v - /bin/ls $a[1] | Should Match "abc.txt" - } + /bin/ls $a[1] | Should -Match "abc.txt" + } + # Test globbing with absolute paths - it shouldn't turn absolute paths into relative paths (#7089) + It 'Should not normalize absolute paths' { + $Matches = /bin/echo /etc/* + # Matched path should start with '/etc/' not '../..' + $Matches.substring(0,5) | Should -Be '/etc/' + } It 'Globbing should not happen with quoted expressions' { $v = "$TESTDRIVE/abc*" - /bin/echo "$v" | Should BeExactly $v - /bin/echo '$v' | Should BeExactly '$v' + /bin/echo "$v" | Should -BeExactly $v + /bin/echo '$v' | Should -BeExactly '$v' } It 'Should return the original pattern () if there are no matches' -TestCases @( @{arg = '/nOSuCH*file'}, # No matching file @@ -63,7 +71,7 @@ Describe 'Native UNIX globbing tests' -tags "CI" { @{arg = '[]'} # Invalid wildcard ) { param($arg) - /bin/echo $arg | Should BeExactly $arg + /bin/echo $arg | Should -BeExactly $arg } $quoteTests = @( @{arg = '"*"'}, @@ -71,7 +79,7 @@ Describe 'Native UNIX globbing tests' -tags "CI" { ) It 'Should not expand quoted strings: ' -TestCases $quoteTests { param($arg) - Invoke-Expression "/bin/echo $arg" | Should BeExactly '*' + Invoke-Expression "/bin/echo $arg" | Should -BeExactly '*' } # Splat tests are skipped because they should work, but don't. # Supporting this scenario would require adding a NoteProperty @@ -84,7 +92,7 @@ Describe 'Native UNIX globbing tests' -tags "CI" { { /bin/echo @args } - Invoke-Expression "Invoke-Echo $arg" | Should BeExactly '*' + Invoke-Expression "Invoke-Echo $arg" | Should -BeExactly '*' } It 'Should not expand quoted strings via splat hash: ' -TestCases $quoteTests -Skip { param($arg) @@ -93,35 +101,35 @@ Describe 'Native UNIX globbing tests' -tags "CI" { { /bin/echo @PSBoundParameters } - Invoke-Expression "Invoke-Echo -quotedArg:$arg" | Should BeExactly "-quotedArg:*" + Invoke-Expression "Invoke-Echo -quotedArg:$arg" | Should -BeExactly "-quotedArg:*" # When specifing a space after the parameter, the space is removed when splatting. # This behavior is debatable, but it's worth adding this test anyway to detect # a change in behavior. - Invoke-Expression "Invoke-Echo -quotedArg: $arg" | Should BeExactly "-quotedArg:*" + Invoke-Expression "Invoke-Echo -quotedArg: $arg" | Should -BeExactly "-quotedArg:*" } # Test the behavior in non-filesystem drives It 'Should not expand patterns on non-filesystem drives' { - /bin/echo env:ps* | Should BeExactly "env:ps*" + /bin/echo env:ps* | Should -BeExactly "env:ps*" } # Test the behavior for files with spaces in the names It 'Globbing filenames with spaces should match 2 files' { "" > "$TESTDRIVE/foo bar.txt" "" > "$TESTDRIVE/foo baz.txt" - (/bin/ls $TESTDRIVE/foo*.txt).Length | Should Be 2 + (/bin/ls $TESTDRIVE/foo*.txt).Length | Should -Be 2 } # Test ~ expansion It 'Tilde should be replaced by the filesystem provider home directory' { - /bin/echo ~ | Should BeExactly ($executioncontext.SessionState.Provider.Get("FileSystem").Home) + /bin/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' { - /bin/echo ~/foo | Should BeExactly "$($executioncontext.SessionState.Provider.Get("FileSystem").Home)/foo" + /bin/echo ~/foo | Should -BeExactly "$($ExecutionContext.SessionState.Provider.Get("FileSystem").Home)/foo" } It '~ should not be replaced when quoted' { - /bin/echo '~' | Should BeExactly '~' - /bin/echo "~" | Should BeExactly '~' - /bin/echo '~/foo' | Should BeExactly '~/foo' - /bin/echo "~/foo" | Should BeExactly '~/foo' + /bin/echo '~' | Should -BeExactly '~' + /bin/echo "~" | Should -BeExactly '~' + /bin/echo '~/foo' | Should -BeExactly '~/foo' + /bin/echo "~/foo" | Should -BeExactly '~/foo' } } 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/OrderedAttributeForHashTables.Tests.ps1 b/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 index c70951e6b9f..71fd1f83bc2 100644 --- a/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 +++ b/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 @@ -1,22 +1,12 @@ -Describe 'Test for cmdlet to support Ordered Attribute on hash literal nodes' -Tags "CI" { - BeforeAll { - If (-not $IsCoreCLR) { - Get-WmiObject -Query "select * from win32_environment where name='TestWmiInstance'" | Remove-WmiObject - } - } - AfterAll { - If (-not $IsCoreCLR) { - Get-WmiObject -Query "select * from win32_environment where name='TestWmiInstance'" | Remove-WmiObject - } - } - +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe 'Test for cmdlet to support Ordered Attribute on hash literal nodes' -Tags "CI" { It 'New-Object - Property Parameter Must take IDictionary' { - $a = new-object psobject -property ([ordered]@{one=1;two=2}) - $a | Should Not Be $null - $a.one | Should Be 1 + $a = New-Object psobject -Property ([ordered]@{one=1;two=2}) + $a | Should -Not -BeNullOrEmpty + $a.one | Should -Be 1 } - Context 'Select-Xml cmdlet - Namespace parameter must take IDictionary' { $script:a = $null @@ -33,38 +23,51 @@ - '@ - { $script:a = select-xml -content $helpXml -xpath "//command:name" -namespace ( + { $script:a = Select-Xml -Content $helpXml -XPath "//command:name" -Namespace ( [ordered]@{command="http://schemas.microsoft.com/maml/dev/command/2004/10"; maml="http://schemas.microsoft.com/maml/2004/10"; - dev="http://schemas.microsoft.com/maml/dev/2004/10"}) } | Should Not Throw + dev="http://schemas.microsoft.com/maml/dev/2004/10"}) } | Should -Not -Throw - It '$a should not be $null' { $script:a | Should Not Be $null } + It '$a should not be $null' { $script:a | Should -Not -BeNullOrEmpty } } + Context 'New-CimInstance cmdlet' { + BeforeAll { + If ($IsWindows) { + Get-CimInstance -ClassName Win32_Environment -Filter "name='TestCimInstance'" | Remove-CimInstance + } + } + AfterAll { + If ($IsWindows) { + Get-CimInstance -ClassName Win32_Environment -Filter "name='TestCimInstance'" | Remove-CimInstance + } + } - It 'Set-WmiInstance cmdlet - Argument parameter must take IDictionary' -skip:$IsCoreCLR { + It 'Property parameter must take IDictionary' -Skip:(-not $IsWindows) { - $script:a = $null + $script:a = $null - { $script:a = set-wmiinstance -class win32_environment -argument ([ordered]@{Name="TestWmiInstance"; - VariableValue="testvalu234e"; - UserName=""}) } | Should Not Throw - $script:a | Should Not Be $null - $script:a.Name | Should Be "TestWmiInstance" + { $script:a = New-CimInstance -ClassName Win32_Environment -Property ([ordered]@{ + Name="TestCimInstance"; + VariableValue="testvalu234e"; + UserName=[System.Environment]::UserName + }) -ClientOnly } | Should -Not -Throw + $script:a | Should -Not -BeNullOrEmpty + $script:a.Name | Should -BeExactly "TestCimInstance" + } } Context 'Select-Object cmdlet - Property parameter (Calculated properties) must take IDictionary' { $script:a = $null - {$script:a = dir | select-object -property Name, ( + {$script:a = Get-ChildItem | Select-Object -Property Name, ( [ordered]@{Name="IsDirectory"; - Expression ={$_.PSIsContainer}})} | Should Not Throw + Expression ={$_.PSIsContainer}})} | Should -Not -Throw - It '$a should not be $null' { $script:a | Should Not Be $null } + It '$a should not be $null' { $script:a | Should -Not -BeNullOrEmpty } } } diff --git a/test/powershell/Language/Scripting/OutErrorVariable.Tests.ps1 b/test/powershell/Language/Scripting/OutErrorVariable.Tests.ps1 index e6e4d01e9a8..a49801996e9 100644 --- a/test/powershell/Language/Scripting/OutErrorVariable.Tests.ps1 +++ b/test/powershell/Language/Scripting/OutErrorVariable.Tests.ps1 @@ -1,4 +1,6 @@ -Describe "Tests OutVariable only" -Tags "CI" { +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe "Tests OutVariable only" -Tags "CI" { BeforeAll { function get-foo1 @@ -14,7 +16,7 @@ [CmdletBinding()] param() - $pscmdlet.writeobject("foo") + $PSCmdlet.writeobject("foo") } function get-bar @@ -23,7 +25,7 @@ param() "bar" - get-foo1 -outVariable script:a + get-foo1 -OutVariable script:a } } @@ -33,7 +35,7 @@ OutVariable = 'a'; Expected = 'foo' }, - @{ Name = 'Updating OutVariable Case 2: $pscmdlet.writeobject'; + @{ Name = 'Updating OutVariable Case 2: $PSCmdlet.writeobject'; Command = "get-foo2"; OutVariable = 'a'; Expected = 'foo' @@ -44,7 +46,7 @@ PreSet = 'a','b'; Expected = @("a", "b", "foo") }, - @{ Name = 'Appending OutVariable Case 2: $pscmdlet.writeobject'; + @{ Name = 'Appending OutVariable Case 2: $PSCmdlet.writeobject'; Command = "get-foo2"; OutVariable = 'a'; PreSet = 'a','b'; @@ -64,14 +66,14 @@ & $Command -OutVariable $OutVariable > $null } $a = Get-Variable -ValueOnly $OutVariable - $a | Should Be $Expected + $a | Should -BeExactly $Expected } It 'Nested OutVariable' { - get-bar -outVariable b > $null - $script:a | Should Be 'foo' - $b | Should Be @("bar", "foo") + get-bar -OutVariable b > $null + $script:a | Should -BeExactly 'foo' + $b | Should -BeExactly @("bar", "foo") } } @@ -82,7 +84,7 @@ Describe "Test ErrorVariable only" -Tags "CI" { [CmdletBinding()] param() - write-error "foo" + Write-Error "foo" } function get-foo2 @@ -90,7 +92,7 @@ Describe "Test ErrorVariable only" -Tags "CI" { [CmdletBinding()] param() - $pscmdlet.WriteError($script:foo[0]) + $PSCmdlet.WriteError($script:foo[0]) } function get-bar @@ -98,8 +100,8 @@ Describe "Test ErrorVariable only" -Tags "CI" { [CmdletBinding()] param() - write-error "bar" - get-foo1 -errorVariable script:a + Write-Error "bar" + get-foo1 -ErrorVariable script:a } } @@ -109,7 +111,7 @@ Describe "Test ErrorVariable only" -Tags "CI" { ErrorVariable = 'a'; Expected = 'foo' }, - @{ Name = 'Updating ErrorVariable Case 2: $pscmdlet.WriteError'; + @{ Name = 'Updating ErrorVariable Case 2: $PSCmdlet.WriteError'; Command = "get-foo1"; ErrorVariable = 'a'; Expected = 'foo' @@ -135,33 +137,33 @@ Describe "Test ErrorVariable only" -Tags "CI" { } $a = (Get-Variable -ValueOnly $ErrorVariable) | ForEach-Object {$_.ToString()} - $a | should be $Expected + $a | Should -BeExactly $Expected } - It 'Appending ErrorVariable Case 2: $pscmdlet.writeerror' { - write-error "foo" -errorVariable script:foo 2> $null + It 'Appending ErrorVariable Case 2: $PSCmdlet.writeerror' { + Write-Error "foo" -ErrorVariable script:foo 2> $null $a = 'a','b' - get-foo2 -errorVariable +a 2> $null + get-foo2 -ErrorVariable +a 2> $null - $a.count | Should Be 3 - $a| ForEach-Object {$_.ToString()} | Should Be @('a', 'b', 'foo') + $a.count | Should -Be 3 + $a| ForEach-Object {$_.ToString()} | Should -BeExactly @('a', 'b', 'foo') } It 'Nested ErrorVariable' { - get-bar -errorVariable b 2> $null + get-bar -ErrorVariable b 2> $null - $script:a | Should be 'foo' - $b | Should be @("bar","foo") + $script:a | Should -BeExactly 'foo' + $b | Should -BeExactly @("bar","foo") } It 'Nested ErrorVariable with redirection' { - get-bar -errorVariable b 2>&1 > $null + get-bar -ErrorVariable b 2>&1 > $null - $script:a | Should be 'foo' - $b | Should be @("bar", "foo") + $script:a | Should -BeExactly 'foo' + $b | Should -BeExactly @("bar", "foo") } } @@ -174,8 +176,8 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param() - write-output "foo-output" - write-error "foo-error" + Write-Output "foo-output" + Write-Error "foo-error" } function get-foo1 @@ -183,7 +185,7 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param() - write-error "foo" + Write-Error "foo" } function get-foo2 @@ -191,7 +193,7 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param() - $pscmdlet.WriteError($script:foo[0]) + $PSCmdlet.WriteError($script:foo[0]) } function get-bar @@ -199,8 +201,8 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param() - write-error "bar" - get-foo1 -errorVariable script:a + Write-Error "bar" + get-foo1 -ErrorVariable script:a } function get-foo3 @@ -209,8 +211,8 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { param() "foo-output-0" - write-output "foo-output-1" - write-error "foo-error" + Write-Output "foo-output-1" + Write-Error "foo-error" } function get-bar2 @@ -219,29 +221,29 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { param() "bar-output-0" - write-output "bar-output-1" - write-error "bar-error" - get-foo3 -OutVariable script:foo_out -errorVariable script:foo_err + Write-Output "bar-output-1" + Write-Error "bar-error" + get-foo3 -OutVariable script:foo_out -ErrorVariable script:foo_err } } It 'Update OutVariable and ErrorVariable' { - get-foo3 -OutVariable out -errorVariable err 2> $null > $null + get-foo3 -OutVariable out -ErrorVariable err 2> $null > $null - $out | Should be @("foo-output-0", "foo-output-1") - $err | Should be "foo-error" + $out | Should -BeExactly @("foo-output-0", "foo-output-1") + $err | Should -BeExactly "foo-error" } It 'Update OutVariable and ErrorVariable' { - get-bar2 -OutVariable script:bar_out -errorVariable script:bar_err 2> $null > $null + get-bar2 -OutVariable script:bar_out -ErrorVariable script:bar_err 2> $null > $null - $foo_out | Should be @("foo-output-0", "foo-output-1") - $foo_err | Should be 'foo-error' + $foo_out | Should -BeExactly @("foo-output-0", "foo-output-1") + $foo_err | Should -BeExactly 'foo-error' - $bar_out | Should be @("bar-output-0", "bar-output-1", "foo-output-0", "foo-output-1") - $bar_err | Should be @("bar-error", "foo-error") + $bar_out | Should -BeExactly @("bar-output-0", "bar-output-1", "foo-output-0", "foo-output-1") + $bar_err | Should -BeExactly @("bar-error", "foo-error") } It 'Verify that exceptions are added to the ErrorVariable' { @@ -250,7 +252,7 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param() - write-error "foo-error" + Write-Error "foo-error" try { @@ -260,9 +262,9 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { {} } - get-foo4 -errorVariable err 2> $null + get-foo4 -ErrorVariable err 2> $null - $err | Should be @("foo-error", "foo-exception") + $err | Should -BeExactly @("foo-error", "foo-exception") } It 'Error variable in multi-command pipeline' { @@ -273,35 +275,35 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { process { - write-output $foo - write-error $foo + Write-Output $foo + Write-Error $foo } } - (get-foo5 "foo-message" -ev foo_err1 -ov foo_out1 | get-foo5 -ev foo_err2 -ov foo_out2 | get-foo5 -ev foo_err3 -ov foo_out3) 2>&1 > $null + (get-foo5 "foo-message" -ErrorVariable foo_err1 -ov foo_out1 | get-foo5 -ErrorVariable foo_err2 -ov foo_out2 | get-foo5 -ErrorVariable foo_err3 -ov foo_out3) 2>&1 > $null - $foo_out1 | Should be "foo-message" - $foo_out2 | Should be "foo-message" - $foo_out3 | Should be "foo-message" - $foo_err1 | Should be "foo-message" - $foo_err2 | Should be "foo-message" - $foo_err3 | Should be "foo-message" + $foo_out1 | Should -BeExactly "foo-message" + $foo_out2 | Should -BeExactly "foo-message" + $foo_out3 | Should -BeExactly "foo-message" + $foo_err1 | Should -BeExactly "foo-message" + $foo_err2 | Should -BeExactly "foo-message" + $foo_err3 | Should -BeExactly "foo-message" } Context 'Error variable in multi-command pipeline (with native cmdlet)' { BeforeAll { - (get-foo -ev foo_err | get-item -ev get_item_err ) 2>&1 > $null + (get-foo -ErrorVariable foo_err | Get-Item -ErrorVariable get_item_err ) 2>&1 > $null } It '$foo_err should be "foo-error"' { - $foo_err | Should Be "foo-error" + $foo_err | Should -BeExactly "foo-error" } It '$get_item_err.count and $get_item_err[0].exception' { - $get_item_err.count | Should Be 1 - $get_item_err[0].exception | Should Not BeNullOrEmpty - $get_item_err[0].exception | Should BeOftype 'System.Management.Automation.ItemNotFoundException' + $get_item_err.count | Should -Be 1 + $get_item_err[0].exception | Should -Not -BeNullOrEmpty + $get_item_err[0].exception | Should -BeOfType System.Management.Automation.ItemNotFoundException } } @@ -312,15 +314,15 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param([Parameter(ValueFromPipeline = $true)][string] $i) - write-error 'bar-error' - write-output 'bar-output' + Write-Error 'bar-error' + Write-Output 'bar-output' get-foo } - (get-foo -errorVariable foo_err | get-bar3 -errorVariable bar_err) 2>&1 > $null + (get-foo -ErrorVariable foo_err | get-bar3 -ErrorVariable bar_err) 2>&1 > $null - $foo_err | Should be 'foo-error' - $bar_err | Should be @("bar-error", "foo-error") + $foo_err | Should -BeExactly 'foo-error' + $bar_err | Should -BeExactly @("bar-error", "foo-error") } It 'multi-command pipeline with nested commands' { @@ -330,8 +332,8 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param([Parameter(ValueFromPipeline = $true)][string] $i) - write-error "foo-error" - write-output $i + Write-Error "foo-error" + Write-Output $i } function get-bar4 @@ -339,15 +341,15 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { [CmdletBinding()] param([Parameter(ValueFromPipeline = $true)][string] $i) - write-error "bar-error" - get-foo6 "foo-output" -errorVariable script:foo_err1 | get-foo6 -errorVariable script:foo_err2 + Write-Error "bar-error" + get-foo6 "foo-output" -ErrorVariable script:foo_err1 | get-foo6 -ErrorVariable script:foo_err2 } - get-bar4 -errorVariable script:bar_err 2>&1 > $null + get-bar4 -ErrorVariable script:bar_err 2>&1 > $null - $script:foo_err1 | Should be "foo-error" - $script:foo_err2 | Should be "foo-error" - $script:bar_err | Should be @("bar-error", "foo-error") + $script:foo_err1 | Should -BeExactly "foo-error" + $script:foo_err2 | Should -BeExactly "foo-error" + $script:bar_err | Should -BeExactly @("bar-error", "foo-error") } It 'Nested output variables' { @@ -357,7 +359,7 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { param([Parameter(ValueFromPipeline = $true)][string] $output) $output - write-error "foo-error" + Write-Error "foo-error" } function get-bar5 @@ -366,28 +368,27 @@ Describe "Update both OutVariable and ErrorVariable" -Tags "CI" { param() "bar-output" - write-error "bar-error" - get-foo7 "foo-output" -ev script:foo_err1 -ov script:foo_out1 | get-foo7 -ev script:foo_err2 -ov script:foo_out2 - get-foo7 "foo-output" -ev script:foo_err3 -ov script:foo_out3 | get-foo7 -ev script:foo_err4 -ov script:foo_out4 + Write-Error "bar-error" + get-foo7 "foo-output" -ErrorVariable script:foo_err1 -ov script:foo_out1 | get-foo7 -ErrorVariable script:foo_err2 -ov script:foo_out2 + get-foo7 "foo-output" -ErrorVariable script:foo_err3 -ov script:foo_out3 | get-foo7 -ErrorVariable script:foo_err4 -ov script:foo_out4 } + get-bar5 -ErrorVariable script:bar_err -ov script:bar_out 2>&1 > $null - get-bar5 -ev script:bar_err -ov script:bar_out 2>&1 > $null + $script:foo_out1 | Should -BeExactly "foo-output" + $script:foo_err1 | Should -BeExactly "foo-error" - $script:foo_out1 | Should be "foo-output" - $script:foo_err1 | Should be "foo-error" + $script:foo_out2 | Should -BeExactly "foo-output" + $script:foo_err2 | Should -BeExactly "foo-error" - $script:foo_out2 | Should be "foo-output" - $script:foo_err2 | Should be "foo-error" + $script:foo_out3 | Should -BeExactly "foo-output" + $script:foo_err3 | Should -BeExactly "foo-error" - $script:foo_out3 | Should be "foo-output" - $script:foo_err3 | Should be "foo-error" + $script:foo_out4 | Should -BeExactly "foo-output" + $script:foo_err4 | Should -BeExactly "foo-error" - $script:foo_out4 | Should be "foo-output" - $script:foo_err4 | Should be "foo-error" - - $script:bar_out | Should be @("bar-output", "foo-output", "foo-output") - $script:bar_err | Should be @("bar-error", "foo-error", "foo-error") + $script:bar_out | Should -BeExactly @("bar-output", "foo-output", "foo-output") + $script:bar_err | Should -BeExactly @("bar-error", "foo-error", "foo-error") } } diff --git a/test/powershell/Language/Scripting/PSSerializer.Tests.ps1 b/test/powershell/Language/Scripting/PSSerializer.Tests.ps1 new file mode 100644 index 00000000000..6771642c14b --- /dev/null +++ b/test/powershell/Language/Scripting/PSSerializer.Tests.ps1 @@ -0,0 +1,52 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Tests for lossless rehydration of serialized types.' -Tags 'CI' { + BeforeAll { + $cmdBp = Set-PSBreakpoint -Command Get-Process + $varBp = Set-PSBreakpoint -Variable ? + $lineBp = Set-PSBreakpoint -Script $PSScriptRoot/PSSerializer.Tests.ps1 -Line 1 + + function ShouldRehydrateLosslessly { + [CmdletBinding()] + param( + [Parameter(Mandatory, ValueFromPipeline)] + [ValidateNotNull()] + [System.Management.Automation.Breakpoint] + $Breakpoint + ) + $dehydratedBp = [System.Management.Automation.PSSerializer]::Serialize($Breakpoint) + $rehydratedBp = [System.Management.Automation.PSSerializer]::Deserialize($dehydratedBp) + foreach ($property in $Breakpoint.PSObject.Properties) { + $bpValue = $Breakpoint.$($property.Name) + $rehydratedBpValue = $rehydratedBp.$($property.Name) + $propertyType = $property.TypeNameOfValue -as [System.Type] + if ($null -eq $bpValue) { + $rehydratedBpValue | Should -Be $null + } elseif ($propertyType.IsValueType) { + $bpValue | Should -Be $rehydratedBpValue + } elseif ($propertyType -eq [string]) { + $bpValue | Should -BeExactly $rehydratedBpValue + } else { + $bpValue.ToString() | Should -BeExactly $rehydratedBpValue.ToString() + } + } + } + } + + AfterAll { + Remove-PSBreakpoint -Breakpoint $cmdBp,$varBp,$lineBp + } + + It 'Losslessly rehydrates command breakpoints' { + $cmdBp | ShouldRehydrateLosslessly + } + + It 'Losslessly rehydrates variable breakpoints' { + $varBp | ShouldRehydrateLosslessly + } + + It 'Losslessly rehydrates line breakpoints' { + $lineBp | ShouldRehydrateLosslessly + } +} diff --git a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 index 2fbf79f845f..8177adab219 100644 --- a/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 +++ b/test/powershell/Language/Scripting/ParameterBinding.Tests.ps1 @@ -1,4 +1,6 @@ -Describe "Tests for parameter binding" -Tags "CI" { +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe "Tests for parameter binding" -Tags "CI" { Context 'Test of Mandatory parameters' { BeforeAll { $f = "function get-foo { param([Parameter(mandatory=`$true)] `$a) `$a };" @@ -16,8 +18,8 @@ $asyncResult = $ps.BeginInvoke() $ps.EndInvoke($asyncResult) - $ps.Streams.Error.Count | Should Be 1 # the host does not implement it. - $ps.InvocationStateInfo.State | Should Be 'Completed' + $ps.Streams.Error.Count | Should -Be 1 # the host does not implement it. + $ps.InvocationStateInfo.State | Should -BeExactly 'Completed' } finally { $ps.Dispose() $rs.Dispose() @@ -35,10 +37,10 @@ { $ps.AddScript($f + "get-foo").invoke() $prompt = $th.ui.streams.prompt[0] - $prompt | should Not BeNullOrEmpty + $prompt | Should -Not -BeNullOrEmpty $result = $prompt.split(":") - $result[0] | Should Match 'get-foo' - $result[-1] | should be 'a' + $result[0] | Should -Match 'get-foo' + $result[-1] | Should -BeExactly 'a' } finally { $rs.Close() $rs.Dispose() @@ -55,8 +57,8 @@ $a } - get-foo a | Should Be a - get-foo -a b | Should Be b + get-foo a | Should -BeExactly 'a' + get-foo -a b | Should -BeExactly 'b' } It 'Positional parameters when only one position specified: position = 1' { @@ -66,7 +68,7 @@ $a } - get-foo b | Should Be b + get-foo b | Should -BeExactly 'b' } It 'Positional parameters when only position specified: position = 2' { @@ -76,7 +78,7 @@ $a } - get-foo b | Should Be b + get-foo b | Should -BeExactly 'b' } It 'Multiple positional parameters case 1' { @@ -87,11 +89,11 @@ $a; $b } - ( get-foo c d ) -join ',' | Should Be 'c,d' - ( get-foo -a c d ) -join ',' | Should Be 'c,d' - ( get-foo -a c -b d ) -join ',' | Should Be 'c,d' - ( get-foo -b d c ) -join ',' | Should Be 'c,d' - ( get-foo c -b d ) -join ',' | Should Be 'c,d' + ( get-foo c d ) -join ',' | Should -BeExactly 'c,d' + ( get-foo -a c d ) -join ',' | Should -BeExactly 'c,d' + ( get-foo -a c -b d ) -join ',' | Should -BeExactly 'c,d' + ( get-foo -b d c ) -join ',' | Should -BeExactly 'c,d' + ( get-foo c -b d ) -join ',' | Should -BeExactly 'c,d' } It 'Multiple positional parameters case 2: the parameters are put in different order?' { @@ -103,7 +105,7 @@ $a; $b } - (get-foo c d) -join ',' | Should Be 'd,c' + (get-foo c d) -join ',' | Should -BeExactly 'd,c' } It 'Value from pipeline' { @@ -119,7 +121,7 @@ } } - (1..10 | get-foo) -join ',' | Should Be '2,4,6,8,10' + (1..10 | get-foo) -join ',' | Should -BeExactly '2,4,6,8,10' } It 'Value from pipeline by property name' { @@ -135,8 +137,8 @@ } } - $b = 1..10 | select-object @{name='foo'; expression={$_ * 10}} | get-foo - $b -join ',' | Should Be '10,20,30,40,50,60,70,80,90,100' + $b = 1..10 | Select-Object @{name='foo'; expression={$_ * 10}} | get-foo + $b -join ',' | Should -BeExactly '10,20,30,40,50,60,70,80,90,100' } It 'Value from remaining arguments' { @@ -149,9 +151,9 @@ $foo } - ( get-foo a b c d ) -join ',' | Should Be 'b,c,d' - ( get-foo a b -a c d ) -join ',' | Should Be 'a,b,d' - ( get-foo a b -a c -q d ) -join ',' | Should Be 'a,b,-q,d' + ( get-foo a b c d ) -join ',' | Should -BeExactly 'b,c,d' + ( get-foo a b -a c d ) -join ',' | Should -BeExactly 'a,b,d' + ( get-foo a b -a c -q d ) -join ',' | Should -BeExactly 'a,b,-q,d' } It 'Multiple parameter sets with Value from remaining arguments' { @@ -163,9 +165,22 @@ $foo } - { get-foo -a a -b b c d } | ShouldBeErrorId 'AmbiguousParameterSet,get-foo' - ( get-foo -a a b c d ) -join ',' | Should Be 'b,c,d' - ( get-foo -b b a c d ) -join ',' | Should Be 'a,c,d' + { get-foo -a a -b b c d } | Should -Throw -ErrorId 'AmbiguousParameterSet,get-foo' + ( get-foo -a a b c d ) -join ',' | Should -BeExactly 'b,c,d' + ( 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' { @@ -179,9 +194,9 @@ } $x,$y,$z=get-foo a b c d - $x | Should Be a - $y | Should Be $null - $z -join ',' | Should Be 'b,c,d' + $x | Should -BeExactly 'a' + $y | Should -BeNullOrEmpty + $z -join ',' | Should -BeExactly 'b,c,d' } It 'Default parameter set with value from remaining argument case 2' { @@ -196,9 +211,9 @@ $x,$y,$z=get-foo a b c d - $x | Should Be $null - $y | Should Be 'a' - $z -join ',' | Should Be 'b,c,d' + $x | Should -BeNullOrEmpty + $y | Should -BeExactly 'a' + $z -join ',' | Should -BeExactly 'b,c,d' } It 'Alias are specified for parameters' { @@ -208,17 +223,17 @@ $a } - get-foo -foo b | Should Be 'b' + get-foo -foo b | Should -BeExactly 'b' } It 'Invoking with script block' { $foo = . { param([Parameter(position=2)] $a, [Parameter(position=1)]$b); $a; $b} a b - $foo[0] | Should Be b + $foo[0] | Should -BeExactly 'b' } It 'Normal functions' { function foo ($a, $b) {$b, $a} - ( foo a b ) -join ',' | Should Be 'b,a' + ( foo a b ) -join ',' | Should -BeExactly 'b,a' } It 'Null is not Allowed when AllowNull attribute is not set' { @@ -228,7 +243,7 @@ $a } - { get-foo -a $null } | ShouldBeErrorId 'ParameterArgumentValidationErrorNullNotAllowed,get-foo' + { get-foo -a $null } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorNullNotAllowed,get-foo' } @@ -239,7 +254,7 @@ $a } - (get-foo -a $null) | Should Be $null + (get-foo -a $null) | Should -BeNullOrEmpty } @@ -250,7 +265,7 @@ $a } - { get-foo -a '' } | ShouldBeErrorID 'ParameterArgumentValidationErrorEmptyStringNotAllowed,get-foo' + { get-foo -a '' } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorEmptyStringNotAllowed,get-foo' } It 'Empty string is allowed when AllowEmptyString Attribute is set' { @@ -260,7 +275,7 @@ $a } - get-foo -a '' | Should Be '' + get-foo -a '' | Should -BeExactly '' } It 'Empty collection is not allowed when AllowEmptyCollection it not set' { @@ -270,7 +285,7 @@ $a } - { get-foo -a @() } | ShouldBeErrorId 'ParameterArgumentValidationErrorEmptyArrayNotAllowed,get-foo' + { get-foo -a @() } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorEmptyArrayNotAllowed,get-foo' } It 'Empty collection is allowed when allowEmptyCollection is set' { @@ -280,7 +295,7 @@ $a } - get-foo -a @() | Should Be $null + get-foo -a @() | Should -BeNullOrEmpty } It 'Unspecified non-mandatory bool should not cause exception' { @@ -291,7 +306,7 @@ $a } - 42 | get-foo | Should be 42 + 42 | get-foo | Should -Be 42 } It 'Parameter binding failure on Parameter1 should not cause parameter binding failure on Length' { @@ -302,7 +317,7 @@ process { $Length } } - 'abc' | get-foo | Should Be 3 + 'abc' | get-foo | Should -Be 3 } It 'Binding array of string to array of bool should fail (cmdletbinding)' { @@ -313,7 +328,7 @@ $Parameter } - { get-foo 'a','b' } | ShouldBeErrorId 'ParameterArgumentTransformationError,get-foo' + { get-foo 'a','b' } | Should -Throw -ErrorId 'ParameterArgumentTransformationError,get-foo' } It "Binding array of string to array of bool should succeed" { @@ -324,8 +339,8 @@ } $x = get-foo 'a','b' - $x[0] | Should be $true - $x[1] | Should be $true + $x[0] | Should -BeTrue + $x[1] | Should -BeTrue } Context 'Default value conversion tests' { @@ -333,7 +348,7 @@ function get-fooa { param( [System.Reflection.MemberTypes] $memberTypes = $([Enum]::GetNames("System.Reflection.MemberTypes") -join ",") ) - $memberTypes | Should BeOfType System.Reflection.MemberTypes + $memberTypes | Should -BeOfType System.Reflection.MemberTypes } get-fooa @@ -344,7 +359,7 @@ { [CmdletBinding()] param( [System.Reflection.MemberTypes] $memberTypes = $([Enum]::GetNames("System.Reflection.MemberTypes") -join ",") ) - $memberTypes | Should BeOfType System.Reflection.MemberTypes + $memberTypes | Should -BeOfType System.Reflection.MemberTypes } get-foob @@ -354,18 +369,17 @@ function get-fooc { param( [Parameter()] [System.Reflection.MemberTypes] $memberTypes ) - $memberTypes | Should Be $null + $memberTypes | Should -BeNullOrEmpty } get-fooc } - It "No default value specified should not cause error when nothing is set on parameter" { function get-food { param( [System.Reflection.MemberTypes] $memberTypes ) - $memberTypes | Should Be $null + $memberTypes | Should -BeNullOrEmpty } get-food @@ -378,7 +392,7 @@ $p } - get-fooe| Should Be 55 + get-fooe | Should -Be 55 } It "Validation attributes should not run on default values when CmdletBinding is set on the parameter" { @@ -389,7 +403,7 @@ $p } - get-foof| Should Be 55 + get-foof | Should -Be 55 } It "Validation attributes should not run on default values" { @@ -399,7 +413,7 @@ $p } - { get-foog } | Should not throw + { get-foog } | Should -Not -Throw } It "Validation attributes should not run on default values when CmdletBinding is set" { @@ -410,7 +424,7 @@ $p } - { get-fooh } | Should not throw + { get-fooh } | Should -Not -Throw } It "ValidateScript can use custom ErrorMessage" { @@ -419,16 +433,9 @@ param([ValidateScript({$_ -gt 2}, ErrorMessage = "Item '{0}' failed '{1}' validation")] $p) $p } - $errMsg = '' - try - { - get-fooi -p 2 - } - catch - { - $errMsg = $_.Exception.Message - } - $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' failed '`$_ -gt 2' validation" + + $err = { get-fooi -p 2 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,get-fooi' -PassThru + $err.Exception.Message | Should -BeExactly "Cannot validate argument on parameter 'p'. Item '2' failed '`$_ -gt 2' validation" } It "ValidatePattern can use custom ErrorMessage" { @@ -438,16 +445,9 @@ param([ValidatePattern("\s+", ErrorMessage = "Item '{0}' failed '{1}' regex")] $p) $p } - $errMsg = '' - try - { - get-fooj -p 2 - } - catch - { - $errMsg = $_.Exception.Message - } - $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' failed '\s+' regex" + + $err = { get-fooj -p 2 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,get-fooj' -PassThru + $err.Exception.Message | Should -BeExactly "Cannot validate argument on parameter 'p'. Item '2' failed '\s+' regex" } It "ValidateSet can use custom ErrorMessage" { @@ -455,28 +455,21 @@ { param([ValidateSet('A', 'B', 'C', IgnoreCase=$false, ErrorMessage="Item '{0}' is not in '{1}'")] $p) } - $errMsg = '' - try - { - get-fook -p 2 - } - catch - { - $errMsg = $_.Exception.Message - } + + $err = { get-fook -p 2 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,get-fook' -PassThru $set = 'A','B','C' -join [Globalization.CultureInfo]::CurrentUICulture.TextInfo.ListSeparator - $errMsg | Should Be "Cannot validate argument on parameter 'p'. Item '2' is not in '$set'" + $err.Exception.Message | Should -BeExactly "Cannot validate argument on parameter 'p'. Item '2' is not in '$set'" } } #known issue 2069 - It 'Some conversions should be attempted before trying to encode a collection' -skip:$IsCoreCLR { + It 'Some conversions should be attempted before trying to encode a collection' -Skip:$IsCoreCLR { try { $null = [Test.Language.ParameterBinding.MyClass] } catch { - add-type -PassThru -TypeDefinition @' + Add-Type -PassThru -TypeDefinition @' using System.Management.Automation; using System; using System.Collections; @@ -506,10 +499,10 @@ } } } -'@ | ForEach-Object {$_.assembly} | Import-module +'@ | ForEach-Object {$_.assembly} | Import-Module } - Get-TestCmdlet -MyParameter @{ a = 42 } | Should Be 'hashtable' + Get-TestCmdlet -MyParameter @{ a = 42 } | Should -BeExactly 'hashtable' } It 'Parameter pasing is consuming enumerators' { @@ -520,6 +513,6 @@ & { } $b #The position of the enumerator shouldn't be modified - $b.current |Should Be 1 + $b.current | Should -Be 1 } } 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: